@clinebot/core 0.0.35 → 0.0.37

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 (441) hide show
  1. package/README.md +1 -2
  2. package/dist/ClineCore.d.ts +362 -39
  3. package/dist/ClineCore.d.ts.map +1 -1
  4. package/dist/account/cline-account-service.d.ts.map +1 -1
  5. package/dist/account/index.d.ts +1 -1
  6. package/dist/account/index.d.ts.map +1 -1
  7. package/dist/account/rpc.d.ts +6 -6
  8. package/dist/account/rpc.d.ts.map +1 -1
  9. package/dist/cron/cron-event-ingress.d.ts +38 -0
  10. package/dist/cron/cron-event-ingress.d.ts.map +1 -0
  11. package/dist/cron/cron-materializer.d.ts +36 -0
  12. package/dist/cron/cron-materializer.d.ts.map +1 -0
  13. package/dist/cron/cron-reconciler.d.ts +62 -0
  14. package/dist/cron/cron-reconciler.d.ts.map +1 -0
  15. package/dist/cron/cron-report-writer.d.ts +41 -0
  16. package/dist/cron/cron-report-writer.d.ts.map +1 -0
  17. package/dist/cron/cron-runner.d.ts +43 -0
  18. package/dist/cron/cron-runner.d.ts.map +1 -0
  19. package/dist/cron/cron-schema.d.ts +3 -0
  20. package/dist/cron/cron-schema.d.ts.map +1 -0
  21. package/dist/cron/cron-service.d.ts +57 -0
  22. package/dist/cron/cron-service.d.ts.map +1 -0
  23. package/dist/cron/cron-spec-parser.d.ts +27 -0
  24. package/dist/cron/cron-spec-parser.d.ts.map +1 -0
  25. package/dist/cron/cron-watcher.d.ts +23 -0
  26. package/dist/cron/cron-watcher.d.ts.map +1 -0
  27. package/dist/cron/resource-limiter.d.ts +9 -0
  28. package/dist/cron/resource-limiter.d.ts.map +1 -0
  29. package/dist/cron/schedule-command-service.d.ts +10 -0
  30. package/dist/cron/schedule-command-service.d.ts.map +1 -0
  31. package/dist/cron/schedule-service.d.ts +100 -0
  32. package/dist/cron/schedule-service.d.ts.map +1 -0
  33. package/dist/cron/scheduler.d.ts +68 -0
  34. package/dist/cron/scheduler.d.ts.map +1 -0
  35. package/dist/cron/sqlite-cron-store.d.ts +230 -0
  36. package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
  37. package/dist/cron/sqlite-schedule-store.d.ts +52 -0
  38. package/dist/cron/sqlite-schedule-store.d.ts.map +1 -0
  39. package/dist/extensions/config/agent-config-loader.d.ts +4 -3
  40. package/dist/extensions/config/agent-config-loader.d.ts.map +1 -1
  41. package/dist/extensions/config/runtime-commands.d.ts +1 -0
  42. package/dist/extensions/config/runtime-commands.d.ts.map +1 -1
  43. package/dist/extensions/config/user-instruction-config-loader.d.ts +1 -0
  44. package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -1
  45. package/dist/extensions/context/agentic-compaction.d.ts +2 -2
  46. package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
  47. package/dist/extensions/context/compaction-shared.d.ts +5 -4
  48. package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
  49. package/dist/extensions/context/compaction.d.ts.map +1 -1
  50. package/dist/extensions/plugin/plugin-config-loader.d.ts +15 -2
  51. package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
  52. package/dist/extensions/plugin/plugin-loader.d.ts +13 -7
  53. package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
  54. package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -1
  55. package/dist/extensions/plugin/plugin-sandbox.d.ts +21 -2
  56. package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
  57. package/dist/extensions/plugin/plugin-targeting.d.ts +7 -0
  58. package/dist/extensions/plugin/plugin-targeting.d.ts.map +1 -0
  59. package/dist/extensions/plugin-sandbox-bootstrap.js +237 -276
  60. package/dist/extensions/tools/constants.d.ts +1 -0
  61. package/dist/extensions/tools/constants.d.ts.map +1 -1
  62. package/dist/extensions/tools/definitions.d.ts +3 -4
  63. package/dist/extensions/tools/definitions.d.ts.map +1 -1
  64. package/dist/extensions/tools/executors/apply-patch.d.ts +3 -1
  65. package/dist/extensions/tools/executors/apply-patch.d.ts.map +1 -1
  66. package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
  67. package/dist/extensions/tools/executors/search.d.ts +1 -1
  68. package/dist/extensions/tools/executors/search.d.ts.map +1 -1
  69. package/dist/extensions/tools/helpers.d.ts +1 -0
  70. package/dist/extensions/tools/helpers.d.ts.map +1 -1
  71. package/dist/extensions/tools/index.d.ts +3 -2
  72. package/dist/extensions/tools/index.d.ts.map +1 -1
  73. package/dist/extensions/tools/presets.d.ts +27 -44
  74. package/dist/extensions/tools/presets.d.ts.map +1 -1
  75. package/dist/extensions/tools/runtime.d.ts +25 -0
  76. package/dist/extensions/tools/runtime.d.ts.map +1 -0
  77. package/dist/extensions/tools/schemas.d.ts +25 -3
  78. package/dist/extensions/tools/schemas.d.ts.map +1 -1
  79. package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
  80. package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
  81. package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
  82. package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
  83. package/dist/extensions/tools/team/team-tools.d.ts +1 -0
  84. package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
  85. package/dist/extensions/tools/types.d.ts +0 -5
  86. package/dist/extensions/tools/types.d.ts.map +1 -1
  87. package/dist/hooks/hook-bridge.d.ts +118 -0
  88. package/dist/hooks/hook-bridge.d.ts.map +1 -0
  89. package/dist/hooks/hook-file-hooks.d.ts +6 -2
  90. package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
  91. package/dist/hooks/hook-registry.d.ts +16 -0
  92. package/dist/hooks/hook-registry.d.ts.map +1 -0
  93. package/dist/hooks/index.d.ts +0 -1
  94. package/dist/hooks/index.d.ts.map +1 -1
  95. package/dist/hooks/subprocess.d.ts +8 -1
  96. package/dist/hooks/subprocess.d.ts.map +1 -1
  97. package/dist/hub/browser-websocket.d.ts +18 -0
  98. package/dist/hub/browser-websocket.d.ts.map +1 -0
  99. package/dist/hub/client.d.ts +51 -0
  100. package/dist/hub/client.d.ts.map +1 -0
  101. package/dist/hub/connect.d.ts +15 -0
  102. package/dist/hub/connect.d.ts.map +1 -0
  103. package/dist/hub/daemon-entry.d.ts +2 -0
  104. package/dist/hub/daemon-entry.d.ts.map +1 -0
  105. package/dist/hub/daemon-entry.js +1305 -0
  106. package/dist/hub/daemon.d.ts +5 -0
  107. package/dist/hub/daemon.d.ts.map +1 -0
  108. package/dist/hub/defaults.d.ts +17 -0
  109. package/dist/hub/defaults.d.ts.map +1 -0
  110. package/dist/hub/discovery.d.ts +29 -0
  111. package/dist/hub/discovery.d.ts.map +1 -0
  112. package/dist/hub/index.d.ts +15 -0
  113. package/dist/hub/index.d.ts.map +1 -0
  114. package/dist/hub/index.js +1294 -0
  115. package/dist/hub/native-transport.d.ts +17 -0
  116. package/dist/hub/native-transport.d.ts.map +1 -0
  117. package/dist/hub/runtime-handlers.d.ts +11 -0
  118. package/dist/hub/runtime-handlers.d.ts.map +1 -0
  119. package/dist/hub/server.d.ts +104 -0
  120. package/dist/hub/server.d.ts.map +1 -0
  121. package/dist/hub/session-client.d.ts +90 -0
  122. package/dist/hub/session-client.d.ts.map +1 -0
  123. package/dist/hub/start-shared-server.d.ts +19 -0
  124. package/dist/hub/start-shared-server.d.ts.map +1 -0
  125. package/dist/hub/transport.d.ts +8 -0
  126. package/dist/hub/transport.d.ts.map +1 -0
  127. package/dist/hub/ui-client.d.ts +45 -0
  128. package/dist/hub/ui-client.d.ts.map +1 -0
  129. package/dist/hub/workspace.d.ts +4 -0
  130. package/dist/hub/workspace.d.ts.map +1 -0
  131. package/dist/index.d.ts +29 -16
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +782 -471
  134. package/dist/llms/cline-recommended-models.d.ts +20 -0
  135. package/dist/llms/cline-recommended-models.d.ts.map +1 -0
  136. package/dist/llms/configured-provider-registry.d.ts +28 -0
  137. package/dist/llms/configured-provider-registry.d.ts.map +1 -0
  138. package/dist/llms/handler-factory.d.ts +16 -0
  139. package/dist/llms/handler-factory.d.ts.map +1 -0
  140. package/dist/llms/provider-defaults.d.ts +27 -0
  141. package/dist/llms/provider-defaults.d.ts.map +1 -0
  142. package/dist/llms/provider-settings.d.ts +245 -0
  143. package/dist/llms/provider-settings.d.ts.map +1 -0
  144. package/dist/llms/runtime-config.d.ts +4 -0
  145. package/dist/llms/runtime-config.d.ts.map +1 -0
  146. package/dist/llms/runtime-registry.d.ts +20 -0
  147. package/dist/llms/runtime-registry.d.ts.map +1 -0
  148. package/dist/llms/runtime-types.d.ts +85 -0
  149. package/dist/llms/runtime-types.d.ts.map +1 -0
  150. package/dist/runtime/agent-config-adapter.d.ts +148 -0
  151. package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
  152. package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
  153. package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
  154. package/dist/runtime/history.d.ts +6 -0
  155. package/dist/runtime/history.d.ts.map +1 -1
  156. package/dist/runtime/host.d.ts +1 -2
  157. package/dist/runtime/host.d.ts.map +1 -1
  158. package/dist/runtime/loop-detection.d.ts +59 -0
  159. package/dist/runtime/loop-detection.d.ts.map +1 -0
  160. package/dist/runtime/mistake-tracker.d.ts +69 -0
  161. package/dist/runtime/mistake-tracker.d.ts.map +1 -0
  162. package/dist/runtime/rules.d.ts +1 -0
  163. package/dist/runtime/rules.d.ts.map +1 -1
  164. package/dist/runtime/runtime-builder.d.ts.map +1 -1
  165. package/dist/runtime/runtime-event-adapter.d.ts +102 -0
  166. package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
  167. package/dist/runtime/runtime-host.d.ts +49 -26
  168. package/dist/runtime/runtime-host.d.ts.map +1 -1
  169. package/dist/runtime/runtime-oauth-token-manager.d.ts.map +1 -1
  170. package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
  171. package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
  172. package/dist/runtime/session-runtime.d.ts +16 -21
  173. package/dist/runtime/session-runtime.d.ts.map +1 -1
  174. package/dist/runtime/user-input-builder.d.ts +24 -0
  175. package/dist/runtime/user-input-builder.d.ts.map +1 -0
  176. package/dist/services/global-settings.d.ts +12 -0
  177. package/dist/services/global-settings.d.ts.map +1 -0
  178. package/dist/services/index.js +28 -0
  179. package/dist/services/local-runtime-bootstrap.d.ts +9 -3
  180. package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
  181. package/dist/services/plugin-tools.d.ts +16 -0
  182. package/dist/services/plugin-tools.d.ts.map +1 -0
  183. package/dist/services/providers/local-provider-registry.d.ts +199 -23
  184. package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
  185. package/dist/services/providers/local-provider-service.d.ts +15 -13
  186. package/dist/services/providers/local-provider-service.d.ts.map +1 -1
  187. package/dist/services/session-data.d.ts +1 -1
  188. package/dist/services/session-data.d.ts.map +1 -1
  189. package/dist/services/session-telemetry.d.ts +7 -2
  190. package/dist/services/session-telemetry.d.ts.map +1 -1
  191. package/dist/services/storage/file-team-store.d.ts.map +1 -1
  192. package/dist/services/storage/provider-settings-legacy-migration.d.ts +1 -1
  193. package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
  194. package/dist/services/storage/provider-settings-manager.d.ts +1 -0
  195. package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
  196. package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
  197. package/dist/services/workspace-manifest.d.ts +11 -0
  198. package/dist/services/workspace-manifest.d.ts.map +1 -1
  199. package/dist/session/conversation-store.d.ts +30 -0
  200. package/dist/session/conversation-store.d.ts.map +1 -0
  201. package/dist/session/message-builder.d.ts +65 -0
  202. package/dist/session/message-builder.d.ts.map +1 -0
  203. package/dist/session/persistence-service.d.ts +11 -23
  204. package/dist/session/persistence-service.d.ts.map +1 -1
  205. package/dist/session/session-manifest-store.d.ts +22 -0
  206. package/dist/session/session-manifest-store.d.ts.map +1 -0
  207. package/dist/session/session-manifest.d.ts +1 -1
  208. package/dist/session/session-row.d.ts +93 -0
  209. package/dist/session/session-row.d.ts.map +1 -0
  210. package/dist/session/session-service.d.ts +2 -102
  211. package/dist/session/session-service.d.ts.map +1 -1
  212. package/dist/session/subagent-session-manager.d.ts +36 -0
  213. package/dist/session/subagent-session-manager.d.ts.map +1 -0
  214. package/dist/session/team-persistence-store.d.ts +24 -0
  215. package/dist/session/team-persistence-store.d.ts.map +1 -0
  216. package/dist/transports/hub.d.ts +58 -0
  217. package/dist/transports/hub.d.ts.map +1 -0
  218. package/dist/transports/local.d.ts +23 -9
  219. package/dist/transports/local.d.ts.map +1 -1
  220. package/dist/transports/remote.d.ts +10 -0
  221. package/dist/transports/remote.d.ts.map +1 -0
  222. package/dist/transports/runtime-host-support.d.ts +3 -2
  223. package/dist/transports/runtime-host-support.d.ts.map +1 -1
  224. package/dist/types/chat-schema.d.ts +15 -17
  225. package/dist/types/chat-schema.d.ts.map +1 -1
  226. package/dist/types/config.d.ts +17 -7
  227. package/dist/types/config.d.ts.map +1 -1
  228. package/dist/types/events.d.ts +7 -6
  229. package/dist/types/events.d.ts.map +1 -1
  230. package/dist/types/provider-settings.d.ts +4 -5
  231. package/dist/types/provider-settings.d.ts.map +1 -1
  232. package/dist/types/session.d.ts +7 -3
  233. package/dist/types/session.d.ts.map +1 -1
  234. package/dist/types.d.ts +11 -4
  235. package/dist/types.d.ts.map +1 -1
  236. package/package.json +20 -6
  237. package/src/ClineCore.ts +757 -44
  238. package/src/account/cline-account-service.ts +44 -6
  239. package/src/account/index.ts +3 -3
  240. package/src/account/rpc.ts +12 -12
  241. package/src/cron/cron-event-ingress.ts +357 -0
  242. package/src/cron/cron-materializer.ts +97 -0
  243. package/src/cron/cron-reconciler.ts +241 -0
  244. package/src/cron/cron-report-writer.ts +153 -0
  245. package/src/cron/cron-runner.ts +495 -0
  246. package/src/cron/cron-schema.ts +127 -0
  247. package/src/cron/cron-service.ts +163 -0
  248. package/src/cron/cron-spec-parser.ts +489 -0
  249. package/src/cron/cron-watcher.ts +102 -0
  250. package/src/cron/index.ts +15 -0
  251. package/src/cron/resource-limiter.ts +46 -0
  252. package/src/cron/schedule-command-service.ts +193 -0
  253. package/src/cron/schedule-service.ts +703 -0
  254. package/src/cron/scheduler.ts +772 -0
  255. package/src/cron/sqlite-cron-store.ts +1286 -0
  256. package/src/cron/sqlite-schedule-store.ts +708 -0
  257. package/src/extensions/config/agent-config-loader.ts +17 -7
  258. package/src/extensions/config/runtime-commands.ts +6 -0
  259. package/src/extensions/config/user-instruction-config-loader.ts +1 -0
  260. package/src/extensions/context/agentic-compaction.ts +3 -3
  261. package/src/extensions/context/basic-compaction.ts +2 -2
  262. package/src/extensions/context/compaction-shared.ts +5 -4
  263. package/src/extensions/context/compaction.ts +3 -3
  264. package/src/extensions/plugin/plugin-config-loader.ts +37 -2
  265. package/src/extensions/plugin/plugin-loader.ts +69 -9
  266. package/src/extensions/plugin/plugin-module-import.ts +0 -2
  267. package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +243 -39
  268. package/src/extensions/plugin/plugin-sandbox.ts +173 -29
  269. package/src/extensions/plugin/plugin-targeting.ts +32 -0
  270. package/src/extensions/tools/constants.ts +2 -0
  271. package/src/extensions/tools/definitions.ts +61 -71
  272. package/src/extensions/tools/executors/apply-patch.ts +69 -80
  273. package/src/extensions/tools/executors/editor.ts +4 -3
  274. package/src/extensions/tools/executors/search.ts +195 -3
  275. package/src/extensions/tools/helpers.ts +24 -0
  276. package/src/extensions/tools/index.ts +11 -2
  277. package/src/extensions/tools/presets.ts +32 -47
  278. package/src/extensions/tools/runtime.ts +261 -0
  279. package/src/extensions/tools/schemas.ts +17 -20
  280. package/src/extensions/tools/team/delegated-agent.ts +8 -3
  281. package/src/extensions/tools/team/multi-agent.ts +135 -19
  282. package/src/extensions/tools/team/team-tools.ts +172 -91
  283. package/src/extensions/tools/types.ts +0 -6
  284. package/src/hooks/hook-bridge.ts +489 -0
  285. package/src/hooks/hook-file-hooks.ts +66 -5
  286. package/src/hooks/hook-registry.ts +257 -0
  287. package/src/hooks/index.ts +0 -7
  288. package/src/hooks/subprocess-runner.ts +1 -1
  289. package/src/hooks/subprocess.ts +9 -0
  290. package/src/hub/browser-websocket.ts +159 -0
  291. package/src/hub/client.ts +633 -0
  292. package/src/hub/connect.ts +156 -0
  293. package/src/hub/daemon-entry.ts +122 -0
  294. package/src/hub/daemon.ts +284 -0
  295. package/src/hub/defaults.ts +70 -0
  296. package/src/hub/discovery.ts +247 -0
  297. package/src/hub/index.ts +14 -0
  298. package/src/hub/native-transport.ts +31 -0
  299. package/src/hub/runtime-handlers.ts +141 -0
  300. package/src/hub/server.ts +2317 -0
  301. package/src/hub/session-client.ts +502 -0
  302. package/src/hub/start-shared-server.ts +61 -0
  303. package/src/hub/transport.ts +14 -0
  304. package/src/hub/ui-client.ts +126 -0
  305. package/src/hub/workspace.ts +19 -0
  306. package/src/index.ts +169 -68
  307. package/src/llms/cline-recommended-models.ts +167 -0
  308. package/src/llms/configured-provider-registry.ts +193 -0
  309. package/src/llms/handler-factory.ts +56 -0
  310. package/src/llms/provider-defaults.ts +653 -0
  311. package/src/llms/provider-settings.ts +310 -0
  312. package/src/llms/runtime-config.ts +43 -0
  313. package/src/llms/runtime-registry.ts +172 -0
  314. package/src/llms/runtime-types.ts +121 -0
  315. package/src/runtime/agent-config-adapter.ts +636 -0
  316. package/src/runtime/agent-runtime-config-builder.ts +205 -0
  317. package/src/runtime/error-feedback.ts +142 -0
  318. package/src/runtime/history.ts +137 -0
  319. package/src/runtime/host.ts +127 -267
  320. package/src/runtime/index.ts +1 -0
  321. package/src/runtime/loop-detection.ts +162 -0
  322. package/src/runtime/mistake-tracker.ts +221 -0
  323. package/src/runtime/rules.ts +12 -0
  324. package/src/runtime/runtime-builder.ts +85 -13
  325. package/src/runtime/runtime-event-adapter.ts +412 -0
  326. package/src/runtime/runtime-host.ts +134 -62
  327. package/src/runtime/runtime-oauth-token-manager.ts +11 -15
  328. package/src/runtime/session-runtime-orchestrator.ts +1253 -0
  329. package/src/runtime/session-runtime.ts +16 -26
  330. package/src/runtime/user-input-builder.ts +167 -0
  331. package/src/services/global-settings.ts +122 -0
  332. package/src/services/local-runtime-bootstrap.ts +175 -31
  333. package/src/services/plugin-tools.ts +86 -0
  334. package/src/services/providers/local-provider-registry.ts +277 -61
  335. package/src/services/providers/local-provider-service.ts +109 -44
  336. package/src/services/session-data.ts +18 -10
  337. package/src/services/session-telemetry.ts +6 -15
  338. package/src/services/storage/file-team-store.ts +1 -5
  339. package/src/services/storage/provider-settings-legacy-migration.ts +14 -51
  340. package/src/services/storage/provider-settings-manager.ts +17 -2
  341. package/src/services/storage/sqlite-team-store.ts +1 -5
  342. package/src/services/workspace-manifest.ts +18 -0
  343. package/src/session/conversation-store.ts +77 -0
  344. package/src/session/file-session-service.ts +1 -1
  345. package/src/session/index.ts +6 -27
  346. package/src/session/message-builder.ts +941 -0
  347. package/src/session/persistence-service.ts +119 -504
  348. package/src/session/session-manifest-store.ts +158 -0
  349. package/src/session/session-row.ts +199 -0
  350. package/src/session/session-service.ts +17 -376
  351. package/src/session/session-team-coordination.ts +1 -1
  352. package/src/session/subagent-session-manager.ts +397 -0
  353. package/src/session/team-persistence-store.ts +176 -0
  354. package/src/transports/hub.ts +1081 -0
  355. package/src/transports/local.ts +419 -93
  356. package/src/transports/remote.ts +27 -0
  357. package/src/transports/runtime-host-support.ts +63 -9
  358. package/src/types/chat-schema.ts +4 -5
  359. package/src/types/config.ts +17 -7
  360. package/src/types/events.ts +8 -6
  361. package/src/types/index.ts +3 -0
  362. package/src/types/provider-settings.ts +18 -7
  363. package/src/types/session.ts +7 -6
  364. package/src/types.ts +42 -2
  365. package/dist/hooks/persistent.d.ts +0 -64
  366. package/dist/hooks/persistent.d.ts.map +0 -1
  367. package/dist/runtime/rpc-runtime-ensure.d.ts +0 -65
  368. package/dist/runtime/rpc-runtime-ensure.d.ts.map +0 -1
  369. package/dist/runtime/rpc-spawn-lease.d.ts +0 -8
  370. package/dist/runtime/rpc-spawn-lease.d.ts.map +0 -1
  371. package/dist/services/telemetry/index.js +0 -15
  372. package/dist/session/rpc-session-service.d.ts +0 -16
  373. package/dist/session/rpc-session-service.d.ts.map +0 -1
  374. package/dist/session/sqlite-rpc-session-backend.d.ts +0 -31
  375. package/dist/session/sqlite-rpc-session-backend.d.ts.map +0 -1
  376. package/dist/transports/rpc.d.ts +0 -51
  377. package/dist/transports/rpc.d.ts.map +0 -1
  378. package/src/ClineCore.test.ts +0 -226
  379. package/src/account/cline-account-service.test.ts +0 -185
  380. package/src/account/featurebase-token.test.ts +0 -175
  381. package/src/account/rpc.test.ts +0 -63
  382. package/src/auth/bounded-ttl-cache.test.ts +0 -38
  383. package/src/auth/client.test.ts +0 -69
  384. package/src/auth/cline.test.ts +0 -267
  385. package/src/auth/codex.test.ts +0 -170
  386. package/src/auth/oca.test.ts +0 -340
  387. package/src/auth/server.test.ts +0 -287
  388. package/src/auth/utils.test.ts +0 -128
  389. package/src/extensions/config/agent-config-loader.test.ts +0 -236
  390. package/src/extensions/config/hooks-config-loader.test.ts +0 -20
  391. package/src/extensions/config/runtime-commands.test.ts +0 -115
  392. package/src/extensions/config/unified-config-file-watcher.test.ts +0 -196
  393. package/src/extensions/config/user-instruction-config-loader.test.ts +0 -246
  394. package/src/extensions/context/compaction.test.ts +0 -483
  395. package/src/extensions/mcp/config-loader.test.ts +0 -238
  396. package/src/extensions/mcp/manager.test.ts +0 -105
  397. package/src/extensions/plugin/plugin-config-loader.test.ts +0 -184
  398. package/src/extensions/plugin/plugin-loader.test.ts +0 -292
  399. package/src/extensions/plugin/plugin-sandbox.test.ts +0 -423
  400. package/src/extensions/tools/definitions.test.ts +0 -780
  401. package/src/extensions/tools/executors/bash.test.ts +0 -87
  402. package/src/extensions/tools/executors/editor.test.ts +0 -35
  403. package/src/extensions/tools/executors/file-read.test.ts +0 -125
  404. package/src/extensions/tools/model-tool-routing.test.ts +0 -86
  405. package/src/extensions/tools/presets.test.ts +0 -70
  406. package/src/extensions/tools/team/multi-agent.lifecycle.test.ts +0 -455
  407. package/src/extensions/tools/team/spawn-agent-tool.test.ts +0 -381
  408. package/src/extensions/tools/team/team-tools.test.ts +0 -918
  409. package/src/hooks/checkpoint-hooks.test.ts +0 -168
  410. package/src/hooks/hook-file-hooks.test.ts +0 -311
  411. package/src/hooks/persistent.ts +0 -661
  412. package/src/runtime/history.test.ts +0 -114
  413. package/src/runtime/host.test.ts +0 -230
  414. package/src/runtime/rpc-runtime-ensure.test.ts +0 -123
  415. package/src/runtime/rpc-runtime-ensure.ts +0 -659
  416. package/src/runtime/rpc-spawn-lease.test.ts +0 -81
  417. package/src/runtime/rpc-spawn-lease.ts +0 -156
  418. package/src/runtime/runtime-builder.team-persistence.test.ts +0 -245
  419. package/src/runtime/runtime-builder.test.ts +0 -615
  420. package/src/runtime/runtime-oauth-token-manager.test.ts +0 -137
  421. package/src/runtime/runtime-parity.test.ts +0 -143
  422. package/src/services/providers/local-provider-service.test.ts +0 -1062
  423. package/src/services/session-data.test.ts +0 -160
  424. package/src/services/storage/provider-settings-legacy-migration.test.ts +0 -424
  425. package/src/services/storage/provider-settings-manager.test.ts +0 -191
  426. package/src/services/telemetry/OpenTelemetryAdapter.test.ts +0 -157
  427. package/src/services/telemetry/OpenTelemetryProvider.test.ts +0 -326
  428. package/src/services/telemetry/TelemetryLoggerSink.test.ts +0 -42
  429. package/src/services/telemetry/TelemetryService.test.ts +0 -134
  430. package/src/services/telemetry/distinct-id.test.ts +0 -57
  431. package/src/services/workspace/file-indexer.d.ts +0 -11
  432. package/src/services/workspace/file-indexer.test.ts +0 -156
  433. package/src/services/workspace/mention-enricher.test.ts +0 -106
  434. package/src/session/persistence-service.test.ts +0 -300
  435. package/src/session/rpc-session-service.ts +0 -114
  436. package/src/session/session-service.team-persistence.test.ts +0 -48
  437. package/src/session/sqlite-rpc-session-backend.ts +0 -301
  438. package/src/transports/local.e2e.test.ts +0 -380
  439. package/src/transports/local.test.ts +0 -2559
  440. package/src/transports/rpc.test.ts +0 -82
  441. package/src/transports/rpc.ts +0 -665
