@band-app/server 0.54.2 → 0.54.4
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-zm8MgtWD.js → TerminalPanel-CxzXrgZc.js} +1 -1
- package/dist/client/assets/{_basePickBy-Df9AWPQJ.js → _basePickBy-ESHGoJr1.js} +1 -1
- package/dist/client/assets/{_baseUniq-C8iGnWYN.js → _baseUniq-Caza5c4E.js} +1 -1
- package/dist/client/assets/{arc-Dotr_YMW.js → arc-DaZonhJY.js} +1 -1
- package/dist/client/assets/{architectureDiagram-VXUJARFQ-BkG8VmpL.js → architectureDiagram-VXUJARFQ-Cq7YbAnJ.js} +1 -1
- package/dist/client/assets/{blockDiagram-VD42YOAC-DZYGOHoq.js → blockDiagram-VD42YOAC-C8s0ghb3.js} +1 -1
- package/dist/client/assets/{c4Diagram-YG6GDRKO-Ce9RV_Np.js → c4Diagram-YG6GDRKO-DIBhFHGh.js} +1 -1
- package/dist/client/assets/channel-D_oNvIqF.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-Ck1qaTAA.js → chunk-4BX2VUAB-D3SZ3P8V.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CeH02gXd.js → chunk-55IACEB6-BfA-9jtI.js} +1 -1
- package/dist/client/assets/{chunk-B4BG7PRW-DqBzx79X.js → chunk-B4BG7PRW-DAygCOyX.js} +1 -1
- package/dist/client/assets/{chunk-DI55MBZ5-p68JZT5Z.js → chunk-DI55MBZ5-CuikvpNq.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-DC8dY12t.js → chunk-FMBD7UC4-JCmcJDsw.js} +1 -1
- package/dist/client/assets/{chunk-QN33PNHL-D1Af7sn_.js → chunk-QN33PNHL-3o6_7ioT.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-BHBPr8iM.js → chunk-QZHKN3VN-5B8nia1f.js} +1 -1
- package/dist/client/assets/{chunk-TZMSLE5B-BP9v1VXR.js → chunk-TZMSLE5B-stQp3cfp.js} +1 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-C8EJzaAq.js +1 -0
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-C8EJzaAq.js +1 -0
- package/dist/client/assets/clone-BhU1fGlA.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-C7S-iRjs.js → cose-bilkent-S5V4N54A-CVQzgyKs.js} +1 -1
- package/dist/client/assets/{dagre-6UL2VRFP-CIdLKDDa.js → dagre-6UL2VRFP-C95ZZlB4.js} +1 -1
- package/dist/client/assets/{diagram-PSM6KHXK-_VfU4I5J.js → diagram-PSM6KHXK-Sb4x5piz.js} +1 -1
- package/dist/client/assets/{diagram-QEK2KX5R-ByPoixdO.js → diagram-QEK2KX5R-TTDYZins.js} +1 -1
- package/dist/client/assets/{diagram-S2PKOQOG-CU2nF5Pi.js → diagram-S2PKOQOG-C8joLdiY.js} +1 -1
- package/dist/client/assets/{erDiagram-Q2GNP2WA-DuReqla0.js → erDiagram-Q2GNP2WA-HXkzVcZE.js} +1 -1
- package/dist/client/assets/{flowDiagram-NV44I4VS-BzbJhnQR.js → flowDiagram-NV44I4VS-BPzPMXG7.js} +1 -1
- package/dist/client/assets/{ganttDiagram-JELNMOA3-BeBP4O51.js → ganttDiagram-JELNMOA3-5JGkIYiO.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-V2S2FVAM-7rU4MgM6.js → gitGraphDiagram-V2S2FVAM-CDCkC2dq.js} +1 -1
- package/dist/client/assets/{graph-v9lPIGDx.js → graph-CQ3VI3Kb.js} +1 -1
- package/dist/client/assets/{highlighted-body-B3W2YXNL-Bt5DPhhI.js → highlighted-body-B3W2YXNL-jA7b7ztt.js} +1 -1
- package/dist/client/assets/{index-XAyPsPHs.js → index-0DuoTbku.js} +1 -1
- package/dist/client/assets/{index-Dy40JkoS.js → index-B6FlIvZu.js} +1 -1
- package/dist/client/assets/{index-DjGPjARU.js → index-Bcr3D2nL.js} +1 -1
- package/dist/client/assets/{index-jC0z3Kaq.js → index-BedjQBXV.js} +1 -1
- package/dist/client/assets/{index-CJefa5Go.js → index-C7Xj47iO.js} +1 -1
- package/dist/client/assets/{index-CsZW3tVv.js → index-CZqDBAUL.js} +1 -1
- package/dist/client/assets/{index-Qk1h8PGS.js → index-Ce8SticC.js} +1 -1
- package/dist/client/assets/{index-D2wFO-D3.js → index-DWepK3T1.js} +1 -1
- package/dist/client/assets/{index-DrM0qB0a.js → index-Dt3jdJaQ.js} +1 -1
- package/dist/client/assets/{index-Be1D1vwS.js → index-F2GbFYex.js} +1 -1
- package/dist/client/assets/{index-DoGYlvHl.js → index-SjDPUTbU.js} +1 -1
- package/dist/client/assets/{index-DGN6wKkW.js → index-sTT9joMB.js} +1 -1
- package/dist/client/assets/{index-Q6D-8JjG.js → index-xEgVFQfI.js} +1 -1
- package/dist/client/assets/{infoDiagram-HS3SLOUP-BOjyAfYW.js → infoDiagram-HS3SLOUP-DXlzs7_8.js} +1 -1
- package/dist/client/assets/{journeyDiagram-XKPGCS4Q-daUwYLfO.js → journeyDiagram-XKPGCS4Q-B7mdzrEf.js} +1 -1
- package/dist/client/assets/{kanban-definition-3W4ZIXB7-Dkcl1vfA.js → kanban-definition-3W4ZIXB7-C3yAE4ky.js} +1 -1
- package/dist/client/assets/{layout-CSSEtaEF.js → layout-BpyKMBbb.js} +1 -1
- package/dist/client/assets/{main-BzXVe5ia.js → main-B169v3Mr.js} +245 -245
- package/dist/client/assets/main-DWmz5QXO.css +1 -0
- package/dist/client/assets/{mindmap-definition-VGOIOE7T-D17_XGFA.js → mindmap-definition-VGOIOE7T-BV1ylQ_o.js} +1 -1
- package/dist/client/assets/{pieDiagram-ADFJNKIX-D07iYAxA.js → pieDiagram-ADFJNKIX-C68Mdt_x.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AYHSOK5B-B9imU2k-.js → quadrantDiagram-AYHSOK5B-5wmdYa_y.js} +1 -1
- package/dist/client/assets/{requirementDiagram-UZGBJVZJ-CMH8dVSi.js → requirementDiagram-UZGBJVZJ-CXCtBgk5.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-TZEHDZUN-CWS08bde.js → sankeyDiagram-TZEHDZUN-CwuKYu6l.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WL72ISMW-BcDiQ1t6.js → sequenceDiagram-WL72ISMW-BwFU-IAs.js} +1 -1
- package/dist/client/assets/{stateDiagram-FKZM4ZOC-Be7DSpGK.js → stateDiagram-FKZM4ZOC-B46B2gqH.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-BvDRMNEQ.js +1 -0
- package/dist/client/assets/{timeline-definition-IT6M3QCI-BwTGYKzr.js → timeline-definition-IT6M3QCI-DHJrxfmL.js} +1 -1
- package/dist/client/assets/{treemap-GDKQZRPO-DDASk1M2.js → treemap-GDKQZRPO-Bn3yodwG.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-C3DGXQD8.js → worktree._worktreeId-7oupLNxr.js} +1 -1
- package/dist/client/assets/{worktree._worktreeId-DsTjUoT9.js → worktree._worktreeId-MU4bksud.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PRI3JC2R-CShK1tn5.js → xychartDiagram-PRI3JC2R-DUqpx4dY.js} +1 -1
- package/dist/migrations/20261028200323_worktree_title/migration.sql +1 -0
- package/dist/migrations/20261028200323_worktree_title/snapshot.json +5228 -0
- package/dist/migrations/20261029080757_worktree_unread/migration.sql +1 -0
- package/dist/migrations/20261029080757_worktree_unread/snapshot.json +5238 -0
- package/dist/openapi.json +186 -1
- package/dist/start-server.mjs +411 -54
- package/package.json +11 -6
- package/dist/client/assets/channel-BQWEKiCC.js +0 -1
- package/dist/client/assets/classDiagram-2ON5EDUG-Bkz5oJ-y.js +0 -1
- package/dist/client/assets/classDiagram-v2-WZHVMYZB-Bkz5oJ-y.js +0 -1
- package/dist/client/assets/clone-Dr7z_3vQ.js +0 -1
- package/dist/client/assets/main-BYKQWpTo.css +0 -1
- package/dist/client/assets/stateDiagram-v2-4FDKWEC3-CSGrJZGI.js +0 -1
package/dist/start-server.mjs
CHANGED
|
@@ -2556,7 +2556,7 @@ var require_thread_stream = __commonJS({
|
|
|
2556
2556
|
var require_transport = __commonJS({
|
|
2557
2557
|
"../../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/transport.js"(exports, module) {
|
|
2558
2558
|
"use strict";
|
|
2559
|
-
var { createRequire:
|
|
2559
|
+
var { createRequire: createRequire3 } = __require("module");
|
|
2560
2560
|
var getCallers = require_caller();
|
|
2561
2561
|
var { join: join59, isAbsolute: isAbsolute7, sep: sep11 } = __require("node:path");
|
|
2562
2562
|
var sleep3 = require_atomic_sleep();
|
|
@@ -2667,7 +2667,7 @@ var require_transport = __commonJS({
|
|
|
2667
2667
|
for (const filePath of callers) {
|
|
2668
2668
|
try {
|
|
2669
2669
|
const context = filePath === "node:repl" ? process.cwd() + sep11 : filePath;
|
|
2670
|
-
fixTarget2 =
|
|
2670
|
+
fixTarget2 = createRequire3(context).resolve(origin);
|
|
2671
2671
|
break;
|
|
2672
2672
|
} catch (err) {
|
|
2673
2673
|
continue;
|
|
@@ -51994,6 +51994,11 @@ var init_schema2 = __esm({
|
|
|
51994
51994
|
// id with no foreign key, like the origin columns: only `RepoQueries.setGroup` writes it, and
|
|
51995
51995
|
// deleting a group nulls it.
|
|
51996
51996
|
groupId: text3("group_id"),
|
|
51997
|
+
// Marked unread by a person: the sidebar shows a dot on every device until the worktree is
|
|
51998
|
+
// opened or marked read. Only `RepoQueries.setUnread` writes it.
|
|
51999
|
+
unread: integer4("unread", { mode: "boolean" }).notNull().default(false),
|
|
52000
|
+
// A custom title shown in place of the name, or null. Only `RepoQueries.setTitle` writes it.
|
|
52001
|
+
title: text3("title"),
|
|
51997
52002
|
// Set when the worktree was restored on another host: the id of the replacement. The copy
|
|
51998
52003
|
// stays on disk untouched and is listed as superseded until a person removes it.
|
|
51999
52004
|
supersededBy: text3("superseded_by"),
|
|
@@ -54078,8 +54083,12 @@ var init_mcp_stdio = __esm({
|
|
|
54078
54083
|
|
|
54079
54084
|
// ../../packages/host-local/src/process/cli-binary.ts
|
|
54080
54085
|
import { lstatSync as lstatSync3, realpathSync as realpathSync4 } from "node:fs";
|
|
54086
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
54081
54087
|
import { platform } from "node:os";
|
|
54082
|
-
import { delimiter as delimiter3, join as join19, resolve as resolve4 } from "node:path";
|
|
54088
|
+
import { delimiter as delimiter3, dirname as dirOf, join as join19, resolve as resolve4 } from "node:path";
|
|
54089
|
+
function cliPackageName(os = platform(), arch = process.arch) {
|
|
54090
|
+
return `@band-app/worker-cli-${os}-${arch}`;
|
|
54091
|
+
}
|
|
54083
54092
|
function findCliBinaryAt(opts) {
|
|
54084
54093
|
const { cwd, dirname: dirname18 } = opts;
|
|
54085
54094
|
const exe = platform() === "win32" ? "band.exe" : "band";
|
|
@@ -54131,6 +54140,15 @@ function findCliBinaryAt(opts) {
|
|
|
54131
54140
|
} catch {
|
|
54132
54141
|
}
|
|
54133
54142
|
}
|
|
54143
|
+
try {
|
|
54144
|
+
const pkgJson = createRequire2(join19(dirname18, "noop.js")).resolve(
|
|
54145
|
+
`${cliPackageName()}/package.json`
|
|
54146
|
+
);
|
|
54147
|
+
const p2 = join19(dirOf(pkgJson), "bin", exe);
|
|
54148
|
+
lstatSync3(p2);
|
|
54149
|
+
return p2;
|
|
54150
|
+
} catch {
|
|
54151
|
+
}
|
|
54134
54152
|
for (const dir of (process.env.PATH ?? "").split(delimiter3)) {
|
|
54135
54153
|
if (!dir) continue;
|
|
54136
54154
|
try {
|
|
@@ -61047,8 +61065,42 @@ import {
|
|
|
61047
61065
|
import { homedir as homedir16, hostname as hostname4, tmpdir as tmpdir2 } from "node:os";
|
|
61048
61066
|
import { join as join34 } from "node:path";
|
|
61049
61067
|
async function createWorktree(spec) {
|
|
61068
|
+
const refs = await execGit(
|
|
61069
|
+
["for-each-ref", "--format=%(refname)", "refs/heads", "refs/remotes/origin"],
|
|
61070
|
+
spec.repoPath
|
|
61071
|
+
);
|
|
61072
|
+
const local = [];
|
|
61073
|
+
const known = [];
|
|
61074
|
+
for (const ref of refs.split("\n").filter(Boolean)) {
|
|
61075
|
+
if (ref.startsWith("refs/heads/")) {
|
|
61076
|
+
local.push(ref.slice("refs/heads/".length));
|
|
61077
|
+
known.push(ref.slice("refs/heads/".length));
|
|
61078
|
+
} else if (ref.startsWith("refs/remotes/origin/")) {
|
|
61079
|
+
known.push(ref.slice("refs/remotes/origin/".length));
|
|
61080
|
+
}
|
|
61081
|
+
}
|
|
61082
|
+
const clash = known.find(
|
|
61083
|
+
(b2) => b2 !== spec.branch && b2.toLowerCase() === spec.branch.toLowerCase()
|
|
61084
|
+
);
|
|
61085
|
+
if (clash) {
|
|
61086
|
+
throw new Error(
|
|
61087
|
+
`Branch "${spec.branch}" differs only in case from the existing branch "${clash}". Use "${clash}" or pick another name.`
|
|
61088
|
+
);
|
|
61089
|
+
}
|
|
61050
61090
|
const args = ["worktree", "add", "-b", spec.branch, spec.path];
|
|
61051
|
-
if (spec.base)
|
|
61091
|
+
if (spec.base) {
|
|
61092
|
+
args.push(spec.base);
|
|
61093
|
+
} else if (!local.includes(spec.branch)) {
|
|
61094
|
+
const remote = `origin/${spec.branch}`;
|
|
61095
|
+
const found = await execGit(
|
|
61096
|
+
["rev-parse", "--verify", "--quiet", `refs/remotes/${remote}`],
|
|
61097
|
+
spec.repoPath
|
|
61098
|
+
).then(() => true).catch(() => false);
|
|
61099
|
+
if (found) {
|
|
61100
|
+
args.splice(2, 0, "--track");
|
|
61101
|
+
args.push(remote);
|
|
61102
|
+
}
|
|
61103
|
+
}
|
|
61052
61104
|
await execGit(args, spec.repoPath);
|
|
61053
61105
|
return { path: spec.path, branch: spec.branch };
|
|
61054
61106
|
}
|
|
@@ -61379,8 +61431,8 @@ var init_local_host = __esm({
|
|
|
61379
61431
|
return new Uint8Array(await readFile8(path2));
|
|
61380
61432
|
},
|
|
61381
61433
|
realpath: (path2) => realpath3(path2),
|
|
61382
|
-
async *readStream(path2) {
|
|
61383
|
-
for await (const chunk of createReadStream3(path2)) {
|
|
61434
|
+
async *readStream(path2, range) {
|
|
61435
|
+
for await (const chunk of createReadStream3(path2, range)) {
|
|
61384
61436
|
const buffer = chunk;
|
|
61385
61437
|
yield new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
61386
61438
|
}
|
|
@@ -61443,7 +61495,21 @@ function reconcileKindForRepo(repo) {
|
|
|
61443
61495
|
if (detectedKind === repo.kind) return false;
|
|
61444
61496
|
repo.kind = detectedKind;
|
|
61445
61497
|
if (detectedKind === "plain") {
|
|
61446
|
-
|
|
61498
|
+
const main2 = repo.worktrees.find(
|
|
61499
|
+
(wt) => wt.name === "main" && (wt.hostId ?? "local") === "local"
|
|
61500
|
+
);
|
|
61501
|
+
repo.worktrees = [
|
|
61502
|
+
{
|
|
61503
|
+
name: "main",
|
|
61504
|
+
branch: "main",
|
|
61505
|
+
path: repo.path,
|
|
61506
|
+
pinned: false,
|
|
61507
|
+
...main2?.origin ? { origin: main2.origin } : {},
|
|
61508
|
+
...main2?.groupId ? { groupId: main2.groupId } : {},
|
|
61509
|
+
...main2?.unread ? { unread: true } : {},
|
|
61510
|
+
...main2?.title ? { title: main2.title } : {}
|
|
61511
|
+
}
|
|
61512
|
+
];
|
|
61447
61513
|
}
|
|
61448
61514
|
return true;
|
|
61449
61515
|
}
|
|
@@ -61492,6 +61558,8 @@ var init_repos = __esm({
|
|
|
61492
61558
|
}
|
|
61493
61559
|
} : {},
|
|
61494
61560
|
...row.groupId ? { groupId: row.groupId } : {},
|
|
61561
|
+
...row.unread ? { unread: true } : {},
|
|
61562
|
+
...row.title ? { title: row.title } : {},
|
|
61495
61563
|
...row.supersededBy ? { supersededBy: row.supersededBy } : {},
|
|
61496
61564
|
...row.continuedFrom ? { continuedFrom: row.continuedFrom } : {},
|
|
61497
61565
|
...row.divergence ? { divergence: row.divergence } : {}
|
|
@@ -61545,7 +61613,9 @@ var init_repos = __esm({
|
|
|
61545
61613
|
originWorktreeId: worktrees.originWorktreeId,
|
|
61546
61614
|
originChatId: worktrees.originChatId,
|
|
61547
61615
|
originTerminalId: worktrees.originTerminalId,
|
|
61548
|
-
groupId: worktrees.groupId
|
|
61616
|
+
groupId: worktrees.groupId,
|
|
61617
|
+
unread: worktrees.unread,
|
|
61618
|
+
title: worktrees.title
|
|
61549
61619
|
}).from(worktrees).all().map((w2) => [`${w2.repoName}\0${w2.name}`, w2])
|
|
61550
61620
|
);
|
|
61551
61621
|
const continuation = new Map(
|
|
@@ -61587,6 +61657,8 @@ var init_repos = __esm({
|
|
|
61587
61657
|
originChatId: origin?.originChatId ?? null,
|
|
61588
61658
|
originTerminalId: origin?.originTerminalId ?? null,
|
|
61589
61659
|
groupId: origin?.groupId ?? null,
|
|
61660
|
+
unread: origin?.unread ?? false,
|
|
61661
|
+
title: origin?.title ?? null,
|
|
61590
61662
|
repoName: repo.name,
|
|
61591
61663
|
name: wt.name,
|
|
61592
61664
|
branch: wt.branch,
|
|
@@ -61795,6 +61867,16 @@ var init_repos = __esm({
|
|
|
61795
61867
|
}).from(repos).where(eq2(repos.name, name5)).get();
|
|
61796
61868
|
return row ? { allowedHostIds: row.allowedHostIds ?? null, primaryHostId: row.primaryHostId ?? null } : void 0;
|
|
61797
61869
|
}
|
|
61870
|
+
/** Sets a worktree's custom title, or clears it with `null`. Focused UPDATE, like `setGroup`. */
|
|
61871
|
+
setTitle(repoName, name5, title, hostId2) {
|
|
61872
|
+
getDb().update(worktrees).set({ title }).where(
|
|
61873
|
+
and2(
|
|
61874
|
+
eq2(worktrees.repoName, repoName),
|
|
61875
|
+
eq2(worktrees.name, name5),
|
|
61876
|
+
...hostId2 === void 0 ? [] : [eq2(worktrees.hostId, hostId2)]
|
|
61877
|
+
)
|
|
61878
|
+
).run();
|
|
61879
|
+
}
|
|
61798
61880
|
/**
|
|
61799
61881
|
* Puts a worktree in a group, or in none with `null`. Focused UPDATE, because `saveAll` keeps
|
|
61800
61882
|
* the stored group and never writes the one in its snapshot.
|
|
@@ -61808,6 +61890,19 @@ var init_repos = __esm({
|
|
|
61808
61890
|
)
|
|
61809
61891
|
).run();
|
|
61810
61892
|
}
|
|
61893
|
+
/**
|
|
61894
|
+
* Marks a worktree unread or read. Focused UPDATE, because `saveAll` keeps the stored flag and
|
|
61895
|
+
* never writes the one in its snapshot.
|
|
61896
|
+
*/
|
|
61897
|
+
setUnread(repoName, name5, unread, hostId2) {
|
|
61898
|
+
getDb().update(worktrees).set({ unread }).where(
|
|
61899
|
+
and2(
|
|
61900
|
+
eq2(worktrees.repoName, repoName),
|
|
61901
|
+
eq2(worktrees.name, name5),
|
|
61902
|
+
...hostId2 === void 0 ? [] : [eq2(worktrees.hostId, hostId2)]
|
|
61903
|
+
)
|
|
61904
|
+
).run();
|
|
61905
|
+
}
|
|
61811
61906
|
/**
|
|
61812
61907
|
* Records where a worktree was started from, or clears it with `null`. Focused UPDATE, because
|
|
61813
61908
|
* `saveAll` keeps the stored origin and never writes the one in its snapshot.
|
|
@@ -66889,13 +66984,28 @@ var init_git_refusals = __esm({
|
|
|
66889
66984
|
|
|
66890
66985
|
// ../../packages/shared/src/branch-name.ts
|
|
66891
66986
|
function slugifyBranchName(input2) {
|
|
66892
|
-
|
|
66987
|
+
let name5 = input2.trim().replace(/\s+/g, "-").replace(/[\x00-\x1f\x7f~^:?*[\\]/g, "").replace(/@\{/g, "").replace(/^@$/, "");
|
|
66988
|
+
let previous;
|
|
66989
|
+
do {
|
|
66990
|
+
previous = name5;
|
|
66991
|
+
name5 = name5.replace(/\.{2,}/g, ".").replace(/@\{/g, "").replace(/-{2,}/g, "-").replace(/\/+/g, "/").replace(/\/\./g, "/").replace(/\.lock(?=\/|$)/g, "").replace(/\.+(?=\/|$)/g, "").replace(/^[-/.]+|[-/.]+$/g, "");
|
|
66992
|
+
} while (name5 !== previous);
|
|
66993
|
+
return name5;
|
|
66893
66994
|
}
|
|
66894
66995
|
var init_branch_name = __esm({
|
|
66895
66996
|
"../../packages/shared/src/branch-name.ts"() {
|
|
66896
66997
|
}
|
|
66897
66998
|
});
|
|
66898
66999
|
|
|
67000
|
+
// ../../packages/shared/src/worktree-display.ts
|
|
67001
|
+
var WORKTREE_TITLE_MAX;
|
|
67002
|
+
var init_worktree_display = __esm({
|
|
67003
|
+
"../../packages/shared/src/worktree-display.ts"() {
|
|
67004
|
+
init_worktree_id();
|
|
67005
|
+
WORKTREE_TITLE_MAX = 100;
|
|
67006
|
+
}
|
|
67007
|
+
});
|
|
67008
|
+
|
|
66899
67009
|
// src/server/infra/db/queries/pending-removals.ts
|
|
66900
67010
|
var PendingRemovalQueries;
|
|
66901
67011
|
var init_pending_removals = __esm({
|
|
@@ -68286,6 +68396,11 @@ var init_mime_types = __esm({
|
|
|
68286
68396
|
".ico": "image/x-icon",
|
|
68287
68397
|
".bmp": "image/bmp",
|
|
68288
68398
|
".avif": "image/avif",
|
|
68399
|
+
".mp4": "video/mp4",
|
|
68400
|
+
".m4v": "video/mp4",
|
|
68401
|
+
".mov": "video/quicktime",
|
|
68402
|
+
".webm": "video/webm",
|
|
68403
|
+
".ogv": "video/ogg",
|
|
68289
68404
|
".pdf": "application/pdf",
|
|
68290
68405
|
".json": "application/json",
|
|
68291
68406
|
".txt": "text/plain",
|
|
@@ -68723,7 +68838,7 @@ var init_channel = __esm({
|
|
|
68723
68838
|
});
|
|
68724
68839
|
|
|
68725
68840
|
// ../../packages/link/src/chats.ts
|
|
68726
|
-
var CAPABILITY_CHAT_OWNERSHIP, CHAT_ID_BLOCK_SIZE, METHOD_CHAT_OWN, METHOD_CHAT_FORWARD, METHOD_CHAT_RELEASE, METHOD_CHAT_SET_CONFIG, METHOD_CHAT_LIST_SESSIONS, METHOD_CHAT_REPLICATE, METHOD_CHAT_ID_BLOCK, METHOD_CHAT_HISTORY, METHOD_CHAT_LAUNCH, METHOD_CHAT_SESSION_STATE, NOTIFY_CHAT_LIVE, NOTIFY_CHAT_TRANSIENT, NOTIFY_CHAT_ACTIVITY;
|
|
68841
|
+
var CAPABILITY_CHAT_OWNERSHIP, CHAT_ID_BLOCK_SIZE, METHOD_CHAT_OWN, METHOD_CHAT_FORWARD, METHOD_CHAT_RELEASE, METHOD_CHAT_SET_CONFIG, METHOD_CHAT_LIST_SESSIONS, METHOD_CHAT_VIEW_SESSION, METHOD_CHAT_REPLICATE, METHOD_CHAT_ID_BLOCK, METHOD_CHAT_HISTORY, METHOD_CHAT_LAUNCH, METHOD_CHAT_SESSION_STATE, NOTIFY_CHAT_LIVE, NOTIFY_CHAT_TRANSIENT, NOTIFY_CHAT_ACTIVITY;
|
|
68727
68842
|
var init_chats2 = __esm({
|
|
68728
68843
|
"../../packages/link/src/chats.ts"() {
|
|
68729
68844
|
"use strict";
|
|
@@ -68734,6 +68849,7 @@ var init_chats2 = __esm({
|
|
|
68734
68849
|
METHOD_CHAT_RELEASE = "chat.release";
|
|
68735
68850
|
METHOD_CHAT_SET_CONFIG = "chat.setConfigOption";
|
|
68736
68851
|
METHOD_CHAT_LIST_SESSIONS = "chat.listSessions";
|
|
68852
|
+
METHOD_CHAT_VIEW_SESSION = "chat.viewSession";
|
|
68737
68853
|
METHOD_CHAT_REPLICATE = "chat.replicate";
|
|
68738
68854
|
METHOD_CHAT_ID_BLOCK = "chat.idBlock";
|
|
68739
68855
|
METHOD_CHAT_HISTORY = "chat.history";
|
|
@@ -69676,7 +69792,7 @@ function forwardedTask(id, options) {
|
|
|
69676
69792
|
codingAgentId: options.codingAgentId
|
|
69677
69793
|
};
|
|
69678
69794
|
}
|
|
69679
|
-
var log23, FORWARD_TIMEOUT_MS, RETRY_MS, HISTORY_MAX_ROWS, ChatOwnershipService, chatOwnershipService;
|
|
69795
|
+
var log23, FORWARD_TIMEOUT_MS, VIEW_SESSION_TIMEOUT_MS, RETRY_MS, HISTORY_MAX_ROWS, ChatOwnershipService, chatOwnershipService;
|
|
69680
69796
|
var init_chat_ownership_service = __esm({
|
|
69681
69797
|
"src/server/services/chat-ownership-service.ts"() {
|
|
69682
69798
|
"use strict";
|
|
@@ -69697,6 +69813,7 @@ var init_chat_ownership_service = __esm({
|
|
|
69697
69813
|
init_worktree_service();
|
|
69698
69814
|
log23 = createLogger("chat-ownership");
|
|
69699
69815
|
FORWARD_TIMEOUT_MS = 6e4;
|
|
69816
|
+
VIEW_SESSION_TIMEOUT_MS = 2e5;
|
|
69700
69817
|
RETRY_MS = 5e3;
|
|
69701
69818
|
HISTORY_MAX_ROWS = 1e3;
|
|
69702
69819
|
ChatOwnershipService = class {
|
|
@@ -70127,6 +70244,35 @@ var init_chat_ownership_service = __esm({
|
|
|
70127
70244
|
);
|
|
70128
70245
|
return readPayload(session, enc);
|
|
70129
70246
|
}
|
|
70247
|
+
/**
|
|
70248
|
+
* The user picked another session for the chat: the owner learns of it from the chat's info,
|
|
70249
|
+
* sent again. A chat with a turn running keeps its session, and the worker's own record wins.
|
|
70250
|
+
*/
|
|
70251
|
+
syncActiveSession(chatId) {
|
|
70252
|
+
const hostId2 = this.ownerOf(chatId);
|
|
70253
|
+
if (!hostId2 || taskService.getTask(chatId)?.status === "running") return;
|
|
70254
|
+
this.enqueue(chatId, hostId2, METHOD_CHAT_OWN, { chatId });
|
|
70255
|
+
}
|
|
70256
|
+
/**
|
|
70257
|
+
* Has the owner load a session Band has no log for, and waits until the replayed rows are
|
|
70258
|
+
* here. An offline owner, or an old worker without the method, does nothing, so the replay
|
|
70259
|
+
* arrives with the first prompt as before.
|
|
70260
|
+
*/
|
|
70261
|
+
async viewSession(chatId) {
|
|
70262
|
+
const sessionId = chatService.get(chatId)?.activeSessionId;
|
|
70263
|
+
const hostId2 = this.ownerOf(chatId);
|
|
70264
|
+
const session = hostId2 ? this.sessions.get(hostId2) : void 0;
|
|
70265
|
+
if (!session?.attached) return;
|
|
70266
|
+
try {
|
|
70267
|
+
await session.request(
|
|
70268
|
+
METHOD_CHAT_VIEW_SESSION,
|
|
70269
|
+
{ chatId, sessionId },
|
|
70270
|
+
{ timeoutMs: VIEW_SESSION_TIMEOUT_MS }
|
|
70271
|
+
);
|
|
70272
|
+
} catch (err) {
|
|
70273
|
+
log23.warn({ chatId, err }, "the owner could not load the session history");
|
|
70274
|
+
}
|
|
70275
|
+
}
|
|
70130
70276
|
/** The agent's own session list on the owner, or null when it can't list or is offline. */
|
|
70131
70277
|
async listSessions(chatId) {
|
|
70132
70278
|
const hostId2 = this.ownerOf(chatId);
|
|
@@ -70380,7 +70526,7 @@ function toTaskInfo(task) {
|
|
|
70380
70526
|
}
|
|
70381
70527
|
async function promptBlocks(task, firstTurn, sharedDir) {
|
|
70382
70528
|
const caps = agentSessionService.promptCapabilities(task.chatId);
|
|
70383
|
-
const blocks = [{ type: "text", text: task.prompt }];
|
|
70529
|
+
const blocks = task.prompt.trim() ? [{ type: "text", text: task.prompt }] : [];
|
|
70384
70530
|
for (const file3 of task.attachments) {
|
|
70385
70531
|
const name5 = file3.filename ?? file3.path.split("/").pop() ?? file3.path;
|
|
70386
70532
|
if (caps.image && file3.mediaType.startsWith("image/")) {
|
|
@@ -70825,10 +70971,15 @@ async function buildSnapshot(host, cwd, wipDir, worktreeId4, label2, seedIndex =
|
|
|
70825
70971
|
await host.fs.mkdir(wipDir, { recursive: true });
|
|
70826
70972
|
const baseSha = await git2(["rev-parse", "HEAD"]);
|
|
70827
70973
|
const branch = await git2(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
70828
|
-
const
|
|
70974
|
+
const slotKey = `${wipDir}\0${worktreeId4}`;
|
|
70975
|
+
const slot = takeIndexSlot(slotKey);
|
|
70976
|
+
const indexFile = posix3.join(
|
|
70977
|
+
wipDir,
|
|
70978
|
+
slot === 0 ? `${worktreeId4}.index` : `${worktreeId4}.${slot}.index`
|
|
70979
|
+
);
|
|
70829
70980
|
const env = { ...SNAPSHOT_BOT, GIT_INDEX_FILE: indexFile };
|
|
70830
|
-
await host.fs.rm(indexFile, { force: true });
|
|
70831
70981
|
try {
|
|
70982
|
+
await host.fs.rm(indexFile, { force: true });
|
|
70832
70983
|
let treeSha;
|
|
70833
70984
|
if (seedIndex) {
|
|
70834
70985
|
try {
|
|
@@ -70850,8 +71001,25 @@ async function buildSnapshot(host, cwd, wipDir, worktreeId4, label2, seedIndex =
|
|
|
70850
71001
|
return { baseSha, branch: branch === "HEAD" ? "" : branch, snapshotSha, treeSha };
|
|
70851
71002
|
} finally {
|
|
70852
71003
|
await host.fs.rm(indexFile, { force: true }).catch(() => void 0);
|
|
71004
|
+
releaseIndexSlot(slotKey, slot);
|
|
70853
71005
|
}
|
|
70854
71006
|
}
|
|
71007
|
+
function takeIndexSlot(key2) {
|
|
71008
|
+
let used = indexSlots.get(key2);
|
|
71009
|
+
if (!used) {
|
|
71010
|
+
used = /* @__PURE__ */ new Set();
|
|
71011
|
+
indexSlots.set(key2, used);
|
|
71012
|
+
}
|
|
71013
|
+
let slot = 0;
|
|
71014
|
+
while (used.has(slot)) slot++;
|
|
71015
|
+
used.add(slot);
|
|
71016
|
+
return slot;
|
|
71017
|
+
}
|
|
71018
|
+
function releaseIndexSlot(key2, slot) {
|
|
71019
|
+
const used = indexSlots.get(key2);
|
|
71020
|
+
used?.delete(slot);
|
|
71021
|
+
if (used?.size === 0) indexSlots.delete(key2);
|
|
71022
|
+
}
|
|
70855
71023
|
async function checkoutSnapshot(host, repoPath, worktreePath, snapshot, options = {}) {
|
|
70856
71024
|
const git2 = gitIn(host, repoPath);
|
|
70857
71025
|
await host.fs.mkdir(posix3.dirname(worktreePath), { recursive: true });
|
|
@@ -70897,7 +71065,7 @@ async function resetCheckoutToSnapshot(host, path2, snapshot) {
|
|
|
70897
71065
|
await git2(["reset", "-q"]);
|
|
70898
71066
|
}
|
|
70899
71067
|
}
|
|
70900
|
-
var WIP_DIR, SNAPSHOT_BOT;
|
|
71068
|
+
var WIP_DIR, SNAPSHOT_BOT, indexSlots;
|
|
70901
71069
|
var init_snapshot_git = __esm({
|
|
70902
71070
|
"../../packages/host-api/src/snapshot-git.ts"() {
|
|
70903
71071
|
"use strict";
|
|
@@ -70908,6 +71076,7 @@ var init_snapshot_git = __esm({
|
|
|
70908
71076
|
GIT_COMMITTER_NAME: "Band",
|
|
70909
71077
|
GIT_COMMITTER_EMAIL: "band@localhost"
|
|
70910
71078
|
};
|
|
71079
|
+
indexSlots = /* @__PURE__ */ new Map();
|
|
70911
71080
|
}
|
|
70912
71081
|
});
|
|
70913
71082
|
|
|
@@ -71273,7 +71442,7 @@ var init_remote_host = __esm({
|
|
|
71273
71442
|
stat: (path2, options) => this.rpc.call("fs.stat", { path: path2, followSymlinks: options?.followSymlinks }),
|
|
71274
71443
|
realpath: (path2) => this.rpc.call("fs.realpath", { path: path2 }),
|
|
71275
71444
|
readFile: (path2) => this.rpc.callBytes("fs.readFile", { path: path2 }),
|
|
71276
|
-
readStream: (path2) => this.streamOf("fs.readStream", { path: path2 }),
|
|
71445
|
+
readStream: (path2, range) => this.streamOf("fs.readStream", { path: path2, start: range?.start, end: range?.end }),
|
|
71277
71446
|
writeFile: (path2, data, options) => this.writeFile(path2, data, options),
|
|
71278
71447
|
glob: (pattern, cwd) => this.rpc.call("fs.glob", { pattern, cwd }),
|
|
71279
71448
|
mkdtemp: (prefix) => this.rpc.call("fs.mkdtemp", { prefix }),
|
|
@@ -77425,6 +77594,8 @@ __export(worktree_service_exports, {
|
|
|
77425
77594
|
worktreeSetGroupInput: () => worktreeSetGroupInput,
|
|
77426
77595
|
worktreeSetOriginInput: () => worktreeSetOriginInput,
|
|
77427
77596
|
worktreeSetPinnedInput: () => worktreeSetPinnedInput,
|
|
77597
|
+
worktreeSetTitleInput: () => worktreeSetTitleInput,
|
|
77598
|
+
worktreeSetUnreadInput: () => worktreeSetUnreadInput,
|
|
77428
77599
|
worktreeVia: () => worktreeVia
|
|
77429
77600
|
});
|
|
77430
77601
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
@@ -77470,7 +77641,7 @@ function expandHome(given, home, hostId2) {
|
|
|
77470
77641
|
}
|
|
77471
77642
|
return path2;
|
|
77472
77643
|
}
|
|
77473
|
-
var TEARDOWN_TIMEOUT_MS, log39, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeSetOriginInput, worktreeRefInput, worktreeSetGroupInput, worktreeReorderInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, InvalidOriginError, PlainRepoError, WorktreeService, worktreeService;
|
|
77644
|
+
var TEARDOWN_TIMEOUT_MS, log39, worktreeVia, worktreeCreateInput, worktreeRemoveInput, worktreeSetPinnedInput, worktreeSetOriginInput, worktreeRefInput, worktreeSetGroupInput, worktreeSetUnreadInput, worktreeSetTitleInput, worktreeReorderInput, worktreeGitInput, worktreeRunScriptInput, RepoNotFoundError, InvalidOriginError, PlainRepoError, WorktreeService, worktreeService;
|
|
77474
77645
|
var init_worktree_service = __esm({
|
|
77475
77646
|
"src/server/services/worktree-service.ts"() {
|
|
77476
77647
|
"use strict";
|
|
@@ -77481,6 +77652,7 @@ var init_worktree_service = __esm({
|
|
|
77481
77652
|
init_path();
|
|
77482
77653
|
init_src();
|
|
77483
77654
|
init_branch_name();
|
|
77655
|
+
init_worktree_display();
|
|
77484
77656
|
init_worktree_id();
|
|
77485
77657
|
init_zod();
|
|
77486
77658
|
init_errors6();
|
|
@@ -77598,6 +77770,17 @@ var init_worktree_service = __esm({
|
|
|
77598
77770
|
// Also moves every worktree started from this one, on any repo and host, and theirs in turn.
|
|
77599
77771
|
withChildren: external_exports2.boolean().optional()
|
|
77600
77772
|
});
|
|
77773
|
+
worktreeSetUnreadInput = external_exports2.object({
|
|
77774
|
+
worktreeId: external_exports2.string().min(1),
|
|
77775
|
+
unread: external_exports2.boolean(),
|
|
77776
|
+
// Tells two same-named worktrees on different workers apart.
|
|
77777
|
+
hostId: external_exports2.string().optional()
|
|
77778
|
+
});
|
|
77779
|
+
worktreeSetTitleInput = external_exports2.object({
|
|
77780
|
+
worktreeId: external_exports2.string().min(1),
|
|
77781
|
+
// Blank or null clears the title, so the card shows the name again.
|
|
77782
|
+
title: external_exports2.string().max(WORKTREE_TITLE_MAX).nullable()
|
|
77783
|
+
});
|
|
77601
77784
|
worktreeReorderInput = external_exports2.object({
|
|
77602
77785
|
// The worktrees of one list in their new order.
|
|
77603
77786
|
order: external_exports2.array(worktreeRefInput).min(1).max(500),
|
|
@@ -77838,6 +78021,14 @@ var init_worktree_service = __esm({
|
|
|
77838
78021
|
(wt) => sameName(wt) && (wt.hostId ?? hostRegistry.local.id) === hostId2
|
|
77839
78022
|
);
|
|
77840
78023
|
if (existing) return { ok: true, path: existing.path, hostId: hostId2 };
|
|
78024
|
+
const caseClash = repo.worktrees.find(
|
|
78025
|
+
(wt) => (wt.hostId ?? hostRegistry.local.id) === hostId2 && [wt.name, wt.branch].some((n) => n.toLowerCase() === input2.branch.toLowerCase())
|
|
78026
|
+
);
|
|
78027
|
+
if (caseClash) {
|
|
78028
|
+
throw new Error(
|
|
78029
|
+
`Branch "${input2.branch}" differs only in case from the existing worktree "${caseClash.branch}". Use that name or pick another.`
|
|
78030
|
+
);
|
|
78031
|
+
}
|
|
77841
78032
|
assertHostAllowed(repo.name, hostId2);
|
|
77842
78033
|
const elsewhere = listedWorktrees(repo).find(
|
|
77843
78034
|
(wt) => sameName(wt) && !wt.supersededBy && (wt.hostId ?? hostRegistry.local.id) !== hostId2
|
|
@@ -78361,6 +78552,20 @@ var init_worktree_service = __esm({
|
|
|
78361
78552
|
emit({ kind: "worktree-origin-changed", worktreeId: worktreeId4 });
|
|
78362
78553
|
return { ok: true };
|
|
78363
78554
|
}
|
|
78555
|
+
/** Sets the title shown for a worktree, or clears it (blank or null). */
|
|
78556
|
+
setTitle(input2) {
|
|
78557
|
+
const title = input2.title?.trim() || null;
|
|
78558
|
+
const state2 = loadState();
|
|
78559
|
+
for (const repo of state2.repos) {
|
|
78560
|
+
for (const wt of repo.worktrees) {
|
|
78561
|
+
if (toWorktreeId(repo.name, wt.name, wt.hostId) !== input2.worktreeId) continue;
|
|
78562
|
+
this.repoQueries.setTitle(repo.name, wt.name, title, wt.hostId ?? "local");
|
|
78563
|
+
emit({ kind: "worktree-origin-changed", worktreeId: input2.worktreeId });
|
|
78564
|
+
return { ok: true, title };
|
|
78565
|
+
}
|
|
78566
|
+
}
|
|
78567
|
+
throw new WorktreeNotFoundError(input2.worktreeId);
|
|
78568
|
+
}
|
|
78364
78569
|
/**
|
|
78365
78570
|
* Puts a worktree in a group, or in none with `null`. With `withChildren` every worktree
|
|
78366
78571
|
* started from it, and theirs in turn, moves too.
|
|
@@ -78395,6 +78600,42 @@ var init_worktree_service = __esm({
|
|
|
78395
78600
|
emit({ kind: "worktree-origin-changed", worktreeId: input2.worktreeId });
|
|
78396
78601
|
return { ok: true, moved };
|
|
78397
78602
|
}
|
|
78603
|
+
/**
|
|
78604
|
+
* Marks a worktree unread or read. Reading also clears a `done` or `failed` attention dot, the
|
|
78605
|
+
* way `statuses.clearNeedsAttention` does.
|
|
78606
|
+
*/
|
|
78607
|
+
setUnread(input2) {
|
|
78608
|
+
const row = this.findRowById(input2.worktreeId, input2.hostId);
|
|
78609
|
+
if (!row) throw new WorktreeNotFoundError(input2.worktreeId);
|
|
78610
|
+
this.repoQueries.setUnread(row.repoName, row.name, input2.unread, row.hostId);
|
|
78611
|
+
if (!input2.unread) {
|
|
78612
|
+
const status = acknowledgeWorktreeAttention(input2.worktreeId);
|
|
78613
|
+
if (status) emit({ kind: "update", status });
|
|
78614
|
+
}
|
|
78615
|
+
emit({ kind: "worktree-origin-changed", worktreeId: input2.worktreeId });
|
|
78616
|
+
return { ok: true };
|
|
78617
|
+
}
|
|
78618
|
+
/**
|
|
78619
|
+
* Clears the unread flag when the user opens the worktree. Does nothing, and emits nothing,
|
|
78620
|
+
* for a worktree that is not marked unread or is gone.
|
|
78621
|
+
*/
|
|
78622
|
+
clearUnreadOnOpen(worktreeId4) {
|
|
78623
|
+
const row = this.findRowById(worktreeId4);
|
|
78624
|
+
if (!row?.unread) return;
|
|
78625
|
+
this.repoQueries.setUnread(row.repoName, row.name, false, row.hostId);
|
|
78626
|
+
emit({ kind: "worktree-origin-changed", worktreeId: worktreeId4 });
|
|
78627
|
+
}
|
|
78628
|
+
findRowById(worktreeId4, hostId2) {
|
|
78629
|
+
for (const repo of loadState().repos) {
|
|
78630
|
+
for (const wt of repo.worktrees) {
|
|
78631
|
+
if (toWorktreeId(repo.name, wt.name, wt.hostId) !== worktreeId4) continue;
|
|
78632
|
+
const rowHost = wt.hostId ?? "local";
|
|
78633
|
+
if (hostId2 !== void 0 && hostId2 !== rowHost) continue;
|
|
78634
|
+
return { repoName: repo.name, name: wt.name, hostId: rowHost, unread: wt.unread === true };
|
|
78635
|
+
}
|
|
78636
|
+
}
|
|
78637
|
+
return void 0;
|
|
78638
|
+
}
|
|
78398
78639
|
/**
|
|
78399
78640
|
* Stores the order of one list of worktrees, and optionally the new parent of one of them. The
|
|
78400
78641
|
* parent change goes first, so a refused parent leaves the order untouched.
|
|
@@ -83414,7 +83655,10 @@ var init_agent_session_service = __esm({
|
|
|
83414
83655
|
async ensureSession(chatId, purpose, options = {}) {
|
|
83415
83656
|
const chat = chatService.get(chatId);
|
|
83416
83657
|
if (!chat) throw new ChatNotFoundError(chatId);
|
|
83417
|
-
if (chat.ownerHostId)
|
|
83658
|
+
if (chat.ownerHostId) {
|
|
83659
|
+
if (purpose === "view") await chatOwnershipService.viewSession(chatId);
|
|
83660
|
+
return null;
|
|
83661
|
+
}
|
|
83418
83662
|
if (purpose === "prompt") await ephemeralLifecycleService.ensureAwake(chat.worktreeId);
|
|
83419
83663
|
const worktree = worktreeService.resolve(chat.worktreeId);
|
|
83420
83664
|
if (!worktree) throw new Error(`Worktree not found: ${chat.worktreeId}`);
|
|
@@ -94669,6 +94913,8 @@ var RepoService = class {
|
|
|
94669
94913
|
head: wt.head,
|
|
94670
94914
|
pinned: tracked2?.pinned ?? false,
|
|
94671
94915
|
...tracked2?.groupId ? { groupId: tracked2.groupId } : {},
|
|
94916
|
+
...tracked2?.unread ? { unread: true } : {},
|
|
94917
|
+
...tracked2?.title ? { title: tracked2.title } : {},
|
|
94672
94918
|
...tracked2?.sortIndex === void 0 ? {} : { sortIndex: tracked2.sortIndex },
|
|
94673
94919
|
...tracked2?.pinSortIndex === void 0 ? {} : { pinSortIndex: tracked2.pinSortIndex }
|
|
94674
94920
|
};
|
|
@@ -94710,6 +94956,8 @@ var RepoService = class {
|
|
|
94710
94956
|
lastCheckpointAt: lastCheckpoints.get(worktreeId4) ?? null,
|
|
94711
94957
|
agent: status?.agent ?? null,
|
|
94712
94958
|
groupId: wt.groupId ?? null,
|
|
94959
|
+
unread: wt.unread ?? false,
|
|
94960
|
+
title: wt.title ?? null,
|
|
94713
94961
|
origin: links.origins.get(worktreeId4) ?? null,
|
|
94714
94962
|
children: links.children.get(worktreeId4) ?? []
|
|
94715
94963
|
};
|
|
@@ -95464,6 +95712,11 @@ var CheckpointService = class {
|
|
|
95464
95712
|
log55.warn({ worktreeId: worktreeId4, trigger, err: errorText3(err) }, "could not take a checkpoint");
|
|
95465
95713
|
}
|
|
95466
95714
|
}
|
|
95715
|
+
/** Refuses when a restore or move holds any of the worktrees. */
|
|
95716
|
+
assertNotBusy(...worktreeIds) {
|
|
95717
|
+
const held = worktreeIds.find((id) => this.busy.has(id));
|
|
95718
|
+
if (held) throw new CheckpointError("conflict", `${held} is being restored or moved.`);
|
|
95719
|
+
}
|
|
95467
95720
|
/** Why no automatic checkpoint is taken for the worktree, or null when one may be. */
|
|
95468
95721
|
ineligible(worktreeId4) {
|
|
95469
95722
|
const found = worktreeService.resolve(worktreeId4);
|
|
@@ -95533,6 +95786,7 @@ var CheckpointService = class {
|
|
|
95533
95786
|
"Checkpoints are for worktrees on a worker, not on the hub's own machine."
|
|
95534
95787
|
);
|
|
95535
95788
|
}
|
|
95789
|
+
this.assertNotBusy(worktreeId4);
|
|
95536
95790
|
if (!opts.ignoreSetting && !checkpointsEnabledFor(found.repo.checkpointsEnabled, found.repo.allowedHostIds)) {
|
|
95537
95791
|
throw new CheckpointError("disabled", `Checkpoints are off for ${found.repo.name}.`);
|
|
95538
95792
|
}
|
|
@@ -95965,9 +96219,8 @@ ${diff}`).digest("hex");
|
|
|
95965
96219
|
`${worktreeId4} was already restored as ${worktree.supersededBy}.`
|
|
95966
96220
|
);
|
|
95967
96221
|
}
|
|
95968
|
-
|
|
95969
|
-
|
|
95970
|
-
}
|
|
96222
|
+
const targetId = toWorktreeId(repo.name, worktree.name, toHostId);
|
|
96223
|
+
this.assertNotBusy(worktreeId4, targetId);
|
|
95971
96224
|
const row = this.pick(worktreeId4, input2.checkpointId);
|
|
95972
96225
|
const sourceUp = this.sessions.get(fromHostId)?.attached === true;
|
|
95973
96226
|
const leaseLeft = sourceUp ? 0 : workerLeaseService.remainingMs(fromHostId);
|
|
@@ -95998,7 +96251,6 @@ ${diff}`).digest("hex");
|
|
|
95998
96251
|
(w2) => w2.name === worktree.name && (w2.hostId ?? LOCAL_HOST_ID8) === toHostId
|
|
95999
96252
|
);
|
|
96000
96253
|
if (onTarget && !onTarget.supersededBy && isRoot) {
|
|
96001
|
-
const targetId = toWorktreeId(repo.name, worktree.name, toHostId);
|
|
96002
96254
|
const busy = chatService.list(targetId).some(
|
|
96003
96255
|
(chat) => hasRunningTask(chat.id) || agentSessionService.isActive(chat.id) || hasQueuedMessages(chat.id)
|
|
96004
96256
|
) || (await terminalService.list(targetId).catch(() => [])).length > 0;
|
|
@@ -96015,6 +96267,7 @@ ${diff}`).digest("hex");
|
|
|
96015
96267
|
`Worker ${toHostId} already has a worktree ${worktree.name} of ${repo.name}. Remove it first.`
|
|
96016
96268
|
);
|
|
96017
96269
|
}
|
|
96270
|
+
this.assertNotBusy(worktreeId4, targetId);
|
|
96018
96271
|
const previousAllowed = repo.allowedHostIds ? [...repo.allowedHostIds] : null;
|
|
96019
96272
|
let widened = false;
|
|
96020
96273
|
let restored = false;
|
|
@@ -96025,7 +96278,9 @@ ${diff}`).digest("hex");
|
|
|
96025
96278
|
widened = true;
|
|
96026
96279
|
}
|
|
96027
96280
|
this.busy.add(worktreeId4);
|
|
96281
|
+
this.busy.add(targetId);
|
|
96028
96282
|
try {
|
|
96283
|
+
await this.inflight.get(targetId)?.catch(() => void 0);
|
|
96029
96284
|
const host = hostRegistry.hostById(toHostId);
|
|
96030
96285
|
const rpc = new RemoteRpc(toHostId, () => target);
|
|
96031
96286
|
const [root] = (await host.info()).roots;
|
|
@@ -96042,7 +96297,7 @@ ${diff}`).digest("hex");
|
|
|
96042
96297
|
input2.hostRepoPath
|
|
96043
96298
|
);
|
|
96044
96299
|
const git2 = gitIn(host, repoPath);
|
|
96045
|
-
const newId =
|
|
96300
|
+
const newId = targetId;
|
|
96046
96301
|
const worktreePath = isRoot ? repoPath : posix8.join(root, ".band-worktrees", repo.name, worktree.name);
|
|
96047
96302
|
const backups = [];
|
|
96048
96303
|
const backupStamp = Date.now();
|
|
@@ -96178,6 +96433,10 @@ ${diff}`).digest("hex");
|
|
|
96178
96433
|
if (worktree.groupId) {
|
|
96179
96434
|
this.repos.setGroup(repo.name, worktree.name, worktree.groupId, toHostId);
|
|
96180
96435
|
}
|
|
96436
|
+
if (worktree.unread) this.repos.setUnread(repo.name, worktree.name, true, toHostId);
|
|
96437
|
+
if (worktree.title) {
|
|
96438
|
+
this.repos.setTitle(repo.name, worktree.name, worktree.title, toHostId);
|
|
96439
|
+
}
|
|
96181
96440
|
this.repos.setSupersededBy(repo.name, worktree.name, fromHostId, newId);
|
|
96182
96441
|
await Promise.all([
|
|
96183
96442
|
workerLeaseService.pushBlockedPaths(fromHostId),
|
|
@@ -96214,6 +96473,7 @@ ${diff}`).digest("hex");
|
|
|
96214
96473
|
}
|
|
96215
96474
|
}
|
|
96216
96475
|
this.busy.delete(worktreeId4);
|
|
96476
|
+
this.busy.delete(targetId);
|
|
96217
96477
|
}
|
|
96218
96478
|
}
|
|
96219
96479
|
async restoreSessions(host, rpc, row, wipDir, fromCwd, toCwd) {
|
|
@@ -97375,8 +97635,9 @@ async function handleChatSubmit(req, res, chatId) {
|
|
|
97375
97635
|
}
|
|
97376
97636
|
const { text: text4, sessionId, mode, model, codingAgentId, files } = body;
|
|
97377
97637
|
let { worktreeId: worktreeId4 } = body;
|
|
97378
|
-
|
|
97379
|
-
|
|
97638
|
+
const hasFiles = Array.isArray(files) && files.length > 0;
|
|
97639
|
+
if (!worktreeId4 || !text4?.trim() && !hasFiles) {
|
|
97640
|
+
sendJson2(res, 400, { error: "worktreeId and text or files are required" });
|
|
97380
97641
|
return;
|
|
97381
97642
|
}
|
|
97382
97643
|
if (sessionId !== void 0 && !SESSION_ID_PATTERN2.test(sessionId)) {
|
|
@@ -97420,7 +97681,7 @@ async function handleChatSubmit(req, res, chatId) {
|
|
|
97420
97681
|
const result = taskService.submitOrQueueTask({
|
|
97421
97682
|
worktreeId: worktreeId4,
|
|
97422
97683
|
chatId,
|
|
97423
|
-
prompt: text4,
|
|
97684
|
+
prompt: text4 ?? "",
|
|
97424
97685
|
sessionId,
|
|
97425
97686
|
attachments,
|
|
97426
97687
|
mode,
|
|
@@ -109943,6 +110204,7 @@ init_zod();
|
|
|
109943
110204
|
init_errors6();
|
|
109944
110205
|
init_agent_login_service();
|
|
109945
110206
|
init_agent_session_service();
|
|
110207
|
+
init_chat_ownership_service();
|
|
109946
110208
|
init_chat_service();
|
|
109947
110209
|
init_settings_service();
|
|
109948
110210
|
init_task_service();
|
|
@@ -110114,6 +110376,7 @@ var chatsRouter = t.router({
|
|
|
110114
110376
|
summary,
|
|
110115
110377
|
lastModified
|
|
110116
110378
|
});
|
|
110379
|
+
chatOwnershipService.syncActiveSession(input2.chatId);
|
|
110117
110380
|
return { ok: true };
|
|
110118
110381
|
}),
|
|
110119
110382
|
send: publicProcedure.input(
|
|
@@ -110275,6 +110538,7 @@ init_zod();
|
|
|
110275
110538
|
|
|
110276
110539
|
// src/server/services/cli-service.ts
|
|
110277
110540
|
init_cli_binary();
|
|
110541
|
+
init_local_host_enabled();
|
|
110278
110542
|
import { execFile as execFile15 } from "node:child_process";
|
|
110279
110543
|
import {
|
|
110280
110544
|
accessSync as accessSync3,
|
|
@@ -110295,10 +110559,14 @@ function windowsBinDir() {
|
|
|
110295
110559
|
return join49(localAppData, "band", "bin");
|
|
110296
110560
|
}
|
|
110297
110561
|
var SYMLINK_PATH = platform2() === "win32" ? join49(windowsBinDir(), "band.cmd") : "/usr/local/bin/band";
|
|
110562
|
+
function symlinkPath() {
|
|
110563
|
+
if (platform2() === "win32") return SYMLINK_PATH;
|
|
110564
|
+
return process.env.BAND_CLI_SYMLINK_PATH?.trim() || SYMLINK_PATH;
|
|
110565
|
+
}
|
|
110298
110566
|
function checkCliWindows() {
|
|
110299
110567
|
let contents;
|
|
110300
110568
|
try {
|
|
110301
|
-
contents = readFileSync11(
|
|
110569
|
+
contents = readFileSync11(symlinkPath(), "utf-8");
|
|
110302
110570
|
} catch (err) {
|
|
110303
110571
|
const code = err.code;
|
|
110304
110572
|
if (code === "EACCES") return "NotWritable";
|
|
@@ -110310,18 +110578,22 @@ function checkCliWindows() {
|
|
|
110310
110578
|
if (basename3(target).toLowerCase() !== "band.exe") return "ConflictingBinary";
|
|
110311
110579
|
return existsSync16(target) ? "Installed" : "NotInstalled";
|
|
110312
110580
|
}
|
|
110581
|
+
function isCliCheckApplicable(admin) {
|
|
110582
|
+
return isLocalHostEnabled() && admin;
|
|
110583
|
+
}
|
|
110313
110584
|
async function checkCli() {
|
|
110314
110585
|
if (platform2() === "win32") {
|
|
110315
110586
|
return checkCliWindows();
|
|
110316
110587
|
}
|
|
110588
|
+
const linkPath = symlinkPath();
|
|
110317
110589
|
try {
|
|
110318
|
-
const stat7 = lstatSync4(
|
|
110590
|
+
const stat7 = lstatSync4(linkPath);
|
|
110319
110591
|
if (!stat7.isSymbolicLink()) {
|
|
110320
110592
|
return "ConflictingBinary";
|
|
110321
110593
|
}
|
|
110322
110594
|
let target;
|
|
110323
110595
|
try {
|
|
110324
|
-
target = realpathSync5(
|
|
110596
|
+
target = realpathSync5(linkPath);
|
|
110325
110597
|
} catch {
|
|
110326
110598
|
return "NotInstalled";
|
|
110327
110599
|
}
|
|
@@ -110330,6 +110602,13 @@ async function checkCli() {
|
|
|
110330
110602
|
if (isCargoBuild || isElectronSidecar) {
|
|
110331
110603
|
return "Installed";
|
|
110332
110604
|
}
|
|
110605
|
+
const resolved = findCliBinary();
|
|
110606
|
+
if (resolved) {
|
|
110607
|
+
try {
|
|
110608
|
+
if (realpathSync5(resolved) === target) return "Installed";
|
|
110609
|
+
} catch {
|
|
110610
|
+
}
|
|
110611
|
+
}
|
|
110333
110612
|
const isStaleAppLink = target.toLowerCase().includes("/band.app/contents/macos/");
|
|
110334
110613
|
if (isStaleAppLink) {
|
|
110335
110614
|
return "NotInstalled";
|
|
@@ -110339,7 +110618,7 @@ async function checkCli() {
|
|
|
110339
110618
|
const code = err.code;
|
|
110340
110619
|
if (code === "ENOENT") {
|
|
110341
110620
|
try {
|
|
110342
|
-
lstatSync4(
|
|
110621
|
+
lstatSync4(dirname13(linkPath));
|
|
110343
110622
|
return "NotInstalled";
|
|
110344
110623
|
} catch {
|
|
110345
110624
|
return "DirNotFound";
|
|
@@ -110364,7 +110643,7 @@ function noBinaryError(env = process.env) {
|
|
|
110364
110643
|
return new Error("Bundled CLI binary missing - try reinstalling Band");
|
|
110365
110644
|
}
|
|
110366
110645
|
return new Error(
|
|
110367
|
-
|
|
110646
|
+
`Could not find band CLI binary. Install ${cliPackageName()} or set BAND_CLI_PATH. In a source checkout, build it with: cargo build --release -p band-cli`
|
|
110368
110647
|
);
|
|
110369
110648
|
}
|
|
110370
110649
|
function runReg(args) {
|
|
@@ -110416,39 +110695,43 @@ async function installCli(_opts = {}) {
|
|
|
110416
110695
|
await installCliWindows(binaryPath);
|
|
110417
110696
|
return;
|
|
110418
110697
|
}
|
|
110419
|
-
const
|
|
110698
|
+
const linkPath = symlinkPath();
|
|
110699
|
+
const dir = dirname13(linkPath);
|
|
110420
110700
|
if (isDirWritable(dir)) {
|
|
110421
110701
|
try {
|
|
110422
|
-
lstatSync4(
|
|
110423
|
-
unlinkSync6(
|
|
110702
|
+
lstatSync4(linkPath);
|
|
110703
|
+
unlinkSync6(linkPath);
|
|
110424
110704
|
} catch (err) {
|
|
110425
110705
|
const code = err.code;
|
|
110426
110706
|
if (code !== "ENOENT") throw err;
|
|
110427
110707
|
}
|
|
110428
|
-
symlinkSync(binaryPath,
|
|
110708
|
+
symlinkSync(binaryPath, linkPath);
|
|
110429
110709
|
return;
|
|
110430
110710
|
}
|
|
110431
110711
|
if (platform2() === "darwin") {
|
|
110432
110712
|
throw new Error("elevation-required");
|
|
110433
110713
|
}
|
|
110434
|
-
throw new Error(`
|
|
110714
|
+
throw new Error(`On the hub's server, run: sudo ln -sf "${binaryPath}" "${linkPath}"`);
|
|
110435
110715
|
}
|
|
110436
110716
|
function resolveCliPaths() {
|
|
110437
110717
|
const binaryPath = findCliBinary();
|
|
110438
110718
|
if (!binaryPath) return null;
|
|
110439
|
-
return { binaryPath, symlinkPath:
|
|
110719
|
+
return { binaryPath, symlinkPath: symlinkPath() };
|
|
110440
110720
|
}
|
|
110441
110721
|
|
|
110442
110722
|
// src/server/api/cli/router.ts
|
|
110443
110723
|
var cliRouter = t.router({
|
|
110444
|
-
|
|
110445
|
-
|
|
110446
|
-
|
|
110724
|
+
// `applicable` is false when nobody can act on the answer: the hub runs no worktrees of its
|
|
110725
|
+
// own (workers carry their own CLI), or the caller is not an admin.
|
|
110726
|
+
check: publicProcedure.query(async ({ ctx }) => {
|
|
110727
|
+
if (!isCliCheckApplicable(ctx.admin))
|
|
110728
|
+
return { status: "Installed", applicable: false };
|
|
110729
|
+
return { status: await checkCli(), applicable: true };
|
|
110447
110730
|
}),
|
|
110448
|
-
resolve:
|
|
110731
|
+
resolve: adminProcedure.query(() => {
|
|
110449
110732
|
return resolveCliPaths();
|
|
110450
110733
|
}),
|
|
110451
|
-
install:
|
|
110734
|
+
install: adminProcedure.input(external_exports2.object({ allowPrompt: external_exports2.boolean().optional() }).optional()).mutation(async ({ input: input2 }) => {
|
|
110452
110735
|
try {
|
|
110453
110736
|
await installCli({ allowPrompt: input2?.allowPrompt });
|
|
110454
110737
|
return { ok: true };
|
|
@@ -111754,7 +112037,7 @@ init_registry();
|
|
|
111754
112037
|
// package.json
|
|
111755
112038
|
var package_default = {
|
|
111756
112039
|
name: "@band-app/server",
|
|
111757
|
-
version: "0.54.
|
|
112040
|
+
version: "0.54.4",
|
|
111758
112041
|
type: "module",
|
|
111759
112042
|
types: "src/index.ts",
|
|
111760
112043
|
exports: {
|
|
@@ -112089,6 +112372,7 @@ var RELAY_PROCEDURES = /* @__PURE__ */ new Set([
|
|
|
112089
112372
|
"repos.remove",
|
|
112090
112373
|
"groups.list",
|
|
112091
112374
|
"worktrees.setGroup",
|
|
112375
|
+
"worktrees.setTitle",
|
|
112092
112376
|
"hosts.list",
|
|
112093
112377
|
"hosts.forAgents",
|
|
112094
112378
|
"workers.list",
|
|
@@ -118475,6 +118759,7 @@ init_zod();
|
|
|
118475
118759
|
init_branch_status_poller();
|
|
118476
118760
|
init_state();
|
|
118477
118761
|
init_watcher_service();
|
|
118762
|
+
init_worktree_service();
|
|
118478
118763
|
var statusesRouter = t.router({
|
|
118479
118764
|
get: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).query(({ input: input2 }) => {
|
|
118480
118765
|
return getWorktreeStatus(input2.worktreeId);
|
|
@@ -118501,7 +118786,15 @@ var statusesRouter = t.router({
|
|
|
118501
118786
|
refreshBranchStatus: publicProcedure.input(external_exports2.object({ worktreeId: external_exports2.string() })).mutation(async ({ input: input2 }) => ({
|
|
118502
118787
|
refreshed: await branchStatusPoller.refreshWorktree(input2.worktreeId)
|
|
118503
118788
|
})),
|
|
118504
|
-
clearNeedsAttention: publicProcedure.input(
|
|
118789
|
+
clearNeedsAttention: publicProcedure.input(
|
|
118790
|
+
external_exports2.object({
|
|
118791
|
+
worktreeId: external_exports2.string(),
|
|
118792
|
+
// Opening a worktree also drops a manual unread mark. The UI leaves it out for a
|
|
118793
|
+
// worktree it just marked while that worktree was open.
|
|
118794
|
+
clearUnread: external_exports2.boolean().optional()
|
|
118795
|
+
})
|
|
118796
|
+
).mutation(({ input: input2 }) => {
|
|
118797
|
+
if (input2.clearUnread) worktreeService.clearUnreadOnOpen(input2.worktreeId);
|
|
118505
118798
|
const status = acknowledgeWorktreeAttention(input2.worktreeId);
|
|
118506
118799
|
if (status) emit({ kind: "update", status });
|
|
118507
118800
|
return { ok: true };
|
|
@@ -122250,6 +122543,23 @@ var worktreesRouter = t.router({
|
|
|
122250
122543
|
throwAsTrpcError3(err);
|
|
122251
122544
|
}
|
|
122252
122545
|
}),
|
|
122546
|
+
setUnread: publicProcedure.input(worktreeSetUnreadInput).mutation(({ input: input2 }) => {
|
|
122547
|
+
try {
|
|
122548
|
+
return worktreeService.setUnread(input2);
|
|
122549
|
+
} catch (err) {
|
|
122550
|
+
throwAsTrpcError3(err);
|
|
122551
|
+
}
|
|
122552
|
+
}),
|
|
122553
|
+
setTitle: publicProcedure.input(worktreeSetTitleInput).mutation(({ input: input2 }) => {
|
|
122554
|
+
try {
|
|
122555
|
+
return worktreeService.setTitle(input2);
|
|
122556
|
+
} catch (err) {
|
|
122557
|
+
if (err instanceof WorktreeNotFoundError) {
|
|
122558
|
+
throw new TRPCError({ code: "NOT_FOUND", message: err.message, cause: err });
|
|
122559
|
+
}
|
|
122560
|
+
throwAsTrpcError3(err);
|
|
122561
|
+
}
|
|
122562
|
+
}),
|
|
122253
122563
|
gitPull: publicProcedure.input(worktreeGitInput).mutation(async ({ input: input2 }) => {
|
|
122254
122564
|
try {
|
|
122255
122565
|
return await worktreeService.gitPull(input2);
|
|
@@ -122364,6 +122674,7 @@ function discoverProcedures() {
|
|
|
122364
122674
|
if (path2 === "repos.addByUrl") continue;
|
|
122365
122675
|
if (path2 === "repos.inspectFolder" || path2 === "repos.resolveRemote") continue;
|
|
122366
122676
|
if (path2 === "worktrees.setOrigin") continue;
|
|
122677
|
+
if (path2 === "worktrees.setUnread") continue;
|
|
122367
122678
|
if (path2.startsWith("groups.") && path2 !== "groups.list") continue;
|
|
122368
122679
|
if (path2 === "worktrees.reorder") continue;
|
|
122369
122680
|
if (path2 === "environment.build") continue;
|
|
@@ -124642,6 +124953,37 @@ async function handleLspConnection(ws, req) {
|
|
|
124642
124953
|
// start-server.ts
|
|
124643
124954
|
init_webhook();
|
|
124644
124955
|
|
|
124956
|
+
// src/server/services/_utils/byte-range.ts
|
|
124957
|
+
function parseByteRange(header, size) {
|
|
124958
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(header?.trim() ?? "");
|
|
124959
|
+
if (!match || match[1] === "" && match[2] === "") return null;
|
|
124960
|
+
if (match[1] !== "" && match[2] !== "" && Number(match[2]) < Number(match[1])) return null;
|
|
124961
|
+
let start;
|
|
124962
|
+
let end;
|
|
124963
|
+
if (match[1] === "") {
|
|
124964
|
+
const length = Number(match[2]);
|
|
124965
|
+
if (length === 0) return "unsatisfiable";
|
|
124966
|
+
start = Math.max(0, size - length);
|
|
124967
|
+
end = size - 1;
|
|
124968
|
+
} else {
|
|
124969
|
+
start = Number(match[1]);
|
|
124970
|
+
end = match[2] === "" ? size - 1 : Math.min(Number(match[2]), size - 1);
|
|
124971
|
+
}
|
|
124972
|
+
if (start >= size || start > end) return "unsatisfiable";
|
|
124973
|
+
return { start, end };
|
|
124974
|
+
}
|
|
124975
|
+
|
|
124976
|
+
// src/server/services/_utils/content-disposition.ts
|
|
124977
|
+
function attachmentDisposition(filename) {
|
|
124978
|
+
const clean = filename.replace(/[/\\]/g, "_") || "download";
|
|
124979
|
+
const ascii = clean.replace(/[^\x20-\x7e]/g, "_").replace(/["%;]/g, "_");
|
|
124980
|
+
const encoded = encodeURIComponent(clean).replace(
|
|
124981
|
+
/['()*]/g,
|
|
124982
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
|
|
124983
|
+
);
|
|
124984
|
+
return `attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`;
|
|
124985
|
+
}
|
|
124986
|
+
|
|
124645
124987
|
// src/server/services/_utils/hosted-file.ts
|
|
124646
124988
|
init_registry();
|
|
124647
124989
|
import { basename as basename6, dirname as dirname17, join as join57 } from "node:path";
|
|
@@ -133203,7 +133545,7 @@ async function serveHostedFile(res, kind, worktreeId4, rawName, chatId) {
|
|
|
133203
133545
|
);
|
|
133204
133546
|
return true;
|
|
133205
133547
|
}
|
|
133206
|
-
async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}, visualEditNonce) {
|
|
133548
|
+
async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}, visualEditNonce, download = false, rangeHeader) {
|
|
133207
133549
|
const worktree = worktreeService.resolve(worktreeId4);
|
|
133208
133550
|
if (!worktree) {
|
|
133209
133551
|
res.writeHead(404);
|
|
@@ -133226,6 +133568,11 @@ async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}, v
|
|
|
133226
133568
|
return;
|
|
133227
133569
|
}
|
|
133228
133570
|
const fileStat = await fs3.stat(target, { followSymlinks: true });
|
|
133571
|
+
if (download && fileStat.kind !== "file") {
|
|
133572
|
+
res.writeHead(400);
|
|
133573
|
+
res.end("Bad request");
|
|
133574
|
+
return;
|
|
133575
|
+
}
|
|
133229
133576
|
const contentType = mimeTypeFromFilename(basename7(target));
|
|
133230
133577
|
const isHtml = contentType.startsWith("text/html");
|
|
133231
133578
|
const editHtml = isHtml && visualEditNonce !== void 0 && fileStat.kind === "file";
|
|
@@ -133241,28 +133588,37 @@ async function serveWorktreeFile(res, worktreeId4, rawPath, extraHeaders = {}, v
|
|
|
133241
133588
|
body = Buffer.from(annotateHtmlForEdit(source, visualEditNonce), "utf8");
|
|
133242
133589
|
}
|
|
133243
133590
|
}
|
|
133244
|
-
|
|
133591
|
+
const range = !body && !trailer && fileStat.kind === "file" ? parseByteRange(rangeHeader, fileStat.size) : null;
|
|
133592
|
+
if (range === "unsatisfiable") {
|
|
133593
|
+
res.writeHead(416, { "Content-Range": `bytes */${fileStat.size}` });
|
|
133594
|
+
res.end();
|
|
133595
|
+
return;
|
|
133596
|
+
}
|
|
133597
|
+
const partial3 = range !== null;
|
|
133598
|
+
res.writeHead(partial3 ? 206 : 200, {
|
|
133245
133599
|
"Content-Type": contentType,
|
|
133246
|
-
"Content-Length": (body?.byteLength ?? fileStat.size + (trailer?.byteLength ?? 0)).toString(),
|
|
133600
|
+
"Content-Length": (partial3 ? range.end - range.start + 1 : body?.byteLength ?? fileStat.size + (trailer?.byteLength ?? 0)).toString(),
|
|
133601
|
+
...partial3 ? { "Content-Range": `bytes ${range.start}-${range.end}/${fileStat.size}` } : {},
|
|
133602
|
+
"Accept-Ranges": "bytes",
|
|
133247
133603
|
"Cache-Control": "private, no-cache",
|
|
133248
|
-
// HTML from a repo must not run with the hub's origin
|
|
133249
|
-
|
|
133250
|
-
|
|
133251
|
-
|
|
133252
|
-
} : {},
|
|
133604
|
+
// HTML and SVG from a repo must not run with the hub's origin, and no
|
|
133605
|
+
// type may be sniffed into one.
|
|
133606
|
+
...hostedFileHeaders(contentType),
|
|
133607
|
+
...download ? { "Content-Disposition": attachmentDisposition(basename7(target)) } : {},
|
|
133253
133608
|
...extraHeaders
|
|
133254
133609
|
});
|
|
133255
133610
|
if (body) {
|
|
133256
133611
|
res.end(body);
|
|
133257
133612
|
return;
|
|
133258
133613
|
}
|
|
133259
|
-
const stream = Readable2.from(fs3.readStream(target), {
|
|
133614
|
+
const stream = Readable2.from(fs3.readStream(target, range ?? void 0), {
|
|
133260
133615
|
objectMode: false,
|
|
133261
133616
|
highWaterMark: 64 * 1024
|
|
133262
133617
|
});
|
|
133263
133618
|
if (trailer) {
|
|
133264
133619
|
stream.on("end", () => res.end(trailer));
|
|
133265
133620
|
stream.on("error", () => res.destroy());
|
|
133621
|
+
res.once("close", () => stream.destroy());
|
|
133266
133622
|
stream.pipe(res, { end: false });
|
|
133267
133623
|
} else {
|
|
133268
133624
|
pipeline2(stream, res, () => {
|
|
@@ -133640,6 +133996,7 @@ async function main() {
|
|
|
133640
133996
|
"/api/worktree-file/".length,
|
|
133641
133997
|
queryIdx === -1 ? void 0 : queryIdx
|
|
133642
133998
|
);
|
|
133999
|
+
const download = queryIdx !== -1 && new URLSearchParams(req.url.slice(queryIdx)).get("download") === "1";
|
|
133643
134000
|
const slashIdx = rest.indexOf("/");
|
|
133644
134001
|
if (slashIdx === -1) {
|
|
133645
134002
|
res.writeHead(400);
|
|
@@ -133661,7 +134018,7 @@ async function main() {
|
|
|
133661
134018
|
res.end("Bad request");
|
|
133662
134019
|
return;
|
|
133663
134020
|
}
|
|
133664
|
-
await serveWorktreeFile(res, wId, filePath);
|
|
134021
|
+
await serveWorktreeFile(res, wId, filePath, {}, void 0, download, req.headers.range);
|
|
133665
134022
|
return;
|
|
133666
134023
|
}
|
|
133667
134024
|
if (req.url?.startsWith("/api/cdp/tabs")) {
|