@staff0rd/assist 0.433.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/dist/commands/sessions/web/bundle.js +2 -2
- package/dist/index.js +347 -259
- 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.433.
|
|
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
|
}
|
|
@@ -15282,7 +15283,7 @@ function collectAllDeps(node) {
|
|
|
15282
15283
|
}
|
|
15283
15284
|
|
|
15284
15285
|
// src/commands/dotnet/findContainingSolutions.ts
|
|
15285
|
-
import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as
|
|
15286
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync33, statSync as statSync5 } from "fs";
|
|
15286
15287
|
import path32 from "path";
|
|
15287
15288
|
function findSlnFiles(dir, maxDepth, depth = 0) {
|
|
15288
15289
|
if (depth > maxDepth) return [];
|
|
@@ -15298,7 +15299,7 @@ function findSlnFiles(dir, maxDepth, depth = 0) {
|
|
|
15298
15299
|
continue;
|
|
15299
15300
|
const full = path32.join(dir, entry);
|
|
15300
15301
|
try {
|
|
15301
|
-
const stat2 =
|
|
15302
|
+
const stat2 = statSync5(full);
|
|
15302
15303
|
if (stat2.isFile() && entry.endsWith(".sln")) {
|
|
15303
15304
|
results.push(full);
|
|
15304
15305
|
} else if (stat2.isDirectory()) {
|
|
@@ -16269,7 +16270,7 @@ import {
|
|
|
16269
16270
|
readdirSync as readdirSync8,
|
|
16270
16271
|
readFileSync as readFileSync35,
|
|
16271
16272
|
rmSync as rmSync3,
|
|
16272
|
-
statSync as
|
|
16273
|
+
statSync as statSync6
|
|
16273
16274
|
} from "fs";
|
|
16274
16275
|
import { basename as basename10, join as join45 } from "path";
|
|
16275
16276
|
|
|
@@ -16342,13 +16343,13 @@ async function migrateFile(orm, origin, file, createdAt) {
|
|
|
16342
16343
|
async function migrateDiskHandovers(orm, origin, cwd = process.cwd()) {
|
|
16343
16344
|
let migrated = 0;
|
|
16344
16345
|
for (const file of collectMarkdown(getHandoversDir(cwd))) {
|
|
16345
|
-
const createdAt = parseArchiveTimestamp(basename10(file)) ??
|
|
16346
|
+
const createdAt = parseArchiveTimestamp(basename10(file)) ?? statSync6(file).mtime;
|
|
16346
16347
|
await migrateFile(orm, origin, file, createdAt);
|
|
16347
16348
|
migrated++;
|
|
16348
16349
|
}
|
|
16349
16350
|
const handoverPath = getHandoverPath(cwd);
|
|
16350
16351
|
if (existsSync42(handoverPath)) {
|
|
16351
|
-
await migrateFile(orm, origin, handoverPath,
|
|
16352
|
+
await migrateFile(orm, origin, handoverPath, statSync6(handoverPath).mtime);
|
|
16352
16353
|
migrated++;
|
|
16353
16354
|
}
|
|
16354
16355
|
return migrated;
|
|
@@ -21808,11 +21809,11 @@ async function runBacklogSession(synthesisPath) {
|
|
|
21808
21809
|
}
|
|
21809
21810
|
|
|
21810
21811
|
// src/commands/review/cachedReviewerResult.ts
|
|
21811
|
-
import { statSync as
|
|
21812
|
+
import { statSync as statSync7 } from "fs";
|
|
21812
21813
|
function cachedReviewerResult(name, outputPath) {
|
|
21813
21814
|
let size;
|
|
21814
21815
|
try {
|
|
21815
|
-
size =
|
|
21816
|
+
size = statSync7(outputPath).size;
|
|
21816
21817
|
} catch {
|
|
21817
21818
|
return null;
|
|
21818
21819
|
}
|
|
@@ -23523,14 +23524,14 @@ async function configure() {
|
|
|
23523
23524
|
}
|
|
23524
23525
|
|
|
23525
23526
|
// src/commands/transcript/list.ts
|
|
23526
|
-
import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as
|
|
23527
|
+
import { existsSync as existsSync48, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
|
|
23527
23528
|
import { join as join56 } from "path";
|
|
23528
23529
|
function list4() {
|
|
23529
23530
|
const { vttDir } = getTranscriptConfig();
|
|
23530
23531
|
if (!existsSync48(vttDir)) return;
|
|
23531
23532
|
for (const entry of readdirSync10(vttDir)) {
|
|
23532
23533
|
if (!entry.endsWith(".vtt")) continue;
|
|
23533
|
-
if (
|
|
23534
|
+
if (statSync8(join56(vttDir, entry)).isDirectory()) continue;
|
|
23534
23535
|
console.log(entry);
|
|
23535
23536
|
}
|
|
23536
23537
|
}
|
|
@@ -24366,7 +24367,7 @@ async function auth() {
|
|
|
24366
24367
|
|
|
24367
24368
|
// src/commands/roam/postRoamActivity.ts
|
|
24368
24369
|
import { execFileSync as execFileSync9 } from "child_process";
|
|
24369
|
-
import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as
|
|
24370
|
+
import { readdirSync as readdirSync11, readFileSync as readFileSync46, statSync as statSync9 } from "fs";
|
|
24370
24371
|
import { join as join63 } from "path";
|
|
24371
24372
|
function findPortFile(roamDir) {
|
|
24372
24373
|
let entries;
|
|
@@ -24378,7 +24379,7 @@ function findPortFile(roamDir) {
|
|
|
24378
24379
|
const candidates = entries.filter((name) => /^roam-local-api(-[^.]+)?\.port$/.test(name)).map((name) => {
|
|
24379
24380
|
const path71 = join63(roamDir, name);
|
|
24380
24381
|
try {
|
|
24381
|
-
return { path: path71, mtimeMs:
|
|
24382
|
+
return { path: path71, mtimeMs: statSync9(path71).mtimeMs };
|
|
24382
24383
|
} catch {
|
|
24383
24384
|
return void 0;
|
|
24384
24385
|
}
|
|
@@ -25572,7 +25573,7 @@ function readDesignSystemPrompt() {
|
|
|
25572
25573
|
import * as pty from "node-pty";
|
|
25573
25574
|
|
|
25574
25575
|
// src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
|
|
25575
|
-
import { chmodSync, existsSync as existsSync57, statSync as
|
|
25576
|
+
import { chmodSync, existsSync as existsSync57, statSync as statSync10 } from "fs";
|
|
25576
25577
|
import { createRequire as createRequire3 } from "module";
|
|
25577
25578
|
import path59 from "path";
|
|
25578
25579
|
var require4 = createRequire3(import.meta.url);
|
|
@@ -25588,7 +25589,7 @@ function ensureSpawnHelperExecutable() {
|
|
|
25588
25589
|
"spawn-helper"
|
|
25589
25590
|
);
|
|
25590
25591
|
if (!existsSync57(helper)) return;
|
|
25591
|
-
const mode =
|
|
25592
|
+
const mode = statSync10(helper).mode;
|
|
25592
25593
|
if ((mode & 73) === 0) chmodSync(helper, mode | 493);
|
|
25593
25594
|
}
|
|
25594
25595
|
|
|
@@ -25648,7 +25649,13 @@ function spawnRun(opts) {
|
|
|
25648
25649
|
return spawnPty(["assist", "run", opts.name, ...opts.args ?? []], opts.cwd);
|
|
25649
25650
|
}
|
|
25650
25651
|
|
|
25651
|
-
// 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
|
|
25652
25659
|
function sessionBase(id, status3) {
|
|
25653
25660
|
const startedAt = Date.now();
|
|
25654
25661
|
return {
|
|
@@ -25662,27 +25669,32 @@ function sessionBase(id, status3) {
|
|
|
25662
25669
|
scrollback: ""
|
|
25663
25670
|
};
|
|
25664
25671
|
}
|
|
25665
|
-
|
|
25666
|
-
|
|
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);
|
|
25667
25676
|
const claudeSessionId = randomUUID5();
|
|
25668
25677
|
return {
|
|
25669
25678
|
...sessionBase(id, prompt ? "running" : "waiting"),
|
|
25670
25679
|
name: prompt?.slice(0, 40) || `Session ${id}`,
|
|
25671
25680
|
commandType: "claude",
|
|
25672
|
-
|
|
25681
|
+
...startOrHoldPty(
|
|
25682
|
+
() => spawnClaude2({ prompt, cwd, sessionId: id, claudeSessionId, design }),
|
|
25683
|
+
holdPty
|
|
25684
|
+
),
|
|
25673
25685
|
cwd,
|
|
25674
25686
|
claudeSessionId,
|
|
25675
25687
|
initialPrompt: prompt,
|
|
25676
25688
|
design
|
|
25677
25689
|
};
|
|
25678
25690
|
}
|
|
25679
|
-
function createPiSession(id, prompt, cwd) {
|
|
25691
|
+
function createPiSession(id, prompt, cwd, holdPty) {
|
|
25680
25692
|
return {
|
|
25681
25693
|
...sessionBase(id, prompt ? "running" : "waiting"),
|
|
25682
25694
|
name: prompt?.slice(0, 40) || `Session ${id}`,
|
|
25683
25695
|
commandType: "claude",
|
|
25684
25696
|
harness: "pi",
|
|
25685
|
-
|
|
25697
|
+
...startOrHoldPty(() => spawnPi2({ prompt, cwd, sessionId: id }), holdPty),
|
|
25686
25698
|
cwd,
|
|
25687
25699
|
initialPrompt: prompt
|
|
25688
25700
|
};
|
|
@@ -26373,12 +26385,12 @@ function shouldAutoDismiss(session, exitCode) {
|
|
|
26373
26385
|
}
|
|
26374
26386
|
|
|
26375
26387
|
// src/commands/sessions/daemon/shouldAutoRun.ts
|
|
26376
|
-
function shouldAutoRun(session) {
|
|
26388
|
+
function shouldAutoRun(session, status3 = session.status) {
|
|
26377
26389
|
if (!session.autoRun) return { run: false, reason: null };
|
|
26378
|
-
if (
|
|
26390
|
+
if (status3 !== "done")
|
|
26379
26391
|
return {
|
|
26380
26392
|
run: false,
|
|
26381
|
-
reason: `session status is "${
|
|
26393
|
+
reason: `session status is "${status3}", not "done"`
|
|
26382
26394
|
};
|
|
26383
26395
|
if (session.commandType !== "assist")
|
|
26384
26396
|
return {
|
|
@@ -26404,7 +26416,8 @@ function shouldAutoRun(session) {
|
|
|
26404
26416
|
function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
|
|
26405
26417
|
if (session.status === status3) return;
|
|
26406
26418
|
const deps2 = { dismiss, notify: notify2, reuseForRun };
|
|
26407
|
-
|
|
26419
|
+
const chainsToNextRun = shouldAutoRun(session, status3).run;
|
|
26420
|
+
if (status3 === "done" && session.worktree && !chainsToNextRun) {
|
|
26408
26421
|
void resolveDoneDurability(
|
|
26409
26422
|
session,
|
|
26410
26423
|
() => finishStatusChange(session, "done", exitCode, deps2),
|
|
@@ -26439,6 +26452,25 @@ function makeStatusChangeHandler(dismiss, notify2, reuseForRun) {
|
|
|
26439
26452
|
return (s, status3, exitCode) => applyStatusChange(s, status3, exitCode, dismiss, notify2, reuseForRun);
|
|
26440
26453
|
}
|
|
26441
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
|
+
|
|
26442
26474
|
// src/commands/sessions/daemon/handleFailedResume.ts
|
|
26443
26475
|
function handleFailedResume(session, exitCode, onStatusChange) {
|
|
26444
26476
|
const failed2 = session.restored === true && exitCode !== 0 && session.scrollback.length === 0;
|
|
@@ -26452,7 +26484,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
|
|
|
26452
26484
|
}
|
|
26453
26485
|
|
|
26454
26486
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
26455
|
-
import { existsSync as
|
|
26487
|
+
import { existsSync as existsSync61, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
|
|
26456
26488
|
import { dirname as dirname29 } from "path";
|
|
26457
26489
|
|
|
26458
26490
|
// src/commands/sessions/daemon/applyReviewPause.ts
|
|
@@ -26533,7 +26565,7 @@ function watchActivity(session, notify2, onClaudeSessionId) {
|
|
|
26533
26565
|
if (timer) clearTimeout(timer);
|
|
26534
26566
|
timer = setTimeout(read, DEBOUNCE_MS2);
|
|
26535
26567
|
});
|
|
26536
|
-
if (
|
|
26568
|
+
if (existsSync61(path71)) read();
|
|
26537
26569
|
}
|
|
26538
26570
|
function refreshActivity(session) {
|
|
26539
26571
|
if (session.commandType !== "assist" || !session.cwd) return;
|
|
@@ -26582,7 +26614,7 @@ function handlePtyExit(session, exitCode, onStatusChange) {
|
|
|
26582
26614
|
if (handleFailedResume(session, exitCode, onStatusChange)) return;
|
|
26583
26615
|
const priorStatus = session.status;
|
|
26584
26616
|
if (exitCode !== 0) {
|
|
26585
|
-
session.error =
|
|
26617
|
+
session.error = exitReason(session, exitCode);
|
|
26586
26618
|
daemonLog(
|
|
26587
26619
|
`session ${session.id} ("${session.name}") pty exited with code ${exitCode} from status "${priorStatus}" \u2014 unexpected exit, marking error`
|
|
26588
26620
|
);
|
|
@@ -26597,21 +26629,23 @@ function handlePtyExit(session, exitCode, onStatusChange) {
|
|
|
26597
26629
|
}
|
|
26598
26630
|
|
|
26599
26631
|
// src/commands/sessions/daemon/wirePtyEvents.ts
|
|
26600
|
-
var MAX_SCROLLBACK = 256 * 1024;
|
|
26601
|
-
function appendScrollback(session, data) {
|
|
26602
|
-
session.scrollback += data;
|
|
26603
|
-
if (session.scrollback.length > MAX_SCROLLBACK) {
|
|
26604
|
-
session.scrollback = session.scrollback.slice(-MAX_SCROLLBACK);
|
|
26605
|
-
}
|
|
26606
|
-
}
|
|
26607
26632
|
function wirePtyEvents(session, clients, onStatusChange) {
|
|
26608
26633
|
if (!session.pty) return;
|
|
26609
|
-
session.pty.onData((data) =>
|
|
26610
|
-
|
|
26611
|
-
|
|
26634
|
+
session.pty.onData((data) => emitSessionOutput(session, clients, data));
|
|
26635
|
+
session.pty.onExit(({ exitCode }) => {
|
|
26636
|
+
handlePtyExit(session, exitCode, onStatusChange);
|
|
26637
|
+
reportSilentFailure(session, clients);
|
|
26612
26638
|
});
|
|
26613
|
-
|
|
26614
|
-
|
|
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
|
+
`
|
|
26615
26649
|
);
|
|
26616
26650
|
}
|
|
26617
26651
|
|
|
@@ -27164,6 +27198,207 @@ function restoreAll(spawn12, sessions) {
|
|
|
27164
27198
|
});
|
|
27165
27199
|
}
|
|
27166
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
|
+
|
|
27167
27402
|
// src/commands/sessions/daemon/retrySession.ts
|
|
27168
27403
|
function retrySession(session, clients, onStatusChange) {
|
|
27169
27404
|
const respawn = respawnThunk(session);
|
|
@@ -27270,8 +27505,44 @@ function shutdownSessions(sessions) {
|
|
|
27270
27505
|
}
|
|
27271
27506
|
}
|
|
27272
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
|
+
|
|
27273
27544
|
// src/commands/sessions/daemon/watchTranscript.ts
|
|
27274
|
-
import { existsSync as
|
|
27545
|
+
import { existsSync as existsSync66, watch as watch3 } from "fs";
|
|
27275
27546
|
|
|
27276
27547
|
// src/commands/sessions/daemon/reconcileTranscriptStatus.ts
|
|
27277
27548
|
async function resolveTranscriptPath(session) {
|
|
@@ -27306,7 +27577,7 @@ function watchTranscript(session, onStatusChange) {
|
|
|
27306
27577
|
if (!session.cwd || !session.claudeSessionId) return;
|
|
27307
27578
|
if (session.watchedTranscriptId === session.claudeSessionId) return;
|
|
27308
27579
|
const dir = projectDirForCwd(session.cwd);
|
|
27309
|
-
if (!
|
|
27580
|
+
if (!existsSync66(dir)) return;
|
|
27310
27581
|
if (session.transcriptWatcher) {
|
|
27311
27582
|
session.transcriptWatcher.close();
|
|
27312
27583
|
session.transcriptWatcher = void 0;
|
|
@@ -27457,13 +27728,13 @@ async function defaultConnect() {
|
|
|
27457
27728
|
}
|
|
27458
27729
|
|
|
27459
27730
|
// src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
|
|
27460
|
-
import { existsSync as
|
|
27731
|
+
import { existsSync as existsSync67, readFileSync as readFileSync49 } from "fs";
|
|
27461
27732
|
import { posix } from "path";
|
|
27462
27733
|
function hasPersistedWindowsSessions() {
|
|
27463
27734
|
const sessionsFile = windowsSessionsFileFromWsl();
|
|
27464
27735
|
if (!sessionsFile) return false;
|
|
27465
27736
|
try {
|
|
27466
|
-
if (!
|
|
27737
|
+
if (!existsSync67(sessionsFile)) return false;
|
|
27467
27738
|
const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
|
|
27468
27739
|
return Array.isArray(data) && data.length > 0;
|
|
27469
27740
|
} catch (error) {
|
|
@@ -27590,7 +27861,7 @@ function replayScrollback2(state, client) {
|
|
|
27590
27861
|
data: stripReplayQueries(data)
|
|
27591
27862
|
});
|
|
27592
27863
|
}
|
|
27593
|
-
function
|
|
27864
|
+
function appendScrollback(state, sessionId, data) {
|
|
27594
27865
|
const next3 = (state.scrollback.get(sessionId) ?? "") + data;
|
|
27595
27866
|
state.scrollback.set(
|
|
27596
27867
|
sessionId,
|
|
@@ -27690,7 +27961,7 @@ function handleCreated(state, msg) {
|
|
|
27690
27961
|
function handleOutput(state, msg) {
|
|
27691
27962
|
const sessionId = nsId(msg);
|
|
27692
27963
|
const data = msg.data;
|
|
27693
|
-
|
|
27964
|
+
appendScrollback(state, sessionId, data);
|
|
27694
27965
|
state.broadcast({ type: "output", sessionId, data });
|
|
27695
27966
|
}
|
|
27696
27967
|
function relayWithSessionId(state, msg) {
|
|
@@ -28006,194 +28277,8 @@ var WindowsProxy = class {
|
|
|
28006
28277
|
}
|
|
28007
28278
|
};
|
|
28008
28279
|
|
|
28009
|
-
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
28010
|
-
import { existsSync as existsSync66 } from "fs";
|
|
28011
|
-
import { basename as basename14 } from "path";
|
|
28012
|
-
|
|
28013
|
-
// src/commands/sessions/daemon/worktree/worktreeConfigFor.ts
|
|
28014
|
-
var DISABLED = {
|
|
28015
|
-
enabled: false,
|
|
28016
|
-
install: true,
|
|
28017
|
-
copy: [".env", "settings.local.json", ".claude/settings.local.json"]
|
|
28018
|
-
};
|
|
28019
|
-
function worktreeConfigFor(cwd) {
|
|
28020
|
-
try {
|
|
28021
|
-
return loadConfigFrom(cwd).worktree ?? DISABLED;
|
|
28022
|
-
} catch {
|
|
28023
|
-
return DISABLED;
|
|
28024
|
-
}
|
|
28025
|
-
}
|
|
28026
|
-
|
|
28027
|
-
// src/commands/sessions/daemon/worktree/detectExistingWorktree.ts
|
|
28028
|
-
function detectExistingWorktree(cwd) {
|
|
28029
|
-
const repoRoot = findRepoRoot(cwd) ?? cwd;
|
|
28030
|
-
if (!worktreeConfigFor(repoRoot).enabled) return void 0;
|
|
28031
|
-
const clone = mainWorktree(repoRoot);
|
|
28032
|
-
if (clone && clone !== repoRoot) return { path: repoRoot, clone };
|
|
28033
|
-
return void 0;
|
|
28034
|
-
}
|
|
28035
|
-
|
|
28036
|
-
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
28037
|
-
import { execFile as execFile8 } from "child_process";
|
|
28038
|
-
import { copyFileSync, existsSync as existsSync65, mkdirSync as mkdirSync26 } from "fs";
|
|
28039
|
-
import { dirname as dirname30, join as join71 } from "path";
|
|
28040
|
-
|
|
28041
|
-
// src/commands/sessions/daemon/worktree/resolveInstallCommand.ts
|
|
28042
|
-
import { existsSync as existsSync64 } from "fs";
|
|
28043
|
-
import { join as join70 } from "path";
|
|
28044
|
-
function detectInstallCommand(repoRoot) {
|
|
28045
|
-
if (!existsSync64(join70(repoRoot, "package.json"))) return null;
|
|
28046
|
-
if (existsSync64(join70(repoRoot, "pnpm-lock.yaml"))) return "pnpm install";
|
|
28047
|
-
if (existsSync64(join70(repoRoot, "yarn.lock"))) return "yarn install";
|
|
28048
|
-
if (existsSync64(join70(repoRoot, "bun.lockb"))) return "bun install";
|
|
28049
|
-
return "npm install";
|
|
28050
|
-
}
|
|
28051
|
-
function resolveInstallCommand(repoRoot, install) {
|
|
28052
|
-
if (install === false) return null;
|
|
28053
|
-
if (typeof install === "string") return install;
|
|
28054
|
-
return detectInstallCommand(repoRoot);
|
|
28055
|
-
}
|
|
28056
|
-
|
|
28057
|
-
// src/commands/sessions/daemon/worktree/seedWorktree.ts
|
|
28058
|
-
function seedWorktree(worktreePath, clone) {
|
|
28059
|
-
const cfg = worktreeConfigFor(clone);
|
|
28060
|
-
copyConfigFiles(worktreePath, clone, cfg.copy);
|
|
28061
|
-
runInstall(worktreePath, clone, cfg.install);
|
|
28062
|
-
}
|
|
28063
|
-
function copyConfigFiles(worktreePath, clone, copy) {
|
|
28064
|
-
for (const rel of copy) {
|
|
28065
|
-
const src = join71(clone, rel);
|
|
28066
|
-
if (!existsSync65(src)) continue;
|
|
28067
|
-
const dest = join71(worktreePath, rel);
|
|
28068
|
-
try {
|
|
28069
|
-
mkdirSync26(dirname30(dest), { recursive: true });
|
|
28070
|
-
copyFileSync(src, dest);
|
|
28071
|
-
daemonLog(`worktree ${worktreePath} seeded ${rel}`);
|
|
28072
|
-
} catch (error) {
|
|
28073
|
-
daemonLog(
|
|
28074
|
-
`worktree ${worktreePath} failed to seed ${rel}: ${message(error)}`
|
|
28075
|
-
);
|
|
28076
|
-
}
|
|
28077
|
-
}
|
|
28078
|
-
}
|
|
28079
|
-
function runInstall(worktreePath, clone, install) {
|
|
28080
|
-
const command = resolveInstallCommand(clone, install);
|
|
28081
|
-
if (!command) return;
|
|
28082
|
-
const windows = /^[A-Za-z]:[\\/]/.test(worktreePath);
|
|
28083
|
-
const shell = windows ? "cmd.exe" : process.env.SHELL ?? "bash";
|
|
28084
|
-
const shellArgs = windows ? ["/c", command] : ["-l", "-c", command];
|
|
28085
|
-
daemonLog(`worktree ${worktreePath} installing deps: ${command}`);
|
|
28086
|
-
execFile8(shell, shellArgs, { cwd: worktreePath }, (error) => {
|
|
28087
|
-
daemonLog(
|
|
28088
|
-
error ? `worktree ${worktreePath} install failed: ${error.message}` : `worktree ${worktreePath} install complete`
|
|
28089
|
-
);
|
|
28090
|
-
});
|
|
28091
|
-
}
|
|
28092
|
-
function message(error) {
|
|
28093
|
-
return error instanceof Error ? error.message : String(error);
|
|
28094
|
-
}
|
|
28095
|
-
|
|
28096
|
-
// src/commands/sessions/daemon/worktree/bindNewWorktree.ts
|
|
28097
|
-
function boundTreeRoots(sessions) {
|
|
28098
|
-
const roots = /* @__PURE__ */ new Set();
|
|
28099
|
-
for (const session of sessions.values())
|
|
28100
|
-
if (session.cwd) roots.add(findRepoRoot(session.cwd) ?? session.cwd);
|
|
28101
|
-
return roots;
|
|
28102
|
-
}
|
|
28103
|
-
function bindNewWorktree(session, alloc, notify2) {
|
|
28104
|
-
if (alloc.kind !== "worktree" || !alloc.cwd || !alloc.clone || !session)
|
|
28105
|
-
return;
|
|
28106
|
-
session.worktree = { path: alloc.cwd, clone: alloc.clone };
|
|
28107
|
-
if (alloc.created) seedWorktree(alloc.cwd, alloc.clone);
|
|
28108
|
-
notify2();
|
|
28109
|
-
}
|
|
28110
|
-
function bindRestoredWorktrees(sessions) {
|
|
28111
|
-
for (const session of sessions.values()) {
|
|
28112
|
-
if (session.worktree || !session.cwd) continue;
|
|
28113
|
-
const worktree = detectExistingWorktree(session.cwd);
|
|
28114
|
-
if (worktree) session.worktree = worktree;
|
|
28115
|
-
}
|
|
28116
|
-
}
|
|
28117
|
-
function bindResumedWorktree(session, cwd, notify2) {
|
|
28118
|
-
if (!session) return;
|
|
28119
|
-
const worktree = detectExistingWorktree(cwd);
|
|
28120
|
-
if (!worktree) return;
|
|
28121
|
-
session.worktree = worktree;
|
|
28122
|
-
notify2();
|
|
28123
|
-
}
|
|
28124
|
-
|
|
28125
|
-
// src/commands/sessions/daemon/worktree/readWorktreeRegistry.ts
|
|
28126
|
-
var REGISTRY_FILE = "worktrees.json";
|
|
28127
|
-
function readWorktreeRegistry() {
|
|
28128
|
-
const data = loadJson(REGISTRY_FILE);
|
|
28129
|
-
return Object.entries(data).map(([path71, record]) => ({ path: path71, ...record }));
|
|
28130
|
-
}
|
|
28131
|
-
function recordWorktree(path71, clone, origin) {
|
|
28132
|
-
const data = loadJson(REGISTRY_FILE);
|
|
28133
|
-
data[path71] = { clone, origin };
|
|
28134
|
-
saveJson(REGISTRY_FILE, data);
|
|
28135
|
-
}
|
|
28136
|
-
|
|
28137
|
-
// src/commands/sessions/daemon/worktree/reconcileWorktreesOnRestore.ts
|
|
28138
|
-
function reconcileWorktreesOnRestore(sessions) {
|
|
28139
|
-
bindRestoredWorktrees(sessions);
|
|
28140
|
-
void pruneWorktrees([...sessions.values()].map((s) => s.cwd));
|
|
28141
|
-
}
|
|
28142
|
-
async function pruneWorktrees(liveCwds) {
|
|
28143
|
-
const accounted = /* @__PURE__ */ new Set();
|
|
28144
|
-
for (const cwd of liveCwds) if (cwd) accounted.add(cwd);
|
|
28145
|
-
for (const s of loadPersistedSessions()) accounted.add(s.cwd);
|
|
28146
|
-
const clonesToPrune = /* @__PURE__ */ new Set();
|
|
28147
|
-
const orphanBranches = [];
|
|
28148
|
-
for (const { path: path71, clone } of readWorktreeRegistry()) {
|
|
28149
|
-
if (accounted.has(path71)) continue;
|
|
28150
|
-
if (!existsSync66(path71)) {
|
|
28151
|
-
clonesToPrune.add(clone);
|
|
28152
|
-
orphanBranches.push({ clone, branch: basename14(path71) });
|
|
28153
|
-
continue;
|
|
28154
|
-
}
|
|
28155
|
-
daemonLog(`worktree ${path71} orphaned across restart; reconciling`);
|
|
28156
|
-
await reapWorktree(path71);
|
|
28157
|
-
}
|
|
28158
|
-
for (const clone of clonesToPrune) {
|
|
28159
|
-
if (!existsSync66(clone)) continue;
|
|
28160
|
-
try {
|
|
28161
|
-
await git3(clone, ["worktree", "prune"]);
|
|
28162
|
-
daemonLog(`worktree bookkeeping pruned for clone ${clone}`);
|
|
28163
|
-
} catch {
|
|
28164
|
-
}
|
|
28165
|
-
}
|
|
28166
|
-
for (const { clone, branch: branch2 } of orphanBranches)
|
|
28167
|
-
if (existsSync66(clone)) await pruneStaleWorktreeBranch(clone, branch2);
|
|
28168
|
-
}
|
|
28169
|
-
async function pruneStaleWorktreeBranch(clone, branch2) {
|
|
28170
|
-
if (!listLocalBranches(clone).includes(branch2)) return;
|
|
28171
|
-
try {
|
|
28172
|
-
await git3(clone, ["branch", "-d", branch2]);
|
|
28173
|
-
daemonLog(`stale worktree branch ${branch2} pruned from clone ${clone}`);
|
|
28174
|
-
} catch {
|
|
28175
|
-
daemonLog(
|
|
28176
|
-
`stale worktree branch ${branch2} retained (unmerged; surfaced not destroyed) in clone ${clone}`
|
|
28177
|
-
);
|
|
28178
|
-
}
|
|
28179
|
-
}
|
|
28180
|
-
|
|
28181
|
-
// src/commands/sessions/daemon/worktree/rearmStoppedSessions.ts
|
|
28182
|
-
function rearmStoppedSessions(sessions, notify2) {
|
|
28183
|
-
for (const s of sessions.values()) {
|
|
28184
|
-
if (s.status !== "stopped" || !s.worktree) continue;
|
|
28185
|
-
void resolveDoneDurability(
|
|
28186
|
-
s,
|
|
28187
|
-
() => {
|
|
28188
|
-
if (dismissSession(sessions, s.id)) notify2();
|
|
28189
|
-
},
|
|
28190
|
-
notify2
|
|
28191
|
-
);
|
|
28192
|
-
}
|
|
28193
|
-
}
|
|
28194
|
-
|
|
28195
28280
|
// src/commands/sessions/daemon/createAssistSession.ts
|
|
28196
|
-
function createAssistSession(id, assistArgs, cwd, meta) {
|
|
28281
|
+
function createAssistSession(id, assistArgs, cwd, meta, holdPty) {
|
|
28197
28282
|
const startedAt = Date.now();
|
|
28198
28283
|
return {
|
|
28199
28284
|
id,
|
|
@@ -28205,7 +28290,10 @@ function createAssistSession(id, assistArgs, cwd, meta) {
|
|
|
28205
28290
|
startedAt,
|
|
28206
28291
|
runningMs: 0,
|
|
28207
28292
|
runningSince: startedAt,
|
|
28208
|
-
|
|
28293
|
+
...startOrHoldPty(
|
|
28294
|
+
() => spawnPty(["assist", ...assistArgs], cwd, id),
|
|
28295
|
+
holdPty
|
|
28296
|
+
),
|
|
28209
28297
|
scrollback: "",
|
|
28210
28298
|
assistArgs,
|
|
28211
28299
|
cwd
|
|
@@ -28234,7 +28322,7 @@ function resumeSession(id, sessionId, cwd, name) {
|
|
|
28234
28322
|
}
|
|
28235
28323
|
|
|
28236
28324
|
// src/commands/sessions/daemon/worktree/createWorktree.ts
|
|
28237
|
-
import { existsSync as
|
|
28325
|
+
import { existsSync as existsSync68 } from "fs";
|
|
28238
28326
|
import { basename as basename16, dirname as dirname31 } from "path";
|
|
28239
28327
|
|
|
28240
28328
|
// src/commands/sessions/daemon/worktree/planAllocation.ts
|
|
@@ -28259,7 +28347,7 @@ function createWorktree(clone, root, boundTreeRoots2) {
|
|
|
28259
28347
|
const path71 = nextWorktreePath(
|
|
28260
28348
|
clone,
|
|
28261
28349
|
base,
|
|
28262
|
-
(candidate) => registered.has(candidate) ||
|
|
28350
|
+
(candidate) => registered.has(candidate) || existsSync68(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
|
|
28263
28351
|
);
|
|
28264
28352
|
const trunk = cloneTrunkBranch(clone);
|
|
28265
28353
|
gitSync(clone, ["config", "push.default", "upstream"]);
|
|
@@ -28311,22 +28399,23 @@ function allocateTree(requestedCwd, boundTreeRoots2) {
|
|
|
28311
28399
|
// src/commands/sessions/daemon/worktree/spawnInTree.ts
|
|
28312
28400
|
function allocateAndBind(ctx, cwd, create) {
|
|
28313
28401
|
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
|
|
28314
|
-
const
|
|
28315
|
-
|
|
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);
|
|
28316
28405
|
return id;
|
|
28317
28406
|
}
|
|
28318
28407
|
function spawnInTree(ctx, prompt, cwd, design, harness) {
|
|
28319
28408
|
return allocateAndBind(
|
|
28320
28409
|
ctx,
|
|
28321
28410
|
cwd,
|
|
28322
|
-
(sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
|
|
28411
|
+
(sid, resolvedCwd, holdUntilSeeded) => createSession(sid, prompt, resolvedCwd, design, harness, holdUntilSeeded)
|
|
28323
28412
|
);
|
|
28324
28413
|
}
|
|
28325
28414
|
function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
|
|
28326
28415
|
return allocateAndBind(
|
|
28327
28416
|
ctx,
|
|
28328
28417
|
cwd,
|
|
28329
|
-
(sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
|
|
28418
|
+
(sid, resolvedCwd, holdUntilSeeded) => createAssistSession(sid, assistArgs, resolvedCwd, meta, holdUntilSeeded)
|
|
28330
28419
|
);
|
|
28331
28420
|
}
|
|
28332
28421
|
function resumeInTree(ctx, sessionId, cwd, name) {
|
|
@@ -28434,10 +28523,7 @@ var SessionManager = class {
|
|
|
28434
28523
|
);
|
|
28435
28524
|
}
|
|
28436
28525
|
restore() {
|
|
28437
|
-
|
|
28438
|
-
reconcileWorktreesOnRestore(this.sessions);
|
|
28439
|
-
rearmStoppedSessions(this.sessions, this.notify);
|
|
28440
|
-
return names;
|
|
28526
|
+
return restoreAllSessions(this.spawnWith, this.sessions, this.notify);
|
|
28441
28527
|
}
|
|
28442
28528
|
drain = () => drainSessions(this.sessions, this.notify);
|
|
28443
28529
|
add(session) {
|
|
@@ -28448,11 +28534,13 @@ var SessionManager = class {
|
|
|
28448
28534
|
}
|
|
28449
28535
|
spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
|
|
28450
28536
|
treeCtx() {
|
|
28451
|
-
return
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
|
|
28455
|
-
|
|
28537
|
+
return treeSpawnContext(
|
|
28538
|
+
this.sessions,
|
|
28539
|
+
this.spawnWith,
|
|
28540
|
+
this.notify,
|
|
28541
|
+
this.clients,
|
|
28542
|
+
this.onStatusChange
|
|
28543
|
+
);
|
|
28456
28544
|
}
|
|
28457
28545
|
spawn(prompt, cwd, design, harness) {
|
|
28458
28546
|
return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
|
|
@@ -29264,13 +29352,13 @@ function buildLimitsSegment(rateLimits) {
|
|
|
29264
29352
|
}
|
|
29265
29353
|
|
|
29266
29354
|
// src/commands/readGitBranch.ts
|
|
29267
|
-
import { readFileSync as readFileSync52, statSync as
|
|
29355
|
+
import { readFileSync as readFileSync52, statSync as statSync12 } from "fs";
|
|
29268
29356
|
import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
|
|
29269
29357
|
function resolveGitDir(cwd) {
|
|
29270
29358
|
const dotGit = join73(cwd, ".git");
|
|
29271
29359
|
let stat2;
|
|
29272
29360
|
try {
|
|
29273
|
-
stat2 =
|
|
29361
|
+
stat2 = statSync12(dotGit);
|
|
29274
29362
|
} catch {
|
|
29275
29363
|
return null;
|
|
29276
29364
|
}
|