@starlove/orbit 1.0.7 → 1.1.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/README.md +22 -7
- package/SKILL-ORBIT.md +26 -1
- package/dist/full/index.html +2 -2
- package/dist/full/js/app.a4f045fc.js +676 -0
- package/dist/full/js/app.a4f045fc.js.map +7 -0
- package/dist/full/{styles.14e9dbbd.css → styles.836872e7.css} +836 -39
- package/dist/full/version.json +2 -2
- package/docs/AGENT_PROTOCOL.md +23 -5
- package/docs/MCP_SETUP.md +5 -3
- package/docs/ORBIT_DISPATCH.md +185 -0
- package/docs/setup/orbit-init-wizard.md +174 -0
- package/package.json +2 -2
- package/public/js/actor-labels.js +65 -0
- package/public/js/app.js +8 -1
- package/public/js/card-actions.js +526 -0
- package/public/js/config.js +2 -1
- package/public/js/format.js +204 -10
- package/public/js/kanban.js +15 -0
- package/public/js/local-backend.js +24 -8
- package/public/js/router.js +3 -2
- package/public/js/search.js +6 -3
- package/public/js/settings.js +96 -26
- package/public/js/state.js +50 -0
- package/public/js/ticket-detail.js +428 -125
- package/public/styles.css +836 -39
- package/src/cli/orbit.js +217 -32
- package/src/core/agent.js +1 -18
- package/src/core/agents-md.js +15 -10
- package/src/core/attachments.js +238 -0
- package/src/core/auth.js +36 -0
- package/src/core/backups.js +9 -3
- package/src/core/board.js +31 -0
- package/src/core/boards.js +1 -1
- package/src/core/db.js +42 -2
- package/src/core/delete-board.js +87 -21
- package/src/core/dispatch.js +532 -0
- package/src/core/http.js +31 -11
- package/src/core/migrate.js +198 -0
- package/src/core/paths.js +6 -0
- package/src/core/registry.js +12 -9
- package/src/core/router.js +61 -5
- package/src/core/search.js +3 -3
- package/src/core/snapshots.js +14 -2
- package/src/mcp/orbit-client.js +81 -38
- package/src/mcp-server.js +190 -6
- package/src/server.js +20 -0
- package/dist/full/js/app.f4fd5e94.js +0 -604
- package/dist/full/js/app.f4fd5e94.js.map +0 -7
package/src/mcp-server.js
CHANGED
|
@@ -5,6 +5,46 @@
|
|
|
5
5
|
// "active board" state.
|
|
6
6
|
|
|
7
7
|
import { createOrbitClient } from "./mcp/orbit-client.js";
|
|
8
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import {
|
|
11
|
+
archivedTicketsForBoard,
|
|
12
|
+
archiveTicket,
|
|
13
|
+
checkpointTicket,
|
|
14
|
+
claimNext,
|
|
15
|
+
completeTicket,
|
|
16
|
+
createBoardEntry,
|
|
17
|
+
createComment,
|
|
18
|
+
createTicket,
|
|
19
|
+
deleteTicket,
|
|
20
|
+
exportBoard,
|
|
21
|
+
getBoardContext,
|
|
22
|
+
getContextPack,
|
|
23
|
+
readComments,
|
|
24
|
+
readTicket,
|
|
25
|
+
getTicketBlockers,
|
|
26
|
+
getTicketRelations,
|
|
27
|
+
localAgentActor,
|
|
28
|
+
restoreTicket,
|
|
29
|
+
searchTickets,
|
|
30
|
+
updateBoard,
|
|
31
|
+
updateTicket
|
|
32
|
+
} from "./core/board.js";
|
|
33
|
+
import { closeAllConnections, createBoardSchema, openConnection } from "./core/db.js";
|
|
34
|
+
import {
|
|
35
|
+
getBoardByRegistryId,
|
|
36
|
+
getBoardBySlug,
|
|
37
|
+
getBoardByRepoPath,
|
|
38
|
+
insertBoard,
|
|
39
|
+
listBoards,
|
|
40
|
+
openBoardDb,
|
|
41
|
+
syncRegistryFromBoardDb,
|
|
42
|
+
touchBoardActive
|
|
43
|
+
} from "./core/registry.js";
|
|
44
|
+
import { scheduleAutomaticBoardBackup } from "./core/backups.js";
|
|
45
|
+
import { provisionRepoBoard } from "./core/provision-repo-board.js";
|
|
46
|
+
import { seedIfEmpty } from "./core/seed.js";
|
|
47
|
+
import { now, normalizePath } from "./core/util.js";
|
|
8
48
|
|
|
9
49
|
const SERVER_INFO = {
|
|
10
50
|
name: "minimal-agent-board",
|
|
@@ -12,6 +52,133 @@ const SERVER_INFO = {
|
|
|
12
52
|
};
|
|
13
53
|
|
|
14
54
|
const orbitClient = await createOrbitClient();
|
|
55
|
+
// Per-process session board. Set on init by walking up from cwd, mutable via
|
|
56
|
+
// `board_set_active`. NEVER read by anything outside this file — every
|
|
57
|
+
// downstream call receives an explicit ctx.
|
|
58
|
+
let sessionBoardRow = null;
|
|
59
|
+
|
|
60
|
+
function setSessionBoard(row) {
|
|
61
|
+
sessionBoardRow = row;
|
|
62
|
+
if (row) touchBoardActive(row.id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getSessionBoard() {
|
|
66
|
+
return sessionBoardRow;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Walk up from `startDir` toward the filesystem root, returning the first
|
|
71
|
+
* directory that satisfies `predicate`. Returns null if none found.
|
|
72
|
+
*/
|
|
73
|
+
function walkUp(startDir, predicate) {
|
|
74
|
+
let dir = startDir;
|
|
75
|
+
while (true) {
|
|
76
|
+
if (predicate(dir)) return dir;
|
|
77
|
+
const parent = dirname(dir);
|
|
78
|
+
if (parent === dir) return null;
|
|
79
|
+
dir = parent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialise the MCP session board. Resolution order:
|
|
85
|
+
* 1. Walk up from PROJECT_ROOT (cwd unless `orbit mcp --cwd` or env overrides it)
|
|
86
|
+
* for an existing `.orbit/board.db` — finds the board even when the agent
|
|
87
|
+
* is launched from a subdirectory or MCP config uses an absolute command.
|
|
88
|
+
* 2. Walk up from PROJECT_ROOT for a `.git/` directory — auto-creates a board at
|
|
89
|
+
* the git root on first launch.
|
|
90
|
+
* 3. Fall back to PROJECT_ROOT itself — supports non-git projects and global installs.
|
|
91
|
+
*/
|
|
92
|
+
function initMcpSessionBoard() {
|
|
93
|
+
const start = normalizePath(process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : process.cwd());
|
|
94
|
+
|
|
95
|
+
// 1. Prefer the registry row for PROJECT_ROOT (or one of its ancestors). New
|
|
96
|
+
// Orbit boards live in central DATA_DIR storage, so there may be no in-repo
|
|
97
|
+
// .orbit/board.db to discover.
|
|
98
|
+
const registeredRoot = walkUp(start, (dir) => Boolean(getBoardByRepoPath(normalizePath(dir))));
|
|
99
|
+
if (registeredRoot) {
|
|
100
|
+
setSessionBoard(getBoardByRepoPath(normalizePath(registeredRoot)));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. Walk up for an existing legacy board db.
|
|
105
|
+
const boardRoot = walkUp(start, (dir) => existsSync(join(dir, ".orbit", "board.db")));
|
|
106
|
+
if (boardRoot) {
|
|
107
|
+
const root = normalizePath(boardRoot);
|
|
108
|
+
const existing = getBoardByRepoPath(root);
|
|
109
|
+
if (existing) { setSessionBoard(existing); return; }
|
|
110
|
+
// db exists on disk but isn't in the registry yet — register it.
|
|
111
|
+
const dbPath = join(boardRoot, ".orbit", "board.db");
|
|
112
|
+
const db = openConnection(dbPath);
|
|
113
|
+
createBoardSchema(db);
|
|
114
|
+
const seeded = seedIfEmpty(db, root);
|
|
115
|
+
if (seeded) {
|
|
116
|
+
const t = now();
|
|
117
|
+
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 });
|
|
118
|
+
}
|
|
119
|
+
const fresh = getBoardByRepoPath(root);
|
|
120
|
+
if (fresh) { syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Walk up for a git root; fall back to cwd for non-git projects.
|
|
125
|
+
const gitRoot = walkUp(start, (dir) => existsSync(join(dir, ".git")));
|
|
126
|
+
const result = provisionRepoBoard(normalizePath(gitRoot || start));
|
|
127
|
+
if (result.registryRow) setSessionBoard(result.registryRow);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createBoardAtRoot(root) {
|
|
131
|
+
const dbPath = join(root, ".orbit", "board.db");
|
|
132
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
133
|
+
const db = openConnection(dbPath);
|
|
134
|
+
createBoardSchema(db);
|
|
135
|
+
const seeded = seedIfEmpty(db, root);
|
|
136
|
+
if (seeded) {
|
|
137
|
+
const t = now();
|
|
138
|
+
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 });
|
|
139
|
+
}
|
|
140
|
+
const fresh = getBoardByRepoPath(root);
|
|
141
|
+
if (fresh) { syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
initMcpSessionBoard();
|
|
145
|
+
closeAllConnections();
|
|
146
|
+
|
|
147
|
+
/** Build a ctx for the session board (or a board the tool argument selected). */
|
|
148
|
+
function ctxFor(boardRow, actor) {
|
|
149
|
+
if (!boardRow) throw rpcError(-32602, "No board in session; pass board_id or board_slug, or call board_set_active first.");
|
|
150
|
+
return { actor, board: boardRow, db: openBoardDb(boardRow) };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sessionCtx(actor) {
|
|
154
|
+
return ctxFor(getSessionBoard(), actor);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function withAutomaticBackup(ctx, result) {
|
|
158
|
+
scheduleAutomaticBoardBackup(ctx.board, ctx.db);
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Resolve a registry row from explicit args (no session fallback). */
|
|
163
|
+
function resolveExplicitBoard(args) {
|
|
164
|
+
if (args.board_id) {
|
|
165
|
+
const row = getBoardByRegistryId(args.board_id);
|
|
166
|
+
if (!row) throw rpcError(-32004, "Board id not found.");
|
|
167
|
+
return row;
|
|
168
|
+
}
|
|
169
|
+
if (args.board_slug || args.board) {
|
|
170
|
+
const row = getBoardBySlug(args.board_slug || args.board);
|
|
171
|
+
if (!row) throw rpcError(-32004, "Board slug not found.");
|
|
172
|
+
return row;
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Resolve a board for a tool that accepts board_id/board_slug AND can fall
|
|
178
|
+
* back to the session board. */
|
|
179
|
+
function resolveBoardOrSession(args) {
|
|
180
|
+
return resolveExplicitBoard(args) || getSessionBoard();
|
|
181
|
+
}
|
|
15
182
|
|
|
16
183
|
const TOOL_DEFS = [
|
|
17
184
|
{
|
|
@@ -75,7 +242,7 @@ const TOOL_DEFS = [
|
|
|
75
242
|
{
|
|
76
243
|
name: "board_get_ticket_context",
|
|
77
244
|
description:
|
|
78
|
-
"Read the
|
|
245
|
+
"Read the default agent context pack for a ticket, including board agent_instructions (project-level agent context), relations, blockers, implementation fields, and child cards. Ticket comments are intentionally omitted; use board_read_comments for explicit comment retrieval.",
|
|
79
246
|
inputSchema: {
|
|
80
247
|
type: "object",
|
|
81
248
|
properties: {
|
|
@@ -90,7 +257,7 @@ const TOOL_DEFS = [
|
|
|
90
257
|
{
|
|
91
258
|
name: "board_read_ticket",
|
|
92
259
|
description:
|
|
93
|
-
"Look up a ticket by ticket_id, number, or exact title (case-insensitive) on the session's active board, and return its title, description, labels, state, and
|
|
260
|
+
"Look up a ticket by ticket_id, number, or exact title (case-insensitive) on the session's active board, and return its title, description, labels, state, and the board manual (agent_instructions + journal + deployment paths). Ticket comments are intentionally omitted; use board_read_comments for explicit comment retrieval.",
|
|
94
261
|
inputSchema: {
|
|
95
262
|
type: "object",
|
|
96
263
|
properties: {
|
|
@@ -332,7 +499,9 @@ const TOOL_DEFS = [
|
|
|
332
499
|
type: "object",
|
|
333
500
|
properties: {
|
|
334
501
|
board_id: { type: "string" },
|
|
335
|
-
board_slug: { type: "string" }
|
|
502
|
+
board_slug: { type: "string" },
|
|
503
|
+
include_attachments: { type: "boolean", description: "Embed attached image bytes in the JSON snapshot." },
|
|
504
|
+
include_images: { type: "boolean", description: "Alias for include_attachments." }
|
|
336
505
|
},
|
|
337
506
|
additionalProperties: false
|
|
338
507
|
},
|
|
@@ -341,7 +510,7 @@ const TOOL_DEFS = [
|
|
|
341
510
|
{
|
|
342
511
|
name: "board_update_settings",
|
|
343
512
|
description:
|
|
344
|
-
"PATCH-equivalent for board settings: name, repo_url, system_path, default_branch, agent_instructions (project-level agent context), and project_notes (Notes For You).
|
|
513
|
+
"PATCH-equivalent for board settings: name (display rename only; slug/canonical URLs stay unchanged), repo_url, system_path, default_branch, agent_instructions (project-level agent context), and project_notes (Notes For You).",
|
|
345
514
|
inputSchema: {
|
|
346
515
|
type: "object",
|
|
347
516
|
properties: {
|
|
@@ -364,13 +533,21 @@ const tools = new Map(TOOL_DEFS.map((tool) => [tool.name, tool]));
|
|
|
364
533
|
|
|
365
534
|
let inputBuffer = Buffer.alloc(0);
|
|
366
535
|
let expectedBodyLength = -1;
|
|
536
|
+
let shuttingDown = false;
|
|
537
|
+
|
|
538
|
+
function shutdown(code = 0) {
|
|
539
|
+
if (shuttingDown) return;
|
|
540
|
+
shuttingDown = true;
|
|
541
|
+
orbitClient.close?.();
|
|
542
|
+
process.exit(code);
|
|
543
|
+
}
|
|
367
544
|
|
|
368
545
|
process.stdin.on("data", (chunk) => {
|
|
369
546
|
inputBuffer = Buffer.concat([inputBuffer, chunk]);
|
|
370
547
|
drainInput();
|
|
371
548
|
});
|
|
372
549
|
|
|
373
|
-
process.stdin.on("end", () =>
|
|
550
|
+
process.stdin.on("end", () => shutdown(0));
|
|
374
551
|
process.stdin.resume();
|
|
375
552
|
|
|
376
553
|
function drainInput() {
|
|
@@ -449,6 +626,8 @@ async function handleMessage(body, transport = "content-length") {
|
|
|
449
626
|
}, transport);
|
|
450
627
|
} catch (error) {
|
|
451
628
|
writeMessage(errorResponse(message.id, error.code || -32000, error.message || "Internal error."), transport);
|
|
629
|
+
} finally {
|
|
630
|
+
orbitClient.close?.();
|
|
452
631
|
}
|
|
453
632
|
}
|
|
454
633
|
|
|
@@ -544,6 +723,11 @@ process.on("unhandledRejection", (error) => {
|
|
|
544
723
|
writeMessage(errorResponse(null, -32000, error?.message || "Unhandled rejection."));
|
|
545
724
|
});
|
|
546
725
|
|
|
726
|
+
process.once("SIGINT", () => shutdown(0));
|
|
727
|
+
process.once("SIGTERM", () => shutdown(0));
|
|
728
|
+
process.once("exit", () => orbitClient.close?.());
|
|
729
|
+
|
|
547
730
|
if (process.env.MAB_MCP_STDERR_LOG === "1") {
|
|
548
|
-
|
|
731
|
+
const target = typeof orbitClient.sessionLabel === "function" ? orbitClient.sessionLabel() : orbitClient.sessionLabel;
|
|
732
|
+
console.error(`Starscape Orbit MCP ready. Mode: ${orbitClient.mode}. Target: ${target}`);
|
|
549
733
|
}
|
package/src/server.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
startSSEStream,
|
|
9
9
|
startupSummary
|
|
10
10
|
} from "./core/board.js";
|
|
11
|
+
import { closeAllConnections } from "./core/db.js";
|
|
11
12
|
|
|
12
13
|
const server = createServer(async (req, res) => {
|
|
13
14
|
try {
|
|
@@ -46,3 +47,22 @@ server.listen(boardRuntime.port, () => {
|
|
|
46
47
|
console.log(`Boards (${summary.boardCount}):`);
|
|
47
48
|
for (const path of summary.boardPaths) console.log(` - ${path}`);
|
|
48
49
|
});
|
|
50
|
+
|
|
51
|
+
let shuttingDown = false;
|
|
52
|
+
|
|
53
|
+
function shutdown() {
|
|
54
|
+
if (shuttingDown) return;
|
|
55
|
+
shuttingDown = true;
|
|
56
|
+
server.close(() => {
|
|
57
|
+
closeAllConnections();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
});
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
closeAllConnections();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}, 1500).unref();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
process.once("SIGINT", shutdown);
|
|
67
|
+
process.once("SIGTERM", shutdown);
|
|
68
|
+
process.once("exit", () => closeAllConnections());
|