whale-code 6.5.4 → 6.5.6

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 (853) hide show
  1. package/README.md +39 -31
  2. package/bin/{swagmanager-mcp.js → whale-code.js} +17 -2
  3. package/dist/cli/app.js +148 -72
  4. package/dist/cli/app.js.map +1 -0
  5. package/dist/cli/chat/AgentSelector.js +105 -10
  6. package/dist/cli/chat/AgentSelector.js.map +1 -0
  7. package/dist/cli/chat/ChatApp.d.ts +31 -0
  8. package/dist/cli/chat/ChatApp.js +539 -286
  9. package/dist/cli/chat/ChatApp.js.map +1 -0
  10. package/dist/cli/chat/ChatInput.js +1088 -770
  11. package/dist/cli/chat/ChatInput.js.map +1 -0
  12. package/dist/cli/chat/MarkdownText.js +39 -14
  13. package/dist/cli/chat/MarkdownText.js.map +1 -0
  14. package/dist/cli/chat/MemoryManager.js +181 -46
  15. package/dist/cli/chat/MemoryManager.js.map +1 -0
  16. package/dist/cli/chat/MessageList.d.ts +2 -3
  17. package/dist/cli/chat/MessageList.js +186 -45
  18. package/dist/cli/chat/MessageList.js.map +1 -0
  19. package/dist/cli/chat/ModelSelector.js +282 -63
  20. package/dist/cli/chat/ModelSelector.js.map +1 -0
  21. package/dist/cli/chat/NodeManager.js +165 -75
  22. package/dist/cli/chat/NodeManager.js.map +1 -0
  23. package/dist/cli/chat/NodeSelector.js +171 -30
  24. package/dist/cli/chat/NodeSelector.js.map +1 -0
  25. package/dist/cli/chat/PlanApproval.js +281 -57
  26. package/dist/cli/chat/PlanApproval.js.map +1 -0
  27. package/dist/cli/chat/RewindViewer.js +559 -144
  28. package/dist/cli/chat/RewindViewer.js.map +1 -0
  29. package/dist/cli/chat/SessionManager.js +137 -30
  30. package/dist/cli/chat/SessionManager.js.map +1 -0
  31. package/dist/cli/chat/SlashMenu.js +293 -164
  32. package/dist/cli/chat/SlashMenu.js.map +1 -0
  33. package/dist/cli/chat/StatusBar.js +172 -9
  34. package/dist/cli/chat/StatusBar.js.map +1 -0
  35. package/dist/cli/chat/StoreSelector.js +147 -18
  36. package/dist/cli/chat/StoreSelector.js.map +1 -0
  37. package/dist/cli/chat/StreamingText.d.ts +1 -5
  38. package/dist/cli/chat/StreamingText.js +22 -7
  39. package/dist/cli/chat/StreamingText.js.map +1 -0
  40. package/dist/cli/chat/SubagentPanel.d.ts +1 -2
  41. package/dist/cli/chat/SubagentPanel.js +612 -72
  42. package/dist/cli/chat/SubagentPanel.js.map +1 -0
  43. package/dist/cli/chat/TeamPanel.d.ts +1 -0
  44. package/dist/cli/chat/TeamPanel.js +230 -30
  45. package/dist/cli/chat/TeamPanel.js.map +1 -0
  46. package/dist/cli/chat/ThemeSelector.js +84 -24
  47. package/dist/cli/chat/ThemeSelector.js.map +1 -0
  48. package/dist/cli/chat/ToolIndicator.js +1476 -371
  49. package/dist/cli/chat/ToolIndicator.js.map +1 -0
  50. package/dist/cli/chat/hooks/useAgentLoop.d.ts +1 -0
  51. package/dist/cli/chat/hooks/useAgentLoop.js +481 -367
  52. package/dist/cli/chat/hooks/useAgentLoop.js.map +1 -0
  53. package/dist/cli/chat/hooks/useSlashCommands.d.ts +3 -14
  54. package/dist/cli/chat/hooks/useSlashCommands.js +744 -572
  55. package/dist/cli/chat/hooks/useSlashCommands.js.map +1 -0
  56. package/dist/cli/commands/config-cmd.js +56 -57
  57. package/dist/cli/commands/config-cmd.js.map +1 -0
  58. package/dist/cli/commands/db.js +184 -169
  59. package/dist/cli/commands/db.js.map +1 -0
  60. package/dist/cli/commands/doctor.js +212 -122
  61. package/dist/cli/commands/doctor.js.map +1 -0
  62. package/dist/cli/commands/init.js +211 -244
  63. package/dist/cli/commands/init.js.map +1 -0
  64. package/dist/cli/commands/mcp.js +127 -122
  65. package/dist/cli/commands/mcp.js.map +1 -0
  66. package/dist/cli/login/LoginApp.js +355 -141
  67. package/dist/cli/login/LoginApp.js.map +1 -0
  68. package/dist/cli/print-mode.js +196 -177
  69. package/dist/cli/print-mode.js.map +1 -0
  70. package/dist/cli/serve-mode.js +615 -530
  71. package/dist/cli/serve-mode.js.map +1 -0
  72. package/dist/cli/services/agent-config.d.ts +29 -0
  73. package/dist/cli/services/agent-config.js +91 -0
  74. package/dist/cli/services/agent-config.js.map +1 -0
  75. package/dist/cli/services/agent-definitions.d.ts +4 -1
  76. package/dist/cli/services/agent-definitions.js +97 -56
  77. package/dist/cli/services/agent-definitions.js.map +1 -0
  78. package/dist/cli/services/agent-events.js +225 -162
  79. package/dist/cli/services/agent-events.js.map +1 -0
  80. package/dist/cli/services/agent-loop.js +978 -669
  81. package/dist/cli/services/agent-loop.js.map +1 -0
  82. package/dist/cli/services/agent-worker-base.d.ts +35 -5
  83. package/dist/cli/services/agent-worker-base.js +337 -153
  84. package/dist/cli/services/agent-worker-base.js.map +1 -0
  85. package/dist/cli/services/api-retry.js +69 -64
  86. package/dist/cli/services/api-retry.js.map +1 -0
  87. package/dist/cli/services/auth-service.d.ts +3 -3
  88. package/dist/cli/services/auth-service.js +209 -132
  89. package/dist/cli/services/auth-service.js.map +1 -0
  90. package/dist/cli/services/background-processes.js +343 -267
  91. package/dist/cli/services/background-processes.js.map +1 -0
  92. package/dist/cli/services/browser-auth.d.ts +2 -2
  93. package/dist/cli/services/browser-auth.js +159 -118
  94. package/dist/cli/services/browser-auth.js.map +1 -0
  95. package/dist/cli/services/claude-md-loader.js +40 -36
  96. package/dist/cli/services/claude-md-loader.js.map +1 -0
  97. package/dist/cli/services/config-store.d.ts +9 -4
  98. package/dist/cli/services/config-store.js +164 -117
  99. package/dist/cli/services/config-store.js.map +1 -0
  100. package/dist/cli/services/debug-log.d.ts +1 -1
  101. package/dist/cli/services/debug-log.js +34 -35
  102. package/dist/cli/services/debug-log.js.map +1 -0
  103. package/dist/cli/services/env-detect.d.ts +7 -0
  104. package/dist/cli/services/env-detect.js +9 -0
  105. package/dist/cli/services/env-detect.js.map +1 -0
  106. package/dist/cli/services/error-logger.d.ts +2 -3
  107. package/dist/cli/services/error-logger.js +189 -180
  108. package/dist/cli/services/error-logger.js.map +1 -0
  109. package/dist/cli/services/file-history.d.ts +1 -1
  110. package/dist/cli/services/file-history.js +50 -54
  111. package/dist/cli/services/file-history.js.map +1 -0
  112. package/dist/cli/services/format-server-response.js +332 -372
  113. package/dist/cli/services/format-server-response.js.map +1 -0
  114. package/dist/cli/services/git-context.js +61 -45
  115. package/dist/cli/services/git-context.js.map +1 -0
  116. package/dist/cli/services/hooks.d.ts +2 -2
  117. package/dist/cli/services/hooks.js +195 -180
  118. package/dist/cli/services/hooks.js.map +1 -0
  119. package/dist/cli/services/ink-incremental.d.ts +19 -0
  120. package/dist/cli/services/ink-incremental.js +59 -0
  121. package/dist/cli/services/ink-incremental.js.map +1 -0
  122. package/dist/cli/services/ink-resize-fix.js +54 -44
  123. package/dist/cli/services/ink-resize-fix.js.map +1 -0
  124. package/dist/cli/services/ink-sync-output.d.ts +12 -0
  125. package/dist/cli/services/ink-sync-output.js +16 -0
  126. package/dist/cli/services/ink-sync-output.js.map +1 -0
  127. package/dist/cli/services/interactive-tools.js +268 -212
  128. package/dist/cli/services/interactive-tools.js.map +1 -0
  129. package/dist/cli/services/keybinding-manager.d.ts +11 -1
  130. package/dist/cli/services/keybinding-manager.js +126 -63
  131. package/dist/cli/services/keybinding-manager.js.map +1 -0
  132. package/dist/cli/services/local-tools.d.ts +1 -1
  133. package/dist/cli/services/local-tools.js +939 -656
  134. package/dist/cli/services/local-tools.js.map +1 -0
  135. package/dist/cli/services/lsp-manager.js +757 -594
  136. package/dist/cli/services/lsp-manager.js.map +1 -0
  137. package/dist/cli/services/mcp-client.d.ts +1 -1
  138. package/dist/cli/services/mcp-client.js +173 -134
  139. package/dist/cli/services/mcp-client.js.map +1 -0
  140. package/dist/cli/services/memory-manager.js +53 -40
  141. package/dist/cli/services/memory-manager.js.map +1 -0
  142. package/dist/cli/services/model-manager.js +55 -40
  143. package/dist/cli/services/model-manager.js.map +1 -0
  144. package/dist/cli/services/model-router.js +115 -85
  145. package/dist/cli/services/model-router.js.map +1 -0
  146. package/dist/cli/services/paths.d.ts +30 -0
  147. package/dist/cli/services/paths.js +81 -0
  148. package/dist/cli/services/paths.js.map +1 -0
  149. package/dist/cli/services/permission-modes.js +32 -25
  150. package/dist/cli/services/permission-modes.js.map +1 -0
  151. package/dist/cli/services/rewind.js +182 -168
  152. package/dist/cli/services/rewind.js.map +1 -0
  153. package/dist/cli/services/ripgrep.js +115 -115
  154. package/dist/cli/services/ripgrep.js.map +1 -0
  155. package/dist/cli/services/sandbox.d.ts +1 -1
  156. package/dist/cli/services/sandbox.js +58 -37
  157. package/dist/cli/services/sandbox.js.map +1 -0
  158. package/dist/cli/services/server-tools.js +738 -565
  159. package/dist/cli/services/server-tools.js.map +1 -0
  160. package/dist/cli/services/session-persistence.js +69 -74
  161. package/dist/cli/services/session-persistence.js.map +1 -0
  162. package/dist/cli/services/subagent-worker.js +42 -27
  163. package/dist/cli/services/subagent-worker.js.map +1 -0
  164. package/dist/cli/services/subagent.d.ts +2 -0
  165. package/dist/cli/services/subagent.js +606 -430
  166. package/dist/cli/services/subagent.js.map +1 -0
  167. package/dist/cli/services/system-prompt.js +86 -78
  168. package/dist/cli/services/system-prompt.js.map +1 -0
  169. package/dist/cli/services/task-decomposer.d.ts +1 -1
  170. package/dist/cli/services/task-decomposer.js +172 -139
  171. package/dist/cli/services/task-decomposer.js.map +1 -0
  172. package/dist/cli/services/team-lead.d.ts +2 -2
  173. package/dist/cli/services/team-lead.js +727 -529
  174. package/dist/cli/services/team-lead.js.map +1 -0
  175. package/dist/cli/services/team-state.js +319 -319
  176. package/dist/cli/services/team-state.js.map +1 -0
  177. package/dist/cli/services/teammate.d.ts +8 -2
  178. package/dist/cli/services/teammate.js +862 -560
  179. package/dist/cli/services/teammate.js.map +1 -0
  180. package/dist/cli/services/telemetry.d.ts +6 -1
  181. package/dist/cli/services/telemetry.js +180 -157
  182. package/dist/cli/services/telemetry.js.map +1 -0
  183. package/dist/cli/services/tools/agent-tools.d.ts +3 -3
  184. package/dist/cli/services/tools/agent-tools.js +480 -322
  185. package/dist/cli/services/tools/agent-tools.js.map +1 -0
  186. package/dist/cli/services/tools/file-ops.js +563 -450
  187. package/dist/cli/services/tools/file-ops.js.map +1 -0
  188. package/dist/cli/services/tools/search-tools.js +231 -162
  189. package/dist/cli/services/tools/search-tools.js.map +1 -0
  190. package/dist/cli/services/tools/shell-exec.js +197 -151
  191. package/dist/cli/services/tools/shell-exec.js.map +1 -0
  192. package/dist/cli/services/tools/task-manager.js +206 -173
  193. package/dist/cli/services/tools/task-manager.js.map +1 -0
  194. package/dist/cli/services/tools/web-tools.js +388 -341
  195. package/dist/cli/services/tools/web-tools.js.map +1 -0
  196. package/dist/cli/setup/SetupApp.d.ts +2 -2
  197. package/dist/cli/setup/SetupApp.js +608 -160
  198. package/dist/cli/setup/SetupApp.js.map +1 -0
  199. package/dist/cli/shared/ErrorBoundary.d.ts +22 -0
  200. package/dist/cli/shared/ErrorBoundary.js +73 -0
  201. package/dist/cli/shared/ErrorBoundary.js.map +1 -0
  202. package/dist/cli/shared/MatrixIntro.js +66 -69
  203. package/dist/cli/shared/MatrixIntro.js.map +1 -0
  204. package/dist/cli/shared/SpinnerSlot.d.ts +14 -0
  205. package/dist/cli/shared/SpinnerSlot.js +63 -0
  206. package/dist/cli/shared/SpinnerSlot.js.map +1 -0
  207. package/dist/cli/shared/Theme.d.ts +1 -1
  208. package/dist/cli/shared/Theme.js +136 -92
  209. package/dist/cli/shared/Theme.js.map +1 -0
  210. package/dist/cli/shared/WhaleBanner.js +99 -11
  211. package/dist/cli/shared/WhaleBanner.js.map +1 -0
  212. package/dist/cli/shared/markdown.d.ts +3 -1
  213. package/dist/cli/shared/markdown.js +736 -674
  214. package/dist/cli/shared/markdown.js.map +1 -0
  215. package/dist/cli/shared/marked-terminal.d.js +2 -0
  216. package/dist/cli/shared/marked-terminal.d.js.map +1 -0
  217. package/dist/cli/shared/theme-manager.js +99 -90
  218. package/dist/cli/shared/theme-manager.js.map +1 -0
  219. package/dist/cli/shared/theme-presets.js +256 -254
  220. package/dist/cli/shared/theme-presets.js.map +1 -0
  221. package/dist/cli/status/StatusApp.js +235 -86
  222. package/dist/cli/status/StatusApp.js.map +1 -0
  223. package/dist/cli/stores/StoreApp.js +275 -65
  224. package/dist/cli/stores/StoreApp.js.map +1 -0
  225. package/dist/index.d.ts +2 -2
  226. package/dist/index.js +509 -396
  227. package/dist/index.js.map +1 -0
  228. package/dist/local-agent/connection.d.ts +2 -2
  229. package/dist/local-agent/connection.js +352 -293
  230. package/dist/local-agent/connection.js.map +1 -0
  231. package/dist/local-agent/discovery.js +259 -122
  232. package/dist/local-agent/discovery.js.map +1 -0
  233. package/dist/local-agent/executor.js +216 -193
  234. package/dist/local-agent/executor.js.map +1 -0
  235. package/dist/local-agent/index.d.ts +2 -2
  236. package/dist/local-agent/index.js +156 -156
  237. package/dist/local-agent/index.js.map +1 -0
  238. package/dist/node/adapters/base.js +18 -8
  239. package/dist/node/adapters/base.js.map +1 -0
  240. package/dist/node/adapters/discord.js +286 -275
  241. package/dist/node/adapters/discord.js.map +1 -0
  242. package/dist/node/adapters/email.js +189 -202
  243. package/dist/node/adapters/email.js.map +1 -0
  244. package/dist/node/adapters/imessage.js +145 -142
  245. package/dist/node/adapters/imessage.js.map +1 -0
  246. package/dist/node/adapters/slack.js +237 -236
  247. package/dist/node/adapters/slack.js.map +1 -0
  248. package/dist/node/adapters/sms.js +149 -151
  249. package/dist/node/adapters/sms.js.map +1 -0
  250. package/dist/node/adapters/telegram.js +88 -92
  251. package/dist/node/adapters/telegram.js.map +1 -0
  252. package/dist/node/adapters/webchat.js +160 -136
  253. package/dist/node/adapters/webchat.js.map +1 -0
  254. package/dist/node/adapters/whatsapp.js +212 -215
  255. package/dist/node/adapters/whatsapp.js.map +1 -0
  256. package/dist/node/cli.js +884 -653
  257. package/dist/node/cli.js.map +1 -0
  258. package/dist/node/config.js +20 -18
  259. package/dist/node/config.js.map +1 -0
  260. package/dist/node/gateway-client.js +191 -181
  261. package/dist/node/gateway-client.js.map +1 -0
  262. package/dist/node/portal/clipboard.js +161 -130
  263. package/dist/node/portal/clipboard.js.map +1 -0
  264. package/dist/node/portal/discovery.js +51 -45
  265. package/dist/node/portal/discovery.js.map +1 -0
  266. package/dist/node/portal/forward.js +64 -58
  267. package/dist/node/portal/forward.js.map +1 -0
  268. package/dist/node/portal/index.js +246 -221
  269. package/dist/node/portal/index.js.map +1 -0
  270. package/dist/node/portal/multiplexer.js +192 -182
  271. package/dist/node/portal/multiplexer.js.map +1 -0
  272. package/dist/node/portal/permissions.js +102 -70
  273. package/dist/node/portal/permissions.js.map +1 -0
  274. package/dist/node/portal/protocol.js +153 -116
  275. package/dist/node/portal/protocol.js.map +1 -0
  276. package/dist/node/portal/screen.js +80 -69
  277. package/dist/node/portal/screen.js.map +1 -0
  278. package/dist/node/portal/session.js +124 -117
  279. package/dist/node/portal/session.js.map +1 -0
  280. package/dist/node/portal/shell.js +140 -113
  281. package/dist/node/portal/shell.js.map +1 -0
  282. package/dist/node/portal/stream.js +77 -75
  283. package/dist/node/portal/stream.js.map +1 -0
  284. package/dist/node/portal/transfer.js +190 -167
  285. package/dist/node/portal/transfer.js.map +1 -0
  286. package/dist/node/portal/ui.js +124 -99
  287. package/dist/node/portal/ui.js.map +1 -0
  288. package/dist/node/remote-desktop/compile-helper.js +50 -45
  289. package/dist/node/remote-desktop/compile-helper.js.map +1 -0
  290. package/dist/node/remote-desktop/index.js +215 -187
  291. package/dist/node/remote-desktop/index.js.map +1 -0
  292. package/dist/node/remote-desktop/protocol.js +45 -29
  293. package/dist/node/remote-desktop/protocol.js.map +1 -0
  294. package/dist/node/runtime.js +493 -410
  295. package/dist/node/runtime.js.map +1 -0
  296. package/dist/server/handlers/__test-utils__/test-db.js +39 -89
  297. package/dist/server/handlers/__test-utils__/test-db.js.map +1 -0
  298. package/dist/server/handlers/analytics.js +467 -261
  299. package/dist/server/handlers/analytics.js.map +1 -0
  300. package/dist/server/handlers/api-docs.d.ts +6 -0
  301. package/dist/server/handlers/api-docs.js +1613 -0
  302. package/dist/server/handlers/api-docs.js.map +1 -0
  303. package/dist/server/handlers/api-keys.js +295 -232
  304. package/dist/server/handlers/api-keys.js.map +1 -0
  305. package/dist/server/handlers/billing.js +330 -239
  306. package/dist/server/handlers/billing.js.map +1 -0
  307. package/dist/server/handlers/browser.js +468 -395
  308. package/dist/server/handlers/browser.js.map +1 -0
  309. package/dist/server/handlers/catalog.js +1377 -978
  310. package/dist/server/handlers/catalog.js.map +1 -0
  311. package/dist/server/handlers/clickhouse.js +157 -109
  312. package/dist/server/handlers/clickhouse.js.map +1 -0
  313. package/dist/server/handlers/comms.d.ts +0 -53
  314. package/dist/server/handlers/comms.js +1443 -970
  315. package/dist/server/handlers/comms.js.map +1 -0
  316. package/dist/server/handlers/creations.js +461 -394
  317. package/dist/server/handlers/creations.js.map +1 -0
  318. package/dist/server/handlers/crm.js +1082 -791
  319. package/dist/server/handlers/crm.js.map +1 -0
  320. package/dist/server/handlers/discovery.js +251 -232
  321. package/dist/server/handlers/discovery.js.map +1 -0
  322. package/dist/server/handlers/embeddings.js +241 -164
  323. package/dist/server/handlers/embeddings.js.map +1 -0
  324. package/dist/server/handlers/enrichment.js +887 -718
  325. package/dist/server/handlers/enrichment.js.map +1 -0
  326. package/dist/server/handlers/image-gen.js +467 -376
  327. package/dist/server/handlers/image-gen.js.map +1 -0
  328. package/dist/server/handlers/inventory.js +797 -424
  329. package/dist/server/handlers/inventory.js.map +1 -0
  330. package/dist/server/handlers/kali.js +272 -230
  331. package/dist/server/handlers/kali.js.map +1 -0
  332. package/dist/server/handlers/llm-providers.js +803 -580
  333. package/dist/server/handlers/llm-providers.js.map +1 -0
  334. package/dist/server/handlers/local-agent.js +133 -105
  335. package/dist/server/handlers/local-agent.js.map +1 -0
  336. package/dist/server/handlers/media.js +1179 -857
  337. package/dist/server/handlers/media.js.map +1 -0
  338. package/dist/server/handlers/meta-ads.js +2669 -2093
  339. package/dist/server/handlers/meta-ads.js.map +1 -0
  340. package/dist/server/handlers/nodes.js +1321 -913
  341. package/dist/server/handlers/nodes.js.map +1 -0
  342. package/dist/server/handlers/operations.js +183 -157
  343. package/dist/server/handlers/operations.js.map +1 -0
  344. package/dist/server/handlers/platform.js +346 -210
  345. package/dist/server/handlers/platform.js.map +1 -0
  346. package/dist/server/handlers/remove-bg.js +118 -86
  347. package/dist/server/handlers/remove-bg.js.map +1 -0
  348. package/dist/server/handlers/storefront.js +586 -446
  349. package/dist/server/handlers/storefront.js.map +1 -0
  350. package/dist/server/handlers/supply-chain.js +546 -326
  351. package/dist/server/handlers/supply-chain.js.map +1 -0
  352. package/dist/server/handlers/transcription.js +106 -97
  353. package/dist/server/handlers/transcription.js.map +1 -0
  354. package/dist/server/handlers/video-gen.js +593 -424
  355. package/dist/server/handlers/video-gen.js.map +1 -0
  356. package/dist/server/handlers/voice.js +1458 -1017
  357. package/dist/server/handlers/voice.js.map +1 -0
  358. package/dist/server/handlers/workflow-steps.js +2837 -2116
  359. package/dist/server/handlers/workflow-steps.js.map +1 -0
  360. package/dist/server/handlers/workflows.js +1630 -933
  361. package/dist/server/handlers/workflows.js.map +1 -0
  362. package/dist/server/index.js +3166 -2390
  363. package/dist/server/index.js.map +1 -0
  364. package/dist/server/lib/batch-client.js +471 -409
  365. package/dist/server/lib/batch-client.js.map +1 -0
  366. package/dist/server/lib/clickhouse-buffer.js +118 -104
  367. package/dist/server/lib/clickhouse-buffer.js.map +1 -0
  368. package/dist/server/lib/clickhouse-client.js +107 -107
  369. package/dist/server/lib/clickhouse-client.js.map +1 -0
  370. package/dist/server/lib/coa-renderer.js +1786 -356
  371. package/dist/server/lib/coa-renderer.js.map +1 -0
  372. package/dist/server/lib/code-worker-pool.js +227 -177
  373. package/dist/server/lib/code-worker-pool.js.map +1 -0
  374. package/dist/server/lib/code-worker.js +174 -164
  375. package/dist/server/lib/code-worker.js.map +1 -0
  376. package/dist/server/lib/compaction-service.d.ts +2 -12
  377. package/dist/server/lib/compaction-service.js +74 -184
  378. package/dist/server/lib/compaction-service.js.map +1 -0
  379. package/dist/server/lib/logger.js +36 -24
  380. package/dist/server/lib/logger.js.map +1 -0
  381. package/dist/server/lib/otel.js +101 -80
  382. package/dist/server/lib/otel.js.map +1 -0
  383. package/dist/server/lib/pdf-renderer.d.ts +1 -1
  384. package/dist/server/lib/pdf-renderer.js +954 -776
  385. package/dist/server/lib/pdf-renderer.js.map +1 -0
  386. package/dist/server/lib/prompt-sanitizer.js +188 -108
  387. package/dist/server/lib/prompt-sanitizer.js.map +1 -0
  388. package/dist/server/lib/provider-capabilities.js +136 -138
  389. package/dist/server/lib/provider-capabilities.js.map +1 -0
  390. package/dist/server/lib/provider-failover.js +190 -168
  391. package/dist/server/lib/provider-failover.js.map +1 -0
  392. package/dist/server/lib/rate-limiter.js +186 -117
  393. package/dist/server/lib/rate-limiter.js.map +1 -0
  394. package/dist/server/lib/react-pdf-layout.js +551 -382
  395. package/dist/server/lib/react-pdf-layout.js.map +1 -0
  396. package/dist/server/lib/server-agent-loop.d.ts +9 -0
  397. package/dist/server/lib/server-agent-loop.js +906 -624
  398. package/dist/server/lib/server-agent-loop.js.map +1 -0
  399. package/dist/server/lib/server-subagent.d.ts +2 -0
  400. package/dist/server/lib/server-subagent.js +260 -162
  401. package/dist/server/lib/server-subagent.js.map +1 -0
  402. package/dist/server/lib/session-checkpoint.js +105 -96
  403. package/dist/server/lib/session-checkpoint.js.map +1 -0
  404. package/dist/server/lib/ssrf-guard.js +193 -184
  405. package/dist/server/lib/ssrf-guard.js.map +1 -0
  406. package/dist/server/lib/supabase-client.js +94 -82
  407. package/dist/server/lib/supabase-client.js.map +1 -0
  408. package/dist/server/lib/template-resolver.js +154 -176
  409. package/dist/server/lib/template-resolver.js.map +1 -0
  410. package/dist/server/lib/utils.js +242 -133
  411. package/dist/server/lib/utils.js.map +1 -0
  412. package/dist/server/local-agent-gateway.d.ts +2 -2
  413. package/dist/server/local-agent-gateway.js +785 -627
  414. package/dist/server/local-agent-gateway.js.map +1 -0
  415. package/dist/server/providers/anthropic.js +254 -176
  416. package/dist/server/providers/anthropic.js.map +1 -0
  417. package/dist/server/providers/bedrock.js +221 -162
  418. package/dist/server/providers/bedrock.js.map +1 -0
  419. package/dist/server/providers/gemini.js +548 -418
  420. package/dist/server/providers/gemini.js.map +1 -0
  421. package/dist/server/providers/openai.js +571 -437
  422. package/dist/server/providers/openai.js.map +1 -0
  423. package/dist/server/providers/registry.js +23 -18
  424. package/dist/server/providers/registry.js.map +1 -0
  425. package/dist/server/providers/shared.js +123 -95
  426. package/dist/server/providers/shared.js.map +1 -0
  427. package/dist/server/providers/types.js +1 -11
  428. package/dist/server/providers/types.js.map +1 -0
  429. package/dist/server/proxy-handlers.js +209 -165
  430. package/dist/server/proxy-handlers.js.map +1 -0
  431. package/dist/server/tool-router.d.ts +13 -0
  432. package/dist/server/tool-router.js +960 -598
  433. package/dist/server/tool-router.js.map +1 -0
  434. package/dist/server/validation.js +248 -188
  435. package/dist/server/validation.js.map +1 -0
  436. package/dist/server/worker.js +202 -133
  437. package/dist/server/worker.js.map +1 -0
  438. package/dist/setup.d.ts +2 -2
  439. package/dist/setup.js +151 -147
  440. package/dist/setup.js.map +1 -0
  441. package/dist/shared/agent-core.d.ts +191 -24
  442. package/dist/shared/agent-core.js +971 -462
  443. package/dist/shared/agent-core.js.map +1 -0
  444. package/dist/shared/anthropic-types.js +1 -6
  445. package/dist/shared/anthropic-types.js.map +1 -0
  446. package/dist/shared/api-client.d.ts +17 -9
  447. package/dist/shared/api-client.js +419 -327
  448. package/dist/shared/api-client.js.map +1 -0
  449. package/dist/shared/compaction.d.ts +36 -0
  450. package/dist/shared/compaction.js +138 -0
  451. package/dist/shared/compaction.js.map +1 -0
  452. package/dist/shared/constants.js +67 -64
  453. package/dist/shared/constants.js.map +1 -0
  454. package/dist/shared/sse-parser.js +221 -219
  455. package/dist/shared/sse-parser.js.map +1 -0
  456. package/dist/shared/tool-dispatch.d.ts +4 -2
  457. package/dist/shared/tool-dispatch.js +226 -165
  458. package/dist/shared/tool-dispatch.js.map +1 -0
  459. package/dist/shared/types.js +1 -6
  460. package/dist/shared/types.js.map +1 -0
  461. package/dist/types/cli-highlight.d.js +2 -0
  462. package/dist/types/cli-highlight.d.js.map +1 -0
  463. package/dist/types/diff.d.js +2 -0
  464. package/dist/types/diff.d.js.map +1 -0
  465. package/dist/types/pdf-parse.d.js +2 -0
  466. package/dist/types/pdf-parse.d.js.map +1 -0
  467. package/dist/updater.d.ts +1 -1
  468. package/dist/updater.js +118 -92
  469. package/dist/updater.js.map +1 -0
  470. package/dist/webchat/widget.js +227 -380
  471. package/dist/webchat/widget.js.map +1 -0
  472. package/package.json +22 -10
  473. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  474. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  475. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  476. package/vendor/ink/build/apply-styles.js +175 -0
  477. package/vendor/ink/build/build-layout.js +77 -0
  478. package/vendor/ink/build/calculate-wrapped-text.js +53 -0
  479. package/vendor/ink/build/colorize.d.ts +3 -0
  480. package/vendor/ink/build/colorize.js +48 -0
  481. package/vendor/ink/build/colorize.js.map +1 -0
  482. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  483. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  484. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  485. package/vendor/ink/build/components/App.d.ts +18 -0
  486. package/vendor/ink/build/components/App.js +351 -0
  487. package/vendor/ink/build/components/App.js.map +1 -0
  488. package/vendor/ink/build/components/AppContext.d.ts +15 -0
  489. package/vendor/ink/build/components/AppContext.js +11 -0
  490. package/vendor/ink/build/components/AppContext.js.map +1 -0
  491. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  492. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  493. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  494. package/vendor/ink/build/components/Box.d.ts +117 -0
  495. package/vendor/ink/build/components/Box.js +34 -0
  496. package/vendor/ink/build/components/Box.js.map +1 -0
  497. package/vendor/ink/build/components/Color.js +62 -0
  498. package/vendor/ink/build/components/Cursor.d.ts +83 -0
  499. package/vendor/ink/build/components/Cursor.js +53 -0
  500. package/vendor/ink/build/components/Cursor.js.map +1 -0
  501. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  502. package/vendor/ink/build/components/CursorContext.js +8 -0
  503. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  504. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  505. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  506. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  507. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  508. package/vendor/ink/build/components/ErrorOverview.js +84 -0
  509. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  510. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  511. package/vendor/ink/build/components/FocusContext.js +17 -0
  512. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  513. package/vendor/ink/build/components/Newline.d.ts +13 -0
  514. package/vendor/ink/build/components/Newline.js +8 -0
  515. package/vendor/ink/build/components/Newline.js.map +1 -0
  516. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  517. package/vendor/ink/build/components/Spacer.js +11 -0
  518. package/vendor/ink/build/components/Spacer.js.map +1 -0
  519. package/vendor/ink/build/components/Static.d.ts +24 -0
  520. package/vendor/ink/build/components/Static.js +28 -0
  521. package/vendor/ink/build/components/Static.js.map +1 -0
  522. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  523. package/vendor/ink/build/components/StderrContext.js +13 -0
  524. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  525. package/vendor/ink/build/components/StdinContext.d.ts +22 -0
  526. package/vendor/ink/build/components/StdinContext.js +19 -0
  527. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  528. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  529. package/vendor/ink/build/components/StdoutContext.js +13 -0
  530. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  531. package/vendor/ink/build/components/Text.d.ts +55 -0
  532. package/vendor/ink/build/components/Text.js +50 -0
  533. package/vendor/ink/build/components/Text.js.map +1 -0
  534. package/vendor/ink/build/components/Transform.d.ts +16 -0
  535. package/vendor/ink/build/components/Transform.js +15 -0
  536. package/vendor/ink/build/components/Transform.js.map +1 -0
  537. package/vendor/ink/build/cursor-helpers.d.ts +38 -0
  538. package/vendor/ink/build/cursor-helpers.js +56 -0
  539. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  540. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  541. package/vendor/ink/build/devtools-window-polyfill.js +65 -0
  542. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  543. package/vendor/ink/build/devtools.d.ts +1 -0
  544. package/vendor/ink/build/devtools.js +11 -0
  545. package/vendor/ink/build/devtools.js.map +1 -0
  546. package/vendor/ink/build/dom.d.ts +56 -0
  547. package/vendor/ink/build/dom.js +124 -0
  548. package/vendor/ink/build/dom.js.map +1 -0
  549. package/vendor/ink/build/experimental/apply-style.js +140 -0
  550. package/vendor/ink/build/experimental/dom.js +123 -0
  551. package/vendor/ink/build/experimental/output.js +91 -0
  552. package/vendor/ink/build/experimental/reconciler.js +141 -0
  553. package/vendor/ink/build/experimental/renderer.js +81 -0
  554. package/vendor/ink/build/get-max-width.d.ts +3 -0
  555. package/vendor/ink/build/get-max-width.js +10 -0
  556. package/vendor/ink/build/get-max-width.js.map +1 -0
  557. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  558. package/vendor/ink/build/hooks/use-app.js +8 -0
  559. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  560. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  561. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  562. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  563. package/vendor/ink/build/hooks/use-focus-manager.d.ts +28 -0
  564. package/vendor/ink/build/hooks/use-focus-manager.js +17 -0
  565. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  566. package/vendor/ink/build/hooks/use-focus.d.ts +29 -0
  567. package/vendor/ink/build/hooks/use-focus.js +42 -0
  568. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  569. package/vendor/ink/build/hooks/use-input.d.ts +131 -0
  570. package/vendor/ink/build/hooks/use-input.js +124 -0
  571. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  572. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +5 -0
  573. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +11 -0
  574. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  575. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  576. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  577. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  578. package/vendor/ink/build/hooks/use-stdin.d.ts +5 -0
  579. package/vendor/ink/build/hooks/use-stdin.js +8 -0
  580. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  581. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  582. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  583. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  584. package/vendor/ink/build/hooks/useInput.js +38 -0
  585. package/vendor/ink/build/index.d.ts +34 -0
  586. package/vendor/ink/build/index.js +20 -0
  587. package/vendor/ink/build/index.js.map +1 -0
  588. package/vendor/ink/build/ink.d.ts +90 -0
  589. package/vendor/ink/build/ink.js +654 -0
  590. package/vendor/ink/build/ink.js.map +1 -0
  591. package/vendor/ink/build/input-parser.d.ts +7 -0
  592. package/vendor/ink/build/input-parser.js +154 -0
  593. package/vendor/ink/build/input-parser.js.map +1 -0
  594. package/vendor/ink/build/instance.js +205 -0
  595. package/vendor/ink/build/instances.d.ts +3 -0
  596. package/vendor/ink/build/instances.js +8 -0
  597. package/vendor/ink/build/instances.js.map +1 -0
  598. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  599. package/vendor/ink/build/kitty-keyboard.js +32 -0
  600. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  601. package/vendor/ink/build/layout.d.ts +7 -0
  602. package/vendor/ink/build/layout.js +33 -0
  603. package/vendor/ink/build/layout.js.map +1 -0
  604. package/vendor/ink/build/log-update.d.ts +19 -0
  605. package/vendor/ink/build/log-update.js +243 -0
  606. package/vendor/ink/build/log-update.js.map +1 -0
  607. package/vendor/ink/build/measure-element.d.ts +16 -0
  608. package/vendor/ink/build/measure-element.js +9 -0
  609. package/vendor/ink/build/measure-element.js.map +1 -0
  610. package/vendor/ink/build/measure-text.d.ts +6 -0
  611. package/vendor/ink/build/measure-text.js +21 -0
  612. package/vendor/ink/build/measure-text.js.map +1 -0
  613. package/vendor/ink/build/options.d.ts +52 -0
  614. package/vendor/ink/build/options.js +2 -0
  615. package/vendor/ink/build/options.js.map +1 -0
  616. package/vendor/ink/build/output.d.ts +35 -0
  617. package/vendor/ink/build/output.js +183 -0
  618. package/vendor/ink/build/output.js.map +1 -0
  619. package/vendor/ink/build/parse-keypress.d.ts +22 -0
  620. package/vendor/ink/build/parse-keypress.js +493 -0
  621. package/vendor/ink/build/parse-keypress.js.map +1 -0
  622. package/vendor/ink/build/reconciler.d.ts +4 -0
  623. package/vendor/ink/build/reconciler.js +274 -0
  624. package/vendor/ink/build/reconciler.js.map +1 -0
  625. package/vendor/ink/build/render-background.d.ts +4 -0
  626. package/vendor/ink/build/render-background.js +25 -0
  627. package/vendor/ink/build/render-background.js.map +1 -0
  628. package/vendor/ink/build/render-border.d.ts +4 -0
  629. package/vendor/ink/build/render-border.js +73 -0
  630. package/vendor/ink/build/render-border.js.map +1 -0
  631. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  632. package/vendor/ink/build/render-node-to-output.js +147 -0
  633. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  634. package/vendor/ink/build/render-to-string.d.ts +38 -0
  635. package/vendor/ink/build/render-to-string.js +115 -0
  636. package/vendor/ink/build/render-to-string.js.map +1 -0
  637. package/vendor/ink/build/render.d.ts +121 -0
  638. package/vendor/ink/build/render.js +55 -0
  639. package/vendor/ink/build/render.js.map +1 -0
  640. package/vendor/ink/build/renderer.d.ts +8 -0
  641. package/vendor/ink/build/renderer.js +55 -0
  642. package/vendor/ink/build/renderer.js.map +1 -0
  643. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  644. package/vendor/ink/build/sanitize-ansi.js +27 -0
  645. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  646. package/vendor/ink/build/screen-reader-update.d.ts +13 -0
  647. package/vendor/ink/build/screen-reader-update.js +38 -0
  648. package/vendor/ink/build/screen-reader-update.js.map +1 -0
  649. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  650. package/vendor/ink/build/squash-text-nodes.js +36 -0
  651. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  652. package/vendor/ink/build/styles.d.ts +240 -0
  653. package/vendor/ink/build/styles.js +232 -0
  654. package/vendor/ink/build/styles.js.map +1 -0
  655. package/vendor/ink/build/utils.d.ts +2 -0
  656. package/vendor/ink/build/utils.js +4 -0
  657. package/vendor/ink/build/utils.js.map +1 -0
  658. package/vendor/ink/build/wrap-text.d.ts +3 -0
  659. package/vendor/ink/build/wrap-text.js +31 -0
  660. package/vendor/ink/build/wrap-text.js.map +1 -0
  661. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  662. package/vendor/ink/build/write-synchronized.js +7 -0
  663. package/vendor/ink/build/write-synchronized.js.map +1 -0
  664. package/vendor/ink/license +10 -0
  665. package/vendor/ink/node_modules/@types/node/LICENSE +21 -0
  666. package/vendor/ink/node_modules/@types/node/README.md +15 -0
  667. package/vendor/ink/node_modules/@types/node/assert/strict.d.ts +105 -0
  668. package/vendor/ink/node_modules/@types/node/assert.d.ts +955 -0
  669. package/vendor/ink/node_modules/@types/node/async_hooks.d.ts +623 -0
  670. package/vendor/ink/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  671. package/vendor/ink/node_modules/@types/node/buffer.d.ts +1810 -0
  672. package/vendor/ink/node_modules/@types/node/child_process.d.ts +1428 -0
  673. package/vendor/ink/node_modules/@types/node/cluster.d.ts +486 -0
  674. package/vendor/ink/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  675. package/vendor/ink/node_modules/@types/node/console.d.ts +151 -0
  676. package/vendor/ink/node_modules/@types/node/constants.d.ts +20 -0
  677. package/vendor/ink/node_modules/@types/node/crypto.d.ts +4065 -0
  678. package/vendor/ink/node_modules/@types/node/dgram.d.ts +564 -0
  679. package/vendor/ink/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  680. package/vendor/ink/node_modules/@types/node/dns/promises.d.ts +503 -0
  681. package/vendor/ink/node_modules/@types/node/dns.d.ts +922 -0
  682. package/vendor/ink/node_modules/@types/node/domain.d.ts +166 -0
  683. package/vendor/ink/node_modules/@types/node/events.d.ts +1054 -0
  684. package/vendor/ink/node_modules/@types/node/fs/promises.d.ts +1329 -0
  685. package/vendor/ink/node_modules/@types/node/fs.d.ts +4676 -0
  686. package/vendor/ink/node_modules/@types/node/globals.d.ts +150 -0
  687. package/vendor/ink/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  688. package/vendor/ink/node_modules/@types/node/http.d.ts +2167 -0
  689. package/vendor/ink/node_modules/@types/node/http2.d.ts +2480 -0
  690. package/vendor/ink/node_modules/@types/node/https.d.ts +405 -0
  691. package/vendor/ink/node_modules/@types/node/index.d.ts +115 -0
  692. package/vendor/ink/node_modules/@types/node/inspector/promises.d.ts +41 -0
  693. package/vendor/ink/node_modules/@types/node/inspector.d.ts +224 -0
  694. package/vendor/ink/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  695. package/vendor/ink/node_modules/@types/node/module.d.ts +819 -0
  696. package/vendor/ink/node_modules/@types/node/net.d.ts +933 -0
  697. package/vendor/ink/node_modules/@types/node/os.d.ts +507 -0
  698. package/vendor/ink/node_modules/@types/node/package.json +155 -0
  699. package/vendor/ink/node_modules/@types/node/path/posix.d.ts +8 -0
  700. package/vendor/ink/node_modules/@types/node/path/win32.d.ts +8 -0
  701. package/vendor/ink/node_modules/@types/node/path.d.ts +187 -0
  702. package/vendor/ink/node_modules/@types/node/perf_hooks.d.ts +643 -0
  703. package/vendor/ink/node_modules/@types/node/process.d.ts +2156 -0
  704. package/vendor/ink/node_modules/@types/node/punycode.d.ts +117 -0
  705. package/vendor/ink/node_modules/@types/node/querystring.d.ts +152 -0
  706. package/vendor/ink/node_modules/@types/node/quic.d.ts +910 -0
  707. package/vendor/ink/node_modules/@types/node/readline/promises.d.ts +161 -0
  708. package/vendor/ink/node_modules/@types/node/readline.d.ts +541 -0
  709. package/vendor/ink/node_modules/@types/node/repl.d.ts +415 -0
  710. package/vendor/ink/node_modules/@types/node/sea.d.ts +162 -0
  711. package/vendor/ink/node_modules/@types/node/sqlite.d.ts +955 -0
  712. package/vendor/ink/node_modules/@types/node/stream/consumers.d.ts +38 -0
  713. package/vendor/ink/node_modules/@types/node/stream/promises.d.ts +211 -0
  714. package/vendor/ink/node_modules/@types/node/stream/web.d.ts +296 -0
  715. package/vendor/ink/node_modules/@types/node/stream.d.ts +1760 -0
  716. package/vendor/ink/node_modules/@types/node/string_decoder.d.ts +67 -0
  717. package/vendor/ink/node_modules/@types/node/test/reporters.d.ts +96 -0
  718. package/vendor/ink/node_modules/@types/node/test.d.ts +2240 -0
  719. package/vendor/ink/node_modules/@types/node/timers/promises.d.ts +108 -0
  720. package/vendor/ink/node_modules/@types/node/timers.d.ts +159 -0
  721. package/vendor/ink/node_modules/@types/node/tls.d.ts +1198 -0
  722. package/vendor/ink/node_modules/@types/node/trace_events.d.ts +197 -0
  723. package/vendor/ink/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +462 -0
  724. package/vendor/ink/node_modules/@types/node/ts5.6/compatibility/float16array.d.ts +71 -0
  725. package/vendor/ink/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +36 -0
  726. package/vendor/ink/node_modules/@types/node/ts5.6/index.d.ts +117 -0
  727. package/vendor/ink/node_modules/@types/node/ts5.7/compatibility/float16array.d.ts +72 -0
  728. package/vendor/ink/node_modules/@types/node/ts5.7/index.d.ts +117 -0
  729. package/vendor/ink/node_modules/@types/node/tty.d.ts +250 -0
  730. package/vendor/ink/node_modules/@types/node/url.d.ts +519 -0
  731. package/vendor/ink/node_modules/@types/node/util/types.d.ts +558 -0
  732. package/vendor/ink/node_modules/@types/node/util.d.ts +1662 -0
  733. package/vendor/ink/node_modules/@types/node/v8.d.ts +983 -0
  734. package/vendor/ink/node_modules/@types/node/vm.d.ts +1208 -0
  735. package/vendor/ink/node_modules/@types/node/wasi.d.ts +202 -0
  736. package/vendor/ink/node_modules/@types/node/web-globals/abortcontroller.d.ts +59 -0
  737. package/vendor/ink/node_modules/@types/node/web-globals/blob.d.ts +23 -0
  738. package/vendor/ink/node_modules/@types/node/web-globals/console.d.ts +9 -0
  739. package/vendor/ink/node_modules/@types/node/web-globals/crypto.d.ts +39 -0
  740. package/vendor/ink/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  741. package/vendor/ink/node_modules/@types/node/web-globals/encoding.d.ts +11 -0
  742. package/vendor/ink/node_modules/@types/node/web-globals/events.d.ts +106 -0
  743. package/vendor/ink/node_modules/@types/node/web-globals/fetch.d.ts +69 -0
  744. package/vendor/ink/node_modules/@types/node/web-globals/importmeta.d.ts +13 -0
  745. package/vendor/ink/node_modules/@types/node/web-globals/messaging.d.ts +23 -0
  746. package/vendor/ink/node_modules/@types/node/web-globals/navigator.d.ts +25 -0
  747. package/vendor/ink/node_modules/@types/node/web-globals/performance.d.ts +45 -0
  748. package/vendor/ink/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  749. package/vendor/ink/node_modules/@types/node/web-globals/streams.d.ts +115 -0
  750. package/vendor/ink/node_modules/@types/node/web-globals/timers.d.ts +44 -0
  751. package/vendor/ink/node_modules/@types/node/web-globals/url.d.ts +24 -0
  752. package/vendor/ink/node_modules/@types/node/worker_threads.d.ts +717 -0
  753. package/vendor/ink/node_modules/@types/node/zlib.d.ts +618 -0
  754. package/vendor/ink/node_modules/node-pty/LICENSE +69 -0
  755. package/vendor/ink/node_modules/node-pty/README.md +164 -0
  756. package/vendor/ink/node_modules/node-pty/binding.gyp +150 -0
  757. package/vendor/ink/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  758. package/vendor/ink/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  759. package/vendor/ink/node_modules/node-pty/lib/index.js +52 -0
  760. package/vendor/ink/node_modules/node-pty/lib/interfaces.js +7 -0
  761. package/vendor/ink/node_modules/node-pty/lib/shared/conout.js +11 -0
  762. package/vendor/ink/node_modules/node-pty/lib/terminal.js +190 -0
  763. package/vendor/ink/node_modules/node-pty/lib/types.js +7 -0
  764. package/vendor/ink/node_modules/node-pty/lib/unixTerminal.js +349 -0
  765. package/vendor/ink/node_modules/node-pty/lib/utils.js +39 -0
  766. package/vendor/ink/node_modules/node-pty/lib/windowsConoutConnection.js +125 -0
  767. package/vendor/ink/node_modules/node-pty/lib/windowsPtyAgent.js +287 -0
  768. package/vendor/ink/node_modules/node-pty/lib/windowsTerminal.js +201 -0
  769. package/vendor/ink/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  770. package/vendor/ink/node_modules/node-pty/package.json +65 -0
  771. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  772. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  773. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  774. package/vendor/ink/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  775. package/vendor/ink/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  776. package/vendor/ink/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  777. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
  778. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
  779. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  780. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty.pdb +0 -0
  781. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  782. package/vendor/ink/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.pdb +0 -0
  783. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
  784. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
  785. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  786. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty.pdb +0 -0
  787. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  788. package/vendor/ink/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.pdb +0 -0
  789. package/vendor/ink/node_modules/node-pty/scripts/post-install.js +76 -0
  790. package/vendor/ink/node_modules/node-pty/scripts/prebuild.js +34 -0
  791. package/vendor/ink/node_modules/node-pty/src/unix/pty.cc +875 -0
  792. package/vendor/ink/node_modules/node-pty/src/unix/spawn-helper.cc +23 -0
  793. package/vendor/ink/node_modules/node-pty/src/win/conpty.cc +582 -0
  794. package/vendor/ink/node_modules/node-pty/src/win/conpty.h +41 -0
  795. package/vendor/ink/node_modules/node-pty/src/win/conpty_console_list.cc +44 -0
  796. package/vendor/ink/node_modules/node-pty/src/win/path_util.cc +95 -0
  797. package/vendor/ink/node_modules/node-pty/src/win/path_util.h +26 -0
  798. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/OpenConsole.exe +0 -0
  799. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-arm64/conpty.dll +0 -0
  800. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/OpenConsole.exe +0 -0
  801. package/vendor/ink/node_modules/node-pty/third_party/conpty/1.23.251008001/win10-x64/conpty.dll +0 -0
  802. package/vendor/ink/node_modules/node-pty/typings/node-pty.d.ts +215 -0
  803. package/vendor/ink/node_modules/undici-types/LICENSE +21 -0
  804. package/vendor/ink/node_modules/undici-types/README.md +6 -0
  805. package/vendor/ink/node_modules/undici-types/agent.d.ts +32 -0
  806. package/vendor/ink/node_modules/undici-types/api.d.ts +43 -0
  807. package/vendor/ink/node_modules/undici-types/balanced-pool.d.ts +30 -0
  808. package/vendor/ink/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  809. package/vendor/ink/node_modules/undici-types/cache.d.ts +36 -0
  810. package/vendor/ink/node_modules/undici-types/client-stats.d.ts +15 -0
  811. package/vendor/ink/node_modules/undici-types/client.d.ts +108 -0
  812. package/vendor/ink/node_modules/undici-types/connector.d.ts +34 -0
  813. package/vendor/ink/node_modules/undici-types/content-type.d.ts +21 -0
  814. package/vendor/ink/node_modules/undici-types/cookies.d.ts +30 -0
  815. package/vendor/ink/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  816. package/vendor/ink/node_modules/undici-types/dispatcher.d.ts +276 -0
  817. package/vendor/ink/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  818. package/vendor/ink/node_modules/undici-types/errors.d.ts +161 -0
  819. package/vendor/ink/node_modules/undici-types/eventsource.d.ts +66 -0
  820. package/vendor/ink/node_modules/undici-types/fetch.d.ts +211 -0
  821. package/vendor/ink/node_modules/undici-types/formdata.d.ts +108 -0
  822. package/vendor/ink/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  823. package/vendor/ink/node_modules/undici-types/global-origin.d.ts +7 -0
  824. package/vendor/ink/node_modules/undici-types/h2c-client.d.ts +73 -0
  825. package/vendor/ink/node_modules/undici-types/handlers.d.ts +15 -0
  826. package/vendor/ink/node_modules/undici-types/header.d.ts +160 -0
  827. package/vendor/ink/node_modules/undici-types/index.d.ts +88 -0
  828. package/vendor/ink/node_modules/undici-types/interceptors.d.ts +73 -0
  829. package/vendor/ink/node_modules/undici-types/mock-agent.d.ts +68 -0
  830. package/vendor/ink/node_modules/undici-types/mock-call-history.d.ts +111 -0
  831. package/vendor/ink/node_modules/undici-types/mock-client.d.ts +27 -0
  832. package/vendor/ink/node_modules/undici-types/mock-errors.d.ts +12 -0
  833. package/vendor/ink/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  834. package/vendor/ink/node_modules/undici-types/mock-pool.d.ts +27 -0
  835. package/vendor/ink/node_modules/undici-types/package.json +55 -0
  836. package/vendor/ink/node_modules/undici-types/patch.d.ts +29 -0
  837. package/vendor/ink/node_modules/undici-types/pool-stats.d.ts +19 -0
  838. package/vendor/ink/node_modules/undici-types/pool.d.ts +41 -0
  839. package/vendor/ink/node_modules/undici-types/proxy-agent.d.ts +29 -0
  840. package/vendor/ink/node_modules/undici-types/readable.d.ts +68 -0
  841. package/vendor/ink/node_modules/undici-types/retry-agent.d.ts +8 -0
  842. package/vendor/ink/node_modules/undici-types/retry-handler.d.ts +125 -0
  843. package/vendor/ink/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  844. package/vendor/ink/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  845. package/vendor/ink/node_modules/undici-types/util.d.ts +18 -0
  846. package/vendor/ink/node_modules/undici-types/utility.d.ts +7 -0
  847. package/vendor/ink/node_modules/undici-types/webidl.d.ts +341 -0
  848. package/vendor/ink/node_modules/undici-types/websocket.d.ts +186 -0
  849. package/vendor/ink/package.json +201 -0
  850. package/vendor/ink/readme.md +2636 -0
  851. package/bin/swag-agent.js +0 -9
  852. package/dist/server/lib/pg-rate-limiter.d.ts +0 -21
  853. package/dist/server/lib/pg-rate-limiter.js +0 -86
