@selesai/code 0.8.4 → 0.8.5

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 (96) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +7 -1
  3. package/dist/core/slash-commands.js +1 -0
  4. package/dist/defaults/models.json +41 -55
  5. package/dist/defaults/settings.json +7 -8
  6. package/dist/extensions/pi-subagents/CHANGELOG.md +26 -3
  7. package/dist/extensions/pi-subagents/README.md +2 -0
  8. package/dist/extensions/pi-subagents/docs/configuration.md +46 -2
  9. package/dist/extensions/pi-subagents/docs/observability.md +1 -1
  10. package/dist/extensions/pi-subagents/docs/tool-reference.md +1 -1
  11. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  12. package/dist/extensions/pi-subagents/package.json +1 -1
  13. package/dist/extensions/pi-subagents/src/agents/agents.ts +9 -3
  14. package/dist/extensions/pi-subagents/src/extension/config.ts +6 -0
  15. package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
  16. package/dist/extensions/pi-subagents/src/extension/index.ts +21 -0
  17. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +5 -0
  18. package/dist/extensions/pi-subagents/src/extension/rpc.ts +3 -9
  19. package/dist/extensions/pi-subagents/src/extension/schemas.ts +2 -2
  20. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
  21. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +4 -7
  22. package/dist/extensions/pi-subagents/src/missions/store.ts +4 -4
  23. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +6 -2
  24. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
  25. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +9 -5
  26. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +117 -29
  27. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -1
  28. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +11 -5
  29. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
  30. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
  31. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
  32. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +5 -1
  33. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -3
  34. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +84 -36
  35. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +37 -2
  36. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +135 -22
  37. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
  38. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
  39. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +614 -187
  40. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +13 -4
  41. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
  42. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +23 -0
  43. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  44. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +44 -1
  45. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
  46. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
  47. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +19 -3
  48. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +17 -5
  49. package/dist/extensions/pi-subagents/src/shared/types.ts +92 -0
  50. package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -1
  51. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +7 -5
  52. package/dist/extensions/pi-subagents/src/tui/fleet.ts +225 -12
  53. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +22 -5
  54. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +87 -0
  55. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +96 -6
  56. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +28 -5
  57. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +8 -5
  58. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +83 -2
  59. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +211 -15
  60. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +41 -3
  61. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +314 -10
  62. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +31 -1
  63. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
  64. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +21 -2
  65. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +16 -1
  66. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +6 -1
  67. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +75 -4
  68. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +18 -0
  69. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +29 -1
  70. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +31 -5
  71. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +139 -1
  72. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  73. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +2 -1
  74. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
  75. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +30 -4
  76. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +14 -0
  77. package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
  78. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +83 -0
  79. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +2 -2
  80. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +55 -2
  81. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +12 -1
  82. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +7 -7
  83. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
  84. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +9 -0
  85. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -3
  86. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
  87. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +10 -1
  88. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +39 -0
  89. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +65 -0
  90. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +61 -0
  91. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  92. package/dist/modes/interactive/interactive-mode.js +48 -1
  93. package/docs/quickstart.md +27 -43
  94. package/docs/settings.md +4 -0
  95. package/docs/usage.md +1 -0
  96. package/package.json +2 -1
