pi-subagents 0.72.1 → 0.73.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 (56) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/docs/agents.md +1 -1
  3. package/docs/standalone-background.md +1 -1
  4. package/docs/tool-reference.md +1 -1
  5. package/docs/workflows.md +5 -1
  6. package/package.json +1 -1
  7. package/src/extension/index.d.ts.map +1 -1
  8. package/src/extension/index.js +2 -0
  9. package/src/extension/index.js.map +1 -1
  10. package/src/extension/tool-activation.js +1 -1
  11. package/src/extension/tool-activation.js.map +1 -1
  12. package/src/runs/background/run-status.d.ts.map +1 -1
  13. package/src/runs/background/run-status.js +1 -0
  14. package/src/runs/background/run-status.js.map +1 -1
  15. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  16. package/src/runs/foreground/subagent-executor.js +142 -64
  17. package/src/runs/foreground/subagent-executor.js.map +1 -1
  18. package/src/runs/shared/async-status-projection.d.ts +3 -0
  19. package/src/runs/shared/async-status-projection.d.ts.map +1 -1
  20. package/src/runs/shared/async-status-projection.js +3 -0
  21. package/src/runs/shared/async-status-projection.js.map +1 -1
  22. package/src/runs/shared/mcp-direct-tool-allowlist.d.ts.map +1 -1
  23. package/src/runs/shared/mcp-direct-tool-allowlist.js +11 -12
  24. package/src/runs/shared/mcp-direct-tool-allowlist.js.map +1 -1
  25. package/src/shared/extension-context.d.ts +2 -0
  26. package/src/shared/extension-context.d.ts.map +1 -1
  27. package/src/shared/extension-context.js +4 -0
  28. package/src/shared/extension-context.js.map +1 -1
  29. package/src/shared/model-info.d.ts +4 -0
  30. package/src/shared/model-info.d.ts.map +1 -1
  31. package/src/shared/model-info.js +9 -0
  32. package/src/shared/model-info.js.map +1 -1
  33. package/src/shared/types.d.ts +2 -0
  34. package/src/shared/types.d.ts.map +1 -1
  35. package/src/shared/types.js.map +1 -1
  36. package/src/tui/fleet-status.d.ts.map +1 -1
  37. package/src/tui/fleet-status.js +12 -6
  38. package/src/tui/fleet-status.js.map +1 -1
  39. package/src/tui/fleet.d.ts.map +1 -1
  40. package/src/tui/fleet.js +12 -1
  41. package/src/tui/fleet.js.map +1 -1
  42. package/src/tui/render.d.ts.map +1 -1
  43. package/src/tui/render.js +54 -34
  44. package/src/tui/render.js.map +1 -1
  45. package/src/tui/running-tone.d.ts +11 -0
  46. package/src/tui/running-tone.d.ts.map +1 -0
  47. package/src/tui/running-tone.js +23 -0
  48. package/src/tui/running-tone.js.map +1 -0
  49. package/src/workflows/scripted-workflow.d.ts +6 -4
  50. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  51. package/src/workflows/scripted-workflow.js +100 -35
  52. package/src/workflows/scripted-workflow.js.map +1 -1
  53. package/src/workflows/workflow-checklist.d.ts +4 -0
  54. package/src/workflows/workflow-checklist.d.ts.map +1 -1
  55. package/src/workflows/workflow-checklist.js +3 -0
  56. package/src/workflows/workflow-checklist.js.map +1 -1
@@ -101,7 +101,7 @@ import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveWorkflowResource
101
101
  import { stableJsonDigest } from "../../shared/launch-contract.js";
102
102
  import { cleanupWorktrees, createWorktrees, preflightWorktreeSource, withWorktreeTransaction, diffWorktrees, formatWorktreeDiffSummary, } from "../shared/worktree.js";
103
103
  import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.js";
