pi-subagents 0.55.0 → 0.57.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 (97) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/agents/claude-code-writer.md +15 -0
  3. package/agents/claude-code.md +15 -0
  4. package/agents/codex-exec-writer.md +15 -0
  5. package/agents/codex-exec.md +15 -0
  6. package/agents/cursor-agent-writer.md +14 -0
  7. package/agents/cursor-agent.md +14 -0
  8. package/docs/agents.md +111 -15
  9. package/docs/configuration.md +37 -0
  10. package/docs/extension-api.md +2 -2
  11. package/docs/models.md +6 -0
  12. package/docs/observability.md +8 -7
  13. package/docs/tool-reference.md +11 -1
  14. package/docs/workflows.md +20 -0
  15. package/package.json +1 -1
  16. package/skills/pi-subagents/references/execution-controls.md +1 -1
  17. package/src/agents/agent-management.ts +13 -5
  18. package/src/agents/agent-refinements.ts +4 -4
  19. package/src/agents/agent-serializer.ts +2 -0
  20. package/src/agents/agents.ts +242 -49
  21. package/src/agents/builtin-names.ts +6 -0
  22. package/src/agents/runtime-agent-registry.ts +8 -2
  23. package/src/api/preflight.ts +22 -3
  24. package/src/extension/config.ts +70 -0
  25. package/src/extension/doctor.ts +3 -3
  26. package/src/extension/index.ts +18 -4
  27. package/src/extension/public-execution.ts +29 -13
  28. package/src/extension/rpc.ts +20 -5
  29. package/src/extension/schemas.ts +9 -3
  30. package/src/extension/tool-description.ts +11 -9
  31. package/src/inspectors/herdr/actions.ts +2 -1
  32. package/src/inspectors/herdr/inspector-runner.ts +2 -10
  33. package/src/inspectors/herdr/session-roots-codec.ts +42 -0
  34. package/src/integrations/herdr-status.ts +46 -3
  35. package/src/runs/background/active-async-capacity.ts +77 -10
  36. package/src/runs/background/async-execution.ts +148 -27
  37. package/src/runs/background/async-job-tracker.ts +5 -0
  38. package/src/runs/background/async-resume.ts +3 -0
  39. package/src/runs/background/async-retention.ts +20 -3
  40. package/src/runs/background/async-status.ts +5 -0
  41. package/src/runs/background/chain-root-attachment.ts +15 -1
  42. package/src/runs/background/fleet-view.ts +16 -10
  43. package/src/runs/background/inspect-rpc.ts +8 -8
  44. package/src/runs/background/result-files.ts +27 -14
  45. package/src/runs/background/result-watcher.ts +9 -5
  46. package/src/runs/background/run-status.ts +33 -6
  47. package/src/runs/background/scheduled-runs.ts +7 -1
  48. package/src/runs/background/subagent-runner.ts +254 -49
  49. package/src/runs/background/wait-completions.ts +4 -0
  50. package/src/runs/foreground/execution.ts +107 -16
  51. package/src/runs/foreground/foreground-control.ts +6 -0
  52. package/src/runs/foreground/foreground-history.ts +25 -3
  53. package/src/runs/foreground/subagent-executor.ts +324 -84
  54. package/src/runs/foreground/workflow-detach-reconcile.ts +31 -7
  55. package/src/runs/shared/acceptance.ts +10 -5
  56. package/src/runs/shared/agent-contract.ts +1 -1
  57. package/src/runs/shared/child-protocol.ts +21 -7
  58. package/src/runs/shared/claude-code-adapter.ts +129 -0
  59. package/src/runs/shared/codex-exec-adapter.ts +129 -0
  60. package/src/runs/shared/completion-guard.ts +3 -2
  61. package/src/runs/shared/cursor-agent-adapter.ts +114 -0
  62. package/src/runs/shared/dynamic-fanout.ts +1 -1
  63. package/src/runs/shared/extension-bindings.ts +78 -0
  64. package/src/runs/shared/external-cli-contract.ts +167 -0
  65. package/src/runs/shared/external-cli-preflight.ts +122 -0
  66. package/src/runs/shared/external-cli-runner.ts +349 -54
  67. package/src/runs/shared/fast-mode-extension.ts +10 -0
  68. package/src/runs/shared/model-exclusions.ts +59 -8
  69. package/src/runs/shared/model-fallback.ts +25 -8
  70. package/src/runs/shared/mutation-evidence.ts +150 -0
  71. package/src/runs/shared/nested-events.ts +3 -1
  72. package/src/runs/shared/nested-render.ts +2 -2
  73. package/src/runs/shared/parallel-utils.ts +3 -0
  74. package/src/runs/shared/pi-args.ts +47 -0
  75. package/src/runs/shared/process-signal.ts +13 -0
  76. package/src/runs/shared/run-history.ts +21 -1
  77. package/src/runs/shared/structured-output.ts +18 -4
  78. package/src/runs/shared/subagent-prompt-runtime.ts +9 -5
  79. package/src/shared/fork-context.ts +21 -0
  80. package/src/shared/formatters.ts +7 -1
  81. package/src/shared/launch-contract.ts +6 -0
  82. package/src/shared/pruned-fork.ts +450 -0
  83. package/src/shared/session-file-trust.ts +19 -0
  84. package/src/shared/session-tokens.ts +14 -3
  85. package/src/shared/settings.ts +16 -3
  86. package/src/shared/shortcuts.ts +17 -0
  87. package/src/shared/types.ts +166 -9
  88. package/src/shared/workflow-child-permit.ts +116 -0
  89. package/src/slash/delegation-adapters.ts +0 -1
  90. package/src/slash/slash-commands.ts +8 -6
  91. package/src/tui/fleet-status.ts +27 -10
  92. package/src/tui/fleet-transcript.ts +11 -5
  93. package/src/tui/fleet.ts +28 -13
  94. package/src/tui/render.ts +47 -21
  95. package/src/workflows/scripted-workflow.ts +298 -30
  96. package/src/workflows/workflow-child-summary.ts +117 -0
  97. package/src/workflows/workflow-receipt.ts +119 -4
