repoburg 1.3.201 → 1.3.204

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 (325) hide show
  1. package/agent-pane/backend/dist/index.js +2714 -32395
  2. package/agent-pane/backend/dist/server.js +45304 -0
  3. package/askuser-after-step5-postgres-selected.png +0 -0
  4. package/askuser-after-step7-answer-sent.png +0 -0
  5. package/askuser-card-visible.png +0 -0
  6. package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.d.ts +16 -0
  7. package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js +87 -0
  8. package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js.map +1 -1
  9. package/backend/dist/src/agent-runtime/agent-runtime.types.d.ts +16 -1
  10. package/backend/dist/src/app.controller.d.ts +1 -0
  11. package/backend/dist/src/app.service.d.ts +1 -0
  12. package/backend/dist/src/app.service.js +4 -1
  13. package/backend/dist/src/app.service.js.map +1 -1
  14. package/backend/dist/src/interactive-chat/chat.service.d.ts +4 -0
  15. package/backend/dist/src/interactive-chat/chat.service.js +21 -1
  16. package/backend/dist/src/interactive-chat/chat.service.js.map +1 -1
  17. package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js +12 -1
  18. package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js.map +1 -1
  19. package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.d.ts +1 -0
  20. package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js +1 -0
  21. package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js.map +1 -1
  22. package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js +2 -0
  23. package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js.map +1 -1
  24. package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
  25. package/daemon/dist/agentPane.js +128 -0
  26. package/daemon/dist/agentPane.spec.js +183 -0
  27. package/daemon/dist/api/services.js +4 -0
  28. package/daemon/dist/index.js +28 -8
  29. package/daemon/dist/serviceManager.js +30 -11
  30. package/daemon/dist/serviceManager.spec.js +26 -0
  31. package/daemon/dist/workspace-validation.js +63 -0
  32. package/daemon/dist/workspace-validation.spec.js +63 -0
  33. package/daemon/package.json +1 -1
  34. package/package.json +15 -2
  35. package/r2-step4-apple-banana.png +0 -0
  36. package/r2-step5-after-rewind-apple-last.png +0 -0
  37. package/r2-step7-apple-cherry.png +0 -0
  38. package/r2-step8-cherry-menu-no-restore.png +0 -0
  39. package/r3-step3-live-task-card-running.png +0 -0
  40. package/r3-step4-after-completion.png +0 -0
  41. package/r3-step5-persistence-after-reload.png +0 -0
  42. package/r3-step6-tasks-tab-card.png +0 -0
  43. package/reverify-answered-state-answer-sent.png +0 -0
  44. package/reverify-card-visible-after-completion.png +0 -0
  45. package/rewind-step3-apple-banana-turns.png +0 -0
  46. package/rewind-step5-ui-unchanged-after-confirmed-rewind.png +0 -0
  47. package/rewind-step6-apple-menu-no-restore.png +0 -0
  48. package/rewind-step7-final-turnlist-unchanged.png +0 -0
  49. package/rewind-step7-negative-error-toast.png +0 -0
  50. package/rewind-step7-negative-error-toast2.png +0 -0
  51. package/subagent-r2-step5-turn-after-reload-no-card.png +0 -0
  52. package/subagent-r2-step6-tasks-panel-card.png +0 -0
  53. package/subagent-step3-tasks-tab-done-card.png +0 -0
  54. package/subagent-step4-turn-stuck-no-cards.png +0 -0
  55. package/agent-pane/.prettierignore +0 -5
  56. package/agent-pane/.prettierrc +0 -8
  57. package/agent-pane/backend/coverage/base.css +0 -224
  58. package/agent-pane/backend/coverage/block-navigation.js +0 -87
  59. package/agent-pane/backend/coverage/clover.xml +0 -887
  60. package/agent-pane/backend/coverage/coverage-final.json +0 -10
  61. package/agent-pane/backend/coverage/favicon.png +0 -0
  62. package/agent-pane/backend/coverage/index.html +0 -176
  63. package/agent-pane/backend/coverage/prettify.css +0 -1
  64. package/agent-pane/backend/coverage/prettify.js +0 -2
  65. package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
  66. package/agent-pane/backend/coverage/sorter.js +0 -210
  67. package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +0 -1402
  68. package/agent-pane/backend/coverage/src/codex/index.html +0 -131
  69. package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +0 -331
  70. package/agent-pane/backend/coverage/src/db/index.html +0 -131
  71. package/agent-pane/backend/coverage/src/db/index.ts.html +0 -436
  72. package/agent-pane/backend/coverage/src/db/schema.ts.html +0 -175
  73. package/agent-pane/backend/coverage/src/index.html +0 -116
  74. package/agent-pane/backend/coverage/src/index.ts.html +0 -262
  75. package/agent-pane/backend/coverage/src/routes/index.html +0 -146
  76. package/agent-pane/backend/coverage/src/routes/sessions.ts.html +0 -991
  77. package/agent-pane/backend/coverage/src/routes/turns.ts.html +0 -256
  78. package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +0 -406
  79. package/agent-pane/backend/coverage/src/ws/index.html +0 -116
  80. package/agent-pane/backend/coverage/src/ws/index.ts.html +0 -238
  81. package/agent-pane/backend/data.db +0 -0
  82. package/agent-pane/backend/data.db-shm +0 -0
  83. package/agent-pane/backend/data.db-wal +0 -0
  84. package/agent-pane/backend/package-lock.json +0 -5945
  85. package/agent-pane/backend/package.json +0 -33
  86. package/agent-pane/backend/scripts/backfill-from-jsonl.ts +0 -206
  87. package/agent-pane/backend/scripts/import-session-from-jsonl.ts +0 -264
  88. package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +0 -271
  89. package/agent-pane/backend/src/claude/claude-message-mapper.ts +0 -259
  90. package/agent-pane/backend/src/claude/claude-provider.test.ts +0 -455
  91. package/agent-pane/backend/src/claude/claude-provider.ts +0 -1319
  92. package/agent-pane/backend/src/claude/claude-types.ts +0 -45
  93. package/agent-pane/backend/src/codex/app-server-client.test.ts +0 -183
  94. package/agent-pane/backend/src/codex/app-server-client.ts +0 -247
  95. package/agent-pane/backend/src/codex/app-server-types.ts +0 -259
  96. package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +0 -56
  97. package/agent-pane/backend/src/codex/codex-provider.test.ts +0 -60
  98. package/agent-pane/backend/src/codex/codex-provider.ts +0 -619
  99. package/agent-pane/backend/src/codex/item-mapper.test.ts +0 -221
  100. package/agent-pane/backend/src/codex/item-mapper.ts +0 -86
  101. package/agent-pane/backend/src/codex/shared-app-server.ts +0 -25
  102. package/agent-pane/backend/src/db/index.test.ts +0 -125
  103. package/agent-pane/backend/src/db/index.ts +0 -230
  104. package/agent-pane/backend/src/db/schema.ts +0 -34
  105. package/agent-pane/backend/src/index.ts +0 -89
  106. package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +0 -29
  107. package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +0 -40
  108. package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +0 -16
  109. package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +0 -60
  110. package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +0 -34
  111. package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +0 -85
  112. package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +0 -48
  113. package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +0 -35
  114. package/agent-pane/backend/src/integration/delta-streaming.test.ts +0 -130
  115. package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +0 -272
  116. package/agent-pane/backend/src/integration/helpers/test-app.ts +0 -110
  117. package/agent-pane/backend/src/integration/helpers/ws-capture.ts +0 -64
  118. package/agent-pane/backend/src/integration/item-lifecycle.test.ts +0 -133
  119. package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +0 -102
  120. package/agent-pane/backend/src/integration/missing-item-started.test.ts +0 -91
  121. package/agent-pane/backend/src/integration/multi-turn.test.ts +0 -121
  122. package/agent-pane/backend/src/integration/session-operations.test.ts +0 -175
  123. package/agent-pane/backend/src/integration/threads-claude-fork.test.ts +0 -241
  124. package/agent-pane/backend/src/integration/threads.test.ts +0 -309
  125. package/agent-pane/backend/src/integration/title-generation.test.ts +0 -109
  126. package/agent-pane/backend/src/integration/turn-cancel.test.ts +0 -111
  127. package/agent-pane/backend/src/integration/turn-failure.test.ts +0 -97
  128. package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +0 -141
  129. package/agent-pane/backend/src/providers/index.ts +0 -16
  130. package/agent-pane/backend/src/providers/registry.test.ts +0 -92
  131. package/agent-pane/backend/src/providers/registry.ts +0 -43
  132. package/agent-pane/backend/src/providers/runtime-provider.ts +0 -43
  133. package/agent-pane/backend/src/repoburg/action-mapping.ts +0 -216
  134. package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +0 -271
  135. package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +0 -262
  136. package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +0 -271
  137. package/agent-pane/backend/src/repoburg/backend-resolver.ts +0 -246
  138. package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +0 -159
  139. package/agent-pane/backend/src/repoburg/repoburg-client.ts +0 -181
  140. package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +0 -723
  141. package/agent-pane/backend/src/repoburg/repoburg-provider.ts +0 -422
  142. package/agent-pane/backend/src/repoburg/turn-projector.test.ts +0 -475
  143. package/agent-pane/backend/src/repoburg/turn-projector.ts +0 -537
  144. package/agent-pane/backend/src/routes/files.ts +0 -31
  145. package/agent-pane/backend/src/routes/mcp.ts +0 -33
  146. package/agent-pane/backend/src/routes/models.test.ts +0 -92
  147. package/agent-pane/backend/src/routes/models.ts +0 -16
  148. package/agent-pane/backend/src/routes/plugins.ts +0 -40
  149. package/agent-pane/backend/src/routes/providers.test.ts +0 -86
  150. package/agent-pane/backend/src/routes/providers.ts +0 -86
  151. package/agent-pane/backend/src/routes/sessions.test.ts +0 -558
  152. package/agent-pane/backend/src/routes/sessions.ts +0 -504
  153. package/agent-pane/backend/src/routes/skills.test.ts +0 -114
  154. package/agent-pane/backend/src/routes/skills.ts +0 -70
  155. package/agent-pane/backend/src/routes/threads.ts +0 -323
  156. package/agent-pane/backend/src/routes/turns.test.ts +0 -196
  157. package/agent-pane/backend/src/routes/turns.ts +0 -103
  158. package/agent-pane/backend/src/routes/workspaces.test.ts +0 -197
  159. package/agent-pane/backend/src/routes/workspaces.ts +0 -128
  160. package/agent-pane/backend/src/ws/index.test.ts +0 -107
  161. package/agent-pane/backend/src/ws/index.ts +0 -51
  162. package/agent-pane/backend/tsconfig.json +0 -25
  163. package/agent-pane/backend/vitest.config.ts +0 -20
  164. package/agent-pane/eslint.config.js +0 -33
  165. package/agent-pane/frontend/coverage/base.css +0 -224
  166. package/agent-pane/frontend/coverage/block-navigation.js +0 -87
  167. package/agent-pane/frontend/coverage/clover.xml +0 -2962
  168. package/agent-pane/frontend/coverage/coverage-final.json +0 -37
  169. package/agent-pane/frontend/coverage/favicon.png +0 -0
  170. package/agent-pane/frontend/coverage/index.html +0 -251
  171. package/agent-pane/frontend/coverage/prettify.css +0 -1
  172. package/agent-pane/frontend/coverage/prettify.js +0 -2
  173. package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
  174. package/agent-pane/frontend/coverage/sorter.js +0 -210
  175. package/agent-pane/frontend/coverage/src/App.tsx.html +0 -217
  176. package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +0 -223
  177. package/agent-pane/frontend/coverage/src/api/client.ts.html +0 -187
  178. package/agent-pane/frontend/coverage/src/api/index.html +0 -161
  179. package/agent-pane/frontend/coverage/src/api/sessions.ts.html +0 -316
  180. package/agent-pane/frontend/coverage/src/api/websocket.ts.html +0 -544
  181. package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +0 -199
  182. package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +0 -226
  183. package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +0 -382
  184. package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +0 -715
  185. package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +0 -160
  186. package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +0 -478
  187. package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +0 -472
  188. package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +0 -574
  189. package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +0 -205
  190. package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +0 -133
  191. package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +0 -217
  192. package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +0 -271
  193. package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +0 -109
  194. package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +0 -313
  195. package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +0 -1216
  196. package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +0 -1138
  197. package/agent-pane/frontend/coverage/src/components/index.html +0 -326
  198. package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +0 -247
  199. package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +0 -160
  200. package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +0 -427
  201. package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +0 -397
  202. package/agent-pane/frontend/coverage/src/components/panels/index.html +0 -161
  203. package/agent-pane/frontend/coverage/src/constants/index.html +0 -116
  204. package/agent-pane/frontend/coverage/src/constants/models.ts.html +0 -406
  205. package/agent-pane/frontend/coverage/src/hooks/index.html +0 -146
  206. package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +0 -124
  207. package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +0 -142
  208. package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +0 -187
  209. package/agent-pane/frontend/coverage/src/index.html +0 -146
  210. package/agent-pane/frontend/coverage/src/main.tsx.html +0 -115
  211. package/agent-pane/frontend/coverage/src/store/index.html +0 -116
  212. package/agent-pane/frontend/coverage/src/store/store.ts.html +0 -2404
  213. package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +0 -190
  214. package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +0 -367
  215. package/agent-pane/frontend/coverage/src/test/index.html +0 -131
  216. package/agent-pane/frontend/coverage/src/types/index.html +0 -116
  217. package/agent-pane/frontend/coverage/src/types/index.ts.html +0 -466
  218. package/agent-pane/frontend/coverage/src/utils/changes.ts.html +0 -202
  219. package/agent-pane/frontend/coverage/src/utils/duration.ts.html +0 -127
  220. package/agent-pane/frontend/coverage/src/utils/index.html +0 -131
  221. package/agent-pane/frontend/dist/assets/index-34WaZYsW.css +0 -1
  222. package/agent-pane/frontend/dist/assets/index-y7xlT86e.js +0 -362
  223. package/agent-pane/frontend/dist/index.html +0 -19
  224. package/agent-pane/frontend/index.html +0 -18
  225. package/agent-pane/frontend/package-lock.json +0 -4211
  226. package/agent-pane/frontend/package.json +0 -35
  227. package/agent-pane/frontend/src/App.test.tsx +0 -74
  228. package/agent-pane/frontend/src/App.tsx +0 -44
  229. package/agent-pane/frontend/src/AppInitializer.test.tsx +0 -142
  230. package/agent-pane/frontend/src/AppInitializer.tsx +0 -46
  231. package/agent-pane/frontend/src/__tests__/ThreadsPanel.test.tsx +0 -192
  232. package/agent-pane/frontend/src/api/client.test.ts +0 -126
  233. package/agent-pane/frontend/src/api/client.ts +0 -34
  234. package/agent-pane/frontend/src/api/models.ts +0 -7
  235. package/agent-pane/frontend/src/api/providers.ts +0 -17
  236. package/agent-pane/frontend/src/api/sessions.test.ts +0 -245
  237. package/agent-pane/frontend/src/api/sessions.ts +0 -97
  238. package/agent-pane/frontend/src/api/skills.ts +0 -12
  239. package/agent-pane/frontend/src/api/websocket.test.ts +0 -288
  240. package/agent-pane/frontend/src/api/websocket.ts +0 -238
  241. package/agent-pane/frontend/src/api/workspaces.test.ts +0 -112
  242. package/agent-pane/frontend/src/api/workspaces.ts +0 -38
  243. package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +0 -49
  244. package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +0 -56
  245. package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +0 -133
  246. package/agent-pane/frontend/src/components/CommandInputBar.tsx +0 -584
  247. package/agent-pane/frontend/src/components/CommandPalette.test.tsx +0 -226
  248. package/agent-pane/frontend/src/components/CommandPalette.tsx +0 -194
  249. package/agent-pane/frontend/src/components/CommentableText.test.tsx +0 -196
  250. package/agent-pane/frontend/src/components/CommentableText.tsx +0 -379
  251. package/agent-pane/frontend/src/components/IdeWorkspace.test.tsx +0 -39
  252. package/agent-pane/frontend/src/components/IdeWorkspace.tsx +0 -51
  253. package/agent-pane/frontend/src/components/ModelSelector.test.tsx +0 -129
  254. package/agent-pane/frontend/src/components/ModelSelector.tsx +0 -191
  255. package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +0 -95
  256. package/agent-pane/frontend/src/components/NewSessionDialog.tsx +0 -129
  257. package/agent-pane/frontend/src/components/RenderArea.test.tsx +0 -160
  258. package/agent-pane/frontend/src/components/RenderArea.tsx +0 -170
  259. package/agent-pane/frontend/src/components/ResizeHandle.tsx +0 -29
  260. package/agent-pane/frontend/src/components/RightPanel.test.tsx +0 -43
  261. package/agent-pane/frontend/src/components/RightPanel.tsx +0 -46
  262. package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +0 -32
  263. package/agent-pane/frontend/src/components/RuntimeIcon.tsx +0 -16
  264. package/agent-pane/frontend/src/components/SessionTabs.test.tsx +0 -72
  265. package/agent-pane/frontend/src/components/SessionTabs.tsx +0 -49
  266. package/agent-pane/frontend/src/components/StatusBar.test.tsx +0 -56
  267. package/agent-pane/frontend/src/components/StatusBar.tsx +0 -129
  268. package/agent-pane/frontend/src/components/StatusDot.test.tsx +0 -25
  269. package/agent-pane/frontend/src/components/StatusDot.tsx +0 -8
  270. package/agent-pane/frontend/src/components/Toast.test.tsx +0 -79
  271. package/agent-pane/frontend/src/components/Toast.tsx +0 -76
  272. package/agent-pane/frontend/src/components/TurnContent.test.tsx +0 -298
  273. package/agent-pane/frontend/src/components/TurnContent.tsx +0 -548
  274. package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +0 -297
  275. package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +0 -351
  276. package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +0 -32
  277. package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +0 -63
  278. package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +0 -78
  279. package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +0 -96
  280. package/agent-pane/frontend/src/components/input/SlashPopup.tsx +0 -82
  281. package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +0 -61
  282. package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +0 -54
  283. package/agent-pane/frontend/src/components/panels/TasksPanel.test.tsx +0 -97
  284. package/agent-pane/frontend/src/components/panels/TasksPanel.tsx +0 -208
  285. package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +0 -382
  286. package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +0 -83
  287. package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +0 -126
  288. package/agent-pane/frontend/src/constants/models.test.ts +0 -97
  289. package/agent-pane/frontend/src/constants/models.ts +0 -166
  290. package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +0 -42
  291. package/agent-pane/frontend/src/hooks/useEscapeKey.ts +0 -13
  292. package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +0 -92
  293. package/agent-pane/frontend/src/hooks/useOutsideClick.ts +0 -19
  294. package/agent-pane/frontend/src/hooks/useProvider.ts +0 -44
  295. package/agent-pane/frontend/src/hooks/useResizable.test.ts +0 -97
  296. package/agent-pane/frontend/src/hooks/useResizable.ts +0 -75
  297. package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +0 -116
  298. package/agent-pane/frontend/src/hooks/useSessionStatus.ts +0 -34
  299. package/agent-pane/frontend/src/main.test.tsx +0 -37
  300. package/agent-pane/frontend/src/main.tsx +0 -10
  301. package/agent-pane/frontend/src/store/sessionSlice.ts +0 -440
  302. package/agent-pane/frontend/src/store/store.test.ts +0 -1319
  303. package/agent-pane/frontend/src/store/store.ts +0 -37
  304. package/agent-pane/frontend/src/store/threadSlice.ts +0 -136
  305. package/agent-pane/frontend/src/store/uiSlice.ts +0 -133
  306. package/agent-pane/frontend/src/store/workspaceSlice.ts +0 -77
  307. package/agent-pane/frontend/src/store/wsSlice.ts +0 -361
  308. package/agent-pane/frontend/src/styles/app.css +0 -611
  309. package/agent-pane/frontend/src/styles/tokens.css +0 -62
  310. package/agent-pane/frontend/src/test/component-helpers.ts +0 -38
  311. package/agent-pane/frontend/src/test/fixtures.ts +0 -100
  312. package/agent-pane/frontend/src/test-setup.ts +0 -5
  313. package/agent-pane/frontend/src/types/index.test.ts +0 -128
  314. package/agent-pane/frontend/src/types/index.ts +0 -132
  315. package/agent-pane/frontend/src/utils/changes.test.ts +0 -81
  316. package/agent-pane/frontend/src/utils/changes.ts +0 -39
  317. package/agent-pane/frontend/src/utils/duration.test.ts +0 -75
  318. package/agent-pane/frontend/src/utils/duration.ts +0 -14
  319. package/agent-pane/frontend/tsconfig.json +0 -25
  320. package/agent-pane/frontend/vite.config.ts +0 -22
  321. package/agent-pane/frontend/vitest.config.ts +0 -29
  322. package/agent-pane/package-lock.json +0 -1527
  323. package/agent-pane/package.json +0 -19
  324. package/agent-pane/shared/provider-types.ts +0 -67
  325. package/agent-pane/shared/types.ts +0 -167
