@starlove/orbit 1.4.0 → 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.
- package/Dockerfile +10 -8
- package/README.md +146 -164
- package/dist/full/csp.txt +1 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
- package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
- package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
- package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
- package/dist/full/fonts/Mona_Sans/README.txt +10 -0
- package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
- package/dist/full/fonts/Newsreader/README.txt +8 -0
- package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
- package/dist/full/index.html +36 -653
- package/dist/full/js/app.BmuponPJ.js +32 -0
- package/dist/full/js/app.BmuponPJ.js.map +1 -0
- package/dist/full/js/chunk.1fJGQGbW.js +2 -0
- package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
- package/dist/full/js/chunk.5D7JO2zp.js +2 -0
- package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
- package/dist/full/js/chunk.BrNn_D79.js +2 -0
- package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
- package/dist/full/js/chunk.CokJXIqM.js +2 -0
- package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
- package/dist/full/js/chunk.D_Haqy6R.js +2 -0
- package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
- package/dist/full/js/chunk.RO0jsH6I.js +15 -0
- package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
- package/dist/full/js/chunk.dqWOEtBM.js +2 -0
- package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
- package/dist/full/js/warm.f260bbbc.js +15 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg-wide.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.2b154ffe.css +1 -0
- package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_GUIDE.md +114 -0
- package/docs/UPDATING.md +249 -64
- package/lib/adapters/indexeddb/blobs.d.ts +3 -0
- package/lib/adapters/indexeddb/blobs.js +54 -0
- package/lib/adapters/indexeddb/database.d.ts +60 -0
- package/lib/adapters/indexeddb/database.js +126 -0
- package/lib/adapters/indexeddb/index.d.ts +5 -0
- package/lib/adapters/indexeddb/index.js +3 -0
- package/lib/adapters/indexeddb/locks.d.ts +14 -0
- package/lib/adapters/indexeddb/locks.js +40 -0
- package/lib/adapters/indexeddb/store.d.ts +40 -0
- package/lib/adapters/indexeddb/store.js +424 -0
- package/lib/adapters/indexeddb/tables.d.ts +43 -0
- package/lib/adapters/indexeddb/tables.js +127 -0
- package/lib/adapters/memory/blobs.d.ts +3 -0
- package/lib/adapters/memory/blobs.js +44 -0
- package/lib/adapters/memory/board.d.ts +41 -0
- package/lib/adapters/memory/board.js +521 -0
- package/lib/adapters/memory/index.d.ts +2 -0
- package/lib/adapters/memory/index.js +2 -0
- package/lib/adapters/memory/search.d.ts +6 -0
- package/lib/adapters/memory/search.js +30 -0
- package/lib/adapters/memory/store.d.ts +34 -0
- package/lib/adapters/memory/store.js +266 -0
- package/lib/adapters/sqlite/backups.d.ts +53 -0
- package/lib/adapters/sqlite/backups.js +232 -0
- package/lib/adapters/sqlite/blobs.d.ts +14 -0
- package/lib/adapters/sqlite/blobs.js +109 -0
- package/lib/adapters/sqlite/board.d.ts +77 -0
- package/lib/adapters/sqlite/board.js +328 -0
- package/lib/adapters/sqlite/database.d.ts +67 -0
- package/lib/adapters/sqlite/database.js +285 -0
- package/lib/adapters/sqlite/index.d.ts +3 -0
- package/lib/adapters/sqlite/index.js +2 -0
- package/lib/adapters/sqlite/lock.d.ts +11 -0
- package/lib/adapters/sqlite/lock.js +34 -0
- package/lib/adapters/sqlite/records.d.ts +54 -0
- package/lib/adapters/sqlite/records.js +546 -0
- package/lib/adapters/sqlite/schema.d.ts +54 -0
- package/lib/adapters/sqlite/schema.js +276 -0
- package/lib/adapters/sqlite/store.d.ts +39 -0
- package/lib/adapters/sqlite/store.js +385 -0
- package/lib/browser/in-page.d.ts +31 -0
- package/lib/browser/in-page.js +46 -0
- package/lib/browser/old-preview.d.ts +13 -0
- package/lib/browser/old-preview.js +80 -0
- package/lib/browser/preview-board.d.ts +8 -0
- package/lib/browser/preview-board.js +69 -0
- package/lib/cli/main.d.ts +7 -0
- package/lib/cli/main.js +343 -0
- package/lib/cli/orbit.d.ts +8 -0
- package/lib/cli/orbit.js +25 -0
- package/lib/client/http.d.ts +38 -0
- package/lib/client/http.js +334 -0
- package/lib/client/local.d.ts +26 -0
- package/lib/client/local.js +190 -0
- package/lib/client/object-url.d.ts +8 -0
- package/lib/client/object-url.js +21 -0
- package/lib/client/types.d.ts +268 -0
- package/lib/client/types.js +8 -0
- package/lib/commands/agent-tools.d.ts +31 -0
- package/lib/commands/agent-tools.js +100 -0
- package/lib/commands/index.d.ts +56 -0
- package/lib/commands/index.js +298 -0
- package/lib/commands/parse.d.ts +73 -0
- package/lib/commands/parse.js +231 -0
- package/lib/commands/present.d.ts +134 -0
- package/lib/commands/present.js +198 -0
- package/lib/commands/session.d.ts +85 -0
- package/lib/commands/session.js +138 -0
- package/lib/commands/verbs.d.ts +35 -0
- package/lib/commands/verbs.js +842 -0
- package/lib/core/actors.d.ts +18 -0
- package/lib/core/actors.js +45 -0
- package/lib/core/agent.d.ts +128 -0
- package/lib/core/agent.js +323 -0
- package/lib/core/boards.d.ts +113 -0
- package/lib/core/boards.js +288 -0
- package/lib/core/bootstrap.d.ts +47 -0
- package/lib/core/bootstrap.js +100 -0
- package/lib/core/bytes.d.ts +10 -0
- package/lib/core/bytes.js +52 -0
- package/lib/core/card-paths.d.ts +25 -0
- package/lib/core/card-paths.js +44 -0
- package/lib/core/context.d.ts +60 -0
- package/lib/core/context.js +42 -0
- package/lib/core/cross-board.d.ts +81 -0
- package/lib/core/cross-board.js +228 -0
- package/lib/core/describe.d.ts +21 -0
- package/lib/core/describe.js +81 -0
- package/lib/core/errors.d.ts +159 -0
- package/lib/core/errors.js +160 -0
- package/lib/core/files.d.ts +79 -0
- package/lib/core/files.js +262 -0
- package/lib/core/history.d.ts +20 -0
- package/lib/core/history.js +53 -0
- package/lib/core/image-types.d.ts +16 -0
- package/lib/core/image-types.js +36 -0
- package/lib/core/model.d.ts +263 -0
- package/lib/core/model.js +34 -0
- package/lib/core/nebula-link.d.ts +14 -0
- package/lib/core/nebula-link.js +39 -0
- package/lib/core/relations.d.ts +58 -0
- package/lib/core/relations.js +112 -0
- package/lib/core/search.d.ts +38 -0
- package/lib/core/search.js +92 -0
- package/lib/core/sessions.d.ts +44 -0
- package/lib/core/sessions.js +158 -0
- package/lib/core/snapshot-schema.d.ts +295 -0
- package/lib/core/snapshot-schema.js +82 -0
- package/lib/core/snapshots.d.ts +75 -0
- package/lib/core/snapshots.js +409 -0
- package/lib/core/states.d.ts +25 -0
- package/lib/core/states.js +126 -0
- package/lib/core/text.d.ts +40 -0
- package/lib/core/text.js +113 -0
- package/lib/core/tickets.d.ts +69 -0
- package/lib/core/tickets.js +311 -0
- package/lib/core/trello.d.ts +17 -0
- package/lib/core/trello.js +269 -0
- package/lib/core/verdicts.d.ts +28 -0
- package/lib/core/verdicts.js +92 -0
- package/lib/core/views.d.ts +95 -0
- package/lib/core/views.js +186 -0
- package/lib/http/app.d.ts +46 -0
- package/lib/http/app.js +491 -0
- package/lib/mcp-server.d.ts +10 -0
- package/lib/mcp-server.js +120 -0
- package/lib/node/assets.d.ts +7 -0
- package/lib/node/assets.js +68 -0
- package/lib/node/data-folder.d.ts +21 -0
- package/lib/node/data-folder.js +34 -0
- package/lib/node/env.d.ts +38 -0
- package/lib/node/env.js +49 -0
- package/lib/node/host-check.d.ts +24 -0
- package/lib/node/host-check.js +53 -0
- package/lib/node/host-services.d.ts +9 -0
- package/lib/node/host-services.js +150 -0
- package/lib/node/local.d.ts +22 -0
- package/lib/node/local.js +61 -0
- package/lib/node/migrate.d.ts +53 -0
- package/lib/node/migrate.js +635 -0
- package/lib/node/package.d.ts +7 -0
- package/lib/node/package.js +14 -0
- package/lib/node/repo-board.d.ts +49 -0
- package/lib/node/repo-board.js +245 -0
- package/lib/node/serve.d.ts +8 -0
- package/lib/node/serve.js +106 -0
- package/lib/orbit.d.ts +47 -0
- package/lib/orbit.js +44 -0
- package/lib/quiet-sqlite-warning.d.ts +1 -0
- package/lib/quiet-sqlite-warning.js +18 -0
- package/lib/store/blob-path.d.ts +6 -0
- package/lib/store/blob-path.js +12 -0
- package/lib/store/conformance.d.ts +52 -0
- package/lib/store/conformance.js +1449 -0
- package/lib/store/search-words.d.ts +10 -0
- package/lib/store/search-words.js +19 -0
- package/lib/store/types.d.ts +452 -0
- package/lib/store/types.js +34 -0
- package/llms.txt +63 -0
- package/package.json +109 -25
- package/src/cli/orbit.js +11 -658
- package/src/mcp-server.js +16 -876
- package/SKILL-ORBIT.md +0 -151
- package/build.mjs +0 -210
- package/dist/full/js/app.62905fcb.js +0 -952
- package/dist/full/js/app.62905fcb.js.map +0 -7
- package/dist/full/styles.4e60d824.css +0 -1
- package/docs/AGENT_PROTOCOL.md +0 -424
- package/docs/MCP_SETUP.md +0 -54
- package/docs/ORBIT_DISPATCH.md +0 -280
- package/docs/SS-Orbit-Dark.png +0 -0
- package/docs/SS-Orbit-Light.png +0 -0
- package/docs/mcp-client.example.json +0 -13
- package/docs/setup/orbit-init-wizard.md +0 -174
- package/public/apple-touch-icon.png +0 -0
- package/public/cursors/live_arrow.cur +0 -0
- package/public/cursors/live_link.cur +0 -0
- package/public/cursors/live_move.cur +0 -0
- package/public/cursors/live_select.cur +0 -0
- package/public/cursors/live_unavail.cur +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/OFL.txt +0 -93
- package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/OFL.txt +0 -93
- package/public/fonts/Inter/README.txt +0 -118
- package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/README.txt +0 -79
- package/public/fonts/OpenRing-Regular.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/README.txt +0 -100
- package/public/fonts/Orbitron/OFL.txt +0 -93
- package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/public/fonts/Orbitron/README.txt +0 -68
- package/public/fonts/Rajdhani/OFL.txt +0 -93
- package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/public/fonts/runescape-uf.ttf +0 -0
- package/public/index.html +0 -769
- package/public/js/actor-labels.js +0 -65
- package/public/js/api.js +0 -39
- package/public/js/app.js +0 -45
- package/public/js/artifact-drawer.js +0 -79
- package/public/js/board-export.js +0 -161
- package/public/js/board-menu.js +0 -374
- package/public/js/card-actions.js +0 -545
- package/public/js/config.js +0 -20
- package/public/js/create-card.js +0 -190
- package/public/js/dom.js +0 -19
- package/public/js/drawer.js +0 -273
- package/public/js/font-preference.js +0 -229
- package/public/js/format.js +0 -433
- package/public/js/help-menu.js +0 -31
- package/public/js/icons.js +0 -16
- package/public/js/kanban.js +0 -694
- package/public/js/local-backend.js +0 -1015
- package/public/js/main.js +0 -161
- package/public/js/modal.js +0 -33
- package/public/js/motion-preference.js +0 -64
- package/public/js/panel.js +0 -58
- package/public/js/router.js +0 -134
- package/public/js/search.js +0 -56
- package/public/js/settings-tabs.js +0 -7
- package/public/js/settings.js +0 -1634
- package/public/js/sse.js +0 -159
- package/public/js/starscape.js +0 -332
- package/public/js/state.js +0 -129
- package/public/js/text-editing.js +0 -128
- package/public/js/theme-preference.js +0 -95
- package/public/js/ticket-detail.js +0 -1516
- package/public/js/ticket-typeahead.js +0 -49
- package/public/js/toast.js +0 -89
- package/public/js/topbar-notes.js +0 -155
- package/public/js/transport.js +0 -22
- package/public/js/typeahead.js +0 -206
- package/public/js/unread.js +0 -121
- package/public/js/update-check.js +0 -58
- package/public/js/url-routes.js +0 -94
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg-wide.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +0 -7709
- package/src/core/agent.js +0 -387
- package/src/core/agents-md.js +0 -109
- package/src/core/artifacts.js +0 -149
- package/src/core/attachments.js +0 -238
- package/src/core/auth.js +0 -85
- package/src/core/backups.js +0 -142
- package/src/core/board.js +0 -148
- package/src/core/boards.js +0 -409
- package/src/core/bootstrap.js +0 -170
- package/src/core/css-bundle.js +0 -30
- package/src/core/db.js +0 -315
- package/src/core/delete-board.js +0 -163
- package/src/core/dispatch.js +0 -1007
- package/src/core/events.js +0 -314
- package/src/core/http.js +0 -115
- package/src/core/migrate.js +0 -198
- package/src/core/paths.js +0 -55
- package/src/core/provision-repo-board.js +0 -119
- package/src/core/queries.js +0 -246
- package/src/core/registry.js +0 -211
- package/src/core/relations.js +0 -163
- package/src/core/review-verdicts.js +0 -138
- package/src/core/router.js +0 -556
- package/src/core/search.js +0 -183
- package/src/core/seed.js +0 -234
- package/src/core/snapshots.js +0 -692
- package/src/core/states.js +0 -167
- package/src/core/system-picker.js +0 -109
- package/src/core/tickets.js +0 -400
- package/src/core/util.js +0 -106
- package/src/core/version-check.js +0 -154
- package/src/mcp/orbit-client.js +0 -287
- 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>;
|