pi-subagents 0.64.0 → 0.65.1
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 +65 -0
- package/README.md +2 -2
- package/agents/reviewer.md +1 -1
- package/agents/scout.md +1 -1
- package/docs/agents.md +19 -17
- package/docs/configuration.md +23 -15
- package/docs/extension-api.md +18 -6
- package/docs/missions.md +2 -0
- package/docs/observability.md +10 -12
- package/docs/tool-reference.md +15 -8
- package/docs/watchdog.md +1 -1
- package/docs/workflows.md +13 -3
- package/package.json +5 -4
- package/runner-server-preload.mjs +13 -0
- package/skills/pi-subagents/SKILL.md +2 -1
- package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/skills/pi-subagents/references/execution-controls.md +7 -3
- package/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
- 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 +7 -4
- package/src/extension/config.ts +4 -2
- package/src/extension/doctor.ts +2 -10
- package/src/extension/fanout-child.ts +9 -11
- package/src/extension/index.ts +58 -7
- package/src/extension/public-execution.ts +14 -0
- package/src/extension/rpc.ts +3 -2
- package/src/extension/schemas.ts +3 -2
- package/src/extension/tool-description.ts +13 -8
- package/src/integrations/pi-web-session-liveness.ts +73 -0
- package/src/intercom/native-supervisor-channel.ts +159 -95
- package/src/intercom/supervisor-ui.ts +244 -0
- package/src/missions/workflow-state.ts +37 -16
- package/src/runs/background/async-execution.ts +51 -25
- package/src/runs/background/async-job-tracker.ts +11 -0
- package/src/runs/background/async-resume.ts +14 -3
- package/src/runs/background/async-retention.ts +9 -0
- package/src/runs/background/control-channel.ts +2 -204
- package/src/runs/background/notify.ts +2 -0
- package/src/runs/background/process-terminal.ts +1 -1
- package/src/runs/background/retained-nested-route-tracker.ts +96 -0
- package/src/runs/background/run-child-session.ts +614 -0
- package/src/runs/background/run-status.ts +0 -1
- package/src/runs/background/runner-aliases.ts +152 -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 -901
- package/src/runs/foreground/async-steering-action.ts +1 -17
- package/src/runs/foreground/execution.ts +204 -378
- package/src/runs/foreground/foreground-control.ts +4 -0
- package/src/runs/foreground/foreground-history.ts +3 -1
- package/src/runs/foreground/prompt-audit.ts +9 -5
- package/src/runs/foreground/subagent-executor.ts +161 -91
- 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 +14 -1
- 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 +337 -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/llm-intent-arbiter.ts +20 -11
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
- package/src/runs/shared/model-exclusions.ts +84 -15
- package/src/runs/shared/model-fallback.ts +78 -6
- package/src/runs/shared/nested-events.ts +32 -48
- 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 +1 -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 +31 -9
- package/src/runs/shared/worktree.ts +192 -40
- package/src/shared/child-session-name.ts +1 -1
- package/src/shared/jsonl-writer.ts +11 -0
- package/src/shared/model-response-aliases.ts +13 -0
- package/src/shared/thinking-ceiling.ts +0 -6
- package/src/shared/types.ts +63 -28
- package/src/shared/utils.ts +6 -4
- package/src/shared/watch-strategy.ts +2 -0
- package/src/slash/slash-commands.ts +0 -6
- package/src/tui/fleet-status.ts +1 -1
- package/src/tui/fleet.ts +0 -1
- package/src/tui/render.ts +254 -41
- package/src/watchdog/child-status.ts +0 -1
- package/src/watchdog/register-child.ts +12 -12
- package/src/workflows/scripted-workflow.ts +80 -7
- package/src/workflows/workflow-checklist.ts +4 -3
- 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/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),
|
|
@@ -1286,7 +1284,7 @@ function workflowChecklistItemPriority(state: WorkflowChecklistState): number {
|
|
|
1286
1284
|
return 6;
|
|
1287
1285
|
}
|
|
1288
1286
|
|
|
1289
|
-
function workflowChecklistItemLine(item: WorkflowChecklistItem, theme: Theme, indent: string, frame
|
|
1287
|
+
function workflowChecklistItemLine(item: WorkflowChecklistItem, theme: Theme, indent: string, frame: number | undefined, includeError: boolean): string {
|
|
1290
1288
|
const identity = [item.label, item.agent && item.agent !== item.label ? item.agent : undefined].filter(Boolean).join(" · ");
|
|
1291
1289
|
const context = contextModeBadge(theme, item.context);
|
|
1292
1290
|
const state = item.state === "complete" ? "" : ` ${theme.fg("dim", `· ${workflowChecklistStateLabel(item.state)}`)}`;
|
|
@@ -1296,15 +1294,26 @@ function workflowChecklistItemLine(item: WorkflowChecklistItem, theme: Theme, in
|
|
|
1296
1294
|
!item.currentTool && item.durationMs !== undefined ? formatDuration(item.durationMs) : undefined,
|
|
1297
1295
|
item.toolCount !== undefined ? `${item.toolCount} tools` : undefined,
|
|
1298
1296
|
item.outputName ? `out:${item.outputName}` : undefined,
|
|
1299
|
-
item.error ? `error:${oneLine(item.error)}` : undefined,
|
|
1297
|
+
includeError && item.error ? `error:${oneLine(item.error)}` : undefined,
|
|
1300
1298
|
].filter(Boolean).join(" · ");
|
|
1301
1299
|
return `${indent}${workflowChecklistGlyph(item, theme, frame)} ${theme.bold(identity || item.key)}${context}${state}${details ? ` ${theme.fg("dim", `· ${details}`)}` : ""}`;
|
|
1302
1300
|
}
|
|
1303
1301
|
|
|
1304
1302
|
const COLLAPSED_WORKFLOW_PHASE_LIMIT = 4;
|
|
1305
1303
|
|
|
1306
|
-
|
|
1304
|
+
interface WorkflowChecklistWidgetOptions {
|
|
1305
|
+
includeSummary?: boolean;
|
|
1306
|
+
limitPhases?: boolean;
|
|
1307
|
+
includeBottleneckOutput?: boolean;
|
|
1308
|
+
includeItemErrors?: boolean;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | undefined, theme: Theme, indent: string, expanded: boolean, frame?: number, options: WorkflowChecklistWidgetOptions = {}): string[] {
|
|
1307
1312
|
if (!checklist?.total) return [];
|
|
1313
|
+
const includeSummary = options.includeSummary ?? true;
|
|
1314
|
+
const limitPhases = options.limitPhases ?? !expanded;
|
|
1315
|
+
const includeBottleneckOutput = options.includeBottleneckOutput ?? expanded;
|
|
1316
|
+
const includeItemErrors = options.includeItemErrors ?? true;
|
|
1308
1317
|
const lines = includeSummary ? [`${indent}${theme.fg("dim", `Checklist ${formatWorkflowChecklistSummary(checklist)}`)}`] : [];
|
|
1309
1318
|
const phases = !limitPhases || checklist.phases.length <= COLLAPSED_WORKFLOW_PHASE_LIMIT
|
|
1310
1319
|
? checklist.phases
|
|
@@ -1328,11 +1337,11 @@ function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | u
|
|
|
1328
1337
|
lines.push(`${indent}${glyph} ${theme.bold(formatWorkflowChecklistPhase(phase))}`);
|
|
1329
1338
|
if (expanded) {
|
|
1330
1339
|
for (const item of [...phase.items].sort((left, right) => workflowChecklistItemPriority(left.state) - workflowChecklistItemPriority(right.state))) {
|
|
1331
|
-
lines.push(workflowChecklistItemLine(item, theme, `${indent} `, frame));
|
|
1340
|
+
lines.push(workflowChecklistItemLine(item, theme, `${indent} `, frame, includeItemErrors || item.kind === "host"));
|
|
1332
1341
|
}
|
|
1333
1342
|
}
|
|
1334
1343
|
}
|
|
1335
|
-
const bottleneck = formatWorkflowChecklistBottleneck(checklist.bottleneck, { includeOutput: includeBottleneckOutput });
|
|
1344
|
+
const bottleneck = formatWorkflowChecklistBottleneck(checklist.bottleneck, { includeOutput: includeBottleneckOutput, includeError: !expanded });
|
|
1336
1345
|
if (bottleneck) {
|
|
1337
1346
|
const tone = checklist.bottleneck?.state === "blocked" || checklist.bottleneck?.state === "failed" ? "error" : checklist.bottleneck?.state === "running" ? "accent" : "warning";
|
|
1338
1347
|
lines.push(`${indent}${theme.fg(tone, `bottleneck · ${bottleneck}`)}`);
|
|
@@ -1340,6 +1349,208 @@ function workflowChecklistWidgetLines(checklist: WorkflowChecklistProjection | u
|
|
|
1340
1349
|
return lines;
|
|
1341
1350
|
}
|
|
1342
1351
|
|
|
1352
|
+
interface CompactWorkflowLaneRow {
|
|
1353
|
+
key: string;
|
|
1354
|
+
state: WorkflowChecklistState;
|
|
1355
|
+
agent?: string;
|
|
1356
|
+
mode?: string;
|
|
1357
|
+
decision?: string;
|
|
1358
|
+
claims?: string;
|
|
1359
|
+
expectedOutput?: string;
|
|
1360
|
+
label?: string;
|
|
1361
|
+
toolUses?: number;
|
|
1362
|
+
durationMs?: number;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
interface CompactWorkflowLaneCounts {
|
|
1366
|
+
total: number;
|
|
1367
|
+
done: number;
|
|
1368
|
+
active: number;
|
|
1369
|
+
queued: number;
|
|
1370
|
+
blocked: number;
|
|
1371
|
+
failed: number;
|
|
1372
|
+
paused: number;
|
|
1373
|
+
stopped: number;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function compactWorkflowLaneState(items: readonly WorkflowChecklistItem[]): WorkflowChecklistState {
|
|
1377
|
+
let state: WorkflowChecklistState | undefined;
|
|
1378
|
+
for (const item of items) {
|
|
1379
|
+
if (state === undefined || workflowChecklistItemPriority(item.state) < workflowChecklistItemPriority(state)) state = item.state;
|
|
1380
|
+
}
|
|
1381
|
+
return state ?? "queued";
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function compactWorkflowLaneOwner(items: readonly WorkflowChecklistItem[]): string | undefined {
|
|
1385
|
+
const owners = [...new Set(items.map((item) => item.agent ?? item.role).filter((value): value is string => Boolean(value?.trim())))];
|
|
1386
|
+
if (owners.length === 0) return undefined;
|
|
1387
|
+
return boundedLaneValue(owners.length > 2 ? `${owners.slice(0, 2).join(", ")} +${owners.length - 2}` : owners.join(", "), 32);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function compactWorkflowLaneLabel(items: readonly WorkflowChecklistItem[], key: string): string | undefined {
|
|
1391
|
+
const labels = [...new Set(items.map((item) => item.label).filter((value): value is string => Boolean(value?.trim() && value.trim() !== key)))];
|
|
1392
|
+
if (labels.length === 0) return undefined;
|
|
1393
|
+
return boundedLaneValue(labels.length > 1 ? `${labels[0]} +${labels.length - 1}` : labels[0], 48);
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function compactWorkflowLaneRow(key: string, items: readonly WorkflowChecklistItem[], lane = items.find((item) => item.preflight)?.preflight, fallbackState: WorkflowChecklistState = "queued"): CompactWorkflowLaneRow {
|
|
1397
|
+
const state = items.length ? compactWorkflowLaneState(items) : fallbackState;
|
|
1398
|
+
const toolCounts = items.map((item) => item.toolCount).filter((value): value is number => value !== undefined);
|
|
1399
|
+
const durations = items.map((item) => item.durationMs).filter((value): value is number => value !== undefined);
|
|
1400
|
+
const label = compactWorkflowLaneLabel(items, key);
|
|
1401
|
+
return {
|
|
1402
|
+
key: boundedLaneValue(key, 40) ?? key,
|
|
1403
|
+
state,
|
|
1404
|
+
agent: compactWorkflowLaneOwner(items),
|
|
1405
|
+
...(lane?.mode ? { mode: lane.mode } : {}),
|
|
1406
|
+
...(lane?.decision ? { decision: boundedLaneValue(lane.decision, 56) } : {}),
|
|
1407
|
+
...(lane?.claims?.length ? { claims: boundedLaneValue(lane.claims.join(", "), 56) } : {}),
|
|
1408
|
+
...(lane?.expectedOutput ? { expectedOutput: boundedLaneValue(lane.expectedOutput, 56) } : {}),
|
|
1409
|
+
...(label ? { label } : {}),
|
|
1410
|
+
...(toolCounts.length ? { toolUses: toolCounts.reduce((sum, value) => sum + value, 0) } : {}),
|
|
1411
|
+
...(durations.length ? { durationMs: Math.max(...durations) } : {}),
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function compactWorkflowFallbackState(job: AsyncJobState): WorkflowChecklistState {
|
|
1416
|
+
if (job.activityState === "needs_attention" || job.timedOut || job.toolBudgetBlocked || job.turnBudgetExceeded) return "blocked";
|
|
1417
|
+
switch (job.status) {
|
|
1418
|
+
case "complete": return "complete";
|
|
1419
|
+
case "failed": return "failed";
|
|
1420
|
+
case "paused": return "paused";
|
|
1421
|
+
case "stopped": return "stopped";
|
|
1422
|
+
case "partial":
|
|
1423
|
+
case "rejected": return "blocked";
|
|
1424
|
+
default: return "queued";
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
/** Flatten the loaded checklist into one compact row per declared lane. */
|
|
1429
|
+
function compactWorkflowLaneRows(job: AsyncJobState, checklist: WorkflowChecklistProjection | undefined): CompactWorkflowLaneRow[] {
|
|
1430
|
+
const groups = new Map<string, WorkflowChecklistItem[]>();
|
|
1431
|
+
for (const item of checklist?.phases.flatMap((phase) => phase.items) ?? []) {
|
|
1432
|
+
const key = item.preflight?.key ?? item.key;
|
|
1433
|
+
const group = groups.get(key);
|
|
1434
|
+
if (group) group.push(item);
|
|
1435
|
+
else groups.set(key, [item]);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const rows: CompactWorkflowLaneRow[] = [];
|
|
1439
|
+
const preflightKeys = new Set(job.preflight?.lanes.map((lane) => lane.key) ?? []);
|
|
1440
|
+
if (job.preflight?.lanes.length) {
|
|
1441
|
+
for (const lane of job.preflight.lanes) {
|
|
1442
|
+
rows.push(compactWorkflowLaneRow(lane.key, groups.get(lane.key) ?? [], lane, compactWorkflowFallbackState(job)));
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
for (const [key, items] of groups) {
|
|
1447
|
+
if (preflightKeys.has(key)) continue;
|
|
1448
|
+
rows.push(compactWorkflowLaneRow(key, items));
|
|
1449
|
+
}
|
|
1450
|
+
return rows;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
function compactWorkflowLaneCounts(rows: readonly CompactWorkflowLaneRow[]): CompactWorkflowLaneCounts {
|
|
1454
|
+
const counts: CompactWorkflowLaneCounts = { total: rows.length, done: 0, active: 0, queued: 0, blocked: 0, failed: 0, paused: 0, stopped: 0 };
|
|
1455
|
+
for (const row of rows) {
|
|
1456
|
+
switch (row.state) {
|
|
1457
|
+
case "complete": counts.done++; break;
|
|
1458
|
+
case "running": counts.active++; break;
|
|
1459
|
+
case "queued": counts.queued++; break;
|
|
1460
|
+
case "blocked": counts.blocked++; break;
|
|
1461
|
+
case "failed": counts.failed++; break;
|
|
1462
|
+
case "paused": counts.paused++; break;
|
|
1463
|
+
case "stopped": counts.stopped++; break;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return counts;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function compactWorkflowLaneLine(row: CompactWorkflowLaneRow, theme: Theme, indent: string, frame?: number): string {
|
|
1470
|
+
const owner = row.agent && row.mode ? `${row.agent}/${row.mode}` : row.agent ?? row.mode;
|
|
1471
|
+
const state = row.state === "complete" ? undefined : row.state === "running" ? "active" : row.state;
|
|
1472
|
+
const intent = [
|
|
1473
|
+
row.decision,
|
|
1474
|
+
row.claims,
|
|
1475
|
+
row.expectedOutput,
|
|
1476
|
+
!row.mode && row.label ? row.label : undefined,
|
|
1477
|
+
].filter((value): value is string => Boolean(value));
|
|
1478
|
+
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(" · ")}`)}` : ""}`;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function compactWorkflowShortId(job: AsyncJobState): string {
|
|
1482
|
+
return job.asyncId.slice(0, 8);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function compactWorkflowDisplayLabel(job: AsyncJobState): string {
|
|
1486
|
+
return boundedLaneValue(job.description, 64) ?? compactWorkflowShortId(job);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
function compactWorkflowStats(job: AsyncJobState, rows: readonly CompactWorkflowLaneRow[], theme: Theme, projection: WorkflowWidgetProjection): string {
|
|
1490
|
+
const counts = compactWorkflowLaneCounts(rows);
|
|
1491
|
+
const total = counts.total || projection.stageProgress?.total || job.stepsTotal || job.agents?.length || 0;
|
|
1492
|
+
const progress = [
|
|
1493
|
+
total > 0 ? `${counts.done}/${total} done` : undefined,
|
|
1494
|
+
counts.active ? `${counts.active} active` : undefined,
|
|
1495
|
+
counts.queued ? `${counts.queued} queued` : undefined,
|
|
1496
|
+
counts.blocked ? `${counts.blocked} blocked` : undefined,
|
|
1497
|
+
counts.failed ? `${counts.failed} failed` : undefined,
|
|
1498
|
+
counts.paused ? `${counts.paused} paused` : undefined,
|
|
1499
|
+
counts.stopped ? `${counts.stopped} stopped` : undefined,
|
|
1500
|
+
].filter((value): value is string => Boolean(value));
|
|
1501
|
+
const rowToolUses = rows.map((row) => row.toolUses).filter((value): value is number => value !== undefined);
|
|
1502
|
+
const toolUses = job.toolCount ?? (rowToolUses.length ? rowToolUses.reduce((sum, value) => sum + value, 0) : undefined);
|
|
1503
|
+
const rowDuration = rows.map((row) => row.durationMs).filter((value): value is number => value !== undefined);
|
|
1504
|
+
const durationMs = job.startedAt !== undefined && job.updatedAt !== undefined
|
|
1505
|
+
? Math.max(0, job.updatedAt - job.startedAt)
|
|
1506
|
+
: rowDuration.length ? Math.max(...rowDuration) : undefined;
|
|
1507
|
+
return statJoin(theme, [
|
|
1508
|
+
`id: ${compactWorkflowShortId(job)}`,
|
|
1509
|
+
...progress,
|
|
1510
|
+
toolUses !== undefined ? formatToolUseStat(toolUses) : undefined,
|
|
1511
|
+
durationMs !== undefined ? formatDuration(durationMs) : undefined,
|
|
1512
|
+
].filter((value): value is string => Boolean(value)));
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
interface CompactWorkflowBottleneck {
|
|
1516
|
+
text: string;
|
|
1517
|
+
tone: "warning" | "error";
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
function compactWorkflowBottleneck(rows: readonly CompactWorkflowLaneRow[], job: AsyncJobState): CompactWorkflowBottleneck | undefined {
|
|
1521
|
+
let row: CompactWorkflowLaneRow | undefined;
|
|
1522
|
+
for (const candidate of rows) {
|
|
1523
|
+
if (candidate.state !== "blocked" && candidate.state !== "failed" && candidate.state !== "paused" && candidate.state !== "stopped") continue;
|
|
1524
|
+
if (!row || workflowChecklistItemPriority(candidate.state) < workflowChecklistItemPriority(row.state)) row = candidate;
|
|
1525
|
+
}
|
|
1526
|
+
if (row) return { text: `${row.key} · ${row.state}`, tone: row.state === "blocked" || row.state === "failed" ? "error" : "warning" };
|
|
1527
|
+
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" };
|
|
1528
|
+
if (job.status === "failed" || job.status === "partial" || job.status === "rejected") return { text: `workflow · ${job.status}`, tone: "error" };
|
|
1529
|
+
if (job.status === "paused" || job.status === "stopped") return { text: `workflow · ${job.status}`, tone: "warning" };
|
|
1530
|
+
return undefined;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
function compactWorkflowHeaderLine(job: AsyncJobState, theme: Theme, width?: number): string {
|
|
1534
|
+
const label = compactWorkflowDisplayLabel(job);
|
|
1535
|
+
const full = `${theme.fg("toolTitle", themeBold(theme, `async workflow: ${label}`))} ${theme.fg("dim", "─ background")}`;
|
|
1536
|
+
if (width === undefined || visibleWidth(full) <= width) return full;
|
|
1537
|
+
return `${theme.fg("toolTitle", themeBold(theme, `async workflow ${label}`))} ${theme.fg("dim", "· background")}`;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function compactWorkflowWidgetBodyLines(job: AsyncJobState, theme: Theme, frame: number | undefined, projection: WorkflowWidgetProjection): string[] {
|
|
1541
|
+
const rows = compactWorkflowLaneRows(job, projection.checklist);
|
|
1542
|
+
const lines = [` ${compactWorkflowStats(job, rows, theme, projection)}`];
|
|
1543
|
+
if (rows.length) {
|
|
1544
|
+
for (const row of rows) lines.push(compactWorkflowLaneLine(row, theme, " ", frame));
|
|
1545
|
+
} else {
|
|
1546
|
+
lines.push(` ${theme.fg("dim", "◦ waiting for workflow lanes")}`);
|
|
1547
|
+
}
|
|
1548
|
+
const bottleneck = compactWorkflowBottleneck(rows, job);
|
|
1549
|
+
if (bottleneck) lines.push(` ${theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`)}`);
|
|
1550
|
+
if (job.status === "running" || rows.some((row) => row.state === "running")) lines.push(` ${theme.fg("accent", workflowDetailHintText())}`);
|
|
1551
|
+
return lines;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1343
1554
|
function widgetStepActivity(step: NonNullable<AsyncJobState["steps"]>[number], snapshotNow?: number): string {
|
|
1344
1555
|
const facts: string[] = [];
|
|
1345
1556
|
if (step.currentTool && step.currentToolStartedAt !== undefined && snapshotNow !== undefined) facts.push(`${step.currentTool} ${formatDuration(Math.max(0, snapshotNow - step.currentToolStartedAt))}`);
|
|
@@ -2154,19 +2365,13 @@ function hostStepWidgetLines(job: AsyncJobState, theme: Theme, indent: string):
|
|
|
2154
2365
|
|
|
2155
2366
|
function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
|
|
2156
2367
|
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;
|
|
2368
|
+
if (!expanded && job.mode === "workflow") {
|
|
2369
|
+
return compactWorkflowWidgetBodyLines(job, theme, frame, projection);
|
|
2165
2370
|
}
|
|
2166
2371
|
if (!steps.length) {
|
|
2167
2372
|
const lane = projectAsyncLane(job, laneStepForJob(job, steps));
|
|
2168
2373
|
return [
|
|
2169
|
-
...workflowPreflightLines(job
|
|
2374
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2170
2375
|
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2171
2376
|
...(lane ? formatLaneProjectionLines(lane, theme, " ") : []),
|
|
2172
2377
|
...hostStepWidgetLines(job, theme, " "),
|
|
@@ -2176,8 +2381,8 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2176
2381
|
}
|
|
2177
2382
|
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, frame);
|
|
2178
2383
|
const lines: string[] = [
|
|
2179
|
-
...workflowPreflightLines(job
|
|
2180
|
-
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2384
|
+
...(expanded ? workflowPreflightLines(job) : []),
|
|
2385
|
+
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame, { includeItemErrors: !expanded }),
|
|
2181
2386
|
];
|
|
2182
2387
|
const group = activeParallelWidgetGroup(job);
|
|
2183
2388
|
if (group) {
|
|
@@ -2210,6 +2415,7 @@ function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded
|
|
|
2210
2415
|
}
|
|
2211
2416
|
|
|
2212
2417
|
function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, frame?: number, projection = buildWorkflowWidgetProjection(job)): string[] {
|
|
2418
|
+
if (!expanded && job.mode === "workflow") return [compactWorkflowHeaderLine(job, theme, width), ...compactWorkflowWidgetBodyLines(job, theme, frame, projection)].map((line) => truncLine(line, width));
|
|
2213
2419
|
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2214
2420
|
const count = job.mode === "workflow"
|
|
2215
2421
|
? projection.checklist?.total ?? projection.stageProgress?.total ?? job.stepsTotal ?? job.agents?.length ?? job.steps?.length
|
|
@@ -2386,11 +2592,17 @@ function progressiveHeaderLine(jobs: AsyncJobState[], theme: Theme, width: numbe
|
|
|
2386
2592
|
}
|
|
2387
2593
|
|
|
2388
2594
|
function progressiveJobLine(job: AsyncJobState, theme: Theme, width: number, frame?: number, projection = buildWorkflowWidgetProjection(job)): string {
|
|
2389
|
-
const
|
|
2390
|
-
const
|
|
2391
|
-
const
|
|
2595
|
+
const compactWorkflow = job.mode === "workflow";
|
|
2596
|
+
const compactRows = compactWorkflow ? compactWorkflowLaneRows(job, projection.checklist) : undefined;
|
|
2597
|
+
const stats = compactWorkflow ? compactWorkflowStats(job, compactRows ?? [], theme, projection) : widgetStats(job, theme, projection, true);
|
|
2598
|
+
if (compactWorkflow) {
|
|
2599
|
+
const bottleneck = compactWorkflowBottleneck(compactRows ?? [], job);
|
|
2600
|
+
const suffix = [stats, bottleneck ? theme.fg(bottleneck.tone, `bottleneck · ${bottleneck.text}`) : undefined].filter((value): value is string => Boolean(value)).join(` ${theme.fg("dim", "·")} `);
|
|
2601
|
+
return truncLine(` ${widgetStatusGlyph(job, theme, frame)} ${compactWorkflowHeaderLine(job, theme, Math.max(0, width - 4))}${suffix ? ` ${theme.fg("dim", "·")} ${suffix}` : ""}`, width);
|
|
2602
|
+
}
|
|
2603
|
+
const activity = widgetActivity(job);
|
|
2392
2604
|
const status = job.status === "complete" ? "done" : job.status;
|
|
2393
|
-
const lane =
|
|
2605
|
+
const lane = projectAsyncLane(job, laneStepForJob(job, projection.steps));
|
|
2394
2606
|
const laneSummary = lane
|
|
2395
2607
|
? [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
2608
|
: "";
|
|
@@ -2557,21 +2769,22 @@ function buildWidgetLinesWithProjection(jobs: AsyncJobState[], theme: Theme, wid
|
|
|
2557
2769
|
let slots = MAX_WIDGET_JOBS;
|
|
2558
2770
|
const appendJob = (job: AsyncJobState): void => {
|
|
2559
2771
|
const projection = projectionFor(job);
|
|
2560
|
-
const
|
|
2561
|
-
const stats = widgetStats(job, theme, projection, !expanded);
|
|
2562
|
-
const details =
|
|
2772
|
+
const compactWorkflow = !expanded && job.mode === "workflow";
|
|
2773
|
+
const stats = compactWorkflow ? "" : widgetStats(job, theme, projection, !expanded);
|
|
2774
|
+
const details = compactWorkflow
|
|
2563
2775
|
? [
|
|
2564
|
-
...
|
|
2565
|
-
...(job.status === "running" || projection.checklist!.running > 0 ? [` ${theme.fg("accent", liveDetailHintText())}`] : []),
|
|
2776
|
+
...compactWorkflowWidgetBodyLines(job, theme, frame, projection),
|
|
2566
2777
|
]
|
|
2567
2778
|
: [
|
|
2568
2779
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
2569
2780
|
...widgetLaneDetailLines(job, theme, projection),
|
|
2570
|
-
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame),
|
|
2781
|
+
...workflowChecklistWidgetLines(projection.checklist, theme, " ", expanded, frame, { includeItemErrors: !expanded || !job.steps?.length }),
|
|
2571
2782
|
...widgetParallelAgentDetails(job, theme, expanded, width, frame),
|
|
2572
2783
|
];
|
|
2573
2784
|
items.push([
|
|
2574
|
-
|
|
2785
|
+
compactWorkflow
|
|
2786
|
+
? compactWorkflowHeaderLine(job, theme, width)
|
|
2787
|
+
: `${widgetStatusGlyph(job, theme, frame)} ${themeBold(theme, widgetJobName(job))}${contextModeBadge(theme, job.context)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
2575
2788
|
...details,
|
|
2576
2789
|
]);
|
|
2577
2790
|
};
|
|
@@ -2789,7 +3002,7 @@ function renderWorkflowChatProgress(d: Details, result: AgentToolResult<Details>
|
|
|
2789
3002
|
if (d.preflight) c.addChild(new Text(truncLine(theme.fg("dim", formatWorkflowPreflightPlanSummary(d.preflight, { indent: rowIndent })), width), 0, 0));
|
|
2790
3003
|
if (phase) c.addChild(new Text(truncLine(theme.fg("dim", `${rowIndent}Phase ${phase}`), width), 0, 0));
|
|
2791
3004
|
const checklist = projectWorkflowChecklist({ hostSteps: workflow?.receipt?.hostSteps, preflight: d.preflight, trace: workflow?.trace, now: Date.now() });
|
|
2792
|
-
for (const line of workflowChecklistWidgetLines(checklist, theme, rowIndent, false, frame,
|
|
3005
|
+
for (const line of workflowChecklistWidgetLines(checklist, theme, rowIndent, false, frame, { limitPhases: !expanded, includeBottleneckOutput: expanded })) {
|
|
2793
3006
|
c.addChild(new Text(truncLine(line, width), 0, 0));
|
|
2794
3007
|
}
|
|
2795
3008
|
if (rows.length === 0) {
|
|
@@ -2871,7 +3084,7 @@ function renderMultiCompact(d: Details, theme: Theme, layout: MainWindowRenderLa
|
|
|
2871
3084
|
const detailIndent = mainWindowIndent(layout, 2);
|
|
2872
3085
|
c.addChild(new Text(truncLine(`${glyph} ${theme.fg("toolTitle", theme.bold(d.mode))}${contextBadge}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`, width), 0, 0));
|
|
2873
3086
|
if (checklistPrimary) {
|
|
2874
|
-
for (const line of workflowChecklistWidgetLines(workflowChecklist, theme, rowIndent, false, frame, false)) {
|
|
3087
|
+
for (const line of workflowChecklistWidgetLines(workflowChecklist, theme, rowIndent, false, frame, { includeSummary: false })) {
|
|
2875
3088
|
c.addChild(new Text(truncLine(line, width), 0, 0));
|
|
2876
3089
|
}
|
|
2877
3090
|
if (hasRunning || workflowChecklist.running > 0) c.addChild(new Text(truncLine(theme.fg("accent", `${rowIndent}${liveDetailHintText()}`), width), 0, 0));
|
|
@@ -3222,7 +3435,7 @@ export function renderSubagentResult(
|
|
|
3222
3435
|
c.addChild(new Text(fit(` ${chainVis}`), 0, 0));
|
|
3223
3436
|
}
|
|
3224
3437
|
const workflowChecklist = foregroundWorkflowChecklist(d);
|
|
3225
|
-
for (const line of workflowChecklistWidgetLines(workflowChecklist, theme, " ", true, frame)) {
|
|
3438
|
+
for (const line of workflowChecklistWidgetLines(workflowChecklist, theme, " ", true, frame, { includeItemErrors: false })) {
|
|
3226
3439
|
c.addChild(new Text(fit(line), 0, 0));
|
|
3227
3440
|
}
|
|
3228
3441
|
|
|
@@ -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;
|