@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/util.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
/** Normalize repo paths for registry lookup (trailing slashes, slashes, Windows case). */
|
|
5
|
-
export function normalizePath(raw) {
|
|
6
|
-
if (raw === null || raw === undefined) return "";
|
|
7
|
-
let s = String(raw).trim();
|
|
8
|
-
if (!s) return "";
|
|
9
|
-
try {
|
|
10
|
-
s = resolve(s);
|
|
11
|
-
} catch {
|
|
12
|
-
return "";
|
|
13
|
-
}
|
|
14
|
-
s = s.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
15
|
-
if (process.platform === "win32") s = s.toLowerCase();
|
|
16
|
-
return s;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function id() {
|
|
20
|
-
return randomUUID();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function now() {
|
|
24
|
-
return new Date().toISOString();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function slugify(value) {
|
|
28
|
-
return String(value)
|
|
29
|
-
.toLowerCase()
|
|
30
|
-
.trim()
|
|
31
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
32
|
-
.replace(/^-+|-+$/g, "")
|
|
33
|
-
.slice(0, 80);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function titleize(value) {
|
|
37
|
-
return String(value)
|
|
38
|
-
.replace(/[-_]+/g, " ")
|
|
39
|
-
.replace(/\s+/g, " ")
|
|
40
|
-
.trim()
|
|
41
|
-
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function httpError(status, code) {
|
|
45
|
-
const error = new Error(code);
|
|
46
|
-
error.status = status;
|
|
47
|
-
error.code = code;
|
|
48
|
-
return error;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function requiredString(value, field) {
|
|
52
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
53
|
-
throw httpError(400, `missing_${field}`);
|
|
54
|
-
}
|
|
55
|
-
return value.trim();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function normalizePriority(value) {
|
|
59
|
-
const number = Number(value ?? 2);
|
|
60
|
-
if (!Number.isFinite(number)) return 2;
|
|
61
|
-
return Math.max(0, Math.min(4, Math.round(number)));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function normalizeTicketType(value) {
|
|
65
|
-
const type = String(value || "task").toLowerCase().trim();
|
|
66
|
-
const allowed = new Set(["epic", "feature", "task", "bug"]);
|
|
67
|
-
if (!allowed.has(type)) throw httpError(400, "invalid_ticket_type");
|
|
68
|
-
return type;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function normalizeProjectEntryType(value) {
|
|
72
|
-
const type = String(value || "").toLowerCase().trim();
|
|
73
|
-
const allowed = new Set(["decision", "lesson"]);
|
|
74
|
-
if (!allowed.has(type)) throw httpError(400, "invalid_project_entry_type");
|
|
75
|
-
return type;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function normalizeStateRole(value) {
|
|
79
|
-
if (value === null || value === undefined || value === "") return null;
|
|
80
|
-
const role = String(value).toLowerCase().trim();
|
|
81
|
-
// `done` marks the terminal lane that auto-resolves blockers (relation-row
|
|
82
|
-
// SSOT — see updateTicket / unresolvedBlockers). The agent-flow roles
|
|
83
|
-
// (ai_ready/in_progress/review) are anchors the claim-next pipeline keys off.
|
|
84
|
-
const allowed = new Set(["ai_ready", "in_progress", "review", "done"]);
|
|
85
|
-
if (!allowed.has(role)) throw httpError(400, "invalid_state_role");
|
|
86
|
-
return role;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function appendFieldNote(existing, note, actorName) {
|
|
90
|
-
void actorName;
|
|
91
|
-
const text = String(note || "").trim();
|
|
92
|
-
if (!text) return existing || "";
|
|
93
|
-
const prior = String(existing || "").trim();
|
|
94
|
-
return [prior, text].filter(Boolean).join("\n\n");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function toFtsQuery(q) {
|
|
98
|
-
return q
|
|
99
|
-
.toLowerCase()
|
|
100
|
-
.replace(/[^a-z0-9_ -]/g, " ")
|
|
101
|
-
.split(/\s+/)
|
|
102
|
-
.map((part) => part.trim())
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.map((part) => `${part}*`)
|
|
105
|
-
.join(" AND ");
|
|
106
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Update checking. The server reads its own version from package.json and
|
|
2
|
-
// compares it against the `version` field of package.json on the project's
|
|
3
|
-
// GitHub default branch. We hit the GitHub *contents API* (not the raw CDN):
|
|
4
|
-
// it honours conditional requests / no-cache so the answer is authoritative,
|
|
5
|
-
// and because only the server polls — not every browser — GitHub's 60 req/hr
|
|
6
|
-
// unauthenticated limit is a non-issue. Results are cached in-memory for
|
|
7
|
-
// ORBIT_UPDATE_TTL minutes; `force` bypasses the cache for a "Check now".
|
|
8
|
-
//
|
|
9
|
-
// Env:
|
|
10
|
-
// ORBIT_UPDATE_CHECK=0 disable the check entirely (returns enabled:false)
|
|
11
|
-
// ORBIT_UPDATE_TTL=<min> cache lifetime, default 60
|
|
12
|
-
// ORBIT_UPDATE_BRANCH branch to read, default the repo default (master)
|
|
13
|
-
// ORBIT_GITHUB_TOKEN optional PAT (private repos / higher rate limit)
|
|
14
|
-
// ORBIT_SELF_UPDATE=1 enable the gated POST /api/update self-update
|
|
15
|
-
// ORBIT_DOCKER=1 force "docker" deploy mode
|
|
16
|
-
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
import { spawn } from "node:child_process";
|
|
20
|
-
import { ROOT_DIR, DATA_DIR } from "./paths.js";
|
|
21
|
-
|
|
22
|
-
const DEFAULT_OWNER = "brucestarlove";
|
|
23
|
-
const DEFAULT_REPO = "ss-orbit";
|
|
24
|
-
const CHECK_TIMEOUT_MS = 5000;
|
|
25
|
-
|
|
26
|
-
let pkgCache = null;
|
|
27
|
-
function readPackage() {
|
|
28
|
-
if (pkgCache) return pkgCache;
|
|
29
|
-
try {
|
|
30
|
-
pkgCache = JSON.parse(readFileSync(join(ROOT_DIR, "package.json"), "utf8"));
|
|
31
|
-
} catch {
|
|
32
|
-
pkgCache = {};
|
|
33
|
-
}
|
|
34
|
-
return pkgCache;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Parse owner/repo out of package.json repository.url, falling back to the
|
|
38
|
-
* known Orbit repo so the check still works on odd checkouts. */
|
|
39
|
-
function repoCoordinates() {
|
|
40
|
-
const url = readPackage()?.repository?.url || "";
|
|
41
|
-
const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/i);
|
|
42
|
-
return { owner: match?.[1] || DEFAULT_OWNER, repo: match?.[2] || DEFAULT_REPO };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function localVersion() {
|
|
46
|
-
return readPackage()?.version || "0.0.0";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function updateBranch() {
|
|
50
|
-
return process.env.ORBIT_UPDATE_BRANCH || "master";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** How this instance is running, which decides the update instructions the
|
|
54
|
-
* client shows. Docker images carry no git tree, so they're rebuild-only. */
|
|
55
|
-
function detectDeployMode() {
|
|
56
|
-
if (process.env.ORBIT_DOCKER === "1" || existsSync("/.dockerenv")) return "docker";
|
|
57
|
-
if (existsSync(join(ROOT_DIR, ".git"))) return "source";
|
|
58
|
-
return "dist";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Numeric "a.b.c" comparison (ignores any prerelease suffix). Returns true
|
|
62
|
-
* when `latest` is strictly newer than `current`. */
|
|
63
|
-
function isNewer(latest, current) {
|
|
64
|
-
const parse = (v) => String(v).replace(/^v/, "").split(/[.+-]/).map((n) => Number(n) || 0);
|
|
65
|
-
const a = parse(latest);
|
|
66
|
-
const b = parse(current);
|
|
67
|
-
for (let i = 0; i < 3; i += 1) {
|
|
68
|
-
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
69
|
-
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function fetchLatestVersion() {
|
|
75
|
-
const { owner, repo } = repoCoordinates();
|
|
76
|
-
const branch = updateBranch();
|
|
77
|
-
const headers = {
|
|
78
|
-
Accept: "application/vnd.github.raw+json",
|
|
79
|
-
"User-Agent": "orbit-update-check"
|
|
80
|
-
};
|
|
81
|
-
if (process.env.ORBIT_GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.ORBIT_GITHUB_TOKEN}`;
|
|
82
|
-
|
|
83
|
-
const controller = new AbortController();
|
|
84
|
-
const timer = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
|
|
85
|
-
try {
|
|
86
|
-
const res = await fetch(
|
|
87
|
-
`https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${encodeURIComponent(branch)}`,
|
|
88
|
-
{ headers, signal: controller.signal, cache: "no-store" }
|
|
89
|
-
);
|
|
90
|
-
if (res.status === 403 || res.status === 429) return { error: "rate_limited" };
|
|
91
|
-
if (!res.ok) return { error: `http_${res.status}` };
|
|
92
|
-
const pkg = JSON.parse(await res.text());
|
|
93
|
-
if (!pkg?.version) return { error: "no_version" };
|
|
94
|
-
return { latest: String(pkg.version) };
|
|
95
|
-
} catch (err) {
|
|
96
|
-
return { error: err?.name === "AbortError" ? "timeout" : "unreachable" };
|
|
97
|
-
} finally {
|
|
98
|
-
clearTimeout(timer);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let cache = null; // { at: epochMs, payload }
|
|
103
|
-
|
|
104
|
-
function staticPayload(extra) {
|
|
105
|
-
const { owner, repo } = repoCoordinates();
|
|
106
|
-
const branch = updateBranch();
|
|
107
|
-
return {
|
|
108
|
-
current: localVersion(),
|
|
109
|
-
deployMode: detectDeployMode(),
|
|
110
|
-
selfUpdate: process.env.ORBIT_SELF_UPDATE === "1",
|
|
111
|
-
repoUrl: `https://github.com/${owner}/${repo}`,
|
|
112
|
-
changesUrl: `https://github.com/${owner}/${repo}/commits/${branch}`,
|
|
113
|
-
...extra
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Get current/latest version info, cached for ORBIT_UPDATE_TTL minutes.
|
|
118
|
-
* Never throws — network problems surface as `{ error, updateAvailable:false }`. */
|
|
119
|
-
export async function getVersionInfo({ force = false } = {}) {
|
|
120
|
-
if (process.env.ORBIT_UPDATE_CHECK === "0") {
|
|
121
|
-
return staticPayload({ enabled: false, latest: null, updateAvailable: false, error: null, checkedAt: null });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const ttlMs = (Number(process.env.ORBIT_UPDATE_TTL) || 60) * 60 * 1000;
|
|
125
|
-
if (!force && cache && Date.now() - cache.at < ttlMs) return cache.payload;
|
|
126
|
-
|
|
127
|
-
const result = await fetchLatestVersion();
|
|
128
|
-
const current = localVersion();
|
|
129
|
-
const payload = staticPayload({
|
|
130
|
-
enabled: true,
|
|
131
|
-
latest: result.latest ?? null,
|
|
132
|
-
updateAvailable: Boolean(result.latest) && isNewer(result.latest, current),
|
|
133
|
-
error: result.error ?? null,
|
|
134
|
-
checkedAt: new Date().toISOString()
|
|
135
|
-
});
|
|
136
|
-
cache = { at: Date.now(), payload };
|
|
137
|
-
return payload;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Kick off scripts/update.sh detached and hand back immediately. Only ever
|
|
141
|
-
* reached when ORBIT_SELF_UPDATE=1 (the router gates the route). The script
|
|
142
|
-
* rebuilds and then signals this process so a supervisor restarts it. */
|
|
143
|
-
export function startSelfUpdate() {
|
|
144
|
-
const script = join(ROOT_DIR, "scripts", "update.sh");
|
|
145
|
-
if (!existsSync(script)) return { started: false, error: "script_missing" };
|
|
146
|
-
const child = spawn("bash", [script], {
|
|
147
|
-
cwd: ROOT_DIR,
|
|
148
|
-
detached: true,
|
|
149
|
-
stdio: "ignore",
|
|
150
|
-
env: { ...process.env, ORBIT_REPO_DIR: ROOT_DIR, ORBIT_SERVER_PID: String(process.pid), ORBIT_DATA_DIR: DATA_DIR }
|
|
151
|
-
});
|
|
152
|
-
child.unref();
|
|
153
|
-
return { started: true };
|
|
154
|
-
}
|
package/src/mcp/orbit-client.js
DELETED
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
|
|
4
|
-
function rpcError(code, message) {
|
|
5
|
-
const error = new Error(message);
|
|
6
|
-
error.code = code;
|
|
7
|
-
return error;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function orbitMode(env = process.env) {
|
|
11
|
-
const mode = String(env.ORBIT_MODE || "local").trim().toLowerCase();
|
|
12
|
-
if (mode === "remote" || mode === "http") return "remote";
|
|
13
|
-
if (mode === "local" || mode === "") return "local";
|
|
14
|
-
throw rpcError(-32602, `Unsupported ORBIT_MODE: ${env.ORBIT_MODE}`);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function createOrbitClient(env = process.env) {
|
|
18
|
-
return orbitMode(env) === "remote" ? createHttpOrbitClient(env) : createLocalOrbitClient(env);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.fetch) {
|
|
22
|
-
const apiUrl = String(env.ORBIT_API_URL || "").trim().replace(/\/+$/, "");
|
|
23
|
-
let defaultBoard = String(env.ORBIT_DEFAULT_BOARD || "").trim();
|
|
24
|
-
if (!apiUrl) throw rpcError(-32602, "ORBIT_MODE=remote requires ORBIT_API_URL.");
|
|
25
|
-
if (typeof fetchImpl !== "function") throw rpcError(-32603, "ORBIT_MODE=remote requires global fetch support.");
|
|
26
|
-
|
|
27
|
-
async function request(method, path, { query = {}, body, ok = [200] } = {}) {
|
|
28
|
-
const url = new URL(`${apiUrl}${path}`);
|
|
29
|
-
for (const [key, value] of Object.entries(query)) {
|
|
30
|
-
if (value !== undefined && value !== null && value !== "") url.searchParams.set(key, String(value));
|
|
31
|
-
}
|
|
32
|
-
const response = await fetchImpl(url, {
|
|
33
|
-
method,
|
|
34
|
-
headers: body === undefined ? undefined : { "Content-Type": "application/json" },
|
|
35
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
36
|
-
});
|
|
37
|
-
let payload = null;
|
|
38
|
-
const text = await response.text();
|
|
39
|
-
if (text) {
|
|
40
|
-
try { payload = JSON.parse(text); } catch { payload = { message: text }; }
|
|
41
|
-
}
|
|
42
|
-
if (!ok.includes(response.status)) {
|
|
43
|
-
const message = payload?.message || payload?.error || `Orbit API ${method} ${url.pathname} failed with HTTP ${response.status}`;
|
|
44
|
-
throw rpcError(-32000, message);
|
|
45
|
-
}
|
|
46
|
-
return payload ?? {};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function boardArg(args = {}) {
|
|
50
|
-
return args.board_id || args.board_slug || args.board || defaultBoard;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function requireBoard(args = {}) {
|
|
54
|
-
const board = boardArg(args);
|
|
55
|
-
if (!board) throw rpcError(-32602, "Remote Orbit MCP requires board_id/board_slug/board or ORBIT_DEFAULT_BOARD.");
|
|
56
|
-
return board;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const ticketQuery = (args = {}) => ({ board_id: args.board_id, board: args.board_slug || args.board || defaultBoard });
|
|
60
|
-
const stripBoardSelectors = (args = {}) => {
|
|
61
|
-
const { board_id, board_slug, board, ...rest } = args;
|
|
62
|
-
return rest;
|
|
63
|
-
};
|
|
64
|
-
const settingsPatch = (args = {}) => {
|
|
65
|
-
const patch = {};
|
|
66
|
-
for (const field of ["name", "repo_url", "system_path", "default_branch", "agent_instructions"]) {
|
|
67
|
-
if (args[field] !== undefined) patch[field] = args[field];
|
|
68
|
-
}
|
|
69
|
-
return patch;
|
|
70
|
-
};
|
|
71
|
-
const searchQuery = (args = {}) => ({
|
|
72
|
-
q: args.q,
|
|
73
|
-
limit: args.limit,
|
|
74
|
-
board_id: args.board_id,
|
|
75
|
-
board: args.board_slug || args.board || defaultBoard,
|
|
76
|
-
mode: args.mode,
|
|
77
|
-
include_full: args.include_full || args.includeFull ? "true" : undefined,
|
|
78
|
-
fields: Array.isArray(args.fields) ? args.fields.join(",") : args.fields,
|
|
79
|
-
max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField
|
|
80
|
-
});
|
|
81
|
-
const contextQuery = (args = {}) => ({
|
|
82
|
-
...ticketQuery(args),
|
|
83
|
-
depth: args.depth,
|
|
84
|
-
max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField,
|
|
85
|
-
comment_limit: args.comment_limit ?? args.commentLimit,
|
|
86
|
-
include_parent_full: args.include_parent_full || args.includeParentFull ? "true" : undefined,
|
|
87
|
-
include_related_full: args.include_related_full || args.includeRelatedFull ? "true" : undefined
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
mode: "remote",
|
|
92
|
-
sessionLabel: apiUrl,
|
|
93
|
-
close() {},
|
|
94
|
-
async boardContext(args = {}) {
|
|
95
|
-
return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/context`, { query: { include_struck: args.include_struck ? "true" : undefined } });
|
|
96
|
-
},
|
|
97
|
-
async boardList() { return request("GET", "/api/boards"); },
|
|
98
|
-
async boardSetActive(args = {}) { if (!args.slug) throw rpcError(-32602, "slug is required."); defaultBoard = String(args.slug); env.ORBIT_DEFAULT_BOARD = defaultBoard; return { ok: true, slug: defaultBoard, mode: "remote" }; },
|
|
99
|
-
async claimNext(args = {}) { return request("POST", "/api/agent/claim-next", { body: { ...args, board: requireBoard(args) } }); },
|
|
100
|
-
async getTicketContext(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
|
|
101
|
-
async getTicketContextFull(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context/full`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
|
|
102
|
-
async getAgentDispatchPacket(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/dispatch-packet`, { query: contextQuery(args) }); },
|
|
103
|
-
async readTicket(args = {}) {
|
|
104
|
-
if (args.ticket_id) {
|
|
105
|
-
return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}`, { query: ticketQuery(args) });
|
|
106
|
-
}
|
|
107
|
-
if (args.number !== undefined || args.title) {
|
|
108
|
-
return request("GET", "/api/tickets/lookup", { query: { ...ticketQuery(args), number: args.number, title: args.title } });
|
|
109
|
-
}
|
|
110
|
-
throw rpcError(-32602, "ticket_id_or_number_or_title_required");
|
|
111
|
-
},
|
|
112
|
-
async readComments(args = {}) {
|
|
113
|
-
if (args.ticket_id) {
|
|
114
|
-
return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/comments`, { query: ticketQuery(args) });
|
|
115
|
-
}
|
|
116
|
-
const found = await this.readTicket(args);
|
|
117
|
-
const ticket = found.ticket || found.results?.find((row) =>
|
|
118
|
-
(args.number !== undefined && Number(row.number) === Number(args.number)) ||
|
|
119
|
-
(args.title && String(row.title || "").toLowerCase() === String(args.title).toLowerCase())
|
|
120
|
-
);
|
|
121
|
-
if (!ticket?.id) throw rpcError(-32004, "ticket_not_found");
|
|
122
|
-
return request("GET", `/api/tickets/${encodeURIComponent(ticket.id)}/comments`, { query: ticketQuery(args) });
|
|
123
|
-
},
|
|
124
|
-
async getTicketRelations(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/relations`, { query: ticketQuery(args) }); },
|
|
125
|
-
async getTicketBlockers(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/blockers`, { query: ticketQuery(args) }); },
|
|
126
|
-
async search(args = {}) { return request("GET", "/api/search", { query: searchQuery(args) }); },
|
|
127
|
-
async createTicket(args = {}) { return request("POST", "/api/tickets", { body: { ...args, board: requireBoard(args) }, ok: [201] }); },
|
|
128
|
-
async updateTicket({ ticket_id, ...patch }) { return request("PATCH", `/api/tickets/${encodeURIComponent(ticket_id)}`, { query: ticketQuery(patch), body: stripBoardSelectors(patch) }); },
|
|
129
|
-
async uploadMarkdownArtifact({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/artifacts/markdown`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
|
|
130
|
-
async addComment({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/comments`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
|
|
131
|
-
async createReviewVerdict({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/review-verdicts`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
|
|
132
|
-
async listReviewVerdicts(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/review-verdicts`, { query: { ...ticketQuery(args), limit: args.limit } }); },
|
|
133
|
-
async getReviewVerdict(args = {}) { return request("GET", `/api/review-verdicts/${encodeURIComponent(args.review_id)}`, { query: ticketQuery(args) }); },
|
|
134
|
-
async addBoardEntry(args = {}) { return request("POST", `/api/boards/${encodeURIComponent(requireBoard(args))}/entries`, { body: stripBoardSelectors(args), ok: [201] }); },
|
|
135
|
-
async checkpoint(args = {}) { const fallback = boardArg(args); return request("POST", "/api/agent/checkpoint", { body: fallback ? { ...args, board: args.board || args.board_slug || fallback } : args }); },
|
|
136
|
-
async complete(args = {}) { const fallback = boardArg(args); return request("POST", "/api/agent/complete", { body: fallback ? { ...args, board: args.board || args.board_slug || fallback } : args }); },
|
|
137
|
-
async archiveTicket(args = {}) { return request("POST", `/api/tickets/${encodeURIComponent(args.ticket_id)}/archive`, { query: ticketQuery(args) }); },
|
|
138
|
-
async restoreTicket(args = {}) { return request("POST", `/api/tickets/${encodeURIComponent(args.ticket_id)}/restore`, { query: ticketQuery(args) }); },
|
|
139
|
-
async deleteTicket(args = {}) { return request("DELETE", `/api/tickets/${encodeURIComponent(args.ticket_id)}`, { query: ticketQuery(args) }); },
|
|
140
|
-
async listArchive(args = {}) { return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/archive`); },
|
|
141
|
-
async exportBoard(args = {}) {
|
|
142
|
-
return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/export`, {
|
|
143
|
-
query: { include_attachments: args.include_attachments || args.include_images ? "true" : undefined }
|
|
144
|
-
});
|
|
145
|
-
},
|
|
146
|
-
async updateSettings(args = {}) { return request("PATCH", `/api/boards/${encodeURIComponent(requireBoard(args))}`, { body: settingsPatch(args) }); }
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export async function createLocalOrbitClient(env = process.env) {
|
|
151
|
-
const board = await import("../core/board.js");
|
|
152
|
-
const dbMod = await import("../core/db.js");
|
|
153
|
-
const registry = await import("../core/registry.js");
|
|
154
|
-
const backups = await import("../core/backups.js");
|
|
155
|
-
const seed = await import("../core/seed.js");
|
|
156
|
-
const util = await import("../core/util.js");
|
|
157
|
-
|
|
158
|
-
const provision = await import("../core/provision-repo-board.js");
|
|
159
|
-
let sessionBoardRow = null;
|
|
160
|
-
const setSessionBoard = (row) => { sessionBoardRow = row; if (row) registry.touchBoardActive(row.id); };
|
|
161
|
-
const getSessionBoard = () => sessionBoardRow;
|
|
162
|
-
const walkUp = (startDir, predicate) => { let dir = startDir; while (true) { if (predicate(dir)) return dir; const parent = dirname(dir); if (parent === dir) return null; dir = parent; } };
|
|
163
|
-
|
|
164
|
-
const start = util.normalizePath(env.PROJECT_ROOT ? resolve(env.PROJECT_ROOT) : process.cwd());
|
|
165
|
-
const displayStart = resolve(env.PROJECT_ROOT ? env.PROJECT_ROOT : process.cwd());
|
|
166
|
-
|
|
167
|
-
// Find repo root (walk up for .git, fall back to start).
|
|
168
|
-
const gitRoot = walkUp(start, (dir) => existsSync(join(dir, ".git")));
|
|
169
|
-
const repoRoot = util.normalizePath(gitRoot || start);
|
|
170
|
-
|
|
171
|
-
// 1. Registry-first lookup: walk upward from start so we find boards regardless
|
|
172
|
-
// of whether they were registered at the git root, at PROJECT_ROOT, or at any
|
|
173
|
-
// ancestor (handles boards originally created from a subdirectory).
|
|
174
|
-
let foundByRegistry = false;
|
|
175
|
-
{
|
|
176
|
-
let dir = start;
|
|
177
|
-
while (true) {
|
|
178
|
-
const row = registry.getBoardByRepoPath(dir);
|
|
179
|
-
if (row && existsSync(row.db_path)) { setSessionBoard(row); foundByRegistry = true; break; }
|
|
180
|
-
const parent = util.normalizePath(dirname(dir));
|
|
181
|
-
if (parent === dir) break; // filesystem root
|
|
182
|
-
dir = parent;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (!foundByRegistry) {
|
|
186
|
-
// 2. Legacy fallback: walk up for an existing in-repo .orbit/board.db.
|
|
187
|
-
const boardRoot = walkUp(start, (dir) => existsSync(join(dir, ".orbit", "board.db")));
|
|
188
|
-
if (boardRoot) {
|
|
189
|
-
const root = util.normalizePath(boardRoot);
|
|
190
|
-
const existing = registry.getBoardByRepoPath(root);
|
|
191
|
-
if (existing) {
|
|
192
|
-
setSessionBoard(existing);
|
|
193
|
-
} else {
|
|
194
|
-
const dbPath = join(boardRoot, ".orbit", "board.db");
|
|
195
|
-
const db = dbMod.openConnection(dbPath);
|
|
196
|
-
dbMod.createBoardSchema(db);
|
|
197
|
-
const seeded = seed.seedIfEmpty(db, root);
|
|
198
|
-
if (seeded) {
|
|
199
|
-
const t = util.now();
|
|
200
|
-
registry.insertBoard({ id: seeded.id, slug: seeded.slug, name: seeded.name, repo_path: root, db_path: dbPath, repo_url: seeded.repo_url || "", default_branch: seeded.default_branch || "main", last_active_at: t, created_at: t, updated_at: t });
|
|
201
|
-
}
|
|
202
|
-
const fresh = registry.getBoardByRepoPath(root);
|
|
203
|
-
if (fresh) { registry.syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
// 3. No board found — auto-create one (goes to central DATA_DIR/boards/).
|
|
207
|
-
provision.provisionRepoBoard(repoRoot, { enableAi: true });
|
|
208
|
-
const fresh = registry.getBoardByRepoPath(repoRoot);
|
|
209
|
-
if (fresh) setSessionBoard(fresh);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const ctxFor = (row, actor) => {
|
|
214
|
-
if (!row) throw rpcError(-32602, "No board in session; pass board_id or board_slug, or call board_set_active first.");
|
|
215
|
-
return { actor, board: row, db: registry.openBoardDb(row) };
|
|
216
|
-
};
|
|
217
|
-
const backup = (ctx, result) => { backups.scheduleAutomaticBoardBackup(ctx.board, ctx.db); return result; };
|
|
218
|
-
const explicit = (args = {}) => {
|
|
219
|
-
if (args.board_id) {
|
|
220
|
-
const row = registry.getBoardByRegistryId(args.board_id);
|
|
221
|
-
if (!row) throw rpcError(-32004, `Board id not found: ${args.board_id}`);
|
|
222
|
-
return row;
|
|
223
|
-
}
|
|
224
|
-
if (args.board_slug || args.board) {
|
|
225
|
-
const slug = args.board_slug || args.board;
|
|
226
|
-
const row = registry.getBoardBySlug(slug);
|
|
227
|
-
if (!row) throw rpcError(-32004, `Board slug not found: ${slug}`);
|
|
228
|
-
return row;
|
|
229
|
-
}
|
|
230
|
-
return null;
|
|
231
|
-
};
|
|
232
|
-
const rowOrSession = (args = {}) => explicit(args) || getSessionBoard();
|
|
233
|
-
const stripBoardSelectors = (args = {}) => {
|
|
234
|
-
const { board_id, board_slug, board: _board, ...rest } = args;
|
|
235
|
-
return rest;
|
|
236
|
-
};
|
|
237
|
-
const settingsPatch = (args = {}) => {
|
|
238
|
-
const patch = {};
|
|
239
|
-
for (const field of ["name", "repo_url", "system_path", "default_branch", "agent_instructions"]) {
|
|
240
|
-
if (args[field] !== undefined) patch[field] = args[field];
|
|
241
|
-
}
|
|
242
|
-
return patch;
|
|
243
|
-
};
|
|
244
|
-
const actor = () => board.localAgentActor();
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
mode: "local",
|
|
248
|
-
sessionLabel: () => {
|
|
249
|
-
const b = getSessionBoard();
|
|
250
|
-
return b ? `${displayStart} target: ${b.repo_path} db: ${b.db_path}` : "(none)";
|
|
251
|
-
},
|
|
252
|
-
close: () => dbMod.closeAllConnections(),
|
|
253
|
-
boardContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getBoardContext(ctx.board.id, ctx, { includeStruck: Boolean(args.include_struck) }); },
|
|
254
|
-
boardList: () => ({ boards: registry.listBoards().map((row) => ({ id: row.id, slug: row.slug, name: row.name, repo_path: row.repo_path })) }),
|
|
255
|
-
boardSetActive: (args = {}) => { const slug = String(args.slug || "").trim(); if (!slug) throw rpcError(-32602, "slug is required."); const row = registry.getBoardBySlug(slug); if (!row) throw rpcError(-32004, `Board slug not found: ${slug}`); setSessionBoard(row); return { ok: true, board_id: row.id, slug: row.slug, name: row.name, db_path: row.db_path }; },
|
|
256
|
-
claimNext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.claimNext(args, ctx)); },
|
|
257
|
-
getTicketContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPack(args.ticket_id, ctx, Number(args.depth || 1), args); },
|
|
258
|
-
getTicketContextFull: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPackFull(args.ticket_id, ctx, Number(args.depth || 1), args); },
|
|
259
|
-
getAgentDispatchPacket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getAgentDispatchPacket(args.ticket_id, ctx, args); },
|
|
260
|
-
readTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readTicket(args, ctx); },
|
|
261
|
-
readComments: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readComments(args, ctx); },
|
|
262
|
-
getTicketRelations: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketRelations(args.ticket_id, ctx); },
|
|
263
|
-
getTicketBlockers: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketBlockers(args.ticket_id, ctx); },
|
|
264
|
-
search: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.searchTickets(args, ctx); },
|
|
265
|
-
createTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); const body = stripBoardSelectors(args); body.board_id = ctx.board.id; return backup(ctx, board.createTicket(body, ctx)); },
|
|
266
|
-
updateTicket: ({ ticket_id, ...patch }) => { const ctx = ctxFor(rowOrSession(patch), actor()); return backup(ctx, board.updateTicket(ticket_id, stripBoardSelectors(patch), ctx)); },
|
|
267
|
-
uploadMarkdownArtifact: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return board.storeTicketMarkdownArtifact(ticket_id, stripBoardSelectors(body), ctx); },
|
|
268
|
-
addComment: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return backup(ctx, board.createComment(ticket_id, stripBoardSelectors(body), ctx)); },
|
|
269
|
-
createReviewVerdict: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return backup(ctx, board.createReviewVerdict(ticket_id, stripBoardSelectors(body), ctx)); },
|
|
270
|
-
listReviewVerdicts: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return { review_verdicts: board.listReviewVerdicts(args.ticket_id, ctx, args) }; },
|
|
271
|
-
getReviewVerdict: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getReviewVerdict(args.review_id, ctx); },
|
|
272
|
-
addBoardEntry: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.createBoardEntry(ctx.board.id, stripBoardSelectors(args), ctx)); },
|
|
273
|
-
checkpoint: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.checkpointTicket(stripBoardSelectors(args), ctx)); },
|
|
274
|
-
complete: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.completeTicket(stripBoardSelectors(args), ctx)); },
|
|
275
|
-
archiveTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.archiveTicket(args.ticket_id, ctx)); },
|
|
276
|
-
restoreTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.restoreTicket(args.ticket_id, ctx)); },
|
|
277
|
-
deleteTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.deleteTicket(args.ticket_id, ctx)); },
|
|
278
|
-
listArchive: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return { tickets: board.archivedTicketsForBoard(ctx.db, ctx.board.id) }; },
|
|
279
|
-
exportBoard: (args = {}) => {
|
|
280
|
-
const ctx = ctxFor(rowOrSession(args), actor());
|
|
281
|
-
return board.exportBoard(ctx.board.id, ctx, {
|
|
282
|
-
includeAttachments: Boolean(args.include_attachments || args.include_images)
|
|
283
|
-
});
|
|
284
|
-
},
|
|
285
|
-
updateSettings: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.updateBoard(ctx.board.id, settingsPatch(args), ctx)); }
|
|
286
|
-
};
|
|
287
|
-
}
|
package/src/server.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { networkInterfaces } from "node:os";
|
|
3
|
-
import {
|
|
4
|
-
boardRuntime,
|
|
5
|
-
handleApi,
|
|
6
|
-
serveStatic,
|
|
7
|
-
sendEmpty,
|
|
8
|
-
sendJson,
|
|
9
|
-
startSSEStream,
|
|
10
|
-
startupSummary
|
|
11
|
-
} from "./core/board.js";
|
|
12
|
-
import { closeAllConnections } from "./core/db.js";
|
|
13
|
-
|
|
14
|
-
const server = createServer(async (req, res) => {
|
|
15
|
-
try {
|
|
16
|
-
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
17
|
-
|
|
18
|
-
if (req.method === "OPTIONS") {
|
|
19
|
-
sendEmpty(res, 204);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (url.pathname === "/api/events/stream" && req.method === "GET") {
|
|
24
|
-
startSSEStream(req, res, url);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (url.pathname.startsWith("/api/")) {
|
|
29
|
-
await handleApi(req, res, url);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
await serveStatic(req, res, url.pathname);
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.error(error);
|
|
36
|
-
if (error.status) {
|
|
37
|
-
sendJson(res, error.status, { error: error.code || "request_error", message: error.message });
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
sendJson(res, 500, { error: "internal_error", message: error.message });
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const requestedPort = boardRuntime.port;
|
|
45
|
-
const requestedHost = process.env.HOST || process.env.ORBIT_HOST || "";
|
|
46
|
-
const hasExplicitPort = Boolean(process.env.PORT);
|
|
47
|
-
const maxAutoPort = requestedPort + 99;
|
|
48
|
-
let currentPort = requestedPort;
|
|
49
|
-
|
|
50
|
-
function listen(port) {
|
|
51
|
-
if (requestedHost) {
|
|
52
|
-
server.listen(port, requestedHost);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
server.listen(port);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function displayHost(host) {
|
|
59
|
-
if (!host || host === "::" || host === "0.0.0.0") return "localhost";
|
|
60
|
-
return host;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function networkUrls(port) {
|
|
64
|
-
return Object.values(networkInterfaces())
|
|
65
|
-
.flat()
|
|
66
|
-
.filter((item) => item && item.family === "IPv4" && !item.internal)
|
|
67
|
-
.map((item) => `http://${item.address}:${port}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
server.on("error", (error) => {
|
|
71
|
-
if (!hasExplicitPort && error?.code === "EADDRINUSE" && currentPort < maxAutoPort) {
|
|
72
|
-
currentPort += 1;
|
|
73
|
-
console.warn(`[orbit] Port ${currentPort - 1} is in use; trying ${currentPort}.`);
|
|
74
|
-
listen(currentPort);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.error(error);
|
|
79
|
-
closeAllConnections();
|
|
80
|
-
process.exit(1);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
listen(currentPort);
|
|
84
|
-
|
|
85
|
-
server.on("listening", () => {
|
|
86
|
-
const address = server.address();
|
|
87
|
-
const port = typeof address === "object" && address ? address.port : currentPort;
|
|
88
|
-
const host = typeof address === "object" && address ? displayHost(address.address) : displayHost(requestedHost);
|
|
89
|
-
const summary = startupSummary(port, host);
|
|
90
|
-
console.log(`Starscape Orbit listening on ${summary.url}`);
|
|
91
|
-
const urls = networkUrls(port).filter((url) => url !== summary.url);
|
|
92
|
-
if (urls.length) console.log(`Network: ${urls.join(", ")}`);
|
|
93
|
-
console.log(`Registry: ${summary.registryPath}`);
|
|
94
|
-
console.log(`Boards (${summary.boardCount}):`);
|
|
95
|
-
for (const path of summary.boardPaths) console.log(` - ${path}`);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
let shuttingDown = false;
|
|
99
|
-
|
|
100
|
-
function shutdown() {
|
|
101
|
-
if (shuttingDown) return;
|
|
102
|
-
shuttingDown = true;
|
|
103
|
-
server.close(() => {
|
|
104
|
-
closeAllConnections();
|
|
105
|
-
process.exit(0);
|
|
106
|
-
});
|
|
107
|
-
setTimeout(() => {
|
|
108
|
-
closeAllConnections();
|
|
109
|
-
process.exit(0);
|
|
110
|
-
}, 1500).unref();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
process.once("SIGINT", shutdown);
|
|
114
|
-
process.once("SIGTERM", shutdown);
|
|
115
|
-
process.once("exit", () => closeAllConnections());
|