@@ -34,6 +34,7 @@ import {
34
34
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
35
35
  import { INTERCOM_BRIDGE_MARKER } from "../../intercom/intercom-bridge.ts";
36
36
  import { runSync } from "./execution.ts";
37
+ import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
37
38
  import { workflowForegroundSteeringLaunchOptions } from "./workflow-foreground-steering.ts";
38
39
  import {
39
40
  beginForegroundChild,
@@ -42,6 +43,7 @@ import {
42
43
  settleForegroundSchedulingOwner,
43
44
  updateForegroundChild,
44
45
  } from "./foreground-control.ts";
46
+ import { updateLiveEffectivePrompt } from "./prompt-audit.ts";
45
47
  import { buildChainSummary } from "../../shared/formatters.ts";
46
48
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
47
49
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
@@ -71,6 +73,7 @@ import {
71
73
  type ResolvedControlConfig,
72
74
  type ResolvedTurnBudget,
73
75
  type ResolvedToolBudget,
76
+ type RunFanoutBudgetDescriptor,
74
77
  type SingleResult,
75
78
  type ToolBudgetConfig,
76
79
  type ChainCheckpointState,
@@ -86,6 +89,7 @@ import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferen
86
89
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
87
90
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
88
91
  import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
92
+ import { claimRunFanoutBatch, RunFanoutLimitError } from "../shared/run-fanout-budget.ts";
89
93
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
90
94
  import type { ChainOutputMap } from "../../shared/types.ts";
91
95
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
@@ -167,6 +171,7 @@ interface ParallelChainRunInput {
167
171
  configToolBudget?: ToolBudgetConfig;
168
172
  globalSemaphore?: Semaphore;
169
173
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
174
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
170
175
  permissions?: PermissionConfig;
171
176
  dynamic?: boolean;
172
177
  }
@@ -368,7 +373,11 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
368
373
  beginForegroundChild(input.foregroundControl, {
369
374
  index: childIndex,
370
375
  agent: task.agent,
371
- description: cleanTask.trim(),
376
+ authoredTask: cleanTask,
377
+ effectivePrompt: taskStr,
378
+ cwd: taskCwd,
379
+ ...(outputPath ? { outputPath } : {}),
380
+ description: `${task.agent} child`,
372
381
  ...(effectiveModel ? { model: effectiveModel } : {}),
373
382
  ...(thinking ? { thinking } : {}),
374
383
  interrupt: () => {
@@ -388,8 +397,10 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
388
397
  result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
389
398
  permissions: input.permissions,
390
399
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
400
+ llmIntentArbiter: createTaskMutationArbiter(input.ctx),
391
401
  ...workflowForegroundSteeringLaunchOptions(input.foregroundControl, childIndex),
392
402
  capabilityCeiling: input.capabilityCeiling,
403
+ runFanoutBudget: input.runFanoutBudget ? { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}chain[${input.stepIndex}]${input.dynamic ? `.expand[${taskIndex}]` : `.parallel[${taskIndex}]`}` } : undefined,
393
404
  context: input.contextForAgent?.(task.agent),
394
405
  cwd: taskCwd,
395
406
  signal: input.signal,
@@ -422,6 +433,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
422
433
  agentContract,
423
434
  acceptance: task.acceptance,
424
435
  acceptanceContext: { mode: "chain", dynamic: input.dynamic && task.acceptance === undefined },
436
+ onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, childIndex, prompt) : undefined,
425
437
  timeoutMs: input.timeoutMs,
426
438
  deadlineAt: input.deadlineAt,
427
439
  turnBudget: input.turnBudget,
@@ -540,6 +552,7 @@ interface ChainExecutionParams {
540
552
  /** Global cap on simultaneously-running tasks within this chain. Defaults to DEFAULT_GLOBAL_CONCURRENCY_LIMIT. */
541
553
  globalConcurrencyLimit?: number;
542
554
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
555
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
543
556
  }
544
557
 
545
558
  interface ChainExecutionResult {
@@ -876,6 +889,7 @@ ${step.message}` : ""}` }],
876
889
  configToolBudget: params.configToolBudget,
877
890
  globalSemaphore,
878
891
  permissions: params.permissions,
892
+ runFanoutBudget: params.runFanoutBudget,
879
893
  });
880
894
  globalTaskIndex += step.parallel.length;
881
895
 
@@ -1002,6 +1016,19 @@ ${step.message}` : ""}` }],
1002
1016
  return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
1003
1017
  }
1004
1018
 
1019
+ try {
1020
+ if (params.runFanoutBudget) claimRunFanoutBatch(params.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
1021
+ } catch (error) {
1022
+ const message = error instanceof Error ? error.message : String(error);
1023
+ dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
1024
+ const result = buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
1025
+ if (error instanceof RunFanoutLimitError) {
1026
+ result.details.runFanoutBudget = error.snapshot;
1027
+ result.details.runFanoutRejection = error.rejection;
1028
+ }
1029
+ return result;
1030
+ }
1031
+
1005
1032
  dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
1006
1033
  agent: step.parallel.agent,
1007
1034
  label: materialized.parallel[itemIndex]?.label,
@@ -1137,6 +1164,7 @@ ${step.message}` : ""}` }],
1137
1164
  configToolBudget: params.configToolBudget,
1138
1165
  globalSemaphore,
1139
1166
  permissions: params.permissions,
1167
+ runFanoutBudget: params.runFanoutBudget,
1140
1168
  dynamic: true,
1141
1169
  });
1142
1170
  globalTaskIndex = dynamicStartIndex + reservedDynamicItems;
@@ -1332,7 +1360,11 @@ ${step.message}` : ""}` }],
1332
1360
  beginForegroundChild(foregroundControl, {
1333
1361
  index: childIndex,
1334
1362
  agent: seqStep.agent,
1335
- description: cleanTask.trim(),
1363
+ authoredTask: cleanTask,
1364
+ effectivePrompt: stepTask,
1365
+ cwd,
1366
+ ...(outputPath ? { outputPath } : {}),
1367
+ description: `${seqStep.agent} child`,
1336
1368
  ...(effectiveModel ? { model: effectiveModel } : {}),
1337
1369
  ...(thinking ? { thinking } : {}),
1338
1370
  interrupt: () => {
@@ -1369,8 +1401,10 @@ ${step.message}` : ""}` }],
1369
1401
  r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
1370
1402
  permissions: params.permissions,
1371
1403
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
1404
+ llmIntentArbiter: createTaskMutationArbiter(ctx),
1372
1405
  ...workflowForegroundSteeringLaunchOptions(params.foregroundControl, childIndex),
1373
1406
  capabilityCeiling: params.capabilityCeiling,
1407
+ runFanoutBudget: params.runFanoutBudget ? { ...params.runFanoutBudget, parentPath: `${params.runFanoutBudget.parentPath ? `${params.runFanoutBudget.parentPath}/` : ""}chain[${stepIndex}]` } : undefined,
1374
1408
  context: params.contextForAgent?.(seqStep.agent),
1375
1409
  cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
1376
1410
  signal,
@@ -1403,6 +1437,7 @@ ${step.message}` : ""}` }],
1403
1437
  agentContract,
1404
1438
  acceptance: seqStep.acceptance,
1405
1439
  acceptanceContext: { mode: "chain" },
1440
+ onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, childIndex, prompt) : undefined,
1406
1441
  timeoutMs: params.timeoutMs,
1407
1442
  deadlineAt,
1408
1443
  turnBudget: params.turnBudget,
@@ -54,22 +54,25 @@ import {
54
54
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
55
55
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
56
56
  import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
57
+ import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
57
58
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
58
59
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
59
60
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
60
61
  import { resolvePermissionRules } from "../shared/permissions.ts";
61
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
62
+ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
62
63
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
63
64
  import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
64
65
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
65
66
  import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
66
67
  import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
67
68
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
68
- import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
69
+ import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
69
70
  import {
70
71
  buildModelCandidates,
71
72
  formatModelAttemptNote,
73
+ formatModelRetryNote,
72
74
  isRetryableModelFailure,
75
+ MODEL_RETRY_DELAYS_MS,
73
76
  } from "../shared/model-fallback.ts";
74
77
  import {
75
78
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
@@ -90,6 +93,7 @@ import {
90
93
  summarizeRecentMutatingFailures,
91
94
  } from "../shared/long-running-guard.ts";
92
95
  import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport, validateAcceptanceInput } from "../shared/acceptance.ts";
96
+ import { PROMPT_REDACTED } from "../../shared/utils.ts";
93
97
  import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
94
98
  import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
95
99
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
@@ -117,6 +121,12 @@ function withRunContext<T extends SingleResult>(result: T, context: RunSyncOptio
117
121
  return result;
118
122
  }
119
123
 
124
+ function redactResultPrompt<T extends SingleResult>(result: T): T {
125
+ result.task = PROMPT_REDACTED;
126
+ if (result.progress) result.progress.task = PROMPT_REDACTED;
127
+ return result;
128
+ }
129
+
120
130
  function sumUsage(target: Usage, source: Usage): void {
121
131
  target.input += source.input;
122
132
  target.output += source.output;
@@ -139,7 +149,7 @@ function persistSingleResultMetadata(input: {
139
149
  writeMetadata(input.metadataPath, {
140
150
  runId: input.runId,
141
151
  agent: input.agent,
142
- task: input.task,
152
+ task: PROMPT_REDACTED,
143
153
  exitCode: target.exitCode,
144
154
  processSignal: target.processSignal,
145
155
  usage: target.usage,
@@ -231,6 +241,7 @@ function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): vo
231
241
  function snapshotProgress(progress: AgentProgress): AgentProgress {
232
242
  return {
233
243
  ...progress,
244
+ task: PROMPT_REDACTED,
234
245
  skills: progress.skills ? [...progress.skills] : undefined,
235
246
  recentTools: boundStreamedRecentTools(progress.recentTools),
236
247
  recentOutput: boundStreamedRecentOutput(progress.recentOutput),
@@ -240,6 +251,7 @@ function snapshotProgress(progress: AgentProgress): AgentProgress {
240
251
  function snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {
241
252
  return {
242
253
  ...result,
254
+ task: PROMPT_REDACTED,
243
255
  messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,
244
256
  usage: { ...result.usage },
245
257
  skills: result.skills ? [...result.skills] : undefined,
@@ -291,6 +303,7 @@ async function runSingleAttempt(
291
303
  attemptNotes: string[];
292
304
  outputSnapshot?: SingleOutputSnapshot;
293
305
  originalTask?: string;
306
+ taskDelivery?: SubagentTaskDelivery;
294
307
  },
295
308
  ): Promise<SingleResult> {
296
309
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
@@ -312,6 +325,7 @@ async function runSingleAttempt(
312
325
  const { args, env: sharedEnv, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit } = buildPiArgs({
313
326
  baseArgs: ["--mode", "json", "-p"],
314
327
  task,
328
+ taskDelivery: shared.taskDelivery,
315
329
  sessionEnabled: shared.sessionEnabled,
316
330
  sessionDir: options.sessionDir,
317
331
  sessionFile: options.sessionFile,
@@ -337,6 +351,7 @@ async function runSingleAttempt(
337
351
  parentControlInbox: options.nestedRoute?.controlInbox,
338
352
  parentRootRunId: options.nestedRoute?.rootRunId,
339
353
  parentCapabilityToken: options.nestedRoute?.capabilityToken,
354
+ runFanoutBudget: options.runFanoutBudget,
340
355
  parentSessionId: options.parentSessionId,
341
356
  steerInboxDir: options.steerInboxDir,
342
357
  steerCapabilityPath: options.steerCapabilityPath,
@@ -1277,15 +1292,38 @@ async function runSingleAttempt(
1277
1292
  mcpDirectTools: agent.mcpDirectTools,
1278
1293
  })
1279
1294
  : undefined;
1280
- const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
1295
+ let completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
1296
+ // The classifier is deliberately narrow, so a read-only review task can
1297
+ // still be misread as implementation. Arbitrate BEFORE any failure side
1298
+ // effect is published (effects, exit code, progress, notifications,
1299
+ // acceptance, output persistence): only a confident read-only verdict
1300
+ // rescues, and the task text alone is evidence — never the child's own
1301
+ // final message.
1302
+ let arbiterRescued = false;
1303
+ if (completionGuardTriggered) {
1304
+ const arbitration = await arbitrateCompletionGuardRescue({
1305
+ guardTriggered: true,
1306
+ task: shared.originalTask ?? task,
1307
+ arbiter: options.llmIntentArbiter,
1308
+ });
1309
+ completionGuardTriggered = arbitration.triggered;
1310
+ arbiterRescued = arbitration.rescued;
1311
+ }
1281
1312
  if (completionGuard) {
1282
1313
  result.effects = {
1283
1314
  ...(result.effects ?? {}),
1284
1315
  fileMutation: {
1285
- status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" : "observed" : "not-applicable",
1316
+ status: completionGuard.expectedMutation
1317
+ ? completionGuardTriggered
1318
+ ? "missing"
1319
+ : arbiterRescued
1320
+ ? "not-applicable"
1321
+ : "observed"
1322
+ : "not-applicable",
1286
1323
  expected: completionGuard.expectedMutation,
1287
1324
  attempted: completionGuard.attemptedMutation || observedMutationAttempt,
1288
1325
  ...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
1326
+ ...(arbiterRescued ? { resolvedBy: "llm-intent-arbiter" } : {}),
1289
1327
  },
1290
1328
  };
1291
1329
  }
@@ -1355,7 +1393,7 @@ async function runSyncCompletion(
1355
1393
  };
1356
1394
  const agent = agents.find((a) => a.name === agentName);
1357
1395
  if (!agent) {
1358
- return withRunContext({
1396
+ return redactResultPrompt(withRunContext({
1359
1397
  index: options.index ?? 0,
1360
1398
  agent: agentName,
1361
1399
  task,
@@ -1363,12 +1401,12 @@ async function runSyncCompletion(
1363
1401
  messages: [],
1364
1402
  usage: emptyUsage(),
1365
1403
  error: `Unknown agent: ${agentName}`,
1366
- }, options.context);
1404
+ }, options.context));
1367
1405
  }
1368
1406
  try {
1369
1407
  assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
1370
1408
  } catch (error) {
1371
- return withRunContext({
1409
+ return redactResultPrompt(withRunContext({
1372
1410
  index: options.index ?? 0,
1373
1411
  agent: agent.name,
1374
1412
  task,
@@ -1377,11 +1415,11 @@ async function runSyncCompletion(
1377
1415
  usage: emptyUsage(),
1378
1416
  error: error instanceof Error ? error.message : String(error),
1379
1417
  ...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
1380
- }, options.context);
1418
+ }, options.context));
1381
1419
  }
1382
1420
  const acceptanceErrors = validateAcceptanceInput(options.acceptance);
1383
1421
  if (acceptanceErrors.length > 0) {
1384
- return withRunContext({
1422
+ return redactResultPrompt(withRunContext({
1385
1423
  index: options.index ?? 0,
1386
1424
  agent: agentName,
1387
1425
  task,
@@ -1389,11 +1427,11 @@ async function runSyncCompletion(
1389
1427
  messages: [],
1390
1428
  usage: emptyUsage(),
1391
1429
  error: acceptanceErrors.join(" "),
1392
- }, options.context);
1430
+ }, options.context));
1393
1431
  }
1394
1432
  const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
1395
1433
  if (outputModeValidationError) {
1396
- return withRunContext({
1434
+ return redactResultPrompt(withRunContext({
1397
1435
  index: options.index ?? 0,
1398
1436
  agent: agentName,
1399
1437
  task,
@@ -1402,7 +1440,7 @@ async function runSyncCompletion(
1402
1440
  usage: emptyUsage(),
1403
1441
  outputMode: options.outputMode,
1404
1442
  error: outputModeValidationError,
1405
- }, options.context);
1443
+ }, options.context));
1406
1444
  }
1407
1445
 
1408
1446
  const shareEnabled = options.share === true;
@@ -1419,6 +1457,7 @@ async function runSyncCompletion(
1419
1457
  });
1420
1458
  const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
1421
1459
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1460
+ options.onEffectivePrompt?.(taskWithAcceptance);
1422
1461
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
1423
1462
  if (options.context === "fork" && options.sessionFile && existsSync(options.sessionFile)) {
1424
1463
  alignForkedSessionCwd(options.sessionFile, options.cwd ?? runtimeCwd);
@@ -1433,7 +1472,7 @@ async function runSyncCompletion(
1433
1472
  agent.filePath ? path.dirname(agent.filePath) : skillCwd,
1434
1473
  );
1435
1474
  if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
1436
- return withRunContext({
1475
+ return redactResultPrompt(withRunContext({
1437
1476
  index: options.index ?? 0,
1438
1477
  agent: agentName,
1439
1478
  task,
@@ -1441,7 +1480,7 @@ async function runSyncCompletion(
1441
1480
  messages: [],
1442
1481
  usage: emptyUsage(),
1443
1482
  error: "Skills not found: pi-subagents",
1444
- }, options.context);
1483
+ }, options.context));
1445
1484
  }
1446
1485
  let systemPrompt = agent.systemPrompt?.trim() || "";
1447
1486
  if (resolvedSkills.length > 0) {
@@ -1476,7 +1515,7 @@ async function runSyncCompletion(
1476
1515
  artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
1477
1516
  ensureArtifactsDir(options.artifactsDir);
1478
1517
  if (options.artifactConfig?.includeInput !== false) {
1479
- writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
1518
+ writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${PROMPT_REDACTED}; live Prompt Audit only.\n`);
1480
1519
  }