@@ -16,17 +16,19 @@
16
16
  * - session-persistence.ts (SessionMeta, save/load/list/find sessions)
17
17
  * - system-prompt.ts (buildSystemPrompt)
18
18
  */
19
- import { LOCAL_TOOL_DEFINITIONS, executeLocalTool, isLocalTool, } from "./local-tools.js";
20
- import { INTERACTIVE_TOOL_DEFINITIONS, executeInteractiveTool, waitForPlanApproval, } from "./interactive-tools.js";
19
+
20
+ import { LOCAL_TOOL_DEFINITIONS, executeLocalTool, isLocalTool } from "./local-tools.js";
21
+ import { INTERACTIVE_TOOL_DEFINITIONS, executeInteractiveTool, waitForPlanApproval } from "./interactive-tools.js";
21
22
  import { loadConfig, resolveConfig, getProxyUrl } from "./config-store.js";
22
23
  import { getValidToken, refreshSession } from "./auth-service.js";
23
- import { isServerTool, loadServerToolDefinitions, executeServerTool, getServerStatus, setServerToolContext, } from "./server-tools.js";
24
- import { nextTurn, createTurnContext, logSpan, generateSpanId, flushCliSpans, } from "./telemetry.js";
24
+ import { isServerTool, loadServerToolDefinitions, executeServerTool, getServerStatus, setServerToolContext } from "./server-tools.js";
25
+ import { nextTurn, createTurnContext, logSpan, generateSpanId, flushCliSpans } from "./telemetry.js";
25
26
  import { captureError, addBreadcrumb } from "./error-logger.js";
