@clinebot/core 0.0.20 → 0.0.22

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 (356) hide show
  1. package/dist/account/cline-account-service.d.ts +3 -2
  2. package/dist/account/cline-account-service.d.ts.map +1 -0
  3. package/dist/account/index.d.ts +1 -0
  4. package/dist/account/index.d.ts.map +1 -0
  5. package/dist/account/rpc.d.ts +1 -0
  6. package/dist/account/rpc.d.ts.map +1 -0
  7. package/dist/account/types.d.ts +1 -0
  8. package/dist/account/types.d.ts.map +1 -0
  9. package/dist/agents/agent-config-loader.d.ts +1 -0
  10. package/dist/agents/agent-config-loader.d.ts.map +1 -0
  11. package/dist/agents/agent-config-parser.d.ts +1 -0
  12. package/dist/agents/agent-config-parser.d.ts.map +1 -0
  13. package/dist/agents/hooks-config-loader.d.ts +1 -0
  14. package/dist/agents/hooks-config-loader.d.ts.map +1 -0
  15. package/dist/agents/index.d.ts +1 -0
  16. package/dist/agents/index.d.ts.map +1 -0
  17. package/dist/agents/plugin-config-loader.d.ts +1 -0
  18. package/dist/agents/plugin-config-loader.d.ts.map +1 -0
  19. package/dist/agents/plugin-loader.d.ts +1 -0
  20. package/dist/agents/plugin-loader.d.ts.map +1 -0
  21. package/dist/agents/plugin-sandbox.d.ts +1 -0
  22. package/dist/agents/plugin-sandbox.d.ts.map +1 -0
  23. package/dist/agents/unified-config-file-watcher.d.ts +1 -0
  24. package/dist/agents/unified-config-file-watcher.d.ts.map +1 -0
  25. package/dist/agents/user-instruction-config-loader.d.ts +1 -0
  26. package/dist/agents/user-instruction-config-loader.d.ts.map +1 -0
  27. package/dist/auth/client.d.ts +1 -0
  28. package/dist/auth/client.d.ts.map +1 -0
  29. package/dist/auth/cline.d.ts +1 -0
  30. package/dist/auth/cline.d.ts.map +1 -0
  31. package/dist/auth/codex.d.ts +1 -0
  32. package/dist/auth/codex.d.ts.map +1 -0
  33. package/dist/auth/oca.d.ts +1 -0
  34. package/dist/auth/oca.d.ts.map +1 -0
  35. package/dist/auth/server.d.ts +1 -0
  36. package/dist/auth/server.d.ts.map +1 -0
  37. package/dist/auth/types.d.ts +1 -0
  38. package/dist/auth/types.d.ts.map +1 -0
  39. package/dist/auth/utils.d.ts +1 -0
  40. package/dist/auth/utils.d.ts.map +1 -0
  41. package/dist/chat/chat-schema.d.ts +13 -12
  42. package/dist/chat/chat-schema.d.ts.map +1 -0
  43. package/dist/index.d.ts +3 -1
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.node.d.ts +2 -0
  46. package/dist/index.node.d.ts.map +1 -0
  47. package/dist/index.node.js +303 -302
  48. package/dist/input/file-indexer.d.ts +1 -0
  49. package/dist/input/file-indexer.d.ts.map +1 -0
  50. package/dist/input/index.d.ts +1 -0
  51. package/dist/input/index.d.ts.map +1 -0
  52. package/dist/input/mention-enricher.d.ts +1 -0
  53. package/dist/input/mention-enricher.d.ts.map +1 -0
  54. package/dist/mcp/config-loader.d.ts +1 -0
  55. package/dist/mcp/config-loader.d.ts.map +1 -0
  56. package/dist/mcp/index.d.ts +1 -0
  57. package/dist/mcp/index.d.ts.map +1 -0
  58. package/dist/mcp/manager.d.ts +1 -0
  59. package/dist/mcp/manager.d.ts.map +1 -0
  60. package/dist/mcp/types.d.ts +1 -0
  61. package/dist/mcp/types.d.ts.map +1 -0
  62. package/dist/providers/local-provider-registry.d.ts +36 -0
  63. package/dist/providers/local-provider-registry.d.ts.map +1 -0
  64. package/dist/providers/local-provider-service.d.ts +2 -1
  65. package/dist/providers/local-provider-service.d.ts.map +1 -0
  66. package/dist/runtime/commands.d.ts +1 -0
  67. package/dist/runtime/commands.d.ts.map +1 -0
  68. package/dist/runtime/hook-file-hooks.d.ts +1 -0
  69. package/dist/runtime/hook-file-hooks.d.ts.map +1 -0
  70. package/dist/runtime/rules.d.ts +1 -0
  71. package/dist/runtime/rules.d.ts.map +1 -0
  72. package/dist/runtime/runtime-builder.d.ts +1 -0
  73. package/dist/runtime/runtime-builder.d.ts.map +1 -0
  74. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +1 -0
  75. package/dist/runtime/sandbox/subprocess-sandbox.d.ts.map +1 -0
  76. package/dist/runtime/session-runtime.d.ts +2 -0
  77. package/dist/runtime/session-runtime.d.ts.map +1 -0
  78. package/dist/runtime/skills.d.ts +1 -0
  79. package/dist/runtime/skills.d.ts.map +1 -0
  80. package/dist/runtime/tool-approval.d.ts +1 -0
  81. package/dist/runtime/tool-approval.d.ts.map +1 -0
  82. package/dist/runtime/workflows.d.ts +1 -0
  83. package/dist/runtime/workflows.d.ts.map +1 -0
  84. package/dist/session/default-session-manager.d.ts +4 -0
  85. package/dist/session/default-session-manager.d.ts.map +1 -0
  86. package/dist/session/file-session-service.d.ts +1 -0
  87. package/dist/session/file-session-service.d.ts.map +1 -0
  88. package/dist/session/rpc-session-service.d.ts +1 -0
  89. package/dist/session/rpc-session-service.d.ts.map +1 -0
  90. package/dist/session/rpc-spawn-lease.d.ts +1 -0
  91. package/dist/session/rpc-spawn-lease.d.ts.map +1 -0
  92. package/dist/session/runtime-oauth-token-manager.d.ts +1 -0
  93. package/dist/session/runtime-oauth-token-manager.d.ts.map +1 -0
  94. package/dist/session/session-agent-events.d.ts +20 -1
  95. package/dist/session/session-agent-events.d.ts.map +1 -0
  96. package/dist/session/session-artifacts.d.ts +1 -0
  97. package/dist/session/session-artifacts.d.ts.map +1 -0
  98. package/dist/session/session-config-builder.d.ts +1 -0
  99. package/dist/session/session-config-builder.d.ts.map +1 -0
  100. package/dist/session/session-graph.d.ts +1 -0
  101. package/dist/session/session-graph.d.ts.map +1 -0
  102. package/dist/session/session-host.d.ts +1 -0
  103. package/dist/session/session-host.d.ts.map +1 -0
  104. package/dist/session/session-manager.d.ts +1 -0
  105. package/dist/session/session-manager.d.ts.map +1 -0
  106. package/dist/session/session-manifest.d.ts +2 -1
  107. package/dist/session/session-manifest.d.ts.map +1 -0
  108. package/dist/session/session-service.d.ts +1 -0
  109. package/dist/session/session-service.d.ts.map +1 -0
  110. package/dist/session/session-team-coordination.d.ts +1 -0
  111. package/dist/session/session-team-coordination.d.ts.map +1 -0
  112. package/dist/session/session-telemetry.d.ts +3 -1
  113. package/dist/session/session-telemetry.d.ts.map +1 -0
  114. package/dist/session/sqlite-rpc-session-backend.d.ts +1 -0
  115. package/dist/session/sqlite-rpc-session-backend.d.ts.map +1 -0
  116. package/dist/session/unified-session-persistence-service.d.ts +1 -0
  117. package/dist/session/unified-session-persistence-service.d.ts.map +1 -0
  118. package/dist/session/utils/helpers.d.ts +1 -0
  119. package/dist/session/utils/helpers.d.ts.map +1 -0
  120. package/dist/session/utils/types.d.ts +1 -0
  121. package/dist/session/utils/types.d.ts.map +1 -0
  122. package/dist/session/utils/usage.d.ts +1 -0
  123. package/dist/session/utils/usage.d.ts.map +1 -0
  124. package/dist/session/workspace-manager.d.ts +1 -0
  125. package/dist/session/workspace-manager.d.ts.map +1 -0
  126. package/dist/session/workspace-manifest.d.ts +1 -0
  127. package/dist/session/workspace-manifest.d.ts.map +1 -0
  128. package/dist/storage/file-team-store.d.ts +1 -0
  129. package/dist/storage/file-team-store.d.ts.map +1 -0
  130. package/dist/storage/provider-settings-legacy-migration.d.ts +1 -0
  131. package/dist/storage/provider-settings-legacy-migration.d.ts.map +1 -0
  132. package/dist/storage/provider-settings-manager.d.ts +1 -0
  133. package/dist/storage/provider-settings-manager.d.ts.map +1 -0
  134. package/dist/storage/sqlite-session-store.d.ts +1 -0
  135. package/dist/storage/sqlite-session-store.d.ts.map +1 -0
  136. package/dist/storage/sqlite-team-store.d.ts +1 -0
  137. package/dist/storage/sqlite-team-store.d.ts.map +1 -0
  138. package/dist/storage/team-store.d.ts +1 -0
  139. package/dist/storage/team-store.d.ts.map +1 -0
  140. package/dist/team/index.d.ts +1 -0
  141. package/dist/team/index.d.ts.map +1 -0
  142. package/dist/team/projections.d.ts +1 -0
  143. package/dist/team/projections.d.ts.map +1 -0
  144. package/dist/telemetry/ITelemetryAdapter.d.ts +1 -0
  145. package/dist/telemetry/ITelemetryAdapter.d.ts.map +1 -0
  146. package/dist/telemetry/LoggerTelemetryAdapter.d.ts +1 -0
  147. package/dist/telemetry/LoggerTelemetryAdapter.d.ts.map +1 -0
  148. package/dist/telemetry/OpenTelemetryAdapter.d.ts +1 -0
  149. package/dist/telemetry/OpenTelemetryAdapter.d.ts.map +1 -0
  150. package/dist/telemetry/OpenTelemetryProvider.d.ts +1 -0
  151. package/dist/telemetry/OpenTelemetryProvider.d.ts.map +1 -0
  152. package/dist/telemetry/TelemetryService.d.ts +1 -0
  153. package/dist/telemetry/TelemetryService.d.ts.map +1 -0
  154. package/dist/telemetry/core-events.d.ts +55 -22
  155. package/dist/telemetry/core-events.d.ts.map +1 -0
  156. package/dist/telemetry/opentelemetry.d.ts +1 -0
  157. package/dist/telemetry/opentelemetry.d.ts.map +1 -0
  158. package/dist/tools/constants.d.ts +1 -0
  159. package/dist/tools/constants.d.ts.map +1 -0
  160. package/dist/tools/definitions.d.ts +8 -1
  161. package/dist/tools/definitions.d.ts.map +1 -0
  162. package/dist/tools/executors/apply-patch-parser.d.ts +1 -0
  163. package/dist/tools/executors/apply-patch-parser.d.ts.map +1 -0
  164. package/dist/tools/executors/apply-patch.d.ts +1 -0
  165. package/dist/tools/executors/apply-patch.d.ts.map +1 -0
  166. package/dist/tools/executors/bash.d.ts +2 -1
  167. package/dist/tools/executors/bash.d.ts.map +1 -0
  168. package/dist/tools/executors/editor.d.ts +1 -0
  169. package/dist/tools/executors/editor.d.ts.map +1 -0
  170. package/dist/tools/executors/file-read.d.ts +1 -0
  171. package/dist/tools/executors/file-read.d.ts.map +1 -0
  172. package/dist/tools/executors/index.d.ts +14 -7
  173. package/dist/tools/executors/index.d.ts.map +1 -0
  174. package/dist/tools/executors/search.d.ts +1 -0
  175. package/dist/tools/executors/search.d.ts.map +1 -0
  176. package/dist/tools/executors/web-fetch.d.ts +1 -0
  177. package/dist/tools/executors/web-fetch.d.ts.map +1 -0
  178. package/dist/tools/helpers.d.ts +15 -0
  179. package/dist/tools/helpers.d.ts.map +1 -0
  180. package/dist/tools/index.d.ts +2 -1
  181. package/dist/tools/index.d.ts.map +1 -0
  182. package/dist/tools/model-tool-routing.d.ts +1 -0
  183. package/dist/tools/model-tool-routing.d.ts.map +1 -0
  184. package/dist/tools/presets.d.ts +1 -0
  185. package/dist/tools/presets.d.ts.map +1 -0
  186. package/dist/tools/schemas.d.ts +41 -0
  187. package/dist/tools/schemas.d.ts.map +1 -0
  188. package/dist/tools/types.d.ts +3 -2
  189. package/dist/tools/types.d.ts.map +1 -0
  190. package/dist/types/common.d.ts +1 -0
  191. package/dist/types/common.d.ts.map +1 -0
  192. package/dist/types/config.d.ts +1 -0
  193. package/dist/types/config.d.ts.map +1 -0
  194. package/dist/types/events.d.ts +1 -0
  195. package/dist/types/events.d.ts.map +1 -0
  196. package/dist/types/provider-settings.d.ts +1 -0
  197. package/dist/types/provider-settings.d.ts.map +1 -0
  198. package/dist/types/sessions.d.ts +1 -0
  199. package/dist/types/sessions.d.ts.map +1 -0
  200. package/dist/types/storage.d.ts +1 -0
  201. package/dist/types/storage.d.ts.map +1 -0
  202. package/dist/types/workspace.d.ts +1 -0
  203. package/dist/types/workspace.d.ts.map +1 -0
  204. package/dist/types.d.ts +1 -0
  205. package/dist/types.d.ts.map +1 -0
  206. package/package.json +8 -6
  207. package/src/account/cline-account-service.test.ts +0 -101
  208. package/src/account/cline-account-service.ts +0 -287
  209. package/src/account/index.ts +0 -22
  210. package/src/account/rpc.test.ts +0 -62
  211. package/src/account/rpc.ts +0 -172
  212. package/src/account/types.ts +0 -98
  213. package/src/agents/agent-config-loader.test.ts +0 -236
  214. package/src/agents/agent-config-loader.ts +0 -108
  215. package/src/agents/agent-config-parser.ts +0 -198
  216. package/src/agents/hooks-config-loader.test.ts +0 -20
  217. package/src/agents/hooks-config-loader.ts +0 -118
  218. package/src/agents/index.ts +0 -85
  219. package/src/agents/plugin-config-loader.test.ts +0 -140
  220. package/src/agents/plugin-config-loader.ts +0 -97
  221. package/src/agents/plugin-loader.test.ts +0 -228
  222. package/src/agents/plugin-loader.ts +0 -172
  223. package/src/agents/plugin-sandbox-bootstrap.ts +0 -445
  224. package/src/agents/plugin-sandbox.test.ts +0 -317
  225. package/src/agents/plugin-sandbox.ts +0 -341
  226. package/src/agents/unified-config-file-watcher.test.ts +0 -196
  227. package/src/agents/unified-config-file-watcher.ts +0 -483
  228. package/src/agents/user-instruction-config-loader.test.ts +0 -158
  229. package/src/agents/user-instruction-config-loader.ts +0 -438
  230. package/src/auth/client.test.ts +0 -40
  231. package/src/auth/client.ts +0 -25
  232. package/src/auth/cline.test.ts +0 -130
  233. package/src/auth/cline.ts +0 -420
  234. package/src/auth/codex.test.ts +0 -170
  235. package/src/auth/codex.ts +0 -491
  236. package/src/auth/oca.test.ts +0 -215
  237. package/src/auth/oca.ts +0 -573
  238. package/src/auth/server.ts +0 -216
  239. package/src/auth/types.ts +0 -81
  240. package/src/auth/utils.test.ts +0 -128
  241. package/src/auth/utils.ts +0 -247
  242. package/src/chat/chat-schema.ts +0 -82
  243. package/src/index.node.ts +0 -285
  244. package/src/index.ts +0 -211
  245. package/src/input/file-indexer.d.ts +0 -11
  246. package/src/input/file-indexer.test.ts +0 -127
  247. package/src/input/file-indexer.ts +0 -327
  248. package/src/input/index.ts +0 -7
  249. package/src/input/mention-enricher.test.ts +0 -85
  250. package/src/input/mention-enricher.ts +0 -122
  251. package/src/mcp/config-loader.test.ts +0 -238
  252. package/src/mcp/config-loader.ts +0 -219
  253. package/src/mcp/index.ts +0 -26
  254. package/src/mcp/manager.test.ts +0 -106
  255. package/src/mcp/manager.ts +0 -262
  256. package/src/mcp/types.ts +0 -88
  257. package/src/providers/local-provider-service.ts +0 -608
  258. package/src/runtime/commands.test.ts +0 -98
  259. package/src/runtime/commands.ts +0 -83
  260. package/src/runtime/hook-file-hooks.test.ts +0 -237
  261. package/src/runtime/hook-file-hooks.ts +0 -859
  262. package/src/runtime/index.ts +0 -37
  263. package/src/runtime/rules.ts +0 -34
  264. package/src/runtime/runtime-builder.team-persistence.test.ts +0 -202
  265. package/src/runtime/runtime-builder.test.ts +0 -371
  266. package/src/runtime/runtime-builder.ts +0 -589
  267. package/src/runtime/runtime-parity.test.ts +0 -143
  268. package/src/runtime/sandbox/subprocess-sandbox.ts +0 -231
  269. package/src/runtime/session-runtime.ts +0 -46
  270. package/src/runtime/skills.ts +0 -44
  271. package/src/runtime/tool-approval.ts +0 -104
  272. package/src/runtime/workflows.test.ts +0 -119
  273. package/src/runtime/workflows.ts +0 -45
  274. package/src/session/default-session-manager.e2e.test.ts +0 -384
  275. package/src/session/default-session-manager.test.ts +0 -1741
  276. package/src/session/default-session-manager.ts +0 -1233
  277. package/src/session/file-session-service.ts +0 -280
  278. package/src/session/index.ts +0 -42
  279. package/src/session/rpc-session-service.ts +0 -107
  280. package/src/session/rpc-spawn-lease.test.ts +0 -49
  281. package/src/session/rpc-spawn-lease.ts +0 -122
  282. package/src/session/runtime-oauth-token-manager.test.ts +0 -137
  283. package/src/session/runtime-oauth-token-manager.ts +0 -272
  284. package/src/session/session-agent-events.ts +0 -159
  285. package/src/session/session-artifacts.ts +0 -106
  286. package/src/session/session-config-builder.ts +0 -113
  287. package/src/session/session-graph.ts +0 -92
  288. package/src/session/session-host.test.ts +0 -29
  289. package/src/session/session-host.ts +0 -242
  290. package/src/session/session-manager.ts +0 -69
  291. package/src/session/session-manifest.ts +0 -29
  292. package/src/session/session-service.team-persistence.test.ts +0 -48
  293. package/src/session/session-service.ts +0 -673
  294. package/src/session/session-team-coordination.ts +0 -229
  295. package/src/session/session-telemetry.ts +0 -95
  296. package/src/session/sqlite-rpc-session-backend.ts +0 -303
  297. package/src/session/unified-session-persistence-service.test.ts +0 -85
  298. package/src/session/unified-session-persistence-service.ts +0 -996
  299. package/src/session/utils/helpers.ts +0 -139
  300. package/src/session/utils/types.ts +0 -57
  301. package/src/session/utils/usage.ts +0 -32
  302. package/src/session/workspace-manager.ts +0 -98
  303. package/src/session/workspace-manifest.ts +0 -100
  304. package/src/storage/artifact-store.ts +0 -1
  305. package/src/storage/file-team-store.ts +0 -257
  306. package/src/storage/index.ts +0 -11
  307. package/src/storage/provider-settings-legacy-migration.test.ts +0 -307
  308. package/src/storage/provider-settings-legacy-migration.ts +0 -689
  309. package/src/storage/provider-settings-manager.test.ts +0 -145
  310. package/src/storage/provider-settings-manager.ts +0 -150
  311. package/src/storage/session-store.ts +0 -1
  312. package/src/storage/sqlite-session-store.ts +0 -275
  313. package/src/storage/sqlite-team-store.ts +0 -454
  314. package/src/storage/team-store.ts +0 -40
  315. package/src/team/index.ts +0 -4
  316. package/src/team/projections.ts +0 -285
  317. package/src/telemetry/ITelemetryAdapter.ts +0 -94
  318. package/src/telemetry/LoggerTelemetryAdapter.test.ts +0 -42
  319. package/src/telemetry/LoggerTelemetryAdapter.ts +0 -114
  320. package/src/telemetry/OpenTelemetryAdapter.test.ts +0 -157
  321. package/src/telemetry/OpenTelemetryAdapter.ts +0 -348
  322. package/src/telemetry/OpenTelemetryProvider.test.ts +0 -113
  323. package/src/telemetry/OpenTelemetryProvider.ts +0 -322
  324. package/src/telemetry/TelemetryService.test.ts +0 -134
  325. package/src/telemetry/TelemetryService.ts +0 -141
  326. package/src/telemetry/core-events.ts +0 -344
  327. package/src/telemetry/opentelemetry.ts +0 -20
  328. package/src/tools/constants.ts +0 -35
  329. package/src/tools/definitions.test.ts +0 -658
  330. package/src/tools/definitions.ts +0 -726
  331. package/src/tools/executors/apply-patch-parser.ts +0 -520
  332. package/src/tools/executors/apply-patch.ts +0 -359
  333. package/src/tools/executors/bash.ts +0 -205
  334. package/src/tools/executors/editor.test.ts +0 -35
  335. package/src/tools/executors/editor.ts +0 -219
  336. package/src/tools/executors/file-read.test.ts +0 -49
  337. package/src/tools/executors/file-read.ts +0 -110
  338. package/src/tools/executors/index.ts +0 -75
  339. package/src/tools/executors/search.ts +0 -278
  340. package/src/tools/executors/web-fetch.ts +0 -259
  341. package/src/tools/index.ts +0 -168
  342. package/src/tools/model-tool-routing.test.ts +0 -86
  343. package/src/tools/model-tool-routing.ts +0 -132
  344. package/src/tools/presets.test.ts +0 -62
  345. package/src/tools/presets.ts +0 -168
  346. package/src/tools/schemas.ts +0 -284
  347. package/src/tools/types.ts +0 -328
  348. package/src/types/common.ts +0 -14
  349. package/src/types/config.ts +0 -84
  350. package/src/types/events.ts +0 -74
  351. package/src/types/index.ts +0 -24
  352. package/src/types/provider-settings.ts +0 -43
  353. package/src/types/sessions.ts +0 -16
  354. package/src/types/storage.ts +0 -64
  355. package/src/types/workspace.ts +0 -7
  356. package/src/types.ts +0 -128