1481
1520
  if (options.artifactConfig?.includeJsonl !== false) {
1482
1521
  jsonlPath = artifactPathsResult.jsonlPath;
@@ -1490,7 +1529,7 @@ async function runSyncCompletion(
1490
1529
  childIndex: options.index,
1491
1530
  cwd: options.cwd ?? runtimeCwd,
1492
1531
  });
1493
- transcriptWriter.writeInitialUserMessage(taskWithAcceptance);
1532
+ transcriptWriter.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1494
1533
  }
1495
1534
  }
1496
1535
 
@@ -1526,6 +1565,10 @@ async function runSyncCompletion(
1526
1565
  };
1527
1566
  let lastResult: SingleResult | undefined;
1528
1567
  const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1568
+ let modelRetryIndex = 0;
1569
+ // Escalated to "file" after an unexplained zero-activity startup failure so
1570
+ // retries keep the task text out of argv (endpoint pre-exec scans may deny it).
1571
+ let taskDeliveryOverride: SubagentTaskDelivery | undefined;
1529
1572
  modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
1530
1573
  const candidate = modelsToTry[modelIndex];
1531
1574
  for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
@@ -1544,6 +1587,7 @@ async function runSyncCompletion(
1544
1587
  .filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
1545
1588
  outputSnapshot,
1546
1589
  originalTask: task,
1590
+ taskDelivery: taskDeliveryOverride,
1547
1591
  });