@@ -10,7 +10,7 @@ import * as path from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { createRequire } from "node:module";
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
- import type { AgentConfig } from "../../agents/agents.ts";
13
+ import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
14
14
  import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
15
15
  import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
16
16
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
@@ -39,6 +39,7 @@ import { validateImplementationToolContract } from "../shared/completion-guard.t
39
39
  import {
40
40
  type AcceptanceInput,
41
41
  type AgentContract,
42
+ type AsyncParallelGroupStatus,
42
43
  type AsyncStatus,
43
44
  type ArtifactConfig,
44
45
  type Details,
@@ -70,10 +71,12 @@ import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
70
71
  import type { SessionLeaseRequest } from "../shared/session-lease.ts";
71
72
  import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
72
73
  import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
74
+ import { statusStepDescription } from "./chain-append.ts";
73
75
  import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
74
76
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
75
77
  import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
76
78
  import { resolvePermissionRules, type PermissionConfig } from "../shared/permissions.ts";
79
+ import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBindings } from "../shared/extension-bindings.ts";
77
80
 
78
81
  const require = createRequire(import.meta.url);
79
82
  const piPackageRoot = resolvePiPackageRoot();
@@ -150,6 +153,8 @@ interface AsyncChainParams {
150
153
  attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
151
154
  resultMode?: SubagentRunMode;
152
155
  agents: AgentConfig[];
156
+ /** Original discovery provenance, retained by normal callers for unknown-agent diagnostics. */
157
+ unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
153
158
  ctx: AsyncExecutionContext;
154
159
  availableModels?: AvailableModelInfo[];
155
160
  cwd?: string;
@@ -175,6 +180,7 @@ interface AsyncChainParams {
175
180
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
176
181
  nestedRoute?: NestedRouteInfo;
177
182
  acceptance?: AcceptanceInput;
183
+ fast?: boolean;
178
184
  timeoutMs?: number;
179
185
  turnBudget?: ResolvedTurnBudget;
180
186
  toolBudget?: ResolvedToolBudget;
@@ -224,6 +230,7 @@ interface AsyncSingleParams {
224
230
  structuredOutputSchema?: JsonSchemaObject;
225
231
  modelOverride?: string;
226
232
  modelOverrideFromParent?: boolean;
233
+ fast?: boolean;
227
234
  thinkingOverride?: AgentConfig["thinking"];
228
235
  availableModels?: AvailableModelInfo[];
229
236
  maxSubagentDepth: number;
@@ -265,6 +272,7 @@ interface AsyncSingleParams {
265
272
  requestId: string;
266
273
  requestDigest: string;
267
274
  };
275
+ extensionBindings?: ExtensionBindings;
268
276
  }
269
277
 
270
278
  interface AsyncExecutionResult {
@@ -279,6 +287,8 @@ export interface AsyncRunnerStepBuildParams {
279
287
  attachRoot?: ImportedAsyncRoot & { agent: string; outputName?: string; label?: string };
280
288
  resultMode?: SubagentRunMode;
281
289
  agents: AgentConfig[];
290
+ /** Exact discovery provenance for failed resolution; omission triggers defensive fallback discovery. */
291
+ unknownAgentDiagnosticContext?: UnknownAgentDiagnosticContext;
282
292
  ctx: AsyncExecutionContext;
283
293
  availableModels?: AvailableModelInfo[];
284
294
  cwd?: string;
@@ -295,6 +305,7 @@ export interface AsyncRunnerStepBuildParams {
295
305
  asyncDir: string;
296
306
  outputBaseDir?: string;
297
307
  validateOutputBindings?: boolean;
308
+ fast?: boolean;
298
309
  toolBudget?: ResolvedToolBudget;
299
310
  configToolBudget?: ResolvedToolBudget;
300
311
  /** Optional per-call hard toolTimeoutMs override from the subagent invocation. */
@@ -488,7 +499,20 @@ interface SpawnRunnerResult {
488
499
  startupDidNotProceed?: boolean;
489
500
  }
490
501
 
491
- function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
502
+ function isStaleExtensionContextError(error: unknown): boolean {
503
+ return error instanceof Error && /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
504
+ }
505
+
506
+ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unknown): void {
507
+ try {
508
+ ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof);
509
+ } catch (error) {
510
+ if (isStaleExtensionContextError(error)) return;
511
+ console.error("Failed to emit subagent process-terminal event:", error);
512
+ }
513
+ }
514
+
515
+ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void): SpawnRunnerResult {
492
516
  if (!jitiCliPath) {
493
517
  return { error: "upstream jiti for TypeScript execution could not be found; ensure package dependencies are installed" };
494
518
  }
@@ -505,8 +529,10 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
505
529
  fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true });
506
530
  const cfgPath = getAsyncConfigPath(suffix);
507
531
  const runnerProcessInstanceId = randomUUID();
508
- const launchConfig = { ...cfg, runnerProcessInstanceId };
509
- fs.writeFileSync(cfgPath, JSON.stringify(launchConfig));
532
+ const hasRevivalLease = typeof (cfg as { revivalLease?: unknown }).revivalLease === "object";
533
+ const launchBarrierToken = hasRevivalLease ? undefined : runnerProcessInstanceId;
534
+ const launchConfig = { ...cfg, runnerProcessInstanceId, ...(launchBarrierToken ? { launchBarrierToken } : {}) };
535
+ writePrivateAtomicJson(cfgPath, launchConfig);
510
536
  const runner = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-runner.ts");
511
537
  const nodeCommand = resolveNodeExecutable();
512
538
  const launchForStartup = launchConfig as typeof launchConfig & { asyncDir?: unknown; id?: unknown; sessionId?: unknown; completionOwnerId?: unknown; revivalLease?: unknown };
@@ -518,7 +544,9 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
518
544
  ? path.join(launchAsyncDir, "runner-startup.json")
519
545
  : undefined;
520
546
  const startupAckPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-ack.json") : undefined;
521
- const startupProceedPath = startupPath ? path.join(path.dirname(startupPath), "runner-startup-proceed.json") : undefined;
547
+ const startupProceedPath = launchAsyncDir && (startupPath || launchBarrierToken)
548
+ ? path.join(launchAsyncDir, "runner-startup-proceed.json")
549
+ : undefined;
522
550
  if (startupPath) fs.rmSync(startupPath, { force: true });
523
551
  if (startupAckPath) fs.rmSync(startupAckPath, { force: true });
524
552
  if (startupProceedPath) fs.rmSync(startupProceedPath, { force: true });
@@ -538,7 +566,7 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
538
566
  stdio: ["ignore", stdoutFd ?? "ignore", stderrFd ?? "ignore"],
539
567
  windowsHide: true,
540
568
  env: {
541
- ...process.env,
569
+ ...omitExtensionBindingsEnv(process.env),
542
570
  ...(piPackageRoot ? { [PI_CODING_AGENT_PACKAGE_ROOT_ENV]: piPackageRoot } : {}),
543
571
  },
544
572
  });
