@sideboard-ai/core 0.1.190 → 0.1.192
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/{abletime-J3EGHIAV.js → abletime-53T7G667.js} +3 -1
- package/dist/{abletime-CAFQ4GEG.js → abletime-JFMBCSIQ.js} +3 -1
- package/dist/{agents-5OBZETUV.js → agents-T5VBB6X3.js} +2 -2
- package/dist/{agents-DTPVTDB5.js → agents-TXGQIK2E.js} +2 -2
- package/dist/{chunk-TEJU6UJY.js → chunk-4M2AK3O5.js} +126 -20
- package/dist/{chunk-XXEIF2IL.js → chunk-7NSXGL6L.js} +3 -3
- package/dist/{chunk-IAOXW6RI.js → chunk-FZ3G5TXM.js} +23 -16
- package/dist/{chunk-Z6TW3A5P.js → chunk-NF6ENGHS.js} +24 -17
- package/dist/{chunk-OLDZXO53.js → chunk-TWSCTJCR.js} +2 -2
- package/dist/{chunk-KEVY4GEK.js → chunk-UBQIELFS.js} +123 -20
- package/dist/{chunk-VIODP5PT.js → chunk-VV7UT3E2.js} +3 -3
- package/dist/{chunk-GBWKMWUQ.js → chunk-WHD4JNTN.js} +2 -2
- package/dist/{coordinator-prompt-LWKOL3AZ.js → coordinator-prompt-PNUDN4DW.js} +1 -1
- package/dist/{coordinator-prompt-MSC44OOM.js → coordinator-prompt-S4HHFSLI.js} +1 -1
- package/dist/{global-workspace-6G5JJH2E.js → global-workspace-2BVCJUAL.js} +1 -1
- package/dist/{global-workspace-XATJTV3N.js → global-workspace-JTBBH2K5.js} +1 -1
- package/dist/index.cjs +565 -57
- package/dist/index.d.cts +96 -6
- package/dist/index.d.ts +96 -6
- package/dist/index.js +422 -30
- package/dist/mcp/run-stdio.cjs +543 -57
- package/dist/mcp/run-stdio.js +407 -30
- package/dist/{orchestrator-BHJELEFM.js → orchestrator-4G76BOHH.js} +5 -3
- package/dist/{orchestrator-HWNGCDGW.js → orchestrator-LWEZ74RK.js} +5 -3
- package/dist/{workspaces-LMFCRXTT.js → workspaces-OY7D5RPU.js} +1 -1
- package/dist/{workspaces-FWKPWSXV.js → workspaces-ZYPME3YD.js} +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7772,8 +7772,8 @@ var init_coordinator_prompt = __esm({
|
|
|
7772
7772
|
"- start_board_card \u2014 same as create_thread for a ticket/PR/named branch (attaches issue text when resolvable). Then send_to_thread.",
|
|
7773
7773
|
"- fork_worktree \u2014 fork a worktree chat into a NEW git worktree + chat (transcript attached); optional agent; leave model unset (Auto) unless you have a reason. Not for orchestration chats.",
|
|
7774
7774
|
"- fork_chat \u2014 fork a worktree chat (same worktree tab) OR a Global orchestration chat (new orchestration tab); optional agent; leave model unset (Auto) unless you have a reason. Remote coordinators: use this to continue another orchestration chat on a different agent after session limits.",
|
|
7775
|
-
"- send_to_thread \u2014
|
|
7776
|
-
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply (includes last-turn usage / costUsd when the child agent reported it). wait_for_turn returns within ~45s even if the child is still working (MCP clients kill longer tool calls). stillRunning is the source of truth \u2014 if it is false, the child is not working; do not tell the user it is running or \u201Cwaiting for a gate,\u201D even if status still says running/queued. If stillRunning is true, progress is a live snapshot of tools/thinking \u2014 call wait_for_turn again. status=queued with no lastActivityAt means the child has not started yet (concurrency cap) \u2014 keep waiting; do not force_stop or
|
|
7775
|
+
"- send_to_thread \u2014 steer a prompt (start/continue a chat turn; Settings \u2192 Follow-up, default steer: interrupt and start now). force_stop: true only to replace a wrong in-flight request and clear the inbox \u2014 never to check in, resume after a halt notice, or because wait_for_turn returned stillRunning (that interrupts the child mid-thought)",
|
|
7776
|
+
"- wait_for_turn / get_turn_result \u2014 wait for and read the agent reply (includes last-turn usage / costUsd when the child agent reported it). wait_for_turn returns within ~45s even if the child is still working (MCP clients kill longer tool calls). stillRunning is the source of truth \u2014 if it is false, the child is not working; do not tell the user it is running or \u201Cwaiting for a gate,\u201D even if status still says running/queued. If stillRunning is true, progress is a live snapshot of tools/thinking \u2014 call wait_for_turn again. status=queued with no lastActivityAt means the child has not started yet (concurrency cap) \u2014 keep waiting; do not force_stop or send_to_thread a check-in (that steers / interrupts). Do not send \u201Care you stuck?\u201D or assume a hang while lastActivityAt is recent. If stillRunning stays true across many waits with the same lastActivityAt, the child is stuck \u2014 tell the user; do not invent a gate. On status error, lastError (and text) is the failure \u2014 switch agent, tell the user, or retry; do not treat empty text as success. On status stopped or broken (or incomplete=true), the child was interrupted or died \u2014 resume with send_to_thread or tell the user; never treat stopped as a finished turn. Sideboard also injects a notice into this chat when a child stops unexpectedly.",
|
|
7777
7777
|
"- stop_thread \u2014 force-stop: kill in-flight turn AND clear queued prompts (do not leave stale queue after an interrupt)",
|
|
7778
7778
|
"- archive_thread / restore_thread \u2014 archive (tears down worktree when last tab) or restore",
|
|
7779
7779
|
"Setup / run:",
|
|
@@ -7783,7 +7783,7 @@ var init_coordinator_prompt = __esm({
|
|
|
7783
7783
|
"- get_diff \u2014 compact diff summary",
|
|
7784
7784
|
"- get_pr_checks \u2014 snapshot of a worktree thread's PR checks (null = no PR). Use this to inspect status. If the user gave a goal, the worktree agent watches with `gh pr checks --watch` \u2014 do not poll for the human.",
|
|
7785
7785
|
'- request_review \u2014 open a Review chat tab on a worktree thread (attaches .claude/skills/review/SKILL.md when present, else .context/review.md copied from .sideboard/review.md / stock; sends "Review changes in this workspace."); then wait_for_turn (loop while stillRunning) / get_turn_result on the returned id. The review stays in that chat so they can read it and type next steps \u2014 do not tell the child to comment or update the PR or ticket.',
|
|
7786
|
-
"- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it
|
|
7786
|
+
"- ask_git \u2014 commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge. When the worktree is clean, Sideboard pushes / opens the PR itself. When dirty, it steers the worktree agent \u2014 then wait_for_turn (loop while stillRunning). Prefer this over paraphrasing. If ask_git / get_thread lastError says the GraphQL/PR body is too long, the branch is already pushed \u2014 send_to_thread so the worktree agent runs `gh pr create --body-file` with a short description. Do not invent SSH or auth failures from that error. If the user gave a goal (Greptile 5/5, CI green), send_to_thread that goal so the worktree agent enters the watch-fix-push loop \u2014 do not tell the human to poll.",
|
|
7787
7787
|
'- Merge (`ask_git` action=merge / send_to_thread "Merge PR.") only when the user explicitly asked to merge that PR. Do not merge because the work looks done, CI is green, or a typical flow includes it.',
|
|
7788
7788
|
'- Or send_to_thread with those exact phrases: "Commit and push.", "Commit, push, and open a draft PR.", "Ready for review.", "Merge the remote branch (main) into your branch and resolve conflicts. Then, commit and push your changes.", "Merge PR." (draft PRs: `gh pr create --draft -R <origin-owner/name>` using the workspace `github:` slug \u2014 never upstream). Never run git/gh from this orchestration cwd, and never merge the PR yourself.',
|
|
7789
7789
|
"Process guides:",
|
|
@@ -15956,8 +15956,8 @@ var init_wait_for_turn = __esm({
|
|
|
15956
15956
|
"src/mcp/wait-for-turn.ts"() {
|
|
15957
15957
|
"use strict";
|
|
15958
15958
|
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
15959
|
-
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not
|
|
15960
|
-
MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not
|
|
15959
|
+
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts) or assume a hang while progress is updating.";
|
|
15960
|
+
MCP_WAIT_QUEUED_HINT = "Child is queued waiting for a concurrency slot \u2014 it has not started yet. Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume it failed to start.";
|
|
15961
15961
|
MCP_WAIT_STOPPED_HINT = "Child was stopped before the turn finished. Do not treat this as success. send_to_thread to resume, or tell the user.";
|
|
15962
15962
|
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
15963
15963
|
MCP_WAIT_ERROR_HINT = "Child turn failed. lastError/text is the failure \u2014 switch agent, tell the user, or retry. Do not treat empty text as success.";
|
|
@@ -16888,13 +16888,17 @@ var init_home_board = __esm({
|
|
|
16888
16888
|
});
|
|
16889
16889
|
|
|
16890
16890
|
// src/integrations/abletime-mcp.ts
|
|
16891
|
+
function abletimeToolUsesPm(name) {
|
|
16892
|
+
return ABLETIME_PM_TOOL_NAMES.includes(name);
|
|
16893
|
+
}
|
|
16891
16894
|
function normalizeAbleTimeHost(raw) {
|
|
16892
16895
|
const trimmed = raw?.trim() || DEFAULT_ABLETIME_HOST;
|
|
16893
16896
|
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
16894
16897
|
return withScheme.replace(/\/+$/, "");
|
|
16895
16898
|
}
|
|
16896
|
-
function abletimeMcpUrl(host) {
|
|
16897
|
-
|
|
16899
|
+
function abletimeMcpUrl(host, opts) {
|
|
16900
|
+
const path2 = opts?.pm ? ABLETIME_MCP_PM_PATH : ABLETIME_MCP_PATH;
|
|
16901
|
+
return `${normalizeAbleTimeHost(host)}${path2}`;
|
|
16898
16902
|
}
|
|
16899
16903
|
function rewriteAbleTimeError(message) {
|
|
16900
16904
|
if (/INTEGRATION_AGENT_ACCESS_DISABLED|agent access/i.test(message)) {
|
|
@@ -16972,7 +16976,8 @@ async function abletimeMcpRequest(method, params, opts) {
|
|
|
16972
16976
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
16973
16977
|
}
|
|
16974
16978
|
const url = abletimeMcpUrl(
|
|
16975
|
-
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost())
|
|
16979
|
+
opts?.host ?? (opts?.token ? DEFAULT_ABLETIME_HOST : getAbleTimeHost()),
|
|
16980
|
+
{ pm: opts?.pm }
|
|
16976
16981
|
);
|
|
16977
16982
|
const res = await httpFetch(url, {
|
|
16978
16983
|
method: "POST",
|
|
@@ -17036,7 +17041,8 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17036
17041
|
if (!opts?.token && !isAbleTimeConnected()) {
|
|
17037
17042
|
throw new Error("AbleTime is not connected \u2014 paste a personal access token in Account settings");
|
|
17038
17043
|
}
|
|
17039
|
-
const
|
|
17044
|
+
const requestOpts = { ...opts, pm: opts?.pm ?? abletimeToolUsesPm(name) };
|
|
17045
|
+
const call = () => abletimeMcpRequest("tools/call", { name, arguments: args }, requestOpts).then((result) => {
|
|
17040
17046
|
return unwrapToolResult(result);
|
|
17041
17047
|
});
|
|
17042
17048
|
try {
|
|
@@ -17044,13 +17050,13 @@ async function callAbleTimeTool(name, args = {}, opts) {
|
|
|
17044
17050
|
} catch (err) {
|
|
17045
17051
|
const message = err instanceof Error ? err.message : String(err);
|
|
17046
17052
|
if (/initialize|session|not initialized|-32000/i.test(message)) {
|
|
17047
|
-
await initializeIfNeeded(
|
|
17053
|
+
await initializeIfNeeded(requestOpts);
|
|
17048
17054
|
return await call();
|
|
17049
17055
|
}
|
|
17050
17056
|
throw err instanceof Error ? new Error(rewriteAbleTimeError(err.message)) : err;
|
|
17051
17057
|
}
|
|
17052
17058
|
}
|
|
17053
|
-
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, MCP_PROTOCOL_VERSION;
|
|
17059
|
+
var DEFAULT_ABLETIME_HOST, ABLETIME_MCP_PATH, ABLETIME_MCP_PM_PATH, MCP_PROTOCOL_VERSION, ABLETIME_PM_TOOL_NAMES;
|
|
17054
17060
|
var init_abletime_mcp = __esm({
|
|
17055
17061
|
"src/integrations/abletime-mcp.ts"() {
|
|
17056
17062
|
"use strict";
|
|
@@ -17058,13 +17064,27 @@ var init_abletime_mcp = __esm({
|
|
|
17058
17064
|
init_app_settings();
|
|
17059
17065
|
DEFAULT_ABLETIME_HOST = "https://track.abletime.com";
|
|
17060
17066
|
ABLETIME_MCP_PATH = "/api/public/v2/mcp";
|
|
17067
|
+
ABLETIME_MCP_PM_PATH = "/api/public/v2/mcp/pm";
|
|
17061
17068
|
MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
17069
|
+
ABLETIME_PM_TOOL_NAMES = [
|
|
17070
|
+
"set_task_assignee",
|
|
17071
|
+
"set_task_priority",
|
|
17072
|
+
"set_task_stage",
|
|
17073
|
+
"set_task_dependency",
|
|
17074
|
+
"set_task_blocked",
|
|
17075
|
+
"set_task_locked",
|
|
17076
|
+
"set_task_schedule",
|
|
17077
|
+
"clear_task_schedule",
|
|
17078
|
+
"archive_task",
|
|
17079
|
+
"delete_task"
|
|
17080
|
+
];
|
|
17062
17081
|
}
|
|
17063
17082
|
});
|
|
17064
17083
|
|
|
17065
17084
|
// src/integrations/abletime.ts
|
|
17066
17085
|
var abletime_exports = {};
|
|
17067
17086
|
__export(abletime_exports, {
|
|
17087
|
+
ableTimeDependencyTarget: () => ableTimeDependencyTarget,
|
|
17068
17088
|
commentAbleTimeTask: () => commentAbleTimeTask,
|
|
17069
17089
|
createAbleTimeTask: () => createAbleTimeTask,
|
|
17070
17090
|
ensureAbleTimeTask: () => ensureAbleTimeTask,
|
|
@@ -17321,39 +17341,122 @@ async function commentAbleTimeTask(input, opts) {
|
|
|
17321
17341
|
body: rec ? firstString(rec, ["body", "text", "comment"]) || body : body
|
|
17322
17342
|
};
|
|
17323
17343
|
}
|
|
17344
|
+
function isAbleTimeNoneToken(value) {
|
|
17345
|
+
if (value == null) return true;
|
|
17346
|
+
const s = value.trim();
|
|
17347
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
17348
|
+
}
|
|
17349
|
+
function taskIdArgs(id) {
|
|
17350
|
+
return { id, task_id: id, task: id };
|
|
17351
|
+
}
|
|
17352
|
+
function ableTimeDependencyTarget(thisId, type, relatedId) {
|
|
17353
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
17354
|
+
if (s === "none" || s === "clear" || s === "null") {
|
|
17355
|
+
return { taskId: thisId, dependsOn: null };
|
|
17356
|
+
}
|
|
17357
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson" || s === "waiton") {
|
|
17358
|
+
return { taskId: thisId, dependsOn: relatedId };
|
|
17359
|
+
}
|
|
17360
|
+
if (s === "blocks" || s === "blocking") {
|
|
17361
|
+
return { taskId: relatedId, dependsOn: thisId };
|
|
17362
|
+
}
|
|
17363
|
+
throw new Error(
|
|
17364
|
+
`AbleTime relation type must be blocks or blockedBy (got ${type}). AbleTime models one wait-on dependency.`
|
|
17365
|
+
);
|
|
17366
|
+
}
|
|
17367
|
+
async function setAbleTimeTaskDependency(taskId, dependsOn, opts) {
|
|
17368
|
+
const args = {
|
|
17369
|
+
...taskIdArgs(taskId)
|
|
17370
|
+
};
|
|
17371
|
+
if (dependsOn) {
|
|
17372
|
+
args.depends_on = dependsOn;
|
|
17373
|
+
args.depends_on_id = dependsOn;
|
|
17374
|
+
args.dependency = dependsOn;
|
|
17375
|
+
args.dependency_id = dependsOn;
|
|
17376
|
+
args.blocked_by = dependsOn;
|
|
17377
|
+
args.wait_on = dependsOn;
|
|
17378
|
+
args.related_task_id = dependsOn;
|
|
17379
|
+
} else {
|
|
17380
|
+
args.depends_on = null;
|
|
17381
|
+
args.dependency = null;
|
|
17382
|
+
args.blocked_by = null;
|
|
17383
|
+
args.clear = true;
|
|
17384
|
+
}
|
|
17385
|
+
await callAbleTimeTool("set_task_dependency", args, opts);
|
|
17386
|
+
}
|
|
17324
17387
|
async function updateAbleTimeTask(input, opts) {
|
|
17325
17388
|
const id = input.id.trim();
|
|
17326
17389
|
if (!id) throw new Error("AbleTime task id is required");
|
|
17327
17390
|
const title = input.title?.trim();
|
|
17328
17391
|
const description = input.description;
|
|
17329
17392
|
const state = input.state?.trim();
|
|
17330
|
-
|
|
17331
|
-
|
|
17393
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
17394
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
17395
|
+
const updateArgs = { ...taskIdArgs(id) };
|
|
17396
|
+
if (title) updateArgs.title = title;
|
|
17397
|
+
if (description !== void 0) updateArgs.description = description;
|
|
17398
|
+
if (input.labels !== void 0) {
|
|
17399
|
+
updateArgs.tags = input.labels;
|
|
17400
|
+
updateArgs.labels = input.labels;
|
|
17401
|
+
updateArgs.tag_names = input.labels;
|
|
17402
|
+
}
|
|
17403
|
+
if (input.project !== void 0) {
|
|
17404
|
+
const projectName = (input.project ?? "").trim();
|
|
17405
|
+
if (isAbleTimeNoneToken(projectName)) {
|
|
17406
|
+
throw new Error("AbleTime tasks stay in a project \u2014 pass a project name or id");
|
|
17407
|
+
}
|
|
17408
|
+
const project = await resolveAbleTimeProject(projectName, opts);
|
|
17409
|
+
updateArgs.project = project.id;
|
|
17410
|
+
updateArgs.project_id = project.id;
|
|
17411
|
+
}
|
|
17412
|
+
if (input.parent !== void 0) {
|
|
17413
|
+
if (isAbleTimeNoneToken(input.parent)) {
|
|
17414
|
+
updateArgs.parent = null;
|
|
17415
|
+
updateArgs.parent_id = null;
|
|
17416
|
+
updateArgs.related_task_id = null;
|
|
17417
|
+
} else {
|
|
17418
|
+
const parent = (input.parent ?? "").trim();
|
|
17419
|
+
updateArgs.parent = parent;
|
|
17420
|
+
updateArgs.parent_id = parent;
|
|
17421
|
+
updateArgs.related_task_id = parent;
|
|
17422
|
+
}
|
|
17423
|
+
}
|
|
17424
|
+
const hasUpdateFields = Boolean(title) || description !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0;
|
|
17425
|
+
if (!hasUpdateFields && !state && !addRelations && !removeRelations) {
|
|
17426
|
+
throw new Error(
|
|
17427
|
+
"abletime_update_task needs at least one of title, description, state, labels, project, parent, relations, removeRelations"
|
|
17428
|
+
);
|
|
17332
17429
|
}
|
|
17333
17430
|
if (state) {
|
|
17334
17431
|
await callAbleTimeTool(
|
|
17335
17432
|
"set_task_state",
|
|
17336
|
-
{ id,
|
|
17433
|
+
{ ...taskIdArgs(id), state },
|
|
17337
17434
|
opts
|
|
17338
17435
|
).catch(async () => {
|
|
17339
17436
|
await callAbleTimeTool(
|
|
17340
17437
|
"update_task",
|
|
17341
|
-
{
|
|
17438
|
+
{ ...updateArgs, state },
|
|
17342
17439
|
opts
|
|
17343
17440
|
);
|
|
17344
17441
|
});
|
|
17345
17442
|
}
|
|
17346
|
-
if (
|
|
17347
|
-
await callAbleTimeTool(
|
|
17348
|
-
|
|
17349
|
-
|
|
17350
|
-
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
);
|
|
17443
|
+
if (hasUpdateFields) {
|
|
17444
|
+
await callAbleTimeTool("update_task", updateArgs, opts);
|
|
17445
|
+
}
|
|
17446
|
+
for (const rel of removeRelations ?? []) {
|
|
17447
|
+
const related = rel.issue.trim() || id;
|
|
17448
|
+
const target = ableTimeDependencyTarget(id, rel.type, related);
|
|
17449
|
+
await setAbleTimeTaskDependency(target.taskId, null, opts);
|
|
17450
|
+
}
|
|
17451
|
+
for (const rel of addRelations ?? []) {
|
|
17452
|
+
const related = rel.issue.trim();
|
|
17453
|
+
if (!related) throw new Error("AbleTime relations.issue is required (id or CRM-232)");
|
|
17454
|
+
const target = ableTimeDependencyTarget(id, rel.type, related);
|
|
17455
|
+
if (!target.dependsOn) {
|
|
17456
|
+
await setAbleTimeTaskDependency(target.taskId, null, opts);
|
|
17457
|
+
continue;
|
|
17458
|
+
}
|
|
17459
|
+
await setAbleTimeTaskDependency(target.taskId, target.dependsOn, opts);
|
|
17357
17460
|
}
|
|
17358
17461
|
return getAbleTimeTask(id, opts);
|
|
17359
17462
|
}
|
|
@@ -20031,17 +20134,17 @@ function formatIssueToolsDirective(opts) {
|
|
|
20031
20134
|
];
|
|
20032
20135
|
if (opts.linear) {
|
|
20033
20136
|
lines.push(
|
|
20034
|
-
"- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state, cycle), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
20137
|
+
"- Linear: `linear_search_issues` (assignee=me; pass `updatedSince` for \u201Cany updates since yesterday?\u201D \u2014 new tickets, edits, and comments), `linear_get_issue` (comments; pass `include=full` if crush.truncated), `linear_comment`, `linear_update_issue` (state, cycle, labels, project, parent, relations), `linear_create_issue` (pass `parent` for spin-offs; call `linear_list_teams` first). Scope errors: reconnect Linear in Settings \u2192 Issues."
|
|
20035
20138
|
);
|
|
20036
20139
|
}
|
|
20037
20140
|
if (github) {
|
|
20038
20141
|
lines.push(
|
|
20039
|
-
"- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
20142
|
+
"- GitHub: `github_search_issues` (assignee=me; `updatedSince` for inbox updates + comments), `github_get_issue` (comments; pass `include=full` if crush.truncated), `github_comment`, `github_update_issue` (state open|closed, labels, project, parent, relations), `github_create_issue` (pass `parent` for spin-offs). Uses Account `gh`."
|
|
20040
20143
|
);
|
|
20041
20144
|
}
|
|
20042
20145
|
if (opts.abletime) {
|
|
20043
20146
|
lines.push(
|
|
20044
|
-
"- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
20147
|
+
"- AbleTime: `abletime_list_tasks` / `abletime_search_tasks` (`updatedSince` for inbox updates + comments), `abletime_get_task` (comments; pass `include=full` if crush.truncated), `abletime_comment`, `abletime_update_task` (state, labels, project, parent, relations), `abletime_create_task` (pass `parent` for spin-offs; `abletime_list_projects` if needed)."
|
|
20045
20148
|
);
|
|
20046
20149
|
}
|
|
20047
20150
|
lines.push(
|
|
@@ -20728,6 +20831,7 @@ __export(orchestrator_exports, {
|
|
|
20728
20831
|
Orchestrator: () => Orchestrator,
|
|
20729
20832
|
getOrchestrator: () => getOrchestrator,
|
|
20730
20833
|
isPidAlive: () => isPidAlive,
|
|
20834
|
+
resolveOrchChildFollowUp: () => resolveOrchChildFollowUp,
|
|
20731
20835
|
resolveSendFollowUp: () => resolveSendFollowUp,
|
|
20732
20836
|
startOrchestration: () => startOrchestration,
|
|
20733
20837
|
waitForPidExit: () => waitForPidExit
|
|
@@ -20758,6 +20862,10 @@ function resolveSendFollowUp(thread, requested) {
|
|
|
20758
20862
|
if (requested === "steer" || requested === "queue") return requested;
|
|
20759
20863
|
return isOrchestratorThread(thread) ? followUpBehavior() : "queue";
|
|
20760
20864
|
}
|
|
20865
|
+
function resolveOrchChildFollowUp(requested) {
|
|
20866
|
+
if (requested === "steer" || requested === "queue") return requested;
|
|
20867
|
+
return followUpBehavior();
|
|
20868
|
+
}
|
|
20761
20869
|
function writeLiveStatus(threadId, status, lastError) {
|
|
20762
20870
|
const latest = readThread(threadId);
|
|
20763
20871
|
if (!latest || latest.status === "archived") return latest;
|
|
@@ -22698,7 +22806,7 @@ var init_orchestrator = __esm({
|
|
|
22698
22806
|
/**
|
|
22699
22807
|
* Desktop git buttons + MCP `ask_git`.
|
|
22700
22808
|
* When the worktree is clean, push / open the PR here (HTTPS via `gh` if SSH
|
|
22701
|
-
* is missing). When dirty,
|
|
22809
|
+
* is missing). When dirty, steer the worktree agent to commit first.
|
|
22702
22810
|
*/
|
|
22703
22811
|
async askGit(threadRef, action) {
|
|
22704
22812
|
if (!AGENT_GIT_ACTIONS.includes(action)) {
|
|
@@ -22735,7 +22843,9 @@ var init_orchestrator = __esm({
|
|
|
22735
22843
|
} catch {
|
|
22736
22844
|
}
|
|
22737
22845
|
}
|
|
22738
|
-
return this.send(threadRef, agentGitPrompt(action, { prBase })
|
|
22846
|
+
return this.send(threadRef, agentGitPrompt(action, { prBase }), {
|
|
22847
|
+
followUp: resolveOrchChildFollowUp()
|
|
22848
|
+
});
|
|
22739
22849
|
}
|
|
22740
22850
|
/** Push origin (gh HTTPS fallback) and create/update the PR when requested. */
|
|
22741
22851
|
async pushAndMaybeOpenPr(thread, action) {
|
|
@@ -23131,6 +23241,8 @@ var init_schedule_runner = __esm({
|
|
|
23131
23241
|
var index_exports = {};
|
|
23132
23242
|
__export(index_exports, {
|
|
23133
23243
|
ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
|
|
23244
|
+
ABLETIME_MCP_PM_PATH: () => ABLETIME_MCP_PM_PATH,
|
|
23245
|
+
ABLETIME_PM_TOOL_NAMES: () => ABLETIME_PM_TOOL_NAMES,
|
|
23134
23246
|
AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
|
|
23135
23247
|
AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
23136
23248
|
ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
|
|
@@ -23220,8 +23332,10 @@ __export(index_exports, {
|
|
|
23220
23332
|
WORKTREE_GITHUB_MCP_TOOLS: () => WORKTREE_GITHUB_MCP_TOOLS,
|
|
23221
23333
|
WORKTREE_LINEAR_MCP_TOOLS: () => WORKTREE_LINEAR_MCP_TOOLS,
|
|
23222
23334
|
WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
|
|
23335
|
+
ableTimeDependencyTarget: () => ableTimeDependencyTarget,
|
|
23223
23336
|
abletimeMcpRequest: () => abletimeMcpRequest,
|
|
23224
23337
|
abletimeMcpUrl: () => abletimeMcpUrl,
|
|
23338
|
+
abletimeToolUsesPm: () => abletimeToolUsesPm,
|
|
23225
23339
|
ackSlackInboundSeen: () => ackSlackInboundSeen,
|
|
23226
23340
|
addBoardPin: () => addBoardPin,
|
|
23227
23341
|
addPrStackLayer: () => addPrStackLayer,
|
|
@@ -23494,6 +23608,7 @@ __export(index_exports, {
|
|
|
23494
23608
|
git: () => git,
|
|
23495
23609
|
githubAgentGitEnv: () => githubAgentGitEnv,
|
|
23496
23610
|
githubHttpsInsteadOfEntries: () => githubHttpsInsteadOfEntries,
|
|
23611
|
+
githubRelationEditFlag: () => githubRelationEditFlag,
|
|
23497
23612
|
globalAgentCwd: () => globalAgentCwd,
|
|
23498
23613
|
groupHomeBoardWorktrees: () => groupHomeBoardWorktrees,
|
|
23499
23614
|
handleSlackInbound: () => handleSlackInbound,
|
|
@@ -23543,6 +23658,7 @@ __export(index_exports, {
|
|
|
23543
23658
|
isInternalAgentStatusText: () => isInternalAgentStatusText,
|
|
23544
23659
|
isIssueSourceConnected: () => isIssueSourceConnected,
|
|
23545
23660
|
isLinearConnected: () => isLinearConnected,
|
|
23661
|
+
isLinearNoneToken: () => isLinearNoneToken,
|
|
23546
23662
|
isLinearOAuthCancelled: () => isLinearOAuthCancelled,
|
|
23547
23663
|
isOptionalServiceId: () => isOptionalServiceId,
|
|
23548
23664
|
isOrchestratorCapableAgent: () => isOrchestratorCapableAgent,
|
|
@@ -23580,6 +23696,7 @@ __export(index_exports, {
|
|
|
23580
23696
|
linearGraphql: () => linearGraphql,
|
|
23581
23697
|
linearOAuthAuthorizeUrl: () => linearOAuthAuthorizeUrl,
|
|
23582
23698
|
linearOAuthCredentials: () => linearOAuthCredentials,
|
|
23699
|
+
linearRelationCreateInput: () => linearRelationCreateInput,
|
|
23583
23700
|
linearTicketIdFromThread: () => linearTicketIdFromThread,
|
|
23584
23701
|
listAbleTimeAssignedIssues: () => listAbleTimeAssignedIssues,
|
|
23585
23702
|
listAbleTimeProjects: () => listAbleTimeProjects,
|
|
@@ -23648,6 +23765,7 @@ __export(index_exports, {
|
|
|
23648
23765
|
nonInteractiveGitProcessEnv: () => nonInteractiveGitProcessEnv,
|
|
23649
23766
|
normalizeAbleTimeHost: () => normalizeAbleTimeHost,
|
|
23650
23767
|
normalizeCodeSelection: () => normalizeCodeSelection,
|
|
23768
|
+
normalizeLinearRelationType: () => normalizeLinearRelationType,
|
|
23651
23769
|
normalizeParseResult: () => normalizeParseResult,
|
|
23652
23770
|
normalizeServiceOrigin: () => normalizeServiceOrigin,
|
|
23653
23771
|
normalizeThinkingEffort: () => normalizeThinkingEffort,
|
|
@@ -23741,11 +23859,14 @@ __export(index_exports, {
|
|
|
23741
23859
|
resolveGithubAgentToken: () => resolveGithubAgentToken,
|
|
23742
23860
|
resolveGithubRepoSlug: () => resolveGithubRepoSlug,
|
|
23743
23861
|
resolveLinearCycle: () => resolveLinearCycle,
|
|
23862
|
+
resolveLinearLabel: () => resolveLinearLabel,
|
|
23863
|
+
resolveLinearProject: () => resolveLinearProject,
|
|
23744
23864
|
resolveLinearState: () => resolveLinearState,
|
|
23745
23865
|
resolveLinearTeam: () => resolveLinearTeam,
|
|
23746
23866
|
resolveLoginCommand: () => resolveLoginCommand,
|
|
23747
23867
|
resolveNewOrchestratorOptions: () => resolveNewOrchestratorOptions,
|
|
23748
23868
|
resolveNewThreadOptions: () => resolveNewThreadOptions,
|
|
23869
|
+
resolveOrchChildFollowUp: () => resolveOrchChildFollowUp,
|
|
23749
23870
|
resolveOrchestratorDefaults: () => resolveOrchestratorDefaults,
|
|
23750
23871
|
resolvePlanMarkdown: () => resolvePlanMarkdown,
|
|
23751
23872
|
resolvePrSelector: () => resolvePrSelector,
|
|
@@ -24358,12 +24479,14 @@ var ISSUE_FIELDS = `
|
|
|
24358
24479
|
children(first: 25) { nodes { ${ISSUE_REF_FIELDS} } }
|
|
24359
24480
|
relations(first: 25) {
|
|
24360
24481
|
nodes {
|
|
24482
|
+
id
|
|
24361
24483
|
type
|
|
24362
24484
|
relatedIssue { ${ISSUE_REF_FIELDS} }
|
|
24363
24485
|
}
|
|
24364
24486
|
}
|
|
24365
24487
|
inverseRelations(first: 25) {
|
|
24366
24488
|
nodes {
|
|
24489
|
+
id
|
|
24367
24490
|
type
|
|
24368
24491
|
issue { ${ISSUE_REF_FIELDS} }
|
|
24369
24492
|
}
|
|
@@ -24483,6 +24606,45 @@ mutation SideboardCommentCreate($input: CommentCreateInput!) {
|
|
|
24483
24606
|
}
|
|
24484
24607
|
}
|
|
24485
24608
|
`;
|
|
24609
|
+
var LABELS_QUERY = `
|
|
24610
|
+
query SideboardLabels($first: Int!) {
|
|
24611
|
+
issueLabels(first: $first) {
|
|
24612
|
+
nodes { id name }
|
|
24613
|
+
}
|
|
24614
|
+
}
|
|
24615
|
+
`;
|
|
24616
|
+
var TEAM_LABELS_QUERY = `
|
|
24617
|
+
query SideboardTeamLabels($id: String!, $first: Int!, $after: String) {
|
|
24618
|
+
team(id: $id) {
|
|
24619
|
+
labels(first: $first, after: $after) {
|
|
24620
|
+
nodes { id name }
|
|
24621
|
+
pageInfo { hasNextPage endCursor }
|
|
24622
|
+
}
|
|
24623
|
+
}
|
|
24624
|
+
}
|
|
24625
|
+
`;
|
|
24626
|
+
var PROJECTS_QUERY = `
|
|
24627
|
+
query SideboardProjects($first: Int!, $filter: ProjectFilter) {
|
|
24628
|
+
projects(first: $first, filter: $filter) {
|
|
24629
|
+
nodes { id name slugId }
|
|
24630
|
+
}
|
|
24631
|
+
}
|
|
24632
|
+
`;
|
|
24633
|
+
var ISSUE_RELATION_CREATE = `
|
|
24634
|
+
mutation SideboardIssueRelationCreate($input: IssueRelationCreateInput!) {
|
|
24635
|
+
issueRelationCreate(input: $input) {
|
|
24636
|
+
success
|
|
24637
|
+
issueRelation { id type }
|
|
24638
|
+
}
|
|
24639
|
+
}
|
|
24640
|
+
`;
|
|
24641
|
+
var ISSUE_RELATION_DELETE = `
|
|
24642
|
+
mutation SideboardIssueRelationDelete($id: String!) {
|
|
24643
|
+
issueRelationDelete(id: $id) {
|
|
24644
|
+
success
|
|
24645
|
+
}
|
|
24646
|
+
}
|
|
24647
|
+
`;
|
|
24486
24648
|
function rewriteLinearError(message) {
|
|
24487
24649
|
if (/scope|permission|not (authorized|allowed)|unauthorized|insufficient/i.test(message)) {
|
|
24488
24650
|
return `${message} \u2014 Disconnect and Connect Linear in Account settings to grant write access.`;
|
|
@@ -24602,12 +24764,18 @@ function mapRelations(node) {
|
|
|
24602
24764
|
for (const rel of node.relations?.nodes ?? []) {
|
|
24603
24765
|
const issue = mapIssueRef(rel.relatedIssue);
|
|
24604
24766
|
if (!issue) continue;
|
|
24605
|
-
|
|
24767
|
+
const id = rel.id?.trim();
|
|
24768
|
+
out.push({ type: String(rel.type ?? "related"), issue, ...id ? { id } : {} });
|
|
24606
24769
|
}
|
|
24607
24770
|
for (const rel of node.inverseRelations?.nodes ?? []) {
|
|
24608
24771
|
const issue = mapIssueRef(rel.issue);
|
|
24609
24772
|
if (!issue) continue;
|
|
24610
|
-
|
|
24773
|
+
const id = rel.id?.trim();
|
|
24774
|
+
out.push({
|
|
24775
|
+
type: flipLinearRelationType(String(rel.type ?? "related")),
|
|
24776
|
+
issue,
|
|
24777
|
+
...id ? { id } : {}
|
|
24778
|
+
});
|
|
24611
24779
|
}
|
|
24612
24780
|
return out;
|
|
24613
24781
|
}
|
|
@@ -24768,6 +24936,171 @@ function resolveLinearCycle(team, cycle) {
|
|
|
24768
24936
|
}
|
|
24769
24937
|
return found.id;
|
|
24770
24938
|
}
|
|
24939
|
+
var LINEAR_NONE_RE = /^(none|clear|unschedule|null)$/i;
|
|
24940
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24941
|
+
function isLinearNoneToken(value) {
|
|
24942
|
+
if (value == null) return true;
|
|
24943
|
+
const s = value.trim();
|
|
24944
|
+
return !s || LINEAR_NONE_RE.test(s);
|
|
24945
|
+
}
|
|
24946
|
+
function resolveLinearLabel(labels, name) {
|
|
24947
|
+
const s = name.trim();
|
|
24948
|
+
if (!s) throw new Error("Linear label is empty");
|
|
24949
|
+
const lower = s.toLowerCase();
|
|
24950
|
+
const found = labels.find((x) => x.id === s) || labels.find((x) => x.name.toLowerCase() === lower);
|
|
24951
|
+
if (!found) {
|
|
24952
|
+
const available = labels.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
24953
|
+
throw new Error(`Linear label not found: ${name}. Available: ${available}`);
|
|
24954
|
+
}
|
|
24955
|
+
return found;
|
|
24956
|
+
}
|
|
24957
|
+
function resolveLinearProject(projects, project) {
|
|
24958
|
+
if (isLinearNoneToken(project)) return null;
|
|
24959
|
+
const s = (project ?? "").trim();
|
|
24960
|
+
const lower = s.toLowerCase();
|
|
24961
|
+
const found = projects.find((x) => x.id === s) || projects.find((x) => x.name.toLowerCase() === lower) || projects.find((x) => x.slugId?.toLowerCase() === lower);
|
|
24962
|
+
if (!found) {
|
|
24963
|
+
const available = projects.map((x) => x.name).filter(Boolean).slice(0, 20).join(", ") || "(none)";
|
|
24964
|
+
throw new Error(`Linear project not found: ${project}. Available: ${available}`);
|
|
24965
|
+
}
|
|
24966
|
+
return found.id;
|
|
24967
|
+
}
|
|
24968
|
+
function normalizeLinearRelationType(type) {
|
|
24969
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
24970
|
+
if (s === "blocks" || s === "block") return "blocks";
|
|
24971
|
+
if (s === "blockedby" || s === "blocked") return "blockedBy";
|
|
24972
|
+
if (s === "related" || s === "relates" || s === "relatesto") return "related";
|
|
24973
|
+
if (s === "duplicate" || s === "dup") return "duplicate";
|
|
24974
|
+
if (s === "duplicateof" || s === "original") return "duplicateOf";
|
|
24975
|
+
if (s === "similar") return "similar";
|
|
24976
|
+
throw new Error(
|
|
24977
|
+
`Linear relation type must be blocks, blockedBy, related, duplicate, or duplicateOf (got ${type})`
|
|
24978
|
+
);
|
|
24979
|
+
}
|
|
24980
|
+
function linearRelationCreateInput(thisIssueId, type, relatedIssueId) {
|
|
24981
|
+
const view = normalizeLinearRelationType(type);
|
|
24982
|
+
if (view === "blockedBy") {
|
|
24983
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "blocks" };
|
|
24984
|
+
}
|
|
24985
|
+
if (view === "duplicateOf") {
|
|
24986
|
+
return { issueId: relatedIssueId, relatedIssueId: thisIssueId, type: "duplicate" };
|
|
24987
|
+
}
|
|
24988
|
+
return { issueId: thisIssueId, relatedIssueId, type: view };
|
|
24989
|
+
}
|
|
24990
|
+
function relationMatches(rel, type, issueRef) {
|
|
24991
|
+
if (rel.type.toLowerCase() !== type.toLowerCase()) return false;
|
|
24992
|
+
const ref = issueRef.trim().toLowerCase();
|
|
24993
|
+
return rel.issue.id.toLowerCase() === ref || rel.issue.identifier.toLowerCase() === ref;
|
|
24994
|
+
}
|
|
24995
|
+
function formatRelationList(relations) {
|
|
24996
|
+
if (!relations.length) return "(none)";
|
|
24997
|
+
return relations.map((r) => `${r.type} ${r.issue.identifier || r.issue.id}`).join(", ");
|
|
24998
|
+
}
|
|
24999
|
+
function mapLabelNodes(nodes) {
|
|
25000
|
+
return (nodes ?? []).flatMap((node) => {
|
|
25001
|
+
const id = String(node.id ?? "").trim();
|
|
25002
|
+
const name = String(node.name ?? "").trim();
|
|
25003
|
+
if (!id) return [];
|
|
25004
|
+
return [{ id, name }];
|
|
25005
|
+
});
|
|
25006
|
+
}
|
|
25007
|
+
async function listLinearWorkspaceLabels(opts) {
|
|
25008
|
+
const json = await linearGraphql(LABELS_QUERY, { first: 250 }, opts);
|
|
25009
|
+
return mapLabelNodes(json.issueLabels?.nodes);
|
|
25010
|
+
}
|
|
25011
|
+
async function listLinearLabels(opts) {
|
|
25012
|
+
const teamId = opts?.teamId?.trim();
|
|
25013
|
+
if (teamId) {
|
|
25014
|
+
const out = [];
|
|
25015
|
+
let after;
|
|
25016
|
+
for (let page = 0; page < 4; page++) {
|
|
25017
|
+
const json = await linearGraphql(
|
|
25018
|
+
TEAM_LABELS_QUERY,
|
|
25019
|
+
after ? { id: teamId, first: 250, after } : { id: teamId, first: 250 },
|
|
25020
|
+
opts
|
|
25021
|
+
);
|
|
25022
|
+
const conn = json.team?.labels;
|
|
25023
|
+
out.push(...mapLabelNodes(conn?.nodes));
|
|
25024
|
+
if (!conn?.pageInfo?.hasNextPage || !conn.pageInfo.endCursor) break;
|
|
25025
|
+
after = conn.pageInfo.endCursor;
|
|
25026
|
+
}
|
|
25027
|
+
if (out.length) return out;
|
|
25028
|
+
}
|
|
25029
|
+
return listLinearWorkspaceLabels(opts);
|
|
25030
|
+
}
|
|
25031
|
+
async function listLinearProjects(filter, opts) {
|
|
25032
|
+
const json = await linearGraphql(PROJECTS_QUERY, { first: 50, filter: filter ?? {} }, opts);
|
|
25033
|
+
return (json.projects?.nodes ?? []).flatMap((node) => {
|
|
25034
|
+
const id = String(node.id ?? "").trim();
|
|
25035
|
+
if (!id) return [];
|
|
25036
|
+
return [
|
|
25037
|
+
{
|
|
25038
|
+
id,
|
|
25039
|
+
name: String(node.name ?? ""),
|
|
25040
|
+
...node.slugId ? { slugId: String(node.slugId) } : {}
|
|
25041
|
+
}
|
|
25042
|
+
];
|
|
25043
|
+
});
|
|
25044
|
+
}
|
|
25045
|
+
async function resolveLinearProjectId(project, opts) {
|
|
25046
|
+
if (isLinearNoneToken(project)) return null;
|
|
25047
|
+
const s = (project ?? "").trim();
|
|
25048
|
+
const filter = UUID_RE.test(s) ? { id: { eq: s } } : { name: { eqIgnoreCase: s } };
|
|
25049
|
+
const matched = await listLinearProjects(filter, opts);
|
|
25050
|
+
try {
|
|
25051
|
+
return resolveLinearProject(matched, s);
|
|
25052
|
+
} catch {
|
|
25053
|
+
if (UUID_RE.test(s)) return s;
|
|
25054
|
+
const listed = await listLinearProjects(void 0, opts);
|
|
25055
|
+
return resolveLinearProject(listed, s);
|
|
25056
|
+
}
|
|
25057
|
+
}
|
|
25058
|
+
async function resolveLinearLabelIds(names, opts) {
|
|
25059
|
+
if (names.length === 0) return [];
|
|
25060
|
+
const labels = await listLinearLabels(opts);
|
|
25061
|
+
return names.map((name) => {
|
|
25062
|
+
const s = name.trim();
|
|
25063
|
+
try {
|
|
25064
|
+
return resolveLinearLabel(labels, s).id;
|
|
25065
|
+
} catch (error) {
|
|
25066
|
+
if (UUID_RE.test(s)) return s;
|
|
25067
|
+
throw error;
|
|
25068
|
+
}
|
|
25069
|
+
});
|
|
25070
|
+
}
|
|
25071
|
+
async function applyLinearIssueRelations(issue, add, remove, opts) {
|
|
25072
|
+
const current = [...issue.relations];
|
|
25073
|
+
for (const rel of remove ?? []) {
|
|
25074
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
25075
|
+
const issueRef = rel.issue.trim();
|
|
25076
|
+
if (!issueRef) throw new Error("Linear removeRelations.issue is required (uuid or ENG-123)");
|
|
25077
|
+
const match = current.find((r) => relationMatches(r, type, issueRef));
|
|
25078
|
+
if (!match?.id) {
|
|
25079
|
+
throw new Error(
|
|
25080
|
+
`Linear relation not found: ${type} ${issueRef}. Existing: ${formatRelationList(current)}`
|
|
25081
|
+
);
|
|
25082
|
+
}
|
|
25083
|
+
const json = await linearGraphql(ISSUE_RELATION_DELETE, { id: match.id }, opts);
|
|
25084
|
+
if (!json.issueRelationDelete?.success) {
|
|
25085
|
+
throw new Error(`Linear issueRelationDelete failed (${type} ${issueRef})`);
|
|
25086
|
+
}
|
|
25087
|
+
const idx = current.indexOf(match);
|
|
25088
|
+
if (idx >= 0) current.splice(idx, 1);
|
|
25089
|
+
}
|
|
25090
|
+
for (const rel of add ?? []) {
|
|
25091
|
+
const type = normalizeLinearRelationType(rel.type);
|
|
25092
|
+
const issueRef = rel.issue.trim();
|
|
25093
|
+
if (!issueRef) throw new Error("Linear relations.issue is required (uuid or ENG-123)");
|
|
25094
|
+
if (current.some((r) => relationMatches(r, type, issueRef))) continue;
|
|
25095
|
+
const related = await getLinearIssue(issueRef, opts);
|
|
25096
|
+
const input = linearRelationCreateInput(issue.id, type, related.id);
|
|
25097
|
+
const json = await linearGraphql(ISSUE_RELATION_CREATE, { input }, opts);
|
|
25098
|
+
if (!json.issueRelationCreate?.success) {
|
|
25099
|
+
throw new Error(`Linear issueRelationCreate failed (${type} ${related.identifier})`);
|
|
25100
|
+
}
|
|
25101
|
+
current.push({ type, issue: related, id: void 0 });
|
|
25102
|
+
}
|
|
25103
|
+
}
|
|
24771
25104
|
function normalizePriority(priority) {
|
|
24772
25105
|
if (priority == null) return void 0;
|
|
24773
25106
|
if (!Number.isInteger(priority) || priority < 0 || priority > 4) {
|
|
@@ -24775,7 +25108,6 @@ function normalizePriority(priority) {
|
|
|
24775
25108
|
}
|
|
24776
25109
|
return priority;
|
|
24777
25110
|
}
|
|
24778
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24779
25111
|
function assigneeFilterKey(assignee) {
|
|
24780
25112
|
return (assignee ?? "").trim();
|
|
24781
25113
|
}
|
|
@@ -24988,8 +25320,10 @@ async function updateLinearIssue(input, opts) {
|
|
|
24988
25320
|
if (input.description !== void 0) {
|
|
24989
25321
|
mutationInput.description = input.description;
|
|
24990
25322
|
}
|
|
24991
|
-
const
|
|
24992
|
-
const
|
|
25323
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
25324
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
25325
|
+
const needsIssue = Boolean(input.state?.trim()) || input.cycle !== void 0 || input.labels !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
25326
|
+
const existing = needsIssue ? await getLinearIssue(issueId, opts) : void 0;
|
|
24993
25327
|
if (input.state?.trim()) {
|
|
24994
25328
|
const { teams } = await listLinearTeams(opts);
|
|
24995
25329
|
const teamRef = existing?.team?.key || existing?.team?.id;
|
|
@@ -25022,14 +25356,47 @@ async function updateLinearIssue(input, opts) {
|
|
|
25022
25356
|
if (input.priority !== void 0) {
|
|
25023
25357
|
mutationInput.priority = normalizePriority(input.priority);
|
|
25024
25358
|
}
|
|
25025
|
-
if (
|
|
25026
|
-
|
|
25359
|
+
if (input.labels !== void 0) {
|
|
25360
|
+
mutationInput.labelIds = await resolveLinearLabelIds(input.labels, {
|
|
25361
|
+
...opts,
|
|
25362
|
+
teamId: existing?.team?.id
|
|
25363
|
+
});
|
|
25364
|
+
}
|
|
25365
|
+
if (input.project !== void 0) {
|
|
25366
|
+
mutationInput.projectId = await resolveLinearProjectId(input.project, opts);
|
|
25367
|
+
}
|
|
25368
|
+
if (input.parent !== void 0) {
|
|
25369
|
+
if (isLinearNoneToken(input.parent)) {
|
|
25370
|
+
mutationInput.parentId = null;
|
|
25371
|
+
} else {
|
|
25372
|
+
const parent = await getLinearIssue((input.parent ?? "").trim(), opts);
|
|
25373
|
+
mutationInput.parentId = parent.id;
|
|
25374
|
+
}
|
|
25375
|
+
}
|
|
25376
|
+
const hasIssueFields = Object.keys(mutationInput).length > 0;
|
|
25377
|
+
const hasRelationWrites = Boolean(addRelations || removeRelations);
|
|
25378
|
+
if (!hasIssueFields && !hasRelationWrites) {
|
|
25379
|
+
throw new Error(
|
|
25380
|
+
"linear_update_issue needs at least one of title, description, state, assignee, priority, cycle, labels, project, parent, relations, removeRelations"
|
|
25381
|
+
);
|
|
25382
|
+
}
|
|
25383
|
+
let updated;
|
|
25384
|
+
if (hasIssueFields) {
|
|
25385
|
+
const json = await linearGraphql(ISSUE_UPDATE, { id: issueId, input: mutationInput }, opts);
|
|
25386
|
+
if (!json.issueUpdate?.success || !json.issueUpdate.issue) {
|
|
25387
|
+
throw new Error("Linear issueUpdate failed");
|
|
25388
|
+
}
|
|
25389
|
+
updated = mapIssue(json.issueUpdate.issue);
|
|
25027
25390
|
}
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25391
|
+
if (hasRelationWrites) {
|
|
25392
|
+
const base = updated ?? existing;
|
|
25393
|
+
if (!base) {
|
|
25394
|
+
throw new Error(`Linear issue not found: ${issueId}`);
|
|
25395
|
+
}
|
|
25396
|
+
await applyLinearIssueRelations(base, addRelations, removeRelations, opts);
|
|
25397
|
+
return getLinearIssue(base.id || issueId, opts);
|
|
25031
25398
|
}
|
|
25032
|
-
return
|
|
25399
|
+
return updated;
|
|
25033
25400
|
}
|
|
25034
25401
|
async function commentLinearIssue(input, opts) {
|
|
25035
25402
|
const issueId = input.id.trim();
|
|
@@ -25251,13 +25618,78 @@ async function commentGitHubIssue(input, opts) {
|
|
|
25251
25618
|
const url = stdout.trim().split(/\s+/).find((part) => /^https?:\/\//i.test(part));
|
|
25252
25619
|
return { body, url };
|
|
25253
25620
|
}
|
|
25621
|
+
function isGitHubNoneToken(value) {
|
|
25622
|
+
if (value == null) return true;
|
|
25623
|
+
const s = value.trim();
|
|
25624
|
+
return !s || /^(none|clear|unschedule|null)$/i.test(s);
|
|
25625
|
+
}
|
|
25626
|
+
function githubRelationEditFlag(type, remove = false) {
|
|
25627
|
+
const s = type.trim().toLowerCase().replace(/[\s_-]+/g, "");
|
|
25628
|
+
if (s === "blockedby" || s === "blocked" || s === "dependson") {
|
|
25629
|
+
return remove ? "--remove-blocked-by" : "--add-blocked-by";
|
|
25630
|
+
}
|
|
25631
|
+
if (s === "blocks" || s === "blocking") {
|
|
25632
|
+
return remove ? "--remove-blocking" : "--add-blocking";
|
|
25633
|
+
}
|
|
25634
|
+
throw new Error(
|
|
25635
|
+
`GitHub relation type must be blocks or blockedBy (got ${type}). Parent is a separate parent= field.`
|
|
25636
|
+
);
|
|
25637
|
+
}
|
|
25638
|
+
function labelsEqual(a, b) {
|
|
25639
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
25640
|
+
}
|
|
25641
|
+
function parseProjectTitles(raw) {
|
|
25642
|
+
const out = [];
|
|
25643
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25644
|
+
const push = (name) => {
|
|
25645
|
+
const title = name.trim();
|
|
25646
|
+
const key = title.toLowerCase();
|
|
25647
|
+
if (!title || seen.has(key)) return;
|
|
25648
|
+
seen.add(key);
|
|
25649
|
+
out.push(title);
|
|
25650
|
+
};
|
|
25651
|
+
const fromItems = raw.projectItems ?? raw.projects ?? raw.projectItemsV2;
|
|
25652
|
+
if (Array.isArray(fromItems)) {
|
|
25653
|
+
for (const item of fromItems) {
|
|
25654
|
+
if (typeof item === "string") {
|
|
25655
|
+
push(item);
|
|
25656
|
+
continue;
|
|
25657
|
+
}
|
|
25658
|
+
if (!item || typeof item !== "object") continue;
|
|
25659
|
+
const rec = item;
|
|
25660
|
+
const nested = rec.project && typeof rec.project === "object" ? rec.project : rec;
|
|
25661
|
+
const title = String(nested.title ?? nested.name ?? rec.title ?? rec.name ?? "").trim();
|
|
25662
|
+
if (title) push(title);
|
|
25663
|
+
}
|
|
25664
|
+
}
|
|
25665
|
+
return out;
|
|
25666
|
+
}
|
|
25667
|
+
async function listGitHubIssueProjects(number, cwd, repoArgs) {
|
|
25668
|
+
const result = await gh(
|
|
25669
|
+
["issue", "view", String(number), ...repoArgs, "--json", "projectItems"],
|
|
25670
|
+
cwd,
|
|
25671
|
+
{ reject: false }
|
|
25672
|
+
);
|
|
25673
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) return [];
|
|
25674
|
+
try {
|
|
25675
|
+
const parsed = JSON.parse(result.stdout);
|
|
25676
|
+
return parseProjectTitles(parsed);
|
|
25677
|
+
} catch {
|
|
25678
|
+
return [];
|
|
25679
|
+
}
|
|
25680
|
+
}
|
|
25254
25681
|
async function updateGitHubIssue(input, opts) {
|
|
25255
25682
|
const number = parseGitHubIssueNumber(input.id);
|
|
25256
25683
|
const title = input.title?.trim();
|
|
25257
25684
|
const body = input.body;
|
|
25258
25685
|
const state = input.state?.trim().toLowerCase();
|
|
25259
|
-
|
|
25260
|
-
|
|
25686
|
+
const addRelations = input.relations?.length ? input.relations : void 0;
|
|
25687
|
+
const removeRelations = input.removeRelations?.length ? input.removeRelations : void 0;
|
|
25688
|
+
const hasEditFields = Boolean(title) || body !== void 0 || input.labels !== void 0 || input.project !== void 0 || input.parent !== void 0 || Boolean(addRelations) || Boolean(removeRelations);
|
|
25689
|
+
if (!hasEditFields && !state) {
|
|
25690
|
+
throw new Error(
|
|
25691
|
+
"github_update_issue needs at least one of title, body, state, labels, project, parent, relations, removeRelations"
|
|
25692
|
+
);
|
|
25261
25693
|
}
|
|
25262
25694
|
const { cwd, repoArgs } = await resolveGitHubIssueRepo(opts?.repoPath);
|
|
25263
25695
|
if (state === "closed" || state === "close") {
|
|
@@ -25273,10 +25705,46 @@ async function updateGitHubIssue(input, opts) {
|
|
|
25273
25705
|
} else if (state) {
|
|
25274
25706
|
throw new Error(`GitHub issue state must be open or closed (got ${input.state})`);
|
|
25275
25707
|
}
|
|
25276
|
-
|
|
25277
|
-
|
|
25278
|
-
|
|
25279
|
-
|
|
25708
|
+
const args = ["issue", "edit", String(number), ...repoArgs];
|
|
25709
|
+
if (title) args.push("--title", title);
|
|
25710
|
+
if (body !== void 0) args.push("--body", body);
|
|
25711
|
+
if (input.labels !== void 0) {
|
|
25712
|
+
const existing = await getGitHubIssue(String(number), opts);
|
|
25713
|
+
const desired = input.labels.map((name) => name.trim()).filter(Boolean);
|
|
25714
|
+
const add = desired.filter((name) => !existing.labels.some((cur) => labelsEqual(cur, name)));
|
|
25715
|
+
const remove = existing.labels.filter((cur) => !desired.some((name) => labelsEqual(cur, name)));
|
|
25716
|
+
if (add.length) args.push("--add-label", add.join(","));
|
|
25717
|
+
if (remove.length) args.push("--remove-label", remove.join(","));
|
|
25718
|
+
}
|
|
25719
|
+
if (input.project !== void 0) {
|
|
25720
|
+
const current = await listGitHubIssueProjects(number, cwd, repoArgs);
|
|
25721
|
+
if (isGitHubNoneToken(input.project)) {
|
|
25722
|
+
for (const name of current) args.push("--remove-project", name);
|
|
25723
|
+
} else {
|
|
25724
|
+
const wanted = (input.project ?? "").trim();
|
|
25725
|
+
if (!current.some((name) => labelsEqual(name, wanted))) {
|
|
25726
|
+
args.push("--add-project", wanted);
|
|
25727
|
+
}
|
|
25728
|
+
for (const name of current) {
|
|
25729
|
+
if (!labelsEqual(name, wanted)) args.push("--remove-project", name);
|
|
25730
|
+
}
|
|
25731
|
+
}
|
|
25732
|
+
}
|
|
25733
|
+
if (input.parent !== void 0) {
|
|
25734
|
+
if (isGitHubNoneToken(input.parent)) {
|
|
25735
|
+
args.push("--remove-parent");
|
|
25736
|
+
} else {
|
|
25737
|
+
args.push("--parent", String(parseGitHubIssueNumber(input.parent ?? "")));
|
|
25738
|
+
}
|
|
25739
|
+
}
|
|
25740
|
+
for (const rel of removeRelations ?? []) {
|
|
25741
|
+
args.push(githubRelationEditFlag(rel.type, true), String(parseGitHubIssueNumber(rel.issue)));
|
|
25742
|
+
}
|
|
25743
|
+
for (const rel of addRelations ?? []) {
|
|
25744
|
+
args.push(githubRelationEditFlag(rel.type, false), String(parseGitHubIssueNumber(rel.issue)));
|
|
25745
|
+
}
|
|
25746
|
+
const editChanged = args.length > 3 + repoArgs.length;
|
|
25747
|
+
if (editChanged) {
|
|
25280
25748
|
requireGhOk(await gh(args, cwd, { reject: false }), `GitHub edit #${number}`);
|
|
25281
25749
|
}
|
|
25282
25750
|
return getGitHubIssue(String(number), opts);
|
|
@@ -26958,14 +27426,23 @@ function registerAbleTimeTools(server) {
|
|
|
26958
27426
|
}
|
|
26959
27427
|
}
|
|
26960
27428
|
);
|
|
27429
|
+
const relationInputSchema = import_zod4.z.object({
|
|
27430
|
+
type: import_zod4.z.string().describe("blockedBy (this waits on issue) or blocks (issue waits on this)."),
|
|
27431
|
+
issue: import_zod4.z.string().describe("Related task id or reference (CRM-232).")
|
|
27432
|
+
});
|
|
26961
27433
|
server.tool(
|
|
26962
27434
|
"abletime_update_task",
|
|
26963
|
-
|
|
27435
|
+
'Update an AbleTime task (id or CRM-232). Pass title, description, state, labels, project, parent, and/or relations. labels replaces tags. project is name or id (tasks stay in a project). parent is CRM-232 or "none". relations sets the wait-on dependency (blockedBy/blocks); removeRelations clears it. When reviewing a PR or ticket, wait until they type a next step before changing the task \u2014 do not ask_user after the review. The author is notified.',
|
|
26964
27436
|
{
|
|
26965
27437
|
id: import_zod4.z.string(),
|
|
26966
27438
|
title: import_zod4.z.string().optional(),
|
|
26967
27439
|
description: import_zod4.z.string().optional(),
|
|
26968
|
-
state: import_zod4.z.string().optional()
|
|
27440
|
+
state: import_zod4.z.string().optional(),
|
|
27441
|
+
labels: import_zod4.z.array(import_zod4.z.string()).optional().describe("Replace tags/labels. Empty array clears when the host accepts it."),
|
|
27442
|
+
project: import_zod4.z.string().nullable().optional().describe("Project name or id. AbleTime tasks stay in a project."),
|
|
27443
|
+
parent: import_zod4.z.string().nullable().optional().describe('Parent task id or reference (CRM-232), or "none" to unset.'),
|
|
27444
|
+
relations: import_zod4.z.array(relationInputSchema).optional().describe("Set a wait-on dependency: blockedBy or blocks."),
|
|
27445
|
+
removeRelations: import_zod4.z.array(relationInputSchema).optional().describe("Clear a wait-on dependency.")
|
|
26969
27446
|
},
|
|
26970
27447
|
async (args) => {
|
|
26971
27448
|
try {
|
|
@@ -27100,14 +27577,23 @@ function registerGithubIssueTools(server) {
|
|
|
27100
27577
|
}
|
|
27101
27578
|
}
|
|
27102
27579
|
);
|
|
27580
|
+
const relationInputSchema = import_zod5.z.object({
|
|
27581
|
+
type: import_zod5.z.string().describe("blocks or blockedBy (this issue's view)."),
|
|
27582
|
+
issue: import_zod5.z.string().describe("Related issue #123 or URL.")
|
|
27583
|
+
});
|
|
27103
27584
|
server.tool(
|
|
27104
27585
|
"github_update_issue",
|
|
27105
|
-
|
|
27586
|
+
'Update a GitHub issue (#123). Pass title, body, state (open|closed), labels, project, parent, and/or relations. labels replaces all names (pass [] to clear). project is a Project title, or "none" to remove. parent is #123 or "none". relations adds blockedBy/blocks; removeRelations drops them (`gh` 2.94+). When reviewing a PR or ticket, wait until they type a next step before changing the issue \u2014 do not ask_user after the review. The author is notified.',
|
|
27106
27587
|
{
|
|
27107
27588
|
id: import_zod5.z.string(),
|
|
27108
27589
|
title: import_zod5.z.string().optional(),
|
|
27109
27590
|
body: import_zod5.z.string().optional(),
|
|
27110
27591
|
state: import_zod5.z.string().optional().describe("open or closed"),
|
|
27592
|
+
labels: import_zod5.z.array(import_zod5.z.string()).optional().describe("Replace all labels. Empty array clears. Call github_get_issue first to keep existing."),
|
|
27593
|
+
project: import_zod5.z.string().nullable().optional().describe('GitHub Project title, or "none" to remove current projects.'),
|
|
27594
|
+
parent: import_zod5.z.string().nullable().optional().describe('Parent issue #123 or URL, or "none" to unset.'),
|
|
27595
|
+
relations: import_zod5.z.array(relationInputSchema).optional().describe("Add blockedBy or blocks relations (issue #123 or URL)."),
|
|
27596
|
+
removeRelations: import_zod5.z.array(relationInputSchema).optional().describe("Remove blockedBy or blocks relations."),
|
|
27111
27597
|
repoPath: repoPathSchema
|
|
27112
27598
|
},
|
|
27113
27599
|
async (args) => {
|
|
@@ -27237,9 +27723,13 @@ function registerLinearTools(server) {
|
|
|
27237
27723
|
}
|
|
27238
27724
|
}
|
|
27239
27725
|
);
|
|
27726
|
+
const relationInputSchema = import_zod6.z.object({
|
|
27727
|
+
type: import_zod6.z.string().describe("blocks, blockedBy, related, duplicate, or duplicateOf (this issue's view)."),
|
|
27728
|
+
issue: import_zod6.z.string().describe("Related issue uuid or identifier (ENG-123).")
|
|
27729
|
+
});
|
|
27240
27730
|
server.tool(
|
|
27241
27731
|
"linear_update_issue",
|
|
27242
|
-
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, and/or
|
|
27732
|
+
'Update a Linear issue (uuid or ENG-123). Pass title, description, state, assignee, priority, cycle, labels, project, parent, and/or relations. Cycle/project/parent: name or id, or "none" to clear. labels replaces all names (pass [] to clear; get the issue first to keep existing). relations adds; removeRelations removes by type + issue. When reviewing a PR or ticket, wait until they type a next step before changing the ticket \u2014 do not ask_user after the review. The author is notified.',
|
|
27243
27733
|
{
|
|
27244
27734
|
id: import_zod6.z.string(),
|
|
27245
27735
|
title: import_zod6.z.string().optional(),
|
|
@@ -27247,7 +27737,12 @@ function registerLinearTools(server) {
|
|
|
27247
27737
|
state: import_zod6.z.string().optional(),
|
|
27248
27738
|
assignee: import_zod6.z.string().nullable().optional(),
|
|
27249
27739
|
priority: prioritySchema,
|
|
27250
|
-
cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.')
|
|
27740
|
+
cycle: import_zod6.z.string().nullable().optional().describe('Cycle name, number, "current"/"active", or "none" to unschedule.'),
|
|
27741
|
+
labels: import_zod6.z.array(import_zod6.z.string()).optional().describe("Replace all labels (names or ids). Empty array clears. Call linear_get_issue first to keep existing."),
|
|
27742
|
+
project: import_zod6.z.string().nullable().optional().describe('Project name, id, slug, or "none" to unassign.'),
|
|
27743
|
+
parent: import_zod6.z.string().nullable().optional().describe('Parent issue uuid or identifier (ENG-123), or "none" to unset.'),
|
|
27744
|
+
relations: import_zod6.z.array(relationInputSchema).optional().describe("Add relations from this issue's view (blocks, blockedBy, related, duplicate, duplicateOf)."),
|
|
27745
|
+
removeRelations: import_zod6.z.array(relationInputSchema).optional().describe("Remove relations by type + related issue (uuid or ENG-123).")
|
|
27251
27746
|
},
|
|
27252
27747
|
async (args) => {
|
|
27253
27748
|
try {
|
|
@@ -28505,7 +29000,7 @@ async function startMcpServer() {
|
|
|
28505
29000
|
);
|
|
28506
29001
|
server.tool(
|
|
28507
29002
|
"send_to_thread",
|
|
28508
|
-
'
|
|
29003
|
+
'Steer a prompt on a worktree thread chat (Settings \u2192 Follow-up, default steer: interrupt and start now, skip the inbox). Use after create_thread to start or continue a conversation. For commit/push/PR, prefer ask_git (canonical desktop-button phrases). Send "Merge PR." / ask_git merge only when the user explicitly asked to merge. force_stop=true kills the in-flight turn and clears the queue before this prompt \u2014 only when the current request is wrong and must be replaced. Do not send_to_thread to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that interrupts the child mid-thought. Call wait_for_turn again instead.',
|
|
28509
29004
|
{
|
|
28510
29005
|
ref: import_zod9.z.string(),
|
|
28511
29006
|
prompt: import_zod9.z.string(),
|
|
@@ -28518,7 +29013,9 @@ async function startMcpServer() {
|
|
|
28518
29013
|
orch.stop(ref, { clearQueue: true, notifyParent: false });
|
|
28519
29014
|
}
|
|
28520
29015
|
}
|
|
28521
|
-
const thread = await orch.send(ref, prompt
|
|
29016
|
+
const thread = await orch.send(ref, prompt, {
|
|
29017
|
+
followUp: resolveOrchChildFollowUp()
|
|
29018
|
+
});
|
|
28522
29019
|
return mcpJson({
|
|
28523
29020
|
id: thread.id,
|
|
28524
29021
|
status: thread.status,
|
|
@@ -28529,7 +29026,7 @@ async function startMcpServer() {
|
|
|
28529
29026
|
);
|
|
28530
29027
|
server.tool(
|
|
28531
29028
|
"wait_for_turn",
|
|
28532
|
-
"Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not
|
|
29029
|
+
"Wait until the thread finishes its current/queued turn, or return early with a live progress snapshot. MCP clients often kill tools around 60s, so this returns within 45s even while the child is still working. stillRunning is the source of truth \u2014 if false, the child is not working (do not say it is waiting for a gate). If stillRunning is true, progress is tools/thinking (or \u201Cqueued, waiting for a concurrency slot\u201D if it has not started). Call wait_for_turn again. Do not send_to_thread a check-in (that steers / interrupts), force_stop, or assume a hang. On status error, lastError/text is the failure. On status stopped or broken, the child did not finish \u2014 resume with send_to_thread or tell the user; do not treat that as success. When finished, usage is the last agent turn\u2019s tokens + costUsd (when the provider reported cost).",
|
|
28533
29030
|
{
|
|
28534
29031
|
ref: import_zod9.z.string(),
|
|
28535
29032
|
timeoutMs: import_zod9.z.number().optional()
|
|
@@ -28686,7 +29183,7 @@ async function startMcpServer() {
|
|
|
28686
29183
|
);
|
|
28687
29184
|
server.tool(
|
|
28688
29185
|
"ask_git",
|
|
28689
|
-
"Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` even when origin is SSH / Settings \u2192 Git is SSH). When dirty,
|
|
29186
|
+
"Commit & push, open a draft PR, mark ready for review, resolve conflicts, or merge \u2014 same actions as the desktop git buttons. When the worktree is clean, Sideboard pushes / opens the PR itself (HTTPS via `gh` even when origin is SSH / Settings \u2192 Git is SSH). When dirty, steers the worktree agent to commit (Settings \u2192 Follow-up, default interrupt now); then wait_for_turn (loop while stillRunning). Do not start a checks loop on a plain push \u2014 only if the user gave a goal (Greptile 5/5, CI green). Pass a worktree thread ref (not the orchestrator). action=ready-for-review and action=merge only when the user explicitly asked (or clicked the desktop button). Do not run git or gh from the orchestration cwd. If this tool errors that the GraphQL/PR body is too long, the branch is already pushed \u2014 have the worktree agent retry `gh pr create --body-file` with a short description (GitHub limit 65,536 characters). Do not invent SSH/auth failures from that error.",
|
|
28690
29187
|
{
|
|
28691
29188
|
ref: import_zod9.z.string().describe("Worktree thread id/ref"),
|
|
28692
29189
|
action: import_zod9.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
@@ -31354,6 +31851,8 @@ init_outbound_watch();
|
|
|
31354
31851
|
// Annotate the CommonJS export names for ESM import in node:
|
|
31355
31852
|
0 && (module.exports = {
|
|
31356
31853
|
ABLETIME_MCP_PATH,
|
|
31854
|
+
ABLETIME_MCP_PM_PATH,
|
|
31855
|
+
ABLETIME_PM_TOOL_NAMES,
|
|
31357
31856
|
AGENT_GIT_ACTIONS,
|
|
31358
31857
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
31359
31858
|
ATTACHMENTS_DIR,
|
|
@@ -31443,8 +31942,10 @@ init_outbound_watch();
|
|
|
31443
31942
|
WORKTREE_GITHUB_MCP_TOOLS,
|
|
31444
31943
|
WORKTREE_LINEAR_MCP_TOOLS,
|
|
31445
31944
|
WORKTREE_MCP_TOOLS,
|
|
31945
|
+
ableTimeDependencyTarget,
|
|
31446
31946
|
abletimeMcpRequest,
|
|
31447
31947
|
abletimeMcpUrl,
|
|
31948
|
+
abletimeToolUsesPm,
|
|
31448
31949
|
ackSlackInboundSeen,
|
|
31449
31950
|
addBoardPin,
|
|
31450
31951
|
addPrStackLayer,
|
|
@@ -31717,6 +32218,7 @@ init_outbound_watch();
|
|
|
31717
32218
|
git,
|
|
31718
32219
|
githubAgentGitEnv,
|
|
31719
32220
|
githubHttpsInsteadOfEntries,
|
|
32221
|
+
githubRelationEditFlag,
|
|
31720
32222
|
globalAgentCwd,
|
|
31721
32223
|
groupHomeBoardWorktrees,
|
|
31722
32224
|
handleSlackInbound,
|
|
@@ -31766,6 +32268,7 @@ init_outbound_watch();
|
|
|
31766
32268
|
isInternalAgentStatusText,
|
|
31767
32269
|
isIssueSourceConnected,
|
|
31768
32270
|
isLinearConnected,
|
|
32271
|
+
isLinearNoneToken,
|
|
31769
32272
|
isLinearOAuthCancelled,
|
|
31770
32273
|
isOptionalServiceId,
|
|
31771
32274
|
isOrchestratorCapableAgent,
|
|
@@ -31803,6 +32306,7 @@ init_outbound_watch();
|
|
|
31803
32306
|
linearGraphql,
|
|
31804
32307
|
linearOAuthAuthorizeUrl,
|
|
31805
32308
|
linearOAuthCredentials,
|
|
32309
|
+
linearRelationCreateInput,
|
|
31806
32310
|
linearTicketIdFromThread,
|
|
31807
32311
|
listAbleTimeAssignedIssues,
|
|
31808
32312
|
listAbleTimeProjects,
|
|
@@ -31871,6 +32375,7 @@ init_outbound_watch();
|
|
|
31871
32375
|
nonInteractiveGitProcessEnv,
|
|
31872
32376
|
normalizeAbleTimeHost,
|
|
31873
32377
|
normalizeCodeSelection,
|
|
32378
|
+
normalizeLinearRelationType,
|
|
31874
32379
|
normalizeParseResult,
|
|
31875
32380
|
normalizeServiceOrigin,
|
|
31876
32381
|
normalizeThinkingEffort,
|
|
@@ -31964,11 +32469,14 @@ init_outbound_watch();
|
|
|
31964
32469
|
resolveGithubAgentToken,
|
|
31965
32470
|
resolveGithubRepoSlug,
|
|
31966
32471
|
resolveLinearCycle,
|
|
32472
|
+
resolveLinearLabel,
|
|
32473
|
+
resolveLinearProject,
|
|
31967
32474
|
resolveLinearState,
|
|
31968
32475
|
resolveLinearTeam,
|
|
31969
32476
|
resolveLoginCommand,
|
|
31970
32477
|
resolveNewOrchestratorOptions,
|
|
31971
32478
|
resolveNewThreadOptions,
|
|
32479
|
+
resolveOrchChildFollowUp,
|
|
31972
32480
|
resolveOrchestratorDefaults,
|
|
31973
32481
|
resolvePlanMarkdown,
|
|
31974
32482
|
resolvePrSelector,
|