@starlove/orbit 1.1.0 → 1.1.1
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/dist/full/version.json +2 -2
- package/package.json +1 -1
- package/src/core/agent.js +114 -7
- package/src/core/board.js +1 -1
- package/src/core/router.js +20 -2
- package/src/core/search.js +80 -7
- package/src/mcp/orbit-client.js +28 -8
- package/src/mcp-server.js +48 -6
package/dist/full/version.json
CHANGED
package/package.json
CHANGED
package/src/core/agent.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
relationRows,
|
|
8
8
|
stateByName,
|
|
9
9
|
stateByRole,
|
|
10
|
+
stateById,
|
|
10
11
|
ticketById,
|
|
11
12
|
ticketByNumber,
|
|
12
13
|
ticketByTitle,
|
|
@@ -209,8 +210,11 @@ export function completeTicket(body, ctx) {
|
|
|
209
210
|
return getContextPack(ticket.id, ctx, 1);
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
export function getContextPack(ticketId, ctx, depth = 1) {
|
|
213
|
+
export function getContextPack(ticketId, ctx, depth = 1, options = {}) {
|
|
213
214
|
const { db, board, actor } = ctx;
|
|
215
|
+
const maxCharsPerField = positiveInt(options.max_chars_per_field ?? options.maxCharsPerField, 0);
|
|
216
|
+
const includeParentFull = Boolean(options.include_parent_full ?? options.includeParentFull);
|
|
217
|
+
const includeRelatedFull = Boolean(options.include_related_full ?? options.includeRelatedFull);
|
|
214
218
|
const ticket = ticketById(db, ticketId);
|
|
215
219
|
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
216
220
|
const innerBoard = boardById(db, ticket.board_id);
|
|
@@ -233,19 +237,122 @@ export function getContextPack(ticketId, ctx, depth = 1) {
|
|
|
233
237
|
]);
|
|
234
238
|
|
|
235
239
|
return {
|
|
236
|
-
ticket: {
|
|
240
|
+
ticket: capTicketFields({
|
|
237
241
|
...ticket,
|
|
238
242
|
labels
|
|
239
|
-
},
|
|
243
|
+
}, maxCharsPerField),
|
|
240
244
|
board: innerBoard,
|
|
241
245
|
board_manual: boardManual(innerBoard.id, ctx),
|
|
242
|
-
parent_ticket: parentTicket ? compactTicket(parentTicket) : null,
|
|
246
|
+
parent_ticket: parentTicket ? (includeParentFull ? capTicketFields({ ...parentTicket, labels: labelsForTicket(db, parentTicket.id) }, maxCharsPerField) : compactTicket(parentTicket)) : null,
|
|
243
247
|
child_tickets: childTickets.map((child) => ({
|
|
244
|
-
...child,
|
|
248
|
+
...capTicketFields(child, maxCharsPerField),
|
|
245
249
|
child_count: childTicketsFor(db, child.id).length
|
|
246
250
|
})),
|
|
247
|
-
relations
|
|
251
|
+
relations: relations.map((relation) => ({
|
|
252
|
+
...relation,
|
|
253
|
+
other_ticket: includeRelatedFull
|
|
254
|
+
? capTicketFields({ ...relation.other_ticket, labels: labelsForTicket(db, relation.other_ticket.id) }, maxCharsPerField)
|
|
255
|
+
: compactTicket(relation.other_ticket)
|
|
256
|
+
})),
|
|
248
257
|
blockers,
|
|
249
|
-
related_tickets: relatedTickets
|
|
258
|
+
related_tickets: relatedTickets.map((related) =>
|
|
259
|
+
includeRelatedFull ? capTicketFields({ ...related, labels: labelsForTicket(db, related.id) }, maxCharsPerField) : compactTicket(related)
|
|
260
|
+
)
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function getAgentDispatchPacket(ticketId, ctx, options = {}) {
|
|
265
|
+
const { db, board, actor } = ctx;
|
|
266
|
+
const maxCharsPerField = positiveInt(options.max_chars_per_field ?? options.maxCharsPerField, 4000);
|
|
267
|
+
const commentLimit = Math.min(nonNegativeInt(options.comment_limit ?? options.commentLimit, 5), 20);
|
|
268
|
+
const ticket = ticketById(db, ticketId);
|
|
269
|
+
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
270
|
+
const innerBoard = boardById(db, ticket.board_id);
|
|
271
|
+
requireBoardAccess(actor, innerBoard);
|
|
272
|
+
|
|
273
|
+
const labels = labelsForTicket(db, ticket.id).map((label) => label.name);
|
|
274
|
+
const parentTicket = ticket.parent_ticket_id ? ticketById(db, ticket.parent_ticket_id) : null;
|
|
275
|
+
const blockers = unresolvedBlockers(db, ticket.id).map(compactTicket);
|
|
276
|
+
const comments = db
|
|
277
|
+
.prepare("SELECT id, author, kind, body, created_at FROM comments WHERE ticket_id = ? ORDER BY created_at DESC LIMIT ?")
|
|
278
|
+
.all(ticket.id, commentLimit)
|
|
279
|
+
.reverse()
|
|
280
|
+
.map((comment) => ({ ...comment, body: capString(comment.body, maxCharsPerField) }));
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
board: {
|
|
284
|
+
id: innerBoard.id,
|
|
285
|
+
slug: innerBoard.slug,
|
|
286
|
+
name: innerBoard.name,
|
|
287
|
+
repo_path: innerBoard.repo_path || innerBoard.system_path || "",
|
|
288
|
+
system_path: innerBoard.system_path || innerBoard.repo_path || "",
|
|
289
|
+
default_branch: innerBoard.default_branch || "main",
|
|
290
|
+
agent_instructions: capString(innerBoard.agent_instructions || "", maxCharsPerField)
|
|
291
|
+
},
|
|
292
|
+
ticket: {
|
|
293
|
+
id: ticket.id,
|
|
294
|
+
ticket_id: ticket.id,
|
|
295
|
+
number: ticket.number,
|
|
296
|
+
title: ticket.title,
|
|
297
|
+
type: ticket.type,
|
|
298
|
+
priority: ticket.priority,
|
|
299
|
+
state_id: ticket.state_id,
|
|
300
|
+
state_name: ticket.state_name,
|
|
301
|
+
state_role: ticket.state_role,
|
|
302
|
+
description: capString(ticket.description || "", maxCharsPerField),
|
|
303
|
+
acceptance: capString(extractAcceptance(ticket.description || ""), maxCharsPerField),
|
|
304
|
+
ai_plan: capString(ticket.ai_plan || "", maxCharsPerField),
|
|
305
|
+
labels
|
|
306
|
+
},
|
|
307
|
+
blockers: {
|
|
308
|
+
can_start: blockers.length === 0,
|
|
309
|
+
blockers
|
|
310
|
+
},
|
|
311
|
+
parent_ticket: parentTicket ? compactTicket(parentTicket) : null,
|
|
312
|
+
relevant_state_ids: relevantStateIds(db, innerBoard.id, ticket.state_id),
|
|
313
|
+
recent_comments: comments
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function positiveInt(value, fallback) {
|
|
318
|
+
const n = Number(value);
|
|
319
|
+
return Number.isInteger(n) && n > 0 ? n : fallback;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function nonNegativeInt(value, fallback) {
|
|
323
|
+
const n = Number(value);
|
|
324
|
+
return Number.isInteger(n) && n >= 0 ? n : fallback;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function capString(value, maxChars) {
|
|
328
|
+
const text = String(value || "");
|
|
329
|
+
return maxChars && text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function capTicketFields(ticket, maxChars) {
|
|
333
|
+
if (!maxChars) return ticket;
|
|
334
|
+
const copy = { ...ticket };
|
|
335
|
+
for (const field of ["description", "ai_plan", "implementation_summary", "implementation_updates"]) {
|
|
336
|
+
if (Object.hasOwn(copy, field)) copy[field] = capString(copy[field], maxChars);
|
|
337
|
+
}
|
|
338
|
+
return copy;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function extractAcceptance(description) {
|
|
342
|
+
const match = String(description || "").match(/(?:^|\n)\s*(?:acceptance criteria|acceptance)\s*:?\s*\n([\s\S]*)/i);
|
|
343
|
+
return match ? match[1].trim() : "";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function relevantStateIds(db, boardId, currentStateId) {
|
|
347
|
+
const byRole = (role) => stateByRole(db, boardId, role)?.id || null;
|
|
348
|
+
const byName = (name) => stateByName(db, boardId, name)?.id || null;
|
|
349
|
+
return {
|
|
350
|
+
current: currentStateId,
|
|
351
|
+
current_role: stateById(db, currentStateId)?.role || null,
|
|
352
|
+
ai_ready: byRole("ai_ready") || byName("AI Ready"),
|
|
353
|
+
in_progress: byRole("in_progress") || byName("In Progress"),
|
|
354
|
+
review: byRole("review") || byName("Review"),
|
|
355
|
+
done: byRole("done") || byName("Done"),
|
|
356
|
+
todo: byRole("todo") || byName("Todo")
|
|
250
357
|
};
|
|
251
358
|
}
|
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, getContextPack, readComments, readTicket } from "./agent.js";
|
|
137
|
+
export { checkpointTicket, claimNext, completeTicket, getAgentDispatchPacket, getContextPack, readComments, readTicket } from "./agent.js";
|
|
138
138
|
export { searchTickets } from "./search.js";
|
|
139
139
|
export { exportBoard } from "./snapshots.js";
|
|
140
140
|
|
package/src/core/router.js
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
checkpointTicket,
|
|
66
66
|
claimNext,
|
|
67
67
|
completeTicket,
|
|
68
|
+
getAgentDispatchPacket,
|
|
68
69
|
getContextPack,
|
|
69
70
|
readComments,
|
|
70
71
|
readTicket
|
|
@@ -104,6 +105,15 @@ function boardHintsFromQuery(url) {
|
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
function agentContextOptions(url) {
|
|
109
|
+
return {
|
|
110
|
+
max_chars_per_field: url.searchParams.get("max_chars_per_field"),
|
|
111
|
+
comment_limit: url.searchParams.get("comment_limit"),
|
|
112
|
+
include_parent_full: url.searchParams.get("include_parent_full") === "true",
|
|
113
|
+
include_related_full: url.searchParams.get("include_related_full") === "true"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
export async function handleApi(req, res, url) {
|
|
108
118
|
setCors(res);
|
|
109
119
|
|
|
@@ -269,7 +279,11 @@ export async function handleApi(req, res, url) {
|
|
|
269
279
|
}
|
|
270
280
|
if (sub === "context" && req.method === "GET") {
|
|
271
281
|
const depth = Number(url.searchParams.get("depth") || 1);
|
|
272
|
-
sendJson(res, 200, getContextPack(ticketId, ctx, depth));
|
|
282
|
+
sendJson(res, 200, getContextPack(ticketId, ctx, depth, agentContextOptions(url)));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if ((sub === "dispatch-packet" || sub === "agent-dispatch-packet") && req.method === "GET") {
|
|
286
|
+
sendJson(res, 200, getAgentDispatchPacket(ticketId, ctx, agentContextOptions(url)));
|
|
273
287
|
return;
|
|
274
288
|
}
|
|
275
289
|
if (sub === "history" && req.method === "GET") {
|
|
@@ -399,7 +413,11 @@ export async function handleApi(req, res, url) {
|
|
|
399
413
|
if (!row) throw httpError(400, "board_id_required");
|
|
400
414
|
const args = {
|
|
401
415
|
q: url.searchParams.get("q") || "",
|
|
402
|
-
limit: url.searchParams.get("limit")
|
|
416
|
+
limit: url.searchParams.get("limit"),
|
|
417
|
+
mode: url.searchParams.get("mode"),
|
|
418
|
+
include_full: url.searchParams.get("include_full") === "true" ? true : undefined,
|
|
419
|
+
fields: url.searchParams.get("fields"),
|
|
420
|
+
max_chars_per_field: url.searchParams.get("max_chars_per_field")
|
|
403
421
|
};
|
|
404
422
|
sendJson(res, 200, searchTickets(args, ctxFromBoardRow(row, actor)));
|
|
405
423
|
return;
|
package/src/core/search.js
CHANGED
|
@@ -12,9 +12,12 @@ export function searchTickets(args, ctx) {
|
|
|
12
12
|
const { db, board, actor } = ctx;
|
|
13
13
|
const q = String(args.q || "").trim();
|
|
14
14
|
const limit = Math.min(Number(args.limit || 20), 50);
|
|
15
|
+
const mode = normalizeMode(args);
|
|
16
|
+
const maxCharsPerField = positiveInt(args.max_chars_per_field ?? args.maxCharsPerField, 0);
|
|
17
|
+
const fields = parseFields(args.fields);
|
|
15
18
|
|
|
16
|
-
if (!q) return { query: q, results: [] };
|
|
17
|
-
if (!canAccessBoard(actor, board)) return { query: q, results: [] };
|
|
19
|
+
if (!q) return { query: q, mode, results: [] };
|
|
20
|
+
if (!canAccessBoard(actor, board)) return { query: q, mode, results: [] };
|
|
18
21
|
|
|
19
22
|
const ticketNumber = ticketNumberQuery(q);
|
|
20
23
|
const fts = toFtsQuery(q);
|
|
@@ -93,12 +96,82 @@ export function searchTickets(args, ctx) {
|
|
|
93
96
|
if (deduped.length >= limit) break;
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
const results = deduped.map((ticket) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}));
|
|
99
|
+
const results = deduped.map((ticket) =>
|
|
100
|
+
shapeSearchResult(ticket, labelsForTicket(db, ticket.id), { mode, fields, maxCharsPerField })
|
|
101
|
+
);
|
|
100
102
|
|
|
101
|
-
return { query: q, results };
|
|
103
|
+
return { query: q, mode, results };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeMode(args) {
|
|
107
|
+
if (args.include_full === true || args.includeFull === true) return "full";
|
|
108
|
+
const mode = String(args.mode || "summary").trim().toLowerCase();
|
|
109
|
+
return ["ids", "summary", "full"].includes(mode) ? mode : "summary";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function positiveInt(value, fallback) {
|
|
113
|
+
const n = Number(value);
|
|
114
|
+
return Number.isInteger(n) && n > 0 ? n : fallback;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseFields(fields) {
|
|
118
|
+
if (Array.isArray(fields)) return new Set(fields.map(String));
|
|
119
|
+
if (typeof fields === "string" && fields.trim()) {
|
|
120
|
+
return new Set(fields.split(",").map((field) => field.trim()).filter(Boolean));
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function cap(value, maxChars) {
|
|
126
|
+
if (!maxChars || typeof value !== "string") return value;
|
|
127
|
+
return value.length > maxChars ? value.slice(0, maxChars) : value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function snippetFor(ticket, maxChars) {
|
|
131
|
+
const text = ticket.description || ticket.ai_plan || ticket.implementation_summary || "";
|
|
132
|
+
return cap(text, maxChars || 240);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function labelNames(labels) {
|
|
136
|
+
return labels.map((label) => label.name);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function pickFields(row, fields) {
|
|
140
|
+
if (!fields) return row;
|
|
141
|
+
const picked = {};
|
|
142
|
+
for (const field of fields) {
|
|
143
|
+
if (Object.hasOwn(row, field)) picked[field] = row[field];
|
|
144
|
+
}
|
|
145
|
+
return picked;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function shapeSearchResult(ticket, labels, { mode, fields, maxCharsPerField }) {
|
|
149
|
+
if (mode === "ids") {
|
|
150
|
+
return pickFields({ ticket_id: ticket.id, id: ticket.id, number: ticket.number, rank: ticket.rank }, fields);
|
|
151
|
+
}
|
|
152
|
+
if (mode === "full") {
|
|
153
|
+
const full = { ...ticket, labels };
|
|
154
|
+
if (maxCharsPerField) {
|
|
155
|
+
for (const key of ["description", "ai_plan", "implementation_summary", "implementation_updates"]) {
|
|
156
|
+
full[key] = cap(full[key], maxCharsPerField);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return pickFields(full, fields);
|
|
160
|
+
}
|
|
161
|
+
return pickFields({
|
|
162
|
+
ticket_id: ticket.id,
|
|
163
|
+
id: ticket.id,
|
|
164
|
+
number: ticket.number,
|
|
165
|
+
title: ticket.title,
|
|
166
|
+
state_name: ticket.state_name,
|
|
167
|
+
state_role: ticket.state_role,
|
|
168
|
+
type: ticket.type,
|
|
169
|
+
priority: ticket.priority,
|
|
170
|
+
parent_ticket_id: ticket.parent_ticket_id,
|
|
171
|
+
rank: ticket.rank,
|
|
172
|
+
snippet: snippetFor(ticket, maxCharsPerField),
|
|
173
|
+
labels: labelNames(labels)
|
|
174
|
+
}, fields);
|
|
102
175
|
}
|
|
103
176
|
|
|
104
177
|
function ticketNumberQuery(q) {
|
package/src/mcp/orbit-client.js
CHANGED
|
@@ -57,6 +57,24 @@ export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
const ticketQuery = (args = {}) => ({ board_id: args.board_id, board: args.board_slug || args.board || defaultBoard });
|
|
60
|
+
const searchQuery = (args = {}) => ({
|
|
61
|
+
q: args.q,
|
|
62
|
+
limit: args.limit,
|
|
63
|
+
board_id: args.board_id,
|
|
64
|
+
board: args.board_slug || args.board || defaultBoard,
|
|
65
|
+
mode: args.mode,
|
|
66
|
+
include_full: args.include_full || args.includeFull ? "true" : undefined,
|
|
67
|
+
fields: Array.isArray(args.fields) ? args.fields.join(",") : args.fields,
|
|
68
|
+
max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField
|
|
69
|
+
});
|
|
70
|
+
const contextQuery = (args = {}) => ({
|
|
71
|
+
...ticketQuery(args),
|
|
72
|
+
depth: args.depth,
|
|
73
|
+
max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField,
|
|
74
|
+
comment_limit: args.comment_limit ?? args.commentLimit,
|
|
75
|
+
include_parent_full: args.include_parent_full || args.includeParentFull ? "true" : undefined,
|
|
76
|
+
include_related_full: args.include_related_full || args.includeRelatedFull ? "true" : undefined
|
|
77
|
+
});
|
|
60
78
|
|
|
61
79
|
return {
|
|
62
80
|
mode: "remote",
|
|
@@ -68,7 +86,8 @@ export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.
|
|
|
68
86
|
async boardList() { return request("GET", "/api/boards"); },
|
|
69
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" }; },
|
|
70
88
|
async claimNext(args = {}) { return request("POST", "/api/agent/claim-next", { body: { ...args, board: requireBoard(args) } }); },
|
|
71
|
-
async getTicketContext(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context`, { query: { ...
|
|
89
|
+
async getTicketContext(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
|
|
90
|
+
async getAgentDispatchPacket(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/dispatch-packet`, { query: contextQuery(args) }); },
|
|
72
91
|
async readTicket(args = {}) {
|
|
73
92
|
if (args.ticket_id) {
|
|
74
93
|
return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}`, { query: ticketQuery(args) });
|
|
@@ -92,7 +111,7 @@ export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.
|
|
|
92
111
|
},
|
|
93
112
|
async getTicketRelations(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/relations`, { query: ticketQuery(args) }); },
|
|
94
113
|
async getTicketBlockers(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/blockers`, { query: ticketQuery(args) }); },
|
|
95
|
-
async search(args = {}) { return request("GET", "/api/search", { query:
|
|
114
|
+
async search(args = {}) { return request("GET", "/api/search", { query: searchQuery(args) }); },
|
|
96
115
|
async createTicket(args = {}) { return request("POST", "/api/tickets", { body: { ...args, board: requireBoard(args) }, ok: [201] }); },
|
|
97
116
|
async updateTicket({ ticket_id, ...patch }) { return request("PATCH", `/api/tickets/${encodeURIComponent(ticket_id)}`, { query: ticketQuery(patch), body: patch }); },
|
|
98
117
|
async addComment({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/comments`, { body, ok: [201] }); },
|
|
@@ -196,12 +215,13 @@ export async function createLocalOrbitClient(env = process.env) {
|
|
|
196
215
|
boardList: () => ({ boards: registry.listBoards().map((row) => ({ id: row.id, slug: row.slug, name: row.name, repo_path: row.repo_path })) }),
|
|
197
216
|
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 }; },
|
|
198
217
|
claimNext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.claimNext(args, ctx)); },
|
|
199
|
-
getTicketContext: (args = {}) => board.getContextPack(args.ticket_id,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
getTicketContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPack(args.ticket_id, ctx, Number(args.depth || 1), args); },
|
|
219
|
+
getAgentDispatchPacket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getAgentDispatchPacket(args.ticket_id, ctx, args); },
|
|
220
|
+
readTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readTicket(args, ctx); },
|
|
221
|
+
readComments: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readComments(args, ctx); },
|
|
222
|
+
getTicketRelations: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketRelations(args.ticket_id, ctx); },
|
|
223
|
+
getTicketBlockers: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketBlockers(args.ticket_id, ctx); },
|
|
224
|
+
search: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.searchTickets(args, ctx); },
|
|
205
225
|
createTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); const { board_slug, board: _b, ...body } = args; body.board_id = ctx.board.id; return backup(ctx, board.createTicket(body, ctx)); },
|
|
206
226
|
updateTicket: ({ ticket_id, ...patch }) => { const ctx = sessionCtx(actor()); return backup(ctx, board.updateTicket(ticket_id, patch, ctx)); },
|
|
207
227
|
addComment: ({ ticket_id, ...body }) => { const ctx = sessionCtx(actor()); return backup(ctx, board.createComment(ticket_id, body, ctx)); },
|
package/src/mcp-server.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
deleteTicket,
|
|
20
20
|
exportBoard,
|
|
21
21
|
getBoardContext,
|
|
22
|
+
getAgentDispatchPacket,
|
|
22
23
|
getContextPack,
|
|
23
24
|
readComments,
|
|
24
25
|
readTicket,
|
|
@@ -247,13 +248,38 @@ const TOOL_DEFS = [
|
|
|
247
248
|
type: "object",
|
|
248
249
|
properties: {
|
|
249
250
|
ticket_id: { type: "string" },
|
|
250
|
-
|
|
251
|
+
board_id: { type: "string" },
|
|
252
|
+
board_slug: { type: "string" },
|
|
253
|
+
board: { type: "string" },
|
|
254
|
+
depth: { type: "integer", minimum: 1, maximum: 3 },
|
|
255
|
+
max_chars_per_field: { type: "integer", minimum: 1 },
|
|
256
|
+
include_parent_full: { type: "boolean" },
|
|
257
|
+
include_related_full: { type: "boolean" }
|
|
251
258
|
},
|
|
252
259
|
required: ["ticket_id"],
|
|
253
260
|
additionalProperties: false
|
|
254
261
|
},
|
|
255
262
|
handler: (args) => orbitClient.getTicketContext(args)
|
|
256
263
|
},
|
|
264
|
+
{
|
|
265
|
+
name: "board_get_agent_dispatch_packet",
|
|
266
|
+
description:
|
|
267
|
+
"Return a lean agent dispatch packet for one ticket: board identity/instructions, capped ticket description/acceptance/AI plan, blockers, shallow parent, relevant workflow state IDs, repo path/default branch, recent capped comments, and label names. Parent/related/implementation bodies are intentionally omitted.",
|
|
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
|
+
max_chars_per_field: { type: "integer", minimum: 1 },
|
|
276
|
+
comment_limit: { type: "integer", minimum: 0, maximum: 20 }
|
|
277
|
+
},
|
|
278
|
+
required: ["ticket_id"],
|
|
279
|
+
additionalProperties: false
|
|
280
|
+
},
|
|
281
|
+
handler: (args) => orbitClient.getAgentDispatchPacket(args)
|
|
282
|
+
},
|
|
257
283
|
{
|
|
258
284
|
name: "board_read_ticket",
|
|
259
285
|
description:
|
|
@@ -263,7 +289,10 @@ const TOOL_DEFS = [
|
|
|
263
289
|
properties: {
|
|
264
290
|
ticket_id: { type: "string" },
|
|
265
291
|
number: { type: "integer" },
|
|
266
|
-
title: { type: "string" }
|
|
292
|
+
title: { type: "string" },
|
|
293
|
+
board_id: { type: "string" },
|
|
294
|
+
board_slug: { type: "string" },
|
|
295
|
+
board: { type: "string" }
|
|
267
296
|
},
|
|
268
297
|
additionalProperties: false
|
|
269
298
|
},
|
|
@@ -278,7 +307,10 @@ const TOOL_DEFS = [
|
|
|
278
307
|
properties: {
|
|
279
308
|
ticket_id: { type: "string" },
|
|
280
309
|
number: { type: "integer" },
|
|
281
|
-
title: { type: "string" }
|
|
310
|
+
title: { type: "string" },
|
|
311
|
+
board_id: { type: "string" },
|
|
312
|
+
board_slug: { type: "string" },
|
|
313
|
+
board: { type: "string" }
|
|
282
314
|
},
|
|
283
315
|
additionalProperties: false
|
|
284
316
|
},
|
|
@@ -290,7 +322,10 @@ const TOOL_DEFS = [
|
|
|
290
322
|
inputSchema: {
|
|
291
323
|
type: "object",
|
|
292
324
|
properties: {
|
|
293
|
-
ticket_id: { type: "string" }
|
|
325
|
+
ticket_id: { type: "string" },
|
|
326
|
+
board_id: { type: "string" },
|
|
327
|
+
board_slug: { type: "string" },
|
|
328
|
+
board: { type: "string" }
|
|
294
329
|
},
|
|
295
330
|
required: ["ticket_id"],
|
|
296
331
|
additionalProperties: false
|
|
@@ -303,7 +338,10 @@ const TOOL_DEFS = [
|
|
|
303
338
|
inputSchema: {
|
|
304
339
|
type: "object",
|
|
305
340
|
properties: {
|
|
306
|
-
ticket_id: { type: "string" }
|
|
341
|
+
ticket_id: { type: "string" },
|
|
342
|
+
board_id: { type: "string" },
|
|
343
|
+
board_slug: { type: "string" },
|
|
344
|
+
board: { type: "string" }
|
|
307
345
|
},
|
|
308
346
|
required: ["ticket_id"],
|
|
309
347
|
additionalProperties: false
|
|
@@ -321,7 +359,11 @@ const TOOL_DEFS = [
|
|
|
321
359
|
board: { type: "string" },
|
|
322
360
|
board_slug: { type: "string" },
|
|
323
361
|
board_id: { type: "string" },
|
|
324
|
-
limit: { type: "integer", minimum: 1, maximum: 50 }
|
|
362
|
+
limit: { type: "integer", minimum: 1, maximum: 50 },
|
|
363
|
+
mode: { type: "string", enum: ["ids", "summary", "full"] },
|
|
364
|
+
include_full: { type: "boolean" },
|
|
365
|
+
fields: { type: "array", items: { type: "string" } },
|
|
366
|
+
max_chars_per_field: { type: "integer", minimum: 1 }
|
|
325
367
|
},
|
|
326
368
|
required: ["q"],
|
|
327
369
|
additionalProperties: false
|