@zhushanwen/pi-subagent-workflow 9.4.0 → 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.
- package/README.md +19 -5
- package/package.json +10 -10
- package/src/host/pi-host.ts +9 -6
- package/src/index.ts +9 -2
- package/src/injectors/__tests__/workflow-list-injector.test.ts +11 -4
- package/src/injectors/resource-list-injector.ts +17 -7
- package/src/injectors/subagent-list-injector.ts +27 -2
- package/src/injectors/workflow-list-injector.ts +1 -1
- package/src/interface/command/commands.ts +126 -66
- package/src/interface/gui/subagent-actions.ts +4 -2
- package/src/interface/tool/__tests__/capture-tool.ts +34 -13
- package/src/interface/tool/__tests__/subagent-tool-actions.test.ts +105 -41
- package/src/interface/tool/__tests__/subagent-tool-path-guard.test.ts +16 -12
- package/src/interface/tool/__tests__/tool-prompt-contract.test.ts +359 -175
- package/src/interface/tool/__tests__/tool-subagents.test.ts +28 -13
- package/src/interface/tool/__tests__/tool-workflow.test.ts +80 -59
- package/src/interface/tool/subagent-tool-schema.ts +79 -70
- package/src/interface/tool/subagent-tool.ts +205 -136
- package/src/interface/tool/tool-render.ts +7 -5
- package/src/interface/tool/tool-result.ts +3 -2
- package/src/interface/tool/tool-subagents.ts +39 -40
- package/src/interface/tool/tool-workflow-script.ts +7 -5
- package/src/interface/tool/tool-workflow.ts +164 -116
- package/src/jsonl-run-store.ts +126 -48
- package/src/session-lifecycle.ts +11 -7
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// src/interface/tool/subagent-tool-schema.ts
|
|
2
2
|
//
|
|
3
|
-
//
|
|
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
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
134
|
-
// session file([U4 §3.2.3]
|
|
135
|
-
//
|
|
136
|
-
//
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
178
|
+
description: "Continuation instruction. Omitted = standard handover frame (reconstruct done/remaining, continue). Whitespace-only treated as omitted.",
|
|
170
179
|
})),
|
|
171
180
|
})),
|
|
172
181
|
});
|