Skills
Skill注册无内存态
Skill 必须以 SKILL.md 文件形式落在引擎会扫描的目录里。
Options.skills的'all'语义是 "enable every discovered skill"Query.reloadSkills()是 "Reload skills from disk"- SessionStart 钩子的
reloadSkills?: boolean是 "Re-scan skill and command directories after SessionStart hooks complete, so skills installed by the hook are available in the same session"〔sdk.d.ts:4775-4777〕 - 运行中还存在 "skills discovered dynamically as the agent works in a subdirectory" 的命令列表推送
Options.skills 是「过滤器」
skills?: string[] | 'all';
'all':启用所有已发现的 skill。skills: ['pdf']不是「只加载 pdf」,而是「把其它已发现 skill 从模型可见列表里隐藏」。未被列出的 skill 文件仍在磁盘,Agent 照样能用Read/Bash读到。因此skills选项不能当安全边界。string[]:只启用列出的;名字匹配SKILL.md的name/ 目录名,或插件限定名plugin:skill〔sdk.d.ts:1919-1920〕。
Skill注册
| 机制 | 作用 | 出处 |
|---|---|---|
plugins: [{type:'local', path}] |
把任意路径的目录作为插件加载,引擎从中读 skills/hooks/agents/commands;plugin:skill 限定名即来源于此 |
〔sdk.d.ts:1757-1771, 1919-1920〕 |
additionalDirectories: string[] + 运行中 add-dir 请求(reload_skills?: boolean) |
把 cwd 之外的目录注册为工作根,并可顺带重扫 skills/plugins/CLAUDE.md | 〔sdk.d.ts:1328-1332, 3665-3672〕 |
CLAUDE_CONFIG_DIR(env)+ cwd(Options) |
决定「用户级 skill 目录」与「项目级 skill 目录」的基准;多租户下随这两个值按用户隔离 | 〔sdk.d.ts:1387-1389〕 |
- (A) 打包成 local plugin,用
plugins:[{type:'local', path:<应用控制的绝对路径>}]指过去;- (B) 放在每用户
cwd下的.claude/skills/(项目级,随 cwd 隔离);- (C) 放在
CLAUDE_CONFIG_DIR下的 skills 目录(用户级,随 CLAUDE_CONFIG_DIR 隔离)。
与
settingSources的关系:settingSources:[]是「不读文件系统设置」,与 skill 发现是两回事--skill 发现本身仍依赖磁盘扫描。要让 skill 可用,至少要让文件落在上面某条路径上。
skill 列表会进系统提示,有预算上限,超了会被截断/缩短〔sdk.d.ts:4998-5004〕:
skillListingMaxDescChars(默认 1536):单条 skill 描述字符上限。skillListingBudgetFraction(默认 0.01 = 上下文窗口的 1%):整个 skill 列表占的字符预算比例。
Subagent 的 Skill
AgentDefinition.skills?: string[] 是 "preload into the agent context"〔sdk.d.ts:65-67〕。匹配规则不同:主会话 Options.skills 只认 canonical name 或 :name 后缀(不认 display name / alias);子代理 AgentDefinition.skills 额外解析 display name 和 alias〔sdk.d.ts:3432〕。两者别混用。
Human in the Loop
interrupt() 等控制方法只在 prompt 传 AsyncIterable<SDKUserMessage>(流式输入)时生效。传字符串 prompt 时,这些方法无效。
| 方法 | 语义 | 后果 | 出处 |
|---|---|---|---|
interrupt() |
优雅中断当前轮,查询仍存活、可继续投喂 | 返回收据 {still_queued, cancelled?};老 CLI 返回 undefined |
〔sdk.d.ts:2293, 3485-3494〕 |
close() |
强制终止底层进程,清理所有资源 | 之后不再收到任何消息 | 〔sdk.d.ts:2576-2584〕 |
abortController |
取消查询并清理资源 | 与 close 类似但走 AbortSignal 机制 |
〔sdk.d.ts:1324-1327〕 |
选择:想「停下来等用户下一步」用
interrupt();想「彻底结束、销毁进程」用close()(或abortController.abort())。
interrupt() 的返回值与能力探测
interrupt(): Promise<SDKControlInterruptResponse | undefined>;
type SDKControlInterruptResponse = {
still_queued: string[]; // 仍会执行的异步消息 uuid
cancelled?: string[]; // 仅当 cancel_queued:true 时出现
};
[]≠「没有东西会跑」。只有带 uuid 的主线程消息才会被列出;无 uuid 的消息仍会执行但不出现。子代理(subagent)消息不在覆盖范围内。- 列表里可能出现客户端从未发送的 uuid(cron 触发、auto-resume 续跑等内部入队)--遇到未知 uuid 应忽略,而非当错误。
- 快照与 abort 同步取证:想在 interrupted result 之后再 probe 队列,永远输给 drain loop(它立刻开始下一条排队轮次)。要拿准确快照,用
interrupt()的返回值,不要事后查。
cancel_queued 与 cancel_async_message
declare type SDKControlInterruptRequest = {
subtype: 'interrupt';
cancel_queued?: boolean; // true = 连同队列里的命令一起取消,still_queued 恒为 []
};
declare type SDKControlCancelAsyncMessageRequest = {
subtype: 'cancel_async_message';
message_uuid: string; // 按 uuid 单独撤掉一条排队消息
};
继续对话
中断后「继续」= 往输入流里再 yield SDKUserMessage。关键字段〔sdk.d.ts:4583-4605〕:
type SDKUserMessage = {
type: 'user';
message: MessageParam;
parent_tool_use_id: string | null;
shouldQuery?: boolean; // false = 只入栈不触发轮次
priority?: 'now' | 'next' | 'later';
isSynthetic?: boolean;
// ...
};
shouldQuery(核心):"When false, the message is appended to the transcript without triggering an assistant turn. It will be merged into the next user message that does query."〔sdk.d.ts:4596-4599〕- 省略/
true:触发一轮 assistant 回复。 false:只把内容追加到 transcript,合并进下一条会触发轮次的消息。适合「先塞一段上下文,再问问题」。
- 省略/
priority?: 'now' | 'next' | 'later':控制消息调度(.d.ts未给显式散文释义,按取值:now=立即、next=下轮、later=排队)〔sdk.d.ts:4592〕。isSynthetic:标记合成的(非用户直发)消息〔sdk.d.ts:4587〕。
区分「会话内中断/继续」与「跨进程恢复」
| 场景 | 机制 | 出处 |
|---|---|---|
| 同一进程内,停下来再继续 | interrupt() + 流式投喂 SDKUserMessage |
本文 §15 |
| 进程重启后,接着上次的会话 | resume / continue / sessionId + forkSession |
§3.4 |
两者正交:interrupt 不落盘新 session,resume 依赖已落盘的 transcript(persistSession:true,默认)。
检测被中断的 assistant 消息
type SDKAssistantMessage = {
type: 'assistant';
// ...
aborted?: true; // 中断/abort 在流完成前截断时为 true
};
"True when this assistant message was truncated by an interrupt/abort before the stream completed: stop_reason was never received and the content may end mid-word."〔sdk.d.ts:2871-2873〕
边界:
aborted:true的消息内容可能停在半句话,且stop_reason从未到达。渲染前端时要标识「被中断」,别当完整回复处理。
区分「会话内中断/继续」与「跨进程恢复」
| 场景 | 机制 | 出处 |
|---|---|---|
| 同一进程内,停下来再继续 | interrupt() + 流式投喂 SDKUserMessage |
本文 §15 |
| 进程重启后,接着上次的会话 | resume / continue / sessionId + forkSession |
§3.4 |
两者正交:interrupt 不落盘新 session,resume 依赖已落盘的 transcript(persistSession:true,默认)。
端到端 HITL 模式(示例)
import { query, type SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
// 1) 流式输入:prompt 是 AsyncIterable,控制方法才生效
async function* userInput() {
yield {
type: "user",
message: { role: "user", content: "先分析一下这段代码" },
parent_tool_use_id: null,
} satisfies SDKUserMessage;
// 2) 用户中途点了「停」-> 外部调 q.interrupt()(见 onStop)
await userClickedStop();
// 3) 注入一条「不触发轮次」的上下文
yield {
type: "user",
message: { role: "user", content: "(附加上下文:项目用 Next.js)" },
parent_tool_use_id: null,
shouldQuery: false, // 只入栈,不触发 assistant 轮次
} satisfies SDKUserMessage;
// 4) 真正继续:触发新一轮
yield {
type: "user",
message: { role: "user", content: "基于上面上下文,给出重构方案" },
parent_tool_use_id: null,
} satisfies SDKUserMessage;
}
const q = query({ prompt: userInput(), options: { /* … */ } });
// 响应用户的「停止」按钮
async function onStop() {
const receipt = await q.interrupt(); // 优雅中断
if (receipt) {
console.log("仍会执行的消息 uuid:", receipt.still_queued);
// 注意:本版本公开 API 无法逐条取消这些 uuid(§15.5)
}
}
for await (const msg of q) {
if (msg.type === "assistant" && msg.aborted) {
console.warn("上一轮被中断,内容可能不完整");
} else if (msg.type === "result") {
// 一轮结束
}
}
其他Feature
startup() + WarmQuery:预热子进程,消灭冷启动
export declare function startup(_params?: {
options?: Options; initializeTimeoutMs?: number;
}): Promise<WarmQuery>;
"Pre-warms the CLI subprocess so the first
query()resolves immediately."
WarmQuery 是「子进程已 spawn 且已完成 initialize 握手」的句柄,warm.query(prompt) 直接写入就绪进程、无启动延迟〔sdk.d.ts:7112-7123〕。
边界:每个 WarmQuery 只能 query() 一次;不用了 close() 丢弃。要做连接池得自己维护多个 WarmQuery。
生产意义:交互式场景下
query()每次 spawn + 握手的首字延迟明显。startup()是官方给的延迟优化路径。
fallbackModel:主模型过载降级链
fallbackModel?: string; // 逗号分隔,按序尝试
"Fallback model(s) to use if the primary model is overloaded or unavailable... The primary model is re-tried at the start of each user turn, so a temporary outage doesn't permanently demote the session."〔sdk.d.ts:1469-1475〕
主模型临时 429/503 时自动切备模型,且每轮重试主模型(不永久降级)。高可用必备。
strictMcpConfig:只认显式传入的 MCP server
strictMcpConfig?: boolean; // = --strict-mcp-config
"Only use MCP servers passed via the
mcpServersoption (and servers declared by explicitly-passed agent definitions inagents), ignoring all other MCP configurations: project.mcp.json, user settings, plugins, and on-disk agent frontmatter"〔sdk.d.ts:1952-1959〕
沙箱关键开关:不设它,项目里的 .mcp.json 会注入你不知道的 MCP server,破坏隔离。一旦加 MCP,务必开此开关。
enableFileCheckpointing + Query.rewindFiles():文件回滚
enableFileCheckpointing?: boolean;
rewindFiles(userMessageId, { dryRun? }): Promise<RewindFilesResult>;
启用后文件改动被 checkpoint,可回滚到任一用户消息时的状态;
dryRun可预览〔sdk.d.ts:1476-1484, 2486-2488〕。agentic coding 的「撤销」语义。
plugins:打包分发 skills+hooks+agents+commands
plugins?: SdkPluginConfig[]; // 目前只支持 { type:'local', path }
"Plugins provide custom commands, agents, skills, and hooks"〔sdk.d.ts:1757-1771〕。
skipMcpDiscovery?: boolean可加载插件里的 skills/hooks/agents/commands 但不读其.mcp.json--当 MCP 由宿主自管时用〔sdk.d.ts:4206-4208〕。
agentProgressSummaries:长任务进度摘要
agentProgressSummaries?: boolean; // 默认 false
每 ~30s fork 子代理会话产出一句现在时进度(如 "Analyzing authentication module"),走
task_progress事件的summary字段;fork 复用 prompt cache,成本极低;前后台子代理均适用〔sdk.d.ts:1790-1799〕。
forwardSubagentText:转发子代理完整对话
forwardSubagentText?: boolean;
默认只转发 tool_use/tool_result(够做心跳计数);开启后转发完整 text+thinking,可在前端渲染嵌套会话〔sdk.d.ts:1632-1638〕。
promptSuggestions:下一句提示建议
promptSuggestions?: boolean;
每轮后发一条
prompt_suggestion消息。在result之后到达,必须继续迭代流才能收到;首轮/plan 模式/API 错误时抑制;搭便车走父级 prompt cache,几乎免费〔sdk.d.ts:1776-1789〕。
边界:很多人在
result就break--开了这个就收不到建议。
abortController:显式取消与清理
abortController?: AbortController;
"When aborted, the query will stop and clean up resources."〔sdk.d.ts:1324-1327〕。官方的取消+清理入口(与 §15 的
interrupt()/close()互补)。
stderr / extraArgs / debugFile:调试与逃生舱
stderr?: (data: string) => void:捕获子进程 stderr〔sdk.d.ts:1947-1951〕。extraArgs?: Record<string, string | null>:透传任意 CLI flag(null= 布尔 flag)〔sdk.d.ts:1463-1468〕。Options 未覆盖的 CLI 能力可由此注入。debug?+debugFile?:写调试日志到文件/stderr〔sdk.d.ts:1935-1946〕。
动态 MCP 管理:setMcpServers / reconnectMcpServer / toggleMcpServer
setMcpServers(servers):运行中替换动态 MCP server 集合;setMcpServers({})清空动态 server,但插件拥有的 server 会被保留〔sdk.d.ts:2529-2550〕。reconnectMcpServer(name):按名重连,失败抛错〔sdk.d.ts:2507-2513〕。toggleMcpServer(name, enabled):启用/禁用〔sdk.d.ts:2514-2521〕。
backgroundTasks / stopTask:后台任务管理
backgroundTasks(toolUseId?):把前台任务转后台(等价终端 Ctrl+B);带toolUseId只转指定任务,不带则全部转后台〔sdk.d.ts:2563-2575〕。stopTask(taskId):停止运行中的后台任务,发task_notificationstatus'stopped'〔sdk.d.ts:2558-2562〕。