pi-subagents 0.62.0 → 0.64.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/docs/agents.md +5 -4
  3. package/docs/configuration.md +28 -2
  4. package/docs/models.md +5 -5
  5. package/docs/observability.md +4 -1
  6. package/docs/tool-reference.md +2 -2
  7. package/docs/watchdog.md +92 -114
  8. package/package.json +1 -1
  9. package/skills/pi-subagents/references/execution-controls.md +4 -3
  10. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -1
  11. package/skills/pi-subagents/references/prompting-and-roles.md +3 -3
  12. package/src/agents/agent-management.ts +22 -4
  13. package/src/agents/agents.ts +107 -125
  14. package/src/api/shared-types.ts +3 -0
  15. package/src/extension/config.ts +20 -0
  16. package/src/inspectors/herdr/inspector-runner.ts +19 -13
  17. package/src/runs/background/active-async-capacity.ts +0 -1
  18. package/src/runs/background/async-execution.ts +56 -7
  19. package/src/runs/background/async-resume.ts +3 -0
  20. package/src/runs/background/async-status.ts +18 -2
  21. package/src/runs/background/notify.ts +54 -3
  22. package/src/runs/background/run-status.ts +22 -2
  23. package/src/runs/background/subagent-runner.ts +53 -3
  24. package/src/runs/background/wait-completions.ts +13 -0
  25. package/src/runs/foreground/execution.ts +7 -1
  26. package/src/runs/foreground/subagent-executor.ts +56 -4
  27. package/src/runs/shared/acceptance.ts +25 -9
  28. package/src/runs/shared/async-status-projection.ts +11 -43
  29. package/src/runs/shared/lane-metadata.ts +24 -3
  30. package/src/runs/shared/parallel-handoff.ts +4 -0
  31. package/src/runs/shared/pi-args.ts +9 -6
  32. package/src/runs/shared/subagent-control.ts +4 -2
  33. package/src/runs/shared/task-intent.ts +5 -2
  34. package/src/runs/shared/worktree.ts +467 -63
  35. package/src/shared/types.ts +38 -2
  36. package/src/shared/utils.ts +18 -7
  37. package/src/slash/subagents-admin.ts +24 -12
  38. package/src/tui/fleet-status.ts +61 -2
  39. package/src/tui/fleet.ts +12 -7
  40. package/src/tui/render.ts +227 -14
  41. package/src/watchdog/child-status.ts +54 -33
  42. package/src/watchdog/diff-tool.ts +77 -0
  43. package/src/watchdog/emission-guard.ts +5 -3
  44. package/src/watchdog/guidance.ts +20 -0
  45. package/src/watchdog/register-child.ts +16 -13
  46. package/src/watchdog/register-main.ts +10 -9
  47. package/src/watchdog/render.ts +4 -5
  48. package/src/watchdog/review.ts +15 -4
  49. package/src/watchdog/rules.ts +70 -0
  50. package/src/watchdog/runtime.ts +75 -92
  51. package/src/watchdog/scope.ts +0 -11
  52. package/src/watchdog/settings.ts +48 -104
  53. package/src/watchdog/types.ts +18 -32
  54. package/src/watchdog/warning-format.ts +0 -1
  55. package/src/workflows/chat-progress.ts +3 -2
  56. package/src/workflows/workflow-checklist.ts +439 -0
  57. package/src/workflows/workflow-preflight.ts +28 -1
@@ -16,7 +16,8 @@ import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
16
16
  import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
17
17
  import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
18
18
  import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
19
- import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
19
+ import { applyWatchdogLaunchRules, sendRuleViolationWarning } from "../../watchdog/rules.ts";
20
+ import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
20
21
  import type { RunnerStep } from "../shared/parallel-utils.ts";
21
22
  import type { ContextMode } from "../shared/context-mode.ts";
22
23
  import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
@@ -31,7 +32,7 @@ import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout
31
32
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
32
33
  import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
