pi-subagents 0.35.1 → 0.37.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/CHANGELOG.md +71 -0
- package/README.md +224 -31
- package/agents/advisor.md +73 -0
- package/agents/delegate.md +2 -0
- package/agents/worker.md +2 -0
- package/package.json +11 -13
- package/skills/pi-subagents/SKILL.md +42 -13
- package/src/agents/agent-management.ts +7 -1
- package/src/agents/agents.ts +121 -17
- package/src/api/capability-ceiling.ts +17 -0
- package/src/api/delegation.ts +127 -0
- package/src/api/preflight.ts +399 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +52 -38
- package/src/extension/rpc.ts +31 -2
- package/src/extension/schemas.ts +29 -3
- package/src/extension/tool-description.ts +4 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +158 -17
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +47 -8
- package/src/runs/background/async-status.ts +94 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/process-terminal.ts +280 -0
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +10 -2
- package/src/runs/background/scheduled-runs.ts +6 -1
- package/src/runs/background/stale-run-reconciler.ts +6 -0
- package/src/runs/background/subagent-runner.ts +406 -54
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +181 -111
- package/src/runs/foreground/execution.ts +147 -47
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +426 -165
- package/src/runs/shared/acceptance.ts +94 -41
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/capability-ceiling.ts +177 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +5 -5
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/src/runs/shared/nested-events.ts +35 -3
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +11 -0
- package/src/runs/shared/pi-args.ts +148 -56
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/session-lease.ts +25 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +31 -19
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +21 -3
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/launch-contract.ts +123 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +314 -28
- package/src/shared/utils.ts +17 -42
- package/src/slash/delegation-adapters.ts +153 -6
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +15 -6
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +17 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
package/src/extension/rpc.ts
CHANGED
|
@@ -6,7 +6,14 @@ import { resolveAsyncRunLocation } from "../runs/background/async-resume.ts";
|
|
|
6
6
|
import { deliverStopRequest } from "../runs/background/control-channel.ts";
|
|
7
7
|
import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.ts";
|
|
8
8
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type Details,
|
|
11
|
+
ASYNC_DIR,
|
|
12
|
+
RESULTS_DIR,
|
|
13
|
+
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
14
|
+
SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
15
|
+
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
16
|
+
} from "../shared/types.ts";
|
|
10
17
|
import { readStatus } from "../shared/utils.ts";
|
|
11
18
|
import { SubagentParams } from "./schemas.ts";
|
|
12
19
|
import { validateChainInput } from "./chain-validation.ts";
|
|
@@ -16,7 +23,7 @@ export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
|
16
23
|
export const SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready";
|
|
17
24
|
export const SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:";
|
|
18
25
|
|
|
19
|
-
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "interrupt", "stop"] as const;
|
|
26
|
+
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "steer", "interrupt", "stop"] as const;
|
|
20
27
|
export type SubagentRpcMethod = typeof SUBAGENT_RPC_METHODS[number];
|
|
21
28
|
|
|
22
29
|
export interface SubagentRpcRequestEnvelope {
|
|
@@ -172,13 +179,18 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
172
179
|
capabilities: {
|
|
173
180
|
status: true,
|
|
174
181
|
asyncSpawn: true,
|
|
182
|
+
steer: true,
|
|
183
|
+
nonRecoveringSteer: true,
|
|
175
184
|
interrupt: true,
|
|
176
185
|
stop: true,
|
|
186
|
+
processTerminalProof: { version: 1, lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION },
|
|
177
187
|
},
|
|
178
188
|
events: {
|
|
179
189
|
ready: SUBAGENT_RPC_READY_EVENT,
|
|
180
190
|
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
181
191
|
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
192
|
+
asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
193
|
+
processTerminal: SUBAGENT_PROCESS_TERMINAL_EVENT,
|
|
182
194
|
},
|
|
183
195
|
session: sessionData(ctx),
|
|
184
196
|
};
|
|
@@ -212,6 +224,20 @@ function spawnParams(params: unknown): SubagentParamsLike {
|
|
|
212
224
|
return { ...(input as SubagentParamsLike), async: true, clarify: false };
|
|
213
225
|
}
|
|
214
226
|
|
|
227
|
+
function steerParams(params: unknown): SubagentParamsLike {
|
|
228
|
+
const input = assertRecordParams(params, "steer");
|
|
229
|
+
if (typeof input.message !== "string" || !input.message.trim())
|
|
230
|
+
throw new SubagentRpcError("invalid_params", "RPC steer requires a non-empty message.");
|
|
231
|
+
const target = normalizeTargetParams(input, "steer");
|
|
232
|
+
if (!target.id && !target.runId && !target.dir) throw new SubagentRpcError("invalid_params", "RPC steer requires id, runId, or dir.");
|
|
233
|
+
return {
|
|
234
|
+
action: "steer",
|
|
235
|
+
...target,
|
|
236
|
+
message: input.message.trim(),
|
|
237
|
+
steeringRecovery: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
function stopAsyncRun(
|
|
216
242
|
params: unknown,
|
|
217
243
|
options: RegisterSubagentRpcBridgeOptions,
|
|
@@ -289,6 +315,9 @@ async function handleRequest(
|
|
|
289
315
|
if (request.method === "status") {
|
|
290
316
|
return executeChecked(options, ctx, request.requestId, request.method, { action: "status", ...normalizeTargetParams(request.params, "status") });
|
|
291
317
|
}
|
|
318
|
+
if (request.method === "steer") {
|
|
319
|
+
return executeChecked(options, ctx, request.requestId, request.method, steerParams(request.params));
|
|
320
|
+
}
|
|
292
321
|
if (request.method === "interrupt") {
|
|
293
322
|
return executeChecked(options, ctx, request.requestId, request.method, { action: "interrupt", ...normalizeTargetParams(request.params, "interrupt") });
|
|
294
323
|
}
|
package/src/extension/schemas.ts
CHANGED
|
@@ -67,11 +67,26 @@ const JsonSchemaObject = Type.Unsafe({
|
|
|
67
67
|
|
|
68
68
|
const AcceptanceOverride = Type.Unsafe({
|
|
69
69
|
anyOf: [
|
|
70
|
-
{ type: "string", enum: ["auto", "attested", "checked", "verified"
|
|
70
|
+
{ type: "string", enum: ["auto", "attested", "checked", "verified"] },
|
|
71
|
+
{
|
|
72
|
+
type: "string",
|
|
73
|
+
enum: ["reviewed"],
|
|
74
|
+
deprecated: true,
|
|
75
|
+
description: "Invalid as an explicit policy. Recognized only so preflight can explain that reviewed is an achieved status.",
|
|
76
|
+
},
|
|
71
77
|
{ type: "boolean", enum: [false] },
|
|
72
78
|
{ type: "object", additionalProperties: true },
|
|
73
79
|
],
|
|
74
|
-
description: "Optional acceptance policy.
|
|
80
|
+
description: "Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Evidence levels end at verified; require independent review with acceptance.review.required. The value reviewed is an achieved status and is accepted by the schema only for actionable preflight recovery. In the current/default contract, omitted means auto-inferred. With agentContract.version=1, omitted means not requested and acceptance failures are reported separately from execution.",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const AgentContractOverride = Type.Object({
|
|
84
|
+
version: Type.Integer({ enum: [1], description: "Opt into generic agent contract v1 for this run/child." }),
|
|
85
|
+
}, { additionalProperties: false, description: "Opt-in compatibility contract. Omit to use current default behavior." });
|
|
86
|
+
|
|
87
|
+
const ChainGateOverride = Type.String({
|
|
88
|
+
enum: ["execution", "acceptance"],
|
|
89
|
+
description: "For agentContract.version=1 chain steps, choose whether the chain advances on execution success or acceptance success. Defaults to execution.",
|
|
75
90
|
});
|
|
76
91
|
|
|
77
92
|
const TurnBudgetOverride = Type.Object({
|
|
@@ -104,7 +119,9 @@ const TaskItem = Type.Object({
|
|
|
104
119
|
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
105
120
|
skill: Type.Optional(SkillOverride),
|
|
106
121
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
122
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
107
123
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
124
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
108
125
|
});
|
|
109
126
|
|
|
110
127
|
// Parallel task item (within a parallel step)
|
|
@@ -125,6 +142,8 @@ export const ParallelTaskSchema = Type.Object({
|
|
|
125
142
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
126
143
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
127
144
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
145
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
146
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
128
147
|
});
|
|
129
148
|
|
|
130
149
|
export const DynamicExpandSchema = Type.Object({
|
|
@@ -153,6 +172,8 @@ export const DynamicParallelTemplateSchema = Type.Object({
|
|
|
153
172
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
154
173
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
155
174
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
175
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
176
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
156
177
|
}, { additionalProperties: false });
|
|
157
178
|
|
|
158
179
|
export const DynamicCollectSchema = Type.Object({
|
|
@@ -179,6 +200,8 @@ export const ChainItem = Type.Object({
|
|
|
179
200
|
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
180
201
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
181
202
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
203
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
204
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
182
205
|
parallel: Type.Optional(Type.Unsafe({
|
|
183
206
|
anyOf: [
|
|
184
207
|
Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
|
|
@@ -218,7 +241,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
218
241
|
task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
|
|
219
242
|
// Management action (when present, tool operates in management mode)
|
|
220
243
|
action: Type.Optional(Type.String({
|
|
221
|
-
description: "
|
|
244
|
+
description: "Optional management/control action. Omit this field entirely for execution/delegation ({agent, task}, {tasks}, or {chain}); use it only for management/control actions."
|
|
222
245
|
})),
|
|
223
246
|
id: Type.Optional(Type.String({
|
|
224
247
|
description: "Run id or prefix for action='status', action='interrupt', action='stop', action='resume', action='steer', or action='append-step'."
|
|
@@ -236,6 +259,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
236
259
|
})),
|
|
237
260
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
238
261
|
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' (revive paused, completed, or failed children, or reach a routed nested run) or live async guidance for action='steer'. Stopped runs are non-resumable. Use index to choose a child from multi-child runs." })),
|
|
262
|
+
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls; extension RPC steering forces false so callers retain exact child ownership." })),
|
|
239
263
|
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
|
|
240
264
|
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
|
|
241
265
|
target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for action='watchdog.configure'. Defaults to main. Use target='child' with agent for a per-agent child watchdog override." })),
|
|
@@ -292,6 +316,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
292
316
|
outputMode: Type.Optional(OutputModeOverride),
|
|
293
317
|
skill: Type.Optional(SkillOverride),
|
|
294
318
|
model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
|
|
319
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
320
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
295
321
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
296
322
|
});
|
|
297
323
|
|
|
@@ -14,7 +14,7 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
|
14
14
|
• Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
|
|
15
15
|
• Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
|
|
16
16
|
|
|
17
|
-
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `
|
|
17
|
+
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below.
|
|
18
18
|
|
|
19
19
|
EXECUTION (use exactly ONE mode):
|
|
20
20
|
• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
|
|
@@ -24,6 +24,7 @@ EXECUTION (use exactly ONE mode):
|
|
|
24
24
|
• Optional context: { context: "fresh" | "fork" } (explicit value overrides every child; when omitted, each requested agent uses its own defaultContext, otherwise "fresh"; inspect agent defaults via { action: "list" })
|
|
25
25
|
• Fork thinking: model strings accept a thinking suffix (provider/model:off|minimal|low|medium|high|xhigh|max). Forking over a parent transcript that carries signed Anthropic thinking blocks forces thinking off only when a child's effective primary or fallback model resolves to the Anthropic provider or anthropic-messages API; unresolved models are treated conservatively. The result notes affected children, including on failures. Use fresh context when an Anthropic child needs thinking.
|
|
26
26
|
• Optional timeout: { timeoutMs } or { maxRuntimeMs } sets a run-level max runtime for foreground and async/background runs
|
|
27
|
+
• Acceptance: omit acceptance for reviewer/read-only calls. Evidence levels end at verified. Use acceptance.review.required to require independent review of a writer result. Never request acceptance:"reviewed"; reviewed is achieved only after an independent reviewer result.
|
|
27
28
|
• If { action: "list" } shows proactive skill subagent suggestions, consider a small fresh-context fanout for broad tasks where one of those skills would materially help
|
|
28
29
|
|
|
29
30
|
CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
@@ -74,12 +75,13 @@ DIAGNOSTICS:
|
|
|
74
75
|
|
|
75
76
|
${SUBAGENT_SAFETY_GUIDANCE}`;
|
|
76
77
|
|
|
77
|
-
export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `
|
|
78
|
+
export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below. Use exactly one mode per call.
|
|
78
79
|
|
|
79
80
|
EXECUTE:
|
|
80
81
|
• Before execution, call { action: "list" }; run only executable/non-disabled configured agents/chains.
|
|
81
82
|
• SINGLE {agent, task?}; PARALLEL {tasks:[{agent,task,count?,output?,reads?,progress?}], concurrency?, worktree?}; CHAIN {chain:[{agent,task?},{parallel:[...]}]}.
|
|
82
83
|
• context can be "fresh" or "fork"; omitted uses each agent defaultContext, otherwise fresh. timeoutMs/maxRuntimeMs apply to foreground and async/background runs.
|
|
84
|
+
• Omit acceptance for reviewer/read-only calls. Evidence levels end at verified; use acceptance.review.required for independent writer review. reviewed is an achieved status, never an explicit input.
|
|
83
85
|
• Chain templates may use {task}, {previous}, {chain_dir}, and named outputs. Parallel worktree isolation requires a clean git repo.
|
|
84
86
|
• Chain example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {parallel: [{agent:"agent-b", task:"Check {previous}", count: 3}]}] }
|
|
85
87
|
• If list shows proactive skill subagent suggestions, use a small fresh-context fanout only when the task is broad enough.
|
|
@@ -161,7 +161,7 @@ export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomB
|
|
|
161
161
|
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
162
162
|
|
|
163
163
|
const bridgeTools = ["intercom", "contact_supervisor"];
|
|
164
|
-
const tools = agent.tools
|
|
164
|
+
const tools = agent.tools && agent.tools.length > 0
|
|
165
165
|
? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
|
|
166
166
|
: agent.tools;
|
|
167
167
|
const instruction = bridge.instruction;
|
|
@@ -447,6 +447,43 @@ function requestMatchesContext(request: SupervisorRequest, state: Pick<SubagentS
|
|
|
447
447
|
return Boolean(currentSessionId && request.orchestratorSessionId === currentSessionId);
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
function rememberedForegroundChild(request: SupervisorRequest, state: SubagentState) {
|
|
451
|
+
const run = state.foregroundRuns?.get(request.runId);
|
|
452
|
+
const child = run?.children.find((candidate) => candidate.index === request.childIndex && candidate.agent === request.agent)
|
|
453
|
+
?? run?.children[request.childIndex];
|
|
454
|
+
return run && child ? { run, child } : undefined;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function markForegroundSupervisorAttention(request: SupervisorRequest, state: SubagentState): void {
|
|
458
|
+
const remembered = rememberedForegroundChild(request, state);
|
|
459
|
+
if (!remembered || remembered.child.status !== "detached") return;
|
|
460
|
+
const updatedAt = Date.now();
|
|
461
|
+
remembered.run.updatedAt = updatedAt;
|
|
462
|
+
remembered.child.activityState = "needs_attention";
|
|
463
|
+
remembered.child.lastActivityAt = request.createdAt;
|
|
464
|
+
remembered.child.currentTool = "contact_supervisor";
|
|
465
|
+
remembered.child.currentToolStartedAt = request.createdAt;
|
|
466
|
+
remembered.child.updatedAt = updatedAt;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function clearForegroundSupervisorAttention(request: SupervisorRequest, pending: Map<string, PendingSupervisorRequest>, state: SubagentState): void {
|
|
470
|
+
if ([...pending.values()].some((candidate) =>
|
|
471
|
+
candidate.expectsReply
|
|
472
|
+
&& candidate.runId === request.runId
|
|
473
|
+
&& candidate.agent === request.agent
|
|
474
|
+
&& candidate.childIndex === request.childIndex
|
|
475
|
+
)) return;
|
|
476
|
+
const remembered = rememberedForegroundChild(request, state);
|
|
477
|
+
if (!remembered || remembered.child.status !== "detached" || remembered.child.currentTool !== "contact_supervisor") return;
|
|
478
|
+
const updatedAt = Date.now();
|
|
479
|
+
remembered.run.updatedAt = updatedAt;
|
|
480
|
+
remembered.child.activityState = undefined;
|
|
481
|
+
remembered.child.lastActivityAt = updatedAt;
|
|
482
|
+
remembered.child.currentTool = undefined;
|
|
483
|
+
remembered.child.currentToolStartedAt = undefined;
|
|
484
|
+
remembered.child.updatedAt = updatedAt;
|
|
485
|
+
}
|
|
486
|
+
|
|
450
487
|
function removeRequestFile(file: string): void {
|
|
451
488
|
try {
|
|
452
489
|
fs.rmSync(file, { force: true });
|
|
@@ -465,10 +502,8 @@ function requestExpiresAt(request: SupervisorRequest, now: number): number {
|
|
|
465
502
|
|
|
466
503
|
function requestRunInactive(request: SupervisorRequest, state: SubagentState): boolean {
|
|
467
504
|
if (state.foregroundControls.has(request.runId)) return false;
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
?? foregroundRun?.children[request.childIndex];
|
|
471
|
-
if (foregroundChild) return foregroundChild.status !== "detached";
|
|
505
|
+
const foreground = rememberedForegroundChild(request, state);
|
|
506
|
+
if (foreground) return foreground.child.status !== "detached";
|
|
472
507
|
|
|
473
508
|
const asyncJob = state.asyncJobs.get(request.runId);
|
|
474
509
|
if (!asyncJob) return false;
|
|
@@ -580,6 +615,7 @@ function buildParentIntercomTool(pending: Map<string, PendingSupervisorRequest>,
|
|
|
580
615
|
const request = resolvePendingRequest(pending, input);
|
|
581
616
|
writeReply(request, input.message ?? "");
|
|
582
617
|
pending.delete(request.id);
|
|
618
|
+
clearForegroundSupervisorAttention(request, pending, state);
|
|
583
619
|
return { content: [{ type: "text", text: `Replied to supervisor request ${request.id}.` }], details: { replyTo: request.id, runId: request.runId, agent: request.agent } };
|
|
584
620
|
}
|
|
585
621
|
if (input.action === "send" || input.action === "ask") {
|
|
@@ -629,7 +665,10 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
629
665
|
continue;
|
|
630
666
|
}
|
|
631
667
|
seenFiles.add(file);
|
|
632
|
-
if (request.expectsReply)
|
|
668
|
+
if (request.expectsReply) {
|
|
669
|
+
pending.set(request.id, request);
|
|
670
|
+
markForegroundSupervisorAttention(request, state);
|
|
671
|
+
}
|
|
633
672
|
else {
|
|
634
673
|
removeRequestFile(request.requestFile);
|
|
635
674
|
}
|
|
@@ -645,7 +684,7 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
645
684
|
agent: request.agent,
|
|
646
685
|
childIndex: request.childIndex,
|
|
647
686
|
},
|
|
648
|
-
});
|
|
687
|
+
}, { triggerTurn: true });
|
|
649
688
|
if (request.expectsReply) {
|
|
650
689
|
(pi as { events?: IntercomEventBus }).events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
|
|
651
690
|
requestId: request.id,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type IntercomEventBus,
|
|
6
6
|
type NestedRunSummary,
|
|
7
7
|
type PublicNestedRunSummary,
|
|
8
|
+
type ParallelHandoffReference,
|
|
8
9
|
type SingleResult,
|
|
9
10
|
type SubagentResultIntercomChild,
|
|
10
11
|
type SubagentResultIntercomPayload,
|
|
@@ -179,6 +180,7 @@ interface GroupedResultIntercomMessageInput {
|
|
|
179
180
|
asyncId?: string;
|
|
180
181
|
asyncDir?: string;
|
|
181
182
|
chainSteps?: number;
|
|
183
|
+
parallelHandoff?: ParallelHandoffReference;
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
function asyncResumeGuidance(input: {
|
|
@@ -207,6 +209,7 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
207
209
|
asyncId?: string;
|
|
208
210
|
asyncDir?: string;
|
|
209
211
|
chainSteps?: number;
|
|
212
|
+
parallelHandoff?: ParallelHandoffReference;
|
|
210
213
|
}): string {
|
|
211
214
|
const counts = countStatuses(input.children);
|
|
212
215
|
const lines: string[] = [
|
|
@@ -222,6 +225,7 @@ function formatSubagentResultIntercomMessage(input: {
|
|
|
222
225
|
}
|
|
223
226
|
if (input.asyncId) lines.push(`Async id: ${input.asyncId}`);
|
|
224
227
|
if (input.asyncDir) lines.push(`Async dir: ${input.asyncDir}`);
|
|
228
|
+
if (input.parallelHandoff) lines.push(`Parallel handoff: ${input.parallelHandoff.path}`);
|
|
225
229
|
const resumeGuidance = asyncResumeGuidance(input);
|
|
226
230
|
if (resumeGuidance) lines.push(resumeGuidance);
|
|
227
231
|
if (input.children.some((child) => child.intercomTarget)) {
|
|
@@ -266,6 +270,7 @@ export function buildSubagentResultIntercomPayload(input: GroupedResultIntercomM
|
|
|
266
270
|
...(input.asyncId ? { asyncId: input.asyncId } : {}),
|
|
267
271
|
...(input.asyncDir ? { asyncDir: input.asyncDir } : {}),
|
|
268
272
|
...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
|
|
273
|
+
...(input.parallelHandoff ? { parallelHandoff: input.parallelHandoff } : {}),
|
|
269
274
|
...(firstChild?.agent ? { agent: firstChild.agent } : {}),
|
|
270
275
|
...(firstChild?.index !== undefined ? { index: firstChild.index } : {}),
|
|
271
276
|
...(firstChild?.artifactPath ? { artifactPath: firstChild.artifactPath } : {}),
|
|
@@ -352,6 +357,8 @@ export function formatSubagentResultReceipt(input: {
|
|
|
352
357
|
`Children: ${formatStatusCounts(counts)}`,
|
|
353
358
|
];
|
|
354
359
|
|
|
360
|
+
if (input.payload.parallelHandoff) lines.push(`Parallel handoff: ${input.payload.parallelHandoff.path}`);
|
|
361
|
+
|
|
355
362
|
const artifacts = input.payload.children.filter((child) => typeof child.artifactPath === "string");
|
|
356
363
|
if (artifacts.length > 0) {
|
|
357
364
|
lines.push("Artifacts:");
|