@zhushanwen/pi-subagent-workflow 9.4.1 → 9.5.0

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.
@@ -1,6 +1,13 @@
1
1
  // src/interface/tool/subagent-tool-schema.ts
2
2
  //
3
- // `subagent` 工具的参数 schema 纯常量叶子(零运行时依赖)。
3
+ // subagent 工具组的参数 schema 纯常量叶子(零运行时依赖)。
4
+ //
5
+ // tool-declaration-slimming D3 拆分终态:按动作拆为三个工具(拆分前是 6 action 拍平 21 参数的单一工具)
6
+ // 拆为三工具,schema 各自真实反映必填性:
7
+ // - `subagent_start` → SubagentStartParams(task/slug 必填 + 13 个 start 场景可选字段)
8
+ // - `subagent_manage` → SubagentManageParams(action: list/cancel/close)
9
+ // - `subagent_message`→ SubagentMessageParams(action: message/fork-from)
10
+ // manage/message 内部保留小 action 枚举是 D3 范围裁决(参数同构、条件必填失真可忽略)。
4
11
  //
5
12
  // 抽取自 subagent-tool.ts(跨包契约另一半):subagent-tool 依赖树沉重(pi SDK /
6
13
  // handler / render 链),structured-output 侧的跨包契约测试若从它 import schema
@@ -26,87 +33,76 @@ import { SLUG_MAX_LENGTH } from "@zhushanwen/subagent-core";
26
33
 
27
34
  export { SLUG_MAX_LENGTH };
28
35
 
