@band-app/server 0.47.0 → 0.48.0
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-DkQbk-aU.js} +1 -1
- package/dist/client/assets/{_basePickBy-D64wD5rn.js → _basePickBy-Dpp3dGrC.js} +1 -1
- package/dist/client/assets/{_baseUniq-pND9PMLH.js → _baseUniq-CL7CCXzw.js} +1 -1
- package/dist/client/assets/{arc-Bilmh-jP.js → arc-Dgc_RxlI.js} +1 -1
- package/dist/client/assets/{architectureDiagram-VXUJARFQ-KfIpeO-l.js → architectureDiagram-VXUJARFQ-CgG43jHE.js} +1 -1
- package/dist/client/assets/{blockDiagram-VD42YOAC-DtuyRI-v.js → blockDiagram-VD42YOAC-Bt7XXELu.js} +1 -1
- package/dist/client/assets/{c4Diagram-YG6GDRKO-CBuSt_Sq.js → c4Diagram-YG6GDRKO-BQwG7Gi0.js} +1 -1
- package/dist/client/assets/channel-BhaORH-2.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CWIj0g_M.js → chunk-4BX2VUAB-BwFghqiW.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-YWkCYv9O.js → chunk-55IACEB6-C7yjJLc1.js} +1 -1
- package/dist/client/assets/{chunk-B4BG7PRW-muM18MGT.js → chunk-B4BG7PRW-BaJd9PuL.js} +1 -1
- package/dist/client/assets/{chunk-DI55MBZ5-DUKyYScZ.js → chunk-DI55MBZ5-CElPgENm.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-YTfHsy8G.js → chunk-FMBD7UC4-D3qe2ywl.js} +1 -1
- package/dist/client/assets/{chunk-QN33PNHL-BdIATH5l.js → chunk-QN33PNHL-DStfzqTU.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-DqRnJnIm.js → chunk-QZHKN3VN-B1rMgEw-.js} +1 -1
- package/dist/client/assets/{chunk-TZMSLE5B-DOL-jYtj.js → chunk-TZMSLE5B-BEcRDS9V.js} +1 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-iB9V-fQK.js +1 -0
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-iB9V-fQK.js +1 -0
- package/dist/client/assets/clone-UwjnYJkb.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-Cffv5KWs.js → cose-bilkent-S5V4N54A-DQnvhn8W.js} +1 -1
- package/dist/client/assets/{dagre-6UL2VRFP-lIXclAkR.js → dagre-6UL2VRFP-Y6UFkfVj.js} +1 -1
- package/dist/client/assets/{diagram-PSM6KHXK-BBQxBoNY.js → diagram-PSM6KHXK-Cn_p_oe9.js} +1 -1
- package/dist/client/assets/{diagram-QEK2KX5R-BkC_Y-i9.js → diagram-QEK2KX5R-CWHFM00-.js} +1 -1
- package/dist/client/assets/{diagram-S2PKOQOG-Culf_egi.js → diagram-S2PKOQOG-C0TgWCYA.js} +1 -1
- package/dist/client/assets/{erDiagram-Q2GNP2WA-D8qVM8rI.js → erDiagram-Q2GNP2WA-BP1po3AK.js} +1 -1
- package/dist/client/assets/{flowDiagram-NV44I4VS-D_CW6QJO.js → flowDiagram-NV44I4VS-B5oUkhMT.js} +1 -1
- package/dist/client/assets/{ganttDiagram-JELNMOA3-DeYroA3a.js → ganttDiagram-JELNMOA3-C7DXX2RL.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-V2S2FVAM-a_sS5cSL.js → gitGraphDiagram-V2S2FVAM-Dn30v3Fe.js} +1 -1
- package/dist/client/assets/{graph-BiyE3_7j.js → graph-E5kkHB_s.js} +1 -1
- package/dist/client/assets/{highlighted-body-B3W2YXNL-HVIXx0Ob.js → highlighted-body-B3W2YXNL-B2bPrtWU.js} +1 -1
- package/dist/client/assets/{index-LKhS8iLe.js → index-A7maATW7.js} +1 -1
- package/dist/client/assets/{index-B1Qa5REm.js → index-B-miY5Sp.js} +1 -1
- package/dist/client/assets/{index-Cnvsv3TN.js → index-B2f2Xih0.js} +1 -1
- package/dist/client/assets/{index-WgCV2JjC.js → index-BScD8646.js} +1 -1
- package/dist/client/assets/{index-BKkkwDGt.js → index-BlpyLCQK.js} +1 -1
- package/dist/client/assets/{index-CMHYIEyK.js → index-BuqiLymH.js} +1 -1
- package/dist/client/assets/{index-DOFpEx7l.js → index-CVOnvTFc.js} +1 -1
- package/dist/client/assets/{index-NNpxvI8j.js → index-CYUAdSi5.js} +1 -1
- package/dist/client/assets/{index-REiX7RF3.js → index-D50A2A_K.js} +1 -1
- package/dist/client/assets/{index-CqPtl_El.js → index-D6MDOO4K.js} +1 -1
- package/dist/client/assets/{index-BuC5LY4k.js → index-DKIwN46L.js} +1 -1
- package/dist/client/assets/{index-DyvxDAAr.js → index-DOWIe80D.js} +1 -1
- package/dist/client/assets/{index-CcH0k4fG.js → index-DzUf0Wwy.js} +1 -1
- package/dist/client/assets/{infoDiagram-HS3SLOUP-BfzBdZLM.js → infoDiagram-HS3SLOUP-CoxVQgxs.js} +1 -1
- package/dist/client/assets/{journeyDiagram-XKPGCS4Q-CUsUym-8.js → journeyDiagram-XKPGCS4Q-BUPqESWB.js} +1 -1
- package/dist/client/assets/{kanban-definition-3W4ZIXB7-b54i6dX1.js → kanban-definition-3W4ZIXB7-DDooJc-Z.js} +1 -1
- package/dist/client/assets/{layout-ztlxnOA9.js → layout-DQ2tNoC7.js} +1 -1
- package/dist/client/assets/{main-CCOutd_8.js → main-BbHA32iD.js} +273 -273
- package/dist/client/assets/main-Bump8gDQ.css +1 -0
- package/dist/client/assets/{mindmap-definition-VGOIOE7T-p7poZwfk.js → mindmap-definition-VGOIOE7T-BBoouni0.js} +1 -1
- package/dist/client/assets/{pieDiagram-ADFJNKIX-DNua1MUn.js → pieDiagram-ADFJNKIX-DAO1Cf3O.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AYHSOK5B-CminxoD5.js → quadrantDiagram-AYHSOK5B-CjJp6wOV.js} +1 -1
- package/dist/client/assets/{requirementDiagram-UZGBJVZJ-D3aBt82J.js → requirementDiagram-UZGBJVZJ-CX3MPDiG.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-TZEHDZUN-B65x-vl8.js → sankeyDiagram-TZEHDZUN-Cqnsrnu8.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WL72ISMW-DswVb1N3.js → sequenceDiagram-WL72ISMW-CTJ2qJj_.js} +1 -1
- package/dist/client/assets/{stateDiagram-FKZM4ZOC-BScRlCMH.js → stateDiagram-FKZM4ZOC-Cy-jjqJZ.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-CT80STo9.js +1 -0
- package/dist/client/assets/{timeline-definition-IT6M3QCI-C5AHVri5.js → timeline-definition-IT6M3QCI-Bbb0ib0X.js} +1 -1
- package/dist/client/assets/{treemap-GDKQZRPO-B4F1SV97.js → treemap-GDKQZRPO-DUB2cMCc.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-DOkSR4Iv.js → worktree._worktreeId-CZWwGpYU.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-Da_9lTBD.js → worktree._worktreeId-DSiEfzZW.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PRI3JC2R-dZFFrnv9.js → xychartDiagram-PRI3JC2R-CI6Pnd76.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 +1503 -551
- package/dist/terminal-daemon.mjs +149 -55
- package/package.json +7 -7
- 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);
|
|
@@ -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(
|
|
@@ -25752,28 +25845,28 @@ var init_to_json_schema = __esm({
|
|
|
25752
25845
|
}
|
|
25753
25846
|
case "file": {
|
|
25754
25847
|
const json3 = _json;
|
|
25755
|
-
const
|
|
25848
|
+
const file3 = {
|
|
25756
25849
|
type: "string",
|
|
25757
25850
|
format: "binary",
|
|
25758
25851
|
contentEncoding: "binary"
|
|
25759
25852
|
};
|
|
25760
25853
|
const { minimum, maximum, mime } = schema._zod.bag;
|
|
25761
25854
|
if (minimum !== void 0)
|
|
25762
|
-
|
|
25855
|
+
file3.minLength = minimum;
|
|
25763
25856
|
if (maximum !== void 0)
|
|
25764
|
-
|
|
25857
|
+
file3.maxLength = maximum;
|
|
25765
25858
|
if (mime) {
|
|
25766
25859
|
if (mime.length === 1) {
|
|
25767
|
-
|
|
25768
|
-
Object.assign(json3,
|
|
25860
|
+
file3.contentMediaType = mime[0];
|
|
25861
|
+
Object.assign(json3, file3);
|
|
25769
25862
|
} else {
|
|
25770
25863
|
json3.anyOf = mime.map((m2) => {
|
|
25771
|
-
const mFile = { ...
|
|
25864
|
+
const mFile = { ...file3, contentMediaType: m2 };
|
|
25772
25865
|
return mFile;
|
|
25773
25866
|
});
|
|
25774
25867
|
}
|
|
25775
25868
|
} else {
|
|
25776
|
-
Object.assign(json3,
|
|
25869
|
+
Object.assign(json3, file3);
|
|
25777
25870
|
}
|
|
25778
25871
|
break;
|
|
25779
25872
|
}
|
|
@@ -26681,7 +26774,7 @@ function literal(value, params) {
|
|
|
26681
26774
|
...util_exports.normalizeParams(params)
|
|
26682
26775
|
});
|
|
26683
26776
|
}
|
|
26684
|
-
function
|
|
26777
|
+
function file2(params) {
|
|
26685
26778
|
return _file(ZodFile, params);
|
|
26686
26779
|
}
|
|
26687
26780
|
function transform(fn) {
|
|
@@ -27539,7 +27632,7 @@ __export(external_exports, {
|
|
|
27539
27632
|
emoji: () => emoji2,
|
|
27540
27633
|
endsWith: () => _endsWith,
|
|
27541
27634
|
enum: () => _enum2,
|
|
27542
|
-
file: () =>
|
|
27635
|
+
file: () => file2,
|
|
27543
27636
|
flattenError: () => flattenError,
|
|
27544
27637
|
float32: () => float32,
|
|
27545
27638
|
float64: () => float64,
|
|
@@ -40205,7 +40298,7 @@ var init_node_sqlite = __esm({
|
|
|
40205
40298
|
|
|
40206
40299
|
// ../../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
40300
|
import crypto2 from "node:crypto";
|
|
40208
|
-
import fs2, { existsSync as
|
|
40301
|
+
import fs2, { existsSync as existsSync7, readdirSync as readdirSync6 } from "node:fs";
|
|
40209
40302
|
import { join as join10 } from "node:path";
|
|
40210
40303
|
function readMigrationFiles(config2) {
|
|
40211
40304
|
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 +40307,7 @@ function readMigrationFiles(config2) {
|
|
|
40214
40307
|
const migrations = readdirSync6(migrationFolderTo).map((subdir) => ({
|
|
40215
40308
|
path: join10(migrationFolderTo, subdir, "migration.sql"),
|
|
40216
40309
|
name: subdir
|
|
40217
|
-
})).filter((it) =>
|
|
40310
|
+
})).filter((it) => existsSync7(it.path));
|
|
40218
40311
|
migrations.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
40219
40312
|
for (const migration of migrations) {
|
|
40220
40313
|
const migrationPath = migration.path;
|
|
@@ -40253,9 +40346,9 @@ var init_migrator2 = __esm({
|
|
|
40253
40346
|
|
|
40254
40347
|
// src/server/infra/db/queries/settings.ts
|
|
40255
40348
|
import { randomBytes } from "node:crypto";
|
|
40256
|
-
import { mkdirSync as
|
|
40349
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, renameSync as renameSync5, statSync as statSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
40257
40350
|
import { homedir as homedir2 } from "node:os";
|
|
40258
|
-
import { dirname as
|
|
40351
|
+
import { dirname as dirname4, join as join11 } from "node:path";
|
|
40259
40352
|
function bandHome() {
|
|
40260
40353
|
if (process.env.BAND_HOME) return process.env.BAND_HOME;
|
|
40261
40354
|
return join11(homedir2(), ".band");
|
|
@@ -40294,7 +40387,7 @@ var init_settings = __esm({
|
|
|
40294
40387
|
const filePath = settingsFile();
|
|
40295
40388
|
let mtimeMs;
|
|
40296
40389
|
try {
|
|
40297
|
-
mtimeMs =
|
|
40390
|
+
mtimeMs = statSync8(filePath).mtimeMs;
|
|
40298
40391
|
} catch {
|
|
40299
40392
|
settingsCache.delete(filePath);
|
|
40300
40393
|
return {};
|
|
@@ -40337,11 +40430,11 @@ var init_settings = __esm({
|
|
|
40337
40430
|
const merged = { ...existing, ...patch };
|
|
40338
40431
|
const data = `${JSON.stringify(merged, null, 2)}
|
|
40339
40432
|
`;
|
|
40340
|
-
const dir =
|
|
40341
|
-
|
|
40433
|
+
const dir = dirname4(filePath);
|
|
40434
|
+
mkdirSync4(dir, { recursive: true });
|
|
40342
40435
|
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
40343
40436
|
writeFileSync3(tmpPath, data, "utf-8");
|
|
40344
|
-
|
|
40437
|
+
renameSync5(tmpPath, filePath);
|
|
40345
40438
|
settingsCache.delete(filePath);
|
|
40346
40439
|
}
|
|
40347
40440
|
/**
|
|
@@ -41075,13 +41168,13 @@ var init_schema2 = __esm({
|
|
|
41075
41168
|
});
|
|
41076
41169
|
|
|
41077
41170
|
// src/server/infra/db/connection.ts
|
|
41078
|
-
import { mkdirSync as
|
|
41171
|
+
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
41079
41172
|
import { join as join12 } from "node:path";
|
|
41080
41173
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
41081
41174
|
function getDb() {
|
|
41082
41175
|
if (_db) return _db;
|
|
41083
41176
|
const home = bandHome();
|
|
41084
|
-
|
|
41177
|
+
mkdirSync5(home, { recursive: true });
|
|
41085
41178
|
const dbPath = join12(home, "band.db");
|
|
41086
41179
|
_sqlite = new DatabaseSync2(dbPath);
|
|
41087
41180
|
_sqlite.exec("PRAGMA journal_mode = WAL");
|
|
@@ -41411,16 +41504,63 @@ var init_cli_invocation = __esm({
|
|
|
41411
41504
|
});
|
|
41412
41505
|
|
|
41413
41506
|
// ../../packages/coding-agent/src/hook-status.ts
|
|
41507
|
+
function hasBackgroundWork(payload) {
|
|
41508
|
+
const crons = payload.session_crons;
|
|
41509
|
+
if (Array.isArray(crons) && crons.length > 0) return true;
|
|
41510
|
+
const tasks3 = payload.background_tasks;
|
|
41511
|
+
if (!Array.isArray(tasks3)) return false;
|
|
41512
|
+
return tasks3.some((task) => {
|
|
41513
|
+
if (typeof task !== "object" || task === null) return true;
|
|
41514
|
+
const { type, status } = task;
|
|
41515
|
+
if (typeof type === "string" && type.trim().toLowerCase() === "teammate") return false;
|
|
41516
|
+
const state2 = typeof status === "string" ? status.trim().toLowerCase() : "";
|
|
41517
|
+
return !TERMINAL_TASK_STATUSES.has(state2);
|
|
41518
|
+
});
|
|
41519
|
+
}
|
|
41414
41520
|
function mapClaudeCodeHookStatus(payload) {
|
|
41415
41521
|
const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
41416
41522
|
const toolName2 = typeof payload.tool_name === "string" ? payload.tool_name : "";
|
|
41417
|
-
|
|
41418
|
-
|
|
41523
|
+
switch (hookEvent) {
|
|
41524
|
+
case "PermissionRequest":
|
|
41525
|
+
return "question";
|
|
41526
|
+
case "PreToolUse":
|
|
41527
|
+
return CLAUDE_CODE_INTERACTIVE_TOOLS.has(toolName2) ? "question" : "working";
|
|
41528
|
+
case "Stop":
|
|
41529
|
+
return hasBackgroundWork(payload) ? "monitoring" : "done";
|
|
41530
|
+
case "StopFailure":
|
|
41531
|
+
return "failed";
|
|
41532
|
+
case "PostCompact":
|
|
41533
|
+
return payload.trigger === "manual" ? "done" : "working";
|
|
41534
|
+
default:
|
|
41535
|
+
return "working";
|
|
41419
41536
|
}
|
|
41420
|
-
|
|
41421
|
-
|
|
41537
|
+
}
|
|
41538
|
+
function foldSubagents(hold, payload, mapped) {
|
|
41539
|
+
const hookEvent = typeof payload.hook_event_name === "string" ? payload.hook_event_name : "";
|
|
41540
|
+
const agentId = typeof payload.agent_id === "string" ? payload.agent_id : "";
|
|
41541
|
+
if (hookEvent === "SubagentStart") {
|
|
41542
|
+
if (agentId) hold.running.add(agentId);
|
|
41543
|
+
return hold.deferred ? "working" : mapped;
|
|
41544
|
+
}
|
|
41545
|
+
if (hookEvent === "SubagentStop") {
|
|
41546
|
+
hold.running.delete(agentId);
|
|
41547
|
+
if (hold.running.size === 0 && hold.deferred) {
|
|
41548
|
+
const settled = hold.deferred;
|
|
41549
|
+
hold.deferred = null;
|
|
41550
|
+
return settled;
|
|
41551
|
+
}
|
|
41552
|
+
return mapped;
|
|
41553
|
+
}
|
|
41554
|
+
if ((hookEvent === "Stop" || hookEvent === "StopFailure") && !agentId) {
|
|
41555
|
+
if (hold.running.size > 0 && (mapped === "done" || mapped === "monitoring")) {
|
|
41556
|
+
hold.deferred = mapped;
|
|
41557
|
+
return "working";
|
|
41558
|
+
}
|
|
41559
|
+
hold.deferred = null;
|
|
41560
|
+
return mapped;
|
|
41422
41561
|
}
|
|
41423
|
-
|
|
41562
|
+
if (hookEvent === "UserPromptSubmit") hold.deferred = null;
|
|
41563
|
+
return mapped;
|
|
41424
41564
|
}
|
|
41425
41565
|
async function mapHookPayloadToStatus(agentType, payload) {
|
|
41426
41566
|
switch (agentType) {
|
|
@@ -41441,10 +41581,31 @@ function isHookSessionEnd(agentType, payload) {
|
|
|
41441
41581
|
function hookSessionId(payload) {
|
|
41442
41582
|
return typeof payload.session_id === "string" && payload.session_id ? payload.session_id : null;
|
|
41443
41583
|
}
|
|
41444
|
-
var CLAUDE_CODE_INTERACTIVE_TOOLS;
|
|
41584
|
+
var CLAUDE_CODE_INTERACTIVE_TOOLS, TERMINAL_TASK_STATUSES;
|
|
41445
41585
|
var init_hook_status = __esm({
|
|
41446
41586
|
"../../packages/coding-agent/src/hook-status.ts"() {
|
|
41447
41587
|
CLAUDE_CODE_INTERACTIVE_TOOLS = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
41588
|
+
TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
41589
|
+
"idle",
|
|
41590
|
+
"done",
|
|
41591
|
+
"success",
|
|
41592
|
+
"succeeded",
|
|
41593
|
+
"complete",
|
|
41594
|
+
"completed",
|
|
41595
|
+
"finished",
|
|
41596
|
+
"failed",
|
|
41597
|
+
"error",
|
|
41598
|
+
"terminated",
|
|
41599
|
+
"exited",
|
|
41600
|
+
"aborted",
|
|
41601
|
+
"expired",
|
|
41602
|
+
"skipped",
|
|
41603
|
+
"crashed",
|
|
41604
|
+
"killed",
|
|
41605
|
+
"cancelled",
|
|
41606
|
+
"canceled",
|
|
41607
|
+
"timed_out"
|
|
41608
|
+
]);
|
|
41448
41609
|
}
|
|
41449
41610
|
});
|
|
41450
41611
|
|
|
@@ -41510,8 +41671,8 @@ var init_pricing = __esm({
|
|
|
41510
41671
|
// ../../packages/coding-agent/src/usage/read-lines.ts
|
|
41511
41672
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
41512
41673
|
import { createInterface } from "node:readline";
|
|
41513
|
-
async function* readLines(
|
|
41514
|
-
const input = createReadStream2(
|
|
41674
|
+
async function* readLines(file3) {
|
|
41675
|
+
const input = createReadStream2(file3);
|
|
41515
41676
|
const rl = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
41516
41677
|
try {
|
|
41517
41678
|
for await (const line2 of rl) yield line2;
|
|
@@ -41568,10 +41729,10 @@ async function repoDirsFor(canonical) {
|
|
|
41568
41729
|
}
|
|
41569
41730
|
return dirs;
|
|
41570
41731
|
}
|
|
41571
|
-
async function readSessionCwd(
|
|
41732
|
+
async function readSessionCwd(file3) {
|
|
41572
41733
|
let handle;
|
|
41573
41734
|
try {
|
|
41574
|
-
handle = await open(
|
|
41735
|
+
handle = await open(file3, "r");
|
|
41575
41736
|
const buf = Buffer.alloc(SESSION_HEAD_BYTES);
|
|
41576
41737
|
const { bytesRead } = await handle.read(buf, 0, SESSION_HEAD_BYTES, 0);
|
|
41577
41738
|
if (bytesRead === 0) return null;
|
|
@@ -41617,13 +41778,13 @@ async function listSessions(dir) {
|
|
|
41617
41778
|
if (!name4.endsWith(".jsonl")) return;
|
|
41618
41779
|
const sessionId = name4.slice(0, -".jsonl".length);
|
|
41619
41780
|
if (!SESSION_ID_RE.test(sessionId)) return;
|
|
41620
|
-
const
|
|
41621
|
-
const cwd = await readSessionCwd(
|
|
41781
|
+
const file3 = join14(repoDir, name4);
|
|
41782
|
+
const cwd = await readSessionCwd(file3);
|
|
41622
41783
|
if (cwd === null) return;
|
|
41623
41784
|
if (cwd !== void 0 && cwd !== dir && cwd !== canonical) return;
|
|
41624
41785
|
let lastModified;
|
|
41625
41786
|
try {
|
|
41626
|
-
lastModified = (await stat(
|
|
41787
|
+
lastModified = (await stat(file3)).mtimeMs;
|
|
41627
41788
|
} catch {
|
|
41628
41789
|
return;
|
|
41629
41790
|
}
|
|
@@ -41637,9 +41798,9 @@ async function listSessions(dir) {
|
|
|
41637
41798
|
}
|
|
41638
41799
|
async function findSessionFile(sessionId, dir) {
|
|
41639
41800
|
for (const repoDir of await repoDirsFor(await canonicalDir(dir))) {
|
|
41640
|
-
const
|
|
41801
|
+
const file3 = join14(repoDir, `${sessionId}.jsonl`);
|
|
41641
41802
|
try {
|
|
41642
|
-
if ((await stat(
|
|
41803
|
+
if ((await stat(file3)).size > 0) return file3;
|
|
41643
41804
|
} catch {
|
|
41644
41805
|
}
|
|
41645
41806
|
}
|
|
@@ -41647,15 +41808,15 @@ async function findSessionFile(sessionId, dir) {
|
|
|
41647
41808
|
}
|
|
41648
41809
|
async function getSessionUsage(sessionId, dir) {
|
|
41649
41810
|
if (!SESSION_ID_RE.test(sessionId)) return null;
|
|
41650
|
-
const
|
|
41651
|
-
if (!
|
|
41811
|
+
const file3 = await findSessionFile(sessionId, dir);
|
|
41812
|
+
if (!file3) return null;
|
|
41652
41813
|
const turns = [];
|
|
41653
41814
|
const turnByMessageId = /* @__PURE__ */ new Map();
|
|
41654
41815
|
let conversationRecords = 0;
|
|
41655
41816
|
let startedAt = Number.POSITIVE_INFINITY;
|
|
41656
41817
|
let updatedAt = 0;
|
|
41657
41818
|
let modelFallback = "";
|
|
41658
|
-
for await (const line2 of readLines(
|
|
41819
|
+
for await (const line2 of readLines(file3)) {
|
|
41659
41820
|
if (!line2.trim()) continue;
|
|
41660
41821
|
let record3;
|
|
41661
41822
|
try {
|
|
@@ -41768,9 +41929,9 @@ async function findSessionFiles() {
|
|
|
41768
41929
|
}
|
|
41769
41930
|
return results;
|
|
41770
41931
|
}
|
|
41771
|
-
async function readSessionMeta(
|
|
41932
|
+
async function readSessionMeta(file3) {
|
|
41772
41933
|
let first;
|
|
41773
|
-
for await (const line2 of readLines(
|
|
41934
|
+
for await (const line2 of readLines(file3)) {
|
|
41774
41935
|
if (!line2.trim()) continue;
|
|
41775
41936
|
first = line2;
|
|
41776
41937
|
break;
|
|
@@ -41782,27 +41943,27 @@ async function readSessionMeta(file2) {
|
|
|
41782
41943
|
}
|
|
41783
41944
|
return void 0;
|
|
41784
41945
|
}
|
|
41785
|
-
async function getSessionMeta(
|
|
41786
|
-
const cached2 = metaCache.get(
|
|
41787
|
-
if (cached2 && cached2.mtimeMs ===
|
|
41946
|
+
async function getSessionMeta(file3) {
|
|
41947
|
+
const cached2 = metaCache.get(file3.path);
|
|
41948
|
+
if (cached2 && cached2.mtimeMs === file3.mtimeMs) return cached2.meta;
|
|
41788
41949
|
try {
|
|
41789
|
-
const meta = await readSessionMeta(
|
|
41790
|
-
metaCache.set(
|
|
41950
|
+
const meta = await readSessionMeta(file3.path);
|
|
41951
|
+
metaCache.set(file3.path, { mtimeMs: file3.mtimeMs, meta });
|
|
41791
41952
|
return meta;
|
|
41792
41953
|
} catch (err) {
|
|
41793
|
-
metaCache.set(
|
|
41954
|
+
metaCache.set(file3.path, { mtimeMs: file3.mtimeMs, meta: void 0 });
|
|
41794
41955
|
throw err;
|
|
41795
41956
|
}
|
|
41796
41957
|
}
|
|
41797
41958
|
async function listSessions2(dir) {
|
|
41798
41959
|
const sessions = [];
|
|
41799
|
-
for (const
|
|
41960
|
+
for (const file3 of await findSessionFiles()) {
|
|
41800
41961
|
try {
|
|
41801
|
-
const meta = await getSessionMeta(
|
|
41962
|
+
const meta = await getSessionMeta(file3);
|
|
41802
41963
|
if (!meta || meta.cwd !== dir) continue;
|
|
41803
|
-
sessions.push({ sessionId: meta.id, lastModified:
|
|
41964
|
+
sessions.push({ sessionId: meta.id, lastModified: file3.mtimeMs });
|
|
41804
41965
|
} catch (err) {
|
|
41805
|
-
log7.debug({ err, file:
|
|
41966
|
+
log7.debug({ err, file: file3.path }, "failed to parse codex session file");
|
|
41806
41967
|
}
|
|
41807
41968
|
}
|
|
41808
41969
|
return sessions.sort((a, b2) => b2.lastModified - a.lastModified);
|
|
@@ -42064,10 +42225,10 @@ var init_src3 = __esm({
|
|
|
42064
42225
|
});
|
|
42065
42226
|
|
|
42066
42227
|
// ../../packages/host-local/src/agents/acp-launch.ts
|
|
42067
|
-
import { accessSync, constants, existsSync as
|
|
42228
|
+
import { accessSync, constants, existsSync as existsSync8, statSync as statSync9 } from "node:fs";
|
|
42068
42229
|
import { createRequire } from "node:module";
|
|
42069
42230
|
import { homedir as homedir6 } from "node:os";
|
|
42070
|
-
import { delimiter as delimiter2, dirname as
|
|
42231
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join16, resolve as resolvePath } from "node:path";
|
|
42071
42232
|
import { fileURLToPath } from "node:url";
|
|
42072
42233
|
function findOnPath(name4, path2, extra = []) {
|
|
42073
42234
|
for (const dir of [...path2.split(delimiter2), ...extra]) {
|
|
@@ -42075,24 +42236,24 @@ function findOnPath(name4, path2, extra = []) {
|
|
|
42075
42236
|
const candidate = join16(dir, name4);
|
|
42076
42237
|
try {
|
|
42077
42238
|
accessSync(candidate, constants.X_OK);
|
|
42078
|
-
if (
|
|
42239
|
+
if (statSync9(candidate).isFile()) return candidate;
|
|
42079
42240
|
} catch {
|
|
42080
42241
|
}
|
|
42081
42242
|
}
|
|
42082
42243
|
return null;
|
|
42083
42244
|
}
|
|
42084
42245
|
function adapterScript(pkg, bundledName) {
|
|
42085
|
-
const bundled = join16(
|
|
42086
|
-
if (
|
|
42087
|
-
return join16(
|
|
42246
|
+
const bundled = join16(dirname5(fileURLToPath(import.meta.url)), "agents", `${bundledName}.mjs`);
|
|
42247
|
+
if (existsSync8(bundled)) return bundled;
|
|
42248
|
+
return join16(dirname5(require2.resolve(`${pkg}/package.json`)), "dist", "index.js");
|
|
42088
42249
|
}
|
|
42089
42250
|
function nodeFor(path2) {
|
|
42090
42251
|
return findOnPath("node", path2) ?? process.execPath;
|
|
42091
42252
|
}
|
|
42092
|
-
function isExecutableFile(
|
|
42253
|
+
function isExecutableFile(file3) {
|
|
42093
42254
|
try {
|
|
42094
|
-
accessSync(
|
|
42095
|
-
return
|
|
42255
|
+
accessSync(file3, constants.X_OK);
|
|
42256
|
+
return statSync9(file3).isFile();
|
|
42096
42257
|
} catch {
|
|
42097
42258
|
return false;
|
|
42098
42259
|
}
|
|
@@ -42165,7 +42326,7 @@ var init_acp_launch = __esm({
|
|
|
42165
42326
|
|
|
42166
42327
|
// ../../packages/host-local/src/agents/claude-defaults.ts
|
|
42167
42328
|
import { execFile as execFile3 } from "node:child_process";
|
|
42168
|
-
import { closeSync as
|
|
42329
|
+
import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync6, readSync, realpathSync as realpathSync2, statSync as statSync10 } from "node:fs";
|
|
42169
42330
|
import { homedir as homedir7 } from "node:os";
|
|
42170
42331
|
import { join as join17, resolve as resolvePath2 } from "node:path";
|
|
42171
42332
|
function claudeConfigDir2(env) {
|
|
@@ -42258,16 +42419,16 @@ function transcriptPath(env, cwd, sessionId) {
|
|
|
42258
42419
|
function readTail(path2, bytes) {
|
|
42259
42420
|
let fd;
|
|
42260
42421
|
try {
|
|
42261
|
-
const size =
|
|
42422
|
+
const size = statSync10(path2).size;
|
|
42262
42423
|
const length = Math.min(size, bytes);
|
|
42263
42424
|
const buf = Buffer.alloc(length);
|
|
42264
|
-
fd =
|
|
42425
|
+
fd = openSync3(path2, "r");
|
|
42265
42426
|
readSync(fd, buf, 0, length, size - length);
|
|
42266
42427
|
return buf.toString("utf8");
|
|
42267
42428
|
} catch {
|
|
42268
42429
|
return null;
|
|
42269
42430
|
} finally {
|
|
42270
|
-
if (fd !== void 0)
|
|
42431
|
+
if (fd !== void 0) closeSync3(fd);
|
|
42271
42432
|
}
|
|
42272
42433
|
}
|
|
42273
42434
|
function reportedClaudeDefaults(opts) {
|
|
@@ -42368,113 +42529,225 @@ var init_claude_defaults = __esm({
|
|
|
42368
42529
|
});
|
|
42369
42530
|
|
|
42370
42531
|
// ../../packages/host-local/src/agents/hooks-install.ts
|
|
42371
|
-
import {
|
|
42532
|
+
import {
|
|
42533
|
+
mkdirSync as mkdirSync6,
|
|
42534
|
+
readFileSync as readFileSync7,
|
|
42535
|
+
realpathSync as realpathSync3,
|
|
42536
|
+
renameSync as renameSync6,
|
|
42537
|
+
statSync as statSync11,
|
|
42538
|
+
unlinkSync as unlinkSync4,
|
|
42539
|
+
writeFileSync as writeFileSync4
|
|
42540
|
+
} from "node:fs";
|
|
42372
42541
|
import { homedir as homedir8 } from "node:os";
|
|
42373
|
-
import { dirname as
|
|
42542
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
42374
42543
|
function claudeSettingsPath() {
|
|
42375
42544
|
return join18(homedir8(), ".claude", "settings.json");
|
|
42376
42545
|
}
|
|
42377
42546
|
function loadClaudeSettings() {
|
|
42547
|
+
let text3;
|
|
42378
42548
|
try {
|
|
42379
|
-
|
|
42380
|
-
|
|
42549
|
+
text3 = readFileSync7(claudeSettingsPath(), "utf-8");
|
|
42550
|
+
} catch (err) {
|
|
42551
|
+
if (err.code === "ENOENT") {
|
|
42552
|
+
return { settings: {}, indent: 2, trailingNewline: false };
|
|
42553
|
+
}
|
|
42554
|
+
throw err;
|
|
42555
|
+
}
|
|
42556
|
+
if (text3.trim() === "") return { settings: {}, indent: 2, trailingNewline: false };
|
|
42557
|
+
let parsed;
|
|
42558
|
+
try {
|
|
42559
|
+
parsed = JSON.parse(text3);
|
|
42560
|
+
} catch {
|
|
42561
|
+
throw new Error(`${claudeSettingsPath()} is not valid JSON; fix it before installing hooks.`);
|
|
42562
|
+
}
|
|
42563
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42564
|
+
throw new Error(
|
|
42565
|
+
`${claudeSettingsPath()} is not a JSON object; fix it before installing hooks.`
|
|
42566
|
+
);
|
|
42567
|
+
}
|
|
42568
|
+
const indentMatch = /^([ \t]+)\S/m.exec(text3);
|
|
42569
|
+
return {
|
|
42570
|
+
settings: parsed,
|
|
42571
|
+
indent: indentMatch ? indentMatch[1] : 2,
|
|
42572
|
+
trailingNewline: text3.endsWith("\n")
|
|
42573
|
+
};
|
|
42574
|
+
}
|
|
42575
|
+
function saveClaudeSettings({ settings: settings3, indent, trailingNewline }) {
|
|
42576
|
+
let path2 = claudeSettingsPath();
|
|
42577
|
+
mkdirSync6(dirname6(path2), { recursive: true });
|
|
42578
|
+
let mode = 384;
|
|
42579
|
+
try {
|
|
42580
|
+
path2 = realpathSync3(path2);
|
|
42581
|
+
mode = statSync11(path2).mode & 511;
|
|
42381
42582
|
} catch {
|
|
42382
|
-
|
|
42583
|
+
}
|
|
42584
|
+
const temp = `${path2}.band-${process.pid}-${Date.now()}.tmp`;
|
|
42585
|
+
try {
|
|
42586
|
+
writeFileSync4(temp, JSON.stringify(settings3, null, indent) + (trailingNewline ? "\n" : ""), {
|
|
42587
|
+
encoding: "utf-8",
|
|
42588
|
+
mode,
|
|
42589
|
+
flag: "wx"
|
|
42590
|
+
});
|
|
42591
|
+
renameSync6(temp, path2);
|
|
42592
|
+
} catch (err) {
|
|
42593
|
+
try {
|
|
42594
|
+
unlinkSync4(temp);
|
|
42595
|
+
} catch {
|
|
42596
|
+
}
|
|
42597
|
+
throw err;
|
|
42383
42598
|
}
|
|
42384
42599
|
}
|
|
42385
42600
|
function isBandHook(command2) {
|
|
42386
|
-
return
|
|
42601
|
+
return BAND_NOTIFY.test(command2);
|
|
42387
42602
|
}
|
|
42388
|
-
|
|
42389
|
-
const
|
|
42603
|
+
function isBandEntry(entry) {
|
|
42604
|
+
const hooks = entry?.hooks;
|
|
42605
|
+
return Array.isArray(hooks) && hooks.some(
|
|
42606
|
+
(h) => h?.type === "command" && typeof h.command === "string" && isBandHook(h.command)
|
|
42607
|
+
);
|
|
42608
|
+
}
|
|
42609
|
+
function isCurrentCommand(hook) {
|
|
42610
|
+
const command2 = hook.command ?? "";
|
|
42611
|
+
return command2.includes("--agent claude-code") && command2.includes(">/dev/null 2>&1") && command2.trimEnd().endsWith("|| true") && hook.async === true;
|
|
42612
|
+
}
|
|
42613
|
+
function hooksOf(settings3) {
|
|
42390
42614
|
const hooks = settings3.hooks;
|
|
42391
|
-
|
|
42392
|
-
|
|
42615
|
+
return hooks !== null && typeof hooks === "object" && !Array.isArray(hooks) ? hooks : {};
|
|
42616
|
+
}
|
|
42617
|
+
async function checkHooks() {
|
|
42618
|
+
let settings3;
|
|
42619
|
+
try {
|
|
42620
|
+
settings3 = loadClaudeSettings().settings;
|
|
42621
|
+
} catch {
|
|
42622
|
+
return statusOf(0, 0, false);
|
|
42393
42623
|
}
|
|
42394
|
-
|
|
42624
|
+
const hooks = hooksOf(settings3);
|
|
42625
|
+
let current3 = 0;
|
|
42626
|
+
let bandEvents = 0;
|
|
42395
42627
|
let otherHookCount = 0;
|
|
42396
|
-
for (const [, eventHooks] of Object.entries(hooks)) {
|
|
42628
|
+
for (const [event, eventHooks] of Object.entries(hooks)) {
|
|
42397
42629
|
if (!Array.isArray(eventHooks)) continue;
|
|
42398
|
-
|
|
42399
|
-
|
|
42400
|
-
|
|
42401
|
-
|
|
42402
|
-
|
|
42403
|
-
|
|
42404
|
-
|
|
42405
|
-
|
|
42406
|
-
|
|
42407
|
-
|
|
42630
|
+
let eventHasBand = false;
|
|
42631
|
+
let eventIsCurrent = false;
|
|
42632
|
+
for (const entry of eventHooks) {
|
|
42633
|
+
const inner = entry?.hooks;
|
|
42634
|
+
if (!Array.isArray(inner)) continue;
|
|
42635
|
+
for (const h of inner) {
|
|
42636
|
+
if (h?.type !== "command" || typeof h.command !== "string") continue;
|
|
42637
|
+
if (isBandHook(h.command)) {
|
|
42638
|
+
eventHasBand = true;
|
|
42639
|
+
if (HOOK_EVENTS.includes(event) && isCurrentCommand(h)) eventIsCurrent = true;
|
|
42640
|
+
} else {
|
|
42641
|
+
otherHookCount++;
|
|
42408
42642
|
}
|
|
42409
42643
|
}
|
|
42410
42644
|
}
|
|
42645
|
+
if (eventHasBand) bandEvents++;
|
|
42646
|
+
if (eventIsCurrent) current3++;
|
|
42411
42647
|
}
|
|
42648
|
+
return statusOf(bandEvents, current3, otherHookCount > 0);
|
|
42649
|
+
}
|
|
42650
|
+
function statusOf(bandEvents, currentEvents, otherHooksExist) {
|
|
42651
|
+
const installed = currentEvents >= HOOK_EVENTS.length && bandEvents === currentEvents;
|
|
42412
42652
|
return {
|
|
42413
|
-
installed
|
|
42414
|
-
|
|
42653
|
+
installed,
|
|
42654
|
+
outdated: !installed && bandEvents > 0,
|
|
42655
|
+
other_hooks_exist: otherHooksExist,
|
|
42656
|
+
version: HOOKS_VERSION
|
|
42415
42657
|
};
|
|
42416
42658
|
}
|
|
42417
|
-
|
|
42418
|
-
|
|
42659
|
+
function hookCommand(bandPath) {
|
|
42660
|
+
return `"${bandPath}" notify --agent claude-code >/dev/null 2>&1 || true`;
|
|
42661
|
+
}
|
|
42662
|
+
async function findBand() {
|
|
42419
42663
|
if (process.platform !== "win32") {
|
|
42420
42664
|
try {
|
|
42421
42665
|
await import("node:fs/promises").then((m2) => m2.stat("/usr/local/bin/band"));
|
|
42422
|
-
|
|
42666
|
+
return "/usr/local/bin/band";
|
|
42423
42667
|
} catch {
|
|
42424
42668
|
}
|
|
42425
42669
|
}
|
|
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 || {};
|
|
42670
|
+
const found = await whichBinary("band");
|
|
42671
|
+
if (!found) throw new Error("Could not find band CLI binary. Please install it first.");
|
|
42672
|
+
return found;
|
|
42673
|
+
}
|
|
42674
|
+
function withoutBandEntries(hooks) {
|
|
42675
|
+
const result = {};
|
|
42436
42676
|
for (const [event, eventHooks] of Object.entries(hooks)) {
|
|
42437
|
-
if (
|
|
42438
|
-
|
|
42439
|
-
|
|
42677
|
+
if (!Array.isArray(eventHooks)) {
|
|
42678
|
+
result[event] = eventHooks;
|
|
42679
|
+
continue;
|
|
42680
|
+
}
|
|
42681
|
+
const kept = [];
|
|
42682
|
+
for (const entry of eventHooks) {
|
|
42683
|
+
if (!isBandEntry(entry)) {
|
|
42684
|
+
kept.push(entry);
|
|
42685
|
+
continue;
|
|
42686
|
+
}
|
|
42440
42687
|
const e = entry;
|
|
42441
|
-
|
|
42442
|
-
|
|
42443
|
-
|
|
42444
|
-
|
|
42445
|
-
hooks[event] = filtered;
|
|
42446
|
-
} else {
|
|
42447
|
-
delete hooks[event];
|
|
42688
|
+
const rest = (e.hooks ?? []).filter(
|
|
42689
|
+
(h) => !(h?.type === "command" && typeof h.command === "string" && isBandHook(h.command))
|
|
42690
|
+
);
|
|
42691
|
+
if (rest.length > 0) kept.push({ ...e, hooks: rest });
|
|
42448
42692
|
}
|
|
42693
|
+
if (kept.length > 0 || eventHooks.length === 0) result[event] = kept;
|
|
42449
42694
|
}
|
|
42695
|
+
return result;
|
|
42696
|
+
}
|
|
42697
|
+
async function installHooks() {
|
|
42698
|
+
const bandPath = await findBand();
|
|
42699
|
+
const loaded = loadClaudeSettings();
|
|
42700
|
+
const hooks = withoutBandEntries(hooksOf(loaded.settings));
|
|
42450
42701
|
for (const event of HOOK_EVENTS) {
|
|
42451
|
-
const existing = hooks[event]
|
|
42452
|
-
|
|
42453
|
-
|
|
42454
|
-
|
|
42455
|
-
|
|
42456
|
-
|
|
42457
|
-
|
|
42458
|
-
|
|
42459
|
-
|
|
42460
|
-
|
|
42702
|
+
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
42703
|
+
hooks[event] = [
|
|
42704
|
+
...existing,
|
|
42705
|
+
{
|
|
42706
|
+
hooks: [
|
|
42707
|
+
{
|
|
42708
|
+
type: "command",
|
|
42709
|
+
command: hookCommand(bandPath),
|
|
42710
|
+
async: true,
|
|
42711
|
+
timeout: HOOK_TIMEOUT_SECONDS
|
|
42712
|
+
}
|
|
42713
|
+
]
|
|
42714
|
+
}
|
|
42715
|
+
];
|
|
42461
42716
|
}
|
|
42462
|
-
|
|
42463
|
-
|
|
42717
|
+
loaded.settings.hooks = hooks;
|
|
42718
|
+
saveClaudeSettings(loaded);
|
|
42719
|
+
}
|
|
42720
|
+
async function removeHooks() {
|
|
42721
|
+
const loaded = loadClaudeSettings();
|
|
42722
|
+
if (!("hooks" in loaded.settings)) return;
|
|
42723
|
+
const before = hooksOf(loaded.settings);
|
|
42724
|
+
const after = withoutBandEntries(before);
|
|
42725
|
+
if (JSON.stringify(after) === JSON.stringify(before)) return;
|
|
42726
|
+
if (Object.keys(after).length > 0) loaded.settings.hooks = after;
|
|
42727
|
+
else delete loaded.settings.hooks;
|
|
42728
|
+
saveClaudeSettings(loaded);
|
|
42464
42729
|
}
|
|
42465
|
-
var HOOK_EVENTS;
|
|
42730
|
+
var HOOK_EVENTS, HOOKS_VERSION, HOOK_TIMEOUT_SECONDS, BAND_NOTIFY;
|
|
42466
42731
|
var init_hooks_install = __esm({
|
|
42467
42732
|
"../../packages/host-local/src/agents/hooks-install.ts"() {
|
|
42468
42733
|
"use strict";
|
|
42469
42734
|
init_path();
|
|
42470
42735
|
HOOK_EVENTS = [
|
|
42471
|
-
"PreToolUse",
|
|
42472
|
-
"PermissionRequest",
|
|
42473
42736
|
"UserPromptSubmit",
|
|
42737
|
+
"PreToolUse",
|
|
42474
42738
|
"PostToolUse",
|
|
42739
|
+
"PostToolUseFailure",
|
|
42740
|
+
"PermissionRequest",
|
|
42475
42741
|
"Stop",
|
|
42742
|
+
"StopFailure",
|
|
42743
|
+
"SubagentStart",
|
|
42744
|
+
"SubagentStop",
|
|
42745
|
+
"PostCompact",
|
|
42476
42746
|
"SessionEnd"
|
|
42477
42747
|
];
|
|
42748
|
+
HOOKS_VERSION = 2;
|
|
42749
|
+
HOOK_TIMEOUT_SECONDS = 5;
|
|
42750
|
+
BAND_NOTIFY = /(^|[\s"'/\\])band(\.exe|\.cmd)?["']?\s+notify(\s|$)/;
|
|
42478
42751
|
}
|
|
42479
42752
|
});
|
|
42480
42753
|
|
|
@@ -42553,11 +42826,11 @@ var init_mcp_stdio = __esm({
|
|
|
42553
42826
|
});
|
|
42554
42827
|
|
|
42555
42828
|
// ../../packages/host-local/src/process/cli-binary.ts
|
|
42556
|
-
import { lstatSync as lstatSync3, realpathSync as
|
|
42829
|
+
import { lstatSync as lstatSync3, realpathSync as realpathSync4 } from "node:fs";
|
|
42557
42830
|
import { platform } from "node:os";
|
|
42558
42831
|
import { delimiter as delimiter3, join as join19, resolve as resolve4 } from "node:path";
|
|
42559
42832
|
function findCliBinaryAt(opts) {
|
|
42560
|
-
const { cwd, dirname:
|
|
42833
|
+
const { cwd, dirname: dirname17 } = opts;
|
|
42561
42834
|
const exe = platform() === "win32" ? "band.exe" : "band";
|
|
42562
42835
|
const explicit = process.env.BAND_CLI_PATH;
|
|
42563
42836
|
if (explicit) {
|
|
@@ -42573,7 +42846,7 @@ function findCliBinaryAt(opts) {
|
|
|
42573
42846
|
// cwd = repo root (fallback)
|
|
42574
42847
|
resolve4(cwd, "apps"),
|
|
42575
42848
|
// From this source file in dev (packages/host-local/src/process/ → apps/)
|
|
42576
|
-
resolve4(
|
|
42849
|
+
resolve4(dirname17, "..", "..", "..", "..", "apps"),
|
|
42577
42850
|
// From bundled `dist/` file (<Resources>/web/dist/ → <Resources>/) only
|
|
42578
42851
|
// — in dev mode this resolves to `packages/`, which has no
|
|
42579
42852
|
// `cli/target/<profile>/band` and is harmless; the walk above is the
|
|
@@ -42581,7 +42854,7 @@ function findCliBinaryAt(opts) {
|
|
|
42581
42854
|
// layout under <Resources>/cli/ would still resolve. Today's Electron
|
|
42582
42855
|
// bundle ships the binary under `binaries/` (handled by Strategy B),
|
|
42583
42856
|
// so this strategy never hits in production either.
|
|
42584
|
-
resolve4(
|
|
42857
|
+
resolve4(dirname17, "..", "..", "..")
|
|
42585
42858
|
];
|
|
42586
42859
|
for (const appsDir of appsStrategies) {
|
|
42587
42860
|
for (const profile of ["release", "debug"]) {
|
|
@@ -42598,7 +42871,7 @@ function findCliBinaryAt(opts) {
|
|
|
42598
42871
|
resolve4(cwd, "..", "binaries", exe),
|
|
42599
42872
|
// From the bundled dist file (<Resources>/web/dist/start-server.mjs)
|
|
42600
42873
|
// → <Resources>/binaries/band
|
|
42601
|
-
resolve4(
|
|
42874
|
+
resolve4(dirname17, "..", "..", "binaries", exe)
|
|
42602
42875
|
];
|
|
42603
42876
|
for (const p2 of electronCandidates) {
|
|
42604
42877
|
try {
|
|
@@ -42610,7 +42883,7 @@ function findCliBinaryAt(opts) {
|
|
|
42610
42883
|
for (const dir of (process.env.PATH ?? "").split(delimiter3)) {
|
|
42611
42884
|
if (!dir) continue;
|
|
42612
42885
|
try {
|
|
42613
|
-
return
|
|
42886
|
+
return realpathSync4(join19(dir, exe));
|
|
42614
42887
|
} catch {
|
|
42615
42888
|
}
|
|
42616
42889
|
}
|
|
@@ -42627,21 +42900,21 @@ var init_cli_binary = __esm({
|
|
|
42627
42900
|
|
|
42628
42901
|
// ../../packages/host-local/src/agents/skills-install.ts
|
|
42629
42902
|
import { execFile as execFile4 } from "node:child_process";
|
|
42630
|
-
import { statSync as
|
|
42903
|
+
import { statSync as statSync12 } from "node:fs";
|
|
42631
42904
|
import { homedir as homedir10 } from "node:os";
|
|
42632
42905
|
import { join as join20 } from "node:path";
|
|
42633
42906
|
async function findBandBinary() {
|
|
42634
42907
|
const override = process.env.BAND_CLI_BIN;
|
|
42635
42908
|
if (override) {
|
|
42636
42909
|
try {
|
|
42637
|
-
|
|
42910
|
+
statSync12(override);
|
|
42638
42911
|
return override;
|
|
42639
42912
|
} catch {
|
|
42640
42913
|
}
|
|
42641
42914
|
}
|
|
42642
42915
|
if (process.platform !== "win32") {
|
|
42643
42916
|
try {
|
|
42644
|
-
|
|
42917
|
+
statSync12("/usr/local/bin/band");
|
|
42645
42918
|
return "/usr/local/bin/band";
|
|
42646
42919
|
} catch {
|
|
42647
42920
|
}
|
|
@@ -42743,7 +43016,7 @@ var init_skills_install = __esm({
|
|
|
42743
43016
|
|
|
42744
43017
|
// ../../packages/host-local/src/browser/chromium.ts
|
|
42745
43018
|
import { spawn as spawn2 } from "node:child_process";
|
|
42746
|
-
import { closeSync as
|
|
43019
|
+
import { closeSync as closeSync4, existsSync as existsSync9, openSync as openSync4, readdirSync as readdirSync7 } from "node:fs";
|
|
42747
43020
|
import { mkdir as mkdir2, readFile as readFile2, rm } from "node:fs/promises";
|
|
42748
43021
|
import { homedir as homedir11 } from "node:os";
|
|
42749
43022
|
import { join as join21 } from "node:path";
|
|
@@ -42752,7 +43025,7 @@ function playwrightCandidates() {
|
|
|
42752
43025
|
process.env.PLAYWRIGHT_BROWSERS_PATH,
|
|
42753
43026
|
join21(homedir11(), "Library/Caches/ms-playwright"),
|
|
42754
43027
|
join21(homedir11(), ".cache/ms-playwright")
|
|
42755
|
-
].filter((root) => !!root &&
|
|
43028
|
+
].filter((root) => !!root && existsSync9(root));
|
|
42756
43029
|
const found = [];
|
|
42757
43030
|
for (const root of roots) {
|
|
42758
43031
|
const dirs = readdirSync7(root).filter((d) => /^chromium-\d+$/.test(d)).sort().reverse();
|
|
@@ -42782,7 +43055,7 @@ function playwrightCandidates() {
|
|
|
42782
43055
|
}
|
|
42783
43056
|
function findChromium() {
|
|
42784
43057
|
const fromEnv = process.env.BAND_CHROMIUM_BIN;
|
|
42785
|
-
if (fromEnv) return
|
|
43058
|
+
if (fromEnv) return existsSync9(fromEnv) ? fromEnv : void 0;
|
|
42786
43059
|
const candidates = [
|
|
42787
43060
|
"/usr/bin/chromium",
|
|
42788
43061
|
"/usr/bin/chromium-browser",
|
|
@@ -42792,10 +43065,10 @@ function findChromium() {
|
|
|
42792
43065
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
42793
43066
|
...playwrightCandidates()
|
|
42794
43067
|
];
|
|
42795
|
-
return candidates.find((c) =>
|
|
43068
|
+
return candidates.find((c) => existsSync9(c));
|
|
42796
43069
|
}
|
|
42797
43070
|
async function readDevToolsPort(profileDir, proc, logPath, timeoutMs) {
|
|
42798
|
-
const
|
|
43071
|
+
const file3 = join21(profileDir, "DevToolsActivePort");
|
|
42799
43072
|
const deadline = Date.now() + timeoutMs;
|
|
42800
43073
|
while (Date.now() < deadline) {
|
|
42801
43074
|
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
@@ -42805,7 +43078,7 @@ async function readDevToolsPort(profileDir, proc, logPath, timeoutMs) {
|
|
|
42805
43078
|
throw new Error("Chromium hit a fatal error at startup");
|
|
42806
43079
|
}
|
|
42807
43080
|
try {
|
|
42808
|
-
const [port, path2] = (await readFile2(
|
|
43081
|
+
const [port, path2] = (await readFile2(file3, "utf8")).split("\n");
|
|
42809
43082
|
if (port && path2) return { port: Number(port), path: path2.trim() };
|
|
42810
43083
|
} catch {
|
|
42811
43084
|
}
|
|
@@ -42955,13 +43228,13 @@ var init_chromium = __esm({
|
|
|
42955
43228
|
if (attempt > 1) {
|
|
42956
43229
|
await Promise.all(SINGLETON_FILES.map((f2) => rm(join21(profileDir, f2), { force: true })));
|
|
42957
43230
|
}
|
|
42958
|
-
const logFd =
|
|
43231
|
+
const logFd = openSync4(logPath, "w", 384);
|
|
42959
43232
|
const proc = spawn2(bin, args, {
|
|
42960
43233
|
stdio: ["ignore", "ignore", logFd],
|
|
42961
43234
|
detached: true,
|
|
42962
43235
|
env: { ...env, HOME: home }
|
|
42963
43236
|
});
|
|
42964
|
-
|
|
43237
|
+
closeSync4(logFd);
|
|
42965
43238
|
const exited = new Promise((resolve15) => {
|
|
42966
43239
|
proc.once("exit", () => resolve15());
|
|
42967
43240
|
proc.once("error", () => resolve15());
|
|
@@ -43380,7 +43653,7 @@ var init_git_client = __esm({
|
|
|
43380
43653
|
|
|
43381
43654
|
// ../../packages/host-local/src/lsp/lsp-manager.ts
|
|
43382
43655
|
import { spawn as spawn3 } from "node:child_process";
|
|
43383
|
-
import { dirname as
|
|
43656
|
+
import { dirname as dirname7, join as join24, resolve as resolve5 } from "node:path";
|
|
43384
43657
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
43385
43658
|
function toServerId(worktreeId4, lang) {
|
|
43386
43659
|
return `${worktreeId4}:${lang}`;
|
|
@@ -43505,7 +43778,7 @@ var init_lsp_manager = __esm({
|
|
|
43505
43778
|
"use strict";
|
|
43506
43779
|
init_src();
|
|
43507
43780
|
init_path();
|
|
43508
|
-
__dirname2 =
|
|
43781
|
+
__dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
43509
43782
|
log11 = createLogger("lsp");
|
|
43510
43783
|
LANG_SERVER_CONFIG = {
|
|
43511
43784
|
typescript: { command: "typescript-language-server", args: ["--stdio"] }
|
|
@@ -47803,27 +48076,27 @@ function parseEnvironment(text3) {
|
|
|
47803
48076
|
if (parsed.success) return { ok: true, environment: parsed.data };
|
|
47804
48077
|
return { ok: false, issues: zodIssues(parsed.error) };
|
|
47805
48078
|
}
|
|
47806
|
-
function repoRelative(
|
|
47807
|
-
if (posix.isAbsolute(
|
|
47808
|
-
const normal = posix.normalize(
|
|
48079
|
+
function repoRelative(file3) {
|
|
48080
|
+
if (posix.isAbsolute(file3) || /^[A-Za-z]:[\\/]/.test(file3)) return null;
|
|
48081
|
+
const normal = posix.normalize(file3.replaceAll("\\", "/"));
|
|
47809
48082
|
if (normal === ".." || normal.startsWith("../")) return null;
|
|
47810
48083
|
return normal;
|
|
47811
48084
|
}
|
|
47812
48085
|
async function checkReferences(environment, exists3) {
|
|
47813
48086
|
const issues = [];
|
|
47814
48087
|
for (const key2 of ["devcontainer", "dockerfile"]) {
|
|
47815
|
-
const
|
|
47816
|
-
if (
|
|
47817
|
-
const relative2 = repoRelative(
|
|
48088
|
+
const file3 = environment.build?.[key2];
|
|
48089
|
+
if (file3 === void 0) continue;
|
|
48090
|
+
const relative2 = repoRelative(file3);
|
|
47818
48091
|
if (relative2 === null) {
|
|
47819
48092
|
issues.push({
|
|
47820
48093
|
path: `build.${key2}`,
|
|
47821
|
-
message: `"${
|
|
48094
|
+
message: `"${file3}" must be a path inside the repository`
|
|
47822
48095
|
});
|
|
47823
48096
|
} else if (!await exists3(relative2)) {
|
|
47824
48097
|
issues.push({
|
|
47825
48098
|
path: `build.${key2}`,
|
|
47826
|
-
message: `file "${
|
|
48099
|
+
message: `file "${file3}" does not exist in the repository`
|
|
47827
48100
|
});
|
|
47828
48101
|
}
|
|
47829
48102
|
}
|
|
@@ -47912,9 +48185,9 @@ function keyInputs(environment) {
|
|
|
47912
48185
|
const inputs = [{ path: ENVIRONMENT_FILE, required: true }];
|
|
47913
48186
|
const build = environment.build;
|
|
47914
48187
|
for (const key2 of ["devcontainer", "dockerfile"]) {
|
|
47915
|
-
const
|
|
47916
|
-
if (
|
|
47917
|
-
const relative2 = repoRelative(
|
|
48188
|
+
const file3 = build?.[key2];
|
|
48189
|
+
if (file3 === void 0) continue;
|
|
48190
|
+
const relative2 = repoRelative(file3);
|
|
47918
48191
|
if (relative2 === null) continue;
|
|
47919
48192
|
inputs.push({ path: relative2, required: true });
|
|
47920
48193
|
const dir = posix2.dirname(relative2);
|
|
@@ -48463,10 +48736,10 @@ var init_remote_url = __esm({
|
|
|
48463
48736
|
// ../../packages/host-local/src/repos/host-repos.ts
|
|
48464
48737
|
import { execFile as execFile10 } from "node:child_process";
|
|
48465
48738
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
|
|
48466
|
-
import { existsSync as
|
|
48739
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
48467
48740
|
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
48741
|
import { homedir as homedir13 } from "node:os";
|
|
48469
|
-
import { dirname as
|
|
48742
|
+
import { dirname as dirname8, join as join27, sep as sep3 } from "node:path";
|
|
48470
48743
|
function defaultReposDir() {
|
|
48471
48744
|
return process.env.BAND_REPOS_DIR?.trim() || join27(homedir13(), "band", "repos");
|
|
48472
48745
|
}
|
|
@@ -48517,10 +48790,10 @@ async function browseFolder(path2) {
|
|
|
48517
48790
|
let isDir = d.isDirectory();
|
|
48518
48791
|
if (d.isSymbolicLink()) isDir = await isDirectory(full);
|
|
48519
48792
|
if (!isDir) continue;
|
|
48520
|
-
entries.push({ name: d.name, path: full, isGit:
|
|
48793
|
+
entries.push({ name: d.name, path: full, isGit: existsSync10(join27(full, ".git")) });
|
|
48521
48794
|
}
|
|
48522
48795
|
entries.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
48523
|
-
const parent =
|
|
48796
|
+
const parent = dirname8(target);
|
|
48524
48797
|
return {
|
|
48525
48798
|
path: target,
|
|
48526
48799
|
parent: parent === target ? null : parent,
|
|
@@ -48556,13 +48829,13 @@ var init_host_repos = __esm({
|
|
|
48556
48829
|
return this.cache;
|
|
48557
48830
|
}
|
|
48558
48831
|
save(mappings) {
|
|
48559
|
-
const
|
|
48832
|
+
const file3 = this.options.mappingsFile();
|
|
48560
48833
|
this.writing = this.writing.then(async () => {
|
|
48561
|
-
await mkdir3(
|
|
48562
|
-
const tmp = `${
|
|
48834
|
+
await mkdir3(dirname8(file3), { recursive: true, mode: 448 });
|
|
48835
|
+
const tmp = `${file3}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
48563
48836
|
await writeFile2(tmp, `${JSON.stringify(mappings, null, 2)}
|
|
48564
48837
|
`, { mode: 384 });
|
|
48565
|
-
await rename2(tmp,
|
|
48838
|
+
await rename2(tmp, file3);
|
|
48566
48839
|
});
|
|
48567
48840
|
return this.writing;
|
|
48568
48841
|
}
|
|
@@ -48619,12 +48892,12 @@ var init_host_repos = __esm({
|
|
|
48619
48892
|
async doEnsure(key2, url2, owner, name4) {
|
|
48620
48893
|
const mappings = await this.load();
|
|
48621
48894
|
const mapped = mappings[key2];
|
|
48622
|
-
if (mapped &&
|
|
48895
|
+
if (mapped && existsSync10(mapped.path) && await isDirectory(mapped.path)) {
|
|
48623
48896
|
return { path: mapped.path, cloned: false };
|
|
48624
48897
|
}
|
|
48625
48898
|
const base = join27(this.options.reposDir(), owner, name4);
|
|
48626
48899
|
let dest = base;
|
|
48627
|
-
if (
|
|
48900
|
+
if (existsSync10(dest)) {
|
|
48628
48901
|
const origin = await git(["remote", "get-url", "origin"], dest);
|
|
48629
48902
|
const there = origin ? parseRemoteUrl(origin)?.key : void 0;
|
|
48630
48903
|
if (there === key2) {
|
|
@@ -48634,7 +48907,7 @@ var init_host_repos = __esm({
|
|
|
48634
48907
|
const empty = (await readdir4(dest).catch(() => ["x"])).length === 0;
|
|
48635
48908
|
if (!empty) dest = `${base}-${createHash3("sha256").update(key2).digest("hex").slice(0, 6)}`;
|
|
48636
48909
|
}
|
|
48637
|
-
await mkdir3(
|
|
48910
|
+
await mkdir3(dirname8(dest), { recursive: true });
|
|
48638
48911
|
await clone2(url2, dest);
|
|
48639
48912
|
const canonical = await realpath2(dest);
|
|
48640
48913
|
await this.map(url2, canonical);
|
|
@@ -48696,9 +48969,9 @@ function createIterator(options) {
|
|
|
48696
48969
|
if (!rawFile || typeof lineNumber !== "number" || rawContent === void 0) {
|
|
48697
48970
|
continue;
|
|
48698
48971
|
}
|
|
48699
|
-
const
|
|
48972
|
+
const file3 = rawFile.startsWith("./") ? rawFile.slice(2) : rawFile;
|
|
48700
48973
|
const content = rawContent.endsWith("\n") ? rawContent.slice(0, -1) : rawContent;
|
|
48701
|
-
matches2.push({ file:
|
|
48974
|
+
matches2.push({ file: file3, line: lineNumber, content });
|
|
48702
48975
|
}
|
|
48703
48976
|
signal();
|
|
48704
48977
|
});
|
|
@@ -48804,15 +49077,15 @@ async function loadRepoConfig(host, worktreePath, repoPath) {
|
|
|
48804
49077
|
}
|
|
48805
49078
|
async function loadEnvironment(host, worktreePath, repoPath) {
|
|
48806
49079
|
for (const base of [worktreePath, repoPath]) {
|
|
48807
|
-
const
|
|
48808
|
-
const text3 = await readConfig(host,
|
|
49080
|
+
const file3 = join28(base, ENVIRONMENT_FILE);
|
|
49081
|
+
const text3 = await readConfig(host, file3);
|
|
48809
49082
|
if (text3 === null) continue;
|
|
48810
49083
|
const exists3 = (relative2) => host.fs.stat(join28(base, relative2)).then(
|
|
48811
49084
|
() => true,
|
|
48812
49085
|
() => false
|
|
48813
49086
|
);
|
|
48814
49087
|
const result = await validateEnvironment(text3, exists3);
|
|
48815
|
-
return result.ok ? { source:
|
|
49088
|
+
return result.ok ? { source: file3, environment: result.environment, issues: [] } : { source: file3, environment: null, issues: result.issues };
|
|
48816
49089
|
}
|
|
48817
49090
|
return { source: null, environment: null, issues: [] };
|
|
48818
49091
|
}
|
|
@@ -48949,7 +49222,7 @@ var init_script_run = __esm({
|
|
|
48949
49222
|
});
|
|
48950
49223
|
|
|
48951
49224
|
// ../../packages/host-local/src/setup/worktree-files.ts
|
|
48952
|
-
import { dirname as
|
|
49225
|
+
import { dirname as dirname9, isAbsolute, join as join30, relative, resolve as resolve6, sep as sep4 } from "node:path";
|
|
48953
49226
|
async function copyWorktreeFiles(host, repoPath, worktreePath) {
|
|
48954
49227
|
const byAbs = /* @__PURE__ */ new Map();
|
|
48955
49228
|
const missingFromConfig = [];
|
|
@@ -48992,7 +49265,7 @@ async function copyWorktreeFiles(host, repoPath, worktreePath) {
|
|
|
48992
49265
|
);
|
|
48993
49266
|
continue;
|
|
48994
49267
|
}
|
|
48995
|
-
await host.fs.mkdir(
|
|
49268
|
+
await host.fs.mkdir(dirname9(dest), { recursive: true });
|
|
48996
49269
|
await host.fs.copy(absSource, dest);
|
|
48997
49270
|
log14.debug({ source, file: rel }, "copied worktree file");
|
|
48998
49271
|
copied.push(rel);
|
|
@@ -49329,7 +49602,8 @@ var init_local_host = __esm({
|
|
|
49329
49602
|
usageReader: async (agent) => getUsageReader(agent.agentType, { command: agent.command }),
|
|
49330
49603
|
installSkills: (options) => installSkills(options),
|
|
49331
49604
|
hooksStatus: () => checkHooks(),
|
|
49332
|
-
installHooks: () => installHooks()
|
|
49605
|
+
installHooks: () => installHooks(),
|
|
49606
|
+
removeHooks: () => removeHooks()
|
|
49333
49607
|
};
|
|
49334
49608
|
get pty() {
|
|
49335
49609
|
return this.options.terminalBackend();
|
|
@@ -49505,11 +49779,11 @@ var init_src5 = __esm({
|
|
|
49505
49779
|
});
|
|
49506
49780
|
|
|
49507
49781
|
// src/server/infra/db/queries/repos.ts
|
|
49508
|
-
import { existsSync as
|
|
49782
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
49509
49783
|
import { join as join32 } from "node:path";
|
|
49510
49784
|
function reconcileKindForRepo(repo) {
|
|
49511
|
-
if (!repo.path || !
|
|
49512
|
-
const detectedKind =
|
|
49785
|
+
if (!repo.path || !existsSync11(repo.path)) return false;
|
|
49786
|
+
const detectedKind = existsSync11(join32(repo.path, ".git")) ? "git" : "plain";
|
|
49513
49787
|
if (detectedKind === repo.kind) return false;
|
|
49514
49788
|
repo.kind = detectedKind;
|
|
49515
49789
|
if (detectedKind === "plain") {
|
|
@@ -49852,12 +50126,18 @@ var init_repos = __esm({
|
|
|
49852
50126
|
* Records where a worktree was started from, or clears it with `null`. Focused UPDATE, because
|
|
49853
50127
|
* `saveAll` keeps the stored origin and never writes the one in its snapshot.
|
|
49854
50128
|
*/
|
|
49855
|
-
setOrigin(repoName, name4, origin) {
|
|
50129
|
+
setOrigin(repoName, name4, origin, hostId2) {
|
|
49856
50130
|
getDb().update(worktrees).set({
|
|
49857
50131
|
originWorktreeId: origin?.worktreeId ?? null,
|
|
49858
50132
|
originChatId: origin?.chatId ?? null,
|
|
49859
50133
|
originTerminalId: origin?.terminalId ?? null
|
|
49860
|
-
}).where(
|
|
50134
|
+
}).where(
|
|
50135
|
+
and(
|
|
50136
|
+
eq(worktrees.repoName, repoName),
|
|
50137
|
+
eq(worktrees.name, name4),
|
|
50138
|
+
...hostId2 === void 0 ? [] : [eq(worktrees.hostId, hostId2)]
|
|
50139
|
+
)
|
|
50140
|
+
).run();
|
|
49861
50141
|
}
|
|
49862
50142
|
/**
|
|
49863
50143
|
* Path and kind of one repo, by primary key. For per-request lookups
|
|
@@ -50053,16 +50333,17 @@ var init_worktree_statuses = __esm({
|
|
|
50053
50333
|
db.delete(worktreeStatuses).where(eq(worktreeStatuses.worktreeId, worktreeId4)).run();
|
|
50054
50334
|
}
|
|
50055
50335
|
/**
|
|
50056
|
-
* Reset every "
|
|
50336
|
+
* Reset every row that is not "done" or "failed" back to "idle" and
|
|
50057
50337
|
* return the count of affected rows. Used at server startup to clean
|
|
50058
50338
|
* up stale state from the previous run.
|
|
50059
50339
|
*/
|
|
50060
|
-
|
|
50340
|
+
resetActiveToIdle(now) {
|
|
50061
50341
|
const db = getDb();
|
|
50062
|
-
const result = db.update(worktreeStatuses).set({ agentStatus: "
|
|
50342
|
+
const result = db.update(worktreeStatuses).set({ agentStatus: "idle", updatedAt: now }).where(
|
|
50063
50343
|
or(
|
|
50064
50344
|
eq(worktreeStatuses.agentStatus, "working"),
|
|
50065
|
-
eq(worktreeStatuses.agentStatus, "
|
|
50345
|
+
eq(worktreeStatuses.agentStatus, "monitoring"),
|
|
50346
|
+
eq(worktreeStatuses.agentStatus, "question")
|
|
50066
50347
|
)
|
|
50067
50348
|
).run();
|
|
50068
50349
|
return Number(result.changes);
|
|
@@ -50108,22 +50389,22 @@ var init_worktree_statuses = __esm({
|
|
|
50108
50389
|
const db = getDb();
|
|
50109
50390
|
db.delete(worktreeStatusSources).where(eq(worktreeStatusSources.worktreeId, worktreeId4)).run();
|
|
50110
50391
|
}
|
|
50111
|
-
/** Delete every source. Used at server startup: no agent state survives it. */
|
|
50112
|
-
removeAll() {
|
|
50113
|
-
getDb().delete(worktreeStatusSources).run();
|
|
50114
|
-
}
|
|
50115
50392
|
/**
|
|
50116
|
-
*
|
|
50117
|
-
*
|
|
50393
|
+
* Delete every source that is not `done` or `failed`. Used at server
|
|
50394
|
+
* startup: no running agent survives it, but a finished one stays.
|
|
50118
50395
|
*/
|
|
50119
|
-
|
|
50120
|
-
|
|
50121
|
-
|
|
50122
|
-
|
|
50123
|
-
|
|
50124
|
-
|
|
50125
|
-
|
|
50126
|
-
|
|
50396
|
+
removeActive() {
|
|
50397
|
+
getDb().delete(worktreeStatusSources).where(notInArray(worktreeStatusSources.status, ["done", "failed"])).run();
|
|
50398
|
+
}
|
|
50399
|
+
/** Set the worktree's `done` and `failed` sources to `idle`. Returns how many changed. */
|
|
50400
|
+
acknowledge(worktreeId4, now) {
|
|
50401
|
+
const result = getDb().update(worktreeStatusSources).set({ status: "idle", updatedAt: now }).where(
|
|
50402
|
+
and(
|
|
50403
|
+
eq(worktreeStatusSources.worktreeId, worktreeId4),
|
|
50404
|
+
inArray(worktreeStatusSources.status, ["done", "failed"])
|
|
50405
|
+
)
|
|
50406
|
+
).run();
|
|
50407
|
+
return Number(result.changes);
|
|
50127
50408
|
}
|
|
50128
50409
|
};
|
|
50129
50410
|
}
|
|
@@ -50183,8 +50464,8 @@ var init_isolation = __esm({
|
|
|
50183
50464
|
});
|
|
50184
50465
|
|
|
50185
50466
|
// src/server/services/_utils/runner-config.ts
|
|
50186
|
-
import { existsSync as
|
|
50187
|
-
import { dirname as
|
|
50467
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
50468
|
+
import { dirname as dirname10, isAbsolute as isAbsolute2, join as join33, resolve as resolve7 } from "node:path";
|
|
50188
50469
|
function parseRunners(raw) {
|
|
50189
50470
|
if (raw === void 0 || raw === null) return { runners: [], errors: [] };
|
|
50190
50471
|
if (!Array.isArray(raw)) return { runners: [], errors: ["runners must be a list"] };
|
|
@@ -50210,8 +50491,8 @@ function bundledRunnersDir() {
|
|
|
50210
50491
|
let dir = import.meta.dirname;
|
|
50211
50492
|
for (let i = 0; i < 10; i++) {
|
|
50212
50493
|
const candidate = join33(dir, "runners");
|
|
50213
|
-
if (
|
|
50214
|
-
const parent =
|
|
50494
|
+
if (existsSync12(join33(candidate, "local", "spawn.sh"))) return candidate;
|
|
50495
|
+
const parent = dirname10(dir);
|
|
50215
50496
|
if (parent === dir) break;
|
|
50216
50497
|
dir = parent;
|
|
50217
50498
|
}
|
|
@@ -50691,16 +50972,20 @@ function upsertWorktreeStatus(worktreeId4, agent) {
|
|
|
50691
50972
|
tabStatuses: listTabStatuses(worktreeId4)
|
|
50692
50973
|
};
|
|
50693
50974
|
}
|
|
50694
|
-
function resolveWorktreeIdByCwd(cwd) {
|
|
50975
|
+
function resolveWorktreeIdByCwd(cwd, hostId2) {
|
|
50695
50976
|
const state2 = loadState();
|
|
50977
|
+
let other = null;
|
|
50696
50978
|
for (const proj of state2.repos) {
|
|
50697
50979
|
for (const wt of proj.worktrees) {
|
|
50698
|
-
|
|
50699
|
-
|
|
50700
|
-
}
|
|
50980
|
+
const wtHost = wt.hostId ?? LOCAL_HOST_ID3;
|
|
50981
|
+
if (hostId2 !== void 0 && wtHost !== hostId2) continue;
|
|
50982
|
+
if (cwd !== wt.path && !cwd.startsWith(`${wt.path}/`)) continue;
|
|
50983
|
+
const id = toWorktreeId(proj.name, wt.name, wt.hostId);
|
|
50984
|
+
if (wtHost === LOCAL_HOST_ID3) return id;
|
|
50985
|
+
other ??= id;
|
|
50701
50986
|
}
|
|
50702
50987
|
}
|
|
50703
|
-
return
|
|
50988
|
+
return other;
|
|
50704
50989
|
}
|
|
50705
50990
|
function chatStatusSource(chatId) {
|
|
50706
50991
|
return `${CHAT_SOURCE_PREFIX}${chatId}`;
|
|
@@ -50718,20 +51003,21 @@ function deriveWorktreeStatus(worktreeId4) {
|
|
|
50718
51003
|
best = status;
|
|
50719
51004
|
}
|
|
50720
51005
|
}
|
|
50721
|
-
return best ?? "
|
|
51006
|
+
return best ?? "idle";
|
|
50722
51007
|
}
|
|
50723
51008
|
function toTabStatuses(sources) {
|
|
50724
51009
|
const staleBefore = Date.now() - STALE_HOOK_WORKING_MS;
|
|
50725
51010
|
const byTab = /* @__PURE__ */ new Map();
|
|
50726
51011
|
for (const source of sources) {
|
|
50727
51012
|
const { status } = source;
|
|
50728
|
-
if (status
|
|
51013
|
+
if (status === "idle" || !(status in STATUS_PRIORITY)) continue;
|
|
50729
51014
|
if (isStaleSource(source, staleBefore)) continue;
|
|
51015
|
+
const tabStatus = status;
|
|
50730
51016
|
let tab;
|
|
50731
51017
|
if (source.sourceId.startsWith(CHAT_SOURCE_PREFIX)) {
|
|
50732
|
-
tab = { chatId: source.sourceId.slice(CHAT_SOURCE_PREFIX.length), status };
|
|
51018
|
+
tab = { chatId: source.sourceId.slice(CHAT_SOURCE_PREFIX.length), status: tabStatus };
|
|
50733
51019
|
} else if (source.terminalId) {
|
|
50734
|
-
tab = { terminalId: source.terminalId, status };
|
|
51020
|
+
tab = { terminalId: source.terminalId, status: tabStatus };
|
|
50735
51021
|
} else {
|
|
50736
51022
|
continue;
|
|
50737
51023
|
}
|
|
@@ -50765,10 +51051,10 @@ function removeWorktreeSource(worktreeId4, sourceId) {
|
|
|
50765
51051
|
if (!statusSourceQueries.remove(worktreeId4, sourceId)) return null;
|
|
50766
51052
|
return rederiveWorktreeStatus(worktreeId4);
|
|
50767
51053
|
}
|
|
50768
|
-
function acknowledgeWorktreeAttention(worktreeId4
|
|
51054
|
+
function acknowledgeWorktreeAttention(worktreeId4) {
|
|
50769
51055
|
const existing = getWorktreeStatus(worktreeId4);
|
|
50770
|
-
if (existing
|
|
50771
|
-
statusSourceQueries.acknowledge(worktreeId4,
|
|
51056
|
+
if (!existing) return null;
|
|
51057
|
+
if (statusSourceQueries.acknowledge(worktreeId4, Date.now()) === 0) return existing;
|
|
50772
51058
|
return upsertWorktreeStatus(worktreeId4, { status: deriveWorktreeStatus(worktreeId4) });
|
|
50773
51059
|
}
|
|
50774
51060
|
function startStatusSourceCleanup() {
|
|
@@ -50786,17 +51072,33 @@ function startStatusSourceCleanup() {
|
|
|
50786
51072
|
for (const status of changed) emit({ kind: "update", status });
|
|
50787
51073
|
});
|
|
50788
51074
|
}
|
|
51075
|
+
function subagentHold(worktreeId4, sourceId) {
|
|
51076
|
+
const key2 = `${worktreeId4}\0${sourceId}`;
|
|
51077
|
+
let hold = subagentHolds.get(key2);
|
|
51078
|
+
if (!hold) {
|
|
51079
|
+
hold = { running: /* @__PURE__ */ new Set(), deferred: null };
|
|
51080
|
+
subagentHolds.set(key2, hold);
|
|
51081
|
+
}
|
|
51082
|
+
return hold;
|
|
51083
|
+
}
|
|
51084
|
+
function dropSubagentHold(worktreeId4, sourceId) {
|
|
51085
|
+
subagentHolds.delete(`${worktreeId4}\0${sourceId}`);
|
|
51086
|
+
}
|
|
50789
51087
|
async function applyHookNotification(notification) {
|
|
50790
51088
|
const { payload } = notification;
|
|
50791
51089
|
if (notification.dispatch === "chat") return null;
|
|
50792
|
-
const worktreeId4 = resolveWorktreeIdByCwd(notification.cwd);
|
|
51090
|
+
const worktreeId4 = resolveWorktreeIdByCwd(notification.cwd, notification.hostId ?? LOCAL_HOST_ID3);
|
|
50793
51091
|
if (!worktreeId4) return null;
|
|
50794
51092
|
const agentType = notification.agent || detectHookAgentType(payload) || settingsService.getAgentDefinition(getWorktreeStatus(worktreeId4)?.agent?.codingAgentId).type;
|
|
50795
51093
|
const sourceId = `hook:${hookSessionId(payload) ?? agentType}`;
|
|
50796
51094
|
if (isHookSessionEnd(agentType, payload)) {
|
|
51095
|
+
dropSubagentHold(worktreeId4, sourceId);
|
|
50797
51096
|
return removeWorktreeSource(worktreeId4, sourceId);
|
|
50798
51097
|
}
|
|
50799
|
-
|
|
51098
|
+
let status = await mapHookPayloadToStatus(agentType, payload);
|
|
51099
|
+
if (agentType === "claude-code") {
|
|
51100
|
+
status = foldSubagents(subagentHold(worktreeId4, sourceId), payload, status);
|
|
51101
|
+
}
|
|
50800
51102
|
return setWorktreeSourceStatus(worktreeId4, sourceId, {
|
|
50801
51103
|
status,
|
|
50802
51104
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -50804,8 +51106,9 @@ async function applyHookNotification(notification) {
|
|
|
50804
51106
|
});
|
|
50805
51107
|
}
|
|
50806
51108
|
function resetAgentStatuses() {
|
|
50807
|
-
|
|
50808
|
-
|
|
51109
|
+
subagentHolds.clear();
|
|
51110
|
+
statusSourceQueries.removeActive();
|
|
51111
|
+
return worktreeStatusQueries.resetActiveToIdle(Date.now());
|
|
50809
51112
|
}
|
|
50810
51113
|
function resolveWorktreeIdentity(worktreeId4) {
|
|
50811
51114
|
return worktreeQueriesForIdentity.findIdentity(worktreeId4);
|
|
@@ -50814,7 +51117,7 @@ function deleteWorktreeStatus(worktreeId4) {
|
|
|
50814
51117
|
worktreeStatusQueries.remove(worktreeId4);
|
|
50815
51118
|
statusSourceQueries.removeForWorktree(worktreeId4);
|
|
50816
51119
|
}
|
|
50817
|
-
var worktreeStatusQueries, statusSourceQueries, worktreeQueriesForIdentity, repoQueries, STALE_HOOK_WORKING_MS, STATUS_PRIORITY, CHAT_SOURCE_PREFIX, MANUAL_STATUS_SOURCE;
|
|
51120
|
+
var worktreeStatusQueries, statusSourceQueries, worktreeQueriesForIdentity, repoQueries, STALE_HOOK_WORKING_MS, LOCAL_HOST_ID3, STATUS_PRIORITY, CHAT_SOURCE_PREFIX, MANUAL_STATUS_SOURCE, subagentHolds;
|
|
50818
51121
|
var init_state = __esm({
|
|
50819
51122
|
"src/server/services/state.ts"() {
|
|
50820
51123
|
"use strict";
|
|
@@ -50831,9 +51134,18 @@ var init_state = __esm({
|
|
|
50831
51134
|
worktreeQueriesForIdentity = new WorktreeQueries();
|
|
50832
51135
|
repoQueries = new RepoQueries();
|
|
50833
51136
|
STALE_HOOK_WORKING_MS = 15 * 6e4;
|
|
50834
|
-
|
|
51137
|
+
LOCAL_HOST_ID3 = "local";
|
|
51138
|
+
STATUS_PRIORITY = {
|
|
51139
|
+
question: 6,
|
|
51140
|
+
failed: 5,
|
|
51141
|
+
working: 4,
|
|
51142
|
+
monitoring: 3,
|
|
51143
|
+
done: 2,
|
|
51144
|
+
idle: 1
|
|
51145
|
+
};
|
|
50835
51146
|
CHAT_SOURCE_PREFIX = "chat:";
|
|
50836
51147
|
MANUAL_STATUS_SOURCE = "manual";
|
|
51148
|
+
subagentHolds = /* @__PURE__ */ new Map();
|
|
50837
51149
|
}
|
|
50838
51150
|
});
|
|
50839
51151
|
|
|
@@ -51733,8 +52045,8 @@ var init_map_limited = __esm({
|
|
|
51733
52045
|
|
|
51734
52046
|
// src/server/infra/subscriptions/github.ts
|
|
51735
52047
|
import { createHmac, randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
51736
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
51737
|
-
import { dirname as
|
|
52048
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
|
|
52049
|
+
import { dirname as dirname11, join as join36 } from "node:path";
|
|
51738
52050
|
function secretPath() {
|
|
51739
52051
|
return join36(bandHome(), "github-webhook-secret");
|
|
51740
52052
|
}
|
|
@@ -51752,7 +52064,7 @@ function ensureWebhookSecret() {
|
|
|
51752
52064
|
const existing = readWebhookSecret();
|
|
51753
52065
|
if (existing) return existing;
|
|
51754
52066
|
const path2 = secretPath();
|
|
51755
|
-
if (!
|
|
52067
|
+
if (!existsSync13(dirname11(path2))) mkdirSync7(dirname11(path2), { recursive: true });
|
|
51756
52068
|
const secret = randomBytes3(32).toString("hex");
|
|
51757
52069
|
writeFileSync5(path2, `${secret}
|
|
51758
52070
|
`, { mode: 384 });
|
|
@@ -52485,7 +52797,7 @@ var init_oauth_client = __esm({
|
|
|
52485
52797
|
|
|
52486
52798
|
// src/server/services/_utils/vault-crypto.ts
|
|
52487
52799
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes5 } from "node:crypto";
|
|
52488
|
-
import { chmodSync as chmodSync3, existsSync as
|
|
52800
|
+
import { chmodSync as chmodSync3, existsSync as existsSync14, readFileSync as readFileSync9, renameSync as renameSync7, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
52489
52801
|
import { join as join37 } from "node:path";
|
|
52490
52802
|
function vaultKeyFile() {
|
|
52491
52803
|
return join37(bandHome(), "vault.key");
|
|
@@ -52507,21 +52819,21 @@ function generateKey() {
|
|
|
52507
52819
|
function loadKey() {
|
|
52508
52820
|
const fromEnv = process.env.BAND_VAULT_KEY?.trim();
|
|
52509
52821
|
if (fromEnv) return parseKey(fromEnv);
|
|
52510
|
-
const
|
|
52511
|
-
if (
|
|
52822
|
+
const file3 = vaultKeyFile();
|
|
52823
|
+
if (existsSync14(file3)) return parseKey(readFileSync9(file3, "utf8"));
|
|
52512
52824
|
const key2 = generateKey();
|
|
52513
|
-
writeFileSync6(
|
|
52825
|
+
writeFileSync6(file3, `${key2.toString("base64")}
|
|
52514
52826
|
`, { mode: 384, flag: "wx" });
|
|
52515
|
-
chmodSync3(
|
|
52827
|
+
chmodSync3(file3, 384);
|
|
52516
52828
|
return key2;
|
|
52517
52829
|
}
|
|
52518
52830
|
function stageKeyFile(key2) {
|
|
52519
|
-
const
|
|
52520
|
-
const temp = `${
|
|
52831
|
+
const file3 = vaultKeyFile();
|
|
52832
|
+
const temp = `${file3}.${process.pid}.next`;
|
|
52521
52833
|
writeFileSync6(temp, `${key2.toString("base64")}
|
|
52522
52834
|
`, { mode: 384 });
|
|
52523
52835
|
chmodSync3(temp, 384);
|
|
52524
|
-
return { commit: () =>
|
|
52836
|
+
return { commit: () => renameSync7(temp, file3), discard: () => rmSync3(temp, { force: true }) };
|
|
52525
52837
|
}
|
|
52526
52838
|
function encrypt(key2, aad, plaintext) {
|
|
52527
52839
|
const nonce = randomBytes5(NONCE_BYTES);
|
|
@@ -53736,9 +54048,15 @@ var init_mime_types = __esm({
|
|
|
53736
54048
|
".txt": "text/plain",
|
|
53737
54049
|
".md": "text/markdown",
|
|
53738
54050
|
".csv": "text/csv",
|
|
53739
|
-
".html": "text/html",
|
|
53740
|
-
".
|
|
53741
|
-
".
|
|
54051
|
+
".html": "text/html; charset=utf-8",
|
|
54052
|
+
".htm": "text/html; charset=utf-8",
|
|
54053
|
+
".css": "text/css; charset=utf-8",
|
|
54054
|
+
".js": "text/javascript; charset=utf-8",
|
|
54055
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
54056
|
+
".woff": "font/woff",
|
|
54057
|
+
".woff2": "font/woff2",
|
|
54058
|
+
".ttf": "font/ttf",
|
|
54059
|
+
".otf": "font/otf",
|
|
53742
54060
|
".ts": "application/typescript",
|
|
53743
54061
|
".xml": "application/xml",
|
|
53744
54062
|
".yaml": "application/x-yaml",
|
|
@@ -53765,6 +54083,132 @@ var init_turn_events = __esm({
|
|
|
53765
54083
|
}
|
|
53766
54084
|
});
|
|
53767
54085
|
|
|
54086
|
+
// src/server/services/chat-status-service.ts
|
|
54087
|
+
function quietMs() {
|
|
54088
|
+
const override = Number(process.env.BAND_AGENT_QUIET_MS);
|
|
54089
|
+
return Number.isFinite(override) && override > 0 ? override : QUIET_MS;
|
|
54090
|
+
}
|
|
54091
|
+
function setChatStatus(worktreeId4, chatId, status) {
|
|
54092
|
+
if (status === "monitoring") monitoringChats.add(chatId);
|
|
54093
|
+
else monitoringChats.delete(chatId);
|
|
54094
|
+
const updated = setWorktreeSourceStatus(worktreeId4, chatStatusSource(chatId), { status });
|
|
54095
|
+
emit({ kind: "update", status: updated });
|
|
54096
|
+
}
|
|
54097
|
+
function restingStatus(chatId) {
|
|
54098
|
+
if (agentSessionService.hasPendingRequest(chatId)) return "question";
|
|
54099
|
+
if (agentSessionService.nextWorkDeadline(chatId) !== null) return "monitoring";
|
|
54100
|
+
return "done";
|
|
54101
|
+
}
|
|
54102
|
+
function beginBandTurn(worktreeId4, chatId) {
|
|
54103
|
+
endAutonomous(chatId);
|
|
54104
|
+
clearMonitorTimer(chatId);
|
|
54105
|
+
setChatStatus(worktreeId4, chatId, "working");
|
|
54106
|
+
}
|
|
54107
|
+
function endBandTurn(worktreeId4, chatId, result) {
|
|
54108
|
+
const status = result === "done" ? restingStatus(chatId) : result;
|
|
54109
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54110
|
+
if (status === "monitoring") watchMonitoring(worktreeId4, chatId);
|
|
54111
|
+
}
|
|
54112
|
+
function endAutonomous(chatId) {
|
|
54113
|
+
const timer = autonomous.get(chatId);
|
|
54114
|
+
if (timer) clearTimeout(timer);
|
|
54115
|
+
autonomous.delete(chatId);
|
|
54116
|
+
}
|
|
54117
|
+
function clearMonitorTimer(chatId) {
|
|
54118
|
+
const timer = monitorTimers.get(chatId);
|
|
54119
|
+
if (timer) clearTimeout(timer);
|
|
54120
|
+
monitorTimers.delete(chatId);
|
|
54121
|
+
}
|
|
54122
|
+
function watchMonitoring(worktreeId4, chatId) {
|
|
54123
|
+
clearMonitorTimer(chatId);
|
|
54124
|
+
const deadline = agentSessionService.nextWorkDeadline(chatId);
|
|
54125
|
+
if (deadline === null) return;
|
|
54126
|
+
const timer = setTimeout(
|
|
54127
|
+
() => {
|
|
54128
|
+
monitorTimers.delete(chatId);
|
|
54129
|
+
if (runningTurn(chatId) || autonomous.has(chatId)) return;
|
|
54130
|
+
reconsiderMonitoring(worktreeId4, chatId);
|
|
54131
|
+
},
|
|
54132
|
+
Math.min(Math.max(deadline - Date.now(), 0) + 100, MAX_TIMER_MS)
|
|
54133
|
+
);
|
|
54134
|
+
timer.unref?.();
|
|
54135
|
+
monitorTimers.set(chatId, timer);
|
|
54136
|
+
}
|
|
54137
|
+
function reconsiderMonitoring(worktreeId4, chatId) {
|
|
54138
|
+
if (!chatService.get(chatId) || !monitoringChats.has(chatId)) return;
|
|
54139
|
+
const status = restingStatus(chatId);
|
|
54140
|
+
if (status === "monitoring") {
|
|
54141
|
+
watchMonitoring(worktreeId4, chatId);
|
|
54142
|
+
return;
|
|
54143
|
+
}
|
|
54144
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54145
|
+
}
|
|
54146
|
+
function settleAutonomous(worktreeId4, chatId) {
|
|
54147
|
+
autonomous.delete(chatId);
|
|
54148
|
+
if (!chatService.get(chatId) || runningTurn(chatId)) return;
|
|
54149
|
+
const status = restingStatus(chatId);
|
|
54150
|
+
setChatStatus(worktreeId4, chatId, status);
|
|
54151
|
+
if (status === "monitoring") watchMonitoring(worktreeId4, chatId);
|
|
54152
|
+
}
|
|
54153
|
+
function armQuietTimer(worktreeId4, chatId) {
|
|
54154
|
+
const previous = autonomous.get(chatId);
|
|
54155
|
+
if (previous) clearTimeout(previous);
|
|
54156
|
+
const timer = setTimeout(() => settleAutonomous(worktreeId4, chatId), quietMs());
|
|
54157
|
+
timer.unref?.();
|
|
54158
|
+
autonomous.set(chatId, timer);
|
|
54159
|
+
}
|
|
54160
|
+
function observeAgentStatus(hasRunningTurn) {
|
|
54161
|
+
if (observing) return;
|
|
54162
|
+
observing = true;
|
|
54163
|
+
runningTurn = hasRunningTurn;
|
|
54164
|
+
agentSessionService.observePending((chatId, worktreeId4) => {
|
|
54165
|
+
const waiting = agentSessionService.hasPendingRequest(chatId);
|
|
54166
|
+
if (runningTurn(chatId)) {
|
|
54167
|
+
setChatStatus(worktreeId4, chatId, waiting ? "question" : "working");
|
|
54168
|
+
return;
|
|
54169
|
+
}
|
|
54170
|
+
if (waiting) {
|
|
54171
|
+
const timer = autonomous.get(chatId);
|
|
54172
|
+
if (timer) clearTimeout(timer);
|
|
54173
|
+
autonomous.set(chatId, null);
|
|
54174
|
+
setChatStatus(worktreeId4, chatId, "question");
|
|
54175
|
+
} else if (autonomous.has(chatId)) {
|
|
54176
|
+
setChatStatus(worktreeId4, chatId, "working");
|
|
54177
|
+
armQuietTimer(worktreeId4, chatId);
|
|
54178
|
+
}
|
|
54179
|
+
});
|
|
54180
|
+
agentSessionService.observeActivity((chatId, worktreeId4, kind) => {
|
|
54181
|
+
if (runningTurn(chatId) || !chatService.get(chatId)) return;
|
|
54182
|
+
if (kind === "update") {
|
|
54183
|
+
if (agentSessionService.hasPendingRequest(chatId)) return;
|
|
54184
|
+
const wasWorking = autonomous.has(chatId);
|
|
54185
|
+
armQuietTimer(worktreeId4, chatId);
|
|
54186
|
+
clearMonitorTimer(chatId);
|
|
54187
|
+
if (!wasWorking) setChatStatus(worktreeId4, chatId, "working");
|
|
54188
|
+
return;
|
|
54189
|
+
}
|
|
54190
|
+
if (autonomous.has(chatId)) return;
|
|
54191
|
+
reconsiderMonitoring(worktreeId4, chatId);
|
|
54192
|
+
});
|
|
54193
|
+
}
|
|
54194
|
+
var QUIET_MS, MAX_TIMER_MS, autonomous, monitorTimers, monitoringChats, runningTurn, observing;
|
|
54195
|
+
var init_chat_status_service = __esm({
|
|
54196
|
+
"src/server/services/chat-status-service.ts"() {
|
|
54197
|
+
"use strict";
|
|
54198
|
+
init_agent_session_service();
|
|
54199
|
+
init_chat_service();
|
|
54200
|
+
init_state();
|
|
54201
|
+
init_watcher_service();
|
|
54202
|
+
QUIET_MS = 1e4;
|
|
54203
|
+
MAX_TIMER_MS = 2 ** 31 - 1;
|
|
54204
|
+
autonomous = /* @__PURE__ */ new Map();
|
|
54205
|
+
monitorTimers = /* @__PURE__ */ new Map();
|
|
54206
|
+
monitoringChats = /* @__PURE__ */ new Set();
|
|
54207
|
+
runningTurn = () => false;
|
|
54208
|
+
observing = false;
|
|
54209
|
+
}
|
|
54210
|
+
});
|
|
54211
|
+
|
|
53768
54212
|
// ../../packages/host-local/src/git/git-refusals.ts
|
|
53769
54213
|
async function pullRefusal(err, cwd, execGit2) {
|
|
53770
54214
|
const stderr = errorText(err);
|
|
@@ -54212,7 +54656,7 @@ var init_worktree_checkpoints = __esm({
|
|
|
54212
54656
|
});
|
|
54213
54657
|
|
|
54214
54658
|
// src/server/services/_utils/checkpoint-store.ts
|
|
54215
|
-
import { rmSync as
|
|
54659
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
54216
54660
|
import { join as join38 } from "node:path";
|
|
54217
54661
|
function worktreeCheckpointDir(worktreeId4) {
|
|
54218
54662
|
return join38(bandHome(), "checkpoints", worktreeId4);
|
|
@@ -54222,7 +54666,7 @@ function checkpointDir(worktreeId4, checkpointId2) {
|
|
|
54222
54666
|
}
|
|
54223
54667
|
function forgetCheckpoints(worktreeId4) {
|
|
54224
54668
|
queries2.deleteForWorktree(worktreeId4);
|
|
54225
|
-
|
|
54669
|
+
rmSync4(worktreeCheckpointDir(worktreeId4), { recursive: true, force: true });
|
|
54226
54670
|
}
|
|
54227
54671
|
var queries2;
|
|
54228
54672
|
var init_checkpoint_store = __esm({
|
|
@@ -54342,13 +54786,13 @@ var init_remote_worktrees = __esm({
|
|
|
54342
54786
|
// ../../packages/shared/src/repo-hosts.ts
|
|
54343
54787
|
function isHostAllowed(allowed2, hostId2) {
|
|
54344
54788
|
if (!allowed2) return true;
|
|
54345
|
-
return allowed2.includes(hostId2 ||
|
|
54789
|
+
return allowed2.includes(hostId2 || LOCAL_HOST_ID4);
|
|
54346
54790
|
}
|
|
54347
54791
|
function effectivePrimaryHost(primaryHostId, allowed2, hostsWithDefaultCheckout) {
|
|
54348
54792
|
if (primaryHostId && isHostAllowed(allowed2, primaryHostId)) return primaryHostId;
|
|
54349
54793
|
const candidates = hostsWithDefaultCheckout.filter((id) => isHostAllowed(allowed2, id));
|
|
54350
54794
|
if (candidates.length === 0) return null;
|
|
54351
|
-
if (candidates.includes(
|
|
54795
|
+
if (candidates.includes(LOCAL_HOST_ID4)) return LOCAL_HOST_ID4;
|
|
54352
54796
|
return [...candidates].sort()[0] ?? null;
|
|
54353
54797
|
}
|
|
54354
54798
|
function onlyAvailableOnMessage(repo, hostNames) {
|
|
@@ -54358,10 +54802,10 @@ function checkpointsEnabledFor(stored, allowed2) {
|
|
|
54358
54802
|
if (stored != null) return stored;
|
|
54359
54803
|
return !allowed2;
|
|
54360
54804
|
}
|
|
54361
|
-
var
|
|
54805
|
+
var LOCAL_HOST_ID4;
|
|
54362
54806
|
var init_repo_hosts = __esm({
|
|
54363
54807
|
"../../packages/shared/src/repo-hosts.ts"() {
|
|
54364
|
-
|
|
54808
|
+
LOCAL_HOST_ID4 = "local";
|
|
54365
54809
|
}
|
|
54366
54810
|
});
|
|
54367
54811
|
|
|
@@ -56246,6 +56690,7 @@ var init_rpc = __esm({
|
|
|
56246
56690
|
"scripts.copyFiles": 2 * 6e4,
|
|
56247
56691
|
"agentEnv.installSkills": 2 * 6e4,
|
|
56248
56692
|
"agentEnv.installHooks": 6e4,
|
|
56693
|
+
"agentEnv.removeHooks": 6e4,
|
|
56249
56694
|
exec: 10 * 6e4
|
|
56250
56695
|
};
|
|
56251
56696
|
RemoteRpc = class {
|
|
@@ -56519,7 +56964,8 @@ var init_remote_host = __esm({
|
|
|
56519
56964
|
// The worker takes no `home` override. It is for tests of the local install.
|
|
56520
56965
|
installSkills: () => this.rpc.call("agentEnv.installSkills"),
|
|
56521
56966
|
hooksStatus: () => this.rpc.call("agentEnv.hooksStatus"),
|
|
56522
|
-
installHooks: () => this.rpc.call("agentEnv.installHooks")
|
|
56967
|
+
installHooks: () => this.rpc.call("agentEnv.installHooks"),
|
|
56968
|
+
removeHooks: () => this.rpc.call("agentEnv.removeHooks")
|
|
56523
56969
|
};
|
|
56524
56970
|
// ---- fs -----------------------------------------------------------------
|
|
56525
56971
|
async writeFile(path2, data, options) {
|
|
@@ -56931,7 +57377,7 @@ function stringRecord(value) {
|
|
|
56931
57377
|
function stringList2(value) {
|
|
56932
57378
|
return Array.isArray(value) ? value.filter((v2) => typeof v2 === "string") : [];
|
|
56933
57379
|
}
|
|
56934
|
-
var log20, repoQueries4,
|
|
57380
|
+
var log20, repoQueries4, LOCAL_HOST_ID5, DEFAULT_TIMEOUT_MS, DEFAULT_LEASE_MS, MAX_LEASE_MS, SWEEP_MS, leaseFilterInput, HostRequestError, PlacementService, placementService;
|
|
56935
57381
|
var init_placement_service = __esm({
|
|
56936
57382
|
"src/server/services/placement-service.ts"() {
|
|
56937
57383
|
"use strict";
|
|
@@ -56955,7 +57401,7 @@ var init_placement_service = __esm({
|
|
|
56955
57401
|
init_worktree_service();
|
|
56956
57402
|
log20 = createLogger("placement");
|
|
56957
57403
|
repoQueries4 = new RepoQueries();
|
|
56958
|
-
|
|
57404
|
+
LOCAL_HOST_ID5 = "local";
|
|
56959
57405
|
DEFAULT_TIMEOUT_MS = 10 * 6e4;
|
|
56960
57406
|
DEFAULT_LEASE_MS = 6e4;
|
|
56961
57407
|
MAX_LEASE_MS = 15 * 6e4;
|
|
@@ -57003,7 +57449,7 @@ var init_placement_service = __esm({
|
|
|
57003
57449
|
for (const row of tokenService.listHosts(1e3)) {
|
|
57004
57450
|
if (row.status !== "online" || !row.usable) continue;
|
|
57005
57451
|
if (row.info?.mode === "ephemeral") continue;
|
|
57006
|
-
const info = row.id ===
|
|
57452
|
+
const info = row.id === LOCAL_HOST_ID5 ? await hostRegistry.local.info().catch(() => null) : null;
|
|
57007
57453
|
const stored = row.info ?? {};
|
|
57008
57454
|
const versions = stringRecord(info?.versions ?? stored.versions);
|
|
57009
57455
|
const tools = stringRecord(info?.tools ?? stored.tools);
|
|
@@ -57035,7 +57481,7 @@ var init_placement_service = __esm({
|
|
|
57035
57481
|
const fits = (await this.candidates()).filter(
|
|
57036
57482
|
(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
57483
|
// described live elsewhere, so it is not held to a report here.
|
|
57038
|
-
(agent === void 0 || c.id ===
|
|
57484
|
+
(agent === void 0 || c.id === LOCAL_HOST_ID5 || c.agents.includes(agent)) && matches(c, placement)
|
|
57039
57485
|
);
|
|
57040
57486
|
if (fits.length === 0) return null;
|
|
57041
57487
|
const load = this.queries.worktreeCounts();
|
|
@@ -57824,7 +58270,7 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57824
58270
|
const log77 = new BuildLog(row.id, (id, text3) => this.queries.setLog(id, text3));
|
|
57825
58271
|
let tmp = null;
|
|
57826
58272
|
const container = `band-env-${row.id.slice(0, 12)}`;
|
|
57827
|
-
let
|
|
58273
|
+
let failure2 = null;
|
|
57828
58274
|
try {
|
|
57829
58275
|
log77.add(`Building ${plan.tag} for ${plan.repo} at ${plan.ref} (${plan.commit.slice(0, 12)})`);
|
|
57830
58276
|
log77.flush();
|
|
@@ -57836,8 +58282,8 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57836
58282
|
if (plan.registry) await this.push(plan, log77);
|
|
57837
58283
|
log77.add(`Done: ${plan.tag}`);
|
|
57838
58284
|
} catch (err) {
|
|
57839
|
-
|
|
57840
|
-
log77.add(`FAILED: ${
|
|
58285
|
+
failure2 = err.message;
|
|
58286
|
+
log77.add(`FAILED: ${failure2}`);
|
|
57841
58287
|
}
|
|
57842
58288
|
await this.exec(plan.host, "docker", ["rm", "--force", container], log77, { quiet: true }).catch(
|
|
57843
58289
|
() => void 0
|
|
@@ -57851,7 +58297,7 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57851
58297
|
).catch(() => void 0);
|
|
57852
58298
|
if (tmp) await plan.host.fs.rm(tmp, { recursive: true, force: true }).catch(() => void 0);
|
|
57853
58299
|
try {
|
|
57854
|
-
if (
|
|
58300
|
+
if (failure2 === null) {
|
|
57855
58301
|
this.queries.finish(row.id, {
|
|
57856
58302
|
status: "ready",
|
|
57857
58303
|
image: plan.tag,
|
|
@@ -57861,7 +58307,7 @@ ${this.text.slice(-MAX_LOG_BYTES)}`;
|
|
|
57861
58307
|
} else {
|
|
57862
58308
|
this.queries.finish(row.id, {
|
|
57863
58309
|
status: "failed",
|
|
57864
|
-
error:
|
|
58310
|
+
error: failure2.slice(0, 2e3),
|
|
57865
58311
|
log: log77.toString(),
|
|
57866
58312
|
at: Date.now()
|
|
57867
58313
|
});
|
|
@@ -58204,8 +58650,8 @@ var init_git_credential_service = __esm({
|
|
|
58204
58650
|
// src/server/services/runner-service.ts
|
|
58205
58651
|
import { spawn as spawn6 } from "node:child_process";
|
|
58206
58652
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
58207
|
-
import { appendFileSync, mkdirSync as
|
|
58208
|
-
import { dirname as
|
|
58653
|
+
import { appendFileSync, mkdirSync as mkdirSync8, readFileSync as readFileSync10, rmSync as rmSync5, statSync as statSync13 } from "node:fs";
|
|
58654
|
+
import { dirname as dirname12, join as join39 } from "node:path";
|
|
58209
58655
|
function snapshotSweepMs() {
|
|
58210
58656
|
const raw = Number(process.env.BAND_SNAPSHOT_SWEEP_MS);
|
|
58211
58657
|
return Number.isFinite(raw) && raw > 0 ? raw : SNAPSHOT_SWEEP_MS;
|
|
@@ -58294,9 +58740,9 @@ var init_runner_service = __esm({
|
|
|
58294
58740
|
"SHELL"
|
|
58295
58741
|
];
|
|
58296
58742
|
RunLog = class {
|
|
58297
|
-
constructor(
|
|
58298
|
-
this.file =
|
|
58299
|
-
|
|
58743
|
+
constructor(file3) {
|
|
58744
|
+
this.file = file3;
|
|
58745
|
+
mkdirSync8(dirname12(file3), { recursive: true, mode: 448 });
|
|
58300
58746
|
}
|
|
58301
58747
|
lines = [];
|
|
58302
58748
|
bytes = 0;
|
|
@@ -58721,7 +59167,7 @@ ${tail}` : head;
|
|
|
58721
59167
|
() => false
|
|
58722
59168
|
);
|
|
58723
59169
|
if (exists3) return;
|
|
58724
|
-
await host.git.exec(["clone", "--quiet", "--", url2, dest],
|
|
59170
|
+
await host.git.exec(["clone", "--quiet", "--", url2, dest], dirname12(dest));
|
|
58725
59171
|
}
|
|
58726
59172
|
/** Records the machine of an attempt. An older machine of the same worker id is gone by now, so it is retired. */
|
|
58727
59173
|
recordMachine(runner, row, workerId) {
|
|
@@ -58796,7 +59242,7 @@ ${tail}` : head;
|
|
|
58796
59242
|
} else if (!runner.destroy) {
|
|
58797
59243
|
outcome = { state: "lost", error: `runner "${runner.id}" has no destroy hook` };
|
|
58798
59244
|
} else {
|
|
58799
|
-
let
|
|
59245
|
+
let failure2 = null;
|
|
58800
59246
|
try {
|
|
58801
59247
|
const code = await this.runDestroy(
|
|
58802
59248
|
runner,
|
|
@@ -58805,17 +59251,17 @@ ${tail}` : head;
|
|
|
58805
59251
|
machine.handle,
|
|
58806
59252
|
runLog
|
|
58807
59253
|
);
|
|
58808
|
-
if (code !== 0)
|
|
59254
|
+
if (code !== 0) failure2 = `destroy exited with code ${code}`;
|
|
58809
59255
|
} catch (err) {
|
|
58810
|
-
|
|
59256
|
+
failure2 = `destroy failed: ${err instanceof Error ? err.message : err}`;
|
|
58811
59257
|
}
|
|
58812
|
-
if (
|
|
59258
|
+
if (failure2 === null) {
|
|
58813
59259
|
outcome = { state: "destroyed", destroyedAt: Date.now(), error: reason };
|
|
58814
59260
|
} else {
|
|
58815
59261
|
const attempts = machine.destroyAttempts + 1;
|
|
58816
|
-
outcome = attempts >= DESTROY_ATTEMPTS ? { state: "lost", destroyAttempts: attempts, error:
|
|
59262
|
+
outcome = attempts >= DESTROY_ATTEMPTS ? { state: "lost", destroyAttempts: attempts, error: failure2 } : {
|
|
58817
59263
|
destroyAttempts: attempts,
|
|
58818
|
-
error: `${
|
|
59264
|
+
error: `${failure2} (try ${attempts} of ${DESTROY_ATTEMPTS})`
|
|
58819
59265
|
};
|
|
58820
59266
|
}
|
|
58821
59267
|
}
|
|
@@ -58829,12 +59275,12 @@ ${tail}` : head;
|
|
|
58829
59275
|
/** Runs `destroy` for a machine the hub has no record of, named only by the handle `status` printed. */
|
|
58830
59276
|
async destroyHandle(runner, handle, reason) {
|
|
58831
59277
|
if (!runner.destroy) return false;
|
|
58832
|
-
const
|
|
59278
|
+
const file3 = join39(bandHome(), "runners", "logs", "reaper.log");
|
|
58833
59279
|
try {
|
|
58834
|
-
if (
|
|
59280
|
+
if (statSync13(file3).size > LOG_FILE_LIMIT) rmSync5(file3, { force: true });
|
|
58835
59281
|
} catch {
|
|
58836
59282
|
}
|
|
58837
|
-
const runLog = new RunLog(
|
|
59283
|
+
const runLog = new RunLog(file3);
|
|
58838
59284
|
runLog.write("hub", `destroying machine ${handle} of runner ${runner.id}: ${reason}`);
|
|
58839
59285
|
try {
|
|
58840
59286
|
const code = await this.runDestroy(runner, null, null, handle, runLog);
|
|
@@ -59078,7 +59524,7 @@ ${tail}` : head;
|
|
|
59078
59524
|
runHook(opts) {
|
|
59079
59525
|
const { runner, name: name4, path: path2, env, timeoutMs, runLog, watch, onStdout, onStderr } = opts;
|
|
59080
59526
|
const cwd = join39(bandHome(), "runners", runner.id);
|
|
59081
|
-
|
|
59527
|
+
mkdirSync8(cwd, { recursive: true, mode: 448 });
|
|
59082
59528
|
return new Promise((resolve15, reject) => {
|
|
59083
59529
|
let child;
|
|
59084
59530
|
try {
|
|
@@ -59136,7 +59582,7 @@ ${tail}` : head;
|
|
|
59136
59582
|
});
|
|
59137
59583
|
|
|
59138
59584
|
// src/server/services/ephemeral-lifecycle-service.ts
|
|
59139
|
-
import { rmSync as
|
|
59585
|
+
import { rmSync as rmSync6 } from "node:fs";
|
|
59140
59586
|
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
|
|
59141
59587
|
import { join as join40, posix as posix5 } from "node:path";
|
|
59142
59588
|
function idleTimeoutMs() {
|
|
@@ -59146,7 +59592,7 @@ function idleTimeoutMs() {
|
|
|
59146
59592
|
function sleepDir(worktreeId4) {
|
|
59147
59593
|
return join40(bandHome(), "sleep", worktreeId4);
|
|
59148
59594
|
}
|
|
59149
|
-
var log23,
|
|
59595
|
+
var log23, LOCAL_HOST_ID6, WAKE_POLL_MS, WAKE_GRACE_MS, EXIT_GRACE_MS, SleepError, errorText2, EphemeralLifecycleService, ephemeralLifecycleService;
|
|
59150
59596
|
var init_ephemeral_lifecycle_service = __esm({
|
|
59151
59597
|
"src/server/services/ephemeral-lifecycle-service.ts"() {
|
|
59152
59598
|
"use strict";
|
|
@@ -59168,7 +59614,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59168
59614
|
init_state();
|
|
59169
59615
|
init_task_service();
|
|
59170
59616
|
log23 = createLogger("ephemeral-lifecycle");
|
|
59171
|
-
|
|
59617
|
+
LOCAL_HOST_ID6 = "local";
|
|
59172
59618
|
WAKE_POLL_MS = 500;
|
|
59173
59619
|
WAKE_GRACE_MS = 5 * 6e4;
|
|
59174
59620
|
EXIT_GRACE_MS = 6e4;
|
|
@@ -59499,7 +59945,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59499
59945
|
}
|
|
59500
59946
|
forget(row) {
|
|
59501
59947
|
this.sleeps.delete(row.worktreeId);
|
|
59502
|
-
|
|
59948
|
+
rmSync6(sleepDir(row.worktreeId), { recursive: true, force: true });
|
|
59503
59949
|
}
|
|
59504
59950
|
// ---- wake ---------------------------------------------------------------
|
|
59505
59951
|
/**
|
|
@@ -59511,7 +59957,7 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59511
59957
|
async ensureAwake(worktreeId4) {
|
|
59512
59958
|
for (let i = 0; i < 3; i++) {
|
|
59513
59959
|
const hostId2 = this.worktrees.findHostId(worktreeId4);
|
|
59514
|
-
if (!hostId2 || hostId2 ===
|
|
59960
|
+
if (!hostId2 || hostId2 === LOCAL_HOST_ID6) return;
|
|
59515
59961
|
const draining = this.draining.get(hostId2);
|
|
59516
59962
|
if (draining) {
|
|
59517
59963
|
await draining;
|
|
@@ -59680,10 +60126,10 @@ var init_ephemeral_lifecycle_service = __esm({
|
|
|
59680
60126
|
}
|
|
59681
60127
|
const stage = posix5.join(wipDir, "sessions", row.worktreeId);
|
|
59682
60128
|
await host.fs.rm(stage, { recursive: true, force: true });
|
|
59683
|
-
for (const
|
|
59684
|
-
const target = posix5.join(stage,
|
|
60129
|
+
for (const file3 of files) {
|
|
60130
|
+
const target = posix5.join(stage, file3.root, file3.rel);
|
|
59685
60131
|
await host.fs.mkdir(posix5.dirname(target), { recursive: true });
|
|
59686
|
-
await host.fs.writeFile(target, Buffer.from(
|
|
60132
|
+
await host.fs.writeFile(target, Buffer.from(file3.data, "base64"));
|
|
59687
60133
|
}
|
|
59688
60134
|
await rpc.call(METHOD_LIFECYCLE_IMPORT_SESSIONS, { dir: stage });
|
|
59689
60135
|
}
|
|
@@ -60843,6 +61289,8 @@ var init_client_state_keys = __esm({
|
|
|
60843
61289
|
exact("band.repos-list.collapsed-pinned", [jsonPart("all")]),
|
|
60844
61290
|
exact("band.repos-list.label-filter", [rawPart("all")]),
|
|
60845
61291
|
exact("band.repos-list.label-last-worktree", [jsonPart("all")]),
|
|
61292
|
+
// The hooks version whose install banner the user dismissed.
|
|
61293
|
+
exact("band:agent-hooks-banner-dismissed", [rawPart("all")]),
|
|
60846
61294
|
perWorktree(CENTER_TABS_PREFIX, [jsonPart("all")]),
|
|
60847
61295
|
perWorktreeLeaf("band:term-split:", [jsonPart("all")]),
|
|
60848
61296
|
perWorktree("band:diff-compare-branch:", [rawPart("all")]),
|
|
@@ -61069,11 +61517,11 @@ var init_cronjobs = __esm({
|
|
|
61069
61517
|
* incomplete state (some jobs missing) — the legacy `cronjob-store.ts`
|
|
61070
61518
|
* had the same gap; the migration is the appropriate moment to fix it.
|
|
61071
61519
|
*/
|
|
61072
|
-
saveFile(key2,
|
|
61520
|
+
saveFile(key2, file3) {
|
|
61073
61521
|
const db = getDb();
|
|
61074
61522
|
db.transaction((tx) => {
|
|
61075
61523
|
tx.delete(cronjobs).where(eq(cronjobs.fileKey, key2)).run();
|
|
61076
|
-
for (const job of
|
|
61524
|
+
for (const job of file3.jobs) {
|
|
61077
61525
|
tx.insert(cronjobs).values({
|
|
61078
61526
|
id: job.id,
|
|
61079
61527
|
fileKey: key2,
|
|
@@ -61265,14 +61713,14 @@ var init_cronjob_service = __esm({
|
|
|
61265
61713
|
list(filter) {
|
|
61266
61714
|
if (filter?.repo) {
|
|
61267
61715
|
const { repo } = filter;
|
|
61268
|
-
const
|
|
61269
|
-
return { jobs:
|
|
61716
|
+
const file3 = this.queries.loadFile(repo);
|
|
61717
|
+
return { jobs: file3.jobs.map((j) => ({ ...j, fileKey: repo })) };
|
|
61270
61718
|
}
|
|
61271
61719
|
if (filter?.worktreeId) {
|
|
61272
61720
|
const { worktreeId: worktreeId4 } = filter;
|
|
61273
|
-
const
|
|
61721
|
+
const file3 = this.queries.loadFile(worktreeId4);
|
|
61274
61722
|
return {
|
|
61275
|
-
jobs:
|
|
61723
|
+
jobs: file3.jobs.map((j) => ({ ...j, fileKey: worktreeId4 }))
|
|
61276
61724
|
};
|
|
61277
61725
|
}
|
|
61278
61726
|
return { jobs: this.queries.listAll() };
|
|
@@ -61300,7 +61748,7 @@ var init_cronjob_service = __esm({
|
|
|
61300
61748
|
if (input.scope === "worktree" && !input.worktreeId) {
|
|
61301
61749
|
throw new CronjobWorktreeMissingError();
|
|
61302
61750
|
}
|
|
61303
|
-
const
|
|
61751
|
+
const file3 = this.queries.loadFile(input.key);
|
|
61304
61752
|
const job = {
|
|
61305
61753
|
id: generateCronjobId(),
|
|
61306
61754
|
name: input.name,
|
|
@@ -61314,8 +61762,8 @@ var init_cronjob_service = __esm({
|
|
|
61314
61762
|
enabled: input.enabled,
|
|
61315
61763
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
61316
61764
|
};
|
|
61317
|
-
|
|
61318
|
-
this.queries.saveFile(input.key,
|
|
61765
|
+
file3.jobs.push(job);
|
|
61766
|
+
this.queries.saveFile(input.key, file3);
|
|
61319
61767
|
this.reloadSchedules();
|
|
61320
61768
|
return { job };
|
|
61321
61769
|
}
|
|
@@ -61330,14 +61778,14 @@ var init_cronjob_service = __esm({
|
|
|
61330
61778
|
if (input.cronExpression !== void 0) {
|
|
61331
61779
|
this.assertValidCron(input.cronExpression);
|
|
61332
61780
|
}
|
|
61333
|
-
const
|
|
61334
|
-
const job =
|
|
61781
|
+
const file3 = this.queries.loadFile(input.key);
|
|
61782
|
+
const job = file3.jobs.find((j) => j.id === input.id);
|
|
61335
61783
|
if (!job) throw new CronjobNotFoundError();
|
|
61336
61784
|
if (input.name !== void 0) job.name = input.name;
|
|
61337
61785
|
if (input.prompt !== void 0) job.prompt = input.prompt;
|
|
61338
61786
|
if (input.cronExpression !== void 0) job.cronExpression = input.cronExpression;
|
|
61339
61787
|
if (input.enabled !== void 0) job.enabled = input.enabled;
|
|
61340
|
-
this.queries.saveFile(input.key,
|
|
61788
|
+
this.queries.saveFile(input.key, file3);
|
|
61341
61789
|
this.reloadSchedules();
|
|
61342
61790
|
return { job };
|
|
61343
61791
|
}
|
|
@@ -61347,11 +61795,11 @@ var init_cronjob_service = __esm({
|
|
|
61347
61795
|
* Throws `CronjobNotFoundError` if the job is already gone.
|
|
61348
61796
|
*/
|
|
61349
61797
|
delete(key2, id) {
|
|
61350
|
-
const
|
|
61351
|
-
const index2 =
|
|
61798
|
+
const file3 = this.queries.loadFile(key2);
|
|
61799
|
+
const index2 = file3.jobs.findIndex((j) => j.id === id);
|
|
61352
61800
|
if (index2 === -1) throw new CronjobNotFoundError();
|
|
61353
|
-
const [removed] =
|
|
61354
|
-
this.queries.saveFile(key2,
|
|
61801
|
+
const [removed] = file3.jobs.splice(index2, 1);
|
|
61802
|
+
this.queries.saveFile(key2, file3);
|
|
61355
61803
|
if (removed?.lastTerminalId) {
|
|
61356
61804
|
const terminalId = removed.lastTerminalId;
|
|
61357
61805
|
void terminalService.kill(terminalId).catch((err) => {
|
|
@@ -61469,8 +61917,8 @@ var init_cronjob_service = __esm({
|
|
|
61469
61917
|
*/
|
|
61470
61918
|
stopJobsForKey(key2) {
|
|
61471
61919
|
const state2 = schedulerState();
|
|
61472
|
-
const
|
|
61473
|
-
for (const job of
|
|
61920
|
+
const file3 = this.queries.loadFile(key2);
|
|
61921
|
+
for (const job of file3.jobs) {
|
|
61474
61922
|
const cron = state2.jobs.get(job.id);
|
|
61475
61923
|
if (cron) {
|
|
61476
61924
|
cron.stop();
|
|
@@ -61621,8 +62069,8 @@ exit`;
|
|
|
61621
62069
|
// Internals
|
|
61622
62070
|
// -------------------------------------------------------------------------
|
|
61623
62071
|
findJob(key2, id) {
|
|
61624
|
-
const
|
|
61625
|
-
return
|
|
62072
|
+
const file3 = this.queries.loadFile(key2);
|
|
62073
|
+
return file3.jobs.find((j) => j.id === id);
|
|
61626
62074
|
}
|
|
61627
62075
|
assertValidCron(expression) {
|
|
61628
62076
|
try {
|
|
@@ -61744,28 +62192,28 @@ exit`;
|
|
|
61744
62192
|
// src/server/services/local-host-policy.ts
|
|
61745
62193
|
function resolveWorktreeHostId(requested) {
|
|
61746
62194
|
if (!isLocalHostEnabled()) {
|
|
61747
|
-
if (requested ===
|
|
62195
|
+
if (requested === LOCAL_HOST_ID7) throw new LocalHostDisabledError();
|
|
61748
62196
|
if (requested) return requested;
|
|
61749
62197
|
const configured = process.env.BAND_DEFAULT_HOST?.trim();
|
|
61750
62198
|
if (configured) {
|
|
61751
|
-
if (configured ===
|
|
62199
|
+
if (configured === LOCAL_HOST_ID7) throw new LocalHostDisabledError();
|
|
61752
62200
|
return configured;
|
|
61753
62201
|
}
|
|
61754
|
-
const online = tokenService.listHosts().filter((h) => h.id !==
|
|
62202
|
+
const online = tokenService.listHosts().filter((h) => h.id !== LOCAL_HOST_ID7 && h.status === "online");
|
|
61755
62203
|
if (online.length === 1) return online[0].id;
|
|
61756
62204
|
throw new NoDefaultHostError(
|
|
61757
62205
|
online.length === 0 ? "No worker is online." : "More than one worker is online."
|
|
61758
62206
|
);
|
|
61759
62207
|
}
|
|
61760
|
-
return requested ??
|
|
62208
|
+
return requested ?? LOCAL_HOST_ID7;
|
|
61761
62209
|
}
|
|
61762
|
-
var
|
|
62210
|
+
var LOCAL_HOST_ID7, LocalHostDisabledError, NoDefaultHostError;
|
|
61763
62211
|
var init_local_host_policy = __esm({
|
|
61764
62212
|
"src/server/services/local-host-policy.ts"() {
|
|
61765
62213
|
"use strict";
|
|
61766
62214
|
init_local_host_enabled();
|
|
61767
62215
|
init_token_service();
|
|
61768
|
-
|
|
62216
|
+
LOCAL_HOST_ID7 = "local";
|
|
61769
62217
|
LocalHostDisabledError = class extends Error {
|
|
61770
62218
|
constructor() {
|
|
61771
62219
|
super(
|
|
@@ -62225,6 +62673,7 @@ var init_worktree_script_service = __esm({
|
|
|
62225
62673
|
// src/server/services/worktree-service.ts
|
|
62226
62674
|
var worktree_service_exports = {};
|
|
62227
62675
|
__export(worktree_service_exports, {
|
|
62676
|
+
InvalidOriginError: () => InvalidOriginError,
|
|
62228
62677
|
PlainRepoError: () => PlainRepoError,
|
|
62229
62678
|
RepoNotFoundError: () => RepoNotFoundError,
|
|
62230
62679
|
WorktreeNotFoundError: () => WorktreeNotFoundError,
|
|
@@ -62236,11 +62685,12 @@ __export(worktree_service_exports, {
|
|
|
62236
62685
|
worktreeRemoveInput: () => worktreeRemoveInput,
|
|
62237
62686
|
worktreeRunScriptInput: () => worktreeRunScriptInput,
|
|
62238
62687
|
worktreeService: () => worktreeService,
|
|
62688
|
+
worktreeSetOriginInput: () => worktreeSetOriginInput,
|
|
62239
62689
|
worktreeSetPinnedInput: () => worktreeSetPinnedInput,
|
|
62240
62690
|
worktreeVia: () => worktreeVia
|
|
62241
62691
|
});
|
|
62242
62692
|
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
62243
|
-
import { unlinkSync as
|
|
62693
|
+
import { unlinkSync as unlinkSync5 } from "node:fs";
|
|
62244
62694
|
import { join as join41, posix as posix6 } from "node:path";
|
|
62245
62695
|
function findWorktreeRow(worktrees2, name4, hostId2) {
|
|
62246
62696
|
const named = worktrees2.filter((wt) => wt.name === name4);
|
|
@@ -62282,7 +62732,7 @@ function expandHome(given, home, hostId2) {
|
|
|
62282
62732
|
}
|
|
62283
62733
|
return path2;
|
|
62284
62734
|
}
|
|
62285
|
-
var TEARDOWN_TIMEOUT_MS, log33, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, PlainRepoError, WorktreeService, worktreeService;
|
|
62735
|
+
var TEARDOWN_TIMEOUT_MS, log33, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeSetOriginInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, InvalidOriginError, PlainRepoError, WorktreeService, worktreeService;
|
|
62286
62736
|
var init_worktree_service = __esm({
|
|
62287
62737
|
"src/server/services/worktree-service.ts"() {
|
|
62288
62738
|
"use strict";
|
|
@@ -62383,6 +62833,14 @@ var init_worktree_service = __esm({
|
|
|
62383
62833
|
hostId: external_exports2.string().optional(),
|
|
62384
62834
|
pinned: external_exports2.boolean()
|
|
62385
62835
|
});
|
|
62836
|
+
worktreeSetOriginInput = external_exports2.object({
|
|
62837
|
+
repo: external_exports2.string(),
|
|
62838
|
+
// Worktree identity (immutable `name`), not the live git branch.
|
|
62839
|
+
name: external_exports2.string(),
|
|
62840
|
+
hostId: external_exports2.string().optional(),
|
|
62841
|
+
// The parent worktree's id, or null to remove the parent.
|
|
62842
|
+
origin: external_exports2.string().min(1).nullable()
|
|
62843
|
+
});
|
|
62386
62844
|
worktreeGitInput = external_exports2.object({
|
|
62387
62845
|
repo: external_exports2.string(),
|
|
62388
62846
|
// Worktree identity (immutable `name`), not the live git branch.
|
|
@@ -62399,6 +62857,12 @@ var init_worktree_service = __esm({
|
|
|
62399
62857
|
this.name = "RepoNotFoundError";
|
|
62400
62858
|
}
|
|
62401
62859
|
};
|
|
62860
|
+
InvalidOriginError = class extends Error {
|
|
62861
|
+
constructor(message) {
|
|
62862
|
+
super(message);
|
|
62863
|
+
this.name = "InvalidOriginError";
|
|
62864
|
+
}
|
|
62865
|
+
};
|
|
62402
62866
|
PlainRepoError = class extends Error {
|
|
62403
62867
|
constructor(message) {
|
|
62404
62868
|
super(message);
|
|
@@ -62913,7 +63377,7 @@ var init_worktree_service = __esm({
|
|
|
62913
63377
|
saveState(state2);
|
|
62914
63378
|
removal.commit();
|
|
62915
63379
|
try {
|
|
62916
|
-
|
|
63380
|
+
unlinkSync5(join41(bandHome(), "workspace-prompts", `${worktreeId4}.json`));
|
|
62917
63381
|
} catch {
|
|
62918
63382
|
}
|
|
62919
63383
|
deleteWorktreeStatus(worktreeId4);
|
|
@@ -63075,6 +63539,52 @@ var init_worktree_service = __esm({
|
|
|
63075
63539
|
saveState(state2);
|
|
63076
63540
|
return { ok: true };
|
|
63077
63541
|
}
|
|
63542
|
+
/**
|
|
63543
|
+
* Sets a worktree's parent (its origin) by hand, or removes it with `null`. A manual parent
|
|
63544
|
+
* carries no chat or terminal. Rejects the worktree itself and any descendant of it, since
|
|
63545
|
+
* either would make a cycle.
|
|
63546
|
+
*/
|
|
63547
|
+
setOrigin(input) {
|
|
63548
|
+
const state2 = loadState();
|
|
63549
|
+
const repo = state2.repos.find((p2) => p2.name === input.repo);
|
|
63550
|
+
if (!repo) throw new RepoNotFoundError(input.repo);
|
|
63551
|
+
const target = findWorktreeRow(repo.worktrees, input.name, input.hostId);
|
|
63552
|
+
if (!target) throw new WorktreeNotFoundError(input.name);
|
|
63553
|
+
const targetHostId = target.hostId ?? "local";
|
|
63554
|
+
const worktreeId4 = toWorktreeId(repo.name, target.name, target.hostId);
|
|
63555
|
+
if (input.origin !== null) {
|
|
63556
|
+
const origins = /* @__PURE__ */ new Map();
|
|
63557
|
+
for (const r of state2.repos) {
|
|
63558
|
+
for (const wt of r.worktrees) {
|
|
63559
|
+
origins.set(toWorktreeId(r.name, wt.name, wt.hostId), wt.origin?.worktreeId);
|
|
63560
|
+
}
|
|
63561
|
+
}
|
|
63562
|
+
if (!origins.has(input.origin)) {
|
|
63563
|
+
throw new InvalidOriginError(`Unknown origin worktree: ${input.origin}`);
|
|
63564
|
+
}
|
|
63565
|
+
if (input.origin === worktreeId4) {
|
|
63566
|
+
throw new InvalidOriginError("A worktree cannot be its own parent.");
|
|
63567
|
+
}
|
|
63568
|
+
const seen = /* @__PURE__ */ new Set();
|
|
63569
|
+
for (let id = input.origin; id && !seen.has(id); ) {
|
|
63570
|
+
if (id === worktreeId4) {
|
|
63571
|
+
throw new InvalidOriginError(
|
|
63572
|
+
"That worktree is a descendant of this one, so it cannot be its parent."
|
|
63573
|
+
);
|
|
63574
|
+
}
|
|
63575
|
+
seen.add(id);
|
|
63576
|
+
id = origins.get(id);
|
|
63577
|
+
}
|
|
63578
|
+
}
|
|
63579
|
+
this.repoQueries.setOrigin(
|
|
63580
|
+
repo.name,
|
|
63581
|
+
target.name,
|
|
63582
|
+
input.origin === null ? null : { worktreeId: input.origin },
|
|
63583
|
+
targetHostId
|
|
63584
|
+
);
|
|
63585
|
+
emit({ kind: "worktree-origin-changed", worktreeId: worktreeId4 });
|
|
63586
|
+
return { ok: true };
|
|
63587
|
+
}
|
|
63078
63588
|
pruning = /* @__PURE__ */ new Set();
|
|
63079
63589
|
/**
|
|
63080
63590
|
* Removes the records of worktrees whose folder is gone from their host (`missing` after a scan),
|
|
@@ -63375,8 +63885,8 @@ var init_worktree_service = __esm({
|
|
|
63375
63885
|
throw new Error(`Script "${input.scriptType}" not found`);
|
|
63376
63886
|
}
|
|
63377
63887
|
try {
|
|
63378
|
-
const { file:
|
|
63379
|
-
await host.exec(
|
|
63888
|
+
const { file: file3, args } = scriptInvocation(scriptPath);
|
|
63889
|
+
await host.exec(file3, args, { cwd: input.path });
|
|
63380
63890
|
} catch (err) {
|
|
63381
63891
|
throw new Error(err instanceof Error ? err.message : String(err));
|
|
63382
63892
|
}
|
|
@@ -63388,15 +63898,6 @@ var init_worktree_service = __esm({
|
|
|
63388
63898
|
});
|
|
63389
63899
|
|
|
63390
63900
|
// 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
63901
|
function persistTask(task) {
|
|
63401
63902
|
const worktree = worktreeService.resolve(task.worktreeId);
|
|
63402
63903
|
try {
|
|
@@ -63429,19 +63930,19 @@ function fileUri(path2) {
|
|
|
63429
63930
|
async function promptBlocks(task, firstTurn, sharedDir) {
|
|
63430
63931
|
const caps = agentSessionService.promptCapabilities(task.chatId);
|
|
63431
63932
|
const blocks = [{ type: "text", text: task.prompt }];
|
|
63432
|
-
for (const
|
|
63433
|
-
const name4 =
|
|
63434
|
-
if (caps.image &&
|
|
63933
|
+
for (const file3 of task.attachments) {
|
|
63934
|
+
const name4 = file3.filename ?? file3.path.split("/").pop() ?? file3.path;
|
|
63935
|
+
if (caps.image && file3.mediaType.startsWith("image/")) {
|
|
63435
63936
|
try {
|
|
63436
63937
|
const host = hostRegistry.hostFor(task.worktreeId);
|
|
63437
|
-
const data = Buffer.from(await host.fs.readFile(
|
|
63438
|
-
blocks.push({ type: "image", mimeType:
|
|
63938
|
+
const data = Buffer.from(await host.fs.readFile(file3.path)).toString("base64");
|
|
63939
|
+
blocks.push({ type: "image", mimeType: file3.mediaType, data, uri: fileUri(file3.path) });
|
|
63439
63940
|
continue;
|
|
63440
63941
|
} catch (err) {
|
|
63441
|
-
log34.warn({ path:
|
|
63942
|
+
log34.warn({ path: file3.path, err }, "could not read image attachment; linking it instead");
|
|
63442
63943
|
}
|
|
63443
63944
|
}
|
|
63444
|
-
blocks.push({ type: "resource_link", uri: fileUri(
|
|
63945
|
+
blocks.push({ type: "resource_link", uri: fileUri(file3.path), name: name4, mimeType: file3.mediaType });
|
|
63445
63946
|
}
|
|
63446
63947
|
if (firstTurn) {
|
|
63447
63948
|
blocks.push({
|
|
@@ -63486,10 +63987,6 @@ function turnUsage(chatId, res, previousCost) {
|
|
|
63486
63987
|
});
|
|
63487
63988
|
return { ...usage, model, costUsd: (previousCost ?? 0) + cost };
|
|
63488
63989
|
}
|
|
63489
|
-
function setScopeStatus(scope3, chatId, status) {
|
|
63490
|
-
const updated = setWorktreeSourceStatus(scope3, chatStatusSource(chatId), { status });
|
|
63491
|
-
emit({ kind: "update", status: updated });
|
|
63492
|
-
}
|
|
63493
63990
|
function submitTask(options) {
|
|
63494
63991
|
const { worktreeId: worktreeId4, chatId, prompt, sessionId, mode, model, codingAgentId } = options;
|
|
63495
63992
|
if (worktreeService.resolve(worktreeId4) === null) {
|
|
@@ -63499,7 +63996,7 @@ function submitTask(options) {
|
|
|
63499
63996
|
if (existing?.status === "running") {
|
|
63500
63997
|
throw new TaskConflictError(chatId);
|
|
63501
63998
|
}
|
|
63502
|
-
|
|
63999
|
+
observeAgentStatus(hasRunningTask);
|
|
63503
64000
|
const task = {
|
|
63504
64001
|
id: generateTaskId(),
|
|
63505
64002
|
worktreeId: worktreeId4,
|
|
@@ -63543,7 +64040,7 @@ function submitOrQueueTask(options) {
|
|
|
63543
64040
|
async function runTask(task) {
|
|
63544
64041
|
const { chatId } = task;
|
|
63545
64042
|
chatService.updateStatus(chatId, "running");
|
|
63546
|
-
|
|
64043
|
+
beginBandTurn(task.worktreeId, chatId);
|
|
63547
64044
|
const chat = chatService.get(chatId);
|
|
63548
64045
|
if (task.codingAgentId && chat && task.codingAgentId !== chat.agent) {
|
|
63549
64046
|
agentSessionService.stop(chatId);
|
|
@@ -63643,7 +64140,7 @@ async function runTask(task) {
|
|
|
63643
64140
|
durationMs: Date.now() - task.startedAt,
|
|
63644
64141
|
usage: turnUsage(chatId, res, previousCost)
|
|
63645
64142
|
});
|
|
63646
|
-
finishTask(task, res.stopReason === "cancelled" ? "cancelled" : "completed");
|
|
64143
|
+
finishTask(task, res.stopReason === "cancelled" ? "cancelled" : "completed", res.stopReason);
|
|
63647
64144
|
} catch (err) {
|
|
63648
64145
|
const message = err instanceof Error ? err.message : String(err);
|
|
63649
64146
|
agentSessionService.record(chatId, {
|
|
@@ -63667,7 +64164,7 @@ async function applyTurnChoice(chatId, category, value) {
|
|
|
63667
64164
|
log34.warn({ chatId, category, value, err }, "could not apply the task's setting");
|
|
63668
64165
|
}
|
|
63669
64166
|
}
|
|
63670
|
-
function finishTask(task, outcome) {
|
|
64167
|
+
function finishTask(task, outcome, stopReason) {
|
|
63671
64168
|
if (task.status !== "running") return;
|
|
63672
64169
|
const status = outcome === "completed" ? "completed" : "failed";
|
|
63673
64170
|
task.status = status;
|
|
@@ -63678,7 +64175,8 @@ function finishTask(task, outcome) {
|
|
|
63678
64175
|
if (status === "completed" && drainQueue(task.worktreeId, task.chatId)) return;
|
|
63679
64176
|
chatService.updateStatus(task.chatId, status === "completed" ? "idle" : "error");
|
|
63680
64177
|
const stopped = outcome === "cancelled" || task.cancelRequested === true;
|
|
63681
|
-
|
|
64178
|
+
const failed = outcome === "failed" || stopReason !== void 0 && FAILED_STOP_REASONS.has(stopReason);
|
|
64179
|
+
endBandTurn(task.worktreeId, task.chatId, stopped ? "idle" : failed ? "failed" : "done");
|
|
63682
64180
|
}
|
|
63683
64181
|
function drainQueue(worktreeId4, chatId) {
|
|
63684
64182
|
const queued = peekQueuedMessage(chatId);
|
|
@@ -63721,7 +64219,7 @@ function cancelTask(taskId) {
|
|
|
63721
64219
|
const record3 = taskQueries.markFailed(taskId);
|
|
63722
64220
|
if (record3) {
|
|
63723
64221
|
if (record3.chatId) {
|
|
63724
|
-
|
|
64222
|
+
setChatStatus(record3.worktreeId, record3.chatId, "idle");
|
|
63725
64223
|
}
|
|
63726
64224
|
log34.info({ taskId, worktreeId: record3.worktreeId }, "orphaned task cancelled");
|
|
63727
64225
|
return { cancelled: true, worktreeId: record3.worktreeId };
|
|
@@ -63735,7 +64233,7 @@ function getTask(chatId) {
|
|
|
63735
64233
|
const task = tasks2.get(chatId);
|
|
63736
64234
|
return task ? toTaskInfo(task) : null;
|
|
63737
64235
|
}
|
|
63738
|
-
var log34, taskQueries, TASKS_KEY, g4, tasks2,
|
|
64236
|
+
var log34, taskQueries, TASKS_KEY, g4, tasks2, TaskConflictError, FAILED_STOP_REASONS, TaskService, taskService;
|
|
63739
64237
|
var init_task_service = __esm({
|
|
63740
64238
|
"src/server/services/task-service.ts"() {
|
|
63741
64239
|
"use strict";
|
|
@@ -63750,8 +64248,8 @@ var init_task_service = __esm({
|
|
|
63750
64248
|
init_turn_events();
|
|
63751
64249
|
init_agent_session_service();
|
|
63752
64250
|
init_chat_service();
|
|
64251
|
+
init_chat_status_service();
|
|
63753
64252
|
init_state();
|
|
63754
|
-
init_watcher_service();
|
|
63755
64253
|
init_worktree_service();
|
|
63756
64254
|
log34 = createLogger("task-service");
|
|
63757
64255
|
taskQueries = new TaskQueries();
|
|
@@ -63759,13 +64257,13 @@ var init_task_service = __esm({
|
|
|
63759
64257
|
g4 = globalThis;
|
|
63760
64258
|
if (!g4[TASKS_KEY]) g4[TASKS_KEY] = /* @__PURE__ */ new Map();
|
|
63761
64259
|
tasks2 = g4[TASKS_KEY];
|
|
63762
|
-
observingPending = false;
|
|
63763
64260
|
TaskConflictError = class extends Error {
|
|
63764
64261
|
constructor(chatId) {
|
|
63765
64262
|
super(`Task already running for chat ${chatId}`);
|
|
63766
64263
|
this.name = "TaskConflictError";
|
|
63767
64264
|
}
|
|
63768
64265
|
};
|
|
64266
|
+
FAILED_STOP_REASONS = /* @__PURE__ */ new Set(["refusal", "max_tokens", "max_turn_requests"]);
|
|
63769
64267
|
TaskService = class {
|
|
63770
64268
|
constructor(queries3 = taskQueries) {
|
|
63771
64269
|
this.queries = queries3;
|
|
@@ -63785,13 +64283,10 @@ var init_task_service = __esm({
|
|
|
63785
64283
|
getTask(chatId) {
|
|
63786
64284
|
return getTask(chatId);
|
|
63787
64285
|
}
|
|
63788
|
-
/**
|
|
63789
|
-
* still waits on the user
|
|
64286
|
+
/** Sets the worktree's finished chats (`done`, `failed`) back to `idle`. A
|
|
64287
|
+
* chat whose agent still waits on the user keeps its `question`. */
|
|
63790
64288
|
acknowledgeAttention(worktreeId4) {
|
|
63791
|
-
return acknowledgeWorktreeAttention(
|
|
63792
|
-
worktreeId4,
|
|
63793
|
-
agentSessionService.chatsWithPendingRequest(worktreeId4)
|
|
63794
|
-
);
|
|
64289
|
+
return acknowledgeWorktreeAttention(worktreeId4);
|
|
63795
64290
|
}
|
|
63796
64291
|
listTaskRecords(filters) {
|
|
63797
64292
|
return this.queries.list(filters);
|
|
@@ -65365,9 +65860,88 @@ function parseReview(data, branch, defaultBranch) {
|
|
|
65365
65860
|
updatedAt: pr.updatedAt,
|
|
65366
65861
|
reviewDecision: reviewDecision(pr.reviewDecision),
|
|
65367
65862
|
mergeState: mergeState(pr.mergeStateStatus),
|
|
65368
|
-
checks: report(head?.oid ?? null, checks)
|
|
65863
|
+
checks: report(head?.oid ?? null, checks),
|
|
65864
|
+
reviewers: parseReviewers(pr),
|
|
65865
|
+
discussion: parseDiscussion(pr)
|
|
65866
|
+
};
|
|
65867
|
+
}
|
|
65868
|
+
function parseReviewers(pr) {
|
|
65869
|
+
const byLogin = /* @__PURE__ */ new Map();
|
|
65870
|
+
const order = [...pr.reviews?.nodes ?? []].sort(
|
|
65871
|
+
(a, b2) => (a.submittedAt ?? "").localeCompare(b2.submittedAt ?? "")
|
|
65872
|
+
);
|
|
65873
|
+
for (const review of order) {
|
|
65874
|
+
const state2 = REVIEWER_STATES[review.state];
|
|
65875
|
+
if (!state2 || !review.author) continue;
|
|
65876
|
+
const existing = byLogin.get(review.author.login);
|
|
65877
|
+
if (existing && state2 === "commented" && existing.state !== "commented") continue;
|
|
65878
|
+
byLogin.set(review.author.login, {
|
|
65879
|
+
...actorOf(review.author),
|
|
65880
|
+
state: state2,
|
|
65881
|
+
submittedAt: review.submittedAt
|
|
65882
|
+
});
|
|
65883
|
+
}
|
|
65884
|
+
for (const request of pr.reviewRequests?.nodes ?? []) {
|
|
65885
|
+
const who = request.requestedReviewer;
|
|
65886
|
+
if (!who) continue;
|
|
65887
|
+
const actor = who.__typename === "Team" ? { login: who.slug, avatarUrl: null } : actorOf({ login: who.login, avatarUrl: who.avatarUrl });
|
|
65888
|
+
const key2 = who.__typename === "Team" ? `team:${actor.login}` : actor.login;
|
|
65889
|
+
const previous = byLogin.get(key2);
|
|
65890
|
+
byLogin.set(key2, {
|
|
65891
|
+
...actor,
|
|
65892
|
+
state: "pending",
|
|
65893
|
+
submittedAt: previous?.submittedAt ?? null,
|
|
65894
|
+
...request.asCodeOwner ? { required: true } : {}
|
|
65895
|
+
});
|
|
65896
|
+
}
|
|
65897
|
+
return [...byLogin.values()];
|
|
65898
|
+
}
|
|
65899
|
+
function fromCommentNode(node) {
|
|
65900
|
+
return {
|
|
65901
|
+
id: node.id,
|
|
65902
|
+
author: actorOf(node.author),
|
|
65903
|
+
body: node.body,
|
|
65904
|
+
createdAt: node.createdAt,
|
|
65905
|
+
url: webUrl(node.url)
|
|
65369
65906
|
};
|
|
65370
65907
|
}
|
|
65908
|
+
function parseDiscussion(pr) {
|
|
65909
|
+
const items = [];
|
|
65910
|
+
for (const node of pr.comments?.nodes ?? []) {
|
|
65911
|
+
items.push({ ...fromCommentNode(node), kind: "comment" });
|
|
65912
|
+
}
|
|
65913
|
+
for (const review of pr.reviews?.nodes ?? []) {
|
|
65914
|
+
const state2 = REVIEWER_STATES[review.state];
|
|
65915
|
+
if (!state2 || !review.body.trim() || !review.submittedAt) continue;
|
|
65916
|
+
items.push({
|
|
65917
|
+
id: review.id,
|
|
65918
|
+
kind: "review",
|
|
65919
|
+
reviewState: state2,
|
|
65920
|
+
author: actorOf(review.author),
|
|
65921
|
+
body: review.body,
|
|
65922
|
+
createdAt: review.submittedAt,
|
|
65923
|
+
url: webUrl(review.url)
|
|
65924
|
+
});
|
|
65925
|
+
}
|
|
65926
|
+
items.sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
65927
|
+
const threads = [];
|
|
65928
|
+
for (const node of pr.reviewThreads?.nodes ?? []) {
|
|
65929
|
+
const comments = node.comments.nodes.map(fromCommentNode);
|
|
65930
|
+
if (comments.length === 0) continue;
|
|
65931
|
+
threads.push({
|
|
65932
|
+
id: node.id,
|
|
65933
|
+
path: node.path,
|
|
65934
|
+
line: node.line ?? node.originalLine,
|
|
65935
|
+
isResolved: node.isResolved,
|
|
65936
|
+
isOutdated: node.isOutdated,
|
|
65937
|
+
comments,
|
|
65938
|
+
...node.comments.totalCount > comments.length ? { truncated: true } : {}
|
|
65939
|
+
});
|
|
65940
|
+
}
|
|
65941
|
+
threads.sort((a, b2) => a.comments[0].createdAt.localeCompare(b2.comments[0].createdAt));
|
|
65942
|
+
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);
|
|
65943
|
+
return { items, threads, ...truncated ? { truncated: true } : {} };
|
|
65944
|
+
}
|
|
65371
65945
|
function parseBranchChecks(data) {
|
|
65372
65946
|
const target = data.repository?.ref?.target;
|
|
65373
65947
|
const latest = /* @__PURE__ */ new Map();
|
|
@@ -65484,10 +66058,16 @@ function reviewDecision(value) {
|
|
|
65484
66058
|
function mergeState(value) {
|
|
65485
66059
|
return value && MERGE_STATES[value] || "unknown";
|
|
65486
66060
|
}
|
|
65487
|
-
var CHECK_RUN_FIELDS, REVIEW_QUERY, LIST_ORDER, AGGREGATE_PRIORITY, MERGE_STATES;
|
|
66061
|
+
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
66062
|
var init_checks_query = __esm({
|
|
65489
66063
|
"../../plugins/github/src/server/checks-query.ts"() {
|
|
65490
66064
|
"use strict";
|
|
66065
|
+
MAX_COMMENTS = 50;
|
|
66066
|
+
MAX_REVIEWS = 50;
|
|
66067
|
+
MAX_THREADS = 50;
|
|
66068
|
+
MAX_THREAD_COMMENTS = 20;
|
|
66069
|
+
MAX_REVIEW_REQUESTS = 30;
|
|
66070
|
+
ACTOR = "login avatarUrl";
|
|
65491
66071
|
CHECK_RUN_FIELDS = `
|
|
65492
66072
|
databaseId
|
|
65493
66073
|
name
|
|
@@ -65512,6 +66092,41 @@ var init_checks_query = __esm({
|
|
|
65512
66092
|
updatedAt
|
|
65513
66093
|
mergeStateStatus
|
|
65514
66094
|
reviewDecision
|
|
66095
|
+
reviewRequests(first: ${MAX_REVIEW_REQUESTS}) {
|
|
66096
|
+
nodes {
|
|
66097
|
+
asCodeOwner
|
|
66098
|
+
requestedReviewer {
|
|
66099
|
+
__typename
|
|
66100
|
+
... on User { ${ACTOR} }
|
|
66101
|
+
... on Bot { ${ACTOR} }
|
|
66102
|
+
... on Mannequin { ${ACTOR} }
|
|
66103
|
+
... on Team { slug }
|
|
66104
|
+
}
|
|
66105
|
+
}
|
|
66106
|
+
}
|
|
66107
|
+
reviews(last: ${MAX_REVIEWS}) {
|
|
66108
|
+
totalCount
|
|
66109
|
+
nodes { id state body submittedAt url author { ${ACTOR} } }
|
|
66110
|
+
}
|
|
66111
|
+
comments(last: ${MAX_COMMENTS}) {
|
|
66112
|
+
totalCount
|
|
66113
|
+
nodes { id body createdAt url author { ${ACTOR} } }
|
|
66114
|
+
}
|
|
66115
|
+
reviewThreads(first: ${MAX_THREADS}) {
|
|
66116
|
+
totalCount
|
|
66117
|
+
nodes {
|
|
66118
|
+
id
|
|
66119
|
+
isResolved
|
|
66120
|
+
isOutdated
|
|
66121
|
+
path
|
|
66122
|
+
line
|
|
66123
|
+
originalLine
|
|
66124
|
+
comments(first: ${MAX_THREAD_COMMENTS}) {
|
|
66125
|
+
totalCount
|
|
66126
|
+
nodes { id body createdAt url author { ${ACTOR} } }
|
|
66127
|
+
}
|
|
66128
|
+
}
|
|
66129
|
+
}
|
|
65515
66130
|
commits(last: 1) {
|
|
65516
66131
|
nodes {
|
|
65517
66132
|
commit {
|
|
@@ -65549,6 +66164,16 @@ var init_checks_query = __esm({
|
|
|
65549
66164
|
}
|
|
65550
66165
|
}
|
|
65551
66166
|
}`;
|
|
66167
|
+
REVIEWER_STATES = {
|
|
66168
|
+
APPROVED: "approved",
|
|
66169
|
+
CHANGES_REQUESTED: "changes_requested",
|
|
66170
|
+
COMMENTED: "commented",
|
|
66171
|
+
DISMISSED: "dismissed"
|
|
66172
|
+
};
|
|
66173
|
+
actorOf = (author) => ({
|
|
66174
|
+
login: author?.login ?? "ghost",
|
|
66175
|
+
avatarUrl: webUrl(author?.avatarUrl ?? null)
|
|
66176
|
+
});
|
|
65552
66177
|
LIST_ORDER = {
|
|
65553
66178
|
failure: 0,
|
|
65554
66179
|
running: 1,
|
|
@@ -68049,11 +68674,14 @@ function stopRuntime(rt) {
|
|
|
68049
68674
|
}
|
|
68050
68675
|
function idleTimeoutMs2() {
|
|
68051
68676
|
const override = Number(process.env.BAND_AGENT_IDLE_TIMEOUT_MS);
|
|
68052
|
-
return Number.isFinite(override) && override > 0 ? override :
|
|
68677
|
+
return Number.isFinite(override) && override > 0 ? override : null;
|
|
68053
68678
|
}
|
|
68054
|
-
function scheduleIdle(rt, delayMs
|
|
68679
|
+
function scheduleIdle(rt, delayMs) {
|
|
68055
68680
|
if (rt.idleTimer) clearTimeout(rt.idleTimer);
|
|
68056
|
-
|
|
68681
|
+
rt.idleTimer = null;
|
|
68682
|
+
const timeout = idleTimeoutMs2();
|
|
68683
|
+
if (timeout === null) return;
|
|
68684
|
+
const delay2 = Math.min(delayMs ?? timeout, MAX_TIMER_MS2);
|
|
68057
68685
|
rt.idleTimer = setTimeout(() => {
|
|
68058
68686
|
rt.idleTimer = null;
|
|
68059
68687
|
if (rt.inTurn || rt.pending.size > 0 || rt.attaching) return;
|
|
@@ -68068,7 +68696,7 @@ function scheduleIdle(rt, delayMs = idleTimeoutMs2()) {
|
|
|
68068
68696
|
},
|
|
68069
68697
|
"idle agent has pending work; not stopping it"
|
|
68070
68698
|
);
|
|
68071
|
-
scheduleIdle(rt, until - now +
|
|
68699
|
+
scheduleIdle(rt, until - now + timeout);
|
|
68072
68700
|
return;
|
|
68073
68701
|
}
|
|
68074
68702
|
log46.info({ chatId: rt.chatId }, "stopping idle agent");
|
|
@@ -68208,6 +68836,9 @@ function routeUpdate(rt, notification) {
|
|
|
68208
68836
|
if (!rt.attaching) {
|
|
68209
68837
|
rt.work.observeUpdate(update, rt.inTurn);
|
|
68210
68838
|
agentActive(rt);
|
|
68839
|
+
if (!rt.inTurn && rt.routing === "log" && AGENT_WORK_UPDATES.has(update.sessionUpdate)) {
|
|
68840
|
+
activityObserver?.(rt.chatId, rt.worktreeId, "update");
|
|
68841
|
+
}
|
|
68211
68842
|
}
|
|
68212
68843
|
if (rt.routing === "drop") return;
|
|
68213
68844
|
if (update.sessionUpdate === "user_message_chunk") {
|
|
@@ -68283,6 +68914,7 @@ function handlersFor(rt, generation) {
|
|
|
68283
68914
|
if (n.sessionId !== rt.sessionId || rt.attaching) return;
|
|
68284
68915
|
rt.work.observeAsyncTask(n.update);
|
|
68285
68916
|
agentActive(rt);
|
|
68917
|
+
activityObserver?.(rt.chatId, rt.worktreeId, "work");
|
|
68286
68918
|
},
|
|
68287
68919
|
onPermission: (req, signal) => requestPermission(rt, req, signal),
|
|
68288
68920
|
onElicitation: (req, signal) => requestElicitation(rt, req, signal),
|
|
@@ -68582,7 +69214,7 @@ function sessionCost(sessionId) {
|
|
|
68582
69214
|
const row = events.latest(sessionId, revision, { kind: "turn-ended" });
|
|
68583
69215
|
return row?.event.type === "turn-ended" ? row.event.usage?.costUsd ?? null : null;
|
|
68584
69216
|
}
|
|
68585
|
-
var log46,
|
|
69217
|
+
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
69218
|
var init_agent_session_service = __esm({
|
|
68587
69219
|
"src/server/services/agent-session-service.ts"() {
|
|
68588
69220
|
"use strict";
|
|
@@ -68600,9 +69232,8 @@ var init_agent_session_service = __esm({
|
|
|
68600
69232
|
init_mcp_proxy_service();
|
|
68601
69233
|
init_worktree_service();
|
|
68602
69234
|
log46 = createLogger("agent-sessions");
|
|
68603
|
-
IDLE_TIMEOUT_MS = 15 * 6e4;
|
|
68604
69235
|
CANCEL_GRACE_MS = 15e3;
|
|
68605
|
-
|
|
69236
|
+
MAX_TIMER_MS2 = 2 ** 31 - 1;
|
|
68606
69237
|
PROBE_SETTLE_MS = 1500;
|
|
68607
69238
|
events = new ChatEventQueries();
|
|
68608
69239
|
settings = new SettingsQueries();
|
|
@@ -68621,6 +69252,15 @@ var init_agent_session_service = __esm({
|
|
|
68621
69252
|
defaultsChangedAt = g6[DEFAULTS_CHANGED_KEY];
|
|
68622
69253
|
transientId = -1e9;
|
|
68623
69254
|
pendingObserver = null;
|
|
69255
|
+
AGENT_WORK_UPDATES = /* @__PURE__ */ new Set([
|
|
69256
|
+
"user_message_chunk",
|
|
69257
|
+
"agent_message_chunk",
|
|
69258
|
+
"agent_thought_chunk",
|
|
69259
|
+
"tool_call",
|
|
69260
|
+
"tool_call_update",
|
|
69261
|
+
"plan"
|
|
69262
|
+
]);
|
|
69263
|
+
activityObserver = null;
|
|
68624
69264
|
ChatNotFoundError = class extends Error {
|
|
68625
69265
|
constructor(chatId) {
|
|
68626
69266
|
super(`Chat not found: ${chatId}`);
|
|
@@ -68647,6 +69287,16 @@ var init_agent_session_service = __esm({
|
|
|
68647
69287
|
observePending(observer) {
|
|
68648
69288
|
pendingObserver = observer;
|
|
68649
69289
|
}
|
|
69290
|
+
/** Called when the agent acts outside a turn Band started: an update from
|
|
69291
|
+
* a turn of its own, or a background task starting or ending. */
|
|
69292
|
+
observeActivity(observer) {
|
|
69293
|
+
activityObserver = observer;
|
|
69294
|
+
}
|
|
69295
|
+
/** The soonest time one of the chat's pending-work holds lapses, or null with none. */
|
|
69296
|
+
nextWorkDeadline(chatId) {
|
|
69297
|
+
const holds = runtimes.get(chatId)?.work.outstanding() ?? [];
|
|
69298
|
+
return holds.length > 0 ? Math.min(...holds.map((h) => h.until)) : null;
|
|
69299
|
+
}
|
|
68650
69300
|
/**
|
|
68651
69301
|
* Makes sure the chat's agent process runs and is attached to the chat's
|
|
68652
69302
|
* session, starting a new session when the chat has none. For `view`,
|
|
@@ -75914,10 +76564,10 @@ init_agent_spawn();
|
|
|
75914
76564
|
import {
|
|
75915
76565
|
appendFileSync as appendFileSync2,
|
|
75916
76566
|
createReadStream as createReadStream5,
|
|
75917
|
-
existsSync as
|
|
75918
|
-
mkdirSync as
|
|
76567
|
+
existsSync as existsSync17,
|
|
76568
|
+
mkdirSync as mkdirSync10,
|
|
75919
76569
|
readFileSync as readFileSync12,
|
|
75920
|
-
statSync as
|
|
76570
|
+
statSync as statSync15
|
|
75921
76571
|
} from "node:fs";
|
|
75922
76572
|
import { createServer as createServer3 } from "node:http";
|
|
75923
76573
|
import { basename as basename7, join as join55, resolve as resolve14, sep as sep10 } from "node:path";
|
|
@@ -75928,7 +76578,7 @@ import { parseArgs } from "node:util";
|
|
|
75928
76578
|
init_src();
|
|
75929
76579
|
init_daemon_backend();
|
|
75930
76580
|
init_in_process_backend();
|
|
75931
|
-
import { existsSync as
|
|
76581
|
+
import { existsSync as existsSync5, statSync as statSync5 } from "node:fs";
|
|
75932
76582
|
import { join as join7 } from "node:path";
|
|
75933
76583
|
var log4 = createLogger("terminal-backend");
|
|
75934
76584
|
function createTerminalBackend(serverRoot, bandHome2) {
|
|
@@ -75937,7 +76587,7 @@ function createTerminalBackend(serverRoot, bandHome2) {
|
|
|
75937
76587
|
const entry = [
|
|
75938
76588
|
join7(serverRoot, "terminal-daemon.mjs"),
|
|
75939
76589
|
join7(serverRoot, "terminal-daemon.ts")
|
|
75940
|
-
].find((candidate) =>
|
|
76590
|
+
].find((candidate) => existsSync5(candidate));
|
|
75941
76591
|
if (!entry) {
|
|
75942
76592
|
log4.error(
|
|
75943
76593
|
{ serverRoot },
|
|
@@ -75945,7 +76595,7 @@ function createTerminalBackend(serverRoot, bandHome2) {
|
|
|
75945
76595
|
);
|
|
75946
76596
|
return new InProcessTerminalBackend();
|
|
75947
76597
|
}
|
|
75948
|
-
const { size, mtimeMs } =
|
|
76598
|
+
const { size, mtimeMs } = statSync5(entry);
|
|
75949
76599
|
return new DaemonTerminalBackend({
|
|
75950
76600
|
entry,
|
|
75951
76601
|
runDir: join7(bandHome2, "run"),
|
|
@@ -78243,14 +78893,14 @@ import { join as join9, normalize, resolve as resolve2, sep as sep2 } from "node
|
|
|
78243
78893
|
|
|
78244
78894
|
// ../../node_modules/.pnpm/totalist@3.0.1/node_modules/totalist/sync/index.mjs
|
|
78245
78895
|
import { join as join8, resolve } from "path";
|
|
78246
|
-
import { readdirSync as readdirSync5, statSync as
|
|
78896
|
+
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
78247
78897
|
function totalist(dir, callback, pre = "") {
|
|
78248
78898
|
dir = resolve(".", dir);
|
|
78249
78899
|
let arr = readdirSync5(dir);
|
|
78250
78900
|
let i = 0, abs, stats;
|
|
78251
78901
|
for (; i < arr.length; i++) {
|
|
78252
78902
|
abs = join8(dir, arr[i]);
|
|
78253
|
-
stats =
|
|
78903
|
+
stats = statSync6(abs);
|
|
78254
78904
|
stats.isDirectory() ? totalist(abs, callback, join8(pre, arr[i])) : callback(join8(pre, arr[i]), abs, stats);
|
|
78255
78905
|
}
|
|
78256
78906
|
}
|
|
@@ -78774,7 +79424,7 @@ function viaLocal(dir, isEtag, uri, extns) {
|
|
|
78774
79424
|
function is404(req, res) {
|
|
78775
79425
|
return res.statusCode = 404, res.end();
|
|
78776
79426
|
}
|
|
78777
|
-
function send(req, res,
|
|
79427
|
+
function send(req, res, file3, stats, headers) {
|
|
78778
79428
|
let code = 200, tmp, opts = {};
|
|
78779
79429
|
headers = { ...headers };
|
|
78780
79430
|
for (let key2 in headers) {
|
|
@@ -78802,7 +79452,7 @@ function send(req, res, file2, stats, headers) {
|
|
|
78802
79452
|
headers["Accept-Ranges"] = "bytes";
|
|
78803
79453
|
}
|
|
78804
79454
|
res.writeHead(code, headers);
|
|
78805
|
-
fs.createReadStream(
|
|
79455
|
+
fs.createReadStream(file3, opts).pipe(res);
|
|
78806
79456
|
}
|
|
78807
79457
|
var ENCODING = {
|
|
78808
79458
|
".br": "br",
|
|
@@ -79524,7 +80174,7 @@ init_map_limited();
|
|
|
79524
80174
|
init_remote_worktrees();
|
|
79525
80175
|
init_repo_host_policy();
|
|
79526
80176
|
init_ephemeral_lifecycle_service();
|
|
79527
|
-
import { existsSync as
|
|
80177
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
79528
80178
|
import { basename as basename2, join as join44, posix as posix7, resolve as resolve9 } from "node:path";
|
|
79529
80179
|
|
|
79530
80180
|
// src/server/services/repo-avatar-service.ts
|
|
@@ -79916,7 +80566,7 @@ var RepoService = class {
|
|
|
79916
80566
|
}
|
|
79917
80567
|
}
|
|
79918
80568
|
const resolvedPath = resolve9(path2);
|
|
79919
|
-
const kind =
|
|
80569
|
+
const kind = existsSync15(join44(resolvedPath, ".git")) ? "git" : "plain";
|
|
79920
80570
|
let defaultBranch = "main";
|
|
79921
80571
|
let worktrees2 = [];
|
|
79922
80572
|
if (kind === "git") {
|
|
@@ -80253,15 +80903,15 @@ var RepoService = class {
|
|
|
80253
80903
|
...isLocalHostEnabled() ? ["local"] : [],
|
|
80254
80904
|
...tokenService.listHosts().filter((h) => h.id !== "local" && h.status === "online").map((h) => h.id)
|
|
80255
80905
|
];
|
|
80256
|
-
let
|
|
80906
|
+
let failure2 = "no host is available to ask";
|
|
80257
80907
|
for (const hostId2 of hostIds2) {
|
|
80258
80908
|
try {
|
|
80259
80909
|
const { stdout } = await hostRegistry.hostById(hostId2).exec("git", ["ls-remote", "--symref", "--", url2, "HEAD"], { timeoutMs: 6e4 });
|
|
80260
80910
|
const match = stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
|
|
80261
80911
|
if (match) return match[1];
|
|
80262
|
-
|
|
80912
|
+
failure2 = "the remote did not report a default branch";
|
|
80263
80913
|
} catch (err) {
|
|
80264
|
-
|
|
80914
|
+
failure2 = err instanceof Error ? err.message : String(err);
|
|
80265
80915
|
}
|
|
80266
80916
|
}
|
|
80267
80917
|
if (asked?.host) {
|
|
@@ -80271,7 +80921,7 @@ var RepoService = class {
|
|
|
80271
80921
|
if (viaApi) return viaApi;
|
|
80272
80922
|
}
|
|
80273
80923
|
throw new RepoInputError(
|
|
80274
|
-
`Could not read the default branch of ${asked?.url ?? url2} (${
|
|
80924
|
+
`Could not read the default branch of ${asked?.url ?? url2} (${failure2}). Pass the branch.`
|
|
80275
80925
|
);
|
|
80276
80926
|
}
|
|
80277
80927
|
/**
|
|
@@ -80294,7 +80944,7 @@ var RepoService = class {
|
|
|
80294
80944
|
message: `Repo "${name4}" is already a git repo`
|
|
80295
80945
|
});
|
|
80296
80946
|
}
|
|
80297
|
-
if (!
|
|
80947
|
+
if (!existsSync15(repo.path)) {
|
|
80298
80948
|
throw new TRPCError({
|
|
80299
80949
|
code: "NOT_FOUND",
|
|
80300
80950
|
message: `Repo path "${repo.path}" no longer exists. Remove the repo and re-add it.`
|
|
@@ -80511,7 +81161,7 @@ var DEFAULT_MAX_BYTES = 200 * 1024 * 1024;
|
|
|
80511
81161
|
var DEFAULT_QUIET_MS = 3 * 6e4;
|
|
80512
81162
|
var DEFAULT_POLL_MS2 = 6e4;
|
|
80513
81163
|
var MAX_VIEW_BYTES = 1024 * 1024;
|
|
80514
|
-
var
|
|
81164
|
+
var LOCAL_HOST_ID8 = "local";
|
|
80515
81165
|
var errorText3 = (err) => err instanceof Error ? err.message : String(err);
|
|
80516
81166
|
function envNumber2(name4, fallback) {
|
|
80517
81167
|
const raw = Number(process.env[name4]);
|
|
@@ -80519,7 +81169,7 @@ function envNumber2(name4, fallback) {
|
|
|
80519
81169
|
}
|
|
80520
81170
|
var checkpointKeep = () => Math.floor(envNumber2("BAND_CHECKPOINT_KEEP", DEFAULT_KEEP));
|
|
80521
81171
|
var checkpointMaxBytes = () => envNumber2("BAND_CHECKPOINT_MAX_BYTES", DEFAULT_MAX_BYTES);
|
|
80522
|
-
var
|
|
81172
|
+
var quietMs2 = () => envNumber2("BAND_CHECKPOINT_QUIET_MS", DEFAULT_QUIET_MS);
|
|
80523
81173
|
var pollMs = () => envNumber2("BAND_CHECKPOINT_POLL_MS", DEFAULT_POLL_MS2);
|
|
80524
81174
|
function toView2(row) {
|
|
80525
81175
|
return {
|
|
@@ -80536,11 +81186,11 @@ function toView2(row) {
|
|
|
80536
81186
|
sessions: row.sessionIds.length
|
|
80537
81187
|
};
|
|
80538
81188
|
}
|
|
80539
|
-
function isSafeSessionFile(
|
|
80540
|
-
if (!/^[A-Za-z0-9_-]+$/.test(
|
|
80541
|
-
if (typeof
|
|
80542
|
-
if (posix8.isAbsolute(
|
|
80543
|
-
const clean = posix8.normalize(
|
|
81189
|
+
function isSafeSessionFile(file3) {
|
|
81190
|
+
if (!/^[A-Za-z0-9_-]+$/.test(file3.root)) return false;
|
|
81191
|
+
if (typeof file3.rel !== "string" || file3.rel === "" || file3.rel.includes("\0")) return false;
|
|
81192
|
+
if (posix8.isAbsolute(file3.rel)) return false;
|
|
81193
|
+
const clean = posix8.normalize(file3.rel);
|
|
80544
81194
|
return clean !== ".." && !clean.startsWith("../") && clean !== ".";
|
|
80545
81195
|
}
|
|
80546
81196
|
function sessionsHashOf(files) {
|
|
@@ -80629,8 +81279,8 @@ var CheckpointService = class {
|
|
|
80629
81279
|
ineligible(worktreeId4) {
|
|
80630
81280
|
const found = worktreeService.resolve(worktreeId4);
|
|
80631
81281
|
if (!found) return "unknown worktree";
|
|
80632
|
-
const hostId2 = found.worktree.hostId ??
|
|
80633
|
-
if (hostId2 ===
|
|
81282
|
+
const hostId2 = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81283
|
+
if (hostId2 === LOCAL_HOST_ID8) return "the worktree is on the hub's own machine";
|
|
80634
81284
|
if (found.repo.kind !== "git") return "not a git repo";
|
|
80635
81285
|
if (found.worktree.supersededBy) return "superseded";
|
|
80636
81286
|
if (this.busy.has(worktreeId4)) return "busy";
|
|
@@ -80673,7 +81323,7 @@ var CheckpointService = class {
|
|
|
80673
81323
|
const out = [];
|
|
80674
81324
|
for (const repo of loadState().repos) {
|
|
80675
81325
|
for (const wt of repo.worktrees) {
|
|
80676
|
-
if ((wt.hostId ??
|
|
81326
|
+
if ((wt.hostId ?? LOCAL_HOST_ID8) === hostId2) {
|
|
80677
81327
|
out.push({
|
|
80678
81328
|
repo: repo.name,
|
|
80679
81329
|
name: wt.name,
|
|
@@ -80687,8 +81337,8 @@ var CheckpointService = class {
|
|
|
80687
81337
|
async doTake(worktreeId4, trigger, opts) {
|
|
80688
81338
|
const found = worktreeService.resolve(worktreeId4);
|
|
80689
81339
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
80690
|
-
const hostId2 = found.worktree.hostId ??
|
|
80691
|
-
if (hostId2 ===
|
|
81340
|
+
const hostId2 = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81341
|
+
if (hostId2 === LOCAL_HOST_ID8) {
|
|
80692
81342
|
throw new CheckpointError(
|
|
80693
81343
|
"invalid",
|
|
80694
81344
|
"Checkpoints are for worktrees on a worker, not on the hub's own machine."
|
|
@@ -80875,7 +81525,7 @@ var CheckpointService = class {
|
|
|
80875
81525
|
if (this.ineligible(worktreeId4)) continue;
|
|
80876
81526
|
live.add(worktreeId4);
|
|
80877
81527
|
try {
|
|
80878
|
-
await this.sweepOne(worktreeId4, wt.hostId ??
|
|
81528
|
+
await this.sweepOne(worktreeId4, wt.hostId ?? LOCAL_HOST_ID8, wt.path);
|
|
80879
81529
|
} catch (err) {
|
|
80880
81530
|
log49.debug({ worktreeId: worktreeId4, err: errorText3(err) }, "checkpoint sweep skipped a worktree");
|
|
80881
81531
|
}
|
|
@@ -80899,7 +81549,7 @@ var CheckpointService = class {
|
|
|
80899
81549
|
this.watches.set(worktreeId4, { signature, changedAt: now, checkpointed: watch.checkpointed });
|
|
80900
81550
|
return;
|
|
80901
81551
|
}
|
|
80902
|
-
if (watch.checkpointed === signature || now - watch.changedAt <
|
|
81552
|
+
if (watch.checkpointed === signature || now - watch.changedAt < quietMs2()) return;
|
|
80903
81553
|
await this.take(worktreeId4, "quiet");
|
|
80904
81554
|
}
|
|
80905
81555
|
/**
|
|
@@ -81074,11 +81724,11 @@ ${diff}`).digest("hex");
|
|
|
81074
81724
|
const found = worktreeService.resolve(worktreeId4);
|
|
81075
81725
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
81076
81726
|
const { repo, worktree } = found;
|
|
81077
|
-
const fromHostId = worktree.hostId ??
|
|
81727
|
+
const fromHostId = worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81078
81728
|
if (fromHostId === toHostId) {
|
|
81079
81729
|
throw new CheckpointError("invalid", `${worktreeId4} is already on ${toHostId}.`);
|
|
81080
81730
|
}
|
|
81081
|
-
if (toHostId ===
|
|
81731
|
+
if (toHostId === LOCAL_HOST_ID8) {
|
|
81082
81732
|
throw new CheckpointError(
|
|
81083
81733
|
"invalid",
|
|
81084
81734
|
"A worktree can be restored on a worker, not on the hub's own machine."
|
|
@@ -81111,7 +81761,7 @@ ${diff}`).digest("hex");
|
|
|
81111
81761
|
}
|
|
81112
81762
|
}
|
|
81113
81763
|
if (repo.worktrees.some(
|
|
81114
|
-
(w2) => w2.name === worktree.name && (w2.hostId ??
|
|
81764
|
+
(w2) => w2.name === worktree.name && (w2.hostId ?? LOCAL_HOST_ID8) === toHostId
|
|
81115
81765
|
)) {
|
|
81116
81766
|
throw new CheckpointError(
|
|
81117
81767
|
"conflict",
|
|
@@ -81250,11 +81900,11 @@ ${diff}`).digest("hex");
|
|
|
81250
81900
|
}
|
|
81251
81901
|
const stage = posix8.join(wipDir, "sessions", `${row.id}`);
|
|
81252
81902
|
await host.fs.rm(stage, { recursive: true, force: true });
|
|
81253
|
-
for (const
|
|
81254
|
-
if (!isSafeSessionFile(
|
|
81255
|
-
const target = posix8.join(stage,
|
|
81903
|
+
for (const file3 of files) {
|
|
81904
|
+
if (!isSafeSessionFile(file3)) continue;
|
|
81905
|
+
const target = posix8.join(stage, file3.root, file3.rel);
|
|
81256
81906
|
await host.fs.mkdir(posix8.dirname(target), { recursive: true });
|
|
81257
|
-
await host.fs.writeFile(target, Buffer.from(
|
|
81907
|
+
await host.fs.writeFile(target, Buffer.from(file3.data, "base64"));
|
|
81258
81908
|
}
|
|
81259
81909
|
await rpc.call(METHOD_LIFECYCLE_IMPORT_SESSIONS, { dir: stage });
|
|
81260
81910
|
}
|
|
@@ -81266,8 +81916,8 @@ ${diff}`).digest("hex");
|
|
|
81266
81916
|
const { worktreeId: worktreeId4 } = input;
|
|
81267
81917
|
const found = worktreeService.resolve(worktreeId4);
|
|
81268
81918
|
if (!found) throw new CheckpointError("invalid", `Unknown worktree "${worktreeId4}"`);
|
|
81269
|
-
const fromHostId = found.worktree.hostId ??
|
|
81270
|
-
if (fromHostId ===
|
|
81919
|
+
const fromHostId = found.worktree.hostId ?? LOCAL_HOST_ID8;
|
|
81920
|
+
if (fromHostId === LOCAL_HOST_ID8) {
|
|
81271
81921
|
throw new CheckpointError("invalid", "Only worktrees on a worker can be moved.");
|
|
81272
81922
|
}
|
|
81273
81923
|
if (!this.sessions.get(fromHostId)?.attached) {
|
|
@@ -81335,7 +81985,7 @@ ${diff}`).digest("hex");
|
|
|
81335
81985
|
for (const repo of loadState().repos) {
|
|
81336
81986
|
const clone3 = hostRegistry.repoPathOn(repo.name, hostId2, "");
|
|
81337
81987
|
for (const wt of repo.worktrees) {
|
|
81338
|
-
if ((wt.hostId ??
|
|
81988
|
+
if ((wt.hostId ?? LOCAL_HOST_ID8) !== hostId2 || wt.supersededBy) continue;
|
|
81339
81989
|
if (clone3 && wt.path === clone3) continue;
|
|
81340
81990
|
out.push({
|
|
81341
81991
|
repo: repo.name,
|
|
@@ -81682,7 +82332,7 @@ init_token_service();
|
|
|
81682
82332
|
init_worktree_script_service();
|
|
81683
82333
|
init_worktree_service();
|
|
81684
82334
|
var log51 = createLogger("follow");
|
|
81685
|
-
var
|
|
82335
|
+
var LOCAL_HOST_ID9 = "local";
|
|
81686
82336
|
var errorText4 = (err) => err instanceof Error ? err.message : String(err);
|
|
81687
82337
|
function autoContinueMaxAgeMs() {
|
|
81688
82338
|
const raw = Number(process.env.BAND_AUTO_CONTINUE_MAX_AGE_MS);
|
|
@@ -81697,7 +82347,7 @@ var FollowService = class {
|
|
|
81697
82347
|
locate(worktreeId4) {
|
|
81698
82348
|
const found = worktreeService.resolve(worktreeId4);
|
|
81699
82349
|
if (!found) return null;
|
|
81700
|
-
return { ...found, hostId: found.worktree.hostId ??
|
|
82350
|
+
return { ...found, hostId: found.worktree.hostId ?? LOCAL_HOST_ID9 };
|
|
81701
82351
|
}
|
|
81702
82352
|
/**
|
|
81703
82353
|
* Whether the worktree is on a worker that is offline and is not asleep. A sleeping worktree
|
|
@@ -81706,7 +82356,7 @@ var FollowService = class {
|
|
|
81706
82356
|
isOffline(worktreeId4) {
|
|
81707
82357
|
const at = this.locate(worktreeId4);
|
|
81708
82358
|
if (!at) return false;
|
|
81709
|
-
if (at.hostId ===
|
|
82359
|
+
if (at.hostId === LOCAL_HOST_ID9) return false;
|
|
81710
82360
|
if (at.worktree.supersededBy) return false;
|
|
81711
82361
|
if (checkpointService.isHostOnline(at.hostId)) return false;
|
|
81712
82362
|
if (ephemeralLifecycleService.stateOf(worktreeId4)) return false;
|
|
@@ -81761,7 +82411,7 @@ var FollowService = class {
|
|
|
81761
82411
|
blocked: { reason, message },
|
|
81762
82412
|
question: message
|
|
81763
82413
|
});
|
|
81764
|
-
if (hostId2 ===
|
|
82414
|
+
if (hostId2 === LOCAL_HOST_ID9) {
|
|
81765
82415
|
return blocked("not-offline", "This worktree is on the hub's own machine, which is up.");
|
|
81766
82416
|
}
|
|
81767
82417
|
if (worktree.supersededBy) {
|
|
@@ -81793,12 +82443,12 @@ var FollowService = class {
|
|
|
81793
82443
|
const needed = this.neededAgents(worktreeId4, opts);
|
|
81794
82444
|
const clones = new Set(
|
|
81795
82445
|
[...this.repos.allHostPaths().get(repo.name)?.keys() ?? []].filter(
|
|
81796
|
-
(id) => id !==
|
|
82446
|
+
(id) => id !== LOCAL_HOST_ID9
|
|
81797
82447
|
)
|
|
81798
82448
|
);
|
|
81799
82449
|
const load = await this.loadByHost();
|
|
81800
82450
|
const candidates = tokenService.listHosts(1e3).filter(
|
|
81801
|
-
(h) => h.id !==
|
|
82451
|
+
(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
82452
|
);
|
|
81803
82453
|
if (candidates.length === 0) {
|
|
81804
82454
|
return {
|
|
@@ -81867,7 +82517,7 @@ var FollowService = class {
|
|
|
81867
82517
|
for (const repo of this.repos.loadAll()) {
|
|
81868
82518
|
for (const wt of repo.worktrees) {
|
|
81869
82519
|
if (wt.supersededBy) continue;
|
|
81870
|
-
const id = wt.hostId ??
|
|
82520
|
+
const id = wt.hostId ?? LOCAL_HOST_ID9;
|
|
81871
82521
|
out.set(id, (out.get(id) ?? 0) + 1);
|
|
81872
82522
|
}
|
|
81873
82523
|
}
|
|
@@ -81883,7 +82533,7 @@ var FollowService = class {
|
|
|
81883
82533
|
async gate(worktreeId4, opts = {}) {
|
|
81884
82534
|
const stale = this.locate(worktreeId4);
|
|
81885
82535
|
const next = stale?.worktree.supersededBy ? this.locate(stale.worktree.supersededBy) : null;
|
|
81886
|
-
if (stale && next && stale.hostId !==
|
|
82536
|
+
if (stale && next && stale.hostId !== LOCAL_HOST_ID9 && !checkpointService.isHostOnline(stale.hostId)) {
|
|
81887
82537
|
return { worktreeId: idOf(next) };
|
|
81888
82538
|
}
|
|
81889
82539
|
if (!this.isOffline(worktreeId4)) return { worktreeId: worktreeId4 };
|
|
@@ -82017,7 +82667,7 @@ var FollowService = class {
|
|
|
82017
82667
|
async onHostReturned(hostId2) {
|
|
82018
82668
|
for (const repo of this.repos.loadAll()) {
|
|
82019
82669
|
for (const wt of repo.worktrees) {
|
|
82020
|
-
if ((wt.hostId ??
|
|
82670
|
+
if ((wt.hostId ?? LOCAL_HOST_ID9) !== hostId2 || !wt.supersededBy) continue;
|
|
82021
82671
|
const oldId = toWorktreeId(repo.name, wt.name, wt.hostId);
|
|
82022
82672
|
if (wt.divergence?.state === "resolved") continue;
|
|
82023
82673
|
try {
|
|
@@ -90379,17 +91029,17 @@ import { execFile as execFile13 } from "node:child_process";
|
|
|
90379
91029
|
import {
|
|
90380
91030
|
accessSync as accessSync3,
|
|
90381
91031
|
constants as constants3,
|
|
90382
|
-
existsSync as
|
|
91032
|
+
existsSync as existsSync16,
|
|
90383
91033
|
lstatSync as lstatSync4,
|
|
90384
|
-
mkdirSync as
|
|
91034
|
+
mkdirSync as mkdirSync9,
|
|
90385
91035
|
readFileSync as readFileSync11,
|
|
90386
|
-
realpathSync as
|
|
91036
|
+
realpathSync as realpathSync5,
|
|
90387
91037
|
symlinkSync,
|
|
90388
|
-
unlinkSync as
|
|
91038
|
+
unlinkSync as unlinkSync6,
|
|
90389
91039
|
writeFileSync as writeFileSync7
|
|
90390
91040
|
} from "node:fs";
|
|
90391
91041
|
import { homedir as homedir15, platform as platform2 } from "node:os";
|
|
90392
|
-
import { basename as basename3, dirname as
|
|
91042
|
+
import { basename as basename3, dirname as dirname13, join as join46 } from "node:path";
|
|
90393
91043
|
function windowsBinDir() {
|
|
90394
91044
|
const localAppData = process.env.LOCALAPPDATA || join46(homedir15(), "AppData", "Local");
|
|
90395
91045
|
return join46(localAppData, "band", "bin");
|
|
@@ -90408,7 +91058,7 @@ function checkCliWindows() {
|
|
|
90408
91058
|
if (!match) return "ConflictingBinary";
|
|
90409
91059
|
const target = match[1];
|
|
90410
91060
|
if (basename3(target).toLowerCase() !== "band.exe") return "ConflictingBinary";
|
|
90411
|
-
return
|
|
91061
|
+
return existsSync16(target) ? "Installed" : "NotInstalled";
|
|
90412
91062
|
}
|
|
90413
91063
|
async function checkCli() {
|
|
90414
91064
|
if (platform2() === "win32") {
|
|
@@ -90421,7 +91071,7 @@ async function checkCli() {
|
|
|
90421
91071
|
}
|
|
90422
91072
|
let target;
|
|
90423
91073
|
try {
|
|
90424
|
-
target =
|
|
91074
|
+
target = realpathSync5(SYMLINK_PATH);
|
|
90425
91075
|
} catch {
|
|
90426
91076
|
return "NotInstalled";
|
|
90427
91077
|
}
|
|
@@ -90500,7 +91150,7 @@ async function addBinDirToUserPath(binDir) {
|
|
|
90500
91150
|
}
|
|
90501
91151
|
async function installCliWindows(binaryPath) {
|
|
90502
91152
|
const binDir = windowsBinDir();
|
|
90503
|
-
|
|
91153
|
+
mkdirSync9(binDir, { recursive: true });
|
|
90504
91154
|
const shim = `@echo off\r
|
|
90505
91155
|
"${binaryPath}" %*\r
|
|
90506
91156
|
`;
|
|
@@ -90516,11 +91166,11 @@ async function installCli(_opts = {}) {
|
|
|
90516
91166
|
await installCliWindows(binaryPath);
|
|
90517
91167
|
return;
|
|
90518
91168
|
}
|
|
90519
|
-
const dir =
|
|
91169
|
+
const dir = dirname13(SYMLINK_PATH);
|
|
90520
91170
|
if (isDirWritable(dir)) {
|
|
90521
91171
|
try {
|
|
90522
91172
|
lstatSync4(SYMLINK_PATH);
|
|
90523
|
-
|
|
91173
|
+
unlinkSync6(SYMLINK_PATH);
|
|
90524
91174
|
} catch (err) {
|
|
90525
91175
|
const code = err.code;
|
|
90526
91176
|
if (code !== "ENOENT") throw err;
|
|
@@ -90774,7 +91424,7 @@ function subscribeToFileChanges(worktreeId4, listener) {
|
|
|
90774
91424
|
// src/server/services/formatter.ts
|
|
90775
91425
|
init_src();
|
|
90776
91426
|
init_registry();
|
|
90777
|
-
import { basename as basename4, dirname as
|
|
91427
|
+
import { basename as basename4, dirname as dirname14, isAbsolute as isAbsolute4, join as join47, resolve as resolvePath3 } from "node:path";
|
|
90778
91428
|
import prettier from "prettier";
|
|
90779
91429
|
var log56 = createLogger("formatter");
|
|
90780
91430
|
var FormatterError = class extends Error {
|
|
@@ -90853,7 +91503,7 @@ async function formatFile(worktreePath, filePath, content, options = {}) {
|
|
|
90853
91503
|
var JSON_CONFIGS = [".prettierrc", ".prettierrc.json"];
|
|
90854
91504
|
async function resolveConfigOnHost(fs3, absFile, worktreePath) {
|
|
90855
91505
|
const root = resolvePath3(worktreePath);
|
|
90856
|
-
let dir =
|
|
91506
|
+
let dir = dirname14(absFile);
|
|
90857
91507
|
for (; ; ) {
|
|
90858
91508
|
for (const name4 of JSON_CONFIGS) {
|
|
90859
91509
|
const parsed = await readJson2(fs3, join47(dir, name4));
|
|
@@ -90863,8 +91513,8 @@ async function resolveConfigOnHost(fs3, absFile, worktreePath) {
|
|
|
90863
91513
|
if (pkg && typeof pkg.prettier === "object" && pkg.prettier !== null) {
|
|
90864
91514
|
return withoutPlugins(pkg.prettier);
|
|
90865
91515
|
}
|
|
90866
|
-
if (dir === root ||
|
|
90867
|
-
dir =
|
|
91516
|
+
if (dir === root || dirname14(dir) === dir) return null;
|
|
91517
|
+
dir = dirname14(dir);
|
|
90868
91518
|
}
|
|
90869
91519
|
}
|
|
90870
91520
|
function withoutPlugins(config2) {
|
|
@@ -90897,7 +91547,7 @@ async function isInsideWorktree(fs3, absFile, worktreePath) {
|
|
|
90897
91547
|
realFile = await fs3.realpath(absFile);
|
|
90898
91548
|
} catch {
|
|
90899
91549
|
try {
|
|
90900
|
-
realFile = join47(await fs3.realpath(
|
|
91550
|
+
realFile = join47(await fs3.realpath(dirname14(absFile)), basename4(absFile));
|
|
90901
91551
|
} catch {
|
|
90902
91552
|
realFile = absFile;
|
|
90903
91553
|
}
|
|
@@ -91160,7 +91810,7 @@ init_environment_build_service();
|
|
|
91160
91810
|
init_src4();
|
|
91161
91811
|
init_dist();
|
|
91162
91812
|
init_registry();
|
|
91163
|
-
import { basename as basename5, dirname as
|
|
91813
|
+
import { basename as basename5, dirname as dirname15, isAbsolute as isAbsolute6 } from "node:path";
|
|
91164
91814
|
init_token_service();
|
|
91165
91815
|
var EnvironmentService = class {
|
|
91166
91816
|
/** The environment of a repo's main checkout, and which hosts meet its `requires`. */
|
|
@@ -91178,8 +91828,8 @@ var EnvironmentService = class {
|
|
|
91178
91828
|
if (!isAbsolute6(path2)) {
|
|
91179
91829
|
throw new TRPCError({ code: "BAD_REQUEST", message: "path must be absolute" });
|
|
91180
91830
|
}
|
|
91181
|
-
const isFile2 = basename5(path2) === "environment.json" && basename5(
|
|
91182
|
-
return this.read(isFile2 ?
|
|
91831
|
+
const isFile2 = basename5(path2) === "environment.json" && basename5(dirname15(path2)) === ".band";
|
|
91832
|
+
return this.read(isFile2 ? dirname15(dirname15(path2)) : path2);
|
|
91183
91833
|
}
|
|
91184
91834
|
read(dir) {
|
|
91185
91835
|
return hostRegistry.local.scripts.environment({ repoPath: dir, worktreePath: dir });
|
|
@@ -91571,26 +92221,83 @@ var historyRouter = t.router({
|
|
|
91571
92221
|
})
|
|
91572
92222
|
});
|
|
91573
92223
|
|
|
92224
|
+
// src/server/api/hooks/router.ts
|
|
92225
|
+
init_zod();
|
|
92226
|
+
|
|
91574
92227
|
// src/server/services/hooks-service.ts
|
|
91575
92228
|
init_registry();
|
|
91576
|
-
|
|
91577
|
-
|
|
92229
|
+
init_token_service();
|
|
92230
|
+
var LOCAL_HOST_ID10 = "local";
|
|
92231
|
+
function hostOf2(hostId2) {
|
|
92232
|
+
return hostRegistry.hostById(hostId2);
|
|
91578
92233
|
}
|
|
91579
|
-
function
|
|
91580
|
-
return
|
|
92234
|
+
function checkHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92235
|
+
return hostOf2(hostId2).agentEnv.hooksStatus();
|
|
92236
|
+
}
|
|
92237
|
+
function installHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92238
|
+
return hostOf2(hostId2).agentEnv.installHooks();
|
|
92239
|
+
}
|
|
92240
|
+
function removeHooks2(hostId2 = LOCAL_HOST_ID10) {
|
|
92241
|
+
return hostOf2(hostId2).agentEnv.removeHooks();
|
|
92242
|
+
}
|
|
92243
|
+
async function claudeCodeOn(hostId2, agents) {
|
|
92244
|
+
if (hostId2 !== LOCAL_HOST_ID10) return agents.includes("claude-code");
|
|
92245
|
+
try {
|
|
92246
|
+
return typeof await hostOf2(hostId2).acp.resolveLaunch({ type: "claude-code" }) !== "string";
|
|
92247
|
+
} catch {
|
|
92248
|
+
return false;
|
|
92249
|
+
}
|
|
92250
|
+
}
|
|
92251
|
+
async function listHostHooks() {
|
|
92252
|
+
const hosts2 = tokenService.listHosts().filter((h) => h.id !== LOCAL_HOST_ID10 || h.usable);
|
|
92253
|
+
return Promise.all(
|
|
92254
|
+
hosts2.map(async (h) => {
|
|
92255
|
+
const online = h.id === LOCAL_HOST_ID10 || h.status === "online";
|
|
92256
|
+
const view4 = {
|
|
92257
|
+
hostId: h.id,
|
|
92258
|
+
name: h.name,
|
|
92259
|
+
online,
|
|
92260
|
+
hasClaudeCode: await claudeCodeOn(h.id, h.agents),
|
|
92261
|
+
status: null
|
|
92262
|
+
};
|
|
92263
|
+
if (!online) return view4;
|
|
92264
|
+
try {
|
|
92265
|
+
view4.status = await checkHooks2(h.id);
|
|
92266
|
+
} catch (err) {
|
|
92267
|
+
view4.error = err instanceof Error ? err.message : String(err);
|
|
92268
|
+
}
|
|
92269
|
+
return view4;
|
|
92270
|
+
})
|
|
92271
|
+
);
|
|
91581
92272
|
}
|
|
91582
92273
|
|
|
91583
92274
|
// src/server/api/hooks/router.ts
|
|
92275
|
+
var hostInput = external_exports2.object({ hostId: external_exports2.string().optional() }).default({});
|
|
92276
|
+
function failure(err) {
|
|
92277
|
+
return new Error(err instanceof Error ? err.message : String(err));
|
|
92278
|
+
}
|
|
91584
92279
|
var hooksRouter = t.router({
|
|
91585
|
-
|
|
91586
|
-
|
|
92280
|
+
/** The hub's own host. */
|
|
92281
|
+
check: adminProcedure.input(hostInput).query(async ({ input }) => {
|
|
92282
|
+
return await checkHooks2(input.hostId);
|
|
91587
92283
|
}),
|
|
91588
|
-
|
|
92284
|
+
/** Every host with its hooks status. */
|
|
92285
|
+
hosts: adminProcedure.query(async () => ({ hosts: await listHostHooks() })),
|
|
92286
|
+
install: adminProcedure.input(hostInput).mutation(async ({ input }) => {
|
|
91589
92287
|
try {
|
|
91590
|
-
await installHooks2();
|
|
92288
|
+
await installHooks2(input.hostId);
|
|
91591
92289
|
return { ok: true };
|
|
91592
92290
|
} catch (err) {
|
|
91593
|
-
throw
|
|
92291
|
+
throw failure(err);
|
|
92292
|
+
}
|
|
92293
|
+
}),
|
|
92294
|
+
/** Removes Band's entries and leaves every other hook and setting. */
|
|
92295
|
+
remove: adminProcedure.input(hostInput).mutation(async ({ input }) => {
|
|
92296
|
+
try {
|
|
92297
|
+
await removeHooks2(input.hostId);
|
|
92298
|
+
return { ok: true };
|
|
92299
|
+
} catch (err) {
|
|
92300
|
+
throw failure(err);
|
|
91594
92301
|
}
|
|
91595
92302
|
})
|
|
91596
92303
|
});
|
|
@@ -91724,6 +92431,17 @@ var RELAY_PROCEDURES = /* @__PURE__ */ new Set([
|
|
|
91724
92431
|
"statuses.notify",
|
|
91725
92432
|
"statuses.clearNeedsAttention",
|
|
91726
92433
|
"repos.list",
|
|
92434
|
+
"repos.add",
|
|
92435
|
+
"repos.addFromWorker",
|
|
92436
|
+
"repos.addByUrl",
|
|
92437
|
+
"repos.inspectFolder",
|
|
92438
|
+
"repos.resolveRemote",
|
|
92439
|
+
"repos.gitInit",
|
|
92440
|
+
"repos.promoteToGit",
|
|
92441
|
+
"repos.update",
|
|
92442
|
+
"repos.updateLabel",
|
|
92443
|
+
"repos.reorder",
|
|
92444
|
+
"repos.remove",
|
|
91727
92445
|
"hosts.list",
|
|
91728
92446
|
"hosts.forAgents",
|
|
91729
92447
|
"cronjobs.list",
|
|
@@ -91736,7 +92454,13 @@ var RELAY_PROCEDURES = /* @__PURE__ */ new Set([
|
|
|
91736
92454
|
"worktrees.create",
|
|
91737
92455
|
"worktrees.remove"
|
|
91738
92456
|
]);
|
|
91739
|
-
var UNBATCHABLE = /* @__PURE__ */ new Set([
|
|
92457
|
+
var UNBATCHABLE = /* @__PURE__ */ new Set([
|
|
92458
|
+
"repos.list",
|
|
92459
|
+
"hosts.list",
|
|
92460
|
+
"worktrees.create",
|
|
92461
|
+
"worktrees.remove",
|
|
92462
|
+
"statuses.notify"
|
|
92463
|
+
]);
|
|
91740
92464
|
function pinHost(input, workerId, keep) {
|
|
91741
92465
|
const base = input !== null && typeof input === "object" ? input : {};
|
|
91742
92466
|
return keep && typeof base.hostId === "string" ? base : { ...base, hostId: workerId };
|
|
@@ -91749,7 +92473,7 @@ function pinWorktreeHost(request, workerId) {
|
|
|
91749
92473
|
} catch {
|
|
91750
92474
|
return request;
|
|
91751
92475
|
}
|
|
91752
|
-
const isTrpc = call === "/trpc/worktrees.create" || call === "/trpc/worktrees.remove";
|
|
92476
|
+
const isTrpc = call === "/trpc/worktrees.create" || call === "/trpc/worktrees.remove" || call === "/trpc/statuses.notify";
|
|
91753
92477
|
const isMcp = call === "/mcp";
|
|
91754
92478
|
if (!isTrpc && !isMcp) return request;
|
|
91755
92479
|
const parsed = parseJson(bodyText(request.body));
|
|
@@ -91903,7 +92627,7 @@ function checkNamed(input, workerId, lookups) {
|
|
|
91903
92627
|
if (worktree === null || lookups.hostOfWorktree(worktree) !== workerId) return outside;
|
|
91904
92628
|
}
|
|
91905
92629
|
for (const cwd of named.cwds) {
|
|
91906
|
-
const worktree = lookups.worktreeOfCwd(path.resolve(cwd));
|
|
92630
|
+
const worktree = lookups.worktreeOfCwd(path.resolve(cwd), workerId);
|
|
91907
92631
|
if (worktree === null || lookups.hostOfWorktree(worktree) !== workerId) return outside;
|
|
91908
92632
|
}
|
|
91909
92633
|
for (const id of named.terminals) {
|
|
@@ -91929,8 +92653,40 @@ function checkChosenId(procedure, input, lookups) {
|
|
|
91929
92653
|
}
|
|
91930
92654
|
return { ok: true };
|
|
91931
92655
|
}
|
|
92656
|
+
var REPO_BY_NAME_PROCEDURES = /* @__PURE__ */ new Set([
|
|
92657
|
+
"repos.update",
|
|
92658
|
+
"repos.updateLabel",
|
|
92659
|
+
"repos.remove",
|
|
92660
|
+
"repos.promoteToGit"
|
|
92661
|
+
]);
|
|
92662
|
+
function checkRepoCall(procedure, input, workerId, lookups) {
|
|
92663
|
+
const record3 = input ?? {};
|
|
92664
|
+
if (REPO_BY_NAME_PROCEDURES.has(procedure)) {
|
|
92665
|
+
if (typeof record3.name !== "string") return deny(400, `${procedure} needs a repo name`);
|
|
92666
|
+
const refusal = lookups.repoHostRefusal(record3.name, workerId);
|
|
92667
|
+
return refusal === null ? { ok: true } : deny(403, `${procedure}: ${refusal}`);
|
|
92668
|
+
}
|
|
92669
|
+
if (procedure === "repos.addFromWorker") {
|
|
92670
|
+
const { hostId: hostId2, name: name4, allowedHostIds } = record3;
|
|
92671
|
+
if (typeof hostId2 !== "string") return deny(400, `${procedure} needs a hostId`);
|
|
92672
|
+
if (Array.isArray(allowedHostIds) && !allowedHostIds.includes(hostId2)) {
|
|
92673
|
+
return deny(
|
|
92674
|
+
403,
|
|
92675
|
+
`${procedure}: The repo would be limited to hosts that exclude ${hostId2}, where the folder is`
|
|
92676
|
+
);
|
|
92677
|
+
}
|
|
92678
|
+
if (typeof name4 === "string") {
|
|
92679
|
+
const refusal = lookups.repoHostRefusal(name4, workerId);
|
|
92680
|
+
if (refusal !== null) return deny(403, `${procedure}: ${refusal}`);
|
|
92681
|
+
}
|
|
92682
|
+
}
|
|
92683
|
+
return { ok: true };
|
|
92684
|
+
}
|
|
91932
92685
|
function checkCall(procedure, input, workerId, lookups) {
|
|
91933
92686
|
if (!RELAY_PROCEDURES.has(procedure)) return deny(403, `${procedure} is not available to agents`);
|
|
92687
|
+
if (procedure.startsWith("repos.") && procedure !== "repos.list") {
|
|
92688
|
+
return checkRepoCall(procedure, input, workerId, lookups);
|
|
92689
|
+
}
|
|
91934
92690
|
let named = input;
|
|
91935
92691
|
if (CRONJOB_KEY_PROCEDURES.has(procedure)) {
|
|
91936
92692
|
const { key: key2, ...rest } = input ?? {};
|
|
@@ -94768,7 +95524,7 @@ init_src();
|
|
|
94768
95524
|
init_zod();
|
|
94769
95525
|
init_registry();
|
|
94770
95526
|
init_queued_message_store();
|
|
94771
|
-
import { realpathSync as
|
|
95527
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
94772
95528
|
import { join as join49, resolve as resolve11, sep as sep7 } from "node:path";
|
|
94773
95529
|
init_chat_service();
|
|
94774
95530
|
init_state();
|
|
@@ -94786,8 +95542,8 @@ function isPathWithinUploadDir(p2) {
|
|
|
94786
95542
|
return false;
|
|
94787
95543
|
}
|
|
94788
95544
|
try {
|
|
94789
|
-
const canonicalUploadDir =
|
|
94790
|
-
const canonicalPath2 =
|
|
95545
|
+
const canonicalUploadDir = realpathSync6(uploadDir);
|
|
95546
|
+
const canonicalPath2 = realpathSync6(p2);
|
|
94791
95547
|
return canonicalPath2 === canonicalUploadDir || canonicalPath2.startsWith(canonicalUploadDir + sep7);
|
|
94792
95548
|
} catch {
|
|
94793
95549
|
return true;
|
|
@@ -94800,38 +95556,38 @@ async function resolveQueuedFiles(chatId, worktreeId4, files) {
|
|
|
94800
95556
|
const needsSave = [];
|
|
94801
95557
|
const needsSaveIdx = [];
|
|
94802
95558
|
for (let i = 0; i < files.length; i++) {
|
|
94803
|
-
const
|
|
94804
|
-
const derivedPath =
|
|
95559
|
+
const file3 = files[i];
|
|
95560
|
+
const derivedPath = file3.path ?? await uploadPathFromUrl(worktreeId4, file3.url) ?? void 0;
|
|
94805
95561
|
if (derivedPath) {
|
|
94806
95562
|
const inside = remote ? await isWithinUploads(worktreeId4, derivedPath) : isPathWithinUploadDir(derivedPath);
|
|
94807
95563
|
if (!inside) {
|
|
94808
95564
|
log57.warn(
|
|
94809
|
-
{ chatId, path: derivedPath, filename:
|
|
95565
|
+
{ chatId, path: derivedPath, filename: file3.filename },
|
|
94810
95566
|
"queue: dropping file with path outside uploads directory"
|
|
94811
95567
|
);
|
|
94812
95568
|
continue;
|
|
94813
95569
|
}
|
|
94814
95570
|
resolved.push({
|
|
94815
|
-
mediaType:
|
|
94816
|
-
url:
|
|
95571
|
+
mediaType: file3.mediaType,
|
|
95572
|
+
url: file3.url,
|
|
94817
95573
|
path: derivedPath,
|
|
94818
|
-
...
|
|
95574
|
+
...file3.filename !== void 0 && { filename: file3.filename }
|
|
94819
95575
|
});
|
|
94820
95576
|
continue;
|
|
94821
95577
|
}
|
|
94822
|
-
if (
|
|
94823
|
-
needsSave.push(
|
|
95578
|
+
if (file3.url.startsWith("data:")) {
|
|
95579
|
+
needsSave.push(file3);
|
|
94824
95580
|
needsSaveIdx.push(resolved.length);
|
|
94825
95581
|
resolved.push({
|
|
94826
|
-
mediaType:
|
|
94827
|
-
url:
|
|
95582
|
+
mediaType: file3.mediaType,
|
|
95583
|
+
url: file3.url,
|
|
94828
95584
|
path: "",
|
|
94829
|
-
filename:
|
|
95585
|
+
filename: file3.filename
|
|
94830
95586
|
});
|
|
94831
95587
|
continue;
|
|
94832
95588
|
}
|
|
94833
95589
|
log57.warn(
|
|
94834
|
-
{ chatId, url:
|
|
95590
|
+
{ chatId, url: file3.url, filename: file3.filename },
|
|
94835
95591
|
"queue: dropping file with no path and non-data URL \u2014 cannot recover disk path"
|
|
94836
95592
|
);
|
|
94837
95593
|
}
|
|
@@ -96220,7 +96976,6 @@ var settingsRouter = t.router({
|
|
|
96220
96976
|
init_zod();
|
|
96221
96977
|
init_branch_status_poller();
|
|
96222
96978
|
init_state();
|
|
96223
|
-
init_task_service();
|
|
96224
96979
|
init_watcher_service();
|
|
96225
96980
|
var statusesRouter = t.router({
|
|
96226
96981
|
get: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).query(({ input }) => {
|
|
@@ -96249,12 +97004,12 @@ var statusesRouter = t.router({
|
|
|
96249
97004
|
refreshed: await branchStatusPoller.refreshWorktree(input.worktreeId)
|
|
96250
97005
|
})),
|
|
96251
97006
|
clearNeedsAttention: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).mutation(({ input }) => {
|
|
96252
|
-
const status =
|
|
97007
|
+
const status = acknowledgeWorktreeAttention(input.worktreeId);
|
|
96253
97008
|
if (status) emit({ kind: "update", status });
|
|
96254
97009
|
return { ok: true };
|
|
96255
97010
|
}),
|
|
96256
|
-
resolve: publicProcedure.input(external_exports2.object({ cwd: external_exports2.string() })).query(({ input }) => {
|
|
96257
|
-
return { worktreeId: resolveWorktreeIdByCwd(input.cwd) };
|
|
97011
|
+
resolve: publicProcedure.input(external_exports2.object({ cwd: external_exports2.string(), hostId: external_exports2.string().optional() })).query(({ input }) => {
|
|
97012
|
+
return { worktreeId: resolveWorktreeIdByCwd(input.cwd, input.hostId) };
|
|
96258
97013
|
}),
|
|
96259
97014
|
/**
|
|
96260
97015
|
* Agent-agnostic entry point for coding-agent lifecycle notifications
|
|
@@ -96275,7 +97030,8 @@ var statusesRouter = t.router({
|
|
|
96275
97030
|
payload: external_exports2.record(external_exports2.string(), external_exports2.unknown()),
|
|
96276
97031
|
agent: external_exports2.string().max(64).optional(),
|
|
96277
97032
|
dispatch: external_exports2.string().max(64).optional(),
|
|
96278
|
-
terminalId: external_exports2.string().max(256).optional()
|
|
97033
|
+
terminalId: external_exports2.string().max(256).optional(),
|
|
97034
|
+
hostId: external_exports2.string().max(256).optional()
|
|
96279
97035
|
})
|
|
96280
97036
|
).mutation(async ({ input }) => {
|
|
96281
97037
|
const status = await applyHookNotification(input);
|
|
@@ -96472,6 +97228,91 @@ var subscriptionsRouter = t.router({
|
|
|
96472
97228
|
init_src();
|
|
96473
97229
|
init_dist();
|
|
96474
97230
|
init_zod();
|
|
97231
|
+
|
|
97232
|
+
// package.json
|
|
97233
|
+
var package_default = {
|
|
97234
|
+
name: "@band-app/server",
|
|
97235
|
+
version: "0.48.0",
|
|
97236
|
+
type: "module",
|
|
97237
|
+
types: "src/index.ts",
|
|
97238
|
+
exports: {
|
|
97239
|
+
".": "./src/index.ts",
|
|
97240
|
+
"./package.json": "./package.json"
|
|
97241
|
+
},
|
|
97242
|
+
bin: {
|
|
97243
|
+
"band-server": "./bin/band-server.mjs"
|
|
97244
|
+
},
|
|
97245
|
+
files: [
|
|
97246
|
+
"dist/client",
|
|
97247
|
+
"dist/start-server.mjs",
|
|
97248
|
+
"dist/terminal-daemon.mjs",
|
|
97249
|
+
"dist/openapi.json",
|
|
97250
|
+
"dist/migrations",
|
|
97251
|
+
"bin"
|
|
97252
|
+
],
|
|
97253
|
+
engines: {
|
|
97254
|
+
node: ">=22.5.0"
|
|
97255
|
+
},
|
|
97256
|
+
os: [
|
|
97257
|
+
"darwin",
|
|
97258
|
+
"linux",
|
|
97259
|
+
"win32"
|
|
97260
|
+
],
|
|
97261
|
+
scripts: {
|
|
97262
|
+
dev: "NODE_ENV=development tsx watch --exclude '../web/**' start-server.ts",
|
|
97263
|
+
build: "./scripts/build-server.sh",
|
|
97264
|
+
start: "node dist/start-server.mjs",
|
|
97265
|
+
prepack: "node scripts/copy-ui.mjs",
|
|
97266
|
+
postpack: "node scripts/copy-ui.mjs --clean",
|
|
97267
|
+
pretest: "pnpm --filter @band-app/web build && pnpm build",
|
|
97268
|
+
test: "NODE_OPTIONS='--no-warnings=ExperimentalWarning' vitest run",
|
|
97269
|
+
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'})"`
|
|
97270
|
+
},
|
|
97271
|
+
dependencies: {
|
|
97272
|
+
"@agentclientprotocol/claude-agent-acp": "0.81.2",
|
|
97273
|
+
"@agentclientprotocol/codex-acp": "1.13.1",
|
|
97274
|
+
"@agentclientprotocol/sdk": "1.5.0",
|
|
97275
|
+
"@vscode/ripgrep": "^1.18.0",
|
|
97276
|
+
fzf: "0.5.2",
|
|
97277
|
+
"node-pty": "^1.1.0",
|
|
97278
|
+
prettier: "^3.8.3",
|
|
97279
|
+
"typescript-language-server": "^5.1.3"
|
|
97280
|
+
},
|
|
97281
|
+
devDependencies: {
|
|
97282
|
+
"@band-app/coding-agent": "workspace:^",
|
|
97283
|
+
"@band-app/environment": "workspace:^",
|
|
97284
|
+
"@band-app/host-api": "workspace:^",
|
|
97285
|
+
"@band-app/host-local": "workspace:^",
|
|
97286
|
+
"@band-app/host-remote": "workspace:^",
|
|
97287
|
+
"@band-app/link": "workspace:^",
|
|
97288
|
+
"@band-app/logger": "workspace:^",
|
|
97289
|
+
"@band-app/plugin-api": "workspace:^",
|
|
97290
|
+
"@band-app/plugin-github": "workspace:^",
|
|
97291
|
+
"@band-app/shared": "workspace:^",
|
|
97292
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
97293
|
+
"@trpc/client": "^11.12.0",
|
|
97294
|
+
"@trpc/openapi": "11.16.0-alpha",
|
|
97295
|
+
"@trpc/server": "^11.12.0",
|
|
97296
|
+
"@types/express": "^5.0.6",
|
|
97297
|
+
"@types/node": "^22.19.13",
|
|
97298
|
+
"@types/ws": "^8.18.0",
|
|
97299
|
+
"@xterm/headless": "6.1.0-beta.302",
|
|
97300
|
+
croner: "^10.0.1",
|
|
97301
|
+
"drizzle-kit": "1.0.0-rc.1",
|
|
97302
|
+
"drizzle-orm": "1.0.0-rc.1",
|
|
97303
|
+
esbuild: "^0.25.0",
|
|
97304
|
+
express: "^5.2.1",
|
|
97305
|
+
sirv: "^3.0.0",
|
|
97306
|
+
tsx: "^4.21.0",
|
|
97307
|
+
typescript: "^5.7.0",
|
|
97308
|
+
vite: "^7.0.0",
|
|
97309
|
+
vitest: "^4.0.18",
|
|
97310
|
+
ws: "^8.18.0",
|
|
97311
|
+
zod: "^3.25.67"
|
|
97312
|
+
}
|
|
97313
|
+
};
|
|
97314
|
+
|
|
97315
|
+
// src/server/api/system/router.ts
|
|
96475
97316
|
init_branch_status_poller();
|
|
96476
97317
|
init_state();
|
|
96477
97318
|
init_tunnel_service();
|
|
@@ -96488,6 +97329,7 @@ var systemRouter = t.router({
|
|
|
96488
97329
|
log62.debug({ tunnel }, "services.health: tunnel status");
|
|
96489
97330
|
const result = {
|
|
96490
97331
|
webserver: true,
|
|
97332
|
+
version: package_default.version,
|
|
96491
97333
|
tunnel: tunnel.running,
|
|
96492
97334
|
tunnel_url: tunnel.url
|
|
96493
97335
|
};
|
|
@@ -97043,6 +97885,45 @@ var vaultRouter = t.router({
|
|
|
97043
97885
|
init_dist();
|
|
97044
97886
|
init_zod();
|
|
97045
97887
|
|
|
97888
|
+
// src/server/services/_utils/worktree-preview-link.ts
|
|
97889
|
+
init_state();
|
|
97890
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual8 } from "node:crypto";
|
|
97891
|
+
import { posix as posix10 } from "node:path";
|
|
97892
|
+
var WORKTREE_PREVIEW_PREFIX = "/api/worktree-preview/";
|
|
97893
|
+
var PREVIEW_TTL_MS = 60 * 60 * 1e3;
|
|
97894
|
+
function signingKey() {
|
|
97895
|
+
const token = resolveAdminToken(process.env.BAND_ADMIN_TOKEN).token;
|
|
97896
|
+
return createHmac2("sha256", token).update("worktree-preview-v1").digest();
|
|
97897
|
+
}
|
|
97898
|
+
function sign(worktreeId4, dir, expiresAt) {
|
|
97899
|
+
return createHmac2("sha256", signingKey()).update(`${worktreeId4}
|
|
97900
|
+
${dir}
|
|
97901
|
+
${expiresAt}`).digest("hex");
|
|
97902
|
+
}
|
|
97903
|
+
function worktreePreviewUrl(worktreeId4, path2, now = Date.now()) {
|
|
97904
|
+
const expiresAt = now + PREVIEW_TTL_MS;
|
|
97905
|
+
const dir = posix10.dirname(path2.replace(/^\/+/, ""));
|
|
97906
|
+
const encodedDir = Buffer.from(dir, "utf8").toString("base64url");
|
|
97907
|
+
const encodedPath = path2.split("/").map(encodeURIComponent).join("/");
|
|
97908
|
+
const grant = `${expiresAt}.${encodedDir}.${sign(worktreeId4, dir, expiresAt)}`;
|
|
97909
|
+
return `${WORKTREE_PREVIEW_PREFIX}${encodeURIComponent(worktreeId4)}/${grant}/${encodedPath}`;
|
|
97910
|
+
}
|
|
97911
|
+
function verifyWorktreePreviewGrant(worktreeId4, grant, filePath, now = Date.now()) {
|
|
97912
|
+
if (filePath.includes("\0")) return false;
|
|
97913
|
+
const [expiry, encodedDir, signature, ...extra] = grant.split(".");
|
|
97914
|
+
const expiresAt = Number(expiry);
|
|
97915
|
+
if (extra.length > 0 || encodedDir === void 0 || !signature || !Number.isSafeInteger(expiresAt) || expiresAt < now) {
|
|
97916
|
+
return false;
|
|
97917
|
+
}
|
|
97918
|
+
const dir = Buffer.from(encodedDir, "base64url").toString("utf8");
|
|
97919
|
+
const expected = Buffer.from(sign(worktreeId4, dir, expiresAt), "hex");
|
|
97920
|
+
const given = Buffer.from(signature, "hex");
|
|
97921
|
+
if (given.length !== expected.length || !timingSafeEqual8(given, expected)) return false;
|
|
97922
|
+
const target = posix10.normalize(filePath.replace(/^\/+/, ""));
|
|
97923
|
+
if (target === ".." || target.startsWith("../")) return false;
|
|
97924
|
+
return dir === "." || target === dir || target.startsWith(`${dir}/`);
|
|
97925
|
+
}
|
|
97926
|
+
|
|
97046
97927
|
// src/server/services/diff-service.ts
|
|
97047
97928
|
init_src2();
|
|
97048
97929
|
init_src();
|
|
@@ -97158,9 +98039,9 @@ function assertWorktreeRelative(cwd, filePath) {
|
|
|
97158
98039
|
}
|
|
97159
98040
|
return target;
|
|
97160
98041
|
}
|
|
97161
|
-
async function readUntrackedFileLines(host, cwd,
|
|
98042
|
+
async function readUntrackedFileLines(host, cwd, file3) {
|
|
97162
98043
|
try {
|
|
97163
|
-
const content = new TextDecoder().decode(await host.fs.readFile(join50(cwd,
|
|
98044
|
+
const content = new TextDecoder().decode(await host.fs.readFile(join50(cwd, file3)));
|
|
97164
98045
|
const lines = content.split("\n");
|
|
97165
98046
|
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
97166
98047
|
lines.pop();
|
|
@@ -97450,17 +98331,17 @@ var DiffService = class {
|
|
|
97450
98331
|
const fileStatuses = parseFileStatuses(nameStatusOutput);
|
|
97451
98332
|
const untrackedFiles = untrackedOutput.trim().split("\n").filter(Boolean);
|
|
97452
98333
|
const untrackedLines = await Promise.all(
|
|
97453
|
-
untrackedFiles.map((
|
|
98334
|
+
untrackedFiles.map((file3) => readUntrackedFileLines(worktree.host, cwd, file3))
|
|
97454
98335
|
);
|
|
97455
98336
|
for (let i = 0; i < untrackedFiles.length; i++) {
|
|
97456
|
-
const
|
|
98337
|
+
const file3 = untrackedFiles[i];
|
|
97457
98338
|
const lines = untrackedLines[i];
|
|
97458
98339
|
if (lines === null) continue;
|
|
97459
|
-
diff += `diff --git a/${
|
|
98340
|
+
diff += `diff --git a/${file3} b/${file3}
|
|
97460
98341
|
`;
|
|
97461
98342
|
diff += "new file mode 100644\n";
|
|
97462
98343
|
diff += "--- /dev/null\n";
|
|
97463
|
-
diff += `+++ b/${
|
|
98344
|
+
diff += `+++ b/${file3}
|
|
97464
98345
|
`;
|
|
97465
98346
|
diff += `@@ -0,0 +1,${lines.length} @@
|
|
97466
98347
|
`;
|
|
@@ -97468,7 +98349,7 @@ var DiffService = class {
|
|
|
97468
98349
|
diff += "\n";
|
|
97469
98350
|
stats.filesChanged++;
|
|
97470
98351
|
stats.insertions += lines.length;
|
|
97471
|
-
fileStatuses[
|
|
98352
|
+
fileStatuses[file3] = "U";
|
|
97472
98353
|
}
|
|
97473
98354
|
return {
|
|
97474
98355
|
diff,
|
|
@@ -97696,7 +98577,7 @@ var diffService = new DiffService();
|
|
|
97696
98577
|
init_errors6();
|
|
97697
98578
|
init_ephemeral_lifecycle_service();
|
|
97698
98579
|
init_worktree_service();
|
|
97699
|
-
import { dirname as
|
|
98580
|
+
import { dirname as dirname16, extname as extname3, join as join51, resolve as resolve13, sep as sep9 } from "node:path";
|
|
97700
98581
|
var MAX_FILE_SIZE2 = 1024 * 1024;
|
|
97701
98582
|
var LANG_MAP2 = {
|
|
97702
98583
|
".js": "javascript",
|
|
@@ -97837,7 +98718,7 @@ var FilesService = class {
|
|
|
97837
98718
|
if (await exists2(fs3, target)) {
|
|
97838
98719
|
throw new Error("A file or directory already exists at this path");
|
|
97839
98720
|
}
|
|
97840
|
-
await assertParentDirectory(fs3,
|
|
98721
|
+
await assertParentDirectory(fs3, dirname16(target), "Parent");
|
|
97841
98722
|
await fs3.writeFile(target, content, { exclusive: true });
|
|
97842
98723
|
return { ok: true };
|
|
97843
98724
|
}
|
|
@@ -97847,7 +98728,7 @@ var FilesService = class {
|
|
|
97847
98728
|
if (await exists2(fs3, target)) {
|
|
97848
98729
|
throw new Error("A file or directory already exists at this path");
|
|
97849
98730
|
}
|
|
97850
|
-
await assertParentDirectory(fs3,
|
|
98731
|
+
await assertParentDirectory(fs3, dirname16(target), "Parent");
|
|
97851
98732
|
await fs3.mkdir(target);
|
|
97852
98733
|
return { ok: true };
|
|
97853
98734
|
}
|
|
@@ -97889,7 +98770,7 @@ var FilesService = class {
|
|
|
97889
98770
|
if (await exists2(fs3, toTarget)) {
|
|
97890
98771
|
throw new Error("A file or directory already exists at the destination");
|
|
97891
98772
|
}
|
|
97892
|
-
await assertParentDirectory(fs3,
|
|
98773
|
+
await assertParentDirectory(fs3, dirname16(toTarget), "Destination parent");
|
|
97893
98774
|
await fs3.rename(fromTarget, toTarget);
|
|
97894
98775
|
return {
|
|
97895
98776
|
ok: true,
|
|
@@ -97922,7 +98803,7 @@ var FilesService = class {
|
|
|
97922
98803
|
if (await exists2(fs3, toTarget)) {
|
|
97923
98804
|
throw new Error("A file or directory already exists at the destination");
|
|
97924
98805
|
}
|
|
97925
|
-
await assertParentDirectory(fs3,
|
|
98806
|
+
await assertParentDirectory(fs3, dirname16(toTarget), "Destination parent");
|
|
97926
98807
|
await fs3.copy(fromTarget, toTarget, { recursive: true, exclusive: true });
|
|
97927
98808
|
return {
|
|
97928
98809
|
ok: true,
|
|
@@ -99408,6 +100289,13 @@ var worktreeRouter = t.router({
|
|
|
99408
100289
|
toPath: external_exports2.string().min(1)
|
|
99409
100290
|
})
|
|
99410
100291
|
).mutation(({ input }) => filesService.copyPath(input.worktreeId, input.fromPath, input.toPath)),
|
|
100292
|
+
/** A signed, expiring URL for one worktree file, for a browser tab that cannot send the auth header. */
|
|
100293
|
+
previewUrl: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string(), path: external_exports2.string().min(1) })).query(({ input }) => {
|
|
100294
|
+
if (!worktreeService.resolve(input.worktreeId)) {
|
|
100295
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Worktree not found" });
|
|
100296
|
+
}
|
|
100297
|
+
return { url: worktreePreviewUrl(input.worktreeId, input.path) };
|
|
100298
|
+
}),
|
|
99411
100299
|
searchFiles: publicProcedure.input(
|
|
99412
100300
|
external_exports2.object({
|
|
99413
100301
|
worktreeId: external_exports2.string(),
|
|
@@ -99491,6 +100379,13 @@ var worktreesRouter = t.router({
|
|
|
99491
100379
|
throwAsTrpcError3(err);
|
|
99492
100380
|
}
|
|
99493
100381
|
}),
|
|
100382
|
+
setOrigin: publicProcedure.input(worktreeSetOriginInput).mutation(({ input }) => {
|
|
100383
|
+
try {
|
|
100384
|
+
return worktreeService.setOrigin(input);
|
|
100385
|
+
} catch (err) {
|
|
100386
|
+
throwAsTrpcError3(err);
|
|
100387
|
+
}
|
|
100388
|
+
}),
|
|
99494
100389
|
gitPull: publicProcedure.input(worktreeGitInput).mutation(async ({ input }) => {
|
|
99495
100390
|
try {
|
|
99496
100391
|
return await worktreeService.gitPull(input);
|
|
@@ -99514,7 +100409,7 @@ var worktreesRouter = t.router({
|
|
|
99514
100409
|
})
|
|
99515
100410
|
});
|
|
99516
100411
|
function throwAsTrpcError3(err) {
|
|
99517
|
-
if (err instanceof PlainRepoError) {
|
|
100412
|
+
if (err instanceof PlainRepoError || err instanceof InvalidOriginError) {
|
|
99518
100413
|
throw new TRPCError({ code: "BAD_REQUEST", message: err.message, cause: err });
|
|
99519
100414
|
}
|
|
99520
100415
|
if (err instanceof RepoHostNotAllowedError) {
|
|
@@ -99585,12 +100480,15 @@ function discoverProcedures() {
|
|
|
99585
100480
|
for (const [path2, procedure] of Object.entries(procRecord)) {
|
|
99586
100481
|
const type = procedure._def.type;
|
|
99587
100482
|
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.")
|
|
100483
|
+
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.
|
|
100484
|
+
path2.startsWith("hooks."))
|
|
99589
100485
|
continue;
|
|
100486
|
+
if (path2 === "worktree.previewUrl") continue;
|
|
99590
100487
|
if (path2 === "environment.validate") continue;
|
|
99591
100488
|
if (path2 === "repos.addFromWorker") continue;
|
|
99592
100489
|
if (path2 === "repos.addByUrl") continue;
|
|
99593
100490
|
if (path2 === "repos.inspectFolder" || path2 === "repos.resolveRemote") continue;
|
|
100491
|
+
if (path2 === "worktrees.setOrigin") continue;
|
|
99594
100492
|
if (path2 === "environment.build") continue;
|
|
99595
100493
|
const toolName2 = `band_${path2.replace(/\./g, "_")}`;
|
|
99596
100494
|
const inputs = procedure._def.inputs;
|
|
@@ -99764,6 +100662,11 @@ function crossHostRefusal(repo, targetHostId, callerHostId) {
|
|
|
99764
100662
|
}
|
|
99765
100663
|
return null;
|
|
99766
100664
|
}
|
|
100665
|
+
function repoHostRefusal(repo, callerHostId) {
|
|
100666
|
+
const allowed2 = allowedHostsOf(repo);
|
|
100667
|
+
if (isHostAllowed(allowed2, callerHostId)) return null;
|
|
100668
|
+
return `${repo} is limited to other hosts (${hostNamesOf(allowed2 ?? []).join(", ")})`;
|
|
100669
|
+
}
|
|
99767
100670
|
|
|
99768
100671
|
// src/server/api/mcp-proxy/band-tools.ts
|
|
99769
100672
|
init_worktree_service();
|
|
@@ -100905,7 +101808,7 @@ async function handleOAuthCallback(req, res) {
|
|
|
100905
101808
|
}
|
|
100906
101809
|
|
|
100907
101810
|
// src/server/api/web-app-manifest.ts
|
|
100908
|
-
import { createReadStream as createReadStream4, statSync as
|
|
101811
|
+
import { createReadStream as createReadStream4, statSync as statSync14 } from "node:fs";
|
|
100909
101812
|
import { join as join52 } from "node:path";
|
|
100910
101813
|
var WEB_APP_MANIFEST_PATH = "/manifest.webmanifest";
|
|
100911
101814
|
var ICON_FILES = ["band-192.png", "band-512.png", "apple-touch-icon.png"];
|
|
@@ -100929,7 +101832,7 @@ function handleWebAppManifest(req, res, publicDir) {
|
|
|
100929
101832
|
}
|
|
100930
101833
|
function sendFile(req, res, filePath, headers) {
|
|
100931
101834
|
try {
|
|
100932
|
-
const { size } =
|
|
101835
|
+
const { size } = statSync14(filePath);
|
|
100933
101836
|
res.writeHead(200, { ...headers, "Content-Length": size.toString() });
|
|
100934
101837
|
if (req.method === "HEAD") res.end();
|
|
100935
101838
|
else
|
|
@@ -101709,7 +102612,6 @@ async function runFirstTimeSetup() {
|
|
|
101709
102612
|
const results = await Promise.allSettled([
|
|
101710
102613
|
repoSync,
|
|
101711
102614
|
ensureSettingsDefaults(),
|
|
101712
|
-
ensureClaudeHooks(),
|
|
101713
102615
|
ensureSkillsInstalled()
|
|
101714
102616
|
]);
|
|
101715
102617
|
for (const r of results) {
|
|
@@ -101847,21 +102749,6 @@ function migrateAgentDefaultMode() {
|
|
|
101847
102749
|
saveSettings({ ...settings3, agents: { ...settings3.agents, defaultMode: mode } });
|
|
101848
102750
|
log73.info("Migrated cli.defaultVia to agents.defaultMode = %s", mode);
|
|
101849
102751
|
}
|
|
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
|
-
}
|
|
101864
|
-
}
|
|
101865
102752
|
async function ensureSkillsInstalled() {
|
|
101866
102753
|
try {
|
|
101867
102754
|
const result = await installSkills2({ log: log73 });
|
|
@@ -102013,11 +102900,12 @@ var defaultLookups = {
|
|
|
102013
102900
|
const chat = chatService.get(id);
|
|
102014
102901
|
return chat?.worktreeId ?? null;
|
|
102015
102902
|
},
|
|
102016
|
-
worktreeOfCwd: (cwd) => resolveWorktreeIdByCwd(cwd),
|
|
102903
|
+
worktreeOfCwd: (cwd, hostId2) => resolveWorktreeIdByCwd(cwd, hostId2),
|
|
102017
102904
|
hostOfTerminal: (id) => terminalService.hostIdOf(id),
|
|
102018
102905
|
worktreeOfBrowser: (id) => browserService.get(id)?.worktreeId ?? null,
|
|
102019
102906
|
worktreeOfSubscription: (id) => subscriptionService.list().find((s2) => s2.id === id)?.worktreeId ?? null,
|
|
102020
|
-
crossHostRefusal
|
|
102907
|
+
crossHostRefusal,
|
|
102908
|
+
repoHostRefusal
|
|
102021
102909
|
};
|
|
102022
102910
|
var WorkerRelayService = class {
|
|
102023
102911
|
constructor(lookups = defaultLookups) {
|
|
@@ -102152,7 +103040,7 @@ var workerRelayService = new WorkerRelayService();
|
|
|
102152
103040
|
init_worktree_service();
|
|
102153
103041
|
var log76 = createLogger("worker-link");
|
|
102154
103042
|
var repoQueries5 = new RepoQueries();
|
|
102155
|
-
var
|
|
103043
|
+
var LOCAL_HOST_ID11 = "local";
|
|
102156
103044
|
var CAPABILITY_REFRESH_MS = 3 * 6e4;
|
|
102157
103045
|
var WORKER_CONNECT_PATH = "/api/workers/connect";
|
|
102158
103046
|
var WORKER_EXCHANGE_PATH = "/api/workers/exchange";
|
|
@@ -102195,10 +103083,10 @@ var WorkerLinkService = class {
|
|
|
102195
103083
|
/** Makes every known worker resolvable, so a worktree on an offline host still finds its host. */
|
|
102196
103084
|
start() {
|
|
102197
103085
|
for (const row of this.queries.listHosts(1e3)) {
|
|
102198
|
-
if (row.id !==
|
|
103086
|
+
if (row.id !== LOCAL_HOST_ID11) this.remoteHost(row.id);
|
|
102199
103087
|
}
|
|
102200
103088
|
for (const row of this.queries.listHosts(1e3)) {
|
|
102201
|
-
if (row.id !==
|
|
103089
|
+
if (row.id !== LOCAL_HOST_ID11 && row.status === "online") {
|
|
102202
103090
|
this.queries.setHostStatus(row.id, "offline");
|
|
102203
103091
|
}
|
|
102204
103092
|
}
|
|
@@ -102224,7 +103112,7 @@ var WorkerLinkService = class {
|
|
|
102224
103112
|
this.refreshing = true;
|
|
102225
103113
|
try {
|
|
102226
103114
|
await Promise.allSettled(
|
|
102227
|
-
this.queries.listHosts(1e3).filter((row) => row.id !==
|
|
103115
|
+
this.queries.listHosts(1e3).filter((row) => row.id !== LOCAL_HOST_ID11 && row.status === "online").map((row) => this.refreshReport(row))
|
|
102228
103116
|
);
|
|
102229
103117
|
} finally {
|
|
102230
103118
|
this.refreshing = false;
|
|
@@ -102383,7 +103271,7 @@ function reportKey(report2) {
|
|
|
102383
103271
|
init_worktree_service();
|
|
102384
103272
|
function logCrash(message) {
|
|
102385
103273
|
try {
|
|
102386
|
-
|
|
103274
|
+
mkdirSync10(bandHome(), { recursive: true });
|
|
102387
103275
|
appendFileSync2(join55(bandHome(), "server.log"), message, "utf-8");
|
|
102388
103276
|
} catch {
|
|
102389
103277
|
}
|
|
@@ -102426,7 +103314,7 @@ function resolveUiDir() {
|
|
|
102426
103314
|
resolve14(SERVER_ROOT, "..", "..", "web", "dist", "client"),
|
|
102427
103315
|
join55(SERVER_ROOT, "client")
|
|
102428
103316
|
];
|
|
102429
|
-
return candidates.find((dir) =>
|
|
103317
|
+
return candidates.find((dir) => existsSync17(join55(dir, "_shell.html"))) ?? candidates[1];
|
|
102430
103318
|
}
|
|
102431
103319
|
var clientDir = resolveUiDir();
|
|
102432
103320
|
var uiRoot = process.env.BAND_UI_ROOT ? resolve14(process.env.BAND_UI_ROOT) : resolve14(SERVER_ROOT, "..", "web");
|
|
@@ -102511,7 +103399,7 @@ function serveStaticFile(res, root, subdir, rawFilename) {
|
|
|
102511
103399
|
}
|
|
102512
103400
|
const filePath = join55(root, subdir, filename);
|
|
102513
103401
|
try {
|
|
102514
|
-
const fileStat =
|
|
103402
|
+
const fileStat = statSync15(filePath);
|
|
102515
103403
|
const contentType = mimeTypeFromFilename(filename);
|
|
102516
103404
|
res.writeHead(200, {
|
|
102517
103405
|
"Content-Type": contentType,
|
|
@@ -102526,26 +103414,26 @@ function serveStaticFile(res, root, subdir, rawFilename) {
|
|
|
102526
103414
|
}
|
|
102527
103415
|
async function serveHostedFile(res, kind, worktreeId4, rawName) {
|
|
102528
103416
|
if (hostRegistry.hostFor(worktreeId4).id === hostRegistry.local.id) return false;
|
|
102529
|
-
const
|
|
102530
|
-
if (!
|
|
103417
|
+
const file3 = await openHostedFile(kind, worktreeId4, rawName);
|
|
103418
|
+
if (!file3) {
|
|
102531
103419
|
res.writeHead(404);
|
|
102532
103420
|
res.end("Not found");
|
|
102533
103421
|
return true;
|
|
102534
103422
|
}
|
|
102535
103423
|
res.writeHead(200, {
|
|
102536
103424
|
"Content-Type": mimeTypeFromFilename(basename7(decodeURIComponent(rawName))),
|
|
102537
|
-
"Content-Length":
|
|
103425
|
+
"Content-Length": file3.size.toString(),
|
|
102538
103426
|
"Cache-Control": "private, max-age=86400"
|
|
102539
103427
|
});
|
|
102540
103428
|
pipeline(
|
|
102541
|
-
Readable2.from(
|
|
103429
|
+
Readable2.from(file3.stream, { objectMode: false, highWaterMark: 64 * 1024 }),
|
|
102542
103430
|
res,
|
|
102543
103431
|
() => {
|
|
102544
103432
|
}
|
|
102545
103433
|
);
|
|
102546
103434
|
return true;
|
|
102547
103435
|
}
|
|
102548
|
-
async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
103436
|
+
async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}) {
|
|
102549
103437
|
const worktree = worktreeService.resolve(worktreeId4);
|
|
102550
103438
|
if (!worktree) {
|
|
102551
103439
|
res.writeHead(404);
|
|
@@ -102561,12 +103449,25 @@ async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
|
102561
103449
|
}
|
|
102562
103450
|
try {
|
|
102563
103451
|
const { fs: fs3 } = worktree.host;
|
|
103452
|
+
const [realRoot, realTarget] = await Promise.all([fs3.realpath(root), fs3.realpath(target)]);
|
|
103453
|
+
if (realTarget !== realRoot && !realTarget.startsWith(realRoot + sep10)) {
|
|
103454
|
+
res.writeHead(400);
|
|
103455
|
+
res.end("Bad request");
|
|
103456
|
+
return;
|
|
103457
|
+
}
|
|
102564
103458
|
const fileStat = await fs3.stat(target, { followSymlinks: true });
|
|
102565
103459
|
const contentType = mimeTypeFromFilename(basename7(target));
|
|
103460
|
+
const isHtml = contentType.startsWith("text/html");
|
|
102566
103461
|
res.writeHead(200, {
|
|
102567
103462
|
"Content-Type": contentType,
|
|
102568
103463
|
"Content-Length": fileStat.size.toString(),
|
|
102569
|
-
"Cache-Control": "private, no-cache"
|
|
103464
|
+
"Cache-Control": "private, no-cache",
|
|
103465
|
+
// HTML from a repo must not run with the hub's origin.
|
|
103466
|
+
...isHtml ? {
|
|
103467
|
+
"Content-Security-Policy": "sandbox allow-scripts",
|
|
103468
|
+
"X-Content-Type-Options": "nosniff"
|
|
103469
|
+
} : {},
|
|
103470
|
+
...extraHeaders
|
|
102570
103471
|
});
|
|
102571
103472
|
pipeline(
|
|
102572
103473
|
Readable2.from(fs3.readStream(target), { objectMode: false, highWaterMark: 64 * 1024 }),
|
|
@@ -102579,6 +103480,41 @@ async function serveWorktreeFile(res, worktreeId4, rawPath) {
|
|
|
102579
103480
|
res.end("Not found");
|
|
102580
103481
|
}
|
|
102581
103482
|
}
|
|
103483
|
+
async function handleWorktreePreview(req, res) {
|
|
103484
|
+
const rest = (req.url ?? "").slice(WORKTREE_PREVIEW_PREFIX.length).split("?")[0];
|
|
103485
|
+
const [rawId, grant, ...pathParts] = rest.split("/");
|
|
103486
|
+
let wId;
|
|
103487
|
+
let filePath;
|
|
103488
|
+
try {
|
|
103489
|
+
wId = decodeURIComponent(rawId ?? "");
|
|
103490
|
+
filePath = decodeURIComponent(pathParts.join("/"));
|
|
103491
|
+
} catch {
|
|
103492
|
+
res.writeHead(400);
|
|
103493
|
+
res.end("Bad request");
|
|
103494
|
+
return;
|
|
103495
|
+
}
|
|
103496
|
+
if (!wId || !grant || !filePath) {
|
|
103497
|
+
res.writeHead(400);
|
|
103498
|
+
res.end("Bad request");
|
|
103499
|
+
return;
|
|
103500
|
+
}
|
|
103501
|
+
if (!verifyWorktreePreviewGrant(wId, grant, filePath)) {
|
|
103502
|
+
res.writeHead(403);
|
|
103503
|
+
res.end("Forbidden");
|
|
103504
|
+
return;
|
|
103505
|
+
}
|
|
103506
|
+
await serveWorktreeFile(res, wId, filePath, {
|
|
103507
|
+
// The page runs repo-controlled scripts on the hub's origin. `sandbox`
|
|
103508
|
+
// without `allow-same-origin` gives it an opaque origin, so it cannot use
|
|
103509
|
+
// the hub's cookies, storage or tRPC. Its own assets are plain GETs under
|
|
103510
|
+
// the grant, and the wildcard CORS header lets scripts fetch them.
|
|
103511
|
+
"Content-Security-Policy": "sandbox allow-scripts allow-forms allow-popups allow-modals",
|
|
103512
|
+
"X-Content-Type-Options": "nosniff",
|
|
103513
|
+
"Referrer-Policy": "no-referrer",
|
|
103514
|
+
"Access-Control-Allow-Origin": "*",
|
|
103515
|
+
"Cache-Control": "private, no-store"
|
|
103516
|
+
});
|
|
103517
|
+
}
|
|
102582
103518
|
var MAX_WEBHOOK_BODY_BYTES = 256 * 1024;
|
|
102583
103519
|
var MAX_GITHUB_HOOK_BODY_BYTES = 2 * 1024 * 1024;
|
|
102584
103520
|
async function handleGithubWebhook(req, res) {
|
|
@@ -102815,6 +103751,10 @@ async function main() {
|
|
|
102815
103751
|
await handleSubscriptionWebhook(req, res, subscriptionId);
|
|
102816
103752
|
return;
|
|
102817
103753
|
}
|
|
103754
|
+
if (req.method === "GET" && req.url?.startsWith(WORKTREE_PREVIEW_PREFIX)) {
|
|
103755
|
+
await handleWorktreePreview(req, res);
|
|
103756
|
+
return;
|
|
103757
|
+
}
|
|
102818
103758
|
if (handleAuth(req, res)) return;
|
|
102819
103759
|
if (req.url?.startsWith("/api/uploads/")) {
|
|
102820
103760
|
const rest = req.url.slice("/api/uploads/".length);
|
|
@@ -102882,21 +103822,33 @@ async function main() {
|
|
|
102882
103822
|
return;
|
|
102883
103823
|
}
|
|
102884
103824
|
if (req.url?.startsWith("/api/worktree-file/")) {
|
|
102885
|
-
const
|
|
103825
|
+
const queryIdx = req.url.indexOf("?");
|
|
103826
|
+
const rest = req.url.slice(
|
|
103827
|
+
"/api/worktree-file/".length,
|
|
103828
|
+
queryIdx === -1 ? void 0 : queryIdx
|
|
103829
|
+
);
|
|
102886
103830
|
const slashIdx = rest.indexOf("/");
|
|
102887
103831
|
if (slashIdx === -1) {
|
|
102888
103832
|
res.writeHead(400);
|
|
102889
103833
|
res.end("Bad request");
|
|
102890
103834
|
return;
|
|
102891
103835
|
}
|
|
102892
|
-
|
|
102893
|
-
|
|
103836
|
+
let wId;
|
|
103837
|
+
let filePath;
|
|
103838
|
+
try {
|
|
103839
|
+
wId = decodeURIComponent(rest.slice(0, slashIdx));
|
|
103840
|
+
filePath = decodeURIComponent(rest.slice(slashIdx + 1));
|
|
103841
|
+
} catch {
|
|
103842
|
+
res.writeHead(400);
|
|
103843
|
+
res.end("Bad request");
|
|
103844
|
+
return;
|
|
103845
|
+
}
|
|
102894
103846
|
if (!wId || !filePath) {
|
|
102895
103847
|
res.writeHead(400);
|
|
102896
103848
|
res.end("Bad request");
|
|
102897
103849
|
return;
|
|
102898
103850
|
}
|
|
102899
|
-
await serveWorktreeFile(res, wId,
|
|
103851
|
+
await serveWorktreeFile(res, wId, filePath);
|
|
102900
103852
|
return;
|
|
102901
103853
|
}
|
|
102902
103854
|
if (req.url?.startsWith("/api/cdp/tabs")) {
|
|
@@ -103087,7 +104039,7 @@ async function main() {
|
|
|
103087
104039
|
}
|
|
103088
104040
|
});
|
|
103089
104041
|
const shellPath2 = join55(clientDir, "_shell.html");
|
|
103090
|
-
if (!
|
|
104042
|
+
if (!existsSync17(shellPath2)) {
|
|
103091
104043
|
throw new Error(
|
|
103092
104044
|
`UI build not found at ${clientDir} (no _shell.html). Run \`pnpm build\` or pass --ui-dir <path>.`
|
|
103093
104045
|
);
|