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