@@ -0,0 +1,1253 @@
1
+ /**
2
+ * Per-session `SessionRuntime` orchestrator (PLAN.md §3.2.3).
3
+ *
4
+ * Owns all cross-turn state for one logical agent session:
5
+ *
6
+ * - `ConversationStore` — message transcript + session-started gate
7
+ * - `MistakeTracker` — per-session consecutive-mistake counter
8
+ * - `LoopDetectionTracker` — per-session repeated-tool-call detector
9
+ * - `MessageBuilder` — provider-message assembly cache
10
+ * - `HookEngine` + `HookBridge` — legacy `AgentHooks`/`AgentExtension[]`
11
+ * → runtime-hook synthesis
12
+ * - `RuntimeEventAdapter` — per-run stateful `AgentRuntimeEvent`
13
+ * → legacy `AgentEvent` translator
14
+ * - listener registry — host subscribers see legacy `AgentEvent`s
15
+ * - pending tool set, abort — per-run lifecycle housekeeping
16
+ *
17
+ * A fresh `AgentRuntime` is instantiated per run via
18
+ * `createAgentRuntime(createAgentRuntimeConfig({...}))`; it is the
19
+ * only class that depends on `@clinebot/agents`. All session-level
20
+ * state outlives any one `AgentRuntime`, making OAuth-retry + run
21
+ * replay feasible (§3.2.2 invariant #3).
22
+ *
23
+ * NOTE: this class lives alongside the pre-existing
24
+ * `packages/core/src/runtime/session-runtime.ts`, which exports
25
+ * transport-level `SessionRuntime`/`RuntimeBuilder` interfaces
26
+ * consumed by `LocalRuntimeHost`. Both coexist through Step 8/9; the
27
+ * transport-level interface is retired in Step 10 (§3.6 Step 10).
28
+ *
29
+ * @see PLAN.md §3.2.3 — public surface.
30
+ * @see PLAN.md §3.2.2 — call graph and invariants.
31
+ * @see PLAN.md §3.6 Step 8d — landing commit.
32
+ */
33
+
34
+ import type { AgentRuntime } from "@clinebot/agents";
35
+ import { createAgentRuntime } from "@clinebot/agents";
36
+ import {
37
+ type AgentConfig,
38
+ type AgentEvent,
39
+ type AgentExtension,
40
+ type AgentExtensionRegistry,
41
+ type AgentFinishReason,
42
+ type AgentResult,
43
+ type AgentRunResult,
44
+ type AgentRuntimeEvent,
45
+ type BasicLogger,
46
+ type ContributionRegistry,
47
+ createContributionRegistry,
48
+ HookEngine,
49
+ type ITelemetryService,
50
+ type LegacyAgentUsage,
51
+ type LoopDetectionConfig,
52
+ type Message,
53
+ type MessageWithMetadata,
54
+ type ModelInfo,
55
+ type Tool,
56
+ type ToolCallRecord,
57
+ } from "@clinebot/shared";
58
+ import { HookBridge } from "../hooks/hook-bridge";
59
+ import {
60
+ createHandlerFromConfig,
61
+ resolveKnownModelsFromConfig,
62
+ } from "../llms/handler-factory";
63
+ import { ConversationStore } from "../session/conversation-store";
64
+ import { MessageBuilder } from "../session/message-builder";
65
+ import {
66
+ agentMessagesToMessagesWithMetadata,
67
+ apiHandlerToAgentModel,
68
+ messagesToAgentMessages,
69
+ toolsToAgentTools,
70
+ } from "./agent-config-adapter";
71
+ import { createAgentRuntimeConfig } from "./agent-runtime-config-builder";
72
+ import { LoopDetectionTracker } from "./loop-detection";
73
+ import { MistakeTracker } from "./mistake-tracker";
74
+ import { RuntimeEventAdapter } from "./runtime-event-adapter";
75
+
76
+ function formatToolResultError(output: unknown): string {
77
+ if (typeof output === "string") {
78
+ return output;
79
+ }
80
+ if (output instanceof Error) {
81
+ return output.message;
82
+ }
83
+ try {
84
+ return JSON.stringify(output);
85
+ } catch {
86
+ return String(output);
87
+ }
88
+ }
89
+
90
+ // =============================================================================
91
+ // Public types
92
+ // =============================================================================
93
+
94
+ /**
95
+ * Listener invoked for every legacy `AgentEvent` produced by the
96
+ * session runtime. Use `subscribeEvents(listener)` — it returns an
97
+ * `unsubscribe` function.
98
+ */
99
+ export type SessionEventListener = (event: AgentEvent) => void;
100
+
101
+ /** Subset of host-side deps needed by the session orchestrator. */
102
+ export interface SessionRuntimeOrchestratorDeps {
103
+ readonly logger?: BasicLogger;
104
+ readonly telemetry?: ITelemetryService;
105
+ /** Optional pre-constructed `HookEngine`. A fresh one is built when omitted. */
106
+ readonly hookEngine?: HookEngine;
107
+ /**
108
+ * Test hook: override the `AgentRuntime` factory. Production
109
+ * callers leave this undefined and get the real `createAgentRuntime`.
110
+ */
111
+ readonly createAgentRuntimeImpl?: (
112
+ config: Parameters<typeof createAgentRuntime>[0],
113
+ ) => AgentRuntime;
114
+ }
115
+
116
+ /** Connection overrides applied via `updateConnection`. */
117
+ export interface ConnectionOverrides {
118
+ providerId?: string;
119
+ modelId?: string;
120
+ apiKey?: string;
121
+ baseUrl?: string;
122
+ headers?: Record<string, string>;
123
+ providerConfig?: unknown;
124
+ reasoningEffort?: AgentConfig["reasoningEffort"];
125
+ thinking?: boolean;
126
+ thinkingBudgetTokens?: number;
127
+ }
128
+
129
+ // =============================================================================
130
+ // SessionRuntime orchestrator
131
+ // =============================================================================
132
+
133
+ /**
134
+ * Per-session orchestrator. Construct once per agent session; call
135
+ * `run` / `continue` repeatedly. The class matches the subset of
136
+ * legacy `Agent` surface that `legacy-agent-facade` needs in Step 8e.
137
+ *
138
+ * Named `SessionRuntime` to match PLAN.md §3.2.3. To avoid a symbol
139
+ * collision with the pre-existing transport-level `SessionRuntime`
140
+ * interface in `session-runtime.ts`, this file is **not** re-exported
141
+ * from `runtime/index.ts` or `src/index.ts`. Consumers (facade in
142
+ * Step 8e) import directly from this module.
143
+ */
144
+ export class SessionRuntime {
145
+ private config: AgentConfig;
146
+ private readonly agentId: string;
147
+ private readonly parentAgentId?: string;
148
+ private readonly logger?: BasicLogger;
149
+ // Reserved for §3.4.4 telemetry parity (not yet consumed — §3.4.4
150
+ // listed as explicitly deferred until telemetry wiring is added).
151
+ // Typed as `readonly` to preserve the field slot for future use
152
+ // without re-touching the constructor.
153
+ readonly telemetry?: ITelemetryService;
154
+ private readonly hookEngine: HookEngine;
155
+ private readonly hookBridge: HookBridge;
156
+ private readonly conversation: ConversationStore;
157
+ private readonly mistakeTracker: MistakeTracker;
158
+ private readonly loopTracker: LoopDetectionTracker;
159
+ /**
160
+ * True when `execution.loopDetection === false` at construction
161
+ * time. Loop inspection is skipped entirely — the tracker still
162
+ * exists for API compatibility but is never fed.
163
+ */
164
+ private readonly loopDetectionDisabled: boolean;
165
+ // Reserved for host-owned message assembly (currently handled
166
+ // inline by `agentMessagesToMessages` in the adapter). Kept as a
167
+ // ready slot so the compaction plugin can install a beforeModel
168
+ // hook that feeds an explicit `MessageBuilder` later.
169
+ readonly messageBuilder: MessageBuilder;
170
+ /**
171
+ * Contribution registry that hosts extension-provided tools,
172
+ * commands, message builders, and providers. Lazily initialized
173
+ * on first run (parity with legacy `Agent.ensureExtensionsInitialized`
174
+ * at `packages/agents/src/agent.ts:1122-1147`).
175
+ */
176
+ private readonly contributionRegistry: ContributionRegistry<
177
+ AgentExtension,
178
+ Tool,
179
+ Message[]
180
+ >;
181
+ private extensionsInitialized = false;
182
+ private readonly listeners = new Set<SessionEventListener>();
183
+ private readonly createAgentRuntimeImpl: (
184
+ config: Parameters<typeof createAgentRuntime>[0],
185
+ ) => AgentRuntime;
186
+
187
+ /** Stable run id shared with the HookBridge during an active run. */
188
+ private activeRunId: string | null = null;
189
+ /** True while a run is in flight. `canStartRun()` is the negation. */
190
+ private running = false;
191
+ /** True once `abort()` has been requested for the active run. */
192
+ private abortRequested = false;
193
+ /** Last abort reason requested for the active run. */
194
+ private abortReason: string | undefined;
195
+ /** Reference to the current run's `AgentRuntime` so `abort` can forward. */
196
+ private activeRuntime: AgentRuntime | null = null;
197
+ /** Promise for the current run so shutdown can await an aborted run's drain. */
198
+ private activeRunPromise: Promise<AgentResult> | null = null;
199
+ /** Per-run `Agent → AgentEvent` adapter; `reset()` each run. */
200
+ private readonly eventAdapter = new RuntimeEventAdapter();
201
+ /** Session-shutdown gate — rejects late runs. */
202
+ private shutdownCalled = false;
203
+ /** Running tally of tool-call records for `AgentResult.toolCalls`. */
204
+ private currentRunToolCalls: ToolCallRecord[] = [];
205
+ /** Aggregated usage across the current run. */
206
+ private currentRunUsage: LegacyAgentUsage = {
207
+ inputTokens: 0,
208
+ outputTokens: 0,
209
+ };
210
+ /** Tool-start timestamps for `ToolCallRecord.durationMs`. */
211
+ private toolStartedAt = new Map<string, Date>();
212
+ /** Tool-call input snapshot for `ToolCallRecord.input`. */
213
+ private toolInputs = new Map<string, unknown>();
214
+ /**
215
+ * Per-turn tool outcome counters used by the MistakeTracker wiring.
216
+ * Reset on every `turn-started` event; consumed on `turn-finished`
217
+ * to feed `mistakeTracker.record` when every tool call erred and no
218
+ * successful call landed. Matches legacy `agent.ts` tool-failure
219
+ * mistake-feed path (§3.4.6 + pre-Step-9 oracle lines 972-997).
220
+ */
221
+ private currentTurnSuccessfulTools = 0;
222
+ private currentTurnFailedTools = 0;
223
+ private currentTurnFailureDetails: string[] = [];
224
+ /**
225
+ * Serial queue for `MistakeTracker.record(...)` + loop-detection
226
+ * side-effects fired from the sync `handleRuntimeEvent` stream. The
227
+ * tracker's `record()` is async but the runtime event stream is
228
+ * synchronous, so we chain tracker work onto a promise and await it
229
+ * in `executeRun` before returning the `AgentResult`.
230
+ */
231
+ private activeTrackerWork: Promise<void> = Promise.resolve();
232
+ /** True when tracker logic has issued an abort for the active run. */
233
+ private trackerAbortInFlight = false;
234
+
235
+ constructor(config: AgentConfig, deps: SessionRuntimeOrchestratorDeps = {}) {
236
+ this.config = config;
237
+ this.agentId = `agent_${Date.now()}_${Math.random()
238
+ .toString(36)
239
+ .slice(2, 8)}`;
240
+ this.parentAgentId = config.parentAgentId;
241
+ this.logger = deps.logger ?? config.logger;
242
+ this.telemetry = deps.telemetry ?? config.telemetry;
243
+ this.hookEngine = deps.hookEngine ?? new HookEngine();
244
+ this.createAgentRuntimeImpl =
245
+ deps.createAgentRuntimeImpl ?? createAgentRuntime;
246
+
247
+ this.conversation = new ConversationStore(config.initialMessages);
248
+ this.messageBuilder = new MessageBuilder();
249
+ this.contributionRegistry = createContributionRegistry<
250
+ AgentExtension,
251
+ Tool,
252
+ Message[]
253
+ >({
254
+ extensions: config.extensions ? [...config.extensions] : [],
255
+ setupContext: {
256
+ session: config.extensionContext?.session,
257
+ client: config.extensionContext?.client,
258
+ user: config.extensionContext?.user,
259
+ workspaceInfo: config.extensionContext?.workspace,
260
+ automation: config.extensionContext?.automation,
261
+ logger: config.extensionContext?.logger ?? this.logger,
262
+ telemetry: config.extensionContext?.telemetry ?? this.telemetry,
263
+ },
264
+ });
265
+ // Resolve + validate eagerly so `getExtensionRegistry()` is
266
+ // callable before the first run (legacy parity with
267
+ // `Agent` constructor at packages/agents/src/agent.ts:158-159).
268
+ // `setup()` is deferred to `ensureExtensionsInitialized` on
269
+ // the first run so async extension setup can't block the
270
+ // constructor.
271
+ this.contributionRegistry.resolve();
272
+ this.contributionRegistry.validate();
273
+ this.hookBridge = new HookBridge({
274
+ agentId: this.agentId,
275
+ conversationId: this.conversation.getConversationId(),
276
+ parentAgentId: this.parentAgentId ?? null,
277
+ hookEngine: this.hookEngine,
278
+ hooks: config.hooks,
279
+ extensions: config.extensions,
280
+ getRunId: () => this.activeRunId ?? "",
281
+ onHookContext: (_source, context) => {
282
+ // Legacy behaviour: hook-returned context becomes a user
283
+ // message on the next turn. Stash it on the conversation
284
+ // store so subsequent turns pick it up.
285
+ this.conversation.appendMessage({
286
+ role: "user",
287
+ content: [{ type: "text", text: context }],
288
+ });
289
+ },
290
+ onDispatchError: (error) => {
291
+ this.logger?.error?.("SessionRuntime hook dispatch failed", {
292
+ agentId: this.agentId,
293
+ error,
294
+ });
295
+ },
296
+ });
297
+
298
+ const maxMistakes = config.execution?.maxConsecutiveMistakes ?? 6;
299
+ this.mistakeTracker = new MistakeTracker({
300
+ maxConsecutiveMistakes: maxMistakes,
301
+ onLimitReached: config.onConsecutiveMistakeLimitReached,
302
+ emit: (event) => this.emitLegacyEvent(event),
303
+ log: (level, message, metadata) =>
304
+ leveledLog(this.logger, level, message, metadata),
305
+ agentId: this.agentId,
306
+ getConversationId: () => this.conversation.getConversationId(),
307
+ getActiveRunId: () => this.activeRunId ?? "",
308
+ appendRecoveryNotice: (message, _reason) => {
309
+ this.conversation.appendMessage({
310
+ role: "user",
311
+ content: [{ type: "text", text: message }],
312
+ });
313
+ },
314
+ });
315
+ const loopDetectionInput = config.execution?.loopDetection;
316
+ this.loopDetectionDisabled = loopDetectionInput === false;
317
+ const loopConfig: Partial<LoopDetectionConfig> | undefined =
318
+ loopDetectionInput === false || loopDetectionInput === undefined
319
+ ? undefined
320
+ : loopDetectionInput;
321
+ this.loopTracker = new LoopDetectionTracker(loopConfig);
322
+ }
323
+
324
+ // -------------------------------------------------------------------
325
+ // Accessors & state mutators
326
+ // -------------------------------------------------------------------
327
+
328
+ getAgentId(): string {
329
+ return this.agentId;
330
+ }
331
+
332
+ getConversationId(): string {
333
+ return this.conversation.getConversationId();
334
+ }
335
+
336
+ getMessages(): MessageWithMetadata[] {
337
+ return this.conversation.getMessages();
338
+ }
339
+
340
+ /** True when no run is currently active and the session is not shut down. */
341
+ canStartRun(): boolean {
342
+ return !this.running && !this.shutdownCalled;
343
+ }
344
+
345
+ /**
346
+ * Snapshot of the contribution registry (tools, commands, and other
347
+ * extension contributions).
348
+ *
349
+ * Before the first run, the registry is in the `validate` phase:
350
+ * extensions are validated but their `setup()` callbacks have not
351
+ * run yet, so the snapshot only reflects eagerly-declared
352
+ * contributions. After the first `run()`/`continue()`, the
353
+ * registry is initialized (§`ensureExtensionsInitialized`), and
354
+ * the snapshot reflects everything extensions registered via
355
+ * `api.registerTool` / `registerCommand` / `registerMessageBuilder`
356
+ * / `registerProvider` / `registerAutomationEventType`.
357
+ */
358
+ getExtensionRegistry(): AgentExtensionRegistry<Tool, Message[]> {
359
+ return this.contributionRegistry.getRegistrySnapshot();
360
+ }
361
+
362
+ /** Append additional tools to every subsequent turn's runtime config. */
363
+ addTools(tools: Tool[]): void {
364
+ if (tools.length === 0) {
365
+ return;
366
+ }
367
+ const existing = new Set(this.config.tools.map((tool) => tool.name));
368
+ const merged = [...this.config.tools];
369
+ for (const tool of tools) {
370
+ if (!existing.has(tool.name)) {
371
+ merged.push(tool);
372
+ existing.add(tool.name);
373
+ }
374
+ }
375
+ this.config = { ...this.config, tools: merged };
376
+ }
377
+
378
+ /** Mutate provider / reasoning fields for subsequent runs. */
379
+ updateConnection(overrides: ConnectionOverrides): void {
380
+ const next: AgentConfig = { ...this.config };
381
+ if (overrides.providerId !== undefined)
382
+ next.providerId = overrides.providerId;
383
+ if (overrides.modelId !== undefined) next.modelId = overrides.modelId;
384
+ if (overrides.apiKey !== undefined) next.apiKey = overrides.apiKey;
385
+ if (overrides.baseUrl !== undefined) next.baseUrl = overrides.baseUrl;
386
+ if (overrides.headers !== undefined) next.headers = overrides.headers;
387
+ if (overrides.providerConfig !== undefined)
388
+ next.providerConfig = overrides.providerConfig;
389
+ if (overrides.reasoningEffort !== undefined)
390
+ next.reasoningEffort = overrides.reasoningEffort;
391
+ if (overrides.thinking !== undefined) next.thinking = overrides.thinking;
392
+ if (overrides.thinkingBudgetTokens !== undefined)
393
+ next.thinkingBudgetTokens = overrides.thinkingBudgetTokens;
394
+ this.config = next;
395
+ }
396
+
397
+ clearHistory(): void {
398
+ this.conversation.clearHistory();
399
+ this.resetConversationBoundaryTrackers();
400
+ }
401
+
402
+ restore(messages: readonly MessageWithMetadata[]): void {
403
+ this.conversation.restore(messages);
404
+ this.resetConversationBoundaryTrackers();
405
+ }
406
+
407
+ private resetConversationBoundaryTrackers(): void {
408
+ this.mistakeTracker.reset();
409
+ this.loopTracker.reset();
410
+ }
411
+
412
+ // -------------------------------------------------------------------
413
+ // Event subscription (legacy shape)
414
+ // -------------------------------------------------------------------
415
+
416
+ /**
417
+ * Subscribe to **legacy** `AgentEvent`s. The session runtime
418
+ * translates the new `AgentRuntimeEvent` stream via
419
+ * `RuntimeEventAdapter` before fanout, so consumers see the
420
+ * pre-swap shape.
421
+ */
422
+ subscribeEvents(listener: SessionEventListener): () => void {
423
+ this.listeners.add(listener);
424
+ return () => {
425
+ this.listeners.delete(listener);
426
+ };
427
+ }
428
+
429
+ // -------------------------------------------------------------------
430
+ // Abort / shutdown
431
+ // -------------------------------------------------------------------
432
+
433
+ abort(reason?: unknown): void {
434
+ const message =
435
+ typeof reason === "string"
436
+ ? reason
437
+ : reason instanceof Error
438
+ ? reason.message
439
+ : reason === undefined
440
+ ? undefined
441
+ : String(reason);
442
+ this.abortRequested = true;
443
+ this.abortReason = message;
444
+ if (this.activeRunPromise) {
445
+ /**
446
+ * Why this exists in hub mode:
447
+ *
448
+ * The TUI and the runtime are not always in the same process. In hub
449
+ * mode, the visible TUI talks to a shared daemon over websocket. When the
450
+ * user sends a prompt, the TUI sends a "start this run" command to the
451
+ * daemon. The daemon starts the AgentRuntime and stores the promise for
452
+ * that run as `activeRunPromise`.
453
+ *
454
+ * If the user presses Escape, the TUI sends a separate "cancel the
455
+ * current run" command to the daemon. Cancelling a run means aborting the
456
+ * AgentRuntime. That is supposed to interrupt the provider stream or any
457
+ * other in-flight async work, and the normal way that interruption shows
458
+ * up in JavaScript is a rejected promise. That rejection is not a bug by
459
+ * itself. It is the expected result of the user saying "stop this
460
+ * request."
461
+ *
462
+ * The important detail is that the rejection is already handled by the
463
+ * code path that started the run. The original "start this run" command
464
+ * is still awaiting `sessionHost.send(...)`, and that await is what
465
+ * should eventually turn the run result or run error into a reply/event
466
+ * for the client.
467
+ *
468
+ * The problem we hit was a timing gap inside the daemon process. The
469
+ * separate cancel command can call `activeRuntime.abort(message)` while
470
+ * the original start command is still waiting elsewhere. The abort can
471
+ * make `activeRunPromise` reject immediately. If the runtime reports that
472
+ * rejection before the original start command observes it, Node/Bun can
473
+ * briefly classify it as an `unhandledRejection`.
474
+ *
475
+ * In the hub daemon, `unhandledRejection` is fatal. That is normally the
476
+ * right policy because real unhandled errors should not be ignored. But
477
+ * for this cancellation path it meant Escape could kill the daemon even
478
+ * though the run error was expected and the original start command was
479
+ * still responsible for handling it. After the daemon died, the next
480
+ * prompt looked like it started loading, then silently stalled because
481
+ * the TUI was talking to a dead runtime process.
482
+ *
483
+ * This `.catch()` is not the real application-level error handling. It is
484
+ * only a local safety observer attached before we trigger the abort, so
485
+ * the daemon does not mistake an expected cancellation rejection for a
486
+ * process crash. We do not replace `activeRunPromise`, await this catch,
487
+ * or convert the rejection into success. The original start command, and
488
+ * any other caller awaiting `run()` / `continue()`, still receives the
489
+ * same result or error it would have received without this observer.
490
+ */
491
+ void this.activeRunPromise.catch(() => {});
492
+ }
493
+ this.activeRuntime?.abort(message);
494
+ }
495
+
496
+ /**
497
+ * Shut the session down. Fires `hook.session_shutdown` exactly once
498
+ * (legacy parity with `Agent.shutdown` at pre-Step-9 `agent.ts:325`)
499
+ * and drains any in-flight async hooks through the hook engine.
500
+ *
501
+ * @param reason Optional caller-supplied reason string (e.g.
502
+ * `"session_complete"`, `"session_error"`,
503
+ * `"session_stop"`, `"ctrl_d"`). Propagated into
504
+ * the `session_shutdown` hook payload as
505
+ * `AgentHookSessionShutdownContext.reason` so hook
506
+ * handlers (e.g. hook-file runners) can route on
507
+ * it — this matches the legacy `Agent.shutdown(reason)`
508
+ * contract and keeps `isAbortReason(ctx.reason)`
509
+ * checks in `hook-file-hooks.ts` working.
510
+ * @param timeoutMs Optional drain timeout forwarded to
511
+ * `HookEngine.shutdown`. Defaults to the engine's
512
+ * own default (3000 ms) when omitted.
513
+ */
514
+ async shutdown(reason?: string, timeoutMs?: number): Promise<void> {
515
+ if (this.running) {
516
+ if (!this.abortRequested || !this.activeRunPromise) {
517
+ throw new Error(
518
+ `SessionRuntime.shutdown called while a run is in progress (agentId=${this.agentId})`,
519
+ );
520
+ }
521
+ await this.activeRunPromise;
522
+ }
523
+ if (this.shutdownCalled) {
524
+ return;
525
+ }
526
+ this.shutdownCalled = true;
527
+ await this.hookBridge.dispatch("hook.session_shutdown", {
528
+ stage: "session_shutdown",
529
+ payload: {
530
+ agentId: this.agentId,
531
+ conversationId: this.conversation.getConversationId(),
532
+ sessionId: this.config.sessionId,
533
+ parentAgentId: this.parentAgentId ?? null,
534
+ reason,
535
+ },
536
+ });
537
+ await this.hookBridge.shutdown(timeoutMs);
538
+ }
539
+
540
+ // -------------------------------------------------------------------
541
+ // Run / continue
542
+ // -------------------------------------------------------------------
543
+
544
+ async run(
545
+ userMessage: string,
546
+ userImages?: string[],
547
+ userFiles?: string[],
548
+ ): Promise<AgentResult> {
549
+ this.conversation.resetForRun();
550
+ this.resetConversationBoundaryTrackers();
551
+ return this.executeRun({
552
+ userMessage,
553
+ userImages,
554
+ userFiles,
555
+ isContinue: false,
556
+ });
557
+ }
558
+
559
+ async continue(
560
+ userMessage?: string,
561
+ userImages?: string[],
562
+ userFiles?: string[],
563
+ ): Promise<AgentResult> {
564
+ return this.executeRun({
565
+ userMessage,
566
+ userImages,
567
+ userFiles,
568
+ isContinue: true,
569
+ });
570
+ }
571
+
572
+ // -------------------------------------------------------------------
573
+ // Private implementation
574
+ // -------------------------------------------------------------------
575
+
576
+ private async executeRun(input: {
577
+ userMessage?: string;
578
+ userImages?: string[];
579
+ userFiles?: string[];
580
+ isContinue: boolean;
581
+ }): Promise<AgentResult> {
582
+ const runPromise = this.executeRunInternal(input);
583
+ this.activeRunPromise = runPromise;
584
+ try {
585
+ return await runPromise;
586
+ } finally {
587
+ if (this.activeRunPromise === runPromise) {
588
+ this.activeRunPromise = null;
589
+ }
590
+ }
591
+ }
592
+
593
+ private async executeRunInternal(input: {
594
+ userMessage?: string;
595
+ userImages?: string[];
596
+ userFiles?: string[];
597
+ isContinue: boolean;
598
+ }): Promise<AgentResult> {
599
+ if (this.shutdownCalled) {
600
+ throw new Error(
601
+ `SessionRuntime.run called after shutdown (agentId=${this.agentId})`,
602
+ );
603
+ }
604
+ if (this.running) {
605
+ throw new Error(
606
+ `SessionRuntime state is "running"; call canStartRun() first (agentId=${this.agentId})`,
607
+ );
608
+ }
609
+ this.running = true;
610
+ this.abortRequested = false;
611
+ this.abortReason = undefined;
612
+ this.activeRunId = `run_${Date.now()}_${Math.random()
613
+ .toString(36)
614
+ .slice(2, 8)}`;
615
+ // Lazily initialize contribution-registry extensions on the
616
+ // first run. Legacy parity with `Agent.ensureExtensionsInitialized`
617
+ // (pre-Step-9 `agent.ts:245`/:277), called before any
618
+ // session_start / input / run_start dispatch.
619
+ await this.ensureExtensionsInitialized();
620
+ this.eventAdapter.reset();
621
+ this.currentRunToolCalls = [];
622
+ this.currentRunUsage = { inputTokens: 0, outputTokens: 0 };
623
+ this.toolStartedAt.clear();
624
+ this.toolInputs.clear();
625
+ this.currentTurnSuccessfulTools = 0;
626
+ this.currentTurnFailedTools = 0;
627
+ this.currentTurnFailureDetails = [];
628
+ this.activeTrackerWork = Promise.resolve();
629
+ this.trackerAbortInFlight = false;
630
+
631
+ const startedAt = new Date();
632
+ const mode: "run" | "continue" = input.isContinue ? "continue" : "run";
633
+
634
+ // ------------------------------------------------------------------
635
+ // session_start — dispatched once per logical session, before any
636
+ // run-scoped work. Parity with legacy agent.ts pre-Step-9 (L503).
637
+ // ConversationStore owns the "started" gate and resets it on
638
+ // resetForRun()/clearHistory()/restore() — i.e. a fresh session
639
+ // boundary refires session_start, matching legacy semantics.
640
+ // ------------------------------------------------------------------
641
+ if (!this.conversation.isSessionStarted()) {
642
+ const sessionStartControl = await this.hookBridge.dispatch(
643
+ "hook.session_start",
644
+ {
645
+ stage: "session_start",
646
+ payload: {
647
+ agentId: this.agentId,
648
+ conversationId: this.conversation.getConversationId(),
649
+ parentAgentId: this.parentAgentId ?? null,
650
+ },
651
+ },
652
+ );
653
+ this.conversation.markSessionStarted();
654
+ if (sessionStartControl?.cancel === true) {
655
+ return this.finalizeAbortedRun(startedAt);
656
+ }
657
+ }
658
+
659
+ // ------------------------------------------------------------------
660
+ // hook.input — dispatched once per run/continue when the caller
661
+ // supplied a user message. Honors control.overrideInput (string)
662
+ // and control.cancel. Parity with legacy agent.ts pre-Step-9
663
+ // (L1154 `prepareUserInput`).
664
+ // ------------------------------------------------------------------
665
+ let effectiveUserMessage = input.userMessage;
666
+ if (effectiveUserMessage !== undefined) {
667
+ const inputControl = await this.hookBridge.dispatch("hook.input", {
668
+ stage: "input",
669
+ payload: {
670
+ agentId: this.agentId,
671
+ conversationId: this.conversation.getConversationId(),
672
+ parentAgentId: this.parentAgentId ?? null,
673
+ mode,
674
+ input: effectiveUserMessage,
675
+ },
676
+ });
677
+ if (inputControl?.cancel === true) {
678
+ return this.finalizeAbortedRun(startedAt);
679
+ }
680
+ if (typeof inputControl?.overrideInput === "string") {
681
+ effectiveUserMessage = inputControl.overrideInput;
682
+ }
683
+ }
684
+
685
+ // Append the user turn (if any) to the conversation store. This
686
+ // must happen BEFORE we snapshot `initialMessages` below so the
687
+ // runtime sees the user message as part of its seed — we then
688
+ // pass an empty input to `runtime.run()` so the runtime does not
689
+ // append the message a second time (AgentRuntime.execute treats
690
+ // a falsy input as "no additional messages", per
691
+ // packages/agents/src/agent-runtime.ts normalizeInput path).
692
+ if (effectiveUserMessage !== undefined) {
693
+ const content = await buildUserTurnContent(
694
+ effectiveUserMessage,
695
+ input.userImages,
696
+ input.userFiles,
697
+ this.config.userFileContentLoader,
698
+ );
699
+ this.conversation.appendMessage({ role: "user", content });
700
+ }
701
+
702
+ // Build the AgentRuntime for this turn.
703
+ const handler = createHandlerFromConfig(this.config, this.logger);
704
+ const agentModel = apiHandlerToAgentModel(handler, {
705
+ prepareMessages: (messages) => this.prepareMessagesForApi(messages),
706
+ });
707
+ // Merge extension-contributed tools with the config-declared
708
+ // tools for this turn. Extensions register tools via
709
+ // `api.registerTool` during `setup()` — parity with legacy
710
+ // `Agent.ensureExtensionsInitialized` at pre-Step-9 `agent.ts:1140-1146`
711
+ // which merged `this.contributionRegistry.getRegisteredTools()`
712
+ // into `this.config.tools`. Dedupe by name so a config tool
713
+ // wins over a same-named extension tool (legacy behaviour:
714
+ // `validateTools` rejects duplicates; here we prefer the
715
+ // explicitly-declared config tool).
716
+ const extensionTools = this.contributionRegistry.getRegisteredTools();
717
+ const mergedToolsByName = new Map<string, Tool>();
718
+ for (const tool of extensionTools) {
719
+ mergedToolsByName.set(tool.name, tool);
720
+ }
721
+ for (const tool of this.config.tools) {
722
+ mergedToolsByName.set(tool.name, tool);
723
+ }
724
+ const conversationId = this.conversation.getConversationId();
725
+ const modelInfo = tryGetModelInfo(this.config);
726
+ const adaptedTools = toolsToAgentTools(
727
+ Array.from(mergedToolsByName.values()),
728
+ {
729
+ conversationId,
730
+ metadata: {
731
+ modelSupportsImages:
732
+ modelInfo?.capabilities?.includes("images") ?? true,
733
+ ...this.config.toolContextMetadata,
734
+ },
735
+ },
736
+ );
737
+ // Seed initialMessages with the full prior transcript (including
738
+ // the user message we just appended) so multi-turn history is
739
+ // preserved across runs. Fixes P1 #1: prior turns were silently
740
+ // lost because `createAgentRuntimeConfig` received no seed and
741
+ // `replaceMessages(runResult.messages)` downstream overwrote the
742
+ // conversation with just the current-turn trail.
743
+ const initialMessages = messagesToAgentMessages(
744
+ this.conversation.getMessages(),
745
+ );
746
+ const runtimeConfig = createAgentRuntimeConfig({
747
+ agentConfig: this.config,
748
+ sessionId: this.config.sessionId,
749
+ agentId: this.agentId,
750
+ conversationId,
751
+ parentAgentId: this.parentAgentId,
752
+ model: agentModel,
753
+ logger: this.logger,
754
+ tools: adaptedTools,
755
+ hookBridge: this.hookBridge,
756
+ initialMessages,
757
+ });
758
+ const runtime = this.createAgentRuntimeImpl(runtimeConfig);
759
+ this.activeRuntime = runtime;
760
+ if (this.abortRequested) {
761
+ runtime.abort(this.abortReason);
762
+ }
763
+
764
+ // Subscribe to runtime events; fan out legacy events to listeners
765
+ // and keep private book-keeping for tool-call records / usage.
766
+ const unsubscribe = runtime.subscribe((event: AgentRuntimeEvent) => {
767
+ this.handleRuntimeEvent(event);
768
+ });
769
+
770
+ let runResult: AgentRunResult | undefined;
771
+ let thrownError: Error | undefined;
772
+ try {
773
+ // Pass empty input so AgentRuntime does not duplicate the
774
+ // user message we already seeded via `initialMessages`. The
775
+ // runtime's `normalizeInput` treats `""`/`undefined` as
776
+ // "no extra messages".
777
+ if (input.isContinue) {
778
+ runResult = await runtime.continue(undefined);
779
+ } else {
780
+ runResult = await runtime.run("");
781
+ }
782
+ } catch (error) {
783
+ thrownError = error instanceof Error ? error : new Error(String(error));
784
+ } finally {
785
+ unsubscribe();
786
+ // Drain any in-flight tracker work (mistake/loop side-effects
787
+ // queued from handleRuntimeEvent) before we clear state so a
788
+ // late abort can still reach the runtime if needed.
789
+ try {
790
+ await this.activeTrackerWork;
791
+ } catch (error) {
792
+ this.logger?.error?.(
793
+ "SessionRuntime tracker work failed during drain",
794
+ { agentId: this.agentId, error },
795
+ );
796
+ }
797
+ this.activeRuntime = null;
798
+ this.running = false;
799
+ this.abortRequested = false;
800
+ this.abortReason = undefined;
801
+ }
802
+
803
+ // Persist the runtime's message trail back into the conversation
804
+ // store so later turns see assistant output. The runtime state
805
+ // was seeded with the full transcript, so `runResult.messages`
806
+ // IS the complete new transcript (seed + newly-produced turn).
807
+ if (runResult && runResult.messages.length > 0) {
808
+ const replacement = agentMessagesToMessagesWithMetadata(
809
+ runResult.messages,
810
+ );
811
+ this.conversation.replaceMessages(replacement);
812
+ }
813
+
814
+ const endedAt = new Date();
815
+ try {
816
+ const result = this.buildLegacyResult({
817
+ runResult,
818
+ thrownError,
819
+ startedAt,
820
+ endedAt,
821
+ });
822
+ await this.dispatchRunEnd(result);
823
+ return result;
824
+ } finally {
825
+ this.activeRunId = null;
826
+ }
827
+ }
828
+
829
+ /**
830
+ * Build an aborted `AgentResult` without invoking the AgentRuntime.
831
+ * Used by the `hook.session_start` / `hook.input` cancel paths —
832
+ * legacy parity: a hook that returns `{ cancel: true }` short-circuits
833
+ * the run and yields `finishReason: "aborted"`.
834
+ */
835
+ private finalizeAbortedRun(startedAt: Date): AgentResult {
836
+ this.activeRuntime = null;
837
+ this.activeRunId = null;
838
+ this.running = false;
839
+ this.abortRequested = false;
840
+ this.abortReason = undefined;
841
+ const endedAt = new Date();
842
+ const messages = this.conversation.getMessages();
843
+ const modelInfo = tryGetModelInfo(this.config);
844
+ return {
845
+ text: "",
846
+ usage: { inputTokens: 0, outputTokens: 0 },
847
+ messages,
848
+ toolCalls: [],
849
+ iterations: 0,
850
+ finishReason: "aborted",
851
+ model: {
852
+ id: this.config.modelId,
853
+ provider: this.config.providerId,
854
+ info: modelInfo,
855
+ },
856
+ startedAt,
857
+ endedAt,
858
+ durationMs: endedAt.getTime() - startedAt.getTime(),
859
+ };
860
+ }
861
+
862
+ /**
863
+ * Initialize the contribution registry once per session. Runs
864
+ * extension `setup()` callbacks so they can `registerTool`,
865
+ * `registerCommand`, `registerMessageBuilder`, and
866
+ * `registerProvider`. Matches legacy `Agent.ensureExtensionsInitialized`
867
+ * at pre-Step-9 `agent.ts:1122-1147`:
868
+ *
869
+ * - on `hookErrorMode === "throw"`, setup failures propagate;
870
+ * - otherwise setup failures emit a recoverable `error` event
871
+ * via the legacy event channel and leave the registry
872
+ * partially initialized.
873
+ *
874
+ * Idempotent: subsequent calls are no-ops once the registry has
875
+ * been activated.
876
+ */
877
+ private async ensureExtensionsInitialized(): Promise<void> {
878
+ if (this.extensionsInitialized) {
879
+ return;
880
+ }
881
+ try {
882
+ await this.contributionRegistry.initialize();
883
+ } catch (error) {
884
+ if (this.config.hookErrorMode === "throw") {
885
+ throw error;
886
+ }
887
+ this.emitLegacyEvent({
888
+ type: "error",
889
+ error: error instanceof Error ? error : new Error(String(error)),
890
+ recoverable: true,
891
+ iteration: 0,
892
+ });
893
+ }
894
+ this.extensionsInitialized = true;
895
+ }
896
+
897
+ private async prepareMessagesForApi(messages: Message[]): Promise<Message[]> {
898
+ let prepared = messages;
899
+ for (const builder of this.contributionRegistry.getRegistrySnapshot()
900
+ .messageBuilder) {
901
+ prepared = await builder.build(prepared);
902
+ }
903
+ return this.messageBuilder.buildForApi(prepared);
904
+ }
905
+
906
+ private handleRuntimeEvent(event: AgentRuntimeEvent): void {
907
+ // Track tool-call records before translation so the timing data
908
+ // is available to observers via `AgentResult.toolCalls`.
909
+ switch (event.type) {
910
+ case "turn-started": {
911
+ // Reset per-turn tool-outcome counters used by the
912
+ // MistakeTracker wiring. Parity with pre-Step-9
913
+ // agent.ts which accumulates per-iteration success/fail
914
+ // counts and feeds them into recordMistake at the
915
+ // turn boundary.
916
+ this.currentTurnSuccessfulTools = 0;
917
+ this.currentTurnFailedTools = 0;
918
+ this.currentTurnFailureDetails = [];
919
+ break;
920
+ }
921
+ case "tool-started": {
922
+ this.toolStartedAt.set(event.toolCall.toolCallId, new Date());
923
+ this.toolInputs.set(event.toolCall.toolCallId, event.toolCall.input);
924
+ // Loop-detection inspection: identical consecutive
925
+ // tool-call signatures trip the tracker. On "soft"
926
+ // verdict we append a recovery notice; on "hard"
927
+ // verdict we feed the mistake tracker with
928
+ // forceAtLimit:true and abort. Parity with pre-Step-9
929
+ // agent.ts L917-954.
930
+ this.inspectLoopForToolCall(
931
+ event.toolCall.toolName,
932
+ event.toolCall.input,
933
+ event.iteration,
934
+ );
935
+ break;
936
+ }
937
+ case "tool-finished": {
938
+ const startedAt = this.toolStartedAt.get(event.toolCall.toolCallId);
939
+ const endedAt = new Date();
940
+ const input = this.toolInputs.get(event.toolCall.toolCallId);
941
+ this.toolStartedAt.delete(event.toolCall.toolCallId);
942
+ this.toolInputs.delete(event.toolCall.toolCallId);
943
+ const resultPart = event.message.content.find(
944
+ (part) => part.type === "tool-result",
945
+ );
946
+ const isError =
947
+ resultPart?.type === "tool-result" && resultPart.isError === true;
948
+ const errorText = isError
949
+ ? formatToolResultError(
950
+ resultPart?.type === "tool-result"
951
+ ? resultPart.output
952
+ : undefined,
953
+ )
954
+ : undefined;
955
+ const record: ToolCallRecord = {
956
+ id: event.toolCall.toolCallId,
957
+ name: event.toolCall.toolName,
958
+ input,
959
+ output:
960
+ resultPart?.type === "tool-result" ? resultPart.output : undefined,
961
+ error: errorText,
962
+ durationMs:
963
+ startedAt === undefined
964
+ ? 0
965
+ : endedAt.getTime() - startedAt.getTime(),
966
+ startedAt: startedAt ?? endedAt,
967
+ endedAt,
968
+ };
969
+ this.currentRunToolCalls.push(record);
970
+ // Per-turn success/failure bookkeeping for MistakeTracker.
971
+ if (isError) {
972
+ this.currentTurnFailedTools += 1;
973
+ if (errorText) {
974
+ this.currentTurnFailureDetails.push(
975
+ `[${event.toolCall.toolName}] ${errorText}`,
976
+ );
977
+ }
978
+ } else {
979
+ this.currentTurnSuccessfulTools += 1;
980
+ }
981
+ break;
982
+ }
983
+ case "turn-finished": {
984
+ // End-of-turn mistake evaluation: legacy parity (pre-Step-9
985
+ // agent.ts L972-997). When some tool calls failed and the
986
+ // turn had no successful tool calls, record a mistake;
987
+ // reset on productive turns.
988
+ const failed = this.currentTurnFailedTools;
989
+ const succeeded = this.currentTurnSuccessfulTools;
990
+ if (failed > 0 && succeeded === 0) {
991
+ const details = this.currentTurnFailureDetails.join("; ");
992
+ this.enqueueMistakeRecord({
993
+ iteration: event.iteration,
994
+ reason: "tool_execution_failed",
995
+ details: `${failed} tool call(s) failed${
996
+ details ? `: ${details}` : ""
997
+ }`,
998
+ });
999
+ } else if (succeeded > 0) {
1000
+ // Productive turn — reset the tracker so transient
1001
+ // failures don't accumulate across unrelated turns.
1002
+ this.mistakeTracker.reset();
1003
+ }
1004
+ break;
1005
+ }
1006
+ case "usage-updated": {
1007
+ this.currentRunUsage = {
1008
+ inputTokens: event.usage.inputTokens,
1009
+ outputTokens: event.usage.outputTokens,
1010
+ cacheReadTokens:
1011
+ event.usage.cacheReadTokens > 0
1012
+ ? event.usage.cacheReadTokens
1013
+ : undefined,
1014
+ cacheWriteTokens:
1015
+ event.usage.cacheWriteTokens > 0
1016
+ ? event.usage.cacheWriteTokens
1017
+ : undefined,
1018
+ totalCost: event.usage.totalCost,
1019
+ };
1020
+ break;
1021
+ }
1022
+ default:
1023
+ break;
1024
+ }
1025
+ for (const legacy of this.eventAdapter.translate(event)) {
1026
+ this.emitLegacyEvent(legacy);
1027
+ }
1028
+ }
1029
+
1030
+ private emitLegacyEvent(event: AgentEvent): void {
1031
+ for (const listener of this.listeners) {
1032
+ try {
1033
+ listener(event);
1034
+ } catch (error) {
1035
+ this.logger?.error?.("SessionRuntime event listener threw", {
1036
+ agentId: this.agentId,
1037
+ error,
1038
+ });
1039
+ }
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Feed the `LoopDetectionTracker` with a tool-call and react to
1045
+ * the returned verdict. Parity with pre-Step-9 agent.ts L917-954:
1046
+ *
1047
+ * - `"soft"` → append a recovery notice telling the model to
1048
+ * change approach;
1049
+ * - `"hard"` → feed `MistakeTracker.record` with
1050
+ * `forceAtLimit:true`. When the tracker returns
1051
+ * `action: "stop"`, append the stop notice and
1052
+ * abort the active runtime.
1053
+ */
1054
+ private inspectLoopForToolCall(
1055
+ toolName: string,
1056
+ input: unknown,
1057
+ iteration: number,
1058
+ ): void {
1059
+ if (this.trackerAbortInFlight || this.loopDetectionDisabled) {
1060
+ return;
1061
+ }
1062
+ const verdict = this.loopTracker.inspect({ name: toolName, input });
1063
+ if (verdict.kind === "ok") {
1064
+ return;
1065
+ }
1066
+ if (verdict.kind === "soft") {
1067
+ if (verdict.message) {
1068
+ this.conversation.appendMessage({
1069
+ role: "user",
1070
+ content: [{ type: "text", text: verdict.message }],
1071
+ });
1072
+ }
1073
+ return;
1074
+ }
1075
+ // Hard escalation.
1076
+ this.enqueueMistakeRecord({
1077
+ iteration,
1078
+ reason: "tool_execution_failed",
1079
+ forceAtLimit: true,
1080
+ details:
1081
+ verdict.message ??
1082
+ `Detected repeated tool calls to \`${toolName}\`; stopping to avoid a loop.`,
1083
+ });
1084
+ }
1085
+
1086
+ /**
1087
+ * Enqueue a mistake-record onto the serial tracker work chain. The
1088
+ * runtime event stream is synchronous but `MistakeTracker.record`
1089
+ * is async — chaining onto a shared promise preserves ordering
1090
+ * (legacy parity) and lets `executeRun` await draining before
1091
+ * returning the `AgentResult`.
1092
+ *
1093
+ * When the tracker returns `action: "stop"`, append the stop notice
1094
+ * to the conversation and abort the active runtime so the run ends
1095
+ * with `finishReason: "aborted"`.
1096
+ */
1097
+ private enqueueMistakeRecord(input: {
1098
+ iteration: number;
1099
+ reason: "api_error" | "invalid_tool_call" | "tool_execution_failed";
1100
+ details?: string;
1101
+ forceAtLimit?: boolean;
1102
+ }): void {
1103
+ if (this.trackerAbortInFlight) {
1104
+ return;
1105
+ }
1106
+ this.activeTrackerWork = this.activeTrackerWork.then(async () => {
1107
+ if (this.trackerAbortInFlight) {
1108
+ return;
1109
+ }
1110
+ const outcome = await this.mistakeTracker.record(input);
1111
+ if (outcome.action === "stop") {
1112
+ this.trackerAbortInFlight = true;
1113
+ this.conversation.appendMessage({
1114
+ role: "user",
1115
+ content: [{ type: "text", text: outcome.message }],
1116
+ });
1117
+ this.activeRuntime?.abort(outcome.reason ?? outcome.message);
1118
+ }
1119
+ });
1120
+ }
1121
+
1122
+ private buildLegacyResult(input: {
1123
+ runResult: AgentRunResult | undefined;
1124
+ thrownError: Error | undefined;
1125
+ startedAt: Date;
1126
+ endedAt: Date;
1127
+ }): AgentResult {
1128
+ const { runResult, thrownError, startedAt, endedAt } = input;
1129
+ const durationMs = endedAt.getTime() - startedAt.getTime();
1130
+ const finishReason: AgentFinishReason = thrownError
1131
+ ? "error"
1132
+ : deriveFinishReason(runResult);
1133
+ const text =
1134
+ runResult?.outputText ||
1135
+ (runResult?.status === "failed" ? runResult.error?.message : undefined) ||
1136
+ "";
1137
+ const usage: LegacyAgentUsage = runResult
1138
+ ? {
1139
+ inputTokens: runResult.usage.inputTokens,
1140
+ outputTokens: runResult.usage.outputTokens,
1141
+ cacheReadTokens:
1142
+ runResult.usage.cacheReadTokens > 0
1143
+ ? runResult.usage.cacheReadTokens
1144
+ : undefined,
1145
+ cacheWriteTokens:
1146
+ runResult.usage.cacheWriteTokens > 0
1147
+ ? runResult.usage.cacheWriteTokens
1148
+ : undefined,
1149
+ totalCost: runResult.usage.totalCost,
1150
+ }
1151
+ : this.currentRunUsage;
1152
+ const messages = runResult
1153
+ ? agentMessagesToMessagesWithMetadata(runResult.messages)
1154
+ : this.conversation.getMessages();
1155
+ const modelInfo = tryGetModelInfo(this.config);
1156
+ if (thrownError) {
1157
+ throw thrownError;
1158
+ }
1159
+ return {
1160
+ text,
1161
+ usage,
1162
+ messages,
1163
+ toolCalls: this.currentRunToolCalls,
1164
+ iterations: runResult?.iterations ?? 0,
1165
+ finishReason,
1166
+ model: {
1167
+ id: this.config.modelId,
1168
+ provider: this.config.providerId,
1169
+ info: modelInfo,
1170
+ },
1171
+ startedAt,
1172
+ endedAt,
1173
+ durationMs,
1174
+ };
1175
+ }
1176
+
1177
+ private async dispatchRunEnd(result: AgentResult): Promise<void> {
1178
+ await this.hookBridge.dispatch("hook.run_end", {
1179
+ stage: "run_end",
1180
+ iteration: result.iterations,
1181
+ payload: {
1182
+ agentId: this.agentId,
1183
+ conversationId: this.conversation.getConversationId(),
1184
+ parentAgentId: this.parentAgentId ?? null,
1185
+ result,
1186
+ },
1187
+ });
1188
+ }
1189
+ }
1190
+
1191
+ // =============================================================================
1192
+ // Module-level helpers
1193
+ // =============================================================================
1194
+
1195
+ function leveledLog(
1196
+ logger: BasicLogger | undefined,
1197
+ level: "debug" | "info" | "warn" | "error",
1198
+ message: string,
1199
+ metadata?: Record<string, unknown>,
1200
+ ): void {
1201
+ if (!logger) {
1202
+ return;
1203
+ }
1204
+ if (level === "debug") {
1205
+ logger.debug(message, metadata);
1206
+ return;
1207
+ }
1208
+ if (level === "error" && logger.error) {
1209
+ logger.error(message, metadata);
1210
+ return;
1211
+ }
1212
+ const severity: "info" | "warn" | "error" =
1213
+ level === "warn" ? "warn" : level === "error" ? "error" : "info";
1214
+ logger.log(message, { ...metadata, severity });
1215
+ }
1216
+
1217
+ function deriveFinishReason(
1218
+ runResult: AgentRunResult | undefined,
1219
+ ): AgentFinishReason {
1220
+ if (!runResult) {
1221
+ return "error";
1222
+ }
1223
+ switch (runResult.status) {
1224
+ case "completed":
1225
+ return "completed";
1226
+ case "aborted":
1227
+ return "aborted";
1228
+ case "failed":
1229
+ return "error";
1230
+ }
1231
+ }
1232
+
1233
+ async function buildUserTurnContent(
1234
+ userMessage: string,
1235
+ userImages: string[] | undefined,
1236
+ userFiles: string[] | undefined,
1237
+ loader: AgentConfig["userFileContentLoader"],
1238
+ ): Promise<Message["content"]> {
1239
+ // Import lazily to avoid a circular-import hazard via runtime barrels.
1240
+ const { buildInitialUserContent } = await import("./user-input-builder");
1241
+ return buildInitialUserContent(userMessage, userImages, userFiles, loader);
1242
+ }
1243
+
1244
+ function tryGetModelInfo(config: AgentConfig): ModelInfo | undefined {
1245
+ if (config.knownModels?.[config.modelId]) {
1246
+ return config.knownModels[config.modelId];
1247
+ }
1248
+ const resolvedKnownModels = resolveKnownModelsFromConfig(config);
1249
+ if (resolvedKnownModels?.[config.modelId]) {
1250
+ return resolvedKnownModels[config.modelId];
1251
+ }
1252
+ return undefined;
1253
+ }