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,6 +2,7 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
2
2
|
import type { Message } from "@earendil-works/pi-ai";
|
|
3
3
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
4
4
|
import { previewSimpleWorkflowRun } from "../workflows/scripted-workflow.ts";
|
|
5
|
+
import { deriveChildSessionName } from "../shared/child-session-name.ts";
|
|
5
6
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
6
7
|
import { type Details, type SingleResult, type Usage, SLASH_RESULT_TYPE } from "../shared/types.ts";
|
|
7
8
|
|
|
@@ -54,9 +55,11 @@ function createPlaceholderResult(
|
|
|
54
55
|
status: "pending" | "running",
|
|
55
56
|
index: number,
|
|
56
57
|
): SingleResult {
|
|
58
|
+
const sessionName = deriveChildSessionName({ agent, task });
|
|
57
59
|
return {
|
|
58
60
|
agent,
|
|
59
61
|
task,
|
|
62
|
+
...(sessionName ? { sessionName } : {}),
|
|
60
63
|
index,
|
|
61
64
|
exitCode: 0,
|
|
62
65
|
messages: EMPTY_MESSAGES,
|
|
@@ -64,6 +67,7 @@ function createPlaceholderResult(
|
|
|
64
67
|
progress: {
|
|
65
68
|
index,
|
|
66
69
|
agent,
|
|
70
|
+
...(sessionName ? { sessionName } : {}),
|
|
67
71
|
status,
|
|
68
72
|
task,
|
|
69
73
|
recentTools: [],
|
|
@@ -84,17 +88,21 @@ function buildParallelInitialResult(params: SubagentParamsLike): AgentToolResult
|
|
|
84
88
|
...(params.async ? { background: true } : {}),
|
|
85
89
|
...(params.context === "fresh" || params.context === "fork" ? { context: params.context } : {}),
|
|
86
90
|
results: tasks.map((task, index) => createPlaceholderResult(task.agent, task.task, "running", index)),
|
|
87
|
-
progress: tasks.map((task, index) =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
progress: tasks.map((task, index) => {
|
|
92
|
+
const sessionName = deriveChildSessionName({ agent: task.agent, task: task.task });
|
|
93
|
+
return {
|
|
94
|
+
index,
|
|
95
|
+
agent: task.agent,
|
|
96
|
+
...(sessionName ? { sessionName } : {}),
|
|
97
|
+
status: "running" as const,
|
|
98
|
+
task: task.task,
|
|
99
|
+
recentTools: [],
|
|
100
|
+
recentOutput: [],
|
|
101
|
+
toolCount: 0,
|
|
102
|
+
tokens: 0,
|
|
103
|
+
durationMs: 0,
|
|
104
|
+
};
|
|
105
|
+
}),
|
|
98
106
|
},
|
|
99
107
|
};
|
|
100
108
|
}
|
|
@@ -143,6 +151,7 @@ function buildChainInitialResult(params: SubagentParamsLike): AgentToolResult<De
|
|
|
143
151
|
progress: results.map((result, index) => ({
|
|
144
152
|
index,
|
|
145
153
|
agent: result.agent,
|
|
154
|
+
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
146
155
|
status: index === 0 ? "running" as const : "pending" as const,
|
|
147
156
|
task: result.task,
|
|
148
157
|
recentTools: [],
|
|
@@ -162,6 +171,7 @@ function buildSingleInitialResult(params: SubagentParamsLike): AgentToolResult<D
|
|
|
162
171
|
const preview = previewSimpleWorkflowRun(params.workflowScript) ?? {};
|
|
163
172
|
const agent = params.agent ?? preview.agent ?? "subagent";
|
|
164
173
|
const task = params.task ?? preview.task ?? "";
|
|
174
|
+
const sessionName = deriveChildSessionName({ agent, task });
|
|
165
175
|
return {
|
|
166
176
|
content: [{ type: "text", text: task }],
|
|
167
177
|
details: {
|
|
@@ -172,6 +182,7 @@ function buildSingleInitialResult(params: SubagentParamsLike): AgentToolResult<D
|
|
|
172
182
|
progress: [{
|
|
173
183
|
index: 0,
|
|
174
184
|
agent,
|
|
185
|
+
...(sessionName ? { sessionName } : {}),
|
|
175
186
|
status: "running",
|
|
176
187
|
task,
|
|
177
188
|
recentTools: [],
|
|
@@ -89,6 +89,7 @@ function buildBuiltinBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
89
89
|
...(agent.thinking !== undefined ? { thinking: agent.thinking } : {}),
|
|
90
90
|
systemPromptMode: agent.systemPromptMode,
|
|
91
91
|
inheritProjectContext: agent.inheritProjectContext,
|
|
92
|
+
inheritGlobalContext: agent.inheritGlobalContext,
|
|
92
93
|
inheritSkills: agent.inheritSkills,
|
|
93
94
|
...(agent.defaultContext !== undefined ? { defaultContext: agent.defaultContext } : {}),
|
|
94
95
|
...(agent.acceptanceRole !== undefined ? { acceptanceRole: agent.acceptanceRole } : {}),
|
|
@@ -98,6 +99,7 @@ function buildBuiltinBase(agent: AgentConfig): BuiltinAgentOverrideBase {
|
|
|
98
99
|
...(agent.tools !== undefined ? { tools: [...agent.tools] } : {}),
|
|
99
100
|
...(agent.mcpDirectTools !== undefined ? { mcpDirectTools: [...agent.mcpDirectTools] } : {}),
|
|
100
101
|
...(agent.subagentOnlyExtensions !== undefined ? { subagentOnlyExtensions: [...agent.subagentOnlyExtensions] } : {}),
|
|
102
|
+
...(agent.mutationTools !== undefined ? { mutationTools: [...agent.mutationTools] } : {}),
|
|
101
103
|
...(agent.completionGuard !== undefined ? { completionGuard: agent.completionGuard } : {}),
|
|
102
104
|
...(agent.toolBudget !== undefined ? { toolBudget: agent.toolBudget } : {}),
|
|
103
105
|
};
|
|
@@ -189,6 +191,7 @@ function metadataFor(agent: AgentConfig): string {
|
|
|
189
191
|
if (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(", ")}`);
|
|
190
192
|
lines.push(`System prompt mode: ${agent.systemPromptMode}`);
|
|
191
193
|
lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`);
|
|
194
|
+
lines.push(`Inherit global context: ${agent.inheritGlobalContext ? "true" : "false"}`);
|
|
192
195
|
lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`);
|
|
193
196
|
if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`);
|
|
194
197
|
if (agent.output) lines.push(`Output: ${agent.output}`);
|
package/src/tui/fleet-status.ts
CHANGED
|
@@ -2,8 +2,11 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { type EditorComponent, isKeyRelease, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
3
3
|
import { snapshotExternalRuns } from "../api/external-runs.ts";
|
|
4
4
|
import { formatModelThinking } from "../shared/formatters.ts";
|
|
5
|
-
import type { AsyncJobState, AsyncJobStep, FleetViewPlacement, HerdrProjectPaneSnapshot, NestedRunSummary, NestedStepSummary, SubagentState } from "../shared/types.ts";
|
|
5
|
+
import type { AsyncJobState, AsyncJobStep, FleetViewPlacement, HerdrProjectPaneSnapshot, HostStepState, HostStepVerdict, NestedRunSummary, NestedStepSummary, SubagentState } from "../shared/types.ts";
|
|
6
|
+
import { projectAsyncWorkflowRows, type AsyncStatusWorkflowRow } from "../runs/shared/async-status-projection.ts";
|
|
6
7
|
import { formatWorkflowJsonPreview } from "../workflows/scripted-workflow.ts";
|
|
8
|
+
import { hostStepReportName, hostStepVerdictLabel } from "../runs/shared/host-step-status.ts";
|
|
9
|
+
import { isStaleExtensionContextError } from "../shared/extension-context.ts";
|
|
7
10
|
|
|
8
11
|
export const FLEET_STATUS_WIDGET_KEY = "subagent-fleet-status";
|
|
9
12
|
|
|
@@ -30,18 +33,7 @@ type FleetStatusEntry = {
|
|
|
30
33
|
external?: true;
|
|
31
34
|
projectPane?: HerdrProjectPaneSnapshot;
|
|
32
35
|
nestedChildren?: NestedRunSummary[];
|
|
33
|
-
workflowRows?:
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type FleetWorkflowRow = {
|
|
37
|
-
name: string;
|
|
38
|
-
state: AsyncJobStep["status"];
|
|
39
|
-
modelThinking?: string;
|
|
40
|
-
activity?: string;
|
|
41
|
-
startedAt?: number;
|
|
42
|
-
tokens?: number;
|
|
43
|
-
window?: number;
|
|
44
|
-
overflow?: number;
|
|
36
|
+
workflowRows?: AsyncStatusWorkflowRow[];
|
|
45
37
|
};
|
|
46
38
|
|
|
47
39
|
type FleetNestedRow = {
|
|
@@ -57,7 +49,7 @@ type FleetNestedRow = {
|
|
|
57
49
|
type FleetTreeRow =
|
|
58
50
|
| { kind: "owner"; entry: FleetStatusEntry }
|
|
59
51
|
| { kind: "child"; entry: FleetStatusEntry; last: boolean }
|
|
60
|
-
| { kind: "workflow"; ownerKey: string; row:
|
|
52
|
+
| { kind: "workflow"; ownerKey: string; row: AsyncStatusWorkflowRow; last: boolean }
|
|
61
53
|
| { kind: "nested"; ownerKey: string; row: FleetNestedRow; last: boolean };
|
|
62
54
|
|
|
63
55
|
export interface FleetStatusOptions {
|
|
@@ -111,45 +103,12 @@ function nestedActivity(node: NestedRunSummary | NestedStepSummary): string | un
|
|
|
111
103
|
return undefined;
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
function
|
|
115
|
-
if (step.currentTool) return `tool ${step.currentTool}`;
|
|
116
|
-
if (step.currentPath) return step.currentPath.split(/[\\/]/).at(-1);
|
|
117
|
-
if (step.activityState === "needs_attention") return "needs attention";
|
|
118
|
-
if (step.activityState === "active_long_running") return "long-running";
|
|
119
|
-
if (step.turnCount !== undefined) return `${step.turnCount} turns`;
|
|
120
|
-
if (step.toolCount !== undefined) return `${step.toolCount} tools`;
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function workflowStepName(step: AsyncJobStep, index: number): string {
|
|
125
|
-
const key = step.workflowKey ?? `step ${index + 1}`;
|
|
126
|
-
const label = step.label && step.label !== key ? ` · ${step.label}` : "";
|
|
127
|
-
const phase = step.phase ? `${step.phase}: ` : "";
|
|
128
|
-
return `${phase}${key}${label} (${step.agent})`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function workflowFleetRows(steps: AsyncJobStep[] | undefined): FleetWorkflowRow[] {
|
|
132
|
-
return (steps ?? []).map((step, index) => {
|
|
133
|
-
const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
|
|
134
|
-
const activity = workflowStepActivity(step);
|
|
135
|
-
return {
|
|
136
|
-
name: workflowStepName(step, index),
|
|
137
|
-
state: step.status,
|
|
138
|
-
...(modelThinking ? { modelThinking } : {}),
|
|
139
|
-
...(activity ? { activity } : {}),
|
|
140
|
-
...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
|
|
141
|
-
...(step.tokens?.total !== undefined ? { tokens: step.tokens.total } : {}),
|
|
142
|
-
...(step.tokens?.window !== undefined ? { window: step.tokens.window } : {}),
|
|
143
|
-
};
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function visibleWorkflowRows(rows: FleetWorkflowRow[] | undefined, visibleLimit: number): FleetWorkflowRow[] {
|
|
106
|
+
function visibleWorkflowRows(rows: AsyncStatusWorkflowRow[] | undefined, visibleLimit: number): AsyncStatusWorkflowRow[] {
|
|
148
107
|
if (!rows?.length) return [];
|
|
149
108
|
if (rows.length <= visibleLimit) return rows;
|
|
150
109
|
const selected = new Set<number>();
|
|
151
110
|
for (const [index, row] of rows.entries()) {
|
|
152
|
-
if (row
|
|
111
|
+
if (!isWorkflowRowTerminal(row)) selected.add(index);
|
|
153
112
|
if (selected.size >= visibleLimit) break;
|
|
154
113
|
}
|
|
155
114
|
for (let index = rows.length - 1; index >= 0 && selected.size < visibleLimit; index--) selected.add(index);
|
|
@@ -157,9 +116,14 @@ function visibleWorkflowRows(rows: FleetWorkflowRow[] | undefined, visibleLimit:
|
|
|
157
116
|
return [{ name: `… +${rows.length - visible.length} hidden workflow steps`, state: "complete", overflow: rows.length - visible.length }, ...visible];
|
|
158
117
|
}
|
|
159
118
|
|
|
160
|
-
function
|
|
119
|
+
function isWorkflowRowTerminal(row: AsyncStatusWorkflowRow): boolean {
|
|
120
|
+
if (row.kind) return row.state === "done" || row.state === "cancelled" || row.state === "error";
|
|
121
|
+
return row.state === "complete" || row.state === "completed";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function nestedStatusGlyph(state: FleetNestedRow["state"] | "planned", theme: Theme): string {
|
|
161
125
|
if (state === "running") return theme.fg("accent", "●");
|
|
162
|
-
if (state === "queued" || state === "pending") return theme.fg("muted", "◦");
|
|
126
|
+
if (state === "queued" || state === "pending" || state === "planned") return theme.fg("muted", "◦");
|
|
163
127
|
if (state === "complete" || state === "completed") return theme.fg("success", "✓");
|
|
164
128
|
if (state === "failed" || state === "rejected") return theme.fg("error", "✗");
|
|
165
129
|
return theme.fg("warning", "■");
|
|
@@ -278,19 +242,31 @@ function fleetTreeRows(entries: FleetStatusEntry[]): FleetTreeRow[] {
|
|
|
278
242
|
return rows;
|
|
279
243
|
}
|
|
280
244
|
|
|
281
|
-
function isStaleExtensionContextError(error: unknown): boolean {
|
|
282
|
-
// Pi currently exposes stale contexts as plain Errors without a stable code or subtype.
|
|
283
|
-
return error instanceof Error
|
|
284
|
-
&& (error.message.includes("This extension ctx is stale")
|
|
285
|
-
|| error.message.includes("Extension context no longer active"));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
245
|
function foregroundDescription(control: { parentWorkflowRunId?: string; workflowKey?: string }, description: string | undefined): string | undefined {
|
|
289
246
|
if (!control.parentWorkflowRunId) return description;
|
|
290
247
|
const workflow = `workflow child: ${control.parentWorkflowRunId}${control.workflowKey ? ` (${control.workflowKey})` : ""}`;
|
|
291
248
|
return description ? `${workflow} · ${description}` : workflow;
|
|
292
249
|
}
|
|
293
250
|
|
|
251
|
+
function workflowIdentityCandidates(step: Pick<AsyncJobStep, "workflowKey" | "runId">): string[] {
|
|
252
|
+
return [...new Set([step.workflowKey, step.runId].filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function asyncJobIdentityCandidates(job: AsyncJobState): string[] {
|
|
256
|
+
return [...new Set([job.workflowKey, job.asyncId].filter((value): value is string => typeof value === "string" && value.length > 0))];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function linkedWorkflowParentKey(parentWorkflowRunId: string | undefined, activeWorkflowKeys: ReadonlySet<string>): string | undefined {
|
|
260
|
+
if (!parentWorkflowRunId) return undefined;
|
|
261
|
+
const parentKey = `async:${parentWorkflowRunId}`;
|
|
262
|
+
return activeWorkflowKeys.has(parentKey) ? parentKey : undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function workflowStepsWithoutMaterializedChildren(steps: AsyncJobStep[] | undefined, materializedChildIds: ReadonlySet<string> | undefined): AsyncJobStep[] | undefined {
|
|
266
|
+
if (!steps?.length || !materializedChildIds?.size) return steps;
|
|
267
|
+
return steps.filter((step) => !workflowIdentityCandidates(step).some((identity) => materializedChildIds.has(identity)));
|
|
268
|
+
}
|
|
269
|
+
|
|
294
270
|
function activeLeafAgentCount(entries: FleetStatusEntry[]): number {
|
|
295
271
|
return entries.filter((entry) => !entry.workflowWrapper && !entry.surface).length;
|
|
296
272
|
}
|
|
@@ -325,12 +301,30 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
325
301
|
const activeWorkflowKeys = new Set([...state.asyncJobs.values()]
|
|
326
302
|
.filter((job) => job.mode === "workflow" && isActiveState(job.status))
|
|
327
303
|
.map((job) => `async:${job.asyncId}`));
|
|
304
|
+
const materializedChildrenByWorkflow = new Map<string, Set<string>>();
|
|
305
|
+
for (const job of state.asyncJobs.values()) {
|
|
306
|
+
if (!isActiveState(job.status) || !job.parentWorkflowRunId) continue;
|
|
307
|
+
const parentKey = linkedWorkflowParentKey(job.parentWorkflowRunId, activeWorkflowKeys);
|
|
308
|
+
if (!parentKey) continue;
|
|
309
|
+
const childIds = materializedChildrenByWorkflow.get(parentKey) ?? new Set<string>();
|
|
310
|
+
for (const identity of asyncJobIdentityCandidates(job)) childIds.add(identity);
|
|
311
|
+
materializedChildrenByWorkflow.set(parentKey, childIds);
|
|
312
|
+
}
|
|
328
313
|
for (const control of state.foregroundControls.values()) {
|
|
329
|
-
const
|
|
330
|
-
const linkedParentKey = parentKey && activeWorkflowKeys.has(parentKey) ? parentKey : undefined;
|
|
314
|
+
const linkedParentKey = linkedWorkflowParentKey(control.parentWorkflowRunId, activeWorkflowKeys);
|
|
331
315
|
if (control.activeChildren) {
|
|
316
|
+
const nestedChildren = control.nestedChildren ?? [];
|
|
317
|
+
const nestedChildrenByParentStep = new Map<number, NestedRunSummary[]>();
|
|
318
|
+
for (const nested of nestedChildren) {
|
|
319
|
+
if (nested.parentStepIndex === undefined) continue;
|
|
320
|
+
const children = nestedChildrenByParentStep.get(nested.parentStepIndex) ?? [];
|
|
321
|
+
children.push(nested);
|
|
322
|
+
nestedChildrenByParentStep.set(nested.parentStepIndex, children);
|
|
323
|
+
}
|
|
332
324
|
for (const child of [...control.activeChildren.values()].sort((left, right) => left.index - right.index)) {
|
|
333
325
|
const modelThinking = formatModelThinking(child.model, child.thinking) || undefined;
|
|
326
|
+
const childNestedChildren = nestedChildrenByParentStep.get(child.index)
|
|
327
|
+
?? (control.activeChildren.size === 1 && nestedChildren.length ? nestedChildren : undefined);
|
|
334
328
|
entries.push({
|
|
335
329
|
key: `foreground-active:${control.runId}:${child.index}`,
|
|
336
330
|
...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
|
|
@@ -338,12 +332,10 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
338
332
|
...(modelThinking ? { modelThinking } : {}),
|
|
339
333
|
description: foregroundDescription(control, child.description),
|
|
340
334
|
startedAt: child.startedAt,
|
|
341
|
-
|
|
342
|
-
|
|
335
|
+
tokens: child.tokens ?? 0,
|
|
336
|
+
...(child.window !== undefined ? { window: child.window } : {}),
|
|
343
337
|
state: "running",
|
|
344
|
-
...(
|
|
345
|
-
? { nestedChildren: control.nestedChildren!.filter((nested) => nested.parentStepIndex === child.index) }
|
|
346
|
-
: control.activeChildren.size === 1 && control.nestedChildren?.length ? { nestedChildren: control.nestedChildren } : {}),
|
|
338
|
+
...(childNestedChildren?.length ? { nestedChildren: childNestedChildren } : {}),
|
|
347
339
|
});
|
|
348
340
|
}
|
|
349
341
|
continue;
|
|
@@ -366,10 +358,14 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
366
358
|
for (const job of state.asyncJobs.values()) {
|
|
367
359
|
if (!isActiveState(job.status)) continue;
|
|
368
360
|
const startedAt = job.startedAt ?? job.updatedAt ?? Date.now();
|
|
361
|
+
const linkedParentKey = linkedWorkflowParentKey(job.parentWorkflowRunId, activeWorkflowKeys);
|
|
369
362
|
if (job.mode === "workflow") {
|
|
370
363
|
const latestEmit = job.workflow?.emits?.length ? formatWorkflowJsonPreview(job.workflow.emits.at(-1), 120) : undefined;
|
|
364
|
+
const workflowSteps = workflowStepsWithoutMaterializedChildren(job.steps, materializedChildrenByWorkflow.get(`async:${job.asyncId}`));
|
|
365
|
+
const workflowRows = projectAsyncWorkflowRows(workflowSteps, job.hostSteps, job.preflight);
|
|
371
366
|
entries.push({
|
|
372
367
|
key: `async:${job.asyncId}`,
|
|
368
|
+
...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
|
|
373
369
|
workflowWrapper: true,
|
|
374
370
|
agent: "workflow",
|
|
375
371
|
description: latestEmit !== undefined ? `latest emit: ${latestEmit}` : job.description,
|
|
@@ -377,7 +373,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
377
373
|
tokens: job.totalTokens?.total ?? 0,
|
|
378
374
|
...(job.totalTokens?.window !== undefined ? { window: job.totalTokens.window } : {}),
|
|
379
375
|
state: job.status,
|
|
380
|
-
...(
|
|
376
|
+
...(workflowRows.length ? { workflowRows } : {}),
|
|
381
377
|
...(job.nestedChildren?.length ? { nestedChildren: job.nestedChildren } : {}),
|
|
382
378
|
});
|
|
383
379
|
continue;
|
|
@@ -392,6 +388,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
392
388
|
if (!steps?.length) {
|
|
393
389
|
entries.push({
|
|
394
390
|
key: `async:${job.asyncId}`,
|
|
391
|
+
...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
|
|
395
392
|
agent: job.mode ?? "subagent",
|
|
396
393
|
description: job.description,
|
|
397
394
|
startedAt,
|
|
@@ -409,6 +406,7 @@ export function collectFleetStatusEntries(state: SubagentState): FleetStatusEntr
|
|
|
409
406
|
const modelThinking = formatModelThinking(step.model, step.thinking) || undefined;
|
|
410
407
|
entries.push({
|
|
411
408
|
key: `async:${job.asyncId}:${index}`,
|
|
409
|
+
...(linkedParentKey ? { parentKey: linkedParentKey } : {}),
|
|
412
410
|
agent: step.label ? `${step.label} (${step.agent})` : step.agent,
|
|
413
411
|
...(modelThinking ? { modelThinking } : {}),
|
|
414
412
|
description: step.description ?? job.description,
|
|
@@ -643,6 +641,10 @@ export class SubagentFleetStatus {
|
|
|
643
641
|
}
|
|
644
642
|
const roster = this.rosterKeys();
|
|
645
643
|
const selectedIndex = Math.max(0, roster.indexOf(this.selectedKey));
|
|
644
|
+
const rosterIndexByKey = new Map<string, number>();
|
|
645
|
+
for (const [index, entry] of this.entries.entries()) {
|
|
646
|
+
if (!rosterIndexByKey.has(entry.key)) rosterIndexByKey.set(entry.key, index + 1);
|
|
647
|
+
}
|
|
646
648
|
const lines = [truncateToWidth(` ${theme.fg("dim", "↑↓/jk select · enter inspect · esc back")}`, width), ""];
|
|
647
649
|
lines.push(truncateToWidth(` ${this.bullet(0, selectedIndex, theme)} main`, width));
|
|
648
650
|
|
|
@@ -656,8 +658,8 @@ export class SubagentFleetStatus {
|
|
|
656
658
|
for (let index = start; index < start + visibleCount; index++) {
|
|
657
659
|
const row = tree[index]!;
|
|
658
660
|
if (row.kind === "owner" || row.kind === "child") {
|
|
659
|
-
const
|
|
660
|
-
lines.push(this.renderEntry(
|
|
661
|
+
const rosterIndex = rosterIndexByKey.get(row.entry.key) ?? 0;
|
|
662
|
+
lines.push(this.renderEntry(rosterIndex, selectedIndex, row.entry, width, theme, row.kind === "child" ? (row.last ? "└─" : "├─") : undefined));
|
|
661
663
|
} else if (row.kind === "workflow") {
|
|
662
664
|
lines.push(this.renderWorkflowRow(row.row, row.last, width, theme));
|
|
663
665
|
} else {
|
|
@@ -665,16 +667,16 @@ export class SubagentFleetStatus {
|
|
|
665
667
|
}
|
|
666
668
|
}
|
|
667
669
|
if (hiddenBelow > 0) lines.push(rightAlign("", theme.fg("dim", `↓ ${hiddenBelow} more`), width));
|
|
668
|
-
this.renderProjectPaneSection(lines, selectedIndex, width, theme);
|
|
670
|
+
this.renderProjectPaneSection(lines, selectedIndex, width, theme, rosterIndexByKey);
|
|
669
671
|
return lines;
|
|
670
672
|
}
|
|
671
673
|
|
|
672
|
-
private renderProjectPaneSection(lines: string[], selectedIndex: number, width: number, theme: Theme): void {
|
|
674
|
+
private renderProjectPaneSection(lines: string[], selectedIndex: number, width: number, theme: Theme, rosterIndexByKey: ReadonlyMap<string, number>): void {
|
|
673
675
|
const entries = this.entries.filter((entry) => entry.surface === "project-pane");
|
|
674
676
|
if (!entries.length) return;
|
|
675
677
|
lines.push("", truncateToWidth(` ${theme.fg("dim", "project panes")}`, width));
|
|
676
678
|
for (const entry of entries) {
|
|
677
|
-
const rosterIndex =
|
|
679
|
+
const rosterIndex = rosterIndexByKey.get(entry.key) ?? 0;
|
|
678
680
|
lines.push(this.renderEntry(rosterIndex, selectedIndex, entry, width, theme));
|
|
679
681
|
}
|
|
680
682
|
}
|
|
@@ -703,16 +705,50 @@ export class SubagentFleetStatus {
|
|
|
703
705
|
return truncateToWidth(`${left}${theme.fg("dim", elapsed)}`, width);
|
|
704
706
|
}
|
|
705
707
|
|
|
706
|
-
private
|
|
708
|
+
private workflowRowGlyph(row: AsyncStatusWorkflowRow, theme: Theme): string {
|
|
709
|
+
if (!row.kind) return nestedStatusGlyph(row.state as FleetNestedRow["state"], theme);
|
|
710
|
+
const state = row.state as HostStepState;
|
|
711
|
+
if (state === "pending") return theme.fg("muted", "◦");
|
|
712
|
+
if (state === "running") return theme.fg("accent", "●");
|
|
713
|
+
if (state === "done") return row.verdict === "pass" ? theme.fg("success", "✓") : row.verdict === "fail" ? theme.fg("error", "✗") : theme.fg("warning", "■");
|
|
714
|
+
if (state === "error") return theme.fg("error", "✗");
|
|
715
|
+
return theme.fg("warning", "■");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private workflowRowStateLabel(row: AsyncStatusWorkflowRow, theme: Theme): string {
|
|
719
|
+
const state = row.kind ? hostStepVerdictLabel(row.state as HostStepState, row.verdict as HostStepVerdict | undefined) : row.state;
|
|
720
|
+
if (state === "running") return theme.fg("accent", state);
|
|
721
|
+
if (state === "pending" || state === "queued") return theme.fg("muted", state);
|
|
722
|
+
if (state === "pass" || state === "complete" || state === "completed") return theme.fg("success", state === "pass" ? "pass" : "complete");
|
|
723
|
+
if (state === "fail" || state === "failed" || state === "error") return theme.fg("error", state === "fail" ? "fail" : state);
|
|
724
|
+
return theme.fg("warning", state);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private renderWorkflowRow(row: AsyncStatusWorkflowRow, last: boolean, width: number, theme: Theme): string {
|
|
707
728
|
const marker = last ? "└─" : "├─";
|
|
708
729
|
const indent = " ";
|
|
709
730
|
if (row.overflow !== undefined) return truncateToWidth(`${indent}${marker} ${theme.fg("dim", `+${row.overflow} hidden workflow steps`)}`, width);
|
|
710
731
|
const modelThinking = row.modelThinking ? ` (${row.modelThinking})` : "";
|
|
711
732
|
const activity = row.activity ? ` · ${row.activity}` : "";
|
|
712
|
-
const
|
|
733
|
+
const kind = row.kind ? `${row.kind}: ` : "";
|
|
734
|
+
const hints = row.preflight ? [
|
|
735
|
+
row.preflight.mode ? `mode:${row.preflight.mode}` : undefined,
|
|
736
|
+
row.preflight.decision ? `decision:${row.preflight.decision}` : undefined,
|
|
737
|
+
row.preflight.claims?.length ? `claims:${row.preflight.claims.join(",")}` : undefined,
|
|
738
|
+
row.preflight.expectedOutput ? `expected:${row.preflight.expectedOutput}` : undefined,
|
|
739
|
+
row.preflight.independence ? `independence:${row.preflight.independence}` : undefined,
|
|
740
|
+
].filter((value): value is string => Boolean(value)).join(" · ") : "";
|
|
741
|
+
const left = `${indent}${marker} ${this.workflowRowGlyph(row, theme)} ${theme.fg("muted", `${kind}${row.name}${modelThinking}`)} · ${this.workflowRowStateLabel(row, theme)}${activity}${hints ? ` · ${hints}` : ""}`;
|
|
713
742
|
const details = [
|
|
714
743
|
row.startedAt !== undefined ? formatFleetElapsed(Date.now() - row.startedAt) : undefined,
|
|
715
744
|
row.tokens !== undefined ? formatFleetTokens(row.tokens, row.window) : undefined,
|
|
745
|
+
row.provider ? `provider:${row.provider}` : undefined,
|
|
746
|
+
row.role ? `role:${row.role}` : undefined,
|
|
747
|
+
row.target,
|
|
748
|
+
row.detail,
|
|
749
|
+
row.reasonCode ? `reason:${row.reasonCode}` : undefined,
|
|
750
|
+
row.freshness?.stale ? "stale" : row.freshness?.observedRef ? `ref:${row.freshness.observedRef}` : undefined,
|
|
751
|
+
row.reportPath ? `out:${hostStepReportName(row.reportPath)}` : undefined,
|
|
716
752
|
].filter(Boolean).join(" · ");
|
|
717
753
|
return truncateToWidth(`${left}${details ? theme.fg("dim", ` · ${details}`) : ""}`, width);
|
|
718
754
|
}
|
|
@@ -767,12 +803,21 @@ export class SubagentFleetStatus {
|
|
|
767
803
|
Math.round((now - entry.startedAt) / 1000),
|
|
768
804
|
entry.tokens,
|
|
769
805
|
visibleWorkflowRows(entry.workflowRows, entry.parentKey ? 2 : 4).map((row) => [
|
|
806
|
+
row.kind,
|
|
770
807
|
row.name,
|
|
771
808
|
row.state,
|
|
772
809
|
row.modelThinking,
|
|
773
810
|
row.activity,
|
|
774
811
|
row.startedAt,
|
|
775
812
|
row.tokens,
|
|
813
|
+
row.provider,
|
|
814
|
+
row.role,
|
|
815
|
+
row.verdict,
|
|
816
|
+
row.reasonCode,
|
|
817
|
+
row.detail,
|
|
818
|
+
row.target,
|
|
819
|
+
row.freshness,
|
|
820
|
+
row.reportPath,
|
|
776
821
|
row.overflow,
|
|
777
822
|
]),
|
|
778
823
|
nestedFleetRows(entry.nestedChildren, entry.parentKey ? 3 : 4).map((row) => [
|
|
@@ -807,7 +852,13 @@ export class SubagentFleetStatus {
|
|
|
807
852
|
|
|
808
853
|
private clearWidget(): void {
|
|
809
854
|
if (!this.widgetRegistered) return;
|
|
810
|
-
|
|
855
|
+
try {
|
|
856
|
+
this.ui?.setWidget(FLEET_STATUS_WIDGET_KEY, undefined);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
859
|
+
this.clearUiRegistration();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
811
862
|
this.widgetRegistered = false;
|
|
812
863
|
this.tui = undefined;
|
|
813
864
|
}
|
package/src/tui/fleet.ts
CHANGED
|
@@ -147,6 +147,7 @@ function trackedJobSummary(job: AsyncJobState): AsyncRunSummary {
|
|
|
147
147
|
...(job.currentStep !== undefined ? { currentStep: job.currentStep } : {}),
|
|
148
148
|
...(job.chainStepCount !== undefined ? { chainStepCount: job.chainStepCount } : {}),
|
|
149
149
|
...(job.parallelGroups?.length ? { parallelGroups: job.parallelGroups } : {}),
|
|
150
|
+
...(job.preflight ? { preflight: job.preflight } : {}),
|
|
150
151
|
steps: (job.steps ?? job.agents?.map((agent, index) => ({ agent, index, status: job.status === "queued" ? "pending" as const : job.status })) ?? []).map((step, index) => ({
|
|
151
152
|
...step,
|
|
152
153
|
index: step.index ?? index,
|
|
@@ -355,7 +356,12 @@ function foregroundPromptAuditCount(item: Extract<FleetItem, { kind: "foreground
|
|
|
355
356
|
return [...item.control.activeChildren.keys()].filter((index) => getLivePromptAudit(item.control, index)).length;
|
|
356
357
|
}
|
|
357
358
|
|
|
358
|
-
function
|
|
359
|
+
function promptAuditString(value: unknown): string | undefined {
|
|
360
|
+
return typeof value === "string" ? value : undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function authoredPromptSummary(text: unknown): string | undefined {
|
|
364
|
+
if (typeof text !== "string") return undefined;
|
|
359
365
|
const summary = text.replace(/\s+/g, " ").trim();
|
|
360
366
|
return summary ? truncateToWidth(summary, PROMPT_AUDIT_SUMMARY_WIDTH, "…") : undefined;
|
|
361
367
|
}
|
|
@@ -366,11 +372,11 @@ function foregroundAuthoredPromptSummary(item: Extract<FleetItem, { kind: "foreg
|
|
|
366
372
|
return prompt ? authoredPromptSummary(prompt.authoredTask) : undefined;
|
|
367
373
|
}
|
|
368
374
|
|
|
369
|
-
function promptAuditText(prompt: LivePromptAudit, view: PromptAuditView): string {
|
|
375
|
+
function promptAuditText(prompt: LivePromptAudit, view: PromptAuditView): string | undefined {
|
|
370
376
|
switch (view) {
|
|
371
|
-
case "authored": return prompt.authoredTask;
|
|
372
|
-
case "runtime": return prompt.runtimeAdditions;
|
|
373
|
-
case "effective": return prompt.finalEffectivePrompt;
|
|
377
|
+
case "authored": return promptAuditString(prompt.authoredTask);
|
|
378
|
+
case "runtime": return promptAuditString(prompt.runtimeAdditions);
|
|
379
|
+
case "effective": return promptAuditString(prompt.finalEffectivePrompt);
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
382
|
|