104
- import { DIRS, DEFAULT_ARTIFACT_CONFIG, DEFAULT_FORK_PREAMBLE, SUBAGENT_ACTIONS, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, checkSubagentDepth, resolveChildMaxSubagentDepth, resolveCurrentMaxSubagentDepth, resolveMaxSubagentSpawnsPerRun, wrapForkTask, } from "../../shared/types.js";
104
+ import { DIRS, DEFAULT_ARTIFACT_CONFIG, DEFAULT_MAX_OUTPUT, DEFAULT_FORK_PREAMBLE, SUBAGENT_ACTIONS, SUBAGENT_ASYNC_STARTED_EVENT, SUBAGENT_CHILD_STATUS_EVENT, SUBAGENT_CONTROL_EVENT, SUBAGENT_CONTROL_INTERCOM_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, SUBAGENT_LIFECYCLE_ARTIFACT_VERSION, checkSubagentDepth, resolveChildMaxSubagentDepth, resolveCurrentMaxSubagentDepth, resolveMaxSubagentSpawnsPerRun, truncateOutput, wrapForkTask, } from "../../shared/types.js";
105
105
  import { deriveChildSessionName } from "../../shared/child-session-name.js";
106
106
  const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.resolve-decision", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "worktree.cleanup", "lane.recordMerge", "lane.recordSupersession", "refine", "refine.rollback", "dismiss", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
107
107
  const DESTRUCTIVE_MANAGEMENT_ACTIONS = new Set(["delete", "eject", "disable", "reset", "mission.close", "worktree.discard", "refine.rollback", "inspector.close", "project.close", "stop", "interrupt", "schedule.delete"]);
@@ -1518,6 +1518,14 @@ async function resumeAsyncRun(input) {
1518
1518
  details: { mode: "management", results: [] },
1519
1519
  };
1520
1520
  }
