pi-subagents 0.66.0 → 0.68.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 (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -243,6 +243,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
243
243
  cwd: next.cwd,
244
244
  sessionId: next.sessionId ?? (typeof existing?.sessionId === "string" ? existing.sessionId : undefined),
245
245
  completionOwnerId: next.completionOwnerId,
246
+ ...(next.scheduleOrigin ? { scheduleOrigin: next.scheduleOrigin } : {}),
246
247
  },
247
248
  receipt,
248
249
  receiptPath,
@@ -294,6 +295,7 @@ export function reconcileDetachedWorkflowChildCompletion(input: {
294
295
  results,
295
296
  sessionId: plan.status.sessionId,
296
297
  completionOwnerId: plan.status.completionOwnerId,
298
+ ...(plan.status.scheduleOrigin ? { scheduleOrigin: plan.status.scheduleOrigin } : {}),
297
299
  timestamp: Date.now(),
298
300
  triggerTurn: true,
299
301
  });
@@ -6,6 +6,7 @@ import { readStatus } from "../../shared/utils.ts";
6
6
  import { steeringReceipt, waitForSteeringAction } from "../background/steering.ts";
7
7
  import { requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
8
8
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
9
+ import { workflowAsyncChildSteeringGuidance } from "../shared/workflow-async-child-guidance.ts";
9
10
 
10
11
  export interface WorkflowForegroundSteeringTarget {
11
12
  control: ForegroundRunControl;
@@ -85,7 +86,7 @@ export async function steerWorkflowRun(input: {
85
86
  }
86
87
  if (state.workflowControllers?.has(runId)) {
87
88
  const route = resolveWorkflowForegroundSteeringTarget({ state, workflowRunId: runId, asyncDirRoot: path.dirname(asyncDir) });
88
- if (!route.ok) return managementError(route.message);
89
+ if (!route.ok) return managementError([route.message, ...workflowAsyncChildSteeringGuidance(status, state)].join("\n"));
89
90
  return steerWorkflowForegroundTarget({ target: route.target, message: input.message, mode: input.mode, index: input.index });
90
91
  }
91
92
  if (!state.currentSessionId) return managementError("Workflow steering requires an active parent session.");
@@ -11,6 +11,7 @@ import type {
11
11
  AcceptanceLedger,
12
12
  AcceptanceLevel,
13
13
  AcceptanceReport,
14
+ AcceptanceReviewGate,
14
15
  AcceptanceRole,
15
16
  AcceptanceRuntimeCheck,
16
17
  AcceptanceRuntimeCheckStatus,
@@ -105,11 +106,15 @@ function inferLevel(input: {
105
106
  const inferredReadOnly = readOnlyTask || ((readOnlyAgent || input.acceptanceRole === "read-only") && !taskMayWrite);
106
107
  const roleResolvesReadOnly = input.acceptanceRole !== undefined && inferredReadOnly;
107
108
  const dynamicResolvesReadOnly = inferredReadOnly && !writeTask;
108
- const keywordRiskReadOnly = input.acceptanceRole === undefined ? intent.kind === "read-only" : inferredReadOnly;
109
+ const riskyKeywordPattern = /\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\b/;
110
+ // Topic keywords cannot override classified read-only intent; unknown tasks keep their risk gate.
111
+ const keywordRiskReadOnly = input.acceptanceRole === undefined
112
+ ? intent.kind === "read-only"
113
+ : inferredReadOnly;
109
114
  const risky = Boolean(input.async && writeTask)
110
115
  || (Boolean(input.dynamic) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
111
116
  || (Boolean(input.dynamicGroup) && !roleResolvesReadOnly && !dynamicResolvesReadOnly)
112
- || (!keywordRiskReadOnly && /\b(?:release|migration|migrate|security|data[- ]loss|destructive|post-review|fix pass)\b/.test(task));
117
+ || (!keywordRiskReadOnly && riskyKeywordPattern.test(task));
113
118
 
114
119
  if (risky) {
115
120
  reasons.push(input.async ? "async write-capable or risky run" : "risky write-capable run");
@@ -367,7 +372,7 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
367
372
  return errors;
368
373
  }
369
374
 
370
- export function validateExecutionAcceptance(input: {
375
+ type ExecutionAcceptanceInput = {
371
376
  acceptance?: unknown;
372
377
  outputSchema?: unknown;
373
378
  tasks?: Array<{ acceptance?: unknown; outputSchema?: unknown }>;
@@ -376,23 +381,39 @@ export function validateExecutionAcceptance(input: {
376
381
  outputSchema?: unknown;
377
382
  parallel?: Array<{ acceptance?: unknown; outputSchema?: unknown }> | { acceptance?: unknown; outputSchema?: unknown };
378
383
  }>;
379
- }): string[] {
384
+ };
385
+
386
+ export function validateExecutionAcceptancePolicy(input: ExecutionAcceptanceInput): string[] {
380
387
  const errors = validateAcceptanceInput(input.acceptance, "acceptance");
381
- errors.push(...validateAcceptanceReportMode(input.acceptance, input.outputSchema, "acceptance"));
382
388
  for (const [index, task] of (input.tasks ?? []).entries()) {
383
389
  errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
384
- errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `tasks[${index}].acceptance`));
385
390
  }
386
391
  for (const [stepIndex, step] of (input.chain ?? []).entries()) {
387
392
  errors.push(...validateAcceptanceInput(step.acceptance, `chain[${stepIndex}].acceptance`));
388
- errors.push(...validateAcceptanceReportMode(step.acceptance, step.outputSchema, `chain[${stepIndex}].acceptance`));
389
393
  if (Array.isArray(step.parallel)) {
390
394
  for (const [taskIndex, task] of step.parallel.entries()) {
391
395
  errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
392
- errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
393
396
  }
394
397
  } else if (step.parallel) {
395
398
  errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
399
+ }
400
+ }
401
+ return errors;
402
+ }
403
+
404
+ export function validateExecutionAcceptance(input: ExecutionAcceptanceInput): string[] {
405
+ const errors = validateExecutionAcceptancePolicy(input);
406
+ errors.push(...validateAcceptanceReportMode(input.acceptance, input.outputSchema, "acceptance"));
407
+ for (const [index, task] of (input.tasks ?? []).entries()) {
408
+ errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `tasks[${index}].acceptance`));
409
+ }
410
+ for (const [stepIndex, step] of (input.chain ?? []).entries()) {
411
+ errors.push(...validateAcceptanceReportMode(step.acceptance, step.outputSchema, `chain[${stepIndex}].acceptance`));
412
+ if (Array.isArray(step.parallel)) {
413
+ for (const [taskIndex, task] of step.parallel.entries()) {
414
+ errors.push(...validateAcceptanceReportMode(task.acceptance, task.outputSchema, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
415
+ }
416
+ } else if (step.parallel) {
396
417
  errors.push(...validateAcceptanceReportMode(step.parallel.acceptance, step.parallel.outputSchema, `chain[${stepIndex}].parallel.acceptance`));
397
418
  }
398
419
  }
@@ -405,7 +426,7 @@ function validateAcceptanceReportMode(acceptance: unknown, outputSchema: unknown
405
426
  acceptance = normalized.value;
406
427
  if (!acceptance || typeof acceptance !== "object" || Array.isArray(acceptance)) return [];
407
428
  if (!("report" in acceptance)) return [];
408
- return outputSchema === undefined ? [`${pathLabel}.report requires outputSchema.`] : [];
429
+ return outputSchema === undefined || outputSchema === false ? [`${pathLabel}.report requires outputSchema.`] : [];
409
430
  }
410
431
 
411
432
  function normalizeCriteria(criteria: Array<string | { id?: string; must?: string; evidence?: AcceptanceEvidenceKind[]; severity?: "required" | "recommended" }> | undefined, evidence: AcceptanceEvidenceKind[]): ResolvedAcceptanceGate[] {
@@ -486,6 +507,12 @@ function acceptanceRequiresChildReport(acceptance: ResolvedAcceptanceConfig): bo
486
507
  return acceptance.criteria.length > 0 || acceptance.evidence.length > 0;
487
508
  }
488
509
 
510
+ /** Label a declared review gate the same way in prompts and capability summaries. */
511
+ export function formatReviewGateLabel(review: AcceptanceReviewGate): string {
512
+ const status = review.required === false ? "optional" : "required";
513
+ return review.agent ? `${status} by ${review.agent}` : status;
514
+ }
515
+
489
516
  export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig, options: { reportOptional?: boolean; structuredOutput?: boolean } = {}): string {
490
517
  if (acceptance.level === "none") return "";
491
518
  if (options.reportOptional && !acceptanceRequiresChildReport(acceptance)) return "";
@@ -505,7 +532,7 @@ export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig, opt
505
532
  for (const command of acceptance.verify) lines.push(`- ${command.id}: ${command.command}`);
506
533
  }
507
534
  if (acceptance.review) {
508
- lines.push("", `Review gate: ${acceptance.review.required === false ? "optional" : "required"}${acceptance.review.agent ? ` by ${acceptance.review.agent}` : ""}.`);
535
+ lines.push("", `Review gate: ${formatReviewGateLabel(acceptance.review)}.`);
509
536
  if (acceptance.review.focus) lines.push(`Review focus: ${acceptance.review.focus}`);
510
537
  }
511
538
  if (acceptance.stopRules.length > 0) {
@@ -1394,7 +1421,7 @@ export async function evaluateAcceptance(input: {
1394
1421
  if (input.watchdog) {
1395
1422
  const unresolved = unresolvedChildWatchdogBlockers(input.watchdog);
1396
1423
  ledger.runtimeChecks.push(unresolved.length
1397
- ? { id: "watchdog-blocker", status: "failed", message: `Unresolved watchdog blocker: ${unresolved[0]!.summary}` }
1424
+ ? { id: "watchdog-blocker", status: "failed", message: "Unresolved watchdog blocker (details are available in child watchdog status)." }
1398
1425
  : { id: "watchdog-blocker", status: "passed", message: "No unresolved watchdog blockers." });
1399
1426
  }
1400
1427
 
@@ -108,6 +108,8 @@ export interface AsyncStatusWorkflowRow {
108
108
  modelThinking?: string;
109
109
  activity?: string;
110
110
  startedAt?: number;
111
+ endedAt?: number;
112
+ durationMs?: number;
111
113
  tokens?: number;
112
114
  window?: number;
113
115
  overflow?: number;
@@ -222,16 +224,25 @@ function appendBoundedChildren(children: AsyncStatusSnapshotNode[], source: read
222
224
  ctx.omitted.children += Math.max(0, source.length - remaining);
223
225
  }
224
226
 
227
+ function stepIdentity(step: AsyncJobStep | NestedStepSummary, index: number, ctx: ProjectionContext) {
228
+ return {
229
+ id: publicText("workflowKey" in step && step.workflowKey ? step.workflowKey : "runId" in step && step.runId ? step.runId : `step:${index}`, `step:${index}`, ctx.caps.maxStringLength),
230
+ label: publicText("label" in step && step.label ? step.label : step.agent, "step", ctx.caps.maxStringLength),
231
+ };
232
+ }
233
+
225
234
  function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
226
235
  const state = normalizeState(step.status);
227
236
  const startedAt = publicTime(step.startedAt);
228
- const endedAt = publicTime(step.endedAt);
237
+ const durationMs = "durationMs" in step ? publicTime(step.durationMs) : undefined;
238
+ const endedAt = publicTime(step.endedAt) ?? (terminalState(state) && startedAt !== undefined && durationMs !== undefined ? publicTime(startedAt + durationMs) : undefined);
229
239
  const updatedAt = endedAt ?? publicTime(step.lastActivityAt) ?? startedAt;
230
240
  const activity = activityFor(step, ctx);
241
+ const { id, label } = stepIdentity(step, index, ctx);
231
242
  const node: AsyncStatusSnapshotNode = {
232
- id: publicText("workflowKey" in step && step.workflowKey ? step.workflowKey : "runId" in step && step.runId ? step.runId : `step:${index}`, `step:${index}`, ctx.caps.maxStringLength),
243
+ id,
233
244
  kind: "step",
234
- label: publicText("label" in step && step.label ? step.label : step.agent, "step", ctx.caps.maxStringLength),
245
+ label,
235
246
  state,
236
247
  ...(startedAt !== undefined ? { startedAt } : {}),
237
248
  ...(updatedAt !== undefined ? { updatedAt } : {}),
@@ -249,16 +260,6 @@ function projectStep(step: AsyncJobStep | NestedStepSummary, index: number, dept
249
260
  return node;
250
261
  }
251
262
 
252
- function projectWorkflowGraphNode(node: WorkflowGraphSnapshot["nodes"][number], index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
253
- const step: AsyncJobStep = {
254
- agent: node.agent ?? node.label,
255
- status: workflowGraphStepStatus(node.status),
256
- workflowKey: node.id,
257
- label: node.label,
258
- };
259
- return projectStep(step, node.flatIndex ?? index, depth, ctx);
260
- }
261
-
262
263
  function projectNestedRun(child: NestedRunSummary, index: number, depth: number, ctx: ProjectionContext): AsyncStatusSnapshotNode {
263
264
  const state = normalizeState(child.state);
264
265
  const startedAt = publicTime(child.startedAt);
@@ -322,7 +323,86 @@ function projectHostStep(hostStep: HostStepNode, ctx: ProjectionContext): AsyncS
322
323
  };
323
324
  }
324
325
 
325
- function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnapshotNode {
326
+ type MaterializedChildren = Map<string, AsyncJobState[]>;
327
+
328
+ function asyncJobOrder(left: AsyncJobState, right: AsyncJobState): number {
329
+ const leftRank = left.status === "running" ? 0 : left.status === "queued" ? 1 : 2;
330
+ const rightRank = right.status === "running" ? 0 : right.status === "queued" ? 1 : 2;
331
+ const leftTime = left.updatedAt ?? left.startedAt ?? 0;
332
+ const rightTime = right.updatedAt ?? right.startedAt ?? 0;
333
+ return leftRank - rightRank || rightTime - leftTime || left.asyncId.localeCompare(right.asyncId);
334
+ }
335
+
336
+ function hasCyclicWorkflowParent(job: AsyncJobState, parents: ReadonlyMap<string, AsyncJobState>): boolean {
337
+ const seen = new Set([job.asyncId]);
338
+ let parentId = job.parentWorkflowRunId;
339
+ while (parentId) {
340
+ if (seen.has(parentId)) return true;
341
+ seen.add(parentId);
342
+ parentId = parents.get(parentId)?.parentWorkflowRunId;
343
+ }
344
+ return false;
345
+ }
346
+
347
+ function groupMaterializedChildren(jobs: readonly AsyncJobState[]) {
348
+ const parents = new Map(jobs.filter((job) => job.mode === "workflow").map((job) => [job.asyncId, job]));
349
+ const childrenByParent: MaterializedChildren = new Map();
350
+ const liveRoots = new Set<AsyncJobState>();
351
+ const roots: AsyncJobState[] = [];
352
+ for (const job of jobs) {
353
+ const parent = job.parentWorkflowRunId ? parents.get(job.parentWorkflowRunId) : undefined;
354
+ const keepLiveRoot = (job.status === "running" || job.status === "queued") && parent?.status !== "running";
355
+ if (parent && !hasCyclicWorkflowParent(job, parents)) {
356
+ const siblings = childrenByParent.get(parent.asyncId) ?? [];
357
+ siblings.push(job);
358
+ childrenByParent.set(parent.asyncId, siblings);
359
+ if (keepLiveRoot) {
360
+ liveRoots.add(job);
361
+ roots.push(job);
362
+ }
363
+ } else {
364
+ roots.push(job);
365
+ }
366
+ }
367
+ for (const children of childrenByParent.values()) children.sort(asyncJobOrder);
368
+ return { roots, childrenByParent, liveRoots };
369
+ }
370
+
371
+ function assignMaterializedChildren(steps: readonly AsyncJobStep[], children: readonly AsyncJobState[]): Map<AsyncJobStep, AsyncJobState> {
372
+ const assigned = new Map<AsyncJobStep, AsyncJobState>();
373
+ const claimed = new Set<AsyncJobState>();
374
+ for (const step of steps) {
375
+ if (!step.runId) continue;
376
+ const child = children.find((candidate) => !claimed.has(candidate) && candidate.asyncId === step.runId);
377
+ if (child) {
378
+ assigned.set(step, child);
379
+ claimed.add(child);
380
+ }
381
+ }
382
+ for (const step of steps) {
383
+ if (assigned.has(step) || !step.workflowKey) continue;
384
+ const child = children.find((candidate) => !claimed.has(candidate) && candidate.workflowKey === step.workflowKey);
385
+ if (child) {
386
+ assigned.set(step, child);
387
+ claimed.add(child);
388
+ }
389
+ }
390
+ return assigned;
391
+ }
392
+
393
+ function projectMaterializedStep(step: AsyncJobStep, index: number, child: AsyncJobState, depth: number, ctx: ProjectionContext, childrenByParent: MaterializedChildren, liveRoots: ReadonlySet<AsyncJobState>): AsyncStatusSnapshotNode {
394
+ const { id, label } = stepIdentity(step, index, ctx);
395
+ const selfStep = child.mode === undefined || child.mode === "single" ? child.steps?.[0] : undefined;
396
+ const live = projectRun(child, ctx, depth, childrenByParent, liveRoots, selfStep !== undefined);
397
+ const laneActivity = activityFor(step, ctx);
398
+ const selfActivity = selfStep ? activityFor(selfStep, ctx) : undefined;
399
+ const activity = laneActivity || selfActivity || live.activity ? { ...laneActivity, ...selfActivity, ...live.activity } : undefined;
400
+ const node: AsyncStatusSnapshotNode = { ...live, id, kind: "step", label };
401
+ if (activity) node.activity = activity;
402
+ return node;
403
+ }
404
+
405
+ function projectRun(job: AsyncJobState, ctx: ProjectionContext, depth: number, childrenByParent: MaterializedChildren, liveRoots: ReadonlySet<AsyncJobState>, omitSteps = false): AsyncStatusSnapshotNode {
326
406
  const state = normalizeState(job.status);
327
407
  const startedAt = publicTime(job.startedAt);
328
408
  const updatedAt = publicTime(job.updatedAt) ?? startedAt;
@@ -337,16 +417,32 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
337
417
  ...(terminalState(state) && updatedAt !== undefined ? { endedAt: updatedAt } : {}),
338
418
  ...(activity ? { activity } : {}),
339
419
  };
340
- if (ctx.caps.maxDepth > 0) {
341
- const stepChildren = job.steps?.map((step, index) => projectStep(step, step.index ?? index, 1, ctx)) ?? [];
342
- const loadedKeys = new Set(job.steps?.flatMap((step) => step.workflowKey ? [step.workflowKey] : []) ?? []);
343
- const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
344
- const graphChildren = graphStages
345
- .filter((graphNode) => !loadedKeys.has(graphNode.id))
346
- .map((graphNode, index) => projectWorkflowGraphNode(graphNode, index, 1, ctx));
347
- const nestedChildren = job.nestedChildren?.map((child, index) => projectNestedRun(child, index, 1, ctx)) ?? [];
420
+ const steps = omitSteps ? [] : job.steps ?? [];
421
+ const materialized = childrenByParent.get(job.asyncId) ?? [];
422
+ const nestedSummaries = (job.nestedChildren ?? []).filter((child) => !materialized.some((live) => live.asyncId === child.id));
423
+ const loadedKeys = new Set(steps.flatMap((step) => step.workflowKey ? [step.workflowKey] : []));
424
+ const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph).filter((graphNode) => !loadedKeys.has(graphNode.id)) : [];
425
+ const graphSteps = graphStages.map((node, index) => ({
426
+ step: { agent: node.agent ?? node.label, status: workflowGraphStepStatus(node.status), workflowKey: node.id, label: node.label },
427
+ index: node.flatIndex ?? index,
428
+ }));
429
+ const lanes = [...steps, ...graphSteps.map(({ step }) => step)];
430
+ const assigned = assignMaterializedChildren(lanes, materialized);
431
+ const claimed = new Set(assigned.values());
432
+ if (depth < ctx.caps.maxDepth) {
433
+ const childDepth = depth + 1;
434
+ const projectLane = (step: AsyncJobStep, index: number): AsyncStatusSnapshotNode | undefined => {
435
+ const child = assigned.get(step);
436
+ if (!child) return projectStep(step, index, childDepth, ctx);
437
+ if (liveRoots.has(child)) return undefined;
438
+ return projectMaterializedStep(step, index, child, childDepth, ctx, childrenByParent, liveRoots);
439
+ };
440
+ const stepChildren = steps.map((step, index) => projectLane(step, step.index ?? index)).filter((child): child is AsyncStatusSnapshotNode => child !== undefined);
441
+ const graphChildren = graphSteps.map(({ step, index }) => projectLane(step, index)).filter((child): child is AsyncStatusSnapshotNode => child !== undefined);
442
+ const nestedChildren = nestedSummaries.map((child, index) => projectNestedRun(child, index, childDepth, ctx));
443
+ const unclaimedChildren = materialized.filter((child) => !claimed.has(child) && !liveRoots.has(child)).map((child) => projectRun(child, ctx, childDepth, childrenByParent, liveRoots));
348
444
  const hostStepChildren = validHostStepList(job.hostSteps).map((hostStep) => projectHostStep(hostStep, ctx));
349
- const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren];
445
+ const ordinaryChildren = [...stepChildren, ...graphChildren, ...nestedChildren, ...unclaimedChildren];
350
446
  const retainedHostSteps = hostStepChildren.slice(0, ctx.caps.maxChildrenPerNode);
351
447
  const retainedOrdinaryChildren = ordinaryChildren.slice(0, ctx.caps.maxChildrenPerNode - retainedHostSteps.length);
352
448
  const bounded: AsyncStatusSnapshotNode[] = [];
@@ -354,10 +450,9 @@ function projectRun(job: AsyncJobState, ctx: ProjectionContext): AsyncStatusSnap
354
450
  ctx.omitted.children += ordinaryChildren.length - retainedOrdinaryChildren.length + hostStepChildren.length - retainedHostSteps.length;
355
451
  if (bounded.length) node.children = bounded;
356
452
  } else {
357
- const loadedKeys = new Set(job.steps?.flatMap((step) => step.workflowKey ? [step.workflowKey] : []) ?? []);
358
- const graphStages = job.mode === "workflow" ? workflowGraphStageNodes(job.workflowGraph) : [];
359
- const graphCount = graphStages.filter((graphNode) => !loadedKeys.has(graphNode.id)).length;
360
- ctx.omitted.children += (job.steps?.length ?? 0) + graphCount + (job.nestedChildren?.length ?? 0) + validHostStepList(job.hostSteps).length;
453
+ const rootAssignments = [...assigned.values()].filter((child) => liveRoots.has(child)).length;
454
+ const unclaimedChildren = materialized.filter((child) => !claimed.has(child) && !liveRoots.has(child)).length;
455
+ ctx.omitted.children += lanes.length - rootAssignments + nestedSummaries.length + unclaimedChildren + validHostStepList(job.hostSteps).length;
361
456
  }
362
457
  return node;
363
458
  }
@@ -536,6 +631,8 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
536
631
  ...(modelThinking ? { modelThinking } : {}),
537
632
  ...(activity ? { activity } : {}),
538
633
  ...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
634
+ ...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
635
+ ...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
539
636
  ...(step.tokens?.total !== undefined ? { tokens: step.tokens.total } : {}),
540
637
  ...(step.tokens?.window !== undefined ? { window: step.tokens.window } : {}),
541
638
  ...(preflight ? { preflight } : {}),
@@ -546,11 +643,8 @@ function projectLoadedWorkflowRow(step: AsyncJobStep, index: number, preflight?:
546
643
  export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, options: AsyncStatusSnapshotOptions = {}): AsyncStatusSnapshot {
547
644
  const caps = resolveCaps(options);
548
645
  const ctx: ProjectionContext = { caps, omitted: { runs: 0, children: 0, byteLimitExceeded: false } };
549
- const sorted = [...jobs].sort((left, right) => {
550
- const leftUpdated = left.updatedAt ?? left.startedAt ?? 0;
551
- const rightUpdated = right.updatedAt ?? right.startedAt ?? 0;
552
- return rightUpdated - leftUpdated || left.asyncId.localeCompare(right.asyncId);
553
- });
646
+ const { roots, childrenByParent, liveRoots } = groupMaterializedChildren([...jobs]);
647
+ const sorted = roots.sort(asyncJobOrder);
554
648
  ctx.omitted.runs += Math.max(0, sorted.length - caps.maxRuns);
555
649
  const snapshot: AsyncStatusSnapshot = {
556
650
  kind: ASYNC_STATUS_SNAPSHOT_KIND,
@@ -558,7 +652,7 @@ export function projectAsyncStatusSnapshot(jobs: Iterable<AsyncJobState>, option
558
652
  generatedAt: options.generatedAt ?? Date.now(),
559
653
  caps,
560
654
  omitted: ctx.omitted,
561
- runs: sorted.slice(0, caps.maxRuns).map((job) => projectRun(job, ctx)),
655
+ runs: sorted.slice(0, caps.maxRuns).map((job) => projectRun(job, ctx, 0, childrenByParent, liveRoots)),
562
656
  };
563
657
  enforceByteLimit(snapshot);
564
658
  return snapshot;
@@ -29,6 +29,8 @@ export interface SubagentCapabilityAudit {
29
29
  effectiveMcpTools: string[];
30
30
  agentAllowed: boolean;
31
31
  agentRestrictionSources?: string[];
32
+ /** Builtin tools declared but unavailable on the host runtime. */
33
+ unavailableHostBuiltins?: string[];
32
34
  }
33
35
 
34
36
  export interface RegisterSubagentCapabilityCeilingOptions {
@@ -1,12 +1,13 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { listBackgroundWorkProviders } from "../../api/background-work.ts";
3
3
  import { ReadonlyDrainObservation } from "./readonly-drain-observation.ts";
4
- import registerFanoutChildSubagentExtension from "../../extension/fanout-child.ts";
4
+ import registerFanoutChildSubagentExtension, { createChildSafeState } from "../../extension/fanout-child.ts";
5
5
  import registerSubagentFastModeExtension from "./fast-mode-extension.ts";
6
6
  import registerSubagentPromptRuntime from "./subagent-prompt-runtime.ts";
7
7
  import type { ChildRuntimeConfig } from "./child-runtime-config.ts";
8
8
  import type { ChildToolDiagnostic } from "./tool-availability.ts";
9
9
  import type { ChildSessionLaunch } from "./child-session.ts";
10
+ import type { ArbiterModelContext } from "./llm-intent-arbiter.ts";
10
11
  import type { ChildTranscriptWriter } from "../../shared/child-transcript.ts";
11
12
  import { projectRuntimeAcknowledgedExtensions } from "./runtime-acknowledged-extensions.ts";
12
13
 
@@ -130,17 +131,22 @@ export function createChildHooks(config: ChildRuntimeConfig): ChildHookExtension
130
131
  export function createCapturedChildHooks(config: ChildRuntimeConfig, runner = false) {
131
132
  let diagnostic: ChildToolDiagnostic | undefined;
132
133
  let acknowledgedIds: string[] | undefined;
133
- let completionIntentContext: Pick<ExtensionContext, "model" | "modelRegistry"> | undefined;
134
+ let completionIntentContext: ArbiterModelContext | undefined;
135
+ let finalDrainHeld = false;
134
136
  const capture: OwnedCapture = {
135
137
  toolDiagnostic: (value) => { diagnostic = value; },
136
138
  runtimeAcknowledgements: (ids) => { acknowledgedIds = ids; },
137
139
  };
138
140
  Object.assign(config, capture);
139
- const hooks = childHooks(config, capture);
141
+ const hooks = childHooks(config, capture, (held) => { finalDrainHeld = held; });
140
142
  if (runner) {
141
143
  hooks.push({ name: "pi-subagents:completion-intent", factory: (pi) => pi.on("session_start", (_event, childCtx) => {
142
- // Retain only attempt model services, not the live child session.
143
- completionIntentContext = { model: childCtx.model, modelRegistry: childCtx.modelRegistry };
144
+ // Retain only attempt model services and the session id string, not the live child session.
145
+ completionIntentContext = {
146
+ model: childCtx.model,
147
+ modelRegistry: childCtx.modelRegistry,
148
+ sessionId: childCtx.sessionManager.getSessionId(),
149
+ };
144
150
  }) });
145
151
  const proof = promptProofs.get(hooks[0]!.factory);
146
152
  if (proof) proof.factories = hooks.map((hook) => hook.factory);
@@ -150,18 +156,27 @@ export function createCapturedChildHooks(config: ChildRuntimeConfig, runner = fa
150
156
  completionIntentContext: () => completionIntentContext,
151
157
  toolDiagnostic: () => diagnostic,
152
158
  runtimeAcknowledgedExtensions: () => acknowledgedIds ? projectRuntimeAcknowledgedExtensions(acknowledgedIds) : undefined,
159
+ finalDrainHeld: () => finalDrainHeld,
153
160
  };
154
161
  }
155
162
 
156
- function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture): ChildHookExtension[] {
163
+ function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture, holdFinalDrain?: (held: boolean) => void): ChildHookExtension[] {
157
164
  const snapshot = readonlyConfig(config, capture);
158
165
  const proof: PromptProof | undefined = snapshot === undefined ? undefined : { config, snapshot, capture };
166
+ const runtime = Object.create(config) as ChildRuntimeConfig;
167
+ const ownedState = Object.getOwnPropertyDescriptor(config, "runtimeState");
168
+ if (!ownedState || !("value" in ownedState) || ownedState.value == null) {
169
+ Object.defineProperty(runtime, "runtimeState", { configurable: true, enumerable: true, writable: true, value: createChildSafeState() });
170
+ }
171
+ if (holdFinalDrain) {
172
+ Object.defineProperty(runtime, "holdFinalDrain", { configurable: true, enumerable: true, writable: true, value: holdFinalDrain });
173
+ }
159
174
  const hooks: ChildHookExtension[] = [
160
175
  { name: "pi-subagents:prompt-runtime", factory: function promptRuntime(pi) {
161
- if (!proof?.observeNext) return registerSubagentPromptRuntime(pi, config);
176
+ if (!proof?.observeNext) return registerSubagentPromptRuntime(pi, runtime);
162
177
  proof.observation = proof.observeNext;
163
178
  proof.observeNext = undefined;
164
- registerSubagentPromptRuntime(pi, config, proof.observation);
179
+ registerSubagentPromptRuntime(pi, runtime, proof.observation);
165
180
  } },
166
181
  ];
167
182
  if (proof) {
@@ -169,6 +184,6 @@ function childHooks(config: ChildRuntimeConfig, capture?: OwnedCapture): ChildHo
169
184
  promptProofs.set(hooks[0]!.factory, proof);
170
185
  }
171
186
  if (config.fast) hooks.push({ name: "pi-subagents:fast-mode", factory: (pi) => registerSubagentFastModeExtension(pi) });
172
- if (config.fanoutChild) hooks.push({ name: "pi-subagents:fanout-child", factory: (pi) => registerFanoutChildSubagentExtension(pi, config) });
187
+ if (config.fanoutChild) hooks.push({ name: "pi-subagents:fanout-child", factory: (pi) => registerFanoutChildSubagentExtension(pi, runtime) });
173
188
  return hooks;
174
189
  }
@@ -1,7 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import type { AgentConfig } from "../../agents/agents.ts";
3
3
  import { resolveChildCwd } from "../../shared/utils.ts";
4
- import type { OutputMode } from "../../shared/types.ts";
4
+ import type { JsonSchemaObject, OutputMode } from "../../shared/types.ts";
5
5
  import { resolveSingleOutputPath } from "./single-output.ts";
6
6
 
7
7
  export interface ResolvedStepBehavior {
@@ -12,6 +12,7 @@ export interface ResolvedStepBehavior {
12
12
  skills: string[] | false;
13
13
  model?: string;
14
14
  fast?: boolean;
15
+ outputSchema?: JsonSchemaObject;
15
16
  }
16
17
 
17
18
  export type OutputOverrideInput = string | boolean;
@@ -24,6 +25,7 @@ export interface StepOverrides {
24
25
  skills?: string[] | false;
25
26
  model?: string;
26
27
  fast?: boolean;
28
+ outputSchema?: JsonSchemaObject | false;
27
29
  }
28
30
 
29
31
  export interface ChildLaunchPlanInput {
@@ -35,6 +37,7 @@ export interface ChildLaunchPlanInput {
35
37
  runtimeCwd: string;
36
38
  stepCwdInput?: string;
37
39
  behaviorCwd?: string;
40
+ machineCwd?: string;
38
41
  chainSkills?: string[];
39
42
  outputBaseDir?: string;
40
43
  parallelOutputNamespace?: { stepIndex: number; taskIndex?: number };
@@ -58,6 +61,14 @@ export function normalizeOutputOverride(output: unknown): string | false | undef
58
61
  return typeof output === "string" && output.length > 0 ? output : undefined;
59
62
  }
60
63
 
64
+ export const resolveEffectiveOutputSchema = (agentConfig: AgentConfig, override?: JsonSchemaObject | false): JsonSchemaObject | undefined => override === false ? undefined : override !== undefined ? override : agentConfig.outputSchema;
65
+
66
+ type OutputSchemaStep = { agent: string; outputSchema?: JsonSchemaObject | false };
67
+ export function projectChainOutputSchemas<S extends OutputSchemaStep, R = S, G = { parallel: R[] | R }>(chain: readonly (S | { parallel: S[] | S })[], agents: AgentConfig[], projectStep?: (step: S, outputSchema: JsonSchemaObject | undefined) => R, projectGroup?: (step: { parallel: S[] | S }, parallel: R[] | R) => G): Array<R | G> {
68
+ const project = (step: S): R => { const agent = agents.find((candidate) => candidate.name === step.agent); if (!agent && !projectStep) return step as unknown as R; const outputSchema = agent && resolveEffectiveOutputSchema(agent, step.outputSchema); return projectStep ? projectStep(step, outputSchema) : { ...step, outputSchema } as unknown as R; };
69
+ return chain.map((step) => { if (!("parallel" in step)) return project(step as S); const parallel = Array.isArray(step.parallel) ? step.parallel.map(project) : project(step.parallel as S); return projectGroup ? projectGroup(step, parallel) : { ...step, parallel } as unknown as G; });
70
+ }
71
+
61
72
  export function resolveStepBehavior(
62
73
  agentConfig: AgentConfig,
63
74
  stepOverrides: StepOverrides,
@@ -94,7 +105,8 @@ export function resolveStepBehavior(
94
105
  const outputMode = stepOverrides.outputMode ?? agentConfig.outputMode ?? "inline";
95
106
  const model = stepOverrides.model ?? agentConfig.model;
96
107
  const fast = stepOverrides.fast ?? agentConfig.fast;
97
- return { output, outputMode, reads, progress, skills, model, fast };
108
+ const outputSchema = resolveEffectiveOutputSchema(agentConfig, stepOverrides.outputSchema);
109
+ return { output, outputMode, reads, progress, skills, model, fast, ...(outputSchema !== undefined ? { outputSchema } : {}) };
98
110
  }
99
111
 
100
112
  export function resolveTaskTextForFileUpdatePolicy(task: string | undefined, originalTask?: string): string | undefined {
@@ -117,7 +129,7 @@ export function suppressProgressForReadOnlyTask(behavior: ResolvedStepBehavior,
117
129
  }
118
130
 
119
131
  export function planChildLaunch(input: ChildLaunchPlanInput): ChildLaunchPlan {
120
- const stepCwd = resolveChildCwd(input.runnerCwd, input.stepCwdInput);
132
+ const stepCwd = input.machineCwd ?? resolveChildCwd(input.runnerCwd, input.stepCwdInput);
121
133
  const instructionCwd = input.behaviorCwd ?? stepCwd;
122
134
  const readExistenceCwd = input.behaviorCwd ? stepCwd : instructionCwd;
123
135
  let behavior = suppressProgressForReadOnlyTask(