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,504 +0,0 @@
1
- import type { FastifyInstance } from 'fastify';
2
- import { v4 as uuidv4 } from 'uuid';
3
- import { getDb } from '../db/index.ts';
4
- import { getProvider } from '../providers/registry.ts';
5
- import type { Session, SessionDetail, Turn, Item, DiscussionThread, Attachment } from '../db/schema.ts';
6
-
7
- /** Raw DB row for turns — token_usage and attachments are JSON strings, not parsed; no items field. */
8
- interface TurnRow extends Omit<Turn, 'token_usage' | 'items' | 'attachments'> {
9
- token_usage: string | null;
10
- attachments: string | null;
11
- }
12
-
13
- /** Raw DB row for items — data is a JSON string, not parsed. */
14
- interface ItemRow extends Omit<Item, 'data'> {
15
- data: string | null;
16
- }
17
-
18
- export async function registerSessionRoutes(fastify: FastifyInstance): Promise<void> {
19
- // GET /api/sessions — list all sessions
20
- fastify.get('/api/sessions', async () => {
21
- const rows = getDb().prepare('SELECT * FROM sessions ORDER BY created_at DESC').all() as Session[];
22
- return rows;
23
- });
24
-
25
- // GET /api/sessions/:id — session with turns + items
26
- fastify.get<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
27
- const { id } = request.params;
28
- const db = getDb();
29
-
30
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
31
- if (!session) {
32
- return reply.code(404).send({ error: 'Session not found' });
33
- }
34
-
35
- // If the session is rewound, only show turns up to and including the rewind point
36
- const turnFilter = session.rewound_at_turn_id
37
- ? db.prepare('SELECT created_at FROM turns WHERE id = ?').get(session.rewound_at_turn_id) as { created_at: string } | undefined
38
- : null;
39
-
40
- const turnRows = turnFilter
41
- ? db
42
- .prepare('SELECT * FROM turns WHERE session_id = ? AND created_at <= ? ORDER BY created_at ASC')
43
- .all(id, turnFilter.created_at) as TurnRow[]
44
- : db
45
- .prepare('SELECT * FROM turns WHERE session_id = ? ORDER BY created_at ASC')
46
- .all(id) as TurnRow[];
47
-
48
- const itemRows = db
49
- .prepare(
50
- 'SELECT * FROM items WHERE turn_id IN (SELECT id FROM turns WHERE session_id = ?' +
51
- (turnFilter ? ' AND created_at <= ?' : '') +
52
- ') ORDER BY created_at ASC',
53
- )
54
- .all(...(turnFilter ? [id, turnFilter.created_at] : [id])) as ItemRow[];
55
-
56
- // Parse item data from JSON string and group by turn_id
57
- const itemsByTurn = new Map<string, Item[]>();
58
- for (const row of itemRows) {
59
- const item: Item = {
60
- ...row,
61
- data: row.data ? JSON.parse(row.data) : null,
62
- };
63
- const list = itemsByTurn.get(item.turn_id) ?? [];
64
- list.push(item);
65
- itemsByTurn.set(item.turn_id, list);
66
- }
67
-
68
- const detail: SessionDetail = {
69
- ...session,
70
- turns: turnRows.map((row) => ({
71
- ...row,
72
- token_usage: row.token_usage ? JSON.parse(row.token_usage) : null,
73
- attachments: row.attachments ? (JSON.parse(row.attachments) as Attachment[]) : null,
74
- items: itemsByTurn.get(row.id) ?? [],
75
- })),
76
- };
77
-
78
- // Fetch discussion threads for this session and attach to the response
79
- const threadRows = db
80
- .prepare('SELECT * FROM discussion_threads WHERE session_id = ? ORDER BY created_at ASC')
81
- .all(id) as DiscussionThread[];
82
-
83
- if (threadRows.length > 0) {
84
- detail.threads = threadRows.map((tr) => ({
85
- ...tr,
86
- turns: detail.turns.filter((t) => t.thread_id === tr.id),
87
- }));
88
- }
89
-
90
- // Restore persisted context-window usage so the status-bar counter survives reload.
91
- const cuRow = db.prepare('SELECT context_usage FROM sessions WHERE id = ?').get(id) as
92
- | { context_usage: string | null }
93
- | undefined;
94
- if (cuRow?.context_usage) {
95
- try {
96
- detail.contextUsage = JSON.parse(cuRow.context_usage) as SessionDetail['contextUsage'];
97
- } catch {
98
- // Corrupt JSON — leave contextUsage unset.
99
- }
100
- }
101
-
102
- return detail;
103
- });
104
-
105
- // POST /api/sessions — create session
106
- fastify.post<{
107
- Body: { title?: string; runtime_type?: string; model?: string; model_reasoning_effort?: string; cwd?: string; runtime_config?: string };
108
- }>('/api/sessions', {
109
- schema: {
110
- body: {
111
- type: 'object',
112
- properties: {
113
- title: { type: 'string' },
114
- runtime_type: { type: 'string' },
115
- model: { type: ['string', 'null'] },
116
- model_reasoning_effort: { type: ['string', 'null'] },
117
- cwd: { type: ['string', 'null'] },
118
- runtime_config: { type: ['string', 'null'] },
119
- },
120
- },
121
- },
122
- }, async (request) => {
123
- const {
124
- title = null,
125
- runtime_type = 'codex',
126
- model = null,
127
- model_reasoning_effort = null,
128
- cwd = null,
129
- runtime_config = null,
130
- } = request.body || {};
131
- const id = uuidv4();
132
- const now = new Date().toISOString();
133
-
134
- getDb()
135
- .prepare(
136
- `INSERT INTO sessions (id, title, runtime_type, model, model_reasoning_effort, cwd, external_thread_id, status, is_running, created_at, updated_at, runtime_config)
137
- VALUES (?, ?, ?, ?, ?, ?, NULL, 'active', 0, ?, ?, ?)`,
138
- )
139
- .run(id, title, runtime_type, model, model_reasoning_effort, cwd, now, now, runtime_config);
140
-
141
- const session = getDb().prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
142
- return session;
143
- });
144
-
145
- // DELETE /api/sessions/:id — delete session (cascade)
146
- fastify.delete<{ Params: { id: string } }>('/api/sessions/:id', async (request, reply) => {
147
- const { id } = request.params;
148
- const db = getDb();
149
-
150
- const existing = db.prepare('SELECT id FROM sessions WHERE id = ?').get(id);
151
- if (!existing) {
152
- return reply.code(404).send({ error: 'Session not found' });
153
- }
154
-
155
- // Delete items first (turns cascade via FK, but better-sqlite3 needs explicit cascade if pragma is set)
156
- db.prepare('DELETE FROM items WHERE turn_id IN (SELECT id FROM turns WHERE session_id = ?)').run(id);
157
- db.prepare('DELETE FROM turns WHERE session_id = ?').run(id);
158
- db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
159
-
160
- return { success: true };
161
- });
162
-
163
- // PATCH /api/sessions/:id — update session (title, lifecycle, has_unreviewed, model, model_reasoning_effort, runtime_config)
164
- fastify.patch<{
165
- Params: { id: string };
166
- Body: {
167
- title?: string;
168
- lifecycle?: string;
169
- has_unreviewed?: number;
170
- model?: string | null;
171
- model_reasoning_effort?: string | null;
172
- runtime_config?: string | null;
173
- runtime_type?: string;
174
- };
175
- }>('/api/sessions/:id', {
176
- schema: {
177
- body: {
178
- type: 'object',
179
- properties: {
180
- title: { type: 'string' },
181
- lifecycle: { type: 'string' },
182
- has_unreviewed: { type: 'number' },
183
- model: { type: ['string', 'null'] },
184
- model_reasoning_effort: { type: ['string', 'null'] },
185
- runtime_config: { type: ['string', 'null'] },
186
- runtime_type: { type: 'string' },
187
- },
188
- },
189
- },
190
- }, async (request, reply) => {
191
- const { id } = request.params;
192
- const { title, lifecycle, has_unreviewed, model, model_reasoning_effort, runtime_config, runtime_type } = request.body || {};
193
- const db = getDb();
194
-
195
- const existing = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
196
- if (!existing) {
197
- return reply.code(404).send({ error: 'Session not found' });
198
- }
199
-
200
- // Block provider switch while a turn is running
201
- if (runtime_type !== undefined && runtime_type !== existing.runtime_type && existing.is_running) {
202
- return reply.code(409).send({ error: 'Cannot switch provider while a turn is running' });
203
- }
204
-
205
- const now = new Date().toISOString();
206
- const updates: string[] = ['updated_at = ?'];
207
- const params: (string | number | null)[] = [now];
208
-
209
- let providerChanged = false;
210
-
211
- if (title !== undefined) {
212
- updates.push('title = ?');
213
- params.push(title);
214
- }
215
- if (lifecycle !== undefined) {
216
- updates.push('lifecycle = ?');
217
- params.push(lifecycle);
218
- }
219
- if (has_unreviewed !== undefined) {
220
- updates.push('has_unreviewed = ?');
221
- params.push(has_unreviewed);
222
- }
223
-
224
- // Provider switch — clear model/reasoning if not explicitly provided
225
- if (runtime_type !== undefined && runtime_type !== existing.runtime_type) {
226
- providerChanged = true; // different provider can't resume the old thread
227
- updates.push('runtime_type = ?');
228
- params.push(runtime_type);
229
-
230
- // Clear model if not explicitly set in this request
231
- if (model === undefined) {
232
- updates.push('model = ?');
233
- params.push(null);
234
- }
235
- // Clear reasoning if not explicitly set
236
- if (model_reasoning_effort === undefined) {
237
- updates.push('model_reasoning_effort = ?');
238
- params.push(null);
239
- }
240
- }
241
-
242
- // Model and reasoning changes update DB columns but do NOT clear the thread.
243
- // The Claude SDK supports query({ resume: sessionId, model: newModel }) —
244
- // changing the model within the same provider preserves conversation context.
245
- // In streaming-input mode, also call query.setModel() on the live session
246
- // so the CLI switches models without restarting.
247
- if (model !== undefined) {
248
- updates.push('model = ?');
249
- params.push(model);
250
- // If the provider supports mid-session model switching and the model
251
- // actually changed (same provider), update the live query.
252
- if (model !== existing.model && !providerChanged) {
253
- const provider = getProvider(existing.runtime_type);
254
- if (provider.setModel) {
255
- provider.setModel(id, model ?? '').catch((err) => {
256
- console.error(`[patch] setModel failed for session ${id}:`, err);
257
- });
258
- }
259
- }
260
- }
261
- if (model_reasoning_effort !== undefined) {
262
- updates.push('model_reasoning_effort = ?');
263
- params.push(model_reasoning_effort);
264
- }
265
- if (runtime_config !== undefined) {
266
- updates.push('runtime_config = ?');
267
- params.push(runtime_config);
268
- }
269
-
270
- db.prepare(`UPDATE sessions SET ${updates.join(', ')} WHERE id = ?`).run(...params, id);
271
-
272
- // Only clear the thread when the PROVIDER changes (different provider can't resume).
273
- // Model/reasoning changes within the same provider keep the thread intact.
274
- if (providerChanged && existing.external_thread_id) {
275
- db.prepare('UPDATE sessions SET external_thread_id = NULL, rewound_at_turn_id = NULL, pre_rewind_thread_id = NULL, updated_at = ? WHERE id = ?').run(now, id);
276
- // Use old provider to clear its in-memory thread state
277
- getProvider(existing.runtime_type).clearThread(id);
278
- }
279
-
280
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
281
- return session;
282
- });
283
-
284
- // POST /api/sessions/:id/rewind — non-destructive rewind: hide turns after target, fork provider session
285
- fastify.post<{ Params: { id: string }; Body: { turn_id: string } }>(
286
- '/api/sessions/:id/rewind',
287
- {
288
- schema: {
289
- body: {
290
- type: 'object',
291
- required: ['turn_id'],
292
- properties: {
293
- turn_id: { type: 'string' },
294
- },
295
- },
296
- },
297
- },
298
- async (request, reply) => {
299
- const { id } = request.params;
300
- const { turn_id } = request.body || {};
301
- if (!turn_id) {
302
- return reply.code(400).send({ error: 'turn_id is required' });
303
- }
304
-
305
- const db = getDb();
306
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
307
- if (!session) {
308
- return reply.code(404).send({ error: 'Session not found' });
309
- }
310
- if (session.is_running) {
311
- return reply.code(409).send({ error: 'Cannot rewind while a turn is running' });
312
- }
313
-
314
- const targetTurnFull = db.prepare('SELECT * FROM turns WHERE id = ? AND session_id = ?').get(turn_id, id) as
315
- | Turn
316
- | undefined;
317
- if (!targetTurnFull) {
318
- return reply.code(404).send({ error: 'Turn not found' });
319
- }
320
-
321
- const now = new Date().toISOString();
322
-
323
- // Mark the session as rewound (non-destructive — turns are NOT deleted, just hidden)
324
- // Save the original external_thread_id only on the first rewind (don't overwrite on subsequent rewinds)
325
- // Use explicit two-step logic instead of COALESCE to avoid any evaluation-order ambiguity
326
- if (!session.pre_rewind_thread_id && session.external_thread_id) {
327
- db.prepare(
328
- 'UPDATE sessions SET rewound_at_turn_id = ?, pre_rewind_thread_id = ?, updated_at = ? WHERE id = ?',
329
- ).run(turn_id, session.external_thread_id, now, id);
330
- console.log(`[rewind] saved pre_rewind_thread_id=${session.external_thread_id} for session ${id}`);
331
- } else {
332
- db.prepare(
333
- 'UPDATE sessions SET rewound_at_turn_id = ?, updated_at = ? WHERE id = ?',
334
- ).run(turn_id, now, id);
335
- console.log(`[rewind] pre_rewind_thread_id already set (${session.pre_rewind_thread_id}) or external_thread_id is null for session ${id}`);
336
- }
337
-
338
- // Fork the provider-native session so the provider has conversation context up to the target turn
339
- const provider = getProvider(session.runtime_type);
340
- if (provider.forkSession) {
341
- try {
342
- const forkedId = await provider.forkSession(session, targetTurnFull);
343
- if (forkedId) {
344
- db.prepare('UPDATE sessions SET external_thread_id = ?, updated_at = ? WHERE id = ?')
345
- .run(forkedId, now, id);
346
- console.log(`[rewind] forked to ${forkedId} for session ${id}`);
347
- } else {
348
- console.warn(`[rewind] forkSession returned null for session ${id} (external_thread_id=${session.external_thread_id}, upToMessageId=${targetTurnFull.external_turn_id})`);
349
- }
350
- } catch (err) {
351
- console.error(`[rewind] forkSession threw for session ${id}:`, err);
352
- }
353
- }
354
-
355
- // Clear in-memory thread entry (the Claude session ID changed)
356
- provider.clearThread(id);
357
-
358
- const updated = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
359
- return updated;
360
- },
361
- );
362
-
363
- // POST /api/sessions/:id/restore — undo rewind: show all turns again, restore original thread
364
- fastify.post<{ Params: { id: string } }>('/api/sessions/:id/restore', async (request, reply) => {
365
- const { id } = request.params;
366
- const db = getDb();
367
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
368
- if (!session) {
369
- return reply.code(404).send({ error: 'Session not found' });
370
- }
371
- if (!session.rewound_at_turn_id) {
372
- return reply.code(409).send({ error: 'Session is not rewound' });
373
- }
374
-
375
- const now = new Date().toISOString();
376
- // Restore the original external_thread_id and clear rewind markers
377
- db.prepare(
378
- 'UPDATE sessions SET rewound_at_turn_id = NULL, pre_rewind_thread_id = NULL, external_thread_id = COALESCE(pre_rewind_thread_id, external_thread_id), updated_at = ? WHERE id = ?',
379
- ).run(now, id);
380
-
381
- // Clear in-memory thread entry (the Claude session ID changed back)
382
- getProvider(session.runtime_type).clearThread(id);
383
-
384
- const updated = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session;
385
- return updated;
386
- });
387
-
388
- // POST /api/sessions/:id/branch — create new session with turns up to and including target turn
389
- fastify.post<{ Params: { id: string }; Body: { turn_id: string; title?: string } }>(
390
- '/api/sessions/:id/branch',
391
- {
392
- schema: {
393
- body: {
394
- type: 'object',
395
- required: ['turn_id'],
396
- properties: {
397
- turn_id: { type: 'string' },
398
- title: { type: 'string' },
399
- },
400
- },
401
- },
402
- },
403
- async (request, reply) => {
404
- const { id } = request.params;
405
- const { turn_id, title } = request.body || {};
406
- if (!turn_id) {
407
- return reply.code(400).send({ error: 'turn_id is required' });
408
- }
409
-
410
- const db = getDb();
411
- const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Session | undefined;
412
- if (!session) {
413
- return reply.code(404).send({ error: 'Session not found' });
414
- }
415
-
416
- const targetTurn = db.prepare('SELECT created_at FROM turns WHERE id = ? AND session_id = ?').get(turn_id, id) as
417
- { created_at: string } | undefined;
418
- if (!targetTurn) {
419
- return reply.code(404).send({ error: 'Turn not found' });
420
- }
421
-
422
- // Create new session
423
- const newSessionId = uuidv4();
424
- const now = new Date().toISOString();
425
- const newTitle = title || `Branch of ${session.title || session.id.slice(0, 8)}`;
426
-
427
- db.prepare(
428
- `INSERT INTO sessions (id, title, runtime_type, model, model_reasoning_effort, cwd, external_thread_id, status, is_running, lifecycle, has_unreviewed, created_at, updated_at, runtime_config)
429
- VALUES (?, ?, ?, ?, ?, ?, NULL, 'active', 0, 'working', 0, ?, ?, ?)`,
430
- ).run(
431
- newSessionId,
432
- newTitle,
433
- session.runtime_type,
434
- session.model,
435
- session.model_reasoning_effort,
436
- session.cwd,
437
- now,
438
- now,
439
- session.runtime_config,
440
- );
441
-
442
- // Copy turns up to and including the target turn
443
- const turnRows = db
444
- .prepare('SELECT * FROM turns WHERE session_id = ? AND created_at <= ? ORDER BY created_at ASC')
445
- .all(id, targetTurn.created_at) as TurnRow[];
446
-
447
- for (const turn of turnRows) {
448
- const newTurnId = uuidv4();
449
- db.prepare(
450
- `INSERT INTO turns (id, session_id, user_prompt, agent_response, thoughts, token_usage, external_turn_id, status, error, attachments, created_at, updated_at)
451
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
452
- ).run(
453
- newTurnId,
454
- newSessionId,
455
- turn.user_prompt,
456
- turn.agent_response,
457
- turn.thoughts,
458
- turn.token_usage,
459
- turn.external_turn_id,
460
- turn.status,
461
- turn.error,
462
- turn.attachments,
463
- turn.created_at,
464
- turn.updated_at,
465
- );
466
-
467
- // Copy items for this turn
468
- const itemRows = db
469
- .prepare('SELECT * FROM items WHERE turn_id = ? ORDER BY created_at ASC')
470
- .all(turn.id) as ItemRow[];
471
- for (const item of itemRows) {
472
- const newItemId = uuidv4();
473
- db.prepare(
474
- `INSERT INTO items (id, turn_id, external_item_id, type, data, status, created_at, updated_at)
475
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
476
- ).run(
477
- newItemId,
478
- newTurnId,
479
- item.external_item_id,
480
- item.type,
481
- item.data,
482
- item.status,
483
- item.created_at,
484
- item.updated_at,
485
- );
486
- }
487
- }
488
-
489
- // Fork the provider-native session so the provider has conversation context
490
- const provider = getProvider(session.runtime_type);
491
- if (provider.forkSession) {
492
- const targetTurnFull = db.prepare('SELECT * FROM turns WHERE id = ?').get(turn_id) as Turn;
493
- const forkedId = await provider.forkSession(session, targetTurnFull);
494
- if (forkedId) {
495
- db.prepare('UPDATE sessions SET external_thread_id = ?, updated_at = ? WHERE id = ?')
496
- .run(forkedId, now, newSessionId);
497
- }
498
- }
499
-
500
- const newSession = db.prepare('SELECT * FROM sessions WHERE id = ?').get(newSessionId) as Session;
501
- return newSession;
502
- },
503
- );
504
- }
@@ -1,114 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import Fastify, { type FastifyInstance } from 'fastify';
3
-
4
- // Mock shared-app-server to return a mock client
5
- const mockRequest = vi.fn();
6
-
7
- vi.mock('../codex/shared-app-server.ts', () => ({
8
- getSharedAppServer: vi.fn(() => ({
9
- request: mockRequest,
10
- isClosed: vi.fn(() => false),
11
- initialize: vi.fn(() => Promise.resolve()),
12
- close: vi.fn(() => Promise.resolve()),
13
- })),
14
- }));
15
-
16
- // Mock node:fs for readFileSync used in GET /api/skills/:name
17
- vi.mock('node:fs', () => ({
18
- readFileSync: vi.fn(),
19
- }));
20
-
21
- import { registerSkillRoutes } from './skills.ts';
22
- import { readFileSync } from 'node:fs';
23
- import type { SkillsListResult } from '../codex/app-server-types.ts';
24
-
25
- const MOCK_SKILLS_RESULT: SkillsListResult = {
26
- data: [
27
- {
28
- cwd: '/tmp',
29
- skills: [
30
- {
31
- name: 'cloudflare',
32
- description: 'Cloudflare skill',
33
- path: '/home/.codex/skills/cloudflare/SKILL.md',
34
- scope: 'user',
35
- enabled: true,
36
- },
37
- {
38
- name: 'imagegen',
39
- description: 'Image generation skill',
40
- path: '/home/.codex/skills/.system/imagegen/SKILL.md',
41
- scope: 'system',
42
- enabled: true,
43
- interface: {
44
- displayName: 'Image Gen',
45
- shortDescription: 'Generate images',
46
- },
47
- },
48
- ],
49
- errors: [],
50
- },
51
- ],
52
- };
53
-
54
- describe('Skill routes', () => {
55
- let app: FastifyInstance;
56
-
57
- beforeEach(async () => {
58
- vi.clearAllMocks();
59
- mockRequest.mockResolvedValue(MOCK_SKILLS_RESULT);
60
- app = Fastify();
61
- await registerSkillRoutes(app);
62
- });
63
-
64
- describe('GET /api/skills', () => {
65
- it('returns skills from app-server', async () => {
66
- const res = await app.inject({ method: 'GET', url: '/api/skills' });
67
- expect(res.statusCode).toBe(200);
68
- const skills = res.json();
69
- expect(skills).toHaveLength(2);
70
- expect(skills[0].name).toBe('cloudflare');
71
- expect(skills[0].type).toBe('skill');
72
- expect(skills[0].metadata.scope).toBe('user');
73
- expect(skills[1].name).toBe('Image Gen');
74
- expect(skills[1].metadata.scope).toBe('system');
75
- });
76
-
77
- it('calls skills/list with cwd from query', async () => {
78
- await app.inject({ method: 'GET', url: '/api/skills?cwd=/custom/path' });
79
- expect(mockRequest).toHaveBeenCalledWith('skills/list', { cwds: ['/custom/path'] });
80
- });
81
-
82
- it('defaults cwd to process.cwd()', async () => {
83
- await app.inject({ method: 'GET', url: '/api/skills' });
84
- expect(mockRequest).toHaveBeenCalledWith('skills/list', {
85
- cwds: [process.cwd()],
86
- });
87
- });
88
-
89
- it('returns empty array when app-server returns no skills', async () => {
90
- mockRequest.mockResolvedValue({ data: [{ cwd: '/tmp', skills: [], errors: [] }] });
91
- const res = await app.inject({ method: 'GET', url: '/api/skills' });
92
- expect(res.statusCode).toBe(200);
93
- expect(res.json()).toEqual([]);
94
- });
95
- });
96
-
97
- describe('GET /api/skills/:name', () => {
98
- it('returns 404 for unknown skill', async () => {
99
- mockRequest.mockResolvedValue(MOCK_SKILLS_RESULT);
100
- const res = await app.inject({ method: 'GET', url: '/api/skills/nonexistent' });
101
- expect(res.statusCode).toBe(404);
102
- expect(res.json()).toEqual({ error: 'Skill not found' });
103
- });
104
-
105
- it('returns SKILL.md content for known skill', async () => {
106
- vi.mocked(readFileSync).mockReturnValue('# Cloudflare\nSkill content here');
107
- const res = await app.inject({ method: 'GET', url: '/api/skills/cloudflare' });
108
- expect(res.statusCode).toBe(200);
109
- expect(res.json().name).toBe('cloudflare');
110
- expect(res.json().content).toContain('# Cloudflare');
111
- expect(res.json().path).toBe('/home/.codex/skills/cloudflare/SKILL.md');
112
- });
113
- });
114
- });
@@ -1,70 +0,0 @@
1
- import type { FastifyInstance } from 'fastify';
2
- import { readFileSync } from 'node:fs';
3
- import type { Extension } from '@agentpane/shared';
4
- import { getSharedAppServer } from '../codex/shared-app-server.ts';
5
- import type { SkillsListResult, AppServerSkill } from '../codex/app-server-types.ts';
6
-
7
- /** Map an app-server skill to the shared Extension type. */
8
- function mapSkill(skill: AppServerSkill): Extension {
9
- return {
10
- id: skill.name,
11
- name: skill.interface?.displayName ?? skill.name,
12
- description: skill.shortDescription ?? skill.interface?.shortDescription ?? skill.description,
13
- type: 'skill',
14
- enabled: skill.enabled,
15
- metadata: {
16
- scope: skill.scope,
17
- path: skill.path,
18
- },
19
- };
20
- }
21
-
22
- /** Register skill routes. */
23
- export async function registerSkillRoutes(fastify: FastifyInstance): Promise<void> {
24
- // GET /api/skills — list all skills from the app-server
25
- fastify.get<{ Querystring: { cwd?: string } }>('/api/skills', async (request) => {
26
- const cwd = request.query.cwd ?? process.cwd();
27
- const result = await getSharedAppServer().request<SkillsListResult>('skills/list', { cwds: [cwd] });
28
-
29
- // Flatten all skills from all cwds in the response
30
- const allSkills: Extension[] = [];
31
- for (const entry of result.data) {
32
- for (const skill of entry.skills) {
33
- allSkills.push(mapSkill(skill));
34
- }
35
- }
36
- return allSkills;
37
- });
38
-
39
- // GET /api/skills/:name — get full SKILL.md content
40
- fastify.get<{ Params: { name: string }; Querystring: { cwd?: string } }>(
41
- '/api/skills/:name',
42
- async (request, reply) => {
43
- const { name } = request.params;
44
- const cwd = request.query.cwd ?? process.cwd();
45
-
46
- // Fetch skills list to find the path for this skill
47
- const result = await getSharedAppServer().request<SkillsListResult>('skills/list', { cwds: [cwd] });
48
-
49
- let skillPath: string | null = null;
50
- for (const entry of result.data) {
51
- const skill = entry.skills.find((s) => s.name === name);
52
- if (skill) {
53
- skillPath = skill.path;
54
- break;
55
- }
56
- }
57
-
58
- if (!skillPath) {
59
- return reply.code(404).send({ error: 'Skill not found' });
60
- }
61
-
62
- try {
63
- const content = readFileSync(skillPath, 'utf-8');
64
- return { name, content, path: skillPath };
65
- } catch {
66
- return reply.code(500).send({ error: 'Failed to read skill' });
67
- }
68
- },
69
- );
70
- }