repoburg 1.3.202 → 1.3.209

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 (288) hide show
  1. package/agent-pane/backend/dist/index.js +2103 -32003
  2. package/agent-pane/backend/dist/server.js +45313 -0
  3. package/backend/dist/src/agent-runtime/agent-runtime.types.d.ts +1 -0
  4. package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js +6 -6
  5. package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js.map +1 -1
  6. package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js +1 -0
  7. package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js.map +1 -1
  8. package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
  9. package/daemon/dist/agentPane.js +128 -0
  10. package/daemon/dist/agentPane.spec.js +183 -0
  11. package/daemon/dist/index.js +28 -8
  12. package/daemon/package.json +1 -1
  13. package/model-selector-scroll.png +0 -0
  14. package/package.json +10 -2
  15. package/agent-pane/.prettierignore +0 -5
  16. package/agent-pane/.prettierrc +0 -8
  17. package/agent-pane/backend/coverage/base.css +0 -224
  18. package/agent-pane/backend/coverage/block-navigation.js +0 -87
  19. package/agent-pane/backend/coverage/clover.xml +0 -887
  20. package/agent-pane/backend/coverage/coverage-final.json +0 -10
  21. package/agent-pane/backend/coverage/favicon.png +0 -0
  22. package/agent-pane/backend/coverage/index.html +0 -176
  23. package/agent-pane/backend/coverage/prettify.css +0 -1
  24. package/agent-pane/backend/coverage/prettify.js +0 -2
  25. package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
  26. package/agent-pane/backend/coverage/sorter.js +0 -210
  27. package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +0 -1402
  28. package/agent-pane/backend/coverage/src/codex/index.html +0 -131
  29. package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +0 -331
  30. package/agent-pane/backend/coverage/src/db/index.html +0 -131
  31. package/agent-pane/backend/coverage/src/db/index.ts.html +0 -436
  32. package/agent-pane/backend/coverage/src/db/schema.ts.html +0 -175
  33. package/agent-pane/backend/coverage/src/index.html +0 -116
  34. package/agent-pane/backend/coverage/src/index.ts.html +0 -262
  35. package/agent-pane/backend/coverage/src/routes/index.html +0 -146
  36. package/agent-pane/backend/coverage/src/routes/sessions.ts.html +0 -991
  37. package/agent-pane/backend/coverage/src/routes/turns.ts.html +0 -256
  38. package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +0 -406
  39. package/agent-pane/backend/coverage/src/ws/index.html +0 -116
  40. package/agent-pane/backend/coverage/src/ws/index.ts.html +0 -238
  41. package/agent-pane/backend/data.db +0 -0
  42. package/agent-pane/backend/data.db-shm +0 -0
  43. package/agent-pane/backend/data.db-wal +0 -0
  44. package/agent-pane/backend/package-lock.json +0 -5945
  45. package/agent-pane/backend/package.json +0 -33
  46. package/agent-pane/backend/scripts/backfill-from-jsonl.ts +0 -206
  47. package/agent-pane/backend/scripts/import-session-from-jsonl.ts +0 -264
  48. package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +0 -271
  49. package/agent-pane/backend/src/claude/claude-message-mapper.ts +0 -259
  50. package/agent-pane/backend/src/claude/claude-provider.test.ts +0 -455
  51. package/agent-pane/backend/src/claude/claude-provider.ts +0 -1319
  52. package/agent-pane/backend/src/claude/claude-types.ts +0 -45
  53. package/agent-pane/backend/src/codex/app-server-client.test.ts +0 -183
  54. package/agent-pane/backend/src/codex/app-server-client.ts +0 -247
  55. package/agent-pane/backend/src/codex/app-server-types.ts +0 -259
  56. package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +0 -56
  57. package/agent-pane/backend/src/codex/codex-provider.test.ts +0 -60
  58. package/agent-pane/backend/src/codex/codex-provider.ts +0 -619
  59. package/agent-pane/backend/src/codex/item-mapper.test.ts +0 -221
  60. package/agent-pane/backend/src/codex/item-mapper.ts +0 -86
  61. package/agent-pane/backend/src/codex/shared-app-server.ts +0 -25
  62. package/agent-pane/backend/src/db/index.test.ts +0 -125
  63. package/agent-pane/backend/src/db/index.ts +0 -236
  64. package/agent-pane/backend/src/db/schema.ts +0 -34
  65. package/agent-pane/backend/src/index.ts +0 -89
  66. package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +0 -29
  67. package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +0 -40
  68. package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +0 -16
  69. package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +0 -60
  70. package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +0 -34
  71. package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +0 -85
  72. package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +0 -48
  73. package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +0 -35
  74. package/agent-pane/backend/src/integration/delta-streaming.test.ts +0 -130
  75. package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +0 -272
  76. package/agent-pane/backend/src/integration/helpers/test-app.ts +0 -110
  77. package/agent-pane/backend/src/integration/helpers/ws-capture.ts +0 -64
  78. package/agent-pane/backend/src/integration/item-lifecycle.test.ts +0 -133
  79. package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +0 -102
  80. package/agent-pane/backend/src/integration/missing-item-started.test.ts +0 -91
  81. package/agent-pane/backend/src/integration/multi-turn.test.ts +0 -121
  82. package/agent-pane/backend/src/integration/session-operations.test.ts +0 -175
  83. package/agent-pane/backend/src/integration/threads-claude-fork.test.ts +0 -241
  84. package/agent-pane/backend/src/integration/threads.test.ts +0 -309
  85. package/agent-pane/backend/src/integration/title-generation.test.ts +0 -109
  86. package/agent-pane/backend/src/integration/turn-cancel.test.ts +0 -111
  87. package/agent-pane/backend/src/integration/turn-failure.test.ts +0 -97
  88. package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +0 -141
  89. package/agent-pane/backend/src/providers/index.ts +0 -16
  90. package/agent-pane/backend/src/providers/registry.test.ts +0 -92
  91. package/agent-pane/backend/src/providers/registry.ts +0 -43
  92. package/agent-pane/backend/src/providers/runtime-provider.ts +0 -49
  93. package/agent-pane/backend/src/repoburg/action-mapping.test.ts +0 -122
  94. package/agent-pane/backend/src/repoburg/action-mapping.ts +0 -294
  95. package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +0 -271
  96. package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +0 -285
  97. package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +0 -356
  98. package/agent-pane/backend/src/repoburg/backend-resolver.ts +0 -261
  99. package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +0 -180
  100. package/agent-pane/backend/src/repoburg/repoburg-client.ts +0 -194
  101. package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +0 -780
  102. package/agent-pane/backend/src/repoburg/repoburg-provider.ts +0 -471
  103. package/agent-pane/backend/src/repoburg/turn-projector.test.ts +0 -621
  104. package/agent-pane/backend/src/repoburg/turn-projector.ts +0 -637
  105. package/agent-pane/backend/src/routes/files.ts +0 -31
  106. package/agent-pane/backend/src/routes/mcp.ts +0 -33
  107. package/agent-pane/backend/src/routes/models.test.ts +0 -92
  108. package/agent-pane/backend/src/routes/models.ts +0 -16
  109. package/agent-pane/backend/src/routes/plugins.ts +0 -40
  110. package/agent-pane/backend/src/routes/providers.test.ts +0 -86
  111. package/agent-pane/backend/src/routes/providers.ts +0 -86
  112. package/agent-pane/backend/src/routes/sessions.test.ts +0 -772
  113. package/agent-pane/backend/src/routes/sessions.ts +0 -565
  114. package/agent-pane/backend/src/routes/skills.test.ts +0 -114
  115. package/agent-pane/backend/src/routes/skills.ts +0 -70
  116. package/agent-pane/backend/src/routes/threads.ts +0 -323
  117. package/agent-pane/backend/src/routes/turns.test.ts +0 -196
  118. package/agent-pane/backend/src/routes/turns.ts +0 -103
  119. package/agent-pane/backend/src/routes/workspaces.test.ts +0 -197
  120. package/agent-pane/backend/src/routes/workspaces.ts +0 -128
  121. package/agent-pane/backend/src/ws/index.test.ts +0 -107
  122. package/agent-pane/backend/src/ws/index.ts +0 -51
  123. package/agent-pane/backend/tsconfig.json +0 -25
  124. package/agent-pane/backend/vitest.config.ts +0 -20
  125. package/agent-pane/eslint.config.js +0 -33
  126. package/agent-pane/frontend/coverage/base.css +0 -224
  127. package/agent-pane/frontend/coverage/block-navigation.js +0 -87
  128. package/agent-pane/frontend/coverage/clover.xml +0 -2962
  129. package/agent-pane/frontend/coverage/coverage-final.json +0 -37
  130. package/agent-pane/frontend/coverage/favicon.png +0 -0
  131. package/agent-pane/frontend/coverage/index.html +0 -251
  132. package/agent-pane/frontend/coverage/prettify.css +0 -1
  133. package/agent-pane/frontend/coverage/prettify.js +0 -2
  134. package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
  135. package/agent-pane/frontend/coverage/sorter.js +0 -210
  136. package/agent-pane/frontend/coverage/src/App.tsx.html +0 -217
  137. package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +0 -223
  138. package/agent-pane/frontend/coverage/src/api/client.ts.html +0 -187
  139. package/agent-pane/frontend/coverage/src/api/index.html +0 -161
  140. package/agent-pane/frontend/coverage/src/api/sessions.ts.html +0 -316
  141. package/agent-pane/frontend/coverage/src/api/websocket.ts.html +0 -544
  142. package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +0 -199
  143. package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +0 -226
  144. package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +0 -382
  145. package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +0 -715
  146. package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +0 -160
  147. package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +0 -478
  148. package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +0 -472
  149. package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +0 -574
  150. package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +0 -205
  151. package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +0 -133
  152. package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +0 -217
  153. package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +0 -271
  154. package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +0 -109
  155. package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +0 -313
  156. package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +0 -1216
  157. package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +0 -1138
  158. package/agent-pane/frontend/coverage/src/components/index.html +0 -326
  159. package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +0 -247
  160. package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +0 -160
  161. package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +0 -427
  162. package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +0 -397
  163. package/agent-pane/frontend/coverage/src/components/panels/index.html +0 -161
  164. package/agent-pane/frontend/coverage/src/constants/index.html +0 -116
  165. package/agent-pane/frontend/coverage/src/constants/models.ts.html +0 -406
  166. package/agent-pane/frontend/coverage/src/hooks/index.html +0 -146
  167. package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +0 -124
  168. package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +0 -142
  169. package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +0 -187
  170. package/agent-pane/frontend/coverage/src/index.html +0 -146
  171. package/agent-pane/frontend/coverage/src/main.tsx.html +0 -115
  172. package/agent-pane/frontend/coverage/src/store/index.html +0 -116
  173. package/agent-pane/frontend/coverage/src/store/store.ts.html +0 -2404
  174. package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +0 -190
  175. package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +0 -367
  176. package/agent-pane/frontend/coverage/src/test/index.html +0 -131
  177. package/agent-pane/frontend/coverage/src/types/index.html +0 -116
  178. package/agent-pane/frontend/coverage/src/types/index.ts.html +0 -466
  179. package/agent-pane/frontend/coverage/src/utils/changes.ts.html +0 -202
  180. package/agent-pane/frontend/coverage/src/utils/duration.ts.html +0 -127
  181. package/agent-pane/frontend/coverage/src/utils/index.html +0 -131
  182. package/agent-pane/frontend/dist/assets/index-CJxD4YmJ.js +0 -370
  183. package/agent-pane/frontend/dist/assets/index-r-xwfOKQ.css +0 -1
  184. package/agent-pane/frontend/dist/index.html +0 -19
  185. package/agent-pane/frontend/index.html +0 -18
  186. package/agent-pane/frontend/package-lock.json +0 -4211
  187. package/agent-pane/frontend/package.json +0 -35
  188. package/agent-pane/frontend/src/App.test.tsx +0 -74
  189. package/agent-pane/frontend/src/App.tsx +0 -44
  190. package/agent-pane/frontend/src/AppInitializer.test.tsx +0 -142
  191. package/agent-pane/frontend/src/AppInitializer.tsx +0 -46
  192. package/agent-pane/frontend/src/__tests__/ThreadsPanel.test.tsx +0 -192
  193. package/agent-pane/frontend/src/api/client.test.ts +0 -126
  194. package/agent-pane/frontend/src/api/client.ts +0 -34
  195. package/agent-pane/frontend/src/api/models.ts +0 -7
  196. package/agent-pane/frontend/src/api/providers.ts +0 -17
  197. package/agent-pane/frontend/src/api/sessions.test.ts +0 -246
  198. package/agent-pane/frontend/src/api/sessions.ts +0 -97
  199. package/agent-pane/frontend/src/api/skills.ts +0 -12
  200. package/agent-pane/frontend/src/api/websocket.test.ts +0 -288
  201. package/agent-pane/frontend/src/api/websocket.ts +0 -240
  202. package/agent-pane/frontend/src/api/workspaces.test.ts +0 -112
  203. package/agent-pane/frontend/src/api/workspaces.ts +0 -38
  204. package/agent-pane/frontend/src/components/AskUserItem.test.tsx +0 -143
  205. package/agent-pane/frontend/src/components/AskUserItem.tsx +0 -207
  206. package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +0 -49
  207. package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +0 -56
  208. package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +0 -133
  209. package/agent-pane/frontend/src/components/CommandInputBar.tsx +0 -584
  210. package/agent-pane/frontend/src/components/CommandPalette.test.tsx +0 -226
  211. package/agent-pane/frontend/src/components/CommandPalette.tsx +0 -194
  212. package/agent-pane/frontend/src/components/CommentableText.test.tsx +0 -196
  213. package/agent-pane/frontend/src/components/CommentableText.tsx +0 -379
  214. package/agent-pane/frontend/src/components/IdeWorkspace.test.tsx +0 -39
  215. package/agent-pane/frontend/src/components/IdeWorkspace.tsx +0 -51
  216. package/agent-pane/frontend/src/components/ModelSelector.test.tsx +0 -129
  217. package/agent-pane/frontend/src/components/ModelSelector.tsx +0 -191
  218. package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +0 -95
  219. package/agent-pane/frontend/src/components/NewSessionDialog.tsx +0 -129
  220. package/agent-pane/frontend/src/components/RenderArea.test.tsx +0 -160
  221. package/agent-pane/frontend/src/components/RenderArea.tsx +0 -170
  222. package/agent-pane/frontend/src/components/ResizeHandle.tsx +0 -29
  223. package/agent-pane/frontend/src/components/RightPanel.test.tsx +0 -43
  224. package/agent-pane/frontend/src/components/RightPanel.tsx +0 -46
  225. package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +0 -32
  226. package/agent-pane/frontend/src/components/RuntimeIcon.tsx +0 -16
  227. package/agent-pane/frontend/src/components/SessionTabs.test.tsx +0 -72
  228. package/agent-pane/frontend/src/components/SessionTabs.tsx +0 -49
  229. package/agent-pane/frontend/src/components/StatusBar.test.tsx +0 -56
  230. package/agent-pane/frontend/src/components/StatusBar.tsx +0 -129
  231. package/agent-pane/frontend/src/components/StatusDot.test.tsx +0 -25
  232. package/agent-pane/frontend/src/components/StatusDot.tsx +0 -8
  233. package/agent-pane/frontend/src/components/Toast.test.tsx +0 -79
  234. package/agent-pane/frontend/src/components/Toast.tsx +0 -76
  235. package/agent-pane/frontend/src/components/TurnContent.test.tsx +0 -377
  236. package/agent-pane/frontend/src/components/TurnContent.tsx +0 -594
  237. package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +0 -297
  238. package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +0 -351
  239. package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +0 -32
  240. package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +0 -63
  241. package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +0 -78
  242. package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +0 -96
  243. package/agent-pane/frontend/src/components/input/SlashPopup.tsx +0 -82
  244. package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +0 -61
  245. package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +0 -54
  246. package/agent-pane/frontend/src/components/panels/TasksPanel.test.tsx +0 -97
  247. package/agent-pane/frontend/src/components/panels/TasksPanel.tsx +0 -208
  248. package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +0 -382
  249. package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +0 -131
  250. package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +0 -131
  251. package/agent-pane/frontend/src/constants/models.test.ts +0 -97
  252. package/agent-pane/frontend/src/constants/models.ts +0 -166
  253. package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +0 -42
  254. package/agent-pane/frontend/src/hooks/useEscapeKey.ts +0 -13
  255. package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +0 -92
  256. package/agent-pane/frontend/src/hooks/useOutsideClick.ts +0 -19
  257. package/agent-pane/frontend/src/hooks/useProvider.ts +0 -44
  258. package/agent-pane/frontend/src/hooks/useResizable.test.ts +0 -97
  259. package/agent-pane/frontend/src/hooks/useResizable.ts +0 -75
  260. package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +0 -116
  261. package/agent-pane/frontend/src/hooks/useSessionStatus.ts +0 -34
  262. package/agent-pane/frontend/src/main.test.tsx +0 -37
  263. package/agent-pane/frontend/src/main.tsx +0 -10
  264. package/agent-pane/frontend/src/store/sessionSlice.ts +0 -440
  265. package/agent-pane/frontend/src/store/store.test.ts +0 -1338
  266. package/agent-pane/frontend/src/store/store.ts +0 -37
  267. package/agent-pane/frontend/src/store/threadSlice.ts +0 -136
  268. package/agent-pane/frontend/src/store/uiSlice.ts +0 -133
  269. package/agent-pane/frontend/src/store/workspaceSlice.ts +0 -77
  270. package/agent-pane/frontend/src/store/wsSlice.ts +0 -368
  271. package/agent-pane/frontend/src/styles/app.css +0 -627
  272. package/agent-pane/frontend/src/styles/tokens.css +0 -62
  273. package/agent-pane/frontend/src/test/component-helpers.ts +0 -38
  274. package/agent-pane/frontend/src/test/fixtures.ts +0 -101
  275. package/agent-pane/frontend/src/test-setup.ts +0 -5
  276. package/agent-pane/frontend/src/types/index.test.ts +0 -128
  277. package/agent-pane/frontend/src/types/index.ts +0 -134
  278. package/agent-pane/frontend/src/utils/changes.test.ts +0 -81
  279. package/agent-pane/frontend/src/utils/changes.ts +0 -39
  280. package/agent-pane/frontend/src/utils/duration.test.ts +0 -76
  281. package/agent-pane/frontend/src/utils/duration.ts +0 -14
  282. package/agent-pane/frontend/tsconfig.json +0 -25
  283. package/agent-pane/frontend/vite.config.ts +0 -22
  284. package/agent-pane/frontend/vitest.config.ts +0 -29
  285. package/agent-pane/package-lock.json +0 -1527
  286. package/agent-pane/package.json +0 -19
  287. package/agent-pane/shared/provider-types.ts +0 -67
  288. package/agent-pane/shared/types.ts +0 -184
