pi-subagents 0.57.0 → 0.58.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 (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/docs/agents.md +13 -6
  3. package/docs/extension-api.md +39 -0
  4. package/docs/models.md +3 -3
  5. package/docs/tool-reference.md +3 -2
  6. package/docs/workflows.md +24 -0
  7. package/package.json +1 -1
  8. package/skills/pi-subagents/SKILL.md +2 -0
  9. package/skills/pi-subagents/references/execution-controls.md +1 -1
  10. package/skills/pi-subagents/references/management-authoring-rpc.md +1 -0
  11. package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
  12. package/src/agents/agent-management.ts +23 -0
  13. package/src/agents/agent-serializer.ts +5 -0
  14. package/src/agents/agents.ts +38 -3
  15. package/src/agents/runtime-agent-events.ts +70 -0
  16. package/src/agents/runtime-agent-registry.ts +10 -2
  17. package/src/api/agents.ts +10 -5
  18. package/src/api/preflight.ts +26 -1
  19. package/src/extension/index.ts +15 -4
  20. package/src/extension/rpc.ts +35 -14
  21. package/src/extension/schemas.ts +2 -2
  22. package/src/extension/tool-description.ts +8 -4
  23. package/src/integrations/herdr-status.ts +5 -0
  24. package/src/runs/background/async-execution.ts +29 -12
  25. package/src/runs/background/async-resume.ts +6 -2
  26. package/src/runs/background/async-status.ts +2 -0
  27. package/src/runs/background/chain-append.ts +2 -0
  28. package/src/runs/background/notify.ts +26 -3
  29. package/src/runs/background/result-delivery-ownership.ts +45 -0
  30. package/src/runs/background/result-watcher.ts +27 -10
  31. package/src/runs/background/subagent-runner.ts +71 -21
  32. package/src/runs/foreground/execution.ts +20 -1
  33. package/src/runs/foreground/subagent-executor.ts +57 -26
  34. package/src/runs/foreground/workflow-detach-reconcile.ts +128 -17
  35. package/src/runs/shared/completion-guard.ts +4 -3
  36. package/src/runs/shared/dynamic-fanout.ts +3 -3
  37. package/src/runs/shared/fast-mode-extension.ts +5 -5
  38. package/src/runs/shared/launch-cwd.ts +16 -0
  39. package/src/runs/shared/long-running-guard.ts +2 -1
  40. package/src/runs/shared/mcp-config-sources.ts +386 -0
  41. package/src/runs/shared/mcp-direct-tool-allowlist.ts +155 -42
  42. package/src/runs/shared/model-exclusions.ts +12 -0
  43. package/src/runs/shared/model-fallback.ts +16 -4
  44. package/src/runs/shared/parallel-utils.ts +8 -1
  45. package/src/runs/shared/pi-args.ts +61 -6
  46. package/src/runs/shared/single-output.ts +17 -0
  47. package/src/runs/shared/subagent-prompt-runtime.ts +85 -9
  48. package/src/shared/formatters.ts +6 -0
  49. package/src/shared/launch-contract.ts +4 -0
  50. package/src/shared/types.ts +42 -1
  51. package/src/shared/utils.ts +6 -29
  52. package/src/slash/subagents-admin.ts +3 -0
  53. package/src/tui/render.ts +14 -6
  54. package/src/workflows/scripted-workflow.ts +52 -5
  55. package/src/workflows/workflow-receipt.ts +39 -4
@@ -14,6 +14,7 @@ import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../s
14
14
  import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest, type StopRequest } from "./control-channel.ts";
15
15
  import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
16
16
  import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
17
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
17
18
  import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
