@bubblebrain-ai/bubble 0.0.63 → 0.0.65

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 (912) hide show
  1. package/README.md +437 -11
  2. package/dist/agent/abort-errors.d.ts +18 -0
  3. package/dist/agent/abort-errors.js +51 -0
  4. package/dist/agent/budget-ledger.d.ts +20 -0
  5. package/dist/agent/budget-ledger.js +45 -0
  6. package/dist/agent/categories.d.ts +100 -0
  7. package/dist/agent/categories.js +203 -0
  8. package/dist/agent/change-tracker.d.ts +40 -0
  9. package/dist/agent/change-tracker.js +89 -0
  10. package/dist/agent/child-runner.d.ts +64 -0
  11. package/dist/agent/child-runner.js +405 -0
  12. package/dist/agent/input-controller.d.ts +14 -0
  13. package/dist/agent/input-controller.js +43 -0
  14. package/dist/agent/internal-reminder-sanitizer.d.ts +25 -0
  15. package/dist/agent/internal-reminder-sanitizer.js +297 -0
  16. package/dist/agent/profiles.d.ts +76 -0
  17. package/dist/agent/profiles.js +526 -0
  18. package/dist/agent/provider-fingerprint.d.ts +6 -0
  19. package/dist/agent/provider-fingerprint.js +89 -0
  20. package/dist/agent/resident-history.d.ts +7 -0
  21. package/dist/agent/resident-history.js +61 -0
  22. package/dist/agent/result-integrator.d.ts +22 -0
  23. package/dist/agent/result-integrator.js +50 -0
  24. package/dist/agent/routing-catalog.d.ts +131 -0
  25. package/dist/agent/routing-catalog.js +260 -0
  26. package/dist/agent/structured-output.d.ts +37 -0
  27. package/dist/agent/structured-output.js +193 -0
  28. package/dist/agent/subagent/router.d.ts +65 -0
  29. package/dist/agent/subagent/router.js +203 -0
  30. package/dist/agent/subagent/runtime.d.ts +235 -0
  31. package/dist/agent/subagent/runtime.js +835 -0
  32. package/dist/agent/subagent-control.d.ts +100 -0
  33. package/dist/agent/subagent-control.js +70 -0
  34. package/dist/agent/subagent-lifecycle-reminder.d.ts +3 -0
  35. package/dist/agent/subagent-lifecycle-reminder.js +112 -0
  36. package/dist/agent/subagent-route-format.d.ts +8 -0
  37. package/dist/agent/subagent-route-format.js +18 -0
  38. package/dist/agent/subagent-scheduler.d.ts +105 -0
  39. package/dist/agent/subagent-scheduler.js +287 -0
  40. package/dist/agent/subagent-store.d.ts +57 -0
  41. package/dist/agent/subagent-store.js +180 -0
  42. package/dist/agent/subagent-summary.d.ts +30 -0
  43. package/dist/agent/subagent-summary.js +74 -0
  44. package/dist/agent/subagent-wait-guidance.d.ts +2 -0
  45. package/dist/agent/subagent-wait-guidance.js +8 -0
  46. package/dist/agent/subtask-policy.d.ts +13 -0
  47. package/dist/agent/subtask-policy.js +56 -0
  48. package/dist/agent/task-lifecycle-reminder.d.ts +26 -0
  49. package/dist/agent/task-lifecycle-reminder.js +59 -0
  50. package/dist/agent/tool-intent.d.ts +45 -0
  51. package/dist/agent/tool-intent.js +318 -0
  52. package/dist/agent/tool-visibility.d.ts +10 -0
  53. package/dist/agent/tool-visibility.js +6 -0
  54. package/dist/agent/transcript-helpers.d.ts +20 -0
  55. package/dist/agent/transcript-helpers.js +63 -0
  56. package/dist/agent/update-queue.d.ts +13 -0
  57. package/dist/agent/update-queue.js +69 -0
  58. package/dist/agent/workflow/control.d.ts +60 -0
  59. package/dist/agent/workflow/control.js +62 -0
  60. package/dist/agent/workflow/errors.d.ts +16 -0
  61. package/dist/agent/workflow/errors.js +24 -0
  62. package/dist/agent/workflow/runs.d.ts +73 -0
  63. package/dist/agent/workflow/runs.js +160 -0
  64. package/dist/agent/workflow/runtime.d.ts +88 -0
  65. package/dist/agent/workflow/runtime.js +319 -0
  66. package/dist/agent/worktree.d.ts +29 -0
  67. package/dist/agent/worktree.js +73 -0
  68. package/dist/agent.d.ts +460 -0
  69. package/dist/agent.js +2210 -0
  70. package/dist/anthropic-thinking.d.ts +4 -0
  71. package/dist/anthropic-thinking.js +11 -0
  72. package/dist/approval/controller.d.ts +71 -0
  73. package/dist/approval/controller.js +286 -0
  74. package/dist/approval/danger.d.ts +13 -0
  75. package/dist/approval/danger.js +55 -0
  76. package/dist/approval/diff-hunks.d.ts +12 -0
  77. package/dist/approval/diff-hunks.js +32 -0
  78. package/dist/approval/protected-paths.d.ts +1 -0
  79. package/dist/approval/protected-paths.js +35 -0
  80. package/dist/approval/session-cache.d.ts +21 -0
  81. package/dist/approval/session-cache.js +38 -0
  82. package/dist/approval/tool-helper.d.ts +14 -0
  83. package/dist/approval/tool-helper.js +47 -0
  84. package/dist/approval/types.d.ts +146 -0
  85. package/dist/approval/types.js +8 -0
  86. package/dist/bin.d.ts +2 -0
  87. package/dist/bin.js +3 -0
  88. package/dist/bubble-home.d.ts +8 -0
  89. package/dist/bubble-home.js +19 -0
  90. package/dist/checkpoints.d.ts +57 -0
  91. package/dist/checkpoints.js +0 -0
  92. package/dist/cli.d.ts +37 -0
  93. package/dist/cli.js +175 -0
  94. package/dist/clipboard.d.ts +14 -0
  95. package/dist/clipboard.js +132 -0
  96. package/dist/config.d.ts +96 -0
  97. package/dist/config.js +287 -0
  98. package/dist/context/budget.d.ts +28 -0
  99. package/dist/context/budget.js +130 -0
  100. package/dist/context/checkpoint.d.ts +18 -0
  101. package/dist/context/checkpoint.js +117 -0
  102. package/dist/context/compact-llm.d.ts +25 -0
  103. package/dist/context/compact-llm.js +175 -0
  104. package/dist/context/compact.d.ts +124 -0
  105. package/dist/context/compact.js +635 -0
  106. package/dist/context/compaction-files.d.ts +44 -0
  107. package/dist/context/compaction-files.js +148 -0
  108. package/dist/context/llm-compactor.d.ts +53 -0
  109. package/dist/context/llm-compactor.js +424 -0
  110. package/dist/context/overflow.d.ts +9 -0
  111. package/dist/context/overflow.js +50 -0
  112. package/dist/context/projector.d.ts +34 -0
  113. package/dist/context/projector.js +251 -0
  114. package/dist/context/prune.d.ts +26 -0
  115. package/dist/context/prune.js +171 -0
  116. package/dist/context/session-write-lock.d.ts +20 -0
  117. package/dist/context/session-write-lock.js +162 -0
  118. package/dist/context/token-estimator.d.ts +14 -0
  119. package/dist/context/token-estimator.js +106 -0
  120. package/dist/context/tool-images.d.ts +3 -0
  121. package/dist/context/tool-images.js +35 -0
  122. package/dist/context/tool-output-truncate.d.ts +24 -0
  123. package/dist/context/tool-output-truncate.js +181 -0
  124. package/dist/context/usage.d.ts +34 -0
  125. package/dist/context/usage.js +230 -0
  126. package/dist/debug-trace.d.ts +27 -0
  127. package/dist/debug-trace.js +401 -0
  128. package/dist/diff-stats.d.ts +5 -0
  129. package/dist/diff-stats.js +21 -0
  130. package/dist/external-runtime/grok-acp.d.ts +56 -0
  131. package/dist/external-runtime/grok-acp.js +386 -0
  132. package/dist/external-runtime/grok-binary.d.ts +25 -0
  133. package/dist/external-runtime/grok-binary.js +108 -0
  134. package/dist/external-runtime/grok-errors.d.ts +7 -0
  135. package/dist/external-runtime/grok-errors.js +15 -0
  136. package/dist/external-runtime/grok-input-policy.d.ts +58 -0
  137. package/dist/external-runtime/grok-input-policy.js +52 -0
  138. package/dist/external-runtime/grok-ndjson.d.ts +14 -0
  139. package/dist/external-runtime/grok-ndjson.js +193 -0
  140. package/dist/external-runtime/grok-network.d.ts +15 -0
  141. package/dist/external-runtime/grok-network.js +81 -0
  142. package/dist/external-runtime/grok-preflight.d.ts +28 -0
  143. package/dist/external-runtime/grok-preflight.js +194 -0
  144. package/dist/external-runtime/grok-process.d.ts +26 -0
  145. package/dist/external-runtime/grok-process.js +184 -0
  146. package/dist/external-runtime/grok-profile.d.ts +32 -0
  147. package/dist/external-runtime/grok-profile.js +419 -0
  148. package/dist/external-runtime/grok-provider.d.ts +31 -0
  149. package/dist/external-runtime/grok-provider.js +41 -0
  150. package/dist/external-runtime/grok-runtime.d.ts +91 -0
  151. package/dist/external-runtime/grok-runtime.js +882 -0
  152. package/dist/external-runtime/index.d.ts +13 -0
  153. package/dist/external-runtime/index.js +13 -0
  154. package/dist/external-runtime/session-policy.d.ts +11 -0
  155. package/dist/external-runtime/session-policy.js +30 -0
  156. package/dist/external-runtime/types.d.ts +65 -0
  157. package/dist/external-runtime/types.js +1 -0
  158. package/dist/feedback/collect.d.ts +7 -0
  159. package/dist/feedback/collect.js +119 -0
  160. package/dist/feedback/config.d.ts +14 -0
  161. package/dist/feedback/config.js +16 -0
  162. package/dist/feedback/redact.d.ts +1 -0
  163. package/dist/feedback/redact.js +25 -0
  164. package/dist/feedback/submit.d.ts +6 -0
  165. package/dist/feedback/submit.js +43 -0
  166. package/dist/feedback/types.d.ts +22 -0
  167. package/dist/feedback/types.js +1 -0
  168. package/dist/feishu/agent-host/approval-card.d.ts +11 -0
  169. package/dist/feishu/agent-host/approval-card.js +85 -0
  170. package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
  171. package/dist/feishu/agent-host/approval-ui.js +214 -0
  172. package/dist/feishu/agent-host/run-driver.d.ts +51 -0
  173. package/dist/feishu/agent-host/run-driver.js +397 -0
  174. package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
  175. package/dist/feishu/agent-host/runtime-deps.js +8 -0
  176. package/dist/feishu/card/budget.d.ts +40 -0
  177. package/dist/feishu/card/budget.js +134 -0
  178. package/dist/feishu/card/renderer.d.ts +29 -0
  179. package/dist/feishu/card/renderer.js +245 -0
  180. package/dist/feishu/card/run-state-types.d.ts +49 -0
  181. package/dist/feishu/card/run-state-types.js +15 -0
  182. package/dist/feishu/card/run-state.d.ts +21 -0
  183. package/dist/feishu/card/run-state.js +223 -0
  184. package/dist/feishu/channel/channel.d.ts +52 -0
  185. package/dist/feishu/channel/channel.js +74 -0
  186. package/dist/feishu/config.d.ts +24 -0
  187. package/dist/feishu/config.js +97 -0
  188. package/dist/feishu/format.d.ts +6 -0
  189. package/dist/feishu/format.js +14 -0
  190. package/dist/feishu/index.d.ts +4 -0
  191. package/dist/feishu/index.js +4 -0
  192. package/dist/feishu/logger.d.ts +31 -0
  193. package/dist/feishu/logger.js +62 -0
  194. package/dist/feishu/paths.d.ts +12 -0
  195. package/dist/feishu/paths.js +38 -0
  196. package/dist/feishu/process-registry.d.ts +29 -0
  197. package/dist/feishu/process-registry.js +90 -0
  198. package/dist/feishu/router/commands.d.ts +38 -0
  199. package/dist/feishu/router/commands.js +286 -0
  200. package/dist/feishu/router/event-router.d.ts +40 -0
  201. package/dist/feishu/router/event-router.js +208 -0
  202. package/dist/feishu/router/whitelist.d.ts +23 -0
  203. package/dist/feishu/router/whitelist.js +20 -0
  204. package/dist/feishu/runtime/active-runs.d.ts +32 -0
  205. package/dist/feishu/runtime/active-runs.js +84 -0
  206. package/dist/feishu/runtime/pending-queue.d.ts +36 -0
  207. package/dist/feishu/runtime/pending-queue.js +98 -0
  208. package/dist/feishu/runtime/process-pool.d.ts +29 -0
  209. package/dist/feishu/runtime/process-pool.js +49 -0
  210. package/dist/feishu/schema.d.ts +17 -0
  211. package/dist/feishu/schema.js +252 -0
  212. package/dist/feishu/scope/scope-registry.d.ts +39 -0
  213. package/dist/feishu/scope/scope-registry.js +148 -0
  214. package/dist/feishu/scope/session-binder.d.ts +44 -0
  215. package/dist/feishu/scope/session-binder.js +100 -0
  216. package/dist/feishu/scope/session-store.d.ts +24 -0
  217. package/dist/feishu/scope/session-store.js +73 -0
  218. package/dist/feishu/secrets.d.ts +37 -0
  219. package/dist/feishu/secrets.js +129 -0
  220. package/dist/feishu/serve.d.ts +12 -0
  221. package/dist/feishu/serve.js +302 -0
  222. package/dist/feishu/types.d.ts +75 -0
  223. package/dist/feishu/types.js +23 -0
  224. package/dist/feishu/wizard.d.ts +24 -0
  225. package/dist/feishu/wizard.js +121 -0
  226. package/dist/goal/command.d.ts +20 -0
  227. package/dist/goal/command.js +76 -0
  228. package/dist/goal/engine.d.ts +33 -0
  229. package/dist/goal/engine.js +65 -0
  230. package/dist/goal/format.d.ts +18 -0
  231. package/dist/goal/format.js +112 -0
  232. package/dist/goal/prompts.d.ts +13 -0
  233. package/dist/goal/prompts.js +84 -0
  234. package/dist/goal/store.d.ts +64 -0
  235. package/dist/goal/store.js +174 -0
  236. package/dist/goal/tools.d.ts +13 -0
  237. package/dist/goal/tools.js +67 -0
  238. package/dist/goal/usage.d.ts +2 -0
  239. package/dist/goal/usage.js +3 -0
  240. package/dist/hooks/config.d.ts +9 -0
  241. package/dist/hooks/config.js +278 -0
  242. package/dist/hooks/controller.d.ts +24 -0
  243. package/dist/hooks/controller.js +254 -0
  244. package/dist/hooks/index.d.ts +6 -0
  245. package/dist/hooks/index.js +4 -0
  246. package/dist/hooks/log.d.ts +14 -0
  247. package/dist/hooks/log.js +54 -0
  248. package/dist/hooks/runner.d.ts +5 -0
  249. package/dist/hooks/runner.js +225 -0
  250. package/dist/hooks/trust.d.ts +37 -0
  251. package/dist/hooks/trust.js +143 -0
  252. package/dist/hooks/types.d.ts +173 -0
  253. package/dist/hooks/types.js +46 -0
  254. package/dist/loop/engine.d.ts +29 -0
  255. package/dist/loop/engine.js +63 -0
  256. package/dist/lsp/config.d.ts +18 -0
  257. package/dist/lsp/config.js +58 -0
  258. package/dist/lsp/diagnostics.d.ts +24 -0
  259. package/dist/lsp/diagnostics.js +103 -0
  260. package/dist/lsp/index.d.ts +3 -0
  261. package/dist/lsp/index.js +3 -0
  262. package/dist/lsp/service.d.ts +97 -0
  263. package/dist/lsp/service.js +815 -0
  264. package/dist/main.d.ts +5 -0
  265. package/dist/main.js +984 -0
  266. package/dist/mcp/client.d.ts +68 -0
  267. package/dist/mcp/client.js +177 -0
  268. package/dist/mcp/config.d.ts +30 -0
  269. package/dist/mcp/config.js +144 -0
  270. package/dist/mcp/manager.d.ts +67 -0
  271. package/dist/mcp/manager.js +328 -0
  272. package/dist/mcp/name.d.ts +26 -0
  273. package/dist/mcp/name.js +40 -0
  274. package/dist/mcp/transports.d.ts +54 -0
  275. package/dist/mcp/transports.js +267 -0
  276. package/dist/mcp/types.d.ts +111 -0
  277. package/dist/mcp/types.js +14 -0
  278. package/dist/memory/db.d.ts +65 -0
  279. package/dist/memory/db.js +332 -0
  280. package/dist/memory/index.d.ts +10 -0
  281. package/dist/memory/index.js +10 -0
  282. package/dist/memory/paths.d.ts +18 -0
  283. package/dist/memory/paths.js +38 -0
  284. package/dist/memory/phase1.d.ts +23 -0
  285. package/dist/memory/phase1.js +292 -0
  286. package/dist/memory/phase2.d.ts +19 -0
  287. package/dist/memory/phase2.js +119 -0
  288. package/dist/memory/prompts.d.ts +19 -0
  289. package/dist/memory/prompts.js +104 -0
  290. package/dist/memory/reset.d.ts +1 -0
  291. package/dist/memory/reset.js +13 -0
  292. package/dist/memory/session-policy.d.ts +25 -0
  293. package/dist/memory/session-policy.js +55 -0
  294. package/dist/memory/start.d.ts +24 -0
  295. package/dist/memory/start.js +50 -0
  296. package/dist/memory/storage.d.ts +11 -0
  297. package/dist/memory/storage.js +95 -0
  298. package/dist/memory/store.d.ts +43 -0
  299. package/dist/memory/store.js +193 -0
  300. package/dist/memory/usage.d.ts +1 -0
  301. package/dist/memory/usage.js +38 -0
  302. package/dist/model-catalog.d.ts +65 -0
  303. package/dist/model-catalog.js +399 -0
  304. package/dist/model-config.d.ts +40 -0
  305. package/dist/model-config.js +74 -0
  306. package/dist/model-data.generated.d.ts +16 -0
  307. package/dist/model-data.generated.js +275 -0
  308. package/dist/model-pricing.d.ts +54 -0
  309. package/dist/model-pricing.js +256 -0
  310. package/dist/model-selection.d.ts +7 -0
  311. package/dist/model-selection.js +18 -0
  312. package/dist/network/chatgpt-transport.d.ts +16 -0
  313. package/dist/network/chatgpt-transport.js +232 -0
  314. package/dist/network/errors.d.ts +47 -0
  315. package/dist/network/errors.js +48 -0
  316. package/dist/network/provider-transport-log.d.ts +12 -0
  317. package/dist/network/provider-transport-log.js +32 -0
  318. package/dist/network/provider-transport.d.ts +37 -0
  319. package/dist/network/provider-transport.js +256 -0
  320. package/dist/network/retry.d.ts +34 -0
  321. package/dist/network/retry.js +97 -0
  322. package/dist/network/system-proxy.d.ts +18 -0
  323. package/dist/network/system-proxy.js +175 -0
  324. package/dist/oauth/grok.d.ts +26 -0
  325. package/dist/oauth/grok.js +251 -0
  326. package/dist/oauth/index.d.ts +4 -0
  327. package/dist/oauth/index.js +3 -0
  328. package/dist/oauth/openai-codex.d.ts +15 -0
  329. package/dist/oauth/openai-codex.js +193 -0
  330. package/dist/oauth/refresh-control.d.ts +12 -0
  331. package/dist/oauth/refresh-control.js +42 -0
  332. package/dist/oauth/storage.d.ts +88 -0
  333. package/dist/oauth/storage.js +357 -0
  334. package/dist/oauth/types.d.ts +15 -0
  335. package/dist/oauth/types.js +1 -0
  336. package/dist/orchestrator/default-hooks.d.ts +22 -0
  337. package/dist/orchestrator/default-hooks.js +127 -0
  338. package/dist/orchestrator/hooks.d.ts +69 -0
  339. package/dist/orchestrator/hooks.js +52 -0
  340. package/dist/permission/mode.d.ts +19 -0
  341. package/dist/permission/mode.js +16 -0
  342. package/dist/permissions/rule.d.ts +42 -0
  343. package/dist/permissions/rule.js +293 -0
  344. package/dist/permissions/settings.d.ts +97 -0
  345. package/dist/permissions/settings.js +289 -0
  346. package/dist/permissions/shell-command.d.ts +36 -0
  347. package/dist/permissions/shell-command.js +226 -0
  348. package/dist/permissions/trust-prompt.d.ts +10 -0
  349. package/dist/permissions/trust-prompt.js +34 -0
  350. package/dist/permissions/trust.d.ts +26 -0
  351. package/dist/permissions/trust.js +113 -0
  352. package/dist/permissions/types.d.ts +61 -0
  353. package/dist/permissions/types.js +14 -0
  354. package/dist/print-output.d.ts +98 -0
  355. package/dist/print-output.js +117 -0
  356. package/dist/prompt/compose.d.ts +19 -0
  357. package/dist/prompt/compose.js +110 -0
  358. package/dist/prompt/delegation.d.ts +14 -0
  359. package/dist/prompt/delegation.js +100 -0
  360. package/dist/prompt/environment.d.ts +12 -0
  361. package/dist/prompt/environment.js +55 -0
  362. package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
  363. package/dist/prompt/provider-prompts/anthropic.js +5 -0
  364. package/dist/prompt/provider-prompts/codex.d.ts +1 -0
  365. package/dist/prompt/provider-prompts/codex.js +5 -0
  366. package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
  367. package/dist/prompt/provider-prompts/deepseek.js +7 -0
  368. package/dist/prompt/provider-prompts/default.d.ts +1 -0
  369. package/dist/prompt/provider-prompts/default.js +6 -0
  370. package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
  371. package/dist/prompt/provider-prompts/gemini.js +5 -0
  372. package/dist/prompt/provider-prompts/glm.d.ts +1 -0
  373. package/dist/prompt/provider-prompts/glm.js +7 -0
  374. package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
  375. package/dist/prompt/provider-prompts/gpt.js +5 -0
  376. package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
  377. package/dist/prompt/provider-prompts/kimi.js +8 -0
  378. package/dist/prompt/reminders.d.ts +33 -0
  379. package/dist/prompt/reminders.js +135 -0
  380. package/dist/prompt/routing.d.ts +14 -0
  381. package/dist/prompt/routing.js +71 -0
  382. package/dist/prompt/runtime.d.ts +12 -0
  383. package/dist/prompt/runtime.js +40 -0
  384. package/dist/prompt/task-reminders.d.ts +12 -0
  385. package/dist/prompt/task-reminders.js +64 -0
  386. package/dist/provider-ai-sdk.d.ts +59 -0
  387. package/dist/provider-ai-sdk.js +584 -0
  388. package/dist/provider-anthropic.d.ts +126 -0
  389. package/dist/provider-anthropic.js +940 -0
  390. package/dist/provider-ark-responses.d.ts +29 -0
  391. package/dist/provider-ark-responses.js +518 -0
  392. package/dist/provider-artifacts.d.ts +7 -0
  393. package/dist/provider-artifacts.js +60 -0
  394. package/dist/provider-error-record.d.ts +43 -0
  395. package/dist/provider-error-record.js +213 -0
  396. package/dist/provider-grok.d.ts +50 -0
  397. package/dist/provider-grok.js +110 -0
  398. package/dist/provider-model-policy.d.ts +16 -0
  399. package/dist/provider-model-policy.js +33 -0
  400. package/dist/provider-openai-codex.d.ts +66 -0
  401. package/dist/provider-openai-codex.js +781 -0
  402. package/dist/provider-registry.d.ts +197 -0
  403. package/dist/provider-registry.js +1194 -0
  404. package/dist/provider-transform.d.ts +12 -0
  405. package/dist/provider-transform.js +293 -0
  406. package/dist/provider-zen-errors.d.ts +9 -0
  407. package/dist/provider-zen-errors.js +36 -0
  408. package/dist/provider.d.ts +85 -0
  409. package/dist/provider.js +925 -0
  410. package/dist/question/controller.d.ts +22 -0
  411. package/dist/question/controller.js +98 -0
  412. package/dist/question/index.d.ts +2 -0
  413. package/dist/question/index.js +2 -0
  414. package/dist/question/normalize.d.ts +6 -0
  415. package/dist/question/normalize.js +11 -0
  416. package/dist/question/types.d.ts +42 -0
  417. package/dist/question/types.js +6 -0
  418. package/dist/reasoning-debug.d.ts +7 -0
  419. package/dist/reasoning-debug.js +33 -0
  420. package/dist/rewind.d.ts +20 -0
  421. package/dist/rewind.js +29 -0
  422. package/dist/sdk/index.d.ts +319 -0
  423. package/dist/sdk/index.js +1068 -0
  424. package/dist/sdk/replay-event-log.d.ts +26 -0
  425. package/dist/sdk/replay-event-log.js +122 -0
  426. package/dist/sdk/session-turn-coordinator.d.ts +45 -0
  427. package/dist/sdk/session-turn-coordinator.js +227 -0
  428. package/dist/session-context-fence.d.ts +16 -0
  429. package/dist/session-context-fence.js +26 -0
  430. package/dist/session-log.d.ts +25 -0
  431. package/dist/session-log.js +444 -0
  432. package/dist/session-title.d.ts +16 -0
  433. package/dist/session-title.js +131 -0
  434. package/dist/session-types.d.ts +86 -0
  435. package/dist/session-types.js +1 -0
  436. package/dist/session.d.ts +135 -0
  437. package/dist/session.js +664 -0
  438. package/dist/skills/discovery.d.ts +12 -0
  439. package/dist/skills/discovery.js +166 -0
  440. package/dist/skills/format.d.ts +32 -0
  441. package/dist/skills/format.js +68 -0
  442. package/dist/skills/frontmatter.d.ts +5 -0
  443. package/dist/skills/frontmatter.js +60 -0
  444. package/dist/skills/invocation.d.ts +8 -0
  445. package/dist/skills/invocation.js +33 -0
  446. package/dist/skills/registry.d.ts +28 -0
  447. package/dist/skills/registry.js +79 -0
  448. package/dist/skills/types.d.ts +34 -0
  449. package/dist/skills/types.js +1 -0
  450. package/dist/slash-commands/commands.d.ts +10 -0
  451. package/dist/slash-commands/commands.js +1251 -0
  452. package/dist/slash-commands/feishu.d.ts +17 -0
  453. package/dist/slash-commands/feishu.js +400 -0
  454. package/dist/slash-commands/index.d.ts +4 -0
  455. package/dist/slash-commands/index.js +8 -0
  456. package/dist/slash-commands/registry.d.ts +32 -0
  457. package/dist/slash-commands/registry.js +73 -0
  458. package/dist/slash-commands/types.d.ts +109 -0
  459. package/dist/slash-commands/types.js +1 -0
  460. package/dist/slash-commands/unified.d.ts +38 -0
  461. package/dist/slash-commands/unified.js +38 -0
  462. package/dist/stats/usage.d.ts +59 -0
  463. package/dist/stats/usage.js +485 -0
  464. package/dist/stream-json.d.ts +13 -0
  465. package/dist/stream-json.js +74 -0
  466. package/dist/system-prompt.d.ts +40 -0
  467. package/dist/system-prompt.js +7 -0
  468. package/dist/tasks/manager.d.ts +145 -0
  469. package/dist/tasks/manager.js +634 -0
  470. package/dist/tasks/process-utils.d.ts +10 -0
  471. package/dist/tasks/process-utils.js +49 -0
  472. package/dist/tasks/promotion.d.ts +17 -0
  473. package/dist/tasks/promotion.js +30 -0
  474. package/dist/tasks/wake.d.ts +53 -0
  475. package/dist/tasks/wake.js +125 -0
  476. package/dist/text-display.d.ts +3 -0
  477. package/dist/text-display.js +25 -0
  478. package/dist/tools/agent-lifecycle.d.ts +30 -0
  479. package/dist/tools/agent-lifecycle.js +790 -0
  480. package/dist/tools/bash.d.ts +23 -0
  481. package/dist/tools/bash.js +443 -0
  482. package/dist/tools/child-tools.d.ts +41 -0
  483. package/dist/tools/child-tools.js +122 -0
  484. package/dist/tools/edit-apply.d.ts +25 -0
  485. package/dist/tools/edit-apply.js +274 -0
  486. package/dist/tools/edit.d.ts +18 -0
  487. package/dist/tools/edit.js +200 -0
  488. package/dist/tools/exa-mcp.d.ts +3 -0
  489. package/dist/tools/exa-mcp.js +74 -0
  490. package/dist/tools/exit-plan-mode.d.ts +22 -0
  491. package/dist/tools/exit-plan-mode.js +79 -0
  492. package/dist/tools/file-mutation-queue.d.ts +2 -0
  493. package/dist/tools/file-mutation-queue.js +43 -0
  494. package/dist/tools/file-state.d.ts +48 -0
  495. package/dist/tools/file-state.js +76 -0
  496. package/dist/tools/grep.d.ts +9 -0
  497. package/dist/tools/grep.js +387 -0
  498. package/dist/tools/index.d.ts +63 -0
  499. package/dist/tools/index.js +76 -0
  500. package/dist/tools/ls.d.ts +3 -0
  501. package/dist/tools/ls.js +85 -0
  502. package/dist/tools/lsp.d.ts +4 -0
  503. package/dist/tools/lsp.js +95 -0
  504. package/dist/tools/memory.d.ts +8 -0
  505. package/dist/tools/memory.js +77 -0
  506. package/dist/tools/path-utils.d.ts +8 -0
  507. package/dist/tools/path-utils.js +53 -0
  508. package/dist/tools/prompt-metadata.d.ts +3 -0
  509. package/dist/tools/prompt-metadata.js +17 -0
  510. package/dist/tools/question.d.ts +3 -0
  511. package/dist/tools/question.js +177 -0
  512. package/dist/tools/read.d.ts +8 -0
  513. package/dist/tools/read.js +309 -0
  514. package/dist/tools/sensitive-paths.d.ts +11 -0
  515. package/dist/tools/sensitive-paths.js +106 -0
  516. package/dist/tools/server-manager.d.ts +6 -0
  517. package/dist/tools/server-manager.js +6 -0
  518. package/dist/tools/server.d.ts +6 -0
  519. package/dist/tools/server.js +249 -0
  520. package/dist/tools/skill-search.d.ts +10 -0
  521. package/dist/tools/skill-search.js +134 -0
  522. package/dist/tools/skill.d.ts +7 -0
  523. package/dist/tools/skill.js +81 -0
  524. package/dist/tools/task-tools.d.ts +10 -0
  525. package/dist/tools/task-tools.js +133 -0
  526. package/dist/tools/tool-search.d.ts +23 -0
  527. package/dist/tools/tool-search.js +145 -0
  528. package/dist/tools/web-fetch.d.ts +6 -0
  529. package/dist/tools/web-fetch.js +76 -0
  530. package/dist/tools/web-search.d.ts +5 -0
  531. package/dist/tools/web-search.js +50 -0
  532. package/dist/tools/write.d.ts +10 -0
  533. package/dist/tools/write.js +155 -0
  534. package/dist/tui/animation-clock.d.ts +14 -0
  535. package/dist/tui/animation-clock.js +34 -0
  536. package/dist/tui/app.d.ts +176 -0
  537. package/dist/tui/app.js +1659 -0
  538. package/dist/tui/components/approval-dialog.d.ts +43 -0
  539. package/dist/tui/components/approval-dialog.js +212 -0
  540. package/dist/tui/components/bottom-sheet.d.ts +9 -0
  541. package/dist/tui/components/bottom-sheet.js +32 -0
  542. package/dist/tui/components/context-info.d.ts +54 -0
  543. package/dist/tui/components/context-info.js +388 -0
  544. package/dist/tui/components/feedback-dialog.d.ts +56 -0
  545. package/dist/tui/components/feedback-dialog.js +262 -0
  546. package/dist/tui/components/feishu-setup-dialog.d.ts +65 -0
  547. package/dist/tui/components/feishu-setup-dialog.js +332 -0
  548. package/dist/tui/components/image-preview.d.ts +32 -0
  549. package/dist/tui/components/image-preview.js +130 -0
  550. package/dist/tui/components/plan-dialog.d.ts +34 -0
  551. package/dist/tui/components/plan-dialog.js +196 -0
  552. package/dist/tui/components/provider-key-input.d.ts +18 -0
  553. package/dist/tui/components/provider-key-input.js +49 -0
  554. package/dist/tui/components/question-dialog.d.ts +25 -0
  555. package/dist/tui/components/question-dialog.js +263 -0
  556. package/dist/tui/components/responsive-transcript.d.ts +31 -0
  557. package/dist/tui/components/responsive-transcript.js +77 -0
  558. package/dist/tui/components/rewind-picker.d.ts +62 -0
  559. package/dist/tui/components/rewind-picker.js +317 -0
  560. package/dist/tui/components/session-picker.d.ts +49 -0
  561. package/dist/tui/components/session-picker.js +303 -0
  562. package/dist/tui/components/skills-panel.d.ts +63 -0
  563. package/dist/tui/components/skills-panel.js +582 -0
  564. package/dist/tui/components/stats-panel.d.ts +40 -0
  565. package/dist/tui/components/stats-panel.js +301 -0
  566. package/dist/tui/components/streaming-message.d.ts +69 -0
  567. package/dist/tui/components/streaming-message.js +398 -0
  568. package/dist/tui/components/subagent-inspector.d.ts +28 -0
  569. package/dist/tui/components/subagent-inspector.js +127 -0
  570. package/dist/tui/components/task-inspector.d.ts +26 -0
  571. package/dist/tui/components/task-inspector.js +70 -0
  572. package/dist/tui/components/tasks-pane.d.ts +126 -0
  573. package/dist/tui/components/tasks-pane.js +566 -0
  574. package/dist/tui/components/transcript.d.ts +103 -0
  575. package/dist/tui/components/transcript.js +858 -0
  576. package/dist/tui/components/welcome.d.ts +20 -0
  577. package/dist/tui/components/welcome.js +133 -0
  578. package/dist/tui/components/workflow-inspector.d.ts +30 -0
  579. package/dist/tui/components/workflow-inspector.js +136 -0
  580. package/dist/tui/composer-autocomplete.d.ts +76 -0
  581. package/dist/tui/composer-autocomplete.js +465 -0
  582. package/dist/tui/composer-style.d.ts +7 -0
  583. package/dist/tui/composer-style.js +29 -0
  584. package/dist/tui/controller/agent-event-reducer.d.ts +75 -0
  585. package/dist/tui/controller/agent-event-reducer.js +239 -0
  586. package/dist/tui/controller/composer-controller.d.ts +95 -0
  587. package/dist/tui/controller/composer-controller.js +502 -0
  588. package/dist/tui/controller/controller.d.ts +199 -0
  589. package/dist/tui/controller/controller.js +1034 -0
  590. package/dist/tui/controller/effects.d.ts +71 -0
  591. package/dist/tui/controller/effects.js +1 -0
  592. package/dist/tui/controller/goal-runtime-controller.d.ts +47 -0
  593. package/dist/tui/controller/goal-runtime-controller.js +202 -0
  594. package/dist/tui/controller/input-queue-machine.d.ts +71 -0
  595. package/dist/tui/controller/input-queue-machine.js +121 -0
  596. package/dist/tui/controller/intents.d.ts +56 -0
  597. package/dist/tui/controller/intents.js +6 -0
  598. package/dist/tui/controller/overlay-controller.d.ts +64 -0
  599. package/dist/tui/controller/overlay-controller.js +129 -0
  600. package/dist/tui/controller/ports.d.ts +57 -0
  601. package/dist/tui/controller/ports.js +9 -0
  602. package/dist/tui/controller/session-transition.d.ts +54 -0
  603. package/dist/tui/controller/session-transition.js +68 -0
  604. package/dist/tui/controller/state.d.ts +14 -0
  605. package/dist/tui/controller/state.js +36 -0
  606. package/dist/tui/controller/task-runtime-controller.d.ts +66 -0
  607. package/dist/tui/controller/task-runtime-controller.js +158 -0
  608. package/dist/tui/detect-theme.d.ts +4 -0
  609. package/dist/tui/detect-theme.js +116 -0
  610. package/dist/tui/display-history.d.ts +64 -0
  611. package/dist/tui/display-history.js +283 -0
  612. package/dist/tui/display-sanitizer.d.ts +3 -0
  613. package/dist/tui/display-sanitizer.js +38 -0
  614. package/dist/tui/edit-diff.d.ts +11 -0
  615. package/dist/tui/edit-diff.js +57 -0
  616. package/dist/tui/file-mentions.d.ts +29 -0
  617. package/dist/tui/file-mentions.js +174 -0
  618. package/dist/tui/footer.d.ts +37 -0
  619. package/dist/tui/footer.js +80 -0
  620. package/dist/tui/formatting/edit-diff.d.ts +11 -0
  621. package/dist/tui/formatting/edit-diff.js +52 -0
  622. package/dist/tui/formatting/summary.d.ts +43 -0
  623. package/dist/tui/formatting/summary.js +150 -0
  624. package/dist/tui/fullscreen.d.ts +48 -0
  625. package/dist/tui/fullscreen.js +256 -0
  626. package/dist/tui/image-display.d.ts +23 -0
  627. package/dist/tui/image-display.js +92 -0
  628. package/dist/tui/image-paste.d.ts +113 -0
  629. package/dist/tui/image-paste.js +565 -0
  630. package/dist/tui/input-history.d.ts +47 -0
  631. package/dist/tui/input-history.js +250 -0
  632. package/dist/tui/markdown-style.d.ts +5 -0
  633. package/dist/tui/markdown-style.js +23 -0
  634. package/dist/tui/model/composer-buffer.d.ts +55 -0
  635. package/dist/tui/model/composer-buffer.js +317 -0
  636. package/dist/tui/model/composer-types.d.ts +17 -0
  637. package/dist/tui/model/composer-types.js +1 -0
  638. package/dist/tui/model/display-history.d.ts +78 -0
  639. package/dist/tui/model/display-history.js +124 -0
  640. package/dist/tui/model/display-reconstruct.d.ts +3 -0
  641. package/dist/tui/model/display-reconstruct.js +130 -0
  642. package/dist/tui/model/file-mentions.d.ts +31 -0
  643. package/dist/tui/model/file-mentions.js +178 -0
  644. package/dist/tui/model/image-attachment.d.ts +15 -0
  645. package/dist/tui/model/image-attachment.js +1 -0
  646. package/dist/tui/model/image-paste.d.ts +119 -0
  647. package/dist/tui/model/image-paste.js +610 -0
  648. package/dist/tui/model/input-history.d.ts +1 -0
  649. package/dist/tui/model/input-history.js +1 -0
  650. package/dist/tui/model/input-queue.d.ts +12 -0
  651. package/dist/tui/model/input-queue.js +17 -0
  652. package/dist/tui/model/key-events.d.ts +9 -0
  653. package/dist/tui/model/key-events.js +8 -0
  654. package/dist/tui/model/paste-operation-tracker.d.ts +13 -0
  655. package/dist/tui/model/paste-operation-tracker.js +25 -0
  656. package/dist/tui/model/recent-activity.d.ts +8 -0
  657. package/dist/tui/model/recent-activity.js +71 -0
  658. package/dist/tui/model/subagent-view.d.ts +87 -0
  659. package/dist/tui/model/subagent-view.js +336 -0
  660. package/dist/tui/model/submit-dedupe.d.ts +5 -0
  661. package/dist/tui/model/submit-dedupe.js +25 -0
  662. package/dist/tui/model/sync-output.d.ts +17 -0
  663. package/dist/tui/model/sync-output.js +35 -0
  664. package/dist/tui/model/task-lifecycle.d.ts +57 -0
  665. package/dist/tui/model/task-lifecycle.js +124 -0
  666. package/dist/tui/model/terminal-env.d.ts +15 -0
  667. package/dist/tui/model/terminal-env.js +22 -0
  668. package/dist/tui/model/terminal-mouse.d.ts +28 -0
  669. package/dist/tui/model/terminal-mouse.js +82 -0
  670. package/dist/tui/model/theme-style.d.ts +10 -0
  671. package/dist/tui/model/theme-style.js +79 -0
  672. package/dist/tui/model/theme.d.ts +72 -0
  673. package/dist/tui/model/theme.js +121 -0
  674. package/dist/tui/model/trace-groups.d.ts +45 -0
  675. package/dist/tui/model/trace-groups.js +558 -0
  676. package/dist/tui/model/trace-interaction.d.ts +63 -0
  677. package/dist/tui/model/trace-interaction.js +87 -0
  678. package/dist/tui/model/width.d.ts +18 -0
  679. package/dist/tui/model/width.js +130 -0
  680. package/dist/tui/model-picker-data.d.ts +9 -0
  681. package/dist/tui/model-picker-data.js +39 -0
  682. package/dist/tui/model-switch.d.ts +49 -0
  683. package/dist/tui/model-switch.js +67 -0
  684. package/dist/tui/paste-placeholder.d.ts +11 -0
  685. package/dist/tui/paste-placeholder.js +52 -0
  686. package/dist/tui/recent-activity.d.ts +8 -0
  687. package/dist/tui/recent-activity.js +71 -0
  688. package/dist/tui/run-session-picker.d.ts +9 -0
  689. package/dist/tui/run-session-picker.js +64 -0
  690. package/dist/tui/run.d.ts +72 -0
  691. package/dist/tui/run.js +139 -0
  692. package/dist/tui/session-display.d.ts +6 -0
  693. package/dist/tui/session-display.js +12 -0
  694. package/dist/tui/session-picker-data.d.ts +18 -0
  695. package/dist/tui/session-picker-data.js +21 -0
  696. package/dist/tui/terminal/multiplexer.d.ts +5 -0
  697. package/dist/tui/terminal/multiplexer.js +14 -0
  698. package/dist/tui/testing/fake-agent.d.ts +24 -0
  699. package/dist/tui/testing/fake-agent.js +22 -0
  700. package/dist/tui/testing/spy-host.d.ts +21 -0
  701. package/dist/tui/testing/spy-host.js +55 -0
  702. package/dist/tui/trace-groups.d.ts +43 -0
  703. package/dist/tui/trace-groups.js +554 -0
  704. package/dist/tui/wordmark.d.ts +18 -0
  705. package/dist/tui/wordmark.js +208 -0
  706. package/dist/types.d.ts +503 -0
  707. package/dist/types.js +4 -0
  708. package/dist/update/index.d.ts +61 -0
  709. package/dist/update/index.js +267 -0
  710. package/dist/variant/thinking-level.d.ts +5 -0
  711. package/dist/variant/thinking-level.js +26 -0
  712. package/dist/variant/variant-resolver.d.ts +28 -0
  713. package/dist/variant/variant-resolver.js +62 -0
  714. package/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
  715. package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
  716. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
  717. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
  718. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
  719. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
  720. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
  721. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
  722. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
  723. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
  724. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
  725. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
  726. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
  727. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
  728. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
  729. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
  730. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
  731. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
  732. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
  733. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
  734. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
  735. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
  736. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
  737. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
  738. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
  739. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
  740. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
  741. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
  742. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
  743. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
  744. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
  745. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
  746. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
  747. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
  748. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
  749. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
  750. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
  751. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
  752. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
  753. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
  754. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
  755. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
  756. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
  757. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
  758. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
  759. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
  760. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
  761. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
  762. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
  763. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
  764. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
  765. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
  766. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
  767. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
  768. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
  769. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
  770. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
  771. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
  772. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
  773. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
  774. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
  775. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
  776. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
  777. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
  778. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
  779. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
  780. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
  781. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
  782. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
  783. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
  784. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
  785. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
  786. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
  787. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
  788. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
  789. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
  790. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
  791. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
  792. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
  793. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
  794. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
  795. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
  796. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
  797. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
  798. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
  799. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
  800. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
  801. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
  802. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
  803. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
  804. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
  805. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
  806. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
  807. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
  808. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
  809. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
  810. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
  811. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
  812. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
  813. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
  814. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
  815. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
  816. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
  817. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
  818. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
  819. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
  820. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
  821. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
  822. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
  823. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
  824. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
  825. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
  826. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
  827. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
  828. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
  829. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
  830. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
  831. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
  832. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
  833. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
  834. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
  835. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
  836. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
  837. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
  838. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
  839. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
  840. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
  841. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
  842. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
  843. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
  844. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
  845. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
  846. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
  847. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
  848. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
  849. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
  850. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
  851. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
  852. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +141 -0
  853. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
  854. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1340 -0
  855. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
  856. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
  857. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
  858. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
  859. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
  860. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +340 -0
  861. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
  862. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1020 -0
  863. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
  864. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
  865. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
  866. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
  867. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
  868. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
  869. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
  870. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
  871. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
  872. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
  873. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
  874. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
  875. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
  876. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
  877. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
  878. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
  879. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
  880. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
  881. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
  882. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  883. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  884. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
  885. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
  886. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
  887. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  888. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  889. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
  890. package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
  891. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  892. package/node_modules/get-east-asian-width/index.js +30 -0
  893. package/node_modules/get-east-asian-width/license +9 -0
  894. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  895. package/node_modules/get-east-asian-width/lookup.js +138 -0
  896. package/node_modules/get-east-asian-width/package.json +71 -0
  897. package/node_modules/get-east-asian-width/readme.md +65 -0
  898. package/node_modules/get-east-asian-width/utilities.js +24 -0
  899. package/node_modules/marked/LICENSE +44 -0
  900. package/node_modules/marked/README.md +115 -0
  901. package/node_modules/marked/bin/main.js +284 -0
  902. package/node_modules/marked/bin/marked.js +16 -0
  903. package/node_modules/marked/lib/marked.d.ts +760 -0
  904. package/node_modules/marked/lib/marked.esm.js +77 -0
  905. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  906. package/node_modules/marked/lib/marked.umd.js +79 -0
  907. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  908. package/node_modules/marked/man/marked.1 +113 -0
  909. package/node_modules/marked/man/marked.1.md +93 -0
  910. package/node_modules/marked/package.json +103 -0
  911. package/package.json +96 -17
  912. package/bin/bubble.js +0 -39
