@band-app/server 0.47.0 → 0.48.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-CQGF9Qku.js → TerminalPanel-CfWUo3Nd.js} +1 -1
- package/dist/client/assets/{_basePickBy-D64wD5rn.js → _basePickBy-De_YMUJc.js} +1 -1
- package/dist/client/assets/{_baseUniq-pND9PMLH.js → _baseUniq-CF2c9fMp.js} +1 -1
- package/dist/client/assets/{arc-Bilmh-jP.js → arc-D-KYih_I.js} +1 -1
- package/dist/client/assets/{architectureDiagram-VXUJARFQ-KfIpeO-l.js → architectureDiagram-VXUJARFQ-i0rZW9Y9.js} +1 -1
- package/dist/client/assets/{blockDiagram-VD42YOAC-DtuyRI-v.js → blockDiagram-VD42YOAC-CI-wLyQa.js} +1 -1
- package/dist/client/assets/{c4Diagram-YG6GDRKO-CBuSt_Sq.js → c4Diagram-YG6GDRKO-C1kVgBMI.js} +1 -1
- package/dist/client/assets/channel-Hy94D-AA.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CWIj0g_M.js → chunk-4BX2VUAB-BZ1Kpo6f.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-YWkCYv9O.js → chunk-55IACEB6-CiRlX7k-.js} +1 -1
- package/dist/client/assets/{chunk-B4BG7PRW-muM18MGT.js → chunk-B4BG7PRW-BcgDiAdd.js} +1 -1
- package/dist/client/assets/{chunk-DI55MBZ5-DUKyYScZ.js → chunk-DI55MBZ5-RWxMQGLt.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-YTfHsy8G.js → chunk-FMBD7UC4-BwIR0iJB.js} +1 -1
- package/dist/client/assets/{chunk-QN33PNHL-BdIATH5l.js → chunk-QN33PNHL-_ihIB9E1.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DqRnJnIm.js → chunk-QZHKN3VN-CktWNnxj.js} +1 -1
- package/dist/client/assets/{chunk-TZMSLE5B-DOL-jYtj.js → chunk-TZMSLE5B-Br831bNt.js} +1 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-Cwx4pAtm.js +1 -0
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-Cwx4pAtm.js +1 -0
- package/dist/client/assets/clone-BKfvT5A4.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-Cffv5KWs.js → cose-bilkent-S5V4N54A-Ca6F1OCk.js} +1 -1
- package/dist/client/assets/{dagre-6UL2VRFP-lIXclAkR.js → dagre-6UL2VRFP-B0-4g4us.js} +1 -1
- package/dist/client/assets/{diagram-PSM6KHXK-BBQxBoNY.js → diagram-PSM6KHXK-Bio1qnrB.js} +1 -1
- package/dist/client/assets/{diagram-QEK2KX5R-BkC_Y-i9.js → diagram-QEK2KX5R-COoEbw7t.js} +1 -1
- package/dist/client/assets/{diagram-S2PKOQOG-Culf_egi.js → diagram-S2PKOQOG-D8lVMqVK.js} +1 -1
- package/dist/client/assets/{erDiagram-Q2GNP2WA-D8qVM8rI.js → erDiagram-Q2GNP2WA-t9P0TS6q.js} +1 -1
- package/dist/client/assets/{flowDiagram-NV44I4VS-D_CW6QJO.js → flowDiagram-NV44I4VS-qikZ7jp7.js} +1 -1
- package/dist/client/assets/{ganttDiagram-JELNMOA3-DeYroA3a.js → ganttDiagram-JELNMOA3--M9lMYuY.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-V2S2FVAM-a_sS5cSL.js → gitGraphDiagram-V2S2FVAM-B0JlDkQD.js} +1 -1
- package/dist/client/assets/{graph-BiyE3_7j.js → graph-DSNlIXAB.js} +1 -1
- package/dist/client/assets/{highlighted-body-B3W2YXNL-HVIXx0Ob.js → highlighted-body-B3W2YXNL-kAw1lMr_.js} +1 -1
- package/dist/client/assets/{index-CqPtl_El.js → index--3ZFGGnZ.js} +1 -1
- package/dist/client/assets/{index-DyvxDAAr.js → index-5WQX4UIw.js} +1 -1
- package/dist/client/assets/{index-BKkkwDGt.js → index-BlF06PC1.js} +1 -1
- package/dist/client/assets/{index-BuC5LY4k.js → index-CmkiQRLV.js} +1 -1
- package/dist/client/assets/{index-LKhS8iLe.js → index-CySzBB0C.js} +1 -1
- package/dist/client/assets/{index-NNpxvI8j.js → index-DFPad7x_.js} +1 -1
- package/dist/client/assets/{index-REiX7RF3.js → index-DMYwO0S-.js} +1 -1
- package/dist/client/assets/{index-DOFpEx7l.js → index-DYX4kWNp.js} +1 -1
- package/dist/client/assets/{index-Cnvsv3TN.js → index-Dga5q50h.js} +1 -1
- package/dist/client/assets/{index-CcH0k4fG.js → index-DrK2f7Cq.js} +1 -1
- package/dist/client/assets/{index-WgCV2JjC.js → index-VnrkP6Up.js} +1 -1
- package/dist/client/assets/{index-B1Qa5REm.js → index-bX18a8nz.js} +1 -1
- package/dist/client/assets/{index-CMHYIEyK.js → index-k-FABVhS.js} +1 -1
- package/dist/client/assets/{infoDiagram-HS3SLOUP-BfzBdZLM.js → infoDiagram-HS3SLOUP-CaK1_LNY.js} +1 -1
- package/dist/client/assets/{journeyDiagram-XKPGCS4Q-CUsUym-8.js → journeyDiagram-XKPGCS4Q-ahkgpOr3.js} +1 -1
- package/dist/client/assets/{kanban-definition-3W4ZIXB7-b54i6dX1.js → kanban-definition-3W4ZIXB7-DrsLcNHB.js} +1 -1
- package/dist/client/assets/{layout-ztlxnOA9.js → layout-kBr-ByLR.js} +1 -1
- package/dist/client/assets/main-BubyOwBK.css +1 -0
- package/dist/client/assets/{main-CCOutd_8.js → main-Tc53m1i8.js} +273 -273
- package/dist/client/assets/{mindmap-definition-VGOIOE7T-p7poZwfk.js → mindmap-definition-VGOIOE7T-CHMj9x7w.js} +1 -1
- package/dist/client/assets/{pieDiagram-ADFJNKIX-DNua1MUn.js → pieDiagram-ADFJNKIX-JtGh9CwE.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AYHSOK5B-CminxoD5.js → quadrantDiagram-AYHSOK5B-BtkeplJz.js} +1 -1
- package/dist/client/assets/{requirementDiagram-UZGBJVZJ-D3aBt82J.js → requirementDiagram-UZGBJVZJ-DSIYZLch.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-TZEHDZUN-B65x-vl8.js → sankeyDiagram-TZEHDZUN-DQGg1SzY.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WL72ISMW-DswVb1N3.js → sequenceDiagram-WL72ISMW-BLkWIPyC.js} +1 -1
- package/dist/client/assets/{stateDiagram-FKZM4ZOC-BScRlCMH.js → stateDiagram-FKZM4ZOC-BuPqxfpf.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-DSMlIvqa.js +1 -0
- package/dist/client/assets/{timeline-definition-IT6M3QCI-C5AHVri5.js → timeline-definition-IT6M3QCI-vh4dIOet.js} +1 -1
- package/dist/client/assets/{treemap-GDKQZRPO-B4F1SV97.js → treemap-GDKQZRPO-C9Mr454A.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-DOkSR4Iv.js → worktree._worktreeId-COUtiXXq.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-Da_9lTBD.js → worktree._worktreeId-DYrSBo4e.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PRI3JC2R-dZFFrnv9.js → xychartDiagram-PRI3JC2R-DTTZebCh.js} +1 -1
- package/dist/migrations/20261012190000_agent_status_states/migration.sql +4 -0
- package/dist/migrations/20261012190000_agent_status_states/snapshot.json +4409 -0
- package/dist/openapi.json +622 -18
- package/dist/start-server.mjs +1661 -661
- package/dist/terminal-daemon.mjs +149 -55
- package/package.json +5 -5
- package/dist/client/assets/channel-CmV-Qsz8.js +0 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-frHxX5yV.js +0 -1
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-frHxX5yV.js +0 -1
- package/dist/client/assets/clone-6GXe8GJt.js +0 -1
- package/dist/client/assets/main-D4-4L0eG.css +0 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-B5QaGcyC.js +0 -1
package/dist/start-server.mjs
CHANGED
|
@@ -1329,7 +1329,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1329
1329
|
var kContentModeUtf8 = "utf8";
|
|
1330
1330
|
var [major, minor] = (process.versions.node || "0.0").split(".").map(Number);
|
|
1331
1331
|
var kCopyBuffer = major >= 22 && minor >= 7;
|
|
1332
|
-
function openFile(
|
|
1332
|
+
function openFile(file3, sonic) {
|
|
1333
1333
|
sonic._opening = true;
|
|
1334
1334
|
sonic._writing = true;
|
|
1335
1335
|
sonic._asyncDrainScheduled = false;
|
|
@@ -1351,7 +1351,7 @@ var require_sonic_boom = __commonJS({
|
|
|
1351
1351
|
}
|
|
1352
1352
|
const reopening = sonic._reopening;
|
|
1353
1353
|
sonic.fd = fd;
|
|
1354
|
-
sonic.file =
|
|
1354
|
+
sonic.file = file3;
|
|
1355
1355
|
sonic._reopening = false;
|
|
1356
1356
|
sonic._opening = false;
|
|
1357
1357
|
sonic._writing = false;
|
|
@@ -1373,20 +1373,20 @@ var require_sonic_boom = __commonJS({
|
|
|
1373
1373
|
const mode = sonic.mode;
|
|
1374
1374
|
if (sonic.sync) {
|
|
1375
1375
|
try {
|
|
1376
|
-
if (sonic.mkdir) fs3.mkdirSync(path2.dirname(
|
|
1377
|
-
const fd = fs3.openSync(
|
|
1376
|
+
if (sonic.mkdir) fs3.mkdirSync(path2.dirname(file3), { recursive: true });
|
|
1377
|
+
const fd = fs3.openSync(file3, flags, mode);
|
|
1378
1378
|
fileOpened(null, fd);
|
|
1379
1379
|
} catch (err) {
|
|
1380
1380
|
fileOpened(err);
|
|
1381
1381
|
throw err;
|
|
1382
1382
|
}
|
|
1383
1383
|
} else if (sonic.mkdir) {
|
|
1384
|
-
fs3.mkdir(path2.dirname(
|
|
1384
|
+
fs3.mkdir(path2.dirname(file3), { recursive: true }, (err) => {
|
|
1385
1385
|
if (err) return fileOpened(err);
|
|
1386
|
-
fs3.open(
|
|
1386
|
+
fs3.open(file3, flags, mode, fileOpened);
|
|
1387
1387
|
});
|
|
1388
1388
|
} else {
|
|
1389
|
-
fs3.open(
|
|
1389
|
+
fs3.open(file3, flags, mode, fileOpened);
|
|
1390
1390
|
}
|
|
1391
1391
|
}
|
|
1392
1392
|
function SonicBoom(opts) {
|
|
@@ -1692,13 +1692,13 @@ var require_sonic_boom = __commonJS({
|
|
|
1692
1692
|
}
|
|
1693
1693
|
this._actualWrite();
|
|
1694
1694
|
}
|
|
1695
|
-
SonicBoom.prototype.reopen = function(
|
|
1695
|
+
SonicBoom.prototype.reopen = function(file3) {
|
|
1696
1696
|
if (this.destroyed) {
|
|
1697
1697
|
throw new Error("SonicBoom destroyed");
|
|
1698
1698
|
}
|
|
1699
1699
|
if (this._opening) {
|
|
1700
1700
|
this.once("ready", () => {
|
|
1701
|
-
this.reopen(
|
|
1701
|
+
this.reopen(file3);
|
|
1702
1702
|
});
|
|
1703
1703
|
return;
|
|
1704
1704
|
}
|
|
@@ -1708,8 +1708,8 @@ var require_sonic_boom = __commonJS({
|
|
|
1708
1708
|
if (!this.file) {
|
|
1709
1709
|
throw new Error("Unable to reopen a file descriptor, you must pass a file to SonicBoom");
|
|
1710
1710
|
}
|
|
1711
|
-
if (
|
|
1712
|
-
this.file =
|
|
1711
|
+
if (file3) {
|
|
1712
|
+
this.file = file3;
|
|
1713
1713
|
}
|
|
1714
1714
|
this._reopening = true;
|
|
1715
1715
|
if (this._writing) {
|
|
@@ -2326,7 +2326,7 @@ var require_thread_stream = __commonJS({
|
|
|
2326
2326
|
}
|
|
2327
2327
|
if (this[kImpl].flushing && this[kImpl].buf.length + data.length >= MAX_STRING) {
|
|
2328
2328
|
try {
|
|
2329
|
-
|
|
2329
|
+
writeSync2(this);
|
|
2330
2330
|
this[kImpl].flushing = true;
|
|
2331
2331
|
} catch (err) {
|
|
2332
2332
|
destroy(this, err);
|
|
@@ -2336,7 +2336,7 @@ var require_thread_stream = __commonJS({
|
|
|
2336
2336
|
this[kImpl].buf += data;
|
|
2337
2337
|
if (this[kImpl].sync) {
|
|
2338
2338
|
try {
|
|
2339
|
-
|
|
2339
|
+
writeSync2(this);
|
|
2340
2340
|
return true;
|
|
2341
2341
|
} catch (err) {
|
|
2342
2342
|
destroy(this, err);
|
|
@@ -2382,7 +2382,7 @@ var require_thread_stream = __commonJS({
|
|
|
2382
2382
|
if (this[kImpl].destroyed) {
|
|
2383
2383
|
return;
|
|
2384
2384
|
}
|
|
2385
|
-
|
|
2385
|
+
writeSync2(this);
|
|
2386
2386
|
flushSync(this);
|
|
2387
2387
|
}
|
|
2388
2388
|
unref() {
|
|
@@ -2486,7 +2486,7 @@ var require_thread_stream = __commonJS({
|
|
|
2486
2486
|
destroy(stream, err);
|
|
2487
2487
|
}
|
|
2488
2488
|
}
|
|
2489
|
-
function
|
|
2489
|
+
function writeSync2(stream) {
|
|
2490
2490
|
const cb = () => {
|
|
2491
2491
|
if (stream[kImpl].ending) {
|
|
2492
2492
|
end(stream);
|
|
@@ -2987,11 +2987,11 @@ var require_tools = __commonJS({
|
|
|
2987
2987
|
}
|
|
2988
2988
|
}
|
|
2989
2989
|
}
|
|
2990
|
-
function buildFormatters(level, bindings,
|
|
2990
|
+
function buildFormatters(level, bindings, log78) {
|
|
2991
2991
|
return {
|
|
2992
2992
|
level,
|
|
2993
2993
|
bindings,
|
|
2994
|
-
log:
|
|
2994
|
+
log: log78
|
|
2995
2995
|
};
|
|
2996
2996
|
}
|
|
2997
2997
|
function normalizeDestFileDescriptor(destination) {
|
|
@@ -3374,11 +3374,11 @@ var require_proto = __commonJS({
|
|
|
3374
3374
|
}
|
|
3375
3375
|
} else instance[serializersSym] = serializers;
|
|
3376
3376
|
if (options.hasOwnProperty("formatters")) {
|
|
3377
|
-
const { level, bindings: chindings, log:
|
|
3377
|
+
const { level, bindings: chindings, log: log78 } = options.formatters;
|
|
3378
3378
|
instance[formattersSym] = buildFormatters(
|
|
3379
3379
|
level || formatters.level,
|
|
3380
3380
|
chindings || resetChildingsFormatter,
|
|
3381
|
-
|
|
3381
|
+
log78 || formatters.log
|
|
3382
3382
|
);
|
|
3383
3383
|
} else {
|
|
3384
3384
|
instance[formattersSym] = buildFormatters(
|
|
@@ -4447,16 +4447,109 @@ var require_pino = __commonJS({
|
|
|
4447
4447
|
});
|
|
4448
4448
|
|
|
4449
4449
|
// ../../packages/logger/src/index.ts
|
|
4450
|
+
import {
|
|
4451
|
+
closeSync,
|
|
4452
|
+
existsSync,
|
|
4453
|
+
mkdirSync,
|
|
4454
|
+
openSync,
|
|
4455
|
+
renameSync,
|
|
4456
|
+
rmSync,
|
|
4457
|
+
statSync,
|
|
4458
|
+
writeSync
|
|
4459
|
+
} from "node:fs";
|
|
4460
|
+
import { dirname } from "node:path";
|
|
4461
|
+
function logFile() {
|
|
4462
|
+
if (file) return file;
|
|
4463
|
+
const path2 = process.env.BAND_LOG_FILE;
|
|
4464
|
+
if (!path2) return void 0;
|
|
4465
|
+
const max2 = Number(process.env.BAND_LOG_MAX_BYTES);
|
|
4466
|
+
const keep = Number(process.env.BAND_LOG_KEEP);
|
|
4467
|
+
const target = new RotatingFile(
|
|
4468
|
+
path2,
|
|
4469
|
+
max2 > 0 ? max2 : DEFAULT_LOG_MAX_BYTES,
|
|
4470
|
+
keep >= 1 ? keep : DEFAULT_LOG_KEEP
|
|
4471
|
+
);
|
|
4472
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
4473
|
+
stream.write = ((chunk, ...rest) => {
|
|
4474
|
+
target.write(chunk);
|
|
4475
|
+
const cb = rest.find((r) => typeof r === "function");
|
|
4476
|
+
cb?.();
|
|
4477
|
+
return true;
|
|
4478
|
+
});
|
|
4479
|
+
}
|
|
4480
|
+
file = target;
|
|
4481
|
+
return target;
|
|
4482
|
+
}
|
|
4450
4483
|
function createLogger(name4) {
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
});
|
|
4484
|
+
const options = { name: name4, level: process.env.LOG_LEVEL || "info" };
|
|
4485
|
+
const target = logFile();
|
|
4486
|
+
return target ? (0, import_pino.default)(options, { write: (line2) => target.write(line2) }) : (0, import_pino.default)(options);
|
|
4455
4487
|
}
|
|
4456
|
-
var import_pino;
|
|
4488
|
+
var import_pino, DEFAULT_LOG_MAX_BYTES, DEFAULT_LOG_KEEP, RotatingFile, file;
|
|
4457
4489
|
var init_src = __esm({
|
|
4458
4490
|
"../../packages/logger/src/index.ts"() {
|
|
4459
4491
|
import_pino = __toESM(require_pino(), 1);
|
|
4492
|
+
DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
4493
|
+
DEFAULT_LOG_KEEP = 3;
|
|
4494
|
+
RotatingFile = class {
|
|
4495
|
+
constructor(path2, maxBytes = DEFAULT_LOG_MAX_BYTES, keep = DEFAULT_LOG_KEEP) {
|
|
4496
|
+
this.path = path2;
|
|
4497
|
+
this.maxBytes = maxBytes;
|
|
4498
|
+
this.keep = keep;
|
|
4499
|
+
mkdirSync(dirname(path2), { recursive: true });
|
|
4500
|
+
if (existsSync(path2) && statSync(path2).size >= maxBytes) this.shift();
|
|
4501
|
+
this.open();
|
|
4502
|
+
}
|
|
4503
|
+
fd;
|
|
4504
|
+
size = 0;
|
|
4505
|
+
write(chunk) {
|
|
4506
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
4507
|
+
if (this.fd === void 0) return;
|
|
4508
|
+
if (this.size > 0 && this.size + buf.length > this.maxBytes) {
|
|
4509
|
+
this.close();
|
|
4510
|
+
this.shift();
|
|
4511
|
+
this.open();
|
|
4512
|
+
}
|
|
4513
|
+
if (this.fd === void 0) return;
|
|
4514
|
+
try {
|
|
4515
|
+
writeSync(this.fd, buf);
|
|
4516
|
+
this.size += buf.length;
|
|
4517
|
+
} catch {
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
close() {
|
|
4521
|
+
if (this.fd === void 0) return;
|
|
4522
|
+
try {
|
|
4523
|
+
closeSync(this.fd);
|
|
4524
|
+
} catch {
|
|
4525
|
+
}
|
|
4526
|
+
this.fd = void 0;
|
|
4527
|
+
}
|
|
4528
|
+
open() {
|
|
4529
|
+
try {
|
|
4530
|
+
this.fd = openSync(this.path, "a", 384);
|
|
4531
|
+
this.size = statSync(this.path).size;
|
|
4532
|
+
} catch {
|
|
4533
|
+
this.fd = void 0;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
shift() {
|
|
4537
|
+
try {
|
|
4538
|
+
const oldest = this.keep - 1;
|
|
4539
|
+
if (oldest < 1) {
|
|
4540
|
+
rmSync(this.path, { force: true });
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
rmSync(`${this.path}.${oldest}`, { force: true });
|
|
4544
|
+
for (let i = oldest - 1; i >= 1; i--) {
|
|
4545
|
+
if (existsSync(`${this.path}.${i}`))
|
|
4546
|
+
renameSync(`${this.path}.${i}`, `${this.path}.${i + 1}`);
|
|
4547
|
+
}
|
|
4548
|
+
renameSync(this.path, `${this.path}.1`);
|
|
4549
|
+
} catch {
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
};
|
|
4460
4553
|
}
|
|
4461
4554
|
});
|
|
4462
4555
|
|
|
@@ -4520,14 +4613,14 @@ import {
|
|
|
4520
4613
|
chmodSync,
|
|
4521
4614
|
linkSync,
|
|
4522
4615
|
lstatSync,
|
|
4523
|
-
mkdirSync,
|
|
4524
|
-
renameSync,
|
|
4525
|
-
statSync,
|
|
4616
|
+
mkdirSync as mkdirSync2,
|
|
4617
|
+
renameSync as renameSync2,
|
|
4618
|
+
statSync as statSync2,
|
|
4526
4619
|
unlinkSync
|
|
4527
4620
|
} from "node:fs";
|
|
4528
|
-
import { dirname, join } from "node:path";
|
|
4621
|
+
import { dirname as dirname2, join } from "node:path";
|
|
4529
4622
|
function ensurePrivateDir(dir) {
|
|
4530
|
-
|
|
4623
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
4531
4624
|
const stats = lstatSync(dir);
|
|
4532
4625
|
const uid = process.getuid?.();
|
|
4533
4626
|
if (!stats.isDirectory()) throw new Error(`${dir} is not a directory`);
|
|
@@ -4536,7 +4629,7 @@ function ensurePrivateDir(dir) {
|
|
|
4536
4629
|
}
|
|
4537
4630
|
function checkEndpointOwner(socketPath) {
|
|
4538
4631
|
const uid = process.getuid?.();
|
|
4539
|
-
const dir =
|
|
4632
|
+
const dir = dirname2(socketPath);
|
|
4540
4633
|
let dirStats;
|
|
4541
4634
|
try {
|
|
4542
4635
|
dirStats = lstatSync(dir);
|
|
@@ -4584,7 +4677,7 @@ var init_endpoint = __esm({
|
|
|
4584
4677
|
// ../../packages/host-local/src/terminals/daemon/protocol.ts
|
|
4585
4678
|
import { createHash } from "node:crypto";
|
|
4586
4679
|
import { readdirSync } from "node:fs";
|
|
4587
|
-
import { basename, dirname as
|
|
4680
|
+
import { basename, dirname as dirname3, join as join2 } from "node:path";
|
|
4588
4681
|
function daemonPaths(runDir, version2 = PROTOCOL_VERSION) {
|
|
4589
4682
|
const inRunDir = join2(runDir, `terminal-daemon-v${version2}.sock`);
|
|
4590
4683
|
const socket = Buffer.byteLength(inRunDir) <= MAX_SOCKET_PATH_BYTES ? inRunDir : join2(
|
|
@@ -4602,7 +4695,7 @@ function daemonPaths(runDir, version2 = PROTOCOL_VERSION) {
|
|
|
4602
4695
|
function retiredDaemonPaths(paths, tag) {
|
|
4603
4696
|
return {
|
|
4604
4697
|
...paths,
|
|
4605
|
-
socket: join2(
|
|
4698
|
+
socket: join2(dirname3(paths.socket), `.r${tag}`),
|
|
4606
4699
|
token: paths.token.replace(/\.token$/, `.retired-${tag}.token`),
|
|
4607
4700
|
pid: paths.pid.replace(/\.pid$/, `.retired-${tag}.pid`)
|
|
4608
4701
|
};
|
|
@@ -4935,10 +5028,10 @@ var init_client = __esm({
|
|
|
4935
5028
|
|
|
4936
5029
|
// ../../packages/host-local/src/terminals/daemon/exit-records.ts
|
|
4937
5030
|
import {
|
|
4938
|
-
mkdirSync as
|
|
5031
|
+
mkdirSync as mkdirSync3,
|
|
4939
5032
|
readdirSync as readdirSync2,
|
|
4940
5033
|
readFileSync,
|
|
4941
|
-
renameSync as
|
|
5034
|
+
renameSync as renameSync3,
|
|
4942
5035
|
unlinkSync as unlinkSync2,
|
|
4943
5036
|
writeFileSync
|
|
4944
5037
|
} from "node:fs";
|
|
@@ -4956,16 +5049,16 @@ function drainExitRecords(runDir) {
|
|
|
4956
5049
|
}
|
|
4957
5050
|
const events2 = [];
|
|
4958
5051
|
for (const name4 of names) {
|
|
4959
|
-
const
|
|
5052
|
+
const file3 = join3(dir, name4);
|
|
4960
5053
|
try {
|
|
4961
|
-
const event = JSON.parse(readFileSync(
|
|
5054
|
+
const event = JSON.parse(readFileSync(file3, "utf8"));
|
|
4962
5055
|
if (typeof event.terminalId === "string" && typeof event.worktreeId === "string") {
|
|
4963
5056
|
events2.push(event);
|
|
4964
5057
|
}
|
|
4965
5058
|
} catch {
|
|
4966
5059
|
}
|
|
4967
5060
|
try {
|
|
4968
|
-
unlinkSync2(
|
|
5061
|
+
unlinkSync2(file3);
|
|
4969
5062
|
} catch {
|
|
4970
5063
|
}
|
|
4971
5064
|
}
|
|
@@ -4981,15 +5074,15 @@ var init_exit_records = __esm({
|
|
|
4981
5074
|
|
|
4982
5075
|
// ../../packages/host-local/src/terminals/daemon/launch.ts
|
|
4983
5076
|
import { fork } from "node:child_process";
|
|
4984
|
-
import { closeSync, openSync, readFileSync as readFileSync2, renameSync as
|
|
5077
|
+
import { closeSync as closeSync2, openSync as openSync2, readFileSync as readFileSync2, renameSync as renameSync4, statSync as statSync3 } from "node:fs";
|
|
4985
5078
|
async function launchDaemon(options) {
|
|
4986
5079
|
const { entry, paths, cwd, buildId, supersede, recordExits, execArgv } = options;
|
|
4987
5080
|
ensurePrivateDir(paths.runDir);
|
|
4988
5081
|
rotateLog(paths.log);
|
|
4989
|
-
const logFd =
|
|
5082
|
+
const logFd = openSync2(paths.log, "a", 384);
|
|
4990
5083
|
let logSize = 0;
|
|
4991
5084
|
try {
|
|
4992
|
-
logSize =
|
|
5085
|
+
logSize = statSync3(paths.log).size;
|
|
4993
5086
|
} catch {
|
|
4994
5087
|
}
|
|
4995
5088
|
const child = (() => {
|
|
@@ -5005,7 +5098,7 @@ async function launchDaemon(options) {
|
|
|
5005
5098
|
env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }
|
|
5006
5099
|
});
|
|
5007
5100
|
} finally {
|
|
5008
|
-
|
|
5101
|
+
closeSync2(logFd);
|
|
5009
5102
|
}
|
|
5010
5103
|
})();
|
|
5011
5104
|
child.on("error", () => {
|
|
@@ -5061,7 +5154,7 @@ async function retireOlderDaemons(runDir, buildId) {
|
|
|
5061
5154
|
}
|
|
5062
5155
|
function rotateLog(path2) {
|
|
5063
5156
|
try {
|
|
5064
|
-
if (
|
|
5157
|
+
if (statSync3(path2).size > LOG_ROTATE_BYTES) renameSync4(path2, `${path2}.1`);
|
|
5065
5158
|
} catch {
|
|
5066
5159
|
}
|
|
5067
5160
|
}
|
|
@@ -5087,7 +5180,7 @@ var init_launch = __esm({
|
|
|
5087
5180
|
});
|
|
5088
5181
|
|
|
5089
5182
|
// ../../packages/host-local/src/terminals/daemon/daemon-backend.ts
|
|
5090
|
-
import { existsSync, lstatSync as lstatSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
5183
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
5091
5184
|
function sameEntry(a, b2) {
|
|
5092
5185
|
return a === b2 || a !== null && b2 !== null && a.dev === b2.dev && a.ino === b2.ino;
|
|
5093
5186
|
}
|
|
@@ -5540,7 +5633,7 @@ var init_daemon_backend = __esm({
|
|
|
5540
5633
|
if (client2.buildId !== this.options.buildId) return false;
|
|
5541
5634
|
const record3 = readPidRecord(this.paths.pid);
|
|
5542
5635
|
if (record3?.pid !== client2.pid || record3.entry === void 0) return true;
|
|
5543
|
-
return
|
|
5636
|
+
return existsSync2(record3.entry);
|
|
5544
5637
|
}
|
|
5545
5638
|
/** Stop starting sessions on `client`; keep using it for the ones it has. */
|
|
5546
5639
|
demote(client2) {
|
|
@@ -5648,13 +5741,13 @@ var init_daemon_backend = __esm({
|
|
|
5648
5741
|
|
|
5649
5742
|
// ../../packages/host-local/src/process/path.ts
|
|
5650
5743
|
import { execFile } from "node:child_process";
|
|
5651
|
-
import { existsSync as
|
|
5744
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
5652
5745
|
import { delimiter } from "node:path";
|
|
5653
5746
|
function defaultShell() {
|
|
5654
5747
|
if (process.env.SHELL) return process.env.SHELL;
|
|
5655
5748
|
if (process.platform === "win32") return process.env.ComSpec || "cmd.exe";
|
|
5656
5749
|
for (const candidate of ["/bin/zsh", "/bin/bash", "/bin/sh"]) {
|
|
5657
|
-
if (
|
|
5750
|
+
if (existsSync3(candidate)) return candidate;
|
|
5658
5751
|
}
|
|
5659
5752
|
return "/bin/sh";
|
|
5660
5753
|
}
|
|
@@ -5665,7 +5758,7 @@ function extraBinDirs() {
|
|
|
5665
5758
|
} else if (process.platform === "linux") {
|
|
5666
5759
|
const dirs = [];
|
|
5667
5760
|
const linuxbrew = "/home/linuxbrew/.linuxbrew/bin";
|
|
5668
|
-
if (
|
|
5761
|
+
if (existsSync3(linuxbrew)) dirs.push(linuxbrew);
|
|
5669
5762
|
dirs.push("/usr/local/bin");
|
|
5670
5763
|
cachedBinDirs = dirs;
|
|
5671
5764
|
} else {
|
|
@@ -5738,7 +5831,7 @@ var init_path = __esm({
|
|
|
5738
5831
|
});
|
|
5739
5832
|
|
|
5740
5833
|
// ../../packages/host-local/src/terminals/claude-resume.ts
|
|
5741
|
-
import { readdirSync as readdirSync3, statSync as
|
|
5834
|
+
import { readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
|
|
5742
5835
|
import { homedir } from "node:os";
|
|
5743
5836
|
import { join as join4 } from "node:path";
|
|
5744
5837
|
function isResumableClaudeCommand(command2) {
|
|
@@ -5761,7 +5854,7 @@ function findLatestClaudeSessionId(cwd) {
|
|
|
5761
5854
|
for (const name4 of names) {
|
|
5762
5855
|
let mtimeMs;
|
|
5763
5856
|
try {
|
|
5764
|
-
mtimeMs =
|
|
5857
|
+
mtimeMs = statSync4(join4(dir, name4)).mtimeMs;
|
|
5765
5858
|
} catch {
|
|
5766
5859
|
continue;
|
|
5767
5860
|
}
|
|
@@ -5818,7 +5911,7 @@ var init_pty_error_hints = __esm({
|
|
|
5818
5911
|
});
|
|
5819
5912
|
|
|
5820
5913
|
// ../../packages/host-local/src/terminals/terminal-history.ts
|
|
5821
|
-
import { readdirSync as readdirSync4, readFileSync as readFileSync4, rmSync } from "node:fs";
|
|
5914
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
5822
5915
|
import { mkdir, rename, writeFile } from "node:fs/promises";
|
|
5823
5916
|
import { join as join5 } from "node:path";
|
|
5824
5917
|
function readJson(path2) {
|
|
@@ -5884,7 +5977,7 @@ var init_terminal_history = __esm({
|
|
|
5884
5977
|
/** Drop a terminal's saved history: its tab or worktree was actually closed or deleted. */
|
|
5885
5978
|
removeSession(terminalId) {
|
|
5886
5979
|
try {
|
|
5887
|
-
|
|
5980
|
+
rmSync2(this.sessionDir(terminalId), { recursive: true, force: true });
|
|
5888
5981
|
} catch (err) {
|
|
5889
5982
|
log2.warn({ err, terminalId }, "failed to remove terminal history directory");
|
|
5890
5983
|
}
|
|
@@ -5908,7 +6001,7 @@ var init_terminal_history = __esm({
|
|
|
5908
6001
|
const meta = readJson(join5(dir, "meta.json"));
|
|
5909
6002
|
if (meta?.worktreeId !== worktreeId4) continue;
|
|
5910
6003
|
try {
|
|
5911
|
-
|
|
6004
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
5912
6005
|
} catch (err) {
|
|
5913
6006
|
log2.warn({ err, dir }, "failed to remove terminal history directory");
|
|
5914
6007
|
}
|
|
@@ -10405,7 +10498,7 @@ var require_addon_unicode11 = __commonJS({
|
|
|
10405
10498
|
|
|
10406
10499
|
// ../../packages/host-local/src/terminals/terminal-pool.ts
|
|
10407
10500
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
10408
|
-
import { existsSync as
|
|
10501
|
+
import { existsSync as existsSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
10409
10502
|
import { tmpdir } from "node:os";
|
|
10410
10503
|
import { join as join6, sep } from "node:path";
|
|
10411
10504
|
function resolveSafeCwd(candidate, worktreeRoot, label2, original) {
|
|
@@ -10413,7 +10506,7 @@ function resolveSafeCwd(candidate, worktreeRoot, label2, original) {
|
|
|
10413
10506
|
log3.warn("Ignoring %s %s \u2014 resolves outside worktree root %s", label2, original, worktreeRoot);
|
|
10414
10507
|
return null;
|
|
10415
10508
|
}
|
|
10416
|
-
if (!
|
|
10509
|
+
if (!existsSync4(candidate)) {
|
|
10417
10510
|
log3.warn("Ignoring %s %s \u2014 directory does not exist", label2, original);
|
|
10418
10511
|
return null;
|
|
10419
10512
|
}
|
|
@@ -10574,10 +10667,10 @@ var init_terminal_pool = __esm({
|
|
|
10574
10667
|
checkpoint.cwd
|
|
10575
10668
|
) ?? cwd;
|
|
10576
10669
|
}
|
|
10577
|
-
if (!
|
|
10670
|
+
if (!existsSync4(cwd)) {
|
|
10578
10671
|
throw new Error(`Worktree directory does not exist: ${cwd}`);
|
|
10579
10672
|
}
|
|
10580
|
-
if (!
|
|
10673
|
+
if (!existsSync4(shell)) {
|
|
10581
10674
|
throw new Error(`Shell not found: ${shell}`);
|
|
10582
10675
|
}
|
|
10583
10676
|
log3.debug(
|
|
@@ -15807,6 +15900,15 @@ var init_wrapper = __esm({
|
|
|
15807
15900
|
}
|
|
15808
15901
|
});
|
|
15809
15902
|
|
|
15903
|
+
// ../../packages/shared/src/chat-events.ts
|
|
15904
|
+
var HISTORY_PAGE_SIZE, HISTORY_PAGE_MAX_ROWS;
|
|
15905
|
+
var init_chat_events = __esm({
|
|
15906
|
+
"../../packages/shared/src/chat-events.ts"() {
|
|
15907
|
+
HISTORY_PAGE_SIZE = 20;
|
|
15908
|
+
HISTORY_PAGE_MAX_ROWS = 300;
|
|
15909
|
+
}
|
|
15910
|
+
});
|
|
15911
|
+
|
|
15810
15912
|
// src/server/services/_utils/queued-message-store.ts
|
|
15811
15913
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
15812
15914
|
function toWireQueuedMessages(messages) {
|
|
@@ -25752,28 +25854,28 @@ var init_to_json_schema = __esm({
|
|
|
25752
25854
|
}
|
|
25753
25855
|
case "file": {
|
|
25754
25856
|
const json3 = _json;
|
|
25755
|
-
const
|
|
25857
|
+
const file3 = {
|
|
25756
25858
|
type: "string",
|
|
25757
25859
|
format: "binary",
|
|
25758
25860
|
contentEncoding: "binary"
|
|
25759
25861
|
};
|
|
25760
25862
|
const { minimum, maximum, mime } = schema._zod.bag;
|
|
25761
25863
|
if (minimum !== void 0)
|
|
25762
|
-
|
|
25864
|
+
file3.minLength = minimum;
|
|
25763
25865
|
if (maximum !== void 0)
|
|
25764
|
-
|
|
25866
|
+
file3.maxLength = maximum;
|
|
25765
25867
|
if (mime) {
|
|
25766
25868
|
if (mime.length === 1) {
|
|
25767
|
-
|
|
25768
|
-
Object.assign(json3,
|
|
25869
|
+
file3.contentMediaType = mime[0];
|
|
25870
|
+
Object.assign(json3, file3);
|
|
25769
25871
|
} else {
|
|
25770
25872
|
json3.anyOf = mime.map((m2) => {
|
|
25771
|
-
const mFile = { ...
|
|
25873
|
+
const mFile = { ...file3, contentMediaType: m2 };
|
|
25772
25874
|
return mFile;
|
|
25773
25875
|
});
|
|
25774
25876
|
}
|
|
25775
25877
|
} else {
|
|
25776
|
-
Object.assign(json3,
|
|
25878
|
+
Object.assign(json3, file3);
|
|
25777
25879
|
}
|
|
25778
25880
|
break;
|
|
25779
25881
|
}
|
|
@@ -26681,7 +26783,7 @@ function literal(value, params) {
|
|
|
26681
26783
|
...util_exports.normalizeParams(params)
|
|
26682
26784
|
});
|
|
26683
26785
|
}
|
|
26684
|
-
function
|
|
26786
|
+
function file2(params) {
|
|
26685
26787
|
return _file(ZodFile, params);
|
|
26686
26788
|
}
|
|
26687
26789
|
function transform(fn) {
|
|
@@ -27539,7 +27641,7 @@ __export(external_exports, {
|
|
|
27539
27641
|
emoji: () => emoji2,
|
|
27540
27642
|
endsWith: () => _endsWith,
|
|
27541
27643
|
enum: () => _enum2,
|
|
27542
|
-
file: () =>
|
|
27644
|
+
file: () => file2,
|
|
27543
27645
|
flattenError: () => flattenError,
|
|
27544
27646
|
float32: () => float32,
|
|
27545
27647
|
float64: () => float64,
|
|
@@ -40205,7 +40307,7 @@ var init_node_sqlite = __esm({
|
|
|
40205
40307
|
|
|
40206
40308
|
// ../../node_modules/.pnpm/drizzle-orm@1.0.0-rc.1_@opentelemetry+api@1.9.0_@types+better-sqlite3@7.6.13_better-sql_95478898f63cc8c3fbbd0c5f8c1e464f/node_modules/drizzle-orm/migrator.js
|
|
40207
40309
|
import crypto2 from "node:crypto";
|
|
40208
|
-
import fs2, { existsSync as
|
|
40310
|
+
import fs2, { existsSync as existsSync7, readdirSync as readdirSync6 } from "node:fs";
|
|
40209
40311
|
import { join as join10 } from "node:path";
|
|
40210
40312
|
function readMigrationFiles(config2) {
|
|
40211
40313
|
if (fs2.existsSync(`${config2.migrationsFolder}/meta/_journal.json`)) throw Error('We detected that you have old drizzle-kit migration folders. You must upgrade drizzle-kit and run "drizzle-kit up"');
|
|
@@ -40214,7 +40316,7 @@ function readMigrationFiles(config2) {
|
|
|
40214
40316
|
const migrations = readdirSync6(migrationFolderTo).map((subdir) => ({
|
|
40215
40317
|
path: join10(migrationFolderTo, subdir, "migration.sql"),
|
|
40216
40318
|
name: subdir
|
|
40217
|
-
})).filter((it) =>
|
|
40319
|
+
})).filter((it) => existsSync7(it.path));
|
|
40218
40320
|
migrations.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
40219
40321
|
for (const migration of migrations) {
|
|
40220
40322
|
const migrationPath = migration.path;
|
|
@@ -40253,9 +40355,9 @@ var init_migrator2 = __esm({
|
|
|
40253
40355
|
|
|
40254
40356
|
// src/server/infra/db/queries/settings.ts
|
|
40255
40357
|
import { randomBytes } from "node:crypto";
|
|
40256
|
-
import { mkdirSync as
|
|
40358
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync5, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
40257
40359
|
import { homedir as homedir2 } from "node:os";
|
|
40258
|
-
import { dirname as
|
|
40360
|
+
import { dirname as dirname4, join as join11 } from "node:path";
|
|
40259
40361
|
function bandHome() {
|
|
40260
40362
|
if (process.env.BAND_HOME) return process.env.BAND_HOME;
|
|
40261
40363
|
return join11(homedir2(), ".band");
|
|
@@ -40294,7 +40396,7 @@ var init_settings = __esm({
|
|
|
40294
40396
|
const filePath = settingsFile();
|
|
40295
40397
|
let mtimeMs;
|
|
40296
40398
|
try {
|
|
40297
|
-
mtimeMs =
|
|
40399
|
+
mtimeMs = statSync8(filePath).mtimeMs;
|
|
40298
40400
|
} catch {
|
|
40299
40401
|
settingsCache.delete(filePath);
|
|
40300
40402
|
return {};
|
|
@@ -40337,11 +40439,11 @@ var init_settings = __esm({
|
|
|
40337
40439
|
const merged = { ...existing, ...patch };
|
|
40338
40440
|
const data = `${JSON.stringify(merged, null, 2)}
|
|
40339
40441
|
`;
|
|
40340
|
-
const dir =
|
|
40341
|
-
|
|
40442
|
+
const dir = dirname4(filePath);
|
|
40443
|
+
mkdirSync4(dir, { recursive: true });
|
|
40342
40444
|
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
40343
40445
|
writeFileSync3(tmpPath, data, "utf-8");
|
|
40344
|
-
|
|
40446
|
+
renameSync5(tmpPath, filePath);
|
|
40345
40447
|
settingsCache.delete(filePath);
|
|
40346
40448
|
}
|
|
40347
40449
|
/**
|
|
@@ -41075,13 +41177,13 @@ var init_schema2 = __esm({
|
|
|
41075
41177
|
});
|
|
41076
41178
|
|
|
41077
41179
|
// src/server/infra/db/connection.ts
|
|
41078
|
-
import { mkdirSync as
|
|
41180
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
41079
41181
|
import { join as join12 } from "node:path";
|
|
41080
41182
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
41081
41183
|
function getDb() {
|
|
41082
41184
|
if (_db) return _db;
|
|
41083
41185
|
const home = bandHome();
|
|
41084
|
-
|
|
41186
|
+
mkdirSync5(home, { recursive: true });
|
|
41085
41187
|
const dbPath = join12(home, "band.db");
|
|
41086
41188
|
_sqlite = new DatabaseSync2(dbPath);
|
|
41087
41189
|
_sqlite.exec("PRAGMA journal_mode = WAL");
|
|
@@ -41147,9 +41249,10 @@ function indexFields(event) {
|
|
|
41147
41249
|
};
|
|
41148
41250
|
}
|
|
41149
41251
|
var ChatEventQueries;
|
|
41150
|
-
var
|
|
41252
|
+
var init_chat_events2 = __esm({
|
|
41151
41253
|
"src/server/infra/db/queries/chat-events.ts"() {
|
|
41152
41254
|
"use strict";
|
|
41255
|
+
init_chat_events();
|
|
41153
41256
|
init_drizzle_orm();
|
|
41154
41257
|
init_connection();
|
|
41155
41258
|
init_schema2();
|
|
@@ -41185,8 +41288,9 @@ var init_chat_events = __esm({
|
|
|
41185
41288
|
}
|
|
41186
41289
|
/**
|
|
41187
41290
|
* The last `turns` turns before `beforeId` (the whole revision when
|
|
41188
|
-
* `beforeId` is undefined), oldest first
|
|
41189
|
-
*
|
|
41291
|
+
* `beforeId` is undefined), oldest first, shortened to the newest whole
|
|
41292
|
+
* turns that fit `HISTORY_PAGE_MAX_ROWS` (the newest turn is always kept).
|
|
41293
|
+
* `hasOlder` says whether rows exist before the returned window.
|
|
41190
41294
|
*/
|
|
41191
41295
|
readTurns(sessionId, revision, turns, beforeId) {
|
|
41192
41296
|
const db = getDb();
|
|
@@ -41196,7 +41300,18 @@ var init_chat_events = __esm({
|
|
|
41196
41300
|
beforeId === void 0 ? void 0 : lt(chatEvents.id, beforeId)
|
|
41197
41301
|
);
|
|
41198
41302
|
const starts = db.select({ id: chatEvents.id }).from(chatEvents).where(and(scope3, eq(chatEvents.turnStart, true))).orderBy(desc(chatEvents.id)).limit(turns).all();
|
|
41199
|
-
|
|
41303
|
+
let kept = starts.length;
|
|
41304
|
+
if (starts.length > 1) {
|
|
41305
|
+
const ids = db.select({ id: chatEvents.id }).from(chatEvents).where(and(scope3, sql`${chatEvents.id} >= ${starts[starts.length - 1].id}`)).orderBy(desc(chatEvents.id)).limit(HISTORY_PAGE_MAX_ROWS + 1).all().map((r) => r.id);
|
|
41306
|
+
for (let i = 1; i < starts.length; i++) {
|
|
41307
|
+
const count2 = ids.findIndex((id) => id < starts[i].id);
|
|
41308
|
+
if ((count2 === -1 ? ids.length : count2) > HISTORY_PAGE_MAX_ROWS) {
|
|
41309
|
+
kept = i;
|
|
41310
|
+
break;
|
|
41311
|
+
}
|
|
41312
|
+
}
|
|
41313
|
+
}
|
|
41314
|
+
const from = kept === starts.length && starts.length < turns ? void 0 : starts[kept - 1]?.id;
|
|
41200
41315
|
const rows = db.select().from(chatEvents).where(and(scope3, from === void 0 ? void 0 : sql`${chatEvents.id} >= ${from}`)).orderBy(chatEvents.id).all().map(toRow);
|
|
41201
41316
|
if (from === void 0) return { rows, hasOlder: false };
|
|
41202
41317
|
const older = db.select({ id: chatEvents.id }).from(chatEvents).where(
|
|
@@ -41411,16 +41526,63 @@ var init_cli_invocation = __esm({
|
|
|
41411
41526
|
});
|
|
41412
41527
|
|
|
41413
41528
|
// ../../packages/coding-agent/src/hook-status.ts
|
|
41529
|
+
function hasBackgroundWork(payload) {
|
|
41530
|
+
const crons = payload.session_crons;
|
|
41531
|
+
if (Array.isArray(crons) && crons.length > 0) return true;
|
|
41532
|
+
const tasks3 = payload.background_tasks;
|
|
41533
|
+
if (!Array.isArray(tasks3)) return false;
|
|
41534
|
+
return tasks3.some((task) => {
|
|
41535
|
+
if (typeof task !== "object" || task === null) return true;
|
|
41536
|
+
const { type, status } = task;
|
|
41537
|
+
if (typeof type === "string" && type.trim().toLowerCase() === "teammate") return false;
|
|
41538
|
+
const state2 = typeof status === "string" ? status.trim().toLowerCase() : "";
|
|
41539
|
+
return !TERMINAL_TASK_STATUSES.has(state2);
|
|
41540
|
+
});
|
|
41541
|
+
}
|
|
41414
41542
|
function mapClaudeCodeHookStatus(payload) {
|
|
41415
41543
|
const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
41416
41544
|
const toolName2 = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
41417
|
-
|
|
41418
|
-
|
|
41545
|
+
switch (hookEvent) {
|
|
41546
|
+
case "PermissionRequest":
|
|
41547
|
+
return "question";
|
|
41548
|
+
case "PreToolUse":
|
|
41549
|
+
return CLAUDE_CODE_INTERACTIVE_TOOLS.has(toolName2) ? "question" : "working";
|
|
41550
|
+
case "Stop":
|
|
41551
|
+
return hasBackgroundWork(payload) ? "monitoring" : "done";
|
|
41552
|
+
case "StopFailure":
|
|
41553
|
+
return "failed";
|
|
41554
|
+
case "PostCompact":
|
|
41555
|
+
return payload.trigger === "manual" ? "done" : "working";
|
|
41556
|
+
default:
|
|
41557
|
+
return "working";
|
|
41419
41558
|
}
|
|
41420
|
-
|
|
41421
|
-
|
|
41559
|
+
}
|
|
41560
|
+
function foldSubagents(hold, payload, mapped) {
|
|
41561
|
+
const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
41562
|
+
const agentId = typeof payload.agent_id === "string" ? payload.agent_id : "";
|
|
41563
|
+
if (hookEvent === "SubagentStart") {
|
|
41564
|
+
if (agentId) hold.running.add(agentId);
|
|
41565
|
+
return hold.deferred ? "working" : mapped;
|
|
41566
|
+
}
|
|
41567
|
+
if (hookEvent === "SubagentStop") {
|
|
41568
|
+
hold.running.delete(agentId);
|
|
41569
|
+
if (hold.running.size === 0 && hold.deferred) {
|
|
41570
|
+
const settled = hold.deferred;
|
|
41571
|
+
hold.deferred = null;
|
|
41572
|
+
return settled;
|
|
41573
|
+
}
|
|
41574
|
+
return mapped;
|
|
41575
|
+
}
|
|
41576
|
+
if ((hookEvent === "Stop" || hookEvent === "StopFailure") && !agentId) {
|
|
41577
|
+
if (hold.running.size > 0 && (mapped === "done" || mapped === "monitoring")) {
|
|
41578
|
+
hold.deferred = mapped;
|
|
41579
|
+
return "working";
|
|
41580
|
+
}
|
|
41581
|
+
hold.deferred = null;
|
|
41582
|
+
return mapped;
|
|
41422
41583
|
}
|
|
41423
|
-
|
|
41584
|
+
if (hookEvent === "UserPromptSubmit") hold.deferred = null;
|
|
41585
|
+
return mapped;
|
|
41424
41586
|
}
|
|
41425
41587
|
async function mapHookPayloadToStatus(agentType, payload) {
|
|
41426
41588
|
switch (agentType) {
|
|
@@ -41441,10 +41603,31 @@ function isHookSessionEnd(agentType, payload) {
|
|
|
41441
41603
|
function hookSessionId(payload) {
|
|
41442
41604
|
return typeof payload.session_id === "string" && payload.session_id ? payload.session_id : null;
|
|
41443
41605
|
}
|
|
41444
|
-
var CLAUDE_CODE_INTERACTIVE_TOOLS;
|
|
41606
|
+
var CLAUDE_CODE_INTERACTIVE_TOOLS, TERMINAL_TASK_STATUSES;
|
|
41445
41607
|
var init_hook_status = __esm({
|
|
41446
41608
|
"../../packages/coding-agent/src/hook-status.ts"() {
|
|
41447
41609
|
CLAUDE_CODE_INTERACTIVE_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
41610
|
+
TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
41611
|
+
"idle",
|
|
41612
|
+
"done",
|
|
41613
|
+
"success",
|
|
41614
|
+
"succeeded",
|
|
41615
|
+
"complete",
|
|
41616
|
+
"completed",
|
|
41617
|
+
"finished",
|
|
41618
|
+
"failed",
|
|
41619
|
+
"error",
|
|
41620
|
+
"terminated",
|
|
41621
|
+
"exited",
|
|
41622
|
+
"aborted",
|
|
41623
|
+
"expired",
|
|
41624
|
+
"skipped",
|
|
41625
|
+
"crashed",
|
|
41626
|
+
"killed",
|
|
41627
|
+
"cancelled",
|
|
41628
|
+
"canceled",
|
|
41629
|
+
"timed_out"
|
|
41630
|
+
]);
|
|
41448
41631
|
}
|
|
41449
41632
|
});
|
|
41450
41633
|
|
|
@@ -41510,8 +41693,8 @@ var init_pricing = __esm({
|
|
|
41510
41693
|
// ../../packages/coding-agent/src/usage/read-lines.ts
|
|
41511
41694
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
41512
41695
|
import { createInterface } from "node:readline";
|
|
41513
|
-
async function* readLines(
|
|
41514
|
-
const input = createReadStream2(
|
|
41696
|
+
async function* readLines(file3) {
|
|
41697
|
+
const input = createReadStream2(file3);
|
|
41515
41698
|
const rl = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
41516
41699
|
try {
|
|
41517
41700
|
for await (const line2 of rl) yield line2;
|
|
@@ -41568,10 +41751,10 @@ async function repoDirsFor(canonical) {
|
|
|
41568
41751
|
}
|
|
41569
41752
|
return dirs;
|
|
41570
41753
|
}
|
|
41571
|
-
async function readSessionCwd(
|
|
41754
|
+
async function readSessionCwd(file3) {
|
|
41572
41755
|
let handle;
|
|
41573
41756
|
try {
|
|
41574
|
-
handle = await open(
|
|
41757
|
+
handle = await open(file3, "r");
|
|
41575
41758
|
const buf = Buffer.alloc(SESSION_HEAD_BYTES);
|
|
41576
41759
|
const { bytesRead } = await handle.read(buf, 0, SESSION_HEAD_BYTES, 0);
|
|
41577
41760
|
if (bytesRead === 0) return null;
|
|
@@ -41617,13 +41800,13 @@ async function listSessions(dir) {
|
|
|
41617
41800
|
if (!name4.endsWith(".jsonl")) return;
|
|
41618
41801
|
const sessionId = name4.slice(0, -".jsonl".length);
|
|
41619
41802
|
if (!SESSION_ID_RE.test(sessionId)) return;
|
|
41620
|
-
const
|
|
41621
|
-
const cwd = await readSessionCwd(
|
|
41803
|
+
const file3 = join14(repoDir, name4);
|
|
41804
|
+
const cwd = await readSessionCwd(file3);
|
|
41622
41805
|
if (cwd === null) return;
|
|
41623
41806
|
if (cwd !== void 0 && cwd !== dir && cwd !== canonical) return;
|
|
41624
41807
|
let lastModified;
|
|
41625
41808
|
try {
|
|
41626
|
-
lastModified = (await stat(
|
|
41809
|
+
lastModified = (await stat(file3)).mtimeMs;
|
|
41627
41810
|
} catch {
|
|
41628
41811
|
return;
|
|
41629
41812
|
}
|
|
@@ -41637,9 +41820,9 @@ async function listSessions(dir) {
|
|
|
41637
41820
|
}
|
|
41638
41821
|
async function findSessionFile(sessionId, dir) {
|
|
41639
41822
|
for (const repoDir of await repoDirsFor(await canonicalDir(dir))) {
|
|
41640
|
-
const
|
|
41823
|
+
const file3 = join14(repoDir, `${sessionId}.jsonl`);
|
|
41641
41824
|
try {
|
|
41642
|
-
if ((await stat(
|
|
41825
|
+
if ((await stat(file3)).size > 0) return file3;
|
|
41643
41826
|
} catch {
|
|
41644
41827
|
}
|
|
41645
41828
|
}
|
|
@@ -41647,15 +41830,15 @@ async function findSessionFile(sessionId, dir) {
|
|
|
41647
41830
|
}
|
|
41648
41831
|
async function getSessionUsage(sessionId, dir) {
|
|
41649
41832
|
if (!SESSION_ID_RE.test(sessionId)) return null;
|
|
41650
|
-
const
|
|
41651
|
-
if (!
|
|
41833
|
+
const file3 = await findSessionFile(sessionId, dir);
|
|
41834
|
+
if (!file3) return null;
|
|
41652
41835
|
const turns = [];
|
|
41653
41836
|
const turnByMessageId = /* @__PURE__ */ new Map();
|
|
41654
41837
|
let conversationRecords = 0;
|
|
41655
41838
|
let startedAt = Number.POSITIVE_INFINITY;
|
|
41656
41839
|
let updatedAt = 0;
|
|
41657
41840
|
let modelFallback = "";
|
|
41658
|
-
for await (const line2 of readLines(
|
|
41841
|
+
for await (const line2 of readLines(file3)) {
|
|
41659
41842
|
if (!line2.trim()) continue;
|
|
41660
41843
|
let record3;
|
|
41661
41844
|
try {
|
|
@@ -41768,9 +41951,9 @@ async function findSessionFiles() {
|
|
|
41768
41951
|
}
|
|
41769
41952
|
return results;
|
|
41770
41953
|
}
|
|
41771
|
-
async function readSessionMeta(
|
|
41954
|
+
async function readSessionMeta(file3) {
|
|
41772
41955
|
let first;
|
|
41773
|
-
for await (const line2 of readLines(
|
|
41956
|
+
for await (const line2 of readLines(file3)) {
|
|
41774
41957
|
if (!line2.trim()) continue;
|
|
41775
41958
|
first = line2;
|
|
41776
41959
|
break;
|
|
@@ -41782,27 +41965,27 @@ async function readSessionMeta(file2) {
|
|
|
41782
41965
|
}
|
|
41783
41966
|
return void 0;
|
|
41784
41967
|
}
|
|
41785
|
-
async function getSessionMeta(
|
|
41786
|
-
const cached2 = metaCache.get(
|
|
41787
|
-
if (cached2 && cached2.mtimeMs ===
|
|
41968
|
+
async function getSessionMeta(file3) {
|
|
41969
|
+
const cached2 = metaCache.get(file3.path);
|
|
41970
|
+
if (cached2 && cached2.mtimeMs === file3.mtimeMs) return cached2.meta;
|
|
41788
41971
|
try {
|
|
41789
|
-
const meta = await readSessionMeta(
|
|
41790
|
-
metaCache.set(
|
|
41972
|
+
const meta = await readSessionMeta(file3.path);
|
|
41973
|
+
metaCache.set(file3.path, { mtimeMs: file3.mtimeMs, meta });
|
|
41791
41974
|
return meta;
|
|
41792
41975
|
} catch (err) {
|
|
41793
|
-
metaCache.set(
|
|
41976
|
+
metaCache.set(file3.path, { mtimeMs: file3.mtimeMs, meta: void 0 });
|
|
41794
41977
|
throw err;
|
|
41795
41978
|
}
|
|
41796
41979
|
}
|
|
41797
41980
|
async function listSessions2(dir) {
|
|
41798
41981
|
const sessions = [];
|
|
41799
|
-
for (const
|
|
41982
|
+
for (const file3 of await findSessionFiles()) {
|
|
41800
41983
|
try {
|
|
41801
|
-
const meta = await getSessionMeta(
|
|
41984
|
+
const meta = await getSessionMeta(file3);
|
|
41802
41985
|
if (!meta || meta.cwd !== dir) continue;
|
|
41803
|
-
sessions.push({ sessionId: meta.id, lastModified:
|
|
41986
|
+
sessions.push({ sessionId: meta.id, lastModified: file3.mtimeMs });
|
|
41804
41987
|
} catch (err) {
|
|
41805
|
-
log7.debug({ err, file:
|
|
41988
|
+
log7.debug({ err, file: file3.path }, "failed to parse codex session file");
|
|
41806
41989
|
}
|
|
41807
41990
|
}
|
|
41808
41991
|
return sessions.sort((a, b2) => b2.lastModified - a.lastModified);
|
|
@@ -42064,10 +42247,10 @@ var init_src3 = __esm({
|
|
|
42064
42247
|
});
|
|
42065
42248
|
|
|
42066
42249
|
// ../../packages/host-local/src/agents/acp-launch.ts
|
|
42067
|
-
import { accessSync, constants, existsSync as
|
|
42250
|
+
import { accessSync, constants, existsSync as existsSync8, statSync as statSync9 } from "node:fs";
|
|
42068
42251
|
import { createRequire } from "node:module";
|
|
42069
42252
|
import { homedir as homedir6 } from "node:os";
|
|
42070
|
-
import { delimiter as delimiter2, dirname as
|
|
42253
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join16, resolve as resolvePath } from "node:path";
|
|
42071
42254
|
import { fileURLToPath } from "node:url";
|
|
42072
42255
|
function findOnPath(name4, path2, extra = []) {
|
|
42073
42256
|
for (const dir of [...path2.split(delimiter2), ...extra]) {
|
|
@@ -42075,24 +42258,24 @@ function findOnPath(name4, path2, extra = []) {
|
|
|
42075
42258
|
const candidate = join16(dir, name4);
|
|
42076
42259
|
try {
|
|
42077
42260
|
accessSync(candidate, constants.X_OK);
|
|
42078
|
-
if (
|
|
42261
|
+
if (statSync9(candidate).isFile()) return candidate;
|
|
42079
42262
|
} catch {
|
|
42080
42263
|
}
|
|
42081
42264
|
}
|
|
42082
42265
|
return null;
|
|
42083
42266
|
}
|
|
42084
42267
|
function adapterScript(pkg, bundledName) {
|
|
42085
|
-
const bundled = join16(
|
|
42086
|
-
if (
|
|
42087
|
-
return join16(
|
|
42268
|
+
const bundled = join16(dirname5(fileURLToPath(import.meta.url)), "agents", `${bundledName}.mjs`);
|
|
42269
|
+
if (existsSync8(bundled)) return bundled;
|
|
42270
|
+
return join16(dirname5(require2.resolve(`${pkg}/package.json`)), "dist", "index.js");
|
|
42088
42271
|
}
|
|
42089
42272
|
function nodeFor(path2) {
|
|
42090
42273
|
return findOnPath("node", path2) ?? process.execPath;
|
|
42091
42274
|
}
|
|
42092
|
-
function isExecutableFile(
|
|
42275
|
+
function isExecutableFile(file3) {
|
|
42093
42276
|
try {
|
|
42094
|
-
accessSync(
|
|
42095
|
-
return
|
|
42277
|
+
accessSync(file3, constants.X_OK);
|
|
42278
|
+
return statSync9(file3).isFile();
|
|
42096
42279
|
} catch {
|
|
42097
42280
|
return false;
|
|
42098
42281
|
}
|
|
@@ -42165,7 +42348,7 @@ var init_acp_launch = __esm({
|
|
|
42165
42348
|
|
|
42166
42349
|
// ../../packages/host-local/src/agents/claude-defaults.ts
|
|
42167
42350
|
import { execFile as execFile3 } from "node:child_process";
|
|
42168
|
-
import { closeSync as
|
|
42351
|
+
import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync6, readSync, realpathSync as realpathSync2, statSync as statSync10 } from "node:fs";
|
|
42169
42352
|
import { homedir as homedir7 } from "node:os";
|
|
42170
42353
|
import { join as join17, resolve as resolvePath2 } from "node:path";
|
|
42171
42354
|
function claudeConfigDir2(env) {
|
|
@@ -42258,16 +42441,16 @@ function transcriptPath(env, cwd, sessionId) {
|
|
|
42258
42441
|
function readTail(path2, bytes) {
|
|
42259
42442
|
let fd;
|
|
42260
42443
|
try {
|
|
42261
|
-
const size =
|
|
42444
|
+
const size = statSync10(path2).size;
|
|
42262
42445
|
const length = Math.min(size, bytes);
|
|
42263
42446
|
const buf = Buffer.alloc(length);
|
|
42264
|
-
fd =
|
|
42447
|
+
fd = openSync3(path2, "r");
|
|
42265
42448
|
readSync(fd, buf, 0, length, size - length);
|
|
42266
42449
|
return buf.toString("utf8");
|
|
42267
42450
|
} catch {
|
|
42268
42451
|
return null;
|
|
42269
42452
|
} finally {
|
|
42270
|
-
if (fd !== void 0)
|
|
42453
|
+
if (fd !== void 0) closeSync3(fd);
|
|
42271
42454
|
}
|
|
42272
42455
|
}
|
|
42273
42456
|
function reportedClaudeDefaults(opts) {
|
|
@@ -42368,113 +42551,225 @@ var init_claude_defaults = __esm({
|
|
|
42368
42551
|
});
|
|
42369
42552
|
|
|
42370
42553
|
// ../../packages/host-local/src/agents/hooks-install.ts
|
|
42371
|
-
import {
|
|
42554
|
+
import {
|
|
42555
|
+
mkdirSync as mkdirSync6,
|
|
42556
|
+
readFileSync as readFileSync7,
|
|
42557
|
+
realpathSync as realpathSync3,
|
|
42558
|
+
renameSync as renameSync6,
|
|
42559
|
+
statSync as statSync11,
|
|
42560
|
+
unlinkSync as unlinkSync4,
|
|
42561
|
+
writeFileSync as writeFileSync4
|
|
42562
|
+
} from "node:fs";
|
|
42372
42563
|
import { homedir as homedir8 } from "node:os";
|
|
42373
|
-
import { dirname as
|
|
42564
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
42374
42565
|
function claudeSettingsPath() {
|
|
42375
42566
|
return join18(homedir8(), ".claude", "settings.json");
|
|
42376
42567
|
}
|
|
42377
42568
|
function loadClaudeSettings() {
|
|
42569
|
+
let text3;
|
|
42378
42570
|
try {
|
|
42379
|
-
|
|
42380
|
-
|
|
42571
|
+
text3 = readFileSync7(claudeSettingsPath(), "utf-8");
|
|
42572
|
+
} catch (err) {
|
|
42573
|
+
if (err.code === "ENOENT") {
|
|
42574
|
+
return { settings: {}, indent: 2, trailingNewline: false };
|
|
42575
|
+
}
|
|
42576
|
+
throw err;
|
|
42577
|
+
}
|
|
42578
|
+
if (text3.trim() === "") return { settings: {}, indent: 2, trailingNewline: false };
|
|
42579
|
+
let parsed;
|
|
42580
|
+
try {
|
|
42581
|
+
parsed = JSON.parse(text3);
|
|
42381
42582
|
} catch {
|
|
42382
|
-
|
|
42583
|
+
throw new Error(`${claudeSettingsPath()} is not valid JSON; fix it before installing hooks.`);
|
|
42584
|
+
}
|
|
42585
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42586
|
+
throw new Error(
|
|
42587
|
+
`${claudeSettingsPath()} is not a JSON object; fix it before installing hooks.`
|
|
42588
|
+
);
|
|
42589
|
+
}
|
|
42590
|
+
const indentMatch = /^([ \t]+)\S/m.exec(text3);
|
|
42591
|
+
return {
|
|
42592
|
+
settings: parsed,
|
|
42593
|
+
indent: indentMatch ? indentMatch[1] : 2,
|
|
42594
|
+
trailingNewline: text3.endsWith("\n")
|
|
42595
|
+
};
|
|
42596
|
+
}
|
|
42597
|
+
function saveClaudeSettings({ settings: settings3, indent, trailingNewline }) {
|
|
42598
|
+
let path2 = claudeSettingsPath();
|
|
42599
|
+
mkdirSync6(dirname6(path2), { recursive: true });
|
|
42600
|
+
let mode = 384;
|
|
42601
|
+
try {
|
|
42602
|
+
path2 = realpathSync3(path2);
|
|
42603
|
+
mode = statSync11(path2).mode & 511;
|
|
42604
|
+
} catch {
|
|
42605
|
+
}
|
|
42606
|
+
const temp = `${path2}.band-${process.pid}-${Date.now()}.tmp`;
|
|
42607
|
+
try {
|
|
42608
|
+
writeFileSync4(temp, JSON.stringify(settings3, null, indent) + (trailingNewline ? "\n" : ""), {
|
|
42609
|
+
encoding: "utf-8",
|
|
42610
|
+
mode,
|
|
42611
|
+
flag: "wx"
|
|
42612
|
+
});
|
|
42613
|
+
renameSync6(temp, path2);
|
|
42614
|
+
} catch (err) {
|
|
42615
|
+
try {
|
|
42616
|
+
unlinkSync4(temp);
|
|
42617
|
+
} catch {
|
|
42618
|
+
}
|
|
42619
|
+
throw err;
|
|
42383
42620
|
}
|
|
42384
42621
|
}
|
|
42385
42622
|
function isBandHook(command2) {
|
|
42386
|
-
return
|
|
42623
|
+
return BAND_NOTIFY.test(command2);
|
|
42387
42624
|
}
|
|
42388
|
-
|
|
42389
|
-
const
|
|
42625
|
+
function isBandEntry(entry) {
|
|
42626
|
+
const hooks = entry?.hooks;
|
|
42627
|
+
return Array.isArray(hooks) && hooks.some(
|
|
42628
|
+
(h) => h?.type === "command" && typeof h.command === "string" && isBandHook(h.command)
|
|
42629
|
+
);
|
|
42630
|
+
}
|
|
42631
|
+
function isCurrentCommand(hook) {
|
|
42632
|
+
const command2 = hook.command ?? "";
|
|
42633
|
+
return command2.includes("--agent claude-code") && command2.includes(">/dev/null 2>&1") && command2.trimEnd().endsWith("|| true") && hook.async === true;
|
|
42634
|
+
}
|
|
42635
|
+
function hooksOf(settings3) {
|
|
42390
42636
|
const hooks = settings3.hooks;
|
|
42391
|
-
|
|
42392
|
-
|
|
42637
|
+
return hooks !== null && typeof hooks === "object" && !Array.isArray(hooks) ? hooks : {};
|
|
42638
|
+
}
|
|
42639
|
+
async function checkHooks() {
|
|
42640
|
+
let settings3;
|
|
42641
|
+
try {
|
|
42642
|
+
settings3 = loadClaudeSettings().settings;
|
|
42643
|
+
} catch {
|
|
42644
|
+
return statusOf(0, 0, false);
|
|
42393
42645
|
}
|
|
42394
|
-
|
|
42646
|
+
const hooks = hooksOf(settings3);
|
|
42647
|
+
let current3 = 0;
|
|
42648
|
+
let bandEvents = 0;
|
|
42395
42649
|
let otherHookCount = 0;
|
|
42396
|
-
for (const [, eventHooks] of Object.entries(hooks)) {
|
|
42650
|
+
for (const [event, eventHooks] of Object.entries(hooks)) {
|
|
42397
42651
|
if (!Array.isArray(eventHooks)) continue;
|
|
42398
|
-
|
|
42399
|
-
|
|
42400
|
-
|
|
42401
|
-
|
|
42402
|
-
|
|
42403
|
-
|
|
42404
|
-
|
|
42405
|
-
|
|
42406
|
-
|
|
42407
|
-
|
|
42652
|
+
let eventHasBand = false;
|
|
42653
|
+
let eventIsCurrent = false;
|
|
42654
|
+
for (const entry of eventHooks) {
|
|
42655
|
+
const inner = entry?.hooks;
|
|
42656
|
+
if (!Array.isArray(inner)) continue;
|
|
42657
|
+
for (const h of inner) {
|
|
42658
|
+
if (h?.type !== "command" || typeof h.command !== "string") continue;
|
|
42659
|
+
if (isBandHook(h.command)) {
|
|
42660
|
+
eventHasBand = true;
|
|
42661
|
+
if (HOOK_EVENTS.includes(event) && isCurrentCommand(h)) eventIsCurrent = true;
|
|
42662
|
+
} else {
|
|
42663
|
+
otherHookCount++;
|
|
42408
42664
|
}
|
|
42409
42665
|
}
|
|
42410
42666
|
}
|
|
42667
|
+
if (eventHasBand) bandEvents++;
|
|
42668
|
+
if (eventIsCurrent) current3++;
|
|
42411
42669
|
}
|
|
42670
|
+
return statusOf(bandEvents, current3, otherHookCount > 0);
|
|
42671
|
+
}
|
|
42672
|
+
function statusOf(bandEvents, currentEvents, otherHooksExist) {
|
|
42673
|
+
const installed = currentEvents >= HOOK_EVENTS.length && bandEvents === currentEvents;
|
|
42412
42674
|
return {
|
|
42413
|
-
installed
|
|
42414
|
-
|
|
42675
|
+
installed,
|
|
42676
|
+
outdated: !installed && bandEvents > 0,
|
|
42677
|
+
other_hooks_exist: otherHooksExist,
|
|
42678
|
+
version: HOOKS_VERSION
|
|
42415
42679
|
};
|
|
42416
42680
|
}
|
|
42417
|
-
|
|
42418
|
-
|
|
42681
|
+
function hookCommand(bandPath) {
|
|
42682
|
+
return `"${bandPath}" notify --agent claude-code >/dev/null 2>&1 || true`;
|
|
42683
|
+
}
|
|
42684
|
+
async function findBand() {
|
|
42419
42685
|
if (process.platform !== "win32") {
|
|
42420
42686
|
try {
|
|
42421
42687
|
await import("node:fs/promises").then((m2) => m2.stat("/usr/local/bin/band"));
|
|
42422
|
-
|
|
42688
|
+
return "/usr/local/bin/band";
|
|
42423
42689
|
} catch {
|
|
42424
42690
|
}
|
|
42425
42691
|
}
|
|
42426
|
-
|
|
42427
|
-
|
|
42428
|
-
|
|
42429
|
-
|
|
42430
|
-
|
|
42431
|
-
}
|
|
42432
|
-
const settingsPath = claudeSettingsPath();
|
|
42433
|
-
mkdirSync5(dirname5(settingsPath), { recursive: true });
|
|
42434
|
-
const settings3 = loadClaudeSettings();
|
|
42435
|
-
const hooks = settings3.hooks || {};
|
|
42692
|
+
const found = await whichBinary("band");
|
|
42693
|
+
if (!found) throw new Error("Could not find band CLI binary. Please install it first.");
|
|
42694
|
+
return found;
|
|
42695
|
+
}
|
|
42696
|
+
function withoutBandEntries(hooks) {
|
|
42697
|
+
const result = {};
|
|
42436
42698
|
for (const [event, eventHooks] of Object.entries(hooks)) {
|
|
42437
|
-
if (
|
|
42438
|
-
|
|
42439
|
-
|
|
42699
|
+
if (!Array.isArray(eventHooks)) {
|
|
42700
|
+
result[event] = eventHooks;
|
|
42701
|
+
continue;
|
|
42702
|
+
}
|
|
42703
|
+
const kept = [];
|
|
42704
|
+
for (const entry of eventHooks) {
|
|
42705
|
+
if (!isBandEntry(entry)) {
|
|
42706
|
+
kept.push(entry);
|
|
42707
|
+
continue;
|
|
42708
|
+
}
|
|
42440
42709
|
const e = entry;
|
|
42441
|
-
|
|
42442
|
-
|
|
42443
|
-
|
|
42444
|
-
|
|
42445
|
-
hooks[event] = filtered;
|
|
42446
|
-
} else {
|
|
42447
|
-
delete hooks[event];
|
|
42710
|
+
const rest = (e.hooks ?? []).filter(
|
|
42711
|
+
(h) => !(h?.type === "command" && typeof h.command === "string" && isBandHook(h.command))
|
|
42712
|
+
);
|
|
42713
|
+
if (rest.length > 0) kept.push({ ...e, hooks: rest });
|
|
42448
42714
|
}
|
|
42715
|
+
if (kept.length > 0 || eventHooks.length === 0) result[event] = kept;
|
|
42449
42716
|
}
|
|
42717
|
+
return result;
|
|
42718
|
+
}
|
|
42719
|
+
async function installHooks() {
|
|
42720
|
+
const bandPath = await findBand();
|
|
42721
|
+
const loaded = loadClaudeSettings();
|
|
42722
|
+
const hooks = withoutBandEntries(hooksOf(loaded.settings));
|
|
42450
42723
|
for (const event of HOOK_EVENTS) {
|
|
42451
|
-
const existing = hooks[event]
|
|
42452
|
-
|
|
42453
|
-
|
|
42454
|
-
|
|
42455
|
-
|
|
42456
|
-
|
|
42457
|
-
|
|
42458
|
-
|
|
42459
|
-
|
|
42460
|
-
|
|
42724
|
+
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
42725
|
+
hooks[event] = [
|
|
42726
|
+
...existing,
|
|
42727
|
+
{
|
|
42728
|
+
hooks: [
|
|
42729
|
+
{
|
|
42730
|
+
type: "command",
|
|
42731
|
+
command: hookCommand(bandPath),
|
|
42732
|
+
async: true,
|
|
42733
|
+
timeout: HOOK_TIMEOUT_SECONDS
|
|
42734
|
+
}
|
|
42735
|
+
]
|
|
42736
|
+
}
|
|
42737
|
+
];
|
|
42461
42738
|
}
|
|
42462
|
-
|
|
42463
|
-
|
|
42739
|
+
loaded.settings.hooks = hooks;
|
|
42740
|
+
saveClaudeSettings(loaded);
|
|
42464
42741
|
}
|
|
42465
|
-
|
|
42742
|
+
async function removeHooks() {
|
|
42743
|
+
const loaded = loadClaudeSettings();
|
|
42744
|
+
if (!("hooks" in loaded.settings)) return;
|
|
42745
|
+
const before = hooksOf(loaded.settings);
|
|
42746
|
+
const after = withoutBandEntries(before);
|
|
42747
|
+
if (JSON.stringify(after) === JSON.stringify(before)) return;
|
|
42748
|
+
if (Object.keys(after).length > 0) loaded.settings.hooks = after;
|
|
42749
|
+
else delete loaded.settings.hooks;
|
|
42750
|
+
saveClaudeSettings(loaded);
|
|
42751
|
+
}
|
|
42752
|
+
var HOOK_EVENTS, HOOKS_VERSION, HOOK_TIMEOUT_SECONDS, BAND_NOTIFY;
|
|
42466
42753
|
var init_hooks_install = __esm({
|
|
42467
42754
|
"../../packages/host-local/src/agents/hooks-install.ts"() {
|
|
42468
42755
|
"use strict";
|
|
42469
42756
|
init_path();
|
|
42470
42757
|
HOOK_EVENTS = [
|
|
42471
|
-
"PreToolUse",
|
|
42472
|
-
"PermissionRequest",
|
|
42473
42758
|
"UserPromptSubmit",
|
|
42759
|
+
"PreToolUse",
|
|
42474
42760
|
"PostToolUse",
|
|
42761
|
+
"PostToolUseFailure",
|
|
42762
|
+
"PermissionRequest",
|
|
42475
42763
|
"Stop",
|
|
42764
|
+
"StopFailure",
|
|
42765
|
+
"SubagentStart",
|
|
42766
|
+
"SubagentStop",
|
|
42767
|
+
"PostCompact",
|
|
42476
42768
|
"SessionEnd"
|
|
42477
42769
|
];
|
|
42770
|
+
HOOKS_VERSION = 2;
|
|
42771
|
+
HOOK_TIMEOUT_SECONDS = 5;
|
|
42772
|
+
BAND_NOTIFY = /(^|[\s"'/\\])band(\.exe|\.cmd)?["']?\s+notify(\s|$)/;
|
|
42478
42773
|
}
|
|
42479
42774
|
});
|
|
42480
42775
|
|
|
@@ -42553,11 +42848,11 @@ var init_mcp_stdio = __esm({
|
|
|
42553
42848
|
});
|
|
42554
42849
|
|
|
42555
42850
|
// ../../packages/host-local/src/process/cli-binary.ts
|
|
42556
|
-
import { lstatSync as lstatSync3, realpathSync as
|
|
42851
|
+
import { lstatSync as lstatSync3, realpathSync as realpathSync4 } from "node:fs";
|
|
42557
42852
|
import { platform } from "node:os";
|
|
42558
42853
|
import { delimiter as delimiter3, join as join19, resolve as resolve4 } from "node:path";
|
|
42559
42854
|
function findCliBinaryAt(opts) {
|
|
42560
|
-
const { cwd, dirname:
|
|
42855
|
+
const { cwd, dirname: dirname17 } = opts;
|
|
42561
42856
|
const exe = platform() === "win32" ? "band.exe" : "band";
|
|
42562
42857
|
const explicit = process.env.BAND_CLI_PATH;
|
|
42563
42858
|
if (explicit) {
|
|
@@ -42573,7 +42868,7 @@ function findCliBinaryAt(opts) {
|
|
|
42573
42868
|
// cwd = repo root (fallback)
|
|
42574
42869
|
resolve4(cwd, "apps"),
|
|
42575
42870
|
// From this source file in dev (packages/host-local/src/process/ → apps/)
|
|
42576
|
-
resolve4(
|
|
42871
|
+
resolve4(dirname17, "..", "..", "..", "..", "apps"),
|
|
42577
42872
|
// From bundled `dist/` file (<Resources>/web/dist/ → <Resources>/) only
|
|
42578
42873
|
// — in dev mode this resolves to `packages/`, which has no
|
|
42579
42874
|
// `cli/target/<profile>/band` and is harmless; the walk above is the
|
|
@@ -42581,7 +42876,7 @@ function findCliBinaryAt(opts) {
|
|
|
42581
42876
|
// layout under <Resources>/cli/ would still resolve. Today's Electron
|
|
42582
42877
|
// bundle ships the binary under `binaries/` (handled by Strategy B),
|
|
42583
42878
|
// so this strategy never hits in production either.
|
|
42584
|
-
resolve4(
|
|
42879
|
+
resolve4(dirname17, "..", "..", "..")
|
|
42585
42880
|
];
|
|
42586
42881
|
for (const appsDir of appsStrategies) {
|
|
42587
42882
|
for (const profile of ["release", "debug"]) {
|
|
@@ -42598,7 +42893,7 @@ function findCliBinaryAt(opts) {
|
|
|
42598
42893
|
resolve4(cwd, "..", "binaries", exe),
|
|
42599
42894
|
// From the bundled dist file (<Resources>/web/dist/start-server.mjs)
|
|
42600
42895
|
// → <Resources>/binaries/band
|
|
42601
|
-
resolve4(
|
|
42896
|
+
resolve4(dirname17, "..", "..", "binaries", exe)
|
|
42602
42897
|
];
|
|
42603
42898
|
for (const p2 of electronCandidates) {
|
|
42604
42899
|
try {
|
|
@@ -42610,7 +42905,7 @@ function findCliBinaryAt(opts) {
|
|
|
42610
42905
|
for (const dir of (process.env.PATH ?? "").split(delimiter3)) {
|
|
42611
42906
|
if (!dir) continue;
|
|
42612
42907
|
try {
|
|
42613
|
-
return
|
|
42908
|
+
return realpathSync4(join19(dir, exe));
|
|
42614
42909
|
} catch {
|
|
42615
42910
|
}
|
|
42616
42911
|
}
|
|
@@ -42627,21 +42922,21 @@ var init_cli_binary = __esm({
|
|
|
42627
42922
|
|
|
42628
42923
|
// ../../packages/host-local/src/agents/skills-install.ts
|
|
42629
42924
|
import { execFile as execFile4 } from "node:child_process";
|
|
42630
|
-
import { statSync as
|
|
42925
|
+
import { statSync as statSync12 } from "node:fs";
|
|
42631
42926
|
import { homedir as homedir10 } from "node:os";
|
|
42632
42927
|
import { join as join20 } from "node:path";
|
|
42633
42928
|
async function findBandBinary() {
|
|
42634
42929
|
const override = process.env.BAND_CLI_BIN;
|
|
42635
42930
|
if (override) {
|
|
42636
42931
|
try {
|
|
42637
|
-
|
|
42932
|
+
statSync12(override);
|
|
42638
42933
|
return override;
|
|
42639
42934
|
} catch {
|
|
42640
42935
|
}
|
|
42641
42936
|
}
|
|
42642
42937
|
if (process.platform !== "win32") {
|
|
42643
42938
|
try {
|
|
42644
|
-
|
|
42939
|
+
statSync12("/usr/local/bin/band");
|
|
42645
42940
|
return "/usr/local/bin/band";
|
|
42646
42941
|
} catch {
|
|
42647
42942
|
}
|
|
@@ -42743,7 +43038,7 @@ var init_skills_install = __esm({
|
|
|
42743
43038
|
|
|
42744
43039
|
// ../../packages/host-local/src/browser/chromium.ts
|
|
42745
43040
|
import { spawn as spawn2 } from "node:child_process";
|
|
42746
|
-
import { closeSync as
|
|
43041
|
+
import { closeSync as closeSync4, existsSync as existsSync9, openSync as openSync4, readdirSync as readdirSync7 } from "node:fs";
|
|
42747
43042
|
import { mkdir as mkdir2, readFile as readFile2, rm } from "node:fs/promises";
|
|
42748
43043
|
import { homedir as homedir11 } from "node:os";
|
|
42749
43044
|
import { join as join21 } from "node:path";
|
|
@@ -42752,7 +43047,7 @@ function playwrightCandidates() {
|
|
|
42752
43047
|
process.env.PLAYWRIGHT_BROWSERS_PATH,
|
|
42753
43048
|
join21(homedir11(), "Library/Caches/ms-playwright"),
|
|
42754
43049
|
join21(homedir11(), ".cache/ms-playwright")
|
|
42755
|
-
].filter((root) => !!root &&
|
|
43050
|
+
].filter((root) => !!root && existsSync9(root));
|
|
42756
43051
|
const found = [];
|
|
42757
43052
|
for (const root of roots) {
|
|
42758
43053
|
const dirs = readdirSync7(root).filter((d) => /^chromium-\d+$/.test(d)).sort().reverse();
|
|
@@ -42782,7 +43077,7 @@ function playwrightCandidates() {
|
|
|
42782
43077
|
}
|
|
42783
43078
|
function findChromium() {
|
|
42784
43079
|
const fromEnv = process.env.BAND_CHROMIUM_BIN;
|
|
42785
|
-
if (fromEnv) return
|
|
43080
|
+
if (fromEnv) return existsSync9(fromEnv) ? fromEnv : void 0;
|
|
42786
43081
|
const candidates = [
|
|
42787
43082
|
"/usr/bin/chromium",
|
|
42788
43083
|
"/usr/bin/chromium-browser",
|
|
@@ -42792,10 +43087,10 @@ function findChromium() {
|
|
|
42792
43087
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
42793
43088
|
...playwrightCandidates()
|
|
42794
43089
|
];
|
|
42795
|
-
return candidates.find((c) =>
|
|
43090
|
+
return candidates.find((c) => existsSync9(c));
|
|
42796
43091
|
}
|
|
42797
43092
|
async function readDevToolsPort(profileDir, proc, logPath, timeoutMs) {
|
|
42798
|
-
const
|
|
43093
|
+
const file3 = join21(profileDir, "DevToolsActivePort");
|
|
42799
43094
|
const deadline = Date.now() + timeoutMs;
|
|
42800
43095
|
while (Date.now() < deadline) {
|
|
42801
43096
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
@@ -42805,7 +43100,7 @@ async function readDevToolsPort(profileDir, proc, logPath, timeoutMs) {
|
|
|
42805
43100
|
throw new Error("Chromium hit a fatal error at startup");
|
|
42806
43101
|
}
|
|
42807
43102
|
try {
|
|
42808
|
-
const [port, path2] = (await readFile2(
|
|
43103
|
+
const [port, path2] = (await readFile2(file3, "utf8")).split("\n");
|
|
42809
43104
|
if (port && path2) return { port: Number(port), path: path2.trim() };
|
|
42810
43105
|
} catch {
|
|
42811
43106
|
}
|
|
@@ -42955,13 +43250,13 @@ var init_chromium = __esm({
|
|
|
42955
43250
|
if (attempt > 1) {
|
|
42956
43251
|
await Promise.all(SINGLETON_FILES.map((f2) => rm(join21(profileDir, f2), { force: true })));
|
|
42957
43252
|
}
|
|
42958
|
-
const logFd =
|
|
43253
|
+
const logFd = openSync4(logPath, "w", 384);
|
|
42959
43254
|
const proc = spawn2(bin, args, {
|
|
42960
43255
|
stdio: ["ignore", "ignore", logFd],
|
|
42961
43256
|
detached: true,
|
|
42962
43257
|
env: { ...env, HOME: home }
|
|
42963
43258
|
});
|
|
42964
|
-
|
|
43259
|
+
closeSync4(logFd);
|
|
42965
43260
|
const exited = new Promise((resolve15) => {
|
|
42966
43261
|
proc.once("exit", () => resolve15());
|
|
42967
43262
|
proc.once("error", () => resolve15());
|
|
@@ -43380,7 +43675,7 @@ var init_git_client = __esm({
|
|
|
43380
43675
|
|
|
43381
43676
|
// ../../packages/host-local/src/lsp/lsp-manager.ts
|
|
43382
43677
|
import { spawn as spawn3 } from "node:child_process";
|
|
43383
|
-
import { dirname as
|
|
43678
|
+
import { dirname as dirname7, join as join24, resolve as resolve5 } from "node:path";
|
|
43384
43679
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
43385
43680
|
function toServerId(worktreeId4, lang) {
|
|
43386
43681
|
return `${worktreeId4}:${lang}`;
|
|
@@ -43505,7 +43800,7 @@ var init_lsp_manager = __esm({
|
|
|
43505
43800
|
"use strict";
|
|
43506
43801
|
init_src();
|
|
43507
43802
|
init_path();
|
|
43508
|
-
__dirname2 =
|
|
43803
|
+
__dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
43509
43804
|
log11 = createLogger("lsp");
|
|
43510
43805
|
LANG_SERVER_CONFIG = {
|
|
43511
43806
|
typescript: { command: "typescript-language-server", args: ["--stdio"] }
|
|
@@ -47803,27 +48098,27 @@ function parseEnvironment(text3) {
|
|
|
47803
48098
|
if (parsed.success) return { ok: true, environment: parsed.data };
|
|
47804
48099
|
return { ok: false, issues: zodIssues(parsed.error) };
|
|
47805
48100
|
}
|
|
47806
|
-
function repoRelative(
|
|
47807
|
-
if (posix.isAbsolute(
|
|
47808
|
-
const normal = posix.normalize(
|
|
48101
|
+
function repoRelative(file3) {
|
|
48102
|
+
if (posix.isAbsolute(file3) || /^[A-Za-z]:[\\/]/.test(file3)) return null;
|
|
48103
|
+
const normal = posix.normalize(file3.replaceAll("\\", "/"));
|
|
47809
48104
|
if (normal === ".." || normal.startsWith("../")) return null;
|
|
47810
48105
|
return normal;
|
|
47811
48106
|
}
|
|
47812
48107
|
async function checkReferences(environment, exists3) {
|
|
47813
48108
|
const issues = [];
|
|
47814
48109
|
for (const key2 of ["devcontainer", "dockerfile"]) {
|
|
47815
|
-
const
|
|
47816
|
-
if (
|
|
47817
|
-
const relative2 = repoRelative(
|
|
48110
|
+
const file3 = environment.build?.[key2];
|
|
48111
|
+
if (file3 === void 0) continue;
|
|
48112
|
+
const relative2 = repoRelative(file3);
|
|
47818
48113
|
if (relative2 === null) {
|
|
47819
48114
|
issues.push({
|
|
47820
48115
|
path: `build.${key2}`,
|
|
47821
|
-
message: `"${
|
|
48116
|
+
message: `"${file3}" must be a path inside the repository`
|
|
47822
48117
|
});
|
|
47823
48118
|
} else if (!await exists3(relative2)) {
|
|
47824
48119
|
issues.push({
|
|
47825
48120
|
path: `build.${key2}`,
|
|
47826
|
-
message: `file "${
|
|
48121
|
+
message: `file "${file3}" does not exist in the repository`
|
|
47827
48122
|
});
|
|
47828
48123
|
}
|
|
47829
48124
|
}
|
|
@@ -47912,9 +48207,9 @@ function keyInputs(environment) {
|
|
|
47912
48207
|
const inputs = [{ path: ENVIRONMENT_FILE, required: true }];
|
|
47913
48208
|
const build = environment.build;
|
|
47914
48209
|
for (const key2 of ["devcontainer", "dockerfile"]) {
|
|
47915
|
-
const
|
|
47916
|
-
if (
|
|
47917
|
-
const relative2 = repoRelative(
|
|
48210
|
+
const file3 = build?.[key2];
|
|
48211
|
+
if (file3 === void 0) continue;
|
|
48212
|
+
const relative2 = repoRelative(file3);
|
|
47918
48213
|
if (relative2 === null) continue;
|
|
47919
48214
|
inputs.push({ path: relative2, required: true });
|
|
47920
48215
|
const dir = posix2.dirname(relative2);
|
|
@@ -48463,10 +48758,10 @@ var init_remote_url = __esm({
|
|
|
48463
48758
|
// ../../packages/host-local/src/repos/host-repos.ts
|
|
48464
48759
|
import { execFile as execFile10 } from "node:child_process";
|
|
48465
48760
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
|
|
48466
|
-
import { existsSync as
|
|
48761
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
48467
48762
|
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, realpath as realpath2, rename as rename2, stat as stat5, writeFile as writeFile2 } from "node:fs/promises";
|
|
48468
48763
|
import { homedir as homedir13 } from "node:os";
|
|
48469
|
-
import { dirname as
|
|
48764
|
+
import { dirname as dirname8, join as join27, sep as sep3 } from "node:path";
|
|
48470
48765
|
function defaultReposDir() {
|
|
48471
48766
|
return process.env.BAND_REPOS_DIR?.trim() || join27(homedir13(), "band", "repos");
|
|
48472
48767
|
}
|
|
@@ -48517,10 +48812,10 @@ async function browseFolder(path2) {
|
|
|
48517
48812
|
let isDir = d.isDirectory();
|
|
48518
48813
|
if (d.isSymbolicLink()) isDir = await isDirectory(full);
|
|
48519
48814
|
if (!isDir) continue;
|
|
48520
|
-
entries.push({ name: d.name, path: full, isGit:
|
|
48815
|
+
entries.push({ name: d.name, path: full, isGit: existsSync10(join27(full, ".git")) });
|
|
48521
48816
|
}
|
|
48522
48817
|
entries.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
48523
|
-
const parent =
|
|
48818
|
+
const parent = dirname8(target);
|
|
48524
48819
|
return {
|
|
48525
48820
|
path: target,
|
|
48526
48821
|
parent: parent === target ? null : parent,
|
|
@@ -48556,13 +48851,13 @@ var init_host_repos = __esm({
|
|
|
48556
48851
|
return this.cache;
|
|
48557
48852
|
}
|
|
48558
48853
|
save(mappings) {
|
|
48559
|
-
const
|
|
48854
|
+
const file3 = this.options.mappingsFile();
|
|
48560
48855
|
this.writing = this.writing.then(async () => {
|
|
48561
|
-
await mkdir3(
|
|
48562
|
-
const tmp = `${
|
|
48856
|
+
await mkdir3(dirname8(file3), { recursive: true, mode: 448 });
|
|
48857
|
+
const tmp = `${file3}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
48563
48858
|
await writeFile2(tmp, `${JSON.stringify(mappings, null, 2)}
|
|
48564
48859
|
`, { mode: 384 });
|
|
48565
|
-
await rename2(tmp,
|
|
48860
|
+
await rename2(tmp, file3);
|
|
48566
48861
|
});
|
|
48567
48862
|
return this.writing;
|
|
48568
48863
|
}
|
|
@@ -48619,12 +48914,12 @@ var init_host_repos = __esm({
|
|
|
48619
48914
|
async doEnsure(key2, url2, owner, name4) {
|
|
48620
48915
|
const mappings = await this.load();
|
|
48621
48916
|
const mapped = mappings[key2];
|
|
48622
|
-
if (mapped &&
|
|
48917
|
+
if (mapped && existsSync10(mapped.path) && await isDirectory(mapped.path)) {
|
|
48623
48918
|
return { path: mapped.path, cloned: false };
|
|
48624
48919
|
}
|
|
48625
48920
|
const base = join27(this.options.reposDir(), owner, name4);
|
|
48626
48921
|
let dest = base;
|
|
48627
|
-
if (
|
|
48922
|
+
if (existsSync10(dest)) {
|
|
48628
48923
|
const origin = await git(["remote", "get-url", "origin"], dest);
|
|
48629
48924
|
const there = origin ? parseRemoteUrl(origin)?.key : void 0;
|
|
48630
48925
|
if (there === key2) {
|
|
@@ -48634,7 +48929,7 @@ var init_host_repos = __esm({
|
|
|
48634
48929
|
const empty = (await readdir4(dest).catch(() => ["x"])).length === 0;
|
|
48635
48930
|
if (!empty) dest = `${base}-${createHash3("sha256").update(key2).digest("hex").slice(0, 6)}`;
|
|
48636
48931
|
}
|
|
48637
|
-
await mkdir3(
|
|
48932
|
+
await mkdir3(dirname8(dest), { recursive: true });
|
|
48638
48933
|
await clone2(url2, dest);
|
|
48639
48934
|
const canonical = await realpath2(dest);
|
|
48640
48935
|
await this.map(url2, canonical);
|
|
@@ -48696,9 +48991,9 @@ function createIterator(options) {
|
|
|
48696
48991
|
if (!rawFile || typeof lineNumber !== "number" || rawContent === void 0) {
|
|
48697
48992
|
continue;
|
|
48698
48993
|
}
|
|
48699
|
-
const
|
|
48994
|
+
const file3 = rawFile.startsWith("./") ? rawFile.slice(2) : rawFile;
|
|
48700
48995
|
const content = rawContent.endsWith("\n") ? rawContent.slice(0, -1) : rawContent;
|
|
48701
|
-
matches2.push({ file:
|
|
48996
|
+
matches2.push({ file: file3, line: lineNumber, content });
|
|
48702
48997
|
}
|
|
48703
48998
|
signal();
|
|
48704
48999
|
});
|
|
@@ -48804,15 +49099,15 @@ async function loadRepoConfig(host, worktreePath, repoPath) {
|
|
|
48804
49099
|
}
|
|
48805
49100
|
async function loadEnvironment(host, worktreePath, repoPath) {
|
|
48806
49101
|
for (const base of [worktreePath, repoPath]) {
|
|
48807
|
-
const
|
|
48808
|
-
const text3 = await readConfig(host,
|
|
49102
|
+
const file3 = join28(base, ENVIRONMENT_FILE);
|
|
49103
|
+
const text3 = await readConfig(host, file3);
|
|
48809
49104
|
if (text3 === null) continue;
|
|
48810
49105
|
const exists3 = (relative2) => host.fs.stat(join28(base, relative2)).then(
|
|
48811
49106
|
() => true,
|
|
48812
49107
|
() => false
|
|
48813
49108
|
);
|
|
48814
49109
|
const result = await validateEnvironment(text3, exists3);
|
|
48815
|
-
return result.ok ? { source:
|
|
49110
|
+
return result.ok ? { source: file3, environment: result.environment, issues: [] } : { source: file3, environment: null, issues: result.issues };
|
|
48816
49111
|
}
|
|
48817
49112
|
return { source: null, environment: null, issues: [] };
|
|
48818
49113
|
}
|
|
@@ -48949,7 +49244,7 @@ var init_script_run = __esm({
|
|
|
48949
49244
|
});
|
|
48950
49245
|
|
|
48951
49246
|
// ../../packages/host-local/src/setup/worktree-files.ts
|
|
48952
|
-
import { dirname as
|
|
49247
|
+
import { dirname as dirname9, isAbsolute, join as join30, relative, resolve as resolve6, sep as sep4 } from "node:path";
|
|
48953
49248
|
async function copyWorktreeFiles(host, repoPath, worktreePath) {
|
|
48954
49249
|
const byAbs = /* @__PURE__ */ new Map();
|
|
48955
49250
|
const missingFromConfig = [];
|
|
@@ -48992,7 +49287,7 @@ async function copyWorktreeFiles(host, repoPath, worktreePath) {
|
|
|
48992
49287
|
);
|
|
48993
49288
|
continue;
|
|
48994
49289
|
}
|
|
48995
|
-
await host.fs.mkdir(
|
|
49290
|
+
await host.fs.mkdir(dirname9(dest), { recursive: true });
|
|
48996
49291
|
await host.fs.copy(absSource, dest);
|
|
48997
49292
|
log14.debug({ source, file: rel }, "copied worktree file");
|
|
48998
49293
|
copied.push(rel);
|
|
@@ -49329,7 +49624,8 @@ var init_local_host = __esm({
|
|
|
49329
49624
|
usageReader: async (agent) => getUsageReader(agent.agentType, { command: agent.command }),
|
|
49330
49625
|
installSkills: (options) => installSkills(options),
|
|
49331
49626
|
hooksStatus: () => checkHooks(),
|
|
49332
|
-
installHooks: () => installHooks()
|
|
49627
|
+
installHooks: () => installHooks(),
|
|
49628
|
+
removeHooks: () => removeHooks()
|
|
49333
49629
|
};
|
|
49334
49630
|
get pty() {
|
|
49335
49631
|
return this.options.terminalBackend();
|
|
@@ -49505,11 +49801,11 @@ var init_src5 = __esm({
|
|
|
49505
49801
|
});
|
|
49506
49802
|
|
|
49507
49803
|
// src/server/infra/db/queries/repos.ts
|
|
49508
|
-
import { existsSync as
|
|
49804
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
49509
49805
|
import { join as join32 } from "node:path";
|
|
49510
49806
|
function reconcileKindForRepo(repo) {
|
|
49511
|
-
if (!repo.path || !
|
|
49512
|
-
const detectedKind =
|
|
49807
|
+
if (!repo.path || !existsSync11(repo.path)) return false;
|
|
49808
|
+
const detectedKind = existsSync11(join32(repo.path, ".git")) ? "git" : "plain";
|
|
49513
49809
|
if (detectedKind === repo.kind) return false;
|
|
49514
49810
|
repo.kind = detectedKind;
|
|
49515
49811
|
if (detectedKind === "plain") {
|
|
@@ -49852,12 +50148,18 @@ var init_repos = __esm({
|
|
|
49852
50148
|
* Records where a worktree was started from, or clears it with `null`. Focused UPDATE, because
|
|
49853
50149
|
* `saveAll` keeps the stored origin and never writes the one in its snapshot.
|
|
49854
50150
|
*/
|
|
49855
|
-
setOrigin(repoName, name4, origin) {
|
|
50151
|
+
setOrigin(repoName, name4, origin, hostId2) {
|
|
49856
50152
|
getDb().update(worktrees).set({
|
|
49857
50153
|
originWorktreeId: origin?.worktreeId ?? null,
|
|
49858
50154
|
originChatId: origin?.chatId ?? null,
|
|
49859
50155
|
originTerminalId: origin?.terminalId ?? null
|
|
49860
|
-
}).where(
|
|
50156
|
+
}).where(
|
|
50157
|
+
and(
|
|
50158
|
+
eq(worktrees.repoName, repoName),
|
|
50159
|
+
eq(worktrees.name, name4),
|
|
50160
|
+
...hostId2 === void 0 ? [] : [eq(worktrees.hostId, hostId2)]
|
|
50161
|
+
)
|
|
50162
|
+
).run();
|
|
49861
50163
|
}
|
|
49862
50164
|
/**
|
|
49863
50165
|
* Path and kind of one repo, by primary key. For per-request lookups
|
|
@@ -50053,16 +50355,17 @@ var init_worktree_statuses = __esm({
|
|
|
50053
50355
|
db.delete(worktreeStatuses).where(eq(worktreeStatuses.worktreeId, worktreeId4)).run();
|
|
50054
50356
|
}
|
|
50055
50357
|
/**
|
|
50056
|
-
* Reset every "
|
|
50358
|
+
* Reset every row that is not "done" or "failed" back to "idle" and
|
|
50057
50359
|
* return the count of affected rows. Used at server startup to clean
|
|
50058
50360
|
* up stale state from the previous run.
|
|
50059
50361
|
*/
|
|
50060
|
-
|
|
50362
|
+
resetActiveToIdle(now) {
|
|
50061
50363
|
const db = getDb();
|
|
50062
|
-
const result = db.update(worktreeStatuses).set({ agentStatus: "
|
|
50364
|
+
const result = db.update(worktreeStatuses).set({ agentStatus: "idle", updatedAt: now }).where(
|
|
50063
50365
|
or(
|
|
50064
50366
|
eq(worktreeStatuses.agentStatus, "working"),
|
|
50065
|
-
eq(worktreeStatuses.agentStatus, "
|
|
50367
|
+
eq(worktreeStatuses.agentStatus, "monitoring"),
|
|
50368
|
+
eq(worktreeStatuses.agentStatus, "question")
|
|
50066
50369
|
)
|
|
50067
50370
|
).run();
|
|
50068
50371
|
return Number(result.changes);
|
|
@@ -50108,22 +50411,22 @@ var init_worktree_statuses = __esm({
|
|
|
50108
50411
|
const db = getDb();
|
|
50109
50412
|
db.delete(worktreeStatusSources).where(eq(worktreeStatusSources.worktreeId, worktreeId4)).run();
|
|
50110
50413
|
}
|
|
50111
|
-
/** Delete every source. Used at server startup: no agent state survives it. */
|
|
50112
|
-
removeAll() {
|
|
50113
|
-
getDb().delete(worktreeStatusSources).run();
|
|
50114
|
-
}
|
|
50115
50414
|
/**
|
|
50116
|
-
*
|
|
50117
|
-
*
|
|
50415
|
+
* Delete every source that is not `done` or `failed`. Used at server
|
|
50416
|
+
* startup: no running agent survives it, but a finished one stays.
|
|
50118
50417
|
*/
|
|
50119
|
-
|
|
50120
|
-
|
|
50121
|
-
|
|
50122
|
-
|
|
50123
|
-
|
|
50124
|
-
|
|
50125
|
-
|
|
50126
|
-
|
|
50418
|
+
removeActive() {
|
|
50419
|
+
getDb().delete(worktreeStatusSources).where(notInArray(worktreeStatusSources.status, ["done", "failed"])).run();
|
|
50420
|
+
}
|
|
50421
|
+
/** Set the worktree's `done` and `failed` sources to `idle`. Returns how many changed. */
|
|
50422
|
+
acknowledge(worktreeId4, now) {
|
|
50423
|
+
const result = getDb().update(worktreeStatusSources).set({ status: "idle", updatedAt: now }).where(
|
|
50424
|
+
and(
|
|
50425
|
+
eq(worktreeStatusSources.worktreeId, worktreeId4),
|
|
50426
|
+
inArray(worktreeStatusSources.status, ["done", "failed"])
|
|
50427
|
+
)
|
|
50428
|
+
).run();
|
|
50429
|
+
return Number(result.changes);
|
|
50127
50430
|
}
|
|
50128
50431
|
};
|
|
50129
50432
|
}
|
|
@@ -50183,8 +50486,8 @@ var init_isolation = __esm({
|
|
|
50183
50486
|
});
|
|
50184
50487
|
|
|
50185
50488
|
// src/server/services/_utils/runner-config.ts
|
|
50186
|
-
import { existsSync as
|
|
50187
|
-
import { dirname as
|
|
50489
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
50490
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join33, resolve as resolve7 } from "node:path";
|
|
50188
50491
|
function parseRunners(raw) {
|
|
50189
50492
|
if (raw === void 0 || raw === null) return { runners: [], errors: [] };
|
|
50190
50493
|
if (!Array.isArray(raw)) return { runners: [], errors: ["runners must be a list"] };
|
|
@@ -50210,8 +50513,8 @@ function bundledRunnersDir() {
|
|
|
50210
50513
|
let dir = import.meta.dirname;
|
|
50211
50514
|
for (let i = 0; i < 10; i++) {
|
|
50212
50515
|
const candidate = join33(dir, "runners");
|
|
50213
|
-
if (
|
|
50214
|
-
const parent =
|
|
50516
|
+
if (existsSync12(join33(candidate, "local", "spawn.sh"))) return candidate;
|
|
50517
|
+
const parent = dirname10(dir);
|
|
50215
50518
|
if (parent === dir) break;
|
|
50216
50519
|
dir = parent;
|
|
50217
50520
|
}
|
|
@@ -50691,16 +50994,20 @@ function upsertWorktreeStatus(worktreeId4, agent) {
|
|
|
50691
50994
|
tabStatuses: listTabStatuses(worktreeId4)
|
|
50692
50995
|
};
|
|
50693
50996
|
}
|
|
50694
|
-
function resolveWorktreeIdByCwd(cwd) {
|
|
50997
|
+
function resolveWorktreeIdByCwd(cwd, hostId2) {
|
|
50695
50998
|
const state2 = loadState();
|
|
50999
|
+
let other = null;
|
|
50696
51000
|
for (const proj of state2.repos) {
|
|
50697
51001
|
for (const wt of proj.worktrees) {
|
|
50698
|
-
|
|
50699
|
-
|
|
50700
|
-
}
|
|
51002
|
+
const wtHost = wt.hostId ?? LOCAL_HOST_ID3;
|
|
51003
|
+
if (hostId2 !== void 0 && wtHost !== hostId2) continue;
|
|
51004
|
+
if (cwd !== wt.path && !cwd.startsWith(`${wt.path}/`)) continue;
|
|
51005
|
+
const id = toWorktreeId(proj.name, wt.name, wt.hostId);
|
|
51006
|
+
if (wtHost === LOCAL_HOST_ID3) return id;
|
|
51007
|
+
other ??= id;
|
|
50701
51008
|
}
|
|
50702
51009
|
}
|
|
50703
|
-
return
|
|
51010
|
+
return other;
|
|
50704
51011
|
}
|
|
50705
51012
|
function chatStatusSource(chatId) {
|
|
50706
51013
|
return `${CHAT_SOURCE_PREFIX}${chatId}`;
|
|
@@ -50718,20 +51025,21 @@ function deriveWorktreeStatus(worktreeId4) {
|
|
|
50718
51025
|
best = status;
|
|
50719
51026
|
}
|
|
50720
51027
|
}
|
|
50721
|
-
return best ?? "
|
|
51028
|
+
return best ?? "idle";
|
|
50722
51029
|
}
|
|
50723
51030
|
function toTabStatuses(sources) {
|
|
50724
51031
|
const staleBefore = Date.now() - STALE_HOOK_WORKING_MS;
|
|
50725
51032
|
const byTab = /* @__PURE__ */ new Map();
|
|
50726
51033
|
for (const source of sources) {
|
|
50727
51034
|
const { status } = source;
|
|
50728
|
-
if (status
|
|
51035
|
+
if (status === "idle" || !(status in STATUS_PRIORITY)) continue;
|
|
50729
51036
|
if (isStaleSource(source, staleBefore)) continue;
|
|
51037
|
+
const tabStatus = status;
|
|
50730
51038
|
let tab;
|
|
50731
51039
|
if (source.sourceId.startsWith(CHAT_SOURCE_PREFIX)) {
|
|
50732
|
-
tab = { chatId: source.sourceId.slice(CHAT_SOURCE_PREFIX.length), status };
|
|
51040
|
+
tab = { chatId: source.sourceId.slice(CHAT_SOURCE_PREFIX.length), status: tabStatus };
|
|
50733
51041
|
} else if (source.terminalId) {
|
|
50734
|
-
tab = { terminalId: source.terminalId, status };
|
|
51042
|
+
tab = { terminalId: source.terminalId, status: tabStatus };
|
|
50735
51043
|
} else {
|
|
50736
51044
|
continue;
|
|
50737
51045
|
}
|
|
@@ -50765,10 +51073,10 @@ function removeWorktreeSource(worktreeId4, sourceId) {
|
|
|
50765
51073
|
if (!statusSourceQueries.remove(worktreeId4, sourceId)) return null;
|
|
50766
51074
|
return rederiveWorktreeStatus(worktreeId4);
|
|
50767
51075
|
}
|
|
50768
|
-
function acknowledgeWorktreeAttention(worktreeId4
|
|
51076
|
+
function acknowledgeWorktreeAttention(worktreeId4) {
|
|
50769
51077
|
const existing = getWorktreeStatus(worktreeId4);
|
|
50770
|
-
if (existing
|
|
50771
|
-
statusSourceQueries.acknowledge(worktreeId4,
|
|
51078
|
+
if (!existing) return null;
|
|
51079
|
+
if (statusSourceQueries.acknowledge(worktreeId4, Date.now()) === 0) return existing;
|
|
50772
51080
|
return upsertWorktreeStatus(worktreeId4, { status: deriveWorktreeStatus(worktreeId4) });
|
|
50773
51081
|
}
|
|
50774
51082
|
function startStatusSourceCleanup() {
|
|
@@ -50786,17 +51094,33 @@ function startStatusSourceCleanup() {
|
|
|
50786
51094
|
for (const status of changed) emit({ kind: "update", status });
|
|
50787
51095
|
});
|
|
50788
51096
|
}
|
|
51097
|
+
function subagentHold(worktreeId4, sourceId) {
|
|
51098
|
+
const key2 = `${worktreeId4}\0${sourceId}`;
|
|
51099
|
+
let hold = subagentHolds.get(key2);
|
|
51100
|
+
if (!hold) {
|
|
51101
|
+
hold = { running: /* @__PURE__ */ new Set(), deferred: null };
|
|
51102
|
+
subagentHolds.set(key2, hold);
|
|
51103
|
+
}
|
|
51104
|
+
return hold;
|
|
51105
|
+
}
|
|
51106
|
+
function dropSubagentHold(worktreeId4, sourceId) {
|
|
51107
|
+
subagentHolds.delete(`${worktreeId4}\0${sourceId}`);
|
|
51108
|
+
}
|
|
50789
51109
|
async function applyHookNotification(notification) {
|
|
50790
51110
|
const { payload } = notification;
|
|
50791
51111
|
if (notification.dispatch === "chat") return null;
|
|
50792
|
-
const worktreeId4 = resolveWorktreeIdByCwd(notification.cwd);
|
|
51112
|
+
const worktreeId4 = resolveWorktreeIdByCwd(notification.cwd, notification.hostId ?? LOCAL_HOST_ID3);
|
|
50793
51113
|
if (!worktreeId4) return null;
|
|
50794
51114
|
const agentType = notification.agent || detectHookAgentType(payload) || settingsService.getAgentDefinition(getWorktreeStatus(worktreeId4)?.agent?.codingAgentId).type;
|
|
50795
51115
|
const sourceId = `hook:${hookSessionId(payload) ?? agentType}`;
|
|
50796
51116
|
if (isHookSessionEnd(agentType, payload)) {
|
|
51117
|
+
dropSubagentHold(worktreeId4, sourceId);
|
|
50797
51118
|
return removeWorktreeSource(worktreeId4, sourceId);
|
|
50798
51119
|
}
|
|
50799
|
-
|
|
51120
|
+
let status = await mapHookPayloadToStatus(agentType, payload);
|
|
51121
|
+
if (agentType === "claude-code") {
|
|
51122
|
+
status = foldSubagents(subagentHold(worktreeId4, sourceId), payload, status);
|
|
51123
|
+
}
|
|
50800
51124
|
return setWorktreeSourceStatus(worktreeId4, sourceId, {
|
|
50801
51125
|
status,
|
|
50802
51126
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -50804,8 +51128,9 @@ async function applyHookNotification(notification) {
|
|
|
50804
51128
|
});
|
|
50805
51129
|
}
|
|
50806
51130
|
function resetAgentStatuses() {
|
|
50807
|
-
|
|
50808
|
-
|
|
51131
|
+
subagentHolds.clear();
|
|
51132
|
+
statusSourceQueries.removeActive();
|
|
51133
|
+
return worktreeStatusQueries.resetActiveToIdle(Date.now());
|
|
50809
51134
|
}
|
|
50810
51135
|
function resolveWorktreeIdentity(worktreeId4) {
|
|
50811
51136
|
return worktreeQueriesForIdentity.findIdentity(worktreeId4);
|
|
@@ -50814,7 +51139,7 @@ function deleteWorktreeStatus(worktreeId4) {
|
|
|
50814
51139
|
worktreeStatusQueries.remove(worktreeId4);
|
|
50815
51140
|
statusSourceQueries.removeForWorktree(worktreeId4);
|
|
50816
51141
|
}
|
|
50817
|
-
var worktreeStatusQueries, statusSourceQueries, worktreeQueriesForIdentity, repoQueries, STALE_HOOK_WORKING_MS, STATUS_PRIORITY, CHAT_SOURCE_PREFIX, MANUAL_STATUS_SOURCE;
|
|
51142
|
+
var worktreeStatusQueries, statusSourceQueries, worktreeQueriesForIdentity, repoQueries, STALE_HOOK_WORKING_MS, LOCAL_HOST_ID3, STATUS_PRIORITY, CHAT_SOURCE_PREFIX, MANUAL_STATUS_SOURCE, subagentHolds;
|
|
50818
51143
|
var init_state = __esm({
|
|
50819
51144
|
"src/server/services/state.ts"() {
|
|
50820
51145
|
"use strict";
|
|
@@ -50831,9 +51156,18 @@ var init_state = __esm({
|
|
|
50831
51156
|
worktreeQueriesForIdentity = new WorktreeQueries();
|
|
50832
51157
|
repoQueries = new RepoQueries();
|
|
50833
51158
|
STALE_HOOK_WORKING_MS = 15 * 6e4;
|
|
50834
|
-
|
|
51159
|
+
LOCAL_HOST_ID3 = "local";
|
|
51160
|
+
STATUS_PRIORITY = {
|
|
51161
|
+
question: 6,
|
|
51162
|
+
failed: 5,
|
|
51163
|
+
working: 4,
|
|
51164
|
+
monitoring: 3,
|
|
51165
|
+
done: 2,
|
|
51166
|
+
idle: 1
|
|
51167
|
+
};
|
|
50835
51168
|
CHAT_SOURCE_PREFIX = "chat:";
|
|
50836
51169
|
MANUAL_STATUS_SOURCE = "manual";
|
|
51170
|
+
subagentHolds = /* @__PURE__ */ new Map();
|
|
50837
51171
|
}
|
|
50838
51172
|
});
|
|
50839
51173
|
|
|
@@ -51733,8 +52067,8 @@ var init_map_limited = __esm({
|
|
|
51733
52067
|
|
|
51734
52068
|
// src/server/infra/subscriptions/github.ts
|
|
51735
52069
|
import { createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
51736
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
51737
|
-
import { dirname as
|
|
52070
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
52071
|
+
import { dirname as dirname11, join as join36 } from "node:path";
|
|
51738
52072
|
function secretPath() {
|
|
51739
52073
|
return join36(bandHome(), "github-webhook-secret");
|
|
51740
52074
|
}
|
|
@@ -51752,7 +52086,7 @@ function ensureWebhookSecret() {
|
|
|
51752
52086
|
const existing = readWebhookSecret();
|
|
51753
52087
|
if (existing) return existing;
|
|
51754
52088
|
const path2 = secretPath();
|
|
51755
|
-
if (!
|
|
52089
|
+
if (!existsSync13(dirname11(path2))) mkdirSync7(dirname11(path2), { recursive: true });
|
|
51756
52090
|
const secret = randomBytes3(32).toString("hex");
|
|
51757
52091
|
writeFileSync5(path2, `${secret}
|
|
51758
52092
|
`, { mode: 384 });
|
|
@@ -52485,7 +52819,7 @@ var init_oauth_client = __esm({
|
|
|
52485
52819
|
|
|
52486
52820
|
// src/server/services/_utils/vault-crypto.ts
|
|
52487
52821
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
|
|
52488
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
52822
|
+
import { chmodSync as chmodSync3, existsSync as existsSync14, readFileSync as readFileSync9, renameSync as renameSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
52489
52823
|
import { join as join37 } from "node:path";
|
|
52490
52824
|
function vaultKeyFile() {
|
|
52491
52825
|
return join37(bandHome(), "vault.key");
|
|
@@ -52507,21 +52841,21 @@ function generateKey() {
|
|
|
52507
52841
|
function loadKey() {
|
|
52508
52842
|
const fromEnv = process.env.BAND_VAULT_KEY?.trim();
|
|
52509
52843
|
if (fromEnv) return parseKey(fromEnv);
|
|
52510
|
-
const
|
|
52511
|
-
if (
|
|
52844
|
+
const file3 = vaultKeyFile();
|
|
52845
|
+
if (existsSync14(file3)) return parseKey(readFileSync9(file3, "utf8"));
|
|
52512
52846
|
const key2 = generateKey();
|
|
52513
|
-
writeFileSync6(
|
|
52847
|
+
writeFileSync6(file3, `${key2.toString("base64")}
|
|
52514
52848
|
`, { mode: 384, flag: "wx" });
|
|
52515
|
-
chmodSync3(
|
|
52849
|
+
chmodSync3(file3, 384);
|
|
52516
52850
|
return key2;
|
|
52517
52851
|
}
|
|
52518
52852
|
function stageKeyFile(key2) {
|
|
52519
|
-
const
|
|
52520
|
-
const temp = `${
|
|
52853
|
+
const file3 = vaultKeyFile();
|
|
52854
|
+
const temp = `${file3}.${process.pid}.next`;
|
|
52521
52855
|
writeFileSync6(temp, `${key2.toString("base64")}
|
|
52522
52856
|
`, { mode: 384 });
|
|
52523
52857
|
chmodSync3(temp, 384);
|
|
52524
|
-
return { commit: () =>
|
|
52858
|
+
return { commit: () => renameSync7(temp, file3), discard: () => rmSync3(temp, { force: true }) };
|
|
52525
52859
|
}
|
|
52526
52860
|
function encrypt(key2, aad, plaintext) {
|
|
52527
52861
|
const nonce = randomBytes5(NONCE_BYTES);
|
|
@@ -53736,9 +54070,15 @@ var init_mime_types = __esm({
|
|
|
53736
54070
|
".txt": "text/plain",
|
|
53737
54071
|
".md": "text/markdown",
|
|
53738
54072
|
".csv": "text/csv",
|
|
53739
|
-
".html": "text/html",
|
|
53740
|
-
".
|
|
53741
|
-
".
|
|
54073
|
+
".html": "text/html; charset=utf-8",
|
|
54074
|
+
".htm": "text/html; charset=utf-8",
|
|
54075
|
+
".css": "text/css; charset=utf-8",
|
|
54076
|
+
".js": "text/javascript; charset=utf-8",
|
|
54077
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
54078
|
+
".woff": "font/woff",
|
|
54079
|
+
".woff2": "font/woff2",
|
|
54080
|
+
".ttf": "font/ttf",
|
|
54081
|
+
".otf": "font/otf",
|
|
53742
54082
|
".ts": "application/typescript",
|
|
53743
54083
|
".xml": "application/xml",
|
|
53744
54084
|
".yaml": "application/x-yaml",
|
|
@@ -53765,6 +54105,132 @@ var init_turn_events = __esm({
|
|
|
53765
54105
|
}
|
|
53766
54106
|
});
|
|
53767
54107
|
|
|
54108
|
+
// src/server/services/chat-status-service.ts
|
|
54109
|
+
function quietMs() {
|
|
54110
|
+
const override = Number(process.env.BAND_AGENT_QUIET_MS);
|
|
54111
|
+
return Number.isFinite(override) && override > 0 ? override : QUIET_MS;
|
|
54112
|
+
}
|
|
54113
|
+
function setChatStatus(worktreeId4, chatId, status) {
|
|
54114
|
+
if (status === "monitoring") monitoringChats.add(chatId);
|
|
54115
|
+
else monitoringChats.delete(chatId);
|
|
54116
|
+
const updated = setWorktreeSourceStatus(worktreeId4, chatStatusSource(chatId), { status });
|
|
54117
|
+
emit({ kind: "update", status: updated });
|
|
54118
|
+
}
|
|
54119
|
+
function restingStatus(chatId) {
|
|
54120
|
+
if (agentSessionService.hasPendingRequest(chatId)) return "question";
|
|
54121
|
+
if (agentSessionService.nextWorkDeadline(chatId) !== null) return "monitoring";
|
|
54122
|
+
return "done";
|
|
54123
|
+
}
|
|
54124
|
+
function beginBandTurn(worktreeId4, chatId) {
|
|
54125
|
+
endAutonomous(chatId);
|
|
54126
|
+
clearMonitorTimer(chatId);
|
|
54127
|
+
setChatStatus(worktreeId4, chatId, "working");
|
|
54128
|
+
}
|
|
54129
|
+
function endBandTurn(worktreeId4, chatId, result) {
|
|
54130
|
+
const status = result === "done" ? restingStatus(chatId) : result;
|
|
54131
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54132
|
+
if (status === "monitoring") watchMonitoring(worktreeId4, chatId);
|
|
54133
|
+
}
|
|
54134
|
+
function endAutonomous(chatId) {
|
|
54135
|
+
const timer = autonomous.get(chatId);
|
|
54136
|
+
if (timer) clearTimeout(timer);
|
|
54137
|
+
autonomous.delete(chatId);
|
|
54138
|
+
}
|
|
54139
|
+
function clearMonitorTimer(chatId) {
|
|
54140
|
+
const timer = monitorTimers.get(chatId);
|
|
54141
|
+
if (timer) clearTimeout(timer);
|
|
54142
|
+
monitorTimers.delete(chatId);
|
|
54143
|
+
}
|
|
54144
|
+
function watchMonitoring(worktreeId4, chatId) {
|
|
54145
|
+
clearMonitorTimer(chatId);
|
|
54146
|
+
const deadline = agentSessionService.nextWorkDeadline(chatId);
|
|
54147
|
+
if (deadline === null) return;
|
|
54148
|
+
const timer = setTimeout(
|
|
54149
|
+
() => {
|
|
54150
|
+
monitorTimers.delete(chatId);
|
|
54151
|
+
if (runningTurn(chatId) || autonomous.has(chatId)) return;
|
|
54152
|
+
reconsiderMonitoring(worktreeId4, chatId);
|
|
54153
|
+
},
|
|
54154
|
+
Math.min(Math.max(deadline - Date.now(), 0) + 100, MAX_TIMER_MS)
|
|
54155
|
+
);
|
|
54156
|
+
timer.unref?.();
|
|
54157
|
+
monitorTimers.set(chatId, timer);
|
|
54158
|
+
}
|
|
54159
|
+
function reconsiderMonitoring(worktreeId4, chatId) {
|
|
54160
|
+
if (!chatService.get(chatId) || !monitoringChats.has(chatId)) return;
|
|
54161
|
+
const status = restingStatus(chatId);
|
|
54162
|
+
if (status === "monitoring") {
|
|
54163
|
+
watchMonitoring(worktreeId4, chatId);
|
|
54164
|
+
return;
|
|
54165
|
+
}
|
|
54166
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54167
|
+
}
|
|
54168
|
+
function settleAutonomous(worktreeId4, chatId) {
|
|
54169
|
+
autonomous.delete(chatId);
|
|
54170
|
+
if (!chatService.get(chatId) || runningTurn(chatId)) return;
|
|
54171
|
+
const status = restingStatus(chatId);
|
|
54172
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54173
|
+
if (status === "monitoring") watchMonitoring(worktreeId4, chatId);
|
|
54174
|
+
}
|
|
54175
|
+
function armQuietTimer(worktreeId4, chatId) {
|
|
54176
|
+
const previous = autonomous.get(chatId);
|
|
54177
|
+
if (previous) clearTimeout(previous);
|
|
54178
|
+
const timer = setTimeout(() => settleAutonomous(worktreeId4, chatId), quietMs());
|
|
54179
|
+
timer.unref?.();
|
|
54180
|
+
autonomous.set(chatId, timer);
|
|
54181
|
+
}
|
|
54182
|
+
function observeAgentStatus(hasRunningTurn) {
|
|
54183
|
+
if (observing) return;
|
|
54184
|
+
observing = true;
|
|
54185
|
+
runningTurn = hasRunningTurn;
|
|
54186
|
+
agentSessionService.observePending((chatId, worktreeId4) => {
|
|
54187
|
+
const waiting = agentSessionService.hasPendingRequest(chatId);
|
|
54188
|
+
if (runningTurn(chatId)) {
|
|
54189
|
+
setChatStatus(worktreeId4, chatId, waiting ? "question" : "working");
|
|
54190
|
+
return;
|
|
54191
|
+
}
|
|
54192
|
+
if (waiting) {
|
|
54193
|
+
const timer = autonomous.get(chatId);
|
|
54194
|
+
if (timer) clearTimeout(timer);
|
|
54195
|
+
autonomous.set(chatId, null);
|
|
54196
|
+
setChatStatus(worktreeId4, chatId, "question");
|
|
54197
|
+
} else if (autonomous.has(chatId)) {
|
|
54198
|
+
setChatStatus(worktreeId4, chatId, "working");
|
|
54199
|
+
armQuietTimer(worktreeId4, chatId);
|
|
54200
|
+
}
|
|
54201
|
+
});
|
|
54202
|
+
agentSessionService.observeActivity((chatId, worktreeId4, kind) => {
|
|
54203
|
+
if (runningTurn(chatId) || !chatService.get(chatId)) return;
|
|
54204
|
+
if (kind === "update") {
|
|
54205
|
+
if (agentSessionService.hasPendingRequest(chatId)) return;
|
|
54206
|
+
const wasWorking = autonomous.has(chatId);
|
|
54207
|
+
armQuietTimer(worktreeId4, chatId);
|
|
54208
|
+
clearMonitorTimer(chatId);
|
|
54209
|
+
if (!wasWorking) setChatStatus(worktreeId4, chatId, "working");
|
|
54210
|
+
return;
|
|
54211
|
+
}
|
|
54212
|
+
if (autonomous.has(chatId)) return;
|
|
54213
|
+
reconsiderMonitoring(worktreeId4, chatId);
|
|
54214
|
+
});
|
|
54215
|
+
}
|
|
54216
|
+
var QUIET_MS, MAX_TIMER_MS, autonomous, monitorTimers, monitoringChats, runningTurn, observing;
|
|
54217
|
+
var init_chat_status_service = __esm({
|
|
54218
|
+
"src/server/services/chat-status-service.ts"() {
|
|
54219
|
+
"use strict";
|
|
54220
|
+
init_agent_session_service();
|
|
54221
|
+
init_chat_service();
|
|
54222
|
+
init_state();
|
|
54223
|
+
init_watcher_service();
|
|
54224
|
+
QUIET_MS = 1e4;
|
|
54225
|
+
MAX_TIMER_MS = 2 ** 31 - 1;
|
|
54226
|
+
autonomous = /* @__PURE__ */ new Map();
|
|
54227
|
+
monitorTimers = /* @__PURE__ */ new Map();
|
|
54228
|
+
monitoringChats = /* @__PURE__ */ new Set();
|
|
54229
|
+
runningTurn = () => false;
|
|
54230
|
+
observing = false;
|
|
54231
|
+
}
|
|
54232
|
+
});
|
|
54233
|
+
|
|
53768
54234
|
// ../../packages/host-local/src/git/git-refusals.ts
|
|
53769
54235
|
async function pullRefusal(err, cwd, execGit2) {
|
|
53770
54236
|
const stderr = errorText(err);
|
|
@@ -54212,7 +54678,7 @@ var init_worktree_checkpoints = __esm({
|
|
|
54212
54678
|
});
|
|
54213
54679
|
|
|
54214
54680
|
// src/server/services/_utils/checkpoint-store.ts
|
|
54215
|
-
import { rmSync as
|
|
54681
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
54216
54682
|
import { join as join38 } from "node:path";
|
|
54217
54683
|
function worktreeCheckpointDir(worktreeId4) {
|
|
54218
54684
|
return join38(bandHome(), "checkpoints", worktreeId4);
|
|
@@ -54222,7 +54688,7 @@ function checkpointDir(worktreeId4, checkpointId2) {
|
|
|
54222
54688
|
}
|
|
54223
54689
|
function forgetCheckpoints(worktreeId4) {
|
|
54224
54690
|
queries2.deleteForWorktree(worktreeId4);
|
|
54225
|
-
|
|
54691
|
+
rmSync4(worktreeCheckpointDir(worktreeId4), { recursive: true, force: true });
|
|
54226
54692
|
}
|
|
54227
54693
|
var queries2;
|
|
54228
54694
|
var init_checkpoint_store = __esm({
|
|
@@ -54342,13 +54808,13 @@ var init_remote_worktrees = __esm({
|
|
|
54342
54808
|
// ../../packages/shared/src/repo-hosts.ts
|
|
54343
54809
|
function isHostAllowed(allowed2, hostId2) {
|
|
54344
54810
|
if (!allowed2) return true;
|
|
54345
|
-
return allowed2.includes(hostId2 ||
|
|
54811
|
+
return allowed2.includes(hostId2 || LOCAL_HOST_ID4);
|
|
54346
54812
|
}
|
|
54347
54813
|
function effectivePrimaryHost(primaryHostId, allowed2, hostsWithDefaultCheckout) {
|
|
54348
54814
|
if (primaryHostId && isHostAllowed(allowed2, primaryHostId)) return primaryHostId;
|
|
54349
54815
|
const candidates = hostsWithDefaultCheckout.filter((id) => isHostAllowed(allowed2, id));
|
|
54350
54816
|
if (candidates.length === 0) return null;
|
|
54351
|
-
if (candidates.includes(
|
|
54817
|
+
if (candidates.includes(LOCAL_HOST_ID4)) return LOCAL_HOST_ID4;
|
|
54352
54818
|
return [...candidates].sort()[0] ?? null;
|
|
54353
54819
|
}
|
|
54354
54820
|
function onlyAvailableOnMessage(repo, hostNames) {
|
|
@@ -54358,10 +54824,10 @@ function checkpointsEnabledFor(stored, allowed2) {
|
|
|
54358
54824
|
if (stored != null) return stored;
|
|
54359
54825
|
return !allowed2;
|
|
54360
54826
|
}
|
|
54361
|
-
var
|
|
54827
|
+
var LOCAL_HOST_ID4;
|
|
54362
54828
|
var init_repo_hosts = __esm({
|
|
54363
54829
|
"../../packages/shared/src/repo-hosts.ts"() {
|
|
54364
|
-
|
|
54830
|
+
LOCAL_HOST_ID4 = "local";
|
|
54365
54831
|
}
|
|
54366
54832
|
});
|
|
54367
54833
|
|
|
@@ -56246,6 +56712,7 @@ var init_rpc = __esm({
|
|
|
56246
56712
|
"scripts.copyFiles": 2 * 6e4,
|
|
56247
56713
|
"agentEnv.installSkills": 2 * 6e4,
|
|
56248
56714
|
"agentEnv.installHooks": 6e4,
|
|
56715
|
+
"agentEnv.removeHooks": 6e4,
|
|
56249
56716
|
exec: 10 * 6e4
|
|
56250
56717
|
};
|
|
56251
56718
|
RemoteRpc = class {
|
|
@@ -56519,7 +56986,8 @@ var init_remote_host = __esm({
|
|
|
56519
56986
|
// The worker takes no `home` override. It is for tests of the local install.
|
|
56520
56987
|
installSkills: () => this.rpc.call("agentEnv.installSkills"),
|
|
56521
56988
|
hooksStatus: () => this.rpc.call("agentEnv.hooksStatus"),
|
|
56522
|
-
installHooks: () => this.rpc.call("agentEnv.installHooks")
|
|
56989
|
+
installHooks: () => this.rpc.call("agentEnv.installHooks"),
|
|
56990
|
+
removeHooks: () => this.rpc.call("agentEnv.removeHooks")
|
|
56523
56991
|
};
|
|
56524
56992
|
// ---- fs -----------------------------------------------------------------
|
|
56525
56993
|
async writeFile(path2, data, options) {
|
|
@@ -56931,7 +57399,7 @@ function stringRecord(value) {
|
|
|
56931
57399
|
function stringList2(value) {
|
|
56932
57400
|
return Array.isArray(value) ? value.filter((v2) => typeof v2 === "string") : [];
|
|
56933
57401
|
}
|
|
56934
|
-
var log20, repoQueries4,
|
|
57402
|
+
var log20, repoQueries4, LOCAL_HOST_ID5, DEFAULT_TIMEOUT_MS, DEFAULT_LEASE_MS, MAX_LEASE_MS, SWEEP_MS, leaseFilterInput, HostRequestError, PlacementService, placementService;
|
|
56935
57403
|
var init_placement_service = __esm({
|
|
56936
57404
|
"src/server/services/placement-service.ts"() {
|
|
56937
57405
|
"use strict";
|
|
@@ -56955,7 +57423,7 @@ var init_placement_service = __esm({
|
|
|
56955
57423
|
init_worktree_service();
|
|
56956
57424
|
log20 = createLogger("placement");
|
|
56957
57425
|
repoQueries4 = new RepoQueries();
|
|
56958
|
-
|
|
57426
|
+
LOCAL_HOST_ID5 = "local";
|
|
56959
57427
|
DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
56960
57428
|
DEFAULT_LEASE_MS = 6e4;
|
|
56961
57429
|
MAX_LEASE_MS = 15 * 6e4;
|
|
@@ -57003,7 +57471,7 @@ var init_placement_service = __esm({
|
|
|
57003
57471
|
for (const row of tokenService.listHosts(1e3)) {
|
|
57004
57472
|
if (row.status !== "online" || !row.usable) continue;
|
|
57005
57473
|
if (row.info?.mode === "ephemeral") continue;
|
|
57006
|
-
const info = row.id ===
|
|
57474
|
+
const info = row.id === LOCAL_HOST_ID5 ? await hostRegistry.local.info().catch(() => null) : null;
|
|
57007
57475
|
const stored = row.info ?? {};
|
|
57008
57476
|
const versions = stringRecord(info?.versions ?? stored.versions);
|
|
57009
57477
|
const tools = stringRecord(info?.tools ?? stored.tools);
|
|
@@ -57035,7 +57503,7 @@ var init_placement_service = __esm({
|
|
|
57035
57503
|
const fits = (await this.candidates()).filter(
|
|
57036
57504
|
(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
|
|
57037
57505
|
// described live elsewhere, so it is not held to a report here.
|
|
57038
|
-
(agent === void 0 || c.id ===
|
|
57506
|
+
(agent === void 0 || c.id === LOCAL_HOST_ID5 || c.agents.includes(agent)) && matches(c, placement)
|
|
57039
57507
|
);
|
|
57040
57508
|
if (fits.length === 0) return null;
|
|
57041
57509
|
const load = this.queries.worktreeCounts();
|
|
@@ -57452,8 +57920,8 @@ var init_environment_builds = __esm({
|
|
|
57452
57920
|
get(id) {
|
|
57453
57921
|
return getDb().select().from(environmentBuilds).where(eq(environmentBuilds.id, id)).get();
|
|
57454
57922
|
}
|
|
57455
|
-
setLog(id,
|
|
57456
|
-
getDb().update(environmentBuilds).set({ log:
|
|
57923
|
+
setLog(id, log78) {
|
|
57924
|
+
getDb().update(environmentBuilds).set({ log: log78 }).where(eq(environmentBuilds.id, id)).run();
|
|
57457
57925
|
}
|
|
57458
57926
|
finish(id, result) {
|
|
57459
57927
|
getDb().update(environmentBuilds).set({
|
|
@@ -57821,48 +58289,48 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57821
58289
|
}
|
|
57822
58290
|
// ---- running ---------------------------------------------------------------
|
|
57823
58291
|
async run(row, plan) {
|
|
57824
|
-
const
|
|
58292
|
+
const log78 = new BuildLog(row.id, (id, text3) => this.queries.setLog(id, text3));
|
|
57825
58293
|
let tmp = null;
|
|
57826
58294
|
const container = `band-env-${row.id.slice(0, 12)}`;
|
|
57827
|
-
let
|
|
58295
|
+
let failure2 = null;
|
|
57828
58296
|
try {
|
|
57829
|
-
|
|
57830
|
-
|
|
58297
|
+
log78.add(`Building ${plan.tag} for ${plan.repo} at ${plan.ref} (${plan.commit.slice(0, 12)})`);
|
|
58298
|
+
log78.flush();
|
|
57831
58299
|
tmp = await plan.host.fs.mkdtemp("band-env-");
|
|
57832
|
-
await this.exportCheckout(plan, tmp,
|
|
57833
|
-
const toolchain = await this.buildToolchain(plan, tmp,
|
|
57834
|
-
const layered = await this.addWorkerLayer(plan, tmp, toolchain,
|
|
57835
|
-
await this.install(plan, tmp, layered, container,
|
|
57836
|
-
if (plan.registry) await this.push(plan,
|
|
57837
|
-
|
|
58300
|
+
await this.exportCheckout(plan, tmp, log78);
|
|
58301
|
+
const toolchain = await this.buildToolchain(plan, tmp, log78);
|
|
58302
|
+
const layered = await this.addWorkerLayer(plan, tmp, toolchain, log78);
|
|
58303
|
+
await this.install(plan, tmp, layered, container, log78);
|
|
58304
|
+
if (plan.registry) await this.push(plan, log78);
|
|
58305
|
+
log78.add(`Done: ${plan.tag}`);
|
|
57838
58306
|
} catch (err) {
|
|
57839
|
-
|
|
57840
|
-
|
|
58307
|
+
failure2 = err.message;
|
|
58308
|
+
log78.add(`FAILED: ${failure2}`);
|
|
57841
58309
|
}
|
|
57842
|
-
await this.exec(plan.host, "docker", ["rm", "--force", container],
|
|
58310
|
+
await this.exec(plan.host, "docker", ["rm", "--force", container], log78, { quiet: true }).catch(
|
|
57843
58311
|
() => void 0
|
|
57844
58312
|
);
|
|
57845
58313
|
await this.exec(
|
|
57846
58314
|
plan.host,
|
|
57847
58315
|
"docker",
|
|
57848
58316
|
["rmi", toolchainTag(plan.repo, plan.key), layeredTag(plan.repo, plan.key)],
|
|
57849
|
-
|
|
58317
|
+
log78,
|
|
57850
58318
|
{ quiet: true }
|
|
57851
58319
|
).catch(() => void 0);
|
|
57852
58320
|
if (tmp) await plan.host.fs.rm(tmp, { recursive: true, force: true }).catch(() => void 0);
|
|
57853
58321
|
try {
|
|
57854
|
-
if (
|
|
58322
|
+
if (failure2 === null) {
|
|
57855
58323
|
this.queries.finish(row.id, {
|
|
57856
58324
|
status: "ready",
|
|
57857
58325
|
image: plan.tag,
|
|
57858
|
-
log:
|
|
58326
|
+
log: log78.toString(),
|
|
57859
58327
|
at: Date.now()
|
|
57860
58328
|
});
|
|
57861
58329
|
} else {
|
|
57862
58330
|
this.queries.finish(row.id, {
|
|
57863
58331
|
status: "failed",
|
|
57864
|
-
error:
|
|
57865
|
-
log:
|
|
58332
|
+
error: failure2.slice(0, 2e3),
|
|
58333
|
+
log: log78.toString(),
|
|
57866
58334
|
at: Date.now()
|
|
57867
58335
|
});
|
|
57868
58336
|
}
|
|
@@ -57871,9 +58339,9 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57871
58339
|
}
|
|
57872
58340
|
}
|
|
57873
58341
|
/** Runs a command on the builder host, adding it and its output to the log. */
|
|
57874
|
-
async exec(host, tool, args,
|
|
58342
|
+
async exec(host, tool, args, log78, options = {}) {
|
|
57875
58343
|
const bin = tool === "docker" ? this.dockerBin(host) : tool === "devcontainer" ? this.devcontainerBin(host) : "git";
|
|
57876
|
-
if (!options.quiet)
|
|
58344
|
+
if (!options.quiet) log78.add(`$ ${tool} ${args.join(" ")}`);
|
|
57877
58345
|
try {
|
|
57878
58346
|
const out = await host.exec(bin, args, {
|
|
57879
58347
|
cwd: options.cwd,
|
|
@@ -57881,35 +58349,35 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57881
58349
|
timeoutMs: envNumber("BAND_ENVIRONMENT_BUILD_TIMEOUT_MS", DEFAULT_STEP_TIMEOUT_MS)
|
|
57882
58350
|
});
|
|
57883
58351
|
if (!options.quiet) {
|
|
57884
|
-
if (out.stdout.trim())
|
|
57885
|
-
if (out.stderr.trim())
|
|
57886
|
-
|
|
58352
|
+
if (out.stdout.trim()) log78.add(out.stdout.trimEnd());
|
|
58353
|
+
if (out.stderr.trim()) log78.add(out.stderr.trimEnd());
|
|
58354
|
+
log78.flush();
|
|
57887
58355
|
}
|
|
57888
58356
|
return out.stdout;
|
|
57889
58357
|
} catch (err) {
|
|
57890
58358
|
if (!options.quiet) {
|
|
57891
|
-
|
|
57892
|
-
|
|
58359
|
+
log78.add(err.message);
|
|
58360
|
+
log78.flush();
|
|
57893
58361
|
}
|
|
57894
58362
|
throw new Error(`${tool} ${args[0] ?? ""} failed: ${firstLine(err.message)}`);
|
|
57895
58363
|
}
|
|
57896
58364
|
}
|
|
57897
58365
|
/** Writes the default branch's files, with no `.git`, to `<tmp>/src`. */
|
|
57898
|
-
async exportCheckout(plan, tmp,
|
|
58366
|
+
async exportCheckout(plan, tmp, log78) {
|
|
57899
58367
|
const src = posix4.join(tmp, "src");
|
|
57900
58368
|
await plan.host.fs.mkdir(src);
|
|
57901
58369
|
const index2 = posix4.join(tmp, "index");
|
|
57902
|
-
await this.exec(plan.host, "git", ["read-tree", plan.ref],
|
|
58370
|
+
await this.exec(plan.host, "git", ["read-tree", plan.ref], log78, {
|
|
57903
58371
|
cwd: plan.repoPath,
|
|
57904
58372
|
env: { GIT_INDEX_FILE: index2 }
|
|
57905
58373
|
});
|
|
57906
|
-
await this.exec(plan.host, "git", ["checkout-index", "--all", `--prefix=${src}/`],
|
|
58374
|
+
await this.exec(plan.host, "git", ["checkout-index", "--all", `--prefix=${src}/`], log78, {
|
|
57907
58375
|
cwd: plan.repoPath,
|
|
57908
58376
|
env: { GIT_INDEX_FILE: index2 }
|
|
57909
58377
|
});
|
|
57910
58378
|
}
|
|
57911
58379
|
/** Layer 2: returns the tag of an image holding the repo's toolchain. */
|
|
57912
|
-
async buildToolchain(plan, tmp,
|
|
58380
|
+
async buildToolchain(plan, tmp, log78) {
|
|
57913
58381
|
const build = plan.environment.build;
|
|
57914
58382
|
const src = posix4.join(tmp, "src");
|
|
57915
58383
|
const tag = toolchainTag(plan.repo, plan.key);
|
|
@@ -57918,7 +58386,7 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57918
58386
|
plan.host,
|
|
57919
58387
|
"docker",
|
|
57920
58388
|
dockerBuildArgs({ checkout: src, dockerfile: build.dockerfile, tag }),
|
|
57921
|
-
|
|
58389
|
+
log78
|
|
57922
58390
|
);
|
|
57923
58391
|
return tag;
|
|
57924
58392
|
}
|
|
@@ -57928,7 +58396,7 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57928
58396
|
plan.host,
|
|
57929
58397
|
"devcontainer",
|
|
57930
58398
|
devcontainerBuildArgs({ checkout: src, config: build.devcontainer, tag }),
|
|
57931
|
-
|
|
58399
|
+
log78
|
|
57932
58400
|
);
|
|
57933
58401
|
} catch (err) {
|
|
57934
58402
|
throw new Error(
|
|
@@ -57938,11 +58406,11 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57938
58406
|
return tag;
|
|
57939
58407
|
}
|
|
57940
58408
|
const image = build?.image;
|
|
57941
|
-
await this.exec(plan.host, "docker", ["pull", image],
|
|
58409
|
+
await this.exec(plan.host, "docker", ["pull", image], log78);
|
|
57942
58410
|
return image;
|
|
57943
58411
|
}
|
|
57944
58412
|
/** Layer 1 on top of layer 2: copies the worker from the worker base image. */
|
|
57945
|
-
async addWorkerLayer(plan, tmp, toolchain,
|
|
58413
|
+
async addWorkerLayer(plan, tmp, toolchain, log78) {
|
|
57946
58414
|
const dir = posix4.join(tmp, "layer");
|
|
57947
58415
|
await plan.host.fs.mkdir(dir);
|
|
57948
58416
|
await plan.host.fs.writeFile(
|
|
@@ -57954,38 +58422,38 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57954
58422
|
});
|
|
57955
58423
|
await plan.host.fs.mkdir(posix4.join(dir, "root", "work"), { recursive: true });
|
|
57956
58424
|
const tag = layeredTag(plan.repo, plan.key);
|
|
57957
|
-
await this.exec(plan.host, "docker", ["build", "--tag", tag, dir],
|
|
58425
|
+
await this.exec(plan.host, "docker", ["build", "--tag", tag, dir], log78);
|
|
57958
58426
|
return tag;
|
|
57959
58427
|
}
|
|
57960
58428
|
/** Runs `install` at the default branch inside the image and commits the result as the final tag. */
|
|
57961
|
-
async install(plan, tmp, layered, container,
|
|
58429
|
+
async install(plan, tmp, layered, container, log78) {
|
|
57962
58430
|
const install = plan.environment.install;
|
|
57963
58431
|
if (install === void 0) {
|
|
57964
|
-
await this.exec(plan.host, "docker", ["tag", layered, plan.tag],
|
|
58432
|
+
await this.exec(plan.host, "docker", ["tag", layered, plan.tag], log78);
|
|
57965
58433
|
return;
|
|
57966
58434
|
}
|
|
57967
58435
|
await this.exec(
|
|
57968
58436
|
plan.host,
|
|
57969
58437
|
"docker",
|
|
57970
58438
|
installCreateArgs({ name: container, image: layered, install }),
|
|
57971
|
-
|
|
58439
|
+
log78
|
|
57972
58440
|
);
|
|
57973
58441
|
await this.exec(
|
|
57974
58442
|
plan.host,
|
|
57975
58443
|
"docker",
|
|
57976
58444
|
["cp", `${posix4.join(tmp, "src")}/.`, `${container}:${IMAGE_WORKDIR}`],
|
|
57977
|
-
|
|
58445
|
+
log78
|
|
57978
58446
|
);
|
|
57979
|
-
await this.exec(plan.host, "docker", ["start", "--attach", container],
|
|
58447
|
+
await this.exec(plan.host, "docker", ["start", "--attach", container], log78);
|
|
57980
58448
|
await this.exec(
|
|
57981
58449
|
plan.host,
|
|
57982
58450
|
"docker",
|
|
57983
58451
|
commitArgs({ container, tag: plan.tag, key: plan.key, commit: plan.commit }),
|
|
57984
|
-
|
|
58452
|
+
log78
|
|
57985
58453
|
);
|
|
57986
58454
|
}
|
|
57987
|
-
async push(plan,
|
|
57988
|
-
await this.exec(plan.host, "docker", ["push", plan.tag],
|
|
58455
|
+
async push(plan, log78) {
|
|
58456
|
+
await this.exec(plan.host, "docker", ["push", plan.tag], log78);
|
|
57989
58457
|
}
|
|
57990
58458
|
// ---- auto trigger ----------------------------------------------------------
|
|
57991
58459
|
/**
|
|
@@ -58204,8 +58672,8 @@ var init_git_credential_service = __esm({
|
|
|
58204
58672
|
// src/server/services/runner-service.ts
|
|
58205
58673
|
import { spawn as spawn6 } from "node:child_process";
|
|
58206
58674
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
58207
|
-
import { appendFileSync, mkdirSync as
|
|
58208
|
-
import { dirname as
|
|
58675
|
+
import { appendFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync10, rmSync as rmSync5, statSync as statSync13 } from "node:fs";
|
|
58676
|
+
import { dirname as dirname12, join as join39 } from "node:path";
|
|
58209
58677
|
function snapshotSweepMs() {
|
|
58210
58678
|
const raw = Number(process.env.BAND_SNAPSHOT_SWEEP_MS);
|
|
58211
58679
|
return Number.isFinite(raw) && raw > 0 ? raw : SNAPSHOT_SWEEP_MS;
|
|
@@ -58294,9 +58762,9 @@ var init_runner_service = __esm({
|
|
|
58294
58762
|
"SHELL"
|
|
58295
58763
|
];
|
|
58296
58764
|
RunLog = class {
|
|
58297
|
-
constructor(
|
|
58298
|
-
this.file =
|
|
58299
|
-
|
|
58765
|
+
constructor(file3) {
|
|
58766
|
+
this.file = file3;
|
|
58767
|
+
mkdirSync8(dirname12(file3), { recursive: true, mode: 448 });
|
|
58300
58768
|
}
|
|
58301
58769
|
lines = [];
|
|
58302
58770
|
bytes = 0;
|
|
@@ -58721,7 +59189,7 @@ ${tail}` : head;
|
|
|
58721
59189
|
() => false
|
|
58722
59190
|
);
|
|
58723
59191
|
if (exists3) return;
|
|
58724
|
-
await host.git.exec(["clone", "--quiet", "--", url2, dest],
|
|
59192
|
+
await host.git.exec(["clone", "--quiet", "--", url2, dest], dirname12(dest));
|
|
58725
59193
|
}
|
|
58726
59194
|
/** Records the machine of an attempt. An older machine of the same worker id is gone by now, so it is retired. */
|
|
58727
59195
|
recordMachine(runner, row, workerId) {
|
|
@@ -58796,7 +59264,7 @@ ${tail}` : head;
|
|
|
58796
59264
|
} else if (!runner.destroy) {
|
|
58797
59265
|
outcome = { state: "lost", error: `runner "${runner.id}" has no destroy hook` };
|
|
58798
59266
|
} else {
|
|
58799
|
-
let
|
|
59267
|
+
let failure2 = null;
|
|
58800
59268
|
try {
|
|
58801
59269
|
const code = await this.runDestroy(
|
|
58802
59270
|
runner,
|
|
@@ -58805,17 +59273,17 @@ ${tail}` : head;
|
|
|
58805
59273
|
machine.handle,
|
|
58806
59274
|
runLog
|
|
58807
59275
|
);
|
|
58808
|
-
if (code !== 0)
|
|
59276
|
+
if (code !== 0) failure2 = `destroy exited with code ${code}`;
|
|
58809
59277
|
} catch (err) {
|
|
58810
|
-
|
|
59278
|
+
failure2 = `destroy failed: ${err instanceof Error ? err.message : err}`;
|
|
58811
59279
|
}
|
|
58812
|
-
if (
|
|
59280
|
+
if (failure2 === null) {
|
|
58813
59281
|
outcome = { state: "destroyed", destroyedAt: Date.now(), error: reason };
|
|
58814
59282
|
} else {
|
|
58815
59283
|
const attempts = machine.destroyAttempts + 1;
|
|
58816
|
-
outcome = attempts >= DESTROY_ATTEMPTS ? { state: "lost", destroyAttempts: attempts, error:
|
|
59284
|
+
outcome = attempts >= DESTROY_ATTEMPTS ? { state: "lost", destroyAttempts: attempts, error: failure2 } : {
|
|
58817
59285
|
destroyAttempts: attempts,
|
|
58818
|
-
error: `${
|
|
59286
|
+
error: `${failure2} (try ${attempts} of ${DESTROY_ATTEMPTS})`
|
|
58819
59287
|
};
|
|
58820
59288
|
}
|
|
58821
59289
|
}
|
|
@@ -58829,12 +59297,12 @@ ${tail}` : head;
|
|
|
58829
59297
|
/** Runs `destroy` for a machine the hub has no record of, named only by the handle `status` printed. */
|
|
58830
59298
|
async destroyHandle(runner, handle, reason) {
|
|
58831
59299
|
if (!runner.destroy) return false;
|
|
58832
|
-
const
|
|
59300
|
+
const file3 = join39(bandHome(), "runners", "logs", "reaper.log");
|
|
58833
59301
|
try {
|
|
58834
|
-
if (
|
|
59302
|
+
if (statSync13(file3).size > LOG_FILE_LIMIT) rmSync5(file3, { force: true });
|
|
58835
59303
|
} catch {
|
|
58836
59304
|
}
|
|
58837
|
-
const runLog = new RunLog(
|
|
59305
|
+
const runLog = new RunLog(file3);
|
|
58838
59306
|
runLog.write("hub", `destroying machine ${handle} of runner ${runner.id}: ${reason}`);
|
|
58839
59307
|
try {
|
|
58840
59308
|
const code = await this.runDestroy(runner, null, null, handle, runLog);
|
|
@@ -59078,7 +59546,7 @@ ${tail}` : head;
|
|
|
59078
59546
|
runHook(opts) {
|
|
59079
59547
|
const { runner, name: name4, path: path2, env, timeoutMs, runLog, watch, onStdout, onStderr } = opts;
|
|
59080
59548
|
const cwd = join39(bandHome(), "runners", runner.id);
|
|
59081
|
-
|
|
59549
|
+
mkdirSync8(cwd, { recursive: true, mode: 448 });
|
|
59082
59550
|
return new Promise((resolve15, reject) => {
|
|
59083
59551
|
let child;
|
|
59084
59552
|
try {
|
|
@@ -59136,7 +59604,7 @@ ${tail}` : head;
|
|
|
59136
59604
|
});
|
|
59137
59605
|
|
|
59138
59606
|
// src/server/services/ephemeral-lifecycle-service.ts
|
|
59139
|
-
import { rmSync as
|
|
59607
|
+
import { rmSync as rmSync6 } from "node:fs";
|
|
59140
59608
|
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
|
|
59141
59609
|
import { join as join40, posix as posix5 } from "node:path";
|
|
59142
59610
|
function idleTimeoutMs() {
|
|
@@ -59146,7 +59614,7 @@ function idleTimeoutMs() {
|
|
|
59146
59614
|
function sleepDir(worktreeId4) {
|
|
59147
59615
|
return join40(bandHome(), "sleep", worktreeId4);
|
|
59148
59616
|
}
|
|
59149
|
-
var log23,
|
|
59617
|
+
var log23, LOCAL_HOST_ID6, WAKE_POLL_MS, WAKE_GRACE_MS, EXIT_GRACE_MS, SleepError, errorText2, EphemeralLifecycleService, ephemeralLifecycleService;
|
|
59150
59618
|
var init_ephemeral_lifecycle_service = __esm({
|
|
59151
59619
|
"src/server/services/ephemeral-lifecycle-service.ts"() {
|
|
59152
59620
|
"use strict";
|
|
@@ -59168,7 +59636,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59168
59636
|
init_state();
|
|
59169
59637
|
init_task_service();
|
|
59170
59638
|
log23 = createLogger("ephemeral-lifecycle");
|
|
59171
|
-
|
|
59639
|
+
LOCAL_HOST_ID6 = "local";
|
|
59172
59640
|
WAKE_POLL_MS = 500;
|
|
59173
59641
|
WAKE_GRACE_MS = 5 * 6e4;
|
|
59174
59642
|
EXIT_GRACE_MS = 6e4;
|
|
@@ -59499,7 +59967,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59499
59967
|
}
|
|
59500
59968
|
forget(row) {
|
|
59501
59969
|
this.sleeps.delete(row.worktreeId);
|
|
59502
|
-
|
|
59970
|
+
rmSync6(sleepDir(row.worktreeId), { recursive: true, force: true });
|
|
59503
59971
|
}
|
|
59504
59972
|
// ---- wake ---------------------------------------------------------------
|
|
59505
59973
|
/**
|
|
@@ -59511,7 +59979,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59511
59979
|
async ensureAwake(worktreeId4) {
|
|
59512
59980
|
for (let i = 0; i < 3; i++) {
|
|
59513
59981
|
const hostId2 = this.worktrees.findHostId(worktreeId4);
|
|
59514
|
-
if (!hostId2 || hostId2 ===
|
|
59982
|
+
if (!hostId2 || hostId2 === LOCAL_HOST_ID6) return;
|
|
59515
59983
|
const draining = this.draining.get(hostId2);
|
|
59516
59984
|
if (draining) {
|
|
59517
59985
|
await draining;
|
|
@@ -59680,10 +60148,10 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59680
60148
|
}
|
|
59681
60149
|
const stage = posix5.join(wipDir, "sessions", row.worktreeId);
|
|
59682
60150
|
await host.fs.rm(stage, { recursive: true, force: true });
|
|
59683
|
-
for (const
|
|
59684
|
-
const target = posix5.join(stage,
|
|
60151
|
+
for (const file3 of files) {
|
|
60152
|
+
const target = posix5.join(stage, file3.root, file3.rel);
|
|
59685
60153
|
await host.fs.mkdir(posix5.dirname(target), { recursive: true });
|
|
59686
|
-
await host.fs.writeFile(target, Buffer.from(
|
|
60154
|
+
await host.fs.writeFile(target, Buffer.from(file3.data, "base64"));
|
|
59687
60155
|
}
|
|
59688
60156
|
await rpc.call(METHOD_LIFECYCLE_IMPORT_SESSIONS, { dir: stage });
|
|
59689
60157
|
}
|
|
@@ -60843,6 +61311,8 @@ var init_client_state_keys = __esm({
|
|
|
60843
61311
|
exact("band.repos-list.collapsed-pinned", [jsonPart("all")]),
|
|
60844
61312
|
exact("band.repos-list.label-filter", [rawPart("all")]),
|
|
60845
61313
|
exact("band.repos-list.label-last-worktree", [jsonPart("all")]),
|
|
61314
|
+
// The hooks version whose install banner the user dismissed.
|
|
61315
|
+
exact("band:agent-hooks-banner-dismissed", [rawPart("all")]),
|
|
60846
61316
|
perWorktree(CENTER_TABS_PREFIX, [jsonPart("all")]),
|
|
60847
61317
|
perWorktreeLeaf("band:term-split:", [jsonPart("all")]),
|
|
60848
61318
|
perWorktree("band:diff-compare-branch:", [rawPart("all")]),
|
|
@@ -61069,11 +61539,11 @@ var init_cronjobs = __esm({
|
|
|
61069
61539
|
* incomplete state (some jobs missing) — the legacy `cronjob-store.ts`
|
|
61070
61540
|
* had the same gap; the migration is the appropriate moment to fix it.
|
|
61071
61541
|
*/
|
|
61072
|
-
saveFile(key2,
|
|
61542
|
+
saveFile(key2, file3) {
|
|
61073
61543
|
const db = getDb();
|
|
61074
61544
|
db.transaction((tx) => {
|
|
61075
61545
|
tx.delete(cronjobs).where(eq(cronjobs.fileKey, key2)).run();
|
|
61076
|
-
for (const job of
|
|
61546
|
+
for (const job of file3.jobs) {
|
|
61077
61547
|
tx.insert(cronjobs).values({
|
|
61078
61548
|
id: job.id,
|
|
61079
61549
|
fileKey: key2,
|
|
@@ -61265,14 +61735,14 @@ var init_cronjob_service = __esm({
|
|
|
61265
61735
|
list(filter) {
|
|
61266
61736
|
if (filter?.repo) {
|
|
61267
61737
|
const { repo } = filter;
|
|
61268
|
-
const
|
|
61269
|
-
return { jobs:
|
|
61738
|
+
const file3 = this.queries.loadFile(repo);
|
|
61739
|
+
return { jobs: file3.jobs.map((j) => ({ ...j, fileKey: repo })) };
|
|
61270
61740
|
}
|
|
61271
61741
|
if (filter?.worktreeId) {
|
|
61272
61742
|
const { worktreeId: worktreeId4 } = filter;
|
|
61273
|
-
const
|
|
61743
|
+
const file3 = this.queries.loadFile(worktreeId4);
|
|
61274
61744
|
return {
|
|
61275
|
-
jobs:
|
|
61745
|
+
jobs: file3.jobs.map((j) => ({ ...j, fileKey: worktreeId4 }))
|
|
61276
61746
|
};
|
|
61277
61747
|
}
|
|
61278
61748
|
return { jobs: this.queries.listAll() };
|
|
@@ -61300,7 +61770,7 @@ var init_cronjob_service = __esm({
|
|
|
61300
61770
|
if (input.scope === "worktree" && !input.worktreeId) {
|
|
61301
61771
|
throw new CronjobWorktreeMissingError();
|
|
61302
61772
|
}
|
|
61303
|
-
const
|
|
61773
|
+
const file3 = this.queries.loadFile(input.key);
|
|
61304
61774
|
const job = {
|
|
61305
61775
|
id: generateCronjobId(),
|
|
61306
61776
|
name: input.name,
|
|
@@ -61314,8 +61784,8 @@ var init_cronjob_service = __esm({
|
|
|
61314
61784
|
enabled: input.enabled,
|
|
61315
61785
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
61316
61786
|
};
|
|
61317
|
-
|
|
61318
|
-
this.queries.saveFile(input.key,
|
|
61787
|
+
file3.jobs.push(job);
|
|
61788
|
+
this.queries.saveFile(input.key, file3);
|
|
61319
61789
|
this.reloadSchedules();
|
|
61320
61790
|
return { job };
|
|
61321
61791
|
}
|
|
@@ -61330,14 +61800,14 @@ var init_cronjob_service = __esm({
|
|
|
61330
61800
|
if (input.cronExpression !== void 0) {
|
|
61331
61801
|
this.assertValidCron(input.cronExpression);
|
|
61332
61802
|
}
|
|
61333
|
-
const
|
|
61334
|
-
const job =
|
|
61803
|
+
const file3 = this.queries.loadFile(input.key);
|
|
61804
|
+
const job = file3.jobs.find((j) => j.id === input.id);
|
|
61335
61805
|
if (!job) throw new CronjobNotFoundError();
|
|
61336
61806
|
if (input.name !== void 0) job.name = input.name;
|
|
61337
61807
|
if (input.prompt !== void 0) job.prompt = input.prompt;
|
|
61338
61808
|
if (input.cronExpression !== void 0) job.cronExpression = input.cronExpression;
|
|
61339
61809
|
if (input.enabled !== void 0) job.enabled = input.enabled;
|
|
61340
|
-
this.queries.saveFile(input.key,
|
|
61810
|
+
this.queries.saveFile(input.key, file3);
|
|
61341
61811
|
this.reloadSchedules();
|
|
61342
61812
|
return { job };
|
|
61343
61813
|
}
|
|
@@ -61347,11 +61817,11 @@ var init_cronjob_service = __esm({
|
|
|
61347
61817
|
* Throws `CronjobNotFoundError` if the job is already gone.
|
|
61348
61818
|
*/
|
|
61349
61819
|
delete(key2, id) {
|
|
61350
|
-
const
|
|
61351
|
-
const index2 =
|
|
61820
|
+
const file3 = this.queries.loadFile(key2);
|
|
61821
|
+
const index2 = file3.jobs.findIndex((j) => j.id === id);
|
|
61352
61822
|
if (index2 === -1) throw new CronjobNotFoundError();
|
|
61353
|
-
const [removed] =
|
|
61354
|
-
this.queries.saveFile(key2,
|
|
61823
|
+
const [removed] = file3.jobs.splice(index2, 1);
|
|
61824
|
+
this.queries.saveFile(key2, file3);
|
|
61355
61825
|
if (removed?.lastTerminalId) {
|
|
61356
61826
|
const terminalId = removed.lastTerminalId;
|
|
61357
61827
|
void terminalService.kill(terminalId).catch((err) => {
|
|
@@ -61469,8 +61939,8 @@ var init_cronjob_service = __esm({
|
|
|
61469
61939
|
*/
|
|
61470
61940
|
stopJobsForKey(key2) {
|
|
61471
61941
|
const state2 = schedulerState();
|
|
61472
|
-
const
|
|
61473
|
-
for (const job of
|
|
61942
|
+
const file3 = this.queries.loadFile(key2);
|
|
61943
|
+
for (const job of file3.jobs) {
|
|
61474
61944
|
const cron = state2.jobs.get(job.id);
|
|
61475
61945
|
if (cron) {
|
|
61476
61946
|
cron.stop();
|
|
@@ -61621,8 +62091,8 @@ exit`;
|
|
|
61621
62091
|
// Internals
|
|
61622
62092
|
// -------------------------------------------------------------------------
|
|
61623
62093
|
findJob(key2, id) {
|
|
61624
|
-
const
|
|
61625
|
-
return
|
|
62094
|
+
const file3 = this.queries.loadFile(key2);
|
|
62095
|
+
return file3.jobs.find((j) => j.id === id);
|
|
61626
62096
|
}
|
|
61627
62097
|
assertValidCron(expression) {
|
|
61628
62098
|
try {
|
|
@@ -61744,28 +62214,28 @@ exit`;
|
|
|
61744
62214
|
// src/server/services/local-host-policy.ts
|
|
61745
62215
|
function resolveWorktreeHostId(requested) {
|
|
61746
62216
|
if (!isLocalHostEnabled()) {
|
|
61747
|
-
if (requested ===
|
|
62217
|
+
if (requested === LOCAL_HOST_ID7) throw new LocalHostDisabledError();
|
|
61748
62218
|
if (requested) return requested;
|
|
61749
62219
|
const configured = process.env.BAND_DEFAULT_HOST?.trim();
|
|
61750
62220
|
if (configured) {
|
|
61751
|
-
if (configured ===
|
|
62221
|
+
if (configured === LOCAL_HOST_ID7) throw new LocalHostDisabledError();
|
|
61752
62222
|
return configured;
|
|
61753
62223
|
}
|
|
61754
|
-
const online = tokenService.listHosts().filter((h) => h.id !==
|
|
62224
|
+
const online = tokenService.listHosts().filter((h) => h.id !== LOCAL_HOST_ID7 && h.status === "online");
|
|
61755
62225
|
if (online.length === 1) return online[0].id;
|
|
61756
62226
|
throw new NoDefaultHostError(
|
|
61757
62227
|
online.length === 0 ? "No worker is online." : "More than one worker is online."
|
|
61758
62228
|
);
|
|
61759
62229
|
}
|
|
61760
|
-
return requested ??
|
|
62230
|
+
return requested ?? LOCAL_HOST_ID7;
|
|
61761
62231
|
}
|
|
61762
|
-
var
|
|
62232
|
+
var LOCAL_HOST_ID7, LocalHostDisabledError, NoDefaultHostError;
|
|
61763
62233
|
var init_local_host_policy = __esm({
|
|
61764
62234
|
"src/server/services/local-host-policy.ts"() {
|
|
61765
62235
|
"use strict";
|
|
61766
62236
|
init_local_host_enabled();
|
|
61767
62237
|
init_token_service();
|
|
61768
|
-
|
|
62238
|
+
LOCAL_HOST_ID7 = "local";
|
|
61769
62239
|
LocalHostDisabledError = class extends Error {
|
|
61770
62240
|
constructor() {
|
|
61771
62241
|
super(
|
|
@@ -61964,7 +62434,7 @@ async function reconcileOneRepo(repo) {
|
|
|
61964
62434
|
repo.worktrees.filter((wt) => !isRemote(wt)).map((wt) => [wt.path, wt])
|
|
61965
62435
|
);
|
|
61966
62436
|
diskWorktrees = gitWorktrees.filter(
|
|
61967
|
-
(wt) => !wt.isBare && !remotePaths.has(wt.path) && (!removingWorktrees.has(wt.path) || existingByPath.has(wt.path) && !removedWorktrees.has(wt.path))
|
|
62437
|
+
(wt) => !wt.isBare && (!remotePaths.has(wt.path) || existingByPath.has(wt.path)) && (!removingWorktrees.has(wt.path) || existingByPath.has(wt.path) && !removedWorktrees.has(wt.path))
|
|
61968
62438
|
).map((wt) => {
|
|
61969
62439
|
const existing = existingByPath.get(wt.path);
|
|
61970
62440
|
return {
|
|
@@ -62225,6 +62695,7 @@ var init_worktree_script_service = __esm({
|
|
|
62225
62695
|
// src/server/services/worktree-service.ts
|
|
62226
62696
|
var worktree_service_exports = {};
|
|
62227
62697
|
__export(worktree_service_exports, {
|
|
62698
|
+
InvalidOriginError: () => InvalidOriginError,
|
|
62228
62699
|
PlainRepoError: () => PlainRepoError,
|
|
62229
62700
|
RepoNotFoundError: () => RepoNotFoundError,
|
|
62230
62701
|
WorktreeNotFoundError: () => WorktreeNotFoundError,
|
|
@@ -62236,11 +62707,12 @@ __export(worktree_service_exports, {
|
|
|
62236
62707
|
worktreeRemoveInput: () => worktreeRemoveInput,
|
|
62237
62708
|
worktreeRunScriptInput: () => worktreeRunScriptInput,
|
|
62238
62709
|
worktreeService: () => worktreeService,
|
|
62710
|
+
worktreeSetOriginInput: () => worktreeSetOriginInput,
|
|
62239
62711
|
worktreeSetPinnedInput: () => worktreeSetPinnedInput,
|
|
62240
62712
|
worktreeVia: () => worktreeVia
|
|
62241
62713
|
});
|
|
62242
62714
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
62243
|
-
import { unlinkSync as
|
|
62715
|
+
import { unlinkSync as unlinkSync5 } from "node:fs";
|
|
62244
62716
|
import { join as join41, posix as posix6 } from "node:path";
|
|
62245
62717
|
function findWorktreeRow(worktrees2, name4, hostId2) {
|
|
62246
62718
|
const named = worktrees2.filter((wt) => wt.name === name4);
|
|
@@ -62282,7 +62754,7 @@ function expandHome(given, home, hostId2) {
|
|
|
62282
62754
|
}
|
|
62283
62755
|
return path2;
|
|
62284
62756
|
}
|
|
62285
|
-
var TEARDOWN_TIMEOUT_MS, log33, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, PlainRepoError, WorktreeService, worktreeService;
|
|
62757
|
+
var TEARDOWN_TIMEOUT_MS, log33, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeSetOriginInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, InvalidOriginError, PlainRepoError, WorktreeService, worktreeService;
|
|
62286
62758
|
var init_worktree_service = __esm({
|
|
62287
62759
|
"src/server/services/worktree-service.ts"() {
|
|
62288
62760
|
"use strict";
|
|
@@ -62383,6 +62855,14 @@ var init_worktree_service = __esm({
|
|
|
62383
62855
|
hostId: external_exports2.string().optional(),
|
|
62384
62856
|
pinned: external_exports2.boolean()
|
|
62385
62857
|
});
|
|
62858
|
+
worktreeSetOriginInput = external_exports2.object({
|
|
62859
|
+
repo: external_exports2.string(),
|
|
62860
|
+
// Worktree identity (immutable `name`), not the live git branch.
|
|
62861
|
+
name: external_exports2.string(),
|
|
62862
|
+
hostId: external_exports2.string().optional(),
|
|
62863
|
+
// The parent worktree's id, or null to remove the parent.
|
|
62864
|
+
origin: external_exports2.string().min(1).nullable()
|
|
62865
|
+
});
|
|
62386
62866
|
worktreeGitInput = external_exports2.object({
|
|
62387
62867
|
repo: external_exports2.string(),
|
|
62388
62868
|
// Worktree identity (immutable `name`), not the live git branch.
|
|
@@ -62399,6 +62879,12 @@ var init_worktree_service = __esm({
|
|
|
62399
62879
|
this.name = "RepoNotFoundError";
|
|
62400
62880
|
}
|
|
62401
62881
|
};
|
|
62882
|
+
InvalidOriginError = class extends Error {
|
|
62883
|
+
constructor(message) {
|
|
62884
|
+
super(message);
|
|
62885
|
+
this.name = "InvalidOriginError";
|
|
62886
|
+
}
|
|
62887
|
+
};
|
|
62402
62888
|
PlainRepoError = class extends Error {
|
|
62403
62889
|
constructor(message) {
|
|
62404
62890
|
super(message);
|
|
@@ -62913,7 +63399,7 @@ var init_worktree_service = __esm({
|
|
|
62913
63399
|
saveState(state2);
|
|
62914
63400
|
removal.commit();
|
|
62915
63401
|
try {
|
|
62916
|
-
|
|
63402
|
+
unlinkSync5(join41(bandHome(), "workspace-prompts", `${worktreeId4}.json`));
|
|
62917
63403
|
} catch {
|
|
62918
63404
|
}
|
|
62919
63405
|
deleteWorktreeStatus(worktreeId4);
|
|
@@ -63075,6 +63561,52 @@ var init_worktree_service = __esm({
|
|
|
63075
63561
|
saveState(state2);
|
|
63076
63562
|
return { ok: true };
|
|
63077
63563
|
}
|
|
63564
|
+
/**
|
|
63565
|
+
* Sets a worktree's parent (its origin) by hand, or removes it with `null`. A manual parent
|
|
63566
|
+
* carries no chat or terminal. Rejects the worktree itself and any descendant of it, since
|
|
63567
|
+
* either would make a cycle.
|
|
63568
|
+
*/
|
|
63569
|
+
setOrigin(input) {
|
|
63570
|
+
const state2 = loadState();
|
|
63571
|
+
const repo = state2.repos.find((p2) => p2.name === input.repo);
|
|
63572
|
+
if (!repo) throw new RepoNotFoundError(input.repo);
|
|
63573
|
+
const target = findWorktreeRow(repo.worktrees, input.name, input.hostId);
|
|
63574
|
+
if (!target) throw new WorktreeNotFoundError(input.name);
|
|
63575
|
+
const targetHostId = target.hostId ?? "local";
|
|
63576
|
+
const worktreeId4 = toWorktreeId(repo.name, target.name, target.hostId);
|
|
63577
|
+
if (input.origin !== null) {
|
|
63578
|
+
const origins = /* @__PURE__ */ new Map();
|
|
63579
|
+
for (const r of state2.repos) {
|
|
63580
|
+
for (const wt of r.worktrees) {
|
|
63581
|
+
origins.set(toWorktreeId(r.name, wt.name, wt.hostId), wt.origin?.worktreeId);
|
|
63582
|
+
}
|
|
63583
|
+
}
|
|
63584
|
+
if (!origins.has(input.origin)) {
|
|
63585
|
+
throw new InvalidOriginError(`Unknown origin worktree: ${input.origin}`);
|
|
63586
|
+
}
|
|
63587
|
+
if (input.origin === worktreeId4) {
|
|
63588
|
+
throw new InvalidOriginError("A worktree cannot be its own parent.");
|
|
63589
|
+
}
|
|
63590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
63591
|
+
for (let id = input.origin; id && !seen.has(id); ) {
|
|
63592
|
+
if (id === worktreeId4) {
|
|
63593
|
+
throw new InvalidOriginError(
|
|
63594
|
+
"That worktree is a descendant of this one, so it cannot be its parent."
|
|
63595
|
+
);
|
|
63596
|
+
}
|
|
63597
|
+
seen.add(id);
|
|
63598
|
+
id = origins.get(id);
|
|
63599
|
+
}
|
|
63600
|
+
}
|
|
63601
|
+
this.repoQueries.setOrigin(
|
|
63602
|
+
repo.name,
|
|
63603
|
+
target.name,
|
|
63604
|
+
input.origin === null ? null : { worktreeId: input.origin },
|
|
63605
|
+
targetHostId
|
|
63606
|
+
);
|
|
63607
|
+
emit({ kind: "worktree-origin-changed", worktreeId: worktreeId4 });
|
|
63608
|
+
return { ok: true };
|
|
63609
|
+
}
|
|
63078
63610
|
pruning = /* @__PURE__ */ new Set();
|
|
63079
63611
|
/**
|
|
63080
63612
|
* Removes the records of worktrees whose folder is gone from their host (`missing` after a scan),
|
|
@@ -63375,8 +63907,8 @@ var init_worktree_service = __esm({
|
|
|
63375
63907
|
throw new Error(`Script "${input.scriptType}" not found`);
|
|
63376
63908
|
}
|
|
63377
63909
|
try {
|
|
63378
|
-
const { file:
|
|
63379
|
-
await host.exec(
|
|
63910
|
+
const { file: file3, args } = scriptInvocation(scriptPath);
|
|
63911
|
+
await host.exec(file3, args, { cwd: input.path });
|
|
63380
63912
|
} catch (err) {
|
|
63381
63913
|
throw new Error(err instanceof Error ? err.message : String(err));
|
|
63382
63914
|
}
|
|
@@ -63388,15 +63920,6 @@ var init_worktree_service = __esm({
|
|
|
63388
63920
|
});
|
|
63389
63921
|
|
|
63390
63922
|
// src/server/services/task-service.ts
|
|
63391
|
-
function observePending() {
|
|
63392
|
-
if (observingPending) return;
|
|
63393
|
-
observingPending = true;
|
|
63394
|
-
agentSessionService.observePending((chatId, worktreeId4) => {
|
|
63395
|
-
if (tasks2.get(chatId)?.status !== "running") return;
|
|
63396
|
-
const waiting = agentSessionService.hasPendingRequest(chatId);
|
|
63397
|
-
setScopeStatus(worktreeId4, chatId, waiting ? "needs_attention" : "working");
|
|
63398
|
-
});
|
|
63399
|
-
}
|
|
63400
63923
|
function persistTask(task) {
|
|
63401
63924
|
const worktree = worktreeService.resolve(task.worktreeId);
|
|
63402
63925
|
try {
|
|
@@ -63429,19 +63952,19 @@ function fileUri(path2) {
|
|
|
63429
63952
|
async function promptBlocks(task, firstTurn, sharedDir) {
|
|
63430
63953
|
const caps = agentSessionService.promptCapabilities(task.chatId);
|
|
63431
63954
|
const blocks = [{ type: "text", text: task.prompt }];
|
|
63432
|
-
for (const
|
|
63433
|
-
const name4 =
|
|
63434
|
-
if (caps.image &&
|
|
63955
|
+
for (const file3 of task.attachments) {
|
|
63956
|
+
const name4 = file3.filename ?? file3.path.split("/").pop() ?? file3.path;
|
|
63957
|
+
if (caps.image && file3.mediaType.startsWith("image/")) {
|
|
63435
63958
|
try {
|
|
63436
63959
|
const host = hostRegistry.hostFor(task.worktreeId);
|
|
63437
|
-
const data = Buffer.from(await host.fs.readFile(
|
|
63438
|
-
blocks.push({ type: "image", mimeType:
|
|
63960
|
+
const data = Buffer.from(await host.fs.readFile(file3.path)).toString("base64");
|
|
63961
|
+
blocks.push({ type: "image", mimeType: file3.mediaType, data, uri: fileUri(file3.path) });
|
|
63439
63962
|
continue;
|
|
63440
63963
|
} catch (err) {
|
|
63441
|
-
log34.warn({ path:
|
|
63964
|
+
log34.warn({ path: file3.path, err }, "could not read image attachment; linking it instead");
|
|
63442
63965
|
}
|
|
63443
63966
|
}
|
|
63444
|
-
blocks.push({ type: "resource_link", uri: fileUri(
|
|
63967
|
+
blocks.push({ type: "resource_link", uri: fileUri(file3.path), name: name4, mimeType: file3.mediaType });
|
|
63445
63968
|
}
|
|
63446
63969
|
if (firstTurn) {
|
|
63447
63970
|
blocks.push({
|
|
@@ -63486,10 +64009,6 @@ function turnUsage(chatId, res, previousCost) {
|
|
|
63486
64009
|
});
|
|
63487
64010
|
return { ...usage, model, costUsd: (previousCost ?? 0) + cost };
|
|
63488
64011
|
}
|
|
63489
|
-
function setScopeStatus(scope3, chatId, status) {
|
|
63490
|
-
const updated = setWorktreeSourceStatus(scope3, chatStatusSource(chatId), { status });
|
|
63491
|
-
emit({ kind: "update", status: updated });
|
|
63492
|
-
}
|
|
63493
64012
|
function submitTask(options) {
|
|
63494
64013
|
const { worktreeId: worktreeId4, chatId, prompt, sessionId, mode, model, codingAgentId } = options;
|
|
63495
64014
|
if (worktreeService.resolve(worktreeId4) === null) {
|
|
@@ -63499,7 +64018,7 @@ function submitTask(options) {
|
|
|
63499
64018
|
if (existing?.status === "running") {
|
|
63500
64019
|
throw new TaskConflictError(chatId);
|
|
63501
64020
|
}
|
|
63502
|
-
|
|
64021
|
+
observeAgentStatus(hasRunningTask);
|
|
63503
64022
|
const task = {
|
|
63504
64023
|
id: generateTaskId(),
|
|
63505
64024
|
worktreeId: worktreeId4,
|
|
@@ -63543,7 +64062,7 @@ function submitOrQueueTask(options) {
|
|
|
63543
64062
|
async function runTask(task) {
|
|
63544
64063
|
const { chatId } = task;
|
|
63545
64064
|
chatService.updateStatus(chatId, "running");
|
|
63546
|
-
|
|
64065
|
+
beginBandTurn(task.worktreeId, chatId);
|
|
63547
64066
|
const chat = chatService.get(chatId);
|
|
63548
64067
|
if (task.codingAgentId && chat && task.codingAgentId !== chat.agent) {
|
|
63549
64068
|
agentSessionService.stop(chatId);
|
|
@@ -63643,7 +64162,7 @@ async function runTask(task) {
|
|
|
63643
64162
|
durationMs: Date.now() - task.startedAt,
|
|
63644
64163
|
usage: turnUsage(chatId, res, previousCost)
|
|
63645
64164
|
});
|
|
63646
|
-
finishTask(task, res.stopReason === "cancelled" ? "cancelled" : "completed");
|
|
64165
|
+
finishTask(task, res.stopReason === "cancelled" ? "cancelled" : "completed", res.stopReason);
|
|
63647
64166
|
} catch (err) {
|
|
63648
64167
|
const message = err instanceof Error ? err.message : String(err);
|
|
63649
64168
|
agentSessionService.record(chatId, {
|
|
@@ -63667,7 +64186,7 @@ async function applyTurnChoice(chatId, category, value) {
|
|
|
63667
64186
|
log34.warn({ chatId, category, value, err }, "could not apply the task's setting");
|
|
63668
64187
|
}
|
|
63669
64188
|
}
|
|
63670
|
-
function finishTask(task, outcome) {
|
|
64189
|
+
function finishTask(task, outcome, stopReason) {
|
|
63671
64190
|
if (task.status !== "running") return;
|
|
63672
64191
|
const status = outcome === "completed" ? "completed" : "failed";
|
|
63673
64192
|
task.status = status;
|
|
@@ -63678,7 +64197,8 @@ function finishTask(task, outcome) {
|
|
|
63678
64197
|
if (status === "completed" && drainQueue(task.worktreeId, task.chatId)) return;
|
|
63679
64198
|
chatService.updateStatus(task.chatId, status === "completed" ? "idle" : "error");
|
|
63680
64199
|
const stopped = outcome === "cancelled" || task.cancelRequested === true;
|
|
63681
|
-
|
|
64200
|
+
const failed = outcome === "failed" || stopReason !== void 0 && FAILED_STOP_REASONS.has(stopReason);
|
|
64201
|
+
endBandTurn(task.worktreeId, task.chatId, stopped ? "idle" : failed ? "failed" : "done");
|
|
63682
64202
|
}
|
|
63683
64203
|
function drainQueue(worktreeId4, chatId) {
|
|
63684
64204
|
const queued = peekQueuedMessage(chatId);
|
|
@@ -63721,7 +64241,7 @@ function cancelTask(taskId) {
|
|
|
63721
64241
|
const record3 = taskQueries.markFailed(taskId);
|
|
63722
64242
|
if (record3) {
|
|
63723
64243
|
if (record3.chatId) {
|
|
63724
|
-
|
|
64244
|
+
setChatStatus(record3.worktreeId, record3.chatId, "idle");
|
|
63725
64245
|
}
|
|
63726
64246
|
log34.info({ taskId, worktreeId: record3.worktreeId }, "orphaned task cancelled");
|
|
63727
64247
|
return { cancelled: true, worktreeId: record3.worktreeId };
|
|
@@ -63735,7 +64255,7 @@ function getTask(chatId) {
|
|
|
63735
64255
|
const task = tasks2.get(chatId);
|
|
63736
64256
|
return task ? toTaskInfo(task) : null;
|
|
63737
64257
|
}
|
|
63738
|
-
var log34, taskQueries, TASKS_KEY, g4, tasks2,
|
|
64258
|
+
var log34, taskQueries, TASKS_KEY, g4, tasks2, TaskConflictError, FAILED_STOP_REASONS, TaskService, taskService;
|
|
63739
64259
|
var init_task_service = __esm({
|
|
63740
64260
|
"src/server/services/task-service.ts"() {
|
|
63741
64261
|
"use strict";
|
|
@@ -63750,8 +64270,8 @@ var init_task_service = __esm({
|
|
|
63750
64270
|
init_turn_events();
|
|
63751
64271
|
init_agent_session_service();
|
|
63752
64272
|
init_chat_service();
|
|
64273
|
+
init_chat_status_service();
|
|
63753
64274
|
init_state();
|
|
63754
|
-
init_watcher_service();
|
|
63755
64275
|
init_worktree_service();
|
|
63756
64276
|
log34 = createLogger("task-service");
|
|
63757
64277
|
taskQueries = new TaskQueries();
|
|
@@ -63759,13 +64279,13 @@ var init_task_service = __esm({
|
|
|
63759
64279
|
g4 = globalThis;
|
|
63760
64280
|
if (!g4[TASKS_KEY]) g4[TASKS_KEY] = /* @__PURE__ */ new Map();
|
|
63761
64281
|
tasks2 = g4[TASKS_KEY];
|
|
63762
|
-
observingPending = false;
|
|
63763
64282
|
TaskConflictError = class extends Error {
|
|
63764
64283
|
constructor(chatId) {
|
|
63765
64284
|
super(`Task already running for chat ${chatId}`);
|
|
63766
64285
|
this.name = "TaskConflictError";
|
|
63767
64286
|
}
|
|
63768
64287
|
};
|
|
64288
|
+
FAILED_STOP_REASONS = /* @__PURE__ */ new Set(["refusal", "max_tokens", "max_turn_requests"]);
|
|
63769
64289
|
TaskService = class {
|
|
63770
64290
|
constructor(queries3 = taskQueries) {
|
|
63771
64291
|
this.queries = queries3;
|
|
@@ -63785,13 +64305,10 @@ var init_task_service = __esm({
|
|
|
63785
64305
|
getTask(chatId) {
|
|
63786
64306
|
return getTask(chatId);
|
|
63787
64307
|
}
|
|
63788
|
-
/**
|
|
63789
|
-
* still waits on the user
|
|
64308
|
+
/** Sets the worktree's finished chats (`done`, `failed`) back to `idle`. A
|
|
64309
|
+
* chat whose agent still waits on the user keeps its `question`. */
|
|
63790
64310
|
acknowledgeAttention(worktreeId4) {
|
|
63791
|
-
return acknowledgeWorktreeAttention(
|
|
63792
|
-
worktreeId4,
|
|
63793
|
-
agentSessionService.chatsWithPendingRequest(worktreeId4)
|
|
63794
|
-
);
|
|
64311
|
+
return acknowledgeWorktreeAttention(worktreeId4);
|
|
63795
64312
|
}
|
|
63796
64313
|
listTaskRecords(filters) {
|
|
63797
64314
|
return this.queries.list(filters);
|
|
@@ -65365,9 +65882,88 @@ function parseReview(data, branch, defaultBranch) {
|
|
|
65365
65882
|
updatedAt: pr.updatedAt,
|
|
65366
65883
|
reviewDecision: reviewDecision(pr.reviewDecision),
|
|
65367
65884
|
mergeState: mergeState(pr.mergeStateStatus),
|
|
65368
|
-
checks: report(head?.oid ?? null, checks)
|
|
65885
|
+
checks: report(head?.oid ?? null, checks),
|
|
65886
|
+
reviewers: parseReviewers(pr),
|
|
65887
|
+
discussion: parseDiscussion(pr)
|
|
65369
65888
|
};
|
|
65370
65889
|
}
|
|
65890
|
+
function parseReviewers(pr) {
|
|
65891
|
+
const byLogin = /* @__PURE__ */ new Map();
|
|
65892
|
+
const order = [...pr.reviews?.nodes ?? []].sort(
|
|
65893
|
+
(a, b2) => (a.submittedAt ?? "").localeCompare(b2.submittedAt ?? "")
|
|
65894
|
+
);
|
|
65895
|
+
for (const review of order) {
|
|
65896
|
+
const state2 = REVIEWER_STATES[review.state];
|
|
65897
|
+
if (!state2 || !review.author) continue;
|
|
65898
|
+
const existing = byLogin.get(review.author.login);
|
|
65899
|
+
if (existing && state2 === "commented" && existing.state !== "commented") continue;
|
|
65900
|
+
byLogin.set(review.author.login, {
|
|
65901
|
+
...actorOf(review.author),
|
|
65902
|
+
state: state2,
|
|
65903
|
+
submittedAt: review.submittedAt
|
|
65904
|
+
});
|
|
65905
|
+
}
|
|
65906
|
+
for (const request of pr.reviewRequests?.nodes ?? []) {
|
|
65907
|
+
const who = request.requestedReviewer;
|
|
65908
|
+
if (!who) continue;
|
|
65909
|
+
const actor = who.__typename === "Team" ? { login: who.slug, avatarUrl: null } : actorOf({ login: who.login, avatarUrl: who.avatarUrl });
|
|
65910
|
+
const key2 = who.__typename === "Team" ? `team:${actor.login}` : actor.login;
|
|
65911
|
+
const previous = byLogin.get(key2);
|
|
65912
|
+
byLogin.set(key2, {
|
|
65913
|
+
...actor,
|
|
65914
|
+
state: "pending",
|
|
65915
|
+
submittedAt: previous?.submittedAt ?? null,
|
|
65916
|
+
...request.asCodeOwner ? { required: true } : {}
|
|
65917
|
+
});
|
|
65918
|
+
}
|
|
65919
|
+
return [...byLogin.values()];
|
|
65920
|
+
}
|
|
65921
|
+
function fromCommentNode(node) {
|
|
65922
|
+
return {
|
|
65923
|
+
id: node.id,
|
|
65924
|
+
author: actorOf(node.author),
|
|
65925
|
+
body: node.body,
|
|
65926
|
+
createdAt: node.createdAt,
|
|
65927
|
+
url: webUrl(node.url)
|
|
65928
|
+
};
|
|
65929
|
+
}
|
|
65930
|
+
function parseDiscussion(pr) {
|
|
65931
|
+
const items = [];
|
|
65932
|
+
for (const node of pr.comments?.nodes ?? []) {
|
|
65933
|
+
items.push({ ...fromCommentNode(node), kind: "comment" });
|
|
65934
|
+
}
|
|
65935
|
+
for (const review of pr.reviews?.nodes ?? []) {
|
|
65936
|
+
const state2 = REVIEWER_STATES[review.state];
|
|
65937
|
+
if (!state2 || !review.body.trim() || !review.submittedAt) continue;
|
|
65938
|
+
items.push({
|
|
65939
|
+
id: review.id,
|
|
65940
|
+
kind: "review",
|
|
65941
|
+
reviewState: state2,
|
|
65942
|
+
author: actorOf(review.author),
|
|
65943
|
+
body: review.body,
|
|
65944
|
+
createdAt: review.submittedAt,
|
|
65945
|
+
url: webUrl(review.url)
|
|
65946
|
+
});
|
|
65947
|
+
}
|
|
65948
|
+
items.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
65949
|
+
const threads = [];
|
|
65950
|
+
for (const node of pr.reviewThreads?.nodes ?? []) {
|
|
65951
|
+
const comments = node.comments.nodes.map(fromCommentNode);
|
|
65952
|
+
if (comments.length === 0) continue;
|
|
65953
|
+
threads.push({
|
|
65954
|
+
id: node.id,
|
|
65955
|
+
path: node.path,
|
|
65956
|
+
line: node.line ?? node.originalLine,
|
|
65957
|
+
isResolved: node.isResolved,
|
|
65958
|
+
isOutdated: node.isOutdated,
|
|
65959
|
+
comments,
|
|
65960
|
+
...node.comments.totalCount > comments.length ? { truncated: true } : {}
|
|
65961
|
+
});
|
|
65962
|
+
}
|
|
65963
|
+
threads.sort((a, b2) => a.comments[0].createdAt.localeCompare(b2.comments[0].createdAt));
|
|
65964
|
+
const truncated = (pr.comments?.totalCount ?? 0) > (pr.comments?.nodes.length ?? 0) || (pr.reviews?.totalCount ?? 0) > (pr.reviews?.nodes.length ?? 0) || (pr.reviewThreads?.totalCount ?? 0) > (pr.reviewThreads?.nodes.length ?? 0);
|
|
65965
|
+
return { items, threads, ...truncated ? { truncated: true } : {} };
|
|
65966
|
+
}
|
|
65371
65967
|
function parseBranchChecks(data) {
|
|
65372
65968
|
const target = data.repository?.ref?.target;
|
|
65373
65969
|
const latest = /* @__PURE__ */ new Map();
|
|
@@ -65484,10 +66080,16 @@ function reviewDecision(value) {
|
|
|
65484
66080
|
function mergeState(value) {
|
|
65485
66081
|
return value && MERGE_STATES[value] || "unknown";
|
|
65486
66082
|
}
|
|
65487
|
-
var CHECK_RUN_FIELDS, REVIEW_QUERY, LIST_ORDER, AGGREGATE_PRIORITY, MERGE_STATES;
|
|
66083
|
+
var MAX_COMMENTS, MAX_REVIEWS, MAX_THREADS, MAX_THREAD_COMMENTS, MAX_REVIEW_REQUESTS, ACTOR, CHECK_RUN_FIELDS, REVIEW_QUERY, REVIEWER_STATES, actorOf, LIST_ORDER, AGGREGATE_PRIORITY, MERGE_STATES;
|
|
65488
66084
|
var init_checks_query = __esm({
|
|
65489
66085
|
"../../plugins/github/src/server/checks-query.ts"() {
|
|
65490
66086
|
"use strict";
|
|
66087
|
+
MAX_COMMENTS = 50;
|
|
66088
|
+
MAX_REVIEWS = 50;
|
|
66089
|
+
MAX_THREADS = 50;
|
|
66090
|
+
MAX_THREAD_COMMENTS = 20;
|
|
66091
|
+
MAX_REVIEW_REQUESTS = 30;
|
|
66092
|
+
ACTOR = "login avatarUrl";
|
|
65491
66093
|
CHECK_RUN_FIELDS = `
|
|
65492
66094
|
databaseId
|
|
65493
66095
|
name
|
|
@@ -65512,6 +66114,41 @@ var init_checks_query = __esm({
|
|
|
65512
66114
|
updatedAt
|
|
65513
66115
|
mergeStateStatus
|
|
65514
66116
|
reviewDecision
|
|
66117
|
+
reviewRequests(first: ${MAX_REVIEW_REQUESTS}) {
|
|
66118
|
+
nodes {
|
|
66119
|
+
asCodeOwner
|
|
66120
|
+
requestedReviewer {
|
|
66121
|
+
__typename
|
|
66122
|
+
... on User { ${ACTOR} }
|
|
66123
|
+
... on Bot { ${ACTOR} }
|
|
66124
|
+
... on Mannequin { ${ACTOR} }
|
|
66125
|
+
... on Team { slug }
|
|
66126
|
+
}
|
|
66127
|
+
}
|
|
66128
|
+
}
|
|
66129
|
+
reviews(last: ${MAX_REVIEWS}) {
|
|
66130
|
+
totalCount
|
|
66131
|
+
nodes { id state body submittedAt url author { ${ACTOR} } }
|
|
66132
|
+
}
|
|
66133
|
+
comments(last: ${MAX_COMMENTS}) {
|
|
66134
|
+
totalCount
|
|
66135
|
+
nodes { id body createdAt url author { ${ACTOR} } }
|
|
66136
|
+
}
|
|
66137
|
+
reviewThreads(first: ${MAX_THREADS}) {
|
|
66138
|
+
totalCount
|
|
66139
|
+
nodes {
|
|
66140
|
+
id
|
|
66141
|
+
isResolved
|
|
66142
|
+
isOutdated
|
|
66143
|
+
path
|
|
66144
|
+
line
|
|
66145
|
+
originalLine
|
|
66146
|
+
comments(first: ${MAX_THREAD_COMMENTS}) {
|
|
66147
|
+
totalCount
|
|
66148
|
+
nodes { id body createdAt url author { ${ACTOR} } }
|
|
66149
|
+
}
|
|
66150
|
+
}
|
|
66151
|
+
}
|
|
65515
66152
|
commits(last: 1) {
|
|
65516
66153
|
nodes {
|
|
65517
66154
|
commit {
|
|
@@ -65549,6 +66186,16 @@ var init_checks_query = __esm({
|
|
|
65549
66186
|
}
|
|
65550
66187
|
}
|
|
65551
66188
|
}`;
|
|
66189
|
+
REVIEWER_STATES = {
|
|
66190
|
+
APPROVED: "approved",
|
|
66191
|
+
CHANGES_REQUESTED: "changes_requested",
|
|
66192
|
+
COMMENTED: "commented",
|
|
66193
|
+
DISMISSED: "dismissed"
|
|
66194
|
+
};
|
|
66195
|
+
actorOf = (author) => ({
|
|
66196
|
+
login: author?.login ?? "ghost",
|
|
66197
|
+
avatarUrl: webUrl(author?.avatarUrl ?? null)
|
|
66198
|
+
});
|
|
65552
66199
|
LIST_ORDER = {
|
|
65553
66200
|
failure: 0,
|
|
65554
66201
|
running: 1,
|
|
@@ -68049,11 +68696,14 @@ function stopRuntime(rt) {
|
|
|
68049
68696
|
}
|
|
68050
68697
|
function idleTimeoutMs2() {
|
|
68051
68698
|
const override = Number(process.env.BAND_AGENT_IDLE_TIMEOUT_MS);
|
|
68052
|
-
return Number.isFinite(override) && override > 0 ? override :
|
|
68699
|
+
return Number.isFinite(override) && override > 0 ? override : null;
|
|
68053
68700
|
}
|
|
68054
|
-
function scheduleIdle(rt, delayMs
|
|
68701
|
+
function scheduleIdle(rt, delayMs) {
|
|
68055
68702
|
if (rt.idleTimer) clearTimeout(rt.idleTimer);
|
|
68056
|
-
|
|
68703
|
+
rt.idleTimer = null;
|
|
68704
|
+
const timeout = idleTimeoutMs2();
|
|
68705
|
+
if (timeout === null) return;
|
|
68706
|
+
const delay2 = Math.min(delayMs ?? timeout, MAX_TIMER_MS2);
|
|
68057
68707
|
rt.idleTimer = setTimeout(() => {
|
|
68058
68708
|
rt.idleTimer = null;
|
|
68059
68709
|
if (rt.inTurn || rt.pending.size > 0 || rt.attaching) return;
|
|
@@ -68068,7 +68718,7 @@ function scheduleIdle(rt, delayMs = idleTimeoutMs2()) {
|
|
|
68068
68718
|
},
|
|
68069
68719
|
"idle agent has pending work; not stopping it"
|
|
68070
68720
|
);
|
|
68071
|
-
scheduleIdle(rt, until - now +
|
|
68721
|
+
scheduleIdle(rt, until - now + timeout);
|
|
68072
68722
|
return;
|
|
68073
68723
|
}
|
|
68074
68724
|
log46.info({ chatId: rt.chatId }, "stopping idle agent");
|
|
@@ -68208,6 +68858,9 @@ function routeUpdate(rt, notification) {
|
|
|
68208
68858
|
if (!rt.attaching) {
|
|
68209
68859
|
rt.work.observeUpdate(update, rt.inTurn);
|
|
68210
68860
|
agentActive(rt);
|
|
68861
|
+
if (!rt.inTurn && rt.routing === "log" && AGENT_WORK_UPDATES.has(update.sessionUpdate)) {
|
|
68862
|
+
activityObserver?.(rt.chatId, rt.worktreeId, "update");
|
|
68863
|
+
}
|
|
68211
68864
|
}
|
|
68212
68865
|
if (rt.routing === "drop") return;
|
|
68213
68866
|
if (update.sessionUpdate === "user_message_chunk") {
|
|
@@ -68283,6 +68936,7 @@ function handlersFor(rt, generation) {
|
|
|
68283
68936
|
if (n.sessionId !== rt.sessionId || rt.attaching) return;
|
|
68284
68937
|
rt.work.observeAsyncTask(n.update);
|
|
68285
68938
|
agentActive(rt);
|
|
68939
|
+
activityObserver?.(rt.chatId, rt.worktreeId, "work");
|
|
68286
68940
|
},
|
|
68287
68941
|
onPermission: (req, signal) => requestPermission(rt, req, signal),
|
|
68288
68942
|
onElicitation: (req, signal) => requestElicitation(rt, req, signal),
|
|
@@ -68582,14 +69236,14 @@ function sessionCost(sessionId) {
|
|
|
68582
69236
|
const row = events.latest(sessionId, revision, { kind: "turn-ended" });
|
|
68583
69237
|
return row?.event.type === "turn-ended" ? row.event.usage?.costUsd ?? null : null;
|
|
68584
69238
|
}
|
|
68585
|
-
var log46,
|
|
69239
|
+
var log46, CANCEL_GRACE_MS, MAX_TIMER_MS2, PROBE_SETTLE_MS, events, settings, RUNTIMES_KEY, LISTENERS_KEY2, CATALOG_KEY, DEFAULTS_CHANGED_KEY, g6, runtimes, listeners2, catalog, defaultsChangedAt, transientId, pendingObserver, AGENT_WORK_UPDATES, activityObserver, ChatNotFoundError, AgentSessionService, agentSessionService;
|
|
68586
69240
|
var init_agent_session_service = __esm({
|
|
68587
69241
|
"src/server/services/agent-session-service.ts"() {
|
|
68588
69242
|
"use strict";
|
|
68589
69243
|
init_src2();
|
|
68590
69244
|
init_src();
|
|
68591
69245
|
init_acp_agent_process();
|
|
68592
|
-
|
|
69246
|
+
init_chat_events2();
|
|
68593
69247
|
init_settings();
|
|
68594
69248
|
init_registry();
|
|
68595
69249
|
init_agent_pending_work();
|
|
@@ -68600,9 +69254,8 @@ var init_agent_session_service = __esm({
|
|
|
68600
69254
|
init_mcp_proxy_service();
|
|
68601
69255
|
init_worktree_service();
|
|
68602
69256
|
log46 = createLogger("agent-sessions");
|
|
68603
|
-
IDLE_TIMEOUT_MS = 15 * 6e4;
|
|
68604
69257
|
CANCEL_GRACE_MS = 15e3;
|
|
68605
|
-
|
|
69258
|
+
MAX_TIMER_MS2 = 2 ** 31 - 1;
|
|
68606
69259
|
PROBE_SETTLE_MS = 1500;
|
|
68607
69260
|
events = new ChatEventQueries();
|
|
68608
69261
|
settings = new SettingsQueries();
|
|
@@ -68621,6 +69274,15 @@ var init_agent_session_service = __esm({
|
|
|
68621
69274
|
defaultsChangedAt = g6[DEFAULTS_CHANGED_KEY];
|
|
68622
69275
|
transientId = -1e9;
|
|
68623
69276
|
pendingObserver = null;
|
|
69277
|
+
AGENT_WORK_UPDATES = /* @__PURE__ */ new Set([
|
|
69278
|
+
"user_message_chunk",
|
|
69279
|
+
"agent_message_chunk",
|
|
69280
|
+
"agent_thought_chunk",
|
|
69281
|
+
"tool_call",
|
|
69282
|
+
"tool_call_update",
|
|
69283
|
+
"plan"
|
|
69284
|
+
]);
|
|
69285
|
+
activityObserver = null;
|
|
68624
69286
|
ChatNotFoundError = class extends Error {
|
|
68625
69287
|
constructor(chatId) {
|
|
68626
69288
|
super(`Chat not found: ${chatId}`);
|
|
@@ -68647,6 +69309,16 @@ var init_agent_session_service = __esm({
|
|
|
68647
69309
|
observePending(observer) {
|
|
68648
69310
|
pendingObserver = observer;
|
|
68649
69311
|
}
|
|
69312
|
+
/** Called when the agent acts outside a turn Band started: an update from
|
|
69313
|
+
* a turn of its own, or a background task starting or ending. */
|
|
69314
|
+
observeActivity(observer) {
|
|
69315
|
+
activityObserver = observer;
|
|
69316
|
+
}
|
|
69317
|
+
/** The soonest time one of the chat's pending-work holds lapses, or null with none. */
|
|
69318
|
+
nextWorkDeadline(chatId) {
|
|
69319
|
+
const holds = runtimes.get(chatId)?.work.outstanding() ?? [];
|
|
69320
|
+
return holds.length > 0 ? Math.min(...holds.map((h) => h.until)) : null;
|
|
69321
|
+
}
|
|
68650
69322
|
/**
|
|
68651
69323
|
* Makes sure the chat's agent process runs and is attached to the chat's
|
|
68652
69324
|
* session, starting a new session when the chat has none. For `view`,
|
|
@@ -73424,11 +74096,11 @@ var require_core = __commonJS({
|
|
|
73424
74096
|
Ajv2.ValidationError = validation_error_1.default;
|
|
73425
74097
|
Ajv2.MissingRefError = ref_error_1.default;
|
|
73426
74098
|
exports.default = Ajv2;
|
|
73427
|
-
function checkOptions(checkOpts, options, msg,
|
|
74099
|
+
function checkOptions(checkOpts, options, msg, log78 = "error") {
|
|
73428
74100
|
for (const key2 in checkOpts) {
|
|
73429
74101
|
const opt = key2;
|
|
73430
74102
|
if (opt in options)
|
|
73431
|
-
this.logger[
|
|
74103
|
+
this.logger[log78](`${msg}: option ${key2}. ${checkOpts[opt]}`);
|
|
73432
74104
|
}
|
|
73433
74105
|
}
|
|
73434
74106
|
function getSchEnv(keyRef) {
|
|
@@ -75914,10 +76586,10 @@ init_agent_spawn();
|
|
|
75914
76586
|
import {
|
|
75915
76587
|
appendFileSync as appendFileSync2,
|
|
75916
76588
|
createReadStream as createReadStream5,
|
|
75917
|
-
existsSync as
|
|
75918
|
-
mkdirSync as
|
|
76589
|
+
existsSync as existsSync17,
|
|
76590
|
+
mkdirSync as mkdirSync10,
|
|
75919
76591
|
readFileSync as readFileSync12,
|
|
75920
|
-
statSync as
|
|
76592
|
+
statSync as statSync15
|
|
75921
76593
|
} from "node:fs";
|
|
75922
76594
|
import { createServer as createServer3 } from "node:http";
|
|
75923
76595
|
import { basename as basename7, join as join55, resolve as resolve14, sep as sep10 } from "node:path";
|
|
@@ -75928,7 +76600,7 @@ import { parseArgs } from "node:util";
|
|
|
75928
76600
|
init_src();
|
|
75929
76601
|
init_daemon_backend();
|
|
75930
76602
|
init_in_process_backend();
|
|
75931
|
-
import { existsSync as
|
|
76603
|
+
import { existsSync as existsSync5, statSync as statSync5 } from "node:fs";
|
|
75932
76604
|
import { join as join7 } from "node:path";
|
|
75933
76605
|
var log4 = createLogger("terminal-backend");
|
|
75934
76606
|
function createTerminalBackend(serverRoot, bandHome2) {
|
|
@@ -75937,7 +76609,7 @@ function createTerminalBackend(serverRoot, bandHome2) {
|
|
|
75937
76609
|
const entry = [
|
|
75938
76610
|
join7(serverRoot, "terminal-daemon.mjs"),
|
|
75939
76611
|
join7(serverRoot, "terminal-daemon.ts")
|
|
75940
|
-
].find((candidate) =>
|
|
76612
|
+
].find((candidate) => existsSync5(candidate));
|
|
75941
76613
|
if (!entry) {
|
|
75942
76614
|
log4.error(
|
|
75943
76615
|
{ serverRoot },
|
|
@@ -75945,7 +76617,7 @@ function createTerminalBackend(serverRoot, bandHome2) {
|
|
|
75945
76617
|
);
|
|
75946
76618
|
return new InProcessTerminalBackend();
|
|
75947
76619
|
}
|
|
75948
|
-
const { size, mtimeMs } =
|
|
76620
|
+
const { size, mtimeMs } = statSync5(entry);
|
|
75949
76621
|
return new DaemonTerminalBackend({
|
|
75950
76622
|
entry,
|
|
75951
76623
|
runDir: join7(bandHome2, "run"),
|
|
@@ -78243,14 +78915,14 @@ import { join as join9, normalize, resolve as resolve2, sep as sep2 } from "node
|
|
|
78243
78915
|
|
|
78244
78916
|
// ../../node_modules/.pnpm/totalist@3.0.1/node_modules/totalist/sync/index.mjs
|
|
78245
78917
|
import { join as join8, resolve } from "path";
|
|
78246
|
-
import { readdirSync as readdirSync5, statSync as
|
|
78918
|
+
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
78247
78919
|
function totalist(dir, callback, pre = "") {
|
|
78248
78920
|
dir = resolve(".", dir);
|
|
78249
78921
|
let arr = readdirSync5(dir);
|
|
78250
78922
|
let i = 0, abs, stats;
|
|
78251
78923
|
for (; i < arr.length; i++) {
|
|
78252
78924
|
abs = join8(dir, arr[i]);
|
|
78253
|
-
stats =
|
|
78925
|
+
stats = statSync6(abs);
|
|
78254
78926
|
stats.isDirectory() ? totalist(abs, callback, join8(pre, arr[i])) : callback(join8(pre, arr[i]), abs, stats);
|
|
78255
78927
|
}
|
|
78256
78928
|
}
|
|
@@ -78774,7 +79446,7 @@ function viaLocal(dir, isEtag, uri, extns) {
|
|
|
78774
79446
|
function is404(req, res) {
|
|
78775
79447
|
return res.statusCode = 404, res.end();
|
|
78776
79448
|
}
|
|
78777
|
-
function send(req, res,
|
|
79449
|
+
function send(req, res, file3, stats, headers) {
|
|
78778
79450
|
let code = 200, tmp, opts = {};
|
|
78779
79451
|
headers = { ...headers };
|
|
78780
79452
|
for (let key2 in headers) {
|
|
@@ -78802,7 +79474,7 @@ function send(req, res, file2, stats, headers) {
|
|
|
78802
79474
|
headers["Accept-Ranges"] = "bytes";
|
|
78803
79475
|
}
|
|
78804
79476
|
res.writeHead(code, headers);
|
|
78805
|
-
fs.createReadStream(
|
|
79477
|
+
fs.createReadStream(file3, opts).pipe(res);
|
|
78806
79478
|
}
|
|
78807
79479
|
var ENCODING = {
|
|
78808
79480
|
".br": "br",
|
|
@@ -79071,11 +79743,7 @@ function createCorsMiddleware(getAllowedOrigins) {
|
|
|
79071
79743
|
|
|
79072
79744
|
// src/api/chat-events.ts
|
|
79073
79745
|
init_src();
|
|
79074
|
-
|
|
79075
|
-
// ../../packages/shared/src/chat-events.ts
|
|
79076
|
-
var HISTORY_PAGE_SIZE = 20;
|
|
79077
|
-
|
|
79078
|
-
// src/api/chat-events.ts
|
|
79746
|
+
init_chat_events();
|
|
79079
79747
|
init_queued_message_store();
|
|
79080
79748
|
|
|
79081
79749
|
// src/server/services/_utils/sse-writer.ts
|
|
@@ -79290,6 +79958,7 @@ async function handleChatEvents(req, res, chatId) {
|
|
|
79290
79958
|
}
|
|
79291
79959
|
|
|
79292
79960
|
// src/api/chat-history.ts
|
|
79961
|
+
init_chat_events();
|
|
79293
79962
|
init_agent_session_service();
|
|
79294
79963
|
init_chat_service();
|
|
79295
79964
|
var MAX_CHAT_ID_LENGTH = 200;
|
|
@@ -79524,7 +80193,7 @@ init_map_limited();
|
|
|
79524
80193
|
init_remote_worktrees();
|
|
79525
80194
|
init_repo_host_policy();
|
|
79526
80195
|
init_ephemeral_lifecycle_service();
|
|
79527
|
-
import { existsSync as
|
|
80196
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
79528
80197
|
import { basename as basename2, join as join44, posix as posix7, resolve as resolve9 } from "node:path";
|
|
79529
80198
|
|
|
79530
80199
|
// src/server/services/repo-avatar-service.ts
|
|
@@ -79916,7 +80585,7 @@ var RepoService = class {
|
|
|
79916
80585
|
}
|
|
79917
80586
|
}
|
|
79918
80587
|
const resolvedPath = resolve9(path2);
|
|
79919
|
-
const kind =
|
|
80588
|
+
const kind = existsSync15(join44(resolvedPath, ".git")) ? "git" : "plain";
|
|
79920
80589
|
let defaultBranch = "main";
|
|
79921
80590
|
let worktrees2 = [];
|
|
79922
80591
|
if (kind === "git") {
|
|
@@ -80253,15 +80922,15 @@ var RepoService = class {
|
|
|
80253
80922
|
...isLocalHostEnabled() ? ["local"] : [],
|
|
80254
80923
|
...tokenService.listHosts().filter((h) => h.id !== "local" && h.status === "online").map((h) => h.id)
|
|
80255
80924
|
];
|
|
80256
|
-
let
|
|
80925
|
+
let failure2 = "no host is available to ask";
|
|
80257
80926
|
for (const hostId2 of hostIds2) {
|
|
80258
80927
|
try {
|
|
80259
80928
|
const { stdout } = await hostRegistry.hostById(hostId2).exec("git", ["ls-remote", "--symref", "--", url2, "HEAD"], { timeoutMs: 6e4 });
|
|
80260
80929
|
const match = stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
|
|
80261
80930
|
if (match) return match[1];
|
|
80262
|
-
|
|
80931
|
+
failure2 = "the remote did not report a default branch";
|
|
80263
80932
|
} catch (err) {
|
|
80264
|
-
|
|
80933
|
+
failure2 = err instanceof Error ? err.message : String(err);
|
|
80265
80934
|
}
|
|
80266
80935
|
}
|
|
80267
80936
|
if (asked?.host) {
|
|
@@ -80271,7 +80940,7 @@ var RepoService = class {
|
|
|
80271
80940
|
if (viaApi) return viaApi;
|
|
80272
80941
|
}
|
|
80273
80942
|
throw new RepoInputError(
|
|
80274
|
-
`Could not read the default branch of ${asked?.url ?? url2} (${
|
|
80943
|
+
`Could not read the default branch of ${asked?.url ?? url2} (${failure2}). Pass the branch.`
|
|
80275
80944
|
);
|
|
80276
80945
|
}
|
|
80277
80946
|
/**
|
|
@@ -80294,7 +80963,7 @@ var RepoService = class {
|
|
|
80294
80963
|
message: `Repo "${name4}" is already a git repo`
|
|
80295
80964
|
});
|
|
80296
80965
|
}
|
|
80297
|
-
if (!
|
|
80966
|
+
if (!existsSync15(repo.path)) {
|
|
80298
80967
|
throw new TRPCError({
|
|
80299
80968
|
code: "NOT_FOUND",
|
|
80300
80969
|
message: `Repo path "${repo.path}" no longer exists. Remove the repo and re-add it.`
|
|
@@ -80511,7 +81180,7 @@ var DEFAULT_MAX_BYTES = 200 * 1024 * 1024;
|
|
|
80511
81180
|
var DEFAULT_QUIET_MS = 3 * 6e4;
|
|
80512
81181
|
var DEFAULT_POLL_MS2 = 6e4;
|
|
80513
81182
|
var MAX_VIEW_BYTES = 1024 * 1024;
|
|
80514
|
-
var
|
|
81183
|
+
var LOCAL_HOST_ID8 = "local";
|
|
80515
81184
|
var errorText3 = (err) => err instanceof Error ? err.message : String(err);
|
|
80516
81185
|
function envNumber2(name4, fallback) {
|
|
80517
81186
|
const raw = Number(process.env[name4]);
|
|
@@ -80519,7 +81188,7 @@ function envNumber2(name4, fallback) {
|
|
|
80519
81188
|
}
|
|
80520
81189
|
var checkpointKeep = () => Math.floor(envNumber2("BAND_CHECKPOINT_KEEP", DEFAULT_KEEP));
|
|
80521
81190
|
var checkpointMaxBytes = () => envNumber2("BAND_CHECKPOINT_MAX_BYTES", DEFAULT_MAX_BYTES);
|
|
80522
|
-
var
|
|
81191
|
+
var quietMs2 = () => envNumber2("BAND_CHECKPOINT_QUIET_MS", DEFAULT_QUIET_MS);
|
|
80523
81192
|
var pollMs = () => envNumber2("BAND_CHECKPOINT_POLL_MS", DEFAULT_POLL_MS2);
|
|
80524
81193
|
function toView2(row) {
|
|
80525
81194
|
return {
|
|
@@ -80536,11 +81205,11 @@ function toView2(row) {
|
|
|
80536
81205
|
sessions: row.sessionIds.length
|
|
80537
81206
|
};
|
|
80538
81207
|
}
|
|
80539
|
-
function isSafeSessionFile(
|
|
80540
|
-
if (!/^[A-Za-z0-9_-]+$/.test(
|
|
80541
|
-
if (typeof
|
|
80542
|
-
if (posix8.isAbsolute(
|
|
80543
|
-
const clean = posix8.normalize(
|
|
81208
|
+
function isSafeSessionFile(file3) {
|
|
81209
|
+
if (!/^[A-Za-z0-9_-]+$/.test(file3.root)) return false;
|
|
81210
|
+
if (typeof file3.rel !== "string" || file3.rel === "" || file3.rel.includes("\0")) return false;
|
|
81211
|
+
if (posix8.isAbsolute(file3.rel)) return false;
|
|
81212
|
+
const clean = posix8.normalize(file3.rel);
|
|
80544
81213
|
return clean !== ".." && !clean.startsWith("../") && clean !== ".";
|
|
80545
81214
|
}
|
|
80546
81215
|
function sessionsHashOf(files) {
|
|
@@ -80629,8 +81298,8 @@ var CheckpointService = class {
|
|
|
80629
81298
|
ineligible(worktreeId4) {
|
|
80630
81299
|
const found = worktreeService.resolve(worktreeId4);
|
|
80631
81300
|
if (!found) return "unknown worktree";
|
|
80632
|
-
const hostId2 = found.worktree.hostId ??
|
|
80633
|
-
if (hostId2 ===
|
|
81301
|
+
const hostId2 = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81302
|
+
if (hostId2 === LOCAL_HOST_ID8) return "the worktree is on the hub's own machine";
|
|
80634
81303
|
if (found.repo.kind !== "git") return "not a git repo";
|
|
80635
81304
|
if (found.worktree.supersededBy) return "superseded";
|
|
80636
81305
|
if (this.busy.has(worktreeId4)) return "busy";
|
|
@@ -80673,7 +81342,7 @@ var CheckpointService = class {
|
|
|
80673
81342
|
const out = [];
|
|
80674
81343
|
for (const repo of loadState().repos) {
|
|
80675
81344
|
for (const wt of repo.worktrees) {
|
|
80676
|
-
if ((wt.hostId ??
|
|
81345
|
+
if ((wt.hostId ?? LOCAL_HOST_ID8) === hostId2) {
|
|
80677
81346
|
out.push({
|
|
80678
81347
|
repo: repo.name,
|
|
80679
81348
|
name: wt.name,
|
|
@@ -80687,8 +81356,8 @@ var CheckpointService = class {
|
|
|
80687
81356
|
async doTake(worktreeId4, trigger, opts) {
|
|
80688
81357
|
const found = worktreeService.resolve(worktreeId4);
|
|
80689
81358
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
80690
|
-
const hostId2 = found.worktree.hostId ??
|
|
80691
|
-
if (hostId2 ===
|
|
81359
|
+
const hostId2 = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81360
|
+
if (hostId2 === LOCAL_HOST_ID8) {
|
|
80692
81361
|
throw new CheckpointError(
|
|
80693
81362
|
"invalid",
|
|
80694
81363
|
"Checkpoints are for worktrees on a worker, not on the hub's own machine."
|
|
@@ -80875,7 +81544,7 @@ var CheckpointService = class {
|
|
|
80875
81544
|
if (this.ineligible(worktreeId4)) continue;
|
|
80876
81545
|
live.add(worktreeId4);
|
|
80877
81546
|
try {
|
|
80878
|
-
await this.sweepOne(worktreeId4, wt.hostId ??
|
|
81547
|
+
await this.sweepOne(worktreeId4, wt.hostId ?? LOCAL_HOST_ID8, wt.path);
|
|
80879
81548
|
} catch (err) {
|
|
80880
81549
|
log49.debug({ worktreeId: worktreeId4, err: errorText3(err) }, "checkpoint sweep skipped a worktree");
|
|
80881
81550
|
}
|
|
@@ -80899,7 +81568,7 @@ var CheckpointService = class {
|
|
|
80899
81568
|
this.watches.set(worktreeId4, { signature, changedAt: now, checkpointed: watch.checkpointed });
|
|
80900
81569
|
return;
|
|
80901
81570
|
}
|
|
80902
|
-
if (watch.checkpointed === signature || now - watch.changedAt <
|
|
81571
|
+
if (watch.checkpointed === signature || now - watch.changedAt < quietMs2()) return;
|
|
80903
81572
|
await this.take(worktreeId4, "quiet");
|
|
80904
81573
|
}
|
|
80905
81574
|
/**
|
|
@@ -81074,11 +81743,11 @@ ${diff}`).digest("hex");
|
|
|
81074
81743
|
const found = worktreeService.resolve(worktreeId4);
|
|
81075
81744
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
81076
81745
|
const { repo, worktree } = found;
|
|
81077
|
-
const fromHostId = worktree.hostId ??
|
|
81746
|
+
const fromHostId = worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81078
81747
|
if (fromHostId === toHostId) {
|
|
81079
81748
|
throw new CheckpointError("invalid", `${worktreeId4} is already on ${toHostId}.`);
|
|
81080
81749
|
}
|
|
81081
|
-
if (toHostId ===
|
|
81750
|
+
if (toHostId === LOCAL_HOST_ID8) {
|
|
81082
81751
|
throw new CheckpointError(
|
|
81083
81752
|
"invalid",
|
|
81084
81753
|
"A worktree can be restored on a worker, not on the hub's own machine."
|
|
@@ -81111,7 +81780,7 @@ ${diff}`).digest("hex");
|
|
|
81111
81780
|
}
|
|
81112
81781
|
}
|
|
81113
81782
|
if (repo.worktrees.some(
|
|
81114
|
-
(w2) => w2.name === worktree.name && (w2.hostId ??
|
|
81783
|
+
(w2) => w2.name === worktree.name && (w2.hostId ?? LOCAL_HOST_ID8) === toHostId
|
|
81115
81784
|
)) {
|
|
81116
81785
|
throw new CheckpointError(
|
|
81117
81786
|
"conflict",
|
|
@@ -81250,11 +81919,11 @@ ${diff}`).digest("hex");
|
|
|
81250
81919
|
}
|
|
81251
81920
|
const stage = posix8.join(wipDir, "sessions", `${row.id}`);
|
|
81252
81921
|
await host.fs.rm(stage, { recursive: true, force: true });
|
|
81253
|
-
for (const
|
|
81254
|
-
if (!isSafeSessionFile(
|
|
81255
|
-
const target = posix8.join(stage,
|
|
81922
|
+
for (const file3 of files) {
|
|
81923
|
+
if (!isSafeSessionFile(file3)) continue;
|
|
81924
|
+
const target = posix8.join(stage, file3.root, file3.rel);
|
|
81256
81925
|
await host.fs.mkdir(posix8.dirname(target), { recursive: true });
|
|
81257
|
-
await host.fs.writeFile(target, Buffer.from(
|
|
81926
|
+
await host.fs.writeFile(target, Buffer.from(file3.data, "base64"));
|
|
81258
81927
|
}
|
|
81259
81928
|
await rpc.call(METHOD_LIFECYCLE_IMPORT_SESSIONS, { dir: stage });
|
|
81260
81929
|
}
|
|
@@ -81266,8 +81935,8 @@ ${diff}`).digest("hex");
|
|
|
81266
81935
|
const { worktreeId: worktreeId4 } = input;
|
|
81267
81936
|
const found = worktreeService.resolve(worktreeId4);
|
|
81268
81937
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
81269
|
-
const fromHostId = found.worktree.hostId ??
|
|
81270
|
-
if (fromHostId ===
|
|
81938
|
+
const fromHostId = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81939
|
+
if (fromHostId === LOCAL_HOST_ID8) {
|
|
81271
81940
|
throw new CheckpointError("invalid", "Only worktrees on a worker can be moved.");
|
|
81272
81941
|
}
|
|
81273
81942
|
if (!this.sessions.get(fromHostId)?.attached) {
|
|
@@ -81335,7 +82004,7 @@ ${diff}`).digest("hex");
|
|
|
81335
82004
|
for (const repo of loadState().repos) {
|
|
81336
82005
|
const clone3 = hostRegistry.repoPathOn(repo.name, hostId2, "");
|
|
81337
82006
|
for (const wt of repo.worktrees) {
|
|
81338
|
-
if ((wt.hostId ??
|
|
82007
|
+
if ((wt.hostId ?? LOCAL_HOST_ID8) !== hostId2 || wt.supersededBy) continue;
|
|
81339
82008
|
if (clone3 && wt.path === clone3) continue;
|
|
81340
82009
|
out.push({
|
|
81341
82010
|
repo: repo.name,
|
|
@@ -81682,7 +82351,7 @@ init_token_service();
|
|
|
81682
82351
|
init_worktree_script_service();
|
|
81683
82352
|
init_worktree_service();
|
|
81684
82353
|
var log51 = createLogger("follow");
|
|
81685
|
-
var
|
|
82354
|
+
var LOCAL_HOST_ID9 = "local";
|
|
81686
82355
|
var errorText4 = (err) => err instanceof Error ? err.message : String(err);
|
|
81687
82356
|
function autoContinueMaxAgeMs() {
|
|
81688
82357
|
const raw = Number(process.env.BAND_AUTO_CONTINUE_MAX_AGE_MS);
|
|
@@ -81697,7 +82366,7 @@ var FollowService = class {
|
|
|
81697
82366
|
locate(worktreeId4) {
|
|
81698
82367
|
const found = worktreeService.resolve(worktreeId4);
|
|
81699
82368
|
if (!found) return null;
|
|
81700
|
-
return { ...found, hostId: found.worktree.hostId ??
|
|
82369
|
+
return { ...found, hostId: found.worktree.hostId ?? LOCAL_HOST_ID9 };
|
|
81701
82370
|
}
|
|
81702
82371
|
/**
|
|
81703
82372
|
* Whether the worktree is on a worker that is offline and is not asleep. A sleeping worktree
|
|
@@ -81706,7 +82375,7 @@ var FollowService = class {
|
|
|
81706
82375
|
isOffline(worktreeId4) {
|
|
81707
82376
|
const at = this.locate(worktreeId4);
|
|
81708
82377
|
if (!at) return false;
|
|
81709
|
-
if (at.hostId ===
|
|
82378
|
+
if (at.hostId === LOCAL_HOST_ID9) return false;
|
|
81710
82379
|
if (at.worktree.supersededBy) return false;
|
|
81711
82380
|
if (checkpointService.isHostOnline(at.hostId)) return false;
|
|
81712
82381
|
if (ephemeralLifecycleService.stateOf(worktreeId4)) return false;
|
|
@@ -81761,7 +82430,7 @@ var FollowService = class {
|
|
|
81761
82430
|
blocked: { reason, message },
|
|
81762
82431
|
question: message
|
|
81763
82432
|
});
|
|
81764
|
-
if (hostId2 ===
|
|
82433
|
+
if (hostId2 === LOCAL_HOST_ID9) {
|
|
81765
82434
|
return blocked("not-offline", "This worktree is on the hub's own machine, which is up.");
|
|
81766
82435
|
}
|
|
81767
82436
|
if (worktree.supersededBy) {
|
|
@@ -81793,12 +82462,12 @@ var FollowService = class {
|
|
|
81793
82462
|
const needed = this.neededAgents(worktreeId4, opts);
|
|
81794
82463
|
const clones = new Set(
|
|
81795
82464
|
[...this.repos.allHostPaths().get(repo.name)?.keys() ?? []].filter(
|
|
81796
|
-
(id) => id !==
|
|
82465
|
+
(id) => id !== LOCAL_HOST_ID9
|
|
81797
82466
|
)
|
|
81798
82467
|
);
|
|
81799
82468
|
const load = await this.loadByHost();
|
|
81800
82469
|
const candidates = tokenService.listHosts(1e3).filter(
|
|
81801
|
-
(h) => h.id !==
|
|
82470
|
+
(h) => h.id !== LOCAL_HOST_ID9 && h.id !== hostId2 && h.status === "online" && checkpointService.isHostOnline(h.id) && !checkpointService.isHostEphemeral(h.id) && isHostAllowed(allowed2, h.id)
|
|
81802
82471
|
);
|
|
81803
82472
|
if (candidates.length === 0) {
|
|
81804
82473
|
return {
|
|
@@ -81867,7 +82536,7 @@ var FollowService = class {
|
|
|
81867
82536
|
for (const repo of this.repos.loadAll()) {
|
|
81868
82537
|
for (const wt of repo.worktrees) {
|
|
81869
82538
|
if (wt.supersededBy) continue;
|
|
81870
|
-
const id = wt.hostId ??
|
|
82539
|
+
const id = wt.hostId ?? LOCAL_HOST_ID9;
|
|
81871
82540
|
out.set(id, (out.get(id) ?? 0) + 1);
|
|
81872
82541
|
}
|
|
81873
82542
|
}
|
|
@@ -81883,7 +82552,7 @@ var FollowService = class {
|
|
|
81883
82552
|
async gate(worktreeId4, opts = {}) {
|
|
81884
82553
|
const stale = this.locate(worktreeId4);
|
|
81885
82554
|
const next = stale?.worktree.supersededBy ? this.locate(stale.worktree.supersededBy) : null;
|
|
81886
|
-
if (stale && next && stale.hostId !==
|
|
82555
|
+
if (stale && next && stale.hostId !== LOCAL_HOST_ID9 && !checkpointService.isHostOnline(stale.hostId)) {
|
|
81887
82556
|
return { worktreeId: idOf(next) };
|
|
81888
82557
|
}
|
|
81889
82558
|
if (!this.isOffline(worktreeId4)) return { worktreeId: worktreeId4 };
|
|
@@ -82017,7 +82686,7 @@ var FollowService = class {
|
|
|
82017
82686
|
async onHostReturned(hostId2) {
|
|
82018
82687
|
for (const repo of this.repos.loadAll()) {
|
|
82019
82688
|
for (const wt of repo.worktrees) {
|
|
82020
|
-
if ((wt.hostId ??
|
|
82689
|
+
if ((wt.hostId ?? LOCAL_HOST_ID9) !== hostId2 || !wt.supersededBy) continue;
|
|
82021
82690
|
const oldId = toWorktreeId(repo.name, wt.name, wt.hostId);
|
|
82022
82691
|
if (wt.divergence?.state === "resolved") continue;
|
|
82023
82692
|
try {
|
|
@@ -90379,17 +91048,17 @@ import { execFile as execFile13 } from "node:child_process";
|
|
|
90379
91048
|
import {
|
|
90380
91049
|
accessSync as accessSync3,
|
|
90381
91050
|
constants as constants3,
|
|
90382
|
-
existsSync as
|
|
91051
|
+
existsSync as existsSync16,
|
|
90383
91052
|
lstatSync as lstatSync4,
|
|
90384
|
-
mkdirSync as
|
|
91053
|
+
mkdirSync as mkdirSync9,
|
|
90385
91054
|
readFileSync as readFileSync11,
|
|
90386
|
-
realpathSync as
|
|
91055
|
+
realpathSync as realpathSync5,
|
|
90387
91056
|
symlinkSync,
|
|
90388
|
-
unlinkSync as
|
|
91057
|
+
unlinkSync as unlinkSync6,
|
|
90389
91058
|
writeFileSync as writeFileSync7
|
|
90390
91059
|
} from "node:fs";
|
|
90391
91060
|
import { homedir as homedir15, platform as platform2 } from "node:os";
|
|
90392
|
-
import { basename as basename3, dirname as
|
|
91061
|
+
import { basename as basename3, dirname as dirname13, join as join46 } from "node:path";
|
|
90393
91062
|
function windowsBinDir() {
|
|
90394
91063
|
const localAppData = process.env.LOCALAPPDATA || join46(homedir15(), "AppData", "Local");
|
|
90395
91064
|
return join46(localAppData, "band", "bin");
|
|
@@ -90408,7 +91077,7 @@ function checkCliWindows() {
|
|
|
90408
91077
|
if (!match) return "ConflictingBinary";
|
|
90409
91078
|
const target = match[1];
|
|
90410
91079
|
if (basename3(target).toLowerCase() !== "band.exe") return "ConflictingBinary";
|
|
90411
|
-
return
|
|
91080
|
+
return existsSync16(target) ? "Installed" : "NotInstalled";
|
|
90412
91081
|
}
|
|
90413
91082
|
async function checkCli() {
|
|
90414
91083
|
if (platform2() === "win32") {
|
|
@@ -90421,7 +91090,7 @@ async function checkCli() {
|
|
|
90421
91090
|
}
|
|
90422
91091
|
let target;
|
|
90423
91092
|
try {
|
|
90424
|
-
target =
|
|
91093
|
+
target = realpathSync5(SYMLINK_PATH);
|
|
90425
91094
|
} catch {
|
|
90426
91095
|
return "NotInstalled";
|
|
90427
91096
|
}
|
|
@@ -90500,7 +91169,7 @@ async function addBinDirToUserPath(binDir) {
|
|
|
90500
91169
|
}
|
|
90501
91170
|
async function installCliWindows(binaryPath) {
|
|
90502
91171
|
const binDir = windowsBinDir();
|
|
90503
|
-
|
|
91172
|
+
mkdirSync9(binDir, { recursive: true });
|
|
90504
91173
|
const shim = `@echo off\r
|
|
90505
91174
|
"${binaryPath}" %*\r
|
|
90506
91175
|
`;
|
|
@@ -90516,11 +91185,11 @@ async function installCli(_opts = {}) {
|
|
|
90516
91185
|
await installCliWindows(binaryPath);
|
|
90517
91186
|
return;
|
|
90518
91187
|
}
|
|
90519
|
-
const dir =
|
|
91188
|
+
const dir = dirname13(SYMLINK_PATH);
|
|
90520
91189
|
if (isDirWritable(dir)) {
|
|
90521
91190
|
try {
|
|
90522
91191
|
lstatSync4(SYMLINK_PATH);
|
|
90523
|
-
|
|
91192
|
+
unlinkSync6(SYMLINK_PATH);
|
|
90524
91193
|
} catch (err) {
|
|
90525
91194
|
const code = err.code;
|
|
90526
91195
|
if (code !== "ENOENT") throw err;
|
|
@@ -90774,7 +91443,7 @@ function subscribeToFileChanges(worktreeId4, listener) {
|
|
|
90774
91443
|
// src/server/services/formatter.ts
|
|
90775
91444
|
init_src();
|
|
90776
91445
|
init_registry();
|
|
90777
|
-
import { basename as basename4, dirname as
|
|
91446
|
+
import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute4, join as join47, resolve as resolvePath3 } from "node:path";
|
|
90778
91447
|
import prettier from "prettier";
|
|
90779
91448
|
var log56 = createLogger("formatter");
|
|
90780
91449
|
var FormatterError = class extends Error {
|
|
@@ -90853,7 +91522,7 @@ async function formatFile(worktreePath, filePath, content, options = {}) {
|
|
|
90853
91522
|
var JSON_CONFIGS = [".prettierrc", ".prettierrc.json"];
|
|
90854
91523
|
async function resolveConfigOnHost(fs3, absFile, worktreePath) {
|
|
90855
91524
|
const root = resolvePath3(worktreePath);
|
|
90856
|
-
let dir =
|
|
91525
|
+
let dir = dirname14(absFile);
|
|
90857
91526
|
for (; ; ) {
|
|
90858
91527
|
for (const name4 of JSON_CONFIGS) {
|
|
90859
91528
|
const parsed = await readJson2(fs3, join47(dir, name4));
|
|
@@ -90863,8 +91532,8 @@ async function resolveConfigOnHost(fs3, absFile, worktreePath) {
|
|
|
90863
91532
|
if (pkg && typeof pkg.prettier === "object" && pkg.prettier !== null) {
|
|
90864
91533
|
return withoutPlugins(pkg.prettier);
|
|
90865
91534
|
}
|
|
90866
|
-
if (dir === root ||
|
|
90867
|
-
dir =
|
|
91535
|
+
if (dir === root || dirname14(dir) === dir) return null;
|
|
91536
|
+
dir = dirname14(dir);
|
|
90868
91537
|
}
|
|
90869
91538
|
}
|
|
90870
91539
|
function withoutPlugins(config2) {
|
|
@@ -90897,7 +91566,7 @@ async function isInsideWorktree(fs3, absFile, worktreePath) {
|
|
|
90897
91566
|
realFile = await fs3.realpath(absFile);
|
|
90898
91567
|
} catch {
|
|
90899
91568
|
try {
|
|
90900
|
-
realFile = join47(await fs3.realpath(
|
|
91569
|
+
realFile = join47(await fs3.realpath(dirname14(absFile)), basename4(absFile));
|
|
90901
91570
|
} catch {
|
|
90902
91571
|
realFile = absFile;
|
|
90903
91572
|
}
|
|
@@ -91160,7 +91829,7 @@ init_environment_build_service();
|
|
|
91160
91829
|
init_src4();
|
|
91161
91830
|
init_dist();
|
|
91162
91831
|
init_registry();
|
|
91163
|
-
import { basename as basename5, dirname as
|
|
91832
|
+
import { basename as basename5, dirname as dirname15, isAbsolute as isAbsolute6 } from "node:path";
|
|
91164
91833
|
init_token_service();
|
|
91165
91834
|
var EnvironmentService = class {
|
|
91166
91835
|
/** The environment of a repo's main checkout, and which hosts meet its `requires`. */
|
|
@@ -91178,8 +91847,8 @@ var EnvironmentService = class {
|
|
|
91178
91847
|
if (!isAbsolute6(path2)) {
|
|
91179
91848
|
throw new TRPCError({ code: "BAD_REQUEST", message: "path must be absolute" });
|
|
91180
91849
|
}
|
|
91181
|
-
const isFile2 = basename5(path2) === "environment.json" && basename5(
|
|
91182
|
-
return this.read(isFile2 ?
|
|
91850
|
+
const isFile2 = basename5(path2) === "environment.json" && basename5(dirname15(path2)) === ".band";
|
|
91851
|
+
return this.read(isFile2 ? dirname15(dirname15(path2)) : path2);
|
|
91183
91852
|
}
|
|
91184
91853
|
read(dir) {
|
|
91185
91854
|
return hostRegistry.local.scripts.environment({ repoPath: dir, worktreePath: dir });
|
|
@@ -91571,26 +92240,83 @@ var historyRouter = t.router({
|
|
|
91571
92240
|
})
|
|
91572
92241
|
});
|
|
91573
92242
|
|
|
92243
|
+
// src/server/api/hooks/router.ts
|
|
92244
|
+
init_zod();
|
|
92245
|
+
|
|
91574
92246
|
// src/server/services/hooks-service.ts
|
|
91575
92247
|
init_registry();
|
|
91576
|
-
|
|
91577
|
-
|
|
92248
|
+
init_token_service();
|
|
92249
|
+
var LOCAL_HOST_ID10 = "local";
|
|
92250
|
+
function hostOf2(hostId2) {
|
|
92251
|
+
return hostRegistry.hostById(hostId2);
|
|
92252
|
+
}
|
|
92253
|
+
function checkHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92254
|
+
return hostOf2(hostId2).agentEnv.hooksStatus();
|
|
91578
92255
|
}
|
|
91579
|
-
function installHooks2() {
|
|
91580
|
-
return
|
|
92256
|
+
function installHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92257
|
+
return hostOf2(hostId2).agentEnv.installHooks();
|
|
92258
|
+
}
|
|
92259
|
+
function removeHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92260
|
+
return hostOf2(hostId2).agentEnv.removeHooks();
|
|
92261
|
+
}
|
|
92262
|
+
async function claudeCodeOn(hostId2, agents) {
|
|
92263
|
+
if (hostId2 !== LOCAL_HOST_ID10) return agents.includes("claude-code");
|
|
92264
|
+
try {
|
|
92265
|
+
return typeof await hostOf2(hostId2).acp.resolveLaunch({ type: "claude-code" }) !== "string";
|
|
92266
|
+
} catch {
|
|
92267
|
+
return false;
|
|
92268
|
+
}
|
|
92269
|
+
}
|
|
92270
|
+
async function listHostHooks() {
|
|
92271
|
+
const hosts2 = tokenService.listHosts().filter((h) => h.id !== LOCAL_HOST_ID10 || h.usable);
|
|
92272
|
+
return Promise.all(
|
|
92273
|
+
hosts2.map(async (h) => {
|
|
92274
|
+
const online = h.id === LOCAL_HOST_ID10 || h.status === "online";
|
|
92275
|
+
const view4 = {
|
|
92276
|
+
hostId: h.id,
|
|
92277
|
+
name: h.name,
|
|
92278
|
+
online,
|
|
92279
|
+
hasClaudeCode: await claudeCodeOn(h.id, h.agents),
|
|
92280
|
+
status: null
|
|
92281
|
+
};
|
|
92282
|
+
if (!online) return view4;
|
|
92283
|
+
try {
|
|
92284
|
+
view4.status = await checkHooks2(h.id);
|
|
92285
|
+
} catch (err) {
|
|
92286
|
+
view4.error = err instanceof Error ? err.message : String(err);
|
|
92287
|
+
}
|
|
92288
|
+
return view4;
|
|
92289
|
+
})
|
|
92290
|
+
);
|
|
91581
92291
|
}
|
|
91582
92292
|
|
|
91583
92293
|
// src/server/api/hooks/router.ts
|
|
92294
|
+
var hostInput = external_exports2.object({ hostId: external_exports2.string().optional() }).default({});
|
|
92295
|
+
function failure(err) {
|
|
92296
|
+
return new Error(err instanceof Error ? err.message : String(err));
|
|
92297
|
+
}
|
|
91584
92298
|
var hooksRouter = t.router({
|
|
91585
|
-
|
|
91586
|
-
|
|
92299
|
+
/** The hub's own host. */
|
|
92300
|
+
check: adminProcedure.input(hostInput).query(async ({ input }) => {
|
|
92301
|
+
return await checkHooks2(input.hostId);
|
|
91587
92302
|
}),
|
|
91588
|
-
|
|
92303
|
+
/** Every host with its hooks status. */
|
|
92304
|
+
hosts: adminProcedure.query(async () => ({ hosts: await listHostHooks() })),
|
|
92305
|
+
install: adminProcedure.input(hostInput).mutation(async ({ input }) => {
|
|
91589
92306
|
try {
|
|
91590
|
-
await installHooks2();
|
|
92307
|
+
await installHooks2(input.hostId);
|
|
91591
92308
|
return { ok: true };
|
|
91592
92309
|
} catch (err) {
|
|
91593
|
-
throw
|
|
92310
|
+
throw failure(err);
|
|
92311
|
+
}
|
|
92312
|
+
}),
|
|
92313
|
+
/** Removes Band's entries and leaves every other hook and setting. */
|
|
92314
|
+
remove: adminProcedure.input(hostInput).mutation(async ({ input }) => {
|
|
92315
|
+
try {
|
|
92316
|
+
await removeHooks2(input.hostId);
|
|
92317
|
+
return { ok: true };
|
|
92318
|
+
} catch (err) {
|
|
92319
|
+
throw failure(err);
|
|
91594
92320
|
}
|
|
91595
92321
|
})
|
|
91596
92322
|
});
|
|
@@ -91724,6 +92450,17 @@ var RELAY_PROCEDURES = /* @__PURE__ */ new Set([
|
|
|
91724
92450
|
"statuses.notify",
|
|
91725
92451
|
"statuses.clearNeedsAttention",
|
|
91726
92452
|
"repos.list",
|
|
92453
|
+
"repos.add",
|
|
92454
|
+
"repos.addFromWorker",
|
|
92455
|
+
"repos.addByUrl",
|
|
92456
|
+
"repos.inspectFolder",
|
|
92457
|
+
"repos.resolveRemote",
|
|
92458
|
+
"repos.gitInit",
|
|
92459
|
+
"repos.promoteToGit",
|
|
92460
|
+
"repos.update",
|
|
92461
|
+
"repos.updateLabel",
|
|
92462
|
+
"repos.reorder",
|
|
92463
|
+
"repos.remove",
|
|
91727
92464
|
"hosts.list",
|
|
91728
92465
|
"hosts.forAgents",
|
|
91729
92466
|
"cronjobs.list",
|
|
@@ -91736,7 +92473,13 @@ var RELAY_PROCEDURES = /* @__PURE__ */ new Set([
|
|
|
91736
92473
|
"worktrees.create",
|
|
91737
92474
|
"worktrees.remove"
|
|
91738
92475
|
]);
|
|
91739
|
-
var UNBATCHABLE = /* @__PURE__ */ new Set([
|
|
92476
|
+
var UNBATCHABLE = /* @__PURE__ */ new Set([
|
|
92477
|
+
"repos.list",
|
|
92478
|
+
"hosts.list",
|
|
92479
|
+
"worktrees.create",
|
|
92480
|
+
"worktrees.remove",
|
|
92481
|
+
"statuses.notify"
|
|
92482
|
+
]);
|
|
91740
92483
|
function pinHost(input, workerId, keep) {
|
|
91741
92484
|
const base = input !== null && typeof input === "object" ? input : {};
|
|
91742
92485
|
return keep && typeof base.hostId === "string" ? base : { ...base, hostId: workerId };
|
|
@@ -91749,7 +92492,7 @@ function pinWorktreeHost(request, workerId) {
|
|
|
91749
92492
|
} catch {
|
|
91750
92493
|
return request;
|
|
91751
92494
|
}
|
|
91752
|
-
const isTrpc = call === "/trpc/worktrees.create" || call === "/trpc/worktrees.remove";
|
|
92495
|
+
const isTrpc = call === "/trpc/worktrees.create" || call === "/trpc/worktrees.remove" || call === "/trpc/statuses.notify";
|
|
91753
92496
|
const isMcp = call === "/mcp";
|
|
91754
92497
|
if (!isTrpc && !isMcp) return request;
|
|
91755
92498
|
const parsed = parseJson(bodyText(request.body));
|
|
@@ -91903,7 +92646,7 @@ function checkNamed(input, workerId, lookups) {
|
|
|
91903
92646
|
if (worktree === null || lookups.hostOfWorktree(worktree) !== workerId) return outside;
|
|
91904
92647
|
}
|
|
91905
92648
|
for (const cwd of named.cwds) {
|
|
91906
|
-
const worktree = lookups.worktreeOfCwd(path.resolve(cwd));
|
|
92649
|
+
const worktree = lookups.worktreeOfCwd(path.resolve(cwd), workerId);
|
|
91907
92650
|
if (worktree === null || lookups.hostOfWorktree(worktree) !== workerId) return outside;
|
|
91908
92651
|
}
|
|
91909
92652
|
for (const id of named.terminals) {
|
|
@@ -91929,8 +92672,40 @@ function checkChosenId(procedure, input, lookups) {
|
|
|
91929
92672
|
}
|
|
91930
92673
|
return { ok: true };
|
|
91931
92674
|
}
|
|
92675
|
+
var REPO_BY_NAME_PROCEDURES = /* @__PURE__ */ new Set([
|
|
92676
|
+
"repos.update",
|
|
92677
|
+
"repos.updateLabel",
|
|
92678
|
+
"repos.remove",
|
|
92679
|
+
"repos.promoteToGit"
|
|
92680
|
+
]);
|
|
92681
|
+
function checkRepoCall(procedure, input, workerId, lookups) {
|
|
92682
|
+
const record3 = input ?? {};
|
|
92683
|
+
if (REPO_BY_NAME_PROCEDURES.has(procedure)) {
|
|
92684
|
+
if (typeof record3.name !== "string") return deny(400, `${procedure} needs a repo name`);
|
|
92685
|
+
const refusal = lookups.repoHostRefusal(record3.name, workerId);
|
|
92686
|
+
return refusal === null ? { ok: true } : deny(403, `${procedure}: ${refusal}`);
|
|
92687
|
+
}
|
|
92688
|
+
if (procedure === "repos.addFromWorker") {
|
|
92689
|
+
const { hostId: hostId2, name: name4, allowedHostIds } = record3;
|
|
92690
|
+
if (typeof hostId2 !== "string") return deny(400, `${procedure} needs a hostId`);
|
|
92691
|
+
if (Array.isArray(allowedHostIds) && !allowedHostIds.includes(hostId2)) {
|
|
92692
|
+
return deny(
|
|
92693
|
+
403,
|
|
92694
|
+
`${procedure}: The repo would be limited to hosts that exclude ${hostId2}, where the folder is`
|
|
92695
|
+
);
|
|
92696
|
+
}
|
|
92697
|
+
if (typeof name4 === "string") {
|
|
92698
|
+
const refusal = lookups.repoHostRefusal(name4, workerId);
|
|
92699
|
+
if (refusal !== null) return deny(403, `${procedure}: ${refusal}`);
|
|
92700
|
+
}
|
|
92701
|
+
}
|
|
92702
|
+
return { ok: true };
|
|
92703
|
+
}
|
|
91932
92704
|
function checkCall(procedure, input, workerId, lookups) {
|
|
91933
92705
|
if (!RELAY_PROCEDURES.has(procedure)) return deny(403, `${procedure} is not available to agents`);
|
|
92706
|
+
if (procedure.startsWith("repos.") && procedure !== "repos.list") {
|
|
92707
|
+
return checkRepoCall(procedure, input, workerId, lookups);
|
|
92708
|
+
}
|
|
91934
92709
|
let named = input;
|
|
91935
92710
|
if (CRONJOB_KEY_PROCEDURES.has(procedure)) {
|
|
91936
92711
|
const { key: key2, ...rest } = input ?? {};
|
|
@@ -94768,7 +95543,7 @@ init_src();
|
|
|
94768
95543
|
init_zod();
|
|
94769
95544
|
init_registry();
|
|
94770
95545
|
init_queued_message_store();
|
|
94771
|
-
import { realpathSync as
|
|
95546
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
94772
95547
|
import { join as join49, resolve as resolve11, sep as sep7 } from "node:path";
|
|
94773
95548
|
init_chat_service();
|
|
94774
95549
|
init_state();
|
|
@@ -94786,8 +95561,8 @@ function isPathWithinUploadDir(p2) {
|
|
|
94786
95561
|
return false;
|
|
94787
95562
|
}
|
|
94788
95563
|
try {
|
|
94789
|
-
const canonicalUploadDir =
|
|
94790
|
-
const canonicalPath2 =
|
|
95564
|
+
const canonicalUploadDir = realpathSync6(uploadDir);
|
|
95565
|
+
const canonicalPath2 = realpathSync6(p2);
|
|
94791
95566
|
return canonicalPath2 === canonicalUploadDir || canonicalPath2.startsWith(canonicalUploadDir + sep7);
|
|
94792
95567
|
} catch {
|
|
94793
95568
|
return true;
|
|
@@ -94800,38 +95575,38 @@ async function resolveQueuedFiles(chatId, worktreeId4, files) {
|
|
|
94800
95575
|
const needsSave = [];
|
|
94801
95576
|
const needsSaveIdx = [];
|
|
94802
95577
|
for (let i = 0; i < files.length; i++) {
|
|
94803
|
-
const
|
|
94804
|
-
const derivedPath =
|
|
95578
|
+
const file3 = files[i];
|
|
95579
|
+
const derivedPath = file3.path ?? await uploadPathFromUrl(worktreeId4, file3.url) ?? void 0;
|
|
94805
95580
|
if (derivedPath) {
|
|
94806
95581
|
const inside = remote ? await isWithinUploads(worktreeId4, derivedPath) : isPathWithinUploadDir(derivedPath);
|
|
94807
95582
|
if (!inside) {
|
|
94808
95583
|
log57.warn(
|
|
94809
|
-
{ chatId, path: derivedPath, filename:
|
|
95584
|
+
{ chatId, path: derivedPath, filename: file3.filename },
|
|
94810
95585
|
"queue: dropping file with path outside uploads directory"
|
|
94811
95586
|
);
|
|
94812
95587
|
continue;
|
|
94813
95588
|
}
|
|
94814
95589
|
resolved.push({
|
|
94815
|
-
mediaType:
|
|
94816
|
-
url:
|
|
95590
|
+
mediaType: file3.mediaType,
|
|
95591
|
+
url: file3.url,
|
|
94817
95592
|
path: derivedPath,
|
|
94818
|
-
...
|
|
95593
|
+
...file3.filename !== void 0 && { filename: file3.filename }
|
|
94819
95594
|
});
|
|
94820
95595
|
continue;
|
|
94821
95596
|
}
|
|
94822
|
-
if (
|
|
94823
|
-
needsSave.push(
|
|
95597
|
+
if (file3.url.startsWith("data:")) {
|
|
95598
|
+
needsSave.push(file3);
|
|
94824
95599
|
needsSaveIdx.push(resolved.length);
|
|
94825
95600
|
resolved.push({
|
|
94826
|
-
mediaType:
|
|
94827
|
-
url:
|
|
95601
|
+
mediaType: file3.mediaType,
|
|
95602
|
+
url: file3.url,
|
|
94828
95603
|
path: "",
|
|
94829
|
-
filename:
|
|
95604
|
+
filename: file3.filename
|
|
94830
95605
|
});
|
|
94831
95606
|
continue;
|
|
94832
95607
|
}
|
|
94833
95608
|
log57.warn(
|
|
94834
|
-
{ chatId, url:
|
|
95609
|
+
{ chatId, url: file3.url, filename: file3.filename },
|
|
94835
95610
|
"queue: dropping file with no path and non-data URL \u2014 cannot recover disk path"
|
|
94836
95611
|
);
|
|
94837
95612
|
}
|
|
@@ -95153,7 +95928,7 @@ var UsageScannerService = class {
|
|
|
95153
95928
|
async scanPair(ws, a) {
|
|
95154
95929
|
let reader;
|
|
95155
95930
|
try {
|
|
95156
|
-
reader = await this.getUsageReader(a);
|
|
95931
|
+
reader = await this.getUsageReader(a, ws.hostId);
|
|
95157
95932
|
} catch (err) {
|
|
95158
95933
|
log58.debug({ err, agentId: a.agentId }, "failed to resolve usage reader for scan");
|
|
95159
95934
|
return 0;
|
|
@@ -95184,6 +95959,7 @@ var UsageScannerService = class {
|
|
|
95184
95959
|
{ err, sessionId: s2.sessionId, agentType: a.agentType },
|
|
95185
95960
|
"getSessionUsage failed"
|
|
95186
95961
|
);
|
|
95962
|
+
if (err instanceof Error && err.name === "HostOfflineError") break;
|
|
95187
95963
|
maxObserved = Math.max(maxObserved, s2.lastModified);
|
|
95188
95964
|
continue;
|
|
95189
95965
|
}
|
|
@@ -95239,7 +96015,8 @@ function defaultListWorktrees() {
|
|
|
95239
96015
|
out.push({
|
|
95240
96016
|
worktreeId: toWorktreeId(repo.name, worktree.name, worktree.hostId),
|
|
95241
96017
|
repo: repo.name,
|
|
95242
|
-
worktreePath: worktree.path
|
|
96018
|
+
worktreePath: worktree.path,
|
|
96019
|
+
hostId: worktree.hostId
|
|
95243
96020
|
});
|
|
95244
96021
|
}
|
|
95245
96022
|
}
|
|
@@ -95254,8 +96031,9 @@ function defaultListAgents() {
|
|
|
95254
96031
|
command: def.command
|
|
95255
96032
|
}));
|
|
95256
96033
|
}
|
|
95257
|
-
function defaultGetUsageReader(agent) {
|
|
95258
|
-
|
|
96034
|
+
function defaultGetUsageReader(agent, hostId2) {
|
|
96035
|
+
const host = hostId2 ? hostRegistry.hostById(hostId2) : hostRegistry.local;
|
|
96036
|
+
return host.agentEnv.usageReader(agent);
|
|
95259
96037
|
}
|
|
95260
96038
|
function defaultIsPollingEnabled() {
|
|
95261
96039
|
try {
|
|
@@ -96220,7 +96998,6 @@ var settingsRouter = t.router({
|
|
|
96220
96998
|
init_zod();
|
|
96221
96999
|
init_branch_status_poller();
|
|
96222
97000
|
init_state();
|
|
96223
|
-
init_task_service();
|
|
96224
97001
|
init_watcher_service();
|
|
96225
97002
|
var statusesRouter = t.router({
|
|
96226
97003
|
get: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).query(({ input }) => {
|
|
@@ -96249,12 +97026,12 @@ var statusesRouter = t.router({
|
|
|
96249
97026
|
refreshed: await branchStatusPoller.refreshWorktree(input.worktreeId)
|
|
96250
97027
|
})),
|
|
96251
97028
|
clearNeedsAttention: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).mutation(({ input }) => {
|
|
96252
|
-
const status =
|
|
97029
|
+
const status = acknowledgeWorktreeAttention(input.worktreeId);
|
|
96253
97030
|
if (status) emit({ kind: "update", status });
|
|
96254
97031
|
return { ok: true };
|
|
96255
97032
|
}),
|
|
96256
|
-
resolve: publicProcedure.input(external_exports2.object({ cwd: external_exports2.string() })).query(({ input }) => {
|
|
96257
|
-
return { worktreeId: resolveWorktreeIdByCwd(input.cwd) };
|
|
97033
|
+
resolve: publicProcedure.input(external_exports2.object({ cwd: external_exports2.string(), hostId: external_exports2.string().optional() })).query(({ input }) => {
|
|
97034
|
+
return { worktreeId: resolveWorktreeIdByCwd(input.cwd, input.hostId) };
|
|
96258
97035
|
}),
|
|
96259
97036
|
/**
|
|
96260
97037
|
* Agent-agnostic entry point for coding-agent lifecycle notifications
|
|
@@ -96275,7 +97052,8 @@ var statusesRouter = t.router({
|
|
|
96275
97052
|
payload: external_exports2.record(external_exports2.string(), external_exports2.unknown()),
|
|
96276
97053
|
agent: external_exports2.string().max(64).optional(),
|
|
96277
97054
|
dispatch: external_exports2.string().max(64).optional(),
|
|
96278
|
-
terminalId: external_exports2.string().max(256).optional()
|
|
97055
|
+
terminalId: external_exports2.string().max(256).optional(),
|
|
97056
|
+
hostId: external_exports2.string().max(256).optional()
|
|
96279
97057
|
})
|
|
96280
97058
|
).mutation(async ({ input }) => {
|
|
96281
97059
|
const status = await applyHookNotification(input);
|
|
@@ -96472,6 +97250,91 @@ var subscriptionsRouter = t.router({
|
|
|
96472
97250
|
init_src();
|
|
96473
97251
|
init_dist();
|
|
96474
97252
|
init_zod();
|
|
97253
|
+
|
|
97254
|
+
// package.json
|
|
97255
|
+
var package_default = {
|
|
97256
|
+
name: "@band-app/server",
|
|
97257
|
+
version: "0.48.1",
|
|
97258
|
+
type: "module",
|
|
97259
|
+
types: "src/index.ts",
|
|
97260
|
+
exports: {
|
|
97261
|
+
".": "./src/index.ts",
|
|
97262
|
+
"./package.json": "./package.json"
|
|
97263
|
+
},
|
|
97264
|
+
bin: {
|
|
97265
|
+
"band-server": "./bin/band-server.mjs"
|
|
97266
|
+
},
|
|
97267
|
+
files: [
|
|
97268
|
+
"dist/client",
|
|
97269
|
+
"dist/start-server.mjs",
|
|
97270
|
+
"dist/terminal-daemon.mjs",
|
|
97271
|
+
"dist/openapi.json",
|
|
97272
|
+
"dist/migrations",
|
|
97273
|
+
"bin"
|
|
97274
|
+
],
|
|
97275
|
+
engines: {
|
|
97276
|
+
node: ">=22.5.0"
|
|
97277
|
+
},
|
|
97278
|
+
os: [
|
|
97279
|
+
"darwin",
|
|
97280
|
+
"linux",
|
|
97281
|
+
"win32"
|
|
97282
|
+
],
|
|
97283
|
+
scripts: {
|
|
97284
|
+
dev: "NODE_ENV=development tsx watch --exclude '../web/**' start-server.ts",
|
|
97285
|
+
build: "./scripts/build-server.sh",
|
|
97286
|
+
start: "node dist/start-server.mjs",
|
|
97287
|
+
prepack: "node scripts/copy-ui.mjs",
|
|
97288
|
+
postpack: "node scripts/copy-ui.mjs --clean",
|
|
97289
|
+
pretest: "pnpm --filter @band-app/web build && pnpm build",
|
|
97290
|
+
test: "NODE_OPTIONS='--no-warnings=ExperimentalWarning' vitest run",
|
|
97291
|
+
postinstall: `node -e "process.platform!=='win32'&&require('child_process').execSync('chmod +x node_modules/node-pty/prebuilds/*/spawn-helper 2>/dev/null || true',{stdio:'ignore'})"`
|
|
97292
|
+
},
|
|
97293
|
+
dependencies: {
|
|
97294
|
+
"@agentclientprotocol/claude-agent-acp": "0.81.2",
|
|
97295
|
+
"@agentclientprotocol/codex-acp": "1.13.1",
|
|
97296
|
+
"@agentclientprotocol/sdk": "1.5.0",
|
|
97297
|
+
"@vscode/ripgrep": "^1.18.0",
|
|
97298
|
+
fzf: "0.5.2",
|
|
97299
|
+
"node-pty": "^1.1.0",
|
|
97300
|
+
prettier: "^3.8.3",
|
|
97301
|
+
"typescript-language-server": "^5.1.3"
|
|
97302
|
+
},
|
|
97303
|
+
devDependencies: {
|
|
97304
|
+
"@band-app/coding-agent": "workspace:^",
|
|
97305
|
+
"@band-app/environment": "workspace:^",
|
|
97306
|
+
"@band-app/host-api": "workspace:^",
|
|
97307
|
+
"@band-app/host-local": "workspace:^",
|
|
97308
|
+
"@band-app/host-remote": "workspace:^",
|
|
97309
|
+
"@band-app/link": "workspace:^",
|
|
97310
|
+
"@band-app/logger": "workspace:^",
|
|
97311
|
+
"@band-app/plugin-api": "workspace:^",
|
|
97312
|
+
"@band-app/plugin-github": "workspace:^",
|
|
97313
|
+
"@band-app/shared": "workspace:^",
|
|
97314
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
97315
|
+
"@trpc/client": "^11.12.0",
|
|
97316
|
+
"@trpc/openapi": "11.16.0-alpha",
|
|
97317
|
+
"@trpc/server": "^11.12.0",
|
|
97318
|
+
"@types/express": "^5.0.6",
|
|
97319
|
+
"@types/node": "^22.19.13",
|
|
97320
|
+
"@types/ws": "^8.18.0",
|
|
97321
|
+
"@xterm/headless": "6.1.0-beta.302",
|
|
97322
|
+
croner: "^10.0.1",
|
|
97323
|
+
"drizzle-kit": "1.0.0-rc.1",
|
|
97324
|
+
"drizzle-orm": "1.0.0-rc.1",
|
|
97325
|
+
esbuild: "^0.25.0",
|
|
97326
|
+
express: "^5.2.1",
|
|
97327
|
+
sirv: "^3.0.0",
|
|
97328
|
+
tsx: "^4.21.0",
|
|
97329
|
+
typescript: "^5.7.0",
|
|
97330
|
+
vite: "^7.0.0",
|
|
97331
|
+
vitest: "^4.0.18",
|
|
97332
|
+
ws: "^8.18.0",
|
|
97333
|
+
zod: "^3.25.67"
|
|
97334
|
+
}
|
|
97335
|
+
};
|
|
97336
|
+
|
|
97337
|
+
// src/server/api/system/router.ts
|
|
96475
97338
|
init_branch_status_poller();
|
|
96476
97339
|
init_state();
|
|
96477
97340
|
init_tunnel_service();
|
|
@@ -96488,6 +97351,7 @@ var systemRouter = t.router({
|
|
|
96488
97351
|
log62.debug({ tunnel }, "services.health: tunnel status");
|
|
96489
97352
|
const result = {
|
|
96490
97353
|
webserver: true,
|
|
97354
|
+
version: package_default.version,
|
|
96491
97355
|
tunnel: tunnel.running,
|
|
96492
97356
|
tunnel_url: tunnel.url
|
|
96493
97357
|
};
|
|
@@ -97043,6 +97907,45 @@ var vaultRouter = t.router({
|
|
|
97043
97907
|
init_dist();
|
|
97044
97908
|
init_zod();
|
|
97045
97909
|
|
|
97910
|
+
// src/server/services/_utils/worktree-preview-link.ts
|
|
97911
|
+
init_state();
|
|
97912
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual8 } from "node:crypto";
|
|
97913
|
+
import { posix as posix10 } from "node:path";
|
|
97914
|
+
var WORKTREE_PREVIEW_PREFIX = "/api/worktree-preview/";
|
|
97915
|
+
var PREVIEW_TTL_MS = 60 * 60 * 1e3;
|
|
97916
|
+
function signingKey() {
|
|
97917
|
+
const token = resolveAdminToken(process.env.BAND_ADMIN_TOKEN).token;
|
|
97918
|
+
return createHmac2("sha256", token).update("worktree-preview-v1").digest();
|
|
97919
|
+
}
|
|
97920
|
+
function sign(worktreeId4, dir, expiresAt) {
|
|
97921
|
+
return createHmac2("sha256", signingKey()).update(`${worktreeId4}
|
|
97922
|
+
${dir}
|
|
97923
|
+
${expiresAt}`).digest("hex");
|
|
97924
|
+
}
|
|
97925
|
+
function worktreePreviewUrl(worktreeId4, path2, now = Date.now()) {
|
|
97926
|
+
const expiresAt = now + PREVIEW_TTL_MS;
|
|
97927
|
+
const dir = posix10.dirname(path2.replace(/^\/+/, ""));
|
|
97928
|
+
const encodedDir = Buffer.from(dir, "utf8").toString("base64url");
|
|
97929
|
+
const encodedPath = path2.split("/").map(encodeURIComponent).join("/");
|
|
97930
|
+
const grant = `${expiresAt}.${encodedDir}.${sign(worktreeId4, dir, expiresAt)}`;
|
|
97931
|
+
return `${WORKTREE_PREVIEW_PREFIX}${encodeURIComponent(worktreeId4)}/${grant}/${encodedPath}`;
|
|
97932
|
+
}
|
|
97933
|
+
function verifyWorktreePreviewGrant(worktreeId4, grant, filePath, now = Date.now()) {
|
|
97934
|
+
if (filePath.includes("\0")) return false;
|
|
97935
|
+
const [expiry, encodedDir, signature, ...extra] = grant.split(".");
|
|
97936
|
+
const expiresAt = Number(expiry);
|
|
97937
|
+
if (extra.length > 0 || encodedDir === void 0 || !signature || !Number.isSafeInteger(expiresAt) || expiresAt < now) {
|
|
97938
|
+
return false;
|
|
97939
|
+
}
|
|
97940
|
+
const dir = Buffer.from(encodedDir, "base64url").toString("utf8");
|
|
97941
|
+
const expected = Buffer.from(sign(worktreeId4, dir, expiresAt), "hex");
|
|
97942
|
+
const given = Buffer.from(signature, "hex");
|
|
97943
|
+
if (given.length !== expected.length || !timingSafeEqual8(given, expected)) return false;
|
|
97944
|
+
const target = posix10.normalize(filePath.replace(/^\/+/, ""));
|
|
97945
|
+
if (target === ".." || target.startsWith("../")) return false;
|
|
97946
|
+
return dir === "." || target === dir || target.startsWith(`${dir}/`);
|
|
97947
|
+
}
|
|
97948
|
+
|
|
97046
97949
|
// src/server/services/diff-service.ts
|
|
97047
97950
|
init_src2();
|
|
97048
97951
|
init_src();
|
|
@@ -97158,9 +98061,9 @@ function assertWorktreeRelative(cwd, filePath) {
|
|
|
97158
98061
|
}
|
|
97159
98062
|
return target;
|
|
97160
98063
|
}
|
|
97161
|
-
async function readUntrackedFileLines(host, cwd,
|
|
98064
|
+
async function readUntrackedFileLines(host, cwd, file3) {
|
|
97162
98065
|
try {
|
|
97163
|
-
const content = new TextDecoder().decode(await host.fs.readFile(join50(cwd,
|
|
98066
|
+
const content = new TextDecoder().decode(await host.fs.readFile(join50(cwd, file3)));
|
|
97164
98067
|
const lines = content.split("\n");
|
|
97165
98068
|
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
97166
98069
|
lines.pop();
|
|
@@ -97450,17 +98353,17 @@ var DiffService = class {
|
|
|
97450
98353
|
const fileStatuses = parseFileStatuses(nameStatusOutput);
|
|
97451
98354
|
const untrackedFiles = untrackedOutput.trim().split("\n").filter(Boolean);
|
|
97452
98355
|
const untrackedLines = await Promise.all(
|
|
97453
|
-
untrackedFiles.map((
|
|
98356
|
+
untrackedFiles.map((file3) => readUntrackedFileLines(worktree.host, cwd, file3))
|
|
97454
98357
|
);
|
|
97455
98358
|
for (let i = 0; i < untrackedFiles.length; i++) {
|
|
97456
|
-
const
|
|
98359
|
+
const file3 = untrackedFiles[i];
|
|
97457
98360
|
const lines = untrackedLines[i];
|
|
97458
98361
|
if (lines === null) continue;
|
|
97459
|
-
diff += `diff --git a/${
|
|
98362
|
+
diff += `diff --git a/${file3} b/${file3}
|
|
97460
98363
|
`;
|
|
97461
98364
|
diff += "new file mode 100644\n";
|
|
97462
98365
|
diff += "--- /dev/null\n";
|
|
97463
|
-
diff += `+++ b/${
|
|
98366
|
+
diff += `+++ b/${file3}
|
|
97464
98367
|
`;
|
|
97465
98368
|
diff += `@@ -0,0 +1,${lines.length} @@
|
|
97466
98369
|
`;
|
|
@@ -97468,7 +98371,7 @@ var DiffService = class {
|
|
|
97468
98371
|
diff += "\n";
|
|
97469
98372
|
stats.filesChanged++;
|
|
97470
98373
|
stats.insertions += lines.length;
|
|
97471
|
-
fileStatuses[
|
|
98374
|
+
fileStatuses[file3] = "U";
|
|
97472
98375
|
}
|
|
97473
98376
|
return {
|
|
97474
98377
|
diff,
|
|
@@ -97696,7 +98599,7 @@ var diffService = new DiffService();
|
|
|
97696
98599
|
init_errors6();
|
|
97697
98600
|
init_ephemeral_lifecycle_service();
|
|
97698
98601
|
init_worktree_service();
|
|
97699
|
-
import { dirname as
|
|
98602
|
+
import { dirname as dirname16, extname as extname3, join as join51, resolve as resolve13, sep as sep9 } from "node:path";
|
|
97700
98603
|
var MAX_FILE_SIZE2 = 1024 * 1024;
|
|
97701
98604
|
var LANG_MAP2 = {
|
|
97702
98605
|
".js": "javascript",
|
|
@@ -97837,7 +98740,7 @@ var FilesService = class {
|
|
|
97837
98740
|
if (await exists2(fs3, target)) {
|
|
97838
98741
|
throw new Error("A file or directory already exists at this path");
|
|
97839
98742
|
}
|
|
97840
|
-
await assertParentDirectory(fs3,
|
|
98743
|
+
await assertParentDirectory(fs3, dirname16(target), "Parent");
|
|
97841
98744
|
await fs3.writeFile(target, content, { exclusive: true });
|
|
97842
98745
|
return { ok: true };
|
|
97843
98746
|
}
|
|
@@ -97847,7 +98750,7 @@ var FilesService = class {
|
|
|
97847
98750
|
if (await exists2(fs3, target)) {
|
|
97848
98751
|
throw new Error("A file or directory already exists at this path");
|
|
97849
98752
|
}
|
|
97850
|
-
await assertParentDirectory(fs3,
|
|
98753
|
+
await assertParentDirectory(fs3, dirname16(target), "Parent");
|
|
97851
98754
|
await fs3.mkdir(target);
|
|
97852
98755
|
return { ok: true };
|
|
97853
98756
|
}
|
|
@@ -97889,7 +98792,7 @@ var FilesService = class {
|
|
|
97889
98792
|
if (await exists2(fs3, toTarget)) {
|
|
97890
98793
|
throw new Error("A file or directory already exists at the destination");
|
|
97891
98794
|
}
|
|
97892
|
-
await assertParentDirectory(fs3,
|
|
98795
|
+
await assertParentDirectory(fs3, dirname16(toTarget), "Destination parent");
|
|
97893
98796
|
await fs3.rename(fromTarget, toTarget);
|
|
97894
98797
|
return {
|
|
97895
98798
|
ok: true,
|
|
@@ -97922,7 +98825,7 @@ var FilesService = class {
|
|
|
97922
98825
|
if (await exists2(fs3, toTarget)) {
|
|
97923
98826
|
throw new Error("A file or directory already exists at the destination");
|
|
97924
98827
|
}
|
|
97925
|
-
await assertParentDirectory(fs3,
|
|
98828
|
+
await assertParentDirectory(fs3, dirname16(toTarget), "Destination parent");
|
|
97926
98829
|
await fs3.copy(fromTarget, toTarget, { recursive: true, exclusive: true });
|
|
97927
98830
|
return {
|
|
97928
98831
|
ok: true,
|
|
@@ -99408,6 +100311,13 @@ var worktreeRouter = t.router({
|
|
|
99408
100311
|
toPath: external_exports2.string().min(1)
|
|
99409
100312
|
})
|
|
99410
100313
|
).mutation(({ input }) => filesService.copyPath(input.worktreeId, input.fromPath, input.toPath)),
|
|
100314
|
+
/** A signed, expiring URL for one worktree file, for a browser tab that cannot send the auth header. */
|
|
100315
|
+
previewUrl: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string(), path: external_exports2.string().min(1) })).query(({ input }) => {
|
|
100316
|
+
if (!worktreeService.resolve(input.worktreeId)) {
|
|
100317
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Worktree not found" });
|
|
100318
|
+
}
|
|
100319
|
+
return { url: worktreePreviewUrl(input.worktreeId, input.path) };
|
|
100320
|
+
}),
|
|
99411
100321
|
searchFiles: publicProcedure.input(
|
|
99412
100322
|
external_exports2.object({
|
|
99413
100323
|
worktreeId: external_exports2.string(),
|
|
@@ -99491,6 +100401,13 @@ var worktreesRouter = t.router({
|
|
|
99491
100401
|
throwAsTrpcError3(err);
|
|
99492
100402
|
}
|
|
99493
100403
|
}),
|
|
100404
|
+
setOrigin: publicProcedure.input(worktreeSetOriginInput).mutation(({ input }) => {
|
|
100405
|
+
try {
|
|
100406
|
+
return worktreeService.setOrigin(input);
|
|
100407
|
+
} catch (err) {
|
|
100408
|
+
throwAsTrpcError3(err);
|
|
100409
|
+
}
|
|
100410
|
+
}),
|
|
99494
100411
|
gitPull: publicProcedure.input(worktreeGitInput).mutation(async ({ input }) => {
|
|
99495
100412
|
try {
|
|
99496
100413
|
return await worktreeService.gitPull(input);
|
|
@@ -99514,7 +100431,7 @@ var worktreesRouter = t.router({
|
|
|
99514
100431
|
})
|
|
99515
100432
|
});
|
|
99516
100433
|
function throwAsTrpcError3(err) {
|
|
99517
|
-
if (err instanceof PlainRepoError) {
|
|
100434
|
+
if (err instanceof PlainRepoError || err instanceof InvalidOriginError) {
|
|
99518
100435
|
throw new TRPCError({ code: "BAD_REQUEST", message: err.message, cause: err });
|
|
99519
100436
|
}
|
|
99520
100437
|
if (err instanceof RepoHostNotAllowedError) {
|
|
@@ -99585,12 +100502,15 @@ function discoverProcedures() {
|
|
|
99585
100502
|
for (const [path2, procedure] of Object.entries(procRecord)) {
|
|
99586
100503
|
const type = procedure._def.type;
|
|
99587
100504
|
if (type === "subscription") continue;
|
|
99588
|
-
if (path2.startsWith("tokens.") || path2.startsWith("vault.") || path2.startsWith("checkpoints.") || path2.startsWith("follow.") || path2.startsWith("mcp.") || path2.startsWith("hosts.") && path2 !== "hosts.forAgents" || path2.startsWith("hostRequests.") || path2.startsWith("runners.")
|
|
100505
|
+
if (path2.startsWith("tokens.") || path2.startsWith("vault.") || path2.startsWith("checkpoints.") || path2.startsWith("follow.") || path2.startsWith("mcp.") || path2.startsWith("hosts.") && path2 !== "hosts.forAgents" || path2.startsWith("hostRequests.") || path2.startsWith("runners.") || // Writes ~/.claude/settings.json on a host; only a person's click may.
|
|
100506
|
+
path2.startsWith("hooks."))
|
|
99589
100507
|
continue;
|
|
100508
|
+
if (path2 === "worktree.previewUrl") continue;
|
|
99590
100509
|
if (path2 === "environment.validate") continue;
|
|
99591
100510
|
if (path2 === "repos.addFromWorker") continue;
|
|
99592
100511
|
if (path2 === "repos.addByUrl") continue;
|
|
99593
100512
|
if (path2 === "repos.inspectFolder" || path2 === "repos.resolveRemote") continue;
|
|
100513
|
+
if (path2 === "worktrees.setOrigin") continue;
|
|
99594
100514
|
if (path2 === "environment.build") continue;
|
|
99595
100515
|
const toolName2 = `band_${path2.replace(/\./g, "_")}`;
|
|
99596
100516
|
const inputs = procedure._def.inputs;
|
|
@@ -99764,6 +100684,11 @@ function crossHostRefusal(repo, targetHostId, callerHostId) {
|
|
|
99764
100684
|
}
|
|
99765
100685
|
return null;
|
|
99766
100686
|
}
|
|
100687
|
+
function repoHostRefusal(repo, callerHostId) {
|
|
100688
|
+
const allowed2 = allowedHostsOf(repo);
|
|
100689
|
+
if (isHostAllowed(allowed2, callerHostId)) return null;
|
|
100690
|
+
return `${repo} is limited to other hosts (${hostNamesOf(allowed2 ?? []).join(", ")})`;
|
|
100691
|
+
}
|
|
99767
100692
|
|
|
99768
100693
|
// src/server/api/mcp-proxy/band-tools.ts
|
|
99769
100694
|
init_worktree_service();
|
|
@@ -100905,7 +101830,7 @@ async function handleOAuthCallback(req, res) {
|
|
|
100905
101830
|
}
|
|
100906
101831
|
|
|
100907
101832
|
// src/server/api/web-app-manifest.ts
|
|
100908
|
-
import { createReadStream as createReadStream4, statSync as
|
|
101833
|
+
import { createReadStream as createReadStream4, statSync as statSync14 } from "node:fs";
|
|
100909
101834
|
import { join as join52 } from "node:path";
|
|
100910
101835
|
var WEB_APP_MANIFEST_PATH = "/manifest.webmanifest";
|
|
100911
101836
|
var ICON_FILES = ["band-192.png", "band-512.png", "apple-touch-icon.png"];
|
|
@@ -100929,7 +101854,7 @@ function handleWebAppManifest(req, res, publicDir) {
|
|
|
100929
101854
|
}
|
|
100930
101855
|
function sendFile(req, res, filePath, headers) {
|
|
100931
101856
|
try {
|
|
100932
|
-
const { size } =
|
|
101857
|
+
const { size } = statSync14(filePath);
|
|
100933
101858
|
res.writeHead(200, { ...headers, "Content-Length": size.toString() });
|
|
100934
101859
|
if (req.method === "HEAD") res.end();
|
|
100935
101860
|
else
|
|
@@ -101677,31 +102602,52 @@ init_src();
|
|
|
101677
102602
|
init_registry();
|
|
101678
102603
|
async function installSkills2(opts = {}) {
|
|
101679
102604
|
const result = await hostRegistry.local.agentEnv.installSkills({ home: opts.home });
|
|
101680
|
-
const
|
|
101681
|
-
if (
|
|
101682
|
-
for (const warning of result.warnings)
|
|
101683
|
-
for (const path2 of result.written)
|
|
102605
|
+
const log78 = opts.log;
|
|
102606
|
+
if (log78) {
|
|
102607
|
+
for (const warning of result.warnings) log78.warn(warning);
|
|
102608
|
+
for (const path2 of result.written) log78.info("Installed skill at %s", path2);
|
|
101684
102609
|
for (const path2 of result.updated) {
|
|
101685
|
-
|
|
102610
|
+
log78.info("Updated skill at %s (content differed \u2014 shipped version reinstalled)", path2);
|
|
101686
102611
|
}
|
|
101687
102612
|
for (const conflict of result.conflicts) {
|
|
101688
|
-
|
|
102613
|
+
log78.warn("Skill symlink conflict \u2014 %s (left as-is; remove it manually to re-link)", conflict);
|
|
101689
102614
|
}
|
|
101690
102615
|
}
|
|
101691
102616
|
return result;
|
|
101692
102617
|
}
|
|
101693
102618
|
|
|
101694
|
-
// src/server/services/
|
|
101695
|
-
|
|
102619
|
+
// src/server/services/coding-agent-seed.ts
|
|
102620
|
+
init_src();
|
|
101696
102621
|
init_state();
|
|
101697
|
-
|
|
101698
|
-
init_worktree_service();
|
|
101699
|
-
var log73 = createLogger("setup");
|
|
102622
|
+
var log73 = createLogger("coding-agent-seed");
|
|
101700
102623
|
var AGENT_CHECKS = [
|
|
101701
102624
|
{ id: "claude-code", type: "claude-code", label: "Claude Code", binary: "claude" },
|
|
101702
102625
|
{ id: "codex", type: "codex", label: "Codex", binary: "codex" },
|
|
101703
102626
|
{ id: "opencode", type: "opencode", label: "OpenCode", binary: "opencode" }
|
|
101704
102627
|
];
|
|
102628
|
+
function seedCodingAgentsFromTypes(types) {
|
|
102629
|
+
const settings3 = loadSettings();
|
|
102630
|
+
if (Array.isArray(settings3.codingAgents) && settings3.codingAgents.length > 0) return;
|
|
102631
|
+
const seeded = AGENT_CHECKS.filter((c) => types.includes(c.type)).map(
|
|
102632
|
+
({ id, type, label: label2 }) => ({ id, type, label: label2 })
|
|
102633
|
+
);
|
|
102634
|
+
if (seeded.length === 0) return;
|
|
102635
|
+
settings3.codingAgents = seeded;
|
|
102636
|
+
if (!settings3.defaultCodingAgent) settings3.defaultCodingAgent = seeded[0].id;
|
|
102637
|
+
saveSettings(settings3);
|
|
102638
|
+
log73.info(
|
|
102639
|
+
"Enabled %d coding agent(s) from a worker; default = %s",
|
|
102640
|
+
seeded.length,
|
|
102641
|
+
settings3.defaultCodingAgent
|
|
102642
|
+
);
|
|
102643
|
+
}
|
|
102644
|
+
|
|
102645
|
+
// src/server/services/setup-service.ts
|
|
102646
|
+
init_settings_service();
|
|
102647
|
+
init_state();
|
|
102648
|
+
init_sync_service();
|
|
102649
|
+
init_worktree_service();
|
|
102650
|
+
var log74 = createLogger("setup");
|
|
101705
102651
|
async function runFirstTimeSetup() {
|
|
101706
102652
|
forgetLocalWorktrees();
|
|
101707
102653
|
const repoSync = ensureRepoStateInSync().then(() => ensureRepoRemotes());
|
|
@@ -101709,12 +102655,11 @@ async function runFirstTimeSetup() {
|
|
|
101709
102655
|
const results = await Promise.allSettled([
|
|
101710
102656
|
repoSync,
|
|
101711
102657
|
ensureSettingsDefaults(),
|
|
101712
|
-
ensureClaudeHooks(),
|
|
101713
102658
|
ensureSkillsInstalled()
|
|
101714
102659
|
]);
|
|
101715
102660
|
for (const r of results) {
|
|
101716
102661
|
if (r.status === "rejected") {
|
|
101717
|
-
|
|
102662
|
+
log74.warn(
|
|
101718
102663
|
"Setup step failed: %s",
|
|
101719
102664
|
r.reason instanceof Error ? r.reason.message : String(r.reason)
|
|
101720
102665
|
);
|
|
@@ -101728,7 +102673,7 @@ async function refreshAgentModelsInBackground() {
|
|
|
101728
102673
|
const ok = results.filter((r) => !r.error).length;
|
|
101729
102674
|
const failed = results.length - ok;
|
|
101730
102675
|
if (results.length > 0) {
|
|
101731
|
-
|
|
102676
|
+
log74.info(
|
|
101732
102677
|
"Background model refresh: %d/%d agents updated (%d failed)",
|
|
101733
102678
|
ok,
|
|
101734
102679
|
results.length,
|
|
@@ -101736,7 +102681,7 @@ async function refreshAgentModelsInBackground() {
|
|
|
101736
102681
|
);
|
|
101737
102682
|
}
|
|
101738
102683
|
} catch (err) {
|
|
101739
|
-
|
|
102684
|
+
log74.warn(
|
|
101740
102685
|
"Background model refresh failed: %s",
|
|
101741
102686
|
err instanceof Error ? err.message : String(err)
|
|
101742
102687
|
);
|
|
@@ -101751,7 +102696,7 @@ async function ensureRepoRemotes() {
|
|
|
101751
102696
|
try {
|
|
101752
102697
|
await repoService.backfillRemotes();
|
|
101753
102698
|
} catch (err) {
|
|
101754
|
-
|
|
102699
|
+
log74.warn(
|
|
101755
102700
|
"Failed to record repo remotes at boot: %s",
|
|
101756
102701
|
err instanceof Error ? err.message : String(err)
|
|
101757
102702
|
);
|
|
@@ -101761,7 +102706,7 @@ function forgetLocalWorktrees() {
|
|
|
101761
102706
|
try {
|
|
101762
102707
|
worktreeService.forgetLocalWorktrees();
|
|
101763
102708
|
} catch (err) {
|
|
101764
|
-
|
|
102709
|
+
log74.warn(
|
|
101765
102710
|
"Failed to forget local worktrees at boot: %s",
|
|
101766
102711
|
err instanceof Error ? err.message : String(err)
|
|
101767
102712
|
);
|
|
@@ -101771,7 +102716,7 @@ async function ensureRepoStateInSync() {
|
|
|
101771
102716
|
try {
|
|
101772
102717
|
await syncService.syncWorktrees();
|
|
101773
102718
|
} catch (err) {
|
|
101774
|
-
|
|
102719
|
+
log74.warn(
|
|
101775
102720
|
"Failed to sync repo state at boot: %s",
|
|
101776
102721
|
err instanceof Error ? err.message : String(err)
|
|
101777
102722
|
);
|
|
@@ -101782,22 +102727,22 @@ async function ensureCliInstalled() {
|
|
|
101782
102727
|
try {
|
|
101783
102728
|
cliStatus = await checkCli();
|
|
101784
102729
|
} catch (err) {
|
|
101785
|
-
|
|
102730
|
+
log74.warn("Could not check CLI status: %s", err instanceof Error ? err.message : String(err));
|
|
101786
102731
|
return;
|
|
101787
102732
|
}
|
|
101788
102733
|
if (cliStatus === "Installed") {
|
|
101789
102734
|
return;
|
|
101790
102735
|
}
|
|
101791
102736
|
if (cliStatus !== "NotInstalled") {
|
|
101792
|
-
|
|
102737
|
+
log74.warn("CLI not auto-installed (status: %s)", cliStatus);
|
|
101793
102738
|
return;
|
|
101794
102739
|
}
|
|
101795
|
-
|
|
102740
|
+
log74.info("Installing band CLI...");
|
|
101796
102741
|
try {
|
|
101797
102742
|
await installCli();
|
|
101798
|
-
|
|
102743
|
+
log74.info("CLI installed to %s", SYMLINK_PATH);
|
|
101799
102744
|
} catch (err) {
|
|
101800
|
-
|
|
102745
|
+
log74.warn("CLI installation failed: %s", err instanceof Error ? err.message : String(err));
|
|
101801
102746
|
}
|
|
101802
102747
|
}
|
|
101803
102748
|
async function ensureDefaultCodingAgents() {
|
|
@@ -101806,17 +102751,17 @@ async function ensureDefaultCodingAgents() {
|
|
|
101806
102751
|
if (Array.isArray(existing) && existing.length > 0) {
|
|
101807
102752
|
return;
|
|
101808
102753
|
}
|
|
101809
|
-
|
|
102754
|
+
log74.info("Detecting installed coding agents...");
|
|
101810
102755
|
const detected = [];
|
|
101811
102756
|
for (const check2 of AGENT_CHECKS) {
|
|
101812
102757
|
const path2 = await systemService.whichBinary(check2.binary);
|
|
101813
102758
|
if (path2) {
|
|
101814
|
-
|
|
102759
|
+
log74.info("Detected coding agent: %s (%s)", check2.id, path2);
|
|
101815
102760
|
detected.push({ id: check2.id, type: check2.type, label: check2.label });
|
|
101816
102761
|
}
|
|
101817
102762
|
}
|
|
101818
102763
|
if (detected.length === 0) {
|
|
101819
|
-
|
|
102764
|
+
log74.info("No coding agent CLIs detected on PATH");
|
|
101820
102765
|
return;
|
|
101821
102766
|
}
|
|
101822
102767
|
const current3 = loadSettings();
|
|
@@ -101825,7 +102770,7 @@ async function ensureDefaultCodingAgents() {
|
|
|
101825
102770
|
current3.defaultCodingAgent = detected[0].id;
|
|
101826
102771
|
}
|
|
101827
102772
|
saveSettings(current3);
|
|
101828
|
-
|
|
102773
|
+
log74.info("Enabled %d coding agent(s); default = %s", detected.length, current3.defaultCodingAgent);
|
|
101829
102774
|
}
|
|
101830
102775
|
function ensureNotificationDefaults() {
|
|
101831
102776
|
const settings3 = loadSettings();
|
|
@@ -101837,7 +102782,7 @@ function ensureNotificationDefaults() {
|
|
|
101837
102782
|
...settings3,
|
|
101838
102783
|
notifications: { ...notifications, soundOnNeedsAttention: true }
|
|
101839
102784
|
});
|
|
101840
|
-
|
|
102785
|
+
log74.info("Set default notifications.soundOnNeedsAttention = true");
|
|
101841
102786
|
}
|
|
101842
102787
|
function migrateAgentDefaultMode() {
|
|
101843
102788
|
const settings3 = loadSettings();
|
|
@@ -101845,30 +102790,15 @@ function migrateAgentDefaultMode() {
|
|
|
101845
102790
|
const mode = agentModeFromVia(settings3.cli?.defaultVia);
|
|
101846
102791
|
if (!mode) return;
|
|
101847
102792
|
saveSettings({ ...settings3, agents: { ...settings3.agents, defaultMode: mode } });
|
|
101848
|
-
|
|
101849
|
-
}
|
|
101850
|
-
async function ensureClaudeHooks() {
|
|
101851
|
-
try {
|
|
101852
|
-
const status = await checkHooks2();
|
|
101853
|
-
if (status.installed) {
|
|
101854
|
-
return;
|
|
101855
|
-
}
|
|
101856
|
-
await installHooks2();
|
|
101857
|
-
log73.info("Installed Claude Code hooks");
|
|
101858
|
-
} catch (err) {
|
|
101859
|
-
log73.warn(
|
|
101860
|
-
"Failed to install Claude Code hooks: %s",
|
|
101861
|
-
err instanceof Error ? err.message : String(err)
|
|
101862
|
-
);
|
|
101863
|
-
}
|
|
102793
|
+
log74.info("Migrated cli.defaultVia to agents.defaultMode = %s", mode);
|
|
101864
102794
|
}
|
|
101865
102795
|
async function ensureSkillsInstalled() {
|
|
101866
102796
|
try {
|
|
101867
|
-
const result = await installSkills2({ log:
|
|
102797
|
+
const result = await installSkills2({ log: log74 });
|
|
101868
102798
|
const wrote = result.written.length + result.updated.length;
|
|
101869
102799
|
const linkChange = result.linked.length;
|
|
101870
102800
|
if (wrote > 0 || linkChange > 0 || result.conflicts.length > 0) {
|
|
101871
|
-
|
|
102801
|
+
log74.info(
|
|
101872
102802
|
"Synced CLI skills (shared: %d written, %d updated, %d unchanged; symlinks: %d created, %d already-linked, %d conflicts, %d skipped)",
|
|
101873
102803
|
result.written.length,
|
|
101874
102804
|
result.updated.length,
|
|
@@ -101880,7 +102810,7 @@ async function ensureSkillsInstalled() {
|
|
|
101880
102810
|
);
|
|
101881
102811
|
}
|
|
101882
102812
|
} catch (err) {
|
|
101883
|
-
|
|
102813
|
+
log74.warn("Failed to sync CLI skills: %s", err instanceof Error ? err.message : String(err));
|
|
101884
102814
|
}
|
|
101885
102815
|
}
|
|
101886
102816
|
var SetupService = class {
|
|
@@ -101919,7 +102849,7 @@ init_src();
|
|
|
101919
102849
|
import { createHash as createHash12 } from "node:crypto";
|
|
101920
102850
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
101921
102851
|
import { join as join54 } from "node:path";
|
|
101922
|
-
var
|
|
102852
|
+
var log75 = createLogger("worker-cli");
|
|
101923
102853
|
var RPC_INVALID_PARAMS2 = -32602;
|
|
101924
102854
|
var PLATFORM_NAME = /^[a-z0-9_]{1,16}$/;
|
|
101925
102855
|
function candidatesFor(platform3, arch) {
|
|
@@ -101955,7 +102885,7 @@ var WorkerCliService = class {
|
|
|
101955
102885
|
}
|
|
101956
102886
|
const sha2562 = createHash12("sha256").update(data).digest("hex");
|
|
101957
102887
|
if (have === sha2562) return { available: true, sha256: sha2562, size: data.length };
|
|
101958
|
-
|
|
102888
|
+
log75.info(
|
|
101959
102889
|
`sending the band CLI (${platform3}-${arch}, ${data.length} bytes) to ${session.workerId}`
|
|
101960
102890
|
);
|
|
101961
102891
|
const ch = session.openChannel("cli.body", { platform: platform3, arch });
|
|
@@ -101997,7 +102927,7 @@ init_chat_service();
|
|
|
101997
102927
|
init_state();
|
|
101998
102928
|
init_subscription_service();
|
|
101999
102929
|
init_terminal_service();
|
|
102000
|
-
var
|
|
102930
|
+
var log76 = createLogger("worker-relay");
|
|
102001
102931
|
var REPLY_HEADERS2 = ["content-type", "cache-control", "mcp-session-id"];
|
|
102002
102932
|
var FORWARDED_HEADERS = [
|
|
102003
102933
|
"content-type",
|
|
@@ -102013,11 +102943,12 @@ var defaultLookups = {
|
|
|
102013
102943
|
const chat = chatService.get(id);
|
|
102014
102944
|
return chat?.worktreeId ?? null;
|
|
102015
102945
|
},
|
|
102016
|
-
worktreeOfCwd: (cwd) => resolveWorktreeIdByCwd(cwd),
|
|
102946
|
+
worktreeOfCwd: (cwd, hostId2) => resolveWorktreeIdByCwd(cwd, hostId2),
|
|
102017
102947
|
hostOfTerminal: (id) => terminalService.hostIdOf(id),
|
|
102018
102948
|
worktreeOfBrowser: (id) => browserService.get(id)?.worktreeId ?? null,
|
|
102019
102949
|
worktreeOfSubscription: (id) => subscriptionService.list().find((s2) => s2.id === id)?.worktreeId ?? null,
|
|
102020
|
-
crossHostRefusal
|
|
102950
|
+
crossHostRefusal,
|
|
102951
|
+
repoHostRefusal
|
|
102021
102952
|
};
|
|
102022
102953
|
var WorkerRelayService = class {
|
|
102023
102954
|
constructor(lookups = defaultLookups) {
|
|
@@ -102036,7 +102967,7 @@ var WorkerRelayService = class {
|
|
|
102036
102967
|
let request = parseRequest(params);
|
|
102037
102968
|
const verdict = checkRelayRequest(request, session.workerId, this.lookups);
|
|
102038
102969
|
if (!verdict.ok) {
|
|
102039
|
-
|
|
102970
|
+
log76.warn(
|
|
102040
102971
|
`refused ${request.method} ${request.path.split("?")[0]} from ${session.workerId}: ${verdict.reason}`
|
|
102041
102972
|
);
|
|
102042
102973
|
return this.answer(session, verdict.status, refusalBody(request, verdict));
|
|
@@ -102071,7 +103002,7 @@ var WorkerRelayService = class {
|
|
|
102071
103002
|
redirect: "manual"
|
|
102072
103003
|
});
|
|
102073
103004
|
} catch (err) {
|
|
102074
|
-
|
|
103005
|
+
log76.warn(`relay call failed: ${err instanceof Error ? err.message : err}`);
|
|
102075
103006
|
return this.answer(session, 502, { error: "The hub could not answer" });
|
|
102076
103007
|
}
|
|
102077
103008
|
const replyHeaders = {};
|
|
@@ -102089,7 +103020,7 @@ var WorkerRelayService = class {
|
|
|
102089
103020
|
const filtered = filterReposReply(await upstream.json(), session.workerId);
|
|
102090
103021
|
return this.answer(session, upstream.status, filtered);
|
|
102091
103022
|
} catch (err) {
|
|
102092
|
-
|
|
103023
|
+
log76.warn(`relay call failed: ${err instanceof Error ? err.message : err}`);
|
|
102093
103024
|
return this.answer(session, 502, { error: "The hub could not answer" });
|
|
102094
103025
|
}
|
|
102095
103026
|
}
|
|
@@ -102097,7 +103028,7 @@ var WorkerRelayService = class {
|
|
|
102097
103028
|
try {
|
|
102098
103029
|
return this.answer(session, upstream.status, redactHostsReply(await upstream.json()));
|
|
102099
103030
|
} catch (err) {
|
|
102100
|
-
|
|
103031
|
+
log76.warn(`relay call failed: ${err instanceof Error ? err.message : err}`);
|
|
102101
103032
|
return this.answer(session, 502, { error: "The hub could not answer" });
|
|
102102
103033
|
}
|
|
102103
103034
|
}
|
|
@@ -102150,9 +103081,9 @@ var workerRelayService = new WorkerRelayService();
|
|
|
102150
103081
|
|
|
102151
103082
|
// src/server/services/worker-link-service.ts
|
|
102152
103083
|
init_worktree_service();
|
|
102153
|
-
var
|
|
103084
|
+
var log77 = createLogger("worker-link");
|
|
102154
103085
|
var repoQueries5 = new RepoQueries();
|
|
102155
|
-
var
|
|
103086
|
+
var LOCAL_HOST_ID11 = "local";
|
|
102156
103087
|
var CAPABILITY_REFRESH_MS = 3 * 6e4;
|
|
102157
103088
|
var WORKER_CONNECT_PATH = "/api/workers/connect";
|
|
102158
103089
|
var WORKER_EXCHANGE_PATH = "/api/workers/exchange";
|
|
@@ -102195,10 +103126,10 @@ var WorkerLinkService = class {
|
|
|
102195
103126
|
/** Makes every known worker resolvable, so a worktree on an offline host still finds its host. */
|
|
102196
103127
|
start() {
|
|
102197
103128
|
for (const row of this.queries.listHosts(1e3)) {
|
|
102198
|
-
if (row.id !==
|
|
103129
|
+
if (row.id !== LOCAL_HOST_ID11) this.remoteHost(row.id);
|
|
102199
103130
|
}
|
|
102200
103131
|
for (const row of this.queries.listHosts(1e3)) {
|
|
102201
|
-
if (row.id !==
|
|
103132
|
+
if (row.id !== LOCAL_HOST_ID11 && row.status === "online") {
|
|
102202
103133
|
this.queries.setHostStatus(row.id, "offline");
|
|
102203
103134
|
}
|
|
102204
103135
|
}
|
|
@@ -102210,7 +103141,7 @@ var WorkerLinkService = class {
|
|
|
102210
103141
|
this.refreshTimer.unref();
|
|
102211
103142
|
this.unsubscribeRevoke = this.tokens.onRevoked((row) => {
|
|
102212
103143
|
if (row.kind === "worker_session" && row.hostId) {
|
|
102213
|
-
if (this.server.drop(row.hostId, "token revoked"))
|
|
103144
|
+
if (this.server.drop(row.hostId, "token revoked")) log77.info(`cut link of ${row.hostId}`);
|
|
102214
103145
|
}
|
|
102215
103146
|
});
|
|
102216
103147
|
}
|
|
@@ -102224,7 +103155,7 @@ var WorkerLinkService = class {
|
|
|
102224
103155
|
this.refreshing = true;
|
|
102225
103156
|
try {
|
|
102226
103157
|
await Promise.allSettled(
|
|
102227
|
-
this.queries.listHosts(1e3).filter((row) => row.id !==
|
|
103158
|
+
this.queries.listHosts(1e3).filter((row) => row.id !== LOCAL_HOST_ID11 && row.status === "online").map((row) => this.refreshReport(row))
|
|
102228
103159
|
);
|
|
102229
103160
|
} finally {
|
|
102230
103161
|
this.refreshing = false;
|
|
@@ -102240,7 +103171,7 @@ var WorkerLinkService = class {
|
|
|
102240
103171
|
this.queries.setHostInfo(row.id, stored);
|
|
102241
103172
|
if (changed) this.publish(row.id, "online");
|
|
102242
103173
|
} catch (err) {
|
|
102243
|
-
|
|
103174
|
+
log77.debug(
|
|
102244
103175
|
`could not refresh the report of ${row.id}: ${err instanceof Error ? err.message : err}`
|
|
102245
103176
|
);
|
|
102246
103177
|
}
|
|
@@ -102282,7 +103213,7 @@ var WorkerLinkService = class {
|
|
|
102282
103213
|
try {
|
|
102283
103214
|
info = await this.remoteHost(workerId).info();
|
|
102284
103215
|
} catch (err) {
|
|
102285
|
-
|
|
103216
|
+
log77.warn(
|
|
102286
103217
|
`could not read host info from ${workerId}: ${err instanceof Error ? err.message : err}`
|
|
102287
103218
|
);
|
|
102288
103219
|
}
|
|
@@ -102299,27 +103230,32 @@ var WorkerLinkService = class {
|
|
|
102299
103230
|
mode: hello.mode
|
|
102300
103231
|
};
|
|
102301
103232
|
this.queries.markHostOnline(workerId, at, { info: stored, version: hello.buildId });
|
|
102302
|
-
|
|
103233
|
+
log77.info(`worker ${workerId} is online`);
|
|
103234
|
+
try {
|
|
103235
|
+
seedCodingAgentsFromTypes(hello.agents);
|
|
103236
|
+
} catch (err) {
|
|
103237
|
+
log77.warn(`could not seed coding agents: ${err instanceof Error ? err.message : err}`);
|
|
103238
|
+
}
|
|
102303
103239
|
this.publish(workerId, "online");
|
|
102304
103240
|
void ephemeralLifecycleService.onConnected(session);
|
|
102305
103241
|
void followService.onHostReturned(workerId).catch((err) => {
|
|
102306
|
-
|
|
103242
|
+
log77.warn(`follow check on ${workerId}: ${err instanceof Error ? err.message : err}`);
|
|
102307
103243
|
});
|
|
102308
103244
|
placementService.onHostOnline(workerId);
|
|
102309
103245
|
void worktreeService.finishPendingRemovals(workerId).catch((err) => {
|
|
102310
|
-
|
|
103246
|
+
log77.warn(`pending removals on ${workerId}: ${err instanceof Error ? err.message : err}`);
|
|
102311
103247
|
});
|
|
102312
103248
|
}
|
|
102313
103249
|
setStatus(workerId, status, lastSeenAt) {
|
|
102314
103250
|
try {
|
|
102315
103251
|
this.queries.setHostStatus(workerId, status, lastSeenAt);
|
|
102316
103252
|
} catch (err) {
|
|
102317
|
-
|
|
103253
|
+
log77.warn(
|
|
102318
103254
|
`could not record ${workerId} as ${status}: ${err instanceof Error ? err.message : err}`
|
|
102319
103255
|
);
|
|
102320
103256
|
return;
|
|
102321
103257
|
}
|
|
102322
|
-
|
|
103258
|
+
log77.info(`worker ${workerId} is ${status}`);
|
|
102323
103259
|
this.publish(workerId, status);
|
|
102324
103260
|
}
|
|
102325
103261
|
publish(hostId2, hostStatus) {
|
|
@@ -102364,7 +103300,7 @@ var WorkerLinkService = class {
|
|
|
102364
103300
|
send2(200, out);
|
|
102365
103301
|
} catch (err) {
|
|
102366
103302
|
if (!(err instanceof TokenExchangeError)) throw err;
|
|
102367
|
-
|
|
103303
|
+
log77.warn(`bootstrap exchange refused: ${err.reason}`);
|
|
102368
103304
|
send2(401, { error: "invalid or expired token" });
|
|
102369
103305
|
}
|
|
102370
103306
|
}
|
|
@@ -102383,7 +103319,7 @@ function reportKey(report2) {
|
|
|
102383
103319
|
init_worktree_service();
|
|
102384
103320
|
function logCrash(message) {
|
|
102385
103321
|
try {
|
|
102386
|
-
|
|
103322
|
+
mkdirSync10(bandHome(), { recursive: true });
|
|
102387
103323
|
appendFileSync2(join55(bandHome(), "server.log"), message, "utf-8");
|
|
102388
103324
|
} catch {
|
|
102389
103325
|
}
|
|
@@ -102426,7 +103362,7 @@ function resolveUiDir() {
|
|
|
102426
103362
|
resolve14(SERVER_ROOT, "..", "..", "web", "dist", "client"),
|
|
102427
103363
|
join55(SERVER_ROOT, "client")
|
|
102428
103364
|
];
|
|
102429
|
-
return candidates.find((dir) =>
|
|
103365
|
+
return candidates.find((dir) => existsSync17(join55(dir, "_shell.html"))) ?? candidates[1];
|
|
102430
103366
|
}
|
|
102431
103367
|
var clientDir = resolveUiDir();
|
|
102432
103368
|
var uiRoot = process.env.BAND_UI_ROOT ? resolve14(process.env.BAND_UI_ROOT) : resolve14(SERVER_ROOT, "..", "web");
|
|
@@ -102511,7 +103447,7 @@ function serveStaticFile(res, root, subdir, rawFilename) {
|
|
|
102511
103447
|
}
|
|
102512
103448
|
const filePath = join55(root, subdir, filename);
|
|
102513
103449
|
try {
|
|
102514
|
-
const fileStat =
|
|
103450
|
+
const fileStat = statSync15(filePath);
|
|
102515
103451
|
const contentType = mimeTypeFromFilename(filename);
|
|
102516
103452
|
res.writeHead(200, {
|
|
102517
103453
|
"Content-Type": contentType,
|
|
@@ -102526,26 +103462,26 @@ function serveStaticFile(res, root, subdir, rawFilename) {
|
|
|
102526
103462
|
}
|
|
102527
103463
|
async function serveHostedFile(res, kind, worktreeId4, rawName) {
|
|
102528
103464
|
if (hostRegistry.hostFor(worktreeId4).id === hostRegistry.local.id) return false;
|
|
102529
|
-
const
|
|
102530
|
-
if (!
|
|
103465
|
+
const file3 = await openHostedFile(kind, worktreeId4, rawName);
|
|
103466
|
+
if (!file3) {
|
|
102531
103467
|
res.writeHead(404);
|
|
102532
103468
|
res.end("Not found");
|
|
102533
103469
|
return true;
|
|
102534
103470
|
}
|
|
102535
103471
|
res.writeHead(200, {
|
|
102536
103472
|
"Content-Type": mimeTypeFromFilename(basename7(decodeURIComponent(rawName))),
|
|
102537
|
-
"Content-Length":
|
|
103473
|
+
"Content-Length": file3.size.toString(),
|
|
102538
103474
|
"Cache-Control": "private, max-age=86400"
|
|
102539
103475
|
});
|
|
102540
103476
|
pipeline(
|
|
102541
|
-
Readable2.from(
|
|
103477
|
+
Readable2.from(file3.stream, { objectMode: false, highWaterMark: 64 * 1024 }),
|
|
102542
103478
|
res,
|
|
102543
103479
|
() => {
|
|
102544
103480
|
}
|
|
102545
103481
|
);
|
|
102546
103482
|
return true;
|
|
102547
103483
|
}
|
|
102548
|
-
async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
103484
|
+
async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}) {
|
|
102549
103485
|
const worktree = worktreeService.resolve(worktreeId4);
|
|
102550
103486
|
if (!worktree) {
|
|
102551
103487
|
res.writeHead(404);
|
|
@@ -102561,12 +103497,25 @@ async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
|
102561
103497
|
}
|
|
102562
103498
|
try {
|
|
102563
103499
|
const { fs: fs3 } = worktree.host;
|
|
103500
|
+
const [realRoot, realTarget] = await Promise.all([fs3.realpath(root), fs3.realpath(target)]);
|
|
103501
|
+
if (realTarget !== realRoot && !realTarget.startsWith(realRoot + sep10)) {
|
|
103502
|
+
res.writeHead(400);
|
|
103503
|
+
res.end("Bad request");
|
|
103504
|
+
return;
|
|
103505
|
+
}
|
|
102564
103506
|
const fileStat = await fs3.stat(target, { followSymlinks: true });
|
|
102565
103507
|
const contentType = mimeTypeFromFilename(basename7(target));
|
|
103508
|
+
const isHtml = contentType.startsWith("text/html");
|
|
102566
103509
|
res.writeHead(200, {
|
|
102567
103510
|
"Content-Type": contentType,
|
|
102568
103511
|
"Content-Length": fileStat.size.toString(),
|
|
102569
|
-
"Cache-Control": "private, no-cache"
|
|
103512
|
+
"Cache-Control": "private, no-cache",
|
|
103513
|
+
// HTML from a repo must not run with the hub's origin.
|
|
103514
|
+
...isHtml ? {
|
|
103515
|
+
"Content-Security-Policy": "sandbox allow-scripts",
|
|
103516
|
+
"X-Content-Type-Options": "nosniff"
|
|
103517
|
+
} : {},
|
|
103518
|
+
...extraHeaders
|
|
102570
103519
|
});
|
|
102571
103520
|
pipeline(
|
|
102572
103521
|
Readable2.from(fs3.readStream(target), { objectMode: false, highWaterMark: 64 * 1024 }),
|
|
@@ -102579,6 +103528,41 @@ async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
|
102579
103528
|
res.end("Not found");
|
|
102580
103529
|
}
|
|
102581
103530
|
}
|
|
103531
|
+
async function handleWorktreePreview(req, res) {
|
|
103532
|
+
const rest = (req.url ?? "").slice(WORKTREE_PREVIEW_PREFIX.length).split("?")[0];
|
|
103533
|
+
const [rawId, grant, ...pathParts] = rest.split("/");
|
|
103534
|
+
let wId;
|
|
103535
|
+
let filePath;
|
|
103536
|
+
try {
|
|
103537
|
+
wId = decodeURIComponent(rawId ?? "");
|
|
103538
|
+
filePath = decodeURIComponent(pathParts.join("/"));
|
|
103539
|
+
} catch {
|
|
103540
|
+
res.writeHead(400);
|
|
103541
|
+
res.end("Bad request");
|
|
103542
|
+
return;
|
|
103543
|
+
}
|
|
103544
|
+
if (!wId || !grant || !filePath) {
|
|
103545
|
+
res.writeHead(400);
|
|
103546
|
+
res.end("Bad request");
|
|
103547
|
+
return;
|
|
103548
|
+
}
|
|
103549
|
+
if (!verifyWorktreePreviewGrant(wId, grant, filePath)) {
|
|
103550
|
+
res.writeHead(403);
|
|
103551
|
+
res.end("Forbidden");
|
|
103552
|
+
return;
|
|
103553
|
+
}
|
|
103554
|
+
await serveWorktreeFile(res, wId, filePath, {
|
|
103555
|
+
// The page runs repo-controlled scripts on the hub's origin. `sandbox`
|
|
103556
|
+
// without `allow-same-origin` gives it an opaque origin, so it cannot use
|
|
103557
|
+
// the hub's cookies, storage or tRPC. Its own assets are plain GETs under
|
|
103558
|
+
// the grant, and the wildcard CORS header lets scripts fetch them.
|
|
103559
|
+
"Content-Security-Policy": "sandbox allow-scripts allow-forms allow-popups allow-modals",
|
|
103560
|
+
"X-Content-Type-Options": "nosniff",
|
|
103561
|
+
"Referrer-Policy": "no-referrer",
|
|
103562
|
+
"Access-Control-Allow-Origin": "*",
|
|
103563
|
+
"Cache-Control": "private, no-store"
|
|
103564
|
+
});
|
|
103565
|
+
}
|
|
102582
103566
|
var MAX_WEBHOOK_BODY_BYTES = 256 * 1024;
|
|
102583
103567
|
var MAX_GITHUB_HOOK_BODY_BYTES = 2 * 1024 * 1024;
|
|
102584
103568
|
async function handleGithubWebhook(req, res) {
|
|
@@ -102815,9 +103799,13 @@ async function main() {
|
|
|
102815
103799
|
await handleSubscriptionWebhook(req, res, subscriptionId);
|
|
102816
103800
|
return;
|
|
102817
103801
|
}
|
|
103802
|
+
if (req.method === "GET" && req.url?.startsWith(WORKTREE_PREVIEW_PREFIX)) {
|
|
103803
|
+
await handleWorktreePreview(req, res);
|
|
103804
|
+
return;
|
|
103805
|
+
}
|
|
102818
103806
|
if (handleAuth(req, res)) return;
|
|
102819
103807
|
if (req.url?.startsWith("/api/uploads/")) {
|
|
102820
|
-
const rest = req.url.slice("/api/uploads/".length);
|
|
103808
|
+
const rest = req.url.slice("/api/uploads/".length).split("?")[0];
|
|
102821
103809
|
const slashIdx = rest.indexOf("/");
|
|
102822
103810
|
if (slashIdx !== -1) {
|
|
102823
103811
|
let wId;
|
|
@@ -102864,7 +103852,7 @@ async function main() {
|
|
|
102864
103852
|
return;
|
|
102865
103853
|
}
|
|
102866
103854
|
if (req.url?.startsWith("/api/shared/")) {
|
|
102867
|
-
const rest = req.url.slice("/api/shared/".length);
|
|
103855
|
+
const rest = req.url.slice("/api/shared/".length).split("?")[0];
|
|
102868
103856
|
const slashIdx = rest.indexOf("/");
|
|
102869
103857
|
if (slashIdx === -1) {
|
|
102870
103858
|
res.writeHead(400);
|
|
@@ -102882,21 +103870,33 @@ async function main() {
|
|
|
102882
103870
|
return;
|
|
102883
103871
|
}
|
|
102884
103872
|
if (req.url?.startsWith("/api/worktree-file/")) {
|
|
102885
|
-
const
|
|
103873
|
+
const queryIdx = req.url.indexOf("?");
|
|
103874
|
+
const rest = req.url.slice(
|
|
103875
|
+
"/api/worktree-file/".length,
|
|
103876
|
+
queryIdx === -1 ? void 0 : queryIdx
|
|
103877
|
+
);
|
|
102886
103878
|
const slashIdx = rest.indexOf("/");
|
|
102887
103879
|
if (slashIdx === -1) {
|
|
102888
103880
|
res.writeHead(400);
|
|
102889
103881
|
res.end("Bad request");
|
|
102890
103882
|
return;
|
|
102891
103883
|
}
|
|
102892
|
-
|
|
102893
|
-
|
|
103884
|
+
let wId;
|
|
103885
|
+
let filePath;
|
|
103886
|
+
try {
|
|
103887
|
+
wId = decodeURIComponent(rest.slice(0, slashIdx));
|
|
103888
|
+
filePath = decodeURIComponent(rest.slice(slashIdx + 1));
|
|
103889
|
+
} catch {
|
|
103890
|
+
res.writeHead(400);
|
|
103891
|
+
res.end("Bad request");
|
|
103892
|
+
return;
|
|
103893
|
+
}
|
|
102894
103894
|
if (!wId || !filePath) {
|
|
102895
103895
|
res.writeHead(400);
|
|
102896
103896
|
res.end("Bad request");
|
|
102897
103897
|
return;
|
|
102898
103898
|
}
|
|
102899
|
-
await serveWorktreeFile(res, wId,
|
|
103899
|
+
await serveWorktreeFile(res, wId, filePath);
|
|
102900
103900
|
return;
|
|
102901
103901
|
}
|
|
102902
103902
|
if (req.url?.startsWith("/api/cdp/tabs")) {
|
|
@@ -103087,7 +104087,7 @@ async function main() {
|
|
|
103087
104087
|
}
|
|
103088
104088
|
});
|
|
103089
104089
|
const shellPath2 = join55(clientDir, "_shell.html");
|
|
103090
|
-
if (!
|
|
104090
|
+
if (!existsSync17(shellPath2)) {
|
|
103091
104091
|
throw new Error(
|
|
103092
104092
|
`UI build not found at ${clientDir} (no _shell.html). Run \`pnpm build\` or pass --ui-dir <path>.`
|
|
103093
104093
|
);
|