@selesai/code 0.3.0 → 0.3.1

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 (230) hide show
  1. package/dist/core/diagnostics.d.ts +1 -0
  2. package/dist/core/diagnostics.d.ts.map +1 -1
  3. package/dist/core/diagnostics.js.map +1 -1
  4. package/dist/core/package-manager.d.ts +10 -0
  5. package/dist/core/package-manager.d.ts.map +1 -1
  6. package/dist/core/package-manager.js +93 -8
  7. package/dist/core/package-manager.js.map +1 -1
  8. package/dist/core/package-manager.test.d.ts +2 -0
  9. package/dist/core/package-manager.test.d.ts.map +1 -0
  10. package/dist/core/package-manager.test.js +20 -0
  11. package/dist/core/package-manager.test.js.map +1 -0
  12. package/dist/core/resource-loader.d.ts +3 -0
  13. package/dist/core/resource-loader.d.ts.map +1 -1
  14. package/dist/core/resource-loader.js +10 -0
  15. package/dist/core/resource-loader.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts +1 -0
  17. package/dist/core/settings-manager.d.ts.map +1 -1
  18. package/dist/core/settings-manager.js.map +1 -1
  19. package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
  20. package/dist/extensions/caveman/index.js +116 -0
  21. package/dist/extensions/caveman/package.json +8 -0
  22. package/dist/extensions/caveman/test/extension.test.js +185 -0
  23. package/dist/extensions/caveman/test/helpers.test.js +67 -0
  24. package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
  25. package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
  26. package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
  27. package/dist/extensions/pi-intercom/config.ts +2 -2
  28. package/dist/extensions/pi-intercom/index.ts +2 -2
  29. package/dist/extensions/pi-intercom/package.json +2 -2
  30. package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
  31. package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
  32. package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
  33. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
  34. package/dist/extensions/pi-powerline-footer/index.ts +9 -16
  35. package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
  36. package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
  37. package/dist/extensions/pi-rewind-hook/index.ts +1 -1
  38. package/dist/extensions/pi-rewind-hook/package.json +1 -1
  39. package/dist/extensions/pi-subagents/README.md +224 -52
  40. package/dist/extensions/pi-subagents/install.mjs +93 -0
  41. package/dist/extensions/pi-subagents/package.json +4 -5
  42. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  43. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  44. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
  45. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
  46. package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
  47. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
  48. package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
  49. package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
  50. package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
  51. package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
  52. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
  53. package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
  54. package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
  55. package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
  56. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
  57. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
  58. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
  59. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
  60. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
  61. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
  62. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
  63. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
  64. package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
  65. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
  66. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
  67. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
  68. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
  69. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
  70. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
  71. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
  72. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
  73. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
  74. package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
  75. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
  76. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
  77. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
  78. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
  79. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
  80. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
  81. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
  82. package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
  83. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
  84. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
  85. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
  86. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
  87. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
  88. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
  89. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
  90. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
  92. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
  93. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
  94. package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
  95. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
  96. package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
  97. package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
  98. package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
  99. package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
  100. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
  101. package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
  102. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
  103. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
  104. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
  105. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
  106. package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
  107. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
  108. package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
  109. package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
  110. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
  111. package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
  112. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
  113. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
  114. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
  115. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
  116. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
  117. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
  118. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
  119. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
  120. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
  121. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
  122. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
  123. package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
  124. package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
  125. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
  126. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
  127. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
  128. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
  129. package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
  130. package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
  131. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
  132. package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
  133. package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
  134. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
  135. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
  136. package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
  137. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
  138. package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
  139. package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
  140. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
  141. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
  142. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
  143. package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
  144. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
  145. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
  146. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
  147. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
  148. package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
  149. package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
  150. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
  152. package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
  154. package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
  155. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
  156. package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
  157. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
  158. package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
  159. package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
  160. package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
  161. package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
  162. package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
  163. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
  164. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
  165. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
  166. package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
  167. package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
  168. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
  169. package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
  170. package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
  171. package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
  172. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
  173. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
  174. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
  175. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
  176. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
  177. package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
  178. package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
  179. package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
  180. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
  181. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
  182. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
  183. package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
  184. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
  185. package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
  186. package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
  187. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
  188. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
  189. package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
  190. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
  191. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
  192. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
  193. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
  194. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
  195. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
  196. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
  197. package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
  198. package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
  199. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
  200. package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
  201. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
  202. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
  203. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
  204. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
  205. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
  206. package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
  207. package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
  208. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
  209. package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
  210. package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
  211. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
  212. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
  213. package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
  214. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
  215. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
  216. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
  217. package/dist/main.d.ts.map +1 -1
  218. package/dist/main.js +4 -0
  219. package/dist/main.js.map +1 -1
  220. package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
  221. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
  222. package/dist/modes/interactive/components/first-time-setup.js +19 -5
  223. package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
  224. package/dist/skills/caveman/SKILL.md +49 -0
  225. package/dist/skills/handoff-text/SKILL.md +13 -0
  226. package/docs/docs.json +4 -0
  227. package/docs/extensions.md +2 -0
  228. package/docs/settings.md +15 -0
  229. package/docs/shared-host-extensions.md +109 -0
  230. package/package.json +1 -1
