@band-app/server 0.42.1 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/_shell.html +0 -0
- package/dist/client/assets/{TerminalPanel-BdNt_1-v.js → TerminalPanel-B4mOqlhb.js} +1 -1
- package/dist/client/assets/{_basePickBy-Dt5aurJR.js → _basePickBy-CHfQ38My.js} +1 -1
- package/dist/client/assets/{_baseUniq-eGBE_G8J.js → _baseUniq-C0VxYqbf.js} +1 -1
- package/dist/client/assets/{arc-C2TAcIr0.js → arc-oM0HaiTF.js} +1 -1
- package/dist/client/assets/{architectureDiagram-VXUJARFQ-CXYQ0Bl3.js → architectureDiagram-VXUJARFQ-BHJF6rDN.js} +1 -1
- package/dist/client/assets/{blockDiagram-VD42YOAC-QNGIe7jl.js → blockDiagram-VD42YOAC-DfoHWvF-.js} +1 -1
- package/dist/client/assets/{c4Diagram-YG6GDRKO-DNBB986h.js → c4Diagram-YG6GDRKO-ofRUHXOe.js} +1 -1
- package/dist/client/assets/channel-Bpwh056d.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-BiYeCu61.js → chunk-4BX2VUAB-CXpLCpos.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-DetvZt2e.js → chunk-55IACEB6-D9t-7fJU.js} +1 -1
- package/dist/client/assets/{chunk-B4BG7PRW-C6yj2phu.js → chunk-B4BG7PRW-BsG1-s4X.js} +1 -1
- package/dist/client/assets/{chunk-DI55MBZ5-C3tA1HuX.js → chunk-DI55MBZ5-8gxNuHjG.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BQgPG9nP.js → chunk-FMBD7UC4-FeEHb48m.js} +1 -1
- package/dist/client/assets/{chunk-QN33PNHL-BEe46vEx.js → chunk-QN33PNHL-z8mbW7ph.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-S1hRRppa.js → chunk-QZHKN3VN-CvLhsLHt.js} +1 -1
- package/dist/client/assets/{chunk-TZMSLE5B-DzuJYhii.js → chunk-TZMSLE5B-s9nFI-vX.js} +1 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-DwcD-A80.js +1 -0
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-DwcD-A80.js +1 -0
- package/dist/client/assets/clone-DbV2PV1D.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CUxpzpxe.js → cose-bilkent-S5V4N54A-onixR54D.js} +1 -1
- package/dist/client/assets/{dagre-6UL2VRFP-CxQr_vx3.js → dagre-6UL2VRFP-CmeoFjCN.js} +1 -1
- package/dist/client/assets/{diagram-PSM6KHXK-BM9S3pcM.js → diagram-PSM6KHXK-Dv0xhzCj.js} +1 -1
- package/dist/client/assets/{diagram-QEK2KX5R-00WgBgwT.js → diagram-QEK2KX5R-Z_UWRAUZ.js} +1 -1
- package/dist/client/assets/{diagram-S2PKOQOG-CkydGteg.js → diagram-S2PKOQOG-BAib5-Ox.js} +1 -1
- package/dist/client/assets/{erDiagram-Q2GNP2WA-DDbgp90K.js → erDiagram-Q2GNP2WA-BhsQblX6.js} +1 -1
- package/dist/client/assets/{flowDiagram-NV44I4VS-1sWwkovK.js → flowDiagram-NV44I4VS-CAmBrRtm.js} +1 -1
- package/dist/client/assets/{ganttDiagram-JELNMOA3-z9MMcnjj.js → ganttDiagram-JELNMOA3-Bt9yOXqD.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-V2S2FVAM-CEeNZDag.js → gitGraphDiagram-V2S2FVAM-CjP86OLx.js} +1 -1
- package/dist/client/assets/{graph-8-N2XIVg.js → graph-DjlnSlEi.js} +1 -1
- package/dist/client/assets/{highlighted-body-B3W2YXNL-oqBJo7Lw.js → highlighted-body-B3W2YXNL-BC26yCeV.js} +1 -1
- package/dist/client/assets/{index-D7GkBMm7.js → index-BRLLIA0E.js} +1 -1
- package/dist/client/assets/{index-BjeVPwqv.js → index-BqHRp8MJ.js} +1 -1
- package/dist/client/assets/{index-B10E2JKM.js → index-BzKNDxvN.js} +1 -1
- package/dist/client/assets/{index-BfpCIunK.js → index-C0J9o47g.js} +1 -1
- package/dist/client/assets/{index-BW8m1RE2.js → index-CL8ph-cw.js} +1 -1
- package/dist/client/assets/{index-Ci4aLxZE.js → index-D1ndeAzk.js} +1 -1
- package/dist/client/assets/{index-UwPr1Bxu.js → index-D7os2dUh.js} +1 -1
- package/dist/client/assets/{index-BSMwXa_n.js → index-DBYA5XXI.js} +1 -1
- package/dist/client/assets/{index-B0DR0_5t.js → index-DS5EEpw2.js} +1 -1
- package/dist/client/assets/{index-DyktM7iC.js → index-Dhjz23HU.js} +1 -1
- package/dist/client/assets/{index-BYyaVskX.js → index-SJc6A-mo.js} +1 -1
- package/dist/client/assets/{index-CWgE6xNM.js → index-csfelsYg.js} +1 -1
- package/dist/client/assets/{index-DgfP7WcS.js → index-sTelM5W4.js} +1 -1
- package/dist/client/assets/{infoDiagram-HS3SLOUP-0xlIsMeE.js → infoDiagram-HS3SLOUP-DANcwC0l.js} +1 -1
- package/dist/client/assets/{journeyDiagram-XKPGCS4Q-URjejIwO.js → journeyDiagram-XKPGCS4Q-Bq7L8SS6.js} +1 -1
- package/dist/client/assets/{kanban-definition-3W4ZIXB7-iMd66xJM.js → kanban-definition-3W4ZIXB7-Rdco6x9s.js} +1 -1
- package/dist/client/assets/{layout-CZKMoUju.js → layout-BZ5I6v7b.js} +1 -1
- package/dist/client/assets/{main-Bf7Cqkhh.js → main-D_sbU-8F.js} +258 -258
- package/dist/client/assets/main-vXnu-Zdi.css +1 -0
- package/dist/client/assets/{mindmap-definition-VGOIOE7T-BgBLqjDc.js → mindmap-definition-VGOIOE7T-Daxduia_.js} +1 -1
- package/dist/client/assets/{pieDiagram-ADFJNKIX-CZlj_xhf.js → pieDiagram-ADFJNKIX-CfQaiffR.js} +1 -1
- package/dist/client/assets/{project._projectId-DkEX_rhg.js → project._projectId-DbodKP8r.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AYHSOK5B-egWT3ySU.js → quadrantDiagram-AYHSOK5B-Dp_MXprC.js} +1 -1
- package/dist/client/assets/{requirementDiagram-UZGBJVZJ-C1f4-YqC.js → requirementDiagram-UZGBJVZJ-qLyV9NbO.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-TZEHDZUN-BIBQCdMV.js → sankeyDiagram-TZEHDZUN-mYTnU11w.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WL72ISMW-CwcAaqPr.js → sequenceDiagram-WL72ISMW-C59cPqQq.js} +1 -1
- package/dist/client/assets/{stateDiagram-FKZM4ZOC-BR0ZE0jR.js → stateDiagram-FKZM4ZOC-DyxRCj8E.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-CSNSj8Vh.js +1 -0
- package/dist/client/assets/{timeline-definition-IT6M3QCI-Br4v6z5c.js → timeline-definition-IT6M3QCI-D71_ooP7.js} +1 -1
- package/dist/client/assets/{treemap-GDKQZRPO-BknlJ8NC.js → treemap-GDKQZRPO-Ctq0P5nh.js} +1 -1
- package/dist/client/assets/{use-worktree-route-BKyPkuvd.js → use-worktree-route-B4VSufa4.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-C4rABxUB.js → worktree._worktreeId-CvHNSUat.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-DmBaR5MI.js → worktree._worktreeId-DQRHrz5g.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PRI3JC2R-CUQFNqNp.js → xychartDiagram-PRI3JC2R-TzranwDg.js} +1 -1
- package/dist/openapi.json +196 -76
- package/dist/start-server.mjs +1339 -892
- package/package.json +5 -5
- package/dist/client/assets/channel-C-DZN68q.js +0 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-CnfpJOrh.js +0 -1
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-CnfpJOrh.js +0 -1
- package/dist/client/assets/clone-HEWjuVIQ.js +0 -1
- package/dist/client/assets/main-D4L0ZncB.css +0 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-Bl6SncpV.js +0 -1
package/dist/start-server.mjs
CHANGED
|
@@ -2031,7 +2031,7 @@ var require_thread_stream = __commonJS({
|
|
|
2031
2031
|
var { version: version2 } = require_package();
|
|
2032
2032
|
var { EventEmitter: EventEmitter3 } = __require("events");
|
|
2033
2033
|
var { Worker: Worker2 } = __require("worker_threads");
|
|
2034
|
-
var { join:
|
|
2034
|
+
var { join: join64 } = __require("path");
|
|
2035
2035
|
var { pathToFileURL } = __require("url");
|
|
2036
2036
|
var { wait } = require_wait();
|
|
2037
2037
|
var {
|
|
@@ -2067,7 +2067,7 @@ var require_thread_stream = __commonJS({
|
|
|
2067
2067
|
function createWorker(stream, opts) {
|
|
2068
2068
|
const { filename, workerData } = opts;
|
|
2069
2069
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
2070
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
2070
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join64(__dirname, "lib", "worker.js");
|
|
2071
2071
|
const worker2 = new Worker2(toExecute, {
|
|
2072
2072
|
...opts.workerOpts,
|
|
2073
2073
|
trackUnmanagedFds: false,
|
|
@@ -2453,7 +2453,7 @@ var require_transport = __commonJS({
|
|
|
2453
2453
|
"use strict";
|
|
2454
2454
|
var { createRequire: createRequire2 } = __require("module");
|
|
2455
2455
|
var getCallers = require_caller();
|
|
2456
|
-
var { join:
|
|
2456
|
+
var { join: join64, isAbsolute: isAbsolute7, sep: sep13 } = __require("node:path");
|
|
2457
2457
|
var sleep3 = require_atomic_sleep();
|
|
2458
2458
|
var onExit = require_on_exit_leak_free();
|
|
2459
2459
|
var ThreadStream = require_thread_stream();
|
|
@@ -2516,7 +2516,7 @@ var require_transport = __commonJS({
|
|
|
2516
2516
|
throw new Error("only one of target or targets can be specified");
|
|
2517
2517
|
}
|
|
2518
2518
|
if (targets) {
|
|
2519
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
2519
|
+
target = bundlerOverrides["pino-worker"] || join64(__dirname, "worker.js");
|
|
2520
2520
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
2521
2521
|
return {
|
|
2522
2522
|
...dest,
|
|
@@ -2534,7 +2534,7 @@ var require_transport = __commonJS({
|
|
|
2534
2534
|
});
|
|
2535
2535
|
});
|
|
2536
2536
|
} else if (pipeline2) {
|
|
2537
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
2537
|
+
target = bundlerOverrides["pino-worker"] || join64(__dirname, "worker.js");
|
|
2538
2538
|
options.pipelines = [pipeline2.map((dest) => {
|
|
2539
2539
|
return {
|
|
2540
2540
|
...dest,
|
|
@@ -2556,7 +2556,7 @@ var require_transport = __commonJS({
|
|
|
2556
2556
|
return origin;
|
|
2557
2557
|
}
|
|
2558
2558
|
if (origin === "pino/file") {
|
|
2559
|
-
return
|
|
2559
|
+
return join64(__dirname, "..", "file.js");
|
|
2560
2560
|
}
|
|
2561
2561
|
let fixTarget2;
|
|
2562
2562
|
for (const filePath of callers) {
|
|
@@ -3545,7 +3545,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
3545
3545
|
return circularValue;
|
|
3546
3546
|
}
|
|
3547
3547
|
let res = "";
|
|
3548
|
-
let
|
|
3548
|
+
let join64 = ",";
|
|
3549
3549
|
const originalIndentation = indentation;
|
|
3550
3550
|
if (Array.isArray(value)) {
|
|
3551
3551
|
if (value.length === 0) {
|
|
@@ -3559,7 +3559,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
3559
3559
|
indentation += spacer;
|
|
3560
3560
|
res += `
|
|
3561
3561
|
${indentation}`;
|
|
3562
|
-
|
|
3562
|
+
join64 = `,
|
|
3563
3563
|
${indentation}`;
|
|
3564
3564
|
}
|
|
3565
3565
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -3567,13 +3567,13 @@ ${indentation}`;
|
|
|
3567
3567
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
3568
3568
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
3569
3569
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
3570
|
-
res +=
|
|
3570
|
+
res += join64;
|
|
3571
3571
|
}
|
|
3572
3572
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
3573
3573
|
res += tmp !== void 0 ? tmp : "null";
|
|
3574
3574
|
if (value.length - 1 > maximumBreadth) {
|
|
3575
3575
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
3576
|
-
res += `${
|
|
3576
|
+
res += `${join64}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
3577
3577
|
}
|
|
3578
3578
|
if (spacer !== "") {
|
|
3579
3579
|
res += `
|
|
@@ -3594,7 +3594,7 @@ ${originalIndentation}`;
|
|
|
3594
3594
|
let separator = "";
|
|
3595
3595
|
if (spacer !== "") {
|
|
3596
3596
|
indentation += spacer;
|
|
3597
|
-
|
|
3597
|
+
join64 = `,
|
|
3598
3598
|
${indentation}`;
|
|
3599
3599
|
whitespace = " ";
|
|
3600
3600
|
}
|
|
@@ -3608,13 +3608,13 @@ ${indentation}`;
|
|
|
3608
3608
|
const tmp = stringifyFnReplacer(key3, value, stack, replacer, spacer, indentation);
|
|
3609
3609
|
if (tmp !== void 0) {
|
|
3610
3610
|
res += `${separator}${strEscape(key3)}:${whitespace}${tmp}`;
|
|
3611
|
-
separator =
|
|
3611
|
+
separator = join64;
|
|
3612
3612
|
}
|
|
3613
3613
|
}
|
|
3614
3614
|
if (keyLength > maximumBreadth) {
|
|
3615
3615
|
const removedKeys = keyLength - maximumBreadth;
|
|
3616
3616
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
3617
|
-
separator =
|
|
3617
|
+
separator = join64;
|
|
3618
3618
|
}
|
|
3619
3619
|
if (spacer !== "" && separator.length > 1) {
|
|
3620
3620
|
res = `
|
|
@@ -3655,7 +3655,7 @@ ${originalIndentation}`;
|
|
|
3655
3655
|
}
|
|
3656
3656
|
const originalIndentation = indentation;
|
|
3657
3657
|
let res = "";
|
|
3658
|
-
let
|
|
3658
|
+
let join64 = ",";
|
|
3659
3659
|
if (Array.isArray(value)) {
|
|
3660
3660
|
if (value.length === 0) {
|
|
3661
3661
|
return "[]";
|
|
@@ -3668,7 +3668,7 @@ ${originalIndentation}`;
|
|
|
3668
3668
|
indentation += spacer;
|
|
3669
3669
|
res += `
|
|
3670
3670
|
${indentation}`;
|
|
3671
|
-
|
|
3671
|
+
join64 = `,
|
|
3672
3672
|
${indentation}`;
|
|
3673
3673
|
}
|
|
3674
3674
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -3676,13 +3676,13 @@ ${indentation}`;
|
|
|
3676
3676
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
3677
3677
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
3678
3678
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
3679
|
-
res +=
|
|
3679
|
+
res += join64;
|
|
3680
3680
|
}
|
|
3681
3681
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
3682
3682
|
res += tmp !== void 0 ? tmp : "null";
|
|
3683
3683
|
if (value.length - 1 > maximumBreadth) {
|
|
3684
3684
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
3685
|
-
res += `${
|
|
3685
|
+
res += `${join64}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
3686
3686
|
}
|
|
3687
3687
|
if (spacer !== "") {
|
|
3688
3688
|
res += `
|
|
@@ -3695,7 +3695,7 @@ ${originalIndentation}`;
|
|
|
3695
3695
|
let whitespace = "";
|
|
3696
3696
|
if (spacer !== "") {
|
|
3697
3697
|
indentation += spacer;
|
|
3698
|
-
|
|
3698
|
+
join64 = `,
|
|
3699
3699
|
${indentation}`;
|
|
3700
3700
|
whitespace = " ";
|
|
3701
3701
|
}
|
|
@@ -3704,7 +3704,7 @@ ${indentation}`;
|
|
|
3704
3704
|
const tmp = stringifyArrayReplacer(key3, value[key3], stack, replacer, spacer, indentation);
|
|
3705
3705
|
if (tmp !== void 0) {
|
|
3706
3706
|
res += `${separator}${strEscape(key3)}:${whitespace}${tmp}`;
|
|
3707
|
-
separator =
|
|
3707
|
+
separator = join64;
|
|
3708
3708
|
}
|
|
3709
3709
|
}
|
|
3710
3710
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -3762,20 +3762,20 @@ ${originalIndentation}`;
|
|
|
3762
3762
|
indentation += spacer;
|
|
3763
3763
|
let res2 = `
|
|
3764
3764
|
${indentation}`;
|
|
3765
|
-
const
|
|
3765
|
+
const join65 = `,
|
|
3766
3766
|
${indentation}`;
|
|
3767
3767
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
3768
3768
|
let i = 0;
|
|
3769
3769
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
3770
3770
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
3771
3771
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
3772
|
-
res2 +=
|
|
3772
|
+
res2 += join65;
|
|
3773
3773
|
}
|
|
3774
3774
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
3775
3775
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
3776
3776
|
if (value.length - 1 > maximumBreadth) {
|
|
3777
3777
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
3778
|
-
res2 += `${
|
|
3778
|
+
res2 += `${join65}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
3779
3779
|
}
|
|
3780
3780
|
res2 += `
|
|
3781
3781
|
${originalIndentation}`;
|
|
@@ -3791,16 +3791,16 @@ ${originalIndentation}`;
|
|
|
3791
3791
|
return '"[Object]"';
|
|
3792
3792
|
}
|
|
3793
3793
|
indentation += spacer;
|
|
3794
|
-
const
|
|
3794
|
+
const join64 = `,
|
|
3795
3795
|
${indentation}`;
|
|
3796
3796
|
let res = "";
|
|
3797
3797
|
let separator = "";
|
|
3798
3798
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
3799
3799
|
if (isTypedArrayWithEntries(value)) {
|
|
3800
|
-
res += stringifyTypedArray(value,
|
|
3800
|
+
res += stringifyTypedArray(value, join64, maximumBreadth);
|
|
3801
3801
|
keys = keys.slice(value.length);
|
|
3802
3802
|
maximumPropertiesToStringify -= value.length;
|
|
3803
|
-
separator =
|
|
3803
|
+
separator = join64;
|
|
3804
3804
|
}
|
|
3805
3805
|
if (deterministic) {
|
|
3806
3806
|
keys = sort(keys, comparator2);
|
|
@@ -3811,13 +3811,13 @@ ${indentation}`;
|
|
|
3811
3811
|
const tmp = stringifyIndent(key3, value[key3], stack, spacer, indentation);
|
|
3812
3812
|
if (tmp !== void 0) {
|
|
3813
3813
|
res += `${separator}${strEscape(key3)}: ${tmp}`;
|
|
3814
|
-
separator =
|
|
3814
|
+
separator = join64;
|
|
3815
3815
|
}
|
|
3816
3816
|
}
|
|
3817
3817
|
if (keyLength > maximumBreadth) {
|
|
3818
3818
|
const removedKeys = keyLength - maximumBreadth;
|
|
3819
3819
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
3820
|
-
separator =
|
|
3820
|
+
separator = join64;
|
|
3821
3821
|
}
|
|
3822
3822
|
if (separator !== "") {
|
|
3823
3823
|
res = `
|
|
@@ -13105,7 +13105,7 @@ var init_sql = __esm({
|
|
|
13105
13105
|
return new SQL([new StringChunk(str4)]);
|
|
13106
13106
|
}
|
|
13107
13107
|
_sql.raw = raw;
|
|
13108
|
-
function
|
|
13108
|
+
function join64(chunks, separator) {
|
|
13109
13109
|
const result = [];
|
|
13110
13110
|
for (const [i, chunk] of chunks.entries()) {
|
|
13111
13111
|
if (i > 0 && separator !== void 0) result.push(separator);
|
|
@@ -13113,7 +13113,7 @@ var init_sql = __esm({
|
|
|
13113
13113
|
}
|
|
13114
13114
|
return new SQL(result);
|
|
13115
13115
|
}
|
|
13116
|
-
_sql.join =
|
|
13116
|
+
_sql.join = join64;
|
|
13117
13117
|
function identifier(value) {
|
|
13118
13118
|
return new Name(value);
|
|
13119
13119
|
}
|
|
@@ -18267,7 +18267,7 @@ var init_select2 = __esm({
|
|
|
18267
18267
|
const baseTableName = this.tableName;
|
|
18268
18268
|
const tableName = getTableLikeName(table);
|
|
18269
18269
|
for (const item of extractUsedTable(table)) this.usedTables.add(item);
|
|
18270
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
18270
|
+
if (typeof tableName === "string" && this.config.joins?.some((join64) => join64.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
18271
18271
|
if (!this.isPartialSelect) {
|
|
18272
18272
|
if (Object.keys(this.joinsNotNullableMap).length === 1 && typeof baseTableName === "string") this.config.fields = { [baseTableName]: this.config.fields };
|
|
18273
18273
|
if (typeof tableName === "string" && !is(table, SQL)) {
|
|
@@ -19517,7 +19517,7 @@ var init_dialect = __esm({
|
|
|
19517
19517
|
if (!joins2) return;
|
|
19518
19518
|
const withEntries = Object.entries(joins2).filter(([_2, v2]) => v2);
|
|
19519
19519
|
if (!withEntries.length) return;
|
|
19520
|
-
return sql.join(withEntries.map(([k2,
|
|
19520
|
+
return sql.join(withEntries.map(([k2, join64]) => {
|
|
19521
19521
|
const relation = tableConfig.relations[k2];
|
|
19522
19522
|
const isSingle2 = is(relation, One);
|
|
19523
19523
|
const targetTable = aliasedTable(relation.targetTable, `d${currentDepth + 1}`);
|
|
@@ -19528,7 +19528,7 @@ var init_dialect = __esm({
|
|
|
19528
19528
|
table: targetTable,
|
|
19529
19529
|
mode: isSingle2 ? "first" : "many",
|
|
19530
19530
|
schema,
|
|
19531
|
-
queryConfig:
|
|
19531
|
+
queryConfig: join64,
|
|
19532
19532
|
tableConfig: schema[relation.targetTableName],
|
|
19533
19533
|
relationWhere: filter,
|
|
19534
19534
|
isNested: true,
|
|
@@ -19542,7 +19542,7 @@ var init_dialect = __esm({
|
|
|
19542
19542
|
key: k2,
|
|
19543
19543
|
selection: innerQuery.selection,
|
|
19544
19544
|
isArray: !isSingle2,
|
|
19545
|
-
isOptional: (relation.optional ?? false) ||
|
|
19545
|
+
isOptional: (relation.optional ?? false) || join64 !== true && !!join64.where
|
|
19546
19546
|
});
|
|
19547
19547
|
const jsonColumns = sql.join(innerQuery.selection.map((s2) => {
|
|
19548
19548
|
return sql`${sql.raw(this.escapeString(s2.key))}, ${s2.selection ? sql`${jsonb2}(${sql.identifier(s2.key)})` : sql.identifier(s2.key)}`;
|
|
@@ -20061,7 +20061,7 @@ var init_update = __esm({
|
|
|
20061
20061
|
createJoin(joinType) {
|
|
20062
20062
|
return ((table, on) => {
|
|
20063
20063
|
const tableName = getTableLikeName(table);
|
|
20064
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
20064
|
+
if (typeof tableName === "string" && this.config.joins.some((join64) => join64.alias === tableName)) throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
20065
20065
|
if (typeof on === "function") {
|
|
20066
20066
|
const from = this.config.from ? is(table, SQLiteTable) ? table[Table.Symbol.Columns] : is(table, Subquery) ? table._.selectedFields : is(table, SQLiteViewBase) ? table[ViewBaseConfig].selectedFields : void 0 : void 0;
|
|
20067
20067
|
on = on(new Proxy(this.config.table[Table.Symbol.Columns], new SelectionProxyHandler({
|
|
@@ -26784,10 +26784,10 @@ var init_zod = __esm({
|
|
|
26784
26784
|
|
|
26785
26785
|
// src/server/infra/db/queries/repos.ts
|
|
26786
26786
|
import { existsSync as existsSync10 } from "node:fs";
|
|
26787
|
-
import { join as
|
|
26787
|
+
import { join as join35 } from "node:path";
|
|
26788
26788
|
function reconcileKindForRepo(repo) {
|
|
26789
26789
|
if (!repo.path || !existsSync10(repo.path)) return false;
|
|
26790
|
-
const detectedKind = existsSync10(
|
|
26790
|
+
const detectedKind = existsSync10(join35(repo.path, ".git")) ? "git" : "plain";
|
|
26791
26791
|
if (detectedKind === repo.kind) return false;
|
|
26792
26792
|
repo.kind = detectedKind;
|
|
26793
26793
|
if (detectedKind === "plain") {
|
|
@@ -27338,7 +27338,7 @@ var init_isolation = __esm({
|
|
|
27338
27338
|
|
|
27339
27339
|
// src/server/services/_utils/runner-config.ts
|
|
27340
27340
|
import { existsSync as existsSync11 } from "node:fs";
|
|
27341
|
-
import { dirname as dirname10, isAbsolute as isAbsolute2, join as
|
|
27341
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join36, resolve as resolve8 } from "node:path";
|
|
27342
27342
|
function parseRunners(raw) {
|
|
27343
27343
|
if (raw === void 0 || raw === null) return { runners: [], errors: [] };
|
|
27344
27344
|
if (!Array.isArray(raw)) return { runners: [], errors: ["runners must be a list"] };
|
|
@@ -27363,8 +27363,8 @@ function bundledRunnersDir() {
|
|
|
27363
27363
|
if (fromEnv) return resolve8(fromEnv);
|
|
27364
27364
|
let dir = import.meta.dirname;
|
|
27365
27365
|
for (let i = 0; i < 10; i++) {
|
|
27366
|
-
const candidate =
|
|
27367
|
-
if (existsSync11(
|
|
27366
|
+
const candidate = join36(dir, "runners");
|
|
27367
|
+
if (existsSync11(join36(candidate, "local", "spawn.sh"))) return candidate;
|
|
27368
27368
|
const parent = dirname10(dir);
|
|
27369
27369
|
if (parent === dir) break;
|
|
27370
27370
|
dir = parent;
|
|
@@ -27375,7 +27375,7 @@ function resolveHookPath(spec, script) {
|
|
|
27375
27375
|
if (spec.startsWith(BUNDLED_PREFIX)) {
|
|
27376
27376
|
const name5 = spec.slice(BUNDLED_PREFIX.length);
|
|
27377
27377
|
if (!/^[a-z][a-z0-9-]*$/.test(name5)) throw new Error(`invalid bundled hook "${spec}"`);
|
|
27378
|
-
return
|
|
27378
|
+
return join36(bundledRunnersDir(), name5, `${script}.sh`);
|
|
27379
27379
|
}
|
|
27380
27380
|
return isAbsolute2(spec) ? spec : resolve8(bandHome(), spec);
|
|
27381
27381
|
}
|
|
@@ -27474,7 +27474,7 @@ var init_runner_config = __esm({
|
|
|
27474
27474
|
});
|
|
27475
27475
|
|
|
27476
27476
|
// src/server/services/settings-service.ts
|
|
27477
|
-
import { join as
|
|
27477
|
+
import { join as join37 } from "node:path";
|
|
27478
27478
|
function agentModeFromVia(via) {
|
|
27479
27479
|
if (via === "chat") return "gui";
|
|
27480
27480
|
if (via === "terminal") return "tui";
|
|
@@ -27668,7 +27668,7 @@ var init_settings_service = __esm({
|
|
|
27668
27668
|
*/
|
|
27669
27669
|
worktreesDir() {
|
|
27670
27670
|
const raw = this.rawWorktreesDir();
|
|
27671
|
-
return raw ??
|
|
27671
|
+
return raw ?? join37(bandHome(), "worktrees");
|
|
27672
27672
|
}
|
|
27673
27673
|
/**
|
|
27674
27674
|
* Return the raw `worktreesDir` value from settings without applying the
|
|
@@ -34771,7 +34771,7 @@ import {
|
|
|
34771
34771
|
statSync as statSync14
|
|
34772
34772
|
} from "node:fs";
|
|
34773
34773
|
import { createServer as createServer3 } from "node:http";
|
|
34774
|
-
import { basename as basename7, join as
|
|
34774
|
+
import { basename as basename7, join as join63, resolve as resolve15, sep as sep12 } from "node:path";
|
|
34775
34775
|
import { pipeline, Readable as Readable2 } from "node:stream";
|
|
34776
34776
|
import { parseArgs } from "node:util";
|
|
34777
34777
|
|
|
@@ -41327,6 +41327,11 @@ function openSseStream(res) {
|
|
|
41327
41327
|
// src/server/services/agent-session-service.ts
|
|
41328
41328
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
41329
41329
|
|
|
41330
|
+
// ../../packages/host-api/src/capabilities.ts
|
|
41331
|
+
function agentIsUsable(a) {
|
|
41332
|
+
return Boolean(a?.installed) && a?.loggedIn !== false;
|
|
41333
|
+
}
|
|
41334
|
+
|
|
41330
41335
|
// ../../packages/host-api/src/errors.ts
|
|
41331
41336
|
var HostPathDeniedError = class extends Error {
|
|
41332
41337
|
path;
|
|
@@ -56856,11 +56861,11 @@ var ChatEventQueries = class {
|
|
|
56856
56861
|
init_settings();
|
|
56857
56862
|
|
|
56858
56863
|
// src/server/infra/host/registry.ts
|
|
56859
|
-
import { join as
|
|
56864
|
+
import { join as join38 } from "node:path";
|
|
56860
56865
|
|
|
56861
56866
|
// ../../packages/host-local/src/local-host.ts
|
|
56862
56867
|
init_src2();
|
|
56863
|
-
import { execFile as
|
|
56868
|
+
import { execFile as execFile12, spawn as spawn7 } from "node:child_process";
|
|
56864
56869
|
import { createReadStream as createReadStream3, watch as fsWatch } from "node:fs";
|
|
56865
56870
|
import {
|
|
56866
56871
|
cp,
|
|
@@ -56872,12 +56877,12 @@ import {
|
|
|
56872
56877
|
readFile as readFile7,
|
|
56873
56878
|
realpath as realpath4,
|
|
56874
56879
|
rename as rename3,
|
|
56875
|
-
rm as
|
|
56880
|
+
rm as rm4,
|
|
56876
56881
|
stat as stat7,
|
|
56877
56882
|
writeFile as writeFile4
|
|
56878
56883
|
} from "node:fs/promises";
|
|
56879
|
-
import { homedir as
|
|
56880
|
-
import { join as
|
|
56884
|
+
import { homedir as homedir14, hostname as hostname3, tmpdir as tmpdir2 } from "node:os";
|
|
56885
|
+
import { join as join34 } from "node:path";
|
|
56881
56886
|
|
|
56882
56887
|
// ../../packages/host-local/src/agents/acp-launch.ts
|
|
56883
56888
|
init_src2();
|
|
@@ -57959,7 +57964,7 @@ var NETWORK_TIMEOUT_MS = 3e4;
|
|
|
57959
57964
|
var LOCAL_TIMEOUT_MS = 3e4;
|
|
57960
57965
|
var MAX_PUSH_ATTEMPTS = 5;
|
|
57961
57966
|
var APPEND_ONLY = ["learnings/**", "inbox/**"];
|
|
57962
|
-
var LOCAL_ONLY = ["/tasks/", "/repos/"
|
|
57967
|
+
var LOCAL_ONLY = ["/tasks/", "/repos/"];
|
|
57963
57968
|
function gitEnv(extra = {}) {
|
|
57964
57969
|
const env = {};
|
|
57965
57970
|
for (const [k2, v2] of Object.entries(process.env)) {
|
|
@@ -58296,6 +58301,32 @@ var ContextSync = class {
|
|
|
58296
58301
|
`);
|
|
58297
58302
|
await this.git(dir, ["config", "rebase.autoStash", "false"]);
|
|
58298
58303
|
await this.git(dir, ["config", "commit.gpgsign", "false"]);
|
|
58304
|
+
await this.dropLegacyInstructions(dir);
|
|
58305
|
+
}
|
|
58306
|
+
/**
|
|
58307
|
+
* Older Band wrote a generated AGENTS.md and CLAUDE.md into each host's copy and kept them out of
|
|
58308
|
+
* the context repo. They are synced files now, so an untracked leftover would conflict with the
|
|
58309
|
+
* hub's seeded file or be pushed as the user's own. Remove an untracked one that still carries
|
|
58310
|
+
* Band's generated header.
|
|
58311
|
+
*/
|
|
58312
|
+
async dropLegacyInstructions(dir) {
|
|
58313
|
+
const markers = {
|
|
58314
|
+
"AGENTS.md": "<!-- Written by Band from the project's settings",
|
|
58315
|
+
"CLAUDE.md": "<!-- Written by Band. Claude Code reads this file"
|
|
58316
|
+
};
|
|
58317
|
+
for (const [file2, marker] of Object.entries(markers)) {
|
|
58318
|
+
try {
|
|
58319
|
+
const text3 = await readFile4(join23(dir, file2), "utf8");
|
|
58320
|
+
if (!text3.startsWith(marker)) continue;
|
|
58321
|
+
const tracked2 = await run2(["ls-files", "--error-unmatch", "--", file2], {
|
|
58322
|
+
cwd: dir,
|
|
58323
|
+
timeoutMs: LOCAL_TIMEOUT_MS
|
|
58324
|
+
});
|
|
58325
|
+
if (tracked2.code === 0) continue;
|
|
58326
|
+
await rm2(join23(dir, file2), { force: true });
|
|
58327
|
+
} catch {
|
|
58328
|
+
}
|
|
58329
|
+
}
|
|
58299
58330
|
}
|
|
58300
58331
|
async branch(dir) {
|
|
58301
58332
|
const r = await this.git(dir, ["symbolic-ref", "--short", "HEAD"]);
|
|
@@ -58646,9 +58677,9 @@ async function listWorktrees(repoPath) {
|
|
|
58646
58677
|
if (currentPath) await pushCurrent();
|
|
58647
58678
|
return worktrees2;
|
|
58648
58679
|
}
|
|
58649
|
-
async function getRepoInfo(worktreePath,
|
|
58680
|
+
async function getRepoInfo(worktreePath, run5 = execGit) {
|
|
58650
58681
|
try {
|
|
58651
|
-
const remoteUrl = (await
|
|
58682
|
+
const remoteUrl = (await run5(["remote", "get-url", "origin"], worktreePath)).trim();
|
|
58652
58683
|
const parsed = parseGitRemoteUrl(remoteUrl);
|
|
58653
58684
|
if (!parsed) {
|
|
58654
58685
|
log10.debug('getRepoInfo: failed to parse remote URL "%s" for %s', remoteUrl, worktreePath);
|
|
@@ -58827,76 +58858,10 @@ function killAllServers() {
|
|
|
58827
58858
|
worktreeServers.clear();
|
|
58828
58859
|
}
|
|
58829
58860
|
|
|
58830
|
-
// ../../packages/host-local/src/process/
|
|
58861
|
+
// ../../packages/host-local/src/process/capabilities.ts
|
|
58831
58862
|
import { execFile as execFile7 } from "node:child_process";
|
|
58832
|
-
import {
|
|
58833
|
-
import { join as join27 } from "node:path";
|
|
58834
|
-
var MAX_DU_BUFFER = 10 * 1024 * 1024;
|
|
58835
|
-
var DU_TIMEOUT_MS = 3e4;
|
|
58836
|
-
var STAT_CHUNK = 256;
|
|
58837
|
-
async function duBytesNodeWalk(path2) {
|
|
58838
|
-
const deadline = Date.now() + DU_TIMEOUT_MS;
|
|
58839
|
-
let total = 0;
|
|
58840
|
-
const stack = [path2];
|
|
58841
|
-
while (stack.length > 0) {
|
|
58842
|
-
if (Date.now() > deadline) break;
|
|
58843
|
-
const dir = stack.pop();
|
|
58844
|
-
try {
|
|
58845
|
-
const entries = await readdir4(dir, { withFileTypes: true });
|
|
58846
|
-
const filePaths = [];
|
|
58847
|
-
for (const entry of entries) {
|
|
58848
|
-
if (entry.isDirectory()) {
|
|
58849
|
-
stack.push(join27(dir, entry.name));
|
|
58850
|
-
} else if (entry.isFile()) {
|
|
58851
|
-
filePaths.push(join27(dir, entry.name));
|
|
58852
|
-
}
|
|
58853
|
-
}
|
|
58854
|
-
for (let i = 0; i < filePaths.length; i += STAT_CHUNK) {
|
|
58855
|
-
if (Date.now() > deadline) break;
|
|
58856
|
-
const sizes = await Promise.all(
|
|
58857
|
-
filePaths.slice(i, i + STAT_CHUNK).map(
|
|
58858
|
-
(p2) => stat4(p2).then(
|
|
58859
|
-
(s2) => s2.size,
|
|
58860
|
-
() => 0
|
|
58861
|
-
// vanished between readdir and stat — count as 0
|
|
58862
|
-
)
|
|
58863
|
-
)
|
|
58864
|
-
);
|
|
58865
|
-
for (const size of sizes) total += size;
|
|
58866
|
-
}
|
|
58867
|
-
} catch {
|
|
58868
|
-
}
|
|
58869
|
-
}
|
|
58870
|
-
return total;
|
|
58871
|
-
}
|
|
58872
|
-
async function duBytes(path2) {
|
|
58873
|
-
if (process.platform === "win32") {
|
|
58874
|
-
return duBytesNodeWalk(path2);
|
|
58875
|
-
}
|
|
58876
|
-
const stdout = await new Promise((resolve16, reject) => {
|
|
58877
|
-
execFile7(
|
|
58878
|
-
"du",
|
|
58879
|
-
["-sk", path2],
|
|
58880
|
-
{ maxBuffer: MAX_DU_BUFFER, timeout: DU_TIMEOUT_MS },
|
|
58881
|
-
(err, out) => {
|
|
58882
|
-
if (err && !out) {
|
|
58883
|
-
reject(err);
|
|
58884
|
-
return;
|
|
58885
|
-
}
|
|
58886
|
-
resolve16(out);
|
|
58887
|
-
}
|
|
58888
|
-
);
|
|
58889
|
-
});
|
|
58890
|
-
const kbStr = stdout.trim().split(/\s+/)[0];
|
|
58891
|
-
const kb = Number.parseInt(kbStr, 10);
|
|
58892
|
-
if (!Number.isFinite(kb)) {
|
|
58893
|
-
throw new Error(`du output not numeric: ${JSON.stringify(stdout)}`);
|
|
58894
|
-
}
|
|
58895
|
-
return kb * 1024;
|
|
58896
|
-
}
|
|
58897
|
-
|
|
58898
|
-
// ../../packages/host-local/src/process/tools.ts
|
|
58899
|
-
import { execFile as execFile8 } from "node:child_process";
|
|
58863
|
+
import { homedir as homedir12 } from "node:os";
|
|
58864
|
+
import { delimiter as delimiter5, join as join27 } from "node:path";
|
|
58900
58865
|
|
|
58901
58866
|
// ../../packages/environment/src/image.ts
|
|
58902
58867
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -59319,7 +59284,213 @@ ${start}
|
|
|
59319
59284
|
return install ?? start ?? null;
|
|
59320
59285
|
}
|
|
59321
59286
|
|
|
59287
|
+
// ../../packages/host-local/src/process/capabilities.ts
|
|
59288
|
+
var AGENT_PROBES = [
|
|
59289
|
+
{
|
|
59290
|
+
type: "claude-code",
|
|
59291
|
+
bin: "claude",
|
|
59292
|
+
login: ["auth", "status"],
|
|
59293
|
+
envKeys: ["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"],
|
|
59294
|
+
install: "npm install -g @anthropic-ai/claude-code",
|
|
59295
|
+
loginFix: "claude auth login"
|
|
59296
|
+
},
|
|
59297
|
+
{
|
|
59298
|
+
type: "codex",
|
|
59299
|
+
bin: "codex",
|
|
59300
|
+
login: ["login", "status"],
|
|
59301
|
+
envKeys: ["OPENAI_API_KEY"],
|
|
59302
|
+
install: "npm install -g @openai/codex",
|
|
59303
|
+
loginFix: "codex login"
|
|
59304
|
+
},
|
|
59305
|
+
{
|
|
59306
|
+
type: "opencode",
|
|
59307
|
+
bin: "opencode",
|
|
59308
|
+
login: null,
|
|
59309
|
+
envKeys: [],
|
|
59310
|
+
install: "npm install -g opencode-ai",
|
|
59311
|
+
loginFix: "opencode auth login"
|
|
59312
|
+
},
|
|
59313
|
+
{
|
|
59314
|
+
type: "gemini-cli",
|
|
59315
|
+
bin: "gemini",
|
|
59316
|
+
login: null,
|
|
59317
|
+
envKeys: ["GEMINI_API_KEY"],
|
|
59318
|
+
install: "npm install -g @google/gemini-cli",
|
|
59319
|
+
loginFix: "gemini"
|
|
59320
|
+
},
|
|
59321
|
+
{
|
|
59322
|
+
type: "cursor-cli",
|
|
59323
|
+
bin: "cursor-agent",
|
|
59324
|
+
login: null,
|
|
59325
|
+
envKeys: ["CURSOR_API_KEY"],
|
|
59326
|
+
install: "curl https://cursor.com/install -fsS | bash",
|
|
59327
|
+
loginFix: "cursor-agent login"
|
|
59328
|
+
}
|
|
59329
|
+
];
|
|
59330
|
+
var PROBE_TIMEOUT_MS = 8e3;
|
|
59331
|
+
function searchPath() {
|
|
59332
|
+
const extra = process.env.BAND_AGENT_BIN_DIRS?.trim();
|
|
59333
|
+
const home = homedir12();
|
|
59334
|
+
const installDirs = [
|
|
59335
|
+
".local/bin",
|
|
59336
|
+
".opencode/bin",
|
|
59337
|
+
".claude/local",
|
|
59338
|
+
".bun/bin",
|
|
59339
|
+
".npm-global/bin"
|
|
59340
|
+
].map((d) => join27(home, d));
|
|
59341
|
+
return [...extra ? [extra] : [], prependBinDirs(process.env.PATH), ...installDirs].join(
|
|
59342
|
+
delimiter5
|
|
59343
|
+
);
|
|
59344
|
+
}
|
|
59345
|
+
function run3(bin, args) {
|
|
59346
|
+
return new Promise((resolve16) => {
|
|
59347
|
+
execFile7(
|
|
59348
|
+
bin,
|
|
59349
|
+
args,
|
|
59350
|
+
{
|
|
59351
|
+
env: { ...process.env, PATH: searchPath() },
|
|
59352
|
+
timeout: PROBE_TIMEOUT_MS
|
|
59353
|
+
},
|
|
59354
|
+
(err, stdout, stderr) => {
|
|
59355
|
+
const notFound = err?.code === "ENOENT";
|
|
59356
|
+
resolve16({ ok: !err, found: !notFound, out: `${stdout}
|
|
59357
|
+
${stderr}` });
|
|
59358
|
+
}
|
|
59359
|
+
);
|
|
59360
|
+
});
|
|
59361
|
+
}
|
|
59362
|
+
async function probeAgent(probe) {
|
|
59363
|
+
const version2 = await run3(probe.bin, ["--version"]);
|
|
59364
|
+
if (!version2.found) {
|
|
59365
|
+
return {
|
|
59366
|
+
type: probe.type,
|
|
59367
|
+
installed: false,
|
|
59368
|
+
version: null,
|
|
59369
|
+
loggedIn: null,
|
|
59370
|
+
fix: probe.install
|
|
59371
|
+
};
|
|
59372
|
+
}
|
|
59373
|
+
let loggedIn = null;
|
|
59374
|
+
if (probe.envKeys.some((k2) => Boolean(process.env[k2]))) loggedIn = true;
|
|
59375
|
+
else if (probe.login) loggedIn = (await run3(probe.bin, probe.login)).ok;
|
|
59376
|
+
return {
|
|
59377
|
+
type: probe.type,
|
|
59378
|
+
installed: true,
|
|
59379
|
+
version: extractVersion(version2.out),
|
|
59380
|
+
loggedIn,
|
|
59381
|
+
fix: loggedIn === false ? probe.loginFix : ""
|
|
59382
|
+
};
|
|
59383
|
+
}
|
|
59384
|
+
async function probeGh() {
|
|
59385
|
+
const v2 = await run3("gh", ["--version"]);
|
|
59386
|
+
if (!v2.found) {
|
|
59387
|
+
return {
|
|
59388
|
+
tool: "gh",
|
|
59389
|
+
installed: false,
|
|
59390
|
+
version: null,
|
|
59391
|
+
loggedIn: null,
|
|
59392
|
+
fix: "install the GitHub CLI from https://cli.github.com"
|
|
59393
|
+
};
|
|
59394
|
+
}
|
|
59395
|
+
const auth2 = process.env.GH_TOKEN || process.env.GITHUB_TOKEN ? { ok: true } : await run3("gh", ["auth", "status"]);
|
|
59396
|
+
return {
|
|
59397
|
+
tool: "gh",
|
|
59398
|
+
installed: true,
|
|
59399
|
+
version: extractVersion(v2.out),
|
|
59400
|
+
loggedIn: auth2.ok,
|
|
59401
|
+
fix: auth2.ok ? "" : "gh auth login"
|
|
59402
|
+
};
|
|
59403
|
+
}
|
|
59404
|
+
async function probeGit() {
|
|
59405
|
+
const v2 = await run3("git", ["--version"]);
|
|
59406
|
+
return {
|
|
59407
|
+
tool: "git",
|
|
59408
|
+
installed: v2.found && v2.ok,
|
|
59409
|
+
version: v2.found ? extractVersion(v2.out) : null,
|
|
59410
|
+
loggedIn: null,
|
|
59411
|
+
fix: v2.found ? "" : "install git"
|
|
59412
|
+
};
|
|
59413
|
+
}
|
|
59414
|
+
async function probeCapabilities(agentTypes) {
|
|
59415
|
+
const probes = agentTypes ? AGENT_PROBES.filter((p2) => agentTypes.includes(p2.type)) : AGENT_PROBES;
|
|
59416
|
+
const [agents, gh, git3] = await Promise.all([
|
|
59417
|
+
Promise.all(probes.map(probeAgent)),
|
|
59418
|
+
probeGh(),
|
|
59419
|
+
probeGit()
|
|
59420
|
+
]);
|
|
59421
|
+
return { agents, tools: [git3, gh], checkedAt: Date.now() };
|
|
59422
|
+
}
|
|
59423
|
+
|
|
59424
|
+
// ../../packages/host-local/src/process/du.ts
|
|
59425
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
59426
|
+
import { readdir as readdir4, stat as stat4 } from "node:fs/promises";
|
|
59427
|
+
import { join as join28 } from "node:path";
|
|
59428
|
+
var MAX_DU_BUFFER = 10 * 1024 * 1024;
|
|
59429
|
+
var DU_TIMEOUT_MS = 3e4;
|
|
59430
|
+
var STAT_CHUNK = 256;
|
|
59431
|
+
async function duBytesNodeWalk(path2) {
|
|
59432
|
+
const deadline = Date.now() + DU_TIMEOUT_MS;
|
|
59433
|
+
let total = 0;
|
|
59434
|
+
const stack = [path2];
|
|
59435
|
+
while (stack.length > 0) {
|
|
59436
|
+
if (Date.now() > deadline) break;
|
|
59437
|
+
const dir = stack.pop();
|
|
59438
|
+
try {
|
|
59439
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
59440
|
+
const filePaths = [];
|
|
59441
|
+
for (const entry of entries) {
|
|
59442
|
+
if (entry.isDirectory()) {
|
|
59443
|
+
stack.push(join28(dir, entry.name));
|
|
59444
|
+
} else if (entry.isFile()) {
|
|
59445
|
+
filePaths.push(join28(dir, entry.name));
|
|
59446
|
+
}
|
|
59447
|
+
}
|
|
59448
|
+
for (let i = 0; i < filePaths.length; i += STAT_CHUNK) {
|
|
59449
|
+
if (Date.now() > deadline) break;
|
|
59450
|
+
const sizes = await Promise.all(
|
|
59451
|
+
filePaths.slice(i, i + STAT_CHUNK).map(
|
|
59452
|
+
(p2) => stat4(p2).then(
|
|
59453
|
+
(s2) => s2.size,
|
|
59454
|
+
() => 0
|
|
59455
|
+
// vanished between readdir and stat — count as 0
|
|
59456
|
+
)
|
|
59457
|
+
)
|
|
59458
|
+
);
|
|
59459
|
+
for (const size of sizes) total += size;
|
|
59460
|
+
}
|
|
59461
|
+
} catch {
|
|
59462
|
+
}
|
|
59463
|
+
}
|
|
59464
|
+
return total;
|
|
59465
|
+
}
|
|
59466
|
+
async function duBytes(path2) {
|
|
59467
|
+
if (process.platform === "win32") {
|
|
59468
|
+
return duBytesNodeWalk(path2);
|
|
59469
|
+
}
|
|
59470
|
+
const stdout = await new Promise((resolve16, reject) => {
|
|
59471
|
+
execFile8(
|
|
59472
|
+
"du",
|
|
59473
|
+
["-sk", path2],
|
|
59474
|
+
{ maxBuffer: MAX_DU_BUFFER, timeout: DU_TIMEOUT_MS },
|
|
59475
|
+
(err, out) => {
|
|
59476
|
+
if (err && !out) {
|
|
59477
|
+
reject(err);
|
|
59478
|
+
return;
|
|
59479
|
+
}
|
|
59480
|
+
resolve16(out);
|
|
59481
|
+
}
|
|
59482
|
+
);
|
|
59483
|
+
});
|
|
59484
|
+
const kbStr = stdout.trim().split(/\s+/)[0];
|
|
59485
|
+
const kb = Number.parseInt(kbStr, 10);
|
|
59486
|
+
if (!Number.isFinite(kb)) {
|
|
59487
|
+
throw new Error(`du output not numeric: ${JSON.stringify(stdout)}`);
|
|
59488
|
+
}
|
|
59489
|
+
return kb * 1024;
|
|
59490
|
+
}
|
|
59491
|
+
|
|
59322
59492
|
// ../../packages/host-local/src/process/tools.ts
|
|
59493
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
59323
59494
|
var PROBES = [
|
|
59324
59495
|
{ tool: "node", bin: "node", args: ["--version"] },
|
|
59325
59496
|
{ tool: "python", bin: "python3", args: ["--version"] },
|
|
@@ -59329,15 +59500,15 @@ var PROBES = [
|
|
|
59329
59500
|
{ tool: "docker", bin: "docker", args: ["--version"] },
|
|
59330
59501
|
{ tool: "git", bin: "git", args: ["--version"] }
|
|
59331
59502
|
];
|
|
59332
|
-
var
|
|
59503
|
+
var PROBE_TIMEOUT_MS2 = 5e3;
|
|
59333
59504
|
function versionOf(bin, args) {
|
|
59334
59505
|
return new Promise((resolve16) => {
|
|
59335
|
-
|
|
59506
|
+
execFile9(
|
|
59336
59507
|
bin,
|
|
59337
59508
|
args,
|
|
59338
59509
|
{
|
|
59339
59510
|
env: { ...process.env, PATH: prependBinDirs(process.env.PATH) },
|
|
59340
|
-
timeout:
|
|
59511
|
+
timeout: PROBE_TIMEOUT_MS2
|
|
59341
59512
|
},
|
|
59342
59513
|
(err, stdout, stderr) => resolve16(err ? null : extractVersion(`${stdout}
|
|
59343
59514
|
${stderr}`))
|
|
@@ -59355,13 +59526,14 @@ async function probeTools() {
|
|
|
59355
59526
|
|
|
59356
59527
|
// ../../packages/host-local/src/project/project-folder.ts
|
|
59357
59528
|
import { spawn as spawn5 } from "node:child_process";
|
|
59358
|
-
import { mkdir as mkdir4, open as open2, readdir as readdir5, realpath as realpath2, stat as stat5 } from "node:fs/promises";
|
|
59359
|
-
import { join as
|
|
59529
|
+
import { mkdir as mkdir4, open as open2, readdir as readdir5, realpath as realpath2, rm as rm3, stat as stat5 } from "node:fs/promises";
|
|
59530
|
+
import { join as join29, resolve as resolve6, sep as sep3 } from "node:path";
|
|
59360
59531
|
var PROJECT_NAME = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
59361
59532
|
var REPO_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
59362
59533
|
var BRANCH_NAME = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,199}$/;
|
|
59363
59534
|
var DEFAULT_FETCH_THROTTLE_MS = 6e4;
|
|
59364
59535
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
59536
|
+
var CLONE_TIMEOUT_MS = 10 * 6e4;
|
|
59365
59537
|
var LOCAL_TIMEOUT_MS2 = 3e4;
|
|
59366
59538
|
var DEFAULT_READ_BYTES = 2e5;
|
|
59367
59539
|
var MAX_READ_BYTES = 1e6;
|
|
@@ -59454,11 +59626,11 @@ var ProjectFolder = class {
|
|
|
59454
59626
|
locks = /* @__PURE__ */ new Map();
|
|
59455
59627
|
folderOf(project) {
|
|
59456
59628
|
checkProject(project);
|
|
59457
|
-
return
|
|
59629
|
+
return join29(this.bandHome(), "projects", project);
|
|
59458
59630
|
}
|
|
59459
59631
|
checkoutOf(project, repo) {
|
|
59460
59632
|
checkRepo({ name: repo });
|
|
59461
|
-
return
|
|
59633
|
+
return join29(this.folderOf(project), "repos", repo);
|
|
59462
59634
|
}
|
|
59463
59635
|
// ---- ensure ------------------------------------------------------------------
|
|
59464
59636
|
async ensure(req) {
|
|
@@ -59483,7 +59655,8 @@ var ProjectFolder = class {
|
|
|
59483
59655
|
});
|
|
59484
59656
|
}
|
|
59485
59657
|
async ensureCheckout(project, repo, fetchMode) {
|
|
59486
|
-
const branch =
|
|
59658
|
+
const branch = repo.defaultBranch;
|
|
59659
|
+
const legacyBranch = (() => {
|
|
59487
59660
|
try {
|
|
59488
59661
|
checkRepo(repo);
|
|
59489
59662
|
return projectBranch(project, repo.defaultBranch);
|
|
@@ -59491,7 +59664,7 @@ var ProjectFolder = class {
|
|
|
59491
59664
|
return projectBranch(project, "?");
|
|
59492
59665
|
}
|
|
59493
59666
|
})();
|
|
59494
|
-
const path2 =
|
|
59667
|
+
const path2 = join29(this.folderOf(project), "repos", repo.name);
|
|
59495
59668
|
const base = {
|
|
59496
59669
|
repo: repo.name,
|
|
59497
59670
|
path: path2,
|
|
@@ -59506,8 +59679,20 @@ var ProjectFolder = class {
|
|
|
59506
59679
|
checkRepo(repo);
|
|
59507
59680
|
let fetchError;
|
|
59508
59681
|
if (fetchMode !== "never") fetchError = await this.fetchClone(repo.clonePath, fetchMode);
|
|
59509
|
-
|
|
59510
|
-
|
|
59682
|
+
let legacyWorktree = false;
|
|
59683
|
+
let created = false;
|
|
59684
|
+
if (!await exists2(join29(path2, ".git"))) {
|
|
59685
|
+
await this.createCheckout(project, repo, path2);
|
|
59686
|
+
created = true;
|
|
59687
|
+
} else if (await isWorktreeCheckout(path2)) {
|
|
59688
|
+
legacyWorktree = !await this.convertWorktree(repo, path2, legacyBranch);
|
|
59689
|
+
if (!legacyWorktree) {
|
|
59690
|
+
await this.createCheckout(project, repo, path2);
|
|
59691
|
+
created = true;
|
|
59692
|
+
}
|
|
59693
|
+
}
|
|
59694
|
+
if (fetchMode !== "never" && !created && !legacyWorktree) {
|
|
59695
|
+
fetchError ??= await this.fetchClone(path2, fetchMode);
|
|
59511
59696
|
}
|
|
59512
59697
|
let state2 = await this.state(path2);
|
|
59513
59698
|
let updated = false;
|
|
@@ -59520,10 +59705,12 @@ var ProjectFolder = class {
|
|
|
59520
59705
|
const status = updated ? "updated" : state2.behind > 0 ? "behind" : state2.ahead > 0 || state2.dirty ? "ahead" : "current";
|
|
59521
59706
|
return {
|
|
59522
59707
|
...base,
|
|
59708
|
+
branch: legacyWorktree ? (await repoGit(path2, ["symbolic-ref", "--short", "HEAD"])).stdout.trim() || legacyBranch : branch,
|
|
59523
59709
|
status,
|
|
59524
59710
|
ahead: state2.ahead,
|
|
59525
59711
|
behind: state2.behind,
|
|
59526
59712
|
dirty: state2.dirty,
|
|
59713
|
+
...legacyWorktree ? { legacyWorktree } : {},
|
|
59527
59714
|
...fetchError ? { fetchError } : {}
|
|
59528
59715
|
};
|
|
59529
59716
|
} catch (err) {
|
|
@@ -59550,9 +59737,25 @@ var ProjectFolder = class {
|
|
|
59550
59737
|
this.lastFetchError.delete(clonePath);
|
|
59551
59738
|
return void 0;
|
|
59552
59739
|
}
|
|
59553
|
-
|
|
59554
|
-
|
|
59555
|
-
|
|
59740
|
+
/**
|
|
59741
|
+
* Removes a worktree an older Band made, with its local branch, when it has nothing the remote
|
|
59742
|
+
* lacks. Returns false and changes nothing when it is dirty or ahead.
|
|
59743
|
+
*/
|
|
59744
|
+
async convertWorktree(repo, path2, legacyBranch) {
|
|
59745
|
+
const state2 = await this.state(path2);
|
|
59746
|
+
if (state2.dirty || state2.ahead > 0) return false;
|
|
59747
|
+
const removed = await repoGit(repo.clonePath, ["worktree", "remove", path2]);
|
|
59748
|
+
if (removed.code !== 0) throw new Error(brief(removed));
|
|
59749
|
+
await repoGit(repo.clonePath, ["branch", "-D", legacyBranch]);
|
|
59750
|
+
return true;
|
|
59751
|
+
}
|
|
59752
|
+
/**
|
|
59753
|
+
* Clones the repo's origin into `path`, borrowing the objects of the repo's own clone and then
|
|
59754
|
+
* copying them (`--reference --dissociate`), so the checkout depends on nothing in that clone.
|
|
59755
|
+
* Without a usable reference it is a plain clone.
|
|
59756
|
+
*/
|
|
59757
|
+
async createCheckout(project, repo, path2) {
|
|
59758
|
+
await mkdir4(join29(this.folderOf(project), "repos"), { recursive: true });
|
|
59556
59759
|
const upstream = `origin/${repo.defaultBranch}`;
|
|
59557
59760
|
const hasUpstream = await repoGit(repo.clonePath, [
|
|
59558
59761
|
"rev-parse",
|
|
@@ -59563,16 +59766,31 @@ var ProjectFolder = class {
|
|
|
59563
59766
|
if (hasUpstream.code !== 0) {
|
|
59564
59767
|
throw new Error(`${repo.name} has no ${upstream}. Fetch the repository first.`);
|
|
59565
59768
|
}
|
|
59566
|
-
const
|
|
59567
|
-
|
|
59568
|
-
if (
|
|
59569
|
-
const
|
|
59570
|
-
|
|
59571
|
-
|
|
59572
|
-
|
|
59573
|
-
|
|
59574
|
-
|
|
59769
|
+
const origin = (await repoGit(repo.clonePath, ["remote", "get-url", "origin"])).stdout.trim();
|
|
59770
|
+
if (!origin) throw new Error(`${repo.name} has no origin remote`);
|
|
59771
|
+
if (origin.startsWith("-")) throw new Error(`${repo.name} has an invalid origin remote`);
|
|
59772
|
+
const existedBefore = await stat5(path2).then(
|
|
59773
|
+
() => true,
|
|
59774
|
+
() => false
|
|
59775
|
+
);
|
|
59776
|
+
const common2 = ["clone", "--quiet", "--branch", repo.defaultBranch, "--no-tags"];
|
|
59777
|
+
let cloned = await repoGit(
|
|
59778
|
+
join29(this.folderOf(project), "repos"),
|
|
59779
|
+
[...common2, "--reference", repo.clonePath, "--dissociate", "--", origin, path2],
|
|
59780
|
+
CLONE_TIMEOUT_MS
|
|
59781
|
+
);
|
|
59782
|
+
if (cloned.code !== 0) {
|
|
59783
|
+
if (existedBefore) throw new Error(brief(cloned));
|
|
59784
|
+
await rm3(path2, { recursive: true, force: true });
|
|
59785
|
+
cloned = await repoGit(
|
|
59786
|
+
join29(this.folderOf(project), "repos"),
|
|
59787
|
+
[...common2, "--", origin, path2],
|
|
59788
|
+
CLONE_TIMEOUT_MS
|
|
59789
|
+
);
|
|
59575
59790
|
}
|
|
59791
|
+
if (cloned.code !== 0) throw new Error(brief(cloned));
|
|
59792
|
+
const pushDefault = await repoGit(path2, ["config", "push.default", "upstream"]);
|
|
59793
|
+
if (pushDefault.code !== 0) throw new Error(brief(pushDefault));
|
|
59576
59794
|
}
|
|
59577
59795
|
async state(path2) {
|
|
59578
59796
|
const counts = await repoGit(path2, ["rev-list", "--left-right", "--count", "HEAD...@{u}"]);
|
|
@@ -59586,7 +59804,7 @@ var ProjectFolder = class {
|
|
|
59586
59804
|
/** The checkout of a repo, which must exist. */
|
|
59587
59805
|
async existingCheckout(project, repo) {
|
|
59588
59806
|
const path2 = this.checkoutOf(project, repo);
|
|
59589
|
-
if (!await exists2(
|
|
59807
|
+
if (!await exists2(join29(path2, ".git"))) {
|
|
59590
59808
|
throw new Error(`Repo "${repo}" has no checkout in project "${project}" on this host`);
|
|
59591
59809
|
}
|
|
59592
59810
|
return path2;
|
|
@@ -59885,7 +60103,7 @@ var ProjectFolder = class {
|
|
|
59885
60103
|
async removeRepo(req) {
|
|
59886
60104
|
const path2 = this.checkoutOf(req.project, req.repo);
|
|
59887
60105
|
await this.exclusive(req.project, async () => {
|
|
59888
|
-
if (!await exists2(
|
|
60106
|
+
if (!await exists2(join29(path2, ".git"))) {
|
|
59889
60107
|
await repoGit(req.clonePath, ["worktree", "prune"]);
|
|
59890
60108
|
return;
|
|
59891
60109
|
}
|
|
@@ -59900,10 +60118,14 @@ var ProjectFolder = class {
|
|
|
59900
60118
|
`The checkout of "${req.repo}" in ${path2} has ${reasons.join(" and ")}. Commit and push or discard them first.`
|
|
59901
60119
|
);
|
|
59902
60120
|
}
|
|
59903
|
-
|
|
59904
|
-
|
|
59905
|
-
|
|
59906
|
-
|
|
60121
|
+
if (await isWorktreeCheckout(path2)) {
|
|
60122
|
+
const removed = await repoGit(req.clonePath, ["worktree", "remove", path2]);
|
|
60123
|
+
if (removed.code !== 0) throw new Error(brief(removed));
|
|
60124
|
+
if (branch.startsWith(`band/${req.project}/`)) {
|
|
60125
|
+
await repoGit(req.clonePath, ["branch", "-D", branch]);
|
|
60126
|
+
}
|
|
60127
|
+
} else {
|
|
60128
|
+
await rm3(path2, { recursive: true, force: true });
|
|
59907
60129
|
}
|
|
59908
60130
|
});
|
|
59909
60131
|
}
|
|
@@ -59919,6 +60141,13 @@ var ProjectFolder = class {
|
|
|
59919
60141
|
}
|
|
59920
60142
|
}
|
|
59921
60143
|
};
|
|
60144
|
+
async function isWorktreeCheckout(path2) {
|
|
60145
|
+
try {
|
|
60146
|
+
return (await stat5(join29(path2, ".git"))).isFile();
|
|
60147
|
+
} catch {
|
|
60148
|
+
return false;
|
|
60149
|
+
}
|
|
60150
|
+
}
|
|
59922
60151
|
async function exists2(path2) {
|
|
59923
60152
|
try {
|
|
59924
60153
|
await stat5(path2);
|
|
@@ -59929,12 +60158,12 @@ async function exists2(path2) {
|
|
|
59929
60158
|
}
|
|
59930
60159
|
|
|
59931
60160
|
// ../../packages/host-local/src/repos/host-repos.ts
|
|
59932
|
-
import { execFile as
|
|
60161
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
59933
60162
|
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
59934
60163
|
import { existsSync as existsSync9 } from "node:fs";
|
|
59935
60164
|
import { mkdir as mkdir5, readdir as readdir6, readFile as readFile6, realpath as realpath3, rename as rename2, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
|
|
59936
|
-
import { homedir as
|
|
59937
|
-
import { dirname as dirname8, join as
|
|
60165
|
+
import { homedir as homedir13 } from "node:os";
|
|
60166
|
+
import { dirname as dirname8, join as join30, sep as sep4 } from "node:path";
|
|
59938
60167
|
|
|
59939
60168
|
// ../../packages/shared/src/remote-url.ts
|
|
59940
60169
|
function stripGitSuffix(path2) {
|
|
@@ -60012,9 +60241,9 @@ function stripUrlCredentials(input) {
|
|
|
60012
60241
|
}
|
|
60013
60242
|
|
|
60014
60243
|
// ../../packages/host-local/src/repos/host-repos.ts
|
|
60015
|
-
var
|
|
60244
|
+
var CLONE_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
60016
60245
|
function defaultReposDir() {
|
|
60017
|
-
return process.env.BAND_REPOS_DIR?.trim() ||
|
|
60246
|
+
return process.env.BAND_REPOS_DIR?.trim() || join30(homedir13(), "band", "repos");
|
|
60018
60247
|
}
|
|
60019
60248
|
async function git(args, cwd) {
|
|
60020
60249
|
try {
|
|
@@ -60027,10 +60256,10 @@ function clone2(url2, dest) {
|
|
|
60027
60256
|
const env = { ...process.env, GIT_TERMINAL_PROMPT: "0" };
|
|
60028
60257
|
env.PATH = prependBinDirs(env.PATH);
|
|
60029
60258
|
return new Promise((resolve16, reject) => {
|
|
60030
|
-
|
|
60259
|
+
execFile10(
|
|
60031
60260
|
"git",
|
|
60032
60261
|
["clone", "--", url2, dest],
|
|
60033
|
-
{ env, timeout:
|
|
60262
|
+
{ env, timeout: CLONE_TIMEOUT_MS2, maxBuffer: 10 * 1024 * 1024 },
|
|
60034
60263
|
(err, _stdout, stderr) => {
|
|
60035
60264
|
if (err)
|
|
60036
60265
|
reject(
|
|
@@ -60123,11 +60352,11 @@ var LocalRepos = class {
|
|
|
60123
60352
|
}
|
|
60124
60353
|
const running = this.ensuring.get(id.key);
|
|
60125
60354
|
if (running) return running;
|
|
60126
|
-
const
|
|
60355
|
+
const run5 = this.doEnsure(id.key, id.url, id.owner, id.name).finally(
|
|
60127
60356
|
() => this.ensuring.delete(id.key)
|
|
60128
60357
|
);
|
|
60129
|
-
this.ensuring.set(id.key,
|
|
60130
|
-
return
|
|
60358
|
+
this.ensuring.set(id.key, run5);
|
|
60359
|
+
return run5;
|
|
60131
60360
|
}
|
|
60132
60361
|
async doEnsure(key2, url2, owner, name5) {
|
|
60133
60362
|
const mappings = await this.load();
|
|
@@ -60135,7 +60364,7 @@ var LocalRepos = class {
|
|
|
60135
60364
|
if (mapped && existsSync9(mapped.path) && await isDirectory(mapped.path)) {
|
|
60136
60365
|
return { path: mapped.path, cloned: false };
|
|
60137
60366
|
}
|
|
60138
|
-
const base =
|
|
60367
|
+
const base = join30(this.options.reposDir(), owner, name5);
|
|
60139
60368
|
let dest = base;
|
|
60140
60369
|
if (existsSync9(dest)) {
|
|
60141
60370
|
const origin = await git(["remote", "get-url", "origin"], dest);
|
|
@@ -60155,7 +60384,7 @@ var LocalRepos = class {
|
|
|
60155
60384
|
}
|
|
60156
60385
|
};
|
|
60157
60386
|
async function browseFolder(path2) {
|
|
60158
|
-
const home =
|
|
60387
|
+
const home = homedir13();
|
|
60159
60388
|
const target = await realpath3(path2?.trim() ? path2 : home).catch(() => {
|
|
60160
60389
|
throw new Error(`${path2} does not exist`);
|
|
60161
60390
|
});
|
|
@@ -60163,11 +60392,11 @@ async function browseFolder(path2) {
|
|
|
60163
60392
|
const dirents = await readdir6(target, { withFileTypes: true });
|
|
60164
60393
|
const entries = [];
|
|
60165
60394
|
for (const d of dirents) {
|
|
60166
|
-
const full =
|
|
60395
|
+
const full = join30(target, d.name);
|
|
60167
60396
|
let isDir = d.isDirectory();
|
|
60168
60397
|
if (d.isSymbolicLink()) isDir = await isDirectory(full);
|
|
60169
60398
|
if (!isDir) continue;
|
|
60170
|
-
entries.push({ name: d.name, path: full, isGit: existsSync9(
|
|
60399
|
+
entries.push({ name: d.name, path: full, isGit: existsSync9(join30(full, ".git")) });
|
|
60171
60400
|
}
|
|
60172
60401
|
entries.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
60173
60402
|
const parent = dirname8(target);
|
|
@@ -60181,7 +60410,7 @@ async function browseFolder(path2) {
|
|
|
60181
60410
|
}
|
|
60182
60411
|
|
|
60183
60412
|
// ../../packages/host-local/src/search/ripgrep-client.ts
|
|
60184
|
-
import { execFile as
|
|
60413
|
+
import { execFile as execFile11, spawn as spawn6 } from "node:child_process";
|
|
60185
60414
|
import { rgPath } from "@vscode/ripgrep";
|
|
60186
60415
|
function streamMatches(options) {
|
|
60187
60416
|
return {
|
|
@@ -60303,7 +60532,7 @@ function listFiles(cwd) {
|
|
|
60303
60532
|
"!**/.DS_Store"
|
|
60304
60533
|
];
|
|
60305
60534
|
return new Promise((resolve16, reject) => {
|
|
60306
|
-
|
|
60535
|
+
execFile11(
|
|
60307
60536
|
rgPath,
|
|
60308
60537
|
args,
|
|
60309
60538
|
{ cwd, maxBuffer: LIST_FILES_MAX_BUFFER, encoding: "utf-8" },
|
|
@@ -60320,13 +60549,13 @@ function listFiles(cwd) {
|
|
|
60320
60549
|
}
|
|
60321
60550
|
|
|
60322
60551
|
// ../../packages/host-local/src/setup/repo-config.ts
|
|
60323
|
-
import { join as
|
|
60552
|
+
import { join as join31 } from "node:path";
|
|
60324
60553
|
init_src();
|
|
60325
60554
|
init_zod();
|
|
60326
60555
|
var log12 = createLogger("repo-config");
|
|
60327
60556
|
async function loadRepoConfig(host, worktreePath, repoPath) {
|
|
60328
60557
|
for (const base of [worktreePath, repoPath]) {
|
|
60329
|
-
const text3 = await readConfig(host,
|
|
60558
|
+
const text3 = await readConfig(host, join31(base, ".band", "config.json"));
|
|
60330
60559
|
if (text3 === null) continue;
|
|
60331
60560
|
try {
|
|
60332
60561
|
return JSON.parse(text3);
|
|
@@ -60337,10 +60566,10 @@ async function loadRepoConfig(host, worktreePath, repoPath) {
|
|
|
60337
60566
|
}
|
|
60338
60567
|
async function loadEnvironment(host, worktreePath, repoPath) {
|
|
60339
60568
|
for (const base of [worktreePath, repoPath]) {
|
|
60340
|
-
const file2 =
|
|
60569
|
+
const file2 = join31(base, ENVIRONMENT_FILE);
|
|
60341
60570
|
const text3 = await readConfig(host, file2);
|
|
60342
60571
|
if (text3 === null) continue;
|
|
60343
|
-
const exists4 = (relative3) => host.fs.stat(
|
|
60572
|
+
const exists4 = (relative3) => host.fs.stat(join31(base, relative3)).then(
|
|
60344
60573
|
() => true,
|
|
60345
60574
|
() => false
|
|
60346
60575
|
);
|
|
@@ -60372,7 +60601,7 @@ async function readConfig(host, configPath) {
|
|
|
60372
60601
|
}
|
|
60373
60602
|
var CopyFilesSchema = external_exports2.array(external_exports2.string()).optional();
|
|
60374
60603
|
async function getCopyFiles(host, repoPath) {
|
|
60375
|
-
const configPath =
|
|
60604
|
+
const configPath = join31(repoPath, ".band", "config.json");
|
|
60376
60605
|
const text3 = await readConfig(host, configPath);
|
|
60377
60606
|
if (text3 === null) return null;
|
|
60378
60607
|
let raw;
|
|
@@ -60397,7 +60626,7 @@ async function getCopyFiles(host, repoPath) {
|
|
|
60397
60626
|
|
|
60398
60627
|
// ../../packages/host-local/src/setup/script-run.ts
|
|
60399
60628
|
init_src();
|
|
60400
|
-
import { join as
|
|
60629
|
+
import { join as join32 } from "node:path";
|
|
60401
60630
|
var log13 = createLogger("script-run");
|
|
60402
60631
|
var POLL_MS = 1e3;
|
|
60403
60632
|
var decoder = new TextDecoder();
|
|
@@ -60406,8 +60635,8 @@ function quote(value) {
|
|
|
60406
60635
|
}
|
|
60407
60636
|
async function prepareScriptRun(host, script, label2) {
|
|
60408
60637
|
const dir = await host.fs.mkdtemp("band-script-");
|
|
60409
|
-
const scriptFile =
|
|
60410
|
-
const exitFile =
|
|
60638
|
+
const scriptFile = join32(dir, `${label2}.sh`);
|
|
60639
|
+
const exitFile = join32(dir, "exit-code");
|
|
60411
60640
|
const partialFile = `${exitFile}.partial`;
|
|
60412
60641
|
const onExit = [
|
|
60413
60642
|
"rc=$?",
|
|
@@ -60467,7 +60696,7 @@ async function prepareScriptRun(host, script, label2) {
|
|
|
60467
60696
|
|
|
60468
60697
|
// ../../packages/host-local/src/setup/worktree-files.ts
|
|
60469
60698
|
init_src();
|
|
60470
|
-
import { dirname as dirname9, isAbsolute, join as
|
|
60699
|
+
import { dirname as dirname9, isAbsolute, join as join33, relative, resolve as resolve7, sep as sep5 } from "node:path";
|
|
60471
60700
|
var log14 = createLogger("worktree-files");
|
|
60472
60701
|
var MAX_COPY_FILES_ENTRIES = 100;
|
|
60473
60702
|
var MAX_COPY_FILES_MATCHES = 500;
|
|
@@ -60498,7 +60727,7 @@ async function copyWorktreeFiles(host, repoPath, worktreePath) {
|
|
|
60498
60727
|
}
|
|
60499
60728
|
for (const [absSource, source] of byAbs) {
|
|
60500
60729
|
const rel = relative(repoPath, absSource);
|
|
60501
|
-
const dest =
|
|
60730
|
+
const dest = join33(worktreePath, rel);
|
|
60502
60731
|
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
60503
60732
|
log14.warn({ source, entry: absSource }, "refusing to copy file outside repo root \u2014 skipping");
|
|
60504
60733
|
continue;
|
|
@@ -60587,7 +60816,7 @@ async function resolveFromConfig(host, repoPath, missing) {
|
|
|
60587
60816
|
return out;
|
|
60588
60817
|
}
|
|
60589
60818
|
async function resolveFromWorktreeInclude(host, repoPath) {
|
|
60590
|
-
const includePath =
|
|
60819
|
+
const includePath = join33(repoPath, ".worktreeinclude");
|
|
60591
60820
|
let includeContent;
|
|
60592
60821
|
try {
|
|
60593
60822
|
includeContent = new TextDecoder().decode(await host.fs.readFile(includePath));
|
|
@@ -60629,14 +60858,15 @@ function hasGlobMeta(pattern) {
|
|
|
60629
60858
|
|
|
60630
60859
|
// ../../packages/host-local/src/local-host.ts
|
|
60631
60860
|
var TOOLS_TTL_MS = 6e4;
|
|
60861
|
+
var REPORT_TTL_MS = 2 * 6e4;
|
|
60632
60862
|
var EXEC_MAX_BUFFER = 50 * 1024 * 1024;
|
|
60633
60863
|
var LOCAL_HOST_ID = "local";
|
|
60634
60864
|
var LocalHost = class {
|
|
60635
60865
|
constructor(options) {
|
|
60636
60866
|
this.options = options;
|
|
60637
|
-
const bandHome2 = () => process.env.BAND_HOME ??
|
|
60867
|
+
const bandHome2 = () => process.env.BAND_HOME ?? join34(homedir14(), ".band");
|
|
60638
60868
|
this.repos = new LocalRepos({
|
|
60639
|
-
mappingsFile: options.repos?.mappingsFile ?? (() =>
|
|
60869
|
+
mappingsFile: options.repos?.mappingsFile ?? (() => join34(bandHome2(), "repo-mappings.json")),
|
|
60640
60870
|
reposDir: options.repos?.reposDir ?? defaultReposDir
|
|
60641
60871
|
});
|
|
60642
60872
|
const source = options.context;
|
|
@@ -60700,7 +60930,7 @@ var LocalHost = class {
|
|
|
60700
60930
|
openStdio: (spec) => openMcpStdio(spec)
|
|
60701
60931
|
};
|
|
60702
60932
|
chromium = new ChromiumManager(
|
|
60703
|
-
() =>
|
|
60933
|
+
() => join34(process.env.BAND_HOME ?? join34(homedir14(), ".band"), "browser-profiles")
|
|
60704
60934
|
);
|
|
60705
60935
|
browser = {
|
|
60706
60936
|
open: (spec) => this.chromium.open(spec),
|
|
@@ -60787,7 +61017,31 @@ var LocalHost = class {
|
|
|
60787
61017
|
})();
|
|
60788
61018
|
return this.cliFacts;
|
|
60789
61019
|
}
|
|
61020
|
+
reportValue;
|
|
61021
|
+
reportAt = 0;
|
|
61022
|
+
reportProbe = null;
|
|
61023
|
+
/**
|
|
61024
|
+
* Probing the agent CLIs runs several processes and `claude auth status` can take seconds, so
|
|
61025
|
+
* `info()` never waits for it: it returns the last report and refreshes it in the background once
|
|
61026
|
+
* it is older than `REPORT_TTL_MS`. The first `info()` has no report yet.
|
|
61027
|
+
*/
|
|
61028
|
+
refreshReport() {
|
|
61029
|
+
if (this.reportProbe) return this.reportProbe;
|
|
61030
|
+
if (this.reportValue && Date.now() - this.reportAt <= REPORT_TTL_MS) return Promise.resolve();
|
|
61031
|
+
this.reportProbe = probeCapabilities().then((value) => {
|
|
61032
|
+
this.reportValue = value;
|
|
61033
|
+
this.reportAt = Date.now();
|
|
61034
|
+
}).catch(() => void 0).finally(() => {
|
|
61035
|
+
this.reportProbe = null;
|
|
61036
|
+
});
|
|
61037
|
+
return this.reportProbe;
|
|
61038
|
+
}
|
|
61039
|
+
/** Waits for the capability report to exist. A worker calls it once before its hello, which carries it. */
|
|
61040
|
+
async awaitReport() {
|
|
61041
|
+
await this.refreshReport();
|
|
61042
|
+
}
|
|
60790
61043
|
async info() {
|
|
61044
|
+
void this.refreshReport();
|
|
60791
61045
|
const { versions: probed, gh } = await this.probeCliFacts();
|
|
60792
61046
|
const versions = { ...probed };
|
|
60793
61047
|
return {
|
|
@@ -60795,11 +61049,12 @@ var LocalHost = class {
|
|
|
60795
61049
|
os: process.platform,
|
|
60796
61050
|
arch: process.arch,
|
|
60797
61051
|
hostname: hostname3(),
|
|
60798
|
-
home:
|
|
61052
|
+
home: homedir14(),
|
|
60799
61053
|
labels: [],
|
|
60800
61054
|
roots: [],
|
|
60801
61055
|
versions,
|
|
60802
61056
|
tools: await this.toolVersions(),
|
|
61057
|
+
report: this.reportValue,
|
|
60803
61058
|
repoMappings: await this.repos.list(),
|
|
60804
61059
|
capabilities: {
|
|
60805
61060
|
git: "git" in versions,
|
|
@@ -60817,7 +61072,7 @@ var LocalHost = class {
|
|
|
60817
61072
|
return new Promise((resolve16, reject) => {
|
|
60818
61073
|
const env = { ...process.env, ...options.env };
|
|
60819
61074
|
env.PATH = prependBinDirs(env.PATH);
|
|
60820
|
-
|
|
61075
|
+
execFile12(
|
|
60821
61076
|
bin,
|
|
60822
61077
|
args,
|
|
60823
61078
|
{ cwd: options.cwd, env, maxBuffer: EXEC_MAX_BUFFER, timeout: options.timeoutMs },
|
|
@@ -60872,7 +61127,7 @@ var localFs = {
|
|
|
60872
61127
|
for await (const match of fsGlob(pattern, { cwd })) matches2.push(match);
|
|
60873
61128
|
return matches2;
|
|
60874
61129
|
},
|
|
60875
|
-
mkdtemp: (prefix) => mkdtemp(
|
|
61130
|
+
mkdtemp: (prefix) => mkdtemp(join34(tmpdir2(), prefix)),
|
|
60876
61131
|
async list(path2) {
|
|
60877
61132
|
const entries = await readdir7(path2, { withFileTypes: true });
|
|
60878
61133
|
return entries.map((entry) => ({ name: entry.name, kind: kindOf(entry) }));
|
|
@@ -60880,7 +61135,7 @@ var localFs = {
|
|
|
60880
61135
|
async mkdir(path2, options) {
|
|
60881
61136
|
await mkdir6(path2, { recursive: options?.recursive });
|
|
60882
61137
|
},
|
|
60883
|
-
rm: (path2, options) =>
|
|
61138
|
+
rm: (path2, options) => rm4(path2, options),
|
|
60884
61139
|
rename: (from, to) => rename3(from, to),
|
|
60885
61140
|
// dereference: copy a symlink's target bytes, as copyFileSync did, so the copy never links back into the source tree.
|
|
60886
61141
|
copy: (from, to, options) => cp(from, to, {
|
|
@@ -61086,6 +61341,12 @@ function chatScope(chat) {
|
|
|
61086
61341
|
throw new Error("A chat has neither a worktree nor a project");
|
|
61087
61342
|
}
|
|
61088
61343
|
|
|
61344
|
+
// src/server/infra/host/local-host-enabled.ts
|
|
61345
|
+
function isLocalHostEnabled() {
|
|
61346
|
+
const value = process.env.BAND_LOCAL_HOST?.trim().toLowerCase();
|
|
61347
|
+
return !(value === "off" || value === "false" || value === "0");
|
|
61348
|
+
}
|
|
61349
|
+
|
|
61089
61350
|
// src/server/infra/host/registry.ts
|
|
61090
61351
|
var worktreeQueries = new WorktreeQueries();
|
|
61091
61352
|
var repoQueries2 = new RepoQueries();
|
|
@@ -61110,13 +61371,22 @@ var HostRegistry = class {
|
|
|
61110
61371
|
return host;
|
|
61111
61372
|
}
|
|
61112
61373
|
hostFor(worktreeId2) {
|
|
61374
|
+
if (projectIdOfScope(worktreeId2)) {
|
|
61375
|
+
const id = this.hostIdOfScope(worktreeId2);
|
|
61376
|
+
if (!id) throw new Error("The project has no host yet: it is waiting for a worker");
|
|
61377
|
+
return this.hostById(id);
|
|
61378
|
+
}
|
|
61113
61379
|
if (this.hosts.size === 1) return this.local;
|
|
61114
61380
|
return this.hostById(this.hostIdOfScope(worktreeId2) ?? this.local.id);
|
|
61115
61381
|
}
|
|
61116
61382
|
/** The host id of a worktree, or of a project chat's scope (the project's coordinator host). */
|
|
61117
61383
|
hostIdOfScope(scopeId) {
|
|
61118
61384
|
const projectId = projectIdOfScope(scopeId);
|
|
61119
|
-
if (projectId)
|
|
61385
|
+
if (projectId) {
|
|
61386
|
+
const placed = projectQueries.find(projectId)?.coordinatorHostId;
|
|
61387
|
+
if (placed && (placed !== this.local.id || isLocalHostEnabled())) return placed;
|
|
61388
|
+
return isLocalHostEnabled() ? this.local.id : null;
|
|
61389
|
+
}
|
|
61120
61390
|
return worktreeQueries.findHostId(scopeId);
|
|
61121
61391
|
}
|
|
61122
61392
|
/**
|
|
@@ -61156,7 +61426,7 @@ var hostRegistry = new HostRegistry(
|
|
|
61156
61426
|
// The hub is a host too. Its working copies sit in its own BAND_HOME and read the bare repos by path.
|
|
61157
61427
|
context: {
|
|
61158
61428
|
bandHome: () => bandHome(),
|
|
61159
|
-
remote: async (name5) => ({ url:
|
|
61429
|
+
remote: async (name5) => ({ url: join38(bandHome(), "context", `${name5}.git`) })
|
|
61160
61430
|
},
|
|
61161
61431
|
// `gh` is the GitHub plugin's tool: with that plugin disabled the host never runs it. The
|
|
61162
61432
|
// import is dynamic because the services import this registry.
|
|
@@ -61278,27 +61548,27 @@ function textChunk(row) {
|
|
|
61278
61548
|
}
|
|
61279
61549
|
function rowsToEvents(rows) {
|
|
61280
61550
|
const out = [];
|
|
61281
|
-
let
|
|
61551
|
+
let run5 = null;
|
|
61282
61552
|
const flush = () => {
|
|
61283
|
-
if (!
|
|
61553
|
+
if (!run5) return;
|
|
61284
61554
|
out.push({
|
|
61285
61555
|
type: "update",
|
|
61286
|
-
update: { ...
|
|
61287
|
-
eventId:
|
|
61288
|
-
createdAt:
|
|
61556
|
+
update: { ...run5.update, content: { type: "text", text: run5.text } },
|
|
61557
|
+
eventId: run5.lastId,
|
|
61558
|
+
createdAt: run5.createdAt
|
|
61289
61559
|
});
|
|
61290
|
-
|
|
61560
|
+
run5 = null;
|
|
61291
61561
|
};
|
|
61292
61562
|
for (const row of rows) {
|
|
61293
61563
|
const chunk = textChunk(row);
|
|
61294
|
-
if (chunk &&
|
|
61295
|
-
if (chunk.content.type === "text")
|
|
61296
|
-
|
|
61564
|
+
if (chunk && run5 && run5.update.sessionUpdate === chunk.sessionUpdate && (run5.update.messageId ?? null) === (chunk.messageId ?? null)) {
|
|
61565
|
+
if (chunk.content.type === "text") run5.text += chunk.content.text;
|
|
61566
|
+
run5.lastId = row.id;
|
|
61297
61567
|
continue;
|
|
61298
61568
|
}
|
|
61299
61569
|
flush();
|
|
61300
61570
|
if (chunk) {
|
|
61301
|
-
|
|
61571
|
+
run5 = {
|
|
61302
61572
|
update: chunk,
|
|
61303
61573
|
text: chunk.content.type === "text" ? chunk.content.text : "",
|
|
61304
61574
|
lastId: row.id,
|
|
@@ -61415,12 +61685,12 @@ function resolvePolicy(policy, coordinatorModel) {
|
|
|
61415
61685
|
// src/server/services/_utils/shared-dir.ts
|
|
61416
61686
|
import { readdirSync as readdirSync8 } from "node:fs";
|
|
61417
61687
|
import { mkdir as mkdir7 } from "node:fs/promises";
|
|
61418
|
-
import { join as
|
|
61688
|
+
import { join as join39 } from "node:path";
|
|
61419
61689
|
init_state();
|
|
61420
61690
|
async function openSharedDir(worktreeId2) {
|
|
61421
61691
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
61422
61692
|
if (host.id === hostRegistry.local.id) {
|
|
61423
|
-
const path3 =
|
|
61693
|
+
const path3 = join39(bandHome(), "shared", worktreeId2);
|
|
61424
61694
|
await mkdir7(path3, { recursive: true });
|
|
61425
61695
|
return {
|
|
61426
61696
|
path: path3,
|
|
@@ -61435,7 +61705,7 @@ async function openSharedDir(worktreeId2) {
|
|
|
61435
61705
|
}
|
|
61436
61706
|
const dirs = (await host.info()).dirs;
|
|
61437
61707
|
if (!dirs) throw new Error(`Host ${host.id} has no directory for shared files`);
|
|
61438
|
-
const path2 =
|
|
61708
|
+
const path2 = join39(dirs.shared, worktreeId2);
|
|
61439
61709
|
await host.fs.mkdir(path2, { recursive: true });
|
|
61440
61710
|
return {
|
|
61441
61711
|
path: path2,
|
|
@@ -61451,7 +61721,7 @@ async function openSharedDir(worktreeId2) {
|
|
|
61451
61721
|
async function agentExtraDirs(worktreeId2) {
|
|
61452
61722
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
61453
61723
|
if (host.id === hostRegistry.local.id) {
|
|
61454
|
-
return [
|
|
61724
|
+
return [join39(bandHome(), "uploads"), join39(bandHome(), "shared")];
|
|
61455
61725
|
}
|
|
61456
61726
|
const dirs = (await host.info()).dirs;
|
|
61457
61727
|
return dirs ? [dirs.uploads, dirs.shared] : [];
|
|
@@ -62013,13 +62283,13 @@ function parseBatchedCIResponse(data, aliases, defaultBranches) {
|
|
|
62013
62283
|
}
|
|
62014
62284
|
let aggregatedState = "success";
|
|
62015
62285
|
let aggregatedUrl = null;
|
|
62016
|
-
for (const
|
|
62286
|
+
for (const run5 of latestByWorkflow.values()) {
|
|
62017
62287
|
let runState;
|
|
62018
|
-
if (
|
|
62019
|
-
runState =
|
|
62020
|
-
} else if (
|
|
62288
|
+
if (run5.status === "IN_PROGRESS" || run5.status === "QUEUED") {
|
|
62289
|
+
runState = run5.status === "QUEUED" ? "pending" : "running";
|
|
62290
|
+
} else if (run5.conclusion === "FAILURE") {
|
|
62021
62291
|
runState = "failure";
|
|
62022
|
-
} else if (
|
|
62292
|
+
} else if (run5.conclusion === "CANCELLED") {
|
|
62023
62293
|
runState = "cancelled";
|
|
62024
62294
|
} else {
|
|
62025
62295
|
runState = "success";
|
|
@@ -62027,7 +62297,7 @@ function parseBatchedCIResponse(data, aliases, defaultBranches) {
|
|
|
62027
62297
|
const priority = statePriority(runState);
|
|
62028
62298
|
if (priority >= statePriority(aggregatedState)) {
|
|
62029
62299
|
aggregatedState = runState;
|
|
62030
|
-
aggregatedUrl =
|
|
62300
|
+
aggregatedUrl = run5.url;
|
|
62031
62301
|
}
|
|
62032
62302
|
}
|
|
62033
62303
|
results.set(alias, {
|
|
@@ -62055,14 +62325,14 @@ async function mapLimited(items, limit, fn) {
|
|
|
62055
62325
|
}
|
|
62056
62326
|
|
|
62057
62327
|
// src/server/services/github-poll-service.ts
|
|
62058
|
-
import { tmpdir as
|
|
62328
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
62059
62329
|
init_src();
|
|
62060
62330
|
|
|
62061
62331
|
// src/server/infra/subscriptions/github.ts
|
|
62062
62332
|
init_settings();
|
|
62063
62333
|
import { createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
62064
62334
|
import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
62065
|
-
import { dirname as dirname11, join as
|
|
62335
|
+
import { dirname as dirname11, join as join40 } from "node:path";
|
|
62066
62336
|
var GITHUB_HOOK_EVENTS = [
|
|
62067
62337
|
"pull_request",
|
|
62068
62338
|
"pull_request_review",
|
|
@@ -62077,7 +62347,7 @@ var SNIPPET_LIMIT = 200;
|
|
|
62077
62347
|
var githubPrKey = (repo, number4) => `github:pr:${repo}#${number4}`;
|
|
62078
62348
|
var githubCiKey = (repo, branch) => `github:ci:${repo}@${branch}`;
|
|
62079
62349
|
function secretPath() {
|
|
62080
|
-
return
|
|
62350
|
+
return join40(bandHome(), "github-webhook-secret");
|
|
62081
62351
|
}
|
|
62082
62352
|
function readWebhookSecret() {
|
|
62083
62353
|
const fromEnv = process.env.BAND_GITHUB_WEBHOOK_SECRET;
|
|
@@ -62213,10 +62483,10 @@ function parseGithubDelivery(eventType, delivery, payload, now = Date.now()) {
|
|
|
62213
62483
|
case "check_suite":
|
|
62214
62484
|
case "workflow_run": {
|
|
62215
62485
|
if (action !== "completed") return { type: "ignored" };
|
|
62216
|
-
const
|
|
62217
|
-
const suite = obj(
|
|
62218
|
-
const branch = str(
|
|
62219
|
-
const sha = str(
|
|
62486
|
+
const run5 = obj(body[eventType]);
|
|
62487
|
+
const suite = obj(run5?.check_suite);
|
|
62488
|
+
const branch = str(run5?.head_branch) || str(suite?.head_branch);
|
|
62489
|
+
const sha = str(run5?.head_sha) || str(suite?.head_sha);
|
|
62220
62490
|
if (!branch || !/^[0-9a-f]{7,64}$/i.test(sha)) return { type: "ignored" };
|
|
62221
62491
|
return { type: "check", trigger: { repo, branch, sha } };
|
|
62222
62492
|
}
|
|
@@ -62265,15 +62535,15 @@ function parseCheckRuns(output) {
|
|
|
62265
62535
|
const runs = parsed?.check_runs;
|
|
62266
62536
|
if (!Array.isArray(runs)) throw new Error("Unexpected check-runs response");
|
|
62267
62537
|
return runs.flatMap((r) => {
|
|
62268
|
-
const
|
|
62269
|
-
if (!
|
|
62538
|
+
const run5 = obj(r);
|
|
62539
|
+
if (!run5) return [];
|
|
62270
62540
|
return [
|
|
62271
62541
|
{
|
|
62272
|
-
name: str(
|
|
62273
|
-
status: str(
|
|
62274
|
-
conclusion: typeof
|
|
62275
|
-
url: str(
|
|
62276
|
-
headSha: str(
|
|
62542
|
+
name: str(run5.name) || "unnamed check",
|
|
62543
|
+
status: str(run5.status),
|
|
62544
|
+
conclusion: typeof run5.conclusion === "string" ? run5.conclusion : null,
|
|
62545
|
+
url: str(run5.html_url) || void 0,
|
|
62546
|
+
headSha: str(run5.head_sha) || void 0
|
|
62277
62547
|
}
|
|
62278
62548
|
];
|
|
62279
62549
|
});
|
|
@@ -62379,7 +62649,7 @@ function initialCursor(createdAt) {
|
|
|
62379
62649
|
}
|
|
62380
62650
|
|
|
62381
62651
|
// src/server/services/_utils/hub-gh-auth.ts
|
|
62382
|
-
import { tmpdir as
|
|
62652
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
62383
62653
|
|
|
62384
62654
|
// src/server/services/vault-service.ts
|
|
62385
62655
|
import { createHash as createHash9, randomUUID as randomUUID13, timingSafeEqual as timingSafeEqual6 } from "node:crypto";
|
|
@@ -62786,14 +63056,14 @@ function accountFromIdToken(idToken) {
|
|
|
62786
63056
|
// src/server/services/_utils/vault-crypto.ts
|
|
62787
63057
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
|
|
62788
63058
|
import { chmodSync as chmodSync3, existsSync as existsSync13, readFileSync as readFileSync9, renameSync as renameSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
62789
|
-
import { join as
|
|
63059
|
+
import { join as join41 } from "node:path";
|
|
62790
63060
|
init_settings();
|
|
62791
63061
|
var VERSION = "v1:";
|
|
62792
63062
|
var KEY_BYTES = 32;
|
|
62793
63063
|
var NONCE_BYTES = 12;
|
|
62794
63064
|
var TAG_BYTES = 16;
|
|
62795
63065
|
function vaultKeyFile() {
|
|
62796
|
-
return
|
|
63066
|
+
return join41(bandHome(), "vault.key");
|
|
62797
63067
|
}
|
|
62798
63068
|
function keySource() {
|
|
62799
63069
|
return process.env.BAND_VAULT_KEY?.trim() ? "env" : "file";
|
|
@@ -62853,7 +63123,7 @@ function decrypt(key2, aad, blob2) {
|
|
|
62853
63123
|
}
|
|
62854
63124
|
|
|
62855
63125
|
// src/server/services/github-webhook-service.ts
|
|
62856
|
-
import { tmpdir as
|
|
63126
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
62857
63127
|
init_src();
|
|
62858
63128
|
|
|
62859
63129
|
// src/server/services/pushed-sha-service.ts
|
|
@@ -64034,7 +64304,7 @@ init_state();
|
|
|
64034
64304
|
init_src2();
|
|
64035
64305
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
64036
64306
|
import { unlinkSync as unlinkSync4 } from "node:fs";
|
|
64037
|
-
import { join as
|
|
64307
|
+
import { join as join48, posix as posix6 } from "node:path";
|
|
64038
64308
|
|
|
64039
64309
|
// ../../packages/host-local/src/git/git-refusals.ts
|
|
64040
64310
|
var MAX_LISTED_FILES = 3;
|
|
@@ -64439,7 +64709,7 @@ var placementInput = external_exports2.object({
|
|
|
64439
64709
|
});
|
|
64440
64710
|
|
|
64441
64711
|
// src/server/services/_utils/write-brief.ts
|
|
64442
|
-
import { dirname as dirname12, join as
|
|
64712
|
+
import { dirname as dirname12, join as join42, posix as posix3 } from "node:path";
|
|
64443
64713
|
|
|
64444
64714
|
// src/server/services/_utils/dispatch-brief.ts
|
|
64445
64715
|
var BRIEF_DIR = ".am";
|
|
@@ -64478,7 +64748,7 @@ ${input.scenarios.map((s2, i) => `- S${i + 1}: ${s2.trim()}`).join("\n")}`
|
|
|
64478
64748
|
|
|
64479
64749
|
// src/server/services/_utils/write-brief.ts
|
|
64480
64750
|
async function writeBrief(host, worktreePath, brief2, remote) {
|
|
64481
|
-
const p2 = remote ? posix3 : { join:
|
|
64751
|
+
const p2 = remote ? posix3 : { join: join42 };
|
|
64482
64752
|
const dir = p2.join(worktreePath, BRIEF_DIR);
|
|
64483
64753
|
await host.fs.mkdir(dir, { recursive: true });
|
|
64484
64754
|
await host.fs.writeFile(p2.join(dir, BRIEF_FILE), brief2);
|
|
@@ -64588,7 +64858,7 @@ function removeTerminalFromLayout(worktreeId2, terminalId) {
|
|
|
64588
64858
|
// src/server/services/ephemeral-lifecycle-service.ts
|
|
64589
64859
|
import { rmSync as rmSync4 } from "node:fs";
|
|
64590
64860
|
import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile5 } from "node:fs/promises";
|
|
64591
|
-
import { join as
|
|
64861
|
+
import { join as join44, posix as posix5 } from "node:path";
|
|
64592
64862
|
|
|
64593
64863
|
// ../../packages/host-remote/src/pty.ts
|
|
64594
64864
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -66299,6 +66569,7 @@ init_repos();
|
|
|
66299
66569
|
init_isolation();
|
|
66300
66570
|
init_runner_config();
|
|
66301
66571
|
init_settings_service();
|
|
66572
|
+
init_state();
|
|
66302
66573
|
|
|
66303
66574
|
// src/server/services/token-service.ts
|
|
66304
66575
|
init_src();
|
|
@@ -66390,6 +66661,10 @@ var TokenQueries = class _TokenQueries {
|
|
|
66390
66661
|
markHostOnline(id, at, fields) {
|
|
66391
66662
|
this.db().update(hosts).set({ status: "online", lastSeenAt: at, info: fields.info, version: fields.version }).where(eq(hosts.id, id)).run();
|
|
66392
66663
|
}
|
|
66664
|
+
/** Replaces what a host reported about itself, without touching its status. */
|
|
66665
|
+
setHostInfo(id, info) {
|
|
66666
|
+
this.db().update(hosts).set({ info }).where(eq(hosts.id, id)).run();
|
|
66667
|
+
}
|
|
66393
66668
|
/** Sets a host's status and, when the worker was last heard from, its last-seen time. */
|
|
66394
66669
|
setHostStatus(id, status, lastSeenAt) {
|
|
66395
66670
|
this.db().update(hosts).set(lastSeenAt === void 0 ? { status } : { status, lastSeenAt }).where(eq(hosts.id, id)).run();
|
|
@@ -66412,12 +66687,6 @@ var TokenQueries = class _TokenQueries {
|
|
|
66412
66687
|
}
|
|
66413
66688
|
};
|
|
66414
66689
|
|
|
66415
|
-
// src/server/infra/host/local-host-enabled.ts
|
|
66416
|
-
function isLocalHostEnabled() {
|
|
66417
|
-
const value = process.env.BAND_LOCAL_HOST?.trim().toLowerCase();
|
|
66418
|
-
return !(value === "off" || value === "false" || value === "0");
|
|
66419
|
-
}
|
|
66420
|
-
|
|
66421
66690
|
// src/server/services/token-service.ts
|
|
66422
66691
|
var log19 = createLogger("token-service");
|
|
66423
66692
|
var SHARED_TOKEN_ID = "shared";
|
|
@@ -66444,6 +66713,16 @@ function stringMap(value) {
|
|
|
66444
66713
|
Object.entries(value).filter((e) => typeof e[1] === "string")
|
|
66445
66714
|
);
|
|
66446
66715
|
}
|
|
66716
|
+
function reportOf(info) {
|
|
66717
|
+
const report2 = info?.report;
|
|
66718
|
+
return report2 && Array.isArray(report2.agents) && Array.isArray(report2.tools) ? report2 : null;
|
|
66719
|
+
}
|
|
66720
|
+
function usableAgents(info) {
|
|
66721
|
+
const report2 = reportOf(info);
|
|
66722
|
+
if (report2)
|
|
66723
|
+
return report2.agents.filter((a) => a.installed && a.loggedIn !== false).map((a) => a.type);
|
|
66724
|
+
return stringList(info?.agents);
|
|
66725
|
+
}
|
|
66447
66726
|
function stringList(value) {
|
|
66448
66727
|
return Array.isArray(value) ? value.filter((v2) => typeof v2 === "string") : [];
|
|
66449
66728
|
}
|
|
@@ -66707,7 +66986,8 @@ var TokenService = class {
|
|
|
66707
66986
|
version: h.version,
|
|
66708
66987
|
createdAt: h.createdAt,
|
|
66709
66988
|
info: h.info,
|
|
66710
|
-
agents:
|
|
66989
|
+
agents: usableAgents(h.info),
|
|
66990
|
+
report: reportOf(h.info),
|
|
66711
66991
|
roots: stringList(h.info?.roots),
|
|
66712
66992
|
capabilities: capabilityNames(h.info?.capabilities),
|
|
66713
66993
|
home: typeof h.info?.home === "string" ? h.info.home : null,
|
|
@@ -66889,6 +67169,7 @@ var PlacementService = class {
|
|
|
66889
67169
|
const tools = stringRecord(info?.tools ?? stored.tools);
|
|
66890
67170
|
out.push({
|
|
66891
67171
|
id: row.id,
|
|
67172
|
+
agents: row.agents,
|
|
66892
67173
|
labels: [
|
|
66893
67174
|
.../* @__PURE__ */ new Set([...stringList2(row.labels), ...stringList2(info?.labels ?? stored.labels)])
|
|
66894
67175
|
],
|
|
@@ -66909,10 +67190,12 @@ var PlacementService = class {
|
|
|
66909
67190
|
* a host, and a `worktree` worktree never lands on a host that was started
|
|
66910
67191
|
* for one of those.
|
|
66911
67192
|
*/
|
|
66912
|
-
async place(placement, onlyHosts) {
|
|
67193
|
+
async place(placement, onlyHosts, agent) {
|
|
66913
67194
|
if (requestedIsolation(placement.environment ?? null) !== "worktree") return null;
|
|
66914
67195
|
const fits = (await this.candidates()).filter(
|
|
66915
|
-
(c) => !isExclusiveHost(c.labels) && (onlyHosts === void 0 || onlyHosts.includes(c.id)) &&
|
|
67196
|
+
(c) => !isExclusiveHost(c.labels) && (onlyHosts === void 0 || onlyHosts.includes(c.id)) && // A worker must report the agent installed and logged in. The hub's own machine is
|
|
67197
|
+
// described live elsewhere, so it is not held to a report here.
|
|
67198
|
+
(agent === void 0 || c.id === LOCAL_HOST_ID2 || c.agents.includes(agent)) && matches(c, placement)
|
|
66916
67199
|
);
|
|
66917
67200
|
if (fits.length === 0) return null;
|
|
66918
67201
|
const load = this.queries.worktreeCounts();
|
|
@@ -66938,7 +67221,8 @@ var PlacementService = class {
|
|
|
66938
67221
|
const open4 = this.queries.findOpenForWorktree(worktreeId2);
|
|
66939
67222
|
if (open4) return { kind: "request", requestId: open4.id };
|
|
66940
67223
|
const holders = this.holdersOfRemotelessRepo(input.repo);
|
|
66941
|
-
const
|
|
67224
|
+
const agent = input.codingAgentId ? loadSettings().codingAgents?.find((a) => a.id === input.codingAgentId)?.type ?? input.codingAgentId : void 0;
|
|
67225
|
+
const hostId2 = await this.place(placement, holders ?? void 0, agent);
|
|
66942
67226
|
if (hostId2) return { kind: "host", hostId: hostId2 };
|
|
66943
67227
|
if (holders) {
|
|
66944
67228
|
throw new Error(
|
|
@@ -67195,7 +67479,7 @@ var placementService = new PlacementService();
|
|
|
67195
67479
|
import { spawn as spawn8 } from "node:child_process";
|
|
67196
67480
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
67197
67481
|
import { appendFileSync, mkdirSync as mkdirSync7, readFileSync as readFileSync10, rmSync as rmSync3, statSync as statSync11 } from "node:fs";
|
|
67198
|
-
import { dirname as dirname13, join as
|
|
67482
|
+
import { dirname as dirname13, join as join43 } from "node:path";
|
|
67199
67483
|
init_src();
|
|
67200
67484
|
|
|
67201
67485
|
// src/server/infra/db/queries/runner-machines.ts
|
|
@@ -68189,7 +68473,7 @@ var RunnerService = class {
|
|
|
68189
68473
|
}
|
|
68190
68474
|
}
|
|
68191
68475
|
logFile(requestId2) {
|
|
68192
|
-
return
|
|
68476
|
+
return join43(bandHome(), "runners", "logs", `${requestId2}.log`);
|
|
68193
68477
|
}
|
|
68194
68478
|
/** Every valid runner in settings. */
|
|
68195
68479
|
configs() {
|
|
@@ -68205,12 +68489,12 @@ var RunnerService = class {
|
|
|
68205
68489
|
}
|
|
68206
68490
|
/** Whether the worker id belongs to an attempt in flight. */
|
|
68207
68491
|
isAttempting(workerId) {
|
|
68208
|
-
for (const
|
|
68492
|
+
for (const run5 of this.running.values()) if (run5.workerId === workerId) return true;
|
|
68209
68493
|
return false;
|
|
68210
68494
|
}
|
|
68211
68495
|
runningCount(runnerId2) {
|
|
68212
68496
|
let n = 0;
|
|
68213
|
-
for (const
|
|
68497
|
+
for (const run5 of this.running.values()) if (run5.runnerId === runnerId2) n++;
|
|
68214
68498
|
return n;
|
|
68215
68499
|
}
|
|
68216
68500
|
// ---- leasing -------------------------------------------------------------
|
|
@@ -68246,7 +68530,7 @@ var RunnerService = class {
|
|
|
68246
68530
|
}
|
|
68247
68531
|
}
|
|
68248
68532
|
begin(runner, row) {
|
|
68249
|
-
const
|
|
68533
|
+
const run5 = {
|
|
68250
68534
|
requestId: row.id,
|
|
68251
68535
|
runnerId: runner.id,
|
|
68252
68536
|
worktreeId: row.worktreeId,
|
|
@@ -68257,27 +68541,27 @@ var RunnerService = class {
|
|
|
68257
68541
|
endedAt: null,
|
|
68258
68542
|
error: null
|
|
68259
68543
|
};
|
|
68260
|
-
this.running.set(row.id,
|
|
68261
|
-
const done = this.run(runner, row,
|
|
68544
|
+
this.running.set(row.id, run5);
|
|
68545
|
+
const done = this.run(runner, row, run5).catch(
|
|
68262
68546
|
(err) => log22.error(`run of ${row.id} crashed: ${err instanceof Error ? err.stack : err}`)
|
|
68263
68547
|
).finally(() => {
|
|
68264
68548
|
this.running.delete(row.id);
|
|
68265
|
-
|
|
68266
|
-
this.history.push(
|
|
68549
|
+
run5.endedAt = Date.now();
|
|
68550
|
+
this.history.push(run5);
|
|
68267
68551
|
if (this.history.length > HISTORY_LIMIT) this.history.shift();
|
|
68268
68552
|
this.settled.delete(done);
|
|
68269
68553
|
});
|
|
68270
68554
|
this.settled.add(done);
|
|
68271
68555
|
}
|
|
68272
68556
|
// ---- one request ---------------------------------------------------------
|
|
68273
|
-
async run(runner, row,
|
|
68557
|
+
async run(runner, row, run5) {
|
|
68274
68558
|
const runLog = new RunLog(this.logFile(row.id));
|
|
68275
68559
|
runLog.write("hub", `runner ${runner.id} took request ${row.id} for ${row.worktreeId}`);
|
|
68276
68560
|
const environment = parseRequestEnvironment(row);
|
|
68277
68561
|
if (!environment.ok) {
|
|
68278
68562
|
runLog.write("hub", environment.error);
|
|
68279
|
-
|
|
68280
|
-
|
|
68563
|
+
run5.status = "failed";
|
|
68564
|
+
run5.error = environment.error;
|
|
68281
68565
|
try {
|
|
68282
68566
|
placementService.fail(row.id, environment.error);
|
|
68283
68567
|
} catch (err) {
|
|
@@ -68287,19 +68571,19 @@ var RunnerService = class {
|
|
|
68287
68571
|
}
|
|
68288
68572
|
let lastError = "";
|
|
68289
68573
|
for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
|
|
68290
|
-
|
|
68574
|
+
run5.attempt = attempt;
|
|
68291
68575
|
runLog.write("hub", `attempt ${attempt} of ${ATTEMPTS}`);
|
|
68292
68576
|
const attemptState = { hostId: null, token: null };
|
|
68293
68577
|
try {
|
|
68294
|
-
const hostId2 = await this.attempt(runner, row,
|
|
68295
|
-
|
|
68296
|
-
|
|
68578
|
+
const hostId2 = await this.attempt(runner, row, run5, runLog, attemptState);
|
|
68579
|
+
run5.workerId = hostId2;
|
|
68580
|
+
run5.status = "ready";
|
|
68297
68581
|
runLog.write("hub", `worker ${hostId2} said hello; request fulfilled`);
|
|
68298
68582
|
return;
|
|
68299
68583
|
} catch (err) {
|
|
68300
68584
|
if (err instanceof Aborted) {
|
|
68301
|
-
|
|
68302
|
-
|
|
68585
|
+
run5.status = "aborted";
|
|
68586
|
+
run5.error = err.message;
|
|
68303
68587
|
runLog.write("hub", `stopped: ${err.message}`);
|
|
68304
68588
|
await this.cleanup(runner, row, attemptState, runLog);
|
|
68305
68589
|
return;
|
|
@@ -68310,8 +68594,8 @@ var RunnerService = class {
|
|
|
68310
68594
|
}
|
|
68311
68595
|
}
|
|
68312
68596
|
const reason = this.failureReason(runner, lastError, runLog);
|
|
68313
|
-
|
|
68314
|
-
|
|
68597
|
+
run5.status = "failed";
|
|
68598
|
+
run5.error = lastError;
|
|
68315
68599
|
try {
|
|
68316
68600
|
placementService.fail(row.id, reason);
|
|
68317
68601
|
} catch (err) {
|
|
@@ -68384,13 +68668,13 @@ ${tail}` : head;
|
|
|
68384
68668
|
* runner holds a snapshot for tries `restore` first, and starts a fresh worker with `spawn` when
|
|
68385
68669
|
* that fails, so the git and session state the sleep stored is what brings the worktree back.
|
|
68386
68670
|
*/
|
|
68387
|
-
async attempt(runner, row,
|
|
68671
|
+
async attempt(runner, row, run5, runLog, state2) {
|
|
68388
68672
|
const wake = wakeOf(row);
|
|
68389
68673
|
const snapshot = wake ? this.restorableSnapshot(runner, wake.hostId) : void 0;
|
|
68390
68674
|
if (snapshot) {
|
|
68391
68675
|
this.restoring.add(snapshot.id);
|
|
68392
68676
|
try {
|
|
68393
|
-
return await this.launch(runner, row,
|
|
68677
|
+
return await this.launch(runner, row, run5, runLog, state2, "restore", snapshot);
|
|
68394
68678
|
} catch (err) {
|
|
68395
68679
|
if (err instanceof Aborted) throw err;
|
|
68396
68680
|
runLog.write(
|
|
@@ -68403,7 +68687,7 @@ ${tail}` : head;
|
|
|
68403
68687
|
this.restoring.delete(snapshot.id);
|
|
68404
68688
|
}
|
|
68405
68689
|
}
|
|
68406
|
-
return this.launch(runner, row,
|
|
68690
|
+
return this.launch(runner, row, run5, runLog, state2, "spawn");
|
|
68407
68691
|
}
|
|
68408
68692
|
/** The snapshot a wake can restore from: this runner's, still alive, and the runner has a `restore` hook. */
|
|
68409
68693
|
restorableSnapshot(runner, hostId2) {
|
|
@@ -68413,7 +68697,7 @@ ${tail}` : head;
|
|
|
68413
68697
|
return void 0;
|
|
68414
68698
|
return latest;
|
|
68415
68699
|
}
|
|
68416
|
-
async launch(runner, row,
|
|
68700
|
+
async launch(runner, row, run5, runLog, state2, kind, snapshot) {
|
|
68417
68701
|
const deadline = Date.now() + runner.timeoutSec * 1e3;
|
|
68418
68702
|
const labelList = Object.entries(row.labels).map(([k2, v2]) => `${k2}=${v2}`);
|
|
68419
68703
|
const wanted = requestedIsolation(row.environment);
|
|
@@ -68425,7 +68709,7 @@ ${tail}` : head;
|
|
|
68425
68709
|
state2.reused = wake !== null;
|
|
68426
68710
|
state2.token = issued.token;
|
|
68427
68711
|
runLog.addSecret(issued.token);
|
|
68428
|
-
|
|
68712
|
+
run5.workerId = issued.hostId;
|
|
68429
68713
|
runLog.write("hub", `issued worker ${issued.hostId}`);
|
|
68430
68714
|
const machineId = this.recordMachine(runner, row, issued.hostId);
|
|
68431
68715
|
state2.machineId = machineId;
|
|
@@ -68555,11 +68839,11 @@ ${tail}` : head;
|
|
|
68555
68839
|
destroyMachine(machineId, reason) {
|
|
68556
68840
|
const inflight = this.destroying.get(machineId);
|
|
68557
68841
|
if (inflight) return inflight;
|
|
68558
|
-
const
|
|
68842
|
+
const run5 = this.doDestroyMachine(machineId, reason).finally(
|
|
68559
68843
|
() => this.destroying.delete(machineId)
|
|
68560
68844
|
);
|
|
68561
|
-
this.destroying.set(machineId,
|
|
68562
|
-
return
|
|
68845
|
+
this.destroying.set(machineId, run5);
|
|
68846
|
+
return run5;
|
|
68563
68847
|
}
|
|
68564
68848
|
async doDestroyMachine(machineId, reason) {
|
|
68565
68849
|
const machine = this.machines.get(machineId);
|
|
@@ -68568,7 +68852,7 @@ ${tail}` : head;
|
|
|
68568
68852
|
const runner = this.findRunner(machine.runnerId);
|
|
68569
68853
|
const request = machine.requestId ? placementService.get(machine.requestId) : void 0;
|
|
68570
68854
|
const runLog = new RunLog(
|
|
68571
|
-
machine.requestId ? this.logFile(machine.requestId) :
|
|
68855
|
+
machine.requestId ? this.logFile(machine.requestId) : join43(bandHome(), "runners", "logs", `${machine.id}.log`)
|
|
68572
68856
|
);
|
|
68573
68857
|
runLog.write(
|
|
68574
68858
|
"hub",
|
|
@@ -68616,7 +68900,7 @@ ${tail}` : head;
|
|
|
68616
68900
|
/** Runs `destroy` for a machine the hub has no record of, named only by the handle `status` printed. */
|
|
68617
68901
|
async destroyHandle(runner, handle, reason) {
|
|
68618
68902
|
if (!runner.destroy) return false;
|
|
68619
|
-
const file2 =
|
|
68903
|
+
const file2 = join43(bandHome(), "runners", "logs", "reaper.log");
|
|
68620
68904
|
try {
|
|
68621
68905
|
if (statSync11(file2).size > LOG_FILE_LIMIT) rmSync3(file2, { force: true });
|
|
68622
68906
|
} catch {
|
|
@@ -68838,7 +69122,7 @@ ${tail}` : head;
|
|
|
68838
69122
|
// The repo's current environment image (plan step 3.2), empty before its first ready build.
|
|
68839
69123
|
BAND_REPO_IMAGE: row ? this.repoImage(row.repo) : "",
|
|
68840
69124
|
BAND_RUNNER_ID: runner.id,
|
|
68841
|
-
BAND_RUNNER_DIR:
|
|
69125
|
+
BAND_RUNNER_DIR: join43(bandHome(), "runners", runner.id),
|
|
68842
69126
|
BAND_NODE: process.execPath
|
|
68843
69127
|
});
|
|
68844
69128
|
if (runner.desktop) env.BAND_DESKTOP = "1";
|
|
@@ -68864,7 +69148,7 @@ ${tail}` : head;
|
|
|
68864
69148
|
*/
|
|
68865
69149
|
runHook(opts) {
|
|
68866
69150
|
const { runner, name: name5, path: path2, env, timeoutMs, runLog, watch, onStdout, onStderr } = opts;
|
|
68867
|
-
const cwd =
|
|
69151
|
+
const cwd = join43(bandHome(), "runners", runner.id);
|
|
68868
69152
|
mkdirSync7(cwd, { recursive: true, mode: 448 });
|
|
68869
69153
|
return new Promise((resolve16, reject) => {
|
|
68870
69154
|
let child;
|
|
@@ -68965,7 +69249,7 @@ function idleTimeoutMs() {
|
|
|
68965
69249
|
return Number.isFinite(raw) && raw > 0 ? raw : void 0;
|
|
68966
69250
|
}
|
|
68967
69251
|
function sleepDir(worktreeId2) {
|
|
68968
|
-
return
|
|
69252
|
+
return join44(bandHome(), "sleep", worktreeId2);
|
|
68969
69253
|
}
|
|
68970
69254
|
var errorText2 = (err) => err instanceof Error ? err.message : String(err);
|
|
68971
69255
|
var EphemeralLifecycleService = class {
|
|
@@ -69266,7 +69550,7 @@ var EphemeralLifecycleService = class {
|
|
|
69266
69550
|
const dir = sleepDir(worktreeId2);
|
|
69267
69551
|
try {
|
|
69268
69552
|
await mkdir8(dir, { recursive: true, mode: 448 });
|
|
69269
|
-
await writeFile5(
|
|
69553
|
+
await writeFile5(join44(dir, "snapshot.bundle"), await host.fs.readFile(bundle), {
|
|
69270
69554
|
mode: 384
|
|
69271
69555
|
});
|
|
69272
69556
|
} catch (err) {
|
|
@@ -69290,7 +69574,7 @@ var EphemeralLifecycleService = class {
|
|
|
69290
69574
|
try {
|
|
69291
69575
|
const dir = sleepDir(worktreeId2);
|
|
69292
69576
|
await mkdir8(dir, { recursive: true, mode: 448 });
|
|
69293
|
-
await writeFile5(
|
|
69577
|
+
await writeFile5(join44(dir, "sessions.json"), JSON.stringify({ files }), { mode: 384 });
|
|
69294
69578
|
} catch (err) {
|
|
69295
69579
|
log23.warn(`could not keep the agent sessions of ${worktreeId2}: ${errorText2(err)}`);
|
|
69296
69580
|
return [];
|
|
@@ -69416,7 +69700,7 @@ var EphemeralLifecycleService = class {
|
|
|
69416
69700
|
const bundle = posix5.join(wipDir, `${row.worktreeId}.bundle`);
|
|
69417
69701
|
await host.fs.writeFile(
|
|
69418
69702
|
bundle,
|
|
69419
|
-
await readFile8(
|
|
69703
|
+
await readFile8(join44(sleepDir(row.worktreeId), "snapshot.bundle"))
|
|
69420
69704
|
);
|
|
69421
69705
|
try {
|
|
69422
69706
|
await git3(["fetch", "--force", bundle, `${local}:${local}`]);
|
|
@@ -69473,7 +69757,7 @@ var EphemeralLifecycleService = class {
|
|
|
69473
69757
|
if (row.sessionIds.length === 0) return;
|
|
69474
69758
|
let files;
|
|
69475
69759
|
try {
|
|
69476
|
-
files = JSON.parse(await readFile8(
|
|
69760
|
+
files = JSON.parse(await readFile8(join44(sleepDir(row.worktreeId), "sessions.json"), "utf8")).files;
|
|
69477
69761
|
} catch (err) {
|
|
69478
69762
|
log23.warn(`${row.worktreeId}: no saved agent sessions: ${errorText2(err)}`);
|
|
69479
69763
|
return;
|
|
@@ -69509,12 +69793,100 @@ init_worktree_id();
|
|
|
69509
69793
|
import { randomBytes as randomBytes12 } from "node:crypto";
|
|
69510
69794
|
init_worktrees();
|
|
69511
69795
|
|
|
69796
|
+
// src/server/services/_utils/project-instructions.ts
|
|
69797
|
+
var INSTRUCTIONS_FILE = "AGENTS.md";
|
|
69798
|
+
var CLAUDE_FILE = "CLAUDE.md";
|
|
69799
|
+
var CLAUDE_TEXT = `@${INSTRUCTIONS_FILE}
|
|
69800
|
+
`;
|
|
69801
|
+
function defaultInstructions(view4) {
|
|
69802
|
+
const p2 = view4.policy;
|
|
69803
|
+
const repos2 = view4.repos.map((r) => `- \`${r.repo}\`${r.role ? ` (role: ${r.role})` : ""}`).join("\n");
|
|
69804
|
+
const limits = [
|
|
69805
|
+
p2.maxConcurrent ? `at most ${p2.maxConcurrent} worker agents run at once` : "no limit on concurrent worker agents",
|
|
69806
|
+
p2.budgetUsd ? `a soft budget of $${p2.budgetUsd} for the project` : "no budget limit",
|
|
69807
|
+
`worker agents run at isolation "${p2.isolationFloor}" or stronger`,
|
|
69808
|
+
p2.labels.length ? `workers go only on hosts labeled ${p2.labels.join(", ")}` : "workers may go on any host"
|
|
69809
|
+
].join("; ");
|
|
69810
|
+
const autonomy = p2.autonomy === "observe" ? "Autonomy is observe. You may read project state and worker chats. You may not message, stop or dispatch anything. Report what you find and recommend actions to the user." : p2.autoMerge ? "Autonomy is autonomous with auto-merge on. You may dispatch workers within the limits and merge pull requests whose CI passed." : "Autonomy is autonomous. You may dispatch workers within the limits. Merging still needs the user's confirmation.";
|
|
69811
|
+
const folderEdits = p2.autonomy === "autonomous" ? "When the user asks for such an edit, you may commit it, and you may push it because autonomy is autonomous." : `When the user asks for such an edit, commit it but do not push without the user's confirmation, because autonomy is ${p2.autonomy}.`;
|
|
69812
|
+
return `${[
|
|
69813
|
+
`# Coordinator of the Band project "${view4.name}"`,
|
|
69814
|
+
view4.description ? `Project description: ${view4.description}` : "",
|
|
69815
|
+
"You coordinate this project. Plan the work across the project's repos, hand it to worker agents, check on them and keep the user informed. Each worker agent works in one git worktree of one repo, with its own chat. Work that spans repos takes one worker per repo, and you keep them in step.",
|
|
69816
|
+
"This file is yours and the user's to edit. It started from the project's settings at creation. The Band hub enforces the limits below in its tools whatever this file says, so a refused call names the reason and you should tell the user what blocked you instead of retrying.",
|
|
69817
|
+
`## Working directory
|
|
69818
|
+
|
|
69819
|
+
You run in the project folder on the coordinator host. It is the working copy of the project context (notes.md, docs/, learnings/, inbox/, handoffs/) and has a checkout of every project repo's default branch under \`repos/<repo>/\` (a clone of the repo's default branch that tracks \`origin/<default>\`). Read the current code there, or through repo_read, repo_search and repo_log. Band fetches before your turns and fast-forwards a checkout only when it is clean, so a checkout with local changes can be behind origin. Code changes go through worker agents (worktree_create). Do not edit a default branch checkout unless the user explicitly asks you to. ${folderEdits} The folder \`repos/\` is never synced to the context repo. Everything else in the project folder (notes, docs, inbox, handoffs, learnings and any file you or the user add) syncs to the hub and to every worker of the project on its own, within seconds: write a file and the others see it, with no commit or push.`,
|
|
69820
|
+
`## Repos
|
|
69821
|
+
|
|
69822
|
+
${repos2 || "- none yet"}`,
|
|
69823
|
+
`## Models and limits
|
|
69824
|
+
|
|
69825
|
+
You run on ${p2.models.coordinator}, worker agents on ${p2.models.worker}, reviewers on ${p2.models.reviewer}. Limits: ${limits}.
|
|
69826
|
+
|
|
69827
|
+
${autonomy}`,
|
|
69828
|
+
`## Context
|
|
69829
|
+
|
|
69830
|
+
The project context repo "${view4.contextName}" is shared by every agent in the project, and the user context holds the user's preferences. Read them before you plan. Layout of the project context: notes.md (running notes), docs/ (design and contracts), media/ (screenshots, recordings), inbox/<agent>.md (pointers to handoffs for an agent), handoffs/ (one file per handoff), learnings/ (what agents learned). Use context_search to find things, context_append_learning to record what future agents should know, and context_handoff to pass work on. Write contracts between repos (API shapes, event formats) to docs/ so the agent on the other side can read them.`,
|
|
69831
|
+
`## Tools
|
|
69832
|
+
|
|
69833
|
+
You act on the project only through the ${COORDINATOR_SERVER} tools: project_status (worktrees, agents, pull requests, spend), worktrees_list, worktree_create, chats_read, chats_send and worktree_stop, plus repo_read {repo, path}, repo_search {repo, query} and repo_log {repo, n} for the default branch checkouts. They are limited to this project's worktrees and repos.`,
|
|
69834
|
+
"## Dispatching\n\nworktree_create starts a worker agent in a new worktree of one repo of the project, on the branch you name, with your brief in .am/BRIEF.md. Write the brief so the worker can act on it alone: the goal, the constraints, the contracts with other repos and what is out of scope, plus acceptance scenarios it can check. For work across repos, start one worker per repo, usually on the same branch name, put the contract between them in docs/ of the project folder first, and decide the order their pull requests merge in.",
|
|
69835
|
+
`## Wake-ups
|
|
69836
|
+
|
|
69837
|
+
Band wakes you with a "Subscription update" message when something needs you: a worker chat ended its turn with an error, is waiting for a permission or an answer, or finished; a pull request of a project worktree has a review comment, a CI result or was merged or closed; or a new file appeared in inbox/ or handoffs/ of the project context. The message names the chat id or the file path. Read the source before you act on it. Events are batched, so one message can carry several. A pull request's subscriptions end when it merges or closes.`,
|
|
69838
|
+
`## Notes and the inbox
|
|
69839
|
+
|
|
69840
|
+
You are the only writer of notes.md in the project context: workers do not edit it, so keep it current with decisions and status. learnings/ is append-only: add entries, never rewrite or delete them. Handoffs from workers land in handoffs/<stamp>-<from>-to-<to>.md and a pointer line in inbox/<to>.md. When you have dealt with an item in inbox/ or handoffs/, move the file into inbox/done/ (a plain move in the project folder, which syncs on its own). A file in inbox/done/ does not wake you again.`,
|
|
69841
|
+
"## Merging\n\nNever merge a pull request unless the user confirmed it in this conversation or auto-merge is on."
|
|
69842
|
+
].filter(Boolean).join("\n\n")}
|
|
69843
|
+
`;
|
|
69844
|
+
}
|
|
69845
|
+
|
|
69846
|
+
// src/server/services/context-browser-service.ts
|
|
69847
|
+
import { mkdtempSync, rmSync as rmSync6 } from "node:fs";
|
|
69848
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
69849
|
+
import { join as join46 } from "node:path";
|
|
69850
|
+
|
|
69851
|
+
// src/server/services/_utils/context-redaction.ts
|
|
69852
|
+
function scanForSecrets(text3) {
|
|
69853
|
+
return scanText("", text3, [], { looseAssignments: true }).map((f2) => ({
|
|
69854
|
+
kind: RULE_LABELS[f2.rule] ?? f2.rule,
|
|
69855
|
+
line: f2.line
|
|
69856
|
+
}));
|
|
69857
|
+
}
|
|
69858
|
+
var PLACEHOLDER = "[redacted]";
|
|
69859
|
+
var EXTRA_PATTERNS = [
|
|
69860
|
+
/\bglpat-[A-Za-z0-9_-]{16,}/g,
|
|
69861
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
69862
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
|
|
69863
|
+
// Credentials inside URLs: scheme://user:password@host.
|
|
69864
|
+
/(?<=:\/\/[^\s/:@]+:)[^\s/@]+(?=@)/g
|
|
69865
|
+
];
|
|
69866
|
+
var ASSIGNMENT2 = /\b([A-Za-z0-9_.-]*(?:password|passwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|credential)[A-Za-z0-9_.-]*)(["']?\s*[:=]\s*["']?)([^\s"',;]{6,})/gi;
|
|
69867
|
+
var globally = (re) => new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
|
|
69868
|
+
function redactSecrets(text3) {
|
|
69869
|
+
let out = text3;
|
|
69870
|
+
for (const [kind, pattern] of SECRET_PATTERNS) {
|
|
69871
|
+
const re = globally(pattern);
|
|
69872
|
+
out = kind === "private key" ? out.replace(
|
|
69873
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
|
|
69874
|
+
PLACEHOLDER
|
|
69875
|
+
) : kind === "bearer token" ? out.replace(re, `Bearer ${PLACEHOLDER}`) : out.replace(re, PLACEHOLDER);
|
|
69876
|
+
}
|
|
69877
|
+
for (const pattern of EXTRA_PATTERNS) out = out.replace(pattern, PLACEHOLDER);
|
|
69878
|
+
return out.replace(
|
|
69879
|
+
ASSIGNMENT2,
|
|
69880
|
+
(match, name5, sep13, value) => value.startsWith("[redacted") ? match : `${name5}${sep13}${PLACEHOLDER}`
|
|
69881
|
+
);
|
|
69882
|
+
}
|
|
69883
|
+
|
|
69512
69884
|
// src/server/services/context-service.ts
|
|
69513
69885
|
init_src();
|
|
69514
69886
|
import { spawn as spawn9 } from "node:child_process";
|
|
69515
69887
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
69516
69888
|
import { mkdirSync as mkdirSync8, rmSync as rmSync5 } from "node:fs";
|
|
69517
|
-
import { join as
|
|
69889
|
+
import { join as join45 } from "node:path";
|
|
69518
69890
|
|
|
69519
69891
|
// src/server/infra/db/queries/contexts.ts
|
|
69520
69892
|
init_drizzle_orm();
|
|
@@ -69581,10 +69953,10 @@ function toView2(row) {
|
|
|
69581
69953
|
};
|
|
69582
69954
|
}
|
|
69583
69955
|
function contextRoot() {
|
|
69584
|
-
return
|
|
69956
|
+
return join45(bandHome(), "context");
|
|
69585
69957
|
}
|
|
69586
69958
|
function contextRepoPath(name5) {
|
|
69587
|
-
return
|
|
69959
|
+
return join45(contextRoot(), `${name5}.git`);
|
|
69588
69960
|
}
|
|
69589
69961
|
function contextGitEnv(extra = {}) {
|
|
69590
69962
|
return {
|
|
@@ -70084,6 +70456,323 @@ var ContextService = class {
|
|
|
70084
70456
|
};
|
|
70085
70457
|
var contextService = new ContextService();
|
|
70086
70458
|
|
|
70459
|
+
// src/server/services/context-browser-service.ts
|
|
70460
|
+
var MAX_FILE_BYTES2 = 1024 * 1024;
|
|
70461
|
+
var MAX_DIFF_BYTES2 = 200 * 1024;
|
|
70462
|
+
var MAX_MESSAGE = 200;
|
|
70463
|
+
var LOG_LIMIT = 200;
|
|
70464
|
+
var CONFLICT = /^(.*?)\.conflict-([A-Za-z0-9]+)((?:\.[^/.]+)?)$/;
|
|
70465
|
+
var ContextConflictError = class extends Error {
|
|
70466
|
+
constructor(message) {
|
|
70467
|
+
super(message);
|
|
70468
|
+
this.name = "ContextConflictError";
|
|
70469
|
+
}
|
|
70470
|
+
};
|
|
70471
|
+
function conflictOriginal(path2) {
|
|
70472
|
+
const m2 = CONFLICT.exec(path2);
|
|
70473
|
+
return m2 ? `${m2[1]}${m2[3]}` : null;
|
|
70474
|
+
}
|
|
70475
|
+
function hasControl(text3, allowWhitespace = false) {
|
|
70476
|
+
for (let i = 0; i < text3.length; i++) {
|
|
70477
|
+
const c = text3.charCodeAt(i);
|
|
70478
|
+
if (allowWhitespace && (c === 9 || c === 10 || c === 13)) continue;
|
|
70479
|
+
if (c < 32 || c === 127) return true;
|
|
70480
|
+
}
|
|
70481
|
+
return false;
|
|
70482
|
+
}
|
|
70483
|
+
function checkPath(path2) {
|
|
70484
|
+
const bad = !path2 || path2.length > 500 || path2.startsWith("/") || path2.endsWith("/") || path2.startsWith(":") || hasControl(path2) || path2.includes("\\") || path2.split("/").some((seg) => seg === "" || seg === "." || seg === ".." || seg === ".git");
|
|
70485
|
+
if (bad) throw new ContextInputError(`"${path2}" is not a valid path inside a context`);
|
|
70486
|
+
return path2;
|
|
70487
|
+
}
|
|
70488
|
+
function checkMessage(message) {
|
|
70489
|
+
const trimmed = message.trim();
|
|
70490
|
+
if (!trimmed) throw new ContextInputError("A commit message is required");
|
|
70491
|
+
if (hasControl(trimmed, true)) {
|
|
70492
|
+
throw new ContextInputError("The commit message holds control characters");
|
|
70493
|
+
}
|
|
70494
|
+
if (scanForSecrets(trimmed).length > 0) {
|
|
70495
|
+
throw new ContextInputError("Not saved: the commit message looks like it holds a credential.");
|
|
70496
|
+
}
|
|
70497
|
+
if (trimmed.length > MAX_MESSAGE) {
|
|
70498
|
+
throw new ContextInputError(`The commit message is longer than ${MAX_MESSAGE} characters`);
|
|
70499
|
+
}
|
|
70500
|
+
return trimmed;
|
|
70501
|
+
}
|
|
70502
|
+
var ContextBrowserService = class {
|
|
70503
|
+
repo(name5) {
|
|
70504
|
+
contextService.require(name5);
|
|
70505
|
+
return contextRepoPath(name5);
|
|
70506
|
+
}
|
|
70507
|
+
/** The commit HEAD points at, or null in a repo with no commits. */
|
|
70508
|
+
async head(repo) {
|
|
70509
|
+
const r = await runGit(["rev-parse", "--verify", "-q", "HEAD^{commit}"], { cwd: repo });
|
|
70510
|
+
return r.code === 0 ? r.stdout.trim() : null;
|
|
70511
|
+
}
|
|
70512
|
+
async tree(name5) {
|
|
70513
|
+
const repo = this.repo(name5);
|
|
70514
|
+
const head = await this.head(repo);
|
|
70515
|
+
if (!head) return { head, entries: [] };
|
|
70516
|
+
const out = await git2(["ls-tree", "-r", "-z", "--long", head], { cwd: repo });
|
|
70517
|
+
const entries = [];
|
|
70518
|
+
for (const rec of out.split("\0")) {
|
|
70519
|
+
if (!rec) continue;
|
|
70520
|
+
const tab = rec.indexOf(" ");
|
|
70521
|
+
const meta = rec.slice(0, tab).split(/\s+/);
|
|
70522
|
+
if (meta[1] !== "blob") continue;
|
|
70523
|
+
const path2 = rec.slice(tab + 1);
|
|
70524
|
+
entries.push({ path: path2, size: Number(meta[3]) || 0, conflictOf: conflictOriginal(path2) });
|
|
70525
|
+
}
|
|
70526
|
+
entries.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
70527
|
+
return { head, entries };
|
|
70528
|
+
}
|
|
70529
|
+
async file(name5, path2) {
|
|
70530
|
+
const repo = this.repo(name5);
|
|
70531
|
+
checkPath(path2);
|
|
70532
|
+
const head = await this.head(repo);
|
|
70533
|
+
if (!head) throw new ContextInputError(`"${path2}" does not exist`);
|
|
70534
|
+
const size = await runGit(["cat-file", "-s", `${head}:${path2}`], { cwd: repo });
|
|
70535
|
+
if (size.code !== 0) throw new ContextInputError(`"${path2}" does not exist`);
|
|
70536
|
+
const bytes = Number(size.stdout.trim());
|
|
70537
|
+
const type = await git2(["cat-file", "-t", `${head}:${path2}`], { cwd: repo });
|
|
70538
|
+
if (type.trim() !== "blob") throw new ContextInputError(`"${path2}" is a folder`);
|
|
70539
|
+
if (bytes > MAX_FILE_BYTES2) {
|
|
70540
|
+
return { path: path2, binary: true, content: null, size: bytes, commit: head };
|
|
70541
|
+
}
|
|
70542
|
+
const content = await git2(["cat-file", "blob", `${head}:${path2}`], { cwd: repo });
|
|
70543
|
+
if (content.includes("\0") || content.includes("\uFFFD")) {
|
|
70544
|
+
return { path: path2, binary: true, content: null, size: bytes, commit: head };
|
|
70545
|
+
}
|
|
70546
|
+
return { path: path2, binary: false, content, size: bytes, commit: head };
|
|
70547
|
+
}
|
|
70548
|
+
async log(name5, path2, limit = 50) {
|
|
70549
|
+
const repo = this.repo(name5);
|
|
70550
|
+
if (path2) checkPath(path2);
|
|
70551
|
+
if (!await this.head(repo)) return [];
|
|
70552
|
+
const args = [
|
|
70553
|
+
"--literal-pathspecs",
|
|
70554
|
+
"log",
|
|
70555
|
+
"--no-color",
|
|
70556
|
+
`-n${Math.min(Math.max(limit, 1), LOG_LIMIT)}`,
|
|
70557
|
+
"--format=%H%x1f%an%x1f%at%x1f%s%x1e"
|
|
70558
|
+
];
|
|
70559
|
+
if (path2) args.push("--", path2);
|
|
70560
|
+
const out = await git2(args, { cwd: repo });
|
|
70561
|
+
return out.split("").map((rec) => rec.trim()).filter(Boolean).map((rec) => {
|
|
70562
|
+
const [sha = "", author = "", at = "0", subject = ""] = rec.split("");
|
|
70563
|
+
return { sha, author, at: Number(at) * 1e3, subject };
|
|
70564
|
+
});
|
|
70565
|
+
}
|
|
70566
|
+
async diff(name5, sha, path2) {
|
|
70567
|
+
const repo = this.repo(name5);
|
|
70568
|
+
if (!/^[0-9a-f]{40}$/.test(sha)) throw new ContextInputError("Not a commit id");
|
|
70569
|
+
if (path2) checkPath(path2);
|
|
70570
|
+
const exists4 = await runGit(["cat-file", "-e", `${sha}^{commit}`], { cwd: repo });
|
|
70571
|
+
if (exists4.code !== 0) throw new ContextInputError("No such commit");
|
|
70572
|
+
const scope3 = path2 ? ["--", path2] : [];
|
|
70573
|
+
const patch = await git2(
|
|
70574
|
+
[
|
|
70575
|
+
"--literal-pathspecs",
|
|
70576
|
+
"show",
|
|
70577
|
+
"--root",
|
|
70578
|
+
"--format=",
|
|
70579
|
+
"--patch",
|
|
70580
|
+
"--no-color",
|
|
70581
|
+
"-M",
|
|
70582
|
+
"--no-ext-diff",
|
|
70583
|
+
sha,
|
|
70584
|
+
...scope3
|
|
70585
|
+
],
|
|
70586
|
+
{ cwd: repo }
|
|
70587
|
+
);
|
|
70588
|
+
const names = await git2(
|
|
70589
|
+
[
|
|
70590
|
+
"diff-tree",
|
|
70591
|
+
"--root",
|
|
70592
|
+
"-m",
|
|
70593
|
+
"--first-parent",
|
|
70594
|
+
"--no-commit-id",
|
|
70595
|
+
"--name-status",
|
|
70596
|
+
"-r",
|
|
70597
|
+
"-M",
|
|
70598
|
+
sha
|
|
70599
|
+
],
|
|
70600
|
+
{ cwd: repo }
|
|
70601
|
+
);
|
|
70602
|
+
const files = names.split("\n").filter(Boolean).map((line2) => {
|
|
70603
|
+
const parts = line2.split(" ");
|
|
70604
|
+
return { status: parts[0] ?? "M", path: parts[parts.length - 1] ?? "" };
|
|
70605
|
+
});
|
|
70606
|
+
const patchBytes = Buffer.from(patch);
|
|
70607
|
+
const truncated = patchBytes.length > MAX_DIFF_BYTES2;
|
|
70608
|
+
return {
|
|
70609
|
+
diff: truncated ? patchBytes.subarray(0, MAX_DIFF_BYTES2).toString("utf8") : patch,
|
|
70610
|
+
truncated,
|
|
70611
|
+
files
|
|
70612
|
+
};
|
|
70613
|
+
}
|
|
70614
|
+
/** The newest learnings and handoffs files, one row per file, newest commit first. */
|
|
70615
|
+
async recent(name5, limit = 20) {
|
|
70616
|
+
const repo = this.repo(name5);
|
|
70617
|
+
if (!await this.head(repo)) return [];
|
|
70618
|
+
const out = await git2(
|
|
70619
|
+
[
|
|
70620
|
+
"log",
|
|
70621
|
+
"--no-color",
|
|
70622
|
+
"-n300",
|
|
70623
|
+
"--name-only",
|
|
70624
|
+
"--format=%x1e%H%x1f%at%x1f%s",
|
|
70625
|
+
"--",
|
|
70626
|
+
"learnings",
|
|
70627
|
+
"handoffs"
|
|
70628
|
+
],
|
|
70629
|
+
{ cwd: repo }
|
|
70630
|
+
);
|
|
70631
|
+
const seen = /* @__PURE__ */ new Set();
|
|
70632
|
+
const rows = [];
|
|
70633
|
+
for (const block of out.split("")) {
|
|
70634
|
+
const [header = "", ...files] = block.split("\n").filter((l, i) => i === 0 || l.trim());
|
|
70635
|
+
const [sha = "", at = "0", subject = ""] = header.split("");
|
|
70636
|
+
if (!sha) continue;
|
|
70637
|
+
for (const path2 of files) {
|
|
70638
|
+
const kind = path2.startsWith("learnings/") ? "learnings" : path2.startsWith("handoffs/") ? "handoffs" : null;
|
|
70639
|
+
if (!kind || path2.endsWith(".gitkeep") || seen.has(path2)) continue;
|
|
70640
|
+
seen.add(path2);
|
|
70641
|
+
rows.push({ path: path2, kind, sha, at: Number(at) * 1e3, subject });
|
|
70642
|
+
}
|
|
70643
|
+
}
|
|
70644
|
+
const { entries } = await this.tree(name5);
|
|
70645
|
+
const live = new Set(entries.map((e) => e.path));
|
|
70646
|
+
return rows.filter((r) => live.has(r.path)).slice(0, limit);
|
|
70647
|
+
}
|
|
70648
|
+
/**
|
|
70649
|
+
* Saves `content` at `path` as one commit. `base` is the commit the editor
|
|
70650
|
+
* loaded: when the file changed since, the save is refused so an edit never
|
|
70651
|
+
* overwrites a newer version unseen.
|
|
70652
|
+
*/
|
|
70653
|
+
async write(name5, path2, content, message, base) {
|
|
70654
|
+
checkPath(path2);
|
|
70655
|
+
if (Buffer.byteLength(content) > MAX_FILE_BYTES2) {
|
|
70656
|
+
throw new ContextInputError("The file is larger than 1 MiB");
|
|
70657
|
+
}
|
|
70658
|
+
const found = scanForSecrets(content);
|
|
70659
|
+
if (found.length > 0) {
|
|
70660
|
+
const list = found.map((f2) => `${f2.kind} on line ${f2.line}`).join(", ");
|
|
70661
|
+
throw new ContextInputError(
|
|
70662
|
+
`Not saved: the text looks like it holds a credential (${list}). Remove it first.`
|
|
70663
|
+
);
|
|
70664
|
+
}
|
|
70665
|
+
return this.commit(name5, [{ path: path2, content }], checkMessage(message), { base, guard: path2 });
|
|
70666
|
+
}
|
|
70667
|
+
/** Keeps one version of a conflicted file at its original path and deletes the conflict copy. */
|
|
70668
|
+
async resolveConflict(name5, conflictPath, keep, message) {
|
|
70669
|
+
checkPath(conflictPath);
|
|
70670
|
+
const original = conflictOriginal(conflictPath);
|
|
70671
|
+
if (!original) throw new ContextInputError(`"${conflictPath}" is not a conflict copy`);
|
|
70672
|
+
const msg = checkMessage(
|
|
70673
|
+
message ?? `Resolve conflict in ${original}: keep ${keep === "conflict" ? "the conflict copy" : "the original"}`
|
|
70674
|
+
);
|
|
70675
|
+
const repo = this.repo(name5);
|
|
70676
|
+
const result = await contextService.exclusive(name5, async () => {
|
|
70677
|
+
const head = await this.head(repo);
|
|
70678
|
+
if (!head) throw new ContextInputError(`"${conflictPath}" does not exist`);
|
|
70679
|
+
const blob2 = await runGit(["rev-parse", "--verify", "-q", `${head}:${conflictPath}`], {
|
|
70680
|
+
cwd: repo
|
|
70681
|
+
});
|
|
70682
|
+
if (blob2.code !== 0) throw new ContextInputError(`"${conflictPath}" does not exist`);
|
|
70683
|
+
if (keep === "original") {
|
|
70684
|
+
const orig = await runGit(["rev-parse", "--verify", "-q", `${head}:${original}`], {
|
|
70685
|
+
cwd: repo
|
|
70686
|
+
});
|
|
70687
|
+
if (orig.code !== 0) {
|
|
70688
|
+
throw new ContextInputError(
|
|
70689
|
+
`"${original}" does not exist, so "${conflictPath}" is not a conflict copy of anything`
|
|
70690
|
+
);
|
|
70691
|
+
}
|
|
70692
|
+
}
|
|
70693
|
+
const changes = [{ path: conflictPath }];
|
|
70694
|
+
if (keep === "conflict") changes.push({ path: original, blob: blob2.stdout.trim() });
|
|
70695
|
+
return this.commitLocked(repo, name5, head, changes, msg);
|
|
70696
|
+
});
|
|
70697
|
+
return { commit: result.commit, path: original };
|
|
70698
|
+
}
|
|
70699
|
+
commit(name5, changes, message, opts = {}) {
|
|
70700
|
+
const repo = this.repo(name5);
|
|
70701
|
+
return contextService.exclusive(name5, async () => {
|
|
70702
|
+
const head = await this.head(repo);
|
|
70703
|
+
if (head && opts.base && opts.guard && head !== opts.base) {
|
|
70704
|
+
const at = async (rev) => {
|
|
70705
|
+
const r = await runGit(["rev-parse", "--verify", "-q", `${rev}:${opts.guard}`], {
|
|
70706
|
+
cwd: repo
|
|
70707
|
+
});
|
|
70708
|
+
return r.code === 0 ? r.stdout.trim() : "";
|
|
70709
|
+
};
|
|
70710
|
+
if (await at(head) !== await at(opts.base)) {
|
|
70711
|
+
throw new ContextConflictError(
|
|
70712
|
+
`"${opts.guard}" changed since you opened it. Reload it to see the newer version.`
|
|
70713
|
+
);
|
|
70714
|
+
}
|
|
70715
|
+
}
|
|
70716
|
+
return this.commitLocked(repo, name5, head, changes, message, opts.author);
|
|
70717
|
+
});
|
|
70718
|
+
}
|
|
70719
|
+
/** Builds the commit in a private index. The caller holds the context's lock. */
|
|
70720
|
+
async commitLocked(repo, name5, head, changes, message, author) {
|
|
70721
|
+
const dir = mkdtempSync(join46(tmpdir3(), "band-context-index-"));
|
|
70722
|
+
try {
|
|
70723
|
+
const env = contextGitEnv({
|
|
70724
|
+
GIT_INDEX_FILE: join46(dir, "index"),
|
|
70725
|
+
...author ? {
|
|
70726
|
+
GIT_AUTHOR_NAME: author,
|
|
70727
|
+
GIT_AUTHOR_EMAIL: `${author}@localhost`,
|
|
70728
|
+
GIT_COMMITTER_NAME: author,
|
|
70729
|
+
GIT_COMMITTER_EMAIL: `${author}@localhost`
|
|
70730
|
+
} : {}
|
|
70731
|
+
});
|
|
70732
|
+
const opts = { cwd: repo, env };
|
|
70733
|
+
if (head) await git2(["read-tree", head], opts);
|
|
70734
|
+
for (const change of changes) {
|
|
70735
|
+
if (change.content === void 0 && change.blob === void 0) {
|
|
70736
|
+
await git2(["update-index", "--index-info"], {
|
|
70737
|
+
...opts,
|
|
70738
|
+
input: `0 ${"0".repeat(40)} ${change.path}
|
|
70739
|
+
`
|
|
70740
|
+
});
|
|
70741
|
+
continue;
|
|
70742
|
+
}
|
|
70743
|
+
const oid = change.blob ?? (await git2(["hash-object", "-w", "--stdin"], { ...opts, input: change.content })).trim();
|
|
70744
|
+
const r = await runGit(
|
|
70745
|
+
["update-index", "--add", "--cacheinfo", `100644,${oid},${change.path}`],
|
|
70746
|
+
opts
|
|
70747
|
+
);
|
|
70748
|
+
if (r.code !== 0) {
|
|
70749
|
+
throw new ContextInputError(
|
|
70750
|
+
`Cannot save "${change.path}": ${r.stderr.trim().slice(0, 200)}`
|
|
70751
|
+
);
|
|
70752
|
+
}
|
|
70753
|
+
}
|
|
70754
|
+
const tree = (await git2(["write-tree"], opts)).trim();
|
|
70755
|
+
if (head) {
|
|
70756
|
+
const current3 = (await git2(["rev-parse", `${head}^{tree}`], opts)).trim();
|
|
70757
|
+
if (current3 === tree) return { commit: head, changed: false };
|
|
70758
|
+
}
|
|
70759
|
+
const commit = (await git2(["commit-tree", tree, ...head ? ["-p", head] : [], "-m", message], opts)).trim();
|
|
70760
|
+
const ref2 = (await git2(["symbolic-ref", "-q", "HEAD"], { cwd: repo })).trim();
|
|
70761
|
+
const moved = await runGit(["update-ref", "-m", "context browser", ref2, commit, head ?? ""], {
|
|
70762
|
+
cwd: repo
|
|
70763
|
+
});
|
|
70764
|
+
if (moved.code !== 0) {
|
|
70765
|
+
throw new ContextConflictError("The context changed while saving. Reload and try again.");
|
|
70766
|
+
}
|
|
70767
|
+
contextService.syncSoon(name5);
|
|
70768
|
+
return { commit, changed: true };
|
|
70769
|
+
} finally {
|
|
70770
|
+
rmSync6(dir, { recursive: true, force: true });
|
|
70771
|
+
}
|
|
70772
|
+
}
|
|
70773
|
+
};
|
|
70774
|
+
var contextBrowserService = new ContextBrowserService();
|
|
70775
|
+
|
|
70087
70776
|
// src/server/services/project-service.ts
|
|
70088
70777
|
init_state();
|
|
70089
70778
|
var log25 = createLogger("project-service");
|
|
@@ -70230,8 +70919,41 @@ var ProjectService = class {
|
|
|
70230
70919
|
throw err;
|
|
70231
70920
|
}
|
|
70232
70921
|
log25.info(`created project ${name5} with ${repos2.length} repos`);
|
|
70922
|
+
await this.seedInstructions(row.id);
|
|
70233
70923
|
return this.get(row.id);
|
|
70234
70924
|
}
|
|
70925
|
+
/**
|
|
70926
|
+
* Gives the project's context repo a default `AGENTS.md` and a `CLAUDE.md` that imports it, when
|
|
70927
|
+
* it has none. The files then belong to the user and are never rewritten. Never throws, so a
|
|
70928
|
+
* failure leaves a project without them and the coordinator without instructions.
|
|
70929
|
+
*/
|
|
70930
|
+
async seedInstructions(ref2) {
|
|
70931
|
+
try {
|
|
70932
|
+
const view4 = this.get(ref2);
|
|
70933
|
+
const have = new Set(
|
|
70934
|
+
(await contextBrowserService.tree(view4.contextName)).entries.map((e) => e.path)
|
|
70935
|
+
);
|
|
70936
|
+
const files = [
|
|
70937
|
+
[
|
|
70938
|
+
INSTRUCTIONS_FILE,
|
|
70939
|
+
defaultInstructions({
|
|
70940
|
+
name: view4.name,
|
|
70941
|
+
description: view4.description,
|
|
70942
|
+
contextName: view4.contextName,
|
|
70943
|
+
repos: view4.repos,
|
|
70944
|
+
policy: view4.effectivePolicy
|
|
70945
|
+
})
|
|
70946
|
+
],
|
|
70947
|
+
[CLAUDE_FILE, CLAUDE_TEXT]
|
|
70948
|
+
];
|
|
70949
|
+
for (const [path2, content] of files) {
|
|
70950
|
+
if (have.has(path2)) continue;
|
|
70951
|
+
await contextBrowserService.write(view4.contextName, path2, content, `Add ${path2}`);
|
|
70952
|
+
}
|
|
70953
|
+
} catch (err) {
|
|
70954
|
+
log25.warn({ project: ref2, err }, "could not add the default AGENTS.md to the project context");
|
|
70955
|
+
}
|
|
70956
|
+
}
|
|
70235
70957
|
update(ref2, patch) {
|
|
70236
70958
|
const row = this.require(ref2);
|
|
70237
70959
|
const set2 = {};
|
|
@@ -70468,59 +71190,49 @@ var projectService = new ProjectService();
|
|
|
70468
71190
|
|
|
70469
71191
|
// src/server/services/project-folder-service.ts
|
|
70470
71192
|
init_state();
|
|
71193
|
+
var LOCAL_HOST_ID4 = "local";
|
|
71194
|
+
var WaitingForWorkerError = class extends Error {
|
|
71195
|
+
constructor(agent) {
|
|
71196
|
+
super(`Waiting for a worker that can run ${agent}`);
|
|
71197
|
+
this.name = "WaitingForWorkerError";
|
|
71198
|
+
}
|
|
71199
|
+
};
|
|
70471
71200
|
var log26 = createLogger("project-folder");
|
|
70472
|
-
var INSTRUCTIONS_FILE = "AGENTS.md";
|
|
70473
|
-
var CLAUDE_FILE = "CLAUDE.md";
|
|
70474
|
-
var CLAUDE_TEXT = `<!-- Written by Band. Claude Code reads this file; it imports the project's instructions. -->
|
|
70475
|
-
@${INSTRUCTIONS_FILE}
|
|
70476
|
-
`;
|
|
70477
71201
|
var ProjectFolderService = class {
|
|
70478
71202
|
states = /* @__PURE__ */ new Map();
|
|
70479
71203
|
inflight = /* @__PURE__ */ new Map();
|
|
70480
|
-
/** The coordinator's charter of a project. Set by `ProjectCoordinatorService`, which imports this file. */
|
|
70481
|
-
instructionsOf = null;
|
|
70482
|
-
setInstructions(fn) {
|
|
70483
|
-
this.instructionsOf = fn;
|
|
70484
|
-
}
|
|
70485
|
-
/**
|
|
70486
|
-
* Writes the project's agent instructions into its folder: `AGENTS.md` holds the coordinator's
|
|
70487
|
-
* charter (Codex, OpenCode and most agents read it) and `CLAUDE.md` imports it for Claude Code. So
|
|
70488
|
-
* any agent started in the folder, in a chat or a terminal, works from the same text. Band
|
|
70489
|
-
* rewrites both whenever the folder is prepared or the project changes, and the context sync
|
|
70490
|
-
* leaves them out, so each host keeps its own copy and an edit never spreads. A file is only
|
|
70491
|
-
* written when its text changed. Never throws.
|
|
70492
|
-
*/
|
|
70493
|
-
async writeInstructions(row, folder) {
|
|
70494
|
-
const charter = this.instructionsOf?.(row);
|
|
70495
|
-
const dir = folder ?? this.states.get(row.id)?.folder;
|
|
70496
|
-
if (!charter || !dir) return;
|
|
70497
|
-
const host = this.hostOf(row);
|
|
70498
|
-
const body = charter.replace(/(?<![`\w])([\w./-]*<[\w-]+>[\w./<>-]*)/g, "`$1`");
|
|
70499
|
-
const agents = `<!-- Written by Band from the project's settings, repos and policy. Edits are overwritten: change the project in Settings > Projects instead. -->
|
|
70500
|
-
|
|
70501
|
-
${body}
|
|
70502
|
-
`;
|
|
70503
|
-
for (const [name5, text3] of [
|
|
70504
|
-
[INSTRUCTIONS_FILE, agents],
|
|
70505
|
-
[CLAUDE_FILE, CLAUDE_TEXT]
|
|
70506
|
-
]) {
|
|
70507
|
-
const path2 = `${dir.replace(/\/+$/, "")}/${name5}`;
|
|
70508
|
-
try {
|
|
70509
|
-
const current3 = await host.fs.readFile(path2).then((b2) => new TextDecoder().decode(b2)).catch(() => null);
|
|
70510
|
-
if (current3 !== text3) await host.fs.writeFile(path2, text3);
|
|
70511
|
-
} catch (err) {
|
|
70512
|
-
log26.warn(
|
|
70513
|
-
{ project: row.name, file: name5, err },
|
|
70514
|
-
"could not write the project's agent instructions"
|
|
70515
|
-
);
|
|
70516
|
-
}
|
|
70517
|
-
}
|
|
70518
|
-
}
|
|
70519
71204
|
/** `project:host` folders a view prepared without checkouts, so the next view asks nothing. */
|
|
70520
71205
|
prepared = /* @__PURE__ */ new Set();
|
|
70521
|
-
/**
|
|
71206
|
+
/**
|
|
71207
|
+
* The id of the host the project's folder is on, or null while no host can take it. A hub with
|
|
71208
|
+
* `BAND_LOCAL_HOST=off` never uses its own machine: the coordinator goes on the first online
|
|
71209
|
+
* worker that reports the coordinator's agent installed and logged in, and that choice is saved.
|
|
71210
|
+
* With no such worker the project waits, and nothing is placed.
|
|
71211
|
+
*/
|
|
71212
|
+
hostIdOf(row) {
|
|
71213
|
+
const localOn = isLocalHostEnabled();
|
|
71214
|
+
const chosen = row.coordinatorHostId;
|
|
71215
|
+
if (chosen && (localOn || chosen !== LOCAL_HOST_ID4)) return chosen;
|
|
71216
|
+
if (localOn) return LOCAL_HOST_ID4;
|
|
71217
|
+
const agent = this.coordinatorAgentType(row);
|
|
71218
|
+
const eligible = tokenService.listHosts().find(
|
|
71219
|
+
(h) => h.id !== LOCAL_HOST_ID4 && h.status === "online" && agentIsUsable(h.report?.agents.find((a) => a.type === agent))
|
|
71220
|
+
);
|
|
71221
|
+
if (!eligible) return null;
|
|
71222
|
+
projectService.update(row.id, { coordinatorHostId: eligible.id });
|
|
71223
|
+
log26.info({ project: row.name, host: eligible.id, agent }, "placed the coordinator on a worker");
|
|
71224
|
+
return eligible.id;
|
|
71225
|
+
}
|
|
71226
|
+
/** The agent type the project's coordinator runs. */
|
|
71227
|
+
coordinatorAgentType(row) {
|
|
71228
|
+
const id = row.coordinatorAgent ?? "claude-code";
|
|
71229
|
+
return loadSettings().codingAgents?.find((a) => a.id === id)?.type ?? id;
|
|
71230
|
+
}
|
|
71231
|
+
/** The host the project's folder is on. Throws while the project waits for a worker. */
|
|
70522
71232
|
hostOf(row) {
|
|
70523
|
-
|
|
71233
|
+
const id = this.hostIdOf(row);
|
|
71234
|
+
if (!id) throw new WaitingForWorkerError(this.coordinatorAgentType(row));
|
|
71235
|
+
return hostRegistry.hostById(id);
|
|
70524
71236
|
}
|
|
70525
71237
|
/**
|
|
70526
71238
|
* The project's git repos with their clone on `host`, and the repos that have none. A repo with a
|
|
@@ -70591,7 +71303,6 @@ ${body}
|
|
|
70591
71303
|
skipped
|
|
70592
71304
|
};
|
|
70593
71305
|
this.states.set(row.id, state2);
|
|
70594
|
-
await this.writeInstructions(row, state2.folder);
|
|
70595
71306
|
return state2;
|
|
70596
71307
|
})().finally(() => {
|
|
70597
71308
|
if (this.inflight.get(row.id) === work) this.inflight.delete(row.id);
|
|
@@ -72721,7 +73432,7 @@ exit`;
|
|
|
72721
73432
|
var cronjobService = new CronjobService();
|
|
72722
73433
|
|
|
72723
73434
|
// src/server/services/local-host-policy.ts
|
|
72724
|
-
var
|
|
73435
|
+
var LOCAL_HOST_ID5 = "local";
|
|
72725
73436
|
var LocalHostDisabledError = class extends Error {
|
|
72726
73437
|
constructor() {
|
|
72727
73438
|
super(
|
|
@@ -72740,20 +73451,20 @@ var NoDefaultHostError = class extends Error {
|
|
|
72740
73451
|
};
|
|
72741
73452
|
function resolveWorktreeHostId(requested) {
|
|
72742
73453
|
if (!isLocalHostEnabled()) {
|
|
72743
|
-
if (requested ===
|
|
73454
|
+
if (requested === LOCAL_HOST_ID5) throw new LocalHostDisabledError();
|
|
72744
73455
|
if (requested) return requested;
|
|
72745
73456
|
const configured = process.env.BAND_DEFAULT_HOST?.trim();
|
|
72746
73457
|
if (configured) {
|
|
72747
|
-
if (configured ===
|
|
73458
|
+
if (configured === LOCAL_HOST_ID5) throw new LocalHostDisabledError();
|
|
72748
73459
|
return configured;
|
|
72749
73460
|
}
|
|
72750
|
-
const online = tokenService.listHosts().filter((h) => h.id !==
|
|
73461
|
+
const online = tokenService.listHosts().filter((h) => h.id !== LOCAL_HOST_ID5 && h.status === "online");
|
|
72751
73462
|
if (online.length === 1) return online[0].id;
|
|
72752
73463
|
throw new NoDefaultHostError(
|
|
72753
73464
|
online.length === 0 ? "No worker is online." : "More than one worker is online."
|
|
72754
73465
|
);
|
|
72755
73466
|
}
|
|
72756
|
-
return requested ??
|
|
73467
|
+
return requested ?? LOCAL_HOST_ID5;
|
|
72757
73468
|
}
|
|
72758
73469
|
|
|
72759
73470
|
// src/server/services/panel-focus-service.ts
|
|
@@ -72847,7 +73558,7 @@ init_settings_service();
|
|
|
72847
73558
|
init_state();
|
|
72848
73559
|
|
|
72849
73560
|
// src/server/services/sync-service.ts
|
|
72850
|
-
import { join as
|
|
73561
|
+
import { join as join47, relative as relative2, sep as sep6 } from "node:path";
|
|
72851
73562
|
|
|
72852
73563
|
// src/server/services/_utils/remote-worktrees.ts
|
|
72853
73564
|
async function refreshRemoteWorktrees(repoName2, repoPath, worktrees2) {
|
|
@@ -72955,7 +73666,7 @@ function isProjectCheckout(path2, roots) {
|
|
|
72955
73666
|
});
|
|
72956
73667
|
}
|
|
72957
73668
|
async function projectFolderRoots() {
|
|
72958
|
-
const root =
|
|
73669
|
+
const root = join47(bandHome(), "projects");
|
|
72959
73670
|
let real3 = root;
|
|
72960
73671
|
try {
|
|
72961
73672
|
real3 = await hostRegistry.hostById("local").fs.realpath(root);
|
|
@@ -73375,7 +74086,7 @@ var WorktreeService = class {
|
|
|
73375
74086
|
const project = projectService.find(projectId);
|
|
73376
74087
|
if (!project) return null;
|
|
73377
74088
|
const host = projectFolderService.hostOf(project);
|
|
73378
|
-
const folder = projectFolderService.state(project.id)?.folder ?? (host.id === hostRegistry.local.id ?
|
|
74089
|
+
const folder = projectFolderService.state(project.id)?.folder ?? (host.id === hostRegistry.local.id ? join48(bandHome(), "projects", project.name) : void 0);
|
|
73379
74090
|
if (!folder) return null;
|
|
73380
74091
|
const worktree = {
|
|
73381
74092
|
name: project.name,
|
|
@@ -73539,8 +74250,8 @@ var WorktreeService = class {
|
|
|
73539
74250
|
const remote = hostId2 !== hostRegistry.local.id;
|
|
73540
74251
|
const repoPath = await this.checkoutOn(repo, host, input.hostRepoPath);
|
|
73541
74252
|
const wtDir = remote ? await this.remoteWorktreesDir(host) : worktreesDir();
|
|
73542
|
-
const worktreePath = remote ? posix6.join(wtDir, input.repo, input.branch) :
|
|
73543
|
-
await host.fs.mkdir(remote ? posix6.join(wtDir, input.repo) :
|
|
74253
|
+
const worktreePath = remote ? posix6.join(wtDir, input.repo, input.branch) : join48(wtDir, input.repo, input.branch);
|
|
74254
|
+
await host.fs.mkdir(remote ? posix6.join(wtDir, input.repo) : join48(wtDir, input.repo), {
|
|
73544
74255
|
recursive: true
|
|
73545
74256
|
});
|
|
73546
74257
|
let base = input.base;
|
|
@@ -73798,7 +74509,7 @@ var WorktreeService = class {
|
|
|
73798
74509
|
saveState(state2);
|
|
73799
74510
|
removal.commit();
|
|
73800
74511
|
try {
|
|
73801
|
-
unlinkSync4(
|
|
74512
|
+
unlinkSync4(join48(bandHome(), "workspace-prompts", `${worktreeId2}.json`));
|
|
73802
74513
|
} catch {
|
|
73803
74514
|
}
|
|
73804
74515
|
deleteWorktreeStatus(worktreeId2);
|
|
@@ -74182,7 +74893,7 @@ var WorktreeService = class {
|
|
|
74182
74893
|
if (!/^[a-zA-Z0-9._-]+$/.test(input.scriptType) || input.scriptType.includes("..")) {
|
|
74183
74894
|
throw new Error(`Invalid script type "${input.scriptType}"`);
|
|
74184
74895
|
}
|
|
74185
|
-
const scriptPath =
|
|
74896
|
+
const scriptPath = join48(input.path, ".band", input.scriptType);
|
|
74186
74897
|
const host = hostRegistry.local;
|
|
74187
74898
|
try {
|
|
74188
74899
|
await host.fs.stat(scriptPath);
|
|
@@ -75309,11 +76020,11 @@ function publicHubUrl() {
|
|
|
75309
76020
|
}
|
|
75310
76021
|
}
|
|
75311
76022
|
function ghApi(args) {
|
|
75312
|
-
return withHubGhCredential((env) => execGh(["api", ...args],
|
|
76023
|
+
return withHubGhCredential((env) => execGh(["api", ...args], tmpdir4(), env));
|
|
75313
76024
|
}
|
|
75314
76025
|
function ghApiJson(endpoint, body) {
|
|
75315
76026
|
return withHubGhCredential(
|
|
75316
|
-
(env) => execGhWithInput(["api", endpoint, "--input", "-"],
|
|
76027
|
+
(env) => execGhWithInput(["api", endpoint, "--input", "-"], tmpdir4(), JSON.stringify(body), env)
|
|
75317
76028
|
);
|
|
75318
76029
|
}
|
|
75319
76030
|
var seenDeliveries = /* @__PURE__ */ new Set();
|
|
@@ -75384,11 +76095,11 @@ var GithubWebhookService = class {
|
|
|
75384
76095
|
if (running) {
|
|
75385
76096
|
await running;
|
|
75386
76097
|
}
|
|
75387
|
-
const
|
|
75388
|
-
if (checking.get(flight) ===
|
|
76098
|
+
const run5 = this.fetchAndIngest(trigger).finally(() => {
|
|
76099
|
+
if (checking.get(flight) === run5) checking.delete(flight);
|
|
75389
76100
|
});
|
|
75390
|
-
checking.set(flight,
|
|
75391
|
-
await
|
|
76101
|
+
checking.set(flight, run5);
|
|
76102
|
+
await run5;
|
|
75392
76103
|
}
|
|
75393
76104
|
async fetchAndIngest(trigger) {
|
|
75394
76105
|
const checks = [];
|
|
@@ -75428,11 +76139,11 @@ var GithubWebhookService = class {
|
|
|
75428
76139
|
const repo = sub.config.repo;
|
|
75429
76140
|
if (sub.source !== "github" || !repo) return;
|
|
75430
76141
|
const previous = registering.get(repo) ?? Promise.resolve();
|
|
75431
|
-
const
|
|
75432
|
-
if (registering.get(repo) ===
|
|
76142
|
+
const run5 = previous.then(() => this.register(sub.id, repo)).finally(() => {
|
|
76143
|
+
if (registering.get(repo) === run5) registering.delete(repo);
|
|
75433
76144
|
});
|
|
75434
|
-
registering.set(repo,
|
|
75435
|
-
await
|
|
76145
|
+
registering.set(repo, run5);
|
|
76146
|
+
await run5;
|
|
75436
76147
|
}
|
|
75437
76148
|
async register(subscriptionId, repo) {
|
|
75438
76149
|
const set2 = (status, error40) => {
|
|
@@ -75990,7 +76701,7 @@ async function machineHasLogin() {
|
|
|
75990
76701
|
if (loginCheck && now - loginCheck.at < LOGIN_CHECK_TTL_MS) return loginCheck.loggedIn;
|
|
75991
76702
|
let loggedIn = false;
|
|
75992
76703
|
try {
|
|
75993
|
-
await execGh(["auth", "status", "--hostname", "github.com"],
|
|
76704
|
+
await execGh(["auth", "status", "--hostname", "github.com"], tmpdir5());
|
|
75994
76705
|
loggedIn = true;
|
|
75995
76706
|
} catch {
|
|
75996
76707
|
loggedIn = false;
|
|
@@ -76006,10 +76717,10 @@ async function hubGhEnv() {
|
|
|
76006
76717
|
async function hubGhHasNoCredential() {
|
|
76007
76718
|
return !await machineHasLogin() && vaultService.findGitHubToken() === void 0;
|
|
76008
76719
|
}
|
|
76009
|
-
async function withHubGhCredential(
|
|
76720
|
+
async function withHubGhCredential(run5) {
|
|
76010
76721
|
const env = await hubGhEnv();
|
|
76011
76722
|
try {
|
|
76012
|
-
return await
|
|
76723
|
+
return await run5(env);
|
|
76013
76724
|
} catch (err) {
|
|
76014
76725
|
const message = err instanceof Error ? err.message : String(err);
|
|
76015
76726
|
if (env.GH_TOKEN === void 0 && UNAUTHENTICATED.test(message) && await hubGhHasNoCredential()) {
|
|
@@ -76172,11 +76883,11 @@ function parseBranchChecks(data) {
|
|
|
76172
76883
|
if (!suite.workflowRun) continue;
|
|
76173
76884
|
const workflowName = suite.workflowRun.workflow.name;
|
|
76174
76885
|
for (const node of suite.checkRuns.nodes) {
|
|
76175
|
-
const
|
|
76176
|
-
const key2 = `${workflowName}\0${
|
|
76886
|
+
const run5 = fromCheckRun(node, workflowName);
|
|
76887
|
+
const key2 = `${workflowName}\0${run5.name}`;
|
|
76177
76888
|
const existing = latest.get(key2);
|
|
76178
|
-
if (!existing || (
|
|
76179
|
-
latest.set(key2,
|
|
76889
|
+
if (!existing || (run5.startedAt ?? "") > (existing.startedAt ?? "")) {
|
|
76890
|
+
latest.set(key2, run5);
|
|
76180
76891
|
}
|
|
76181
76892
|
}
|
|
76182
76893
|
}
|
|
@@ -76588,7 +77299,7 @@ var GithubPollService = class {
|
|
|
76588
77299
|
/** One `gh` call, counted against the poll's budget. */
|
|
76589
77300
|
gh(args) {
|
|
76590
77301
|
this.state.callsLeft--;
|
|
76591
|
-
return withHubGhCredential((env) => execGh(args,
|
|
77302
|
+
return withHubGhCredential((env) => execGh(args, tmpdir6(), env));
|
|
76592
77303
|
}
|
|
76593
77304
|
/** Logs a failure once per distinct message, and skips the item for longer each time it repeats. */
|
|
76594
77305
|
fail(item, poll, err) {
|
|
@@ -77120,7 +77831,7 @@ var WatcherService = class {
|
|
|
77120
77831
|
this.poller.start();
|
|
77121
77832
|
const statuses = loadCurrentStatuses();
|
|
77122
77833
|
const runningScripts = worktreeScriptService.getRunning();
|
|
77123
|
-
const runningSetups = [...new Set(runningScripts.map((
|
|
77834
|
+
const runningSetups = [...new Set(runningScripts.map((run5) => run5.worktreeId))];
|
|
77124
77835
|
listener({ kind: "snapshot", statuses, runningSetups });
|
|
77125
77836
|
for (const event of this.loadCurrentBranchStatuses()) {
|
|
77126
77837
|
listener(event);
|
|
@@ -77250,47 +77961,14 @@ var agentSessionRegistry = new AgentSessionRegistryService();
|
|
|
77250
77961
|
// src/server/services/context-capture-service.ts
|
|
77251
77962
|
init_src();
|
|
77252
77963
|
|
|
77253
|
-
// src/server/services/_utils/context-redaction.ts
|
|
77254
|
-
function scanForSecrets(text3) {
|
|
77255
|
-
return scanText("", text3, [], { looseAssignments: true }).map((f2) => ({
|
|
77256
|
-
kind: RULE_LABELS[f2.rule] ?? f2.rule,
|
|
77257
|
-
line: f2.line
|
|
77258
|
-
}));
|
|
77259
|
-
}
|
|
77260
|
-
var PLACEHOLDER = "[redacted]";
|
|
77261
|
-
var EXTRA_PATTERNS = [
|
|
77262
|
-
/\bglpat-[A-Za-z0-9_-]{16,}/g,
|
|
77263
|
-
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g,
|
|
77264
|
-
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
|
|
77265
|
-
// Credentials inside URLs: scheme://user:password@host.
|
|
77266
|
-
/(?<=:\/\/[^\s/:@]+:)[^\s/@]+(?=@)/g
|
|
77267
|
-
];
|
|
77268
|
-
var ASSIGNMENT2 = /\b([A-Za-z0-9_.-]*(?:password|passwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|credential)[A-Za-z0-9_.-]*)(["']?\s*[:=]\s*["']?)([^\s"',;]{6,})/gi;
|
|
77269
|
-
var globally = (re) => new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
|
|
77270
|
-
function redactSecrets(text3) {
|
|
77271
|
-
let out = text3;
|
|
77272
|
-
for (const [kind, pattern] of SECRET_PATTERNS) {
|
|
77273
|
-
const re = globally(pattern);
|
|
77274
|
-
out = kind === "private key" ? out.replace(
|
|
77275
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g,
|
|
77276
|
-
PLACEHOLDER
|
|
77277
|
-
) : kind === "bearer token" ? out.replace(re, `Bearer ${PLACEHOLDER}`) : out.replace(re, PLACEHOLDER);
|
|
77278
|
-
}
|
|
77279
|
-
for (const pattern of EXTRA_PATTERNS) out = out.replace(pattern, PLACEHOLDER);
|
|
77280
|
-
return out.replace(
|
|
77281
|
-
ASSIGNMENT2,
|
|
77282
|
-
(match, name5, sep13, value) => value.startsWith("[redacted") ? match : `${name5}${sep13}${PLACEHOLDER}`
|
|
77283
|
-
);
|
|
77284
|
-
}
|
|
77285
|
-
|
|
77286
77964
|
// src/server/services/context-tools-service.ts
|
|
77287
77965
|
init_worktree_id();
|
|
77288
77966
|
import { randomBytes as randomBytes14 } from "node:crypto";
|
|
77289
77967
|
|
|
77290
77968
|
// src/server/services/context-store.ts
|
|
77291
|
-
import { mkdtempSync, rmSync as
|
|
77292
|
-
import { tmpdir as
|
|
77293
|
-
import { join as
|
|
77969
|
+
import { mkdtempSync as mkdtempSync2, rmSync as rmSync7 } from "node:fs";
|
|
77970
|
+
import { tmpdir as tmpdir7 } from "node:os";
|
|
77971
|
+
import { join as join49 } from "node:path";
|
|
77294
77972
|
var MAX_TERMS = 8;
|
|
77295
77973
|
var MAX_FILES_SCANNED = 5e3;
|
|
77296
77974
|
var SNIPPETS_PER_FILE = 3;
|
|
@@ -77349,11 +78027,11 @@ async function commitAppends(name5, edits, message) {
|
|
|
77349
78027
|
const sha = await contextService.withLock(name5, async () => {
|
|
77350
78028
|
let lastError = "";
|
|
77351
78029
|
for (let attempt = 0; attempt < WRITE_ATTEMPTS; attempt++) {
|
|
77352
|
-
const dir =
|
|
78030
|
+
const dir = mkdtempSync2(join49(tmpdir7(), "band-context-"));
|
|
77353
78031
|
try {
|
|
77354
78032
|
const headRef = (await git2(["symbolic-ref", "-q", "HEAD"], { cwd: repo })).trim();
|
|
77355
78033
|
const parent = await headCommit(repo);
|
|
77356
|
-
const env = contextGitEnv({ GIT_INDEX_FILE:
|
|
78034
|
+
const env = contextGitEnv({ GIT_INDEX_FILE: join49(dir, "index") });
|
|
77357
78035
|
if (parent) await git2(["read-tree", parent], { cwd: repo, env });
|
|
77358
78036
|
for (const edit of edits) {
|
|
77359
78037
|
let current3 = "";
|
|
@@ -77375,7 +78053,7 @@ async function commitAppends(name5, edits, message) {
|
|
|
77375
78053
|
if (update.code === 0) return commit;
|
|
77376
78054
|
lastError = update.stderr.trim();
|
|
77377
78055
|
} finally {
|
|
77378
|
-
|
|
78056
|
+
rmSync7(dir, { recursive: true, force: true });
|
|
77379
78057
|
}
|
|
77380
78058
|
}
|
|
77381
78059
|
throw new Error(`The context moved while writing: ${lastError.slice(0, 200)}`);
|
|
@@ -78163,7 +78841,7 @@ init_src();
|
|
|
78163
78841
|
// src/server/services/context-sync-service.ts
|
|
78164
78842
|
init_src();
|
|
78165
78843
|
var log47 = createLogger("context-sync");
|
|
78166
|
-
var
|
|
78844
|
+
var LOCAL_HOST_ID6 = "local";
|
|
78167
78845
|
var DEFAULT_PULL_TIMEOUT_MS2 = 1e4;
|
|
78168
78846
|
function pullTimeoutMs() {
|
|
78169
78847
|
return Number(process.env.BAND_CONTEXT_PULL_TIMEOUT_MS) || DEFAULT_PULL_TIMEOUT_MS2;
|
|
@@ -78179,8 +78857,10 @@ var ContextSyncService = class {
|
|
|
78179
78857
|
const project = projectId ? projectService.find(projectId) : void 0;
|
|
78180
78858
|
const worktree = projectId ? void 0 : worktreeService.resolve(scopeId);
|
|
78181
78859
|
if (projectId ? !project : !worktree) return null;
|
|
78182
|
-
const
|
|
78183
|
-
|
|
78860
|
+
const projectHostId = project ? projectFolderService.hostIdOf(project) : null;
|
|
78861
|
+
if (project && !projectHostId) return null;
|
|
78862
|
+
const host = project ? hostRegistry.hostById(projectHostId ?? LOCAL_HOST_ID6) : worktree.host;
|
|
78863
|
+
const labels3 = host.id === LOCAL_HOST_ID6 ? (await host.info().catch(() => null))?.labels ?? [] : tokenService.hostLabels(host.id) ?? [];
|
|
78184
78864
|
const rows = project ? contextService.forSession("", labels3, contextService.find(project.contextName)) : contextService.forSession(
|
|
78185
78865
|
worktree.repo.name,
|
|
78186
78866
|
labels3,
|
|
@@ -78228,7 +78908,7 @@ var ContextSyncService = class {
|
|
|
78228
78908
|
const { readOnly } = target;
|
|
78229
78909
|
const hostId2 = target.host.id;
|
|
78230
78910
|
const writable = target.specs.filter(
|
|
78231
|
-
(s2) => target.host.id !==
|
|
78911
|
+
(s2) => target.host.id !== LOCAL_HOST_ID6 || !readOnly.has(s2.name)
|
|
78232
78912
|
);
|
|
78233
78913
|
if (writable.length === 0) return [];
|
|
78234
78914
|
const results = await target.host.context.push({
|
|
@@ -79152,6 +79832,7 @@ var mcpProxyService = new McpProxyService();
|
|
|
79152
79832
|
// src/server/services/project-coordinator-service.ts
|
|
79153
79833
|
init_src();
|
|
79154
79834
|
init_worktree_id();
|
|
79835
|
+
init_status_event_bus();
|
|
79155
79836
|
|
|
79156
79837
|
// src/server/services/project-subscription-service.ts
|
|
79157
79838
|
init_src();
|
|
@@ -79345,11 +80026,11 @@ var ProjectSubscriptionService = class {
|
|
|
79345
80026
|
/** Records the member's PR and subscribes or unsubscribes the coordinator for it. */
|
|
79346
80027
|
syncMemberPr(worktreeId2, pr) {
|
|
79347
80028
|
const previous = this.memberLanes.get(worktreeId2) ?? Promise.resolve();
|
|
79348
|
-
const
|
|
79349
|
-
if (this.memberLanes.get(worktreeId2) ===
|
|
80029
|
+
const run5 = previous.catch(() => void 0).then(() => this.syncMemberPrNow(worktreeId2, pr)).finally(() => {
|
|
80030
|
+
if (this.memberLanes.get(worktreeId2) === run5) this.memberLanes.delete(worktreeId2);
|
|
79350
80031
|
});
|
|
79351
|
-
this.memberLanes.set(worktreeId2,
|
|
79352
|
-
return
|
|
80032
|
+
this.memberLanes.set(worktreeId2, run5);
|
|
80033
|
+
return run5;
|
|
79353
80034
|
}
|
|
79354
80035
|
async syncMemberPrNow(worktreeId2, pr) {
|
|
79355
80036
|
const row = projectService.projectOfWorker(worktreeId2);
|
|
@@ -79419,10 +80100,10 @@ var ProjectSubscriptionService = class {
|
|
|
79419
80100
|
const row = projectService.findByContext(name5);
|
|
79420
80101
|
if (!row?.coordinatorChatId) return;
|
|
79421
80102
|
const previous = this.contextLanes.get(name5) ?? Promise.resolve();
|
|
79422
|
-
const
|
|
79423
|
-
if (this.contextLanes.get(name5) ===
|
|
80103
|
+
const run5 = previous.catch(() => void 0).then(() => this.scanContext(row, name5)).catch((err) => log51.warn({ err, context: name5 }, "could not scan the context for new files")).finally(() => {
|
|
80104
|
+
if (this.contextLanes.get(name5) === run5) this.contextLanes.delete(name5);
|
|
79424
80105
|
});
|
|
79425
|
-
this.contextLanes.set(name5,
|
|
80106
|
+
this.contextLanes.set(name5, run5);
|
|
79426
80107
|
}
|
|
79427
80108
|
async scanContext(row, name5) {
|
|
79428
80109
|
const head = await this.headOf(name5);
|
|
@@ -79467,7 +80148,7 @@ var projectSubscriptionService = new ProjectSubscriptionService();
|
|
|
79467
80148
|
// src/server/services/project-coordinator-service.ts
|
|
79468
80149
|
var log52 = createLogger("project-coordinator");
|
|
79469
80150
|
var DEFAULT_COORDINATOR_AGENT = "claude-code";
|
|
79470
|
-
var
|
|
80151
|
+
var MAX_MESSAGE2 = 2e4;
|
|
79471
80152
|
var MAX_READ_TURNS = 20;
|
|
79472
80153
|
var MAX_TRANSCRIPT_CHARS = 3e4;
|
|
79473
80154
|
var CoordinatorToolError = class extends Error {
|
|
@@ -79484,7 +80165,7 @@ var ProjectCoordinatorService = class {
|
|
|
79484
80165
|
// ---- session start -----------------------------------------------------------------
|
|
79485
80166
|
/**
|
|
79486
80167
|
* Creates the coordinator's chat when the project has none, and starts the agent so it
|
|
79487
|
-
*
|
|
80168
|
+
* starts with it. Needs at least one repo. The agent starts in the background (it
|
|
79488
80169
|
* prepares the project folder first), so a slow or failing agent never fails the caller.
|
|
79489
80170
|
* Safe to call again: a running start is shared.
|
|
79490
80171
|
*/
|
|
@@ -79505,14 +80186,47 @@ var ProjectCoordinatorService = class {
|
|
|
79505
80186
|
* the new host when `coordinatorHostId` changed.
|
|
79506
80187
|
*/
|
|
79507
80188
|
async update(ref2, patch) {
|
|
79508
|
-
const
|
|
80189
|
+
const row = projectService.row(ref2);
|
|
80190
|
+
const before = projectFolderService.hostIdOf(row);
|
|
80191
|
+
const next = patch.coordinatorHostId?.trim() || null;
|
|
80192
|
+
if (before && next && next !== before) await this.assertMovable(row, before);
|
|
79509
80193
|
const updated = projectService.update(ref2, patch);
|
|
79510
80194
|
this.syncModel(updated.id);
|
|
79511
|
-
if (projectFolderService.
|
|
80195
|
+
if (projectFolderService.hostIdOf(projectService.row(updated.id)) !== before) {
|
|
79512
80196
|
await this.moveHost(updated.id);
|
|
79513
80197
|
}
|
|
79514
80198
|
return projectService.get(updated.id);
|
|
79515
80199
|
}
|
|
80200
|
+
/**
|
|
80201
|
+
* Refuses a move while a default-branch checkout on the old host has changes or commits that
|
|
80202
|
+
* exist nowhere else. A host that cannot be reached (offline, or no checkout there) cannot be
|
|
80203
|
+
* checked, so it does not block the move.
|
|
80204
|
+
*/
|
|
80205
|
+
async assertMovable(row, hostId2) {
|
|
80206
|
+
const host = hostRegistry.hostById(hostId2);
|
|
80207
|
+
const blockers = [];
|
|
80208
|
+
for (const { repo } of projectService.get(row.id).repos) {
|
|
80209
|
+
try {
|
|
80210
|
+
const status = await host.project.status({ project: row.name, repo });
|
|
80211
|
+
const reasons = [];
|
|
80212
|
+
if (status.dirty) reasons.push("uncommitted changes");
|
|
80213
|
+
if (status.ahead > 0) {
|
|
80214
|
+
reasons.push(`${status.ahead} unpushed commit${status.ahead === 1 ? "" : "s"}`);
|
|
80215
|
+
}
|
|
80216
|
+
if (reasons.length > 0) blockers.push(`${repo} has ${reasons.join(" and ")}`);
|
|
80217
|
+
} catch (err) {
|
|
80218
|
+
log52.info(
|
|
80219
|
+
{ project: row.name, repo, hostId: hostId2, err },
|
|
80220
|
+
"could not check a checkout before a move"
|
|
80221
|
+
);
|
|
80222
|
+
}
|
|
80223
|
+
}
|
|
80224
|
+
if (blockers.length > 0) {
|
|
80225
|
+
throw new ProjectConflictError(
|
|
80226
|
+
`Cannot move the coordinator off host "${hostId2}": ${blockers.join("; ")}. Commit and push or discard them first.`
|
|
80227
|
+
);
|
|
80228
|
+
}
|
|
80229
|
+
}
|
|
79516
80230
|
/**
|
|
79517
80231
|
* Moves the project's folder view to its current coordinator host after `coordinatorHostId`
|
|
79518
80232
|
* changed. Every chat of the view stops its agent process on the old host and starts a fresh
|
|
@@ -79532,6 +80246,26 @@ var ProjectCoordinatorService = class {
|
|
|
79532
80246
|
lastError(projectId) {
|
|
79533
80247
|
return this.startErrors.get(projectId) ?? null;
|
|
79534
80248
|
}
|
|
80249
|
+
/** Whether the project has no coordinator host yet, because no worker can run the agent. */
|
|
80250
|
+
isWaiting(projectId) {
|
|
80251
|
+
return this.waiting.has(projectId) && !this.projectById(projectId)?.coordinatorHostId;
|
|
80252
|
+
}
|
|
80253
|
+
waiting = /* @__PURE__ */ new Set();
|
|
80254
|
+
stopListening = null;
|
|
80255
|
+
/**
|
|
80256
|
+
* Starts the coordinators that wait for a worker when a worker comes online or reports new
|
|
80257
|
+
* capabilities. Called once the server is up.
|
|
80258
|
+
*/
|
|
80259
|
+
start() {
|
|
80260
|
+
this.stopListening ??= subscribe((event) => {
|
|
80261
|
+
if (event.kind !== "host-status-changed" || event.hostStatus !== "online") return;
|
|
80262
|
+
for (const id of [...this.waiting]) {
|
|
80263
|
+
void this.ensureCoordinator(id).catch((err) => {
|
|
80264
|
+
log52.warn({ projectId: id, err }, "could not start a waiting coordinator");
|
|
80265
|
+
});
|
|
80266
|
+
}
|
|
80267
|
+
});
|
|
80268
|
+
}
|
|
79535
80269
|
async create(projectId) {
|
|
79536
80270
|
try {
|
|
79537
80271
|
const row = projectService.row(projectId);
|
|
@@ -79547,11 +80281,14 @@ var ProjectCoordinatorService = class {
|
|
|
79547
80281
|
projectService.setCoordinator(row.id, chat.id);
|
|
79548
80282
|
}
|
|
79549
80283
|
this.startErrors.delete(row.id);
|
|
80284
|
+
await projectService.seedInstructions(row.id);
|
|
79550
80285
|
const chatId = chat.id;
|
|
79551
80286
|
void projectSubscriptionService.reconcile(row.id).catch((err) => {
|
|
79552
80287
|
log52.warn({ projectId: row.id, err }, "could not subscribe the coordinator");
|
|
79553
80288
|
});
|
|
79554
|
-
void agentSessionService.ensureSession(chatId, "prompt").catch((err) => {
|
|
80289
|
+
void agentSessionService.ensureSession(chatId, "prompt").then(() => this.waiting.delete(row.id)).catch((err) => {
|
|
80290
|
+
if (err instanceof WaitingForWorkerError) this.waiting.add(row.id);
|
|
80291
|
+
else this.waiting.delete(row.id);
|
|
79555
80292
|
this.startErrors.set(row.id, err instanceof Error ? err.message : String(err));
|
|
79556
80293
|
log52.warn({ projectId: row.id, chatId, err }, "coordinator agent did not start");
|
|
79557
80294
|
});
|
|
@@ -79643,7 +80380,7 @@ var ProjectCoordinatorService = class {
|
|
|
79643
80380
|
chatService.update(row.coordinatorChatId, { model: row.coordinatorModel });
|
|
79644
80381
|
}
|
|
79645
80382
|
}
|
|
79646
|
-
// ----
|
|
80383
|
+
// ---- session scope --------------------------------------------------------
|
|
79647
80384
|
/** The project a chat coordinates, or undefined for any other chat. */
|
|
79648
80385
|
projectOfChat(chatId) {
|
|
79649
80386
|
const chat = chatService.get(chatId);
|
|
@@ -79658,53 +80395,6 @@ var ProjectCoordinatorService = class {
|
|
|
79658
80395
|
return void 0;
|
|
79659
80396
|
}
|
|
79660
80397
|
}
|
|
79661
|
-
/** The charter the project page shows. */
|
|
79662
|
-
charterFor(ref2) {
|
|
79663
|
-
return this.charter(projectService.row(ref2));
|
|
79664
|
-
}
|
|
79665
|
-
/** The system prompt appended to a coordinator session. */
|
|
79666
|
-
charter(row) {
|
|
79667
|
-
const view4 = projectService.get(row.id);
|
|
79668
|
-
const p2 = view4.effectivePolicy;
|
|
79669
|
-
const repos2 = view4.repos.map((r) => `- ${r.repo}${r.role ? ` (role: ${r.role})` : ""}`).join("\n");
|
|
79670
|
-
const limits = [
|
|
79671
|
-
p2.maxConcurrent ? `at most ${p2.maxConcurrent} worker agents run at once` : "no limit on concurrent worker agents",
|
|
79672
|
-
p2.budgetUsd ? `a soft budget of $${p2.budgetUsd} for the project` : "no budget limit",
|
|
79673
|
-
`worker agents run at isolation "${p2.isolationFloor}" or stronger`,
|
|
79674
|
-
p2.labels.length ? `workers go only on hosts labeled ${p2.labels.join(", ")}` : "workers may go on any host"
|
|
79675
|
-
].join("; ");
|
|
79676
|
-
const autonomy = {
|
|
79677
|
-
observe: "Autonomy is observe. You may read project state and worker chats. You may not message, stop or dispatch anything. Report what you find and recommend actions to the user.",
|
|
79678
|
-
autonomous: p2.autoMerge ? "Autonomy is autonomous with auto-merge on. You may dispatch workers within the limits and merge pull requests whose CI passed." : "Autonomy is autonomous. You may dispatch workers within the limits. Merging still needs the user's confirmation."
|
|
79679
|
-
};
|
|
79680
|
-
return [
|
|
79681
|
-
`You are the coordinator of the Band project "${view4.name}".`,
|
|
79682
|
-
view4.description ? `
|
|
79683
|
-
Project description: ${view4.description}` : "",
|
|
79684
|
-
"\nYour job is to plan the work across the project's repos, hand it to worker agents, check on them and keep the user informed. Each worker agent works in one git worktree of one repo, with its own chat. Work that spans repos takes one worker per repo, and you keep them in step.",
|
|
79685
|
-
`
|
|
79686
|
-
Working directory. You run in the project folder on the coordinator host. It is the working copy of the project context (notes.md, docs/, learnings/, inbox/, handoffs/) and has a checkout of every project repo's default branch under repos/<repo>/ (the local branch band/${view4.name}/<default>, which tracks origin/<default>). Read the current code there, or through repo_read, repo_search and repo_log. Band fetches before your turns and fast-forwards a checkout only when it is clean, so a checkout with local changes can be behind origin. Code changes go through worker agents (worktree_create). Do not edit a default branch checkout unless the user explicitly asks you to. ${p2.autonomy === "autonomous" ? "When the user asks for such an edit, you may commit it, and you may push it because autonomy is autonomous." : `When the user asks for such an edit, commit it but do not push without the user's confirmation, because autonomy is ${p2.autonomy}.`} The folder repos/ is never synced to the context repo. Everything else in the project folder (notes, docs, inbox, handoffs, learnings and any file you or the user add) syncs to the hub and to every worker of the project on its own, within seconds: write a file and the others see it, with no commit or push.`,
|
|
79687
|
-
`
|
|
79688
|
-
Repos in this project:
|
|
79689
|
-
${repos2 || "- none yet"}`,
|
|
79690
|
-
`
|
|
79691
|
-
Models: you run on ${p2.models.coordinator}, worker agents on ${p2.models.worker}, reviewers on ${p2.models.reviewer}.`,
|
|
79692
|
-
`
|
|
79693
|
-
Policy: ${limits}.`,
|
|
79694
|
-
`
|
|
79695
|
-
${autonomy[p2.autonomy]}`,
|
|
79696
|
-
`
|
|
79697
|
-
Context. The project context repo "${view4.contextName}" is shared by every agent in the project, and the user context holds the user's preferences. Read them before you plan. Layout of the project context: notes.md (running notes), docs/ (design and contracts), media/ (screenshots, recordings), inbox/<agent>.md (pointers to handoffs for an agent), handoffs/ (one file per handoff), learnings/ (what agents learned). Use context_search to find things, context_append_learning to record what future agents should know, and context_handoff to pass work on. Write contracts between repos (API shapes, event formats) to docs/ so the agent on the other side can read them.`,
|
|
79698
|
-
`
|
|
79699
|
-
Tools. You act on the project only through the ${COORDINATOR_SERVER} tools: project_status (worktrees, agents, pull requests, spend), worktrees_list, worktree_create, chats_read, chats_send and worktree_stop, plus repo_read {repo, path}, repo_search {repo, query} and repo_log {repo, n} for the default branch checkouts. They are limited to this project's worktrees and repos. A refused call names the reason, so tell the user what blocked you instead of retrying.`,
|
|
79700
|
-
"\nDispatching. worktree_create starts a worker agent in a new worktree of one repo of the project, on the branch you name, with your brief in .am/BRIEF.md. Write the brief so the worker can act on it alone: the goal, the constraints, the contracts with other repos and what is out of scope, plus acceptance scenarios it can check. For work across repos, start one worker per repo, usually on the same branch name, put the contract between them in docs/ of the project folder first, and decide the order their pull requests merge in.",
|
|
79701
|
-
`
|
|
79702
|
-
Wake-ups. Band wakes you with a "Subscription update" message when something needs you: a worker chat ended its turn with an error, is waiting for a permission or an answer, or finished; a pull request of a project worktree has a review comment, a CI result or was merged or closed; or a new file appeared in inbox/ or handoffs/ of the project context. The message names the chat id or the file path. Read the source before you act on it. Events are batched, so one message can carry several. A pull request's subscriptions end when it merges or closes.`,
|
|
79703
|
-
`
|
|
79704
|
-
Notes and the inbox. You are the only writer of notes.md in the project context: workers do not edit it, so keep it current with decisions and status. learnings/ is append-only: add entries, never rewrite or delete them. Handoffs from workers land in handoffs/<stamp>-<from>-to-<to>.md and a pointer line in inbox/<to>.md. When you have dealt with an item in inbox/ or handoffs/, move the file into inbox/done/ (a plain move in the project folder, which syncs on its own). A file in inbox/done/ does not wake you again.`,
|
|
79705
|
-
"\nMerging. Never merge a pull request unless the user confirmed it in this conversation or auto-merge is on."
|
|
79706
|
-
].filter(Boolean).join("\n");
|
|
79707
|
-
}
|
|
79708
80398
|
// ---- tool logic ---------------------------------------------------------------------
|
|
79709
80399
|
workersOf(row) {
|
|
79710
80400
|
return projectService.workersOf(row).map((w2) => {
|
|
@@ -79846,8 +80536,8 @@ Notes and the inbox. You are the only writer of notes.md in the project context:
|
|
|
79846
80536
|
const chat = this.workerChat(row, chatId);
|
|
79847
80537
|
const text3 = message.trim();
|
|
79848
80538
|
if (!text3) throw new CoordinatorToolError("The message is empty.");
|
|
79849
|
-
if (text3.length >
|
|
79850
|
-
throw new CoordinatorToolError(`The message is over ${
|
|
80539
|
+
if (text3.length > MAX_MESSAGE2) {
|
|
80540
|
+
throw new CoordinatorToolError(`The message is over ${MAX_MESSAGE2} characters.`);
|
|
79851
80541
|
}
|
|
79852
80542
|
this.checkDispatch(row, { newRun: !this.isRunning(chatId) });
|
|
79853
80543
|
const result = submitOrQueueTask({ worktreeId: chatScope(chat), chatId, prompt: text3 });
|
|
@@ -79901,7 +80591,6 @@ Notes and the inbox. You are the only writer of notes.md in the project context:
|
|
|
79901
80591
|
}
|
|
79902
80592
|
};
|
|
79903
80593
|
var projectCoordinatorService = new ProjectCoordinatorService();
|
|
79904
|
-
projectFolderService.setInstructions((row) => projectCoordinatorService.charter(row));
|
|
79905
80594
|
|
|
79906
80595
|
// src/server/services/agent-session-service.ts
|
|
79907
80596
|
var log53 = createLogger("agent-sessions");
|
|
@@ -80003,6 +80692,7 @@ function stopRuntime(rt) {
|
|
|
80003
80692
|
rt.process = null;
|
|
80004
80693
|
rt.sessionId = null;
|
|
80005
80694
|
runtimes.delete(rt.chatId);
|
|
80695
|
+
mcpProxyService.revokeSession(rt.chatId);
|
|
80006
80696
|
}
|
|
80007
80697
|
function idleTimeoutMs2() {
|
|
80008
80698
|
const override = Number(process.env.BAND_AGENT_IDLE_TIMEOUT_MS);
|
|
@@ -80320,7 +81010,9 @@ async function ensureProcess(rt, def, cwd) {
|
|
|
80320
81010
|
const exited = handlers2.onExit;
|
|
80321
81011
|
handlers2.onExit = (code, stderr) => {
|
|
80322
81012
|
void grant?.revoke();
|
|
80323
|
-
|
|
81013
|
+
if (runtimes.get(rt.chatId) === rt && rt.generation === generation) {
|
|
81014
|
+
mcpProxyService.revokeSession(rt.chatId);
|
|
81015
|
+
}
|
|
80324
81016
|
exited?.(code, stderr);
|
|
80325
81017
|
};
|
|
80326
81018
|
let proc;
|
|
@@ -80427,6 +81119,13 @@ function sessionMcpServers(rt, proc) {
|
|
|
80427
81119
|
);
|
|
80428
81120
|
return [];
|
|
80429
81121
|
}
|
|
81122
|
+
if (!rt.relayEnv && hostRegistry.hostFor(rt.worktreeId).id !== hostRegistry.local.id) {
|
|
81123
|
+
log53.warn(
|
|
81124
|
+
{ chatId: rt.chatId, scope: rt.worktreeId },
|
|
81125
|
+
"the agent runs on a worker but has no relay grant, so no MCP servers are passed"
|
|
81126
|
+
);
|
|
81127
|
+
return [];
|
|
81128
|
+
}
|
|
80430
81129
|
mcpProxyService.revokeSession(rt.chatId);
|
|
80431
81130
|
const { token } = mcpProxyService.issueSessionToken(
|
|
80432
81131
|
rt.chatId,
|
|
@@ -80439,6 +81138,16 @@ function sessionMcpServers(rt, proc) {
|
|
|
80439
81138
|
if (rt.relayEnv?.BAND_TOKEN) {
|
|
80440
81139
|
headers.push({ name: "X-Band-Relay-Token", value: rt.relayEnv.BAND_TOKEN });
|
|
80441
81140
|
}
|
|
81141
|
+
log53.info(
|
|
81142
|
+
{
|
|
81143
|
+
chatId: rt.chatId,
|
|
81144
|
+
scope: rt.worktreeId,
|
|
81145
|
+
servers: names,
|
|
81146
|
+
baseUrl: base,
|
|
81147
|
+
viaRelay: Boolean(rt.relayEnv?.BAND_TOKEN)
|
|
81148
|
+
},
|
|
81149
|
+
"MCP servers for the session"
|
|
81150
|
+
);
|
|
80442
81151
|
return names.map((name5) => ({
|
|
80443
81152
|
type: "http",
|
|
80444
81153
|
name: name5,
|
|
@@ -81287,7 +81996,7 @@ var sessionIdSchema = external_exports2.string().regex(SESSION_ID_PATTERN3, "inv
|
|
|
81287
81996
|
|
|
81288
81997
|
// src/server/services/_utils/upload-utils.ts
|
|
81289
81998
|
import { mkdir as mkdir9, writeFile as writeFile6 } from "node:fs/promises";
|
|
81290
|
-
import { join as
|
|
81999
|
+
import { join as join50, resolve as resolve9, sep as sep7 } from "node:path";
|
|
81291
82000
|
init_state();
|
|
81292
82001
|
function decodeParts(fileParts) {
|
|
81293
82002
|
const baseTimestamp = Date.now();
|
|
@@ -81308,11 +82017,11 @@ function decodeParts(fileParts) {
|
|
|
81308
82017
|
return decoded;
|
|
81309
82018
|
}
|
|
81310
82019
|
async function saveUploadedFilesDetailed(fileParts) {
|
|
81311
|
-
const uploadDir =
|
|
82020
|
+
const uploadDir = join50(bandHome(), "uploads");
|
|
81312
82021
|
await mkdir9(uploadDir, { recursive: true });
|
|
81313
82022
|
const saved = [];
|
|
81314
82023
|
for (const part of decodeParts(fileParts)) {
|
|
81315
|
-
const filePath =
|
|
82024
|
+
const filePath = join50(uploadDir, part.storedName);
|
|
81316
82025
|
await writeFile6(filePath, part.buffer);
|
|
81317
82026
|
saved.push({
|
|
81318
82027
|
path: filePath,
|
|
@@ -81327,7 +82036,7 @@ async function saveUploadedFilesDetailed(fileParts) {
|
|
|
81327
82036
|
async function remoteUploadDir(host, worktreeId2) {
|
|
81328
82037
|
const dirs = (await host.info()).dirs;
|
|
81329
82038
|
if (!dirs) throw new Error(`Host ${host.id} has no directory for uploads`);
|
|
81330
|
-
return
|
|
82039
|
+
return join50(dirs.uploads, worktreeId2);
|
|
81331
82040
|
}
|
|
81332
82041
|
async function saveWorktreeUploads(worktreeId2, fileParts) {
|
|
81333
82042
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
@@ -81336,7 +82045,7 @@ async function saveWorktreeUploads(worktreeId2, fileParts) {
|
|
|
81336
82045
|
await host.fs.mkdir(dir, { recursive: true });
|
|
81337
82046
|
const saved = [];
|
|
81338
82047
|
for (const part of decodeParts(fileParts)) {
|
|
81339
|
-
const filePath =
|
|
82048
|
+
const filePath = join50(dir, part.storedName);
|
|
81340
82049
|
await host.fs.writeFile(filePath, part.buffer, { exclusive: true, mode: 384 });
|
|
81341
82050
|
saved.push({
|
|
81342
82051
|
path: filePath,
|
|
@@ -81352,19 +82061,19 @@ async function uploadPathFromUrl(worktreeId2, url2) {
|
|
|
81352
82061
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
81353
82062
|
const match = url2.match(/^\/api\/uploads\/(.+)$/);
|
|
81354
82063
|
if (!match) return null;
|
|
81355
|
-
if (host.id === hostRegistry.local.id) return
|
|
82064
|
+
if (host.id === hostRegistry.local.id) return join50(bandHome(), "uploads", match[1]);
|
|
81356
82065
|
const rest = match[1].split("/");
|
|
81357
82066
|
if (rest.length !== 2) return null;
|
|
81358
82067
|
try {
|
|
81359
82068
|
if (decodeURIComponent(rest[0]) !== worktreeId2) return null;
|
|
81360
|
-
return
|
|
82069
|
+
return join50(await remoteUploadDir(host, worktreeId2), decodeURIComponent(rest[1]));
|
|
81361
82070
|
} catch {
|
|
81362
82071
|
return null;
|
|
81363
82072
|
}
|
|
81364
82073
|
}
|
|
81365
82074
|
async function isWithinUploads(worktreeId2, path2) {
|
|
81366
82075
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
81367
|
-
const dir = host.id === hostRegistry.local.id ?
|
|
82076
|
+
const dir = host.id === hostRegistry.local.id ? join50(bandHome(), "uploads") : await remoteUploadDir(host, worktreeId2).catch(() => null);
|
|
81368
82077
|
if (!dir) return false;
|
|
81369
82078
|
const normalized2 = resolve9(path2);
|
|
81370
82079
|
return normalized2 === dir || normalized2.startsWith(dir + sep7);
|
|
@@ -88353,7 +89062,8 @@ function createContext(opts) {
|
|
|
88353
89062
|
return {
|
|
88354
89063
|
chatId: readHeader(opts?.req?.headers, CHAT_ID_HEADER),
|
|
88355
89064
|
worktreeId: readHeader(opts?.req?.headers, WORKTREE_ID_HEADER),
|
|
88356
|
-
admin: opts?.admin === true
|
|
89065
|
+
admin: opts?.admin === true,
|
|
89066
|
+
tokenId: opts?.tokenId
|
|
88357
89067
|
};
|
|
88358
89068
|
}
|
|
88359
89069
|
|
|
@@ -89306,7 +90016,7 @@ var chatsRouter = t.router({
|
|
|
89306
90016
|
init_zod();
|
|
89307
90017
|
|
|
89308
90018
|
// src/server/services/cli-service.ts
|
|
89309
|
-
import { execFile as
|
|
90019
|
+
import { execFile as execFile13 } from "node:child_process";
|
|
89310
90020
|
import {
|
|
89311
90021
|
accessSync as accessSync3,
|
|
89312
90022
|
constants as constants3,
|
|
@@ -89319,13 +90029,13 @@ import {
|
|
|
89319
90029
|
unlinkSync as unlinkSync5,
|
|
89320
90030
|
writeFileSync as writeFileSync7
|
|
89321
90031
|
} from "node:fs";
|
|
89322
|
-
import { homedir as
|
|
89323
|
-
import { basename as basename2, dirname as dirname14, join as
|
|
90032
|
+
import { homedir as homedir15, platform as platform2 } from "node:os";
|
|
90033
|
+
import { basename as basename2, dirname as dirname14, join as join51 } from "node:path";
|
|
89324
90034
|
function windowsBinDir() {
|
|
89325
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
89326
|
-
return
|
|
90035
|
+
const localAppData = process.env.LOCALAPPDATA || join51(homedir15(), "AppData", "Local");
|
|
90036
|
+
return join51(localAppData, "band", "bin");
|
|
89327
90037
|
}
|
|
89328
|
-
var SYMLINK_PATH = platform2() === "win32" ?
|
|
90038
|
+
var SYMLINK_PATH = platform2() === "win32" ? join51(windowsBinDir(), "band.cmd") : "/usr/local/bin/band";
|
|
89329
90039
|
function checkCliWindows() {
|
|
89330
90040
|
let contents;
|
|
89331
90041
|
try {
|
|
@@ -89356,8 +90066,8 @@ async function checkCli() {
|
|
|
89356
90066
|
} catch {
|
|
89357
90067
|
return "NotInstalled";
|
|
89358
90068
|
}
|
|
89359
|
-
const isCargoBuild = target.includes(
|
|
89360
|
-
const isElectronSidecar = target.endsWith(
|
|
90069
|
+
const isCargoBuild = target.includes(join51("apps", "cli", "target"));
|
|
90070
|
+
const isElectronSidecar = target.endsWith(join51("binaries", "band"));
|
|
89361
90071
|
if (isCargoBuild || isElectronSidecar) {
|
|
89362
90072
|
return "Installed";
|
|
89363
90073
|
}
|
|
@@ -89400,7 +90110,7 @@ function noBinaryError(env = process.env) {
|
|
|
89400
90110
|
}
|
|
89401
90111
|
function runReg(args) {
|
|
89402
90112
|
return new Promise((resolve16) => {
|
|
89403
|
-
|
|
90113
|
+
execFile13("reg", args, { windowsHide: true }, (err, stdout) => {
|
|
89404
90114
|
resolve16(err ? "" : stdout ?? "");
|
|
89405
90115
|
});
|
|
89406
90116
|
});
|
|
@@ -89530,328 +90240,6 @@ var clientStateRouter = t.router({
|
|
|
89530
90240
|
|
|
89531
90241
|
// src/server/api/context/router.ts
|
|
89532
90242
|
init_zod();
|
|
89533
|
-
|
|
89534
|
-
// src/server/services/context-browser-service.ts
|
|
89535
|
-
import { mkdtempSync as mkdtempSync2, rmSync as rmSync7 } from "node:fs";
|
|
89536
|
-
import { tmpdir as tmpdir7 } from "node:os";
|
|
89537
|
-
import { join as join50 } from "node:path";
|
|
89538
|
-
var MAX_FILE_BYTES2 = 1024 * 1024;
|
|
89539
|
-
var MAX_DIFF_BYTES2 = 200 * 1024;
|
|
89540
|
-
var MAX_MESSAGE2 = 200;
|
|
89541
|
-
var LOG_LIMIT = 200;
|
|
89542
|
-
var CONFLICT = /^(.*?)\.conflict-([A-Za-z0-9]+)((?:\.[^/.]+)?)$/;
|
|
89543
|
-
var ContextConflictError = class extends Error {
|
|
89544
|
-
constructor(message) {
|
|
89545
|
-
super(message);
|
|
89546
|
-
this.name = "ContextConflictError";
|
|
89547
|
-
}
|
|
89548
|
-
};
|
|
89549
|
-
function conflictOriginal(path2) {
|
|
89550
|
-
const m2 = CONFLICT.exec(path2);
|
|
89551
|
-
return m2 ? `${m2[1]}${m2[3]}` : null;
|
|
89552
|
-
}
|
|
89553
|
-
function hasControl(text3, allowWhitespace = false) {
|
|
89554
|
-
for (let i = 0; i < text3.length; i++) {
|
|
89555
|
-
const c = text3.charCodeAt(i);
|
|
89556
|
-
if (allowWhitespace && (c === 9 || c === 10 || c === 13)) continue;
|
|
89557
|
-
if (c < 32 || c === 127) return true;
|
|
89558
|
-
}
|
|
89559
|
-
return false;
|
|
89560
|
-
}
|
|
89561
|
-
function checkPath(path2) {
|
|
89562
|
-
const bad = !path2 || path2.length > 500 || path2.startsWith("/") || path2.endsWith("/") || path2.startsWith(":") || hasControl(path2) || path2.includes("\\") || path2.split("/").some((seg) => seg === "" || seg === "." || seg === ".." || seg === ".git");
|
|
89563
|
-
if (bad) throw new ContextInputError(`"${path2}" is not a valid path inside a context`);
|
|
89564
|
-
return path2;
|
|
89565
|
-
}
|
|
89566
|
-
function checkMessage(message) {
|
|
89567
|
-
const trimmed = message.trim();
|
|
89568
|
-
if (!trimmed) throw new ContextInputError("A commit message is required");
|
|
89569
|
-
if (hasControl(trimmed, true)) {
|
|
89570
|
-
throw new ContextInputError("The commit message holds control characters");
|
|
89571
|
-
}
|
|
89572
|
-
if (scanForSecrets(trimmed).length > 0) {
|
|
89573
|
-
throw new ContextInputError("Not saved: the commit message looks like it holds a credential.");
|
|
89574
|
-
}
|
|
89575
|
-
if (trimmed.length > MAX_MESSAGE2) {
|
|
89576
|
-
throw new ContextInputError(`The commit message is longer than ${MAX_MESSAGE2} characters`);
|
|
89577
|
-
}
|
|
89578
|
-
return trimmed;
|
|
89579
|
-
}
|
|
89580
|
-
var ContextBrowserService = class {
|
|
89581
|
-
repo(name5) {
|
|
89582
|
-
contextService.require(name5);
|
|
89583
|
-
return contextRepoPath(name5);
|
|
89584
|
-
}
|
|
89585
|
-
/** The commit HEAD points at, or null in a repo with no commits. */
|
|
89586
|
-
async head(repo) {
|
|
89587
|
-
const r = await runGit(["rev-parse", "--verify", "-q", "HEAD^{commit}"], { cwd: repo });
|
|
89588
|
-
return r.code === 0 ? r.stdout.trim() : null;
|
|
89589
|
-
}
|
|
89590
|
-
async tree(name5) {
|
|
89591
|
-
const repo = this.repo(name5);
|
|
89592
|
-
const head = await this.head(repo);
|
|
89593
|
-
if (!head) return { head, entries: [] };
|
|
89594
|
-
const out = await git2(["ls-tree", "-r", "-z", "--long", head], { cwd: repo });
|
|
89595
|
-
const entries = [];
|
|
89596
|
-
for (const rec of out.split("\0")) {
|
|
89597
|
-
if (!rec) continue;
|
|
89598
|
-
const tab = rec.indexOf(" ");
|
|
89599
|
-
const meta = rec.slice(0, tab).split(/\s+/);
|
|
89600
|
-
if (meta[1] !== "blob") continue;
|
|
89601
|
-
const path2 = rec.slice(tab + 1);
|
|
89602
|
-
entries.push({ path: path2, size: Number(meta[3]) || 0, conflictOf: conflictOriginal(path2) });
|
|
89603
|
-
}
|
|
89604
|
-
entries.sort((a, b2) => a.path.localeCompare(b2.path));
|
|
89605
|
-
return { head, entries };
|
|
89606
|
-
}
|
|
89607
|
-
async file(name5, path2) {
|
|
89608
|
-
const repo = this.repo(name5);
|
|
89609
|
-
checkPath(path2);
|
|
89610
|
-
const head = await this.head(repo);
|
|
89611
|
-
if (!head) throw new ContextInputError(`"${path2}" does not exist`);
|
|
89612
|
-
const size = await runGit(["cat-file", "-s", `${head}:${path2}`], { cwd: repo });
|
|
89613
|
-
if (size.code !== 0) throw new ContextInputError(`"${path2}" does not exist`);
|
|
89614
|
-
const bytes = Number(size.stdout.trim());
|
|
89615
|
-
const type = await git2(["cat-file", "-t", `${head}:${path2}`], { cwd: repo });
|
|
89616
|
-
if (type.trim() !== "blob") throw new ContextInputError(`"${path2}" is a folder`);
|
|
89617
|
-
if (bytes > MAX_FILE_BYTES2) {
|
|
89618
|
-
return { path: path2, binary: true, content: null, size: bytes, commit: head };
|
|
89619
|
-
}
|
|
89620
|
-
const content = await git2(["cat-file", "blob", `${head}:${path2}`], { cwd: repo });
|
|
89621
|
-
if (content.includes("\0") || content.includes("\uFFFD")) {
|
|
89622
|
-
return { path: path2, binary: true, content: null, size: bytes, commit: head };
|
|
89623
|
-
}
|
|
89624
|
-
return { path: path2, binary: false, content, size: bytes, commit: head };
|
|
89625
|
-
}
|
|
89626
|
-
async log(name5, path2, limit = 50) {
|
|
89627
|
-
const repo = this.repo(name5);
|
|
89628
|
-
if (path2) checkPath(path2);
|
|
89629
|
-
if (!await this.head(repo)) return [];
|
|
89630
|
-
const args = [
|
|
89631
|
-
"--literal-pathspecs",
|
|
89632
|
-
"log",
|
|
89633
|
-
"--no-color",
|
|
89634
|
-
`-n${Math.min(Math.max(limit, 1), LOG_LIMIT)}`,
|
|
89635
|
-
"--format=%H%x1f%an%x1f%at%x1f%s%x1e"
|
|
89636
|
-
];
|
|
89637
|
-
if (path2) args.push("--", path2);
|
|
89638
|
-
const out = await git2(args, { cwd: repo });
|
|
89639
|
-
return out.split("").map((rec) => rec.trim()).filter(Boolean).map((rec) => {
|
|
89640
|
-
const [sha = "", author = "", at = "0", subject = ""] = rec.split("");
|
|
89641
|
-
return { sha, author, at: Number(at) * 1e3, subject };
|
|
89642
|
-
});
|
|
89643
|
-
}
|
|
89644
|
-
async diff(name5, sha, path2) {
|
|
89645
|
-
const repo = this.repo(name5);
|
|
89646
|
-
if (!/^[0-9a-f]{40}$/.test(sha)) throw new ContextInputError("Not a commit id");
|
|
89647
|
-
if (path2) checkPath(path2);
|
|
89648
|
-
const exists4 = await runGit(["cat-file", "-e", `${sha}^{commit}`], { cwd: repo });
|
|
89649
|
-
if (exists4.code !== 0) throw new ContextInputError("No such commit");
|
|
89650
|
-
const scope3 = path2 ? ["--", path2] : [];
|
|
89651
|
-
const patch = await git2(
|
|
89652
|
-
[
|
|
89653
|
-
"--literal-pathspecs",
|
|
89654
|
-
"show",
|
|
89655
|
-
"--root",
|
|
89656
|
-
"--format=",
|
|
89657
|
-
"--patch",
|
|
89658
|
-
"--no-color",
|
|
89659
|
-
"-M",
|
|
89660
|
-
"--no-ext-diff",
|
|
89661
|
-
sha,
|
|
89662
|
-
...scope3
|
|
89663
|
-
],
|
|
89664
|
-
{ cwd: repo }
|
|
89665
|
-
);
|
|
89666
|
-
const names = await git2(
|
|
89667
|
-
[
|
|
89668
|
-
"diff-tree",
|
|
89669
|
-
"--root",
|
|
89670
|
-
"-m",
|
|
89671
|
-
"--first-parent",
|
|
89672
|
-
"--no-commit-id",
|
|
89673
|
-
"--name-status",
|
|
89674
|
-
"-r",
|
|
89675
|
-
"-M",
|
|
89676
|
-
sha
|
|
89677
|
-
],
|
|
89678
|
-
{ cwd: repo }
|
|
89679
|
-
);
|
|
89680
|
-
const files = names.split("\n").filter(Boolean).map((line2) => {
|
|
89681
|
-
const parts = line2.split(" ");
|
|
89682
|
-
return { status: parts[0] ?? "M", path: parts[parts.length - 1] ?? "" };
|
|
89683
|
-
});
|
|
89684
|
-
const patchBytes = Buffer.from(patch);
|
|
89685
|
-
const truncated = patchBytes.length > MAX_DIFF_BYTES2;
|
|
89686
|
-
return {
|
|
89687
|
-
diff: truncated ? patchBytes.subarray(0, MAX_DIFF_BYTES2).toString("utf8") : patch,
|
|
89688
|
-
truncated,
|
|
89689
|
-
files
|
|
89690
|
-
};
|
|
89691
|
-
}
|
|
89692
|
-
/** The newest learnings and handoffs files, one row per file, newest commit first. */
|
|
89693
|
-
async recent(name5, limit = 20) {
|
|
89694
|
-
const repo = this.repo(name5);
|
|
89695
|
-
if (!await this.head(repo)) return [];
|
|
89696
|
-
const out = await git2(
|
|
89697
|
-
[
|
|
89698
|
-
"log",
|
|
89699
|
-
"--no-color",
|
|
89700
|
-
"-n300",
|
|
89701
|
-
"--name-only",
|
|
89702
|
-
"--format=%x1e%H%x1f%at%x1f%s",
|
|
89703
|
-
"--",
|
|
89704
|
-
"learnings",
|
|
89705
|
-
"handoffs"
|
|
89706
|
-
],
|
|
89707
|
-
{ cwd: repo }
|
|
89708
|
-
);
|
|
89709
|
-
const seen = /* @__PURE__ */ new Set();
|
|
89710
|
-
const rows = [];
|
|
89711
|
-
for (const block of out.split("")) {
|
|
89712
|
-
const [header = "", ...files] = block.split("\n").filter((l, i) => i === 0 || l.trim());
|
|
89713
|
-
const [sha = "", at = "0", subject = ""] = header.split("");
|
|
89714
|
-
if (!sha) continue;
|
|
89715
|
-
for (const path2 of files) {
|
|
89716
|
-
const kind = path2.startsWith("learnings/") ? "learnings" : path2.startsWith("handoffs/") ? "handoffs" : null;
|
|
89717
|
-
if (!kind || path2.endsWith(".gitkeep") || seen.has(path2)) continue;
|
|
89718
|
-
seen.add(path2);
|
|
89719
|
-
rows.push({ path: path2, kind, sha, at: Number(at) * 1e3, subject });
|
|
89720
|
-
}
|
|
89721
|
-
}
|
|
89722
|
-
const { entries } = await this.tree(name5);
|
|
89723
|
-
const live = new Set(entries.map((e) => e.path));
|
|
89724
|
-
return rows.filter((r) => live.has(r.path)).slice(0, limit);
|
|
89725
|
-
}
|
|
89726
|
-
/**
|
|
89727
|
-
* Saves `content` at `path` as one commit. `base` is the commit the editor
|
|
89728
|
-
* loaded: when the file changed since, the save is refused so an edit never
|
|
89729
|
-
* overwrites a newer version unseen.
|
|
89730
|
-
*/
|
|
89731
|
-
async write(name5, path2, content, message, base) {
|
|
89732
|
-
checkPath(path2);
|
|
89733
|
-
if (Buffer.byteLength(content) > MAX_FILE_BYTES2) {
|
|
89734
|
-
throw new ContextInputError("The file is larger than 1 MiB");
|
|
89735
|
-
}
|
|
89736
|
-
const found = scanForSecrets(content);
|
|
89737
|
-
if (found.length > 0) {
|
|
89738
|
-
const list = found.map((f2) => `${f2.kind} on line ${f2.line}`).join(", ");
|
|
89739
|
-
throw new ContextInputError(
|
|
89740
|
-
`Not saved: the text looks like it holds a credential (${list}). Remove it first.`
|
|
89741
|
-
);
|
|
89742
|
-
}
|
|
89743
|
-
return this.commit(name5, [{ path: path2, content }], checkMessage(message), { base, guard: path2 });
|
|
89744
|
-
}
|
|
89745
|
-
/** Keeps one version of a conflicted file at its original path and deletes the conflict copy. */
|
|
89746
|
-
async resolveConflict(name5, conflictPath, keep, message) {
|
|
89747
|
-
checkPath(conflictPath);
|
|
89748
|
-
const original = conflictOriginal(conflictPath);
|
|
89749
|
-
if (!original) throw new ContextInputError(`"${conflictPath}" is not a conflict copy`);
|
|
89750
|
-
const msg = checkMessage(
|
|
89751
|
-
message ?? `Resolve conflict in ${original}: keep ${keep === "conflict" ? "the conflict copy" : "the original"}`
|
|
89752
|
-
);
|
|
89753
|
-
const repo = this.repo(name5);
|
|
89754
|
-
const result = await contextService.exclusive(name5, async () => {
|
|
89755
|
-
const head = await this.head(repo);
|
|
89756
|
-
if (!head) throw new ContextInputError(`"${conflictPath}" does not exist`);
|
|
89757
|
-
const blob2 = await runGit(["rev-parse", "--verify", "-q", `${head}:${conflictPath}`], {
|
|
89758
|
-
cwd: repo
|
|
89759
|
-
});
|
|
89760
|
-
if (blob2.code !== 0) throw new ContextInputError(`"${conflictPath}" does not exist`);
|
|
89761
|
-
if (keep === "original") {
|
|
89762
|
-
const orig = await runGit(["rev-parse", "--verify", "-q", `${head}:${original}`], {
|
|
89763
|
-
cwd: repo
|
|
89764
|
-
});
|
|
89765
|
-
if (orig.code !== 0) {
|
|
89766
|
-
throw new ContextInputError(
|
|
89767
|
-
`"${original}" does not exist, so "${conflictPath}" is not a conflict copy of anything`
|
|
89768
|
-
);
|
|
89769
|
-
}
|
|
89770
|
-
}
|
|
89771
|
-
const changes = [{ path: conflictPath }];
|
|
89772
|
-
if (keep === "conflict") changes.push({ path: original, blob: blob2.stdout.trim() });
|
|
89773
|
-
return this.commitLocked(repo, name5, head, changes, msg);
|
|
89774
|
-
});
|
|
89775
|
-
return { commit: result.commit, path: original };
|
|
89776
|
-
}
|
|
89777
|
-
commit(name5, changes, message, opts = {}) {
|
|
89778
|
-
const repo = this.repo(name5);
|
|
89779
|
-
return contextService.exclusive(name5, async () => {
|
|
89780
|
-
const head = await this.head(repo);
|
|
89781
|
-
if (head && opts.base && opts.guard && head !== opts.base) {
|
|
89782
|
-
const at = async (rev) => {
|
|
89783
|
-
const r = await runGit(["rev-parse", "--verify", "-q", `${rev}:${opts.guard}`], {
|
|
89784
|
-
cwd: repo
|
|
89785
|
-
});
|
|
89786
|
-
return r.code === 0 ? r.stdout.trim() : "";
|
|
89787
|
-
};
|
|
89788
|
-
if (await at(head) !== await at(opts.base)) {
|
|
89789
|
-
throw new ContextConflictError(
|
|
89790
|
-
`"${opts.guard}" changed since you opened it. Reload it to see the newer version.`
|
|
89791
|
-
);
|
|
89792
|
-
}
|
|
89793
|
-
}
|
|
89794
|
-
return this.commitLocked(repo, name5, head, changes, message, opts.author);
|
|
89795
|
-
});
|
|
89796
|
-
}
|
|
89797
|
-
/** Builds the commit in a private index. The caller holds the context's lock. */
|
|
89798
|
-
async commitLocked(repo, name5, head, changes, message, author) {
|
|
89799
|
-
const dir = mkdtempSync2(join50(tmpdir7(), "band-context-index-"));
|
|
89800
|
-
try {
|
|
89801
|
-
const env = contextGitEnv({
|
|
89802
|
-
GIT_INDEX_FILE: join50(dir, "index"),
|
|
89803
|
-
...author ? {
|
|
89804
|
-
GIT_AUTHOR_NAME: author,
|
|
89805
|
-
GIT_AUTHOR_EMAIL: `${author}@localhost`,
|
|
89806
|
-
GIT_COMMITTER_NAME: author,
|
|
89807
|
-
GIT_COMMITTER_EMAIL: `${author}@localhost`
|
|
89808
|
-
} : {}
|
|
89809
|
-
});
|
|
89810
|
-
const opts = { cwd: repo, env };
|
|
89811
|
-
if (head) await git2(["read-tree", head], opts);
|
|
89812
|
-
for (const change of changes) {
|
|
89813
|
-
if (change.content === void 0 && change.blob === void 0) {
|
|
89814
|
-
await git2(["update-index", "--index-info"], {
|
|
89815
|
-
...opts,
|
|
89816
|
-
input: `0 ${"0".repeat(40)} ${change.path}
|
|
89817
|
-
`
|
|
89818
|
-
});
|
|
89819
|
-
continue;
|
|
89820
|
-
}
|
|
89821
|
-
const oid = change.blob ?? (await git2(["hash-object", "-w", "--stdin"], { ...opts, input: change.content })).trim();
|
|
89822
|
-
const r = await runGit(
|
|
89823
|
-
["update-index", "--add", "--cacheinfo", `100644,${oid},${change.path}`],
|
|
89824
|
-
opts
|
|
89825
|
-
);
|
|
89826
|
-
if (r.code !== 0) {
|
|
89827
|
-
throw new ContextInputError(
|
|
89828
|
-
`Cannot save "${change.path}": ${r.stderr.trim().slice(0, 200)}`
|
|
89829
|
-
);
|
|
89830
|
-
}
|
|
89831
|
-
}
|
|
89832
|
-
const tree = (await git2(["write-tree"], opts)).trim();
|
|
89833
|
-
if (head) {
|
|
89834
|
-
const current3 = (await git2(["rev-parse", `${head}^{tree}`], opts)).trim();
|
|
89835
|
-
if (current3 === tree) return { commit: head, changed: false };
|
|
89836
|
-
}
|
|
89837
|
-
const commit = (await git2(["commit-tree", tree, ...head ? ["-p", head] : [], "-m", message], opts)).trim();
|
|
89838
|
-
const ref2 = (await git2(["symbolic-ref", "-q", "HEAD"], { cwd: repo })).trim();
|
|
89839
|
-
const moved = await runGit(["update-ref", "-m", "context browser", ref2, commit, head ?? ""], {
|
|
89840
|
-
cwd: repo
|
|
89841
|
-
});
|
|
89842
|
-
if (moved.code !== 0) {
|
|
89843
|
-
throw new ContextConflictError("The context changed while saving. Reload and try again.");
|
|
89844
|
-
}
|
|
89845
|
-
contextService.syncSoon(name5);
|
|
89846
|
-
return { commit, changed: true };
|
|
89847
|
-
} finally {
|
|
89848
|
-
rmSync7(dir, { recursive: true, force: true });
|
|
89849
|
-
}
|
|
89850
|
-
}
|
|
89851
|
-
};
|
|
89852
|
-
var contextBrowserService = new ContextBrowserService();
|
|
89853
|
-
|
|
89854
|
-
// src/server/api/context/router.ts
|
|
89855
90243
|
var name2 = external_exports2.string().trim().min(1).max(63);
|
|
89856
90244
|
var labels = external_exports2.array(external_exports2.string().min(1).max(100)).max(20);
|
|
89857
90245
|
var repoNames = external_exports2.array(external_exports2.string().trim().min(1).max(200)).max(100);
|
|
@@ -90048,7 +90436,7 @@ function throwAsTRPCError(err) {
|
|
|
90048
90436
|
init_zod();
|
|
90049
90437
|
|
|
90050
90438
|
// src/server/services/editor-service.ts
|
|
90051
|
-
import { isAbsolute as isAbsolute5, join as
|
|
90439
|
+
import { isAbsolute as isAbsolute5, join as join53, resolve as resolve10, sep as sep8 } from "node:path";
|
|
90052
90440
|
|
|
90053
90441
|
// ../../packages/shared/src/file-location.ts
|
|
90054
90442
|
function formatFileLocation(filePath, line2, options) {
|
|
@@ -90148,7 +90536,7 @@ function subscribeToFileChanges(worktreeId2, listener) {
|
|
|
90148
90536
|
|
|
90149
90537
|
// src/server/services/formatter.ts
|
|
90150
90538
|
init_src();
|
|
90151
|
-
import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute4, join as
|
|
90539
|
+
import { basename as basename3, dirname as dirname15, isAbsolute as isAbsolute4, join as join52, resolve as resolvePath3 } from "node:path";
|
|
90152
90540
|
import prettier from "prettier";
|
|
90153
90541
|
var log59 = createLogger("formatter");
|
|
90154
90542
|
var FormatterError = class extends Error {
|
|
@@ -90230,10 +90618,10 @@ async function resolveConfigOnHost(fs3, absFile, worktreePath) {
|
|
|
90230
90618
|
let dir = dirname15(absFile);
|
|
90231
90619
|
for (; ; ) {
|
|
90232
90620
|
for (const name5 of JSON_CONFIGS) {
|
|
90233
|
-
const parsed = await readJson2(fs3,
|
|
90621
|
+
const parsed = await readJson2(fs3, join52(dir, name5));
|
|
90234
90622
|
if (parsed && typeof parsed === "object") return withoutPlugins(parsed);
|
|
90235
90623
|
}
|
|
90236
|
-
const pkg = await readJson2(fs3,
|
|
90624
|
+
const pkg = await readJson2(fs3, join52(dir, "package.json"));
|
|
90237
90625
|
if (pkg && typeof pkg.prettier === "object" && pkg.prettier !== null) {
|
|
90238
90626
|
return withoutPlugins(pkg.prettier);
|
|
90239
90627
|
}
|
|
@@ -90271,7 +90659,7 @@ async function isInsideWorktree(fs3, absFile, worktreePath) {
|
|
|
90271
90659
|
realFile = await fs3.realpath(absFile);
|
|
90272
90660
|
} catch {
|
|
90273
90661
|
try {
|
|
90274
|
-
realFile =
|
|
90662
|
+
realFile = join52(await fs3.realpath(dirname15(absFile)), basename3(absFile));
|
|
90275
90663
|
} catch {
|
|
90276
90664
|
realFile = absFile;
|
|
90277
90665
|
}
|
|
@@ -90456,7 +90844,7 @@ async function canonicalizeMaybeMissing(fs3, p2) {
|
|
|
90456
90844
|
const prefix = parts.slice(0, i).join(sep8) || sep8;
|
|
90457
90845
|
try {
|
|
90458
90846
|
const canonicalPrefix = await fs3.realpath(prefix);
|
|
90459
|
-
return
|
|
90847
|
+
return join53(canonicalPrefix, ...parts.slice(i));
|
|
90460
90848
|
} catch {
|
|
90461
90849
|
}
|
|
90462
90850
|
}
|
|
@@ -90531,14 +90919,14 @@ import { basename as basename5, dirname as dirname16, isAbsolute as isAbsolute6
|
|
|
90531
90919
|
|
|
90532
90920
|
// src/server/services/repo-service.ts
|
|
90533
90921
|
import { existsSync as existsSync15 } from "node:fs";
|
|
90534
|
-
import { basename as basename4, join as
|
|
90922
|
+
import { basename as basename4, join as join55, posix as posix7, resolve as resolve11 } from "node:path";
|
|
90535
90923
|
init_worktree_id();
|
|
90536
90924
|
init_repos();
|
|
90537
90925
|
init_worktree_statuses();
|
|
90538
90926
|
|
|
90539
90927
|
// src/server/services/repo-avatar-service.ts
|
|
90540
90928
|
import { mkdir as mkdir10, readFile as readFile9, rename as rename4, writeFile as writeFile7 } from "node:fs/promises";
|
|
90541
|
-
import { join as
|
|
90929
|
+
import { join as join54 } from "node:path";
|
|
90542
90930
|
init_src();
|
|
90543
90931
|
init_repos();
|
|
90544
90932
|
init_settings();
|
|
@@ -90690,7 +91078,7 @@ var RepoAvatarService = class {
|
|
|
90690
91078
|
return null;
|
|
90691
91079
|
}
|
|
90692
91080
|
await mkdir10(cacheDir(), { recursive: true });
|
|
90693
|
-
const imagePath =
|
|
91081
|
+
const imagePath = join54(cacheDir(), `${key2}.img`);
|
|
90694
91082
|
await writeFile7(`${imagePath}.tmp`, result.bytes);
|
|
90695
91083
|
await rename4(`${imagePath}.tmp`, imagePath);
|
|
90696
91084
|
await this.writeMeta(key2, entry, {
|
|
@@ -90708,7 +91096,7 @@ var RepoAvatarService = class {
|
|
|
90708
91096
|
async readCached(key2, meta) {
|
|
90709
91097
|
if (meta?.status !== "ok" || !meta.contentType) return null;
|
|
90710
91098
|
try {
|
|
90711
|
-
const bytes = await readFile9(
|
|
91099
|
+
const bytes = await readFile9(join54(cacheDir(), `${key2}.img`));
|
|
90712
91100
|
return { bytes, contentType: meta.contentType };
|
|
90713
91101
|
} catch {
|
|
90714
91102
|
return null;
|
|
@@ -90718,7 +91106,7 @@ var RepoAvatarService = class {
|
|
|
90718
91106
|
entry.meta = meta;
|
|
90719
91107
|
entry.retryAfter = 0;
|
|
90720
91108
|
await mkdir10(cacheDir(), { recursive: true });
|
|
90721
|
-
await writeFile7(
|
|
91109
|
+
await writeFile7(join54(cacheDir(), `${key2}.json`), JSON.stringify(meta));
|
|
90722
91110
|
}
|
|
90723
91111
|
/** In-memory view of one owner's cache sidecar, loaded from disk once. */
|
|
90724
91112
|
async entry(ref2) {
|
|
@@ -90727,7 +91115,7 @@ var RepoAvatarService = class {
|
|
|
90727
91115
|
if (entry) return entry;
|
|
90728
91116
|
let meta = null;
|
|
90729
91117
|
try {
|
|
90730
|
-
meta = JSON.parse(await readFile9(
|
|
91118
|
+
meta = JSON.parse(await readFile9(join54(cacheDir(), `${key2}.json`), "utf-8"));
|
|
90731
91119
|
} catch {
|
|
90732
91120
|
}
|
|
90733
91121
|
entry = this.entries.get(key2) ?? { meta, retryAfter: 0 };
|
|
@@ -90749,7 +91137,7 @@ var RepoAvatarService = class {
|
|
|
90749
91137
|
}
|
|
90750
91138
|
};
|
|
90751
91139
|
function cacheDir() {
|
|
90752
|
-
return
|
|
91140
|
+
return join54(bandHome(), "cache", "github-avatars");
|
|
90753
91141
|
}
|
|
90754
91142
|
function cacheKey2(ref2) {
|
|
90755
91143
|
return `${ref2.host.replace(/[^a-z0-9.-]/g, "_")}__${ref2.owner.toLowerCase()}`;
|
|
@@ -90899,7 +91287,7 @@ var RepoService = class {
|
|
|
90899
91287
|
}
|
|
90900
91288
|
}
|
|
90901
91289
|
const resolvedPath = resolve11(path2);
|
|
90902
|
-
const kind = existsSync15(
|
|
91290
|
+
const kind = existsSync15(join55(resolvedPath, ".git")) ? "git" : "plain";
|
|
90903
91291
|
let defaultBranch = "main";
|
|
90904
91292
|
let worktrees2 = [];
|
|
90905
91293
|
if (kind === "git") {
|
|
@@ -91851,14 +92239,16 @@ var hostsRouter = t.router({
|
|
|
91851
92239
|
list: publicProcedure.input(
|
|
91852
92240
|
external_exports2.object({ limit: external_exports2.number().int().min(1).max(MAX_LIST_LIMIT).default(DEFAULT_LIST_LIMIT) }).default({ limit: DEFAULT_LIST_LIMIT })
|
|
91853
92241
|
).query(async ({ input }) => {
|
|
91854
|
-
const
|
|
92242
|
+
const all = tokenService.listHosts(input.limit);
|
|
92243
|
+
const hosts2 = all.filter((h) => h.id !== "local" || h.usable);
|
|
91855
92244
|
const local = hosts2.find((h) => h.id === "local");
|
|
91856
92245
|
if (local) {
|
|
91857
92246
|
const info = await hostRegistry.local.info().catch(() => null);
|
|
91858
92247
|
if (info) {
|
|
91859
92248
|
local.home = info.home ?? null;
|
|
91860
92249
|
local.capabilities = Object.entries(info.capabilities).filter(([, on]) => on).map(([name5]) => name5);
|
|
91861
|
-
local.
|
|
92250
|
+
local.report = info.report ?? null;
|
|
92251
|
+
local.agents = info.report ? info.report.agents.filter((a) => a.installed && a.loggedIn !== false).map((a) => a.type) : await agentTypesOn(hostRegistry.local);
|
|
91862
92252
|
local.tools = info.tools;
|
|
91863
92253
|
}
|
|
91864
92254
|
}
|
|
@@ -94630,10 +95020,10 @@ var pluginsRouter = t.router({
|
|
|
94630
95020
|
});
|
|
94631
95021
|
|
|
94632
95022
|
// ../../packages/host-local/src/process/install.ts
|
|
94633
|
-
import { execFile as
|
|
95023
|
+
import { execFile as execFile14 } from "node:child_process";
|
|
94634
95024
|
async function brewInstall(pkg, resolvedPath) {
|
|
94635
95025
|
await new Promise((resolve16, reject) => {
|
|
94636
|
-
|
|
95026
|
+
execFile14(
|
|
94637
95027
|
"brew",
|
|
94638
95028
|
["install", pkg],
|
|
94639
95029
|
{ env: { ...process.env, PATH: resolvedPath }, timeout: 12e4 },
|
|
@@ -94933,16 +95323,16 @@ async function checkout(fn) {
|
|
|
94933
95323
|
});
|
|
94934
95324
|
}
|
|
94935
95325
|
function present(view4) {
|
|
94936
|
-
return {
|
|
95326
|
+
return {
|
|
95327
|
+
...view4,
|
|
95328
|
+
coordinatorError: projectCoordinatorService.lastError(view4.id),
|
|
95329
|
+
coordinatorWaiting: projectCoordinatorService.isWaiting(view4.id)
|
|
95330
|
+
};
|
|
94937
95331
|
}
|
|
94938
95332
|
var projectsRouter = t.router({
|
|
94939
95333
|
list: publicProcedure.query(() => ({ projects: projectService.list().map(present) })),
|
|
94940
95334
|
/** `project` is an id or a name. */
|
|
94941
95335
|
get: publicProcedure.input(external_exports2.object({ project: ref })).query(({ input }) => guard3(() => ({ project: present(projectService.get(input.project)) }))),
|
|
94942
|
-
/** The charter the coordinator starts with, for the project's view. */
|
|
94943
|
-
charter: publicProcedure.input(external_exports2.object({ project: ref })).query(
|
|
94944
|
-
({ input }) => guard3(() => ({ charter: projectCoordinatorService.charterFor(input.project) }))
|
|
94945
|
-
),
|
|
94946
95336
|
create: adminProcedure.input(
|
|
94947
95337
|
external_exports2.object({
|
|
94948
95338
|
name: external_exports2.string().trim().min(1).max(63),
|
|
@@ -94984,7 +95374,6 @@ var projectsRouter = t.router({
|
|
|
94984
95374
|
({ input }) => guard3(async () => {
|
|
94985
95375
|
const { project, ...patch } = input;
|
|
94986
95376
|
const updated = await projectCoordinatorService.update(project, patch);
|
|
94987
|
-
void projectFolderService.writeInstructions(projectService.row(updated.id));
|
|
94988
95377
|
return { project: present(updated) };
|
|
94989
95378
|
})
|
|
94990
95379
|
),
|
|
@@ -95040,7 +95429,6 @@ var projectsRouter = t.router({
|
|
|
95040
95429
|
throw new ProjectConflictError(err instanceof Error ? err.message : String(err));
|
|
95041
95430
|
}
|
|
95042
95431
|
const view4 = projectService.removeRepo(row.id, input.repo);
|
|
95043
|
-
void projectFolderService.writeInstructions(projectService.row(row.id));
|
|
95044
95432
|
return { project: view4 };
|
|
95045
95433
|
})
|
|
95046
95434
|
),
|
|
@@ -95161,7 +95549,7 @@ var projectsRouter = t.router({
|
|
|
95161
95549
|
init_src();
|
|
95162
95550
|
init_zod();
|
|
95163
95551
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
95164
|
-
import { join as
|
|
95552
|
+
import { join as join56, resolve as resolve12, sep as sep9 } from "node:path";
|
|
95165
95553
|
init_state();
|
|
95166
95554
|
var log63 = createLogger("trpc.queue");
|
|
95167
95555
|
var queuedFileSchema = external_exports2.object({
|
|
@@ -95171,7 +95559,7 @@ var queuedFileSchema = external_exports2.object({
|
|
|
95171
95559
|
filename: external_exports2.string().optional()
|
|
95172
95560
|
});
|
|
95173
95561
|
function isPathWithinUploadDir(p2) {
|
|
95174
|
-
const uploadDir =
|
|
95562
|
+
const uploadDir = join56(bandHome(), "uploads");
|
|
95175
95563
|
const normalized2 = resolve12(p2);
|
|
95176
95564
|
if (normalized2 !== uploadDir && !normalized2.startsWith(uploadDir + sep9)) {
|
|
95177
95565
|
return false;
|
|
@@ -95911,12 +96299,12 @@ function allowed(args) {
|
|
|
95911
96299
|
var UNAUTHENTICATED2 = /gh auth login|GH_TOKEN|GITHUB_TOKEN|authentication|HTTP 401|bad credentials/i;
|
|
95912
96300
|
var MISSING_GH = /ENOENT|command not found|executable file not found|no such file/i;
|
|
95913
96301
|
var GH_TIMEOUT_MS2 = 3e4;
|
|
95914
|
-
function withTimeout2(
|
|
96302
|
+
function withTimeout2(run5) {
|
|
95915
96303
|
let timer;
|
|
95916
96304
|
const timeout = new Promise((_2, reject) => {
|
|
95917
96305
|
timer = setTimeout(() => reject(new Error("gh timed out")), GH_TIMEOUT_MS2);
|
|
95918
96306
|
});
|
|
95919
|
-
return Promise.race([
|
|
96307
|
+
return Promise.race([run5, timeout]).finally(() => clearTimeout(timer));
|
|
95920
96308
|
}
|
|
95921
96309
|
function firstLine2(err) {
|
|
95922
96310
|
return (err instanceof Error ? err.message : String(err)).split("\n")[0];
|
|
@@ -97190,6 +97578,11 @@ var terminalsRouters = {
|
|
|
97190
97578
|
init_zod();
|
|
97191
97579
|
var label = external_exports2.string().trim().min(1).max(100);
|
|
97192
97580
|
var tokensRouter = t.router({
|
|
97581
|
+
/** Which token this request uses. Any device token may ask; it returns no secret. */
|
|
97582
|
+
current: publicProcedure.query(({ ctx }) => ({
|
|
97583
|
+
tokenId: ctx.tokenId ?? null,
|
|
97584
|
+
admin: ctx.admin
|
|
97585
|
+
})),
|
|
97193
97586
|
list: adminProcedure.input(
|
|
97194
97587
|
external_exports2.object({ limit: external_exports2.number().int().min(1).max(MAX_LIST_LIMIT).default(DEFAULT_LIST_LIMIT) }).default({ limit: DEFAULT_LIST_LIMIT })
|
|
97195
97588
|
).query(({ input }) => ({ tokens: tokenService.list(input.limit) })),
|
|
@@ -97313,7 +97706,7 @@ var vaultRouter = t.router({
|
|
|
97313
97706
|
init_zod();
|
|
97314
97707
|
|
|
97315
97708
|
// src/server/services/diff-service.ts
|
|
97316
|
-
import { join as
|
|
97709
|
+
import { join as join57, resolve as resolve13, sep as sep10 } from "node:path";
|
|
97317
97710
|
init_src();
|
|
97318
97711
|
var log71 = createLogger("diff-service");
|
|
97319
97712
|
var EMPTY_TREE_ARGS = ["hash-object", "-t", "tree", "/dev/null"];
|
|
@@ -97411,7 +97804,7 @@ function parseFileStatuses(nameStatusOutput) {
|
|
|
97411
97804
|
return fileStatuses;
|
|
97412
97805
|
}
|
|
97413
97806
|
function assertWorktreeRelative(cwd, filePath) {
|
|
97414
|
-
const target = resolve13(
|
|
97807
|
+
const target = resolve13(join57(cwd, filePath));
|
|
97415
97808
|
if (target !== cwd && !target.startsWith(cwd + sep10)) {
|
|
97416
97809
|
throw new Error("Invalid path");
|
|
97417
97810
|
}
|
|
@@ -97426,7 +97819,7 @@ function assertWorktreeRelative(cwd, filePath) {
|
|
|
97426
97819
|
}
|
|
97427
97820
|
async function readUntrackedFileLines(host, cwd, file2) {
|
|
97428
97821
|
try {
|
|
97429
|
-
const content = new TextDecoder().decode(await host.fs.readFile(
|
|
97822
|
+
const content = new TextDecoder().decode(await host.fs.readFile(join57(cwd, file2)));
|
|
97430
97823
|
const lines = content.split("\n");
|
|
97431
97824
|
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
97432
97825
|
lines.pop();
|
|
@@ -97773,7 +98166,7 @@ var DiffService = class {
|
|
|
97773
98166
|
const compareBranch = options.compareBranch ?? defaultBranch;
|
|
97774
98167
|
const cwd = worktree.worktree.path;
|
|
97775
98168
|
const execGit2 = gitRunner(worktree.host);
|
|
97776
|
-
const hasGit = await worktree.host.fs.stat(
|
|
98169
|
+
const hasGit = await worktree.host.fs.stat(join57(cwd, ".git")).then(
|
|
97777
98170
|
() => true,
|
|
97778
98171
|
(err) => {
|
|
97779
98172
|
if (err instanceof HostOfflineError || err instanceof HostTimeoutError) throw err;
|
|
@@ -97811,7 +98204,7 @@ var DiffService = class {
|
|
|
97811
98204
|
applyLineCounts(status.staged, parseNumstat(stagedNumstat));
|
|
97812
98205
|
await Promise.all(
|
|
97813
98206
|
status.untracked.map(async (entry) => {
|
|
97814
|
-
const lines = await countUntrackedLines(worktree.host,
|
|
98207
|
+
const lines = await countUntrackedLines(worktree.host, join57(cwd, entry.path));
|
|
97815
98208
|
if (lines !== null) {
|
|
97816
98209
|
entry.additions = lines;
|
|
97817
98210
|
entry.deletions = 0;
|
|
@@ -97959,7 +98352,7 @@ var DiffService = class {
|
|
|
97959
98352
|
var diffService = new DiffService();
|
|
97960
98353
|
|
|
97961
98354
|
// src/server/services/files-service.ts
|
|
97962
|
-
import { dirname as dirname17, extname as extname3, join as
|
|
98355
|
+
import { dirname as dirname17, extname as extname3, join as join58, resolve as resolve14, sep as sep11 } from "node:path";
|
|
97963
98356
|
var MAX_FILE_SIZE2 = 1024 * 1024;
|
|
97964
98357
|
var LANG_MAP2 = {
|
|
97965
98358
|
".js": "javascript",
|
|
@@ -98023,7 +98416,7 @@ var FilesService = class {
|
|
|
98023
98416
|
throw new WorktreeNotFoundError(worktreeId2);
|
|
98024
98417
|
}
|
|
98025
98418
|
const root = worktree.worktree.path;
|
|
98026
|
-
const target = resolve14(
|
|
98419
|
+
const target = resolve14(join58(root, relative3));
|
|
98027
98420
|
const insideRoot = target === root || target.startsWith(root + sep11);
|
|
98028
98421
|
if (!insideRoot) {
|
|
98029
98422
|
throw new Error("Invalid path");
|
|
@@ -99902,7 +100295,7 @@ function createMcpServer(req) {
|
|
|
99902
100295
|
return server;
|
|
99903
100296
|
}
|
|
99904
100297
|
function registerContextTools(server, caller) {
|
|
99905
|
-
const
|
|
100298
|
+
const run5 = async (fn) => {
|
|
99906
100299
|
try {
|
|
99907
100300
|
return { content: [{ type: "text", text: JSON.stringify(await fn(), null, 2) }] };
|
|
99908
100301
|
} catch (err) {
|
|
@@ -99922,7 +100315,7 @@ function registerContextTools(server, caller) {
|
|
|
99922
100315
|
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK accepts Zod schemas as AnySchema
|
|
99923
100316
|
},
|
|
99924
100317
|
// biome-ignore lint/suspicious/noExplicitAny: handler args follow the schema above
|
|
99925
|
-
(args) =>
|
|
100318
|
+
(args) => run5(() => contextToolsService.search(caller, args.query, args.scope, args.limit))
|
|
99926
100319
|
);
|
|
99927
100320
|
server.registerTool(
|
|
99928
100321
|
"context_append_learning",
|
|
@@ -99935,7 +100328,7 @@ function registerContextTools(server, caller) {
|
|
|
99935
100328
|
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK accepts Zod schemas as AnySchema
|
|
99936
100329
|
},
|
|
99937
100330
|
// biome-ignore lint/suspicious/noExplicitAny: handler args follow the schema above
|
|
99938
|
-
(args) =>
|
|
100331
|
+
(args) => run5(() => contextToolsService.appendLearning(caller, args))
|
|
99939
100332
|
);
|
|
99940
100333
|
server.registerTool(
|
|
99941
100334
|
"context_handoff",
|
|
@@ -99949,7 +100342,7 @@ function registerContextTools(server, caller) {
|
|
|
99949
100342
|
// biome-ignore lint/suspicious/noExplicitAny: MCP SDK accepts Zod schemas as AnySchema
|
|
99950
100343
|
},
|
|
99951
100344
|
// biome-ignore lint/suspicious/noExplicitAny: handler args follow the schema above
|
|
99952
|
-
(args) =>
|
|
100345
|
+
(args) => run5(() => contextToolsService.handoff(caller, args))
|
|
99953
100346
|
);
|
|
99954
100347
|
}
|
|
99955
100348
|
async function handleMcpRequest(req, res) {
|
|
@@ -100245,8 +100638,8 @@ init_isolation();
|
|
|
100245
100638
|
var log73 = createLogger("project-dispatch");
|
|
100246
100639
|
var DEFAULT_WORKER_AGENT = "claude-code";
|
|
100247
100640
|
var lanes = /* @__PURE__ */ new Map();
|
|
100248
|
-
function serialized(projectId,
|
|
100249
|
-
const next = (lanes.get(projectId) ?? Promise.resolve()).then(
|
|
100641
|
+
function serialized(projectId, run5) {
|
|
100642
|
+
const next = (lanes.get(projectId) ?? Promise.resolve()).then(run5, run5);
|
|
100250
100643
|
const tail = next.catch(() => void 0);
|
|
100251
100644
|
lanes.set(projectId, tail);
|
|
100252
100645
|
void tail.then(() => {
|
|
@@ -100332,7 +100725,9 @@ var ProjectDispatchService = class {
|
|
|
100332
100725
|
repo: input.repo,
|
|
100333
100726
|
branch,
|
|
100334
100727
|
projectId: row.id,
|
|
100335
|
-
|
|
100728
|
+
// With the hub's own machine off there is no default host, so a call that names no
|
|
100729
|
+
// placement still goes through placement and gets the least loaded capable worker.
|
|
100730
|
+
...Object.keys(placement).length > 0 || !isLocalHostEnabled() ? { placement } : {},
|
|
100336
100731
|
brief: renderBrief({
|
|
100337
100732
|
title: input.title?.trim() || void 0,
|
|
100338
100733
|
project: row.name,
|
|
@@ -100436,12 +100831,12 @@ function lookUpToolsOnce(server, listPage) {
|
|
|
100436
100831
|
if (running) return running;
|
|
100437
100832
|
const last = lookupMissedAt.get(server.id);
|
|
100438
100833
|
if (last !== void 0 && Date.now() - last < LOOKUP_COOLDOWN_MS) return Promise.resolve();
|
|
100439
|
-
const
|
|
100834
|
+
const run5 = lookUpTools(server, listPage).catch(() => void 0).finally(() => {
|
|
100440
100835
|
lookupsInFlight.delete(server.id);
|
|
100441
100836
|
lookupMissedAt.set(server.id, Date.now());
|
|
100442
100837
|
});
|
|
100443
|
-
lookupsInFlight.set(server.id,
|
|
100444
|
-
return
|
|
100838
|
+
lookupsInFlight.set(server.id, run5);
|
|
100839
|
+
return run5;
|
|
100445
100840
|
}
|
|
100446
100841
|
async function lookUpTools(server, listPage) {
|
|
100447
100842
|
const tools = /* @__PURE__ */ new Map();
|
|
@@ -100600,7 +100995,7 @@ async function readBody2(req, max2) {
|
|
|
100600
100995
|
|
|
100601
100996
|
// src/server/api/mcp-proxy/coordinator.ts
|
|
100602
100997
|
var log74 = createLogger("coordinator-mcp");
|
|
100603
|
-
async function
|
|
100998
|
+
async function run4(fn) {
|
|
100604
100999
|
try {
|
|
100605
101000
|
const value = await fn();
|
|
100606
101001
|
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
@@ -100623,7 +101018,7 @@ function createServer2(projectId) {
|
|
|
100623
101018
|
server.registerTool(
|
|
100624
101019
|
name5,
|
|
100625
101020
|
{ description, inputSchema },
|
|
100626
|
-
(args) =>
|
|
101021
|
+
(args) => run4(() => handler(args ?? {}))
|
|
100627
101022
|
);
|
|
100628
101023
|
};
|
|
100629
101024
|
tool(
|
|
@@ -101131,7 +101526,7 @@ import {
|
|
|
101131
101526
|
statSync as statSync12,
|
|
101132
101527
|
writeFileSync as writeFileSync8
|
|
101133
101528
|
} from "node:fs";
|
|
101134
|
-
import { join as
|
|
101529
|
+
import { join as join59 } from "node:path";
|
|
101135
101530
|
var MEDIA_ID = /^[0-9a-f]{64}$/;
|
|
101136
101531
|
var DEFAULT_MAX_MEDIA_BYTES = 50 * 1024 * 1024;
|
|
101137
101532
|
var ALLOWED_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -101159,7 +101554,7 @@ function maxMediaBytes() {
|
|
|
101159
101554
|
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_MEDIA_BYTES;
|
|
101160
101555
|
}
|
|
101161
101556
|
function mediaDir() {
|
|
101162
|
-
return
|
|
101557
|
+
return join59(bandHome(), "media");
|
|
101163
101558
|
}
|
|
101164
101559
|
function allowedMediaType(header) {
|
|
101165
101560
|
const type = (header ?? "").split(";")[0].trim().toLowerCase();
|
|
@@ -101179,7 +101574,7 @@ var mediaService = {
|
|
|
101179
101574
|
}
|
|
101180
101575
|
const dir = mediaDir();
|
|
101181
101576
|
mkdirSync10(dir, { recursive: true });
|
|
101182
|
-
const tmp =
|
|
101577
|
+
const tmp = join59(dir, `.upload-${randomBytes17(8).toString("hex")}`);
|
|
101183
101578
|
const hash = createHash12("sha256");
|
|
101184
101579
|
const limit = maxMediaBytes();
|
|
101185
101580
|
let size = 0;
|
|
@@ -101200,11 +101595,11 @@ var mediaService = {
|
|
|
101200
101595
|
if (expectedId && expectedId !== id) {
|
|
101201
101596
|
throw new MediaRefusedError(400, "The id is not the SHA-256 of the content");
|
|
101202
101597
|
}
|
|
101203
|
-
const target =
|
|
101598
|
+
const target = join59(dir, id);
|
|
101204
101599
|
const created = !existsSync16(target);
|
|
101205
|
-
const typePath =
|
|
101600
|
+
const typePath = join59(dir, `${id}.type`);
|
|
101206
101601
|
if (created || !existsSync16(typePath)) {
|
|
101207
|
-
const typeTmp =
|
|
101602
|
+
const typeTmp = join59(dir, `.type-${randomBytes17(8).toString("hex")}`);
|
|
101208
101603
|
writeFileSync8(typeTmp, contentType);
|
|
101209
101604
|
renameSync6(typeTmp, typePath);
|
|
101210
101605
|
}
|
|
@@ -101225,15 +101620,15 @@ var mediaService = {
|
|
|
101225
101620
|
meta(id) {
|
|
101226
101621
|
if (!MEDIA_ID.test(id)) return null;
|
|
101227
101622
|
try {
|
|
101228
|
-
const size = statSync12(
|
|
101229
|
-
const contentType = readFileSync12(
|
|
101623
|
+
const size = statSync12(join59(mediaDir(), id)).size;
|
|
101624
|
+
const contentType = readFileSync12(join59(mediaDir(), `${id}.type`), "utf8").trim();
|
|
101230
101625
|
return { size, contentType };
|
|
101231
101626
|
} catch {
|
|
101232
101627
|
return null;
|
|
101233
101628
|
}
|
|
101234
101629
|
},
|
|
101235
101630
|
read(id, range) {
|
|
101236
|
-
return createReadStream4(
|
|
101631
|
+
return createReadStream4(join59(mediaDir(), id), range);
|
|
101237
101632
|
}
|
|
101238
101633
|
};
|
|
101239
101634
|
|
|
@@ -101747,14 +102142,14 @@ async function handleOAuthCallback(req, res) {
|
|
|
101747
102142
|
|
|
101748
102143
|
// src/server/api/web-app-manifest.ts
|
|
101749
102144
|
import { createReadStream as createReadStream5, statSync as statSync13 } from "node:fs";
|
|
101750
|
-
import { join as
|
|
102145
|
+
import { join as join60 } from "node:path";
|
|
101751
102146
|
var WEB_APP_MANIFEST_PATH = "/manifest.webmanifest";
|
|
101752
102147
|
var ICON_FILES = ["band-192.png", "band-512.png", "apple-touch-icon.png"];
|
|
101753
102148
|
function handleWebAppManifest(req, res, publicDir) {
|
|
101754
102149
|
if (req.method !== "GET" && req.method !== "HEAD") return false;
|
|
101755
102150
|
const pathname = req.url?.split("?")[0];
|
|
101756
102151
|
if (pathname === WEB_APP_MANIFEST_PATH) {
|
|
101757
|
-
sendFile(req, res,
|
|
102152
|
+
sendFile(req, res, join60(publicDir, "manifest.webmanifest"), {
|
|
101758
102153
|
"Content-Type": "application/manifest+json",
|
|
101759
102154
|
"Cache-Control": "no-cache"
|
|
101760
102155
|
});
|
|
@@ -101762,7 +102157,7 @@ function handleWebAppManifest(req, res, publicDir) {
|
|
|
101762
102157
|
}
|
|
101763
102158
|
const icon = ICON_FILES.find((name5) => pathname === `/icons/${name5}`);
|
|
101764
102159
|
if (!icon) return false;
|
|
101765
|
-
sendFile(req, res,
|
|
102160
|
+
sendFile(req, res, join60(publicDir, "icons", icon), {
|
|
101766
102161
|
"Content-Type": "image/png",
|
|
101767
102162
|
"Cache-Control": "public, max-age=86400"
|
|
101768
102163
|
});
|
|
@@ -102022,6 +102417,10 @@ var MSG_KEY_EVENT = 4;
|
|
|
102022
102417
|
var MSG_POINTER_EVENT = 5;
|
|
102023
102418
|
var MSG_CLIENT_CUT_TEXT = 6;
|
|
102024
102419
|
var MAX_MESSAGE_BYTES2 = 1024 * 1024;
|
|
102420
|
+
function cutTextSize(payload) {
|
|
102421
|
+
if (8 + payload > MAX_MESSAGE_BYTES2) throw new RfbFilterError("RFB message too large");
|
|
102422
|
+
return 8 + payload;
|
|
102423
|
+
}
|
|
102025
102424
|
var RfbFilterError = class extends Error {
|
|
102026
102425
|
};
|
|
102027
102426
|
var RfbClientFilter = class {
|
|
@@ -102072,7 +102471,7 @@ var RfbClientFilter = class {
|
|
|
102072
102471
|
case MSG_POINTER_EVENT:
|
|
102073
102472
|
return 6;
|
|
102074
102473
|
case MSG_CLIENT_CUT_TEXT:
|
|
102075
|
-
return buf.length < 8 ? null :
|
|
102474
|
+
return buf.length < 8 ? null : cutTextSize(Math.abs(buf.readInt32BE(4)));
|
|
102076
102475
|
default:
|
|
102077
102476
|
throw new RfbFilterError(`Unsupported RFB message type ${buf[0]}`);
|
|
102078
102477
|
}
|
|
@@ -102419,7 +102818,7 @@ async function handleLspConnection(ws, req) {
|
|
|
102419
102818
|
}
|
|
102420
102819
|
|
|
102421
102820
|
// src/server/services/_utils/hosted-file.ts
|
|
102422
|
-
import { basename as basename6, join as
|
|
102821
|
+
import { basename as basename6, join as join61 } from "node:path";
|
|
102423
102822
|
async function openHostedFile(kind, worktreeId2, rawName) {
|
|
102424
102823
|
const host = hostRegistry.hostFor(worktreeId2);
|
|
102425
102824
|
if (host.id === hostRegistry.local.id) return null;
|
|
@@ -102433,7 +102832,7 @@ async function openHostedFile(kind, worktreeId2, rawName) {
|
|
|
102433
102832
|
try {
|
|
102434
102833
|
const dirs = (await host.info()).dirs;
|
|
102435
102834
|
if (!dirs) return null;
|
|
102436
|
-
const path2 =
|
|
102835
|
+
const path2 = join61(dirs[kind], worktreeId2, name5);
|
|
102437
102836
|
const stat8 = await host.fs.stat(path2, { followSymlinks: true });
|
|
102438
102837
|
if (stat8.kind !== "file") return null;
|
|
102439
102838
|
return { size: stat8.size, stream: host.fs.readStream(path2) };
|
|
@@ -102479,7 +102878,7 @@ async function listenWithFallback(server, startPort, attempts = 20) {
|
|
|
102479
102878
|
init_src();
|
|
102480
102879
|
init_worktree_id();
|
|
102481
102880
|
var log82 = createLogger("context-autosync");
|
|
102482
|
-
var
|
|
102881
|
+
var LOCAL_HOST_ID7 = "local";
|
|
102483
102882
|
var DEFAULT_INTERVAL_MS2 = 5e3;
|
|
102484
102883
|
var SOON_MS = 300;
|
|
102485
102884
|
var MAX_STALE_WAIT = 12;
|
|
@@ -102620,18 +103019,20 @@ var ContextAutosyncService = class {
|
|
|
102620
103019
|
const online = new Set(
|
|
102621
103020
|
tokenService.listHosts().filter((h) => h.status === "online").map((h) => h.id)
|
|
102622
103021
|
);
|
|
102623
|
-
online.add(
|
|
103022
|
+
online.add(LOCAL_HOST_ID7);
|
|
102624
103023
|
let localLabels;
|
|
102625
103024
|
const lifecycle = ephemeralLifecycleService.states();
|
|
102626
103025
|
const out = [];
|
|
102627
103026
|
for (const project of projectService.rows()) {
|
|
102628
103027
|
const context = contextService.find(project.contextName);
|
|
102629
103028
|
if (!context) continue;
|
|
102630
|
-
const hostIds = /* @__PURE__ */ new Set(
|
|
103029
|
+
const hostIds = /* @__PURE__ */ new Set();
|
|
103030
|
+
const folderHost = projectFolderService.hostIdOf(project);
|
|
103031
|
+
if (folderHost) hostIds.add(folderHost);
|
|
102631
103032
|
for (const w2 of projectService.allWorktreesOf(project.id)) {
|
|
102632
103033
|
const id = toWorktreeId(w2.repoName, w2.name);
|
|
102633
103034
|
if (lifecycle.has(id)) continue;
|
|
102634
|
-
hostIds.add(w2.hostId ??
|
|
103035
|
+
hostIds.add(w2.hostId ?? LOCAL_HOST_ID7);
|
|
102635
103036
|
}
|
|
102636
103037
|
for (const hostId2 of hostIds) {
|
|
102637
103038
|
if (!online.has(hostId2)) continue;
|
|
@@ -102641,10 +103042,10 @@ var ContextAutosyncService = class {
|
|
|
102641
103042
|
} catch {
|
|
102642
103043
|
continue;
|
|
102643
103044
|
}
|
|
102644
|
-
if (hostId2 ===
|
|
103045
|
+
if (hostId2 === LOCAL_HOST_ID7 && localLabels === void 0) {
|
|
102645
103046
|
localLabels = (await host.info().catch(() => null))?.labels ?? [];
|
|
102646
103047
|
}
|
|
102647
|
-
const labels3 = hostId2 ===
|
|
103048
|
+
const labels3 = hostId2 === LOCAL_HOST_ID7 ? localLabels ?? [] : tokenService.hostLabels(hostId2) ?? [];
|
|
102648
103049
|
if (contextService.forSession("", labels3, context).every((r) => r.name !== context.name)) {
|
|
102649
103050
|
continue;
|
|
102650
103051
|
}
|
|
@@ -102652,7 +103053,7 @@ var ContextAutosyncService = class {
|
|
|
102652
103053
|
project,
|
|
102653
103054
|
host,
|
|
102654
103055
|
spec: { name: context.name, kind: "project" },
|
|
102655
|
-
readOnly: hostId2 !==
|
|
103056
|
+
readOnly: hostId2 !== LOCAL_HOST_ID7 && context.workerAccess === "read-only"
|
|
102656
103057
|
});
|
|
102657
103058
|
}
|
|
102658
103059
|
}
|
|
@@ -102883,7 +103284,7 @@ init_repos();
|
|
|
102883
103284
|
// src/server/services/worker-cli-service.ts
|
|
102884
103285
|
import { createHash as createHash13 } from "node:crypto";
|
|
102885
103286
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
102886
|
-
import { join as
|
|
103287
|
+
import { join as join62 } from "node:path";
|
|
102887
103288
|
init_src();
|
|
102888
103289
|
var log84 = createLogger("worker-cli");
|
|
102889
103290
|
var RPC_INVALID_PARAMS2 = -32602;
|
|
@@ -102891,7 +103292,7 @@ var PLATFORM_NAME = /^[a-z0-9_]{1,16}$/;
|
|
|
102891
103292
|
function candidatesFor(platform3, arch) {
|
|
102892
103293
|
const out = [];
|
|
102893
103294
|
const dir = process.env.BAND_CLI_BINARIES_DIR;
|
|
102894
|
-
if (dir) out.push(
|
|
103295
|
+
if (dir) out.push(join62(dir, `band-${platform3}-${arch}${platform3 === "win32" ? ".exe" : ""}`));
|
|
102895
103296
|
if (platform3 === process.platform && arch === process.arch) {
|
|
102896
103297
|
const own = findCliBinary();
|
|
102897
103298
|
if (own) out.push(own);
|
|
@@ -103441,7 +103842,8 @@ var workerRelayService = new WorkerRelayService();
|
|
|
103441
103842
|
// src/server/services/worker-link-service.ts
|
|
103442
103843
|
var log86 = createLogger("worker-link");
|
|
103443
103844
|
var repoQueries4 = new RepoQueries();
|
|
103444
|
-
var
|
|
103845
|
+
var LOCAL_HOST_ID8 = "local";
|
|
103846
|
+
var CAPABILITY_REFRESH_MS = 3 * 6e4;
|
|
103445
103847
|
var WORKER_CONNECT_PATH = "/api/workers/connect";
|
|
103446
103848
|
var WORKER_EXCHANGE_PATH = "/api/workers/exchange";
|
|
103447
103849
|
var MAX_EXCHANGE_BODY_BYTES = 4 * 1024;
|
|
@@ -103457,6 +103859,7 @@ var WorkerLinkService = class {
|
|
|
103457
103859
|
handleProtocols: selectWsProtocol
|
|
103458
103860
|
});
|
|
103459
103861
|
unsubscribeRevoke = null;
|
|
103862
|
+
refreshTimer = null;
|
|
103460
103863
|
constructor(options = {}) {
|
|
103461
103864
|
this.tokens = options.tokens ?? tokenService;
|
|
103462
103865
|
this.queries = options.queries ?? new TokenQueries();
|
|
@@ -103482,20 +103885,59 @@ var WorkerLinkService = class {
|
|
|
103482
103885
|
/** Makes every known worker resolvable, so a worktree on an offline host still finds its host. */
|
|
103483
103886
|
start() {
|
|
103484
103887
|
for (const row of this.queries.listHosts(1e3)) {
|
|
103485
|
-
if (row.id !==
|
|
103888
|
+
if (row.id !== LOCAL_HOST_ID8) this.remoteHost(row.id);
|
|
103486
103889
|
}
|
|
103487
103890
|
for (const row of this.queries.listHosts(1e3)) {
|
|
103488
|
-
if (row.id !==
|
|
103891
|
+
if (row.id !== LOCAL_HOST_ID8 && row.status === "online") {
|
|
103489
103892
|
this.queries.setHostStatus(row.id, "offline");
|
|
103490
103893
|
}
|
|
103491
103894
|
}
|
|
103895
|
+
const every = Number(process.env.BAND_CAPABILITY_REFRESH_MS);
|
|
103896
|
+
this.refreshTimer = setInterval(
|
|
103897
|
+
() => void this.refreshReports(),
|
|
103898
|
+
Number.isFinite(every) && every > 0 ? every : CAPABILITY_REFRESH_MS
|
|
103899
|
+
);
|
|
103900
|
+
this.refreshTimer.unref();
|
|
103492
103901
|
this.unsubscribeRevoke = this.tokens.onRevoked((row) => {
|
|
103493
103902
|
if (row.kind === "worker_session" && row.hostId) {
|
|
103494
103903
|
if (this.server.drop(row.hostId, "token revoked")) log86.info(`cut link of ${row.hostId}`);
|
|
103495
103904
|
}
|
|
103496
103905
|
});
|
|
103497
103906
|
}
|
|
103907
|
+
/**
|
|
103908
|
+
* Reads `host.info` again from every online worker and stores its capability report. The call is
|
|
103909
|
+
* passive, so it does not keep an ephemeral worker awake. A change goes out as a host status
|
|
103910
|
+
* event, which Settings > Hosts follows and a coordinator waiting for a worker listens to.
|
|
103911
|
+
*/
|
|
103912
|
+
async refreshReports() {
|
|
103913
|
+
if (this.refreshing) return;
|
|
103914
|
+
this.refreshing = true;
|
|
103915
|
+
try {
|
|
103916
|
+
await Promise.allSettled(
|
|
103917
|
+
this.queries.listHosts(1e3).filter((row) => row.id !== LOCAL_HOST_ID8 && row.status === "online").map((row) => this.refreshReport(row))
|
|
103918
|
+
);
|
|
103919
|
+
} finally {
|
|
103920
|
+
this.refreshing = false;
|
|
103921
|
+
}
|
|
103922
|
+
}
|
|
103923
|
+
refreshing = false;
|
|
103924
|
+
async refreshReport(row) {
|
|
103925
|
+
try {
|
|
103926
|
+
const info = await this.remoteHost(row.id).info();
|
|
103927
|
+
const { repoMappings: _mappings, ...facts } = info;
|
|
103928
|
+
const stored = { ...row.info ?? {}, ...facts };
|
|
103929
|
+
const changed = reportKey(stored.report) !== reportKey(row.info?.report);
|
|
103930
|
+
this.queries.setHostInfo(row.id, stored);
|
|
103931
|
+
if (changed) this.publish(row.id, "online");
|
|
103932
|
+
} catch (err) {
|
|
103933
|
+
log86.debug(
|
|
103934
|
+
`could not refresh the report of ${row.id}: ${err instanceof Error ? err.message : err}`
|
|
103935
|
+
);
|
|
103936
|
+
}
|
|
103937
|
+
}
|
|
103498
103938
|
async close() {
|
|
103939
|
+
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
103940
|
+
this.refreshTimer = null;
|
|
103499
103941
|
this.unsubscribeRevoke?.();
|
|
103500
103942
|
for (const ws of this.wss.clients) ws.terminate();
|
|
103501
103943
|
await this.server.close();
|
|
@@ -103618,12 +104060,16 @@ var WorkerLinkService = class {
|
|
|
103618
104060
|
}
|
|
103619
104061
|
};
|
|
103620
104062
|
var workerLinkService = new WorkerLinkService();
|
|
104063
|
+
function reportKey(report2) {
|
|
104064
|
+
const { checkedAt: _at, ...rest } = report2 ?? {};
|
|
104065
|
+
return JSON.stringify(rest);
|
|
104066
|
+
}
|
|
103621
104067
|
|
|
103622
104068
|
// start-server.ts
|
|
103623
104069
|
function logCrash(message) {
|
|
103624
104070
|
try {
|
|
103625
104071
|
mkdirSync11(bandHome(), { recursive: true });
|
|
103626
|
-
appendFileSync2(
|
|
104072
|
+
appendFileSync2(join63(bandHome(), "server.log"), message, "utf-8");
|
|
103627
104073
|
} catch {
|
|
103628
104074
|
}
|
|
103629
104075
|
}
|
|
@@ -103663,9 +104109,9 @@ function resolveUiDir() {
|
|
|
103663
104109
|
if (explicit) return resolve15(explicit);
|
|
103664
104110
|
const candidates2 = [
|
|
103665
104111
|
resolve15(SERVER_ROOT, "..", "..", "web", "dist", "client"),
|
|
103666
|
-
|
|
104112
|
+
join63(SERVER_ROOT, "client")
|
|
103667
104113
|
];
|
|
103668
|
-
return candidates2.find((dir) => existsSync17(
|
|
104114
|
+
return candidates2.find((dir) => existsSync17(join63(dir, "_shell.html"))) ?? candidates2[1];
|
|
103669
104115
|
}
|
|
103670
104116
|
var clientDir = resolveUiDir();
|
|
103671
104117
|
var uiRoot = process.env.BAND_UI_ROOT ? resolve15(process.env.BAND_UI_ROOT) : resolve15(SERVER_ROOT, "..", "web");
|
|
@@ -103701,12 +104147,12 @@ function allowedOrigins() {
|
|
|
103701
104147
|
];
|
|
103702
104148
|
}
|
|
103703
104149
|
var handleCors = createCorsMiddleware(allowedOrigins);
|
|
103704
|
-
function
|
|
103705
|
-
if (!expectedToken) return true;
|
|
104150
|
+
function requestDevice(req) {
|
|
104151
|
+
if (!expectedToken) return { admin: true, tokenId: void 0 };
|
|
103706
104152
|
const token = authenticatedCredential(req, (c) => tokenService.resolveDevice(c), {
|
|
103707
104153
|
allowCookie: req.headers.origin !== "null"
|
|
103708
104154
|
});
|
|
103709
|
-
return token?.admin === true;
|
|
104155
|
+
return { admin: token?.admin === true, tokenId: token?.id };
|
|
103710
104156
|
}
|
|
103711
104157
|
var assets = null;
|
|
103712
104158
|
var _openApiSpec = null;
|
|
@@ -103716,7 +104162,7 @@ function getOpenApiSpec() {
|
|
|
103716
104162
|
if (isDev) {
|
|
103717
104163
|
const { generateOpenAPIDocument } = await import("@trpc/openapi");
|
|
103718
104164
|
const doc = await generateOpenAPIDocument(
|
|
103719
|
-
|
|
104165
|
+
join63(SERVER_ROOT, "src", "server", "api", "router.ts"),
|
|
103720
104166
|
{
|
|
103721
104167
|
title: "Band API",
|
|
103722
104168
|
version: "1.0.0"
|
|
@@ -103725,7 +104171,7 @@ function getOpenApiSpec() {
|
|
|
103725
104171
|
doc.servers = [{ url: "/trpc" }];
|
|
103726
104172
|
return JSON.stringify(doc, null, 2);
|
|
103727
104173
|
}
|
|
103728
|
-
const openApiDoc = JSON.parse(readFileSync13(
|
|
104174
|
+
const openApiDoc = JSON.parse(readFileSync13(join63(SERVER_ROOT, "openapi.json"), "utf-8"));
|
|
103729
104175
|
openApiDoc.servers = [{ url: "/trpc" }];
|
|
103730
104176
|
return JSON.stringify(openApiDoc, null, 2);
|
|
103731
104177
|
})().catch((err) => {
|
|
@@ -103748,7 +104194,7 @@ function serveStaticFile(res, root, subdir, rawFilename) {
|
|
|
103748
104194
|
res.end("Bad request");
|
|
103749
104195
|
return;
|
|
103750
104196
|
}
|
|
103751
|
-
const filePath =
|
|
104197
|
+
const filePath = join63(root, subdir, filename);
|
|
103752
104198
|
try {
|
|
103753
104199
|
const fileStat = statSync14(filePath);
|
|
103754
104200
|
const contentType = mimeTypeFromFilename(filename);
|
|
@@ -103792,7 +104238,7 @@ async function serveWorktreeFile(res, worktreeId2, rawPath) {
|
|
|
103792
104238
|
return;
|
|
103793
104239
|
}
|
|
103794
104240
|
const root = worktree.worktree.path;
|
|
103795
|
-
const target = resolve15(
|
|
104241
|
+
const target = resolve15(join63(root, rawPath));
|
|
103796
104242
|
if (!target.startsWith(root + sep12) && target !== root) {
|
|
103797
104243
|
res.writeHead(400);
|
|
103798
104244
|
res.end("Bad request");
|
|
@@ -104024,7 +104470,7 @@ async function main() {
|
|
|
104024
104470
|
res.end(JSON.stringify({ status: "ok", app: "band-web-server" }));
|
|
104025
104471
|
return;
|
|
104026
104472
|
}
|
|
104027
|
-
if (handleWebAppManifest(req, res, isDev ?
|
|
104473
|
+
if (handleWebAppManifest(req, res, isDev ? join63(uiRoot, "public") : clientDir)) return;
|
|
104028
104474
|
if (req.url?.split("?")[0] === WORKER_EXCHANGE_PATH) {
|
|
104029
104475
|
await workerLinkService.handleExchange(req, res);
|
|
104030
104476
|
return;
|
|
@@ -104125,7 +104571,7 @@ async function main() {
|
|
|
104125
104571
|
return;
|
|
104126
104572
|
}
|
|
104127
104573
|
if (await serveHostedFile(res, "shared", partition, rest.slice(slashIdx + 1))) return;
|
|
104128
|
-
serveStaticFile(res, bandHome(),
|
|
104574
|
+
serveStaticFile(res, bandHome(), join63("shared", partition), rest.slice(slashIdx + 1));
|
|
104129
104575
|
return;
|
|
104130
104576
|
}
|
|
104131
104577
|
if (req.url?.startsWith("/api/worktree-file/")) {
|
|
@@ -104260,7 +104706,7 @@ async function main() {
|
|
|
104260
104706
|
endpoint: "/trpc",
|
|
104261
104707
|
req: request,
|
|
104262
104708
|
router: appRouter,
|
|
104263
|
-
createContext: ({ req: webRequest }) => createContext({ req: webRequest,
|
|
104709
|
+
createContext: ({ req: webRequest }) => createContext({ req: webRequest, ...requestDevice(req) })
|
|
104264
104710
|
});
|
|
104265
104711
|
pipeWebResponseToNodeRes(response, res);
|
|
104266
104712
|
return;
|
|
@@ -104333,7 +104779,7 @@ async function main() {
|
|
|
104333
104779
|
if (pathname.startsWith("/_shell")) res.setHeader("Cache-Control", "no-cache");
|
|
104334
104780
|
}
|
|
104335
104781
|
});
|
|
104336
|
-
const shellPath2 =
|
|
104782
|
+
const shellPath2 = join63(clientDir, "_shell.html");
|
|
104337
104783
|
if (!existsSync17(shellPath2)) {
|
|
104338
104784
|
throw new Error(
|
|
104339
104785
|
`UI build not found at ${clientDir} (no _shell.html). Run \`pnpm build\` or pass --ui-dir <path>.`
|
|
@@ -104345,7 +104791,7 @@ async function main() {
|
|
|
104345
104791
|
const wssHandler = applyWSSHandler({
|
|
104346
104792
|
wss,
|
|
104347
104793
|
router: appRouter,
|
|
104348
|
-
createContext: ({ req }) => createContext({ req,
|
|
104794
|
+
createContext: ({ req }) => createContext({ req, ...requestDevice(req) })
|
|
104349
104795
|
});
|
|
104350
104796
|
const terminalWss = new import_websocket_server.default({ noServer: true, handleProtocols: selectWsProtocol });
|
|
104351
104797
|
const lspWss = new import_websocket_server.default({ noServer: true, handleProtocols: selectWsProtocol });
|
|
@@ -104489,6 +104935,7 @@ Failed to bind any port in ${initialPort}..${initialPort + 19}:
|
|
|
104489
104935
|
subscriptionService.start();
|
|
104490
104936
|
void projectCoordinatorService.removeLegacyWorktrees().catch((err) => console.error("Failed to remove legacy coordinator worktrees:", err)).finally(() => {
|
|
104491
104937
|
projectSubscriptionService.start();
|
|
104938
|
+
projectCoordinatorService.start();
|
|
104492
104939
|
void projectFolderService.warmAll();
|
|
104493
104940
|
contextAutosyncService.start();
|
|
104494
104941
|
});
|