@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
@@ -0,0 +1,126 @@
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 const HANDOFF_SKILL_ID = "handoff";
15
+ /**
16
+ * 交接文档兜底模板。
17
+ *
18
+ * 仅在磁盘上读不到 `handoff` Skill 时使用(用户删除 / 目录不可读)。
19
+ * 它是**保险丝**,不是第二真相源:正常路径下模板一律来自 SKILL.md。
20
+ */
21
+ export const FALLBACK_HANDOFF_TEMPLATE = [
22
+ "# 交接文档",
23
+ "",
24
+ "立即停工:不再实现任何功能、不继续未完成任务、不向用户提问。",
25
+ "唯一任务是:核实当前真实状态 → 把交接文档写到「本次交接参数」给出的路径。",
26
+ "",
27
+ "## 💬 对话断点",
28
+ "## 🎯 任务目标",
29
+ "## 📊 当前状态",
30
+ "## ✅ 已完成",
31
+ "## 📋 未完成",
32
+ "## 🧠 关键决策",
33
+ "## ⚠️ 约束与偏好",
34
+ "## 📁 文件索引",
35
+ "## 🔔 接手第一步",
36
+ "",
37
+ "无法核实的信息写「⚠️ 未核实」,禁止推测。",
38
+ ].join("\n");
39
+ /** 出厂 Skill:交接(handoff) */
40
+ const HANDOFF_SKILL = `---
41
+ name: handoff
42
+ description: 编写任务交接文档——对话停手离场时,把任务目标、当前真实状态、关键决策与下一步写成一份可被他人直接接手的文档。
43
+ metadata:
44
+ custom: "false"
45
+ ---
46
+
47
+ # 交接文档书写规范
48
+
49
+ ## 何时适用
50
+
51
+ 上下文即将超限触发任务迁移、用户明确要求「写交接文档」、或你被要求「交班」时。
52
+
53
+ ## 铁律(违反即视为失败)
54
+
55
+ 1. **立即停工**:不再实现任何功能、不继续未完成的任务、不顺手修 bug、不做重构。
56
+ 2. **不向用户提问**,不做方案讨论,不征求确认。
57
+ 3. 只做两件事:**核实当前真实状态** → **写文档**。
58
+ 4. 允许读取类操作(read / ls / glob / find_text / inspect_file / cwd,以及 git status、git diff、git log 等只读命令)。
59
+ 禁止修改工作区:不改源码、不 npm install、不 commit、不 push。唯一允许写入的文件是交接文档本身。
60
+ 5. **一次性写完**,不要反复重写;不要读大文件;不要把长代码贴进文档。
61
+
62
+ ## 落盘
63
+
64
+ 路径由「本次交接参数」给出,**原样使用**,不得改名、不得换目录、不得省略。
65
+ 用 write 工具写入后,用 read 回读一次,确认内容完整(无 read 能力时跳过此步)。
66
+
67
+ ## 文档结构(按序书写,标题保持原样)
68
+
69
+ ## 💬 对话断点
70
+ 用户最后要求了什么、你最后回复/交付了什么。各一段,不要复述整段对话。
71
+
72
+ ## 🎯 任务目标
73
+ 用户到底想要什么(意图与验收标准),而不是你执行过的步骤清单。
74
+
75
+ ## 📊 当前状态
76
+ 分支与 commit、未提交改动、测试是否通过、有无进程/端口在跑、依赖是否装好。
77
+
78
+ ## ✅ 已完成
79
+ 每项写:做了什么 + 产出路径(文件/目录) + 怎么验证它确实生效。
80
+
81
+ ## 📋 未完成
82
+ 按优先级排列;每项写「下一步最小动作」,具体到文件或可执行命令。
83
+
84
+ ## 🧠 关键决策
85
+ 为什么这样做(取舍理由);以及走过的死路——防止接手者重蹈覆辙。
86
+
87
+ ## ⚠️ 约束与偏好
88
+ 用户强调过的规矩、项目约定、不可改动的部分、必须遵守的风格。
89
+
90
+ ## 📁 文件索引
91
+ 路径 + 作用 + 本次是否改动(新增 / 修改 / 仅阅读)。
92
+
93
+ ## 🔔 接手第一步
94
+ 3 条以内可直接执行的命令或动作,让接手者立刻能开工。
95
+
96
+ ## 状态核实清单
97
+
98
+ - \`git status\` / \`git diff --stat\`:当前分支、是否有未提交改动
99
+ - 最近一次测试结果:跑过就写结果摘要;没跑就写「未运行」,**不要写「应该能过」**
100
+ - 交接涉及的关键文件:用 ls / read 确认确实存在于磁盘
101
+ - 正在运行的服务与端口:\`ps\` / \`lsof -i\` 或项目自带的启动脚本
102
+
103
+ > ⚠️ 若「本次交接参数」声明工具不可用:以上每一项都写「⚠️ 未核实」,**禁止推测**具体路径、进度与测试状态。
104
+
105
+ ## 写作要求
106
+
107
+ - 每句话都要让接手者能「立刻开始干活」;禁止「继续开发」「完善功能」「优化一下」这类空话。
108
+ - 已无法在当前上下文中确定的信息,写「需向用户确认 XXX」,不要编造。
109
+ - 全文控制在 300 行以内。
110
+ `;
111
+ /** 全部出厂 Skill(顺序无意义,按 id 去重) */
112
+ export const FACTORY_SKILLS = [
113
+ { id: HANDOFF_SKILL_ID, content: HANDOFF_SKILL },
114
+ ];
115
+ /**
116
+ * 判断一份 SKILL.md 是否被用户声明为自有。
117
+ *
118
+ * 只检查 frontmatter 区块内的 `custom: true`(允许带引号),
119
+ * 正文里出现同样的字样不生效——避免误判导致模板停止更新。
120
+ */
121
+ export function isCustomSkill(content) {
122
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
123
+ if (!match)
124
+ return false;
125
+ return /^\s*custom:\s*["']?true["']?\s*$/m.test(match[1]);
126
+ }
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export { SubAgentsScopePlugin } from "./scope/plugins/subagents/SubAgentsScopePl
20
20
  export { allInOne } from "./scope/plugins/allInOne.js";
21
21
  export type { AllInOneOptions } from "./scope/plugins/allInOne.js";
22
22
  export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
23
+ export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
23
24
  export { EntityRepository } from "./shared/EntityRepository.js";
24
25
  export { AgentRepository } from "./crud/AgentRepository.js";
25
26
  export { createModel } from "./runtime/createModel.js";
@@ -30,16 +31,17 @@ export { McpConnectionManager } from "./scope/plugins/mcp/McpConnectionManager.j
30
31
  export type { McpConnectOptions } from "./scope/plugins/mcp/McpConnectionManager.js";
31
32
  export { SdkCallbackTool } from "./scope/plugins/builtin/SdkCallbackTool.js";
32
33
  export type { SdkCallbackToolOptions } from "./scope/plugins/builtin/SdkCallbackTool.js";
33
- export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
34
- export type { MigrationContext, MigrationStrategy, TaskMigrationServiceOptions, } from "./runtime/TaskMigrationService.js";
34
+ export { HandoffService } from "./runtime/HandoffService.js";
35
+ export type { HandoffContext, HandoffMode, HandoffParams, HandoffServiceOptions, MigrateParams, MigrationStrategy, } from "./runtime/HandoffService.js";
36
+ export { HandoffGuardPlugin } from "./runtime/HandoffGuardPlugin.js";
35
37
  export { AutoMigratePlugin } from "./agent-plugins/AutoMigratePlugin.js";
36
- export type { AutoMigrateOptions } from "./agent-plugins/AutoMigratePlugin.js";
38
+ export type { AutoMigrateOptions, AutoMigrateTriggerInfo, AutoMigrateModeResolver, } from "./agent-plugins/AutoMigratePlugin.js";
37
39
  export { AutoRefreshToolsPlugin } from "./agent-plugins/AutoRefreshToolsPlugin.js";
38
40
  export { ContextGuardPlugin } from "./agent-plugins/ContextGuardPlugin.js";
39
41
  export type { ContextGuardOptions } from "./agent-plugins/ContextGuardPlugin.js";
40
42
  export { UnknownToolHintPlugin } from "./agent-plugins/UnknownToolHintPlugin.js";
41
43
  export { BUILTIN_TOOL_CLASSES } from "./scope/plugins/builtin/index.js";
42
- export { CwdTool, ReadFileTool, InspectFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./scope/plugins/builtin/index.js";
44
+ export { CwdTool, ReadTool, InspectFileTool, WriteTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, SpawnTool, SleepTool, } from "./scope/plugins/builtin/index.js";
43
45
  export { GenerateImageTool } from "./scope/plugins/builtin/GenerateImageTool.js";
44
46
  export { ViewImageTool } from "./scope/plugins/builtin/ViewImageTool.js";
45
47
  export { createLoadSkillTool, createCallSkillSubAgentTool } from "./scope/plugins/skills/skillTools.js";
package/dist/index.js CHANGED
@@ -17,6 +17,8 @@ export { SubAgentsScopePlugin } from "./scope/plugins/subagents/SubAgentsScopePl
17
17
  export { allInOne } from "./scope/plugins/allInOne.js";
18
18
  // 配置 — ConfigManager
19
19
  export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
20
+ // 出厂资产 — 出厂 Skill(随版本释放到 <basePath>/skills/)
21
+ export { FACTORY_SKILLS, HANDOFF_SKILL_ID, FALLBACK_HANDOFF_TEMPLATE, isCustomSkill, } from "./config/factory-skills.js";
20
22
  // CRUD — 实体仓储(仅 Agent 定义等能力实体)
21
23
  export { EntityRepository } from "./shared/EntityRepository.js";
22
24
  export { AgentRepository } from "./crud/AgentRepository.js";
@@ -26,7 +28,8 @@ export { SdkAgent } from "./runtime/SdkAgent.js";
26
28
  export { createAgent } from "./runtime/createAgent.js";
27
29
  export { McpConnectionManager } from "./scope/plugins/mcp/McpConnectionManager.js";
28
30
  export { SdkCallbackTool } from "./scope/plugins/builtin/SdkCallbackTool.js";
29
- export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
31
+ export { HandoffService } from "./runtime/HandoffService.js";
32
+ export { HandoffGuardPlugin } from "./runtime/HandoffGuardPlugin.js";
30
33
  // 插件
31
34
  export { AutoMigratePlugin } from "./agent-plugins/AutoMigratePlugin.js";
32
35
  export { AutoRefreshToolsPlugin } from "./agent-plugins/AutoRefreshToolsPlugin.js";
@@ -34,7 +37,7 @@ export { ContextGuardPlugin } from "./agent-plugins/ContextGuardPlugin.js";
34
37
  export { UnknownToolHintPlugin } from "./agent-plugins/UnknownToolHintPlugin.js";
35
38
  // 工具 — 内置工具类 + 动态工具工厂
36
39
  export { BUILTIN_TOOL_CLASSES } from "./scope/plugins/builtin/index.js";
37
- export { CwdTool, ReadFileTool, InspectFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./scope/plugins/builtin/index.js";
40
+ export { CwdTool, ReadTool, InspectFileTool, WriteTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, SpawnTool, SleepTool, } from "./scope/plugins/builtin/index.js";
38
41
  export { GenerateImageTool } from "./scope/plugins/builtin/GenerateImageTool.js";
39
42
  export { ViewImageTool } from "./scope/plugins/builtin/ViewImageTool.js";
40
43
  export { createLoadSkillTool, createCallSkillSubAgentTool } from "./scope/plugins/skills/skillTools.js";
@@ -0,0 +1,35 @@
1
+ import type { AgentPlugin, ToolCallContext } from "@ai-zen/agents-core";
2
+ /**
3
+ * 交接回合的工具守卫 —— 保证「原 agent 停下手头一切工作」不只是提示词约定。
4
+ *
5
+ * 交接由**同一个 agent** 在自己的会话里完成,它天然有继续干活的动力。
6
+ * 本插件在交接回合期间拦截写入类工具,只放行对交接文档那一份文件的写入:
7
+ * - `write`:仅当目标路径(按交接时给定的 cwd 归一后)等于交接文档路径时放行;
8
+ * - `edit` / `batch_edit` / `rm` / `rename` / `mkdir` / `copy`:一律拒绝;
9
+ * - 读取类工具与其他工具:放行。
10
+ *
11
+ * 拒绝方式是**软拒绝**(返回字符串):core 的 `onToolCall` 语义会把该字符串作为
12
+ * 工具结果回给模型并继续下一轮,因此模型能读到引导(「只允许写交接文档」)并转回正题。
13
+ * 反例:抛错会让该工具结果标记为 Error 且 `isPreventDefault`,整轮交接直接中断。
14
+ *
15
+ * 边界(必须如实记录,不要误以为是安全边界):
16
+ * - **挡不住 `exec`**:`echo > file`、`cat > file <<EOF` 永远能绕过。本插件降低的是
17
+ * 「顺手改代码」的误操作概率,不是沙箱。
18
+ * - 一次性:`arm()` 后生效,`disarm()` 后完全让行。`Agent.use()` 没有移除 API,
19
+ * 故用「同一实例 + 开关」而非反复注册,避免插件数组无限增长。
20
+ */
21
+ export declare class HandoffGuardPlugin implements AgentPlugin {
22
+ /** 写入类工具(除白名单的 write 外一律拒绝) */
23
+ private static readonly WRITE_TOOLS;
24
+ /** 当前交接文档绝对路径;undefined 表示未处于交接回合(完全让行) */
25
+ private handoffPath?;
26
+ /** 相对路径解析基准(交接时 agent 的 cwd) */
27
+ private cwd;
28
+ /** 进入交接回合:仅允许写入 handoffPath(相对路径按 cwd 归一) */
29
+ arm(handoffPath: string, cwd?: string): void;
30
+ /** 离开交接回合:恢复为完全让行 */
31
+ disarm(): void;
32
+ /** 是否处于交接回合(供上层/测试观察) */
33
+ get armed(): boolean;
34
+ onToolCall(ctx: ToolCallContext): string | undefined;
35
+ }
@@ -0,0 +1,65 @@
1
+ import { resolve as resolvePath } from "node:path";
2
+ /**
3
+ * 交接回合的工具守卫 —— 保证「原 agent 停下手头一切工作」不只是提示词约定。
4
+ *
5
+ * 交接由**同一个 agent** 在自己的会话里完成,它天然有继续干活的动力。
6
+ * 本插件在交接回合期间拦截写入类工具,只放行对交接文档那一份文件的写入:
7
+ * - `write`:仅当目标路径(按交接时给定的 cwd 归一后)等于交接文档路径时放行;
8
+ * - `edit` / `batch_edit` / `rm` / `rename` / `mkdir` / `copy`:一律拒绝;
9
+ * - 读取类工具与其他工具:放行。
10
+ *
11
+ * 拒绝方式是**软拒绝**(返回字符串):core 的 `onToolCall` 语义会把该字符串作为
12
+ * 工具结果回给模型并继续下一轮,因此模型能读到引导(「只允许写交接文档」)并转回正题。
13
+ * 反例:抛错会让该工具结果标记为 Error 且 `isPreventDefault`,整轮交接直接中断。
14
+ *
15
+ * 边界(必须如实记录,不要误以为是安全边界):
16
+ * - **挡不住 `exec`**:`echo > file`、`cat > file <<EOF` 永远能绕过。本插件降低的是
17
+ * 「顺手改代码」的误操作概率,不是沙箱。
18
+ * - 一次性:`arm()` 后生效,`disarm()` 后完全让行。`Agent.use()` 没有移除 API,
19
+ * 故用「同一实例 + 开关」而非反复注册,避免插件数组无限增长。
20
+ */
21
+ export class HandoffGuardPlugin {
22
+ /** 写入类工具(除白名单的 write 外一律拒绝) */
23
+ static WRITE_TOOLS = new Set([
24
+ "edit",
25
+ "batch_edit",
26
+ "rm",
27
+ "rename",
28
+ "mkdir",
29
+ "copy",
30
+ ]);
31
+ /** 当前交接文档绝对路径;undefined 表示未处于交接回合(完全让行) */
32
+ handoffPath;
33
+ /** 相对路径解析基准(交接时 agent 的 cwd) */
34
+ cwd = process.cwd();
35
+ /** 进入交接回合:仅允许写入 handoffPath(相对路径按 cwd 归一) */
36
+ arm(handoffPath, cwd) {
37
+ this.cwd = cwd ? resolvePath(cwd) : process.cwd();
38
+ this.handoffPath = resolvePath(this.cwd, handoffPath);
39
+ }
40
+ /** 离开交接回合:恢复为完全让行 */
41
+ disarm() {
42
+ this.handoffPath = undefined;
43
+ }
44
+ /** 是否处于交接回合(供上层/测试观察) */
45
+ get armed() {
46
+ return this.handoffPath !== undefined;
47
+ }
48
+ onToolCall(ctx) {
49
+ const handoffPath = this.handoffPath;
50
+ if (handoffPath == null)
51
+ return;
52
+ const name = ctx.tool_call?.function?.name ?? "";
53
+ if (name === "write") {
54
+ const raw = ctx.parsedArgs?.path;
55
+ if (typeof raw === "string" && resolvePath(this.cwd, raw) === handoffPath) {
56
+ return;
57
+ }
58
+ return `交接回合只允许写交接文档:${handoffPath}(write 的 path 请原样使用该路径),请立即开始撰写。`;
59
+ }
60
+ if (HandoffGuardPlugin.WRITE_TOOLS.has(name)) {
61
+ return `交接回合禁止修改工作区(${name}):你的唯一任务是把交接文档写入 ${handoffPath}。`;
62
+ }
63
+ return;
64
+ }
65
+ }
@@ -0,0 +1,199 @@
1
+ import { AgentNS } from "@ai-zen/agents-core";
2
+ import type OpenAI from "openai";
3
+ import { type Logger } from "../shared/logger.js";
4
+ import type { SdkAgent } from "./SdkAgent.js";
5
+ /**
6
+ * 交接文档的执行者。
7
+ *
8
+ * - `self`:**原 agent 在自己的会话里**停工后写(默认)。上下文完整、可用工具核实真实状态,
9
+ * 产出质量最好;代价是多 1~2 次全量输入。
10
+ * - `summary`:一次独立的模型调用,把序列化后的历史摘要成交接文档(无工具、无原会话上下文)。
11
+ * 便宜、确定,但保真度低——它是**逃生模式**,也是 `self` 失败时的单向降级目标。
12
+ */
13
+ export type HandoffMode = "self" | "summary";
14
+ /**
15
+ * 迁移后的历史处理策略。
16
+ *
17
+ * - `omit`(默认):历史消息标记 `omit: true`,仍留在 `agent.messages` 可审计/回放,但不再发给模型;
18
+ * - `prune`:物理剔除历史,只保留 `definition.messages`(系统提示等模板)+ 交接断点。
19
+ */
20
+ export type MigrationStrategy = "omit" | "prune";
21
+ /**
22
+ * 一次交接的完整上下文 —— 贯穿「交接前 → 写文档 → 迁移后」,供钩子与调用方使用。
23
+ */
24
+ export interface HandoffContext {
25
+ /** 当前对话 Agent(引用不变,仅替换消息列表) */
26
+ agent: SdkAgent;
27
+ /** **实际**使用的执行者(`self` 失败降级后会是 `summary`) */
28
+ mode: HandoffMode;
29
+ /** 调用方请求的执行者 */
30
+ requestedMode: HandoffMode;
31
+ /** 交接文档绝对路径 */
32
+ handoffPath: string;
33
+ /** 交接文档正文:仅 `summary` 模式由模型返回;`self` 模式不读盘,故为 undefined */
34
+ handoffDoc?: string;
35
+ /** 生成文档所用模型名 */
36
+ model: string;
37
+ /** 当前 prompt token 用量(自动迁移时由插件提供;手动触发可省略) */
38
+ promptTokens?: number;
39
+ /** 上下文阈值(自动迁移时由插件提供;手动触发可省略) */
40
+ maxTokens?: number;
41
+ /** 本次是否会在交接后开新会话(`migrate()` 为 true,`handoff()` 为 false)。钩子据此区分两种语义 */
42
+ migrating: boolean;
43
+ /** 交接前消息总数 */
44
+ messageCountBefore: number;
45
+ /** 序列化后的历史(仅 `summary` 模式填充;`self` 模式为空串) */
46
+ historyText: string;
47
+ }
48
+ /** 交接服务构造参数 */
49
+ export interface HandoffServiceOptions {
50
+ /**
51
+ * 交接文档目录(绝对路径推荐)。
52
+ * 默认按进程 cwd 推导为 `<cwd>/.ai-zen/handoffs`;多工作区场景请显式传入,勿依赖默认值。
53
+ */
54
+ handoffDir?: string;
55
+ /** 默认执行者,默认 `self`(可被 `handoff()/migrate()` 的参数覆盖) */
56
+ mode?: HandoffMode;
57
+ /** 迁移后历史处理策略,默认 `omit`(可被 `migrate()` 的参数覆盖) */
58
+ strategy?: MigrationStrategy;
59
+ /** 查找 `handoff` Skill 的目录列表(优先级从高到低);为空则直接用兜底模板 */
60
+ skillDirs?: string[];
61
+ /** `summary` 模式所用客户端(可选;未传则回退到 agent.client) */
62
+ client?: OpenAI;
63
+ /** `summary` 模式所用模型名(可选;未传则回退到 agent.model) */
64
+ model?: string;
65
+ /** `summary` 模式所用模型参数(可选;未传则回退到 agent.modelConfig) */
66
+ modelConfig?: Record<string, unknown>;
67
+ /** 交接文档有效性阈值(字节),默认 400。低于此值视为「没写出来」 */
68
+ minHandoffBytes?: number;
69
+ /** 交接回合开始前触发(此时 `agent.messages` 仍是完整旧历史) */
70
+ onBeforeHandoff?: (ctx: HandoffContext) => void | Promise<void>;
71
+ /** 交接文档写好后、迁移(清历史)之前触发 */
72
+ onHandoff?: (ctx: HandoffContext) => void | Promise<void>;
73
+ /** 迁移完成后触发(此时交接断点已注入 `agent.messages`) */
74
+ onMigrated?: (ctx: HandoffContext) => void | Promise<void>;
75
+ /** 可注入日志(可选,默认使用全局单例) */
76
+ logger?: Logger;
77
+ }
78
+ /** `handoff()` 参数 */
79
+ export interface HandoffParams {
80
+ agent: SdkAgent;
81
+ /** 本次执行者(覆盖构造时的默认值) */
82
+ mode?: HandoffMode;
83
+ /** 当前 prompt token 用量(可选,仅透传给钩子) */
84
+ promptTokens?: number;
85
+ /** 上下文阈值(可选,仅透传给钩子) */
86
+ maxTokens?: number;
87
+ /** 会话 id(可选,用于交接文件名,便于回溯) */
88
+ conversationId?: string;
89
+ }
90
+ /** `migrate()` 参数 */
91
+ export interface MigrateParams extends HandoffParams {
92
+ /** 本次迁移的历史处理策略(覆盖构造时的默认值) */
93
+ strategy?: MigrationStrategy;
94
+ }
95
+ /**
96
+ * 任务交接服务。
97
+ *
98
+ * 职责分层(这是本模块唯一的设计主线):
99
+ * - `handoff()`:**只产出交接文档**(停工 → 写文档 → 校验 → 失败降级),不动会话;
100
+ * - `migrate()`:`handoff()` 之后清历史并开新会话(原「任务迁移」语义)。
101
+ *
102
+ * 模板的唯一真相源是 `handoff` Skill(`SKILL.md`);代码只负责
103
+ * 「读正文 → 注入 → 校验文件 → 读不到时用兜底模板」,不持有第二份章节清单。
104
+ *
105
+ * 重入:`handoff()` 与 `migrate()` 共用 `busy` 标志。交接回合本身也是一次 `send()`,
106
+ * 若不在 busy 期间让 `AutoMigratePlugin` / `ContextGuardPlugin` 让行,就会「交接触发迁移、
107
+ * 迁移再触发交接」无限递归,或在最满的时刻被护栏直接击杀。
108
+ */
109
+ export declare class HandoffService {
110
+ /** 交接文档兜底模板(仅在读不到 Skill 时使用) */
111
+ static readonly FALLBACK_TEMPLATE: string;
112
+ private readonly handoffDirPath;
113
+ private readonly defaultMode;
114
+ private readonly defaultStrategy;
115
+ private readonly skillDirs;
116
+ private readonly client?;
117
+ private readonly model?;
118
+ private readonly modelConfig?;
119
+ private readonly minHandoffBytes;
120
+ private readonly onBeforeHandoff?;
121
+ private readonly onHandoff?;
122
+ private readonly onMigrated?;
123
+ private readonly logger;
124
+ /** 交接/迁移进行中标志(插件据此让行) */
125
+ private _busy;
126
+ /** 交接回合的工具守卫(同一实例复用 + arm/disarm,避免插件数组无限增长) */
127
+ private guard?;
128
+ /** 已注册过守卫的 agent(每个 agent 只 use 一次) */
129
+ private readonly guardedAgents;
130
+ constructor(options?: HandoffServiceOptions);
131
+ /** 是否正在交接/迁移(`AutoMigratePlugin` / `ContextGuardPlugin` 据此让行) */
132
+ get busy(): boolean;
133
+ /** 交接文档目录 */
134
+ get handoffDir(): string;
135
+ /**
136
+ * 交接文件名:`<YYYYMMDD-HHmmss>-<会话id前8位>.md`(本地时间,便于人眼归档)。
137
+ */
138
+ static createHandoffFileName(date?: Date, conversationId?: string): string;
139
+ /**
140
+ * 拼装「本轮交接」指令:`[Skill 正文] + [本次交接参数]`。
141
+ *
142
+ * 不在 Skill 正文里发明占位符(本体系没有模板变量引擎),参数一律由代码拼在后面,
143
+ * 这样 Skill 只讲「怎么写」、代码只讲「写到哪、有什么能力」。
144
+ */
145
+ static createHandoffInstruction(params: {
146
+ skillContent: string;
147
+ handoffPath: string;
148
+ /** 执行者是否具备工具能力(`summary` 模式为 false) */
149
+ toolsAvailable: boolean;
150
+ }): string;
151
+ /**
152
+ * 生成迁移后追加的「交接断点」消息(role=user):只给路径,让接手方自己 read。
153
+ *
154
+ * 为什么不内联全文:接手方第一次真正需要的是「去哪里取交接」,读一次文件的输入成本
155
+ * 与内联全文相当,但内联会让每条请求都携带全文;路径方案把成本推迟到真正需要时。
156
+ */
157
+ static createPostMessages(handoffPath: string): AgentNS.Message[];
158
+ /** 序列化消息为 `[role]: content` 文本(仅 `summary` 模式使用) */
159
+ static serializeMessages(messages: AgentNS.Message[]): string;
160
+ /**
161
+ * 只产出交接文档,**不改动会话**(`/handoff`)。
162
+ *
163
+ * 不清理历史:交接回合(指令 + 工具调用 + 回执)会留在 `agent.messages` 里,
164
+ * 用户可继续让 agent 打磨这份文档(重写同一路径即可)。
165
+ */
166
+ handoff(params: HandoffParams): Promise<HandoffContext>;
167
+ /**
168
+ * 交接 + 开新会话(`/migrate`、自动迁移)。
169
+ *
170
+ * 策略(`strategy`):
171
+ * - `omit`(默认):历史标记 `omit: true`(保留可审计),末尾追加交接断点;
172
+ * - `prune`:物理剔除历史,只留 `definition.messages` + 交接断点。
173
+ */
174
+ migrate(params: MigrateParams): Promise<HandoffContext>;
175
+ /** 串行化 + 防重入:交接与迁移共用同一把锁 */
176
+ private withBusy;
177
+ private runHandoff;
178
+ /** 自交接是否可用:需要 `write` 工具 + `send` 能力 */
179
+ private supportsSelfHandoff;
180
+ /**
181
+ * 自交接回合:让原 agent 停工、核实、写文档,并校验文件确实落盘。
182
+ *
183
+ * 失败判定(任一成立即视为失败,交由上层单向降级 summary):
184
+ * - 文件不存在 / 小于阈值;
185
+ * - 模型/工具链抛错(例如没有权限、被护栏中断)。
186
+ */
187
+ private runSelfHandoff;
188
+ /** 独立摘要:一次模型调用,用同一份 Skill 正文作为规范 */
189
+ private generateSummary;
190
+ /** 落盘(仅 `summary` 模式需要;`self` 模式由 agent 的 write 工具完成) */
191
+ private writeHandoffFile;
192
+ /** 交接文档是否可用:文件存在且不小于阈值(不读内容,避免二次读取) */ private isHandoffFileReady;
193
+ /** 读取 `handoff` Skill 正文;读不到时回落到兜底模板 */
194
+ private readSkillContent;
195
+ /** 守卫按 agent 注册一次(`Agent.use()` 无移除 API,故复用实例 + 开关) */
196
+ private ensureGuard;
197
+ /** 钩子容错:抛错只记录,不中断交接(与既有迁移钩子语义一致) */
198
+ private safeHook;
199
+ }