@zhushanwen/pi-subagent-workflow 7.3.4 → 8.0.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 +39 -12
- package/agents/analyst.md +61 -0
- package/agents/coder.md +70 -0
- package/agents/debugger.md +67 -0
- package/agents/doc-reviewer.md +3 -3
- package/agents/explorer.md +50 -18
- package/agents/general-purpose.md +19 -8
- package/agents/orchestrator.md +37 -32
- package/agents/planner.md +45 -11
- package/agents/researcher.md +53 -11
- package/agents/reviewer.md +74 -0
- package/package.json +1 -1
- package/skills/workflow-script-format/SKILL.md +1 -1
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-registry.test.ts +13 -11
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +10 -4
- package/src/execution/__tests__/before-agent-start-injection.test.ts +132 -0
- package/src/execution/__tests__/bg-notify-render.test.ts +15 -15
- package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +365 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +215 -0
- package/src/execution/__tests__/conversation-wiring.test.ts +198 -0
- package/src/execution/__tests__/crash-recovery.test.ts +8 -2
- package/src/execution/__tests__/delivery-methods.test.ts +385 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +49 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -72
- package/src/execution/__tests__/execution-record.test.ts +199 -0
- package/src/execution/__tests__/finalize-record.test.ts +170 -14
- package/src/execution/__tests__/format.test.ts +131 -7
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +25 -7
- package/src/execution/__tests__/index-session-start-identity.test.ts +371 -0
- package/src/execution/__tests__/index-session-start.test.ts +257 -5
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +337 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/list-component.test.ts +59 -5
- package/src/execution/__tests__/list-fields.test.ts +109 -0
- package/src/execution/__tests__/model-resolver.test.ts +38 -1
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/notifier-flush.test.ts +209 -7
- package/src/execution/__tests__/one-shot-upgrade.test.ts +205 -0
- package/src/execution/__tests__/parent-child-matrix.test.ts +336 -0
- package/src/execution/__tests__/record-store.test.ts +158 -52
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +11 -12
- package/src/execution/__tests__/recursive-visibility-env.test.ts +18 -20
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +18 -25
- package/src/execution/__tests__/run-spawn-integration.test.ts +29 -25
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +14 -11
- package/src/execution/__tests__/session-pending.test.ts +61 -2
- package/src/execution/__tests__/session-reconstructor.test.ts +4 -4
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +15 -21
- package/src/execution/__tests__/session-start-reaper.test.ts +10 -8
- package/src/execution/__tests__/spawn-args.test.ts +127 -49
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +1 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/status-refactor.test.ts +345 -0
- package/src/execution/__tests__/stdin-writer.test.ts +97 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +598 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +49 -11
- package/src/execution/__tests__/timeout-integration.test.ts +27 -13
- package/src/execution/__tests__/tool-action.test.ts +12 -10
- package/src/execution/__tests__/truncline-snapshot.test.ts +81 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/worktree-manager.test.ts +292 -89
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +13 -12
- package/src/execution/argv-mirror.ts +21 -2
- package/src/execution/execution-record.ts +126 -9
- package/src/execution/finalize-record.ts +90 -13
- package/src/execution/host-mode.ts +1 -1
- package/src/execution/lifecycle-manager.ts +484 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +61 -16
- package/src/execution/model-resolver.ts +26 -5
- package/src/execution/notifier.ts +69 -12
- package/src/execution/pi-invocation.ts +21 -1
- package/src/execution/record-store.ts +554 -107
- package/src/execution/session-pending.ts +116 -45
- package/src/execution/session-reconstructor.ts +224 -7
- package/src/execution/session-runner.ts +290 -75
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/stdin-writer.ts +93 -7
- package/src/execution/stream-sink.ts +20 -3
- package/src/execution/subagent-service.ts +867 -138
- package/src/execution/turn-limiter.ts +14 -0
- package/src/execution/types.ts +204 -22
- package/src/execution/worktree-manager.ts +128 -49
- package/src/execution/worktree-registry.ts +13 -2
- package/src/index.ts +277 -19
- package/src/injectors/subagent-list-injector.ts +26 -8
- package/src/injectors/workflow-list-injector.ts +25 -8
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +18 -5
- package/src/interface/__tests__/tool-render.test.ts +15 -13
- package/src/interface/bg-notify-render.ts +32 -8
- package/src/interface/command-actions.ts +26 -7
- package/src/interface/commands.ts +21 -22
- package/src/interface/format.ts +44 -17
- package/src/interface/gui-mappers.ts +6 -8
- package/src/interface/helpers.ts +170 -10
- package/src/interface/list-component.ts +53 -14
- package/src/interface/subagent-actions.ts +235 -17
- package/src/interface/subagent-tool.ts +82 -17
- package/src/interface/subagents.ts +2 -1
- package/src/interface/tool-render.ts +11 -24
- package/src/interface/tool-workflow.ts +20 -35
- package/src/interface/views/WorkflowsView.ts +89 -32
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +264 -0
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +1 -1
- package/src/interface/views/format.ts +3 -3
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +325 -0
- package/src/orchestration/__tests__/args-validator.test.ts +1 -1
- package/src/orchestration/__tests__/config-loader.test.ts +38 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +394 -4
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +4 -4
- package/src/orchestration/__tests__/execute-agent-call.test.ts +95 -0
- package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +657 -18
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -2
- package/src/orchestration/__tests__/lifecycle.test.ts +332 -149
- package/src/orchestration/__tests__/skill-discovery.test.ts +157 -0
- package/src/orchestration/__tests__/test-mocks.ts +191 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +98 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -2
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/__tests__/workflows-e2e.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +4 -1
- package/src/orchestration/args-validator.ts +2 -2
- package/src/orchestration/config-loader.ts +30 -1
- package/src/orchestration/error-recovery.ts +133 -29
- package/src/orchestration/execute-agent-call.ts +31 -7
- package/src/orchestration/jsonl-run-store.ts +287 -40
- package/src/orchestration/launcher.ts +7 -1
- package/src/orchestration/lifecycle.ts +135 -132
- package/src/orchestration/models/__tests__/trace.test.ts +408 -0
- package/src/orchestration/models/budget.ts +1 -1
- package/src/orchestration/models/run-runtime.ts +15 -17
- package/src/orchestration/models/run-spec.ts +2 -2
- package/src/orchestration/models/run-state.ts +3 -3
- package/src/orchestration/models/trace.ts +95 -15
- package/src/orchestration/models/types.ts +8 -9
- package/src/orchestration/models/workflow-run.ts +50 -71
- package/src/orchestration/models/workflow-script.ts +32 -1
- package/src/orchestration/skill-discovery.ts +30 -0
- package/src/orchestration/worker-handle.ts +1 -1
- package/src/orchestration/worker-host.ts +1 -1
- package/src/orchestration/worker-script-builder.ts +29 -10
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +280 -0
- package/src/shared/__tests__/resource-discovery.test.ts +55 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/agent-ref.ts +16 -0
- package/src/shared/resource-discovery.ts +147 -58
- package/src/shared/schema-jsonify.ts +53 -0
- package/workflows/README.md +4 -4
- package/agents/code-reviewer.md +0 -47
- package/agents/context-builder.md +0 -21
- package/agents/oracle.md +0 -34
- package/agents/worker.md +0 -20
|
@@ -21,7 +21,7 @@ import { getSubagentService } from "../execution/subagent-service.ts";
|
|
|
21
21
|
import type { SubagentToolResult } from "../execution/types.ts";
|
|
22
22
|
import { extractAgentName } from "./format.ts";
|
|
23
23
|
import { toGuiCtx } from "./gui-mappers.ts";
|
|
24
|
-
import { adapter, cancelHandler, listHandler, startHandler } from "./subagent-actions.ts";
|
|
24
|
+
import { adapter, cancelHandler, closeHandler, listHandler, messageHandler, startHandler } from "./subagent-actions.ts";
|
|
25
25
|
import { type RenderContext,renderSubagentCall, renderSubagentResult } from "./tool-render.ts";
|
|
26
26
|
|
|
27
27
|
// ============================================================
|
|
@@ -71,8 +71,8 @@ type SubagentRenderResultCb = (
|
|
|
71
71
|
// (subagent_start / subagent_list / subagent_cancel),让每个 tool 的 schema 真实
|
|
72
72
|
// 反映必填性。勿在此基础上继续堆 action 条件逻辑——要加就拆 tool。
|
|
73
73
|
const SubagentParams = Type.Object({
|
|
74
|
-
action: StringEnum(["start", "list", "cancel"], {
|
|
75
|
-
description: "Operation: 'start' runs a subagent, 'list' shows running
|
|
74
|
+
action: StringEnum(["start", "list", "cancel", "message", "close"], {
|
|
75
|
+
description: "Operation: 'start' runs a subagent, 'list' shows subagents, 'cancel' stops a background subagent, 'message' sends a follow-up to a running subagent (one-shot subagents are auto-upgraded to conversation mode on first message), 'close' ends a running subagent (conversation-mode or one-shot).",
|
|
76
76
|
}),
|
|
77
77
|
// ── action:"start" fields (flattened to top level). task/slug REQUIRED for start. ──
|
|
78
78
|
// Missing/empty task or slug throws at runtime (startHandler).
|
|
@@ -93,7 +93,7 @@ const SubagentParams = Type.Object({
|
|
|
93
93
|
description: 'Model override in "provider/modelId" format. 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.',
|
|
94
94
|
})),
|
|
95
95
|
thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
|
|
96
|
-
description: "Thinking depth override (derived from THINKING_ORDER SSOT, includes 'max'). Omit to
|
|
96
|
+
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).",
|
|
97
97
|
})),
|
|
98
98
|
skillPath: Type.Optional(Type.String()),
|
|
99
99
|
appendSystemPrompt: Type.Optional(Type.Array(Type.String())),
|
|
@@ -113,6 +113,20 @@ const SubagentParams = Type.Object({
|
|
|
113
113
|
cwd: Type.Optional(Type.String({
|
|
114
114
|
description: 'Override the working directory for the subagent execution. Must be an absolute path. Defaults to the parent session\'s cwd.',
|
|
115
115
|
})),
|
|
116
|
+
conversation: Type.Optional(Type.Boolean({
|
|
117
|
+
description:
|
|
118
|
+
"Enable continuous chat with this subagent. When true, the subagent stays available after each reply — you can send follow-up messages (action:'message') and it keeps the full conversation context across rounds, with no need to re-spawn or re-explain. " +
|
|
119
|
+
"\nUse conversation:true for: multi-round collaboration (iterative review-fix loops, back-and-forth refinement), any task where you expect to send follow-up messages after the initial result. " +
|
|
120
|
+
"\nOmit (or false) for: one-shot tasks — single exploration, lookup, file read, code generation that needs no follow-up. The subagent runs once, notifies on completion, and is cleaned up automatically (default). " +
|
|
121
|
+
"\nFor long-interval collaboration (each round spaced >5min apart), set conversation:true AND increase idleTimeoutMs to avoid premature timeout. " +
|
|
122
|
+
"Cost: a conversation-mode subagent holds resources (memory, and a worktree if enabled) until you explicitly end it with action:'close'. Always close when done.",
|
|
123
|
+
})),
|
|
124
|
+
idleTimeoutMs: Type.Optional(Type.Number({
|
|
125
|
+
description:
|
|
126
|
+
"Idle timeout in milliseconds for conversation-mode subagents. Controls how long an idle subagent (between rounds) stays alive before automatic cleanup. " +
|
|
127
|
+
"Default: 300000 (5min). Override for long-interval collaboration where each round is spaced >5min apart. " +
|
|
128
|
+
"Only meaningful with conversation:true; ignored for one-shot subagents.",
|
|
129
|
+
})),
|
|
116
130
|
// action:"list" → listParam OPTIONAL (all fields optional, defaults apply). Ignored by other actions.
|
|
117
131
|
listParam: Type.Optional(Type.Object({
|
|
118
132
|
includeFinished: Type.Optional(Type.Boolean({
|
|
@@ -128,6 +142,29 @@ const SubagentParams = Type.Object({
|
|
|
128
142
|
description: "REQUIRED for action:'cancel'. The subagentId to cancel. Throws if missing. Only background subagents can be cancelled.",
|
|
129
143
|
}),
|
|
130
144
|
})),
|
|
145
|
+
// action:"message" → messageParam.subagentId + text REQUIRED. Any RUNNING subagent works —
|
|
146
|
+
// one-shot subagents are auto-upgraded to conversation mode on first message (SP-5); ended ones throw.
|
|
147
|
+
messageParam: Type.Optional(Type.Object({
|
|
148
|
+
subagentId: Type.String({
|
|
149
|
+
description: "REQUIRED for action:'message'. The subagentId to message (any running subagent; a one-shot subagent is auto-upgraded to conversation mode on first message, so you may also message one-shot subagents that are still running).",
|
|
150
|
+
}),
|
|
151
|
+
text: Type.String({
|
|
152
|
+
description: "REQUIRED for action:'message'. The message to send. Whitespace-only throws.",
|
|
153
|
+
}),
|
|
154
|
+
interrupt: Type.Optional(Type.Boolean({
|
|
155
|
+
description: "If true, interrupt the subagent's current work immediately (in-progress output stops, it switches to your new message). If false (default), the message is queued and processed after the current round completes. When the subagent is idle (between rounds), interrupt has no effect — the message always starts a new round.",
|
|
156
|
+
})),
|
|
157
|
+
})),
|
|
158
|
+
// action:"close" → closeParam.subagentId REQUIRED. Ends a running subagent (conversation-mode
|
|
159
|
+
// or one-shot — closeSubagent behavior split covers both).
|
|
160
|
+
closeParam: Type.Optional(Type.Object({
|
|
161
|
+
subagentId: Type.String({
|
|
162
|
+
description: "REQUIRED for action:'close'. The subagentId to close (any running subagent, conversation-mode or one-shot).",
|
|
163
|
+
}),
|
|
164
|
+
force: Type.Optional(Type.Boolean({
|
|
165
|
+
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.",
|
|
166
|
+
})),
|
|
167
|
+
})),
|
|
131
168
|
});
|
|
132
169
|
|
|
133
170
|
// ============================================================
|
|
@@ -142,13 +179,13 @@ function assertNever(value: never): string {
|
|
|
142
179
|
}
|
|
143
180
|
|
|
144
181
|
/** Subagent action 字面量联合(与 parameters schema 的 StringEnum 取值一致)。 */
|
|
145
|
-
type SubagentAction = "start" | "list" | "cancel";
|
|
182
|
+
type SubagentAction = "start" | "list" | "cancel" | "message" | "close";
|
|
146
183
|
|
|
147
184
|
/** 类型守卫:把 schema 投影出的 string 形式 action 收窄回字面量联合。
|
|
148
185
|
* typebox v1 的 StringEnum Static 退化为 string,需运行时校验 + 类型收窄
|
|
149
186
|
* 才能恢复 switch 的 exhaustiveness 约束。 */
|
|
150
187
|
function isSubagentAction(value: string): value is SubagentAction {
|
|
151
|
-
return value === "start" || value === "list" || value === "cancel";
|
|
188
|
+
return value === "start" || value === "list" || value === "cancel" || value === "message" || value === "close";
|
|
152
189
|
}
|
|
153
190
|
|
|
154
191
|
/** unknown 是否为含 model/thinkingLevel 的对象(类型守卫,替代全可选结构 `as`)。 */
|
|
@@ -182,26 +219,36 @@ CRITICAL — executionMode "sequential": multiple \`subagent\` calls in the SAME
|
|
|
182
219
|
|
|
183
220
|
## When to delegate
|
|
184
221
|
|
|
185
|
-
Delegate when the task needs a distinct specialized role, context isolation (fork/worktree), or parallelism while you do other work. Delegate FIRST when the task involves any of: reading 3+ files, writing 100+ lines of implementation, parallel research, or specialized review — doing these yourself floods your context
|
|
222
|
+
Delegate when the task needs a distinct specialized role, context isolation (fork/worktree), or parallelism while you do other work. Delegate FIRST when the task involves any of: reading 3+ files, writing 100+ lines of implementation, parallel research, or specialized review — doing these yourself floods your context.
|
|
223
|
+
|
|
224
|
+
## Before starting — list first
|
|
225
|
+
|
|
226
|
+
action:"list" before action:"start" — a reusable running subagent may exist; compaction can swallow its id.
|
|
186
227
|
|
|
187
228
|
## Actions
|
|
188
229
|
|
|
189
|
-
- action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd. Background only: returns a subagentId immediately, notifies on completion.
|
|
230
|
+
- action:"start" — run a subagent. Pass task and slug as top-level fields (REQUIRED). Optional: agent, model, thinkingLevel, skillPath, appendSystemPrompt, schema, maxTurns, graceTurns, fork, worktree, cwd, conversation, idleTimeoutMs. Background only: returns a subagentId immediately, notifies on completion.
|
|
231
|
+
- action:"message" — send a follow-up message to a running subagent (conversation-mode or one-shot); it keeps the full context across rounds. REQUIRED messageParam: { subagentId, text }. Optional: interrupt (default false). The reply auto-notifies when the round completes.
|
|
232
|
+
- action:"close" — end a running subagent and release its resources. REQUIRED closeParam: { subagentId }. Optional: force (default false; true terminates mid-round immediately). Always close when done.
|
|
190
233
|
- action:"list" — list subagents. Pass listParam: { includeFinished?, limit? } (all optional). Read an item's sessionFile for full detail.
|
|
191
|
-
- action:"cancel" —
|
|
234
|
+
- action:"cancel" — stop a background subagent (legacy verb; for conversation-mode use close). REQUIRED cancelParam: { subagentId }.
|
|
192
235
|
|
|
193
236
|
## Examples
|
|
194
237
|
|
|
195
238
|
\`\`\`
|
|
196
239
|
{"action":"start","task":"<your task>","slug":"<kebab-case>"}
|
|
197
|
-
{"action":"start","task":"...","slug":"fix-login","agent":"
|
|
240
|
+
{"action":"start","task":"...","slug":"fix-login","agent":"coder","model":"anthropic/claude-3.5-sonnet","fork":true}
|
|
241
|
+
{"action":"start","task":"review iteratively","slug":"review","conversation":true}
|
|
242
|
+
{"action":"message","messageParam":{"subagentId":"sa-550e8400","text":"now also handle the empty-list case"}}
|
|
243
|
+
{"action":"message","messageParam":{"subagentId":"sa-550e8400","text":"stop, switch direction to X","interrupt":true}}
|
|
244
|
+
{"action":"close","closeParam":{"subagentId":"sa-550e8400"}}
|
|
198
245
|
{"action":"list","listParam":{"includeFinished":false,"limit":20}}
|
|
199
246
|
{"action":"cancel","cancelParam":{"subagentId":"sa-550e8400"}}
|
|
200
247
|
\`\`\`
|
|
201
248
|
|
|
202
249
|
## After launching — do NOT wait
|
|
203
250
|
|
|
204
|
-
Completion auto-notifies you (steer wakes next turn
|
|
251
|
+
Completion auto-notifies you (steer wakes the next turn):
|
|
205
252
|
- DO NOT sleep, busy-wait, or poll — there is no poll action; use action:"list" only when you concretely need state.
|
|
206
253
|
- DO useful non-overlapping work, otherwise STOP.
|
|
207
254
|
- On auto-injected completion: process directly. The notification IS the confirmation — do NOT call action:"list" to re-confirm.
|
|
@@ -209,25 +256,39 @@ Completion auto-notifies you (steer wakes next turn, even mid-poll). So:
|
|
|
209
256
|
|
|
210
257
|
## Anti-patterns
|
|
211
258
|
|
|
212
|
-
- Forgetting the REQUIRED top-level task/slug fields for action:"start"
|
|
259
|
+
- Forgetting the REQUIRED top-level task/slug fields for action:"start" (not nested).
|
|
213
260
|
- Over-generalizing the flatten: ONLY start fields are top-level. list and cancel params stay nested under listParam / cancelParam (e.g. {"action":"list","listParam":{"includeFinished":true}}, NOT {"action":"list","includeFinished":true}).
|
|
214
261
|
- Launching background, then sleeping/polling instead of working or stopping.
|
|
215
262
|
- Treating subagent results as authoritative without verification.
|
|
216
263
|
- Canceling by guessing a subagentId instead of using action:"list" first.
|
|
217
264
|
|
|
265
|
+
## Continuous chat (conversation mode)
|
|
266
|
+
|
|
267
|
+
For multi-round work, set conversation:true on start. The subagent stays available across replies — action:"message" continues with full context retained, action:"close" releases it. Always close when finished.
|
|
268
|
+
|
|
269
|
+
When to use:
|
|
270
|
+
- ✅ Multi-round collaboration (review/fix loops) → conversation:true
|
|
271
|
+
- ✅ Long-interval rounds (>5min apart) → conversation:true + idleTimeoutMs increased
|
|
272
|
+
- ❌ Single exploration/lookup → default (one-shot)
|
|
273
|
+
|
|
274
|
+
idleTimeoutMs: per-subagent idle timeout (default 300000 / 5min). Env XYZ_SUBAGENT_IDLE_TIMEOUT_MS sets the global default; per-call param takes precedence.
|
|
275
|
+
|
|
218
276
|
## You cannot
|
|
219
277
|
|
|
220
|
-
- Get a synchronous/inline result — always
|
|
221
|
-
- Pause or resume a subagent (only cancel).
|
|
278
|
+
- Get a synchronous/inline result — start always returns a subagentId immediately (background).
|
|
222
279
|
- Read mid-flight streaming output — wait for the completion notification.
|
|
223
280
|
|
|
224
281
|
## Calling patterns
|
|
225
282
|
|
|
226
|
-
|
|
283
|
+
Chain dependent tasks: send the next start after prior completion. Run N independent tasks concurrently: N action:"start" calls in the SAME message. Cancel if direction changes.
|
|
284
|
+
|
|
285
|
+
## Nested spawning (recursion)
|
|
286
|
+
|
|
287
|
+
A subagent MAY call the \`subagent\` tool itself (depth appears in the environment block as "Depth: N/10"). The hard cap is 10 levels — depth 11 fails as a tool error, NOT a reason to avoid nesting entirely (Do NOT refuse a sub-subagent).
|
|
227
288
|
|
|
228
|
-
|
|
289
|
+
Recursion is for TREE-SHAPED work only: a task that decomposes naturally into independent, independently-verifiable sub-tasks. Each level's \`task\` must be SELF-CONTAINED — the child does not see your conversation (unless fork:true). Each level must have its own acceptance criteria, or errors compound silently down the chain.
|
|
229
290
|
|
|
230
|
-
|
|
291
|
+
Do NOT recurse when: the work is linear/flat (use chain or parallel instead); the child needs your context to do the job; or you are delegating the judgment/decision your own level is responsible for. Depth should match the task tree (2-3 levels for most work; deep trees only when the decomposition genuinely demands it) — 10 is a safety rail against infinite delegation loops, not a budget to spend. Prefer fork:false in recursion: fork chains copy parent history at every level and blow up context volume linearly.`,
|
|
231
292
|
executionMode: "sequential",
|
|
232
293
|
parameters: SubagentParams,
|
|
233
294
|
renderCall: subagentRenderCall,
|
|
@@ -321,6 +382,10 @@ const executeSubagent: SubagentExecuteCb = async (
|
|
|
321
382
|
return adapter({ action: "list", domain: listHandler(service, params.listParam) }, toGuiCtx(_ctx));
|
|
322
383
|
case "cancel":
|
|
323
384
|
return adapter({ action: "cancel", domain: await cancelHandler(service, params.cancelParam) }, toGuiCtx(_ctx));
|
|
385
|
+
case "message":
|
|
386
|
+
return adapter({ action: "message", domain: await messageHandler(service, params.messageParam) }, toGuiCtx(_ctx));
|
|
387
|
+
case "close":
|
|
388
|
+
return adapter({ action: "close", domain: await closeHandler(service, params.closeParam) }, toGuiCtx(_ctx));
|
|
324
389
|
default:
|
|
325
390
|
// assertNever:让 exhaustiveness 成为承重约束——新增 action 时 tsc 报错,
|
|
326
391
|
// 而非悄悄落入此分支。
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
import { getSubagentService } from "../execution/subagent-service.ts";
|
|
11
|
+
import { displayAgentName } from "../shared/agent-ref.ts";
|
|
11
12
|
import { parseSubagentRpcCommand } from "./command-actions.ts";
|
|
12
13
|
import { LIST_LIMIT } from "./list-shared.ts";
|
|
13
14
|
import { createSubagentsView } from "./list-view.ts";
|
|
@@ -39,7 +40,7 @@ export function registerSubagentsCommand(pi: ExtensionAPI): void {
|
|
|
39
40
|
return records.map((r) => ({
|
|
40
41
|
label: r.id,
|
|
41
42
|
value: r.id,
|
|
42
|
-
description: `${r.agent} [${r.status}]`,
|
|
43
|
+
description: `${displayAgentName(r.agent)} [${r.status}]`,
|
|
43
44
|
}));
|
|
44
45
|
} catch {
|
|
45
46
|
// 拿不到运行时数据(service disposed 等)→ 静默降级,补全失败不影响 command
|
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
ListResponse,
|
|
26
26
|
SubagentToolResult,
|
|
27
27
|
} from "../execution/types.ts";
|
|
28
|
+
import { displayAgentName } from "../shared/agent-ref.ts";
|
|
28
29
|
import {
|
|
29
30
|
extractAgentName,
|
|
30
31
|
firstLine,
|
|
@@ -75,9 +76,9 @@ export interface RenderContext {
|
|
|
75
76
|
// ============================================================
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* renderCall:tool 标题行(agent + model + thinking
|
|
79
|
+
* renderCall:tool 标题行(agent + model + thinking 等级,不变信息)。
|
|
79
80
|
*
|
|
80
|
-
* "subagent worker · glm-5.2 ·
|
|
81
|
+
* "subagent worker · glm-5.2 · high"
|
|
81
82
|
*
|
|
82
83
|
* model/thinkingLevel 由调用方(subagent-tool.ts 的闭包)预解析后传入,
|
|
83
84
|
* 因为 renderCall 在 execute 前调用,但 model 解析是同步的(只读配置)。
|
|
@@ -94,8 +95,9 @@ export function renderSubagentCall(
|
|
|
94
95
|
const t = theme as ThemeLike;
|
|
95
96
|
// args 结构(拍平后):{ action:"start", agent, task, slug, ... }(见 subagent-tool.ts schema)。
|
|
96
97
|
// 13 字段直接在顶层,extractAgentName / slug / task 都从 args 顶层提取,
|
|
97
|
-
// 对齐 nicobailon 的 renderCall 多行布局。
|
|
98
|
-
|
|
98
|
+
// 对齐 nicobailon 的 renderCall 多行布局。agent ref 是绝对路径,显示取 basename 短名
|
|
99
|
+
// (displayAgentName);extractAgentName 原值另被 subagent-tool 的 resolveModel 消费,不动。
|
|
100
|
+
const agent = displayAgentName(extractAgentName(args));
|
|
99
101
|
// slug:从顶层 args 提取(必填字段),非空时在 agent 后用 · 分隔展示。
|
|
100
102
|
const slug = typeof args === "object" && args !== null && "slug" in args
|
|
101
103
|
? (args as { slug?: unknown }).slug
|
|
@@ -111,26 +113,12 @@ export function renderSubagentCall(
|
|
|
111
113
|
parts.push(t.fg("dim", " ("));
|
|
112
114
|
parts.push(t.fg("accent", resolved.model));
|
|
113
115
|
if (resolved.thinkingLevel) {
|
|
114
|
-
parts.push(t.fg("dim", ` ·
|
|
116
|
+
parts.push(t.fg("dim", ` · ${resolved.thinkingLevel})`));
|
|
115
117
|
} else {
|
|
116
118
|
parts.push(t.fg("dim", ")"));
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
// task preview 行——对齐 nicobailon:renderCall 输出多行(标题 + \n + task 预览)。
|
|
121
|
-
// 实验假设:call 多行让首帧(无 result)与后续帧(有 result)的高度跳变模式
|
|
122
|
-
// 与 nicobailon 一致,可能影响 pi diff 引擎的行对齐路径。preview 截断到 60 字符。
|
|
123
|
-
const task = typeof args === "object" && args !== null && "task" in args
|
|
124
|
-
? (args as { task?: unknown }).task
|
|
125
|
-
: undefined;
|
|
126
|
-
if (typeof task === "string" && task.length > 0) {
|
|
127
|
-
// task 取首行——prompt 常含换行(多行指令),直接 slice 会保留 \n,
|
|
128
|
-
// 渲染时意外换行破坏 tool block 行对齐。
|
|
129
|
-
const taskFirst = task.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
130
|
-
const preview = taskFirst.length > 60 ? `${taskFirst.slice(0, 60)}...` : taskFirst;
|
|
131
|
-
if (preview) parts.push(`\n ${t.fg("dim", preview)}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
122
|
return new Text(parts.join(""), 0, 0);
|
|
135
123
|
}
|
|
136
124
|
|
|
@@ -215,10 +203,9 @@ function buildCompactLines(d: SubagentToolResult, theme: ThemeLike): string[] {
|
|
|
215
203
|
}
|
|
216
204
|
// ── start 分支:background ──
|
|
217
205
|
if ("bgResponse" in d) {
|
|
218
|
-
const slugPart = d.slug ? `${theme.fg("dim", " · ")}${theme.fg("accent", d.slug)}` : "";
|
|
219
206
|
return [truncLine(
|
|
220
|
-
`${theme.fg("accent", "●")} ${theme.fg("dim", "background: ")}${theme.fg("accent", d.subagentId ?? "?")}
|
|
221
|
-
+ ` ${theme.fg("dim", "·
|
|
207
|
+
`${theme.fg("accent", "●")} ${theme.fg("dim", "background: ")}${theme.fg("accent", d.subagentId ?? "?")}`
|
|
208
|
+
+ ` ${theme.fg("dim", "· detached")}`,
|
|
222
209
|
width,
|
|
223
210
|
)];
|
|
224
211
|
}
|
|
@@ -244,9 +231,9 @@ function buildExpandedLines(d: SubagentToolResult, theme: ThemeLike): string[] {
|
|
|
244
231
|
const lines: string[] = [];
|
|
245
232
|
// bg 占位 expanded 与 compact 同(一次性 block 无细节可展开)
|
|
246
233
|
if ("bgResponse" in d) {
|
|
247
|
-
const slugPart = d.slug ? `${theme.fg("dim", " · ")}${theme.fg("accent", d.slug)}` : "";
|
|
248
234
|
lines.push(truncLine(
|
|
249
|
-
`${theme.fg("accent", "●")} ${theme.fg("dim", "background: ")}${theme.fg("accent", d.subagentId ?? "?")}
|
|
235
|
+
`${theme.fg("accent", "●")} ${theme.fg("dim", "background: ")}${theme.fg("accent", d.subagentId ?? "?")}`
|
|
236
|
+
+ ` ${theme.fg("dim", "· detached")}`,
|
|
250
237
|
width,
|
|
251
238
|
));
|
|
252
239
|
return lines;
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Workflow Extension — workflow tool(
|
|
2
|
+
* Workflow Extension — workflow tool(3 actions,FR-5 tool 收口)。
|
|
3
3
|
*
|
|
4
4
|
* 合并原 tool-workflow.ts + tool-workflow-run.ts 为单 tool。
|
|
5
5
|
*
|
|
6
6
|
* Actions:
|
|
7
7
|
* - run: registry.get → runWorkflow(直接启动,无需用户确认)
|
|
8
8
|
* - status: 列出 runs(deps.runs)
|
|
9
|
-
* - pause: 调 pauseRun
|
|
10
|
-
* - resume: 调 resumeRun
|
|
11
9
|
* - abort: 调 abortRun
|
|
12
10
|
*
|
|
13
|
-
* **restart 不包含**(D-9
|
|
11
|
+
* **restart 不包含**(D-9 废弃);**pause/resume 不包含**(一次性生命周期——run
|
|
12
|
+
* 不可挂起,提前停止用 abort,要新结果开新 run)。
|
|
14
13
|
*
|
|
15
14
|
* 层归属:Interface。依赖 Pi SDK + Engine lifecycle/launcher + helpers。
|
|
16
15
|
*
|
|
@@ -36,7 +35,7 @@ import { SLUG_MAX_LENGTH } from "../execution/execute-options-mapper.ts";
|
|
|
36
35
|
import { THINKING_ORDER } from "../execution/model-resolver.ts";
|
|
37
36
|
import type { LauncherDeps } from "../orchestration/launcher.ts";
|
|
38
37
|
import { ArgsValidationError } from "../orchestration/args-validator.ts";
|
|
39
|
-
import { abortRun,
|
|
38
|
+
import { abortRun, runWorkflow } from "../orchestration/lifecycle.ts";
|
|
40
39
|
import type { RunStore } from "../orchestration/models/ports.ts";
|
|
41
40
|
import type { WorkflowRun } from "../orchestration/models/workflow-run.ts";
|
|
42
41
|
import { mapRunIcon, mapRunStatus, toGuiCtx } from "./gui-mappers.ts";
|
|
@@ -54,15 +53,11 @@ import { formatElapsed, renderTextFallback } from "./views/format.ts";
|
|
|
54
53
|
export type WorkflowAction =
|
|
55
54
|
| "run"
|
|
56
55
|
| "status"
|
|
57
|
-
| "pause"
|
|
58
|
-
| "resume"
|
|
59
56
|
| "abort";
|
|
60
57
|
|
|
61
58
|
const WORKFLOW_ACTIONS: readonly WorkflowAction[] = [
|
|
62
59
|
"run",
|
|
63
60
|
"status",
|
|
64
|
-
"pause",
|
|
65
|
-
"resume",
|
|
66
61
|
"abort",
|
|
67
62
|
];
|
|
68
63
|
|
|
@@ -80,7 +75,7 @@ const WorkflowParams = Type.Object({
|
|
|
80
75
|
}),
|
|
81
76
|
),
|
|
82
77
|
runId: Type.Optional(
|
|
83
|
-
Type.String({ description: "Workflow run ID (
|
|
78
|
+
Type.String({ description: "Workflow run ID (abort action)" }),
|
|
84
79
|
),
|
|
85
80
|
args: Type.Optional(
|
|
86
81
|
Type.Record(Type.String(), Type.Unknown(), {
|
|
@@ -96,7 +91,7 @@ const WorkflowParams = Type.Object({
|
|
|
96
91
|
description: "Run-level model override in 'provider/modelId' format. When set, all agents spawned by this run inherit it by default (unless a per-call agent() opts.model is set). Omit to inherit the main agent's model.",
|
|
97
92
|
})),
|
|
98
93
|
thinkingLevel: Type.Optional(StringEnum(THINKING_ORDER, {
|
|
99
|
-
description: "Run-level thinkingLevel override (off/minimal/low/medium/high/xhigh/max). All agents in this run inherit it by default. Omit to
|
|
94
|
+
description: "Run-level thinkingLevel override (off/minimal/low/medium/high/xhigh/max). All agents in this run inherit it by default. Omit to default each agent to its model's highest available level.",
|
|
100
95
|
})),
|
|
101
96
|
});
|
|
102
97
|
|
|
@@ -229,7 +224,7 @@ interface RunSummary {
|
|
|
229
224
|
export type WorkflowToolDetails =
|
|
230
225
|
| { action: "run"; runId: string; status: "running" | "not_found" | "invalid_args"; name: string; slug?: string; stateFile?: string; __gui__?: GuiRenderResult }
|
|
231
226
|
| { action: "status"; runs: RunSummary[]; __gui__?: GuiRenderResult }
|
|
232
|
-
| { action: "
|
|
227
|
+
| { action: "abort"; runId: string; status: string; reason?: string; __gui__?: GuiRenderResult };
|
|
233
228
|
|
|
234
229
|
/** Result returned by the `workflow` tool's execute. */
|
|
235
230
|
export interface ToolResult {
|
|
@@ -283,14 +278,12 @@ export function buildWorkflowGui(details: WorkflowToolDetails) {
|
|
|
283
278
|
}),
|
|
284
279
|
});
|
|
285
280
|
}
|
|
286
|
-
//
|
|
287
|
-
// abort 是破坏性终止、pause 是挂起(非成功完成),用 warn 区分;resume 保留 ok
|
|
288
|
-
const severity = details.action === "abort" || details.action === "pause" ? "warn" as const : "ok" as const;
|
|
281
|
+
// abort(唯一 lifecycle action):破坏性终止非成功完成,用 warn 与成功区分
|
|
289
282
|
return guiComponent("stats-line", {
|
|
290
283
|
items: [{
|
|
291
284
|
label: details.action,
|
|
292
285
|
value: details.runId.slice(0, RUNID_SHORT),
|
|
293
|
-
severity,
|
|
286
|
+
severity: "warn" as const,
|
|
294
287
|
}],
|
|
295
288
|
});
|
|
296
289
|
}
|
|
@@ -298,7 +291,8 @@ export function buildWorkflowGui(details: WorkflowToolDetails) {
|
|
|
298
291
|
// ── Tool registration ────────────────────────────────────────
|
|
299
292
|
|
|
300
293
|
/**
|
|
301
|
-
* 注册 workflow tool(
|
|
294
|
+
* 注册 workflow tool(3 actions: run / status / abort;pause/resume 已随一次性
|
|
295
|
+
* 生命周期移除——enum 拒绝由 pi 核心校验拦截,见 F3)。
|
|
302
296
|
*
|
|
303
297
|
* @param pi ExtensionAPI
|
|
304
298
|
* @param deps LauncherDeps(LifecycleDeps + registry)
|
|
@@ -318,9 +312,9 @@ export function registerWorkflowTool(
|
|
|
318
312
|
name: "workflow",
|
|
319
313
|
label: "Workflow",
|
|
320
314
|
description:
|
|
321
|
-
"Execute and control workflows: run (start), status,
|
|
315
|
+
"Execute and control workflows: run (start), status, abort.\n" +
|
|
322
316
|
"Replaces workflow + workflow-run tools.",
|
|
323
|
-
promptSnippet: "Run,
|
|
317
|
+
promptSnippet: "Run, abort, or check workflow status",
|
|
324
318
|
promptGuidelines: [
|
|
325
319
|
"PRIORITY: When user says 'workflow', 'run workflow', try run action FIRST.",
|
|
326
320
|
"All listed workflows run DIRECTLY with action:run — refs/descriptions come from " +
|
|
@@ -329,10 +323,11 @@ export function registerWorkflowTool(
|
|
|
329
323
|
"workflow-script generate for patterns already covered by available workflows.",
|
|
330
324
|
"run: pass the absolute .js path from <available_workflows> <location> as name, then start in background (no user confirmation needed).",
|
|
331
325
|
"Do NOT poll status after starting — results appear automatically via notifyDone.",
|
|
326
|
+
"Runs are one-shot: there is no pause/resume — to stop a run early use abort; for a fresh result start a new run.",
|
|
332
327
|
"Call shapes (JSON): " +
|
|
333
328
|
"- run: {\"action\":\"run\",\"name\":\"<script>\",\"args\":{...},\"tokens\":N,\"time\":N,\"model\":\"<provider/modelId>\",\"thinkingLevel\":\"<level>\"}. " +
|
|
334
329
|
"- status: {\"action\":\"status\"}. " +
|
|
335
|
-
"-
|
|
330
|
+
"- abort: {\"action\":\"abort\",\"runId\":\"<id>\"} (optional: {\"error\":\"<reason>\"}).",
|
|
336
331
|
"Budget: Do NOT set tokens/time unless the user explicitly requests a limit. Built-in workflows run unlimited by default.",
|
|
337
332
|
"Model/thinkingLevel: omit by default (inherit main agent's model). Only set model/thinkingLevel when the user explicitly requests a specific model or thinking depth for this run.",
|
|
338
333
|
"Anti-patterns: Flattening args sub-fields (task/items/...) to the top level — they belong inside args. Calling {\"action\":\"run\"} without name.",
|
|
@@ -369,12 +364,6 @@ export function registerWorkflowTool(
|
|
|
369
364
|
case "status":
|
|
370
365
|
result = actionStatus(deps);
|
|
371
366
|
break;
|
|
372
|
-
case "pause":
|
|
373
|
-
result = await actionLifecycle("pause", params, deps);
|
|
374
|
-
break;
|
|
375
|
-
case "resume":
|
|
376
|
-
result = await actionLifecycle("resume", params, deps);
|
|
377
|
-
break;
|
|
378
367
|
case "abort":
|
|
379
368
|
result = await actionLifecycle("abort", params, deps);
|
|
380
369
|
break;
|
|
@@ -551,10 +540,12 @@ function actionStatus(deps: LauncherDeps): ToolResult {
|
|
|
551
540
|
};
|
|
552
541
|
}
|
|
553
542
|
|
|
554
|
-
// ──
|
|
543
|
+
// ── abort lifecycle action ────────────────────────────────────
|
|
555
544
|
|
|
545
|
+
// 一次性生命周期:abort 是唯一的提前停止方式(pause/resume 已随 D-2 移除),
|
|
546
|
+
// action 参数保留字面量类型与 WorkflowAction 单成员分发对齐。
|
|
556
547
|
async function actionLifecycle(
|
|
557
|
-
action: "
|
|
548
|
+
action: "abort",
|
|
558
549
|
params: WorkflowToolParams,
|
|
559
550
|
deps: LauncherDeps,
|
|
560
551
|
): Promise<ToolResult> {
|
|
@@ -571,13 +562,7 @@ async function actionLifecycle(
|
|
|
571
562
|
}
|
|
572
563
|
try {
|
|
573
564
|
const oldStatus = run.state.status;
|
|
574
|
-
|
|
575
|
-
await pauseRun(runId, deps);
|
|
576
|
-
} else if (action === "resume") {
|
|
577
|
-
await resumeRun(runId, deps);
|
|
578
|
-
} else {
|
|
579
|
-
await abortRun(runId, deps, params.error);
|
|
580
|
-
}
|
|
565
|
+
await abortRun(runId, deps, params.error);
|
|
581
566
|
const newStatus = run.state.status;
|
|
582
567
|
const reasonSuffix = run.state.reason ? ` (${run.state.reason})` : "";
|
|
583
568
|
return {
|