@starlove/orbit 1.1.1 → 1.1.2
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 +2 -2
- package/dist/full/version.json +2 -2
- package/docs/AGENT_PROTOCOL.md +22 -6
- package/package.json +1 -1
- package/public/js/local-backend.js +15 -4
- package/src/core/agent.js +25 -4
- package/src/core/board.js +1 -1
- package/src/core/dispatch.js +2 -2
- package/src/core/router.js +6 -0
- package/src/mcp/orbit-client.js +2 -0
- package/src/mcp-server.js +23 -2
package/README.md
CHANGED
|
@@ -176,8 +176,8 @@ A copy-editable client config lives at [docs/mcp-client.example.json](docs/mcp-c
|
|
|
176
176
|
| Purpose | Tool |
|
|
177
177
|
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
178
178
|
| Index | `board_list` (tiny registry), `board_set_active` |
|
|
179
|
-
| Board context | `board_context` (settings + journal + deployment; optional board + `include_struck`)
|
|
180
|
-
| Read | `board_read_ticket`, `board_read_comments`, `board_get_ticket_context` (
|
|
179
|
+
| Board context | `board_context` (settings + journal + deployment; optional board + `include_struck`) |
|
|
180
|
+
| Read | `board_read_ticket`, `board_read_comments`, `board_get_ticket_context` (lean), `board_get_ticket_context_full` (manual/journal), `board_get_ticket_relations`, `board_get_ticket_blockers`, `board_search`, `board_list_archive` |
|
|
181
181
|
| Agent flow | `board_claim_next`, `board_checkpoint`, `board_complete` |
|
|
182
182
|
| Write | `board_update_ticket`, `board_add_comment`, `board_add_board_entry`, `board_archive_ticket`, `board_restore_ticket`, `board_delete_ticket` |
|
|
183
183
|
| Admin | `board_export_board`, `board_update_settings` |
|
package/dist/full/version.json
CHANGED
package/docs/AGENT_PROTOCOL.md
CHANGED
|
@@ -72,7 +72,7 @@ Returns the first schedulable `AI Ready` ticket. The scheduler skips tickets blo
|
|
|
72
72
|
|
|
73
73
|
By default, `claim-next` skips `epic` cards because they are index/planning cards. To claim an epic for planning, pass `"type": "epic"` or `"include_epics": true`.
|
|
74
74
|
|
|
75
|
-
The response shape is `{ claimed: true, ticket_id, context }` where `context` is the lightweight `board_read_ticket` pack (`ticket` with title/description/labels/state and the `board_manual`). Ticket comments are not included in the default claim context; call `GET /api/tickets/:id/comments` (or MCP `board_read_comments`) when an explicit chat/comment surface needs them. For the
|
|
75
|
+
The response shape is `{ claimed: true, ticket_id, context }` where `context` is the lightweight `board_read_ticket` pack (`ticket` with title/description/labels/state and the `board_manual`). Ticket comments are not included in the default claim context; call `GET /api/tickets/:id/comments` (or MCP `board_read_comments`) when an explicit chat/comment surface needs them. For the lean ticket relationship pack, call `GET /api/tickets/:id/context` (or MCP `board_get_ticket_context`). For the historical heavy pack with board manual/journal attached, call `GET /api/tickets/:id/context/full` (or MCP `board_get_ticket_context_full`).
|
|
76
76
|
|
|
77
77
|
### Read Lightweight Ticket
|
|
78
78
|
|
|
@@ -86,7 +86,7 @@ MCP equivalent: `board_read_ticket`.
|
|
|
86
86
|
|
|
87
87
|
Returns the lightweight agent read shape: `ticket` with identity, title, description, labels, state/type/priority, plus `board_manual`. Ticket comments, relations, blockers, child cards, and implementation fields are intentionally omitted. Use this for quick orientation and claim context. The `lookup` form is exact by per-board ticket number or exact title; it does not use fuzzy search or comment/implementation indexes.
|
|
88
88
|
|
|
89
|
-
### Get Context Pack
|
|
89
|
+
### Get Lean Ticket Context Pack
|
|
90
90
|
|
|
91
91
|
```http
|
|
92
92
|
GET /api/tickets/:id/context?depth=1
|
|
@@ -94,15 +94,31 @@ GET /api/tickets/:id/context?depth=1
|
|
|
94
94
|
|
|
95
95
|
Returns:
|
|
96
96
|
|
|
97
|
-
- Ticket, board, state, labels.
|
|
98
|
-
- The board manual (`board_manual`), including `agent_instructions` and `project_notes`.
|
|
97
|
+
- Ticket, board identity, state, labels.
|
|
99
98
|
- Dedicated implementation fields: `ai_plan`, `implementation_summary`, and `implementation_updates`.
|
|
100
99
|
- Parent epic/story ticket when present.
|
|
101
100
|
- Child feature cards when the ticket is an epic/story.
|
|
102
101
|
- Related tickets.
|
|
103
102
|
- Blockers and blocking tickets.
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
Project manual/journal (`agent_instructions`, `project_notes`, and board entries) and ticket comments are intentionally omitted from this default agent context. Fetch project context and comments explicitly when needed:
|
|
105
|
+
|
|
106
|
+
```http
|
|
107
|
+
GET /api/boards/:board_id/context?include_struck=false
|
|
108
|
+
GET /api/tickets/:id/comments
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
MCP equivalents: `board_context` and `board_read_comments`.
|
|
112
|
+
|
|
113
|
+
### Get Full Ticket Context Pack
|
|
114
|
+
|
|
115
|
+
```http
|
|
116
|
+
GET /api/tickets/:id/context/full?depth=1
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
MCP equivalent: `board_get_ticket_context_full`.
|
|
120
|
+
|
|
121
|
+
Returns the historical heavy context shape: everything in the lean ticket context plus the full board row and `board_manual` / journal. Comments are still intentionally omitted; explicit comment/chat consumers should call:
|
|
106
122
|
|
|
107
123
|
```http
|
|
108
124
|
GET /api/tickets/:id/comments
|
|
@@ -176,7 +192,7 @@ Searches ticket titles, descriptions, and comments.
|
|
|
176
192
|
GET /api/boards/:board_id/context?include_struck=false
|
|
177
193
|
```
|
|
178
194
|
|
|
179
|
-
Returns the same shape as MCP `board_context`:
|
|
195
|
+
Returns the same shape as MCP `board_context`: board metadata (`agent_instructions`, `project_notes`, repository paths, default branch), `entries` (journal / decisions / lessons), and `deployment` paths. Use `include_struck=true` to include struck journal rows (default off).
|
|
180
196
|
|
|
181
197
|
MCP: `board_context` with optional `board_id` / `board_slug` / `board` and optional `include_struck`.
|
|
182
198
|
|
package/package.json
CHANGED
|
@@ -726,7 +726,7 @@ async function handleTicketArchive(ticketId, archive) {
|
|
|
726
726
|
});
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
-
async function handleTicketContext(ticketId) {
|
|
729
|
+
async function handleTicketContext(ticketId, { includeProjectContext = false } = {}) {
|
|
730
730
|
const db = await openDb();
|
|
731
731
|
const snap = await readBoardSnapshot(db);
|
|
732
732
|
const ticket = snap.tickets.find((t) => t.id === ticketId);
|
|
@@ -776,16 +776,26 @@ async function handleTicketContext(ticketId) {
|
|
|
776
776
|
other_ticket: compact(other)
|
|
777
777
|
};
|
|
778
778
|
});
|
|
779
|
-
|
|
779
|
+
const context = {
|
|
780
780
|
ticket: { ...ticket, labels },
|
|
781
|
-
board:
|
|
782
|
-
|
|
781
|
+
board: includeProjectContext
|
|
782
|
+
? snap.board
|
|
783
|
+
: {
|
|
784
|
+
id: snap.board.id,
|
|
785
|
+
slug: snap.board.slug,
|
|
786
|
+
name: snap.board.name,
|
|
787
|
+
repo_path: snap.board.system_path || "",
|
|
788
|
+
system_path: snap.board.system_path || "",
|
|
789
|
+
default_branch: snap.board.default_branch || "main"
|
|
790
|
+
},
|
|
783
791
|
parent_ticket: compact(parent),
|
|
784
792
|
child_tickets: childTickets,
|
|
785
793
|
relations,
|
|
786
794
|
blockers: [],
|
|
787
795
|
related_tickets: [...childTickets, ...(parent ? [parent] : [])]
|
|
788
796
|
};
|
|
797
|
+
if (includeProjectContext) context.board_manual = { entries: snap.boardEntries.filter((e) => !e.struck_at) };
|
|
798
|
+
return context;
|
|
789
799
|
}
|
|
790
800
|
|
|
791
801
|
async function handleTicketComments(ticketId) {
|
|
@@ -955,6 +965,7 @@ const ROUTES = [
|
|
|
955
965
|
{ m: "DELETE", re: /^\/api\/tickets\/([^/]+)$/, fn: (m) => handleTicketDelete(decodeURIComponent(m[1])) },
|
|
956
966
|
{ m: "POST", re: /^\/api\/tickets\/([^/]+)\/archive$/, fn: (m) => handleTicketArchive(decodeURIComponent(m[1]), true) },
|
|
957
967
|
{ m: "POST", re: /^\/api\/tickets\/([^/]+)\/restore$/, fn: (m) => handleTicketArchive(decodeURIComponent(m[1]), false) },
|
|
968
|
+
{ m: "GET", re: /^\/api\/tickets\/([^/]+)\/context\/full$/, fn: (m) => handleTicketContext(decodeURIComponent(m[1]), { includeProjectContext: true }) },
|
|
958
969
|
{ m: "GET", re: /^\/api\/tickets\/([^/]+)\/context$/, fn: (m) => handleTicketContext(decodeURIComponent(m[1])) },
|
|
959
970
|
{ m: "GET", re: /^\/api\/tickets\/([^/]+)\/comments$/, fn: (m) => handleTicketComments(decodeURIComponent(m[1])) },
|
|
960
971
|
{ m: "GET", re: /^\/api\/tickets\/([^/]+)\/history$/, fn: (m) => handleTicketHistory(decodeURIComponent(m[1])) },
|
package/src/core/agent.js
CHANGED
|
@@ -210,7 +210,18 @@ export function completeTicket(body, ctx) {
|
|
|
210
210
|
return getContextPack(ticket.id, ctx, 1);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
|
|
213
|
+
function contextBoardIdentity(innerBoard, ctx) {
|
|
214
|
+
return {
|
|
215
|
+
id: innerBoard.id,
|
|
216
|
+
slug: innerBoard.slug,
|
|
217
|
+
name: innerBoard.name,
|
|
218
|
+
repo_path: innerBoard.system_path || ctx.board.repo_path || "",
|
|
219
|
+
system_path: innerBoard.system_path || ctx.board.repo_path || "",
|
|
220
|
+
default_branch: innerBoard.default_branch || "main"
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildContextPack(ticketId, ctx, depth = 1, options = {}, { includeProjectContext = false } = {}) {
|
|
214
225
|
const { db, board, actor } = ctx;
|
|
215
226
|
const maxCharsPerField = positiveInt(options.max_chars_per_field ?? options.maxCharsPerField, 0);
|
|
216
227
|
const includeParentFull = Boolean(options.include_parent_full ?? options.includeParentFull);
|
|
@@ -236,13 +247,12 @@ export function getContextPack(ticketId, ctx, depth = 1, options = {}) {
|
|
|
236
247
|
...(parentTicket ? [parentTicket] : [])
|
|
237
248
|
]);
|
|
238
249
|
|
|
239
|
-
|
|
250
|
+
const pack = {
|
|
240
251
|
ticket: capTicketFields({
|
|
241
252
|
...ticket,
|
|
242
253
|
labels
|
|
243
254
|
}, maxCharsPerField),
|
|
244
|
-
board: innerBoard,
|
|
245
|
-
board_manual: boardManual(innerBoard.id, ctx),
|
|
255
|
+
board: includeProjectContext ? innerBoard : contextBoardIdentity(innerBoard, ctx),
|
|
246
256
|
parent_ticket: parentTicket ? (includeParentFull ? capTicketFields({ ...parentTicket, labels: labelsForTicket(db, parentTicket.id) }, maxCharsPerField) : compactTicket(parentTicket)) : null,
|
|
247
257
|
child_tickets: childTickets.map((child) => ({
|
|
248
258
|
...capTicketFields(child, maxCharsPerField),
|
|
@@ -259,6 +269,17 @@ export function getContextPack(ticketId, ctx, depth = 1, options = {}) {
|
|
|
259
269
|
includeRelatedFull ? capTicketFields({ ...related, labels: labelsForTicket(db, related.id) }, maxCharsPerField) : compactTicket(related)
|
|
260
270
|
)
|
|
261
271
|
};
|
|
272
|
+
|
|
273
|
+
if (includeProjectContext) pack.board_manual = boardManual(innerBoard.id, ctx);
|
|
274
|
+
return pack;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function getContextPack(ticketId, ctx, depth = 1, options = {}) {
|
|
278
|
+
return buildContextPack(ticketId, ctx, depth, options, { includeProjectContext: false });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function getContextPackFull(ticketId, ctx, depth = 1, options = {}) {
|
|
282
|
+
return buildContextPack(ticketId, ctx, depth, options, { includeProjectContext: true });
|
|
262
283
|
}
|
|
263
284
|
|
|
264
285
|
export function getAgentDispatchPacket(ticketId, ctx, options = {}) {
|
package/src/core/board.js
CHANGED
|
@@ -134,7 +134,7 @@ export {
|
|
|
134
134
|
export { archiveTicket, createComment, createTicket, deleteTicket, restoreTicket, updateTicket } from "./tickets.js";
|
|
135
135
|
export { archivedTicketsForBoard } from "./queries.js";
|
|
136
136
|
export { getTicketBlockers, getTicketRelations } from "./relations.js";
|
|
137
|
-
export { checkpointTicket, claimNext, completeTicket, getAgentDispatchPacket, getContextPack, readComments, readTicket } from "./agent.js";
|
|
137
|
+
export { checkpointTicket, claimNext, completeTicket, getAgentDispatchPacket, getContextPack, getContextPackFull, readComments, readTicket } from "./agent.js";
|
|
138
138
|
export { searchTickets } from "./search.js";
|
|
139
139
|
export { exportBoard } from "./snapshots.js";
|
|
140
140
|
|
package/src/core/dispatch.js
CHANGED
|
@@ -3,7 +3,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
|
|
|
3
3
|
import { extname, join, resolve } from "node:path";
|
|
4
4
|
import { localOwnerActor } from "./auth.js";
|
|
5
5
|
import { DISPATCH_RUNS_DIR } from "./paths.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getContextPackFull } from "./agent.js";
|
|
7
7
|
import { openBoardDb, getBoardByRegistryId, getBoardByRepoPath, getBoardBySlug, touchBoardActive } from "./registry.js";
|
|
8
8
|
import { stateByName, stateByRole, ticketById, ticketByNumber, unresolvedBlockers } from "./queries.js";
|
|
9
9
|
import { createRegistrySchema } from "./db.js";
|
|
@@ -438,7 +438,7 @@ export function dispatchTicket(options) {
|
|
|
438
438
|
|
|
439
439
|
mkdirSync(runDir, { recursive: true });
|
|
440
440
|
const policyBin = createPolicyBin(runDir, policyName);
|
|
441
|
-
const context =
|
|
441
|
+
const context = getContextPackFull(ticket.id, ctx, 1);
|
|
442
442
|
const handoff = buildHandoff({
|
|
443
443
|
boardRow,
|
|
444
444
|
context,
|
package/src/core/router.js
CHANGED
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
completeTicket,
|
|
68
68
|
getAgentDispatchPacket,
|
|
69
69
|
getContextPack,
|
|
70
|
+
getContextPackFull,
|
|
70
71
|
readComments,
|
|
71
72
|
readTicket
|
|
72
73
|
} from "./agent.js";
|
|
@@ -282,6 +283,11 @@ export async function handleApi(req, res, url) {
|
|
|
282
283
|
sendJson(res, 200, getContextPack(ticketId, ctx, depth, agentContextOptions(url)));
|
|
283
284
|
return;
|
|
284
285
|
}
|
|
286
|
+
if (sub === "context/full" && req.method === "GET") {
|
|
287
|
+
const depth = Number(url.searchParams.get("depth") || 1);
|
|
288
|
+
sendJson(res, 200, getContextPackFull(ticketId, ctx, depth, agentContextOptions(url)));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
285
291
|
if ((sub === "dispatch-packet" || sub === "agent-dispatch-packet") && req.method === "GET") {
|
|
286
292
|
sendJson(res, 200, getAgentDispatchPacket(ticketId, ctx, agentContextOptions(url)));
|
|
287
293
|
return;
|
package/src/mcp/orbit-client.js
CHANGED
|
@@ -87,6 +87,7 @@ export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.
|
|
|
87
87
|
async boardSetActive(args = {}) { if (!args.slug) throw rpcError(-32602, "slug is required."); env.ORBIT_DEFAULT_BOARD = String(args.slug); return { ok: true, slug: String(args.slug), mode: "remote" }; },
|
|
88
88
|
async claimNext(args = {}) { return request("POST", "/api/agent/claim-next", { body: { ...args, board: requireBoard(args) } }); },
|
|
89
89
|
async getTicketContext(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
|
|
90
|
+
async getTicketContextFull(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context/full`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
|
|
90
91
|
async getAgentDispatchPacket(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/dispatch-packet`, { query: contextQuery(args) }); },
|
|
91
92
|
async readTicket(args = {}) {
|
|
92
93
|
if (args.ticket_id) {
|
|
@@ -216,6 +217,7 @@ export async function createLocalOrbitClient(env = process.env) {
|
|
|
216
217
|
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 }; },
|
|
217
218
|
claimNext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.claimNext(args, ctx)); },
|
|
218
219
|
getTicketContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPack(args.ticket_id, ctx, Number(args.depth || 1), args); },
|
|
220
|
+
getTicketContextFull: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPackFull(args.ticket_id, ctx, Number(args.depth || 1), args); },
|
|
219
221
|
getAgentDispatchPacket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getAgentDispatchPacket(args.ticket_id, ctx, args); },
|
|
220
222
|
readTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readTicket(args, ctx); },
|
|
221
223
|
readComments: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readComments(args, ctx); },
|
package/src/mcp-server.js
CHANGED
|
@@ -185,7 +185,7 @@ const TOOL_DEFS = [
|
|
|
185
185
|
{
|
|
186
186
|
name: "board_context",
|
|
187
187
|
description:
|
|
188
|
-
"Board context pack:
|
|
188
|
+
"Board/project context pack: board metadata, agent_instructions, journal entries (board_entries), and deployment paths. Complements the lean board_get_ticket_context. Resolves board from board_id / board_slug / board, else the MCP session board. Set include_struck true to include struck journal rows (default false). Same payload as GET /api/boards/:id/context.",
|
|
189
189
|
inputSchema: {
|
|
190
190
|
type: "object",
|
|
191
191
|
properties: {
|
|
@@ -243,7 +243,7 @@ const TOOL_DEFS = [
|
|
|
243
243
|
{
|
|
244
244
|
name: "board_get_ticket_context",
|
|
245
245
|
description:
|
|
246
|
-
"
|
|
246
|
+
"Return the default lean context pack for a ticket: ticket fields, board identity only, relations, blockers, parent/children, and related ticket summaries. Project manual/journal and comments are intentionally omitted; use board_context for board manual/journal and board_read_comments for comment threads.",
|
|
247
247
|
inputSchema: {
|
|
248
248
|
type: "object",
|
|
249
249
|
properties: {
|
|
@@ -261,6 +261,27 @@ const TOOL_DEFS = [
|
|
|
261
261
|
},
|
|
262
262
|
handler: (args) => orbitClient.getTicketContext(args)
|
|
263
263
|
},
|
|
264
|
+
{
|
|
265
|
+
name: "board_get_ticket_context_full",
|
|
266
|
+
description:
|
|
267
|
+
"Return the explicit heavy ticket context pack, preserving the historical board_get_ticket_context shape: ticket, full board row, board_manual/journal, relations, blockers, parent/children, and related tickets. Comments are still intentionally omitted; use board_read_comments for comment threads.",
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
ticket_id: { type: "string" },
|
|
272
|
+
board_id: { type: "string" },
|
|
273
|
+
board_slug: { type: "string" },
|
|
274
|
+
board: { type: "string" },
|
|
275
|
+
depth: { type: "integer", minimum: 1, maximum: 3 },
|
|
276
|
+
max_chars_per_field: { type: "integer", minimum: 1 },
|
|
277
|
+
include_parent_full: { type: "boolean" },
|
|
278
|
+
include_related_full: { type: "boolean" }
|
|
279
|
+
},
|
|
280
|
+
required: ["ticket_id"],
|
|
281
|
+
additionalProperties: false
|
|
282
|
+
},
|
|
283
|
+
handler: (args) => orbitClient.getTicketContextFull(args)
|
|
284
|
+
},
|
|
264
285
|
{
|
|
265
286
|
name: "board_get_agent_dispatch_packet",
|
|
266
287
|
description:
|