@bastani/atomic 0.9.13-alpha.2 → 0.9.13-alpha.3

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 (255) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/builtin/i-have-adhd/package.json +1 -1
  4. package/dist/builtin/intercom/CHANGELOG.md +14 -0
  5. package/dist/builtin/intercom/README.md +12 -12
  6. package/dist/builtin/intercom/incoming-message-delivery.ts +1 -1
  7. package/dist/builtin/intercom/index.ts +1 -1
  8. package/dist/builtin/intercom/intercom-tool.ts +1 -1
  9. package/dist/builtin/intercom/intercom-utils.ts +3 -6
  10. package/dist/builtin/intercom/package.json +1 -1
  11. package/dist/builtin/intercom/result-renderers.ts +1 -1
  12. package/dist/builtin/intercom/session-target.ts +1 -15
  13. package/dist/builtin/intercom/skills/intercom/SKILL.md +6 -6
  14. package/dist/builtin/intercom/terminal-ordering-barrier.ts +2 -2
  15. package/dist/builtin/intercom/ui/inline-message.ts +7 -5
  16. package/dist/builtin/intercom/ui/session-list.ts +23 -10
  17. package/dist/builtin/mcp/package.json +1 -1
  18. package/dist/builtin/subagents/CHANGELOG.md +19 -0
  19. package/dist/builtin/subagents/README.md +29 -73
  20. package/dist/builtin/subagents/package.json +2 -2
  21. package/dist/builtin/subagents/prompts/parallel-cleanup.md +1 -1
  22. package/dist/builtin/subagents/prompts/parallel-review.md +1 -1
  23. package/dist/builtin/subagents/prompts/review-loop.md +2 -2
  24. package/dist/builtin/subagents/skills/subagent/SKILL.md +23 -59
  25. package/dist/builtin/subagents/skills/tmux/SKILL.md +1 -1
  26. package/dist/builtin/subagents/src/extension/control-notices.ts +2 -3
  27. package/dist/builtin/subagents/src/extension/doctor.ts +2 -14
  28. package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -17
  29. package/dist/builtin/subagents/src/extension/index.ts +9 -70
  30. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  31. package/dist/builtin/subagents/src/extension/prompt-guidance.ts +2 -2
  32. package/dist/builtin/subagents/src/extension/schemas.ts +2 -8
  33. package/dist/builtin/subagents/src/extension/tool-description.ts +2 -3
  34. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  35. package/dist/builtin/subagents/src/intercom/result-intercom.ts +5 -46
  36. package/dist/builtin/subagents/src/runs/{background → foreground}/completion-notification.ts +2 -2
  37. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +25 -88
  38. package/dist/builtin/subagents/src/runs/{background → foreground}/notify.ts +5 -5
  39. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +11 -28
  40. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +3 -0
  41. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +2 -0
  42. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-runtime.ts +0 -5
  43. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +30 -20
  44. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +4 -4
  45. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +0 -13
  46. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +104 -48
  47. package/dist/builtin/subagents/src/runs/inprocess/index.ts +1 -1
  48. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +5 -5
  49. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-api.ts +0 -7
  50. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-core.ts +0 -10
  51. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-projection.ts +3 -108
  52. package/dist/builtin/subagents/src/runs/inprocess/runtime-support/nested-sanitize.ts +0 -4
  53. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +1 -1
  54. package/dist/builtin/subagents/src/shared/formatters.ts +3 -5
  55. package/dist/builtin/subagents/src/shared/model-resolution.ts +78 -0
  56. package/dist/builtin/subagents/src/shared/status-format.ts +4 -11
  57. package/dist/builtin/subagents/src/shared/types-config.ts +10 -9
  58. package/dist/builtin/subagents/src/shared/types-nested.ts +127 -0
  59. package/dist/builtin/subagents/src/shared/types-results.ts +2 -8
  60. package/dist/builtin/subagents/src/shared/types-runtime.ts +0 -9
  61. package/dist/builtin/subagents/src/shared/types.ts +1 -1
  62. package/dist/builtin/subagents/src/shared/utils.ts +1 -109
  63. package/dist/builtin/subagents/src/slash/slash-commands.ts +7 -17
  64. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -252
  65. package/dist/builtin/subagents/src/tui/render-layout.ts +4 -4
  66. package/dist/builtin/subagents/src/tui/render-result.ts +0 -9
  67. package/dist/builtin/subagents/src/tui/render-stable-output.ts +1 -34
  68. package/dist/builtin/subagents/src/tui/render.ts +0 -2
  69. package/dist/builtin/web-access/package.json +1 -1
  70. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  71. package/dist/builtin/workflows/README.md +3 -3
  72. package/dist/builtin/workflows/package.json +1 -1
  73. package/dist/builtin/workflows/src/durable/backend.ts +11 -0
  74. package/dist/builtin/workflows/src/durable/completed-catalog-stage-groups.ts +63 -25
  75. package/dist/builtin/workflows/src/durable/completed-catalog.ts +166 -25
  76. package/dist/builtin/workflows/src/durable/dbos-backend.ts +42 -24
  77. package/dist/builtin/workflows/src/durable/dbos-envelope.ts +2 -1
  78. package/dist/builtin/workflows/src/durable/run-timing.ts +28 -7
  79. package/dist/builtin/workflows/src/durable/scoped-backend.ts +4 -0
  80. package/dist/builtin/workflows/src/durable/targeted-inspection.ts +110 -0
  81. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +2 -2
  82. package/dist/builtin/workflows/src/engine/runtime.ts +19 -2
  83. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
  84. package/dist/builtin/workflows/src/extension/index.bundle.mjs +680 -152
  85. package/dist/builtin/workflows/src/extension/runtime-durable-resume.ts +6 -0
  86. package/dist/builtin/workflows/src/extension/ui-surface.ts +2 -0
  87. package/dist/builtin/workflows/src/extension/workflow-command-registration.ts +6 -1
  88. package/dist/builtin/workflows/src/extension/workflow-targets.ts +18 -10
  89. package/dist/builtin/workflows/src/extension/workflow-tool-inspection.ts +31 -16
  90. package/dist/builtin/workflows/src/extension/workflow-tool-send.ts +75 -3
  91. package/dist/builtin/workflows/src/extension/workflow-tool.ts +53 -6
  92. package/dist/builtin/workflows/src/runs/background/run-inspect.ts +5 -1
  93. package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +94 -60
  94. package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +1 -1
  95. package/dist/builtin/workflows/src/runs/foreground/executor-stage-control.ts +7 -7
  96. package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +25 -4
  97. package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +4 -1
  98. package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +2 -0
  99. package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +142 -4
  100. package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +2 -0
  101. package/dist/builtin/workflows/src/runs/foreground/stage-session-heartbeat.ts +88 -0
  102. package/dist/builtin/workflows/src/tui/run-detail.ts +25 -12
  103. package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +1 -0
  104. package/dist/core/agent-session-events.d.ts.map +1 -1
  105. package/dist/core/agent-session-events.js +16 -5
  106. package/dist/core/agent-session-events.js.map +1 -1
  107. package/dist/core/agent-session-export.d.ts.map +1 -1
  108. package/dist/core/agent-session-export.js +2 -1
  109. package/dist/core/agent-session-export.js.map +1 -1
  110. package/dist/core/agent-session-methods.d.ts +8 -4
  111. package/dist/core/agent-session-methods.d.ts.map +1 -1
  112. package/dist/core/agent-session-methods.js.map +1 -1
  113. package/dist/core/agent-session-prompt.d.ts.map +1 -1
  114. package/dist/core/agent-session-prompt.js +5 -0
  115. package/dist/core/agent-session-prompt.js.map +1 -1
  116. package/dist/core/agent-session-summary.d.ts +21 -0
  117. package/dist/core/agent-session-summary.d.ts.map +1 -0
  118. package/dist/core/agent-session-summary.js +156 -0
  119. package/dist/core/agent-session-summary.js.map +1 -0
  120. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  121. package/dist/core/agent-session-tool-registry.js +0 -4
  122. package/dist/core/agent-session-tool-registry.js.map +1 -1
  123. package/dist/core/agent-session-transfer.d.ts.map +1 -1
  124. package/dist/core/agent-session-transfer.js +0 -2
  125. package/dist/core/agent-session-transfer.js.map +1 -1
  126. package/dist/core/agent-session-tree.d.ts.map +1 -1
  127. package/dist/core/agent-session-tree.js +6 -0
  128. package/dist/core/agent-session-tree.js.map +1 -1
  129. package/dist/core/agent-session.d.ts +7 -3
  130. package/dist/core/agent-session.d.ts.map +1 -1
  131. package/dist/core/agent-session.js +8 -5
  132. package/dist/core/agent-session.js.map +1 -1
  133. package/dist/core/atomic-guide-command.js +2 -2
  134. package/dist/core/atomic-guide-command.js.map +1 -1
  135. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  136. package/dist/core/compaction/branch-summarization.js +1 -0
  137. package/dist/core/compaction/branch-summarization.js.map +1 -1
  138. package/dist/core/compaction/index.d.ts +1 -0
  139. package/dist/core/compaction/index.d.ts.map +1 -1
  140. package/dist/core/compaction/index.js +1 -0
  141. package/dist/core/compaction/index.js.map +1 -1
  142. package/dist/core/compaction/session-summarization.d.ts +41 -0
  143. package/dist/core/compaction/session-summarization.d.ts.map +1 -0
  144. package/dist/core/compaction/session-summarization.js +97 -0
  145. package/dist/core/compaction/session-summarization.js.map +1 -0
  146. package/dist/core/extensions/ui-types.d.ts +2 -0
  147. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  148. package/dist/core/extensions/ui-types.js.map +1 -1
  149. package/dist/core/session-manager-core.d.ts +3 -1
  150. package/dist/core/session-manager-core.d.ts.map +1 -1
  151. package/dist/core/session-manager-core.js +9 -1
  152. package/dist/core/session-manager-core.js.map +1 -1
  153. package/dist/core/session-manager-entries.d.ts +24 -1
  154. package/dist/core/session-manager-entries.d.ts.map +1 -1
  155. package/dist/core/session-manager-entries.js +48 -0
  156. package/dist/core/session-manager-entries.js.map +1 -1
  157. package/dist/core/session-manager-list.d.ts.map +1 -1
  158. package/dist/core/session-manager-list.js +10 -0
  159. package/dist/core/session-manager-list.js.map +1 -1
  160. package/dist/core/session-manager-types.d.ts +10 -1
  161. package/dist/core/session-manager-types.d.ts.map +1 -1
  162. package/dist/core/session-manager-types.js.map +1 -1
  163. package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
  164. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  165. package/dist/core/settings-manager-basic-accessors.js +5 -0
  166. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  167. package/dist/core/settings-manager.d.ts +1 -1
  168. package/dist/core/settings-manager.d.ts.map +1 -1
  169. package/dist/core/settings-manager.js.map +1 -1
  170. package/dist/core/settings-types.d.ts +4 -0
  171. package/dist/core/settings-types.d.ts.map +1 -1
  172. package/dist/core/settings-types.js.map +1 -1
  173. package/dist/core/slash-commands.js +2 -2
  174. package/dist/core/slash-commands.js.map +1 -1
  175. package/dist/core/tools/bash.d.ts +0 -15
  176. package/dist/core/tools/bash.d.ts.map +1 -1
  177. package/dist/core/tools/bash.js +3 -81
  178. package/dist/core/tools/bash.js.map +1 -1
  179. package/dist/core/tools/fetch-url.d.ts +12 -0
  180. package/dist/core/tools/fetch-url.d.ts.map +1 -1
  181. package/dist/core/tools/fetch-url.js +76 -10
  182. package/dist/core/tools/fetch-url.js.map +1 -1
  183. package/dist/core/tools/index.js +4 -4
  184. package/dist/core/tools/index.js.map +1 -1
  185. package/dist/core/tools/session-temp-dir.d.ts +2 -2
  186. package/dist/core/tools/session-temp-dir.d.ts.map +1 -1
  187. package/dist/core/tools/session-temp-dir.js +6 -7
  188. package/dist/core/tools/session-temp-dir.js.map +1 -1
  189. package/dist/core/usage-totals.js +1 -1
  190. package/dist/core/usage-totals.js.map +1 -1
  191. package/dist/modes/interactive/components/footer.js +1 -1
  192. package/dist/modes/interactive/components/footer.js.map +1 -1
  193. package/dist/modes/interactive/components/host-session-picker.d.ts.map +1 -1
  194. package/dist/modes/interactive/components/host-session-picker.js +1 -0
  195. package/dist/modes/interactive/components/host-session-picker.js.map +1 -1
  196. package/dist/modes/interactive/components/session-selector-list.d.ts.map +1 -1
  197. package/dist/modes/interactive/components/session-selector-list.js +29 -4
  198. package/dist/modes/interactive/components/session-selector-list.js.map +1 -1
  199. package/dist/modes/interactive/components/session-selector-search.js +1 -1
  200. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  201. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/tree-selector-content.js +6 -0
  203. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  204. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  205. package/dist/modes/interactive-engine/protocol.js +4 -1
  206. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  207. package/docs/environment-variables.md +1 -1
  208. package/docs/intercom.md +20 -21
  209. package/docs/quickstart.md +5 -1
  210. package/docs/sdk.md +1 -1
  211. package/docs/session-format.md +15 -0
  212. package/docs/sessions.md +8 -0
  213. package/docs/settings.md +6 -0
  214. package/docs/subagents.md +19 -31
  215. package/docs/tools.md +3 -5
  216. package/docs/usage.md +1 -1
  217. package/docs/windows.md +1 -1
  218. package/docs/workflows.md +60 -32
  219. package/npm-shrinkwrap.json +29 -29
  220. package/package.json +2 -2
  221. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +0 -331
  222. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-async.ts +0 -161
  223. package/dist/builtin/subagents/src/runs/inprocess/background-single.ts +0 -195
  224. package/dist/builtin/subagents/src/runs/inprocess/background.ts +0 -90
  225. package/dist/builtin/subagents/src/shared/types-async.ts +0 -247
  226. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +0 -217
  227. package/dist/builtin/subagents/src/tui/render-widget.ts +0 -366
  228. package/dist/core/async/format.d.ts +0 -3
  229. package/dist/core/async/format.d.ts.map +0 -1
  230. package/dist/core/async/format.js +0 -52
  231. package/dist/core/async/format.js.map +0 -1
  232. package/dist/core/async/job-manager.d.ts +0 -40
  233. package/dist/core/async/job-manager.d.ts.map +0 -1
  234. package/dist/core/async/job-manager.js +0 -293
  235. package/dist/core/async/job-manager.js.map +0 -1
  236. package/dist/core/async/session-manager.d.ts +0 -16
  237. package/dist/core/async/session-manager.d.ts.map +0 -1
  238. package/dist/core/async/session-manager.js +0 -34
  239. package/dist/core/async/session-manager.js.map +0 -1
  240. package/dist/core/async/types.d.ts +0 -23
  241. package/dist/core/async/types.d.ts.map +0 -1
  242. package/dist/core/async/types.js +0 -2
  243. package/dist/core/async/types.js.map +0 -1
  244. package/dist/core/tools/bash-async-execution.d.ts +0 -27
  245. package/dist/core/tools/bash-async-execution.d.ts.map +0 -1
  246. package/dist/core/tools/bash-async-execution.js +0 -81
  247. package/dist/core/tools/bash-async-execution.js.map +0 -1
  248. package/dist/core/tools/bash-async-jobs.d.ts +0 -30
  249. package/dist/core/tools/bash-async-jobs.d.ts.map +0 -1
  250. package/dist/core/tools/bash-async-jobs.js +0 -87
  251. package/dist/core/tools/bash-async-jobs.js.map +0 -1
  252. package/dist/core/tools/bash-async-output.d.ts +0 -13
  253. package/dist/core/tools/bash-async-output.d.ts.map +0 -1
  254. package/dist/core/tools/bash-async-output.js +0 -122
  255. package/dist/core/tools/bash-async-output.js.map +0 -1
