@staff0rd/assist 0.431.0 → 0.432.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/sessions/web/bundle.js +407 -407
- package/dist/index.js +919 -272
- 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.432.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -432,6 +432,16 @@ var assistConfigSchema = z2.strictObject({
|
|
|
432
432
|
promptJira: z2.boolean().default(false)
|
|
433
433
|
}).optional(),
|
|
434
434
|
repos: z2.record(z2.string(), z2.record(z2.string(), z2.unknown())).optional(),
|
|
435
|
+
worktree: z2.strictObject({
|
|
436
|
+
enabled: z2.boolean().default(false),
|
|
437
|
+
root: z2.string().optional(),
|
|
438
|
+
install: z2.union([z2.boolean(), z2.string()]).default(true),
|
|
439
|
+
copy: z2.array(z2.string()).default([
|
|
440
|
+
".env",
|
|
441
|
+
"settings.local.json",
|
|
442
|
+
".claude/settings.local.json"
|
|
443
|
+
])
|
|
444
|
+
}).optional(),
|
|
435
445
|
run: z2.array(z2.union([runConfigSchema, runLinkSchema])).optional(),
|
|
436
446
|
subtasks: z2.array(
|
|
437
447
|
z2.strictObject({
|
|
@@ -1542,6 +1552,7 @@ function warnIfUnexpectedBranch(config) {
|
|
|
1542
1552
|
if (!expected) return;
|
|
1543
1553
|
const current = currentBranch();
|
|
1544
1554
|
if (current === null || current === expected) return;
|
|
1555
|
+
if (upstreamBranch() === expected) return;
|
|
1545
1556
|
const line = "\u2500".repeat(64);
|
|
1546
1557
|
console.warn(
|
|
1547
1558
|
chalk3.yellow.bold(
|
|
@@ -1565,6 +1576,18 @@ function currentBranch() {
|
|
|
1565
1576
|
return null;
|
|
1566
1577
|
}
|
|
1567
1578
|
}
|
|
1579
|
+
function upstreamBranch() {
|
|
1580
|
+
try {
|
|
1581
|
+
const upstream = execSync(
|
|
1582
|
+
"git rev-parse --abbrev-ref --symbolic-full-name @{upstream}",
|
|
1583
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }
|
|
1584
|
+
).trim();
|
|
1585
|
+
const slash = upstream.indexOf("/");
|
|
1586
|
+
return slash === -1 ? upstream || null : upstream.slice(slash + 1) || null;
|
|
1587
|
+
} catch {
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1568
1591
|
|
|
1569
1592
|
// src/commands/commit/findConflictMarkers.ts
|
|
1570
1593
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1726,7 +1749,7 @@ function gitRefUrl(kind, ref, cwd) {
|
|
|
1726
1749
|
}
|
|
1727
1750
|
|
|
1728
1751
|
// src/commands/commit/collectCommitRefs.ts
|
|
1729
|
-
function collectCommitRefs(
|
|
1752
|
+
function collectCommitRefs(message2) {
|
|
1730
1753
|
const refs = [];
|
|
1731
1754
|
const branch2 = git("rev-parse --abbrev-ref HEAD");
|
|
1732
1755
|
if (branch2 && branch2 !== "HEAD") {
|
|
@@ -1743,7 +1766,7 @@ function collectCommitRefs(message) {
|
|
|
1743
1766
|
ref: sha,
|
|
1744
1767
|
url: gitRefUrl("commit", sha)
|
|
1745
1768
|
};
|
|
1746
|
-
const subject =
|
|
1769
|
+
const subject = message2.split("\n", 1)[0];
|
|
1747
1770
|
if (subject) ref.title = subject;
|
|
1748
1771
|
refs.push(ref);
|
|
1749
1772
|
}
|
|
@@ -1762,16 +1785,16 @@ function git(args) {
|
|
|
1762
1785
|
|
|
1763
1786
|
// src/commands/commit/stageAndCommit.ts
|
|
1764
1787
|
import { execSync as execSync5 } from "child_process";
|
|
1765
|
-
function commitStaged(
|
|
1766
|
-
execSync5(`git commit -m ${shellQuote(
|
|
1788
|
+
function commitStaged(message2) {
|
|
1789
|
+
execSync5(`git commit -m ${shellQuote(message2)}`, { stdio: "inherit" });
|
|
1767
1790
|
return execSync5("git rev-parse --short=7 HEAD", {
|
|
1768
1791
|
encoding: "utf8"
|
|
1769
1792
|
}).trim();
|
|
1770
1793
|
}
|
|
1771
|
-
function stageAndCommit(files,
|
|
1794
|
+
function stageAndCommit(files, message2) {
|
|
1772
1795
|
const escaped = files.map(shellQuote).join(" ");
|
|
1773
1796
|
execSync5(`git add ${escaped}`, { stdio: "inherit" });
|
|
1774
|
-
return commitStaged(
|
|
1797
|
+
return commitStaged(message2);
|
|
1775
1798
|
}
|
|
1776
1799
|
|
|
1777
1800
|
// src/commands/sessions/summarise/iterateUserMessages.ts
|
|
@@ -1866,34 +1889,34 @@ function backlogRefError(subject, context, ids) {
|
|
|
1866
1889
|
// src/commands/commit/validateMessage.ts
|
|
1867
1890
|
var MAX_MESSAGE_LENGTH = 50;
|
|
1868
1891
|
var CONVENTIONAL_COMMIT_REGEX = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(!)?(\(.+\))?!?: .+$/;
|
|
1869
|
-
function validateMessage(
|
|
1870
|
-
if (
|
|
1892
|
+
function validateMessage(message2, config) {
|
|
1893
|
+
if (message2.toLowerCase().includes("claude")) {
|
|
1871
1894
|
console.error("Error: Commit message must not reference Claude");
|
|
1872
1895
|
process.exit(1);
|
|
1873
1896
|
}
|
|
1874
|
-
const backlogIds = findBacklogRefs(
|
|
1897
|
+
const backlogIds = findBacklogRefs(message2);
|
|
1875
1898
|
if (backlogIds.length > 0) {
|
|
1876
1899
|
console.error(
|
|
1877
1900
|
backlogRefError("Commit message", "commit messages", backlogIds)
|
|
1878
1901
|
);
|
|
1879
1902
|
process.exit(1);
|
|
1880
1903
|
}
|
|
1881
|
-
if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(
|
|
1904
|
+
if (config.commit?.conventional && !CONVENTIONAL_COMMIT_REGEX.test(message2)) {
|
|
1882
1905
|
console.error(
|
|
1883
1906
|
"Error: Commit message must follow conventional commit format (e.g., 'feat: add feature', 'fix(scope): fix bug')"
|
|
1884
1907
|
);
|
|
1885
1908
|
process.exit(1);
|
|
1886
1909
|
}
|
|
1887
|
-
if (
|
|
1910
|
+
if (message2.length > MAX_MESSAGE_LENGTH) {
|
|
1888
1911
|
console.error(
|
|
1889
|
-
`Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${
|
|
1912
|
+
`Error: Commit message must be ${MAX_MESSAGE_LENGTH} characters or less (current: ${message2.length})`
|
|
1890
1913
|
);
|
|
1891
1914
|
process.exit(1);
|
|
1892
1915
|
}
|
|
1893
1916
|
}
|
|
1894
1917
|
|
|
1895
1918
|
// src/commands/commit.ts
|
|
1896
|
-
async function execCommit(files,
|
|
1919
|
+
async function execCommit(files, message2, config) {
|
|
1897
1920
|
try {
|
|
1898
1921
|
warnIfUnexpectedBranch(config);
|
|
1899
1922
|
const pulled = Boolean(config.commit?.pull);
|
|
@@ -1901,21 +1924,21 @@ async function execCommit(files, message, config) {
|
|
|
1901
1924
|
execSync6("git pull --autostash", { stdio: "inherit" });
|
|
1902
1925
|
}
|
|
1903
1926
|
abortOnConflicts(files, pulled);
|
|
1904
|
-
const sha = files.length > 0 ? stageAndCommit(files,
|
|
1927
|
+
const sha = files.length > 0 ? stageAndCommit(files, message2) : commitStaged(message2);
|
|
1905
1928
|
console.log(`Committed: ${sha}`);
|
|
1906
1929
|
if (config.commit?.push) {
|
|
1907
1930
|
execSync6("git push", { stdio: "inherit" });
|
|
1908
1931
|
console.log("Pushed to remote");
|
|
1909
1932
|
}
|
|
1910
|
-
await recordCommitActivity(
|
|
1933
|
+
await recordCommitActivity(message2);
|
|
1911
1934
|
process.exit(0);
|
|
1912
1935
|
} catch {
|
|
1913
1936
|
process.exit(1);
|
|
1914
1937
|
}
|
|
1915
1938
|
}
|
|
1916
|
-
async function recordCommitActivity(
|
|
1939
|
+
async function recordCommitActivity(message2) {
|
|
1917
1940
|
if (resolveSessionItemId() === null) return;
|
|
1918
|
-
await recordSessionRefs(collectCommitRefs(
|
|
1941
|
+
await recordSessionRefs(collectCommitRefs(message2));
|
|
1919
1942
|
}
|
|
1920
1943
|
async function commit(args) {
|
|
1921
1944
|
if (args[0] === "status") {
|
|
@@ -1928,11 +1951,11 @@ async function commit(args) {
|
|
|
1928
1951
|
console.error("Usage: assist commit <message> [files...]");
|
|
1929
1952
|
process.exit(1);
|
|
1930
1953
|
}
|
|
1931
|
-
const
|
|
1954
|
+
const message2 = args[0];
|
|
1932
1955
|
const files = args.slice(1);
|
|
1933
1956
|
const config = loadConfig();
|
|
1934
|
-
validateMessage(
|
|
1935
|
-
await execCommit(files,
|
|
1957
|
+
validateMessage(message2, config);
|
|
1958
|
+
await execCommit(files, message2, config);
|
|
1936
1959
|
}
|
|
1937
1960
|
|
|
1938
1961
|
// src/commands/coverage.ts
|
|
@@ -1970,12 +1993,12 @@ async function exitOnCancel(promise) {
|
|
|
1970
1993
|
}
|
|
1971
1994
|
|
|
1972
1995
|
// src/shared/promptMultiselect.ts
|
|
1973
|
-
async function promptMultiselect(
|
|
1996
|
+
async function promptMultiselect(message2, options2) {
|
|
1974
1997
|
const { selected } = await exitOnCancel(
|
|
1975
1998
|
enquirer.prompt({
|
|
1976
1999
|
type: "multiselect",
|
|
1977
2000
|
name: "selected",
|
|
1978
|
-
message,
|
|
2001
|
+
message: message2,
|
|
1979
2002
|
choices: options2.map((opt) => ({
|
|
1980
2003
|
name: opt.value,
|
|
1981
2004
|
message: `${opt.name} - ${chalk4.dim(opt.description)}`
|
|
@@ -2217,12 +2240,12 @@ import chalk13 from "chalk";
|
|
|
2217
2240
|
|
|
2218
2241
|
// src/shared/promptConfirm.ts
|
|
2219
2242
|
import enquirer2 from "enquirer";
|
|
2220
|
-
async function promptConfirm(
|
|
2243
|
+
async function promptConfirm(message2, initial = true) {
|
|
2221
2244
|
const { confirmed } = await exitOnCancel(
|
|
2222
2245
|
enquirer2.prompt({
|
|
2223
2246
|
type: "confirm",
|
|
2224
2247
|
name: "confirmed",
|
|
2225
|
-
message,
|
|
2248
|
+
message: message2,
|
|
2226
2249
|
initial,
|
|
2227
2250
|
// @ts-expect-error - enquirer types don't include symbols but it's supported
|
|
2228
2251
|
symbols: {
|
|
@@ -4532,13 +4555,13 @@ function getSnoreToastPath() {
|
|
|
4532
4555
|
return path21.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
|
|
4533
4556
|
}
|
|
4534
4557
|
function showWindowsNotificationFromWsl(options2) {
|
|
4535
|
-
const { title, message, sound } = options2;
|
|
4558
|
+
const { title, message: message2, sound } = options2;
|
|
4536
4559
|
const snoreToastPath = getSnoreToastPath();
|
|
4537
4560
|
try {
|
|
4538
4561
|
fs16.chmodSync(snoreToastPath, 493);
|
|
4539
4562
|
} catch {
|
|
4540
4563
|
}
|
|
4541
|
-
const args = ["-t", title, "-m",
|
|
4564
|
+
const args = ["-t", title, "-m", message2];
|
|
4542
4565
|
if (sound) {
|
|
4543
4566
|
args.push("-s", "ms-winsoundevent:Notification.Default");
|
|
4544
4567
|
}
|
|
@@ -4552,14 +4575,14 @@ function showWindowsNotificationFromWsl(options2) {
|
|
|
4552
4575
|
|
|
4553
4576
|
// src/commands/notify/showNotification/index.ts
|
|
4554
4577
|
function showNotification(options2) {
|
|
4555
|
-
const { title, message, sound } = options2;
|
|
4578
|
+
const { title, message: message2, sound } = options2;
|
|
4556
4579
|
const platform = detectPlatform();
|
|
4557
4580
|
if (platform === "wsl") {
|
|
4558
|
-
return showWindowsNotificationFromWsl({ title, message, sound });
|
|
4581
|
+
return showWindowsNotificationFromWsl({ title, message: message2, sound });
|
|
4559
4582
|
}
|
|
4560
4583
|
const notificationOptions = {
|
|
4561
4584
|
title,
|
|
4562
|
-
message,
|
|
4585
|
+
message: message2,
|
|
4563
4586
|
wait: false
|
|
4564
4587
|
};
|
|
4565
4588
|
if (platform === "windows") {
|
|
@@ -4581,7 +4604,7 @@ async function notify() {
|
|
|
4581
4604
|
}
|
|
4582
4605
|
const inputData = await readStdin();
|
|
4583
4606
|
const data = JSON.parse(inputData);
|
|
4584
|
-
const { notification_type, cwd, message } = data;
|
|
4607
|
+
const { notification_type, cwd, message: message2 } = data;
|
|
4585
4608
|
const projectName = cwd?.split(/[/\\]/).pop() ?? "Unknown Project";
|
|
4586
4609
|
let title;
|
|
4587
4610
|
let body;
|
|
@@ -4589,7 +4612,7 @@ async function notify() {
|
|
|
4589
4612
|
switch (notification_type) {
|
|
4590
4613
|
case "permission_prompt":
|
|
4591
4614
|
title = "Claude needs permission";
|
|
4592
|
-
body = `${projectName} - ${
|
|
4615
|
+
body = `${projectName} - ${message2 || "Permission required"}`;
|
|
4593
4616
|
sound = "Alarm";
|
|
4594
4617
|
break;
|
|
4595
4618
|
case "idle_prompt":
|
|
@@ -4599,7 +4622,7 @@ async function notify() {
|
|
|
4599
4622
|
break;
|
|
4600
4623
|
default:
|
|
4601
4624
|
title = "Claude Code";
|
|
4602
|
-
body =
|
|
4625
|
+
body = message2 ? `${projectName} - ${message2}` : projectName;
|
|
4603
4626
|
sound = "Default";
|
|
4604
4627
|
}
|
|
4605
4628
|
showNotification({ title, message: body, sound });
|
|
@@ -4853,8 +4876,8 @@ function readScheduleBlock(crontab) {
|
|
|
4853
4876
|
}
|
|
4854
4877
|
|
|
4855
4878
|
// src/commands/backup/scheduleBackup.ts
|
|
4856
|
-
function fail(
|
|
4857
|
-
console.error(chalk29.red(
|
|
4879
|
+
function fail(message2) {
|
|
4880
|
+
console.error(chalk29.red(message2));
|
|
4858
4881
|
process.exit(1);
|
|
4859
4882
|
}
|
|
4860
4883
|
async function scheduleBackup({
|
|
@@ -5371,8 +5394,8 @@ function buildArgs(prompt, options2) {
|
|
|
5371
5394
|
|
|
5372
5395
|
// src/commands/sessions/daemon/appendDaemonLog.ts
|
|
5373
5396
|
import { appendFileSync } from "fs";
|
|
5374
|
-
function appendDaemonLog(
|
|
5375
|
-
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${
|
|
5397
|
+
function appendDaemonLog(message2) {
|
|
5398
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`;
|
|
5376
5399
|
try {
|
|
5377
5400
|
appendFileSync(daemonPaths.log, `${line}
|
|
5378
5401
|
`);
|
|
@@ -6330,10 +6353,10 @@ async function reconcileResumePhase(orm, item, startPhase, resumeSessionId) {
|
|
|
6330
6353
|
if (resumedPhaseIdx === void 0 || resumedPhaseIdx === startPhase) {
|
|
6331
6354
|
return startPhase;
|
|
6332
6355
|
}
|
|
6333
|
-
const
|
|
6334
|
-
appendDaemonLog(`backlog run ${item.id}: ${
|
|
6356
|
+
const message2 = `resume reconciliation: currentPhase implied phase ${startPhase + 1} but the resumed conversation ${resumeSessionId} ran phase ${resumedPhaseIdx + 1}; resuming phase ${resumedPhaseIdx + 1} to match the conversation`;
|
|
6357
|
+
appendDaemonLog(`backlog run ${item.id}: ${message2}`);
|
|
6335
6358
|
try {
|
|
6336
|
-
await appendComment(orm, item.id, `Resume reconciled \u2014 ${
|
|
6359
|
+
await appendComment(orm, item.id, `Resume reconciled \u2014 ${message2}`, {
|
|
6337
6360
|
phase: resumedPhaseIdx + 1
|
|
6338
6361
|
});
|
|
6339
6362
|
} catch {
|
|
@@ -6396,10 +6419,10 @@ async function awaitClaude(done2, context) {
|
|
|
6396
6419
|
try {
|
|
6397
6420
|
return await done2;
|
|
6398
6421
|
} catch (error) {
|
|
6399
|
-
const
|
|
6422
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6400
6423
|
console.error(
|
|
6401
6424
|
chalk39.red(`
|
|
6402
|
-
Failed to launch Claude for ${context}: ${
|
|
6425
|
+
Failed to launch Claude for ${context}: ${message2}`)
|
|
6403
6426
|
);
|
|
6404
6427
|
return CLAUDE_SPAWN_FAILED;
|
|
6405
6428
|
}
|
|
@@ -6425,7 +6448,7 @@ async function isDaemonRunning() {
|
|
|
6425
6448
|
|
|
6426
6449
|
// src/commands/sessions/daemon/sendToDaemon.ts
|
|
6427
6450
|
var WRITE_TIMEOUT_MS = 500;
|
|
6428
|
-
function sendToDaemon(
|
|
6451
|
+
function sendToDaemon(message2) {
|
|
6429
6452
|
return new Promise((resolve19, reject) => {
|
|
6430
6453
|
connectToDaemon().then((socket) => {
|
|
6431
6454
|
const timer = setTimeout(() => {
|
|
@@ -6436,7 +6459,7 @@ function sendToDaemon(message) {
|
|
|
6436
6459
|
clearTimeout(timer);
|
|
6437
6460
|
reject(error);
|
|
6438
6461
|
});
|
|
6439
|
-
socket.write(`${JSON.stringify(
|
|
6462
|
+
socket.write(`${JSON.stringify(message2)}
|
|
6440
6463
|
`, () => {
|
|
6441
6464
|
clearTimeout(timer);
|
|
6442
6465
|
socket.end();
|
|
@@ -6462,7 +6485,7 @@ function readSocketLines(socket, onLine) {
|
|
|
6462
6485
|
|
|
6463
6486
|
// src/commands/sessions/daemon/sendToDaemonAwaitAck.ts
|
|
6464
6487
|
var ACK_TIMEOUT_MS = 1e3;
|
|
6465
|
-
function sendToDaemonAwaitAck(
|
|
6488
|
+
function sendToDaemonAwaitAck(message2) {
|
|
6466
6489
|
return new Promise((resolve19, reject) => {
|
|
6467
6490
|
connectToDaemon().then((socket) => {
|
|
6468
6491
|
let settled = false;
|
|
@@ -6486,7 +6509,7 @@ function sendToDaemonAwaitAck(message) {
|
|
|
6486
6509
|
"close",
|
|
6487
6510
|
() => finish(new Error("daemon closed before acknowledging"))
|
|
6488
6511
|
);
|
|
6489
|
-
socket.write(`${JSON.stringify(
|
|
6512
|
+
socket.write(`${JSON.stringify(message2)}
|
|
6490
6513
|
`);
|
|
6491
6514
|
}, reject);
|
|
6492
6515
|
});
|
|
@@ -7223,13 +7246,13 @@ async function findSessionJsonlPath(sessionId) {
|
|
|
7223
7246
|
// src/commands/backlog/verifyResumeConversation.ts
|
|
7224
7247
|
async function verifyResumeConversation(itemId2, resumeSessionId, phaseLabel2) {
|
|
7225
7248
|
if (await findSessionJsonlPath(resumeSessionId)) return true;
|
|
7226
|
-
const
|
|
7249
|
+
const message2 = `${phaseLabel2}: resume found no conversation for session ${resumeSessionId}; phase not advanced`;
|
|
7227
7250
|
console.error(chalk42.red(`
|
|
7228
|
-
${
|
|
7229
|
-
appendDaemonLog(`backlog run ${itemId2}: ${
|
|
7251
|
+
${message2}`));
|
|
7252
|
+
appendDaemonLog(`backlog run ${itemId2}: ${message2}`);
|
|
7230
7253
|
try {
|
|
7231
7254
|
const { orm } = await getReady();
|
|
7232
|
-
await appendComment(orm, itemId2, `Resume failed \u2014 ${
|
|
7255
|
+
await appendComment(orm, itemId2, `Resume failed \u2014 ${message2}`);
|
|
7233
7256
|
} catch {
|
|
7234
7257
|
}
|
|
7235
7258
|
return false;
|
|
@@ -9569,8 +9592,8 @@ async function runRestartItem(item, { runRestartDaemon, reExec }) {
|
|
|
9569
9592
|
reExec();
|
|
9570
9593
|
}
|
|
9571
9594
|
} catch (error) {
|
|
9572
|
-
const
|
|
9573
|
-
console.error(chalk57.red(`Restart failed: ${
|
|
9595
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
9596
|
+
console.error(chalk57.red(`Restart failed: ${message2}`));
|
|
9574
9597
|
}
|
|
9575
9598
|
}
|
|
9576
9599
|
|
|
@@ -10120,8 +10143,8 @@ function originToSshUrl(origin) {
|
|
|
10120
10143
|
}
|
|
10121
10144
|
|
|
10122
10145
|
// src/commands/backlog/cloneRepo.ts
|
|
10123
|
-
function fail2(
|
|
10124
|
-
console.log(chalk66.red(
|
|
10146
|
+
function fail2(message2) {
|
|
10147
|
+
console.log(chalk66.red(message2));
|
|
10125
10148
|
process.exitCode = 1;
|
|
10126
10149
|
}
|
|
10127
10150
|
async function cloneRepo(originRaw) {
|
|
@@ -10777,8 +10800,8 @@ function validateLinkTarget(fromItem, fromNum, toNum, linkType) {
|
|
|
10777
10800
|
}
|
|
10778
10801
|
|
|
10779
10802
|
// src/commands/backlog/link.ts
|
|
10780
|
-
function fail3(
|
|
10781
|
-
console.log(chalk78.red(
|
|
10803
|
+
function fail3(message2) {
|
|
10804
|
+
console.log(chalk78.red(message2));
|
|
10782
10805
|
return void 0;
|
|
10783
10806
|
}
|
|
10784
10807
|
function parseLinkType(type) {
|
|
@@ -10900,8 +10923,8 @@ Pass the full origin.`
|
|
|
10900
10923
|
}
|
|
10901
10924
|
|
|
10902
10925
|
// src/commands/backlog/move-repo/index.ts
|
|
10903
|
-
function fail4(
|
|
10904
|
-
console.log(chalk81.red(
|
|
10926
|
+
function fail4(message2) {
|
|
10927
|
+
console.log(chalk81.red(message2));
|
|
10905
10928
|
process.exitCode = 1;
|
|
10906
10929
|
}
|
|
10907
10930
|
async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
|
|
@@ -11056,11 +11079,11 @@ async function handleLaunchSignal(slashCommand, once) {
|
|
|
11056
11079
|
await next({ allowEdits: true, once });
|
|
11057
11080
|
}
|
|
11058
11081
|
} catch (error) {
|
|
11059
|
-
const
|
|
11082
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
11060
11083
|
console.error(
|
|
11061
11084
|
chalk83.yellow(
|
|
11062
11085
|
`
|
|
11063
|
-
Could not complete post-run step (${
|
|
11086
|
+
Could not complete post-run step (${message2}).
|
|
11064
11087
|
This is usually a transient database/network blip \u2014 the work is saved and the session is safe to resume.`
|
|
11065
11088
|
)
|
|
11066
11089
|
);
|
|
@@ -13168,15 +13191,15 @@ function loadDenyConfig(global) {
|
|
|
13168
13191
|
}
|
|
13169
13192
|
|
|
13170
13193
|
// src/commands/deny/denyAdd.ts
|
|
13171
|
-
function denyAdd(pattern2,
|
|
13194
|
+
function denyAdd(pattern2, message2, options2) {
|
|
13172
13195
|
const { deny, saveDeny } = loadDenyConfig(options2.global);
|
|
13173
13196
|
if (deny.some((r) => r.pattern === pattern2)) {
|
|
13174
13197
|
console.log(chalk110.yellow(`Deny rule already exists for: ${pattern2}`));
|
|
13175
13198
|
return;
|
|
13176
13199
|
}
|
|
13177
|
-
deny.push({ pattern: pattern2, message });
|
|
13200
|
+
deny.push({ pattern: pattern2, message: message2 });
|
|
13178
13201
|
saveDeny(deny);
|
|
13179
|
-
console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${
|
|
13202
|
+
console.log(chalk110.green(`Added deny rule: ${pattern2} \u2192 ${message2}`));
|
|
13180
13203
|
}
|
|
13181
13204
|
|
|
13182
13205
|
// src/commands/deny/denyList.ts
|
|
@@ -14733,14 +14756,14 @@ function parseGitLogCommits(output, ignore3, afterDate) {
|
|
|
14733
14756
|
const commitsByDate = /* @__PURE__ */ new Map();
|
|
14734
14757
|
for (const line of lines) {
|
|
14735
14758
|
const [date, hash, ...messageParts] = line.split("|");
|
|
14736
|
-
const
|
|
14759
|
+
const message2 = messageParts.join("|");
|
|
14737
14760
|
if (afterDate && date <= afterDate) {
|
|
14738
14761
|
continue;
|
|
14739
14762
|
}
|
|
14740
14763
|
const files = getCommitFiles(hash);
|
|
14741
14764
|
if (!shouldIgnoreCommit(files, ignore3)) {
|
|
14742
14765
|
const existing = commitsByDate.get(date) || [];
|
|
14743
|
-
existing.push({ date, hash, message, files });
|
|
14766
|
+
existing.push({ date, hash, message: message2, files });
|
|
14744
14767
|
commitsByDate.set(date, existing);
|
|
14745
14768
|
}
|
|
14746
14769
|
}
|
|
@@ -16353,10 +16376,10 @@ function advisory(count7) {
|
|
|
16353
16376
|
const noun = count7 === 1 ? "handover" : "handovers";
|
|
16354
16377
|
return `${count7} unrecalled ${noun} for this repo. Run /recall to load.`;
|
|
16355
16378
|
}
|
|
16356
|
-
function emit2(
|
|
16379
|
+
function emit2(message2) {
|
|
16357
16380
|
const json = JSON.stringify({
|
|
16358
16381
|
hookSpecificOutput: { hookEventName: "SessionStart" },
|
|
16359
|
-
systemMessage:
|
|
16382
|
+
systemMessage: message2
|
|
16360
16383
|
});
|
|
16361
16384
|
console.log(json);
|
|
16362
16385
|
return json;
|
|
@@ -16540,11 +16563,11 @@ import { execSync as execSync39 } from "child_process";
|
|
|
16540
16563
|
// src/shared/promptInput.ts
|
|
16541
16564
|
import Enquirer from "enquirer";
|
|
16542
16565
|
var prompts = Enquirer;
|
|
16543
|
-
async function promptInput(name,
|
|
16544
|
-
return exitOnCancel(new prompts.Input({ name, message, initial }).run());
|
|
16566
|
+
async function promptInput(name, message2, initial) {
|
|
16567
|
+
return exitOnCancel(new prompts.Input({ name, message: message2, initial }).run());
|
|
16545
16568
|
}
|
|
16546
|
-
async function promptPassword(name,
|
|
16547
|
-
return exitOnCancel(new prompts.Password({ name, message }).run());
|
|
16569
|
+
async function promptPassword(name, message2) {
|
|
16570
|
+
return exitOnCancel(new prompts.Password({ name, message: message2 }).run());
|
|
16548
16571
|
}
|
|
16549
16572
|
|
|
16550
16573
|
// src/commands/jira/jiraAuth.ts
|
|
@@ -18021,9 +18044,9 @@ function deleteCommentsCache(prNumber) {
|
|
|
18021
18044
|
|
|
18022
18045
|
// src/commands/prs/replyToComment.ts
|
|
18023
18046
|
import { execSync as execSync41 } from "child_process";
|
|
18024
|
-
function replyToComment(org, repo, prNumber, commentId,
|
|
18047
|
+
function replyToComment(org, repo, prNumber, commentId, message2) {
|
|
18025
18048
|
execSync41(
|
|
18026
|
-
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${
|
|
18049
|
+
`gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message2.replace(/"/g, String.raw`\"`)}" -F in_reply_to=${commentId}`,
|
|
18027
18050
|
{ stdio: ["inherit", "pipe", "inherit"] }
|
|
18028
18051
|
);
|
|
18029
18052
|
}
|
|
@@ -18071,12 +18094,12 @@ function cleanupCacheIfDone(cache2, prNumber, commentId) {
|
|
|
18071
18094
|
);
|
|
18072
18095
|
if (!hasRemaining) deleteCommentsCache(prNumber);
|
|
18073
18096
|
}
|
|
18074
|
-
function resolveCommentWithReply(commentId,
|
|
18097
|
+
function resolveCommentWithReply(commentId, message2) {
|
|
18075
18098
|
const prNumber = getCurrentPrNumber();
|
|
18076
18099
|
const { org, repo } = getRepoInfo();
|
|
18077
18100
|
const cache2 = requireCache(prNumber);
|
|
18078
18101
|
const comment3 = requireLineComment(cache2, commentId);
|
|
18079
|
-
replyToComment(org, repo, prNumber, commentId,
|
|
18102
|
+
replyToComment(org, repo, prNumber, commentId, message2);
|
|
18080
18103
|
console.log("Reply posted successfully.");
|
|
18081
18104
|
resolveThread(comment3.threadId);
|
|
18082
18105
|
console.log("Thread resolved successfully.");
|
|
@@ -18099,9 +18122,9 @@ function fixed(commentId, sha) {
|
|
|
18099
18122
|
const fullSha = verifySha(sha);
|
|
18100
18123
|
const { org, repo } = getRepoInfo();
|
|
18101
18124
|
const repoUrl = `https://github.com/${org}/${repo}`;
|
|
18102
|
-
const
|
|
18125
|
+
const message2 = `Fixed in [${fullSha}](${repoUrl}/commit/${fullSha})`;
|
|
18103
18126
|
execSync43("git push", { stdio: "inherit" });
|
|
18104
|
-
resolveCommentWithReply(commentId,
|
|
18127
|
+
resolveCommentWithReply(commentId, message2);
|
|
18105
18128
|
} catch (error) {
|
|
18106
18129
|
if (isGhNotInstalled(error)) {
|
|
18107
18130
|
console.error("Error: GitHub CLI (gh) is not installed.");
|
|
@@ -19018,9 +19041,9 @@ function getItemFields(itemId2) {
|
|
|
19018
19041
|
|
|
19019
19042
|
// src/commands/ravendb/selectOpSecret.ts
|
|
19020
19043
|
var { Input, Select } = Enquirer2;
|
|
19021
|
-
async function selectOne(
|
|
19044
|
+
async function selectOne(message2, choices) {
|
|
19022
19045
|
if (choices.length === 1) return choices[0].value;
|
|
19023
|
-
const selected = await new Select({ name: "choice", message, choices }).run();
|
|
19046
|
+
const selected = await new Select({ name: "choice", message: message2, choices }).run();
|
|
19024
19047
|
return choices.find((c) => c.name === selected)?.value ?? selected;
|
|
19025
19048
|
}
|
|
19026
19049
|
async function selectOpSecret(searchTerm) {
|
|
@@ -20735,8 +20758,8 @@ function findRootParent(file, importedBy, visited) {
|
|
|
20735
20758
|
function clusterFiles(graph) {
|
|
20736
20759
|
const clusters = /* @__PURE__ */ new Map();
|
|
20737
20760
|
for (const file of graph.files) {
|
|
20738
|
-
const
|
|
20739
|
-
if (
|
|
20761
|
+
const basename17 = path52.basename(file, path52.extname(file));
|
|
20762
|
+
if (basename17 === "index") continue;
|
|
20740
20763
|
const importers = graph.importedBy.get(file);
|
|
20741
20764
|
if (!importers || importers.size !== 1) continue;
|
|
20742
20765
|
const parent = [...importers][0];
|
|
@@ -21470,9 +21493,9 @@ function postFindings(findings) {
|
|
|
21470
21493
|
posted++;
|
|
21471
21494
|
} catch (error) {
|
|
21472
21495
|
failed2++;
|
|
21473
|
-
const
|
|
21496
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
21474
21497
|
console.error(
|
|
21475
|
-
`Failed to post comment on ${finding.file}:${finding.line}: ${
|
|
21498
|
+
`Failed to post comment on ${finding.file}:${finding.line}: ${message2}`
|
|
21476
21499
|
);
|
|
21477
21500
|
}
|
|
21478
21501
|
}
|
|
@@ -21491,8 +21514,8 @@ function submitPendingReview(body) {
|
|
|
21491
21514
|
console.error("Error: GitHub CLI (gh) is not installed.");
|
|
21492
21515
|
return;
|
|
21493
21516
|
}
|
|
21494
|
-
const
|
|
21495
|
-
console.error(`Failed to submit review: ${
|
|
21517
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
21518
|
+
console.error(`Failed to submit review: ${message2}`);
|
|
21496
21519
|
}
|
|
21497
21520
|
}
|
|
21498
21521
|
|
|
@@ -22283,10 +22306,10 @@ function messageFor(err, command) {
|
|
|
22283
22306
|
return err.message || String(err);
|
|
22284
22307
|
}
|
|
22285
22308
|
function handleSpawnError(ctx, err) {
|
|
22286
|
-
const
|
|
22309
|
+
const message2 = messageFor(err, ctx.command);
|
|
22287
22310
|
const stderr = ctx.stderr ? `${ctx.stderr}
|
|
22288
|
-
${
|
|
22289
|
-
if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${
|
|
22311
|
+
${message2}` : message2;
|
|
22312
|
+
if (!ctx.quiet) console.error(`[${ctx.name}] failed: ${message2}`);
|
|
22290
22313
|
return {
|
|
22291
22314
|
exitCode: 127,
|
|
22292
22315
|
stderr,
|
|
@@ -25150,8 +25173,8 @@ import { createInterface as createInterface6 } from "readline";
|
|
|
25150
25173
|
var RING_CAPACITY = 1e3;
|
|
25151
25174
|
var ring = [];
|
|
25152
25175
|
var sink;
|
|
25153
|
-
function daemonLog(
|
|
25154
|
-
emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${
|
|
25176
|
+
function daemonLog(message2) {
|
|
25177
|
+
emit3(`${(/* @__PURE__ */ new Date()).toISOString()} [${process.pid}] ${message2}`);
|
|
25155
25178
|
}
|
|
25156
25179
|
function relayDaemonLog(line) {
|
|
25157
25180
|
emit3(`[windows] ${line}`);
|
|
@@ -25177,7 +25200,7 @@ var persistedSessionSchema = z5.object({
|
|
|
25177
25200
|
subtitle: z5.string().optional(),
|
|
25178
25201
|
commandType: z5.enum(["claude", "run", "assist"]),
|
|
25179
25202
|
harness: z5.enum(["claude", "codex", "pi"]).optional(),
|
|
25180
|
-
status: z5.enum(["running", "waiting", "done", "error"]).optional(),
|
|
25203
|
+
status: z5.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
|
|
25181
25204
|
cwd: z5.string(),
|
|
25182
25205
|
startedAt: z5.number(),
|
|
25183
25206
|
runningMs: z5.number().optional(),
|
|
@@ -25187,7 +25210,8 @@ var persistedSessionSchema = z5.object({
|
|
|
25187
25210
|
runArgs: z5.array(z5.string()).optional(),
|
|
25188
25211
|
assistArgs: z5.array(z5.string()).optional(),
|
|
25189
25212
|
activity: activitySchema.optional(),
|
|
25190
|
-
starred: z5.boolean().optional()
|
|
25213
|
+
starred: z5.boolean().optional(),
|
|
25214
|
+
undurable: z5.object({ reason: z5.string() }).optional()
|
|
25191
25215
|
});
|
|
25192
25216
|
|
|
25193
25217
|
// src/commands/sessions/daemon/toPersistedSession.ts
|
|
@@ -25208,7 +25232,8 @@ function toPersistedSession(session) {
|
|
|
25208
25232
|
runArgs: session.runArgs,
|
|
25209
25233
|
assistArgs: session.assistArgs,
|
|
25210
25234
|
activity: session.activity,
|
|
25211
|
-
starred: session.starred
|
|
25235
|
+
starred: session.starred,
|
|
25236
|
+
undurable: session.undurable
|
|
25212
25237
|
};
|
|
25213
25238
|
}
|
|
25214
25239
|
function accumulatedRunningMs(session) {
|
|
@@ -25230,7 +25255,7 @@ function savePersistedSessions(sessions) {
|
|
|
25230
25255
|
}
|
|
25231
25256
|
function persistLiveSessions(sessions) {
|
|
25232
25257
|
const live = [...sessions.values()].filter(
|
|
25233
|
-
(s) => s.pty && s.status !== "done"
|
|
25258
|
+
(s) => (s.pty || s.status === "stopped") && s.status !== "done"
|
|
25234
25259
|
);
|
|
25235
25260
|
savePersistedSessions(live.map(toPersistedSession));
|
|
25236
25261
|
logPersist(live);
|
|
@@ -25282,7 +25307,7 @@ function requestDrain(socket) {
|
|
|
25282
25307
|
}
|
|
25283
25308
|
|
|
25284
25309
|
// src/commands/sessions/daemon/runDaemon.ts
|
|
25285
|
-
import { mkdirSync as
|
|
25310
|
+
import { mkdirSync as mkdirSync27 } from "fs";
|
|
25286
25311
|
|
|
25287
25312
|
// src/commands/sessions/daemon/createAutoExit.ts
|
|
25288
25313
|
var DEFAULT_GRACE_MS = 6e4;
|
|
@@ -25385,7 +25410,8 @@ function toSessionInfo({
|
|
|
25385
25410
|
starred,
|
|
25386
25411
|
usedPct,
|
|
25387
25412
|
design,
|
|
25388
|
-
pendingPrPreview
|
|
25413
|
+
pendingPrPreview,
|
|
25414
|
+
undurable
|
|
25389
25415
|
}) {
|
|
25390
25416
|
return {
|
|
25391
25417
|
id,
|
|
@@ -25413,7 +25439,8 @@ function toSessionInfo({
|
|
|
25413
25439
|
starred,
|
|
25414
25440
|
usedPct,
|
|
25415
25441
|
design,
|
|
25416
|
-
pendingPrPreview
|
|
25442
|
+
pendingPrPreview,
|
|
25443
|
+
undurable
|
|
25417
25444
|
};
|
|
25418
25445
|
}
|
|
25419
25446
|
|
|
@@ -25503,20 +25530,56 @@ var ClientHub = class extends Set {
|
|
|
25503
25530
|
};
|
|
25504
25531
|
};
|
|
25505
25532
|
|
|
25533
|
+
// src/commands/sessions/daemon/createSession.ts
|
|
25534
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
25535
|
+
|
|
25536
|
+
// src/commands/sessions/daemon/resolveRunConfig.ts
|
|
25537
|
+
function resolveRunConfig(name, cwd) {
|
|
25538
|
+
try {
|
|
25539
|
+
const { run: run4 } = loadConfigFrom(cwd);
|
|
25540
|
+
const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
|
|
25541
|
+
const exact = configs.find((r) => r.name === name);
|
|
25542
|
+
if (exact) return exact;
|
|
25543
|
+
const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
|
|
25544
|
+
return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
|
|
25545
|
+
} catch {
|
|
25546
|
+
return void 0;
|
|
25547
|
+
}
|
|
25548
|
+
}
|
|
25549
|
+
|
|
25550
|
+
// src/commands/sessions/daemon/serverRunMeta.ts
|
|
25551
|
+
function serverRunMeta(runName, cwd) {
|
|
25552
|
+
const dir = cwd ?? process.cwd();
|
|
25553
|
+
const config = resolveRunConfig(runName, dir);
|
|
25554
|
+
if (!config?.server) return { server: false };
|
|
25555
|
+
return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
|
|
25556
|
+
}
|
|
25557
|
+
|
|
25558
|
+
// src/commands/sessions/daemon/readDesignSystemPrompt.ts
|
|
25559
|
+
import * as fs35 from "fs";
|
|
25560
|
+
import * as path58 from "path";
|
|
25561
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
25562
|
+
var __filename4 = fileURLToPath8(import.meta.url);
|
|
25563
|
+
var __dirname6 = path58.dirname(__filename4);
|
|
25564
|
+
function readDesignSystemPrompt() {
|
|
25565
|
+
const promptPath = path58.join(__dirname6, "..", "claude", "system-prompt.md");
|
|
25566
|
+
return fs35.readFileSync(promptPath, "utf8");
|
|
25567
|
+
}
|
|
25568
|
+
|
|
25506
25569
|
// src/commands/sessions/daemon/spawnPty.ts
|
|
25507
25570
|
import * as pty from "node-pty";
|
|
25508
25571
|
|
|
25509
25572
|
// src/commands/sessions/daemon/ensureSpawnHelperExecutable.ts
|
|
25510
25573
|
import { chmodSync, existsSync as existsSync57, statSync as statSync9 } from "fs";
|
|
25511
25574
|
import { createRequire as createRequire3 } from "module";
|
|
25512
|
-
import
|
|
25575
|
+
import path59 from "path";
|
|
25513
25576
|
var require4 = createRequire3(import.meta.url);
|
|
25514
25577
|
var ensured = false;
|
|
25515
25578
|
function ensureSpawnHelperExecutable() {
|
|
25516
25579
|
if (ensured || process.platform !== "darwin") return;
|
|
25517
25580
|
ensured = true;
|
|
25518
|
-
const ptyRoot =
|
|
25519
|
-
const helper =
|
|
25581
|
+
const ptyRoot = path59.join(path59.dirname(require4.resolve("node-pty")), "..");
|
|
25582
|
+
const helper = path59.join(
|
|
25520
25583
|
ptyRoot,
|
|
25521
25584
|
"prebuilds",
|
|
25522
25585
|
`${process.platform}-${process.arch}`,
|
|
@@ -25553,62 +25616,6 @@ function shellEscape(s) {
|
|
|
25553
25616
|
return `'${s.replace(/'/g, String.raw`'\''`)}'`;
|
|
25554
25617
|
}
|
|
25555
25618
|
|
|
25556
|
-
// src/commands/sessions/daemon/createAssistSession.ts
|
|
25557
|
-
function createAssistSession(id, assistArgs, cwd, meta) {
|
|
25558
|
-
const startedAt = Date.now();
|
|
25559
|
-
return {
|
|
25560
|
-
id,
|
|
25561
|
-
name: `assist ${assistArgs.join(" ")}`,
|
|
25562
|
-
title: meta?.title,
|
|
25563
|
-
subtitle: meta?.subtitle,
|
|
25564
|
-
commandType: "assist",
|
|
25565
|
-
status: "running",
|
|
25566
|
-
startedAt,
|
|
25567
|
-
runningMs: 0,
|
|
25568
|
-
runningSince: startedAt,
|
|
25569
|
-
pty: spawnPty(["assist", ...assistArgs], cwd, id),
|
|
25570
|
-
scrollback: "",
|
|
25571
|
-
assistArgs,
|
|
25572
|
-
cwd
|
|
25573
|
-
};
|
|
25574
|
-
}
|
|
25575
|
-
|
|
25576
|
-
// src/commands/sessions/daemon/createSession.ts
|
|
25577
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
25578
|
-
|
|
25579
|
-
// src/commands/sessions/daemon/resolveRunConfig.ts
|
|
25580
|
-
function resolveRunConfig(name, cwd) {
|
|
25581
|
-
try {
|
|
25582
|
-
const { run: run4 } = loadConfigFrom(cwd);
|
|
25583
|
-
const configs = resolveRunConfigs(run4, getConfigDirFrom(cwd));
|
|
25584
|
-
const exact = configs.find((r) => r.name === name);
|
|
25585
|
-
if (exact) return exact;
|
|
25586
|
-
const suffixMatches = configs.filter((r) => r.name.endsWith(`:${name}`));
|
|
25587
|
-
return suffixMatches.length === 1 ? suffixMatches[0] : void 0;
|
|
25588
|
-
} catch {
|
|
25589
|
-
return void 0;
|
|
25590
|
-
}
|
|
25591
|
-
}
|
|
25592
|
-
|
|
25593
|
-
// src/commands/sessions/daemon/serverRunMeta.ts
|
|
25594
|
-
function serverRunMeta(runName, cwd) {
|
|
25595
|
-
const dir = cwd ?? process.cwd();
|
|
25596
|
-
const config = resolveRunConfig(runName, dir);
|
|
25597
|
-
if (!config?.server) return { server: false };
|
|
25598
|
-
return { server: true, port: config.port, origin: getCurrentOrigin(dir) };
|
|
25599
|
-
}
|
|
25600
|
-
|
|
25601
|
-
// src/commands/sessions/daemon/readDesignSystemPrompt.ts
|
|
25602
|
-
import * as fs35 from "fs";
|
|
25603
|
-
import * as path59 from "path";
|
|
25604
|
-
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
25605
|
-
var __filename4 = fileURLToPath8(import.meta.url);
|
|
25606
|
-
var __dirname6 = path59.dirname(__filename4);
|
|
25607
|
-
function readDesignSystemPrompt() {
|
|
25608
|
-
const promptPath = path59.join(__dirname6, "..", "claude", "system-prompt.md");
|
|
25609
|
-
return fs35.readFileSync(promptPath, "utf8");
|
|
25610
|
-
}
|
|
25611
|
-
|
|
25612
25619
|
// src/commands/sessions/daemon/spawnClaude.ts
|
|
25613
25620
|
function spawnClaude2(opts = {}) {
|
|
25614
25621
|
return spawnPty(buildArgs3(opts), opts.cwd, opts.sessionId);
|
|
@@ -25693,6 +25700,170 @@ function createRunSession(id, runName, runArgs, cwd, meta = serverRunMeta(runNam
|
|
|
25693
25700
|
};
|
|
25694
25701
|
}
|
|
25695
25702
|
|
|
25703
|
+
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
25704
|
+
import { existsSync as existsSync58 } from "fs";
|
|
25705
|
+
|
|
25706
|
+
// src/commands/sessions/daemon/worktree/git.ts
|
|
25707
|
+
import { execFile as execFile7, execFileSync as execFileSync12 } from "child_process";
|
|
25708
|
+
function isWindowsPath(cwd) {
|
|
25709
|
+
return /^[A-Za-z]:[\\/]/.test(cwd);
|
|
25710
|
+
}
|
|
25711
|
+
function gitInvocation(cwd, args) {
|
|
25712
|
+
const windows = isWindowsPath(cwd);
|
|
25713
|
+
return {
|
|
25714
|
+
file: windows ? "git.exe" : "git",
|
|
25715
|
+
argv: windows ? ["-C", cwd, ...args] : args,
|
|
25716
|
+
options: { encoding: "utf8", ...windows ? {} : { cwd } }
|
|
25717
|
+
};
|
|
25718
|
+
}
|
|
25719
|
+
function git3(cwd, args) {
|
|
25720
|
+
const { file, argv, options: options2 } = gitInvocation(cwd, args);
|
|
25721
|
+
return new Promise((resolve19, reject) => {
|
|
25722
|
+
execFile7(file, argv, options2, (error, stdout) => {
|
|
25723
|
+
if (error) reject(error);
|
|
25724
|
+
else resolve19(stdout.toString());
|
|
25725
|
+
});
|
|
25726
|
+
});
|
|
25727
|
+
}
|
|
25728
|
+
async function gitOrNull(cwd, args) {
|
|
25729
|
+
try {
|
|
25730
|
+
return (await git3(cwd, args)).trim() || null;
|
|
25731
|
+
} catch {
|
|
25732
|
+
return null;
|
|
25733
|
+
}
|
|
25734
|
+
}
|
|
25735
|
+
function gitSync(cwd, args) {
|
|
25736
|
+
const { file, argv, options: options2 } = gitInvocation(cwd, args);
|
|
25737
|
+
return execFileSync12(file, argv, {
|
|
25738
|
+
...options2,
|
|
25739
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25740
|
+
}).toString();
|
|
25741
|
+
}
|
|
25742
|
+
function gitSyncOrNull(cwd, args) {
|
|
25743
|
+
try {
|
|
25744
|
+
return gitSync(cwd, args).trim() || null;
|
|
25745
|
+
} catch {
|
|
25746
|
+
return null;
|
|
25747
|
+
}
|
|
25748
|
+
}
|
|
25749
|
+
function listWorktreePaths(cwd) {
|
|
25750
|
+
const out = gitSyncOrNull(cwd, ["worktree", "list", "--porcelain"]);
|
|
25751
|
+
if (!out) return [];
|
|
25752
|
+
return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice("worktree ".length).trim());
|
|
25753
|
+
}
|
|
25754
|
+
function mainWorktree(cwd) {
|
|
25755
|
+
return listWorktreePaths(cwd)[0] ?? null;
|
|
25756
|
+
}
|
|
25757
|
+
function listLocalBranches(cwd) {
|
|
25758
|
+
const out = gitSyncOrNull(cwd, [
|
|
25759
|
+
"for-each-ref",
|
|
25760
|
+
"--format=%(refname:short)",
|
|
25761
|
+
"refs/heads"
|
|
25762
|
+
]);
|
|
25763
|
+
return out ? out.split("\n").map((line) => line.trim()) : [];
|
|
25764
|
+
}
|
|
25765
|
+
function gitCommonDir(cwd) {
|
|
25766
|
+
return gitSyncOrNull(cwd, [
|
|
25767
|
+
"rev-parse",
|
|
25768
|
+
"--path-format=absolute",
|
|
25769
|
+
"--git-common-dir"
|
|
25770
|
+
]);
|
|
25771
|
+
}
|
|
25772
|
+
|
|
25773
|
+
// src/commands/sessions/daemon/worktree/treeDurability.ts
|
|
25774
|
+
function treeDurability(state) {
|
|
25775
|
+
if (state.dirty) return { durable: false, reason: "uncommitted changes" };
|
|
25776
|
+
if (!state.push && state.localOnlyCommits)
|
|
25777
|
+
return { durable: false, reason: "unpushed commits" };
|
|
25778
|
+
return { durable: true };
|
|
25779
|
+
}
|
|
25780
|
+
async function hasLocalOnlyCommits(cwd) {
|
|
25781
|
+
const upstream = await gitOrNull(cwd, [
|
|
25782
|
+
"rev-parse",
|
|
25783
|
+
"--abbrev-ref",
|
|
25784
|
+
"--symbolic-full-name",
|
|
25785
|
+
"@{upstream}"
|
|
25786
|
+
]);
|
|
25787
|
+
if (upstream) {
|
|
25788
|
+
const ahead = await gitOrNull(cwd, [
|
|
25789
|
+
"rev-list",
|
|
25790
|
+
"--count",
|
|
25791
|
+
"@{upstream}..HEAD"
|
|
25792
|
+
]);
|
|
25793
|
+
return Number(ahead ?? "0") > 0;
|
|
25794
|
+
}
|
|
25795
|
+
const remoteBranches = await gitOrNull(cwd, [
|
|
25796
|
+
"branch",
|
|
25797
|
+
"-r",
|
|
25798
|
+
"--contains",
|
|
25799
|
+
"HEAD"
|
|
25800
|
+
]);
|
|
25801
|
+
return !remoteBranches;
|
|
25802
|
+
}
|
|
25803
|
+
async function collectTreeState(cwd, push) {
|
|
25804
|
+
const status3 = await gitOrNull(cwd, ["status", "--porcelain"]);
|
|
25805
|
+
return {
|
|
25806
|
+
dirty: status3 !== null,
|
|
25807
|
+
push,
|
|
25808
|
+
localOnlyCommits: await hasLocalOnlyCommits(cwd)
|
|
25809
|
+
};
|
|
25810
|
+
}
|
|
25811
|
+
function pushOnCommit(cwd) {
|
|
25812
|
+
try {
|
|
25813
|
+
return loadConfigFrom(cwd).commit.push;
|
|
25814
|
+
} catch {
|
|
25815
|
+
return false;
|
|
25816
|
+
}
|
|
25817
|
+
}
|
|
25818
|
+
async function checkDurability(cwd) {
|
|
25819
|
+
return treeDurability(await collectTreeState(cwd, pushOnCommit(cwd)));
|
|
25820
|
+
}
|
|
25821
|
+
|
|
25822
|
+
// src/commands/sessions/daemon/worktree/reapWorktree.ts
|
|
25823
|
+
async function reapWorktree(worktreePath, force = false) {
|
|
25824
|
+
if (!existsSync58(worktreePath)) {
|
|
25825
|
+
daemonLog(`worktree ${worktreePath} already gone; skipping reap`);
|
|
25826
|
+
return false;
|
|
25827
|
+
}
|
|
25828
|
+
if (!force) {
|
|
25829
|
+
const durability = await checkDurability(worktreePath);
|
|
25830
|
+
if (!durability.durable) {
|
|
25831
|
+
daemonLog(`worktree ${worktreePath} not reaped: ${durability.reason}`);
|
|
25832
|
+
return false;
|
|
25833
|
+
}
|
|
25834
|
+
}
|
|
25835
|
+
const clone = mainWorktree(worktreePath) ?? worktreePath;
|
|
25836
|
+
const branch2 = await gitOrNull(worktreePath, [
|
|
25837
|
+
"symbolic-ref",
|
|
25838
|
+
"--short",
|
|
25839
|
+
"HEAD"
|
|
25840
|
+
]);
|
|
25841
|
+
try {
|
|
25842
|
+
await git3(clone, [
|
|
25843
|
+
"worktree",
|
|
25844
|
+
"remove",
|
|
25845
|
+
...force ? ["--force"] : [],
|
|
25846
|
+
worktreePath
|
|
25847
|
+
]);
|
|
25848
|
+
if (branch2) await deleteBranch(clone, branch2);
|
|
25849
|
+
daemonLog(`worktree ${worktreePath} reaped${force ? " (forced)" : ""}`);
|
|
25850
|
+
return true;
|
|
25851
|
+
} catch (error) {
|
|
25852
|
+
daemonLog(
|
|
25853
|
+
`worktree ${worktreePath} reap failed: ${error instanceof Error ? error.message : String(error)}`
|
|
25854
|
+
);
|
|
25855
|
+
return false;
|
|
25856
|
+
}
|
|
25857
|
+
}
|
|
25858
|
+
async function deleteBranch(clone, branch2) {
|
|
25859
|
+
const current = await gitOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
|
|
25860
|
+
if (branch2 === current) return;
|
|
25861
|
+
try {
|
|
25862
|
+
await git3(clone, ["branch", "-D", branch2]);
|
|
25863
|
+
} catch {
|
|
25864
|
+
}
|
|
25865
|
+
}
|
|
25866
|
+
|
|
25696
25867
|
// src/commands/sessions/daemon/dismissSession.ts
|
|
25697
25868
|
function dismissSession(sessions, id) {
|
|
25698
25869
|
const s = sessions.get(id);
|
|
@@ -25700,10 +25871,12 @@ function dismissSession(sessions, id) {
|
|
|
25700
25871
|
if (s.status !== "done") s.pty?.kill();
|
|
25701
25872
|
s.activityWatcher?.close();
|
|
25702
25873
|
s.transcriptWatcher?.close();
|
|
25874
|
+
s.gitWatcher?.close();
|
|
25703
25875
|
removeActivity(s.id);
|
|
25704
25876
|
if (s.activity?.itemId != null) releaseLock(s.activity.itemId);
|
|
25705
25877
|
sessions.delete(id);
|
|
25706
25878
|
daemonLog(`session ${id} dismissed (${s.name})`);
|
|
25879
|
+
if (s.worktree) void reapWorktree(s.worktree.path);
|
|
25707
25880
|
return true;
|
|
25708
25881
|
}
|
|
25709
25882
|
function drainSessions(sessions, onDrained) {
|
|
@@ -25717,6 +25890,113 @@ function drainSessions(sessions, onDrained) {
|
|
|
25717
25890
|
return ids.length;
|
|
25718
25891
|
}
|
|
25719
25892
|
|
|
25893
|
+
// src/commands/sessions/daemon/killPtyTree.ts
|
|
25894
|
+
function killPtyTree(pty2) {
|
|
25895
|
+
if (process.platform === "win32") {
|
|
25896
|
+
pty2.kill();
|
|
25897
|
+
return;
|
|
25898
|
+
}
|
|
25899
|
+
try {
|
|
25900
|
+
process.kill(-pty2.pid, "SIGHUP");
|
|
25901
|
+
} catch {
|
|
25902
|
+
try {
|
|
25903
|
+
pty2.kill();
|
|
25904
|
+
} catch {
|
|
25905
|
+
}
|
|
25906
|
+
}
|
|
25907
|
+
}
|
|
25908
|
+
|
|
25909
|
+
// src/commands/sessions/daemon/setStatus.ts
|
|
25910
|
+
function setStatus2(session, newStatus) {
|
|
25911
|
+
const now = Date.now();
|
|
25912
|
+
if (session.status === "running" && session.runningSince != null) {
|
|
25913
|
+
session.runningMs += now - session.runningSince;
|
|
25914
|
+
}
|
|
25915
|
+
session.runningSince = newStatus === "running" ? now : null;
|
|
25916
|
+
session.status = newStatus;
|
|
25917
|
+
}
|
|
25918
|
+
|
|
25919
|
+
// src/commands/sessions/daemon/worktree/watchGitState.ts
|
|
25920
|
+
import { existsSync as existsSync59, watch } from "fs";
|
|
25921
|
+
var DEBOUNCE_MS = 500;
|
|
25922
|
+
function watchGitState(cwd, onChange) {
|
|
25923
|
+
const common = gitCommonDir(cwd);
|
|
25924
|
+
if (!common || !existsSync59(common)) return void 0;
|
|
25925
|
+
let timer = null;
|
|
25926
|
+
try {
|
|
25927
|
+
return watch(common, { recursive: true }, () => {
|
|
25928
|
+
if (timer) clearTimeout(timer);
|
|
25929
|
+
timer = setTimeout(onChange, DEBOUNCE_MS);
|
|
25930
|
+
});
|
|
25931
|
+
} catch (error) {
|
|
25932
|
+
daemonLog(
|
|
25933
|
+
`git-state watch failed for ${cwd}: ${error instanceof Error ? error.message : String(error)}`
|
|
25934
|
+
);
|
|
25935
|
+
return void 0;
|
|
25936
|
+
}
|
|
25937
|
+
}
|
|
25938
|
+
|
|
25939
|
+
// src/commands/sessions/daemon/worktree/resolveDoneDurability.ts
|
|
25940
|
+
async function resolveDoneDurability(session, finalize, notify2) {
|
|
25941
|
+
const worktree = session.worktree;
|
|
25942
|
+
if (!worktree) {
|
|
25943
|
+
finalize();
|
|
25944
|
+
return;
|
|
25945
|
+
}
|
|
25946
|
+
const durability = await checkDurability(worktree.path);
|
|
25947
|
+
if (!durability.durable) {
|
|
25948
|
+
holdStopped(session, durability.reason, finalize, notify2);
|
|
25949
|
+
return;
|
|
25950
|
+
}
|
|
25951
|
+
await reapWorktree(worktree.path);
|
|
25952
|
+
session.worktree = void 0;
|
|
25953
|
+
session.undurable = void 0;
|
|
25954
|
+
session.gitWatcher?.close();
|
|
25955
|
+
session.gitWatcher = void 0;
|
|
25956
|
+
finalize();
|
|
25957
|
+
}
|
|
25958
|
+
function holdStopped(session, reason, finalize, notify2) {
|
|
25959
|
+
if (session.undurable?.reason !== reason)
|
|
25960
|
+
daemonLog(`session ${session.id} stopped; reap blocked: ${reason}`);
|
|
25961
|
+
session.undurable = { reason };
|
|
25962
|
+
setStatus2(session, "stopped");
|
|
25963
|
+
if (!session.gitWatcher && session.worktree)
|
|
25964
|
+
session.gitWatcher = watchGitState(session.worktree.path, () => {
|
|
25965
|
+
if (session.status !== "stopped") return;
|
|
25966
|
+
void resolveDoneDurability(session, finalize, notify2);
|
|
25967
|
+
});
|
|
25968
|
+
notify2();
|
|
25969
|
+
}
|
|
25970
|
+
|
|
25971
|
+
// src/commands/sessions/daemon/dismissSessionGated.ts
|
|
25972
|
+
function dismissSessionGated(sessions, id, notify2, discard = false) {
|
|
25973
|
+
const s = sessions.get(id);
|
|
25974
|
+
if (!s?.worktree) {
|
|
25975
|
+
if (dismissSession(sessions, id)) notify2();
|
|
25976
|
+
return;
|
|
25977
|
+
}
|
|
25978
|
+
const removeCard = () => {
|
|
25979
|
+
if (dismissSession(sessions, id)) notify2();
|
|
25980
|
+
};
|
|
25981
|
+
const discardTree = async () => {
|
|
25982
|
+
if (s.worktree) {
|
|
25983
|
+
await reapWorktree(s.worktree.path, true);
|
|
25984
|
+
s.worktree = void 0;
|
|
25985
|
+
}
|
|
25986
|
+
removeCard();
|
|
25987
|
+
};
|
|
25988
|
+
if (s.pty) {
|
|
25989
|
+
s.pendingDismiss = discard ? () => void discardTree() : () => void resolveDoneDurability(s, removeCard, notify2);
|
|
25990
|
+
daemonLog(
|
|
25991
|
+
`session ${id} ${discard ? "discard" : "dismiss"} requested: killing process tree first`
|
|
25992
|
+
);
|
|
25993
|
+
killPtyTree(s.pty);
|
|
25994
|
+
return;
|
|
25995
|
+
}
|
|
25996
|
+
if (discard) void discardTree();
|
|
25997
|
+
else void resolveDoneDurability(s, removeCard, notify2);
|
|
25998
|
+
}
|
|
25999
|
+
|
|
25720
26000
|
// src/shared/db/recordPhaseActiveMs.ts
|
|
25721
26001
|
import { sql as sql20 } from "drizzle-orm";
|
|
25722
26002
|
async function recordPhaseActiveMs(db, itemId2, phaseIdx, activeMs) {
|
|
@@ -25897,6 +26177,12 @@ function applySetStatus(sessions, id, status3, source, onStatusChange) {
|
|
|
25897
26177
|
);
|
|
25898
26178
|
return;
|
|
25899
26179
|
}
|
|
26180
|
+
if (session.status === "stopped") {
|
|
26181
|
+
daemonLog(
|
|
26182
|
+
`set-status for stopped session id=${id} status=${status3} (held pending durable close; ignoring)`
|
|
26183
|
+
);
|
|
26184
|
+
return;
|
|
26185
|
+
}
|
|
25900
26186
|
if (source === "permission") session.permissionActive = true;
|
|
25901
26187
|
if (status3 === "running") session.permissionActive = false;
|
|
25902
26188
|
onStatusChange(session, status3);
|
|
@@ -26070,16 +26356,6 @@ function applyUsageRecord(sessions, windows, notify2, claudeSessionId, transcrip
|
|
|
26070
26356
|
notify2();
|
|
26071
26357
|
}
|
|
26072
26358
|
|
|
26073
|
-
// src/commands/sessions/daemon/setStatus.ts
|
|
26074
|
-
function setStatus2(session, newStatus) {
|
|
26075
|
-
const now = Date.now();
|
|
26076
|
-
if (session.status === "running" && session.runningSince != null) {
|
|
26077
|
-
session.runningMs += now - session.runningSince;
|
|
26078
|
-
}
|
|
26079
|
-
session.runningSince = newStatus === "running" ? now : null;
|
|
26080
|
-
session.status = newStatus;
|
|
26081
|
-
}
|
|
26082
|
-
|
|
26083
26359
|
// src/commands/sessions/daemon/shouldAutoDismiss.ts
|
|
26084
26360
|
function shouldAutoDismiss(session, exitCode) {
|
|
26085
26361
|
if (session.status !== "done" || exitCode !== 0) {
|
|
@@ -26125,13 +26401,26 @@ function shouldAutoRun(session) {
|
|
|
26125
26401
|
// src/commands/sessions/daemon/applyStatusChange.ts
|
|
26126
26402
|
function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseForRun) {
|
|
26127
26403
|
if (session.status === status3) return;
|
|
26404
|
+
const deps2 = { dismiss, notify: notify2, reuseForRun };
|
|
26405
|
+
if (status3 === "done" && session.worktree) {
|
|
26406
|
+
void resolveDoneDurability(
|
|
26407
|
+
session,
|
|
26408
|
+
() => finishStatusChange(session, "done", exitCode, deps2),
|
|
26409
|
+
notify2
|
|
26410
|
+
);
|
|
26411
|
+
return;
|
|
26412
|
+
}
|
|
26413
|
+
finishStatusChange(session, status3, exitCode, deps2);
|
|
26414
|
+
}
|
|
26415
|
+
function finishStatusChange(session, status3, exitCode, deps2) {
|
|
26128
26416
|
daemonLog(`session ${session.id} status: ${session.status} -> ${status3}`);
|
|
26129
26417
|
void flushPhaseActiveMs(session);
|
|
26130
26418
|
setStatus2(session, status3);
|
|
26419
|
+
if (status3 !== "waiting") session.undurable = void 0;
|
|
26131
26420
|
const autoRun = shouldAutoRun(session);
|
|
26132
26421
|
if (autoRun.run) {
|
|
26133
|
-
reuseForRun(session, autoRun.itemId);
|
|
26134
|
-
|
|
26422
|
+
deps2.reuseForRun(session, autoRun.itemId);
|
|
26423
|
+
deps2.notify();
|
|
26135
26424
|
return;
|
|
26136
26425
|
}
|
|
26137
26426
|
if (status3 === "done" && autoRun.reason != null) {
|
|
@@ -26139,8 +26428,8 @@ function applyStatusChange(session, status3, exitCode, dismiss, notify2, reuseFo
|
|
|
26139
26428
|
`session ${session.id} auto-run enabled but skipped (exit ${exitCode ?? "none"}): ${autoRun.reason}`
|
|
26140
26429
|
);
|
|
26141
26430
|
}
|
|
26142
|
-
if (shouldAutoDismiss(session, exitCode)) dismiss(session.id);
|
|
26143
|
-
else
|
|
26431
|
+
if (shouldAutoDismiss(session, exitCode)) deps2.dismiss(session.id);
|
|
26432
|
+
else deps2.notify();
|
|
26144
26433
|
}
|
|
26145
26434
|
|
|
26146
26435
|
// src/commands/sessions/daemon/makeStatusChangeHandler.ts
|
|
@@ -26161,7 +26450,7 @@ function handleFailedResume(session, exitCode, onStatusChange) {
|
|
|
26161
26450
|
}
|
|
26162
26451
|
|
|
26163
26452
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
26164
|
-
import { existsSync as
|
|
26453
|
+
import { existsSync as existsSync60, mkdirSync as mkdirSync25, watch as watch2 } from "fs";
|
|
26165
26454
|
import { dirname as dirname29 } from "path";
|
|
26166
26455
|
|
|
26167
26456
|
// src/commands/sessions/daemon/applyReviewPause.ts
|
|
@@ -26212,7 +26501,7 @@ async function seedRunningMsFromUsage(session, notify2) {
|
|
|
26212
26501
|
}
|
|
26213
26502
|
|
|
26214
26503
|
// src/commands/sessions/daemon/watchActivity.ts
|
|
26215
|
-
var
|
|
26504
|
+
var DEBOUNCE_MS2 = 50;
|
|
26216
26505
|
function watchActivity(session, notify2, onClaudeSessionId) {
|
|
26217
26506
|
if (session.commandType !== "assist" || !session.cwd) return;
|
|
26218
26507
|
const path71 = activityPath(session.id);
|
|
@@ -26237,12 +26526,12 @@ function watchActivity(session, notify2, onClaudeSessionId) {
|
|
|
26237
26526
|
void seedRunningMsFromUsage(session, notify2);
|
|
26238
26527
|
notify2();
|
|
26239
26528
|
};
|
|
26240
|
-
session.activityWatcher =
|
|
26529
|
+
session.activityWatcher = watch2(dir, (_event, filename) => {
|
|
26241
26530
|
if (filename && !path71.endsWith(filename)) return;
|
|
26242
26531
|
if (timer) clearTimeout(timer);
|
|
26243
|
-
timer = setTimeout(read,
|
|
26532
|
+
timer = setTimeout(read, DEBOUNCE_MS2);
|
|
26244
26533
|
});
|
|
26245
|
-
if (
|
|
26534
|
+
if (existsSync60(path71)) read();
|
|
26246
26535
|
}
|
|
26247
26536
|
function refreshActivity(session) {
|
|
26248
26537
|
if (session.commandType !== "assist" || !session.cwd) return;
|
|
@@ -26277,6 +26566,15 @@ function handlePtyExit(session, exitCode, onStatusChange) {
|
|
|
26277
26566
|
return;
|
|
26278
26567
|
}
|
|
26279
26568
|
session.pty = null;
|
|
26569
|
+
if (session.pendingDismiss) {
|
|
26570
|
+
const dismiss = session.pendingDismiss;
|
|
26571
|
+
session.pendingDismiss = void 0;
|
|
26572
|
+
daemonLog(
|
|
26573
|
+
`session ${session.id} ("${session.name}") pty exited (code ${exitCode}) for dismiss; resolving durability now the process is gone`
|
|
26574
|
+
);
|
|
26575
|
+
dismiss();
|
|
26576
|
+
return;
|
|
26577
|
+
}
|
|
26280
26578
|
if (handleStoppedExit(session, exitCode, onStatusChange)) return;
|
|
26281
26579
|
refreshActivity(session);
|
|
26282
26580
|
if (handleFailedResume(session, exitCode, onStatusChange)) return;
|
|
@@ -26317,6 +26615,10 @@ function wirePtyEvents(session, clients, onStatusChange) {
|
|
|
26317
26615
|
|
|
26318
26616
|
// src/commands/sessions/daemon/respawnSession.ts
|
|
26319
26617
|
function respawnSession(session, respawn, status3, clients, onStatusChange) {
|
|
26618
|
+
session.gitWatcher?.close();
|
|
26619
|
+
session.gitWatcher = void 0;
|
|
26620
|
+
session.undurable = void 0;
|
|
26621
|
+
session.pendingDismiss = void 0;
|
|
26320
26622
|
session.scrollback = "";
|
|
26321
26623
|
session.startedAt = Date.now();
|
|
26322
26624
|
session.runningMs = 0;
|
|
@@ -26324,6 +26626,11 @@ function respawnSession(session, respawn, status3, clients, onStatusChange) {
|
|
|
26324
26626
|
setStatus2(session, status3);
|
|
26325
26627
|
session.restored = void 0;
|
|
26326
26628
|
session.pty = respawn();
|
|
26629
|
+
if (session.cols && session.rows)
|
|
26630
|
+
try {
|
|
26631
|
+
session.pty?.resize(session.cols, session.rows);
|
|
26632
|
+
} catch {
|
|
26633
|
+
}
|
|
26327
26634
|
broadcast(clients, { type: "clear", sessionId: session.id });
|
|
26328
26635
|
wirePtyEvents(session, clients, onStatusChange);
|
|
26329
26636
|
}
|
|
@@ -26405,20 +26712,6 @@ function restartSession(session, clients, onStatusChange) {
|
|
|
26405
26712
|
}
|
|
26406
26713
|
return true;
|
|
26407
26714
|
}
|
|
26408
|
-
function killPtyTree(pty2) {
|
|
26409
|
-
if (process.platform === "win32") {
|
|
26410
|
-
pty2.kill();
|
|
26411
|
-
return;
|
|
26412
|
-
}
|
|
26413
|
-
try {
|
|
26414
|
-
process.kill(-pty2.pid, "SIGHUP");
|
|
26415
|
-
} catch {
|
|
26416
|
-
try {
|
|
26417
|
-
pty2.kill();
|
|
26418
|
-
} catch {
|
|
26419
|
-
}
|
|
26420
|
-
}
|
|
26421
|
-
}
|
|
26422
26715
|
|
|
26423
26716
|
// src/commands/sessions/daemon/restartManagedSession.ts
|
|
26424
26717
|
function restartManagedSession(sessions, id, clients, onStatusChange) {
|
|
@@ -26519,9 +26812,9 @@ function normalizeEntry(entry) {
|
|
|
26519
26812
|
return null;
|
|
26520
26813
|
}
|
|
26521
26814
|
function normalizeAssistant(entry) {
|
|
26522
|
-
const
|
|
26523
|
-
const stopReason = typeof
|
|
26524
|
-
const content =
|
|
26815
|
+
const message2 = asRecord2(entry.message);
|
|
26816
|
+
const stopReason = typeof message2?.stop_reason === "string" ? message2.stop_reason : null;
|
|
26817
|
+
const content = message2?.content;
|
|
26525
26818
|
const toolUses = [];
|
|
26526
26819
|
if (Array.isArray(content))
|
|
26527
26820
|
for (const block of content) {
|
|
@@ -26699,6 +26992,12 @@ function runningSession(base, persisted, pty2) {
|
|
|
26699
26992
|
function waitingSession(base, persisted, pty2) {
|
|
26700
26993
|
return restoredSession(base, persisted, pty2, "waiting");
|
|
26701
26994
|
}
|
|
26995
|
+
function stoppedSession(base, persisted) {
|
|
26996
|
+
return {
|
|
26997
|
+
...restoredSession(base, persisted, null, "stopped"),
|
|
26998
|
+
undurable: persisted.undurable
|
|
26999
|
+
};
|
|
27000
|
+
}
|
|
26702
27001
|
function restoredSession(base, persisted, pty2, status3) {
|
|
26703
27002
|
const startedAt = Date.now();
|
|
26704
27003
|
return {
|
|
@@ -26781,6 +27080,7 @@ function isUpdate(persisted) {
|
|
|
26781
27080
|
// src/commands/sessions/daemon/restoreSession.ts
|
|
26782
27081
|
function restoreSession(id, persisted) {
|
|
26783
27082
|
const base = restoreBase(id, persisted);
|
|
27083
|
+
if (persisted.status === "stopped") return stoppedSession(base, persisted);
|
|
26784
27084
|
if (isUpdate(persisted)) return updatedSession(id, persisted);
|
|
26785
27085
|
const idle = deriveRestoreStatus(persisted) !== "running";
|
|
26786
27086
|
if (needsWrapperRelaunch(persisted)) {
|
|
@@ -26862,27 +27162,6 @@ function restoreAll(spawn12, sessions) {
|
|
|
26862
27162
|
});
|
|
26863
27163
|
}
|
|
26864
27164
|
|
|
26865
|
-
// src/commands/sessions/daemon/resumeSession.ts
|
|
26866
|
-
function resumeSession(id, sessionId, cwd, name) {
|
|
26867
|
-
const startedAt = Date.now();
|
|
26868
|
-
return {
|
|
26869
|
-
id,
|
|
26870
|
-
name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
|
|
26871
|
-
commandType: "claude",
|
|
26872
|
-
status: "waiting",
|
|
26873
|
-
startedAt,
|
|
26874
|
-
runningMs: 0,
|
|
26875
|
-
runningSince: null,
|
|
26876
|
-
pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
|
|
26877
|
-
scrollback: "",
|
|
26878
|
-
cwd,
|
|
26879
|
-
/* why: bind the card to the conversation it is resuming so a daemon restart
|
|
26880
|
-
* re-resumes this same transcript, not whichever .jsonl a cwd poller would
|
|
26881
|
-
* have re-discovered (#413). */
|
|
26882
|
-
claudeSessionId: sessionId
|
|
26883
|
-
};
|
|
26884
|
-
}
|
|
26885
|
-
|
|
26886
27165
|
// src/commands/sessions/daemon/retrySession.ts
|
|
26887
27166
|
function retrySession(session, clients, onStatusChange) {
|
|
26888
27167
|
const respawn = respawnThunk(session);
|
|
@@ -26958,6 +27237,9 @@ function runRetry(sessions, id, replace, deps2) {
|
|
|
26958
27237
|
function reuseSessionForRun(session, itemId2, clients, onStatusChange) {
|
|
26959
27238
|
const assistArgs = ["backlog", "run", String(itemId2)];
|
|
26960
27239
|
session.pty?.kill();
|
|
27240
|
+
session.gitWatcher?.close();
|
|
27241
|
+
session.gitWatcher = void 0;
|
|
27242
|
+
session.undurable = void 0;
|
|
26961
27243
|
session.assistArgs = assistArgs;
|
|
26962
27244
|
session.name = `assist ${assistArgs.join(" ")}`;
|
|
26963
27245
|
session.commandType = "assist";
|
|
@@ -26987,7 +27269,7 @@ function shutdownSessions(sessions) {
|
|
|
26987
27269
|
}
|
|
26988
27270
|
|
|
26989
27271
|
// src/commands/sessions/daemon/watchTranscript.ts
|
|
26990
|
-
import { existsSync as
|
|
27272
|
+
import { existsSync as existsSync62, watch as watch3 } from "fs";
|
|
26991
27273
|
|
|
26992
27274
|
// src/commands/sessions/daemon/reconcileTranscriptStatus.ts
|
|
26993
27275
|
async function resolveTranscriptPath(session) {
|
|
@@ -26999,7 +27281,8 @@ async function resolveTranscriptPath(session) {
|
|
|
26999
27281
|
return filePath;
|
|
27000
27282
|
}
|
|
27001
27283
|
async function reconcileTranscriptStatus(session, onStatusChange) {
|
|
27002
|
-
if (session.status === "done" || session.status === "error")
|
|
27284
|
+
if (session.status === "done" || session.status === "error" || session.status === "stopped")
|
|
27285
|
+
return;
|
|
27003
27286
|
const filePath = await resolveTranscriptPath(session);
|
|
27004
27287
|
if (!filePath) return;
|
|
27005
27288
|
const entries = await readTranscriptTail(filePath);
|
|
@@ -27016,12 +27299,12 @@ async function reconcileTranscriptStatus(session, onStatusChange) {
|
|
|
27016
27299
|
}
|
|
27017
27300
|
|
|
27018
27301
|
// src/commands/sessions/daemon/watchTranscript.ts
|
|
27019
|
-
var
|
|
27302
|
+
var DEBOUNCE_MS3 = 100;
|
|
27020
27303
|
function watchTranscript(session, onStatusChange) {
|
|
27021
27304
|
if (!session.cwd || !session.claudeSessionId) return;
|
|
27022
27305
|
if (session.watchedTranscriptId === session.claudeSessionId) return;
|
|
27023
27306
|
const dir = projectDirForCwd(session.cwd);
|
|
27024
|
-
if (!
|
|
27307
|
+
if (!existsSync62(dir)) return;
|
|
27025
27308
|
if (session.transcriptWatcher) {
|
|
27026
27309
|
session.transcriptWatcher.close();
|
|
27027
27310
|
session.transcriptWatcher = void 0;
|
|
@@ -27034,10 +27317,10 @@ function watchTranscript(session, onStatusChange) {
|
|
|
27034
27317
|
void reconcileTranscriptStatus(session, onStatusChange);
|
|
27035
27318
|
};
|
|
27036
27319
|
try {
|
|
27037
|
-
session.transcriptWatcher =
|
|
27320
|
+
session.transcriptWatcher = watch3(dir, (_event, changed) => {
|
|
27038
27321
|
if (changed && changed !== fileName) return;
|
|
27039
27322
|
if (timer) clearTimeout(timer);
|
|
27040
|
-
timer = setTimeout(run4,
|
|
27323
|
+
timer = setTimeout(run4, DEBOUNCE_MS3);
|
|
27041
27324
|
});
|
|
27042
27325
|
} catch (error) {
|
|
27043
27326
|
daemonLog(
|
|
@@ -27172,19 +27455,19 @@ async function defaultConnect() {
|
|
|
27172
27455
|
}
|
|
27173
27456
|
|
|
27174
27457
|
// src/commands/sessions/daemon/hasPersistedWindowsSessions.ts
|
|
27175
|
-
import { existsSync as
|
|
27458
|
+
import { existsSync as existsSync63, readFileSync as readFileSync49 } from "fs";
|
|
27176
27459
|
import { posix } from "path";
|
|
27177
27460
|
function hasPersistedWindowsSessions() {
|
|
27178
27461
|
const sessionsFile = windowsSessionsFileFromWsl();
|
|
27179
27462
|
if (!sessionsFile) return false;
|
|
27180
27463
|
try {
|
|
27181
|
-
if (!
|
|
27464
|
+
if (!existsSync63(sessionsFile)) return false;
|
|
27182
27465
|
const data = JSON.parse(readFileSync49(sessionsFile, "utf8"));
|
|
27183
27466
|
return Array.isArray(data) && data.length > 0;
|
|
27184
27467
|
} catch (error) {
|
|
27185
|
-
const
|
|
27468
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
27186
27469
|
daemonLog(
|
|
27187
|
-
`windows proxy: could not read windows sessions.json: ${
|
|
27470
|
+
`windows proxy: could not read windows sessions.json: ${message2}`
|
|
27188
27471
|
);
|
|
27189
27472
|
return false;
|
|
27190
27473
|
}
|
|
@@ -27211,8 +27494,8 @@ async function discoverWindowsSessions(conn) {
|
|
|
27211
27494
|
try {
|
|
27212
27495
|
await conn.ensure();
|
|
27213
27496
|
} catch (error) {
|
|
27214
|
-
const
|
|
27215
|
-
daemonLog(`windows proxy: discovery failed: ${
|
|
27497
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
27498
|
+
daemonLog(`windows proxy: discovery failed: ${message2}`);
|
|
27216
27499
|
}
|
|
27217
27500
|
}
|
|
27218
27501
|
|
|
@@ -27250,11 +27533,11 @@ async function forwardWindowsCreate(conn, state, client, data) {
|
|
|
27250
27533
|
state.pendingCreators.push({ client, timer });
|
|
27251
27534
|
conn.write(stripOutboundSessionId(data));
|
|
27252
27535
|
} catch (error) {
|
|
27253
|
-
const
|
|
27254
|
-
daemonLog(`windows proxy: forwardCreate failed: ${
|
|
27536
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
27537
|
+
daemonLog(`windows proxy: forwardCreate failed: ${message2}`);
|
|
27255
27538
|
sendTo(client, {
|
|
27256
27539
|
type: "error",
|
|
27257
|
-
message: `Windows session unavailable: ${
|
|
27540
|
+
message: `Windows session unavailable: ${message2}`
|
|
27258
27541
|
});
|
|
27259
27542
|
}
|
|
27260
27543
|
}
|
|
@@ -27289,10 +27572,10 @@ function takePendingCreator(state) {
|
|
|
27289
27572
|
clearTimeout(pending.timer);
|
|
27290
27573
|
return pending.client;
|
|
27291
27574
|
}
|
|
27292
|
-
function failPendingCreators(state,
|
|
27575
|
+
function failPendingCreators(state, message2) {
|
|
27293
27576
|
for (const { client, timer } of state.pendingCreators) {
|
|
27294
27577
|
clearTimeout(timer);
|
|
27295
|
-
sendTo(client, { type: "error", message });
|
|
27578
|
+
sendTo(client, { type: "error", message: message2 });
|
|
27296
27579
|
}
|
|
27297
27580
|
state.pendingCreators = [];
|
|
27298
27581
|
}
|
|
@@ -27432,9 +27715,9 @@ async function healWindowsDaemon() {
|
|
|
27432
27715
|
try {
|
|
27433
27716
|
await runOnWindowsHost("assist update", UPDATE_TIMEOUT_MS);
|
|
27434
27717
|
} catch (error) {
|
|
27435
|
-
const
|
|
27718
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
27436
27719
|
daemonLog(
|
|
27437
|
-
`windows daemon: auto-heal: \`assist update\` failed: ${
|
|
27720
|
+
`windows daemon: auto-heal: \`assist update\` failed: ${message2}`
|
|
27438
27721
|
);
|
|
27439
27722
|
throw error;
|
|
27440
27723
|
}
|
|
@@ -27609,11 +27892,11 @@ async function autoHealWindowsDaemon(conn, state, heal, version2) {
|
|
|
27609
27892
|
daemonLog("windows proxy: heal complete, reconnecting to windows daemon");
|
|
27610
27893
|
await conn.ensure();
|
|
27611
27894
|
} catch (error) {
|
|
27612
|
-
const
|
|
27613
|
-
daemonLog(`windows proxy: auto-heal failed: ${
|
|
27895
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
27896
|
+
daemonLog(`windows proxy: auto-heal failed: ${message2}`);
|
|
27614
27897
|
state.broadcast({
|
|
27615
27898
|
type: "error",
|
|
27616
|
-
message: `Windows host auto-update failed: ${
|
|
27899
|
+
message: `Windows host auto-update failed: ${message2}`
|
|
27617
27900
|
});
|
|
27618
27901
|
}
|
|
27619
27902
|
}
|
|
@@ -27721,6 +28004,335 @@ var WindowsProxy = class {
|
|
|
27721
28004
|
}
|
|
27722
28005
|
};
|
|
27723
28006
|
|
|
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
|
+
// src/commands/sessions/daemon/createAssistSession.ts
|
|
28194
|
+
function createAssistSession(id, assistArgs, cwd, meta) {
|
|
28195
|
+
const startedAt = Date.now();
|
|
28196
|
+
return {
|
|
28197
|
+
id,
|
|
28198
|
+
name: `assist ${assistArgs.join(" ")}`,
|
|
28199
|
+
title: meta?.title,
|
|
28200
|
+
subtitle: meta?.subtitle,
|
|
28201
|
+
commandType: "assist",
|
|
28202
|
+
status: "running",
|
|
28203
|
+
startedAt,
|
|
28204
|
+
runningMs: 0,
|
|
28205
|
+
runningSince: startedAt,
|
|
28206
|
+
pty: spawnPty(["assist", ...assistArgs], cwd, id),
|
|
28207
|
+
scrollback: "",
|
|
28208
|
+
assistArgs,
|
|
28209
|
+
cwd
|
|
28210
|
+
};
|
|
28211
|
+
}
|
|
28212
|
+
|
|
28213
|
+
// src/commands/sessions/daemon/resumeSession.ts
|
|
28214
|
+
function resumeSession(id, sessionId, cwd, name) {
|
|
28215
|
+
const startedAt = Date.now();
|
|
28216
|
+
return {
|
|
28217
|
+
id,
|
|
28218
|
+
name: name ? `${name.slice(0, 36)} (R)` : `Resume ${sessionId.slice(0, 8)}`,
|
|
28219
|
+
commandType: "claude",
|
|
28220
|
+
status: "waiting",
|
|
28221
|
+
startedAt,
|
|
28222
|
+
runningMs: 0,
|
|
28223
|
+
runningSince: null,
|
|
28224
|
+
pty: spawnClaude2({ resumeSessionId: sessionId, cwd, sessionId: id }),
|
|
28225
|
+
scrollback: "",
|
|
28226
|
+
cwd,
|
|
28227
|
+
/* why: bind the card to the conversation it is resuming so a daemon restart
|
|
28228
|
+
* re-resumes this same transcript, not whichever .jsonl a cwd poller would
|
|
28229
|
+
* have re-discovered (#413). */
|
|
28230
|
+
claudeSessionId: sessionId
|
|
28231
|
+
};
|
|
28232
|
+
}
|
|
28233
|
+
|
|
28234
|
+
// src/commands/sessions/daemon/worktree/createWorktree.ts
|
|
28235
|
+
import { existsSync as existsSync67 } from "fs";
|
|
28236
|
+
import { basename as basename16, dirname as dirname31 } from "path";
|
|
28237
|
+
|
|
28238
|
+
// src/commands/sessions/daemon/worktree/planAllocation.ts
|
|
28239
|
+
import { basename as basename15, join as join72 } from "path";
|
|
28240
|
+
function planAllocation(clone, boundTreeRoots2) {
|
|
28241
|
+
return boundTreeRoots2.has(clone) ? "spill" : "primary";
|
|
28242
|
+
}
|
|
28243
|
+
function nextWorktreePath(clone, base, isTaken) {
|
|
28244
|
+
const name = basename15(clone);
|
|
28245
|
+
for (let n = 2; n < 1e3; n++) {
|
|
28246
|
+
const candidate = join72(base, `${name}-${n}`);
|
|
28247
|
+
if (!isTaken(candidate)) return candidate;
|
|
28248
|
+
}
|
|
28249
|
+
throw new Error(`no free worktree suffix for ${clone}`);
|
|
28250
|
+
}
|
|
28251
|
+
|
|
28252
|
+
// src/commands/sessions/daemon/worktree/createWorktree.ts
|
|
28253
|
+
function createWorktree(clone, root, boundTreeRoots2) {
|
|
28254
|
+
const base = root ? expandTilde2(root) : dirname31(clone);
|
|
28255
|
+
const registered = new Set(listWorktreePaths(clone));
|
|
28256
|
+
const branches = new Set(listLocalBranches(clone));
|
|
28257
|
+
const path71 = nextWorktreePath(
|
|
28258
|
+
clone,
|
|
28259
|
+
base,
|
|
28260
|
+
(candidate) => registered.has(candidate) || existsSync67(candidate) || boundTreeRoots2.has(candidate) || branches.has(basename16(candidate))
|
|
28261
|
+
);
|
|
28262
|
+
const trunk = cloneTrunkBranch(clone);
|
|
28263
|
+
gitSync(clone, ["config", "push.default", "upstream"]);
|
|
28264
|
+
gitSync(clone, [
|
|
28265
|
+
"worktree",
|
|
28266
|
+
"add",
|
|
28267
|
+
"--track",
|
|
28268
|
+
"-b",
|
|
28269
|
+
basename16(path71),
|
|
28270
|
+
path71,
|
|
28271
|
+
`origin/${trunk}`
|
|
28272
|
+
]);
|
|
28273
|
+
recordWorktree(path71, clone, getCurrentOrigin(clone));
|
|
28274
|
+
daemonLog(
|
|
28275
|
+
`worktree allocated ${path71} (branch ${basename16(path71)} tracking origin/${trunk}) for clone ${clone}`
|
|
28276
|
+
);
|
|
28277
|
+
return path71;
|
|
28278
|
+
}
|
|
28279
|
+
function cloneTrunkBranch(clone) {
|
|
28280
|
+
const branch2 = gitSyncOrNull(clone, ["symbolic-ref", "--short", "HEAD"]);
|
|
28281
|
+
if (branch2) return branch2;
|
|
28282
|
+
const head = gitSyncOrNull(clone, [
|
|
28283
|
+
"symbolic-ref",
|
|
28284
|
+
"--short",
|
|
28285
|
+
"refs/remotes/origin/HEAD"
|
|
28286
|
+
]);
|
|
28287
|
+
if (head) {
|
|
28288
|
+
const slash = head.indexOf("/");
|
|
28289
|
+
return slash === -1 ? head : head.slice(slash + 1);
|
|
28290
|
+
}
|
|
28291
|
+
return "main";
|
|
28292
|
+
}
|
|
28293
|
+
|
|
28294
|
+
// src/commands/sessions/daemon/worktree/allocateTree.ts
|
|
28295
|
+
function allocateTree(requestedCwd, boundTreeRoots2) {
|
|
28296
|
+
if (!requestedCwd)
|
|
28297
|
+
return { cwd: requestedCwd, kind: "primary", created: false };
|
|
28298
|
+
const repoRoot = findRepoRoot(requestedCwd) ?? requestedCwd;
|
|
28299
|
+
const cfg = worktreeConfigFor(repoRoot);
|
|
28300
|
+
if (!cfg.enabled)
|
|
28301
|
+
return { cwd: requestedCwd, kind: "primary", created: false };
|
|
28302
|
+
const clone = mainWorktree(repoRoot) ?? repoRoot;
|
|
28303
|
+
if (planAllocation(clone, boundTreeRoots2) === "primary")
|
|
28304
|
+
return { cwd: clone, kind: "primary", created: false, clone };
|
|
28305
|
+
const path71 = createWorktree(clone, cfg.root, boundTreeRoots2);
|
|
28306
|
+
return { cwd: path71, kind: "worktree", created: true, clone };
|
|
28307
|
+
}
|
|
28308
|
+
|
|
28309
|
+
// src/commands/sessions/daemon/worktree/spawnInTree.ts
|
|
28310
|
+
function allocateAndBind(ctx, cwd, create) {
|
|
28311
|
+
const alloc = allocateTree(cwd, boundTreeRoots(ctx.sessions));
|
|
28312
|
+
const id = ctx.spawnWith((sid) => create(sid, alloc.cwd));
|
|
28313
|
+
bindNewWorktree(ctx.sessions.get(id), alloc, ctx.notify);
|
|
28314
|
+
return id;
|
|
28315
|
+
}
|
|
28316
|
+
function spawnInTree(ctx, prompt, cwd, design, harness) {
|
|
28317
|
+
return allocateAndBind(
|
|
28318
|
+
ctx,
|
|
28319
|
+
cwd,
|
|
28320
|
+
(sid, resolvedCwd) => createSession(sid, prompt, resolvedCwd, design, harness)
|
|
28321
|
+
);
|
|
28322
|
+
}
|
|
28323
|
+
function spawnAssistInTree(ctx, assistArgs, cwd, meta) {
|
|
28324
|
+
return allocateAndBind(
|
|
28325
|
+
ctx,
|
|
28326
|
+
cwd,
|
|
28327
|
+
(sid, resolvedCwd) => createAssistSession(sid, assistArgs, resolvedCwd, meta)
|
|
28328
|
+
);
|
|
28329
|
+
}
|
|
28330
|
+
function resumeInTree(ctx, sessionId, cwd, name) {
|
|
28331
|
+
const id = ctx.spawnWith((sid) => resumeSession(sid, sessionId, cwd, name));
|
|
28332
|
+
bindResumedWorktree(ctx.sessions.get(id), cwd, ctx.notify);
|
|
28333
|
+
return id;
|
|
28334
|
+
}
|
|
28335
|
+
|
|
27724
28336
|
// src/commands/sessions/daemon/watchPromptSubmit.ts
|
|
27725
28337
|
var CARRIAGE_RETURN = "\r";
|
|
27726
28338
|
function isPromptSubmit(data) {
|
|
@@ -27744,6 +28356,8 @@ function writeToSession(sessions, id, data, onStatusChange) {
|
|
|
27744
28356
|
function resizeSession(sessions, id, cols, rows) {
|
|
27745
28357
|
const s = sessions.get(id);
|
|
27746
28358
|
if (!s || s.status === "done" || !s.pty) return;
|
|
28359
|
+
s.cols = cols;
|
|
28360
|
+
s.rows = rows;
|
|
27747
28361
|
try {
|
|
27748
28362
|
s.pty.resize(cols, rows);
|
|
27749
28363
|
} catch (error) {
|
|
@@ -27818,7 +28432,10 @@ var SessionManager = class {
|
|
|
27818
28432
|
);
|
|
27819
28433
|
}
|
|
27820
28434
|
restore() {
|
|
27821
|
-
|
|
28435
|
+
const names = restoreAll(this.spawnWith, this.sessions);
|
|
28436
|
+
reconcileWorktreesOnRestore(this.sessions);
|
|
28437
|
+
rearmStoppedSessions(this.sessions, this.notify);
|
|
28438
|
+
return names;
|
|
27822
28439
|
}
|
|
27823
28440
|
drain = () => drainSessions(this.sessions, this.notify);
|
|
27824
28441
|
add(session) {
|
|
@@ -27828,10 +28445,15 @@ var SessionManager = class {
|
|
|
27828
28445
|
return session.id;
|
|
27829
28446
|
}
|
|
27830
28447
|
spawnWith = (create) => this.add(create(sessionLimits.nextId(this.sessions.size, this.idCounter)));
|
|
28448
|
+
treeCtx() {
|
|
28449
|
+
return {
|
|
28450
|
+
sessions: this.sessions,
|
|
28451
|
+
spawnWith: this.spawnWith,
|
|
28452
|
+
notify: this.notify
|
|
28453
|
+
};
|
|
28454
|
+
}
|
|
27831
28455
|
spawn(prompt, cwd, design, harness) {
|
|
27832
|
-
return this.
|
|
27833
|
-
(id) => createSession(id, prompt, cwd, design, harness)
|
|
27834
|
-
);
|
|
28456
|
+
return spawnInTree(this.treeCtx(), prompt, cwd, design, harness);
|
|
27835
28457
|
}
|
|
27836
28458
|
spawnRun(runName, runArgs, cwd, meta) {
|
|
27837
28459
|
return this.spawnWith(
|
|
@@ -27842,12 +28464,10 @@ var SessionManager = class {
|
|
|
27842
28464
|
return liveServerRun(this.sessions, origin, excludeId);
|
|
27843
28465
|
}
|
|
27844
28466
|
spawnAssist(assistArgs, cwd, meta) {
|
|
27845
|
-
return this.
|
|
27846
|
-
(id) => createAssistSession(id, assistArgs, cwd, meta)
|
|
27847
|
-
);
|
|
28467
|
+
return spawnAssistInTree(this.treeCtx(), assistArgs, cwd, meta);
|
|
27848
28468
|
}
|
|
27849
28469
|
resume(sessionId, cwd, name) {
|
|
27850
|
-
return this.
|
|
28470
|
+
return resumeInTree(this.treeCtx(), sessionId, cwd, name);
|
|
27851
28471
|
}
|
|
27852
28472
|
onStatusChange = makeStatusChangeHandler(
|
|
27853
28473
|
(id) => this.dismissSession(id),
|
|
@@ -27884,7 +28504,10 @@ var SessionManager = class {
|
|
|
27884
28504
|
return result;
|
|
27885
28505
|
}
|
|
27886
28506
|
dismissSession = (id) => {
|
|
27887
|
-
|
|
28507
|
+
dismissSessionGated(this.sessions, id, this.notify);
|
|
28508
|
+
};
|
|
28509
|
+
discardSession = (id) => {
|
|
28510
|
+
dismissSessionGated(this.sessions, id, this.notify, true);
|
|
27888
28511
|
};
|
|
27889
28512
|
stopSession(id) {
|
|
27890
28513
|
stopServerSession(this.sessions, id);
|
|
@@ -28165,6 +28788,7 @@ var messageHandlers = {
|
|
|
28165
28788
|
sendTo(client, { type: "error", message: result.reason });
|
|
28166
28789
|
}),
|
|
28167
28790
|
dismiss: routed((_client, m, d) => m.dismissSession(d.sessionId)),
|
|
28791
|
+
discard: routed((_client, m, d) => m.discardSession(d.sessionId)),
|
|
28168
28792
|
stop: routed((_client, m, d) => m.stopSession(d.sessionId)),
|
|
28169
28793
|
"set-autorun": routed(
|
|
28170
28794
|
(_client, m, d) => m.setAutoRun(d.sessionId, d.enabled)
|
|
@@ -28326,7 +28950,7 @@ async function recoverFromAddrInUse(server, manager, checkAutoExit) {
|
|
|
28326
28950
|
|
|
28327
28951
|
// src/commands/sessions/daemon/runDaemon.ts
|
|
28328
28952
|
async function runDaemon() {
|
|
28329
|
-
|
|
28953
|
+
mkdirSync27(daemonPaths.dir, { recursive: true });
|
|
28330
28954
|
daemonLog(
|
|
28331
28955
|
`starting (reason: ${process.env.ASSIST_DAEMON_SPAWN_REASON ?? "manual"})`
|
|
28332
28956
|
);
|
|
@@ -28379,6 +29003,50 @@ function registerSetStatusCommand(cmd) {
|
|
|
28379
29003
|
);
|
|
28380
29004
|
}
|
|
28381
29005
|
|
|
29006
|
+
// src/commands/sessions/sessionsConfigHelp.ts
|
|
29007
|
+
var sessionsConfigHelp = [
|
|
29008
|
+
{
|
|
29009
|
+
key: "sessions.windowsProjectsRoot",
|
|
29010
|
+
setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
|
|
29011
|
+
note: "WSL path to the Windows .claude/projects root to discover"
|
|
29012
|
+
},
|
|
29013
|
+
{
|
|
29014
|
+
key: "sessions.windowsDaemonHost",
|
|
29015
|
+
setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
|
|
29016
|
+
note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
|
|
29017
|
+
},
|
|
29018
|
+
{
|
|
29019
|
+
key: "sessions.windowsDaemonPort",
|
|
29020
|
+
setter: "assist config set sessions.windowsDaemonPort 51764",
|
|
29021
|
+
note: "TCP port the Windows daemon listens on (default: 51764)"
|
|
29022
|
+
},
|
|
29023
|
+
{
|
|
29024
|
+
key: "sessions.windowsVersionCheck",
|
|
29025
|
+
setter: "assist config set sessions.windowsVersionCheck block",
|
|
29026
|
+
note: "Windows daemon version mismatch handling: block | warn | off"
|
|
29027
|
+
},
|
|
29028
|
+
{
|
|
29029
|
+
key: "worktree.enabled",
|
|
29030
|
+
setter: "assist config set worktree.enabled true --repo",
|
|
29031
|
+
note: "opt in per repo: spill concurrent sessions into adjacent <clone>-N worktrees (default off)"
|
|
29032
|
+
},
|
|
29033
|
+
{
|
|
29034
|
+
key: "worktree.root",
|
|
29035
|
+
setter: "assist config set worktree.root ~/git --repo",
|
|
29036
|
+
note: "optional; parent dir for <clone>-N worktrees (default: the clone's parent)"
|
|
29037
|
+
},
|
|
29038
|
+
{
|
|
29039
|
+
key: "worktree.install",
|
|
29040
|
+
setter: "assist config set worktree.install true --repo",
|
|
29041
|
+
note: "per-worktree dep install: true auto-detects the package manager, or give an explicit command; false to skip"
|
|
29042
|
+
},
|
|
29043
|
+
{
|
|
29044
|
+
key: "worktree.copy",
|
|
29045
|
+
setter: "assist config set worktree.copy .env,.claude/settings.local.json --repo",
|
|
29046
|
+
note: "gitignored files copied into a new worktree so it can build/run"
|
|
29047
|
+
}
|
|
29048
|
+
];
|
|
29049
|
+
|
|
28382
29050
|
// src/commands/sessions/summarise/index.ts
|
|
28383
29051
|
import * as fs41 from "fs";
|
|
28384
29052
|
import chalk204 from "chalk";
|
|
@@ -28397,7 +29065,7 @@ function summaryPathFor(jsonlPath2) {
|
|
|
28397
29065
|
}
|
|
28398
29066
|
|
|
28399
29067
|
// src/commands/sessions/summarise/summariseSession.ts
|
|
28400
|
-
import { execFileSync as
|
|
29068
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
28401
29069
|
|
|
28402
29070
|
// src/commands/sessions/summarise/extractFirstUserMessage.ts
|
|
28403
29071
|
function extractFirstUserMessage(filePath) {
|
|
@@ -28421,7 +29089,7 @@ function summariseSession(jsonlPath2) {
|
|
|
28421
29089
|
}
|
|
28422
29090
|
const prompt = buildPrompt3(firstMessage, backlogIds);
|
|
28423
29091
|
try {
|
|
28424
|
-
const output =
|
|
29092
|
+
const output = execFileSync13("claude", ["-p", "--model", "haiku", prompt], {
|
|
28425
29093
|
encoding: "utf8",
|
|
28426
29094
|
timeout: 3e4,
|
|
28427
29095
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -28516,28 +29184,7 @@ function registerSessions(program2) {
|
|
|
28516
29184
|
);
|
|
28517
29185
|
cmd.command("summarise").description("Generate one-line summaries for Claude sessions").option("-f, --force", "Re-generate all summaries, even existing ones").option("-n, --limit <count>", "Maximum number of sessions to summarise").action(summarise2);
|
|
28518
29186
|
registerSetStatusCommand(cmd);
|
|
28519
|
-
configHelp(cmd,
|
|
28520
|
-
{
|
|
28521
|
-
key: "sessions.windowsProjectsRoot",
|
|
28522
|
-
setter: "assist config set sessions.windowsProjectsRoot /mnt/c/Users/you/.claude/projects",
|
|
28523
|
-
note: "WSL path to the Windows .claude/projects root to discover"
|
|
28524
|
-
},
|
|
28525
|
-
{
|
|
28526
|
-
key: "sessions.windowsDaemonHost",
|
|
28527
|
-
setter: "assist config set sessions.windowsDaemonHost 127.0.0.1",
|
|
28528
|
-
note: "host the WSL daemon dials to reach the Windows daemon (default: 127.0.0.1)"
|
|
28529
|
-
},
|
|
28530
|
-
{
|
|
28531
|
-
key: "sessions.windowsDaemonPort",
|
|
28532
|
-
setter: "assist config set sessions.windowsDaemonPort 51764",
|
|
28533
|
-
note: "TCP port the Windows daemon listens on (default: 51764)"
|
|
28534
|
-
},
|
|
28535
|
-
{
|
|
28536
|
-
key: "sessions.windowsVersionCheck",
|
|
28537
|
-
setter: "assist config set sessions.windowsVersionCheck block",
|
|
28538
|
-
note: "Windows daemon version mismatch handling: block | warn | off"
|
|
28539
|
-
}
|
|
28540
|
-
]);
|
|
29187
|
+
configHelp(cmd, sessionsConfigHelp);
|
|
28541
29188
|
}
|
|
28542
29189
|
|
|
28543
29190
|
// src/commands/statusLine.ts
|
|
@@ -28616,9 +29263,9 @@ function buildLimitsSegment(rateLimits) {
|
|
|
28616
29263
|
|
|
28617
29264
|
// src/commands/readGitBranch.ts
|
|
28618
29265
|
import { readFileSync as readFileSync52, statSync as statSync11 } from "fs";
|
|
28619
|
-
import { isAbsolute as isAbsolute2, join as
|
|
29266
|
+
import { isAbsolute as isAbsolute2, join as join73, resolve as resolve17 } from "path";
|
|
28620
29267
|
function resolveGitDir(cwd) {
|
|
28621
|
-
const dotGit =
|
|
29268
|
+
const dotGit = join73(cwd, ".git");
|
|
28622
29269
|
let stat2;
|
|
28623
29270
|
try {
|
|
28624
29271
|
stat2 = statSync11(dotGit);
|
|
@@ -28648,7 +29295,7 @@ function readGitBranch(cwd) {
|
|
|
28648
29295
|
}
|
|
28649
29296
|
let head;
|
|
28650
29297
|
try {
|
|
28651
|
-
head = readFileSync52(
|
|
29298
|
+
head = readFileSync52(join73(gitDir, "HEAD"), "utf8");
|
|
28652
29299
|
} catch {
|
|
28653
29300
|
return null;
|
|
28654
29301
|
}
|