29
- // Params schema(跨包契约测试的真实 typebox 校验入口)。
30
- //
31
- // action:"start" 的 16 字段(task/slug/agent/model/...)拍平在顶层,不再用 startParam
32
- // 嵌套容器包。原因:弱模型(GLM/DeepSeek)信任 schema 结构信号 > 文本信号,经常省略
33
- // startParam 嵌套层把 task/slug 直接平铺到顶层导致调用失败。拍平后 schema 结构与模型
34
- // 的自然倾向一致,消除这层误用。task/slug 必填性由 startHandler runtime 校验(flat
35
- // JSON Schema 无法表达「action 条件必填」)。
36
- //
37
- // TODO(long-term, option-A): listParam/cancelParam 仍标 Optional 也是 flat JSON Schema
38
- // 表达「action 分发条件必填」的妥协——长期方案是拆成 3 个独立 tool
39
- // (subagent_start / subagent_list / subagent_cancel),让每个 tool 的 schema 真实
40
- // 反映必填性。勿在此基础上继续堆 action 条件逻辑——要加就拆 tool。
41
- export const SubagentParams = Type.Object({
42
- action: StringEnum(["start", "list", "cancel", "message", "close", "fork-from"], {
43
- description: "Operation: 'start' runs a subagent (for 2+ independent tasks dispatched together in ONE call, use the `subagents` tool instead — it batches them and returns all results as one notification), 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to any of your subagents (running or idle — an idle one transparently revives and continues on its original session file), 'close' archives a subagent (immediately when idle; after the current round, or immediately with force:true, when running), 'fork-from' spawns a NEW subagent inheriting an older one's history (recovery for restart-disconnected subagents; the old record is untouched).",
36
+ // ── subagent_start:task/slug 必填由 schema 表达(拆分收益本体,D3)──
37
+
38
+ export const SubagentStartParams = Type.Object({
39
+ task: Type.String({
40
+ description: "The task to execute. Throws if missing or whitespace-only.",
44
41
  }),
45
- // ── action:"start" fields (flattened to top level). task/slug REQUIRED for start. ──
46
- // Missing/empty task or slug throws at runtime (startHandler).
47
- // (flat JSON Schema can't express conditional requirement — see file-level TODO.)
48
- task: Type.Optional(Type.String({
49
- description: "REQUIRED for action:'start'. The task for the subagent to execute. Throws if missing or whitespace-only. For 2+ independent tasks in one dispatch, use the `subagents` tool (tasks array) instead of N separate starts — same subagent semantics, one call, one combined result notification.",
50
- })),
51
- slug: Type.Optional(Type.String({
42
+ slug: Type.String({
52
43
  description:
53
- "REQUIRED for action:'start'. Short label (≤35 chars) for this subagent, e.g. 'fix-login', 'extract-urls'. " +
44
+ "Short label (≤35 chars), e.g. 'fix-login'. " +
54
45
  "Shown in TUI to distinguish concurrent subagents.",
55
46
  maxLength: SLUG_MAX_LENGTH,
56
- })),
47
+ }),
57
48
  agent: Type.Optional(Type.String({
58
- description: 'Agent ref: absolute path to an agent .md file — usually a <location> from <available_subagents>, but ANY absolute .md path works if the file is a readable agent definition (the list is not required). Relative paths and bare names are rejected. If omitted, defaults to "general-purpose" — a generic agent that inherits the main agent\'s model and project context.',
49
+ description: 'Agent ref: absolute path to an agent .md file — usually a <location> from <available_subagents>, but ANY absolute .md path works. Relative paths and bare names are rejected. Omit for the default general-purpose agent.',
59
50
  })),
60
51
  model: Type.Optional(Type.String({
61
- description: 'Model override in "provider/modelId" format. CASE-SENSITIVE: the string must equal a registry entry exactly, including letter case (e.g. "zai-coding-cn/GLM-5.3-Flash", NOT "zai-coding-cn/glm-5.3-flash"). A non-exact match is rejected immediately with "Did you mean" suggestions — retry with the exact suggested string; the system never auto-corrects your input. Resolution order (top wins): (1) this param, (2) agent .md frontmatter model, (3) the main agent\'s current model (zero-config default). An explicit model (param or frontmatter) that is missing or unauthorized THROWS — there is no silent fallback to the main model. Omit this param to inherit the main model.',
52
+ // 缺省继承主 agent 的纪律句已删(tool-declaration-slimming D1 单源化:缺省继承
53
+ // 属跨工具通用纪律,收敛到 <available_subagents> 注入段(subagent-list-injector.ts
54
+ // SUBAGENT_DISPATCH_SHARED_RULES)说一次,原文见 git 历史。Resolution order
55
+ // 是本参数的功能语义(三层优先级链),保留。
56
+ description: 'Model override in "provider/modelId" format. CASE-SENSITIVE — must equal a registry entry exactly; mismatches are rejected with "Did you mean" suggestions (retry with the exact string). Resolution order (top wins): this param > agent .md frontmatter > the main agent\'s current model. A missing or unauthorized explicit model THROWS — no silent fallback.',
62
57
  })),
63
58
  thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
64
- description: "Thinking depth override (derived from THINKING_ORDER SSOT, includes 'max'). Omit to default to the model's highest available level (not the main agent's level).",
59
+ description: "Thinking depth override (THINKING_ORDER SSOT, includes 'max'). Omit for the model's highest available level.",
65
60
  })),
66
61
  skillPath: Type.Optional(Type.String({
67
62
  description:
68
- "Absolute path to a skill directory, injected into the subagent's pi process via --skill " +
69
- "(e.g. a path under .agents/skills/ already resolved for the caller). Must be an absolute path; " +
70
- "'..' traversal segments are rejected.",
63
+ "Absolute path to a skill directory, injected via --skill. Must be absolute; '..' segments rejected.",
71
64
  pattern: "^/",
72
65
  })),
73
66
  appendSystemPrompt: Type.Optional(Type.Array(Type.String())),
74
67
  schema: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
75
68
  maxTurns: Type.Optional(Type.Number({
76
- description: "Turn limit. The subagent is terminated via SIGTERM after maxTurns turn_end events + graceTurns of slack. There is no graceful wrap-up message — the process is killed. 0 or omitted = unlimited.",
69
+ description: "Turn limit: SIGTERM after maxTurns turn_end events + graceTurns slack (no graceful wrap-up). 0/omitted = unlimited.",
77
70
  })),
78
71
  graceTurns: Type.Optional(Type.Number({
79
- description: "Extra turns allowed after maxTurns is reached before SIGTERM (default 2). Only meaningful when maxTurns is set.",
72
+ description: "Extra turns after maxTurns before SIGTERM (default 2). Only with maxTurns.",
80
73
  })),
81
74
  fork: Type.Optional(Type.Boolean({
82
- description: "Fork mode: inherit the parent's conversation context. When true, the subagent receives the parent's session file via --fork and builds a branched conversation (it sees prior turns/messages). The subagent still runs in a separate spawned child process (process isolation) — fork is about context inheritance, not process sharing; independent of worktree (file-system isolation, see worktree param). When to use: only when the task extends from the parent and genuinely needs key information from the parent's conversation history that a self-contained task prompt cannot carry — most tasks a plain prompt can describe do NOT need fork, so keep false by default and enable only when the user explicitly asks or the task truly depends on seeing prior turns. Caveat: fork drags in the parent's dispatch records and unrelated task context, polluting the subagent (it cannot tell 'context meant for me' from 'parent dispatching me'); when state lives in an external store the subagent can query (e.g., cw handoff), prefer that over fork.",
75
+ description: "Fork mode: inherit the parent's conversation via --fork (the subagent sees prior turns). Still a separate process — context inheritance, not process sharing; independent of worktree. Only for tasks that genuinely extend prior turns; keep false by default (fork drags in unrelated parent context).",
83
76
  })),
84
77
  worktree: Type.Optional(Type.Boolean({
85
- description: "Worktree isolation: run the subagent in a dedicated git worktree, providing file-system level isolation from the parent session (prevents concurrent file-write conflicts). Independent of fork — worktree may be combined with fork:false (file isolation does not require context inheritance). When to use: parallel development scenarios where multiple agents write files concurrently and need isolated working directories (each gets its own checkout; merge later); leave false for single-agent or read-only tasks.",
78
+ description: "Worktree isolation: run in a dedicated git worktree (file-system isolation — prevents write conflicts). Independent of fork. For parallel multi-agent writes; false for single-agent or read-only tasks.",
86
79
  })),
87
80
  cwd: Type.Optional(Type.String({
88
- description: 'Override the working directory for the subagent execution. Must be an absolute path (no "~" shorthand, no relative paths); ".." segments are rejected. Defaults to the parent session\'s cwd.',
81
+ description: "Working directory override. Absolute path only (no '~', no '..'). Defaults to the parent session's cwd.",
89
82
  pattern: "^/",
90
83
  })),
91
84
  // [modeless 波5] conversation 参数已删除:chatMode 字段消亡后「模式」不存在——
92
85
  // 一切 record 永续可续聊(idle 后 message 即续、fork-from 可继承),无模式开关可表达。
93
86
  idleTimeoutMs: Type.Optional(Type.Number({
94
87
  description:
95
- "Idle-recycle cadence for ALL subagents (modeless: every subagent stays continuable — this is NOT a mode switch). Controls how long an idle subagent (between rounds, no activity) stays before being automatically archived. " +
96
- "Default: 300000 (5min). Raise it for long-interval collaboration where your next message may arrive more than 5min after a round ends. " +
97
- "Pass 0 or a negative value to DISABLE idle recycling entirely (subagent stays available until you close it). " +
98
- "Priority: this param > env TAIJI_SUBAGENT_IDLE_TIMEOUT_MS > default.",
88
+ "Idle-recycle cadence for ALL subagents (modeless, not a mode switch): idle subagent auto-archives after this long. Default 300000 (5min); 0/negative disables. Priority: this param > env TAIJI_SUBAGENT_IDLE_TIMEOUT_MS > default.",
99
89
  })),
100
90
  engine: Type.Optional(StringEnum(["pi", "zcode"], {
101
91
  description:
102
- "Execution engine for this subagent. Omit to inherit the global config. " +
103
- "Three-layer priority: this parameter > agent .md frontmatter engine > config.json defaultEngine. " +
104
- "Non-pi engines do not support fork/worktree (rejected before the subagent is created).",
92
+ "Execution engine. Omit to inherit config. Priority: this param > agent .md frontmatter > config.json defaultEngine. Non-pi engines reject fork/worktree.",
105
93
  })),
106
94
  // [collect 退役] collect 参数已删除——批量编排的唯一入口是 `subagents` tool(fan-out run
107
95
  // 管道),单数 start 不再有通知路由选项。旧调用形态(显式带 collect)不会被 pi 参数校验
108
- // 拒绝(typebox Object 不产 additionalProperties → 未知字段静默放行),迁移期提示见
109
- // subagent-tool.ts description 的 start 段。
96
+ // 拒绝(typebox Object 不产 additionalProperties → 未知字段静默放行),通知路由不再生效;
97
+ // 声明层无迁移期提示(原 description 的退役说明已随声明压缩删除,未在其他层重新承载)。
98
+ });
99
+
100
+ // ── subagent_manage:list / cancel / close 三管理动作 ──
101
+
102
+ export const SubagentManageParams = Type.Object({
103
+ action: StringEnum(["list", "cancel", "close"], {
104
+ description: "Operation: 'list' shows subagents, 'cancel' stops a background one, 'close' archives (recoverable).",
105
+ }),
110
106
  // action:"list" → listParam OPTIONAL (all fields optional, defaults apply). Ignored by other actions.
111
107
  listParam: Type.Optional(Type.Object({
112
108
  includeFinished: Type.Optional(Type.Boolean({
@@ -114,29 +110,49 @@ export const SubagentParams = Type.Object({
114
110
  })),
115
111
  includeWorkflow: Type.Optional(Type.Boolean({
116
112
  description:
117
- "Include records dispatched by workflow scripts (agent() calls). Default false — subagents " +
118
- "spawned inside workflows are hidden from list output. Set true only when troubleshooting a " +
119
- "workflow run's subagents (e.g. a workflow step failed and you need to inspect its records). " +
120
- "Pair with includeFinished:true — finished workflow records stay hidden unless both flags are set.",
113
+ "Include workflow-dispatched records. Default false; pair with includeFinished:true for finished ones.",
121
114
  })),
122
115
  limit: Type.Optional(Type.Number({
123
116
  description: "Max items to return. Default 20, clamped to [1, 100].",
124
117
  })),
125
118
  })),
126
- // action:"cancel" → cancelParam.subagentId REQUIRED. Throws if missing. Ignored by other actions.
119
+ // action:"cancel" → cancelParam.subagentId REQUIRED (handler throws if missing).
127
120
  cancelParam: Type.Optional(Type.Object({
128
121
  subagentId: Type.String({
129
- description: "REQUIRED for action:'cancel'. The subagentId to cancel. Throws if missing. Only background subagents can be cancelled.",
122
+ description: "REQUIRED for action:'cancel'. The subagentId to cancel. Only background subagents are cancellable.",
130
123
  }),
131
124
  })),
125
+ // action:"close" → closeParam.subagentId REQUIRED. 归档(archived):列表隐藏、可寻回、
126
+ // 非终态化([U5 §3.2.5])。idle 立即归档;running 默认等当前轮收口后归档(closeHandler 分流)。
127
+ closeParam: Type.Optional(Type.Object({
128
+ subagentId: Type.String({
129
+ description: "REQUIRED for action:'close'. The subagentId to close (running or idle). Archives the record: hidden from list, recoverable, not terminal.",
130
+ }),
131
+ force: Type.Optional(Type.Boolean({
132
+ description: "true = terminate immediately even mid-round (work lost). false (default) = let the round finish; idle closes immediately regardless.",
133
+ })),
134
+ })),
135
+ });
136
+
137
+ // ── subagent_message:message / fork-from 两续聊动作 ──
138
+
139
+ export const SubagentMessageParams = Type.Object({
140
+ action: StringEnum(["message", "fork-from"], {
141
+ description: "Operation: 'message' sends a follow-up to a subagent, 'fork-from' spawns a new subagent from older history (restart recovery).",
142
+ }),
132
143
  // action:"message" → messageParam.subagentId + text REQUIRED. Any reachable subagent works —
133
- // running joins the in-flight round (D2 打断入队);idle transparently revives on the same
134
- // session file([U4 §3.2.3] 万物可续——形态枚举 gate 与 SP-5 升级路径均消亡,message 直接
135
- // 续聊任何 idle record)。引擎续聊能力轴 gate 保留(core messageHandler 入口)。
136
- // description 与实现锚点见 messageHandler。
144
+ // running joins the in-flight round(D2 打断入队);idle transparently revives on the same
145
+ // session file([U4 §3.2.3] 万物可续)。engine 能力轴 gate 保留(core messageHandler 入口)。
146
+ // 拒绝条件与恢复动作(unknown id / 异进程占用 / 跨 session 树 / workflow-origin / 引擎
147
+ // 不支持续聊)由 core messageHandler 的运行时错误文案承载(tool-declaration-slimming D1:
148
+ // 拒绝清单不进 schema 说明——运行时错误本就带恢复动作),此处不重复。
137
149
  messageParam: Type.Optional(Type.Object({
138
150
  subagentId: Type.String({
139
- description: "REQUIRED for action:'message'. The subagentId to message. Any subagent reachable in this session tree works, running or idle: an idle subagent transparently revives on the same id and continues writing its original session file; a running subagent has your message interrupt-and-join its in-flight round. Rejections: unknown id, session file held by another live process, a record from a different session tree, workflow-origin records (their results belong to the workflow run), and records on engines that do not support continuation (fork-from or re-dispatch instead).",
151
+ // message 的打断/复活副作用在此承载:必填字段的说明是模型填参的必读面,
152
+ // 不依赖模型去翻 deprecated interrupt 字段(D1 单源化——interrupt 只声明 no-op)。
153
+ description:
154
+ "REQUIRED for action:'message'. The subagentId to message — any subagent in this session tree, running or idle. " +
155
+ "A message to a running subagent interrupts its in-flight round; an idle one revives on the same id and starts a new round.",
140
156
  }),
141
157
  text: Type.String({
142
158
  description: "REQUIRED for action:'message'. The message to send. Whitespace-only throws.",
@@ -144,29 +160,22 @@ export const SubagentParams = Type.Object({
144
160
  interrupt: Type.Optional(Type.Boolean({
145
161
  // [H1 U6 / D2] 参数已退役(messageHandler 零消费):在途轮存在即打断入队,
146
162
  // 不区分抢占/排队——字段保留防存量调用 schema 报错,description 据实声明 no-op。
147
- description: "Deprecated, no effect: a message to a running subagent always interrupts its in-flight round (the round aborts and your message is processed next) regardless of this flag; an idle subagent always starts a new round.",
148
- })),
149
- })),
150
- // action:"close" → closeParam.subagentId REQUIRED. 归档(archived):列表隐藏、可寻回、
151
- // 非终态化([U5 §3.2.5])。idle 立即归档收口;running 默认等当前轮收口后归档,
152
- // force:true 立即终止随即归档(closeHandler 头注行为分流)。
153
- closeParam: Type.Optional(Type.Object({
154
- subagentId: Type.String({
155
- description: "REQUIRED for action:'close'. The subagentId to close (any reachable subagent — running or idle). Close archives the record: hidden from list, recoverable, not a terminal state. Idle subagents close immediately; running ones finish the current round first, or terminate immediately when force:true.",
156
- }),
157
- force: Type.Optional(Type.Boolean({
158
- description: "If true, terminate immediately even if mid-round (in-progress work is lost). If false (default), let the current round finish, then close. When idle, the subagent closes immediately regardless.",
163
+ description:
164
+ "Deprecated, no effect — kept only so legacy calls pass schema validation. " +
165
+ "Interrupt/revive semantics live on subagentId above.",
159
166
  })),
160
167
  })),
161
168
  // action:"fork-from" → forkFromParam.sourceSubagentId REQUIRED ([v8.5 B] 断联恢复通道).
162
169
  // 从旧 subagent 的会话历史 spawn 新 id:新进程 --fork 指向旧 session 文件(copy-on-write,
163
170
  // 源文件只读不续写);旧记录/状态机不动。pi 引擎限定(非 pi 在 execute 层拒绝)。
171
+ // 拒绝条件与恢复指引(still-running / 异进程活跃 / worktree-bound / 锚不可解析 → message
172
+ // reopen)由 core forkFromHandler 守卫链文案承载(D1,同 messageParam),此处不重复。
164
173
  forkFromParam: Type.Optional(Type.Object({
165
174
  sourceSubagentId: Type.String({
166
- description: "REQUIRED for action:'fork-from'. The OLD subagentId whose conversation history becomes the inherited context of the new subagent. Works for any idle record — disconnected by a session restart, already finished, or previously closed/cancelled. Rejections: still-running sources (message them instead), sources held by another live process, worktree-bound sources, and unknown ids; an unparseable history anchor is guided to action:'message' (same-id reopen) instead.",
175
+ description: "REQUIRED for action:'fork-from'. The OLD subagentId whose history the new subagent inherits (pi engines only). Works for any idle record (disconnected, finished, or closed).",
167
176
  }),
168
177
  prompt: Type.Optional(Type.String({
169
- description: "Continuation instruction for the new subagent (what to do next on top of the inherited history). When omitted, a standard handover frame is injected: reconstruct done/decided/remaining from the inherited history, then continue to completion. Whitespace-only treated as omitted.",
178
+ description: "Continuation instruction. Omitted = standard handover frame (reconstruct done/remaining, continue). Whitespace-only treated as omitted.",
170
179
  })),
171
180
  })),
172
181
  });