@@ -1,23 +1,32 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
1
3
  import type { ExtensionContext } from "@bastani/atomic";
2
4
  import { handleManagementAction } from "../../agents/agent-management.ts";
3
5
  import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
4
6
  import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
5
7
  import { buildDoctorReport } from "../../extension/doctor.ts";
6
8
  import {
9
+ INTERCOM_BRIDGE_MARKER,
7
10
  resolveIntercomBridge,
8
11
  resolveIntercomSessionTarget,
9
12
  resolveSubagentIntercomTarget,
10
13
  } from "../../intercom/intercom-bridge.ts";
11
14
  import { requestSupervisorAuthorization } from "../../intercom/supervisor-authorization.ts";
12
15
  import { getArtifactsDir } from "../../shared/artifacts.ts";
13
- import { toModelInfo } from "../../shared/model-info.ts";
14
- import { resolveSingleProgress } from "../../shared/settings.ts";
16
+ import { collectKnownModelProviders, toModelInfo } from "../../shared/model-info.ts";
17
+ import { createCandidateModelResolver } from "../../shared/model-resolution.ts";
18
+ import {
19
+ injectSingleProgressInstruction,
20
+ resolveSingleProgress,
21
+ writeInitialProgressFile,
22
+ } from "../../shared/settings.ts";
15
23
  import {
16
24
  DEFAULT_ARTIFACT_CONFIG,
17
25
  getCurrentSubagentDepth,
18
26
  isWorkflowStageOrchestrationContext,
19
27
  resolveChildMaxSubagentDepth,
20
28
  resolveWorkflowStageMaxSubagentDepth,
29
+ type SingleResult,
21
30
  SUBAGENT_ACTIONS,
22
31
  type SubagentToolResult,
23
32
  workflowSessionMetadataFromContext,
@@ -30,16 +39,17 @@ import {
30
39
  import { inheritedIntercomGroup } from "../shared/intercom-group.js";
31
40
  import { currentModelFullId } from "../shared/model-fallback.ts";
32
41
  import { resolveControlConfig } from "../shared/subagent-control.ts";
33
- import { runAsyncPath } from "./subagent-executor-async.ts";
34
42
  import { checkDepthForExecution, prepareExecutionContext } from "./subagent-executor-context.ts";
35
43
  import { toExecutionErrorResult, withForkContext } from "./subagent-executor-input.ts";
36
44
  import { runParallelPath } from "./subagent-executor-parallel.ts";
37
45
  import { resolveRequestedCwd } from "./subagent-executor-resume.ts";
38
46
  import { resolveSubagentExecutorRuntimeDeps } from "./subagent-executor-runtime.ts";
39
- import { runSinglePath } from "./subagent-executor-single.ts";
47
+ import { createForwardSingleUpdate, runSinglePath } from "./subagent-executor-single.ts";
40
48
  import {
41
49
  foregroundStatusResult,
42
50
  getForegroundControl,
51
+ notifyDetachedForegroundChildExit,
52
+ replaceForegroundRunChild,
43
53
  retainedForegroundStatusResult,
44
54
  } from "./subagent-executor-status.ts";
45
55
  import {
@@ -54,6 +64,7 @@ async function resumeRetainedForegroundChild(
54
64
  message: string,
55
65
  ctx: ExtensionContext,
56
66
  deps: ResolvedExecutorDeps,
67
+ onUpdate: ((r: SubagentToolResult) => void) | undefined,
57
68
  ): Promise<SubagentToolResult | undefined> {
58
69
  const requested = params.id ?? params.runId;
59
70
  if (!requested) return undefined;
@@ -82,47 +93,93 @@ async function resumeRetainedForegroundChild(
82
93
  ? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
83
94
  : undefined;
84
95
  const supervisorAuthorization = await requestSupervisorAuthorization(deps.pi.events, supervisedTarget);
85
- return deps.runtime.executeAsyncSingle(run.runId, {
86
- agent: child.agent,
87
- agentConfig,
88
- task: message,
89
- ctx: {
90
- pi: deps.pi,
96
+ const artifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: params.artifacts !== false };
97
+ const artifactsDir = getArtifactsDir(parentSessionFile);
98
+ const progressDir = resolveSingleProgress(agentConfig, params.progress, message)
99
+ ? path.join(artifactsDir, "progress", run.runId)
100
+ : undefined;
101
+ if (progressDir) {
102
+ writeInitialProgressFile(progressDir);
103
+ message = injectSingleProgressInstruction(message, progressDir);
104
+ }
105
+ const cleanupProgress = (): void => {
106
+ if (!progressDir || artifactConfig.enabled) return;
107
+ try {
108
+ fs.rmSync(progressDir, { recursive: true, force: true });
109
+ } catch {
110
+ // Scratch cleanup must never replace the child run's result or original error.
111
+ }
112
+ };
113
+ const forwardSingleUpdate = createForwardSingleUpdate(
114
+ onUpdate,
115
+ getForegroundControl(deps.state, run.runId),
116
+ child.agent,
117
+ child.index,
118
+ );
119
+ let result: SingleResult;
120
+ try {
121
+ result = await deps.runtime.runSync(run.cwd, agents, child.agent, message, {
91
122
  cwd: run.cwd,
92
- currentSessionId: ctx.sessionManager.getSessionId(),
93
- currentModelProvider: ctx.model?.provider,
94
- currentModel: currentModelFullId(ctx.model),
95
- intercomGroup: inheritedIntercomGroup(ctx),
123
+ signal: ctx.signal,
124
+ interruptSignal: ctx.signal,
125
+ allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
126
+ intercomEvents: deps.pi.events,
127
+ runId: run.runId,
128
+ index: child.index,
129
+ sessionDir: child.sessionFile ? path.dirname(child.sessionFile) : undefined,
130
+ sessionFile: child.sessionFile,
131
+ share: params.share === true,
132
+ artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
133
+ artifactConfig,
134
+ maxOutput: params.maxOutput,
135
+ maxSubagentDepth:
136
+ child.maxSubagentDepth ??
137
+ resolveChildMaxSubagentDepth(
138
+ resolveWorkflowStageMaxSubagentDepth(ctx, deps.config.maxSubagentDepth),
139
+ agentConfig.maxSubagentDepth,
140
+ ),
141
+ parentDepth: getCurrentSubagentDepth(ctx),
142
+ workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(ctx),
96
143
  workflowSessionMetadata: workflowSessionMetadataFromContext(ctx),
97
- },
98
- cwd: run.cwd,
99
- maxOutput: params.maxOutput,
100
- artifactsDir: getArtifactsDir(parentSessionFile),
101
- artifactConfig: { ...DEFAULT_ARTIFACT_CONFIG, enabled: params.artifacts !== false },
102
- shareEnabled: params.share === true,
103
- sessionRoot: deps.getSubagentSessionRoot(parentSessionFile),
104
- sessionFile: child.sessionFile,
105
- progress: resolveSingleProgress(agentConfig, params.progress, message),
106
- modelOverride: params.model,
107
- availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
108
- // The child's own effective limit, recorded when the run was retained. An
109
- // older record without one falls back to narrowing the current limit by the
110
- // agent definition, so a resume never widens a child's delegation budget.
111
- maxSubagentDepth:
112
- child.maxSubagentDepth ??
113
- resolveChildMaxSubagentDepth(
114
- resolveWorkflowStageMaxSubagentDepth(ctx, deps.config.maxSubagentDepth),
115
- agentConfig.maxSubagentDepth,
116
- ),
117
- parentDepth: getCurrentSubagentDepth(ctx),
118
- workflowStageSubagentGuard: isWorkflowStageOrchestrationContext(ctx),
119
- controlConfig: resolveControlConfig(deps.config.control, params.control),
120
- controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
121
- childIntercomTarget: intercomBridge.active
122
- ? (agent, index) => resolveSubagentIntercomTarget(run.runId, agent, index)
123
- : undefined,
124
- supervisorAuthorization,
125
- });
144
+ controlConfig: resolveControlConfig(deps.config.control, params.control),
145
+ intercomSessionName: intercomBridge.active
146
+ ? resolveSubagentIntercomTarget(run.runId, child.agent, child.index)
147
+ : undefined,
148
+ orchestratorIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
149
+ intercomGroup: inheritedIntercomGroup(ctx),
150
+ supervisorAuthorization,
151
+ modelOverride: params.model,
152
+ availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
153
+ knownModelProviders: collectKnownModelProviders(ctx.modelRegistry),
154
+ resolveCandidateModel: createCandidateModelResolver(ctx.modelRegistry, ctx.model?.provider),
155
+ preferredModelProvider: ctx.model?.provider,
156
+ currentModel: currentModelFullId(ctx.model),
157
+ onUpdate: forwardSingleUpdate,
158
+ onDetachedExit: (detachedResult) => {
159
+ cleanupProgress();
160
+ if (detachedResult) {
161
+ replaceForegroundRunChild(deps.state, run.runId, child.index, detachedResult);
162
+ notifyDetachedForegroundChildExit({
163
+ pi: deps.pi,
164
+ runId: run.runId,
165
+ mode: "single",
166
+ index: child.index,
167
+ result: detachedResult,
168
+ });
169
+ }
170
+ },
171
+ });
172
+ } catch (error) {
173
+ cleanupProgress();
174
+ throw error;
175
+ }
176
+ if (!result.detached) cleanupProgress();
177
+ replaceForegroundRunChild(deps.state, run.runId, child.index, result, { onlyWhenDetached: false });
178
+ return {
179
+ content: [{ type: "text", text: result.finalOutput ?? result.envelope ?? result.error ?? "" }],
180
+ details: { mode: "single", runId: run.runId, results: [result] },
181
+ ...(result.status === "error" ? { isError: true } : {}),
182
+ };
126
183
  }
127
184
  const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
128
185
  /**
@@ -141,8 +198,9 @@ async function handleManagementRequest(input: {
141
198
  requestCwd: string;
142
199
  ctx: ExtensionContext;
143
200
  deps: ResolvedExecutorDeps;
201
+ onUpdate?: (r: SubagentToolResult) => void;
144
202
  }): Promise<SubagentToolResult> {
145
- const { params, paramsWithResolvedCwd, requestCwd, ctx, deps } = input;
203
+ const { params, paramsWithResolvedCwd, requestCwd, ctx, deps, onUpdate } = input;
146
204
  const action = params.action;
147
205
  if (!action) {
148
206
  return {
@@ -243,7 +301,7 @@ async function handleManagementRequest(input: {
243
301
  }
244
302
  const inProcess = await resumeInProcessChild(targetRunId, message, { model: ctx.model });
245
303
  if (inProcess) return inProcess;
246
- const retained = await resumeRetainedForegroundChild(paramsWithResolvedCwd, message, ctx, deps);
304
+ const retained = await resumeRetainedForegroundChild(paramsWithResolvedCwd, message, ctx, deps, onUpdate);
247
305
  if (retained) return retained;
248
306
  return {
249
307
  content: [{ type: "text", text: `No in-process child found for '${targetRunId}'.` }],
@@ -315,7 +373,7 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
315
373
  const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd);
316
374
  const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd };
317
375
  if (params.action) {
318
- return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps });
376
+ return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps, onUpdate });
319
377
  }
320
378
  // Fanout authorization gates delegation and every management action that can
321
379
  // start or continue agent execution. Only `list`, `get`, `status`, and
@@ -337,8 +395,6 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
337
395
  const prepared = built.prepared!;
338
396
  let nestedForegroundStarted = false;
339
397
  try {
340
- const asyncResult = await runAsyncPath(prepared.execData, deps);
341
- if (asyncResult) return withForkContext(asyncResult, prepared.effectiveParams.context);
342
398
  if (prepared.foregroundControl) {
343
399
  prepared.writeNestedForegroundEvent("subagent.nested.started");
344
400
  nestedForegroundStarted = true;
@@ -40,7 +40,7 @@ export type {
40
40
  export {
41
41
  AdmittedChild,
42
42
  admit_child_session,
43
- continue_in_background,
43
+ continue_detached,
44
44
  createSubagentControl,
45
45
  deliver_child_result,
46
46
  reload_cold_child,
@@ -45,7 +45,7 @@ import { type InProcessNestedResumeOutcome, registerInProcessNestedAttempt } fro
45
45
  import { createInProcessChildPromptBehavior } from "./prompt-behavior.ts";
46
46
 
47
47
  export type ChildStatus = NativeAgentStatus;
48
- export type ContinuationReason = "async-requested" | "intercom-coordination";
48
+ export type ContinuationReason = "intercom-coordination";
49
49
  export type TerminationCauseName = NativeTerminationCause;
50
50
  export type TerminalStatus = "ok" | "error" | "skipped" | "interrupted" | "continued";
51
51
 
@@ -1105,8 +1105,8 @@ export class SubagentControlRuntime {
1105
1105
  return running;
1106
1106
  }
1107
1107
 
1108
- continueInBackground(running: RunningAttempt, _reason: ContinuationReason): string {
1109
- if (running.status !== "running") throw new Error("continue_in_background requires a running attempt");
1108
+ continueDetached(running: RunningAttempt, _reason: ContinuationReason): string {
1109
+ if (running.status !== "running") throw new Error("continue_detached requires a running attempt");
1110
1110
  this.native.publishChildStatus(running.child.identity.path, nativeStatus("continued"));
1111
1111
  running.status = "continued";
1112
1112
  running.promise.catch(() => undefined);
@@ -1363,12 +1363,12 @@ export function run_child_attempt(
1363
1363
  );
1364
1364
  }
1365
1365
 
1366
- export function continue_in_background(
1366
+ export function continue_detached(
1367
1367
  control: SubagentControlRuntime,
1368
1368
  running: RunningAttempt,
1369
1369
  reason: ContinuationReason,
1370
1370
  ): string {
1371
- return control.continueInBackground(running, reason);
1371
+ return control.continueDetached(running, reason);
1372
1372
  }
1373
1373
 
1374
1374
  export function reload_cold_child(control: SubagentControlRuntime, pathValue: string, message: string): AdmittedResult {
@@ -25,9 +25,7 @@ export {
25
25
  MAX_NESTED_STEPS,
26
26
  MAX_PROCESSED_NESTED_EVENTS,
27
27
  NESTED_EVENTS_DIR,
28
- NESTED_RUNS_DIR,
29
28
  resolveInheritedNestedRouteFromEnv,
30
- resolveNestedAsyncDir,
31
29
  resolveNestedParentAddressFromEnv,
32
30
  resolveNestedRouteFromEnv,
33
31
  validateNestedRouteShape,
@@ -41,11 +39,6 @@ export {
41
39
  export {
42
40
  attachRootChildrenToSteps,
43
41
  hasLiveNestedDescendants,
44
- isTopLevelAsyncDir,
45
- nestedArtifactEnv,
46
- nestedResultsPath,
47
- nestedSummaryFromAsyncStatus,
48
- updateAsyncJobNestedProjection,
49
42
  updateForegroundNestedProjection,
50
43
  } from "./nested-projection.ts";
51
44
  export type { NestedRunMatch, NestedRunResolutionScope } from "./nested-registry.ts";
@@ -7,7 +7,6 @@ import { registerInProcessNestedRoute } from "../nested-routing.ts";
7
7
  import { isSafeNestedPathId, type NestedPathEntry } from "./nested-paths.ts";
8
8
 
9
9
  export const NESTED_EVENTS_DIR = path.join(TEMP_ROOT_DIR, "nested-subagent-events");
10
- export const NESTED_RUNS_DIR = path.join(TEMP_ROOT_DIR, "nested-subagent-runs");
11
10
  export const ROUTE_FILE = "route.json";
12
11
  export const REGISTRY_FILE = "registry.json";
13
12
  export const MAX_NESTED_EVENT_BYTES = 64 * 1024;
@@ -163,7 +162,6 @@ function cleanupOldSubdirectories(root: string, maxAgeDays: number): void {
163
162
 
164
163
  export function cleanupOldNestedRuntimeDirs(maxAgeDays: number): void {
165
164
  cleanupOldSubdirectories(NESTED_EVENTS_DIR, maxAgeDays);
166
- cleanupOldSubdirectories(NESTED_RUNS_DIR, maxAgeDays);
167
165
  }
168
166
 
169
167
  export function resolveNestedRouteFromEnv(_env: NodeJS.ProcessEnv = process.env): NestedRoute | undefined {
@@ -180,14 +178,6 @@ export function resolveNestedParentAddressFromEnv(
180
178
  return undefined;
181
179
  }
182
180
 
183
- export function resolveNestedAsyncDir(rootRunId: string, run: NestedRunSummary): string | undefined {
184
- if (!run.asyncDir) return undefined;
185
- const resolved = path.resolve(run.asyncDir);
186
- const nestedRoot = path.resolve(NESTED_RUNS_DIR, rootRunId, run.id);
187
- const relative = path.relative(nestedRoot, resolved);
188
- return resolved === nestedRoot || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved : undefined;
189
- }
190
-
191
181
  export function clampNumber(value: unknown): number | undefined {
192
182
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
193
183
  }
@@ -1,15 +1,5 @@
1
- import * as path from "node:path";
2
- import { APP_NAME } from "@bastani/atomic";
3
- import {
4
- ASYNC_DIR,
5
- type AsyncJobState,
6
- type AsyncStatus,
7
- type NestedRunSummary,
8
- RESULTS_DIR,
9
- type SubagentRunMode,
10
- type SubagentState,
11
- } from "../../../shared/types.ts";
12
- import { assertSafeId, containedPath, MAX_NESTED_CHILDREN, MAX_NESTED_STEPS, NESTED_RUNS_DIR } from "./nested-core.ts";
1
+ import type { NestedRunSummary, SubagentState } from "../../../shared/types.ts";
2
+ import { MAX_NESTED_CHILDREN } from "./nested-core.ts";
13
3
  import { projectNestedEvents } from "./nested-registry.ts";
14
4
  import { terminal } from "./nested-sanitize.ts";
15
5
 
@@ -19,9 +9,7 @@ export function attachRootChildrenToSteps<T extends { children?: NestedRunSummar
19
9
  children: NestedRunSummary[] | undefined,
20
10
  ): void {
21
11
  if (!steps?.length) return;
22
- for (const step of steps) {
23
- step.children = undefined;
24
- }
12
+ for (const step of steps) step.children = undefined;
25
13
  if (!children?.length) return;
26
14
  for (const child of children) {
27
15
  if (child.parentRunId !== rootRunId || child.parentStepIndex === undefined) continue;
@@ -35,13 +23,6 @@ export function attachRootChildrenToSteps<T extends { children?: NestedRunSummar
35
23
  }
36
24
  }
37
25
 
38
- export function updateAsyncJobNestedProjection(job: AsyncJobState): void {
39
- if (!job.nestedRoute) return;
40
- const registry = projectNestedEvents(job.nestedRoute);
41
- job.nestedChildren = registry.children;
42
- attachRootChildrenToSteps(job.asyncId, job.steps, registry.children);
43
- }
44
-
45
26
  export function updateForegroundNestedProjection(
46
27
  control: SubagentState["foregroundControls"] extends Map<string, infer T> ? T : never,
47
28
  ): void {
@@ -59,89 +40,3 @@ export function hasLiveNestedDescendants(children: NestedRunSummary[] | undefine
59
40
  }
60
41
  return false;
61
42
  }
62
-
63
- export function nestedSummaryFromAsyncStatus(
64
- status: AsyncStatus,
65
- asyncDir: string,
66
- fallback: {
67
- id: string;
68
- parentRunId: string;
69
- parentStepIndex?: number;
70
- depth: number;
71
- path?: Array<{ runId: string; stepIndex?: number; agent?: string }>;
72
- mode?: SubagentRunMode;
73
- ts: number;
74
- },
75
- ): NestedRunSummary {
76
- return {
77
- id: status.runId || fallback.id,
78
- parentRunId: fallback.parentRunId,
79
- ...(fallback.parentStepIndex !== undefined ? { parentStepIndex: fallback.parentStepIndex } : {}),
80
- depth: fallback.depth,
81
- path: fallback.path ?? [
82
- {
83
- runId: fallback.parentRunId,
84
- ...(fallback.parentStepIndex !== undefined ? { stepIndex: fallback.parentStepIndex } : {}),
85
- },
86
- ],
87
- asyncDir,
88
- ...(status.pid ? { pid: status.pid } : {}),
89
- ...(status.sessionId ? { sessionId: status.sessionId } : {}),
90
- mode: status.mode ?? fallback.mode,
91
- state: status.state,
92
- ...(status.activityState ? { activityState: status.activityState } : {}),
93
- ...(status.lastActivityAt !== undefined ? { lastActivityAt: status.lastActivityAt } : {}),
94
- ...(status.currentTool ? { currentTool: status.currentTool } : {}),
95
- ...(status.currentToolStartedAt !== undefined ? { currentToolStartedAt: status.currentToolStartedAt } : {}),
96
- ...(status.currentPath ? { currentPath: status.currentPath } : {}),
97
- ...(status.turnCount !== undefined ? { turnCount: status.turnCount } : {}),
98
- ...(status.toolCount !== undefined ? { toolCount: status.toolCount } : {}),
99
- ...(status.totalTokens ? { totalTokens: status.totalTokens } : {}),
100
- ...(status.startedAt !== undefined ? { startedAt: status.startedAt } : { startedAt: fallback.ts }),
101
- ...(status.endedAt !== undefined ? { endedAt: status.endedAt } : {}),
102
- lastUpdate: status.lastUpdate ?? fallback.ts,
103
- ...(status.sessionFile ? { sessionFile: status.sessionFile } : {}),
104
- ...(status.steps?.length
105
- ? {
106
- steps: status.steps
107
- .map((step) => ({
108
- agent: step.agent,
109
- status: step.status,
110
- ...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
111
- ...(step.activityState ? { activityState: step.activityState } : {}),
112
- ...(step.lastActivityAt !== undefined ? { lastActivityAt: step.lastActivityAt } : {}),
113
- ...(step.currentTool ? { currentTool: step.currentTool } : {}),
114
- ...(step.currentToolStartedAt !== undefined
115
- ? { currentToolStartedAt: step.currentToolStartedAt }
116
- : {}),
117
- ...(step.currentPath ? { currentPath: step.currentPath } : {}),
118
- ...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
119
- ...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
120
- ...(step.startedAt !== undefined ? { startedAt: step.startedAt } : {}),
121
- ...(step.endedAt !== undefined ? { endedAt: step.endedAt } : {}),
122
- ...(step.error ? { error: step.error } : {}),
123
- }))
124
- .slice(0, MAX_NESTED_STEPS),
125
- }
126
- : {}),
127
- };
128
- }
129
-
130
- export function nestedArtifactEnv(rootRunId: string, parentRunId: string): Record<string, string> {
131
- const envPrefix = APP_NAME.toUpperCase();
132
- return {
133
- [`${envPrefix}_SUBAGENT_NESTED_ROOT_RUN_ID`]: rootRunId,
134
- [`${envPrefix}_SUBAGENT_NESTED_PARENT_RUN_ID`]: parentRunId,
135
- };
136
- }
137
-
138
- export function isTopLevelAsyncDir(asyncDir: string): boolean {
139
- const resolved = path.resolve(asyncDir);
140
- return containedPath(ASYNC_DIR, resolved) && !containedPath(NESTED_RUNS_DIR, resolved);
141
- }
142
-
143
- export function nestedResultsPath(rootRunId: string, id: string): string {
144
- assertSafeId("rootRunId", rootRunId);
145
- assertSafeId("id", id);
146
- return path.join(RESULTS_DIR, "nested", rootRunId, `${id}.json`);
147
- }
@@ -91,10 +91,6 @@ export function sanitizeSummary(input: unknown, depth = 0): NestedRunSummary | u
91
91
  depth: Math.min(Math.max(0, clampNumber(raw.depth) ?? 0), MAX_NESTED_DEPTH),
92
92
  path: pathParts,
93
93
  state: sanitizeState(raw.state, "running"),
94
- ...(stringValue(raw.asyncDir, 2048) ? { asyncDir: stringValue(raw.asyncDir, 2048) } : {}),
95
- ...(clampNumber(raw.pid) !== undefined && clampNumber(raw.pid)! > 0 && Number.isInteger(clampNumber(raw.pid))
96
- ? { pid: clampNumber(raw.pid) }
97
- : {}),
98
94
  ...(stringValue(raw.sessionId, 256) ? { sessionId: stringValue(raw.sessionId, 256) } : {}),
99
95
  ...(stringValue(raw.sessionFile, 2048) ? { sessionFile: stringValue(raw.sessionFile, 2048) } : {}),
100
96
  ...(stringValue(raw.intercomTarget, 256) ? { intercomTarget: stringValue(raw.intercomTarget, 256) } : {}),
@@ -8,7 +8,7 @@ import type {
8
8
  } from "../../shared/types.ts";
9
9
 
10
10
  const CONTROL_EVENT_TYPES: ControlEventType[] = ["active_long_running", "needs_attention"];
11
- const CONTROL_NOTIFICATION_CHANNELS: ControlNotificationChannel[] = ["event", "async", "intercom"];
11
+ const CONTROL_NOTIFICATION_CHANNELS: ControlNotificationChannel[] = ["event", "intercom"];
12
12
  const DEFAULT_NOTIFY_ON: ControlEventType[] = ["active_long_running", "needs_attention"];
13
13
 
14
14
  export const DEFAULT_CONTROL_CONFIG: ResolvedControlConfig = {
@@ -14,13 +14,11 @@ export function formatTokens(n: number): string {
14
14
 
15
15
  export function formatModelThinking(model?: string, thinking?: string, fastMode?: boolean): string {
16
16
  const parsed = model ? splitKnownThinkingSuffix(model) : undefined;
17
- let displayModel = parsed?.baseModel ?? model;
17
+ // Keep the full `provider/model` id so subagent lines read exactly like the
18
+ // main chat's model display; only a known thinking suffix is split off.
19
+ const displayModel = parsed?.baseModel ?? model;
18
20
  const explicitThinking = THINKING_LEVELS.find((level) => level === thinking?.trim());
19
21
  const displayThinking = parsed?.thinkingSuffix ? parsed.thinkingSuffix.slice(1) : explicitThinking;
20
- if (displayModel) {
21
- const slashIdx = displayModel.lastIndexOf("/");
22
- if (slashIdx !== -1) displayModel = displayModel.slice(slashIdx + 1);
23
- }
24
22
  const parts = [displayModel, displayThinking ? `thinking ${displayThinking}` : undefined].filter(Boolean);
25
23
  if (fastMode && parts.length > 0) parts.push("fast");
26
24
  return parts.join(" · ");
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Resolution of a selected model candidate id to a concrete registry model.
3
+ *
4
+ * `buildModelCandidates` yields `provider/model[:thinking]` strings. Those
5
+ * strings alone never reach the child session: `createAgentSession` takes a
6
+ * `Model<Api>` object, and when no model is supplied it restores the model
7
+ * persisted in the session file. For a fork-context child that file is a copy
8
+ * of the parent's, so the parent's model silently won. Resolving the selected
9
+ * candidate to a real model here is what lets the configured model be handed
10
+ * to the child session instead.
11
+ *
12
+ * The lookup semantics deliberately mirror the SDK's own fallback-entry
13
+ * resolution (`resolveFallbackModel`), so the primary candidate and the
14
+ * fallback candidates handed to the same session agree on what a candidate
15
+ * string means.
16
+ */
17
+
18
+ import type { Api, Model } from "@earendil-works/pi-ai/compat";
19
+ import { splitKnownThinkingSuffix, THINKING_LEVELS, type ThinkingLevel } from "./model-info.ts";
20
+
21
+ /** Registry surface needed to resolve a candidate; satisfied by `ctx.modelRegistry`. */
22
+ export interface CandidateModelLookup {
23
+ getAvailable(): Model<Api>[];
24
+ find(provider: string, modelId: string): Model<Api> | undefined;
25
+ hasConfiguredAuth(model: Model<Api>): boolean;
26
+ }
27
+
28
+ /** A candidate string resolved to a concrete model and its requested thinking level. */
29
+ export interface ResolvedCandidateModel {
30
+ model: Model<Api>;
31
+ thinkingLevel?: ThinkingLevel;
32
+ }
33
+
34
+ /**
35
+ * Resolve one `provider/model[:thinking]` candidate id against the registry.
36
+ * Returns `undefined` when the candidate names no usable model, which leaves
37
+ * the caller on its previous behavior rather than failing the run.
38
+ */
39
+ export type CandidateModelResolver = (candidateId: string) => ResolvedCandidateModel | undefined;
40
+
41
+ function thinkingLevelFromSuffix(suffix: string): ThinkingLevel | undefined {
42
+ if (!suffix) return undefined;
43
+ const level = suffix.slice(1);
44
+ return THINKING_LEVELS.find((known) => known === level);
45
+ }
46
+
47
+ export function resolveCandidateModel(
48
+ candidateId: string,
49
+ lookup: CandidateModelLookup,
50
+ preferredProvider?: string,
51
+ ): ResolvedCandidateModel | undefined {
52
+ const trimmed = candidateId.trim();
53
+ if (!trimmed) return undefined;
54
+ const { baseModel, thinkingSuffix } = splitKnownThinkingSuffix(trimmed);
55
+ const thinkingLevel = thinkingLevelFromSuffix(thinkingSuffix);
56
+ const slash = baseModel.indexOf("/");
57
+ if (slash < 0) {
58
+ const available = lookup.getAvailable().filter((entry) => entry.id === baseModel);
59
+ const model =
60
+ available.find((entry) => entry.provider === preferredProvider) ??
61
+ (available.length === 1 ? available[0] : undefined);
62
+ if (!model) return undefined;
63
+ return thinkingLevel === undefined ? { model } : { model, thinkingLevel };
64
+ }
65
+ const provider = baseModel.slice(0, slash);
66
+ const modelId = baseModel.slice(slash + 1);
67
+ const model = lookup.find(provider, modelId);
68
+ if (!model || !lookup.hasConfiguredAuth(model)) return undefined;
69
+ return thinkingLevel === undefined ? { model } : { model, thinkingLevel };
70
+ }
71
+
72
+ /** Bind a registry and the parent's provider into a reusable candidate resolver. */
73
+ export function createCandidateModelResolver(
74
+ lookup: CandidateModelLookup,
75
+ preferredProvider?: string,
76
+ ): CandidateModelResolver {
77
+ return (candidateId: string) => resolveCandidateModel(candidateId, lookup, preferredProvider);
78
+ }
@@ -1,6 +1,7 @@
1
- import type { ActivityState, AsyncJobStep } from "./types.ts";
1
+ import type { ActivityState } from "./types.ts";
2
2
 
3
- type StepStatusLike = Pick<AsyncJobStep, "status">;
3
+ type StepStatus = "pending" | "running" | "complete" | "completed" | "failed" | "paused";
4
+ type StepStatusLike = { status: StepStatus };
4
5
 
5
6
  function formatActivityAge(ms: number): string {
6
7
  if (ms < 1000) return "now";
@@ -24,18 +25,10 @@ export function formatActivityLabel(
24
25
  return age === "now" ? "active now" : `active ${age} ago`;
25
26
  }
26
27
 
27
- function isCompletedStepStatus(status: AsyncJobStep["status"]): boolean {
28
+ function isCompletedStepStatus(status: StepStatus): boolean {
28
29
  return status === "complete" || status === "completed";
29
30
  }
30
31
 
31
- export function aggregateStepStatus(steps: StepStatusLike[]): AsyncJobStep["status"] {
32
- if (steps.some((step) => step.status === "running")) return "running";
33
- if (steps.some((step) => step.status === "failed")) return "failed";
34
- if (steps.some((step) => step.status === "paused")) return "paused";
35
- if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status))) return "complete";
36
- return "pending";
37
- }
38
-
39
32
  export function formatAgentRunningLabel(count: number): string {
40
33
  return count === 1 ? "1 agent running" : `${count} agents running`;
41
34
  }