@ai-zen/agents-sdk 1.0.0-alpha.2 → 1.0.0-alpha.21
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.
- package/CHANGELOG.md +368 -0
- package/README.md +15 -14
- package/README.zh.md +15 -14
- package/dist/agent-plugins/AutoMigratePlugin.d.ts +35 -9
- package/dist/agent-plugins/AutoMigratePlugin.js +23 -10
- package/dist/agent-plugins/ContextGuardPlugin.d.ts +14 -16
- package/dist/agent-plugins/ContextGuardPlugin.js +13 -17
- package/dist/config/ConfigManager.d.ts +57 -8
- package/dist/config/ConfigManager.js +166 -25
- package/dist/config/constants.d.ts +7 -0
- package/dist/config/constants.js +38 -13
- package/dist/config/factory-skills.d.ts +33 -0
- package/dist/config/factory-skills.js +126 -0
- package/dist/config/migrate.d.ts +67 -0
- package/dist/config/migrate.js +127 -0
- package/dist/config/reasoning-presets.d.ts +37 -0
- package/dist/config/reasoning-presets.js +64 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.js +10 -2
- package/dist/runtime/HandoffGuardPlugin.d.ts +35 -0
- package/dist/runtime/HandoffGuardPlugin.js +65 -0
- package/dist/runtime/HandoffService.d.ts +245 -0
- package/dist/runtime/HandoffService.js +472 -0
- package/dist/runtime/SdkAgent.d.ts +7 -1
- package/dist/runtime/SdkAgent.js +3 -0
- package/dist/runtime/createAgent.d.ts +16 -1
- package/dist/runtime/createAgent.js +10 -5
- package/dist/runtime/createModel.d.ts +8 -4
- package/dist/runtime/createModel.js +19 -2
- package/dist/scope/plugins/builtin/BatchEditTool.js +5 -2
- package/dist/scope/plugins/builtin/DownloadFileTool.js +1 -1
- package/dist/scope/plugins/builtin/EditTool.js +6 -1
- package/dist/scope/plugins/builtin/ExecTool.js +2 -2
- package/dist/scope/plugins/builtin/FindTextTool.js +6 -6
- package/dist/scope/plugins/builtin/GenerateImageTool.d.ts +1 -1
- package/dist/scope/plugins/builtin/GenerateImageTool.js +7 -7
- package/dist/scope/plugins/builtin/GlobTool.js +1 -1
- package/dist/scope/plugins/builtin/InspectFileTool.d.ts +2 -2
- package/dist/scope/plugins/builtin/InspectFileTool.js +5 -5
- package/dist/scope/plugins/builtin/LsTool.js +1 -1
- package/dist/scope/plugins/builtin/{ReadFileTool.d.ts → ReadTool.d.ts} +1 -1
- package/dist/scope/plugins/builtin/{ReadFileTool.js → ReadTool.js} +3 -3
- package/dist/scope/plugins/builtin/{ExecAsyncTool.d.ts → SpawnTool.d.ts} +1 -1
- package/dist/scope/plugins/builtin/{ExecAsyncTool.js → SpawnTool.js} +2 -2
- package/dist/scope/plugins/builtin/ViewImageTool.d.ts +6 -4
- package/dist/scope/plugins/builtin/ViewImageTool.js +43 -59
- package/dist/scope/plugins/builtin/{WriteFileTool.d.ts → WriteTool.d.ts} +1 -1
- package/dist/scope/plugins/builtin/{WriteFileTool.js → WriteTool.js} +2 -2
- package/dist/scope/plugins/builtin/index.d.ts +4 -4
- package/dist/scope/plugins/builtin/index.js +7 -7
- package/dist/scope/plugins/builtin/outputGuard.d.ts +2 -2
- package/dist/scope/plugins/builtin/outputGuard.js +1 -1
- package/dist/scope/plugins/mcp/McpScopePlugin.d.ts +1 -1
- package/dist/scope/plugins/mcp/McpScopePlugin.js +4 -3
- package/dist/scope/plugins/mcp/mcpTools.d.ts +9 -3
- package/dist/scope/plugins/mcp/mcpTools.js +196 -18
- package/dist/scope/plugins/skills/discover.js +1 -2
- package/dist/scope/plugins/skills/skillTools.js +0 -2
- package/dist/scope/plugins/subagents/subAgentTools.js +2 -1
- package/dist/shared/content-blocks.d.ts +108 -0
- package/dist/shared/content-blocks.js +253 -0
- package/dist/shared/errors.d.ts +4 -8
- package/dist/shared/errors.js +8 -14
- package/dist/types/index.d.ts +103 -10
- package/package.json +3 -3
- package/dist/runtime/TaskMigrationService.d.ts +0 -128
- package/dist/runtime/TaskMigrationService.js +0 -211
package/dist/index.js
CHANGED
|
@@ -17,16 +17,24 @@ 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";
|
|
22
|
+
// 出厂资产 — 思考策略表(随版本释放到 <basePath>/reasoning-presets.json)
|
|
23
|
+
// 表是跨客户端共享的「定义 + 出厂默认」,SDK 不拿它生成请求参数
|
|
24
|
+
export { FACTORY_REASONING_PRESETS, REASONING_PRESETS_FILE, } from "./config/reasoning-presets.js";
|
|
20
25
|
// CRUD — 实体仓储(仅 Agent 定义等能力实体)
|
|
21
26
|
export { EntityRepository } from "./shared/EntityRepository.js";
|
|
22
27
|
export { AgentRepository } from "./crud/AgentRepository.js";
|
|
28
|
+
// 文件 → 内容块(统一入口)— 上层应用构造媒体消息时请走这里,不要自己判断上传还是内联
|
|
29
|
+
export { fileToContentSection, resolveModelAndEndpoint, supportsFilesApi, mimeOf, toDataUrl, base64ByteLength, contentHash, clearUploadCache, MIME_BY_EXT, DEFAULT_MAX_INLINE_BYTES, } from "./shared/content-blocks.js";
|
|
23
30
|
// 运行时
|
|
24
31
|
export { createModel } from "./runtime/createModel.js";
|
|
25
32
|
export { SdkAgent } from "./runtime/SdkAgent.js";
|
|
26
33
|
export { createAgent } from "./runtime/createAgent.js";
|
|
27
34
|
export { McpConnectionManager } from "./scope/plugins/mcp/McpConnectionManager.js";
|
|
28
35
|
export { SdkCallbackTool } from "./scope/plugins/builtin/SdkCallbackTool.js";
|
|
29
|
-
export {
|
|
36
|
+
export { HandoffAbortedError, HandoffService } from "./runtime/HandoffService.js";
|
|
37
|
+
export { HandoffGuardPlugin } from "./runtime/HandoffGuardPlugin.js";
|
|
30
38
|
// 插件
|
|
31
39
|
export { AutoMigratePlugin } from "./agent-plugins/AutoMigratePlugin.js";
|
|
32
40
|
export { AutoRefreshToolsPlugin } from "./agent-plugins/AutoRefreshToolsPlugin.js";
|
|
@@ -34,7 +42,7 @@ export { ContextGuardPlugin } from "./agent-plugins/ContextGuardPlugin.js";
|
|
|
34
42
|
export { UnknownToolHintPlugin } from "./agent-plugins/UnknownToolHintPlugin.js";
|
|
35
43
|
// 工具 — 内置工具类 + 动态工具工厂
|
|
36
44
|
export { BUILTIN_TOOL_CLASSES } from "./scope/plugins/builtin/index.js";
|
|
37
|
-
export { CwdTool,
|
|
45
|
+
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
46
|
export { GenerateImageTool } from "./scope/plugins/builtin/GenerateImageTool.js";
|
|
39
47
|
export { ViewImageTool } from "./scope/plugins/builtin/ViewImageTool.js";
|
|
40
48
|
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,245 @@
|
|
|
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
|
+
* 交接被调用方取消(`AbortSignal` 触发)。
|
|
23
|
+
*
|
|
24
|
+
* 单独成类型而不是复用 `Error`,原因有两个:
|
|
25
|
+
* - 调用方(如 TUI)要把「用户按了 Ctrl+C」与「交接真的失败了」区分开 ——
|
|
26
|
+
* 前者应静默收尾并提示「已取消」,后者才该报错;
|
|
27
|
+
* - 它**不可降级**:`self` 被取消后绝不能退到 `summary` 再跑一遍。
|
|
28
|
+
* 把「用户中止」当成「模型没写出文档」,会白白多烧一轮全量输入。
|
|
29
|
+
*/
|
|
30
|
+
export declare class HandoffAbortedError extends Error {
|
|
31
|
+
constructor();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 一次交接的完整上下文 —— 贯穿「交接前 → 写文档 → 迁移后」,供钩子与调用方使用。
|
|
35
|
+
*/
|
|
36
|
+
export interface HandoffContext {
|
|
37
|
+
/** 当前对话 Agent(引用不变,仅替换消息列表) */
|
|
38
|
+
agent: SdkAgent;
|
|
39
|
+
/** **实际**使用的执行者(`self` 失败降级后会是 `summary`) */
|
|
40
|
+
mode: HandoffMode;
|
|
41
|
+
/** 调用方请求的执行者 */
|
|
42
|
+
requestedMode: HandoffMode;
|
|
43
|
+
/** 交接文档绝对路径 */
|
|
44
|
+
handoffPath: string;
|
|
45
|
+
/** 交接文档正文:仅 `summary` 模式由模型返回;`self` 模式不读盘,故为 undefined */
|
|
46
|
+
handoffDoc?: string;
|
|
47
|
+
/** 生成文档所用模型名 */
|
|
48
|
+
model: string;
|
|
49
|
+
/** 当前 prompt token 用量(自动迁移时由插件提供;手动触发可省略) */
|
|
50
|
+
promptTokens?: number;
|
|
51
|
+
/** 上下文阈值(自动迁移时由插件提供;手动触发可省略) */
|
|
52
|
+
handoffTokens?: number;
|
|
53
|
+
/** 本次是否会在交接后开新会话(`migrate()` 为 true,`handoff()` 为 false)。钩子据此区分两种语义 */
|
|
54
|
+
migrating: boolean;
|
|
55
|
+
/** 交接前消息总数 */
|
|
56
|
+
messageCountBefore: number;
|
|
57
|
+
/** 序列化后的历史(仅 `summary` 模式填充;`self` 模式为空串) */
|
|
58
|
+
historyText: string;
|
|
59
|
+
}
|
|
60
|
+
/** 交接服务构造参数 */
|
|
61
|
+
export interface HandoffServiceOptions {
|
|
62
|
+
/**
|
|
63
|
+
* 交接文档目录(绝对路径推荐)。
|
|
64
|
+
* 默认按进程 cwd 推导为 `<cwd>/.ai-zen/handoffs`;多工作区场景请显式传入,勿依赖默认值。
|
|
65
|
+
*/
|
|
66
|
+
handoffDir?: string;
|
|
67
|
+
/** 默认执行者,默认 `self`(可被 `handoff()/migrate()` 的参数覆盖) */
|
|
68
|
+
mode?: HandoffMode;
|
|
69
|
+
/** 迁移后历史处理策略,默认 `omit`(可被 `migrate()` 的参数覆盖) */
|
|
70
|
+
strategy?: MigrationStrategy;
|
|
71
|
+
/** 查找 `handoff` Skill 的目录列表(优先级从高到低);为空则直接用兜底模板 */
|
|
72
|
+
skillDirs?: string[];
|
|
73
|
+
/** `summary` 模式所用客户端(可选;未传则回退到 agent.client) */
|
|
74
|
+
client?: OpenAI;
|
|
75
|
+
/** `summary` 模式所用模型名(可选;未传则回退到 agent.model) */
|
|
76
|
+
model?: string;
|
|
77
|
+
/** `summary` 模式所用模型参数(可选;未传则回退到 agent.modelConfig) */
|
|
78
|
+
modelConfig?: Record<string, unknown>;
|
|
79
|
+
/** 交接文档有效性阈值(字节),默认 400。低于此值视为「没写出来」 */
|
|
80
|
+
minHandoffBytes?: number;
|
|
81
|
+
/** 交接回合开始前触发(此时 `agent.messages` 仍是完整旧历史) */
|
|
82
|
+
onBeforeHandoff?: (ctx: HandoffContext) => void | Promise<void>;
|
|
83
|
+
/** 交接文档写好后、迁移(清历史)之前触发 */
|
|
84
|
+
onHandoff?: (ctx: HandoffContext) => void | Promise<void>;
|
|
85
|
+
/** 迁移完成后触发(此时交接断点已注入 `agent.messages`) */
|
|
86
|
+
onMigrated?: (ctx: HandoffContext) => void | Promise<void>;
|
|
87
|
+
/** 可注入日志(可选,默认使用全局单例) */
|
|
88
|
+
logger?: Logger;
|
|
89
|
+
}
|
|
90
|
+
/** `handoff()` 参数 */
|
|
91
|
+
export interface HandoffParams {
|
|
92
|
+
agent: SdkAgent;
|
|
93
|
+
/** 本次执行者(覆盖构造时的默认值) */
|
|
94
|
+
mode?: HandoffMode;
|
|
95
|
+
/** 当前 prompt token 用量(可选,仅透传给钩子) */
|
|
96
|
+
promptTokens?: number;
|
|
97
|
+
/** 上下文阈值(可选,仅透传给钩子) */
|
|
98
|
+
handoffTokens?: number;
|
|
99
|
+
/** 会话 id(可选,用于交接文件名,便于回溯) */
|
|
100
|
+
conversationId?: string;
|
|
101
|
+
/**
|
|
102
|
+
* 取消信号(可选)。触发后交接**立即停止且不降级**,抛 {@link HandoffAbortedError}。
|
|
103
|
+
*
|
|
104
|
+
* 两种执行者都要认它:
|
|
105
|
+
* - `summary`:直接作为 `chat.completions.create` 的 RequestOptions 传下去,
|
|
106
|
+
* 请求会真的被中断(否则该请求根本无从取消);
|
|
107
|
+
* - `self`:`agent.abort()` 不抛错,所以每个 `send()` 之后要主动检查 `aborted`。
|
|
108
|
+
*/
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
}
|
|
111
|
+
/** `migrate()` 参数 */
|
|
112
|
+
export interface MigrateParams extends HandoffParams {
|
|
113
|
+
/** 本次迁移的历史处理策略(覆盖构造时的默认值) */
|
|
114
|
+
strategy?: MigrationStrategy;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 任务交接服务。
|
|
118
|
+
*
|
|
119
|
+
* 职责分层(这是本模块唯一的设计主线):
|
|
120
|
+
* - `handoff()`:**只产出交接文档**(停工 → 写文档 → 校验 → 失败降级),不动会话;
|
|
121
|
+
* - `migrate()`:`handoff()` 之后清历史并开新会话(原「任务迁移」语义)。
|
|
122
|
+
*
|
|
123
|
+
* 模板的唯一真相源是 `handoff` Skill(`SKILL.md`);代码只负责
|
|
124
|
+
* 「读正文 → 注入 → 校验文件 → 读不到时用兜底模板」,不持有第二份章节清单。
|
|
125
|
+
*
|
|
126
|
+
* 重入:`handoff()` 与 `migrate()` 共用 `busy` 标志。交接回合本身也是一次 `send()`,
|
|
127
|
+
* 若不在 busy 期间让 `AutoMigratePlugin` / `ContextGuardPlugin` 让行,就会「交接触发迁移、
|
|
128
|
+
* 迁移再触发交接」无限递归,或在最满的时刻被护栏直接击杀。
|
|
129
|
+
*/
|
|
130
|
+
export declare class HandoffService {
|
|
131
|
+
/** 交接文档兜底模板(仅在读不到 Skill 时使用) */
|
|
132
|
+
static readonly FALLBACK_TEMPLATE: string;
|
|
133
|
+
private readonly handoffDirPath;
|
|
134
|
+
private readonly defaultMode;
|
|
135
|
+
private readonly defaultStrategy;
|
|
136
|
+
private readonly skillDirs;
|
|
137
|
+
private readonly client?;
|
|
138
|
+
private readonly model?;
|
|
139
|
+
private readonly modelConfig?;
|
|
140
|
+
private readonly minHandoffBytes;
|
|
141
|
+
private readonly onBeforeHandoff?;
|
|
142
|
+
private readonly onHandoff?;
|
|
143
|
+
private readonly onMigrated?;
|
|
144
|
+
private readonly logger;
|
|
145
|
+
/** 交接/迁移进行中标志(插件据此让行) */
|
|
146
|
+
private _busy;
|
|
147
|
+
/** 交接回合的工具守卫(同一实例复用 + arm/disarm,避免插件数组无限增长) */
|
|
148
|
+
private guard?;
|
|
149
|
+
/** 已注册过守卫的 agent(每个 agent 只 use 一次) */
|
|
150
|
+
private readonly guardedAgents;
|
|
151
|
+
constructor(options?: HandoffServiceOptions);
|
|
152
|
+
/** 是否正在交接/迁移(`AutoMigratePlugin` / `ContextGuardPlugin` 据此让行) */
|
|
153
|
+
get busy(): boolean;
|
|
154
|
+
/** 交接文档目录 */
|
|
155
|
+
get handoffDir(): string;
|
|
156
|
+
/**
|
|
157
|
+
* 交接文件名:`<YYYYMMDD-HHmmss>-<会话id前8位>.md`(本地时间,便于人眼归档)。
|
|
158
|
+
*/
|
|
159
|
+
static createHandoffFileName(date?: Date, conversationId?: string): string;
|
|
160
|
+
/**
|
|
161
|
+
* 拼装「本轮交接」指令:`[Skill 正文] + [本次交接参数]`。
|
|
162
|
+
*
|
|
163
|
+
* 不在 Skill 正文里发明占位符(本体系没有模板变量引擎),参数一律由代码拼在后面,
|
|
164
|
+
* 这样 Skill 只讲「怎么写」、代码只讲「写到哪、有什么能力」。
|
|
165
|
+
*/
|
|
166
|
+
static createHandoffInstruction(params: {
|
|
167
|
+
skillContent: string;
|
|
168
|
+
handoffPath: string;
|
|
169
|
+
/** 执行者是否具备工具能力(`summary` 模式为 false) */
|
|
170
|
+
toolsAvailable: boolean;
|
|
171
|
+
}): string;
|
|
172
|
+
/**
|
|
173
|
+
* 生成迁移后追加的「交接断点」消息(role=user):只给路径,让接手方自己 read。
|
|
174
|
+
*
|
|
175
|
+
* 为什么不内联全文:接手方第一次真正需要的是「去哪里取交接」,读一次文件的输入成本
|
|
176
|
+
* 与内联全文相当,但内联会让每条请求都携带全文;路径方案把成本推迟到真正需要时。
|
|
177
|
+
*/
|
|
178
|
+
static createPostMessages(handoffPath: string): AgentNS.Message[];
|
|
179
|
+
/** 序列化消息为 `[role]: content` 文本(仅 `summary` 模式使用) */
|
|
180
|
+
static serializeMessages(messages: AgentNS.Message[]): string;
|
|
181
|
+
/**
|
|
182
|
+
* 只产出交接文档,**不改动会话**(`/handoff`)。
|
|
183
|
+
*
|
|
184
|
+
* 不清理历史:交接回合(指令 + 工具调用 + 回执)会留在 `agent.messages` 里,
|
|
185
|
+
* 用户可继续让 agent 打磨这份文档(重写同一路径即可)。
|
|
186
|
+
*/
|
|
187
|
+
handoff(params: HandoffParams): Promise<HandoffContext>;
|
|
188
|
+
/**
|
|
189
|
+
* 交接 + 开新会话(`/migrate`、自动迁移)。
|
|
190
|
+
*
|
|
191
|
+
* 策略(`strategy`):
|
|
192
|
+
* - `omit`(默认):历史标记 `omit: true`(保留可审计),末尾追加交接断点;
|
|
193
|
+
* - `prune`:物理剔除历史,只留 `definition.messages` + 交接断点。
|
|
194
|
+
*/
|
|
195
|
+
migrate(params: MigrateParams): Promise<HandoffContext>;
|
|
196
|
+
/** 串行化 + 防重入:交接与迁移共用同一把锁 */
|
|
197
|
+
private withBusy;
|
|
198
|
+
/**
|
|
199
|
+
* 把底层各种「取消」形态统一成 {@link HandoffAbortedError}。
|
|
200
|
+
*
|
|
201
|
+
* 取消可能从三处冒上来:`self` 回合后的主动检查(已是 HandoffAbortedError)、
|
|
202
|
+
* `summary` 请求被 abort 时 OpenAI SDK 抛的 `APIUserAbortError`、以及
|
|
203
|
+
* `readSkillContent` 这类前置 await 期间被取消。调用方只需要认一个类型。
|
|
204
|
+
*/
|
|
205
|
+
private runCatchingAbort;
|
|
206
|
+
/** 取消点检查:每跨过一个阶段就确认一次,避免取消后继续往下一步冲 */
|
|
207
|
+
private throwIfAborted;
|
|
208
|
+
private fileExists;
|
|
209
|
+
/**
|
|
210
|
+
* 取消后清掉半成品文档。
|
|
211
|
+
*
|
|
212
|
+
* 只删「本次新建」的文件:交接文件名含时间戳,正常不会撞已有文件,但调用方也
|
|
213
|
+
* 可以把 `handoffDir` 指向人工目录;不管怎样都不该删掉别人的文件。
|
|
214
|
+
* 留着半成品更糟 —— 下一轮接手方会把不完整的内容当成真实交接读进去。
|
|
215
|
+
*/
|
|
216
|
+
private discardPartialFile;
|
|
217
|
+
private runHandoff;
|
|
218
|
+
/** 自交接是否可用:需要 `write` 工具 + `send` 能力 */
|
|
219
|
+
private supportsSelfHandoff;
|
|
220
|
+
/**
|
|
221
|
+
* 自交接回合:让原 agent 停工、核实、写文档,并校验文件确实落盘。
|
|
222
|
+
*
|
|
223
|
+
* 失败判定(任一成立即视为失败,交由上层单向降级 summary):
|
|
224
|
+
* - 文件不存在 / 小于阈值;
|
|
225
|
+
* - 模型/工具链抛错(例如没有权限、被护栏中断)。
|
|
226
|
+
*
|
|
227
|
+
* **取消不算失败**:`signal` 已触发时抛 {@link HandoffAbortedError},既不走
|
|
228
|
+
* 「文件没写好→重试一次」也不降级 summary。 这条分支必须显式写:
|
|
229
|
+
* `agent.abort()` 不抛错(core 把消息标记为 Aborted 后正常返回),否则「用户
|
|
230
|
+
* 按了 Ctrl+C」会被下面的文件校验误判成「模型没写出文档」,于是重试一轮、再降级
|
|
231
|
+
* 一轮 —— 用户以为取消了,实际多烧了两轮全量输入。
|
|
232
|
+
*/
|
|
233
|
+
private runSelfHandoff;
|
|
234
|
+
/** 独立摘要:一次模型调用,用同一份 Skill 正文作为规范 */
|
|
235
|
+
private generateSummary;
|
|
236
|
+
/** 落盘(仅 `summary` 模式需要;`self` 模式由 agent 的 write 工具完成) */
|
|
237
|
+
private writeHandoffFile;
|
|
238
|
+
/** 交接文档是否可用:文件存在且不小于阈值(不读内容,避免二次读取) */ private isHandoffFileReady;
|
|
239
|
+
/** 读取 `handoff` Skill 正文;读不到时回落到兜底模板 */
|
|
240
|
+
private readSkillContent;
|
|
241
|
+
/** 守卫按 agent 注册一次(`Agent.use()` 无移除 API,故复用实例 + 开关) */
|
|
242
|
+
private ensureGuard;
|
|
243
|
+
/** 钩子容错:抛错只记录,不中断交接(与既有迁移钩子语义一致) */
|
|
244
|
+
private safeHook;
|
|
245
|
+
}
|