26
- import { setGlobalEmitter, clearGlobalEmitter, } from "./agent-events.js";
27
+ import { setGlobalEmitter, clearGlobalEmitter } from "./agent-events.js";
27
28
  import { mcpClientManager } from "./mcp-client.js";
28
29
  import { loadHooks, runBeforeToolHook, runAfterToolHook, runSessionHook } from "./hooks.js";
29
- import { LoopDetector, COMPACTION_TRIGGER_TOKENS, COMPACTION_TOTAL_BUDGET, getCompactionConfig, DEFAULT_SESSION_COST_BUDGET_USD, emitCostWarningIfNeeded } from "../../shared/agent-core.js";
30
+ import { LoopDetector, COMPACTION_TOTAL_BUDGET, getCompactionConfig, getContextManagement, DEFAULT_SESSION_COST_BUDGET_USD, emitCostWarningIfNeeded, resolveAgentLoopConfig, AGENT_DEFAULTS } from "../../shared/agent-core.js";
31
+ import { loadCLIAgentConfig, resetAgentConfigCache } from "./agent-config.js";
30
32
  import { parseSSEStream, processStreamWithCallbacks, collectStreamResult } from "../../shared/sse-parser.js";
31
33
  import { callServerProxy, callTranscribe, buildAPIRequest, buildSystemBlocks, prepareWithCaching, trimGeminiContext, trimOpenAIContext, requestProviderCompaction } from "../../shared/api-client.js";