@@ -7,6 +7,7 @@ import * as path from "node:path";
7
7
  import type { Message } from "@earendil-works/pi-ai";
8
8
  import type { FSWatcher } from "node:fs";
9
9
  import type { ExtensionContext } from "@selesai/code";
10
+ import type { ModelScopeConfig } from "../runs/shared/model-scope.ts";
10
11
 
11
12
  // ============================================================================
12
13
  // Basic Types
@@ -88,6 +89,47 @@ export interface Usage {
88
89
  turns: number;
89
90
  }
90
91
 
92
+ export interface TurnBudgetConfig {
93
+ maxTurns: number;
94
+ graceTurns?: number;
95
+ }
96
+
97
+ export interface ResolvedTurnBudget {
98
+ maxTurns: number;
99
+ graceTurns: number;
100
+ }
101
+
102
+ export interface ToolBudgetConfig {
103
+ soft?: number;
104
+ hard: number;
105
+ block?: string[] | "*";
106
+ }
107
+
108
+ export interface ResolvedToolBudget {
109
+ soft?: number;
110
+ hard: number;
111
+ block: string[] | "*";
112
+ }
113
+
114
+ export type ToolBudgetOutcome = "within-budget" | "soft-reached" | "hard-blocked";
115
+
116
+ export interface ToolBudgetState extends ResolvedToolBudget {
117
+ outcome: ToolBudgetOutcome;
118
+ toolCount: number;
119
+ softReachedAt?: number;
120
+ hardReachedAt?: number;
121
+ blockedTool?: string;
122
+ }
123
+
124
+ export type TurnBudgetOutcome = "within-budget" | "wrap-up-requested" | "exceeded";
125
+
126
+ export interface TurnBudgetState extends ResolvedTurnBudget {
127
+ outcome: TurnBudgetOutcome;
128
+ turnCount: number;
129
+ wrapUpRequestedAtTurn?: number;
130
+ exceededAtTurn?: number;
131
+ }
132
+
91
133
  export interface TokenUsage {
92
134
  input: number;
93
135
  output: number;
@@ -120,6 +162,25 @@ export interface ResolvedControlConfig {
120
162
  notifyChannels: ControlNotificationChannel[];
121
163
  }
122
164
 
165
+ /**
166
+ * Smart completion batching for async-completion notifications. Successful
167
+ * sibling completions are held briefly so they arrive as one grouped message;
168
+ * failure and attention signals bypass grouping and always fire immediately.
169
+ */
170
+ export interface CompletionBatchConfig {
171
+ enabled?: boolean;
172
+ /** Idle window after each arrival; resets on every new item. */
173
+ debounceMs?: number;
174
+ /** Hard cap measured from the first item in a group. */
175
+ maxWaitMs?: number;
176
+ /** Shorter idle window for straggler groups. */
177
+ stragglerDebounceMs?: number;
178
+ /** Shorter hard cap for straggler groups. */
179
+ stragglerMaxWaitMs?: number;
180
+ /** Arrivals within this window after an emit join a straggler group. */
181
+ stragglerWindowMs?: number;
182
+ }
183
+
123
184
  export interface ControlEvent {
124
185
  type: ControlEventType;
125
186
  from?: ActivityState;
@@ -144,17 +205,25 @@ export interface ControlEvent {
144
205
 
145
206
  export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
146
207
  export type SubagentRunMode = "single" | "parallel" | "chain";
208
+ export const SUBAGENT_LIFECYCLE_ARTIFACT_VERSION = 1;
209
+ export type SubagentLifecycleArtifactVersion = typeof SUBAGENT_LIFECYCLE_ARTIFACT_VERSION;
147
210
 
148
211
  export type PublicNestedStepSummary = Pick<
149
212
  NestedStepSummary,
150
- "agent" | "status" | "sessionFile" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "startedAt" | "endedAt" | "error"
213
+ "agent" | "status" | "sessionFile" | "transcriptPath" | "transcriptError" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "startedAt" | "endedAt" | "error" | "timedOut"
151
214
  > & {
152
215
  children?: PublicNestedRunSummary[];
153
216
  };
154
217
 
218
+ export type CostSummary = {
219
+ inputTokens: number;
220
+ outputTokens: number;
221
+ costUsd: number;
222
+ };
223
+
155
224
  export type PublicNestedRunSummary = Pick<
156
225
  NestedRunSummary,
157
- "id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "totalTokens" | "startedAt" | "endedAt" | "lastUpdate" | "error"
226
+ "id" | "parentRunId" | "parentStepIndex" | "parentAgent" | "depth" | "path" | "asyncDir" | "sessionId" | "sessionFile" | "intercomTarget" | "ownerIntercomTarget" | "leafIntercomTarget" | "ownerState" | "mode" | "state" | "agent" | "agents" | "currentStep" | "chainStepCount" | "parallelGroups" | "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount" | "toolBudget" | "toolBudgetBlocked" | "totalTokens" | "totalCost" | "startedAt" | "endedAt" | "lastUpdate" | "error" | "timeoutMs" | "deadlineAt" | "timedOut" | "turnBudget" | "turnBudgetExceeded" | "wrapUpRequested"
158
227
  > & {
159
228
  steps?: PublicNestedStepSummary[];
160
229
  children?: PublicNestedRunSummary[];
@@ -392,6 +461,11 @@ export interface SingleResult {
392
461
  detachedReason?: string;
393
462
  interrupted?: boolean;
394
463
  timedOut?: boolean;
464
+ turnBudget?: TurnBudgetState;
465
+ turnBudgetExceeded?: boolean;
466
+ wrapUpRequested?: boolean;
467
+ toolBudget?: ToolBudgetState;
468
+ toolBudgetBlocked?: boolean;
395
469
  messages?: Message[];
396
470
  usage: Usage;
397
471
  model?: string;
@@ -416,6 +490,9 @@ export interface SingleResult {
416
490
  structuredOutputPath?: string;
417
491
  structuredOutputSchemaPath?: string;
418
492
  acceptance?: AcceptanceLedger;
493
+ transcriptPath?: string;
494
+ transcriptError?: string;
495
+ children?: NestedRunSummary[];
419
496
  }
420
497
 
421
498
  export interface Details {
@@ -426,6 +503,11 @@ export interface Details {
426
503
  controlEvents?: ControlEvent[];
427
504
  asyncId?: string;
428
505
  asyncDir?: string;
506
+ timeoutMs?: number;
507
+ deadlineAt?: number;
508
+ timedOut?: boolean;
509
+ turnBudget?: ResolvedTurnBudget;
510
+ toolBudget?: ResolvedToolBudget;
429
511
  progress?: AgentProgress[];
430
512
  progressSummary?: ProgressSummary;
431
513
  artifacts?: {
@@ -444,6 +526,10 @@ export interface Details {
444
526
  currentStepIndex?: number; // 0-indexed current step (for running chains)
445
527
  workflowGraph?: WorkflowGraphSnapshot;
446
528
  outputs?: ChainOutputMap;
529
+ // Aggregated child usage across all agents in the run
530
+ totalChildUsage?: Usage;
531
+ // Aggregated cost across all agents in the run
532
+ totalCost?: CostSummary;
447
533
  }
448
534
 
449
535
  // ============================================================================
@@ -454,6 +540,7 @@ export interface ArtifactPaths {
454
540
  inputPath: string;
455
541
  outputPath: string;
456
542
  jsonlPath: string;
543
+ transcriptPath: string;
457
544
  metadataPath: string;
458
545
  }
459
546
 
@@ -462,6 +549,7 @@ export interface ArtifactConfig {
462
549
  includeInput: boolean;
463
550
  includeOutput: boolean;
464
551
  includeJsonl: boolean;
552
+ includeTranscript?: boolean;
465
553
  includeMetadata: boolean;
466
554
  cleanupDays: number;
467
555
  }
@@ -492,6 +580,8 @@ export interface NestedStepSummary {
492
580
  agent: string;
493
581
  status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
494
582
  sessionFile?: string;
583
+ transcriptPath?: string;
584
+ transcriptError?: string;
495
585
  activityState?: ActivityState;
496
586
  lastActivityAt?: number;
497
587
  currentTool?: string;
@@ -502,6 +592,12 @@ export interface NestedStepSummary {
502
592
  startedAt?: number;
503
593
  endedAt?: number;
504
594
  error?: string;
595
+ timedOut?: boolean;
596
+ turnBudget?: TurnBudgetState;
597
+ turnBudgetExceeded?: boolean;
598
+ wrapUpRequested?: boolean;
599
+ toolBudget?: ToolBudgetState;
600
+ toolBudgetBlocked?: boolean;
505
601
  children?: NestedRunSummary[];
506
602
  }
507
603
 
@@ -533,9 +629,18 @@ export interface NestedRunSummary extends NestedRunAddress {
533
629
  turnCount?: number;
534
630
  toolCount?: number;
535
631
  totalTokens?: TokenUsage;
632
+ totalCost?: CostSummary;
536
633
  startedAt?: number;
537
634
  endedAt?: number;
538
635
  lastUpdate?: number;
636
+ timeoutMs?: number;
637
+ deadlineAt?: number;
638
+ timedOut?: boolean;
639
+ turnBudget?: TurnBudgetState;
640
+ turnBudgetExceeded?: boolean;
641
+ wrapUpRequested?: boolean;
642
+ toolBudget?: ToolBudgetState;
643
+ toolBudgetBlocked?: boolean;
539
644
  error?: string;
540
645
  }
541
646
 
@@ -547,6 +652,7 @@ export interface NestedRouteInfo {
547
652
  }
548
653
 
549
654
  export interface AsyncStartedEvent {
655
+ lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
550
656
  id?: string;
551
657
  asyncDir?: string;
552
658
  pid?: number;
@@ -558,14 +664,19 @@ export interface AsyncStartedEvent {
558
664
  chainStepCount?: number;
559
665
  parallelGroups?: AsyncParallelGroupStatus[];
560
666
  workflowGraph?: WorkflowGraphSnapshot;
667
+ timeoutMs?: number;
668
+ deadlineAt?: number;
669
+ turnBudget?: TurnBudgetState;
561
670
  nestedRoute?: NestedRouteInfo;
562
671
  }
563
672
 
564
673
  export interface AsyncStatus {
674
+ lifecycleArtifactVersion?: SubagentLifecycleArtifactVersion;
565
675
  runId: string;
566
676
  sessionId?: string;
567
677
  mode: SubagentRunMode;
568
678
  state: "queued" | "running" | "complete" | "failed" | "paused";
679
+ error?: string;
569
680
  activityState?: ActivityState;
570
681
  lastActivityAt?: number;
571
682
  currentTool?: string;
@@ -573,9 +684,19 @@ export interface AsyncStatus {
573
684
  currentPath?: string;
574
685
  turnCount?: number;
575
686
  toolCount?: number;
687
+ steerCount?: number;
688
+ lastSteerAt?: number;
576
689
  startedAt: number;
577
690
  endedAt?: number;
578
691
  lastUpdate?: number;
692
+ timeoutMs?: number;
693
+ deadlineAt?: number;
694
+ timedOut?: boolean;
695
+ turnBudget?: TurnBudgetState;
696
+ turnBudgetExceeded?: boolean;
697
+ wrapUpRequested?: boolean;
698
+ toolBudget?: ToolBudgetState;
699
+ toolBudgetBlocked?: boolean;
579
700
  pid?: number;
580
701
  cwd?: string;
581
702
  currentStep?: number;
@@ -592,6 +713,8 @@ export interface AsyncStatus {
592
713
  status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
593
714
  children?: NestedRunSummary[];
594
715
  sessionFile?: string;
716
+ transcriptPath?: string;
717
+ transcriptError?: string;
595
718
  activityState?: ActivityState;
596
719
  lastActivityAt?: number;
597
720
  currentTool?: string;
@@ -606,12 +729,21 @@ export interface AsyncStatus {
606
729
  endedAt?: number;
607
730
  durationMs?: number;
608
731
  exitCode?: number | null;
732
+ timedOut?: boolean;
733
+ turnBudget?: TurnBudgetState;
734
+ turnBudgetExceeded?: boolean;
735
+ wrapUpRequested?: boolean;
736
+ toolBudget?: ToolBudgetState;
737
+ toolBudgetBlocked?: boolean;
609
738
  tokens?: TokenUsage;
610
739
  skills?: string[];
611
740
  model?: string;
612
741
  thinking?: string;
613
742
  attemptedModels?: string[];
614
743
  modelAttempts?: ModelAttempt[];
744
+ totalCost?: CostSummary;
745
+ steerCount?: number;
746
+ lastSteerAt?: number;
615
747
  error?: string;
616
748
  structuredOutput?: unknown;
617
749
  structuredOutputPath?: string;
@@ -621,6 +753,7 @@ export interface AsyncStatus {
621
753
  sessionDir?: string;
622
754
  outputFile?: string;
623
755
  totalTokens?: TokenUsage;
756
+ totalCost?: CostSummary;
624
757
  sessionFile?: string;
625
758
  outputs?: ChainOutputMap;
626
759
  }
@@ -642,6 +775,8 @@ export interface AsyncJobState {
642
775
  currentPath?: string;
643
776
  turnCount?: number;
644
777
  toolCount?: number;
778
+ steerCount?: number;
779
+ lastSteerAt?: number;
645
780
  mode?: SubagentRunMode;
646
781
  agents?: string[];
647
782
  currentStep?: number;
@@ -655,6 +790,14 @@ export interface AsyncJobState {
655
790
  activeParallelGroup?: boolean;
656
791
  startedAt?: number;
657
792
  updatedAt?: number;
793
+ timeoutMs?: number;
794
+ deadlineAt?: number;
795
+ timedOut?: boolean;
796
+ turnBudget?: TurnBudgetState;
797
+ turnBudgetExceeded?: boolean;
798
+ wrapUpRequested?: boolean;
799
+ toolBudget?: ToolBudgetState;
800
+ toolBudgetBlocked?: boolean;
658
801
  sessionDir?: string;
659
802
  outputFile?: string;
660
803
  totalTokens?: TokenUsage;
@@ -669,6 +812,13 @@ export interface ForegroundResumeChild {
669
812
  index: number;
670
813
  sessionFile?: string;
671
814
  status: SubagentResultStatus;
815
+ exitCode?: number;
816
+ finalOutput?: string;
817
+ artifactPaths?: ArtifactPaths;
818
+ transcriptPath?: string;
819
+ transcriptError?: string;
820
+ detachedReason?: string;
821
+ updatedAt?: number;
672
822
  }
673
823
 
674
824
  export interface ForegroundResumeRun {
@@ -683,6 +833,7 @@ export interface SubagentState {
683
833
  baseCwd: string;
684
834
  currentSessionId: string | null;
685
835
  subagentInProgress?: boolean;
836
+ subagentSpawns?: { sessionId: string | null; count: number };
686
837
  asyncJobs: Map<string, AsyncJobState>;
687
838
  foregroundRuns?: Map<string, ForegroundResumeRun>;
688
839
  foregroundControls: Map<string, {
@@ -763,10 +914,13 @@ export interface RunSyncOptions {
763
914
  interruptSignal?: AbortSignal;
764
915
  timeoutMs?: number;
765
916
  deadlineAt?: number;
917
+ turnBudget?: ResolvedTurnBudget;
918
+ toolBudget?: ResolvedToolBudget;
766
919
  allowIntercomDetach?: boolean;
767
920
  intercomEvents?: IntercomEventBus;
768
921
  onUpdate?: (r: import("@earendil-works/pi-agent-core").AgentToolResult<Details>) => void;
769
922
  onControlEvent?: (event: ControlEvent) => void;
923
+ onDetachedExit?: (result: SingleResult) => void;
770
924
  controlConfig?: ResolvedControlConfig;
771
925
  intercomSessionName?: string;
772
926
  orchestratorIntercomTarget?: string;
@@ -784,10 +938,14 @@ export interface RunSyncOptions {
784
938
  nestedRoute?: NestedRouteInfo;
785
939
  /** Override the agent's default model (format: "provider/id" or just "id") */
786
940
  modelOverride?: string;
941
+ /** Override the agent's default thinking level for this run */
942
+ thinkingOverride?: AgentConfig["thinking"];
787
943
  /** Registry models available for heuristic bare-model resolution */
788
944
  availableModels?: Array<{ provider: string; id: string; fullId: string }>;
789
945
  /** Current parent-session provider to prefer for ambiguous bare model ids */
790
946
  preferredModelProvider?: string;
947
+ /** Optional subagent model-scope enforcement for fallback candidates */
948
+ modelScope?: ModelScopeConfig;
791
949
  /** Skills to make available (overrides agent default if provided) */
792
950
  skills?: string[];
793
951
  structuredOutput?: {
@@ -829,18 +987,37 @@ export interface ProactiveSkillSubagentsConfig {
829
987
  preferredAgent?: string;
830
988
  }
831
989
 
990
+ export type ToolDescriptionMode = "full" | "compact" | "custom";
991
+
992
+ export interface ScheduledRunsConfig {
993
+ enabled?: boolean;
994
+ maxLatenessMs?: number;
995
+ maxPending?: number;
996
+ }
997
+
832
998
  export interface ExtensionConfig {
833
999
  asyncByDefault?: boolean;
1000
+ /** Tool description variant registered for the parent-facing subagent tool. Defaults to full. */
1001
+ toolDescriptionMode?: ToolDescriptionMode;
834
1002
  forceTopLevelAsync?: boolean;
835
1003
  defaultSessionDir?: string;
1004
+ singleRunOutputBaseDir?: string;
836
1005
  maxSubagentDepth?: number;
1006
+ maxSubagentSpawnsPerSession?: number;
1007
+ /** Global cap on simultaneously-running subagent tasks within a single run. Defaults to 20. */
1008
+ globalConcurrencyLimit?: number;
837
1009
  control?: ControlConfig;
1010
+ completionBatch?: CompletionBatchConfig;
1011
+ turnBudget?: TurnBudgetConfig;
1012
+ toolBudget?: ToolBudgetConfig;
838
1013
  parallel?: TopLevelParallelConfig;
839
1014
  chain?: ExtensionChainConfig;
840
1015
  worktreeSetupHook?: string;
841
1016
  worktreeSetupHookTimeoutMs?: number;
1017
+ worktreeBaseDir?: string;
842
1018
  intercomBridge?: IntercomBridgeConfig;
843
1019
  proactiveSkillSubagents?: ProactiveSkillSubagentsConfig | false;
1020
+ scheduledRuns?: ScheduledRunsConfig;
844
1021
  }
845
1022
 
846
1023
  // ============================================================================
@@ -857,6 +1034,7 @@ export const DEFAULT_ARTIFACT_CONFIG: ArtifactConfig = {
857
1034
  includeInput: true,
858
1035
  includeOutput: true,
859
1036
  includeJsonl: false,
1037
+ includeTranscript: true,
860
1038
  includeMetadata: true,
861
1039
  cleanupDays: 7,
862
1040
  };
@@ -932,7 +1110,8 @@ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
932
1110
  export const POLL_INTERVAL_MS = 250;
933
1111
  export const MAX_WIDGET_JOBS = 4;
934
1112
  export const DEFAULT_SUBAGENT_MAX_DEPTH = 2;
935
- export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "status", "interrupt", "resume", "append-step", "doctor"] as const;
1113
+ export const DEFAULT_MAX_SUBAGENT_SPAWNS_PER_SESSION = 40;
1114
+ export const SUBAGENT_ACTIONS = ["list", "get", "models", "create", "update", "delete", "eject", "disable", "enable", "reset", "status", "interrupt", "resume", "steer", "append-step", "doctor", "schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
936
1115
 
937
1116
  export const DEFAULT_FORK_PREAMBLE =
938
1117
  "You are a delegated subagent running from a fork of the parent session. " +
@@ -975,12 +1154,16 @@ export function wrapForkTask(task: string, preamble?: string | false): string {
975
1154
  // Recursion Depth Guard
976
1155
  // ============================================================================
977
1156
 
978
- export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
1157
+ function normalizeNonNegativeInteger(value: unknown): number | undefined {
979
1158
  const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
980
1159
  if (!Number.isInteger(parsed) || parsed < 0) return undefined;
981
1160
  return parsed;
982
1161
  }
983
1162
 
1163
+ export function normalizeMaxSubagentDepth(value: unknown): number | undefined {
1164
+ return normalizeNonNegativeInteger(value);
1165
+ }
1166
+
984
1167
  export function resolveCurrentMaxSubagentDepth(configMaxDepth?: number): number {
985
1168
  return normalizeMaxSubagentDepth(process.env.SELESAI_SUBAGENT_MAX_DEPTH)
986
1169
  ?? normalizeMaxSubagentDepth(configMaxDepth)
@@ -1009,6 +1192,16 @@ export function getSubagentDepthEnv(maxDepth?: number): Record<string, string> {
1009
1192
  };
1010
1193
  }
1011
1194
 
1195
+ export function normalizeMaxSubagentSpawnsPerSession(value: unknown): number | undefined {
1196
+ return normalizeNonNegativeInteger(value);
1197
+ }
1198
+
1199
+ export function resolveMaxSubagentSpawnsPerSession(configMaxSpawns?: number): number {
1200
+ return normalizeMaxSubagentSpawnsPerSession(process.env.SELESAI_SUBAGENT_MAX_SPAWNS_PER_SESSION)
1201
+ ?? normalizeMaxSubagentSpawnsPerSession(configMaxSpawns)
1202
+ ?? DEFAULT_MAX_SUBAGENT_SPAWNS_PER_SESSION;
1203
+ }
1204
+
1012
1205
  // ============================================================================
1013
1206
  // Utility Functions
1014
1207
  // ============================================================================
@@ -5,23 +5,61 @@
5
5
  import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
- import * as piCodingAgent from "@selesai/code";
9
8
  import type { Message } from "@earendil-works/pi-ai";
10
- import { stripThinkingTagsFromText } from "../../../../utils/thinking-tags.ts";
9
+ import { stripThinkingTagsFromText } from "../../../../utils/thinking-tags.js"; // ponytail: .js not .ts — extension .ts is copied verbatim into dist/, only compiled .js exists there
11
10
  import { formatToolCall } from "./formatters.ts";
12
- import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, SingleResult, ToolCallSummary } from "./types.ts";
11
+ import type { AgentProgress, AsyncStatus, Details, DisplayItem, ErrorInfo, NestedRunSummary, SingleResult, ToolCallSummary, Usage } from "./types.ts";
13
12
 
14
13
  // ============================================================================
15
14
  // File System Utilities
16
15
  // ============================================================================
17
16
 
18
17
  const DEFAULT_CONFIG_DIR_NAME = ".selesai";
18
+ const SELESAI_CODING_AGENT_PACKAGE_NAME = "@selesai/code";
19
+ export const SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV = "SELESAI_SUBAGENTS_SELESAI_PACKAGE_ROOT";
19
20
 
20
- export function resolveConfigDirName(codingAgentModule: unknown = piCodingAgent): string {
21
- const value = codingAgentModule && typeof codingAgentModule === "object"
22
- ? (codingAgentModule as { CONFIG_DIR_NAME?: unknown }).CONFIG_DIR_NAME
21
+ function validConfigDirName(value: unknown): string | undefined {
22
+ return typeof value === "string" && value.trim() ? value : undefined;
23
+ }
24
+
25
+ function readConfigDirNameFromPackageRoot(packageRoot: string | undefined): string | undefined {
26
+ if (!packageRoot) return undefined;
27
+ try {
28
+ const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8")) as {
29
+ name?: unknown;
30
+ piConfig?: { configDir?: unknown };
31
+ };
32
+ if (pkg.name !== SELESAI_CODING_AGENT_PACKAGE_NAME) return undefined;
33
+ return validConfigDirName(pkg.piConfig?.configDir);
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ function resolveConfigDirNameFromPackageJson(entryPoint = process.argv[1], packageRoot = process.env[SELESAI_CODING_AGENT_PACKAGE_ROOT_ENV]): string | undefined {
40
+ const packageRootValue = readConfigDirNameFromPackageRoot(packageRoot);
41
+ if (packageRootValue) return packageRootValue;
42
+ if (!entryPoint) return undefined;
43
+ try {
44
+ let dir = path.dirname(fs.realpathSync(entryPoint));
45
+ while (dir !== path.dirname(dir)) {
46
+ const value = readConfigDirNameFromPackageRoot(dir);
47
+ if (value) return value;
48
+ dir = path.dirname(dir);
49
+ }
50
+ } catch {
51
+ // Package metadata lookup is best-effort; detached runners must not fail here.
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ export function resolveConfigDirName(codingAgentModule?: unknown, entryPoint?: string, packageRoot?: string): string {
57
+ const moduleValue = codingAgentModule && typeof codingAgentModule === "object"
58
+ ? validConfigDirName((codingAgentModule as { CONFIG_DIR_NAME?: unknown }).CONFIG_DIR_NAME)
23
59
  : undefined;
24
- return typeof value === "string" && value.trim() ? value : DEFAULT_CONFIG_DIR_NAME;
60
+ return moduleValue
61
+ ?? resolveConfigDirNameFromPackageJson(entryPoint, packageRoot)
62
+ ?? DEFAULT_CONFIG_DIR_NAME;
25
63
  }
26
64
 
27
65
  export function getConfigDirName(): string {
@@ -205,19 +243,28 @@ function writePrompt(agent: string, prompt: string): { dir: string; path: string
205
243
  * Get the final text output from a list of messages
206
244
  */
207
245
  export function getFinalOutput(messages: Message[]): string {
246
+ const validTextParts: string[] = [];
208
247
  for (let i = messages.length - 1; i >= 0; i--) {
209
248
  const msg = messages[i];
210
- if (msg.role === "assistant") {
211
- const hasAssistantError = ("errorMessage" in msg && typeof msg.errorMessage === "string" && msg.errorMessage.length > 0)
212
- || ("stopReason" in msg && msg.stopReason === "error");
213
- if (hasAssistantError) continue;
214
- for (let j = msg.content.length - 1; j >= 0; j--) {
215
- const part = msg.content[j];
216
- if (part.type === "text" && part.text.trim().length > 0) return part.text;
249
+ if (msg.role !== "assistant") continue;
250
+ const hasAssistantError = ("errorMessage" in msg && typeof msg.errorMessage === "string" && msg.errorMessage.length > 0)
251
+ || ("stopReason" in msg && msg.stopReason === "error");
252
+ if (hasAssistantError) continue;
253
+ for (let j = msg.content.length - 1; j >= 0; j--) {
254
+ const part = msg.content[j];
255
+ if (part.type !== "text" || part.text.trim().length === 0) continue;
256
+ validTextParts.push(part.text);
257
+ if (/```acceptance-report\s*\n[\s\S]*?```/i.test(part.text)) return part.text;
258
+ for (const match of part.text.matchAll(/```(?:json|jsonc|json5)\s*\n([\s\S]*?)```/gi)) {
259
+ const body = match[1] ?? "";
260
+ if (/"criteriaSatisfied"/.test(body) && /"(?:changedFiles|testsAddedOrUpdated|commandsRun|validationOutput|residualRisks|noStagedFiles|diffSummary|reviewFindings|manualNotes)"/.test(body)) {
261
+ return part.text;
262
+ }
217
263
  }
264
+ if (/ACCEPTANCE_REPORT\s*:/i.test(part.text)) return part.text;
218
265
  }
219
266
  }
220
- return "";
267
+ return validTextParts[0] ?? "";
221
268
  }
222
269
 
223
270
  export function getSingleResultOutput(result: Pick<SingleResult, "finalOutput" | "messages">): string {
@@ -279,6 +326,44 @@ function extractToolCallSummaries(messages: Message[] | undefined): ToolCallSumm
279
326
  return summaries;
280
327
  }
281
328
 
329
+ export function sumResultsUsage(results: SingleResult[]): Usage {
330
+ const usage: Usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
331
+ for (const result of results) {
332
+ usage.input += result.usage.input;
333
+ usage.output += result.usage.output;
334
+ usage.cacheRead += result.usage.cacheRead;
335
+ usage.cacheWrite += result.usage.cacheWrite;
336
+ usage.cost += result.usage.cost;
337
+ usage.turns += result.usage.turns;
338
+ }
339
+ return usage;
340
+ }
341
+
342
+ function addNestedCost(total: NonNullable<Details["totalCost"]>, children: NestedRunSummary[] | undefined): void {
343
+ for (const child of children ?? []) {
344
+ if (child.totalCost) {
345
+ total.inputTokens += child.totalCost.inputTokens;
346
+ total.outputTokens += child.totalCost.outputTokens;
347
+ total.costUsd += child.totalCost.costUsd;
348
+ continue;
349
+ }
350
+ addNestedCost(total, child.children);
351
+ for (const step of child.steps ?? []) addNestedCost(total, step.children);
352
+ }
353
+ }
354
+
355
+ /** Sum input tokens, output tokens, and cost across a set of SingleResults. */
356
+ export function sumResultsCost(results: SingleResult[]): NonNullable<Details["totalCost"]> {
357
+ const total = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
358
+ for (const result of results) {
359
+ total.inputTokens += result.usage.input;
360
+ total.outputTokens += result.usage.output;
361
+ total.costUsd += result.usage.cost;
362
+ addNestedCost(total, result.children);
363
+ }
364
+ return total;
365
+ }
366
+
282
367
  export function compactForegroundResult(result: SingleResult): SingleResult {
283
368
  if (result.progress?.status === "running") return result;
284
369
  const toolCalls = result.toolCalls?.length ? result.toolCalls : extractToolCallSummaries(result.messages);