toyon 0.16.0 → 0.17.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 +1 -1
- package/dist/CHANGELOG.md +35 -0
- package/dist/cli.js +114 -47
- package/dist/daemon.js +1350 -502
- package/dist/shell/assets/{Editor-BATywBIx.js → Editor-BMBnZV2V.js} +175 -175
- package/dist/shell/assets/{XTerm-So8-Vafj.js → XTerm-xxm4nCyN.js} +1 -1
- package/dist/shell/assets/{cssMode-DlHcrkXj.js → cssMode-DRRmIKjD.js} +1 -1
- package/dist/shell/assets/{freemarker2-D3xOnVxc.js → freemarker2-BWXbPnFF.js} +1 -1
- package/dist/shell/assets/{grammar-RZJ5oG33.js → grammar-BlgE3B1J.js} +1 -1
- package/dist/shell/assets/{handlebars-UbIG7080.js → handlebars-swdzvdWB.js} +1 -1
- package/dist/shell/assets/{html-D92eLK6U.js → html-GPVhANjL.js} +1 -1
- package/dist/shell/assets/{htmlMode-DsZIJzoj.js → htmlMode-BTtuR8kD.js} +1 -1
- package/dist/shell/assets/index-9aG0ri4Y.css +1 -0
- package/dist/shell/assets/index-miqqiN7Z.js +269 -0
- package/dist/shell/assets/{javascript-DUkgFtV2.js → javascript-CFXn2iKE.js} +1 -1
- package/dist/shell/assets/{jsonMode-BiIooPg9.js → jsonMode-wmrRCrSu.js} +1 -1
- package/dist/shell/assets/{liquid-DFFjZ5TH.js → liquid-Bz7Svg4V.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-C7Ug4qkg.js → lspLanguageFeatures-BDP4BowE.js} +1 -1
- package/dist/shell/assets/{mdx-DdjSgAuG.js → mdx-CzERTPdi.js} +1 -1
- package/dist/shell/assets/{python-DPUF4OK9.js → python-3OsSAVmo.js} +1 -1
- package/dist/shell/assets/{razor-IRYJ0pOA.js → razor-BKY4SJuy.js} +1 -1
- package/dist/shell/assets/{tsMode-CljE7MPO.js → tsMode-BESW693Z.js} +1 -1
- package/dist/shell/assets/{xml-D8Zv3Gd0.js → xml-SaTEQQ9V.js} +1 -1
- package/dist/shell/assets/{yaml-Dxx9PL0B.js → yaml-gHi84-nk.js} +1 -1
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/index-BvYzBRq_.css +0 -1
- package/dist/shell/assets/index-CHQwm2uN.js +0 -268
package/dist/daemon.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../daemon/src/index.ts
|
|
3
3
|
import { rmSync as rmSync9, writeFileSync as writeFileSync13 } from "fs";
|
|
4
|
-
import {
|
|
4
|
+
import { homedir as homedir15 } from "os";
|
|
5
|
+
import { dirname as dirname13, join as join32 } from "path";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
7
|
|
|
7
8
|
// ../shared/src/protocol/events.ts
|
|
@@ -342,6 +343,8 @@ var APP_SCHEME = "toyon";
|
|
|
342
343
|
var START_LINK = `${APP_SCHEME}://start`;
|
|
343
344
|
var OFFLINE_SLOTS = { start: "data-start", port: "data-port" };
|
|
344
345
|
var LSREGISTER = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
|
|
346
|
+
var FD_LIMIT_LOW = 4096;
|
|
347
|
+
var fdLimitLine = (n) => `open-file limit is ${n}; raise it with ulimit -n 8192 before toyon starts`;
|
|
345
348
|
var DAEMON_FILES = {
|
|
346
349
|
token: "token",
|
|
347
350
|
helper: "Toyon.app",
|
|
@@ -6598,7 +6601,7 @@ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
|
|
|
6598
6601
|
}
|
|
6599
6602
|
|
|
6600
6603
|
// ../shared/src/protocol/limits.ts
|
|
6601
|
-
var PROTOCOL_VERSION =
|
|
6604
|
+
var PROTOCOL_VERSION = 115;
|
|
6602
6605
|
var PAGE_ERROR_MAX_CHARS = 2000;
|
|
6603
6606
|
var PAGE_TITLE_MAX_CHARS = 1000;
|
|
6604
6607
|
var FILE_MAX_CHARS = 5000000;
|
|
@@ -6947,6 +6950,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6947
6950
|
object({ t: literal("list-files"), worktreeId: id }),
|
|
6948
6951
|
object({ t: literal("list-commands"), worktreeId: id }),
|
|
6949
6952
|
object({ t: literal("search"), worktreeId: id, query: string2().max(500) }),
|
|
6953
|
+
object({ t: literal("search-stop") }),
|
|
6950
6954
|
object({ t: literal("find-element"), worktreeId: id, seq, element: elementTraitsSchema }),
|
|
6951
6955
|
object({ t: literal("design-scan"), worktreeId: id }),
|
|
6952
6956
|
object({ t: literal("discard-files"), worktreeId: id, paths: array(relPath).min(1).max(500) }),
|
|
@@ -7011,6 +7015,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
7011
7015
|
start: string2().max(4000),
|
|
7012
7016
|
purpose: _enum(["location", "open"])
|
|
7013
7017
|
}),
|
|
7018
|
+
object({ t: literal("choose-file"), start: string2().max(4000) }),
|
|
7014
7019
|
object({ t: literal("cancel-folder") }),
|
|
7015
7020
|
object({ t: literal("forget-repo"), repoId: id }),
|
|
7016
7021
|
object({ t: literal("set-theme"), prefs: themePrefsSchema }),
|
|
@@ -8514,7 +8519,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
|
|
|
8514
8519
|
// ../daemon/package.json
|
|
8515
8520
|
var package_default = {
|
|
8516
8521
|
name: "@toyon/daemon",
|
|
8517
|
-
version: "0.
|
|
8522
|
+
version: "0.17.0",
|
|
8518
8523
|
private: true,
|
|
8519
8524
|
type: "module",
|
|
8520
8525
|
main: "src/index.ts",
|
|
@@ -12617,17 +12622,26 @@ function envLevel() {
|
|
|
12617
12622
|
return v && v in LEVELS ? v : "info";
|
|
12618
12623
|
}
|
|
12619
12624
|
var threshold = LEVELS[envLevel()];
|
|
12620
|
-
|
|
12625
|
+
var BUGS = [TypeError, RangeError, ReferenceError, SyntaxError];
|
|
12626
|
+
function fmtError(level, e) {
|
|
12627
|
+
const line = `${e.name}: ${e.message}`;
|
|
12628
|
+
const whole = level === "error" || threshold <= LEVELS.debug || BUGS.some((c) => e instanceof c);
|
|
12629
|
+
return whole ? e.stack ?? line : line;
|
|
12630
|
+
}
|
|
12631
|
+
function fmtExtra(level, extra) {
|
|
12621
12632
|
if (extra === undefined)
|
|
12622
12633
|
return "";
|
|
12623
12634
|
if (extra instanceof Error)
|
|
12624
|
-
return ` ${
|
|
12635
|
+
return ` ${fmtError(level, extra)}`;
|
|
12625
12636
|
try {
|
|
12626
12637
|
return ` ${JSON.stringify(extra)}`;
|
|
12627
12638
|
} catch {
|
|
12628
12639
|
return ` ${String(extra)}`;
|
|
12629
12640
|
}
|
|
12630
12641
|
}
|
|
12642
|
+
function formatLine(level, tag, message, extra, iso = new Date().toISOString()) {
|
|
12643
|
+
return `${iso.slice(11, 19)} ${level.padEnd(5)} [${tag}] ${message}${fmtExtra(level, extra)}`;
|
|
12644
|
+
}
|
|
12631
12645
|
var lastDay = "";
|
|
12632
12646
|
function write(level, tag, message, extra) {
|
|
12633
12647
|
if (LEVELS[level] < threshold)
|
|
@@ -12639,7 +12653,7 @@ function write(level, tag, message, extra) {
|
|
|
12639
12653
|
process.stderr.write(`-------- ${day} --------
|
|
12640
12654
|
`);
|
|
12641
12655
|
}
|
|
12642
|
-
process.stderr.write(`${
|
|
12656
|
+
process.stderr.write(`${formatLine(level, tag, message, extra, iso)}
|
|
12643
12657
|
`);
|
|
12644
12658
|
}
|
|
12645
12659
|
var log = {
|
|
@@ -13794,23 +13808,90 @@ import { join as join3 } from "path";
|
|
|
13794
13808
|
// ../daemon/src/core/metrics.ts
|
|
13795
13809
|
var WARN_MS = 50;
|
|
13796
13810
|
var TICK_MS = 250;
|
|
13811
|
+
var SLEEP_MS = 30000;
|
|
13797
13812
|
var lastGit = { cmd: "", ms: 0, at: 0 };
|
|
13813
|
+
var gitSpawns = {};
|
|
13814
|
+
function noteSpawn(args) {
|
|
13815
|
+
const key = args.slice(0, 2).join(" ");
|
|
13816
|
+
gitSpawns[key] = (gitSpawns[key] ?? 0) + 1;
|
|
13817
|
+
}
|
|
13818
|
+
var treeReads = { core: 0, extras: 0, superseded: 0, failed: 0 };
|
|
13819
|
+
var syncHot = {};
|
|
13820
|
+
function noteSync(name, ms) {
|
|
13821
|
+
if (ms > (syncHot[name] ?? 0))
|
|
13822
|
+
syncHot[name] = ms;
|
|
13823
|
+
}
|
|
13824
|
+
var limits = { fdLimit: 0 };
|
|
13825
|
+
var lagHist = { over50: 0, over100: 0, over250: 0, over1000: 0 };
|
|
13798
13826
|
var lag = { last: 0, max: 0, maxCause: null, over: 0 };
|
|
13827
|
+
|
|
13828
|
+
class LagMinute {
|
|
13829
|
+
minute = -1;
|
|
13830
|
+
ticks = 0;
|
|
13831
|
+
worst = 0;
|
|
13832
|
+
git = null;
|
|
13833
|
+
gitMs = null;
|
|
13834
|
+
note(now, late, git) {
|
|
13835
|
+
const minute = Math.floor(now / 60000);
|
|
13836
|
+
let out = null;
|
|
13837
|
+
if (minute !== this.minute) {
|
|
13838
|
+
if (this.ticks > 0)
|
|
13839
|
+
out = { ticks: this.ticks, worst: this.worst, git: this.git, gitMs: this.gitMs };
|
|
13840
|
+
this.minute = minute;
|
|
13841
|
+
this.ticks = 0;
|
|
13842
|
+
this.worst = 0;
|
|
13843
|
+
this.git = null;
|
|
13844
|
+
this.gitMs = null;
|
|
13845
|
+
}
|
|
13846
|
+
if (late > WARN_MS && late <= SLEEP_MS) {
|
|
13847
|
+
this.ticks++;
|
|
13848
|
+
if (late > this.worst) {
|
|
13849
|
+
this.worst = late;
|
|
13850
|
+
this.git = git?.cmd ?? null;
|
|
13851
|
+
this.gitMs = git?.ms ?? null;
|
|
13852
|
+
}
|
|
13853
|
+
}
|
|
13854
|
+
return out;
|
|
13855
|
+
}
|
|
13856
|
+
}
|
|
13857
|
+
function describeLag(l) {
|
|
13858
|
+
const times = l.ticks === 1 ? "once" : `${l.ticks} times`;
|
|
13859
|
+
const cause = l.git ? ` after ${l.git} (${l.gitMs}ms)` : "";
|
|
13860
|
+
return `event loop late ${times} in the last minute, worst ${l.worst}ms${cause}`;
|
|
13861
|
+
}
|
|
13799
13862
|
function startLagSampler() {
|
|
13800
13863
|
let expected = Date.now() + TICK_MS;
|
|
13864
|
+
const minute = new LagMinute;
|
|
13801
13865
|
const t = setInterval(() => {
|
|
13802
13866
|
const now = Date.now();
|
|
13803
13867
|
const late = Math.max(0, now - expected);
|
|
13804
13868
|
expected = now + TICK_MS;
|
|
13805
13869
|
lag.last = late;
|
|
13870
|
+
const git = lastGit.cmd && now - lastGit.at < TICK_MS + late ? { cmd: lastGit.cmd, ms: lastGit.ms } : null;
|
|
13871
|
+
const line = minute.note(now, late, git);
|
|
13872
|
+
if (line) {
|
|
13873
|
+
log.warn("lag", describeLag(line), {
|
|
13874
|
+
ticks: line.ticks,
|
|
13875
|
+
worst: line.worst,
|
|
13876
|
+
...line.git ? { git: line.git, gitMs: line.gitMs } : {}
|
|
13877
|
+
});
|
|
13878
|
+
}
|
|
13879
|
+
if (late > SLEEP_MS)
|
|
13880
|
+
return;
|
|
13806
13881
|
if (late > lag.max) {
|
|
13807
13882
|
lag.max = late;
|
|
13808
|
-
lag.maxCause =
|
|
13883
|
+
lag.maxCause = git ? `${git.cmd} (${git.ms}ms)` : null;
|
|
13809
13884
|
}
|
|
13810
|
-
if (late > WARN_MS)
|
|
13885
|
+
if (late > WARN_MS)
|
|
13811
13886
|
lag.over++;
|
|
13812
|
-
|
|
13813
|
-
|
|
13887
|
+
if (late > 50)
|
|
13888
|
+
lagHist.over50++;
|
|
13889
|
+
if (late > 100)
|
|
13890
|
+
lagHist.over100++;
|
|
13891
|
+
if (late > 250)
|
|
13892
|
+
lagHist.over250++;
|
|
13893
|
+
if (late > 1000)
|
|
13894
|
+
lagHist.over1000++;
|
|
13814
13895
|
}, TICK_MS);
|
|
13815
13896
|
return () => clearInterval(t);
|
|
13816
13897
|
}
|
|
@@ -13860,8 +13941,13 @@ var NO_PROMPT = {
|
|
|
13860
13941
|
GIT_SSH_COMMAND: "ssh -o BatchMode=yes"
|
|
13861
13942
|
};
|
|
13862
13943
|
var SPAWN_ENV = { GIT_OPTIONAL_LOCKS: "0" };
|
|
13863
|
-
|
|
13944
|
+
var RUN_CEILING_MS = 10 * 60000;
|
|
13945
|
+
async function run(cmd, args, cwd, env, opts = {}) {
|
|
13864
13946
|
const started = Date.now();
|
|
13947
|
+
const ceiling = opts.timeoutMs ?? RUN_CEILING_MS;
|
|
13948
|
+
let timer;
|
|
13949
|
+
if (cmd === GIT)
|
|
13950
|
+
noteSpawn(args);
|
|
13865
13951
|
try {
|
|
13866
13952
|
const p = Bun.spawn([cmd, ...args], {
|
|
13867
13953
|
cwd,
|
|
@@ -13870,17 +13956,31 @@ async function run(cmd, args, cwd, env) {
|
|
|
13870
13956
|
stdin: "ignore",
|
|
13871
13957
|
env: { ...process.env, ...SPAWN_ENV, ...env }
|
|
13872
13958
|
});
|
|
13873
|
-
const
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13959
|
+
const done = (async () => {
|
|
13960
|
+
const [rawOut, err, status] = await Promise.all([
|
|
13961
|
+
new Response(p.stdout).text(),
|
|
13962
|
+
new Response(p.stderr).text(),
|
|
13963
|
+
p.exited
|
|
13964
|
+
]);
|
|
13965
|
+
const exit = p.signalCode ?? status;
|
|
13966
|
+
return { ok: status === 0, out: rawOut.trim(), rawOut, err: err.trim(), exit };
|
|
13967
|
+
})();
|
|
13968
|
+
const gaveUp = new Promise((resolve) => {
|
|
13969
|
+
timer = setTimeout(() => {
|
|
13970
|
+
const what = `${cmd === GIT ? "git" : cmd} ${args.slice(0, 3).join(" ")}`;
|
|
13971
|
+
const msg = `gave up after ${describeDuration(ceiling)}`;
|
|
13972
|
+
log.warn("git", `${what} ${msg} in ${cwd}`);
|
|
13973
|
+
p.kill("SIGKILL");
|
|
13974
|
+
resolve({ ok: false, out: "", rawOut: "", err: msg, exit: "timeout" });
|
|
13975
|
+
}, ceiling);
|
|
13976
|
+
});
|
|
13977
|
+
return await Promise.race([done, gaveUp]);
|
|
13880
13978
|
} catch (e) {
|
|
13881
13979
|
const msg = e instanceof Error ? e.message : String(e);
|
|
13882
13980
|
return { ok: false, out: "", rawOut: "", err: msg, exit: msg };
|
|
13883
13981
|
} finally {
|
|
13982
|
+
if (timer)
|
|
13983
|
+
clearTimeout(timer);
|
|
13884
13984
|
if (cmd === GIT) {
|
|
13885
13985
|
lastGit.cmd = `git ${args.slice(0, 3).join(" ")}`;
|
|
13886
13986
|
lastGit.ms = Date.now() - started;
|
|
@@ -13942,6 +14042,80 @@ async function runLive(cmd, args, cwd, opts = {}) {
|
|
|
13942
14042
|
}
|
|
13943
14043
|
}
|
|
13944
14044
|
}
|
|
14045
|
+
async function runRows(cmd, args, cwd, opts) {
|
|
14046
|
+
const started = Date.now();
|
|
14047
|
+
const rowMax = opts.rowMax ?? 8192;
|
|
14048
|
+
let stopped = false;
|
|
14049
|
+
try {
|
|
14050
|
+
const p = Bun.spawn([cmd, ...args], {
|
|
14051
|
+
cwd,
|
|
14052
|
+
stdout: "pipe",
|
|
14053
|
+
stderr: "pipe",
|
|
14054
|
+
stdin: "ignore",
|
|
14055
|
+
env: { ...process.env, ...SPAWN_ENV }
|
|
14056
|
+
});
|
|
14057
|
+
const reader = p.stdout.getReader();
|
|
14058
|
+
const stop = () => {
|
|
14059
|
+
stopped = true;
|
|
14060
|
+
p.kill();
|
|
14061
|
+
reader.cancel().catch(() => {});
|
|
14062
|
+
};
|
|
14063
|
+
if (opts.signal?.aborted)
|
|
14064
|
+
stop();
|
|
14065
|
+
else
|
|
14066
|
+
opts.signal?.addEventListener("abort", stop, { once: true });
|
|
14067
|
+
const pump = async () => {
|
|
14068
|
+
const dec = new TextDecoder;
|
|
14069
|
+
let buf = "";
|
|
14070
|
+
let skipping = false;
|
|
14071
|
+
while (!stopped) {
|
|
14072
|
+
const { done, value } = await reader.read();
|
|
14073
|
+
if (done)
|
|
14074
|
+
break;
|
|
14075
|
+
buf += dec.decode(value, { stream: true });
|
|
14076
|
+
while (!stopped) {
|
|
14077
|
+
const nl = buf.indexOf(`
|
|
14078
|
+
`);
|
|
14079
|
+
if (nl < 0) {
|
|
14080
|
+
if (skipping)
|
|
14081
|
+
buf = "";
|
|
14082
|
+
else if (buf.length > rowMax) {
|
|
14083
|
+
const keep = buf.slice(0, rowMax);
|
|
14084
|
+
buf = "";
|
|
14085
|
+
skipping = true;
|
|
14086
|
+
if (opts.onRow(keep) === false)
|
|
14087
|
+
stop();
|
|
14088
|
+
}
|
|
14089
|
+
break;
|
|
14090
|
+
}
|
|
14091
|
+
const row = buf.slice(0, nl);
|
|
14092
|
+
buf = buf.slice(nl + 1);
|
|
14093
|
+
if (skipping) {
|
|
14094
|
+
skipping = false;
|
|
14095
|
+
continue;
|
|
14096
|
+
}
|
|
14097
|
+
if (row && opts.onRow(row.length > rowMax ? row.slice(0, rowMax) : row) === false)
|
|
14098
|
+
stop();
|
|
14099
|
+
}
|
|
14100
|
+
}
|
|
14101
|
+
if (!stopped && buf && !skipping)
|
|
14102
|
+
opts.onRow(buf);
|
|
14103
|
+
};
|
|
14104
|
+
const [, err, status] = await Promise.all([pump(), new Response(p.stderr).text(), p.exited]);
|
|
14105
|
+
opts.signal?.removeEventListener("abort", stop);
|
|
14106
|
+
const exit = p.signalCode ?? status;
|
|
14107
|
+
return { ok: status === 0 || stopped, out: "", err: err.trim(), exit, stopped };
|
|
14108
|
+
} catch (e) {
|
|
14109
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
14110
|
+
return { ok: false, out: "", err: msg, exit: msg, stopped };
|
|
14111
|
+
} finally {
|
|
14112
|
+
if (cmd === GIT) {
|
|
14113
|
+
lastGit.cmd = `git ${args.slice(0, 3).join(" ")}`;
|
|
14114
|
+
lastGit.ms = Date.now() - started;
|
|
14115
|
+
lastGit.at = Date.now();
|
|
14116
|
+
}
|
|
14117
|
+
}
|
|
14118
|
+
}
|
|
13945
14119
|
function watchHooks(stream, onHook) {
|
|
13946
14120
|
let failed;
|
|
13947
14121
|
if (!stream)
|
|
@@ -14443,7 +14617,26 @@ function planEdited(cwd, rel, proposed) {
|
|
|
14443
14617
|
|
|
14444
14618
|
// ../daemon/src/agent/sandbox.ts
|
|
14445
14619
|
var CACHE_DIRS = [".bun", ".npm", ".cache", ".yarn", ".pnpm-store", "Library/Caches"].map((d) => resolve2(homedir2(), d));
|
|
14446
|
-
var AGENT_SETTINGS = [
|
|
14620
|
+
var AGENT_SETTINGS = [
|
|
14621
|
+
".claude/settings.json",
|
|
14622
|
+
".claude/settings.local.json",
|
|
14623
|
+
".claude/hooks",
|
|
14624
|
+
".claude/agents",
|
|
14625
|
+
".codex/config.toml",
|
|
14626
|
+
".codex/hooks.json",
|
|
14627
|
+
".codex/hooks",
|
|
14628
|
+
".codex/rules",
|
|
14629
|
+
"opencode.json",
|
|
14630
|
+
"opencode.jsonc",
|
|
14631
|
+
".opencode/opencode.json",
|
|
14632
|
+
".opencode/opencode.jsonc",
|
|
14633
|
+
".opencode/plugin",
|
|
14634
|
+
".opencode/plugins",
|
|
14635
|
+
".opencode/tool",
|
|
14636
|
+
".opencode/tools",
|
|
14637
|
+
".opencode/agent",
|
|
14638
|
+
".opencode/agents"
|
|
14639
|
+
];
|
|
14447
14640
|
function uniq2(xs) {
|
|
14448
14641
|
return [...new Set(xs)];
|
|
14449
14642
|
}
|
|
@@ -14588,10 +14781,7 @@ function installErrorLine(stderr) {
|
|
|
14588
14781
|
}
|
|
14589
14782
|
var UNTRUSTED_CA = /UNKNOWN_CERTIFICATE_VERIFICATION_ERROR|SELF_SIGNED_CERT|UNABLE_TO_GET_ISSUER_CERT|UNABLE_TO_VERIFY_LEAF_SIGNATURE/;
|
|
14590
14783
|
var bunInstall = async (dir) => {
|
|
14591
|
-
const r = await run(process.execPath, ["install", "--no-progress", "--no-summary"], dir, {
|
|
14592
|
-
NO_COLOR: "1",
|
|
14593
|
-
FORCE_COLOR: "0"
|
|
14594
|
-
});
|
|
14784
|
+
const r = await run(process.execPath, ["install", "--no-progress", "--no-summary"], dir, { NO_COLOR: "1", FORCE_COLOR: "0" }, { timeoutMs: 30 * 60000 });
|
|
14595
14785
|
return { ok: r.ok, err: r.ok ? "" : installErrorLine(r.err) };
|
|
14596
14786
|
};
|
|
14597
14787
|
var RETRY_AFTER_MS = 30000;
|
|
@@ -15676,7 +15866,7 @@ function decide(req, bounds, cwd, mode = "auto", sandboxed = true) {
|
|
|
15676
15866
|
kind: "reject",
|
|
15677
15867
|
tool,
|
|
15678
15868
|
path: raw,
|
|
15679
|
-
reason: "Agent settings are
|
|
15869
|
+
reason: "Agent settings, hooks and plugin code are yours to change, never the agent's."
|
|
15680
15870
|
};
|
|
15681
15871
|
}
|
|
15682
15872
|
if (kind && NON_WRITE_KINDS.has(kind))
|
|
@@ -15718,14 +15908,14 @@ var SYSTEM_APPEND = [
|
|
|
15718
15908
|
"You are working inside a dedicated git worktree managed by Toyon.",
|
|
15719
15909
|
"Make every change inside the current working directory and never modify files outside it. Reading files outside it is fine when the user points you there.",
|
|
15720
15910
|
"When a change belongs in another project Toyon has open rather than in this worktree, do not edit outside it and do not stop at saying so: propose it with the `handoff` tool when you have one, naming the project and writing the message its agent will start from, with what is needed and why, how to reproduce it, and the paths here that depend on it; the user decides on a card, and once it is proposed you finish your turn.",
|
|
15721
|
-
"A write Toyon refuses (outside the worktree, or to an agent's
|
|
15911
|
+
"A write Toyon refuses (outside the worktree, or to an agent's settings, hooks or plugin code such as .claude/settings.json, .claude/hooks or opencode.json) was refused by Toyon and not by the user, who was never asked. Report it as the boundary that stopped it, never as the user declining.",
|
|
15722
15912
|
"Never run `git push`, delete branches, or create pull requests, and never offer to; shipping is handled by the Toyon UI.",
|
|
15723
15913
|
"Never run `git commit` unless the user explicitly asks you to, and never offer to, through the question tool or otherwise; leave changes uncommitted for the user to review and commit themselves.",
|
|
15724
15914
|
"Keep the scope tight: do the asked task well, then stop. A follow-up the user might want later is a sentence in your reply, not a question.",
|
|
15725
15915
|
"When the work is done and the only thing left is whether to do something you recommend, do not end your turn on a prose offer waiting for a yes. Ask it with your question tool when you have one: one question, two options with the recommended one first, each saying what it changes, and typed text allowed so the answer can carry instructions. The card is the first thing the user reads, before any prose of yours, so the question itself states what you propose and why in a sentence or two; a question that is only the yes-or-no leaves them guessing what it is. Ask this way only where you would otherwise have stopped and waited; never to confirm work already asked for.",
|
|
15726
15916
|
"An answer to a question of yours can carry typed text beside the option picked, and the text outranks the pick. Text that doubts the pick, asks you something or says the user cannot decide is not a yes: answer it in prose and change nothing until they have decided. Text that only adds detail to the pick is an instruction for carrying it out.",
|
|
15727
15917
|
"When referencing a file in chat, use a Markdown link whose target is its absolute path, with an optional `:line`; Toyon opens it in the built-in editor, whether it is in this worktree or anywhere else on this machine. A folder, a URL route or a command is not a file: name it in backticks, never as a link.",
|
|
15728
|
-
"`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists.",
|
|
15918
|
+
"`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists; `@<branch>` names another worktree of this project, and the context attached to the message says where its chat is on disk.",
|
|
15729
15919
|
'Toyon previews the project in a browser panel by running the commands in its settings file, .toyon/settings.json (or toyon.json at the repo root, in a repo that keeps it there), shaped like {"setup": ["bun install"], "run": {"web": "bun run dev --port $PORT"}, "check": "bun run check", "land": {"route": "merge"}}: setup runs once in every new worktree, teardown (optional) runs once when a worktree is removed, every command in run keeps running and must listen on $PORT, which toyon sets differently for each worktree, check (optional) runs after each of your turns and must pass before the work can land, and land (optional) says how the user lands work on main: route "merge" (onto main here), "push" (onto main here, then pushed) or "pr" (a pull request); method "merge", "squash" or "rebase" for how the commits arrive on main; automerge true when GitHub should merge the pull request itself; timeouts (optional) gives setup, check and commit each a ceiling as a duration like "30m" (ten minutes each when unset), for a suite that runs longer. A settings.local.json beside it (toyon.local.json at the root) holds one person\'s overrides and is never committed. Both files may carry comments and trailing commas; leave any you find.',
|
|
15730
15920
|
"Toyon runs those commands itself in every worktree, and the user watches the result live in a preview beside this chat, so never start a dev server or open a browser of your own to check your work. Each message says where this worktree's preview answers; fetch a page from there when you want to see it.",
|
|
15731
15921
|
"When you scaffold a project, write its .gitignore (dependencies, build output, local env files) before installing anything, so an install never leaves thousands of untracked files for the user to wade through or commit.",
|
|
@@ -15748,7 +15938,7 @@ function fixPrompt(f) {
|
|
|
15748
15938
|
case "hook":
|
|
15749
15939
|
return hookFixPrompt(f.hook);
|
|
15750
15940
|
case "conflict":
|
|
15751
|
-
return conflictFixPrompt(f.base, f.how, !!f.step);
|
|
15941
|
+
return conflictFixPrompt(f.base, f.how, !!f.step, f.carried);
|
|
15752
15942
|
case "check":
|
|
15753
15943
|
return checkFixPrompt(f.command);
|
|
15754
15944
|
case "command":
|
|
@@ -15763,6 +15953,8 @@ function fixWhy(f) {
|
|
|
15763
15953
|
case "hook":
|
|
15764
15954
|
return `the ${f.hook} hook refused the ${f.hook.includes("push") ? "push" : "commit"}`;
|
|
15765
15955
|
case "conflict":
|
|
15956
|
+
if (f.carried)
|
|
15957
|
+
return `the uncommitted changes conflict with ${f.base}`;
|
|
15766
15958
|
return f.how === "rebase" ? `the branch needs a rebase onto ${f.base}` : `the branch needs ${f.base} merged in`;
|
|
15767
15959
|
case "check":
|
|
15768
15960
|
return "the check failed";
|
|
@@ -15793,8 +15985,12 @@ function hookFixPrompt(hook) {
|
|
|
15793
15985
|
"Do not skip the hook, do not edit it or its configuration to get past it, and do not commit: Toyon runs the check when your turn ends, and the user lands from there."
|
|
15794
15986
|
].join(" ");
|
|
15795
15987
|
}
|
|
15796
|
-
function conflictFixPrompt(base, how, shown) {
|
|
15988
|
+
function conflictFixPrompt(base, how, shown, carried) {
|
|
15797
15989
|
const ask = how === "rebase" ? `Rebase this branch onto ${base} and resolve the conflicts, keeping any uncommitted changes, then verify the app still works.` : `Merge ${base} into this branch, resolve the conflicts and commit the merge, then verify the app still works.`;
|
|
15990
|
+
if (carried) {
|
|
15991
|
+
const where = carried.length ? `, in ${carried.join(", ")}` : "";
|
|
15992
|
+
return `${ask} The branch's own commits take ${base} cleanly; the conflicts are in the uncommitted changes${where}.`;
|
|
15993
|
+
}
|
|
15798
15994
|
return shown ? `${ask} What git printed when Toyon tried is attached below.` : ask;
|
|
15799
15995
|
}
|
|
15800
15996
|
function checkFixPrompt(command) {
|
|
@@ -15873,6 +16069,17 @@ function handoffAskPrompt(repo, text) {
|
|
|
15873
16069
|
function handoffLandedContext(title, project, url) {
|
|
15874
16070
|
return `The work this worktree handed off to ${project}, "${title}", has landed there${url ? ` (${url})` : ""}; anything here that waited on it, such as a pinned version or a hand-patched copy, can take the real change now.`;
|
|
15875
16071
|
}
|
|
16072
|
+
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16073
|
+
function worktreeContext(text, siblings) {
|
|
16074
|
+
const out = [];
|
|
16075
|
+
for (const s of siblings) {
|
|
16076
|
+
const named = new RegExp(`(^|\\s)@${escapeRegExp(s.branch)}(?=$|[\\s.,;:!?)\\]}'"])`);
|
|
16077
|
+
if (!named.test(text))
|
|
16078
|
+
continue;
|
|
16079
|
+
out.push(`@${s.branch} is the worktree "${s.title}" in this project. Its chat is at ${s.transcript}, one JSON object per line: a "user-message" event is what the person wrote, the "text-delta" events after it are the agent's reply, and the rest is tool activity.`);
|
|
16080
|
+
}
|
|
16081
|
+
return out;
|
|
16082
|
+
}
|
|
15876
16083
|
function ambientBlock(paragraphs) {
|
|
15877
16084
|
const said = paragraphs.filter((p) => !!p);
|
|
15878
16085
|
if (said.length === 0)
|
|
@@ -16811,7 +17018,13 @@ class AcpSession {
|
|
|
16811
17018
|
this.emit({ type: "turn-end", stopReason: mapStopReason(res.stopReason), ts: Date.now() });
|
|
16812
17019
|
}
|
|
16813
17020
|
contextFor(item) {
|
|
16814
|
-
return ambientBlock([
|
|
17021
|
+
return ambientBlock([
|
|
17022
|
+
...item.context ?? [],
|
|
17023
|
+
...this.d.mentioned?.(item.text) ?? [],
|
|
17024
|
+
this.d.preview?.(),
|
|
17025
|
+
this.d.pageErrors?.(),
|
|
17026
|
+
...this.landedSince()
|
|
17027
|
+
]);
|
|
16815
17028
|
}
|
|
16816
17029
|
landedSince() {
|
|
16817
17030
|
const entries = this.log.entries;
|
|
@@ -18574,6 +18787,11 @@ function defaultAgent(wt, d, preview, onProcess) {
|
|
|
18574
18787
|
cwd: wt.path,
|
|
18575
18788
|
preview: () => previewContext(preview()),
|
|
18576
18789
|
pageErrors: () => d.pageErrors?.(wt.id),
|
|
18790
|
+
mentioned: (text) => worktreeContext(text, d.state.worktrees.filter((w) => w.repoId === wt.repoId && w.id !== wt.id && w.kind === "worktree").map((w) => ({
|
|
18791
|
+
branch: w.branch,
|
|
18792
|
+
title: w.title,
|
|
18793
|
+
transcript: transcriptPathFor(d.paths.transcriptsDir, w.id)
|
|
18794
|
+
}))),
|
|
18577
18795
|
onProcess,
|
|
18578
18796
|
spec,
|
|
18579
18797
|
connect: (app, spec, prepared) => spawnAcp(app, d.agents.launch(spec, prepared), wt.path, wt.id),
|
|
@@ -20447,13 +20665,14 @@ class KeepAwake {
|
|
|
20447
20665
|
import { existsSync as existsSync16 } from "fs";
|
|
20448
20666
|
import { homedir as homedir6 } from "os";
|
|
20449
20667
|
var PROMPTS = {
|
|
20450
|
-
location: "Choose where the new project goes",
|
|
20451
|
-
open: "Choose a project folder to open"
|
|
20668
|
+
location: { prompt: "Choose where the new project goes", pick: "folder" },
|
|
20669
|
+
open: { prompt: "Choose a project folder to open", pick: "folder" },
|
|
20670
|
+
file: { prompt: "Choose a file to open", pick: "file" }
|
|
20452
20671
|
};
|
|
20453
|
-
var
|
|
20672
|
+
var script = (pick) => [
|
|
20454
20673
|
"on run argv",
|
|
20455
20674
|
"activate",
|
|
20456
|
-
|
|
20675
|
+
`POSIX path of (choose ${pick} with prompt (item 1 of argv) default location (POSIX file (item 2 of argv)))`,
|
|
20457
20676
|
"end run"
|
|
20458
20677
|
];
|
|
20459
20678
|
function folderDialog() {
|
|
@@ -20464,7 +20683,8 @@ function folderDialog() {
|
|
|
20464
20683
|
const typed = start === "~" || start.startsWith("~/") ? `${homedir6()}${start.slice(1)}` : start;
|
|
20465
20684
|
const from = typed.startsWith("/") && existsSync16(typed) ? typed : homedir6();
|
|
20466
20685
|
cancelled = false;
|
|
20467
|
-
const
|
|
20686
|
+
const { prompt, pick } = PROMPTS[purpose];
|
|
20687
|
+
const p = Bun.spawn(["osascript", ...script(pick).flatMap((line) => ["-e", line]), prompt, from], {
|
|
20468
20688
|
stdout: "pipe",
|
|
20469
20689
|
stderr: "pipe",
|
|
20470
20690
|
stdin: "ignore"
|
|
@@ -20485,7 +20705,7 @@ function folderDialog() {
|
|
|
20485
20705
|
return {
|
|
20486
20706
|
choose(start, purpose) {
|
|
20487
20707
|
if (process.platform !== "darwin") {
|
|
20488
|
-
return Promise.reject(new UserError(
|
|
20708
|
+
return Promise.reject(new UserError(`choosing a ${PROMPTS[purpose].pick} in Finder is only available on macOS`));
|
|
20489
20709
|
}
|
|
20490
20710
|
if (!open) {
|
|
20491
20711
|
open = ask(start, purpose).finally(() => {
|
|
@@ -20933,8 +21153,21 @@ class RemoteSwitch {
|
|
|
20933
21153
|
|
|
20934
21154
|
// ../daemon/src/core/restart.ts
|
|
20935
21155
|
import { spawn as spawn4 } from "child_process";
|
|
20936
|
-
import { openSync } from "fs";
|
|
20937
21156
|
import { homedir as homedir7 } from "os";
|
|
21157
|
+
|
|
21158
|
+
// ../shared/src/logfile.ts
|
|
21159
|
+
import { chmodSync, existsSync as existsSync18, openSync, renameSync as renameSync3, statSync as statSync6 } from "fs";
|
|
21160
|
+
var LOG_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
21161
|
+
var previousLog = (path) => `${path}.1`;
|
|
21162
|
+
function openLog(path, maxBytes = LOG_ROTATE_BYTES) {
|
|
21163
|
+
if (existsSync18(path) && statSync6(path).size > maxBytes)
|
|
21164
|
+
renameSync3(path, previousLog(path));
|
|
21165
|
+
const fd = openSync(path, "a", 384);
|
|
21166
|
+
chmodSync(path, 384);
|
|
21167
|
+
return fd;
|
|
21168
|
+
}
|
|
21169
|
+
|
|
21170
|
+
// ../daemon/src/core/restart.ts
|
|
20938
21171
|
function restartable(env = process.env) {
|
|
20939
21172
|
if (env.TOYON_WORKTREE !== undefined) {
|
|
20940
21173
|
return { ok: false, reason: "this daemon runs as a toyon process; restart it from its terminal tab" };
|
|
@@ -20952,7 +21185,7 @@ async function replacementRuns() {
|
|
|
20952
21185
|
};
|
|
20953
21186
|
}
|
|
20954
21187
|
function respawn(logFile) {
|
|
20955
|
-
const logFd =
|
|
21188
|
+
const logFd = openLog(logFile);
|
|
20956
21189
|
const child = spawn4(process.execPath, process.argv.slice(1), {
|
|
20957
21190
|
detached: true,
|
|
20958
21191
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -21095,7 +21328,7 @@ class SelfWatch {
|
|
|
21095
21328
|
|
|
21096
21329
|
// ../daemon/src/core/state.ts
|
|
21097
21330
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
21098
|
-
import { existsSync as
|
|
21331
|
+
import { existsSync as existsSync19, readFileSync as readFileSync12, renameSync as renameSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
21099
21332
|
var TRUSTED_MAX = 16;
|
|
21100
21333
|
var DAEMON_GROUPS = "daemon";
|
|
21101
21334
|
var GROUPS_SAVE_MS = 250;
|
|
@@ -21104,7 +21337,7 @@ var empty = { repos: [], worktrees: [], sessions: {} };
|
|
|
21104
21337
|
function loadState(paths) {
|
|
21105
21338
|
ensureDirs(paths);
|
|
21106
21339
|
const STATE_FILE = paths.stateFile;
|
|
21107
|
-
if (!
|
|
21340
|
+
if (!existsSync19(STATE_FILE))
|
|
21108
21341
|
return structuredClone(empty);
|
|
21109
21342
|
let raw;
|
|
21110
21343
|
try {
|
|
@@ -21151,12 +21384,12 @@ function loadState(paths) {
|
|
|
21151
21384
|
function saveState(paths, state) {
|
|
21152
21385
|
const tmp = `${paths.stateFile}.tmp`;
|
|
21153
21386
|
writeFileSync8(tmp, JSON.stringify(state, null, 2));
|
|
21154
|
-
|
|
21387
|
+
renameSync4(tmp, paths.stateFile);
|
|
21155
21388
|
}
|
|
21156
21389
|
function loadOrCreateToken(paths) {
|
|
21157
21390
|
ensureDirs(paths);
|
|
21158
21391
|
const TOKEN_FILE = paths.tokenFile;
|
|
21159
|
-
if (
|
|
21392
|
+
if (existsSync19(TOKEN_FILE))
|
|
21160
21393
|
return readFileSync12(TOKEN_FILE, "utf8").trim();
|
|
21161
21394
|
const token = randomBytes6(32).toString("hex");
|
|
21162
21395
|
writeFileSync8(TOKEN_FILE, token, { mode: 384 });
|
|
@@ -21505,7 +21738,7 @@ class Tailnet {
|
|
|
21505
21738
|
}
|
|
21506
21739
|
async start() {
|
|
21507
21740
|
await this.refresh();
|
|
21508
|
-
this.timer = setInterval(() =>
|
|
21741
|
+
this.timer = setInterval(() => fireAndForget("tailscale", this.refresh(), "tailscale readiness"), READINESS_EVERY_MS);
|
|
21509
21742
|
this.timer.unref?.();
|
|
21510
21743
|
}
|
|
21511
21744
|
stop() {
|
|
@@ -21564,7 +21797,10 @@ class Tailnet {
|
|
|
21564
21797
|
const now = (this.d.now ?? Date.now)();
|
|
21565
21798
|
if (this.status === null || fresh || now - this.statusAt > STATUS_FRESH_MS) {
|
|
21566
21799
|
this.statusAt = now;
|
|
21567
|
-
this.status = ts(["status", "--json"]).then((r) => r.ok ? r.out : r.out || r.err)
|
|
21800
|
+
this.status = ts(["status", "--json"]).then((r) => r.ok ? r.out : r.out || r.err, (e) => {
|
|
21801
|
+
log.warn("tailscale", "could not run tailscale status", e);
|
|
21802
|
+
return "";
|
|
21803
|
+
});
|
|
21568
21804
|
}
|
|
21569
21805
|
return this.status;
|
|
21570
21806
|
}
|
|
@@ -22191,7 +22427,7 @@ async function loadProjectTypescript(root) {
|
|
|
22191
22427
|
}
|
|
22192
22428
|
|
|
22193
22429
|
// ../daemon/src/drafts/store.ts
|
|
22194
|
-
import { existsSync as
|
|
22430
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13, renameSync as renameSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
22195
22431
|
var SAVE_DELAY_MS = 1000;
|
|
22196
22432
|
|
|
22197
22433
|
class DraftStore {
|
|
@@ -22328,7 +22564,7 @@ class DraftStore {
|
|
|
22328
22564
|
}
|
|
22329
22565
|
}
|
|
22330
22566
|
function readRecord(file) {
|
|
22331
|
-
if (!
|
|
22567
|
+
if (!existsSync20(file))
|
|
22332
22568
|
return [];
|
|
22333
22569
|
try {
|
|
22334
22570
|
const raw = JSON.parse(readFileSync13(file, "utf8"));
|
|
@@ -22342,7 +22578,7 @@ function writeAtomic(file, value) {
|
|
|
22342
22578
|
const tmp = `${file}.tmp`;
|
|
22343
22579
|
try {
|
|
22344
22580
|
writeFileSync9(tmp, JSON.stringify(value));
|
|
22345
|
-
|
|
22581
|
+
renameSync5(tmp, file);
|
|
22346
22582
|
} catch (e) {
|
|
22347
22583
|
log.error("drafts", `could not write ${file}`, e);
|
|
22348
22584
|
}
|
|
@@ -22588,7 +22824,7 @@ import { stat as stat3 } from "fs/promises";
|
|
|
22588
22824
|
import { basename as basename6, resolve as resolve7 } from "path";
|
|
22589
22825
|
|
|
22590
22826
|
// ../daemon/src/repos/browse.ts
|
|
22591
|
-
import { existsSync as
|
|
22827
|
+
import { existsSync as existsSync21, statSync as statSync7 } from "fs";
|
|
22592
22828
|
import { readdir } from "fs/promises";
|
|
22593
22829
|
import { homedir as homedir8 } from "os";
|
|
22594
22830
|
import { dirname as dirname6, join as join16 } from "path";
|
|
@@ -22605,7 +22841,7 @@ function holdsNothing(names) {
|
|
|
22605
22841
|
}
|
|
22606
22842
|
async function describeFolder(abs) {
|
|
22607
22843
|
const path = collapseTilde(abs);
|
|
22608
|
-
if (
|
|
22844
|
+
if (existsSync21(join16(abs, ".git")))
|
|
22609
22845
|
return { path, kind: "project" };
|
|
22610
22846
|
try {
|
|
22611
22847
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -22613,17 +22849,21 @@ async function describeFolder(abs) {
|
|
|
22613
22849
|
return { path, kind: "folder" };
|
|
22614
22850
|
}
|
|
22615
22851
|
}
|
|
22616
|
-
var NOWHERE = { exists: false, isDir: false, isRepo: false, parentExists: false };
|
|
22852
|
+
var NOWHERE = { exists: false, isDir: false, isRepo: false, isFile: false, parentExists: false };
|
|
22617
22853
|
function describe2(typed, parent) {
|
|
22618
22854
|
let isDir = false;
|
|
22855
|
+
let isFile = false;
|
|
22619
22856
|
try {
|
|
22620
|
-
|
|
22857
|
+
const st = statSync7(typed);
|
|
22858
|
+
isDir = st.isDirectory();
|
|
22859
|
+
isFile = st.isFile();
|
|
22621
22860
|
} catch {}
|
|
22622
22861
|
return {
|
|
22623
|
-
exists:
|
|
22862
|
+
exists: existsSync21(typed),
|
|
22624
22863
|
isDir,
|
|
22625
|
-
isRepo: isDir &&
|
|
22626
|
-
|
|
22864
|
+
isRepo: isDir && existsSync21(join16(typed, ".git")),
|
|
22865
|
+
isFile,
|
|
22866
|
+
parentExists: existsSync21(parent)
|
|
22627
22867
|
};
|
|
22628
22868
|
}
|
|
22629
22869
|
async function browsePath(raw) {
|
|
@@ -22634,25 +22874,37 @@ async function browsePath(raw) {
|
|
|
22634
22874
|
const dir = listing ? typed : dirname6(typed);
|
|
22635
22875
|
const prefix = listing ? "" : typed.slice(dir.length).replace(/^\//, "").toLowerCase();
|
|
22636
22876
|
const target = describe2(listing ? typed.replace(/\/+$/, "") : typed, dir);
|
|
22637
|
-
let
|
|
22877
|
+
let found;
|
|
22638
22878
|
try {
|
|
22639
|
-
const
|
|
22640
|
-
|
|
22879
|
+
const listed = await readdir(dir, { withFileTypes: true });
|
|
22880
|
+
found = listed.map((e) => ({
|
|
22881
|
+
name: e.name,
|
|
22882
|
+
isFile: e.isSymbolicLink() ? isFileAt(join16(dir, e.name)) : e.isFile() ? true : e.isDirectory() ? false : null
|
|
22883
|
+
})).filter((e) => e.isFile !== null);
|
|
22641
22884
|
} catch {
|
|
22642
22885
|
return { entries: [], target };
|
|
22643
22886
|
}
|
|
22644
22887
|
const entries = [];
|
|
22645
|
-
for (const name of
|
|
22888
|
+
for (const { name, isFile } of found) {
|
|
22646
22889
|
if (name.startsWith(".") && !prefix.startsWith("."))
|
|
22647
22890
|
continue;
|
|
22648
22891
|
if (!name.toLowerCase().startsWith(prefix))
|
|
22649
22892
|
continue;
|
|
22650
22893
|
const abs = join16(dir, name);
|
|
22651
|
-
entries.push({ path: collapseTilde(abs), name, isRepo:
|
|
22894
|
+
entries.push({ path: collapseTilde(abs), name, isRepo: !isFile && existsSync21(join16(abs, ".git")), isFile });
|
|
22652
22895
|
}
|
|
22653
|
-
|
|
22896
|
+
const rank = (e) => e.isRepo ? 0 : e.isFile ? 2 : 1;
|
|
22897
|
+
entries.sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name));
|
|
22654
22898
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
22655
22899
|
}
|
|
22900
|
+
function isFileAt(abs) {
|
|
22901
|
+
try {
|
|
22902
|
+
const st = statSync7(abs);
|
|
22903
|
+
return st.isFile() ? true : st.isDirectory() ? false : null;
|
|
22904
|
+
} catch {
|
|
22905
|
+
return null;
|
|
22906
|
+
}
|
|
22907
|
+
}
|
|
22656
22908
|
|
|
22657
22909
|
// ../daemon/src/files/content.ts
|
|
22658
22910
|
function versionOf(bytes) {
|
|
@@ -22855,20 +23107,20 @@ async function numstat(worktreePath, ...range) {
|
|
|
22855
23107
|
const r = await git(worktreePath, "diff", "--numstat", "--no-renames", ...range);
|
|
22856
23108
|
return r.ok ? parseNumstat(r.out) : new Map;
|
|
22857
23109
|
}
|
|
22858
|
-
async function untrackedLines(worktreePath, file) {
|
|
23110
|
+
async function untrackedLines(worktreePath, file, budget) {
|
|
22859
23111
|
try {
|
|
22860
23112
|
const f = Bun.file(join17(worktreePath, file));
|
|
22861
|
-
if (f.size > 2000000)
|
|
23113
|
+
if (f.size > 2000000 || f.size > budget.left)
|
|
22862
23114
|
return {};
|
|
23115
|
+
budget.left -= f.size;
|
|
22863
23116
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
22864
23117
|
if (buf.subarray(0, 8000).includes(0))
|
|
22865
23118
|
return {};
|
|
22866
23119
|
if (buf.length === 0)
|
|
22867
23120
|
return { add: 0, del: 0 };
|
|
22868
23121
|
let n = 0;
|
|
22869
|
-
for (
|
|
22870
|
-
|
|
22871
|
-
n++;
|
|
23122
|
+
for (let i = buf.indexOf(10);i !== -1; i = buf.indexOf(10, i + 1))
|
|
23123
|
+
n++;
|
|
22872
23124
|
if (buf[buf.length - 1] !== 10)
|
|
22873
23125
|
n++;
|
|
22874
23126
|
return { add: n, del: 0 };
|
|
@@ -22877,16 +23129,38 @@ async function untrackedLines(worktreePath, file) {
|
|
|
22877
23129
|
}
|
|
22878
23130
|
}
|
|
22879
23131
|
var MAX_UNTRACKED_COUNTED = 500;
|
|
22880
|
-
|
|
22881
|
-
|
|
23132
|
+
var UNTRACKED_BYTES_MAX = 16 * 1024 * 1024;
|
|
23133
|
+
function statusEntries(worktreePath) {
|
|
23134
|
+
return readStatus(worktreePath, []);
|
|
23135
|
+
}
|
|
23136
|
+
async function countEntries(worktreePath, files, folded, bytesMax = UNTRACKED_BYTES_MAX) {
|
|
23137
|
+
const out = new Map;
|
|
22882
23138
|
if (files.length === 0)
|
|
22883
|
-
return
|
|
22884
|
-
const
|
|
22885
|
-
const
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
23139
|
+
return out;
|
|
23140
|
+
const tracked = files.some((f) => f.xy !== "??" && !folded.has(f.path));
|
|
23141
|
+
const counts = tracked ? await numstat(worktreePath, "HEAD") : new Map;
|
|
23142
|
+
const untracked = files.filter((f) => f.xy === "??");
|
|
23143
|
+
const countUntracked = untracked.length <= MAX_UNTRACKED_COUNTED;
|
|
23144
|
+
const budget = { left: bytesMax };
|
|
23145
|
+
for (const f of files) {
|
|
23146
|
+
if (folded.has(f.path)) {
|
|
23147
|
+
if (f.add !== undefined || f.del !== undefined)
|
|
23148
|
+
out.set(f.path, { add: f.add, del: f.del });
|
|
23149
|
+
continue;
|
|
23150
|
+
}
|
|
23151
|
+
if (f.xy !== "??") {
|
|
23152
|
+
const c = counts.get(f.path);
|
|
23153
|
+
if (c)
|
|
23154
|
+
out.set(f.path, c);
|
|
23155
|
+
continue;
|
|
23156
|
+
}
|
|
23157
|
+
if (!countUntracked)
|
|
23158
|
+
continue;
|
|
23159
|
+
const c = await untrackedLines(worktreePath, f.path, budget);
|
|
23160
|
+
if (c.add !== undefined)
|
|
23161
|
+
out.set(f.path, c);
|
|
23162
|
+
}
|
|
23163
|
+
return out;
|
|
22890
23164
|
}
|
|
22891
23165
|
async function committedFiles(worktreePath, defaultBr) {
|
|
22892
23166
|
const base = await git(worktreePath, "merge-base", "HEAD", defaultBr);
|
|
@@ -23209,6 +23483,20 @@ async function viteLineOffset(worktreePath, path, target) {
|
|
|
23209
23483
|
|
|
23210
23484
|
// ../daemon/src/files/service.ts
|
|
23211
23485
|
var SEARCH_MAX = 300;
|
|
23486
|
+
var SEARCH_PER_FILE = 20;
|
|
23487
|
+
var HIT_TEXT_MAX = 200;
|
|
23488
|
+
function parseGrepRow(row) {
|
|
23489
|
+
const a = row.indexOf("\x00");
|
|
23490
|
+
if (a < 0)
|
|
23491
|
+
return null;
|
|
23492
|
+
const b = row.indexOf("\x00", a + 1);
|
|
23493
|
+
if (b < 0)
|
|
23494
|
+
return null;
|
|
23495
|
+
const line = Number(row.slice(a + 1, b));
|
|
23496
|
+
if (!Number.isInteger(line) || line < 1)
|
|
23497
|
+
return null;
|
|
23498
|
+
return { path: row.slice(0, a), line, text: row.slice(b + 1) };
|
|
23499
|
+
}
|
|
23212
23500
|
var FIND_ROWS_MAX = 5000;
|
|
23213
23501
|
var NOT_WHERE_WRITTEN = [
|
|
23214
23502
|
":!*.css",
|
|
@@ -23282,6 +23570,7 @@ class FileService {
|
|
|
23282
23570
|
this.runtime = runtime;
|
|
23283
23571
|
this.readable = readable;
|
|
23284
23572
|
}
|
|
23573
|
+
searches = new Map;
|
|
23285
23574
|
require(worktreeId) {
|
|
23286
23575
|
const r = this.readable(worktreeId);
|
|
23287
23576
|
if (!r)
|
|
@@ -23361,37 +23650,73 @@ class FileService {
|
|
|
23361
23650
|
]);
|
|
23362
23651
|
return parseListing(staged.out, untracked.out, deleted.out, ignored.out);
|
|
23363
23652
|
}
|
|
23364
|
-
async search(worktreeId, query) {
|
|
23653
|
+
async search(worktreeId, query, asker) {
|
|
23365
23654
|
const { path: cwd } = this.require(worktreeId);
|
|
23366
23655
|
const q = query.trim();
|
|
23656
|
+
if (q.length < 2)
|
|
23657
|
+
return { hits: [], truncated: false };
|
|
23658
|
+
this.searches.get(asker)?.abort();
|
|
23659
|
+
const ctl = new AbortController;
|
|
23660
|
+
this.searches.set(asker, ctl);
|
|
23367
23661
|
const hits = [];
|
|
23368
23662
|
let truncated = false;
|
|
23369
|
-
|
|
23370
|
-
|
|
23371
|
-
|
|
23372
|
-
|
|
23373
|
-
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
|
|
23381
|
-
|
|
23382
|
-
|
|
23383
|
-
|
|
23663
|
+
let inFile = "";
|
|
23664
|
+
let inFileRows = 0;
|
|
23665
|
+
try {
|
|
23666
|
+
await runRows(GIT, [
|
|
23667
|
+
"grep",
|
|
23668
|
+
"-n",
|
|
23669
|
+
"-z",
|
|
23670
|
+
"-I",
|
|
23671
|
+
"-i",
|
|
23672
|
+
"-F",
|
|
23673
|
+
"--untracked",
|
|
23674
|
+
"--no-color",
|
|
23675
|
+
`--max-count=${SEARCH_PER_FILE + 1}`,
|
|
23676
|
+
"-e",
|
|
23677
|
+
q,
|
|
23678
|
+
"--"
|
|
23679
|
+
], cwd, {
|
|
23680
|
+
signal: ctl.signal,
|
|
23681
|
+
onRow: (row) => {
|
|
23682
|
+
const hit = parseGrepRow(row);
|
|
23683
|
+
if (!hit)
|
|
23684
|
+
return;
|
|
23685
|
+
if (hit.path !== inFile) {
|
|
23686
|
+
inFile = hit.path;
|
|
23687
|
+
inFileRows = 0;
|
|
23688
|
+
}
|
|
23689
|
+
if (++inFileRows > SEARCH_PER_FILE) {
|
|
23690
|
+
truncated = true;
|
|
23691
|
+
return;
|
|
23692
|
+
}
|
|
23693
|
+
if (hits.length >= SEARCH_MAX) {
|
|
23694
|
+
truncated = true;
|
|
23695
|
+
return false;
|
|
23696
|
+
}
|
|
23697
|
+
hits.push({ ...hit, text: hit.text.trim().slice(0, HIT_TEXT_MAX) });
|
|
23698
|
+
}
|
|
23699
|
+
});
|
|
23700
|
+
} finally {
|
|
23701
|
+
if (this.searches.get(asker) === ctl)
|
|
23702
|
+
this.searches.delete(asker);
|
|
23384
23703
|
}
|
|
23385
|
-
return { hits, truncated };
|
|
23704
|
+
return ctl.signal.aborted ? null : { hits, truncated };
|
|
23705
|
+
}
|
|
23706
|
+
stopSearch(asker) {
|
|
23707
|
+
this.searches.get(asker)?.abort();
|
|
23708
|
+
this.searches.delete(asker);
|
|
23386
23709
|
}
|
|
23387
23710
|
async findElement(worktreeId, element) {
|
|
23388
23711
|
const { path: cwd } = this.require(worktreeId);
|
|
23389
23712
|
const needles = needlesOf(element);
|
|
23390
23713
|
if (!needles.length)
|
|
23391
23714
|
return { hits: [], sure: false };
|
|
23392
|
-
const
|
|
23715
|
+
const rows = [];
|
|
23716
|
+
await runRows(GIT, [
|
|
23393
23717
|
"grep",
|
|
23394
23718
|
"-n",
|
|
23719
|
+
"-z",
|
|
23395
23720
|
"-I",
|
|
23396
23721
|
"-F",
|
|
23397
23722
|
"--untracked",
|
|
@@ -23400,17 +23725,14 @@ class FileService {
|
|
|
23400
23725
|
...needles.flatMap((n) => ["-e", n.text]),
|
|
23401
23726
|
"--",
|
|
23402
23727
|
...NOT_WHERE_WRITTEN
|
|
23403
|
-
], cwd
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
if (rows.length >= FIND_ROWS_MAX)
|
|
23412
|
-
break;
|
|
23413
|
-
}
|
|
23728
|
+
], cwd, {
|
|
23729
|
+
onRow: (row) => {
|
|
23730
|
+
const hit = parseGrepRow(row);
|
|
23731
|
+
if (hit)
|
|
23732
|
+
rows.push(hit);
|
|
23733
|
+
return rows.length < FIND_ROWS_MAX;
|
|
23734
|
+
}
|
|
23735
|
+
});
|
|
23414
23736
|
return rankElementSources(element, rows);
|
|
23415
23737
|
}
|
|
23416
23738
|
async changedRanges(worktreeId, path) {
|
|
@@ -23459,12 +23781,13 @@ class OpenService {
|
|
|
23459
23781
|
if (st.isDirectory()) {
|
|
23460
23782
|
if (!await isGitRepo(path))
|
|
23461
23783
|
throw new UserError(`${raw} is not a git repository: open one, or a file`);
|
|
23462
|
-
return { kind: "repo", repoId: (await this.d.register(path)).id };
|
|
23784
|
+
return { kind: "repo", repoId: (await this.d.register(path)).id, delivered: false };
|
|
23463
23785
|
}
|
|
23464
23786
|
const opened = await this.openFile(raw, path, st);
|
|
23465
|
-
|
|
23787
|
+
const entry = { opened, at: this.now() };
|
|
23788
|
+
this.pending.push(entry);
|
|
23466
23789
|
this.d.hub.emit("opened", opened);
|
|
23467
|
-
return opened;
|
|
23790
|
+
return { ...opened, delivered: !this.pending.includes(entry) };
|
|
23468
23791
|
}
|
|
23469
23792
|
async file(raw) {
|
|
23470
23793
|
const { path, st } = await this.locate(raw);
|
|
@@ -23892,7 +24215,7 @@ class AfterLand {
|
|
|
23892
24215
|
}
|
|
23893
24216
|
|
|
23894
24217
|
// ../daemon/src/repos/registry.ts
|
|
23895
|
-
import { existsSync as
|
|
24218
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
23896
24219
|
import { homedir as homedir10 } from "os";
|
|
23897
24220
|
import { basename as basename8, dirname as dirname8, join as join21 } from "path";
|
|
23898
24221
|
|
|
@@ -23905,7 +24228,7 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
23905
24228
|
}
|
|
23906
24229
|
|
|
23907
24230
|
// ../daemon/src/repos/config.ts
|
|
23908
|
-
import { closeSync, existsSync as
|
|
24231
|
+
import { closeSync, existsSync as existsSync22, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, readSync, statSync as statSync8 } from "fs";
|
|
23909
24232
|
import { join as join18 } from "path";
|
|
23910
24233
|
|
|
23911
24234
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
@@ -25270,7 +25593,7 @@ function applyEdits(text, edits) {
|
|
|
25270
25593
|
|
|
25271
25594
|
// ../daemon/src/repos/config.ts
|
|
25272
25595
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
25273
|
-
var present = (repoPath, place) => [place.shared, place.local].filter((rel) =>
|
|
25596
|
+
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync22(join18(repoPath, rel)));
|
|
25274
25597
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
25275
25598
|
function parseSettings(text) {
|
|
25276
25599
|
const errors = [];
|
|
@@ -25355,7 +25678,7 @@ function configTarget(repoPath, made) {
|
|
|
25355
25678
|
function configBody(repoPath, rel, config) {
|
|
25356
25679
|
const place = PLACES.find((p) => p.local === rel);
|
|
25357
25680
|
const abs = place && join18(repoPath, place.shared);
|
|
25358
|
-
if (!abs || !
|
|
25681
|
+
if (!abs || !existsSync22(abs))
|
|
25359
25682
|
return config;
|
|
25360
25683
|
let shared;
|
|
25361
25684
|
try {
|
|
@@ -25403,7 +25726,7 @@ function detectConfig(repoPath) {
|
|
|
25403
25726
|
if (explicit)
|
|
25404
25727
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
25405
25728
|
const pkgPath = join18(repoPath, "package.json");
|
|
25406
|
-
if (
|
|
25729
|
+
if (existsSync22(pkgPath)) {
|
|
25407
25730
|
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
25408
25731
|
const scripts = pkg.scripts ?? {};
|
|
25409
25732
|
const runner = detectRunner(repoPath, pkg.packageManager);
|
|
@@ -25425,7 +25748,7 @@ function detectConfig(repoPath) {
|
|
|
25425
25748
|
};
|
|
25426
25749
|
}
|
|
25427
25750
|
}
|
|
25428
|
-
if (
|
|
25751
|
+
if (existsSync22(join18(repoPath, "start.sh"))) {
|
|
25429
25752
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
25430
25753
|
}
|
|
25431
25754
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -25468,7 +25791,7 @@ function assumeNothingToRun(repoPath) {
|
|
|
25468
25791
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
25469
25792
|
}
|
|
25470
25793
|
function markerIn(repoPath) {
|
|
25471
|
-
const named = NO_PAGE_MARKERS.find((f) =>
|
|
25794
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync22(join18(repoPath, f)));
|
|
25472
25795
|
if (named)
|
|
25473
25796
|
return named;
|
|
25474
25797
|
let entries;
|
|
@@ -25480,7 +25803,7 @@ function markerIn(repoPath) {
|
|
|
25480
25803
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
25481
25804
|
}
|
|
25482
25805
|
function servesPage(repoPath) {
|
|
25483
|
-
if (WEB_FILES.some((f) =>
|
|
25806
|
+
if (WEB_FILES.some((f) => existsSync22(join18(repoPath, f))))
|
|
25484
25807
|
return true;
|
|
25485
25808
|
if (/^web:/m.test(head(join18(repoPath, "Procfile"))))
|
|
25486
25809
|
return true;
|
|
@@ -25517,7 +25840,7 @@ function mentions(text, name) {
|
|
|
25517
25840
|
return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i").test(text);
|
|
25518
25841
|
}
|
|
25519
25842
|
function head(path) {
|
|
25520
|
-
if (!
|
|
25843
|
+
if (!existsSync22(path))
|
|
25521
25844
|
return "";
|
|
25522
25845
|
try {
|
|
25523
25846
|
const fd = openSync2(path, "r");
|
|
@@ -25558,7 +25881,7 @@ function isLocalDatabase(url) {
|
|
|
25558
25881
|
}
|
|
25559
25882
|
function detectServices(repoPath) {
|
|
25560
25883
|
const out = {};
|
|
25561
|
-
const compose = COMPOSE_FILES.find((f) =>
|
|
25884
|
+
const compose = COMPOSE_FILES.find((f) => existsSync22(join18(repoPath, f)));
|
|
25562
25885
|
if (compose)
|
|
25563
25886
|
out.compose = compose;
|
|
25564
25887
|
for (const file of ENV_FILES) {
|
|
@@ -25586,7 +25909,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
25586
25909
|
return;
|
|
25587
25910
|
let dir = false;
|
|
25588
25911
|
try {
|
|
25589
|
-
dir =
|
|
25912
|
+
dir = statSync8(join18(repoPath, clean)).isDirectory();
|
|
25590
25913
|
} catch {
|
|
25591
25914
|
return;
|
|
25592
25915
|
}
|
|
@@ -25597,21 +25920,21 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
25597
25920
|
const t = tokens[i];
|
|
25598
25921
|
if (t === "cd") {
|
|
25599
25922
|
const dir = tokens[i + 1];
|
|
25600
|
-
if (dir && !dir.startsWith("-") &&
|
|
25923
|
+
if (dir && !dir.startsWith("-") && existsSync22(join18(repoPath, dir)))
|
|
25601
25924
|
add(dir);
|
|
25602
25925
|
continue;
|
|
25603
25926
|
}
|
|
25604
25927
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
25605
25928
|
continue;
|
|
25606
25929
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
25607
|
-
if (mod?.[1] &&
|
|
25930
|
+
if (mod?.[1] && existsSync22(join18(repoPath, mod[1]))) {
|
|
25608
25931
|
add(mod[1]);
|
|
25609
25932
|
continue;
|
|
25610
25933
|
}
|
|
25611
25934
|
const rel = t.replace(/^\.\//, "");
|
|
25612
25935
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
25613
25936
|
continue;
|
|
25614
|
-
if (!
|
|
25937
|
+
if (!existsSync22(join18(repoPath, rel)))
|
|
25615
25938
|
continue;
|
|
25616
25939
|
add(rel);
|
|
25617
25940
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
@@ -25638,9 +25961,9 @@ function procCommand(runner, script, body) {
|
|
|
25638
25961
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
25639
25962
|
}
|
|
25640
25963
|
function detectPages(repoPath, scripts, runner) {
|
|
25641
|
-
if (!scripts.build || !
|
|
25964
|
+
if (!scripts.build || !existsSync22(join18(repoPath, "functions")))
|
|
25642
25965
|
return null;
|
|
25643
|
-
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) =>
|
|
25966
|
+
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) => existsSync22(f));
|
|
25644
25967
|
if (!cfg)
|
|
25645
25968
|
return null;
|
|
25646
25969
|
const out = readFileSync14(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
@@ -25649,17 +25972,17 @@ function detectPages(repoPath, scripts, runner) {
|
|
|
25649
25972
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
25650
25973
|
}
|
|
25651
25974
|
function detectRunner(repoPath, packageManager) {
|
|
25652
|
-
if (
|
|
25975
|
+
if (existsSync22(join18(repoPath, "bun.lock")) || existsSync22(join18(repoPath, "bun.lockb")))
|
|
25653
25976
|
return "bun";
|
|
25654
|
-
if (
|
|
25977
|
+
if (existsSync22(join18(repoPath, "pnpm-lock.yaml")))
|
|
25655
25978
|
return "pnpm";
|
|
25656
|
-
if (
|
|
25979
|
+
if (existsSync22(join18(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
25657
25980
|
return "yarn";
|
|
25658
25981
|
return "npm";
|
|
25659
25982
|
}
|
|
25660
25983
|
|
|
25661
25984
|
// ../daemon/src/repos/create.ts
|
|
25662
|
-
import { existsSync as
|
|
25985
|
+
import { existsSync as existsSync23, realpathSync as realpathSync2, statSync as statSync9 } from "fs";
|
|
25663
25986
|
import { appendFile as appendFile2, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
25664
25987
|
import { homedir as homedir9 } from "os";
|
|
25665
25988
|
import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
|
|
@@ -25674,13 +25997,13 @@ function planProject(opts, home = homedir9()) {
|
|
|
25674
25997
|
const first = firstProjectsFolder(opts.parent, home);
|
|
25675
25998
|
const parent = first ?? resolveParent(opts.parent);
|
|
25676
25999
|
const dir = join19(parent, opts.name.trim());
|
|
25677
|
-
if (
|
|
26000
|
+
if (existsSync23(dir))
|
|
25678
26001
|
throw new UserError(`${dir} already exists`);
|
|
25679
26002
|
return { parent, dir, makeParent: first !== null };
|
|
25680
26003
|
}
|
|
25681
26004
|
function firstProjectsFolder(raw, home) {
|
|
25682
26005
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
25683
|
-
if (typed !== join19(home, PROJECTS_FOLDER) ||
|
|
26006
|
+
if (typed !== join19(home, PROJECTS_FOLDER) || existsSync23(typed) || !existsSync23(home))
|
|
25684
26007
|
return null;
|
|
25685
26008
|
return join19(realpathSync2(home), PROJECTS_FOLDER);
|
|
25686
26009
|
}
|
|
@@ -25690,10 +26013,10 @@ function planInPlace(opts) {
|
|
|
25690
26013
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
25691
26014
|
}
|
|
25692
26015
|
const typed = join19(resolveParent(opts.parent), name);
|
|
25693
|
-
if (!
|
|
26016
|
+
if (!existsSync23(typed) || !statSync9(typed).isDirectory())
|
|
25694
26017
|
throw new UserError(`${typed} is not a folder`);
|
|
25695
26018
|
const dir = realpathSync2(typed);
|
|
25696
|
-
if (
|
|
26019
|
+
if (existsSync23(join19(dir, ".git")))
|
|
25697
26020
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
25698
26021
|
return { parent: dirname7(dir), dir, makeParent: false };
|
|
25699
26022
|
}
|
|
@@ -25701,9 +26024,9 @@ function resolveParent(raw) {
|
|
|
25701
26024
|
const typed = expandTilde(raw.trim());
|
|
25702
26025
|
if (!typed.startsWith("/"))
|
|
25703
26026
|
throw new UserError(`${raw} is not a folder path`);
|
|
25704
|
-
if (!
|
|
26027
|
+
if (!existsSync23(typed))
|
|
25705
26028
|
throw new UserError(`${raw} does not exist; make it first`);
|
|
25706
|
-
if (!
|
|
26029
|
+
if (!statSync9(typed).isDirectory())
|
|
25707
26030
|
throw new UserError(`${raw} is not a folder`);
|
|
25708
26031
|
return realpathSync2(typed);
|
|
25709
26032
|
}
|
|
@@ -25818,7 +26141,7 @@ async function ok(p, what) {
|
|
|
25818
26141
|
}
|
|
25819
26142
|
|
|
25820
26143
|
// ../daemon/src/repos/watcher.ts
|
|
25821
|
-
import { existsSync as
|
|
26144
|
+
import { existsSync as existsSync24, watch } from "fs";
|
|
25822
26145
|
import { basename as basename7, join as join20 } from "path";
|
|
25823
26146
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
25824
26147
|
const last = { local: null, upstream: null };
|
|
@@ -25883,11 +26206,11 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
25883
26206
|
let mid = null;
|
|
25884
26207
|
const armInner = () => {
|
|
25885
26208
|
inner?.close();
|
|
25886
|
-
inner =
|
|
26209
|
+
inner = existsSync24(mine) ? tryWatch(mine) : null;
|
|
25887
26210
|
};
|
|
25888
26211
|
const armMid = () => {
|
|
25889
26212
|
mid?.close();
|
|
25890
|
-
mid =
|
|
26213
|
+
mid = existsSync24(remotes) ? tryWatch(remotes, (_event, filename) => {
|
|
25891
26214
|
if (!filename || filename === remote.out)
|
|
25892
26215
|
armInner();
|
|
25893
26216
|
schedule();
|
|
@@ -25981,7 +26304,7 @@ function watchConfigFile(repoPath, onChange) {
|
|
|
25981
26304
|
const armInner = () => {
|
|
25982
26305
|
inner?.close();
|
|
25983
26306
|
inner = null;
|
|
25984
|
-
if (!
|
|
26307
|
+
if (!existsSync24(dir))
|
|
25985
26308
|
return;
|
|
25986
26309
|
try {
|
|
25987
26310
|
inner = watch(dir, (_event, filename) => {
|
|
@@ -26035,7 +26358,7 @@ class RepoRegistry {
|
|
|
26035
26358
|
}
|
|
26036
26359
|
async boot() {
|
|
26037
26360
|
const { state } = this.d;
|
|
26038
|
-
const gone = state.worktrees.filter((wt) => !
|
|
26361
|
+
const gone = state.worktrees.filter((wt) => !existsSync25(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
|
|
26039
26362
|
for (const wt of gone)
|
|
26040
26363
|
await this.d.worktrees.forgetGone(wt);
|
|
26041
26364
|
for (const repo of state.repos) {
|
|
@@ -26103,7 +26426,7 @@ class RepoRegistry {
|
|
|
26103
26426
|
return `${repo.name} is set up now`;
|
|
26104
26427
|
if (main.promptedAt || main.lastTurn)
|
|
26105
26428
|
return `${repo.name} has a chat now`;
|
|
26106
|
-
if (
|
|
26429
|
+
if (existsSync25(join21(repo.path, ".claude")))
|
|
26107
26430
|
return `an agent has already run in ${repo.name}`;
|
|
26108
26431
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
26109
26432
|
return `${repo.name} has files in it now`;
|
|
@@ -26184,7 +26507,7 @@ class RepoRegistry {
|
|
|
26184
26507
|
}
|
|
26185
26508
|
async register(rawPath, made) {
|
|
26186
26509
|
const path = expandTilde(rawPath);
|
|
26187
|
-
if (!
|
|
26510
|
+
if (!existsSync25(path))
|
|
26188
26511
|
throw new UserError(`${path} does not exist`);
|
|
26189
26512
|
if (!await isGitRepo(path))
|
|
26190
26513
|
throw new UserError(`${path} is not a git repository`);
|
|
@@ -26258,7 +26581,7 @@ class RepoRegistry {
|
|
|
26258
26581
|
throw new UserError(current.reason);
|
|
26259
26582
|
const was = configTarget(repo.path, !!repo.made);
|
|
26260
26583
|
const rel = configSibling(was, kind);
|
|
26261
|
-
const moved = rel !== was &&
|
|
26584
|
+
const moved = rel !== was && existsSync25(join21(repo.path, was));
|
|
26262
26585
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
26263
26586
|
try {
|
|
26264
26587
|
rmSync4(join21(repo.path, was));
|
|
@@ -26374,7 +26697,7 @@ class RepoRegistry {
|
|
|
26374
26697
|
}
|
|
26375
26698
|
placeConfig(repo) {
|
|
26376
26699
|
const rel = configTarget(repo.path, !!repo.made);
|
|
26377
|
-
if (isLocalConfigFile(rel) &&
|
|
26700
|
+
if (isLocalConfigFile(rel) && existsSync25(join21(repo.path, rel))) {
|
|
26378
26701
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
26379
26702
|
}
|
|
26380
26703
|
if (repo.configFile === rel)
|
|
@@ -27450,6 +27773,14 @@ var bunFs = {
|
|
|
27450
27773
|
}
|
|
27451
27774
|
}
|
|
27452
27775
|
};
|
|
27776
|
+
function scanKey(git) {
|
|
27777
|
+
if (git.version !== undefined)
|
|
27778
|
+
return `v${git.version}`;
|
|
27779
|
+
const entries = git.files.filter((f) => isManifest(f.path) || CODE_FILE.test(f.path)).map((f) => `${f.xy}${f.path}`).sort();
|
|
27780
|
+
return `${git.head ?? ""}
|
|
27781
|
+
${entries.join(`
|
|
27782
|
+
`)}`;
|
|
27783
|
+
}
|
|
27453
27784
|
var signatureOf = (list) => list.map((e) => `${e.path} ${e.title ?? ""}`).join(`
|
|
27454
27785
|
`);
|
|
27455
27786
|
|
|
@@ -27514,7 +27845,11 @@ class RouteService {
|
|
|
27514
27845
|
if (!wt)
|
|
27515
27846
|
return { routes: [], unseen: {} };
|
|
27516
27847
|
const scan = this.scanFor(worktreeId, wt.path);
|
|
27517
|
-
|
|
27848
|
+
const key = scanKey(git);
|
|
27849
|
+
if (!scan.routes || scan.key !== key) {
|
|
27850
|
+
scan.key = key;
|
|
27851
|
+
await this.routesOf(scan);
|
|
27852
|
+
}
|
|
27518
27853
|
scan.git = git;
|
|
27519
27854
|
await this.refreshUnseen(worktreeId, scan);
|
|
27520
27855
|
return scan.pages ?? { routes: [], unseen: {} };
|
|
@@ -27565,7 +27900,7 @@ class RouteService {
|
|
|
27565
27900
|
async refreshUnseen(worktreeId, scan) {
|
|
27566
27901
|
if (!scan.routes)
|
|
27567
27902
|
return false;
|
|
27568
|
-
const changed = scan.git ? [...scan.git.files, ...scan.git.committed ?? []] : [];
|
|
27903
|
+
const changed = scan.git ? [...scan.git.files, ...scan.git.extras?.committed ?? []] : [];
|
|
27569
27904
|
const seen = this.d.state.seenOf(worktreeId);
|
|
27570
27905
|
const candidates = badgeCandidates(scan.routes, changed, seen?.here).slice(0, MAX_HASHED);
|
|
27571
27906
|
const hashes = {};
|
|
@@ -27590,7 +27925,8 @@ class RouteService {
|
|
|
27590
27925
|
manifests: new Map,
|
|
27591
27926
|
code: new Map,
|
|
27592
27927
|
git: null,
|
|
27593
|
-
pages: null
|
|
27928
|
+
pages: null,
|
|
27929
|
+
key: null
|
|
27594
27930
|
};
|
|
27595
27931
|
scan.root = root;
|
|
27596
27932
|
this.scans.delete(worktreeId);
|
|
@@ -27953,7 +28289,7 @@ function isAlive(pid) {
|
|
|
27953
28289
|
}
|
|
27954
28290
|
|
|
27955
28291
|
// ../daemon/src/runtime/bridge-script.ts
|
|
27956
|
-
import { existsSync as
|
|
28292
|
+
import { existsSync as existsSync26, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
|
|
27957
28293
|
var LEARNED_MAX = 8;
|
|
27958
28294
|
|
|
27959
28295
|
class BridgeScript {
|
|
@@ -27983,9 +28319,9 @@ class BridgeScript {
|
|
|
27983
28319
|
this.cache = null;
|
|
27984
28320
|
}
|
|
27985
28321
|
get() {
|
|
27986
|
-
if (!
|
|
28322
|
+
if (!existsSync26(this.path))
|
|
27987
28323
|
return "// toyon bridge not built";
|
|
27988
|
-
const s =
|
|
28324
|
+
const s = statSync10(this.path);
|
|
27989
28325
|
const stamp = `${s.mtimeMs}:${s.size}`;
|
|
27990
28326
|
if (this.cache && stamp === this.stamp)
|
|
27991
28327
|
return this.cache;
|
|
@@ -28009,6 +28345,51 @@ function servedLocally(origin) {
|
|
|
28009
28345
|
return h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]" || h.endsWith(".localhost");
|
|
28010
28346
|
}
|
|
28011
28347
|
|
|
28348
|
+
// ../daemon/src/server/interest.ts
|
|
28349
|
+
class Interest {
|
|
28350
|
+
keys = new Map;
|
|
28351
|
+
first = [];
|
|
28352
|
+
last = [];
|
|
28353
|
+
subscribe(key, id) {
|
|
28354
|
+
let set = this.keys.get(id);
|
|
28355
|
+
if (!set) {
|
|
28356
|
+
set = new Set;
|
|
28357
|
+
this.keys.set(id, set);
|
|
28358
|
+
}
|
|
28359
|
+
const was = set.size;
|
|
28360
|
+
set.add(key);
|
|
28361
|
+
if (was === 0)
|
|
28362
|
+
for (const fn of this.first)
|
|
28363
|
+
fn(id);
|
|
28364
|
+
}
|
|
28365
|
+
unsubscribe(key, id) {
|
|
28366
|
+
const set = this.keys.get(id);
|
|
28367
|
+
if (!set?.delete(key))
|
|
28368
|
+
return;
|
|
28369
|
+
if (set.size === 0) {
|
|
28370
|
+
this.keys.delete(id);
|
|
28371
|
+
for (const fn of this.last)
|
|
28372
|
+
fn(id);
|
|
28373
|
+
}
|
|
28374
|
+
}
|
|
28375
|
+
drop(key) {
|
|
28376
|
+
for (const id of [...this.keys.keys()])
|
|
28377
|
+
this.unsubscribe(key, id);
|
|
28378
|
+
}
|
|
28379
|
+
isSubscribed(id) {
|
|
28380
|
+
return (this.keys.get(id)?.size ?? 0) > 0;
|
|
28381
|
+
}
|
|
28382
|
+
subscribed() {
|
|
28383
|
+
return [...this.keys.keys()];
|
|
28384
|
+
}
|
|
28385
|
+
onFirst(fn) {
|
|
28386
|
+
this.first.push(fn);
|
|
28387
|
+
}
|
|
28388
|
+
onLast(fn) {
|
|
28389
|
+
this.last.push(fn);
|
|
28390
|
+
}
|
|
28391
|
+
}
|
|
28392
|
+
|
|
28012
28393
|
// ../daemon/src/server/ws.ts
|
|
28013
28394
|
import { homedir as homedir12, hostname as hostname2 } from "os";
|
|
28014
28395
|
|
|
@@ -28016,7 +28397,7 @@ import { homedir as homedir12, hostname as hostname2 } from "os";
|
|
|
28016
28397
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
28017
28398
|
|
|
28018
28399
|
// ../daemon/src/agent/config.ts
|
|
28019
|
-
import { existsSync as
|
|
28400
|
+
import { existsSync as existsSync27, readFileSync as readFileSync16 } from "fs";
|
|
28020
28401
|
import { homedir as homedir11 } from "os";
|
|
28021
28402
|
import { join as join23 } from "path";
|
|
28022
28403
|
function agentConfigFiles(spec, repoPath, home = homedir11()) {
|
|
@@ -28024,7 +28405,7 @@ function agentConfigFiles(spec, repoPath, home = homedir11()) {
|
|
|
28024
28405
|
id,
|
|
28025
28406
|
label,
|
|
28026
28407
|
path,
|
|
28027
|
-
exists:
|
|
28408
|
+
exists: existsSync27(path)
|
|
28028
28409
|
});
|
|
28029
28410
|
if (spec.id === "claude") {
|
|
28030
28411
|
return [
|
|
@@ -28056,7 +28437,7 @@ function describeAgentConfig(spec, repoPath, home = homedir11()) {
|
|
|
28056
28437
|
return { agent: spec.id, files, servers };
|
|
28057
28438
|
}
|
|
28058
28439
|
function readJson(path) {
|
|
28059
|
-
if (!
|
|
28440
|
+
if (!existsSync27(path))
|
|
28060
28441
|
return null;
|
|
28061
28442
|
try {
|
|
28062
28443
|
const v = JSON.parse(readFileSync16(path, "utf8"));
|
|
@@ -28077,7 +28458,7 @@ function serversOf(raw, scope) {
|
|
|
28077
28458
|
});
|
|
28078
28459
|
}
|
|
28079
28460
|
function codexServers(path) {
|
|
28080
|
-
if (!
|
|
28461
|
+
if (!existsSync27(path))
|
|
28081
28462
|
return [];
|
|
28082
28463
|
const out = [];
|
|
28083
28464
|
let current = null;
|
|
@@ -28619,11 +29000,12 @@ function daylightNow(tz, at = new Date) {
|
|
|
28619
29000
|
var shipped = (worktreeId, ok, message, extra = {}) => ({ t: "shipped", worktreeId, ok, message, ...extra });
|
|
28620
29001
|
var errorText = (e) => e instanceof Error ? e.message : String(e);
|
|
28621
29002
|
var gitStatus = async (s, ctx, worktreeId) => {
|
|
28622
|
-
const info = await s.worktrees.
|
|
29003
|
+
const info = s.worktrees.readable(worktreeId) ? await s.worktrees.tree.current(worktreeId, FIRST_FRAME_WAIT_MS) : await s.worktrees.archivedStatus(worktreeId);
|
|
28623
29004
|
if (info)
|
|
28624
29005
|
ctx.reply({ t: "git-status", worktreeId, ...info });
|
|
28625
29006
|
return info;
|
|
28626
29007
|
};
|
|
29008
|
+
var FIRST_FRAME_WAIT_MS = 250;
|
|
28627
29009
|
var gitAndPages = async (s, ctx, worktreeId) => {
|
|
28628
29010
|
const info = await gitStatus(s, ctx, worktreeId);
|
|
28629
29011
|
ctx.reply({ t: "routes", worktreeId, ...await s.routes.pages(worktreeId, info ?? { files: [] }) });
|
|
@@ -28922,8 +29304,12 @@ var handlers = {
|
|
|
28922
29304
|
ctx.reply({ t: "files", worktreeId: msg.worktreeId, ...await s.files.list(msg.worktreeId) });
|
|
28923
29305
|
},
|
|
28924
29306
|
async search(msg, ctx, s) {
|
|
28925
|
-
const
|
|
28926
|
-
|
|
29307
|
+
const page = await s.files.search(msg.worktreeId, msg.query, ctx.socket);
|
|
29308
|
+
if (page)
|
|
29309
|
+
ctx.reply({ t: "search-results", worktreeId: msg.worktreeId, query: msg.query, ...page });
|
|
29310
|
+
},
|
|
29311
|
+
"search-stop"(_msg, ctx, s) {
|
|
29312
|
+
s.files.stopSearch(ctx.socket);
|
|
28927
29313
|
},
|
|
28928
29314
|
async "find-element"(msg, ctx, s) {
|
|
28929
29315
|
const { hits, sure } = await s.files.findElement(msg.worktreeId, msg.element);
|
|
@@ -29060,6 +29446,11 @@ var handlers = {
|
|
|
29060
29446
|
}
|
|
29061
29447
|
ctx.reply({ t: "folder-chosen", folder: path ? await describeFolder(path) : null });
|
|
29062
29448
|
},
|
|
29449
|
+
async "choose-file"(msg, ctx, s) {
|
|
29450
|
+
const path = await s.folderDialog.choose(msg.start, "file");
|
|
29451
|
+
if (path)
|
|
29452
|
+
ctx.reply(openedFrame(await s.opens.file(path)));
|
|
29453
|
+
},
|
|
29063
29454
|
"cancel-folder"(_msg, _ctx, s) {
|
|
29064
29455
|
s.folderDialog.cancel();
|
|
29065
29456
|
},
|
|
@@ -29259,7 +29650,7 @@ async function dispatch(msg, ctx, s) {
|
|
|
29259
29650
|
}
|
|
29260
29651
|
|
|
29261
29652
|
// ../daemon/src/server/http.ts
|
|
29262
|
-
import { existsSync as
|
|
29653
|
+
import { existsSync as existsSync28 } from "fs";
|
|
29263
29654
|
import { hostname } from "os";
|
|
29264
29655
|
import { join as join24 } from "path";
|
|
29265
29656
|
|
|
@@ -29564,7 +29955,7 @@ function createFetch(opts) {
|
|
|
29564
29955
|
return new Response("unauthorized", { status: 401 });
|
|
29565
29956
|
const id = url.pathname.slice("/uploads/".length);
|
|
29566
29957
|
const path = opts.attachments.uploads.path(id);
|
|
29567
|
-
if (!path || !
|
|
29958
|
+
if (!path || !existsSync28(path))
|
|
29568
29959
|
return new Response("not found", { status: 404 });
|
|
29569
29960
|
return new Response(Bun.file(path), {
|
|
29570
29961
|
headers: { "cache-control": IMMUTABLE, ...attachedHeaders(opts.attachments.uploads.imageType(id)) }
|
|
@@ -29577,7 +29968,7 @@ function createFetch(opts) {
|
|
|
29577
29968
|
const places = worktreeId && file && !extra ? [opts.attachments.fileFor(worktreeId, file), opts.archivedAttachment(worktreeId, file)] : [];
|
|
29578
29969
|
if (worktreeId && file)
|
|
29579
29970
|
await opts.attachments.whenWritten(worktreeId, file);
|
|
29580
|
-
const path = places.find((p) => !!p &&
|
|
29971
|
+
const path = places.find((p) => !!p && existsSync28(p));
|
|
29581
29972
|
if (!path)
|
|
29582
29973
|
return new Response("not found", { status: 404 });
|
|
29583
29974
|
if (url.searchParams.has("where")) {
|
|
@@ -29617,7 +30008,7 @@ function createFetch(opts) {
|
|
|
29617
30008
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
29618
30009
|
const file = join24(opts.shellDist, rel.replaceAll("..", ""));
|
|
29619
30010
|
const hashed = rel.startsWith("/assets/");
|
|
29620
|
-
if (
|
|
30011
|
+
if (existsSync28(file) && Bun.file(file).size > 0) {
|
|
29621
30012
|
if (rel === "/manifest.json") {
|
|
29622
30013
|
const { bar, ground } = opts.manifestColors();
|
|
29623
30014
|
const manifest = { ...await Bun.file(file).json(), theme_color: bar, background_color: ground };
|
|
@@ -29637,7 +30028,7 @@ function createFetch(opts) {
|
|
|
29637
30028
|
if (hashed)
|
|
29638
30029
|
return new Response("not found", { status: 404 });
|
|
29639
30030
|
const index = join24(opts.shellDist, "index.html");
|
|
29640
|
-
if (
|
|
30031
|
+
if (existsSync28(index))
|
|
29641
30032
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE2 } });
|
|
29642
30033
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
29643
30034
|
}
|
|
@@ -29780,6 +30171,11 @@ function startServer(opts) {
|
|
|
29780
30171
|
};
|
|
29781
30172
|
const metrics = () => ({
|
|
29782
30173
|
lag,
|
|
30174
|
+
lagHist,
|
|
30175
|
+
gitSpawns,
|
|
30176
|
+
treeReads,
|
|
30177
|
+
syncHot,
|
|
30178
|
+
fdLimit: limits.fdLimit || undefined,
|
|
29783
30179
|
worktrees: {
|
|
29784
30180
|
total: s.state.worktrees.filter((w) => w.kind !== "spare").length,
|
|
29785
30181
|
running: s.runtime.runningCount(),
|
|
@@ -29811,24 +30207,19 @@ function startServer(opts) {
|
|
|
29811
30207
|
s.hub.on("pendingChanged", () => broadcast({ t: "pending-repos", pending: s.repos.pending }));
|
|
29812
30208
|
s.hub.on("proc", (worktreeId, proc) => broadcast({ t: "proc", worktreeId, proc }));
|
|
29813
30209
|
s.hub.on("log", (worktreeId, proc, line, retract) => sendTo(worktreeId, retract ? { t: "log", worktreeId, proc, line, retract } : { t: "log", worktreeId, proc, line }));
|
|
30210
|
+
s.hub.on("agent", (worktreeId, seq, event) => sendTo(worktreeId, { t: "agent", worktreeId, seq, event }));
|
|
29814
30211
|
s.hub.on("queue", (worktreeId, items) => sendTo(worktreeId, { t: "queue", worktreeId, items }));
|
|
29815
30212
|
s.hub.on("agentCommands", (worktreeId, commands) => sendTo(worktreeId, { t: "agent-commands", worktreeId, commands }));
|
|
29816
30213
|
s.hub.on("termData", (worktreeId, stream, data) => queueTerm(worktreeId, stream, data));
|
|
29817
30214
|
s.hub.on("termExit", (worktreeId, stream, exitCode) => sendTerm(worktreeId, stream, { t: "term-exit", worktreeId, stream, exitCode }));
|
|
29818
|
-
|
|
29819
|
-
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
};
|
|
29826
|
-
s.hub.on("agent", (worktreeId, seq, event) => {
|
|
29827
|
-
sendTo(worktreeId, { t: "agent", worktreeId, seq, event });
|
|
29828
|
-
if (event.type === "tool-end" || event.type === "turn-end")
|
|
29829
|
-
refreshGitStatus(worktreeId);
|
|
30215
|
+
s.hub.on("treeChanged", (worktreeId) => {
|
|
30216
|
+
const frame = s.worktrees.tree.frame(worktreeId);
|
|
30217
|
+
if (!frame)
|
|
30218
|
+
return;
|
|
30219
|
+
sendTo(worktreeId, { t: "git-status", worktreeId, ...frame });
|
|
30220
|
+
if (frame.extras)
|
|
30221
|
+
fireAndForget(worktreeId, s.routes.pages(worktreeId, frame).then((p) => sendPages(worktreeId, p)), "pages after git status");
|
|
29830
30222
|
});
|
|
29831
|
-
s.hub.on("filesChanged", refreshGitStatus);
|
|
29832
30223
|
s.hub.on("opened", (o) => {
|
|
29833
30224
|
if (sockets.size === 0)
|
|
29834
30225
|
return;
|
|
@@ -29836,18 +30227,6 @@ function startServer(opts) {
|
|
|
29836
30227
|
s.opens.delivered();
|
|
29837
30228
|
});
|
|
29838
30229
|
s.hub.on("openRefused", (message) => broadcast({ t: "error", message }));
|
|
29839
|
-
const shellQuiet = new Map;
|
|
29840
|
-
s.hub.on("termData", (worktreeId, stream) => {
|
|
29841
|
-
if (stream !== SHELL_STREAM)
|
|
29842
|
-
return;
|
|
29843
|
-
clearTimeout(shellQuiet.get(worktreeId));
|
|
29844
|
-
shellQuiet.set(worktreeId, setTimeout(() => {
|
|
29845
|
-
shellQuiet.delete(worktreeId);
|
|
29846
|
-
s.worktrees.invalidateCounts(worktreeId);
|
|
29847
|
-
worktreesChanged();
|
|
29848
|
-
fireAndForget(worktreeId, pushGitStatus(worktreeId), "git status after the shell went quiet");
|
|
29849
|
-
}, 1500));
|
|
29850
|
-
});
|
|
29851
30230
|
const sentPages = new Map;
|
|
29852
30231
|
const sendPages = (worktreeId, pages) => {
|
|
29853
30232
|
const json = JSON.stringify(pages);
|
|
@@ -29856,32 +30235,12 @@ function startServer(opts) {
|
|
|
29856
30235
|
sentPages.set(worktreeId, json);
|
|
29857
30236
|
sendTo(worktreeId, { t: "routes", worktreeId, ...pages });
|
|
29858
30237
|
};
|
|
29859
|
-
const pushGitStatus = async (worktreeId) => {
|
|
29860
|
-
if (![...sockets].some((ws) => ws.data.subs.has(worktreeId)))
|
|
29861
|
-
return;
|
|
29862
|
-
const info = await s.worktrees.gitStatus(worktreeId);
|
|
29863
|
-
if (!info)
|
|
29864
|
-
return;
|
|
29865
|
-
sendTo(worktreeId, { t: "git-status", worktreeId, ...info });
|
|
29866
|
-
sendPages(worktreeId, await s.routes.pages(worktreeId, info));
|
|
29867
|
-
};
|
|
29868
30238
|
s.hub.on("pagesChanged", (worktreeId) => {
|
|
29869
30239
|
const pages = s.routes.cached(worktreeId);
|
|
29870
30240
|
if (pages)
|
|
29871
30241
|
sendPages(worktreeId, pages);
|
|
29872
30242
|
});
|
|
29873
|
-
s.hub.on("repoTick", (
|
|
29874
|
-
worktreesChanged();
|
|
29875
|
-
const subscribed = new Set;
|
|
29876
|
-
for (const ws of sockets)
|
|
29877
|
-
for (const id of ws.data.subs)
|
|
29878
|
-
subscribed.add(id);
|
|
29879
|
-
for (const id of subscribed) {
|
|
29880
|
-
if (s.worktrees.readable(id)?.repoId !== repoId)
|
|
29881
|
-
continue;
|
|
29882
|
-
fireAndForget(id, pushGitStatus(id), "git status on ref tick");
|
|
29883
|
-
}
|
|
29884
|
-
});
|
|
30243
|
+
s.hub.on("repoTick", () => worktreesChanged());
|
|
29885
30244
|
const themesChanged = () => {
|
|
29886
30245
|
const cur = s.themes.current();
|
|
29887
30246
|
setWaitingColors({ bg: cur.colors.surface0, fg: cur.colors.text1 });
|
|
@@ -30024,8 +30383,11 @@ function startServer(opts) {
|
|
|
30024
30383
|
close(ws) {
|
|
30025
30384
|
if (ws.data.preview)
|
|
30026
30385
|
ws.data.preview.handler.close(ws.data.preview.data);
|
|
30027
|
-
else
|
|
30386
|
+
else {
|
|
30028
30387
|
s.idle.drop(ws.data);
|
|
30388
|
+
s.interest.drop(ws.data);
|
|
30389
|
+
}
|
|
30390
|
+
s.files.stopSearch(ws.data);
|
|
30029
30391
|
sockets.delete(ws);
|
|
30030
30392
|
unlink(ws);
|
|
30031
30393
|
},
|
|
@@ -30055,10 +30417,12 @@ function startServer(opts) {
|
|
|
30055
30417
|
if (ws.data.subs.has(id))
|
|
30056
30418
|
return false;
|
|
30057
30419
|
ws.data.subs.add(id);
|
|
30420
|
+
s.interest.subscribe(ws.data, id);
|
|
30058
30421
|
return true;
|
|
30059
30422
|
},
|
|
30060
30423
|
unsubscribe: (id) => {
|
|
30061
|
-
ws.data.subs.delete(id)
|
|
30424
|
+
if (ws.data.subs.delete(id))
|
|
30425
|
+
s.interest.unsubscribe(ws.data, id);
|
|
30062
30426
|
if (ws.data.view === id) {
|
|
30063
30427
|
ws.data.view = null;
|
|
30064
30428
|
s.idle.view(ws.data, null);
|
|
@@ -30075,7 +30439,8 @@ function startServer(opts) {
|
|
|
30075
30439
|
const key = streamKey(id, stream);
|
|
30076
30440
|
ws.data.terms.delete(key);
|
|
30077
30441
|
ws.data.dropped.delete(key);
|
|
30078
|
-
}
|
|
30442
|
+
},
|
|
30443
|
+
socket: ws.data
|
|
30079
30444
|
};
|
|
30080
30445
|
try {
|
|
30081
30446
|
await dispatch(parsed.msg, ctx, s);
|
|
@@ -30112,7 +30477,7 @@ function startServer(opts) {
|
|
|
30112
30477
|
}
|
|
30113
30478
|
|
|
30114
30479
|
// ../daemon/src/themes/store.ts
|
|
30115
|
-
import { existsSync as
|
|
30480
|
+
import { existsSync as existsSync29, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
30116
30481
|
import { homedir as homedir13 } from "os";
|
|
30117
30482
|
import { basename as basename9, dirname as dirname9, join as join25, resolve as resolve9 } from "path";
|
|
30118
30483
|
var home = homedir13();
|
|
@@ -30234,7 +30599,7 @@ function readVscodeTheme(file, depth = 0) {
|
|
|
30234
30599
|
return t;
|
|
30235
30600
|
}
|
|
30236
30601
|
function loadDir(dir) {
|
|
30237
|
-
if (!
|
|
30602
|
+
if (!existsSync29(dir))
|
|
30238
30603
|
return [];
|
|
30239
30604
|
const out = [];
|
|
30240
30605
|
for (const f of readdirSync8(dir)) {
|
|
@@ -30256,7 +30621,7 @@ function loadDir(dir) {
|
|
|
30256
30621
|
return out;
|
|
30257
30622
|
}
|
|
30258
30623
|
function discover(dir) {
|
|
30259
|
-
if (!
|
|
30624
|
+
if (!existsSync29(dir))
|
|
30260
30625
|
return [];
|
|
30261
30626
|
const out = [];
|
|
30262
30627
|
let entries;
|
|
@@ -30269,7 +30634,7 @@ function discover(dir) {
|
|
|
30269
30634
|
const extDir = join25(dir, name);
|
|
30270
30635
|
const pkgFile = join25(extDir, "package.json");
|
|
30271
30636
|
try {
|
|
30272
|
-
if (!
|
|
30637
|
+
if (!statSync11(extDir).isDirectory() || !existsSync29(pkgFile))
|
|
30273
30638
|
continue;
|
|
30274
30639
|
const pkg = JSON.parse(readFileSync17(pkgFile, "utf8"));
|
|
30275
30640
|
const themes = pkg.contributes?.themes;
|
|
@@ -30278,7 +30643,7 @@ function discover(dir) {
|
|
|
30278
30643
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
30279
30644
|
let nls = {};
|
|
30280
30645
|
const nlsFile = join25(extDir, "package.nls.json");
|
|
30281
|
-
if (
|
|
30646
|
+
if (existsSync29(nlsFile)) {
|
|
30282
30647
|
try {
|
|
30283
30648
|
nls = JSON.parse(readFileSync17(nlsFile, "utf8"));
|
|
30284
30649
|
} catch {}
|
|
@@ -30692,7 +31057,7 @@ class BackendShare {
|
|
|
30692
31057
|
|
|
30693
31058
|
// ../daemon/src/worktrees/cache.ts
|
|
30694
31059
|
import { createHash as createHash4 } from "crypto";
|
|
30695
|
-
import { existsSync as
|
|
31060
|
+
import { existsSync as existsSync30, readFileSync as readFileSync18 } from "fs";
|
|
30696
31061
|
import { mkdir as mkdir5, readdir as readdir2, rename, rm as rm4, stat as stat4, utimes, writeFile as writeFile3 } from "fs/promises";
|
|
30697
31062
|
import { join as join26 } from "path";
|
|
30698
31063
|
|
|
@@ -30725,7 +31090,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
30725
31090
|
var TMP = ".tmp-";
|
|
30726
31091
|
var MANIFEST = "manifest.json";
|
|
30727
31092
|
function defaultTools(dir) {
|
|
30728
|
-
const has = (...files) => files.some((f) =>
|
|
31093
|
+
const has = (...files) => files.some((f) => existsSync30(join26(dir, f)));
|
|
30729
31094
|
const out = [];
|
|
30730
31095
|
if (has("bun.lock", "bun.lockb"))
|
|
30731
31096
|
out.push("bun --version");
|
|
@@ -30738,7 +31103,7 @@ function defaultTools(dir) {
|
|
|
30738
31103
|
return out;
|
|
30739
31104
|
}
|
|
30740
31105
|
function withSidecars(root, rel) {
|
|
30741
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) =>
|
|
31106
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync30(join26(root, n)));
|
|
30742
31107
|
}
|
|
30743
31108
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
30744
31109
|
async function listEntries(familyDir) {
|
|
@@ -30754,7 +31119,7 @@ async function listEntries(familyDir) {
|
|
|
30754
31119
|
if (!m?.[1] || !m[2])
|
|
30755
31120
|
continue;
|
|
30756
31121
|
const dir = join26(familyDir, name);
|
|
30757
|
-
if (!
|
|
31122
|
+
if (!existsSync30(join26(dir, MANIFEST)))
|
|
30758
31123
|
continue;
|
|
30759
31124
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
30760
31125
|
}
|
|
@@ -30792,7 +31157,7 @@ class ArtifactCache {
|
|
|
30792
31157
|
}
|
|
30793
31158
|
const restored = [];
|
|
30794
31159
|
for (const rel of policy.paths) {
|
|
30795
|
-
if (
|
|
31160
|
+
if (existsSync30(join26(wt.path, rel)))
|
|
30796
31161
|
continue;
|
|
30797
31162
|
const names = withSidecars(entry.dir, rel);
|
|
30798
31163
|
if (names.length === 0)
|
|
@@ -30833,7 +31198,7 @@ class ArtifactCache {
|
|
|
30833
31198
|
let tmp;
|
|
30834
31199
|
let slot;
|
|
30835
31200
|
try {
|
|
30836
|
-
const present = policy.paths.filter((rel) =>
|
|
31201
|
+
const present = policy.paths.filter((rel) => existsSync30(join26(wt.path, rel)));
|
|
30837
31202
|
if (present.length === 0)
|
|
30838
31203
|
return;
|
|
30839
31204
|
const family = await this.family(wt, repo, policy);
|
|
@@ -31633,6 +31998,9 @@ class LandingService {
|
|
|
31633
31998
|
};
|
|
31634
31999
|
}
|
|
31635
32000
|
async hasWork(wt) {
|
|
32001
|
+
const rec = await this.d.worktrees.tree?.refresh(wt.id);
|
|
32002
|
+
if (rec && rec.failedAt === undefined)
|
|
32003
|
+
return rec.core.files.length > 0 || (rec.core.ahead ?? 0) > 0;
|
|
31636
32004
|
const repo = this.d.state.requireRepo(wt.repoId);
|
|
31637
32005
|
const files = await statusFiles(wt.path);
|
|
31638
32006
|
if (files.length > 0)
|
|
@@ -31857,11 +32225,8 @@ class PrService {
|
|
|
31857
32225
|
if (!fresh || !now || now.pr?.number !== pr.number)
|
|
31858
32226
|
return fresh;
|
|
31859
32227
|
this.d.worktrees.setPr(worktreeId, fresh);
|
|
31860
|
-
if (fresh.state === "merged" && !prTaken(now))
|
|
31861
|
-
|
|
31862
|
-
if (!r.ok)
|
|
31863
|
-
log.info(worktreeId, `PR #${pr.number} merged; main here did not follow: ${r.message}`);
|
|
31864
|
-
}
|
|
32228
|
+
if (fresh.state === "merged" && !prTaken(now))
|
|
32229
|
+
await this.d.worktrees.prMerged(worktreeId);
|
|
31865
32230
|
return fresh;
|
|
31866
32231
|
} finally {
|
|
31867
32232
|
this.reading.delete(worktreeId);
|
|
@@ -32172,9 +32537,9 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
32172
32537
|
}
|
|
32173
32538
|
|
|
32174
32539
|
// ../daemon/src/worktrees/service.ts
|
|
32175
|
-
import { cpSync, existsSync as
|
|
32540
|
+
import { cpSync, existsSync as existsSync35, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
|
|
32176
32541
|
import { rm as rm5 } from "fs/promises";
|
|
32177
|
-
import { dirname as dirname12, join as
|
|
32542
|
+
import { dirname as dirname12, join as join31 } from "path";
|
|
32178
32543
|
|
|
32179
32544
|
// ../daemon/src/git/archive.ts
|
|
32180
32545
|
import { rmSync as rmSync5 } from "fs";
|
|
@@ -32298,8 +32663,8 @@ async function dropLandRefs(repoPath, worktreeId) {
|
|
|
32298
32663
|
}
|
|
32299
32664
|
|
|
32300
32665
|
// ../daemon/src/git/land.ts
|
|
32301
|
-
import { existsSync as
|
|
32302
|
-
var STEP_TIMEOUT_MS =
|
|
32666
|
+
import { existsSync as existsSync31 } from "fs";
|
|
32667
|
+
var STEP_TIMEOUT_MS = RUN_CEILING_MS;
|
|
32303
32668
|
function rowOf(r) {
|
|
32304
32669
|
return r.shown && r.toolId && r.command ? { toolId: r.toolId, command: r.command, text: r.text } : undefined;
|
|
32305
32670
|
}
|
|
@@ -32387,7 +32752,7 @@ async function setAside(worktreePath) {
|
|
|
32387
32752
|
}
|
|
32388
32753
|
return { sha: made.out, head: head.out };
|
|
32389
32754
|
}
|
|
32390
|
-
async function putBack(worktreePath, defaultBr, aside, taken) {
|
|
32755
|
+
async function putBack(worktreePath, defaultBr, how, aside, taken) {
|
|
32391
32756
|
let clash = null;
|
|
32392
32757
|
if (taken.ok) {
|
|
32393
32758
|
const applied = await git(worktreePath, "stash", "apply", aside.sha);
|
|
@@ -32411,9 +32776,12 @@ async function putBack(worktreePath, defaultBr, aside, taken) {
|
|
|
32411
32776
|
if (!clash)
|
|
32412
32777
|
return taken;
|
|
32413
32778
|
const named = clash.length ? ` (${listed(clash)})` : "";
|
|
32779
|
+
const ask = how === "rebase" ? `rebase onto ${defaultBr}` : `merge ${defaultBr}`;
|
|
32414
32780
|
return {
|
|
32415
32781
|
ok: false,
|
|
32416
|
-
|
|
32782
|
+
conflict: how,
|
|
32783
|
+
carried: clash,
|
|
32784
|
+
message: `the uncommitted changes here conflict with ${defaultBr}${named}: ask the agent to ${ask} and resolve them`
|
|
32417
32785
|
};
|
|
32418
32786
|
}
|
|
32419
32787
|
var MIDWAY = [
|
|
@@ -32429,7 +32797,7 @@ async function midway(worktreePath) {
|
|
|
32429
32797
|
if (!r.ok)
|
|
32430
32798
|
return null;
|
|
32431
32799
|
const at = r.out.split(`
|
|
32432
|
-
`).findIndex((file) => file &&
|
|
32800
|
+
`).findIndex((file) => file && existsSync31(file));
|
|
32433
32801
|
return MIDWAY[at]?.[1] ?? null;
|
|
32434
32802
|
}
|
|
32435
32803
|
async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
|
|
@@ -32445,11 +32813,12 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = f
|
|
|
32445
32813
|
const { behind } = await aheadBehind(worktreePath, defaultBr);
|
|
32446
32814
|
if (behind === 0)
|
|
32447
32815
|
return { ok: true, message: `already up to date with ${defaultBr}` };
|
|
32816
|
+
const how = await howToBring(worktreePath, defaultBr, own);
|
|
32448
32817
|
const aside = carry ? await setAside(worktreePath) : null;
|
|
32449
32818
|
if (aside && "message" in aside)
|
|
32450
32819
|
return aside;
|
|
32451
|
-
const taken = await bringIn(worktreePath, defaultBr,
|
|
32452
|
-
return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
|
|
32820
|
+
const taken = await bringIn(worktreePath, defaultBr, how, behind, w);
|
|
32821
|
+
return aside ? putBack(worktreePath, defaultBr, how, aside, taken) : taken;
|
|
32453
32822
|
}
|
|
32454
32823
|
async function requireLinear(worktreePath, base) {
|
|
32455
32824
|
const merges = await git(worktreePath, "rev-list", "--merges", "--count", `${base}..HEAD`);
|
|
@@ -32461,9 +32830,14 @@ async function requireLinear(worktreePath, base) {
|
|
|
32461
32830
|
message: `the branch holds a merge commit, which landing by rebase would put on ${base}: ask the agent to rebase onto ${base} first`
|
|
32462
32831
|
};
|
|
32463
32832
|
}
|
|
32464
|
-
async function
|
|
32465
|
-
|
|
32466
|
-
|
|
32833
|
+
async function howToBring(worktreePath, defaultBr, own) {
|
|
32834
|
+
if (!own)
|
|
32835
|
+
return "merge";
|
|
32836
|
+
const merges = await git(worktreePath, "rev-list", "--merges", "--count", `${defaultBr}..HEAD`);
|
|
32837
|
+
return merges.out === "0" ? "rebase" : "merge";
|
|
32838
|
+
}
|
|
32839
|
+
async function bringIn(worktreePath, defaultBr, how, behind, w) {
|
|
32840
|
+
if (how === "rebase") {
|
|
32467
32841
|
w.step(`rebasing onto ${defaultBr}`);
|
|
32468
32842
|
const r = await w.git(worktreePath, ["rebase", defaultBr]);
|
|
32469
32843
|
if (!r.ok) {
|
|
@@ -32716,7 +33090,7 @@ async function openPr(o, w = UNWATCHED) {
|
|
|
32716
33090
|
const push = await pushBranch(o.worktreePath, o.branch, w);
|
|
32717
33091
|
if (!push.ok)
|
|
32718
33092
|
return push;
|
|
32719
|
-
w.step("
|
|
33093
|
+
w.step("creating the PR");
|
|
32720
33094
|
const titled = o.subject ? ["--title", o.subject, "--body", o.body ?? ""] : ["--fill"];
|
|
32721
33095
|
const created = await gh(["pr", "create", "--head", o.branch, "--base", o.defaultBr, ...titled], o.worktreePath);
|
|
32722
33096
|
let url = null;
|
|
@@ -32735,7 +33109,7 @@ ${created.exit}`)) {
|
|
|
32735
33109
|
return { ok: false, message: `gh pr create failed: ${created.err.slice(0, 300)}` };
|
|
32736
33110
|
const number = url ? prNumberOf(url) : null;
|
|
32737
33111
|
if (!url || number === null)
|
|
32738
|
-
return { ok: true, message: `pushed ${o.branch}; PR
|
|
33112
|
+
return { ok: true, message: `pushed ${o.branch}; PR created but its URL was not read` };
|
|
32739
33113
|
let note = "";
|
|
32740
33114
|
if (o.automerge) {
|
|
32741
33115
|
const method = await ghMethod(o.worktreePath, o.method);
|
|
@@ -32773,7 +33147,7 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
32773
33147
|
}
|
|
32774
33148
|
|
|
32775
33149
|
// ../daemon/src/git/turn.ts
|
|
32776
|
-
import { copyFileSync, existsSync as
|
|
33150
|
+
import { copyFileSync, existsSync as existsSync32, rmSync as rmSync6 } from "fs";
|
|
32777
33151
|
import { join as join27 } from "path";
|
|
32778
33152
|
var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
|
|
32779
33153
|
var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
|
|
@@ -32799,7 +33173,7 @@ async function treeNow(wtPath, head, dirty) {
|
|
|
32799
33173
|
const tree = await run(GIT, ["write-tree"], wtPath, env);
|
|
32800
33174
|
return tree.ok && tree.out ? tree.out : null;
|
|
32801
33175
|
};
|
|
32802
|
-
if (!
|
|
33176
|
+
if (!existsSync32(index))
|
|
32803
33177
|
was = undefined;
|
|
32804
33178
|
const paths = dirty && !dirty.some((f) => /[RC]/.test(f.xy)) ? dirty.map((f) => f.path) : null;
|
|
32805
33179
|
if (was && paths && was.head === head) {
|
|
@@ -32814,9 +33188,9 @@ async function treeNow(wtPath, head, dirty) {
|
|
|
32814
33188
|
}
|
|
32815
33189
|
}
|
|
32816
33190
|
synced.delete(wtPath);
|
|
32817
|
-
if (!
|
|
33191
|
+
if (!existsSync32(index)) {
|
|
32818
33192
|
const own = join27(index, "..", "index");
|
|
32819
|
-
if (
|
|
33193
|
+
if (existsSync32(own))
|
|
32820
33194
|
copyFileSync(own, index);
|
|
32821
33195
|
else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
32822
33196
|
return null;
|
|
@@ -32897,7 +33271,7 @@ async function dropTurnRefs(repoPath, worktreeId) {
|
|
|
32897
33271
|
}
|
|
32898
33272
|
|
|
32899
33273
|
// ../daemon/src/worktrees/archive.ts
|
|
32900
|
-
import { existsSync as
|
|
33274
|
+
import { existsSync as existsSync33, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync19, renameSync as renameSync6, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
32901
33275
|
import { basename as basename11, dirname as dirname11, join as join28 } from "path";
|
|
32902
33276
|
class WorktreeArchive {
|
|
32903
33277
|
dir;
|
|
@@ -32907,7 +33281,7 @@ class WorktreeArchive {
|
|
|
32907
33281
|
mkdirSync8(dir, { recursive: true });
|
|
32908
33282
|
for (const id of readdirSync9(dir)) {
|
|
32909
33283
|
const file = this.filesOf(id).record;
|
|
32910
|
-
if (!
|
|
33284
|
+
if (!existsSync33(file))
|
|
32911
33285
|
continue;
|
|
32912
33286
|
try {
|
|
32913
33287
|
this.records.set(id, JSON.parse(readFileSync19(file, "utf8")));
|
|
@@ -32966,7 +33340,7 @@ class WorktreeArchive {
|
|
|
32966
33340
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
32967
33341
|
return null;
|
|
32968
33342
|
const file = join28(this.filesOf(id).plans, basename11(rel));
|
|
32969
|
-
if (!
|
|
33343
|
+
if (!existsSync33(file))
|
|
32970
33344
|
return null;
|
|
32971
33345
|
try {
|
|
32972
33346
|
return readFileSync19(file, "utf8");
|
|
@@ -33007,7 +33381,7 @@ function summarize(r, repoId, transcript) {
|
|
|
33007
33381
|
};
|
|
33008
33382
|
}
|
|
33009
33383
|
function lastUsage(transcript) {
|
|
33010
|
-
if (!
|
|
33384
|
+
if (!existsSync33(transcript))
|
|
33011
33385
|
return null;
|
|
33012
33386
|
const lines = readFileSync19(transcript, "utf8").split(`
|
|
33013
33387
|
`);
|
|
@@ -33023,7 +33397,7 @@ function lastUsage(transcript) {
|
|
|
33023
33397
|
return null;
|
|
33024
33398
|
}
|
|
33025
33399
|
function firstPrompt(transcript) {
|
|
33026
|
-
if (!
|
|
33400
|
+
if (!existsSync33(transcript))
|
|
33027
33401
|
return;
|
|
33028
33402
|
for (const line of readFileSync19(transcript, "utf8").split(`
|
|
33029
33403
|
`)) {
|
|
@@ -33038,11 +33412,11 @@ function firstPrompt(transcript) {
|
|
|
33038
33412
|
return;
|
|
33039
33413
|
}
|
|
33040
33414
|
function move(from, to) {
|
|
33041
|
-
if (!
|
|
33415
|
+
if (!existsSync33(from))
|
|
33042
33416
|
return;
|
|
33043
33417
|
mkdirSync8(dirname11(to), { recursive: true });
|
|
33044
33418
|
rmSync7(to, { recursive: true, force: true });
|
|
33045
|
-
|
|
33419
|
+
renameSync6(from, to);
|
|
33046
33420
|
}
|
|
33047
33421
|
|
|
33048
33422
|
// ../daemon/src/worktrees/archivedGit.ts
|
|
@@ -33102,95 +33476,6 @@ class ArchivedGit {
|
|
|
33102
33476
|
}
|
|
33103
33477
|
}
|
|
33104
33478
|
|
|
33105
|
-
// ../daemon/src/worktrees/counts.ts
|
|
33106
|
-
var COUNTS_FLOOR_MS = 1e4;
|
|
33107
|
-
var same2 = (a, b) => a.ahead === b.ahead && a.behind === b.behind && a.unpushed === b.unpushed && a.dirty === b.dirty;
|
|
33108
|
-
|
|
33109
|
-
class Counts {
|
|
33110
|
-
d;
|
|
33111
|
-
cache = new Map;
|
|
33112
|
-
reading = new Map;
|
|
33113
|
-
marks = new Map;
|
|
33114
|
-
markAll = 0;
|
|
33115
|
-
constructor(d) {
|
|
33116
|
-
this.d = d;
|
|
33117
|
-
}
|
|
33118
|
-
get(id) {
|
|
33119
|
-
return this.cache.get(id)?.value;
|
|
33120
|
-
}
|
|
33121
|
-
stale(id) {
|
|
33122
|
-
if (id === undefined) {
|
|
33123
|
-
this.markAll++;
|
|
33124
|
-
for (const c of this.cache.values())
|
|
33125
|
-
c.at = 0;
|
|
33126
|
-
return;
|
|
33127
|
-
}
|
|
33128
|
-
this.marks.set(id, (this.marks.get(id) ?? 0) + 1);
|
|
33129
|
-
const c = this.cache.get(id);
|
|
33130
|
-
if (c)
|
|
33131
|
-
c.at = 0;
|
|
33132
|
-
}
|
|
33133
|
-
put(id, value) {
|
|
33134
|
-
return this.store(id, value);
|
|
33135
|
-
}
|
|
33136
|
-
refresh(ids) {
|
|
33137
|
-
const reads = [];
|
|
33138
|
-
for (const id of new Set(ids)) {
|
|
33139
|
-
const running = this.reading.get(id);
|
|
33140
|
-
if (running) {
|
|
33141
|
-
reads.push(running);
|
|
33142
|
-
continue;
|
|
33143
|
-
}
|
|
33144
|
-
const cached = this.cache.get(id);
|
|
33145
|
-
if (cached && Date.now() - cached.at < COUNTS_FLOOR_MS)
|
|
33146
|
-
continue;
|
|
33147
|
-
if (this.d.going(id))
|
|
33148
|
-
continue;
|
|
33149
|
-
reads.push(this.readOne(id));
|
|
33150
|
-
}
|
|
33151
|
-
if (!reads.length)
|
|
33152
|
-
return null;
|
|
33153
|
-
return Promise.all(reads).then((moved) => moved.some(Boolean));
|
|
33154
|
-
}
|
|
33155
|
-
async read(id) {
|
|
33156
|
-
this.stale(id);
|
|
33157
|
-
await this.refresh([id]);
|
|
33158
|
-
const c = this.cache.get(id);
|
|
33159
|
-
return c?.failed || !c?.value ? null : c.value;
|
|
33160
|
-
}
|
|
33161
|
-
markOf(id) {
|
|
33162
|
-
return (this.marks.get(id) ?? 0) + this.markAll;
|
|
33163
|
-
}
|
|
33164
|
-
readOne(id) {
|
|
33165
|
-
const read = (async () => {
|
|
33166
|
-
for (;; ) {
|
|
33167
|
-
const mark = this.markOf(id);
|
|
33168
|
-
let value = null;
|
|
33169
|
-
try {
|
|
33170
|
-
value = await this.d.read(id);
|
|
33171
|
-
} catch {}
|
|
33172
|
-
if (this.d.going(id))
|
|
33173
|
-
return false;
|
|
33174
|
-
if (this.markOf(id) !== mark)
|
|
33175
|
-
continue;
|
|
33176
|
-
if (value)
|
|
33177
|
-
return this.store(id, value);
|
|
33178
|
-
this.cache.set(id, { value: this.get(id), at: Date.now(), failed: true });
|
|
33179
|
-
return false;
|
|
33180
|
-
}
|
|
33181
|
-
})().finally(() => {
|
|
33182
|
-
this.reading.delete(id);
|
|
33183
|
-
});
|
|
33184
|
-
this.reading.set(id, read);
|
|
33185
|
-
return read;
|
|
33186
|
-
}
|
|
33187
|
-
store(id, value) {
|
|
33188
|
-
const prev = this.cache.get(id)?.value;
|
|
33189
|
-
this.cache.set(id, { value, at: Date.now() });
|
|
33190
|
-
return !prev || !same2(prev, value);
|
|
33191
|
-
}
|
|
33192
|
-
}
|
|
33193
|
-
|
|
33194
33479
|
// ../daemon/src/worktrees/landSubjects.ts
|
|
33195
33480
|
function unfilled(mark) {
|
|
33196
33481
|
return mark.subjects === undefined;
|
|
@@ -33209,15 +33494,19 @@ async function fillLandMarks(jobs, read, width = 8) {
|
|
|
33209
33494
|
}
|
|
33210
33495
|
|
|
33211
33496
|
// ../daemon/src/worktrees/spare.ts
|
|
33212
|
-
import { existsSync as
|
|
33497
|
+
import { existsSync as existsSync34 } from "fs";
|
|
33213
33498
|
import { basename as basename12, join as join29 } from "path";
|
|
33214
33499
|
class SparePool {
|
|
33215
33500
|
d;
|
|
33216
33501
|
inflight = new Map;
|
|
33217
33502
|
refreshing = new Map;
|
|
33503
|
+
writing = new Set;
|
|
33218
33504
|
constructor(d) {
|
|
33219
33505
|
this.d = d;
|
|
33220
33506
|
}
|
|
33507
|
+
checkingOut(id) {
|
|
33508
|
+
return this.writing.has(id);
|
|
33509
|
+
}
|
|
33221
33510
|
spareOf(repoId) {
|
|
33222
33511
|
return this.d.state.worktrees.find((w) => w.repoId === repoId && w.kind === "spare");
|
|
33223
33512
|
}
|
|
@@ -33229,7 +33518,7 @@ class SparePool {
|
|
|
33229
33518
|
if (!text.trim())
|
|
33230
33519
|
return;
|
|
33231
33520
|
const wt = this.d.state.worktree(boxId);
|
|
33232
|
-
if (wt?.kind !== "spare" || !
|
|
33521
|
+
if (wt?.kind !== "spare" || !existsSync34(wt.path))
|
|
33233
33522
|
return;
|
|
33234
33523
|
const agent = this.d.runtime.ensureAgent(wt).agent;
|
|
33235
33524
|
if (agent.runningAgent === null)
|
|
@@ -33293,16 +33582,18 @@ class SparePool {
|
|
|
33293
33582
|
const repoId = repo.id;
|
|
33294
33583
|
let cut = () => {};
|
|
33295
33584
|
const added = (async () => {
|
|
33296
|
-
if (
|
|
33585
|
+
if (existsSync34(wt.path))
|
|
33297
33586
|
return;
|
|
33298
33587
|
const run = this.d.runs.begin(wt.id, "setup", {
|
|
33299
33588
|
timeoutMs: timeoutFor(repo.config, "setup"),
|
|
33300
33589
|
stage: "checking out"
|
|
33301
33590
|
});
|
|
33591
|
+
this.writing.add(wt.id);
|
|
33302
33592
|
try {
|
|
33303
33593
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", "--no-checkout", wt.path, baseOf(repo)));
|
|
33304
33594
|
await withLock(`checkout:${repo.path}`, () => gitOrThrow(wt.path, "reset", "--hard"));
|
|
33305
33595
|
} finally {
|
|
33596
|
+
this.writing.delete(wt.id);
|
|
33306
33597
|
run.finish(0);
|
|
33307
33598
|
}
|
|
33308
33599
|
})();
|
|
@@ -33352,7 +33643,7 @@ class SparePool {
|
|
|
33352
33643
|
}
|
|
33353
33644
|
log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
|
|
33354
33645
|
this.d.carryDraft?.(wt.id, wt.repoId);
|
|
33355
|
-
if (
|
|
33646
|
+
if (existsSync34(wt.path)) {
|
|
33356
33647
|
await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
|
|
33357
33648
|
return;
|
|
33358
33649
|
}
|
|
@@ -33424,7 +33715,7 @@ class SparePool {
|
|
|
33424
33715
|
try {
|
|
33425
33716
|
await running?.added;
|
|
33426
33717
|
running?.cut();
|
|
33427
|
-
if (!
|
|
33718
|
+
if (!existsSync34(wt.path))
|
|
33428
33719
|
throw new Error("the worktree's directory is gone");
|
|
33429
33720
|
await this.refreshing.get(repoId);
|
|
33430
33721
|
await prepare(wt);
|
|
@@ -33452,7 +33743,7 @@ class SparePool {
|
|
|
33452
33743
|
for (const k of Object.keys(stamps))
|
|
33453
33744
|
delete wt[k];
|
|
33454
33745
|
delete wt.promptedAt;
|
|
33455
|
-
if (!
|
|
33746
|
+
if (!existsSync34(wt.path)) {
|
|
33456
33747
|
await this.rollBack(wt, e);
|
|
33457
33748
|
return;
|
|
33458
33749
|
}
|
|
@@ -33463,6 +33754,518 @@ class SparePool {
|
|
|
33463
33754
|
}
|
|
33464
33755
|
}
|
|
33465
33756
|
|
|
33757
|
+
// ../daemon/src/worktrees/treeState.ts
|
|
33758
|
+
import { watch as watch2 } from "fs";
|
|
33759
|
+
import { stat as stat5 } from "fs/promises";
|
|
33760
|
+
import { join as join30 } from "path";
|
|
33761
|
+
var EDIT_DELAY_MS = 150;
|
|
33762
|
+
var SHELL_QUIET_MS2 = 1500;
|
|
33763
|
+
var WATCH_DELAY_MS = 300;
|
|
33764
|
+
var FLOOR_MS = 5 * 60000;
|
|
33765
|
+
var FLOOR_BATCH = 3;
|
|
33766
|
+
var SLOTS = { fg: 6, bg: 3, extras: 2 };
|
|
33767
|
+
var RETRY_MS = [2000, 1e4, 60000];
|
|
33768
|
+
var SIGNATURE_STATS = 2000;
|
|
33769
|
+
var sameCounts = (a, b) => !!a && a.ahead === b.ahead && a.behind === b.behind && a.unpushed === b.unpushed && a.dirty === b.dirty;
|
|
33770
|
+
|
|
33771
|
+
class TreeState {
|
|
33772
|
+
d;
|
|
33773
|
+
records = new Map;
|
|
33774
|
+
reading = new Map;
|
|
33775
|
+
marks = new Map;
|
|
33776
|
+
markAll = 0;
|
|
33777
|
+
extrasRunning = new Map;
|
|
33778
|
+
inSideEffects = new Set;
|
|
33779
|
+
extrasAgain = new Set;
|
|
33780
|
+
timers = new Map;
|
|
33781
|
+
lastFailure = new Map;
|
|
33782
|
+
retries = new Map;
|
|
33783
|
+
watchers = new Map;
|
|
33784
|
+
pending = new Set;
|
|
33785
|
+
offs = [];
|
|
33786
|
+
stopped = false;
|
|
33787
|
+
slots;
|
|
33788
|
+
constructor(d) {
|
|
33789
|
+
this.d = d;
|
|
33790
|
+
this.slots = d.slots ?? SLOTS;
|
|
33791
|
+
const { hub } = d;
|
|
33792
|
+
this.offs.push(hub.on("agent", (id, _seq, event) => {
|
|
33793
|
+
if (event.type === "tool-end" || event.type === "turn-end")
|
|
33794
|
+
this.signal(id, "edit");
|
|
33795
|
+
}), hub.on("filesChanged", (id) => this.signal(id, "edit")), hub.on("termData", (id, stream) => {
|
|
33796
|
+
if (stream === SHELL_STREAM)
|
|
33797
|
+
this.signal(id, "shell");
|
|
33798
|
+
}), hub.on("turnSettled", (id) => this.signal(id, "now")), hub.on("repoTick", (repoId) => {
|
|
33799
|
+
for (const id of this.d.rowsOf(repoId)) {
|
|
33800
|
+
this.invalidate(id);
|
|
33801
|
+
if (this.hot(id))
|
|
33802
|
+
this.signal(id, "now");
|
|
33803
|
+
}
|
|
33804
|
+
}));
|
|
33805
|
+
}
|
|
33806
|
+
now() {
|
|
33807
|
+
return (this.d.now ?? Date.now)();
|
|
33808
|
+
}
|
|
33809
|
+
get(id) {
|
|
33810
|
+
return this.records.get(id);
|
|
33811
|
+
}
|
|
33812
|
+
counts(id) {
|
|
33813
|
+
const rec = this.records.get(id);
|
|
33814
|
+
if (!rec)
|
|
33815
|
+
return;
|
|
33816
|
+
const { ahead, behind, unpushed } = rec.core;
|
|
33817
|
+
return {
|
|
33818
|
+
...ahead === undefined ? {} : { ahead },
|
|
33819
|
+
...behind === undefined ? {} : { behind },
|
|
33820
|
+
...unpushed === undefined ? {} : { unpushed },
|
|
33821
|
+
dirty: rec.core.files.length
|
|
33822
|
+
};
|
|
33823
|
+
}
|
|
33824
|
+
frame(id) {
|
|
33825
|
+
const rec = this.records.get(id);
|
|
33826
|
+
if (!rec)
|
|
33827
|
+
return null;
|
|
33828
|
+
const { ahead, behind, unpushed, head } = rec.core;
|
|
33829
|
+
const x = rec.extras;
|
|
33830
|
+
const files = x ? rec.core.files.map((f) => ({ ...f, ...x.counts.get(f.path) ?? {} })) : rec.core.files;
|
|
33831
|
+
return {
|
|
33832
|
+
version: rec.version,
|
|
33833
|
+
files,
|
|
33834
|
+
...ahead === undefined ? {} : { ahead },
|
|
33835
|
+
...behind === undefined ? {} : { behind },
|
|
33836
|
+
...unpushed === undefined ? {} : { unpushed },
|
|
33837
|
+
...head === undefined ? {} : { head },
|
|
33838
|
+
...x ? { extras: { committed: x.committed ?? [], ...x.turn ? { turn: x.turn } : {} } } : {}
|
|
33839
|
+
};
|
|
33840
|
+
}
|
|
33841
|
+
async current(id, waitExtrasMs = 0) {
|
|
33842
|
+
if (!this.records.has(id))
|
|
33843
|
+
await this.refresh(id);
|
|
33844
|
+
const rec = this.records.get(id);
|
|
33845
|
+
if (!rec)
|
|
33846
|
+
return null;
|
|
33847
|
+
if (!rec.extras && this.d.interested(id)) {
|
|
33848
|
+
this.scheduleExtras(id);
|
|
33849
|
+
const run = this.extrasRunning.get(id);
|
|
33850
|
+
if (run && waitExtrasMs > 0) {
|
|
33851
|
+
await Promise.race([run, new Promise((r) => setTimeout(r, waitExtrasMs))]);
|
|
33852
|
+
}
|
|
33853
|
+
}
|
|
33854
|
+
return this.frame(id);
|
|
33855
|
+
}
|
|
33856
|
+
async full(id) {
|
|
33857
|
+
const rec = await this.refresh(id);
|
|
33858
|
+
if (!rec)
|
|
33859
|
+
return { frame: null, moved: false };
|
|
33860
|
+
if (!rec.extras) {
|
|
33861
|
+
this.scheduleExtras(id, true);
|
|
33862
|
+
await this.extrasRunning.get(id);
|
|
33863
|
+
}
|
|
33864
|
+
return { frame: this.frame(id), moved: rec.moved };
|
|
33865
|
+
}
|
|
33866
|
+
invalidate(id) {
|
|
33867
|
+
this.marks.set(id, (this.marks.get(id) ?? 0) + 1);
|
|
33868
|
+
const rec = this.records.get(id);
|
|
33869
|
+
if (rec)
|
|
33870
|
+
rec.staleSince ??= this.now();
|
|
33871
|
+
}
|
|
33872
|
+
invalidateAll() {
|
|
33873
|
+
this.markAll++;
|
|
33874
|
+
const now = this.now();
|
|
33875
|
+
for (const [id, rec] of this.records) {
|
|
33876
|
+
rec.staleSince ??= now;
|
|
33877
|
+
if (this.hot(id))
|
|
33878
|
+
this.kick(id);
|
|
33879
|
+
}
|
|
33880
|
+
}
|
|
33881
|
+
forget(id) {
|
|
33882
|
+
this.records.delete(id);
|
|
33883
|
+
this.marks.delete(id);
|
|
33884
|
+
this.lastFailure.delete(id);
|
|
33885
|
+
this.unwatch(id);
|
|
33886
|
+
const retry = this.retries.get(id);
|
|
33887
|
+
if (retry) {
|
|
33888
|
+
clearTimeout(retry);
|
|
33889
|
+
this.retries.delete(id);
|
|
33890
|
+
}
|
|
33891
|
+
const t = this.timers.get(id);
|
|
33892
|
+
if (t) {
|
|
33893
|
+
clearTimeout(t.t);
|
|
33894
|
+
this.timers.delete(id);
|
|
33895
|
+
t.done();
|
|
33896
|
+
}
|
|
33897
|
+
}
|
|
33898
|
+
refresh(id) {
|
|
33899
|
+
this.invalidate(id);
|
|
33900
|
+
if (this.d.going(id))
|
|
33901
|
+
return Promise.resolve(null);
|
|
33902
|
+
if (this.inSideEffects.has(id)) {
|
|
33903
|
+
const rec = this.records.get(id);
|
|
33904
|
+
return Promise.resolve(rec ? { ...rec, moved: false } : null);
|
|
33905
|
+
}
|
|
33906
|
+
return this.reading.get(id) ?? this.readOne(id);
|
|
33907
|
+
}
|
|
33908
|
+
refreshStale(ids) {
|
|
33909
|
+
const reads = [];
|
|
33910
|
+
const floor = [];
|
|
33911
|
+
const now = this.now();
|
|
33912
|
+
for (const id of new Set(ids)) {
|
|
33913
|
+
if (this.d.going(id))
|
|
33914
|
+
continue;
|
|
33915
|
+
const running = this.reading.get(id);
|
|
33916
|
+
if (running) {
|
|
33917
|
+
reads.push(running.then(() => false));
|
|
33918
|
+
continue;
|
|
33919
|
+
}
|
|
33920
|
+
const rec = this.records.get(id);
|
|
33921
|
+
if (!rec) {
|
|
33922
|
+
const failed = this.lastFailure.get(id);
|
|
33923
|
+
if (failed === undefined || now - failed > (this.d.floorMs ?? FLOOR_MS))
|
|
33924
|
+
reads.push(this.readMoved(id));
|
|
33925
|
+
continue;
|
|
33926
|
+
}
|
|
33927
|
+
if (rec.failedAt !== undefined && this.retries.has(id))
|
|
33928
|
+
continue;
|
|
33929
|
+
if (rec.staleSince !== undefined && this.hot(id)) {
|
|
33930
|
+
reads.push(this.readMoved(id));
|
|
33931
|
+
continue;
|
|
33932
|
+
}
|
|
33933
|
+
if (rec.staleSince !== undefined || now - rec.readAt > (this.d.floorMs ?? FLOOR_MS)) {
|
|
33934
|
+
floor.push({ id, at: rec.staleSince ?? rec.readAt });
|
|
33935
|
+
}
|
|
33936
|
+
}
|
|
33937
|
+
floor.sort((a, b) => a.at - b.at);
|
|
33938
|
+
for (const { id } of floor.slice(0, this.d.floorBatch ?? FLOOR_BATCH))
|
|
33939
|
+
reads.push(this.readMoved(id));
|
|
33940
|
+
if (!reads.length)
|
|
33941
|
+
return null;
|
|
33942
|
+
const all = Promise.all(reads).then((moved) => moved.some(Boolean));
|
|
33943
|
+
this.track(all);
|
|
33944
|
+
return all;
|
|
33945
|
+
}
|
|
33946
|
+
signal(id, kind) {
|
|
33947
|
+
if (this.stopped)
|
|
33948
|
+
return;
|
|
33949
|
+
const prev = this.timers.get(id);
|
|
33950
|
+
const delay = kind === "now" ? 0 : kind === "edit" ? this.d.editDelayMs ?? EDIT_DELAY_MS : this.d.shellQuietMs ?? SHELL_QUIET_MS2;
|
|
33951
|
+
const at = Date.now() + delay;
|
|
33952
|
+
if (prev) {
|
|
33953
|
+
if (kind !== "now" && prev.at <= at)
|
|
33954
|
+
return;
|
|
33955
|
+
clearTimeout(prev.t);
|
|
33956
|
+
this.timers.delete(id);
|
|
33957
|
+
prev.done();
|
|
33958
|
+
}
|
|
33959
|
+
if (kind === "now") {
|
|
33960
|
+
this.kick(id);
|
|
33961
|
+
return;
|
|
33962
|
+
}
|
|
33963
|
+
let done = () => {};
|
|
33964
|
+
const waited = new Promise((r) => {
|
|
33965
|
+
done = r;
|
|
33966
|
+
});
|
|
33967
|
+
this.track(waited);
|
|
33968
|
+
const t = setTimeout(() => {
|
|
33969
|
+
this.timers.delete(id);
|
|
33970
|
+
done();
|
|
33971
|
+
this.kick(id);
|
|
33972
|
+
}, delay);
|
|
33973
|
+
t.unref?.();
|
|
33974
|
+
this.timers.set(id, { at, t, done });
|
|
33975
|
+
}
|
|
33976
|
+
kick(id) {
|
|
33977
|
+
this.invalidate(id);
|
|
33978
|
+
if (this.d.going(id))
|
|
33979
|
+
return;
|
|
33980
|
+
const read = this.reading.get(id) ?? this.readOne(id);
|
|
33981
|
+
this.track(read.then((rec) => {
|
|
33982
|
+
if (rec?.moved)
|
|
33983
|
+
this.d.hub.emit("worktreesChanged");
|
|
33984
|
+
}));
|
|
33985
|
+
}
|
|
33986
|
+
watch(id) {
|
|
33987
|
+
const r = this.d.readable(id);
|
|
33988
|
+
if (!r || this.watchers.has(id) || this.stopped)
|
|
33989
|
+
return;
|
|
33990
|
+
if (this.records.has(id))
|
|
33991
|
+
this.scheduleExtras(id);
|
|
33992
|
+
let timer = null;
|
|
33993
|
+
const ignore = new Set([".git"]);
|
|
33994
|
+
let w;
|
|
33995
|
+
try {
|
|
33996
|
+
w = watch2(r.path, { recursive: true }, (_event, filename) => {
|
|
33997
|
+
const name = filename ? String(filename) : "";
|
|
33998
|
+
if (!name)
|
|
33999
|
+
return;
|
|
34000
|
+
for (const dir of ignore)
|
|
34001
|
+
if (name === dir || name.startsWith(`${dir}/`))
|
|
34002
|
+
return;
|
|
34003
|
+
if (timer)
|
|
34004
|
+
clearTimeout(timer);
|
|
34005
|
+
timer = setTimeout(() => {
|
|
34006
|
+
timer = null;
|
|
34007
|
+
this.signal(id, "edit");
|
|
34008
|
+
}, this.d.watchDelayMs ?? WATCH_DELAY_MS);
|
|
34009
|
+
timer.unref?.();
|
|
34010
|
+
});
|
|
34011
|
+
} catch (e) {
|
|
34012
|
+
log.warn(id, "could not watch the worktree", e);
|
|
34013
|
+
return;
|
|
34014
|
+
}
|
|
34015
|
+
w.on("error", (e) => {
|
|
34016
|
+
log.warn(id, "the worktree watcher stopped", e);
|
|
34017
|
+
this.unwatch(id);
|
|
34018
|
+
});
|
|
34019
|
+
w.unref?.();
|
|
34020
|
+
this.watchers.set(id, { w, ignore });
|
|
34021
|
+
fireAndForget(id, git(r.path, "ls-files", "-o", "-i", "--exclude-standard", "--directory").then((res) => {
|
|
34022
|
+
if (!res.ok)
|
|
34023
|
+
return;
|
|
34024
|
+
for (const line of res.out.split(`
|
|
34025
|
+
`)) {
|
|
34026
|
+
const dir = line.replace(/\/$/, "");
|
|
34027
|
+
if (dir)
|
|
34028
|
+
ignore.add(dir);
|
|
34029
|
+
}
|
|
34030
|
+
}), "ignored folders for the watcher");
|
|
34031
|
+
}
|
|
34032
|
+
unwatch(id) {
|
|
34033
|
+
const w = this.watchers.get(id);
|
|
34034
|
+
if (!w)
|
|
34035
|
+
return;
|
|
34036
|
+
this.watchers.delete(id);
|
|
34037
|
+
w.w.close();
|
|
34038
|
+
}
|
|
34039
|
+
async settled() {
|
|
34040
|
+
let waited = false;
|
|
34041
|
+
while (this.pending.size) {
|
|
34042
|
+
waited = true;
|
|
34043
|
+
await Promise.all(this.pending);
|
|
34044
|
+
}
|
|
34045
|
+
return waited;
|
|
34046
|
+
}
|
|
34047
|
+
stop() {
|
|
34048
|
+
this.stopped = true;
|
|
34049
|
+
for (const off of this.offs)
|
|
34050
|
+
off();
|
|
34051
|
+
for (const [id, t] of this.timers) {
|
|
34052
|
+
clearTimeout(t.t);
|
|
34053
|
+
this.timers.delete(id);
|
|
34054
|
+
t.done();
|
|
34055
|
+
}
|
|
34056
|
+
for (const t of this.retries.values())
|
|
34057
|
+
clearTimeout(t);
|
|
34058
|
+
this.retries.clear();
|
|
34059
|
+
for (const id of [...this.watchers.keys()])
|
|
34060
|
+
this.unwatch(id);
|
|
34061
|
+
}
|
|
34062
|
+
hot(id) {
|
|
34063
|
+
return this.d.interested(id) || this.d.shown(id) || this.d.live(id);
|
|
34064
|
+
}
|
|
34065
|
+
markOf(id) {
|
|
34066
|
+
return (this.marks.get(id) ?? 0) + this.markAll;
|
|
34067
|
+
}
|
|
34068
|
+
track(p) {
|
|
34069
|
+
this.pending.add(p);
|
|
34070
|
+
const done = () => this.pending.delete(p);
|
|
34071
|
+
p.then(done, done);
|
|
34072
|
+
return p;
|
|
34073
|
+
}
|
|
34074
|
+
readMoved(id) {
|
|
34075
|
+
return this.readOne(id).then((rec) => !!rec?.moved);
|
|
34076
|
+
}
|
|
34077
|
+
readOne(id) {
|
|
34078
|
+
const read = (async () => {
|
|
34079
|
+
for (let reruns = 0;; ) {
|
|
34080
|
+
const mark = this.markOf(id);
|
|
34081
|
+
const r = this.d.readable(id);
|
|
34082
|
+
if (!r) {
|
|
34083
|
+
this.records.delete(id);
|
|
34084
|
+
return null;
|
|
34085
|
+
}
|
|
34086
|
+
let core = null;
|
|
34087
|
+
const key = this.hot(id) ? "tree:fg" : "tree:bg";
|
|
34088
|
+
const cap = key === "tree:fg" ? this.slots.fg : this.slots.bg;
|
|
34089
|
+
try {
|
|
34090
|
+
core = await withSlot(key, cap, () => (this.d.readCore ?? this.readCore)(r));
|
|
34091
|
+
treeReads.core++;
|
|
34092
|
+
} catch (e) {
|
|
34093
|
+
log.warn(id, "git state read failed", e);
|
|
34094
|
+
}
|
|
34095
|
+
if (this.d.going(id))
|
|
34096
|
+
return null;
|
|
34097
|
+
if (this.markOf(id) !== mark) {
|
|
34098
|
+
treeReads.superseded++;
|
|
34099
|
+
continue;
|
|
34100
|
+
}
|
|
34101
|
+
if (!core) {
|
|
34102
|
+
this.noteFailure(id);
|
|
34103
|
+
return null;
|
|
34104
|
+
}
|
|
34105
|
+
let again = false;
|
|
34106
|
+
if (r.wt) {
|
|
34107
|
+
this.inSideEffects.add(id);
|
|
34108
|
+
try {
|
|
34109
|
+
again = await this.d.afterCore(r, core);
|
|
34110
|
+
} catch (e) {
|
|
34111
|
+
log.warn(id, "git state side effects failed", e);
|
|
34112
|
+
} finally {
|
|
34113
|
+
this.inSideEffects.delete(id);
|
|
34114
|
+
}
|
|
34115
|
+
}
|
|
34116
|
+
if (again && reruns++ === 0)
|
|
34117
|
+
continue;
|
|
34118
|
+
if (this.markOf(id) !== mark) {
|
|
34119
|
+
treeReads.superseded++;
|
|
34120
|
+
continue;
|
|
34121
|
+
}
|
|
34122
|
+
return this.store(id, core);
|
|
34123
|
+
}
|
|
34124
|
+
})().finally(() => this.reading.delete(id));
|
|
34125
|
+
this.reading.set(id, read);
|
|
34126
|
+
return read;
|
|
34127
|
+
}
|
|
34128
|
+
readCore = async (r) => {
|
|
34129
|
+
const isMain = r.wt?.kind === "main";
|
|
34130
|
+
const countable = !isMain && (!!r.wt || !!r.branch);
|
|
34131
|
+
const [entries, ab, unpushed, head, branch] = await Promise.all([
|
|
34132
|
+
statusEntries(r.path),
|
|
34133
|
+
isMain ? this.d.trunkBehind(r.id, r.path) : countable ? aheadBehind(r.path, r.base) : Promise.resolve({}),
|
|
34134
|
+
this.d.prOpen(r.id) ? aheadUpstream(r.path, r.branch) : Promise.resolve(null),
|
|
34135
|
+
git(r.path, "rev-parse", "HEAD"),
|
|
34136
|
+
git(r.path, "branch", "--show-current")
|
|
34137
|
+
]);
|
|
34138
|
+
const files = entries.files.map((f) => ({
|
|
34139
|
+
path: f.path,
|
|
34140
|
+
xy: f.xy,
|
|
34141
|
+
...entries.folded.has(f.path) ? { add: f.add, del: f.del } : {}
|
|
34142
|
+
}));
|
|
34143
|
+
const stats = await Promise.all(files.slice(0, SIGNATURE_STATS).map((f) => stat5(join30(r.path, f.path)).then((s) => `${s.mtimeMs}:${s.size}`, () => "-")));
|
|
34144
|
+
const t0 = performance.now();
|
|
34145
|
+
const counts = { ...ab, ...unpushed === null ? {} : { unpushed } };
|
|
34146
|
+
const signature = String(Bun.hash([
|
|
34147
|
+
head.ok ? head.out : "",
|
|
34148
|
+
counts.ahead,
|
|
34149
|
+
counts.behind,
|
|
34150
|
+
counts.unpushed,
|
|
34151
|
+
...files.map((f) => `${f.xy}${f.path}`),
|
|
34152
|
+
...stats
|
|
34153
|
+
].join("\x00")));
|
|
34154
|
+
noteSync("tree.signature", performance.now() - t0);
|
|
34155
|
+
return {
|
|
34156
|
+
files,
|
|
34157
|
+
...counts,
|
|
34158
|
+
...head.ok ? { head: head.out } : {},
|
|
34159
|
+
...branch.ok && branch.out ? { branch: branch.out } : {},
|
|
34160
|
+
folded: entries.folded,
|
|
34161
|
+
signature
|
|
34162
|
+
};
|
|
34163
|
+
};
|
|
34164
|
+
readExtras = async (r, core, folded) => {
|
|
34165
|
+
const isMain = r.wt?.kind === "main";
|
|
34166
|
+
const counts = await countEntries(r.path, core.files, folded);
|
|
34167
|
+
const committed = !isMain && (core.ahead ?? 0) > 0 ? await committedFiles(r.path, r.base) : undefined;
|
|
34168
|
+
const turn = r.wt && core.files.length > 0 ? await turnChanges(r.path, r.wt.id, r.base, !isMain, core.files) : null;
|
|
34169
|
+
return { counts, ...committed ? { committed } : {}, ...turn ? { turn } : {} };
|
|
34170
|
+
};
|
|
34171
|
+
store(id, read) {
|
|
34172
|
+
const { folded, signature, ...core } = read;
|
|
34173
|
+
const prev = this.records.get(id);
|
|
34174
|
+
const now = this.now();
|
|
34175
|
+
const retry = this.retries.get(id);
|
|
34176
|
+
if (retry) {
|
|
34177
|
+
clearTimeout(retry);
|
|
34178
|
+
this.retries.delete(id);
|
|
34179
|
+
}
|
|
34180
|
+
this.lastFailure.delete(id);
|
|
34181
|
+
if (prev && prev.signature === signature) {
|
|
34182
|
+
prev.readAt = now;
|
|
34183
|
+
prev.staleSince = undefined;
|
|
34184
|
+
prev.failedAt = undefined;
|
|
34185
|
+
prev.failures = 0;
|
|
34186
|
+
return { ...prev, moved: false };
|
|
34187
|
+
}
|
|
34188
|
+
const before = prev && this.counts(id);
|
|
34189
|
+
const rec = {
|
|
34190
|
+
version: (prev?.version ?? 0) + 1,
|
|
34191
|
+
core,
|
|
34192
|
+
signature,
|
|
34193
|
+
folded,
|
|
34194
|
+
readAt: now,
|
|
34195
|
+
failures: 0
|
|
34196
|
+
};
|
|
34197
|
+
this.records.set(id, rec);
|
|
34198
|
+
const moved = !sameCounts(before, this.counts(id));
|
|
34199
|
+
if (this.d.interested(id)) {
|
|
34200
|
+
this.d.hub.emit("treeChanged", id);
|
|
34201
|
+
this.scheduleExtras(id);
|
|
34202
|
+
}
|
|
34203
|
+
return { ...rec, moved };
|
|
34204
|
+
}
|
|
34205
|
+
noteFailure(id) {
|
|
34206
|
+
treeReads.failed++;
|
|
34207
|
+
const now = this.now();
|
|
34208
|
+
const rec = this.records.get(id);
|
|
34209
|
+
this.lastFailure.set(id, now);
|
|
34210
|
+
if (rec) {
|
|
34211
|
+
if (rec.failedAt === undefined)
|
|
34212
|
+
log.warn(id, "git could not read the tree; the last numbers stand");
|
|
34213
|
+
rec.failedAt = now;
|
|
34214
|
+
rec.failures++;
|
|
34215
|
+
rec.readAt = now;
|
|
34216
|
+
rec.staleSince = undefined;
|
|
34217
|
+
}
|
|
34218
|
+
if (!this.d.interested(id) || this.retries.has(id) || this.stopped)
|
|
34219
|
+
return;
|
|
34220
|
+
const delay = RETRY_MS[Math.min((rec?.failures ?? 1) - 1, RETRY_MS.length - 1)] ?? RETRY_MS[0];
|
|
34221
|
+
const t = setTimeout(() => {
|
|
34222
|
+
this.retries.delete(id);
|
|
34223
|
+
if (this.d.interested(id))
|
|
34224
|
+
this.kick(id);
|
|
34225
|
+
}, delay);
|
|
34226
|
+
t.unref?.();
|
|
34227
|
+
this.retries.set(id, t);
|
|
34228
|
+
}
|
|
34229
|
+
scheduleExtras(id, asked = false) {
|
|
34230
|
+
if (this.extrasRunning.has(id)) {
|
|
34231
|
+
this.extrasAgain.add(id);
|
|
34232
|
+
return;
|
|
34233
|
+
}
|
|
34234
|
+
const rec = this.records.get(id);
|
|
34235
|
+
if (!rec || rec.extras || !(asked || this.d.interested(id)) || this.stopped)
|
|
34236
|
+
return;
|
|
34237
|
+
const version = rec.version;
|
|
34238
|
+
const run = (async () => {
|
|
34239
|
+
const r = this.d.readable(id);
|
|
34240
|
+
if (!r)
|
|
34241
|
+
return;
|
|
34242
|
+
let extras;
|
|
34243
|
+
try {
|
|
34244
|
+
extras = await withSlot("tree:extras", this.slots.extras, () => (this.d.readExtras ?? this.readExtras)(r, rec.core, rec.folded));
|
|
34245
|
+
treeReads.extras++;
|
|
34246
|
+
} catch (e) {
|
|
34247
|
+
log.warn(id, "git state extras failed", e);
|
|
34248
|
+
return;
|
|
34249
|
+
}
|
|
34250
|
+
const now = this.records.get(id);
|
|
34251
|
+
if (!now || now.version !== version || this.d.going(id)) {
|
|
34252
|
+
treeReads.superseded++;
|
|
34253
|
+
return;
|
|
34254
|
+
}
|
|
34255
|
+
now.extras = extras;
|
|
34256
|
+
now.version++;
|
|
34257
|
+
if (this.d.interested(id))
|
|
34258
|
+
this.d.hub.emit("treeChanged", id);
|
|
34259
|
+
})().finally(() => {
|
|
34260
|
+
this.extrasRunning.delete(id);
|
|
34261
|
+
if (this.extrasAgain.delete(id))
|
|
34262
|
+
this.scheduleExtras(id);
|
|
34263
|
+
});
|
|
34264
|
+
this.extrasRunning.set(id, run);
|
|
34265
|
+
this.track(run);
|
|
34266
|
+
}
|
|
34267
|
+
}
|
|
34268
|
+
|
|
33466
34269
|
// ../daemon/src/worktrees/trunk.ts
|
|
33467
34270
|
var FETCH_EVERY_MS = 5 * 60000;
|
|
33468
34271
|
var OPEN_FETCH_MIN_MS = 60000;
|
|
@@ -33489,11 +34292,9 @@ class Trunk {
|
|
|
33489
34292
|
const main = this.d.state.worktree(mainId);
|
|
33490
34293
|
if (main)
|
|
33491
34294
|
this.noteFetch(main.repoId, r);
|
|
33492
|
-
if (!r.ok)
|
|
33493
|
-
log.warn("fetch", `could not fetch ${path}: ${r.err.slice(0, 200)}`);
|
|
34295
|
+
if (!r.ok)
|
|
33494
34296
|
return;
|
|
33495
|
-
|
|
33496
|
-
this.d.invalidateCounts();
|
|
34297
|
+
this.d.invalidateCounts(mainId);
|
|
33497
34298
|
this.d.hub.emit("worktreesChanged");
|
|
33498
34299
|
if (await behindUpstream(path) ?? 0)
|
|
33499
34300
|
await this.follow(mainId);
|
|
@@ -33512,11 +34313,9 @@ class Trunk {
|
|
|
33512
34313
|
this.lastFetch.set(repo.path, Date.now());
|
|
33513
34314
|
const f = await withFetchLock(repo.path, () => run(GIT, ["fetch", "--quiet"], repo.path, NO_PROMPT));
|
|
33514
34315
|
this.noteFetch(repo.id, f);
|
|
33515
|
-
if (!f.ok)
|
|
33516
|
-
log.warn("fetch", `could not fetch ${repo.path}: ${f.err.slice(0, 200)}`);
|
|
34316
|
+
if (!f.ok)
|
|
33517
34317
|
return;
|
|
33518
|
-
|
|
33519
|
-
this.d.invalidateCounts();
|
|
34318
|
+
this.d.invalidateCounts(main.id);
|
|
33520
34319
|
await this.follow(main.id);
|
|
33521
34320
|
}
|
|
33522
34321
|
noteFetch(repoId, r) {
|
|
@@ -33524,6 +34323,11 @@ class Trunk {
|
|
|
33524
34323
|
const next = r.ok ? { at: Date.now() } : { ...was.at ? { at: was.at } : {}, failed: r.err.split(`
|
|
33525
34324
|
`).find((l) => l.trim()) ?? "fetch failed" };
|
|
33526
34325
|
this.fetches.set(repoId, next);
|
|
34326
|
+
const path = this.d.state.repo(repoId)?.path ?? repoId;
|
|
34327
|
+
if (next.failed !== undefined && next.failed !== was.failed)
|
|
34328
|
+
log.warn("fetch", `could not fetch ${path}: ${next.failed}`);
|
|
34329
|
+
if (r.ok && was.failed !== undefined)
|
|
34330
|
+
log.info("fetch", `origin answers again for ${path}`);
|
|
33527
34331
|
if (was.failed !== next.failed || was.at !== next.at)
|
|
33528
34332
|
this.d.hub.emit("worktreesChanged");
|
|
33529
34333
|
}
|
|
@@ -33538,7 +34342,7 @@ class Trunk {
|
|
|
33538
34342
|
this.noteFetch(repo.id, f);
|
|
33539
34343
|
if (!f.ok)
|
|
33540
34344
|
return { ok: false, message: refused2("fetch failed", f) };
|
|
33541
|
-
this.d.invalidateCounts();
|
|
34345
|
+
this.d.invalidateCounts(main.id);
|
|
33542
34346
|
return this.follow(main.id);
|
|
33543
34347
|
}
|
|
33544
34348
|
async catchUp(repoId) {
|
|
@@ -33559,7 +34363,7 @@ class Trunk {
|
|
|
33559
34363
|
else
|
|
33560
34364
|
this.stale.delete(repo.id);
|
|
33561
34365
|
if (ff.moved) {
|
|
33562
|
-
this.d.invalidateCounts();
|
|
34366
|
+
this.d.invalidateCounts(mainId);
|
|
33563
34367
|
this.d.hub.emit("worktreesChanged");
|
|
33564
34368
|
} else if (was !== ff.stale)
|
|
33565
34369
|
this.d.hub.emit("worktreesChanged");
|
|
@@ -33656,6 +34460,7 @@ function stopOf(prev, next) {
|
|
|
33656
34460
|
}
|
|
33657
34461
|
|
|
33658
34462
|
// ../daemon/src/worktrees/service.ts
|
|
34463
|
+
var LIST_FLOOR_MS = 1e4;
|
|
33659
34464
|
var LOCAL_CONFIG_FILES = [".env", ".env.local", ".env.development", ".env.development.local", ".dev.vars"];
|
|
33660
34465
|
var BASE_IGNORE = "base .gitignore";
|
|
33661
34466
|
var REFUSAL_CHARS = 400;
|
|
@@ -33702,7 +34507,7 @@ class WorktreeService {
|
|
|
33702
34507
|
going = new Set;
|
|
33703
34508
|
arriving = new Set;
|
|
33704
34509
|
firstPrompt = new Map;
|
|
33705
|
-
|
|
34510
|
+
tree;
|
|
33706
34511
|
handChecked = new Map;
|
|
33707
34512
|
naming = new Set;
|
|
33708
34513
|
discoverCache = new Map;
|
|
@@ -33714,15 +34519,23 @@ class WorktreeService {
|
|
|
33714
34519
|
this.d = d;
|
|
33715
34520
|
this.runs = d.runs ?? new RunService({ state: d.state, hub: d.hub });
|
|
33716
34521
|
this.archive = new WorktreeArchive(d.paths.archiveDir);
|
|
33717
|
-
this.counts = new Counts({
|
|
33718
|
-
read: (id) => this.readCounts(id),
|
|
33719
|
-
going: (id) => this.going.has(id) || this.arriving.has(id)
|
|
33720
|
-
});
|
|
33721
34522
|
this.trunk = new Trunk({
|
|
33722
34523
|
state: d.state,
|
|
33723
34524
|
hub: d.hub,
|
|
33724
|
-
counts: (main) => this.counts
|
|
33725
|
-
invalidateCounts: () => this.
|
|
34525
|
+
counts: (main) => this.tree.counts(main.id),
|
|
34526
|
+
invalidateCounts: (mainId) => this.tree.invalidate(mainId)
|
|
34527
|
+
});
|
|
34528
|
+
this.tree = new TreeState({
|
|
34529
|
+
hub: d.hub,
|
|
34530
|
+
readable: (id) => this.readable(id),
|
|
34531
|
+
going: (id) => this.going.has(id) || this.arriving.has(id) || this.spare.checkingOut(id),
|
|
34532
|
+
interested: (id) => d.interested?.(id) ?? false,
|
|
34533
|
+
shown: (id) => d.shown?.(id) ?? false,
|
|
34534
|
+
live: (id) => d.runtime.busy(id),
|
|
34535
|
+
trunkBehind: (mainId, path) => this.trunk.behind(mainId, path),
|
|
34536
|
+
prOpen: (id) => d.state.worktree(id)?.pr?.state === "open",
|
|
34537
|
+
afterCore: (r, core) => this.afterCore(r, core),
|
|
34538
|
+
rowsOf: (repoId) => this.rowsOf(repoId)
|
|
33726
34539
|
});
|
|
33727
34540
|
this.spare = new SparePool({
|
|
33728
34541
|
state: d.state,
|
|
@@ -33762,7 +34575,6 @@ class WorktreeService {
|
|
|
33762
34575
|
this.usage.set(worktreeId, { used, size, ...cost !== undefined ? { cost } : {} });
|
|
33763
34576
|
});
|
|
33764
34577
|
d.hub.on("turnSettled", (worktreeId, turn) => {
|
|
33765
|
-
this.counts.stale(worktreeId);
|
|
33766
34578
|
if (turn.end === "done") {
|
|
33767
34579
|
const wt = d.state.worktree(worktreeId);
|
|
33768
34580
|
const prompt = wt?.unnamed ? firstAskOf(d.runtime.agentFor(worktreeId)?.transcript() ?? []) : undefined;
|
|
@@ -33854,8 +34666,8 @@ class WorktreeService {
|
|
|
33854
34666
|
if (!await isGitRepo(repo.path)) {
|
|
33855
34667
|
throw new UserError(`git no longer reads ${repo.name} as a checkout, so there is nothing to start a worktree from. If \`git config core.bare\` says true there, \`git config --unset core.bare\` puts it back.`);
|
|
33856
34668
|
}
|
|
33857
|
-
const { id, dir } = freeSlot(
|
|
33858
|
-
const wtPath =
|
|
34669
|
+
const { id, dir } = freeSlot(join31(this.d.paths.worktreesDir, repo.name));
|
|
34670
|
+
const wtPath = join31(this.d.paths.worktreesDir, repo.name, dir);
|
|
33859
34671
|
const branch = `toyon/${dir}`;
|
|
33860
34672
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
33861
34673
|
const wt = {
|
|
@@ -33905,14 +34717,12 @@ class WorktreeService {
|
|
|
33905
34717
|
async carryMainOrThrow(repo, wt, move) {
|
|
33906
34718
|
const main = repo.defaultBranch;
|
|
33907
34719
|
if (!move) {
|
|
33908
|
-
const count = this.counts
|
|
34720
|
+
const count = this.tree.counts(this.mainOf(repo.id)?.id ?? "")?.dirty ?? 0;
|
|
33909
34721
|
return { branch: main, moved: false, count };
|
|
33910
34722
|
}
|
|
33911
34723
|
let count = 0;
|
|
33912
|
-
let left;
|
|
33913
34724
|
const unmoved = await withRepoLock(repo.path, async () => {
|
|
33914
34725
|
count = (await statusFiles(repo.path)).length;
|
|
33915
|
-
left = count;
|
|
33916
34726
|
if (count === 0)
|
|
33917
34727
|
return;
|
|
33918
34728
|
const before = await git(repo.path, "rev-parse", "-q", "--verify", "refs/stash");
|
|
@@ -33923,7 +34733,6 @@ class WorktreeService {
|
|
|
33923
34733
|
const sha = after.out.trim();
|
|
33924
34734
|
if (!after.ok || before.ok && before.out.trim() === sha)
|
|
33925
34735
|
return;
|
|
33926
|
-
left = 0;
|
|
33927
34736
|
const entry = async () => {
|
|
33928
34737
|
const list = await git(repo.path, "stash", "list", "--format=%H");
|
|
33929
34738
|
const i = list.out.split(`
|
|
@@ -33932,7 +34741,7 @@ class WorktreeService {
|
|
|
33932
34741
|
};
|
|
33933
34742
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
33934
34743
|
const restored = (carried.ok ? carried.out.split(`
|
|
33935
|
-
`).filter(Boolean) : []).filter((p) => !
|
|
34744
|
+
`).filter(Boolean) : []).filter((p) => !existsSync35(join31(wt.path, p)));
|
|
33936
34745
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
33937
34746
|
if (applied.ok) {
|
|
33938
34747
|
const ref = await entry();
|
|
@@ -33947,14 +34756,11 @@ class WorktreeService {
|
|
|
33947
34756
|
}
|
|
33948
34757
|
const ref = await entry();
|
|
33949
34758
|
const back = ref ? await git(repo.path, "stash", "pop", ref) : null;
|
|
33950
|
-
left = back?.ok ? count : undefined;
|
|
33951
34759
|
return back?.ok ? `the changes on ${main} did not apply in ${wt.title}, so they stayed on ${main}` : `the changes on ${main} did not apply in ${wt.title}; they are kept in git stash as "toyon: into ${wt.title}"`;
|
|
33952
34760
|
});
|
|
33953
34761
|
const mainWt = this.mainOf(repo.id);
|
|
33954
34762
|
if (mainWt) {
|
|
33955
|
-
|
|
33956
|
-
this.counts.put(mainWt.id, { ...this.counts.get(mainWt.id), dirty: left });
|
|
33957
|
-
this.counts.stale(mainWt.id);
|
|
34763
|
+
await this.tree.refresh(mainWt.id);
|
|
33958
34764
|
}
|
|
33959
34765
|
return { branch: main, moved: count > 0 && !unmoved, count, unmoved };
|
|
33960
34766
|
}
|
|
@@ -33972,9 +34778,9 @@ class WorktreeService {
|
|
|
33972
34778
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
33973
34779
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
33974
34780
|
let slug = branchSlug(title) || "branch";
|
|
33975
|
-
if (
|
|
34781
|
+
if (existsSync35(join31(this.d.paths.worktreesDir, repo.name, slug)))
|
|
33976
34782
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
33977
|
-
const wtPath =
|
|
34783
|
+
const wtPath = join31(this.d.paths.worktreesDir, repo.name, slug);
|
|
33978
34784
|
await withRepoLock(repo.path, async () => {
|
|
33979
34785
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
33980
34786
|
if (out)
|
|
@@ -34223,6 +35029,7 @@ class WorktreeService {
|
|
|
34223
35029
|
const sessionId = this.d.state.session(worktreeId);
|
|
34224
35030
|
const placed = repo.promptedAt;
|
|
34225
35031
|
this.d.state.removeWorktree(worktreeId);
|
|
35032
|
+
this.tree.forget(worktreeId);
|
|
34226
35033
|
if (repo.promptedAt !== placed)
|
|
34227
35034
|
this.d.hub.emit("reposChanged");
|
|
34228
35035
|
releasePort(wt.proxyPort);
|
|
@@ -34240,7 +35047,7 @@ class WorktreeService {
|
|
|
34240
35047
|
}
|
|
34241
35048
|
async teardown(wt, repo) {
|
|
34242
35049
|
const cmds = repo.config.teardown ?? [];
|
|
34243
|
-
if (cmds.length === 0 || !
|
|
35050
|
+
if (cmds.length === 0 || !existsSync35(wt.path))
|
|
34244
35051
|
return;
|
|
34245
35052
|
for (const cmd of cmds) {
|
|
34246
35053
|
const code = await runSetup(cmd, wt.path, (line) => this.d.hub.emit("log", wt.id, "setup", line), worktreeEnv(wt, repo), { timeoutMs: timeoutFor(repo.config, "setup") });
|
|
@@ -34249,7 +35056,7 @@ class WorktreeService {
|
|
|
34249
35056
|
}
|
|
34250
35057
|
}
|
|
34251
35058
|
async keep(repo, wt) {
|
|
34252
|
-
const index =
|
|
35059
|
+
const index = join31(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
34253
35060
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
34254
35061
|
if (!kept)
|
|
34255
35062
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -34286,11 +35093,11 @@ class WorktreeService {
|
|
|
34286
35093
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
34287
35094
|
}
|
|
34288
35095
|
plansScratch(worktreeId) {
|
|
34289
|
-
return
|
|
35096
|
+
return join31(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
34290
35097
|
}
|
|
34291
35098
|
keepPlans(wt) {
|
|
34292
|
-
const from =
|
|
34293
|
-
if (!
|
|
35099
|
+
const from = join31(wt.path, PLANS_DIR);
|
|
35100
|
+
if (!existsSync35(from))
|
|
34294
35101
|
return;
|
|
34295
35102
|
try {
|
|
34296
35103
|
cpSync(from, this.plansScratch(wt.id), { recursive: true });
|
|
@@ -34341,6 +35148,7 @@ class WorktreeService {
|
|
|
34341
35148
|
const repo = this.d.state.repo(wt.repoId);
|
|
34342
35149
|
const sessionId = this.d.state.session(wt.id);
|
|
34343
35150
|
this.d.state.removeWorktree(wt.id);
|
|
35151
|
+
this.tree.forget(wt.id);
|
|
34344
35152
|
if (!repo || wt.kind !== "worktree") {
|
|
34345
35153
|
this.deleteChat(wt.id);
|
|
34346
35154
|
return;
|
|
@@ -34429,7 +35237,7 @@ class WorktreeService {
|
|
|
34429
35237
|
}
|
|
34430
35238
|
archivedAttachment(archiveId, file) {
|
|
34431
35239
|
const files = this.archive.chatFiles(archiveId);
|
|
34432
|
-
return files && isAttachmentFile(file) ?
|
|
35240
|
+
return files && isAttachmentFile(file) ? join31(files.attachments, file) : null;
|
|
34433
35241
|
}
|
|
34434
35242
|
archivedGit(archiveId) {
|
|
34435
35243
|
const rec = this.archive.get(archiveId);
|
|
@@ -34443,7 +35251,8 @@ class WorktreeService {
|
|
|
34443
35251
|
if (!kept)
|
|
34444
35252
|
return null;
|
|
34445
35253
|
try {
|
|
34446
|
-
|
|
35254
|
+
const { files, committed, head } = await kept.status();
|
|
35255
|
+
return { version: 0, files, head, extras: { committed } };
|
|
34447
35256
|
} catch (e) {
|
|
34448
35257
|
log.warn(archiveId, "reading its kept changes failed", e);
|
|
34449
35258
|
return null;
|
|
@@ -34484,7 +35293,7 @@ class WorktreeService {
|
|
|
34484
35293
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
34485
35294
|
if (rec.auto)
|
|
34486
35295
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
34487
|
-
const path =
|
|
35296
|
+
const path = existsSync35(old.path) ? join31(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
|
|
34488
35297
|
const { variant: _variant, ...rest } = old;
|
|
34489
35298
|
const wt = {
|
|
34490
35299
|
...rest,
|
|
@@ -34523,11 +35332,11 @@ class WorktreeService {
|
|
|
34523
35332
|
run.finish(0);
|
|
34524
35333
|
this.arriving.delete(wt.id);
|
|
34525
35334
|
wt.branch = branch;
|
|
34526
|
-
move(chat.plans,
|
|
35335
|
+
move(chat.plans, join31(path, PLANS_DIR));
|
|
34527
35336
|
const dropped = await git(repo.path, "update-ref", "-d", archiveRef(old.id));
|
|
34528
35337
|
if (!dropped.ok)
|
|
34529
35338
|
log.warn(old.id, `could not drop its archive ref: ${dropped.err}`);
|
|
34530
|
-
this.
|
|
35339
|
+
this.tree.invalidate(wt.id);
|
|
34531
35340
|
this.d.state.save();
|
|
34532
35341
|
this.d.hub.emit("worktreesChanged");
|
|
34533
35342
|
fireAndForget(wt.id, this.setupAndStart(wt, repo, repo.path), "setup + start");
|
|
@@ -34590,6 +35399,7 @@ class WorktreeService {
|
|
|
34590
35399
|
agent.unqueue(0);
|
|
34591
35400
|
await this.d.runtime.stop(wt.id);
|
|
34592
35401
|
this.d.state.removeWorktree(wt.id, { refused: true });
|
|
35402
|
+
this.tree.forget(wt.id);
|
|
34593
35403
|
releasePort(wt.proxyPort);
|
|
34594
35404
|
}
|
|
34595
35405
|
async deleteArchived(archiveId) {
|
|
@@ -34598,12 +35408,12 @@ class WorktreeService {
|
|
|
34598
35408
|
return;
|
|
34599
35409
|
const repo = this.repoOf(rec);
|
|
34600
35410
|
const repoPath = repo?.path ?? rec.repoPath;
|
|
34601
|
-
if (rec.kept &&
|
|
35411
|
+
if (rec.kept && existsSync35(repoPath)) {
|
|
34602
35412
|
const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
|
|
34603
35413
|
if (!r.ok)
|
|
34604
35414
|
log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
|
|
34605
35415
|
}
|
|
34606
|
-
if (rec.worktree.lands?.length &&
|
|
35416
|
+
if (rec.worktree.lands?.length && existsSync35(repoPath))
|
|
34607
35417
|
await dropLandRefs(repoPath, archiveId);
|
|
34608
35418
|
this.archive.delete(archiveId);
|
|
34609
35419
|
this.d.drafts?.drop(archiveId);
|
|
@@ -34711,9 +35521,9 @@ class WorktreeService {
|
|
|
34711
35521
|
}
|
|
34712
35522
|
}
|
|
34713
35523
|
async mirrorBaseIgnore(repo) {
|
|
34714
|
-
const src =
|
|
34715
|
-
const tracked =
|
|
34716
|
-
const mirror =
|
|
35524
|
+
const src = join31(repo.path, ".gitignore");
|
|
35525
|
+
const tracked = existsSync35(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
|
|
35526
|
+
const mirror = existsSync35(src) && !tracked;
|
|
34717
35527
|
const lines = mirror ? readFileSync20(src, "utf8").split(`
|
|
34718
35528
|
`).filter((l) => l.trim() !== "") : [];
|
|
34719
35529
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
@@ -34726,9 +35536,9 @@ class WorktreeService {
|
|
|
34726
35536
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
34727
35537
|
run.finish(0);
|
|
34728
35538
|
}
|
|
34729
|
-
const srcNm =
|
|
34730
|
-
const dstNm =
|
|
34731
|
-
if (
|
|
35539
|
+
const srcNm = join31(depsSource, "node_modules");
|
|
35540
|
+
const dstNm = join31(wt.path, "node_modules");
|
|
35541
|
+
if (existsSync35(srcNm) && !existsSync35(dstNm)) {
|
|
34732
35542
|
const run = this.runs.begin(wt.id, "setup", { timeoutMs: ceiling, stage: "copying dependencies" });
|
|
34733
35543
|
const r = await cloneTree(srcNm, dstNm);
|
|
34734
35544
|
run.finish(0);
|
|
@@ -34738,10 +35548,10 @@ class WorktreeService {
|
|
|
34738
35548
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
34739
35549
|
}
|
|
34740
35550
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
34741
|
-
const src =
|
|
34742
|
-
if (!
|
|
35551
|
+
const src = join31(depsSource, f);
|
|
35552
|
+
if (!existsSync35(src) || existsSync35(join31(wt.path, f)))
|
|
34743
35553
|
continue;
|
|
34744
|
-
const r = await run("cp", [src,
|
|
35554
|
+
const r = await run("cp", [src, join31(wt.path, f)], wt.path);
|
|
34745
35555
|
if (r.ok)
|
|
34746
35556
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
34747
35557
|
else
|
|
@@ -34818,6 +35628,9 @@ class WorktreeService {
|
|
|
34818
35628
|
this.d.state.save();
|
|
34819
35629
|
this.d.hub.emit("worktreesChanged");
|
|
34820
35630
|
}
|
|
35631
|
+
fingerprint(path) {
|
|
35632
|
+
return (this.d.fingerprint ?? treeFingerprint)(path);
|
|
35633
|
+
}
|
|
34821
35634
|
setLanding(worktreeId, landing) {
|
|
34822
35635
|
const wt = this.d.state.worktree(worktreeId);
|
|
34823
35636
|
if (!wt || wt.landing === undefined && landing === undefined)
|
|
@@ -34898,7 +35711,7 @@ ${wt.landing.body}` : s;
|
|
|
34898
35711
|
return;
|
|
34899
35712
|
const { subject, body, unanswered, ...kept } = l;
|
|
34900
35713
|
const message = opts.dropMessage ? {} : { ...subject ? { subject } : {}, ...body ? { body } : {}, ...unanswered ? { unanswered } : {} };
|
|
34901
|
-
this.setLanding(wt.id, { ...kept, ...message, fingerprint: await
|
|
35714
|
+
this.setLanding(wt.id, { ...kept, ...message, fingerprint: await this.fingerprint(wt.path) });
|
|
34902
35715
|
}
|
|
34903
35716
|
async landedByHand(wt, mark) {
|
|
34904
35717
|
const repo = this.d.state.requireRepo(wt.repoId);
|
|
@@ -34931,7 +35744,13 @@ ${wt.landing.body}` : s;
|
|
|
34931
35744
|
return { ...result, message: "the commit-msg hook refused the message: another is being written" };
|
|
34932
35745
|
}
|
|
34933
35746
|
const { hook } = result;
|
|
34934
|
-
const failure = hook ? { kind: "hook", hook: hook.name, toolId: hook.toolId, command: hook.command, text: hook.text } : result.conflict ? {
|
|
35747
|
+
const failure = hook ? { kind: "hook", hook: hook.name, toolId: hook.toolId, command: hook.command, text: hook.text } : result.conflict ? {
|
|
35748
|
+
kind: "conflict",
|
|
35749
|
+
base,
|
|
35750
|
+
how: result.conflict,
|
|
35751
|
+
...result.step ? { step: result.step } : {},
|
|
35752
|
+
...result.carried ? { carried: result.carried } : {}
|
|
35753
|
+
} : null;
|
|
34935
35754
|
return failure && this.d.fix.report(wt.id, failure) ? { ...result, asked: true } : result;
|
|
34936
35755
|
}
|
|
34937
35756
|
async fixPreview(worktreeId, clientId) {
|
|
@@ -34956,7 +35775,7 @@ ${wt.landing.body}` : s;
|
|
|
34956
35775
|
if (status !== "working" && status !== "waiting")
|
|
34957
35776
|
return null;
|
|
34958
35777
|
const l = wt.landing;
|
|
34959
|
-
if (!l || l.fingerprint === await
|
|
35778
|
+
if (!l || l.fingerprint === await this.fingerprint(wt.path))
|
|
34960
35779
|
return null;
|
|
34961
35780
|
return { ok: false, message: "the agent has changed files since the check: land once its turn ends" };
|
|
34962
35781
|
}
|
|
@@ -34988,9 +35807,17 @@ ${wt.landing.body}` : s;
|
|
|
34988
35807
|
const release = this.d.runtime.agentFor(id)?.hold();
|
|
34989
35808
|
this.ops.set(id, { op });
|
|
34990
35809
|
this.d.hub.emit("worktreesChanged");
|
|
35810
|
+
const started = Date.now();
|
|
35811
|
+
const watchdog = setInterval(() => {
|
|
35812
|
+
const step = this.ops.get(id)?.step;
|
|
35813
|
+
const at = step ? ` at "${step}"` : "";
|
|
35814
|
+
log.warn(id, `${shipNoun(op)} still running after ${describeDuration(Date.now() - started)}${at}`);
|
|
35815
|
+
}, RUN_CEILING_MS);
|
|
35816
|
+
watchdog.unref?.();
|
|
34991
35817
|
try {
|
|
34992
35818
|
return await run();
|
|
34993
35819
|
} finally {
|
|
35820
|
+
clearInterval(watchdog);
|
|
34994
35821
|
this.ops.delete(id);
|
|
34995
35822
|
release?.();
|
|
34996
35823
|
this.d.hub.emit("worktreesChanged");
|
|
@@ -35031,10 +35858,12 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35031
35858
|
const committed = await this.commitIfDirty(wt, message);
|
|
35032
35859
|
if (committed && !committed.ok)
|
|
35033
35860
|
return { result: committed };
|
|
35861
|
+
this.tree.invalidate(wt.id);
|
|
35034
35862
|
const taken = await takeMainIn(wt.path, base, own, w);
|
|
35035
35863
|
if (!taken.ok)
|
|
35036
35864
|
return { result: taken };
|
|
35037
35865
|
await this.headMoved(wt.id);
|
|
35866
|
+
this.tree.invalidate(wt.id);
|
|
35038
35867
|
const pushed = await pushBranch(wt.path, wt.branch, w);
|
|
35039
35868
|
if (!pushed.ok)
|
|
35040
35869
|
return { result: pushed };
|
|
@@ -35047,6 +35876,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35047
35876
|
if (committed && !committed.ok)
|
|
35048
35877
|
return { result: committed };
|
|
35049
35878
|
const mark = await landingMark(wt.path, base);
|
|
35879
|
+
this.tree.invalidate(wt.id);
|
|
35050
35880
|
const taken = await takeMainIn(wt.path, base, own, w);
|
|
35051
35881
|
if (!taken.ok)
|
|
35052
35882
|
return { result: taken };
|
|
@@ -35135,6 +35965,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35135
35965
|
const fetched = await this.fetchBase(wt, repo, w);
|
|
35136
35966
|
if (!fetched.ok)
|
|
35137
35967
|
return { result: fetched };
|
|
35968
|
+
this.tree.invalidate(wt.id);
|
|
35138
35969
|
const taken = await takeMainIn(wt.path, base, own, w);
|
|
35139
35970
|
if (!taken.ok)
|
|
35140
35971
|
return { result: taken };
|
|
@@ -35145,6 +35976,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35145
35976
|
break;
|
|
35146
35977
|
}
|
|
35147
35978
|
squash = method === "squash" ? await squashMessage(wt.path, base, suggested, !!message?.trim()) : "";
|
|
35979
|
+
this.tree.invalidate(wt.id);
|
|
35148
35980
|
const built = await landingCommit(wt.path, wt.branch, base, method, squash, own, w);
|
|
35149
35981
|
await this.headMoved(wt.id);
|
|
35150
35982
|
if (!built.ok || !built.sha)
|
|
@@ -35211,11 +36043,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35211
36043
|
async restartFromMain(wt, base) {
|
|
35212
36044
|
if (!hasOwnBranch(wt))
|
|
35213
36045
|
return;
|
|
36046
|
+
this.tree.invalidate(wt.id);
|
|
35214
36047
|
const r = await git(wt.path, "reset", "--keep", base);
|
|
35215
36048
|
if (!r.ok)
|
|
35216
36049
|
log.warn(wt.id, `could not restart ${wt.branch} from ${base}: ${r.err}`);
|
|
35217
|
-
this.
|
|
35218
|
-
await this.counts.refresh([wt.id]);
|
|
36050
|
+
await this.tree.refresh(wt.id);
|
|
35219
36051
|
}
|
|
35220
36052
|
async fetchBase(wt, repo, w) {
|
|
35221
36053
|
if (!baseIsRemote(repo))
|
|
@@ -35225,13 +36057,22 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35225
36057
|
return r;
|
|
35226
36058
|
}
|
|
35227
36059
|
async prMerged(worktreeId) {
|
|
35228
|
-
const out = this.ops.get(worktreeId);
|
|
35229
|
-
if (out)
|
|
35230
|
-
return { ok: false, message: `${shipNoun(out.op)} is already running here` };
|
|
35231
36060
|
const wt = this.d.state.worktree(worktreeId);
|
|
36061
|
+
const pr = wt?.pr ? `PR #${wt.pr.number}` : "the PR";
|
|
36062
|
+
const out = this.ops.get(worktreeId);
|
|
36063
|
+
if (out) {
|
|
36064
|
+
const message = `${shipNoun(out.op)} is already running here`;
|
|
36065
|
+
log.info(worktreeId, `${pr} merged on GitHub; ${message}, landing waits`);
|
|
36066
|
+
return { ok: false, message };
|
|
36067
|
+
}
|
|
35232
36068
|
if (wt && prTaken(wt))
|
|
35233
|
-
return { ok: true, message:
|
|
36069
|
+
return { ok: true, message: `${pr} merged; landed already` };
|
|
36070
|
+
log.info(worktreeId, `${pr} merged on GitHub; landing it`);
|
|
35234
36071
|
const { mark, ...result } = await this.ship(worktreeId, "land", () => this.prMergedOp(worktreeId));
|
|
36072
|
+
if (result.ok)
|
|
36073
|
+
log.info(worktreeId, `landed: ${result.message}`);
|
|
36074
|
+
else
|
|
36075
|
+
log.warn(worktreeId, `landing failed: ${result.message}`);
|
|
35235
36076
|
if (result.ok && wt)
|
|
35236
36077
|
this.noteLanded(wt, result.message, this.siblingIds(wt), mark);
|
|
35237
36078
|
return result;
|
|
@@ -35284,6 +36125,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35284
36125
|
if (!fetched.ok)
|
|
35285
36126
|
return fetched;
|
|
35286
36127
|
}
|
|
36128
|
+
this.tree.invalidate(worktreeId);
|
|
35287
36129
|
const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w, carry));
|
|
35288
36130
|
if (taken.ok) {
|
|
35289
36131
|
await this.headMoved(worktreeId);
|
|
@@ -35368,19 +36210,26 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35368
36210
|
return found ?? undefined;
|
|
35369
36211
|
}
|
|
35370
36212
|
async headMoved(worktreeId) {
|
|
35371
|
-
this.
|
|
35372
|
-
await this.counts.refresh([worktreeId]);
|
|
36213
|
+
await this.tree.refresh(worktreeId);
|
|
35373
36214
|
this.d.hub.emit("worktreesChanged");
|
|
35374
36215
|
}
|
|
35375
36216
|
async recountAll() {
|
|
35376
|
-
this.
|
|
35377
|
-
|
|
36217
|
+
this.tree.invalidateAll();
|
|
36218
|
+
for (;; ) {
|
|
36219
|
+
const reading = this.tree.refreshStale(this.countedIds());
|
|
36220
|
+
if (!reading)
|
|
36221
|
+
return;
|
|
36222
|
+
await reading;
|
|
36223
|
+
}
|
|
35378
36224
|
}
|
|
35379
36225
|
invalidateCounts(worktreeId) {
|
|
35380
|
-
|
|
36226
|
+
if (worktreeId)
|
|
36227
|
+
this.tree.invalidate(worktreeId);
|
|
36228
|
+
else
|
|
36229
|
+
this.tree.invalidateAll();
|
|
35381
36230
|
}
|
|
35382
36231
|
recount(repoId) {
|
|
35383
|
-
this.
|
|
36232
|
+
this.tree.invalidateAll();
|
|
35384
36233
|
this.d.hub.emit("repoTick", repoId);
|
|
35385
36234
|
const repo = this.d.state.repo(repoId);
|
|
35386
36235
|
if (repo && baseIsRemote(repo))
|
|
@@ -35410,7 +36259,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35410
36259
|
}
|
|
35411
36260
|
listDue(repoId) {
|
|
35412
36261
|
const cached = this.discoverCache.get(repoId);
|
|
35413
|
-
return !cached || Date.now() - cached.at >=
|
|
36262
|
+
return !cached || Date.now() - cached.at >= LIST_FLOOR_MS;
|
|
35414
36263
|
}
|
|
35415
36264
|
async relist(repos) {
|
|
35416
36265
|
const moved = await Promise.all(repos.map((repo) => {
|
|
@@ -35444,29 +36293,13 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35444
36293
|
this.d.hub.emit("worktreesChanged");
|
|
35445
36294
|
return any;
|
|
35446
36295
|
}
|
|
35447
|
-
async readCounts(id) {
|
|
35448
|
-
const wt = this.d.state.worktree(id);
|
|
35449
|
-
const found = wt ? null : this.discoveredById(id);
|
|
35450
|
-
const row = wt ?? found;
|
|
35451
|
-
const repo = row && this.d.state.repo(row.repoId);
|
|
35452
|
-
if (!row || !repo)
|
|
35453
|
-
return null;
|
|
35454
|
-
const countable = wt ? !isMain(wt) : !!found?.branch;
|
|
35455
|
-
const ab = countable ? await aheadBehind(row.path, baseOf(repo)) : await this.trunk.behind(id, row.path);
|
|
35456
|
-
const dirty = (await statusFiles(row.path)).length;
|
|
35457
|
-
const unpushed = await this.unpushed(id, row.path);
|
|
35458
|
-
return { ...ab, ...unpushed === undefined ? {} : { unpushed }, dirty };
|
|
35459
|
-
}
|
|
35460
|
-
async unpushed(id, path) {
|
|
35461
|
-
const wt = this.d.state.worktree(id);
|
|
35462
|
-
if (wt?.pr?.state !== "open")
|
|
35463
|
-
return;
|
|
35464
|
-
return await aheadUpstream(path, wt.branch) ?? undefined;
|
|
35465
|
-
}
|
|
35466
36296
|
async freshCounts(worktreeId) {
|
|
35467
36297
|
if (this.going.has(worktreeId))
|
|
35468
36298
|
return {};
|
|
35469
|
-
|
|
36299
|
+
const rec = await this.tree.refresh(worktreeId);
|
|
36300
|
+
if (!rec || rec.failedAt !== undefined)
|
|
36301
|
+
return {};
|
|
36302
|
+
return { ...rec.core.ahead === undefined ? {} : { ahead: rec.core.ahead }, dirty: rec.core.files.length };
|
|
35470
36303
|
}
|
|
35471
36304
|
readable(id) {
|
|
35472
36305
|
const wt = this.d.state.worktree(id);
|
|
@@ -35503,68 +36336,60 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35503
36336
|
if (wt && !this.going.has(worktreeId))
|
|
35504
36337
|
await markTurn(wt.path, wt.id);
|
|
35505
36338
|
}
|
|
35506
|
-
async
|
|
35507
|
-
|
|
35508
|
-
|
|
35509
|
-
|
|
35510
|
-
if (
|
|
35511
|
-
return
|
|
35512
|
-
|
|
35513
|
-
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
|
|
35518
|
-
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
const counts = { ...ab, ...unpushed === undefined ? {} : { unpushed } };
|
|
35524
|
-
const ahead = counts.ahead ?? 0;
|
|
35525
|
-
const committed = !isMain && ahead > 0 ? await committedFiles(r.path, r.base) : undefined;
|
|
35526
|
-
const turn = r.wt && files.length > 0 ? await turnChanges(r.path, r.wt.id, r.base, !isMain, files) : null;
|
|
35527
|
-
if (r.wt && !isMain && hasOwnBranch(r.wt) && files.length === 0 && ahead === 0 && head.ok && this.handChecked.get(r.wt.id) !== head.out) {
|
|
35528
|
-
this.handChecked.set(r.wt.id, head.out);
|
|
35529
|
-
const mark = await handLanding(r.path, r.wt.branch, r.base, r.wt.lands?.at(-1)?.tip);
|
|
35530
|
-
if (mark) {
|
|
35531
|
-
await this.landedByHand(r.wt, mark);
|
|
35532
|
-
return this.gitStatus(worktreeId);
|
|
35533
|
-
}
|
|
36339
|
+
async afterCore(r, core) {
|
|
36340
|
+
const wt = r.wt;
|
|
36341
|
+
if (!wt)
|
|
36342
|
+
return false;
|
|
36343
|
+
if (this.ops.has(wt.id))
|
|
36344
|
+
return false;
|
|
36345
|
+
const isMain = wt.kind === "main";
|
|
36346
|
+
if (core.branch)
|
|
36347
|
+
await this.followBranch(wt, core.branch);
|
|
36348
|
+
const { files, head } = core;
|
|
36349
|
+
const ahead = core.ahead ?? 0;
|
|
36350
|
+
if (!isMain && hasOwnBranch(wt) && files.length === 0 && ahead === 0 && head && this.handChecked.get(wt.id) !== head) {
|
|
36351
|
+
this.handChecked.set(wt.id, head);
|
|
36352
|
+
const mark = await handLanding(r.path, wt.branch, r.base, wt.lands?.at(-1)?.tip);
|
|
36353
|
+
if (mark) {
|
|
36354
|
+
await this.landedByHand(wt, mark);
|
|
36355
|
+
return true;
|
|
35534
36356
|
}
|
|
35535
|
-
|
|
35536
|
-
|
|
35537
|
-
|
|
35538
|
-
|
|
35539
|
-
|
|
35540
|
-
|
|
35541
|
-
|
|
35542
|
-
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
}
|
|
36357
|
+
}
|
|
36358
|
+
if (!isMain && head) {
|
|
36359
|
+
const facts = { clean: files.length === 0, head, ahead };
|
|
36360
|
+
if (prTaken(wt) && movedPastLand(wt, facts))
|
|
36361
|
+
this.setPr(wt.id, undefined);
|
|
36362
|
+
await this.refreshLanded(wt, facts);
|
|
36363
|
+
}
|
|
36364
|
+
const landing = wt.landing;
|
|
36365
|
+
if (landing && landing.check !== "pending") {
|
|
36366
|
+
const moved = landing.fingerprint !== await this.fingerprint(r.path);
|
|
36367
|
+
if (wt.landing === landing && moved !== !!landing.stale) {
|
|
36368
|
+
const { stale: _was, ...rest } = landing;
|
|
36369
|
+
this.setLanding(wt.id, moved ? { ...rest, stale: true } : rest);
|
|
35547
36370
|
}
|
|
35548
|
-
if (r.wt && isMain)
|
|
35549
|
-
this.setEmpty(r.wt, files.length === 0 ? await treeEmpty(r.path) : false);
|
|
35550
|
-
if (this.going.has(worktreeId) || this.arriving.has(worktreeId))
|
|
35551
|
-
return null;
|
|
35552
|
-
this.noteCounts(worktreeId, isMain, files.length, counts);
|
|
35553
|
-
return { files, committed, ...turn ? { turn } : {}, head: head.ok ? head.out : undefined, ...counts };
|
|
35554
|
-
} catch (e) {
|
|
35555
|
-
log.warn(worktreeId, "git status failed", e);
|
|
35556
|
-
return null;
|
|
35557
36371
|
}
|
|
36372
|
+
if (isMain)
|
|
36373
|
+
this.setEmpty(wt, files.length === 0 ? await treeEmpty(r.path) : false);
|
|
36374
|
+
return false;
|
|
35558
36375
|
}
|
|
35559
|
-
|
|
35560
|
-
|
|
35561
|
-
|
|
35562
|
-
|
|
35563
|
-
|
|
35564
|
-
const behind = main ? prev?.behind : ab.behind;
|
|
35565
|
-
const unpushed = main ? prev?.unpushed : ab.unpushed;
|
|
35566
|
-
if (this.counts.put(id, { ahead, behind, unpushed, dirty }))
|
|
36376
|
+
async gitStatus(worktreeId) {
|
|
36377
|
+
if (!this.readable(worktreeId))
|
|
36378
|
+
return this.archivedStatus(worktreeId);
|
|
36379
|
+
const { frame, moved } = await this.tree.full(worktreeId);
|
|
36380
|
+
if (moved)
|
|
35567
36381
|
this.d.hub.emit("worktreesChanged");
|
|
36382
|
+
return frame;
|
|
36383
|
+
}
|
|
36384
|
+
rowsOf(repoId) {
|
|
36385
|
+
const ids = this.d.state.worktrees.filter((wt) => wt.repoId === repoId && this.isRow(wt)).map((wt) => wt.id);
|
|
36386
|
+
for (const f of this.foundCached())
|
|
36387
|
+
if (f.repoId === repoId)
|
|
36388
|
+
ids.push(f.id);
|
|
36389
|
+
const main = this.mainOf(repoId);
|
|
36390
|
+
if (main && !ids.includes(main.id))
|
|
36391
|
+
ids.push(main.id);
|
|
36392
|
+
return ids;
|
|
35568
36393
|
}
|
|
35569
36394
|
async gitLog(worktreeId) {
|
|
35570
36395
|
const r = this.readable(worktreeId);
|
|
@@ -35611,9 +36436,9 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35611
36436
|
snapshot() {
|
|
35612
36437
|
const rows = [...this.ownedRows(), ...this.foundRows()];
|
|
35613
36438
|
const trunks = this.trunksNow();
|
|
35614
|
-
const reading = this.
|
|
36439
|
+
const reading = this.tree.refreshStale(idsOf(rows, trunks));
|
|
35615
36440
|
if (reading) {
|
|
35616
|
-
this.behind("
|
|
36441
|
+
this.behind("tree", reading.then((moved) => {
|
|
35617
36442
|
if (moved)
|
|
35618
36443
|
this.d.hub.emit("worktreesChanged");
|
|
35619
36444
|
}), "badge counts");
|
|
@@ -35637,11 +36462,18 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35637
36462
|
}
|
|
35638
36463
|
async settled() {
|
|
35639
36464
|
let waited = false;
|
|
35640
|
-
|
|
35641
|
-
|
|
35642
|
-
|
|
36465
|
+
for (;; ) {
|
|
36466
|
+
if (this.pending.size) {
|
|
36467
|
+
waited = true;
|
|
36468
|
+
await Promise.all(this.pending);
|
|
36469
|
+
continue;
|
|
36470
|
+
}
|
|
36471
|
+
if (await this.tree.settled()) {
|
|
36472
|
+
waited = true;
|
|
36473
|
+
continue;
|
|
36474
|
+
}
|
|
36475
|
+
return waited;
|
|
35643
36476
|
}
|
|
35644
|
-
return waited;
|
|
35645
36477
|
}
|
|
35646
36478
|
mainOf(repoId) {
|
|
35647
36479
|
return mainOf(this.d.state, repoId);
|
|
@@ -35658,7 +36490,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35658
36490
|
ownedRows() {
|
|
35659
36491
|
return this.d.state.worktrees.filter((wt) => this.isRow(wt)).map((wt) => {
|
|
35660
36492
|
const rt = this.d.runtime.get(wt.id);
|
|
35661
|
-
const { ahead, behind, unpushed, dirty } = this.counts
|
|
36493
|
+
const { ahead, behind, unpushed, dirty } = this.tree.counts(wt.id) ?? {};
|
|
35662
36494
|
return {
|
|
35663
36495
|
id: wt.id,
|
|
35664
36496
|
repoId: wt.repoId,
|
|
@@ -35686,7 +36518,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
|
|
|
35686
36518
|
}
|
|
35687
36519
|
foundRows() {
|
|
35688
36520
|
return this.foundNow().map((f) => {
|
|
35689
|
-
const { ahead, behind, dirty } = this.counts
|
|
36521
|
+
const { ahead, behind, dirty } = this.tree.counts(f.id) ?? {};
|
|
35690
36522
|
return {
|
|
35691
36523
|
...f,
|
|
35692
36524
|
procs: [],
|
|
@@ -35792,6 +36624,7 @@ class ArchiveSweep {
|
|
|
35792
36624
|
// ../daemon/src/index.ts
|
|
35793
36625
|
process.on("unhandledRejection", (e) => log.error("daemon", "unhandled rejection", e));
|
|
35794
36626
|
process.on("uncaughtException", (e) => log.error("daemon", "uncaught exception", e));
|
|
36627
|
+
log.info("daemon", `toyon ${package_default.version} on bun ${Bun.version}, pid ${process.pid}, ${process.argv[1]}`);
|
|
35795
36628
|
var here = dirname13(fileURLToPath(import.meta.url));
|
|
35796
36629
|
var {
|
|
35797
36630
|
shellDist: SHELL_DIST,
|
|
@@ -35808,17 +36641,17 @@ if (pruned > 0)
|
|
|
35808
36641
|
var managed = await loadManaged();
|
|
35809
36642
|
var paths = makePaths();
|
|
35810
36643
|
ensureDirs(paths);
|
|
35811
|
-
forwardTranscripts([paths.transcriptsDir, paths.archiveDir],
|
|
36644
|
+
forwardTranscripts([paths.transcriptsDir, paths.archiveDir], join32(paths.home, "transcripts-forwarded"));
|
|
35812
36645
|
var token = loadOrCreateToken(paths);
|
|
35813
36646
|
var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
|
|
35814
|
-
var helperApp =
|
|
36647
|
+
var helperApp = join32(paths.home, DAEMON_FILES.helper);
|
|
35815
36648
|
var helper = helperWanted({ platform: process.platform, cloud: cloud.enabled, port, home: process.env.TOYON_HOME }) ? new Helper({
|
|
35816
36649
|
dir: helperApp,
|
|
35817
36650
|
stub: HELPER_STUB,
|
|
35818
36651
|
bun: process.execPath,
|
|
35819
36652
|
cli: CLI_ENTRY,
|
|
35820
|
-
log:
|
|
35821
|
-
icon:
|
|
36653
|
+
log: join32(paths.home, DAEMON_FILES.launcherLog),
|
|
36654
|
+
icon: join32(SHELL_DIST, "icon.svg"),
|
|
35822
36655
|
version: package_default.version
|
|
35823
36656
|
}) : null;
|
|
35824
36657
|
if (helper) {
|
|
@@ -35903,6 +36736,7 @@ var runs = new RunService({ state, hub });
|
|
|
35903
36736
|
var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
|
|
35904
36737
|
var cache = new ArtifactCache({ paths, state, hub });
|
|
35905
36738
|
var fix = new FixService({ state, hub, runtime });
|
|
36739
|
+
var interest = new Interest;
|
|
35906
36740
|
var worktrees = new WorktreeService({
|
|
35907
36741
|
state,
|
|
35908
36742
|
hub,
|
|
@@ -35914,8 +36748,12 @@ var worktrees = new WorktreeService({
|
|
|
35914
36748
|
watch: (id, command, run) => exec.watch(id, command, run),
|
|
35915
36749
|
runs,
|
|
35916
36750
|
fix,
|
|
35917
|
-
pageErrors: (id) => pageErrors.ambient(id)
|
|
36751
|
+
pageErrors: (id) => pageErrors.ambient(id),
|
|
36752
|
+
interested: (id) => interest.isSubscribed(id),
|
|
36753
|
+
shown: (id) => idle.isShown(id)
|
|
35918
36754
|
});
|
|
36755
|
+
interest.onFirst((id) => worktrees.tree.watch(id));
|
|
36756
|
+
interest.onLast((id) => worktrees.tree.unwatch(id));
|
|
35919
36757
|
new BackendShare({ state, hub, runtime, own: (id, names) => worktrees.ownProcs(id, names) });
|
|
35920
36758
|
var handoff2 = new HandoffService({
|
|
35921
36759
|
state,
|
|
@@ -36064,6 +36902,7 @@ var {
|
|
|
36064
36902
|
hub,
|
|
36065
36903
|
repos,
|
|
36066
36904
|
worktrees,
|
|
36905
|
+
interest,
|
|
36067
36906
|
turns,
|
|
36068
36907
|
files,
|
|
36069
36908
|
opens,
|
|
@@ -36109,6 +36948,14 @@ fireAndForget("remote", tailnet.start(), "ask Tailscale");
|
|
|
36109
36948
|
writeFileSync13(paths.pidFile, `${process.pid}
|
|
36110
36949
|
`);
|
|
36111
36950
|
var stopLagSampler = startLagSampler();
|
|
36951
|
+
fireAndForget("daemon", run("sh", ["-c", "ulimit -n"], homedir15()).then((r) => {
|
|
36952
|
+
const n = Number.parseInt(r.out, 10);
|
|
36953
|
+
if (!r.ok || !Number.isFinite(n))
|
|
36954
|
+
return;
|
|
36955
|
+
limits.fdLimit = n;
|
|
36956
|
+
if (n < FD_LIMIT_LOW)
|
|
36957
|
+
log.warn("daemon", fdLimitLine(n));
|
|
36958
|
+
}), "open-file limit");
|
|
36112
36959
|
await runtime.reclaimOrphans();
|
|
36113
36960
|
await repos.boot();
|
|
36114
36961
|
drafts.prune((id) => !!state.worktree(id) || worktrees.hasArchived(id));
|
|
@@ -36131,7 +36978,7 @@ if (repoArg) {
|
|
|
36131
36978
|
if (r?.front === "edge") {
|
|
36132
36979
|
console.log(`Toyon daemon on https://${r.host}/, ${previewsLine(r)}; token read from ${paths.tokenFile}`);
|
|
36133
36980
|
} else {
|
|
36134
|
-
console.log(`Toyon daemon on http://toyon.localhost${branded ? "" : `:${port}`}
|
|
36981
|
+
console.log(`Toyon daemon on http://toyon.localhost${branded ? "" : `:${port}`}/; token read from ${paths.tokenFile}`);
|
|
36135
36982
|
if (r)
|
|
36136
36983
|
console.log(` remote: https://${r.host}/, ${previewsLine(r)}`);
|
|
36137
36984
|
}
|
|
@@ -36157,6 +37004,7 @@ async function shutdown(signal, opts = {}) {
|
|
|
36157
37004
|
repos.stopWatchers();
|
|
36158
37005
|
prs.stop();
|
|
36159
37006
|
sweep.stop();
|
|
37007
|
+
worktrees.tree.stop();
|
|
36160
37008
|
stopLagSampler();
|
|
36161
37009
|
stopServer();
|
|
36162
37010
|
routes.flush();
|