1548
1592
  lastResult = result;
1549
1593
  if (startupAttemptIndex === 0) {
@@ -1612,6 +1656,10 @@ async function runSyncCompletion(
1612
1656
  }
1613
1657
  break modelAttemptsLoop;
1614
1658
  }
1659
+ if (!taskDeliveryOverride && result.processSignal === "SIGKILL") {
1660
+ taskDeliveryOverride = "file";
1661
+ attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
1662
+ }
1615
1663
  attempt.error = retryNote;
1616
1664
  attemptNotes.push(retryNote);
1617
1665
  continue;
@@ -1630,9 +1678,48 @@ async function runSyncCompletion(
1630
1678
  attempt.error = startupError;
1631
1679
  break modelAttemptsLoop;
1632
1680
  }
1633
- if (!isRetryableModelFailure(result.error) || modelIndex === modelsToTry.length - 1) break modelAttemptsLoop;
1634
- attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[modelIndex + 1]));
1635
- break;
1681
+ if (isRetryableModelFailure(result.error)) {
1682
+ if (modelIndex < modelsToTry.length - 1) {
1683
+ attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[modelIndex + 1]));
1684
+ modelRetryIndex = 0;
1685
+ break;
1686
+ }
1687
+ const modelRetryDelayMs = MODEL_RETRY_DELAYS_MS[modelRetryIndex];
1688
+ if (modelRetryDelayMs !== undefined) {
1689
+ const retryNote = formatModelRetryNote({
1690
+ model: attempt.model,
1691
+ attempt: modelRetryIndex + 1,
1692
+ maxAttempts: MODEL_RETRY_DELAYS_MS.length + 1,
1693
+ delayMs: modelRetryDelayMs,
1694
+ });
1695
+ const shouldRetry = await waitForSubagentStartupRetry(modelRetryDelayMs, [options.signal, options.interruptSignal]);
1696
+ if (!shouldRetry) {
1697
+ if (options.interruptSignal?.aborted) {
1698
+ result.exitCode = 0;
1699
+ result.interrupted = true;
1700
+ result.error = undefined;
1701
+ result.finalOutput = "Interrupted. Waiting for explicit next action.";
1702
+ if (result.progress) {
1703
+ result.progress.status = "running";
1704
+ result.progress.error = undefined;
1705
+ }
1706
+ } else {
1707
+ const cancellationError = "Subagent model retry cancelled before relaunch.";
1708
+ result.error = cancellationError;
1709
+ result.finalOutput = cancellationError;
1710
+ attempt.error = cancellationError;
1711
+ if (result.progress) result.progress.error = cancellationError;
1712
+ }
1713
+ break modelAttemptsLoop;
1714
+ }
1715
+ attempt.error = retryNote;
1716
+ attemptNotes.push(retryNote);
1717
+ modelRetryIndex += 1;
1718
+ startupAttemptIndex = -1;
1719
+ continue;
1720
+ }
1721
+ }
1722
+ break modelAttemptsLoop;
1636
1723
  }
