mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-01-31 19:37:45 +01:00
fix: inject skills prompt list
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
- Config: support inline env vars in config (`env.*` / `env.vars`) and document env precedence.
|
||||
- Agent: enable adaptive context pruning by default for tool-result trimming.
|
||||
- Agent: drop empty error assistant messages when sanitizing session history. (#591) — thanks @steipete
|
||||
- Agent: inject eligible skills list into the system prompt so bundled skills load from their actual locations. (#551) — thanks @gabriel-trigo
|
||||
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
|
||||
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
||||
- Doctor: repair gateway service entrypoint when switching between npm and git installs; add Docker e2e coverage. — thanks @steipete
|
||||
|
||||
@@ -49,10 +49,19 @@ Use `agents.defaults.userTimezone` in `~/.clawdbot/clawdbot.json` to change the
|
||||
|
||||
## Skills
|
||||
|
||||
Skills are **not** auto-injected. Instead, the prompt instructs the model to use `read` to load skill instructions on demand:
|
||||
When eligible skills exist, Clawdbot injects a compact **available skills list**
|
||||
(`formatSkillsForPrompt`) that includes the **file path** for each skill. The
|
||||
prompt instructs the model to use `read` to load the SKILL.md at the listed
|
||||
location (workspace, managed, or bundled).
|
||||
|
||||
```
|
||||
<workspace>/skills/<name>/SKILL.md
|
||||
<available_skills>
|
||||
<skill>
|
||||
<name>...</name>
|
||||
<description>...</description>
|
||||
<location>...</location>
|
||||
</skill>
|
||||
</available_skills>
|
||||
```
|
||||
|
||||
This keeps the base prompt small while still enabling targeted skill usage.
|
||||
|
||||
@@ -6,9 +6,11 @@ import {
|
||||
applyGoogleTurnOrderingFix,
|
||||
buildEmbeddedSandboxInfo,
|
||||
createSystemPromptOverride,
|
||||
resolveSkillsPrompt,
|
||||
splitSdkTools,
|
||||
} from "./pi-embedded-runner.js";
|
||||
import type { SandboxContext } from "./sandbox.js";
|
||||
import type { SkillEntry } from "./skills.js";
|
||||
|
||||
describe("buildEmbeddedSandboxInfo", () => {
|
||||
it("returns undefined when sandbox is missing", () => {
|
||||
@@ -122,6 +124,35 @@ describe("createSystemPromptOverride", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSkillsPrompt", () => {
|
||||
it("prefers snapshot prompt when available", () => {
|
||||
const prompt = resolveSkillsPrompt({
|
||||
skillsSnapshot: { prompt: "SNAPSHOT", skills: [] },
|
||||
workspaceDir: "/tmp/clawd",
|
||||
});
|
||||
expect(prompt).toBe("SNAPSHOT");
|
||||
});
|
||||
|
||||
it("builds prompt from entries when snapshot is missing", () => {
|
||||
const entry: SkillEntry = {
|
||||
skill: {
|
||||
name: "demo-skill",
|
||||
description: "Demo",
|
||||
filePath: "/app/skills/demo-skill/SKILL.md",
|
||||
baseDir: "/app/skills/demo-skill",
|
||||
source: "clawdbot-bundled",
|
||||
},
|
||||
frontmatter: {},
|
||||
};
|
||||
const prompt = resolveSkillsPrompt({
|
||||
skillEntries: [entry],
|
||||
workspaceDir: "/tmp/clawd",
|
||||
});
|
||||
expect(prompt).toContain("<available_skills>");
|
||||
expect(prompt).toContain("/app/skills/demo-skill/SKILL.md");
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyGoogleTurnOrderingFix", () => {
|
||||
const makeAssistantFirst = () =>
|
||||
[
|
||||
|
||||
@@ -89,7 +89,9 @@ import { resolveSandboxContext } from "./sandbox.js";
|
||||
import {
|
||||
applySkillEnvOverrides,
|
||||
applySkillEnvOverridesFromSnapshot,
|
||||
buildWorkspaceSkillsPrompt,
|
||||
loadWorkspaceSkillEntries,
|
||||
type SkillEntry,
|
||||
type SkillSnapshot,
|
||||
} from "./skills.js";
|
||||
import { buildAgentSystemPrompt } from "./system-prompt.js";
|
||||
@@ -578,6 +580,7 @@ function buildEmbeddedSystemPrompt(params: {
|
||||
ownerNumbers?: string[];
|
||||
reasoningTagHint: boolean;
|
||||
heartbeatPrompt?: string;
|
||||
skillsPrompt?: string;
|
||||
runtimeInfo: {
|
||||
host: string;
|
||||
os: string;
|
||||
@@ -601,6 +604,7 @@ function buildEmbeddedSystemPrompt(params: {
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
reasoningTagHint: params.reasoningTagHint,
|
||||
heartbeatPrompt: params.heartbeatPrompt,
|
||||
skillsPrompt: params.skillsPrompt,
|
||||
runtimeInfo: params.runtimeInfo,
|
||||
sandboxInfo: params.sandboxInfo,
|
||||
toolNames: params.tools.map((tool) => tool.name),
|
||||
@@ -618,6 +622,24 @@ export function createSystemPromptOverride(
|
||||
return () => trimmed;
|
||||
}
|
||||
|
||||
export function resolveSkillsPrompt(params: {
|
||||
skillsSnapshot?: SkillSnapshot;
|
||||
skillEntries?: SkillEntry[];
|
||||
config?: ClawdbotConfig;
|
||||
workspaceDir: string;
|
||||
}): string {
|
||||
const snapshotPrompt = params.skillsSnapshot?.prompt?.trim();
|
||||
if (snapshotPrompt) return snapshotPrompt;
|
||||
if (params.skillEntries && params.skillEntries.length > 0) {
|
||||
const prompt = buildWorkspaceSkillsPrompt(params.workspaceDir, {
|
||||
entries: params.skillEntries,
|
||||
config: params.config,
|
||||
});
|
||||
return prompt.trim() ? prompt : "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Tool names are now capitalized (Bash, Read, Write, Edit) to bypass Anthropic's
|
||||
// OAuth token blocking of lowercase names. However, pi-coding-agent's SDK has
|
||||
// hardcoded lowercase names in its built-in tool registry, so we must pass ALL
|
||||
@@ -844,6 +866,12 @@ export async function compactEmbeddedPiSession(params: {
|
||||
skills: skillEntries ?? [],
|
||||
config: params.config,
|
||||
});
|
||||
const skillsPrompt = resolveSkillsPrompt({
|
||||
skillsSnapshot: params.skillsSnapshot,
|
||||
skillEntries: shouldLoadSkillEntries ? skillEntries : undefined,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
});
|
||||
|
||||
const bootstrapFiles =
|
||||
await loadWorkspaceBootstrapFiles(effectiveWorkspace);
|
||||
@@ -895,6 +923,7 @@ export async function compactEmbeddedPiSession(params: {
|
||||
heartbeatPrompt: resolveHeartbeatPrompt(
|
||||
params.config?.agents?.defaults?.heartbeat?.prompt,
|
||||
),
|
||||
skillsPrompt,
|
||||
runtimeInfo,
|
||||
sandboxInfo,
|
||||
tools,
|
||||
@@ -1167,6 +1196,12 @@ export async function runEmbeddedPiAgent(params: {
|
||||
skills: skillEntries ?? [],
|
||||
config: params.config,
|
||||
});
|
||||
const skillsPrompt = resolveSkillsPrompt({
|
||||
skillsSnapshot: params.skillsSnapshot,
|
||||
skillEntries: shouldLoadSkillEntries ? skillEntries : undefined,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
});
|
||||
|
||||
const bootstrapFiles =
|
||||
await loadWorkspaceBootstrapFiles(effectiveWorkspace);
|
||||
@@ -1208,6 +1243,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
heartbeatPrompt: resolveHeartbeatPrompt(
|
||||
params.config?.agents?.defaults?.heartbeat?.prompt,
|
||||
),
|
||||
skillsPrompt,
|
||||
runtimeInfo,
|
||||
sandboxInfo,
|
||||
tools,
|
||||
|
||||
@@ -90,10 +90,21 @@ describe("buildAgentSystemPrompt", () => {
|
||||
|
||||
expect(prompt).toContain("## Skills");
|
||||
expect(prompt).toContain(
|
||||
"Use `read` to load from /tmp/clawd/skills/<name>/SKILL.md",
|
||||
"Use `read` to load the SKILL.md at the location listed for that skill.",
|
||||
);
|
||||
});
|
||||
|
||||
it("appends available skills when provided", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/clawd",
|
||||
skillsPrompt:
|
||||
"<available_skills>\n <skill>\n <name>demo</name>\n </skill>\n</available_skills>",
|
||||
});
|
||||
|
||||
expect(prompt).toContain("<available_skills>");
|
||||
expect(prompt).toContain("<name>demo</name>");
|
||||
});
|
||||
|
||||
it("renders project context files when provided", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/clawd",
|
||||
|
||||
@@ -12,6 +12,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
userTimezone?: string;
|
||||
userTime?: string;
|
||||
contextFiles?: EmbeddedContextFile[];
|
||||
skillsPrompt?: string;
|
||||
heartbeatPrompt?: string;
|
||||
runtimeInfo?: {
|
||||
host?: string;
|
||||
@@ -121,6 +122,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
: undefined;
|
||||
const userTimezone = params.userTimezone?.trim();
|
||||
const userTime = params.userTime?.trim();
|
||||
const skillsPrompt = params.skillsPrompt?.trim();
|
||||
const heartbeatPrompt = params.heartbeatPrompt?.trim();
|
||||
const heartbeatPromptLine = heartbeatPrompt
|
||||
? `Heartbeat prompt: ${heartbeatPrompt}`
|
||||
@@ -136,6 +138,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
const telegramInlineButtonsEnabled =
|
||||
runtimeProvider === "telegram" &&
|
||||
runtimeCapabilitiesLower.has("inlinebuttons");
|
||||
const skillsLines = skillsPrompt ? [skillsPrompt, ""] : [];
|
||||
|
||||
const lines = [
|
||||
"You are a personal assistant running inside Clawdbot.",
|
||||
@@ -164,7 +167,8 @@ export function buildAgentSystemPrompt(params: {
|
||||
"If a task is more complex or takes longer, spawn a sub-agent. It will do the work for you and ping you when it's done. You can always check up on it.",
|
||||
"",
|
||||
"## Skills",
|
||||
`Skills provide task-specific instructions. Use \`read\` to load from ${params.workspaceDir}/skills/<name>/SKILL.md when needed.`,
|
||||
"Skills provide task-specific instructions. Use `read` to load the SKILL.md at the location listed for that skill.",
|
||||
...skillsLines,
|
||||
"",
|
||||
hasGateway ? "## Clawdbot Self-Update" : "",
|
||||
hasGateway
|
||||
|
||||
Reference in New Issue
Block a user