@starlove/orbit 1.4.1 → 2.0.0

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 (329) hide show
  1. package/Dockerfile +10 -8
  2. package/README.md +146 -164
  3. package/dist/full/csp.txt +1 -0
  4. package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
  5. package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
  6. package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
  7. package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
  8. package/dist/full/fonts/Mona_Sans/README.txt +10 -0
  9. package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
  10. package/dist/full/fonts/Newsreader/README.txt +8 -0
  11. package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
  12. package/dist/full/index.html +36 -653
  13. package/dist/full/js/app.BmuponPJ.js +32 -0
  14. package/dist/full/js/app.BmuponPJ.js.map +1 -0
  15. package/dist/full/js/chunk.1fJGQGbW.js +2 -0
  16. package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
  17. package/dist/full/js/chunk.5D7JO2zp.js +2 -0
  18. package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
  19. package/dist/full/js/chunk.BrNn_D79.js +2 -0
  20. package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
  21. package/dist/full/js/chunk.CokJXIqM.js +2 -0
  22. package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
  23. package/dist/full/js/chunk.D_Haqy6R.js +2 -0
  24. package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
  25. package/dist/full/js/chunk.RO0jsH6I.js +15 -0
  26. package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
  27. package/dist/full/js/chunk.dqWOEtBM.js +2 -0
  28. package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
  29. package/dist/full/js/warm.f260bbbc.js +15 -0
  30. package/dist/full/orbits-favicon.png +0 -0
  31. package/dist/full/orbits-logo-bg-wide.png +0 -0
  32. package/dist/full/orbits-logo-bg.png +0 -0
  33. package/dist/full/orbits-logo.png +0 -0
  34. package/dist/full/styles.2b154ffe.css +1 -0
  35. package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
  36. package/dist/full/version.json +2 -2
  37. package/docs/AGENT_GUIDE.md +114 -0
  38. package/docs/UPDATING.md +249 -64
  39. package/lib/adapters/indexeddb/blobs.d.ts +3 -0
  40. package/lib/adapters/indexeddb/blobs.js +54 -0
  41. package/lib/adapters/indexeddb/database.d.ts +60 -0
  42. package/lib/adapters/indexeddb/database.js +126 -0
  43. package/lib/adapters/indexeddb/index.d.ts +5 -0
  44. package/lib/adapters/indexeddb/index.js +3 -0
  45. package/lib/adapters/indexeddb/locks.d.ts +14 -0
  46. package/lib/adapters/indexeddb/locks.js +40 -0
  47. package/lib/adapters/indexeddb/store.d.ts +40 -0
  48. package/lib/adapters/indexeddb/store.js +424 -0
  49. package/lib/adapters/indexeddb/tables.d.ts +43 -0
  50. package/lib/adapters/indexeddb/tables.js +127 -0
  51. package/lib/adapters/memory/blobs.d.ts +3 -0
  52. package/lib/adapters/memory/blobs.js +44 -0
  53. package/lib/adapters/memory/board.d.ts +41 -0
  54. package/lib/adapters/memory/board.js +521 -0
  55. package/lib/adapters/memory/index.d.ts +2 -0
  56. package/lib/adapters/memory/index.js +2 -0
  57. package/lib/adapters/memory/search.d.ts +6 -0
  58. package/lib/adapters/memory/search.js +30 -0
  59. package/lib/adapters/memory/store.d.ts +34 -0
  60. package/lib/adapters/memory/store.js +266 -0
  61. package/lib/adapters/sqlite/backups.d.ts +53 -0
  62. package/lib/adapters/sqlite/backups.js +232 -0
  63. package/lib/adapters/sqlite/blobs.d.ts +14 -0
  64. package/lib/adapters/sqlite/blobs.js +109 -0
  65. package/lib/adapters/sqlite/board.d.ts +77 -0
  66. package/lib/adapters/sqlite/board.js +328 -0
  67. package/lib/adapters/sqlite/database.d.ts +67 -0
  68. package/lib/adapters/sqlite/database.js +285 -0
  69. package/lib/adapters/sqlite/index.d.ts +3 -0
  70. package/lib/adapters/sqlite/index.js +2 -0
  71. package/lib/adapters/sqlite/lock.d.ts +11 -0
  72. package/lib/adapters/sqlite/lock.js +34 -0
  73. package/lib/adapters/sqlite/records.d.ts +54 -0
  74. package/lib/adapters/sqlite/records.js +546 -0
  75. package/lib/adapters/sqlite/schema.d.ts +54 -0
  76. package/lib/adapters/sqlite/schema.js +276 -0
  77. package/lib/adapters/sqlite/store.d.ts +39 -0
  78. package/lib/adapters/sqlite/store.js +385 -0
  79. package/lib/browser/in-page.d.ts +31 -0
  80. package/lib/browser/in-page.js +46 -0
  81. package/lib/browser/old-preview.d.ts +13 -0
  82. package/lib/browser/old-preview.js +80 -0
  83. package/lib/browser/preview-board.d.ts +8 -0
  84. package/lib/browser/preview-board.js +69 -0
  85. package/lib/cli/main.d.ts +7 -0
  86. package/lib/cli/main.js +343 -0
  87. package/lib/cli/orbit.d.ts +8 -0
  88. package/lib/cli/orbit.js +25 -0
  89. package/lib/client/http.d.ts +38 -0
  90. package/lib/client/http.js +334 -0
  91. package/lib/client/local.d.ts +26 -0
  92. package/lib/client/local.js +190 -0
  93. package/lib/client/object-url.d.ts +8 -0
  94. package/lib/client/object-url.js +21 -0
  95. package/lib/client/types.d.ts +268 -0
  96. package/lib/client/types.js +8 -0
  97. package/lib/commands/agent-tools.d.ts +31 -0
  98. package/lib/commands/agent-tools.js +100 -0
  99. package/lib/commands/index.d.ts +56 -0
  100. package/lib/commands/index.js +298 -0
  101. package/lib/commands/parse.d.ts +73 -0
  102. package/lib/commands/parse.js +231 -0
  103. package/lib/commands/present.d.ts +134 -0
  104. package/lib/commands/present.js +198 -0
  105. package/lib/commands/session.d.ts +85 -0
  106. package/lib/commands/session.js +138 -0
  107. package/lib/commands/verbs.d.ts +35 -0
  108. package/lib/commands/verbs.js +842 -0
  109. package/lib/core/actors.d.ts +18 -0
  110. package/lib/core/actors.js +45 -0
  111. package/lib/core/agent.d.ts +128 -0
  112. package/lib/core/agent.js +323 -0
  113. package/lib/core/boards.d.ts +113 -0
  114. package/lib/core/boards.js +288 -0
  115. package/lib/core/bootstrap.d.ts +47 -0
  116. package/lib/core/bootstrap.js +100 -0
  117. package/lib/core/bytes.d.ts +10 -0
  118. package/lib/core/bytes.js +52 -0
  119. package/lib/core/card-paths.d.ts +25 -0
  120. package/lib/core/card-paths.js +44 -0
  121. package/lib/core/context.d.ts +60 -0
  122. package/lib/core/context.js +42 -0
  123. package/lib/core/cross-board.d.ts +81 -0
  124. package/lib/core/cross-board.js +228 -0
  125. package/lib/core/describe.d.ts +21 -0
  126. package/lib/core/describe.js +81 -0
  127. package/lib/core/errors.d.ts +159 -0
  128. package/lib/core/errors.js +160 -0
  129. package/lib/core/files.d.ts +79 -0
  130. package/lib/core/files.js +262 -0
  131. package/lib/core/history.d.ts +20 -0
  132. package/lib/core/history.js +53 -0
  133. package/lib/core/image-types.d.ts +16 -0
  134. package/lib/core/image-types.js +36 -0
  135. package/lib/core/model.d.ts +263 -0
  136. package/lib/core/model.js +34 -0
  137. package/lib/core/nebula-link.d.ts +14 -0
  138. package/lib/core/nebula-link.js +39 -0
  139. package/lib/core/relations.d.ts +58 -0
  140. package/lib/core/relations.js +112 -0
  141. package/lib/core/search.d.ts +38 -0
  142. package/lib/core/search.js +92 -0
  143. package/lib/core/sessions.d.ts +44 -0
  144. package/lib/core/sessions.js +158 -0
  145. package/lib/core/snapshot-schema.d.ts +295 -0
  146. package/lib/core/snapshot-schema.js +82 -0
  147. package/lib/core/snapshots.d.ts +75 -0
  148. package/lib/core/snapshots.js +409 -0
  149. package/lib/core/states.d.ts +25 -0
  150. package/lib/core/states.js +126 -0
  151. package/lib/core/text.d.ts +40 -0
  152. package/lib/core/text.js +113 -0
  153. package/lib/core/tickets.d.ts +69 -0
  154. package/lib/core/tickets.js +311 -0
  155. package/lib/core/trello.d.ts +17 -0
  156. package/lib/core/trello.js +269 -0
  157. package/lib/core/verdicts.d.ts +28 -0
  158. package/lib/core/verdicts.js +92 -0
  159. package/lib/core/views.d.ts +95 -0
  160. package/lib/core/views.js +186 -0
  161. package/lib/http/app.d.ts +46 -0
  162. package/lib/http/app.js +491 -0
  163. package/lib/mcp-server.d.ts +10 -0
  164. package/lib/mcp-server.js +120 -0
  165. package/lib/node/assets.d.ts +7 -0
  166. package/lib/node/assets.js +68 -0
  167. package/lib/node/data-folder.d.ts +21 -0
  168. package/lib/node/data-folder.js +34 -0
  169. package/lib/node/env.d.ts +38 -0
  170. package/lib/node/env.js +49 -0
  171. package/lib/node/host-check.d.ts +24 -0
  172. package/lib/node/host-check.js +53 -0
  173. package/lib/node/host-services.d.ts +9 -0
  174. package/lib/node/host-services.js +150 -0
  175. package/lib/node/local.d.ts +22 -0
  176. package/lib/node/local.js +61 -0
  177. package/lib/node/migrate.d.ts +53 -0
  178. package/lib/node/migrate.js +635 -0
  179. package/lib/node/package.d.ts +7 -0
  180. package/lib/node/package.js +14 -0
  181. package/lib/node/repo-board.d.ts +49 -0
  182. package/lib/node/repo-board.js +245 -0
  183. package/lib/node/serve.d.ts +8 -0
  184. package/lib/node/serve.js +106 -0
  185. package/lib/orbit.d.ts +47 -0
  186. package/lib/orbit.js +44 -0
  187. package/lib/quiet-sqlite-warning.d.ts +1 -0
  188. package/lib/quiet-sqlite-warning.js +18 -0
  189. package/lib/store/blob-path.d.ts +6 -0
  190. package/lib/store/blob-path.js +12 -0
  191. package/lib/store/conformance.d.ts +52 -0
  192. package/lib/store/conformance.js +1449 -0
  193. package/lib/store/search-words.d.ts +10 -0
  194. package/lib/store/search-words.js +19 -0
  195. package/lib/store/types.d.ts +452 -0
  196. package/lib/store/types.js +34 -0
  197. package/llms.txt +63 -0
  198. package/package.json +109 -25
  199. package/src/cli/orbit.js +11 -658
  200. package/src/mcp-server.js +16 -876
  201. package/SKILL-ORBIT.md +0 -151
  202. package/build.mjs +0 -210
  203. package/dist/full/js/app.4e6fbe20.js +0 -952
  204. package/dist/full/js/app.4e6fbe20.js.map +0 -7
  205. package/dist/full/styles.4e60d824.css +0 -1
  206. package/docs/AGENT_PROTOCOL.md +0 -424
  207. package/docs/MCP_SETUP.md +0 -54
  208. package/docs/ORBIT_DISPATCH.md +0 -280
  209. package/docs/SS-Orbit-Dark.png +0 -0
  210. package/docs/SS-Orbit-Light.png +0 -0
  211. package/docs/mcp-client.example.json +0 -13
  212. package/docs/setup/orbit-init-wizard.md +0 -174
  213. package/public/apple-touch-icon.png +0 -0
  214. package/public/cursors/live_arrow.cur +0 -0
  215. package/public/cursors/live_link.cur +0 -0
  216. package/public/cursors/live_move.cur +0 -0
  217. package/public/cursors/live_select.cur +0 -0
  218. package/public/cursors/live_unavail.cur +0 -0
  219. package/public/favicon-32x32.png +0 -0
  220. package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
  221. package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
  222. package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
  223. package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
  224. package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
  225. package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
  226. package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
  227. package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
  228. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
  229. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
  230. package/public/fonts/Chakra_Petch/OFL.txt +0 -93
  231. package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
  232. package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
  233. package/public/fonts/Inter/OFL.txt +0 -93
  234. package/public/fonts/Inter/README.txt +0 -118
  235. package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  236. package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
  237. package/public/fonts/JetBrains_Mono/README.txt +0 -79
  238. package/public/fonts/OpenRing-Regular.ttf +0 -0
  239. package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  240. package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  241. package/public/fonts/Open_Sans/README.txt +0 -100
  242. package/public/fonts/Orbitron/OFL.txt +0 -93
  243. package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
  244. package/public/fonts/Orbitron/README.txt +0 -68
  245. package/public/fonts/Rajdhani/OFL.txt +0 -93
  246. package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
  247. package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
  248. package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
  249. package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
  250. package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
  251. package/public/fonts/runescape-uf.ttf +0 -0
  252. package/public/index.html +0 -769
  253. package/public/js/actor-labels.js +0 -65
  254. package/public/js/api.js +0 -39
  255. package/public/js/app.js +0 -45
  256. package/public/js/artifact-drawer.js +0 -79
  257. package/public/js/board-export.js +0 -161
  258. package/public/js/board-menu.js +0 -374
  259. package/public/js/card-actions.js +0 -545
  260. package/public/js/config.js +0 -20
  261. package/public/js/create-card.js +0 -190
  262. package/public/js/dom.js +0 -19
  263. package/public/js/drawer.js +0 -273
  264. package/public/js/font-preference.js +0 -230
  265. package/public/js/format.js +0 -433
  266. package/public/js/help-menu.js +0 -31
  267. package/public/js/icons.js +0 -16
  268. package/public/js/kanban.js +0 -694
  269. package/public/js/local-backend.js +0 -1015
  270. package/public/js/main.js +0 -161
  271. package/public/js/modal.js +0 -33
  272. package/public/js/motion-preference.js +0 -64
  273. package/public/js/panel.js +0 -58
  274. package/public/js/router.js +0 -134
  275. package/public/js/search.js +0 -56
  276. package/public/js/settings-tabs.js +0 -7
  277. package/public/js/settings.js +0 -1634
  278. package/public/js/sse.js +0 -159
  279. package/public/js/starscape.js +0 -332
  280. package/public/js/state.js +0 -129
  281. package/public/js/text-editing.js +0 -128
  282. package/public/js/theme-preference.js +0 -95
  283. package/public/js/ticket-detail.js +0 -1516
  284. package/public/js/ticket-typeahead.js +0 -49
  285. package/public/js/toast.js +0 -89
  286. package/public/js/topbar-notes.js +0 -155
  287. package/public/js/transport.js +0 -22
  288. package/public/js/typeahead.js +0 -206
  289. package/public/js/unread.js +0 -121
  290. package/public/js/update-check.js +0 -58
  291. package/public/js/url-routes.js +0 -94
  292. package/public/orbits-favicon.png +0 -0
  293. package/public/orbits-logo-bg-wide.png +0 -0
  294. package/public/orbits-logo-bg.png +0 -0
  295. package/public/orbits-logo.png +0 -0
  296. package/public/styles.css +0 -7709
  297. package/src/core/agent.js +0 -387
  298. package/src/core/agents-md.js +0 -109
  299. package/src/core/artifacts.js +0 -149
  300. package/src/core/attachments.js +0 -238
  301. package/src/core/auth.js +0 -85
  302. package/src/core/backups.js +0 -142
  303. package/src/core/board.js +0 -148
  304. package/src/core/boards.js +0 -409
  305. package/src/core/bootstrap.js +0 -170
  306. package/src/core/css-bundle.js +0 -30
  307. package/src/core/db.js +0 -315
  308. package/src/core/delete-board.js +0 -163
  309. package/src/core/dispatch.js +0 -1007
  310. package/src/core/events.js +0 -314
  311. package/src/core/http.js +0 -115
  312. package/src/core/migrate.js +0 -198
  313. package/src/core/paths.js +0 -55
  314. package/src/core/provision-repo-board.js +0 -119
  315. package/src/core/queries.js +0 -246
  316. package/src/core/registry.js +0 -211
  317. package/src/core/relations.js +0 -163
  318. package/src/core/review-verdicts.js +0 -138
  319. package/src/core/router.js +0 -556
  320. package/src/core/search.js +0 -183
  321. package/src/core/seed.js +0 -234
  322. package/src/core/snapshots.js +0 -692
  323. package/src/core/states.js +0 -167
  324. package/src/core/system-picker.js +0 -109
  325. package/src/core/tickets.js +0 -400
  326. package/src/core/util.js +0 -106
  327. package/src/core/version-check.js +0 -154
  328. package/src/mcp/orbit-client.js +0 -287
  329. package/src/server.js +0 -115
