@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/index.cjs
CHANGED
|
@@ -578,27 +578,27 @@ var init_nested_electron_env = __esm({
|
|
|
578
578
|
});
|
|
579
579
|
|
|
580
580
|
// src/store/account-profile.ts
|
|
581
|
-
function
|
|
582
|
-
const known =
|
|
581
|
+
function legacyRoleLabel(role) {
|
|
582
|
+
const known = LEGACY_ROLE_LABELS[role];
|
|
583
583
|
if (known) return known;
|
|
584
584
|
return role.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
|
|
585
585
|
}
|
|
586
|
-
function
|
|
586
|
+
function normalizeLegacyRole(value) {
|
|
587
587
|
const key = (value ?? "").trim().toLowerCase().replace(/[_\s]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
588
588
|
if (key === "both") return void 0;
|
|
589
589
|
if (key.length < 2 || key.length > 40) return void 0;
|
|
590
590
|
if (!/^[a-z][a-z0-9-]*$/.test(key)) return void 0;
|
|
591
591
|
return key;
|
|
592
592
|
}
|
|
593
|
-
function
|
|
593
|
+
function legacyRoleLabels(roles, legacyRole) {
|
|
594
594
|
const raw = Array.isArray(roles) ? roles : [];
|
|
595
595
|
const out = [];
|
|
596
596
|
const seen = /* @__PURE__ */ new Set();
|
|
597
597
|
const push = (value) => {
|
|
598
|
-
const role =
|
|
599
|
-
if (!role || seen.has(role) || out.length >=
|
|
598
|
+
const role = normalizeLegacyRole(typeof value === "string" ? value : "");
|
|
599
|
+
if (!role || seen.has(role) || out.length >= 16) return;
|
|
600
600
|
seen.add(role);
|
|
601
|
-
out.push(role);
|
|
601
|
+
out.push(legacyRoleLabel(role));
|
|
602
602
|
};
|
|
603
603
|
for (const item of raw) push(item);
|
|
604
604
|
if (out.length === 0) push(legacyRole);
|
|
@@ -608,117 +608,83 @@ function normalizeProfileNotes(value) {
|
|
|
608
608
|
if (typeof value !== "string") return "";
|
|
609
609
|
return value.replace(/\r\n/g, "\n").trim().slice(0, PROFILE_NOTES_MAX);
|
|
610
610
|
}
|
|
611
|
-
function
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (seen.has(key)) continue;
|
|
619
|
-
seen.add(key);
|
|
620
|
-
out.push(hint);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return out;
|
|
624
|
-
}
|
|
625
|
-
function teamHintsForCustomRole(role) {
|
|
626
|
-
const slug = role.trim().toLowerCase();
|
|
627
|
-
if (!slug) return [];
|
|
628
|
-
if (slug.endsWith("-team") || slug.endsWith("-squad")) return [slug];
|
|
629
|
-
return [`${slug}-team`, slug];
|
|
611
|
+
function foldLegacyRolesIntoNotes(roles, legacyRole, notes) {
|
|
612
|
+
const text7 = normalizeProfileNotes(notes);
|
|
613
|
+
const labels = legacyRoleLabels(roles, legacyRole);
|
|
614
|
+
if (!labels.length) return text7;
|
|
615
|
+
if (text7.toLowerCase().startsWith("roles:")) return text7;
|
|
616
|
+
const prefix = `Roles: ${labels.join(", ")}`;
|
|
617
|
+
return text7 ? `${prefix}. ${text7}` : prefix;
|
|
630
618
|
}
|
|
631
619
|
function resolveAccountProfile(input) {
|
|
632
620
|
return resolveViewerProfile(input);
|
|
633
621
|
}
|
|
634
622
|
function resolveViewerProfile(account, project) {
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const projectNotes =
|
|
641
|
-
|
|
623
|
+
const accountNotes = foldLegacyRolesIntoNotes(
|
|
624
|
+
account?.roles,
|
|
625
|
+
account?.role,
|
|
626
|
+
account?.notes
|
|
627
|
+
);
|
|
628
|
+
const projectNotes = foldLegacyRolesIntoNotes(
|
|
629
|
+
project?.roles,
|
|
630
|
+
project?.role,
|
|
631
|
+
project?.notes
|
|
632
|
+
);
|
|
642
633
|
return {
|
|
643
|
-
|
|
644
|
-
roleLabels: roles.map((role) => accountRoleLabel(role)),
|
|
645
|
-
reviewTeamHints: reviewTeamHintsForRoles(roles),
|
|
646
|
-
notes,
|
|
634
|
+
notes: [accountNotes, projectNotes].filter(Boolean).join("\n"),
|
|
647
635
|
accountNotes,
|
|
648
|
-
projectNotes
|
|
649
|
-
rolesFromProject
|
|
636
|
+
projectNotes
|
|
650
637
|
};
|
|
651
638
|
}
|
|
652
|
-
function preferTeamsForRole(viewerTeams, hints) {
|
|
653
|
-
const teams = viewerTeams.map((t) => t.trim()).filter(Boolean);
|
|
654
|
-
const want = hints.map((t) => t.trim()).filter(Boolean);
|
|
655
|
-
if (!want.length) return teams;
|
|
656
|
-
if (!teams.length) return want;
|
|
657
|
-
const hintKeys = want.map((t) => t.toLowerCase());
|
|
658
|
-
const matched = teams.filter((team) => {
|
|
659
|
-
const key = team.toLowerCase();
|
|
660
|
-
return hintKeys.some((hint) => key === hint || key.includes(hint) || hint.includes(key));
|
|
661
|
-
});
|
|
662
|
-
return matched.length ? matched : teams;
|
|
663
|
-
}
|
|
664
639
|
function compactNotes(notes, max = 160) {
|
|
665
640
|
const oneLine = notes.replace(/\s+/g, " ").trim();
|
|
666
641
|
if (oneLine.length <= max) return oneLine;
|
|
667
642
|
return `${oneLine.slice(0, max - 1).trimEnd()}\u2026`;
|
|
668
643
|
}
|
|
669
644
|
function formatAccountProfilePlaybookLine(profile) {
|
|
670
|
-
if (
|
|
671
|
-
|
|
672
|
-
if (profile.roleLabels.length) {
|
|
673
|
-
parts.push(
|
|
674
|
-
`roles ${profile.roleLabels.join(", ")} (tickets to work on / PRs to review; prefer those teams)`
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
if (profile.notes) {
|
|
678
|
-
parts.push(`notes: ${compactNotes(profile.notes)}`);
|
|
679
|
-
}
|
|
680
|
-
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.`;
|
|
645
|
+
if (!profile.accountNotes) return "";
|
|
646
|
+
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).`;
|
|
681
647
|
}
|
|
682
648
|
function formatProjectProfilePlaybookLines(projects) {
|
|
683
649
|
const lines = projects.map((project) => {
|
|
684
|
-
const roles = (project.roleLabels ?? []).filter(Boolean);
|
|
685
650
|
const notes = normalizeProfileNotes(project.notes);
|
|
686
|
-
if (!
|
|
687
|
-
|
|
688
|
-
if (roles.length) bits.push(`roles ${roles.join(", ")}`);
|
|
689
|
-
if (notes) bits.push(compactNotes(notes, 120));
|
|
690
|
-
return `- ${project.name}: ${bits.join(". ")}`;
|
|
651
|
+
if (!notes) return "";
|
|
652
|
+
return `- ${project.name}: ${compactNotes(notes, 120)}`;
|
|
691
653
|
}).filter(Boolean);
|
|
692
654
|
if (!lines.length) return "";
|
|
693
|
-
return [
|
|
694
|
-
"\
|
|
695
|
-
|
|
655
|
+
return [
|
|
656
|
+
"Project context (Settings \u2192 Projects; adds to account context):",
|
|
657
|
+
...lines
|
|
658
|
+
].join("\n");
|
|
696
659
|
}
|
|
697
660
|
function formatWorkspaceProfileSuffix(profile) {
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
661
|
+
if (!profile.projectNotes) return "";
|
|
662
|
+
return ` context:${compactNotes(profile.projectNotes, 80)}`;
|
|
663
|
+
}
|
|
664
|
+
function formatViewerContextDirective(profile) {
|
|
665
|
+
const account = profile.accountNotes || "(empty)";
|
|
666
|
+
const project = profile.projectNotes || "(empty \u2014 account context applies)";
|
|
667
|
+
return [
|
|
668
|
+
"Viewer context (Settings \u2192 Agents / Projects) \u2014 how to find this user\u2019s tickets and review PRs:",
|
|
669
|
+
`- Account: ${compactNotes(profile.accountNotes || account, 240)}`,
|
|
670
|
+
`- This project: ${compactNotes(profile.projectNotes || project, 240)}`,
|
|
671
|
+
"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."
|
|
672
|
+
].join("\n");
|
|
673
|
+
}
|
|
674
|
+
function formatViewerContextReminder() {
|
|
675
|
+
return "Viewer context: get_viewer_context to read; update_viewer_context only after ask_user confirms (Save this context / Do not save).";
|
|
702
676
|
}
|
|
703
|
-
var
|
|
677
|
+
var PROFILE_NOTES_MAX, LEGACY_ROLE_LABELS;
|
|
704
678
|
var init_account_profile = __esm({
|
|
705
679
|
"src/store/account-profile.ts"() {
|
|
706
680
|
"use strict";
|
|
707
|
-
ACCOUNT_ROLE_PRESETS = ["engineering", "design", "product"];
|
|
708
|
-
ACCOUNT_ROLES = ACCOUNT_ROLE_PRESETS;
|
|
709
|
-
ACCOUNT_ROLE_MAX = 16;
|
|
710
681
|
PROFILE_NOTES_MAX = 2e3;
|
|
711
|
-
|
|
682
|
+
LEGACY_ROLE_LABELS = {
|
|
712
683
|
engineering: "Engineering",
|
|
713
684
|
design: "Design",
|
|
714
685
|
product: "Product",
|
|
715
686
|
qa: "QA"
|
|
716
687
|
};
|
|
717
|
-
ROLE_TEAM_HINTS = {
|
|
718
|
-
engineering: ["engineering-team", "engineering", "eng-team"],
|
|
719
|
-
design: ["design-team", "design"],
|
|
720
|
-
product: ["product-team", "product"]
|
|
721
|
-
};
|
|
722
688
|
}
|
|
723
689
|
});
|
|
724
690
|
|
|
@@ -982,16 +948,11 @@ var init_issue_source_labels = __esm({
|
|
|
982
948
|
// src/store/app-settings.ts
|
|
983
949
|
var app_settings_exports = {};
|
|
984
950
|
__export(app_settings_exports, {
|
|
985
|
-
ACCOUNT_ROLES: () => ACCOUNT_ROLES,
|
|
986
|
-
ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
|
|
987
|
-
ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
|
|
988
|
-
ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
|
|
989
951
|
FOLLOW_UP_BEHAVIORS: () => FOLLOW_UP_BEHAVIORS,
|
|
990
952
|
GITHUB_GIT_AUTH_MODES: () => GITHUB_GIT_AUTH_MODES,
|
|
991
953
|
HARNESS_ENV_KEYS: () => HARNESS_ENV_KEYS,
|
|
992
954
|
ISSUE_SOURCE_LABELS: () => ISSUE_SOURCE_LABELS,
|
|
993
955
|
PROFILE_NOTES_MAX: () => PROFILE_NOTES_MAX,
|
|
994
|
-
accountRoleLabel: () => accountRoleLabel,
|
|
995
956
|
appSettingsPath: () => appSettingsPath,
|
|
996
957
|
applyAppEnvironment: () => applyAppEnvironment,
|
|
997
958
|
autoArchiveOnMergeEnabled: () => autoArchiveOnMergeEnabled,
|
|
@@ -1015,9 +976,12 @@ __export(app_settings_exports, {
|
|
|
1015
976
|
emptyPublicIntegrations: () => emptyPublicIntegrations,
|
|
1016
977
|
ensureSlackDeviceIdentity: () => ensureSlackDeviceIdentity,
|
|
1017
978
|
findProjectProfileKey: () => findProjectProfileKey,
|
|
979
|
+
foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
|
|
1018
980
|
followUpBehavior: () => followUpBehavior,
|
|
1019
981
|
formatAccountProfilePlaybookLine: () => formatAccountProfilePlaybookLine,
|
|
1020
982
|
formatProjectProfilePlaybookLines: () => formatProjectProfilePlaybookLines,
|
|
983
|
+
formatViewerContextDirective: () => formatViewerContextDirective,
|
|
984
|
+
formatViewerContextReminder: () => formatViewerContextReminder,
|
|
1021
985
|
formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
|
|
1022
986
|
getAbleTimeAccessToken: () => getAbleTimeAccessToken,
|
|
1023
987
|
getAbleTimeHost: () => getAbleTimeHost,
|
|
@@ -1038,7 +1002,7 @@ __export(app_settings_exports, {
|
|
|
1038
1002
|
maxConcurrentAgents: () => maxConcurrentAgents,
|
|
1039
1003
|
orchestrationQuotaFallbackAgent: () => orchestrationQuotaFallbackAgent,
|
|
1040
1004
|
orchestrationQuotaOnLimit: () => orchestrationQuotaOnLimit,
|
|
1041
|
-
|
|
1005
|
+
readViewerContext: () => readViewerContext,
|
|
1042
1006
|
resolveAccountProfile: () => resolveAccountProfile,
|
|
1043
1007
|
resolveAccountProfileFromSettings: () => resolveAccountProfileFromSettings,
|
|
1044
1008
|
resolveAgentExecutable: () => resolveAgentExecutable,
|
|
@@ -1048,7 +1012,6 @@ __export(app_settings_exports, {
|
|
|
1048
1012
|
resolveThreadDefaults: () => resolveThreadDefaults,
|
|
1049
1013
|
resolveViewerProfile: () => resolveViewerProfile,
|
|
1050
1014
|
resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
|
|
1051
|
-
reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
|
|
1052
1015
|
saveAbleTimeConnection: () => saveAbleTimeConnection,
|
|
1053
1016
|
saveAppSettings: () => saveAppSettings,
|
|
1054
1017
|
saveLinearOAuth: () => saveLinearOAuth,
|
|
@@ -1065,7 +1028,8 @@ __export(app_settings_exports, {
|
|
|
1065
1028
|
updateDefaultsSettings: () => updateDefaultsSettings,
|
|
1066
1029
|
updateIntegrationsSettings: () => updateIntegrationsSettings,
|
|
1067
1030
|
updateOpencodeSettings: () => updateOpencodeSettings,
|
|
1068
|
-
updateProjectProfileSettings: () => updateProjectProfileSettings
|
|
1031
|
+
updateProjectProfileSettings: () => updateProjectProfileSettings,
|
|
1032
|
+
writeViewerContext: () => writeViewerContext
|
|
1069
1033
|
});
|
|
1070
1034
|
function emptyPublicIntegrations() {
|
|
1071
1035
|
return {
|
|
@@ -1323,9 +1287,7 @@ function normalizeDefaults(raw) {
|
|
|
1323
1287
|
if (typeof source.fast === "boolean") {
|
|
1324
1288
|
out.fast = source.fast;
|
|
1325
1289
|
}
|
|
1326
|
-
const
|
|
1327
|
-
if (roles.length) out.roles = roles;
|
|
1328
|
-
const notes = normalizeProfileNotes(source.notes);
|
|
1290
|
+
const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
|
|
1329
1291
|
if (notes) out.notes = notes;
|
|
1330
1292
|
return out;
|
|
1331
1293
|
}
|
|
@@ -1336,11 +1298,9 @@ function normalizeProjectProfile(raw) {
|
|
|
1336
1298
|
if (!raw || typeof raw !== "object") return void 0;
|
|
1337
1299
|
const source = raw;
|
|
1338
1300
|
const out = {};
|
|
1339
|
-
const
|
|
1340
|
-
if (roles.length) out.roles = roles;
|
|
1341
|
-
const notes = normalizeProfileNotes(source.notes);
|
|
1301
|
+
const notes = foldLegacyRolesIntoNotes(source.roles, source.role, source.notes);
|
|
1342
1302
|
if (notes) out.notes = notes;
|
|
1343
|
-
return out.
|
|
1303
|
+
return out.notes ? out : void 0;
|
|
1344
1304
|
}
|
|
1345
1305
|
function normalizeProjects(raw) {
|
|
1346
1306
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
@@ -1899,17 +1859,13 @@ function updateDefaultsSettings(patch) {
|
|
|
1899
1859
|
defaults.fast = Boolean(patch.fast);
|
|
1900
1860
|
}
|
|
1901
1861
|
}
|
|
1902
|
-
if ("roles" in patch) {
|
|
1903
|
-
const roles = normalizeAccountRoles(patch.roles);
|
|
1904
|
-
if (!roles.length) delete defaults.roles;
|
|
1905
|
-
else defaults.roles = roles;
|
|
1906
|
-
delete defaults.role;
|
|
1907
|
-
}
|
|
1908
1862
|
if ("notes" in patch) {
|
|
1909
1863
|
const notes = normalizeProfileNotes(patch.notes);
|
|
1910
1864
|
if (!notes) delete defaults.notes;
|
|
1911
1865
|
else defaults.notes = notes;
|
|
1912
1866
|
}
|
|
1867
|
+
delete defaults.roles;
|
|
1868
|
+
delete defaults.role;
|
|
1913
1869
|
return saveAppSettings({ ...current, defaults });
|
|
1914
1870
|
}
|
|
1915
1871
|
function updateProjectProfileSettings(repoPath, patch) {
|
|
@@ -1919,17 +1875,14 @@ function updateProjectProfileSettings(repoPath, patch) {
|
|
|
1919
1875
|
const projects = { ...current.projects };
|
|
1920
1876
|
const existing = projects[key] ?? {};
|
|
1921
1877
|
const next = { ...existing };
|
|
1922
|
-
if ("roles" in patch) {
|
|
1923
|
-
const roles = normalizeAccountRoles(patch.roles);
|
|
1924
|
-
if (!roles.length) delete next.roles;
|
|
1925
|
-
else next.roles = roles;
|
|
1926
|
-
}
|
|
1927
1878
|
if ("notes" in patch) {
|
|
1928
1879
|
const notes = normalizeProfileNotes(patch.notes);
|
|
1929
1880
|
if (!notes) delete next.notes;
|
|
1930
1881
|
else next.notes = notes;
|
|
1931
1882
|
}
|
|
1932
|
-
|
|
1883
|
+
delete next.roles;
|
|
1884
|
+
delete next.role;
|
|
1885
|
+
if (!next.notes) delete projects[key];
|
|
1933
1886
|
else projects[key] = next;
|
|
1934
1887
|
return saveAppSettings({ ...current, projects });
|
|
1935
1888
|
}
|
|
@@ -1954,6 +1907,60 @@ function resolveViewerProfileForRepo(repoPath, settings = loadAppSettings()) {
|
|
|
1954
1907
|
const project = key ? settings.projects[key] : void 0;
|
|
1955
1908
|
return resolveViewerProfile(settings.defaults, project);
|
|
1956
1909
|
}
|
|
1910
|
+
function readViewerContext(repoPath) {
|
|
1911
|
+
const settings = loadAppSettings();
|
|
1912
|
+
const key = findProjectProfileKey(settings.projects, repoPath);
|
|
1913
|
+
const resolved = resolveViewerProfile(
|
|
1914
|
+
settings.defaults,
|
|
1915
|
+
key ? settings.projects[key] : void 0
|
|
1916
|
+
);
|
|
1917
|
+
return {
|
|
1918
|
+
account: resolved.accountNotes,
|
|
1919
|
+
project: resolved.projectNotes,
|
|
1920
|
+
combined: resolved.notes,
|
|
1921
|
+
repoPath: key ?? (repoPath?.trim() ? profileRepoKey(repoPath) : null)
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
function writeViewerContext(input) {
|
|
1925
|
+
const proposed = normalizeProfileNotes(input.context);
|
|
1926
|
+
if (input.scope === "project") {
|
|
1927
|
+
const settings = loadAppSettings();
|
|
1928
|
+
const key = findProjectProfileKey(settings.projects, input.repoPath) ?? profileRepoKey(input.repoPath ?? "");
|
|
1929
|
+
if (!key) {
|
|
1930
|
+
return {
|
|
1931
|
+
ok: false,
|
|
1932
|
+
error: "repoPath required",
|
|
1933
|
+
message: "Pass repoPath (or run from a registered worktree) when updating project context.",
|
|
1934
|
+
current: "",
|
|
1935
|
+
proposed
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
const current2 = resolveViewerProfileForRepo(key, settings).projectNotes;
|
|
1939
|
+
if (!input.confirmed) {
|
|
1940
|
+
return {
|
|
1941
|
+
ok: false,
|
|
1942
|
+
error: "Confirmation required",
|
|
1943
|
+
message: CONFIRM_PROJECT,
|
|
1944
|
+
current: current2,
|
|
1945
|
+
proposed
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
updateProjectProfileSettings(key, { notes: proposed || null });
|
|
1949
|
+
return { ok: true, scope: "project", context: proposed, repoPath: key };
|
|
1950
|
+
}
|
|
1951
|
+
const current = resolveAccountProfileFromSettings().accountNotes;
|
|
1952
|
+
if (!input.confirmed) {
|
|
1953
|
+
return {
|
|
1954
|
+
ok: false,
|
|
1955
|
+
error: "Confirmation required",
|
|
1956
|
+
message: CONFIRM_ACCOUNT,
|
|
1957
|
+
current,
|
|
1958
|
+
proposed
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
updateDefaultsSettings({ notes: proposed || null });
|
|
1962
|
+
return { ok: true, scope: "account", context: proposed };
|
|
1963
|
+
}
|
|
1957
1964
|
function resolveThreadDefaults(settings = loadAppSettings()) {
|
|
1958
1965
|
return {
|
|
1959
1966
|
agent: getDefaultAgent(settings),
|
|
@@ -2300,7 +2307,7 @@ function childEnvWithAppSettings(extra) {
|
|
|
2300
2307
|
function harnessEnvKey(harness) {
|
|
2301
2308
|
return HARNESS_ENV_KEYS[harness];
|
|
2302
2309
|
}
|
|
2303
|
-
var import_node_crypto2, import_node_fs7, import_node_os3, import_node_path8, 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;
|
|
2310
|
+
var import_node_crypto2, import_node_fs7, import_node_os3, import_node_path8, 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;
|
|
2304
2311
|
var init_app_settings = __esm({
|
|
2305
2312
|
"src/store/app-settings.ts"() {
|
|
2306
2313
|
"use strict";
|
|
@@ -2351,6 +2358,8 @@ var init_app_settings = __esm({
|
|
|
2351
2358
|
projects: {},
|
|
2352
2359
|
advanced: {}
|
|
2353
2360
|
};
|
|
2361
|
+
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.";
|
|
2362
|
+
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.";
|
|
2354
2363
|
DEFAULT_CLI_BIN = {
|
|
2355
2364
|
claude: "claude",
|
|
2356
2365
|
codex: "codex",
|
|
@@ -4373,12 +4382,12 @@ var init_worktree_labels = __esm({
|
|
|
4373
4382
|
});
|
|
4374
4383
|
|
|
4375
4384
|
// src/git/gh-errors.ts
|
|
4376
|
-
function isGhRateLimitError(
|
|
4377
|
-
return /API rate limit (already )?exceeded/i.test(
|
|
4385
|
+
function isGhRateLimitError(text7) {
|
|
4386
|
+
return /API rate limit (already )?exceeded/i.test(text7) || /rate limit exceeded/i.test(text7);
|
|
4378
4387
|
}
|
|
4379
|
-
function isGhPrBodyTooLongError(
|
|
4388
|
+
function isGhPrBodyTooLongError(text7) {
|
|
4380
4389
|
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(
|
|
4381
|
-
|
|
4390
|
+
text7
|
|
4382
4391
|
);
|
|
4383
4392
|
}
|
|
4384
4393
|
function clampGithubPrBody(body, max = GITHUB_PR_BODY_SAFE_CHARS) {
|
|
@@ -4398,21 +4407,21 @@ function formatRateLimitResetHint(resetEpochSec, nowMs = Date.now()) {
|
|
|
4398
4407
|
const hours = Math.ceil(mins / 60);
|
|
4399
4408
|
return `in about ${hours} hour${hours === 1 ? "" : "s"}`;
|
|
4400
4409
|
}
|
|
4401
|
-
function omitGhBodyArg(
|
|
4402
|
-
return
|
|
4410
|
+
function omitGhBodyArg(text7) {
|
|
4411
|
+
return text7.replace(
|
|
4403
4412
|
/--body(?:-file)?(\s+|=)(?:'[^']*'|"[^"]*"|\S+)/g,
|
|
4404
4413
|
"--body <omitted>"
|
|
4405
4414
|
);
|
|
4406
4415
|
}
|
|
4407
|
-
function capGhErrorText(
|
|
4408
|
-
const t =
|
|
4416
|
+
function capGhErrorText(text7, max = 400) {
|
|
4417
|
+
const t = text7.trim();
|
|
4409
4418
|
if (t.length <= max) return t;
|
|
4410
4419
|
return `${t.slice(0, max - 1).trimEnd()}\u2026`;
|
|
4411
4420
|
}
|
|
4412
|
-
function extractGhErrorDetail(
|
|
4413
|
-
const trimmed = omitGhBodyArg(
|
|
4421
|
+
function extractGhErrorDetail(text7) {
|
|
4422
|
+
const trimmed = omitGhBodyArg(text7.trim());
|
|
4414
4423
|
if (!trimmed) return "";
|
|
4415
|
-
if (isGhPrBodyTooLongError(
|
|
4424
|
+
if (isGhPrBodyTooLongError(text7) || isGhPrBodyTooLongError(trimmed)) {
|
|
4416
4425
|
return "GraphQL: body is too long (GitHub PR body / request size limit).";
|
|
4417
4426
|
}
|
|
4418
4427
|
const graphql = trimmed.match(/\bGraphQL:\s*(.+)$/im);
|
|
@@ -4452,9 +4461,9 @@ function formatGhLandError(raw, opts) {
|
|
|
4452
4461
|
}
|
|
4453
4462
|
return detail || "Failed to create or update pull request";
|
|
4454
4463
|
}
|
|
4455
|
-
function isPrNotMergeableError(
|
|
4464
|
+
function isPrNotMergeableError(text7) {
|
|
4456
4465
|
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(
|
|
4457
|
-
|
|
4466
|
+
text7
|
|
4458
4467
|
);
|
|
4459
4468
|
}
|
|
4460
4469
|
function formatMergePrError(raw) {
|
|
@@ -4666,8 +4675,8 @@ var init_github_agent_auth = __esm({
|
|
|
4666
4675
|
});
|
|
4667
4676
|
|
|
4668
4677
|
// src/git/stale-lock.ts
|
|
4669
|
-
function isIndexLockError(
|
|
4670
|
-
return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(
|
|
4678
|
+
function isIndexLockError(text7) {
|
|
4679
|
+
return /Unable to create ['"][^'"]*index\.lock['"]: File exists/i.test(text7);
|
|
4671
4680
|
}
|
|
4672
4681
|
function clearStaleIndexLock(gitDir, maxAgeMs = STALE_INDEX_LOCK_MS, now = Date.now()) {
|
|
4673
4682
|
const lockPath = (0, import_node_path17.join)(gitDir, "index.lock");
|
|
@@ -6090,14 +6099,7 @@ async function listPrs(repoPath, opts) {
|
|
|
6090
6099
|
const limit = resolved.limit ?? 200;
|
|
6091
6100
|
const reviewerKey = (resolved.reviewer ?? "").trim().toLowerCase();
|
|
6092
6101
|
const needsUnclaimed = reviewerKey === "unassigned" || reviewerKey === "none" || reviewerKey === "null";
|
|
6093
|
-
|
|
6094
|
-
try {
|
|
6095
|
-
profile = resolveViewerProfileForRepo(repoPath);
|
|
6096
|
-
} catch {
|
|
6097
|
-
profile = resolveViewerProfile();
|
|
6098
|
-
}
|
|
6099
|
-
const fetchedTeams = needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : [];
|
|
6100
|
-
const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? preferTeamsForRole(fetchedTeams, profile.reviewTeamHints) : void 0);
|
|
6102
|
+
const viewerTeams = resolved.viewerTeams ?? (needsUnclaimed ? await listGithubViewerTeamSlugs(repoPath).catch(() => []) : void 0);
|
|
6101
6103
|
const fetchLimit = prListFetchLimit(limit, resolved.reviewer);
|
|
6102
6104
|
const args = buildGhPrListArgs({
|
|
6103
6105
|
slug,
|
|
@@ -7430,16 +7432,15 @@ function accountRolePlaybookLine() {
|
|
|
7430
7432
|
return "";
|
|
7431
7433
|
}
|
|
7432
7434
|
}
|
|
7433
|
-
function
|
|
7435
|
+
function accountContextReminderLine() {
|
|
7434
7436
|
let profile;
|
|
7435
7437
|
try {
|
|
7436
7438
|
profile = resolveAccountProfileFromSettings();
|
|
7437
7439
|
} catch {
|
|
7438
7440
|
return "";
|
|
7439
7441
|
}
|
|
7440
|
-
if (
|
|
7441
|
-
|
|
7442
|
-
return `- Viewer profile: ${roles}. Use Settings notes with list_issues / list_prs when they ask to find work.`;
|
|
7442
|
+
if (!profile.accountNotes) return "";
|
|
7443
|
+
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.";
|
|
7443
7444
|
}
|
|
7444
7445
|
function projectProfilePlaybookBlock() {
|
|
7445
7446
|
let settings;
|
|
@@ -7456,7 +7457,6 @@ function projectProfilePlaybookBlock() {
|
|
|
7456
7457
|
const key = path2.replace(/\/+$/, "");
|
|
7457
7458
|
return {
|
|
7458
7459
|
name: names.get(key) ?? key.split("/").filter(Boolean).pop() ?? path2,
|
|
7459
|
-
roleLabels: resolved.rolesFromProject ? resolved.roleLabels : [],
|
|
7460
7460
|
notes: resolved.projectNotes
|
|
7461
7461
|
};
|
|
7462
7462
|
});
|
|
@@ -7470,7 +7470,7 @@ function coordinatorTurnReminder(opts) {
|
|
|
7470
7470
|
`- YOUR orchestration thread id is ${opts.parentId} \u2014 pass parentThreadId="${opts.parentId}" on create_thread, or omit it.`,
|
|
7471
7471
|
goal ? `- Goal / title: ${goal}` : null,
|
|
7472
7472
|
accountDefaultsPlaybookLine(),
|
|
7473
|
-
|
|
7473
|
+
accountContextReminderLine() || null,
|
|
7474
7474
|
"- 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."
|
|
7475
7475
|
].filter(Boolean).join("\n");
|
|
7476
7476
|
}
|
|
@@ -7524,7 +7524,7 @@ function ensureGlobalCoordinatorCwd(opts) {
|
|
|
7524
7524
|
"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.",
|
|
7525
7525
|
"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).",
|
|
7526
7526
|
"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.",
|
|
7527
|
-
"Typical flow (find work): list_workspaces \u2192 pick repo(s) matching
|
|
7527
|
+
"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.",
|
|
7528
7528
|
'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".',
|
|
7529
7529
|
"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.",
|
|
7530
7530
|
"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."
|
|
@@ -7578,10 +7578,10 @@ var init_coordinator_prompt = __esm({
|
|
|
7578
7578
|
"Role: you oversee Sideboard worktree agents across registered repos. You do not live inside one of those worktrees.",
|
|
7579
7579
|
"Sideboard MCP (fleet control \u2014 prefer these for status and orchestration):",
|
|
7580
7580
|
"Discover:",
|
|
7581
|
-
"- list_workspaces \u2014 registered repos (path + github slug +
|
|
7581
|
+
"- 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.",
|
|
7582
7582
|
"- 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.",
|
|
7583
|
-
'- 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.
|
|
7584
|
-
"- 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)
|
|
7583
|
+
'- 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).',
|
|
7584
|
+
"- 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.",
|
|
7585
7585
|
"- 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.",
|
|
7586
7586
|
"- 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.",
|
|
7587
7587
|
"- 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.",
|
|
@@ -7591,7 +7591,8 @@ var init_coordinator_prompt = __esm({
|
|
|
7591
7591
|
"- get_pr_stack / open_pr_stack_layers / add_stack_layer / create_pr_stack \u2014 GitHub stacked PRs (`gh stack`); one worktree per layer",
|
|
7592
7592
|
"- list_models \u2014 only when you need a specific model (rare); otherwise omit model so Account defaults apply",
|
|
7593
7593
|
"- 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.",
|
|
7594
|
-
"- 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.",
|
|
7594
|
+
"- 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.",
|
|
7595
|
+
"- 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.",
|
|
7595
7596
|
"- 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.",
|
|
7596
7597
|
"- 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.",
|
|
7597
7598
|
"Workspaces:",
|
|
@@ -8126,8 +8127,8 @@ ${input.permalink}` : "";
|
|
|
8126
8127
|
|
|
8127
8128
|
${body}${link}`;
|
|
8128
8129
|
}
|
|
8129
|
-
function isSlackExternalReplyPrompt(
|
|
8130
|
-
return
|
|
8130
|
+
function isSlackExternalReplyPrompt(text7) {
|
|
8131
|
+
return text7.startsWith("Slack reply from ") && text7.includes("not a command");
|
|
8131
8132
|
}
|
|
8132
8133
|
function pendingSlackExternalReplies(messages) {
|
|
8133
8134
|
let i = messages.length - 1;
|
|
@@ -8168,9 +8169,9 @@ async function continueSourceThread(threadId, prompt) {
|
|
|
8168
8169
|
function listSlackOutboundWatches() {
|
|
8169
8170
|
return pruneWatches(readStore3());
|
|
8170
8171
|
}
|
|
8171
|
-
function formatOwnerSlackFyi(userName,
|
|
8172
|
+
function formatOwnerSlackFyi(userName, text7) {
|
|
8172
8173
|
const who = userName.trim() || "Someone";
|
|
8173
|
-
const body =
|
|
8174
|
+
const body = text7.trim() || "(no text)";
|
|
8174
8175
|
return `${who} replied in Slack:
|
|
8175
8176
|
${body}`;
|
|
8176
8177
|
}
|
|
@@ -8185,7 +8186,7 @@ async function relayExternalReply(opts) {
|
|
|
8185
8186
|
const thread = readThread(threadId);
|
|
8186
8187
|
if (!thread || thread.status === "archived") return "ignored";
|
|
8187
8188
|
try {
|
|
8188
|
-
const
|
|
8189
|
+
const text7 = formatSlackExternalReplyPrompt({
|
|
8189
8190
|
userName: opts.reply.userName,
|
|
8190
8191
|
kind: opts.watch.kind,
|
|
8191
8192
|
toLabel: opts.watch.toLabel,
|
|
@@ -8194,7 +8195,7 @@ async function relayExternalReply(opts) {
|
|
|
8194
8195
|
});
|
|
8195
8196
|
appendMessage(threadId, {
|
|
8196
8197
|
role: "agent",
|
|
8197
|
-
text:
|
|
8198
|
+
text: text7,
|
|
8198
8199
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
8199
8200
|
});
|
|
8200
8201
|
} catch {
|
|
@@ -8508,20 +8509,20 @@ function looksLikeMinifiedJsDump(line) {
|
|
|
8508
8509
|
function looksLikeNestedElectronCrash(line) {
|
|
8509
8510
|
return /HasCustomHostObject|ElectronInitializeICUandStartNode/i.test(line);
|
|
8510
8511
|
}
|
|
8511
|
-
function looksLikeNativeEventLoopCrash(
|
|
8512
|
-
return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(
|
|
8512
|
+
function looksLikeNativeEventLoopCrash(text7) {
|
|
8513
|
+
return /uv_run|uv__io_poll|SpinEventLoopInternal/i.test(text7);
|
|
8513
8514
|
}
|
|
8514
|
-
function looksLikeV8Oom(
|
|
8515
|
+
function looksLikeV8Oom(text7) {
|
|
8515
8516
|
return /javascript heap|reached heap limit|OOMErrorHandler|FatalProcessOutOfMemory|Allocation failed - JavaScript heap/i.test(
|
|
8516
|
-
|
|
8517
|
+
text7
|
|
8517
8518
|
);
|
|
8518
8519
|
}
|
|
8519
|
-
function looksLikeHomebrewLibuvCrash(
|
|
8520
|
-
if (!looksLikeNativeEventLoopCrash(
|
|
8521
|
-
return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(
|
|
8520
|
+
function looksLikeHomebrewLibuvCrash(text7) {
|
|
8521
|
+
if (!looksLikeNativeEventLoopCrash(text7)) return false;
|
|
8522
|
+
return /Cellar\/(?:libuv|node)|libuv\.\d+\.dylib/i.test(text7);
|
|
8522
8523
|
}
|
|
8523
|
-
function clipStderr(
|
|
8524
|
-
const trimmed =
|
|
8524
|
+
function clipStderr(text7, maxChars) {
|
|
8525
|
+
const trimmed = text7.trim();
|
|
8525
8526
|
if (trimmed.length <= maxChars) return trimmed;
|
|
8526
8527
|
return trimmed.slice(0, maxChars);
|
|
8527
8528
|
}
|
|
@@ -8555,16 +8556,16 @@ function summarizeTurnStderr(tail, maxChars = 500) {
|
|
|
8555
8556
|
const joined = (useful.length ? useful : tail).slice(-6).join("\n").trim();
|
|
8556
8557
|
return clipStderr(joined, maxChars);
|
|
8557
8558
|
}
|
|
8558
|
-
function looksLikeInvalidAgentSession(
|
|
8559
|
-
const lower =
|
|
8559
|
+
function looksLikeInvalidAgentSession(text7) {
|
|
8560
|
+
const lower = text7.trim().toLowerCase();
|
|
8560
8561
|
if (!lower) return false;
|
|
8561
8562
|
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);
|
|
8562
8563
|
}
|
|
8563
|
-
function looksLikeRetryableRunnerCrash(
|
|
8564
|
-
if (looksLikeAgentFailureMessage(
|
|
8565
|
-
if (looksLikeInvalidAgentSession(
|
|
8566
|
-
if (looksLikeV8Oom(
|
|
8567
|
-
const lower =
|
|
8564
|
+
function looksLikeRetryableRunnerCrash(text7) {
|
|
8565
|
+
if (looksLikeAgentFailureMessage(text7)) return false;
|
|
8566
|
+
if (looksLikeInvalidAgentSession(text7)) return false;
|
|
8567
|
+
if (looksLikeV8Oom(text7)) return false;
|
|
8568
|
+
const lower = text7.trim().toLowerCase();
|
|
8568
8569
|
if (/cannot find (?:package|module)|err_module_not_found/.test(lower)) return false;
|
|
8569
8570
|
if (!lower) return true;
|
|
8570
8571
|
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(
|
|
@@ -8576,12 +8577,12 @@ function shouldRetryFailedAgentTurn(detail, opts) {
|
|
|
8576
8577
|
if (looksLikeV8Oom(detail) && opts.hasSession) return true;
|
|
8577
8578
|
return looksLikeRetryableRunnerCrash(detail);
|
|
8578
8579
|
}
|
|
8579
|
-
function looksLikeAgentFailureMessage(
|
|
8580
|
-
const lower =
|
|
8580
|
+
function looksLikeAgentFailureMessage(text7) {
|
|
8581
|
+
const lower = text7.trim().toLowerCase();
|
|
8581
8582
|
if (!lower) return false;
|
|
8582
8583
|
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(
|
|
8583
8584
|
lower
|
|
8584
|
-
) || /\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(
|
|
8585
|
+
) || /\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);
|
|
8585
8586
|
}
|
|
8586
8587
|
function fallbackTurnFailDetail(assistantText) {
|
|
8587
8588
|
const t = assistantText.trim();
|
|
@@ -9640,10 +9641,10 @@ var init_brightsy = __esm({
|
|
|
9640
9641
|
});
|
|
9641
9642
|
|
|
9642
9643
|
// src/agents/claude-mcp.ts
|
|
9643
|
-
function parseMcpList(
|
|
9644
|
+
function parseMcpList(text7) {
|
|
9644
9645
|
const servers = [];
|
|
9645
9646
|
const seen = /* @__PURE__ */ new Set();
|
|
9646
|
-
for (const raw of
|
|
9647
|
+
for (const raw of text7.split("\n")) {
|
|
9647
9648
|
const line = raw.trim();
|
|
9648
9649
|
if (!line || /^Checking MCP/i.test(line)) continue;
|
|
9649
9650
|
const m = line.match(/^(.+?):\s+\S+/);
|
|
@@ -9763,6 +9764,9 @@ function toolDescription(name, input) {
|
|
|
9763
9764
|
if (/present_files$/i.test(name)) {
|
|
9764
9765
|
return str2(input?.title) ? `Files ${str2(input?.title)}` : "Present files";
|
|
9765
9766
|
}
|
|
9767
|
+
if (/wait_for_job$/i.test(name)) {
|
|
9768
|
+
return str2(input?.id) ? `Wait for ${str2(input?.id)}` : "Wait for job";
|
|
9769
|
+
}
|
|
9766
9770
|
if (isSubagentToolName(name)) {
|
|
9767
9771
|
const desc = str2(input?.description);
|
|
9768
9772
|
const sub = asRecord(input?.subagentType);
|
|
@@ -9831,8 +9835,8 @@ function isSubagentToolName(name) {
|
|
|
9831
9835
|
if (/^(task|agent|spawn_agent)$/i.test(n)) return true;
|
|
9832
9836
|
return /connectedAgentRequest/i.test(n);
|
|
9833
9837
|
}
|
|
9834
|
-
function isInternalAgentStatusText(
|
|
9835
|
-
const t =
|
|
9838
|
+
function isInternalAgentStatusText(text7) {
|
|
9839
|
+
const t = text7.trim();
|
|
9836
9840
|
if (!t) return false;
|
|
9837
9841
|
if (/waiting for gate to pass/i.test(t)) return true;
|
|
9838
9842
|
if (/^agent is running\b/i.test(t) && t.length < 160) return true;
|
|
@@ -9846,9 +9850,9 @@ function lastTextPart(parts, type) {
|
|
|
9846
9850
|
for (let i = parts.length - 1; i >= 0; i--) {
|
|
9847
9851
|
const p = parts[i];
|
|
9848
9852
|
if (p.type !== type) continue;
|
|
9849
|
-
const
|
|
9850
|
-
if (!
|
|
9851
|
-
return
|
|
9853
|
+
const text7 = p.text.trim();
|
|
9854
|
+
if (!text7 || isInternalAgentStatusText(text7)) continue;
|
|
9855
|
+
return text7;
|
|
9852
9856
|
}
|
|
9853
9857
|
return "";
|
|
9854
9858
|
}
|
|
@@ -9937,7 +9941,7 @@ function liveActivitySummary(parts, opts) {
|
|
|
9937
9941
|
);
|
|
9938
9942
|
const runningPoll = [...tools].reverse().find((t) => t.status === "running" && isPollWrapperToolName(t.name));
|
|
9939
9943
|
const thinking = lastTextPart(parts, "thinking");
|
|
9940
|
-
const
|
|
9944
|
+
const text7 = lastTextPart(parts, "text");
|
|
9941
9945
|
if (runningSubs.length > 0) {
|
|
9942
9946
|
const heads = runningSubs.map(toolLabel);
|
|
9943
9947
|
const head = runningSubs.length === 1 ? heads[0] : `${runningSubs.length} subagents \xB7 ${heads.slice(0, 2).join(" \xB7 ")}`;
|
|
@@ -9947,7 +9951,7 @@ function liveActivitySummary(parts, opts) {
|
|
|
9947
9951
|
if (runningTop) return toolLabel(runningTop);
|
|
9948
9952
|
if (runningPoll) return toolLabel(runningPoll);
|
|
9949
9953
|
if (thinking) return thinking.length > 96 ? `\u2026${thinking.slice(-96)}` : thinking;
|
|
9950
|
-
if (
|
|
9954
|
+
if (text7) return "Writing reply\u2026";
|
|
9951
9955
|
const last = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? tools.at(-1);
|
|
9952
9956
|
if (last) {
|
|
9953
9957
|
const label = toolLabel(last);
|
|
@@ -9979,9 +9983,9 @@ function toolFilePath(input) {
|
|
|
9979
9983
|
if (!input) return void 0;
|
|
9980
9984
|
return str2(input.file_path) ?? str2(input.path) ?? str2(input.filePath) ?? str2(input.filename);
|
|
9981
9985
|
}
|
|
9982
|
-
function countLines(
|
|
9983
|
-
if (!
|
|
9984
|
-
return
|
|
9986
|
+
function countLines(text7) {
|
|
9987
|
+
if (!text7) return 0;
|
|
9988
|
+
return text7.split("\n").length;
|
|
9985
9989
|
}
|
|
9986
9990
|
function diffFromInput(input) {
|
|
9987
9991
|
if (!input) return {};
|
|
@@ -10206,9 +10210,9 @@ function partsToAssistantText(parts) {
|
|
|
10206
10210
|
(p) => p.type === "text" && !messagePartParentId(p)
|
|
10207
10211
|
).map((p) => p.text).join("").trim();
|
|
10208
10212
|
}
|
|
10209
|
-
function stripBrightsyNdjsonNoise(
|
|
10210
|
-
if (!
|
|
10211
|
-
let out =
|
|
10213
|
+
function stripBrightsyNdjsonNoise(text7) {
|
|
10214
|
+
if (!text7 || !text7.includes('"type"')) return text7;
|
|
10215
|
+
let out = text7;
|
|
10212
10216
|
out = out.replace(
|
|
10213
10217
|
/\{"type":"(?:tool_use|tool_result|tool|thinking|usage|done|error)"[\s\S]*?\}\s*(?=\{"type":"|$|(?=[A-Za-z*#]))/g,
|
|
10214
10218
|
""
|
|
@@ -10250,7 +10254,10 @@ var init_profile = __esm({
|
|
|
10250
10254
|
"ask_user",
|
|
10251
10255
|
"present_plan",
|
|
10252
10256
|
"present_schema",
|
|
10253
|
-
"present_files"
|
|
10257
|
+
"present_files",
|
|
10258
|
+
"wait_for_job",
|
|
10259
|
+
"get_viewer_context",
|
|
10260
|
+
"update_viewer_context"
|
|
10254
10261
|
];
|
|
10255
10262
|
WORKTREE_GITHUB_MCP_TOOLS = [
|
|
10256
10263
|
"github_get_issue",
|
|
@@ -10563,8 +10570,8 @@ function isBrightsyConnected() {
|
|
|
10563
10570
|
return false;
|
|
10564
10571
|
}
|
|
10565
10572
|
}
|
|
10566
|
-
function promptMentionsBrightsy(
|
|
10567
|
-
return BRIGHTSY_WORD.test(
|
|
10573
|
+
function promptMentionsBrightsy(text7) {
|
|
10574
|
+
return BRIGHTSY_WORD.test(text7 ?? "");
|
|
10568
10575
|
}
|
|
10569
10576
|
function isBrightsyMcpToolName(name) {
|
|
10570
10577
|
const n = name.toLowerCase();
|
|
@@ -10842,7 +10849,10 @@ var init_injected_mcp = __esm({
|
|
|
10842
10849
|
"mcp__sideboard__present_schema",
|
|
10843
10850
|
"mcp__sideboard__present_files",
|
|
10844
10851
|
"mcp__sideboard__ask_user",
|
|
10845
|
-
"mcp__sideboard__present_plan"
|
|
10852
|
+
"mcp__sideboard__present_plan",
|
|
10853
|
+
"mcp__sideboard__wait_for_job",
|
|
10854
|
+
"mcp__sideboard__get_viewer_context",
|
|
10855
|
+
"mcp__sideboard__update_viewer_context"
|
|
10846
10856
|
];
|
|
10847
10857
|
SIDEBOARD_GITHUB_MCP_ALLOWED_TOOLS = ["mcp__sideboard__github_*"];
|
|
10848
10858
|
SIDEBOARD_LINEAR_MCP_ALLOWED_TOOLS = ["mcp__sideboard__linear_*"];
|
|
@@ -11179,9 +11189,9 @@ function compactMetadataFromClaude(obj) {
|
|
|
11179
11189
|
return { trigger, postTokens };
|
|
11180
11190
|
}
|
|
11181
11191
|
function parseIssuesJson(raw) {
|
|
11182
|
-
const
|
|
11183
|
-
const candidates = [
|
|
11184
|
-
const match =
|
|
11192
|
+
const text7 = raw.trim();
|
|
11193
|
+
const candidates = [text7];
|
|
11194
|
+
const match = text7.match(/\[[\s\S]*\]/);
|
|
11185
11195
|
if (match) candidates.push(match[0]);
|
|
11186
11196
|
for (const c of candidates) {
|
|
11187
11197
|
try {
|
|
@@ -11449,8 +11459,8 @@ var init_claude = __esm({
|
|
|
11449
11459
|
if (errorDetail) {
|
|
11450
11460
|
events.push({ type: "stderr", data: errorDetail });
|
|
11451
11461
|
} else {
|
|
11452
|
-
const
|
|
11453
|
-
if (typeof
|
|
11462
|
+
const text7 = obj.result;
|
|
11463
|
+
if (typeof text7 === "string" && text7) events.push({ type: "stdout", data: text7 });
|
|
11454
11464
|
}
|
|
11455
11465
|
const usage = usageFromClaude(obj.usage);
|
|
11456
11466
|
if (usage) {
|
|
@@ -11591,9 +11601,9 @@ function asObject2(value) {
|
|
|
11591
11601
|
function listMcpNamesFromJsonMap(raw, key) {
|
|
11592
11602
|
return Object.keys(asObject2(asObject2(raw)[key])).filter((n) => n.trim());
|
|
11593
11603
|
}
|
|
11594
|
-
function listMcpNamesFromCodexToml(
|
|
11604
|
+
function listMcpNamesFromCodexToml(text7) {
|
|
11595
11605
|
try {
|
|
11596
|
-
const parsed = (0, import_smol_toml2.parse)(
|
|
11606
|
+
const parsed = (0, import_smol_toml2.parse)(text7);
|
|
11597
11607
|
return Object.keys(asObject2(parsed.mcp_servers)).filter((n) => n.trim());
|
|
11598
11608
|
} catch {
|
|
11599
11609
|
return [];
|
|
@@ -11724,8 +11734,8 @@ function codexConfigHasNetworkAccess() {
|
|
|
11724
11734
|
];
|
|
11725
11735
|
for (const path2 of candidates) {
|
|
11726
11736
|
if (!(0, import_node_fs32.existsSync)(path2)) continue;
|
|
11727
|
-
const
|
|
11728
|
-
if (/network_access\s*=\s*true/.test(
|
|
11737
|
+
const text7 = (0, import_node_fs32.readFileSync)(path2, "utf8");
|
|
11738
|
+
if (/network_access\s*=\s*true/.test(text7)) return true;
|
|
11729
11739
|
}
|
|
11730
11740
|
return false;
|
|
11731
11741
|
}
|
|
@@ -11738,8 +11748,8 @@ function unwrapCodexMcpResult(result) {
|
|
|
11738
11748
|
const texts = [];
|
|
11739
11749
|
for (const item of rec.content) {
|
|
11740
11750
|
if (!item || typeof item !== "object") continue;
|
|
11741
|
-
const
|
|
11742
|
-
if (typeof
|
|
11751
|
+
const text7 = item.text;
|
|
11752
|
+
if (typeof text7 === "string") texts.push(text7);
|
|
11743
11753
|
}
|
|
11744
11754
|
if (texts.length) return texts.join("\n");
|
|
11745
11755
|
}
|
|
@@ -11981,8 +11991,8 @@ var init_codex = __esm({
|
|
|
11981
11991
|
if (msg) nested.push(msg);
|
|
11982
11992
|
}
|
|
11983
11993
|
}
|
|
11984
|
-
for (const
|
|
11985
|
-
events.push({ type: "stdout", data:
|
|
11994
|
+
for (const text7 of nested) {
|
|
11995
|
+
events.push({ type: "stdout", data: text7, parentId: item.id });
|
|
11986
11996
|
}
|
|
11987
11997
|
events.push({
|
|
11988
11998
|
type: "tool_result",
|
|
@@ -12733,8 +12743,8 @@ var init_opencode = __esm({
|
|
|
12733
12743
|
return { type: "session_id", data: sid };
|
|
12734
12744
|
}
|
|
12735
12745
|
if (obj.type === "text") {
|
|
12736
|
-
const
|
|
12737
|
-
if (
|
|
12746
|
+
const text7 = obj.part?.text ?? obj.text;
|
|
12747
|
+
if (text7) return { type: "stdout", data: text7 };
|
|
12738
12748
|
}
|
|
12739
12749
|
if (obj.type === "tool_use") {
|
|
12740
12750
|
const part = obj.part;
|
|
@@ -13013,8 +13023,8 @@ var init_list_models = __esm({
|
|
|
13013
13023
|
});
|
|
13014
13024
|
|
|
13015
13025
|
// src/agents/session-quota.ts
|
|
13016
|
-
function isSessionQuotaLimit(
|
|
13017
|
-
const lower =
|
|
13026
|
+
function isSessionQuotaLimit(text7) {
|
|
13027
|
+
const lower = text7.trim().toLowerCase();
|
|
13018
13028
|
if (!lower) return false;
|
|
13019
13029
|
if (/credit balance is too low|out of credits|insufficient.?quota|billing/.test(lower)) {
|
|
13020
13030
|
return false;
|
|
@@ -13022,10 +13032,10 @@ function isSessionQuotaLimit(text6) {
|
|
|
13022
13032
|
if (/prompt is too long|context.*(too long|exceed)|conversation too long/.test(lower)) {
|
|
13023
13033
|
return false;
|
|
13024
13034
|
}
|
|
13025
|
-
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(
|
|
13035
|
+
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);
|
|
13026
13036
|
}
|
|
13027
|
-
function parseSessionQuotaResetAt(
|
|
13028
|
-
const absolute =
|
|
13037
|
+
function parseSessionQuotaResetAt(text7, now = /* @__PURE__ */ new Date()) {
|
|
13038
|
+
const absolute = text7.match(
|
|
13029
13039
|
/resets\s+(?:at\s+)?(\d{1,2}):(\d{2})\s*(am|pm)(?:\s*\(([^)]+)\))?/i
|
|
13030
13040
|
);
|
|
13031
13041
|
if (absolute) {
|
|
@@ -13043,7 +13053,7 @@ function parseSessionQuotaResetAt(text6, now = /* @__PURE__ */ new Date()) {
|
|
|
13043
13053
|
}
|
|
13044
13054
|
return at;
|
|
13045
13055
|
}
|
|
13046
|
-
const relative =
|
|
13056
|
+
const relative = text7.match(
|
|
13047
13057
|
/resets\s+in\s+(\d+)\s*(minutes?|hours?|days?)/i
|
|
13048
13058
|
);
|
|
13049
13059
|
if (relative) {
|
|
@@ -13624,8 +13634,8 @@ async function spawnAgentTurn(thread, input, onEvent) {
|
|
|
13624
13634
|
onEvent({ type: "exit", data: exitCode });
|
|
13625
13635
|
const finalized = finalizeParts(parts);
|
|
13626
13636
|
const rawText = assistantText.trim() || partsToAssistantText(finalized);
|
|
13627
|
-
const
|
|
13628
|
-
return { exitCode, sessionId, assistantText:
|
|
13637
|
+
const text7 = thread.agent === "brightsy" ? stripBrightsyNdjsonNoise(rawText) : rawText;
|
|
13638
|
+
return { exitCode, sessionId, assistantText: text7, parts: finalized, usage };
|
|
13629
13639
|
});
|
|
13630
13640
|
return {
|
|
13631
13641
|
pid: child.pid,
|
|
@@ -13904,9 +13914,9 @@ async function tryClaudeSummary(transcript, opts) {
|
|
|
13904
13914
|
{ cwd: opts?.cwd, reject: false }
|
|
13905
13915
|
);
|
|
13906
13916
|
if (exitCode !== 0) return null;
|
|
13907
|
-
const
|
|
13908
|
-
if (
|
|
13909
|
-
return
|
|
13917
|
+
const text7 = stdout.trim();
|
|
13918
|
+
if (text7.length < 40) return null;
|
|
13919
|
+
return text7;
|
|
13910
13920
|
} catch {
|
|
13911
13921
|
return null;
|
|
13912
13922
|
}
|
|
@@ -13955,9 +13965,9 @@ function extractiveSummary(transcript) {
|
|
|
13955
13965
|
);
|
|
13956
13966
|
return parts.join("\n");
|
|
13957
13967
|
}
|
|
13958
|
-
function clipSummary(
|
|
13959
|
-
if (
|
|
13960
|
-
return `${
|
|
13968
|
+
function clipSummary(text7) {
|
|
13969
|
+
if (text7.length <= MAX_SUMMARY_CHARS) return text7;
|
|
13970
|
+
return `${text7.slice(0, MAX_SUMMARY_CHARS)}
|
|
13961
13971
|
|
|
13962
13972
|
[\u2026summary truncated\u2026]`;
|
|
13963
13973
|
}
|
|
@@ -14111,8 +14121,8 @@ function findLastBrightsyContextSummary(messages) {
|
|
|
14111
14121
|
continue;
|
|
14112
14122
|
}
|
|
14113
14123
|
if (part.status === "error") continue;
|
|
14114
|
-
const
|
|
14115
|
-
if (
|
|
14124
|
+
const text7 = extractBrightsyContextSummary(part.result);
|
|
14125
|
+
if (text7) return { index: i, text: text7 };
|
|
14116
14126
|
}
|
|
14117
14127
|
}
|
|
14118
14128
|
return null;
|
|
@@ -15321,6 +15331,242 @@ var init_child_halt = __esm({
|
|
|
15321
15331
|
}
|
|
15322
15332
|
});
|
|
15323
15333
|
|
|
15334
|
+
// src/mcp/wait-for-turn.ts
|
|
15335
|
+
function mcpWaitForTurnTimeoutMs(requested) {
|
|
15336
|
+
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
15337
|
+
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
15338
|
+
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
15339
|
+
}
|
|
15340
|
+
function mcpWaitStillRunningHint(status) {
|
|
15341
|
+
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
15342
|
+
}
|
|
15343
|
+
function mcpWaitFinishedHint(status) {
|
|
15344
|
+
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
15345
|
+
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
15346
|
+
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
15347
|
+
return void 0;
|
|
15348
|
+
}
|
|
15349
|
+
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;
|
|
15350
|
+
var init_wait_for_turn = __esm({
|
|
15351
|
+
"src/mcp/wait-for-turn.ts"() {
|
|
15352
|
+
"use strict";
|
|
15353
|
+
MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
15354
|
+
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.";
|
|
15355
|
+
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.";
|
|
15356
|
+
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.";
|
|
15357
|
+
MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
15358
|
+
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.";
|
|
15359
|
+
}
|
|
15360
|
+
});
|
|
15361
|
+
|
|
15362
|
+
// src/mcp/wait-for-job.ts
|
|
15363
|
+
function mcpWaitForJobTimeoutMs(requested) {
|
|
15364
|
+
return mcpWaitForTurnTimeoutMs(requested);
|
|
15365
|
+
}
|
|
15366
|
+
function sanitizeDetachedJobId(id) {
|
|
15367
|
+
const s = id.trim();
|
|
15368
|
+
if (!JOB_ID_RE.test(s)) {
|
|
15369
|
+
throw new Error(`detached-job id must be 1\u201364 chars [A-Za-z0-9._-], got ${JSON.stringify(id)}`);
|
|
15370
|
+
}
|
|
15371
|
+
return s;
|
|
15372
|
+
}
|
|
15373
|
+
function jobAlive(pid) {
|
|
15374
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
15375
|
+
try {
|
|
15376
|
+
process.kill(pid, 0);
|
|
15377
|
+
return true;
|
|
15378
|
+
} catch {
|
|
15379
|
+
return false;
|
|
15380
|
+
}
|
|
15381
|
+
}
|
|
15382
|
+
function readIntFile(file) {
|
|
15383
|
+
if (!(0, import_node_fs40.existsSync)(file)) return null;
|
|
15384
|
+
const n = Number.parseInt((0, import_node_fs40.readFileSync)(file, "utf8").trim(), 10);
|
|
15385
|
+
return Number.isInteger(n) ? n : null;
|
|
15386
|
+
}
|
|
15387
|
+
function jobDir(root, id, legacy = false) {
|
|
15388
|
+
return (0, import_node_path40.join)(root, legacy ? LEGACY_DETACHED_JOBS_DIR : DETACHED_JOBS_DIR, id);
|
|
15389
|
+
}
|
|
15390
|
+
function resolveJobDir(root, id) {
|
|
15391
|
+
const modern = jobDir(root, id, false);
|
|
15392
|
+
if ((0, import_node_fs40.existsSync)(modern)) return modern;
|
|
15393
|
+
const legacy = jobDir(root, id, true);
|
|
15394
|
+
if ((0, import_node_fs40.existsSync)(legacy)) return legacy;
|
|
15395
|
+
return modern;
|
|
15396
|
+
}
|
|
15397
|
+
function listJobIdsIn(root, rel) {
|
|
15398
|
+
const dir = (0, import_node_path40.join)(root, rel);
|
|
15399
|
+
if (!(0, import_node_fs40.existsSync)(dir)) return [];
|
|
15400
|
+
return (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && JOB_ID_RE.test(e.name)).map((e) => e.name);
|
|
15401
|
+
}
|
|
15402
|
+
function listRunningDetachedJobs(worktreePath) {
|
|
15403
|
+
const root = worktreePath.trim();
|
|
15404
|
+
if (!root) return [];
|
|
15405
|
+
const ids = /* @__PURE__ */ new Set([
|
|
15406
|
+
...listJobIdsIn(root, DETACHED_JOBS_DIR),
|
|
15407
|
+
...listJobIdsIn(root, LEGACY_DETACHED_JOBS_DIR)
|
|
15408
|
+
]);
|
|
15409
|
+
const running = [];
|
|
15410
|
+
for (const id of ids) {
|
|
15411
|
+
const dir = resolveJobDir(root, id);
|
|
15412
|
+
const pid = readIntFile((0, import_node_path40.join)(dir, "pid"));
|
|
15413
|
+
if (pid != null && jobAlive(pid)) running.push(id);
|
|
15414
|
+
}
|
|
15415
|
+
return running.sort();
|
|
15416
|
+
}
|
|
15417
|
+
function looksLikeDeferredDonePromise(text7) {
|
|
15418
|
+
const t = (text7 ?? "").trim();
|
|
15419
|
+
if (!t) return false;
|
|
15420
|
+
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);
|
|
15421
|
+
}
|
|
15422
|
+
function formatJobStillRunningContinuePrompt(jobIds) {
|
|
15423
|
+
const ids = jobIds.join(", ");
|
|
15424
|
+
return [
|
|
15425
|
+
`Detached job still running: ${ids}.`,
|
|
15426
|
+
"Do not end this turn. Loop wait_for_job (same id) and present_artifact type=log with content=delta until stillRunning is false.",
|
|
15427
|
+
"Then report the result. Do not tell the user you will let them know later."
|
|
15428
|
+
].join(" ");
|
|
15429
|
+
}
|
|
15430
|
+
function formatDeferredDoneContinuePrompt() {
|
|
15431
|
+
return [
|
|
15432
|
+
"You ended the turn after promising to report later, but no detached job is running.",
|
|
15433
|
+
"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.",
|
|
15434
|
+
"Do not say you will let the user know later."
|
|
15435
|
+
].join(" ");
|
|
15436
|
+
}
|
|
15437
|
+
function turnWatchedDetachedJob(parts) {
|
|
15438
|
+
return parts.some((p) => {
|
|
15439
|
+
if (p.type !== "tool") return false;
|
|
15440
|
+
if (/wait_for_job$/i.test(p.name ?? "")) return true;
|
|
15441
|
+
const blob = [p.name, p.detail, p.description, p.input ? JSON.stringify(p.input) : ""].filter(Boolean).join(" ");
|
|
15442
|
+
return /detached-job\.js\b/i.test(blob);
|
|
15443
|
+
});
|
|
15444
|
+
}
|
|
15445
|
+
function planJobContinue(opts) {
|
|
15446
|
+
if (opts.isOrchestrator) return { action: "none" };
|
|
15447
|
+
if (opts.agent === "brightsy") return { action: "none" };
|
|
15448
|
+
if (opts.queueLength > 0) return { action: "none" };
|
|
15449
|
+
if (opts.continueCount >= MAX_JOB_CONTINUES) return { action: "none" };
|
|
15450
|
+
const farewell = looksLikeDeferredDonePromise(opts.chatText);
|
|
15451
|
+
if (opts.runningJobIds.length > 0 && (farewell || opts.watchedJob)) {
|
|
15452
|
+
return {
|
|
15453
|
+
action: "wait",
|
|
15454
|
+
jobIds: opts.runningJobIds,
|
|
15455
|
+
prompt: formatJobStillRunningContinuePrompt(opts.runningJobIds)
|
|
15456
|
+
};
|
|
15457
|
+
}
|
|
15458
|
+
if (looksLikeDeferredDonePromise(opts.chatText) && !opts.alreadyNudged) {
|
|
15459
|
+
return { action: "nudge", prompt: formatDeferredDoneContinuePrompt() };
|
|
15460
|
+
}
|
|
15461
|
+
return { action: "none" };
|
|
15462
|
+
}
|
|
15463
|
+
function tailProgress(logFile, maxLines = 12) {
|
|
15464
|
+
if (!(0, import_node_fs40.existsSync)(logFile)) return "(no log yet)";
|
|
15465
|
+
const lines = (0, import_node_fs40.readFileSync)(logFile, "utf8").split("\n");
|
|
15466
|
+
return lines.slice(-maxLines).join("\n");
|
|
15467
|
+
}
|
|
15468
|
+
function readLogLines(file) {
|
|
15469
|
+
if (!(0, import_node_fs40.existsSync)(file)) return [];
|
|
15470
|
+
const lines = (0, import_node_fs40.readFileSync)(file, "utf8").split("\n");
|
|
15471
|
+
if (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
15472
|
+
return lines;
|
|
15473
|
+
}
|
|
15474
|
+
function takeDelta(logFile, cursorFile) {
|
|
15475
|
+
const lines = readLogLines(logFile);
|
|
15476
|
+
const cursor = readIntFile(cursorFile) ?? 0;
|
|
15477
|
+
const start = Math.min(Math.max(0, cursor), lines.length);
|
|
15478
|
+
return { delta: lines.slice(start).join("\n"), nextCursor: lines.length };
|
|
15479
|
+
}
|
|
15480
|
+
function snapshotJob(dir) {
|
|
15481
|
+
const pid = readIntFile((0, import_node_path40.join)(dir, "pid"));
|
|
15482
|
+
const running = pid != null && jobAlive(pid);
|
|
15483
|
+
return {
|
|
15484
|
+
pid,
|
|
15485
|
+
running,
|
|
15486
|
+
exitCode: readIntFile((0, import_node_path40.join)(dir, "exit")),
|
|
15487
|
+
log: (0, import_node_path40.join)(dir, "log"),
|
|
15488
|
+
cursor: (0, import_node_path40.join)(dir, "present.cursor"),
|
|
15489
|
+
progress: tailProgress((0, import_node_path40.join)(dir, "log"))
|
|
15490
|
+
};
|
|
15491
|
+
}
|
|
15492
|
+
function toResult(id, snap, extra) {
|
|
15493
|
+
const failed = extra?.failed === true || !snap.running && snap.exitCode != null && snap.exitCode !== 0;
|
|
15494
|
+
const ok = !snap.running && snap.exitCode === 0;
|
|
15495
|
+
const stillRunning = snap.running && !ok;
|
|
15496
|
+
const { delta, nextCursor } = takeDelta(snap.log, snap.cursor);
|
|
15497
|
+
try {
|
|
15498
|
+
(0, import_node_fs40.mkdirSync)((0, import_node_path40.dirname)(snap.cursor), { recursive: true });
|
|
15499
|
+
(0, import_node_fs40.writeFileSync)(snap.cursor, `${nextCursor}
|
|
15500
|
+
`);
|
|
15501
|
+
} catch {
|
|
15502
|
+
}
|
|
15503
|
+
const status = ok ? "ok" : failed && !stillRunning ? "failed" : stillRunning ? "running" : "idle";
|
|
15504
|
+
return {
|
|
15505
|
+
stillRunning,
|
|
15506
|
+
ok,
|
|
15507
|
+
failed: Boolean(failed && !stillRunning && !ok),
|
|
15508
|
+
status,
|
|
15509
|
+
id,
|
|
15510
|
+
pid: snap.pid,
|
|
15511
|
+
exitCode: snap.exitCode ?? void 0,
|
|
15512
|
+
delta,
|
|
15513
|
+
progress: extra?.progress ?? snap.progress,
|
|
15514
|
+
hint: stillRunning ? MCP_WAIT_JOB_STILL_RUNNING_HINT : void 0
|
|
15515
|
+
};
|
|
15516
|
+
}
|
|
15517
|
+
async function sleepMs(ms) {
|
|
15518
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
15519
|
+
}
|
|
15520
|
+
async function waitForDetachedJob(cwd, id, opts) {
|
|
15521
|
+
const jobId = sanitizeDetachedJobId(id);
|
|
15522
|
+
const root = cwd.trim() || process.cwd();
|
|
15523
|
+
const dir = resolveJobDir(root, jobId);
|
|
15524
|
+
const timeoutMs = mcpWaitForJobTimeoutMs(opts?.timeoutMs);
|
|
15525
|
+
const sleep2 = opts?.sleep ?? sleepMs;
|
|
15526
|
+
if (!(0, import_node_fs40.existsSync)(dir)) {
|
|
15527
|
+
return {
|
|
15528
|
+
stillRunning: false,
|
|
15529
|
+
ok: false,
|
|
15530
|
+
failed: true,
|
|
15531
|
+
status: "failed",
|
|
15532
|
+
id: jobId,
|
|
15533
|
+
delta: "",
|
|
15534
|
+
progress: "No detached job. Start one first.",
|
|
15535
|
+
hint: "Start with detached-job.js start <id> -- <command>, then call wait_for_job again."
|
|
15536
|
+
};
|
|
15537
|
+
}
|
|
15538
|
+
const deadline = Date.now() + timeoutMs;
|
|
15539
|
+
let snap = snapshotJob(dir);
|
|
15540
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
15541
|
+
if (!snap.running && snap.pid == null && snap.progress === "(no log yet)") {
|
|
15542
|
+
return toResult(jobId, snap, {
|
|
15543
|
+
failed: true,
|
|
15544
|
+
progress: "No detached job. Start one first."
|
|
15545
|
+
});
|
|
15546
|
+
}
|
|
15547
|
+
while (Date.now() < deadline) {
|
|
15548
|
+
snap = snapshotJob(dir);
|
|
15549
|
+
if (snap.exitCode === 0 && !snap.running) return toResult(jobId, snap);
|
|
15550
|
+
if (!snap.running) return toResult(jobId, snap, { failed: true });
|
|
15551
|
+
await sleep2(Math.min(2e3, Math.max(50, deadline - Date.now())));
|
|
15552
|
+
}
|
|
15553
|
+
snap = snapshotJob(dir);
|
|
15554
|
+
return toResult(jobId, snap);
|
|
15555
|
+
}
|
|
15556
|
+
var import_node_fs40, import_node_path40, MAX_JOB_CONTINUES, MCP_WAIT_JOB_STILL_RUNNING_HINT, JOB_ID_RE;
|
|
15557
|
+
var init_wait_for_job = __esm({
|
|
15558
|
+
"src/mcp/wait-for-job.ts"() {
|
|
15559
|
+
"use strict";
|
|
15560
|
+
import_node_fs40 = require("fs");
|
|
15561
|
+
import_node_path40 = require("path");
|
|
15562
|
+
init_workspace_scratch();
|
|
15563
|
+
init_wait_for_turn();
|
|
15564
|
+
MAX_JOB_CONTINUES = 8;
|
|
15565
|
+
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.";
|
|
15566
|
+
JOB_ID_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
15567
|
+
}
|
|
15568
|
+
});
|
|
15569
|
+
|
|
15324
15570
|
// src/detect/detect.ts
|
|
15325
15571
|
async function detectAgents() {
|
|
15326
15572
|
ensureAgentPath();
|
|
@@ -15915,11 +16161,11 @@ function unwrapToolResult(result) {
|
|
|
15915
16161
|
return typeof item.text === "string" ? item.text : "";
|
|
15916
16162
|
}).filter(Boolean);
|
|
15917
16163
|
if (texts.length === 1) {
|
|
15918
|
-
const
|
|
16164
|
+
const text7 = texts[0];
|
|
15919
16165
|
try {
|
|
15920
|
-
return JSON.parse(
|
|
16166
|
+
return JSON.parse(text7);
|
|
15921
16167
|
} catch {
|
|
15922
|
-
return
|
|
16168
|
+
return text7;
|
|
15923
16169
|
}
|
|
15924
16170
|
}
|
|
15925
16171
|
if (texts.length > 1) return texts.join("\n");
|
|
@@ -16447,7 +16693,7 @@ function reuseLiveThread(input, repoPath, match) {
|
|
|
16447
16693
|
}
|
|
16448
16694
|
async function createThread(input, _onSetupLine) {
|
|
16449
16695
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
16450
|
-
if (!(0,
|
|
16696
|
+
if (!(0, import_node_fs41.existsSync)(repoPath)) {
|
|
16451
16697
|
throw new Error(`Repo not found: ${repoPath}`);
|
|
16452
16698
|
}
|
|
16453
16699
|
const reused = reuseLiveThread(input, repoPath, {
|
|
@@ -16627,11 +16873,11 @@ async function listLinearIssues(agent, repoPath) {
|
|
|
16627
16873
|
}
|
|
16628
16874
|
return adapter.listLinearIssues(repoPath);
|
|
16629
16875
|
}
|
|
16630
|
-
var
|
|
16876
|
+
var import_node_fs41;
|
|
16631
16877
|
var init_create = __esm({
|
|
16632
16878
|
"src/threads/create.ts"() {
|
|
16633
16879
|
"use strict";
|
|
16634
|
-
|
|
16880
|
+
import_node_fs41 = require("fs");
|
|
16635
16881
|
init_detect();
|
|
16636
16882
|
init_worktree();
|
|
16637
16883
|
init_home_board();
|
|
@@ -16681,8 +16927,8 @@ function summarizeTurnLive(parts) {
|
|
|
16681
16927
|
const interesting = [...tools].reverse().find((t) => !isPollWrapperToolName(t.name)) ?? lastTool;
|
|
16682
16928
|
const lastToolLabel = interesting ? interesting.description || toolDescription(interesting.name, interesting.input) || interesting.name : void 0;
|
|
16683
16929
|
const thinking = lastText(parts, "thinking");
|
|
16684
|
-
const
|
|
16685
|
-
const excerptRaw = thinking ||
|
|
16930
|
+
const text7 = lastText(parts, "text");
|
|
16931
|
+
const excerptRaw = thinking || text7;
|
|
16686
16932
|
const excerpt = excerptRaw.length > 280 ? `${excerptRaw.slice(-280)}` : excerptRaw || void 0;
|
|
16687
16933
|
const summary = liveActivitySummary(parts);
|
|
16688
16934
|
return {
|
|
@@ -16734,20 +16980,20 @@ function writeTurnLive(threadId, progress) {
|
|
|
16734
16980
|
const path2 = threadLivePath(threadId);
|
|
16735
16981
|
const tmp = `${path2}.${process.pid}.tmp`;
|
|
16736
16982
|
try {
|
|
16737
|
-
(0,
|
|
16738
|
-
(0,
|
|
16983
|
+
(0, import_node_fs42.writeFileSync)(tmp, JSON.stringify(progress), "utf8");
|
|
16984
|
+
(0, import_node_fs42.renameSync)(tmp, path2);
|
|
16739
16985
|
} catch {
|
|
16740
16986
|
try {
|
|
16741
|
-
(0,
|
|
16987
|
+
(0, import_node_fs42.unlinkSync)(tmp);
|
|
16742
16988
|
} catch {
|
|
16743
16989
|
}
|
|
16744
16990
|
}
|
|
16745
16991
|
}
|
|
16746
16992
|
function readTurnLive(threadId) {
|
|
16747
16993
|
const path2 = threadLivePath(threadId);
|
|
16748
|
-
if (!(0,
|
|
16994
|
+
if (!(0, import_node_fs42.existsSync)(path2)) return null;
|
|
16749
16995
|
try {
|
|
16750
|
-
const raw = JSON.parse((0,
|
|
16996
|
+
const raw = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
|
|
16751
16997
|
if (!raw || typeof raw.summary !== "string") return null;
|
|
16752
16998
|
return raw;
|
|
16753
16999
|
} catch {
|
|
@@ -16759,17 +17005,17 @@ function clearTurnLive(threadId) {
|
|
|
16759
17005
|
if (buf?.timer) clearTimeout(buf.timer);
|
|
16760
17006
|
buffers.delete(threadId);
|
|
16761
17007
|
const path2 = threadLivePath(threadId);
|
|
16762
|
-
if (!(0,
|
|
17008
|
+
if (!(0, import_node_fs42.existsSync)(path2)) return;
|
|
16763
17009
|
try {
|
|
16764
|
-
(0,
|
|
17010
|
+
(0, import_node_fs42.unlinkSync)(path2);
|
|
16765
17011
|
} catch {
|
|
16766
17012
|
}
|
|
16767
17013
|
}
|
|
16768
|
-
var
|
|
17014
|
+
var import_node_fs42, buffers, FLUSH_MS, MAX_PARTS;
|
|
16769
17015
|
var init_turn_live = __esm({
|
|
16770
17016
|
"src/store/turn-live.ts"() {
|
|
16771
17017
|
"use strict";
|
|
16772
|
-
|
|
17018
|
+
import_node_fs42 = require("fs");
|
|
16773
17019
|
init_message_parts();
|
|
16774
17020
|
init_paths();
|
|
16775
17021
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -16921,8 +17167,8 @@ function buildQuotaHandoffAttachment(from, limitText, fallbackAgent) {
|
|
|
16921
17167
|
);
|
|
16922
17168
|
const recent = from.messages.slice(-8).map((m) => {
|
|
16923
17169
|
const role = m.role === "user" ? "User" : m.role === "agent" ? "Agent" : "Summary";
|
|
16924
|
-
const
|
|
16925
|
-
return
|
|
17170
|
+
const text7 = m.text.trim().replace(/\s+/g, " ").slice(0, 280);
|
|
17171
|
+
return text7 ? `- ${role}: ${text7}` : null;
|
|
16926
17172
|
}).filter(Boolean);
|
|
16927
17173
|
const body = [
|
|
16928
17174
|
`# Orchestration handoff`,
|
|
@@ -16989,7 +17235,7 @@ var init_quota_failover = __esm({
|
|
|
16989
17235
|
// src/threads/adopt.ts
|
|
16990
17236
|
function thisModuleFile() {
|
|
16991
17237
|
const cjsFile = typeof __filename !== "undefined" ? __filename : "";
|
|
16992
|
-
return cjsFile || process.argv[1] || (0,
|
|
17238
|
+
return cjsFile || process.argv[1] || (0, import_node_path41.join)(process.cwd(), "package.json");
|
|
16993
17239
|
}
|
|
16994
17240
|
function openReadonlySqlite(file) {
|
|
16995
17241
|
const req = (0, import_node_module4.createRequire)(thisModuleFile());
|
|
@@ -17007,25 +17253,25 @@ function mapAgentType(raw) {
|
|
|
17007
17253
|
return null;
|
|
17008
17254
|
}
|
|
17009
17255
|
function resolveConductorCursorAgentId(workspacePath) {
|
|
17010
|
-
if (!workspacePath || !(0,
|
|
17256
|
+
if (!workspacePath || !(0, import_node_fs43.existsSync)(CURSOR_SDK_STORE)) return null;
|
|
17011
17257
|
const normalized = workspacePath.replace(/\/$/, "");
|
|
17012
17258
|
let best = null;
|
|
17013
17259
|
let hashes;
|
|
17014
17260
|
try {
|
|
17015
|
-
hashes = (0,
|
|
17261
|
+
hashes = (0, import_node_fs43.readdirSync)(CURSOR_SDK_STORE);
|
|
17016
17262
|
} catch {
|
|
17017
17263
|
return null;
|
|
17018
17264
|
}
|
|
17019
17265
|
for (const hash of hashes) {
|
|
17020
|
-
const agentsFile = (0,
|
|
17021
|
-
if (!(0,
|
|
17022
|
-
let
|
|
17266
|
+
const agentsFile = (0, import_node_path41.join)(CURSOR_SDK_STORE, hash, "agents.ndjson");
|
|
17267
|
+
if (!(0, import_node_fs43.existsSync)(agentsFile)) continue;
|
|
17268
|
+
let text7;
|
|
17023
17269
|
try {
|
|
17024
|
-
|
|
17270
|
+
text7 = (0, import_node_fs43.readFileSync)(agentsFile, "utf8");
|
|
17025
17271
|
} catch {
|
|
17026
17272
|
continue;
|
|
17027
17273
|
}
|
|
17028
|
-
for (const line of
|
|
17274
|
+
for (const line of text7.split("\n")) {
|
|
17029
17275
|
const trimmed = line.trim();
|
|
17030
17276
|
if (!trimmed) continue;
|
|
17031
17277
|
try {
|
|
@@ -17045,7 +17291,7 @@ function resolveConductorCursorAgentId(workspacePath) {
|
|
|
17045
17291
|
return best?.agentId ?? null;
|
|
17046
17292
|
}
|
|
17047
17293
|
async function adoptThread(input) {
|
|
17048
|
-
if (!(0,
|
|
17294
|
+
if (!(0, import_node_fs43.existsSync)(input.worktreePath)) {
|
|
17049
17295
|
throw new Error(`Worktree not found: ${input.worktreePath}`);
|
|
17050
17296
|
}
|
|
17051
17297
|
const repoPath = await resolveRepoRoot(input.worktreePath);
|
|
@@ -17074,18 +17320,18 @@ function conductorDbPath() {
|
|
|
17074
17320
|
return CONDUCTOR_DB;
|
|
17075
17321
|
}
|
|
17076
17322
|
function listConductorWorkspaces() {
|
|
17077
|
-
if (!(0,
|
|
17323
|
+
if (!(0, import_node_fs43.existsSync)(CONDUCTOR_DB)) {
|
|
17078
17324
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
17079
17325
|
}
|
|
17080
|
-
const tmp = (0,
|
|
17081
|
-
const snapshot = (0,
|
|
17326
|
+
const tmp = (0, import_node_fs43.mkdtempSync)((0, import_node_path41.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
|
|
17327
|
+
const snapshot = (0, import_node_path41.join)(tmp, "conductor.db");
|
|
17082
17328
|
try {
|
|
17083
|
-
(0,
|
|
17329
|
+
(0, import_node_fs43.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
17084
17330
|
for (const suffix of ["-wal", "-shm"]) {
|
|
17085
17331
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
17086
|
-
if ((0,
|
|
17332
|
+
if ((0, import_node_fs43.existsSync)(src)) {
|
|
17087
17333
|
try {
|
|
17088
|
-
(0,
|
|
17334
|
+
(0, import_node_fs43.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
17089
17335
|
} catch {
|
|
17090
17336
|
}
|
|
17091
17337
|
}
|
|
@@ -17161,22 +17407,22 @@ function listConductorWorkspaces() {
|
|
|
17161
17407
|
db.close();
|
|
17162
17408
|
}
|
|
17163
17409
|
} finally {
|
|
17164
|
-
(0,
|
|
17410
|
+
(0, import_node_fs43.rmSync)(tmp, { recursive: true, force: true });
|
|
17165
17411
|
}
|
|
17166
17412
|
}
|
|
17167
17413
|
function importConductorWorkspace(workspaceId) {
|
|
17168
|
-
if (!(0,
|
|
17414
|
+
if (!(0, import_node_fs43.existsSync)(CONDUCTOR_DB)) {
|
|
17169
17415
|
throw new Error(`Conductor DB not found at ${CONDUCTOR_DB}`);
|
|
17170
17416
|
}
|
|
17171
|
-
const tmp = (0,
|
|
17172
|
-
const snapshot = (0,
|
|
17417
|
+
const tmp = (0, import_node_fs43.mkdtempSync)((0, import_node_path41.join)((0, import_node_os13.tmpdir)(), "sideboard-conductor-"));
|
|
17418
|
+
const snapshot = (0, import_node_path41.join)(tmp, "conductor.db");
|
|
17173
17419
|
try {
|
|
17174
|
-
(0,
|
|
17420
|
+
(0, import_node_fs43.copyFileSync)(CONDUCTOR_DB, snapshot);
|
|
17175
17421
|
for (const suffix of ["-wal", "-shm"]) {
|
|
17176
17422
|
const src = `${CONDUCTOR_DB}${suffix}`;
|
|
17177
|
-
if ((0,
|
|
17423
|
+
if ((0, import_node_fs43.existsSync)(src)) {
|
|
17178
17424
|
try {
|
|
17179
|
-
(0,
|
|
17425
|
+
(0, import_node_fs43.copyFileSync)(src, `${snapshot}${suffix}`);
|
|
17180
17426
|
} catch {
|
|
17181
17427
|
}
|
|
17182
17428
|
}
|
|
@@ -17194,7 +17440,7 @@ function importConductorWorkspace(workspaceId) {
|
|
|
17194
17440
|
).get(workspaceId);
|
|
17195
17441
|
if (!row) throw new Error(`Conductor workspace not found: ${workspaceId}`);
|
|
17196
17442
|
const worktreePath = String(row.workspacePath);
|
|
17197
|
-
if (!(0,
|
|
17443
|
+
if (!(0, import_node_fs43.existsSync)(worktreePath)) {
|
|
17198
17444
|
throw new Error(`Conductor worktree missing on disk: ${worktreePath}`);
|
|
17199
17445
|
}
|
|
17200
17446
|
let sessionId = null;
|
|
@@ -17257,31 +17503,31 @@ function importConductorWorkspace(workspaceId) {
|
|
|
17257
17503
|
db.close();
|
|
17258
17504
|
}
|
|
17259
17505
|
} finally {
|
|
17260
|
-
(0,
|
|
17506
|
+
(0, import_node_fs43.rmSync)(tmp, { recursive: true, force: true });
|
|
17261
17507
|
}
|
|
17262
17508
|
}
|
|
17263
17509
|
async function importConductorWorkspaceAsync(workspaceId) {
|
|
17264
17510
|
return importConductorWorkspace(workspaceId);
|
|
17265
17511
|
}
|
|
17266
|
-
var import_node_child_process4,
|
|
17512
|
+
var import_node_child_process4, import_node_fs43, import_node_os13, import_node_path41, import_node_module4, CONDUCTOR_APP_SUPPORT, CONDUCTOR_DB, CURSOR_SDK_STORE;
|
|
17267
17513
|
var init_adopt = __esm({
|
|
17268
17514
|
"src/threads/adopt.ts"() {
|
|
17269
17515
|
"use strict";
|
|
17270
17516
|
import_node_child_process4 = require("child_process");
|
|
17271
|
-
|
|
17517
|
+
import_node_fs43 = require("fs");
|
|
17272
17518
|
import_node_os13 = require("os");
|
|
17273
|
-
|
|
17519
|
+
import_node_path41 = require("path");
|
|
17274
17520
|
import_node_module4 = require("module");
|
|
17275
17521
|
init_worktree();
|
|
17276
17522
|
init_thread_store();
|
|
17277
|
-
CONDUCTOR_APP_SUPPORT = (0,
|
|
17523
|
+
CONDUCTOR_APP_SUPPORT = (0, import_node_path41.join)(
|
|
17278
17524
|
process.env.HOME ?? "",
|
|
17279
17525
|
"Library",
|
|
17280
17526
|
"Application Support",
|
|
17281
17527
|
"com.conductor.app"
|
|
17282
17528
|
);
|
|
17283
|
-
CONDUCTOR_DB = (0,
|
|
17284
|
-
CURSOR_SDK_STORE = (0,
|
|
17529
|
+
CONDUCTOR_DB = (0, import_node_path41.join)(CONDUCTOR_APP_SUPPORT, "conductor.db");
|
|
17530
|
+
CURSOR_SDK_STORE = (0, import_node_path41.join)(CONDUCTOR_APP_SUPPORT, "cursor-sdk-store");
|
|
17285
17531
|
}
|
|
17286
17532
|
});
|
|
17287
17533
|
|
|
@@ -17348,7 +17594,7 @@ async function openStackLayer(input, _onSetupLine) {
|
|
|
17348
17594
|
let createdWorktree = false;
|
|
17349
17595
|
const trees = await listWorktrees(repoPath);
|
|
17350
17596
|
const checkedOut = trees.find((w) => w.branch === branchName);
|
|
17351
|
-
if (checkedOut?.path && (0,
|
|
17597
|
+
if (checkedOut?.path && (0, import_node_fs44.existsSync)(checkedOut.path)) {
|
|
17352
17598
|
if (input.reuseExistingWorktree !== false) {
|
|
17353
17599
|
worktreePath = checkedOut.path;
|
|
17354
17600
|
} else {
|
|
@@ -17490,7 +17736,7 @@ async function initStackFromThread(input, onSetupLine) {
|
|
|
17490
17736
|
async function createPrStack(input, onSetupLine) {
|
|
17491
17737
|
await requireAgent(input.agent);
|
|
17492
17738
|
const repoPath = await resolveRepoRoot(input.repoPath);
|
|
17493
|
-
if (!(0,
|
|
17739
|
+
if (!(0, import_node_fs44.existsSync)(repoPath)) throw new Error(`Repo not found: ${repoPath}`);
|
|
17494
17740
|
if (!input.branches.length) throw new Error("At least one branch name required");
|
|
17495
17741
|
const status = await detectGhStack(repoPath);
|
|
17496
17742
|
if (!status.available) throw new Error(status.reason);
|
|
@@ -17557,7 +17803,7 @@ async function createPrStack(input, onSetupLine) {
|
|
|
17557
17803
|
}
|
|
17558
17804
|
}
|
|
17559
17805
|
const claimed = new Set(threads.map((t) => t.worktreePath));
|
|
17560
|
-
if (!claimed.has(bootstrap.worktreePath) && (0,
|
|
17806
|
+
if (!claimed.has(bootstrap.worktreePath) && (0, import_node_fs44.existsSync)(bootstrap.worktreePath)) {
|
|
17561
17807
|
try {
|
|
17562
17808
|
await removeWorktree(repoPath, bootstrap.worktreePath, {
|
|
17563
17809
|
deleteBranch: bootstrap.branchName
|
|
@@ -17577,11 +17823,11 @@ function stackAgentDefaultsFrom(input) {
|
|
|
17577
17823
|
planMode: input.planMode
|
|
17578
17824
|
};
|
|
17579
17825
|
}
|
|
17580
|
-
var
|
|
17826
|
+
var import_node_fs44;
|
|
17581
17827
|
var init_stack_layers = __esm({
|
|
17582
17828
|
"src/threads/stack-layers.ts"() {
|
|
17583
17829
|
"use strict";
|
|
17584
|
-
|
|
17830
|
+
import_node_fs44 = require("fs");
|
|
17585
17831
|
init_detect();
|
|
17586
17832
|
init_run();
|
|
17587
17833
|
init_stack();
|
|
@@ -17594,7 +17840,7 @@ var init_stack_layers = __esm({
|
|
|
17594
17840
|
|
|
17595
17841
|
// src/diff/diff.ts
|
|
17596
17842
|
async function inspectGitWorktree(worktreePath) {
|
|
17597
|
-
if (!worktreePath || !(0,
|
|
17843
|
+
if (!worktreePath || !(0, import_node_fs45.existsSync)(worktreePath)) return "missing_worktree";
|
|
17598
17844
|
const check = await git(["rev-parse", "--is-inside-work-tree"], worktreePath, {
|
|
17599
17845
|
reject: false
|
|
17600
17846
|
});
|
|
@@ -17602,7 +17848,7 @@ async function inspectGitWorktree(worktreePath) {
|
|
|
17602
17848
|
return "ok";
|
|
17603
17849
|
}
|
|
17604
17850
|
async function initializeGitRepository(worktreePath) {
|
|
17605
|
-
if (!worktreePath || !(0,
|
|
17851
|
+
if (!worktreePath || !(0, import_node_fs45.existsSync)(worktreePath)) {
|
|
17606
17852
|
throw new Error("Worktree not found");
|
|
17607
17853
|
}
|
|
17608
17854
|
const status = await inspectGitWorktree(worktreePath);
|
|
@@ -17736,11 +17982,11 @@ new file mode 100644
|
|
|
17736
17982
|
};
|
|
17737
17983
|
}
|
|
17738
17984
|
async function untrackedPatch(worktreePath, path2, maxHunk) {
|
|
17739
|
-
const abs = (0,
|
|
17985
|
+
const abs = (0, import_node_path42.join)(worktreePath, path2);
|
|
17740
17986
|
try {
|
|
17741
|
-
const st = (0,
|
|
17987
|
+
const st = (0, import_node_fs45.statSync)(abs);
|
|
17742
17988
|
if (st.isFile() && st.size > maxHunk) {
|
|
17743
|
-
const buf = (0,
|
|
17989
|
+
const buf = (0, import_node_fs45.readFileSync)(abs).subarray(0, maxHunk);
|
|
17744
17990
|
return syntheticAddPatch(path2, buf.toString("utf8"), maxHunk);
|
|
17745
17991
|
}
|
|
17746
17992
|
} catch {
|
|
@@ -17834,13 +18080,13 @@ function formatStat(files) {
|
|
|
17834
18080
|
return `${n} file${n === 1 ? "" : "s"} changed, ${additions} insertions(+), ${deletions} deletions(-)`;
|
|
17835
18081
|
}
|
|
17836
18082
|
function emptyScopeStats() {
|
|
17837
|
-
const
|
|
18083
|
+
const z8 = emptyStat();
|
|
17838
18084
|
return {
|
|
17839
|
-
commits:
|
|
17840
|
-
uncommitted:
|
|
17841
|
-
staged:
|
|
17842
|
-
unstaged:
|
|
17843
|
-
last_turn:
|
|
18085
|
+
commits: z8,
|
|
18086
|
+
uncommitted: z8,
|
|
18087
|
+
staged: z8,
|
|
18088
|
+
unstaged: z8,
|
|
18089
|
+
last_turn: z8
|
|
17844
18090
|
};
|
|
17845
18091
|
}
|
|
17846
18092
|
function filesFromDiff(nameStatus, numstat, combinedDiff, maxHunk) {
|
|
@@ -18229,8 +18475,8 @@ function isImageRelativePath(relativePath) {
|
|
|
18229
18475
|
function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
18230
18476
|
assertSafeRelativePath(relativePath);
|
|
18231
18477
|
const maxBytes = opts?.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
18232
|
-
const abs = (0,
|
|
18233
|
-
const st = (0,
|
|
18478
|
+
const abs = (0, import_node_path42.join)(worktreePath, relativePath);
|
|
18479
|
+
const st = (0, import_node_fs45.statSync)(abs);
|
|
18234
18480
|
if (!st.isFile()) {
|
|
18235
18481
|
throw new Error(`Not a file: ${relativePath}`);
|
|
18236
18482
|
}
|
|
@@ -18239,7 +18485,7 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
18239
18485
|
`File too large to upload (${st.size} bytes; max ${maxBytes})`
|
|
18240
18486
|
);
|
|
18241
18487
|
}
|
|
18242
|
-
const buf = (0,
|
|
18488
|
+
const buf = (0, import_node_fs45.readFileSync)(abs);
|
|
18243
18489
|
return {
|
|
18244
18490
|
path: relativePath,
|
|
18245
18491
|
contentBase64: buf.toString("base64"),
|
|
@@ -18249,12 +18495,12 @@ function readWorktreeFileForUpload(worktreePath, relativePath, opts) {
|
|
|
18249
18495
|
function readWorktreeFile(worktreePath, relativePath, opts) {
|
|
18250
18496
|
assertSafeRelativePath(relativePath);
|
|
18251
18497
|
const maxBytes = opts?.maxBytes ?? 2e5;
|
|
18252
|
-
const abs = (0,
|
|
18253
|
-
const st = (0,
|
|
18498
|
+
const abs = (0, import_node_path42.join)(worktreePath, relativePath);
|
|
18499
|
+
const st = (0, import_node_fs45.statSync)(abs);
|
|
18254
18500
|
if (!st.isFile()) {
|
|
18255
18501
|
throw new Error(`Not a file: ${relativePath}`);
|
|
18256
18502
|
}
|
|
18257
|
-
const buf = (0,
|
|
18503
|
+
const buf = (0, import_node_fs45.readFileSync)(abs);
|
|
18258
18504
|
if (isImageRelativePath(relativePath)) {
|
|
18259
18505
|
const maxImageBytes = Math.max(maxBytes, 15e6);
|
|
18260
18506
|
const truncated2 = buf.length > maxImageBytes;
|
|
@@ -18297,9 +18543,9 @@ function assertSafeRelativePath(relativePath) {
|
|
|
18297
18543
|
}
|
|
18298
18544
|
function writeWorktreeFile(worktreePath, relativePath, content) {
|
|
18299
18545
|
assertSafeRelativePath(relativePath);
|
|
18300
|
-
const abs = (0,
|
|
18301
|
-
(0,
|
|
18302
|
-
(0,
|
|
18546
|
+
const abs = (0, import_node_path42.join)(worktreePath, relativePath);
|
|
18547
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(abs), { recursive: true });
|
|
18548
|
+
(0, import_node_fs45.writeFileSync)(abs, content, "utf8");
|
|
18303
18549
|
return { path: relativePath };
|
|
18304
18550
|
}
|
|
18305
18551
|
async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
@@ -18316,12 +18562,12 @@ async function getDiffSummary(worktreePath, repoPath, opts) {
|
|
|
18316
18562
|
truncated: full.files.length > maxFiles
|
|
18317
18563
|
};
|
|
18318
18564
|
}
|
|
18319
|
-
var
|
|
18565
|
+
var import_node_fs45, import_node_path42, mergeBaseCache, MERGE_BASE_TTL_MS, SHA_RE, IMAGE_EXTENSIONS2, DEFAULT_UPLOAD_MAX_BYTES;
|
|
18320
18566
|
var init_diff = __esm({
|
|
18321
18567
|
"src/diff/diff.ts"() {
|
|
18322
18568
|
"use strict";
|
|
18323
|
-
|
|
18324
|
-
|
|
18569
|
+
import_node_fs45 = require("fs");
|
|
18570
|
+
import_node_path42 = require("path");
|
|
18325
18571
|
init_run();
|
|
18326
18572
|
init_worktree();
|
|
18327
18573
|
mergeBaseCache = /* @__PURE__ */ new Map();
|
|
@@ -18470,7 +18716,7 @@ var init_long_running = __esm({
|
|
|
18470
18716
|
|
|
18471
18717
|
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.
|
|
18472
18718
|
|
|
18473
|
-
Do **not** ask the human to check back. Detach, then **wait** in 45s slices (same idea as
|
|
18719
|
+
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
|
|
18474
18720
|
|
|
18475
18721
|
## Tool
|
|
18476
18722
|
|
|
@@ -18480,7 +18726,8 @@ Use the helper from the Sideboard playbook \u2014 an absolute \`node "\u2026" st
|
|
|
18480
18726
|
# Start (exits in ~1s; job survives this turn)
|
|
18481
18727
|
node <detached-job.js> start <id> -- <command> [args...]
|
|
18482
18728
|
|
|
18483
|
-
# Wait \u2014
|
|
18729
|
+
# Wait \u2014 prefer MCP wait_for_job (same 45s / stillRunning contract).
|
|
18730
|
+
# Shell fallback:
|
|
18484
18731
|
node <detached-job.js> wait <id>
|
|
18485
18732
|
|
|
18486
18733
|
# Block until the process exits (humans / a turn that will not be interrupted)
|
|
@@ -18493,7 +18740,7 @@ node <detached-job.js> status <id>
|
|
|
18493
18740
|
|
|
18494
18741
|
Wait JSON:
|
|
18495
18742
|
|
|
18496
|
-
- \`stillRunning: true\` \u2192
|
|
18743
|
+
- \`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.
|
|
18497
18744
|
- \`ok: true\` \u2192 exit 0 \u2192 continue the rest of the task.
|
|
18498
18745
|
- \`failed: true\` \u2192 exit 1 \u2192 read \`progress\`, fix, start **once**.
|
|
18499
18746
|
|
|
@@ -18523,7 +18770,7 @@ node <detached-job.js> wait --pid-file FILE --log-file FILE [--ok-pattern TEXT]
|
|
|
18523
18770
|
|
|
18524
18771
|
1. \`start\` once. If JSON says \`already-running\`, do not start again.
|
|
18525
18772
|
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.
|
|
18526
|
-
3. Loop \`
|
|
18773
|
+
3. Loop \`wait_for_job\` (or shell \`wait\`). After each slice, \`present_artifact\` the same id with \`content=delta\` only.
|
|
18527
18774
|
4. On \`ok\`, present once more (\`status=ok\`, last \`delta\`) and finish the task. On \`failed\`, fix from the log.
|
|
18528
18775
|
|
|
18529
18776
|
Never tell the user \u201Csay status when it\u2019s done.\u201D You wait.
|
|
@@ -18589,7 +18836,7 @@ function parseFrontmatter(content) {
|
|
|
18589
18836
|
}
|
|
18590
18837
|
function readSkill(skillMd, source) {
|
|
18591
18838
|
try {
|
|
18592
|
-
const content = (0,
|
|
18839
|
+
const content = (0, import_node_fs46.readFileSync)(skillMd, "utf8");
|
|
18593
18840
|
const { name: fmName, description } = parseFrontmatter(content);
|
|
18594
18841
|
const dirName = skillMd.split("/").slice(-2, -1)[0] || "skill";
|
|
18595
18842
|
const name = fmName || dirName;
|
|
@@ -18608,19 +18855,19 @@ function readSkill(skillMd, source) {
|
|
|
18608
18855
|
}
|
|
18609
18856
|
}
|
|
18610
18857
|
function scanSkillsDir(dir, source, out) {
|
|
18611
|
-
if (!(0,
|
|
18858
|
+
if (!(0, import_node_fs46.existsSync)(dir)) return;
|
|
18612
18859
|
let entries;
|
|
18613
18860
|
try {
|
|
18614
|
-
entries = (0,
|
|
18861
|
+
entries = (0, import_node_fs46.readdirSync)(dir);
|
|
18615
18862
|
} catch {
|
|
18616
18863
|
return;
|
|
18617
18864
|
}
|
|
18618
18865
|
for (const entry of entries) {
|
|
18619
18866
|
if (entry.startsWith(".")) continue;
|
|
18620
|
-
const skillMd = (0,
|
|
18621
|
-
if (!(0,
|
|
18867
|
+
const skillMd = (0, import_node_path43.join)(dir, entry, "SKILL.md");
|
|
18868
|
+
if (!(0, import_node_fs46.existsSync)(skillMd)) continue;
|
|
18622
18869
|
try {
|
|
18623
|
-
if (!(0,
|
|
18870
|
+
if (!(0, import_node_fs46.statSync)(skillMd).isFile()) continue;
|
|
18624
18871
|
} catch {
|
|
18625
18872
|
continue;
|
|
18626
18873
|
}
|
|
@@ -18629,24 +18876,24 @@ function scanSkillsDir(dir, source, out) {
|
|
|
18629
18876
|
}
|
|
18630
18877
|
}
|
|
18631
18878
|
function scanClaudePluginSkills(pluginsRoot, out) {
|
|
18632
|
-
if (!(0,
|
|
18879
|
+
if (!(0, import_node_fs46.existsSync)(pluginsRoot)) return;
|
|
18633
18880
|
const walk = (dir, depth, lookingForSkillsDir) => {
|
|
18634
18881
|
if (depth > 7) return;
|
|
18635
18882
|
let entries;
|
|
18636
18883
|
try {
|
|
18637
|
-
entries = (0,
|
|
18884
|
+
entries = (0, import_node_fs46.readdirSync)(dir);
|
|
18638
18885
|
} catch {
|
|
18639
18886
|
return;
|
|
18640
18887
|
}
|
|
18641
18888
|
if (lookingForSkillsDir && entries.includes("SKILL.md")) {
|
|
18642
|
-
const skill = readSkill((0,
|
|
18889
|
+
const skill = readSkill((0, import_node_path43.join)(dir, "SKILL.md"), "cli");
|
|
18643
18890
|
if (skill) out.push(skill);
|
|
18644
18891
|
}
|
|
18645
18892
|
for (const entry of entries) {
|
|
18646
18893
|
if (entry === "node_modules" || entry === ".git") continue;
|
|
18647
|
-
const full = (0,
|
|
18894
|
+
const full = (0, import_node_path43.join)(dir, entry);
|
|
18648
18895
|
try {
|
|
18649
|
-
if (!(0,
|
|
18896
|
+
if (!(0, import_node_fs46.statSync)(full).isDirectory()) continue;
|
|
18650
18897
|
} catch {
|
|
18651
18898
|
continue;
|
|
18652
18899
|
}
|
|
@@ -18664,17 +18911,17 @@ function discoverSkills(worktreePath) {
|
|
|
18664
18911
|
const home = (0, import_node_os14.homedir)();
|
|
18665
18912
|
const collected = [];
|
|
18666
18913
|
for (const rel of [".claude/skills", ".cursor/skills", ".sideboard/skills", ".brightsy/skills", "skills"]) {
|
|
18667
|
-
scanSkillsDir((0,
|
|
18914
|
+
scanSkillsDir((0, import_node_path43.join)(worktreePath, rel), "workspace", collected);
|
|
18668
18915
|
}
|
|
18669
18916
|
for (const abs of [
|
|
18670
|
-
(0,
|
|
18671
|
-
(0,
|
|
18672
|
-
(0,
|
|
18673
|
-
(0,
|
|
18917
|
+
(0, import_node_path43.join)(home, ".claude/skills"),
|
|
18918
|
+
(0, import_node_path43.join)(home, ".cursor/skills"),
|
|
18919
|
+
(0, import_node_path43.join)(home, ".sideboard/skills"),
|
|
18920
|
+
(0, import_node_path43.join)(home, ".brightsy/skills")
|
|
18674
18921
|
]) {
|
|
18675
18922
|
scanSkillsDir(abs, "user", collected);
|
|
18676
18923
|
}
|
|
18677
|
-
scanClaudePluginSkills((0,
|
|
18924
|
+
scanClaudePluginSkills((0, import_node_path43.join)(home, ".claude/plugins"), collected);
|
|
18678
18925
|
collected.push(...bundledSkills());
|
|
18679
18926
|
const rank = {
|
|
18680
18927
|
workspace: 0,
|
|
@@ -18700,7 +18947,7 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
18700
18947
|
\u2026(truncated)` : body;
|
|
18701
18948
|
}
|
|
18702
18949
|
}
|
|
18703
|
-
const raw = (0,
|
|
18950
|
+
const raw = (0, import_node_fs46.readFileSync)(skillPath, "utf8");
|
|
18704
18951
|
if (raw.startsWith("---")) {
|
|
18705
18952
|
const end = raw.indexOf("\n---", 3);
|
|
18706
18953
|
if (end >= 0) {
|
|
@@ -18714,13 +18961,13 @@ function readSkillBody(skillPath, maxChars = 12e3) {
|
|
|
18714
18961
|
|
|
18715
18962
|
\u2026(truncated)` : raw;
|
|
18716
18963
|
}
|
|
18717
|
-
var
|
|
18964
|
+
var import_node_fs46, import_node_os14, import_node_path43, BUNDLED_SKILL_PREFIX;
|
|
18718
18965
|
var init_discover = __esm({
|
|
18719
18966
|
"src/skills/discover.ts"() {
|
|
18720
18967
|
"use strict";
|
|
18721
|
-
|
|
18968
|
+
import_node_fs46 = require("fs");
|
|
18722
18969
|
import_node_os14 = require("os");
|
|
18723
|
-
|
|
18970
|
+
import_node_path43 = require("path");
|
|
18724
18971
|
init_long_running();
|
|
18725
18972
|
BUNDLED_SKILL_PREFIX = "bundled:";
|
|
18726
18973
|
}
|
|
@@ -18815,17 +19062,17 @@ var init_expand = __esm({
|
|
|
18815
19062
|
function packagedDetachedJobPath() {
|
|
18816
19063
|
const dir = packagedMcpDir();
|
|
18817
19064
|
if (!dir) return null;
|
|
18818
|
-
const script = (0,
|
|
18819
|
-
return (0,
|
|
19065
|
+
const script = (0, import_node_path44.join)(dir, "scripts", "detached-job.js");
|
|
19066
|
+
return (0, import_node_fs47.existsSync)(script) ? script : null;
|
|
18820
19067
|
}
|
|
18821
19068
|
function resolveDetachedJobScript() {
|
|
18822
19069
|
const packaged = packagedDetachedJobPath();
|
|
18823
19070
|
if (packaged) return packaged;
|
|
18824
|
-
let dir = (0,
|
|
19071
|
+
let dir = (0, import_node_path44.dirname)((0, import_node_url3.fileURLToPath)(import_meta3.url));
|
|
18825
19072
|
for (let i = 0; i < 8; i++) {
|
|
18826
|
-
const candidate = (0,
|
|
18827
|
-
if ((0,
|
|
18828
|
-
const parent = (0,
|
|
19073
|
+
const candidate = (0, import_node_path44.join)(dir, "scripts", "detached-job.js");
|
|
19074
|
+
if ((0, import_node_fs47.existsSync)(candidate)) return candidate;
|
|
19075
|
+
const parent = (0, import_node_path44.dirname)(dir);
|
|
18829
19076
|
if (parent === dir) break;
|
|
18830
19077
|
dir = parent;
|
|
18831
19078
|
}
|
|
@@ -18836,12 +19083,12 @@ function formatDetachedJobInvoke(scriptPath) {
|
|
|
18836
19083
|
if (resolved) return `node ${JSON.stringify(resolved)}`;
|
|
18837
19084
|
return "node scripts/detached-job.js";
|
|
18838
19085
|
}
|
|
18839
|
-
var
|
|
19086
|
+
var import_node_fs47, import_node_path44, import_node_url3, import_meta3;
|
|
18840
19087
|
var init_detached_job_path = __esm({
|
|
18841
19088
|
"src/skills/detached-job-path.ts"() {
|
|
18842
19089
|
"use strict";
|
|
18843
|
-
|
|
18844
|
-
|
|
19090
|
+
import_node_fs47 = require("fs");
|
|
19091
|
+
import_node_path44 = require("path");
|
|
18845
19092
|
import_node_url3 = require("url");
|
|
18846
19093
|
init_packaged_runtime();
|
|
18847
19094
|
import_meta3 = {};
|
|
@@ -19062,14 +19309,15 @@ function formatLongRunningDirective(opts) {
|
|
|
19062
19309
|
`Helper (same tool as \`scripts/detached-job.js\` when that file exists in the worktree): \`${invoke}\``,
|
|
19063
19310
|
`- Start once: \`${invoke} start <id> -- <command> [args...]\` (cwd = this worktree). If JSON says already-running, do not start again.`,
|
|
19064
19311
|
"- Immediately `present_artifact` `type=log` with `artifact_id=<id>` and `status=running` \u2014 the side column is the live view.",
|
|
19065
|
-
`- Loop
|
|
19312
|
+
`- 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.`,
|
|
19313
|
+
"- Do not end the turn with \u201CI\u2019ll let you know when it\u2019s done.\u201D Stay in the loop until stillRunning is false.",
|
|
19066
19314
|
"- ok \u2192 finish the task. failed \u2192 read the log, fix, start once.",
|
|
19067
19315
|
"State: `.context/.sideboard/detached-jobs/<id>/` (local scratch). Full guide: `/long-running` (always available)."
|
|
19068
19316
|
].join("\n");
|
|
19069
19317
|
}
|
|
19070
19318
|
function formatLongRunningReminder(opts) {
|
|
19071
19319
|
const invoke = formatDetachedJobInvoke(opts?.scriptPath);
|
|
19072
|
-
return `Long jobs: \`${invoke} start <id> -- <cmd>\`, loop wait, present_artifact type=log (delta). Do not
|
|
19320
|
+
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.`;
|
|
19073
19321
|
}
|
|
19074
19322
|
function formatArtifactDirective() {
|
|
19075
19323
|
return [
|
|
@@ -19106,11 +19354,11 @@ function loadAgentInstructions(worktreePath, agent) {
|
|
|
19106
19354
|
const out = [];
|
|
19107
19355
|
for (const rel of candidates) {
|
|
19108
19356
|
if (seenPaths.has(rel)) continue;
|
|
19109
|
-
const abs = (0,
|
|
19110
|
-
if (!(0,
|
|
19357
|
+
const abs = (0, import_node_path45.join)(worktreePath, rel);
|
|
19358
|
+
if (!(0, import_node_fs48.existsSync)(abs)) continue;
|
|
19111
19359
|
try {
|
|
19112
|
-
if (!(0,
|
|
19113
|
-
let content = (0,
|
|
19360
|
+
if (!(0, import_node_fs48.statSync)(abs).isFile()) continue;
|
|
19361
|
+
let content = (0, import_node_fs48.readFileSync)(abs, "utf8");
|
|
19114
19362
|
if (!content.trim()) continue;
|
|
19115
19363
|
if (content.length > MAX_CHARS_PER_FILE) {
|
|
19116
19364
|
content = `${content.slice(0, MAX_CHARS_PER_FILE)}
|
|
@@ -19150,12 +19398,12 @@ function withAgentInstructions(prompt, files) {
|
|
|
19150
19398
|
|
|
19151
19399
|
${prompt}`;
|
|
19152
19400
|
}
|
|
19153
|
-
var
|
|
19401
|
+
var import_node_fs48, import_node_path45, GITHUB_TICKET_REF, KEYED_TICKET_REF, FILES_BY_AGENT, MAX_CHARS_PER_FILE;
|
|
19154
19402
|
var init_instructions = __esm({
|
|
19155
19403
|
"src/agents/instructions.ts"() {
|
|
19156
19404
|
"use strict";
|
|
19157
|
-
|
|
19158
|
-
|
|
19405
|
+
import_node_fs48 = require("fs");
|
|
19406
|
+
import_node_path45 = require("path");
|
|
19159
19407
|
init_git_auth_mode();
|
|
19160
19408
|
init_worktree_labels();
|
|
19161
19409
|
init_detached_job_path();
|
|
@@ -19212,12 +19460,12 @@ function firstString2(record, keys) {
|
|
|
19212
19460
|
return "";
|
|
19213
19461
|
}
|
|
19214
19462
|
async function readJson(res) {
|
|
19215
|
-
const
|
|
19216
|
-
if (!
|
|
19463
|
+
const text7 = await res.text();
|
|
19464
|
+
if (!text7.trim()) return null;
|
|
19217
19465
|
try {
|
|
19218
|
-
return JSON.parse(
|
|
19466
|
+
return JSON.parse(text7);
|
|
19219
19467
|
} catch {
|
|
19220
|
-
return
|
|
19468
|
+
return text7;
|
|
19221
19469
|
}
|
|
19222
19470
|
}
|
|
19223
19471
|
async function authorizedGet(url, token) {
|
|
@@ -19467,11 +19715,11 @@ function resolvePlanMarkdown(opts) {
|
|
|
19467
19715
|
source: "exit_plan"
|
|
19468
19716
|
};
|
|
19469
19717
|
}
|
|
19470
|
-
const
|
|
19471
|
-
if (
|
|
19718
|
+
const text7 = opts.text?.trim();
|
|
19719
|
+
if (text7 && text7.length >= 80) {
|
|
19472
19720
|
return {
|
|
19473
19721
|
title: "Plan",
|
|
19474
|
-
content:
|
|
19722
|
+
content: text7,
|
|
19475
19723
|
path: PLAN_FILE_REL,
|
|
19476
19724
|
source: "text"
|
|
19477
19725
|
};
|
|
@@ -19503,40 +19751,40 @@ __export(plan_file_exports, {
|
|
|
19503
19751
|
writePlanFile: () => writePlanFile
|
|
19504
19752
|
});
|
|
19505
19753
|
function ensureAttachmentsGitignore(worktreePath) {
|
|
19506
|
-
const gitignoreAbs = (0,
|
|
19507
|
-
if ((0,
|
|
19508
|
-
(0,
|
|
19509
|
-
(0,
|
|
19754
|
+
const gitignoreAbs = (0, import_node_path46.join)(worktreePath, ATTACHMENTS_DIR, ".gitignore");
|
|
19755
|
+
if ((0, import_node_fs49.existsSync)(gitignoreAbs)) return;
|
|
19756
|
+
(0, import_node_fs49.mkdirSync)((0, import_node_path46.dirname)(gitignoreAbs), { recursive: true });
|
|
19757
|
+
(0, import_node_fs49.writeFileSync)(gitignoreAbs, attachmentsGitignoreBody(), "utf8");
|
|
19510
19758
|
}
|
|
19511
19759
|
function planFileAbs(worktreePath) {
|
|
19512
|
-
return (0,
|
|
19760
|
+
return (0, import_node_path46.join)(worktreePath, PLAN_FILE_REL);
|
|
19513
19761
|
}
|
|
19514
19762
|
function readTextIfPresent2(abs) {
|
|
19515
|
-
if (!(0,
|
|
19763
|
+
if (!(0, import_node_fs49.existsSync)(abs)) return null;
|
|
19516
19764
|
try {
|
|
19517
|
-
const content = (0,
|
|
19765
|
+
const content = (0, import_node_fs49.readFileSync)(abs, "utf8");
|
|
19518
19766
|
return content.trim() ? content : null;
|
|
19519
19767
|
} catch {
|
|
19520
19768
|
return null;
|
|
19521
19769
|
}
|
|
19522
19770
|
}
|
|
19523
19771
|
function readPlanFile(worktreePath) {
|
|
19524
|
-
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0,
|
|
19772
|
+
return readTextIfPresent2(planFileAbs(worktreePath)) ?? readTextIfPresent2((0, import_node_path46.join)(worktreePath, `${LEGACY_ATTACHMENTS_DIR}/plan.md`)) ?? readTextIfPresent2((0, import_node_path46.join)(worktreePath, LEGACY_PLAN_FILE_REL));
|
|
19525
19773
|
}
|
|
19526
19774
|
function writePlanFile(worktreePath, content) {
|
|
19527
19775
|
ensureAttachmentsGitignore(worktreePath);
|
|
19528
19776
|
const abs = planFileAbs(worktreePath);
|
|
19529
|
-
(0,
|
|
19777
|
+
(0, import_node_fs49.mkdirSync)((0, import_node_path46.dirname)(abs), { recursive: true });
|
|
19530
19778
|
const body = content.trimEnd() + (content.endsWith("\n") ? "" : "\n");
|
|
19531
|
-
(0,
|
|
19779
|
+
(0, import_node_fs49.writeFileSync)(abs, body, "utf8");
|
|
19532
19780
|
return PLAN_FILE_REL;
|
|
19533
19781
|
}
|
|
19534
|
-
var
|
|
19782
|
+
var import_node_fs49, import_node_path46;
|
|
19535
19783
|
var init_plan_file = __esm({
|
|
19536
19784
|
"src/plan/plan-file.ts"() {
|
|
19537
19785
|
"use strict";
|
|
19538
|
-
|
|
19539
|
-
|
|
19786
|
+
import_node_fs49 = require("fs");
|
|
19787
|
+
import_node_path46 = require("path");
|
|
19540
19788
|
init_workspace_scratch();
|
|
19541
19789
|
init_plan_present();
|
|
19542
19790
|
init_plan_present();
|
|
@@ -19584,13 +19832,13 @@ var init_sync_branch = __esm({
|
|
|
19584
19832
|
|
|
19585
19833
|
// src/agents/cursor-store.ts
|
|
19586
19834
|
function cursorSdkStoreDir(threadId) {
|
|
19587
|
-
const root = (0,
|
|
19835
|
+
const root = (0, import_node_path47.join)(appDataDir(), CURSOR_SDK_STORE_DIR);
|
|
19588
19836
|
const id = sanitizeCursorStoreSegment(threadId);
|
|
19589
19837
|
if (!id) return root;
|
|
19590
|
-
return (0,
|
|
19838
|
+
return (0, import_node_path47.join)(root, "threads", id);
|
|
19591
19839
|
}
|
|
19592
19840
|
function cursorSdkRunsNdjsonPath(threadId) {
|
|
19593
|
-
return (0,
|
|
19841
|
+
return (0, import_node_path47.join)(cursorSdkStoreDir(threadId), "runs.ndjson");
|
|
19594
19842
|
}
|
|
19595
19843
|
function cursorSdkRunsNdjsonSearchPaths(threadId) {
|
|
19596
19844
|
const scoped = cursorSdkRunsNdjsonPath(threadId);
|
|
@@ -19603,11 +19851,11 @@ function cursorSdkRunsNdjsonSearchPaths(threadId) {
|
|
|
19603
19851
|
function sanitizeCursorStoreSegment(threadId) {
|
|
19604
19852
|
return (threadId ?? "").trim().replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
19605
19853
|
}
|
|
19606
|
-
var
|
|
19854
|
+
var import_node_path47, CURSOR_SDK_STORE_DIR;
|
|
19607
19855
|
var init_cursor_store = __esm({
|
|
19608
19856
|
"src/agents/cursor-store.ts"() {
|
|
19609
19857
|
"use strict";
|
|
19610
|
-
|
|
19858
|
+
import_node_path47 = require("path");
|
|
19611
19859
|
init_paths();
|
|
19612
19860
|
CURSOR_SDK_STORE_DIR = "cursor-sdk-store";
|
|
19613
19861
|
}
|
|
@@ -19629,9 +19877,9 @@ function recoverFinishedCursorRun(opts) {
|
|
|
19629
19877
|
return best;
|
|
19630
19878
|
}
|
|
19631
19879
|
function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
|
|
19632
|
-
if (!(0,
|
|
19880
|
+
if (!(0, import_node_fs50.existsSync)(runsPath)) return null;
|
|
19633
19881
|
try {
|
|
19634
|
-
const lines = (0,
|
|
19882
|
+
const lines = (0, import_node_fs50.readFileSync)(runsPath, "utf8").split("\n");
|
|
19635
19883
|
let best = null;
|
|
19636
19884
|
for (const line of lines) {
|
|
19637
19885
|
const trimmed = line.trim();
|
|
@@ -19657,11 +19905,11 @@ function scanFinishedCursorRuns(runsPath, agentId, startedAfterMs) {
|
|
|
19657
19905
|
return null;
|
|
19658
19906
|
}
|
|
19659
19907
|
}
|
|
19660
|
-
var
|
|
19908
|
+
var import_node_fs50;
|
|
19661
19909
|
var init_cursor_recover = __esm({
|
|
19662
19910
|
"src/agents/cursor-recover.ts"() {
|
|
19663
19911
|
"use strict";
|
|
19664
|
-
|
|
19912
|
+
import_node_fs50 = require("fs");
|
|
19665
19913
|
init_cursor_store();
|
|
19666
19914
|
}
|
|
19667
19915
|
});
|
|
@@ -19793,13 +20041,13 @@ async function startOrchestration(opts) {
|
|
|
19793
20041
|
}
|
|
19794
20042
|
return updated;
|
|
19795
20043
|
}
|
|
19796
|
-
var import_node_events,
|
|
20044
|
+
var import_node_events, import_node_fs51, LIVE_TURN_SPAWN_GRACE_MS, STALE_AGENT_PID_WAIT_MS, Orchestrator, singleton;
|
|
19797
20045
|
var init_orchestrator = __esm({
|
|
19798
20046
|
"src/orchestrator/orchestrator.ts"() {
|
|
19799
20047
|
"use strict";
|
|
19800
20048
|
import_node_events = require("events");
|
|
19801
20049
|
init_outbound_watch();
|
|
19802
|
-
|
|
20050
|
+
import_node_fs51 = require("fs");
|
|
19803
20051
|
init_error_detail();
|
|
19804
20052
|
init_run();
|
|
19805
20053
|
init_stale_lock();
|
|
@@ -19820,6 +20068,7 @@ var init_orchestrator = __esm({
|
|
|
19820
20068
|
init_thread_store();
|
|
19821
20069
|
init_desktop_host();
|
|
19822
20070
|
init_child_halt();
|
|
20071
|
+
init_wait_for_job();
|
|
19823
20072
|
init_create();
|
|
19824
20073
|
init_cowboy();
|
|
19825
20074
|
init_orchestrator_capable();
|
|
@@ -19884,6 +20133,9 @@ var init_orchestrator = __esm({
|
|
|
19884
20133
|
* finish or a user send so a later crash can recover again.
|
|
19885
20134
|
*/
|
|
19886
20135
|
crashContinued = /* @__PURE__ */ new Set();
|
|
20136
|
+
/** Auto-continues after a worktree turn ended while a detached job still runs. */
|
|
20137
|
+
jobContinueCount = /* @__PURE__ */ new Map();
|
|
20138
|
+
jobContinueNudged = /* @__PURE__ */ new Set();
|
|
19887
20139
|
maxConcurrent;
|
|
19888
20140
|
runningCount = 0;
|
|
19889
20141
|
constructor(opts) {
|
|
@@ -19967,7 +20219,7 @@ var init_orchestrator = __esm({
|
|
|
19967
20219
|
}
|
|
19968
20220
|
continue;
|
|
19969
20221
|
}
|
|
19970
|
-
if (!(0,
|
|
20222
|
+
if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
|
|
19971
20223
|
setStatus(thread.id, "broken", "Worktree missing on disk");
|
|
19972
20224
|
this.emit({ type: "status_changed", threadId: thread.id, status: "broken" });
|
|
19973
20225
|
continue;
|
|
@@ -20182,6 +20434,37 @@ var init_orchestrator = __esm({
|
|
|
20182
20434
|
this.emit({ type: "queue_changed", threadId, queue });
|
|
20183
20435
|
this.haltDrain.delete(threadId);
|
|
20184
20436
|
}
|
|
20437
|
+
/**
|
|
20438
|
+
* Worktree agent ended the turn after “I’ll let you know” (or left a
|
|
20439
|
+
* detached test/pack job running). Queue a continue so the chat does not
|
|
20440
|
+
* go idle with nobody watching the log.
|
|
20441
|
+
*/
|
|
20442
|
+
maybeEnqueueJobContinue(threadId, chatText, parts = []) {
|
|
20443
|
+
if (this.haltDrain.has(threadId)) return;
|
|
20444
|
+
const thread = readThread(threadId);
|
|
20445
|
+
if (!thread || thread.status === "archived") return;
|
|
20446
|
+
const runningJobIds = listRunningDetachedJobs(thread.worktreePath ?? "");
|
|
20447
|
+
const decision = planJobContinue({
|
|
20448
|
+
runningJobIds,
|
|
20449
|
+
chatText,
|
|
20450
|
+
queueLength: thread.queue.length,
|
|
20451
|
+
continueCount: this.jobContinueCount.get(threadId) ?? 0,
|
|
20452
|
+
alreadyNudged: this.jobContinueNudged.has(threadId),
|
|
20453
|
+
isOrchestrator: isOrchestratorThread(thread),
|
|
20454
|
+
agent: thread.agent,
|
|
20455
|
+
watchedJob: turnWatchedDetachedJob(parts)
|
|
20456
|
+
});
|
|
20457
|
+
if (decision.action === "none") {
|
|
20458
|
+
if (runningJobIds.length === 0) this.jobContinueCount.delete(threadId);
|
|
20459
|
+
return;
|
|
20460
|
+
}
|
|
20461
|
+
if (decision.action === "nudge") this.jobContinueNudged.add(threadId);
|
|
20462
|
+
else this.jobContinueCount.set(threadId, (this.jobContinueCount.get(threadId) ?? 0) + 1);
|
|
20463
|
+
const queue = [decision.prompt, ...thread.queue];
|
|
20464
|
+
updateThread(threadId, { queue });
|
|
20465
|
+
this.emit({ type: "queue_changed", threadId, queue });
|
|
20466
|
+
this.haltDrain.delete(threadId);
|
|
20467
|
+
}
|
|
20185
20468
|
getThreads(includeArchived = false) {
|
|
20186
20469
|
return listThreads({ includeArchived });
|
|
20187
20470
|
}
|
|
@@ -20281,6 +20564,8 @@ var init_orchestrator = __esm({
|
|
|
20281
20564
|
}
|
|
20282
20565
|
const queue = [...current.queue, prompt];
|
|
20283
20566
|
this.crashContinued.delete(thread.id);
|
|
20567
|
+
this.jobContinueCount.delete(thread.id);
|
|
20568
|
+
this.jobContinueNudged.delete(thread.id);
|
|
20284
20569
|
this.haltDrain.delete(thread.id);
|
|
20285
20570
|
const inFlight = this.activeTurns.has(thread.id) || this.startingTurns.has(thread.id) || current.status === "running";
|
|
20286
20571
|
shouldSteer = followUp === "steer" && (inFlight || current.queue.length > 0);
|
|
@@ -20313,11 +20598,11 @@ var init_orchestrator = __esm({
|
|
|
20313
20598
|
return results;
|
|
20314
20599
|
}
|
|
20315
20600
|
/** Edit the text of a not-yet-started queued message. */
|
|
20316
|
-
async editQueuedMessage(threadRef, index,
|
|
20601
|
+
async editQueuedMessage(threadRef, index, text7) {
|
|
20317
20602
|
const thread = this.requireThread(threadRef);
|
|
20318
20603
|
return withThreadLock(thread.id, async () => {
|
|
20319
20604
|
const current = this.requireThread(thread.id);
|
|
20320
|
-
const trimmed =
|
|
20605
|
+
const trimmed = text7.trim();
|
|
20321
20606
|
if (!trimmed || index < 0 || index >= current.queue.length) {
|
|
20322
20607
|
return current;
|
|
20323
20608
|
}
|
|
@@ -20503,6 +20788,7 @@ var init_orchestrator = __esm({
|
|
|
20503
20788
|
ticketId: issueTicket?.id,
|
|
20504
20789
|
ticketProvider: issueTicket?.provider
|
|
20505
20790
|
}) : null;
|
|
20791
|
+
const viewerContextReminder = thread.agent !== "brightsy" && !isOrchestratorThread(thread) ? formatViewerContextReminder() : null;
|
|
20506
20792
|
const slackReplyContext = formatSlackRepliesForTurn(
|
|
20507
20793
|
pendingSlackExternalReplies(thread.messages)
|
|
20508
20794
|
);
|
|
@@ -20512,6 +20798,7 @@ var init_orchestrator = __esm({
|
|
|
20512
20798
|
worktreeReminder,
|
|
20513
20799
|
optionalServicesReminder,
|
|
20514
20800
|
issueToolsReminder,
|
|
20801
|
+
viewerContextReminder,
|
|
20515
20802
|
artifactReminder,
|
|
20516
20803
|
longRunningReminder,
|
|
20517
20804
|
slackReplyContext,
|
|
@@ -20552,6 +20839,18 @@ var init_orchestrator = __esm({
|
|
|
20552
20839
|
ticketId: freshIssueTicket?.id,
|
|
20553
20840
|
ticketProvider: freshIssueTicket?.provider
|
|
20554
20841
|
});
|
|
20842
|
+
let viewerContextDirective = null;
|
|
20843
|
+
if (!isBrightsy && !isOrchestration) {
|
|
20844
|
+
try {
|
|
20845
|
+
viewerContextDirective = formatViewerContextDirective(
|
|
20846
|
+
resolveViewerProfileForRepo(fresh.repoPath || fresh.worktreePath)
|
|
20847
|
+
);
|
|
20848
|
+
} catch {
|
|
20849
|
+
viewerContextDirective = formatViewerContextDirective(
|
|
20850
|
+
resolveViewerProfileForRepo()
|
|
20851
|
+
);
|
|
20852
|
+
}
|
|
20853
|
+
}
|
|
20555
20854
|
const settings = loadWorkspaceSettings(fresh.worktreePath, fresh.repoPath);
|
|
20556
20855
|
const renameBranchDirective = !isBrightsy && !isOrchestration && autoRenameBranchEnabled2() ? formatRenameBranchDirective(fresh, {
|
|
20557
20856
|
customPrompt: settings?.prompts?.renameBranch
|
|
@@ -20583,6 +20882,7 @@ var init_orchestrator = __esm({
|
|
|
20583
20882
|
worktreeDirective,
|
|
20584
20883
|
optionalServicesDirective,
|
|
20585
20884
|
issueToolsDirective,
|
|
20885
|
+
viewerContextDirective,
|
|
20586
20886
|
artifactDirective,
|
|
20587
20887
|
longRunningDirective,
|
|
20588
20888
|
renameBranchDirective,
|
|
@@ -20693,6 +20993,7 @@ var init_orchestrator = __esm({
|
|
|
20693
20993
|
worktreeDirective,
|
|
20694
20994
|
optionalServicesDirective,
|
|
20695
20995
|
issueToolsDirective,
|
|
20996
|
+
viewerContextDirective,
|
|
20696
20997
|
artifactDirective,
|
|
20697
20998
|
longRunningDirective,
|
|
20698
20999
|
renameBranchDirective,
|
|
@@ -20795,6 +21096,7 @@ var init_orchestrator = __esm({
|
|
|
20795
21096
|
this.emit({ type: "turn_finished", threadId, exitCode });
|
|
20796
21097
|
if (exitCode === 0) {
|
|
20797
21098
|
this.crashContinued.delete(threadId);
|
|
21099
|
+
this.maybeEnqueueJobContinue(threadId, chatText, parts);
|
|
20798
21100
|
} else {
|
|
20799
21101
|
const blob = [chatText, detail].filter(Boolean).join("\n");
|
|
20800
21102
|
void this.maybeHandleOrchestrationQuotaFailover(threadId, blob);
|
|
@@ -21226,13 +21528,13 @@ var init_orchestrator = __esm({
|
|
|
21226
21528
|
const lastAgent = [...thread.messages].reverse().find((m) => m.role === "agent");
|
|
21227
21529
|
const lastError = thread.lastError ?? null;
|
|
21228
21530
|
const rawText = (lastAgent?.text ?? "").trim();
|
|
21229
|
-
const
|
|
21531
|
+
const text7 = (rawText && !isInternalAgentStatusText(rawText) ? rawText : "") || (thread.status === "error" || thread.status === "stopped" || thread.status === "broken" ? lastError ?? "" : "");
|
|
21230
21532
|
const stillRunning = this.threadLooksLive(thread);
|
|
21231
21533
|
const live = stillRunning ? readTurnLive(thread.id) : null;
|
|
21232
21534
|
const liveSummary = live?.summary && !isInternalAgentStatusText(live.summary) ? live.summary : null;
|
|
21233
21535
|
const queuedHint = stillRunning && thread.status === "queued" && !liveSummary ? "Queued \u2014 waiting for a concurrency slot" : null;
|
|
21234
21536
|
return {
|
|
21235
|
-
text:
|
|
21537
|
+
text: text7,
|
|
21236
21538
|
status: thread.status,
|
|
21237
21539
|
sessionId: thread.sessionId,
|
|
21238
21540
|
lastError,
|
|
@@ -21795,7 +22097,7 @@ var init_orchestrator = __esm({
|
|
|
21795
22097
|
this.emit({ type: "status_changed", threadId: restored2.id, status: restored2.status });
|
|
21796
22098
|
return restored2;
|
|
21797
22099
|
}
|
|
21798
|
-
if (!(0,
|
|
22100
|
+
if (!(0, import_node_fs51.existsSync)(thread.worktreePath)) {
|
|
21799
22101
|
if (isCowboyThread(thread) || isPrimaryCheckoutThread(thread)) {
|
|
21800
22102
|
throw new Error(
|
|
21801
22103
|
`Cowboy checkout missing: ${thread.worktreePath}. Re-add the project folder, then restore.`
|
|
@@ -21999,10 +22301,6 @@ var init_schedule_runner = __esm({
|
|
|
21999
22301
|
var index_exports = {};
|
|
22000
22302
|
__export(index_exports, {
|
|
22001
22303
|
ABLETIME_MCP_PATH: () => ABLETIME_MCP_PATH,
|
|
22002
|
-
ACCOUNT_ROLES: () => ACCOUNT_ROLES,
|
|
22003
|
-
ACCOUNT_ROLE_LABELS: () => ACCOUNT_ROLE_LABELS,
|
|
22004
|
-
ACCOUNT_ROLE_MAX: () => ACCOUNT_ROLE_MAX,
|
|
22005
|
-
ACCOUNT_ROLE_PRESETS: () => ACCOUNT_ROLE_PRESETS,
|
|
22006
22304
|
AGENT_GIT_ACTIONS: () => AGENT_GIT_ACTIONS,
|
|
22007
22305
|
AGENT_RUNNER_MAX_OLD_SPACE_MB: () => AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
22008
22306
|
ATTACHMENTS_DIR: () => ATTACHMENTS_DIR,
|
|
@@ -22089,7 +22387,6 @@ __export(index_exports, {
|
|
|
22089
22387
|
WORKTREE_MCP_TOOLS: () => WORKTREE_MCP_TOOLS,
|
|
22090
22388
|
abletimeMcpRequest: () => abletimeMcpRequest,
|
|
22091
22389
|
abletimeMcpUrl: () => abletimeMcpUrl,
|
|
22092
|
-
accountRoleLabel: () => accountRoleLabel,
|
|
22093
22390
|
ackSlackInboundSeen: () => ackSlackInboundSeen,
|
|
22094
22391
|
addBoardPin: () => addBoardPin,
|
|
22095
22392
|
addPrStackLayer: () => addPrStackLayer,
|
|
@@ -22270,6 +22567,7 @@ __export(index_exports, {
|
|
|
22270
22567
|
fireSchedule: () => fireSchedule,
|
|
22271
22568
|
flattenTurnInput: () => flattenTurnInput,
|
|
22272
22569
|
flipLinearRelationType: () => flipLinearRelationType,
|
|
22570
|
+
foldLegacyRolesIntoNotes: () => foldLegacyRolesIntoNotes,
|
|
22273
22571
|
followUpBehavior: () => followUpBehavior,
|
|
22274
22572
|
forkChatTab: () => forkChatTab,
|
|
22275
22573
|
forkMessageSlice: () => forkMessageSlice,
|
|
@@ -22310,6 +22608,8 @@ __export(index_exports, {
|
|
|
22310
22608
|
formatSlackWorkingText: () => formatSlackWorkingText,
|
|
22311
22609
|
formatTranscriptMarkdown: () => formatTranscriptMarkdown,
|
|
22312
22610
|
formatUiReminder: () => formatUiReminder,
|
|
22611
|
+
formatViewerContextDirective: () => formatViewerContextDirective,
|
|
22612
|
+
formatViewerContextReminder: () => formatViewerContextReminder,
|
|
22313
22613
|
formatWorkspaceInventory: () => formatWorkspaceInventory,
|
|
22314
22614
|
formatWorkspaceProfileSuffix: () => formatWorkspaceProfileSuffix,
|
|
22315
22615
|
formatWorktreeDirective: () => formatWorktreeDirective,
|
|
@@ -22547,7 +22847,6 @@ __export(index_exports, {
|
|
|
22547
22847
|
planQuestionsSignature: () => planQuestionsSignature,
|
|
22548
22848
|
pollSlackOutboundWatches: () => pollSlackOutboundWatches,
|
|
22549
22849
|
posixShellSingleQuote: () => posixShellSingleQuote,
|
|
22550
|
-
preferTeamsForRole: () => preferTeamsForRole,
|
|
22551
22850
|
preferredCursorCostCents: () => preferredCursorCostCents,
|
|
22552
22851
|
prepareTerminalCommand: () => prepareTerminalCommand,
|
|
22553
22852
|
previewLand: () => previewLand,
|
|
@@ -22559,6 +22858,7 @@ __export(index_exports, {
|
|
|
22559
22858
|
readSetupLog: () => readSetupLog,
|
|
22560
22859
|
readSkillBody: () => readSkillBody,
|
|
22561
22860
|
readThread: () => readThread,
|
|
22861
|
+
readViewerContext: () => readViewerContext,
|
|
22562
22862
|
readWorktreeFile: () => readWorktreeFile,
|
|
22563
22863
|
readWorktreeFileForUpload: () => readWorktreeFileForUpload,
|
|
22564
22864
|
readWorktreeInclude: () => readWorktreeInclude,
|
|
@@ -22615,7 +22915,6 @@ __export(index_exports, {
|
|
|
22615
22915
|
resolveViewerProfile: () => resolveViewerProfile,
|
|
22616
22916
|
resolveViewerProfileForRepo: () => resolveViewerProfileForRepo,
|
|
22617
22917
|
resolveWorktreeStartPoint: () => resolveWorktreeStartPoint,
|
|
22618
|
-
reviewTeamHintsForRoles: () => reviewTeamHintsForRoles,
|
|
22619
22918
|
rewriteAbleTimeError: () => rewriteAbleTimeError,
|
|
22620
22919
|
rewriteLinearError: () => rewriteLinearError,
|
|
22621
22920
|
run: () => run,
|
|
@@ -22749,6 +23048,7 @@ __export(index_exports, {
|
|
|
22749
23048
|
writeInjectedMcpConfig: () => writeInjectedMcpConfig,
|
|
22750
23049
|
writePlanFile: () => writePlanFile,
|
|
22751
23050
|
writeThread: () => writeThread,
|
|
23051
|
+
writeViewerContext: () => writeViewerContext,
|
|
22752
23052
|
writeWorktreeFile: () => writeWorktreeFile
|
|
22753
23053
|
});
|
|
22754
23054
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -22797,9 +23097,9 @@ async function getGitHubStatus() {
|
|
|
22797
23097
|
};
|
|
22798
23098
|
}
|
|
22799
23099
|
const status = await run("gh", ["auth", "status"], { reject: false });
|
|
22800
|
-
const
|
|
23100
|
+
const text7 = `${status.stdout}
|
|
22801
23101
|
${status.stderr}`;
|
|
22802
|
-
const loginMatch =
|
|
23102
|
+
const loginMatch = text7.match(/Logged in to ([^\s]+) account (\S+)/i) ?? text7.match(/Logged in to ([^\s]+) as (\S+)/i);
|
|
22803
23103
|
if (loginMatch) {
|
|
22804
23104
|
return {
|
|
22805
23105
|
connected: true,
|
|
@@ -24165,11 +24465,11 @@ function fenceLanguage(path2, language) {
|
|
|
24165
24465
|
}
|
|
24166
24466
|
function buildCodeRefAttachment(input) {
|
|
24167
24467
|
const path2 = input.path.trim();
|
|
24168
|
-
const
|
|
24468
|
+
const text7 = input.text.replace(/\n$/, "");
|
|
24169
24469
|
if (!path2) {
|
|
24170
24470
|
throw new Error("code reference requires a file path");
|
|
24171
24471
|
}
|
|
24172
|
-
if (!
|
|
24472
|
+
if (!text7.trim()) {
|
|
24173
24473
|
throw new Error("code reference requires selected text");
|
|
24174
24474
|
}
|
|
24175
24475
|
if (input.startLine < 1 || input.endLine < 1 || input.endLine < input.startLine) {
|
|
@@ -24182,7 +24482,7 @@ function buildCodeRefAttachment(input) {
|
|
|
24182
24482
|
`Referenced code from \`${path2}\` (${range}).`,
|
|
24183
24483
|
"",
|
|
24184
24484
|
fence,
|
|
24185
|
-
|
|
24485
|
+
text7,
|
|
24186
24486
|
"```"
|
|
24187
24487
|
].join("\n");
|
|
24188
24488
|
return {
|
|
@@ -24239,16 +24539,16 @@ var PASTE_ATTACH_MIN_CHARS = 1200;
|
|
|
24239
24539
|
var PASTE_ATTACH_MIN_LINES = 15;
|
|
24240
24540
|
var PASTED_NAME_RE = /^Pasted text #(\d+)\.txt$/i;
|
|
24241
24541
|
var PASTED_NAME_ALT_RE = /^pasted-(\d+)\.txt$/i;
|
|
24242
|
-
function pastedTextStats(
|
|
24243
|
-
const chars =
|
|
24542
|
+
function pastedTextStats(text7) {
|
|
24543
|
+
const chars = text7.length;
|
|
24244
24544
|
if (chars === 0) return { chars: 0, lines: 0 };
|
|
24245
|
-
const lines =
|
|
24545
|
+
const lines = text7.split(/\r\n|\r|\n/).length;
|
|
24246
24546
|
return { chars, lines };
|
|
24247
24547
|
}
|
|
24248
|
-
function shouldAttachPastedText(
|
|
24249
|
-
const trimmed =
|
|
24548
|
+
function shouldAttachPastedText(text7) {
|
|
24549
|
+
const trimmed = text7.trim();
|
|
24250
24550
|
if (!trimmed) return false;
|
|
24251
|
-
const { chars, lines } = pastedTextStats(
|
|
24551
|
+
const { chars, lines } = pastedTextStats(text7);
|
|
24252
24552
|
return chars >= PASTE_ATTACH_MIN_CHARS || lines >= PASTE_ATTACH_MIN_LINES;
|
|
24253
24553
|
}
|
|
24254
24554
|
function nextPastedTextName(existing) {
|
|
@@ -24259,13 +24559,13 @@ function nextPastedTextName(existing) {
|
|
|
24259
24559
|
}
|
|
24260
24560
|
return `Pasted text #${max + 1}.txt`;
|
|
24261
24561
|
}
|
|
24262
|
-
function buildPastedTextAttachment(
|
|
24562
|
+
function buildPastedTextAttachment(text7, opts) {
|
|
24263
24563
|
return {
|
|
24264
24564
|
id: opts?.id ?? (0, import_node_crypto11.randomUUID)(),
|
|
24265
24565
|
name: opts?.name ?? "Pasted text #1.txt",
|
|
24266
24566
|
kind: "file",
|
|
24267
24567
|
path: opts?.path,
|
|
24268
|
-
content:
|
|
24568
|
+
content: text7
|
|
24269
24569
|
};
|
|
24270
24570
|
}
|
|
24271
24571
|
|
|
@@ -24365,8 +24665,8 @@ function latestPendingPlanQuestions(input) {
|
|
|
24365
24665
|
return null;
|
|
24366
24666
|
}
|
|
24367
24667
|
var PLAN_QUESTION_ANSWERS_PREFIX = "Answers to your questions:";
|
|
24368
|
-
function isPlanQuestionAnswersMessage(
|
|
24369
|
-
return
|
|
24668
|
+
function isPlanQuestionAnswersMessage(text7) {
|
|
24669
|
+
return text7.startsWith(PLAN_QUESTION_ANSWERS_PREFIX);
|
|
24370
24670
|
}
|
|
24371
24671
|
function formatPlanQuestionAnswers(questions, answers) {
|
|
24372
24672
|
const lines = [PLAN_QUESTION_ANSWERS_PREFIX, ""];
|
|
@@ -24413,9 +24713,9 @@ init_orphan_cleanup();
|
|
|
24413
24713
|
// src/mcp/server.ts
|
|
24414
24714
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
24415
24715
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
24416
|
-
var
|
|
24716
|
+
var import_zod7 = require("zod");
|
|
24417
24717
|
var import_node_crypto13 = require("crypto");
|
|
24418
|
-
var
|
|
24718
|
+
var import_node_path50 = require("path");
|
|
24419
24719
|
init_orchestrator();
|
|
24420
24720
|
init_worktree();
|
|
24421
24721
|
init_global_workspace();
|
|
@@ -24432,30 +24732,8 @@ function mcpArchiveBlockedReason(thread) {
|
|
|
24432
24732
|
|
|
24433
24733
|
// src/mcp/server.ts
|
|
24434
24734
|
init_profile();
|
|
24435
|
-
|
|
24436
|
-
|
|
24437
|
-
var MCP_WAIT_FOR_TURN_MAX_MS = 45e3;
|
|
24438
|
-
function mcpWaitForTurnTimeoutMs(requested) {
|
|
24439
|
-
const n = requested ?? MCP_WAIT_FOR_TURN_MAX_MS;
|
|
24440
|
-
if (!Number.isFinite(n)) return MCP_WAIT_FOR_TURN_MAX_MS;
|
|
24441
|
-
return Math.min(Math.max(1e3, Math.floor(n)), MCP_WAIT_FOR_TURN_MAX_MS);
|
|
24442
|
-
}
|
|
24443
|
-
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.";
|
|
24444
|
-
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.";
|
|
24445
|
-
function mcpWaitStillRunningHint(status) {
|
|
24446
|
-
return status === "queued" ? MCP_WAIT_QUEUED_HINT : MCP_WAIT_STILL_RUNNING_HINT;
|
|
24447
|
-
}
|
|
24448
|
-
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.";
|
|
24449
|
-
var MCP_WAIT_BROKEN_HINT = "Child worktree is broken (missing on disk). Tell the user \u2014 do not treat this as success.";
|
|
24450
|
-
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.";
|
|
24451
|
-
function mcpWaitFinishedHint(status) {
|
|
24452
|
-
if (status === "stopped") return MCP_WAIT_STOPPED_HINT;
|
|
24453
|
-
if (status === "broken") return MCP_WAIT_BROKEN_HINT;
|
|
24454
|
-
if (status === "error") return MCP_WAIT_ERROR_HINT;
|
|
24455
|
-
return void 0;
|
|
24456
|
-
}
|
|
24457
|
-
|
|
24458
|
-
// src/mcp/server.ts
|
|
24735
|
+
init_wait_for_turn();
|
|
24736
|
+
init_wait_for_job();
|
|
24459
24737
|
init_message_parts();
|
|
24460
24738
|
init_turn_live();
|
|
24461
24739
|
|
|
@@ -24464,9 +24742,9 @@ init_message_parts();
|
|
|
24464
24742
|
function lastMessagePreview(messages, max = 160) {
|
|
24465
24743
|
if (!messages?.length) return null;
|
|
24466
24744
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
24467
|
-
const
|
|
24468
|
-
if (!
|
|
24469
|
-
const flat =
|
|
24745
|
+
const text7 = messages[i]?.text?.trim();
|
|
24746
|
+
if (!text7 || isInternalAgentStatusText(text7)) continue;
|
|
24747
|
+
const flat = text7.replace(/\s+/g, " ");
|
|
24470
24748
|
return flat.length > max ? `${flat.slice(0, max)}\u2026` : flat;
|
|
24471
24749
|
}
|
|
24472
24750
|
return null;
|
|
@@ -24700,8 +24978,8 @@ function labelGithubUrl(url) {
|
|
|
24700
24978
|
}
|
|
24701
24979
|
return { kind: "other", label: "GitHub", url: raw };
|
|
24702
24980
|
}
|
|
24703
|
-
function appendGithubLink(
|
|
24704
|
-
const body =
|
|
24981
|
+
function appendGithubLink(text7, githubUrl) {
|
|
24982
|
+
const body = text7.trimEnd();
|
|
24705
24983
|
if (!githubUrl?.trim()) return body;
|
|
24706
24984
|
const labeled = labelGithubUrl(githubUrl);
|
|
24707
24985
|
if (!labeled) {
|
|
@@ -25397,6 +25675,104 @@ function registerConnectedIssueVendorTools(server, settings = loadAppSettings())
|
|
|
25397
25675
|
if (isAbleTimeConnected(settings)) registerAbleTimeTools(server);
|
|
25398
25676
|
}
|
|
25399
25677
|
|
|
25678
|
+
// src/mcp/viewer-context-tools.ts
|
|
25679
|
+
var import_zod5 = require("zod");
|
|
25680
|
+
init_worktree();
|
|
25681
|
+
init_app_settings();
|
|
25682
|
+
init_workspaces();
|
|
25683
|
+
function text5(payload, isError = false) {
|
|
25684
|
+
return mcpJson(payload, isError);
|
|
25685
|
+
}
|
|
25686
|
+
function matchRegisteredWorkspace(repoPath) {
|
|
25687
|
+
const hint = (repoPath ?? "").trim().replace(/\/+$/, "");
|
|
25688
|
+
if (!hint) return null;
|
|
25689
|
+
const match = listWorkspaces().find((w) => {
|
|
25690
|
+
const stored = w.path.replace(/\/+$/, "");
|
|
25691
|
+
return stored === hint || hint.startsWith(`${stored}/`) || stored.startsWith(`${hint}/`);
|
|
25692
|
+
});
|
|
25693
|
+
return match?.path.replace(/\/+$/, "") ?? null;
|
|
25694
|
+
}
|
|
25695
|
+
async function resolveProjectRepoPath(repoPath) {
|
|
25696
|
+
const hint = repoPath?.trim() || process.cwd();
|
|
25697
|
+
let root = hint;
|
|
25698
|
+
try {
|
|
25699
|
+
root = await resolveRepoRoot(hint);
|
|
25700
|
+
} catch {
|
|
25701
|
+
root = hint.replace(/\/+$/, "");
|
|
25702
|
+
}
|
|
25703
|
+
return matchRegisteredWorkspace(root) ?? matchRegisteredWorkspace(hint);
|
|
25704
|
+
}
|
|
25705
|
+
var PROJECT_PATH_HINT = "Pass repoPath from list_workspaces. Orchestration cwd is synthetic and empty \u2014 do not omit repoPath for project context.";
|
|
25706
|
+
function registerViewerContextTools(server) {
|
|
25707
|
+
server.tool(
|
|
25708
|
+
"get_viewer_context",
|
|
25709
|
+
"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).",
|
|
25710
|
+
{
|
|
25711
|
+
repoPath: import_zod5.z.string().optional().describe(
|
|
25712
|
+
"Registered workspace or worktree path. Required from orchestration for a project. Omit on a worktree turn (uses cwd)."
|
|
25713
|
+
)
|
|
25714
|
+
},
|
|
25715
|
+
async ({ repoPath }) => {
|
|
25716
|
+
try {
|
|
25717
|
+
const path2 = await resolveProjectRepoPath(repoPath);
|
|
25718
|
+
return text5(readViewerContext(path2 ?? void 0));
|
|
25719
|
+
} catch (err) {
|
|
25720
|
+
return text5(
|
|
25721
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
25722
|
+
true
|
|
25723
|
+
);
|
|
25724
|
+
}
|
|
25725
|
+
}
|
|
25726
|
+
);
|
|
25727
|
+
server.tool(
|
|
25728
|
+
"update_viewer_context",
|
|
25729
|
+
"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.",
|
|
25730
|
+
{
|
|
25731
|
+
scope: import_zod5.z.enum(["account", "project"]).describe("account = Settings \u2192 Agents; project = Settings \u2192 Projects for one repo"),
|
|
25732
|
+
context: import_zod5.z.string().describe("Full replacement text (roles, tickets, review queues). Empty clears."),
|
|
25733
|
+
confirmed: import_zod5.z.boolean().optional().describe("Must be true after the user confirmed via ask_user. False/omit is rejected."),
|
|
25734
|
+
repoPath: import_zod5.z.string().optional().describe(
|
|
25735
|
+
"Required for scope=project from orchestration (path from list_workspaces). Worktree turns may omit it (cwd)."
|
|
25736
|
+
)
|
|
25737
|
+
},
|
|
25738
|
+
async ({ scope, context, confirmed, repoPath }) => {
|
|
25739
|
+
try {
|
|
25740
|
+
if (scope === "project") {
|
|
25741
|
+
const path2 = await resolveProjectRepoPath(repoPath);
|
|
25742
|
+
if (!path2) {
|
|
25743
|
+
return text5(
|
|
25744
|
+
{
|
|
25745
|
+
ok: false,
|
|
25746
|
+
error: "repoPath required",
|
|
25747
|
+
message: PROJECT_PATH_HINT
|
|
25748
|
+
},
|
|
25749
|
+
true
|
|
25750
|
+
);
|
|
25751
|
+
}
|
|
25752
|
+
const result2 = writeViewerContext({
|
|
25753
|
+
scope,
|
|
25754
|
+
context,
|
|
25755
|
+
repoPath: path2,
|
|
25756
|
+
confirmed: confirmed === true
|
|
25757
|
+
});
|
|
25758
|
+
return text5(result2, !result2.ok);
|
|
25759
|
+
}
|
|
25760
|
+
const result = writeViewerContext({
|
|
25761
|
+
scope,
|
|
25762
|
+
context,
|
|
25763
|
+
confirmed: confirmed === true
|
|
25764
|
+
});
|
|
25765
|
+
return text5(result, !result.ok);
|
|
25766
|
+
} catch (err) {
|
|
25767
|
+
return text5(
|
|
25768
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
25769
|
+
true
|
|
25770
|
+
);
|
|
25771
|
+
}
|
|
25772
|
+
}
|
|
25773
|
+
);
|
|
25774
|
+
}
|
|
25775
|
+
|
|
25400
25776
|
// src/mcp/pr-list.ts
|
|
25401
25777
|
function compactPrRow(pr) {
|
|
25402
25778
|
return {
|
|
@@ -25436,17 +25812,17 @@ init_list_prs();
|
|
|
25436
25812
|
init_app_settings();
|
|
25437
25813
|
|
|
25438
25814
|
// src/mcp/schedule-tools.ts
|
|
25439
|
-
var
|
|
25815
|
+
var import_zod6 = require("zod");
|
|
25440
25816
|
init_schedules();
|
|
25441
25817
|
init_schedule_runner();
|
|
25442
|
-
function
|
|
25818
|
+
function text6(payload, isError = false) {
|
|
25443
25819
|
return {
|
|
25444
25820
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
25445
25821
|
...isError ? { isError: true } : {}
|
|
25446
25822
|
};
|
|
25447
25823
|
}
|
|
25448
25824
|
function fail5(err) {
|
|
25449
|
-
return
|
|
25825
|
+
return text6(
|
|
25450
25826
|
{ error: err instanceof Error ? err.message : String(err) },
|
|
25451
25827
|
true
|
|
25452
25828
|
);
|
|
@@ -25471,7 +25847,7 @@ function registerScheduleTools(server) {
|
|
|
25471
25847
|
{},
|
|
25472
25848
|
async () => {
|
|
25473
25849
|
try {
|
|
25474
|
-
return
|
|
25850
|
+
return text6({ schedules: listSchedules() });
|
|
25475
25851
|
} catch (err) {
|
|
25476
25852
|
return fail5(err);
|
|
25477
25853
|
}
|
|
@@ -25481,17 +25857,17 @@ function registerScheduleTools(server) {
|
|
|
25481
25857
|
"create_schedule",
|
|
25482
25858
|
"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.",
|
|
25483
25859
|
{
|
|
25484
|
-
prompt:
|
|
25485
|
-
name:
|
|
25486
|
-
at:
|
|
25487
|
-
every:
|
|
25488
|
-
cron:
|
|
25489
|
-
tz:
|
|
25490
|
-
threadId:
|
|
25860
|
+
prompt: import_zod6.z.string().describe("User message / goal queued when the schedule fires"),
|
|
25861
|
+
name: import_zod6.z.string().optional(),
|
|
25862
|
+
at: import_zod6.z.string().optional().describe("ISO datetime for a one-shot"),
|
|
25863
|
+
every: import_zod6.z.string().optional().describe("Interval such as 15m, 1h, 6h, 1d"),
|
|
25864
|
+
cron: import_zod6.z.string().optional().describe("5-field cron expression"),
|
|
25865
|
+
tz: import_zod6.z.string().optional().describe("IANA timezone for cron (default: system)"),
|
|
25866
|
+
threadId: import_zod6.z.string().optional().describe(
|
|
25491
25867
|
'Existing orchestration chat id, or "self" for this coordinator. Omit to create a new Global chat on fire.'
|
|
25492
25868
|
),
|
|
25493
|
-
agent:
|
|
25494
|
-
model:
|
|
25869
|
+
agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional().describe("Agent for a new Global chat (omit for Account default)"),
|
|
25870
|
+
model: import_zod6.z.string().optional()
|
|
25495
25871
|
},
|
|
25496
25872
|
async (args) => {
|
|
25497
25873
|
try {
|
|
@@ -25511,7 +25887,7 @@ function registerScheduleTools(server) {
|
|
|
25511
25887
|
model: args.model,
|
|
25512
25888
|
createdBy: "mcp"
|
|
25513
25889
|
});
|
|
25514
|
-
return
|
|
25890
|
+
return text6({
|
|
25515
25891
|
schedule,
|
|
25516
25892
|
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."
|
|
25517
25893
|
});
|
|
@@ -25524,17 +25900,17 @@ function registerScheduleTools(server) {
|
|
|
25524
25900
|
"update_schedule",
|
|
25525
25901
|
"Update a local schedule (prompt, cadence, target thread, enabled). Pass id from list_schedules.",
|
|
25526
25902
|
{
|
|
25527
|
-
id:
|
|
25528
|
-
prompt:
|
|
25529
|
-
name:
|
|
25530
|
-
at:
|
|
25531
|
-
every:
|
|
25532
|
-
cron:
|
|
25533
|
-
tz:
|
|
25534
|
-
threadId:
|
|
25535
|
-
agent:
|
|
25536
|
-
model:
|
|
25537
|
-
enabled:
|
|
25903
|
+
id: import_zod6.z.string(),
|
|
25904
|
+
prompt: import_zod6.z.string().optional(),
|
|
25905
|
+
name: import_zod6.z.string().optional(),
|
|
25906
|
+
at: import_zod6.z.string().optional(),
|
|
25907
|
+
every: import_zod6.z.string().optional(),
|
|
25908
|
+
cron: import_zod6.z.string().optional(),
|
|
25909
|
+
tz: import_zod6.z.string().optional(),
|
|
25910
|
+
threadId: import_zod6.z.string().optional().describe('Existing orchestration chat, "self", or empty string to create a new chat each run'),
|
|
25911
|
+
agent: import_zod6.z.enum(["claude", "cursor", "codex", "opencode"]).optional(),
|
|
25912
|
+
model: import_zod6.z.string().optional(),
|
|
25913
|
+
enabled: import_zod6.z.boolean().optional()
|
|
25538
25914
|
},
|
|
25539
25915
|
async (args) => {
|
|
25540
25916
|
try {
|
|
@@ -25563,7 +25939,7 @@ function registerScheduleTools(server) {
|
|
|
25563
25939
|
enabled: args.enabled,
|
|
25564
25940
|
model: args.model
|
|
25565
25941
|
});
|
|
25566
|
-
return
|
|
25942
|
+
return text6({ schedule });
|
|
25567
25943
|
} catch (err) {
|
|
25568
25944
|
return fail5(err);
|
|
25569
25945
|
}
|
|
@@ -25572,11 +25948,11 @@ function registerScheduleTools(server) {
|
|
|
25572
25948
|
server.tool(
|
|
25573
25949
|
"delete_schedule",
|
|
25574
25950
|
"Delete a local schedule. Pass id from list_schedules.",
|
|
25575
|
-
{ id:
|
|
25951
|
+
{ id: import_zod6.z.string() },
|
|
25576
25952
|
async ({ id }) => {
|
|
25577
25953
|
try {
|
|
25578
25954
|
deleteSchedule(id);
|
|
25579
|
-
return
|
|
25955
|
+
return text6({ ok: true, id });
|
|
25580
25956
|
} catch (err) {
|
|
25581
25957
|
return fail5(err);
|
|
25582
25958
|
}
|
|
@@ -25585,14 +25961,14 @@ function registerScheduleTools(server) {
|
|
|
25585
25961
|
server.tool(
|
|
25586
25962
|
"run_schedule",
|
|
25587
25963
|
"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.",
|
|
25588
|
-
{ id:
|
|
25964
|
+
{ id: import_zod6.z.string() },
|
|
25589
25965
|
async ({ id }) => {
|
|
25590
25966
|
try {
|
|
25591
25967
|
if (!getSchedule(id)) {
|
|
25592
25968
|
return fail5(new Error(`Schedule not found: ${id}`));
|
|
25593
25969
|
}
|
|
25594
25970
|
const schedule = await fireSchedule(id);
|
|
25595
|
-
return
|
|
25971
|
+
return text6({ schedule });
|
|
25596
25972
|
} catch (err) {
|
|
25597
25973
|
return fail5(err);
|
|
25598
25974
|
}
|
|
@@ -25606,27 +25982,27 @@ init_gh_errors();
|
|
|
25606
25982
|
init_git_auth_mode();
|
|
25607
25983
|
|
|
25608
25984
|
// src/board/load-home-board.ts
|
|
25609
|
-
var
|
|
25610
|
-
var
|
|
25985
|
+
var import_node_fs53 = require("fs");
|
|
25986
|
+
var import_node_path49 = require("path");
|
|
25611
25987
|
init_worktree();
|
|
25612
25988
|
init_paths();
|
|
25613
25989
|
|
|
25614
25990
|
// src/board/board-pins.ts
|
|
25615
25991
|
var import_node_crypto12 = require("crypto");
|
|
25616
|
-
var
|
|
25617
|
-
var
|
|
25992
|
+
var import_node_fs52 = require("fs");
|
|
25993
|
+
var import_node_path48 = require("path");
|
|
25618
25994
|
init_paths();
|
|
25619
25995
|
init_home_board();
|
|
25620
25996
|
var FILE = "home-board-pins.json";
|
|
25621
25997
|
var VERSION = 1;
|
|
25622
25998
|
function pinsFile() {
|
|
25623
|
-
return (0,
|
|
25999
|
+
return (0, import_node_path48.join)(appDataDir(), FILE);
|
|
25624
26000
|
}
|
|
25625
26001
|
function readDisk() {
|
|
25626
26002
|
const path2 = pinsFile();
|
|
25627
|
-
if (!(0,
|
|
26003
|
+
if (!(0, import_node_fs52.existsSync)(path2)) return [];
|
|
25628
26004
|
try {
|
|
25629
|
-
const raw = JSON.parse((0,
|
|
26005
|
+
const raw = JSON.parse((0, import_node_fs52.readFileSync)(path2, "utf8"));
|
|
25630
26006
|
if (raw?.version !== VERSION || !Array.isArray(raw.items)) return [];
|
|
25631
26007
|
return raw.items.filter((item) => item?.id && item.kind && item.ref);
|
|
25632
26008
|
} catch {
|
|
@@ -25634,8 +26010,8 @@ function readDisk() {
|
|
|
25634
26010
|
}
|
|
25635
26011
|
}
|
|
25636
26012
|
function writeDisk(items) {
|
|
25637
|
-
(0,
|
|
25638
|
-
(0,
|
|
26013
|
+
(0, import_node_fs52.mkdirSync)(appDataDir(), { recursive: true });
|
|
26014
|
+
(0, import_node_fs52.writeFileSync)(pinsFile(), JSON.stringify({ version: VERSION, items }, null, 2), "utf8");
|
|
25639
26015
|
}
|
|
25640
26016
|
function listBoardPins() {
|
|
25641
26017
|
return readDisk();
|
|
@@ -25687,7 +26063,7 @@ function replaceBoardPins(items) {
|
|
|
25687
26063
|
}
|
|
25688
26064
|
function clearBoardPins() {
|
|
25689
26065
|
try {
|
|
25690
|
-
(0,
|
|
26066
|
+
(0, import_node_fs52.unlinkSync)(pinsFile());
|
|
25691
26067
|
} catch {
|
|
25692
26068
|
}
|
|
25693
26069
|
}
|
|
@@ -25704,7 +26080,7 @@ function homeBoardWorkspaceKey(workspaces) {
|
|
|
25704
26080
|
return workspaces.map((w) => w.path).filter(Boolean).sort().join("\n");
|
|
25705
26081
|
}
|
|
25706
26082
|
function cacheFile() {
|
|
25707
|
-
return (0,
|
|
26083
|
+
return (0, import_node_path49.join)(appDataDir(), "home-board-cache.json");
|
|
25708
26084
|
}
|
|
25709
26085
|
function emptyInputs() {
|
|
25710
26086
|
return {
|
|
@@ -25730,9 +26106,9 @@ function shouldCacheHomeBoardInputs(inputs) {
|
|
|
25730
26106
|
}
|
|
25731
26107
|
function readDiskCache() {
|
|
25732
26108
|
const path2 = cacheFile();
|
|
25733
|
-
if (!(0,
|
|
26109
|
+
if (!(0, import_node_fs53.existsSync)(path2)) return null;
|
|
25734
26110
|
try {
|
|
25735
|
-
const raw = JSON.parse((0,
|
|
26111
|
+
const raw = JSON.parse((0, import_node_fs53.readFileSync)(path2, "utf8"));
|
|
25736
26112
|
if (raw?.version !== CACHE_VERSION || typeof raw.fetchedAt !== "number") {
|
|
25737
26113
|
return null;
|
|
25738
26114
|
}
|
|
@@ -25746,14 +26122,14 @@ function readDiskCache() {
|
|
|
25746
26122
|
}
|
|
25747
26123
|
function writeDiskCache(entry) {
|
|
25748
26124
|
try {
|
|
25749
|
-
(0,
|
|
25750
|
-
(0,
|
|
26125
|
+
(0, import_node_fs53.mkdirSync)(appDataDir(), { recursive: true });
|
|
26126
|
+
(0, import_node_fs53.writeFileSync)(cacheFile(), JSON.stringify(entry), "utf8");
|
|
25751
26127
|
} catch {
|
|
25752
26128
|
}
|
|
25753
26129
|
}
|
|
25754
26130
|
function deleteDiskCache() {
|
|
25755
26131
|
try {
|
|
25756
|
-
(0,
|
|
26132
|
+
(0, import_node_fs53.unlinkSync)(cacheFile());
|
|
25757
26133
|
} catch {
|
|
25758
26134
|
}
|
|
25759
26135
|
}
|
|
@@ -26023,13 +26399,14 @@ async function startMcpServer() {
|
|
|
26023
26399
|
version: "0.1.0"
|
|
26024
26400
|
});
|
|
26025
26401
|
const worktreeProfile = sideboardMcpProfile() === "worktree";
|
|
26402
|
+
registerViewerContextTools(server);
|
|
26026
26403
|
if (worktreeProfile) {
|
|
26027
26404
|
registerConnectedIssueVendorTools(server);
|
|
26028
26405
|
}
|
|
26029
26406
|
if (!worktreeProfile) {
|
|
26030
26407
|
server.tool(
|
|
26031
26408
|
"list_workspaces",
|
|
26032
|
-
"List registered Sideboard workspaces (repos). Each line is name, path, github:owner/repo when resolvable, and
|
|
26409
|
+
"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.",
|
|
26033
26410
|
{},
|
|
26034
26411
|
async () => {
|
|
26035
26412
|
const workspaces = orch.listWorkspaces();
|
|
@@ -26060,7 +26437,7 @@ async function startMcpServer() {
|
|
|
26060
26437
|
async () => {
|
|
26061
26438
|
const threads = orch.getThreads(true);
|
|
26062
26439
|
const lines = threads.map((t) => {
|
|
26063
|
-
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0,
|
|
26440
|
+
const repo = t.repoPath === GLOBAL_WORKSPACE_ID ? "Orchestration" : (0, import_node_path50.basename)(t.repoPath) || t.repoPath;
|
|
26064
26441
|
const live = orch.threadLooksLive(t) ? readTurnLive(t.id) : null;
|
|
26065
26442
|
const parent = t.parentThreadId ? ` parent:${t.parentThreadId.slice(0, 8)}` : "";
|
|
26066
26443
|
const preview = lastMessagePreview(t.messages, 80);
|
|
@@ -26078,13 +26455,13 @@ async function startMcpServer() {
|
|
|
26078
26455
|
"list_board",
|
|
26079
26456
|
"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.",
|
|
26080
26457
|
{
|
|
26081
|
-
query:
|
|
26082
|
-
repoPath:
|
|
26083
|
-
kind:
|
|
26084
|
-
column:
|
|
26458
|
+
query: import_zod7.z.string().optional().describe("Case-insensitive token search across title, id, labels, repo"),
|
|
26459
|
+
repoPath: import_zod7.z.string().optional().describe("Limit to one workspace path from list_workspaces"),
|
|
26460
|
+
kind: import_zod7.z.enum(["all", "tickets", "prs", "branches", "threads"]).optional().describe("Filter by worktree source (default all)"),
|
|
26461
|
+
column: import_zod7.z.enum(["new", "draft", "review", "done", "needs_you"]).optional().describe(
|
|
26085
26462
|
"Return cards for this column only (totals still include the rest). needs_you is a legacy alias for new."
|
|
26086
26463
|
),
|
|
26087
|
-
limit:
|
|
26464
|
+
limit: import_zod7.z.number().int().positive().optional().describe("Max cards per column (default 40). hidden counts the remainder.")
|
|
26088
26465
|
},
|
|
26089
26466
|
async ({ query, repoPath, kind, column, limit }) => {
|
|
26090
26467
|
const workspaces = orch.listWorkspaces();
|
|
@@ -26122,7 +26499,7 @@ async function startMcpServer() {
|
|
|
26122
26499
|
server.tool(
|
|
26123
26500
|
"get_thread",
|
|
26124
26501
|
"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.",
|
|
26125
|
-
{ ref:
|
|
26502
|
+
{ ref: import_zod7.z.string() },
|
|
26126
26503
|
async ({ ref }) => {
|
|
26127
26504
|
const t = orch.getThread(ref);
|
|
26128
26505
|
if (!t) {
|
|
@@ -26164,17 +26541,17 @@ async function startMcpServer() {
|
|
|
26164
26541
|
"present_artifact",
|
|
26165
26542
|
"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.",
|
|
26166
26543
|
{
|
|
26167
|
-
title:
|
|
26168
|
-
type:
|
|
26544
|
+
title: import_zod7.z.string().describe("Short title shown in the artifact pane header"),
|
|
26545
|
+
type: import_zod7.z.enum(["html", "svg", "markdown", "react", "log"]).describe(
|
|
26169
26546
|
"html/svg/markdown/react replace the pane. log appends content to the same artifact_id (new lines only)."
|
|
26170
26547
|
),
|
|
26171
|
-
content:
|
|
26548
|
+
content: import_zod7.z.string().describe(
|
|
26172
26549
|
"html/svg/markdown/react: full document. log: only the new lines since the last call (empty is ok for a status-only update)."
|
|
26173
26550
|
),
|
|
26174
|
-
artifact_id:
|
|
26175
|
-
status:
|
|
26176
|
-
phase:
|
|
26177
|
-
mode:
|
|
26551
|
+
artifact_id: import_zod7.z.string().optional().describe("Stable id. Required for type=log so later calls append to the same pane."),
|
|
26552
|
+
status: import_zod7.z.enum(["running", "ok", "failed", "idle"]).optional().describe("Log header pill: running (working), ok (done), failed, idle"),
|
|
26553
|
+
phase: import_zod7.z.string().optional().describe("Log subtitle (Signing, Notarizing, \u2026)"),
|
|
26554
|
+
mode: import_zod7.z.enum(["append", "replace"]).optional().describe("log only: append (default) or replace the buffer")
|
|
26178
26555
|
},
|
|
26179
26556
|
async ({ title, type, artifact_id, status, phase, mode }) => {
|
|
26180
26557
|
const id = artifact_id?.trim() || `artifact_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -26195,15 +26572,15 @@ async function startMcpServer() {
|
|
|
26195
26572
|
"ask_user",
|
|
26196
26573
|
"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.",
|
|
26197
26574
|
{
|
|
26198
|
-
questions:
|
|
26199
|
-
|
|
26200
|
-
question:
|
|
26201
|
-
header:
|
|
26202
|
-
multiSelect:
|
|
26203
|
-
options:
|
|
26204
|
-
|
|
26205
|
-
label:
|
|
26206
|
-
description:
|
|
26575
|
+
questions: import_zod7.z.array(
|
|
26576
|
+
import_zod7.z.object({
|
|
26577
|
+
question: import_zod7.z.string().describe("Full question text ending with ?"),
|
|
26578
|
+
header: import_zod7.z.string().max(24).optional().describe("Short label shown above the question"),
|
|
26579
|
+
multiSelect: import_zod7.z.boolean().optional().describe("Allow selecting multiple options"),
|
|
26580
|
+
options: import_zod7.z.array(
|
|
26581
|
+
import_zod7.z.object({
|
|
26582
|
+
label: import_zod7.z.string(),
|
|
26583
|
+
description: import_zod7.z.string().optional().describe("What this option means / when to choose it (strongly preferred)")
|
|
26207
26584
|
})
|
|
26208
26585
|
).min(2).max(6).describe("2\u20136 choices (Sideboard also offers Other)")
|
|
26209
26586
|
})
|
|
@@ -26222,9 +26599,9 @@ async function startMcpServer() {
|
|
|
26222
26599
|
"present_plan",
|
|
26223
26600
|
"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.",
|
|
26224
26601
|
{
|
|
26225
|
-
title:
|
|
26226
|
-
content:
|
|
26227
|
-
thread_id:
|
|
26602
|
+
title: import_zod7.z.string().optional().describe("Short plan title (defaults to Plan)"),
|
|
26603
|
+
content: import_zod7.z.string().min(1).describe("Full plan markdown (headings, steps, risks, open questions)"),
|
|
26604
|
+
thread_id: import_zod7.z.string().optional().describe("Sideboard thread id when cwd is not the worktree")
|
|
26228
26605
|
},
|
|
26229
26606
|
async ({ title, content, thread_id }) => {
|
|
26230
26607
|
const { writePlanFile: writePlanFile2 } = await Promise.resolve().then(() => (init_plan_file(), plan_file_exports));
|
|
@@ -26247,15 +26624,15 @@ async function startMcpServer() {
|
|
|
26247
26624
|
"present_schema",
|
|
26248
26625
|
"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.",
|
|
26249
26626
|
{
|
|
26250
|
-
title:
|
|
26251
|
-
mode:
|
|
26252
|
-
datasource:
|
|
26253
|
-
resource_id:
|
|
26254
|
-
record_id:
|
|
26255
|
-
resource:
|
|
26256
|
-
record:
|
|
26257
|
-
records:
|
|
26258
|
-
pane_id:
|
|
26627
|
+
title: import_zod7.z.string().describe("Pane title"),
|
|
26628
|
+
mode: import_zod7.z.enum(["table", "form"]).optional().describe("table = list/filter records; form = edit one record"),
|
|
26629
|
+
datasource: import_zod7.z.enum(["brightsy", "inline"]).optional().describe("brightsy resolves via login; inline uses embedded resource/records"),
|
|
26630
|
+
resource_id: import_zod7.z.string().optional().describe("Brightsy record type UUID (or generic resource id)"),
|
|
26631
|
+
record_id: import_zod7.z.string().optional().describe("Record id when opening form mode"),
|
|
26632
|
+
resource: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline { id, title, schema, schemaUi?, slug? }"),
|
|
26633
|
+
record: import_zod7.z.record(import_zod7.z.unknown()).optional().describe("Inline record { id, data, published_at? }"),
|
|
26634
|
+
records: import_zod7.z.array(import_zod7.z.record(import_zod7.z.unknown())).optional().describe("Inline records for table mode"),
|
|
26635
|
+
pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
|
|
26259
26636
|
},
|
|
26260
26637
|
async (args) => {
|
|
26261
26638
|
const id = args.pane_id?.trim() || `schema_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -26276,10 +26653,10 @@ async function startMcpServer() {
|
|
|
26276
26653
|
"present_files",
|
|
26277
26654
|
"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.",
|
|
26278
26655
|
{
|
|
26279
|
-
title:
|
|
26280
|
-
datasource:
|
|
26281
|
-
path:
|
|
26282
|
-
pane_id:
|
|
26656
|
+
title: import_zod7.z.string().optional().describe("Pane title (default: Files)"),
|
|
26657
|
+
datasource: import_zod7.z.enum(["brightsy", "memory"]).optional().describe("brightsy = account storage via login; memory = session demo store"),
|
|
26658
|
+
path: import_zod7.z.string().optional().describe("Initial folder path (e.g. public)"),
|
|
26659
|
+
pane_id: import_zod7.z.string().optional().describe("Stable pane id across updates")
|
|
26283
26660
|
},
|
|
26284
26661
|
async (args) => {
|
|
26285
26662
|
const id = args.pane_id?.trim() || `files_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -26294,6 +26671,20 @@ async function startMcpServer() {
|
|
|
26294
26671
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
26295
26672
|
}
|
|
26296
26673
|
);
|
|
26674
|
+
server.tool(
|
|
26675
|
+
"wait_for_job",
|
|
26676
|
+
"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.",
|
|
26677
|
+
{
|
|
26678
|
+
id: import_zod7.z.string().describe("Detached job id (same kebab-case id passed to detached-job.js start)"),
|
|
26679
|
+
timeoutMs: import_zod7.z.number().optional()
|
|
26680
|
+
},
|
|
26681
|
+
async ({ id, timeoutMs }) => {
|
|
26682
|
+
const result = await waitForDetachedJob(process.cwd(), id, {
|
|
26683
|
+
timeoutMs: mcpWaitForJobTimeoutMs(timeoutMs)
|
|
26684
|
+
});
|
|
26685
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
26686
|
+
}
|
|
26687
|
+
);
|
|
26297
26688
|
if (!worktreeProfile) {
|
|
26298
26689
|
registerSlackTools(server);
|
|
26299
26690
|
registerConnectedIssueVendorTools(server);
|
|
@@ -26306,7 +26697,7 @@ async function startMcpServer() {
|
|
|
26306
26697
|
"set_caffeinate",
|
|
26307
26698
|
"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.",
|
|
26308
26699
|
{
|
|
26309
|
-
enabled:
|
|
26700
|
+
enabled: import_zod7.z.boolean().describe("true = hold caffeinate on; false = release and let the Mac sleep")
|
|
26310
26701
|
},
|
|
26311
26702
|
async ({ enabled }) => {
|
|
26312
26703
|
const threadId = process.env.SIDEBOARD_ORCHESTRATOR_THREAD_ID?.trim() || null;
|
|
@@ -26344,18 +26735,18 @@ async function startMcpServer() {
|
|
|
26344
26735
|
"create_thread",
|
|
26345
26736
|
`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.`,
|
|
26346
26737
|
{
|
|
26347
|
-
sourceType:
|
|
26348
|
-
sourceRef:
|
|
26349
|
-
agent:
|
|
26350
|
-
model:
|
|
26738
|
+
sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]),
|
|
26739
|
+
sourceRef: import_zod7.z.string(),
|
|
26740
|
+
agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]).optional().describe(`Omit to use Account default agent (${accountDefaults.agent})`),
|
|
26741
|
+
model: import_zod7.z.string().nullable().optional().describe(
|
|
26351
26742
|
`Usually omit to use Account default model (${accountDefaults.model?.trim() || "Auto"}). Pass null only to force Auto / agent-default.`
|
|
26352
26743
|
),
|
|
26353
|
-
repoPath:
|
|
26354
|
-
title:
|
|
26355
|
-
cowboy:
|
|
26744
|
+
repoPath: import_zod7.z.string(),
|
|
26745
|
+
title: import_zod7.z.string().optional(),
|
|
26746
|
+
cowboy: import_zod7.z.boolean().optional().describe(
|
|
26356
26747
|
"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."
|
|
26357
26748
|
),
|
|
26358
|
-
parentThreadId:
|
|
26749
|
+
parentThreadId: import_zod7.z.string().optional().describe(
|
|
26359
26750
|
"Orchestration: omit (preferred) or pass YOUR chat id from the turn reminder / AGENTS.md. Do not invent uuids."
|
|
26360
26751
|
)
|
|
26361
26752
|
},
|
|
@@ -26371,10 +26762,10 @@ async function startMcpServer() {
|
|
|
26371
26762
|
"start_board_card",
|
|
26372
26763
|
"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.",
|
|
26373
26764
|
{
|
|
26374
|
-
kind:
|
|
26375
|
-
ref:
|
|
26376
|
-
repoPath:
|
|
26377
|
-
title:
|
|
26765
|
+
kind: import_zod7.z.enum(["ticket", "pr", "branch"]),
|
|
26766
|
+
ref: import_zod7.z.string().describe("Ticket identifier (ENG-12), PR number (44), or branch name from list_board"),
|
|
26767
|
+
repoPath: import_zod7.z.string().describe("Workspace path from list_workspaces / list_board"),
|
|
26768
|
+
title: import_zod7.z.string().optional()
|
|
26378
26769
|
},
|
|
26379
26770
|
async ({ kind, ref, repoPath, title }) => {
|
|
26380
26771
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -26478,9 +26869,9 @@ async function startMcpServer() {
|
|
|
26478
26869
|
"send_to_thread",
|
|
26479
26870
|
'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.',
|
|
26480
26871
|
{
|
|
26481
|
-
ref:
|
|
26482
|
-
prompt:
|
|
26483
|
-
force_stop:
|
|
26872
|
+
ref: import_zod7.z.string(),
|
|
26873
|
+
prompt: import_zod7.z.string(),
|
|
26874
|
+
force_stop: import_zod7.z.boolean().optional()
|
|
26484
26875
|
},
|
|
26485
26876
|
async ({ ref, prompt, force_stop }) => {
|
|
26486
26877
|
if (force_stop) {
|
|
@@ -26509,8 +26900,8 @@ async function startMcpServer() {
|
|
|
26509
26900
|
"wait_for_turn",
|
|
26510
26901
|
"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).",
|
|
26511
26902
|
{
|
|
26512
|
-
ref:
|
|
26513
|
-
timeoutMs:
|
|
26903
|
+
ref: import_zod7.z.string(),
|
|
26904
|
+
timeoutMs: import_zod7.z.number().optional()
|
|
26514
26905
|
},
|
|
26515
26906
|
async ({ ref, timeoutMs }) => {
|
|
26516
26907
|
const thread = await orch.waitForTurn(ref, mcpWaitForTurnTimeoutMs(timeoutMs), {
|
|
@@ -26540,7 +26931,7 @@ async function startMcpServer() {
|
|
|
26540
26931
|
server.tool(
|
|
26541
26932
|
"get_turn_result",
|
|
26542
26933
|
"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).",
|
|
26543
|
-
{ ref:
|
|
26934
|
+
{ ref: import_zod7.z.string() },
|
|
26544
26935
|
async ({ ref }) => {
|
|
26545
26936
|
const result = orch.getTurnResult(ref);
|
|
26546
26937
|
return {
|
|
@@ -26561,8 +26952,8 @@ async function startMcpServer() {
|
|
|
26561
26952
|
"stop_thread",
|
|
26562
26953
|
"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.",
|
|
26563
26954
|
{
|
|
26564
|
-
ref:
|
|
26565
|
-
force:
|
|
26955
|
+
ref: import_zod7.z.string(),
|
|
26956
|
+
force: import_zod7.z.boolean().optional()
|
|
26566
26957
|
},
|
|
26567
26958
|
async ({ ref, force }) => {
|
|
26568
26959
|
const t = orch.getThread(ref);
|
|
@@ -26592,7 +26983,7 @@ async function startMcpServer() {
|
|
|
26592
26983
|
server.tool(
|
|
26593
26984
|
"archive_thread",
|
|
26594
26985
|
"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.",
|
|
26595
|
-
{ ref:
|
|
26986
|
+
{ ref: import_zod7.z.string() },
|
|
26596
26987
|
async ({ ref }) => {
|
|
26597
26988
|
const t = orch.getThread(ref);
|
|
26598
26989
|
if (!t) {
|
|
@@ -26625,7 +27016,7 @@ async function startMcpServer() {
|
|
|
26625
27016
|
server.tool(
|
|
26626
27017
|
"restore_thread",
|
|
26627
27018
|
"Restore an archived thread (recreates worktree from branch when needed)",
|
|
26628
|
-
{ ref:
|
|
27019
|
+
{ ref: import_zod7.z.string() },
|
|
26629
27020
|
async ({ ref }) => {
|
|
26630
27021
|
try {
|
|
26631
27022
|
const restored = await orch.restore(ref);
|
|
@@ -26651,8 +27042,8 @@ async function startMcpServer() {
|
|
|
26651
27042
|
"get_diff",
|
|
26652
27043
|
"Compact diff summary (capped hunks, paginated)",
|
|
26653
27044
|
{
|
|
26654
|
-
ref:
|
|
26655
|
-
maxFiles:
|
|
27045
|
+
ref: import_zod7.z.string(),
|
|
27046
|
+
maxFiles: import_zod7.z.number().optional()
|
|
26656
27047
|
},
|
|
26657
27048
|
async ({ ref, maxFiles }) => {
|
|
26658
27049
|
const summary = await orch.diffSummary(ref);
|
|
@@ -26672,7 +27063,7 @@ async function startMcpServer() {
|
|
|
26672
27063
|
server.tool(
|
|
26673
27064
|
"get_pr_checks",
|
|
26674
27065
|
"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.",
|
|
26675
|
-
{ ref:
|
|
27066
|
+
{ ref: import_zod7.z.string().describe("Worktree thread id/ref") },
|
|
26676
27067
|
async ({ ref }) => {
|
|
26677
27068
|
try {
|
|
26678
27069
|
const checks = await orch.getPrChecks(ref);
|
|
@@ -26688,7 +27079,7 @@ async function startMcpServer() {
|
|
|
26688
27079
|
server.tool(
|
|
26689
27080
|
"request_review",
|
|
26690
27081
|
'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.',
|
|
26691
|
-
{ ref:
|
|
27082
|
+
{ ref: import_zod7.z.string().describe("Worktree thread id/ref to review") },
|
|
26692
27083
|
async ({ ref }) => {
|
|
26693
27084
|
try {
|
|
26694
27085
|
const tab = await orch.requestReview(ref);
|
|
@@ -26717,8 +27108,8 @@ async function startMcpServer() {
|
|
|
26717
27108
|
"ask_git",
|
|
26718
27109
|
"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.",
|
|
26719
27110
|
{
|
|
26720
|
-
ref:
|
|
26721
|
-
action:
|
|
27111
|
+
ref: import_zod7.z.string().describe("Worktree thread id/ref"),
|
|
27112
|
+
action: import_zod7.z.enum(AGENT_GIT_ACTIONS).describe(
|
|
26722
27113
|
"commit-push | create-draft | create-web | resolve-conflicts | merge"
|
|
26723
27114
|
)
|
|
26724
27115
|
},
|
|
@@ -26763,7 +27154,7 @@ async function startMcpServer() {
|
|
|
26763
27154
|
}
|
|
26764
27155
|
}
|
|
26765
27156
|
);
|
|
26766
|
-
const agentEnum =
|
|
27157
|
+
const agentEnum = import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]);
|
|
26767
27158
|
server.tool(
|
|
26768
27159
|
"list_models",
|
|
26769
27160
|
"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.",
|
|
@@ -26786,11 +27177,11 @@ async function startMcpServer() {
|
|
|
26786
27177
|
"fork_worktree",
|
|
26787
27178
|
"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.",
|
|
26788
27179
|
{
|
|
26789
|
-
ref:
|
|
26790
|
-
through_index:
|
|
27180
|
+
ref: import_zod7.z.string().describe("Worktree thread id/ref to fork"),
|
|
27181
|
+
through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
26791
27182
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
26792
|
-
model:
|
|
26793
|
-
title:
|
|
27183
|
+
model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
27184
|
+
title: import_zod7.z.string().optional()
|
|
26794
27185
|
},
|
|
26795
27186
|
async ({ ref, through_index, agent, model, title }) => {
|
|
26796
27187
|
try {
|
|
@@ -26831,11 +27222,11 @@ async function startMcpServer() {
|
|
|
26831
27222
|
"fork_chat",
|
|
26832
27223
|
"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.",
|
|
26833
27224
|
{
|
|
26834
|
-
ref:
|
|
26835
|
-
through_index:
|
|
27225
|
+
ref: import_zod7.z.string().describe("Thread id/ref to fork (worktree agent or orchestration chat)"),
|
|
27226
|
+
through_index: import_zod7.z.number().optional().describe("Inclusive message index to include in the transcript (default: all)"),
|
|
26836
27227
|
agent: agentEnum.optional().describe("Agent for the forked chat (default: same as source)"),
|
|
26837
|
-
model:
|
|
26838
|
-
title:
|
|
27228
|
+
model: import_zod7.z.string().nullable().optional().describe("Usually omit (Auto). Only set from list_models when you need a specific model"),
|
|
27229
|
+
title: import_zod7.z.string().optional()
|
|
26839
27230
|
},
|
|
26840
27231
|
async ({ ref, through_index, agent, model, title }) => {
|
|
26841
27232
|
try {
|
|
@@ -26881,8 +27272,8 @@ async function startMcpServer() {
|
|
|
26881
27272
|
"run_dev_script",
|
|
26882
27273
|
"Start a .sideboard/.conductor run script for a thread (default script if name omitted); returns port",
|
|
26883
27274
|
{
|
|
26884
|
-
ref:
|
|
26885
|
-
name:
|
|
27275
|
+
ref: import_zod7.z.string(),
|
|
27276
|
+
name: import_zod7.z.string().optional()
|
|
26886
27277
|
},
|
|
26887
27278
|
async ({ ref, name }) => {
|
|
26888
27279
|
const result = await orch.startDev(ref, name);
|
|
@@ -26904,7 +27295,7 @@ async function startMcpServer() {
|
|
|
26904
27295
|
server.tool(
|
|
26905
27296
|
"list_run_scripts",
|
|
26906
27297
|
"List named run scripts available for a thread",
|
|
26907
|
-
{ ref:
|
|
27298
|
+
{ ref: import_zod7.z.string() },
|
|
26908
27299
|
async ({ ref }) => {
|
|
26909
27300
|
const scripts = orch.listThreadRunScripts(ref);
|
|
26910
27301
|
const active = orch.getActiveRuns(ref);
|
|
@@ -26922,8 +27313,8 @@ async function startMcpServer() {
|
|
|
26922
27313
|
"stop_dev_script",
|
|
26923
27314
|
"Stop a running script for a thread (all scripts if name omitted)",
|
|
26924
27315
|
{
|
|
26925
|
-
ref:
|
|
26926
|
-
name:
|
|
27316
|
+
ref: import_zod7.z.string(),
|
|
27317
|
+
name: import_zod7.z.string().optional()
|
|
26927
27318
|
},
|
|
26928
27319
|
async ({ ref, name }) => {
|
|
26929
27320
|
orch.stopDev(ref, name);
|
|
@@ -26933,7 +27324,7 @@ async function startMcpServer() {
|
|
|
26933
27324
|
server.tool(
|
|
26934
27325
|
"run_setup",
|
|
26935
27326
|
"Re-run workspace setup (Sideboard/Conductor settings, .cursor/worktrees.json, or script/setup). New worktrees already run this automatically.",
|
|
26936
|
-
{ ref:
|
|
27327
|
+
{ ref: import_zod7.z.string() },
|
|
26937
27328
|
async ({ ref }) => {
|
|
26938
27329
|
const result = await orch.runSetup(ref);
|
|
26939
27330
|
return {
|
|
@@ -26944,7 +27335,7 @@ async function startMcpServer() {
|
|
|
26944
27335
|
server.tool(
|
|
26945
27336
|
"add_workspace",
|
|
26946
27337
|
"Register a git repo as a Sideboard workspace",
|
|
26947
|
-
{ repoPath:
|
|
27338
|
+
{ repoPath: import_zod7.z.string() },
|
|
26948
27339
|
async ({ repoPath }) => {
|
|
26949
27340
|
const ws = await orch.addWorkspace(repoPath);
|
|
26950
27341
|
return { content: [{ type: "text", text: JSON.stringify(ws) }] };
|
|
@@ -26953,7 +27344,7 @@ async function startMcpServer() {
|
|
|
26953
27344
|
server.tool(
|
|
26954
27345
|
"remove_workspace",
|
|
26955
27346
|
"Unregister a Sideboard workspace (does not archive threads)",
|
|
26956
|
-
{ repoPath:
|
|
27347
|
+
{ repoPath: import_zod7.z.string() },
|
|
26957
27348
|
async ({ repoPath }) => {
|
|
26958
27349
|
orch.removeWorkspace(repoPath);
|
|
26959
27350
|
return { content: [{ type: "text", text: "ok" }] };
|
|
@@ -26963,12 +27354,12 @@ async function startMcpServer() {
|
|
|
26963
27354
|
"fanout",
|
|
26964
27355
|
"Best-of-n: create one thread per agent with the same prompt (parallel attempts)",
|
|
26965
27356
|
{
|
|
26966
|
-
prompt:
|
|
26967
|
-
agents:
|
|
26968
|
-
repoPath:
|
|
26969
|
-
sourceType:
|
|
26970
|
-
sourceRef:
|
|
26971
|
-
title:
|
|
27357
|
+
prompt: import_zod7.z.string(),
|
|
27358
|
+
agents: import_zod7.z.array(import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"])),
|
|
27359
|
+
repoPath: import_zod7.z.string(),
|
|
27360
|
+
sourceType: import_zod7.z.enum(["branch", "pr", "ticket"]).optional(),
|
|
27361
|
+
sourceRef: import_zod7.z.string().optional(),
|
|
27362
|
+
title: import_zod7.z.string().optional()
|
|
26972
27363
|
},
|
|
26973
27364
|
async (args) => {
|
|
26974
27365
|
const threads = await orch.bestOfN(args);
|
|
@@ -26995,8 +27386,8 @@ async function startMcpServer() {
|
|
|
26995
27386
|
"list_branches",
|
|
26996
27387
|
"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).",
|
|
26997
27388
|
{
|
|
26998
|
-
repoPath:
|
|
26999
|
-
unmergedOnly:
|
|
27389
|
+
repoPath: import_zod7.z.string(),
|
|
27390
|
+
unmergedOnly: import_zod7.z.boolean().optional()
|
|
27000
27391
|
},
|
|
27001
27392
|
async ({ repoPath, unmergedOnly }) => {
|
|
27002
27393
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -27017,19 +27408,19 @@ async function startMcpServer() {
|
|
|
27017
27408
|
"list_prs",
|
|
27018
27409
|
'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.',
|
|
27019
27410
|
{
|
|
27020
|
-
repoPath:
|
|
27021
|
-
query:
|
|
27022
|
-
queue:
|
|
27411
|
+
repoPath: import_zod7.z.string(),
|
|
27412
|
+
query: import_zod7.z.string().optional().describe("GitHub search tokens (title, draft:true, \u2026)"),
|
|
27413
|
+
queue: import_zod7.z.enum(["review", "mine", "approved", "changes"]).optional().describe(
|
|
27023
27414
|
'review = unclaimed eng-review inbox (use for "get me N tickets to review"). mine = review-requested:@me. approved / changes = eng-approved / eng-requested-changes.'
|
|
27024
27415
|
),
|
|
27025
|
-
state:
|
|
27026
|
-
label:
|
|
27416
|
+
state: import_zod7.z.enum(["open", "closed", "merged", "all", "review"]).optional().describe("GitHub PR state (default open). review is an alias for queue=review."),
|
|
27417
|
+
label: import_zod7.z.string().optional().describe(
|
|
27027
27418
|
"GitHub label / workflow tag. Comma-separated AND. Examples: eng-review, eng-approved, eng-requested-changes"
|
|
27028
27419
|
),
|
|
27029
|
-
reviewer:
|
|
27420
|
+
reviewer: import_zod7.z.string().optional().describe(
|
|
27030
27421
|
"me (review requested of you), unassigned (no individual reviewer; team queues like engineering-team still count), all, or a GitHub login"
|
|
27031
27422
|
),
|
|
27032
|
-
limit:
|
|
27423
|
+
limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
27033
27424
|
},
|
|
27034
27425
|
async ({ repoPath, query, queue, state, label, reviewer, limit }) => {
|
|
27035
27426
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -27061,7 +27452,7 @@ async function startMcpServer() {
|
|
|
27061
27452
|
server.tool(
|
|
27062
27453
|
"get_pr_stack",
|
|
27063
27454
|
"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).",
|
|
27064
|
-
{ ref:
|
|
27455
|
+
{ ref: import_zod7.z.string() },
|
|
27065
27456
|
async ({ ref }) => {
|
|
27066
27457
|
const stack = await orch.getPrStack(ref);
|
|
27067
27458
|
return {
|
|
@@ -27073,8 +27464,8 @@ async function startMcpServer() {
|
|
|
27073
27464
|
"open_pr_stack_layers",
|
|
27074
27465
|
"Materialize one worktree+thread per stack layer (or a single 1-based layer). Pass a thread ref already on the stack.",
|
|
27075
27466
|
{
|
|
27076
|
-
ref:
|
|
27077
|
-
layer:
|
|
27467
|
+
ref: import_zod7.z.string(),
|
|
27468
|
+
layer: import_zod7.z.number().int().positive().optional()
|
|
27078
27469
|
},
|
|
27079
27470
|
async ({ ref, layer }) => {
|
|
27080
27471
|
const result = await orch.openPrStackLayers(ref, { layer });
|
|
@@ -27108,9 +27499,9 @@ async function startMcpServer() {
|
|
|
27108
27499
|
"add_stack_layer",
|
|
27109
27500
|
"Add a branch on top of the current stack (`gh stack add`) and open a worktree+thread for it.",
|
|
27110
27501
|
{
|
|
27111
|
-
ref:
|
|
27112
|
-
branchName:
|
|
27113
|
-
title:
|
|
27502
|
+
ref: import_zod7.z.string(),
|
|
27503
|
+
branchName: import_zod7.z.string(),
|
|
27504
|
+
title: import_zod7.z.string().optional()
|
|
27114
27505
|
},
|
|
27115
27506
|
async ({ ref, branchName, title }) => {
|
|
27116
27507
|
const result = await orch.addStackLayer(ref, branchName, { title });
|
|
@@ -27139,11 +27530,11 @@ async function startMcpServer() {
|
|
|
27139
27530
|
"create_pr_stack",
|
|
27140
27531
|
"Create a new GitHub PR stack with one Sideboard worktree per layer (bottom\u2192top branch names). Requires `gh extension install github/gh-stack`.",
|
|
27141
27532
|
{
|
|
27142
|
-
repoPath:
|
|
27143
|
-
branches:
|
|
27144
|
-
agent:
|
|
27145
|
-
base:
|
|
27146
|
-
title:
|
|
27533
|
+
repoPath: import_zod7.z.string(),
|
|
27534
|
+
branches: import_zod7.z.array(import_zod7.z.string()).min(1),
|
|
27535
|
+
agent: import_zod7.z.enum(["claude", "codex", "opencode", "brightsy", "cursor"]),
|
|
27536
|
+
base: import_zod7.z.string().optional(),
|
|
27537
|
+
title: import_zod7.z.string().optional()
|
|
27147
27538
|
},
|
|
27148
27539
|
async (args) => {
|
|
27149
27540
|
const result = await orch.createPrStack({
|
|
@@ -27182,10 +27573,10 @@ async function startMcpServer() {
|
|
|
27182
27573
|
"list_issues",
|
|
27183
27574
|
"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.",
|
|
27184
27575
|
{
|
|
27185
|
-
repoPath:
|
|
27186
|
-
query:
|
|
27187
|
-
assignee:
|
|
27188
|
-
limit:
|
|
27576
|
+
repoPath: import_zod7.z.string(),
|
|
27577
|
+
query: import_zod7.z.string().optional().describe("Search title, identifier, or description"),
|
|
27578
|
+
assignee: import_zod7.z.string().optional().describe("me (Linear default), unassigned, all, a user id, or a GitHub login"),
|
|
27579
|
+
limit: import_zod7.z.number().int().positive().max(250).optional().describe("Page size (default 40, max 250). Raise when truncated is true.")
|
|
27189
27580
|
},
|
|
27190
27581
|
async ({ repoPath, query, assignee, limit }) => {
|
|
27191
27582
|
const root = await resolveRepoRoot(repoPath);
|
|
@@ -27325,8 +27716,8 @@ var BrightsySideboardApi = class {
|
|
|
27325
27716
|
};
|
|
27326
27717
|
function taskMessageText(task) {
|
|
27327
27718
|
const parts = task.message?.parts ?? [];
|
|
27328
|
-
const
|
|
27329
|
-
return
|
|
27719
|
+
const text7 = parts.find((p) => p.kind === "text")?.text ?? "";
|
|
27720
|
+
return text7.trim();
|
|
27330
27721
|
}
|
|
27331
27722
|
|
|
27332
27723
|
// src/brightsy/cloud-connect.ts
|
|
@@ -27828,11 +28219,11 @@ var import_ws = require("ws");
|
|
|
27828
28219
|
init_api();
|
|
27829
28220
|
var BACKOFF_START_MS = 1e3;
|
|
27830
28221
|
var BACKOFF_MAX_MS = 3e4;
|
|
27831
|
-
function stripSlackMentions(
|
|
27832
|
-
return
|
|
28222
|
+
function stripSlackMentions(text7) {
|
|
28223
|
+
return text7.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
|
|
27833
28224
|
}
|
|
27834
|
-
function isSlackStopCommand(
|
|
27835
|
-
const t = stripSlackMentions(
|
|
28225
|
+
function isSlackStopCommand(text7) {
|
|
28226
|
+
const t = stripSlackMentions(text7).toLowerCase();
|
|
27836
28227
|
return t === "stop" || t === "sideboard_force_stop";
|
|
27837
28228
|
}
|
|
27838
28229
|
function parseSlackSocketFrame(raw) {
|
|
@@ -27854,8 +28245,8 @@ function inboundFromSocketFrame(frame) {
|
|
|
27854
28245
|
const ts = event.ts?.trim();
|
|
27855
28246
|
if (!channelId || !ts) return null;
|
|
27856
28247
|
const rawText = event.text?.trim() ?? "";
|
|
27857
|
-
const
|
|
27858
|
-
if (!
|
|
28248
|
+
const text7 = stripSlackMentions(rawText);
|
|
28249
|
+
if (!text7) return null;
|
|
27859
28250
|
const teamId = (frame.payload?.team_id || event.team || "").trim();
|
|
27860
28251
|
if (!teamId) return null;
|
|
27861
28252
|
const isDm = event.type === "message" && (event.channel_type === "im" || event.channel_type === "mpim" || !event.channel_type && channelId.startsWith("D"));
|
|
@@ -27867,7 +28258,7 @@ function inboundFromSocketFrame(frame) {
|
|
|
27867
28258
|
ts,
|
|
27868
28259
|
threadTs: event.thread_ts?.trim() || void 0,
|
|
27869
28260
|
userId: event.user?.trim(),
|
|
27870
|
-
text:
|
|
28261
|
+
text: text7,
|
|
27871
28262
|
kind: isMention ? "mention" : "dm"
|
|
27872
28263
|
};
|
|
27873
28264
|
}
|
|
@@ -28411,12 +28802,12 @@ function formatSlackInboundPrompt(msg) {
|
|
|
28411
28802
|
|
|
28412
28803
|
${msg.text}`;
|
|
28413
28804
|
}
|
|
28414
|
-
function isSlackInboundUserPrompt(
|
|
28415
|
-
return
|
|
28805
|
+
function isSlackInboundUserPrompt(text7) {
|
|
28806
|
+
return text7.startsWith("Slack DM\n") || text7.startsWith("Slack @mention\n");
|
|
28416
28807
|
}
|
|
28417
|
-
function formatSlackSignedReply(deviceLabel,
|
|
28808
|
+
function formatSlackSignedReply(deviceLabel, text7) {
|
|
28418
28809
|
const label = deviceLabel.trim();
|
|
28419
|
-
const body =
|
|
28810
|
+
const body = text7.trim();
|
|
28420
28811
|
if (!body) return body;
|
|
28421
28812
|
if (!label) return body;
|
|
28422
28813
|
const head = `${label}:`;
|
|
@@ -28425,8 +28816,8 @@ function formatSlackSignedReply(deviceLabel, text6) {
|
|
|
28425
28816
|
}
|
|
28426
28817
|
return `${label}: ${body}`;
|
|
28427
28818
|
}
|
|
28428
|
-
function signForThisMac(
|
|
28429
|
-
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel,
|
|
28819
|
+
function signForThisMac(text7) {
|
|
28820
|
+
return formatSlackSignedReply(ensureSlackDeviceIdentity().deviceLabel, text7);
|
|
28430
28821
|
}
|
|
28431
28822
|
function slackReplyThreadTs(msg) {
|
|
28432
28823
|
if (msg.threadTs && msg.threadTs !== msg.ts) return msg.threadTs;
|
|
@@ -28487,9 +28878,9 @@ function replyStub(target) {
|
|
|
28487
28878
|
kind: "dm"
|
|
28488
28879
|
};
|
|
28489
28880
|
}
|
|
28490
|
-
async function postSlackText(target,
|
|
28881
|
+
async function postSlackText(target, text7, opts) {
|
|
28491
28882
|
if (opts.postReply) {
|
|
28492
|
-
const result = await opts.postReply(replyStub(target),
|
|
28883
|
+
const result = await opts.postReply(replyStub(target), text7);
|
|
28493
28884
|
const ts2 = result && typeof result === "object" && typeof result.ts === "string" ? result.ts.trim() : "";
|
|
28494
28885
|
return ts2 ? { ts: ts2 } : null;
|
|
28495
28886
|
}
|
|
@@ -28499,7 +28890,7 @@ async function postSlackText(target, text6, opts) {
|
|
|
28499
28890
|
"chat.postMessage",
|
|
28500
28891
|
{
|
|
28501
28892
|
channel: target.channelId,
|
|
28502
|
-
text:
|
|
28893
|
+
text: text7,
|
|
28503
28894
|
thread_ts: target.threadTs
|
|
28504
28895
|
},
|
|
28505
28896
|
opts.fetchImpl
|
|
@@ -28507,9 +28898,9 @@ async function postSlackText(target, text6, opts) {
|
|
|
28507
28898
|
const ts = json.ts?.trim();
|
|
28508
28899
|
return ts ? { ts } : null;
|
|
28509
28900
|
}
|
|
28510
|
-
async function updateSlackText(target, ts,
|
|
28901
|
+
async function updateSlackText(target, ts, text7, opts) {
|
|
28511
28902
|
if (opts.updateReply) {
|
|
28512
|
-
await opts.updateReply(replyStub(target), ts,
|
|
28903
|
+
await opts.updateReply(replyStub(target), ts, text7);
|
|
28513
28904
|
return;
|
|
28514
28905
|
}
|
|
28515
28906
|
const token = writeTokenForTeam(target.teamId);
|
|
@@ -28519,7 +28910,7 @@ async function updateSlackText(target, ts, text6, opts) {
|
|
|
28519
28910
|
{
|
|
28520
28911
|
channel: target.channelId,
|
|
28521
28912
|
ts,
|
|
28522
|
-
text:
|
|
28913
|
+
text: text7
|
|
28523
28914
|
},
|
|
28524
28915
|
opts.fetchImpl
|
|
28525
28916
|
);
|
|
@@ -28547,8 +28938,8 @@ function replyTargetOf(msg) {
|
|
|
28547
28938
|
threadTs: slackReplyThreadTs(msg)
|
|
28548
28939
|
};
|
|
28549
28940
|
}
|
|
28550
|
-
async function postSlackReply(msg,
|
|
28551
|
-
await postSlackText(replyTargetOf(msg), signForThisMac(
|
|
28941
|
+
async function postSlackReply(msg, text7, opts) {
|
|
28942
|
+
await postSlackText(replyTargetOf(msg), signForThisMac(text7), opts);
|
|
28552
28943
|
}
|
|
28553
28944
|
function startSlackTurnProgress(msg, threadId, opts) {
|
|
28554
28945
|
const log = opts.onLog ?? (() => void 0);
|
|
@@ -28631,10 +29022,10 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
28631
29022
|
}
|
|
28632
29023
|
});
|
|
28633
29024
|
},
|
|
28634
|
-
finishWith: (
|
|
29025
|
+
finishWith: (text7) => {
|
|
28635
29026
|
stop();
|
|
28636
29027
|
return run2(async () => {
|
|
28637
|
-
const signed = signForThisMac(
|
|
29028
|
+
const signed = signForThisMac(text7.trim());
|
|
28638
29029
|
if (!signed) {
|
|
28639
29030
|
if (!postedTs) return;
|
|
28640
29031
|
const ts = postedTs;
|
|
@@ -28662,11 +29053,11 @@ function startSlackTurnProgress(msg, threadId, opts) {
|
|
|
28662
29053
|
};
|
|
28663
29054
|
}
|
|
28664
29055
|
var lastRelayed = /* @__PURE__ */ new Map();
|
|
28665
|
-
function markRelayed(threadId,
|
|
28666
|
-
lastRelayed.set(threadId, `${threadId}:${
|
|
29056
|
+
function markRelayed(threadId, text7) {
|
|
29057
|
+
lastRelayed.set(threadId, `${threadId}:${text7}`);
|
|
28667
29058
|
}
|
|
28668
|
-
function alreadyRelayed(threadId,
|
|
28669
|
-
return lastRelayed.get(threadId) === `${threadId}:${
|
|
29059
|
+
function alreadyRelayed(threadId, text7) {
|
|
29060
|
+
return lastRelayed.get(threadId) === `${threadId}:${text7}`;
|
|
28670
29061
|
}
|
|
28671
29062
|
async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
28672
29063
|
const target = getSlackReplyTarget(threadId);
|
|
@@ -28675,14 +29066,14 @@ async function relayCoordinatorReplyToSlack(threadId, opts) {
|
|
|
28675
29066
|
const lastUser = thread ? [...thread.messages].reverse().find((m) => m.role === "user") : void 0;
|
|
28676
29067
|
if (lastUser && isSlackInboundUserPrompt(lastUser.text)) return;
|
|
28677
29068
|
const result = getOrchestrator().getTurnResult(threadId);
|
|
28678
|
-
const
|
|
28679
|
-
if (!
|
|
28680
|
-
if (alreadyRelayed(threadId,
|
|
28681
|
-
markRelayed(threadId,
|
|
29069
|
+
const text7 = result.text.trim();
|
|
29070
|
+
if (!text7) return;
|
|
29071
|
+
if (alreadyRelayed(threadId, text7)) return;
|
|
29072
|
+
markRelayed(threadId, text7);
|
|
28682
29073
|
const log = opts.onLog ?? (() => void 0);
|
|
28683
29074
|
try {
|
|
28684
|
-
await postSlackText(target, signForThisMac(
|
|
28685
|
-
log(`replied ${target.threadTs ?? target.channelId} (${
|
|
29075
|
+
await postSlackText(target, signForThisMac(text7), opts);
|
|
29076
|
+
log(`replied ${target.threadTs ?? target.channelId} (${text7.length} chars)`);
|
|
28686
29077
|
} catch (err) {
|
|
28687
29078
|
lastRelayed.delete(threadId);
|
|
28688
29079
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -28931,10 +29322,10 @@ function resolveSlackListenMode(opts) {
|
|
|
28931
29322
|
|
|
28932
29323
|
// src/slack/relay-hub.ts
|
|
28933
29324
|
init_api();
|
|
28934
|
-
function parseSlackDeviceDestination(
|
|
28935
|
-
const m =
|
|
28936
|
-
if (!m) return { label: null, rest:
|
|
28937
|
-
return { label: m[1], rest:
|
|
29325
|
+
function parseSlackDeviceDestination(text7) {
|
|
29326
|
+
const m = text7.match(/^\s*(?:to\s+)?(?:@|#)?([A-Za-z][\w-]{0,63})\s*[::]\s*/);
|
|
29327
|
+
if (!m) return { label: null, rest: text7 };
|
|
29328
|
+
return { label: m[1], rest: text7.slice(m[0].length) };
|
|
28938
29329
|
}
|
|
28939
29330
|
var SlackRelayHub = class {
|
|
28940
29331
|
sessions = /* @__PURE__ */ new Map();
|
|
@@ -29156,9 +29547,9 @@ var import_node_http3 = require("http");
|
|
|
29156
29547
|
var import_ws3 = require("ws");
|
|
29157
29548
|
|
|
29158
29549
|
// src/slack/relay-static.ts
|
|
29159
|
-
var
|
|
29550
|
+
var import_node_fs54 = require("fs");
|
|
29160
29551
|
var import_promises = require("fs/promises");
|
|
29161
|
-
var
|
|
29552
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
29162
29553
|
var TYPES = {
|
|
29163
29554
|
".css": "text/css; charset=utf-8",
|
|
29164
29555
|
".html": "text/html; charset=utf-8",
|
|
@@ -29189,9 +29580,9 @@ function resolveStaticPath(root, requestUrl) {
|
|
|
29189
29580
|
return null;
|
|
29190
29581
|
}
|
|
29191
29582
|
if (!pathname.startsWith("/") || pathname.includes("\0")) return null;
|
|
29192
|
-
const rootResolved =
|
|
29193
|
-
const candidate =
|
|
29194
|
-
if (candidate !== rootResolved && !candidate.startsWith(rootResolved +
|
|
29583
|
+
const rootResolved = import_node_path51.default.resolve(root);
|
|
29584
|
+
const candidate = import_node_path51.default.resolve(rootResolved, `.${pathname}`);
|
|
29585
|
+
if (candidate !== rootResolved && !candidate.startsWith(rootResolved + import_node_path51.default.sep)) {
|
|
29195
29586
|
return null;
|
|
29196
29587
|
}
|
|
29197
29588
|
return candidate;
|
|
@@ -29205,7 +29596,7 @@ async function fileSize(file) {
|
|
|
29205
29596
|
}
|
|
29206
29597
|
}
|
|
29207
29598
|
function sendFile(req, res, file, size) {
|
|
29208
|
-
const ext =
|
|
29599
|
+
const ext = import_node_path51.default.extname(file).toLowerCase();
|
|
29209
29600
|
res.writeHead(200, {
|
|
29210
29601
|
"Content-Type": TYPES[ext] ?? "application/octet-stream",
|
|
29211
29602
|
"Content-Length": size,
|
|
@@ -29215,7 +29606,7 @@ function sendFile(req, res, file, size) {
|
|
|
29215
29606
|
res.end();
|
|
29216
29607
|
return true;
|
|
29217
29608
|
}
|
|
29218
|
-
(0,
|
|
29609
|
+
(0, import_node_fs54.createReadStream)(file).pipe(res);
|
|
29219
29610
|
return true;
|
|
29220
29611
|
}
|
|
29221
29612
|
async function tryServeStatic(req, res, root) {
|
|
@@ -29224,7 +29615,7 @@ async function tryServeStatic(req, res, root) {
|
|
|
29224
29615
|
if (!candidate) return false;
|
|
29225
29616
|
const direct = await fileSize(candidate);
|
|
29226
29617
|
if (direct != null) return sendFile(req, res, candidate, direct);
|
|
29227
|
-
const asIndex =
|
|
29618
|
+
const asIndex = import_node_path51.default.join(candidate, "index.html");
|
|
29228
29619
|
const indexSize = await fileSize(asIndex);
|
|
29229
29620
|
if (indexSize != null) return sendFile(req, res, asIndex, indexSize);
|
|
29230
29621
|
return false;
|
|
@@ -29460,10 +29851,6 @@ init_outbound_watch();
|
|
|
29460
29851
|
// Annotate the CommonJS export names for ESM import in node:
|
|
29461
29852
|
0 && (module.exports = {
|
|
29462
29853
|
ABLETIME_MCP_PATH,
|
|
29463
|
-
ACCOUNT_ROLES,
|
|
29464
|
-
ACCOUNT_ROLE_LABELS,
|
|
29465
|
-
ACCOUNT_ROLE_MAX,
|
|
29466
|
-
ACCOUNT_ROLE_PRESETS,
|
|
29467
29854
|
AGENT_GIT_ACTIONS,
|
|
29468
29855
|
AGENT_RUNNER_MAX_OLD_SPACE_MB,
|
|
29469
29856
|
ATTACHMENTS_DIR,
|
|
@@ -29550,7 +29937,6 @@ init_outbound_watch();
|
|
|
29550
29937
|
WORKTREE_MCP_TOOLS,
|
|
29551
29938
|
abletimeMcpRequest,
|
|
29552
29939
|
abletimeMcpUrl,
|
|
29553
|
-
accountRoleLabel,
|
|
29554
29940
|
ackSlackInboundSeen,
|
|
29555
29941
|
addBoardPin,
|
|
29556
29942
|
addPrStackLayer,
|
|
@@ -29731,6 +30117,7 @@ init_outbound_watch();
|
|
|
29731
30117
|
fireSchedule,
|
|
29732
30118
|
flattenTurnInput,
|
|
29733
30119
|
flipLinearRelationType,
|
|
30120
|
+
foldLegacyRolesIntoNotes,
|
|
29734
30121
|
followUpBehavior,
|
|
29735
30122
|
forkChatTab,
|
|
29736
30123
|
forkMessageSlice,
|
|
@@ -29771,6 +30158,8 @@ init_outbound_watch();
|
|
|
29771
30158
|
formatSlackWorkingText,
|
|
29772
30159
|
formatTranscriptMarkdown,
|
|
29773
30160
|
formatUiReminder,
|
|
30161
|
+
formatViewerContextDirective,
|
|
30162
|
+
formatViewerContextReminder,
|
|
29774
30163
|
formatWorkspaceInventory,
|
|
29775
30164
|
formatWorkspaceProfileSuffix,
|
|
29776
30165
|
formatWorktreeDirective,
|
|
@@ -30008,7 +30397,6 @@ init_outbound_watch();
|
|
|
30008
30397
|
planQuestionsSignature,
|
|
30009
30398
|
pollSlackOutboundWatches,
|
|
30010
30399
|
posixShellSingleQuote,
|
|
30011
|
-
preferTeamsForRole,
|
|
30012
30400
|
preferredCursorCostCents,
|
|
30013
30401
|
prepareTerminalCommand,
|
|
30014
30402
|
previewLand,
|
|
@@ -30020,6 +30408,7 @@ init_outbound_watch();
|
|
|
30020
30408
|
readSetupLog,
|
|
30021
30409
|
readSkillBody,
|
|
30022
30410
|
readThread,
|
|
30411
|
+
readViewerContext,
|
|
30023
30412
|
readWorktreeFile,
|
|
30024
30413
|
readWorktreeFileForUpload,
|
|
30025
30414
|
readWorktreeInclude,
|
|
@@ -30076,7 +30465,6 @@ init_outbound_watch();
|
|
|
30076
30465
|
resolveViewerProfile,
|
|
30077
30466
|
resolveViewerProfileForRepo,
|
|
30078
30467
|
resolveWorktreeStartPoint,
|
|
30079
|
-
reviewTeamHintsForRoles,
|
|
30080
30468
|
rewriteAbleTimeError,
|
|
30081
30469
|
rewriteLinearError,
|
|
30082
30470
|
run,
|
|
@@ -30210,5 +30598,6 @@ init_outbound_watch();
|
|
|
30210
30598
|
writeInjectedMcpConfig,
|
|
30211
30599
|
writePlanFile,
|
|
30212
30600
|
writeThread,
|
|
30601
|
+
writeViewerContext,
|
|
30213
30602
|
writeWorktreeFile
|
|
30214
30603
|
});
|