@sideboard-ai/core 0.1.184 → 0.1.186
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/{chunk-IMCIHSIF.js → chunk-3QTEXR7L.js} +20 -14
- package/dist/{chunk-4IQQEJ2C.js → chunk-VTIVVVEC.js} +20 -14
- package/dist/index.cjs +110 -72
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +93 -61
- package/dist/mcp/run-stdio.cjs +110 -72
- package/dist/mcp/run-stdio.js +93 -61
- package/dist/{orchestrator-4GLAG7YH.js → orchestrator-RJEULR5I.js} +1 -1
- package/dist/{orchestrator-DKVX67MH.js → orchestrator-RNDMJXAY.js} +1 -1
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -14340,7 +14340,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
14340
14340
|
const ids = jobIds.join(", ");
|
|
14341
14341
|
return [
|
|
14342
14342
|
`Detached job still running: ${ids}.`,
|
|
14343
|
-
"Do not end this turn. Loop wait_for_job (same id)
|
|
14343
|
+
"Do not end this turn. Loop wait_for_job (same id) until stillRunning is false. The log pane updates from each wait result.",
|
|
14344
14344
|
"If the job is hanging or doing the wrong thing, call stop_job (same id) instead of looping forever.",
|
|
14345
14345
|
"Then report the result. Do not tell the user you will let them know later."
|
|
14346
14346
|
].join(" ");
|
|
@@ -14348,7 +14348,7 @@ function formatJobStillRunningContinuePrompt(jobIds) {
|
|
|
14348
14348
|
function formatDeferredDoneContinuePrompt() {
|
|
14349
14349
|
return [
|
|
14350
14350
|
"You ended the turn after promising to report later, but no detached job is running.",
|
|
14351
|
-
"If tests/pack/deploy still need to run: start once with detached-job.cjs,
|
|
14351
|
+
"If tests/pack/deploy still need to run: start once with detached-job.cjs, then loop wait_for_job until stillRunning is false (the log pane updates from wait JSON).",
|
|
14352
14352
|
"Do not say you will let the user know later."
|
|
14353
14353
|
].join(" ");
|
|
14354
14354
|
}
|
|
@@ -14383,6 +14383,10 @@ function tailProgress(logFile, maxLines = 12) {
|
|
|
14383
14383
|
const lines = (0, import_node_fs40.readFileSync)(logFile, "utf8").split("\n");
|
|
14384
14384
|
return lines.slice(-maxLines).join("\n");
|
|
14385
14385
|
}
|
|
14386
|
+
function lastProgressLine(progress) {
|
|
14387
|
+
const last = progress.split("\n").map((l) => l.trim()).filter((l) => l && l !== "(no log yet)").at(-1);
|
|
14388
|
+
return last ? last.slice(0, 72) : void 0;
|
|
14389
|
+
}
|
|
14386
14390
|
function readLogLines(file) {
|
|
14387
14391
|
if (!(0, import_node_fs40.existsSync)(file)) return [];
|
|
14388
14392
|
const lines = (0, import_node_fs40.readFileSync)(file, "utf8").split("\n");
|
|
@@ -14419,6 +14423,7 @@ function toResult(id, snap, extra) {
|
|
|
14419
14423
|
} catch {
|
|
14420
14424
|
}
|
|
14421
14425
|
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
14426
|
+
const progress = extra?.progress ?? snap.progress;
|
|
14422
14427
|
return {
|
|
14423
14428
|
stillRunning,
|
|
14424
14429
|
ok,
|
|
@@ -14427,8 +14432,9 @@ function toResult(id, snap, extra) {
|
|
|
14427
14432
|
id,
|
|
14428
14433
|
pid: snap.pid,
|
|
14429
14434
|
exitCode: snap.exitCode ?? void 0,
|
|
14435
|
+
phase: lastProgressLine(progress),
|
|
14430
14436
|
delta,
|
|
14431
|
-
progress
|
|
14437
|
+
progress,
|
|
14432
14438
|
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
14433
14439
|
};
|
|
14434
14440
|
}
|
|
@@ -14568,8 +14574,8 @@ var init_wait_for_job = __esm({
|
|
|
14568
14574
|
init_workspace_scratch();
|
|
14569
14575
|
init_wait_for_turn();
|
|
14570
14576
|
MAX_JOB_CONTINUES = 8;
|
|
14571
|
-
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running.
|
|
14572
|
-
MCP_STOP_JOB_HINT = "Stopped.
|
|
14577
|
+
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. The type=log pane already appended this delta. Call wait_for_job again. If it is hanging, producing no useful output, or doing the wrong thing, call stop_job (same id) instead of looping forever. Do not end the turn or tell the user you will let them know later.";
|
|
14578
|
+
MCP_STOP_JOB_HINT = "Stopped. The type=log pane is status=failed with the last delta. Do not wait again unless you start a new command.";
|
|
14573
14579
|
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
14574
14580
|
}
|
|
14575
14581
|
});
|
|
@@ -14694,7 +14700,7 @@ function classifyWorktreeOwnership(group, viewerLogin = "") {
|
|
|
14694
14700
|
const reviewing = group.some((t) => {
|
|
14695
14701
|
const author = normalizeViewerLogin(t.prAuthorLogin);
|
|
14696
14702
|
if (author && me && author !== me) return true;
|
|
14697
|
-
if (t.sourceType === "pr" && (!author ||
|
|
14703
|
+
if (t.sourceType === "pr" && me && (!author || author !== me)) return true;
|
|
14698
14704
|
if (me && loginsInclude(t.prReviewerLogins, me)) return true;
|
|
14699
14705
|
return false;
|
|
14700
14706
|
});
|
|
@@ -17755,7 +17761,7 @@ Wait JSON:
|
|
|
17755
17761
|
|
|
17756
17762
|
## Sideboard UI (append-only log)
|
|
17757
17763
|
|
|
17758
|
-
The side column **is** the live view.
|
|
17764
|
+
The side column **is** the live view. \`wait_for_job\` / \`stop_job\` (and shell \`detached-job\` wait JSON) open a \`type=log\` pane automatically (\`artifact_id\` = job id, content = \`delta\`). You may still call \`present_artifact\` **\`type=log\`** for a custom title. Each update **appends** \u2014 send only \`delta\`, not the full log and not HTML.
|
|
17759
17765
|
|
|
17760
17766
|
\`\`\`
|
|
17761
17767
|
present_artifact
|
|
@@ -17767,7 +17773,7 @@ present_artifact
|
|
|
17767
17773
|
phase: <wait.phase>
|
|
17768
17774
|
\`\`\`
|
|
17769
17775
|
|
|
17770
|
-
After **start**,
|
|
17776
|
+
After **start**, the first \`wait_for_job\` opens the pane (\`status=running\`). Do **not** dump the log in chat. Do **not** also fence HTML.
|
|
17771
17777
|
|
|
17772
17778
|
Ad-hoc pid/log (legacy or another tool\u2019s files):
|
|
17773
17779
|
|
|
@@ -17778,8 +17784,8 @@ node <detached-job.cjs> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
17778
17784
|
## Agent loop
|
|
17779
17785
|
|
|
17780
17786
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
17781
|
-
2.
|
|
17782
|
-
3.
|
|
17787
|
+
2. Loop \`wait_for_job\` (or shell \`wait\`). The human should see **working** in the side column from the wait JSON \u2014 do not also dump the log in chat.
|
|
17788
|
+
3. After each slice, the same log pane appends \`delta\`. Optional: \`present_artifact\` the same id for a custom title.
|
|
17783
17789
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
17784
17790
|
5. **Stop** when the job is hanging, buffering forever, watching the wrong thing, or you already have the answer. MCP \`stop_job\` (same id, optional \`reason\`) or \`detached-job.cjs stop <id>\`. Do **not** stop a pack/test/deploy that is clearly making progress.
|
|
17785
17791
|
|
|
@@ -18285,7 +18291,7 @@ function formatLongRunningDirective(opts) {
|
|
|
18285
18291
|
"Long-running jobs (mandatory when a command may run more than ~30s \u2014 pack, test, deploy, `gh pr checks --watch`):",
|
|
18286
18292
|
"A Sideboard worktree turn SIGTERMs the agent shell (and its process group) when the user sends another message or the turn is interrupted; `block_until_ms: 0` is not enough. Detach instead, and never ask the human to poll.",
|
|
18287
18293
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree; same tool as \`scripts/detached-job.cjs\` when the worktree has it). If JSON says already-running, do not start again.`,
|
|
18288
|
-
"-
|
|
18294
|
+
"- Loop Sideboard MCP `wait_for_job` (returns in ~45s). The desktop opens a `type=log` pane from wait/stop (and shell detached-job JSON) automatically \u2014 you may still `present_artifact` with `artifact_id=<id>` for a custom title. stillRunning \u2192 wait again. Shell fallback: the same helper with `wait <id>`.",
|
|
18289
18295
|
"- Hanging, no useful output, or the wrong thing \u2192 `stop_job` (or the helper with `stop <id>`). Do not stop a pack/test/deploy that is clearly making progress.",
|
|
18290
18296
|
"- Stay in the loop until stillRunning is false (or you stopped it). ok \u2192 finish the task. failed / stopped \u2192 read the log, fix or narrow the command, start once.",
|
|
18291
18297
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
@@ -18293,7 +18299,7 @@ function formatLongRunningDirective(opts) {
|
|
|
18293
18299
|
}
|
|
18294
18300
|
function formatLongRunningReminder(opts) {
|
|
18295
18301
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
18296
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait)
|
|
18302
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait). The log pane updates from wait JSON; present_artifact type=log is optional. stop_job if hanging or wrong. Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
18297
18303
|
}
|
|
18298
18304
|
function formatArtifactDirective() {
|
|
18299
18305
|
return [
|
|
@@ -21709,10 +21715,32 @@ function issueNumberFromApiUrl(url) {
|
|
|
21709
21715
|
const number = Number(match[1]);
|
|
21710
21716
|
return Number.isFinite(number) && number > 0 ? number : null;
|
|
21711
21717
|
}
|
|
21718
|
+
var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
|
|
21719
|
+
var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
|
|
21720
|
+
function parseGitHubIssueCommentNode(item, allowed) {
|
|
21721
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
21722
|
+
if (!rec) return null;
|
|
21723
|
+
const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
|
|
21724
|
+
const number = issueNumberFromApiUrl(issueUrl);
|
|
21725
|
+
if (number == null) return null;
|
|
21726
|
+
if (allowed && !allowed.has(number)) return null;
|
|
21727
|
+
const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
|
|
21728
|
+
const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
|
|
21729
|
+
const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
|
|
21730
|
+
const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
|
|
21731
|
+
return {
|
|
21732
|
+
identifier: `#${number}`,
|
|
21733
|
+
...user ? { author: user } : {},
|
|
21734
|
+
...createdAt ? { createdAt } : {},
|
|
21735
|
+
body,
|
|
21736
|
+
...url ? { url } : {}
|
|
21737
|
+
};
|
|
21738
|
+
}
|
|
21712
21739
|
async function listGitHubIssueCommentsSince(opts) {
|
|
21713
21740
|
const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
21714
21741
|
const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
|
|
21715
21742
|
if (!slug) return [];
|
|
21743
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
21716
21744
|
const allowed = new Set(
|
|
21717
21745
|
(opts.issueIdentifiers ?? []).map((id) => {
|
|
21718
21746
|
try {
|
|
@@ -21722,36 +21750,27 @@ async function listGitHubIssueCommentsSince(opts) {
|
|
|
21722
21750
|
}
|
|
21723
21751
|
}).filter((n) => n != null)
|
|
21724
21752
|
);
|
|
21725
|
-
|
|
21726
|
-
const
|
|
21727
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
21728
|
-
let parsed;
|
|
21729
|
-
try {
|
|
21730
|
-
parsed = JSON.parse(result.stdout);
|
|
21731
|
-
} catch {
|
|
21732
|
-
return [];
|
|
21733
|
-
}
|
|
21734
|
-
if (!Array.isArray(parsed)) return [];
|
|
21753
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
21754
|
+
const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
|
|
21735
21755
|
const out = [];
|
|
21736
|
-
for (
|
|
21737
|
-
const
|
|
21738
|
-
|
|
21739
|
-
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
21743
|
-
|
|
21744
|
-
|
|
21745
|
-
|
|
21746
|
-
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
if (out.length >= limit) break;
|
|
21756
|
+
for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
|
|
21757
|
+
const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
|
|
21758
|
+
const result = await gh(["api", path], cwd, { reject: false });
|
|
21759
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) break;
|
|
21760
|
+
let parsed;
|
|
21761
|
+
try {
|
|
21762
|
+
parsed = JSON.parse(result.stdout);
|
|
21763
|
+
} catch {
|
|
21764
|
+
break;
|
|
21765
|
+
}
|
|
21766
|
+
if (!Array.isArray(parsed) || parsed.length === 0) break;
|
|
21767
|
+
for (const item of parsed) {
|
|
21768
|
+
const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
|
|
21769
|
+
if (!comment) continue;
|
|
21770
|
+
out.push(comment);
|
|
21771
|
+
if (out.length >= limit) return out;
|
|
21772
|
+
}
|
|
21773
|
+
if (parsed.length < pageSize) break;
|
|
21755
21774
|
}
|
|
21756
21775
|
return out;
|
|
21757
21776
|
}
|
|
@@ -22361,12 +22380,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
22361
22380
|
function assigneeFilterKey(assignee) {
|
|
22362
22381
|
return (assignee ?? "").trim();
|
|
22363
22382
|
}
|
|
22383
|
+
function applyLinearIssueQueryFilter(filter, query) {
|
|
22384
|
+
const term = query?.trim();
|
|
22385
|
+
if (!term) return;
|
|
22386
|
+
const or = [
|
|
22387
|
+
{ title: { containsIgnoreCase: term } },
|
|
22388
|
+
{ description: { containsIgnoreCase: term } }
|
|
22389
|
+
];
|
|
22390
|
+
const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
|
|
22391
|
+
const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
|
|
22392
|
+
if (Number.isFinite(asNumber) && asNumber > 0) {
|
|
22393
|
+
or.push({ number: { eq: asNumber } });
|
|
22394
|
+
}
|
|
22395
|
+
filter.or = or;
|
|
22396
|
+
}
|
|
22364
22397
|
function buildLinearIssueFilter(input) {
|
|
22365
22398
|
const filter = {
|
|
22366
22399
|
state: { type: { nin: ["completed", "canceled"] } }
|
|
22367
22400
|
};
|
|
22368
22401
|
const since = input?.updatedSince?.trim();
|
|
22369
22402
|
if (since) filter.updatedAt = { gte: since };
|
|
22403
|
+
applyLinearIssueQueryFilter(filter, input?.query);
|
|
22370
22404
|
const raw = assigneeFilterKey(input?.assignee) || "me";
|
|
22371
22405
|
const key = raw.toLowerCase();
|
|
22372
22406
|
if (key === "all" || key === "*") return filter;
|
|
@@ -22432,6 +22466,11 @@ async function listLinearIssuesFiltered(opts) {
|
|
|
22432
22466
|
async function listLinearCommentsSince(opts) {
|
|
22433
22467
|
const first = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
22434
22468
|
const query = opts.query?.trim() ?? "";
|
|
22469
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
22470
|
+
const allowed = new Set(
|
|
22471
|
+
(opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
|
|
22472
|
+
);
|
|
22473
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
22435
22474
|
const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
|
|
22436
22475
|
const json = await linearGraphql(
|
|
22437
22476
|
COMMENTS_SINCE_QUERY,
|
|
@@ -22439,7 +22478,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
22439
22478
|
first,
|
|
22440
22479
|
filter: {
|
|
22441
22480
|
createdAt: { gte: opts.since },
|
|
22442
|
-
issue: buildLinearIssueFilter({ assignee })
|
|
22481
|
+
issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
|
|
22443
22482
|
}
|
|
22444
22483
|
},
|
|
22445
22484
|
opts
|
|
@@ -22447,6 +22486,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
22447
22486
|
const out = [];
|
|
22448
22487
|
for (const node of json.comments?.nodes ?? []) {
|
|
22449
22488
|
const identifier = String(node.issue?.identifier ?? "").trim();
|
|
22489
|
+
if (restrictToIssues && !allowed.has(identifier)) continue;
|
|
22450
22490
|
const body = previewIssueCommentBody(String(node.body ?? ""));
|
|
22451
22491
|
if (!identifier && !body) continue;
|
|
22452
22492
|
out.push({
|
|
@@ -22672,20 +22712,19 @@ async function listIssues(repoPath, opts) {
|
|
|
22672
22712
|
const assignee = opts?.assignee?.trim() || void 0;
|
|
22673
22713
|
const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
|
|
22674
22714
|
if (source === "linear") {
|
|
22675
|
-
const
|
|
22676
|
-
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
22681
|
-
|
|
22682
|
-
since
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
22687
|
-
|
|
22688
|
-
]);
|
|
22715
|
+
const listed = await listLinearIssuesFiltered({
|
|
22716
|
+
assignee,
|
|
22717
|
+
query,
|
|
22718
|
+
limit: opts?.limit,
|
|
22719
|
+
updatedSince: since
|
|
22720
|
+
});
|
|
22721
|
+
const comments2 = since ? await listLinearCommentsSince({
|
|
22722
|
+
since,
|
|
22723
|
+
assignee,
|
|
22724
|
+
query,
|
|
22725
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
22726
|
+
limit: opts?.limit
|
|
22727
|
+
}) : void 0;
|
|
22689
22728
|
return {
|
|
22690
22729
|
source,
|
|
22691
22730
|
preferredSource,
|
|
@@ -24040,20 +24079,19 @@ function registerLinearTools(server) {
|
|
|
24040
24079
|
try {
|
|
24041
24080
|
const page = clampMcpIssueLimit(limit);
|
|
24042
24081
|
const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
|
|
24043
|
-
const
|
|
24044
|
-
|
|
24045
|
-
|
|
24046
|
-
|
|
24047
|
-
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
since
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
]);
|
|
24082
|
+
const listed = await listLinearIssuesFiltered({
|
|
24083
|
+
query,
|
|
24084
|
+
assignee,
|
|
24085
|
+
limit: page + 1,
|
|
24086
|
+
updatedSince: since
|
|
24087
|
+
});
|
|
24088
|
+
const comments = since ? await listLinearCommentsSince({
|
|
24089
|
+
since,
|
|
24090
|
+
assignee,
|
|
24091
|
+
query,
|
|
24092
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
24093
|
+
limit: page + 1
|
|
24094
|
+
}) : void 0;
|
|
24057
24095
|
return mcpJson(
|
|
24058
24096
|
formatListedIssuesForMcp(
|
|
24059
24097
|
{
|
|
@@ -25164,7 +25202,7 @@ async function startMcpServer() {
|
|
|
25164
25202
|
);
|
|
25165
25203
|
server.tool(
|
|
25166
25204
|
"wait_for_job",
|
|
25167
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true,
|
|
25205
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
25168
25206
|
{
|
|
25169
25207
|
id: import_zod9.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
25170
25208
|
timeoutMs: import_zod9.z.number().optional()
|
|
@@ -25178,7 +25216,7 @@ async function startMcpServer() {
|
|
|
25178
25216
|
);
|
|
25179
25217
|
server.tool(
|
|
25180
25218
|
"stop_job",
|
|
25181
|
-
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress.
|
|
25219
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
|
|
25182
25220
|
{
|
|
25183
25221
|
id: import_zod9.z.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
25184
25222
|
reason: import_zod9.z.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|
package/dist/mcp/run-stdio.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
syncBoardPins,
|
|
38
38
|
updateSchedule,
|
|
39
39
|
waitForDetachedJob
|
|
40
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-3QTEXR7L.js";
|
|
41
41
|
import {
|
|
42
42
|
applySystemCaEnv,
|
|
43
43
|
isCursorApiKeyConfigured,
|
|
@@ -262,10 +262,32 @@ function issueNumberFromApiUrl(url) {
|
|
|
262
262
|
const number = Number(match[1]);
|
|
263
263
|
return Number.isFinite(number) && number > 0 ? number : null;
|
|
264
264
|
}
|
|
265
|
+
var GITHUB_ISSUE_COMMENTS_PAGE_SIZE = 100;
|
|
266
|
+
var GITHUB_ISSUE_COMMENTS_MAX_PAGES = 5;
|
|
267
|
+
function parseGitHubIssueCommentNode(item, allowed) {
|
|
268
|
+
const rec = item && typeof item === "object" ? item : null;
|
|
269
|
+
if (!rec) return null;
|
|
270
|
+
const issueUrl = typeof rec.issue_url === "string" ? rec.issue_url : "";
|
|
271
|
+
const number = issueNumberFromApiUrl(issueUrl);
|
|
272
|
+
if (number == null) return null;
|
|
273
|
+
if (allowed && !allowed.has(number)) return null;
|
|
274
|
+
const body = previewIssueCommentBody(typeof rec.body === "string" ? rec.body : "");
|
|
275
|
+
const user = rec.user && typeof rec.user === "object" ? String(rec.user.login ?? "").trim() : "";
|
|
276
|
+
const createdAt = typeof rec.created_at === "string" ? rec.created_at : typeof rec.createdAt === "string" ? rec.createdAt : void 0;
|
|
277
|
+
const url = typeof rec.html_url === "string" ? rec.html_url : void 0;
|
|
278
|
+
return {
|
|
279
|
+
identifier: `#${number}`,
|
|
280
|
+
...user ? { author: user } : {},
|
|
281
|
+
...createdAt ? { createdAt } : {},
|
|
282
|
+
body,
|
|
283
|
+
...url ? { url } : {}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
265
286
|
async function listGitHubIssueCommentsSince(opts) {
|
|
266
287
|
const limit = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
267
288
|
const { cwd, slug } = await resolveGitHubIssueRepo(opts.repoPath);
|
|
268
289
|
if (!slug) return [];
|
|
290
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
269
291
|
const allowed = new Set(
|
|
270
292
|
(opts.issueIdentifiers ?? []).map((id) => {
|
|
271
293
|
try {
|
|
@@ -275,36 +297,27 @@ async function listGitHubIssueCommentsSince(opts) {
|
|
|
275
297
|
}
|
|
276
298
|
}).filter((n) => n != null)
|
|
277
299
|
);
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
281
|
-
let parsed;
|
|
282
|
-
try {
|
|
283
|
-
parsed = JSON.parse(result.stdout);
|
|
284
|
-
} catch {
|
|
285
|
-
return [];
|
|
286
|
-
}
|
|
287
|
-
if (!Array.isArray(parsed)) return [];
|
|
300
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
301
|
+
const pageSize = Math.min(GITHUB_ISSUE_COMMENTS_PAGE_SIZE, Math.max(1, limit));
|
|
288
302
|
const out = [];
|
|
289
|
-
for (
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (out.length >= limit) break;
|
|
303
|
+
for (let page = 1; page <= GITHUB_ISSUE_COMMENTS_MAX_PAGES; page++) {
|
|
304
|
+
const path = `repos/${slug}/issues/comments?since=${encodeURIComponent(opts.since)}&sort=created&direction=desc&per_page=${pageSize}&page=${page}`;
|
|
305
|
+
const result = await gh(["api", path], cwd, { reject: false });
|
|
306
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) break;
|
|
307
|
+
let parsed;
|
|
308
|
+
try {
|
|
309
|
+
parsed = JSON.parse(result.stdout);
|
|
310
|
+
} catch {
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
if (!Array.isArray(parsed) || parsed.length === 0) break;
|
|
314
|
+
for (const item of parsed) {
|
|
315
|
+
const comment = parseGitHubIssueCommentNode(item, restrictToIssues ? allowed : null);
|
|
316
|
+
if (!comment) continue;
|
|
317
|
+
out.push(comment);
|
|
318
|
+
if (out.length >= limit) return out;
|
|
319
|
+
}
|
|
320
|
+
if (parsed.length < pageSize) break;
|
|
308
321
|
}
|
|
309
322
|
return out;
|
|
310
323
|
}
|
|
@@ -909,12 +922,27 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
909
922
|
function assigneeFilterKey(assignee) {
|
|
910
923
|
return (assignee ?? "").trim();
|
|
911
924
|
}
|
|
925
|
+
function applyLinearIssueQueryFilter(filter, query) {
|
|
926
|
+
const term = query?.trim();
|
|
927
|
+
if (!term) return;
|
|
928
|
+
const or = [
|
|
929
|
+
{ title: { containsIgnoreCase: term } },
|
|
930
|
+
{ description: { containsIgnoreCase: term } }
|
|
931
|
+
];
|
|
932
|
+
const identifierMatch = term.match(/^[A-Za-z][\w]*-(\d+)$/);
|
|
933
|
+
const asNumber = identifierMatch ? Number(identifierMatch[1]) : /^\d+$/.test(term) ? Number(term) : NaN;
|
|
934
|
+
if (Number.isFinite(asNumber) && asNumber > 0) {
|
|
935
|
+
or.push({ number: { eq: asNumber } });
|
|
936
|
+
}
|
|
937
|
+
filter.or = or;
|
|
938
|
+
}
|
|
912
939
|
function buildLinearIssueFilter(input) {
|
|
913
940
|
const filter = {
|
|
914
941
|
state: { type: { nin: ["completed", "canceled"] } }
|
|
915
942
|
};
|
|
916
943
|
const since = input?.updatedSince?.trim();
|
|
917
944
|
if (since) filter.updatedAt = { gte: since };
|
|
945
|
+
applyLinearIssueQueryFilter(filter, input?.query);
|
|
918
946
|
const raw = assigneeFilterKey(input?.assignee) || "me";
|
|
919
947
|
const key = raw.toLowerCase();
|
|
920
948
|
if (key === "all" || key === "*") return filter;
|
|
@@ -980,6 +1008,11 @@ async function listLinearIssuesFiltered(opts) {
|
|
|
980
1008
|
async function listLinearCommentsSince(opts) {
|
|
981
1009
|
const first = Math.max(1, Math.min(250, opts.limit ?? 200));
|
|
982
1010
|
const query = opts.query?.trim() ?? "";
|
|
1011
|
+
const restrictToIssues = opts.issueIdentifiers != null;
|
|
1012
|
+
const allowed = new Set(
|
|
1013
|
+
(opts.issueIdentifiers ?? []).map((id) => id.trim()).filter(Boolean)
|
|
1014
|
+
);
|
|
1015
|
+
if (restrictToIssues && allowed.size === 0) return [];
|
|
983
1016
|
const assignee = assigneeFilterKey(opts.assignee) || (query ? "all" : "me");
|
|
984
1017
|
const json = await linearGraphql(
|
|
985
1018
|
COMMENTS_SINCE_QUERY,
|
|
@@ -987,7 +1020,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
987
1020
|
first,
|
|
988
1021
|
filter: {
|
|
989
1022
|
createdAt: { gte: opts.since },
|
|
990
|
-
issue: buildLinearIssueFilter({ assignee })
|
|
1023
|
+
issue: buildLinearIssueFilter({ assignee, query: query || void 0 })
|
|
991
1024
|
}
|
|
992
1025
|
},
|
|
993
1026
|
opts
|
|
@@ -995,6 +1028,7 @@ async function listLinearCommentsSince(opts) {
|
|
|
995
1028
|
const out = [];
|
|
996
1029
|
for (const node of json.comments?.nodes ?? []) {
|
|
997
1030
|
const identifier = String(node.issue?.identifier ?? "").trim();
|
|
1031
|
+
if (restrictToIssues && !allowed.has(identifier)) continue;
|
|
998
1032
|
const body = previewIssueCommentBody(String(node.body ?? ""));
|
|
999
1033
|
if (!identifier && !body) continue;
|
|
1000
1034
|
out.push({
|
|
@@ -1220,20 +1254,19 @@ async function listIssues(repoPath, opts) {
|
|
|
1220
1254
|
const assignee = opts?.assignee?.trim() || void 0;
|
|
1221
1255
|
const since = opts?.updatedSince ? parseIssueSince(opts.updatedSince) : void 0;
|
|
1222
1256
|
if (source === "linear") {
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
since
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
]);
|
|
1257
|
+
const listed = await listLinearIssuesFiltered({
|
|
1258
|
+
assignee,
|
|
1259
|
+
query,
|
|
1260
|
+
limit: opts?.limit,
|
|
1261
|
+
updatedSince: since
|
|
1262
|
+
});
|
|
1263
|
+
const comments2 = since ? await listLinearCommentsSince({
|
|
1264
|
+
since,
|
|
1265
|
+
assignee,
|
|
1266
|
+
query,
|
|
1267
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
1268
|
+
limit: opts?.limit
|
|
1269
|
+
}) : void 0;
|
|
1237
1270
|
return {
|
|
1238
1271
|
source,
|
|
1239
1272
|
preferredSource,
|
|
@@ -2563,20 +2596,19 @@ function registerLinearTools(server) {
|
|
|
2563
2596
|
try {
|
|
2564
2597
|
const page = clampMcpIssueLimit(limit);
|
|
2565
2598
|
const since = updatedSince ? parseIssueSince(updatedSince) : void 0;
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
since
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
]);
|
|
2599
|
+
const listed = await listLinearIssuesFiltered({
|
|
2600
|
+
query,
|
|
2601
|
+
assignee,
|
|
2602
|
+
limit: page + 1,
|
|
2603
|
+
updatedSince: since
|
|
2604
|
+
});
|
|
2605
|
+
const comments = since ? await listLinearCommentsSince({
|
|
2606
|
+
since,
|
|
2607
|
+
assignee,
|
|
2608
|
+
query,
|
|
2609
|
+
issueIdentifiers: query ? listed.issues.map((issue) => issue.identifier) : void 0,
|
|
2610
|
+
limit: page + 1
|
|
2611
|
+
}) : void 0;
|
|
2580
2612
|
return mcpJson(
|
|
2581
2613
|
formatListedIssuesForMcp(
|
|
2582
2614
|
{
|
|
@@ -3663,7 +3695,7 @@ async function startMcpServer() {
|
|
|
3663
3695
|
);
|
|
3664
3696
|
server.tool(
|
|
3665
3697
|
"wait_for_job",
|
|
3666
|
-
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true,
|
|
3698
|
+
"Wait on a detached long job (tests, pack, deploy, connector CLI) started with detached-job.cjs. MCP clients kill tools around 60s, so this returns within 45s. Sideboard opens a type=log pane from this result (artifact_id = job id, content = delta). stillRunning is the source of truth \u2014 if true, call wait_for_job again. If the job is hanging, producing no useful output, or doing the wrong thing, call stop_job instead of looping forever. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
3667
3699
|
{
|
|
3668
3700
|
id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
3669
3701
|
timeoutMs: z9.number().optional()
|
|
@@ -3677,7 +3709,7 @@ async function startMcpServer() {
|
|
|
3677
3709
|
);
|
|
3678
3710
|
server.tool(
|
|
3679
3711
|
"stop_job",
|
|
3680
|
-
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress.
|
|
3712
|
+
"Stop a detached job you started with detached-job.cjs when it is hanging, producing no useful output, buffering forever, or doing the wrong thing (wrong project, infinite watch, huge dump). Do not stop a pack/test/deploy that is clearly making progress. The type=log pane updates to status=failed with the last delta. Then decide the next step.",
|
|
3681
3713
|
{
|
|
3682
3714
|
id: z9.string().describe("Detached job id (same kebab-case id passed to detached-job.cjs start)"),
|
|
3683
3715
|
reason: z9.string().optional().describe("Why you are stopping (hanging, no progress, wrong output, already have the error)")
|