@@ -1,288 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import { connectWebSocket, disconnectWebSocket } from './websocket.ts';
3
- import type { Item, TokenUsage } from '../types/index.ts';
4
-
5
- // Mock the store to capture state dispatches
6
- vi.mock('../store/store.ts', () => {
7
- const handlers: Record<string, (...args: unknown[]) => void> = {};
8
- return {
9
- useStore: {
10
- getState: () => handlers,
11
- setState: vi.fn(),
12
- },
13
- };
14
- });
15
-
16
- // We need to dynamically set the handlers on the mock
17
- import { useStore } from '../store/store.ts';
18
-
19
- const NOW = '2025-01-01T00:00:00Z';
20
-
21
- // Type-safe accessor for the mock store state
22
- function getMockStore() {
23
- return useStore.getState() as unknown as Record<string, ReturnType<typeof vi.fn>>;
24
- }
25
-
26
- // Reset the mock store to have all the actions as vi.fn()
27
- function setupMockStore() {
28
- const store = getMockStore();
29
- const actions = [
30
- 'setWsConnected',
31
- 'addLogEntry',
32
- 'refreshActiveSession',
33
- 'addToast',
34
- 'onTurnStarted',
35
- 'onItemStarted',
36
- 'onItemUpdated',
37
- 'onItemCompleted',
38
- 'onItemDelta',
39
- 'onTurnCompleted',
40
- 'onTokenUsageUpdated',
41
- 'onTurnFailed',
42
- 'onTurnCancelled',
43
- 'onSessionTitle',
44
- ];
45
- for (const action of actions) {
46
- store[action] = vi.fn();
47
- }
48
- }
49
-
50
- // Mock WebSocket class
51
- class MockWebSocket {
52
- static instances: MockWebSocket[] = [];
53
- static lastInstance: MockWebSocket | null = null;
54
-
55
- static readonly OPEN = 1;
56
- static readonly CONNECTING = 0;
57
- static readonly CLOSED = 3;
58
-
59
- readyState = MockWebSocket.CONNECTING;
60
- onopen: (() => void) | null = null;
61
- onclose: (() => void) | null = null;
62
- onerror: (() => void) | null = null;
63
- onmessage: ((event: { data: string }) => void) | null = null;
64
-
65
- constructor(public url: string) {
66
- MockWebSocket.instances.push(this);
67
- MockWebSocket.lastInstance = this;
68
- }
69
-
70
- close() {
71
- this.readyState = MockWebSocket.CLOSED;
72
- if (this.onclose) this.onclose();
73
- }
74
- }
75
-
76
- describe('connectWebSocket', () => {
77
- beforeEach(() => {
78
- vi.clearAllMocks();
79
- MockWebSocket.instances = [];
80
- MockWebSocket.lastInstance = null;
81
- setupMockStore();
82
- vi.stubGlobal('WebSocket', MockWebSocket);
83
- // Reset module-level state by calling disconnect first
84
- disconnectWebSocket();
85
- });
86
-
87
- afterEach(() => {
88
- disconnectWebSocket();
89
- vi.unstubAllGlobals();
90
- });
91
-
92
- it('creates a new WebSocket connection', () => {
93
- connectWebSocket();
94
- expect(MockWebSocket.lastInstance).not.toBeNull();
95
- expect(MockWebSocket.lastInstance!.url).toContain('/ws');
96
- });
97
-
98
- it('does not create duplicate connection when already connecting', () => {
99
- connectWebSocket();
100
- connectWebSocket();
101
- expect(MockWebSocket.instances).toHaveLength(1);
102
- });
103
-
104
- it('calls setWsConnected(true) and addLogEntry on open', () => {
105
- connectWebSocket();
106
- const ws = MockWebSocket.lastInstance!;
107
- ws.readyState = MockWebSocket.OPEN;
108
- ws.onopen!();
109
-
110
- const store = getMockStore();
111
- expect(store.setWsConnected).toHaveBeenCalledWith(true);
112
- expect(store.addLogEntry).toHaveBeenCalledWith('INFO', 'WebSocket connected');
113
- });
114
-
115
- it('does not call refreshActiveSession on first connect', () => {
116
- connectWebSocket();
117
- const ws = MockWebSocket.lastInstance!;
118
- ws.readyState = MockWebSocket.OPEN;
119
- ws.onopen!();
120
-
121
- const store = getMockStore();
122
- expect(store.refreshActiveSession).not.toHaveBeenCalled();
123
- });
124
-
125
- it('calls setWsConnected(false) on close', () => {
126
- connectWebSocket();
127
- const ws = MockWebSocket.lastInstance!;
128
- ws.onclose!();
129
-
130
- const store = getMockStore();
131
- expect(store.setWsConnected).toHaveBeenCalledWith(false);
132
- expect(store.addLogEntry).toHaveBeenCalledWith('WARN', 'WebSocket disconnected');
133
- });
134
- });
135
-
136
- describe('disconnectWebSocket', () => {
137
- beforeEach(() => {
138
- vi.clearAllMocks();
139
- MockWebSocket.instances = [];
140
- MockWebSocket.lastInstance = null;
141
- setupMockStore();
142
- vi.stubGlobal('WebSocket', MockWebSocket);
143
- });
144
-
145
- afterEach(() => {
146
- vi.unstubAllGlobals();
147
- });
148
-
149
- it('closes existing connection and clears handlers', () => {
150
- connectWebSocket();
151
- const ws = MockWebSocket.lastInstance!;
152
- const closeSpy = vi.spyOn(ws, 'close');
153
-
154
- disconnectWebSocket();
155
-
156
- expect(closeSpy).toHaveBeenCalled();
157
- expect(ws.onclose).toBeNull(); // prevents reconnect on intentional close
158
- const store = getMockStore();
159
- expect(store.setWsConnected).toHaveBeenCalledWith(false);
160
- });
161
-
162
- it('does nothing when no connection exists', () => {
163
- disconnectWebSocket();
164
- // Should not throw
165
- expect(MockWebSocket.instances).toHaveLength(0);
166
- });
167
- });
168
-
169
- describe('WebSocket event dispatch', () => {
170
- beforeEach(() => {
171
- vi.clearAllMocks();
172
- MockWebSocket.instances = [];
173
- MockWebSocket.lastInstance = null;
174
- setupMockStore();
175
- vi.stubGlobal('WebSocket', MockWebSocket);
176
- connectWebSocket();
177
- });
178
-
179
- afterEach(() => {
180
- disconnectWebSocket();
181
- vi.unstubAllGlobals();
182
- });
183
-
184
- function sendMessage(data: unknown) {
185
- const ws = MockWebSocket.lastInstance!;
186
- ws.onmessage!({ data: JSON.stringify(data) });
187
- }
188
-
189
- it('dispatches turn:started event', () => {
190
- sendMessage({ event: 'turn:started', data: { sessionId: 's1', turnId: 't1' } });
191
- const store = getMockStore();
192
- expect(store.onTurnStarted).toHaveBeenCalledWith('s1', 't1');
193
- });
194
-
195
- it('dispatches item:started event', () => {
196
- const item: Item = {
197
- id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'agent_message',
198
- data: { text: 'hello' }, status: 'in_progress', created_at: NOW, updated_at: NOW,
199
- };
200
- sendMessage({ event: 'item:started', data: { sessionId: 's1', turnId: 't1', item } });
201
- const store = getMockStore();
202
- expect(store.onItemStarted).toHaveBeenCalledWith('s1', 't1', item);
203
- });
204
-
205
- it('dispatches item:updated event', () => {
206
- const item: Item = {
207
- id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'command_execution',
208
- data: { command: 'ls' }, status: 'in_progress', created_at: NOW, updated_at: NOW,
209
- };
210
- sendMessage({ event: 'item:updated', data: { sessionId: 's1', turnId: 't1', item } });
211
- const store = getMockStore();
212
- expect(store.onItemUpdated).toHaveBeenCalledWith('s1', 't1', item);
213
- });
214
-
215
- it('dispatches item:completed event', () => {
216
- const item: Item = {
217
- id: 'i1', turn_id: 't1', external_item_id: 'e1', type: 'agent_message',
218
- data: { text: 'done' }, status: 'completed', created_at: NOW, updated_at: NOW,
219
- };
220
- sendMessage({ event: 'item:completed', data: { sessionId: 's1', turnId: 't1', item } });
221
- const store = getMockStore();
222
- expect(store.onItemCompleted).toHaveBeenCalledWith('s1', 't1', item);
223
- });
224
-
225
- it('dispatches item:delta event', () => {
226
- sendMessage({ event: 'item:delta', data: { sessionId: 's1', turnId: 't1', itemId: 'ext-i1', delta: 'Hello' } });
227
- const store = getMockStore();
228
- expect(store.onItemDelta).toHaveBeenCalledWith('s1', 't1', 'ext-i1', 'Hello');
229
- });
230
-
231
- it('dispatches turn:tokenUsage event', () => {
232
- const usage: TokenUsage = {
233
- input_tokens: 100, cached_input_tokens: 0, cache_write_input_tokens: 0,
234
- output_tokens: 200, reasoning_output_tokens: 0,
235
- };
236
- sendMessage({ event: 'turn:tokenUsage', data: { sessionId: 's1', turnId: 't1', usage } });
237
- const store = getMockStore();
238
- expect(store.onTokenUsageUpdated).toHaveBeenCalledWith('s1', 't1', usage);
239
- });
240
-
241
- it('dispatches turn:completed event with usage', () => {
242
- const usage: TokenUsage = {
243
- input_tokens: 100, cached_input_tokens: 0, cache_write_input_tokens: 0,
244
- output_tokens: 200, reasoning_output_tokens: 0,
245
- };
246
- sendMessage({ event: 'turn:completed', data: { sessionId: 's1', turnId: 't1', usage, hasUnreviewed: true } });
247
- const store = getMockStore();
248
- expect(store.onTurnCompleted).toHaveBeenCalledWith('s1', 't1', usage, true, undefined);
249
- });
250
-
251
- it('dispatches turn:completed event without usage (real app-server)', () => {
252
- sendMessage({ event: 'turn:completed', data: { sessionId: 's1', turnId: 't1', hasUnreviewed: true } });
253
- const store = getMockStore();
254
- expect(store.onTurnCompleted).toHaveBeenCalledWith('s1', 't1', undefined, true, undefined);
255
- });
256
-
257
- it('dispatches turn:failed event with error', () => {
258
- sendMessage({ event: 'turn:failed', data: { sessionId: 's1', turnId: 't1', error: 'crashed' } });
259
- const store = getMockStore();
260
- expect(store.onTurnFailed).toHaveBeenCalledWith('s1', 't1', 'crashed');
261
- });
262
-
263
- it('dispatches turn:cancelled event', () => {
264
- sendMessage({ event: 'turn:cancelled', data: { sessionId: 's1', turnId: 't1' } });
265
- const store = getMockStore();
266
- expect(store.onTurnCancelled).toHaveBeenCalledWith('s1', 't1');
267
- });
268
-
269
- it('dispatches session:title event', () => {
270
- sendMessage({ event: 'session:title', data: { sessionId: 's1', title: 'New Title' } });
271
- const store = getMockStore();
272
- expect(store.onSessionTitle).toHaveBeenCalledWith('s1', 'New Title');
273
- });
274
-
275
- it('ignores malformed JSON without error', () => {
276
- const ws = MockWebSocket.lastInstance!;
277
- // Should not throw
278
- ws.onmessage!({ data: 'not json' });
279
- const store = getMockStore();
280
- expect(store.onTurnStarted).not.toHaveBeenCalled();
281
- });
282
-
283
- it('ignores unknown event types', () => {
284
- sendMessage({ event: 'unknown:event', data: {} });
285
- const store = getMockStore();
286
- expect(store.onTurnStarted).not.toHaveBeenCalled();
287
- });
288
- });
@@ -1,240 +0,0 @@
1
- import { useStore } from '../store/store.ts';
2
- import type { Item, TokenUsage, Turn, DiscussionThread, ContextUsage } from '../types/index.ts';
3
-
4
- type WsEventHandler = (data: unknown) => void;
5
-
6
- let ws: WebSocket | null = null;
7
- let reconnectTimer: number | null = null;
8
- let reconnectDelay = 1000;
9
- const MAX_RECONNECT_DELAY = 10000;
10
-
11
- /** Build the WebSocket URL from the current page location. */
12
- function buildWsUrl(): string {
13
- const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
14
- return `${proto}//${window.location.host}/ws`;
15
- }
16
-
17
- /** Connect to the backend WebSocket server. Dispatches events to store actions. */
18
- export function connectWebSocket(): void {
19
- if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
20
- return;
21
- }
22
-
23
- ws = new WebSocket(buildWsUrl());
24
-
25
- ws.onopen = () => {
26
- const wasDisconnected = reconnectDelay > 1000;
27
- reconnectDelay = 1000; // reset backoff
28
- useStore.getState().setWsConnected(true);
29
- useStore.getState().addLogEntry('INFO', 'WebSocket connected');
30
-
31
- // On reconnect (not initial connect): reconcile active session
32
- if (wasDisconnected) {
33
- const state = useStore.getState();
34
- if (state.activeSessionId) {
35
- state.refreshActiveSession();
36
- state.addToast('info', 'WebSocket reconnected — session state reconciled');
37
- }
38
- }
39
- };
40
-
41
- ws.onclose = () => {
42
- useStore.getState().setWsConnected(false);
43
- useStore.getState().addLogEntry('WARN', 'WebSocket disconnected');
44
- useStore.getState().addToast('warn', 'WebSocket disconnected — reconnecting…');
45
- scheduleReconnect();
46
- };
47
-
48
- ws.onerror = () => {
49
- // onclose will fire after onerror
50
- };
51
-
52
- ws.onmessage = (event: MessageEvent) => {
53
- try {
54
- const msg = JSON.parse(event.data as string);
55
- const handler = HANDLERS[msg.event as string];
56
- if (handler) {
57
- handler(msg.data);
58
- }
59
- } catch {
60
- // Ignore malformed messages
61
- }
62
- };
63
- }
64
-
65
- /** Disconnect the WebSocket (used on unmount). */
66
- export function disconnectWebSocket(): void {
67
- if (reconnectTimer) {
68
- clearTimeout(reconnectTimer);
69
- reconnectTimer = null;
70
- }
71
- if (ws) {
72
- ws.onclose = null; // prevent reconnect on intentional close
73
- ws.close();
74
- ws = null;
75
- }
76
- useStore.getState().setWsConnected(false);
77
- }
78
-
79
- /** Schedule a reconnect with exponential backoff. */
80
- function scheduleReconnect(): void {
81
- if (reconnectTimer) return;
82
- reconnectTimer = window.setTimeout(() => {
83
- reconnectTimer = null;
84
- connectWebSocket();
85
- }, reconnectDelay);
86
- reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
87
- }
88
-
89
- // --- Event handlers — dispatch to store actions ---
90
-
91
- interface TurnEventPayload {
92
- sessionId: string;
93
- turnId: string;
94
- }
95
-
96
- interface ItemEventPayload {
97
- sessionId: string;
98
- turnId: string;
99
- item: Item;
100
- }
101
-
102
- interface TurnCompletedPayload {
103
- sessionId: string;
104
- turnId: string;
105
- usage?: TokenUsage;
106
- hasUnreviewed?: boolean;
107
- /** Final turn text (repoburg final-action rounds with no agent_message item). */
108
- response?: string | null;
109
- }
110
-
111
- interface ItemDeltaPayload {
112
- sessionId: string;
113
- turnId: string;
114
- itemId: string;
115
- delta: string;
116
- }
117
-
118
- interface TokenUsageUpdatedPayload {
119
- sessionId: string;
120
- turnId: string;
121
- usage: TokenUsage;
122
- }
123
-
124
- interface ContextUsagePayload {
125
- sessionId: string;
126
- totalTokens: number;
127
- contextWindow: number;
128
- percentage: number;
129
- }
130
-
131
- interface TurnFailedPayload {
132
- sessionId: string;
133
- turnId: string;
134
- error: string;
135
- }
136
-
137
- interface SessionTitlePayload {
138
- sessionId: string;
139
- title: string;
140
- }
141
-
142
- interface BackgroundTasksPayload {
143
- sessionId: string;
144
- tasks: Array<{ task_id: string; task_type: string; description: string }>;
145
- }
146
-
147
- interface ThreadCreatedPayload {
148
- sessionId: string;
149
- thread: DiscussionThread;
150
- turn: Turn;
151
- }
152
-
153
- interface ThreadFollowupPayload {
154
- sessionId: string;
155
- threadId: string;
156
- turn: Turn;
157
- }
158
-
159
- interface ThreadResolvedPayload {
160
- sessionId: string;
161
- threadId: string;
162
- status: 'open' | 'resolved';
163
- }
164
-
165
- interface ThreadDeletedPayload {
166
- sessionId: string;
167
- threadId: string;
168
- }
169
-
170
- const HANDLERS: Record<string, WsEventHandler> = {
171
- 'turn:started': (data) => {
172
- const p = data as TurnEventPayload;
173
- useStore.getState().onTurnStarted(p.sessionId, p.turnId);
174
- },
175
- 'item:started': (data) => {
176
- const p = data as ItemEventPayload;
177
- useStore.getState().onItemStarted(p.sessionId, p.turnId, p.item);
178
- },
179
- 'item:updated': (data) => {
180
- const p = data as ItemEventPayload;
181
- useStore.getState().onItemUpdated(p.sessionId, p.turnId, p.item);
182
- },
183
- 'item:completed': (data) => {
184
- const p = data as ItemEventPayload;
185
- useStore.getState().onItemCompleted(p.sessionId, p.turnId, p.item);
186
- },
187
- 'item:delta': (data) => {
188
- const p = data as ItemDeltaPayload;
189
- useStore.getState().onItemDelta(p.sessionId, p.turnId, p.itemId, p.delta);
190
- },
191
- 'turn:completed': (data) => {
192
- const p = data as TurnCompletedPayload;
193
- useStore.getState().onTurnCompleted(p.sessionId, p.turnId, p.usage, p.hasUnreviewed, p.response ?? undefined);
194
- },
195
- 'turn:tokenUsage': (data) => {
196
- const p = data as TokenUsageUpdatedPayload;
197
- useStore.getState().onTokenUsageUpdated(p.sessionId, p.turnId, p.usage);
198
- },
199
- 'turn:contextUsage': (data) => {
200
- const p = data as ContextUsagePayload;
201
- const ctx: ContextUsage = {
202
- totalTokens: p.totalTokens,
203
- contextWindow: p.contextWindow,
204
- percentage: p.percentage,
205
- };
206
- useStore.getState().onContextUsageUpdated(p.sessionId, ctx);
207
- },
208
- 'turn:failed': (data) => {
209
- const p = data as TurnFailedPayload;
210
- useStore.getState().onTurnFailed(p.sessionId, p.turnId, p.error);
211
- },
212
- 'turn:cancelled': (data) => {
213
- const p = data as TurnEventPayload;
214
- useStore.getState().onTurnCancelled(p.sessionId, p.turnId);
215
- },
216
- 'session:title': (data) => {
217
- const p = data as SessionTitlePayload;
218
- useStore.getState().onSessionTitle(p.sessionId, p.title);
219
- },
220
- 'background_tasks': (data) => {
221
- const p = data as BackgroundTasksPayload;
222
- useStore.getState().onBackgroundTasksChanged(p.sessionId, p.tasks);
223
- },
224
- 'thread:created': (data) => {
225
- const p = data as ThreadCreatedPayload;
226
- useStore.getState().onThreadCreated(p.sessionId, p.thread, p.turn);
227
- },
228
- 'thread:followup': (data) => {
229
- const p = data as ThreadFollowupPayload;
230
- useStore.getState().onThreadFollowup(p.sessionId, p.threadId, p.turn);
231
- },
232
- 'thread:resolved': (data) => {
233
- const p = data as ThreadResolvedPayload;
234
- useStore.getState().onThreadResolved(p.sessionId, p.threadId, p.status);
235
- },
236
- 'thread:deleted': (data) => {
237
- const p = data as ThreadDeletedPayload;
238
- useStore.getState().onThreadDeleted(p.sessionId, p.threadId);
239
- },
240
- };
@@ -1,112 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
-
3
- // Mock the client module to capture call signatures without network access
4
- vi.mock('./client.ts', () => ({
5
- apiGet: vi.fn(),
6
- apiPost: vi.fn(),
7
- apiDelete: vi.fn(),
8
- }));
9
-
10
- import { apiGet, apiPost, apiDelete } from './client.ts';
11
- import {
12
- fetchWorkspaces,
13
- createWorkspace,
14
- hideWorkspace,
15
- deleteWorkspace,
16
- pickDirectory,
17
- } from './workspaces.ts';
18
- import type { Workspace } from '../types/index.ts';
19
-
20
- const NOW = '2025-01-01T00:00:00Z';
21
-
22
- function makeWorkspace(overrides: Partial<Workspace> = {}): Workspace {
23
- return {
24
- id: 'w1',
25
- path: '/test/path',
26
- label: null,
27
- deleted: 0,
28
- created_at: NOW,
29
- ...overrides,
30
- };
31
- }
32
-
33
- describe('fetchWorkspaces', () => {
34
- beforeEach(() => vi.clearAllMocks());
35
-
36
- it('calls apiGet with /workspaces', async () => {
37
- const workspaces = [makeWorkspace()];
38
- vi.mocked(apiGet).mockResolvedValue(workspaces);
39
- const result = await fetchWorkspaces();
40
- expect(apiGet).toHaveBeenCalledWith('/workspaces');
41
- expect(result).toEqual(workspaces);
42
- });
43
- });
44
-
45
- describe('createWorkspace', () => {
46
- beforeEach(() => vi.clearAllMocks());
47
-
48
- it('calls apiPost with path', async () => {
49
- const ws = makeWorkspace({ path: '/new' });
50
- vi.mocked(apiPost).mockResolvedValue(ws);
51
- await createWorkspace('/new');
52
- expect(apiPost).toHaveBeenCalledWith('/workspaces', { path: '/new', label: undefined });
53
- });
54
-
55
- it('passes label when provided', async () => {
56
- const ws = makeWorkspace({ path: '/new', label: 'My Project' });
57
- vi.mocked(apiPost).mockResolvedValue(ws);
58
- await createWorkspace('/new', 'My Project');
59
- expect(apiPost).toHaveBeenCalledWith('/workspaces', { path: '/new', label: 'My Project' });
60
- });
61
- });
62
-
63
- describe('hideWorkspace', () => {
64
- beforeEach(() => vi.clearAllMocks());
65
-
66
- it('calls apiPost with path on /workspaces/hide', async () => {
67
- vi.mocked(apiPost).mockResolvedValue({ success: true });
68
- await hideWorkspace('/path');
69
- expect(apiPost).toHaveBeenCalledWith('/workspaces/hide', { path: '/path' });
70
- });
71
- });
72
-
73
- describe('deleteWorkspace', () => {
74
- beforeEach(() => vi.clearAllMocks());
75
-
76
- it('calls apiDelete with /workspaces/:id', async () => {
77
- vi.mocked(apiDelete).mockResolvedValue({ success: true });
78
- await deleteWorkspace('w1');
79
- expect(apiDelete).toHaveBeenCalledWith('/workspaces/w1');
80
- });
81
- });
82
-
83
- describe('pickDirectory', () => {
84
- beforeEach(() => {
85
- vi.clearAllMocks();
86
- global.fetch = vi.fn();
87
- });
88
-
89
- it('returns cancelled when status is 204', async () => {
90
- (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({ status: 204 });
91
- const result = await pickDirectory();
92
- expect(result).toEqual({ cancelled: true });
93
- });
94
-
95
- it('returns path on ok response', async () => {
96
- (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
97
- ok: true,
98
- json: () => Promise.resolve({ path: '/selected/dir' }),
99
- });
100
- const result = await pickDirectory();
101
- expect(result).toEqual({ path: '/selected/dir' });
102
- });
103
-
104
- it('throws on error response', async () => {
105
- (global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
106
- ok: false,
107
- status: 500,
108
- statusText: 'Internal Server Error',
109
- });
110
- await expect(pickDirectory()).rejects.toThrow('GET /api/pick-directory failed: 500 Internal Server Error');
111
- });
112
- });
@@ -1,38 +0,0 @@
1
- import { apiGet, apiPost, apiDelete } from './client.ts';
2
- import type { Workspace } from '../types/index.ts';
3
-
4
- interface PickDirectoryResult {
5
- path?: string;
6
- cancelled?: boolean;
7
- }
8
-
9
- /** Fetch all workspaces (active and soft-deleted), ordered by creation date. */
10
- export function fetchWorkspaces(): Promise<Workspace[]> {
11
- return apiGet<Workspace[]>('/workspaces');
12
- }
13
-
14
- /** Create a new workspace at the given path, or restore a soft-deleted one. */
15
- export function createWorkspace(path: string, label?: string): Promise<Workspace> {
16
- return apiPost<Workspace>('/workspaces', { path, label });
17
- }
18
-
19
- /** Soft-delete (hide) a workspace by path. Sessions keep their cwd. */
20
- export function hideWorkspace(path: string): Promise<{ success: boolean }> {
21
- return apiPost<{ success: boolean }>('/workspaces/hide', { path });
22
- }
23
-
24
- /** Soft-delete a workspace by ID. Does NOT delete sessions. */
25
- export function deleteWorkspace(id: string): Promise<{ success: boolean }> {
26
- return apiDelete<{ success: boolean }>(`/workspaces/${id}`);
27
- }
28
-
29
- /** Open the native OS folder picker dialog (macOS only via osascript).
30
- * @returns `{ path }` on success, or `{ cancelled: true }` if the user dismissed the dialog. */
31
- export async function pickDirectory(): Promise<PickDirectoryResult> {
32
- const res = await fetch('/api/pick-directory');
33
- if (res.status === 204) return { cancelled: true };
34
- if (!res.ok) {
35
- throw new Error(`GET /api/pick-directory failed: ${res.status} ${res.statusText}`);
36
- }
37
- return res.json() as Promise<PickDirectoryResult>;
38
- }