@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
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Agents' sessions on every board, for the quick menu (`GET /api/sessions`).
3
+ * Not part of the command language, so agents
4
+ * pay nothing for them.
5
+ *
6
+ * A session is one agent's run of writes on one board with no gap over 30
7
+ * minutes; its id is `board id:agent:first event id`, so dismissing it hides
8
+ * that run and not the agent. Its state comes from its last write:
9
+ * - waiting: an ask, or a finish into a review lane, while the card still sits
10
+ * in the lane it moved to;
11
+ * - working: any other write in the last 5 minutes;
12
+ * - finished: anything else. A session shows until 2 hours after its last write.
13
+ * A person's writes (actor `local`) are never sessions; an agent that never
14
+ * said its name writes as `agent`. Events are read from each board, so writes
15
+ * from MCP and the CLI in other processes count.
16
+ */
17
+ import type { Ctx } from "./context.ts";
18
+ import type { AgentSession, OrbitEvent, RegisteredBoard, State } from "./model.ts";
19
+ export declare const GAP_MS: number;
20
+ export declare const WORKING_MS: number;
21
+ export declare const SHOW_MS: number;
22
+ /** "30m" or "2h" (at most 24h) in milliseconds; the default is 2 hours. */
23
+ export declare function parseWindow(value: unknown): number;
24
+ /** An event with its card's number, title and current lane, and an ask's question. */
25
+ export interface SessionEvent extends OrbitEvent {
26
+ number: number | null;
27
+ title: string | null;
28
+ card_lane: string | null;
29
+ question: string | null;
30
+ }
31
+ /** The sessions on one board from its events, oldest first; runs whose last write is before `sinceMs` are left out. */
32
+ export declare function sessionsFromEvents(events: readonly SessionEvent[], options: {
33
+ board: Pick<RegisteredBoard, "id" | "slug" | "name">;
34
+ lanes?: Pick<State, "name" | "role">[];
35
+ nowMs: number;
36
+ sinceMs: number;
37
+ }): AgentSession[];
38
+ /** Every board's sessions whose last write is within `since` (default 2h), with the window's start. */
39
+ export declare function listSessions(c: Ctx, options?: {
40
+ since?: unknown;
41
+ }): Promise<{
42
+ since: string;
43
+ sessions: AgentSession[];
44
+ }>;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Agents' sessions on every board, for the quick menu (`GET /api/sessions`).
3
+ * Not part of the command language, so agents
4
+ * pay nothing for them.
5
+ *
6
+ * A session is one agent's run of writes on one board with no gap over 30
7
+ * minutes; its id is `board id:agent:first event id`, so dismissing it hides
8
+ * that run and not the agent. Its state comes from its last write:
9
+ * - waiting: an ask, or a finish into a review lane, while the card still sits
10
+ * in the lane it moved to;
11
+ * - working: any other write in the last 5 minutes;
12
+ * - finished: anything else. A session shows until 2 hours after its last write.
13
+ * A person's writes (actor `local`) are never sessions; an agent that never
14
+ * said its name writes as `agent`. Events are read from each board, so writes
15
+ * from MCP and the CLI in other processes count.
16
+ */
17
+ import { describeEvent } from "./describe.js";
18
+ import { OrbitError } from "./errors.js";
19
+ import { asText } from "./text.js";
20
+ const MINUTE = 60_000;
21
+ export const GAP_MS = 30 * MINUTE;
22
+ export const WORKING_MS = 5 * MINUTE;
23
+ export const SHOW_MS = 120 * MINUTE;
24
+ /** How far before the window a run is read back, so a long run keeps its first id. */
25
+ const LOOKBACK_MS = 24 * 60 * MINUTE;
26
+ const MAX_WINDOW_MS = 24 * 60 * MINUTE;
27
+ const PERSON = "local";
28
+ const UNNAMED = "agent";
29
+ const ORDER = { waiting: 0, working: 1, finished: 2 };
30
+ /** "30m" or "2h" (at most 24h) in milliseconds; the default is 2 hours. */
31
+ export function parseWindow(value) {
32
+ if (value === undefined || value === null || value === "")
33
+ return SHOW_MS;
34
+ const match = /^(\d{1,4})(m|h)$/.exec(asText(value).trim());
35
+ const ms = match ? Number(match[1]) * (match[2] === "h" ? 60 * MINUTE : MINUTE) : NaN;
36
+ if (!(ms > 0 && ms <= MAX_WINDOW_MS))
37
+ throw new OrbitError("invalid_since", { message: "since takes a time like 30m or 2h, up to 24h." });
38
+ return ms;
39
+ }
40
+ function bodyOf(json) {
41
+ try {
42
+ const body = JSON.parse(json || "{}");
43
+ return body && typeof body === "object" ? body : {};
44
+ }
45
+ catch {
46
+ return {};
47
+ }
48
+ }
49
+ function stateOf(last, roles, nowMs) {
50
+ const body = bodyOf(last.body_json);
51
+ const to = typeof body.to === "string" ? body.to : "";
52
+ const stillThere = Boolean(to) && last.card_lane === to;
53
+ if (last.type === "checkpoint_requested")
54
+ return stillThere ? ["waiting", "ask"] : ["finished", null];
55
+ if (last.type === "agent_completed")
56
+ return stillThere && roles.get(to) === "review" ? ["waiting", "review"] : ["finished", null];
57
+ return nowMs - Date.parse(last.created_at) <= WORKING_MS ? ["working", null] : ["finished", null];
58
+ }
59
+ /** The sessions on one board from its events, oldest first; runs whose last write is before `sinceMs` are left out. */
60
+ export function sessionsFromEvents(events, options) {
61
+ const { board, nowMs, sinceMs } = options;
62
+ const roles = new Map((options.lanes ?? []).map((lane) => [lane.name, lane.role]));
63
+ const open = new Map();
64
+ const runs = [];
65
+ for (const event of events) {
66
+ if (!event.actor || event.actor === PERSON)
67
+ continue;
68
+ const time = Date.parse(event.created_at);
69
+ const run = open.get(event.actor);
70
+ if (run && time - run.lastMs <= GAP_MS) {
71
+ run.events.push(event);
72
+ run.lastMs = time;
73
+ }
74
+ else {
75
+ if (run)
76
+ runs.push(run);
77
+ open.set(event.actor, { actor: event.actor, events: [event], lastMs: time });
78
+ }
79
+ }
80
+ runs.push(...open.values());
81
+ const sessions = [];
82
+ for (const { actor, events: list, lastMs } of runs) {
83
+ const first = list[0];
84
+ const last = list[list.length - 1];
85
+ if (!first || !last || lastMs < sinceMs)
86
+ continue;
87
+ const [state, reason] = stateOf(last, roles, nowMs);
88
+ const withCard = [...list].reverse().find((event) => event.number !== null);
89
+ const card = withCard?.ticket_id && withCard.number !== null ? { id: withCard.ticket_id, number: withCard.number, title: withCard.title ?? "" } : null;
90
+ sessions.push({
91
+ id: `${board.id}:${actor}:${first.id}`,
92
+ board: { id: board.id, slug: board.slug, name: board.name },
93
+ agent: actor,
94
+ unnamed: actor === UNNAMED,
95
+ state,
96
+ reason,
97
+ what: describeEvent(last),
98
+ detail: reason === "ask" ? (last.question ?? "") : (card?.title ?? ""),
99
+ card,
100
+ started_at: first.created_at,
101
+ at: last.created_at,
102
+ writes: list.length
103
+ });
104
+ }
105
+ return sortSessions(sessions);
106
+ }
107
+ function sortSessions(sessions) {
108
+ return sessions.sort((a, b) => ORDER[a.state] - ORDER[b.state] || Date.parse(b.at) - Date.parse(a.at));
109
+ }
110
+ /** Every board's sessions whose last write is within `since` (default 2h), with the window's start. */
111
+ export async function listSessions(c, options = {}) {
112
+ const windowMs = parseWindow(options.since);
113
+ const nowMs = Date.parse(c.clock.now());
114
+ const sinceMs = nowMs - windowMs;
115
+ const from = new Date(sinceMs - LOOKBACK_MS).toISOString();
116
+ const all = [];
117
+ for (const row of await c.store.boards.list()) {
118
+ try {
119
+ const data = await c.store.board(row.id);
120
+ const { events, lanes } = await data.read(async (r) => {
121
+ const raw = (await r.events.list({ since: from })).filter((event) => event.actor !== PERSON);
122
+ if (!raw.length)
123
+ return { events: [], lanes: [] };
124
+ const ids = [...new Set(raw.map((event) => event.ticket_id).filter((id) => id !== null))];
125
+ const [states, tickets] = await Promise.all([r.states.list(), ids.length ? r.tickets.list({ ids }) : Promise.resolve([])]);
126
+ const byId = new Map(tickets.map((ticket) => [ticket.id, ticket]));
127
+ const laneName = new Map(states.map((state) => [state.id, state.name]));
128
+ const questions = new Map();
129
+ for (const event of raw) {
130
+ if (event.type !== "checkpoint_requested" || !event.ticket_id)
131
+ continue;
132
+ const key = `${event.ticket_id}\n${event.actor}`;
133
+ if (questions.has(key))
134
+ continue;
135
+ const asked = (await r.comments.list(event.ticket_id)).filter((comment) => comment.author === event.actor && comment.kind === "checkpoint");
136
+ questions.set(key, asked[asked.length - 1]?.body ?? "");
137
+ }
138
+ const events = raw.map((event) => {
139
+ const ticket = event.ticket_id ? byId.get(event.ticket_id) : undefined;
140
+ return {
141
+ ...event,
142
+ number: ticket?.number ?? null,
143
+ title: ticket?.title ?? null,
144
+ card_lane: ticket ? (laneName.get(ticket.state_id) ?? null) : null,
145
+ question: event.type === "checkpoint_requested" && event.ticket_id ? (questions.get(`${event.ticket_id}\n${event.actor}`) ?? null) : null
146
+ };
147
+ });
148
+ return { events, lanes: states };
149
+ });
150
+ if (events.length)
151
+ all.push(...sessionsFromEvents(events, { board: row, lanes, nowMs, sinceMs }));
152
+ }
153
+ catch {
154
+ // One unreadable board never hides the others' sessions.
155
+ }
156
+ }
157
+ return { since: new Date(sinceMs).toISOString(), sessions: sortSessions(all) };
158
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * What an import may carry, checked with zod. Its own module so zod loads
3
+ * only when an import runs: the commands people run most never pay for it.
4
+ */
5
+ import { z } from "zod";
6
+ export declare const BoardIn: z.ZodObject<{
7
+ slug: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
8
+ name: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
9
+ repo_url: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
10
+ default_branch: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
11
+ project_notes: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
12
+ agent_instructions: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
13
+ ai_enabled: z.ZodOptional<z.ZodUnknown>;
14
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
15
+ updated_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
16
+ }, z.core.$loose>;
17
+ export declare const SnapshotIn: z.ZodObject<{
18
+ format: z.ZodLiteral<"orbit-board-export">;
19
+ source_format: z.ZodOptional<z.ZodString>;
20
+ imported_counts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
21
+ board: z.ZodUnknown;
22
+ states: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
23
+ id: z.ZodString;
24
+ name: z.ZodOptional<z.ZodUnknown>;
25
+ position: z.ZodNumber;
26
+ is_default: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodBoolean]>>>, z.ZodTransform<0 | 1, number | boolean | null | undefined>>;
27
+ role: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
28
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
29
+ }, z.core.$loose>>>>, z.ZodTransform<{
30
+ [x: string]: unknown;
31
+ id: string;
32
+ position: number;
33
+ is_default: 0 | 1;
34
+ role: string | null;
35
+ created_at: string | null;
36
+ name?: unknown;
37
+ }[], {
38
+ [x: string]: unknown;
39
+ id: string;
40
+ position: number;
41
+ is_default: 0 | 1;
42
+ role: string | null;
43
+ created_at: string | null;
44
+ name?: unknown;
45
+ }[] | null | undefined>>;
46
+ labels: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
47
+ id: z.ZodString;
48
+ name: z.ZodString;
49
+ color: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
50
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
51
+ }, z.core.$loose>>>>, z.ZodTransform<{
52
+ [x: string]: unknown;
53
+ id: string;
54
+ name: string;
55
+ color: string | null;
56
+ created_at: string | null;
57
+ }[], {
58
+ [x: string]: unknown;
59
+ id: string;
60
+ name: string;
61
+ color: string | null;
62
+ created_at: string | null;
63
+ }[] | null | undefined>>;
64
+ tickets: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
65
+ id: z.ZodString;
66
+ number: z.ZodNumber;
67
+ title: z.ZodString;
68
+ description: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
69
+ type: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
70
+ parent_ticket_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
71
+ ai_plan: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
72
+ plan_artifact_path: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
73
+ implementation_summary: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
74
+ implementation_artifact_path: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
75
+ implementation_updates: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
76
+ nebula_url: z.ZodOptional<z.ZodUnknown>;
77
+ state_id: z.ZodString;
78
+ priority: z.ZodOptional<z.ZodUnknown>;
79
+ created_by: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
80
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
81
+ updated_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
82
+ archived_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
83
+ labels: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
84
+ id: z.ZodString;
85
+ }, z.core.$loose>>>>;
86
+ }, z.core.$loose>>>>, z.ZodTransform<{
87
+ [x: string]: unknown;
88
+ id: string;
89
+ number: number;
90
+ title: string;
91
+ description: string;
92
+ type: string | null;
93
+ parent_ticket_id: string | null;
94
+ ai_plan: string;
95
+ plan_artifact_path: string;
96
+ implementation_summary: string;
97
+ implementation_artifact_path: string;
98
+ implementation_updates: string;
99
+ state_id: string;
100
+ created_by: string | null;
101
+ created_at: string | null;
102
+ updated_at: string | null;
103
+ archived_at: string | null;
104
+ nebula_url?: unknown;
105
+ priority?: unknown;
106
+ labels?: {
107
+ [x: string]: unknown;
108
+ id: string;
109
+ }[] | null | undefined;
110
+ }[], {
111
+ [x: string]: unknown;
112
+ id: string;
113
+ number: number;
114
+ title: string;
115
+ description: string;
116
+ type: string | null;
117
+ parent_ticket_id: string | null;
118
+ ai_plan: string;
119
+ plan_artifact_path: string;
120
+ implementation_summary: string;
121
+ implementation_artifact_path: string;
122
+ implementation_updates: string;
123
+ state_id: string;
124
+ created_by: string | null;
125
+ created_at: string | null;
126
+ updated_at: string | null;
127
+ archived_at: string | null;
128
+ nebula_url?: unknown;
129
+ priority?: unknown;
130
+ labels?: {
131
+ [x: string]: unknown;
132
+ id: string;
133
+ }[] | null | undefined;
134
+ }[] | null | undefined>>;
135
+ comments: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
136
+ id: z.ZodString;
137
+ ticket_id: z.ZodString;
138
+ author: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
139
+ kind: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
140
+ body: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
141
+ omitted_for_ai: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodNumber, z.ZodBoolean]>>>, z.ZodTransform<0 | 1, number | boolean | null | undefined>>;
142
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
143
+ }, z.core.$loose>>>>, z.ZodTransform<{
144
+ [x: string]: unknown;
145
+ id: string;
146
+ ticket_id: string;
147
+ author: string | null;
148
+ kind: string | null;
149
+ body: string;
150
+ omitted_for_ai: 0 | 1;
151
+ created_at: string | null;
152
+ }[], {
153
+ [x: string]: unknown;
154
+ id: string;
155
+ ticket_id: string;
156
+ author: string | null;
157
+ kind: string | null;
158
+ body: string;
159
+ omitted_for_ai: 0 | 1;
160
+ created_at: string | null;
161
+ }[] | null | undefined>>;
162
+ review_verdicts: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
163
+ id: z.ZodString;
164
+ ticket_id: z.ZodString;
165
+ verdict: z.ZodEnum<{
166
+ PASS: "PASS";
167
+ BLOCK: "BLOCK";
168
+ QUESTION: "QUESTION";
169
+ }>;
170
+ blocking_findings_json: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
171
+ optional_findings_json: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
172
+ evidence_commands_json: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
173
+ reviewer_profile: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
174
+ reviewer_session_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
175
+ reviewed_commit_sha: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
176
+ dispatch_run_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
177
+ supersedes_prior_review_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
178
+ created_by: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
179
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
180
+ }, z.core.$loose>>>>, z.ZodTransform<{
181
+ [x: string]: unknown;
182
+ id: string;
183
+ ticket_id: string;
184
+ verdict: "PASS" | "BLOCK" | "QUESTION";
185
+ blocking_findings_json: string | null;
186
+ optional_findings_json: string | null;
187
+ evidence_commands_json: string | null;
188
+ reviewer_profile: string;
189
+ reviewer_session_id: string;
190
+ reviewed_commit_sha: string;
191
+ dispatch_run_id: string;
192
+ supersedes_prior_review_id: string | null;
193
+ created_by: string | null;
194
+ created_at: string | null;
195
+ }[], {
196
+ [x: string]: unknown;
197
+ id: string;
198
+ ticket_id: string;
199
+ verdict: "PASS" | "BLOCK" | "QUESTION";
200
+ blocking_findings_json: string | null;
201
+ optional_findings_json: string | null;
202
+ evidence_commands_json: string | null;
203
+ reviewer_profile: string;
204
+ reviewer_session_id: string;
205
+ reviewed_commit_sha: string;
206
+ dispatch_run_id: string;
207
+ supersedes_prior_review_id: string | null;
208
+ created_by: string | null;
209
+ created_at: string | null;
210
+ }[] | null | undefined>>;
211
+ relations: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
212
+ id: z.ZodString;
213
+ source_ticket_id: z.ZodString;
214
+ target_ticket_id: z.ZodString;
215
+ type: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
216
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
217
+ }, z.core.$loose>>>>, z.ZodTransform<{
218
+ [x: string]: unknown;
219
+ id: string;
220
+ source_ticket_id: string;
221
+ target_ticket_id: string;
222
+ type: string | null;
223
+ created_at: string | null;
224
+ }[], {
225
+ [x: string]: unknown;
226
+ id: string;
227
+ source_ticket_id: string;
228
+ target_ticket_id: string;
229
+ type: string | null;
230
+ created_at: string | null;
231
+ }[] | null | undefined>>;
232
+ cross_board_relations: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>>, z.ZodTransform<Record<string, unknown>[], Record<string, unknown>[] | null | undefined>>;
233
+ comments_hidden_from_ai_left_out: z.ZodOptional<z.ZodUnknown>;
234
+ attachments: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>>>, z.ZodTransform<Record<string, unknown>[], Record<string, unknown>[] | null | undefined>>;
235
+ board_entries: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
236
+ id: z.ZodString;
237
+ type: z.ZodOptional<z.ZodUnknown>;
238
+ title: z.ZodOptional<z.ZodUnknown>;
239
+ body: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string, string | null | undefined>>;
240
+ ticket_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
241
+ created_by: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
242
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
243
+ updated_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
244
+ struck_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
245
+ }, z.core.$loose>>>>, z.ZodTransform<{
246
+ [x: string]: unknown;
247
+ id: string;
248
+ body: string;
249
+ ticket_id: string | null;
250
+ created_by: string | null;
251
+ created_at: string | null;
252
+ updated_at: string | null;
253
+ struck_at: string | null;
254
+ type?: unknown;
255
+ title?: unknown;
256
+ }[], {
257
+ [x: string]: unknown;
258
+ id: string;
259
+ body: string;
260
+ ticket_id: string | null;
261
+ created_by: string | null;
262
+ created_at: string | null;
263
+ updated_at: string | null;
264
+ struck_at: string | null;
265
+ type?: unknown;
266
+ title?: unknown;
267
+ }[] | null | undefined>>;
268
+ events: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
269
+ id: z.ZodString;
270
+ ticket_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
271
+ actor: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
272
+ type: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
273
+ body_json: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
274
+ created_at: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
275
+ }, z.core.$loose>>>>, z.ZodTransform<{
276
+ [x: string]: unknown;
277
+ id: string;
278
+ ticket_id: string | null;
279
+ actor: string | null;
280
+ type: string | null;
281
+ body_json: string | null;
282
+ created_at: string | null;
283
+ }[], {
284
+ [x: string]: unknown;
285
+ id: string;
286
+ ticket_id: string | null;
287
+ actor: string | null;
288
+ type: string | null;
289
+ body_json: string | null;
290
+ created_at: string | null;
291
+ }[] | null | undefined>>;
292
+ }, z.core.$loose>;
293
+ export type Snapshot = z.infer<typeof SnapshotIn> & {
294
+ board: z.infer<typeof BoardIn>;
295
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * What an import may carry, checked with zod. Its own module so zod loads
3
+ * only when an import runs: the commands people run most never pay for it.
4
+ */
5
+ import { z } from "zod";
6
+ import { EXPORT_FORMAT } from "./snapshots.js";
7
+ // No compiled parsers: zod's probe for them runs `new Function`, which the
8
+ // app page's script policy refuses (and reports) in the browser edition.
9
+ // Imports are rare, so the interpreted parse costs nothing anyone sees.
10
+ z.config({ jitless: true });
11
+ // What an import may carry. Records are loose (an older export has more
12
+ // fields, a hand-made one fewer); what Orbit needs to keep a record is checked.
13
+ // zod 4 treats a bare z.unknown() key as required, so every key Orbit fills
14
+ // in by itself when it's missing is marked optional.
15
+ const text = z.string().nullish().transform((value) => value ?? "");
16
+ const maybeText = z.string().nullish().transform((value) => value || null);
17
+ const flag = z.union([z.number(), z.boolean()]).nullish().transform((value) => (value ? 1 : 0));
18
+ const id = z.string().min(1);
19
+ export const BoardIn = z.looseObject({ slug: text, name: text, repo_url: text, default_branch: text, project_notes: text, agent_instructions: text, ai_enabled: z.unknown().optional(), created_at: maybeText, updated_at: maybeText });
20
+ const StateIn = z.looseObject({ id, name: z.unknown().optional(), position: z.number(), is_default: flag, role: maybeText, created_at: maybeText });
21
+ const LabelIn = z.looseObject({ id, name: z.string(), color: maybeText, created_at: maybeText });
22
+ const TicketIn = z.looseObject({
23
+ id,
24
+ number: z.number().int(),
25
+ title: z.string(),
26
+ description: text,
27
+ type: maybeText,
28
+ parent_ticket_id: maybeText,
29
+ ai_plan: text,
30
+ plan_artifact_path: text,
31
+ implementation_summary: text,
32
+ implementation_artifact_path: text,
33
+ implementation_updates: text,
34
+ nebula_url: z.unknown().optional(),
35
+ state_id: id,
36
+ priority: z.unknown().optional(),
37
+ created_by: maybeText,
38
+ created_at: maybeText,
39
+ updated_at: maybeText,
40
+ archived_at: maybeText,
41
+ labels: z.array(z.looseObject({ id })).nullish()
42
+ });
43
+ const CommentIn = z.looseObject({ id, ticket_id: id, author: maybeText, kind: maybeText, body: text, omitted_for_ai: flag, created_at: maybeText });
44
+ const VerdictIn = z.looseObject({
45
+ id,
46
+ ticket_id: id,
47
+ verdict: z.enum(["PASS", "BLOCK", "QUESTION"]),
48
+ blocking_findings_json: maybeText,
49
+ optional_findings_json: maybeText,
50
+ evidence_commands_json: maybeText,
51
+ reviewer_profile: text,
52
+ reviewer_session_id: text,
53
+ reviewed_commit_sha: text,
54
+ dispatch_run_id: text,
55
+ supersedes_prior_review_id: maybeText,
56
+ created_by: maybeText,
57
+ created_at: maybeText
58
+ });
59
+ const RelationIn = z.looseObject({ id, source_ticket_id: id, target_ticket_id: id, type: maybeText, created_at: maybeText });
60
+ const EntryIn = z.looseObject({ id, type: z.unknown().optional(), title: z.unknown().optional(), body: text, ticket_id: maybeText, created_by: maybeText, created_at: maybeText, updated_at: maybeText, struck_at: maybeText });
61
+ const EventIn = z.looseObject({ id, ticket_id: maybeText, actor: maybeText, type: maybeText, body_json: maybeText, created_at: maybeText });
62
+ const list = (item) => z.array(item).nullish().transform((value) => value ?? []);
63
+ export const SnapshotIn = z.looseObject({
64
+ format: z.literal(EXPORT_FORMAT),
65
+ source_format: z.string().optional(),
66
+ imported_counts: z.record(z.string(), z.number()).optional(),
67
+ board: z.unknown(),
68
+ states: list(StateIn),
69
+ labels: list(LabelIn),
70
+ tickets: list(TicketIn),
71
+ comments: list(CommentIn),
72
+ review_verdicts: list(VerdictIn),
73
+ relations: list(RelationIn),
74
+ // Checked one by one where they're kept (snapshots.ts), so one odd link never stops a board.
75
+ cross_board_relations: list(z.record(z.string(), z.unknown())),
76
+ // An agent's export says how many comments hidden from AI it left out. Read as sent, never coerced:
77
+ // a replace from a file that has the key goes ahead only when it is exactly 0 (importReplacing).
78
+ comments_hidden_from_ai_left_out: z.unknown().optional(),
79
+ attachments: list(z.record(z.string(), z.unknown())),
80
+ board_entries: list(EntryIn),
81
+ events: list(EventIn)
82
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A board's export (`orbit-board-export`), and importing one: into a new
3
+ * board, or over a board it replaces (keeping that board's id and slug). An
4
+ * import is checked where it enters, with zod; a Trello export is read as
5
+ * one first. One field Orbit wouldn't keep (a link with a password in it)
6
+ * is dropped, not refused, so one bad field never stops a whole board.
7
+ */
8
+ import type { Snapshot } from "./snapshot-schema.ts";
9
+ import type { BoardCtx, Ctx } from "./context.ts";
10
+ import type { Id, RegisteredBoard } from "./model.ts";
11
+ export declare const EXPORT_FORMAT = "orbit-board-export";
12
+ /** A label colour as an import keeps it. It is CSS the app paints with, so only `#rgb` or `#rrggbb` is kept; anything else (a `url(...)`) gets the default. */
13
+ export declare function keptLabelColor(value: unknown): string;
14
+ export declare const EXPORT_VERSION = 9;
15
+ /** Exports a board whole: every record, events included; images' bytes when asked. */
16
+ export declare function exportBoard(c: BoardCtx, options?: {
17
+ includeAttachments?: boolean;
18
+ }): Promise<Record<string, unknown>>;
19
+ export interface ImportInput {
20
+ snapshot?: unknown;
21
+ replace_existing?: unknown;
22
+ create_new?: unknown;
23
+ slug?: unknown;
24
+ [field: string]: unknown;
25
+ }
26
+ export interface ImportResult {
27
+ ok: true;
28
+ imported_board_id: Id;
29
+ imported_board_slug: string;
30
+ replace_existing: boolean;
31
+ source_format: string;
32
+ imported_counts: Record<string, number>;
33
+ /** Images kept as files to download only: an SVG, or bytes that aren't an image type Orbit shows. */
34
+ images_kept_as_downloads: number;
35
+ /** Cross-board links the export carried that didn't come (see `plannedCrossLinks`). */
36
+ cross_board_links_dropped: number;
37
+ created_new_board?: true;
38
+ }
39
+ /**
40
+ * Replacing a board deletes what it holds, so it is the person's call,
41
+ * never an agent's, as with a self-update. Refused before anything is read,
42
+ * whatever the transport: the JSON API, or a host's in-process client.
43
+ */
44
+ export declare function refuseAgentReplace(c: Ctx, input: ImportInput): void;
45
+ /**
46
+ * Replaces a board with an export. The board keeps its own id and slug, so
47
+ * links to it still work; a copy of it is kept first where storage can.
48
+ * Never an agent's (`refuseAgentReplace`).
49
+ */
50
+ export declare function importReplacing(c: BoardCtx, input: ImportInput): Promise<ImportResult>;
51
+ /**
52
+ * Brings a board from an older Orbit (its folder read as an export, or its
53
+ * export): a new board with the board's own id and slug, every record with
54
+ * its own id, and no event of its own, so the board reads as it did there.
55
+ * Refused when this Orbit already has a board with that id or slug.
56
+ */
57
+ export declare function importKeepingBoard(c: Ctx, raw: unknown, board: {
58
+ id: Id;
59
+ slug: string;
60
+ }): Promise<{
61
+ board: RegisteredBoard;
62
+ snapshot: Snapshot;
63
+ downloads: number;
64
+ crossDropped: number;
65
+ }>;
66
+ /** What the browser preview of 1.x and 2.0 exported (and kept in its database): `starscape-orbit-preview`. */
67
+ export declare const OLD_PREVIEW_FORMAT = "orbit.board.snapshot/v1";
68
+ /**
69
+ * The old browser preview's board as an export: it kept the cards' labels
70
+ * beside them and a relation's type as `kind`. `raw` is its export, or its
71
+ * database's stores read whole.
72
+ */
73
+ export declare function fromOldPreview(raw: Record<string, unknown>): Record<string, unknown>;
74
+ /** Imports an export as a new board, under its own slug or the next free one. */
75
+ export declare function importAsNewBoard(c: Ctx, input: ImportInput): Promise<ImportResult>;