pi-subagents 0.63.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 +57 -1
- 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 +92 -114
- 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 +6 -5
- 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 +46 -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/notify.ts +41 -2
- 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 +203 -898
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +196 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/subagent-executor.ts +121 -58
- package/src/runs/foreground/workflow-foreground-steering.ts +24 -98
- package/src/runs/shared/abort-recovery.ts +3 -3
- package/src/runs/shared/acceptance.ts +10 -0
- package/src/runs/shared/async-status-projection.ts +11 -43
- 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 +6 -2
- 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 +64 -30
- 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 +238 -31
- package/src/watchdog/child-status.ts +54 -34
- package/src/watchdog/diff-tool.ts +77 -0
- package/src/watchdog/emission-guard.ts +5 -3
- package/src/watchdog/guidance.ts +20 -0
- package/src/watchdog/register-child.ts +28 -25
- package/src/watchdog/register-main.ts +10 -9
- package/src/watchdog/render.ts +4 -5
- package/src/watchdog/review.ts +15 -4
- package/src/watchdog/rules.ts +70 -0
- package/src/watchdog/runtime.ts +75 -92
- package/src/watchdog/scope.ts +0 -11
- package/src/watchdog/settings.ts +48 -104
- package/src/watchdog/types.ts +18 -32
- package/src/watchdog/warning-format.ts +0 -1
- package/src/workflows/chat-progress.ts +3 -2
- package/src/workflows/scripted-workflow.ts +48 -1
- package/src/workflows/workflow-checklist.ts +10 -12
- package/src/workflows/workflow-preflight.ts +28 -1
- package/src/runs/shared/child-protocol.ts +0 -415
- package/src/runs/shared/pi-args.ts +0 -1062
- 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
|
@@ -15,6 +15,7 @@ import type { ThinkingLevel } from "./model-info.ts";
|
|
|
15
15
|
import type { GlobalMissionIndexRecord, MissionRecord, MissionStoreConfig } from "../missions/types.ts";
|
|
16
16
|
import type { ExtensionBindings } from "../runs/shared/extension-bindings.ts";
|
|
17
17
|
import type { WorkflowChildPermitContext } from "./workflow-child-permit.ts";
|
|
18
|
+
import type { WatchdogWarningDetails } from "../watchdog/types.ts";
|
|
18
19
|
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// Basic Types
|
|
@@ -368,6 +369,7 @@ export interface ControlEvent {
|
|
|
368
369
|
tokens?: number;
|
|
369
370
|
toolCount?: number;
|
|
370
371
|
currentTool?: string;
|
|
372
|
+
toolCallId?: string;
|
|
371
373
|
currentToolDurationMs?: number;
|
|
372
374
|
currentPath?: string;
|
|
373
375
|
elapsedMs?: number;
|
|
@@ -785,6 +787,8 @@ export interface SteeringRecoveryDescriptor {
|
|
|
785
787
|
agentContract?: AgentContract;
|
|
786
788
|
agent: string;
|
|
787
789
|
sessionFile?: string;
|
|
790
|
+
/** Git ref used to allocate managed worktrees for this run. */
|
|
791
|
+
baseRef?: string;
|
|
788
792
|
cwd: string;
|
|
789
793
|
model?: string;
|
|
790
794
|
modelProvider?: string;
|
|
@@ -894,13 +898,19 @@ export interface SubagentResultIntercomPayload {
|
|
|
894
898
|
// Progress Tracking
|
|
895
899
|
// ============================================================================
|
|
896
900
|
|
|
901
|
+
export interface ChildWatchdogWarningSummary extends Pick<WatchdogWarningDetails, "severity" | "category" | "summary" | "evidence" | "recommendedAction" | "displayedAt"> {
|
|
902
|
+
/** True when a later assistant turn in the child followed the warning. */
|
|
903
|
+
addressed: boolean;
|
|
904
|
+
stalemate: boolean;
|
|
905
|
+
}
|
|
906
|
+
|
|
897
907
|
export interface ChildWatchdogProgress {
|
|
898
|
-
phase: "idle" | "reviewing" | "
|
|
908
|
+
phase: "idle" | "reviewing" | "stale" | "failed";
|
|
899
909
|
seq: number;
|
|
900
910
|
lastUpdate: number;
|
|
901
|
-
followUpPending: boolean;
|
|
902
911
|
reason?: string;
|
|
903
912
|
timedOut?: boolean;
|
|
913
|
+
warnings?: ChildWatchdogWarningSummary[];
|
|
904
914
|
}
|
|
905
915
|
|
|
906
916
|
export interface AgentProgress {
|
|
@@ -1152,15 +1162,6 @@ export interface AcceptanceLedger {
|
|
|
1152
1162
|
};
|
|
1153
1163
|
}
|
|
1154
1164
|
|
|
1155
|
-
export interface ProtocolOutputLimit {
|
|
1156
|
-
code: "protocol_output_limit";
|
|
1157
|
-
stream: "stdout" | "stderr";
|
|
1158
|
-
limitBytes: number;
|
|
1159
|
-
observedBytes: number;
|
|
1160
|
-
diagnosticPrefix: string;
|
|
1161
|
-
diagnosticTail: string;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
1165
|
export interface LaunchResolvedChildExtensionsV1 {
|
|
1165
1166
|
version: 1;
|
|
1166
1167
|
/** This is parent-resolved launch intent, not child-runtime acknowledgement that extensions loaded. */
|
|
@@ -1255,7 +1256,6 @@ export interface SingleResult {
|
|
|
1255
1256
|
* a signal to reduce input size or re-decompose the task.
|
|
1256
1257
|
*/
|
|
1257
1258
|
contextOverflow?: boolean;
|
|
1258
|
-
protocolError?: ProtocolOutputLimit;
|
|
1259
1259
|
sessionFile?: string;
|
|
1260
1260
|
skills?: string[];
|
|
1261
1261
|
skillsWarning?: string;
|
|
@@ -1362,7 +1362,7 @@ export interface AgentCapabilityRow {
|
|
|
1362
1362
|
executable: boolean;
|
|
1363
1363
|
restrictionSources?: string[];
|
|
1364
1364
|
aliases?: string[];
|
|
1365
|
-
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"] };
|
|
1366
1366
|
tools: { ambient: boolean; names: string[]; excludeTools?: string[]; mcpDirectTools: string[]; mutationTools?: string[] };
|
|
1367
1367
|
model?: { value?: string; fallbackModels?: string[]; thinking?: string | false };
|
|
1368
1368
|
execution?: { defaultAsync?: boolean; timeoutMs?: number };
|
|
@@ -2100,6 +2100,18 @@ export interface ForegroundChildControl {
|
|
|
2100
2100
|
toolCount?: number;
|
|
2101
2101
|
interrupt?: () => boolean;
|
|
2102
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;
|
|
2103
2115
|
}
|
|
2104
2116
|
|
|
2105
2117
|
export interface ForegroundRunControl {
|
|
@@ -2108,8 +2120,6 @@ export interface ForegroundRunControl {
|
|
|
2108
2120
|
parentWorkflowRunId?: string;
|
|
2109
2121
|
/** Stable workflow lane key for this live foreground child. */
|
|
2110
2122
|
workflowKey?: string;
|
|
2111
|
-
/** Private control root used to steer a live workflow-owned child. */
|
|
2112
|
-
workflowSteeringDir?: string;
|
|
2113
2123
|
/** Originating parent session; required for public fleet projection. */
|
|
2114
2124
|
sessionId?: string;
|
|
2115
2125
|
mode: SubagentRunMode;
|
|
@@ -2151,6 +2161,7 @@ export interface ForegroundRunControl {
|
|
|
2151
2161
|
nestedChildren?: NestedRunSummary[];
|
|
2152
2162
|
interrupt?: () => boolean;
|
|
2153
2163
|
detach?: () => boolean;
|
|
2164
|
+
steer?: ForegroundChildControl["steer"];
|
|
2154
2165
|
}
|
|
2155
2166
|
|
|
2156
2167
|
export interface WaitSubscriptionRecord {
|
|
@@ -2311,17 +2322,25 @@ export interface SubagentChildStatusEvent {
|
|
|
2311
2322
|
// Execution Options
|
|
2312
2323
|
// ============================================================================
|
|
2313
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
|
+
|
|
2314
2331
|
export interface RunSyncOptions {
|
|
2315
2332
|
/** Exact discovery provenance for an unknown-agent error; omission uses defensive fallback discovery. */
|
|
2316
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;
|
|
2317
2340
|
/** Opt-in global permission rules; missing tools remain allowed. */
|
|
2318
2341
|
permissions?: import("../runs/shared/permissions.ts").PermissionConfig;
|
|
2319
2342
|
/** Session id of the direct parent session for permission-system ask forwarding. */
|
|
2320
2343
|
parentSessionId?: string;
|
|
2321
|
-
/** Private prompt-runtime steering transport for workflow-owned foreground children. */
|
|
2322
|
-
steerInboxDir?: string;
|
|
2323
|
-
steerCapabilityPath?: string;
|
|
2324
|
-
steerAckDir?: string;
|
|
2325
2344
|
/** Resolved launch context for this child. */
|
|
2326
2345
|
context?: "fresh" | "fork";
|
|
2327
2346
|
cwd?: string;
|
|
@@ -2403,6 +2422,7 @@ export interface RunSyncOptions {
|
|
|
2403
2422
|
schemaPath: string;
|
|
2404
2423
|
outputPath: string;
|
|
2405
2424
|
acceptanceReportPath?: string;
|
|
2425
|
+
acceptanceReportRequired?: boolean;
|
|
2406
2426
|
};
|
|
2407
2427
|
agentContract?: AgentContract;
|
|
2408
2428
|
acceptance?: AcceptanceInput;
|
|
@@ -2758,8 +2778,17 @@ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
|
|
|
2758
2778
|
return normalizeNonNegativeInteger(value);
|
|
2759
2779
|
}
|
|
2760
2780
|
|
|
2761
|
-
|
|
2762
|
-
|
|
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])
|
|
2763
2792
|
?? normalizeMaxSubagentDepth(configMaxDepth)
|
|
2764
2793
|
?? DEFAULT_SUBAGENT_MAX_DEPTH;
|
|
2765
2794
|
}
|
|
@@ -2770,19 +2799,24 @@ export function resolveChildMaxSubagentDepth(parentMaxDepth: number, agentMaxDep
|
|
|
2770
2799
|
return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
|
|
2771
2800
|
}
|
|
2772
2801
|
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
const
|
|
2776
|
-
|
|
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;
|
|
2777
2812
|
return { blocked, depth, maxDepth };
|
|
2778
2813
|
}
|
|
2779
2814
|
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
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> {
|
|
2783
2817
|
return {
|
|
2784
|
-
|
|
2785
|
-
|
|
2818
|
+
depth: resolveCurrentSubagentDepth(runtime) + 1,
|
|
2819
|
+
maxDepth: normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth(undefined, runtime),
|
|
2786
2820
|
};
|
|
2787
2821
|
}
|
|
2788
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
|
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
8
8
|
import { getMarkdownTheme, keyText, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
10
|
+
import { unresolvedChildWatchdogBlockers } from "../watchdog/child-status.ts";
|
|
10
11
|
import {
|
|
11
12
|
type AgentProgress,
|
|
12
13
|
type AsyncJobState,
|
|
@@ -93,6 +94,10 @@ export function liveDetailHintText(): string {
|
|
|
93
94
|
return `Press ${liveDetailKeyText()} for live detail · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
function workflowDetailHintText(): string {
|
|
98
|
+
return `Press ${liveDetailKeyText()} for details · ${formatShortcutLabel(FLEET_OPEN_SHORTCUT)} Fleet`;
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
function foregroundSingleHintText(shortcut?: string): string {
|
|
97
102
|
if (!shortcut) return liveDetailHintText();
|
|
98
103
|
const label = formatShortcutLabel(shortcut);
|
|
@@ -360,6 +365,7 @@ function workflowStepPriority(step: AsyncJobStep, currentNodeId?: string): numbe
|
|
|
360
365
|
|| step.turnBudgetExceeded === true
|
|
361
366
|
|| step.activityState === "needs_attention"
|
|
362
367
|
|| step.watchdog?.phase === "stale"
|
|
368
|
+
|| unresolvedChildWatchdogBlockers(step.watchdog).length > 0
|
|
363
369
|
|| gate !== undefined
|
|
364
370
|
) return 1;
|
|
365
371
|
if (step.status === "pending") return 2;
|
|
@@ -505,6 +511,7 @@ function laneGate(step: AsyncJobStep | undefined): string | undefined {
|
|
|
505
511
|
}
|
|
506
512
|
|
|
507
513
|
function laneNextAction(state: AsyncLaneProjection["state"], step: AsyncJobStep | undefined, output: string | undefined, gate: string | undefined): string | undefined {
|
|
514
|
+
if (unresolvedChildWatchdogBlockers(step?.watchdog).length > 0) return "resolve watchdog blockers";
|
|
508
515
|
if (step?.watchdog?.phase === "stale") return "inspect stale state";
|
|
509
516
|
if (step?.toolBudgetBlocked === true || step?.turnBudgetExceeded === true) return "inspect blocked state";
|
|
510
517
|
if (gate === "review blockers") return "resolve review blockers";
|
|
@@ -543,6 +550,7 @@ export function projectAsyncLane(job: AsyncJobState, ...args: [selectedStep?: As
|
|
|
543
550
|
selectedStep?.activityState === "active_long_running" ? "long-running" : undefined,
|
|
544
551
|
selectedStep?.activityState === "needs_attention" ? "attention" : undefined,
|
|
545
552
|
selectedStep?.watchdog?.phase === "stale" ? "stale" : undefined,
|
|
553
|
+
unresolvedChildWatchdogBlockers(selectedStep?.watchdog).length > 0 ? `wd:${unresolvedChildWatchdogBlockers(selectedStep?.watchdog).length}` : undefined,
|
|
546
554
|
selectedStep?.toolBudgetBlocked === true || selectedStep?.turnBudgetExceeded === true ? "blocked" : undefined,
|
|
547
555
|
].filter((chip): chip is string => Boolean(chip));
|
|
548
556
|
const state = isTerminalLaneState(job.status) ? job.status : selectedStep?.status ?? job.status;
|
|
@@ -605,15 +613,8 @@ function widgetLaneDetailLines(job: AsyncJobState, theme: Theme, projection?: Wo
|
|
|
605
613
|
return lane ? formatLaneProjectionLines(lane, theme, " ") : [];
|
|
606
614
|
}
|
|
607
615
|
|
|
608
|
-
function workflowPreflightLines(job: AsyncJobState
|
|
616
|
+
function workflowPreflightLines(job: AsyncJobState): string[] {
|
|
609
617
|
if (job.mode !== "workflow" || !job.preflight) return [];
|
|
610
|
-
if (!expanded) {
|
|
611
|
-
const warning = formatWorkflowPreflightWarningSummary(job.workflow?.preflightWarnings, { indent: " ", hint: "expand for debug" });
|
|
612
|
-
return [
|
|
613
|
-
formatWorkflowPreflightPlanSummary(job.preflight, { indent: " " }),
|
|
614
|
-
...(warning ? [warning] : []),
|
|
615
|
-
];
|
|
616
|
-
}
|
|
617
618
|
return [
|
|
618
619
|
...formatWorkflowPreflight(job.preflight, { indent: " " }).split("\n"),
|
|
619
620
|
...(job.workflow?.preflightWarnings ? formatWorkflowPreflightWarnings(job.workflow.preflightWarnings, { indent: " " }).split("\n") : []),
|
|
@@ -1001,6 +1002,7 @@ function widgetStepRenderKey(step: AsyncJobStep, index: number, expanded = false
|
|
|
1001
1002
|
step.execution?.stopped,
|
|
1002
1003
|
step.execution?.detached,
|
|
1003
1004
|
step.watchdog?.phase,
|
|
1005
|
+
unresolvedChildWatchdogBlockers(step.watchdog).length,
|
|
1004
1006
|
step.error,
|
|
1005
1007
|
expanded ? expandedStepActivityRenderKey(step) : undefined,
|
|
1006
1008
|
nestedRenderKey(step.children, expanded),
|
|
@@ -1053,6 +1055,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1053
1055
|
return JSON.stringify({
|
|
1054
1056
|
asyncDir: job.asyncDir,
|
|
1055
1057
|
status: job.status,
|
|
1058
|
+
description: job.mode === "workflow" ? job.description : undefined,
|
|
1056
1059
|
activityState: job.activityState,
|
|
1057
1060
|
lastActivityAt: job.lastActivityAt,
|
|
1058
1061
|
currentTool: job.currentTool,
|
|
@@ -1070,8 +1073,8 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1070
1073
|
currentNodeId: job.workflowGraph.currentNodeId,
|
|
1071
1074
|
stages: projection.stages.map((node) => [node.id, node.status, node.agent, node.phase, node.label, node.flatIndex, node.outputName, node.structured, node.error]),
|
|
1072
1075
|
} : undefined,
|
|
1073
|
-
preflight: expanded ? job.preflight : job.preflight
|
|
1074
|
-
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,
|
|
1075
1078
|
checklist: projection.checklist ? {
|
|
1076
1079
|
total: projection.checklist.total,
|
|
1077
1080
|
done: projection.checklist.done,
|
|
@@ -1079,7 +1082,7 @@ export function widgetRenderKey(job: AsyncJobState, expanded = false): string {
|
|
|
1079
1082
|
queued: projection.checklist.queued,
|
|
1080
1083
|
blocked: projection.checklist.blocked,
|
|
1081
1084
|
failed: projection.checklist.failed,
|
|
1082
|
-
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])]),
|
|
1083
1086
|
} : undefined,
|
|
1084
1087
|
steps: job.steps?.map((step, index) => widgetStepRenderKey(step, index, expanded)),
|
|
1085
1088
|
nestedChildren: nestedRenderKey(job.nestedChildren, expanded),
|
|
@@ -1335,6 +1338,208 @@ function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | u
|
|
|
1335
1338
|
return lines;
|
|
1336
1339
|
}
|
|
1337
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
|
+
|
|
1338
1543
|
function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number], snapshotNow?: number): string {
|
|
1339
1544
|
const facts: string[] = [];
|
|
1340
1545
|
if (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);
|
|
@@ -2149,19 +2354,13 @@ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string):
|
|
|
2149
2354
|
|
|
2150
2355
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
|
|
2151
2356
|
const { steps } = projection;
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
const lines = [
|
|
2155
|
-
...workflowPreflightLines(job, false),
|
|
2156
|
-
...workflowChecklistWidgetLines(projection.checklist, theme, " ", false, frame, false),
|
|
2157
|
-
];
|
|
2158
|
-
if (job.status === "running" || projection.checklist!.running > 0) lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
|
|
2159
|
-
return lines;
|
|
2357
|
+
if (!expanded && job.mode === "workflow") {
|
|
2358
|
+
return compactWorkflowWidgetBodyLines(job, theme, frame, projection);
|
|
2160
2359
|
}
|
|
2161
2360
|
if (!steps.length) {
|
|
2162
2361
|
const lane = projectAsyncLane(job, laneStepForJob(job, steps));
|
|
2163
2362
|
return [
|
|
2164
|
-
...workflowPreflightLines(job
|
|
2363
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2165
2364
|
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2166
2365
|
...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
|
|
2167
2366
|
...hostStepWidgetLines(job, theme, " "),
|
|
@@ -2171,7 +2370,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2171
2370
|
}
|
|
2172
2371
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
2173
2372
|
const lines: string[] = [
|
|
2174
|
-
...workflowPreflightLines(job
|
|
2373
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2175
2374
|
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2176
2375
|
];
|
|
2177
2376
|
const group = activeParallelWidgetGroup(job);
|
|
@@ -2205,6 +2404,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2205
2404
|
}
|
|
2206
2405
|
|
|
2207
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));
|
|
2208
2408
|
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2209
2409
|
const count = job.mode === "workflow"
|
|
2210
2410
|
? projection.checklist?.total ?? projection.stageProgress?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length
|
|
@@ -2381,11 +2581,17 @@ function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: numbe
|
|
|
2381
2581
|
}
|
|
2382
2582
|
|
|
2383
2583
|
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string {
|
|
2384
|
-
const
|
|
2385
|
-
const
|
|
2386
|
-
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);
|
|
2387
2593
|
const status = job.status === "complete" ? "done" : job.status;
|
|
2388
|
-
const lane =
|
|
2594
|
+
const lane = projectAsyncLane(job, laneStepForJob(job, projection.steps));
|
|
2389
2595
|
const laneSummary = lane
|
|
2390
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(" · ")
|
|
2391
2597
|
: "";
|
|
@@ -2552,12 +2758,11 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
|
|
|
2552
2758
|
let slots = MAX_WIDGET_JOBS;
|
|
2553
2759
|
const appendJob = (job: AsyncJobState): void => {
|
|
2554
2760
|
const projection = projectionFor(job);
|
|
2555
|
-
const
|
|
2556
|
-
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2557
|
-
const details =
|
|
2761
|
+
const compactWorkflow = !expanded && job.mode === "workflow";
|
|
2762
|
+
const stats = compactWorkflow ? "" : widgetStats(job, theme, projection, !expanded);
|
|
2763
|
+
const details = compactWorkflow
|
|
2558
2764
|
? [
|
|
2559
|
-
...
|
|
2560
|
-
...(job.status === "running" || projection.checklist!.running > 0 ? [` ${theme.fg("accent", liveDetailHintText())}`] : []),
|
|
2765
|
+
...compactWorkflowWidgetBodyLines(job, theme, frame, projection),
|
|
2561
2766
|
]
|
|
2562
2767
|
: [
|
|
2563
2768
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
@@ -2566,7 +2771,9 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
|
|
|
2566
2771
|
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
2567
2772
|
];
|
|
2568
2773
|
items.push([
|
|
2569
|
-
|
|
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}` : ""}`,
|
|
2570
2777
|
...details,
|
|
2571
2778
|
]);
|
|
2572
2779
|
};
|