1637
1724
  }
1638
1725
 
@@ -1726,13 +1813,39 @@ async function runSyncCompletion(
1726
1813
  stripAcceptanceReportsFromMessages(result.messages);
1727
1814
  if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
1728
1815
  result.exitCode = 1;
1816
+ if (result.savedOutputPath) {
1817
+ result.finalOutput = finalizeSingleOutput({
1818
+ fullOutput: result.finalOutput ?? "",
1819
+ outputPath: options.outputPath,
1820
+ outputMode: result.outputMode,
1821
+ exitCode: result.exitCode,
1822
+ preserveSavedOutput: true,
1823
+ savedPath: result.savedOutputPath,
1824
+ outputReference: result.outputReference,
1825
+ }).displayOutput;
1826
+ artifactOutputByResult.set(result, result.finalOutput);
1827
+ }
1729
1828
  result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
1829
+ if (artifactPathsResult && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
1830
+ try {
1831
+ writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
1832
+ output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
1833
+ error: result.error,
1834
+ transcriptPath: result.transcriptPath,
1835
+ metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
1836
+ }));
1837
+ } catch (error) {
1838
+ const message = `Artifact output post-processing failed: ${error instanceof Error ? error.message : String(error)}`;
1839
+ result.outputSaveError = result.outputSaveError ? `${result.outputSaveError}\n${message}` : message;
1840
+ }
1841
+ }
1730
1842
  if (result.progress) {
1731
1843
  result.progress.status = "failed";
1732
1844
  result.progress.error = result.error;
1733
1845
  }
