@ai-zen/agents-sdk 1.0.0-alpha.1 → 1.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +15 -14
  3. package/README.zh.md +15 -14
  4. package/dist/agent-plugins/AutoMigratePlugin.d.ts +33 -7
  5. package/dist/agent-plugins/AutoMigratePlugin.js +20 -6
  6. package/dist/agent-plugins/ContextGuardPlugin.d.ts +13 -3
  7. package/dist/agent-plugins/ContextGuardPlugin.js +9 -6
  8. package/dist/config/ConfigManager.d.ts +12 -1
  9. package/dist/config/ConfigManager.js +40 -1
  10. package/dist/config/constants.d.ts +3 -1
  11. package/dist/config/constants.js +10 -2
  12. package/dist/config/factory-skills.d.ts +33 -0
  13. package/dist/config/factory-skills.js +126 -0
  14. package/dist/index.d.ts +6 -4
  15. package/dist/index.js +5 -2
  16. package/dist/runtime/HandoffGuardPlugin.d.ts +35 -0
  17. package/dist/runtime/HandoffGuardPlugin.js +65 -0
  18. package/dist/runtime/HandoffService.d.ts +199 -0
  19. package/dist/runtime/HandoffService.js +372 -0
  20. package/dist/scope/plugins/builtin/BatchEditTool.js +1 -1
  21. package/dist/scope/plugins/builtin/DownloadFileTool.js +1 -1
  22. package/dist/scope/plugins/builtin/ExecTool.js +2 -2
  23. package/dist/scope/plugins/builtin/FindTextTool.js +6 -6
  24. package/dist/scope/plugins/builtin/GenerateImageTool.d.ts +1 -1
  25. package/dist/scope/plugins/builtin/GenerateImageTool.js +7 -7
  26. package/dist/scope/plugins/builtin/GlobTool.js +1 -1
  27. package/dist/scope/plugins/builtin/InspectFileTool.d.ts +2 -2
  28. package/dist/scope/plugins/builtin/InspectFileTool.js +5 -5
  29. package/dist/scope/plugins/builtin/LsTool.js +1 -1
  30. package/dist/scope/plugins/builtin/{ReadFileTool.d.ts → ReadTool.d.ts} +1 -1
  31. package/dist/scope/plugins/builtin/{ReadFileTool.js → ReadTool.js} +3 -3
  32. package/dist/scope/plugins/builtin/{ExecAsyncTool.d.ts → SpawnTool.d.ts} +1 -1
  33. package/dist/scope/plugins/builtin/{ExecAsyncTool.js → SpawnTool.js} +2 -2
  34. package/dist/scope/plugins/builtin/ViewImageTool.js +2 -2
  35. package/dist/scope/plugins/builtin/{WriteFileTool.d.ts → WriteTool.d.ts} +1 -1
  36. package/dist/scope/plugins/builtin/{WriteFileTool.js → WriteTool.js} +2 -2
  37. package/dist/scope/plugins/builtin/index.d.ts +4 -4
  38. package/dist/scope/plugins/builtin/index.js +7 -7
  39. package/dist/scope/plugins/builtin/outputGuard.d.ts +2 -2
  40. package/dist/scope/plugins/builtin/outputGuard.js +1 -1
  41. package/dist/types/index.d.ts +3 -3
  42. package/package.json +3 -3
  43. package/dist/runtime/TaskMigrationService.d.ts +0 -128
  44. package/dist/runtime/TaskMigrationService.js +0 -211