33
34
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
34
- import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
35
+ import { resolveExpectedWorktreeAgentCwd, resolveWorktreeProvider, shouldDeferWorktreeCwd, WORKTREE_AGENT_CWD_PLACEHOLDER } from "../shared/worktree.ts";
35
36
  import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
36
37
  import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
37
38
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
@@ -178,6 +179,8 @@ interface AsyncChainParams {
178
179
  worktreeSetupHook?: string;
179
180
  worktreeSetupHookTimeoutMs?: number;
180
181
  worktreeBaseDir?: string;
182
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
183
+ worktreeBranchPrefix?: string;
181
184
  controlConfig?: ResolvedControlConfig;
182
185
  controlIntercomTarget?: string;
183
186
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
@@ -245,6 +248,8 @@ interface AsyncSingleParams {
245
248
  worktreeSetupHook?: string;
246
249
  worktreeSetupHookTimeoutMs?: number;
247
250
  worktreeBaseDir?: string;
251
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
252
+ worktreeBranchPrefix?: string;
248
253
  worktree?: boolean;
249
254
  controlConfig?: ResolvedControlConfig;
250
255
  intercomBridge?: IntercomBridgeConfig;
@@ -310,6 +315,8 @@ export interface AsyncRunnerStepBuildParams {
310
315
  waitToolEnabled?: boolean;
311
316
  waitToolDefaultTimeoutMs?: number;
312
317
  worktreeBaseDir?: string;
318
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
319
+ worktreeBranchPrefix?: string;
313
320
  asyncDir: string;
314
321
  outputBaseDir?: string;
315
322
  validateOutputBindings?: boolean;
@@ -740,6 +747,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
740
747
  thinkingOverridesByFlatIndex,
741
748
  maxSubagentDepth,
742
749
  worktreeBaseDir,
750
+ worktreeProvider,
751
+ worktreeBranchPrefix,
743
752
  asyncDir,
744
753
  } = params;
745
754
  const outputBaseDir = params.outputBaseDir;
@@ -747,6 +756,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
747
756
  const chainSkills = params.chainSkills ?? [];
748
757
  const availableModels = params.availableModels;
749
758
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
759
+ let managedWorktreeProvider: "native" | "worktrunk" | undefined;
760
+ try {
761
+ if (chain.some((step) => "worktree" in step && step.worktree === true)) {
762
+ const resolved = resolveWorktreeProvider(worktreeProvider, worktreeBaseDir);
763
+ managedWorktreeProvider = shouldDeferWorktreeCwd(worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
764
+ }
765
+ } catch (error) {
766
+ return { error: error instanceof Error ? error.message : String(error) };
767
+ }
750
768
  const progressDir = params.progressDir ?? runnerCwd;
751
769
  const graphChain: ChainStep[] = params.attachRoot
752
770
  ? [{
@@ -873,7 +891,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
873
891
  if (validationError) throw new AsyncStartValidationError(validationError);
874
892
  let taskTemplate = s.task ?? "{previous}";
875
893
  taskTemplate = taskTemplate.replace(/\{task\}/g, originalTask ?? "");
876
- taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
894
+ taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, behaviorCwd ?? runnerCwd);
877
895
  const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
878
896
  const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
879
897
 
@@ -922,6 +940,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
922
940
  throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
923
941
  }
924
942
  }
943
+ const launchRuleError = applyWatchdogLaunchRules({ cwd: stepCwd, agent: a.name, model: modelCandidates[0] ?? model, warn: (violation) => sendRuleViolationWarning(ctx.pi, violation) });
944
+ if (launchRuleError) throw new AsyncStartValidationError(launchRuleError);
925
945
  const fast = s.fast ?? params.fast ?? a.fast;
926
946
  const toolPlan = resolvePiLaunchToolPlan({
927
947
  tools: a.tools,
@@ -1058,7 +1078,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1058
1078
  return {
1059
1079
  parallel: s.parallel.map((t, taskIndex) => {
1060
1080
  let behaviorCwd: string | undefined;
1061
- if (s.worktree) {
1081
+ if (s.worktree && managedWorktreeProvider === "worktrunk") {
1082
+ behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
1083
+ } else if (s.worktree && managedWorktreeProvider === "native") {
1062
1084
  try {
1063
1085
  behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex, worktreeBaseDir);
1064
1086
  } catch {
@@ -1113,7 +1135,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1113
1135
  }
1114
1136
  const sequential = s as SequentialStep;
1115
1137
  let behaviorCwd: string | undefined;
1116
- if (sequential.worktree) {
1138
+ if (sequential.worktree && managedWorktreeProvider === "worktrunk") {
1139
+ behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
1140
+ } else if (sequential.worktree && managedWorktreeProvider === "native") {
1117
1141
  try {
1118
1142
  behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, 0, worktreeBaseDir);
1119
1143
  } catch {
@@ -1183,6 +1207,8 @@ export function executeAsyncChain(
1183
1207
  worktreeSetupHook,
1184
1208
  worktreeSetupHookTimeoutMs,
1185
1209
  worktreeBaseDir,
1210
+ worktreeProvider,
1211
+ worktreeBranchPrefix,
1186
1212
  controlConfig,
1187
1213
  controlIntercomTarget,
1188
1214
  childIntercomTarget,
@@ -1239,6 +1265,8 @@ export function executeAsyncChain(
1239
1265
  waitToolEnabled: params.waitToolEnabled,
1240
1266
  waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
1241
1267
  worktreeBaseDir,
1268
+ worktreeProvider,
1269
+ worktreeBranchPrefix,
1242
1270
  asyncDir,
1243
1271
  fast: params.fast,
1244
1272
  toolBudget: params.toolBudget,
@@ -1314,6 +1342,8 @@ export function executeAsyncChain(
1314
1342
  worktreeSetupHook,
1315
1343
  worktreeSetupHookTimeoutMs,
1316
1344
  worktreeBaseDir,
1345
+ worktreeProvider,
1346
+ worktreeBranchPrefix,
1317
1347
  controlConfig,
1318
1348
  toolBudget: params.toolBudget,
1319
1349
  usageBudget: params.usageBudget,
@@ -1511,6 +1541,8 @@ export function executeAsyncSingle(
1511
1541
  worktreeSetupHook,
1512
1542
  worktreeSetupHookTimeoutMs,
1513
1543
  worktreeBaseDir,
1544
+ worktreeProvider,
1545
+ worktreeBranchPrefix,
1514
1546
  controlConfig,
1515
1547
  controlIntercomTarget,
1516
1548
  childIntercomTarget,
@@ -1556,7 +1588,18 @@ export function executeAsyncSingle(
1556
1588
  return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1557
1589
  }
1558
1590
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
1559
- const instructionCwd = params.worktree === true
1591
+ let managedWorktreeProvider: "native" | "worktrunk" | undefined;
1592
+ if (params.worktree === true) {
1593
+ try {
1594
+ const resolved = resolveWorktreeProvider(params.worktreeProvider, worktreeBaseDir);
1595
+ managedWorktreeProvider = shouldDeferWorktreeCwd(params.worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
1596
+ } catch (error) {
1597
+ return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1598
+ }
1599
+ }
1600
+ const instructionCwd = params.worktree === true && managedWorktreeProvider === "worktrunk"
1601
+ ? WORKTREE_AGENT_CWD_PLACEHOLDER
1602
+ : params.worktree === true && managedWorktreeProvider === "native"
1560
1603
  ? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
1561
1604
  : runnerCwd;
1562
1605
  const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
@@ -1610,7 +1653,11 @@ export function executeAsyncSingle(
1610
1653
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
1611
1654
  // absolute paths pass through; relative paths resolve against the child cwd.
1612
1655
  const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
1613
- const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, readExistenceCwd) : [];
1656
+ const readPaths = Array.isArray(reads)
1657
+ ? managedWorktreeProvider === "worktrunk"
1658
+ ? resolveExistingReadInstructionPaths(reads, instructionCwd, readExistenceCwd)
1659
+ : resolveExistingReadPaths(reads, readExistenceCwd)
1660
+ : [];
1614
1661
  const readsInstruction = readPaths.length > 0
1615
1662
  ? `[Read from: ${readPaths.join(", ")}]\n\n`
1616
1663
  : "";
@@ -1901,6 +1948,8 @@ export function executeAsyncSingle(
1901
1948
  worktreeSetupHook,
1902
1949
  worktreeSetupHookTimeoutMs,
1903
1950
  worktreeBaseDir,
1951
+ worktreeProvider,
1952
+ worktreeBranchPrefix,
1904
1953
  controlConfig,
1905
1954
  timeoutMs,
1906
1955
  deadlineAt,
@@ -45,6 +45,8 @@ export type AsyncResumeTarget = {
45
45
  sessionName?: string;
46
46
  index: number;
47
47
  cwd?: string;
48
+ /** True when cwd is the retained managed worktree recorded by the handoff. */
49
+ managedWorktree?: boolean;
48
50
  sessionFile?: string;
49
51
  model?: string;
50
52
  thinking?: string;
@@ -569,6 +571,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
569
571
  ...(statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName ? { sessionName: statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName } : {}),
570
572
  index,
571
573
  ...(resumeCwd ? { cwd: resumeCwd } : {}),
574
+ ...(managedWorktreeCwd ? { managedWorktree: true } : {}),
572
575
  ...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
573
576
  ...(stepModel ? { model: stepModel } : {}),
574
577
  ...(stepThinking ? { thinking: stepThinking } : {}),
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
4
4
  import { previewDisplayText } from "../../shared/display-text.ts";
5
5
  import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
6
- import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
6
+ import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorktreeNaming, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
7
7
  import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
8
8
  import { readStatus } from "../../shared/utils.ts";
9
9
  import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
@@ -24,6 +24,7 @@ import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
24
24
  import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
25
25
  import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
26
26
  import { formatTimeoutRecoveryLines, projectTimeoutRecovery } from "../shared/mutation-evidence.ts";
27
+ import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
27
28
 
28
29
  interface AsyncRunStepSummary {
29
30
  index: number;
@@ -39,6 +40,8 @@ interface AsyncRunStepSummary {
39
40
  lane?: AsyncJobStep["lane"];
40
41
  worktreePath?: string;
41
42
  branch?: string;
43
+ provider?: "native" | "worktrunk";
44
+ naming?: WorktreeNaming;
42
45
  runId?: string;
43
46
  outputName?: string;
44
47
  structured?: boolean;
@@ -337,6 +340,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
337
340
  ...(step.lane ? { lane: step.lane } : {}),
338
341
  ...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
339
342
  ...(step.branch ? { branch: step.branch } : {}),
343
+ ...(step.provider ? { provider: step.provider } : {}),
344
+ ...(step.naming ? { naming: step.naming } : {}),
340
345
  ...(step.runId ? { runId: step.runId } : {}),
341
346
  ...(step.outputName ? { outputName: step.outputName } : {}),
342
347
  ...(step.structured ? { structured: step.structured } : {}),
@@ -614,7 +619,7 @@ function formatStepLine(step: AsyncRunStepSummary): string {
614
619
  if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
615
620
  if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
616
621
  if (step.lane) parts.push(`lane ${step.lane.key}`);
617
- if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
622
+ if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}`);
618
623
  return parts.join(" | ");
619
624
  }
620
625
 
@@ -707,6 +712,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
707
712
  if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
708
713
  const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
709
714
  if (preflightWarning) lines.push(preflightWarning);
715
+ if (run.mode === "workflow") {
716
+ const checklist = projectWorkflowChecklist({
717
+ graph: run.workflowGraph,
718
+ steps: run.steps,
719
+ hostSteps: run.hostSteps,
720
+ preflight: run.preflight,
721
+ trace: run.workflow?.trace,
722
+ now: run.lastUpdate ?? run.endedAt ?? Date.now(),
723
+ });
724
+ lines.push(...formatWorkflowChecklistText(checklist, " ", { includeItems: false }));
725
+ }
710
726
  for (const step of run.steps) {
711
727
  lines.push(` ${formatStepLine(step)}`);
712
728
  lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));
@@ -14,7 +14,7 @@ import {
14
14
  createCompletionBatcher,
15
15
  resolveCompletionBatchConfig,
16
16
  } from "./completion-batcher.ts";
17
- import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type ParallelHandoffReference, type ScheduleOrigin, type SubagentState } from "../../shared/types.ts";
17
+ import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type ChildWatchdogProgress, type ChildWatchdogWarningSummary, type ParallelHandoffReference, type ScheduleOrigin, type SubagentState } from "../../shared/types.ts";
18
18
  import { safeTerminalText } from "../../shared/display-text.ts";
19
19
  import { resolveSubagentResultStatus } from "../../intercom/result-intercom.ts";
20
20
  import { isUnexplainedProcessSignal } from "../shared/process-signal.ts";
@@ -31,6 +31,8 @@ export interface SubagentNotifyChildOutput {
31
31
  previewUnavailableReason?: string;
32
32
  }
33
33
 
34
+ export type SubagentNotifyWatchdogBlocker = Pick<ChildWatchdogWarningSummary, "summary" | "addressed" | "stalemate"> & { agent: string };
35
+
34
36
  export interface SubagentNotifyDetails {
35
37
  agent: string;
36
38
  status: "completed" | "failed" | "paused" | "stopped";
@@ -47,6 +49,7 @@ export interface SubagentNotifyDetails {
47
49
  handoffPath?: string;
48
50
  /** Present when a durable schedule launched the run. */
49
51
  scheduleOrigin?: ScheduleOrigin;
52
+ watchdogBlockers?: SubagentNotifyWatchdogBlocker[];
50
53
  }
51
54
 
52
55
  export interface CompletionNotification {
@@ -85,7 +88,9 @@ export interface CompletionNotification {
85
88
  timedOut?: boolean;
86
89
  stopped?: boolean;
87
90
  turnBudgetExceeded?: boolean;
91
+ watchdog?: ChildWatchdogProgress;
88
92
  }>;
93
+ watchdog?: ChildWatchdogProgress;
89
94
  timestamp?: number;
90
95
  durationMs?: number;
91
96
  cwd?: string;
@@ -249,9 +254,28 @@ function formatCorrelationLines(details: SubagentNotifyDetails): string[] {
249
254
  ].filter((line): line is string => line !== undefined);
250
255
  }
251
256
 
257
+ const WATCHDOG_BLOCKERS_HEADING = "Watchdog blockers:";
258
+
259
+ function formatWatchdogBlockerLines(details: SubagentNotifyDetails): string[] {
260
+ if (!details.watchdogBlockers?.length) return [];
261
+ return [WATCHDOG_BLOCKERS_HEADING, ...details.watchdogBlockers.map((blocker) => `- ${blocker.agent}: ${blocker.summary} (${blocker.stalemate ? "stalemate" : blocker.addressed ? "addressed" : "unaddressed"})`)];
262
+ }
263
+
264
+ // A stalemate blocker parses back as unaddressed; acceptance treats both as unresolved.
265
+ function parseWatchdogBlockerLines(lines: string[]): SubagentNotifyWatchdogBlocker[] {
266
+ const blockers: SubagentNotifyWatchdogBlocker[] = [];
267
+ for (const line of lines) {
268
+ const match = line.match(/^- (.+?): (.+) \((addressed|unaddressed|stalemate)\)$/);
269
+ if (!match) break;
270
+ blockers.push({ agent: match[1]!, summary: match[2]!, addressed: match[3] === "addressed", stalemate: match[3] === "stalemate" });
271
+ }
272
+ return blockers;
273
+ }
274
+
252
275
  export function formatSingleCompletion(details: SubagentNotifyDetails): string {
253
276
  const sessionLine = formatSessionLine(details);
254
277
  const correlationLines = formatCorrelationLines(details);
278
+ const watchdogLines = formatWatchdogBlockerLines(details);
255
279
  const taskKind = details.source === "foreground" ? "Detached foreground task" : "Background task";
256
280
  const scheduleLine = details.scheduleOrigin
257
281
  ? `Scheduled run from **${details.scheduleOrigin.name ?? details.scheduleOrigin.id}** (schedule ${details.scheduleOrigin.id}).`
@@ -262,6 +286,7 @@ export function formatSingleCompletion(details: SubagentNotifyDetails): string {
262
286
  scheduleLine,
263
287
  scheduleLine ? "" : undefined,
264
288
  formatResultPreview(details),
289
+ ...(watchdogLines.length ? ["", ...watchdogLines] : []),
265
290
  details.handoffPath ? "" : undefined,
266
291
  details.handoffPath ? `Parallel handoff: ${details.handoffPath}` : undefined,
267
292
  correlationLines.length && !details.handoffPath ? "" : undefined,
@@ -300,7 +325,9 @@ export function parseSubagentNotifyContent(content: string): SubagentNotifyDetai
300
325
  const workflowRunIndex = body.findIndex((line) => line.startsWith("Workflow run: "));
301
326
  const childRunsIndex = body.findIndex((line) => line.startsWith("Child runs: "));
302
327
  const reconciledIndex = body.findIndex((line) => line.startsWith("Reconciled detached child: "));
303
- const metadataIndexes = [sessionIndex, handoffIndex, workflowRunIndex, childRunsIndex, reconciledIndex].filter((index) => index >= 0);
328
+ const watchdogIndex = body.findIndex((line) => line === WATCHDOG_BLOCKERS_HEADING);
329
+ const watchdogBlockers = watchdogIndex >= 0 ? parseWatchdogBlockerLines(body.slice(watchdogIndex + 1)) : [];
330
+ const metadataIndexes = [sessionIndex, handoffIndex, workflowRunIndex, childRunsIndex, reconciledIndex, watchdogIndex].filter((index) => index >= 0);
304
331
  const firstMetadataIndex = metadataIndexes.length ? Math.min(...metadataIndexes) : body.length;
305
332
  const resultEnd = firstMetadataIndex > 0 && body[firstMetadataIndex - 1]?.trim() === "" ? firstMetadataIndex - 1 : firstMetadataIndex;
306
333
  const resultPreview = body.slice(0, resultEnd).join("\n").trim() || "(no output)";
@@ -336,6 +363,7 @@ export function parseSubagentNotifyContent(content: string): SubagentNotifyDetai
336
363
  ...(workflowRunId ? { workflowRunId } : {}),
337
364
  ...(childRuns?.length ? { childRuns } : {}),
338
365
  ...(reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {}),
366
+ ...(watchdogBlockers.length ? { watchdogBlockers } : {}),
339
367
  ...(sessionLabel && sessionValue ? { sessionLabel, sessionValue } : {}),
340
368
  };
341
369
  }
@@ -349,6 +377,7 @@ export function formatGroupedCompletion(details: SubagentNotifyDetails[]): strin
349
377
  const sessionLine = formatSessionLine(detail);
350
378
  blocks.push(`${index + 1}. ${detail.agent}${detail.taskInfo ?? ""}${detail.scheduleOrigin ? ` — scheduled run from ${detail.scheduleOrigin.name ?? detail.scheduleOrigin.id} (schedule ${detail.scheduleOrigin.id})` : ""}`);
351
379
  blocks.push(formatResultPreview(detail));
380
+ blocks.push(...formatWatchdogBlockerLines(detail));
352
381
  if (detail.handoffPath) blocks.push(`Parallel handoff: ${detail.handoffPath}`);
353
382
  blocks.push(...formatCorrelationLines(detail));
354
383
  if (sessionLine) blocks.push(sessionLine);
@@ -420,6 +449,18 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
420
449
  const handoffPath = typeof parallelHandoff?.path === "string" ? parallelHandoff.path : undefined;
421
450
  const rawRunId = typeof result.runId === "string" ? result.runId : typeof result.id === "string" ? result.id : undefined;
422
451
  const workflowRunId = (result.mode === "workflow" || agent === "workflow") && rawRunId ? rawRunId : undefined;
452
+ const directChild = !workflowRunId && result.results?.length === 1 ? result.results[0]! : undefined;
453
+ const directStructuredPreview = directChild
454
+ ? childInlinePreview(directChild).preview
455
+ : undefined;
456
+ const directSummary = summary.trim();
457
+ const directAgent = typeof directChild?.agent === "string" ? directChild.agent : agent;
458
+ const directNoOutputSummary = directChild && (!directSummary
459
+ || directSummary === "(no output)"
460
+ || (directAgent && directSummary === `${directAgent}:\n(no output)`));
461
+ const resultPreview = directStructuredPreview && directNoOutputSummary
462
+ ? `Structured output:\n${directStructuredPreview}`
463
+ : summary;
423
464
  const childRuns = result.results?.flatMap((child) => {
424
465
  const runId = typeof child.runId === "string" && child.runId.trim() ? child.runId.trim() : undefined;
425
466
  const workflowKey = typeof child.workflowKey === "string" && child.workflowKey.trim() ? child.workflowKey.trim() : undefined;
@@ -450,6 +491,15 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
450
491
  })
451
492
  : undefined;
452
493
  const reconciledFromDetachedChild = typeof result.reconciledFromDetachedChild === "string" ? result.reconciledFromDetachedChild : undefined;
494
+ const watchdogBlockers: SubagentNotifyWatchdogBlocker[] = [];
495
+ const collectWatchdogBlockers = (owner: string, progress: ChildWatchdogProgress | undefined) => {
496
+ for (const warning of progress?.warnings ?? []) {
497
+ if (warning.severity !== "blocker") continue;
498
+ watchdogBlockers.push({ agent: owner, summary: warning.summary, addressed: warning.addressed, stalemate: warning.stalemate });
499
+ }
500
+ };
501
+ collectWatchdogBlockers(agent, result.watchdog);
502
+ for (const child of result.results ?? []) collectWatchdogBlockers(typeof child.agent === "string" ? child.agent : agent, child.watchdog);
453
503
  const session =
454
504
  result.shareUrl
455
505
  ? { label: "Session", value: result.shareUrl }
@@ -468,13 +518,14 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
468
518
  ...(scheduleOrigin ? { scheduleOrigin } : {}),
469
519
  ...(result.source ? { source: result.source } : {}),
470
520
  ...(taskInfo ? { taskInfo } : {}),
471
- resultPreview: summary,
521
+ resultPreview,
472
522
  ...(typeof result.durationMs === "number" ? { durationMs: result.durationMs } : {}),
473
523
  ...(handoffPath ? { handoffPath } : {}),
474
524
  ...(workflowRunId ? { workflowRunId } : {}),
475
525
  ...(childRuns.length ? { childRuns } : {}),
476
526
  ...(childOutputs?.length ? { childOutputs } : {}),
477
527
  ...(reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {}),
528
+ ...(watchdogBlockers.length ? { watchdogBlockers } : {}),
478
529
  ...(session ? { sessionLabel: session.label, sessionValue: session.value } : {}),
479
530
  };
480
531
  }
@@ -28,6 +28,8 @@ import { formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetD
28
28
  import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
29
29
  import { getExternalJobProvider } from "../../api/external-job-provider.ts";
30
30
  import { formatTimeoutRecoveryLines } from "../shared/mutation-evidence.ts";
31
+ import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
32
+ import { validHostStepNodes } from "../shared/host-step-status.ts";
31
33
 
32
34
  interface RunStatusParams {
33
35
  action?: string;
@@ -74,7 +76,7 @@ function formatWorkflowDebug(status: AsyncStatus): string[] {
74
76
  status.lane ? `Lane: ${status.lane.key}${status.lane.mode ? ` (${status.lane.mode})` : ""}` : undefined,
75
77
  ].filter((line): line is string => line !== undefined);
76
78
  for (const [index, step] of (status.steps ?? []).entries()) {
77
- lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${runStatusStepDisplayName(step)} · ${step.status} · async ${step.async === undefined ? "unknown" : step.async ? "yes" : "no"}${step.runId ? ` · run ${step.runId}` : ""}${step.lane ? ` · lane ${step.lane.key}` : ""}${step.worktreePath ? ` · worktree ${step.worktreePath} · branch ${step.branch ?? "unknown"}` : ""}`);
79
+ lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${runStatusStepDisplayName(step)} · ${step.status} · async ${step.async === undefined ? "unknown" : step.async ? "yes" : "no"}${step.runId ? ` · run ${step.runId}` : ""}${step.lane ? ` · lane ${step.lane.key}` : ""}${step.worktreePath ? ` · worktree ${step.worktreePath} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}` : ""}`);
78
80
  }
79
81
  return lines;
80
82
  }
@@ -522,6 +524,14 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
522
524
  status.mode === "workflow" && workflowReturnPreview !== undefined ? `Return: ${workflowReturnPreview}` : undefined,
523
525
  status.mode === "workflow" && workflowEmitPreview !== undefined ? `Latest emit: ${workflowEmitPreview}` : undefined,
524
526
  `Progress: ${progressLabel}`,
527
+ ...(status.mode === "workflow" ? formatWorkflowChecklistText(projectWorkflowChecklist({
528
+ graph: status.workflowGraph,
529
+ steps: status.steps,
530
+ hostSteps: validHostStepNodes(status.workflowGraph),
531
+ preflight: status.preflight,
532
+ trace: status.workflow?.trace,
533
+ now: status.lastUpdate ?? status.endedAt ?? Date.now(),
534
+ }), "", { includeItems: false }) : []),
525
535
  status.pendingAppends ? `Pending appends: ${status.pendingAppends}` : undefined,
526
536
  `Started: ${started}`,
527
537
  `Updated: ${updated}`,
@@ -551,6 +561,9 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
551
561
  const display = runStatusStepDisplayName(step);
552
562
  const phase = step.phase ? `[${step.phase}] ` : "";
553
563
  lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${steeringSuffix}${acceptanceText}${budgetText}${errorText}`);
564
+ const structuredOutputPreview = step.structuredOutput === undefined ? undefined : formatWorkflowJsonPreview(step.structuredOutput, 4_000);
565
+ if (structuredOutputPreview !== undefined) lines.push(` Structured output: ${structuredOutputPreview}`);
566
+ if (step.structuredOutputPath) lines.push(` Structured output path: ${step.structuredOutputPath}`);
554
567
  lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));
555
568
  if (step.runner?.type === "external-cli") {
556
569
  const runner = normalizeExternalCliRunnerStatus(step.runner);
@@ -670,7 +683,14 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
670
683
  if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
671
684
  const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : [];
672
685
  lines.push(...formatTimeoutRecoveryLines(data.timeoutRecovery, " "));
673
- for (const child of children) lines.push(...formatTimeoutRecoveryLines(child.timeoutRecovery, " "));
686
+ for (const [index, child] of children.entries()) {
687
+ const structuredOutput = (child as { structuredOutput?: unknown }).structuredOutput;
688
+ const structuredOutputPreview = structuredOutput === undefined ? undefined : formatWorkflowJsonPreview(structuredOutput, 4_000);
689
+ if (structuredOutputPreview !== undefined) lines.push(` Structured output${children.length > 1 ? ` (${index + 1})` : ""}: ${structuredOutputPreview}`);
690
+ const structuredOutputPath = (child as { structuredOutputPath?: unknown }).structuredOutputPath;
691
+ if (typeof structuredOutputPath === "string" && structuredOutputPath.trim()) lines.push(` Structured output path${children.length > 1 ? ` (${index + 1})` : ""}: ${structuredOutputPath}`);
692
+ lines.push(...formatTimeoutRecoveryLines(child.timeoutRecovery, " "));
693
+ }
674
694
  lines.push(formatResumeGuidance(runId, children, data.sessionFile, { stopped: status === "stopped" }));
675
695
  if (data.summary) lines.push("", data.summary);
676
696
  const workflowChildren = parseWorkflowChildSummary((data as unknown as Record<string, unknown>).workflowChildren);