@@ -600,6 +628,27 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, onProcessTerminal
600
628
  if (typeof proc.pid !== "number") {
601
629
  return { error: `async runner did not produce a pid for cwd: ${cwd}` };
602
630
  }
631
+ try {
632
+ writePrivateAtomicJson(initialStatusPath, {
633
+ ...initialStatus,
634
+ pid: proc.pid,
635
+ processTerminal: { version: 1, state: "pending", runId: initialStatus.runId, runnerProcessInstanceId },
636
+ });
637
+ } catch (error) {
638
+ const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
639
+ const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
640
+ return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
641
+ }
642
+ if (launchBarrierToken && startupProceedPath) {
643
+ try {
644
+ writeRunnerStartupControl(startupProceedPath, { action: "proceed", token: launchBarrierToken });
645
+ } catch (error) {
646
+ const message = `Failed to authorize async runner startup: ${error instanceof Error ? error.message : String(error)}`;
647
+ if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
648
+ const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
649
+ return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
650
+ }
651
+ }
603
652
  proc.unref();
604
653
  if (startupPath && startupAckPath && startupProceedPath) {
605
654
  const persistStartupFailure = (message: string) => {
@@ -704,6 +753,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
704
753
  }
705
754
  const workflowGraph = buildWorkflowGraphSnapshot({ runId: id, mode: resultMode, steps: graphChain });
706
755
 
756
+ const diagnosticContext = params.unknownAgentDiagnosticContext
757
+ ?? unknownAgentDiagnosticContext(discoverAgents(path.resolve(runnerCwd), "both"));
707
758
  for (const s of chain) {
708
759
  const stepAgents = isParallelStep(s)
709
760
  ? s.parallel.map((t) => t.agent)
@@ -711,9 +762,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
711
762
  ? [s.parallel.agent]
712
763
  : [(s as SequentialStep).agent];
713
764
  for (const agentName of stepAgents) {
714
- if (!agents.find((x) => x.name === agentName)) {
715
- return { error: `Unknown agent: ${agentName}` };
716
- }
765
+ if (!agents.find((x) => x.name === agentName)) return { error: formatUnknownAgentError(agentName, diagnosticContext) };
717
766
  }
718
767
  }
719
768
 
@@ -727,6 +776,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
727
776
  ...(s.progress !== undefined ? { progress: s.progress } : {}),
728
777
  ...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}),
729
778
  ...(s.model !== undefined ? { model: s.model } : {}),
779
+ ...(s.fast !== undefined ? { fast: s.fast } : {}),
730
780
  };
731
781
  };
