@staff0rd/assist 0.432.0 → 0.433.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/commands/sessions/web/bundle.js +2 -2
- package/dist/index.js +356 -266
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.433.1",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -8070,17 +8070,18 @@ function delay(ms) {
|
|
|
8070
8070
|
|
|
8071
8071
|
// src/shared/createBundleHandler.ts
|
|
8072
8072
|
import { createHash } from "crypto";
|
|
8073
|
-
import { readFileSync as readFileSync18 } from "fs";
|
|
8073
|
+
import { readFileSync as readFileSync18, statSync as statSync2 } from "fs";
|
|
8074
8074
|
import { dirname as dirname19, join as join22 } from "path";
|
|
8075
8075
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8076
8076
|
function createBundleHandler(importMetaUrl, bundlePath, contentType = "application/javascript") {
|
|
8077
|
-
const
|
|
8077
|
+
const file = join22(dirname19(fileURLToPath4(importMetaUrl)), bundlePath);
|
|
8078
8078
|
let cache2;
|
|
8079
8079
|
return (req, res) => {
|
|
8080
|
-
|
|
8081
|
-
|
|
8080
|
+
const mtimeMs = statSync2(file).mtimeMs;
|
|
8081
|
+
if (cache2?.mtimeMs !== mtimeMs) {
|
|
8082
|
+
const body = readFileSync18(file, "utf8");
|
|
8082
8083
|
const etag = `"${createHash("sha256").update(body).digest("hex").slice(0, 16)}"`;
|
|
8083
|
-
cache2 = { body, etag };
|
|
8084
|
+
cache2 = { body, etag, mtimeMs };
|
|
8084
8085
|
}
|
|
8085
8086
|
const headers = {
|
|
8086
8087
|
ETag: cache2.etag,
|
|
@@ -8700,14 +8701,14 @@ import { basename as basename6, join as join25 } from "path";
|
|
|
8700
8701
|
import { promisify as promisify3 } from "util";
|
|
8701
8702
|
|
|
8702
8703
|
// src/commands/sessions/web/findSynthesisForBranch.ts
|
|
8703
|
-
import { existsSync as existsSync26, readdirSync as readdirSync2, statSync as
|
|
8704
|
+
import { existsSync as existsSync26, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
8704
8705
|
import { basename as basename5, dirname as dirname20, join as join24 } from "path";
|
|
8705
8706
|
function findSynthesisForBranch(repoReviewsDir, branch2) {
|
|
8706
8707
|
const branchKeyPath = join24(repoReviewsDir, `${branch2}-`);
|
|
8707
8708
|
const parent = dirname20(branchKeyPath);
|
|
8708
8709
|
const branchPrefix = basename5(branchKeyPath);
|
|
8709
8710
|
if (!existsSync26(parent)) return null;
|
|
8710
|
-
const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join24(parent, name, "synthesis.md")).filter((path71) => existsSync26(path71)).map((path71) => ({ path: path71, mtime:
|
|
8711
|
+
const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join24(parent, name, "synthesis.md")).filter((path71) => existsSync26(path71)).map((path71) => ({ path: path71, mtime: statSync3(path71).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
8711
8712
|
return synthesisFiles[0]?.path ?? null;
|
|
8712
8713
|
}
|
|
8713
8714
|
|
|
@@ -13295,7 +13296,7 @@ function getPinStatePath(pin) {
|
|
|
13295
13296
|
}
|
|
13296
13297
|
|
|
13297
13298
|
// src/commands/codeComment/sweepRestrictedDir.ts
|
|
13298
|
-
import { readdirSync as readdirSync3, statSync as
|
|
13299
|
+
import { readdirSync as readdirSync3, statSync as statSync4, unlinkSync as unlinkSync7 } from "fs";
|
|
13299
13300
|
import { join as join34 } from "path";
|
|
13300
13301
|
var STALE_AFTER_MS = 30 * 60 * 1e3;
|
|
13301
13302
|
function sweepRestrictedDir(dir = getRestrictedDir()) {
|
|
@@ -13309,7 +13310,7 @@ function sweepRestrictedDir(dir = getRestrictedDir()) {
|
|
|
13309
13310
|
for (const entry of entries) {
|
|
13310
13311
|
const path71 = join34(dir, entry);
|
|
13311
13312
|
try {
|
|
13312
|
-
if (
|
|
13313
|
+
if (statSync4(path71).mtimeMs < cutoff) unlinkSync7(path71);
|
|
13313
13314
|
} catch {
|
|
13314
13315
|
continue;
|
|
13315
13316
|
}
|
|
@@ -14373,8 +14374,10 @@ function resolveRepoTarget(key, value, repo) {
|
|
|
14373
14374
|
|
|
14374
14375
|
// src/commands/config/configSet.ts
|
|
14375
14376
|
function configSet(key, value, options2 = {}) {
|
|
14376
|
-
if (options2.repo !== void 0 && options2.global) {
|
|
14377
|
-
console.error(
|
|
14377
|
+
if (options2.repo !== void 0 && !options2.global) {
|
|
14378
|
+
console.error(
|
|
14379
|
+
chalk127.red("--repo writes to the global config; add -g (e.g. -g --repo)")
|
|
14380
|
+
);
|
|
14378
14381
|
process.exit(1);
|
|
14379
14382
|
}
|
|
14380
14383
|
const resolved = resolveRepoTarget(key, value, options2.repo);
|
|
@@ -14399,7 +14402,7 @@ function registerConfig(program2) {
|
|
|
14399
14402
|
const configCommand = program2.command("config").description("View and modify assist.yml configuration");
|
|
14400
14403
|
configCommand.command("set <key> [value]").description("Set a config value (e.g. commit.push true)").option("-g, --global", "Write to global ~/.assist.yml").option(
|
|
14401
14404
|
"-r, --repo [name]",
|
|
14402
|
-
"
|
|
14405
|
+
"Requires -g: scope the global write to a repo's identity (defaults to the current repo)"
|
|
14403
14406
|
).action((key, value, options2) => configSet(key, value, options2));
|
|
14404
14407
|
configCommand.command("get <key>").description("Get a config value").action(configGet);
|
|
14405
14408
|
configCommand.command("list").description("List all config values").action(configList);
|
|
@@ -15280,7 +15283,7 @@ function collectAllDeps(node) {
|
|
|
15280
15283
|
}
|
|
15281
15284
|
|
|
15282
15285
|
// src/commands/dotnet/findContainingSolutions.ts
|
|
15283
|
-
import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as
|
|
15286
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as statSync5 } from "fs";
|
|
15284
15287
|
import path32 from "path";
|
|
15285
15288
|
function findSlnFiles(dir, maxDepth, depth = 0) {
|
|
15286
15289
|
if (depth > maxDepth) return [];
|
|
@@ -15296,7 +15299,7 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
|
|
|
15296
15299
|
continue;
|
|
15297
15300
|
const full = path32.join(dir, entry);
|
|
15298
15301
|
try {
|
|
15299
|
-
const stat2 =
|
|
15302
|
+
const stat2 = statSync5(full);
|
|
15300
15303
|
if (stat2.isFile() && entry.endsWith(".sln")) {
|
|
15301
15304
|
results.push(full);
|
|
15302
15305
|
} else if (stat2.isDirectory()) {
|
|
@@ -16267,7 +16270,7 @@ import {
|
|
|
16267
16270
|
readdirSync as readdirSync8,
|
|
16268
16271
|
readFileSync as readFileSync35,
|
|
16269
16272
|
rmSync as rmSync3,
|
|
16270
|
-
statSync as
|
|
16273
|
+
statSync as statSync6
|
|
16271
16274
|
} from "fs";
|
|
16272
16275
|
import { basename as basename10, join as join45 } from "path";
|
|
16273
16276
|
|
|
@@ -16340,13 +16343,13 @@ async function migrateFile(orm, origin, file, createdAt) {
|
|
|
16340
16343
|
async function migrateDiskHandovers(orm, origin, cwd = process.cwd()) {
|
|
16341
16344
|
let migrated = 0;
|
|
16342
16345
|
for (const file of collectMarkdown(getHandoversDir(cwd))) {
|
|
16343
|
-
const createdAt = parseArchiveTimestamp(basename10(file)) ??
|
|
16346
|
+
const createdAt = parseArchiveTimestamp(basename10(file)) ?? statSync6(file).mtime;
|
|
16344
16347
|
await migrateFile(orm, origin, file, createdAt);
|
|
16345
16348
|
migrated++;
|
|
16346
16349
|
}
|
|
16347
16350
|
const handoverPath = getHandoverPath(cwd);
|
|
16348
16351
|
if (existsSync42(handoverPath)) {
|
|
16349
|
-
await migrateFile(orm, origin, handoverPath,
|
|
16352
|
+
await migrateFile(orm, origin, handoverPath, statSync6(handoverPath).mtime);
|
|
16350
16353
|
migrated++;
|
|
16351
16354
|
}
|
|
16352
16355
|
return migrated;
|
|
@@ -21806,11 +21809,11 @@ async function runBacklogSession(synthesisPath) {
|
|
|
21806
21809
|
}
|
|
21807
21810
|
|
|
21808
21811
|
// src/commands/review/cachedReviewerResult.ts
|
|
21809
|
-
import { statSync as
|
|
21812
|
+
import { statSync as statSync7 } from "fs";
|
|
21810
21813
|
function cachedReviewerResult(name, outputPath) {
|
|
21811
21814
|
let size;
|
|
21812
21815
|
try {
|
|
21813
|
-
size =
|
|
21816
|
+
size = statSync7(outputPath).size;
|
|
21814
21817
|
} catch {
|
|
21815
21818
|
return null;
|
|
21816
21819
|
}
|
|
@@ -23521,14 +23524,14 @@ async function configure() {
|
|
|
23521
23524
|
}
|
|
23522
23525
|
|
|
23523
23526
|
// src/commands/transcript/list.ts
|
|
23524
|
-
import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as
|
|
23527
|
+
import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
23525
23528
|
import { join as join56 } from "path";
|
|
23526
23529
|
function list4() {
|
|
23527
23530
|
const { vttDir } = getTranscriptConfig();
|
|
23528
23531
|
if (!existsSync48(vttDir)) return;
|
|
23529
23532
|
for (const entry of readdirSync10(vttDir)) {
|
|
23530
23533
|
if (!entry.endsWith(".vtt")) continue;
|
|
23531
|
-
if (
|
|
23534
|
+
if (statSync8(join56(vttDir, entry)).isDirectory()) continue;
|
|
23532
23535
|
console.log(entry);
|
|
23533
23536
|
}
|
|
23534
23537
|
}
|
|
@@ -24364,7 +24367,7 @@ async function auth() {
|
|
|
24364
24367
|
|
|
24365
24368
|
// src/commands/roam/postRoamActivity.ts
|
|
24366
24369
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
24367
|
-
import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as
|
|
24370
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as statSync9 } from "fs";
|
|
24368
24371
|
import { join as join63 } from "path";
|
|
24369
24372
|
function findPortFile(roamDir) {
|
|
24370
24373
|
let entries;
|
|
@@ -24376,7 +24379,7 @@ function findPortFile(roamDir) {
|
|
|
24376
24379
|
const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
|
|
24377
24380
|
const path71 = join63(roamDir, name);
|
|
24378
24381
|
try {
|
|
24379
|
-
return { path: path71, mtimeMs:
|
|
24382
|
+
return { path: path71, mtimeMs: statSync9(path71).mtimeMs };
|
|
24380
24383
|
} catch {
|
|
24381
24384
|
return void 0;
|
|
24382
24385
|
}
|
|
@@ -25570,7 +25573,7 @@ function readDesignSystemPrompt() {
|
|
|
25570
25573
|
import * as pty from "node-pty";
|
|
25571
25574
|
|
|
25572
25575
|
// src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
|
|
25573
|
-
import { chmodSync, existsSync as existsSync57, statSync as
|
|
25576
|
+
import { chmodSync, existsSync as existsSync57, statSync as statSync10 } from "fs";
|
|
25574
25577
|
import { createRequire as createRequire3 } from "module";
|
|
25575
25578
|
import path59 from "path";
|
|
25576
25579
|
var require4 = createRequire3(import.meta.url);
|
|
@@ -25586,7 +25589,7 @@ function ensureSpawnHelperExecutable() {
|
|
|
25586
25589
|
"spawn-helper"
|
|
25587
25590
|
);
|
|
25588
25591
|
if (!existsSync57(helper)) return;
|
|
25589
|
-
const mode =
|
|
25592
|
+
const mode = statSync10(helper).mode;
|
|
25590
25593
|
if ((mode & 73) === 0) chmodSync(helper, mode | 493);
|
|
25591
25594
|
}
|
|
25592
25595
|
|
|
@@ -25646,7 +25649,13 @@ function spawnRun(opts) {
|
|
|
25646
25649
|
return spawnPty(["assist", "run", opts.name, ...opts.args ?? []], opts.cwd);
|
|
25647
25650
|
}
|
|
25648
25651
|
|
|
25649
|
-
// src/commands/sessions/daemon/
|
|
25652
|
+
// src/commands/sessions/daemon/startOrHoldPty.ts
|
|
25653
|
+
function startOrHoldPty(start3, hold) {
|
|
25654
|
+
if (hold) return { pty: null, pendingStart: start3 };
|
|
25655
|
+
return { pty: start3() };
|
|
25656
|
+
}
|
|
25657
|
+
|
|
25658
|
+
// src/commands/sessions/daemon/sessionBase.ts
|
|
25650
25659
|
function sessionBase(id, status3) {
|
|
25651
25660
|
const startedAt = Date.now();
|
|
25652
25661
|
return {
|
|
@@ -25660,27 +25669,32 @@ function sessionBase(id, status3) {
|
|
|
25660
25669
|
scrollback: ""
|
|
25661
25670
|
};
|
|
25662
25671
|
}
|
|
25663
|
-
|
|
25664
|
-
|
|
25672
|
+
|
|
25673
|
+
// src/commands/sessions/daemon/createSession.ts
|
|
25674
|
+
function createSession(id, prompt, cwd, design, harness, holdPty) {
|
|
25675
|
+
if (harness === "pi") return createPiSession(id, prompt, cwd, holdPty);
|
|
25665
25676
|
const claudeSessionId = randomUUID5();
|
|
25666
25677
|
return {
|
|
25667
25678
|
...sessionBase(id, prompt ? "running" : "waiting"),
|
|
25668
25679
|
name: prompt?.slice(0, 40) || `Session ${id}`,
|
|
25669
25680
|
commandType: "claude",
|
|
25670
|
-
|
|
25681
|
+
...startOrHoldPty(
|
|
25682
|
+
() => spawnClaude2({ prompt, cwd, sessionId: id, claudeSessionId, design }),
|
|
25683
|
+
holdPty
|
|
25684
|
+
),
|
|
25671
25685
|
cwd,
|
|
25672
25686
|
claudeSessionId,
|
|
25673
25687
|
initialPrompt: prompt,
|
|
25674
25688
|
design
|
|
25675
25689
|
};
|
|
25676
25690
|
}
|
|
25677
|
-
function createPiSession(id, prompt, cwd) {
|
|
25691
|
+
function createPiSession(id, prompt, cwd, holdPty) {
|
|
25678
25692
|
return {
|
|
25679
25693
|
...sessionBase(id, prompt ? "running" : "waiting"),
|
|
25680
25694
|
name: prompt?.slice(0, 40) || `Session ${id}`,
|
|
25681
25695
|
commandType: "claude",
|
|
25682
25696
|
harness: "pi",
|
|
25683
|
-
|
|
25697
|
+
...startOrHoldPty(() => spawnPi2({ prompt, cwd, sessionId: id }), holdPty),
|
|
25684
25698
|
cwd,
|
|
25685
25699
|
initialPrompt: prompt
|
|
25686
25700
|
};
|
|
@@ -26371,12 +26385,12 @@ function shouldAutoDismiss(session, exitCode) {
|
|
|
26371
26385
|
}
|
|
26372
26386
|
|
|
26373
26387
|
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
26374
|
-
function shouldAutoRun(session) {
|
|
26388
|
+
function shouldAutoRun(session, status3 = session.status) {
|
|
26375
26389
|
if (!session.autoRun) return { run: false, reason: null };
|
|
26376
|
-
if (
|
|
26390
|
+
if (status3 !== "done")
|
|
26377
26391
|
return {
|
|
26378
26392
|
run: false,
|
|
26379
|
-
reason: `session status is "${
|
|
26393
|
+
reason: `session status is "${status3}", not "done"`
|
|
26380
26394
|
};
|
|
26381
26395
|
if (session.commandType !== "assist")
|
|
26382
26396
|
return {
|
|
@@ -26402,7 +26416,8 @@ function shouldAutoRun(session) {
|
|
|
26402
26416
|
function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
|
|
26403
26417
|
if (session.status === status3) return;
|
|
26404
26418
|
const deps2 = { dismiss, notify: notify2, reuseForRun };
|
|
26405
|
-
|
|
26419
|
+
const chainsToNextRun = shouldAutoRun(session, status3).run;
|
|
26420
|
+
if (status3 === "done" && session.worktree && !chainsToNextRun) {
|
|
26406
26421
|
void resolveDoneDurability(
|
|
26407
26422
|
session,
|
|
26408
26423
|
() => finishStatusChange(session, "done", exitCode, deps2),
|
|
@@ -26437,6 +26452,25 @@ function makeStatusChangeHandler(dismiss, notify2, reuseForRun) {
|
|
|
26437
26452
|
return (s, status3, exitCode) => applyStatusChange(s, status3, exitCode, dismiss, notify2, reuseForRun);
|
|
26438
26453
|
}
|
|
26439
26454
|
|
|
26455
|
+
// src/commands/sessions/daemon/emitSessionOutput.ts
|
|
26456
|
+
var MAX_SCROLLBACK = 256 * 1024;
|
|
26457
|
+
function emitSessionOutput(session, clients, data) {
|
|
26458
|
+
session.scrollback += data;
|
|
26459
|
+
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
26460
|
+
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
26461
|
+
}
|
|
26462
|
+
broadcast(clients, { type: "output", sessionId: session.id, data });
|
|
26463
|
+
}
|
|
26464
|
+
|
|
26465
|
+
// src/commands/sessions/daemon/exitReason.ts
|
|
26466
|
+
import { existsSync as existsSync60 } from "fs";
|
|
26467
|
+
function exitReason(session, exitCode) {
|
|
26468
|
+
const base = `process exited with code ${exitCode}`;
|
|
26469
|
+
if (session.cwd && !existsSync60(session.cwd))
|
|
26470
|
+
return `${base}: working directory ${session.cwd} no longer exists`;
|
|
26471
|
+
return base;
|
|
26472
|
+
}
|
|
26473
|
+
|
|
26440
26474
|
// src/commands/sessions/daemon/handleFailedResume.ts
|
|
26441
26475
|
function handleFailedResume(session, exitCode, onStatusChange) {
|
|
26442
26476
|
const failed2 = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
|
|
@@ -26450,7 +26484,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
|
|
|
26450
26484
|
}
|
|
26451
26485
|
|
|
26452
26486
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
26453
|
-
import { existsSync as
|
|
26487
|
+
import { existsSync as existsSync61, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
|
|
26454
26488
|
import { dirname as dirname29 } from "path";
|
|
26455
26489
|
|
|
26456
26490
|
// src/commands/sessions/daemon/applyReviewPause.ts
|
|
@@ -26531,7 +26565,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
|
|
|
26531
26565
|
if (timer) clearTimeout(timer);
|
|
26532
26566
|
timer = setTimeout(read, DEBOUNCE_MS2);
|
|
26533
26567
|
});
|
|
26534
|
-
if (
|
|
26568
|
+
if (existsSync61(path71)) read();
|
|
26535
26569
|
}
|
|
26536
26570
|
function refreshActivity(session) {
|
|
26537
26571
|
if (session.commandType !== "assist" || !session.cwd) return;
|
|
@@ -26580,7 +26614,7 @@ function handlePtyExit(session, exitCode, onStatusChange) {
|
|
|
26580
26614
|
if (handleFailedResume(session, exitCode, onStatusChange)) return;
|
|
26581
26615
|
const priorStatus = session.status;
|
|
26582
26616
|
if (exitCode !== 0) {
|
|
26583
|
-
session.error =
|
|
26617
|
+
session.error = exitReason(session, exitCode);
|
|
26584
26618
|
daemonLog(
|
|
26585
26619
|
`session ${session.id} ("${session.name}") pty exited with code ${exitCode} from status "${priorStatus}" \u2014 unexpected exit, marking error`
|
|
26586
26620
|
);
|
|
@@ -26595,21 +26629,23 @@ function handlePtyExit(session, exitCode, onStatusChange) {
|
|
|
26595
26629
|
}
|
|
26596
26630
|
|
|
26597
26631
|
// src/commands/sessions/daemon/wirePtyEvents.ts
|
|
26598
|
-
var MAX_SCROLLBACK = 256 * 1024;
|
|
26599
|
-
function appendScrollback(session, data) {
|
|
26600
|
-
session.scrollback += data;
|
|
26601
|
-
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
26602
|
-
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
26603
|
-
}
|
|
26604
|
-
}
|
|
26605
26632
|
function wirePtyEvents(session, clients, onStatusChange) {
|
|
26606
26633
|
if (!session.pty) return;
|
|
26607
|
-
session.pty.onData((data) =>
|
|
26608
|
-
|
|
26609
|
-
|
|
26634
|
+
session.pty.onData((data) => emitSessionOutput(session, clients, data));
|
|
26635
|
+
session.pty.onExit(({ exitCode }) => {
|
|
26636
|
+
handlePtyExit(session, exitCode, onStatusChange);
|
|
26637
|
+
reportSilentFailure(session, clients);
|
|
26610
26638
|
});
|
|
26611
|
-
|
|
26612
|
-
|
|
26639
|
+
}
|
|
26640
|
+
function reportSilentFailure(session, clients) {
|
|
26641
|
+
if (session.status !== "error" || !session.error) return;
|
|
26642
|
+
if (session.scrollback.length > 0) return;
|
|
26643
|
+
emitSessionOutput(
|
|
26644
|
+
session,
|
|
26645
|
+
clients,
|
|
26646
|
+
`\r
|
|
26647
|
+
\x1B[31m${session.error}\x1B[0m\r
|
|
26648
|
+
`
|
|
26613
26649
|
);
|
|
26614
26650
|
}
|
|
26615
26651
|
|
|
@@ -27162,6 +27198,207 @@ function restoreAll(spawn12, sessions) {
|
|
|
27162
27198
|
});
|
|
27163
27199
|
}
|
|
27164
27200
|
|
|
27201
|
+
// src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
|
|
27202
|
+
function rearmStoppedSessions(sessions, notify2) {
|
|
27203
|
+
for (const s of sessions.values()) {
|
|
27204
|
+
if (s.status !== "stopped" || !s.worktree) continue;
|
|
27205
|
+
void resolveDoneDurability(
|
|
27206
|
+
s,
|
|
27207
|
+
() => {
|
|
27208
|
+
if (dismissSession(sessions, s.id)) notify2();
|
|
27209
|
+
},
|
|
27210
|
+
notify2
|
|
27211
|
+
);
|
|
27212
|
+
}
|
|
27213
|
+
}
|
|
27214
|
+
|
|
27215
|
+
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
27216
|
+
import { existsSync as existsSync65 } from "fs";
|
|
27217
|
+
import { basename as basename14 } from "path";
|
|
27218
|
+
|
|
27219
|
+
// src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
|
|
27220
|
+
var DISABLED = {
|
|
27221
|
+
enabled: false,
|
|
27222
|
+
install: true,
|
|
27223
|
+
copy: [".env", "settings.local.json", ".claude/settings.local.json"]
|
|
27224
|
+
};
|
|
27225
|
+
function worktreeConfigFor(cwd) {
|
|
27226
|
+
try {
|
|
27227
|
+
return loadConfigFrom(cwd).worktree ?? DISABLED;
|
|
27228
|
+
} catch {
|
|
27229
|
+
return DISABLED;
|
|
27230
|
+
}
|
|
27231
|
+
}
|
|
27232
|
+
|
|
27233
|
+
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
27234
|
+
function detectExistingWorktree(cwd) {
|
|
27235
|
+
const repoRoot = findRepoRoot(cwd) ?? cwd;
|
|
27236
|
+
if (!worktreeConfigFor(repoRoot).enabled) return void 0;
|
|
27237
|
+
const clone = mainWorktree(repoRoot);
|
|
27238
|
+
if (clone && clone !== repoRoot) return { path: repoRoot, clone };
|
|
27239
|
+
return void 0;
|
|
27240
|
+
}
|
|
27241
|
+
|
|
27242
|
+
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
27243
|
+
import { execFile as execFile8 } from "child_process";
|
|
27244
|
+
import { copyFileSync, existsSync as existsSync64, mkdirSync as mkdirSync26 } from "fs";
|
|
27245
|
+
import { dirname as dirname30, join as join71 } from "path";
|
|
27246
|
+
|
|
27247
|
+
// src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
|
|
27248
|
+
import { existsSync as existsSync63 } from "fs";
|
|
27249
|
+
import { join as join70 } from "path";
|
|
27250
|
+
function detectInstallCommand(repoRoot) {
|
|
27251
|
+
if (!existsSync63(join70(repoRoot, "package.json"))) return null;
|
|
27252
|
+
if (existsSync63(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
|
|
27253
|
+
if (existsSync63(join70(repoRoot, "yarn.lock"))) return "yarn install";
|
|
27254
|
+
if (existsSync63(join70(repoRoot, "bun.lockb"))) return "bun install";
|
|
27255
|
+
return "npm install";
|
|
27256
|
+
}
|
|
27257
|
+
function resolveInstallCommand(repoRoot, install) {
|
|
27258
|
+
if (install === false) return null;
|
|
27259
|
+
if (typeof install === "string") return install;
|
|
27260
|
+
return detectInstallCommand(repoRoot);
|
|
27261
|
+
}
|
|
27262
|
+
|
|
27263
|
+
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
27264
|
+
function seedWorktree(worktreePath, clone, onSeeded = () => {
|
|
27265
|
+
}) {
|
|
27266
|
+
const cfg = worktreeConfigFor(clone);
|
|
27267
|
+
copyConfigFiles(worktreePath, clone, cfg.copy);
|
|
27268
|
+
runInstall(worktreePath, clone, cfg.install, onSeeded);
|
|
27269
|
+
}
|
|
27270
|
+
function copyConfigFiles(worktreePath, clone, copy) {
|
|
27271
|
+
for (const rel of copy) {
|
|
27272
|
+
const src = join71(clone, rel);
|
|
27273
|
+
if (!existsSync64(src)) continue;
|
|
27274
|
+
const dest = join71(worktreePath, rel);
|
|
27275
|
+
try {
|
|
27276
|
+
mkdirSync26(dirname30(dest), { recursive: true });
|
|
27277
|
+
copyFileSync(src, dest);
|
|
27278
|
+
daemonLog(`worktree ${worktreePath} seeded ${rel}`);
|
|
27279
|
+
} catch (error) {
|
|
27280
|
+
daemonLog(
|
|
27281
|
+
`worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
|
|
27282
|
+
);
|
|
27283
|
+
}
|
|
27284
|
+
}
|
|
27285
|
+
}
|
|
27286
|
+
function runInstall(worktreePath, clone, install, onSeeded) {
|
|
27287
|
+
const command = resolveInstallCommand(clone, install);
|
|
27288
|
+
if (!command) {
|
|
27289
|
+
onSeeded();
|
|
27290
|
+
return;
|
|
27291
|
+
}
|
|
27292
|
+
const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
|
|
27293
|
+
const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
|
|
27294
|
+
const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
|
|
27295
|
+
daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
|
|
27296
|
+
execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
|
|
27297
|
+
daemonLog(
|
|
27298
|
+
error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
|
|
27299
|
+
);
|
|
27300
|
+
onSeeded();
|
|
27301
|
+
});
|
|
27302
|
+
}
|
|
27303
|
+
function message(error) {
|
|
27304
|
+
return error instanceof Error ? error.message : String(error);
|
|
27305
|
+
}
|
|
27306
|
+
|
|
27307
|
+
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
27308
|
+
function boundTreeRoots(sessions) {
|
|
27309
|
+
const roots = /* @__PURE__ */ new Set();
|
|
27310
|
+
for (const session of sessions.values())
|
|
27311
|
+
if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
|
|
27312
|
+
return roots;
|
|
27313
|
+
}
|
|
27314
|
+
function bindNewWorktree(session, alloc, notify2, startHeld = () => {
|
|
27315
|
+
}) {
|
|
27316
|
+
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
27317
|
+
return;
|
|
27318
|
+
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
27319
|
+
if (alloc.created)
|
|
27320
|
+
seedWorktree(alloc.cwd, alloc.clone, () => startHeld(session));
|
|
27321
|
+
notify2();
|
|
27322
|
+
}
|
|
27323
|
+
function bindRestoredWorktrees(sessions) {
|
|
27324
|
+
for (const session of sessions.values()) {
|
|
27325
|
+
if (session.worktree || !session.cwd) continue;
|
|
27326
|
+
const worktree = detectExistingWorktree(session.cwd);
|
|
27327
|
+
if (worktree) session.worktree = worktree;
|
|
27328
|
+
}
|
|
27329
|
+
}
|
|
27330
|
+
function bindResumedWorktree(session, cwd, notify2) {
|
|
27331
|
+
if (!session) return;
|
|
27332
|
+
const worktree = detectExistingWorktree(cwd);
|
|
27333
|
+
if (!worktree) return;
|
|
27334
|
+
session.worktree = worktree;
|
|
27335
|
+
notify2();
|
|
27336
|
+
}
|
|
27337
|
+
|
|
27338
|
+
// src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
|
|
27339
|
+
var REGISTRY_FILE = "worktrees.json";
|
|
27340
|
+
function readWorktreeRegistry() {
|
|
27341
|
+
const data = loadJson(REGISTRY_FILE);
|
|
27342
|
+
return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
|
|
27343
|
+
}
|
|
27344
|
+
function recordWorktree(path71, clone, origin) {
|
|
27345
|
+
const data = loadJson(REGISTRY_FILE);
|
|
27346
|
+
data[path71] = { clone, origin };
|
|
27347
|
+
saveJson(REGISTRY_FILE, data);
|
|
27348
|
+
}
|
|
27349
|
+
|
|
27350
|
+
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
27351
|
+
function reconcileWorktreesOnRestore(sessions) {
|
|
27352
|
+
bindRestoredWorktrees(sessions);
|
|
27353
|
+
void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
|
|
27354
|
+
}
|
|
27355
|
+
async function pruneWorktrees(liveCwds) {
|
|
27356
|
+
const accounted = /* @__PURE__ */ new Set();
|
|
27357
|
+
for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
|
|
27358
|
+
for (const s of loadPersistedSessions()) accounted.add(s.cwd);
|
|
27359
|
+
const clonesToPrune = /* @__PURE__ */ new Set();
|
|
27360
|
+
const orphanBranches = [];
|
|
27361
|
+
for (const { path: path71, clone } of readWorktreeRegistry()) {
|
|
27362
|
+
if (accounted.has(path71)) continue;
|
|
27363
|
+
if (!existsSync65(path71)) {
|
|
27364
|
+
clonesToPrune.add(clone);
|
|
27365
|
+
orphanBranches.push({ clone, branch: basename14(path71) });
|
|
27366
|
+
continue;
|
|
27367
|
+
}
|
|
27368
|
+
daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
|
|
27369
|
+
await reapWorktree(path71);
|
|
27370
|
+
}
|
|
27371
|
+
for (const clone of clonesToPrune) {
|
|
27372
|
+
if (!existsSync65(clone)) continue;
|
|
27373
|
+
try {
|
|
27374
|
+
await git3(clone, ["worktree", "prune"]);
|
|
27375
|
+
daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
|
|
27376
|
+
} catch {
|
|
27377
|
+
}
|
|
27378
|
+
}
|
|
27379
|
+
for (const { clone, branch: branch2 } of orphanBranches)
|
|
27380
|
+
if (existsSync65(clone)) await pruneStaleWorktreeBranch(clone, branch2);
|
|
27381
|
+
}
|
|
27382
|
+
async function pruneStaleWorktreeBranch(clone, branch2) {
|
|
27383
|
+
if (!listLocalBranches(clone).includes(branch2)) return;
|
|
27384
|
+
try {
|
|
27385
|
+
await git3(clone, ["branch", "-d", branch2]);
|
|
27386
|
+
daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
|
|
27387
|
+
} catch {
|
|
27388
|
+
daemonLog(
|
|
27389
|
+
`stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
|
|
27390
|
+
);
|
|
27391
|
+
}
|
|
27392
|
+
}
|
|
27393
|
+
|
|
27394
|
+
// src/commands/sessions/daemon/restoreAllSessions.ts
|
|
27395
|
+
function restoreAllSessions(spawnWith, sessions, notify2) {
|
|
27396
|
+
const names = restoreAll(spawnWith, sessions);
|
|
27397
|
+
reconcileWorktreesOnRestore(sessions);
|
|
27398
|
+
rearmStoppedSessions(sessions, notify2);
|
|
27399
|
+
return names;
|
|
27400
|
+
}
|
|
27401
|
+
|
|
27165
27402
|
// src/commands/sessions/daemon/retrySession.ts
|
|
27166
27403
|
function retrySession(session, clients, onStatusChange) {
|
|
27167
27404
|
const respawn = respawnThunk(session);
|
|
@@ -27268,8 +27505,44 @@ function shutdownSessions(sessions) {
|
|
|
27268
27505
|
}
|
|
27269
27506
|
}
|
|
27270
27507
|
|
|
27508
|
+
// src/commands/sessions/daemon/startHeldSession.ts
|
|
27509
|
+
function startHeldSession(session, sessions, clients, onStatusChange, notify2) {
|
|
27510
|
+
const start3 = session.pendingStart;
|
|
27511
|
+
if (!start3) return;
|
|
27512
|
+
session.pendingStart = void 0;
|
|
27513
|
+
if (sessions.get(session.id) !== session) {
|
|
27514
|
+
daemonLog(
|
|
27515
|
+
`session ${session.id} not started after seeding: card is gone (${session.worktree?.path ?? "no worktree"})`
|
|
27516
|
+
);
|
|
27517
|
+
return;
|
|
27518
|
+
}
|
|
27519
|
+
session.startedAt = Date.now();
|
|
27520
|
+
if (session.status === "running") session.runningSince = session.startedAt;
|
|
27521
|
+
session.pty = start3();
|
|
27522
|
+
if (session.cols && session.rows)
|
|
27523
|
+
try {
|
|
27524
|
+
session.pty?.resize(session.cols, session.rows);
|
|
27525
|
+
} catch {
|
|
27526
|
+
}
|
|
27527
|
+
wirePtyEvents(session, clients, onStatusChange);
|
|
27528
|
+
daemonLog(
|
|
27529
|
+
`session ${session.id} started once worktree ${session.worktree?.path ?? session.cwd} finished seeding`
|
|
27530
|
+
);
|
|
27531
|
+
notify2();
|
|
27532
|
+
}
|
|
27533
|
+
|
|
27534
|
+
// src/commands/sessions/daemon/treeSpawnContext.ts
|
|
27535
|
+
function treeSpawnContext(sessions, spawnWith, notify2, clients, onStatusChange) {
|
|
27536
|
+
return {
|
|
27537
|
+
sessions,
|
|
27538
|
+
spawnWith,
|
|
27539
|
+
notify: notify2,
|
|
27540
|
+
startHeld: (session) => startHeldSession(session, sessions, clients, onStatusChange, notify2)
|
|
27541
|
+
};
|
|
27542
|
+
}
|
|
27543
|
+
|
|
27271
27544
|
// src/commands/sessions/daemon/watchTranscript.ts
|
|
27272
|
-
import { existsSync as
|
|
27545
|
+
import { existsSync as existsSync66, watch as watch3 } from "fs";
|
|
27273
27546
|
|
|
27274
27547
|
// src/commands/sessions/daemon/reconcileTranscriptStatus.ts
|
|
27275
27548
|
async function resolveTranscriptPath(session) {
|
|
@@ -27304,7 +27577,7 @@ function watchTranscript(session, onStatusChange) {
|
|
|
27304
27577
|
if (!session.cwd || !session.claudeSessionId) return;
|
|
27305
27578
|
if (session.watchedTranscriptId === session.claudeSessionId) return;
|
|
27306
27579
|
const dir = projectDirForCwd(session.cwd);
|
|
27307
|
-
if (!
|
|
27580
|
+
if (!existsSync66(dir)) return;
|
|
27308
27581
|
if (session.transcriptWatcher) {
|
|
27309
27582
|
session.transcriptWatcher.close();
|
|
27310
27583
|
session.transcriptWatcher = void 0;
|
|
@@ -27455,13 +27728,13 @@ async function defaultConnect() {
|
|
|
27455
27728
|
}
|
|
27456
27729
|
|
|
27457
27730
|
// src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
|
|
27458
|
-
import { existsSync as
|
|
27731
|
+
import { existsSync as existsSync67, readFileSync as readFileSync49 } from "fs";
|
|
27459
27732
|
import { posix } from "path";
|
|
27460
27733
|
function hasPersistedWindowsSessions() {
|
|
27461
27734
|
const sessionsFile = windowsSessionsFileFromWsl();
|
|
27462
27735
|
if (!sessionsFile) return false;
|
|
27463
27736
|
try {
|
|
27464
|
-
if (!
|
|
27737
|
+
if (!existsSync67(sessionsFile)) return false;
|
|
27465
27738
|
const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
|
|
27466
27739
|
return Array.isArray(data) && data.length > 0;
|
|
27467
27740
|
} catch (error) {
|
|
@@ -27588,7 +27861,7 @@ function replayScrollback2(state, client) {
|
|
|
27588
27861
|
data: stripReplayQueries(data)
|
|
27589
27862
|
});
|
|
27590
27863
|
}
|
|
27591
|
-
function
|
|
27864
|
+
function appendScrollback(state, sessionId, data) {
|
|
27592
27865
|
const next3 = (state.scrollback.get(sessionId) ?? "") + data;
|
|
27593
27866
|
state.scrollback.set(
|
|
27594
27867
|
sessionId,
|
|
@@ -27688,7 +27961,7 @@ function handleCreated(state, msg) {
|
|
|
27688
27961
|
function handleOutput(state, msg) {
|
|
27689
27962
|
const sessionId = nsId(msg);
|
|
27690
27963
|
const data = msg.data;
|
|
27691
|
-
|
|
27964
|
+
appendScrollback(state, sessionId, data);
|
|
27692
27965
|
state.broadcast({ type: "output", sessionId, data });
|
|
27693
27966
|
}
|
|
27694
27967
|
function relayWithSessionId(state, msg) {
|
|
@@ -28004,194 +28277,8 @@ var WindowsProxy = class {
|
|
|
28004
28277
|
}
|
|
28005
28278
|
};
|
|
28006
28279
|
|
|
28007
|
-
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
28008
|
-
import { existsSync as existsSync66 } from "fs";
|
|
28009
|
-
import { basename as basename14 } from "path";
|
|
28010
|
-
|
|
28011
|
-
// src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
|
|
28012
|
-
var DISABLED = {
|
|
28013
|
-
enabled: false,
|
|
28014
|
-
install: true,
|
|
28015
|
-
copy: [".env", "settings.local.json", ".claude/settings.local.json"]
|
|
28016
|
-
};
|
|
28017
|
-
function worktreeConfigFor(cwd) {
|
|
28018
|
-
try {
|
|
28019
|
-
return loadConfigFrom(cwd).worktree ?? DISABLED;
|
|
28020
|
-
} catch {
|
|
28021
|
-
return DISABLED;
|
|
28022
|
-
}
|
|
28023
|
-
}
|
|
28024
|
-
|
|
28025
|
-
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
28026
|
-
function detectExistingWorktree(cwd) {
|
|
28027
|
-
const repoRoot = findRepoRoot(cwd) ?? cwd;
|
|
28028
|
-
if (!worktreeConfigFor(repoRoot).enabled) return void 0;
|
|
28029
|
-
const clone = mainWorktree(repoRoot);
|
|
28030
|
-
if (clone && clone !== repoRoot) return { path: repoRoot, clone };
|
|
28031
|
-
return void 0;
|
|
28032
|
-
}
|
|
28033
|
-
|
|
28034
|
-
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
28035
|
-
import { execFile as execFile8 } from "child_process";
|
|
28036
|
-
import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
|
|
28037
|
-
import { dirname as dirname30, join as join71 } from "path";
|
|
28038
|
-
|
|
28039
|
-
// src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
|
|
28040
|
-
import { existsSync as existsSync64 } from "fs";
|
|
28041
|
-
import { join as join70 } from "path";
|
|
28042
|
-
function detectInstallCommand(repoRoot) {
|
|
28043
|
-
if (!existsSync64(join70(repoRoot, "package.json"))) return null;
|
|
28044
|
-
if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
|
|
28045
|
-
if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
|
|
28046
|
-
if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
|
|
28047
|
-
return "npm install";
|
|
28048
|
-
}
|
|
28049
|
-
function resolveInstallCommand(repoRoot, install) {
|
|
28050
|
-
if (install === false) return null;
|
|
28051
|
-
if (typeof install === "string") return install;
|
|
28052
|
-
return detectInstallCommand(repoRoot);
|
|
28053
|
-
}
|
|
28054
|
-
|
|
28055
|
-
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
28056
|
-
function seedWorktree(worktreePath, clone) {
|
|
28057
|
-
const cfg = worktreeConfigFor(clone);
|
|
28058
|
-
copyConfigFiles(worktreePath, clone, cfg.copy);
|
|
28059
|
-
runInstall(worktreePath, clone, cfg.install);
|
|
28060
|
-
}
|
|
28061
|
-
function copyConfigFiles(worktreePath, clone, copy) {
|
|
28062
|
-
for (const rel of copy) {
|
|
28063
|
-
const src = join71(clone, rel);
|
|
28064
|
-
if (!existsSync65(src)) continue;
|
|
28065
|
-
const dest = join71(worktreePath, rel);
|
|
28066
|
-
try {
|
|
28067
|
-
mkdirSync26(dirname30(dest), { recursive: true });
|
|
28068
|
-
copyFileSync(src, dest);
|
|
28069
|
-
daemonLog(`worktree ${worktreePath} seeded ${rel}`);
|
|
28070
|
-
} catch (error) {
|
|
28071
|
-
daemonLog(
|
|
28072
|
-
`worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
|
|
28073
|
-
);
|
|
28074
|
-
}
|
|
28075
|
-
}
|
|
28076
|
-
}
|
|
28077
|
-
function runInstall(worktreePath, clone, install) {
|
|
28078
|
-
const command = resolveInstallCommand(clone, install);
|
|
28079
|
-
if (!command) return;
|
|
28080
|
-
const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
|
|
28081
|
-
const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
|
|
28082
|
-
const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
|
|
28083
|
-
daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
|
|
28084
|
-
execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
|
|
28085
|
-
daemonLog(
|
|
28086
|
-
error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
|
|
28087
|
-
);
|
|
28088
|
-
});
|
|
28089
|
-
}
|
|
28090
|
-
function message(error) {
|
|
28091
|
-
return error instanceof Error ? error.message : String(error);
|
|
28092
|
-
}
|
|
28093
|
-
|
|
28094
|
-
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
28095
|
-
function boundTreeRoots(sessions) {
|
|
28096
|
-
const roots = /* @__PURE__ */ new Set();
|
|
28097
|
-
for (const session of sessions.values())
|
|
28098
|
-
if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
|
|
28099
|
-
return roots;
|
|
28100
|
-
}
|
|
28101
|
-
function bindNewWorktree(session, alloc, notify2) {
|
|
28102
|
-
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
28103
|
-
return;
|
|
28104
|
-
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
28105
|
-
if (alloc.created) seedWorktree(alloc.cwd, alloc.clone);
|
|
28106
|
-
notify2();
|
|
28107
|
-
}
|
|
28108
|
-
function bindRestoredWorktrees(sessions) {
|
|
28109
|
-
for (const session of sessions.values()) {
|
|
28110
|
-
if (session.worktree || !session.cwd) continue;
|
|
28111
|
-
const worktree = detectExistingWorktree(session.cwd);
|
|
28112
|
-
if (worktree) session.worktree = worktree;
|
|
28113
|
-
}
|
|
28114
|
-
}
|
|
28115
|
-
function bindResumedWorktree(session, cwd, notify2) {
|
|
28116
|
-
if (!session) return;
|
|
28117
|
-
const worktree = detectExistingWorktree(cwd);
|
|
28118
|
-
if (!worktree) return;
|
|
28119
|
-
session.worktree = worktree;
|
|
28120
|
-
notify2();
|
|
28121
|
-
}
|
|
28122
|
-
|
|
28123
|
-
// src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
|
|
28124
|
-
var REGISTRY_FILE = "worktrees.json";
|
|
28125
|
-
function readWorktreeRegistry() {
|
|
28126
|
-
const data = loadJson(REGISTRY_FILE);
|
|
28127
|
-
return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
|
|
28128
|
-
}
|
|
28129
|
-
function recordWorktree(path71, clone, origin) {
|
|
28130
|
-
const data = loadJson(REGISTRY_FILE);
|
|
28131
|
-
data[path71] = { clone, origin };
|
|
28132
|
-
saveJson(REGISTRY_FILE, data);
|
|
28133
|
-
}
|
|
28134
|
-
|
|
28135
|
-
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
28136
|
-
function reconcileWorktreesOnRestore(sessions) {
|
|
28137
|
-
bindRestoredWorktrees(sessions);
|
|
28138
|
-
void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
|
|
28139
|
-
}
|
|
28140
|
-
async function pruneWorktrees(liveCwds) {
|
|
28141
|
-
const accounted = /* @__PURE__ */ new Set();
|
|
28142
|
-
for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
|
|
28143
|
-
for (const s of loadPersistedSessions()) accounted.add(s.cwd);
|
|
28144
|
-
const clonesToPrune = /* @__PURE__ */ new Set();
|
|
28145
|
-
const orphanBranches = [];
|
|
28146
|
-
for (const { path: path71, clone } of readWorktreeRegistry()) {
|
|
28147
|
-
if (accounted.has(path71)) continue;
|
|
28148
|
-
if (!existsSync66(path71)) {
|
|
28149
|
-
clonesToPrune.add(clone);
|
|
28150
|
-
orphanBranches.push({ clone, branch: basename14(path71) });
|
|
28151
|
-
continue;
|
|
28152
|
-
}
|
|
28153
|
-
daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
|
|
28154
|
-
await reapWorktree(path71);
|
|
28155
|
-
}
|
|
28156
|
-
for (const clone of clonesToPrune) {
|
|
28157
|
-
if (!existsSync66(clone)) continue;
|
|
28158
|
-
try {
|
|
28159
|
-
await git3(clone, ["worktree", "prune"]);
|
|
28160
|
-
daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
|
|
28161
|
-
} catch {
|
|
28162
|
-
}
|
|
28163
|
-
}
|
|
28164
|
-
for (const { clone, branch: branch2 } of orphanBranches)
|
|
28165
|
-
if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
|
|
28166
|
-
}
|
|
28167
|
-
async function pruneStaleWorktreeBranch(clone, branch2) {
|
|
28168
|
-
if (!listLocalBranches(clone).includes(branch2)) return;
|
|
28169
|
-
try {
|
|
28170
|
-
await git3(clone, ["branch", "-d", branch2]);
|
|
28171
|
-
daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
|
|
28172
|
-
} catch {
|
|
28173
|
-
daemonLog(
|
|
28174
|
-
`stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
|
|
28175
|
-
);
|
|
28176
|
-
}
|
|
28177
|
-
}
|
|
28178
|
-
|
|
28179
|
-
// src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
|
|
28180
|
-
function rearmStoppedSessions(sessions, notify2) {
|
|
28181
|
-
for (const s of sessions.values()) {
|
|
28182
|
-
if (s.status !== "stopped" || !s.worktree) continue;
|
|
28183
|
-
void resolveDoneDurability(
|
|
28184
|
-
s,
|
|
28185
|
-
() => {
|
|
28186
|
-
if (dismissSession(sessions, s.id)) notify2();
|
|
28187
|
-
},
|
|
28188
|
-
notify2
|
|
28189
|
-
);
|
|
28190
|
-
}
|
|
28191
|
-
}
|
|
28192
|
-
|
|
28193
28280
|
// src/commands/sessions/daemon/createAssistSession.ts
|
|
28194
|
-
function createAssistSession(id, assistArgs, cwd, meta) {
|
|
28281
|
+
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
28195
28282
|
const startedAt = Date.now();
|
|
28196
28283
|
return {
|
|
28197
28284
|
id,
|
|
@@ -28203,7 +28290,10 @@ function createAssistSession(id, assistArgs, cwd, meta) {
|
|
|
28203
28290
|
startedAt,
|
|
28204
28291
|
runningMs: 0,
|
|
28205
28292
|
runningSince: startedAt,
|
|
28206
|
-
|
|
28293
|
+
...startOrHoldPty(
|
|
28294
|
+
() => spawnPty(["assist", ...assistArgs], cwd, id),
|
|
28295
|
+
holdPty
|
|
28296
|
+
),
|
|
28207
28297
|
scrollback: "",
|
|
28208
28298
|
assistArgs,
|
|
28209
28299
|
cwd
|
|
@@ -28232,7 +28322,7 @@ function resumeSession(id, sessionId, cwd, name) {
|
|
|
28232
28322
|
}
|
|
28233
28323
|
|
|
28234
28324
|
// src/commands/sessions/daemon/worktree/createWorktree.ts
|
|
28235
|
-
import { existsSync as
|
|
28325
|
+
import { existsSync as existsSync68 } from "fs";
|
|
28236
28326
|
import { basename as basename16, dirname as dirname31 } from "path";
|
|
28237
28327
|
|
|
28238
28328
|
// src/commands/sessions/daemon/worktree/planAllocation.ts
|
|
@@ -28257,7 +28347,7 @@ function createWorktree(clone, root, boundTreeRoots2) {
|
|
|
28257
28347
|
const path71 = nextWorktreePath(
|
|
28258
28348
|
clone,
|
|
28259
28349
|
base,
|
|
28260
|
-
(candidate) => registered.has(candidate) ||
|
|
28350
|
+
(candidate) => registered.has(candidate) || existsSync68(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
|
|
28261
28351
|
);
|
|
28262
28352
|
const trunk = cloneTrunkBranch(clone);
|
|
28263
28353
|
gitSync(clone, ["config", "push.default", "upstream"]);
|
|
@@ -28309,22 +28399,23 @@ function allocateTree(requestedCwd, boundTreeRoots2) {
|
|
|
28309
28399
|
// src/commands/sessions/daemon/worktree/spawnInTree.ts
|
|
28310
28400
|
function allocateAndBind(ctx, cwd, create) {
|
|
28311
28401
|
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
|
|
28312
|
-
const
|
|
28313
|
-
|
|
28402
|
+
const needsSeeding = alloc.kind === "worktree" && alloc.created === true;
|
|
28403
|
+
const id = ctx.spawnWith((sid) => create(sid, alloc.cwd, needsSeeding));
|
|
28404
|
+
bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify, ctx.startHeld);
|
|
28314
28405
|
return id;
|
|
28315
28406
|
}
|
|
28316
28407
|
function spawnInTree(ctx, prompt, cwd, design, harness) {
|
|
28317
28408
|
return allocateAndBind(
|
|
28318
28409
|
ctx,
|
|
28319
28410
|
cwd,
|
|
28320
|
-
(sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
|
|
28411
|
+
(sid, resolvedCwd, holdUntilSeeded) => createSession(sid, prompt, resolvedCwd, design, harness, holdUntilSeeded)
|
|
28321
28412
|
);
|
|
28322
28413
|
}
|
|
28323
28414
|
function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
|
|
28324
28415
|
return allocateAndBind(
|
|
28325
28416
|
ctx,
|
|
28326
28417
|
cwd,
|
|
28327
|
-
(sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
|
|
28418
|
+
(sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded)
|
|
28328
28419
|
);
|
|
28329
28420
|
}
|
|
28330
28421
|
function resumeInTree(ctx, sessionId, cwd, name) {
|
|
@@ -28432,10 +28523,7 @@ var SessionManager = class {
|
|
|
28432
28523
|
);
|
|
28433
28524
|
}
|
|
28434
28525
|
restore() {
|
|
28435
|
-
|
|
28436
|
-
reconcileWorktreesOnRestore(this.sessions);
|
|
28437
|
-
rearmStoppedSessions(this.sessions, this.notify);
|
|
28438
|
-
return names;
|
|
28526
|
+
return restoreAllSessions(this.spawnWith, this.sessions, this.notify);
|
|
28439
28527
|
}
|
|
28440
28528
|
drain = () => drainSessions(this.sessions, this.notify);
|
|
28441
28529
|
add(session) {
|
|
@@ -28446,11 +28534,13 @@ var SessionManager = class {
|
|
|
28446
28534
|
}
|
|
28447
28535
|
spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
|
|
28448
28536
|
treeCtx() {
|
|
28449
|
-
return
|
|
28450
|
-
|
|
28451
|
-
|
|
28452
|
-
|
|
28453
|
-
|
|
28537
|
+
return treeSpawnContext(
|
|
28538
|
+
this.sessions,
|
|
28539
|
+
this.spawnWith,
|
|
28540
|
+
this.notify,
|
|
28541
|
+
this.clients,
|
|
28542
|
+
this.onStatusChange
|
|
28543
|
+
);
|
|
28454
28544
|
}
|
|
28455
28545
|
spawn(prompt, cwd, design, harness) {
|
|
28456
28546
|
return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
|
|
@@ -29027,22 +29117,22 @@ var sessionsConfigHelp = [
|
|
|
29027
29117
|
},
|
|
29028
29118
|
{
|
|
29029
29119
|
key: "worktree.enabled",
|
|
29030
|
-
setter: "assist config set worktree.enabled true --repo",
|
|
29120
|
+
setter: "assist config set worktree.enabled true -g --repo",
|
|
29031
29121
|
note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
|
|
29032
29122
|
},
|
|
29033
29123
|
{
|
|
29034
29124
|
key: "worktree.root",
|
|
29035
|
-
setter: "assist config set worktree.root ~/git --repo",
|
|
29125
|
+
setter: "assist config set worktree.root ~/git -g --repo",
|
|
29036
29126
|
note: "optional; parent dir for <clone>-N worktrees (default: the clone's parent)"
|
|
29037
29127
|
},
|
|
29038
29128
|
{
|
|
29039
29129
|
key: "worktree.install",
|
|
29040
|
-
setter: "assist config set worktree.install true --repo",
|
|
29130
|
+
setter: "assist config set worktree.install true -g --repo",
|
|
29041
29131
|
note: "per-worktree dep install: true auto-detects the package manager, or give an explicit command; false to skip"
|
|
29042
29132
|
},
|
|
29043
29133
|
{
|
|
29044
29134
|
key: "worktree.copy",
|
|
29045
|
-
setter: "assist config set worktree.copy .env,.claude/settings.local.json --repo",
|
|
29135
|
+
setter: "assist config set worktree.copy .env,.claude/settings.local.json -g --repo",
|
|
29046
29136
|
note: "gitignored files copied into a new worktree so it can build/run"
|
|
29047
29137
|
}
|
|
29048
29138
|
];
|
|
@@ -29262,13 +29352,13 @@ function buildLimitsSegment(rateLimits) {
|
|
|
29262
29352
|
}
|
|
29263
29353
|
|
|
29264
29354
|
// src/commands/readGitBranch.ts
|
|
29265
|
-
import { readFileSync as readFileSync52, statSync as
|
|
29355
|
+
import { readFileSync as readFileSync52, statSync as statSync12 } from "fs";
|
|
29266
29356
|
import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
|
|
29267
29357
|
function resolveGitDir(cwd) {
|
|
29268
29358
|
const dotGit = join73(cwd, ".git");
|
|
29269
29359
|
let stat2;
|
|
29270
29360
|
try {
|
|
29271
|
-
stat2 =
|
|
29361
|
+
stat2 = statSync12(dotGit);
|
|
29272
29362
|
} catch {
|
|
29273
29363
|
return null;
|
|
29274
29364
|
}
|