@sideboard-ai/core 0.1.157 → 0.1.159
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-BEIDJG7W.js → abletime-HOQBDQC4.js} +2 -2
- package/dist/{abletime-GWZE6OYM.js → abletime-LK7GV556.js} +2 -2
- package/dist/{agents-LCDXN2B2.js → agents-5HY76BCR.js} +4 -4
- package/dist/{agents-C55LAEUR.js → agents-67ZBH5OE.js} +5 -5
- package/dist/{app-settings-VRQI3FIE.js → app-settings-BJ5GFAE5.js} +13 -17
- package/dist/{app-settings-H3INSR67.js → app-settings-YWJ4MVYM.js} +13 -17
- package/dist/{chunk-NVXVEGGM.js → chunk-5U4NZLBH.js} +4 -0
- package/dist/{chunk-DVJBO3M7.js → chunk-AGLPYXDT.js} +1 -1
- package/dist/{chunk-KQ4JOGUH.js → chunk-DWUOKF3W.js} +11 -5
- package/dist/{chunk-D7EGJ3EI.js → chunk-EQIJ5MHR.js} +119 -110
- package/dist/{chunk-3BWVOUQG.js → chunk-FLG4A5FJ.js} +119 -110
- package/dist/{chunk-2SDMENAL.js → chunk-JV4VNFMS.js} +1 -1
- package/dist/{chunk-LQMFCS54.js → chunk-KEDHHXKW.js} +3 -13
- package/dist/{chunk-CJQLPPEJ.js → chunk-MEZXWELT.js} +4 -14
- package/dist/{chunk-CVR4RJ6G.js → chunk-O7RWWPMR.js} +12 -13
- package/dist/{chunk-GWEEGE6L.js → chunk-OYOXLY6B.js} +404 -115
- package/dist/{chunk-4I4VKAPZ.js → chunk-SIMFKJIB.js} +13 -14
- package/dist/{chunk-SUCJAWV4.js → chunk-TGU3LSDG.js} +402 -113
- package/dist/{chunk-N27GVFZY.js → chunk-UBMCEZRE.js} +1 -1
- package/dist/{chunk-TIJ6QVX5.js → chunk-V4R6L2ZL.js} +15 -6
- package/dist/{coordinator-prompt-AM47SATF.js → coordinator-prompt-7NKY7QRK.js} +4 -4
- package/dist/{coordinator-prompt-26IJU2AV.js → coordinator-prompt-CNI6AK65.js} +3 -3
- package/dist/{global-workspace-QV7CC7SK.js → global-workspace-FKAJZHO5.js} +4 -4
- package/dist/{global-workspace-5BIW26YR.js → global-workspace-IJYCUO4M.js} +3 -3
- package/dist/index.cjs +1037 -648
- package/dist/index.d.cts +71 -57
- package/dist/index.d.ts +71 -57
- package/dist/index.js +339 -250
- package/dist/mcp/run-stdio.cjs +967 -575
- package/dist/mcp/run-stdio.js +283 -187
- package/dist/{orchestrator-BUH3LJLL.js → orchestrator-KFLS5BTP.js} +5 -5
- package/dist/{orchestrator-KQO4MXBI.js → orchestrator-NSVP7DJH.js} +7 -7
- package/dist/{plan-file-OZEBFSGQ.js → plan-file-I24VJVP2.js} +2 -2
- package/dist/{workspaces-NYSM2EPX.js → workspaces-D2M3AM2M.js} +3 -3
- package/dist/{workspaces-Q2VCHIAS.js → workspaces-OFSDJ7PZ.js} +4 -4
- package/dist/{worktree-PEWDAX5O.js → worktree-BQE5KLRY.js} +3 -3
- package/dist/{worktree-T4SWFSTT.js → worktree-EA6AD6D6.js} +2 -2
- package/package.json +1 -1
package/dist/mcp/run-stdio.cjs
CHANGED
|
@@ -1086,8 +1086,8 @@ ${input.permalink}` : "";
|
|
|
1086
1086
|
|
|
1087
1087
|
${body}${link}`;
|
|
1088
1088
|
}
|
|
1089
|
-
function isSlackExternalReplyPrompt(
|
|
1090
|
-
return
|
|
1089
|
+
function isSlackExternalReplyPrompt(text7) {
|
|
1090
|
+
return text7.startsWith("Slack reply from ") && text7.includes("not a command");
|
|
1091
1091
|
}
|
|
1092
1092
|
function pendingSlackExternalReplies(messages) {
|
|
1093
1093
|
let i = messages.length - 1;
|
|
@@ -1128,9 +1128,9 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
1128
1128
|
function listSlackOutboundWatches() {
|
|
1129
1129
|
return pruneWatches(readStore3());
|
|
1130
1130
|
}
|
|
1131
|
-
function formatOwnerSlackFyi(userName,
|
|
1131
|
+
function formatOwnerSlackFyi(userName, text7) {
|
|
1132
1132
|
const who = userName.trim() || "Someone";
|
|
1133
|
-
const body =
|
|
1133
|
+
const body = text7.trim() || "(no text)";
|
|
1134
1134
|
return `${who} replied in Slack:
|
|
1135
1135
|
${body}`;
|
|
1136
1136
|
}
|
|
@@ -1145,7 +1145,7 @@ async function relayExternalReply(opts) {
|
|
|
1145
1145
|
const thread = readThread(threadId);
|
|
1146
1146
|
if (!thread || thread.status === "archived") return "ignored";
|
|
1147
1147
|
try {
|
|
1148
|
-
const
|
|
1148
|
+
const text7 = formatSlackExternalReplyPrompt({
|
|
1149
1149
|
userName: opts.reply.userName,
|
|
1150
1150
|
kind: opts.watch.kind,
|
|
1151
1151
|
toLabel: opts.watch.toLabel,
|
|
@@ -1154,7 +1154,7 @@ async function relayExternalReply(opts) {
|
|
|
1154
1154
|
});
|
|
1155
1155
|
appendMessage(threadId, {
|
|
1156
1156
|
role: "agent",
|
|
1157
|
-
text:
|
|
1157
|
+
text: text7,
|
|
1158
1158
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
1159
1159
|
});
|
|
1160
1160
|
} catch {
|
|
@@ -1468,20 +1468,20 @@ function looksLikeMinifiedJsDump(line) {
|
|
|
1468
1468
|
function looksLikeNestedElectronCrash(line) {
|
|
1469
1469
|
return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
|
|
1470
1470
|
}
|
|
1471
|
-
function looksLikeNativeEventLoopCrash(
|
|
1472
|
-
return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(
|
|
1471
|
+
function looksLikeNativeEventLoopCrash(text7) {
|
|
1472
|
+
return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text7);
|
|
1473
1473
|
}
|
|
1474
|
-
function looksLikeV8Oom(
|
|
1474
|
+
function looksLikeV8Oom(text7) {
|
|
1475
1475
|
return /javascript heap|reached heap limit|OOMErrorHandler|FatalProcessOutOfMemory|Allocation failed - JavaScript heap/i.test(
|
|
1476
|
-
|
|
1476
|
+
text7
|
|
1477
1477
|
);
|
|
1478
1478
|
}
|
|
1479
|
-
function looksLikeHomebrewLibuvCrash(
|
|
1480
|
-
if (!looksLikeNativeEventLoopCrash(
|
|
1481
|
-
return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(
|
|
1479
|
+
function looksLikeHomebrewLibuvCrash(text7) {
|
|
1480
|
+
if (!looksLikeNativeEventLoopCrash(text7)) return false;
|
|
1481
|
+
return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text7);
|
|
1482
1482
|
}
|
|
1483
|
-
function clipStderr(
|
|
1484
|
-
const trimmed =
|
|
1483
|
+
function clipStderr(text7, maxChars) {
|
|
1484
|
+
const trimmed = text7.trim();
|
|
1485
1485
|
if (trimmed.length <= maxChars) return trimmed;
|
|
1486
1486
|
return trimmed.slice(0, maxChars);
|
|
1487
1487
|
}
|
|
@@ -1515,16 +1515,16 @@ function summarizeTurnStderr(tail, maxChars = 500) {
|
|
|
1515
1515
|
const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
|
|
1516
1516
|
return clipStderr(joined, maxChars);
|
|
1517
1517
|
}
|
|
1518
|
-
function looksLikeInvalidAgentSession(
|
|
1519
|
-
const lower =
|
|
1518
|
+
function looksLikeInvalidAgentSession(text7) {
|
|
1519
|
+
const lower = text7.trim().toLowerCase();
|
|
1520
1520
|
if (!lower) return false;
|
|
1521
1521
|
return /session not found/.test(lower) || /no conversation found/.test(lower) || /conversation .+ not found/.test(lower) || /thread .+ not found/.test(lower) || /unknown session/.test(lower) || /invalid session/.test(lower) || /session .+ (missing|expired|deleted|gone)/.test(lower) || /cannot resume/.test(lower) || /failed to (load|resume|open) session/.test(lower) || /corrupt local agent checkpoint/.test(lower) || /missing root blob/.test(lower) || /\bagent\b.{0,120}\bnot found\b/.test(lower) || /\brun\b.{0,80}\bnot found for agent\b/.test(lower);
|
|
1522
1522
|
}
|
|
1523
|
-
function looksLikeRetryableRunnerCrash(
|
|
1524
|
-
if (looksLikeAgentFailureMessage(
|
|
1525
|
-
if (looksLikeInvalidAgentSession(
|
|
1526
|
-
if (looksLikeV8Oom(
|
|
1527
|
-
const lower =
|
|
1523
|
+
function looksLikeRetryableRunnerCrash(text7) {
|
|
1524
|
+
if (looksLikeAgentFailureMessage(text7)) return false;
|
|
1525
|
+
if (looksLikeInvalidAgentSession(text7)) return false;
|
|
1526
|
+
if (looksLikeV8Oom(text7)) return false;
|
|
1527
|
+
const lower = text7.trim().toLowerCase();
|
|
1528
1528
|
if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
|
|
1529
1529
|
if (!lower) return true;
|
|
1530
1530
|
return /uv_run|spineventloopinternal|libuv|homebrew node \+ shared libuv|cursor runner crashed in node|hascustomhostobject|electroninitializeicuandstartnode|nested chromium|truncated crash dump|connection stalled|network request failed|cursor startup failed:.+\(retryable\)|sig(?:segv|abrt|ill)|segmentation fault|illegal instruction|fatal error/.test(
|
|
@@ -1536,12 +1536,12 @@ function shouldRetryFailedAgentTurn(detail, opts) {
|
|
|
1536
1536
|
if (looksLikeV8Oom(detail) && opts.hasSession) return true;
|
|
1537
1537
|
return looksLikeRetryableRunnerCrash(detail);
|
|
1538
1538
|
}
|
|
1539
|
-
function looksLikeAgentFailureMessage(
|
|
1540
|
-
const lower =
|
|
1539
|
+
function looksLikeAgentFailureMessage(text7) {
|
|
1540
|
+
const lower = text7.trim().toLowerCase();
|
|
1541
1541
|
if (!lower) return false;
|
|
1542
1542
|
return /you've hit your|hit your (session|weekly|opus) limit|usage limit/.test(lower) || /credit balance is too low|out of credits|insufficient.?quota|quota.?exceeded/.test(lower) || /invalid user api key|invalid api key|not logged in|not authenticated|unauthorized/.test(
|
|
1543
1543
|
lower
|
|
1544
|
-
) || /\b429\b|too many requests|rate.?limit/.test(lower) || /prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower) || /\[resource_exhausted\]|resource_exhausted/.test(lower) || /findFilesWithRipgrep/.test(
|
|
1544
|
+
) || /\b429\b|too many requests|rate.?limit/.test(lower) || /prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower) || /\[resource_exhausted\]|resource_exhausted/.test(lower) || /findFilesWithRipgrep/.test(text7);
|
|
1545
1545
|
}
|
|
1546
1546
|
function fallbackTurnFailDetail(assistantText) {
|
|
1547
1547
|
const t = assistantText.trim();
|
|
@@ -1822,8 +1822,8 @@ var init_github_agent_auth = __esm({
|
|
|
1822
1822
|
});
|
|
1823
1823
|
|
|
1824
1824
|
// src/git/stale-lock.ts
|
|
1825
|
-
function isIndexLockError(
|
|
1826
|
-
return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(
|
|
1825
|
+
function isIndexLockError(text7) {
|
|
1826
|
+
return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
|
|
1827
1827
|
}
|
|
1828
1828
|
function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
|
|
1829
1829
|
const lockPath = (0, import_node_path13.join)(gitDir, "index.lock");
|
|
@@ -2802,12 +2802,12 @@ var init_worktree_labels = __esm({
|
|
|
2802
2802
|
});
|
|
2803
2803
|
|
|
2804
2804
|
// src/git/gh-errors.ts
|
|
2805
|
-
function isGhRateLimitError(
|
|
2806
|
-
return /API rate limit (already )?exceeded/i.test(
|
|
2805
|
+
function isGhRateLimitError(text7) {
|
|
2806
|
+
return /API rate limit (already )?exceeded/i.test(text7) || /rate limit exceeded/i.test(text7);
|
|
2807
2807
|
}
|
|
2808
|
-
function isGhPrBodyTooLongError(
|
|
2808
|
+
function isGhPrBodyTooLongError(text7) {
|
|
2809
2809
|
return /body is too long|body.{0,20}too long|maximum is 65536|exceeds the maximum allowed size|request (entity |is )?too large|payload too large|413 Request Entity Too Large/i.test(
|
|
2810
|
-
|
|
2810
|
+
text7
|
|
2811
2811
|
);
|
|
2812
2812
|
}
|
|
2813
2813
|
function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
|
|
@@ -2827,21 +2827,21 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
|
|
|
2827
2827
|
const hours = Math.ceil(mins / 60);
|
|
2828
2828
|
return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
|
|
2829
2829
|
}
|
|
2830
|
-
function omitGhBodyArg(
|
|
2831
|
-
return
|
|
2830
|
+
function omitGhBodyArg(text7) {
|
|
2831
|
+
return text7.replace(
|
|
2832
2832
|
/--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
|
|
2833
2833
|
"--body <omitted>"
|
|
2834
2834
|
);
|
|
2835
2835
|
}
|
|
2836
|
-
function capGhErrorText(
|
|
2837
|
-
const t =
|
|
2836
|
+
function capGhErrorText(text7, max = 400) {
|
|
2837
|
+
const t = text7.trim();
|
|
2838
2838
|
if (t.length <= max) return t;
|
|
2839
2839
|
return `${t.slice(0, max - 1).trimEnd()}\u2026`;
|
|
2840
2840
|
}
|
|
2841
|
-
function extractGhErrorDetail(
|
|
2842
|
-
const trimmed = omitGhBodyArg(
|
|
2841
|
+
function extractGhErrorDetail(text7) {
|
|
2842
|
+
const trimmed = omitGhBodyArg(text7.trim());
|
|
2843
2843
|
if (!trimmed) return "";
|
|
2844
|
-
if (isGhPrBodyTooLongError(
|
|
2844
|
+
if (isGhPrBodyTooLongError(text7) || isGhPrBodyTooLongError(trimmed)) {
|
|
2845
2845
|
return "GraphQL: body is too long (GitHub PR body / request size limit).";
|
|
2846
2846
|
}
|
|
2847
2847
|
const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
|
|
@@ -2881,9 +2881,9 @@ function formatGhLandError(raw, opts) {
|
|
|
2881
2881
|
}
|
|
2882
2882
|
return detail || "Failed to create or update pull request";
|
|
2883
2883
|
}
|
|
2884
|
-
function isPrNotMergeableError(
|
|
2884
|
+
function isPrNotMergeableError(text7) {
|
|
2885
2885
|
return /not mergeable|cannot be cleanly created|cannot merge cleanly|Merge conflict|\bCONFLICTING\b|must be (updated|rebased)|branch is out of date|needs? to be (updated|rebased)|Resolve conflicts or update the branch/i.test(
|
|
2886
|
-
|
|
2886
|
+
text7
|
|
2887
2887
|
);
|
|
2888
2888
|
}
|
|
2889
2889
|
function formatMergePrError(raw) {
|
|
@@ -2971,27 +2971,27 @@ var init_worktree_exclude = __esm({
|
|
|
2971
2971
|
});
|
|
2972
2972
|
|
|
2973
2973
|
// src/store/account-profile.ts
|
|
2974
|
-
function
|
|
2975
|
-
const known =
|
|
2974
|
+
function legacyRoleLabel(role) {
|
|
2975
|
+
const known = LEGACY_ROLE_LABELS[role];
|
|
2976
2976
|
if (known) return known;
|
|
2977
2977
|
return role.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
2978
2978
|
}
|
|
2979
|
-
function
|
|
2979
|
+
function normalizeLegacyRole(value) {
|
|
2980
2980
|
const key = (value ?? "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2981
2981
|
if (key === "both") return void 0;
|
|
2982
2982
|
if (key.length < 2 || key.length > 40) return void 0;
|
|
2983
2983
|
if (!/^[a-z][a-z0-9-]*$/.test(key)) return void 0;
|
|
2984
2984
|
return key;
|
|
2985
2985
|
}
|
|
2986
|
-
function
|
|
2986
|
+
function legacyRoleLabels(roles, legacyRole) {
|
|
2987
2987
|
const raw = Array.isArray(roles) ? roles : [];
|
|
2988
2988
|
const out = [];
|
|
2989
2989
|
const seen = /* @__PURE__ */ new Set();
|
|
2990
2990
|
const push = (value) => {
|
|
2991
|
-
const role =
|
|
2992
|
-
if (!role || seen.has(role) || out.length >=
|
|
2991
|
+
const role = normalizeLegacyRole(typeof value === "string" ? value : "");
|
|
2992
|
+
if (!role || seen.has(role) || out.length >= 16) return;
|
|
2993
2993
|
seen.add(role);
|
|
2994
|
-
out.push(role);
|
|
2994
|
+
out.push(legacyRoleLabel(role));
|
|
2995
2995
|
};
|
|
2996
2996
|
for (const item of raw) push(item);
|
|
2997
2997
|
if (out.length === 0) push(legacyRole);
|
|
@@ -3001,117 +3001,83 @@ function normalizeProfileNotes(value) {
|
|
|
3001
3001
|
if (typeof value !== "string") return "";
|
|
3002
3002
|
return value.replace(/\r\n/g, "\n").trim().slice(0, PROFILE_NOTES_MAX);
|
|
3003
3003
|
}
|
|
3004
|
-
function
|
|
3005
|
-
const
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
if (seen.has(key)) continue;
|
|
3012
|
-
seen.add(key);
|
|
3013
|
-
out.push(hint);
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
return out;
|
|
3017
|
-
}
|
|
3018
|
-
function teamHintsForCustomRole(role) {
|
|
3019
|
-
const slug = role.trim().toLowerCase();
|
|
3020
|
-
if (!slug) return [];
|
|
3021
|
-
if (slug.endsWith("-team") || slug.endsWith("-squad")) return [slug];
|
|
3022
|
-
return [`${slug}-team`, slug];
|
|
3004
|
+
function foldLegacyRolesIntoNotes(roles, legacyRole, notes) {
|
|
3005
|
+
const text7 = normalizeProfileNotes(notes);
|
|
3006
|
+
const labels = legacyRoleLabels(roles, legacyRole);
|
|
3007
|
+
if (!labels.length) return text7;
|
|
3008
|
+
if (text7.toLowerCase().startsWith("roles:")) return text7;
|
|
3009
|
+
const prefix = `Roles: ${labels.join(", ")}`;
|
|
3010
|
+
return text7 ? `${prefix}. ${text7}` : prefix;
|
|
3023
3011
|
}
|
|
3024
3012
|
function resolveAccountProfile(input) {
|
|
3025
3013
|
return resolveViewerProfile(input);
|
|
3026
3014
|
}
|
|
3027
3015
|
function resolveViewerProfile(account, project) {
|
|
3028
|
-
const
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
const projectNotes =
|
|
3034
|
-
|
|
3016
|
+
const accountNotes = foldLegacyRolesIntoNotes(
|
|
3017
|
+
account?.roles,
|
|
3018
|
+
account?.role,
|
|
3019
|
+
account?.notes
|
|
3020
|
+
);
|
|
3021
|
+
const projectNotes = foldLegacyRolesIntoNotes(
|
|
3022
|
+
project?.roles,
|
|
3023
|
+
project?.role,
|
|
3024
|
+
project?.notes
|
|
3025
|
+
);
|
|
3035
3026
|
return {
|
|
3036
|
-
|
|
3037
|
-
roleLabels: roles.map((role) => accountRoleLabel(role)),
|
|
3038
|
-
reviewTeamHints: reviewTeamHintsForRoles(roles),
|
|
3039
|
-
notes,
|
|
3027
|
+
notes: [accountNotes, projectNotes].filter(Boolean).join("\n"),
|
|
3040
3028
|
accountNotes,
|
|
3041
|
-
projectNotes
|
|
3042
|
-
rolesFromProject
|
|
3029
|
+
projectNotes
|
|
3043
3030
|
};
|
|
3044
3031
|
}
|
|
3045
|
-
function preferTeamsForRole(viewerTeams, hints) {
|
|
3046
|
-
const teams = viewerTeams.map((t) => t.trim()).filter(Boolean);
|
|
3047
|
-
const want = hints.map((t) => t.trim()).filter(Boolean);
|
|
3048
|
-
if (!want.length) return teams;
|
|
3049
|
-
if (!teams.length) return want;
|
|
3050
|
-
const hintKeys = want.map((t) => t.toLowerCase());
|
|
3051
|
-
const matched = teams.filter((team) => {
|
|
3052
|
-
const key = team.toLowerCase();
|
|
3053
|
-
return hintKeys.some((hint) => key === hint || key.includes(hint) || hint.includes(key));
|
|
3054
|
-
});
|
|
3055
|
-
return matched.length ? matched : teams;
|
|
3056
|
-
}
|
|
3057
3032
|
function compactNotes(notes, max = 160) {
|
|
3058
3033
|
const oneLine = notes.replace(/\s+/g, " ").trim();
|
|
3059
3034
|
if (oneLine.length <= max) return oneLine;
|
|
3060
3035
|
return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
|
|
3061
3036
|
}
|
|
3062
3037
|
function formatAccountProfilePlaybookLine(profile) {
|
|
3063
|
-
if (
|
|
3064
|
-
|
|
3065
|
-
if (profile.roleLabels.length) {
|
|
3066
|
-
parts.push(
|
|
3067
|
-
`roles ${profile.roleLabels.join(", ")} (tickets to work on / PRs to review; prefer those teams)`
|
|
3068
|
-
);
|
|
3069
|
-
}
|
|
3070
|
-
if (profile.notes) {
|
|
3071
|
-
parts.push(`notes: ${compactNotes(profile.notes)}`);
|
|
3072
|
-
}
|
|
3073
|
-
return `- Viewer profile: ${parts.join(". ")}. A team review request is not a claim \u2014 only an individual reviewer is. When they ask to find work, use these notes with list_issues (tickets) and list_prs(queue=review) (reviews) and show the options. Only create_thread and start when they also asked to start.`;
|
|
3038
|
+
if (!profile.accountNotes) return "";
|
|
3039
|
+
return `- Account context (Settings \u2192 Agents): ${compactNotes(profile.accountNotes)}. When they ask to find work, use this with list_issues (tickets) and list_prs(queue=review) (reviews) and show the options. Only create_thread and start when they also asked to start. To change it, show the proposed text, ask_user (Save this context / Do not save), wait, then update_viewer_context(scope=account, confirmed=true).`;
|
|
3074
3040
|
}
|
|
3075
3041
|
function formatProjectProfilePlaybookLines(projects) {
|
|
3076
3042
|
const lines = projects.map((project) => {
|
|
3077
|
-
const roles = (project.roleLabels ?? []).filter(Boolean);
|
|
3078
3043
|
const notes = normalizeProfileNotes(project.notes);
|
|
3079
|
-
if (!
|
|
3080
|
-
|
|
3081
|
-
if (roles.length) bits.push(`roles ${roles.join(", ")}`);
|
|
3082
|
-
if (notes) bits.push(compactNotes(notes, 120));
|
|
3083
|
-
return `- ${project.name}: ${bits.join(". ")}`;
|
|
3044
|
+
if (!notes) return "";
|
|
3045
|
+
return `- ${project.name}: ${compactNotes(notes, 120)}`;
|
|
3084
3046
|
}).filter(Boolean);
|
|
3085
3047
|
if (!lines.length) return "";
|
|
3086
|
-
return [
|
|
3087
|
-
"\
|
|
3088
|
-
|
|
3048
|
+
return [
|
|
3049
|
+
"Project context (Settings \u2192 Projects; adds to account context):",
|
|
3050
|
+
...lines
|
|
3051
|
+
].join("\n");
|
|
3089
3052
|
}
|
|
3090
3053
|
function formatWorkspaceProfileSuffix(profile) {
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3054
|
+
if (!profile.projectNotes) return "";
|
|
3055
|
+
return ` context:${compactNotes(profile.projectNotes, 80)}`;
|
|
3056
|
+
}
|
|
3057
|
+
function formatViewerContextDirective(profile) {
|
|
3058
|
+
const account = profile.accountNotes || "(empty)";
|
|
3059
|
+
const project = profile.projectNotes || "(empty \u2014 account context applies)";
|
|
3060
|
+
return [
|
|
3061
|
+
"Viewer context (Settings \u2192 Agents / Projects) \u2014 how to find this user\u2019s tickets and review PRs:",
|
|
3062
|
+
`- Account: ${compactNotes(profile.accountNotes || account, 240)}`,
|
|
3063
|
+
`- This project: ${compactNotes(profile.projectNotes || project, 240)}`,
|
|
3064
|
+
"To change these, show the proposed text in chat, call ask_user (Save this context / Do not save), wait for the answer, then update_viewer_context with confirmed=true. Never write context without confirmation. get_viewer_context reads the current values."
|
|
3065
|
+
].join("\n");
|
|
3066
|
+
}
|
|
3067
|
+
function formatViewerContextReminder() {
|
|
3068
|
+
return "Viewer context: get_viewer_context to read; update_viewer_context only after ask_user confirms (Save this context / Do not save).";
|
|
3095
3069
|
}
|
|
3096
|
-
var
|
|
3070
|
+
var PROFILE_NOTES_MAX, LEGACY_ROLE_LABELS;
|
|
3097
3071
|
var init_account_profile = __esm({
|
|
3098
3072
|
"src/store/account-profile.ts"() {
|
|
3099
3073
|
"use strict";
|
|
3100
|
-
ACCOUNT_ROLE_PRESETS = ["engineering", "design", "product"];
|
|
3101
|
-
ACCOUNT_ROLES = ACCOUNT_ROLE_PRESETS;
|
|
3102
|
-
ACCOUNT_ROLE_MAX = 16;
|
|
3103
3074
|
PROFILE_NOTES_MAX = 2e3;
|
|
3104
|
-
|
|
3075
|
+
LEGACY_ROLE_LABELS = {
|
|
3105
3076
|
engineering: "Engineering",
|
|
3106
3077
|
design: "Design",
|
|
3107
3078
|
product: "Product",
|
|
3108
3079
|
qa: "QA"
|
|
3109
3080
|
};
|
|
3110
|
-
ROLE_TEAM_HINTS = {
|
|
3111
|
-
engineering: ["engineering-team", "engineering", "eng-team"],
|
|
3112
|
-
design: ["design-team", "design"],
|
|
3113
|
-
product: ["product-team", "product"]
|
|
3114
|
-
};
|
|
3115
3081
|
}
|
|
3116
3082
|
});
|
|
3117
3083
|
|
|
@@ -3209,16 +3175,11 @@ var init_issue_source_labels = __esm({
|
|
|
3209
3175
|
// src/store/app-settings.ts
|
|
3210
3176
|
var app_settings_exports = {};
|
|
3211
3177
|
__export(app_settings_exports, {
|
|
3212
|
-
ACCOUNT_ROLES: () => ACCOUNT_ROLES,
|
|
3213
|
-
ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
|
|
3214
|
-
ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
|
|
3215
|
-
ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
|
|
3216
3178
|
FOLLOW_UP_BEHAVIORS: () => FOLLOW_UP_BEHAVIORS,
|
|
3217
3179
|
GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
|
|
3218
3180
|
HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
|
|
3219
3181
|
ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
|
|
3220
3182
|
PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
|
|
3221
|
-
accountRoleLabel: () => accountRoleLabel,
|
|
3222
3183
|
appSettingsPath: () => appSettingsPath,
|
|
3223
3184
|
applyAppEnvironment: () => applyAppEnvironment,
|
|
3224
3185
|
autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
|
|
@@ -3242,9 +3203,12 @@ __export(app_settings_exports, {
|
|
|
3242
3203
|
emptyPublicIntegrations: () => emptyPublicIntegrations,
|
|
3243
3204
|
ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
|
|
3244
3205
|
findProjectProfileKey: () => findProjectProfileKey,
|
|
3206
|
+
foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
|
|
3245
3207
|
followUpBehavior: () => followUpBehavior,
|
|
3246
3208
|
formatAccountProfilePlaybookLine: () => formatAccountProfilePlaybookLine,
|
|
3247
3209
|
formatProjectProfilePlaybookLines: () => formatProjectProfilePlaybookLines,
|
|
3210
|
+
formatViewerContextDirective: () => formatViewerContextDirective,
|
|
3211
|
+
formatViewerContextReminder: () => formatViewerContextReminder,
|
|
3248
3212
|
formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
|
|
3249
3213
|
getAbleTimeAccessToken: () => getAbleTimeAccessToken,
|
|
3250
3214
|
getAbleTimeHost: () => getAbleTimeHost,
|
|
@@ -3265,7 +3229,7 @@ __export(app_settings_exports, {
|
|
|
3265
3229
|
maxConcurrentAgents: () => maxConcurrentAgents,
|
|
3266
3230
|
orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
|
|
3267
3231
|
orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
|
|
3268
|
-
|
|
3232
|
+
readViewerContext: () => readViewerContext,
|
|
3269
3233
|
resolveAccountProfile: () => resolveAccountProfile,
|
|
3270
3234
|
resolveAccountProfileFromSettings: () => resolveAccountProfileFromSettings,
|
|
3271
3235
|
resolveAgentExecutable: () => resolveAgentExecutable,
|
|
@@ -3275,7 +3239,6 @@ __export(app_settings_exports, {
|
|
|
3275
3239
|
resolveThreadDefaults: () => resolveThreadDefaults,
|
|
3276
3240
|
resolveViewerProfile: () => resolveViewerProfile,
|
|
3277
3241
|
resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
|
|
3278
|
-
reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
|
|
3279
3242
|
saveAbleTimeConnection: () => saveAbleTimeConnection,
|
|
3280
3243
|
saveAppSettings: () => saveAppSettings,
|
|
3281
3244
|
saveLinearOAuth: () => saveLinearOAuth,
|
|
@@ -3292,7 +3255,8 @@ __export(app_settings_exports, {
|
|
|
3292
3255
|
updateDefaultsSettings: () => updateDefaultsSettings,
|
|
3293
3256
|
updateIntegrationsSettings: () => updateIntegrationsSettings,
|
|
3294
3257
|
updateOpencodeSettings: () => updateOpencodeSettings,
|
|
3295
|
-
updateProjectProfileSettings: () => updateProjectProfileSettings
|
|
3258
|
+
updateProjectProfileSettings: () => updateProjectProfileSettings,
|
|
3259
|
+
writeViewerContext: () => writeViewerContext
|
|
3296
3260
|
});
|
|
3297
3261
|
function emptyPublicIntegrations() {
|
|
3298
3262
|
return {
|
|
@@ -3550,9 +3514,7 @@ function normalizeDefaults(raw) {
|
|
|
3550
3514
|
if (typeof source.fast === "boolean") {
|
|
3551
3515
|
out.fast = source.fast;
|
|
3552
3516
|
}
|
|
3553
|
-
const
|
|
3554
|
-
if (roles.length) out.roles = roles;
|
|
3555
|
-
const notes = normalizeProfileNotes(source.notes);
|
|
3517
|
+
const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
|
|
3556
3518
|
if (notes) out.notes = notes;
|
|
3557
3519
|
return out;
|
|
3558
3520
|
}
|
|
@@ -3563,11 +3525,9 @@ function normalizeProjectProfile(raw) {
|
|
|
3563
3525
|
if (!raw || typeof raw !== "object") return void 0;
|
|
3564
3526
|
const source = raw;
|
|
3565
3527
|
const out = {};
|
|
3566
|
-
const
|
|
3567
|
-
if (roles.length) out.roles = roles;
|
|
3568
|
-
const notes = normalizeProfileNotes(source.notes);
|
|
3528
|
+
const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
|
|
3569
3529
|
if (notes) out.notes = notes;
|
|
3570
|
-
return out.
|
|
3530
|
+
return out.notes ? out : void 0;
|
|
3571
3531
|
}
|
|
3572
3532
|
function normalizeProjects(raw) {
|
|
3573
3533
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
@@ -4126,17 +4086,13 @@ function updateDefaultsSettings(patch) {
|
|
|
4126
4086
|
defaults.fast = Boolean(patch.fast);
|
|
4127
4087
|
}
|
|
4128
4088
|
}
|
|
4129
|
-
if ("roles" in patch) {
|
|
4130
|
-
const roles = normalizeAccountRoles(patch.roles);
|
|
4131
|
-
if (!roles.length) delete defaults.roles;
|
|
4132
|
-
else defaults.roles = roles;
|
|
4133
|
-
delete defaults.role;
|
|
4134
|
-
}
|
|
4135
4089
|
if ("notes" in patch) {
|
|
4136
4090
|
const notes = normalizeProfileNotes(patch.notes);
|
|
4137
4091
|
if (!notes) delete defaults.notes;
|
|
4138
4092
|
else defaults.notes = notes;
|
|
4139
4093
|
}
|
|
4094
|
+
delete defaults.roles;
|
|
4095
|
+
delete defaults.role;
|
|
4140
4096
|
return saveAppSettings({ ...current, defaults });
|
|
4141
4097
|
}
|
|
4142
4098
|
function updateProjectProfileSettings(repoPath, patch) {
|
|
@@ -4146,17 +4102,14 @@ function updateProjectProfileSettings(repoPath, patch) {
|
|
|
4146
4102
|
const projects = { ...current.projects };
|
|
4147
4103
|
const existing = projects[key] ?? {};
|
|
4148
4104
|
const next = { ...existing };
|
|
4149
|
-
if ("roles" in patch) {
|
|
4150
|
-
const roles = normalizeAccountRoles(patch.roles);
|
|
4151
|
-
if (!roles.length) delete next.roles;
|
|
4152
|
-
else next.roles = roles;
|
|
4153
|
-
}
|
|
4154
4105
|
if ("notes" in patch) {
|
|
4155
4106
|
const notes = normalizeProfileNotes(patch.notes);
|
|
4156
4107
|
if (!notes) delete next.notes;
|
|
4157
4108
|
else next.notes = notes;
|
|
4158
4109
|
}
|
|
4159
|
-
|
|
4110
|
+
delete next.roles;
|
|
4111
|
+
delete next.role;
|
|
4112
|
+
if (!next.notes) delete projects[key];
|
|
4160
4113
|
else projects[key] = next;
|
|
4161
4114
|
return saveAppSettings({ ...current, projects });
|
|
4162
4115
|
}
|
|
@@ -4181,6 +4134,60 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
|
|
|
4181
4134
|
const project = key ? settings.projects[key] : void 0;
|
|
4182
4135
|
return resolveViewerProfile(settings.defaults, project);
|
|
4183
4136
|
}
|
|
4137
|
+
function readViewerContext(repoPath) {
|
|
4138
|
+
const settings = loadAppSettings();
|
|
4139
|
+
const key = findProjectProfileKey(settings.projects, repoPath);
|
|
4140
|
+
const resolved = resolveViewerProfile(
|
|
4141
|
+
settings.defaults,
|
|
4142
|
+
key ? settings.projects[key] : void 0
|
|
4143
|
+
);
|
|
4144
|
+
return {
|
|
4145
|
+
account: resolved.accountNotes,
|
|
4146
|
+
project: resolved.projectNotes,
|
|
4147
|
+
combined: resolved.notes,
|
|
4148
|
+
repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
|
|
4149
|
+
};
|
|
4150
|
+
}
|
|
4151
|
+
function writeViewerContext(input) {
|
|
4152
|
+
const proposed = normalizeProfileNotes(input.context);
|
|
4153
|
+
if (input.scope === "project") {
|
|
4154
|
+
const settings = loadAppSettings();
|
|
4155
|
+
const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
|
|
4156
|
+
if (!key) {
|
|
4157
|
+
return {
|
|
4158
|
+
ok: false,
|
|
4159
|
+
error: "repoPath required",
|
|
4160
|
+
message: "Pass repoPath (or run from a registered worktree) when updating project context.",
|
|
4161
|
+
current: "",
|
|
4162
|
+
proposed
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
|
|
4166
|
+
if (!input.confirmed) {
|
|
4167
|
+
return {
|
|
4168
|
+
ok: false,
|
|
4169
|
+
error: "Confirmation required",
|
|
4170
|
+
message: CONFIRM_PROJECT,
|
|
4171
|
+
current: current2,
|
|
4172
|
+
proposed
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
updateProjectProfileSettings(key, { notes: proposed || null });
|
|
4176
|
+
return { ok: true, scope: "project", context: proposed, repoPath: key };
|
|
4177
|
+
}
|
|
4178
|
+
const current = resolveAccountProfileFromSettings().accountNotes;
|
|
4179
|
+
if (!input.confirmed) {
|
|
4180
|
+
return {
|
|
4181
|
+
ok: false,
|
|
4182
|
+
error: "Confirmation required",
|
|
4183
|
+
message: CONFIRM_ACCOUNT,
|
|
4184
|
+
current,
|
|
4185
|
+
proposed
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
updateDefaultsSettings({ notes: proposed || null });
|
|
4189
|
+
return { ok: true, scope: "account", context: proposed };
|
|
4190
|
+
}
|
|
4184
4191
|
function resolveThreadDefaults(settings = loadAppSettings()) {
|
|
4185
4192
|
return {
|
|
4186
4193
|
agent: getDefaultAgent(settings),
|
|
@@ -4527,7 +4534,7 @@ function childEnvWithAppSettings(extra) {
|
|
|
4527
4534
|
function harnessEnvKey(harness) {
|
|
4528
4535
|
return HARNESS_ENV_KEYS[harness];
|
|
4529
4536
|
}
|
|
4530
|
-
var import_node_crypto3, import_node_fs14, import_node_os5, import_node_path16, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, DEFAULT_CLI_BIN;
|
|
4537
|
+
var import_node_crypto3, import_node_fs14, import_node_os5, import_node_path16, HARNESS_ENV_KEYS, DEFAULT_AGENTS, GITHUB_GIT_AUTH_MODES, FOLLOW_UP_BEHAVIORS, CLOUD_CONNECT_AGENTS, ISSUE_SOURCES, GIT_AUTH_MODES, EMPTY_SETTINGS, CONFIRM_ACCOUNT, CONFIRM_PROJECT, DEFAULT_CLI_BIN;
|
|
4531
4538
|
var init_app_settings = __esm({
|
|
4532
4539
|
"src/store/app-settings.ts"() {
|
|
4533
4540
|
"use strict";
|
|
@@ -4578,6 +4585,8 @@ var init_app_settings = __esm({
|
|
|
4578
4585
|
projects: {},
|
|
4579
4586
|
advanced: {}
|
|
4580
4587
|
};
|
|
4588
|
+
CONFIRM_ACCOUNT = "Show the user the proposed account context, call ask_user (Save this context / Do not save), wait for their answer, then retry with confirmed=true.";
|
|
4589
|
+
CONFIRM_PROJECT = "Show the user the proposed project context, call ask_user (Save this context / Do not save), wait for their answer, then retry with confirmed=true.";
|
|
4581
4590
|
DEFAULT_CLI_BIN = {
|
|
4582
4591
|
claude: "claude",
|
|
4583
4592
|
codex: "codex",
|
|
@@ -5491,12 +5500,14 @@ function isWorkspaceScratchPath(relativePath) {
|
|
|
5491
5500
|
const p = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
|
|
5492
5501
|
return p === ATTACHMENTS_DIR || p.startsWith(`${ATTACHMENTS_DIR}/`) || p === LEGACY_ATTACHMENTS_DIR || p.startsWith(`${LEGACY_ATTACHMENTS_DIR}/`) || p === ".sideboard" || p.startsWith(".sideboard/") || p === ".context" || p.startsWith(".context/");
|
|
5493
5502
|
}
|
|
5494
|
-
var ATTACHMENTS_DIR, LEGACY_ATTACHMENTS_DIR, ATTACHMENTS_GITIGNORE;
|
|
5503
|
+
var ATTACHMENTS_DIR, DETACHED_JOBS_DIR, LEGACY_ATTACHMENTS_DIR, LEGACY_DETACHED_JOBS_DIR, ATTACHMENTS_GITIGNORE;
|
|
5495
5504
|
var init_workspace_scratch = __esm({
|
|
5496
5505
|
"src/paths/workspace-scratch.ts"() {
|
|
5497
5506
|
"use strict";
|
|
5498
5507
|
ATTACHMENTS_DIR = ".context/attachments";
|
|
5508
|
+
DETACHED_JOBS_DIR = ".context/.sideboard/detached-jobs";
|
|
5499
5509
|
LEGACY_ATTACHMENTS_DIR = ".sideboard/attachments";
|
|
5510
|
+
LEGACY_DETACHED_JOBS_DIR = ".sideboard/detached-jobs";
|
|
5500
5511
|
ATTACHMENTS_GITIGNORE = `# Sideboard / workspace attachments (local only)
|
|
5501
5512
|
*
|
|
5502
5513
|
!.gitignore
|
|
@@ -5785,14 +5796,7 @@ async function listPrs(repoPath, opts) {
|
|
|
5785
5796
|
const limit = resolved.limit ?? 200;
|
|
5786
5797
|
const reviewerKey = (resolved.reviewer ?? "").trim().toLowerCase();
|
|
5787
5798
|
const needsUnclaimed = reviewerKey === "unassigned" || reviewerKey === "none" || reviewerKey === "null";
|
|
5788
|
-
|
|
5789
|
-
try {
|
|
5790
|
-
profile = resolveViewerProfileForRepo(repoPath);
|
|
5791
|
-
} catch {
|
|
5792
|
-
profile = resolveViewerProfile();
|
|
5793
|
-
}
|
|
5794
|
-
const fetchedTeams = needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : [];
|
|
5795
|
-
const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? preferTeamsForRole(fetchedTeams, profile.reviewTeamHints) : void 0);
|
|
5799
|
+
const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : void 0);
|
|
5796
5800
|
const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
|
|
5797
5801
|
const args = buildGhPrListArgs({
|
|
5798
5802
|
slug,
|
|
@@ -7417,16 +7421,15 @@ function accountRolePlaybookLine() {
|
|
|
7417
7421
|
return "";
|
|
7418
7422
|
}
|
|
7419
7423
|
}
|
|
7420
|
-
function
|
|
7424
|
+
function accountContextReminderLine() {
|
|
7421
7425
|
let profile;
|
|
7422
7426
|
try {
|
|
7423
7427
|
profile = resolveAccountProfileFromSettings();
|
|
7424
7428
|
} catch {
|
|
7425
7429
|
return "";
|
|
7426
7430
|
}
|
|
7427
|
-
if (
|
|
7428
|
-
|
|
7429
|
-
return `- Viewer profile: ${roles}. Use Settings notes with list_issues / list_prs when they ask to find work.`;
|
|
7431
|
+
if (!profile.accountNotes) return "";
|
|
7432
|
+
return "- Account context is set. Use get_viewer_context / Settings notes with list_issues / list_prs when they ask to find work. Update only after ask_user confirms.";
|
|
7430
7433
|
}
|
|
7431
7434
|
function projectProfilePlaybookBlock() {
|
|
7432
7435
|
let settings;
|
|
@@ -7443,7 +7446,6 @@ function projectProfilePlaybookBlock() {
|
|
|
7443
7446
|
const key = path.replace(/\/+$/, "");
|
|
7444
7447
|
return {
|
|
7445
7448
|
name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path,
|
|
7446
|
-
roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
|
|
7447
7449
|
notes: resolved.projectNotes
|
|
7448
7450
|
};
|
|
7449
7451
|
});
|
|
@@ -7457,7 +7459,7 @@ function coordinatorTurnReminder(opts) {
|
|
|
7457
7459
|
`- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
|
|
7458
7460
|
goal ? `- Goal / title: ${goal}` : null,
|
|
7459
7461
|
accountDefaultsPlaybookLine(),
|
|
7460
|
-
|
|
7462
|
+
accountContextReminderLine() || null,
|
|
7461
7463
|
"- Status: list_board (worktree Kanban: New \u2192 Draft \u2192 Review \u2192 Merged) or list_threads. Link chats as `[Title](sideboard://thread/<id>)`. Merge only if the user asked. If a child is stopped/error/broken, it did not finish \u2014 resume or tell the user."
|
|
7462
7464
|
].filter(Boolean).join("\n");
|
|
7463
7465
|
}
|
|
@@ -7511,7 +7513,7 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
7511
7513
|
"Never pass `agent=codex` when you yourself are Codex \u2014 nested Codex deadlocks on shared ~/.codex locks. Omit agent (Account default) or use cursor/claude.",
|
|
7512
7514
|
"Typical flow (Home board): list_board \u2192 create_thread (sourceType=ticket|pr|branch) \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft \u2192 wait_for_turn. Merge only if the user explicitly asked (`ask_git` merge).",
|
|
7513
7515
|
"Typical flow (branch / explicit source): list_workspaces \u2192 list_branches|list_prs|list_issues \u2192 create_thread \u2192 send_to_thread \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
|
|
7514
|
-
"Typical flow (find work): list_workspaces \u2192 pick repo(s) matching
|
|
7516
|
+
"Typical flow (find work): list_workspaces \u2192 pick repo(s) matching account / project context \u2192 Sideboard list_issues / linear_* (tickets) or list_prs(queue=review) (reviews) \u2192 show the options. Never wait on Claude Linear MCP. Only create_thread + send_to_thread when they asked to start (e.g. \u201Cfind me work and start it\u201D) \u2014 then wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
|
|
7515
7517
|
'Typical flow (review inbox): list_workspaces \u2192 list_prs(queue=review, limit=N) \u2192 show those PRs (ticket ids in the title when present). create_thread sourceType=pr only when they asked to start / do the work. Do not list_issues for "tickets to review".',
|
|
7516
7518
|
"Typical flow (new app): Bash create/clone under repos dir \u2192 add_workspace \u2192 create_thread \u2192 send_to_thread (implement) \u2192 wait_for_turn (loop while stillRunning) \u2192 ask_git create-draft.",
|
|
7517
7519
|
"Always ask worktree agents to commit, push, and open draft PRs (`ask_git` / `send_to_thread`). If the user gave a goal (Greptile 5/5, CI green), pass that goal through so they watch-fix-push until it lands \u2014 do not start that loop on a plain push, and do not tell the human to poll. Tell them to merge only when the user explicitly asked. The worktree agent runs git/gh; never merge from this orchestration cwd."
|
|
@@ -7565,10 +7567,10 @@ var init_coordinator_prompt = __esm({
|
|
|
7565
7567
|
"Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
|
|
7566
7568
|
"Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
|
|
7567
7569
|
"Discover:",
|
|
7568
|
-
"- list_workspaces \u2014 registered repos (path + github slug +
|
|
7570
|
+
"- list_workspaces \u2014 registered repos (path + github slug + project context when set). Use account / project context to pick the right repo for tickets or reviews.",
|
|
7569
7571
|
"- list_board \u2014 Home Kanban of worktrees (New / Draft / Review / Merged; one card per checkout). Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind, column, limit.",
|
|
7570
|
-
'- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. Review is PRs (the surface for assigned ticket work), not the tickets: "Get me N tickets to review" \u2192 list_prs(queue=review, limit=N) then create_thread sourceType=pr. That is open non-draft PRs labeled eng-review with no individual user reviewer yet. A team request (engineering-team) is not a claim \u2014 the viewer is on that team and can pick it up; claimed means an individual account is the reviewer. Bots ignored.
|
|
7571
|
-
"- Find work: when they ask to find work, pick up tickets, or list reviews, use Settings \u2192 Agents (account) plus Settings \u2192 Projects (per-repo)
|
|
7572
|
+
'- list_branches / list_prs / list_issues \u2014 pass repoPath from list_workspaces. Review is PRs (the surface for assigned ticket work), not the tickets: "Get me N tickets to review" \u2192 list_prs(queue=review, limit=N) then create_thread sourceType=pr. That is open non-draft PRs labeled eng-review with no individual user reviewer yet. A team request (engineering-team) is not a claim \u2014 the viewer is on that team and can pick it up; claimed means an individual account is the reviewer. Bots ignored. Use Settings \u2192 Agents / Projects context (roles, teams, labels \u2014 freeform text; project adds to account) to prefer the right queues. queue=mine is review-requested:@me; queue=approved|changes uses those labels. Also: state, label, reviewer=me|unassigned|login, query, limit default 40 max 250; raise limit or tighten when truncated. list_issues (query, assignee=me|unassigned|all|user, limit default 40 max 250) lists Linear, AbleTime, or GitHub tickets \u2014 do not use it for that review-inbox ask. When they ask for tickets to work on, follow their context (assignee=me or unassigned as it says).',
|
|
7573
|
+
"- Find work: when they ask to find work, pick up tickets, or list reviews, use Settings \u2192 Agents (account) plus Settings \u2192 Projects (per-repo) context. Tickets \u2192 Sideboard list_issues / linear_* (Account Linear). Reviews \u2192 list_prs(queue=review). Do not call Claude Linear MCP or any other vendor Linear MCP \u2014 those HTTP connectors hang or flap on the first turn. If a vendor MCP is down or reconnecting, ignore it and keep going with Sideboard tools. Show the options \u2014 do not create_thread or start unless they also asked to start (e.g. \u201Cfind me work and start it\u201D). Do not start this unprompted. To change that context, show the proposed text, ask_user (Save this context / Do not save), wait, then update_viewer_context with confirmed=true. Never write it without confirmation.",
|
|
7572
7574
|
"- linear_* (when Linear is connected) \u2014 list_teams for team key/states; get/create/update/comment with ENG-123. Worktree agents have the same Account tools \u2014 prefer they comment, update status, and create spin-offs (parent=) on their own ticket. Scope errors: reconnect Linear in Account settings. Prefer these over any Linear MCP the CLI may still list.",
|
|
7573
7575
|
"- github_* \u2014 get/comment/update/create GitHub issues via Account `gh` (#123). Worktree agents have these too. Prefer over any vendor GitHub MCP. Pass parent= for spin-offs.",
|
|
7574
7576
|
"- abletime_* (when AbleTime is connected) \u2014 orientation first; get/comment/update/create (parent= for spin-offs); ensure_task when work has no ticket (or create_thread from the default branch auto-creates one). Worktree agents have the same Account tools.",
|
|
@@ -7578,7 +7580,8 @@ var init_coordinator_prompt = __esm({
|
|
|
7578
7580
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
7579
7581
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
7580
7582
|
"- list_threads / get_thread \u2014 live thread list (parent id + last message preview). get_thread on this orchestration chat lists child worktree agents (status + lastText). Also includes usage / lastTurnUsage.",
|
|
7581
|
-
"- ask_user \u2014 composer multiple-choice only when blocked on a concrete choice (approach fork, which API). Never for hellos, check-ins, or invented \u201Cwhat should we do?\u201D menus \u2014 reply in chat. Explain options first, description on every option, then wait.",
|
|
7583
|
+
"- ask_user \u2014 composer multiple-choice only when blocked on a concrete choice (approach fork, which API, Save this context / Do not save). Never for hellos, check-ins, or invented \u201Cwhat should we do?\u201D menus \u2014 reply in chat. Explain options first, description on every option, then wait.",
|
|
7584
|
+
"- get_viewer_context / update_viewer_context \u2014 read or replace Settings \u2192 Agents (account) and Settings \u2192 Projects (per-repo) context. You can update a project: pass scope=project and repoPath from list_workspaces (this cwd is not a project). update_viewer_context requires confirmed=true after ask_user. Never write context unprompted.",
|
|
7582
7585
|
"- set_caffeinate \u2014 keep this Mac awake across turns (macOS caffeinate). Turn on for Slack / away-from-keyboard work, overnight schedules, or when the user will be away. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the machine awake. Closing this chat also releases it.",
|
|
7583
7586
|
"- list_schedules / create_schedule / update_schedule / delete_schedule / run_schedule \u2014 local jobs that send a prompt to an orchestration chat (threadId or self) or start a new Global chat (omit threadId). One-shot `at`, interval `every` (15m/1h/6h/1d), or 5-field `cron`. Recurring jobs without threadId open a new chat each run. Jobs fire only while Sideboard.app is running; sleep skips until wake. Overnight/unattended runs: ask the user to enable Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or call set_caffeinate.",
|
|
7584
7587
|
"Workspaces:",
|
|
@@ -8605,10 +8608,10 @@ var init_brightsy = __esm({
|
|
|
8605
8608
|
});
|
|
8606
8609
|
|
|
8607
8610
|
// src/agents/claude-mcp.ts
|
|
8608
|
-
function parseMcpList(
|
|
8611
|
+
function parseMcpList(text7) {
|
|
8609
8612
|
const servers = [];
|
|
8610
8613
|
const seen = /* @__PURE__ */ new Set();
|
|
8611
|
-
for (const raw of
|
|
8614
|
+
for (const raw of text7.split("\n")) {
|
|
8612
8615
|
const line = raw.trim();
|
|
8613
8616
|
if (!line || /^Checking MCP/i.test(line)) continue;
|
|
8614
8617
|
const m = line.match(/^(.+?):\s+\S+/);
|
|
@@ -8692,6 +8695,9 @@ function toolDescription(name, input) {
|
|
|
8692
8695
|
if (/present_files$/i.test(name)) {
|
|
8693
8696
|
return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
|
|
8694
8697
|
}
|
|
8698
|
+
if (/wait_for_job$/i.test(name)) {
|
|
8699
|
+
return str2(input?.id) ? `Wait for ${str2(input?.id)}` : "Wait for job";
|
|
8700
|
+
}
|
|
8695
8701
|
if (isSubagentToolName(name)) {
|
|
8696
8702
|
const desc = str2(input?.description);
|
|
8697
8703
|
const sub = asRecord(input?.subagentType);
|
|
@@ -8760,8 +8766,8 @@ function isSubagentToolName(name) {
|
|
|
8760
8766
|
if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
|
|
8761
8767
|
return /connectedAgentRequest/i.test(n);
|
|
8762
8768
|
}
|
|
8763
|
-
function isInternalAgentStatusText(
|
|
8764
|
-
const t =
|
|
8769
|
+
function isInternalAgentStatusText(text7) {
|
|
8770
|
+
const t = text7.trim();
|
|
8765
8771
|
if (!t) return false;
|
|
8766
8772
|
if (/waiting for gate to pass/i.test(t)) return true;
|
|
8767
8773
|
if (/^agent is running\b/i.test(t) && t.length < 160) return true;
|
|
@@ -8775,9 +8781,9 @@ function lastTextPart(parts, type) {
|
|
|
8775
8781
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
8776
8782
|
const p = parts[i];
|
|
8777
8783
|
if (p.type !== type) continue;
|
|
8778
|
-
const
|
|
8779
|
-
if (!
|
|
8780
|
-
return
|
|
8784
|
+
const text7 = p.text.trim();
|
|
8785
|
+
if (!text7 || isInternalAgentStatusText(text7)) continue;
|
|
8786
|
+
return text7;
|
|
8781
8787
|
}
|
|
8782
8788
|
return "";
|
|
8783
8789
|
}
|
|
@@ -8798,7 +8804,7 @@ function liveActivitySummary(parts, opts) {
|
|
|
8798
8804
|
);
|
|
8799
8805
|
const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
|
|
8800
8806
|
const thinking = lastTextPart(parts, "thinking");
|
|
8801
|
-
const
|
|
8807
|
+
const text7 = lastTextPart(parts, "text");
|
|
8802
8808
|
if (runningSubs.length > 0) {
|
|
8803
8809
|
const heads = runningSubs.map(toolLabel);
|
|
8804
8810
|
const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
|
|
@@ -8808,7 +8814,7 @@ function liveActivitySummary(parts, opts) {
|
|
|
8808
8814
|
if (runningTop) return toolLabel(runningTop);
|
|
8809
8815
|
if (runningPoll) return toolLabel(runningPoll);
|
|
8810
8816
|
if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
|
|
8811
|
-
if (
|
|
8817
|
+
if (text7) return "Writing reply\u2026";
|
|
8812
8818
|
const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
|
|
8813
8819
|
if (last) {
|
|
8814
8820
|
const label = toolLabel(last);
|
|
@@ -8836,9 +8842,9 @@ function toolFilePath(input) {
|
|
|
8836
8842
|
if (!input) return void 0;
|
|
8837
8843
|
return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
|
|
8838
8844
|
}
|
|
8839
|
-
function countLines(
|
|
8840
|
-
if (!
|
|
8841
|
-
return
|
|
8845
|
+
function countLines(text7) {
|
|
8846
|
+
if (!text7) return 0;
|
|
8847
|
+
return text7.split("\n").length;
|
|
8842
8848
|
}
|
|
8843
8849
|
function diffFromInput(input) {
|
|
8844
8850
|
if (!input) return {};
|
|
@@ -9063,9 +9069,9 @@ function partsToAssistantText(parts) {
|
|
|
9063
9069
|
(p) => p.type === "text" && !messagePartParentId(p)
|
|
9064
9070
|
).map((p) => p.text).join("").trim();
|
|
9065
9071
|
}
|
|
9066
|
-
function stripBrightsyNdjsonNoise(
|
|
9067
|
-
if (!
|
|
9068
|
-
let out =
|
|
9072
|
+
function stripBrightsyNdjsonNoise(text7) {
|
|
9073
|
+
if (!text7 || !text7.includes('"type"')) return text7;
|
|
9074
|
+
let out = text7;
|
|
9069
9075
|
out = out.replace(
|
|
9070
9076
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
9071
9077
|
""
|
|
@@ -9388,8 +9394,8 @@ function isBrightsyConnected() {
|
|
|
9388
9394
|
return false;
|
|
9389
9395
|
}
|
|
9390
9396
|
}
|
|
9391
|
-
function promptMentionsBrightsy(
|
|
9392
|
-
return BRIGHTSY_WORD.test(
|
|
9397
|
+
function promptMentionsBrightsy(text7) {
|
|
9398
|
+
return BRIGHTSY_WORD.test(text7 ?? "");
|
|
9393
9399
|
}
|
|
9394
9400
|
function isBrightsyMcpToolName(name) {
|
|
9395
9401
|
const n = name.toLowerCase();
|
|
@@ -9664,7 +9670,10 @@ var init_injected_mcp = __esm({
|
|
|
9664
9670
|
"mcp__sideboard__present_schema",
|
|
9665
9671
|
"mcp__sideboard__present_files",
|
|
9666
9672
|
"mcp__sideboard__ask_user",
|
|
9667
|
-
"mcp__sideboard__present_plan"
|
|
9673
|
+
"mcp__sideboard__present_plan",
|
|
9674
|
+
"mcp__sideboard__wait_for_job",
|
|
9675
|
+
"mcp__sideboard__get_viewer_context",
|
|
9676
|
+
"mcp__sideboard__update_viewer_context"
|
|
9668
9677
|
];
|
|
9669
9678
|
SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
|
|
9670
9679
|
SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
|
|
@@ -9997,9 +10006,9 @@ function compactMetadataFromClaude(obj) {
|
|
|
9997
10006
|
return { trigger, postTokens };
|
|
9998
10007
|
}
|
|
9999
10008
|
function parseIssuesJson(raw) {
|
|
10000
|
-
const
|
|
10001
|
-
const candidates = [
|
|
10002
|
-
const match =
|
|
10009
|
+
const text7 = raw.trim();
|
|
10010
|
+
const candidates = [text7];
|
|
10011
|
+
const match = text7.match(/\[[\s\S]*\]/);
|
|
10003
10012
|
if (match) candidates.push(match[0]);
|
|
10004
10013
|
for (const c of candidates) {
|
|
10005
10014
|
try {
|
|
@@ -10267,8 +10276,8 @@ var init_claude = __esm({
|
|
|
10267
10276
|
if (errorDetail) {
|
|
10268
10277
|
events.push({ type: "stderr", data: errorDetail });
|
|
10269
10278
|
} else {
|
|
10270
|
-
const
|
|
10271
|
-
if (typeof
|
|
10279
|
+
const text7 = obj.result;
|
|
10280
|
+
if (typeof text7 === "string" && text7) events.push({ type: "stdout", data: text7 });
|
|
10272
10281
|
}
|
|
10273
10282
|
const usage = usageFromClaude(obj.usage);
|
|
10274
10283
|
if (usage) {
|
|
@@ -10345,9 +10354,9 @@ function asObject(value) {
|
|
|
10345
10354
|
function listMcpNamesFromJsonMap(raw, key) {
|
|
10346
10355
|
return Object.keys(asObject(asObject(raw)[key])).filter((n) => n.trim());
|
|
10347
10356
|
}
|
|
10348
|
-
function listMcpNamesFromCodexToml(
|
|
10357
|
+
function listMcpNamesFromCodexToml(text7) {
|
|
10349
10358
|
try {
|
|
10350
|
-
const parsed = (0, import_smol_toml2.parse)(
|
|
10359
|
+
const parsed = (0, import_smol_toml2.parse)(text7);
|
|
10351
10360
|
return Object.keys(asObject(parsed.mcp_servers)).filter((n) => n.trim());
|
|
10352
10361
|
} catch {
|
|
10353
10362
|
return [];
|
|
@@ -10478,8 +10487,8 @@ function codexConfigHasNetworkAccess() {
|
|
|
10478
10487
|
];
|
|
10479
10488
|
for (const path of candidates) {
|
|
10480
10489
|
if (!(0, import_node_fs28.existsSync)(path)) continue;
|
|
10481
|
-
const
|
|
10482
|
-
if (/network_access\s*=\s*true/.test(
|
|
10490
|
+
const text7 = (0, import_node_fs28.readFileSync)(path, "utf8");
|
|
10491
|
+
if (/network_access\s*=\s*true/.test(text7)) return true;
|
|
10483
10492
|
}
|
|
10484
10493
|
return false;
|
|
10485
10494
|
}
|
|
@@ -10492,8 +10501,8 @@ function unwrapCodexMcpResult(result) {
|
|
|
10492
10501
|
const texts = [];
|
|
10493
10502
|
for (const item of rec.content) {
|
|
10494
10503
|
if (!item || typeof item !== "object") continue;
|
|
10495
|
-
const
|
|
10496
|
-
if (typeof
|
|
10504
|
+
const text7 = item.text;
|
|
10505
|
+
if (typeof text7 === "string") texts.push(text7);
|
|
10497
10506
|
}
|
|
10498
10507
|
if (texts.length) return texts.join("\n");
|
|
10499
10508
|
}
|
|
@@ -10735,8 +10744,8 @@ var init_codex = __esm({
|
|
|
10735
10744
|
if (msg) nested.push(msg);
|
|
10736
10745
|
}
|
|
10737
10746
|
}
|
|
10738
|
-
for (const
|
|
10739
|
-
events.push({ type: "stdout", data:
|
|
10747
|
+
for (const text7 of nested) {
|
|
10748
|
+
events.push({ type: "stdout", data: text7, parentId: item.id });
|
|
10740
10749
|
}
|
|
10741
10750
|
events.push({
|
|
10742
10751
|
type: "tool_result",
|
|
@@ -11287,8 +11296,8 @@ var init_opencode = __esm({
|
|
|
11287
11296
|
return { type: "session_id", data: sid };
|
|
11288
11297
|
}
|
|
11289
11298
|
if (obj.type === "text") {
|
|
11290
|
-
const
|
|
11291
|
-
if (
|
|
11299
|
+
const text7 = obj.part?.text ?? obj.text;
|
|
11300
|
+
if (text7) return { type: "stdout", data: text7 };
|
|
11292
11301
|
}
|
|
11293
11302
|
if (obj.type === "tool_use") {
|
|
11294
11303
|
const part = obj.part;
|
|
@@ -11567,8 +11576,8 @@ var init_list_models = __esm({
|
|
|
11567
11576
|
});
|
|
11568
11577
|
|
|
11569
11578
|
// src/agents/session-quota.ts
|
|
11570
|
-
function isSessionQuotaLimit(
|
|
11571
|
-
const lower =
|
|
11579
|
+
function isSessionQuotaLimit(text7) {
|
|
11580
|
+
const lower = text7.trim().toLowerCase();
|
|
11572
11581
|
if (!lower) return false;
|
|
11573
11582
|
if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
|
|
11574
11583
|
return false;
|
|
@@ -11576,10 +11585,10 @@ function isSessionQuotaLimit(text6) {
|
|
|
11576
11585
|
if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
|
|
11577
11586
|
return false;
|
|
11578
11587
|
}
|
|
11579
|
-
return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(
|
|
11588
|
+
return /you've hit your/.test(lower) || /hit your (session|weekly|opus) limit/.test(lower) || /usage limit/.test(lower) || /rate.?limit|too many requests|\b429\b/.test(lower) && /reset/i.test(text7);
|
|
11580
11589
|
}
|
|
11581
|
-
function parseSessionQuotaResetAt(
|
|
11582
|
-
const absolute =
|
|
11590
|
+
function parseSessionQuotaResetAt(text7, now = /* @__PURE__ */ new Date()) {
|
|
11591
|
+
const absolute = text7.match(
|
|
11583
11592
|
/resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
|
|
11584
11593
|
);
|
|
11585
11594
|
if (absolute) {
|
|
@@ -11597,7 +11606,7 @@ function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
|
|
|
11597
11606
|
}
|
|
11598
11607
|
return at;
|
|
11599
11608
|
}
|
|
11600
|
-
const relative =
|
|
11609
|
+
const relative = text7.match(
|
|
11601
11610
|
/resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
|
|
11602
11611
|
);
|
|
11603
11612
|
if (relative) {
|
|
@@ -11897,8 +11906,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
11897
11906
|
onEvent({ type: "exit", data: exitCode });
|
|
11898
11907
|
const finalized = finalizeParts(parts);
|
|
11899
11908
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
11900
|
-
const
|
|
11901
|
-
return { exitCode, sessionId, assistantText:
|
|
11909
|
+
const text7 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
11910
|
+
return { exitCode, sessionId, assistantText: text7, parts: finalized, usage };
|
|
11902
11911
|
});
|
|
11903
11912
|
return {
|
|
11904
11913
|
pid: child.pid,
|
|
@@ -12157,9 +12166,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
12157
12166
|
{ cwd: opts?.cwd, reject: false }
|
|
12158
12167
|
);
|
|
12159
12168
|
if (exitCode !== 0) return null;
|
|
12160
|
-
const
|
|
12161
|
-
if (
|
|
12162
|
-
return
|
|
12169
|
+
const text7 = stdout.trim();
|
|
12170
|
+
if (text7.length < 40) return null;
|
|
12171
|
+
return text7;
|
|
12163
12172
|
} catch {
|
|
12164
12173
|
return null;
|
|
12165
12174
|
}
|
|
@@ -12208,9 +12217,9 @@ function extractiveSummary(transcript) {
|
|
|
12208
12217
|
);
|
|
12209
12218
|
return parts.join("\n");
|
|
12210
12219
|
}
|
|
12211
|
-
function clipSummary(
|
|
12212
|
-
if (
|
|
12213
|
-
return `${
|
|
12220
|
+
function clipSummary(text7) {
|
|
12221
|
+
if (text7.length <= MAX_SUMMARY_CHARS) return text7;
|
|
12222
|
+
return `${text7.slice(0, MAX_SUMMARY_CHARS)}
|
|
12214
12223
|
|
|
12215
12224
|
[\u2026summary truncated\u2026]`;
|
|
12216
12225
|
}
|
|
@@ -12364,8 +12373,8 @@ function findLastBrightsyContextSummary(messages) {
|
|
|
12364
12373
|
continue;
|
|
12365
12374
|
}
|
|
12366
12375
|
if (part.status === "error") continue;
|
|
12367
|
-
const
|
|
12368
|
-
if (
|
|
12376
|
+
const text7 = extractBrightsyContextSummary(part.result);
|
|
12377
|
+
if (text7) return { index: i, text: text7 };
|
|
12369
12378
|
}
|
|
12370
12379
|
}
|
|
12371
12380
|
return null;
|
|
@@ -13686,6 +13695,242 @@ var init_child_halt = __esm({
|
|
|
13686
13695
|
}
|
|
13687
13696
|
});
|
|
13688
13697
|
|
|
13698
|
+
// src/mcp/wait-for-turn.ts
|
|
13699
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
13700
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
13701
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
13702
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
13703
|
+
}
|
|
13704
|
+
function mcpWaitStillRunningHint(status) {
|
|
13705
|
+
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
13706
|
+
}
|
|
13707
|
+
function mcpWaitFinishedHint(status) {
|
|
13708
|
+
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
13709
|
+
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
13710
|
+
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
13711
|
+
return void 0;
|
|
13712
|
+
}
|
|
13713
|
+
var MCP_WAIT_FOR_TURN_MAX_MS, MCP_WAIT_STILL_RUNNING_HINT, MCP_WAIT_QUEUED_HINT, MCP_WAIT_STOPPED_HINT, MCP_WAIT_BROKEN_HINT, MCP_WAIT_ERROR_HINT;
|
|
13714
|
+
var init_wait_for_turn = __esm({
|
|
13715
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
13716
|
+
"use strict";
|
|
13717
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
13718
|
+
MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
13719
|
+
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 a check-in prompt, force_stop, or assume it failed to start.";
|
|
13720
|
+
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.";
|
|
13721
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
13722
|
+
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.";
|
|
13723
|
+
}
|
|
13724
|
+
});
|
|
13725
|
+
|
|
13726
|
+
// src/mcp/wait-for-job.ts
|
|
13727
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
13728
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
13729
|
+
}
|
|
13730
|
+
function sanitizeDetachedJobId(id) {
|
|
13731
|
+
const s = id.trim();
|
|
13732
|
+
if (!JOB_ID_RE.test(s)) {
|
|
13733
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
13734
|
+
}
|
|
13735
|
+
return s;
|
|
13736
|
+
}
|
|
13737
|
+
function jobAlive(pid) {
|
|
13738
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
13739
|
+
try {
|
|
13740
|
+
process.kill(pid, 0);
|
|
13741
|
+
return true;
|
|
13742
|
+
} catch {
|
|
13743
|
+
return false;
|
|
13744
|
+
}
|
|
13745
|
+
}
|
|
13746
|
+
function readIntFile(file) {
|
|
13747
|
+
if (!(0, import_node_fs38.existsSync)(file)) return null;
|
|
13748
|
+
const n = Number.parseInt((0, import_node_fs38.readFileSync)(file, "utf8").trim(), 10);
|
|
13749
|
+
return Number.isInteger(n) ? n : null;
|
|
13750
|
+
}
|
|
13751
|
+
function jobDir(root, id, legacy = false) {
|
|
13752
|
+
return (0, import_node_path38.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
13753
|
+
}
|
|
13754
|
+
function resolveJobDir(root, id) {
|
|
13755
|
+
const modern = jobDir(root, id, false);
|
|
13756
|
+
if ((0, import_node_fs38.existsSync)(modern)) return modern;
|
|
13757
|
+
const legacy = jobDir(root, id, true);
|
|
13758
|
+
if ((0, import_node_fs38.existsSync)(legacy)) return legacy;
|
|
13759
|
+
return modern;
|
|
13760
|
+
}
|
|
13761
|
+
function listJobIdsIn(root, rel) {
|
|
13762
|
+
const dir = (0, import_node_path38.join)(root, rel);
|
|
13763
|
+
if (!(0, import_node_fs38.existsSync)(dir)) return [];
|
|
13764
|
+
return (0, import_node_fs38.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
13765
|
+
}
|
|
13766
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
13767
|
+
const root = worktreePath.trim();
|
|
13768
|
+
if (!root) return [];
|
|
13769
|
+
const ids = /* @__PURE__ */ new Set([
|
|
13770
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
13771
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
13772
|
+
]);
|
|
13773
|
+
const running = [];
|
|
13774
|
+
for (const id of ids) {
|
|
13775
|
+
const dir = resolveJobDir(root, id);
|
|
13776
|
+
const pid = readIntFile((0, import_node_path38.join)(dir, "pid"));
|
|
13777
|
+
if (pid != null && jobAlive(pid)) running.push(id);
|
|
13778
|
+
}
|
|
13779
|
+
return running.sort();
|
|
13780
|
+
}
|
|
13781
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
13782
|
+
const t = (text7 ?? "").trim();
|
|
13783
|
+
if (!t) return false;
|
|
13784
|
+
return /\b(i['’]?ll|i will)\s+let you know\b/i.test(t) || /\blet you know when\b/i.test(t) || /\b(check back|ping me)\s+when\b/i.test(t) || /\bi(?:['’]ll| will)\s+(report|update you)\s+when\b/i.test(t);
|
|
13785
|
+
}
|
|
13786
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
13787
|
+
const ids = jobIds.join(", ");
|
|
13788
|
+
return [
|
|
13789
|
+
`Detached job still running: ${ids}.`,
|
|
13790
|
+
"Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
|
|
13791
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
13792
|
+
].join(" ");
|
|
13793
|
+
}
|
|
13794
|
+
function formatDeferredDoneContinuePrompt() {
|
|
13795
|
+
return [
|
|
13796
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
13797
|
+
"If tests/pack/deploy still need to run: start once with detached-job.js, present_artifact type=log, then loop wait_for_job until stillRunning is false.",
|
|
13798
|
+
"Do not say you will let the user know later."
|
|
13799
|
+
].join(" ");
|
|
13800
|
+
}
|
|
13801
|
+
function turnWatchedDetachedJob(parts) {
|
|
13802
|
+
return parts.some((p) => {
|
|
13803
|
+
if (p.type !== "tool") return false;
|
|
13804
|
+
if (/wait_for_job$/i.test(p.name ?? "")) return true;
|
|
13805
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
13806
|
+
return /detached-job\.js\b/i.test(blob);
|
|
13807
|
+
});
|
|
13808
|
+
}
|
|
13809
|
+
function planJobContinue(opts) {
|
|
13810
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
13811
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
13812
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
13813
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
13814
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
13815
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
13816
|
+
return {
|
|
13817
|
+
action: "wait",
|
|
13818
|
+
jobIds: opts.runningJobIds,
|
|
13819
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
13820
|
+
};
|
|
13821
|
+
}
|
|
13822
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
13823
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
13824
|
+
}
|
|
13825
|
+
return { action: "none" };
|
|
13826
|
+
}
|
|
13827
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
13828
|
+
if (!(0, import_node_fs38.existsSync)(logFile)) return "(no log yet)";
|
|
13829
|
+
const lines = (0, import_node_fs38.readFileSync)(logFile, "utf8").split("\n");
|
|
13830
|
+
return lines.slice(-maxLines).join("\n");
|
|
13831
|
+
}
|
|
13832
|
+
function readLogLines(file) {
|
|
13833
|
+
if (!(0, import_node_fs38.existsSync)(file)) return [];
|
|
13834
|
+
const lines = (0, import_node_fs38.readFileSync)(file, "utf8").split("\n");
|
|
13835
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
13836
|
+
return lines;
|
|
13837
|
+
}
|
|
13838
|
+
function takeDelta(logFile, cursorFile) {
|
|
13839
|
+
const lines = readLogLines(logFile);
|
|
13840
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
13841
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
13842
|
+
return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
|
|
13843
|
+
}
|
|
13844
|
+
function snapshotJob(dir) {
|
|
13845
|
+
const pid = readIntFile((0, import_node_path38.join)(dir, "pid"));
|
|
13846
|
+
const running = pid != null && jobAlive(pid);
|
|
13847
|
+
return {
|
|
13848
|
+
pid,
|
|
13849
|
+
running,
|
|
13850
|
+
exitCode: readIntFile((0, import_node_path38.join)(dir, "exit")),
|
|
13851
|
+
log: (0, import_node_path38.join)(dir, "log"),
|
|
13852
|
+
cursor: (0, import_node_path38.join)(dir, "present.cursor"),
|
|
13853
|
+
progress: tailProgress((0, import_node_path38.join)(dir, "log"))
|
|
13854
|
+
};
|
|
13855
|
+
}
|
|
13856
|
+
function toResult(id, snap, extra) {
|
|
13857
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
13858
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
13859
|
+
const stillRunning = snap.running && !ok;
|
|
13860
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
13861
|
+
try {
|
|
13862
|
+
(0, import_node_fs38.mkdirSync)((0, import_node_path38.dirname)(snap.cursor), { recursive: true });
|
|
13863
|
+
(0, import_node_fs38.writeFileSync)(snap.cursor, `${nextCursor}
|
|
13864
|
+
`);
|
|
13865
|
+
} catch {
|
|
13866
|
+
}
|
|
13867
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
13868
|
+
return {
|
|
13869
|
+
stillRunning,
|
|
13870
|
+
ok,
|
|
13871
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
13872
|
+
status,
|
|
13873
|
+
id,
|
|
13874
|
+
pid: snap.pid,
|
|
13875
|
+
exitCode: snap.exitCode ?? void 0,
|
|
13876
|
+
delta,
|
|
13877
|
+
progress: extra?.progress ?? snap.progress,
|
|
13878
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
13879
|
+
};
|
|
13880
|
+
}
|
|
13881
|
+
async function sleepMs(ms) {
|
|
13882
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
13883
|
+
}
|
|
13884
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
13885
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
13886
|
+
const root = cwd.trim() || process.cwd();
|
|
13887
|
+
const dir = resolveJobDir(root, jobId);
|
|
13888
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
13889
|
+
const sleep = opts?.sleep ?? sleepMs;
|
|
13890
|
+
if (!(0, import_node_fs38.existsSync)(dir)) {
|
|
13891
|
+
return {
|
|
13892
|
+
stillRunning: false,
|
|
13893
|
+
ok: false,
|
|
13894
|
+
failed: true,
|
|
13895
|
+
status: "failed",
|
|
13896
|
+
id: jobId,
|
|
13897
|
+
delta: "",
|
|
13898
|
+
progress: "No detached job. Start one first.",
|
|
13899
|
+
hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
|
|
13900
|
+
};
|
|
13901
|
+
}
|
|
13902
|
+
const deadline = Date.now() + timeoutMs;
|
|
13903
|
+
let snap = snapshotJob(dir);
|
|
13904
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
13905
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
13906
|
+
return toResult(jobId, snap, {
|
|
13907
|
+
failed: true,
|
|
13908
|
+
progress: "No detached job. Start one first."
|
|
13909
|
+
});
|
|
13910
|
+
}
|
|
13911
|
+
while (Date.now() < deadline) {
|
|
13912
|
+
snap = snapshotJob(dir);
|
|
13913
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
13914
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
13915
|
+
await sleep(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
13916
|
+
}
|
|
13917
|
+
snap = snapshotJob(dir);
|
|
13918
|
+
return toResult(jobId, snap);
|
|
13919
|
+
}
|
|
13920
|
+
var import_node_fs38, import_node_path38, MAX_JOB_CONTINUES, MCP_WAIT_JOB_STILL_RUNNING_HINT, JOB_ID_RE;
|
|
13921
|
+
var init_wait_for_job = __esm({
|
|
13922
|
+
"src/mcp/wait-for-job.ts"() {
|
|
13923
|
+
"use strict";
|
|
13924
|
+
import_node_fs38 = require("fs");
|
|
13925
|
+
import_node_path38 = require("path");
|
|
13926
|
+
init_workspace_scratch();
|
|
13927
|
+
init_wait_for_turn();
|
|
13928
|
+
MAX_JOB_CONTINUES = 8;
|
|
13929
|
+
MCP_WAIT_JOB_STILL_RUNNING_HINT = "Job is still running. present_artifact type=log with the same artifact_id and content=delta (new lines only). Then call wait_for_job again. Do not end the turn or tell the user you will let them know later.";
|
|
13930
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
13931
|
+
}
|
|
13932
|
+
});
|
|
13933
|
+
|
|
13689
13934
|
// src/detect/detect.ts
|
|
13690
13935
|
async function requireAgent(agent, opts) {
|
|
13691
13936
|
ensureAgentPath();
|
|
@@ -14238,11 +14483,11 @@ function unwrapToolResult(result) {
|
|
|
14238
14483
|
return typeof item.text === "string" ? item.text : "";
|
|
14239
14484
|
}).filter(Boolean);
|
|
14240
14485
|
if (texts.length === 1) {
|
|
14241
|
-
const
|
|
14486
|
+
const text7 = texts[0];
|
|
14242
14487
|
try {
|
|
14243
|
-
return JSON.parse(
|
|
14488
|
+
return JSON.parse(text7);
|
|
14244
14489
|
} catch {
|
|
14245
|
-
return
|
|
14490
|
+
return text7;
|
|
14246
14491
|
}
|
|
14247
14492
|
}
|
|
14248
14493
|
if (texts.length > 1) return texts.join("\n");
|
|
@@ -14770,7 +15015,7 @@ function reuseLiveThread(input, repoPath, match) {
|
|
|
14770
15015
|
}
|
|
14771
15016
|
async function createThread(input, _onSetupLine) {
|
|
14772
15017
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
14773
|
-
if (!(0,
|
|
15018
|
+
if (!(0, import_node_fs39.existsSync)(repoPath)) {
|
|
14774
15019
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
14775
15020
|
}
|
|
14776
15021
|
const reused = reuseLiveThread(input, repoPath, {
|
|
@@ -14941,11 +15186,11 @@ async function createThread(input, _onSetupLine) {
|
|
|
14941
15186
|
await ensureWorkspace(repoPath);
|
|
14942
15187
|
return readThread(thread.id) ?? thread;
|
|
14943
15188
|
}
|
|
14944
|
-
var
|
|
15189
|
+
var import_node_fs39;
|
|
14945
15190
|
var init_create = __esm({
|
|
14946
15191
|
"src/threads/create.ts"() {
|
|
14947
15192
|
"use strict";
|
|
14948
|
-
|
|
15193
|
+
import_node_fs39 = require("fs");
|
|
14949
15194
|
init_detect();
|
|
14950
15195
|
init_worktree();
|
|
14951
15196
|
init_home_board();
|
|
@@ -14995,8 +15240,8 @@ function summarizeTurnLive(parts) {
|
|
|
14995
15240
|
const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
|
|
14996
15241
|
const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
|
|
14997
15242
|
const thinking = lastText(parts, "thinking");
|
|
14998
|
-
const
|
|
14999
|
-
const excerptRaw = thinking ||
|
|
15243
|
+
const text7 = lastText(parts, "text");
|
|
15244
|
+
const excerptRaw = thinking || text7;
|
|
15000
15245
|
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
15001
15246
|
const summary = liveActivitySummary(parts);
|
|
15002
15247
|
return {
|
|
@@ -15048,20 +15293,20 @@ function writeTurnLive(threadId, progress) {
|
|
|
15048
15293
|
const path = threadLivePath(threadId);
|
|
15049
15294
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
15050
15295
|
try {
|
|
15051
|
-
(0,
|
|
15052
|
-
(0,
|
|
15296
|
+
(0, import_node_fs40.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
|
|
15297
|
+
(0, import_node_fs40.renameSync)(tmp, path);
|
|
15053
15298
|
} catch {
|
|
15054
15299
|
try {
|
|
15055
|
-
(0,
|
|
15300
|
+
(0, import_node_fs40.unlinkSync)(tmp);
|
|
15056
15301
|
} catch {
|
|
15057
15302
|
}
|
|
15058
15303
|
}
|
|
15059
15304
|
}
|
|
15060
15305
|
function readTurnLive(threadId) {
|
|
15061
15306
|
const path = threadLivePath(threadId);
|
|
15062
|
-
if (!(0,
|
|
15307
|
+
if (!(0, import_node_fs40.existsSync)(path)) return null;
|
|
15063
15308
|
try {
|
|
15064
|
-
const raw = JSON.parse((0,
|
|
15309
|
+
const raw = JSON.parse((0, import_node_fs40.readFileSync)(path, "utf8"));
|
|
15065
15310
|
if (!raw || typeof raw.summary !== "string") return null;
|
|
15066
15311
|
return raw;
|
|
15067
15312
|
} catch {
|
|
@@ -15073,17 +15318,17 @@ function clearTurnLive(threadId) {
|
|
|
15073
15318
|
if (buf?.timer) clearTimeout(buf.timer);
|
|
15074
15319
|
buffers.delete(threadId);
|
|
15075
15320
|
const path = threadLivePath(threadId);
|
|
15076
|
-
if (!(0,
|
|
15321
|
+
if (!(0, import_node_fs40.existsSync)(path)) return;
|
|
15077
15322
|
try {
|
|
15078
|
-
(0,
|
|
15323
|
+
(0, import_node_fs40.unlinkSync)(path);
|
|
15079
15324
|
} catch {
|
|
15080
15325
|
}
|
|
15081
15326
|
}
|
|
15082
|
-
var
|
|
15327
|
+
var import_node_fs40, buffers, FLUSH_MS, MAX_PARTS;
|
|
15083
15328
|
var init_turn_live = __esm({
|
|
15084
15329
|
"src/store/turn-live.ts"() {
|
|
15085
15330
|
"use strict";
|
|
15086
|
-
|
|
15331
|
+
import_node_fs40 = require("fs");
|
|
15087
15332
|
init_message_parts();
|
|
15088
15333
|
init_paths();
|
|
15089
15334
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -15232,8 +15477,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
15232
15477
|
);
|
|
15233
15478
|
const recent = from.messages.slice(-8).map((m) => {
|
|
15234
15479
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
15235
|
-
const
|
|
15236
|
-
return
|
|
15480
|
+
const text7 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
15481
|
+
return text7 ? `- ${role}: ${text7}` : null;
|
|
15237
15482
|
}).filter(Boolean);
|
|
15238
15483
|
const body = [
|
|
15239
15484
|
`# Orchestration handoff`,
|
|
@@ -15300,7 +15545,7 @@ var init_quota_failover = __esm({
|
|
|
15300
15545
|
// src/threads/adopt.ts
|
|
15301
15546
|
function thisModuleFile() {
|
|
15302
15547
|
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
15303
|
-
return cjsFile || process.argv[1] || (0,
|
|
15548
|
+
return cjsFile || process.argv[1] || (0, import_node_path39.join)(process.cwd(), "package.json");
|
|
15304
15549
|
}
|
|
15305
15550
|
function openReadonlySqlite(file) {
|
|
15306
15551
|
const req = (0, import_node_module4.createRequire)(thisModuleFile());
|
|
@@ -15318,25 +15563,25 @@ function mapAgentType(raw) {
|
|
|
15318
15563
|
return null;
|
|
15319
15564
|
}
|
|
15320
15565
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
15321
|
-
if (!workspacePath || !(0,
|
|
15566
|
+
if (!workspacePath || !(0, import_node_fs41.existsSync)(CURSOR_SDK_STORE)) return null;
|
|
15322
15567
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
15323
15568
|
let best = null;
|
|
15324
15569
|
let hashes;
|
|
15325
15570
|
try {
|
|
15326
|
-
hashes = (0,
|
|
15571
|
+
hashes = (0, import_node_fs41.readdirSync)(CURSOR_SDK_STORE);
|
|
15327
15572
|
} catch {
|
|
15328
15573
|
return null;
|
|
15329
15574
|
}
|
|
15330
15575
|
for (const hash of hashes) {
|
|
15331
|
-
const agentsFile = (0,
|
|
15332
|
-
if (!(0,
|
|
15333
|
-
let
|
|
15576
|
+
const agentsFile = (0, import_node_path39.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
15577
|
+
if (!(0, import_node_fs41.existsSync)(agentsFile)) continue;
|
|
15578
|
+
let text7;
|
|
15334
15579
|
try {
|
|
15335
|
-
|
|
15580
|
+
text7 = (0, import_node_fs41.readFileSync)(agentsFile, "utf8");
|
|
15336
15581
|
} catch {
|
|
15337
15582
|
continue;
|
|
15338
15583
|
}
|
|
15339
|
-
for (const line of
|
|
15584
|
+
for (const line of text7.split("\n")) {
|
|
15340
15585
|
const trimmed = line.trim();
|
|
15341
15586
|
if (!trimmed) continue;
|
|
15342
15587
|
try {
|
|
@@ -15356,7 +15601,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
15356
15601
|
return best?.agentId ?? null;
|
|
15357
15602
|
}
|
|
15358
15603
|
async function adoptThread(input) {
|
|
15359
|
-
if (!(0,
|
|
15604
|
+
if (!(0, import_node_fs41.existsSync)(input.worktreePath)) {
|
|
15360
15605
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
15361
15606
|
}
|
|
15362
15607
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -15382,18 +15627,18 @@ async function adoptThread(input) {
|
|
|
15382
15627
|
return thread;
|
|
15383
15628
|
}
|
|
15384
15629
|
function listConductorWorkspaces() {
|
|
15385
|
-
if (!(0,
|
|
15630
|
+
if (!(0, import_node_fs41.existsSync)(CONDUCTOR_DB)) {
|
|
15386
15631
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
15387
15632
|
}
|
|
15388
|
-
const tmp = (0,
|
|
15389
|
-
const snapshot = (0,
|
|
15633
|
+
const tmp = (0, import_node_fs41.mkdtempSync)((0, import_node_path39.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
|
|
15634
|
+
const snapshot = (0, import_node_path39.join)(tmp, "conductor.db");
|
|
15390
15635
|
try {
|
|
15391
|
-
(0,
|
|
15636
|
+
(0, import_node_fs41.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
15392
15637
|
for (const suffix of ["-wal", "-shm"]) {
|
|
15393
15638
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
15394
|
-
if ((0,
|
|
15639
|
+
if ((0, import_node_fs41.existsSync)(src)) {
|
|
15395
15640
|
try {
|
|
15396
|
-
(0,
|
|
15641
|
+
(0, import_node_fs41.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
15397
15642
|
} catch {
|
|
15398
15643
|
}
|
|
15399
15644
|
}
|
|
@@ -15469,22 +15714,22 @@ function listConductorWorkspaces() {
|
|
|
15469
15714
|
db.close();
|
|
15470
15715
|
}
|
|
15471
15716
|
} finally {
|
|
15472
|
-
(0,
|
|
15717
|
+
(0, import_node_fs41.rmSync)(tmp, { recursive: true, force: true });
|
|
15473
15718
|
}
|
|
15474
15719
|
}
|
|
15475
15720
|
function importConductorWorkspace(workspaceId) {
|
|
15476
|
-
if (!(0,
|
|
15721
|
+
if (!(0, import_node_fs41.existsSync)(CONDUCTOR_DB)) {
|
|
15477
15722
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
15478
15723
|
}
|
|
15479
|
-
const tmp = (0,
|
|
15480
|
-
const snapshot = (0,
|
|
15724
|
+
const tmp = (0, import_node_fs41.mkdtempSync)((0, import_node_path39.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
|
|
15725
|
+
const snapshot = (0, import_node_path39.join)(tmp, "conductor.db");
|
|
15481
15726
|
try {
|
|
15482
|
-
(0,
|
|
15727
|
+
(0, import_node_fs41.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
15483
15728
|
for (const suffix of ["-wal", "-shm"]) {
|
|
15484
15729
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
15485
|
-
if ((0,
|
|
15730
|
+
if ((0, import_node_fs41.existsSync)(src)) {
|
|
15486
15731
|
try {
|
|
15487
|
-
(0,
|
|
15732
|
+
(0, import_node_fs41.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
15488
15733
|
} catch {
|
|
15489
15734
|
}
|
|
15490
15735
|
}
|
|
@@ -15502,7 +15747,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
15502
15747
|
).get(workspaceId);
|
|
15503
15748
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
15504
15749
|
const worktreePath = String(row.workspacePath);
|
|
15505
|
-
if (!(0,
|
|
15750
|
+
if (!(0, import_node_fs41.existsSync)(worktreePath)) {
|
|
15506
15751
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
15507
15752
|
}
|
|
15508
15753
|
let sessionId = null;
|
|
@@ -15565,31 +15810,31 @@ function importConductorWorkspace(workspaceId) {
|
|
|
15565
15810
|
db.close();
|
|
15566
15811
|
}
|
|
15567
15812
|
} finally {
|
|
15568
|
-
(0,
|
|
15813
|
+
(0, import_node_fs41.rmSync)(tmp, { recursive: true, force: true });
|
|
15569
15814
|
}
|
|
15570
15815
|
}
|
|
15571
15816
|
async function importConductorWorkspaceAsync(workspaceId) {
|
|
15572
15817
|
return importConductorWorkspace(workspaceId);
|
|
15573
15818
|
}
|
|
15574
|
-
var import_node_child_process3,
|
|
15819
|
+
var import_node_child_process3, import_node_fs41, import_node_os13, import_node_path39, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
|
|
15575
15820
|
var init_adopt = __esm({
|
|
15576
15821
|
"src/threads/adopt.ts"() {
|
|
15577
15822
|
"use strict";
|
|
15578
15823
|
import_node_child_process3 = require("child_process");
|
|
15579
|
-
|
|
15824
|
+
import_node_fs41 = require("fs");
|
|
15580
15825
|
import_node_os13 = require("os");
|
|
15581
|
-
|
|
15826
|
+
import_node_path39 = require("path");
|
|
15582
15827
|
import_node_module4 = require("module");
|
|
15583
15828
|
init_worktree();
|
|
15584
15829
|
init_thread_store();
|
|
15585
|
-
CONDUCTOR_APP_SUPPORT = (0,
|
|
15830
|
+
CONDUCTOR_APP_SUPPORT = (0, import_node_path39.join)(
|
|
15586
15831
|
process.env.HOME ?? "",
|
|
15587
15832
|
"Library",
|
|
15588
15833
|
"Application Support",
|
|
15589
15834
|
"com.conductor.app"
|
|
15590
15835
|
);
|
|
15591
|
-
CONDUCTOR_DB = (0,
|
|
15592
|
-
CURSOR_SDK_STORE = (0,
|
|
15836
|
+
CONDUCTOR_DB = (0, import_node_path39.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
15837
|
+
CURSOR_SDK_STORE = (0, import_node_path39.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
15593
15838
|
}
|
|
15594
15839
|
});
|
|
15595
15840
|
|
|
@@ -15656,7 +15901,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
15656
15901
|
let createdWorktree = false;
|
|
15657
15902
|
const trees = await listWorktrees(repoPath);
|
|
15658
15903
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
15659
|
-
if (checkedOut?.path && (0,
|
|
15904
|
+
if (checkedOut?.path && (0, import_node_fs42.existsSync)(checkedOut.path)) {
|
|
15660
15905
|
if (input.reuseExistingWorktree !== false) {
|
|
15661
15906
|
worktreePath = checkedOut.path;
|
|
15662
15907
|
} else {
|
|
@@ -15798,7 +16043,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
15798
16043
|
async function createPrStack(input, onSetupLine) {
|
|
15799
16044
|
await requireAgent(input.agent);
|
|
15800
16045
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
15801
|
-
if (!(0,
|
|
16046
|
+
if (!(0, import_node_fs42.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
15802
16047
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
15803
16048
|
const status = await detectGhStack(repoPath);
|
|
15804
16049
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -15865,7 +16110,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
15865
16110
|
}
|
|
15866
16111
|
}
|
|
15867
16112
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
15868
|
-
if (!claimed.has(bootstrap.worktreePath) && (0,
|
|
16113
|
+
if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs42.existsSync)(bootstrap.worktreePath)) {
|
|
15869
16114
|
try {
|
|
15870
16115
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
15871
16116
|
deleteBranch: bootstrap.branchName
|
|
@@ -15875,11 +16120,11 @@ async function createPrStack(input, onSetupLine) {
|
|
|
15875
16120
|
}
|
|
15876
16121
|
return { stack, threads, createdThreadIds };
|
|
15877
16122
|
}
|
|
15878
|
-
var
|
|
16123
|
+
var import_node_fs42;
|
|
15879
16124
|
var init_stack_layers = __esm({
|
|
15880
16125
|
"src/threads/stack-layers.ts"() {
|
|
15881
16126
|
"use strict";
|
|
15882
|
-
|
|
16127
|
+
import_node_fs42 = require("fs");
|
|
15883
16128
|
init_detect();
|
|
15884
16129
|
init_run();
|
|
15885
16130
|
init_stack();
|
|
@@ -15892,7 +16137,7 @@ var init_stack_layers = __esm({
|
|
|
15892
16137
|
|
|
15893
16138
|
// src/diff/diff.ts
|
|
15894
16139
|
async function inspectGitWorktree(worktreePath) {
|
|
15895
|
-
if (!worktreePath || !(0,
|
|
16140
|
+
if (!worktreePath || !(0, import_node_fs43.existsSync)(worktreePath)) return "missing_worktree";
|
|
15896
16141
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
15897
16142
|
reject: false
|
|
15898
16143
|
});
|
|
@@ -15900,7 +16145,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
15900
16145
|
return "ok";
|
|
15901
16146
|
}
|
|
15902
16147
|
async function initializeGitRepository(worktreePath) {
|
|
15903
|
-
if (!worktreePath || !(0,
|
|
16148
|
+
if (!worktreePath || !(0, import_node_fs43.existsSync)(worktreePath)) {
|
|
15904
16149
|
throw new Error("Worktree not found");
|
|
15905
16150
|
}
|
|
15906
16151
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -16034,11 +16279,11 @@ new file mode 100644
|
|
|
16034
16279
|
};
|
|
16035
16280
|
}
|
|
16036
16281
|
async function untrackedPatch(worktreePath, path, maxHunk) {
|
|
16037
|
-
const abs = (0,
|
|
16282
|
+
const abs = (0, import_node_path40.join)(worktreePath, path);
|
|
16038
16283
|
try {
|
|
16039
|
-
const st = (0,
|
|
16284
|
+
const st = (0, import_node_fs43.statSync)(abs);
|
|
16040
16285
|
if (st.isFile() && st.size > maxHunk) {
|
|
16041
|
-
const buf = (0,
|
|
16286
|
+
const buf = (0, import_node_fs43.readFileSync)(abs).subarray(0, maxHunk);
|
|
16042
16287
|
return syntheticAddPatch(path, buf.toString("utf8"), maxHunk);
|
|
16043
16288
|
}
|
|
16044
16289
|
} catch {
|
|
@@ -16132,13 +16377,13 @@ function formatStat(files) {
|
|
|
16132
16377
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
16133
16378
|
}
|
|
16134
16379
|
function emptyScopeStats() {
|
|
16135
|
-
const
|
|
16380
|
+
const z8 = emptyStat();
|
|
16136
16381
|
return {
|
|
16137
|
-
commits:
|
|
16138
|
-
uncommitted:
|
|
16139
|
-
staged:
|
|
16140
|
-
unstaged:
|
|
16141
|
-
last_turn:
|
|
16382
|
+
commits: z8,
|
|
16383
|
+
uncommitted: z8,
|
|
16384
|
+
staged: z8,
|
|
16385
|
+
unstaged: z8,
|
|
16386
|
+
last_turn: z8
|
|
16142
16387
|
};
|
|
16143
16388
|
}
|
|
16144
16389
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -16527,8 +16772,8 @@ function isImageRelativePath(relativePath) {
|
|
|
16527
16772
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
16528
16773
|
assertSafeRelativePath(relativePath);
|
|
16529
16774
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
16530
|
-
const abs = (0,
|
|
16531
|
-
const st = (0,
|
|
16775
|
+
const abs = (0, import_node_path40.join)(worktreePath, relativePath);
|
|
16776
|
+
const st = (0, import_node_fs43.statSync)(abs);
|
|
16532
16777
|
if (!st.isFile()) {
|
|
16533
16778
|
throw new Error(`Not a file: ${relativePath}`);
|
|
16534
16779
|
}
|
|
@@ -16537,7 +16782,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
16537
16782
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
16538
16783
|
);
|
|
16539
16784
|
}
|
|
16540
|
-
const buf = (0,
|
|
16785
|
+
const buf = (0, import_node_fs43.readFileSync)(abs);
|
|
16541
16786
|
return {
|
|
16542
16787
|
path: relativePath,
|
|
16543
16788
|
contentBase64: buf.toString("base64"),
|
|
@@ -16547,12 +16792,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
16547
16792
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
16548
16793
|
assertSafeRelativePath(relativePath);
|
|
16549
16794
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
16550
|
-
const abs = (0,
|
|
16551
|
-
const st = (0,
|
|
16795
|
+
const abs = (0, import_node_path40.join)(worktreePath, relativePath);
|
|
16796
|
+
const st = (0, import_node_fs43.statSync)(abs);
|
|
16552
16797
|
if (!st.isFile()) {
|
|
16553
16798
|
throw new Error(`Not a file: ${relativePath}`);
|
|
16554
16799
|
}
|
|
16555
|
-
const buf = (0,
|
|
16800
|
+
const buf = (0, import_node_fs43.readFileSync)(abs);
|
|
16556
16801
|
if (isImageRelativePath(relativePath)) {
|
|
16557
16802
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
16558
16803
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -16595,9 +16840,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
16595
16840
|
}
|
|
16596
16841
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
16597
16842
|
assertSafeRelativePath(relativePath);
|
|
16598
|
-
const abs = (0,
|
|
16599
|
-
(0,
|
|
16600
|
-
(0,
|
|
16843
|
+
const abs = (0, import_node_path40.join)(worktreePath, relativePath);
|
|
16844
|
+
(0, import_node_fs43.mkdirSync)((0, import_node_path40.dirname)(abs), { recursive: true });
|
|
16845
|
+
(0, import_node_fs43.writeFileSync)(abs, content, "utf8");
|
|
16601
16846
|
return { path: relativePath };
|
|
16602
16847
|
}
|
|
16603
16848
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -16614,12 +16859,12 @@ async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
|
16614
16859
|
truncated: full.files.length > maxFiles
|
|
16615
16860
|
};
|
|
16616
16861
|
}
|
|
16617
|
-
var
|
|
16862
|
+
var import_node_fs43, import_node_path40, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
|
|
16618
16863
|
var init_diff = __esm({
|
|
16619
16864
|
"src/diff/diff.ts"() {
|
|
16620
16865
|
"use strict";
|
|
16621
|
-
|
|
16622
|
-
|
|
16866
|
+
import_node_fs43 = require("fs");
|
|
16867
|
+
import_node_path40 = require("path");
|
|
16623
16868
|
init_run();
|
|
16624
16869
|
init_worktree();
|
|
16625
16870
|
mergeBaseCache = /* @__PURE__ */ new Map();
|
|
@@ -16768,7 +17013,7 @@ var init_long_running = __esm({
|
|
|
16768
17013
|
|
|
16769
17014
|
A Sideboard or Cursor **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 \u2014 the child stays in that group.
|
|
16770
17015
|
|
|
16771
|
-
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as
|
|
17016
|
+
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (MCP \`wait_for_job\`, same idea as \`wait_for_turn\`) until \`stillRunning\` is false. Do not end the turn with \u201CI\u2019ll let you know.\u201D
|
|
16772
17017
|
|
|
16773
17018
|
## Tool
|
|
16774
17019
|
|
|
@@ -16778,7 +17023,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
|
|
|
16778
17023
|
# Start (exits in ~1s; job survives this turn)
|
|
16779
17024
|
node <detached-job.js> start <id> -- <command> [args...]
|
|
16780
17025
|
|
|
16781
|
-
# Wait \u2014
|
|
17026
|
+
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
17027
|
+
# Shell fallback:
|
|
16782
17028
|
node <detached-job.js> wait <id>
|
|
16783
17029
|
|
|
16784
17030
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
@@ -16791,7 +17037,7 @@ node <detached-job.js> status <id>
|
|
|
16791
17037
|
|
|
16792
17038
|
Wait JSON:
|
|
16793
17039
|
|
|
16794
|
-
- \`stillRunning: true\` \u2192
|
|
17040
|
+
- \`stillRunning: true\` \u2192 **call wait_for_job again** (or shell wait). Progress is in \`progress\` / \`phase\`. Do not start a second job. Do not ping the user.
|
|
16795
17041
|
- \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
|
|
16796
17042
|
- \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
|
|
16797
17043
|
|
|
@@ -16821,7 +17067,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
16821
17067
|
|
|
16822
17068
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
16823
17069
|
2. Immediately \`present_artifact\` \`type=log\` (same \`artifact_id\`, \`status=running\`) \u2014 the human should see **working** in the side column, not a \u201Ccheck back later\u201D message.
|
|
16824
|
-
3. Loop \`
|
|
17070
|
+
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
16825
17071
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
16826
17072
|
|
|
16827
17073
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
|
|
@@ -16887,7 +17133,7 @@ function parseFrontmatter(content) {
|
|
|
16887
17133
|
}
|
|
16888
17134
|
function readSkill(skillMd, source) {
|
|
16889
17135
|
try {
|
|
16890
|
-
const content = (0,
|
|
17136
|
+
const content = (0, import_node_fs44.readFileSync)(skillMd, "utf8");
|
|
16891
17137
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
16892
17138
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
16893
17139
|
const name = fmName || dirName;
|
|
@@ -16906,19 +17152,19 @@ function readSkill(skillMd, source) {
|
|
|
16906
17152
|
}
|
|
16907
17153
|
}
|
|
16908
17154
|
function scanSkillsDir(dir, source, out) {
|
|
16909
|
-
if (!(0,
|
|
17155
|
+
if (!(0, import_node_fs44.existsSync)(dir)) return;
|
|
16910
17156
|
let entries;
|
|
16911
17157
|
try {
|
|
16912
|
-
entries = (0,
|
|
17158
|
+
entries = (0, import_node_fs44.readdirSync)(dir);
|
|
16913
17159
|
} catch {
|
|
16914
17160
|
return;
|
|
16915
17161
|
}
|
|
16916
17162
|
for (const entry of entries) {
|
|
16917
17163
|
if (entry.startsWith(".")) continue;
|
|
16918
|
-
const skillMd = (0,
|
|
16919
|
-
if (!(0,
|
|
17164
|
+
const skillMd = (0, import_node_path41.join)(dir, entry, "SKILL.md");
|
|
17165
|
+
if (!(0, import_node_fs44.existsSync)(skillMd)) continue;
|
|
16920
17166
|
try {
|
|
16921
|
-
if (!(0,
|
|
17167
|
+
if (!(0, import_node_fs44.statSync)(skillMd).isFile()) continue;
|
|
16922
17168
|
} catch {
|
|
16923
17169
|
continue;
|
|
16924
17170
|
}
|
|
@@ -16927,24 +17173,24 @@ function scanSkillsDir(dir, source, out) {
|
|
|
16927
17173
|
}
|
|
16928
17174
|
}
|
|
16929
17175
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
16930
|
-
if (!(0,
|
|
17176
|
+
if (!(0, import_node_fs44.existsSync)(pluginsRoot)) return;
|
|
16931
17177
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
16932
17178
|
if (depth > 7) return;
|
|
16933
17179
|
let entries;
|
|
16934
17180
|
try {
|
|
16935
|
-
entries = (0,
|
|
17181
|
+
entries = (0, import_node_fs44.readdirSync)(dir);
|
|
16936
17182
|
} catch {
|
|
16937
17183
|
return;
|
|
16938
17184
|
}
|
|
16939
17185
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
16940
|
-
const skill = readSkill((0,
|
|
17186
|
+
const skill = readSkill((0, import_node_path41.join)(dir, "SKILL.md"), "cli");
|
|
16941
17187
|
if (skill) out.push(skill);
|
|
16942
17188
|
}
|
|
16943
17189
|
for (const entry of entries) {
|
|
16944
17190
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
16945
|
-
const full = (0,
|
|
17191
|
+
const full = (0, import_node_path41.join)(dir, entry);
|
|
16946
17192
|
try {
|
|
16947
|
-
if (!(0,
|
|
17193
|
+
if (!(0, import_node_fs44.statSync)(full).isDirectory()) continue;
|
|
16948
17194
|
} catch {
|
|
16949
17195
|
continue;
|
|
16950
17196
|
}
|
|
@@ -16962,17 +17208,17 @@ function discoverSkills(worktreePath) {
|
|
|
16962
17208
|
const home = (0, import_node_os14.homedir)();
|
|
16963
17209
|
const collected = [];
|
|
16964
17210
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
16965
|
-
scanSkillsDir((0,
|
|
17211
|
+
scanSkillsDir((0, import_node_path41.join)(worktreePath, rel), "workspace", collected);
|
|
16966
17212
|
}
|
|
16967
17213
|
for (const abs of [
|
|
16968
|
-
(0,
|
|
16969
|
-
(0,
|
|
16970
|
-
(0,
|
|
16971
|
-
(0,
|
|
17214
|
+
(0, import_node_path41.join)(home, ".claude/skills"),
|
|
17215
|
+
(0, import_node_path41.join)(home, ".cursor/skills"),
|
|
17216
|
+
(0, import_node_path41.join)(home, ".sideboard/skills"),
|
|
17217
|
+
(0, import_node_path41.join)(home, ".brightsy/skills")
|
|
16972
17218
|
]) {
|
|
16973
17219
|
scanSkillsDir(abs, "user", collected);
|
|
16974
17220
|
}
|
|
16975
|
-
scanClaudePluginSkills((0,
|
|
17221
|
+
scanClaudePluginSkills((0, import_node_path41.join)(home, ".claude/plugins"), collected);
|
|
16976
17222
|
collected.push(...bundledSkills());
|
|
16977
17223
|
const rank = {
|
|
16978
17224
|
workspace: 0,
|
|
@@ -16998,7 +17244,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
16998
17244
|
\u2026(truncated)` : body;
|
|
16999
17245
|
}
|
|
17000
17246
|
}
|
|
17001
|
-
const raw = (0,
|
|
17247
|
+
const raw = (0, import_node_fs44.readFileSync)(skillPath, "utf8");
|
|
17002
17248
|
if (raw.startsWith("---")) {
|
|
17003
17249
|
const end = raw.indexOf("\n---", 3);
|
|
17004
17250
|
if (end >= 0) {
|
|
@@ -17012,13 +17258,13 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
17012
17258
|
|
|
17013
17259
|
\u2026(truncated)` : raw;
|
|
17014
17260
|
}
|
|
17015
|
-
var
|
|
17261
|
+
var import_node_fs44, import_node_os14, import_node_path41, BUNDLED_SKILL_PREFIX;
|
|
17016
17262
|
var init_discover = __esm({
|
|
17017
17263
|
"src/skills/discover.ts"() {
|
|
17018
17264
|
"use strict";
|
|
17019
|
-
|
|
17265
|
+
import_node_fs44 = require("fs");
|
|
17020
17266
|
import_node_os14 = require("os");
|
|
17021
|
-
|
|
17267
|
+
import_node_path41 = require("path");
|
|
17022
17268
|
init_long_running();
|
|
17023
17269
|
BUNDLED_SKILL_PREFIX = "bundled:";
|
|
17024
17270
|
}
|
|
@@ -17113,17 +17359,17 @@ var init_expand = __esm({
|
|
|
17113
17359
|
function packagedDetachedJobPath() {
|
|
17114
17360
|
const dir = packagedMcpDir();
|
|
17115
17361
|
if (!dir) return null;
|
|
17116
|
-
const script = (0,
|
|
17117
|
-
return (0,
|
|
17362
|
+
const script = (0, import_node_path42.join)(dir, "scripts", "detached-job.js");
|
|
17363
|
+
return (0, import_node_fs45.existsSync)(script) ? script : null;
|
|
17118
17364
|
}
|
|
17119
17365
|
function resolveDetachedJobScript() {
|
|
17120
17366
|
const packaged = packagedDetachedJobPath();
|
|
17121
17367
|
if (packaged) return packaged;
|
|
17122
|
-
let dir = (0,
|
|
17368
|
+
let dir = (0, import_node_path42.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
|
|
17123
17369
|
for (let i = 0; i < 8; i++) {
|
|
17124
|
-
const candidate = (0,
|
|
17125
|
-
if ((0,
|
|
17126
|
-
const parent = (0,
|
|
17370
|
+
const candidate = (0, import_node_path42.join)(dir, "scripts", "detached-job.js");
|
|
17371
|
+
if ((0, import_node_fs45.existsSync)(candidate)) return candidate;
|
|
17372
|
+
const parent = (0, import_node_path42.dirname)(dir);
|
|
17127
17373
|
if (parent === dir) break;
|
|
17128
17374
|
dir = parent;
|
|
17129
17375
|
}
|
|
@@ -17134,12 +17380,12 @@ function formatDetachedJobInvoke(scriptPath) {
|
|
|
17134
17380
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
17135
17381
|
return "node scripts/detached-job.js";
|
|
17136
17382
|
}
|
|
17137
|
-
var
|
|
17383
|
+
var import_node_fs45, import_node_path42, import_node_url3, import_meta3;
|
|
17138
17384
|
var init_detached_job_path = __esm({
|
|
17139
17385
|
"src/skills/detached-job-path.ts"() {
|
|
17140
17386
|
"use strict";
|
|
17141
|
-
|
|
17142
|
-
|
|
17387
|
+
import_node_fs45 = require("fs");
|
|
17388
|
+
import_node_path42 = require("path");
|
|
17143
17389
|
import_node_url3 = require("url");
|
|
17144
17390
|
init_packaged_runtime();
|
|
17145
17391
|
import_meta3 = {};
|
|
@@ -17338,14 +17584,15 @@ function formatLongRunningDirective(opts) {
|
|
|
17338
17584
|
`Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
|
|
17339
17585
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
17340
17586
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
17341
|
-
`- Loop
|
|
17587
|
+
`- Loop Sideboard MCP \`wait_for_job\` with the same id (returns in ~45s). stillRunning \u2192 present the same id with \`content=delta\` only \u2192 wait_for_job again. Shell fallback: \`${invoke} wait <id>\`. Do not resend the full log or HTML.`,
|
|
17588
|
+
"- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
|
|
17342
17589
|
"- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
|
|
17343
17590
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
17344
17591
|
].join("\n");
|
|
17345
17592
|
}
|
|
17346
17593
|
function formatLongRunningReminder(opts) {
|
|
17347
17594
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
17348
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not
|
|
17595
|
+
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait_for_job (or detached-job wait), present_artifact type=log (delta). Do not say you will let the user know later \u2014 stay in the turn.`;
|
|
17349
17596
|
}
|
|
17350
17597
|
function formatArtifactDirective() {
|
|
17351
17598
|
return [
|
|
@@ -17375,12 +17622,12 @@ function formatArtifactDirective() {
|
|
|
17375
17622
|
function formatUiReminder() {
|
|
17376
17623
|
return "Sideboard UI: markdown table is enough to read data; present_schema if they ask to edit/filter (even after markdown); present_files for the file manager. html fence or present_artifact, not both for the same document. type=log appends (same artifact_id, new lines only). ask_user only for a real multiple-choice (not hellos or \u201Cwhat next?\u201D) \u2014 reply in chat. Do not say artifacts/CMS UI are unavailable.";
|
|
17377
17624
|
}
|
|
17378
|
-
var
|
|
17625
|
+
var import_node_fs46, import_node_path43, GITHUB_TICKET_REF, KEYED_TICKET_REF;
|
|
17379
17626
|
var init_instructions = __esm({
|
|
17380
17627
|
"src/agents/instructions.ts"() {
|
|
17381
17628
|
"use strict";
|
|
17382
|
-
|
|
17383
|
-
|
|
17629
|
+
import_node_fs46 = require("fs");
|
|
17630
|
+
import_node_path43 = require("path");
|
|
17384
17631
|
init_git_auth_mode();
|
|
17385
17632
|
init_worktree_labels();
|
|
17386
17633
|
init_detached_job_path();
|
|
@@ -17516,11 +17763,11 @@ function resolvePlanMarkdown(opts) {
|
|
|
17516
17763
|
source: "exit_plan"
|
|
17517
17764
|
};
|
|
17518
17765
|
}
|
|
17519
|
-
const
|
|
17520
|
-
if (
|
|
17766
|
+
const text7 = opts.text?.trim();
|
|
17767
|
+
if (text7 && text7.length >= 80) {
|
|
17521
17768
|
return {
|
|
17522
17769
|
title: "Plan",
|
|
17523
|
-
content:
|
|
17770
|
+
content: text7,
|
|
17524
17771
|
path: PLAN_FILE_REL,
|
|
17525
17772
|
source: "text"
|
|
17526
17773
|
};
|
|
@@ -17552,40 +17799,40 @@ __export(plan_file_exports, {
|
|
|
17552
17799
|
writePlanFile: () => writePlanFile
|
|
17553
17800
|
});
|
|
17554
17801
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
17555
|
-
const gitignoreAbs = (0,
|
|
17556
|
-
if ((0,
|
|
17557
|
-
(0,
|
|
17558
|
-
(0,
|
|
17802
|
+
const gitignoreAbs = (0, import_node_path44.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
17803
|
+
if ((0, import_node_fs47.existsSync)(gitignoreAbs)) return;
|
|
17804
|
+
(0, import_node_fs47.mkdirSync)((0, import_node_path44.dirname)(gitignoreAbs), { recursive: true });
|
|
17805
|
+
(0, import_node_fs47.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
17559
17806
|
}
|
|
17560
17807
|
function planFileAbs(worktreePath) {
|
|
17561
|
-
return (0,
|
|
17808
|
+
return (0, import_node_path44.join)(worktreePath, PLAN_FILE_REL);
|
|
17562
17809
|
}
|
|
17563
17810
|
function readTextIfPresent2(abs) {
|
|
17564
|
-
if (!(0,
|
|
17811
|
+
if (!(0, import_node_fs47.existsSync)(abs)) return null;
|
|
17565
17812
|
try {
|
|
17566
|
-
const content = (0,
|
|
17813
|
+
const content = (0, import_node_fs47.readFileSync)(abs, "utf8");
|
|
17567
17814
|
return content.trim() ? content : null;
|
|
17568
17815
|
} catch {
|
|
17569
17816
|
return null;
|
|
17570
17817
|
}
|
|
17571
17818
|
}
|
|
17572
17819
|
function readPlanFile(worktreePath) {
|
|
17573
|
-
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0,
|
|
17820
|
+
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path44.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path44.join)(worktreePath, LEGACY_PLAN_FILE_REL));
|
|
17574
17821
|
}
|
|
17575
17822
|
function writePlanFile(worktreePath, content) {
|
|
17576
17823
|
ensureAttachmentsGitignore(worktreePath);
|
|
17577
17824
|
const abs = planFileAbs(worktreePath);
|
|
17578
|
-
(0,
|
|
17825
|
+
(0, import_node_fs47.mkdirSync)((0, import_node_path44.dirname)(abs), { recursive: true });
|
|
17579
17826
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
17580
|
-
(0,
|
|
17827
|
+
(0, import_node_fs47.writeFileSync)(abs, body, "utf8");
|
|
17581
17828
|
return PLAN_FILE_REL;
|
|
17582
17829
|
}
|
|
17583
|
-
var
|
|
17830
|
+
var import_node_fs47, import_node_path44;
|
|
17584
17831
|
var init_plan_file = __esm({
|
|
17585
17832
|
"src/plan/plan-file.ts"() {
|
|
17586
17833
|
"use strict";
|
|
17587
|
-
|
|
17588
|
-
|
|
17834
|
+
import_node_fs47 = require("fs");
|
|
17835
|
+
import_node_path44 = require("path");
|
|
17589
17836
|
init_workspace_scratch();
|
|
17590
17837
|
init_plan_present();
|
|
17591
17838
|
init_plan_present();
|
|
@@ -17645,7 +17892,7 @@ function setCaffeinateHoldHooks(next) {
|
|
|
17645
17892
|
hooks = next;
|
|
17646
17893
|
}
|
|
17647
17894
|
function caffeinateHoldPath() {
|
|
17648
|
-
return (0,
|
|
17895
|
+
return (0, import_node_path45.join)(appDataDir(), "caffeinate-hold.json");
|
|
17649
17896
|
}
|
|
17650
17897
|
function processAlive(pid) {
|
|
17651
17898
|
if (hooks.processAlive) return hooks.processAlive(pid);
|
|
@@ -17679,9 +17926,9 @@ function uniqueIds(ids) {
|
|
|
17679
17926
|
}
|
|
17680
17927
|
function readHold() {
|
|
17681
17928
|
const path = caffeinateHoldPath();
|
|
17682
|
-
if (!(0,
|
|
17929
|
+
if (!(0, import_node_fs48.existsSync)(path)) return null;
|
|
17683
17930
|
try {
|
|
17684
|
-
const parsed = JSON.parse((0,
|
|
17931
|
+
const parsed = JSON.parse((0, import_node_fs48.readFileSync)(path, "utf8"));
|
|
17685
17932
|
if (typeof parsed?.pid === "number" && parsed.pid > 0) {
|
|
17686
17933
|
return {
|
|
17687
17934
|
pid: parsed.pid,
|
|
@@ -17703,7 +17950,7 @@ function writeHold(pid, threadIds) {
|
|
|
17703
17950
|
}
|
|
17704
17951
|
function clearHold() {
|
|
17705
17952
|
try {
|
|
17706
|
-
(0,
|
|
17953
|
+
(0, import_node_fs48.unlinkSync)(caffeinateHoldPath());
|
|
17707
17954
|
} catch {
|
|
17708
17955
|
}
|
|
17709
17956
|
}
|
|
@@ -17788,13 +18035,13 @@ function releaseCaffeinateHoldForThread(threadId) {
|
|
|
17788
18035
|
}
|
|
17789
18036
|
return setCaffeinateHold(false, { threadId: id });
|
|
17790
18037
|
}
|
|
17791
|
-
var import_node_child_process4,
|
|
18038
|
+
var import_node_child_process4, import_node_fs48, import_node_path45, hooks;
|
|
17792
18039
|
var init_caffeinate_hold = __esm({
|
|
17793
18040
|
"src/store/caffeinate-hold.ts"() {
|
|
17794
18041
|
"use strict";
|
|
17795
18042
|
import_node_child_process4 = require("child_process");
|
|
17796
|
-
|
|
17797
|
-
|
|
18043
|
+
import_node_fs48 = require("fs");
|
|
18044
|
+
import_node_path45 = require("path");
|
|
17798
18045
|
init_paths();
|
|
17799
18046
|
init_private_file();
|
|
17800
18047
|
hooks = {};
|
|
@@ -17803,7 +18050,7 @@ var init_caffeinate_hold = __esm({
|
|
|
17803
18050
|
|
|
17804
18051
|
// src/store/schedules.ts
|
|
17805
18052
|
function schedulesPath() {
|
|
17806
|
-
return (0,
|
|
18053
|
+
return (0, import_node_path46.join)(appDataDir(), "schedules.json");
|
|
17807
18054
|
}
|
|
17808
18055
|
function parseDurationMs(every) {
|
|
17809
18056
|
const m = every.trim().match(DURATION_RE);
|
|
@@ -17903,9 +18150,9 @@ function normalizeTask(raw) {
|
|
|
17903
18150
|
}
|
|
17904
18151
|
function readAll2() {
|
|
17905
18152
|
const path = schedulesPath();
|
|
17906
|
-
if (!(0,
|
|
18153
|
+
if (!(0, import_node_fs49.existsSync)(path)) return [];
|
|
17907
18154
|
try {
|
|
17908
|
-
const parsed = JSON.parse((0,
|
|
18155
|
+
const parsed = JSON.parse((0, import_node_fs49.readFileSync)(path, "utf8"));
|
|
17909
18156
|
if (!Array.isArray(parsed)) return [];
|
|
17910
18157
|
return parsed.map((row) => {
|
|
17911
18158
|
try {
|
|
@@ -18017,13 +18264,13 @@ function recordScheduleRun(id, result) {
|
|
|
18017
18264
|
writeAll2(rows);
|
|
18018
18265
|
return next;
|
|
18019
18266
|
}
|
|
18020
|
-
var import_node_crypto9,
|
|
18267
|
+
var import_node_crypto9, import_node_fs49, import_node_path46, import_croner, DURATION_RE, UNIT_MS;
|
|
18021
18268
|
var init_schedules = __esm({
|
|
18022
18269
|
"src/store/schedules.ts"() {
|
|
18023
18270
|
"use strict";
|
|
18024
18271
|
import_node_crypto9 = require("crypto");
|
|
18025
|
-
|
|
18026
|
-
|
|
18272
|
+
import_node_fs49 = require("fs");
|
|
18273
|
+
import_node_path46 = require("path");
|
|
18027
18274
|
import_croner = require("croner");
|
|
18028
18275
|
init_orchestrator_capable();
|
|
18029
18276
|
init_paths();
|
|
@@ -18165,13 +18412,13 @@ var init_schedule_runner = __esm({
|
|
|
18165
18412
|
|
|
18166
18413
|
// src/agents/cursor-store.ts
|
|
18167
18414
|
function cursorSdkStoreDir(threadId) {
|
|
18168
|
-
const root = (0,
|
|
18415
|
+
const root = (0, import_node_path47.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
|
|
18169
18416
|
const id = sanitizeCursorStoreSegment(threadId);
|
|
18170
18417
|
if (!id) return root;
|
|
18171
|
-
return (0,
|
|
18418
|
+
return (0, import_node_path47.join)(root, "threads", id);
|
|
18172
18419
|
}
|
|
18173
18420
|
function cursorSdkRunsNdjsonPath(threadId) {
|
|
18174
|
-
return (0,
|
|
18421
|
+
return (0, import_node_path47.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
|
|
18175
18422
|
}
|
|
18176
18423
|
function cursorSdkRunsNdjsonSearchPaths(threadId) {
|
|
18177
18424
|
const scoped = cursorSdkRunsNdjsonPath(threadId);
|
|
@@ -18184,11 +18431,11 @@ function cursorSdkRunsNdjsonSearchPaths(threadId) {
|
|
|
18184
18431
|
function sanitizeCursorStoreSegment(threadId) {
|
|
18185
18432
|
return (threadId ?? "").trim().replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
18186
18433
|
}
|
|
18187
|
-
var
|
|
18434
|
+
var import_node_path47, CURSOR_SDK_STORE_DIR;
|
|
18188
18435
|
var init_cursor_store = __esm({
|
|
18189
18436
|
"src/agents/cursor-store.ts"() {
|
|
18190
18437
|
"use strict";
|
|
18191
|
-
|
|
18438
|
+
import_node_path47 = require("path");
|
|
18192
18439
|
init_paths();
|
|
18193
18440
|
CURSOR_SDK_STORE_DIR = "cursor-sdk-store";
|
|
18194
18441
|
}
|
|
@@ -18210,9 +18457,9 @@ function recoverFinishedCursorRun(opts) {
|
|
|
18210
18457
|
return best;
|
|
18211
18458
|
}
|
|
18212
18459
|
function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
|
|
18213
|
-
if (!(0,
|
|
18460
|
+
if (!(0, import_node_fs50.existsSync)(runsPath)) return null;
|
|
18214
18461
|
try {
|
|
18215
|
-
const lines = (0,
|
|
18462
|
+
const lines = (0, import_node_fs50.readFileSync)(runsPath, "utf8").split("\n");
|
|
18216
18463
|
let best = null;
|
|
18217
18464
|
for (const line of lines) {
|
|
18218
18465
|
const trimmed = line.trim();
|
|
@@ -18238,11 +18485,11 @@ function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
|
|
|
18238
18485
|
return null;
|
|
18239
18486
|
}
|
|
18240
18487
|
}
|
|
18241
|
-
var
|
|
18488
|
+
var import_node_fs50;
|
|
18242
18489
|
var init_cursor_recover = __esm({
|
|
18243
18490
|
"src/agents/cursor-recover.ts"() {
|
|
18244
18491
|
"use strict";
|
|
18245
|
-
|
|
18492
|
+
import_node_fs50 = require("fs");
|
|
18246
18493
|
init_cursor_store();
|
|
18247
18494
|
}
|
|
18248
18495
|
});
|
|
@@ -18374,13 +18621,13 @@ async function startOrchestration(opts) {
|
|
|
18374
18621
|
}
|
|
18375
18622
|
return updated;
|
|
18376
18623
|
}
|
|
18377
|
-
var import_node_events,
|
|
18624
|
+
var import_node_events, import_node_fs51, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
|
|
18378
18625
|
var init_orchestrator = __esm({
|
|
18379
18626
|
"src/orchestrator/orchestrator.ts"() {
|
|
18380
18627
|
"use strict";
|
|
18381
18628
|
import_node_events = require("events");
|
|
18382
18629
|
init_outbound_watch();
|
|
18383
|
-
|
|
18630
|
+
import_node_fs51 = require("fs");
|
|
18384
18631
|
init_error_detail();
|
|
18385
18632
|
init_run();
|
|
18386
18633
|
init_stale_lock();
|
|
@@ -18401,6 +18648,7 @@ var init_orchestrator = __esm({
|
|
|
18401
18648
|
init_thread_store();
|
|
18402
18649
|
init_desktop_host();
|
|
18403
18650
|
init_child_halt();
|
|
18651
|
+
init_wait_for_job();
|
|
18404
18652
|
init_create();
|
|
18405
18653
|
init_cowboy();
|
|
18406
18654
|
init_orchestrator_capable();
|
|
@@ -18465,6 +18713,9 @@ var init_orchestrator = __esm({
|
|
|
18465
18713
|
* finish or a user send so a later crash can recover again.
|
|
18466
18714
|
*/
|
|
18467
18715
|
crashContinued = /* @__PURE__ */ new Set();
|
|
18716
|
+
/** Auto-continues after a worktree turn ended while a detached job still runs. */
|
|
18717
|
+
jobContinueCount = /* @__PURE__ */ new Map();
|
|
18718
|
+
jobContinueNudged = /* @__PURE__ */ new Set();
|
|
18468
18719
|
maxConcurrent;
|
|
18469
18720
|
runningCount = 0;
|
|
18470
18721
|
constructor(opts) {
|
|
@@ -18548,7 +18799,7 @@ var init_orchestrator = __esm({
|
|
|
18548
18799
|
}
|
|
18549
18800
|
continue;
|
|
18550
18801
|
}
|
|
18551
|
-
if (!(0,
|
|
18802
|
+
if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
|
|
18552
18803
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
18553
18804
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
18554
18805
|
continue;
|
|
@@ -18763,6 +19014,37 @@ var init_orchestrator = __esm({
|
|
|
18763
19014
|
this.emit({ type: "queue_changed", threadId, queue });
|
|
18764
19015
|
this.haltDrain.delete(threadId);
|
|
18765
19016
|
}
|
|
19017
|
+
/**
|
|
19018
|
+
* Worktree agent ended the turn after “I’ll let you know” (or left a
|
|
19019
|
+
* detached test/pack job running). Queue a continue so the chat does not
|
|
19020
|
+
* go idle with nobody watching the log.
|
|
19021
|
+
*/
|
|
19022
|
+
maybeEnqueueJobContinue(threadId, chatText, parts = []) {
|
|
19023
|
+
if (this.haltDrain.has(threadId)) return;
|
|
19024
|
+
const thread = readThread(threadId);
|
|
19025
|
+
if (!thread || thread.status === "archived") return;
|
|
19026
|
+
const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
|
|
19027
|
+
const decision = planJobContinue({
|
|
19028
|
+
runningJobIds,
|
|
19029
|
+
chatText,
|
|
19030
|
+
queueLength: thread.queue.length,
|
|
19031
|
+
continueCount: this.jobContinueCount.get(threadId) ?? 0,
|
|
19032
|
+
alreadyNudged: this.jobContinueNudged.has(threadId),
|
|
19033
|
+
isOrchestrator: isOrchestratorThread(thread),
|
|
19034
|
+
agent: thread.agent,
|
|
19035
|
+
watchedJob: turnWatchedDetachedJob(parts)
|
|
19036
|
+
});
|
|
19037
|
+
if (decision.action === "none") {
|
|
19038
|
+
if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
|
|
19039
|
+
return;
|
|
19040
|
+
}
|
|
19041
|
+
if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
|
|
19042
|
+
else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
|
|
19043
|
+
const queue = [decision.prompt, ...thread.queue];
|
|
19044
|
+
updateThread(threadId, { queue });
|
|
19045
|
+
this.emit({ type: "queue_changed", threadId, queue });
|
|
19046
|
+
this.haltDrain.delete(threadId);
|
|
19047
|
+
}
|
|
18766
19048
|
getThreads(includeArchived = false) {
|
|
18767
19049
|
return listThreads({ includeArchived });
|
|
18768
19050
|
}
|
|
@@ -18862,6 +19144,8 @@ var init_orchestrator = __esm({
|
|
|
18862
19144
|
}
|
|
18863
19145
|
const queue = [...current.queue, prompt];
|
|
18864
19146
|
this.crashContinued.delete(thread.id);
|
|
19147
|
+
this.jobContinueCount.delete(thread.id);
|
|
19148
|
+
this.jobContinueNudged.delete(thread.id);
|
|
18865
19149
|
this.haltDrain.delete(thread.id);
|
|
18866
19150
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
|
|
18867
19151
|
shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
|
|
@@ -18894,11 +19178,11 @@ var init_orchestrator = __esm({
|
|
|
18894
19178
|
return results;
|
|
18895
19179
|
}
|
|
18896
19180
|
/** Edit the text of a not-yet-started queued message. */
|
|
18897
|
-
async editQueuedMessage(threadRef, index,
|
|
19181
|
+
async editQueuedMessage(threadRef, index, text7) {
|
|
18898
19182
|
const thread = this.requireThread(threadRef);
|
|
18899
19183
|
return withThreadLock(thread.id, async () => {
|
|
18900
19184
|
const current = this.requireThread(thread.id);
|
|
18901
|
-
const trimmed =
|
|
19185
|
+
const trimmed = text7.trim();
|
|
18902
19186
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
18903
19187
|
return current;
|
|
18904
19188
|
}
|
|
@@ -19084,6 +19368,7 @@ var init_orchestrator = __esm({
|
|
|
19084
19368
|
ticketId: issueTicket?.id,
|
|
19085
19369
|
ticketProvider: issueTicket?.provider
|
|
19086
19370
|
}) : null;
|
|
19371
|
+
const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
|
|
19087
19372
|
const slackReplyContext = formatSlackRepliesForTurn(
|
|
19088
19373
|
pendingSlackExternalReplies(thread.messages)
|
|
19089
19374
|
);
|
|
@@ -19093,6 +19378,7 @@ var init_orchestrator = __esm({
|
|
|
19093
19378
|
worktreeReminder,
|
|
19094
19379
|
optionalServicesReminder,
|
|
19095
19380
|
issueToolsReminder,
|
|
19381
|
+
viewerContextReminder,
|
|
19096
19382
|
artifactReminder,
|
|
19097
19383
|
longRunningReminder,
|
|
19098
19384
|
slackReplyContext,
|
|
@@ -19133,6 +19419,18 @@ var init_orchestrator = __esm({
|
|
|
19133
19419
|
ticketId: freshIssueTicket?.id,
|
|
19134
19420
|
ticketProvider: freshIssueTicket?.provider
|
|
19135
19421
|
});
|
|
19422
|
+
let viewerContextDirective = null;
|
|
19423
|
+
if (!isBrightsy && !isOrchestration) {
|
|
19424
|
+
try {
|
|
19425
|
+
viewerContextDirective = formatViewerContextDirective(
|
|
19426
|
+
resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
|
|
19427
|
+
);
|
|
19428
|
+
} catch {
|
|
19429
|
+
viewerContextDirective = formatViewerContextDirective(
|
|
19430
|
+
resolveViewerProfileForRepo()
|
|
19431
|
+
);
|
|
19432
|
+
}
|
|
19433
|
+
}
|
|
19136
19434
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
19137
19435
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
|
|
19138
19436
|
customPrompt: settings?.prompts?.renameBranch
|
|
@@ -19164,6 +19462,7 @@ var init_orchestrator = __esm({
|
|
|
19164
19462
|
worktreeDirective,
|
|
19165
19463
|
optionalServicesDirective,
|
|
19166
19464
|
issueToolsDirective,
|
|
19465
|
+
viewerContextDirective,
|
|
19167
19466
|
artifactDirective,
|
|
19168
19467
|
longRunningDirective,
|
|
19169
19468
|
renameBranchDirective,
|
|
@@ -19274,6 +19573,7 @@ var init_orchestrator = __esm({
|
|
|
19274
19573
|
worktreeDirective,
|
|
19275
19574
|
optionalServicesDirective,
|
|
19276
19575
|
issueToolsDirective,
|
|
19576
|
+
viewerContextDirective,
|
|
19277
19577
|
artifactDirective,
|
|
19278
19578
|
longRunningDirective,
|
|
19279
19579
|
renameBranchDirective,
|
|
@@ -19376,6 +19676,7 @@ var init_orchestrator = __esm({
|
|
|
19376
19676
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
19377
19677
|
if (exitCode === 0) {
|
|
19378
19678
|
this.crashContinued.delete(threadId);
|
|
19679
|
+
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
19379
19680
|
} else {
|
|
19380
19681
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
19381
19682
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
@@ -19807,13 +20108,13 @@ var init_orchestrator = __esm({
|
|
|
19807
20108
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
19808
20109
|
const lastError = thread.lastError ?? null;
|
|
19809
20110
|
const rawText = (lastAgent?.text ?? "").trim();
|
|
19810
|
-
const
|
|
20111
|
+
const text7 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
|
|
19811
20112
|
const stillRunning = this.threadLooksLive(thread);
|
|
19812
20113
|
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
19813
20114
|
const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
|
|
19814
20115
|
const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
|
|
19815
20116
|
return {
|
|
19816
|
-
text:
|
|
20117
|
+
text: text7,
|
|
19817
20118
|
status: thread.status,
|
|
19818
20119
|
sessionId: thread.sessionId,
|
|
19819
20120
|
lastError,
|
|
@@ -20376,7 +20677,7 @@ var init_orchestrator = __esm({
|
|
|
20376
20677
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
20377
20678
|
return restored2;
|
|
20378
20679
|
}
|
|
20379
|
-
if (!(0,
|
|
20680
|
+
if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
|
|
20380
20681
|
if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
|
|
20381
20682
|
throw new Error(
|
|
20382
20683
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
@@ -20457,9 +20758,9 @@ init_nested_electron_env();
|
|
|
20457
20758
|
// src/mcp/server.ts
|
|
20458
20759
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
20459
20760
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
20460
|
-
var
|
|
20761
|
+
var import_zod7 = require("zod");
|
|
20461
20762
|
var import_node_crypto12 = require("crypto");
|
|
20462
|
-
var
|
|
20763
|
+
var import_node_path50 = require("path");
|
|
20463
20764
|
init_orchestrator();
|
|
20464
20765
|
init_worktree();
|
|
20465
20766
|
|
|
@@ -21308,30 +21609,8 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
21308
21609
|
|
|
21309
21610
|
// src/mcp/server.ts
|
|
21310
21611
|
init_profile();
|
|
21311
|
-
|
|
21312
|
-
|
|
21313
|
-
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
21314
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
21315
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
21316
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
21317
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
21318
|
-
}
|
|
21319
|
-
var MCP_WAIT_STILL_RUNNING_HINT = "Child is still working. Call wait_for_turn again. Do not send a check-in prompt or assume a hang while progress is updating.";
|
|
21320
|
-
var 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 a check-in prompt, force_stop, or assume it failed to start.";
|
|
21321
|
-
function mcpWaitStillRunningHint(status) {
|
|
21322
|
-
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
21323
|
-
}
|
|
21324
|
-
var 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.";
|
|
21325
|
-
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
21326
|
-
var 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.";
|
|
21327
|
-
function mcpWaitFinishedHint(status) {
|
|
21328
|
-
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
21329
|
-
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
21330
|
-
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
21331
|
-
return void 0;
|
|
21332
|
-
}
|
|
21333
|
-
|
|
21334
|
-
// src/mcp/server.ts
|
|
21612
|
+
init_wait_for_turn();
|
|
21613
|
+
init_wait_for_job();
|
|
21335
21614
|
init_message_parts();
|
|
21336
21615
|
init_turn_live();
|
|
21337
21616
|
|
|
@@ -21340,9 +21619,9 @@ init_message_parts();
|
|
|
21340
21619
|
function lastMessagePreview(messages, max = 160) {
|
|
21341
21620
|
if (!messages?.length) return null;
|
|
21342
21621
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
21343
|
-
const
|
|
21344
|
-
if (!
|
|
21345
|
-
const flat =
|
|
21622
|
+
const text7 = messages[i]?.text?.trim();
|
|
21623
|
+
if (!text7 || isInternalAgentStatusText(text7)) continue;
|
|
21624
|
+
const flat = text7.replace(/\s+/g, " ");
|
|
21346
21625
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
21347
21626
|
}
|
|
21348
21627
|
return null;
|
|
@@ -21576,8 +21855,8 @@ function labelGithubUrl(url) {
|
|
|
21576
21855
|
}
|
|
21577
21856
|
return { kind: "other", label: "GitHub", url: raw };
|
|
21578
21857
|
}
|
|
21579
|
-
function appendGithubLink(
|
|
21580
|
-
const body =
|
|
21858
|
+
function appendGithubLink(text7, githubUrl) {
|
|
21859
|
+
const body = text7.trimEnd();
|
|
21581
21860
|
if (!githubUrl?.trim()) return body;
|
|
21582
21861
|
const labeled = labelGithubUrl(githubUrl);
|
|
21583
21862
|
if (!labeled) {
|
|
@@ -22449,6 +22728,104 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
|
|
|
22449
22728
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
22450
22729
|
}
|
|
22451
22730
|
|
|
22731
|
+
// src/mcp/viewer-context-tools.ts
|
|
22732
|
+
var import_zod5 = require("zod");
|
|
22733
|
+
init_worktree();
|
|
22734
|
+
init_app_settings();
|
|
22735
|
+
init_workspaces2();
|
|
22736
|
+
function text5(payload, isError = false) {
|
|
22737
|
+
return mcpJson(payload, isError);
|
|
22738
|
+
}
|
|
22739
|
+
function matchRegisteredWorkspace(repoPath) {
|
|
22740
|
+
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
22741
|
+
if (!hint) return null;
|
|
22742
|
+
const match = listWorkspaces().find((w) => {
|
|
22743
|
+
const stored = w.path.replace(/\/+$/, "");
|
|
22744
|
+
return stored === hint || hint.startsWith(`${stored}/`) || stored.startsWith(`${hint}/`);
|
|
22745
|
+
});
|
|
22746
|
+
return match?.path.replace(/\/+$/, "") ?? null;
|
|
22747
|
+
}
|
|
22748
|
+
async function resolveProjectRepoPath(repoPath) {
|
|
22749
|
+
const hint = repoPath?.trim() || process.cwd();
|
|
22750
|
+
let root = hint;
|
|
22751
|
+
try {
|
|
22752
|
+
root = await resolveRepoRoot(hint);
|
|
22753
|
+
} catch {
|
|
22754
|
+
root = hint.replace(/\/+$/, "");
|
|
22755
|
+
}
|
|
22756
|
+
return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
|
|
22757
|
+
}
|
|
22758
|
+
var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
|
|
22759
|
+
function registerViewerContextTools(server) {
|
|
22760
|
+
server.tool(
|
|
22761
|
+
"get_viewer_context",
|
|
22762
|
+
"Read account and project user context (Settings \u2192 Agents / Projects): roles, tickets, and review queues as freeform text. Orchestration must pass repoPath from list_workspaces for project context. Worktree turns may omit repoPath (uses cwd).",
|
|
22763
|
+
{
|
|
22764
|
+
repoPath: import_zod5.z.string().optional().describe(
|
|
22765
|
+
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
22766
|
+
)
|
|
22767
|
+
},
|
|
22768
|
+
async ({ repoPath }) => {
|
|
22769
|
+
try {
|
|
22770
|
+
const path = await resolveProjectRepoPath(repoPath);
|
|
22771
|
+
return text5(readViewerContext(path ?? void 0));
|
|
22772
|
+
} catch (err) {
|
|
22773
|
+
return text5(
|
|
22774
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
22775
|
+
true
|
|
22776
|
+
);
|
|
22777
|
+
}
|
|
22778
|
+
}
|
|
22779
|
+
);
|
|
22780
|
+
server.tool(
|
|
22781
|
+
"update_viewer_context",
|
|
22782
|
+
"Replace account or project user context (Settings \u2192 Agents / Projects). Never call until the user confirmed. Show the proposed text, ask_user (Save this context / Do not save), wait, then pass confirmed=true. Empty context clears that field.",
|
|
22783
|
+
{
|
|
22784
|
+
scope: import_zod5.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
22785
|
+
context: import_zod5.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
22786
|
+
confirmed: import_zod5.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
22787
|
+
repoPath: import_zod5.z.string().optional().describe(
|
|
22788
|
+
"Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
|
|
22789
|
+
)
|
|
22790
|
+
},
|
|
22791
|
+
async ({ scope, context, confirmed, repoPath }) => {
|
|
22792
|
+
try {
|
|
22793
|
+
if (scope === "project") {
|
|
22794
|
+
const path = await resolveProjectRepoPath(repoPath);
|
|
22795
|
+
if (!path) {
|
|
22796
|
+
return text5(
|
|
22797
|
+
{
|
|
22798
|
+
ok: false,
|
|
22799
|
+
error: "repoPath required",
|
|
22800
|
+
message: PROJECT_PATH_HINT
|
|
22801
|
+
},
|
|
22802
|
+
true
|
|
22803
|
+
);
|
|
22804
|
+
}
|
|
22805
|
+
const result2 = writeViewerContext({
|
|
22806
|
+
scope,
|
|
22807
|
+
context,
|
|
22808
|
+
repoPath: path,
|
|
22809
|
+
confirmed: confirmed === true
|
|
22810
|
+
});
|
|
22811
|
+
return text5(result2, !result2.ok);
|
|
22812
|
+
}
|
|
22813
|
+
const result = writeViewerContext({
|
|
22814
|
+
scope,
|
|
22815
|
+
context,
|
|
22816
|
+
confirmed: confirmed === true
|
|
22817
|
+
});
|
|
22818
|
+
return text5(result, !result.ok);
|
|
22819
|
+
} catch (err) {
|
|
22820
|
+
return text5(
|
|
22821
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
22822
|
+
true
|
|
22823
|
+
);
|
|
22824
|
+
}
|
|
22825
|
+
}
|
|
22826
|
+
);
|
|
22827
|
+
}
|
|
22828
|
+
|
|
22452
22829
|
// src/mcp/pr-list.ts
|
|
22453
22830
|
function compactPrRow(pr) {
|
|
22454
22831
|
return {
|
|
@@ -22488,17 +22865,17 @@ init_list_prs();
|
|
|
22488
22865
|
init_app_settings();
|
|
22489
22866
|
|
|
22490
22867
|
// src/mcp/schedule-tools.ts
|
|
22491
|
-
var
|
|
22868
|
+
var import_zod6 = require("zod");
|
|
22492
22869
|
init_schedules();
|
|
22493
22870
|
init_schedule_runner();
|
|
22494
|
-
function
|
|
22871
|
+
function text6(payload, isError = false) {
|
|
22495
22872
|
return {
|
|
22496
22873
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
22497
22874
|
...isError ? { isError: true } : {}
|
|
22498
22875
|
};
|
|
22499
22876
|
}
|
|
22500
22877
|
function fail5(err) {
|
|
22501
|
-
return
|
|
22878
|
+
return text6(
|
|
22502
22879
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
22503
22880
|
true
|
|
22504
22881
|
);
|
|
@@ -22523,7 +22900,7 @@ function registerScheduleTools(server) {
|
|
|
22523
22900
|
{},
|
|
22524
22901
|
async () => {
|
|
22525
22902
|
try {
|
|
22526
|
-
return
|
|
22903
|
+
return text6({ schedules: listSchedules() });
|
|
22527
22904
|
} catch (err) {
|
|
22528
22905
|
return fail5(err);
|
|
22529
22906
|
}
|
|
@@ -22533,17 +22910,17 @@ function registerScheduleTools(server) {
|
|
|
22533
22910
|
"create_schedule",
|
|
22534
22911
|
"Create a local schedule that, when due, sends a prompt to an orchestration chat (threadId) or starts a new Global orchestration chat (omit threadId). Pass threadId=self to continue this coordinator. Exactly one of at (ISO datetime), every (15m/1h/6h/1d), or cron (5-field). Recurring jobs without threadId open a new chat each run. Overnight/unattended runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate. Sideboard.app must be running for the job to fire.",
|
|
22535
22912
|
{
|
|
22536
|
-
prompt:
|
|
22537
|
-
name:
|
|
22538
|
-
at:
|
|
22539
|
-
every:
|
|
22540
|
-
cron:
|
|
22541
|
-
tz:
|
|
22542
|
-
threadId:
|
|
22913
|
+
prompt: import_zod6.z.string().describe("User message / goal queued when the schedule fires"),
|
|
22914
|
+
name: import_zod6.z.string().optional(),
|
|
22915
|
+
at: import_zod6.z.string().optional().describe("ISO datetime for a one-shot"),
|
|
22916
|
+
every: import_zod6.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
22917
|
+
cron: import_zod6.z.string().optional().describe("5-field cron expression"),
|
|
22918
|
+
tz: import_zod6.z.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
22919
|
+
threadId: import_zod6.z.string().optional().describe(
|
|
22543
22920
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
22544
22921
|
),
|
|
22545
|
-
agent:
|
|
22546
|
-
model:
|
|
22922
|
+
agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
22923
|
+
model: import_zod6.z.string().optional()
|
|
22547
22924
|
},
|
|
22548
22925
|
async (args) => {
|
|
22549
22926
|
try {
|
|
@@ -22563,7 +22940,7 @@ function registerScheduleTools(server) {
|
|
|
22563
22940
|
model: args.model,
|
|
22564
22941
|
createdBy: "mcp"
|
|
22565
22942
|
});
|
|
22566
|
-
return
|
|
22943
|
+
return text6({
|
|
22567
22944
|
schedule,
|
|
22568
22945
|
hint: "Fires while Sideboard.app is running. Recurring jobs without threadId create a new orchestration chat each run. Overnight runs need Settings \u2192 Advanced \u2192 Caffeinate while schedules are enabled, or set_caffeinate."
|
|
22569
22946
|
});
|
|
@@ -22576,17 +22953,17 @@ function registerScheduleTools(server) {
|
|
|
22576
22953
|
"update_schedule",
|
|
22577
22954
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
22578
22955
|
{
|
|
22579
|
-
id:
|
|
22580
|
-
prompt:
|
|
22581
|
-
name:
|
|
22582
|
-
at:
|
|
22583
|
-
every:
|
|
22584
|
-
cron:
|
|
22585
|
-
tz:
|
|
22586
|
-
threadId:
|
|
22587
|
-
agent:
|
|
22588
|
-
model:
|
|
22589
|
-
enabled:
|
|
22956
|
+
id: import_zod6.z.string(),
|
|
22957
|
+
prompt: import_zod6.z.string().optional(),
|
|
22958
|
+
name: import_zod6.z.string().optional(),
|
|
22959
|
+
at: import_zod6.z.string().optional(),
|
|
22960
|
+
every: import_zod6.z.string().optional(),
|
|
22961
|
+
cron: import_zod6.z.string().optional(),
|
|
22962
|
+
tz: import_zod6.z.string().optional(),
|
|
22963
|
+
threadId: import_zod6.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
22964
|
+
agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
22965
|
+
model: import_zod6.z.string().optional(),
|
|
22966
|
+
enabled: import_zod6.z.boolean().optional()
|
|
22590
22967
|
},
|
|
22591
22968
|
async (args) => {
|
|
22592
22969
|
try {
|
|
@@ -22615,7 +22992,7 @@ function registerScheduleTools(server) {
|
|
|
22615
22992
|
enabled: args.enabled,
|
|
22616
22993
|
model: args.model
|
|
22617
22994
|
});
|
|
22618
|
-
return
|
|
22995
|
+
return text6({ schedule });
|
|
22619
22996
|
} catch (err) {
|
|
22620
22997
|
return fail5(err);
|
|
22621
22998
|
}
|
|
@@ -22624,11 +23001,11 @@ function registerScheduleTools(server) {
|
|
|
22624
23001
|
server.tool(
|
|
22625
23002
|
"delete_schedule",
|
|
22626
23003
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
22627
|
-
{ id:
|
|
23004
|
+
{ id: import_zod6.z.string() },
|
|
22628
23005
|
async ({ id }) => {
|
|
22629
23006
|
try {
|
|
22630
23007
|
deleteSchedule(id);
|
|
22631
|
-
return
|
|
23008
|
+
return text6({ ok: true, id });
|
|
22632
23009
|
} catch (err) {
|
|
22633
23010
|
return fail5(err);
|
|
22634
23011
|
}
|
|
@@ -22637,14 +23014,14 @@ function registerScheduleTools(server) {
|
|
|
22637
23014
|
server.tool(
|
|
22638
23015
|
"run_schedule",
|
|
22639
23016
|
"Fire a schedule now (does not wait for nextRunAt). Queues the prompt or starts a new Global chat. If Sideboard.app is running, the desktop drains the turn.",
|
|
22640
|
-
{ id:
|
|
23017
|
+
{ id: import_zod6.z.string() },
|
|
22641
23018
|
async ({ id }) => {
|
|
22642
23019
|
try {
|
|
22643
23020
|
if (!getSchedule(id)) {
|
|
22644
23021
|
return fail5(new Error(`Schedule not found: ${id}`));
|
|
22645
23022
|
}
|
|
22646
23023
|
const schedule = await fireSchedule(id);
|
|
22647
|
-
return
|
|
23024
|
+
return text6({ schedule });
|
|
22648
23025
|
} catch (err) {
|
|
22649
23026
|
return fail5(err);
|
|
22650
23027
|
}
|
|
@@ -22658,27 +23035,27 @@ init_gh_errors();
|
|
|
22658
23035
|
init_git_auth_mode();
|
|
22659
23036
|
|
|
22660
23037
|
// src/board/load-home-board.ts
|
|
22661
|
-
var
|
|
22662
|
-
var
|
|
23038
|
+
var import_node_fs53 = require("fs");
|
|
23039
|
+
var import_node_path49 = require("path");
|
|
22663
23040
|
init_worktree();
|
|
22664
23041
|
init_paths();
|
|
22665
23042
|
|
|
22666
23043
|
// src/board/board-pins.ts
|
|
22667
23044
|
var import_node_crypto11 = require("crypto");
|
|
22668
|
-
var
|
|
22669
|
-
var
|
|
23045
|
+
var import_node_fs52 = require("fs");
|
|
23046
|
+
var import_node_path48 = require("path");
|
|
22670
23047
|
init_paths();
|
|
22671
23048
|
init_home_board();
|
|
22672
23049
|
var FILE = "home-board-pins.json";
|
|
22673
23050
|
var VERSION = 1;
|
|
22674
23051
|
function pinsFile() {
|
|
22675
|
-
return (0,
|
|
23052
|
+
return (0, import_node_path48.join)(appDataDir(), FILE);
|
|
22676
23053
|
}
|
|
22677
23054
|
function readDisk() {
|
|
22678
23055
|
const path = pinsFile();
|
|
22679
|
-
if (!(0,
|
|
23056
|
+
if (!(0, import_node_fs52.existsSync)(path)) return [];
|
|
22680
23057
|
try {
|
|
22681
|
-
const raw = JSON.parse((0,
|
|
23058
|
+
const raw = JSON.parse((0, import_node_fs52.readFileSync)(path, "utf8"));
|
|
22682
23059
|
if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
|
|
22683
23060
|
return raw.items.filter((item) => item?.id && item.kind && item.ref);
|
|
22684
23061
|
} catch {
|
|
@@ -22686,8 +23063,8 @@ function readDisk() {
|
|
|
22686
23063
|
}
|
|
22687
23064
|
}
|
|
22688
23065
|
function writeDisk(items) {
|
|
22689
|
-
(0,
|
|
22690
|
-
(0,
|
|
23066
|
+
(0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
|
|
23067
|
+
(0, import_node_fs52.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
|
|
22691
23068
|
}
|
|
22692
23069
|
function listBoardPins() {
|
|
22693
23070
|
return readDisk();
|
|
@@ -22708,7 +23085,7 @@ function homeBoardWorkspaceKey(workspaces) {
|
|
|
22708
23085
|
return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
|
|
22709
23086
|
}
|
|
22710
23087
|
function cacheFile() {
|
|
22711
|
-
return (0,
|
|
23088
|
+
return (0, import_node_path49.join)(appDataDir(), "home-board-cache.json");
|
|
22712
23089
|
}
|
|
22713
23090
|
function emptyInputs() {
|
|
22714
23091
|
return {
|
|
@@ -22734,9 +23111,9 @@ function shouldCacheHomeBoardInputs(inputs) {
|
|
|
22734
23111
|
}
|
|
22735
23112
|
function readDiskCache() {
|
|
22736
23113
|
const path = cacheFile();
|
|
22737
|
-
if (!(0,
|
|
23114
|
+
if (!(0, import_node_fs53.existsSync)(path)) return null;
|
|
22738
23115
|
try {
|
|
22739
|
-
const raw = JSON.parse((0,
|
|
23116
|
+
const raw = JSON.parse((0, import_node_fs53.readFileSync)(path, "utf8"));
|
|
22740
23117
|
if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
|
|
22741
23118
|
return null;
|
|
22742
23119
|
}
|
|
@@ -22750,8 +23127,8 @@ function readDiskCache() {
|
|
|
22750
23127
|
}
|
|
22751
23128
|
function writeDiskCache(entry) {
|
|
22752
23129
|
try {
|
|
22753
|
-
(0,
|
|
22754
|
-
(0,
|
|
23130
|
+
(0, import_node_fs53.mkdirSync)(appDataDir(), { recursive: true });
|
|
23131
|
+
(0, import_node_fs53.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
|
|
22755
23132
|
} catch {
|
|
22756
23133
|
}
|
|
22757
23134
|
}
|
|
@@ -23013,13 +23390,14 @@ async function startMcpServer() {
|
|
|
23013
23390
|
version: "0.1.0"
|
|
23014
23391
|
});
|
|
23015
23392
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
23393
|
+
registerViewerContextTools(server);
|
|
23016
23394
|
if (worktreeProfile) {
|
|
23017
23395
|
registerConnectedIssueVendorTools(server);
|
|
23018
23396
|
}
|
|
23019
23397
|
if (!worktreeProfile) {
|
|
23020
23398
|
server.tool(
|
|
23021
23399
|
"list_workspaces",
|
|
23022
|
-
"List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and
|
|
23400
|
+
"List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and project context when set \u2014 use path as repoPath for list_board/list_branches/list_prs/list_issues/create_thread. Account / project context (Settings \u2192 Agents / Projects) says which tickets and review PRs belong to the user.",
|
|
23023
23401
|
{},
|
|
23024
23402
|
async () => {
|
|
23025
23403
|
const workspaces = orch.listWorkspaces();
|
|
@@ -23050,7 +23428,7 @@ async function startMcpServer() {
|
|
|
23050
23428
|
async () => {
|
|
23051
23429
|
const threads = orch.getThreads(true);
|
|
23052
23430
|
const lines = threads.map((t) => {
|
|
23053
|
-
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0,
|
|
23431
|
+
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path50.basename)(t.repoPath) || t.repoPath;
|
|
23054
23432
|
const live = orch.threadLooksLive(t) ? readTurnLive(t.id) : null;
|
|
23055
23433
|
const parent = t.parentThreadId ? ` parent:${t.parentThreadId.slice(0, 8)}` : "";
|
|
23056
23434
|
const preview = lastMessagePreview(t.messages, 80);
|
|
@@ -23068,13 +23446,13 @@ async function startMcpServer() {
|
|
|
23068
23446
|
"list_board",
|
|
23069
23447
|
"Home Kanban of worktrees (New, Draft, Review, Merged) \u2014 one card per checkout; sibling chat tabs nest as inner cards. Same cards as desktop Home. Path to merge: no PR \u2192 draft PR \u2192 open PR \u2192 merged. Archive removes the card to Settings \u2192 History. Queued/running are activity on the card, not columns. Orchestration chats are not on the board. Filters: query, repoPath, kind (ticket/PR/branch source), column, limit (default 40). create_thread adds a worktree (and a Home card), or returns the live one if that ticket/PR/named branch is already checked out.",
|
|
23070
23448
|
{
|
|
23071
|
-
query:
|
|
23072
|
-
repoPath:
|
|
23073
|
-
kind:
|
|
23074
|
-
column:
|
|
23449
|
+
query: import_zod7.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
23450
|
+
repoPath: import_zod7.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
23451
|
+
kind: import_zod7.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
23452
|
+
column: import_zod7.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
23075
23453
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
23076
23454
|
),
|
|
23077
|
-
limit:
|
|
23455
|
+
limit: import_zod7.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
23078
23456
|
},
|
|
23079
23457
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
23080
23458
|
const workspaces = orch.listWorkspaces();
|
|
@@ -23112,7 +23490,7 @@ async function startMcpServer() {
|
|
|
23112
23490
|
server.tool(
|
|
23113
23491
|
"get_thread",
|
|
23114
23492
|
"Get a compact thread summary by id/ref. Includes last message preview, parentThreadId, and child worktree threads (status + lastText). While running, includes progress (last tool/thinking) and lastActivityAt. Includes usage (thread billed token + costUsd totals when providers reported cost) and lastTurnUsage.",
|
|
23115
|
-
{ ref:
|
|
23493
|
+
{ ref: import_zod7.z.string() },
|
|
23116
23494
|
async ({ ref }) => {
|
|
23117
23495
|
const t = orch.getThread(ref);
|
|
23118
23496
|
if (!t) {
|
|
@@ -23154,17 +23532,17 @@ async function startMcpServer() {
|
|
|
23154
23532
|
"present_artifact",
|
|
23155
23533
|
"Show a document or live log in Sideboard\u2019s side column. For html/svg/markdown/react, pass the FULL document and do not also fence that same body in chat. For type=log, pass only NEW lines (same artifact_id appends). Prefer type=log for long-running job output \u2014 do not resend HTML. type=react is a single default-export component (JSX/TSX); only react/react-dom imports.",
|
|
23156
23534
|
{
|
|
23157
|
-
title:
|
|
23158
|
-
type:
|
|
23535
|
+
title: import_zod7.z.string().describe("Short title shown in the artifact pane header"),
|
|
23536
|
+
type: import_zod7.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
23159
23537
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
23160
23538
|
),
|
|
23161
|
-
content:
|
|
23539
|
+
content: import_zod7.z.string().describe(
|
|
23162
23540
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
23163
23541
|
),
|
|
23164
|
-
artifact_id:
|
|
23165
|
-
status:
|
|
23166
|
-
phase:
|
|
23167
|
-
mode:
|
|
23542
|
+
artifact_id: import_zod7.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
23543
|
+
status: import_zod7.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
23544
|
+
phase: import_zod7.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
23545
|
+
mode: import_zod7.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
23168
23546
|
},
|
|
23169
23547
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
23170
23548
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23185,15 +23563,15 @@ async function startMcpServer() {
|
|
|
23185
23563
|
"ask_user",
|
|
23186
23564
|
"Ask the user a clarifying multiple-choice question in Sideboard\u2019s composer. Call only when work is blocked on choosing among a few concrete options (approach fork, which API, auth vs cookies). Do not call for greetings, check-ins, \u201Chello\u201D, open-ended how-can-I-help, or to invent a menu of possible next tasks \u2014 reply in chat instead. If one option is the obvious default, proceed without asking. Before calling, write a short chat message explaining the decision and what each option means. Include a description on every option. After calling, stop and wait for answers. Not for \u201Cis the plan ready?\u201D.",
|
|
23187
23565
|
{
|
|
23188
|
-
questions:
|
|
23189
|
-
|
|
23190
|
-
question:
|
|
23191
|
-
header:
|
|
23192
|
-
multiSelect:
|
|
23193
|
-
options:
|
|
23194
|
-
|
|
23195
|
-
label:
|
|
23196
|
-
description:
|
|
23566
|
+
questions: import_zod7.z.array(
|
|
23567
|
+
import_zod7.z.object({
|
|
23568
|
+
question: import_zod7.z.string().describe("Full question text ending with ?"),
|
|
23569
|
+
header: import_zod7.z.string().max(24).optional().describe("Short label shown above the question"),
|
|
23570
|
+
multiSelect: import_zod7.z.boolean().optional().describe("Allow selecting multiple options"),
|
|
23571
|
+
options: import_zod7.z.array(
|
|
23572
|
+
import_zod7.z.object({
|
|
23573
|
+
label: import_zod7.z.string(),
|
|
23574
|
+
description: import_zod7.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
23197
23575
|
})
|
|
23198
23576
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
23199
23577
|
})
|
|
@@ -23212,9 +23590,9 @@ async function startMcpServer() {
|
|
|
23212
23590
|
"present_plan",
|
|
23213
23591
|
"Save the implementation plan as markdown to .context/attachments/plan.md and show it in Sideboard chat for user approval (Copy / Hand off / Approve). Call this when the plan is ready \u2014 required in plan mode. Pass the full plan body in content. Then Claude should call ExitPlanMode.",
|
|
23214
23592
|
{
|
|
23215
|
-
title:
|
|
23216
|
-
content:
|
|
23217
|
-
thread_id:
|
|
23593
|
+
title: import_zod7.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
23594
|
+
content: import_zod7.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
23595
|
+
thread_id: import_zod7.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
23218
23596
|
},
|
|
23219
23597
|
async ({ title, content, thread_id }) => {
|
|
23220
23598
|
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
@@ -23237,15 +23615,15 @@ async function startMcpServer() {
|
|
|
23237
23615
|
"present_schema",
|
|
23238
23616
|
"Open Sideboard\u2019s schema-driven side column (filterable table and/or form) when the user needs to filter, edit, publish, or persist records. Do not call this just to re-display rows you already wrote as a markdown table. If the user asks for an editable / interactive table, call this even if chat already showed those rows. Pass JSON Schema + optional schemaUi. Prefer datasource=inline with embedded resource/records. Use datasource=brightsy with resource_id only when the user is logged into Brightsy.",
|
|
23239
23617
|
{
|
|
23240
|
-
title:
|
|
23241
|
-
mode:
|
|
23242
|
-
datasource:
|
|
23243
|
-
resource_id:
|
|
23244
|
-
record_id:
|
|
23245
|
-
resource:
|
|
23246
|
-
record:
|
|
23247
|
-
records:
|
|
23248
|
-
pane_id:
|
|
23618
|
+
title: import_zod7.z.string().describe("Pane title"),
|
|
23619
|
+
mode: import_zod7.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
23620
|
+
datasource: import_zod7.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
23621
|
+
resource_id: import_zod7.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
23622
|
+
record_id: import_zod7.z.string().optional().describe("Record id when opening form mode"),
|
|
23623
|
+
resource: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
23624
|
+
record: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
23625
|
+
records: import_zod7.z.array(import_zod7.z.record(import_zod7.z.unknown())).optional().describe("Inline records for table mode"),
|
|
23626
|
+
pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
|
|
23249
23627
|
},
|
|
23250
23628
|
async (args) => {
|
|
23251
23629
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23266,10 +23644,10 @@ async function startMcpServer() {
|
|
|
23266
23644
|
"present_files",
|
|
23267
23645
|
"Open Sideboard\u2019s Files column (CMS-style file manager: browse, upload, pick). Use datasource=brightsy when the user is logged into Brightsy account storage; datasource=memory for a session-local demo store. Prefer this over claiming a file manager UI is unavailable. Pair with present_schema when editing records that need media.",
|
|
23268
23646
|
{
|
|
23269
|
-
title:
|
|
23270
|
-
datasource:
|
|
23271
|
-
path:
|
|
23272
|
-
pane_id:
|
|
23647
|
+
title: import_zod7.z.string().optional().describe("Pane title (default: Files)"),
|
|
23648
|
+
datasource: import_zod7.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
23649
|
+
path: import_zod7.z.string().optional().describe("Initial folder path (e.g. public)"),
|
|
23650
|
+
pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
|
|
23273
23651
|
},
|
|
23274
23652
|
async (args) => {
|
|
23275
23653
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -23284,6 +23662,20 @@ async function startMcpServer() {
|
|
|
23284
23662
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
23285
23663
|
}
|
|
23286
23664
|
);
|
|
23665
|
+
server.tool(
|
|
23666
|
+
"wait_for_job",
|
|
23667
|
+
"Wait on a detached long job (tests, pack, deploy) started with detached-job.js. MCP clients kill tools around 60s, so this returns within 45s. stillRunning is the source of truth \u2014 if true, present_artifact type=log with content=delta (same artifact_id) and call wait_for_job again. Do not end the turn or tell the user you will let them know later. If false, ok/failed is the result.",
|
|
23668
|
+
{
|
|
23669
|
+
id: import_zod7.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
23670
|
+
timeoutMs: import_zod7.z.number().optional()
|
|
23671
|
+
},
|
|
23672
|
+
async ({ id, timeoutMs }) => {
|
|
23673
|
+
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
23674
|
+
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
23675
|
+
});
|
|
23676
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
23677
|
+
}
|
|
23678
|
+
);
|
|
23287
23679
|
if (!worktreeProfile) {
|
|
23288
23680
|
registerSlackTools(server);
|
|
23289
23681
|
registerConnectedIssueVendorTools(server);
|
|
@@ -23296,7 +23688,7 @@ async function startMcpServer() {
|
|
|
23296
23688
|
"set_caffeinate",
|
|
23297
23689
|
"Keep this Mac awake with caffeinate (like Claude Code) across turns \u2014 independent of Settings toggles. Turn ON when the user will be away from the keyboard, is driving work from Slack, or asks you to keep the machine awake. Turn OFF when they say they are done, wrapping up, going to sleep, or no longer need the Mac awake. Closing or archiving this orchestration chat also releases the hold. macOS only.",
|
|
23298
23690
|
{
|
|
23299
|
-
enabled:
|
|
23691
|
+
enabled: import_zod7.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
23300
23692
|
},
|
|
23301
23693
|
async ({ enabled }) => {
|
|
23302
23694
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -23334,18 +23726,18 @@ async function startMcpServer() {
|
|
|
23334
23726
|
"create_thread",
|
|
23335
23727
|
`Create a worktree thread (chat) from branch, pr, or ticket. A ticket, PR, or named branch may have only one live worktree \u2014 if one already matches, returns it (alreadyStarted=true) instead of a second checkout. Creating from the default branch still opens a new isolated worktree. Pass repoPath from list_workspaces. cowboy=true uses the project folder on the default branch (no isolated worktree; land is commit+push). From an orchestration chat, omit parentThreadId (Sideboard binds the child to this chat) or pass the exact id from the turn reminder \u2014 never invent a uuid. Prefer omitting agent/model so Sideboard applies ${accountDefaultsHint}. Setup (settings.toml, .cursor/worktrees.json, or script/setup) runs in the background in parallel with the first turn (skipped for cowboy). Then use send_to_thread to chat.`,
|
|
23336
23728
|
{
|
|
23337
|
-
sourceType:
|
|
23338
|
-
sourceRef:
|
|
23339
|
-
agent:
|
|
23340
|
-
model:
|
|
23729
|
+
sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]),
|
|
23730
|
+
sourceRef: import_zod7.z.string(),
|
|
23731
|
+
agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
23732
|
+
model: import_zod7.z.string().nullable().optional().describe(
|
|
23341
23733
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
23342
23734
|
),
|
|
23343
|
-
repoPath:
|
|
23344
|
-
title:
|
|
23345
|
-
cowboy:
|
|
23735
|
+
repoPath: import_zod7.z.string(),
|
|
23736
|
+
title: import_zod7.z.string().optional(),
|
|
23737
|
+
cowboy: import_zod7.z.boolean().optional().describe(
|
|
23346
23738
|
"If true, work in the project folder on the default branch (no thread/* worktree). Requires Settings \u2192 Advanced \u2192 Cowboy mode. Land is commit+push to that branch. Archive does not delete the folder."
|
|
23347
23739
|
),
|
|
23348
|
-
parentThreadId:
|
|
23740
|
+
parentThreadId: import_zod7.z.string().optional().describe(
|
|
23349
23741
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
23350
23742
|
)
|
|
23351
23743
|
},
|
|
@@ -23361,10 +23753,10 @@ async function startMcpServer() {
|
|
|
23361
23753
|
"start_board_card",
|
|
23362
23754
|
"Same as create_thread for a ticket, PR, or named branch (attaches issue text when Sideboard can resolve it). Does not create a second worktree when one already matches \u2014 returns that thread (alreadyStarted). Then send_to_thread.",
|
|
23363
23755
|
{
|
|
23364
|
-
kind:
|
|
23365
|
-
ref:
|
|
23366
|
-
repoPath:
|
|
23367
|
-
title:
|
|
23756
|
+
kind: import_zod7.z.enum(["ticket", "pr", "branch"]),
|
|
23757
|
+
ref: import_zod7.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
23758
|
+
repoPath: import_zod7.z.string().describe("Workspace path from list_workspaces / list_board"),
|
|
23759
|
+
title: import_zod7.z.string().optional()
|
|
23368
23760
|
},
|
|
23369
23761
|
async ({ kind, ref, repoPath, title }) => {
|
|
23370
23762
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -23468,9 +23860,9 @@ async function startMcpServer() {
|
|
|
23468
23860
|
"send_to_thread",
|
|
23469
23861
|
'Queue a prompt on a worktree thread chat (runs under concurrency cap). 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 force_stop to check in, resume after a halt notice, or because wait_for_turn returned stillRunning; that stops the child mid-thought. Call wait_for_turn again instead.',
|
|
23470
23862
|
{
|
|
23471
|
-
ref:
|
|
23472
|
-
prompt:
|
|
23473
|
-
force_stop:
|
|
23863
|
+
ref: import_zod7.z.string(),
|
|
23864
|
+
prompt: import_zod7.z.string(),
|
|
23865
|
+
force_stop: import_zod7.z.boolean().optional()
|
|
23474
23866
|
},
|
|
23475
23867
|
async ({ ref, prompt, force_stop }) => {
|
|
23476
23868
|
if (force_stop) {
|
|
@@ -23499,8 +23891,8 @@ async function startMcpServer() {
|
|
|
23499
23891
|
"wait_for_turn",
|
|
23500
23892
|
"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 a check-in prompt, 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).",
|
|
23501
23893
|
{
|
|
23502
|
-
ref:
|
|
23503
|
-
timeoutMs:
|
|
23894
|
+
ref: import_zod7.z.string(),
|
|
23895
|
+
timeoutMs: import_zod7.z.number().optional()
|
|
23504
23896
|
},
|
|
23505
23897
|
async ({ ref, timeoutMs }) => {
|
|
23506
23898
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -23530,7 +23922,7 @@ async function startMcpServer() {
|
|
|
23530
23922
|
server.tool(
|
|
23531
23923
|
"get_turn_result",
|
|
23532
23924
|
"Assistant message when the turn finished, or live progress while stillRunning. Not the full transcript. Includes usage for the last agent turn (tokens + costUsd when reported).",
|
|
23533
|
-
{ ref:
|
|
23925
|
+
{ ref: import_zod7.z.string() },
|
|
23534
23926
|
async ({ ref }) => {
|
|
23535
23927
|
const result = orch.getTurnResult(ref);
|
|
23536
23928
|
return {
|
|
@@ -23551,8 +23943,8 @@ async function startMcpServer() {
|
|
|
23551
23943
|
"stop_thread",
|
|
23552
23944
|
"Force-stop a thread: kill any in-flight agent turn AND clear queued prompts so drainQueue cannot continue. Does not archive the worktree. Optional force defaults to true.",
|
|
23553
23945
|
{
|
|
23554
|
-
ref:
|
|
23555
|
-
force:
|
|
23946
|
+
ref: import_zod7.z.string(),
|
|
23947
|
+
force: import_zod7.z.boolean().optional()
|
|
23556
23948
|
},
|
|
23557
23949
|
async ({ ref, force }) => {
|
|
23558
23950
|
const t = orch.getThread(ref);
|
|
@@ -23582,7 +23974,7 @@ async function startMcpServer() {
|
|
|
23582
23974
|
server.tool(
|
|
23583
23975
|
"archive_thread",
|
|
23584
23976
|
"Archive a thread (stops agent/dev, runs archive script, removes worktree when last chat tab). Coordinators commit, push, and open PRs by asking the worktree agent (ask_git). Merge only when the user explicitly asked.",
|
|
23585
|
-
{ ref:
|
|
23977
|
+
{ ref: import_zod7.z.string() },
|
|
23586
23978
|
async ({ ref }) => {
|
|
23587
23979
|
const t = orch.getThread(ref);
|
|
23588
23980
|
if (!t) {
|
|
@@ -23615,7 +24007,7 @@ async function startMcpServer() {
|
|
|
23615
24007
|
server.tool(
|
|
23616
24008
|
"restore_thread",
|
|
23617
24009
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
23618
|
-
{ ref:
|
|
24010
|
+
{ ref: import_zod7.z.string() },
|
|
23619
24011
|
async ({ ref }) => {
|
|
23620
24012
|
try {
|
|
23621
24013
|
const restored = await orch.restore(ref);
|
|
@@ -23641,8 +24033,8 @@ async function startMcpServer() {
|
|
|
23641
24033
|
"get_diff",
|
|
23642
24034
|
"Compact diff summary (capped hunks, paginated)",
|
|
23643
24035
|
{
|
|
23644
|
-
ref:
|
|
23645
|
-
maxFiles:
|
|
24036
|
+
ref: import_zod7.z.string(),
|
|
24037
|
+
maxFiles: import_zod7.z.number().optional()
|
|
23646
24038
|
},
|
|
23647
24039
|
async ({ ref, maxFiles }) => {
|
|
23648
24040
|
const summary = await orch.diffSummary(ref);
|
|
@@ -23662,7 +24054,7 @@ async function startMcpServer() {
|
|
|
23662
24054
|
server.tool(
|
|
23663
24055
|
"get_pr_checks",
|
|
23664
24056
|
"Snapshot of GitHub PR checks for a worktree thread (`gh pr checks` plus merge/review gates). null = no PR. If the user gave a goal (Greptile 5/5, CI green), the worktree agent watches with `gh pr checks --watch` via /long-running \u2014 this tool is a snapshot, not a waiter. Coordinators: do not run gh from the orchestration cwd.",
|
|
23665
|
-
{ ref:
|
|
24057
|
+
{ ref: import_zod7.z.string().describe("Worktree thread id/ref") },
|
|
23666
24058
|
async ({ ref }) => {
|
|
23667
24059
|
try {
|
|
23668
24060
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -23678,7 +24070,7 @@ async function startMcpServer() {
|
|
|
23678
24070
|
server.tool(
|
|
23679
24071
|
"request_review",
|
|
23680
24072
|
'Start a merge-readiness Review on a worktree agent thread (same as the desktop Review button). Opens a new Review chat tab, attaches .claude/skills/review/SKILL.md when present (else copies .sideboard/review.md into .context/review.md, or seeds that file from the stock template), and sends "Review changes in this workspace." Expect Approve / Approve with nits / Request changes / Needs more information. Pass a worktree thread ref \u2014 not the orchestrator. Then wait_for_turn (loop while stillRunning) / get_turn_result on the returned review tab id.',
|
|
23681
|
-
{ ref:
|
|
24073
|
+
{ ref: import_zod7.z.string().describe("Worktree thread id/ref to review") },
|
|
23682
24074
|
async ({ ref }) => {
|
|
23683
24075
|
try {
|
|
23684
24076
|
const tab = await orch.requestReview(ref);
|
|
@@ -23707,8 +24099,8 @@ async function startMcpServer() {
|
|
|
23707
24099
|
"ask_git",
|
|
23708
24100
|
"Commit & push, open a draft PR, 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, queues the worktree agent to commit; 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=merge only when the user explicitly asked to merge that PR. 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.",
|
|
23709
24101
|
{
|
|
23710
|
-
ref:
|
|
23711
|
-
action:
|
|
24102
|
+
ref: import_zod7.z.string().describe("Worktree thread id/ref"),
|
|
24103
|
+
action: import_zod7.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
23712
24104
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
23713
24105
|
)
|
|
23714
24106
|
},
|
|
@@ -23753,7 +24145,7 @@ async function startMcpServer() {
|
|
|
23753
24145
|
}
|
|
23754
24146
|
}
|
|
23755
24147
|
);
|
|
23756
|
-
const agentEnum =
|
|
24148
|
+
const agentEnum = import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
23757
24149
|
server.tool(
|
|
23758
24150
|
"list_models",
|
|
23759
24151
|
"List models for an agent. Prefer Auto: do not call this unless you have a reason to pin a specific model (user request, cost/latency, capability). Omit agent to list all.",
|
|
@@ -23776,11 +24168,11 @@ async function startMcpServer() {
|
|
|
23776
24168
|
"fork_worktree",
|
|
23777
24169
|
"Fork a worktree agent chat into a NEW git worktree + chat (desktop \u201CFork to new workspace\u201D). Seeds a transcript (through through_index, default all). Optional agent override. Leave model unset for Auto (default) \u2014 only pass model when you have a reason. Not for the orchestrator. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id.",
|
|
23778
24170
|
{
|
|
23779
|
-
ref:
|
|
23780
|
-
through_index:
|
|
24171
|
+
ref: import_zod7.z.string().describe("Worktree thread id/ref to fork"),
|
|
24172
|
+
through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
23781
24173
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
23782
|
-
model:
|
|
23783
|
-
title:
|
|
24174
|
+
model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
24175
|
+
title: import_zod7.z.string().optional()
|
|
23784
24176
|
},
|
|
23785
24177
|
async ({ ref, through_index, agent, model, title }) => {
|
|
23786
24178
|
try {
|
|
@@ -23821,11 +24213,11 @@ async function startMcpServer() {
|
|
|
23821
24213
|
"fork_chat",
|
|
23822
24214
|
"Fork a chat into a NEW tab on the SAME workspace: worktree agent \u2192 same worktree tab; Global orchestration chat \u2192 new orchestration chat (same synthetic home). Seeds a transcript; optional agent override. Leave model unset for Auto unless you have a reason. Orchestration forks require an MCP-capable agent (claude, cursor, codex, opencode \u2014 not brightsy). Slack / Global orchestrators use this to continue an orchestration chat on another agent after session limits. Then send_to_thread / wait_for_turn (loop while stillRunning) on the returned id. Use fork_worktree only for worktree agents that need a new git worktree.",
|
|
23823
24215
|
{
|
|
23824
|
-
ref:
|
|
23825
|
-
through_index:
|
|
24216
|
+
ref: import_zod7.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
24217
|
+
through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
23826
24218
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
23827
|
-
model:
|
|
23828
|
-
title:
|
|
24219
|
+
model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
24220
|
+
title: import_zod7.z.string().optional()
|
|
23829
24221
|
},
|
|
23830
24222
|
async ({ ref, through_index, agent, model, title }) => {
|
|
23831
24223
|
try {
|
|
@@ -23871,8 +24263,8 @@ async function startMcpServer() {
|
|
|
23871
24263
|
"run_dev_script",
|
|
23872
24264
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
23873
24265
|
{
|
|
23874
|
-
ref:
|
|
23875
|
-
name:
|
|
24266
|
+
ref: import_zod7.z.string(),
|
|
24267
|
+
name: import_zod7.z.string().optional()
|
|
23876
24268
|
},
|
|
23877
24269
|
async ({ ref, name }) => {
|
|
23878
24270
|
const result = await orch.startDev(ref, name);
|
|
@@ -23894,7 +24286,7 @@ async function startMcpServer() {
|
|
|
23894
24286
|
server.tool(
|
|
23895
24287
|
"list_run_scripts",
|
|
23896
24288
|
"List named run scripts available for a thread",
|
|
23897
|
-
{ ref:
|
|
24289
|
+
{ ref: import_zod7.z.string() },
|
|
23898
24290
|
async ({ ref }) => {
|
|
23899
24291
|
const scripts = orch.listThreadRunScripts(ref);
|
|
23900
24292
|
const active = orch.getActiveRuns(ref);
|
|
@@ -23912,8 +24304,8 @@ async function startMcpServer() {
|
|
|
23912
24304
|
"stop_dev_script",
|
|
23913
24305
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
23914
24306
|
{
|
|
23915
|
-
ref:
|
|
23916
|
-
name:
|
|
24307
|
+
ref: import_zod7.z.string(),
|
|
24308
|
+
name: import_zod7.z.string().optional()
|
|
23917
24309
|
},
|
|
23918
24310
|
async ({ ref, name }) => {
|
|
23919
24311
|
orch.stopDev(ref, name);
|
|
@@ -23923,7 +24315,7 @@ async function startMcpServer() {
|
|
|
23923
24315
|
server.tool(
|
|
23924
24316
|
"run_setup",
|
|
23925
24317
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
23926
|
-
{ ref:
|
|
24318
|
+
{ ref: import_zod7.z.string() },
|
|
23927
24319
|
async ({ ref }) => {
|
|
23928
24320
|
const result = await orch.runSetup(ref);
|
|
23929
24321
|
return {
|
|
@@ -23934,7 +24326,7 @@ async function startMcpServer() {
|
|
|
23934
24326
|
server.tool(
|
|
23935
24327
|
"add_workspace",
|
|
23936
24328
|
"Register a git repo as a Sideboard workspace",
|
|
23937
|
-
{ repoPath:
|
|
24329
|
+
{ repoPath: import_zod7.z.string() },
|
|
23938
24330
|
async ({ repoPath }) => {
|
|
23939
24331
|
const ws = await orch.addWorkspace(repoPath);
|
|
23940
24332
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -23943,7 +24335,7 @@ async function startMcpServer() {
|
|
|
23943
24335
|
server.tool(
|
|
23944
24336
|
"remove_workspace",
|
|
23945
24337
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
23946
|
-
{ repoPath:
|
|
24338
|
+
{ repoPath: import_zod7.z.string() },
|
|
23947
24339
|
async ({ repoPath }) => {
|
|
23948
24340
|
orch.removeWorkspace(repoPath);
|
|
23949
24341
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -23953,12 +24345,12 @@ async function startMcpServer() {
|
|
|
23953
24345
|
"fanout",
|
|
23954
24346
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
23955
24347
|
{
|
|
23956
|
-
prompt:
|
|
23957
|
-
agents:
|
|
23958
|
-
repoPath:
|
|
23959
|
-
sourceType:
|
|
23960
|
-
sourceRef:
|
|
23961
|
-
title:
|
|
24348
|
+
prompt: import_zod7.z.string(),
|
|
24349
|
+
agents: import_zod7.z.array(import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
24350
|
+
repoPath: import_zod7.z.string(),
|
|
24351
|
+
sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]).optional(),
|
|
24352
|
+
sourceRef: import_zod7.z.string().optional(),
|
|
24353
|
+
title: import_zod7.z.string().optional()
|
|
23962
24354
|
},
|
|
23963
24355
|
async (args) => {
|
|
23964
24356
|
const threads = await orch.bestOfN(args);
|
|
@@ -23985,8 +24377,8 @@ async function startMcpServer() {
|
|
|
23985
24377
|
"list_branches",
|
|
23986
24378
|
"List git branches in a registered workspace. Pass repoPath from list_workspaces (unmerged into the default branch by default \u2014 for create_thread sourceType=branch).",
|
|
23987
24379
|
{
|
|
23988
|
-
repoPath:
|
|
23989
|
-
unmergedOnly:
|
|
24380
|
+
repoPath: import_zod7.z.string(),
|
|
24381
|
+
unmergedOnly: import_zod7.z.boolean().optional()
|
|
23990
24382
|
},
|
|
23991
24383
|
async ({ repoPath, unmergedOnly }) => {
|
|
23992
24384
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -24007,19 +24399,19 @@ async function startMcpServer() {
|
|
|
24007
24399
|
"list_prs",
|
|
24008
24400
|
'List GitHub PRs for a registered workspace (the review surface for assigned ticket work \u2014 not the tickets). Pass repoPath from list_workspaces. "Get me N tickets to review" \u2192 queue=review and limit=N: open non-draft PRs labeled eng-review with no individual user reviewer. Prefer teams that match Settings \u2192 Agents / Projects roles for this repo. A team like engineering-team is not a claim (you are on that team). Also: state (open|closed|merged|all|review), label, reviewer (me|unassigned|login), query, limit (default 40, max 250). Then create_thread with sourceType=pr.',
|
|
24009
24401
|
{
|
|
24010
|
-
repoPath:
|
|
24011
|
-
query:
|
|
24012
|
-
queue:
|
|
24402
|
+
repoPath: import_zod7.z.string(),
|
|
24403
|
+
query: import_zod7.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
24404
|
+
queue: import_zod7.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
24013
24405
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
24014
24406
|
),
|
|
24015
|
-
state:
|
|
24016
|
-
label:
|
|
24407
|
+
state: import_zod7.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
24408
|
+
label: import_zod7.z.string().optional().describe(
|
|
24017
24409
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
24018
24410
|
),
|
|
24019
|
-
reviewer:
|
|
24411
|
+
reviewer: import_zod7.z.string().optional().describe(
|
|
24020
24412
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
24021
24413
|
),
|
|
24022
|
-
limit:
|
|
24414
|
+
limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
24023
24415
|
},
|
|
24024
24416
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
24025
24417
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -24051,7 +24443,7 @@ async function startMcpServer() {
|
|
|
24051
24443
|
server.tool(
|
|
24052
24444
|
"get_pr_stack",
|
|
24053
24445
|
"Load the GitHub PR stack for a thread worktree (`gh stack view --json`). Returns null JSON when the branch is not stacked. Prefer this before ask_git merge on stacked PRs (and only merge when the user explicitly asked).",
|
|
24054
|
-
{ ref:
|
|
24446
|
+
{ ref: import_zod7.z.string() },
|
|
24055
24447
|
async ({ ref }) => {
|
|
24056
24448
|
const stack = await orch.getPrStack(ref);
|
|
24057
24449
|
return {
|
|
@@ -24063,8 +24455,8 @@ async function startMcpServer() {
|
|
|
24063
24455
|
"open_pr_stack_layers",
|
|
24064
24456
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
24065
24457
|
{
|
|
24066
|
-
ref:
|
|
24067
|
-
layer:
|
|
24458
|
+
ref: import_zod7.z.string(),
|
|
24459
|
+
layer: import_zod7.z.number().int().positive().optional()
|
|
24068
24460
|
},
|
|
24069
24461
|
async ({ ref, layer }) => {
|
|
24070
24462
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -24098,9 +24490,9 @@ async function startMcpServer() {
|
|
|
24098
24490
|
"add_stack_layer",
|
|
24099
24491
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
24100
24492
|
{
|
|
24101
|
-
ref:
|
|
24102
|
-
branchName:
|
|
24103
|
-
title:
|
|
24493
|
+
ref: import_zod7.z.string(),
|
|
24494
|
+
branchName: import_zod7.z.string(),
|
|
24495
|
+
title: import_zod7.z.string().optional()
|
|
24104
24496
|
},
|
|
24105
24497
|
async ({ ref, branchName, title }) => {
|
|
24106
24498
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -24129,11 +24521,11 @@ async function startMcpServer() {
|
|
|
24129
24521
|
"create_pr_stack",
|
|
24130
24522
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
24131
24523
|
{
|
|
24132
|
-
repoPath:
|
|
24133
|
-
branches:
|
|
24134
|
-
agent:
|
|
24135
|
-
base:
|
|
24136
|
-
title:
|
|
24524
|
+
repoPath: import_zod7.z.string(),
|
|
24525
|
+
branches: import_zod7.z.array(import_zod7.z.string()).min(1),
|
|
24526
|
+
agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
24527
|
+
base: import_zod7.z.string().optional(),
|
|
24528
|
+
title: import_zod7.z.string().optional()
|
|
24137
24529
|
},
|
|
24138
24530
|
async (args) => {
|
|
24139
24531
|
const result = await orch.createPrStack({
|
|
@@ -24172,10 +24564,10 @@ async function startMcpServer() {
|
|
|
24172
24564
|
"list_issues",
|
|
24173
24565
|
"List or search issues (Linear, AbleTime, or GitHub; falls back to GitHub). Use Settings \u2192 Agents / Projects notes and roles to pick tickets relevant to the viewer (query + assignee=me or unassigned as the notes say). Default 40; pass query and/or limit (max 250) when truncated. assignee: me (Linear default), unassigned, all, or a user id. Then create_thread with sourceType=ticket.",
|
|
24174
24566
|
{
|
|
24175
|
-
repoPath:
|
|
24176
|
-
query:
|
|
24177
|
-
assignee:
|
|
24178
|
-
limit:
|
|
24567
|
+
repoPath: import_zod7.z.string(),
|
|
24568
|
+
query: import_zod7.z.string().optional().describe("Search title, identifier, or description"),
|
|
24569
|
+
assignee: import_zod7.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
24570
|
+
limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
24179
24571
|
},
|
|
24180
24572
|
async ({ repoPath, query, assignee, limit }) => {
|
|
24181
24573
|
const root = await resolveRepoRoot(repoPath);
|