1521
+ const timeoutOverflowError = timerDelayOverflowError("timeoutMs", input.params.timeoutMs);
1522
+ if (timeoutOverflowError) {
1523
+ return {
1524
+ content: [{ type: "text", text: timeoutOverflowError }],
1525
+ isError: true,
1526
+ details: { mode: "management", results: [] },
1527
+ };
1528
+ }
1521
1529
  if (input.params.model !== undefined) {
1522
1530
  return {
1523
1531
  content: [{ type: "text", text: "action='resume' reuses the persisted child model and does not accept a model override." }],
@@ -2134,6 +2142,28 @@ function canonicalizeAgentName(name, agents, diagnostics, context) {
2134
2142
  return { error: formatUnknownAgentError(name, context) };
2135
2143
  return { name: resolved.agent.name };
2136
2144
  }
2145
+ // Mirrors child launch: workflow children without their own cwd/agentScope discover agents at the workflow cwd and scope.
2146
+ function workflowValidationOptions(deps, params, runtimeCwd, parentModelProvider) {
2147
+ const cwd = resolveRequestedCwd(runtimeCwd, params.cwd);
2148
+ const scope = resolveExecutionAgentScope(params.agentScope);
2149
+ let discovered;
2150
+ return {
2151
+ maxSubagentSpawnsPerRun: params.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
2152
+ agentNameError: (name) => {
2153
+ discovered ??= deps.discoverAgents(cwd, scope, parentModelProvider());
2154
+ const { agents } = discovered;
2155
+ const resolved = resolveAgentName(name, agents);
2156
+ if (resolved.agent || resolved.error)
2157
+ return canonicalizeAgentName(name, agents, discovered.agentDiagnostics, diagnosticContextFromDiscovery(discovered, cwd, scope)).error;
2158
+ const requested = name.trim().toLowerCase();
2159
+ const suggestion = [...new Set(agents.flatMap((agent) => [agent.name, ...(agent.localName ? [agent.localName] : []), ...(agent.aliases ?? [])]))]
2160
+ .map((candidate) => ({ candidate, distance: editDistance(requested, candidate.toLowerCase()) }))
2161
+ .filter(({ candidate, distance }) => distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate.toLowerCase()))
2162
+ .sort((left, right) => left.distance - right.distance || left.candidate.localeCompare(right.candidate))[0]?.candidate;
2163
+ return `Unknown agent '${name}'.${suggestion ? ` Did you mean '${suggestion}'?` : ""} Use subagent({ action: "list" }) to inspect agents.`;
2164
+ },
2165
+ };
2166
+ }
2137
2167
  function canonicalizeExecutionParams(params, agents, diagnostics, context) {
2138
2168
  const resolve = (name, location) => {
2139
2169
  const result = canonicalizeAgentName(name, agents, diagnostics, context);
@@ -2535,6 +2565,11 @@ export { DEFAULT_ASYNC_TIMEOUT_MS };
2535
2565
  * for a timer must stay within this bound.
2536
2566
  */
2537
2567
  const MAX_TIMER_DELAY_MS = 2_147_483_647;
2568
+ function timerDelayOverflowError(name, value) {
2569
+ return typeof value === "number" && value > MAX_TIMER_DELAY_MS
2570
+ ? `${name} must be a positive integer no larger than ${MAX_TIMER_DELAY_MS}.`
2571
+ : undefined;
2572
+ }
2538
2573
  /**
2539
2574
  * Resolve the optional global default runtime deadline from extension config
2540
2575
  * (`config.timeoutMs`). Returns undefined for unset or invalid values so callers
@@ -2559,6 +2594,9 @@ export function resolveForegroundTimeout(params, defaultTimeoutMs) {
2559
2594
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
2560
2595
  return { error: `${name} must be a positive integer.` };
2561
2596
  }
2597
+ const overflowError = timerDelayOverflowError(name, value);
2598
+ if (overflowError)
2599
+ return { error: overflowError };
2562
2600
  }
2563
2601
  if (rawTimeout !== undefined && rawMaxRuntime !== undefined && rawTimeout !== rawMaxRuntime) {
2564
2602
  return { error: "timeoutMs and maxRuntimeMs are aliases; provide only one value or use the same value for both." };
@@ -3170,6 +3208,36 @@ function writeWorkflowAggregateOutput(outputPath, text, producedChildOutputPaths
3170
3208
  function appendWorkflowOutputWarning(text, warning) {
3171
3209
  return warning ? `${text}\n\n${warning}` : text;
3172
3210
  }
3211
+ const WORKFLOW_TRACE_ERROR_MAX_CHARS = 500;
3212
+ // Return, emits, console, and trace errors (which carry child text) have no size limit of their
3213
+ // own, so only they are cut; other host sections stay whole. The saved full result is uncut.
3214
+ function formatWorkflowResultText(input) {
3215
+ const traceSection = (error) => input.trace.length > 0
3216
+ ? [`Call trace:\n${input.trace.map((entry) => `${entry.line}${entry.error ? ` — ${error(entry.error)}` : ""}`).join("\n")}`]
3217
+ : [];
3218
+ const fullText = [...input.head, ...input.script, ...traceSection((error) => error), ...input.tail].join("\n\n");
3219
+ const outputWarning = writeWorkflowAggregateOutput(input.aggregateOutputPath, fullText, input.producedChildOutputPaths);
3220
+ const scriptText = input.script.join("\n\n");
3221
+ const config = { ...DEFAULT_MAX_OUTPUT, ...input.maxOutput };
3222
+ const scriptCut = truncateOutput(scriptText, config).truncated;
3223
+ const traceCut = input.trace.some((entry) => (entry.error?.length ?? 0) > WORKFLOW_TRACE_ERROR_MAX_CHARS);
3224
+ let shownScriptText = scriptText;
3225
+ let saveWarning;
3226
+ let savedPath;
3227
+ if (scriptCut || traceCut) {
3228
+ const aggregateHoldsFullText = input.aggregateOutputPath !== undefined && !outputWarning
3229
+ && !input.producedChildOutputPaths.has(resolveWorkflowHostOutputClaimPath(input.aggregateOutputPath));
3230
+ if (!aggregateHoldsFullText)
3231
+ saveWarning = writeWorkflowAggregateOutput(input.fullResultPath, fullText, new Set());
3232
+ savedPath = aggregateHoldsFullText ? input.aggregateOutputPath : saveWarning ? undefined : input.fullResultPath;
3233
+ if (scriptCut)
3234
+ shownScriptText = truncateOutput(scriptText, config, savedPath).text;
3235
+ }
3236
+ const shownTrace = traceSection((error) => error.length > WORKFLOW_TRACE_ERROR_MAX_CHARS ? `${error.slice(0, WORKFLOW_TRACE_ERROR_MAX_CHARS)}… (+${error.length - WORKFLOW_TRACE_ERROR_MAX_CHARS} chars)` : error);
3237
+ const traceNote = traceCut && !scriptCut && savedPath ? [`[TRUNCATED: trace errors shortened - full output at ${savedPath}]`] : [];
3238
+ const text = [...input.head, ...(shownScriptText ? [shownScriptText] : []), ...shownTrace, ...input.tail, ...traceNote].join("\n\n");
3239
+ return appendWorkflowOutputWarning(appendWorkflowOutputWarning(text, outputWarning), saveWarning);
3240
+ }
3173
3241
  export function resolveWorkflowChildLocalCwd(input) {
3174
3242
  if (input.params.machine !== undefined)
3175
3243
  return input.workflowCwd;
@@ -4536,6 +4604,15 @@ export function createSubagentExecutor(deps) {
4536
4604
  if (capacityOverrideError)
4537
4605
  return buildRequestedModeError(requestParams, capacityOverrideError);
4538
4606
  let workflowPreflight;
4607
+ // Only workflow requests read the session here; other actions (for example doctor) must not.
4608
+ const resolveWorkflowParentModel = () => parentModelOverride !== undefined
4609
+ ? parentModelOverride
4610
+ : (() => {
4611
+ const currentParentModel = normalizeParentModel(ctx.model);
4612
+ return (preserveActiveSession
4613
+ ? currentParentModel
4614
+ : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4615
+ })();
4539
4616
  try {
4540
4617
  if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
4541
4618
  throw new Error("preflight requires workflowScript or workflowScriptPath.");
@@ -4547,9 +4624,7 @@ export function createSubagentExecutor(deps) {
4547
4624
  catch (error) {
4548
4625
  const message = error instanceof Error ? error.message : String(error);
4549
4626
  if (requestParams.action?.trim() === "validate") {
4550
- const validation = validateWorkflowScript(requestParams.workflowScript ?? "", {
4551
- maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
4552
- });
4627
+ const validation = validateWorkflowScript(requestParams.workflowScript ?? "", workflowValidationOptions(deps, requestParams, ctx.cwd, () => resolveWorkflowParentModel()?.provider));
4553
4628
  const invalidValidation = { ...validation, ok: false, errors: [...validation.errors, { message }] };
4554
4629
  return {
4555
4630
  content: [{ type: "text", text: JSON.stringify(invalidValidation) }],
@@ -4560,9 +4635,7 @@ export function createSubagentExecutor(deps) {
4560
4635
  return buildRequestedModeError(requestParams, message);
4561
4636
  }
4562
4637
  if (requestParams.action?.trim() === "validate") {
4563
- const validation = validateWorkflowScript(requestParams.workflowScript ?? "", {
4564
- maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
4565
- });
4638
+ const validation = validateWorkflowScript(requestParams.workflowScript ?? "", workflowValidationOptions(deps, requestParams, ctx.cwd, () => resolveWorkflowParentModel()?.provider));
4566
4639
  return buildWorkflowValidationResult(validation, "management", workflowPreflight);
4567
4640
  }
4568
4641
  const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
@@ -4581,14 +4654,13 @@ export function createSubagentExecutor(deps) {
4581
4654
  const workflowArgsEvidence = requestParams.args === undefined ? undefined : { args: requestParams.args, argsDigest: stableJsonDigest(requestParams.args) };
4582
4655
  const workflowArgs = workflowArgsEvidence?.args;
4583
4656
  const workflowArgsDigest = workflowArgsEvidence?.argsDigest;
4584
- const workflowValidation = validateWorkflowScript(requestParams.workflowScript, {
4585
- maxSubagentSpawnsPerRun: requestParams.maxSubagentSpawnsPerRun ?? resolveMaxSubagentSpawnsPerRun(deps.config.maxSubagentSpawnsPerRun),
4586
- });
4657
+ const workflowParentModel = resolveWorkflowParentModel();
4658
+ const workflowValidation = validateWorkflowScript(requestParams.workflowScript, workflowValidationOptions(deps, requestParams, ctx.cwd, () => workflowParentModel?.provider));
4587
4659
  if (!workflowValidation.ok && publicExecution)
4588
4660
  return buildWorkflowValidationResult(workflowValidation, "workflow", workflowPreflight);
4589
- const spawnBudgetErrors = workflowValidation.errors.filter((error) => error.kind === "spawn-budget");
4590
- if (spawnBudgetErrors.length > 0) {
4591
- return buildRequestedModeError(requestParams, `Workflow '${_id}' validation failed before child launch; no children launched. ${spawnBudgetErrors.map((error) => error.message).join(" ")}`);
4661
+ const launchBlockingErrors = workflowValidation.errors.filter((error) => error.kind === "spawn-budget" || error.kind === "agent");
4662
+ if (launchBlockingErrors.length > 0) {
4663
+ return buildRequestedModeError(requestParams, `Workflow '${_id}' validation failed before child launch; no children launched. ${launchBlockingErrors.map((error) => error.message).join(" ")}`);
4592
4664
  }
4593
4665
  for (const warning of workflowValidation.warnings ?? [])
4594
4666
  console.warn(`[pi-subagents] ${warning.message}`);
@@ -4603,14 +4675,6 @@ export function createSubagentExecutor(deps) {
4603
4675
  if (requestParams.async !== false)
4604
4676
  return buildRequestedModeError(requestParams, "Workflow child permit supports foreground workflow roots only; set async:false.");
4605
4677
  }
4606
- const workflowParentModel = parentModelOverride !== undefined
4607
- ? parentModelOverride
4608
- : (() => {
4609
- const currentParentModel = normalizeParentModel(ctx.model);
4610
- return (preserveActiveSession
4611
- ? currentParentModel
4612
- : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4613
- })();
4614
4678
  if (requestParams.extensionBindings !== undefined) {
4615
4679
  try {
4616
4680
  requestParams.extensionBindings = normalizeExtensionBindings(requestParams.extensionBindings).value;
@@ -4620,6 +4684,13 @@ export function createSubagentExecutor(deps) {
4620
4684
  }
4621
4685
  }
4622
4686
  const parentCwd = ctx.cwd;
4687
+ for (const [name, value] of [["timeoutMs", requestParams.timeoutMs], ["maxRuntimeMs", requestParams.maxRuntimeMs]]) {
4688
+ if (value === undefined)
4689
+ continue;
4690
+ const overflowError = timerDelayOverflowError(name, value);
4691
+ if (overflowError)
4692
+ return buildRequestedModeError(requestParams, overflowError);
4693
+ }
4623
4694
  const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? resolveConfigDefaultTimeoutMs(deps.config.timeoutMs) ?? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
4624
4695
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4625
4696
  if (workflowUsageBudget.error)
@@ -5522,10 +5593,13 @@ export function createSubagentExecutor(deps) {
5522
5593
  });
5523
5594
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, workflow.trace, { settled: true });
5524
5595
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5525
- const returnPreview = formatWorkflowValue(workflow.value).slice(0, 1_000);
5526
- const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${workflow.emits.map(formatWorkflowValue).join(", ").slice(0, 1_000)}` : "";
5596
+ const returnText = formatWorkflowValue(workflow.value);
5597
+ const emitText = workflow.emits.map(formatWorkflowValue).join(", ");
5598
+ const returnPreview = returnText.length > 1_000 ? `${returnText.slice(0, 1_000)}…` : returnText;
5599
+ const emitPreview = workflow.emits.length > 0 ? ` Emitted: ${emitText.length > 1_000 ? `${emitText.slice(0, 1_000)}…` : emitText}` : "";
5600
+ const previewNote = returnText.length > 1_000 || emitText.length > 1_000 ? ` (truncated; full return value and emits: ${statusPath} (workflow.value, workflow.emits))` : "";
5527
5601
  const runningSummary = workflowRunningChildrenSummary(workflow.children);
5528
- const summary = `${runningSummary ? `Workflow dispatch completed; ${runningSummary}` : `Workflow completed with ${workflow.children.length} child run(s).`} Return: ${returnPreview}${emitPreview} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5602
+ const summary = `${runningSummary ? `Workflow dispatch completed; ${runningSummary}` : `Workflow completed with ${workflow.children.length} child run(s).`} Return: ${returnPreview}${emitPreview}${previewNote} Trace: ${workflow.trace.length} event(s).${workflowOutputPathMappingSummary(workflow.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5529
5603
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, summary, producedChildOutputPaths);
5530
5604
  const resultSummary = appendWorkflowOutputWarning(summary, outputWarning);
5531
5605
  const workflowUsage = sumResultsUsage(workflowResults);
@@ -5576,15 +5650,17 @@ export function createSubagentExecutor(deps) {
5576
5650
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5577
5651
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5578
5652
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5579
- status = compactOptional({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5653
+ status = compactOptional({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(error instanceof WorkflowScriptError && error.errorKind ? { failureKind: error.errorKind } : {}), ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5580
5654
  if (pauseForDetached) {
5581
5655
  const promoted = promotePausedWorkflowIfSettled(status);
5582
5656
  if (promoted)
5583
5657
  status = promoted;
5584
5658
  }
5659
+ // A script can throw an error of any size; status.json keeps the full text.
5660
+ const errorPreview = status.error && status.error.length > 1_000 ? `${status.error.slice(0, 1_000)}… (truncated; full error: ${statusPath} (error))` : status.error;
5585
5661
  const terminalSummary = `${status.state === "complete"
5586
5662
  ? "Workflow completed after detached child finished."
5587
- : status.error ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5663
+ : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5588
5664
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
5589
5665
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5590
5666
  const receiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
@@ -5632,6 +5708,9 @@ export function createSubagentExecutor(deps) {
5632
5708
  const workflowOutput = typeof workflowChildDefaults.output === "string" || typeof workflowChildDefaults.output === "boolean" ? workflowChildDefaults.output : undefined;
5633
5709
  const configuredOutputBaseDir = resolveConfiguredSingleRunOutputBaseDir(deps);
5634
5710
  const workflowAggregateOutputPath = resolveSingleOutputPath(workflowOutput, ctx.cwd, workflowCwd, resolveSingleRunOutputBaseDir(deps, workflowArtifactsDir, foregroundWorkflowRunId));
5711
+ // A top-level artifact file: outside outputs/, where children save their reports, and
5712
+ // within artifact retention, which only removes top-level files.
5713
+ const workflowFullResultPath = path.join(workflowArtifactsDir, `${sanitizeRunPathSegment(foregroundWorkflowRunId)}_workflow-result.md`);
5635
5714
  const claimedOutputPaths = new Map();
5636
5715
  const childOutputOverrides = new Map();
5637
5716
  const childOutputClaimPaths = new Map();
@@ -5810,27 +5889,26 @@ export function createSubagentExecutor(deps) {
5810
5889
  const finalPreflightTrace = annotateWorkflowPreflightTrace(workflow.trace, workflowPreflight);
5811
5890
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children });
5812
5891
  const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "complete", workflow.children, workflowChildren, undefined, [...workflowHostSteps.values()], workflowResource?.provenance, workflowArgsDigest);
5813
- const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5892
+ const trace = finalPreflightTrace.map((entry) => ({ line: `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.durationMs !== undefined ? ` in ${entry.durationMs}ms` : ""}${entry.warning ? ` — ${entry.warning}` : ""}`, error: entry.error }));
5814
5893
  const runningSummary = workflowRunningChildrenSummary(workflow.children);
5815
- const sections = [
5816
- ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
5817
- runningSummary ? `Workflow dispatch completed; ${runningSummary}` : "Workflow completed.",
5818
- `Return:\n${formatWorkflowValue(workflow.value)}`,
5819
- ];
5820
- if (workflow.emits.length > 0)
5821
- sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
5822
- if (workflow.console.length > 0)
5823
- sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5824
- if (traceLines.length > 0)
5825
- sections.push(`Call trace:\n${traceLines.join("\n")}`);
5826
- if (finalPreflightWarnings.length > 0)
5827
- sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
5828
5894
  const outputMappings = workflowOutputPathMappingSummary(workflow.children).trim();
5829
- if (outputMappings)
5830
- sections.push(outputMappings);
5831
- const workflowText = sections.join("\n\n");
5832
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
5833
- const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5895
+ const displayText = formatWorkflowResultText({
5896
+ head: [...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []), runningSummary ? `Workflow dispatch completed; ${runningSummary}` : "Workflow completed."],
5897
+ script: [
5898
+ `Return:\n${formatWorkflowValue(workflow.value)}`,
5899
+ ...(workflow.emits.length > 0 ? [`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`] : []),
5900
+ ...(workflow.console.length > 0 ? [`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`] : []),
5901
+ ],
5902
+ trace,
5903
+ tail: [
5904
+ ...(finalPreflightWarnings.length > 0 ? [formatWorkflowPreflightWarnings(finalPreflightWarnings)] : []),
5905
+ ...(outputMappings ? [outputMappings] : []),
5906
+ ],
5907
+ maxOutput: requestParams.maxOutput,
5908
+ aggregateOutputPath: workflowAggregateOutputPath,
5909
+ fullResultPath: workflowFullResultPath,
5910
+ producedChildOutputPaths,
5911
+ });
5834
5912
  return attachWorkflowMission(withRunFanoutBudget({
5835
5913
  content: [{ type: "text", text: displayText }],
5836
5914
  details: compactOptional({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(workflow.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
@@ -5841,32 +5919,32 @@ export function createSubagentExecutor(deps) {
5841
5919
  const text = workflowFailureMessage(error, foregroundWorkflowRunId, partial.children);
5842
5920
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5843
5921
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5844
- const traceLines = finalPreflightTrace.map((entry) => `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}${entry.error ? ` — ${entry.error}` : ""}`);
5845
- const sections = [
5846
- ...(workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : []),
5847
- `Workflow failed: ${text}`,
5848
- ];
5849
- if (partial.emits.length > 0)
5850
- sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
5851
- if (partial.console.length > 0)
5852
- sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
5853
- if (traceLines.length > 0)
5854
- sections.push(`Call trace:\n${traceLines.join("\n")}`);
5855
- if (finalPreflightWarnings.length > 0)
5856
- sections.push(formatWorkflowPreflightWarnings(finalPreflightWarnings));
5922
+ const trace = finalPreflightTrace.map((entry) => ({ line: `- ${entry.operation} ${entry.key}: ${entry.state}${entry.runId ? ` (${entry.runId})` : ""}${entry.warning ? ` — ${entry.warning}` : ""}`, error: entry.error }));
5857
5923
  const outputMappings = workflowOutputPathMappingSummary(partial.children).trim();
5858
- if (outputMappings)
5859
- sections.push(outputMappings);
5860
- const workflowText = sections.join("\n\n");
5861
- const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, workflowText, producedChildOutputPaths);
5862
- const displayText = appendWorkflowOutputWarning(workflowText, outputWarning);
5924
+ const displayText = formatWorkflowResultText({
5925
+ head: workflowPreflight ? [formatWorkflowPreflight(workflowPreflight)] : [],
5926
+ script: [
5927
+ `Workflow failed: ${text}`,
5928
+ ...(partial.emits.length > 0 ? [`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`] : []),
5929
+ ...(partial.console.length > 0 ? [`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`] : []),
5930
+ ],
5931
+ trace,
5932
+ tail: [
5933
+ ...(finalPreflightWarnings.length > 0 ? [formatWorkflowPreflightWarnings(finalPreflightWarnings)] : []),
5934
+ ...(outputMappings ? [outputMappings] : []),
5935
+ ],
5936
+ maxOutput: requestParams.maxOutput,
5937
+ aggregateOutputPath: workflowAggregateOutputPath,
5938
+ fullResultPath: workflowFullResultPath,
5939
+ producedChildOutputPaths,
5940
+ });
5863
5941
  const workflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "failed", inventoryComplete: true, trace: partial.trace, children: partial.children });
5864
5942
  const terminalOutcome = workflowFailureTerminalOutcome(error, partial.children, usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)));
5865
5943
  const receipt = terminalWorkflowReceipt(foregroundWorkflowRunId, "failed", partial.children, workflowChildren, terminalOutcome, [...workflowHostSteps.values()], workflowResource?.provenance, workflowArgsDigest);
5866
5944
  return attachWorkflowMission(withRunFanoutBudget({
5867
5945
  content: [{ type: "text", text: displayText }],
5868
5946
  isError: true,
5869
- details: compactOptional({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5947
+ details: compactOptional({ mode: "workflow", runId: foregroundWorkflowRunId, results: workflowDetailsResults(partial.children), ...(workflowPreflight ? { preflight: workflowPreflight } : {}), workflowChildren, totalChildUsage: sumResultsUsage(workflowResults), totalCost: sumResultsCost(workflowResults), usageBudget: usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults)), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(error instanceof WorkflowScriptError && error.errorKind ? { failureKind: error.errorKind } : {}), ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}), receipt }, chatProgress }),
5870
5948
  }, workflowFanoutBudget));
5871
5949
  }
5872
5950
  finally {