pi-subagents 0.64.0 → 0.65.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 +33 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +18 -16
- package/docs/configuration.md +7 -15
- package/docs/extension-api.md +15 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +7 -6
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +5 -3
- package/package.json +3 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +2 -2
- package/src/agents/agent-management.ts +47 -15
- package/src/api/capability-ceiling.ts +0 -1
- package/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
- package/src/api/preflight.ts +2 -3
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +27 -5
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +2 -1
- package/src/extension/tool-description.ts +9 -8
- package/src/intercom/native-supervisor-channel.ts +138 -60
- package/src/intercom/supervisor-ui.ts +243 -0
- package/src/runs/background/async-execution.ts +43 -24
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +11 -2
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/run-child-session.ts +613 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +125 -0
- package/src/runs/background/runner-child-sessions.ts +31 -0
- package/src/runs/background/scheduled-runs.ts +18 -4
- package/src/runs/background/subagent-runner.ts +184 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +189 -377
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +100 -57
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/capability-ceiling.ts +1 -2
- package/src/runs/shared/child-hooks.ts +25 -0
- package/src/runs/shared/child-identity.ts +13 -2
- package/src/runs/shared/child-launch.ts +314 -0
- package/src/runs/shared/child-lifecycle.ts +25 -0
- package/src/runs/shared/child-runtime-config.ts +126 -0
- package/src/runs/shared/child-session.ts +342 -0
- package/src/runs/shared/child-tool-plan.ts +530 -0
- package/src/runs/shared/claude-code-adapter.ts +5 -1
- package/src/runs/shared/completion-guard.ts +1 -1
- package/src/runs/shared/external-cli-preflight.ts +16 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +82 -14
- package/src/runs/shared/model-fallback.ts +47 -4
- package/src/runs/shared/nested-events.ts +29 -45
- package/src/runs/shared/nested-path.ts +0 -14
- package/src/runs/shared/orca-progress-tabs.ts +12 -7
- package/src/runs/shared/parallel-utils.ts +0 -2
- package/src/runs/shared/permissions.ts +0 -13
- package/src/runs/shared/process-signal.ts +4 -1
- package/src/runs/shared/run-fanout-budget.ts +0 -13
- package/src/runs/shared/runtime-acknowledged-extensions.ts +0 -27
- package/src/runs/shared/structured-output.ts +17 -4
- package/src/runs/shared/subagent-control.ts +2 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +87 -384
- package/src/runs/shared/tool-availability.ts +18 -62
- package/src/runs/shared/tool-budget.ts +0 -14
- package/src/runs/shared/worktree-cleanup-plan.ts +25 -6
- package/src/runs/shared/worktree.ts +117 -30
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +55 -28
- package/src/shared/utils.ts +3 -4
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +233 -31
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +48 -1
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1059
- package/src/runs/shared/subagent-startup-retry.ts +0 -116
- package/src/shared/post-exit-stdio-guard.ts +0 -85
package/src/shared/types.ts
CHANGED
|
@@ -369,6 +369,7 @@ export interface ControlEvent {
|
|
|
369
369
|
tokens?: number;
|
|
370
370
|
toolCount?: number;
|
|
371
371
|
currentTool?: string;
|
|
372
|
+
toolCallId?: string;
|
|
372
373
|
currentToolDurationMs?: number;
|
|
373
374
|
currentPath?: string;
|
|
374
375
|
elapsedMs?: number;
|
|
@@ -786,6 +787,8 @@ export interface SteeringRecoveryDescriptor {
|
|
|
786
787
|
agentContract?: AgentContract;
|
|
787
788
|
agent: string;
|
|
788
789
|
sessionFile?: string;
|
|
790
|
+
/** Git ref used to allocate managed worktrees for this run. */
|
|
791
|
+
baseRef?: string;
|
|
789
792
|
cwd: string;
|
|
790
793
|
model?: string;
|
|
791
794
|
modelProvider?: string;
|
|
@@ -1159,15 +1162,6 @@ export interface AcceptanceLedger {
|
|
|
1159
1162
|
};
|
|
1160
1163
|
}
|
|
1161
1164
|
|
|
1162
|
-
export interface ProtocolOutputLimit {
|
|
1163
|
-
code: "protocol_output_limit";
|
|
1164
|
-
stream: "stdout" | "stderr";
|
|
1165
|
-
limitBytes: number;
|
|
1166
|
-
observedBytes: number;
|
|
1167
|
-
diagnosticPrefix: string;
|
|
1168
|
-
diagnosticTail: string;
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
1165
|
export interface LaunchResolvedChildExtensionsV1 {
|
|
1172
1166
|
version: 1;
|
|
1173
1167
|
/** This is parent-resolved launch intent, not child-runtime acknowledgement that extensions loaded. */
|
|
@@ -1262,7 +1256,6 @@ export interface SingleResult {
|
|
|
1262
1256
|
* a signal to reduce input size or re-decompose the task.
|
|
1263
1257
|
*/
|
|
1264
1258
|
contextOverflow?: boolean;
|
|
1265
|
-
protocolError?: ProtocolOutputLimit;
|
|
1266
1259
|
sessionFile?: string;
|
|
1267
1260
|
skills?: string[];
|
|
1268
1261
|
skillsWarning?: string;
|
|
@@ -1369,7 +1362,7 @@ export interface AgentCapabilityRow {
|
|
|
1369
1362
|
executable: boolean;
|
|
1370
1363
|
restrictionSources?: string[];
|
|
1371
1364
|
aliases?: string[];
|
|
1372
|
-
runner: { type: "pi" } | { type: "external-cli"; adapter?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
|
|
1365
|
+
runner: { type: "pi" } | { type: "external-cli"; adapter?: string; command: string; available: boolean; unavailableReason?: string; capabilities: ExternalCliCapabilities } | { type: "external-job"; provider: string; available?: boolean; capabilities: ExternalJobRunnerStatus["capabilities"] };
|
|
1373
1366
|
tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
|
|
1374
1367
|
model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
|
|
1375
1368
|
execution?: { defaultAsync?: boolean; timeoutMs?: number };
|
|
@@ -2107,6 +2100,18 @@ export interface ForegroundChildControl {
|
|
|
2107
2100
|
toolCount?: number;
|
|
2108
2101
|
interrupt?: () => boolean;
|
|
2109
2102
|
detach?: () => boolean;
|
|
2103
|
+
/** Steer the live in-process child session; undefined until the session exists. */
|
|
2104
|
+
steer?: (input: ForegroundSteerInput) => Promise<ForegroundSteerOutcome>;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
export interface ForegroundSteerInput {
|
|
2108
|
+
message: string;
|
|
2109
|
+
mode?: "steer" | "follow_up" | "auto";
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
export interface ForegroundSteerOutcome {
|
|
2113
|
+
state: "delivered" | "queued" | "failed";
|
|
2114
|
+
reason?: string;
|
|
2110
2115
|
}
|
|
2111
2116
|
|
|
2112
2117
|
export interface ForegroundRunControl {
|
|
@@ -2115,8 +2120,6 @@ export interface ForegroundRunControl {
|
|
|
2115
2120
|
parentWorkflowRunId?: string;
|
|
2116
2121
|
/** Stable workflow lane key for this live foreground child. */
|
|
2117
2122
|
workflowKey?: string;
|
|
2118
|
-
/** Private control root used to steer a live workflow-owned child. */
|
|
2119
|
-
workflowSteeringDir?: string;
|
|
2120
2123
|
/** Originating parent session; required for public fleet projection. */
|
|
2121
2124
|
sessionId?: string;
|
|
2122
2125
|
mode: SubagentRunMode;
|
|
@@ -2158,6 +2161,7 @@ export interface ForegroundRunControl {
|
|
|
2158
2161
|
nestedChildren?: NestedRunSummary[];
|
|
2159
2162
|
interrupt?: () => boolean;
|
|
2160
2163
|
detach?: () => boolean;
|
|
2164
|
+
steer?: ForegroundChildControl["steer"];
|
|
2161
2165
|
}
|
|
2162
2166
|
|
|
2163
2167
|
export interface WaitSubscriptionRecord {
|
|
@@ -2318,17 +2322,25 @@ export interface SubagentChildStatusEvent {
|
|
|
2318
2322
|
// Execution Options
|
|
2319
2323
|
// ============================================================================
|
|
2320
2324
|
|
|
2325
|
+
/** Live controls for one in-process foreground child session. */
|
|
2326
|
+
export interface ForegroundChildSessionControls {
|
|
2327
|
+
steer: (text: string) => Promise<void>;
|
|
2328
|
+
followUp: (text: string) => Promise<void>;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2321
2331
|
export interface RunSyncOptions {
|
|
2322
2332
|
/** Exact discovery provenance for an unknown-agent error; omission uses defensive fallback discovery. */
|
|
2323
2333
|
unknownAgentDiagnosticContext?: import("../agents/agents.ts").UnknownAgentDiagnosticContext;
|
|
2334
|
+
/** Session factory for the in-process child; defaults to the process-wide factory. */
|
|
2335
|
+
childSessionFactory?: import("../runs/shared/child-session.ts").ChildSessionFactory;
|
|
2336
|
+
/** The launching executor's own child runtime when it is itself an in-process child. */
|
|
2337
|
+
childRuntime?: import("../runs/shared/child-runtime-config.ts").ChildRuntimeConfig;
|
|
2338
|
+
/** Fires once the child session exists and can be steered. */
|
|
2339
|
+
onChildSession?: (controls: ForegroundChildSessionControls) => void;
|
|
2324
2340
|
/** Opt-in global permission rules; missing tools remain allowed. */
|
|
2325
2341
|
permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
|
|
2326
2342
|
/** Session id of the direct parent session for permission-system ask forwarding. */
|
|
2327
2343
|
parentSessionId?: string;
|
|
2328
|
-
/** Private prompt-runtime steering transport for workflow-owned foreground children. */
|
|
2329
|
-
steerInboxDir?: string;
|
|
2330
|
-
steerCapabilityPath?: string;
|
|
2331
|
-
steerAckDir?: string;
|
|
2332
2344
|
/** Resolved launch context for this child. */
|
|
2333
2345
|
context?: "fresh" | "fork";
|
|
2334
2346
|
cwd?: string;
|
|
@@ -2410,6 +2422,7 @@ export interface RunSyncOptions {
|
|
|
2410
2422
|
schemaPath: string;
|
|
2411
2423
|
outputPath: string;
|
|
2412
2424
|
acceptanceReportPath?: string;
|
|
2425
|
+
acceptanceReportRequired?: boolean;
|
|
2413
2426
|
};
|
|
2414
2427
|
agentContract?: AgentContract;
|
|
2415
2428
|
acceptance?: AcceptanceInput;
|
|
@@ -2765,8 +2778,17 @@ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
|
|
|
2765
2778
|
return normalizeNonNegativeInteger(value);
|
|
2766
2779
|
}
|
|
2767
2780
|
|
|
2768
|
-
|
|
2769
|
-
|
|
2781
|
+
/** Depth context of the executor's own child runtime, when it runs as an in-process child. */
|
|
2782
|
+
export interface SubagentDepthContext {
|
|
2783
|
+
depth: number;
|
|
2784
|
+
maxDepth?: number;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
/** Operator override for the top-level parent; children inherit their limit through their runtime config. */
|
|
2788
|
+
export const SUBAGENT_MAX_DEPTH_ENV = "PI_SUBAGENT_MAX_DEPTH";
|
|
2789
|
+
|
|
2790
|
+
export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number, runtime?: SubagentDepthContext): number {
|
|
2791
|
+
return normalizeMaxSubagentDepth(runtime ? runtime.maxDepth : process.env[SUBAGENT_MAX_DEPTH_ENV])
|
|
2770
2792
|
?? normalizeMaxSubagentDepth(configMaxDepth)
|
|
2771
2793
|
?? DEFAULT_SUBAGENT_MAX_DEPTH;
|
|
2772
2794
|
}
|
|
@@ -2777,19 +2799,24 @@ export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDep
|
|
|
2777
2799
|
return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
|
|
2778
2800
|
}
|
|
2779
2801
|
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2802
|
+
/** Depth of the executor itself: 0 for a top-level parent, its own child depth otherwise. */
|
|
2803
|
+
export function resolveCurrentSubagentDepth(runtime?: SubagentDepthContext): number {
|
|
2804
|
+
const depth = runtime ? runtime.depth : 0;
|
|
2805
|
+
return Number.isFinite(depth) ? depth : 0;
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
export function checkSubagentDepth(configMaxDepth?: number, runtime?: SubagentDepthContext): { blocked: boolean; depth: number; maxDepth: number } {
|
|
2809
|
+
const depth = resolveCurrentSubagentDepth(runtime);
|
|
2810
|
+
const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth, runtime);
|
|
2811
|
+
const blocked = depth >= maxDepth;
|
|
2784
2812
|
return { blocked, depth, maxDepth };
|
|
2785
2813
|
}
|
|
2786
2814
|
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
const nextDepth = Number.isFinite(parentDepth) ? parentDepth + 1 : 1;
|
|
2815
|
+
/** Depth context handed to a child launched by an executor at `runtime` (undefined for a top-level parent). */
|
|
2816
|
+
export function resolveChildDepth(maxDepth?: number, runtime?: SubagentDepthContext): Required<SubagentDepthContext> {
|
|
2790
2817
|
return {
|
|
2791
|
-
|
|
2792
|
-
|
|
2818
|
+
depth: resolveCurrentSubagentDepth(runtime) + 1,
|
|
2819
|
+
maxDepth: normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth(undefined, runtime),
|
|
2793
2820
|
};
|
|
2794
2821
|
}
|
|
2795
2822
|
|
package/src/shared/utils.ts
CHANGED
|
@@ -413,10 +413,9 @@ export function compactForegroundDetails(details: Details): Details {
|
|
|
413
413
|
*
|
|
414
414
|
* The completed-compaction helpers above bail out while a child is still
|
|
415
415
|
* `running`, so a long or deeply nested fan-out streams full, unbounded progress on
|
|
416
|
-
* every tick.
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* `protocol_output_limit`.
|
|
416
|
+
* every tick. The parent records every streamed `tool_execution_update` in its
|
|
417
|
+
* transcript and `events.jsonl`, so an unbounded running snapshot grows those
|
|
418
|
+
* artifacts and the live display state without bound.
|
|
420
419
|
*
|
|
421
420
|
* These bound the STREAMED snapshot only. The final returned result keeps the full
|
|
422
421
|
* live progress and message transcript, and every live-display consumer already
|
|
@@ -21,7 +21,6 @@ import { formatTokens, shortenPath } from "../shared/formatters.ts";
|
|
|
21
21
|
import { listAsyncRuns, formatAsyncRunProgressLabel, type AsyncRunSummary } from "../runs/background/async-status.ts";
|
|
22
22
|
import { encodeInspectReply, handleInspectRpcArgs, INSPECT_WIDGET_KEY } from "../runs/background/inspect-rpc.ts";
|
|
23
23
|
import { listScheduledRunSummaries } from "../runs/background/scheduled-runs.ts";
|
|
24
|
-
import { SUBAGENT_FANOUT_CHILD_ENV } from "../runs/shared/pi-args.ts";
|
|
25
24
|
import { resolveAsyncStatusChild } from "../runs/shared/child-identity.ts";
|
|
26
25
|
import { readStatus } from "../shared/utils.ts";
|
|
27
26
|
import { getArtifactPaths, getArtifactsDir } from "../shared/artifacts.ts";
|
|
@@ -1030,11 +1029,6 @@ export function registerSlashCommands(
|
|
|
1030
1029
|
return;
|
|
1031
1030
|
}
|
|
1032
1031
|
|
|
1033
|
-
if (process.env[SUBAGENT_FANOUT_CHILD_ENV] === "1") {
|
|
1034
|
-
sendSlashText(pi, "Selector unavailable in child-safe fanout mode. Pass an explicit current-session top-level async run id, for example `/subagents-stop <run-id>` or `subagent({ action: \"stop\", id: \"<run-id>\" })`.");
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
1032
|
let targets: StopSelectorTarget[];
|
|
1039
1033
|
try {
|
|
1040
1034
|
targets = discoverStopTargets(ctx, state);
|
package/src/tui/fleet.ts
CHANGED
|
@@ -204,7 +204,6 @@ export function collectFleetSnapshot(
|
|
|
204
204
|
if (!control.parentWorkflowRunId
|
|
205
205
|
|| !workflowParentIds.has(control.parentWorkflowRunId)
|
|
206
206
|
|| !belongsToCurrentSession(control.sessionId, state.currentSessionId)
|
|
207
|
-
|| !control.workflowSteeringDir
|
|
208
207
|
|| activeChildCount === 0) continue;
|
|
209
208
|
liveWorkflowForegroundControls.add(control);
|
|
210
209
|
workflowForegroundChildCounts.set(control.parentWorkflowRunId, (workflowForegroundChildCounts.get(control.parentWorkflowRunId) ?? 0) + activeChildCount);
|
package/src/tui/render.ts
CHANGED
|
@@ -94,6 +94,10 @@ export function liveDetailHintText(): string {
|
|
|
94
94
|
return `Press ${liveDetailKeyText()} for live detail · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function workflowDetailHintText(): string {
|
|
98
|
+
return `Press ${liveDetailKeyText()} for details · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
|
|
99
|
+
}
|
|
100
|
+
|
|
97
101
|
function foregroundSingleHintText(shortcut?: string): string {
|
|
98
102
|
if (!shortcut) return liveDetailHintText();
|
|
99
103
|
const label = formatShortcutLabel(shortcut);
|
|
@@ -609,15 +613,8 @@ function widgetLaneDetailLines(job: AsyncJobState, theme: Theme, projection?: Wo
|
|
|
609
613
|
return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
|
|
610
614
|
}
|
|
611
615
|
|
|
612
|
-
function workflowPreflightLines(job: AsyncJobState
|
|
616
|
+
function workflowPreflightLines(job: AsyncJobState): string[] {
|
|
613
617
|
if (job.mode !== "workflow" || !job.preflight) return [];
|
|
614
|
-
if (!expanded) {
|
|
615
|
-
const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
|
|
616
|
-
return [
|
|
617
|
-
formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
|
|
618
|
-
...(warning ? [warning] : []),
|
|
619
|
-
];
|
|
620
|
-
}
|
|
621
618
|
return [
|
|
622
619
|
...formatWorkflowPreflight(job.preflight, { indent: " " }).split("\n"),
|
|
623
620
|
...(job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split("\n") : []),
|
|
@@ -1058,6 +1055,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1058
1055
|
return JSON.stringify({
|
|
1059
1056
|
asyncDir: job.asyncDir,
|
|
1060
1057
|
status: job.status,
|
|
1058
|
+
description: job.mode === "workflow" ? job.description : undefined,
|
|
1061
1059
|
activityState: job.activityState,
|
|
1062
1060
|
lastActivityAt: job.lastActivityAt,
|
|
1063
1061
|
currentTool: job.currentTool,
|
|
@@ -1075,8 +1073,8 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1075
1073
|
currentNodeId: job.workflowGraph.currentNodeId,
|
|
1076
1074
|
stages: projection.stages.map((node) => [node.id, node.status, node.agent, node.phase, node.label, node.flatIndex, node.outputName, node.structured, node.error]),
|
|
1077
1075
|
} : undefined,
|
|
1078
|
-
preflight: expanded ? job.preflight : job.preflight
|
|
1079
|
-
preflightWarnings: expanded ? job.workflow?.preflightWarnings :
|
|
1076
|
+
preflight: expanded ? job.preflight : job.preflight?.lanes.map((lane) => [lane.key, lane.mode, lane.decision, lane.claims, lane.expectedOutput]),
|
|
1077
|
+
preflightWarnings: expanded ? job.workflow?.preflightWarnings : undefined,
|
|
1080
1078
|
checklist: projection.checklist ? {
|
|
1081
1079
|
total: projection.checklist.total,
|
|
1082
1080
|
done: projection.checklist.done,
|
|
@@ -1084,7 +1082,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1084
1082
|
queued: projection.checklist.queued,
|
|
1085
1083
|
blocked: projection.checklist.blocked,
|
|
1086
1084
|
failed: projection.checklist.failed,
|
|
1087
|
-
phases: projection.checklist.phases.map((phase) => [phase.key, phase.state, phase.done, phase.total, phase.running, phase.queued, phase.blocked, phase.failed, phase.paused, phase.stopped, phase.items.map((item) => [item.key, item.state, item.currentTool, item.currentPath, item.durationMs, item.toolCount, item.error])]),
|
|
1085
|
+
phases: projection.checklist.phases.map((phase) => [phase.key, phase.state, phase.done, phase.total, phase.running, phase.queued, phase.blocked, phase.failed, phase.paused, phase.stopped, phase.items.map((item) => [item.key, item.label, item.agent, item.state, item.preflight?.key, item.preflight?.mode, item.preflight?.decision, item.preflight?.claims, item.preflight?.expectedOutput, item.currentTool, item.currentPath, item.durationMs, item.toolCount, item.error])]),
|
|
1088
1086
|
} : undefined,
|
|
1089
1087
|
steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
|
|
1090
1088
|
nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
|
|
@@ -1340,6 +1338,208 @@ function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | u
|
|
|
1340
1338
|
return lines;
|
|
1341
1339
|
}
|
|
1342
1340
|
|
|
1341
|
+
interface CompactWorkflowLaneRow {
|
|
1342
|
+
key: string;
|
|
1343
|
+
state: WorkflowChecklistState;
|
|
1344
|
+
agent?: string;
|
|
1345
|
+
mode?: string;
|
|
1346
|
+
decision?: string;
|
|
1347
|
+
claims?: string;
|
|
1348
|
+
expectedOutput?: string;
|
|
1349
|
+
label?: string;
|
|
1350
|
+
toolUses?: number;
|
|
1351
|
+
durationMs?: number;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
interface CompactWorkflowLaneCounts {
|
|
1355
|
+
total: number;
|
|
1356
|
+
done: number;
|
|
1357
|
+
active: number;
|
|
1358
|
+
queued: number;
|
|
1359
|
+
blocked: number;
|
|
1360
|
+
failed: number;
|
|
1361
|
+
paused: number;
|
|
1362
|
+
stopped: number;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function compactWorkflowLaneState(items: readonly WorkflowChecklistItem[]): WorkflowChecklistState {
|
|
1366
|
+
let state: WorkflowChecklistState | undefined;
|
|
1367
|
+
for (const item of items) {
|
|
1368
|
+
if (state === undefined || workflowChecklistItemPriority(item.state) < workflowChecklistItemPriority(state)) state = item.state;
|
|
1369
|
+
}
|
|
1370
|
+
return state ?? "queued";
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function compactWorkflowLaneOwner(items: readonly WorkflowChecklistItem[]): string | undefined {
|
|
1374
|
+
const owners = [...new Set(items.map((item) => item.agent ?? item.role).filter((value): value is string => Boolean(value?.trim())))];
|
|
1375
|
+
if (owners.length === 0) return undefined;
|
|
1376
|
+
return boundedLaneValue(owners.length > 2 ? `${owners.slice(0, 2).join(", ")} +${owners.length - 2}` : owners.join(", "), 32);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function compactWorkflowLaneLabel(items: readonly WorkflowChecklistItem[], key: string): string | undefined {
|
|
1380
|
+
const labels = [...new Set(items.map((item) => item.label).filter((value): value is string => Boolean(value?.trim() && value.trim() !== key)))];
|
|
1381
|
+
if (labels.length === 0) return undefined;
|
|
1382
|
+
return boundedLaneValue(labels.length > 1 ? `${labels[0]} +${labels.length - 1}` : labels[0], 48);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function compactWorkflowLaneRow(key: string, items: readonly WorkflowChecklistItem[], lane = items.find((item) => item.preflight)?.preflight, fallbackState: WorkflowChecklistState = "queued"): CompactWorkflowLaneRow {
|
|
1386
|
+
const state = items.length ? compactWorkflowLaneState(items) : fallbackState;
|
|
1387
|
+
const toolCounts = items.map((item) => item.toolCount).filter((value): value is number => value !== undefined);
|
|
1388
|
+
const durations = items.map((item) => item.durationMs).filter((value): value is number => value !== undefined);
|
|
1389
|
+
const label = compactWorkflowLaneLabel(items, key);
|
|
1390
|
+
return {
|
|
1391
|
+
key: boundedLaneValue(key, 40) ?? key,
|
|
1392
|
+
state,
|
|
1393
|
+
agent: compactWorkflowLaneOwner(items),
|
|
1394
|
+
...(lane?.mode ? { mode: lane.mode } : {}),
|
|
1395
|
+
...(lane?.decision ? { decision: boundedLaneValue(lane.decision, 56) } : {}),
|
|
1396
|
+
...(lane?.claims?.length ? { claims: boundedLaneValue(lane.claims.join(", "), 56) } : {}),
|
|
1397
|
+
...(lane?.expectedOutput ? { expectedOutput: boundedLaneValue(lane.expectedOutput, 56) } : {}),
|
|
1398
|
+
...(label ? { label } : {}),
|
|
1399
|
+
...(toolCounts.length ? { toolUses: toolCounts.reduce((sum, value) => sum + value, 0) } : {}),
|
|
1400
|
+
...(durations.length ? { durationMs: Math.max(...durations) } : {}),
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function compactWorkflowFallbackState(job: AsyncJobState): WorkflowChecklistState {
|
|
1405
|
+
if (job.activityState === "needs_attention" || job.timedOut || job.toolBudgetBlocked || job.turnBudgetExceeded) return "blocked";
|
|
1406
|
+
switch (job.status) {
|
|
1407
|
+
case "complete": return "complete";
|
|
1408
|
+
case "failed": return "failed";
|
|
1409
|
+
case "paused": return "paused";
|
|
1410
|
+
case "stopped": return "stopped";
|
|
1411
|
+
case "partial":
|
|
1412
|
+
case "rejected": return "blocked";
|
|
1413
|
+
default: return "queued";
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/** Flatten the loaded checklist into one compact row per declared lane. */
|
|
1418
|
+
function compactWorkflowLaneRows(job: AsyncJobState, checklist: WorkflowChecklistProjection | undefined): CompactWorkflowLaneRow[] {
|
|
1419
|
+
const groups = new Map<string, WorkflowChecklistItem[]>();
|
|
1420
|
+
for (const item of checklist?.phases.flatMap((phase) => phase.items) ?? []) {
|
|
1421
|
+
const key = item.preflight?.key ?? item.key;
|
|
1422
|
+
const group = groups.get(key);
|
|
1423
|
+
if (group) group.push(item);
|
|
1424
|
+
else groups.set(key, [item]);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const rows: CompactWorkflowLaneRow[] = [];
|
|
1428
|
+
const preflightKeys = new Set(job.preflight?.lanes.map((lane) => lane.key) ?? []);
|
|
1429
|
+
if (job.preflight?.lanes.length) {
|
|
1430
|
+
for (const lane of job.preflight.lanes) {
|
|
1431
|
+
rows.push(compactWorkflowLaneRow(lane.key, groups.get(lane.key) ?? [], lane, compactWorkflowFallbackState(job)));
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
for (const [key, items] of groups) {
|
|
1436
|
+
if (preflightKeys.has(key)) continue;
|
|
1437
|
+
rows.push(compactWorkflowLaneRow(key, items));
|
|
1438
|
+
}
|
|
1439
|
+
return rows;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function compactWorkflowLaneCounts(rows: readonly CompactWorkflowLaneRow[]): CompactWorkflowLaneCounts {
|
|
1443
|
+
const counts: CompactWorkflowLaneCounts = { total: rows.length, done: 0, active: 0, queued: 0, blocked: 0, failed: 0, paused: 0, stopped: 0 };
|
|
1444
|
+
for (const row of rows) {
|
|
1445
|
+
switch (row.state) {
|
|
1446
|
+
case "complete": counts.done++; break;
|
|
1447
|
+
case "running": counts.active++; break;
|
|
1448
|
+
case "queued": counts.queued++; break;
|
|
1449
|
+
case "blocked": counts.blocked++; break;
|
|
1450
|
+
case "failed": counts.failed++; break;
|
|
1451
|
+
case "paused": counts.paused++; break;
|
|
1452
|
+
case "stopped": counts.stopped++; break;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return counts;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function compactWorkflowLaneLine(row: CompactWorkflowLaneRow, theme: Theme, indent: string, frame?: number): string {
|
|
1459
|
+
const owner = row.agent && row.mode ? `${row.agent}/${row.mode}` : row.agent ?? row.mode;
|
|
1460
|
+
const state = row.state === "complete" ? undefined : row.state === "running" ? "active" : row.state;
|
|
1461
|
+
const intent = [
|
|
1462
|
+
row.decision,
|
|
1463
|
+
row.claims,
|
|
1464
|
+
row.expectedOutput,
|
|
1465
|
+
!row.mode && row.label ? row.label : undefined,
|
|
1466
|
+
].filter((value): value is string => Boolean(value));
|
|
1467
|
+
return `${indent}${workflowChecklistGlyph({ state: row.state, durationMs: row.durationMs, toolCount: row.toolUses }, theme, frame)} ${theme.bold(row.key)}${owner ? ` ${theme.fg("dim", `· ${owner}`)}` : ""}${state ? ` ${theme.fg("dim", `· ${state}`)}` : ""}${intent.length ? ` ${theme.fg("dim", `· ${intent.join(" · ")}`)}` : ""}`;
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
function compactWorkflowShortId(job: AsyncJobState): string {
|
|
1471
|
+
return job.asyncId.slice(0, 8);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function compactWorkflowDisplayLabel(job: AsyncJobState): string {
|
|
1475
|
+
return boundedLaneValue(job.description, 64) ?? compactWorkflowShortId(job);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
function compactWorkflowStats(job: AsyncJobState, rows: readonly CompactWorkflowLaneRow[], theme: Theme, projection: WorkflowWidgetProjection): string {
|
|
1479
|
+
const counts = compactWorkflowLaneCounts(rows);
|
|
1480
|
+
const total = counts.total || projection.stageProgress?.total || job.stepsTotal || job.agents?.length || 0;
|
|
1481
|
+
const progress = [
|
|
1482
|
+
total > 0 ? `${counts.done}/${total} done` : undefined,
|
|
1483
|
+
counts.active ? `${counts.active} active` : undefined,
|
|
1484
|
+
counts.queued ? `${counts.queued} queued` : undefined,
|
|
1485
|
+
counts.blocked ? `${counts.blocked} blocked` : undefined,
|
|
1486
|
+
counts.failed ? `${counts.failed} failed` : undefined,
|
|
1487
|
+
counts.paused ? `${counts.paused} paused` : undefined,
|
|
1488
|
+
counts.stopped ? `${counts.stopped} stopped` : undefined,
|
|
1489
|
+
].filter((value): value is string => Boolean(value));
|
|
1490
|
+
const rowToolUses = rows.map((row) => row.toolUses).filter((value): value is number => value !== undefined);
|
|
1491
|
+
const toolUses = job.toolCount ?? (rowToolUses.length ? rowToolUses.reduce((sum, value) => sum + value, 0) : undefined);
|
|
1492
|
+
const rowDuration = rows.map((row) => row.durationMs).filter((value): value is number => value !== undefined);
|
|
1493
|
+
const durationMs = job.startedAt !== undefined && job.updatedAt !== undefined
|
|
1494
|
+
? Math.max(0, job.updatedAt - job.startedAt)
|
|
1495
|
+
: rowDuration.length ? Math.max(...rowDuration) : undefined;
|
|
1496
|
+
return statJoin(theme, [
|
|
1497
|
+
`id: ${compactWorkflowShortId(job)}`,
|
|
1498
|
+
...progress,
|
|
1499
|
+
toolUses !== undefined ? formatToolUseStat(toolUses) : undefined,
|
|
1500
|
+
durationMs !== undefined ? formatDuration(durationMs) : undefined,
|
|
1501
|
+
].filter((value): value is string => Boolean(value)));
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
interface CompactWorkflowBottleneck {
|
|
1505
|
+
text: string;
|
|
1506
|
+
tone: "warning" | "error";
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function compactWorkflowBottleneck(rows: readonly CompactWorkflowLaneRow[], job: AsyncJobState): CompactWorkflowBottleneck | undefined {
|
|
1510
|
+
let row: CompactWorkflowLaneRow | undefined;
|
|
1511
|
+
for (const candidate of rows) {
|
|
1512
|
+
if (candidate.state !== "blocked" && candidate.state !== "failed" && candidate.state !== "paused" && candidate.state !== "stopped") continue;
|
|
1513
|
+
if (!row || workflowChecklistItemPriority(candidate.state) < workflowChecklistItemPriority(row.state)) row = candidate;
|
|
1514
|
+
}
|
|
1515
|
+
if (row) return { text: `${row.key} · ${row.state}`, tone: row.state === "blocked" || row.state === "failed" ? "error" : "warning" };
|
|
1516
|
+
if (job.activityState === "needs_attention" || job.timedOut || job.toolBudgetBlocked || job.turnBudgetExceeded) return { text: `workflow · ${job.activityState === "needs_attention" ? "needs attention" : "blocked"}`, tone: job.activityState === "needs_attention" ? "warning" : "error" };
|
|
1517
|
+
if (job.status === "failed" || job.status === "partial" || job.status === "rejected") return { text: `workflow · ${job.status}`, tone: "error" };
|
|
1518
|
+
if (job.status === "paused" || job.status === "stopped") return { text: `workflow · ${job.status}`, tone: "warning" };
|
|
1519
|
+
return undefined;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function compactWorkflowHeaderLine(job: AsyncJobState, theme: Theme, width?: number): string {
|
|
1523
|
+
const label = compactWorkflowDisplayLabel(job);
|
|
1524
|
+
const full = `${theme.fg("toolTitle", themeBold(theme, `async workflow: ${label}`))} ${theme.fg("dim", "─ background")}`;
|
|
1525
|
+
if (width === undefined || visibleWidth(full) <= width) return full;
|
|
1526
|
+
return `${theme.fg("toolTitle", themeBold(theme, `async workflow ${label}`))} ${theme.fg("dim", "· background")}`;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function compactWorkflowWidgetBodyLines(job: AsyncJobState, theme: Theme, frame: number | undefined, projection: WorkflowWidgetProjection): string[] {
|
|
1530
|
+
const rows = compactWorkflowLaneRows(job, projection.checklist);
|
|
1531
|
+
const lines = [` ${compactWorkflowStats(job, rows, theme, projection)}`];
|
|
1532
|
+
if (rows.length) {
|
|
1533
|
+
for (const row of rows) lines.push(compactWorkflowLaneLine(row, theme, " ", frame));
|
|
1534
|
+
} else {
|
|
1535
|
+
lines.push(` ${theme.fg("dim", "◦ waiting for workflow lanes")}`);
|
|
1536
|
+
}
|
|
1537
|
+
const bottleneck = compactWorkflowBottleneck(rows, job);
|
|
1538
|
+
if (bottleneck) lines.push(` ${theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`)}`);
|
|
1539
|
+
if (job.status === "running" || rows.some((row) => row.state === "running")) lines.push(` ${theme.fg("accent", workflowDetailHintText())}`);
|
|
1540
|
+
return lines;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1343
1543
|
function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number], snapshotNow?: number): string {
|
|
1344
1544
|
const facts: string[] = [];
|
|
1345
1545
|
if (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);
|
|
@@ -2154,19 +2354,13 @@ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string):
|
|
|
2154
2354
|
|
|
2155
2355
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
|
|
2156
2356
|
const { steps } = projection;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
const lines = [
|
|
2160
|
-
...workflowPreflightLines(job, false),
|
|
2161
|
-
...workflowChecklistWidgetLines(projection.checklist, theme, " ", false, frame, false),
|
|
2162
|
-
];
|
|
2163
|
-
if (job.status === "running" || projection.checklist!.running > 0) lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
|
|
2164
|
-
return lines;
|
|
2357
|
+
if (!expanded && job.mode === "workflow") {
|
|
2358
|
+
return compactWorkflowWidgetBodyLines(job, theme, frame, projection);
|
|
2165
2359
|
}
|
|
2166
2360
|
if (!steps.length) {
|
|
2167
2361
|
const lane = projectAsyncLane(job, laneStepForJob(job, steps));
|
|
2168
2362
|
return [
|
|
2169
|
-
...workflowPreflightLines(job
|
|
2363
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2170
2364
|
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2171
2365
|
...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
|
|
2172
2366
|
...hostStepWidgetLines(job, theme, " "),
|
|
@@ -2176,7 +2370,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2176
2370
|
}
|
|
2177
2371
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
2178
2372
|
const lines: string[] = [
|
|
2179
|
-
...workflowPreflightLines(job
|
|
2373
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2180
2374
|
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2181
2375
|
];
|
|
2182
2376
|
const group = activeParallelWidgetGroup(job);
|
|
@@ -2210,6 +2404,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2210
2404
|
}
|
|
2211
2405
|
|
|
2212
2406
|
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
|
|
2407
|
+
if (!expanded && job.mode === "workflow") return [compactWorkflowHeaderLine(job, theme, width), ...compactWorkflowWidgetBodyLines(job, theme, frame, projection)].map((line) => truncLine(line, width));
|
|
2213
2408
|
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2214
2409
|
const count = job.mode === "workflow"
|
|
2215
2410
|
? projection.checklist?.total ?? projection.stageProgress?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length
|
|
@@ -2386,11 +2581,17 @@ function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: numbe
|
|
|
2386
2581
|
}
|
|
2387
2582
|
|
|
2388
2583
|
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string {
|
|
2389
|
-
const
|
|
2390
|
-
const
|
|
2391
|
-
const
|
|
2584
|
+
const compactWorkflow = job.mode === "workflow";
|
|
2585
|
+
const compactRows = compactWorkflow ? compactWorkflowLaneRows(job, projection.checklist) : undefined;
|
|
2586
|
+
const stats = compactWorkflow ? compactWorkflowStats(job, compactRows ?? [], theme, projection) : widgetStats(job, theme, projection, true);
|
|
2587
|
+
if (compactWorkflow) {
|
|
2588
|
+
const bottleneck = compactWorkflowBottleneck(compactRows ?? [], job);
|
|
2589
|
+
const suffix = [stats, bottleneck ? theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`) : undefined].filter((value): value is string => Boolean(value)).join(` ${theme.fg("dim", "·")} `);
|
|
2590
|
+
return truncLine(` ${widgetStatusGlyph(job, theme, frame)} ${compactWorkflowHeaderLine(job, theme, Math.max(0, width - 4))}${suffix ? ` ${theme.fg("dim", "·")} ${suffix}` : ""}`, width);
|
|
2591
|
+
}
|
|
2592
|
+
const activity = widgetActivity(job);
|
|
2392
2593
|
const status = job.status === "complete" ? "done" : job.status;
|
|
2393
|
-
const lane =
|
|
2594
|
+
const lane = projectAsyncLane(job, laneStepForJob(job, projection.steps));
|
|
2394
2595
|
const laneSummary = lane
|
|
2395
2596
|
? [lane.label ?? lane.role, lane.phase ? `phase:${lane.phase}` : undefined, lane.output ? `out:${lane.output}` : undefined, lane.workspace ? `workspace:${lane.workspace}` : `ref:${lane.ref}`].filter(Boolean).join(" · ")
|
|
2396
2597
|
: "";
|
|
@@ -2557,12 +2758,11 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
|
|
|
2557
2758
|
let slots = MAX_WIDGET_JOBS;
|
|
2558
2759
|
const appendJob = (job: AsyncJobState): void => {
|
|
2559
2760
|
const projection = projectionFor(job);
|
|
2560
|
-
const
|
|
2561
|
-
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2562
|
-
const details =
|
|
2761
|
+
const compactWorkflow = !expanded && job.mode === "workflow";
|
|
2762
|
+
const stats = compactWorkflow ? "" : widgetStats(job, theme, projection, !expanded);
|
|
2763
|
+
const details = compactWorkflow
|
|
2563
2764
|
? [
|
|
2564
|
-
...
|
|
2565
|
-
...(job.status === "running" || projection.checklist!.running > 0 ? [` ${theme.fg("accent", liveDetailHintText())}`] : []),
|
|
2765
|
+
...compactWorkflowWidgetBodyLines(job, theme, frame, projection),
|
|
2566
2766
|
]
|
|
2567
2767
|
: [
|
|
2568
2768
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
@@ -2571,7 +2771,9 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
|
|
|
2571
2771
|
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
2572
2772
|
];
|
|
2573
2773
|
items.push([
|
|
2574
|
-
|
|
2774
|
+
compactWorkflow
|
|
2775
|
+
? compactWorkflowHeaderLine(job, theme, width)
|
|
2776
|
+
: `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
2575
2777
|
...details,
|
|
2576
2778
|
]);
|
|
2577
2779
|
};
|
|
@@ -3,7 +3,6 @@ import { SUBAGENT_WATCHDOG_WARNING_TYPE, type ResolvedWatchdogConfig, type Watch
|
|
|
3
3
|
|
|
4
4
|
export const CHILD_WATCHDOG_WARNING_LIMIT = 20;
|
|
5
5
|
|
|
6
|
-
export const CHILD_WATCHDOG_CONFIG_ENV = "PI_SUBAGENT_WATCHDOG_CHILD_CONFIG";
|
|
7
6
|
export const CHILD_WATCHDOG_STATUS_EVENT = "subagent.watchdog.status";
|
|
8
7
|
|
|
9
8
|
export const CHILD_WATCHDOG_PHASES = ["idle", "reviewing", "stale", "failed"] as const;
|
|
@@ -5,11 +5,10 @@ import { createMainWatchdogReview } from "./review.ts";
|
|
|
5
5
|
import { DEFAULT_WATCHDOG_CONFIG } from "./settings.ts";
|
|
6
6
|
import { createWatchdogWarningMessage } from "./warning-format.ts";
|
|
7
7
|
import {
|
|
8
|
-
CHILD_WATCHDOG_CONFIG_ENV,
|
|
9
8
|
CHILD_WATCHDOG_STATUS_EVENT,
|
|
10
|
-
decodeChildWatchdogConfig,
|
|
11
9
|
type ChildWatchdogConfig,
|
|
12
10
|
type ChildWatchdogPhase,
|
|
11
|
+
type ChildWatchdogStatusEvent,
|
|
13
12
|
} from "./child-status.ts";
|
|
14
13
|
import type { ResolvedWatchdogConfig, WatchdogWarningDetails } from "./types.ts";
|
|
15
14
|
|
|
@@ -43,17 +42,18 @@ function childWarningDetails(details: WatchdogWarningDetails, config: ChildWatch
|
|
|
43
42
|
};
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Register the child-side watchdog. Status events go to the sink the hosting
|
|
47
|
+
* process passed in the child runtime config; the host folds them into the
|
|
48
|
+
* child's event stream.
|
|
49
|
+
*/
|
|
50
|
+
export function registerChildWatchdog(
|
|
51
|
+
pi: ExtensionAPI,
|
|
52
|
+
childConfig: ChildWatchdogConfig | undefined,
|
|
53
|
+
writeStatus: ((event: ChildWatchdogStatusEvent) => void) | undefined,
|
|
54
|
+
): MainWatchdogRuntime | undefined {
|
|
56
55
|
if (!childConfig) return undefined;
|
|
56
|
+
if (!writeStatus) throw new Error("Child watchdog status sink is missing; the host must pass ChildRuntimeConfig.watchdogStatus.");
|
|
57
57
|
let currentContext: ExtensionContext | undefined;
|
|
58
58
|
let diffBaseline: WatchdogDiffBaseline | undefined;
|
|
59
59
|
let seq = 0;
|