@@ -1,1741 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import type { AgentResult } from "@clinebot/agents";
5
- import { setClineDir, setHomeDir } from "@clinebot/shared/storage";
6
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
- import { TelemetryService } from "../telemetry/TelemetryService";
8
- import { SessionSource } from "../types/common";
9
- import type { CoreSessionConfig } from "../types/config";
10
- import { DefaultSessionManager } from "./default-session-manager";
11
- import type { SessionManifest } from "./session-manifest";
12
-
13
- const distinctId = "test-machine-id";
14
-
15
- function createResult(overrides: Partial<AgentResult> = {}): AgentResult {
16
- return {
17
- text: "ok",
18
- iterations: 1,
19
- finishReason: "completed",
20
- usage: {
21
- inputTokens: 1,
22
- outputTokens: 2,
23
- totalCost: 0,
24
- },
25
- messages: [],
26
- toolCalls: [],
27
- durationMs: 1,
28
- model: {
29
- id: "mock-model",
30
- provider: "mock-provider",
31
- },
32
- startedAt: new Date("2026-01-01T00:00:00.000Z"),
33
- endedAt: new Date("2026-01-01T00:00:01.000Z"),
34
- ...overrides,
35
- };
36
- }
37
-
38
- function createManifest(sessionId: string): SessionManifest {
39
- return {
40
- version: 1,
41
- session_id: sessionId,
42
- source: SessionSource.CLI,
43
- pid: process.pid,
44
- started_at: "2026-01-01T00:00:00.000Z",
45
- status: "running",
46
- interactive: false,
47
- provider: "mock-provider",
48
- model: "mock-model",
49
- cwd: "/tmp/project",
50
- workspace_root: "/tmp/project",
51
- enable_tools: true,
52
- enable_spawn: true,
53
- enable_teams: true,
54
- prompt: "hello",
55
- messages_path: "/tmp/messages.json",
56
- };
57
- }
58
-
59
- type PluginEventTestHarness = {
60
- handlePluginEvent: (
61
- rootSessionId: string,
62
- event: { name: string; payload?: unknown },
63
- ) => Promise<void>;
64
- getPendingPrompts: (
65
- sessionId: string,
66
- ) => Array<{ prompt: string; delivery: "queue" | "steer" }>;
67
- };
68
-
69
- function createPluginEventHarness(
70
- manager: DefaultSessionManager,
71
- ): PluginEventTestHarness {
72
- const target = manager as object;
73
- return {
74
- handlePluginEvent: async (rootSessionId, event) => {
75
- const handler = Reflect.get(target, "handlePluginEvent");
76
- if (typeof handler !== "function") {
77
- throw new Error("handlePluginEvent test hook unavailable");
78
- }
79
- await Reflect.apply(
80
- handler as (
81
- rootSessionId: string,
82
- event: { name: string; payload?: unknown },
83
- ) => Promise<void>,
84
- target,
85
- [rootSessionId, event],
86
- );
87
- },
88
- getPendingPrompts: (sessionId) => {
89
- const getter = Reflect.get(target, "getSessionOrThrow");
90
- if (typeof getter !== "function") {
91
- throw new Error("getSessionOrThrow test hook unavailable");
92
- }
93
- const session = Reflect.apply(
94
- getter as (sessionId: string) => {
95
- pendingPrompts: Array<{
96
- id: string;
97
- prompt: string;
98
- delivery: "queue" | "steer";
99
- userFiles?: unknown;
100
- userImages?: unknown;
101
- }>;
102
- },
103
- target,
104
- [sessionId],
105
- );
106
- return session.pendingPrompts.map(({ prompt, delivery }) => ({
107
- prompt,
108
- delivery,
109
- }));
110
- },
111
- };
112
- }
113
-
114
- function createConfig(
115
- overrides: Partial<CoreSessionConfig> = {},
116
- ): CoreSessionConfig {
117
- return {
118
- providerId: "mock-provider",
119
- modelId: "mock-model",
120
- cwd: "/tmp/project",
121
- systemPrompt: "You are a test agent",
122
- enableTools: true,
123
- enableSpawnAgent: true,
124
- enableAgentTeams: true,
125
- ...overrides,
126
- };
127
- }
128
-
129
- describe("DefaultSessionManager", () => {
130
- const envSnapshot = {
131
- HOME: process.env.HOME,
132
- CLINE_DIR: process.env.CLINE_DIR,
133
- };
134
- let isolatedHomeDir = "";
135
-
136
- beforeEach(() => {
137
- isolatedHomeDir = mkdtempSync(join(tmpdir(), "core-session-home-"));
138
- process.env.HOME = isolatedHomeDir;
139
- process.env.CLINE_DIR = join(isolatedHomeDir, ".cline");
140
- setHomeDir(isolatedHomeDir);
141
- setClineDir(process.env.CLINE_DIR);
142
- });
143
-
144
- afterEach(() => {
145
- process.env.HOME = envSnapshot.HOME;
146
- process.env.CLINE_DIR = envSnapshot.CLINE_DIR;
147
- setHomeDir(envSnapshot.HOME ?? "~");
148
- setClineDir(envSnapshot.CLINE_DIR ?? join("~", ".cline"));
149
- rmSync(isolatedHomeDir, { recursive: true, force: true });
150
- });
151
-
152
- it("emits session lifecycle telemetry when configured", async () => {
153
- const sessionId = "sess-telemetry";
154
- const manifest = createManifest(sessionId);
155
- const adapter = {
156
- name: "test",
157
- emit: vi.fn(),
158
- emitRequired: vi.fn(),
159
- recordCounter: vi.fn(),
160
- recordHistogram: vi.fn(),
161
- recordGauge: vi.fn(),
162
- isEnabled: vi.fn(() => true),
163
- flush: vi.fn().mockResolvedValue(undefined),
164
- dispose: vi.fn().mockResolvedValue(undefined),
165
- };
166
- const telemetry = new TelemetryService({
167
- adapters: [adapter],
168
- distinctId: distinctId,
169
- });
170
- const sessionService = {
171
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
172
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
173
- manifestPath: "/tmp/manifest.json",
174
- transcriptPath: "/tmp/transcript.log",
175
- hookPath: "/tmp/hook.log",
176
- messagesPath: "/tmp/messages.json",
177
- manifest,
178
- }),
179
- persistSessionMessages: vi.fn(),
180
- updateSessionStatus: vi.fn().mockResolvedValue({
181
- updated: true,
182
- endedAt: "2026-01-01T00:00:05.000Z",
183
- }),
184
- writeSessionManifest: vi.fn(),
185
- listSessions: vi.fn().mockResolvedValue([]),
186
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
187
- };
188
- const runtimeBuilder = {
189
- build: vi.fn().mockReturnValue({
190
- tools: [],
191
- shutdown: vi.fn(),
192
- }),
193
- };
194
- const agent = {
195
- run: vi.fn().mockResolvedValue(createResult()),
196
- continue: vi.fn().mockResolvedValue(createResult()),
197
- getMessages: vi.fn().mockReturnValue([]),
198
- abort: vi.fn(),
199
- shutdown: vi.fn().mockResolvedValue(undefined),
200
- };
201
- const manager = new DefaultSessionManager({
202
- distinctId,
203
- sessionService: sessionService as never,
204
- runtimeBuilder: runtimeBuilder as never,
205
- createAgent: () => agent as never,
206
- telemetry,
207
- });
208
-
209
- await manager.start({
210
- config: createConfig({ telemetry, sessionId }),
211
- prompt: "hello",
212
- });
213
-
214
- expect(adapter.emit).toHaveBeenCalledWith(
215
- "session.started",
216
- expect.objectContaining({
217
- sessionId,
218
- distinct_id: distinctId,
219
- }),
220
- );
221
- });
222
-
223
- it("runs a non-interactive prompt and persists messages/status", async () => {
224
- const sessionId = "sess-1";
225
- const manifest = createManifest(sessionId);
226
- const createRootSessionWithArtifacts = vi.fn().mockResolvedValue({
227
- manifestPath: "/tmp/manifest.json",
228
- transcriptPath: "/tmp/transcript.log",
229
- hookPath: "/tmp/hook.log",
230
- messagesPath: "/tmp/messages.json",
231
- manifest,
232
- });
233
- const persistSessionMessages = vi.fn();
234
- const updateSessionStatus = vi.fn().mockResolvedValue({
235
- updated: true,
236
- endedAt: "2026-01-01T00:00:05.000Z",
237
- });
238
- const writeSessionManifest = vi.fn();
239
- const listSessions = vi.fn().mockResolvedValue([]);
240
- const deleteSession = vi.fn().mockResolvedValue({ deleted: true });
241
- const sessionService = {
242
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
243
- createRootSessionWithArtifacts,
244
- persistSessionMessages,
245
- updateSessionStatus,
246
- writeSessionManifest,
247
- listSessions,
248
- deleteSession,
249
- };
250
-
251
- const shutdown = vi.fn();
252
- const runtimeBuilder = {
253
- build: vi.fn().mockReturnValue({
254
- tools: [],
255
- shutdown,
256
- }),
257
- };
258
- const run = vi.fn().mockResolvedValue(
259
- createResult({
260
- messages: [
261
- { role: "user", content: [{ type: "text", text: "hello" }] },
262
- ],
263
- }),
264
- );
265
- const continueFn = vi.fn();
266
- const agent = {
267
- run,
268
- continue: continueFn,
269
- abort: vi.fn(),
270
- shutdown: vi.fn().mockResolvedValue(undefined),
271
- getMessages: vi.fn().mockReturnValue([]),
272
- messages: [],
273
- };
274
-
275
- const manager = new DefaultSessionManager({
276
- distinctId,
277
- sessionService: sessionService as never,
278
- runtimeBuilder,
279
- createAgent: () => agent as never,
280
- });
281
-
282
- const started = await manager.start({
283
- config: createConfig({ sessionId }),
284
- prompt: "hello",
285
- interactive: false,
286
- });
287
-
288
- expect(started.sessionId).toBe(sessionId);
289
- expect(started.result?.finishReason).toBe("completed");
290
- expect(run).toHaveBeenCalledTimes(1);
291
- expect(continueFn).not.toHaveBeenCalled();
292
- expect(persistSessionMessages).toHaveBeenCalledTimes(1);
293
- expect(updateSessionStatus).toHaveBeenCalledWith(sessionId, "completed", 0);
294
- expect(writeSessionManifest).toHaveBeenCalledTimes(1);
295
- expect(shutdown).toHaveBeenCalledTimes(1);
296
- });
297
-
298
- it("persists assistant message metadata for usage and model identity", async () => {
299
- const sessionId = "sess-meta";
300
- const manifest = createManifest(sessionId);
301
- const persistSessionMessages = vi.fn();
302
- const sessionService = {
303
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
304
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
305
- manifestPath: "/tmp/manifest-meta.json",
306
- transcriptPath: "/tmp/transcript-meta.log",
307
- hookPath: "/tmp/hook-meta.log",
308
- messagesPath: "/tmp/messages-meta.json",
309
- manifest,
310
- }),
311
- persistSessionMessages,
312
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
313
- writeSessionManifest: vi.fn(),
314
- listSessions: vi.fn().mockResolvedValue([]),
315
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
316
- };
317
- const runtimeBuilder = {
318
- build: vi.fn().mockReturnValue({
319
- tools: [],
320
- shutdown: vi.fn(),
321
- }),
322
- };
323
- const run = vi.fn().mockResolvedValue(
324
- createResult({
325
- usage: {
326
- inputTokens: 33,
327
- outputTokens: 12,
328
- cacheReadTokens: 4,
329
- cacheWriteTokens: 1,
330
- totalCost: 0.42,
331
- },
332
- model: {
333
- id: "claude-sonnet-4-6",
334
- provider: "anthropic",
335
- },
336
- endedAt: new Date("2026-01-01T00:00:02.000Z"),
337
- messages: [
338
- { role: "user", content: [{ type: "text", text: "hello" }] },
339
- { role: "assistant", content: [{ type: "text", text: "world" }] },
340
- ],
341
- }),
342
- );
343
- const manager = new DefaultSessionManager({
344
- distinctId,
345
- sessionService: sessionService as never,
346
- runtimeBuilder,
347
- createAgent: () =>
348
- ({
349
- run,
350
- continue: vi.fn(),
351
- abort: vi.fn(),
352
- shutdown: vi.fn().mockResolvedValue(undefined),
353
- getMessages: vi.fn().mockReturnValue([]),
354
- messages: [],
355
- }) as never,
356
- });
357
-
358
- await manager.start({
359
- config: createConfig({
360
- sessionId,
361
- providerId: "anthropic",
362
- modelId: "claude-sonnet-4-6",
363
- }),
364
- prompt: "hello",
365
- interactive: false,
366
- });
367
-
368
- expect(persistSessionMessages).toHaveBeenCalledTimes(1);
369
- const persisted = persistSessionMessages.mock.calls[0]?.[1];
370
- expect(Array.isArray(persisted)).toBe(true);
371
- expect(persisted?.[1]).toMatchObject({
372
- role: "assistant",
373
- providerId: "anthropic",
374
- modelId: "claude-sonnet-4-6",
375
- modelInfo: {
376
- id: "claude-sonnet-4-6",
377
- provider: "anthropic",
378
- },
379
- metrics: {
380
- inputTokens: 33,
381
- outputTokens: 12,
382
- cacheReadTokens: 4,
383
- cacheWriteTokens: 1,
384
- cost: 0.42,
385
- },
386
- ts: new Date("2026-01-01T00:00:02.000Z").getTime(),
387
- });
388
- });
389
-
390
- it("queues sandbox steer messages back into the active session", async () => {
391
- const sessionId = "sess-steer";
392
- const manifest = createManifest(sessionId);
393
- const sessionService = {
394
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
395
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
396
- manifestPath: "/tmp/manifest.json",
397
- transcriptPath: "/tmp/transcript.log",
398
- hookPath: "/tmp/hook.log",
399
- messagesPath: "/tmp/messages.json",
400
- manifest,
401
- }),
402
- persistSessionMessages: vi.fn(),
403
- updateSessionStatus: vi.fn().mockResolvedValue({
404
- updated: true,
405
- endedAt: "2026-01-01T00:00:05.000Z",
406
- }),
407
- writeSessionManifest: vi.fn(),
408
- listSessions: vi.fn().mockResolvedValue([]),
409
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
410
- };
411
- const runtimeBuilder = {
412
- build: vi.fn().mockReturnValue({
413
- tools: [],
414
- shutdown: vi.fn(),
415
- }),
416
- };
417
- const run = vi.fn().mockResolvedValue(
418
- createResult({
419
- messages: [
420
- { role: "user", content: [{ type: "text", text: "hello" }] },
421
- ],
422
- }),
423
- );
424
- const continueFn = vi.fn().mockResolvedValue(
425
- createResult({
426
- text: "steered",
427
- messages: [
428
- { role: "user", content: [{ type: "text", text: "hello" }] },
429
- {
430
- role: "assistant",
431
- content: [{ type: "text", text: "steered" }],
432
- },
433
- ],
434
- }),
435
- );
436
- const agent = {
437
- run,
438
- continue: continueFn,
439
- abort: vi.fn(),
440
- shutdown: vi.fn().mockResolvedValue(undefined),
441
- getMessages: vi
442
- .fn()
443
- .mockReturnValue([
444
- { role: "user", content: [{ type: "text", text: "hello" }] },
445
- ]),
446
- canStartRun: vi.fn().mockReturnValue(true),
447
- };
448
-
449
- const manager = new DefaultSessionManager({
450
- distinctId,
451
- sessionService: sessionService as never,
452
- runtimeBuilder,
453
- createAgent: () => agent as never,
454
- });
455
-
456
- await manager.start({
457
- config: createConfig({ sessionId }),
458
- prompt: "hello",
459
- interactive: true,
460
- });
461
-
462
- const harness = createPluginEventHarness(manager);
463
- await harness.handlePluginEvent(sessionId, {
464
- name: "steer_message",
465
- payload: { prompt: "async result" },
466
- });
467
- await vi.waitFor(() => {
468
- expect(continueFn).toHaveBeenCalledTimes(2);
469
- });
470
- expect(continueFn).toHaveBeenLastCalledWith(
471
- '<user_input mode="act">async result</user_input>',
472
- undefined,
473
- undefined,
474
- );
475
- });
476
-
477
- it("promotes queued prompts to the front when they become steer", async () => {
478
- const sessionId = "sess-steer-priority";
479
- const manifest = createManifest(sessionId);
480
- const sessionService = {
481
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
482
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
483
- manifestPath: "/tmp/manifest.json",
484
- transcriptPath: "/tmp/transcript.log",
485
- hookPath: "/tmp/hook.log",
486
- messagesPath: "/tmp/messages.json",
487
- manifest,
488
- }),
489
- persistSessionMessages: vi.fn(),
490
- updateSessionStatus: vi.fn().mockResolvedValue({
491
- updated: true,
492
- endedAt: "2026-01-01T00:00:05.000Z",
493
- }),
494
- writeSessionManifest: vi.fn(),
495
- listSessions: vi.fn().mockResolvedValue([]),
496
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
497
- };
498
- const runtimeBuilder = {
499
- build: vi.fn().mockReturnValue({
500
- tools: [],
501
- shutdown: vi.fn(),
502
- }),
503
- };
504
- const agent = {
505
- run: vi.fn().mockResolvedValue(createResult()),
506
- continue: vi.fn().mockResolvedValue(createResult()),
507
- abort: vi.fn(),
508
- shutdown: vi.fn().mockResolvedValue(undefined),
509
- getMessages: vi.fn().mockReturnValue([]),
510
- canStartRun: vi.fn().mockReturnValue(false),
511
- };
512
-
513
- const manager = new DefaultSessionManager({
514
- distinctId,
515
- sessionService: sessionService as never,
516
- runtimeBuilder,
517
- createAgent: () => agent as never,
518
- });
519
-
520
- await manager.start({
521
- config: createConfig({ sessionId }),
522
- prompt: "hello",
523
- interactive: true,
524
- });
525
-
526
- const harness = createPluginEventHarness(manager);
527
-
528
- await harness.handlePluginEvent(sessionId, {
529
- name: "queue_message",
530
- payload: { prompt: "queued first" },
531
- });
532
- await harness.handlePluginEvent(sessionId, {
533
- name: "queue_message",
534
- payload: { prompt: "queued second" },
535
- });
536
- await harness.handlePluginEvent(sessionId, {
537
- name: "steer_message",
538
- payload: { prompt: "queued first" },
539
- });
540
-
541
- expect(harness.getPendingPrompts(sessionId)).toEqual([
542
- { prompt: "queued first", delivery: "steer" },
543
- { prompt: "queued second", delivery: "queue" },
544
- ]);
545
- });
546
-
547
- it("preserves per-turn metadata on prior assistant messages across turns", async () => {
548
- const sessionId = "sess-meta-multi";
549
- const manifest = createManifest(sessionId);
550
- const persistSessionMessages = vi.fn();
551
- const runtimeBuilder = {
552
- build: vi.fn().mockReturnValue({
553
- tools: [],
554
- shutdown: vi.fn(),
555
- }),
556
- };
557
- const firstTurnMessages = [
558
- {
559
- role: "user" as const,
560
- content: [{ type: "text" as const, text: "hello" }],
561
- },
562
- {
563
- role: "assistant" as const,
564
- content: [{ type: "text" as const, text: "world" }],
565
- },
566
- ];
567
- const secondTurnMessages = [
568
- ...firstTurnMessages,
569
- {
570
- role: "user" as const,
571
- content: [{ type: "text" as const, text: "again" }],
572
- },
573
- {
574
- role: "assistant" as const,
575
- content: [{ type: "text" as const, text: "still here" }],
576
- },
577
- ];
578
- const run = vi.fn().mockResolvedValue(
579
- createResult({
580
- usage: {
581
- inputTokens: 33,
582
- outputTokens: 12,
583
- cacheReadTokens: 4,
584
- cacheWriteTokens: 1,
585
- totalCost: 0.42,
586
- },
587
- model: {
588
- id: "claude-sonnet-4-6",
589
- provider: "anthropic",
590
- },
591
- endedAt: new Date("2026-01-01T00:00:02.000Z"),
592
- messages: firstTurnMessages,
593
- }),
594
- );
595
- const continueFn = vi.fn().mockResolvedValue(
596
- createResult({
597
- usage: {
598
- inputTokens: 10,
599
- outputTokens: 5,
600
- cacheReadTokens: 2,
601
- cacheWriteTokens: 0,
602
- totalCost: 0.12,
603
- },
604
- model: {
605
- id: "claude-sonnet-4-6",
606
- provider: "anthropic",
607
- },
608
- endedAt: new Date("2026-01-01T00:00:03.000Z"),
609
- messages: secondTurnMessages,
610
- }),
611
- );
612
- const agent = {
613
- run,
614
- continue: continueFn,
615
- abort: vi.fn(),
616
- shutdown: vi.fn().mockResolvedValue(undefined),
617
- restore: vi.fn(),
618
- getMessages: vi.fn().mockReturnValue([]),
619
- messages: [],
620
- };
621
- const sessionService = {
622
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
623
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
624
- manifestPath: "/tmp/manifest-meta-multi.json",
625
- transcriptPath: "/tmp/transcript-meta-multi.log",
626
- hookPath: "/tmp/hook-meta-multi.log",
627
- messagesPath: "/tmp/messages-meta-multi.json",
628
- manifest,
629
- }),
630
- persistSessionMessages,
631
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
632
- writeSessionManifest: vi.fn(),
633
- listSessions: vi.fn().mockResolvedValue([]),
634
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
635
- };
636
- const manager = new DefaultSessionManager({
637
- distinctId,
638
- sessionService: sessionService as never,
639
- runtimeBuilder,
640
- createAgent: () => agent as never,
641
- });
642
-
643
- await manager.start({
644
- config: createConfig({
645
- sessionId,
646
- providerId: "anthropic",
647
- modelId: "claude-sonnet-4-6",
648
- }),
649
- interactive: true,
650
- });
651
-
652
- await manager.send({ sessionId, prompt: "hello" });
653
- await manager.send({ sessionId, prompt: "again" });
654
-
655
- const persisted = persistSessionMessages.mock.calls[1]?.[1];
656
- expect(persisted?.[1]).toMatchObject({
657
- role: "assistant",
658
- metrics: {
659
- inputTokens: 33,
660
- outputTokens: 12,
661
- cacheReadTokens: 4,
662
- cacheWriteTokens: 1,
663
- cost: 0.42,
664
- },
665
- });
666
- expect(persisted?.[3]).toMatchObject({
667
- role: "assistant",
668
- metrics: {
669
- inputTokens: 10,
670
- outputTokens: 5,
671
- cacheReadTokens: 2,
672
- cacheWriteTokens: 0,
673
- cost: 0.12,
674
- },
675
- });
676
- });
677
-
678
- it("persists rendered messages when a turn fails", async () => {
679
- const sessionId = "sess-failed-turn";
680
- const manifest = createManifest(sessionId);
681
- const persistSessionMessages = vi.fn();
682
- const sessionService = {
683
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
684
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
685
- manifestPath: "/tmp/manifest-failed-turn.json",
686
- transcriptPath: "/tmp/transcript-failed-turn.log",
687
- hookPath: "/tmp/hook-failed-turn.log",
688
- messagesPath: "/tmp/messages-failed-turn.json",
689
- manifest,
690
- }),
691
- persistSessionMessages,
692
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
693
- writeSessionManifest: vi.fn(),
694
- listSessions: vi.fn().mockResolvedValue([]),
695
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
696
- };
697
- const runtimeBuilder = {
698
- build: vi.fn().mockReturnValue({
699
- tools: [],
700
- shutdown: vi.fn(),
701
- }),
702
- };
703
- const renderedMessages = [
704
- { role: "user", content: [{ type: "text", text: "hello" }] },
705
- { role: "assistant", content: [{ type: "text", text: "partial" }] },
706
- ];
707
- const manager = new DefaultSessionManager({
708
- distinctId,
709
- sessionService: sessionService as never,
710
- runtimeBuilder,
711
- createAgent: () =>
712
- ({
713
- run: vi.fn().mockRejectedValue(new Error("boom")),
714
- continue: vi.fn(),
715
- abort: vi.fn(),
716
- restore: vi.fn(),
717
- shutdown: vi.fn().mockResolvedValue(undefined),
718
- getMessages: vi
719
- .fn()
720
- .mockReturnValueOnce([])
721
- .mockReturnValue(renderedMessages),
722
- messages: [],
723
- }) as never,
724
- });
725
-
726
- await expect(
727
- manager.start({
728
- config: createConfig({ sessionId }),
729
- prompt: "hello",
730
- interactive: false,
731
- }),
732
- ).rejects.toThrow("boom");
733
-
734
- expect(persistSessionMessages).toHaveBeenCalledTimes(1);
735
- expect(persistSessionMessages).toHaveBeenCalledWith(
736
- sessionId,
737
- renderedMessages,
738
- "You are a test agent",
739
- );
740
- expect(sessionService.updateSessionStatus).toHaveBeenCalledWith(
741
- sessionId,
742
- "failed",
743
- 1,
744
- );
745
- });
746
-
747
- it("uses run for first send then continue for subsequent sends", async () => {
748
- const sessionId = "sess-2";
749
- const manifest = createManifest(sessionId);
750
- const sessionService = {
751
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
752
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
753
- manifestPath: "/tmp/manifest-2.json",
754
- transcriptPath: "/tmp/transcript-2.log",
755
- hookPath: "/tmp/hook-2.log",
756
- messagesPath: "/tmp/messages-2.json",
757
- manifest,
758
- }),
759
- persistSessionMessages: vi.fn(),
760
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
761
- writeSessionManifest: vi.fn(),
762
- listSessions: vi.fn().mockResolvedValue([]),
763
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
764
- };
765
- const runtimeBuilder = {
766
- build: vi.fn().mockReturnValue({
767
- tools: [],
768
- shutdown: vi.fn(),
769
- }),
770
- };
771
- const run = vi.fn().mockResolvedValue(createResult({ text: "first" }));
772
- const continueFn = vi
773
- .fn()
774
- .mockResolvedValue(createResult({ text: "second" }));
775
- const manager = new DefaultSessionManager({
776
- distinctId,
777
- sessionService: sessionService as never,
778
- runtimeBuilder,
779
- createAgent: () =>
780
- ({
781
- run,
782
- continue: continueFn,
783
- abort: vi.fn(),
784
- shutdown: vi.fn().mockResolvedValue(undefined),
785
- getMessages: vi.fn().mockReturnValue([]),
786
- messages: [],
787
- }) as never,
788
- });
789
-
790
- await manager.start({
791
- config: createConfig({ sessionId }),
792
- interactive: true,
793
- });
794
- const first = await manager.send({ sessionId, prompt: "first" });
795
- const second = await manager.send({ sessionId, prompt: "second" });
796
-
797
- expect(first?.text).toBe("first");
798
- expect(second?.text).toBe("second");
799
- expect(run).toHaveBeenCalledTimes(1);
800
- expect(continueFn).toHaveBeenCalledTimes(1);
801
- expect(sessionService.persistSessionMessages).toHaveBeenCalledTimes(2);
802
- });
803
-
804
- it("tracks accumulated usage per session across turns", async () => {
805
- const sessionId = "sess-usage";
806
- const manifest = createManifest(sessionId);
807
- const sessionService = {
808
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
809
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
810
- manifestPath: "/tmp/manifest-usage.json",
811
- transcriptPath: "/tmp/transcript-usage.log",
812
- hookPath: "/tmp/hook-usage.log",
813
- messagesPath: "/tmp/messages-usage.json",
814
- manifest,
815
- }),
816
- persistSessionMessages: vi.fn(),
817
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
818
- writeSessionManifest: vi.fn(),
819
- listSessions: vi.fn().mockResolvedValue([]),
820
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
821
- };
822
- const runtimeBuilder = {
823
- build: vi.fn().mockReturnValue({
824
- tools: [],
825
- shutdown: vi.fn(),
826
- }),
827
- };
828
- const run = vi.fn().mockResolvedValue(
829
- createResult({
830
- text: "first",
831
- usage: {
832
- inputTokens: 10,
833
- outputTokens: 3,
834
- cacheReadTokens: 1,
835
- cacheWriteTokens: 2,
836
- totalCost: 0.11,
837
- },
838
- }),
839
- );
840
- const continueFn = vi.fn().mockResolvedValue(
841
- createResult({
842
- text: "second",
843
- usage: {
844
- inputTokens: 8,
845
- outputTokens: 4,
846
- cacheReadTokens: 2,
847
- cacheWriteTokens: 0,
848
- totalCost: 0.09,
849
- },
850
- }),
851
- );
852
- const manager = new DefaultSessionManager({
853
- distinctId,
854
- sessionService: sessionService as never,
855
- runtimeBuilder,
856
- createAgent: () =>
857
- ({
858
- run,
859
- continue: continueFn,
860
- abort: vi.fn(),
861
- shutdown: vi.fn().mockResolvedValue(undefined),
862
- getMessages: vi.fn().mockReturnValue([]),
863
- messages: [],
864
- }) as never,
865
- });
866
-
867
- await manager.start({
868
- config: createConfig({ sessionId }),
869
- interactive: true,
870
- });
871
-
872
- await manager.send({ sessionId, prompt: "first" });
873
- expect(await manager.getAccumulatedUsage(sessionId)).toEqual({
874
- inputTokens: 10,
875
- outputTokens: 3,
876
- cacheReadTokens: 1,
877
- cacheWriteTokens: 2,
878
- totalCost: 0.11,
879
- });
880
-
881
- await manager.send({ sessionId, prompt: "second" });
882
- expect(await manager.getAccumulatedUsage(sessionId)).toEqual({
883
- inputTokens: 18,
884
- outputTokens: 7,
885
- cacheReadTokens: 3,
886
- cacheWriteTokens: 2,
887
- totalCost: 0.2,
888
- });
889
- });
890
-
891
- it("queues sends with explicit queue or steer delivery and emits snapshots", async () => {
892
- const sessionId = "sess-delivery-queue";
893
- const manifest = createManifest(sessionId);
894
- const sessionService = {
895
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
896
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
897
- manifestPath: "/tmp/manifest-queue.json",
898
- transcriptPath: "/tmp/transcript-queue.log",
899
- hookPath: "/tmp/hook-queue.log",
900
- messagesPath: "/tmp/messages-queue.json",
901
- manifest,
902
- }),
903
- persistSessionMessages: vi.fn(),
904
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
905
- writeSessionManifest: vi.fn(),
906
- listSessions: vi.fn().mockResolvedValue([]),
907
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
908
- };
909
- const runtimeBuilder = {
910
- build: vi.fn().mockReturnValue({
911
- tools: [],
912
- shutdown: vi.fn(),
913
- }),
914
- };
915
- let canStartRun = false;
916
- const run = vi.fn().mockResolvedValue(createResult({ text: "first" }));
917
- const continueFn = vi
918
- .fn()
919
- .mockResolvedValue(createResult({ text: "next" }));
920
- const manager = new DefaultSessionManager({
921
- distinctId,
922
- sessionService: sessionService as never,
923
- runtimeBuilder,
924
- createAgent: () =>
925
- ({
926
- run,
927
- continue: continueFn,
928
- canStartRun: vi.fn(() => canStartRun),
929
- abort: vi.fn(),
930
- shutdown: vi.fn().mockResolvedValue(undefined),
931
- getMessages: vi.fn().mockReturnValue([]),
932
- messages: [],
933
- }) as never,
934
- });
935
- const events: Array<unknown> = [];
936
- manager.subscribe((event) => {
937
- events.push(event);
938
- });
939
-
940
- await manager.start({
941
- config: createConfig({ sessionId }),
942
- interactive: true,
943
- });
944
-
945
- await expect(
946
- manager.send({ sessionId, prompt: "queued first", delivery: "queue" }),
947
- ).resolves.toBeUndefined();
948
- await expect(
949
- manager.send({ sessionId, prompt: "queued second", delivery: "steer" }),
950
- ).resolves.toBeUndefined();
951
-
952
- expect(run).not.toHaveBeenCalled();
953
- expect(continueFn).not.toHaveBeenCalled();
954
- const promptSnapshots = events
955
- .filter((event) => {
956
- return (
957
- typeof event === "object" &&
958
- event !== null &&
959
- "type" in event &&
960
- event.type === "pending_prompts"
961
- );
962
- })
963
- .map((event) => (event as { payload: { prompts: unknown[] } }).payload);
964
- expect(promptSnapshots.at(-1)).toEqual({
965
- prompts: [
966
- expect.objectContaining({
967
- prompt: "queued second",
968
- delivery: "steer",
969
- attachmentCount: 0,
970
- }),
971
- expect.objectContaining({
972
- prompt: "queued first",
973
- delivery: "queue",
974
- attachmentCount: 0,
975
- }),
976
- ],
977
- sessionId,
978
- });
979
-
980
- canStartRun = true;
981
- await manager.send({ sessionId, prompt: "run now" });
982
- expect(run).toHaveBeenCalledTimes(1);
983
- expect(
984
- events.some((event) => {
985
- return (
986
- typeof event === "object" &&
987
- event !== null &&
988
- "type" in event &&
989
- event.type === "pending_prompt_submitted" &&
990
- "payload" in event &&
991
- (event.payload as { prompt?: string }).prompt === "queued second"
992
- );
993
- }),
994
- ).toBe(true);
995
- });
996
-
997
- it("returns undefined accumulated usage for unknown sessions", async () => {
998
- const manager = new DefaultSessionManager({
999
- distinctId,
1000
- sessionService: {
1001
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1002
- listSessions: vi.fn().mockResolvedValue([]),
1003
- deleteSession: vi.fn().mockResolvedValue({ deleted: false }),
1004
- } as never,
1005
- runtimeBuilder: {
1006
- build: vi.fn().mockReturnValue({
1007
- tools: [],
1008
- shutdown: vi.fn(),
1009
- }),
1010
- },
1011
- createAgent: () =>
1012
- ({
1013
- run: vi.fn(),
1014
- continue: vi.fn(),
1015
- abort: vi.fn(),
1016
- shutdown: vi.fn().mockResolvedValue(undefined),
1017
- getMessages: vi.fn().mockReturnValue([]),
1018
- messages: [],
1019
- }) as never,
1020
- });
1021
-
1022
- expect(
1023
- await manager.getAccumulatedUsage("missing-session"),
1024
- ).toBeUndefined();
1025
- });
1026
-
1027
- it("marks a failed single-run session as failed when run throws", async () => {
1028
- const sessionId = "sess-fail";
1029
- const manifest = createManifest(sessionId);
1030
- const sessionService = {
1031
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1032
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1033
- manifestPath: "/tmp/manifest-fail.json",
1034
- transcriptPath: "/tmp/transcript-fail.log",
1035
- hookPath: "/tmp/hook-fail.log",
1036
- messagesPath: "/tmp/messages-fail.json",
1037
- manifest,
1038
- }),
1039
- persistSessionMessages: vi.fn(),
1040
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1041
- writeSessionManifest: vi.fn(),
1042
- listSessions: vi.fn().mockResolvedValue([]),
1043
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1044
- };
1045
- const runtimeShutdown = vi.fn();
1046
- const runtimeBuilder = {
1047
- build: vi.fn().mockReturnValue({
1048
- tools: [],
1049
- shutdown: runtimeShutdown,
1050
- }),
1051
- };
1052
- const run = vi.fn().mockRejectedValue(new Error("run failed"));
1053
- const agentShutdown = vi.fn().mockResolvedValue(undefined);
1054
- const manager = new DefaultSessionManager({
1055
- distinctId,
1056
- sessionService: sessionService as never,
1057
- runtimeBuilder,
1058
- createAgent: () =>
1059
- ({
1060
- run,
1061
- continue: vi.fn(),
1062
- abort: vi.fn(),
1063
- shutdown: agentShutdown,
1064
- getMessages: vi.fn().mockReturnValue([]),
1065
- messages: [],
1066
- }) as never,
1067
- });
1068
-
1069
- await expect(
1070
- manager.start({
1071
- config: createConfig({ sessionId }),
1072
- prompt: "hello",
1073
- interactive: false,
1074
- }),
1075
- ).rejects.toThrow("run failed");
1076
- expect(sessionService.updateSessionStatus).toHaveBeenCalledWith(
1077
- sessionId,
1078
- "failed",
1079
- 1,
1080
- );
1081
- expect(agentShutdown).toHaveBeenCalledTimes(1);
1082
- expect(runtimeShutdown).toHaveBeenCalledTimes(1);
1083
- });
1084
-
1085
- it("does not persist or emit shutdown hooks when no prompt was submitted", async () => {
1086
- const sessionService = {
1087
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1088
- createRootSessionWithArtifacts: vi.fn(),
1089
- persistSessionMessages: vi.fn(),
1090
- updateSessionStatus: vi.fn(),
1091
- writeSessionManifest: vi.fn(),
1092
- listSessions: vi.fn().mockResolvedValue([]),
1093
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1094
- };
1095
- const runtimeShutdown = vi.fn();
1096
- const runtimeBuilder = {
1097
- build: vi.fn().mockReturnValue({
1098
- tools: [],
1099
- shutdown: runtimeShutdown,
1100
- }),
1101
- };
1102
- const agentShutdown = vi.fn().mockResolvedValue(undefined);
1103
- const manager = new DefaultSessionManager({
1104
- distinctId,
1105
- sessionService: sessionService as never,
1106
- runtimeBuilder,
1107
- createAgent: () =>
1108
- ({
1109
- run: vi.fn(),
1110
- continue: vi.fn(),
1111
- abort: vi.fn(),
1112
- shutdown: agentShutdown,
1113
- getMessages: vi.fn().mockReturnValue([]),
1114
- messages: [],
1115
- }) as never,
1116
- });
1117
-
1118
- const started = await manager.start({
1119
- config: createConfig({ sessionId: "sess-no-prompt" }),
1120
- interactive: true,
1121
- });
1122
- await manager.stop(started.sessionId);
1123
-
1124
- expect(
1125
- sessionService.createRootSessionWithArtifacts,
1126
- ).not.toHaveBeenCalled();
1127
- expect(sessionService.updateSessionStatus).not.toHaveBeenCalled();
1128
- expect(agentShutdown).not.toHaveBeenCalled();
1129
- expect(runtimeShutdown).toHaveBeenCalledTimes(1);
1130
- });
1131
-
1132
- it("updates agent connection with refreshed OAuth key before turn", async () => {
1133
- const sessionId = "sess-oauth";
1134
- const manifest = createManifest(sessionId);
1135
- const sessionService = {
1136
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1137
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1138
- manifestPath: "/tmp/manifest-oauth.json",
1139
- transcriptPath: "/tmp/transcript-oauth.log",
1140
- hookPath: "/tmp/hook-oauth.log",
1141
- messagesPath: "/tmp/messages-oauth.json",
1142
- manifest,
1143
- }),
1144
- persistSessionMessages: vi.fn(),
1145
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1146
- writeSessionManifest: vi.fn(),
1147
- listSessions: vi.fn().mockResolvedValue([]),
1148
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1149
- };
1150
- const runtimeBuilder = {
1151
- build: vi.fn().mockReturnValue({
1152
- tools: [],
1153
- shutdown: vi.fn(),
1154
- }),
1155
- };
1156
- const run = vi.fn().mockResolvedValue(createResult({ text: "ok" }));
1157
- const updateConnection = vi.fn();
1158
- const manager = new DefaultSessionManager({
1159
- distinctId,
1160
- sessionService: sessionService as never,
1161
- runtimeBuilder,
1162
- oauthTokenManager: {
1163
- resolveProviderApiKey: vi.fn().mockResolvedValue({
1164
- providerId: "openai-codex",
1165
- apiKey: "oauth-access-new",
1166
- refreshed: true,
1167
- }),
1168
- } as never,
1169
- createAgent: () =>
1170
- ({
1171
- run,
1172
- continue: vi.fn(),
1173
- abort: vi.fn(),
1174
- restore: vi.fn(),
1175
- updateConnection,
1176
- shutdown: vi.fn().mockResolvedValue(undefined),
1177
- getMessages: vi.fn().mockReturnValue([]),
1178
- messages: [],
1179
- }) as never,
1180
- });
1181
-
1182
- await manager.start({
1183
- config: createConfig({
1184
- sessionId,
1185
- providerId: "openai-codex",
1186
- apiKey: "oauth-access-old",
1187
- }),
1188
- interactive: true,
1189
- });
1190
- await manager.send({ sessionId, prompt: "hello" });
1191
-
1192
- expect(updateConnection).toHaveBeenCalledWith({
1193
- apiKey: "oauth-access-new",
1194
- });
1195
- expect(run).toHaveBeenCalledTimes(1);
1196
- });
1197
-
1198
- it("hydrates provider-specific config from provider settings", async () => {
1199
- const sessionId = "sess-provider-config";
1200
- const manifest = createManifest(sessionId);
1201
- const sessionService = {
1202
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1203
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1204
- manifestPath: "/tmp/manifest-provider-config.json",
1205
- transcriptPath: "/tmp/transcript-provider-config.log",
1206
- hookPath: "/tmp/hook-provider-config.log",
1207
- messagesPath: "/tmp/messages-provider-config.json",
1208
- manifest,
1209
- }),
1210
- persistSessionMessages: vi.fn(),
1211
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1212
- writeSessionManifest: vi.fn(),
1213
- listSessions: vi.fn().mockResolvedValue([]),
1214
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1215
- };
1216
- const run = vi.fn().mockResolvedValue(
1217
- createResult({
1218
- model: {
1219
- id: "claude-sonnet-4@20250514",
1220
- provider: "vertex",
1221
- },
1222
- }),
1223
- );
1224
- const createAgent = vi.fn().mockReturnValue({
1225
- run,
1226
- continue: vi.fn(),
1227
- abort: vi.fn(),
1228
- restore: vi.fn(),
1229
- shutdown: vi.fn().mockResolvedValue(undefined),
1230
- getMessages: vi.fn().mockReturnValue([]),
1231
- messages: [],
1232
- });
1233
- const manager = new DefaultSessionManager({
1234
- distinctId,
1235
- sessionService: sessionService as never,
1236
- runtimeBuilder: {
1237
- build: vi.fn().mockReturnValue({
1238
- tools: [],
1239
- shutdown: vi.fn(),
1240
- }),
1241
- },
1242
- createAgent: createAgent as never,
1243
- providerSettingsManager: {
1244
- getProviderSettings: vi.fn().mockReturnValue({
1245
- provider: "vertex",
1246
- gcp: {
1247
- projectId: "test-project",
1248
- region: "us-central1",
1249
- },
1250
- }),
1251
- } as never,
1252
- });
1253
-
1254
- await manager.start({
1255
- config: createConfig({
1256
- sessionId,
1257
- providerId: "vertex",
1258
- modelId: "claude-sonnet-4@20250514",
1259
- }),
1260
- interactive: true,
1261
- });
1262
- await manager.send({ sessionId, prompt: "hello" });
1263
-
1264
- expect(createAgent).toHaveBeenCalledWith(
1265
- expect.objectContaining({
1266
- providerId: "vertex",
1267
- modelId: "claude-sonnet-4@20250514",
1268
- providerConfig: expect.objectContaining({
1269
- providerId: "vertex",
1270
- modelId: "claude-sonnet-4@20250514",
1271
- gcp: {
1272
- projectId: "test-project",
1273
- region: "us-central1",
1274
- },
1275
- }),
1276
- }),
1277
- );
1278
- });
1279
-
1280
- it("formats prompt in core and merges explicit + mention user files", async () => {
1281
- const tempCwd = mkdtempSync(join(tmpdir(), "core-session-format-"));
1282
- try {
1283
- const srcDir = join(tempCwd, "src");
1284
- const docsDir = join(tempCwd, "docs");
1285
- mkdirSync(srcDir, { recursive: true });
1286
- mkdirSync(docsDir, { recursive: true });
1287
- const mentionPath = join(srcDir, "app.ts");
1288
- const explicitPath = join(docsDir, "note.md");
1289
- writeFileSync(mentionPath, "export const v = 1;\n", "utf8");
1290
- writeFileSync(explicitPath, "note\n", "utf8");
1291
-
1292
- const sessionId = "sess-format";
1293
- const manifest = createManifest(sessionId);
1294
- const sessionService = {
1295
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1296
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1297
- manifestPath: "/tmp/manifest-format.json",
1298
- transcriptPath: "/tmp/transcript-format.log",
1299
- hookPath: "/tmp/hook-format.log",
1300
- messagesPath: "/tmp/messages-format.json",
1301
- manifest,
1302
- }),
1303
- persistSessionMessages: vi.fn(),
1304
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1305
- writeSessionManifest: vi.fn(),
1306
- listSessions: vi.fn().mockResolvedValue([]),
1307
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1308
- };
1309
- const run = vi.fn().mockResolvedValue(createResult({ text: "ok" }));
1310
- const manager = new DefaultSessionManager({
1311
- distinctId,
1312
- sessionService: sessionService as never,
1313
- runtimeBuilder: {
1314
- build: vi.fn().mockReturnValue({
1315
- tools: [],
1316
- shutdown: vi.fn(),
1317
- }),
1318
- },
1319
- createAgent: () =>
1320
- ({
1321
- run,
1322
- continue: vi.fn(),
1323
- abort: vi.fn(),
1324
- shutdown: vi.fn().mockResolvedValue(undefined),
1325
- getMessages: vi.fn().mockReturnValue([]),
1326
- messages: [],
1327
- }) as never,
1328
- });
1329
-
1330
- await manager.start({
1331
- config: createConfig({
1332
- sessionId,
1333
- cwd: join(tempCwd, "docs"),
1334
- workspaceRoot: tempCwd,
1335
- }),
1336
- interactive: true,
1337
- });
1338
- await manager.send({
1339
- sessionId,
1340
- prompt: '<user_input mode="act">explain @src/app.ts</user_input>',
1341
- userFiles: ["note.md"],
1342
- });
1343
-
1344
- expect(run).toHaveBeenCalledWith(
1345
- '<user_input mode="act">explain @src/app.ts</user_input>',
1346
- undefined,
1347
- expect.arrayContaining([mentionPath, explicitPath]),
1348
- );
1349
- } finally {
1350
- rmSync(tempCwd, { recursive: true, force: true });
1351
- }
1352
- });
1353
-
1354
- it("force refreshes and retries once when turn fails with auth error", async () => {
1355
- const sessionId = "sess-oauth-retry";
1356
- const manifest = createManifest(sessionId);
1357
- const sessionService = {
1358
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1359
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1360
- manifestPath: "/tmp/manifest-oauth-retry.json",
1361
- transcriptPath: "/tmp/transcript-oauth-retry.log",
1362
- hookPath: "/tmp/hook-oauth-retry.log",
1363
- messagesPath: "/tmp/messages-oauth-retry.json",
1364
- manifest,
1365
- }),
1366
- persistSessionMessages: vi.fn(),
1367
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1368
- writeSessionManifest: vi.fn(),
1369
- listSessions: vi.fn().mockResolvedValue([]),
1370
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1371
- };
1372
- const runtimeBuilder = {
1373
- build: vi.fn().mockReturnValue({
1374
- tools: [],
1375
- shutdown: vi.fn(),
1376
- }),
1377
- };
1378
- const run = vi
1379
- .fn()
1380
- .mockRejectedValueOnce(new Error("401 Unauthorized"))
1381
- .mockResolvedValueOnce(createResult({ text: "retried" }));
1382
- const restore = vi.fn();
1383
- const updateConnection = vi.fn();
1384
- const resolveProviderApiKey = vi
1385
- .fn()
1386
- .mockResolvedValueOnce(null)
1387
- .mockResolvedValueOnce({
1388
- providerId: "openai-codex",
1389
- apiKey: "oauth-access-new",
1390
- refreshed: true,
1391
- });
1392
- const manager = new DefaultSessionManager({
1393
- distinctId,
1394
- sessionService: sessionService as never,
1395
- runtimeBuilder,
1396
- oauthTokenManager: {
1397
- resolveProviderApiKey,
1398
- } as never,
1399
- createAgent: () =>
1400
- ({
1401
- run,
1402
- continue: vi.fn(),
1403
- abort: vi.fn(),
1404
- restore,
1405
- updateConnection,
1406
- shutdown: vi.fn().mockResolvedValue(undefined),
1407
- getMessages: vi.fn().mockReturnValue([]),
1408
- messages: [],
1409
- }) as never,
1410
- });
1411
-
1412
- await manager.start({
1413
- config: createConfig({
1414
- sessionId,
1415
- providerId: "openai-codex",
1416
- apiKey: "oauth-access-old",
1417
- }),
1418
- interactive: true,
1419
- });
1420
- const result = await manager.send({ sessionId, prompt: "hello" });
1421
-
1422
- expect(result?.text).toBe("retried");
1423
- expect(run).toHaveBeenCalledTimes(2);
1424
- expect(restore).toHaveBeenCalledTimes(1);
1425
- expect(resolveProviderApiKey).toHaveBeenNthCalledWith(1, {
1426
- providerId: "openai-codex",
1427
- forceRefresh: undefined,
1428
- });
1429
- expect(resolveProviderApiKey).toHaveBeenNthCalledWith(2, {
1430
- providerId: "openai-codex",
1431
- forceRefresh: true,
1432
- });
1433
- expect(updateConnection).toHaveBeenCalledWith({
1434
- apiKey: "oauth-access-new",
1435
- });
1436
- });
1437
-
1438
- it("auto-continues when async teammate runs complete after lead turn", async () => {
1439
- const sessionId = "sess-team-auto-continue";
1440
- const manifest = createManifest(sessionId);
1441
- const sessionService = {
1442
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1443
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1444
- manifestPath: "/tmp/manifest-team-auto-continue.json",
1445
- transcriptPath: "/tmp/transcript-team-auto-continue.log",
1446
- hookPath: "/tmp/hook-team-auto-continue.log",
1447
- messagesPath: "/tmp/messages-team-auto-continue.json",
1448
- manifest,
1449
- }),
1450
- persistSessionMessages: vi.fn(),
1451
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1452
- writeSessionManifest: vi.fn(),
1453
- listSessions: vi.fn().mockResolvedValue([]),
1454
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1455
- };
1456
-
1457
- let onTeamEvent: ((event: unknown) => void) | undefined;
1458
- const runtimeBuilder = {
1459
- build: vi
1460
- .fn()
1461
- .mockImplementation(
1462
- (input: { onTeamEvent?: (event: unknown) => void }) => {
1463
- onTeamEvent = input.onTeamEvent;
1464
- return {
1465
- tools: [],
1466
- shutdown: vi.fn(),
1467
- };
1468
- },
1469
- ),
1470
- };
1471
-
1472
- const run = vi.fn().mockImplementation(async () => {
1473
- onTeamEvent?.({
1474
- type: "run_started",
1475
- run: {
1476
- id: "run_0001",
1477
- agentId: "investigator",
1478
- status: "running",
1479
- message: "Investigate",
1480
- priority: 0,
1481
- retryCount: 0,
1482
- maxRetries: 0,
1483
- startedAt: new Date("2026-01-01T00:00:00.000Z"),
1484
- },
1485
- });
1486
- setTimeout(() => {
1487
- onTeamEvent?.({
1488
- type: "run_completed",
1489
- run: {
1490
- id: "run_0001",
1491
- agentId: "investigator",
1492
- status: "completed",
1493
- message: "Investigate",
1494
- priority: 0,
1495
- retryCount: 0,
1496
- maxRetries: 0,
1497
- startedAt: new Date("2026-01-01T00:00:00.000Z"),
1498
- endedAt: new Date("2026-01-01T00:00:02.000Z"),
1499
- result: createResult({ iterations: 3 }),
1500
- },
1501
- });
1502
- }, 0);
1503
- return createResult({ text: "lead scheduled teammate" });
1504
- });
1505
- const continueFn = vi
1506
- .fn()
1507
- .mockResolvedValue(
1508
- createResult({ text: "lead processed teammate result" }),
1509
- );
1510
- const manager = new DefaultSessionManager({
1511
- distinctId,
1512
- sessionService: sessionService as never,
1513
- runtimeBuilder,
1514
- createAgent: () =>
1515
- ({
1516
- run,
1517
- continue: continueFn,
1518
- abort: vi.fn(),
1519
- shutdown: vi.fn().mockResolvedValue(undefined),
1520
- getMessages: vi.fn().mockReturnValue([]),
1521
- messages: [],
1522
- }) as never,
1523
- });
1524
-
1525
- await manager.start({
1526
- config: createConfig({ sessionId }),
1527
- interactive: false,
1528
- });
1529
- const result = await manager.send({
1530
- sessionId,
1531
- prompt: "run teammate work",
1532
- });
1533
-
1534
- expect(result?.text).toBe("lead processed teammate result");
1535
- expect(run).toHaveBeenCalledTimes(1);
1536
- expect(continueFn).toHaveBeenCalledTimes(1);
1537
- expect(continueFn.mock.calls[0]?.[0]).toContain(
1538
- "System-delivered teammate async run updates:",
1539
- );
1540
- expect(sessionService.updateSessionStatus).toHaveBeenCalledWith(
1541
- sessionId,
1542
- "completed",
1543
- 0,
1544
- );
1545
- });
1546
-
1547
- it("persists failed teammate task messages for team-task sub-sessions", async () => {
1548
- const sessionId = "sess-team-task-failure-messages";
1549
- const manifest = createManifest(sessionId);
1550
- const sessionService = {
1551
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1552
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1553
- manifestPath: "/tmp/manifest-team-task-failure-messages.json",
1554
- transcriptPath: "/tmp/transcript-team-task-failure-messages.log",
1555
- hookPath: "/tmp/hook-team-task-failure-messages.log",
1556
- messagesPath: "/tmp/messages-team-task-failure-messages.json",
1557
- manifest,
1558
- }),
1559
- persistSessionMessages: vi.fn(),
1560
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1561
- writeSessionManifest: vi.fn(),
1562
- listSessions: vi.fn().mockResolvedValue([]),
1563
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1564
- onTeamTaskStart: vi.fn().mockResolvedValue(undefined),
1565
- onTeamTaskEnd: vi.fn().mockResolvedValue(undefined),
1566
- };
1567
-
1568
- let onTeamEvent: ((event: unknown) => void) | undefined;
1569
- const runtimeBuilder = {
1570
- build: vi
1571
- .fn()
1572
- .mockImplementation(
1573
- (input: { onTeamEvent?: (event: unknown) => void }) => {
1574
- onTeamEvent = input.onTeamEvent;
1575
- return {
1576
- tools: [],
1577
- shutdown: vi.fn(),
1578
- };
1579
- },
1580
- ),
1581
- };
1582
-
1583
- const failedMessages = [
1584
- { role: "user", content: [{ type: "text", text: "delegated prompt" }] },
1585
- { role: "assistant", content: [{ type: "text", text: "partial work" }] },
1586
- ];
1587
- const manager = new DefaultSessionManager({
1588
- distinctId,
1589
- sessionService: sessionService as never,
1590
- runtimeBuilder,
1591
- createAgent: () =>
1592
- ({
1593
- run: vi.fn().mockImplementation(async () => {
1594
- onTeamEvent?.({
1595
- type: "task_start",
1596
- agentId: "providers-investigator",
1597
- message: "Investigate provider boundaries",
1598
- });
1599
- onTeamEvent?.({
1600
- type: "task_end",
1601
- agentId: "providers-investigator",
1602
- error: new Error("401 Unauthorized"),
1603
- messages: failedMessages,
1604
- });
1605
- return createResult({ text: "lead handled failure" });
1606
- }),
1607
- continue: vi.fn(),
1608
- abort: vi.fn(),
1609
- shutdown: vi.fn().mockResolvedValue(undefined),
1610
- getMessages: vi.fn().mockReturnValue([]),
1611
- messages: [],
1612
- }) as never,
1613
- });
1614
-
1615
- await manager.start({
1616
- config: createConfig({ sessionId }),
1617
- prompt: "run teammate work",
1618
- interactive: false,
1619
- });
1620
-
1621
- expect(sessionService.onTeamTaskStart).toHaveBeenCalledTimes(1);
1622
- expect(sessionService.onTeamTaskEnd).toHaveBeenCalledWith(
1623
- sessionId,
1624
- "providers-investigator",
1625
- "failed",
1626
- "[error] 401 Unauthorized",
1627
- failedMessages,
1628
- );
1629
- });
1630
-
1631
- it("persists teammate progress updates for team-task sub-sessions", async () => {
1632
- const sessionId = "sess-team-task-progress";
1633
- const manifest = createManifest(sessionId);
1634
- const sessionService = {
1635
- ensureSessionsDir: vi.fn().mockReturnValue("/tmp/sessions"),
1636
- createRootSessionWithArtifacts: vi.fn().mockResolvedValue({
1637
- manifestPath: "/tmp/manifest-team-task-progress.json",
1638
- transcriptPath: "/tmp/transcript-team-task-progress.log",
1639
- hookPath: "/tmp/hook-team-task-progress.log",
1640
- messagesPath: "/tmp/messages-team-task-progress.json",
1641
- manifest,
1642
- }),
1643
- persistSessionMessages: vi.fn(),
1644
- updateSessionStatus: vi.fn().mockResolvedValue({ updated: true }),
1645
- writeSessionManifest: vi.fn(),
1646
- listSessions: vi.fn().mockResolvedValue([]),
1647
- deleteSession: vi.fn().mockResolvedValue({ deleted: true }),
1648
- onTeamTaskStart: vi.fn().mockResolvedValue(undefined),
1649
- onTeamTaskEnd: vi.fn().mockResolvedValue(undefined),
1650
- onTeamTaskProgress: vi.fn().mockResolvedValue(undefined),
1651
- };
1652
-
1653
- let onTeamEvent: ((event: unknown) => void) | undefined;
1654
- const runtimeBuilder = {
1655
- build: vi
1656
- .fn()
1657
- .mockImplementation(
1658
- (input: { onTeamEvent?: (event: unknown) => void }) => {
1659
- onTeamEvent = input.onTeamEvent;
1660
- return {
1661
- tools: [],
1662
- shutdown: vi.fn(),
1663
- };
1664
- },
1665
- ),
1666
- };
1667
-
1668
- const manager = new DefaultSessionManager({
1669
- distinctId,
1670
- sessionService: sessionService as never,
1671
- runtimeBuilder,
1672
- createAgent: () =>
1673
- ({
1674
- run: vi.fn().mockImplementation(async () => {
1675
- onTeamEvent?.({
1676
- type: "task_start",
1677
- agentId: "providers-investigator",
1678
- message: "Investigate provider boundaries",
1679
- });
1680
- onTeamEvent?.({
1681
- type: "run_progress",
1682
- run: {
1683
- id: "run_00002",
1684
- agentId: "providers-investigator",
1685
- status: "running",
1686
- message: "Investigate provider boundaries",
1687
- priority: 0,
1688
- retryCount: 0,
1689
- maxRetries: 0,
1690
- continueConversation: false,
1691
- startedAt: new Date("2026-01-01T00:00:00.000Z"),
1692
- lastProgressAt: new Date("2026-01-01T00:00:01.000Z"),
1693
- lastProgressMessage: "heartbeat",
1694
- currentActivity: "heartbeat",
1695
- },
1696
- message: "heartbeat",
1697
- });
1698
- onTeamEvent?.({
1699
- type: "agent_event",
1700
- agentId: "providers-investigator",
1701
- event: {
1702
- type: "content_start",
1703
- contentType: "text",
1704
- text: "Drafting the provider boundary analysis now.",
1705
- },
1706
- });
1707
- onTeamEvent?.({
1708
- type: "task_end",
1709
- agentId: "providers-investigator",
1710
- result: createResult(),
1711
- });
1712
- return createResult({ text: "lead handled progress" });
1713
- }),
1714
- continue: vi.fn(),
1715
- abort: vi.fn(),
1716
- shutdown: vi.fn().mockResolvedValue(undefined),
1717
- getMessages: vi.fn().mockReturnValue([]),
1718
- messages: [],
1719
- }) as never,
1720
- });
1721
-
1722
- await manager.start({
1723
- config: createConfig({ sessionId }),
1724
- prompt: "run teammate work",
1725
- interactive: false,
1726
- });
1727
-
1728
- expect(sessionService.onTeamTaskProgress).toHaveBeenCalledWith(
1729
- sessionId,
1730
- "providers-investigator",
1731
- "heartbeat",
1732
- { kind: "heartbeat" },
1733
- );
1734
- expect(sessionService.onTeamTaskProgress).toHaveBeenCalledWith(
1735
- sessionId,
1736
- "providers-investigator",
1737
- "Drafting the provider boundary analysis now.",
1738
- { kind: "text" },
1739
- );
1740
- });
1741
- });