@@ -1,723 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
-
3
- // Mock the HTTP client class — the provider must only talk to the interface.
4
- vi.mock('./repoburg-client.ts', () => ({
5
- RepoburgHttpClient: vi.fn(),
6
- }));
7
-
8
- // Mock the daemon resolver — lifecycle logic has its own test file.
9
- vi.mock('./backend-resolver.ts', () => ({
10
- resolveBackendUrl: vi.fn(),
11
- invalidateBackend: vi.fn(),
12
- }));
13
-
14
- // Capture SSE stream invocations — deliver agent-runtime messages manually
15
- // and resolve the stream promise to simulate stream end / reconnect.
16
- interface StreamCall {
17
- baseUrl: string;
18
- sessionId: string;
19
- onMessage: (message: AgentRuntimeMessage) => void;
20
- opts: { signal?: AbortSignal; onActivity?: () => void };
21
- resolve: (reason: string) => void;
22
- }
23
- const streamCalls: StreamCall[] = [];
24
- vi.mock('./agent-runtime-client.ts', () => ({
25
- streamAgentRuntimeMessages: vi.fn(
26
- (baseUrl: string, sessionId: string, onMessage: (m: AgentRuntimeMessage) => void, opts: StreamCall['opts']) =>
27
- new Promise((resolve) => {
28
- streamCalls.push({ baseUrl, sessionId, onMessage, opts, resolve });
29
- }),
30
- ),
31
- }));
32
-
33
- import { RepoburgHttpClient } from './repoburg-client.ts';
34
- import {
35
- resolveBackendUrl,
36
- invalidateBackend as invalidateBackendImport,
37
- } from './backend-resolver.ts';
38
- import { streamAgentRuntimeMessages } from './agent-runtime-client.ts';
39
- import type { AgentRuntimeMessage } from './agent-runtime-client.ts';
40
- import type { RepoburgRuntimeClient } from './repoburg-client.ts';
41
-
42
- // Capture broadcasts without touching a real WebSocket server.
43
- const captured: Array<{ event: string; data: unknown }> = [];
44
- vi.mock('../ws/index.ts', async (importOriginal) => {
45
- const actual = (await importOriginal()) as Record<string, unknown>;
46
- return {
47
- ...actual,
48
- broadcast: vi.fn((event: string, data: unknown) => {
49
- captured.push({ event, data });
50
- }),
51
- };
52
- });
53
-
54
- // Import after mocks are set up
55
- import { repoburgProvider, mapReasoningEffort } from './repoburg-provider.ts';
56
- import { initDb } from '../db/index.ts';
57
- import type Database from 'better-sqlite3';
58
- import type { Session, Turn } from '../db/schema.ts';
59
-
60
- /** Build a mock runtime client (HTTP control surface only). */
61
- function makeMockClient(): { client: RepoburgRuntimeClient } {
62
- const client = {
63
- createSession: vi.fn(),
64
- patchSession: vi.fn(),
65
- getSession: vi.fn(),
66
- createInput: vi.fn(),
67
- getInputWithActions: vi.fn(),
68
- getSessionInputs: vi.fn(),
69
- cancel: vi.fn().mockResolvedValue({ success: true, cancelled: true }),
70
- getModels: vi.fn(),
71
- } as unknown as RepoburgRuntimeClient;
72
- return { client };
73
- }
74
-
75
- let mock = makeMockClient();
76
-
77
- /** Deliver an agent-runtime message through the active stream. */
78
- function deliver(message: AgentRuntimeMessage): void {
79
- const call = streamCalls[streamCalls.length - 1];
80
- call.onMessage(message);
81
- call.opts.onActivity?.();
82
- }
83
-
84
- describe('repoburgProvider', () => {
85
- let db: Database.Database;
86
-
87
- beforeEach(async () => {
88
- vi.clearAllMocks();
89
- await repoburgProvider.shutdown(); // reset module state + session map
90
- mock = makeMockClient();
91
- streamCalls.length = 0;
92
- vi.mocked(RepoburgHttpClient).mockImplementation(() => mock.client as never);
93
- vi.mocked(resolveBackendUrl)
94
- .mockReset()
95
- .mockResolvedValue({ baseUrl: 'http://repoburg.test', port: 1234, name: 'test' });
96
- captured.length = 0;
97
-
98
- db = initDb(':memory:');
99
- const now = new Date().toISOString();
100
- db.prepare(
101
- `INSERT INTO sessions (id, title, runtime_type, model, model_reasoning_effort, status, is_running, lifecycle, created_at, updated_at)
102
- VALUES ('s1', NULL, 'repoburg', 'ollama/glm-5.3:cloud', 'medium', 'active', 0, 'working', ?, ?)`,
103
- ).run(now, now);
104
- });
105
-
106
- afterEach(() => {
107
- db.close();
108
- });
109
-
110
- /** Insert a pending turn and load session + turn rows for startTurn. */
111
- function seedTurn(turnId: string, prompt = 'hello'): { session: Session; turn: Turn } {
112
- const now = new Date().toISOString();
113
- db.prepare(
114
- `INSERT INTO turns (id, session_id, user_prompt, status, created_at, updated_at)
115
- VALUES (?, 's1', ?, 'pending', ?, ?)`,
116
- ).run(turnId, prompt, now, now);
117
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get('s1') as Session;
118
- const turn = db.prepare('SELECT * FROM turns WHERE id = ?').get(turnId) as Turn;
119
- return { session, turn };
120
- }
121
-
122
- /** Start a seeded turn with a successfully created runtime session. */
123
- async function startedTurn(turnId = 't1'): Promise<void> {
124
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
125
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
126
- const { session, turn } = seedTurn(turnId);
127
- await repoburgProvider.startTurn(session, turn);
128
- await vi.waitFor(() => expect(streamCalls.length).toBeGreaterThanOrEqual(1));
129
- }
130
-
131
- it('implements all RuntimeProvider methods', () => {
132
- expect(repoburgProvider.startTurn).toBeTypeOf('function');
133
- expect(repoburgProvider.interruptTurn).toBeTypeOf('function');
134
- expect(repoburgProvider.clearThread).toBeTypeOf('function');
135
- expect(repoburgProvider.clearAllThreads).toBeTypeOf('function');
136
- expect(repoburgProvider.getModels).toBeTypeOf('function');
137
- expect(repoburgProvider.shutdown).toBeTypeOf('function');
138
- });
139
-
140
- describe('mapReasoningEffort', () => {
141
- it('passes valid Ollama levels through and clamps xhigh to high', () => {
142
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'low')).toBe('low');
143
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'medium')).toBe('medium');
144
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'high')).toBe('high');
145
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'xhigh')).toBe('high');
146
- });
147
-
148
- it('omits Ollama-inexpressible levels (minimal/none) and unknown values', () => {
149
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'minimal')).toBeUndefined();
150
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'none')).toBeUndefined();
151
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', 'ultra')).toBeUndefined();
152
- expect(mapReasoningEffort('ollama/glm-5.3:cloud', null)).toBeUndefined();
153
- });
154
-
155
- it('passes the full root enum through for non-Ollama providers', () => {
156
- expect(mapReasoningEffort('zai/glm-5', 'xhigh')).toBe('xhigh');
157
- expect(mapReasoningEffort('zai/glm-5', 'minimal')).toBe('minimal');
158
- expect(mapReasoningEffort(null, 'high')).toBe('high');
159
- });
160
- });
161
-
162
- it('getModels maps runtime models to ModelInfo', async () => {
163
- mock.client.getModels = vi.fn().mockResolvedValue([
164
- { id: 'ollama/glm-5.3:cloud', name: 'glm-5.3:cloud' },
165
- ]);
166
-
167
- const models = await repoburgProvider.getModels();
168
- expect(models).toHaveLength(1);
169
- expect(models[0].slug).toBe('ollama/glm-5.3:cloud');
170
- expect(models[0].displayName).toBe('glm-5.3:cloud');
171
- expect(models[0].reasoningLevels.length).toBeGreaterThan(0);
172
- });
173
-
174
- it('getModels offers 3 levels for Ollama models, 4 for other providers', async () => {
175
- mock.client.getModels = vi.fn().mockResolvedValue([
176
- { id: 'ollama/glm-5.3:cloud', name: 'glm-5.3:cloud' },
177
- { id: 'zai/glm-5', name: 'glm-5' },
178
- ]);
179
-
180
- const models = await repoburgProvider.getModels();
181
- expect(models[0].reasoningLevels.map((l) => l.effort)).toEqual(['low', 'medium', 'high']);
182
- expect(models[1].reasoningLevels.map((l) => l.effort)).toEqual([
183
- 'low',
184
- 'medium',
185
- 'high',
186
- 'xhigh',
187
- ]);
188
- });
189
-
190
- it('getModels never auto-starts a backend (autoStart=false)', async () => {
191
- mock.client.getModels = vi.fn().mockResolvedValue([]);
192
- await repoburgProvider.getModels();
193
- expect(resolveBackendUrl).toHaveBeenCalledWith(process.cwd(), { autoStart: false });
194
- });
195
-
196
- it('getModels returns empty array when no backend is running', async () => {
197
- vi.mocked(resolveBackendUrl).mockResolvedValue(null);
198
- const models = await repoburgProvider.getModels();
199
- expect(models).toEqual([]);
200
- expect(mock.client.getModels).not.toHaveBeenCalled();
201
- });
202
-
203
- it('getModels returns empty array on error', async () => {
204
- mock.client.getModels = vi.fn().mockRejectedValue(new Error('backend down'));
205
-
206
- const models = await repoburgProvider.getModels();
207
- expect(models).toEqual([]);
208
- });
209
-
210
- it('startTurn resolves the workspace backend before creating the session', async () => {
211
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
212
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
213
- db.prepare('UPDATE sessions SET cwd = ? WHERE id = ?').run('/tmp/proj', 's1');
214
-
215
- const { session, turn } = seedTurn('t1');
216
- await repoburgProvider.startTurn(session, turn);
217
-
218
- expect(resolveBackendUrl).toHaveBeenCalledWith('/tmp/proj');
219
- expect(mock.client.createSession).toHaveBeenCalled();
220
- });
221
-
222
- it('startTurn fails the turn when backend resolution fails (daemon down)', async () => {
223
- vi.mocked(resolveBackendUrl).mockRejectedValue(
224
- new Error(
225
- "Repoburg daemon not reachable at http://localhost:9998. Start it with 'repoburg-daemon'.",
226
- ),
227
- );
228
-
229
- const { session, turn } = seedTurn('t1');
230
- await repoburgProvider.startTurn(session, turn);
231
-
232
- const failedTurn = db.prepare('SELECT status, error FROM turns WHERE id = ?').get('t1') as
233
- | { status: string; error: string | null }
234
- | undefined;
235
- expect(failedTurn?.status).toBe('failed');
236
- expect(failedTurn?.error).toMatch(/daemon not reachable/);
237
-
238
- const failed = captured.find((b) => b.event === 'turn:failed');
239
- expect(failed).toBeDefined();
240
- expect(mock.client.createSession).not.toHaveBeenCalled();
241
- });
242
-
243
- it('startTurn invalidates the resolver cache when the backend connection drops', async () => {
244
- mock.client.createSession = vi.fn().mockRejectedValue(new Error('fetch failed ECONNREFUSED'));
245
-
246
- const { session, turn } = seedTurn('t1');
247
- await repoburgProvider.startTurn(session, turn);
248
-
249
- expect(vi.mocked(invalidateBackendImport)).toHaveBeenCalledWith('http://repoburg.test');
250
- const failedTurn = db.prepare('SELECT status FROM turns WHERE id = ?').get('t1') as
251
- | { status: string }
252
- | undefined;
253
- expect(failedTurn?.status).toBe('failed');
254
- });
255
-
256
- it('startTurn invalidates the resolver cache when createInput drops mid-turn', async () => {
257
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
258
- mock.client.createInput = vi.fn().mockRejectedValue(new Error('fetch failed ECONNREFUSED'));
259
-
260
- const { session, turn } = seedTurn('t1');
261
- await repoburgProvider.startTurn(session, turn);
262
-
263
- expect(vi.mocked(invalidateBackendImport)).toHaveBeenCalledWith('http://repoburg.test');
264
- const failedTurn = db.prepare('SELECT status FROM turns WHERE id = ?').get('t1') as
265
- | { status: string }
266
- | undefined;
267
- expect(failedTurn?.status).toBe('failed');
268
- expect(captured.find((b) => b.event === 'turn:failed')).toBeDefined();
269
- });
270
-
271
- it('startTurn creates a runtime session, persists external_thread_id, and starts the input + SSE stream', async () => {
272
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
273
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
274
-
275
- const { session, turn } = seedTurn('t1');
276
- await repoburgProvider.startTurn(session, turn);
277
- await vi.waitFor(() => expect(streamCalls.length).toBeGreaterThanOrEqual(1));
278
-
279
- // Runtime session created with the agent-pane model + reasoning config.
280
- expect(mock.client.createSession).toHaveBeenCalledWith({
281
- model_id: 'ollama/glm-5.3:cloud',
282
- reasoning_effort: 'medium',
283
- });
284
- // Repoburg session id persisted as external_thread_id.
285
- const row = db.prepare('SELECT external_thread_id FROM sessions WHERE id = ?').get('s1') as
286
- | { external_thread_id: string | null }
287
- | undefined;
288
- expect(row?.external_thread_id).toBe('rs-1');
289
-
290
- // Turn lifecycle started.
291
- expect(captured.find((b) => b.event === 'turn:started')).toBeDefined();
292
- expect(db.prepare('SELECT status FROM turns WHERE id = ?').get('t1')).toEqual({
293
- status: 'running',
294
- });
295
-
296
- // SSE message stream opened against the runtime session.
297
- expect(streamCalls[0].baseUrl).toBe('http://repoburg.test');
298
- expect(streamCalls[0].sessionId).toBe('rs-1');
299
- expect(streamCalls[0].opts.signal).toBeInstanceOf(AbortSignal);
300
-
301
- // Input started with the autonomous execution strategy.
302
- expect(mock.client.createInput).toHaveBeenCalledWith('rs-1', {
303
- user_prompt: 'hello',
304
- execution_strategy: 'auto_apply',
305
- });
306
- });
307
-
308
- it('startTurn clamps Ollama xhigh to high when creating the session', async () => {
309
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
310
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
311
- db.prepare('UPDATE sessions SET model_reasoning_effort = ? WHERE id = ?').run('xhigh', 's1');
312
-
313
- const { session, turn } = seedTurn('t1');
314
- await repoburgProvider.startTurn(session, turn);
315
-
316
- expect(mock.client.createSession).toHaveBeenCalledWith({
317
- model_id: 'ollama/glm-5.3:cloud',
318
- reasoning_effort: 'high', // xhigh is invalid for Ollama `think`
319
- });
320
- });
321
-
322
- it('startTurn pushes a patch when only the reasoning effort changes', async () => {
323
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
324
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
325
-
326
- // First turn — model ollama/a, effort medium.
327
- db.prepare('UPDATE sessions SET model = ? WHERE id = ?').run('ollama/a', 's1');
328
- const first = seedTurn('t1');
329
- await repoburgProvider.startTurn(first.session, first.turn);
330
- expect(mock.client.patchSession).not.toHaveBeenCalled();
331
-
332
- // Same model, different effort — patch must still fire.
333
- db.prepare('UPDATE sessions SET model_reasoning_effort = ? WHERE id = ?').run('high', 's1');
334
- const secondSession = db.prepare('SELECT * FROM sessions WHERE id = ?').get('s1') as Session;
335
- const second = seedTurn('t2');
336
- await repoburgProvider.startTurn(secondSession, second.turn);
337
-
338
- expect(mock.client.patchSession).toHaveBeenCalledWith('rs-1', {
339
- model_id: 'ollama/a',
340
- reasoning_effort: 'high',
341
- });
342
- });
343
-
344
- it('startTurn re-pushes the clamped effort on resumed sessions (self-heal)', async () => {
345
- // Pre-fix session: runtime session already exists (external_thread_id) with
346
- // an unsafe xhigh effort — the first new turn must PATCH the clamped value.
347
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
348
- db.prepare(
349
- 'UPDATE sessions SET external_thread_id = ?, model_reasoning_effort = ? WHERE id = ?',
350
- ).run('rs-1', 'xhigh', 's1');
351
-
352
- const { session, turn } = seedTurn('t1');
353
- await repoburgProvider.startTurn(session, turn);
354
-
355
- expect(mock.client.patchSession).toHaveBeenCalledWith('rs-1', {
356
- model_id: 'ollama/glm-5.3:cloud',
357
- reasoning_effort: 'high',
358
- });
359
- });
360
-
361
- it('startTurn pushes mid-session model changes to the runtime session', async () => {
362
- mock.client.createSession = vi.fn().mockResolvedValue({ id: 'rs-1' });
363
- mock.client.createInput = vi.fn().mockResolvedValue({ id: 'in-1' });
364
-
365
- // First turn — session created, model recorded as 'ollama/a'.
366
- db.prepare('UPDATE sessions SET model = ? WHERE id = ?').run('ollama/a', 's1');
367
- const first = seedTurn('t1');
368
- await repoburgProvider.startTurn(first.session, first.turn);
369
- expect(mock.client.patchSession).not.toHaveBeenCalled();
370
-
371
- // Model changed in agent-pane — second turn pushes the change.
372
- db.prepare('UPDATE sessions SET model = ? WHERE id = ?').run('ollama/b', 's1');
373
- const secondSession = db.prepare('SELECT * FROM sessions WHERE id = ?').get('s1') as Session;
374
- const second = seedTurn('t2');
375
- await repoburgProvider.startTurn(secondSession, second.turn);
376
-
377
- expect(mock.client.patchSession).toHaveBeenCalledWith('rs-1', {
378
- model_id: 'ollama/b',
379
- reasoning_effort: 'medium',
380
- });
381
- });
382
-
383
- it('full turn: live deltas, assistant, tool results, result → completed turn with usage and title', async () => {
384
- mock.client.getSession = vi.fn().mockResolvedValue({
385
- id: 'rs-1',
386
- session_title: 'Auto Title',
387
- is_running: false,
388
- });
389
-
390
- await startedTurn('t1');
391
-
392
- // Live text delta (starts the agent_message item)…
393
- deliver({ type: 'stream_event', sessionId: 'rs-1', inputId: 'in-1', delta: { type: 'text', text: 'Hello' } });
394
- const started = captured.find((b) => b.event === 'item:started');
395
- expect(started).toBeDefined();
396
- expect((started!.data as { item: { type: string } }).item.type).toBe('agent_message');
397
-
398
- // …then a delta…
399
- deliver({ type: 'stream_event', sessionId: 'rs-1', inputId: 'in-1', delta: { type: 'text', text: ' world' } });
400
- const delta = captured.find((b) => b.event === 'item:delta');
401
- expect(delta).toBeDefined();
402
- expect((delta!.data as { itemId: string; delta: string }).itemId).toBe('in-1-text');
403
- expect((delta!.data as { itemId: string; delta: string }).delta).toBe(' world');
404
-
405
- // …and a thinking delta (starts the reasoning item).
406
- deliver({ type: 'stream_event', sessionId: 'rs-1', inputId: 'in-1', delta: { type: 'thinking', text: 'thinking about it' } });
407
- expect(
408
- captured
409
- .filter((b) => b.event === 'item:started')
410
- .some((b) => (b.data as { item: { type: string } }).item.type === 'reasoning'),
411
- ).toBe(true);
412
-
413
- // Round ends — assistant completes the streamed items.
414
- deliver({
415
- type: 'assistant',
416
- sessionId: 'rs-1',
417
- inputId: 'in-1',
418
- content: [
419
- { type: 'thinking', thinking: 'thinking about it' },
420
- { type: 'text', text: 'Hello world' },
421
- ],
422
- });
423
-
424
- // Executed actions become items (one tool_result each).
425
- deliver({
426
- type: 'user',
427
- sessionId: 'rs-1',
428
- inputId: 'in-1',
429
- content: [
430
- {
431
- type: 'tool_result',
432
- tool_use_id: 'a1',
433
- content: 'created',
434
- is_error: false,
435
- action: {
436
- id: 'a1',
437
- action_type: 'create_file',
438
- file_path: 'src/a.ts',
439
- command_string: null,
440
- status: 'CONFIRMED_KEPT',
441
- server_name: null,
442
- tool_name: null,
443
- arguments: null,
444
- plain: null,
445
- order_of_execution: 0,
446
- },
447
- },
448
- {
449
- type: 'tool_result',
450
- tool_use_id: 'a2',
451
- content: 'a.ts',
452
- is_error: false,
453
- action: {
454
- id: 'a2',
455
- action_type: 'run_command',
456
- file_path: null,
457
- command_string: 'ls',
458
- status: 'CONFIRMED_KEPT',
459
- server_name: null,
460
- tool_name: null,
461
- arguments: null,
462
- plain: null,
463
- order_of_execution: 1,
464
- },
465
- },
466
- ],
467
- });
468
- expect(
469
- captured.some(
470
- (b) => b.event === 'item:completed' && (b.data as { item: { type: string } }).item.type === 'file_change',
471
- ),
472
- ).toBe(true);
473
-
474
- const items = db.prepare('SELECT type, status FROM items WHERE turn_id = ? ORDER BY rowid').all('t1') as Array<{
475
- type: string;
476
- status: string;
477
- }>;
478
- expect(items.map((i) => i.type)).toEqual([
479
- 'agent_message',
480
- 'reasoning',
481
- 'file_change',
482
- 'command_execution',
483
- ]);
484
- expect(items.every((i) => i.status === 'completed')).toBe(true);
485
-
486
- // Terminal result — turn completes with result text + accumulated usage.
487
- deliver({
488
- type: 'result',
489
- subtype: 'success',
490
- sessionId: 'rs-1',
491
- is_error: false,
492
- result: 'Hello world',
493
- usage: {
494
- input_tokens: 100,
495
- output_tokens: 20,
496
- cache_read_input_tokens: 5,
497
- cache_creation_input_tokens: 0,
498
- },
499
- num_rounds: 1,
500
- });
501
-
502
- const completedTurn = db.prepare(
503
- 'SELECT status, agent_response, token_usage FROM turns WHERE id = ?',
504
- ).get('t1') as { status: string; agent_response: string | null; token_usage: string | null } | undefined;
505
- expect(completedTurn?.status).toBe('completed');
506
- expect(completedTurn?.agent_response).toBe('Hello world');
507
- expect(JSON.parse(completedTurn!.token_usage!)).toMatchObject({
508
- input_tokens: 100,
509
- output_tokens: 20,
510
- cached_input_tokens: 5,
511
- });
512
- expect(captured.find((b) => b.event === 'turn:completed')).toBeDefined();
513
-
514
- // Title synced from the runtime session (async — fire-and-forget).
515
- await vi.waitFor(() => {
516
- const sess = db.prepare('SELECT title, has_unreviewed FROM sessions WHERE id = ?').get('s1') as
517
- | { title: string | null; has_unreviewed: number }
518
- | undefined;
519
- expect(sess?.title).toBe('Auto Title');
520
- expect(sess?.has_unreviewed).toBe(1);
521
- });
522
- expect(captured.find((b) => b.event === 'session:title')).toBeDefined();
523
-
524
- // Stream ends — no reconnect after the terminal result.
525
- streamCalls[0].resolve('result');
526
- await new Promise((r) => setTimeout(r, 20));
527
- expect(streamCalls.length).toBe(1);
528
- });
529
-
530
- it('result error_during_execution fails the turn and closes open items as failed', async () => {
531
- await startedTurn('t1');
532
-
533
- deliver({ type: 'stream_event', sessionId: 'rs-1', inputId: 'in-1', delta: { type: 'text', text: 'partial' } });
534
- deliver({
535
- type: 'result',
536
- subtype: 'error_during_execution',
537
- sessionId: 'rs-1',
538
- is_error: true,
539
- result: '',
540
- num_rounds: 1,
541
- errors: ['boom'],
542
- });
543
-
544
- const failedTurn = db.prepare('SELECT status, error FROM turns WHERE id = ?').get('t1') as
545
- | { status: string; error: string | null }
546
- | undefined;
547
- expect(failedTurn?.status).toBe('failed');
548
- expect(failedTurn?.error).toBe('boom');
549
-
550
- // The in_progress streamed item is closed as failed.
551
- const items = db.prepare('SELECT status FROM items WHERE turn_id = ?').all('t1') as Array<{
552
- status: string;
553
- }>;
554
- expect(items.length).toBe(1);
555
- expect(items[0].status).toBe('failed');
556
-
557
- const failed = captured.find((b) => b.event === 'turn:failed');
558
- expect(failed).toBeDefined();
559
- expect((failed!.data as { error: string }).error).toBe('boom');
560
- });
561
-
562
- it('result aborted aborts the turn', async () => {
563
- await startedTurn('t1');
564
-
565
- deliver({
566
- type: 'result',
567
- subtype: 'aborted',
568
- sessionId: 'rs-1',
569
- is_error: true,
570
- result: '',
571
- num_rounds: 0,
572
- });
573
-
574
- expect(db.prepare('SELECT status FROM turns WHERE id = ?').get('t1')).toEqual({
575
- status: 'aborted',
576
- });
577
- expect(captured.find((b) => b.event === 'turn:cancelled')).toBeDefined();
578
- });
579
-
580
- it('replayed messages after reconnect are projected idempotently (no duplicate items)', async () => {
581
- await startedTurn('t1');
582
-
583
- deliver({
584
- type: 'assistant',
585
- sessionId: 'rs-1',
586
- inputId: 'in-1',
587
- content: [{ type: 'text', text: 'Hello world' }],
588
- });
589
- deliver({
590
- type: 'user',
591
- sessionId: 'rs-1',
592
- inputId: 'in-1',
593
- content: [
594
- {
595
- type: 'tool_result',
596
- tool_use_id: 'a1',
597
- content: 'created',
598
- is_error: false,
599
- action: {
600
- id: 'a1',
601
- action_type: 'create_file',
602
- file_path: 'src/a.ts',
603
- command_string: null,
604
- status: 'CONFIRMED_KEPT',
605
- server_name: null,
606
- tool_name: null,
607
- arguments: null,
608
- plain: null,
609
- order_of_execution: 0,
610
- },
611
- },
612
- ],
613
- });
614
-
615
- // Reconnect: the SAME messages replay on a second stream call.
616
- streamCalls[0].resolve('aborted'); // stall-style disconnect
617
- await vi.waitFor(() => expect(streamCalls.length).toBe(2));
618
- deliver({
619
- type: 'assistant',
620
- sessionId: 'rs-1',
621
- inputId: 'in-1',
622
- content: [{ type: 'text', text: 'Hello world' }],
623
- });
624
- deliver({
625
- type: 'user',
626
- sessionId: 'rs-1',
627
- inputId: 'in-1',
628
- content: [
629
- {
630
- type: 'tool_result',
631
- tool_use_id: 'a1',
632
- content: 'created',
633
- is_error: false,
634
- action: {
635
- id: 'a1',
636
- action_type: 'create_file',
637
- file_path: 'src/a.ts',
638
- command_string: null,
639
- status: 'CONFIRMED_KEPT',
640
- server_name: null,
641
- tool_name: null,
642
- arguments: null,
643
- plain: null,
644
- order_of_execution: 0,
645
- },
646
- },
647
- ],
648
- });
649
-
650
- const items = db.prepare('SELECT external_item_id FROM items WHERE turn_id = ?').all('t1') as Array<{
651
- external_item_id: string;
652
- }>;
653
- expect(items.map((i) => i.external_item_id).sort()).toEqual(['in-1-action-a1', 'in-1-text']);
654
-
655
- deliver({
656
- type: 'result',
657
- subtype: 'success',
658
- sessionId: 'rs-1',
659
- is_error: false,
660
- result: 'done',
661
- num_rounds: 1,
662
- });
663
- expect(db.prepare('SELECT status FROM turns WHERE id = ?').get('t1')).toEqual({
664
- status: 'completed',
665
- });
666
- });
667
-
668
- it('stream session-not-found fails the turn', async () => {
669
- await startedTurn('t1');
670
-
671
- streamCalls[0].resolve('session-not-found');
672
- await vi.waitFor(() => {
673
- const failedTurn = db.prepare('SELECT status, error FROM turns WHERE id = ?').get('t1') as
674
- | { status: string; error: string | null }
675
- | undefined;
676
- expect(failedTurn?.status).toBe('failed');
677
- expect(failedTurn?.error).toMatch(/no longer exists/);
678
- });
679
- });
680
-
681
- it('interruptTurn cancels the runtime round and aborts the turn', async () => {
682
- await startedTurn('t1');
683
-
684
- const cancelled = await repoburgProvider.interruptTurn('s1');
685
- expect(cancelled).toBe(true);
686
- expect(mock.client.cancel).toHaveBeenCalledWith('rs-1');
687
-
688
- expect(db.prepare('SELECT status FROM turns WHERE id = ?').get('t1')).toEqual({
689
- status: 'aborted',
690
- });
691
- expect(captured.find((b) => b.event === 'turn:cancelled')).toBeDefined();
692
-
693
- // Messages after cancellation are ignored — the turn stays aborted.
694
- deliver({ type: 'result', subtype: 'success', sessionId: 'rs-1', is_error: false, result: 'late', num_rounds: 1 });
695
- expect(db.prepare('SELECT status FROM turns WHERE id = ?').get('t1')).toEqual({
696
- status: 'aborted',
697
- });
698
-
699
- // The interrupted stream signal is aborted (fetch closed).
700
- expect(streamCalls[0]!.opts.signal!.aborted).toBe(true);
701
- streamCalls[0]!.resolve('aborted');
702
- });
703
-
704
- it('interruptTurn returns false for unknown session', async () => {
705
- expect(await repoburgProvider.interruptTurn('unknown')).toBe(false);
706
- });
707
-
708
- it('clearThread removes session state', async () => {
709
- await startedTurn('t1');
710
-
711
- repoburgProvider.clearThread('s1');
712
- expect(await repoburgProvider.interruptTurn('s1')).toBe(false);
713
- });
714
-
715
- it('clearAllThreads and shutdown do not throw when empty', async () => {
716
- expect(() => repoburgProvider.clearAllThreads()).not.toThrow();
717
- await expect(repoburgProvider.shutdown()).resolves.not.toThrow();
718
- });
719
-
720
- it('streamAgentRuntimeMessages is imported once and used per turn', () => {
721
- expect(streamAgentRuntimeMessages).toBeDefined();
722
- });
723
- });