732
782
  const buildSeqStep = (s: SequentialStep, sessionFile?: string, behaviorCwd?: string, progressPrecreated = false, resolvedBehavior?: ResolvedStepBehavior, flatIndex?: number, parallelOutputNamespace?: { stepIndex: number; taskIndex?: number }, runFanoutPath?: string) => {
@@ -739,6 +789,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
739
789
  if (s.outputSchema !== undefined) unsupported.push("structured output");
740
790
  if (s.acceptance !== undefined || params.agentContract !== undefined || s.agentContract !== undefined) unsupported.push("acceptance/agent contract");
741
791
  if (s.toolBudget !== undefined || params.toolBudget !== undefined || a.toolBudget !== undefined || params.configToolBudget !== undefined) unsupported.push("tool budget");
792
+ if ((s.fast ?? params.fast ?? a.fast) === true) unsupported.push("fast mode");
742
793
  if (params.contextForAgent?.(s.agent) === "fork") unsupported.push("fork context");
743
794
  if (unsupported.length > 0) throw new AsyncStartValidationError(`Agent '${a.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
744
795
  }
@@ -831,6 +882,21 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
831
882
  }
832
883
  const agentContract = s.agentContract ?? params.agentContract;
833
884
  const permissionRules = resolvePermissionRules(ctx.permissions, a.permissions);
885
+ const modelCandidates = externalRunner ? [] : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
886
+ scope: modelScopes,
887
+ primaryModelFromParent,
888
+ }).flatMap((candidate) => {
889
+ const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
890
+ return resolved ? [resolved] : [];
891
+ });
892
+ if (!externalRunner) {
893
+ try {
894
+ for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
895
+ } catch (error) {
896
+ throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
897
+ }
898
+ }
899
+ const fast = s.fast ?? params.fast ?? a.fast;
834
900
  const toolPlan = resolvePiLaunchToolPlan({
835
901
  tools: a.tools,
836
902
  extensions: a.extensions,
@@ -839,6 +905,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
839
905
  cwd: stepCwd,
840
906
  requireReadTool: Boolean(resolvedSkills.length),
841
907
  structuredOutput: Boolean(s.outputSchema),
908
+ fast,
909
+ model,
910
+ modelCandidates,
842
911
  capabilityCeiling: params.capabilityCeiling,
843
912
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
844
913
  agentName: a.name,
@@ -862,20 +931,6 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
862
931
  });
863
932
  if (contractError) throw new AsyncStartValidationError(contractError);
864
933
  }
865
- const modelCandidates = externalRunner ? [] : buildModelCandidates(primaryModel, a.fallbackModels, availableModels, a.modelProvider ?? ctx.currentModelProvider, {
866
- scope: modelScopes,
867
- primaryModelFromParent,
868
- }).flatMap((candidate) => {
869
- const resolved = applyThinkingSuffix(candidate, effectiveThinking, thinkingOverride !== undefined);
870
- return resolved ? [resolved] : [];
871
- });
872
- if (!externalRunner) {
873
- try {
874
- for (const candidate of modelCandidates) assertThinkingWithinCeiling({ model: candidate, configThinking: effectiveThinking, ceiling: thinkingCeiling, agent: a.name, runId: id });
875
- } catch (error) {
876
- throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
877
- }
878
- }
879
934
  return {
880
935
  parentSessionId: ctx.parentSessionId ?? ctx.currentSessionId,
881
936
  permissionRules,
@@ -892,6 +947,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
892
947
  structured: Boolean(s.outputSchema),
893
948
  cwd: stepCwd,
894
949
  model,
950
+ ...(fast !== undefined ? { fast } : {}),
895
951
  thinking: resolveEffectiveThinking(model, effectiveThinking),
896
952
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
897
953
  launchResolvedExtensions,
@@ -930,7 +986,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
930
986
  acceptanceRole: a.acceptanceRole,
931
987
  ...(s.gateOn ? { gateOn: s.gateOn } : {}),
932
988
  ...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
933
- ...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output")) } : {}),
989
+ ...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: s.acceptance !== false }) } : {}),
934
990
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
935
991
  ...(s.worktree ? { worktree: true } : {}),
936
992
  };
@@ -1128,6 +1184,7 @@ export function executeAsyncChain(
1128
1184
  attachRoot: params.attachRoot,
1129
1185
  resultMode,
1130
1186
  agents,
1187
+ unknownAgentDiagnosticContext: params.unknownAgentDiagnosticContext,
1131
1188
  ctx,
1132
1189
  availableModels: params.availableModels,
1133
1190
  cwd,
@@ -1143,6 +1200,7 @@ export function executeAsyncChain(
1143
1200
  waitToolEnabled: params.waitToolEnabled,
1144
1201
  worktreeBaseDir,
1145
1202
  asyncDir,
1203
+ fast: params.fast,
1146
1204
  toolBudget: params.toolBudget,
1147
1205
  configToolBudget: params.configToolBudget,
1148
1206
  callToolTimeoutMs: params.callToolTimeoutMs,
@@ -1178,6 +1236,21 @@ export function executeAsyncChain(
1178
1236
  }
1179
1237
  return "agent" in step ? [childIntercomTarget(step.agent, childTargetIndex++)] : [undefined];
1180
1238
  }) : undefined;
1239
+ const initialStatusSteps = eventChain.flatMap((step) => isParallelStep(step)
1240
+ ? step.parallel.map((task) => ({ agent: task.agent, ...(statusStepDescription(task.task) ? { description: statusStepDescription(task.task) } : {}), ...(task.label ? { label: task.label } : {}), ...(task.as ? { outputName: task.as } : {}), status: "pending" as const }))
1241
+ : isDynamicParallelStep(step)
1242
+ ? [{ agent: `expand:${step.parallel.agent}`, label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`, outputName: step.collect.as, status: "pending" as const }]
1243
+ : [{ agent: (step as SequentialStep).agent, ...(statusStepDescription((step as SequentialStep).task) ? { description: statusStepDescription((step as SequentialStep).task) } : {}), ...((step as SequentialStep).label ? { label: (step as SequentialStep).label } : {}), ...((step as SequentialStep).as ? { outputName: (step as SequentialStep).as } : {}), status: "pending" as const }]);
1244
+ const initialParallelGroups: AsyncParallelGroupStatus[] = [];
1245
+ let initialFlatIndex = 0;
1246
+ for (let stepIndex = 0; stepIndex < eventChain.length; stepIndex++) {
1247
+ const step = eventChain[stepIndex]!;
1248
+ if (isParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: step.parallel.length, stepIndex });
1249
+ else if (isDynamicParallelStep(step)) initialParallelGroups.push({ start: initialFlatIndex, count: 1, stepIndex });
1250
+ initialFlatIndex += isParallelStep(step) ? step.parallel.length : 1;
1251
+ }
1252
+ const initialStatusAt = Date.now();
1253
+ const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
1181
1254
 