@@ -1,556 +0,0 @@
1
- // HTTP router. Every request resolves its own per-call `ctx = {actor, board,
2
- // db}` from the request itself — there is intentionally no module-level
3
- // "active board" pointer that could race across concurrent requests. If a
4
- // request reaches a board-scoped route without enough information to pick a
5
- // board, it is rejected with 400 board_id_required rather than silently
6
- // inheriting some other caller's recent state.
7
-
8
- import { actorFromHttpRequest } from "./auth.js";
9
- import { scheduleAutomaticBoardBackup } from "./backups.js";
10
- import { readJson, readRaw, sendFile, sendJson, setCors } from "./http.js";
11
- import {
12
- findBoardForBoardEntry,
13
- findBoardForRelation,
14
- findBoardForState,
15
- findBoardForTicket,
16
- getBoardByRegistryId,
17
- getBoardBySlug,
18
- listBoards,
19
- openBoardDb,
20
- touchBoardActive
21
- } from "./registry.js";
22
- import { getBootstrap } from "./bootstrap.js";
23
- import { deleteRegisteredBoard } from "./delete-board.js";
24
- import {
25
- createBoard,
26
- createBoardEntry,
27
- getBoardContext,
28
- updateBoard,
29
- updateBoardEntry
30
- } from "./boards.js";
31
- import { exportBoard, importBoardSnapshot, importBoardSnapshotAsNewBoard } from "./snapshots.js";
32
- import {
33
- createState,
34
- deleteState,
35
- reorderStates,
36
- updateState
37
- } from "./states.js";
38
- import {
39
- archiveTicket,
40
- createComment,
41
- createTicket,
42
- deleteTicket,
43
- getTicketStatusHistory,
44
- restoreTicket,
45
- updateCommentAiOmission,
46
- updateTicket
47
- } from "./tickets.js";
48
- import { archivedTicketsForBoard } from "./queries.js";
49
- import { httpError } from "./util.js";
50
- import { getVersionInfo, startSelfUpdate } from "./version-check.js";
51
- import { pickFolder } from "./system-picker.js";
52
- import {
53
- createRelation,
54
- deleteRelation,
55
- getTicketBlockers,
56
- getTicketRelations
57
- } from "./relations.js";
58
- import { searchTickets } from "./search.js";
59
- import {
60
- createTicketAttachment,
61
- deleteTicketAttachment,
62
- getTicketAttachmentContent,
63
- listTicketAttachments,
64
- MAX_IMAGE_ATTACHMENT_BYTES
65
- } from "./attachments.js";
66
- import {
67
- checkpointTicket,
68
- claimNext,
69
- completeTicket,
70
- getAgentDispatchPacket,
71
- getContextPack,
72
- getContextPackFull,
73
- readComments,
74
- readTicket
75
- } from "./agent.js";
76
- import { createReviewVerdict, getReviewVerdict, listReviewVerdicts } from "./review-verdicts.js";
77
- import { getTicketMarkdownArtifact, storeTicketMarkdownArtifact } from "./artifacts.js";
78
-
79
- /** Build a ctx for an already-resolved registry row + actor. */
80
- function ctxFromBoardRow(boardRow, actor) {
81
- return { actor, board: boardRow, db: openBoardDb(boardRow) };
82
- }
83
-
84
- function sendMutationJson(res, status, body, ctx) {
85
- sendJson(res, status, body);
86
- scheduleAutomaticBoardBackup(ctx.board, ctx.db);
87
- }
88
-
89
- /** Resolve a board from a hint (id or slug) or null. Throws 404 if a hint
90
- * was given but no board matches. */
91
- function resolveBoardFromHint(idHint, slugHint) {
92
- if (idHint) {
93
- const row = getBoardByRegistryId(idHint);
94
- if (!row) throw httpError(404, "board_not_found");
95
- return row;
96
- }
97
- if (slugHint) {
98
- const row = getBoardBySlug(slugHint);
99
- if (!row) throw httpError(404, "board_not_found");
100
- return row;
101
- }
102
- return null;
103
- }
104
-
105
- /** Read board hints from query string (or null). */
106
- function boardHintsFromQuery(url) {
107
- return {
108
- boardId: url.searchParams.get("board_id"),
109
- boardSlug: url.searchParams.get("board")
110
- };
111
- }
112
-
113
- function agentContextOptions(url) {
114
- return {
115
- max_chars_per_field: url.searchParams.get("max_chars_per_field"),
116
- comment_limit: url.searchParams.get("comment_limit"),
117
- include_parent_full: url.searchParams.get("include_parent_full") === "true",
118
- include_related_full: url.searchParams.get("include_related_full") === "true"
119
- };
120
- }
121
-
122
- export async function handleApi(req, res, url) {
123
- setCors(res);
124
-
125
- if (req.method === "GET" && url.pathname === "/api/health") {
126
- sendJson(res, 200, { ok: true });
127
- return;
128
- }
129
-
130
- // Update check (no actor needed). The client polls this on load and on a
131
- // manual "Check now" (?refresh=1 bypasses the server's cached result).
132
- if (req.method === "GET" && url.pathname === "/api/version") {
133
- sendJson(res, 200, await getVersionInfo({ force: url.searchParams.get("refresh") === "1" }));
134
- return;
135
- }
136
-
137
- // Opt-in self-update. Hidden (404) unless the operator set ORBIT_SELF_UPDATE=1,
138
- // since it pulls + rebuilds + restarts the running server.
139
- if (req.method === "POST" && url.pathname === "/api/update") {
140
- if (process.env.ORBIT_SELF_UPDATE !== "1") throw httpError(404, "not_found");
141
- sendJson(res, 202, startSelfUpdate());
142
- return;
143
- }
144
-
145
- const actor = actorFromHttpRequest(req);
146
-
147
- // Bootstrap: read-only registry + one chosen board. No global state mutated.
148
- if (req.method === "GET" && url.pathname === "/api/bootstrap") {
149
- sendJson(res, 200, getBootstrap(actor, url.searchParams.get("board_id"), url.searchParams.get("board")));
150
- return;
151
- }
152
-
153
- /** Registry-only board list (small JSON). For tickets/lanes/labels use `GET /api/bootstrap` or open a board context route. */
154
- if (req.method === "GET" && url.pathname === "/api/boards") {
155
- sendJson(res, 200, {
156
- boards: listBoards().map((row) => ({
157
- id: row.id,
158
- slug: row.slug,
159
- name: row.name,
160
- repo_path: row.repo_path,
161
- repo_url: row.repo_url,
162
- default_branch: row.default_branch,
163
- db_path: row.db_path,
164
- last_active_at: row.last_active_at ?? null
165
- }))
166
- });
167
- return;
168
- }
169
-
170
- if (req.method === "POST" && url.pathname === "/api/system/pick-folder") {
171
- const result = await pickFolder();
172
- if (result.unsupported) throw httpError(501, "folder_picker_unavailable");
173
- sendJson(res, 200, result);
174
- return;
175
- }
176
-
177
- // Board CRUD that doesn't take a per-board ctx.
178
- if (req.method === "POST" && url.pathname === "/api/boards") {
179
- const body = await readJson(req);
180
- const created = createBoard(body, { actor });
181
- const row = getBoardByRegistryId(created.id);
182
- if (row) scheduleAutomaticBoardBackup(row, openBoardDb(row));
183
- sendJson(res, 201, created);
184
- return;
185
- }
186
-
187
- // Path-prefixed board routes: /api/boards/:slugOrId/...
188
- const boardPrefix = url.pathname.match(/^\/api\/boards\/([^/]+)(?:\/(.*))?$/);
189
- if (boardPrefix) {
190
- const ident = decodeURIComponent(boardPrefix[1]);
191
- const sub = boardPrefix[2] || "";
192
- const row = getBoardByRegistryId(ident) || getBoardBySlug(ident);
193
- if (!row) throw httpError(404, "board_not_found");
194
-
195
- if (sub === "" && req.method === "DELETE") {
196
- const body = await readJson(req);
197
- sendJson(res, 200, deleteRegisteredBoard(row, body, actor));
198
- return;
199
- }
200
-
201
- const ctx = ctxFromBoardRow(row, actor);
202
- touchBoardActive(row.id);
203
-
204
- if (sub === "" && req.method === "PATCH") {
205
- const body = await readJson(req);
206
- sendMutationJson(res, 200, updateBoard(row.id, body, ctx), ctx);
207
- return;
208
- }
209
- if (sub === "context" && req.method === "GET") {
210
- sendJson(res, 200, getBoardContext(row.id, ctx, {
211
- includeStruck: url.searchParams.get("include_struck") === "true"
212
- }));
213
- return;
214
- }
215
- if (sub === "archive" && req.method === "GET") {
216
- sendJson(res, 200, { tickets: archivedTicketsForBoard(ctx.db, row.id) });
217
- return;
218
- }
219
- if (sub === "entries" && req.method === "POST") {
220
- const body = await readJson(req);
221
- sendMutationJson(res, 201, createBoardEntry(row.id, body, ctx), ctx);
222
- return;
223
- }
224
- if (sub === "export" && req.method === "GET") {
225
- sendJson(res, 200, exportBoard(row.id, ctx, {
226
- includeAttachments: url.searchParams.get("include_attachments") === "true" || url.searchParams.get("include_images") === "true"
227
- }));
228
- return;
229
- }
230
- if (sub === "states" && req.method === "POST") {
231
- const body = await readJson(req);
232
- sendMutationJson(res, 201, createState(row.id, body, ctx), ctx);
233
- return;
234
- }
235
- if (sub === "states" && req.method === "PATCH") {
236
- const body = await readJson(req);
237
- sendMutationJson(res, 200, reorderStates(row.id, body, ctx), ctx);
238
- return;
239
- }
240
- sendJson(res, 404, { error: "not_found" });
241
- return;
242
- }
243
-
244
- // /api/tickets POST — body.board_id required.
245
- if (req.method === "POST" && url.pathname === "/api/tickets") {
246
- const body = await readJson(req);
247
- const hintId = body.board_id || url.searchParams.get("board_id");
248
- const hintSlug = body.board || url.searchParams.get("board");
249
- const row = resolveBoardFromHint(hintId, hintSlug);
250
- if (!row) throw httpError(400, "board_id_required");
251
- const ctx = ctxFromBoardRow(row, actor);
252
- sendMutationJson(res, 201, createTicket(body, ctx), ctx);
253
- return;
254
- }
255
-
256
- // /api/tickets/lookup GET — exact lightweight lookup by number or title.
257
- if (req.method === "GET" && url.pathname === "/api/tickets/lookup") {
258
- const { boardId, boardSlug } = boardHintsFromQuery(url);
259
- const row = resolveBoardFromHint(boardId, boardSlug);
260
- if (!row) throw httpError(400, "board_id_required");
261
- const lookup = {};
262
- if (url.searchParams.has("number")) lookup.number = url.searchParams.get("number");
263
- if (url.searchParams.has("title")) lookup.title = url.searchParams.get("title");
264
- sendJson(res, 200, readTicket(lookup, ctxFromBoardRow(row, actor)));
265
- return;
266
- }
267
-
268
- // /api/tickets/:id/... — board resolved from query hint or via lookup.
269
- const ticketPath = url.pathname.match(/^\/api\/tickets\/([^/]+)(?:\/(.*))?$/);
270
- if (ticketPath) {
271
- const ticketId = decodeURIComponent(ticketPath[1]);
272
- const sub = ticketPath[2] || "";
273
- const { boardId, boardSlug } = boardHintsFromQuery(url);
274
- const hintRow = resolveBoardFromHint(boardId, boardSlug);
275
- const found = findBoardForTicket(ticketId, hintRow?.id || null);
276
- if (!found) throw httpError(404, "ticket_not_found");
277
- const ctx = ctxFromBoardRow(found.board, actor);
278
-
279
- if (sub === "" && req.method === "GET") {
280
- sendJson(res, 200, readTicket(ticketId, ctx));
281
- return;
282
- }
283
- if (sub === "" && req.method === "PATCH") {
284
- const body = await readJson(req);
285
- sendMutationJson(res, 200, updateTicket(ticketId, body, ctx), ctx);
286
- return;
287
- }
288
- if (sub === "" && req.method === "DELETE") {
289
- sendMutationJson(res, 200, deleteTicket(ticketId, ctx), ctx);
290
- return;
291
- }
292
- if (sub === "archive" && req.method === "POST") {
293
- sendMutationJson(res, 200, archiveTicket(ticketId, ctx), ctx);
294
- return;
295
- }
296
- if (sub === "restore" && req.method === "POST") {
297
- sendMutationJson(res, 200, restoreTicket(ticketId, ctx), ctx);
298
- return;
299
- }
300
- if (sub === "context" && req.method === "GET") {
301
- const depth = Number(url.searchParams.get("depth") || 1);
302
- sendJson(res, 200, getContextPack(ticketId, ctx, depth, agentContextOptions(url)));
303
- return;
304
- }
305
- if (sub === "context/full" && req.method === "GET") {
306
- const depth = Number(url.searchParams.get("depth") || 1);
307
- sendJson(res, 200, getContextPackFull(ticketId, ctx, depth, agentContextOptions(url)));
308
- return;
309
- }
310
- if ((sub === "dispatch-packet" || sub === "agent-dispatch-packet") && req.method === "GET") {
311
- sendJson(res, 200, getAgentDispatchPacket(ticketId, ctx, agentContextOptions(url)));
312
- return;
313
- }
314
- if (sub === "history" && req.method === "GET") {
315
- sendJson(res, 200, getTicketStatusHistory(ticketId, ctx));
316
- return;
317
- }
318
- if (sub === "relations" && req.method === "GET") {
319
- sendJson(res, 200, getTicketRelations(ticketId, ctx));
320
- return;
321
- }
322
- if (sub === "blockers" && req.method === "GET") {
323
- sendJson(res, 200, getTicketBlockers(ticketId, ctx));
324
- return;
325
- }
326
- if (sub === "comments" && req.method === "GET") {
327
- sendJson(res, 200, readComments(ticketId, ctx));
328
- return;
329
- }
330
- if (sub === "artifacts/markdown" && req.method === "GET") {
331
- sendJson(res, 200, getTicketMarkdownArtifact(ticketId, url.searchParams.get("path"), ctx));
332
- return;
333
- }
334
- if (sub === "artifacts/markdown" && req.method === "POST") {
335
- const body = await readJson(req);
336
- sendJson(res, 201, storeTicketMarkdownArtifact(ticketId, body, ctx));
337
- return;
338
- }
339
- if (sub === "comments" && req.method === "POST") {
340
- const body = await readJson(req);
341
- sendMutationJson(res, 201, createComment(ticketId, body, ctx), ctx);
342
- return;
343
- }
344
- const commentPath = sub.match(/^comments\/([^/]+)$/);
345
- if (commentPath && req.method === "PATCH") {
346
- const body = await readJson(req);
347
- sendMutationJson(res, 200, updateCommentAiOmission(ticketId, decodeURIComponent(commentPath[1]), body, ctx), ctx);
348
- return;
349
- }
350
- if (sub === "review-verdicts" && req.method === "GET") {
351
- sendJson(res, 200, { review_verdicts: listReviewVerdicts(ticketId, ctx, { limit: url.searchParams.get("limit") }) });
352
- return;
353
- }
354
- if (sub === "review-verdicts" && req.method === "POST") {
355
- const body = await readJson(req);
356
- sendMutationJson(res, 201, createReviewVerdict(ticketId, body, ctx), ctx);
357
- return;
358
- }
359
- if (sub === "attachments" && req.method === "GET") {
360
- sendJson(res, 200, listTicketAttachments(ticketId, ctx));
361
- return;
362
- }
363
- if (sub === "attachments" && req.method === "POST") {
364
- const bytes = await readRaw(req, MAX_IMAGE_ATTACHMENT_BYTES);
365
- const attachment = createTicketAttachment(ticketId, {
366
- bytes,
367
- mime_type: req.headers["content-type"],
368
- original_name: req.headers["x-file-name"] || url.searchParams.get("filename") || "image"
369
- }, ctx);
370
- sendMutationJson(res, 201, attachment, ctx);
371
- return;
372
- }
373
- const attachmentContentPath = sub.match(/^attachments\/([^/]+)\/content$/);
374
- if (attachmentContentPath && req.method === "GET") {
375
- const result = getTicketAttachmentContent(ticketId, decodeURIComponent(attachmentContentPath[1]), ctx);
376
- sendFile(res, result.filePath, result.row.mime_type);
377
- return;
378
- }
379
- const attachmentPath = sub.match(/^attachments\/([^/]+)$/);
380
- if (attachmentPath && req.method === "DELETE") {
381
- sendMutationJson(res, 200, deleteTicketAttachment(ticketId, decodeURIComponent(attachmentPath[1]), ctx), ctx);
382
- return;
383
- }
384
- sendJson(res, 404, { error: "not_found" });
385
- return;
386
- }
387
-
388
- const reviewVerdictPath = url.pathname.match(/^\/api\/review-verdicts\/([^/]+)$/);
389
- if (reviewVerdictPath && req.method === "GET") {
390
- const reviewId = decodeURIComponent(reviewVerdictPath[1]);
391
- const { boardId, boardSlug } = boardHintsFromQuery(url);
392
- const row = resolveBoardFromHint(boardId, boardSlug);
393
- if (!row) throw httpError(400, "board_id_required");
394
- sendJson(res, 200, getReviewVerdict(reviewId, ctxFromBoardRow(row, actor)));
395
- return;
396
- }
397
-
398
- // /api/states/:id PATCH/DELETE — board resolved from query hint or scan.
399
- const statePath = url.pathname.match(/^\/api\/states\/([^/]+)$/);
400
- if (statePath) {
401
- const stateId = decodeURIComponent(statePath[1]);
402
- const { boardId, boardSlug } = boardHintsFromQuery(url);
403
- const hintRow = resolveBoardFromHint(boardId, boardSlug);
404
- const found = findBoardForState(stateId, hintRow?.id || null);
405
- if (!found) throw httpError(404, "state_not_found");
406
- const ctx = ctxFromBoardRow(found.board, actor);
407
- if (req.method === "PATCH") {
408
- const body = await readJson(req);
409
- sendMutationJson(res, 200, updateState(stateId, body, ctx), ctx);
410
- return;
411
- }
412
- if (req.method === "DELETE") {
413
- sendMutationJson(res, 200, deleteState(stateId, ctx), ctx);
414
- return;
415
- }
416
- sendJson(res, 405, { error: "method_not_allowed" });
417
- return;
418
- }
419
-
420
- // /api/board-entries/:id PATCH — board resolved from query hint or scan.
421
- const entryPath = url.pathname.match(/^\/api\/board-entries\/([^/]+)$/);
422
- if (entryPath && req.method === "PATCH") {
423
- const entryId = decodeURIComponent(entryPath[1]);
424
- const { boardId, boardSlug } = boardHintsFromQuery(url);
425
- const hintRow = resolveBoardFromHint(boardId, boardSlug);
426
- const found = findBoardForBoardEntry(entryId, hintRow?.id || null);
427
- if (!found) throw httpError(404, "board_entry_not_found");
428
- const body = await readJson(req);
429
- const ctx = ctxFromBoardRow(found.board, actor);
430
- sendMutationJson(res, 200, updateBoardEntry(entryId, body, ctx), ctx);
431
- return;
432
- }
433
-
434
- // /api/relations POST — board resolved from source_ticket_id (cross-board
435
- // relations are explicitly not supported; the resolved source's board IS
436
- // the relation's board).
437
- if (req.method === "POST" && url.pathname === "/api/relations") {
438
- const body = await readJson(req);
439
- const hintId = body.board_id || url.searchParams.get("board_id");
440
- const hintSlug = body.board || url.searchParams.get("board");
441
- const explicit = resolveBoardFromHint(hintId, hintSlug);
442
- let row = explicit;
443
- if (!row && body.source_ticket_id) {
444
- const found = findBoardForTicket(body.source_ticket_id);
445
- if (!found) throw httpError(404, "ticket_not_found");
446
- row = found.board;
447
- }
448
- if (!row) throw httpError(400, "board_id_required");
449
- const ctx = ctxFromBoardRow(row, actor);
450
- sendMutationJson(res, 201, createRelation(body, ctx), ctx);
451
- return;
452
- }
453
-
454
- // /api/relations/:id DELETE — board resolved from query hint or scan.
455
- const relationPath = url.pathname.match(/^\/api\/relations\/([^/]+)$/);
456
- if (relationPath && req.method === "DELETE") {
457
- const relationId = decodeURIComponent(relationPath[1]);
458
- const { boardId, boardSlug } = boardHintsFromQuery(url);
459
- const hintRow = resolveBoardFromHint(boardId, boardSlug);
460
- const found = findBoardForRelation(relationId, hintRow?.id || null);
461
- if (!found) throw httpError(404, "relation_not_found");
462
- const ctx = ctxFromBoardRow(found.board, actor);
463
- deleteRelation(relationId, ctx);
464
- sendMutationJson(res, 200, { ok: true }, ctx);
465
- return;
466
- }
467
-
468
- // /api/search — board required (slug or id).
469
- if (req.method === "GET" && url.pathname === "/api/search") {
470
- const { boardId, boardSlug } = boardHintsFromQuery(url);
471
- const row = resolveBoardFromHint(boardId, boardSlug);
472
- if (!row) throw httpError(400, "board_id_required");
473
- const args = {
474
- q: url.searchParams.get("q") || "",
475
- limit: url.searchParams.get("limit"),
476
- mode: url.searchParams.get("mode"),
477
- include_full: url.searchParams.get("include_full") === "true" ? true : undefined,
478
- fields: url.searchParams.get("fields"),
479
- max_chars_per_field: url.searchParams.get("max_chars_per_field")
480
- };
481
- sendJson(res, 200, searchTickets(args, ctxFromBoardRow(row, actor)));
482
- return;
483
- }
484
-
485
- // Agent routes — board explicitly required to prevent cross-board claim.
486
- if (req.method === "POST" && url.pathname === "/api/agent/claim-next") {
487
- const body = await readJson(req);
488
- const hintId = body.board_id || url.searchParams.get("board_id");
489
- const hintSlug = body.board || url.searchParams.get("board");
490
- const row = resolveBoardFromHint(hintId, hintSlug);
491
- if (!row) throw httpError(400, "board_id_required");
492
- const ctx = ctxFromBoardRow(row, actor);
493
- sendMutationJson(res, 200, claimNext(body, ctx), ctx);
494
- return;
495
- }
496
-
497
- if (req.method === "POST" && url.pathname === "/api/agent/checkpoint") {
498
- const body = await readJson(req);
499
- const tid = body.ticket_id;
500
- const hintId = body.board_id || url.searchParams.get("board_id");
501
- const hintSlug = body.board || url.searchParams.get("board");
502
- const explicit = resolveBoardFromHint(hintId, hintSlug);
503
- let row = explicit;
504
- if (!row && tid) {
505
- const found = findBoardForTicket(tid);
506
- if (!found) throw httpError(404, "ticket_not_found");
507
- row = found.board;
508
- }
509
- if (!row) throw httpError(400, "board_id_required");
510
- const ctx = ctxFromBoardRow(row, actor);
511
- sendMutationJson(res, 200, checkpointTicket(body, ctx), ctx);
512
- return;
513
- }
514
-
515
- if (req.method === "POST" && url.pathname === "/api/agent/complete") {
516
- const body = await readJson(req);
517
- const tid = body.ticket_id;
518
- const hintId = body.board_id || url.searchParams.get("board_id");
519
- const hintSlug = body.board || url.searchParams.get("board");
520
- const explicit = resolveBoardFromHint(hintId, hintSlug);
521
- let row = explicit;
522
- if (!row && tid) {
523
- const found = findBoardForTicket(tid);
524
- if (!found) throw httpError(404, "ticket_not_found");
525
- row = found.board;
526
- }
527
- if (!row) throw httpError(400, "board_id_required");
528
- const ctx = ctxFromBoardRow(row, actor);
529
- sendMutationJson(res, 200, completeTicket(body, ctx), ctx);
530
- return;
531
- }
532
-
533
- // /api/admin/import — by default callers import into a brand-new board.
534
- // Legacy/advanced replace imports require body.board_id so we know which
535
- // board DB to replace. Replacement is destructive within that one board DB.
536
- if (req.method === "POST" && url.pathname === "/api/admin/import") {
537
- const body = await readJson(req);
538
- const hintId = body.board_id || url.searchParams.get("board_id");
539
- const hintSlug = body.board || url.searchParams.get("board");
540
- const row = resolveBoardFromHint(hintId, hintSlug);
541
- if (body.create_new) {
542
- const seedCtx = row ? ctxFromBoardRow(row, actor) : { actor, board: {}, db: null };
543
- const result = importBoardSnapshotAsNewBoard(body, seedCtx);
544
- sendJson(res, 201, result);
545
- const newRow = getBoardByRegistryId(result.imported_board_id);
546
- if (newRow) scheduleAutomaticBoardBackup(newRow, openBoardDb(newRow));
547
- return;
548
- }
549
- if (!row) throw httpError(400, "board_id_required");
550
- const ctx = ctxFromBoardRow(row, actor);
551
- sendMutationJson(res, 201, importBoardSnapshot(body, ctx), ctx);
552
- return;
553
- }
554
-
555
- sendJson(res, 404, { error: "not_found" });
556
- }