package/CHANGELOG.md CHANGED
@@ -1,5 +1,132 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-alpha.10] - 2026-10-20
4
+
5
+ ### 💥 Breaking Changes
6
+
7
+ - **`TaskMigrationService` → `HandoffService`,并拆成 `handoff()` / `migrate()` 两层** —— 「交接」(写交接文档)与「迁移」(交接 + 开新会话)此前混在一个方法里,现在概念与 API 一一对应:
8
+ - `handoff(params)`:停工 → 核实 → 写文档 → 校验(失败降级)。**不改动会话**;
9
+ - `migrate(params)`:`handoff()` 之后按 `strategy` 清历史并注入断点消息。
10
+ - 类型随迁:`MigrationContext` → `HandoffContext`(新增 `mode` / `requestedMode` / `handoffPath` / `migrating`),`TaskMigrationServiceOptions` → `HandoffServiceOptions`(新增 `handoffDir` / `mode` / `skillDirs` / `minHandoffBytes`)。
11
+ - 钩子重命名:`onBeforeMigrate` → `onBeforeHandoff`,新增 `onHandoff`(文档写好、清历史之前)。
12
+ - **交接文档改由「原 agent 在自己会话里写」(`mode: "self"`,默认)**,而非另起一次独立的摘要调用。原实现把历史序列化成 `[role]: content` 后交给一个无工具、无原会话上下文的调用,工具名与调用参数在序列化时全部丢失,写文档的一方也无法核实分支/改动/测试状态,只能靠推演——这是文档质量差的根因。现在的 `self` 回合:原 agent 保留全部上下文与工具,可 `read` / `exec` / `git status` 核实后落盘。
13
+ - **`summary`(旧的独立摘要)保留为逃生模式**,并作为 `self` 失败时的**单向**降级目标(无 `write` 工具 / 文件缺失或过小(重试一次)/ 工具或护栏报错)。反向永不发生——那会在上下文最满的时刻再加一次全量请求。
14
+ - **`AutoMigrateOptions` 新增 `resolveMode`**,把「用谁交接」从插件继承转移到回调:返回 `HandoffMode` 或 `null`(跳过本次迁移)。原 CLI 的 `AutoMigrateConfirmPlugin` 子类因此退化为纯回调。
15
+
16
+ ### ✨ 新增
17
+
18
+ - **`HandoffGuardPlugin`**:交接回合的一次性工具守卫。只放行 `write` 到交接文档(绝对路径归一后比较),拒绝 `edit` / `batch_edit` / `rm` / `rename` / `mkdir` / `copy`。拒绝返回字符串(软拒绝)而非抛错——抛错会被 core 标记为 Error + `isPreventDefault`,整轮交接直接死。边界如实记录:挡不住 `exec`,它降低误操作概率,不是沙箱。
19
+ - **`ContextGuardPlugin` 新增 `isSuspended` / `suspendedRatio`**:交接回合(`service.busy`)把硬上限从 `× 1.5` 放宽到 `× 2`,既不被误杀又保留熔断。
20
+ - **出厂 Skill 机制**:`ConfigManager.ensureFactorySkills()` 在 `bootstrap()` 中释放 / 同步 `skills/handoff/SKILL.md`。覆盖策略与 `syncManagedModels` 同构——frontmatter 标 `metadata.custom: "true"` 视为用户自有、永不覆盖;否则随版本同步。内容以字符串常量内联在 `config/factory-skills.ts`(`tsc` 直接编译,无需打包非 TS 资源)。
21
+ - **交接模板的唯一真相源改为 Skill**:`HANDOFF_SECTIONS` / `createPrompt()` 已删除,代码只保留「读 Skill → 注入 → 校验文件 → 读不到时用 `FALLBACK_HANDOFF_TEMPLATE`」。Skill 只讲「怎么写」,代码只讲「写到哪、有什么能力」(本体系无模板变量引擎,不发明占位符)。
22
+ - `HandoffService.createHandoffFileName()`(`<YYYYMMDD-HHmmss>-<会话id前8位>.md`)、`createHandoffInstruction()`、`createPostMessages(handoffPath)`(断点**只给路径**,由接手方 `read`)。
23
+
24
+ ### 🔧 修复
25
+
26
+ - **交接触发的无限递归**:交接回合本身是一次 `send()`,其 `onAfterSend` 会再次进入 `AutoMigratePlugin`。现以 `service.busy` 让行(`handoff()` / `migrate()` 共用一把锁,顺带拒绝重入)。
27
+ - **交接回合被自己触发/被护栏击杀**:交接恰好发生在上下文最满的时刻。除 `busy` 让行外,交接回合开跑前清空 `agent.lastUsage`(纵深防御),并由调用方在迁移后清空(否则护栏拿交接回合用量误杀新会话第一条消息)。
28
+
29
+ ### ✅ 测试
30
+
31
+ - 新增 `HandoffService.test.ts`(24 例)、`HandoffGuardPlugin.test.ts`(7 例)、`factory-skills.test.ts`(7 例);重写 `AutoMigratePlugin.test.ts`、`ContextGuardPlugin.test.ts`(新增 `isSuspended` 4 例)。全量 **54 文件 / 525 用例**通过,`tsc --noEmit` 与 `build` 零错误。
32
+
33
+ ### 📄 Docs
34
+
35
+ - `docs/sdk-design-v1.md` §18 重写为「任务交接(HandoffService)」(两层语义、Template 单一真相源、两种执行者、自交接三道保险、接口、断点只给路径);§17 插件章节、目录树与消费示例同步。
36
+ - `README.md` / `README.zh.md`、`../docs/{en,zh}/sdk.md`、`architecture.md`、`getting-started.md` 及网站镜像同步。
37
+
38
+
39
+ ## [1.0.0-alpha.9] - 2026-10-14
40
+
41
+ ### ⚠️ Dependency
42
+
43
+ - **`@ai-zen/agents-core` bumped to `^4.5.1`** — docs-only patch; no SDK source change.
44
+
45
+ ### 📄 Docs
46
+
47
+ - Corrected the factory-defaults description in `docs/sdk-design-v1.md`: the preset **model count is 8** (was 7), and `DEFAULT_MCP_CONFIG` ships **two** servers (`socket-pty` + `chrome-devtools`).
48
+
49
+ ## [1.0.0-alpha.8] - 2026-10-14
50
+
51
+ ### ⚠️ Dependency
52
+
53
+ - **`@ai-zen/agents-core` bumped to `^4.5.0`** — picks up `AgentNS.Message.subAgentUsage` and the `sumUsage()` primitive. The skill sub-agent tool (`call_skill_sub_agent`) is built on `AgentToolLazy`, so it inherits the delegation-boundary stamping with no SDK source change.
54
+
55
+ ## [1.0.0-alpha.7] - 2026-10-14
56
+
57
+ ### ⚠️ Dependency
58
+
59
+ - **`@ai-zen/agents-core` bumped to `^4.4.0`** — picks up `AgentNS.Message.usage` (per-message token usage, stamped from the final stream chunk). No SDK source change; the `workspace:^` range resolves to `^4.4.0` on publish.
60
+
61
+ ## [1.0.0-alpha.6] - 2026-10-13
62
+
63
+ ### 💥 Breaking Changes
64
+
65
+ - **`exec_async` renamed to `spawn`** — the tool wraps `child_process.spawn` and returns `{ success, pid, message }`, so the new name matches both the implementation and the "start a process, get its pid" contract. It also pairs cleanly with `exec`: `exec` runs and **waits** (captures output, has a `timeout`), `spawn` starts and **returns immediately**. Permission lists, skill `allowed-tools` and any prompt naming `exec_async` must switch to `spawn`. The behaviour is otherwise unchanged — still shell-parsed, still does **not** capture output (redirect to a file if you need it).
66
+ - **Built-in tool classes renamed to match their new tool names** — `ReadFileTool` → `ReadTool`, `WriteFileTool` → `WriteTool`, `ExecAsyncTool` → `SpawnTool`, with the source files renamed to match. The classes re-exported from `@ai-zen/agents-sdk` change accordingly, so consumers importing them by name must update.
67
+
68
+ ## [1.0.0-alpha.5] - 2026-10-13
69
+
70
+ ### 💥 Breaking Changes
71
+
72
+ - **All built-in tool names unified on `snake_case`** — the multi-word tools that used camelCase were renamed. A tool name is the API contract the model sees and is matched **verbatim** by the permission system, so any `permissions.allow` / `deny` list, skill `allowed-tools` entry or prompt that names a tool must be updated:
73
+
74
+ | old | new |
75
+ |---|---|
76
+ | `readFile` | `read` |
77
+ | `writeFile` | `write` |
78
+ | `inspectFile` | `inspect_file` |
79
+ | `findText` | `find_text` |
80
+ | `batchEdit` | `batch_edit` |
81
+ | `downloadFile` | `download_file` |
82
+ | `generateImage` | `generate_image` |
83
+ | `viewImage` | `view_image` |
84
+
85
+ Single-word tools (`cwd`, `edit`, `glob`, `ls`, `mkdir`, `rm`, `rename`, `copy`, `exec`, `exist`, `sleep`), the already-`snake_case` `exec_async`, the MCP / skill wrapper tools and core's `indexed_search` are unchanged. Tool descriptions and docs were updated in the same pass.
86
+
87
+ ### 🔧 Changed
88
+
89
+ - **Dependency `@ai-zen/agents-core` bumped to `^4.3.2`** (carries the `indexedSearch` → `indexed_search` rename).
90
+
91
+ ### 📄 Docs
92
+
93
+ - Current-version references in `docs/zh` and `docs/en` updated to `1.0.0-alpha.5`.
94
+
95
+ ## [1.0.0-alpha.4] - 2026-10-13
96
+
97
+ ### 🔧 Changed
98
+
99
+ - **Dependency `@ai-zen/agents-core` bumped to `^4.3.1`** (declared as `workspace:^`, expanded to `^4.3.1` at publish time). Pulls in core 4.3.1's fix for interrupted parallel tool rounds — the assistant message carrying `tool_calls` and its results now stay all-in or all-out, so an interrupted parallel tool call no longer leaves orphan `tool` results that the server rejects. `AgentContext` gains `abortedToolRoundStrategy` (default `"fill"`) and `abortedToolRoundReason`; set `agent.abortedToolRoundStrategy = "drop"` to drop the whole round instead
100
+
101
+ ### 📄 Docs
102
+
103
+ - Current-version references in `docs/zh` and `docs/en` updated to `1.0.0-alpha.4`.
104
+
105
+ ## [1.0.0-alpha.3] - 2026-10-03
106
+
107
+ ### 🔧 Fixed
108
+
109
+ - **`generateImage` `model` parameter description no longer points at a host-CLI command** — the description used to read "配置中的图片模型可通过 'aiz config show' 查看" (the configured image models can be viewed via `aiz config show`). Since tool descriptions ship inside every request's `tools` payload, that hint propagated into **every layer** of the agent stack: the model would `exec("aiz config show")`, but the CLI resolves a bare `aiz config show` invocation as stdio mode with the instruction `config show` — i.e. a **brand-new agent** — whose own `generateImage` description repeats the very same hint, producing a self-referential recursion that keeps spawning nested agents. The parameter now reads "若指定的模型不存在,本工具会返回当前可用的模型列表" (if the specified model does not exist, this tool returns the list of currently available models), which matches the actual `!imageModel` branch in `call()` and stops the model from shelling out to the host CLI.
110
+
111
+ ### 📄 Docs
112
+
113
+ - Current-version references in `docs/zh` and `docs/en` updated to `1.0.0-alpha.3`.
114
+
115
+ ## [1.0.0-alpha.2] - 2026-10-03
116
+
117
+ ### 🚀 New Features
118
+
119
+ - **`chrome-devtools` added to the factory default MCP config** — `DEFAULT_MCP_CONFIG` now ships two servers instead of one: alongside `socket-pty`, a freshly released `~/.ai-zen/mcp.json` includes `chrome-devtools` (`npx -y chrome-devtools-mcp@latest`, Google's official `chrome-devtools-mcp`), so browser debugging and automation work out of the box — page navigation, performance traces, network and console inspection, screenshots. Existing `mcp.json` files are still never overwritten, so user configurations (including custom flags such as `--executablePath`) are untouched. Defined in `packages/sdk/src/config/constants.ts`, released by `ConfigManager.ensureDefaultMcpConfig()`.
120
+
121
+ ### ✅ Tests
122
+
123
+ - `ConfigManager.bootstrap.test.ts` now asserts the factory-default `mcp.json` carries both `socket-pty` and `chrome-devtools` (the latter resolved from the `chrome-devtools-mcp` package via `npx`), covering both the `bootstrap()` and `ensureDefaultMcpConfig()` paths.
124
+
125
+ ### 📄 Docs
126
+
127
+ - Factory-default MCP descriptions in `docs/zh/mcp.md` / `docs/en/mcp.md` and `docs/zh/sdk.md` / `docs/en/sdk.md` now list both `socket-pty` and `chrome-devtools`.
128
+ - Current-version references in `docs/zh` and `docs/en` updated to `1.0.0-alpha.2`.
129
+
3
130
  ## [1.0.0-alpha.1] - 2026-10-03
4
131
 
5
132
  ### 💥 Breaking Changes
package/README.md CHANGED
@@ -21,7 +21,7 @@ Desktop ──┘ │
21
21
  types ← pure types, zero business dependencies (incl. ToolEnv)
22
22
  config ← ConfigManager + constants: config.json read/write + directory setup + factory defaults
23
23
  crud ← capability-entity CRUD (Agent definitions; conversations/drafts are persisted by each consumer)
24
- runtime ← model factory + Agent assembly + task migration (createModel / createAgent / SdkAgent / TaskMigrationService)
24
+ runtime ← model factory + Agent assembly + task handoff/migration (createModel / createAgent / SdkAgent / HandoffService / HandoffGuardPlugin)
25
25
  scope ← Scope orchestration container + ScopePlugin contract + PermissionEvaluator / disclosure + plugins/ (5 self-contained capability sources: discovery + tools + plugin)
26
26
  agent-plugins ← Agent plugins (autoMigrate, autoRefreshTools, contextGuard, unknownToolHint)
27
27
  shared ← logging, errors
@@ -68,8 +68,9 @@ const scope = new Scope({
68
68
  await scope.init();
69
69
 
70
70
  const agent = await createAgent(scope, "my-agent");
71
- const migrationService = new TaskMigrationService({ onMigrated }); // migrates via the agent's own model client
72
- agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
71
+ const handoffService = new HandoffService({ handoffDir: "/work/.ai-zen/handoffs", skillDirs });
72
+ agent.use(new ContextGuardPlugin({ maxTokens, isSuspended: () => handoffService.busy }));
73
+ agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens }));
73
74
  agent.use(new AutoRefreshToolsPlugin());