18
19
  import {
19
20
  type ActivityState,
@@ -49,6 +50,7 @@ import {
49
50
  type SteeringTargetState,
50
51
  type SteeringTargetStatus,
51
52
  type SubagentChildStatusEvent,
53
+ type SettlementDiagnostic,
52
54
  DEFAULT_MAX_OUTPUT,
53
55
  type MaxOutputConfig,
54
56
  SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
@@ -101,7 +103,7 @@ import { createOwnedProcessTreeController, type OwnedProcessTreeController } fro
101
103
  import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
102
104
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
103
105
  import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
104
- import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
106
+ import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
105
107
  import {
106
108
  createMutatingFailureState,
107
109
  didMutatingToolFail,
@@ -124,7 +126,7 @@ import {
124
126
  formatWorktreeTaskCwdConflict,
125
127
  type WorktreeSetup,
126
128
  } from "../shared/worktree.ts";
127
- import { resolveEffectiveThinking } from "../../shared/model-info.ts";
129
+ import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
128
130
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, SUBAGENT_THINKING_CEILING_ENV } from "../../shared/thinking-ceiling.ts";
129
131
  import { launchBindingDigest } from "../../shared/launch-contract.ts";
130
132
  import { writeInitialProgressFile } from "../../shared/settings.ts";
@@ -543,6 +545,7 @@ interface RunPiStreamingResult {
543
545
  observedMutationAttempt?: boolean;
544
546
  structuredOutputToolInvoked?: boolean;
545
547
  structuredOutputMessageStartIndex?: number;
548
+ structuredOutput?: unknown;
546
549
  watchdog?: ChildWatchdogStateSnapshot;
547
550
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
548
551
  processInstanceId: string;
@@ -553,6 +556,7 @@ interface RunPiStreamingResult {
553
556
  currentToolArgs?: string;
554
557
  currentPath?: string;
555
558
  afterCompactionSettlement?: boolean;
559
+ effects?: import("../../shared/types.ts").EffectsProjection;
556
560
  }
557
561
 
558
562
  const MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS = 8_192;
@@ -561,16 +565,18 @@ function formatChildFailureDiagnostic(input: {
561
565
  error: string | undefined;
562
566
  afterCompactionSettlement?: boolean;
563
567
  missingFileOnlyOutput?: string;
568
+ missingStructuredOutput?: string;
564
569
  }): string | undefined {
565
- if (!input.error) return undefined;
566
570
  const notes = [
567
571
  input.afterCompactionSettlement ? "Child failure followed session compaction and agent settlement." : undefined,
568
572
  input.missingFileOnlyOutput ? `Required file-only output was not produced: ${input.missingFileOnlyOutput.slice(0, 2_048)}` : undefined,
573
+ input.missingStructuredOutput ? `Required structured output was not produced: ${input.missingStructuredOutput.slice(0, 2_048)}` : undefined,
569
574
  ].filter((note): note is string => Boolean(note));
570
575
  if (notes.length === 0) return input.error;
571
576
  const context = notes.join("\n");
572
577
  const errorLimit = MAX_CHILD_FAILURE_DIAGNOSTIC_CHARS - (context ? context.length + 1 : 0);
573
- return `${input.error.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
578
+ const baseError = input.error || "Subagent failed.";
579
+ return `${baseError.slice(0, Math.max(0, errorLimit))}${context ? `\n${context}` : ""}`;
574
580
  }
575
581
 
576
582
  function runPiStreaming(
@@ -596,6 +602,7 @@ function runPiStreaming(
596
602
  orcaProgressTab?: OrcaProgressTab,
597
603
  expectedModelForVerification?: string,
598
604
  modelVerificationRegistry?: Array<{ provider: string; id: string; fullId: string }>,
605
+ mutationTools?: readonly string[],
599
606
  ): Promise<RunPiStreamingResult> {
600
607
  return new Promise((resolve) => {
601
608
  const startedAt = Date.now();
@@ -791,7 +798,7 @@ function runPiStreaming(
791
798
  structuredOutputToolInvoked = true;
792
799
  structuredOutputMessageStartIndex = messages.length;
793
800
  }
794
- observedMutationAttempt = observedMutationAttempt || isMutatingTool(event.toolName, event.args);
801
+ observedMutationAttempt = observedMutationAttempt || isMutatingTool(event.toolName, event.args, mutationTools);
795
802
  const toolArgs = extractToolArgsPreview(event.args ?? {});
796
803
  writeOutputLine(toolArgs ? `${event.toolName}: ${toolArgs}` : event.toolName);
797
804
  return;
@@ -1297,7 +1304,7 @@ interface SingleStepContext {
1297
1304
  nestedRoute?: NestedRouteInfo;
1298
1305
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1299
1306
  runFanoutBudget?: RunFanoutBudgetDescriptor;
1300
- onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
1307
+ onAttemptStart?: (attempt: { model?: string; thinking?: string; contextLimit?: number }) => void;
1301
1308
  onChildEvent?: (event: ChildEvent) => void;
1302
1309
  onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
1303
1310
  onExternalProcess?: (process: ExternalProcessStatus) => void;
@@ -1399,6 +1406,8 @@ async function runSingleStepInner(
1399
1406
  model: step.model,
1400
1407
  modelCandidates: step.modelCandidates,
1401
1408
  mcpDirectTools: step.mcpDirectTools,
1409
+ mcpConfig: step.mcpConfig,
1410
+ runtimeServerNames: step.runtimeServerNames,
1402
1411
  cwd: step.cwd ?? ctx.cwd,
1403
1412
  requireReadTool: Boolean(step.skills?.length),
1404
1413
  structuredOutput: Boolean(effectiveStructuredOutput),
@@ -1594,6 +1603,10 @@ async function runSingleStepInner(
1594
1603
  });
1595
1604
  }
1596
1605
 
1606
+ const effectiveCwd = step.cwd ?? ctx.cwd;
1607
+ const cwdError = preflightLaunchCwd(step.requestedCwd ?? effectiveCwd, effectiveCwd);
1608
+ if (cwdError) return { agent: step.agent, output: cwdError, error: cwdError, exitCode: 1, context: step.context };
1609
+
1597
1610
  const candidates = step.modelCandidates !== undefined
1598
1611
  ? step.modelCandidates.length > 0 ? step.modelCandidates : [undefined]
1599
1612
  : step.model
@@ -1636,7 +1649,11 @@ async function runSingleStepInner(
1636
1649
  const message = error instanceof Error ? error.message : String(error);
1637
1650
  return omitUndefinedProperties({ agent: step.agent, output: message, error: message, exitCode: 1, context: step.context, thinkingCeiling: step.thinkingCeiling });
1638
1651
  }
1639
- ctx.onAttemptStart?.(omitUndefinedProperties({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) }));
1652
+ ctx.onAttemptStart?.(omitUndefinedProperties({
1653
+ model: candidate,
1654
+ thinking: resolveEffectiveThinking(candidate, step.thinking),
1655
+ contextLimit: findModelInfo(candidate, step.modelVerificationRegistry)?.contextWindow,
1656
+ }));
1640
1657
  const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1641
1658
  if (effectiveStructuredOutput) {
1642
1659
  try {
@@ -1665,6 +1682,7 @@ async function runSingleStepInner(
1665
1682
  sessionFile: step.sessionFile,
1666
1683
  model: candidate,
1667
1684
  inheritProjectContext: step.inheritProjectContext,
1685
+ inheritGlobalContext: step.inheritGlobalContext,
1668
1686
  inheritSkills: step.inheritSkills,
1669
1687
  requireReadTool: Boolean(step.skills?.length),
1670
1688
  tools: step.tools,
@@ -1675,6 +1693,8 @@ async function runSingleStepInner(
1675
1693
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1676
1694
  systemPromptMode: step.systemPromptMode,
1677
1695
  mcpDirectTools: step.mcpDirectTools,
1696
+ mcpConfig: step.mcpConfig,
1697
+ runtimeServerNames: step.runtimeServerNames,
1678
1698
  capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
1679
1699
  cwd: step.cwd ?? ctx.cwd,
1680
1700
  promptFileStem: step.agent,
@@ -1718,6 +1738,8 @@ async function runSingleStepInner(
1718
1738
  model: step.model,
1719
1739
  modelCandidates: step.modelCandidates,
1720
1740
  mcpDirectTools: step.mcpDirectTools,
1741
+ mcpConfig: step.mcpConfig,
1742
+ runtimeServerNames: step.runtimeServerNames,
1721
1743
  cwd: step.cwd ?? ctx.cwd,
1722
1744
  requireReadTool: Boolean(step.skills?.length),
1723
1745
  structuredOutput: Boolean(effectiveStructuredOutput),
@@ -1737,6 +1759,7 @@ async function runSingleStepInner(
1737
1759
  systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
1738
1760
  systemPromptMode: step.systemPromptMode,
1739
1761
  inheritProjectContext: step.inheritProjectContext,
1762
+ inheritGlobalContext: step.inheritGlobalContext,
1740
1763
  inheritSkills: step.inheritSkills,
1741
1764
  skills: step.skills,
1742
1765
  tools: toolPlan.effectiveToolAllowlist,
@@ -1773,6 +1796,7 @@ async function runSingleStepInner(
1773
1796
  ctx.orcaProgressTab,
1774
1797
  expectedModelForVerification,
1775
1798
  step.modelVerificationRegistry,
1799
+ step.mutationTools,
1776
1800
  );
1777
1801
  if (run.processCloseObservedAt !== undefined) {
1778
1802
  writerProcesses.push({
@@ -1863,6 +1887,7 @@ async function runSingleStepInner(
1863
1887
  : undefined;
1864
1888
  const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
1865
1889
  const completionToolPlan = resolvedTaskToolPlan;
1890
+ const completionTools = completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools;
1866
1891
  const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, step.cwd ?? ctx.cwd);
1867
1892
  finalMutationEvidence = mutationEvidence;
1868
1893
  const completionMutationEvidence = ctx.trackedMutationEvidenceForCompletionGuard === false ? undefined : mutationEvidence;
@@ -1871,8 +1896,9 @@ async function runSingleStepInner(
1871
1896
  agent: step.agent,
1872
1897
  task: taskForCompletionGuard,
1873
1898
  messages: run.messages,
1874
- tools: completionToolPlan ? (completionToolPlan.explicitToolAllowlist ? completionToolPlan.effectiveToolAllowlist : undefined) : step.tools,
1899
+ tools: completionTools,
1875
1900
  mcpDirectTools: completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools,
1901
+ mutationTools: step.mutationTools,
1876
1902
  toolAvailabilityError,
1877
1903
  mutationEvidence: completionMutationEvidence,
1878
1904
  }))
@@ -1880,6 +1906,9 @@ async function runSingleStepInner(
1880
1906
  const mutationAttemptObserved = run.observedMutationAttempt === true || completionMutationEvidence?.attemptedMutation === true;
1881
1907
  const completionGuardTriggered = completionGuard?.triggered === true && !mutationAttemptObserved;
1882
1908
  const completionGuardBlocked = completionGuard?.blocked === true;
1909
+ const mutationExpected = completionGuard?.expectedMutation ?? (completionGuardEnabled
1910
+ && hasMutationToolCapability(completionTools, completionToolPlan?.effectiveMcpTools ?? step.mcpDirectTools)
1911
+ && expectsImplementationMutation(step.agent, taskForCompletionGuard));
1883
1912
  const fileMutationEffect = completionGuard
1884
1913
  ? {
1885
1914
  status: completionGuardBlocked ? "blocked" as const : completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
@@ -1940,7 +1969,24 @@ async function runSingleStepInner(
1940
1969
  toolBudgetBlocked = Boolean(blockedMessage);
1941
1970
  toolBudget = toolBudgetState(step.toolBudget, toolMessages.length, blockedMessage ? (blockedMessage as { toolName?: string }).toolName : undefined);
1942
1971
  }
1943
- finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect ? { effects: { fileMutation: fileMutationEffect } } : {}) } as RunPiStreamingResult & { structuredOutput?: unknown; agentContract?: import("../../shared/types.ts").AgentContract; effects?: import("../../shared/types.ts").EffectsProjection };
1972
+ const requiredOutput = step.outputMode === "file-only" && step.outputPath
1973
+ ? { kind: "file-only" as const, path: step.outputPath, missing: !fs.existsSync(step.outputPath) }
1974
+ : effectiveStructuredOutput
1975
+ ? { kind: "structured" as const, path: effectiveStructuredOutput.outputPath, missing: !fs.existsSync(effectiveStructuredOutput.outputPath) }
1976
+ : undefined;
1977
+ const settlementDiagnostic: SettlementDiagnostic | undefined = effectiveExitCode !== 0 || completionGuardTriggered || completionGuardBlocked
1978
+ ? {
1979
+ finalTextPresent: Boolean(stripAcceptanceReport(run.finalOutput).trim()),
1980
+ mutation: {
1981
+ expected: mutationExpected,
1982
+ attempted: completionGuard?.attemptedMutation === true || mutationAttemptObserved,
1983
+ observed: mutationEvidence.attemptedMutation,
1984
+ },
1985
+ ...(requiredOutput ? { requiredOutput } : {}),
1986
+ afterCompactionSettlement: run.afterCompactionSettlement === true,
1987
+ }
1988
+ : undefined;
1989
+ finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, runtimeAcknowledgedExtensions, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect || settlementDiagnostic ? { effects: { ...(fileMutationEffect ? { fileMutation: fileMutationEffect } : {}), ...(settlementDiagnostic ? { settlementDiagnostic } : {}) } } : {}) } as RunPiStreamingResult;
1944
1990
  if (run.turnBudgetExceeded) break modelAttemptsLoop;
1945
1991
  if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break modelAttemptsLoop;
1946
1992
  if (attempt.success || completionGuardTriggered) break modelAttemptsLoop;
@@ -2105,13 +2151,12 @@ async function runSingleStepInner(
2105
2151
  : finalResult?.error ?? (intercomDetachReceipt ? INTERCOM_DETACH_RECEIPT : undefined);
2106
2152
  const effectiveFinalError = formatChildFailureDiagnostic({
2107
2153
  error: baseFinalError,
2108
- afterCompactionSettlement: finalResult?.afterCompactionSettlement,
2109
- missingFileOnlyOutput: effectiveFinalExitCode !== 0
2110
- && finalResult?.afterCompactionSettlement
2111
- && step.outputMode === "file-only"
2112
- && step.outputPath
2113
- && !fs.existsSync(step.outputPath)
2114
- ? step.outputPath
2154
+ afterCompactionSettlement: effectiveFinalExitCode !== 0 ? finalResult?.afterCompactionSettlement : undefined,
2155
+ missingFileOnlyOutput: effectiveFinalExitCode !== 0 && finalResult?.effects?.settlementDiagnostic?.requiredOutput?.kind === "file-only" && finalResult.effects.settlementDiagnostic.requiredOutput.missing
2156
+ ? finalResult.effects.settlementDiagnostic.requiredOutput.path
2157
+ : undefined,
2158
+ missingStructuredOutput: effectiveFinalExitCode !== 0 && finalResult?.effects?.settlementDiagnostic?.requiredOutput?.kind === "structured" && finalResult.effects.settlementDiagnostic.requiredOutput.missing
2159
+ ? finalResult.effects.settlementDiagnostic.requiredOutput.path
2115
2160
  : undefined,
2116
2161
  });
2117
2162
 
@@ -2514,6 +2559,7 @@ async function runSubagent(
2514
2559
  ...(transcriptPath ? { transcriptPath } : {}),
2515
2560
  skills: task.skills,
2516
2561
  model: task.model,
2562
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
2517
2563
  thinking: task.thinking,
2518
2564
  attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
2519
2565
  recentTools: [],
@@ -2531,6 +2577,7 @@ async function runSubagent(
2531
2577
  label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
2532
2578
  outputName: step.collect.as,
2533
2579
  structured: Boolean(step.collect.outputSchema),
2580
+ ...(step.parallel.contextLimit !== undefined ? { contextLimit: step.parallel.contextLimit } : {}),
2534
2581
  ...(step.agentContract ? { agentContract: step.agentContract } : {}),
2535
2582
  ...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
2536
2583
  ...(step.thinkingCeiling ? { thinkingCeiling: step.thinkingCeiling } : {}),
@@ -2563,6 +2610,7 @@ async function runSubagent(
2563
2610
  ...(transcriptPath ? { transcriptPath } : {}),
2564
2611
  skills: step.skills,
2565
2612
  model: step.model,
2613
+ ...(step.contextLimit !== undefined ? { contextLimit: step.contextLimit } : {}),
2566
2614
  thinking: step.thinking,
2567
2615
  attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
2568
2616
  recentTools: [],
@@ -3424,11 +3472,12 @@ async function runSubagent(
3424
3472
  }
3425
3473
  pendingStepSteers.push(...remaining);
3426
3474
  };
3427
- const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, now = Date.now()): void => {
3475
+ const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, contextLimit?: number, now = Date.now()): void => {
3428
3476
  const step = statusPayload.steps[flatIndex];
3429
3477
  if (!step) return;
3430
3478
  setOptionalProperty(step, "model", model);
3431
3479
  setOptionalProperty(step, "thinking", thinking);
3480
+ setOptionalProperty(step, "contextLimit", contextLimit);
3432
3481
  statusPayload.lastUpdate = now;
3433
3482
  writeStatusPayload();
3434
3483
  };
@@ -3514,7 +3563,7 @@ async function runSubagent(
3514
3563
  return;
3515
3564
  }
3516
3565
  if (event.type === "tool_execution_start" && event.toolName) {
3517
- const mutates = isMutatingTool(event.toolName, event.args);
3566
+ const mutates = isMutatingTool(event.toolName, event.args, flatSteps[flatIndex]?.mutationTools);
3518
3567
  const currentPath = resolveCurrentPath(event.toolName, event.args);
3519
3568
  const argsPreview = extractToolArgsPreview(event.args ?? {});
3520
3569
  const blocksSupervisor = isBlockingSupervisorTool(event.toolName, event.args);
@@ -4059,6 +4108,7 @@ async function runSubagent(
4059
4108
  ...(transcriptPath ? { transcriptPath } : {}),
4060
4109
  ...(task.skills ? { skills: task.skills } : {}),
4061
4110
  ...(task.model ? { model: task.model } : {}),
4111
+ ...(task.contextLimit !== undefined ? { contextLimit: task.contextLimit } : {}),
4062
4112
  ...(task.thinking ? { thinking: task.thinking } : {}),
4063
4113
  ...(task.thinkingCeiling ? { thinkingCeiling: task.thinkingCeiling } : {}),
4064
4114
  ...(task.modelCandidates && task.modelCandidates.length > 0 ? { attemptedModels: task.modelCandidates } : task.model ? { attemptedModels: [task.model] } : {}),
@@ -4188,7 +4238,7 @@ async function runSubagent(
4188
4238
  stopMessage,
4189
4239
  turnBudget: config.turnBudget,
4190
4240
  toolTimeoutMs: task.toolTimeoutMs ?? config.toolTimeoutMs,
4191
- onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
4241
+ onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
4192
4242
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
4193
4243
  onWriterProcess,
4194
4244
  onExternalProcess: (process) => updateExternalProcess(fi, process),
@@ -4584,7 +4634,7 @@ async function runSubagent(
4584
4634
  stopMessage,
4585
4635
  turnBudget: config.turnBudget,
4586
4636
  toolTimeoutMs: taskForRun.toolTimeoutMs ?? config.toolTimeoutMs,
4587
- onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
4637
+ onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking, attempt.contextLimit),
4588
4638
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
4589
4639
  onWriterProcess,
4590
4640
  onExternalProcess: (process) => updateExternalProcess(fi, process),
@@ -4939,7 +4989,7 @@ async function runSubagent(
4939
4989
  stopMessage,
4940
4990
  turnBudget: config.turnBudget,
4941
4991
  toolTimeoutMs: seqStep.toolTimeoutMs ?? config.toolTimeoutMs,
4942
- onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
4992
+ onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking, attempt.contextLimit),
4943
4993
  onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
4944
4994
  onWriterProcess,
4945
4995
  onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
@@ -58,6 +58,7 @@ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs,
58
58
  import { evaluateCompletionMutationGuard, validateImplementationToolContract } from "../shared/completion-guard.ts";
59
59
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
60
60
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
61
+ import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
61
62
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
62
63
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
63
64
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
@@ -346,6 +347,7 @@ async function runSingleAttempt(
346
347
  thinking: effectiveThinking,
347
348
  systemPromptMode: agent.systemPromptMode,
348
349
  inheritProjectContext: agent.inheritProjectContext,
350
+ inheritGlobalContext: agent.inheritGlobalContext,
349
351
  inheritSkills: agent.inheritSkills,
350
352
  requireReadTool: Boolean(shared.resolvedSkillNames?.length),
351
353
  tools: agent.tools,
@@ -381,6 +383,7 @@ async function runSingleAttempt(
381
383
  capabilityCeiling: options.capabilityCeiling,
382
384
  thinkingCeiling: options.thinkingCeiling,
383
385
  extensionBindings: options.extensionBindings,
386
+ runtimeSnapshotHost: options.runtimeSnapshotHost,
384
387
  });
385
388
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
386
389
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -403,6 +406,7 @@ async function runSingleAttempt(
403
406
  inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
404
407
  agentName: agent.name,
405
408
  permissionRules,
409
+ runtimeSnapshotHost: options.runtimeSnapshotHost,
406
410
  });
407
411
  const contractTools = toolPlan.explicitToolAllowlist ? toolPlan.effectiveToolAllowlist : undefined;
408
412
  const contractError = validateImplementationToolContract({
@@ -446,6 +450,7 @@ async function runSingleAttempt(
446
450
  systemPrompt: effectiveSystemPrompt,
447
451
  systemPromptMode: agent.systemPromptMode,
448
452
  inheritProjectContext: agent.inheritProjectContext,
453
+ inheritGlobalContext: agent.inheritGlobalContext,
449
454
  inheritSkills: agent.inheritSkills,
450
455
  skills: shared.resolvedSkillNames ?? [],
451
456
  tools: toolPlan.effectiveToolAllowlist,
@@ -1064,7 +1069,7 @@ async function runSingleAttempt(
1064
1069
  if (options.toolBudget) {
1065
1070
  result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);
1066
1071
  }
1067
- const mutates = isMutatingTool(evt.toolName, toolArgs);
1072
+ const mutates = isMutatingTool(evt.toolName, toolArgs, agent.mutationTools);
1068
1073
  observedMutationAttempt = observedMutationAttempt || mutates;
1069
1074
  pendingToolResult = { tool: evt.toolName ?? "tool", path: activeTool?.path, mutates, startedAt: now };
1070
1075
  fireUpdate();
@@ -1583,6 +1588,7 @@ async function runSingleAttempt(
1583
1588
  messages: result.messages ?? [],
1584
1589
  tools: contractTools,
1585
1590
  mcpDirectTools: toolPlan.effectiveMcpTools,
1591
+ mutationTools: agent.mutationTools,
1586
1592
  toolAvailabilityError,
1587
1593
  mutationEvidence,
1588
1594
  })
@@ -1688,6 +1694,19 @@ async function runSyncCompletionInner(
1688
1694
  task: string,
1689
1695
  options: RunSyncOptions,
1690
1696
  ): Promise<SingleResult> {
1697
+ const effectiveCwd = options.cwd ?? runtimeCwd;
1698
+ const cwdError = preflightLaunchCwd(options.requestedCwd ?? effectiveCwd, effectiveCwd);
1699
+ if (cwdError) {
1700
+ return redactResultPrompt(withRunContext({
1701
+ index: options.index ?? 0,
1702
+ agent: agentName,
1703
+ task,
1704
+ exitCode: 1,
1705
+ messages: [],
1706
+ usage: emptyUsage(),
1707
+ error: cwdError,
1708
+ }, options.context));
1709
+ }
1691
1710
  options = {
1692
1711
  ...options,
1693
1712
  capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])),