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.mdname / 目录名,或插件限定名 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() 等控制方法只在 promptAsyncIterable<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 时出现
};
  1. [] ≠「没有东西会跑」。只有带 uuid 的主线程消息才会被列出;无 uuid 的消息仍会执行但不出现。子代理(subagent)消息不在覆盖范围内。
  2. 列表里可能出现客户端从未发送的 uuid(cron 触发、auto-resume 续跑等内部入队)--遇到未知 uuid 应忽略,而非当错误。
  3. 快照与 abort 同步取证:想在 interrupted result 之后再 probe 队列,永远输给 drain loop(它立刻开始下一条排队轮次)。要拿准确快照,用 interrupt() 的返回值,不要事后查。

cancel_queuedcancel_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 mcpServers option (and servers declared by explicitly-passed agent definitions in agents), 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〕。

边界:很多人在 resultbreak--开了这个就收不到建议。

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_notification status 'stopped'〔sdk.d.ts:2558-2562〕。