32
34
  import { getProvider, MODELS } from "../../shared/constants.js";
@@ -40,39 +42,60 @@ import { setPermissionMode, getPermissionMode, isToolAllowedByPermission } from
40
42
  import { setModel, setModelById, getModel, getModelShortName, estimateCostUsd } from "./model-manager.js";
41
43
  import { saveSession, loadSession, listSessions, findLatestSessionForCwd } from "./session-persistence.js";
42
44
  import { buildSystemPrompt } from "./system-prompt.js";
45
+
43
46
  // ============================================================================
44
47
  // RE-EXPORTS — all consumers keep importing from agent-loop.ts
45
48
  // ============================================================================
49
+
46
50
  // Memory
47
51
  export { loadMemory, addMemory, removeMemory, listMemories };
52
+
48
53
  // Git context
49
54
  export { refreshGitContext };
55
+
50
56
  // CLAUDE.md
51
57
  export { loadClaudeMd, reloadClaudeMd };
58
+
52
59
  // Permission modes
53
60
  export { setPermissionMode, getPermissionMode, isToolAllowedByPermission };
61
+
54
62
  // Model management
55
63
  export { setModel, getModel, getModelShortName, estimateCostUsd };
64
+
56
65
  // Session persistence
57
66
  export { saveSession, loadSession, listSessions, findLatestSessionForCwd };
67
+
58
68
  // Server status (pass-through)
59
69
  export { getServerStatus };
70
+
60
71
  // MCP client manager
61
72
  export { mcpClientManager };
73
+
62
74
  // Re-export background process listing for /tasks command
63
75
  export { listProcesses, listBackgroundAgents } from "./background-processes.js";
76
+
64
77
  // Re-export event emitter for ChatApp
65
78
  export { AgentEventEmitter } from "./agent-events.js";
79
+
80
+ // ============================================================================
81
+ // TYPES
82
+ // ============================================================================
83
+
66
84
  // ============================================================================
67
85
  // SESSION STATE
68
86
  // ============================================================================
87
+
69
88
  // CLI-only: Session-wide token tracking (actual counts from API responses).
70
89
  // Reset via resetSessionState() when starting a new conversation.
71
90
  let sessionInputTokens = 0;
72
91
  let sessionOutputTokens = 0;
73
92
  export function getSessionTokens() {
74
- return { input: sessionInputTokens, output: sessionOutputTokens };
93
+ return {
94
+ input: sessionInputTokens,
95
+ output: sessionOutputTokens
96
+ };
75
97
  }
98
+
76
99
  /**
77
100
  * Reset all CLI-only session state. Call when starting a new conversation
78
101
  * (e.g., /clear command, new print-mode run) to prevent stale token counts,
@@ -82,738 +105,1024 @@ export function getSessionTokens() {
82
105
  * that persist intentionally until explicitly changed.
83
106
  */
84
107
  export function resetSessionState() {
85
- sessionInputTokens = 0;
86
- sessionOutputTokens = 0;
87
- sessionLoopDetector = null;
88
- resetGitContext();
89
- resetClaudeMdCache();
90
- clearReadCache();
108
+ sessionInputTokens = 0;
109
+ sessionOutputTokens = 0;
110
+ sessionLoopDetector = null;
111
+ resetGitContext();
112
+ resetClaudeMdCache();
113
+ clearReadCache();
114
+ resetAgentConfigCache();
91
115
  }
116
+
92
117
  /** CLI-only: loop detector — persists session error state across turns (reset by resetSessionState) */
93
118
  let sessionLoopDetector = null;
94
- const MAX_TURNS = 200; // Match Claude Code — effectively unlimited within a session
119
+
120
+ // No artificial turn cap — agent self-regulates via end_turn (Claude Code parity).
121
+ // Safety: LoopDetector catches loops, cost budget catches runaway spending, user has Ctrl+C.
122
+ const CLI_NO_LIMIT = Number.MAX_SAFE_INTEGER;
123
+
95
124
  // ============================================================================
96
125
  // SHELL OUTPUT SUMMARIZATION
97
126
  // ============================================================================
127
+
98
128
  const SHELL_SUMMARIZE_LINE_THRESHOLD = 800;
99
129
  const SHELL_SUMMARIZE_SIZE_THRESHOLD = 200_000; // 200KB — only summarize truly huge outputs
100
130
  const SHELL_SUMMARIZE_MAX_INPUT = 300_000; // 300KB max to summarizer
101
131
  const SHELL_SUMMARIZE_ORIGINAL_PREVIEW_LINES = 20;
132
+
102
133
  /**
103
134
  * Check if shell output should be summarized based on line count or size.
104
135
  */
105
136
  function shouldSummarizeShellOutput(output) {
106
- if (output.length > SHELL_SUMMARIZE_SIZE_THRESHOLD)
107
- return true;
108
- const lineCount = output.split("\n").length;
109
- return lineCount > SHELL_SUMMARIZE_LINE_THRESHOLD;
137
+ if (output.length > SHELL_SUMMARIZE_SIZE_THRESHOLD) return true;
138
+ const lineCount = output.split("\n").length;
139
+ return lineCount > SHELL_SUMMARIZE_LINE_THRESHOLD;
110
140
  }
141
+
111
142
  /**
112
143
  * Summarize long shell output using Haiku via server proxy.
113
144
  * Returns summarized output or original if summarization fails.
114
145
  */
115
146
  async function summarizeShellOutput(output, proxyUrl, token, storeId) {
116
- const lineCount = output.split("\n").length;
117
- const truncatedForSummary = output.length > SHELL_SUMMARIZE_MAX_INPUT
118
- ? output.slice(0, SHELL_SUMMARIZE_MAX_INPUT) + "\n... (truncated for summarization)"
119
- : output;
120
- try {
121
- const summaryConfig = buildAPIRequest({
122
- model: MODELS.HAIKU,
123
- contextProfile: "subagent",
124
- });
125
- const stream = await callServerProxy({
126
- proxyUrl,
127
- token,
128
- model: MODELS.HAIKU,
129
- system: [{ type: "text", text: "You are a concise technical summarizer. Summarize shell/command output preserving key information, errors, warnings, file paths, and actionable items. Be brief but thorough." }],
130
- messages: [{ role: "user", content: `Summarize this shell output concisely, preserving key information, errors, and actionable items:\n\n${truncatedForSummary}` }],
131
- tools: [],
132
- apiConfig: summaryConfig,
133
- storeId,
134
- timeoutMs: 15_000,
135
- });
136
- const result = await collectStreamResult(parseSSEStream(stream));
137
- const summary = result.text.trim();
138
- if (!summary)
139
- return output; // Summarization failed, return original
140
- // Build first N lines preview
141
- const originalLines = output.split("\n");
142
- const preview = originalLines.slice(0, SHELL_SUMMARIZE_ORIGINAL_PREVIEW_LINES).join("\n");
143
- return `[Summarized from ${lineCount} lines]\n\n${summary}\n\n[First ${SHELL_SUMMARIZE_ORIGINAL_PREVIEW_LINES} lines of original output]\n${preview}`;
144
- }
145
- catch {
146
- // Summarization failed silently — return original output
147
- return output;
148
- }
147
+ const lineCount = output.split("\n").length;
148
+ const truncatedForSummary = output.length > SHELL_SUMMARIZE_MAX_INPUT ? output.slice(0, SHELL_SUMMARIZE_MAX_INPUT) + "\n... (truncated for summarization)" : output;
149
+ try {
150
+ const summaryConfig = buildAPIRequest({
151
+ model: MODELS.HAIKU,
152
+ contextProfile: "subagent"
153
+ });
154
+ const stream = await callServerProxy({
155
+ proxyUrl,
156
+ token,
157
+ model: MODELS.HAIKU,
158
+ system: [{
159
+ type: "text",
160
+ text: "You are a concise technical summarizer. Summarize shell/command output preserving key information, errors, warnings, file paths, and actionable items. Be brief but thorough."
161
+ }],
162
+ messages: [{
163
+ role: "user",
164
+ content: `Summarize this shell output concisely, preserving key information, errors, and actionable items:\n\n${truncatedForSummary}`
165
+ }],
166
+ tools: [],
167
+ apiConfig: summaryConfig,
168
+ storeId,
169
+ timeoutMs: 15_000
170
+ });
171
+ const result = await collectStreamResult(parseSSEStream(stream));
172
+ const summary = result.text.trim();
173
+ if (!summary) return output; // Summarization failed, return original
174
+
175
+ // Build first N lines preview
176
+ const originalLines = output.split("\n");
177
+ const preview = originalLines.slice(0, SHELL_SUMMARIZE_ORIGINAL_PREVIEW_LINES).join("\n");
178
+ return `[Summarized from ${lineCount} lines]\n\n${summary}\n\n[First ${SHELL_SUMMARIZE_ORIGINAL_PREVIEW_LINES} lines of original output]\n${preview}`;
179
+ } catch {
180
+ // Summarization failed silently — return original output
181
+ return output;
182
+ }
149
183
  }
184
+
150
185
  /**
151
186
  * Post-process tool results to summarize long bash output.
152
187
  * Only affects bash tool results that exceed size/line thresholds.
153
188
  */
154
189
  async function summarizeLongToolResults(toolResults, toolNames, proxyUrl, token, shellSummarization, storeId) {
155
- if (!shellSummarization)
156
- return toolResults;
157
- const tasks = toolResults.map(async (result) => {
158
- // Only summarize bash tool string results
159
- const toolName = toolNames.get(result.tool_use_id);
160
- if (toolName !== "bash" || typeof result.content !== "string")
161
- return result;
162
- // Check thresholds
163
- if (!shouldSummarizeShellOutput(result.content))
164
- return result;
165
- const summarized = await summarizeShellOutput(result.content, proxyUrl, token, storeId);
166
- return { ...result, content: summarized };
167
- });
168
- return Promise.all(tasks);
190
+ if (!shellSummarization) return toolResults;
191
+ const tasks = toolResults.map(async result => {
192
+ // Only summarize bash tool string results
193
+ const toolName = toolNames.get(result.tool_use_id);
194
+ if (toolName !== "bash" || typeof result.content !== "string") return result;
195
+
196
+ // Check thresholds
197
+ if (!shouldSummarizeShellOutput(result.content)) return result;
198
+ const summarized = await summarizeShellOutput(result.content, proxyUrl, token, storeId);
199
+ return {
200
+ ...result,
201
+ content: summarized
202
+ };
203
+ });
204
+ return Promise.all(tasks);
169
205
  }
206
+
170
207
  // ============================================================================