1734
1846
  }
1735
1847
  if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
1848
+ redactResultPrompt(result);
1736
1849
  try {
1737
1850
  persistResultMetadata(result);
1738
1851
  } catch (error) {
@@ -1817,7 +1930,7 @@ export async function runSync(
1817
1930
  durationMs: publishedReceipt.progressSummary?.durationMs ?? 0,
1818
1931
  error: failureMessage,
1819
1932
  };
1820
- const failedResult: SingleResult = {
1933
+ const failedResult: SingleResult = redactResultPrompt({
1821
1934
  ...publishedReceipt,
1822
1935
  detached: undefined,
1823
1936
  detachedReason,
@@ -1833,7 +1946,7 @@ export async function runSync(
1833
1946
  acceptance: publishedReceipt.acceptance
1834
1947
  ? buildSkippedAcceptanceLedger(publishedReceipt.acceptance.effectiveAcceptance, { id: "completion-pipeline", message: failureMessage })
1835
1948
  : undefined,
1836
- };
1949
+ });
1837
1950
  if (strictContract) attachContractProjections(failedResult);
1838
1951
  try {
1839
1952
  // Replace the provisional detach receipt metadata with the authoritative
@@ -1,9 +1,15 @@
1
1
  import type { AgentProgress, ForegroundChildControl, ForegroundRunControl } from "../../shared/types.ts";
2
+ import { registerLivePromptAudit, removeLivePromptAudit, type PromptAuditRerunContract } from "./prompt-audit.ts";
2
3
 
3
4
  interface BeginForegroundChildInput {
4
5
  index: number;
5
6
  agent: string;
6
7
  description?: string;
8
+ authoredTask: string;
9
+ effectivePrompt: string;
10
+ cwd?: string;
11
+ outputPath?: string;
12
+ rerun?: PromptAuditRerunContract;
7
13
  model?: string;
8
14
  thinking?: string;
9
15
  interrupt: () => boolean;
@@ -109,6 +115,11 @@ export function beginForegroundChild(control: ForegroundRunControl, input: Begin
109
115
  }
110
116
  control.activeChildren ??= new Map();
111
117
  control.activeChildren.set(input.index, child);
118
+ registerLivePromptAudit(control, input.index, input.authoredTask, input.effectivePrompt, {
119
+ ...(input.cwd ? { cwd: input.cwd } : {}),
120
+ ...(input.outputPath ? { outputPath: input.outputPath } : {}),
121
+ ...(input.rerun ? { rerun: input.rerun } : {}),
122
+ });
112
123
  syncCurrentChild(control, child);
113
124
  }
114
125
 
@@ -121,6 +132,7 @@ export function updateForegroundChild(control: ForegroundRunControl, index: numb
121
132
  }
122
133
 
123
134
  export function finishForegroundChild(control: ForegroundRunControl, index: number): void {
135
+ removeLivePromptAudit(control, index);
124
136
  control.activeChildren?.delete(index);
125
137
  if (control.currentIndex === index) {
126
138
  const next = [...(control.activeChildren?.values() ?? [])]