pi-subagents 0.57.0 → 0.59.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 +104 -0
- package/docs/agents.md +18 -9
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +40 -1
- package/docs/models.md +27 -4
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +92 -6
- package/docs/workflows.md +115 -0
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +3 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
- package/src/agents/agent-management.ts +28 -17
- package/src/agents/agent-serializer.ts +9 -2
- package/src/agents/agents.ts +103 -27
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +19 -18
- package/src/api/agents.ts +10 -5
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +32 -10
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +66 -27
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/rpc.ts +35 -14
- package/src/extension/schemas.ts +37 -12
- package/src/extension/tool-description.ts +25 -6
- package/src/integrations/herdr-status.ts +5 -0
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +78 -46
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +29 -27
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +68 -7
- package/src/runs/background/chain-append.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +184 -9
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +29 -10
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +389 -350
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +134 -121
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +527 -257
- package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +5 -4
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +422 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-exclusions.ts +12 -0
- package/src/runs/shared/model-fallback.ts +25 -6
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +15 -1
- package/src/runs/shared/pi-args.ts +81 -8
- package/src/runs/shared/single-output.ts +44 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +11 -2
- package/src/shared/launch-contract.ts +5 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +240 -30
- package/src/shared/utils.ts +41 -84
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +330 -33
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +496 -35
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +79 -5
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState } from "../../shared/types.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1 } from "../../shared/types.ts";
|
|
6
6
|
import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
|
|
7
7
|
import { readStatus } from "../../shared/utils.ts";
|
|
8
8
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
@@ -17,16 +17,25 @@ import { readRecentTerminalRunIndex, TERMINAL_RUN_INDEX_DIR } from "./terminal-r
|
|
|
17
17
|
import { canScanAsyncRunPrefix } from "./run-id-query.ts";
|
|
18
18
|
import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
|
|
19
19
|
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
20
|
+
import { assertWorkflowGraphHostSteps, hostStepReportName, hostStepVerdictLabel, validHostStepNodes } from "../shared/host-step-status.ts";
|
|
21
|
+
import { projectAsyncWorkflowRows } from "../shared/async-status-projection.ts";
|
|
22
|
+
import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
|
|
23
|
+
import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
|
|
20
24
|
|
|
21
25
|
interface AsyncRunStepSummary {
|
|
22
26
|
index: number;
|
|
23
27
|
childId?: string;
|
|
24
28
|
agent: string;
|
|
29
|
+
/** Human-readable display name for the child session, when derived at launch. */
|
|
30
|
+
sessionName?: string;
|
|
25
31
|
context?: ContextMode;
|
|
26
32
|
label?: string;
|
|
27
33
|
description?: string;
|
|
28
34
|
phase?: string;
|
|
29
35
|
workflowKey?: string;
|
|
36
|
+
lane?: AsyncJobStep["lane"];
|
|
37
|
+
worktreePath?: string;
|
|
38
|
+
branch?: string;
|
|
30
39
|
runId?: string;
|
|
31
40
|
outputName?: string;
|
|
32
41
|
structured?: boolean;
|
|
@@ -48,6 +57,7 @@ interface AsyncRunStepSummary {
|
|
|
48
57
|
totalCost?: CostSummary;
|
|
49
58
|
skills?: string[];
|
|
50
59
|
model?: string;
|
|
60
|
+
contextLimit?: number;
|
|
51
61
|
thinking?: string;
|
|
52
62
|
attemptedModels?: string[];
|
|
53
63
|
sessionFile?: string;
|
|
@@ -57,6 +67,7 @@ interface AsyncRunStepSummary {
|
|
|
57
67
|
stopped?: boolean;
|
|
58
68
|
turnBudget?: TurnBudgetState;
|
|
59
69
|
turnBudgetExceeded?: boolean;
|
|
70
|
+
toolBudgetBlocked?: boolean;
|
|
60
71
|
wrapUpRequested?: boolean;
|
|
61
72
|
acceptance?: AsyncJobStep["acceptance"];
|
|
62
73
|
agentContract?: AsyncJobStep["agentContract"];
|
|
@@ -76,7 +87,7 @@ export interface AsyncRunSummary {
|
|
|
76
87
|
asyncDir: string;
|
|
77
88
|
toolCallId?: string;
|
|
78
89
|
sessionId?: string;
|
|
79
|
-
state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped" | "rejected";
|
|
90
|
+
state: "queued" | "running" | "complete" | "failed" | "partial" | "paused" | "stopped" | "rejected";
|
|
80
91
|
error?: string;
|
|
81
92
|
activityState?: ActivityState;
|
|
82
93
|
lastActivityAt?: number;
|
|
@@ -104,6 +115,7 @@ export interface AsyncRunSummary {
|
|
|
104
115
|
chainStepCount?: number;
|
|
105
116
|
pendingAppends?: number;
|
|
106
117
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
118
|
+
hostSteps?: HostStepNodeV1[];
|
|
107
119
|
steps: AsyncRunStepSummary[];
|
|
108
120
|
sessionDir?: string;
|
|
109
121
|
outputFile?: string;
|
|
@@ -121,8 +133,10 @@ export interface AsyncRunSummary {
|
|
|
121
133
|
capabilityAudit?: SubagentCapabilityAudit;
|
|
122
134
|
parentWorkflowRunId?: string;
|
|
123
135
|
workflowKey?: string;
|
|
136
|
+
lane?: AsyncStatus["lane"];
|
|
124
137
|
workflow?: Details["workflow"];
|
|
125
138
|
workflowChildren?: Details["workflowChildren"];
|
|
139
|
+
preflight?: WorkflowPreflightV1;
|
|
126
140
|
}
|
|
127
141
|
|
|
128
142
|
interface AsyncRunListOptions {
|
|
@@ -231,21 +245,32 @@ function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { acti
|
|
|
231
245
|
}
|
|
232
246
|
|
|
233
247
|
function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string }, nestedWarnings: string[] = [], nestedRoute?: NestedRoute): AsyncRunSummary {
|
|
248
|
+
validateAsyncStatusLaneMetadata(status, `Invalid async status '${path.join(asyncDir, "status.json")}'`);
|
|
234
249
|
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
235
250
|
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': workflowChildren.workflowRunId does not match.`);
|
|
251
|
+
assertWorkflowGraphHostSteps(status.workflowGraph, path.join(asyncDir, "status.json"), status.runId);
|
|
252
|
+
const hostSteps = validHostStepNodes(status.workflowGraph);
|
|
236
253
|
if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
|
|
237
254
|
throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
|
|
238
255
|
}
|
|
239
256
|
const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status);
|
|
240
257
|
const processTerminal = readProcessTerminal(asyncDir, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId })
|
|
241
258
|
?? sanitizeProcessTerminal(status.processTerminal, { runId: status.runId, runnerProcessInstanceId: status.processTerminal?.runnerProcessInstanceId }, path.join(asyncDir, "status.json"));
|
|
242
|
-
const
|
|
243
|
-
|
|
259
|
+
const nestedProjectionAllowed = nestedWarnings.length === 0;
|
|
260
|
+
// Degrade to the status-stored snapshot when the persisted budget is unavailable (e.g. removed
|
|
261
|
+
// by OS temp cleanup) instead of failing the whole run list; admission paths stay strict.
|
|
262
|
+
let runFanoutBudget: AsyncStatus["runFanoutBudget"] = status.runFanoutBudget;
|
|
263
|
+
try {
|
|
264
|
+
const runFanoutBudgetDescriptor = readRunFanoutBudgetDescriptor(asyncDir);
|
|
265
|
+
if (runFanoutBudgetDescriptor) runFanoutBudget = getRunFanoutBudgetSnapshot(runFanoutBudgetDescriptor);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
nestedWarnings.push(`Run fan-out status unavailable: ${getErrorMessage(error)}`);
|
|
268
|
+
}
|
|
244
269
|
const steps = status.steps ?? [];
|
|
245
270
|
const chainStepCount = status.chainStepCount ?? steps.length;
|
|
246
271
|
const parallelGroups = normalizeParallelGroups(status.parallelGroups, steps.length, chainStepCount);
|
|
247
272
|
let nestedChildren: NestedRunSummary[] = [];
|
|
248
|
-
if (
|
|
273
|
+
if (nestedProjectionAllowed && nestedRoute) {
|
|
249
274
|
try {
|
|
250
275
|
// The route is resolved by the caller via buildNestedRouteIndex, so this
|
|
251
276
|
// avoids a fresh scan of the nested-events directory per run.
|
|
@@ -261,11 +286,15 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
261
286
|
index,
|
|
262
287
|
childId: asyncStatusChildIdentity(step, index),
|
|
263
288
|
agent: step.agent,
|
|
289
|
+
...(step.sessionName ? { sessionName: step.sessionName } : {}),
|
|
264
290
|
...(step.context ? { context: step.context } : {}),
|
|
265
291
|
...(step.label ? { label: step.label } : {}),
|
|
266
292
|
...(step.description ? { description: step.description } : {}),
|
|
267
293
|
...(step.phase ? { phase: step.phase } : {}),
|
|
268
294
|
...(step.workflowKey ? { workflowKey: step.workflowKey } : {}),
|
|
295
|
+
...(step.lane ? { lane: step.lane } : {}),
|
|
296
|
+
...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
|
|
297
|
+
...(step.branch ? { branch: step.branch } : {}),
|
|
269
298
|
...(step.runId ? { runId: step.runId } : {}),
|
|
270
299
|
...(step.outputName ? { outputName: step.outputName } : {}),
|
|
271
300
|
...(step.structured ? { structured: step.structured } : {}),
|
|
@@ -287,6 +316,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
287
316
|
...(step.totalCost ? { totalCost: step.totalCost } : {}),
|
|
288
317
|
...(step.skills ? { skills: step.skills } : {}),
|
|
289
318
|
...(step.model ? { model: step.model } : {}),
|
|
319
|
+
...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
|
|
290
320
|
...(step.thinking ? { thinking: step.thinking } : {}),
|
|
291
321
|
...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
|
|
292
322
|
...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
@@ -299,6 +329,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
299
329
|
...(step.stopRequestedAt !== undefined ? { stopRequestedAt: step.stopRequestedAt } : {}),
|
|
300
330
|
...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
|
|
301
331
|
...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
|
|
332
|
+
...(step.toolBudgetBlocked !== undefined ? { toolBudgetBlocked: step.toolBudgetBlocked } : {}),
|
|
302
333
|
...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
|
|
303
334
|
...(step.acceptance ? { acceptance: step.acceptance } : {}),
|
|
304
335
|
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
@@ -308,6 +339,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
308
339
|
...(step.execution ? { execution: step.execution } : {}),
|
|
309
340
|
...(step.review ? { review: step.review } : {}),
|
|
310
341
|
...(step.effects ? { effects: step.effects } : {}),
|
|
342
|
+
...(step.watchdog ? { watchdog: step.watchdog } : {}),
|
|
311
343
|
...(step.processTerminal ? { processTerminal: sanitizeProcessTerminal(step.processTerminal, { runId: status.runId, runnerProcessInstanceId: step.processTerminal.runnerProcessInstanceId }, `${path.join(asyncDir, "status.json")} step ${index}`) } : {}),
|
|
312
344
|
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
313
345
|
...(step.capabilityAudit ? { capabilityAudit: step.capabilityAudit } : {}),
|
|
@@ -348,6 +380,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
348
380
|
...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}),
|
|
349
381
|
...(status.pendingAppends !== undefined ? { pendingAppends: status.pendingAppends } : {}),
|
|
350
382
|
...(parallelGroups.length ? { parallelGroups } : {}),
|
|
383
|
+
...(hostSteps.length ? { hostSteps } : {}),
|
|
351
384
|
steps: summarizedSteps,
|
|
352
385
|
...(nestedChildren.length ? { nestedChildren } : {}),
|
|
353
386
|
...(nestedWarnings.length ? { nestedWarnings } : {}),
|
|
@@ -360,8 +393,10 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
360
393
|
...(status.capabilityAudit ? { capabilityAudit: status.capabilityAudit } : {}),
|
|
361
394
|
...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
|
|
362
395
|
...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
|
|
396
|
+
...(status.lane ? { lane: status.lane } : {}),
|
|
363
397
|
...(status.workflow ? { workflow: status.workflow } : {}),
|
|
364
398
|
...(workflowChildren ? { workflowChildren } : {}),
|
|
399
|
+
...(status.preflight ? { preflight: status.preflight } : {}),
|
|
365
400
|
...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
|
|
366
401
|
...(status.outputFile ? { outputFile: status.outputFile } : {}),
|
|
367
402
|
...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
|
|
@@ -381,6 +416,7 @@ function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
|
381
416
|
case "running": return 0;
|
|
382
417
|
case "queued": return 1;
|
|
383
418
|
case "failed": return 2;
|
|
419
|
+
case "partial": return 2;
|
|
384
420
|
case "stopped": return 2;
|
|
385
421
|
case "paused": return 2;
|
|
386
422
|
case "complete": return 3;
|
|
@@ -509,7 +545,7 @@ function formatActivityFacts(input: { activityState?: ActivityState; lastActivit
|
|
|
509
545
|
}
|
|
510
546
|
|
|
511
547
|
function formatStepLine(step: AsyncRunStepSummary): string {
|
|
512
|
-
const display = step.label ? `${step.label} (${step.agent})` : step.agent;
|
|
548
|
+
const display = step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
|
|
513
549
|
const context = contextModeLabel(step.context);
|
|
514
550
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
515
551
|
const parts = [`${step.index + 1}. ${phase}${display}${context ? ` ${context}` : ""}`, step.status];
|
|
@@ -519,9 +555,26 @@ function formatStepLine(step: AsyncRunStepSummary): string {
|
|
|
519
555
|
if (modelThinking) parts.push(modelThinking);
|
|
520
556
|
if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
|
|
521
557
|
if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
|
|
558
|
+
if (step.lane) parts.push(`lane ${step.lane.key}`);
|
|
559
|
+
if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
|
|
522
560
|
return parts.join(" | ");
|
|
523
561
|
}
|
|
524
562
|
|
|
563
|
+
function formatHostStepLine(row: ReturnType<typeof projectAsyncWorkflowRows>[number]): string {
|
|
564
|
+
if (!row.kind) return "";
|
|
565
|
+
const state = hostStepVerdictLabel(row.state as HostStepState, row.verdict);
|
|
566
|
+
const details = [
|
|
567
|
+
row.provider ? `provider:${row.provider}` : undefined,
|
|
568
|
+
row.role ? `role:${row.role}` : undefined,
|
|
569
|
+
row.target,
|
|
570
|
+
row.detail,
|
|
571
|
+
row.reasonCode ? `reason:${row.reasonCode}` : undefined,
|
|
572
|
+
row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
|
|
573
|
+
row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined,
|
|
574
|
+
].filter((value): value is string => Boolean(value));
|
|
575
|
+
return `host ${row.kind}: ${row.name} | ${state}${details.length ? ` | ${details.join(" | ")}` : ""}`;
|
|
576
|
+
}
|
|
577
|
+
|
|
525
578
|
export function formatAsyncRunOutputPath(run: Pick<AsyncRunSummary, "asyncDir" | "outputFile">): string | undefined {
|
|
526
579
|
if (!run.outputFile) return undefined;
|
|
527
580
|
return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile);
|
|
@@ -554,7 +607,8 @@ function formatRunHeader(run: AsyncRunSummary): string {
|
|
|
554
607
|
const activity = formatActivityFacts(run);
|
|
555
608
|
const pending = run.pendingAppends ? ` | ${run.pendingAppends} pending append${run.pendingAppends === 1 ? "" : "s"}` : "";
|
|
556
609
|
const context = contextModeLabel(run.context);
|
|
557
|
-
|
|
610
|
+
const lane = run.lane ? ` | lane ${run.lane.key}` : "";
|
|
611
|
+
return `${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode}${context ? ` ${context}` : ""} | ${stepLabel}${pending}${lane} | ${cwd}`;
|
|
558
612
|
}
|
|
559
613
|
|
|
560
614
|
export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active async runs"): string {
|
|
@@ -563,10 +617,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
|
|
|
563
617
|
const lines = [`${heading}: ${runs.length}`, ""];
|
|
564
618
|
for (const run of runs) {
|
|
565
619
|
lines.push(`- ${formatRunHeader(run)}`);
|
|
620
|
+
if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
|
|
621
|
+
const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
|
|
622
|
+
if (preflightWarning) lines.push(preflightWarning);
|
|
566
623
|
for (const step of run.steps) {
|
|
567
624
|
lines.push(` ${formatStepLine(step)}`);
|
|
568
625
|
lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", maxLines: 12 }));
|
|
569
626
|
}
|
|
627
|
+
for (const row of projectAsyncWorkflowRows([], run.hostSteps)) {
|
|
628
|
+
const line = formatHostStepLine(row);
|
|
629
|
+
if (line) lines.push(` ${line}`);
|
|
630
|
+
}
|
|
570
631
|
const attached = new Set(run.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
571
632
|
const unattached = run.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
572
633
|
lines.push(...formatNestedRunStatusLines(unattached, { indent: " ", maxLines: 12 }));
|
|
@@ -3,8 +3,9 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
5
5
|
import { appendJsonl } from "../../shared/artifacts.ts";
|
|
6
|
-
import type {
|
|
6
|
+
import type { AsyncStatus, WorkflowGraphNode, WorkflowGraphSnapshot } from "../../shared/types.ts";
|
|
7
7
|
import { PROMPT_REDACTED, readStatus } from "../../shared/utils.ts";
|
|
8
|
+
import { deriveChildSessionName } from "../../shared/child-session-name.ts";
|
|
8
9
|
import type { DynamicRunnerGroup, ParallelStepGroup, RunnerStep, RunnerSubagentStep } from "../shared/parallel-utils.ts";
|
|
9
10
|
import { isDynamicRunnerGroup, isParallelGroup } from "../shared/parallel-utils.ts";
|
|
10
11
|
|
|
@@ -160,8 +161,10 @@ export function statusStepDescription(task: string | undefined): string | undefi
|
|
|
160
161
|
|
|
161
162
|
function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
162
163
|
const description = statusStepDescription(task.task);
|
|
164
|
+
const sessionName = task.sessionName ?? deriveChildSessionName({ agent: task.agent, task: task.task, label: task.label });
|
|
163
165
|
return {
|
|
164
166
|
agent: task.agent,
|
|
167
|
+
...(sessionName ? { sessionName } : {}),
|
|
165
168
|
...(description ? { description } : {}),
|
|
166
169
|
...(task.context ? { context: task.context } : {}),
|
|
167
170
|
phase: task.phase,
|
|
@@ -172,6 +175,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
|
172
175
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
173
176
|
skills: task.skills,
|
|
174
177
|
model: task.model,
|
|
178
|
+
...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
|
|
175
179
|
thinking: task.thinking,
|
|
176
180
|
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
177
181
|
recentTools: [],
|
|
@@ -179,7 +183,7 @@ function statusStepForTask(task: RunnerSubagentStep): StatusStep {
|
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
function statusStepsForRunnerStep(step: RunnerStep
|
|
186
|
+
function statusStepsForRunnerStep(step: RunnerStep): StatusStep[] {
|
|
183
187
|
if (isParallelGroup(step)) return step.parallel.map(statusStepForTask);
|
|
184
188
|
if (isDynamicRunnerGroup(step)) {
|
|
185
189
|
return [{
|
|
@@ -189,6 +193,7 @@ function statusStepsForRunnerStep(step: RunnerStep, stepIndex: number): StatusSt
|
|
|
189
193
|
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
190
194
|
outputName: step.collect.as,
|
|
191
195
|
structured: Boolean(step.collect.outputSchema),
|
|
196
|
+
...(step.parallel.contextLimit !== undefined ? { contextLimit: step.parallel.contextLimit } : {}),
|
|
192
197
|
status: "pending",
|
|
193
198
|
recentTools: [],
|
|
194
199
|
recentOutput: [],
|
|
@@ -295,7 +300,7 @@ export function appendRunnerStepsToStatus(input: {
|
|
|
295
300
|
for (const step of input.steps) {
|
|
296
301
|
const stepIndex = input.status.chainStepCount ?? input.status.steps?.length ?? 0;
|
|
297
302
|
const flatIndex = input.status.steps?.length ?? 0;
|
|
298
|
-
const statusSteps = statusStepsForRunnerStep(step
|
|
303
|
+
const statusSteps = statusStepsForRunnerStep(step);
|
|
299
304
|
input.status.steps ??= [];
|
|
300
305
|
input.status.steps.push(...statusSteps);
|
|
301
306
|
if (isParallelGroup(step)) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { resultFilePath, resultPayloadPathForSessionRun } from "./result-files.ts";
|
|
4
|
+
import type { AcceptanceLedger, ArtifactPaths, AsyncStatus, CostSummary, EffectsProjection, ExecutionProjection, ModelAttempt, Usage } from "../../shared/types.ts";
|
|
4
5
|
import { readStatus } from "../../shared/utils.ts";
|
|
5
6
|
|
|
6
7
|
export interface ImportedAsyncRoot {
|
|
@@ -12,6 +13,8 @@ export interface ImportedAsyncRoot {
|
|
|
12
13
|
|
|
13
14
|
export interface ImportedAsyncRootResult {
|
|
14
15
|
agent: string;
|
|
16
|
+
/** Human-readable display name for the child session, when derived at launch. */
|
|
17
|
+
sessionName?: string;
|
|
15
18
|
output: string;
|
|
16
19
|
success: boolean;
|
|
17
20
|
exitCode: number;
|
|
@@ -23,6 +26,7 @@ export interface ImportedAsyncRootResult {
|
|
|
23
26
|
modelAttempts?: ModelAttempt[];
|
|
24
27
|
contextOverflow?: boolean;
|
|
25
28
|
totalCost?: CostSummary;
|
|
29
|
+
usage?: Usage;
|
|
26
30
|
structuredOutput?: unknown;
|
|
27
31
|
structuredOutputPath?: string;
|
|
28
32
|
structuredOutputSchemaPath?: string;
|
|
@@ -33,6 +37,8 @@ export interface ImportedAsyncRootResult {
|
|
|
33
37
|
transcriptError?: string;
|
|
34
38
|
timedOut?: boolean;
|
|
35
39
|
stopped?: boolean;
|
|
40
|
+
execution?: ExecutionProjection;
|
|
41
|
+
effects?: EffectsProjection;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
interface AsyncResultFile {
|
|
@@ -44,11 +50,14 @@ interface AsyncResultFile {
|
|
|
44
50
|
stopped?: boolean;
|
|
45
51
|
results?: Array<{
|
|
46
52
|
agent?: string;
|
|
53
|
+
sessionName?: string;
|
|
47
54
|
output?: string;
|
|
48
55
|
error?: string;
|
|
49
56
|
success?: boolean;
|
|
50
57
|
timedOut?: boolean;
|
|
51
58
|
stopped?: boolean;
|
|
59
|
+
execution?: ExecutionProjection;
|
|
60
|
+
effects?: EffectsProjection;
|
|
52
61
|
sessionFile?: string;
|
|
53
62
|
intercomTarget?: string;
|
|
54
63
|
model?: string;
|
|
@@ -56,6 +65,7 @@ interface AsyncResultFile {
|
|
|
56
65
|
modelAttempts?: ModelAttempt[];
|
|
57
66
|
contextOverflow?: boolean;
|
|
58
67
|
totalCost?: CostSummary;
|
|
68
|
+
usage?: Usage;
|
|
59
69
|
structuredOutput?: unknown;
|
|
60
70
|
structuredOutputPath?: string;
|
|
61
71
|
structuredOutputSchemaPath?: string;
|
|
@@ -67,8 +77,25 @@ interface AsyncResultFile {
|
|
|
67
77
|
}>;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
const TERMINAL_STATES = new Set(["complete", "failed", "paused", "stopped"]);
|
|
71
|
-
const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "paused", "stopped"]);
|
|
80
|
+
const TERMINAL_STATES = new Set(["complete", "failed", "partial", "paused", "stopped"]);
|
|
81
|
+
const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "partial", "paused", "stopped"]);
|
|
82
|
+
|
|
83
|
+
function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage | undefined {
|
|
84
|
+
if (!attempts?.length) return undefined;
|
|
85
|
+
const usage: Usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
86
|
+
for (const attempt of attempts) {
|
|
87
|
+
if (!attempt.usage) continue;
|
|
88
|
+
usage.input += attempt.usage.input;
|
|
89
|
+
usage.output += attempt.usage.output;
|
|
90
|
+
usage.cacheRead += attempt.usage.cacheRead;
|
|
91
|
+
usage.cacheWrite += attempt.usage.cacheWrite;
|
|
92
|
+
usage.cost += attempt.usage.cost;
|
|
93
|
+
usage.turns += attempt.usage.turns;
|
|
94
|
+
}
|
|
95
|
+
return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0
|
|
96
|
+
? usage
|
|
97
|
+
: undefined;
|
|
98
|
+
}
|
|
72
99
|
|
|
73
100
|
function readResultFile(resultPath: string): AsyncResultFile | undefined {
|
|
74
101
|
try {
|
|
@@ -81,6 +108,13 @@ function readResultFile(resultPath: string): AsyncResultFile | undefined {
|
|
|
81
108
|
}
|
|
82
109
|
}
|
|
83
110
|
|
|
111
|
+
function readImportedResultFile(root: ImportedAsyncRoot, status: AsyncStatus | null): AsyncResultFile | undefined {
|
|
112
|
+
const direct = readResultFile(root.resultPath);
|
|
113
|
+
if (direct || !status?.sessionId) return direct;
|
|
114
|
+
const indexedPath = resultPayloadPathForSessionRun(path.dirname(root.resultPath), status.sessionId, root.runId);
|
|
115
|
+
return indexedPath ? readResultFile(indexedPath) : undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
84
118
|
function selectedStatusStep(status: AsyncStatus | null, index: number): NonNullable<AsyncStatus["steps"]>[number] | undefined {
|
|
85
119
|
return status?.steps?.[index];
|
|
86
120
|
}
|
|
@@ -92,12 +126,12 @@ function isTerminalStatus(status: AsyncStatus | null, index: number): boolean {
|
|
|
92
126
|
return TERMINAL_STATES.has(status.state);
|
|
93
127
|
}
|
|
94
128
|
|
|
95
|
-
function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "paused" | "stopped" | undefined {
|
|
129
|
+
function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "partial" | "paused" | "stopped" | undefined {
|
|
96
130
|
if (!result) return undefined;
|
|
97
131
|
if (child?.stopped === true) return "stopped";
|
|
98
132
|
if (child?.success === true) return "complete";
|
|
99
|
-
if (child?.success === false) return result.state === "stopped" ? "stopped" : result.state === "paused" ? "paused" : "failed";
|
|
100
|
-
if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "stopped") return result.state;
|
|
133
|
+
if (child?.success === false) return result.state === "stopped" ? "stopped" : result.state === "paused" ? "paused" : result.state === "partial" ? "partial" : "failed";
|
|
134
|
+
if (result.state === "complete" || result.state === "failed" || result.state === "partial" || result.state === "paused" || result.state === "stopped") return result.state;
|
|
101
135
|
if (result.success === true) return "complete";
|
|
102
136
|
if (result.success === false) return "failed";
|
|
103
137
|
return undefined;
|
|
@@ -108,6 +142,11 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
|
|
|
108
142
|
const timedOut = step?.timedOut === true || status.timedOut === true;
|
|
109
143
|
const stopped = step?.stopped === true || status.stopped === true || status.state === "stopped";
|
|
110
144
|
const message = step?.error ?? status.error ?? (stopped ? "Subagent stopped by user." : `Attached async root ${root.runId} ended without a result file at ${root.resultPath}.`);
|
|
145
|
+
const partialStatus = status.state === "partial" || step?.status === "partial";
|
|
146
|
+
const execution = step?.execution ?? (partialStatus
|
|
147
|
+
? { status: "partial" as const, success: false, exitCode: 1, error: message }
|
|
148
|
+
: undefined);
|
|
149
|
+
const usage = usageFromAttempts(step?.modelAttempts);
|
|
111
150
|
return {
|
|
112
151
|
agent,
|
|
113
152
|
output: message,
|
|
@@ -116,22 +155,27 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
|
|
|
116
155
|
error: message,
|
|
117
156
|
...(timedOut ? { timedOut: true } : {}),
|
|
118
157
|
...(stopped ? { stopped: true } : {}),
|
|
158
|
+
...(step?.sessionName ? { sessionName: step.sessionName } : {}),
|
|
119
159
|
...(step?.sessionFile ?? status.sessionFile ? { sessionFile: step?.sessionFile ?? status.sessionFile } : {}),
|
|
120
160
|
...(step?.model ? { model: step.model } : {}),
|
|
121
161
|
...(step?.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
122
162
|
...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
|
|
123
163
|
...(step?.contextOverflow ? { contextOverflow: true } : {}),
|
|
124
164
|
...(step?.totalCost ? { totalCost: step.totalCost } : {}),
|
|
165
|
+
...(usage ? { usage } : {}),
|
|
125
166
|
...(step?.structuredOutput !== undefined ? { structuredOutput: step.structuredOutput } : {}),
|
|
126
167
|
...(step?.structuredOutputPath ? { structuredOutputPath: step.structuredOutputPath } : {}),
|
|
127
168
|
...(step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: step.structuredOutputSchemaPath } : {}),
|
|
128
169
|
...(step?.acceptance ? { acceptance: step.acceptance } : {}),
|
|
170
|
+
...(execution ? { execution } : {}),
|
|
171
|
+
...(step?.effects ? { effects: step.effects } : {}),
|
|
129
172
|
...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
|
|
130
173
|
};
|
|
131
174
|
}
|
|
132
175
|
|
|
133
176
|
function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null, message: string): ImportedAsyncRootResult {
|
|
134
177
|
const step = selectedStatusStep(status, root.index);
|
|
178
|
+
const usage = usageFromAttempts(step?.modelAttempts);
|
|
135
179
|
return {
|
|
136
180
|
agent: step?.agent ?? status?.steps?.[root.index]?.agent ?? "subagent",
|
|
137
181
|
output: message,
|
|
@@ -139,12 +183,14 @@ function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null,
|
|
|
139
183
|
exitCode: 1,
|
|
140
184
|
error: message,
|
|
141
185
|
timedOut: true,
|
|
186
|
+
...(step?.sessionName ? { sessionName: step.sessionName } : {}),
|
|
142
187
|
...(step?.sessionFile ?? status?.sessionFile ? { sessionFile: step?.sessionFile ?? status?.sessionFile } : {}),
|
|
143
188
|
...(step?.model ? { model: step.model } : {}),
|
|
144
189
|
...(step?.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
145
190
|
...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
|
|
146
191
|
...(step?.contextOverflow ? { contextOverflow: true } : {}),
|
|
147
192
|
...(step?.totalCost ? { totalCost: step.totalCost } : {}),
|
|
193
|
+
...(usage ? { usage } : {}),
|
|
148
194
|
...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
|
|
149
195
|
};
|
|
150
196
|
}
|
|
@@ -159,6 +205,8 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
159
205
|
const stopped = child?.stopped === true || step?.stopped === true || result.stopped === true || status?.stopped === true || state === "stopped";
|
|
160
206
|
const success = state === "complete" && !timedOut && !stopped;
|
|
161
207
|
const error = child?.error ?? (success ? undefined : stopped ? "Subagent stopped by user." : result.error ?? result.summary ?? status?.error ?? `Attached async root ${root.runId} did not complete successfully.`);
|
|
208
|
+
const execution = state ? { status: state === "complete" ? "completed" as const : state, success, exitCode: success ? 0 : 1, ...(error ? { error } : {}) } : undefined;
|
|
209
|
+
const usage = child?.usage ?? usageFromAttempts(step?.modelAttempts);
|
|
162
210
|
return {
|
|
163
211
|
agent,
|
|
164
212
|
output: success ? output : (output || error || ""),
|
|
@@ -167,6 +215,7 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
167
215
|
...(error ? { error } : {}),
|
|
168
216
|
...(timedOut ? { timedOut: true } : {}),
|
|
169
217
|
...(stopped ? { stopped: true } : {}),
|
|
218
|
+
...(child?.sessionName ?? step?.sessionName ? { sessionName: child?.sessionName ?? step?.sessionName } : {}),
|
|
170
219
|
...(child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile ? { sessionFile: child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile } : {}),
|
|
171
220
|
...(child?.intercomTarget ? { intercomTarget: child.intercomTarget } : {}),
|
|
172
221
|
...(child?.model ?? step?.model ? { model: child?.model ?? step?.model } : {}),
|
|
@@ -174,6 +223,7 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
174
223
|
...(child?.modelAttempts ?? step?.modelAttempts ? { modelAttempts: child?.modelAttempts ?? step?.modelAttempts } : {}),
|
|
175
224
|
...(child?.contextOverflow || step?.contextOverflow ? { contextOverflow: true } : {}),
|
|
176
225
|
...(child?.totalCost ?? step?.totalCost ? { totalCost: child?.totalCost ?? step?.totalCost } : {}),
|
|
226
|
+
...(usage ? { usage } : {}),
|
|
177
227
|
...(child?.structuredOutput !== undefined ? { structuredOutput: child.structuredOutput } : step?.structuredOutput !== undefined ? { structuredOutput: step.structuredOutput } : {}),
|
|
178
228
|
...(child?.structuredOutputPath ?? step?.structuredOutputPath ? { structuredOutputPath: child?.structuredOutputPath ?? step?.structuredOutputPath } : {}),
|
|
179
229
|
...(child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath } : {}),
|
|
@@ -182,6 +232,8 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
182
232
|
...(child?.outputSaveError ? { outputSaveError: child.outputSaveError } : {}),
|
|
183
233
|
...(child?.transcriptPath ?? step?.transcriptPath ? { transcriptPath: child?.transcriptPath ?? step?.transcriptPath } : {}),
|
|
184
234
|
...(child?.transcriptError ? { transcriptError: child.transcriptError } : {}),
|
|
235
|
+
...(execution ? { execution } : {}),
|
|
236
|
+
...(child?.effects ?? step?.effects ? { effects: child?.effects ?? step?.effects } : {}),
|
|
185
237
|
};
|
|
186
238
|
}
|
|
187
239
|
|
|
@@ -196,7 +248,7 @@ export async function waitForImportedAsyncRoot(
|
|
|
196
248
|
for (;;) {
|
|
197
249
|
const status = readStatus(root.asyncDir);
|
|
198
250
|
if (options.shouldAbort?.()) return outputFromTimeout(root, status, options.timeoutMessage ?? "Subagent timed out.");
|
|
199
|
-
const result =
|
|
251
|
+
const result = readImportedResultFile(root, status);
|
|
200
252
|
if (result) return buildImportedResult(root, status, result);
|
|
201
253
|
if (isTerminalStatus(status, root.index)) {
|
|
202
254
|
terminalSince ??= now();
|
|
@@ -72,6 +72,14 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
72
72
|
return result;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function fleetChildDisplayName(child: { agent?: string; sessionName?: string }, fallback = "subagent"): string {
|
|
76
|
+
return child.sessionName?.trim() || child.agent || fallback;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fleetStepDisplayName(step: Pick<AsyncJobStep, "agent" | "sessionName" | "label">): string {
|
|
80
|
+
return step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
function resolveMaybeRelative(asyncDir: string, filePath: string | undefined): string | undefined {
|
|
76
84
|
if (!filePath) return undefined;
|
|
77
85
|
return path.resolve(asyncDir, filePath);
|
|
@@ -283,7 +291,8 @@ function formatActivityFacts(input: {
|
|
|
283
291
|
}
|
|
284
292
|
|
|
285
293
|
function foregroundModeName(control: ForegroundControl): string {
|
|
286
|
-
|
|
294
|
+
const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
|
|
295
|
+
if (control.mode === "single" && currentDisplayName) return currentDisplayName;
|
|
287
296
|
return control.mode;
|
|
288
297
|
}
|
|
289
298
|
|
|
@@ -302,7 +311,8 @@ function formatForegroundFleetLines(controls: ForegroundControl[]): string[] {
|
|
|
302
311
|
toolCount: control.toolCount,
|
|
303
312
|
...(control.tokens !== undefined ? { tokens: { total: control.tokens } } : {}),
|
|
304
313
|
});
|
|
305
|
-
const
|
|
314
|
+
const currentDisplayName = control.sessionName?.trim() || control.currentAgent;
|
|
315
|
+
const current = currentDisplayName ? ` | ${currentDisplayName}${control.currentIndex !== undefined ? ` #${control.currentIndex}` : ""}` : "";
|
|
306
316
|
lines.push(`- ${control.runId} | running | ${foregroundModeName(control)}${current}${activity ? ` | ${activity}` : ""}`);
|
|
307
317
|
lines.push(` status: subagent({ action: "status", id: "${control.runId}" })`);
|
|
308
318
|
lines.push(" transcript: live in the expanded foreground result; persisted session transcript appears after completion when sessions are enabled.");
|
|
@@ -317,7 +327,7 @@ function formatDetachedForegroundFleetLines(runs: ForegroundRun[]): string[] {
|
|
|
317
327
|
const ordered = [...runs].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
318
328
|
for (const run of ordered) {
|
|
319
329
|
const detachedChildren = run.children.filter((child) => child.status === "detached");
|
|
320
|
-
const childSummary = detachedChildren.map((child) => `${child
|
|
330
|
+
const childSummary = detachedChildren.map((child) => `${fleetChildDisplayName(child)} #${child.index}`).join(", ");
|
|
321
331
|
lines.push(`- ${run.runId} | detached | ${run.mode}${childSummary ? ` | ${childSummary}` : ""}`);
|
|
322
332
|
lines.push(` status: subagent({ action: "status", id: "${run.runId}" })`);
|
|
323
333
|
lines.push(` recovery: reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
|
|
@@ -338,7 +348,7 @@ function formatAsyncFleetLines(runs: AsyncRunSummary[]): string[] {
|
|
|
338
348
|
lines.push(` status: subagent({ action: "status", id: "${run.id}" })`);
|
|
339
349
|
lines.push(` transcript: subagent({ action: "status", id: "${run.id}", view: "transcript" })`);
|
|
340
350
|
for (const step of run.steps) {
|
|
341
|
-
const display =
|
|
351
|
+
const display = fleetStepDisplayName(step);
|
|
342
352
|
const stepContext = contextModeLabel(step.context);
|
|
343
353
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
344
354
|
const stepActivity = formatActivityFacts(step);
|
|
@@ -435,7 +445,7 @@ function selectTranscriptStep(status: AsyncStatus, options: TranscriptOptions):
|
|
|
435
445
|
}
|
|
436
446
|
const step = selectedIndex !== undefined ? steps[selectedIndex] : undefined;
|
|
437
447
|
const hint = options.index === undefined && steps.length > 1
|
|
438
|
-
? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${candidate
|
|
448
|
+
? `Tip: pass index to inspect a specific child transcript (${steps.map((candidate, index) => `${index}=${fleetChildDisplayName(candidate)}`).join(", ")}).`
|
|
439
449
|
: undefined;
|
|
440
450
|
return { index: selectedIndex, step, hint };
|
|
441
451
|
}
|
|
@@ -445,7 +455,7 @@ function stepStateLine(mode: SubagentRunMode, index: number | undefined, step: A
|
|
|
445
455
|
const modelThinking = formatModelThinking(step.model, step.thinking);
|
|
446
456
|
const context = contextModeLabel(step.context);
|
|
447
457
|
const parts = [
|
|
448
|
-
`${mode === "parallel" ? "Agent" : "Step"}: ${index} (${step
|
|
458
|
+
`${mode === "parallel" ? "Agent" : "Step"}: ${index} (${fleetChildDisplayName(step)})${context ? ` ${context}` : ""}`,
|
|
449
459
|
step.status,
|
|
450
460
|
formatActivityFacts(step),
|
|
451
461
|
modelThinking,
|
|
@@ -553,7 +563,7 @@ export function formatNestedRunTranscript(run: NestedRunSummary, options: Transc
|
|
|
553
563
|
`Nested run: ${run.id}`,
|
|
554
564
|
`State: ${run.state}`,
|
|
555
565
|
run.mode ? `Mode: ${run.mode}` : undefined,
|
|
556
|
-
run.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(", ")}` : undefined,
|
|
566
|
+
run.sessionName?.trim() ? `Agent: ${run.sessionName.trim()}` : run.agent ? `Agent: ${run.agent}` : run.agents?.length ? `Agents: ${run.agents.join(", ")}` : undefined,
|
|
557
567
|
].filter((line): line is string => Boolean(line));
|
|
558
568
|
appendKnownArtifacts(lines, { outputPaths: [], sessionFile: run.sessionFile });
|
|
559
569
|
if (!run.sessionFile) {
|
|
@@ -579,14 +589,14 @@ export function formatAsyncResultTranscript(data: {
|
|
|
579
589
|
sessionFile?: string;
|
|
580
590
|
agent?: string;
|
|
581
591
|
exitCode?: number | null;
|
|
582
|
-
results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }>;
|
|
592
|
+
results?: Array<{ agent?: string; sessionName?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }>;
|
|
583
593
|
}, resultPath: string, options: TranscriptOptions = {}): string {
|
|
584
594
|
const lineLimit = transcriptLineLimit(options.lines);
|
|
585
595
|
const runId = data.runId ?? data.id ?? path.basename(resultPath, ".json");
|
|
586
596
|
const children = Array.isArray(data.results)
|
|
587
597
|
? data.results
|
|
588
598
|
: data.agent
|
|
589
|
-
? [{ agent: data.agent, output: data.output, summary: data.summary, sessionFile: data.sessionFile, state: data.state, success: data.success, exitCode: data.exitCode }]
|
|
599
|
+
? [{ agent: data.agent, sessionName: undefined, output: data.output, summary: data.summary, sessionFile: data.sessionFile, state: data.state, success: data.success, exitCode: data.exitCode }]
|
|
590
600
|
: [];
|
|
591
601
|
let index = options.index;
|
|
592
602
|
if (index !== undefined && !Number.isInteger(index)) throw new Error("Transcript index must be an integer.");
|
|
@@ -601,8 +611,8 @@ export function formatAsyncResultTranscript(data: {
|
|
|
601
611
|
const lines = [
|
|
602
612
|
`Run: ${runId}`,
|
|
603
613
|
`State: ${data.state ?? (data.success ? "complete" : "failed")}`,
|
|
604
|
-
index !== undefined && child ? `Child: ${index} (${child
|
|
605
|
-
index === undefined && children.length > 1 ? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${candidate
|
|
614
|
+
index !== undefined && child ? `Child: ${index} (${fleetChildDisplayName(child)})` : undefined,
|
|
615
|
+
index === undefined && children.length > 1 ? `Tip: pass index to inspect a specific child transcript (${children.map((candidate, childIndex) => `${childIndex}=${fleetChildDisplayName(candidate)}`).join(", ")}).` : undefined,
|
|
606
616
|
].filter((line): line is string => Boolean(line));
|
|
607
617
|
appendKnownArtifacts(lines, { outputPaths: [], sessionFile, resultPath });
|
|
608
618
|
appendTranscriptBody(lines, "Result transcript tail", transcriptLines.filter((line) => line.trim()), output.split(/\r?\n/).length > lineLimit);
|