171
208
  // TOOL DEFINITIONS
172
209
  // ============================================================================
210
+
173
211
  async function getTools(allowedTools, disallowedTools) {
174
- const localTools = LOCAL_TOOL_DEFINITIONS.map((t) => ({
175
- name: t.name,
176
- description: t.description,
177
- input_schema: t.input_schema,
178
- }));
179
- // Add interactive tools (ask_user_question, enter_plan_mode, exit_plan_mode)
180
- const interactiveTools = INTERACTIVE_TOOL_DEFINITIONS.map((t) => ({
181
- name: t.name,
182
- description: t.description,
183
- input_schema: t.input_schema,
184
- }));
185
- localTools.push(...interactiveTools);
186
- let serverTools = [];
187
- try {
188
- serverTools = await loadServerToolDefinitions();
189
- }
190
- catch {
191
- // Server tools silently unavailable
192
- }
193
- // Deduplicate: local tools take priority over server tools with the same name
194
- const localNames = new Set(localTools.map(t => t.name));
195
- const uniqueServerTools = serverTools.filter(t => !localNames.has(t.name));
196
- // MCP tools from external servers
197
- const mcpTools = mcpClientManager.getTools();
198
- let allTools = [...localTools, ...uniqueServerTools, ...mcpTools];
199
- // Apply tool filtering
200
- if (allowedTools && allowedTools.length > 0) {
201
- const allowed = new Set(allowedTools);
202
- allTools = allTools.filter(t => allowed.has(t.name));
203
- }
204
- if (disallowedTools && disallowedTools.length > 0) {
205
- const disallowed = new Set(disallowedTools);
206
- allTools = allTools.filter(t => !disallowed.has(t.name));
207
- }
208
- return {
209
- tools: allTools,
210
- serverToolCount: uniqueServerTools.length,
211
- };
212
+ const localTools = LOCAL_TOOL_DEFINITIONS.map(t => ({
213
+ name: t.name,
214
+ description: t.description,
215
+ input_schema: t.input_schema
216
+ }));
217
+
218
+ // Add interactive tools (ask_user_question, enter_plan_mode, exit_plan_mode)
219
+ const interactiveTools = INTERACTIVE_TOOL_DEFINITIONS.map(t => ({
220
+ name: t.name,
221
+ description: t.description,
222
+ input_schema: t.input_schema
223
+ }));
224
+ localTools.push(...interactiveTools);
225
+ let serverTools = [];
226
+ try {
227
+ serverTools = await loadServerToolDefinitions();
228
+ } catch {
229
+ // Server tools silently unavailable
230
+ }
231
+
232
+ // Deduplicate: local tools take priority over server tools with the same name
233
+ const localNames = new Set(localTools.map(t => t.name));
234
+ const uniqueServerTools = serverTools.filter(t => !localNames.has(t.name));
235
+
236
+ // MCP tools from external servers
237
+ const mcpTools = mcpClientManager.getTools();
238
+ let allTools = [...localTools, ...uniqueServerTools, ...mcpTools];
239
+
240
+ // Apply tool filtering
241
+ if (allowedTools && allowedTools.length > 0) {
242
+ const allowed = new Set(allowedTools);
243
+ allTools = allTools.filter(t => allowed.has(t.name));
244
+ }
245
+ if (disallowedTools && disallowedTools.length > 0) {
246
+ const disallowed = new Set(disallowedTools);
247
+ allTools = allTools.filter(t => !disallowed.has(t.name));
248
+ }
249
+ return {
250
+ tools: allTools,
251
+ serverToolCount: uniqueServerTools.length
252
+ };
212
253
  }
254
+
213
255
  /** Exposed for /status command */
214
256
  export async function getServerToolCount() {
215
- try {
216
- const defs = await loadServerToolDefinitions();
217
- return defs.length;
218
- }
219
- catch {
220
- return 0;
221
- }
257
+ try {
258
+ const defs = await loadServerToolDefinitions();
259
+ return defs.length;
260
+ } catch {
261
+ return 0;
262
+ }
222
263
  }
264
+
223
265
  // ============================================================================
224
266
  // MAIN LOOP
225
267
  // ============================================================================
