pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
|
+
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
6
|
+
import type { AsyncStatus, Details, SubagentState, ToolBudgetConfig, TurnBudgetConfig } from "../../shared/types.ts";
|
|
7
|
+
import { readStatus } from "../../shared/utils.ts";
|
|
8
|
+
import { consumeSteerAcks, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
9
|
+
import { resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
10
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
11
|
+
import { actionResultFromSteeringStatus, claimSteeringRecovery, remainingSteeringRecoveryLimits, updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
12
|
+
|
|
13
|
+
export async function steerAsyncRun(input: {
|
|
14
|
+
state: SubagentState;
|
|
15
|
+
runId: string;
|
|
16
|
+
message: string;
|
|
17
|
+
index?: number;
|
|
18
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
19
|
+
location: { asyncDir: string | null };
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
ackTimeoutMs?: number;
|
|
22
|
+
recoveryTimeoutMs?: number;
|
|
23
|
+
onRequestQueued?: (requestPath: string) => void;
|
|
24
|
+
onBeforeRecoveryClaim?: (requestId: string, committedAt: number) => void;
|
|
25
|
+
onRecoveryCommitted?: (requestId: string, committedAt: number) => void;
|
|
26
|
+
recover?: (limits: { timeoutMs?: number; absoluteDeadlineAt?: number; turnBudget?: TurnBudgetConfig; toolBudget?: ToolBudgetConfig }) => Promise<AgentToolResult<Details>>;
|
|
27
|
+
}): Promise<AgentToolResult<Details>> {
|
|
28
|
+
if (!input.location.asyncDir) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: `Async run '${input.runId}' has no live run directory to steer.` }],
|
|
31
|
+
isError: true,
|
|
32
|
+
details: { mode: "management", results: [] },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const asyncDir = input.location.asyncDir;
|
|
36
|
+
const status = reconcileAsyncRun(asyncDir, { kill: input.kill }).status;
|
|
37
|
+
if (input.state.currentSessionId && status?.sessionId !== input.state.currentSessionId) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: `Async run '${input.runId}' was not found in the active session.` }],
|
|
40
|
+
isError: true,
|
|
41
|
+
details: { mode: "management", results: [] },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (!status || (status.state !== "running" && status.state !== "queued")) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: `Async run '${input.runId}' is not running or queued and cannot be steered.` }],
|
|
47
|
+
isError: true,
|
|
48
|
+
details: { mode: "management", results: [] },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const steps = status.steps ?? [];
|
|
52
|
+
if (input.index !== undefined) {
|
|
53
|
+
if (input.index < 0 || input.index >= steps.length) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Async run '${status.runId}' has ${steps.length} children. Index ${input.index} is out of range.` }],
|
|
56
|
+
isError: true,
|
|
57
|
+
details: { mode: "management", results: [] },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const targetStep = steps[input.index];
|
|
61
|
+
if (targetStep && targetStep.status !== "running" && targetStep.status !== "pending") {
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: "text", text: `Async run '${status.runId}' child ${input.index} is ${targetStep.status} and cannot be steered.` }],
|
|
64
|
+
isError: true,
|
|
65
|
+
details: { mode: "management", results: [] },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const running = steps.filter((step) => step.status === "running");
|
|
70
|
+
if (running.length === 0 && steps.length > 1) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "text", text: `Async run '${status.runId}' has no running child yet. Provide index to steer a queued child.` }],
|
|
73
|
+
isError: true,
|
|
74
|
+
details: { mode: "management", results: [] },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const runningIndexes = steps
|
|
79
|
+
.map((step, index) => step.status === "running" ? index : undefined)
|
|
80
|
+
.filter((index): index is number => index !== undefined);
|
|
81
|
+
const effectiveTargetIndex = input.index ?? (status.mode === "single" && runningIndexes.length === 0 && steps[0]?.status === "pending" ? 0 : undefined);
|
|
82
|
+
const targetIndexes = effectiveTargetIndex !== undefined ? [effectiveTargetIndex] : runningIndexes;
|
|
83
|
+
if (targetIndexes.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `Async run '${status.runId}' has no running child to steer.` }],
|
|
86
|
+
isError: true,
|
|
87
|
+
details: { mode: "management", results: [] },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const requestId = randomUUID();
|
|
91
|
+
let requestPath: string;
|
|
92
|
+
try {
|
|
93
|
+
requestPath = requestAsyncSteer(asyncDir, {
|
|
94
|
+
message: input.message,
|
|
95
|
+
...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
|
|
96
|
+
source: "steer-action",
|
|
97
|
+
id: requestId,
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: `Failed to queue steering for async run ${status.runId}: ${error instanceof Error ? error.message : String(error)}` }],
|
|
102
|
+
isError: true,
|
|
103
|
+
details: { mode: "management", results: [] },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
input.onRequestQueued?.(requestPath);
|
|
107
|
+
const tracked = input.state.asyncJobs.get(status.runId);
|
|
108
|
+
if (tracked) tracked.updatedAt = Date.now();
|
|
109
|
+
const targets = targetIndexes.map((index) => ({ index, state: status.steps?.[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
110
|
+
if (targets.every((target) => target.state === "scheduled")) {
|
|
111
|
+
const scheduled = { requestId, state: "scheduled" as const, sourceRunId: status.runId, targets };
|
|
112
|
+
return { content: [{ type: "text", text: `Steering scheduled for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
113
|
+
}
|
|
114
|
+
const waited = await waitForSteeringAction({ asyncDir, sourceRunId: status.runId, requestId, timeoutMs: input.ackTimeoutMs ?? 3_000, signal: input.signal });
|
|
115
|
+
const result = waited ?? { requestId, state: "pending" as const, sourceRunId: status.runId, targets };
|
|
116
|
+
if (input.signal?.aborted) {
|
|
117
|
+
return { content: [{ type: "text", text: `Steering pending for async run ${status.runId} (request ${requestId}); caller aborted before recovery.` }], details: { mode: "management", results: [], steering: result } };
|
|
118
|
+
}
|
|
119
|
+
const finalStatus = readStatus(asyncDir);
|
|
120
|
+
const finalResult = finalStatus?.steering ? actionResultFromSteeringStatus(finalStatus.steering, status.runId, requestId) : undefined;
|
|
121
|
+
if (finalResult?.state === "delivered") {
|
|
122
|
+
return { content: [{ type: "text", text: `Steering delivered for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: finalResult } };
|
|
123
|
+
}
|
|
124
|
+
const running = (finalStatus?.steps ?? status.steps).filter((step) => step.status === "running");
|
|
125
|
+
const recoveryAllowed = status.mode === "single" && status.isNested !== true && running.length === 1 && Boolean(finalStatus?.steering) && (input.index === undefined || input.index === 0);
|
|
126
|
+
if (recoveryAllowed && finalResult?.state !== "scheduled" && input.recover) {
|
|
127
|
+
const appendSteeringNotice = (state: "failed" | "recovered", message: string): void => {
|
|
128
|
+
try {
|
|
129
|
+
fs.appendFileSync(path.join(asyncDir, "events.jsonl"), `${JSON.stringify({ type: "subagent.steering.notice", ts: Date.now(), runId: status.runId, requestId, state, message, ...(status.sessionId ? { currentSessionId: status.sessionId } : {}) })}\n`);
|
|
130
|
+
} catch {
|
|
131
|
+
// The action result and status remain authoritative if diagnostic notification persistence fails.
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
const latest = readStatus(asyncDir);
|
|
136
|
+
const latestResult = latest?.steering ? actionResultFromSteeringStatus(latest.steering, status.runId, requestId) : undefined;
|
|
137
|
+
if (latestResult?.state === "delivered") return { content: [{ type: "text", text: `Steering delivered for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: latestResult } };
|
|
138
|
+
const committedAt = Date.now();
|
|
139
|
+
input.onBeforeRecoveryClaim?.(requestId, committedAt);
|
|
140
|
+
const { claimPath, markerPath } = claimSteeringRecovery(asyncDir, { requestId, sourceRunId: status.runId, committedAt });
|
|
141
|
+
input.onRecoveryCommitted?.(requestId, committedAt);
|
|
142
|
+
const preCommitStatus = readStatus(asyncDir);
|
|
143
|
+
const preCommitResult = preCommitStatus?.steering ? actionResultFromSteeringStatus(preCommitStatus.steering, status.runId, requestId) : undefined;
|
|
144
|
+
if (preCommitResult?.state === "delivered" && preCommitResult.targets.every((target) => target.deliveredAt !== undefined && target.deliveredAt <= committedAt)) {
|
|
145
|
+
fs.rmSync(markerPath, { force: true });
|
|
146
|
+
fs.rmSync(claimPath, { force: true });
|
|
147
|
+
return { content: [{ type: "text", text: `Steering delivered for async run ${status.runId} (request ${requestId}).` }], details: { mode: "management", results: [], steering: preCommitResult } };
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
deliverInterruptRequest({ asyncDir, pid: latest?.pid ?? status.pid, kill: input.kill, source: "steering-recovery" });
|
|
151
|
+
} catch (error) {
|
|
152
|
+
fs.rmSync(markerPath, { force: true });
|
|
153
|
+
fs.rmSync(claimPath, { force: true });
|
|
154
|
+
throw new Error(`Failed to commit steering recovery interrupt: ${error instanceof Error ? error.message : String(error)}`);
|
|
155
|
+
}
|
|
156
|
+
const pauseDeadline = Date.now() + (input.recoveryTimeoutMs ?? 15_000);
|
|
157
|
+
let paused: AsyncStatus | null = null;
|
|
158
|
+
while (Date.now() <= pauseDeadline) {
|
|
159
|
+
if (input.signal?.aborted) break;
|
|
160
|
+
const candidate = readStatus(asyncDir);
|
|
161
|
+
if (candidate?.state === "paused" && candidate.endedAt !== undefined) { paused = candidate; break; }
|
|
162
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 50));
|
|
163
|
+
}
|
|
164
|
+
if (!paused) throw new Error("Source run did not reach confirmed paused state within 15 seconds; no replacement was launched and the recovery claim remains committed to prevent a delayed duplicate.");
|
|
165
|
+
let lateAckRecorded = false;
|
|
166
|
+
for (const ack of consumeSteerAcks(asyncDir)) {
|
|
167
|
+
if (!paused.steering?.recent.some((request) => request.id === ack.requestId && request.targets.some((target) => target.index === ack.index))) continue;
|
|
168
|
+
const state = ack.state === "delivered" ? "late" : "failed";
|
|
169
|
+
const reason = ack.state === "delivered" ? "acknowledged after recovery commit" : ack.message;
|
|
170
|
+
updateSteeringTarget(paused.steering, ack.requestId, ack.index, state, ack.ts, { reason });
|
|
171
|
+
const stepSteering = paused.steps?.[ack.index]?.steering;
|
|
172
|
+
if (stepSteering) updateSteeringTarget(stepSteering, ack.requestId, ack.index, state, ack.ts, { reason });
|
|
173
|
+
lateAckRecorded = true;
|
|
174
|
+
try {
|
|
175
|
+
fs.appendFileSync(path.join(asyncDir, "events.jsonl"), `${JSON.stringify({ type: ack.state === "delivered" ? "subagent.steer.delivered" : "subagent.steer.failed", ts: ack.ts, runId: status.runId, requestId: ack.requestId, index: ack.index, late: true, message: ack.message })}\n`);
|
|
176
|
+
} catch {
|
|
177
|
+
// Status remains authoritative when diagnostic event persistence fails.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (lateAckRecorded) writeAtomicJson(path.join(asyncDir, "status.json"), paused);
|
|
181
|
+
let recoveryTarget;
|
|
182
|
+
try {
|
|
183
|
+
recoveryTarget = resolveAsyncResumeTarget(
|
|
184
|
+
{ id: status.runId },
|
|
185
|
+
{ kill: input.kill },
|
|
186
|
+
{ sessionId: input.state.currentSessionId ?? undefined },
|
|
187
|
+
);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new Error(`Source run remains paused and cannot be revived safely: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
|
+
}
|
|
191
|
+
if (recoveryTarget.kind !== "revive" || !recoveryTarget.sessionFile) throw new Error("Source run has no valid persisted child session; it remains paused and requires a new run.");
|
|
192
|
+
if (!recoveryTarget.recoveryDescriptor) throw new Error("Source run has no private recovery descriptor; it remains paused and is not safely resumable.");
|
|
193
|
+
const limits = remainingSteeringRecoveryLimits(recoveryTarget.recoveryDescriptor, paused);
|
|
194
|
+
const revived = await input.recover(limits);
|
|
195
|
+
if (revived.isError || !revived.details.asyncId) throw new Error(revived.content[0]?.type === "text" ? revived.content[0].text : "Replacement launch failed; source run remains paused.");
|
|
196
|
+
const sourceStatus = readStatus(asyncDir);
|
|
197
|
+
const targetIndex = input.index ?? status.steps.findIndex((step) => step.status === "running");
|
|
198
|
+
if (sourceStatus?.state === "paused" && sourceStatus.steering && targetIndex >= 0) {
|
|
199
|
+
updateSteeringTarget(sourceStatus.steering, requestId, targetIndex, "recovered", Date.now(), { replacementRunId: revived.details.asyncId });
|
|
200
|
+
const stepSteering = sourceStatus.steps?.[targetIndex]?.steering;
|
|
201
|
+
if (stepSteering) updateSteeringTarget(stepSteering, requestId, targetIndex, "recovered", Date.now(), { replacementRunId: revived.details.asyncId });
|
|
202
|
+
writeAtomicJson(path.join(asyncDir, "status.json"), sourceStatus);
|
|
203
|
+
}
|
|
204
|
+
const recovered = sourceStatus?.steering ? actionResultFromSteeringStatus(sourceStatus.steering, status.runId, requestId, revived.details.asyncId) : undefined;
|
|
205
|
+
appendSteeringNotice("recovered", `Steering recovered for run ${status.runId}; replacement ${revived.details.asyncId} launched.`);
|
|
206
|
+
return { content: [{ type: "text", text: `Steering recovered for async run ${status.runId}; replacement ${revived.details.asyncId} launched after the source paused.` }], details: { mode: "management", results: [], steering: recovered ?? { requestId, state: "recovered", sourceRunId: status.runId, replacementRunId: revived.details.asyncId, targets: [{ index: input.index ?? 0, state: "recovered" }] } } };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
209
|
+
const failedStatus = readStatus(asyncDir);
|
|
210
|
+
const targetIndex = input.index ?? status.steps.findIndex((step) => step.status === "running");
|
|
211
|
+
if (failedStatus && failedStatus.state !== "running" && failedStatus.state !== "queued" && failedStatus.steering && targetIndex >= 0) {
|
|
212
|
+
updateSteeringTarget(failedStatus.steering, requestId, targetIndex, "failed", Date.now(), { reason });
|
|
213
|
+
const stepSteering = failedStatus.steps?.[targetIndex]?.steering;
|
|
214
|
+
if (stepSteering) updateSteeringTarget(stepSteering, requestId, targetIndex, "failed", Date.now(), { reason });
|
|
215
|
+
failedStatus.activityState = "needs_attention";
|
|
216
|
+
writeAtomicJson(path.join(asyncDir, "status.json"), failedStatus);
|
|
217
|
+
}
|
|
218
|
+
const failed = failedStatus?.steering ? actionResultFromSteeringStatus(failedStatus.steering, status.runId, requestId) : undefined;
|
|
219
|
+
appendSteeringNotice("failed", `Steering failed for run ${status.runId}: ${reason}`);
|
|
220
|
+
return { content: [{ type: "text", text: `Steering failed for async run ${status.runId} (request ${requestId}): ${reason}` }], isError: true, details: { mode: "management", results: [], steering: failed ?? { requestId, state: "failed", sourceRunId: status.runId, targets: [{ index: input.index ?? 0, state: "failed", reason }] } } };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const stateText = result.state === "delivered" ? "delivered" : result.state === "scheduled" ? "scheduled" : result.state === "recovered" ? "recovered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
|
|
224
|
+
const isError = result.state === "failed" || result.state === "partial";
|
|
225
|
+
return {
|
|
226
|
+
content: [{ type: "text", text: `Steering ${stateText} for async run ${status.runId} (request ${requestId}).` }],
|
|
227
|
+
...(isError ? { isError: true } : {}),
|
|
228
|
+
details: { mode: "management", results: [], steering: result },
|
|
229
|
+
};
|
|
230
|
+
}
|
|
@@ -1004,6 +1004,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1004
1004
|
"medium": "Moderate reasoning",
|
|
1005
1005
|
"high": "Deep reasoning",
|
|
1006
1006
|
"xhigh": "Maximum reasoning (ultrathink)",
|
|
1007
|
+
"max": "Maximum available reasoning",
|
|
1007
1008
|
};
|
|
1008
1009
|
|
|
1009
1010
|
const levels = this.getAvailableThinkingLevels(this.editingStep!);
|
|
@@ -1102,18 +1103,29 @@ export class ChainClarifyComponent implements Component {
|
|
|
1102
1103
|
return lines;
|
|
1103
1104
|
}
|
|
1104
1105
|
|
|
1105
|
-
private
|
|
1106
|
-
const
|
|
1106
|
+
private getShortcutHelpText(): string {
|
|
1107
|
+
const th = this.theme;
|
|
1108
|
+
const shortcut = (key: string, label: string) =>
|
|
1109
|
+
th.fg("accent", `[${key}]`) + th.fg("dim", label);
|
|
1110
|
+
const backgroundState = this.runInBackground ? "ON" : "OFF";
|
|
1111
|
+
const background = shortcut("b", `Background:${backgroundState}`);
|
|
1112
|
+
|
|
1107
1113
|
switch (this.mode) {
|
|
1108
1114
|
case 'single':
|
|
1109
|
-
return `
|
|
1115
|
+
return ` ${shortcut("e", "Task")} ${shortcut("m", "Model")} ${shortcut("t", "Think")} ${shortcut("w", "Output")} ${shortcut("s", "Skills")} ${background}`;
|
|
1110
1116
|
case 'parallel':
|
|
1111
|
-
return `
|
|
1117
|
+
return ` ${shortcut("e", "Task")} ${shortcut("m", "Model")} ${shortcut("t", "Think")} ${shortcut("s", "Skills")} ${background}`;
|
|
1112
1118
|
case 'chain':
|
|
1113
|
-
return `
|
|
1119
|
+
return ` ${shortcut("e", "Task")} ${shortcut("m", "Model")} ${shortcut("t", "Think")} ${shortcut("w", "Output")} ${shortcut("r", "Reads")} ${shortcut("p", "Prog")} ${shortcut("s", "Skills")} ${background}`;
|
|
1114
1120
|
}
|
|
1115
1121
|
}
|
|
1116
1122
|
|
|
1123
|
+
private getFooterText(): string {
|
|
1124
|
+
return this.mode === 'single'
|
|
1125
|
+
? " [Enter] Run • [Esc] Cancel "
|
|
1126
|
+
: " [Enter] Run • [Esc] Cancel • [↑↓] Navigate ";
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1117
1129
|
private appendNotice(lines: string[]): void {
|
|
1118
1130
|
if (!this.noticeMessage) return;
|
|
1119
1131
|
const color = this.noticeMessage.type === "error" ? "error" : "success";
|
|
@@ -1162,7 +1174,7 @@ export class ChainClarifyComponent implements Component {
|
|
|
1162
1174
|
const skillsLabel = th.fg("dim", "skills: ");
|
|
1163
1175
|
lines.push(this.row(` ${skillsLabel}${truncateToWidth(skillsValue, innerW - 14)}`));
|
|
1164
1176
|
|
|
1165
|
-
lines.push(this.row(
|
|
1177
|
+
lines.push(this.row(this.getShortcutHelpText()));
|
|
1166
1178
|
|
|
1167
1179
|
this.appendNotice(lines);
|
|
1168
1180
|
lines.push(this.renderFooter(this.getFooterText()));
|
|
@@ -1216,6 +1228,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
1216
1228
|
lines.push(this.row(""));
|
|
1217
1229
|
}
|
|
1218
1230
|
|
|
1231
|
+
if (this.agentConfigs.length > 0) lines.pop();
|
|
1232
|
+
lines.push(this.row(this.getShortcutHelpText()));
|
|
1219
1233
|
this.appendNotice(lines);
|
|
1220
1234
|
lines.push(this.renderFooter(this.getFooterText()));
|
|
1221
1235
|
|
|
@@ -1319,6 +1333,8 @@ export class ChainClarifyComponent implements Component {
|
|
|
1319
1333
|
lines.push(this.row(""));
|
|
1320
1334
|
}
|
|
1321
1335
|
|
|
1336
|
+
if (this.agentConfigs.length > 0) lines.pop();
|
|
1337
|
+
lines.push(this.row(this.getShortcutHelpText()));
|
|
1322
1338
|
this.appendNotice(lines);
|
|
1323
1339
|
lines.push(this.renderFooter(this.getFooterText()));
|
|
1324
1340
|
|
|
@@ -63,9 +63,9 @@ import {
|
|
|
63
63
|
MAX_CONCURRENCY,
|
|
64
64
|
resolveChildMaxSubagentDepth,
|
|
65
65
|
} from "../../shared/types.ts";
|
|
66
|
-
import {
|
|
66
|
+
import { resolveEffectiveSubagentModel } from "../shared/model-fallback.ts";
|
|
67
67
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
68
|
-
import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
68
|
+
import { injectSingleOutputInstruction, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
69
69
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
70
70
|
import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
71
71
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
@@ -88,7 +88,7 @@ interface ChainExecutionDetailsInput {
|
|
|
88
88
|
outputs?: ChainOutputMap;
|
|
89
89
|
currentFlatIndex?: number;
|
|
90
90
|
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
91
|
-
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
91
|
+
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
interface ParallelChainRunInput {
|
|
@@ -109,8 +109,8 @@ interface ParallelChainRunInput {
|
|
|
109
109
|
globalTaskIndex: number;
|
|
110
110
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
111
111
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
112
|
-
sessionFileForTask?: (agentName: string, idx?: number) => string | undefined;
|
|
113
|
-
thinkingOverrideForTask?: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
112
|
+
sessionFileForTask?: (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
113
|
+
thinkingOverrideForTask?: (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
114
114
|
shareEnabled: boolean;
|
|
115
115
|
artifactConfig: ArtifactConfig;
|
|
116
116
|
artifactsDir: string;
|
|
@@ -152,6 +152,7 @@ interface ParallelChainRunInput {
|
|
|
152
152
|
toolBudget?: ResolvedToolBudget;
|
|
153
153
|
configToolBudget?: ToolBudgetConfig;
|
|
154
154
|
globalSemaphore?: Semaphore;
|
|
155
|
+
dynamic?: boolean;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details {
|
|
@@ -230,6 +231,21 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
230
231
|
const concurrency = input.step.concurrency ?? MAX_CONCURRENCY;
|
|
231
232
|
const failFast = input.step.failFast ?? false;
|
|
232
233
|
let aborted = false;
|
|
234
|
+
const effectiveModels = input.step.parallel.map((task) => {
|
|
235
|
+
const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
236
|
+
return resolveEffectiveSubagentModel(
|
|
237
|
+
task.model,
|
|
238
|
+
taskAgentConfig?.model,
|
|
239
|
+
input.ctx.model,
|
|
240
|
+
input.availableModels,
|
|
241
|
+
input.ctx.model?.provider,
|
|
242
|
+
{ scope: input.modelScope },
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
for (let taskIndex = 0; taskIndex < input.step.parallel.length; taskIndex++) {
|
|
246
|
+
const task = input.step.parallel[taskIndex]!;
|
|
247
|
+
input.sessionFileForTask?.(task.agent, input.globalTaskIndex + taskIndex, effectiveModels[taskIndex]);
|
|
248
|
+
}
|
|
233
249
|
|
|
234
250
|
const parallelResults = await mapConcurrent(
|
|
235
251
|
input.step.parallel,
|
|
@@ -248,9 +264,10 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
248
264
|
|
|
249
265
|
const taskTemplate = input.parallelTemplates[taskIndex] ?? "{previous}";
|
|
250
266
|
const behavior = suppressProgressForReadOnlyTask(input.parallelBehaviors[taskIndex]!, taskTemplate, input.originalTask);
|
|
267
|
+
const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
251
268
|
const templateHasPrevious = taskTemplate.includes("{previous}");
|
|
252
269
|
const { prefix, suffix } = buildChainInstructions(
|
|
253
|
-
behavior,
|
|
270
|
+
{ ...behavior, output: false },
|
|
254
271
|
input.chainDir,
|
|
255
272
|
false,
|
|
256
273
|
templateHasPrevious ? undefined : input.prev,
|
|
@@ -263,14 +280,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
263
280
|
const cleanTask = taskStr;
|
|
264
281
|
taskStr = prefix + taskStr + suffix;
|
|
265
282
|
|
|
266
|
-
const
|
|
267
|
-
const effectiveModel = resolveSubagentModelOverride(
|
|
268
|
-
task.model ?? taskAgentConfig?.model,
|
|
269
|
-
input.ctx.model,
|
|
270
|
-
input.availableModels,
|
|
271
|
-
input.ctx.model?.provider,
|
|
272
|
-
{ scope: input.modelScope, source: task.model ? "explicit" : "inherited" },
|
|
273
|
-
);
|
|
283
|
+
const effectiveModel = effectiveModels[taskIndex];
|
|
274
284
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
|
|
275
285
|
const toolBudget = resolveChainToolBudget({ stepBudget: task.toolBudget, runBudget: input.toolBudget, agentBudget: taskAgentConfig?.toolBudget, configBudget: input.configToolBudget });
|
|
276
286
|
if (toolBudget.error) throw new Error(toolBudget.error);
|
|
@@ -282,6 +292,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
282
292
|
const outputPath = typeof behavior.output === "string"
|
|
283
293
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output))
|
|
284
294
|
: undefined;
|
|
295
|
+
taskStr = injectSingleOutputInstruction(taskStr, outputPath, taskAgentConfig);
|
|
285
296
|
const interruptController = new AbortController();
|
|
286
297
|
if (input.foregroundControl) {
|
|
287
298
|
input.foregroundControl.currentAgent = task.agent;
|
|
@@ -310,9 +321,9 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
310
321
|
runId: input.runId,
|
|
311
322
|
index: input.globalTaskIndex + taskIndex,
|
|
312
323
|
sessionDir: input.sessionDirForIndex(input.globalTaskIndex + taskIndex),
|
|
313
|
-
sessionFile: input.sessionFileForTask?.(task.agent, input.globalTaskIndex + taskIndex)
|
|
324
|
+
sessionFile: input.sessionFileForTask?.(task.agent, input.globalTaskIndex + taskIndex, effectiveModel)
|
|
314
325
|
?? input.sessionFileForIndex?.(input.globalTaskIndex + taskIndex),
|
|
315
|
-
thinkingOverride: input.thinkingOverrideForTask?.(task.agent, input.globalTaskIndex + taskIndex),
|
|
326
|
+
thinkingOverride: input.thinkingOverrideForTask?.(task.agent, input.globalTaskIndex + taskIndex, effectiveModel),
|
|
316
327
|
share: input.shareEnabled,
|
|
317
328
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
318
329
|
artifactConfig: input.artifactConfig,
|
|
@@ -331,7 +342,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
331
342
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
332
343
|
structuredOutput: structuredRuntime,
|
|
333
344
|
acceptance: task.acceptance,
|
|
334
|
-
acceptanceContext: { mode: "chain" },
|
|
345
|
+
acceptanceContext: { mode: "chain", dynamic: input.dynamic && task.acceptance === undefined },
|
|
335
346
|
timeoutMs: input.timeoutMs,
|
|
336
347
|
deadlineAt: input.deadlineAt,
|
|
337
348
|
turnBudget: input.turnBudget,
|
|
@@ -412,8 +423,8 @@ interface ChainExecutionParams {
|
|
|
412
423
|
shareEnabled: boolean;
|
|
413
424
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
414
425
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
415
|
-
sessionFileForTask?: (agentName: string, idx?: number) => string | undefined;
|
|
416
|
-
thinkingOverrideForTask?: (agentName: string, idx?: number) => AgentConfig["thinking"] | undefined;
|
|
426
|
+
sessionFileForTask?: (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
427
|
+
thinkingOverrideForTask?: (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
417
428
|
artifactsDir: string;
|
|
418
429
|
artifactConfig: ArtifactConfig;
|
|
419
430
|
includeProgress?: boolean;
|
|
@@ -627,7 +638,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
627
638
|
return {
|
|
628
639
|
...step,
|
|
629
640
|
task: result.templates[i]!,
|
|
630
|
-
...(override?.model ? { model: override.model } : {}),
|
|
641
|
+
...(override?.model !== undefined ? { model: override.model } : {}),
|
|
631
642
|
...(override?.output !== undefined ? { output: override.output } : {}),
|
|
632
643
|
...("outputMode" in step && step.outputMode !== undefined ? { outputMode: step.outputMode } : {}),
|
|
633
644
|
...(override?.reads !== undefined ? { reads: override.reads } : {}),
|
|
@@ -768,7 +779,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
768
779
|
const detached = detachedIndexInStep >= 0 ? parallelResults[detachedIndexInStep] : undefined;
|
|
769
780
|
if (detached) {
|
|
770
781
|
return {
|
|
771
|
-
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first.
|
|
782
|
+
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
772
783
|
details: buildChainExecutionDetails(makeDetailsInput({
|
|
773
784
|
currentStepIndex: stepIndex,
|
|
774
785
|
currentFlatIndex: globalTaskIndex - step.parallel.length + detachedIndexInStep,
|
|
@@ -869,7 +880,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
869
880
|
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
870
881
|
explicit: step.acceptance,
|
|
871
882
|
agentName: step.parallel.agent,
|
|
872
|
-
|
|
883
|
+
acceptanceRole: agents.find((agent) => agent.name === step.parallel.agent)?.acceptanceRole,
|
|
884
|
+
task: (step.parallel.task ?? originalTask ?? "").replace(/\{task\}/g, originalTask ?? ""),
|
|
873
885
|
mode: "chain",
|
|
874
886
|
dynamicGroup: true,
|
|
875
887
|
});
|
|
@@ -964,6 +976,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
964
976
|
toolBudget: params.toolBudget,
|
|
965
977
|
configToolBudget: params.configToolBudget,
|
|
966
978
|
globalSemaphore,
|
|
979
|
+
dynamic: true,
|
|
967
980
|
});
|
|
968
981
|
globalTaskIndex = dynamicStartIndex + reservedDynamicItems;
|
|
969
982
|
|
|
@@ -988,7 +1001,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
988
1001
|
const detached = detachedIndexInStep >= 0 ? parallelResults[detachedIndexInStep] : undefined;
|
|
989
1002
|
if (detached) {
|
|
990
1003
|
return {
|
|
991
|
-
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first.
|
|
1004
|
+
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
992
1005
|
details: buildChainExecutionDetails(makeDetailsInput({
|
|
993
1006
|
currentStepIndex: stepIndex,
|
|
994
1007
|
currentFlatIndex: dynamicStartIndex + detachedIndexInStep,
|
|
@@ -1034,7 +1047,10 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1034
1047
|
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
1035
1048
|
explicit: step.acceptance,
|
|
1036
1049
|
agentName: step.parallel.agent,
|
|
1037
|
-
|
|
1050
|
+
acceptanceRole: agents.find((agent) => agent.name === step.parallel.agent)?.acceptanceRole,
|
|
1051
|
+
task: materialized.parallel
|
|
1052
|
+
.map((task) => (task.task ?? originalTask ?? "").replace(/\{task\}/g, originalTask ?? ""))
|
|
1053
|
+
.join("\n"),
|
|
1038
1054
|
mode: "chain",
|
|
1039
1055
|
dynamicGroup: true,
|
|
1040
1056
|
});
|
|
@@ -1048,7 +1064,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1048
1064
|
cwd: cwd ?? ctx.cwd,
|
|
1049
1065
|
});
|
|
1050
1066
|
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
1051
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
1067
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance.explicit ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
1052
1068
|
if (groupAcceptanceFailure) {
|
|
1053
1069
|
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
1054
1070
|
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
|
|
@@ -1096,7 +1112,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1096
1112
|
|
|
1097
1113
|
const templateHasPrevious = stepTemplate.includes("{previous}");
|
|
1098
1114
|
const { prefix, suffix } = buildChainInstructions(
|
|
1099
|
-
behavior,
|
|
1115
|
+
{ ...behavior, output: false },
|
|
1100
1116
|
chainDir,
|
|
1101
1117
|
isFirstProgress,
|
|
1102
1118
|
templateHasPrevious ? undefined : prev,
|
|
@@ -1110,17 +1126,19 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1110
1126
|
stepTask = prefix + stepTask + suffix;
|
|
1111
1127
|
|
|
1112
1128
|
const explicitStepModel = tuiOverride?.model ?? seqStep.model;
|
|
1113
|
-
const effectiveModel =
|
|
1114
|
-
explicitStepModel
|
|
1129
|
+
const effectiveModel = resolveEffectiveSubagentModel(
|
|
1130
|
+
explicitStepModel,
|
|
1131
|
+
agentConfig.model,
|
|
1115
1132
|
ctx.model,
|
|
1116
1133
|
availableModels,
|
|
1117
1134
|
ctx.model?.provider,
|
|
1118
|
-
{ scope: modelScope
|
|
1135
|
+
{ scope: modelScope },
|
|
1119
1136
|
);
|
|
1120
1137
|
|
|
1121
1138
|
const outputPath = typeof behavior.output === "string"
|
|
1122
1139
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
1123
1140
|
: undefined;
|
|
1141
|
+
stepTask = injectSingleOutputInstruction(stepTask, outputPath, agentConfig);
|
|
1124
1142
|
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Chain step ${stepIndex + 1} (${seqStep.agent})`);
|
|
1125
1143
|
if (validationError) {
|
|
1126
1144
|
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
@@ -1172,9 +1190,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1172
1190
|
runId,
|
|
1173
1191
|
index: childIndex,
|
|
1174
1192
|
sessionDir: sessionDirForIndex(childIndex),
|
|
1175
|
-
sessionFile: sessionFileForTask?.(seqStep.agent, childIndex)
|
|
1193
|
+
sessionFile: sessionFileForTask?.(seqStep.agent, childIndex, effectiveModel)
|
|
1176
1194
|
?? sessionFileForIndex?.(childIndex),
|
|
1177
|
-
thinkingOverride: thinkingOverrideForTask?.(seqStep.agent, childIndex),
|
|
1195
|
+
thinkingOverride: thinkingOverrideForTask?.(seqStep.agent, childIndex, effectiveModel),
|
|
1178
1196
|
share: shareEnabled,
|
|
1179
1197
|
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1180
1198
|
artifactConfig,
|
|
@@ -1264,7 +1282,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1264
1282
|
}
|
|
1265
1283
|
if (r.detached) {
|
|
1266
1284
|
return {
|
|
1267
|
-
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${r.agent}). Reply to the supervisor request first.
|
|
1285
|
+
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${r.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
1268
1286
|
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: childIndex })),
|
|
1269
1287
|
};
|
|
1270
1288
|
}
|