74
75
  agent.use(new UnknownToolHintPlugin({ scope }));
75
76
  await agent.init();
@@ -97,21 +98,21 @@ All built-in tools are classes (extending `SdkCallbackTool`), instantiated by th
97
98
  | Tool | Description |
98
99
  |------|-------------|
99
100
  | `cwd` | Get the current working directory |
100
- | `readFile` | Read a file (optional `range` for line/column slicing; oversized output warns and suggests batched reads) |
101
- | `inspectFile` | Inspect a file's structural overview (line count, column distribution, etc.); streamed scan, not limited by the 300KB read threshold |
102
- | `writeFile` | Write a file |
101
+ | `read` | Read a file (optional `range` for line/column slicing; oversized output warns and suggests batched reads) |
102
+ | `inspect_file` | Inspect a file's structural overview (line count, column distribution, etc.); streamed scan, not limited by the 300KB read threshold |
103
+ | `write` | Write a file |
103
104
  | `exec` | Execute a command (supports `timeout`; oversized stdout+stderr is dumped to `stdout.log` / `stderr.log`) |
104
- | `exec_async` | Execute a command asynchronously, returns immediately without waiting |
105
+ | `spawn` | Execute a command asynchronously, returns immediately without waiting |
105
106
  | `mkdir` | Create a directory |