268
+
226
269
  export async function runAgentLoop(opts) {
227
- const { message, conversationHistory, callbacks, abortSignal, emitter } = opts;
228
- if (opts.model)
229
- setModel(opts.model);
230
- // Set global emitter for subagents to use
231
- if (emitter) {
232
- setGlobalEmitter(emitter);
270
+ const {
271
+ message,
272
+ conversationHistory,
273
+ callbacks,
274
+ abortSignal,
275
+ emitter
276
+ } = opts;
277
+ if (opts.model) setModel(opts.model);
278
+
279
+ // Set global emitter for subagents to use
280
+ if (emitter) {
281
+ setGlobalEmitter(emitter);
282
+ }
283
+
284
+ // effectiveMaxTurns resolved after DB config fetch below
285
+ // 0 or unset = no limit (Claude Code parity — agent self-regulates via end_turn)
286
+ let effectiveMaxTurns = opts.maxTurns || CLI_NO_LIMIT;
287
+
288
+ // Load hooks from project and user config
289
+ const hooksCwd = opts.cwd || process.cwd();
290
+ const hooks = loadHooks(hooksCwd);
291
+
292
+ // Fire SessionStart hook (non-blocking)
293
+ if (hooks.length > 0) {
294
+ runSessionHook(hooks, "SessionStart", {
295
+ session_id: `turn-${Date.now()}`
296
+ }).catch(() => {});
297
+ }
298
+
299
+ // Shell summarization config (default: true)
300
+ const shellSummarization = opts.shellSummarization !== false;
301
+ const {
302
+ tools,
303
+ serverToolCount
304
+ } = await getTools(opts.allowedTools, opts.disallowedTools);
305
+ const systemPrompt = await buildSystemPrompt(serverToolCount > 0, opts.effort);
306
+
307
+ // Build user content — text-only string or content blocks array with images
308
+ let userContent;
309
+ if (opts.images && opts.images.length > 0) {
310
+ const blocks = [];
311
+ for (const img of opts.images) {
312
+ blocks.push({
313
+ type: "image",
314
+ source: {
315
+ type: "base64",
316
+ media_type: img.mediaType,
317
+ data: img.base64
318
+ }
319
+ });
233
320
  }
234
- const effectiveMaxTurns = opts.maxTurns || MAX_TURNS;
235
- // Load hooks from project and user config
236
- const hooksCwd = opts.cwd || process.cwd();
237
- const hooks = loadHooks(hooksCwd);
238
- // Fire SessionStart hook (non-blocking)
239
- if (hooks.length > 0) {
240
- runSessionHook(hooks, "SessionStart", { session_id: `turn-${Date.now()}` }).catch(() => { });
321
+ blocks.push({
322
+ type: "text",
323
+ text: message || "(see attached images)"
324
+ });
325
+ userContent = blocks;
326
+ } else {
327
+ userContent = message;
328
+ }
329
+ const messages = [...conversationHistory, {
330
+ role: "user",
331
+ content: userContent
332
+ }];
333
+ let totalIn = 0;
334
+ let totalOut = 0;
335
+ let totalCacheCreation = 0;
336
+ let totalCacheRead = 0;
337
+ let totalThinking = 0;
338
+ let allAssistantText = [];
339
+ let prevIterationInputTokens = 0; // Tracks actual context size from last API call
340
+
341
+ // Telemetry: one turn per user message (not per API call)
342
+ const sessionStart = Date.now();
343
+ const {
344
+ storeId
345
+ } = resolveConfig();
346
+ const turnNum = nextTurn(); // ONCE per user message
347
+ const turnCtx = createTurnContext({
348
+ model: getModel(),
349
+ turnNumber: turnNum
350
+ });
351
+ addBreadcrumb("agent", `Turn ${turnNum}: ${message.slice(0, 100)}`, "info");
352
+
353
+ // Set server tool context so tool calls carry trace/user identity to Fly.io server
354
+ setServerToolContext({
355
+ traceId: turnCtx.traceId,
356
+ conversationId: turnCtx.conversationId,
357
+ userId: turnCtx.userId,
358
+ userEmail: turnCtx.userEmail,
359
+ source: "whale-code"
360
+ });
361
+ logSpan({
362
+ action: "chat.user_message",
363
+ durationMs: 0,
364
+ context: turnCtx,
365
+ storeId: storeId || undefined,
366
+ details: {
367
+ message: message,
368
+ conversation_history_length: conversationHistory.length
241
369
  }
242
- // Shell summarization config (default: true)
243
- const shellSummarization = opts.shellSummarization !== false;
244
- const { tools, serverToolCount } = await getTools(opts.allowedTools, opts.disallowedTools);
245
- const systemPrompt = await buildSystemPrompt(serverToolCount > 0, opts.effort);
246
- // Build user content text-only string or content blocks array with images
247
- let userContent;
248
- if (opts.images && opts.images.length > 0) {
249
- const blocks = [];
250
- for (const img of opts.images) {
251
- blocks.push({
252
- type: "image",
253
- source: {
254
- type: "base64",
255
- media_type: img.mediaType,
256
- data: img.base64,
257
- },
258
- });
259
- }
260
- blocks.push({ type: "text", text: message || "(see attached images)" });
261
- userContent = blocks;
370
+ });
371
+ let sessionCostUsd = 0;
372
+ let compactionCount = 0;
373
+ let consecutiveCompactions = 0; // Circuit breaker: consecutive compactions without progress
374
+ let maxTokensContinuations = 0; // Cap continuation loops to prevent infinite looping
375
+ const costWarningsEmitted = new Set();
376
+ const activeModel = getModel();
377
+
378
+ // Fetch agent config from DB — single source of truth, nothing hardcoded.
379
+ // Falls back to AGENT_DEFAULTS only if DB is unreachable (never blocks).
380
+ const dbAgent = await loadCLIAgentConfig();
381
+ const resolved = dbAgent ? resolveAgentLoopConfig(dbAgent, "sse") : null;
382
+
383
+ // Session-level loop detector: persists failed strategies across turns.
384
+ // Created once per conversation, reset only when user starts a new conversation.
385
+ // Must be created AFTER config resolution so DB thresholds apply.
386
+ if (!sessionLoopDetector || conversationHistory.length === 0) {
387
+ sessionLoopDetector = resolved ? LoopDetector.fromResolved(resolved) : new LoopDetector();
388
+ }
389
+ const loopDetector = sessionLoopDetector;
390
+ loopDetector.resetTurn();
391
+ const effectiveBudget = opts.maxBudgetUsd ?? resolved?.contextOverrides.session_cost_budget_usd ?? DEFAULT_SESSION_COST_BUDGET_USD;
392
+
393
+ // Compaction settings from DB via getContextManagement — pass DB overrides so
394
+ // the trigger/budget values match what the Anthropic API actually uses.
395
+ const ctxOverrides = resolved?.contextOverrides;
396
+ const ctxMgmt = getContextManagement(activeModel, ctxOverrides);
397
+ const compactEdit = ctxMgmt.config.edits.find(e => e.type === "compact_20260112");
398
+ const effectiveCompactionTrigger = compactEdit?.trigger?.value ?? AGENT_DEFAULTS.compactionTriggerTokens;
399
+ const effectiveCompactionBudget = ctxOverrides?.compaction_total_budget ?? COMPACTION_TOTAL_BUDGET;
400
+
401
+ // Override maxTurns from DB config (if not explicitly set by caller)
402
+ // 0 = no limit (Claude Code parity)
403
+ if (!opts.maxTurns && resolved && resolved.maxTurns > 0) {
404
+ effectiveMaxTurns = resolved.maxTurns;
405
+ }
406
+
407
+ // Max continuations: resolved from DB config → AGENT_DEFAULTS fallback
408
+ const maxContinuations = resolved?.maxContinuations ?? AGENT_DEFAULTS.maxContinuations;
409
+
410
+ // Tool executor — routes to interactive, local, server, or MCP tools.
411
+ // Wraps execution with before/after hooks when hooks are loaded.
412
+ const INTERACTIVE_TOOL_NAMES = new Set(INTERACTIVE_TOOL_DEFINITIONS.map(t => t.name));
413
+ const toolExecutor = async (name, input) => {
414
+ if (!name) {
415
+ return {
416
+ success: false,
417
+ output: "Tool call missing name — skipping."
418
+ };
262
419
  }
263
- else {
264
- userContent = message;
420
+
421
+ // Permission mode enforcement
422
+ if (!isToolAllowedByPermission(name)) {
423
+ return {
424
+ success: false,
425
+ output: `Tool "${name}" blocked by ${getPermissionMode()} mode. Switch modes with /mode.`
426
+ };
265
427
  }
266
- const messages = [
267
- ...conversationHistory,
268
- { role: "user", content: userContent },
269
- ];
270
- // Session-level loop detector: persists failed strategies across turns.
271
- // Created once per conversation, reset only when user starts a new conversation.
272
- if (!sessionLoopDetector || conversationHistory.length === 0) {
273
- sessionLoopDetector = new LoopDetector();
428
+
429
+ // BeforeTool hook — may block or modify input
430
+ let effectiveInput = input;
431
+ if (hooks.length > 0) {
432
+ const hookResult = await runBeforeToolHook(hooks, name, input);
433
+ if (!hookResult.allow) {
434
+ return {
435
+ success: false,
436
+ output: hookResult.message || "Blocked by hook"
437
+ };
438
+ }
439
+ if (hookResult.modifiedInput) {
440
+ effectiveInput = hookResult.modifiedInput;
441
+ }
274
442
  }
275
- const loopDetector = sessionLoopDetector;
276
- loopDetector.resetTurn();
277
- let totalIn = 0;
278
- let totalOut = 0;
279
- let totalCacheCreation = 0;
280
- let totalCacheRead = 0;
281
- let totalThinking = 0;
282
- let allAssistantText = [];
283
- let prevIterationInputTokens = 0; // Tracks actual context size from last API call
284
- // Telemetry: one turn per user message (not per API call)
285
- const sessionStart = Date.now();
286
- const { storeId } = resolveConfig();
287
- const turnNum = nextTurn(); // ONCE per user message
288
- const turnCtx = createTurnContext({ model: getModel(), turnNumber: turnNum });
289
- addBreadcrumb("agent", `Turn ${turnNum}: ${message.slice(0, 100)}`, "info");
290
- // Set server tool context so tool calls carry trace/user identity to Fly.io server
291
- setServerToolContext({
292
- traceId: turnCtx.traceId,
293
- conversationId: turnCtx.conversationId,
294
- userId: turnCtx.userId,
295
- userEmail: turnCtx.userEmail,
296
- source: "whale-code",
297
- });
298
- logSpan({
299
- action: "chat.user_message",
300
- durationMs: 0,
301
- context: turnCtx,
302
- storeId: storeId || undefined,
303
- details: {
304
- message: message,
305
- conversation_history_length: conversationHistory.length,
306
- },
307
- });
308
- let sessionCostUsd = 0;
309
- let compactionCount = 0;
310
- const costWarningsEmitted = new Set();
311
- const effectiveBudget = opts.maxBudgetUsd ?? DEFAULT_SESSION_COST_BUDGET_USD;
312
- const activeModel = getModel();
313
- // Tool executor — routes to interactive, local, server, or MCP tools.
314
- // Wraps execution with before/after hooks when hooks are loaded.
315
- const INTERACTIVE_TOOL_NAMES = new Set(INTERACTIVE_TOOL_DEFINITIONS.map(t => t.name));
316
- const toolExecutor = async (name, input) => {
317
- if (!name) {
318
- return { success: false, output: "Tool call missing name — skipping." };
443
+ let result;
444
+ if (INTERACTIVE_TOOL_NAMES.has(name)) {
445
+ result = await executeInteractiveTool(name, effectiveInput);
446
+
447
+ // For exit_plan_mode: wait for UI approval, then map decision to result
448
+ if (name === "exit_plan_mode" && result.success) {
449
+ const decision = await waitForPlanApproval();
450
+ switch (decision.action) {
451
+ case "execute":
452
+ result = {
453
+ success: true,
454
+ output: `__PLAN_APPROVED_CLEAN__\n${result.output}`
455
+ };
456
+ break;
457
+ case "edit":
458
+ result = {
459
+ success: true,
460
+ output: "Plan returned for revision. Make changes and use ExitPlanMode again when ready."
461
+ };
462
+ break;
463
+ case "feedback":
464
+ result = {
465
+ success: true,
466
+ output: `User feedback on plan:\n\n${decision.feedback || "(no feedback provided)"}\n\nRevise the plan based on this feedback, then use ExitPlanMode again.`
467
+ };
468
+ break;
469
+ case "cancel":
470
+ result = {
471
+ success: true,
472
+ output: "Plan cancelled by user. Do not proceed with implementation."
473
+ };
474
+ break;
319
475
  }
320
- // Permission mode enforcement
321
- if (!isToolAllowedByPermission(name)) {
322
- return { success: false, output: `Tool "${name}" blocked by ${getPermissionMode()} mode. Switch modes with /mode.` };
476
+ }
477
+ } else if (isLocalTool(name)) {
478
+ result = await executeLocalTool(name, effectiveInput, abortSignal);
479
+ } else if (isServerTool(name)) {
480
+ result = await executeServerTool(name, effectiveInput, emitter);
481
+ } else if (mcpClientManager.isMcpTool(name)) {
482
+ result = await mcpClientManager.callTool(name, effectiveInput);
483
+ } else {
484
+ result = {
485
+ success: false,
486
+ output: `Unknown tool: ${name}`
487
+ };
488
+ }
489
+
490
+ // AfterTool hook — may modify output
491
+ if (hooks.length > 0) {
492
+ const afterResult = await runAfterToolHook(hooks, name, result.output, result.success);
493
+ if (afterResult.modifiedOutput !== undefined) {
494
+ result = {
495
+ ...result,
496
+ output: afterResult.modifiedOutput
497
+ };
498
+ }
499
+ }
500
+ return result;
501
+ };
502
+ try {
503
+ for (let iteration = 0; iteration < effectiveMaxTurns; iteration++) {
504
+ if (abortSignal?.aborted) {
505
+ logSpan({
506
+ action: "chat.cancelled",
507
+ durationMs: Date.now() - sessionStart,
508
+ context: turnCtx,
509
+ storeId: storeId || undefined,
510
+ details: {
511
+ iteration,
512
+ reason: "user_abort"
513
+ }
514
+ });
515
+ callbacks.onError("Cancelled", messages);
516
+ return;
517
+ }
518
+
519
+ // Budget enforcement — always enforced (defaults to DEFAULT_SESSION_COST_BUDGET_USD)
520
+ if (sessionCostUsd >= effectiveBudget) {
521
+ logSpan({
522
+ action: "chat.budget_exceeded",
523
+ durationMs: Date.now() - sessionStart,
524
+ context: turnCtx,
525
+ storeId: storeId || undefined,
526
+ severity: "warn",
527
+ details: {
528
+ session_cost_usd: sessionCostUsd,
529
+ max_budget_usd: effectiveBudget,
530
+ iteration
531
+ }
532
+ });
533
+ callbacks.onError(`Budget exceeded: $${sessionCostUsd.toFixed(4)} >= $${effectiveBudget}`, messages);
534
+ return;
535
+ }
536
+ const apiStart = Date.now();
537
+ const apiSpanId = generateSpanId();
538
+ const apiRowId = crypto.randomUUID(); // UUID for this span's row — children reference via parent_id
539
+ const costContext = `Session cost: $${sessionCostUsd.toFixed(2)} | Budget remaining: $${(effectiveBudget - sessionCostUsd).toFixed(2)}`;
540
+
541
+ // Build API request config
542
+ const currentModel = getModel();
543
+ const apiConfig = buildAPIRequest({
544
+ model: currentModel,
545
+ contextProfile: "main",
546
+ thinkingEnabled: opts.thinking,
547
+ contextOverrides: ctxOverrides
548
+ });
549
+
550
+ // Prepare with prompt caching
551
+ let {
552
+ tools: cachedTools,
553
+ messages: cachedMessages
554
+ } = prepareWithCaching(tools, messages);
555
+ const system = buildSystemBlocks(systemPrompt, costContext);
556
+
557
+ // Client-side context trimming for non-Anthropic providers.
558
+ // Uses prevIterationInputTokens (actual context size from last API call) — NOT
559
+ // cumulative sessionInputTokens, which grows quadratically and would trigger too early.
560
+ const provider = getProvider(currentModel);
561
+ if (provider === "gemini" || provider === "openai") {
562
+ const preTrimMessages = cachedMessages;
563
+ if (provider === "gemini") {
564
+ // Emergency fallback only — compaction fires at 700K first; trim at 950K catches failures
565
+ cachedMessages = trimGeminiContext(cachedMessages, prevIterationInputTokens);
566
+ } else {
567
+ // Emergency fallback only — compaction fires at 120K first; trim at 190K catches failures
568
+ // GPT-4o: 128K context → 96K threshold (no compaction headroom)
569
+ const threshold = currentModel === "gpt-4o" ? 96_000 : 190_000;
570
+ cachedMessages = trimOpenAIContext(cachedMessages, prevIterationInputTokens, threshold);
323
571
  }
324
- // BeforeTool hook may block or modify input
325
- let effectiveInput = input;
326
- if (hooks.length > 0) {
327
- const hookResult = await runBeforeToolHook(hooks, name, input);
328
- if (!hookResult.allow) {
329
- return { success: false, output: hookResult.message || "Blocked by hook" };
330
- }
331
- if (hookResult.modifiedInput) {
332
- effectiveInput = hookResult.modifiedInput;
333
- }
572
+ // Notify UI when trimming actually occurred (trim returns same ref if no-op)
573
+ if (cachedMessages !== preTrimMessages) {
574
+ // Count tool results before/after to report meaningful numbers
575
+ const countToolResults = msgs => msgs.reduce((sum, m) => sum + (Array.isArray(m.content) ? m.content.filter(b => b.type === "tool_result" && b.content !== "[trimmed]").length : 0), 0);
576
+ const activeBefore = countToolResults(preTrimMessages);
577
+ const activeAfter = countToolResults(cachedMessages);
578
+ const estimatedSaved = Math.round(prevIterationInputTokens * ((activeBefore - activeAfter) / Math.max(activeBefore, 1)));
579
+ callbacks.onAutoCompact?.(activeBefore, activeAfter, estimatedSaved);
580
+ emitter?.emitCompact(activeBefore, activeAfter, estimatedSaved);
334
581
  }
335
- let result;
336
- if (INTERACTIVE_TOOL_NAMES.has(name)) {
337
- result = await executeInteractiveTool(name, effectiveInput);
338
- // For exit_plan_mode: wait for UI approval, then map decision to result
339
- if (name === "exit_plan_mode" && result.success) {
340
- const decision = await waitForPlanApproval();
341
- switch (decision.action) {
342
- case "execute":
343
- result = { success: true, output: `__PLAN_APPROVED_CLEAN__\n${result.output}` };
344
- break;
345
- case "edit":
346
- result = { success: true, output: "Plan returned for revision. Make changes and use ExitPlanMode again when ready." };
347
- break;
348
- case "feedback":
349
- result = { success: true, output: `User feedback on plan:\n\n${decision.feedback || "(no feedback provided)"}\n\nRevise the plan based on this feedback, then use ExitPlanMode again.` };
350
- break;
351
- case "cancel":
352
- result = { success: true, output: "Plan cancelled by user. Do not proceed with implementation." };
353
- break;
354
- }
582
+ }
583
+
584
+ // Get auth token
585
+ const token = await getValidToken();
586
+ if (!token) {
587
+ throw new Error("Not logged in. Run: whale login");
588
+ }
589
+
590
+ // Call server proxy with built-in retry
591
+ const originalModel = currentModel;
592
+ const stream = await callServerProxy({
593
+ proxyUrl: getProxyUrl(),
594
+ token,
595
+ model: currentModel,
596
+ system,
597
+ messages: cachedMessages,
598
+ tools: cachedTools,
599
+ apiConfig,
600
+ signal: abortSignal,
601
+ fallbackModel: opts.fallbackModel,
602
+ storeId: storeId || undefined,
603
+ onFallback: (from, to) => {
604
+ setModel(to);
605
+ logSpan({
606
+ action: "claude_api_fallback",
607
+ durationMs: 0,
608
+ context: {
609
+ ...turnCtx,
610
+ spanId: apiSpanId
611
+ },
612
+ storeId: storeId || undefined,
613
+ details: {
614
+ from_model: from,
615
+ to_model: to
355
616
  }
617
+ });
618
+ },
619
+ onRetry: (attempt, max, err) => {
620
+ const msg = `\n\x1b[33m\u21BB Retrying (${attempt}/${max})... ${err.slice(0, 80)}\x1b[0m\n`;
621
+ if (emitter) {
622
+ emitter.emitText(msg);
623
+ } else {
624
+ callbacks.onText(msg);
625
+ }
626
+ },
627
+ onTokenRefresh: async () => {
628
+ const result = await refreshSession();
629
+ return result.success ? result.config.access_token : null;
356
630
  }
357
- else if (isLocalTool(name)) {
358
- result = await executeLocalTool(name, effectiveInput);
631
+ });
632
+
633
+ // Process stream events with UI callbacks
634
+ let thinkingChunks = 0;
635
+ const result = await processStreamWithCallbacks(parseSSEStream(stream, abortSignal), {
636
+ onText: text => {
637
+ if (emitter) {
638
+ emitter.emitText(text);
639
+ } else {
640
+ callbacks.onText(text);
641
+ }
642
+ },
643
+ onThinking: () => {
644
+ thinkingChunks++;
645
+ emitter?.emitThinking(thinkingChunks);
646
+ },
647
+ onToolStart: (name, input) => {
648
+ // NOTE: Do NOT call callbacks.onToolStart here — dispatchTools.onStart
649
+ // fires it once per tool at execution time. Calling it here too would
650
+ // send duplicate tool_start events (stale spinners in WhaleChat).
651
+ if (input) {
652
+ // Tool block complete — emit structured start for CLI TUI only
653
+ emitter?.emitToolStart("", name);
654
+ }
359
655
  }
360
- else if (isServerTool(name)) {
361
- result = await executeServerTool(name, effectiveInput, emitter);
656
+ }, abortSignal);
657
+
658
+ // Flush buffered text
659
+ emitter?.flushText();
660
+
661
+ // Restore original model after transient fallback
662
+ if (getModel() !== originalModel && opts.fallbackModel) {
663
+ setModelById(originalModel);
664
+ }
665
+
666
+ // Update session token tracking
667
+ sessionInputTokens += result.usage.inputTokens;
668
+ sessionOutputTokens += result.usage.outputTokens;
669
+ prevIterationInputTokens = result.usage.inputTokens; // Actual context size for next trim check
670
+
671
+ // Emit usage with model + cost context for all providers
672
+ if (emitter && (result.usage.inputTokens > 0 || result.usage.outputTokens > 0)) {
673
+ const iterCost = estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
674
+ emitter.emitUsage(result.usage.inputTokens, result.usage.outputTokens, currentModel, iterCost, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
675
+ }
676
+ totalIn += result.usage.inputTokens;
677
+ totalOut += result.usage.outputTokens;
678
+ totalCacheCreation += result.usage.cacheCreationTokens;
679
+ totalCacheRead += result.usage.cacheReadTokens;
680
+ totalThinking += result.thinkingTokens;
681
+ sessionCostUsd += estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
682
+
683
+ // Graduated cost warnings
684
+ emitCostWarningIfNeeded(sessionCostUsd, effectiveBudget, costWarningsEmitted, text => {
685
+ if (emitter) {
686
+ emitter.emitText(text);
687
+ } else {
688
+ callbacks.onText(text);
362
689
  }
363
- else if (mcpClientManager.isMcpTool(name)) {
364
- result = await mcpClientManager.callTool(name, effectiveInput);
690
+ });
691
+
692
+ // Server-side context management notification
693
+ if (result.contextManagementApplied) {
694
+ callbacks.onAutoCompact?.(messages.length, messages.length, 0);
695
+ emitter?.emitCompact(messages.length, messages.length, 0);
696
+ logSpan({
697
+ action: "chat.api_compaction",
698
+ durationMs: Date.now() - apiStart,
699
+ context: turnCtx,
700
+ storeId: storeId || undefined,
701
+ details: {
702
+ type: "server_side",
703
+ has_compaction_content: result.compactionContent !== null,
704
+ iteration
705
+ }
706
+ });
707
+ }
708
+ if (result.text) allAssistantText.push(result.text);
709
+
710
+ // Telemetry: API call span
711
+ const iterCostUsd = estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
712
+ logSpan({
713
+ action: "claude_api_request",
714
+ durationMs: Date.now() - apiStart,
715
+ context: {
716
+ ...turnCtx,
717
+ spanId: apiSpanId,
718
+ rowId: apiRowId,
719
+ inputTokens: result.usage.inputTokens,
720
+ outputTokens: result.usage.outputTokens,
721
+ totalCost: iterCostUsd,
722
+ model: currentModel
723
+ },
724
+ storeId: storeId || undefined,
725
+ details: {
726
+ "gen_ai.request.model": currentModel,
727
+ "gen_ai.usage.input_tokens": result.usage.inputTokens,
728
+ "gen_ai.usage.output_tokens": result.usage.outputTokens,
729
+ "gen_ai.usage.cache_creation_tokens": result.usage.cacheCreationTokens,
730
+ "gen_ai.usage.cache_read_tokens": result.usage.cacheReadTokens,
731
+ "gen_ai.usage.cost": iterCostUsd,
732
+ stop_reason: result.stopReason === "compaction" ? "compaction" : result.toolUseBlocks.length > 0 ? "tool_use" : "end_turn",
733
+ iteration,
734
+ tool_count: result.toolUseBlocks.length,
735
+ tool_names: result.toolUseBlocks.map(t => t.name)
365
736
  }
366
- else {
367
- result = { success: false, output: `Unknown tool: ${name}` };
737
+ });
738
+
739
+ // Compaction handling — API paused after generating summary.
740
+ // Per Anthropic docs: the compaction block replaces all prior context.
741
+ // Do NOT preserve old messages — they can exceed the trigger threshold
742
+ // and cause an infinite compaction loop. The summary captures all context.
743
+ if (result.stopReason === "compaction" && result.compactionContent) {
744
+ compactionCount++;
745
+ consecutiveCompactions++;
746
+ logSpan({
747
+ action: "chat.compaction_pause",
748
+ durationMs: Date.now() - apiStart,
749
+ context: turnCtx,
750
+ storeId: storeId || undefined,
751
+ details: {
752
+ compaction_count: compactionCount,
753
+ consecutive: consecutiveCompactions,
754
+ messages_before: messages.length
755
+ }
756
+ });
757
+
758
+ // Circuit breaker: if compaction fires repeatedly without progress,
759
+ // the summary + system prompt alone may exceed the trigger. Force wrap-up.
760
+ const MAX_CONSECUTIVE_COMPACTIONS = 3;
761
+ const shouldWrapUp = consecutiveCompactions > MAX_CONSECUTIVE_COMPACTIONS || compactionCount * effectiveCompactionTrigger >= effectiveCompactionBudget;
762
+ const compactedMessages = [{
763
+ role: "assistant",
764
+ content: [{
765
+ type: "compaction",
766
+ content: result.compactionContent
767
+ }]
768
+ }, {
769
+ role: "user",
770
+ content: [{
771
+ type: "text",
772
+ text: shouldWrapUp ? "You have reached the context budget. Please wrap up your current work and provide a final summary of what was accomplished and what remains." : "Continue with your task."
773
+ }]
774
+ }];
775
+ messages.length = 0;
776
+ messages.push(...compactedMessages);
777
+ if (shouldWrapUp) {
778
+ const budgetMsg = consecutiveCompactions > MAX_CONSECUTIVE_COMPACTIONS ? "\n[Compaction loop detected — wrapping up.]" : "\n[Context budget reached — wrapping up.]";
779
+ if (emitter) {
780
+ emitter.emitText(budgetMsg);
781
+ } else {
782
+ callbacks.onText(budgetMsg);
783
+ }
368
784
  }
369
- // AfterTool hook may modify output
370
- if (hooks.length > 0) {
371
- const afterResult = await runAfterToolHook(hooks, name, result.output, result.success);
372
- if (afterResult.modifiedOutput !== undefined) {
373
- result = { ...result, output: afterResult.modifiedOutput };
374
- }
785
+ iteration--; // Don't count compaction as an iteration
786
+ continue;
787
+ }
788
+
789
+ // No tool calls check if we should continue or stop
790
+ if (result.toolUseBlocks.length === 0) {
791
+ // If model hit max_tokens, it was truncated mid-response — continue so it can finish.
792
+ // Cap at MAX_CONTINUATIONS to prevent infinite loops on very large outputs (e.g. HTML reports).
793
+ if (result.stopReason === "max_tokens" && maxTokensContinuations < maxContinuations) {
794
+ maxTokensContinuations++;
795
+ const truncatedText = result.text || "";
796
+ const assistantContent = buildAssistantContent({
797
+ text: truncatedText,
798
+ toolUseBlocks: [],
799
+ thinkingBlocks: result.thinkingBlocks,
800
+ compactionContent: result.compactionContent
801
+ });
802
+ messages.push({
803
+ role: "assistant",
804
+ content: assistantContent
805
+ });
806
+
807
+ // Escalating continuation prompts — if the model keeps hitting max_tokens,
808
+ // it's probably trying to write a large file. Guide it toward smaller chunks.
809
+ const continuationText = maxTokensContinuations === 1 ? "[Your response was truncated due to the output token limit. If you were writing a large file, split it into multiple smaller write_file calls (~200 lines each) or use run_command with a heredoc. Continue concisely.]" : `[Your response was truncated again (attempt ${maxTokensContinuations}/${maxContinuations}). You MUST reduce output size. For large files: use multiple write_file calls of ~100-200 lines each, or write via run_command. Do NOT attempt to write the entire file in one call.]`;
810
+ messages.push({
811
+ role: "user",
812
+ content: [{
813
+ type: "text",
814
+ text: continuationText
815
+ }]
816
+ });
817
+ continue;
375
818
  }
376
- return result;
377
- };
378
- // Deprecated: Anthropic context_management handles limits via clear_tool_uses + compaction.
379
- // tool-dispatch.ts uses SAFETY_MAX_CHARS (500K) this value is ignored.
380
- const maxResultChars = undefined;
381
- try {
382
- for (let iteration = 0; iteration < effectiveMaxTurns; iteration++) {
383
- if (abortSignal?.aborted) {
384
- logSpan({ action: "chat.cancelled", durationMs: Date.now() - sessionStart, context: turnCtx, storeId: storeId || undefined, details: { iteration, reason: "user_abort" } });
385
- callbacks.onError("Cancelled", messages);
386
- return;
387
- }
388
- // Budget enforcement — always enforced (defaults to DEFAULT_SESSION_COST_BUDGET_USD)
389
- if (sessionCostUsd >= effectiveBudget) {
390
- logSpan({ action: "chat.budget_exceeded", durationMs: Date.now() - sessionStart, context: turnCtx, storeId: storeId || undefined, severity: "warn", details: { session_cost_usd: sessionCostUsd, max_budget_usd: effectiveBudget, iteration } });
391
- callbacks.onError(`Budget exceeded: $${sessionCostUsd.toFixed(4)} >= $${effectiveBudget}`, messages);
392
- return;
393
- }
394
- const apiStart = Date.now();
395
- const apiSpanId = generateSpanId();
396
- const apiRowId = crypto.randomUUID(); // UUID for this span's row — children reference via parent_id
397
- const costContext = `Session cost: $${sessionCostUsd.toFixed(2)} | Budget remaining: $${(effectiveBudget - sessionCostUsd).toFixed(2)}`;
398
- // Build API request config
399
- const currentModel = getModel();
400
- const apiConfig = buildAPIRequest({
401
- model: currentModel,
402
- contextProfile: "main",
403
- thinkingEnabled: opts.thinking,
404
- });
405
- // Prepare with prompt caching
406
- let { tools: cachedTools, messages: cachedMessages } = prepareWithCaching(tools, messages);
407
- const system = buildSystemBlocks(systemPrompt, costContext);
408
- // Client-side context trimming for non-Anthropic providers.
409
- // Uses prevIterationInputTokens (actual context size from last API call) — NOT
410
- // cumulative sessionInputTokens, which grows quadratically and would trigger too early.
411
- const provider = getProvider(currentModel);
412
- if (provider === "gemini" || provider === "openai") {
413
- const preTrimMessages = cachedMessages;
414
- if (provider === "gemini") {
415
- // Emergency fallback only — compaction fires at 700K first; trim at 950K catches failures
416
- cachedMessages = trimGeminiContext(cachedMessages, prevIterationInputTokens);
417
- }
418
- else {
419
- // Emergency fallback only — compaction fires at 120K first; trim at 190K catches failures
420
- // GPT-4o: 128K context → 96K threshold (no compaction headroom)
421
- const threshold = currentModel === "gpt-4o" ? 96_000 : 190_000;
422
- cachedMessages = trimOpenAIContext(cachedMessages, prevIterationInputTokens, threshold);
423
- }
424
- // Notify UI when trimming actually occurred (trim returns same ref if no-op)
425
- if (cachedMessages !== preTrimMessages) {
426
- // Count tool results before/after to report meaningful numbers
427
- const countToolResults = (msgs) => msgs.reduce((sum, m) => sum + (Array.isArray(m.content)
428
- ? m.content.filter(b => b.type === "tool_result" && b.content !== "[trimmed]").length
429
- : 0), 0);
430
- const activeBefore = countToolResults(preTrimMessages);
431
- const activeAfter = countToolResults(cachedMessages);
432
- const estimatedSaved = Math.round(prevIterationInputTokens * ((activeBefore - activeAfter) / Math.max(activeBefore, 1)));
433
- callbacks.onAutoCompact?.(activeBefore, activeAfter, estimatedSaved);
434
- emitter?.emitCompact(activeBefore, activeAfter, estimatedSaved);
435
- }
436
- }
437
- // Get auth token
438
- const token = await getValidToken();
439
- if (!token) {
440
- throw new Error("Not logged in. Run: whale login");
441
- }
442
- // Call server proxy with built-in retry
443
- const originalModel = currentModel;
444
- const stream = await callServerProxy({
445
- proxyUrl: getProxyUrl(),
446
- token,
447
- model: currentModel,
448
- system,
449
- messages: cachedMessages,
450
- tools: cachedTools,
451
- apiConfig,
452
- signal: abortSignal,
453
- fallbackModel: opts.fallbackModel,
454
- storeId: storeId || undefined,
455
- onFallback: (from, to) => {
456
- setModel(to);
457
- logSpan({ action: "claude_api_fallback", durationMs: 0, context: { ...turnCtx, spanId: apiSpanId }, storeId: storeId || undefined, details: { from_model: from, to_model: to } });
458
- },
459
- onRetry: (attempt, max, err) => {
460
- const msg = `\n\x1b[33m\u21BB Retrying (${attempt}/${max})... ${err.slice(0, 80)}\x1b[0m\n`;
461
- if (emitter) {
462
- emitter.emitText(msg);
463
- }
464
- else {
465
- callbacks.onText(msg);
466
- }
467
- },
468
- onTokenRefresh: async () => {
469
- const result = await refreshSession();
470
- return result.success ? result.config.access_token : null;
471
- },
472
- });
473
- // Process stream events with UI callbacks
474
- let thinkingChunks = 0;
475
- const result = await processStreamWithCallbacks(parseSSEStream(stream, abortSignal), {
476
- onText: (text) => {
477
- if (emitter) {
478
- emitter.emitText(text);
479
- }
480
- else {
481
- callbacks.onText(text);
482
- }
483
- },
484
- onThinking: () => {
485
- thinkingChunks++;
486
- emitter?.emitThinking(thinkingChunks);
487
- },
488
- onToolStart: (name, input) => {
489
- // NOTE: Do NOT call callbacks.onToolStart here — dispatchTools.onStart
490
- // fires it once per tool at execution time. Calling it here too would
491
- // send duplicate tool_start events (stale spinners in WhaleChat).
492
- if (input) {
493
- // Tool block complete — emit structured start for CLI TUI only
494
- emitter?.emitToolStart("", name);
495
- }
496
- },
497
- }, abortSignal);
498
- // Flush buffered text
499
- emitter?.flushText();
500
- // Restore original model after transient fallback
501
- if (getModel() !== originalModel && opts.fallbackModel) {
502
- setModelById(originalModel);
503
- }
504
- // Update session token tracking
505
- sessionInputTokens += result.usage.inputTokens;
506
- sessionOutputTokens += result.usage.outputTokens;
507
- prevIterationInputTokens = result.usage.inputTokens; // Actual context size for next trim check
508
- // Emit usage with model + cost context for all providers
509
- if (emitter && (result.usage.inputTokens > 0 || result.usage.outputTokens > 0)) {
510
- const iterCost = estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
511
- emitter.emitUsage(result.usage.inputTokens, result.usage.outputTokens, currentModel, iterCost, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
512
- }
513
- totalIn += result.usage.inputTokens;
514
- totalOut += result.usage.outputTokens;
515
- totalCacheCreation += result.usage.cacheCreationTokens;
516
- totalCacheRead += result.usage.cacheReadTokens;
517
- totalThinking += result.thinkingTokens;
518
- sessionCostUsd += estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
519
- // Graduated cost warnings
520
- emitCostWarningIfNeeded(sessionCostUsd, effectiveBudget, costWarningsEmitted, (text) => { if (emitter) {
521
- emitter.emitText(text);
522
- }
523
- else {
524
- callbacks.onText(text);
525
- } });
526
- // Server-side context management notification
527
- if (result.contextManagementApplied) {
528
- callbacks.onAutoCompact?.(messages.length, messages.length, 0);
529
- emitter?.emitCompact(messages.length, messages.length, 0);
530
- logSpan({ action: "chat.api_compaction", durationMs: Date.now() - apiStart, context: turnCtx, storeId: storeId || undefined, details: { type: "server_side", has_compaction_content: result.compactionContent !== null, iteration } });
531
- }
532
- if (result.text)
533
- allAssistantText.push(result.text);
534
- // Telemetry: API call span
535
- const iterCostUsd = estimateCostUsd(result.usage.inputTokens, result.usage.outputTokens, currentModel, result.thinkingTokens, result.usage.cacheReadTokens, result.usage.cacheCreationTokens);
536
- logSpan({
537
- action: "claude_api_request",
538
- durationMs: Date.now() - apiStart,
539
- context: { ...turnCtx, spanId: apiSpanId, rowId: apiRowId, inputTokens: result.usage.inputTokens, outputTokens: result.usage.outputTokens, totalCost: iterCostUsd, model: currentModel },
540
- storeId: storeId || undefined,
541
- details: {
542
- "gen_ai.request.model": currentModel,
543
- "gen_ai.usage.input_tokens": result.usage.inputTokens,
544
- "gen_ai.usage.output_tokens": result.usage.outputTokens,
545
- "gen_ai.usage.cache_creation_tokens": result.usage.cacheCreationTokens,
546
- "gen_ai.usage.cache_read_tokens": result.usage.cacheReadTokens,
547
- "gen_ai.usage.cost": iterCostUsd,
548
- stop_reason: result.stopReason === "compaction" ? "compaction" : result.toolUseBlocks.length > 0 ? "tool_use" : "end_turn",
549
- iteration,
550
- tool_count: result.toolUseBlocks.length,
551
- tool_names: result.toolUseBlocks.map(t => t.name),
552
- },
553
- });
554
- // Compaction handling — API paused after generating summary.
555
- // Preserve last 2 messages for continuity, then resume.
556
- if (result.stopReason === "compaction" && result.compactionContent) {
557
- compactionCount++;
558
- logSpan({ action: "chat.compaction_pause", durationMs: Date.now() - apiStart, context: turnCtx, storeId: storeId || undefined, details: { compaction_count: compactionCount, messages_before: messages.length } });
559
- // Budget enforcement
560
- if (compactionCount * COMPACTION_TRIGGER_TOKENS >= COMPACTION_TOTAL_BUDGET) {
561
- const budgetMsg = "\n[Context budget reached — wrapping up.]";
562
- if (emitter) {
563
- emitter.emitText(budgetMsg);
564
- }
565
- else {
566
- callbacks.onText(budgetMsg);
567
- }
568
- const compactedMessages = [
569
- { role: "assistant", content: [{ type: "compaction", content: result.compactionContent }] },
570
- { role: "user", content: [{ type: "text", text: "You have reached the context budget. Please wrap up your current work and provide a final summary of what was accomplished and what remains." }] },
571
- ];
572
- messages.length = 0;
573
- messages.push(...compactedMessages);
574
- continue;
575
- }
576
- // Normal compaction: preserve last 2 messages for continuity
577
- const preserved = messages.slice(-2);
578
- const compactedMessages = [
579
- { role: "assistant", content: [{ type: "compaction", content: result.compactionContent }] },
580
- ...preserved,
581
- ];
582
- messages.length = 0;
583
- messages.push(...compactedMessages);
584
- iteration--; // Don't count compaction as an iteration
585
- continue;
586
- }
587
- // No tool calls — check if we should continue or stop
588
- if (result.toolUseBlocks.length === 0) {
589
- // If model hit max_tokens, it was truncated mid-response — continue so it can finish
590
- if (result.stopReason === "max_tokens") {
591
- const truncatedText = result.text || "";
592
- const assistantContent = buildAssistantContent({
593
- text: truncatedText,
594
- toolUseBlocks: [],
595
- thinkingBlocks: result.thinkingBlocks,
596
- compactionContent: result.compactionContent,
597
- });
598
- messages.push({ role: "assistant", content: assistantContent });
599
- messages.push({ role: "user", content: [{ type: "text", text: "[Your response was truncated due to length. Please continue where you left off.]" }] });
600
- continue;
601
- }
602
- break;
603
- }
604
- // Execute tools via shared dispatch
605
- const { results: toolResults, bailOut, bailMessage } = await dispatchTools(result.toolUseBlocks, toolExecutor, {
606
- loopDetector,
607
- maxConcurrent: 7,
608
- maxResultChars,
609
- onStart: (name, input) => {
610
- callbacks.onToolStart(name, input);
611
- },
612
- onResult: (name, success, output, durationMs) => {
613
- callbacks.onToolResult(name, success, output, undefined, durationMs);
614
- logSpan({
615
- action: `tool.${name}`,
616
- durationMs,
617
- context: { ...turnCtx, spanId: generateSpanId(), parentSpanId: apiSpanId, parentId: apiRowId },
618
- storeId: storeId || undefined,
619
- error: success ? undefined : output,
620
- details: {
621
- tool_input: {},
622
- tool_result: truncateResult(output, 2000),
623
- error_type: success ? undefined : classifyToolError(output),
624
- iteration,
625
- },
626
- });
627
- },
628
- signal: abortSignal,
629
- transcribeAudio: storeId
630
- ? async (base64, mediaType) => callTranscribe({
631
- proxyUrl: getProxyUrl(),
632
- token,
633
- storeId: storeId,
634
- audioBase64: base64,
635
- mediaType,
636
- })
637
- : undefined,
638
- });
639
- if (bailOut) {
640
- logSpan({ action: "chat.bail_out", durationMs: Date.now() - sessionStart, context: turnCtx, storeId: storeId || undefined, severity: "warn", details: { ...loopDetector.getSessionStats(), message: bailMessage, iteration } });
641
- }
642
- // Shell output summarization — summarize long bash output to save context window
643
- const toolNameMap = new Map(result.toolUseBlocks.map(t => [t.id, t.name]));
644
- const finalToolResults = await summarizeLongToolResults(toolResults, toolNameMap, getProxyUrl(), token, shellSummarization, storeId || undefined);
645
- // Build assistant content and append to conversation
646
- const assistantContent = buildAssistantContent({
647
- text: result.text,
648
- toolUseBlocks: result.toolUseBlocks,
649
- thinkingBlocks: result.thinkingBlocks,
650
- compactionContent: result.compactionContent,
651
- });
652
- messages.push({ role: "assistant", content: assistantContent });
653
- // Check for __PLAN_APPROVED_CLEAN__ marker — clear context and start fresh with just the plan
654
- const planCleanMarker = "__PLAN_APPROVED_CLEAN__\n";
655
- const hasCleanPlanApproval = finalToolResults.some((tr) => {
656
- const content = typeof tr.content === "string" ? tr.content : "";
657
- return content.startsWith(planCleanMarker);
658
- });
659
- if (hasCleanPlanApproval) {
660
- // Extract plan content from the marker
661
- const planResult = finalToolResults.find((tr) => {
662
- const content = typeof tr.content === "string" ? tr.content : "";
663
- return content.startsWith(planCleanMarker);
664
- });
665
- const planText = planResult.content.slice(planCleanMarker.length);
666
- const beforeCount = messages.length;
667
- messages.length = 0;
668
- messages.push({ role: "user", content: [{ type: "text", text: `Implement this plan:\n\n${planText}` }] });
669
- callbacks.onAutoCompact?.(beforeCount, 1, 0);
670
- continue;
671
- }
672
- messages.push({ role: "user", content: finalToolResults });
673
- // Non-native compaction for OpenAI/Gemini — fires after tool results appended
674
- const compactionCfg = getCompactionConfig(currentModel);
675
- if (!compactionCfg.isNative && result.usage.inputTokens >= compactionCfg.triggerTokens) {
676
- compactionCount++;
677
- if (compactionCount * compactionCfg.triggerTokens >= compactionCfg.totalBudget) {
678
- // Budget exhaustion — force wrap-up (same as native compaction budget logic)
679
- const budgetMsg = "\n[Context budget reached — wrapping up.]";
680
- if (emitter) {
681
- emitter.emitText(budgetMsg);
682
- }
683
- else {
684
- callbacks.onText(budgetMsg);
685
- }
686
- const summary = await requestProviderCompaction({
687
- proxyUrl: getProxyUrl(),
688
- token,
689
- messages: messages,
690
- systemPrompt,
691
- });
692
- const compactedMessages = [
693
- ...(summary
694
- ? [{ role: "assistant", content: [{ type: "compaction", content: summary }] }]
695
- : []),
696
- { role: "user", content: [{ type: "text", text: "You have reached the context budget. Please wrap up your current work and provide a final summary of what was accomplished and what remains." }] },
697
- ];
698
- messages.length = 0;
699
- messages.push(...compactedMessages);
700
- continue;
701
- }
702
- // Normal compaction — summarize and preserve last 2 messages
703
- const summary = await requestProviderCompaction({
704
- proxyUrl: getProxyUrl(),
705
- token,
706
- messages: messages,
707
- systemPrompt,
708
- });
709
- if (summary) {
710
- const preserved = messages.slice(-2);
711
- const compactedMessages = [
712
- { role: "assistant", content: [{ type: "compaction", content: summary }] },
713
- ...preserved,
714
- ];
715
- messages.length = 0;
716
- messages.push(...compactedMessages);
717
- iteration--; // Don't count compaction as an iteration
718
- callbacks.onAutoCompact?.(messages.length + preserved.length, messages.length, Math.round(result.usage.inputTokens * 0.7));
719
- emitter?.emitCompact(messages.length + preserved.length, messages.length, Math.round(result.usage.inputTokens * 0.7));
720
- logSpan({ action: "chat.provider_compaction", durationMs: 0, context: turnCtx, storeId: storeId || undefined, details: { compaction_count: compactionCount, provider, model: currentModel, input_tokens: result.usage.inputTokens, trigger_tokens: compactionCfg.triggerTokens } });
721
- }
819
+ break;
820
+ }
821
+
822
+ // Reset counters model is taking tool actions, making progress
823
+ maxTokensContinuations = 0;
824
+ consecutiveCompactions = 0;
825
+
826
+ // Execute tools via shared dispatch
827
+ const {
828
+ results: toolResults,
829
+ bailOut,
830
+ bailMessage
831
+ } = await dispatchTools(result.toolUseBlocks, toolExecutor, {
832
+ loopDetector,
833
+ maxConcurrent: resolved?.maxConcurrentTools ?? AGENT_DEFAULTS.maxConcurrentTools,
834
+ batchErrorLimit: resolved?.loopBatchErrorLimit ?? AGENT_DEFAULTS.loopBatchErrorLimit,
835
+ onStart: (name, input) => {
836
+ callbacks.onToolStart(name, input);
837
+ },
838
+ onResult: (name, success, output, durationMs) => {
839
+ callbacks.onToolResult(name, success, output, undefined, durationMs);
840
+ logSpan({
841
+ action: `tool.${name}`,
842
+ durationMs,
843
+ context: {
844
+ ...turnCtx,
845
+ spanId: generateSpanId(),
846
+ parentSpanId: apiSpanId,
847
+ parentId: apiRowId
848
+ },
849
+ storeId: storeId || undefined,
850
+ error: success ? undefined : output,
851
+ details: {
852
+ tool_input: {},
853
+ tool_result: truncateResult(output, 2000),
854
+ error_type: success ? undefined : classifyToolError(output),
855
+ iteration
722
856
  }
723
- }
724
- // Telemetry: session summary
857
+ });
858
+ },
859
+ signal: abortSignal,
860
+ transcribeAudio: storeId ? async (base64, mediaType) => callTranscribe({
861
+ proxyUrl: getProxyUrl(),
862
+ token,
863
+ storeId: storeId,
864
+ audioBase64: base64,
865
+ mediaType
866
+ }) : undefined
867
+ });
868
+ if (bailOut) {
725
869
  logSpan({
726
- action: "chat.session_complete",
727
- durationMs: Date.now() - sessionStart,
728
- context: { ...turnCtx, inputTokens: totalIn, outputTokens: totalOut, model: activeModel },
870
+ action: "chat.bail_out",
871
+ durationMs: Date.now() - sessionStart,
872
+ context: turnCtx,
873
+ storeId: storeId || undefined,
874
+ severity: "warn",
875
+ details: {
876
+ ...loopDetector.getSessionStats(),
877
+ message: bailMessage,
878
+ iteration
879
+ }
880
+ });
881
+ }
882
+
883
+ // Shell output summarization — summarize long bash output to save context window
884
+ const toolNameMap = new Map(result.toolUseBlocks.map(t => [t.id, t.name]));
885
+ const finalToolResults = await summarizeLongToolResults(toolResults, toolNameMap, getProxyUrl(), token, shellSummarization, storeId || undefined);
886
+
887
+ // Build assistant content and append to conversation
888
+ const assistantContent = buildAssistantContent({
889
+ text: result.text,
890
+ toolUseBlocks: result.toolUseBlocks,
891
+ thinkingBlocks: result.thinkingBlocks,
892
+ compactionContent: result.compactionContent
893
+ });
894
+ messages.push({
895
+ role: "assistant",
896
+ content: assistantContent
897
+ });
898
+
899
+ // Check for __PLAN_APPROVED_CLEAN__ marker — clear context and start fresh with just the plan
900
+ const planCleanMarker = "__PLAN_APPROVED_CLEAN__\n";
901
+ const hasCleanPlanApproval = finalToolResults.some(tr => {
902
+ const content = typeof tr.content === "string" ? tr.content : "";
903
+ return content.startsWith(planCleanMarker);
904
+ });
905
+ if (hasCleanPlanApproval) {
906
+ // Extract plan content from the marker
907
+ const planResult = finalToolResults.find(tr => {
908
+ const content = typeof tr.content === "string" ? tr.content : "";
909
+ return content.startsWith(planCleanMarker);
910
+ });
911
+ const planText = planResult.content.slice(planCleanMarker.length);
912
+ const beforeCount = messages.length;
913
+ messages.length = 0;
914
+ messages.push({
915
+ role: "user",
916
+ content: [{
917
+ type: "text",
918
+ text: `Implement this plan:\n\n${planText}`
919
+ }]
920
+ });
921
+ callbacks.onAutoCompact?.(beforeCount, 1, 0);
922
+ continue;
923
+ }
924
+ messages.push({
925
+ role: "user",
926
+ content: finalToolResults
927
+ });
928
+
929
+ // Non-native compaction for OpenAI/Gemini — fires after tool results appended
930
+ const compactionCfg = getCompactionConfig(currentModel);
931
+ if (!compactionCfg.isNative && result.usage.inputTokens >= compactionCfg.triggerTokens) {
932
+ compactionCount++;
933
+ if (compactionCount * compactionCfg.triggerTokens >= compactionCfg.totalBudget) {
934
+ // Budget exhaustion — force wrap-up (same as native compaction budget logic)
935
+ const budgetMsg = "\n[Context budget reached — wrapping up.]";
936
+ if (emitter) {
937
+ emitter.emitText(budgetMsg);
938
+ } else {
939
+ callbacks.onText(budgetMsg);
940
+ }
941
+ const summary = await requestProviderCompaction({
942
+ proxyUrl: getProxyUrl(),
943
+ token,
944
+ messages,
945
+ systemPrompt
946
+ });
947
+ const compactedMessages = [...(summary ? [{
948
+ role: "assistant",
949
+ content: [{
950
+ type: "compaction",
951
+ content: summary
952
+ }]
953
+ }] : []), {
954
+ role: "user",
955
+ content: [{
956
+ type: "text",
957
+ text: "You have reached the context budget. Please wrap up your current work and provide a final summary of what was accomplished and what remains."
958
+ }]
959
+ }];
960
+ messages.length = 0;
961
+ messages.push(...compactedMessages);
962
+ continue;
963
+ }
964
+
965
+ // Normal compaction — summarize, don't preserve old messages (prevents loop)
966
+ const summary = await requestProviderCompaction({
967
+ proxyUrl: getProxyUrl(),
968
+ token,
969
+ messages: messages,
970
+ systemPrompt
971
+ });
972
+ if (summary) {
973
+ const beforeCount = messages.length;
974
+ const compactedMessages = [{
975
+ role: "assistant",
976
+ content: [{
977
+ type: "compaction",
978
+ content: summary
979
+ }]
980
+ }, {
981
+ role: "user",
982
+ content: [{
983
+ type: "text",
984
+ text: "Continue with your task."
985
+ }]
986
+ }];
987
+ messages.length = 0;
988
+ messages.push(...compactedMessages);
989
+ iteration--; // Don't count compaction as an iteration
990
+
991
+ callbacks.onAutoCompact?.(beforeCount, messages.length, Math.round(result.usage.inputTokens * 0.7));
992
+ emitter?.emitCompact(beforeCount, messages.length, Math.round(result.usage.inputTokens * 0.7));
993
+ logSpan({
994
+ action: "chat.provider_compaction",
995
+ durationMs: 0,
996
+ context: turnCtx,
729
997
  storeId: storeId || undefined,
730
998
  details: {
731
- input_tokens: totalIn, output_tokens: totalOut, total_tokens: totalIn + totalOut,
732
- cache_creation_tokens: totalCacheCreation, cache_read_tokens: totalCacheRead,
733
- session_input_tokens: sessionInputTokens, session_output_tokens: sessionOutputTokens,
734
- model: activeModel,
735
- },
736
- });
737
- const turnCostUsd = estimateCostUsd(totalIn, totalOut, activeModel, totalThinking, totalCacheRead, totalCacheCreation);
738
- callbacks.onUsage(totalIn, totalOut, totalThinking, activeModel, turnCostUsd, totalCacheRead, totalCacheCreation);
739
- // Flush telemetry spans to server before session ends
740
- flushCliSpans();
741
- // Fire SessionEnd hook (non-blocking)
742
- if (hooks.length > 0) {
743
- runSessionHook(hooks, "SessionEnd", { session_id: `turn-${sessionStart}` }).catch(() => { });
999
+ compaction_count: compactionCount,
1000
+ provider,
1001
+ model: currentModel,
1002
+ input_tokens: result.usage.inputTokens,
1003
+ trigger_tokens: compactionCfg.triggerTokens
1004
+ }
1005
+ });
744
1006
  }
745
- const finalText = allAssistantText.length > 0 ? allAssistantText[allAssistantText.length - 1] : "";
746
- emitter?.emitDone(finalText, messages);
747
- if (emitter)
748
- clearGlobalEmitter();
749
- callbacks.onDone(messages);
1007
+ }
750
1008
  }
751
- catch (err) {
752
- const errorMsg = abortSignal?.aborted || err?.message === "Cancelled"
753
- ? "Cancelled"
754
- : String(err?.message || err);
755
- logSpan({
756
- action: errorMsg === "Cancelled" ? "chat.cancelled" : "chat.fatal_error",
757
- durationMs: Date.now() - sessionStart,
758
- context: { ...turnCtx, inputTokens: totalIn, outputTokens: totalOut, model: activeModel },
759
- storeId: storeId || undefined,
760
- severity: errorMsg === "Cancelled" ? "info" : "error",
761
- error: errorMsg === "Cancelled" ? undefined : errorMsg,
762
- details: { input_tokens: totalIn, output_tokens: totalOut, session_cost_usd: sessionCostUsd, model: activeModel },
763
- });
764
- // Capture to error_events (not just audit_logs) for non-cancellations
765
- if (errorMsg !== "Cancelled") {
766
- captureError({
767
- error: err instanceof Error ? err : undefined,
768
- errorType: "AgentLoopError",
769
- errorMessage: errorMsg,
770
- severity: "error",
771
- traceId: turnCtx.traceId,
772
- spanId: turnCtx.spanId,
773
- storeId: storeId || undefined,
774
- tags: { model: activeModel, turn: String(turnNum) },
775
- });
1009
+
1010
+ // Telemetry: session summary
1011
+ logSpan({
1012
+ action: "chat.session_complete",
1013
+ durationMs: Date.now() - sessionStart,
1014
+ context: {
1015
+ ...turnCtx,
1016
+ inputTokens: totalIn,
1017
+ outputTokens: totalOut,
1018
+ model: activeModel
1019
+ },
1020
+ storeId: storeId || undefined,
1021
+ details: {
1022
+ input_tokens: totalIn,
1023
+ output_tokens: totalOut,
1024
+ total_tokens: totalIn + totalOut,
1025
+ cache_creation_tokens: totalCacheCreation,
1026
+ cache_read_tokens: totalCacheRead,
1027
+ session_input_tokens: sessionInputTokens,
1028
+ session_output_tokens: sessionOutputTokens,
1029
+ model: activeModel
1030
+ }
1031
+ });
1032
+ const turnCostUsd = estimateCostUsd(totalIn, totalOut, activeModel, totalThinking, totalCacheRead, totalCacheCreation);
1033
+ callbacks.onUsage(totalIn, totalOut, totalThinking, activeModel, turnCostUsd, totalCacheRead, totalCacheCreation);
1034
+
1035
+ // Flush telemetry spans to server before session ends
1036
+ flushCliSpans();
1037
+
1038
+ // Fire SessionEnd hook (non-blocking)
1039
+ if (hooks.length > 0) {
1040
+ runSessionHook(hooks, "SessionEnd", {
1041
+ session_id: `turn-${sessionStart}`
1042
+ }).catch(() => {});
1043
+ }
1044
+ const finalText = allAssistantText.length > 0 ? allAssistantText[allAssistantText.length - 1] : "";
1045
+ emitter?.emitDone(finalText, messages);
1046
+ if (emitter) clearGlobalEmitter();
1047
+ callbacks.onDone(messages);
1048
+ } catch (err) {
1049
+ const errorMsg = abortSignal?.aborted || err?.message === "Cancelled" ? "Cancelled" : String(err?.message || err);
1050
+ logSpan({
1051
+ action: errorMsg === "Cancelled" ? "chat.cancelled" : "chat.fatal_error",
1052
+ durationMs: Date.now() - sessionStart,
1053
+ context: {
1054
+ ...turnCtx,
1055
+ inputTokens: totalIn,
1056
+ outputTokens: totalOut,
1057
+ model: activeModel
1058
+ },
1059
+ storeId: storeId || undefined,
1060
+ severity: errorMsg === "Cancelled" ? "info" : "error",
1061
+ error: errorMsg === "Cancelled" ? undefined : errorMsg,
1062
+ details: {
1063
+ input_tokens: totalIn,
1064
+ output_tokens: totalOut,
1065
+ session_cost_usd: sessionCostUsd,
1066
+ model: activeModel
1067
+ }
1068
+ });
1069
+
1070
+ // Capture to error_events (not just audit_logs) for non-cancellations
1071
+ if (errorMsg !== "Cancelled") {
1072
+ captureError({
1073
+ error: err instanceof Error ? err : undefined,
1074
+ errorType: "AgentLoopError",
1075
+ errorMessage: errorMsg,
1076
+ severity: "error",
1077
+ traceId: turnCtx.traceId,
1078
+ spanId: turnCtx.spanId,
1079
+ storeId: storeId || undefined,
1080
+ tags: {
1081
+ model: activeModel,
1082
+ turn: String(turnNum)
776
1083
  }
777
- // Flush telemetry on error path too
778
- flushCliSpans();
779
- emitter?.emitError(errorMsg);
780
- if (emitter)
781
- clearGlobalEmitter();
782
- callbacks.onError(errorMsg, messages);
1084
+ });
783
1085
  }
1086
+
1087
+ // Flush telemetry on error path too
1088
+ flushCliSpans();
1089
+ emitter?.emitError(errorMsg);
1090
+ if (emitter) clearGlobalEmitter();
1091
+ callbacks.onError(errorMsg, messages);
1092
+ }
784
1093
  }
1094
+
785
1095
  // ============================================================================
786
1096
  // TELEMETRY HELPERS
787
1097
  // ============================================================================
1098
+
788
1099
  export function truncateResult(output, maxLen) {
789
- if (output.length <= maxLen)
790
- return output;
791
- return output.slice(0, maxLen) + `... (${output.length} chars total)`;
1100
+ if (output.length <= maxLen) return output;
1101
+ return output.slice(0, maxLen) + `... (${output.length} chars total)`;
792
1102
  }
793
1103
  export function classifyToolError(output) {
794
- const lower = output.toLowerCase();
795
- if (lower.includes("timed out") || lower.includes("timeout"))
796
- return "timeout";
797
- if (lower.includes("permission denied") || lower.includes("eacces"))
798
- return "permission";
799
- if (lower.includes("not found") || lower.includes("no such file"))
800
- return "not_found";
801
- if (lower.includes("command not found") || lower.includes("exit code 127"))
802
- return "command_not_found";
803
- if (lower.includes("import") && lower.includes("error"))
804
- return "import_error";
805
- if (lower.includes("syntax") || lower.includes("parse"))
806
- return "syntax_error";
807
- if (lower.includes("externally-managed"))
808
- return "env_managed";
809
- return "unknown";
1104
+ const lower = output.toLowerCase();
1105
+ if (lower.includes("timed out") || lower.includes("timeout")) return "timeout";
1106
+ if (lower.includes("permission denied") || lower.includes("eacces")) return "permission";
1107
+ if (lower.includes("not found") || lower.includes("no such file")) return "not_found";
1108
+ if (lower.includes("command not found") || lower.includes("exit code 127")) return "command_not_found";
1109
+ if (lower.includes("import") && lower.includes("error")) return "import_error";
1110
+ if (lower.includes("syntax") || lower.includes("parse")) return "syntax_error";
1111
+ if (lower.includes("externally-managed")) return "env_managed";
1112
+ return "unknown";
810
1113
  }
1114
+
811
1115
  // Convenience: check if user can use the agent (logged in OR has API key)
812
1116
  export function canUseAgent() {
813
- const config = loadConfig();
814
- const hasToken = !!(config.access_token && config.refresh_token);
815
- const hasApiKey = !!(process.env.ANTHROPIC_API_KEY || config.anthropic_api_key);
816
- if (hasToken || hasApiKey)
817
- return { ready: true };
818
- return { ready: false, reason: "Run `whale login` to authenticate." };
1117
+ const config = loadConfig();
1118
+ const hasToken = !!(config.access_token && config.refresh_token);
1119
+ const hasApiKey = !!(process.env.ANTHROPIC_API_KEY || config.anthropic_api_key);
1120
+ if (hasToken || hasApiKey) return {
1121
+ ready: true
1122
+ };
1123
+ return {
1124
+ ready: false,
1125
+ reason: "Run `whale login` to authenticate."
1126
+ };
819
1127
  }
1128
+ //# sourceMappingURL=agent-loop.js.map