package/dist/agent.js ADDED
@@ -0,0 +1,2210 @@
1
+ import { toolImageObservation } from "./context/tool-images.js";
2
+ /**
3
+ * Agent - The core decision loop.
4
+ * It maintains message state, calls the LLM, executes tools, and auto-continues.
5
+ */
6
+ import { compactCurrentTurnToolGroups, compactMessages, isCompactionSummaryMessage, splitLeadingContext } from "./context/compact.js";
7
+ import { createContextCheckpoint, checkpointMessages, hasCompleteToolGroups } from "./context/checkpoint.js";
8
+ import { randomUUID } from "node:crypto";
9
+ import { estimateToolDefinitionsTokens, getContextBudget, getMaxInputTokens, } from "./context/budget.js";
10
+ import { buildContextUsageSnapshot } from "./context/usage.js";
11
+ import { isContextOverflowError } from "./context/overflow.js";
12
+ import { computeRetryDelayMs, isProviderStreamInterruption, providerStreamRetryLimit, sleepBeforeRetry, } from "./network/retry.js";
13
+ import { projectMessages } from "./context/projector.js";
14
+ import { aggressivePruneMessages, pruneMessages, markStableCurrentToolResultsForCache, markToolResultCacheStable } from "./context/prune.js";
15
+ import { normalizeToolResultForModel, truncateToolOutputForModel } from "./context/tool-output-truncate.js";
16
+ import { buildDeferredToolsReminder, buildToolFreezeReminder, isPermissionModeReminder, reminderForMode } from "./prompt/reminders.js";
17
+ import { HookBus } from "./orchestrator/hooks.js";
18
+ import { normalizeHookInput, truncateHookText, } from "./hooks/index.js";
19
+ import { createDefaultHooks } from "./orchestrator/default-hooks.js";
20
+ import { buildTaskLifecycleReminder } from "./agent/task-lifecycle-reminder.js";
21
+ import { DEFAULT_AGENT_ROUTING, sanitizeAgentRouting } from "./agent/routing-catalog.js";
22
+ import { SubagentRouter } from "./agent/subagent/router.js";
23
+ import { normalizeWaitTimeout, SubagentRuntime } from "./agent/subagent/runtime.js";
24
+ import { WorkflowLedger } from "./agent/workflow/runs.js";
25
+ import { EMPTY_ASSISTANT_FALLBACK, throwIfAborted, isAbortLikeError, isAbortError } from "./agent/abort-errors.js";
26
+ import { appendProviderContentBlock, buildProviderRequestFingerprint } from "./agent/provider-fingerprint.js";
27
+ import { countUserTurns, estimateResidentChars, estimateToolPayloadChars, getCurrentHeapUsed, RESIDENT_HISTORY_HEAP_HARD_LIMIT, RESIDENT_HISTORY_KEEP_RECENT_TURNS } from "./agent/resident-history.js";
28
+ import { cancelledToolResult, createModelInterruptedMessage, findMissingRequiredArgs, isSubagentLifecycleTool, lastProviderMessage, shouldAppendModelInterruptedBoundary } from "./agent/transcript-helpers.js";
29
+ import { createUpdateQueue } from "./agent/update-queue.js";
30
+ export { createUpdateQueue } from "./agent/update-queue.js";
31
+ import { isHiddenToolResult } from "./agent/tool-visibility.js";
32
+ import { createStreamingInternalReminderSanitizer, sanitizeAssistantProviderMetadata, sanitizeInternalReasoningText, sanitizeInternalReminderBlocks, } from "./agent/internal-reminder-sanitizer.js";
33
+ import { debugReasoningStream, summarizeDebugText } from "./reasoning-debug.js";
34
+ import { buildToolPromptOptions } from "./tools/prompt-metadata.js";
35
+ import { stopAutoServersForSession } from "./tools/server-manager.js";
36
+ import { createSanitizedProviderError, } from "./provider-error-record.js";
37
+ import { summarizeAgentEventForTrace, summarizeTraceError, summarizeTraceMessage, summarizeTraceToolResult, summarizeTraceValue, traceEvent, } from "./debug-trace.js";
38
+ const MAX_CONSECUTIVE_OVERFLOW_RECOVERIES = 2;
39
+ const MAX_EMPTY_ASSISTANT_RECOVERIES = 1;
40
+ const EMPTY_ASSISTANT_RECOVERY_REMINDER = "The previous model response contained no user-visible assistant content and no tool calls. " +
41
+ "Respond now with a concise, user-visible answer, or call an available tool if more work is required. " +
42
+ "Do not put the final answer only in hidden reasoning.";
43
+ class LocalContextPreflightError extends Error {
44
+ constructor(estimatedTokens, maxInputTokens) {
45
+ super(`Local request preflight: estimated prompt length ${estimatedTokens} tokens exceeds maximum prompt length ${maxInputTokens}.`);
46
+ this.name = "LocalContextPreflightError";
47
+ }
48
+ }
49
+ export { AgentAbortError, SubagentAbortError } from "./agent/abort-errors.js";
50
+ // Model-facing interruption boundary. Persisted into the transcript so the
51
+ // next turn sees an explicit stop instead of a dangling request — but it must
52
+ // never render in the UI as if the assistant said it (the TUIs strip it and
53
+ // show their own interrupt indicator instead).
54
+ export const INTERRUPTED_ASSISTANT_CONTENT = "Interrupted by user. The prior request was stopped and should not be resumed unless the user asks.";
55
+ function agentEventFromHookProgress(event) {
56
+ const source = `${event.source.scope}:${event.source.index}`;
57
+ if (event.type === "hook_start") {
58
+ return {
59
+ type: "hook_start",
60
+ eventName: event.eventName,
61
+ hookId: event.hookId,
62
+ source,
63
+ };
64
+ }
65
+ if (event.type === "hook_end") {
66
+ return {
67
+ type: "hook_end",
68
+ eventName: event.eventName,
69
+ hookId: event.hookId,
70
+ source,
71
+ elapsedMs: event.elapsedMs ?? 0,
72
+ decision: event.decision ?? "allow",
73
+ reason: event.reason,
74
+ };
75
+ }
76
+ return {
77
+ type: "hook_error",
78
+ eventName: event.eventName,
79
+ hookId: event.hookId,
80
+ source,
81
+ elapsedMs: event.elapsedMs,
82
+ decision: event.decision,
83
+ reason: event.reason,
84
+ error: event.error ?? "Hook failed.",
85
+ };
86
+ }
87
+ export class Agent {
88
+ /**
89
+ * Backing store for the public `messages` accessor. All whole-array
90
+ * rewrites (clear/compact/rewind/session switch/subagent fork) go through
91
+ * the setter, which notifies `onContextChanged` subscribers — that push
92
+ * channel is what keeps derived UI (e.g. the footer usage readout) in sync
93
+ * without per-path refresh calls that are easy to forget.
94
+ */
95
+ _messages = [];
96
+ contextChangedListeners = new Set();
97
+ notifyingContextChange = false;
98
+ get messages() {
99
+ return this._messages;
100
+ }
101
+ set messages(value) {
102
+ this._messages = value;
103
+ this.notifyContextChanged();
104
+ }
105
+ provider;
106
+ sessionID;
107
+ /**
108
+ * Bridge to the unified process manager's background tasks, wired by the
109
+ * host (main.ts) when the host supports them (background-tasks design
110
+ * §2.3a). list() is pre-filtered to the agent's bound session.
111
+ */
112
+ backgroundTasks;
113
+ lastTaskReminderVersion = -1;
114
+ /**
115
+ * Once-per-session latch for the large-task delegation nudge
116
+ * (large-task-delegation design §2): goal-loop continuations rebuild
117
+ * TurnHookState every run, so a per-turn latch would nag on every turn.
118
+ * If the model read the nudge once and chose not to delegate, repeating
119
+ * it is noise.
120
+ */
121
+ _providerId;
122
+ _model;
123
+ tools = new Map();
124
+ unlockedDeferred = new Set();
125
+ temperature;
126
+ thinkingLevel;
127
+ _mode;
128
+ _modeVersion = 0;
129
+ onModeUpdate;
130
+ onCompactionApplied;
131
+ onContextCheckpoint;
132
+ getContextRevision;
133
+ lastCompactionId;
134
+ onMessageAppend;
135
+ onProviderError;
136
+ onToolResult;
137
+ hookDefinitions;
138
+ externalHooks;
139
+ agentRole;
140
+ subAgentId;
141
+ maxTurns;
142
+ taskBudget;
143
+ budgetLedger;
144
+ budgetSource;
145
+ skillSummaries;
146
+ skillCatalogChars;
147
+ memoryPrompt;
148
+ fileStateTracker;
149
+ agentCategories;
150
+ agentRouting;
151
+ /** Owns the routing decision chain, the §6 detector state and the §4 menu. */
152
+ router;
153
+ providerFactory;
154
+ /**
155
+ * Subagent thread lifecycle + workflow script execution: the store,
156
+ * scheduler, ChildRunner and their dispatch/ingestion plumbing.
157
+ */
158
+ subagents;
159
+ /**
160
+ * Background dynamic-workflow runs (option C Phase 4): run records, waiters,
161
+ * cancellation and the pending-delivery set. Execution stays on Agent.
162
+ */
163
+ workflowLedger;
164
+ subagentsConfig;
165
+ rateLimitPolicy;
166
+ lastInputTokens = null;
167
+ lastAnchorMessageCount = null;
168
+ // How often each compaction path actually rewrote history this run.
169
+ // Surfaced via getCompactionStats() for print-mode telemetry: without it,
170
+ // "did the model ever stop seeing the original instruction?" is
171
+ // unanswerable from the outside. `fired` counts successful compaction
172
+ // computations including ones whose rewrite was later rejected — a
173
+ // fired-but-never-written gap is the churn signal.
174
+ compactionStats = { resident: 0, subturn: 0, llm: 0, heuristic: 0, overflow: 0, fired: 0, droppedMessages: 0 };
175
+ contextEvents = [];
176
+ constructor(options) {
177
+ this.provider = options.provider;
178
+ this.sessionID = options.sessionID;
179
+ this._providerId = options.providerId ?? "";
180
+ this._model = options.model;
181
+ this.temperature = options.temperature ?? 0.2;
182
+ this.thinkingLevel = options.thinkingLevel ?? "off";
183
+ this._mode = options.mode ?? "default";
184
+ this.onMessageAppend = options.onMessageAppend;
185
+ this.onProviderError = options.onProviderError;
186
+ this.onToolResult = options.onToolResult;
187
+ this.onModeUpdate = options.onModeUpdate;
188
+ this.onCompactionApplied = options.onCompactionApplied;
189
+ this.onContextCheckpoint = options.onContextCheckpoint;
190
+ this.getContextRevision = options.getContextRevision;
191
+ this.hookDefinitions = options.hooks ?? [];
192
+ this.externalHooks = options.externalHooks;
193
+ this.agentRole = options.agentRole ?? "parent";
194
+ this.subAgentId = options.subAgentId;
195
+ this.maxTurns = options.maxTurns ?? options.steps;
196
+ this.taskBudget = options.taskBudget;
197
+ this.budgetLedger = options.budgetLedger;
198
+ this.budgetSource = options.budgetSource ?? { runId: this.sessionID ?? "agent" };
199
+ this.skillSummaries = options.skills ?? [];
200
+ this.skillCatalogChars = options.skillCatalogChars;
201
+ this.memoryPrompt = options.memoryPrompt;
202
+ this.fileStateTracker = options.fileStateTracker;
203
+ this.agentCategories = options.agentCategories ?? {};
204
+ this.agentRouting = options.agentRouting
205
+ ? sanitizeAgentRouting(options.agentRouting)
206
+ : { ...DEFAULT_AGENT_ROUTING };
207
+ // parentRoute is a thunk, not a snapshot: /model reassigns provider/model/
208
+ // thinking mid-session and children must inherit the CURRENT values.
209
+ this.router = new SubagentRouter({
210
+ parentRoute: () => ({
211
+ providerId: this.providerId,
212
+ model: this.apiModel,
213
+ thinkingLevel: this.thinkingLevel,
214
+ }),
215
+ categories: this.agentCategories,
216
+ routing: this.agentRouting,
217
+ snapshot: options.routingSnapshot,
218
+ routableModels: options.routableModels,
219
+ });
220
+ this.providerFactory = options.providerFactory;
221
+ this.workflowLedger = new WorkflowLedger({
222
+ execute: (cwd, opts) => this.subagents.executeWorkflow(cwd, opts),
223
+ });
224
+ this.subagentsConfig = options.subagents ?? {};
225
+ this.rateLimitPolicy = options.rateLimitPolicy;
226
+ const persistDir = this.deriveSubagentPersistDir();
227
+ // `self` so the parent adapter's getters read through to this agent.
228
+ const self = this;
229
+ this.subagents = new SubagentRuntime({
230
+ parent: {
231
+ // Live accessors, never snapshots — /model reassigns these mid-session.
232
+ get provider() { return self.provider; },
233
+ get providerId() { return self.providerId; },
234
+ get apiModel() { return self.apiModel; },
235
+ get thinkingLevel() { return self.thinkingLevel; },
236
+ get memoryPrompt() { return self.memoryPrompt; },
237
+ get providerFactory() { return self.providerFactory; },
238
+ // Unfiltered on purpose: profile admission and child tool selection
239
+ // must see deferred-but-registered tools (an explicit include in a
240
+ // profile is the author pre-unlocking them).
241
+ allTools: () => [...this.tools.values()],
242
+ getSkillSummaries: () => this.getSkillSummaries(),
243
+ getSkillCatalogChars: () => this.getSkillCatalogChars(),
244
+ createChild: (spec) => this.createChildAgent(spec),
245
+ recordProviderError: (error, context) => this.recordProviderError(error, context),
246
+ runExternalHook: (input, abortSignal) => this.runExternalHook(input, abortSignal),
247
+ },
248
+ router: this.router,
249
+ categories: this.agentCategories,
250
+ config: this.subagentsConfig,
251
+ persistDir,
252
+ });
253
+ if (options.systemPrompt) {
254
+ this._messages.push({ role: "system", content: options.systemPrompt });
255
+ }
256
+ for (const tool of options.tools) {
257
+ this.tools.set(tool.name, tool);
258
+ }
259
+ // If the agent boots in a non-default mode, inject the corresponding reminder so the
260
+ // model sees the active rules on its very first turn. Default mode needs no reminder.
261
+ if (this._mode !== "default") {
262
+ this.injectModeReminder();
263
+ }
264
+ // Advertise any deferred tools so the model knows they exist and how to
265
+ // reach them. Keeps the per-turn tool list small; schemas load on demand.
266
+ this.injectDeferredToolsReminder();
267
+ }
268
+ /**
269
+ * Re-inject the deferred-tools advertisement. Hosts that reassign
270
+ * agent.messages (session resume, Feishu conversation rebuild) drop the
271
+ * constructor-injected meta reminder — without re-injection the model has
272
+ * no way to discover deferred tools, since they are also filtered out of
273
+ * the prompt's tool list.
274
+ */
275
+ injectDeferredToolsReminder() {
276
+ const deferredNames = [...this.tools.values()]
277
+ .filter((t) => t.deferred)
278
+ .map((t) => t.name);
279
+ if (deferredNames.length > 0) {
280
+ this.injectSystemReminder(buildDeferredToolsReminder(deferredNames));
281
+ }
282
+ }
283
+ /**
284
+ * Rebinds the agent to a new session (TUI session switch). Keeps
285
+ * ownerSessionId on newly spawned background tasks and managed servers
286
+ * accurate (background-tasks design §2.2c).
287
+ */
288
+ setSessionID(sessionID) {
289
+ this.sessionID = sessionID;
290
+ // The TUI reuses one Agent across session switches, so the subagent
291
+ // persist directory must follow the session — otherwise children spawned
292
+ // after a switch land in the OLD session's directory and the new
293
+ // session's persisted children never load (known-defects #2).
294
+ this.subagents.repointPersistDir(this.deriveSubagentPersistDir());
295
+ }
296
+ /**
297
+ * Children persist next to the session file so a later process can resume
298
+ * them via send_input (design §7). An explicit subagents.persistDir config
299
+ * always wins over the sessionID derivation. Child agents themselves
300
+ * (agentRole "subagent") never persist children — no recursion exists.
301
+ */
302
+ deriveSubagentPersistDir() {
303
+ if (this.agentRole !== "parent")
304
+ return undefined;
305
+ return this.subagentsConfig.persistDir
306
+ ?? (this.sessionID?.endsWith(".jsonl") ? this.sessionID.replace(/\.jsonl$/, ".subagents") : undefined);
307
+ }
308
+ getSessionID() {
309
+ return this.sessionID;
310
+ }
311
+ /**
312
+ * Background-task reminder, state-change gated (design §2.3a): returns a
313
+ * reminder only when the owned task set changed since the last emission
314
+ * (started/finished/killed). The reminder channel is append-only, so
315
+ * emitting on every model call while tasks are merely alive would stack
316
+ * persistent duplicates that neither pruning nor compaction removes.
317
+ */
318
+ consumeBackgroundTaskReminder() {
319
+ if (!this.backgroundTasks)
320
+ return undefined;
321
+ const version = this.backgroundTasks.version();
322
+ if (version === this.lastTaskReminderVersion)
323
+ return undefined;
324
+ this.lastTaskReminderVersion = version;
325
+ return buildTaskLifecycleReminder({
326
+ tasks: this.backgroundTasks.list(),
327
+ outputTail: (id) => this.backgroundTasks?.outputTail(id),
328
+ toolsAvailable: this.unlockedDeferred?.has("task_output") ?? false,
329
+ });
330
+ }
331
+ async runExternalHook(request, abortSignal) {
332
+ const events = [];
333
+ if (!this.externalHooks) {
334
+ return {
335
+ result: {
336
+ eventName: request.eventName,
337
+ decision: "allow",
338
+ modelContext: [],
339
+ results: [],
340
+ diagnostics: [],
341
+ matched: 0,
342
+ },
343
+ events,
344
+ };
345
+ }
346
+ const result = await this.externalHooks.runEvent({
347
+ agentRole: this.agentRole,
348
+ subAgentId: this.subAgentId,
349
+ sessionId: this.sessionID,
350
+ ...request,
351
+ }, {
352
+ abortSignal,
353
+ onProgress: (event) => events.push(agentEventFromHookProgress(event)),
354
+ });
355
+ return { result, events };
356
+ }
357
+ injectHookModelContext(result) {
358
+ for (const context of result.modelContext) {
359
+ this.injectSystemReminder(`[Hook ${result.eventName}] ${context}`);
360
+ }
361
+ }
362
+ /** Whether a tool is registered on this agent (e.g. delegation tools on parents). */
363
+ hasToolAvailable(name) {
364
+ return this.tools.has(name);
365
+ }
366
+ /** Unlock a list of deferred tools so they're included in subsequent turns. */
367
+ unlockDeferredTools(names) {
368
+ for (const n of names) {
369
+ if (this.tools.has(n))
370
+ this.unlockedDeferred.add(n);
371
+ }
372
+ }
373
+ /** All deferred tools in this session (for tool_search to inspect). */
374
+ listDeferredTools() {
375
+ return [...this.tools.values()].filter((t) => t.deferred);
376
+ }
377
+ getSystemPromptToolOptions() {
378
+ return {
379
+ // Rebuilt prompts (model switch, /skills changes) keep the Skills catalog.
380
+ skills: this.skillSummaries,
381
+ skillCatalogChars: this.skillCatalogChars,
382
+ ...buildToolPromptOptions(this.getActiveToolEntries()),
383
+ // Rendered through the live accessor (design §1.5), so every host-
384
+ // triggered prompt rebuild picks up the current catalog.
385
+ modelRoutingPrompt: this.buildModelRoutingPromptSection(),
386
+ // Model switches rebuild the prompt from these options; without this the
387
+ // persistent memory (buildMemoryPrompt) is silently dropped on switch.
388
+ memoryPrompt: this.memoryPrompt,
389
+ };
390
+ }
391
+ getSkillSummaries() {
392
+ return this.skillSummaries.slice();
393
+ }
394
+ getSkillCatalogChars() {
395
+ return this.skillCatalogChars;
396
+ }
397
+ /** Refresh the live Skill catalog after /skills reloads or changes enablement; hosts then rebuild the system prompt. */
398
+ setSkillSummaries(skills) {
399
+ this.skillSummaries = skills.slice();
400
+ this.notifyContextChanged();
401
+ }
402
+ /** Current routing menu (design §4); undefined when no accessor is wired. */
403
+ buildModelRoutingPromptSection() {
404
+ return this.router.promptSection();
405
+ }
406
+ /**
407
+ * Routing menu rendered for a prospective parent route — used by model-
408
+ * switch transactions to build the NEXT prompt before mutating the agent
409
+ * (design §1.5).
410
+ */
411
+ renderModelRoutingPromptFor(parent) {
412
+ return this.router.promptSectionFor(parent);
413
+ }
414
+ /**
415
+ * Push subscription for derived-context consumers (TUI footer, hosts).
416
+ * Fires whenever resident context mutates in a way that changes a
417
+ * `getContextUsageSnapshot()` reading: whole-array message rewrites,
418
+ * appended messages, system-prompt replacement, or model/provider switches
419
+ * (window size / estimation basis). Returns an unsubscribe function.
420
+ */
421
+ onContextChanged(listener) {
422
+ this.contextChangedListeners.add(listener);
423
+ return () => {
424
+ this.contextChangedListeners.delete(listener);
425
+ };
426
+ }
427
+ notifyContextChanged() {
428
+ if (this.notifyingContextChange)
429
+ return;
430
+ this.notifyingContextChange = true;
431
+ try {
432
+ for (const listener of [...this.contextChangedListeners]) {
433
+ try {
434
+ listener();
435
+ }
436
+ catch (error) {
437
+ // A broken subscriber must never corrupt agent state mid-mutation,
438
+ // but leave a trace — otherwise a throwing consumer (e.g. the footer
439
+ // readout hitting a pathological message) fails silently forever.
440
+ traceEvent("context_listener_error", {
441
+ error: error instanceof Error ? error.message : String(error),
442
+ }, {
443
+ sessionFile: this.sessionID,
444
+ provider: this._providerId || "none",
445
+ model: this.apiModel || "none",
446
+ });
447
+ }
448
+ }
449
+ }
450
+ finally {
451
+ this.notifyingContextChange = false;
452
+ }
453
+ }
454
+ getContextUsageSnapshot() {
455
+ return buildContextUsageSnapshot({
456
+ providerId: this.providerId,
457
+ modelId: this.apiModel,
458
+ messages: this.messages,
459
+ toolEntries: this.getActiveToolEntries(),
460
+ deferredToolEntries: this.listDeferredTools(),
461
+ skills: this.skillSummaries,
462
+ });
463
+ }
464
+ resetContextUsageAnchor() {
465
+ this.lastInputTokens = null;
466
+ this.lastAnchorMessageCount = null;
467
+ this.fileStateTracker?.invalidateReadHistory();
468
+ }
469
+ /** Whether a given tool is deferred and not yet unlocked. */
470
+ isDeferredAndLocked(name) {
471
+ const tool = this.tools.get(name);
472
+ return !!tool?.deferred && !this.unlockedDeferred.has(name);
473
+ }
474
+ getActiveToolEntries() {
475
+ return [...this.tools.values()]
476
+ .filter((tool) => !tool.deferred || this.unlockedDeferred.has(tool.name));
477
+ }
478
+ injectSystemReminder(content) {
479
+ this.appendMessage({ role: "meta", kind: "system-reminder", content });
480
+ }
481
+ injectModeReminder() {
482
+ const reminder = reminderForMode(this._mode);
483
+ const last = this.messages.at(-1);
484
+ if (last?.role === "meta"
485
+ && last.kind === "system-reminder"
486
+ && last.content === reminder
487
+ && last.includeInLlm !== false) {
488
+ return;
489
+ }
490
+ this.retireStaleModeReminders();
491
+ this.injectSystemReminder(reminder);
492
+ }
493
+ /**
494
+ * Mode reminders are enter-announcements with no cancel semantics, so a stale
495
+ * one keeps describing a mode the session left long ago. Shift+Tab cycles
496
+ * through plan on the way to bypassPermissions, which used to leave a live
497
+ * "Plan mode is now ACTIVE" (plus its "call exit_plan_mode" rule) in history
498
+ * for the rest of the session — the model then planned instead of building,
499
+ * and hunted for a tool that is filtered out outside plan mode.
500
+ *
501
+ * Hidden rather than spliced: keeping the array shape intact leaves message
502
+ * indices (usage anchors, compaction bookkeeping) valid, and the transcript
503
+ * still shows what happened. Cache cost is one invalidated prefix per real
504
+ * mode switch — the no-op path above keeps repeat injections free.
505
+ */
506
+ retireStaleModeReminders() {
507
+ for (const message of this.messages) {
508
+ if (message.role !== "meta")
509
+ continue;
510
+ if (message.kind !== "system-reminder")
511
+ continue;
512
+ if (message.includeInLlm === false)
513
+ continue;
514
+ if (!isPermissionModeReminder(message.content))
515
+ continue;
516
+ message.includeInLlm = false;
517
+ }
518
+ }
519
+ get role() {
520
+ return this.agentRole;
521
+ }
522
+ get model() {
523
+ return this._model;
524
+ }
525
+ set model(value) {
526
+ if (this._model === value)
527
+ return;
528
+ this._model = value;
529
+ // Context window (and thus usage readings) is model-derived.
530
+ this.notifyContextChanged();
531
+ }
532
+ get providerId() {
533
+ return this._providerId;
534
+ }
535
+ set providerId(value) {
536
+ if (this._providerId === value)
537
+ return;
538
+ this._providerId = value;
539
+ // Token estimation basis is provider-specific.
540
+ this.notifyContextChanged();
541
+ }
542
+ get apiModel() {
543
+ if (this._model.includes(":")) {
544
+ return this._model.split(":").slice(1).join(":");
545
+ }
546
+ return this._model;
547
+ }
548
+ setProvider(provider) {
549
+ this.provider = provider;
550
+ }
551
+ complete(messages, options) {
552
+ return this.provider.complete(projectMessages(messages), {
553
+ model: options?.model ?? this.apiModel,
554
+ temperature: options?.temperature ?? this.temperature,
555
+ thinkingLevel: options?.thinkingLevel ?? this.thinkingLevel,
556
+ abortSignal: options?.abortSignal,
557
+ });
558
+ }
559
+ get thinking() {
560
+ return this.thinkingLevel;
561
+ }
562
+ set thinking(value) {
563
+ this.thinkingLevel = value;
564
+ }
565
+ get reasoning() {
566
+ return this.thinkingLevel;
567
+ }
568
+ set reasoning(value) {
569
+ this.thinkingLevel = value;
570
+ }
571
+ get mode() {
572
+ return this._mode;
573
+ }
574
+ set mode(value) {
575
+ this.setMode(value);
576
+ }
577
+ setMode(value) {
578
+ if (this._mode === value)
579
+ return;
580
+ this._mode = value;
581
+ this._modeVersion += 1;
582
+ this.injectModeReminder();
583
+ this.onModeUpdate?.(value);
584
+ }
585
+ /** Internal: snapshot counter that bumps on every mode change. Used by run loop. */
586
+ get modeVersion() {
587
+ return this._modeVersion;
588
+ }
589
+ setSystemPrompt(prompt) {
590
+ const systemMessage = { role: "system", content: prompt };
591
+ if (this.messages[0]?.role === "system") {
592
+ if (this.messages[0].content === prompt)
593
+ return;
594
+ this.messages[0] = systemMessage;
595
+ this.notifyContextChanged();
596
+ return;
597
+ }
598
+ this.messages.unshift(systemMessage);
599
+ this.notifyContextChanged();
600
+ }
601
+ async *run(userInput, cwd, options = {}) {
602
+ const abortSignal = options.abortSignal;
603
+ const inputController = options.inputController;
604
+ const traceContext = {
605
+ cwd,
606
+ sessionFile: this.sessionID,
607
+ provider: this._providerId || "none",
608
+ model: this.apiModel || "none",
609
+ };
610
+ const runId = randomUUID();
611
+ const emit = (event) => {
612
+ traceEvent("agent_event", summarizeAgentEventForTrace(event), traceContext);
613
+ return event;
614
+ };
615
+ traceEvent("agent_run_start", {
616
+ input: summarizeTraceValue(userInput),
617
+ mode: this._mode,
618
+ messageCount: this.messages.length,
619
+ toolCount: this.tools.size,
620
+ deferredUnlocked: this.unlockedDeferred.size,
621
+ }, traceContext);
622
+ throwIfAborted(abortSignal);
623
+ const hookBus = new HookBus();
624
+ for (const hooks of createDefaultHooks()) {
625
+ hookBus.register(hooks);
626
+ }
627
+ for (const hooks of this.hookDefinitions) {
628
+ hookBus.register(hooks);
629
+ }
630
+ const hookState = {};
631
+ const reminderQueue = [];
632
+ const queueReminder = (reminder) => {
633
+ reminderQueue.push(reminder);
634
+ };
635
+ const pendingInputCount = () => inputController?.pendingInputCount() ?? 0;
636
+ const applyPendingInputs = async () => {
637
+ const pendingInputs = inputController?.drainPendingInputs() ?? [];
638
+ if (pendingInputs.length === 0)
639
+ return [];
640
+ const events = [];
641
+ for (const input of pendingInputs) {
642
+ const hook = await this.runExternalHook({
643
+ eventName: "SteerInputApplied",
644
+ cwd,
645
+ runId,
646
+ target: "current_turn",
647
+ payload: {
648
+ id: input.id,
649
+ target: "current_turn",
650
+ ...normalizeHookInput(input.content),
651
+ },
652
+ fullPayload: { prompt: input.content },
653
+ }, abortSignal);
654
+ events.push(...hook.events);
655
+ this.injectHookModelContext(hook.result);
656
+ this.appendMessage({ role: "user", content: input.content });
657
+ // A steer is a new ask: the tests-touched disclosure must be able to
658
+ // fire again for the follow-up's closing pass.
659
+ hookState.testsTouchedDisclosed = false;
660
+ events.push({
661
+ type: "input_applied",
662
+ id: input.id,
663
+ content: input.content,
664
+ target: "current_turn",
665
+ });
666
+ }
667
+ events.push({ type: "input_pending_changed", pending: pendingInputCount() });
668
+ return events;
669
+ };
670
+ const rejectPendingInputs = async (reason) => {
671
+ // Seal before the first await. A host can submit in reaction to the final
672
+ // turn boundary; accepting after this drain would orphan that input.
673
+ const pendingInputs = inputController?.closePendingInputs?.()
674
+ ?? inputController?.drainPendingInputs()
675
+ ?? [];
676
+ if (pendingInputs.length === 0)
677
+ return [];
678
+ const events = [];
679
+ for (const input of pendingInputs) {
680
+ const hook = await this.runExternalHook({
681
+ eventName: "QueuedInputRejected",
682
+ cwd,
683
+ runId,
684
+ target: "next_turn",
685
+ payload: {
686
+ id: input.id,
687
+ reason,
688
+ target: "next_turn",
689
+ ...normalizeHookInput(input.content),
690
+ },
691
+ fullPayload: { prompt: input.content },
692
+ }, abortSignal);
693
+ events.push(...hook.events);
694
+ this.injectHookModelContext(hook.result);
695
+ events.push({
696
+ type: "input_rejected",
697
+ id: input.id,
698
+ content: input.content,
699
+ reason,
700
+ target: "next_turn",
701
+ });
702
+ }
703
+ events.push({ type: "input_pending_changed", pending: pendingInputCount() });
704
+ return events;
705
+ };
706
+ const flushQueuedReminders = () => {
707
+ for (const reminder of reminderQueue.splice(0, reminderQueue.length)) {
708
+ this.injectSystemReminder(reminder);
709
+ }
710
+ };
711
+ if (!options.resumeWithoutInput) {
712
+ const promptHook = await this.runExternalHook({
713
+ eventName: "UserPromptSubmit",
714
+ cwd,
715
+ runId,
716
+ target: typeof userInput === "string" ? userInput : "content_parts",
717
+ payload: normalizeHookInput(userInput),
718
+ fullPayload: { prompt: userInput },
719
+ }, abortSignal);
720
+ for (const event of promptHook.events)
721
+ yield emit(event);
722
+ if (promptHook.result.decision === "deny") {
723
+ const message = promptHook.result.reason
724
+ ?? `Prompt blocked by hook ${promptHook.result.sourceHookId ?? "<unknown>"}.`;
725
+ yield emit({ type: "turn_start" });
726
+ yield emit({ type: "text_delta", content: message });
727
+ yield emit({ type: "turn_end", willContinue: false });
728
+ for (const event of await rejectPendingInputs("no_continuation"))
729
+ yield emit(event);
730
+ yield emit({ type: "agent_end" });
731
+ return;
732
+ }
733
+ this.injectHookModelContext(promptHook.result);
734
+ this.appendMessage({
735
+ role: "user",
736
+ content: userInput,
737
+ ...(options.userMessageUi ? { ui: { ...options.userMessageUi } } : {}),
738
+ });
739
+ }
740
+ await hookBus.runBeforeTurn({
741
+ agent: this,
742
+ cwd,
743
+ input: userInput,
744
+ state: hookState,
745
+ queueReminder,
746
+ flushReminders: flushQueuedReminders,
747
+ });
748
+ flushQueuedReminders();
749
+ let consecutiveOverflowRecoveries = 0;
750
+ let consecutiveEmptyAssistantRecoveries = 0;
751
+ let consecutiveStreamInterruptionRetries = 0;
752
+ let step = 0;
753
+ let autoServersStopped = false;
754
+ const stopOwnedAutoServers = async () => {
755
+ if (autoServersStopped)
756
+ return;
757
+ autoServersStopped = true;
758
+ await stopAutoServersForSession(this.sessionID);
759
+ };
760
+ let currentAssistantMsg;
761
+ let currentAssistantAppended = false;
762
+ try {
763
+ while (true) {
764
+ throwIfAborted(abortSignal);
765
+ flushQueuedReminders();
766
+ // Background child completions surface before the next inference turn
767
+ // without requiring a wait_agent call (design §5).
768
+ for (const notice of this.subagents.drainIngestionNotices()) {
769
+ this.injectSystemReminder(notice);
770
+ }
771
+ this.flushWorkflowDeliveries();
772
+ for (const update of this.subagents.drainToolUpdates())
773
+ yield emit(update);
774
+ for (const event of await applyPendingInputs())
775
+ yield emit(event);
776
+ yield emit({ type: "turn_start" });
777
+ step += 1;
778
+ hookState.turnCount = step;
779
+ if (this.taskBudget) {
780
+ hookState.taskBudget = {
781
+ total: this.taskBudget.total,
782
+ spent: hookState.taskBudget?.spent ?? 0,
783
+ };
784
+ }
785
+ let forceTextOnlyReason = hookState.forceTextOnlyReason;
786
+ if (!forceTextOnlyReason && this.maxTurns !== undefined && step >= this.maxTurns) {
787
+ forceTextOnlyReason = "The configured maximum turns for this agent have been reached.";
788
+ hookState.forceTextOnlyReason = forceTextOnlyReason;
789
+ }
790
+ if (forceTextOnlyReason) {
791
+ this.injectSystemReminder(buildToolFreezeReminder(forceTextOnlyReason));
792
+ }
793
+ const assistantMsg = {
794
+ role: "assistant",
795
+ content: "",
796
+ reasoning: "",
797
+ toolCalls: [],
798
+ model: this._model,
799
+ providerId: this.providerId,
800
+ modelId: this.apiModel,
801
+ };
802
+ const streamingToolCalls = new Map();
803
+ const textSanitizer = createStreamingInternalReminderSanitizer();
804
+ const reasoningSanitizer = createStreamingInternalReminderSanitizer();
805
+ let turnUsage;
806
+ let turnSystemFingerprint;
807
+ let assistantAppended = false;
808
+ currentAssistantMsg = assistantMsg;
809
+ currentAssistantAppended = false;
810
+ let toolEntries = Array.from(this.tools.values())
811
+ .filter((t) => !t.deferred || this.unlockedDeferred.has(t.name))
812
+ .filter((t) => t.enabled?.() !== false);
813
+ const beforeModelCallCtx = {
814
+ agent: this,
815
+ cwd,
816
+ input: userInput,
817
+ state: hookState,
818
+ queueReminder,
819
+ flushReminders: flushQueuedReminders,
820
+ toolEntries,
821
+ disableTools: (reason) => {
822
+ hookState.forceTextOnlyReason = reason;
823
+ },
824
+ };
825
+ await hookBus.runBeforeModelCall(beforeModelCallCtx);
826
+ toolEntries = beforeModelCallCtx.toolEntries;
827
+ const preModelHook = await this.runExternalHook({
828
+ eventName: "PreModelCall",
829
+ cwd,
830
+ runId,
831
+ target: this.apiModel,
832
+ payload: {
833
+ providerId: this.providerId,
834
+ model: this.apiModel,
835
+ mode: this._mode,
836
+ toolCount: toolEntries.length,
837
+ ...normalizeHookInput(userInput),
838
+ },
839
+ fullPayload: { prompt: userInput },
840
+ }, abortSignal);
841
+ for (const event of preModelHook.events)
842
+ yield emit(event);
843
+ this.injectHookModelContext(preModelHook.result);
844
+ flushQueuedReminders();
845
+ const textOnly = !!hookState.forceTextOnlyReason;
846
+ const toolDefinitions = toolEntries
847
+ .map((t) => ({
848
+ name: t.name,
849
+ description: t.description,
850
+ parameters: t.parameters,
851
+ }));
852
+ const toolDefinitionTokens = estimateToolDefinitionsTokens(toolDefinitions, this.providerId);
853
+ let currentRequestMessageCount = this.messages.length;
854
+ const persistTerminalProviderError = (error) => this.persistProviderError(error, {
855
+ messageCount: currentRequestMessageCount,
856
+ toolCount: toolDefinitions.length,
857
+ });
858
+ // LLM-driven compaction runs ahead of projector's algorithmic passes. If
859
+ // it succeeds, this.messages is replaced with [preserved system+meta] +
860
+ // [LLM summary] + [last user msg], and the projector becomes a no-op for
861
+ // budget. If it fails (network error, etc.), the projector's existing
862
+ // algorithmic fallback still kicks in.
863
+ for await (const event of this.maybeCompactWithLLM(toolDefinitionTokens, abortSignal))
864
+ yield emit(event);
865
+ for (const event of this.contextEvents.splice(0))
866
+ yield emit(event);
867
+ const bufferedStreamingToolCallIds = new Set();
868
+ let currentRequestEstimate;
869
+ let currentContextWindow;
870
+ try {
871
+ markStableCurrentToolResultsForCache(this.messages);
872
+ const projectedMessages = projectMessages(this.messages, {
873
+ // Semantic compaction is committed above; projection only renders/thins.
874
+ mode: "pruned",
875
+ providerId: this.providerId,
876
+ modelId: this.apiModel,
877
+ // Projection changes message identity/count (meta rendering, pruning,
878
+ // chain repair), so a resident-array anchor index is not safe here.
879
+ // A fresh estimate also lets us account for current tool schemas.
880
+ additionalInputTokens: toolDefinitionTokens,
881
+ });
882
+ currentRequestMessageCount = projectedMessages.length;
883
+ const requestBudget = getContextBudget(this.providerId, this.apiModel, projectedMessages, { additionalInputTokens: toolDefinitionTokens });
884
+ currentRequestEstimate = requestBudget.estimatedTokens;
885
+ currentContextWindow = requestBudget.contextWindow;
886
+ yield emit({ type: "context_usage", usedTokens: currentRequestEstimate, contextWindow: currentContextWindow, estimated: true });
887
+ const maxInputTokens = getMaxInputTokens(requestBudget.contextWindow);
888
+ if (maxInputTokens !== undefined && currentRequestEstimate > maxInputTokens) {
889
+ throw new LocalContextPreflightError(currentRequestEstimate, maxInputTokens);
890
+ }
891
+ const providerStartedAt = Date.now();
892
+ let streamTextChars = 0;
893
+ let streamReasoningChars = 0;
894
+ let streamToolCallDeltas = 0;
895
+ traceEvent("provider_stream_start", {
896
+ residentMessageCount: this.messages.length,
897
+ projectedMessageCount: projectedMessages.length,
898
+ toolCount: toolDefinitions.length,
899
+ thinkingLevel: this.thinkingLevel,
900
+ mode: this._mode,
901
+ requestFingerprint: buildProviderRequestFingerprint(projectedMessages, toolDefinitions, this.providerId, toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined),
902
+ }, traceContext);
903
+ const stream = this.provider.streamChat(projectedMessages, {
904
+ model: this.apiModel,
905
+ tools: toolDefinitions,
906
+ toolChoice: toolDefinitions.length > 0 ? (textOnly ? "none" : "auto") : undefined,
907
+ temperature: this.temperature,
908
+ thinkingLevel: this.thinkingLevel,
909
+ abortSignal,
910
+ rateLimitPolicy: this.rateLimitPolicy,
911
+ });
912
+ for await (const chunk of stream) {
913
+ throwIfAborted(abortSignal);
914
+ switch (chunk.type) {
915
+ case "text":
916
+ {
917
+ const sanitizedDelta = textSanitizer.push(chunk.content);
918
+ if (sanitizedDelta) {
919
+ assistantMsg.content += sanitizedDelta;
920
+ streamTextChars += sanitizedDelta.length;
921
+ yield emit({ type: "text_delta", content: sanitizedDelta });
922
+ }
923
+ }
924
+ break;
925
+ case "reasoning_delta":
926
+ {
927
+ const sanitizedDelta = reasoningSanitizer.push(chunk.content);
928
+ if (sanitizedDelta) {
929
+ debugReasoningStream({
930
+ stage: "agent_receive",
931
+ providerId: this._providerId,
932
+ modelId: this.apiModel,
933
+ turnStep: step,
934
+ beforeLength: assistantMsg.reasoning?.length ?? 0,
935
+ delta: summarizeDebugText(sanitizedDelta),
936
+ afterLength: (assistantMsg.reasoning?.length ?? 0) + sanitizedDelta.length,
937
+ });
938
+ assistantMsg.reasoning = (assistantMsg.reasoning || "") + sanitizedDelta;
939
+ streamReasoningChars += sanitizedDelta.length;
940
+ yield emit({ type: "reasoning_delta", content: sanitizedDelta });
941
+ }
942
+ }
943
+ break;
944
+ case "provider_content_block":
945
+ appendProviderContentBlock(assistantMsg, chunk.provider, chunk.block);
946
+ break;
947
+ case "tool_call":
948
+ // `toolChoice: "none"` is a governance boundary, not merely a
949
+ // provider hint. Ignore any provider-side violation so a forced
950
+ // text-only turn can never reach the tool execution path.
951
+ if (textOnly) {
952
+ traceEvent("text_only_tool_call_ignored", {
953
+ id: chunk.id,
954
+ name: chunk.name,
955
+ isStart: chunk.isStart,
956
+ isEnd: chunk.isEnd,
957
+ }, traceContext);
958
+ break;
959
+ }
960
+ if (chunk.isStart) {
961
+ streamingToolCalls.set(chunk.id, { id: chunk.id, name: chunk.name, args: "" });
962
+ if (!bufferedStreamingToolCallIds.has(chunk.id)) {
963
+ yield emit({ type: "tool_call_start", id: chunk.id, name: chunk.name });
964
+ }
965
+ }
966
+ if (!streamingToolCalls.has(chunk.id)) {
967
+ streamingToolCalls.set(chunk.id, { id: chunk.id, name: chunk.name, args: "" });
968
+ }
969
+ const currentToolCall = streamingToolCalls.get(chunk.id);
970
+ if (currentToolCall) {
971
+ currentToolCall.name = chunk.name || currentToolCall.name;
972
+ currentToolCall.args += chunk.arguments;
973
+ if (chunk.argumentsFull !== undefined) {
974
+ currentToolCall.args = chunk.argumentsFull;
975
+ }
976
+ if (chunk.argumentsCorrupt) {
977
+ currentToolCall.argsCorrupt = true;
978
+ }
979
+ if (chunk.arguments) {
980
+ streamToolCallDeltas += 1;
981
+ if (!bufferedStreamingToolCallIds.has(chunk.id)) {
982
+ yield emit({
983
+ type: "tool_call_delta",
984
+ id: currentToolCall.id,
985
+ name: currentToolCall.name,
986
+ argumentsDelta: chunk.arguments,
987
+ arguments: currentToolCall.args,
988
+ });
989
+ }
990
+ }
991
+ }
992
+ if (chunk.isEnd && currentToolCall) {
993
+ assistantMsg.toolCalls.push({
994
+ id: currentToolCall.id,
995
+ name: currentToolCall.name,
996
+ arguments: currentToolCall.args,
997
+ ...(currentToolCall.argsCorrupt ? { argsCorrupt: true } : {}),
998
+ });
999
+ if (!bufferedStreamingToolCallIds.has(chunk.id)) {
1000
+ yield emit({
1001
+ type: "tool_call_end",
1002
+ id: currentToolCall.id,
1003
+ name: currentToolCall.name,
1004
+ arguments: currentToolCall.args,
1005
+ });
1006
+ }
1007
+ streamingToolCalls.delete(chunk.id);
1008
+ }
1009
+ break;
1010
+ case "usage":
1011
+ turnUsage = chunk.usage;
1012
+ assistantMsg.usage = chunk.usage;
1013
+ this.budgetLedger?.recordUsage(chunk.usage, this.budgetSource);
1014
+ this.lastInputTokens = chunk.usage.promptTokens;
1015
+ this.lastAnchorMessageCount = this.messages.length;
1016
+ if (hookState.taskBudget) {
1017
+ hookState.taskBudget.spent += chunk.usage.promptTokens + chunk.usage.completionTokens;
1018
+ if (hookState.taskBudget.spent >= hookState.taskBudget.total) {
1019
+ hookState.forceTextOnlyReason = "The configured task budget for this agent has been exhausted.";
1020
+ }
1021
+ }
1022
+ break;
1023
+ case "response_metadata":
1024
+ turnSystemFingerprint = chunk.systemFingerprint;
1025
+ assistantMsg.systemFingerprint = chunk.systemFingerprint;
1026
+ break;
1027
+ }
1028
+ for (const update of this.subagents.drainToolUpdates())
1029
+ yield emit(update);
1030
+ }
1031
+ const flushedText = textSanitizer.flush();
1032
+ if (flushedText) {
1033
+ assistantMsg.content += flushedText;
1034
+ streamTextChars += flushedText.length;
1035
+ yield emit({ type: "text_delta", content: flushedText });
1036
+ }
1037
+ const flushedReasoning = reasoningSanitizer.flush();
1038
+ if (flushedReasoning) {
1039
+ debugReasoningStream({
1040
+ stage: "agent_receive_flush",
1041
+ providerId: this._providerId,
1042
+ modelId: this.apiModel,
1043
+ turnStep: step,
1044
+ beforeLength: assistantMsg.reasoning?.length ?? 0,
1045
+ delta: summarizeDebugText(flushedReasoning),
1046
+ afterLength: (assistantMsg.reasoning?.length ?? 0) + flushedReasoning.length,
1047
+ });
1048
+ assistantMsg.reasoning = (assistantMsg.reasoning || "") + flushedReasoning;
1049
+ streamReasoningChars += flushedReasoning.length;
1050
+ yield emit({ type: "reasoning_delta", content: flushedReasoning });
1051
+ }
1052
+ traceEvent("provider_stream_end", {
1053
+ elapsedMs: Date.now() - providerStartedAt,
1054
+ textChars: streamTextChars,
1055
+ reasoningChars: streamReasoningChars,
1056
+ toolCallDeltas: streamToolCallDeltas,
1057
+ toolCalls: assistantMsg.toolCalls?.length ?? 0,
1058
+ usage: turnUsage,
1059
+ }, traceContext);
1060
+ throwIfAborted(abortSignal);
1061
+ const assistantHasContent = assistantMsg.content.trim().length > 0;
1062
+ const assistantHasToolCalls = !!assistantMsg.toolCalls && assistantMsg.toolCalls.length > 0;
1063
+ if (!assistantHasContent && !assistantHasToolCalls) {
1064
+ if (consecutiveEmptyAssistantRecoveries < MAX_EMPTY_ASSISTANT_RECOVERIES) {
1065
+ consecutiveEmptyAssistantRecoveries += 1;
1066
+ this.injectSystemReminder(EMPTY_ASSISTANT_RECOVERY_REMINDER);
1067
+ yield emit({ type: "turn_end", usage: turnUsage, systemFingerprint: turnSystemFingerprint, willContinue: true });
1068
+ continue;
1069
+ }
1070
+ assistantMsg.content = EMPTY_ASSISTANT_FALLBACK;
1071
+ assistantMsg.reasoning = "";
1072
+ yield emit({ type: "text_delta", content: assistantMsg.content });
1073
+ }
1074
+ this.appendMessage(assistantMsg);
1075
+ assistantAppended = true;
1076
+ currentAssistantAppended = true;
1077
+ }
1078
+ catch (error) {
1079
+ traceEvent("provider_stream_error", {
1080
+ error: summarizeTraceError(error),
1081
+ }, traceContext);
1082
+ if (assistantAppended) {
1083
+ if (!isAbortLikeError(error, abortSignal))
1084
+ persistTerminalProviderError(error);
1085
+ throw error;
1086
+ }
1087
+ const streamInterruption = isProviderStreamInterruption(error) ? error : undefined;
1088
+ const streamRetryLimit = streamInterruption
1089
+ ? providerStreamRetryLimit(streamInterruption)
1090
+ : 0;
1091
+ if (streamInterruption
1092
+ && !isAbortLikeError(error, abortSignal)
1093
+ && consecutiveStreamInterruptionRetries < streamRetryLimit) {
1094
+ // The provider stream died after partial content. The half-built
1095
+ // assistantMsg was never appended to this.messages, and the next
1096
+ // turn_start resets the streaming display, so re-issuing the whole
1097
+ // request is safe.
1098
+ consecutiveStreamInterruptionRetries += 1;
1099
+ this.persistProviderError(error, {
1100
+ messageCount: currentRequestMessageCount,
1101
+ toolCount: toolDefinitions.length,
1102
+ retry: { attempt: consecutiveStreamInterruptionRetries, maxAttempts: streamRetryLimit },
1103
+ });
1104
+ yield emit({
1105
+ type: "provider_retry",
1106
+ attempt: consecutiveStreamInterruptionRetries,
1107
+ maxAttempts: streamRetryLimit,
1108
+ reason: streamInterruption.message,
1109
+ });
1110
+ await sleepBeforeRetry(computeRetryDelayMs(consecutiveStreamInterruptionRetries, {
1111
+ retryAfterMs: streamInterruption.retryAfterMs,
1112
+ }), abortSignal).catch(() => undefined);
1113
+ continue;
1114
+ }
1115
+ if (!isContextOverflowError(error)) {
1116
+ if (!isAbortLikeError(error, abortSignal) && shouldAppendModelInterruptedBoundary(this.messages)) {
1117
+ this.appendMessage(createModelInterruptedMessage(error, {
1118
+ model: this._model,
1119
+ providerId: this.providerId,
1120
+ modelId: this.apiModel,
1121
+ }));
1122
+ assistantAppended = true;
1123
+ }
1124
+ if (!isAbortLikeError(error, abortSignal))
1125
+ persistTerminalProviderError(error);
1126
+ throw error;
1127
+ }
1128
+ if (consecutiveOverflowRecoveries >= MAX_CONSECUTIVE_OVERFLOW_RECOVERIES) {
1129
+ persistTerminalProviderError(error);
1130
+ throw error;
1131
+ }
1132
+ const messagesBeforeRecovery = this.messages;
1133
+ const revisionBeforeRecovery = this.getContextRevision?.();
1134
+ const failedRequestEstimate = currentRequestEstimate ?? getContextBudget(this.providerId, this.apiModel, projectMessages(this.messages, {
1135
+ mode: "pruned",
1136
+ providerId: this.providerId,
1137
+ modelId: this.apiModel,
1138
+ additionalInputTokens: toolDefinitionTokens,
1139
+ }), { additionalInputTokens: toolDefinitionTokens }).estimatedTokens;
1140
+ const candidate = await this.recoverFromOverflow(consecutiveOverflowRecoveries, error instanceof LocalContextPreflightError ? 0.999_999 : 0.9, abortSignal);
1141
+ if (!candidate) {
1142
+ persistTerminalProviderError(error);
1143
+ throw error;
1144
+ }
1145
+ const droppedMessages = messagesBeforeRecovery.length - candidate.length;
1146
+ const recoveredRequestEstimate = getContextBudget(this.providerId, this.apiModel, projectMessages(candidate, {
1147
+ mode: "pruned",
1148
+ providerId: this.providerId,
1149
+ modelId: this.apiModel,
1150
+ additionalInputTokens: toolDefinitionTokens,
1151
+ }), { additionalInputTokens: toolDefinitionTokens }).estimatedTokens;
1152
+ // Never issue a retry that is the same size or larger. Apart from
1153
+ // avoiding a useless retry loop, this proves that already-completed
1154
+ // tool calls stay historical data rather than being executed again.
1155
+ const madeProgress = recoveredRequestEstimate < failedRequestEstimate;
1156
+ const meaningfulProviderProgress = error instanceof LocalContextPreflightError
1157
+ || recoveredRequestEstimate <= Math.floor(failedRequestEstimate * 0.9);
1158
+ if (!madeProgress || !meaningfulProviderProgress) {
1159
+ persistTerminalProviderError(error);
1160
+ throw error;
1161
+ }
1162
+ this.applyContextCheckpoint(candidate, "overflow", undefined, revisionBeforeRecovery, abortSignal);
1163
+ consecutiveOverflowRecoveries += 1;
1164
+ this.compactionStats.overflow += 1;
1165
+ this.compactionStats.fired += 1;
1166
+ this.compactionStats.droppedMessages += droppedMessages;
1167
+ traceEvent("compaction_fired", {
1168
+ path: "overflow",
1169
+ droppedMessages,
1170
+ beforeEstimatedTokens: failedRequestEstimate,
1171
+ afterEstimatedTokens: recoveredRequestEstimate,
1172
+ });
1173
+ yield emit({ type: "context_recovered", droppedMessages, reason: "overflow" });
1174
+ yield emit({ type: "context_compaction", status: "completed", compactionId: this.lastCompactionId, persisted: !!this.onContextCheckpoint, preTokens: failedRequestEstimate, postTokens: recoveredRequestEstimate, contextWindow: currentContextWindow });
1175
+ continue;
1176
+ }
1177
+ consecutiveOverflowRecoveries = 0;
1178
+ consecutiveEmptyAssistantRecoveries = 0;
1179
+ consecutiveStreamInterruptionRetries = 0;
1180
+ if (turnUsage && Number.isFinite(turnUsage.promptTokens)) {
1181
+ // Provider prompt usage already includes cached input; never add cache hits twice.
1182
+ yield emit({ type: "context_usage", usedTokens: turnUsage.promptTokens + (turnUsage.completionTokens || 0), contextWindow: currentContextWindow, estimated: false });
1183
+ }
1184
+ // Execute tools if any
1185
+ if (assistantMsg.toolCalls && assistantMsg.toolCalls.length > 0) {
1186
+ const parsedCalls = [];
1187
+ for (let index = 0; index < assistantMsg.toolCalls.length; index++) {
1188
+ const tc = assistantMsg.toolCalls[index];
1189
+ try {
1190
+ parsedCalls.push({
1191
+ ...tc,
1192
+ parsedArgs: JSON.parse(tc.arguments),
1193
+ ...(tc.argsCorrupt ? { argsCorrupt: true } : {}),
1194
+ });
1195
+ }
1196
+ catch {
1197
+ parsedCalls.push({ ...tc, parsedArgs: {}, argsCorrupt: true });
1198
+ }
1199
+ }
1200
+ const executedResults = [];
1201
+ const appendCancelledToolMessages = (startIndex) => {
1202
+ for (let pendingIndex = startIndex; pendingIndex < parsedCalls.length; pendingIndex++) {
1203
+ const pending = parsedCalls[pendingIndex];
1204
+ const pendingResult = cancelledToolResult(pending.name);
1205
+ this.appendMessage({
1206
+ role: "tool",
1207
+ toolCallId: pending.id,
1208
+ content: pendingResult.content,
1209
+ metadata: pendingResult.metadata,
1210
+ isError: pendingResult.isError,
1211
+ });
1212
+ executedResults.push(pendingResult);
1213
+ }
1214
+ };
1215
+ for (let index = 0; index < parsedCalls.length; index++) {
1216
+ if (abortSignal?.aborted) {
1217
+ appendCancelledToolMessages(index);
1218
+ throwIfAborted(abortSignal);
1219
+ }
1220
+ let tc = parsedCalls[index];
1221
+ let blockedResult;
1222
+ // run_workflow must be the only tool call in its response: it starts
1223
+ // a background orchestration whose result lands next turn, and
1224
+ // sibling calls racing it defeat the serialized fan-out contract.
1225
+ if (tc.name === "run_workflow" && parsedCalls.length > 1) {
1226
+ blockedResult = {
1227
+ content: [
1228
+ "run_workflow must be the only tool call in your response.",
1229
+ "Re-issue run_workflow alone — one call, nothing else in the same message — and run other tools after it returns.",
1230
+ ].join(" "),
1231
+ isError: true,
1232
+ status: "blocked",
1233
+ };
1234
+ }
1235
+ await hookBus.runBeforeToolCall({
1236
+ agent: this,
1237
+ cwd,
1238
+ input: userInput,
1239
+ state: hookState,
1240
+ queueReminder,
1241
+ flushReminders: flushQueuedReminders,
1242
+ toolCall: tc,
1243
+ blockedResult,
1244
+ replaceToolCall: (toolCall) => {
1245
+ tc = toolCall;
1246
+ },
1247
+ blockToolCall: (result) => {
1248
+ blockedResult = result;
1249
+ },
1250
+ });
1251
+ const preToolHook = await this.runExternalHook({
1252
+ eventName: "PreToolUse",
1253
+ cwd,
1254
+ runId,
1255
+ target: tc.name,
1256
+ payload: {
1257
+ id: tc.id,
1258
+ name: tc.name,
1259
+ argsPreview: truncateHookText(tc.arguments, 1000),
1260
+ },
1261
+ fullPayload: {
1262
+ toolArgs: tc.parsedArgs,
1263
+ toolArguments: tc.arguments,
1264
+ },
1265
+ }, abortSignal);
1266
+ for (const event of preToolHook.events)
1267
+ yield emit(event);
1268
+ this.injectHookModelContext(preToolHook.result);
1269
+ if (preToolHook.result.decision === "deny") {
1270
+ blockedResult = {
1271
+ content: preToolHook.result.reason
1272
+ ?? `Tool call blocked by hook ${preToolHook.result.sourceHookId ?? "<unknown>"}.`,
1273
+ isError: true,
1274
+ metadata: {
1275
+ hook: {
1276
+ eventName: "PreToolUse",
1277
+ hookId: preToolHook.result.sourceHookId,
1278
+ },
1279
+ },
1280
+ };
1281
+ }
1282
+ assistantMsg.toolCalls[index] = {
1283
+ id: tc.id,
1284
+ name: tc.name,
1285
+ arguments: tc.arguments,
1286
+ };
1287
+ flushQueuedReminders();
1288
+ if (bufferedStreamingToolCallIds.has(tc.id) && !isHiddenToolResult(blockedResult)) {
1289
+ yield emit({ type: "tool_call_start", id: tc.id, name: tc.name });
1290
+ if (tc.arguments) {
1291
+ yield emit({
1292
+ type: "tool_call_delta",
1293
+ id: tc.id,
1294
+ name: tc.name,
1295
+ argumentsDelta: tc.arguments,
1296
+ arguments: tc.arguments,
1297
+ });
1298
+ }
1299
+ yield emit({ type: "tool_call_end", id: tc.id, name: tc.name, arguments: tc.arguments });
1300
+ }
1301
+ if (isHiddenToolResult(blockedResult)) {
1302
+ let result = normalizeToolResultForModel(blockedResult, this.providerId, this.apiModel);
1303
+ await hookBus.runAfterToolCall({
1304
+ agent: this,
1305
+ cwd,
1306
+ input: userInput,
1307
+ state: hookState,
1308
+ queueReminder,
1309
+ flushReminders: flushQueuedReminders,
1310
+ toolCall: tc,
1311
+ result,
1312
+ replaceResult: (next) => {
1313
+ result = next;
1314
+ },
1315
+ });
1316
+ result = normalizeToolResultForModel(result, this.providerId, this.apiModel);
1317
+ const postToolHook = await this.runExternalHook({
1318
+ eventName: result.isError ? "PostToolUseFailure" : "PostToolUse",
1319
+ cwd,
1320
+ runId,
1321
+ target: tc.name,
1322
+ payload: {
1323
+ id: tc.id,
1324
+ name: tc.name,
1325
+ argsPreview: truncateHookText(tc.arguments, 1000),
1326
+ resultPreview: truncateHookText(result.content, 1000),
1327
+ isError: result.isError === true,
1328
+ },
1329
+ fullPayload: {
1330
+ toolArgs: tc.parsedArgs,
1331
+ toolArguments: tc.arguments,
1332
+ toolResult: result,
1333
+ },
1334
+ }, abortSignal);
1335
+ for (const event of postToolHook.events)
1336
+ yield emit(event);
1337
+ this.injectHookModelContext(postToolHook.result);
1338
+ traceEvent("speculative_read_blocked", {
1339
+ id: tc.id,
1340
+ name: tc.name,
1341
+ args: summarizeTraceValue(tc.parsedArgs),
1342
+ result: summarizeTraceToolResult(result),
1343
+ }, traceContext);
1344
+ this.appendMessage({
1345
+ role: "tool",
1346
+ toolCallId: tc.id,
1347
+ content: result.content,
1348
+ metadata: result.metadata,
1349
+ isError: result.isError,
1350
+ });
1351
+ executedResults.push(result);
1352
+ flushQueuedReminders();
1353
+ continue;
1354
+ }
1355
+ const toolStartedAt = Date.now();
1356
+ traceEvent("tool_execute_start", {
1357
+ id: tc.id,
1358
+ name: tc.name,
1359
+ args: summarizeTraceValue(tc.parsedArgs),
1360
+ argsCorrupt: tc.argsCorrupt,
1361
+ }, traceContext);
1362
+ yield emit({ type: "tool_start", id: tc.id, name: tc.name, args: tc.parsedArgs });
1363
+ const modeVersionBefore = this._modeVersion;
1364
+ const updateQueue = createUpdateQueue();
1365
+ let result;
1366
+ if (blockedResult) {
1367
+ result = blockedResult;
1368
+ }
1369
+ else {
1370
+ const toolExecution = this.executeTool(tc, cwd, abortSignal, (update) => updateQueue.push(update));
1371
+ let settled = false;
1372
+ let cancelledByAbort = false;
1373
+ let resolved;
1374
+ let rejected;
1375
+ void toolExecution
1376
+ .then((value) => {
1377
+ resolved = value;
1378
+ })
1379
+ .catch((error) => {
1380
+ rejected = error;
1381
+ })
1382
+ .finally(() => {
1383
+ settled = true;
1384
+ updateQueue.wake();
1385
+ });
1386
+ const unsubscribe = this.subagents.subscribe(updateQueue.wake);
1387
+ try {
1388
+ while (!settled || updateQueue.hasItems() || this.subagents.hasPendingUpdates()) {
1389
+ for (const update of updateQueue.drain()) {
1390
+ yield emit({ type: "tool_update", id: tc.id, name: tc.name, update });
1391
+ }
1392
+ for (const update of this.subagents.drainToolUpdates())
1393
+ yield emit(update);
1394
+ // A wake() that fires while this generator is suspended at a
1395
+ // yield above finds no parked waiter. The queue latches such a
1396
+ // wake, and this synchronous re-check covers the same case from
1397
+ // the other side — an update pushed during the yield must not
1398
+ // stall until the next unrelated wake or the tool settles.
1399
+ if (!settled && !this.subagents.hasPendingUpdates()) {
1400
+ const waitStatus = await updateQueue.wait(abortSignal);
1401
+ if (waitStatus === "aborted" && !settled) {
1402
+ cancelledByAbort = true;
1403
+ break;
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+ finally {
1409
+ unsubscribe();
1410
+ }
1411
+ if (cancelledByAbort) {
1412
+ result = cancelledToolResult(tc.name);
1413
+ }
1414
+ else {
1415
+ if (rejected)
1416
+ throw rejected;
1417
+ result = resolved ?? { content: `Error: Tool "${tc.name}" returned no result`, isError: true };
1418
+ }
1419
+ }
1420
+ // Hooks receive a bounded canonical result. This prevents a tool
1421
+ // from handing an unbounded multi-megabyte value to PostToolUse.
1422
+ result = normalizeToolResultForModel(result, this.providerId, this.apiModel);
1423
+ await hookBus.runAfterToolCall({
1424
+ agent: this,
1425
+ cwd,
1426
+ input: userInput,
1427
+ state: hookState,
1428
+ queueReminder,
1429
+ flushReminders: flushQueuedReminders,
1430
+ toolCall: tc,
1431
+ result,
1432
+ replaceResult: (next) => {
1433
+ result = next;
1434
+ },
1435
+ });
1436
+ // A hook is allowed to replace the result, so enforce the invariant
1437
+ // again before persistence, callbacks, UI events and provider reuse.
1438
+ result = normalizeToolResultForModel(result, this.providerId, this.apiModel);
1439
+ const postToolHook = await this.runExternalHook({
1440
+ eventName: result.isError ? "PostToolUseFailure" : "PostToolUse",
1441
+ cwd,
1442
+ runId,
1443
+ target: tc.name,
1444
+ payload: {
1445
+ id: tc.id,
1446
+ name: tc.name,
1447
+ argsPreview: truncateHookText(tc.arguments, 1000),
1448
+ resultPreview: truncateHookText(result.content, 1000),
1449
+ isError: result.isError === true,
1450
+ },
1451
+ fullPayload: {
1452
+ toolArgs: tc.parsedArgs,
1453
+ toolArguments: tc.arguments,
1454
+ toolResult: result,
1455
+ },
1456
+ }, abortSignal);
1457
+ for (const event of postToolHook.events)
1458
+ yield emit(event);
1459
+ this.injectHookModelContext(postToolHook.result);
1460
+ // Honor the model's server-declared per-tool-output token cap (e.g.
1461
+ // gpt-5.5 reports 10000). Without this, 4-5 large file reads in a row
1462
+ // blow past the input window even though our local estimate looks fine.
1463
+ const truncatedOutput = truncateToolOutputForModel(result.content, this.providerId, this.apiModel);
1464
+ traceEvent("tool_execute_end", {
1465
+ id: tc.id,
1466
+ name: tc.name,
1467
+ elapsedMs: Date.now() - toolStartedAt,
1468
+ result: summarizeTraceToolResult(result),
1469
+ outputTruncation: {
1470
+ truncated: truncatedOutput.truncated,
1471
+ originalTokens: truncatedOutput.originalTokens,
1472
+ finalTokens: truncatedOutput.finalTokens,
1473
+ originalBytes: truncatedOutput.originalBytes,
1474
+ finalBytes: truncatedOutput.finalBytes,
1475
+ limit: truncatedOutput.limit,
1476
+ hardByteLimit: truncatedOutput.hardByteLimit,
1477
+ },
1478
+ }, traceContext);
1479
+ const toolMessage = {
1480
+ role: "tool",
1481
+ toolCallId: tc.id,
1482
+ content: truncatedOutput.content,
1483
+ metadata: result.metadata,
1484
+ isError: result.isError,
1485
+ };
1486
+ // Stamp before appending: appendMessage persists to the session log
1487
+ // synchronously, so a mark added on a later turn never reaches disk.
1488
+ markToolResultCacheStable(tc.name, toolMessage);
1489
+ this.appendMessage(toolMessage);
1490
+ flushQueuedReminders();
1491
+ this.onToolResult?.(tc.name, result);
1492
+ executedResults.push(result);
1493
+ yield emit({ type: "tool_end", id: tc.id, name: tc.name, result });
1494
+ for (const event of this.contextEvents.splice(0))
1495
+ yield emit(event);
1496
+ for (const update of this.subagents.drainToolUpdates())
1497
+ yield emit(update);
1498
+ if (this._modeVersion !== modeVersionBefore) {
1499
+ yield emit({ type: "mode_changed", mode: this._mode });
1500
+ }
1501
+ if (abortSignal?.aborted) {
1502
+ appendCancelledToolMessages(index + 1);
1503
+ throwIfAborted(abortSignal);
1504
+ }
1505
+ }
1506
+ const imageObservation = toolImageObservation(executedResults);
1507
+ if (imageObservation)
1508
+ this.appendMessage(imageObservation);
1509
+ // A checkpoint must contain the whole batch, never provider repair's
1510
+ // placeholder for a sibling that has not executed yet.
1511
+ this.compactResidentHistory();
1512
+ for (const event of this.contextEvents.splice(0))
1513
+ yield emit(event);
1514
+ await hookBus.runBeforeContinuation({
1515
+ agent: this,
1516
+ cwd,
1517
+ input: userInput,
1518
+ state: hookState,
1519
+ queueReminder,
1520
+ flushReminders: flushQueuedReminders,
1521
+ toolCalls: parsedCalls,
1522
+ toolResults: executedResults,
1523
+ requestTextOnlyTurn: (reason) => {
1524
+ hookState.forceTextOnlyReason = reason;
1525
+ },
1526
+ });
1527
+ flushQueuedReminders();
1528
+ yield emit({ type: "turn_end", usage: turnUsage, systemFingerprint: turnSystemFingerprint, willContinue: true });
1529
+ // Auto-continue: if we have tool results, the LLM needs to respond to them.
1530
+ // Emitting the turn boundary keeps UI renderers aligned with the persisted
1531
+ // assistant/tool message sequence instead of merging the next answer into
1532
+ // the tool-call turn.
1533
+ continue;
1534
+ }
1535
+ await hookBus.runAfterTurn({
1536
+ agent: this,
1537
+ cwd,
1538
+ input: userInput,
1539
+ state: hookState,
1540
+ queueReminder,
1541
+ flushReminders: flushQueuedReminders,
1542
+ });
1543
+ flushQueuedReminders();
1544
+ const stopHook = await this.runExternalHook({
1545
+ eventName: "Stop",
1546
+ cwd,
1547
+ runId,
1548
+ target: "turn",
1549
+ payload: {
1550
+ providerId: this.providerId,
1551
+ model: this.apiModel,
1552
+ mode: this._mode,
1553
+ assistantChars: assistantMsg.content.length,
1554
+ toolCalls: assistantMsg.toolCalls?.length ?? 0,
1555
+ },
1556
+ }, abortSignal);
1557
+ for (const event of stopHook.events)
1558
+ yield emit(event);
1559
+ const willContinue = !!hookState.forceContinuationReason
1560
+ || (options.continueOnPendingInput === true && pendingInputCount() > 0);
1561
+ yield emit({ type: "turn_end", usage: turnUsage, systemFingerprint: turnSystemFingerprint, willContinue });
1562
+ if (willContinue) {
1563
+ delete hookState.forceContinuationReason;
1564
+ continue;
1565
+ }
1566
+ // A follow-up can arrive while the consumer handles turn_end. Recheck
1567
+ // synchronously before closing admission so an accepted input is not lost.
1568
+ if (options.continueOnPendingInput && pendingInputCount() > 0)
1569
+ continue;
1570
+ for (const event of await rejectPendingInputs("no_continuation"))
1571
+ yield emit(event);
1572
+ break;
1573
+ }
1574
+ for (const update of this.subagents.drainToolUpdates())
1575
+ yield emit(update);
1576
+ await stopOwnedAutoServers();
1577
+ yield emit({ type: "agent_end" });
1578
+ }
1579
+ catch (error) {
1580
+ if (isAbortError(error, abortSignal)) {
1581
+ const appendedBoundary = this.appendInterruptedAssistantBoundary(currentAssistantMsg, currentAssistantAppended);
1582
+ traceEvent("agent_run_interrupted", {
1583
+ appendedBoundary,
1584
+ messageCount: this.messages.length,
1585
+ }, traceContext);
1586
+ }
1587
+ else {
1588
+ const stopFailureHook = await this.runExternalHook({
1589
+ eventName: "StopFailure",
1590
+ cwd,
1591
+ runId,
1592
+ target: "run_error",
1593
+ payload: {
1594
+ error: summarizeTraceError(error),
1595
+ },
1596
+ }, abortSignal);
1597
+ for (const event of stopFailureHook.events)
1598
+ yield emit(event);
1599
+ }
1600
+ throw error;
1601
+ }
1602
+ finally {
1603
+ await stopOwnedAutoServers();
1604
+ traceEvent("agent_run_end", {
1605
+ messageCount: this.messages.length,
1606
+ }, traceContext);
1607
+ }
1608
+ }
1609
+ async recoverFromOverflow(attempt, maximumSizeRatio = 0.999_999, abortSignal) {
1610
+ const originalMessages = this.messages;
1611
+ const beforeBytes = serializedMessageBytes(originalMessages);
1612
+ const smaller = (candidate) => !!candidate && serializedMessageBytes(candidate) <= Math.floor(beforeBytes * maximumSizeRatio);
1613
+ throwIfAborted(abortSignal);
1614
+ if (attempt === 0) {
1615
+ const pruned = aggressivePruneMessages(originalMessages);
1616
+ if (smaller(pruned))
1617
+ return pruned;
1618
+ }
1619
+ // Unlike ordinary pruning, overflow recovery is allowed to thin the most
1620
+ // recent completed tool batch. Keeping it protected is exactly what made
1621
+ // a one-turn 6MB grep result impossible to recover from.
1622
+ const emergencyToolLimit = attempt === 0 ? 16 * 1024 : 4 * 1024;
1623
+ const thinnedLatestTools = originalMessages.map((message) => {
1624
+ if (message.role !== "tool")
1625
+ return message;
1626
+ const bounded = truncateToolOutputForModel(message.content, this.providerId, this.apiModel, { hardByteLimit: emergencyToolLimit });
1627
+ return bounded.truncated ? { ...message, content: bounded.content } : message;
1628
+ });
1629
+ if (smaller(thinnedLatestTools))
1630
+ return thinnedLatestTools;
1631
+ const keepRecentTurns = attempt >= 1 ? 1 : 2;
1632
+ // Use the same budget-aware summarizer for single- and multi-turn history.
1633
+ const { compactWithLLM } = await import("./context/llm-compactor.js");
1634
+ const singleTurnResult = await compactWithLLM(originalMessages, {
1635
+ provider: this.provider,
1636
+ modelId: this.apiModel,
1637
+ providerId: this.providerId,
1638
+ abortSignal,
1639
+ });
1640
+ throwIfAborted(abortSignal);
1641
+ if (singleTurnResult.compacted && smaller(singleTurnResult.messages))
1642
+ return singleTurnResult.messages;
1643
+ const fallback = compactMessages(originalMessages, { keepRecentTurns });
1644
+ if (fallback.compacted && smaller(fallback.messages))
1645
+ return fallback.messages;
1646
+ const subturn = compactCurrentTurnToolGroups(originalMessages, {
1647
+ keepRecentGroups: attempt === 0 ? 1 : 0,
1648
+ });
1649
+ if (subturn.compacted && smaller(subturn.messages))
1650
+ return subturn.messages;
1651
+ // A two-turn text history has nothing to give at keepRecentTurns 2 and no
1652
+ // tool groups; returning undefined on attempt 0 aborts before attempt 1.
1653
+ if (keepRecentTurns > 1) {
1654
+ const lastTurnOnly = compactMessages(originalMessages, { keepRecentTurns: 1 });
1655
+ if (lastTurnOnly.compacted && smaller(lastTurnOnly.messages))
1656
+ return lastTurnOnly.messages;
1657
+ }
1658
+ // Fail explicitly rather than silently deleting the original instruction.
1659
+ return undefined;
1660
+ }
1661
+ compactResidentHistory() {
1662
+ this.maybeCompactResidentHistory();
1663
+ }
1664
+ async *maybeCompactWithLLM(additionalInputTokens = 0, abortSignal) {
1665
+ if (!this.providerId || !this.apiModel)
1666
+ return;
1667
+ if (this.messages.length === 0)
1668
+ return;
1669
+ const tail = this.lastAnchorMessageCount !== null
1670
+ ? this.messages.slice(this.lastAnchorMessageCount)
1671
+ : undefined;
1672
+ const budget = getContextBudget(this.providerId, this.apiModel, this.messages, {
1673
+ usageAnchorTokens: this.lastInputTokens ?? undefined,
1674
+ tailMessages: tail,
1675
+ // Usage anchors already include the schemas sent with that request.
1676
+ additionalInputTokens: this.lastInputTokens === null ? additionalInputTokens : 0,
1677
+ });
1678
+ if (!budget.shouldCompact)
1679
+ return;
1680
+ yield { type: "context_compaction", status: "started", preTokens: budget.estimatedTokens, contextWindow: budget.contextWindow };
1681
+ let completed = false;
1682
+ const revision = this.getContextRevision?.();
1683
+ try {
1684
+ const { compactWithLLM } = await import("./context/llm-compactor.js");
1685
+ let result = await compactWithLLM(this.messages, {
1686
+ provider: this.provider,
1687
+ providerId: this.providerId,
1688
+ modelId: this.apiModel,
1689
+ abortSignal,
1690
+ });
1691
+ throwIfAborted(abortSignal);
1692
+ let path = "llm";
1693
+ if (!result.compacted) {
1694
+ // The request projection no longer compacts, so this fallback must
1695
+ // escalate as far as the former budgeted passes did (down to one kept
1696
+ // turn / one kept tool group) or a two-turn text history has no way
1697
+ // under the window when the summarizer is unavailable. Take the first
1698
+ // pass that fits the budget, otherwise the smallest one that fired.
1699
+ const passes = [
1700
+ ["heuristic", () => compactMessages(this.messages, { keepRecentTurns: 2 })],
1701
+ ["heuristic", () => compactMessages(this.messages, { keepRecentTurns: 1 })],
1702
+ ["subturn", () => compactCurrentTurnToolGroups(this.messages, { keepRecentGroups: 2 })],
1703
+ ["subturn", () => compactCurrentTurnToolGroups(this.messages, { keepRecentGroups: 1 })],
1704
+ ];
1705
+ let smallest = Infinity;
1706
+ for (const [passPath, pass] of passes) {
1707
+ const candidate = pass();
1708
+ if (!candidate.compacted || !candidate.messages)
1709
+ continue;
1710
+ const candidateBudget = getContextBudget(this.providerId, this.apiModel, candidate.messages, { additionalInputTokens });
1711
+ if (candidateBudget.estimatedTokens < smallest) {
1712
+ smallest = candidateBudget.estimatedTokens;
1713
+ result = candidate;
1714
+ path = passPath;
1715
+ }
1716
+ if (!candidateBudget.shouldCompact)
1717
+ break;
1718
+ }
1719
+ }
1720
+ const beforeEstimate = getContextBudget(this.providerId, this.apiModel, this.messages, { additionalInputTokens }).estimatedTokens;
1721
+ const candidateEstimate = result.messages
1722
+ ? getContextBudget(this.providerId, this.apiModel, result.messages, { additionalInputTokens }).estimatedTokens : beforeEstimate;
1723
+ if (result.compacted && result.messages && candidateEstimate < beforeEstimate) {
1724
+ this.applyContextCheckpoint(result.messages, "auto", result.summary, revision, abortSignal);
1725
+ this.compactionStats[path] += 1;
1726
+ this.compactionStats.fired += 1;
1727
+ traceEvent("compaction_fired", { path });
1728
+ completed = true;
1729
+ const after = getContextBudget(this.providerId, this.apiModel, this.messages, { additionalInputTokens });
1730
+ yield { type: "context_compaction", status: "completed", compactionId: this.lastCompactionId, persisted: !!this.onContextCheckpoint, preTokens: budget.estimatedTokens, postTokens: after.estimatedTokens, contextWindow: budget.contextWindow };
1731
+ }
1732
+ }
1733
+ finally {
1734
+ if (!completed)
1735
+ yield { type: "context_compaction", status: "failed", preTokens: budget.estimatedTokens, contextWindow: budget.contextWindow };
1736
+ }
1737
+ // If LLM compaction failed for any reason, leave this.messages alone —
1738
+ // the projector's algorithmic budgeted-mode passes will still try.
1739
+ }
1740
+ /** Snapshot of how often each compaction path rewrote history this run. */
1741
+ getCompactionStats() {
1742
+ return { ...this.compactionStats };
1743
+ }
1744
+ /**
1745
+ * Stream a 9-section handoff summary of `oldMessages` from the session model.
1746
+ * Powers the manual `/compact` command: streaming (rather than `complete()`)
1747
+ * is what lets the TUI show live progress as the summary is produced.
1748
+ *
1749
+ * `onDelta` receives the full accumulated text and the latest delta on each
1750
+ * chunk. Returns the trimmed summary, or "" if the model produced nothing
1751
+ * (the caller falls back to heuristic compaction in that case). Throws only
1752
+ * if the provider stream itself errors.
1753
+ */
1754
+ async summarizeForCompaction(oldMessages, onDelta, abortSignal, userContext) {
1755
+ if (oldMessages.length === 0)
1756
+ return "";
1757
+ const { buildCompactionPromptMessages } = await import("./context/compact-llm.js");
1758
+ const { collectCompactionFileOps, isCompactionSummaryMessage, messageText } = await import("./context/compact.js");
1759
+ const { appendFileBlocks, stripFileBlocks } = await import("./context/compaction-files.js");
1760
+ // File blocks stay out of the model round-trip: stripped from the input
1761
+ // here, merged deterministically, re-appended to the returned summary.
1762
+ const priorSummaries = oldMessages.filter(isCompactionSummaryMessage);
1763
+ const fileOps = collectCompactionFileOps(oldMessages.filter((message) => !isCompactionSummaryMessage(message)), priorSummaries);
1764
+ const promptInput = oldMessages.map((message) => isCompactionSummaryMessage(message) && typeof message.content === "string"
1765
+ ? { ...message, content: stripFileBlocks(messageText(message)) }
1766
+ : message);
1767
+ const promptMessages = buildCompactionPromptMessages(promptInput, userContext);
1768
+ const stream = this.provider.streamChat(promptMessages, {
1769
+ model: this.apiModel,
1770
+ temperature: 0.2,
1771
+ thinkingLevel: "off",
1772
+ abortSignal,
1773
+ });
1774
+ let full = "";
1775
+ for await (const chunk of stream) {
1776
+ if (chunk.type === "text" && chunk.content) {
1777
+ full += chunk.content;
1778
+ onDelta?.(full, chunk.content);
1779
+ }
1780
+ }
1781
+ // Strip any internal reminder markup the summarizer may have reproduced from
1782
+ // the transcript: this summary is both displayed in the compaction card and
1783
+ // re-injected as a `Previous conversation summary` system message.
1784
+ const summary = sanitizeInternalReminderBlocks(full).trim();
1785
+ if (!summary)
1786
+ return "";
1787
+ return appendFileBlocks(summary, fileOps);
1788
+ }
1789
+ // ---- Subagent lifecycle: delegated to SubagentRuntime. ----
1790
+ // These stay on Agent as one-line forwards so every existing caller
1791
+ // (tools/agent-lifecycle.ts, orchestrator/default-hooks.ts) is unchanged.
1792
+ async runSubAgent(input, cwd, options) {
1793
+ return this.subagents.runSubAgent(input, cwd, options);
1794
+ }
1795
+ async spawnSubAgent(input, cwd, options) {
1796
+ return this.subagents.spawnSubAgent(input, cwd, options);
1797
+ }
1798
+ async waitSubAgents(options = {}) {
1799
+ return this.subagents.waitSubAgents(options);
1800
+ }
1801
+ async sendSubAgentInput(agentId, input, cwd, options = {}) {
1802
+ return this.subagents.sendSubAgentInput(agentId, input, cwd, options);
1803
+ }
1804
+ async closeSubAgent(agentId) {
1805
+ return this.subagents.closeSubAgent(agentId);
1806
+ }
1807
+ /**
1808
+ * Live (non-final) children only — the delegation-nudge gate
1809
+ * (large-task-delegation design §2). listSubAgents() is a grows-only
1810
+ * session history (finished + resumed children stay forever), so it must
1811
+ * never be used to answer "am I currently delegating?".
1812
+ */
1813
+ activeSubAgentCount() {
1814
+ return this.subagents.activeSubAgentCount();
1815
+ }
1816
+ listSubAgents() {
1817
+ return this.subagents.listSubAgents();
1818
+ }
1819
+ /** Read-only child transcript for the TUI inspector. */
1820
+ getSubAgentMessages(agentId) {
1821
+ const record = this.subagentStore.get(agentId);
1822
+ const messages = record?.agent?.messages ?? record?.messages ?? [];
1823
+ return messages.map((message) => ({ ...message }));
1824
+ }
1825
+ /** Marks a child's full summary as delivered to parent context (design §3.3). */
1826
+ markSubagentDelivered(agentId) {
1827
+ this.subagents.markSubagentDelivered(agentId);
1828
+ }
1829
+ /**
1830
+ * The child store, still reachable at its old name. Several tests inspect a
1831
+ * spawned child's record (and its live child Agent) through
1832
+ * `(agent as any).subagentStore`; keeping this forward means the extraction
1833
+ * changed no caller, test or otherwise.
1834
+ */
1835
+ get subagentStore() {
1836
+ return this.subagents.store;
1837
+ }
1838
+ /** run_workflow children are workflowInternal and invisible to listSubAgents. */
1839
+ hasRunningWorkflow() {
1840
+ return this.workflowLedger.hasRunning();
1841
+ }
1842
+ /**
1843
+ * Dynamic workflow (option C): runs an LLM-authored JS orchestration script in
1844
+ * a QuickJS sandbox. Each agent() call in the script becomes a real scheduled
1845
+ * subagent (same route resolution, ChildRunner, scheduler, schema validation
1846
+ * as spawn_agent), so the script expresses deterministic control flow while
1847
+ * the runtime keeps owning concurrency/budget/retry.
1848
+ *
1849
+ * Foreground entry point (used by `-p`/headless and tests): awaits to
1850
+ * completion and returns the result. Background runs go through startWorkflow.
1851
+ */
1852
+ async runWorkflow(cwd, options) {
1853
+ return this.subagents.executeWorkflow(cwd, {
1854
+ script: options.script,
1855
+ args: options.args,
1856
+ parentToolCallId: options.parentToolCallId,
1857
+ abortSignal: options.abortSignal,
1858
+ directEmit: options.emitUpdate,
1859
+ ensureProfileTrusted: options.ensureProfileTrusted,
1860
+ });
1861
+ }
1862
+ /**
1863
+ * Starts a workflow in the BACKGROUND (option C Phase 4): returns a runId
1864
+ * immediately; the script runs detached, its agents stream progress through
1865
+ * the queued channel (drained at turn boundaries like spawn_agent), and its
1866
+ * result is ingested at the next turn. Collect explicitly with waitWorkflow.
1867
+ */
1868
+ startWorkflow(cwd, options) {
1869
+ return this.workflowLedger.start(cwd, options);
1870
+ }
1871
+ /** Blocks until a background workflow reaches a final state (or times out). */
1872
+ async waitWorkflow(runId, timeoutMs) {
1873
+ return this.workflowLedger.wait(runId, normalizeWaitTimeout(timeoutMs));
1874
+ }
1875
+ /** Cancels a running background workflow. */
1876
+ closeWorkflow(runId) {
1877
+ return this.workflowLedger.close(runId);
1878
+ }
1879
+ listWorkflows() {
1880
+ return this.workflowLedger.list();
1881
+ }
1882
+ /** Injects completed background-workflow results before the next turn (§5 analog). */
1883
+ flushWorkflowDeliveries() {
1884
+ for (const notice of this.workflowLedger.drainDeliveryNotices()) {
1885
+ this.injectSystemReminder(notice);
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Resolves a child's model route. Priority, highest first (design v2 §1.1):
1890
+ * call-site override (model/effort) > profile.model > category > inherit parent.
1891
+ * The call-site override is what lets the model say "opus for this reviewer,
1892
+ * haiku for these twenty scouts" per spawn/batch member at request time.
1893
+ */
1894
+ /** Kept as a thin delegator: routing decisions now live in SubagentRouter. */
1895
+ resolveRouteForSubagent(profile, category, override) {
1896
+ return this.router.resolve(profile, category, override);
1897
+ }
1898
+ /** Routable catalog across runnable providers (design v3.6); undefined when unwired. */
1899
+ listRoutableModels() {
1900
+ return this.router.listRoutableModels();
1901
+ }
1902
+ /**
1903
+ * The ONE place a child Agent is constructed. The runtime hands over what it
1904
+ * resolved (provider/route/tools/prompt); everything inherited from the
1905
+ * parent is filled in here, which is also what keeps the deliberately
1906
+ * ABSENT arguments absent — `onMessageAppend` (a child must not append to
1907
+ * the parent's session log), `sessionID` (a child must not stop the
1908
+ * parent's auto servers) and the routing catalog (children route by
1909
+ * default, not from the parent's menu).
1910
+ */
1911
+ createChildAgent(spec) {
1912
+ const child = new Agent({
1913
+ provider: spec.provider,
1914
+ providerId: spec.providerId,
1915
+ model: spec.model,
1916
+ tools: spec.tools,
1917
+ temperature: this.temperature,
1918
+ thinkingLevel: spec.thinkingLevel,
1919
+ mode: spec.mode,
1920
+ maxTurns: spec.maxTurns,
1921
+ budgetLedger: this.budgetLedger,
1922
+ budgetSource: spec.budgetSource,
1923
+ systemPrompt: spec.systemPrompt,
1924
+ hooks: this.hookDefinitions,
1925
+ externalHooks: this.externalHooks,
1926
+ agentRole: "subagent",
1927
+ subAgentId: spec.subAgentId,
1928
+ agentCategories: this.agentCategories,
1929
+ providerFactory: this.providerFactory,
1930
+ // The scheduler owns 429 backoff for children; the transport must not
1931
+ // stack its own retries on top (design §4.5).
1932
+ rateLimitPolicy: "defer",
1933
+ });
1934
+ if (spec.resumeMessages && spec.resumeMessages.length > 0) {
1935
+ child.messages = spec.resumeMessages;
1936
+ }
1937
+ else if (spec.forkContext) {
1938
+ child.messages = this.forkMessagesForSubagent(spec.systemPrompt);
1939
+ }
1940
+ return child;
1941
+ }
1942
+ forkMessagesForSubagent(childSystemPrompt) {
1943
+ const forked = this.messages
1944
+ .filter((message) => {
1945
+ if (message.role === "system" || message.role === "meta")
1946
+ return false;
1947
+ if (message.role === "assistant" && message.toolCalls?.some((call) => isSubagentLifecycleTool(call.name))) {
1948
+ return false;
1949
+ }
1950
+ if (message.role === "tool" && message.metadata?.kind === "subagent") {
1951
+ return false;
1952
+ }
1953
+ return true;
1954
+ })
1955
+ .slice(-20);
1956
+ return [{ role: "system", content: childSystemPrompt }, ...forked];
1957
+ }
1958
+ maybeCompactResidentHistory() {
1959
+ if (this.messages.length === 0 || !hasCompleteToolGroups(this.messages)) {
1960
+ return;
1961
+ }
1962
+ const before = this.messages;
1963
+ const beforeChars = estimateResidentChars(before);
1964
+ const beforeToolChars = estimateToolPayloadChars(before);
1965
+ // Compact canonical history, not the provider projection: projection
1966
+ // renders runtime meta as user text and can synthesize missing tool results.
1967
+ let candidate = pruneMessages(before);
1968
+ const budget = this.providerId && this.apiModel
1969
+ ? getContextBudget(this.providerId, this.apiModel, candidate)
1970
+ : undefined;
1971
+ const heapUsed = getCurrentHeapUsed();
1972
+ const keepRecentTurns = countUserTurns(candidate) > 10
1973
+ ? 2
1974
+ : RESIDENT_HISTORY_KEEP_RECENT_TURNS;
1975
+ // Cliff-edge only (docs/harness-thinning.md, wave 2): history is the
1976
+ // model's working memory and is NEVER trimmed early. The message-count
1977
+ // and char-size triggers that used to fire here mid-run created re-read
1978
+ // loops - the model lost what it had read and read it again. Compaction
1979
+ // now waits for the context-window budget itself; the heap guard stays
1980
+ // as process life-support, not policy.
1981
+ const shouldAggressivelyPrune = heapUsed >= RESIDENT_HISTORY_HEAP_HARD_LIMIT;
1982
+ // Model summarization runs at the request boundary. Resident compaction is
1983
+ // an emergency heap guard, not a competing token-triggered lossy policy.
1984
+ const shouldCompact = shouldAggressivelyPrune && !!budget?.shouldCompact;
1985
+ if (shouldAggressivelyPrune) {
1986
+ candidate = aggressivePruneMessages(candidate);
1987
+ }
1988
+ let compactedPath;
1989
+ let residentSummary;
1990
+ if (shouldCompact) {
1991
+ const compacted = compactMessages(candidate, { keepRecentTurns });
1992
+ if (compacted.compacted && compacted.messages) {
1993
+ candidate = compacted.messages;
1994
+ compactedPath = "resident";
1995
+ residentSummary = compacted.summary;
1996
+ traceEvent("compaction_fired", { path: "resident", droppedEntries: compacted.droppedEntries });
1997
+ }
1998
+ else {
1999
+ // Single-instruction runs (one real user turn) never satisfy the
2000
+ // turn-level compactor; fold old tool groups within the turn instead.
2001
+ const subturn = compactCurrentTurnToolGroups(candidate, { keepRecentGroups: 3 });
2002
+ if (subturn.compacted && subturn.messages) {
2003
+ candidate = subturn.messages;
2004
+ compactedPath = "subturn";
2005
+ residentSummary = subturn.summary;
2006
+ traceEvent("compaction_fired", { path: "subturn", droppedEntries: subturn.droppedEntries });
2007
+ }
2008
+ }
2009
+ if (compactedPath)
2010
+ this.compactionStats.fired += 1;
2011
+ }
2012
+ const afterChars = estimateResidentChars(candidate);
2013
+ const afterToolChars = estimateToolPayloadChars(candidate);
2014
+ // Chars-not-increasing is a NECESSARY condition: the old OR gate let a
2015
+ // fewer-but-bigger candidate (summary stacking) rewrite history.
2016
+ if (afterChars <= beforeChars
2017
+ && (afterChars < beforeChars
2018
+ || afterToolChars < beforeToolChars
2019
+ || candidate.length < before.length)) {
2020
+ if (!compactedPath) {
2021
+ // Request-only thinning must not mutate durable history. Under heap
2022
+ // pressure below the token threshold, the pruned candidate still has to
2023
+ // replace resident memory or the life-support guard frees nothing; the
2024
+ // session log keeps the full tool output.
2025
+ if (shouldAggressivelyPrune) {
2026
+ this.messages = candidate;
2027
+ this.lastInputTokens = null;
2028
+ this.lastAnchorMessageCount = null;
2029
+ this.fileStateTracker?.invalidateReadHistory();
2030
+ }
2031
+ return;
2032
+ }
2033
+ this.applyContextCheckpoint(candidate, "resident", residentSummary, this.getContextRevision?.());
2034
+ if (compactedPath === "resident") {
2035
+ this.compactionStats.resident += 1;
2036
+ }
2037
+ if (compactedPath === "subturn")
2038
+ this.compactionStats.subturn += 1;
2039
+ if (compactedPath && budget) {
2040
+ this.contextEvents.push({ type: "context_compaction", status: "completed", compactionId: this.lastCompactionId, persisted: !!this.onContextCheckpoint, preTokens: budget.estimatedTokens,
2041
+ postTokens: getContextBudget(this.providerId, this.apiModel, candidate).estimatedTokens, contextWindow: budget.contextWindow });
2042
+ }
2043
+ }
2044
+ }
2045
+ /** Persist conversational context first; keep live reminders outside the durable projection. */
2046
+ applyContextCheckpoint(messages, reason, summary, baseRevision, abortSignal) {
2047
+ throwIfAborted(abortSignal);
2048
+ const checkpoint = createContextCheckpoint(messages, reason, summary, baseRevision);
2049
+ this.onContextCheckpoint?.(checkpoint);
2050
+ if (!this.onContextCheckpoint && summary?.trim())
2051
+ this.onCompactionApplied?.(summary);
2052
+ const { leading } = splitLeadingContext(messages);
2053
+ // The compactor may have folded away reminders from its candidate. Their
2054
+ // lifecycle belongs to the live Agent, not the durable summary/checkpoint.
2055
+ const runtimeReminders = splitLeadingContext(this.messages).body.filter(message => message.role === "meta" && message.includeInLlm !== false && !isCompactionSummaryMessage(message));
2056
+ this.messages = [...leading, ...checkpointMessages(checkpoint), ...runtimeReminders];
2057
+ this.lastCompactionId = checkpoint.compactionId;
2058
+ this.lastInputTokens = null;
2059
+ this.lastAnchorMessageCount = null;
2060
+ this.fileStateTracker?.invalidateReadHistory();
2061
+ }
2062
+ appendMessage(message) {
2063
+ if (message.role === "assistant" && message.content) {
2064
+ message.content = sanitizeInternalReminderBlocks(message.content);
2065
+ }
2066
+ if (message.role === "assistant" && message.reasoning) {
2067
+ message.reasoning = sanitizeInternalReasoningText(message.reasoning);
2068
+ }
2069
+ if (message.role === "assistant" && message.providerMetadata) {
2070
+ message.providerMetadata = sanitizeAssistantProviderMetadata(message.providerMetadata, message.modelId ?? message.model);
2071
+ }
2072
+ this.messages.push(message);
2073
+ traceEvent("agent_message_append", {
2074
+ message: summarizeTraceMessage(message),
2075
+ messageCount: this.messages.length,
2076
+ }, {
2077
+ sessionFile: this.sessionID,
2078
+ provider: this._providerId || "none",
2079
+ model: this.apiModel || "none",
2080
+ });
2081
+ this.onMessageAppend?.(message);
2082
+ this.notifyContextChanged();
2083
+ }
2084
+ persistProviderError(error, request) {
2085
+ this.recordProviderError(error, {
2086
+ providerId: this.providerId,
2087
+ modelId: this.apiModel,
2088
+ model: this._model,
2089
+ thinkingLevel: this.thinkingLevel,
2090
+ messageCount: request.messageCount,
2091
+ toolCount: request.toolCount,
2092
+ retry: request.retry,
2093
+ });
2094
+ }
2095
+ recordProviderError(error, context) {
2096
+ if (!this.onProviderError)
2097
+ return;
2098
+ try {
2099
+ this.onProviderError(createSanitizedProviderError(error, context));
2100
+ }
2101
+ catch {
2102
+ // Diagnostics are best-effort and must never replace the provider error.
2103
+ }
2104
+ }
2105
+ appendInterruptedAssistantBoundary(currentAssistant, currentAssistantAppended) {
2106
+ const last = lastProviderMessage(this.messages);
2107
+ if (last?.role === "assistant" && last.error?.aborted) {
2108
+ return false;
2109
+ }
2110
+ const partialText = !currentAssistantAppended ? currentAssistant?.content.trim() : "";
2111
+ const content = partialText
2112
+ ? `${partialText}\n\n${INTERRUPTED_ASSISTANT_CONTENT}`
2113
+ : INTERRUPTED_ASSISTANT_CONTENT;
2114
+ this.appendMessage({
2115
+ role: "assistant",
2116
+ content,
2117
+ reasoning: !currentAssistantAppended ? currentAssistant?.reasoning : undefined,
2118
+ error: {
2119
+ name: "MessageAbortedError",
2120
+ message: "Assistant response was interrupted by the user.",
2121
+ aborted: true,
2122
+ },
2123
+ });
2124
+ return true;
2125
+ }
2126
+ async executeTool(toolCall, cwd, abortSignal, emitUpdate) {
2127
+ throwIfAborted(abortSignal);
2128
+ if (toolCall.name === "exit_plan_mode" && this._mode !== "plan") {
2129
+ return {
2130
+ content: "Ignored exit_plan_mode because plan mode is not active. " +
2131
+ "Continue with the user's request directly using the regular tools.",
2132
+ };
2133
+ }
2134
+ const tool = this.tools.get(toolCall.name);
2135
+ if (!tool) {
2136
+ return {
2137
+ content: `Error: Unknown tool "${toolCall.name}"`,
2138
+ isError: true,
2139
+ };
2140
+ }
2141
+ if (this._mode === "plan" && !tool.readOnly) {
2142
+ return {
2143
+ content: `Error: Tool "${toolCall.name}" is not allowed in plan mode. ` +
2144
+ `In plan mode you may only use read-only tools (read, ls, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, tool_search, question, exit_plan_mode). ` +
2145
+ `To modify files or run commands, present your proposal and call exit_plan_mode so the user can review and approve it.`,
2146
+ isError: true,
2147
+ };
2148
+ }
2149
+ if (tool.deferred && !this.unlockedDeferred.has(tool.name)) {
2150
+ return {
2151
+ content: `Error: Tool "${toolCall.name}" is a deferred tool; its schema is not yet loaded. ` +
2152
+ `Call tool_search first with query "select:${toolCall.name}" to load its schema, then retry.`,
2153
+ isError: true,
2154
+ };
2155
+ }
2156
+ if (toolCall.argsCorrupt) {
2157
+ return {
2158
+ content: `Error: The arguments for "${toolCall.name}" failed to parse as JSON, indicating the provider returned truncated or malformed tool arguments. ` +
2159
+ `Re-issue the call with valid JSON arguments; do not assume the previous attempt ran.`,
2160
+ isError: true,
2161
+ status: "blocked",
2162
+ metadata: { kind: "security", reason: "args_corrupt" },
2163
+ };
2164
+ }
2165
+ let preparedArgs = toolCall.parsedArgs;
2166
+ if (tool.prepareArguments) {
2167
+ try {
2168
+ preparedArgs = tool.prepareArguments(preparedArgs);
2169
+ }
2170
+ catch (err) {
2171
+ return {
2172
+ content: `Error: Tool "${toolCall.name}" arguments could not be normalized before execution: ` +
2173
+ `${err instanceof Error ? err.message : String(err)}. Re-issue the call with valid arguments.`,
2174
+ isError: true,
2175
+ status: "blocked",
2176
+ metadata: { kind: "security", reason: "args_prepare_failed" },
2177
+ };
2178
+ }
2179
+ }
2180
+ const missingRequired = findMissingRequiredArgs(tool.parameters, preparedArgs);
2181
+ if (missingRequired.length > 0) {
2182
+ return {
2183
+ content: `Error: Tool "${toolCall.name}" was called without required argument${missingRequired.length === 1 ? "" : "s"}: ${missingRequired.map((name) => `"${name}"`).join(", ")}. ` +
2184
+ `Re-issue the call with all required fields filled. Do not assume the previous attempt ran with default values.`,
2185
+ isError: true,
2186
+ status: "blocked",
2187
+ metadata: { kind: "security", reason: "missing_required_args", missing: missingRequired },
2188
+ };
2189
+ }
2190
+ try {
2191
+ return await tool.execute(preparedArgs, {
2192
+ cwd,
2193
+ sessionID: this.sessionID,
2194
+ abortSignal,
2195
+ toolCall: { id: toolCall.id, name: toolCall.name },
2196
+ agent: this,
2197
+ emitUpdate,
2198
+ });
2199
+ }
2200
+ catch (err) {
2201
+ return {
2202
+ content: `Error executing ${toolCall.name}: ${err.message || String(err)}`,
2203
+ isError: true,
2204
+ };
2205
+ }
2206
+ }
2207
+ }
2208
+ function serializedMessageBytes(messages) {
2209
+ return Buffer.byteLength(JSON.stringify(messages), "utf8");
2210
+ }