pi-subagents 0.49.0 → 0.51.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 (117) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/gpt-pro.md +17 -0
  3. package/agents/oracle.md +7 -5
  4. package/agents/researcher.md +1 -1
  5. package/agents/reviewer.md +2 -2
  6. package/agents/scout.md +1 -1
  7. package/agents/worker.md +1 -1
  8. package/async-retention-discovery-worker.mjs +180 -0
  9. package/docs/agents.md +37 -2
  10. package/docs/configuration.md +76 -14
  11. package/docs/extension-api.md +78 -1
  12. package/docs/missions.md +1 -1
  13. package/docs/observability.md +20 -4
  14. package/docs/tool-reference.md +55 -39
  15. package/docs/workflows.md +171 -5
  16. package/package.json +4 -2
  17. package/skills/pi-subagents/SKILL.md +5 -4
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +9 -6
  19. package/skills/pi-subagents/references/execution-controls.md +22 -18
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  21. package/skills/pi-subagents/references/prompting-and-roles.md +33 -17
  22. package/src/agents/agent-management.ts +100 -345
  23. package/src/agents/agent-serializer.ts +2 -0
  24. package/src/agents/agents.ts +135 -25
  25. package/src/api/external-job-provider.ts +185 -0
  26. package/src/api/external-runs.ts +174 -84
  27. package/src/api/preflight.ts +42 -11
  28. package/src/api/shared-types.ts +2 -0
  29. package/src/extension/config.ts +36 -3
  30. package/src/extension/doctor.ts +3 -6
  31. package/src/extension/fanout-child.ts +2 -2
  32. package/src/extension/index.ts +210 -90
  33. package/src/extension/public-execution.ts +31 -2
  34. package/src/extension/rpc.ts +5 -1
  35. package/src/extension/schemas.ts +14 -36
  36. package/src/extension/tool-description.ts +37 -24
  37. package/src/inspectors/herdr/actions.ts +5 -9
  38. package/src/inspectors/herdr/inspector-runner.ts +2 -1
  39. package/src/inspectors/herdr/project-panes.ts +4 -8
  40. package/src/inspectors/herdr/shell-command.ts +16 -0
  41. package/src/intercom/intercom-bridge.ts +2 -3
  42. package/src/intercom/native-supervisor-channel.ts +49 -51
  43. package/src/missions/goal-driver.ts +3 -1
  44. package/src/missions/lifecycle.ts +6 -1
  45. package/src/missions/store.ts +4 -9
  46. package/src/profiles/profiles.ts +3 -1
  47. package/src/runs/background/active-run-index.ts +94 -1
  48. package/src/runs/background/async-execution.ts +98 -38
  49. package/src/runs/background/async-job-tracker.ts +21 -4
  50. package/src/runs/background/async-resume.ts +30 -17
  51. package/src/runs/background/async-retention.ts +888 -0
  52. package/src/runs/background/async-status-snapshot.ts +277 -0
  53. package/src/runs/background/async-status.ts +47 -56
  54. package/src/runs/background/chain-append.ts +3 -33
  55. package/src/runs/background/chain-root-attachment.ts +2 -2
  56. package/src/runs/background/completion-replay.ts +11 -1
  57. package/src/runs/background/control-channel.ts +14 -68
  58. package/src/runs/background/fleet-view.ts +3 -1
  59. package/src/runs/background/index-segment.ts +59 -0
  60. package/src/runs/background/notify.ts +3 -1
  61. package/src/runs/background/result-files.ts +505 -0
  62. package/src/runs/background/result-watcher.ts +250 -51
  63. package/src/runs/background/retained-children.ts +79 -20
  64. package/src/runs/background/run-id-query.ts +7 -0
  65. package/src/runs/background/run-id-resolver.ts +37 -29
  66. package/src/runs/background/run-status.ts +34 -20
  67. package/src/runs/background/scheduled-runs.ts +71 -27
  68. package/src/runs/background/stale-run-reconciler.ts +33 -16
  69. package/src/runs/background/steering.ts +11 -1
  70. package/src/runs/background/subagent-runner.ts +535 -161
  71. package/src/runs/background/subagent-wait.ts +9 -7
  72. package/src/runs/background/terminal-run-index.ts +129 -0
  73. package/src/runs/background/wait-completions.ts +22 -2
  74. package/src/runs/background/wait-subscriptions.ts +80 -1
  75. package/src/runs/foreground/async-dismiss-action.ts +2 -1
  76. package/src/runs/foreground/async-steering-action.ts +21 -14
  77. package/src/runs/foreground/execution.ts +221 -15
  78. package/src/runs/foreground/subagent-executor.ts +529 -1519
  79. package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
  80. package/src/runs/shared/chain-outputs.ts +1 -3
  81. package/src/runs/shared/completion-guard.ts +96 -6
  82. package/src/runs/shared/external-cli-runner.ts +4 -0
  83. package/src/runs/shared/external-job-bridge.ts +450 -0
  84. package/src/runs/shared/external-job-runner.ts +286 -0
  85. package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
  86. package/src/runs/shared/model-fallback.ts +34 -3
  87. package/src/runs/shared/nested-events.ts +66 -62
  88. package/src/runs/shared/orca-progress-tabs.ts +437 -0
  89. package/src/runs/shared/parallel-handoff.ts +46 -4
  90. package/src/runs/shared/parallel-utils.ts +6 -15
  91. package/src/runs/shared/permissions.ts +5 -1
  92. package/src/runs/shared/pi-args.ts +8 -1
  93. package/src/runs/shared/subagent-control.ts +41 -4
  94. package/src/runs/shared/subagent-prompt-runtime.ts +13 -15
  95. package/src/runs/shared/subagent-startup-retry.ts +12 -0
  96. package/src/runs/shared/tool-timeout.ts +93 -0
  97. package/src/runs/shared/workflow-graph.ts +1 -23
  98. package/src/runs/shared/worktree.ts +12 -1
  99. package/src/shared/atomic-json.ts +22 -2
  100. package/src/shared/capacity-resilient-json.ts +102 -0
  101. package/src/shared/completion-owner.ts +14 -0
  102. package/src/shared/file-system-retry.ts +49 -1
  103. package/src/shared/fork-context.ts +42 -0
  104. package/src/shared/prompt-resources.ts +0 -40
  105. package/src/shared/settings.ts +3 -27
  106. package/src/shared/types.ts +88 -27
  107. package/src/shared/utils.ts +8 -0
  108. package/src/shared/watch-strategy.ts +10 -0
  109. package/src/slash/slash-commands.ts +45 -28
  110. package/src/slash/slash-live-state.ts +3 -0
  111. package/src/tui/fleet-status.ts +160 -45
  112. package/src/tui/fleet.ts +186 -28
  113. package/src/tui/render.ts +41 -10
  114. package/src/workflows/chat-progress.ts +7 -5
  115. package/src/workflows/scripted-workflow.ts +424 -125
  116. package/src/runs/foreground/chain-clarify.ts +0 -1354
  117. package/src/runs/foreground/chain-execution.ts +0 -1565