1182
1255
  let spawnResult: SpawnRunnerResult = {};
1183
1256
  try {
@@ -1227,7 +1300,22 @@ export function executeAsyncChain(
1227
1300
  },
1228
1301
  id,
1229
1302
  runnerCwd,
1230
- (proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
1303
+ {
1304
+ lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
1305
+ runId: id,
1306
+ ...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
1307
+ ...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
1308
+ mode: resultMode,
1309
+ state: "running",
1310
+ startedAt: initialStatusAt,
1311
+ lastUpdate: initialStatusAt,
1312
+ currentStep: 0,
1313
+ chainStepCount: eventChain.length,
1314
+ ...(initialParallelGroups.length ? { parallelGroups: initialParallelGroups } : {}),
1315
+ steps: initialStatusSteps,
1316
+ },
1317
+ path.join(asyncDir, "status.json"),
1318
+ (proof) => emitProcessTerminalEvent(ctx, proof),
1231
1319
  );
1232
1320
  } catch (error) {
1233
1321
  params.activeAsyncCapacity?.rollback();
@@ -1391,6 +1479,12 @@ export function executeAsyncSingle(
1391
1479
  nestedRoute,
1392
1480
  } = params;
1393
1481
  const task = params.task ?? "";
1482
+ let extensionBindings: ExtensionBindings | undefined;
1483
+ try {
1484
+ extensionBindings = normalizeExtensionBindings(params.extensionBindings)?.value;
1485
+ } catch (error) {
1486
+ return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1487
+ }
1394
1488
  const acceptanceErrors = validateAcceptanceInput(params.acceptance);
1395
1489
  if (acceptanceErrors.length > 0) return formatAsyncStartError("single", acceptanceErrors.join(" "));
1396
1490
  const externalRunner = agentConfig.runner?.type === "external-cli" || agentConfig.runner?.type === "external-job";
@@ -1399,6 +1493,7 @@ export function executeAsyncSingle(
1399
1493
  if (externalRunner) {
1400
1494
  const unsupported: string[] = [];
1401
1495
  if (params.modelOverride !== undefined) unsupported.push("model override");
1496
+ if ((params.fast ?? agentConfig.fast) === true) unsupported.push("fast mode");
1402
1497
  if (params.thinkingOverride !== undefined) unsupported.push("thinking override");
1403
1498
  if (params.structuredOutputSchema !== undefined) unsupported.push("structured output");
1404
1499
  if (params.acceptance !== undefined || params.agentContract !== undefined) unsupported.push("acceptance/agent contract");
@@ -1406,6 +1501,7 @@ export function executeAsyncSingle(
1406
1501
  if (params.context === "fork") unsupported.push("fork context");
1407
1502
  if ((params.skills?.length ?? 0) > 0) unsupported.push("skills");
1408
1503
  if (permissionRules) unsupported.push("native Pi child permissions");
1504
+ if (extensionBindings !== undefined) unsupported.push("extension bindings");
1409
1505
  if (unsupported.length > 0) return formatAsyncStartError("single", `Agent '${agentConfig.name}' uses runner.type='${externalRunnerType}' and does not support: ${unsupported.join(", ")}.`);
1410
1506
  }
1411
1507
  const capabilityCeiling = intersectSubagentCapabilityCeilings(params.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(ctx.currentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]));
@@ -1518,7 +1614,7 @@ export function executeAsyncSingle(
1518
1614
  const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
1519
1615
  const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
1520
1616
  const structuredOutput = params.structuredOutputSchema
1521
- ? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"))
1617
+ ? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: params.acceptance !== false })
1522
1618
  : undefined;
1523
1619
  const modelCandidates = externalRunner
1524
1620
  ? []
@@ -1546,6 +1642,9 @@ export function executeAsyncSingle(
1546
1642
  cwd: runnerCwd,
1547
1643
  requireReadTool: Boolean(resolvedSkills.length),
1548
1644
  structuredOutput: Boolean(params.structuredOutputSchema),
1645
+ fast: params.fast ?? agentConfig.fast,
1646
+ model,
1647
+ modelCandidates,
1549
1648
  capabilityCeiling,
1550
1649
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
1551
1650
  agentName: agentConfig.name,
@@ -1571,6 +1670,7 @@ export function executeAsyncSingle(
1571
1670
  task,
1572
1671
  ...(model ? { model } : {}),
1573
1672
  modelCandidates,
1673
+ ...((params.fast ?? agentConfig.fast) !== undefined ? { fast: params.fast ?? agentConfig.fast } : {}),
1574
1674
  ...(resolveEffectiveThinking(model, effectiveThinking) ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1575
1675
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
1576
1676
  systemPrompt: effectiveSystemPrompt,
@@ -1584,6 +1684,7 @@ export function executeAsyncSingle(
1584
1684
  ...(outputPath ? { outputPath } : {}),
1585
1685
  outputMode,
1586
1686
  ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
1687
+ ...(extensionBindings ? { extensionBindings } : {}),
1587
1688
  });
1588
1689
  const resolvedAcceptance = resolveEffectiveAcceptance({
1589
1690
  explicit: params.acceptance,
@@ -1598,6 +1699,7 @@ export function executeAsyncSingle(
1598
1699
  const recoveryDescriptor: SteeringRecoveryDescriptor = {
1599
1700
  version: 1,
1600
1701
  launchContractDigest,
1702
+ ...(extensionBindings ? { extensionBindings } : {}),
1601
1703
  runFanoutBudget,
1602
1704
  sourceRunId: id,
1603
1705
  ...(params.agentContract ? { agentContract: params.agentContract } : {}),
@@ -1606,6 +1708,7 @@ export function executeAsyncSingle(
1606
1708
  ...(sessionFile ? { sessionFile } : {}),
1607
1709
  cwd: runnerCwd,
1608
1710
  ...(model ? { model } : {}),
1711
+ ...(params.fast ?? recoveryAgentConfig.fast ? { fast: params.fast ?? recoveryAgentConfig.fast } : {}),
1609
1712
  ...(recoveryAgentConfig.modelProvider ? { modelProvider: recoveryAgentConfig.modelProvider } : {}),
1610
1713
  ...(params.modelOverrideFromParent ? { modelOverrideFromParent: true } : {}),
1611
1714
  ...(recoveryAgentConfig.fallbackModels ? { fallbackModels: [...recoveryAgentConfig.fallbackModels] } : {}),
@@ -1650,6 +1753,8 @@ export function executeAsyncSingle(
1650
1753
  }
1651
1754
  }
1652
1755
  let spawnResult: SpawnRunnerResult = {};
1756
+ const initialStatusAt = Date.now();
1757
+ const initialCompletionOwnerId = ctx.completionOwnerId ?? currentCompletionOwnerId();
1653
1758
  try {
1654
1759
  spawnResult = spawnRunner(
1655
1760
  {
@@ -1666,6 +1771,7 @@ export function executeAsyncSingle(
1666
1771
  ...(params.context ? { context: params.context } : {}),
1667
1772
  cwd: runnerCwd,
1668
1773
  model,
1774
+ ...(params.fast ?? agentConfig.fast ? { fast: params.fast ?? agentConfig.fast } : {}),
1669
1775
  thinking: resolveEffectiveThinking(model, effectiveThinking),
1670
1776
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
1671
1777
  modelCandidates,
@@ -1690,6 +1796,7 @@ export function executeAsyncSingle(
1690
1796
  definitionDigest: agentDefinitionDigest(agentConfig),
1691
1797
  launchBindingTask: task,
1692
1798
  launchContractDigest,
1799
+ ...(extensionBindings ? { extensionBindings } : {}),
1693
1800
  launchResolvedExtensions,
1694
1801
  effectiveAcceptance: resolvedAcceptance,
1695
1802
  ...(structuredOutput ? { structuredOutput } : {}),
@@ -1743,7 +1850,21 @@ export function executeAsyncSingle(
1743
1850
  },
1744
1851
  id,
1745
1852
  runnerCwd,
1746
- (proof) => ctx.pi.events.emit(SUBAGENT_PROCESS_TERMINAL_EVENT, proof),
1853
+ {
1854
+ lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
1855
+ runId: id,
1856
+ ...(ctx.currentSessionId ? { sessionId: ctx.currentSessionId } : {}),
1857
+ ...(initialCompletionOwnerId ? { completionOwnerId: initialCompletionOwnerId } : {}),
1858
+ mode: "single",
1859
+ state: "running",
1860
+ startedAt: initialStatusAt,
1861
+ lastUpdate: initialStatusAt,
1862
+ currentStep: 0,
1863
+ chainStepCount: 1,
1864
+ steps: [{ agent, status: "pending" }],
1865
+ },
1866
+ path.join(asyncDir, "status.json"),
1867
+ (proof) => emitProcessTerminalEvent(ctx, proof),
1747
1868
  );
1748
1869
  } catch (error) {
1749
1870
  params.activeAsyncCapacity?.rollback();
@@ -24,6 +24,7 @@ import { findNestedRouteForRootId, hasLiveNestedDescendants, updateAsyncJobNeste
24
24
  import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
25
25
  import { EXTERNAL_JOB_BRIDGE_REQUEST_DIR, serviceExternalJobBridgeRequests } from "../shared/external-job-bridge.ts";
26
26
  import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.ts";
27
+ import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
27
28
 
28
29
  interface AsyncJobTrackerOptions {
29
30
  completionRetentionMs?: number;
@@ -171,6 +172,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
171
172
  parentWorkflowRunId: run.parentWorkflowRunId,
172
173
  workflowKey: run.workflowKey,
173
174
  workflow: run.workflow,
175
+ workflowChildren: parseWorkflowChildSummary(run.workflowChildren),
174
176
  };
175
177
  };
176
178
  const cancelCleanup = (asyncId: string) => {
@@ -398,6 +400,9 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
398
400
  job.parentWorkflowRunId = status.parentWorkflowRunId ?? job.parentWorkflowRunId;
399
401
  job.workflowKey = status.workflowKey ?? job.workflowKey;
400
402
  job.workflow = status.workflow ?? job.workflow;
403
+ const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
404
+ if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
405
+ job.workflowChildren = workflowChildren ?? job.workflowChildren;
401
406
  job.currentStep = status.currentStep ?? job.currentStep;
402
407
  job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
403
408
  job.startedAt = status.startedAt ?? job.startedAt;
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { DIRS, type AcceptanceInput, type AsyncStatus, type ResolvedTurnBudget, type SteeringRecoveryDescriptor, type SubagentRunMode } from "../../shared/types.ts";
4
4
  import type { AgentConfig } from "../../agents/agents.ts";
5
+ import { normalizeExtensionBindings } from "../shared/extension-bindings.ts";
5
6
  import { validateAcceptanceInput } from "../shared/acceptance.ts";
6
7
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
7
8
  import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
@@ -328,6 +329,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
328
329
  "skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
329
330
  "artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
330
331
  "launchResolvedExtensions", "runFanoutBudget",
332
+ "extensionBindings",
331
333
  ]);
332
334
  for (const field of Object.keys(parsed)) {
333
335
  if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
@@ -344,6 +346,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
344
346
  }
345
347
  if (parsed.capabilityCeiling !== undefined) parsed.capabilityCeiling = parseSubagentCapabilityCeiling(parsed.capabilityCeiling, `async recovery descriptor '${descriptorPath}' capabilityCeiling`);
346
348
  if (parsed.thinkingCeiling !== undefined) parsed.thinkingCeiling = parseThinkingLevel(parsed.thinkingCeiling, `async recovery descriptor '${descriptorPath}' thinkingCeiling`);
349
+ if (parsed.extensionBindings !== undefined) parsed.extensionBindings = normalizeExtensionBindings(parsed.extensionBindings)!.value;
347
350
  if (parsed.agentContract !== undefined) {
348
351
  if (!parsed.agentContract || typeof parsed.agentContract !== "object" || Array.isArray(parsed.agentContract)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be an object.`);
349
352
  const contract = parsed.agentContract as Record<string, unknown>;
@@ -9,6 +9,7 @@ import type { AsyncStatus } from "../../shared/types.ts";
9
9
  import { MISSION_BINDING_FILE } from "../../missions/lifecycle.ts";
10
10
  import { ACTIVE_RUN_INDEX_DIR } from "./active-run-index.ts";
11
11
  import { encodeIndexSegment } from "./index-segment.ts";
12
+ import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
12
13
 
13
14
  export const ASYNC_RETENTION_DAYS = 30;
14
15
  export const ASYNC_RETENTION_BATCH_SIZE = 100;
@@ -96,11 +97,13 @@ export interface AsyncRetentionOptions {
96
97
  lstatSync?: typeof fs.lstatSync;
97
98
  signal?: AbortSignal;
98
99
  discoveryWorkerUrl?: URL;
100
+ reconcileKill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
99
101
  }
100
102
 
101
103
  export interface AsyncRetentionResult {
102
104
  acquired: boolean;
103
105
  scanned: number;
106
+ repairedRuns: number;
104
107
  deletedRuns: number;
105
108
  deletedResults: number;
106
109
  reapedTombstones: number;
@@ -135,7 +138,7 @@ function listDir(dir: string): fs.Dirent[] {
135
138
 
136
139
  function readJson(filePath: string): Record<string, unknown> | undefined {
137
140
  try {
138
- const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8")) as unknown;
141
+ const parsed = JSON.parse(fs.readFileSync(filePath).toString("utf8")) as unknown;
139
142
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
140
143
  } catch {
141
144
  return undefined;
@@ -320,7 +323,7 @@ function readCursor(root: string): RetentionCursor {
320
323
  function processStartIdentity(pid: number): string | undefined {
321
324
  if (process.platform === "linux") {
322
325
  try {
323
- const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf-8");
326
+ const stat = fs.readFileSync(`/proc/${pid}/stat`).toString("utf8");
324
327
  const commandEnd = stat.lastIndexOf(")");
325
328
  if (commandEnd === -1) return undefined;
326
329
  const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
@@ -504,6 +507,7 @@ function appendMaintenanceLog(root: string, result: AsyncRetentionResult, now: n
504
507
  at: new Date(now).toISOString(),
505
508
  acquired: result.acquired,
506
509
  scanned: result.scanned,
510
+ repairedRuns: result.repairedRuns,
507
511
  deletedRuns: result.deletedRuns,
508
512
  deletedResults: result.deletedResults,
509
513
  reapedTombstones: result.reapedTombstones,
@@ -654,6 +658,7 @@ export async function cleanupAsyncRetention(options: AsyncRetentionOptions): Pro
654
658
  const result: AsyncRetentionResult = {
655
659
  acquired: false,
656
660
  scanned: 0,
661
+ repairedRuns: 0,
657
662
  deletedRuns: 0,
658
663
  deletedResults: 0,
659
664
  reapedTombstones: 0,
@@ -750,7 +755,19 @@ export async function cleanupAsyncRetention(options: AsyncRetentionOptions): Pro
750
755
  increment(result.skipped, "unsafe-run-path");
751
756
  continue;
752
757
  }
753
- const status = readStatus(runDir);
758
+ let status = readStatus(runDir);
759
+ if (status?.state === "running"
760
+ && validRunId(status.runId)
761
+ && path.basename(runDir) === status.runId
762
+ && !protectedRunIds.has(status.runId)) {
763
+ const reconciliation = reconcileAsyncRun(runDir, {
764
+ resultsDir: options.resultsDir,
765
+ kill: options.reconcileKill,
766
+ now,
767
+ });
768
+ status = reconciliation.status ?? undefined;
769
+ if (reconciliation.repaired) result.repairedRuns += 1;
770
+ }
754
771
  const initialReason = runSkipReason({ runDir, status, asyncDirRoot: options.asyncDirRoot, resultsDir: options.resultsDir, cutoff, protectedRunIds, waitRunIds: waitReferences.runIds });
755
772
  if (initialReason) {
756
773
  increment(result.skipped, initialReason);
@@ -16,6 +16,7 @@ import { ACTIVE_RUN_INDEX_DIR, DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS, activeRu
16
16
  import { readRecentTerminalRunIndex, TERMINAL_RUN_INDEX_DIR } from "./terminal-run-index.ts";
17
17
  import { canScanAsyncRunPrefix } from "./run-id-query.ts";
18
18
  import { asyncStatusChildIdentity } from "../shared/child-identity.ts";
19
+ import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
19
20
 
20
21
  interface AsyncRunStepSummary {
21
22
  index: number;
@@ -121,6 +122,7 @@ export interface AsyncRunSummary {
121
122
  parentWorkflowRunId?: string;
122
123
  workflowKey?: string;
123
124
  workflow?: Details["workflow"];
125
+ workflowChildren?: Details["workflowChildren"];
124
126
  }
125
127
 
126
128
  interface AsyncRunListOptions {
@@ -229,6 +231,8 @@ function deriveAsyncActivityState(asyncDir: string, status: AsyncStatus): { acti
229
231
  }
230
232
 
231
233
  function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string }, nestedWarnings: string[] = [], nestedRoute?: NestedRoute): AsyncRunSummary {
234
+ const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
235
+ if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': workflowChildren.workflowRunId does not match.`);
232
236
  if (status.sessionId !== undefined && typeof status.sessionId !== "string") {
233
237
  throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`);
234
238
  }
@@ -357,6 +361,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
357
361
  ...(status.parentWorkflowRunId ? { parentWorkflowRunId: status.parentWorkflowRunId } : {}),
358
362
  ...(status.workflowKey ? { workflowKey: status.workflowKey } : {}),
359
363
  ...(status.workflow ? { workflow: status.workflow } : {}),
364
+ ...(workflowChildren ? { workflowChildren } : {}),
360
365
  ...(status.sessionDir ? { sessionDir: status.sessionDir } : {}),
361
366
  ...(status.outputFile ? { outputFile: status.outputFile } : {}),
362
367
  ...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import { resultFilePath } from "./result-files.ts";
3
- import type { AcceptanceLedger, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
3
+ import type { AcceptanceLedger, ArtifactPaths, AsyncStatus, CostSummary, ModelAttempt } from "../../shared/types.ts";
4
4
  import { readStatus } from "../../shared/utils.ts";
5
5
 
6
6
  export interface ImportedAsyncRoot {
@@ -27,6 +27,10 @@ export interface ImportedAsyncRootResult {
27
27
  structuredOutputPath?: string;
28
28
  structuredOutputSchemaPath?: string;
29
29
  acceptance?: AcceptanceLedger;
30
+ artifactPaths?: ArtifactPaths;
31
+ outputSaveError?: string;
32
+ transcriptPath?: string;
33
+ transcriptError?: string;
30
34
  timedOut?: boolean;
31
35
  stopped?: boolean;
32
36
  }
@@ -56,6 +60,10 @@ interface AsyncResultFile {
56
60
  structuredOutputPath?: string;
57
61
  structuredOutputSchemaPath?: string;
58
62
  acceptance?: AcceptanceLedger;
63
+ artifactPaths?: ArtifactPaths;
64
+ outputSaveError?: string;
65
+ transcriptPath?: string;
66
+ transcriptError?: string;
59
67
  }>;
60
68
  }
61
69
 
@@ -118,6 +126,7 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
118
126
  ...(step?.structuredOutputPath ? { structuredOutputPath: step.structuredOutputPath } : {}),
119
127
  ...(step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: step.structuredOutputSchemaPath } : {}),
120
128
  ...(step?.acceptance ? { acceptance: step.acceptance } : {}),
129
+ ...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
121
130
  };
122
131
  }
123
132
 
@@ -136,6 +145,7 @@ function outputFromTimeout(root: ImportedAsyncRoot, status: AsyncStatus | null,
136
145
  ...(step?.modelAttempts ? { modelAttempts: step.modelAttempts } : {}),
137
146
  ...(step?.contextOverflow ? { contextOverflow: true } : {}),
138
147
  ...(step?.totalCost ? { totalCost: step.totalCost } : {}),
148
+ ...(step?.transcriptPath ? { transcriptPath: step.transcriptPath } : {}),
139
149
  };
140
150
  }
141
151
 
@@ -168,6 +178,10 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
168
178
  ...(child?.structuredOutputPath ?? step?.structuredOutputPath ? { structuredOutputPath: child?.structuredOutputPath ?? step?.structuredOutputPath } : {}),
169
179
  ...(child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath ? { structuredOutputSchemaPath: child?.structuredOutputSchemaPath ?? step?.structuredOutputSchemaPath } : {}),
170
180
  ...(child?.acceptance ?? step?.acceptance ? { acceptance: child?.acceptance ?? step?.acceptance } : {}),
181
+ ...(child?.artifactPaths ? { artifactPaths: child.artifactPaths } : {}),
182
+ ...(child?.outputSaveError ? { outputSaveError: child.outputSaveError } : {}),
183
+ ...(child?.transcriptPath ?? step?.transcriptPath ? { transcriptPath: child?.transcriptPath ?? step?.transcriptPath } : {}),
184
+ ...(child?.transcriptError ? { transcriptError: child.transcriptError } : {}),
171
185
  };
172
186
  }
173
187