106
107
  | `rm` | Delete a file or directory |
107
108
  | `glob` | Scan and find files using glob patterns (oversized output dumped to `result.json`) |
108
109
  | `ls` | List directory contents (oversized output dumped to `result.json`) |
109
110
  | `exist` | Check whether a file or directory exists |
110
- | `findText` | Search for text or regex in files (oversized output dumped to `result.json`) |
111
- | `downloadFile` | Download a file from a URL and save it locally |
111
+ | `find_text` | Search for text or regex in files (oversized output dumped to `result.json`) |
112
+ | `download_file` | Download a file from a URL and save it locally |
112
113
  | `rename` | Rename or move a file/directory |
113
114
  | `copy` | Copy a file or directory |
114
- | `batchEdit` | Batch-edit file text (only unmatched items are echoed back; successful ones are counted) |
115
+ | `batch_edit` | Batch-edit file text (only unmatched items are echoed back; successful ones are counted) |
115
116
  | `edit` | Edit text in a file |
116
117
  | `sleep` | Wait for a specified number of milliseconds |
117
118
 
@@ -119,16 +120,16 @@ Conditionally injected based on the active model / config:
119
120
 
120
121
  | Tool | Injection Condition | Description |
121
122
  |------|---------------------|-------------|
122
- | `generateImage` | Only when `defaultImageModel` is configured | Generate an image from a text description |
123
- | `viewImage` | Only for vision models (the agent's `modelId` resolves to a model with `vision: true`) | View / analyze an image: local images are auto-uploaded via the Files API, network URLs are referenced directly |
123
+ | `generate_image` | Only when `defaultImageModel` is configured | Generate an image from a text description |
124
+ | `view_image` | Only for vision models (the agent's `modelId` resolves to a model with `vision: true`) | View / analyze an image: local images are auto-uploaded via the Files API, network URLs are referenced directly |
124
125
 
125
- Tool output protection: `AppConfig.maxToolOutput` (in characters, default 32768) is the unified ceiling for tool output. Each tool handles overflow itself — `exec` dumps `stdout.log` / `stderr.log` separately (returning head/tail previews per stream), `findText` / `glob` / `ls` dump `result.json` (returning a head preview), and `readFile` only warns and suggests batched reads via `range`. Dumps go to `<tmpdir>/ai-zen/tool-output/<tool>-<timestamp>-<random>/`, one directory per call, with no self-cleanup. See [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md).
126
+ Tool output protection: `AppConfig.maxToolOutput` (in characters, default 32768) is the unified ceiling for tool output. Each tool handles overflow itself — `exec` dumps `stdout.log` / `stderr.log` separately (returning head/tail previews per stream), `find_text` / `glob` / `ls` dump `result.json` (returning a head preview), and `read` only warns and suggests batched reads via `range`. Dumps go to `<tmpdir>/ai-zen/tool-output/<tool>-<timestamp>-<random>/`, one directory per call, with no self-cleanup. See [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md).
126
127
 
127
128
  ## Built-in Plugins
128
129
 
129
130
  | Plugin | Description |
130
131
  |--------|-------------|
131
- | `AutoMigratePlugin` | Automatically triggers task migration when the context overflows; delegates the actual migration to the injected `TaskMigrationService` instance (history handled per `strategy`: default `omit` keeps it auditable, optional `prune` drops it), which reuses the agent's own model client |
132
+ | `AutoMigratePlugin` | Automatically triggers task migration when the context overflows; delegates the actual migration to the injected `HandoffService` (history handled per `strategy`: default `omit` keeps it auditable, optional `prune` drops it). `resolveMode` lets the caller pick the handoff executor or skip the round. Steps aside while `service.busy` to avoid the handoff→migrate→handoff recursion |
132
133
  | `AutoRefreshToolsPlugin` | Re-scans the file system before each `send()` to refresh the tool list |
133
134
  | `ContextGuardPlugin` | Context safety guard — before each request, throws `ContextOverflowError` (interrupting the conversation) when the previous round's `usage.prompt_tokens` exceeds `maxTokens × ratio` (default 1.5), preventing context runaway from reading oversized files |
134
135
  | `UnknownToolHintPlugin` | Smarter unknown-tool hints — when the LLM calls a nonexistent tool, guides it to `call_mcp_tool` / points out permission issues based on MCP config (opt-in via `agent.use`) |
package/README.zh.md CHANGED
@@ -20,7 +20,7 @@ Desktop ──┘ │
20
20
  types ← 纯类型,零业务依赖(含 ToolEnv 工具环境)
21
21
  config ← ConfigManager + constants:读写 config.json + 目录初始化 + 出厂默认
22
22
  crud ← 能力实体 CRUD(Agent 定义等;会话/草稿已下放给各端自行持久化)
23
- runtime ← 模型工厂 + Agent 组装 + 任务迁移(createModel / createAgent / SdkAgent / TaskMigrationService)
23
+ runtime ← 模型工厂 + Agent 组装 + 任务交接/迁移(createModel / createAgent / SdkAgent / HandoffService / HandoffGuardPlugin)
24
24
  scope ← Scope 编排容器 + ScopePlugin 契约 + PermissionEvaluator / disclosure + plugins/(5 个自包含能力来源:发现 + 工具 + 插件)
25
25
  agent-plugins ← Agent 插件(autoMigrate、autoRefreshTools、contextGuard、unknownToolHint)
26
26
  shared ← 日志、错误
@@ -67,8 +67,9 @@ const scope = new Scope({
67
67
  await scope.init();
68
68
 
69
69
  const agent = await createAgent(scope, "my-agent");
70
- const migrationService = new TaskMigrationService({ onMigrated }); // 迁移复用传入 agent 自身的模型调用
71
- agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
70
+ const handoffService = new HandoffService({ handoffDir: "/work/.ai-zen/handoffs", skillDirs });
71
+ agent.use(new ContextGuardPlugin({ maxTokens, isSuspended: () => handoffService.busy }));
72
+ agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens }));
72
73
  agent.use(new AutoRefreshToolsPlugin());
73
74
  agent.use(new UnknownToolHintPlugin({ scope }));
74
75
  await agent.init();
@@ -96,21 +97,21 @@ await scope.dispose(); // 断开 MCP、释放插件资源
96
97
  | 工具 | 说明 |
97
98
  |------|------|
98
99
  | `cwd` | 获取当前工作目录 |
99
- | `readFile` | 读取文件(可选 `range` 按行列范围读取;输出超限仅警告并提示用 `range` 分批) |
100
- | `inspectFile` | 勘察文件结构概况(行数、列数分布等),流式扫描不受 300KB 限制 |
101
- | `writeFile` | 写入文件 |
100
+ | `read` | 读取文件(可选 `range` 按行列范围读取;输出超限仅警告并提示用 `range` 分批) |
101
+ | `inspect_file` | 勘察文件结构概况(行数、列数分布等),流式扫描不受 300KB 限制 |
102
+ | `write` | 写入文件 |
102
103
  | `exec` | 执行命令(支持 `timeout` 超时参数;stdout+stderr 超限时分文件落盘 `stdout.log` / `stderr.log`) |
103
- | `exec_async` | 异步执行命令,启动后立即返回,不等待结果 |
104
+ | `spawn` | 异步执行命令,启动后立即返回,不等待结果 |
104
105
  | `mkdir` | 创建目录 |
105
106
  | `rm` | 删除文件或目录 |
106
107
  | `glob` | 使用 glob 模式扫描查找文件(输出超限落盘 `result.json`) |
107
108
  | `ls` | 列出目录内容(输出超限落盘 `result.json`) |
108
109
  | `exist` | 检查文件或目录是否存在 |
109
- | `findText` | 在文件中搜索文本或正则(输出超限落盘 `result.json`) |
110
- | `downloadFile` | 从 URL 下载文件并保存到本地 |
110
+ | `find_text` | 在文件中搜索文本或正则(输出超限落盘 `result.json`) |
111
+ | `download_file` | 从 URL 下载文件并保存到本地 |
111
112
  | `rename` | 重命名或移动文件/目录 |
112
113
  | `copy` | 复制文件或目录 |
113
- | `batchEdit` | 批量编辑文件文本(仅回显未匹配项,成功项只计数) |
114
+ | `batch_edit` | 批量编辑文件文本(仅回显未匹配项,成功项只计数) |
114
115
  | `edit` | 编辑文件中的文本 |
115
116
  | `sleep` | 等待指定毫秒数后继续 |
116
117
 
@@ -118,16 +119,16 @@ await scope.dispose(); // 断开 MCP、释放插件资源
118
119
 
119
120
  | 工具 | 注入条件 | 说明 |
120
121
  |------|----------|------|
121
- | `generateImage` | 配置了 `defaultImageModel` 才注册 | 根据文字描述生成图片 |
122
- | `viewImage` | 仅视觉模型可用(Agent 的 `modelId` 解析为 `vision: true` 的模型) | 查看/分析图片:本地图片自动经 Files API 上传,网络 URL 直接引用 |
122
+ | `generate_image` | 配置了 `defaultImageModel` 才注册 | 根据文字描述生成图片 |
123
+ | `view_image` | 仅视觉模型可用(Agent 的 `modelId` 解析为 `vision: true` 的模型) | 查看/分析图片:本地图片自动经 Files API 上传,网络 URL 直接引用 |
123
124
 
124
- 工具输出保护:`AppConfig.maxToolOutput`(字符数,缺省 32768)是工具输出的统一上限,超限时由各工具自行处置——`exec` 分文件落盘 `stdout.log` / `stderr.log`(返回各流头尾预览),`findText` / `glob` / `ls` 落盘 `result.json`(返回头部预览),`readFile` 仅警告并提示用 `range` 分批读取。落盘目录为 `<tmpdir>/ai-zen/tool-output/<工具名>-<时间戳>-<随机串>/`,每次调用独立、不自清理。详见 [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md)。
125
+ 工具输出保护:`AppConfig.maxToolOutput`(字符数,缺省 32768)是工具输出的统一上限,超限时由各工具自行处置——`exec` 分文件落盘 `stdout.log` / `stderr.log`(返回各流头尾预览),`find_text` / `glob` / `ls` 落盘 `result.json`(返回头部预览),`read` 仅警告并提示用 `range` 分批读取。落盘目录为 `<tmpdir>/ai-zen/tool-output/<工具名>-<时间戳>-<随机串>/`,每次调用独立、不自清理。详见 [`docs/sdk-design-v1.md` §12](docs/sdk-design-v1.md)。
125
126
 
126
127
  ## 内置插件
127
128
 
128
129
  | 插件 | 说明 |
129
130
  |------|------|
130
- | `AutoMigratePlugin` | 上下文超限时自动触发任务迁移,生成交接文档并追加对话断点(历史按 `strategy` 处理:默认 `omit` 标记保留可审计,可选 `prune` 物理剔除;委托注入的 `TaskMigrationService`,复用 Agent 自身的模型调用) |
131
+ | `AutoMigratePlugin` | 上下文超限时自动触发任务迁移(历史按 `strategy` 处理:默认 `omit` 标记保留可审计,可选 `prune` 物理剔除),委托注入的 `HandoffService`;`resolveMode` 让调用方决定交接执行者或跳过本轮;`service.busy` 期间让行,避免「交接→迁移→交接」递归 |
131
132
  | `AutoRefreshToolsPlugin` | 每次 `send()` 前重新扫描文件系统,刷新工具列表 |
132
133
  | `ContextGuardPlugin` | 上下文安全护栏 — 每轮发请求前检测上一轮 `usage.prompt_tokens`,超过 `maxTokens × ratio`(默认 1.5,即 +50%)时抛 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控 |
133
134
  | `UnknownToolHintPlugin` | 未知工具智能提示 — LLM 调用不存在的工具时,根据 MCP 配置引导使用 `call_mcp_tool` / 提示权限问题(调用方显式 `agent.use` 注册) |
@@ -1,23 +1,49 @@
1
1
  import type { AgentPlugin, SendContext } from "@ai-zen/agents-core";
2
- import { TaskMigrationService } from "../runtime/TaskMigrationService.js";
2
+ import type { HandoffMode, HandoffService } from "../runtime/HandoffService.js";
3
+ /** 触发迁移时的用量信息(供执行者选择、确认文案使用) */
4
+ export interface AutoMigrateTriggerInfo {
5
+ /** 当前 prompt token 用量 */
6
+ promptTokens: number;
7
+ /** 上下文阈值(当前模型的 maxContextTokens) */
8
+ maxTokens: number;
9
+ }
10
+ /**
11
+ * 迁移执行者解析器:只负责「用什么方式交接」,不负责「何时迁移」。
12
+ *
13
+ * - 返回 `HandoffMode` → 用该执行者迁移;
14
+ * - 返回 `null` → 跳过本次迁移(例如用户拒绝);
15
+ * - 未提供 → 使用 `HandoffService` 构造时的默认执行者。
16
+ */
17
+ export type AutoMigrateModeResolver = (info: AutoMigrateTriggerInfo) => HandoffMode | null | Promise<HandoffMode | null>;
3
18
  export interface AutoMigrateOptions {
4
- /** 迁移服务实例(持有迁移前后钩子;生成交接文档时复用传入 agent 的模型调用) */
5
- service: TaskMigrationService;
19
+ /** 交接服务实例(持有交接前后钩子;编排 handoff / migrate 两条链路) */
20
+ service: HandoffService;
6
21
  /** 触发迁移的上下文阈值 */
7
22
  maxTokens: number;
23
+ /**
24
+ * 执行者解析(可选)。上层可在此弹确认框、读用户偏好,
25
+ * 或直接返回 `null` 表达「本次不迁移」——**无需**再继承本插件。
26
+ */
27
+ resolveMode?: AutoMigrateModeResolver;
8
28
  }
9
29
  /**
10
- * 自动迁移插件 — 只负责「触发」,实际迁移逻辑统一委托给持有的 `TaskMigrationService` 实例。
30
+ * 自动迁移插件 —— 只负责「何时触发」,实际迁移统一委托给持有的 `HandoffService`。
11
31
  *
12
- * 当 token 使用量超过 maxTokens 时触发迁移。`service.migrate()` 需要 `agent`(当前对话)作为
13
- * 唯一运行时参数,其余(模型、迁移前后钩子)已由服务在构造时持有。
32
+ * 当 `usage.prompt_tokens` 超过 maxTokens 时触发迁移:
14
33
  *
15
34
  * ```ts
16
35
  * agent.use(new AutoMigratePlugin({
17
- * service: new TaskMigrationService({ onMigrated }),
36
+ * service: handoffService,
18
37
  * maxTokens: 250_000,
38
+ * resolveMode: async ({ promptTokens, maxTokens }) =>
39
+ * (await askUser(promptTokens, maxTokens)) ? "self" : null,
19
40
  * }));
20
41
  * ```
42
+ *
43
+ * 两层让行(缺一不可):
44
+ * - `service.busy`:交接回合本身就是一次 `send()`,其 `onAfterSend` 会再次到这里;
45
+ * 不跳过就会「交接触发迁移 → 迁移再交接」无限递归;
46
+ * - 交接回合结束后的用量也仍可能超阈值,故 busy 判断必须覆盖整个交接 + 迁移过程。
21
47
  */
22
48
  export declare class AutoMigratePlugin implements AgentPlugin {
23
49
  private options;
@@ -1,17 +1,23 @@
1
1
  import { getLogger } from "../shared/logger.js";
2
2
  const log = getLogger();
3
3
  /**
4
- * 自动迁移插件 — 只负责「触发」,实际迁移逻辑统一委托给持有的 `TaskMigrationService` 实例。
4
+ * 自动迁移插件 —— 只负责「何时触发」,实际迁移统一委托给持有的 `HandoffService`。
5
5
  *
6
- * 当 token 使用量超过 maxTokens 时触发迁移。`service.migrate()` 需要 `agent`(当前对话)作为
7
- * 唯一运行时参数,其余(模型、迁移前后钩子)已由服务在构造时持有。
6
+ * 当 `usage.prompt_tokens` 超过 maxTokens 时触发迁移:
8
7
  *
9
8
  * ```ts
10
9
  * agent.use(new AutoMigratePlugin({
11
- * service: new TaskMigrationService({ onMigrated }),
10
+ * service: handoffService,
12
11
  * maxTokens: 250_000,
12
+ * resolveMode: async ({ promptTokens, maxTokens }) =>
13
+ * (await askUser(promptTokens, maxTokens)) ? "self" : null,
13
14
  * }));
14
15
  * ```
16
+ *
17
+ * 两层让行(缺一不可):
18
+ * - `service.busy`:交接回合本身就是一次 `send()`,其 `onAfterSend` 会再次到这里;
19
+ * 不跳过就会「交接触发迁移 → 迁移再交接」无限递归;
20
+ * - 交接回合结束后的用量也仍可能超阈值,故 busy 判断必须覆盖整个交接 + 迁移过程。
15
21
  */
16
22
  export class AutoMigratePlugin {
17
23
  options;
@@ -20,14 +26,22 @@ export class AutoMigratePlugin {
20
26
  }
21
27
  async onAfterSend(ctx) {
22
28
  const agent = ctx.agent;
23
- const { service, maxTokens } = this.options;
29
+ const { service, maxTokens, resolveMode } = this.options;
30
+ // 交接/迁移进行中:让行(防递归)
31
+ if (service.busy)
32
+ return;
24
33
  const promptTokens = agent.lastUsage?.prompt_tokens;
25
34
  if (promptTokens == null)
26
35
  return;
27
36
  if (promptTokens <= maxTokens)
28
37
  return;
29
38
  try {
30
- await service.migrate({ agent, promptTokens, maxTokens });
39
+ const mode = resolveMode
40
+ ? await resolveMode({ promptTokens, maxTokens })
41
+ : undefined;
42
+ if (mode === null)
43
+ return; // 用户选择跳过本次迁移
44
+ await service.migrate({ agent, promptTokens, maxTokens, mode: mode ?? undefined });
31
45
  }
32
46
  catch (err) {
33
47
  log.error(`[autoMigrate] 迁移失败: ${err?.message ?? err}`);
@@ -7,6 +7,15 @@ export interface ContextGuardOptions {
7
7
  * 在请求前抛出 ContextOverflowError 中断对话。默认 1.5(即 +50%)。
8
8
  */
9
9
  ratio?: number;
10
+ /**
11
+ * 是否处于「需要放宽」的阶段(例如交接回合)。返回 true 时改用 `suspendedRatio`。
12
+ *
13
+ * 为什么需要:交接恰好发生在上下文最满的时刻,而交接回合本身还要再发 1~2 次请求。
14
+ * 若仍按 1.5 倍判定,正常交接会被护栏误杀;完全让行又会失去熔断,故改为放宽而非放行。
15
+ */
16
+ isSuspended?: () => boolean;
17
+ /** 放宽后的比例,默认 2.0(即 maxTokens × 2) */
18
+ suspendedRatio?: number;
10
19
  }
11
20
  /**
12
21
  * 上下文安全护栏插件。
@@ -18,13 +27,14 @@ export interface ContextGuardOptions {
18
27
  * 与迁移插件职责分离:
19
28
  * - ContextGuardPlugin 只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
20
29
  * - AutoMigratePlugin 在正常范围内(超 maxTokens 但未达硬上限)做交接迁移。
30
+ * - 交接回合由 `isSuspended` 放宽阈值(而非完全让行),保证交接不被误杀、又保留熔断。
21
31
  *
22
32
  * ```ts
23
- * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
24
- * agent.use(new AutoMigratePlugin({
25
- * service: new TaskMigrationService({ onMigrated }),
33
+ * agent.use(new ContextGuardPlugin({
26
34
  * maxTokens: 250_000,
35
+ * isSuspended: () => handoffService.busy,
27
36
  * }));
37
+ * agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens: 250_000 }));
28
38
  * ```
29
39
  */
30
40
  export declare class ContextGuardPlugin implements AgentPlugin {
@@ -9,13 +9,14 @@ import { ContextOverflowError } from "../shared/errors.js";
9
9
  * 与迁移插件职责分离:
10
10
  * - ContextGuardPlugin 只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
11
11
  * - AutoMigratePlugin 在正常范围内(超 maxTokens 但未达硬上限)做交接迁移。
12
+ * - 交接回合由 `isSuspended` 放宽阈值(而非完全让行),保证交接不被误杀、又保留熔断。
12
13
  *
13
14
  * ```ts
14
- * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
15
- * agent.use(new AutoMigratePlugin({
16
- * service: new TaskMigrationService({ onMigrated }),
15
+ * agent.use(new ContextGuardPlugin({
17
16
  * maxTokens: 250_000,
17
+ * isSuspended: () => handoffService.busy,
18
18
  * }));
19
+ * agent.use(new AutoMigratePlugin({ service: handoffService, maxTokens: 250_000 }));
19
20
  * ```
20
21
  */
21
22
  export class ContextGuardPlugin {
@@ -25,13 +26,15 @@ export class ContextGuardPlugin {
25
26
  }
26
27
  async onInnerLoopStart(ctx) {
27
28
  const { agent } = ctx;
28
- const { maxTokens, ratio = 1.5 } = this.options;
29
+ const { maxTokens, ratio = 1.5, isSuspended, suspendedRatio = 2 } = this.options;
29
30
  // 首轮请求前尚无用量数据,跳过(只能在发出首轮请求、拿到 usage 后才有据可查)
30
31
  const promptTokens = agent.lastUsage?.prompt_tokens;
31
32
  if (promptTokens == null)
32
33
  return;
33
- if (promptTokens > maxTokens * ratio) {
34
- throw new ContextOverflowError(promptTokens, maxTokens, ratio);
34
+ // 交接回合等需要放宽的阶段:放宽阈值但保留熔断
35
+ const effectiveRatio = isSuspended?.() ? suspendedRatio : ratio;
36
+ if (promptTokens > maxTokens * effectiveRatio) {
37
+ throw new ContextOverflowError(promptTokens, maxTokens, effectiveRatio);
35
38
  }
36
39
  }
37
40
  }
@@ -52,10 +52,21 @@ export declare class ConfigManager {
52
52
  agent: AgentDefinition | null;
53
53
  subAgent: AgentDefinition | null;
54
54
  }>;
55
+ /**
56
+ * 释放 / 同步出厂 Skill。
57
+ *
58
+ * 覆盖策略与 `syncManagedModels` 同构:
59
+ * - 磁盘上不存在 → 写入出厂内容;
60
+ * - 存在且 frontmatter 标了 `custom: "true"` → 用户自有,原样保留;
61
+ * - 存在但未标 → 视为出厂托管,同步为当前版本内容(内容相同则不写盘)。
62
+ *
63
+ * @returns 实际写入的 Skill id 列表(内容未变化 / 用户自有 均不计入)
64
+ */
65
+ ensureFactorySkills(): Promise<string[]>;
55
66
  /** mcp.json 路径:{basePath}/mcp.json */
56
67
  get mcpPath(): string;
57
68
  /**
58
- * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty)。
69
+ * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty 与 chrome-devtools)。
59
70
  * 已有文件不被覆盖,尊重用户已配置的 MCP 服务器。
60
71
  * @returns 返回当前生效的 MCP 配置原始内容
61
72
  */
@@ -1,6 +1,7 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
4
+ import { FACTORY_SKILLS, isCustomSkill } from "./factory-skills.js";
4
5
  /**
5
6
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
6
7
  */
@@ -178,9 +179,47 @@ export class ConfigManager {
178
179
  const agent = await this.ensureDefaultAgent();
179
180
  const subAgent = await this.ensureDefaultSubAgent();
180
181
  await this.ensureDefaultMcpConfig();
182
+ await this.ensureFactorySkills();
181
183
  return { config, agent, subAgent };
182
184
  }
183
185
  // -----------------------------------------------------------------------
186
+ // 出厂 Skill({basePath}/skills/<id>/SKILL.md)
187
+ // -----------------------------------------------------------------------
188
+ /**
189
+ * 释放 / 同步出厂 Skill。
190
+ *
191
+ * 覆盖策略与 `syncManagedModels` 同构:
192
+ * - 磁盘上不存在 → 写入出厂内容;
193
+ * - 存在且 frontmatter 标了 `custom: "true"` → 用户自有,原样保留;
194
+ * - 存在但未标 → 视为出厂托管,同步为当前版本内容(内容相同则不写盘)。
195
+ *
196
+ * @returns 实际写入的 Skill id 列表(内容未变化 / 用户自有 均不计入)
197
+ */
198
+ async ensureFactorySkills() {
199
+ const written = [];
200
+ for (const skill of FACTORY_SKILLS) {
201
+ const dir = join(this.basePath, "skills", skill.id);
202
+ const file = join(dir, "SKILL.md");
203
+ let existing = null;
204
+ try {
205
+ existing = await fs.readFile(file, "utf-8");
206
+ }
207
+ catch {
208
+ existing = null;
209
+ }
210
+ // 用户声明自有 → 永不覆盖
211
+ if (existing != null && isCustomSkill(existing))
212
+ continue;
213
+ // 内容已一致 → 不重复写盘
214
+ if (existing === skill.content)
215
+ continue;
216
+ await fs.mkdir(dir, { recursive: true });
217
+ await fs.writeFile(file, skill.content, "utf-8");
218
+ written.push(skill.id);
219
+ }
220
+ return written;
221
+ }
222
+ // -----------------------------------------------------------------------
184
223
  // MCP 配置(~/.ai-zen/mcp.json)
185
224
  // -----------------------------------------------------------------------
186
225
  /** mcp.json 路径:{basePath}/mcp.json */
@@ -188,7 +227,7 @@ export class ConfigManager {
188
227
  return join(this.basePath, "mcp.json");
189
228
  }
190
229
  /**
191
- * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty)。
230
+ * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty 与 chrome-devtools)。
192
231
  * 已有文件不被覆盖,尊重用户已配置的 MCP 服务器。
193
232
  * @returns 返回当前生效的 MCP 配置原始内容
194
233
  */
@@ -11,7 +11,9 @@ export declare const DEFAULT_SUBAGENT_DEFINITION: Omit<AgentDefinition, "created
11
11
  export declare const DEFAULT_APP_CONFIG: AppConfig;
12
12
  /**
13
13
  * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
14
- * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
14
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用:
15
+ * - socket-pty 伪终端(pty)能力
16
+ * - chrome-devtools Chrome DevTools 浏览器调试/自动化能力(Google 官方 chrome-devtools-mcp)
15
17
  *
16
18
  * 若文件已存在则不覆盖(尊重用户已有配置)。
17
19
  */
@@ -214,11 +214,13 @@ export const DEFAULT_APP_CONFIG = {
214
214
  maxToolOutput: 32_768,
215
215
  };
216
216
  // ---------------------------------------------------------------------------
217
- // 默认 MCP 配置(socket-pty 终端 MCP)
217
+ // 默认 MCP 配置(socket-pty 终端 MCP + chrome-devtools 浏览器调试 MCP)
218
218
  // ---------------------------------------------------------------------------
219
219
  /**
220
220
  * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
221
- * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
221
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用:
222
+ * - socket-pty 伪终端(pty)能力
223
+ * - chrome-devtools Chrome DevTools 浏览器调试/自动化能力(Google 官方 chrome-devtools-mcp)
222
224
  *
223
225
  * 若文件已存在则不覆盖(尊重用户已有配置)。
224
226
  */
@@ -230,6 +232,12 @@ export const DEFAULT_MCP_CONFIG = {
230
232
  args: ["-y", "@ai-zen/socket-pty", "mcp"],
231
233
  description: "可托管的伪终端(pty):spawn/read/wait/write/resize/status/kill",
232
234
  },
235
+ "chrome-devtools": {
236
+ type: "stdio",
237
+ command: "npx",
238
+ args: ["-y", "chrome-devtools-mcp@latest"],
239
+ description: "Chrome DevTools 浏览器调试与自动化(Google chrome-devtools-mcp):页面导航、性能追踪、网络与控制台检查、截图等",
240
+ },
233
241
  },
234
242
  };
235
243
  // ---------------------------------------------------------------------------
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 出厂 Skill 资产 —— 随 SDK 版本释放到 `<basePath>/skills/<id>/SKILL.md`。
3
+ *
4
+ * 为什么以 TS 字符串常量内联、而不是独立 .md 文件:
5
+ * SDK 用 `tsc` 直接编译,不拷贝非 TS 资源;内容内联后 `dist/` 天然携带,
6
+ * 无需额外打包步骤,也不依赖包内相对资源路径。
7
+ *
8
+ * 覆盖策略(与 `syncManagedModels` 同构):
9
+ * - 磁盘上的 SKILL.md frontmatter 里写了 `custom: "true"` → 视为用户自有,永不被覆盖;
10
+ * - 否则每次 `ConfigManager.bootstrap()` 同步为当前版本内容(模板属于产品行为,
11
+ * 改进了就要让老用户吃到)。
12
+ */
13
+ /** 交接 Skill 的 id(目录名 = frontmatter name) */
14
+ export declare const HANDOFF_SKILL_ID = "handoff";
15
+ /**
16
+ * 交接文档兜底模板。
17
+ *
18
+ * 仅在磁盘上读不到 `handoff` Skill 时使用(用户删除 / 目录不可读)。
19
+ * 它是**保险丝**,不是第二真相源:正常路径下模板一律来自 SKILL.md。
20
+ */
21
+ export declare const FALLBACK_HANDOFF_TEMPLATE: string;
22
+ /** 全部出厂 Skill(顺序无意义,按 id 去重) */
23
+ export declare const FACTORY_SKILLS: readonly {
24
+ id: string;
25
+ content: string;
26
+ }[];
27
+ /**
28
+ * 判断一份 SKILL.md 是否被用户声明为自有。
29
+ *
30
+ * 只检查 frontmatter 区块内的 `custom: true`(允许带引号),
31
+ * 正文里出现同样的字样不生效——避免误判导致模板停止更新。
32
+ */
33
+ export declare function isCustomSkill(content: string): boolean;