@@ -38,6 +38,7 @@ import {
38
38
  buildControlEvent,
39
39
  claimControlNotification,
40
40
  deriveActivityState,
41
+ shouldEmitOpenToolAttention,
41
42
  shouldNotifyControlEvent,
42
43
  } from "../shared/subagent-control.ts";
43
44
  import {
@@ -53,10 +54,12 @@ import {
53
54
  } from "../../shared/utils.ts";
54
55
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
55
56
  import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
57
+ import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
56
58
  import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
57
59
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
58
60
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
59
61
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
62
+ import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
60
63
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
61
64
  import { resolvePermissionRules } from "../shared/permissions.ts";
62
65
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
@@ -74,6 +77,7 @@ import {
74
77
  } from "../shared/model-fallback.ts";
75
78
  import {
76
79
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
80
+ formatSubagentExtensionConflictError,
77
81
  formatSubagentStartupRetryExhaustedError,
78
82
  formatSubagentStartupRetryNote,
79
83
  isRetryableSubagentStartupFailure,
@@ -302,6 +306,7 @@ async function runSingleAttempt(
302
306
  outputSnapshot?: SingleOutputSnapshot;
303
307
  originalTask?: string;
304
308
  taskDelivery?: SubagentTaskDelivery;
309
+ orcaProgressTab?: OrcaProgressTab;
305
310
  },
306
311
  ): Promise<SingleResult> {
307
312
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
@@ -670,6 +675,7 @@ async function runSingleAttempt(
670
675
  clearWatchdogTailTimer();
671
676
  clearStdioGuard();
672
677
  clearTimeoutTimers();
678
+ clearAllToolTimeouts();
673
679
  clearTurnBudgetTimers();
674
680
  if (protocolHardKillTimer) {
675
681
  clearTimeout(protocolHardKillTimer);
@@ -694,6 +700,64 @@ async function runSingleAttempt(
694
700
 
695
701
  let activeLongRunningNotified = false;
696
702
  let pendingToolResult: { tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined;
703
+ type ActiveToolCall = { key: string; tool: string; args: string; startedAt: number; path?: string };
704
+ let activeToolSequence = 0;
705
+ const activeToolCalls = new Map<string, ActiveToolCall>();
706
+ const activeToolKeysByName = new Map<string, string[]>();
707
+ const latestActiveToolCall = (): ActiveToolCall | undefined => [...activeToolCalls.values()].sort((left, right) => right.startedAt - left.startedAt)[0];
708
+ const refreshCurrentTool = (): void => {
709
+ const active = latestActiveToolCall();
710
+ if (!active) {
711
+ progress.currentTool = undefined;
712
+ progress.currentToolArgs = undefined;
713
+ progress.currentToolStartedAt = undefined;
714
+ progress.currentPath = undefined;
715
+ return;
716
+ }
717
+ progress.currentTool = active.tool;
718
+ progress.currentToolArgs = active.args;
719
+ progress.currentToolStartedAt = active.startedAt;
720
+ progress.currentPath = active.path;
721
+ };
722
+ const recordActiveToolCall = (event: { toolCallId?: unknown; toolName: string }, args: Record<string, unknown>, now: number): ActiveToolCall => {
723
+ const key = toolTimeoutCallKey(event, ++activeToolSequence);
724
+ const path = resolveCurrentPath(event.toolName, args);
725
+ const active: ActiveToolCall = {
726
+ key,
727
+ tool: event.toolName,
728
+ args: extractToolArgsPreview(args),
729
+ startedAt: now,
730
+ ...(path !== undefined ? { path } : {}),
731
+ };
732
+ activeToolCalls.set(key, active);
733
+ const keys = activeToolKeysByName.get(active.tool) ?? [];
734
+ keys.push(key);
735
+ activeToolKeysByName.set(active.tool, keys);
736
+ refreshCurrentTool();
737
+ return active;
738
+ };
739
+ const removeActiveToolCallKey = (key: string): ActiveToolCall | undefined => {
740
+ const active = activeToolCalls.get(key);
741
+ if (!active) return undefined;
742
+ activeToolCalls.delete(key);
743
+ const keys = activeToolKeysByName.get(active.tool)?.filter((candidate) => candidate !== key) ?? [];
744
+ if (keys.length > 0) activeToolKeysByName.set(active.tool, keys);
745
+ else activeToolKeysByName.delete(active.tool);
746
+ return active;
747
+ };
748
+ const removeActiveToolCall = (event: { toolCallId?: unknown; toolName?: unknown }): ActiveToolCall | undefined => {
749
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
750
+ ? `id:${event.toolCallId}`
751
+ : typeof event.toolName === "string"
752
+ ? activeToolKeysByName.get(event.toolName)?.[0]
753
+ : activeToolCalls.size === 1
754
+ ? [...activeToolCalls.keys()][0]
755
+ : undefined;
756
+ return key ? removeActiveToolCallKey(key) : undefined;
757
+ };
758
+ const openToolAttentionTarget = (now: number): ActiveToolCall | undefined => [...activeToolCalls.values()]
759
+ .filter((active) => shouldEmitOpenToolAttention({ config: controlConfig, currentTool: active.tool, currentToolStartedAt: active.startedAt, now }))
760
+ .sort((left, right) => left.startedAt - right.startedAt)[0];
697
761
  const mutatingFailures = createMutatingFailureState();
698
762
  const mutatingFailureWindowMs = 5 * 60_000;
699
763
  const currentToolDurationMs = (now: number) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined;
@@ -719,6 +783,7 @@ async function runSingleAttempt(
719
783
  currentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now),
720
784
  currentPath: input.currentPath ?? progress.currentPath,
721
785
  recentFailureSummary: input.recentFailureSummary,
786
+ taskPreview: task,
722
787
  });
723
788
  emitControlEvent(event);
724
789
  return previous !== "needs_attention";
@@ -745,6 +810,7 @@ async function runSingleAttempt(
745
810
  currentToolDurationMs: currentToolDurationMs(now),
746
811
  currentPath: progress.currentPath,
747
812
  elapsedMs: now - startTime,
813
+ taskPreview: task,
748
814
  }));
749
815
  return true;
750
816
  };
@@ -811,6 +877,17 @@ async function runSingleAttempt(
811
877
  if (idleState === "needs_attention") {
812
878
  return progress.activityState === "needs_attention" ? false : emitNeedsAttention(now);
813
879
  }
880
+ const toolAttentionTarget = progress.activityState !== "needs_attention" ? openToolAttentionTarget(now) : undefined;
881
+ if (toolAttentionTarget) {
882
+ const durationMs = Math.max(0, now - toolAttentionTarget.startedAt);
883
+ return emitNeedsAttention(now, {
884
+ message: `${agent.name} has had tool '${toolAttentionTarget.tool}' open for ${Math.floor(durationMs / 1000)}s`,
885
+ reason: "tool_open_threshold",
886
+ currentTool: toolAttentionTarget.tool,
887
+ currentPath: toolAttentionTarget.path,
888
+ currentToolDurationMs: durationMs,
889
+ });
890
+ }
814
891
  const activeReason = nextLongRunningTrigger(controlConfig, {
815
892
  startedAt: startTime,
816
893
  now,
@@ -848,16 +925,18 @@ async function runSingleAttempt(
848
925
  const processLine = (line: string) => {
849
926
  if (!line.trim()) return;
850
927
  jsonlWriter.writeLine(line);
851
- let evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
928
+ let evt: { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
852
929
  try {
853
- evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
930
+ evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; toolCallId?: string; args?: unknown; willRetry?: unknown };
854
931
  } catch {
855
932
  rawStdoutTail.push(`${line}\n`);
856
933
  shared.transcriptWriter?.writeStdoutLine(line);
934
+ shared.orcaProgressTab?.append(`${line}\n`);
857
935
  // Non-JSON stdout lines are expected; only structured events are parsed.
858
936
  return;
859
937
  }
860
938
  shared.transcriptWriter?.writeChildEvent(evt);
939
+ shared.orcaProgressTab?.event(evt);
861
940
  if (evt.type === "agent_settled") agentSettledReceived = true;
862
941
  applyChildLifecycle(projectChildLifecycle(evt));
863
942
 
@@ -892,6 +971,8 @@ async function runSingleAttempt(
892
971
  const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
893
972
  ? evt.args as Record<string, unknown>
894
973
  : {};
974
+ const activeTool = evt.toolName !== undefined ? recordActiveToolCall(evt as { toolCallId?: unknown; toolName: string }, toolArgs, now) : undefined;
975
+ if (evt.toolName !== undefined) armToolTimeout(evt as { toolCallId?: unknown; toolName: string });
895
976
  if (options.structuredOutput && evt.toolName === "structured_output") {
896
977
  structuredOutputToolInvoked = true;
897
978
  structuredOutputMessageStartIndex = result.messages?.length ?? 0;
@@ -903,28 +984,23 @@ async function runSingleAttempt(
903
984
  if (options.toolBudget) {
904
985
  result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);
905
986
  }
906
- progress.currentTool = evt.toolName;
907
- progress.currentToolArgs = extractToolArgsPreview(toolArgs);
908
- progress.currentToolStartedAt = now;
909
- progress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);
910
987
  const mutates = isMutatingTool(evt.toolName, toolArgs);
911
988
  observedMutationAttempt = observedMutationAttempt || mutates;
912
- pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
989
+ pendingToolResult = { tool: evt.toolName ?? "tool", path: activeTool?.path, mutates, startedAt: now };
913
990
  fireUpdate();
914
991
  }
915
992
 
916
993
  if (evt.type === "tool_execution_end") {
917
- if (progress.currentTool) {
994
+ clearActiveToolTimeout(evt);
995
+ const endedTool = removeActiveToolCall(evt);
996
+ if (endedTool) {
918
997
  progress.recentTools.push({
919
- tool: progress.currentTool,
920
- args: progress.currentToolArgs || "",
998
+ tool: endedTool.tool,
999
+ args: endedTool.args,
921
1000
  endMs: now,
922
1001
  });
923
1002
  }
924
- progress.currentTool = undefined;
925
- progress.currentToolArgs = undefined;
926
- progress.currentToolStartedAt = undefined;
927
- progress.currentPath = undefined;
1003
+ refreshCurrentTool();
928
1004
  fireUpdate();
929
1005
  }
930
1006
 
@@ -1022,6 +1098,7 @@ async function runSingleAttempt(
1022
1098
  timeoutTimer = setTimeout(() => {
1023
1099
  if (processClosed || lifecycleFinished || interruptedByControl) return;
1024
1100
  result.timedOut = true;
1101
+ clearAllToolTimeouts();
1025
1102
  result.error = attemptTimeout.message;
1026
1103
  result.finalOutput = attemptTimeout.message;
1027
1104
  progress.status = "failed";
@@ -1043,6 +1120,81 @@ async function runSingleAttempt(
1043
1120
  timeoutTimer.unref?.();
1044
1121
  }
1045
1122
 
1123
+ let toolTimeoutSequence = 0;
1124
+ const activeToolTimeouts = new Map<string, { toolName: string; timer: ReturnType<typeof setTimeout> }>();
1125
+ const activeToolTimeoutKeysByName = new Map<string, string[]>();
1126
+ let toolTimeoutTerminationTimer: ReturnType<typeof setTimeout> | undefined;
1127
+ let toolTimeoutHardKillTimer: ReturnType<typeof setTimeout> | undefined;
1128
+ const removeToolTimeoutKey = (key: string): void => {
1129
+ const active = activeToolTimeouts.get(key);
1130
+ if (!active) return;
1131
+ clearTimeout(active.timer);
1132
+ activeToolTimeouts.delete(key);
1133
+ const keys = activeToolTimeoutKeysByName.get(active.toolName)?.filter((candidate) => candidate !== key) ?? [];
1134
+ if (keys.length > 0) activeToolTimeoutKeysByName.set(active.toolName, keys);
1135
+ else activeToolTimeoutKeysByName.delete(active.toolName);
1136
+ };
1137
+ const clearActiveToolTimeout = (event: { toolCallId?: unknown; toolName?: unknown }): void => {
1138
+ const key = typeof event.toolCallId === "string" && event.toolCallId.length > 0
1139
+ ? `id:${event.toolCallId}`
1140
+ : typeof event.toolName === "string"
1141
+ ? activeToolTimeoutKeysByName.get(event.toolName)?.[0]
1142
+ : activeToolTimeouts.size === 1
1143
+ ? [...activeToolTimeouts.keys()][0]
1144
+ : undefined;
1145
+ if (key) removeToolTimeoutKey(key);
1146
+ };
1147
+ const clearAllToolTimeouts = (): void => {
1148
+ for (const key of [...activeToolTimeouts.keys()]) removeToolTimeoutKey(key);
1149
+ if (toolTimeoutTerminationTimer) {
1150
+ clearTimeout(toolTimeoutTerminationTimer);
1151
+ toolTimeoutTerminationTimer = undefined;
1152
+ }
1153
+ if (toolTimeoutHardKillTimer) {
1154
+ clearTimeout(toolTimeoutHardKillTimer);
1155
+ toolTimeoutHardKillTimer = undefined;
1156
+ }
1157
+ };
1158
+ const terminateForToolTimeout = (message: string): void => {
1159
+ if (processClosed || lifecycleFinished || interruptedByControl) return;
1160
+ result.timedOut = true;
1161
+ result.error = message;
1162
+ result.finalOutput = message;
1163
+ progress.status = "failed";
1164
+ progress.error = message;
1165
+ progress.durationMs = Date.now() - startTime;
1166
+ fireUpdate();
1167
+ trySignalChild(proc, "SIGINT");
1168
+ toolTimeoutTerminationTimer = setTimeout(() => {
1169
+ if (processClosed || lifecycleFinished) return;
1170
+ trySignalChild(proc, "SIGTERM");
1171
+ }, 1000);
1172
+ toolTimeoutTerminationTimer.unref?.();
1173
+ toolTimeoutHardKillTimer = setTimeout(() => {
1174
+ if (processClosed || lifecycleFinished) return;
1175
+ trySignalChild(proc, "SIGKILL");
1176
+ }, 4000);
1177
+ toolTimeoutHardKillTimer.unref?.();
1178
+ };
1179
+ const armToolTimeout = (event: { toolCallId?: unknown; toolName: string }): void => {
1180
+ const timeoutForTool = effectiveToolTimeoutMs(event.toolName, options.toolTimeoutMs);
1181
+ if (timeoutForTool === undefined) return;
1182
+ const elapsed = Date.now() - startTime;
1183
+ const runRemaining = attemptTimeout ? Math.max(0, attemptTimeout.remainingMs - elapsed) : undefined;
1184
+ if (runRemaining !== undefined && timeoutForTool >= runRemaining) return;
1185
+ const key = toolTimeoutCallKey(event, ++toolTimeoutSequence);
1186
+ const toolName = event.toolName;
1187
+ const timer = setTimeout(() => {
1188
+ removeToolTimeoutKey(key);
1189
+ terminateForToolTimeout(formatToolTimeoutMessage(toolName, timeoutForTool));
1190
+ }, timeoutForTool);
1191
+ timer.unref?.();
1192
+ activeToolTimeouts.set(key, { toolName, timer });
1193
+ const keys = activeToolTimeoutKeysByName.get(toolName) ?? [];
1194
+ keys.push(key);
1195
+ activeToolTimeoutKeysByName.set(toolName, keys);
1196
+ };
1197
+
1046
1198
  const stderrTail = createBoundedByteTail();
1047
1199
  const failProtocol = (limit: ProtocolOutputLimit): void => {
1048
1200
  if (result.protocolError) return;
@@ -1077,6 +1229,7 @@ async function runSingleAttempt(
1077
1229
  proc.stderr.on("data", (chunk: Buffer) => {
1078
1230
  stderrTail.push(chunk);
1079
1231
  stderrReader.push(chunk);
1232
+ shared.orcaProgressTab?.append(chunk.toString("utf-8"));
1080
1233
  });
1081
1234
  proc.on("exit", () => {
1082
1235
  childExited = true;
@@ -1156,6 +1309,7 @@ async function runSingleAttempt(
1156
1309
  if (result.timedOut) return;
1157
1310
  interruptedByControl = true;
1158
1311
  clearTimeoutTimers();
1312
+ clearAllToolTimeouts();
1159
1313
  progress.status = "running";
1160
1314
  progress.durationMs = Date.now() - startTime;
1161
1315
  result.interrupted = true;
@@ -1201,6 +1355,10 @@ async function runSingleAttempt(
1201
1355
  };
1202
1356
  return result;
1203
1357
  }
1358
+ result.error = formatSubagentExtensionConflictError(result.error, {
1359
+ agent: agent.name,
1360
+ ambientExtensionsEnabled: !launchResolvedExtensions.disableAmbientExtensions,
1361
+ });
1204
1362
  if (result.error && result.exitCode === 0) {
1205
1363
  result.exitCode = 1;
1206
1364
  }
@@ -1378,7 +1536,7 @@ async function runSingleAttempt(
1378
1536
  /**
1379
1537
  * Run a subagent synchronously (blocking until complete)
1380
1538
  */
1381
- async function runSyncCompletion(
1539
+ async function runSyncCompletionInner(
1382
1540
  runtimeCwd: string,
1383
1541
  agents: AgentConfig[],
1384
1542
  agentName: string,
@@ -1427,6 +1585,24 @@ async function runSyncCompletion(
1427
1585
  error: acceptanceErrors.join(" "),
1428
1586
  }, options.context));
1429
1587
  }
1588
+ const toolTimeout = resolveToolTimeoutMs({
1589
+ callValue: options.toolTimeoutMs,
1590
+ agentValue: agent.defaultToolTimeoutMs,
1591
+ configValue: options.configToolTimeoutMs,
1592
+ envValue: toolTimeoutFromEnv(),
1593
+ });
1594
+ if (toolTimeout.error) {
1595
+ return redactResultPrompt(withRunContext({
1596
+ index: options.index ?? 0,
1597
+ agent: agentName,
1598
+ task,
1599
+ exitCode: 1,
1600
+ messages: [],
1601
+ usage: emptyUsage(),
1602
+ error: toolTimeout.error,
1603
+ }, options.context));
1604
+ }
1605
+ options = { ...options, toolTimeoutMs: toolTimeout.toolTimeoutMs };
1430
1606
  const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
1431
1607
  if (outputModeValidationError) {
1432
1608
  return redactResultPrompt(withRunContext({
@@ -1531,6 +1707,14 @@ async function runSyncCompletion(
1531
1707
  }
1532
1708
  }
1533
1709
 
1710
+ const orcaProgressTab = createOrcaProgressTab({
1711
+ cwd: options.cwd ?? runtimeCwd,
1712
+ runId: options.runId,
1713
+ agent: agentName,
1714
+ index: options.index ?? 0,
1715
+ });
1716
+ if (orcaProgressTab) options.onOrcaProgressTabCreated?.(orcaProgressTab);
1717
+
1534
1718
  const persistResultMetadata = (target: SingleResult): void => {
1535
1719
  persistSingleResultMetadata({
1536
1720
  metadataPath: artifactPathsResult?.metadataPath,
@@ -1585,6 +1769,7 @@ async function runSyncCompletion(
1585
1769
  outputSnapshot,
1586
1770
  originalTask: task,
1587
1771
  taskDelivery: taskDeliveryOverride,
1772
+ orcaProgressTab,
1588
1773
  });
1589
1774
  lastResult = result;
1590
1775
  if (startupAttemptIndex === 0) {
@@ -1813,6 +1998,27 @@ async function runSyncCompletion(
1813
1998
  return result;
1814
1999
  }
1815
2000
 
2001
+ async function runSyncCompletion(
2002
+ runtimeCwd: string,
2003
+ agents: AgentConfig[],
2004
+ agentName: string,
2005
+ task: string,
2006
+ options: RunSyncOptions,
2007
+ ): Promise<SingleResult> {
2008
+ let orcaProgressTab: OrcaProgressTab | undefined;
2009
+ try {
2010
+ const result = await runSyncCompletionInner(runtimeCwd, agents, agentName, task, {
2011
+ ...options,
2012
+ onOrcaProgressTabCreated: (tab) => { orcaProgressTab = tab; },
2013
+ });
2014
+ orcaProgressTab?.finish(result.stopped ? "stopped" : result.exitCode === 0 && !result.error ? "completed" : "failed", result.sessionFile);
2015
+ return result;
2016
+ } catch (error) {
2017
+ orcaProgressTab?.finish("failed");
2018
+ throw error;
2019
+ }
2020
+ }
2021
+
1816
2022
  /**
1817
2023
  * Runs the authoritative completion pipeline independently from the foreground
1818
2024
  * receipt. Detachment is same-runtime only: it does not adopt or daemonize work.