toyon 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bridge.js +1 -1
- package/dist/cli.js +19 -1
- package/dist/daemon.js +863 -405
- package/dist/shell/assets/{Editor-Duhn2F88.js → Editor-CrfFlcLA.js} +4 -4
- package/dist/shell/assets/XTerm-CsuyFSMK.js +58 -0
- package/dist/shell/assets/{cssMode-CaBWxaDj.js → cssMode-CWkICf31.js} +1 -1
- package/dist/shell/assets/{freemarker2-Qj5oMLk2.js → freemarker2-C2fP12sE.js} +1 -1
- package/dist/shell/assets/{grammar-YjPoOd1d.js → grammar-5S_Kl1e8.js} +1 -1
- package/dist/shell/assets/{handlebars-N_jFNLBH.js → handlebars-CkRyqZGC.js} +1 -1
- package/dist/shell/assets/{html-CuTufuXa.js → html-C9iJKWKA.js} +1 -1
- package/dist/shell/assets/{htmlMode-DzHlP1s-.js → htmlMode-Dps-6NJd.js} +1 -1
- package/dist/shell/assets/index-BKyLPsvB.js +258 -0
- package/dist/shell/assets/index-DIDGdnCY.css +1 -0
- package/dist/shell/assets/{javascript-DBXFKiXL.js → javascript-DM5EiBY7.js} +1 -1
- package/dist/shell/assets/{jsonMode-DVynVZZ-.js → jsonMode-ampRCTsW.js} +1 -1
- package/dist/shell/assets/{liquid-CPflmua8.js → liquid-CEUat1ZS.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-Bw23HdhQ.js → lspLanguageFeatures-ChupARNW.js} +1 -1
- package/dist/shell/assets/{mdx-BIo7iUIS.js → mdx-DAglTq4w.js} +1 -1
- package/dist/shell/assets/{python-w7xWVunv.js → python-DOPtAG-1.js} +1 -1
- package/dist/shell/assets/{razor-C_h6ql0l.js → razor-qRDOBjWc.js} +1 -1
- package/dist/shell/assets/{tsMode-BD-oIhVs.js → tsMode-BD49NPBt.js} +1 -1
- package/dist/shell/assets/{xml-BDJjSxEs.js → xml-p5Js1H7t.js} +1 -1
- package/dist/shell/assets/{yaml-BXfVW0R6.js → yaml-CctH9qfh.js} +1 -1
- package/dist/shell/index.html +2 -2
- package/package.json +1 -1
- package/dist/shell/assets/XTerm-CF8bkkwi.js +0 -40
- package/dist/shell/assets/index-BWSk_4yc.js +0 -250
- package/dist/shell/assets/index-C01XmhdL.css +0 -1
package/dist/daemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../daemon/src/index.ts
|
|
3
3
|
import { rmSync as rmSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
4
|
-
import { dirname as
|
|
4
|
+
import { dirname as dirname12 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
7
7
|
// ../shared/src/agent-tools.ts
|
|
@@ -6469,7 +6469,7 @@ var PAIR_TTL_MS = 120000;
|
|
|
6469
6469
|
var pairLink = (host, code) => `https://${host}/#pair=${code}`;
|
|
6470
6470
|
|
|
6471
6471
|
// ../shared/src/protocol/limits.ts
|
|
6472
|
-
var PROTOCOL_VERSION =
|
|
6472
|
+
var PROTOCOL_VERSION = 83;
|
|
6473
6473
|
var FILE_MAX_CHARS = 5000000;
|
|
6474
6474
|
var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
6475
6475
|
var IMAGE_MAX_EDGE = 2576;
|
|
@@ -6542,6 +6542,7 @@ var elementTraitsSchema = object({
|
|
|
6542
6542
|
// ../shared/src/protocol/events.ts
|
|
6543
6543
|
var SHELL_TOOL = "shell";
|
|
6544
6544
|
var CHECK_TOOL = "check";
|
|
6545
|
+
var TOOL_SEARCH = "ToolSearch";
|
|
6545
6546
|
|
|
6546
6547
|
// ../shared/src/protocol/ws.ts
|
|
6547
6548
|
var id = string2().min(1).max(200);
|
|
@@ -6793,7 +6794,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6793
6794
|
object({ t: literal("search"), worktreeId: id, query: string2().max(500) }),
|
|
6794
6795
|
object({ t: literal("find-element"), worktreeId: id, seq, element: elementTraitsSchema }),
|
|
6795
6796
|
object({ t: literal("design-scan"), worktreeId: id }),
|
|
6796
|
-
object({ t: literal("discard-
|
|
6797
|
+
object({ t: literal("discard-files"), worktreeId: id, paths: array(relPath).min(1).max(500) }),
|
|
6797
6798
|
object({ t: literal("judge"), worktreeId: id, note: string2().max(500).optional() }),
|
|
6798
6799
|
object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
|
|
6799
6800
|
object({ t: literal("stop-agent"), worktreeId: id }),
|
|
@@ -6905,6 +6906,18 @@ function parseClientMsg(raw) {
|
|
|
6905
6906
|
return { ok: false, reason: issueReason(r.error, "invalid message") };
|
|
6906
6907
|
}
|
|
6907
6908
|
|
|
6909
|
+
// ../shared/src/query.ts
|
|
6910
|
+
var TERM = /["\u201C\u201D]([^"\u201C\u201D]*)["\u201C\u201D]?|(\S+)/g;
|
|
6911
|
+
function queryTerms(query) {
|
|
6912
|
+
const out = new Set;
|
|
6913
|
+
for (const m of query.matchAll(TERM)) {
|
|
6914
|
+
const term = (m[1] ?? m[2] ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
6915
|
+
if (term)
|
|
6916
|
+
out.add(term);
|
|
6917
|
+
}
|
|
6918
|
+
return [...out];
|
|
6919
|
+
}
|
|
6920
|
+
|
|
6908
6921
|
// ../shared/src/restart.ts
|
|
6909
6922
|
var RESTART_NOW = "now";
|
|
6910
6923
|
|
|
@@ -8141,7 +8154,7 @@ function safeUser() {
|
|
|
8141
8154
|
// ../daemon/package.json
|
|
8142
8155
|
var package_default = {
|
|
8143
8156
|
name: "@toyon/daemon",
|
|
8144
|
-
version: "0.
|
|
8157
|
+
version: "0.8.1",
|
|
8145
8158
|
private: true,
|
|
8146
8159
|
type: "module",
|
|
8147
8160
|
main: "src/index.ts",
|
|
@@ -13424,7 +13437,7 @@ var BUILTIN_AGENTS = [
|
|
|
13424
13437
|
name: "Claude Code",
|
|
13425
13438
|
short: "Claude",
|
|
13426
13439
|
builtin: true,
|
|
13427
|
-
run: { kind: "npm-bin", pkg: "@agentclientprotocol/claude-agent-acp", version: "0.
|
|
13440
|
+
run: { kind: "npm-bin", pkg: "@agentclientprotocol/claude-agent-acp", version: "0.84.0", bin: "claude-agent-acp" },
|
|
13428
13441
|
...cloud.enabled ? { env: { NO_BROWSER: "1" } } : {},
|
|
13429
13442
|
terminalLogins: {
|
|
13430
13443
|
"claude-login": { args: ["--cli", "auth", "login", "--claudeai"], env: { NO_BROWSER: "1" }, plan: true }
|
|
@@ -13443,7 +13456,7 @@ var BUILTIN_AGENTS = [
|
|
|
13443
13456
|
id: "codex",
|
|
13444
13457
|
name: "Codex",
|
|
13445
13458
|
builtin: true,
|
|
13446
|
-
run: { kind: "npm-bin", pkg: "@agentclientprotocol/codex-acp", version: "
|
|
13459
|
+
run: { kind: "npm-bin", pkg: "@agentclientprotocol/codex-acp", version: "2.0.1", bin: "codex-acp" },
|
|
13447
13460
|
env: {
|
|
13448
13461
|
INITIAL_AGENT_MODE: "agent",
|
|
13449
13462
|
CODEX_CONFIG: JSON.stringify({ features: { default_mode_request_user_input: true } }),
|
|
@@ -13460,7 +13473,7 @@ var BUILTIN_AGENTS = [
|
|
|
13460
13473
|
id: "opencode",
|
|
13461
13474
|
name: "OpenCode",
|
|
13462
13475
|
builtin: true,
|
|
13463
|
-
run: { kind: "npm-native", pkg: "opencode", version: "1.18.
|
|
13476
|
+
run: { kind: "npm-native", pkg: "opencode", version: "1.18.33", bin: "bin/opencode", args: ["acp"] },
|
|
13464
13477
|
onDemand: true,
|
|
13465
13478
|
confinement: "toyon-sandbox",
|
|
13466
13479
|
stateDirs: [".local/share/opencode", ".local/state/opencode", ".cache/opencode"],
|
|
@@ -13645,6 +13658,19 @@ class AgentRegistry {
|
|
|
13645
13658
|
await this.install(id);
|
|
13646
13659
|
}
|
|
13647
13660
|
}
|
|
13661
|
+
adapters() {
|
|
13662
|
+
const out = [];
|
|
13663
|
+
for (const spec of this.list()) {
|
|
13664
|
+
if (spec.run.kind === "command" || !this.packageName(spec))
|
|
13665
|
+
continue;
|
|
13666
|
+
const installed = this.installed(spec) ? this.installedVersion(spec) : null;
|
|
13667
|
+
const error = this.installErrors.get(spec.id);
|
|
13668
|
+
if (spec.onDemand && installed === null && !error)
|
|
13669
|
+
continue;
|
|
13670
|
+
out.push({ id: spec.id, installed, pinned: spec.run.version, ...error ? { error } : {} });
|
|
13671
|
+
}
|
|
13672
|
+
return out;
|
|
13673
|
+
}
|
|
13648
13674
|
infos() {
|
|
13649
13675
|
return this.list().map((spec) => {
|
|
13650
13676
|
const reason = this.unavailable(spec);
|
|
@@ -13725,6 +13751,29 @@ function modeAfterPlan(optionName) {
|
|
|
13725
13751
|
return /manual/i.test(optionName) ? "ask" : "auto";
|
|
13726
13752
|
}
|
|
13727
13753
|
|
|
13754
|
+
// ../daemon/src/agent/output.ts
|
|
13755
|
+
var OUTPUT_CAP = 200000;
|
|
13756
|
+
function formatOutput(text, exit, truncated, ceiling) {
|
|
13757
|
+
const body = text.replace(/\n+$/, "");
|
|
13758
|
+
const notes = [];
|
|
13759
|
+
if (truncated)
|
|
13760
|
+
notes.push(`output cut at ${Math.round(OUTPUT_CAP / 1000)} KB`);
|
|
13761
|
+
if (exit === "timeout")
|
|
13762
|
+
notes.push(ceiling ? `gave up after ${describeDuration(ceiling)}` : "gave up at the ceiling");
|
|
13763
|
+
else if (typeof exit === "string")
|
|
13764
|
+
notes.push(`killed (${exit})`);
|
|
13765
|
+
else if (exit !== 0)
|
|
13766
|
+
notes.push(`exit ${exit}`);
|
|
13767
|
+
const parts = [];
|
|
13768
|
+
if (body.trim())
|
|
13769
|
+
parts.push(`\`\`\`
|
|
13770
|
+
${body}
|
|
13771
|
+
\`\`\``);
|
|
13772
|
+
parts.push(...notes);
|
|
13773
|
+
return parts.join(`
|
|
13774
|
+
`);
|
|
13775
|
+
}
|
|
13776
|
+
|
|
13728
13777
|
// ../daemon/src/agent/policy.ts
|
|
13729
13778
|
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
13730
13779
|
var NON_WRITE_KINDS = new Set(["execute", "read", "search", "fetch", "think"]);
|
|
@@ -14087,9 +14136,171 @@ async function cleanUp(ctx, a, sessionId) {
|
|
|
14087
14136
|
await ctx.request(methods.agent.session.delete, { sessionId }).catch(quietly("delete"));
|
|
14088
14137
|
}
|
|
14089
14138
|
|
|
14139
|
+
// ../daemon/src/agent/acp/background.ts
|
|
14140
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "fs";
|
|
14141
|
+
import { open as open2 } from "fs/promises";
|
|
14142
|
+
import { homedir as homedir4 } from "os";
|
|
14143
|
+
import { basename as basename2, dirname as dirname4, join as join7 } from "path";
|
|
14144
|
+
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
14145
|
+
function backgroundStart(input, output) {
|
|
14146
|
+
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
14147
|
+
if (flag !== true)
|
|
14148
|
+
return null;
|
|
14149
|
+
const m = STARTED.exec(output);
|
|
14150
|
+
return m ? { taskId: m[1], file: m[2] } : null;
|
|
14151
|
+
}
|
|
14152
|
+
function spawnDetached(output) {
|
|
14153
|
+
return output.includes("Async agent launched successfully");
|
|
14154
|
+
}
|
|
14155
|
+
function claudeConfigDir() {
|
|
14156
|
+
return process.env.CLAUDE_CONFIG_DIR || join7(homedir4(), ".claude");
|
|
14157
|
+
}
|
|
14158
|
+
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
14159
|
+
const tasks = dirname4(file);
|
|
14160
|
+
if (basename2(tasks) !== "tasks")
|
|
14161
|
+
return null;
|
|
14162
|
+
const session = dirname4(tasks);
|
|
14163
|
+
const path = join7(configDir, "projects", basename2(dirname4(session)), `${basename2(session)}.jsonl`);
|
|
14164
|
+
return existsSync8(path) ? path : null;
|
|
14165
|
+
}
|
|
14166
|
+
function taskEnd(text, toolId) {
|
|
14167
|
+
const at = text.indexOf(`<tool-use-id>${toolId}</tool-use-id>`);
|
|
14168
|
+
if (at === -1)
|
|
14169
|
+
return null;
|
|
14170
|
+
const tail = text.slice(at, at + 2000);
|
|
14171
|
+
const status = /<status>(\w+)<\/status>/.exec(tail)?.[1] ?? "completed";
|
|
14172
|
+
const exit = /exit code (\d+)/.exec(tail)?.[1];
|
|
14173
|
+
return { status, exit: exit === undefined ? null : Number(exit) };
|
|
14174
|
+
}
|
|
14175
|
+
var POLL_MS = 500;
|
|
14176
|
+
var DEFAULT_BASH_TIMEOUT_MS = 120000;
|
|
14177
|
+
var GRACE_MS = 60000;
|
|
14178
|
+
var READ_CHUNK = 1 << 20;
|
|
14179
|
+
var CARRY = 4000;
|
|
14180
|
+
|
|
14181
|
+
class BackgroundTasks {
|
|
14182
|
+
deps;
|
|
14183
|
+
watched = new Map;
|
|
14184
|
+
constructor(deps) {
|
|
14185
|
+
this.deps = deps;
|
|
14186
|
+
}
|
|
14187
|
+
get size() {
|
|
14188
|
+
return this.watched.size;
|
|
14189
|
+
}
|
|
14190
|
+
start(toolId, start, input) {
|
|
14191
|
+
const logPath = sessionLogFor(start.file, this.deps.configDir);
|
|
14192
|
+
if (!logPath)
|
|
14193
|
+
return false;
|
|
14194
|
+
const timeout = input?.timeout;
|
|
14195
|
+
const ceiling = (typeof timeout === "number" ? timeout : DEFAULT_BASH_TIMEOUT_MS) + (this.deps.graceMs ?? GRACE_MS);
|
|
14196
|
+
const w = {
|
|
14197
|
+
toolId,
|
|
14198
|
+
file: start.file,
|
|
14199
|
+
log: logPath,
|
|
14200
|
+
read: 0,
|
|
14201
|
+
logRead: statSync3(logPath).size,
|
|
14202
|
+
carry: "",
|
|
14203
|
+
decoder: new TextDecoder,
|
|
14204
|
+
text: "",
|
|
14205
|
+
truncated: false,
|
|
14206
|
+
ceiling: Date.now() + ceiling,
|
|
14207
|
+
timer: null
|
|
14208
|
+
};
|
|
14209
|
+
this.watched.set(toolId, w);
|
|
14210
|
+
this.schedule(w);
|
|
14211
|
+
return true;
|
|
14212
|
+
}
|
|
14213
|
+
endAll(reason) {
|
|
14214
|
+
for (const w of [...this.watched.values()]) {
|
|
14215
|
+
fireAndForget("background", this.finish(w, null, reason, true), `ending ${w.toolId}`);
|
|
14216
|
+
}
|
|
14217
|
+
}
|
|
14218
|
+
schedule(w) {
|
|
14219
|
+
w.timer = setTimeout(() => this.poll(w), this.deps.pollMs ?? POLL_MS);
|
|
14220
|
+
w.timer.unref?.();
|
|
14221
|
+
}
|
|
14222
|
+
async poll(w) {
|
|
14223
|
+
w.timer = null;
|
|
14224
|
+
try {
|
|
14225
|
+
await this.drain(w);
|
|
14226
|
+
const fresh = await readSince(w.log, w.logRead);
|
|
14227
|
+
w.logRead += fresh.length;
|
|
14228
|
+
const text = w.carry + fresh.toString("utf8");
|
|
14229
|
+
w.carry = text.slice(-CARRY);
|
|
14230
|
+
const end = fresh.length > 0 ? taskEnd(text, w.toolId) : null;
|
|
14231
|
+
if (end)
|
|
14232
|
+
return this.finish(w, end);
|
|
14233
|
+
if (Date.now() >= w.ceiling)
|
|
14234
|
+
return this.finish(w, null, "no word of its end; the row stops watching");
|
|
14235
|
+
} catch (e) {
|
|
14236
|
+
log.warn("background", `watching ${w.toolId} failed`, e);
|
|
14237
|
+
}
|
|
14238
|
+
if (this.watched.has(w.toolId))
|
|
14239
|
+
this.schedule(w);
|
|
14240
|
+
}
|
|
14241
|
+
async drain(w) {
|
|
14242
|
+
const fresh = await readSince(w.file, w.read);
|
|
14243
|
+
w.read += fresh.length;
|
|
14244
|
+
if (fresh.length === 0 || w.truncated)
|
|
14245
|
+
return;
|
|
14246
|
+
const chunk = w.decoder.decode(fresh, { stream: true });
|
|
14247
|
+
const take = chunk.slice(0, OUTPUT_CAP - w.text.length);
|
|
14248
|
+
w.text += take;
|
|
14249
|
+
if (take.length < chunk.length)
|
|
14250
|
+
w.truncated = true;
|
|
14251
|
+
if (take)
|
|
14252
|
+
this.deps.note({ type: "tool-delta", toolId: w.toolId, text: take });
|
|
14253
|
+
}
|
|
14254
|
+
async finish(w, end, note, killed = false) {
|
|
14255
|
+
if (!this.watched.delete(w.toolId))
|
|
14256
|
+
return;
|
|
14257
|
+
if (w.timer)
|
|
14258
|
+
clearTimeout(w.timer);
|
|
14259
|
+
try {
|
|
14260
|
+
await this.drain(w);
|
|
14261
|
+
} catch (e) {
|
|
14262
|
+
log.warn("background", `reading the last of ${w.toolId} failed`, e);
|
|
14263
|
+
}
|
|
14264
|
+
const exit = end ? end.exit ?? (end.status === "completed" ? 0 : null) : null;
|
|
14265
|
+
const lines = [formatOutput(w.text, exit ?? 0, w.truncated)];
|
|
14266
|
+
if (end && exit === null)
|
|
14267
|
+
lines.push(end.status);
|
|
14268
|
+
if (note)
|
|
14269
|
+
lines.push(note);
|
|
14270
|
+
this.deps.note({
|
|
14271
|
+
type: "tool-end",
|
|
14272
|
+
toolId: w.toolId,
|
|
14273
|
+
output: lines.filter(Boolean).join(`
|
|
14274
|
+
`),
|
|
14275
|
+
isError: killed || end !== null && (exit !== 0 || end.status !== "completed")
|
|
14276
|
+
});
|
|
14277
|
+
this.deps.onEnd?.();
|
|
14278
|
+
}
|
|
14279
|
+
}
|
|
14280
|
+
async function readSince(path, offset) {
|
|
14281
|
+
let fh;
|
|
14282
|
+
try {
|
|
14283
|
+
fh = await open2(path, "r");
|
|
14284
|
+
} catch (e) {
|
|
14285
|
+
if (e.code === "ENOENT")
|
|
14286
|
+
return Buffer.alloc(0);
|
|
14287
|
+
throw e;
|
|
14288
|
+
}
|
|
14289
|
+
try {
|
|
14290
|
+
const { size } = await fh.stat();
|
|
14291
|
+
if (size <= offset)
|
|
14292
|
+
return Buffer.alloc(0);
|
|
14293
|
+
const buf = Buffer.alloc(Math.min(size - offset, READ_CHUNK));
|
|
14294
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, offset);
|
|
14295
|
+
return buf.subarray(0, bytesRead);
|
|
14296
|
+
} finally {
|
|
14297
|
+
await fh.close();
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14300
|
+
|
|
14090
14301
|
// ../daemon/src/agent/acp/elicit.ts
|
|
14091
|
-
var CUSTOM_ANSWER_META = "_askUserQuestionCustomAnswer";
|
|
14092
14302
|
var CODEX_META = "codex";
|
|
14303
|
+
var CLAUDE_CUSTOM_KEY = /^(question_\d+)_custom$/;
|
|
14093
14304
|
var OPTION_PREVIEW_META = "_claude/askUserQuestionOption";
|
|
14094
14305
|
function field(value, key) {
|
|
14095
14306
|
return value && typeof value === "object" ? value[key] : undefined;
|
|
@@ -14100,12 +14311,12 @@ function meta2(value, key) {
|
|
|
14100
14311
|
function text(value) {
|
|
14101
14312
|
return typeof value === "string" && value ? value : undefined;
|
|
14102
14313
|
}
|
|
14103
|
-
function customAnswerFor(prop) {
|
|
14104
|
-
const claude = text(field(meta2(prop, CUSTOM_ANSWER_META), "questionId"));
|
|
14105
|
-
if (claude)
|
|
14106
|
-
return claude;
|
|
14314
|
+
function customAnswerFor(key, prop, keys) {
|
|
14107
14315
|
const codex = meta2(prop, CODEX_META);
|
|
14108
|
-
|
|
14316
|
+
if (field(codex, "role") === "user_note")
|
|
14317
|
+
return text(field(codex, "questionId")) ?? null;
|
|
14318
|
+
const owner = CLAUDE_CUSTOM_KEY.exec(key)?.[1];
|
|
14319
|
+
return owner && keys.has(owner) ? owner : null;
|
|
14109
14320
|
}
|
|
14110
14321
|
function optionsOf(titled) {
|
|
14111
14322
|
return titled.map((o) => {
|
|
@@ -14148,26 +14359,28 @@ function parseForm(req) {
|
|
|
14148
14359
|
if (!props)
|
|
14149
14360
|
return null;
|
|
14150
14361
|
const entries = Object.entries(props);
|
|
14362
|
+
const keys = new Set(Object.keys(props));
|
|
14151
14363
|
const required = new Set(req.requestedSchema.required ?? []);
|
|
14152
14364
|
const notes = new Map;
|
|
14153
14365
|
for (const [key, prop] of entries) {
|
|
14154
|
-
const owner = customAnswerFor(prop);
|
|
14366
|
+
const owner = customAnswerFor(key, prop, keys);
|
|
14155
14367
|
if (owner)
|
|
14156
14368
|
notes.set(owner, { key, label: text(field(prop, "title")) ?? "Other" });
|
|
14157
14369
|
}
|
|
14158
14370
|
const questions = [];
|
|
14159
14371
|
const fields = [];
|
|
14160
14372
|
for (const [key, prop] of entries) {
|
|
14161
|
-
if (customAnswerFor(prop))
|
|
14373
|
+
if (customAnswerFor(key, prop, keys))
|
|
14162
14374
|
continue;
|
|
14163
14375
|
const select = selectOf(prop);
|
|
14164
14376
|
if (!select)
|
|
14165
14377
|
return null;
|
|
14166
14378
|
const note = notes.get(key);
|
|
14167
|
-
const
|
|
14379
|
+
const swapped = meta2(prop, CODEX_META) !== undefined;
|
|
14380
|
+
const header = text(field(prop, swapped ? "description" : "title"));
|
|
14168
14381
|
questions.push({
|
|
14169
14382
|
id: key,
|
|
14170
|
-
text: text(field(prop, "description")) ?? "",
|
|
14383
|
+
text: text(field(prop, swapped ? "title" : "description")) ?? "",
|
|
14171
14384
|
...header ? { header } : {},
|
|
14172
14385
|
options: select.options,
|
|
14173
14386
|
...select.multi ? { multi: true } : {},
|
|
@@ -14193,8 +14406,7 @@ function toContent(form, answers) {
|
|
|
14193
14406
|
const note = answer.note?.trim();
|
|
14194
14407
|
if (!note || !field.note)
|
|
14195
14408
|
return;
|
|
14196
|
-
|
|
14197
|
-
out[field.note] = labels.length > 0 ? `${labels.join(", ")}: ${note}` : note;
|
|
14409
|
+
out[field.note] = note;
|
|
14198
14410
|
});
|
|
14199
14411
|
return out;
|
|
14200
14412
|
}
|
|
@@ -14261,7 +14473,8 @@ function spawnOf(meta) {
|
|
|
14261
14473
|
const claude = asRecord(asRecord(meta).claudeCode);
|
|
14262
14474
|
const parentToolId = typeof claude.parentToolUseId === "string" ? claude.parentToolUseId : undefined;
|
|
14263
14475
|
const codex = asRecord(asRecord(meta).codex).subagent;
|
|
14264
|
-
const
|
|
14476
|
+
const spawns = claude.toolName === "Agent" || claude.toolName === "Task";
|
|
14477
|
+
const subagent = spawns || claude.subagent === true || Object.keys(asRecord(codex)).length > 0;
|
|
14265
14478
|
return { ...parentToolId ? { parentToolId } : {}, ...subagent ? { subagent: true } : {} };
|
|
14266
14479
|
}
|
|
14267
14480
|
function asRecord(v) {
|
|
@@ -14271,7 +14484,6 @@ var NETWORK_ASK = "SandboxNetworkAccess";
|
|
|
14271
14484
|
function isNetworkAsk(update) {
|
|
14272
14485
|
return update.name === NETWORK_ASK || asRecord(asRecord(update._meta).claudeCode).toolName === NETWORK_ASK;
|
|
14273
14486
|
}
|
|
14274
|
-
var TOOL_SEARCH = "ToolSearch";
|
|
14275
14487
|
function isToolSearch(update) {
|
|
14276
14488
|
return update.name === TOOL_SEARCH || asRecord(asRecord(update._meta).claudeCode).toolName === TOOL_SEARCH;
|
|
14277
14489
|
}
|
|
@@ -14319,7 +14531,7 @@ function abandoned(memos, parent) {
|
|
|
14319
14531
|
}
|
|
14320
14532
|
return out;
|
|
14321
14533
|
}
|
|
14322
|
-
function mapUpdate(update, memos, tag, sink) {
|
|
14534
|
+
function mapUpdate(update, memos, tag, sink, background) {
|
|
14323
14535
|
switch (update.sessionUpdate) {
|
|
14324
14536
|
case "agent_message_chunk": {
|
|
14325
14537
|
const { parentToolId } = spawnOf(update._meta);
|
|
@@ -14347,11 +14559,13 @@ function mapUpdate(update, memos, tag, sink) {
|
|
|
14347
14559
|
const head = heading(update);
|
|
14348
14560
|
const memo = {
|
|
14349
14561
|
...head,
|
|
14562
|
+
input,
|
|
14350
14563
|
content: update.content ?? [],
|
|
14351
14564
|
rawOutput: update.rawOutput,
|
|
14352
14565
|
ended: false,
|
|
14353
14566
|
writing: isWrittenKind(head.kind) && emptyInput(input),
|
|
14354
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
|
|
14567
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
14568
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
14355
14569
|
};
|
|
14356
14570
|
memos.set(update.toolCallId, memo);
|
|
14357
14571
|
out.push({
|
|
@@ -14364,7 +14578,7 @@ function mapUpdate(update, memos, tag, sink) {
|
|
|
14364
14578
|
...spawn
|
|
14365
14579
|
});
|
|
14366
14580
|
if (update.status === "completed" || update.status === "failed")
|
|
14367
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink));
|
|
14581
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
14368
14582
|
return out;
|
|
14369
14583
|
}
|
|
14370
14584
|
case "tool_call_update": {
|
|
@@ -14377,10 +14591,12 @@ function mapUpdate(update, memos, tag, sink) {
|
|
|
14377
14591
|
name: update.name ?? "",
|
|
14378
14592
|
title: update.title ?? update.name ?? "tool",
|
|
14379
14593
|
...update.kind ? { kind: update.kind } : {},
|
|
14594
|
+
input,
|
|
14380
14595
|
content: [],
|
|
14381
14596
|
ended: false,
|
|
14382
14597
|
writing: isWrittenKind(update.kind ?? undefined) && emptyInput(input),
|
|
14383
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
|
|
14598
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
14599
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
14384
14600
|
};
|
|
14385
14601
|
memos.set(update.toolCallId, memo);
|
|
14386
14602
|
out.push({
|
|
@@ -14407,7 +14623,7 @@ function mapUpdate(update, memos, tag, sink) {
|
|
|
14407
14623
|
if (update.kind && update.kind !== memo.kind)
|
|
14408
14624
|
refined.kind = memo.kind = update.kind;
|
|
14409
14625
|
if (update.rawInput !== undefined) {
|
|
14410
|
-
refined.input = update.rawInput;
|
|
14626
|
+
refined.input = memo.input = update.rawInput;
|
|
14411
14627
|
if (!emptyInput(update.rawInput))
|
|
14412
14628
|
memo.writing = false;
|
|
14413
14629
|
}
|
|
@@ -14418,7 +14634,7 @@ function mapUpdate(update, memos, tag, sink) {
|
|
|
14418
14634
|
if (update.rawOutput !== undefined)
|
|
14419
14635
|
memo.rawOutput = update.rawOutput;
|
|
14420
14636
|
if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
|
|
14421
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink));
|
|
14637
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
14422
14638
|
}
|
|
14423
14639
|
return out;
|
|
14424
14640
|
}
|
|
@@ -14449,17 +14665,32 @@ function textOf(content, tag, what) {
|
|
|
14449
14665
|
log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
|
|
14450
14666
|
return null;
|
|
14451
14667
|
}
|
|
14452
|
-
function endOf(toolId, memo, status, sink) {
|
|
14668
|
+
function endOf(toolId, memo, status, sink, background) {
|
|
14453
14669
|
memo.ended = true;
|
|
14670
|
+
const output = summarizeToolOutput(memo.content, memo.rawOutput);
|
|
14671
|
+
if (status === "completed" && memo.kind === "execute" && background) {
|
|
14672
|
+
const start = backgroundStart(memo.input, output);
|
|
14673
|
+
if (start && background(toolId, start, memo.input))
|
|
14674
|
+
return { type: "tool-update", toolId, background: true };
|
|
14675
|
+
}
|
|
14454
14676
|
const images = sink ? toolImages(memo.content, sink) : [];
|
|
14677
|
+
const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
|
|
14455
14678
|
return {
|
|
14456
14679
|
type: "tool-end",
|
|
14457
14680
|
toolId,
|
|
14458
|
-
output
|
|
14681
|
+
output,
|
|
14459
14682
|
isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
|
|
14683
|
+
...detached ? { detached: true } : {},
|
|
14460
14684
|
...images.length ? { images } : {}
|
|
14461
14685
|
};
|
|
14462
14686
|
}
|
|
14687
|
+
function wholeText(content, rawOutput) {
|
|
14688
|
+
const parts = content.flatMap((c) => c.type === "content" && c.content.type === "text" ? [c.content.text] : []);
|
|
14689
|
+
if (typeof rawOutput === "string")
|
|
14690
|
+
parts.push(rawOutput);
|
|
14691
|
+
return parts.join(`
|
|
14692
|
+
`);
|
|
14693
|
+
}
|
|
14463
14694
|
function toolImages(content, sink) {
|
|
14464
14695
|
const out = [];
|
|
14465
14696
|
for (const c of content) {
|
|
@@ -14566,6 +14797,7 @@ class AcpSession {
|
|
|
14566
14797
|
live = null;
|
|
14567
14798
|
reaper = null;
|
|
14568
14799
|
own = null;
|
|
14800
|
+
tasks;
|
|
14569
14801
|
asking = 0;
|
|
14570
14802
|
asks = new Map;
|
|
14571
14803
|
log;
|
|
@@ -14575,6 +14807,12 @@ class AcpSession {
|
|
|
14575
14807
|
this.d = d;
|
|
14576
14808
|
this.commandList = d.seedCommands?.() ?? [];
|
|
14577
14809
|
this.log = new Transcript(transcriptPathFor(d.transcriptsDir, d.worktreeId), d.worktreeId);
|
|
14810
|
+
this.tasks = new BackgroundTasks({
|
|
14811
|
+
note: (e) => this.emit(e),
|
|
14812
|
+
onEnd: () => this.maybeArmReaper(),
|
|
14813
|
+
...d.taskPollMs !== undefined ? { pollMs: d.taskPollMs } : {},
|
|
14814
|
+
...d.claudeConfigDir !== undefined ? { configDir: d.claudeConfigDir } : {}
|
|
14815
|
+
});
|
|
14578
14816
|
this.seq = nextNumbers(this.log.entries.map(({ event }) => event.type === "user-message" ? event.attachments : undefined));
|
|
14579
14817
|
const open = new Set;
|
|
14580
14818
|
for (const { event } of this.log.entries) {
|
|
@@ -14585,6 +14823,20 @@ class AcpSession {
|
|
|
14585
14823
|
}
|
|
14586
14824
|
for (const id of open)
|
|
14587
14825
|
this.emit({ type: "agent-ask-end", id, outcome: "expired", ts: Date.now() });
|
|
14826
|
+
const held = new Map;
|
|
14827
|
+
for (const { event } of this.log.entries) {
|
|
14828
|
+
if (event.type === "tool-update" && event.background)
|
|
14829
|
+
held.set(event.toolId, "");
|
|
14830
|
+
else if (event.type === "tool-delta" && held.has(event.toolId))
|
|
14831
|
+
held.set(event.toolId, held.get(event.toolId) + event.text);
|
|
14832
|
+
else if (event.type === "tool-end")
|
|
14833
|
+
held.delete(event.toolId);
|
|
14834
|
+
}
|
|
14835
|
+
for (const [toolId, text] of held) {
|
|
14836
|
+
const output = [formatOutput(text, 0, false), "ended with the daemon that was watching it"].filter(Boolean);
|
|
14837
|
+
this.emit({ type: "tool-end", toolId, output: output.join(`
|
|
14838
|
+
`), isError: true });
|
|
14839
|
+
}
|
|
14588
14840
|
}
|
|
14589
14841
|
get commands() {
|
|
14590
14842
|
return this.commandList;
|
|
@@ -14983,6 +15235,7 @@ class AcpSession {
|
|
|
14983
15235
|
this.conn = null;
|
|
14984
15236
|
this.live = null;
|
|
14985
15237
|
this.cancelAsks();
|
|
15238
|
+
this.tasks.endAll("the agent stopped, and the command with it");
|
|
14986
15239
|
this.endOwn("interrupted");
|
|
14987
15240
|
}
|
|
14988
15241
|
});
|
|
@@ -15033,7 +15286,8 @@ class AcpSession {
|
|
|
15033
15286
|
sessionId,
|
|
15034
15287
|
cwd: this.d.cwd,
|
|
15035
15288
|
mcpServers: [],
|
|
15036
|
-
additionalDirectories
|
|
15289
|
+
additionalDirectories,
|
|
15290
|
+
...conn.spec.systemPrompt === "meta-append" ? { _meta: { systemPrompt: { append: SYSTEM_APPEND } } } : {}
|
|
15037
15291
|
});
|
|
15038
15292
|
modes = r.modes;
|
|
15039
15293
|
configOptions = r.configOptions;
|
|
@@ -15155,7 +15409,7 @@ class AcpSession {
|
|
|
15155
15409
|
}
|
|
15156
15410
|
if (params.update.sessionUpdate === "config_option_update")
|
|
15157
15411
|
this.absorb(live, params.update.configOptions);
|
|
15158
|
-
const events = mapUpdate(params.update, live.tools, this.d.worktreeId, (file, bytes) => fireAndForget(this.d.worktreeId, this.d.attachments.putToolImage(this.d.worktreeId, file, bytes), "tool image"));
|
|
15412
|
+
const events = mapUpdate(params.update, live.tools, this.d.worktreeId, (file, bytes) => fireAndForget(this.d.worktreeId, this.d.attachments.putToolImage(this.d.worktreeId, file, bytes), "tool image"), (toolId, start, input) => this.tasks.start(toolId, start, input));
|
|
15159
15413
|
if (!this.running)
|
|
15160
15414
|
this.trackOwn(events);
|
|
15161
15415
|
for (const ev of events) {
|
|
@@ -15296,7 +15550,7 @@ class AcpSession {
|
|
|
15296
15550
|
for (const e of events) {
|
|
15297
15551
|
if (e.type === "tool-start")
|
|
15298
15552
|
this.own.open.add(e.toolId);
|
|
15299
|
-
else if (e.type === "tool-end")
|
|
15553
|
+
else if (e.type === "tool-end" || e.type === "tool-update" && e.background)
|
|
15300
15554
|
this.own.open.delete(e.toolId);
|
|
15301
15555
|
}
|
|
15302
15556
|
if (this.own.settle)
|
|
@@ -15318,8 +15572,11 @@ class AcpSession {
|
|
|
15318
15572
|
this.syncStatus();
|
|
15319
15573
|
this.maybeArmReaper();
|
|
15320
15574
|
}
|
|
15575
|
+
quiet() {
|
|
15576
|
+
return !this.running && !this.own && !this.asking && this.asks.size === 0 && this.tasks.size === 0;
|
|
15577
|
+
}
|
|
15321
15578
|
maybeArmReaper() {
|
|
15322
|
-
if (
|
|
15579
|
+
if (this.quiet() && this.conn && !this.stopped)
|
|
15323
15580
|
this.armReaper();
|
|
15324
15581
|
}
|
|
15325
15582
|
armReaper() {
|
|
@@ -15327,7 +15584,7 @@ class AcpSession {
|
|
|
15327
15584
|
const idleMs = this.d.idleMs ?? DEFAULT_IDLE_MS;
|
|
15328
15585
|
const t = setTimeout(() => {
|
|
15329
15586
|
this.reaper = null;
|
|
15330
|
-
if (this.
|
|
15587
|
+
if (!this.quiet() || !this.conn)
|
|
15331
15588
|
return;
|
|
15332
15589
|
fireAndForget(this.d.worktreeId, this.dropConn(`idle for ${Math.round(idleMs / 60000)} min`), "reap agent");
|
|
15333
15590
|
}, idleMs);
|
|
@@ -15341,6 +15598,7 @@ class AcpSession {
|
|
|
15341
15598
|
}
|
|
15342
15599
|
async dropConn(why) {
|
|
15343
15600
|
this.cancelAsks();
|
|
15601
|
+
this.tasks.endAll("the agent stopped, and the command with it");
|
|
15344
15602
|
const conn = this.conn;
|
|
15345
15603
|
this.conn = null;
|
|
15346
15604
|
this.live = null;
|
|
@@ -15413,15 +15671,15 @@ function sameCommands(a, b) {
|
|
|
15413
15671
|
}
|
|
15414
15672
|
|
|
15415
15673
|
// ../daemon/src/agent/planDoc.ts
|
|
15416
|
-
import { existsSync as
|
|
15417
|
-
import { join as
|
|
15418
|
-
var PLANS_DIR =
|
|
15674
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
15675
|
+
import { join as join8 } from "path";
|
|
15676
|
+
var PLANS_DIR = join8(".toyon", "plans");
|
|
15419
15677
|
var PLAN_NAME = /^(\d+)\.md$/;
|
|
15420
15678
|
function isPlanPath(rel) {
|
|
15421
15679
|
return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
|
|
15422
15680
|
}
|
|
15423
15681
|
function nextNumber(dir) {
|
|
15424
|
-
if (!
|
|
15682
|
+
if (!existsSync9(dir))
|
|
15425
15683
|
return 1;
|
|
15426
15684
|
let max = 0;
|
|
15427
15685
|
for (const name of readdirSync2(dir)) {
|
|
@@ -15432,11 +15690,11 @@ function nextNumber(dir) {
|
|
|
15432
15690
|
return max + 1;
|
|
15433
15691
|
}
|
|
15434
15692
|
async function writePlanDoc(cwd, markdown) {
|
|
15435
|
-
const dir =
|
|
15436
|
-
const rel =
|
|
15693
|
+
const dir = join8(cwd, PLANS_DIR);
|
|
15694
|
+
const rel = join8(PLANS_DIR, `${nextNumber(dir)}.md`);
|
|
15437
15695
|
try {
|
|
15438
15696
|
mkdirSync5(dir, { recursive: true });
|
|
15439
|
-
writeFileSync4(
|
|
15697
|
+
writeFileSync4(join8(cwd, rel), markdown.endsWith(`
|
|
15440
15698
|
`) ? markdown : `${markdown}
|
|
15441
15699
|
`);
|
|
15442
15700
|
} catch (e) {
|
|
@@ -15447,8 +15705,8 @@ async function writePlanDoc(cwd, markdown) {
|
|
|
15447
15705
|
return rel;
|
|
15448
15706
|
}
|
|
15449
15707
|
function planEdited(cwd, rel, proposed) {
|
|
15450
|
-
const file =
|
|
15451
|
-
if (!
|
|
15708
|
+
const file = join8(cwd, rel);
|
|
15709
|
+
if (!existsSync9(file))
|
|
15452
15710
|
return false;
|
|
15453
15711
|
try {
|
|
15454
15712
|
return readFileSync8(file, "utf8").trim() !== proposed.trim();
|
|
@@ -15459,7 +15717,7 @@ function planEdited(cwd, rel, proposed) {
|
|
|
15459
15717
|
}
|
|
15460
15718
|
|
|
15461
15719
|
// ../daemon/src/runtime/forward.ts
|
|
15462
|
-
var
|
|
15720
|
+
var POLL_MS2 = 100;
|
|
15463
15721
|
var HOLD_MS = 60000;
|
|
15464
15722
|
var PENDING_CAP = 1 << 20;
|
|
15465
15723
|
function keep(chunk) {
|
|
@@ -15547,7 +15805,7 @@ function startForward(opts) {
|
|
|
15547
15805
|
return connectUp(conn, t);
|
|
15548
15806
|
if (!opts.coming() || Date.now() >= deadline)
|
|
15549
15807
|
return closePair(conn);
|
|
15550
|
-
await Bun.sleep(
|
|
15808
|
+
await Bun.sleep(POLL_MS2);
|
|
15551
15809
|
}
|
|
15552
15810
|
};
|
|
15553
15811
|
const listener = Bun.listen({
|
|
@@ -15906,7 +16164,7 @@ function expandEnv(env, vars) {
|
|
|
15906
16164
|
|
|
15907
16165
|
// ../daemon/src/core/remote.ts
|
|
15908
16166
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
15909
|
-
import { existsSync as
|
|
16167
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
15910
16168
|
function loadRemote(file, env = process.env, managed = MANAGED_DEFAULTS) {
|
|
15911
16169
|
if (env.TOYON_CLOUD === "1") {
|
|
15912
16170
|
const host = (env.TOYON_PUBLIC_HOST ?? "").toLowerCase();
|
|
@@ -15919,7 +16177,7 @@ function loadRemote(file, env = process.env, managed = MANAGED_DEFAULTS) {
|
|
|
15919
16177
|
throw new Error(`TOYON_PREVIEWS: ${why}`);
|
|
15920
16178
|
return { host, previews, front: "edge" };
|
|
15921
16179
|
}
|
|
15922
|
-
if (!
|
|
16180
|
+
if (!existsSync10(file))
|
|
15923
16181
|
return null;
|
|
15924
16182
|
if (managed.remote === "off") {
|
|
15925
16183
|
log.warn("remote", `${file} is ignored: remote access is turned off by your organization's policy`);
|
|
@@ -17526,8 +17784,8 @@ class RuntimeRegistry {
|
|
|
17526
17784
|
|
|
17527
17785
|
// ../daemon/src/worktrees/naming.ts
|
|
17528
17786
|
import { randomBytes } from "crypto";
|
|
17529
|
-
import { existsSync as
|
|
17530
|
-
import { join as
|
|
17787
|
+
import { existsSync as existsSync11 } from "fs";
|
|
17788
|
+
import { join as join9 } from "path";
|
|
17531
17789
|
function shortId() {
|
|
17532
17790
|
return randomBytes(5).toString("hex");
|
|
17533
17791
|
}
|
|
@@ -17535,7 +17793,7 @@ function freeSlot(parent) {
|
|
|
17535
17793
|
for (;; ) {
|
|
17536
17794
|
const id = shortId();
|
|
17537
17795
|
const dir = `wt-${id.slice(0, 4)}`;
|
|
17538
|
-
if (!
|
|
17796
|
+
if (!existsSync11(join9(parent, dir)))
|
|
17539
17797
|
return { id, dir };
|
|
17540
17798
|
}
|
|
17541
17799
|
}
|
|
@@ -17949,20 +18207,20 @@ function makePlanner(agents) {
|
|
|
17949
18207
|
}
|
|
17950
18208
|
|
|
17951
18209
|
// ../daemon/src/core/assets.ts
|
|
17952
|
-
import { existsSync as
|
|
17953
|
-
import { join as
|
|
18210
|
+
import { existsSync as existsSync12, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "fs";
|
|
18211
|
+
import { join as join10, resolve as resolve5 } from "path";
|
|
17954
18212
|
function locateAssets(here, env = process.env) {
|
|
17955
|
-
const packaged =
|
|
18213
|
+
const packaged = existsSync12(join10(here, "shell", "index.html"));
|
|
17956
18214
|
return {
|
|
17957
|
-
shellDist: env.TOYON_SHELL_DIST ?? (packaged ?
|
|
17958
|
-
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ?
|
|
18215
|
+
shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join10(here, "shell") : join10(here, "../../shell/dist")),
|
|
18216
|
+
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join10(here, "bridge.js") : join10(here, "../../bridge/dist/bridge.js")),
|
|
17959
18217
|
sourceRoot: packaged ? null : resolve5(here, "../../.."),
|
|
17960
|
-
packageJson: packaged ?
|
|
18218
|
+
packageJson: packaged ? join10(here, "..", "package.json") : null
|
|
17961
18219
|
};
|
|
17962
18220
|
}
|
|
17963
18221
|
var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
17964
18222
|
function pruneAssets(shellDist) {
|
|
17965
|
-
const dir =
|
|
18223
|
+
const dir = join10(shellDist, "assets");
|
|
17966
18224
|
let names;
|
|
17967
18225
|
try {
|
|
17968
18226
|
names = readdirSync3(dir);
|
|
@@ -17971,9 +18229,9 @@ function pruneAssets(shellDist) {
|
|
|
17971
18229
|
}
|
|
17972
18230
|
const found = [];
|
|
17973
18231
|
for (const name of names) {
|
|
17974
|
-
const path =
|
|
18232
|
+
const path = join10(dir, name);
|
|
17975
18233
|
try {
|
|
17976
|
-
found.push({ path, mtime:
|
|
18234
|
+
found.push({ path, mtime: statSync4(path).mtimeMs });
|
|
17977
18235
|
} catch {}
|
|
17978
18236
|
}
|
|
17979
18237
|
if (found.length === 0)
|
|
@@ -17992,8 +18250,8 @@ function pruneAssets(shellDist) {
|
|
|
17992
18250
|
}
|
|
17993
18251
|
|
|
17994
18252
|
// ../daemon/src/core/dialog.ts
|
|
17995
|
-
import { existsSync as
|
|
17996
|
-
import { homedir as
|
|
18253
|
+
import { existsSync as existsSync13 } from "fs";
|
|
18254
|
+
import { homedir as homedir5 } from "os";
|
|
17997
18255
|
var PROMPTS = {
|
|
17998
18256
|
location: "Choose where the new project goes",
|
|
17999
18257
|
open: "Choose a project folder to open"
|
|
@@ -18009,8 +18267,8 @@ function folderDialog() {
|
|
|
18009
18267
|
let child = null;
|
|
18010
18268
|
let cancelled = false;
|
|
18011
18269
|
const ask = async (start, purpose) => {
|
|
18012
|
-
const typed = start === "~" || start.startsWith("~/") ? `${
|
|
18013
|
-
const from = typed.startsWith("/") &&
|
|
18270
|
+
const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
|
|
18271
|
+
const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
|
|
18014
18272
|
cancelled = false;
|
|
18015
18273
|
const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
|
|
18016
18274
|
stdout: "pipe",
|
|
@@ -18476,12 +18734,23 @@ class IdleExit {
|
|
|
18476
18734
|
// ../daemon/src/core/restart.ts
|
|
18477
18735
|
import { spawn as spawn3 } from "child_process";
|
|
18478
18736
|
import { openSync } from "fs";
|
|
18737
|
+
import { homedir as homedir6 } from "os";
|
|
18479
18738
|
function restartable(env = process.env) {
|
|
18480
18739
|
if (env.TOYON_WORKTREE !== undefined) {
|
|
18481
18740
|
return { ok: false, reason: "this daemon runs as a toyon process; restart it from its terminal tab" };
|
|
18482
18741
|
}
|
|
18483
18742
|
return { ok: true };
|
|
18484
18743
|
}
|
|
18744
|
+
async function replacementRuns() {
|
|
18745
|
+
const r = await run(process.execPath, ["--version"], homedir6());
|
|
18746
|
+
if (r.ok && /^\d+\.\d+\.\d+/.test(r.out))
|
|
18747
|
+
return { ok: true };
|
|
18748
|
+
const why = r.err !== "" ? r.err : `exit ${r.exit}`;
|
|
18749
|
+
return {
|
|
18750
|
+
ok: false,
|
|
18751
|
+
reason: `the bun Toyon runs on did not start (${process.execPath}: ${why}); an install may still be writing it`
|
|
18752
|
+
};
|
|
18753
|
+
}
|
|
18485
18754
|
function respawn(logFile) {
|
|
18486
18755
|
const logFd = openSync(logFile, "a");
|
|
18487
18756
|
const child = spawn3(process.execPath, process.argv.slice(1), {
|
|
@@ -18508,8 +18777,8 @@ class Restarter {
|
|
|
18508
18777
|
d.hub.on("agentStatus", settle);
|
|
18509
18778
|
d.hub.on("worktreesChanged", settle);
|
|
18510
18779
|
}
|
|
18511
|
-
request(opts = {}) {
|
|
18512
|
-
const refused = this.d.refusal();
|
|
18780
|
+
async request(opts = {}) {
|
|
18781
|
+
const refused = await this.d.refusal();
|
|
18513
18782
|
if (refused)
|
|
18514
18783
|
return refused;
|
|
18515
18784
|
this.asked = true;
|
|
@@ -18626,14 +18895,14 @@ class SelfWatch {
|
|
|
18626
18895
|
|
|
18627
18896
|
// ../daemon/src/core/state.ts
|
|
18628
18897
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
18629
|
-
import { existsSync as
|
|
18898
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
18630
18899
|
var GROUPS_SAVE_MS = 250;
|
|
18631
18900
|
var SEEN_WORKTREES = 50;
|
|
18632
18901
|
var empty = { repos: [], worktrees: [], sessions: {} };
|
|
18633
18902
|
function loadState(paths) {
|
|
18634
18903
|
ensureDirs(paths);
|
|
18635
18904
|
const STATE_FILE = paths.stateFile;
|
|
18636
|
-
if (!
|
|
18905
|
+
if (!existsSync14(STATE_FILE))
|
|
18637
18906
|
return structuredClone(empty);
|
|
18638
18907
|
let raw;
|
|
18639
18908
|
try {
|
|
@@ -18685,7 +18954,7 @@ function saveState(paths, state) {
|
|
|
18685
18954
|
function loadOrCreateToken(paths) {
|
|
18686
18955
|
ensureDirs(paths);
|
|
18687
18956
|
const TOKEN_FILE = paths.tokenFile;
|
|
18688
|
-
if (
|
|
18957
|
+
if (existsSync14(TOKEN_FILE))
|
|
18689
18958
|
return readFileSync10(TOKEN_FILE, "utf8").trim();
|
|
18690
18959
|
const token = randomBytes2(32).toString("hex");
|
|
18691
18960
|
writeFileSync5(TOKEN_FILE, token, { mode: 384 });
|
|
@@ -18901,7 +19170,7 @@ class StateStore {
|
|
|
18901
19170
|
}
|
|
18902
19171
|
|
|
18903
19172
|
// ../daemon/src/design/service.ts
|
|
18904
|
-
import { join as
|
|
19173
|
+
import { join as join11 } from "path";
|
|
18905
19174
|
|
|
18906
19175
|
// ../daemon/src/design/scan.ts
|
|
18907
19176
|
var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
|
|
@@ -19351,7 +19620,7 @@ class DesignService {
|
|
|
19351
19620
|
async function readAll(root, paths) {
|
|
19352
19621
|
const out = [];
|
|
19353
19622
|
for (const path of paths) {
|
|
19354
|
-
const file = Bun.file(
|
|
19623
|
+
const file = Bun.file(join11(root, path));
|
|
19355
19624
|
if (file.size === 0 || file.size > MAX_FILE_BYTES)
|
|
19356
19625
|
continue;
|
|
19357
19626
|
try {
|
|
@@ -19466,14 +19735,14 @@ function typicalUse(classes) {
|
|
|
19466
19735
|
var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
|
|
19467
19736
|
async function loadProjectTypescript(root) {
|
|
19468
19737
|
try {
|
|
19469
|
-
return await import(
|
|
19738
|
+
return await import(join11(root, "node_modules", "typescript", "lib", "typescript.js"));
|
|
19470
19739
|
} catch {
|
|
19471
19740
|
return null;
|
|
19472
19741
|
}
|
|
19473
19742
|
}
|
|
19474
19743
|
|
|
19475
19744
|
// ../daemon/src/drafts/store.ts
|
|
19476
|
-
import { existsSync as
|
|
19745
|
+
import { existsSync as existsSync15, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
19477
19746
|
var SAVE_DELAY_MS = 1000;
|
|
19478
19747
|
|
|
19479
19748
|
class DraftStore {
|
|
@@ -19482,7 +19751,7 @@ class DraftStore {
|
|
|
19482
19751
|
saveTimer = null;
|
|
19483
19752
|
constructor(d) {
|
|
19484
19753
|
this.d = d;
|
|
19485
|
-
if (!
|
|
19754
|
+
if (!existsSync15(d.file))
|
|
19486
19755
|
return;
|
|
19487
19756
|
try {
|
|
19488
19757
|
const raw = JSON.parse(readFileSync11(d.file, "utf8"));
|
|
@@ -19557,7 +19826,6 @@ class DraftStore {
|
|
|
19557
19826
|
}
|
|
19558
19827
|
|
|
19559
19828
|
// ../daemon/src/exec/service.ts
|
|
19560
|
-
var OUTPUT_CAP = 200000;
|
|
19561
19829
|
var TIMEOUT_MS = 10 * 60000;
|
|
19562
19830
|
var DRAIN_GRACE_MS = 500;
|
|
19563
19831
|
var BACKGROUND_POLL_MS = 250;
|
|
@@ -19780,42 +20048,22 @@ class ExecService {
|
|
|
19780
20048
|
await Bun.sleep(BACKGROUND_POLL_MS);
|
|
19781
20049
|
}
|
|
19782
20050
|
}
|
|
19783
|
-
function formatOutput(text, exit, truncated, ceiling) {
|
|
19784
|
-
const body = text.replace(/\n+$/, "");
|
|
19785
|
-
const notes = [];
|
|
19786
|
-
if (truncated)
|
|
19787
|
-
notes.push(`output cut at ${Math.round(OUTPUT_CAP / 1000)} KB`);
|
|
19788
|
-
if (exit === "timeout")
|
|
19789
|
-
notes.push(ceiling ? `gave up after ${describeDuration(ceiling)}` : "gave up at the ceiling");
|
|
19790
|
-
else if (typeof exit === "string")
|
|
19791
|
-
notes.push(`killed (${exit})`);
|
|
19792
|
-
else if (exit !== 0)
|
|
19793
|
-
notes.push(`exit ${exit}`);
|
|
19794
|
-
const parts = [];
|
|
19795
|
-
if (body.trim())
|
|
19796
|
-
parts.push(`\`\`\`
|
|
19797
|
-
${body}
|
|
19798
|
-
\`\`\``);
|
|
19799
|
-
parts.push(...notes);
|
|
19800
|
-
return parts.join(`
|
|
19801
|
-
`);
|
|
19802
|
-
}
|
|
19803
20051
|
|
|
19804
20052
|
// ../daemon/src/files/open.ts
|
|
19805
20053
|
import { stat as stat2 } from "fs/promises";
|
|
19806
|
-
import { basename as
|
|
20054
|
+
import { basename as basename3, resolve as resolve7 } from "path";
|
|
19807
20055
|
|
|
19808
20056
|
// ../daemon/src/repos/browse.ts
|
|
19809
|
-
import { existsSync as
|
|
20057
|
+
import { existsSync as existsSync16, statSync as statSync5 } from "fs";
|
|
19810
20058
|
import { readdir } from "fs/promises";
|
|
19811
|
-
import { homedir as
|
|
19812
|
-
import { dirname as
|
|
20059
|
+
import { homedir as homedir7 } from "os";
|
|
20060
|
+
import { dirname as dirname5, join as join12 } from "path";
|
|
19813
20061
|
var MAX_ENTRIES = 40;
|
|
19814
20062
|
function expandTilde(raw) {
|
|
19815
|
-
return raw === "~" || raw.startsWith("~/") ?
|
|
20063
|
+
return raw === "~" || raw.startsWith("~/") ? join12(homedir7(), raw.slice(1)) : raw;
|
|
19816
20064
|
}
|
|
19817
20065
|
function collapseTilde(abs) {
|
|
19818
|
-
const home =
|
|
20066
|
+
const home = homedir7();
|
|
19819
20067
|
return abs === home || abs.startsWith(`${home}/`) ? `~${abs.slice(home.length)}` : abs;
|
|
19820
20068
|
}
|
|
19821
20069
|
function holdsNothing(names) {
|
|
@@ -19823,7 +20071,7 @@ function holdsNothing(names) {
|
|
|
19823
20071
|
}
|
|
19824
20072
|
async function describeFolder(abs) {
|
|
19825
20073
|
const path = collapseTilde(abs);
|
|
19826
|
-
if (
|
|
20074
|
+
if (existsSync16(join12(abs, ".git")))
|
|
19827
20075
|
return { path, kind: "project" };
|
|
19828
20076
|
try {
|
|
19829
20077
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -19835,13 +20083,13 @@ var NOWHERE = { exists: false, isDir: false, isRepo: false, parentExists: false
|
|
|
19835
20083
|
function describe2(typed, parent) {
|
|
19836
20084
|
let isDir = false;
|
|
19837
20085
|
try {
|
|
19838
|
-
isDir =
|
|
20086
|
+
isDir = statSync5(typed).isDirectory();
|
|
19839
20087
|
} catch {}
|
|
19840
20088
|
return {
|
|
19841
|
-
exists:
|
|
20089
|
+
exists: existsSync16(typed),
|
|
19842
20090
|
isDir,
|
|
19843
|
-
isRepo: isDir &&
|
|
19844
|
-
parentExists:
|
|
20091
|
+
isRepo: isDir && existsSync16(join12(typed, ".git")),
|
|
20092
|
+
parentExists: existsSync16(parent)
|
|
19845
20093
|
};
|
|
19846
20094
|
}
|
|
19847
20095
|
async function browsePath(raw) {
|
|
@@ -19849,7 +20097,7 @@ async function browsePath(raw) {
|
|
|
19849
20097
|
if (!typed.startsWith("/"))
|
|
19850
20098
|
return { entries: [], target: NOWHERE };
|
|
19851
20099
|
const listing = typed.endsWith("/");
|
|
19852
|
-
const dir = listing ? typed :
|
|
20100
|
+
const dir = listing ? typed : dirname5(typed);
|
|
19853
20101
|
const prefix = listing ? "" : typed.slice(dir.length).replace(/^\//, "").toLowerCase();
|
|
19854
20102
|
const target = describe2(listing ? typed.replace(/\/+$/, "") : typed, dir);
|
|
19855
20103
|
let names;
|
|
@@ -19865,8 +20113,8 @@ async function browsePath(raw) {
|
|
|
19865
20113
|
continue;
|
|
19866
20114
|
if (!name.toLowerCase().startsWith(prefix))
|
|
19867
20115
|
continue;
|
|
19868
|
-
const abs =
|
|
19869
|
-
entries.push({ path: collapseTilde(abs), name, isRepo:
|
|
20116
|
+
const abs = join12(dir, name);
|
|
20117
|
+
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(join12(abs, ".git")) });
|
|
19870
20118
|
}
|
|
19871
20119
|
entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
|
|
19872
20120
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
@@ -19977,7 +20225,7 @@ function withRepoLock(repoPath, fn) {
|
|
|
19977
20225
|
var fileLockKey = (absPath) => `file:${absPath}`;
|
|
19978
20226
|
|
|
19979
20227
|
// ../daemon/src/git/status.ts
|
|
19980
|
-
import { join as
|
|
20228
|
+
import { join as join13 } from "path";
|
|
19981
20229
|
async function treeFingerprint(worktreePath) {
|
|
19982
20230
|
const [head, diff, untracked] = await Promise.all([
|
|
19983
20231
|
git(worktreePath, "rev-parse", "HEAD"),
|
|
@@ -19987,7 +20235,7 @@ async function treeFingerprint(worktreePath) {
|
|
|
19987
20235
|
const others = await Promise.all(untracked.out.split(`
|
|
19988
20236
|
`).filter(Boolean).map(async (p) => {
|
|
19989
20237
|
try {
|
|
19990
|
-
const f = Bun.file(
|
|
20238
|
+
const f = Bun.file(join13(worktreePath, p));
|
|
19991
20239
|
const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
|
|
19992
20240
|
return `${p}:${body}`;
|
|
19993
20241
|
} catch {
|
|
@@ -20038,7 +20286,7 @@ async function numstat(worktreePath, ...range) {
|
|
|
20038
20286
|
}
|
|
20039
20287
|
async function untrackedLines(worktreePath, file) {
|
|
20040
20288
|
try {
|
|
20041
|
-
const f = Bun.file(
|
|
20289
|
+
const f = Bun.file(join13(worktreePath, file));
|
|
20042
20290
|
if (f.size > 2000000)
|
|
20043
20291
|
return {};
|
|
20044
20292
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
@@ -20662,7 +20910,7 @@ class OpenService {
|
|
|
20662
20910
|
return hit ? { kind: "file", worktreeId: hit.id, path: path.slice(hit.root.length + 1) } : null;
|
|
20663
20911
|
}
|
|
20664
20912
|
async grant(path, size, mtimeMs) {
|
|
20665
|
-
const name =
|
|
20913
|
+
const name = basename3(path);
|
|
20666
20914
|
if (this.d.refused.some((r) => within2(path, canonical(resolve7(r))))) {
|
|
20667
20915
|
throw new UserError(`${name} is inside Toyon's own folder, which is not for editing`);
|
|
20668
20916
|
}
|
|
@@ -20867,17 +21115,28 @@ class AfterLand {
|
|
|
20867
21115
|
return;
|
|
20868
21116
|
}
|
|
20869
21117
|
this.running.add(repoId);
|
|
20870
|
-
|
|
20871
|
-
this.
|
|
20872
|
-
|
|
20873
|
-
this.run(repoId);
|
|
20874
|
-
});
|
|
21118
|
+
if (this.d.self.building(true))
|
|
21119
|
+
this.d.hub.emit("selfChanged");
|
|
21120
|
+
const done = this.runs(repo, commands).finally(() => this.running.delete(repoId));
|
|
20875
21121
|
fireAndForget(repo.id, done, "afterLand");
|
|
20876
21122
|
}
|
|
20877
|
-
async
|
|
20878
|
-
|
|
20879
|
-
|
|
21123
|
+
async runs(repo, commands) {
|
|
21124
|
+
let failure;
|
|
21125
|
+
let notice;
|
|
21126
|
+
do {
|
|
21127
|
+
await this.d.settled?.();
|
|
21128
|
+
({ failure, notice } = await this.exec(repo, commands));
|
|
21129
|
+
} while (this.again.delete(repo.id));
|
|
21130
|
+
const onNotice = this.d.self.reports(repo);
|
|
21131
|
+
if (this.d.self.building(false, failure))
|
|
20880
21132
|
this.d.hub.emit("selfChanged");
|
|
21133
|
+
if (notice !== undefined && !onNotice) {
|
|
21134
|
+
const main = this.mainOf(repo);
|
|
21135
|
+
if (main)
|
|
21136
|
+
this.d.hub.emit("failed", main.id, notice);
|
|
21137
|
+
}
|
|
21138
|
+
}
|
|
21139
|
+
async exec(repo, commands) {
|
|
20881
21140
|
let failure;
|
|
20882
21141
|
let notice;
|
|
20883
21142
|
for (const command of commands) {
|
|
@@ -20897,14 +21156,7 @@ class AfterLand {
|
|
|
20897
21156
|
log.warn(repo.id, `afterLand stopped: ${command} exited ${code}`);
|
|
20898
21157
|
break;
|
|
20899
21158
|
}
|
|
20900
|
-
|
|
20901
|
-
if (this.d.self.building(false, failure))
|
|
20902
|
-
this.d.hub.emit("selfChanged");
|
|
20903
|
-
if (notice !== undefined && !onNotice) {
|
|
20904
|
-
const main = this.mainOf(repo);
|
|
20905
|
-
if (main)
|
|
20906
|
-
this.d.hub.emit("failed", main.id, notice);
|
|
20907
|
-
}
|
|
21159
|
+
return { failure, notice };
|
|
20908
21160
|
}
|
|
20909
21161
|
tellMain(repo, line, retract) {
|
|
20910
21162
|
const main = this.mainOf(repo);
|
|
@@ -20917,9 +21169,9 @@ class AfterLand {
|
|
|
20917
21169
|
}
|
|
20918
21170
|
|
|
20919
21171
|
// ../daemon/src/repos/registry.ts
|
|
20920
|
-
import { existsSync as
|
|
20921
|
-
import { homedir as
|
|
20922
|
-
import { basename as
|
|
21172
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
21173
|
+
import { homedir as homedir9 } from "os";
|
|
21174
|
+
import { basename as basename5, dirname as dirname7, join as join17 } from "path";
|
|
20923
21175
|
|
|
20924
21176
|
// ../daemon/src/git/base.ts
|
|
20925
21177
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -20930,8 +21182,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
20930
21182
|
}
|
|
20931
21183
|
|
|
20932
21184
|
// ../daemon/src/repos/config.ts
|
|
20933
|
-
import { closeSync, existsSync as
|
|
20934
|
-
import { join as
|
|
21185
|
+
import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync12, readSync, statSync as statSync6 } from "fs";
|
|
21186
|
+
import { join as join14 } from "path";
|
|
20935
21187
|
|
|
20936
21188
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
20937
21189
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -22295,7 +22547,7 @@ function applyEdits(text, edits) {
|
|
|
22295
22547
|
|
|
22296
22548
|
// ../daemon/src/repos/config.ts
|
|
22297
22549
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
22298
|
-
var present = (repoPath, place) => [place.shared, place.local].filter((rel) =>
|
|
22550
|
+
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(join14(repoPath, rel)));
|
|
22299
22551
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
22300
22552
|
function parseSettings(text) {
|
|
22301
22553
|
const errors = [];
|
|
@@ -22320,7 +22572,7 @@ function readConfigFile(repoPath) {
|
|
|
22320
22572
|
for (const rel of files) {
|
|
22321
22573
|
let raw;
|
|
22322
22574
|
try {
|
|
22323
|
-
raw = parseSettings(readFileSync12(
|
|
22575
|
+
raw = parseSettings(readFileSync12(join14(repoPath, rel), "utf8"));
|
|
22324
22576
|
} catch (e) {
|
|
22325
22577
|
return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
22326
22578
|
}
|
|
@@ -22379,8 +22631,8 @@ function configTarget(repoPath, made) {
|
|
|
22379
22631
|
}
|
|
22380
22632
|
function configBody(repoPath, rel, config) {
|
|
22381
22633
|
const place = PLACES.find((p) => p.local === rel);
|
|
22382
|
-
const abs = place &&
|
|
22383
|
-
if (!abs || !
|
|
22634
|
+
const abs = place && join14(repoPath, place.shared);
|
|
22635
|
+
if (!abs || !existsSync17(abs))
|
|
22384
22636
|
return config;
|
|
22385
22637
|
let shared;
|
|
22386
22638
|
try {
|
|
@@ -22397,7 +22649,7 @@ function configText(repoPath, rel, config) {
|
|
|
22397
22649
|
let text;
|
|
22398
22650
|
let was;
|
|
22399
22651
|
try {
|
|
22400
|
-
text = readFileSync12(
|
|
22652
|
+
text = readFileSync12(join14(repoPath, rel), "utf8");
|
|
22401
22653
|
was = parseSettings(text);
|
|
22402
22654
|
} catch {
|
|
22403
22655
|
return fresh;
|
|
@@ -22427,8 +22679,8 @@ function detectConfig(repoPath) {
|
|
|
22427
22679
|
return { config: explicit.config, needsSetup: false };
|
|
22428
22680
|
if (explicit)
|
|
22429
22681
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
22430
|
-
const pkgPath =
|
|
22431
|
-
if (
|
|
22682
|
+
const pkgPath = join14(repoPath, "package.json");
|
|
22683
|
+
if (existsSync17(pkgPath)) {
|
|
22432
22684
|
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
22433
22685
|
const scripts = pkg.scripts ?? {};
|
|
22434
22686
|
const runner = detectRunner(repoPath, pkg.packageManager);
|
|
@@ -22450,7 +22702,7 @@ function detectConfig(repoPath) {
|
|
|
22450
22702
|
};
|
|
22451
22703
|
}
|
|
22452
22704
|
}
|
|
22453
|
-
if (
|
|
22705
|
+
if (existsSync17(join14(repoPath, "start.sh"))) {
|
|
22454
22706
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
22455
22707
|
}
|
|
22456
22708
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -22493,7 +22745,7 @@ function assumeNothingToRun(repoPath) {
|
|
|
22493
22745
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
22494
22746
|
}
|
|
22495
22747
|
function markerIn(repoPath) {
|
|
22496
|
-
const named = NO_PAGE_MARKERS.find((f) =>
|
|
22748
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync17(join14(repoPath, f)));
|
|
22497
22749
|
if (named)
|
|
22498
22750
|
return named;
|
|
22499
22751
|
let entries;
|
|
@@ -22505,12 +22757,12 @@ function markerIn(repoPath) {
|
|
|
22505
22757
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
22506
22758
|
}
|
|
22507
22759
|
function servesPage(repoPath) {
|
|
22508
|
-
if (WEB_FILES.some((f) =>
|
|
22760
|
+
if (WEB_FILES.some((f) => existsSync17(join14(repoPath, f))))
|
|
22509
22761
|
return true;
|
|
22510
|
-
if (/^web:/m.test(head(
|
|
22762
|
+
if (/^web:/m.test(head(join14(repoPath, "Procfile"))))
|
|
22511
22763
|
return true;
|
|
22512
22764
|
for (const [files, names] of WEB_DEPENDENCIES) {
|
|
22513
|
-
const text = files.map((f) => head(
|
|
22765
|
+
const text = files.map((f) => head(join14(repoPath, f))).join(`
|
|
22514
22766
|
`);
|
|
22515
22767
|
if (text.trim() && names.some((n) => mentions(text, n)))
|
|
22516
22768
|
return true;
|
|
@@ -22527,7 +22779,7 @@ function frontEndBeside(repoPath) {
|
|
|
22527
22779
|
return dirs.some((d) => {
|
|
22528
22780
|
if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
|
|
22529
22781
|
return false;
|
|
22530
|
-
const pkg = head(
|
|
22782
|
+
const pkg = head(join14(repoPath, d.name, "package.json"));
|
|
22531
22783
|
if (!pkg)
|
|
22532
22784
|
return false;
|
|
22533
22785
|
try {
|
|
@@ -22542,7 +22794,7 @@ function mentions(text, name) {
|
|
|
22542
22794
|
return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i").test(text);
|
|
22543
22795
|
}
|
|
22544
22796
|
function head(path) {
|
|
22545
|
-
if (!
|
|
22797
|
+
if (!existsSync17(path))
|
|
22546
22798
|
return "";
|
|
22547
22799
|
try {
|
|
22548
22800
|
const fd = openSync2(path, "r");
|
|
@@ -22583,11 +22835,11 @@ function isLocalDatabase(url) {
|
|
|
22583
22835
|
}
|
|
22584
22836
|
function detectServices(repoPath) {
|
|
22585
22837
|
const out = {};
|
|
22586
|
-
const compose = COMPOSE_FILES.find((f) =>
|
|
22838
|
+
const compose = COMPOSE_FILES.find((f) => existsSync17(join14(repoPath, f)));
|
|
22587
22839
|
if (compose)
|
|
22588
22840
|
out.compose = compose;
|
|
22589
22841
|
for (const file of ENV_FILES) {
|
|
22590
|
-
const text = head(
|
|
22842
|
+
const text = head(join14(repoPath, file));
|
|
22591
22843
|
if (!text)
|
|
22592
22844
|
continue;
|
|
22593
22845
|
for (const name of DB_URL_NAMES) {
|
|
@@ -22611,7 +22863,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22611
22863
|
return;
|
|
22612
22864
|
let dir = false;
|
|
22613
22865
|
try {
|
|
22614
|
-
dir =
|
|
22866
|
+
dir = statSync6(join14(repoPath, clean)).isDirectory();
|
|
22615
22867
|
} catch {
|
|
22616
22868
|
return;
|
|
22617
22869
|
}
|
|
@@ -22622,25 +22874,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
22622
22874
|
const t = tokens[i];
|
|
22623
22875
|
if (t === "cd") {
|
|
22624
22876
|
const dir = tokens[i + 1];
|
|
22625
|
-
if (dir && !dir.startsWith("-") &&
|
|
22877
|
+
if (dir && !dir.startsWith("-") && existsSync17(join14(repoPath, dir)))
|
|
22626
22878
|
add(dir);
|
|
22627
22879
|
continue;
|
|
22628
22880
|
}
|
|
22629
22881
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
22630
22882
|
continue;
|
|
22631
22883
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
22632
|
-
if (mod?.[1] &&
|
|
22884
|
+
if (mod?.[1] && existsSync17(join14(repoPath, mod[1]))) {
|
|
22633
22885
|
add(mod[1]);
|
|
22634
22886
|
continue;
|
|
22635
22887
|
}
|
|
22636
22888
|
const rel = t.replace(/^\.\//, "");
|
|
22637
22889
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
22638
22890
|
continue;
|
|
22639
|
-
if (!
|
|
22891
|
+
if (!existsSync17(join14(repoPath, rel)))
|
|
22640
22892
|
continue;
|
|
22641
22893
|
add(rel);
|
|
22642
22894
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
22643
|
-
for (const p of inferPaths(head(
|
|
22895
|
+
for (const p of inferPaths(head(join14(repoPath, rel)), repoPath, depth - 1))
|
|
22644
22896
|
out.add(p);
|
|
22645
22897
|
}
|
|
22646
22898
|
}
|
|
@@ -22663,9 +22915,9 @@ function procCommand(runner, script, body) {
|
|
|
22663
22915
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
22664
22916
|
}
|
|
22665
22917
|
function detectPages(repoPath, scripts, runner) {
|
|
22666
|
-
if (!scripts.build || !
|
|
22918
|
+
if (!scripts.build || !existsSync17(join14(repoPath, "functions")))
|
|
22667
22919
|
return null;
|
|
22668
|
-
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) =>
|
|
22920
|
+
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join14(repoPath, f)).find((f) => existsSync17(f));
|
|
22669
22921
|
if (!cfg)
|
|
22670
22922
|
return null;
|
|
22671
22923
|
const out = readFileSync12(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
@@ -22674,68 +22926,68 @@ function detectPages(repoPath, scripts, runner) {
|
|
|
22674
22926
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
22675
22927
|
}
|
|
22676
22928
|
function detectRunner(repoPath, packageManager) {
|
|
22677
|
-
if (
|
|
22929
|
+
if (existsSync17(join14(repoPath, "bun.lock")) || existsSync17(join14(repoPath, "bun.lockb")))
|
|
22678
22930
|
return "bun";
|
|
22679
|
-
if (
|
|
22931
|
+
if (existsSync17(join14(repoPath, "pnpm-lock.yaml")))
|
|
22680
22932
|
return "pnpm";
|
|
22681
|
-
if (
|
|
22933
|
+
if (existsSync17(join14(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
22682
22934
|
return "yarn";
|
|
22683
22935
|
return "npm";
|
|
22684
22936
|
}
|
|
22685
22937
|
|
|
22686
22938
|
// ../daemon/src/repos/create.ts
|
|
22687
|
-
import { existsSync as
|
|
22939
|
+
import { existsSync as existsSync18, readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
22688
22940
|
import { appendFile as appendFile2, mkdir as mkdir2, rm } from "fs/promises";
|
|
22689
|
-
import { homedir as
|
|
22690
|
-
import { dirname as
|
|
22941
|
+
import { homedir as homedir8 } from "os";
|
|
22942
|
+
import { dirname as dirname6, join as join15, resolve as resolve8 } from "path";
|
|
22691
22943
|
function isInside(dir, parent) {
|
|
22692
22944
|
const p = parent.replace(/\/+$/, "");
|
|
22693
22945
|
return dir === p || dir.startsWith(`${p}/`);
|
|
22694
22946
|
}
|
|
22695
|
-
function planProject(opts, home =
|
|
22947
|
+
function planProject(opts, home = homedir8()) {
|
|
22696
22948
|
const nameError = projectNameError(opts.name);
|
|
22697
22949
|
if (nameError)
|
|
22698
22950
|
throw new UserError(nameError);
|
|
22699
22951
|
const first = firstProjectsFolder(opts.parent, home);
|
|
22700
22952
|
const parent = first ?? resolveParent(opts.parent);
|
|
22701
|
-
const dir =
|
|
22702
|
-
if (
|
|
22953
|
+
const dir = join15(parent, opts.name.trim());
|
|
22954
|
+
if (existsSync18(dir))
|
|
22703
22955
|
throw new UserError(`${dir} already exists`);
|
|
22704
22956
|
return { parent, dir, makeParent: first !== null };
|
|
22705
22957
|
}
|
|
22706
22958
|
function firstProjectsFolder(raw, home) {
|
|
22707
22959
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
22708
|
-
if (typed !==
|
|
22960
|
+
if (typed !== join15(home, PROJECTS_FOLDER) || existsSync18(typed) || !existsSync18(home))
|
|
22709
22961
|
return null;
|
|
22710
|
-
return
|
|
22962
|
+
return join15(realpathSync2(home), PROJECTS_FOLDER);
|
|
22711
22963
|
}
|
|
22712
22964
|
function planInPlace(opts) {
|
|
22713
22965
|
const name = opts.name.trim();
|
|
22714
22966
|
if (!name || name === "." || name === ".." || name.includes("/")) {
|
|
22715
22967
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
22716
22968
|
}
|
|
22717
|
-
const typed =
|
|
22718
|
-
if (!
|
|
22969
|
+
const typed = join15(resolveParent(opts.parent), name);
|
|
22970
|
+
if (!existsSync18(typed) || !statSync7(typed).isDirectory())
|
|
22719
22971
|
throw new UserError(`${typed} is not a folder`);
|
|
22720
22972
|
const dir = realpathSync2(typed);
|
|
22721
|
-
if (
|
|
22973
|
+
if (existsSync18(join15(dir, ".git")))
|
|
22722
22974
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
22723
22975
|
if (!holdsNothing(readdirSync5(dir))) {
|
|
22724
22976
|
throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
|
|
22725
22977
|
}
|
|
22726
|
-
return { parent:
|
|
22978
|
+
return { parent: dirname6(dir), dir, makeParent: false };
|
|
22727
22979
|
}
|
|
22728
22980
|
function resolveParent(raw) {
|
|
22729
22981
|
const typed = expandTilde(raw.trim());
|
|
22730
22982
|
if (!typed.startsWith("/"))
|
|
22731
22983
|
throw new UserError(`${raw} is not a folder path`);
|
|
22732
|
-
if (!
|
|
22984
|
+
if (!existsSync18(typed))
|
|
22733
22985
|
throw new UserError(`${raw} does not exist; make it first`);
|
|
22734
|
-
if (!
|
|
22986
|
+
if (!statSync7(typed).isDirectory())
|
|
22735
22987
|
throw new UserError(`${raw} is not a folder`);
|
|
22736
22988
|
return realpathSync2(typed);
|
|
22737
22989
|
}
|
|
22738
|
-
async function createRepoDir(opts, home =
|
|
22990
|
+
async function createRepoDir(opts, home = homedir8()) {
|
|
22739
22991
|
const { parent, dir, makeParent } = planProject(opts, home);
|
|
22740
22992
|
await requireGitIdentity(makeParent ? home : parent);
|
|
22741
22993
|
if (makeParent)
|
|
@@ -22770,7 +23022,7 @@ async function initWithEmptyCommit(dir) {
|
|
|
22770
23022
|
if (!exclude.ok)
|
|
22771
23023
|
throw new UserError(`git rev-parse failed: ${exclude.err}`);
|
|
22772
23024
|
const excludePath = resolve8(dir, exclude.out);
|
|
22773
|
-
await mkdir2(
|
|
23025
|
+
await mkdir2(dirname6(excludePath), { recursive: true });
|
|
22774
23026
|
await appendFile2(excludePath, `
|
|
22775
23027
|
${LOCAL_EXCLUDES.join(`
|
|
22776
23028
|
`)}
|
|
@@ -22809,13 +23061,13 @@ async function undoCreate(dir) {
|
|
|
22809
23061
|
}
|
|
22810
23062
|
async function undoInit(dir) {
|
|
22811
23063
|
try {
|
|
22812
|
-
await rm(
|
|
23064
|
+
await rm(join15(dir, ".git"), { recursive: true, force: true });
|
|
22813
23065
|
} catch (e) {
|
|
22814
23066
|
log.warn(dir, "could not take back a half-made .git", e);
|
|
22815
23067
|
}
|
|
22816
23068
|
}
|
|
22817
23069
|
async function unmakeProject(dir, made) {
|
|
22818
|
-
const target = made === "folder" ? dir :
|
|
23070
|
+
const target = made === "folder" ? dir : join15(dir, ".git");
|
|
22819
23071
|
try {
|
|
22820
23072
|
await rm(target, { recursive: true, force: true });
|
|
22821
23073
|
} catch (e) {
|
|
@@ -22830,8 +23082,8 @@ async function hasGitIdentity(cwd) {
|
|
|
22830
23082
|
return !!email.out && !!name.out;
|
|
22831
23083
|
}
|
|
22832
23084
|
async function setGitIdentity(identity) {
|
|
22833
|
-
await ok(git(
|
|
22834
|
-
await ok(git(
|
|
23085
|
+
await ok(git(homedir8(), "config", "--global", "user.name", identity.name.trim()), "git config");
|
|
23086
|
+
await ok(git(homedir8(), "config", "--global", "user.email", identity.email.trim()), "git config");
|
|
22835
23087
|
}
|
|
22836
23088
|
async function requireGitIdentity(cwd) {
|
|
22837
23089
|
if (!await hasGitIdentity(cwd)) {
|
|
@@ -22845,8 +23097,8 @@ async function ok(p, what) {
|
|
|
22845
23097
|
}
|
|
22846
23098
|
|
|
22847
23099
|
// ../daemon/src/repos/watcher.ts
|
|
22848
|
-
import { existsSync as
|
|
22849
|
-
import { basename as
|
|
23100
|
+
import { existsSync as existsSync19, watch } from "fs";
|
|
23101
|
+
import { basename as basename4, join as join16 } from "path";
|
|
22850
23102
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
22851
23103
|
const last = { local: null, upstream: null };
|
|
22852
23104
|
const read = async () => {
|
|
@@ -22899,29 +23151,29 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
22899
23151
|
]);
|
|
22900
23152
|
if (stopped)
|
|
22901
23153
|
return;
|
|
22902
|
-
const gitDir = common.ok && common.out ? common.out :
|
|
23154
|
+
const gitDir = common.ok && common.out ? common.out : join16(repoPath, ".git");
|
|
22903
23155
|
tryWatch(gitDir);
|
|
22904
|
-
tryWatch(
|
|
23156
|
+
tryWatch(join16(gitDir, "refs", "heads"));
|
|
22905
23157
|
if (!remote.ok || !remote.out)
|
|
22906
23158
|
return;
|
|
22907
|
-
const remotes =
|
|
22908
|
-
const mine =
|
|
23159
|
+
const remotes = join16(gitDir, "refs", "remotes");
|
|
23160
|
+
const mine = join16(remotes, remote.out);
|
|
22909
23161
|
let inner = null;
|
|
22910
23162
|
let mid = null;
|
|
22911
23163
|
const armInner = () => {
|
|
22912
23164
|
inner?.close();
|
|
22913
|
-
inner =
|
|
23165
|
+
inner = existsSync19(mine) ? tryWatch(mine) : null;
|
|
22914
23166
|
};
|
|
22915
23167
|
const armMid = () => {
|
|
22916
23168
|
mid?.close();
|
|
22917
|
-
mid =
|
|
23169
|
+
mid = existsSync19(remotes) ? tryWatch(remotes, (_event, filename) => {
|
|
22918
23170
|
if (!filename || filename === remote.out)
|
|
22919
23171
|
armInner();
|
|
22920
23172
|
schedule();
|
|
22921
23173
|
}) : null;
|
|
22922
23174
|
armInner();
|
|
22923
23175
|
};
|
|
22924
|
-
tryWatch(
|
|
23176
|
+
tryWatch(join16(gitDir, "refs"), (_event, filename) => {
|
|
22925
23177
|
if (!filename || filename === "remotes")
|
|
22926
23178
|
armMid();
|
|
22927
23179
|
schedule();
|
|
@@ -22950,7 +23202,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
22950
23202
|
}, 300);
|
|
22951
23203
|
};
|
|
22952
23204
|
const arm = (commonDir) => {
|
|
22953
|
-
const dir =
|
|
23205
|
+
const dir = join16(commonDir, "worktrees");
|
|
22954
23206
|
const armInner = () => {
|
|
22955
23207
|
inner?.close();
|
|
22956
23208
|
inner = null;
|
|
@@ -22992,8 +23244,8 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
22992
23244
|
}
|
|
22993
23245
|
function watchConfigFile(repoPath, onChange) {
|
|
22994
23246
|
const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
|
|
22995
|
-
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) =>
|
|
22996
|
-
const dir =
|
|
23247
|
+
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename4(rel)));
|
|
23248
|
+
const dir = join16(repoPath, CONFIG_DIR);
|
|
22997
23249
|
let timer = null;
|
|
22998
23250
|
let outer = null;
|
|
22999
23251
|
let inner = null;
|
|
@@ -23008,7 +23260,7 @@ function watchConfigFile(repoPath, onChange) {
|
|
|
23008
23260
|
const armInner = () => {
|
|
23009
23261
|
inner?.close();
|
|
23010
23262
|
inner = null;
|
|
23011
|
-
if (!
|
|
23263
|
+
if (!existsSync19(dir))
|
|
23012
23264
|
return;
|
|
23013
23265
|
try {
|
|
23014
23266
|
inner = watch(dir, (_event, filename) => {
|
|
@@ -23062,7 +23314,7 @@ class RepoRegistry {
|
|
|
23062
23314
|
}
|
|
23063
23315
|
async boot() {
|
|
23064
23316
|
const { state } = this.d;
|
|
23065
|
-
const gone = state.worktrees.filter((wt) => !
|
|
23317
|
+
const gone = state.worktrees.filter((wt) => !existsSync20(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
|
|
23066
23318
|
for (const wt of gone)
|
|
23067
23319
|
await this.d.worktrees.forgetGone(wt);
|
|
23068
23320
|
for (const repo of state.repos) {
|
|
@@ -23107,7 +23359,7 @@ class RepoRegistry {
|
|
|
23107
23359
|
}
|
|
23108
23360
|
}
|
|
23109
23361
|
gitIdentity() {
|
|
23110
|
-
this.identity ??= hasGitIdentity(
|
|
23362
|
+
this.identity ??= hasGitIdentity(homedir9());
|
|
23111
23363
|
return this.identity;
|
|
23112
23364
|
}
|
|
23113
23365
|
async unmake(repoId) {
|
|
@@ -23130,7 +23382,7 @@ class RepoRegistry {
|
|
|
23130
23382
|
return `${repo.name} is set up now`;
|
|
23131
23383
|
if (main.promptedAt || main.lastTurn)
|
|
23132
23384
|
return `${repo.name} has a chat now`;
|
|
23133
|
-
if (
|
|
23385
|
+
if (existsSync20(join17(repo.path, ".claude")))
|
|
23134
23386
|
return `an agent has already run in ${repo.name}`;
|
|
23135
23387
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
23136
23388
|
return `${repo.name} has files in it now`;
|
|
@@ -23207,7 +23459,7 @@ class RepoRegistry {
|
|
|
23207
23459
|
}
|
|
23208
23460
|
async register(rawPath, made) {
|
|
23209
23461
|
const path = expandTilde(rawPath);
|
|
23210
|
-
if (!
|
|
23462
|
+
if (!existsSync20(path))
|
|
23211
23463
|
throw new UserError(`${path} does not exist`);
|
|
23212
23464
|
if (!await isGitRepo(path))
|
|
23213
23465
|
throw new UserError(`${path} is not a git repository`);
|
|
@@ -23221,7 +23473,7 @@ class RepoRegistry {
|
|
|
23221
23473
|
const repo = {
|
|
23222
23474
|
id: shortId(),
|
|
23223
23475
|
path: root,
|
|
23224
|
-
name:
|
|
23476
|
+
name: basename5(root),
|
|
23225
23477
|
defaultBranch: await defaultBranch(root),
|
|
23226
23478
|
config: detected.config,
|
|
23227
23479
|
configFile: configTarget(root, !!made),
|
|
@@ -23281,10 +23533,10 @@ class RepoRegistry {
|
|
|
23281
23533
|
throw new UserError(current.reason);
|
|
23282
23534
|
const was = configTarget(repo.path, !!repo.made);
|
|
23283
23535
|
const rel = configSibling(was, kind);
|
|
23284
|
-
const moved = rel !== was &&
|
|
23536
|
+
const moved = rel !== was && existsSync20(join17(repo.path, was));
|
|
23285
23537
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
23286
23538
|
try {
|
|
23287
|
-
rmSync2(
|
|
23539
|
+
rmSync2(join17(repo.path, was));
|
|
23288
23540
|
} catch (e) {
|
|
23289
23541
|
log.warn(repoId, `could not remove ${was}`, e);
|
|
23290
23542
|
}
|
|
@@ -23293,9 +23545,9 @@ class RepoRegistry {
|
|
|
23293
23545
|
}
|
|
23294
23546
|
if (isLocalConfigFile(rel))
|
|
23295
23547
|
await excludeFromGit(repo.path, rel);
|
|
23296
|
-
const file =
|
|
23548
|
+
const file = join17(repo.path, rel);
|
|
23297
23549
|
try {
|
|
23298
|
-
mkdirSync6(
|
|
23550
|
+
mkdirSync6(dirname7(file), { recursive: true });
|
|
23299
23551
|
writeFileSync7(file, configText(repo.path, rel, config));
|
|
23300
23552
|
} catch (e) {
|
|
23301
23553
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
@@ -23397,7 +23649,7 @@ class RepoRegistry {
|
|
|
23397
23649
|
}
|
|
23398
23650
|
placeConfig(repo) {
|
|
23399
23651
|
const rel = configTarget(repo.path, !!repo.made);
|
|
23400
|
-
if (isLocalConfigFile(rel) &&
|
|
23652
|
+
if (isLocalConfigFile(rel) && existsSync20(join17(repo.path, rel))) {
|
|
23401
23653
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
23402
23654
|
}
|
|
23403
23655
|
if (repo.configFile === rel)
|
|
@@ -23489,7 +23741,7 @@ class RepoRegistry {
|
|
|
23489
23741
|
}
|
|
23490
23742
|
|
|
23491
23743
|
// ../daemon/src/routes/service.ts
|
|
23492
|
-
import { join as
|
|
23744
|
+
import { join as join18 } from "path";
|
|
23493
23745
|
|
|
23494
23746
|
// ../daemon/src/routes/fileRouters.ts
|
|
23495
23747
|
var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
|
|
@@ -24689,7 +24941,7 @@ class RouteService {
|
|
|
24689
24941
|
return mergeRoutes(fileRoutes(paths, roots), code);
|
|
24690
24942
|
}
|
|
24691
24943
|
async cachedRead(root, path, had, kept, decode) {
|
|
24692
|
-
const abs =
|
|
24944
|
+
const abs = join18(root, path);
|
|
24693
24945
|
const stat = await this.fs.stat(abs);
|
|
24694
24946
|
if (!stat)
|
|
24695
24947
|
return null;
|
|
@@ -24714,7 +24966,7 @@ class RouteService {
|
|
|
24714
24966
|
}
|
|
24715
24967
|
}
|
|
24716
24968
|
async hash(root, file) {
|
|
24717
|
-
const abs =
|
|
24969
|
+
const abs = join18(root, file);
|
|
24718
24970
|
const stat = await this.fs.stat(abs);
|
|
24719
24971
|
if (!stat)
|
|
24720
24972
|
return null;
|
|
@@ -24976,7 +25228,7 @@ function isAlive(pid) {
|
|
|
24976
25228
|
}
|
|
24977
25229
|
|
|
24978
25230
|
// ../daemon/src/runtime/bridge-script.ts
|
|
24979
|
-
import { existsSync as
|
|
25231
|
+
import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
24980
25232
|
var LEARNED_MAX = 8;
|
|
24981
25233
|
|
|
24982
25234
|
class BridgeScript {
|
|
@@ -25006,9 +25258,9 @@ class BridgeScript {
|
|
|
25006
25258
|
this.cache = null;
|
|
25007
25259
|
}
|
|
25008
25260
|
get() {
|
|
25009
|
-
if (!
|
|
25261
|
+
if (!existsSync21(this.path))
|
|
25010
25262
|
return "// toyon bridge not built";
|
|
25011
|
-
const s =
|
|
25263
|
+
const s = statSync8(this.path);
|
|
25012
25264
|
const stamp = `${s.mtimeMs}:${s.size}`;
|
|
25013
25265
|
if (this.cache && stamp === this.stamp)
|
|
25014
25266
|
return this.cache;
|
|
@@ -25033,7 +25285,7 @@ function servedLocally(origin) {
|
|
|
25033
25285
|
}
|
|
25034
25286
|
|
|
25035
25287
|
// ../daemon/src/server/ws.ts
|
|
25036
|
-
import { homedir as
|
|
25288
|
+
import { homedir as homedir11 } from "os";
|
|
25037
25289
|
|
|
25038
25290
|
// ../daemon/src/core/pair.ts
|
|
25039
25291
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
@@ -25056,47 +25308,47 @@ class PairCodes {
|
|
|
25056
25308
|
}
|
|
25057
25309
|
|
|
25058
25310
|
// ../daemon/src/agent/config.ts
|
|
25059
|
-
import { existsSync as
|
|
25060
|
-
import { homedir as
|
|
25061
|
-
import { join as
|
|
25062
|
-
function agentConfigFiles(spec, repoPath, home =
|
|
25311
|
+
import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
|
|
25312
|
+
import { homedir as homedir10 } from "os";
|
|
25313
|
+
import { join as join19 } from "path";
|
|
25314
|
+
function agentConfigFiles(spec, repoPath, home = homedir10()) {
|
|
25063
25315
|
const file = (id, label, path) => ({
|
|
25064
25316
|
id,
|
|
25065
25317
|
label,
|
|
25066
25318
|
path,
|
|
25067
|
-
exists:
|
|
25319
|
+
exists: existsSync22(path)
|
|
25068
25320
|
});
|
|
25069
25321
|
if (spec.id === "claude") {
|
|
25070
25322
|
return [
|
|
25071
|
-
file("user-config", "user config (MCP servers, projects)",
|
|
25072
|
-
file("user-settings", "user settings (permissions, hooks)",
|
|
25073
|
-
...repoPath ? [file("project-mcp", "project MCP servers",
|
|
25074
|
-
...repoPath ? [file("project-settings", "project settings",
|
|
25323
|
+
file("user-config", "user config (MCP servers, projects)", join19(home, ".claude.json")),
|
|
25324
|
+
file("user-settings", "user settings (permissions, hooks)", join19(home, ".claude", "settings.json")),
|
|
25325
|
+
...repoPath ? [file("project-mcp", "project MCP servers", join19(repoPath, ".mcp.json"))] : [],
|
|
25326
|
+
...repoPath ? [file("project-settings", "project settings", join19(repoPath, ".claude", "settings.json"))] : []
|
|
25075
25327
|
];
|
|
25076
25328
|
}
|
|
25077
25329
|
if (spec.id === "codex")
|
|
25078
|
-
return [file("user-config", "config",
|
|
25330
|
+
return [file("user-config", "config", join19(home, ".codex", "config.toml"))];
|
|
25079
25331
|
return [];
|
|
25080
25332
|
}
|
|
25081
|
-
function describeAgentConfig(spec, repoPath, home =
|
|
25333
|
+
function describeAgentConfig(spec, repoPath, home = homedir10()) {
|
|
25082
25334
|
const files = agentConfigFiles(spec, repoPath, home);
|
|
25083
25335
|
const servers = [];
|
|
25084
25336
|
if (spec.id === "claude") {
|
|
25085
|
-
const user = readJson(
|
|
25337
|
+
const user = readJson(join19(home, ".claude.json"));
|
|
25086
25338
|
servers.push(...serversOf(user?.mcpServers, "user"));
|
|
25087
25339
|
if (repoPath) {
|
|
25088
25340
|
const projects = user?.projects;
|
|
25089
25341
|
const mine = projects?.[repoPath];
|
|
25090
25342
|
servers.push(...serversOf(mine?.mcpServers, "local"));
|
|
25091
|
-
servers.push(...serversOf(readJson(
|
|
25343
|
+
servers.push(...serversOf(readJson(join19(repoPath, ".mcp.json"))?.mcpServers, "project"));
|
|
25092
25344
|
}
|
|
25093
25345
|
} else if (spec.id === "codex") {
|
|
25094
|
-
servers.push(...codexServers(
|
|
25346
|
+
servers.push(...codexServers(join19(home, ".codex", "config.toml")));
|
|
25095
25347
|
}
|
|
25096
25348
|
return { agent: spec.id, files, servers };
|
|
25097
25349
|
}
|
|
25098
25350
|
function readJson(path) {
|
|
25099
|
-
if (!
|
|
25351
|
+
if (!existsSync22(path))
|
|
25100
25352
|
return null;
|
|
25101
25353
|
try {
|
|
25102
25354
|
const v = JSON.parse(readFileSync14(path, "utf8"));
|
|
@@ -25117,7 +25369,7 @@ function serversOf(raw, scope) {
|
|
|
25117
25369
|
});
|
|
25118
25370
|
}
|
|
25119
25371
|
function codexServers(path) {
|
|
25120
|
-
if (!
|
|
25372
|
+
if (!existsSync22(path))
|
|
25121
25373
|
return [];
|
|
25122
25374
|
const out = [];
|
|
25123
25375
|
let current = null;
|
|
@@ -25883,16 +26135,16 @@ var handlers = {
|
|
|
25883
26135
|
s.afterLand.run(msg.repoId);
|
|
25884
26136
|
ctx.reply({ t: "self", self: s.self.get() });
|
|
25885
26137
|
},
|
|
25886
|
-
"restart-daemon"(_msg, _ctx, s) {
|
|
25887
|
-
const refused = s.restarter.request();
|
|
26138
|
+
async "restart-daemon"(_msg, _ctx, s) {
|
|
26139
|
+
const refused = await s.restarter.request();
|
|
25888
26140
|
if (refused)
|
|
25889
26141
|
throw new UserError(refused);
|
|
25890
26142
|
},
|
|
25891
26143
|
"update-now"(_msg, _ctx, s) {
|
|
25892
26144
|
return s.update.updateNow();
|
|
25893
26145
|
},
|
|
25894
|
-
"check-update"(_msg,
|
|
25895
|
-
|
|
26146
|
+
async "check-update"(_msg, ctx, s) {
|
|
26147
|
+
ctx.reply({ t: "update-checked", check: await s.update.checkNow() });
|
|
25896
26148
|
},
|
|
25897
26149
|
async commit(msg, ctx, s) {
|
|
25898
26150
|
const result = await s.worktrees.commit(msg.worktreeId, msg.message);
|
|
@@ -25945,10 +26197,14 @@ var handlers = {
|
|
|
25945
26197
|
reveal(msg, _ctx, s) {
|
|
25946
26198
|
s.files.reveal(msg.worktreeId, msg.path);
|
|
25947
26199
|
},
|
|
25948
|
-
async "discard-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
|
|
26200
|
+
async "discard-files"(msg, _ctx, s) {
|
|
26201
|
+
try {
|
|
26202
|
+
for (const path of msg.paths)
|
|
26203
|
+
await s.files.discard(msg.worktreeId, path);
|
|
26204
|
+
} finally {
|
|
26205
|
+
s.hub.emit("filesChanged", msg.worktreeId);
|
|
26206
|
+
s.landing.recheck(msg.worktreeId);
|
|
26207
|
+
}
|
|
25952
26208
|
},
|
|
25953
26209
|
async judge(msg, _ctx, s) {
|
|
25954
26210
|
await s.landing.judge(msg.worktreeId, msg.note);
|
|
@@ -26168,8 +26424,8 @@ async function dispatch(msg, ctx, s) {
|
|
|
26168
26424
|
}
|
|
26169
26425
|
|
|
26170
26426
|
// ../daemon/src/server/http.ts
|
|
26171
|
-
import { existsSync as
|
|
26172
|
-
import { join as
|
|
26427
|
+
import { existsSync as existsSync23 } from "fs";
|
|
26428
|
+
import { join as join20 } from "path";
|
|
26173
26429
|
var IMMUTABLE = "private, max-age=31536000, immutable";
|
|
26174
26430
|
var NO_STORE = "no-store";
|
|
26175
26431
|
function createFetch(opts) {
|
|
@@ -26245,7 +26501,7 @@ function createFetch(opts) {
|
|
|
26245
26501
|
return new Response("unauthorized", { status: 401 });
|
|
26246
26502
|
if (req.method === "GET")
|
|
26247
26503
|
return Response.json(opts.restartWait(), { headers: { "cache-control": NO_STORE } });
|
|
26248
|
-
const refused = opts.restart(url.searchParams.has(RESTART_NOW));
|
|
26504
|
+
const refused = await opts.restart(url.searchParams.has(RESTART_NOW));
|
|
26249
26505
|
return refused ? new Response(refused, { status: 409 }) : new Response(null, { status: 202 });
|
|
26250
26506
|
}
|
|
26251
26507
|
if (url.pathname === "/pair" && req.method === "POST") {
|
|
@@ -26312,7 +26568,7 @@ function createFetch(opts) {
|
|
|
26312
26568
|
const places = worktreeId && file && !extra ? [opts.attachments.fileFor(worktreeId, file), opts.archivedAttachment(worktreeId, file)] : [];
|
|
26313
26569
|
if (worktreeId && file)
|
|
26314
26570
|
await opts.attachments.whenWritten(worktreeId, file);
|
|
26315
|
-
const path = places.find((p) => !!p &&
|
|
26571
|
+
const path = places.find((p) => !!p && existsSync23(p));
|
|
26316
26572
|
if (!path)
|
|
26317
26573
|
return new Response("not found", { status: 404 });
|
|
26318
26574
|
return new Response(Bun.file(path), { headers: { "cache-control": IMMUTABLE } });
|
|
@@ -26343,15 +26599,15 @@ function createFetch(opts) {
|
|
|
26343
26599
|
});
|
|
26344
26600
|
}
|
|
26345
26601
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
26346
|
-
const file =
|
|
26602
|
+
const file = join20(opts.shellDist, rel.replaceAll("..", ""));
|
|
26347
26603
|
const hashed = rel.startsWith("/assets/");
|
|
26348
|
-
if (
|
|
26604
|
+
if (existsSync23(file) && Bun.file(file).size > 0) {
|
|
26349
26605
|
return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
|
|
26350
26606
|
}
|
|
26351
26607
|
if (hashed)
|
|
26352
26608
|
return new Response("not found", { status: 404 });
|
|
26353
|
-
const index =
|
|
26354
|
-
if (
|
|
26609
|
+
const index = join20(opts.shellDist, "index.html");
|
|
26610
|
+
if (existsSync23(index))
|
|
26355
26611
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE } });
|
|
26356
26612
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
26357
26613
|
};
|
|
@@ -26459,6 +26715,7 @@ function startServer(opts) {
|
|
|
26459
26715
|
},
|
|
26460
26716
|
costs: s.idle.costs(),
|
|
26461
26717
|
updates: s.update.status(),
|
|
26718
|
+
agents: s.agents.adapters(),
|
|
26462
26719
|
sockets: [...sockets].map((ws) => ({ subs: [...ws.data.subs], sent: ws.data.sent, bytes: ws.data.bytes }))
|
|
26463
26720
|
});
|
|
26464
26721
|
let statusesInFlight = false;
|
|
@@ -26592,6 +26849,7 @@ function startServer(opts) {
|
|
|
26592
26849
|
t: "hello",
|
|
26593
26850
|
version,
|
|
26594
26851
|
install: s.update.install(),
|
|
26852
|
+
registry: await s.update.registry(),
|
|
26595
26853
|
protocol: PROTOCOL_VERSION,
|
|
26596
26854
|
repos: s.state.repos,
|
|
26597
26855
|
rows: await s.worktrees.rows({ quick: true }),
|
|
@@ -26601,7 +26859,7 @@ function startServer(opts) {
|
|
|
26601
26859
|
agents: agentInfos(),
|
|
26602
26860
|
defaultAgent: s.agents.defaultId(s.state.defaultAgent),
|
|
26603
26861
|
agentChosen: s.state.defaultAgent !== undefined,
|
|
26604
|
-
home:
|
|
26862
|
+
home: homedir11(),
|
|
26605
26863
|
folderDialog: process.platform === "darwin" && !cloud.enabled,
|
|
26606
26864
|
remote: opts.remote && { host: opts.remote.host, previews: opts.remote.previews },
|
|
26607
26865
|
paired: s.state.paired,
|
|
@@ -26752,16 +27010,16 @@ function startServer(opts) {
|
|
|
26752
27010
|
}
|
|
26753
27011
|
|
|
26754
27012
|
// ../daemon/src/themes/store.ts
|
|
26755
|
-
import { existsSync as
|
|
26756
|
-
import { homedir as
|
|
26757
|
-
import { basename as
|
|
26758
|
-
var home =
|
|
27013
|
+
import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
27014
|
+
import { homedir as homedir12 } from "os";
|
|
27015
|
+
import { basename as basename6, dirname as dirname8, join as join21, resolve as resolve9 } from "path";
|
|
27016
|
+
var home = homedir12();
|
|
26759
27017
|
var defaultExtensionDirs = [
|
|
26760
|
-
|
|
26761
|
-
|
|
26762
|
-
|
|
26763
|
-
|
|
26764
|
-
|
|
27018
|
+
join21(home, ".vscode", "extensions"),
|
|
27019
|
+
join21(home, ".vscode-insiders", "extensions"),
|
|
27020
|
+
join21(home, ".vscode-oss", "extensions"),
|
|
27021
|
+
join21(home, ".cursor", "extensions"),
|
|
27022
|
+
join21(home, ".windsurf", "extensions"),
|
|
26765
27023
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
|
|
26766
27024
|
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
|
|
26767
27025
|
"/Applications/Cursor.app/Contents/Resources/app/extensions",
|
|
@@ -26839,13 +27097,13 @@ class ThemeStore {
|
|
|
26839
27097
|
if (json && typeof json === "object" && typeof json.include === "string") {
|
|
26840
27098
|
throw new ThemeImportError("this theme file uses `include`; drop the whole extension folder's theme into ~/.toyon/themes or install it in VS Code and rescan");
|
|
26841
27099
|
}
|
|
26842
|
-
const base = slug(
|
|
27100
|
+
const base = slug(basename6(name).replace(/\.(json|jsonc)$/i, ""));
|
|
26843
27101
|
const theme = vscodeToTheme(json, {
|
|
26844
27102
|
id: `file:${base}`,
|
|
26845
27103
|
name: json?.name ?? base,
|
|
26846
27104
|
source: "file"
|
|
26847
27105
|
});
|
|
26848
|
-
writeFileSync8(
|
|
27106
|
+
writeFileSync8(join21(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
|
|
26849
27107
|
`);
|
|
26850
27108
|
this.load();
|
|
26851
27109
|
return theme;
|
|
@@ -26860,7 +27118,7 @@ function readVscodeTheme(file, depth = 0) {
|
|
|
26860
27118
|
throw new ThemeImportError(`${file}: not a JSON object`);
|
|
26861
27119
|
const t = json;
|
|
26862
27120
|
if (typeof t.include === "string" && depth < 5) {
|
|
26863
|
-
const parent = readVscodeTheme(resolve9(
|
|
27121
|
+
const parent = readVscodeTheme(resolve9(dirname8(file), t.include), depth + 1);
|
|
26864
27122
|
return {
|
|
26865
27123
|
...parent,
|
|
26866
27124
|
...t,
|
|
@@ -26874,13 +27132,13 @@ function readVscodeTheme(file, depth = 0) {
|
|
|
26874
27132
|
return t;
|
|
26875
27133
|
}
|
|
26876
27134
|
function loadDir(dir) {
|
|
26877
|
-
if (!
|
|
27135
|
+
if (!existsSync24(dir))
|
|
26878
27136
|
return [];
|
|
26879
27137
|
const out = [];
|
|
26880
27138
|
for (const f of readdirSync6(dir)) {
|
|
26881
27139
|
if (!/\.jsonc?$/i.test(f))
|
|
26882
27140
|
continue;
|
|
26883
|
-
const file =
|
|
27141
|
+
const file = join21(dir, f);
|
|
26884
27142
|
const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
|
|
26885
27143
|
try {
|
|
26886
27144
|
const json = readJsonc(file);
|
|
@@ -26896,7 +27154,7 @@ function loadDir(dir) {
|
|
|
26896
27154
|
return out;
|
|
26897
27155
|
}
|
|
26898
27156
|
function discover(dir) {
|
|
26899
|
-
if (!
|
|
27157
|
+
if (!existsSync24(dir))
|
|
26900
27158
|
return [];
|
|
26901
27159
|
const out = [];
|
|
26902
27160
|
let entries;
|
|
@@ -26906,10 +27164,10 @@ function discover(dir) {
|
|
|
26906
27164
|
return out;
|
|
26907
27165
|
}
|
|
26908
27166
|
for (const name of entries) {
|
|
26909
|
-
const extDir =
|
|
26910
|
-
const pkgFile =
|
|
27167
|
+
const extDir = join21(dir, name);
|
|
27168
|
+
const pkgFile = join21(extDir, "package.json");
|
|
26911
27169
|
try {
|
|
26912
|
-
if (!
|
|
27170
|
+
if (!statSync9(extDir).isDirectory() || !existsSync24(pkgFile))
|
|
26913
27171
|
continue;
|
|
26914
27172
|
const pkg = JSON.parse(readFileSync15(pkgFile, "utf8"));
|
|
26915
27173
|
const themes = pkg.contributes?.themes;
|
|
@@ -26917,8 +27175,8 @@ function discover(dir) {
|
|
|
26917
27175
|
continue;
|
|
26918
27176
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
26919
27177
|
let nls = {};
|
|
26920
|
-
const nlsFile =
|
|
26921
|
-
if (
|
|
27178
|
+
const nlsFile = join21(extDir, "package.nls.json");
|
|
27179
|
+
if (existsSync24(nlsFile)) {
|
|
26922
27180
|
try {
|
|
26923
27181
|
nls = JSON.parse(readFileSync15(nlsFile, "utf8"));
|
|
26924
27182
|
} catch {}
|
|
@@ -26945,11 +27203,10 @@ function discover(dir) {
|
|
|
26945
27203
|
}
|
|
26946
27204
|
|
|
26947
27205
|
// ../daemon/src/update/infra.ts
|
|
26948
|
-
import { homedir as
|
|
27206
|
+
import { homedir as homedir13 } from "os";
|
|
26949
27207
|
async function latestVersion() {
|
|
26950
|
-
const
|
|
26951
|
-
const
|
|
26952
|
-
const r = await run("npm", ["view", "toyon", "version", "--json"], homedir11());
|
|
27208
|
+
const registry = await npmRegistry();
|
|
27209
|
+
const r = await run("npm", ["view", "toyon", "version", "--json"], homedir13());
|
|
26953
27210
|
if (!r.ok)
|
|
26954
27211
|
return { version: null, registry };
|
|
26955
27212
|
try {
|
|
@@ -26959,12 +27216,16 @@ async function latestVersion() {
|
|
|
26959
27216
|
return { version: null, registry };
|
|
26960
27217
|
}
|
|
26961
27218
|
}
|
|
27219
|
+
async function npmRegistry() {
|
|
27220
|
+
const config = await run("npm", ["config", "get", "registry"], homedir13());
|
|
27221
|
+
return config.ok && config.out !== "" ? config.out : "the npm registry";
|
|
27222
|
+
}
|
|
26962
27223
|
var LOG_POINTER = /complete log of this run/i;
|
|
26963
27224
|
async function runInstall(command) {
|
|
26964
27225
|
const [cmd, ...args] = command;
|
|
26965
27226
|
if (!cmd)
|
|
26966
27227
|
return { ok: false, line: "nothing to run" };
|
|
26967
|
-
const r = await runLive(cmd, args,
|
|
27228
|
+
const r = await runLive(cmd, args, homedir13());
|
|
26968
27229
|
const lines = `${r.out}
|
|
26969
27230
|
${r.err}`.split(`
|
|
26970
27231
|
`).map((l) => l.trim()).filter((l) => l !== "" && !LOG_POINTER.test(l));
|
|
@@ -26987,12 +27248,16 @@ var CHECK_EVERY_MS = 6 * 60 * 60000;
|
|
|
26987
27248
|
var FIRST_CHECK_MS = 2 * 60000;
|
|
26988
27249
|
var SETTLED_MS = 2 * 60000;
|
|
26989
27250
|
var RETRY_AFTER_MS = 24 * 60 * 60000;
|
|
27251
|
+
var INSTALL_SETTLE_MS = 60000;
|
|
26990
27252
|
|
|
26991
27253
|
class UpdateService {
|
|
26992
27254
|
d;
|
|
26993
27255
|
installed = null;
|
|
26994
27256
|
latest = null;
|
|
26995
27257
|
unreachable = null;
|
|
27258
|
+
registryName = null;
|
|
27259
|
+
seen = null;
|
|
27260
|
+
seenAt = 0;
|
|
26996
27261
|
installing = false;
|
|
26997
27262
|
failed = null;
|
|
26998
27263
|
wanted = false;
|
|
@@ -27026,25 +27291,45 @@ class UpdateService {
|
|
|
27026
27291
|
await this.refresh();
|
|
27027
27292
|
await this.auto();
|
|
27028
27293
|
}
|
|
27029
|
-
async refresh() {
|
|
27294
|
+
async refresh(settled = false) {
|
|
27030
27295
|
const v = await this.d.installed();
|
|
27031
27296
|
if (v === null)
|
|
27032
27297
|
return;
|
|
27298
|
+
if (!settled && v !== this.d.running) {
|
|
27299
|
+
if (v !== this.seen) {
|
|
27300
|
+
this.seen = v;
|
|
27301
|
+
this.seenAt = this.now();
|
|
27302
|
+
return;
|
|
27303
|
+
}
|
|
27304
|
+
if (this.now() - this.seenAt < INSTALL_SETTLE_MS)
|
|
27305
|
+
return;
|
|
27306
|
+
}
|
|
27033
27307
|
this.installed = v === this.d.running ? null : v;
|
|
27034
27308
|
this.announce();
|
|
27035
27309
|
}
|
|
27310
|
+
async registry() {
|
|
27311
|
+
if (this.d.method === "none" || this.d.managedBy)
|
|
27312
|
+
return null;
|
|
27313
|
+
this.registryName ??= await this.d.registry();
|
|
27314
|
+
return this.registryName;
|
|
27315
|
+
}
|
|
27036
27316
|
async check() {
|
|
27037
27317
|
if (this.d.method === "none" || this.d.managedBy)
|
|
27038
27318
|
return;
|
|
27319
|
+
await this.ask();
|
|
27320
|
+
}
|
|
27321
|
+
async ask() {
|
|
27039
27322
|
const answer = await this.d.latest();
|
|
27323
|
+
this.registryName = answer.registry;
|
|
27040
27324
|
if (answer.version === null) {
|
|
27041
27325
|
this.unreachable = answer.registry;
|
|
27042
|
-
return;
|
|
27326
|
+
return { registry: answer.registry, latest: null };
|
|
27043
27327
|
}
|
|
27044
27328
|
this.unreachable = null;
|
|
27045
27329
|
this.latest = newer(answer.version, this.d.running) ? answer.version : null;
|
|
27046
27330
|
this.announce();
|
|
27047
27331
|
await this.auto();
|
|
27332
|
+
return { registry: answer.registry, latest: answer.version };
|
|
27048
27333
|
}
|
|
27049
27334
|
async updateNow() {
|
|
27050
27335
|
this.refuseManaged();
|
|
@@ -27061,13 +27346,17 @@ class UpdateService {
|
|
|
27061
27346
|
async checkNow() {
|
|
27062
27347
|
this.refuseManaged();
|
|
27063
27348
|
if (this.d.method === "none") {
|
|
27064
|
-
|
|
27349
|
+
const answer = await this.d.latest();
|
|
27350
|
+
return { registry: answer.registry, latest: answer.version };
|
|
27065
27351
|
}
|
|
27066
|
-
await this.
|
|
27067
|
-
|
|
27068
|
-
|
|
27069
|
-
|
|
27070
|
-
|
|
27352
|
+
const answer = await this.ask();
|
|
27353
|
+
const target = this.target();
|
|
27354
|
+
if (target !== null && !this.wanted && !(this.needsInstall() && this.d.command(target) === null)) {
|
|
27355
|
+
this.failed = null;
|
|
27356
|
+
this.wanted = true;
|
|
27357
|
+
fireAndForget("update", this.proceed(), "update");
|
|
27358
|
+
}
|
|
27359
|
+
return answer;
|
|
27071
27360
|
}
|
|
27072
27361
|
install() {
|
|
27073
27362
|
return this.d.method;
|
|
@@ -27088,7 +27377,12 @@ class UpdateService {
|
|
|
27088
27377
|
};
|
|
27089
27378
|
}
|
|
27090
27379
|
status() {
|
|
27091
|
-
return {
|
|
27380
|
+
return {
|
|
27381
|
+
managedBy: this.d.managedBy,
|
|
27382
|
+
registry: this.d.method === "none" || this.d.managedBy ? null : this.registryName,
|
|
27383
|
+
unreachable: this.unreachable,
|
|
27384
|
+
latest: this.latest
|
|
27385
|
+
};
|
|
27092
27386
|
}
|
|
27093
27387
|
refuseManaged() {
|
|
27094
27388
|
if (this.d.managedBy === "policy")
|
|
@@ -27149,10 +27443,10 @@ class UpdateService {
|
|
|
27149
27443
|
this.announce();
|
|
27150
27444
|
return;
|
|
27151
27445
|
}
|
|
27152
|
-
await this.refresh();
|
|
27446
|
+
await this.refresh(true);
|
|
27153
27447
|
}
|
|
27154
27448
|
this.wanted = false;
|
|
27155
|
-
const refused = this.d.restarter.request();
|
|
27449
|
+
const refused = await this.d.restarter.request();
|
|
27156
27450
|
if (refused) {
|
|
27157
27451
|
this.failed = { version: target ?? this.d.running, line: refused, command: "toyon restart" };
|
|
27158
27452
|
log.warn("update", `restart refused: ${refused}`);
|
|
@@ -27296,16 +27590,16 @@ class BackendShare {
|
|
|
27296
27590
|
|
|
27297
27591
|
// ../daemon/src/worktrees/cache.ts
|
|
27298
27592
|
import { createHash as createHash3 } from "crypto";
|
|
27299
|
-
import { existsSync as
|
|
27593
|
+
import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
|
|
27300
27594
|
import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as stat3, utimes, writeFile as writeFile2 } from "fs/promises";
|
|
27301
|
-
import { join as
|
|
27595
|
+
import { join as join22 } from "path";
|
|
27302
27596
|
|
|
27303
27597
|
// ../daemon/src/core/clone.ts
|
|
27304
27598
|
import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
27305
|
-
import { dirname as
|
|
27599
|
+
import { dirname as dirname9 } from "path";
|
|
27306
27600
|
async function cloneTree(src, dst) {
|
|
27307
27601
|
const started = Date.now();
|
|
27308
|
-
await mkdir3(
|
|
27602
|
+
await mkdir3(dirname9(dst), { recursive: true });
|
|
27309
27603
|
const attempts = [
|
|
27310
27604
|
["clonefile", ["-Rc", src, dst]],
|
|
27311
27605
|
["reflink", ["-R", "--reflink=auto", src, dst]],
|
|
@@ -27313,7 +27607,7 @@ async function cloneTree(src, dst) {
|
|
|
27313
27607
|
];
|
|
27314
27608
|
let error = "";
|
|
27315
27609
|
for (const [how, args] of attempts) {
|
|
27316
|
-
const r = await run("cp", args,
|
|
27610
|
+
const r = await run("cp", args, dirname9(dst));
|
|
27317
27611
|
if (r.ok)
|
|
27318
27612
|
return { ok: true, how, ms: Date.now() - started };
|
|
27319
27613
|
error = r.err.split(`
|
|
@@ -27329,7 +27623,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
27329
27623
|
var TMP = ".tmp-";
|
|
27330
27624
|
var MANIFEST = "manifest.json";
|
|
27331
27625
|
function defaultTools(dir) {
|
|
27332
|
-
const has = (...files) => files.some((f) =>
|
|
27626
|
+
const has = (...files) => files.some((f) => existsSync25(join22(dir, f)));
|
|
27333
27627
|
const out = [];
|
|
27334
27628
|
if (has("bun.lock", "bun.lockb"))
|
|
27335
27629
|
out.push("bun --version");
|
|
@@ -27342,7 +27636,7 @@ function defaultTools(dir) {
|
|
|
27342
27636
|
return out;
|
|
27343
27637
|
}
|
|
27344
27638
|
function withSidecars(root, rel) {
|
|
27345
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) =>
|
|
27639
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(join22(root, n)));
|
|
27346
27640
|
}
|
|
27347
27641
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
27348
27642
|
async function listEntries(familyDir) {
|
|
@@ -27357,8 +27651,8 @@ async function listEntries(familyDir) {
|
|
|
27357
27651
|
const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
|
|
27358
27652
|
if (!m?.[1] || !m[2])
|
|
27359
27653
|
continue;
|
|
27360
|
-
const dir =
|
|
27361
|
-
if (!
|
|
27654
|
+
const dir = join22(familyDir, name);
|
|
27655
|
+
if (!existsSync25(join22(dir, MANIFEST)))
|
|
27362
27656
|
continue;
|
|
27363
27657
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
27364
27658
|
}
|
|
@@ -27388,7 +27682,7 @@ class ArtifactCache {
|
|
|
27388
27682
|
try {
|
|
27389
27683
|
const family = await this.family(wt, repo, policy);
|
|
27390
27684
|
const base = await this.baseCommit(wt, repo);
|
|
27391
|
-
const entries = await listEntries(
|
|
27685
|
+
const entries = await listEntries(join22(this.d.paths.cacheDir, repo.id, family.key));
|
|
27392
27686
|
const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
|
|
27393
27687
|
if (!entry) {
|
|
27394
27688
|
say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
|
|
@@ -27396,14 +27690,14 @@ class ArtifactCache {
|
|
|
27396
27690
|
}
|
|
27397
27691
|
const restored = [];
|
|
27398
27692
|
for (const rel of policy.paths) {
|
|
27399
|
-
if (
|
|
27693
|
+
if (existsSync25(join22(wt.path, rel)))
|
|
27400
27694
|
continue;
|
|
27401
27695
|
const names = withSidecars(entry.dir, rel);
|
|
27402
27696
|
if (names.length === 0)
|
|
27403
27697
|
continue;
|
|
27404
27698
|
let whole = true;
|
|
27405
27699
|
for (const name of names) {
|
|
27406
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
27700
|
+
const r = await (this.d.clone ?? cloneTree)(join22(entry.dir, name), join22(wt.path, name));
|
|
27407
27701
|
if (r.ok)
|
|
27408
27702
|
continue;
|
|
27409
27703
|
say(`cache: could not restore ${name}: ${r.error}`);
|
|
@@ -27437,12 +27731,12 @@ class ArtifactCache {
|
|
|
27437
27731
|
let tmp;
|
|
27438
27732
|
let slot;
|
|
27439
27733
|
try {
|
|
27440
|
-
const present = policy.paths.filter((rel) =>
|
|
27734
|
+
const present = policy.paths.filter((rel) => existsSync25(join22(wt.path, rel)));
|
|
27441
27735
|
if (present.length === 0)
|
|
27442
27736
|
return;
|
|
27443
27737
|
const family = await this.family(wt, repo, policy);
|
|
27444
27738
|
const base = await this.baseCommit(wt, repo);
|
|
27445
|
-
const familyDir =
|
|
27739
|
+
const familyDir = join22(this.d.paths.cacheDir, repo.id, family.key);
|
|
27446
27740
|
const key = `${familyDir}/${base.slice(0, 12)}`;
|
|
27447
27741
|
const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
|
|
27448
27742
|
if (kept || this.making.has(key)) {
|
|
@@ -27453,21 +27747,21 @@ class ArtifactCache {
|
|
|
27453
27747
|
this.making.add(key);
|
|
27454
27748
|
const at = Date.now();
|
|
27455
27749
|
const name = entryName(base, at);
|
|
27456
|
-
tmp =
|
|
27750
|
+
tmp = join22(familyDir, `${TMP}${name}`);
|
|
27457
27751
|
await mkdir4(tmp, { recursive: true });
|
|
27458
27752
|
let how = "clonefile";
|
|
27459
27753
|
for (const rel of present) {
|
|
27460
27754
|
for (const n of withSidecars(wt.path, rel)) {
|
|
27461
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
27755
|
+
const r = await (this.d.clone ?? cloneTree)(join22(wt.path, n), join22(tmp, n));
|
|
27462
27756
|
if (!r.ok)
|
|
27463
27757
|
throw new Error(`could not keep ${n}: ${r.error}`);
|
|
27464
27758
|
how = r.how;
|
|
27465
27759
|
}
|
|
27466
27760
|
}
|
|
27467
27761
|
const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
|
|
27468
|
-
await writeFile2(
|
|
27762
|
+
await writeFile2(join22(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
27469
27763
|
`);
|
|
27470
|
-
await rename(tmp,
|
|
27764
|
+
await rename(tmp, join22(familyDir, name));
|
|
27471
27765
|
tmp = undefined;
|
|
27472
27766
|
log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
|
|
27473
27767
|
await this.prune(familyDir);
|
|
@@ -27495,7 +27789,7 @@ class ArtifactCache {
|
|
|
27495
27789
|
return;
|
|
27496
27790
|
}
|
|
27497
27791
|
for (const repoId of repoIds) {
|
|
27498
|
-
const repoDir =
|
|
27792
|
+
const repoDir = join22(root, repoId);
|
|
27499
27793
|
if (!this.d.state.repo(repoId)) {
|
|
27500
27794
|
await rm3(repoDir, { recursive: true, force: true });
|
|
27501
27795
|
continue;
|
|
@@ -27507,7 +27801,7 @@ class ArtifactCache {
|
|
|
27507
27801
|
continue;
|
|
27508
27802
|
}
|
|
27509
27803
|
for (const family of families) {
|
|
27510
|
-
const familyDir =
|
|
27804
|
+
const familyDir = join22(repoDir, family);
|
|
27511
27805
|
let names;
|
|
27512
27806
|
try {
|
|
27513
27807
|
names = await readdir2(familyDir);
|
|
@@ -27516,7 +27810,7 @@ class ArtifactCache {
|
|
|
27516
27810
|
}
|
|
27517
27811
|
for (const n of names)
|
|
27518
27812
|
if (n.startsWith(TMP))
|
|
27519
|
-
await rm3(
|
|
27813
|
+
await rm3(join22(familyDir, n), { recursive: true, force: true });
|
|
27520
27814
|
const entries = await listEntries(familyDir);
|
|
27521
27815
|
let newest = 0;
|
|
27522
27816
|
for (const e of entries) {
|
|
@@ -27536,7 +27830,7 @@ class ArtifactCache {
|
|
|
27536
27830
|
for (const f of policy.key) {
|
|
27537
27831
|
h.update(`\x00${f}\x00`);
|
|
27538
27832
|
try {
|
|
27539
|
-
h.update(readFileSync16(
|
|
27833
|
+
h.update(readFileSync16(join22(wt.path, f)));
|
|
27540
27834
|
} catch {
|
|
27541
27835
|
h.update("(absent)");
|
|
27542
27836
|
}
|
|
@@ -27602,11 +27896,9 @@ function saidRows(entries) {
|
|
|
27602
27896
|
}
|
|
27603
27897
|
return out;
|
|
27604
27898
|
}
|
|
27605
|
-
function
|
|
27606
|
-
const
|
|
27607
|
-
|
|
27608
|
-
return [];
|
|
27609
|
-
return [...new Set(needle.split(" "))];
|
|
27899
|
+
function needleTerms(query) {
|
|
27900
|
+
const terms = queryTerms(query);
|
|
27901
|
+
return terms.join(" ").length < MIN_QUERY ? [] : terms;
|
|
27610
27902
|
}
|
|
27611
27903
|
function snippet(text, ranges) {
|
|
27612
27904
|
const first = Math.min(...ranges.map(([at]) => at));
|
|
@@ -27622,13 +27914,13 @@ function snippet(text, ranges) {
|
|
|
27622
27914
|
}
|
|
27623
27915
|
return { text: `${head}${text.slice(start, end)}${tail}`, match };
|
|
27624
27916
|
}
|
|
27625
|
-
function searchRows(rows,
|
|
27917
|
+
function searchRows(rows, terms, chat, limit = CHAT_HITS_PER_WORKTREE) {
|
|
27626
27918
|
const hits = [];
|
|
27627
|
-
if (
|
|
27919
|
+
if (terms.length === 0)
|
|
27628
27920
|
return { hits, more: false };
|
|
27629
27921
|
for (let i = rows.length - 1;i >= 0; i--) {
|
|
27630
27922
|
const row = rows[i];
|
|
27631
|
-
const ranges =
|
|
27923
|
+
const ranges = termRanges(row.lower, terms);
|
|
27632
27924
|
if (!ranges)
|
|
27633
27925
|
continue;
|
|
27634
27926
|
if (hits.length === limit)
|
|
@@ -27637,13 +27929,13 @@ function searchRows(rows, words, chat, limit = CHAT_HITS_PER_WORKTREE) {
|
|
|
27637
27929
|
}
|
|
27638
27930
|
return { hits, more: false };
|
|
27639
27931
|
}
|
|
27640
|
-
function
|
|
27932
|
+
function termRanges(lower, terms) {
|
|
27641
27933
|
const ranges = [];
|
|
27642
|
-
for (const
|
|
27643
|
-
const at = lower.indexOf(
|
|
27934
|
+
for (const term of terms) {
|
|
27935
|
+
const at = lower.indexOf(term);
|
|
27644
27936
|
if (at < 0)
|
|
27645
27937
|
return null;
|
|
27646
|
-
ranges.push([at,
|
|
27938
|
+
ranges.push([at, term.length]);
|
|
27647
27939
|
}
|
|
27648
27940
|
return ranges;
|
|
27649
27941
|
}
|
|
@@ -27656,8 +27948,8 @@ class ChatSearch {
|
|
|
27656
27948
|
}
|
|
27657
27949
|
async search(repoId, query) {
|
|
27658
27950
|
this.d.state.requireRepo(repoId);
|
|
27659
|
-
const
|
|
27660
|
-
if (
|
|
27951
|
+
const terms = needleTerms(query);
|
|
27952
|
+
if (terms.length === 0)
|
|
27661
27953
|
return { hits: [], truncated: false };
|
|
27662
27954
|
const chats = [
|
|
27663
27955
|
...this.d.state.worktrees.filter((w) => w.repoId === repoId && w.kind === "worktree").map((w) => ({ id: w.id, archived: false, path: this.d.transcriptPath(w.id) })),
|
|
@@ -27666,7 +27958,7 @@ class ChatSearch {
|
|
|
27666
27958
|
const read = new Set;
|
|
27667
27959
|
const found = await Promise.all(chats.map(async (c) => {
|
|
27668
27960
|
const rows = await this.rowsOf(c.id, c.archived, c.path, read);
|
|
27669
|
-
return searchRows(rows,
|
|
27961
|
+
return searchRows(rows, terms, { worktreeId: c.id, archived: c.archived });
|
|
27670
27962
|
}));
|
|
27671
27963
|
for (const key of this.said.keys())
|
|
27672
27964
|
if (!read.has(key))
|
|
@@ -27719,9 +28011,14 @@ class LandingService {
|
|
|
27719
28011
|
d.hub.on("agentStatus", (id, status) => {
|
|
27720
28012
|
this.note(id, status);
|
|
27721
28013
|
if (status === "working")
|
|
27722
|
-
this.
|
|
28014
|
+
this.turnStarts(id);
|
|
27723
28015
|
});
|
|
27724
28016
|
}
|
|
28017
|
+
turnStarts(worktreeId) {
|
|
28018
|
+
if (!this.d.state.worktree(worktreeId)?.landing?.ready)
|
|
28019
|
+
return this.clear(worktreeId);
|
|
28020
|
+
this.judging.delete(worktreeId);
|
|
28021
|
+
}
|
|
27725
28022
|
note(worktreeId, status) {
|
|
27726
28023
|
if (status === "working" || status === "waiting")
|
|
27727
28024
|
this.busy.add(worktreeId);
|
|
@@ -27747,14 +28044,69 @@ class LandingService {
|
|
|
27747
28044
|
const wt = this.d.state.worktree(worktreeId);
|
|
27748
28045
|
if (!wt || !canLand(wt))
|
|
27749
28046
|
return;
|
|
27750
|
-
|
|
27751
|
-
|
|
28047
|
+
const held = await this.standing(wt);
|
|
28048
|
+
if (turn.end !== "done") {
|
|
28049
|
+
if (!held)
|
|
28050
|
+
this.clear(worktreeId);
|
|
28051
|
+
return;
|
|
28052
|
+
}
|
|
27752
28053
|
if (!await this.hasWork(wt)) {
|
|
27753
28054
|
this.clear(worktreeId);
|
|
27754
28055
|
return this.recapAnswer(wt, turn);
|
|
27755
28056
|
}
|
|
28057
|
+
if (held)
|
|
28058
|
+
return this.reword(wt, turn, held);
|
|
27756
28059
|
await this.run(wt, { at: turn.at, ask: true, quiet: false });
|
|
27757
28060
|
}
|
|
28061
|
+
async standing(wt) {
|
|
28062
|
+
const l = wt.landing;
|
|
28063
|
+
if (!l?.ready)
|
|
28064
|
+
return;
|
|
28065
|
+
return l.fingerprint === await treeFingerprint(wt.path) ? l : undefined;
|
|
28066
|
+
}
|
|
28067
|
+
async reword(wt, turn, held) {
|
|
28068
|
+
if (!this.d.judge)
|
|
28069
|
+
return;
|
|
28070
|
+
const worktreeId = wt.id;
|
|
28071
|
+
const repo = this.d.state.requireRepo(wt.repoId);
|
|
28072
|
+
this.judging.set(worktreeId, turn.at);
|
|
28073
|
+
const started = Date.now();
|
|
28074
|
+
const entries = this.d.transcript(worktreeId);
|
|
28075
|
+
const prompt = landPrompt({
|
|
28076
|
+
title: wt.title,
|
|
28077
|
+
firstAsk: firstAskOf(entries),
|
|
28078
|
+
turns: turnsSince(entries, 0),
|
|
28079
|
+
diffStat: await diffSummary(wt.path, baseOf(repo)),
|
|
28080
|
+
recentSubjects: await recentSubjects(wt.path, baseOf(repo))
|
|
28081
|
+
});
|
|
28082
|
+
let verdict = null;
|
|
28083
|
+
try {
|
|
28084
|
+
verdict = await this.d.judge(wt, prompt);
|
|
28085
|
+
} catch (e) {
|
|
28086
|
+
log.warn(worktreeId, "landing verdict kept its words: the question went unanswered", e);
|
|
28087
|
+
}
|
|
28088
|
+
if (this.stopping || this.judging.get(worktreeId) !== turn.at)
|
|
28089
|
+
return;
|
|
28090
|
+
this.judging.delete(worktreeId);
|
|
28091
|
+
const row = this.d.state.worktree(worktreeId);
|
|
28092
|
+
const now = row?.landing;
|
|
28093
|
+
if (!verdict || !row || !now?.ready || now.fingerprint !== held.fingerprint)
|
|
28094
|
+
return;
|
|
28095
|
+
const { why: _was, stale: _moved, ...rest } = now;
|
|
28096
|
+
const caveat = await this.sharedMigration(wt, repo);
|
|
28097
|
+
const doubt = [!verdict.ready && verdict.why ? verdict.why : "", caveat ?? ""].filter(Boolean).join(". ");
|
|
28098
|
+
const landing = { ...rest, at: turn.at, ...doubt ? { why: doubt } : {} };
|
|
28099
|
+
if (!landing.subject && verdict.subject) {
|
|
28100
|
+
landing.subject = verdict.subject;
|
|
28101
|
+
if (verdict.body)
|
|
28102
|
+
landing.body = verdict.body;
|
|
28103
|
+
delete landing.unanswered;
|
|
28104
|
+
}
|
|
28105
|
+
if (row.lastTurn?.at === turn.at && verdict.recap)
|
|
28106
|
+
row.lastTurn.recap = { at: Date.now(), text: verdict.recap };
|
|
28107
|
+
log.info(worktreeId, `landing: tree unchanged, check not run again${doubt ? ", doubted" : ""}, ${Date.now() - started}ms`);
|
|
28108
|
+
this.d.worktrees.setLanding(worktreeId, landing);
|
|
28109
|
+
}
|
|
27758
28110
|
async recapAnswer(wt, turn) {
|
|
27759
28111
|
if (!this.d.recap)
|
|
27760
28112
|
return;
|
|
@@ -27946,7 +28298,7 @@ async function recentSubjects(path, defaultBr) {
|
|
|
27946
28298
|
}
|
|
27947
28299
|
|
|
27948
28300
|
// ../daemon/src/worktrees/prs.ts
|
|
27949
|
-
var
|
|
28301
|
+
var POLL_MS3 = 3 * 60000;
|
|
27950
28302
|
|
|
27951
28303
|
class PrService {
|
|
27952
28304
|
d;
|
|
@@ -27955,7 +28307,7 @@ class PrService {
|
|
|
27955
28307
|
constructor(d) {
|
|
27956
28308
|
this.d = d;
|
|
27957
28309
|
d.hub.on("repoTick", (repoId) => this.refreshRepo(repoId));
|
|
27958
|
-
this.timer = setInterval(() => this.refreshAll(), d.everyMs ??
|
|
28310
|
+
this.timer = setInterval(() => this.refreshAll(), d.everyMs ?? POLL_MS3);
|
|
27959
28311
|
this.timer.unref?.();
|
|
27960
28312
|
}
|
|
27961
28313
|
stop() {
|
|
@@ -28057,7 +28409,7 @@ async function mergedBranches(repoPath, defaultBr) {
|
|
|
28057
28409
|
|
|
28058
28410
|
// ../daemon/src/worktrees/discover.ts
|
|
28059
28411
|
import { createHash as createHash4 } from "crypto";
|
|
28060
|
-
import { basename as
|
|
28412
|
+
import { basename as basename7 } from "path";
|
|
28061
28413
|
|
|
28062
28414
|
// ../daemon/src/git/worktrees.ts
|
|
28063
28415
|
function parseWorktreeList(out) {
|
|
@@ -28146,7 +28498,7 @@ function subtractKnown(repoId, listed, known) {
|
|
|
28146
28498
|
rows.push({
|
|
28147
28499
|
id: discoveredId(g.path),
|
|
28148
28500
|
repoId,
|
|
28149
|
-
name: g.branch ??
|
|
28501
|
+
name: g.branch ?? basename7(g.path),
|
|
28150
28502
|
path: g.path,
|
|
28151
28503
|
...g.branch ? { branch: g.branch } : {},
|
|
28152
28504
|
...g.locked ? { locked: true } : {},
|
|
@@ -28308,9 +28660,9 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
28308
28660
|
}
|
|
28309
28661
|
|
|
28310
28662
|
// ../daemon/src/worktrees/service.ts
|
|
28311
|
-
import { cpSync, existsSync as
|
|
28663
|
+
import { cpSync, existsSync as existsSync28, readFileSync as readFileSync18, rmSync as rmSync5 } from "fs";
|
|
28312
28664
|
import { rm as rm4 } from "fs/promises";
|
|
28313
|
-
import { dirname as
|
|
28665
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
28314
28666
|
|
|
28315
28667
|
// ../daemon/src/git/archive.ts
|
|
28316
28668
|
import { rmSync as rmSync3 } from "fs";
|
|
@@ -28463,28 +28815,107 @@ async function requireClean(worktreePath) {
|
|
|
28463
28815
|
}
|
|
28464
28816
|
return null;
|
|
28465
28817
|
}
|
|
28466
|
-
|
|
28818
|
+
function overwritten(text) {
|
|
28819
|
+
const files = [];
|
|
28820
|
+
let listing = false;
|
|
28821
|
+
for (const line of text.split(`
|
|
28822
|
+
`)) {
|
|
28823
|
+
if (/would be overwritten by (merge|checkout):\s*$/.test(line)) {
|
|
28824
|
+
listing = true;
|
|
28825
|
+
} else if (listing && line.startsWith("\t")) {
|
|
28826
|
+
files.push(line.trim());
|
|
28827
|
+
} else {
|
|
28828
|
+
listing = false;
|
|
28829
|
+
}
|
|
28830
|
+
}
|
|
28831
|
+
return files;
|
|
28832
|
+
}
|
|
28833
|
+
var listed = (files) => files.length > 3 ? `${files.slice(0, 3).join(", ")} and ${files.length - 3} more` : files.join(", ");
|
|
28834
|
+
function inTheWay(defaultBr, what, files) {
|
|
28835
|
+
return `${defaultBr} has uncommitted files the ${what} would overwrite (${listed(files)}): commit or stash them there first`;
|
|
28836
|
+
}
|
|
28837
|
+
function untrackedInTheWay(defaultBr, files) {
|
|
28838
|
+
return `${defaultBr} has files that are untracked here (${listed(files)}): move or delete them, then sync`;
|
|
28839
|
+
}
|
|
28840
|
+
async function requireMainReady(repoPath, defaultBr, clean) {
|
|
28467
28841
|
const current = await git(repoPath, "branch", "--show-current");
|
|
28468
28842
|
if (current.out !== defaultBr) {
|
|
28469
28843
|
return { ok: false, message: `main checkout is on '${current.out}', not ${defaultBr}; switch it first` };
|
|
28470
28844
|
}
|
|
28471
|
-
if ((await statusFiles(repoPath)).length > 0) {
|
|
28845
|
+
if (clean && (await statusFiles(repoPath)).length > 0) {
|
|
28472
28846
|
return { ok: false, message: `${defaultBr} has uncommitted changes: commit or stash them there first` };
|
|
28473
28847
|
}
|
|
28474
28848
|
return null;
|
|
28475
28849
|
}
|
|
28476
|
-
|
|
28477
|
-
|
|
28478
|
-
|
|
28479
|
-
|
|
28850
|
+
var CARRIED = "refs/worktree/toyon/carried";
|
|
28851
|
+
async function setAside(worktreePath) {
|
|
28852
|
+
const head = await git(worktreePath, "rev-parse", "HEAD");
|
|
28853
|
+
const made = await git(worktreePath, "stash", "create");
|
|
28854
|
+
if (!head.ok || !made.ok) {
|
|
28855
|
+
return { ok: false, message: `could not set the uncommitted changes aside: ${made.err.slice(0, 200)}` };
|
|
28856
|
+
}
|
|
28857
|
+
if (!made.out)
|
|
28858
|
+
return null;
|
|
28859
|
+
const kept = await git(worktreePath, "update-ref", CARRIED, made.out);
|
|
28860
|
+
const cleared = kept.ok ? await git(worktreePath, "reset", "--hard", "-q") : kept;
|
|
28861
|
+
if (!cleared.ok) {
|
|
28862
|
+
return { ok: false, message: `could not set the uncommitted changes aside: ${cleared.err.slice(0, 200)}` };
|
|
28863
|
+
}
|
|
28864
|
+
return { sha: made.out, head: head.out };
|
|
28865
|
+
}
|
|
28866
|
+
async function putBack(worktreePath, defaultBr, aside, taken) {
|
|
28867
|
+
let clash = null;
|
|
28868
|
+
if (taken.ok) {
|
|
28869
|
+
const applied = await git(worktreePath, "stash", "apply", aside.sha);
|
|
28870
|
+
if (applied.ok) {
|
|
28871
|
+
await git(worktreePath, "update-ref", "-d", CARRIED);
|
|
28872
|
+
return taken;
|
|
28873
|
+
}
|
|
28874
|
+
const unmerged = await git(worktreePath, "diff", "--name-only", "--diff-filter=U");
|
|
28875
|
+
clash = unmerged.out.split(`
|
|
28876
|
+
`).filter(Boolean);
|
|
28877
|
+
await git(worktreePath, "reset", "--hard", "-q", aside.head);
|
|
28878
|
+
}
|
|
28879
|
+
const back = await git(worktreePath, "stash", "apply", "--index", aside.sha);
|
|
28880
|
+
if (!back.ok) {
|
|
28881
|
+
return {
|
|
28882
|
+
ok: false,
|
|
28883
|
+
message: `sync stopped with the uncommitted changes set aside: git stash apply ${CARRIED} in the worktree brings them back`
|
|
28884
|
+
};
|
|
28885
|
+
}
|
|
28886
|
+
await git(worktreePath, "update-ref", "-d", CARRIED);
|
|
28887
|
+
if (!clash)
|
|
28888
|
+
return taken;
|
|
28889
|
+
const named = clash.length ? ` (${listed(clash)})` : "";
|
|
28890
|
+
return {
|
|
28891
|
+
ok: false,
|
|
28892
|
+
message: `the uncommitted changes here conflict with ${defaultBr}${named}: commit them first, then sync`
|
|
28893
|
+
};
|
|
28894
|
+
}
|
|
28895
|
+
async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
|
|
28896
|
+
if (!carry) {
|
|
28897
|
+
const cErr = await requireClean(worktreePath);
|
|
28898
|
+
if (cErr)
|
|
28899
|
+
return cErr;
|
|
28900
|
+
}
|
|
28480
28901
|
const { behind } = await aheadBehind(worktreePath, defaultBr);
|
|
28481
28902
|
if (behind === 0)
|
|
28482
28903
|
return { ok: true, message: `already up to date with ${defaultBr}` };
|
|
28904
|
+
const aside = carry ? await setAside(worktreePath) : null;
|
|
28905
|
+
if (aside && "message" in aside)
|
|
28906
|
+
return aside;
|
|
28907
|
+
const taken = await bringIn(worktreePath, defaultBr, own, behind, w);
|
|
28908
|
+
return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
|
|
28909
|
+
}
|
|
28910
|
+
async function bringIn(worktreePath, defaultBr, own, behind, w) {
|
|
28483
28911
|
if (own) {
|
|
28484
28912
|
w.step(`rebasing onto ${defaultBr}`);
|
|
28485
28913
|
const r = await w.git(worktreePath, ["rebase", defaultBr]);
|
|
28486
28914
|
if (!r.ok) {
|
|
28487
28915
|
await git(worktreePath, "rebase", "--abort");
|
|
28916
|
+
const files = overwritten(r.text);
|
|
28917
|
+
if (files.length)
|
|
28918
|
+
return { ok: false, message: untrackedInTheWay(defaultBr, files) };
|
|
28488
28919
|
const ask = `rebase onto ${defaultBr} conflicts: ask the agent to bring ${defaultBr} in and resolve them`;
|
|
28489
28920
|
return {
|
|
28490
28921
|
ok: false,
|
|
@@ -28497,6 +28928,9 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED) {
|
|
|
28497
28928
|
w.step(`merging ${defaultBr} in`);
|
|
28498
28929
|
const m = await w.git(worktreePath, ["merge", "--no-edit", defaultBr]);
|
|
28499
28930
|
if (!m.ok) {
|
|
28931
|
+
const files = overwritten(m.text);
|
|
28932
|
+
if (files.length)
|
|
28933
|
+
return { ok: false, message: untrackedInTheWay(defaultBr, files) };
|
|
28500
28934
|
return mergeFailure(worktreePath, m, `sync conflicts with ${defaultBr}: ask the agent to merge ${defaultBr} and resolve them`);
|
|
28501
28935
|
}
|
|
28502
28936
|
return { ok: true, message: `synced ${behind} commit(s) from ${defaultBr}` };
|
|
@@ -28508,14 +28942,18 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
|
|
|
28508
28942
|
const { ahead } = await aheadBehind(worktreePath, defaultBr);
|
|
28509
28943
|
if (ahead === 0)
|
|
28510
28944
|
return { ok: false, message: `nothing to merge: no commits ahead of ${defaultBr}` };
|
|
28511
|
-
const mErr = await requireMainReady(repoPath, defaultBr);
|
|
28945
|
+
const mErr = await requireMainReady(repoPath, defaultBr, method === "squash");
|
|
28512
28946
|
if (mErr)
|
|
28513
28947
|
return mErr;
|
|
28514
28948
|
if (method === "rebase") {
|
|
28515
28949
|
w.step(`moving ${defaultBr} onto the branch`);
|
|
28516
28950
|
const ff = await w.git(repoPath, ["merge", "--ff-only", branch]);
|
|
28517
|
-
if (!ff.ok)
|
|
28951
|
+
if (!ff.ok) {
|
|
28952
|
+
const files = overwritten(ff.text);
|
|
28953
|
+
if (files.length)
|
|
28954
|
+
return { ok: false, message: inTheWay(defaultBr, "landing", files) };
|
|
28518
28955
|
return { ok: false, message: `${branch} is not a fast-forward of ${defaultBr}: take ${defaultBr} in first` };
|
|
28956
|
+
}
|
|
28519
28957
|
return { ok: true, message: `${defaultBr} moved onto ${branch}` };
|
|
28520
28958
|
}
|
|
28521
28959
|
if (method === "squash") {
|
|
@@ -28534,8 +28972,12 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
|
|
|
28534
28972
|
}
|
|
28535
28973
|
w.step(`merging into ${defaultBr}`);
|
|
28536
28974
|
const m = await w.git(repoPath, ["merge", "--no-ff", "--no-edit", branch]);
|
|
28537
|
-
if (!m.ok)
|
|
28975
|
+
if (!m.ok) {
|
|
28976
|
+
const files = overwritten(m.text);
|
|
28977
|
+
if (files.length)
|
|
28978
|
+
return { ok: false, message: inTheWay(defaultBr, "landing", files) };
|
|
28538
28979
|
return mergeFailure(repoPath, m, `merge conflicts with ${defaultBr}: take ${defaultBr} in first`);
|
|
28980
|
+
}
|
|
28539
28981
|
return { ok: true, message: `merged ${branch} into ${defaultBr}` };
|
|
28540
28982
|
}
|
|
28541
28983
|
async function squashMessage(worktreePath, defaultBr, suggested) {
|
|
@@ -28561,22 +29003,11 @@ async function fastForwardFetched(repoPath, defaultBr) {
|
|
|
28561
29003
|
}
|
|
28562
29004
|
if (behind === 0)
|
|
28563
29005
|
return { ok: true, moved: false, message: "already up to date with origin" };
|
|
28564
|
-
if ((await statusFiles(repoPath)).length > 0) {
|
|
28565
|
-
return {
|
|
28566
|
-
ok: false,
|
|
28567
|
-
stale: "dirty",
|
|
28568
|
-
message: `${defaultBr} has uncommitted changes: commit or stash them there first`
|
|
28569
|
-
};
|
|
28570
|
-
}
|
|
28571
29006
|
const m = await git(repoPath, "merge", "--ff-only", "@{upstream}");
|
|
28572
29007
|
if (!m.ok) {
|
|
28573
|
-
const
|
|
29008
|
+
const files = overwritten(`${m.out}
|
|
28574
29009
|
${m.err}`);
|
|
28575
|
-
return
|
|
28576
|
-
ok: false,
|
|
28577
|
-
stale: "dirty",
|
|
28578
|
-
message: `${defaultBr} here has edits the pull would overwrite; commit or stash them first`
|
|
28579
|
-
} : { ok: false, stale: "diverged", message: `${defaultBr} has diverged from origin; reconcile it in a terminal` };
|
|
29010
|
+
return files.length ? { ok: false, stale: "dirty", message: inTheWay(defaultBr, "pull", files) } : { ok: false, stale: "diverged", message: `${defaultBr} has diverged from origin; reconcile it in a terminal` };
|
|
28580
29011
|
}
|
|
28581
29012
|
return { ok: true, moved: true, message: `pulled ${behind} commit(s) from origin` };
|
|
28582
29013
|
}
|
|
@@ -28753,8 +29184,8 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
28753
29184
|
}
|
|
28754
29185
|
|
|
28755
29186
|
// ../daemon/src/worktrees/archive.ts
|
|
28756
|
-
import { existsSync as
|
|
28757
|
-
import { basename as
|
|
29187
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
29188
|
+
import { basename as basename8, dirname as dirname10, join as join23 } from "path";
|
|
28758
29189
|
class WorktreeArchive {
|
|
28759
29190
|
dir;
|
|
28760
29191
|
records = new Map;
|
|
@@ -28763,7 +29194,7 @@ class WorktreeArchive {
|
|
|
28763
29194
|
mkdirSync7(dir, { recursive: true });
|
|
28764
29195
|
for (const id of readdirSync7(dir)) {
|
|
28765
29196
|
const file = this.filesOf(id).record;
|
|
28766
|
-
if (!
|
|
29197
|
+
if (!existsSync26(file))
|
|
28767
29198
|
continue;
|
|
28768
29199
|
try {
|
|
28769
29200
|
this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
|
|
@@ -28812,8 +29243,8 @@ class WorktreeArchive {
|
|
|
28812
29243
|
planFile(id, rel) {
|
|
28813
29244
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
28814
29245
|
return null;
|
|
28815
|
-
const file =
|
|
28816
|
-
if (!
|
|
29246
|
+
const file = join23(this.filesOf(id).plans, basename8(rel));
|
|
29247
|
+
if (!existsSync26(file))
|
|
28817
29248
|
return null;
|
|
28818
29249
|
try {
|
|
28819
29250
|
return readFileSync17(file, "utf8");
|
|
@@ -28823,13 +29254,13 @@ class WorktreeArchive {
|
|
|
28823
29254
|
}
|
|
28824
29255
|
}
|
|
28825
29256
|
filesOf(id) {
|
|
28826
|
-
const dir =
|
|
29257
|
+
const dir = join23(this.dir, id);
|
|
28827
29258
|
return {
|
|
28828
29259
|
dir,
|
|
28829
|
-
record:
|
|
28830
|
-
transcript:
|
|
28831
|
-
attachments:
|
|
28832
|
-
plans:
|
|
29260
|
+
record: join23(dir, "record.json"),
|
|
29261
|
+
transcript: join23(dir, "transcript.jsonl"),
|
|
29262
|
+
attachments: join23(dir, "attachments"),
|
|
29263
|
+
plans: join23(dir, "plans")
|
|
28833
29264
|
};
|
|
28834
29265
|
}
|
|
28835
29266
|
}
|
|
@@ -28848,12 +29279,13 @@ function summarize(r, repoId, transcript) {
|
|
|
28848
29279
|
...r.sessionId ? { sessionId: r.sessionId } : {},
|
|
28849
29280
|
...r.kept?.snapshot ? { uncommitted: true, ...r.kept.dirty ? { dirty: r.kept.dirty } : {} } : {},
|
|
28850
29281
|
...r.worktree.landed ? { landed: true } : {},
|
|
29282
|
+
...r.worktree.lands?.length ? { landedAt: r.worktree.lands[r.worktree.lands.length - 1]?.at } : {},
|
|
28851
29283
|
...r.cost !== undefined ? { cost: r.cost } : {},
|
|
28852
29284
|
...r.auto ? { auto: r.auto } : {}
|
|
28853
29285
|
};
|
|
28854
29286
|
}
|
|
28855
29287
|
function lastUsage(transcript) {
|
|
28856
|
-
if (!
|
|
29288
|
+
if (!existsSync26(transcript))
|
|
28857
29289
|
return null;
|
|
28858
29290
|
const lines = readFileSync17(transcript, "utf8").split(`
|
|
28859
29291
|
`);
|
|
@@ -28869,7 +29301,7 @@ function lastUsage(transcript) {
|
|
|
28869
29301
|
return null;
|
|
28870
29302
|
}
|
|
28871
29303
|
function firstPrompt(transcript) {
|
|
28872
|
-
if (!
|
|
29304
|
+
if (!existsSync26(transcript))
|
|
28873
29305
|
return;
|
|
28874
29306
|
for (const line of readFileSync17(transcript, "utf8").split(`
|
|
28875
29307
|
`)) {
|
|
@@ -28884,9 +29316,9 @@ function firstPrompt(transcript) {
|
|
|
28884
29316
|
return;
|
|
28885
29317
|
}
|
|
28886
29318
|
function move(from, to) {
|
|
28887
|
-
if (!
|
|
29319
|
+
if (!existsSync26(from))
|
|
28888
29320
|
return;
|
|
28889
|
-
mkdirSync7(
|
|
29321
|
+
mkdirSync7(dirname10(to), { recursive: true });
|
|
28890
29322
|
rmSync4(to, { recursive: true, force: true });
|
|
28891
29323
|
renameSync4(from, to);
|
|
28892
29324
|
}
|
|
@@ -28949,8 +29381,8 @@ class ArchivedGit {
|
|
|
28949
29381
|
}
|
|
28950
29382
|
|
|
28951
29383
|
// ../daemon/src/worktrees/spare.ts
|
|
28952
|
-
import { existsSync as
|
|
28953
|
-
import { basename as
|
|
29384
|
+
import { existsSync as existsSync27 } from "fs";
|
|
29385
|
+
import { basename as basename9, join as join24 } from "path";
|
|
28954
29386
|
class SparePool {
|
|
28955
29387
|
d;
|
|
28956
29388
|
inflight = new Map;
|
|
@@ -28969,7 +29401,7 @@ class SparePool {
|
|
|
28969
29401
|
if (!text.trim())
|
|
28970
29402
|
return;
|
|
28971
29403
|
const wt = this.d.state.worktree(boxId);
|
|
28972
|
-
if (wt?.kind !== "spare" || !
|
|
29404
|
+
if (wt?.kind !== "spare" || !existsSync27(wt.path))
|
|
28973
29405
|
return;
|
|
28974
29406
|
const agent = this.d.runtime.ensureAgent(wt).agent;
|
|
28975
29407
|
if (agent.runningAgent === null)
|
|
@@ -29014,11 +29446,11 @@ class SparePool {
|
|
|
29014
29446
|
return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
|
|
29015
29447
|
}
|
|
29016
29448
|
async record(repoId, repo) {
|
|
29017
|
-
const { id, dir } = freeSlot(
|
|
29449
|
+
const { id, dir } = freeSlot(join24(this.d.paths.worktreesDir, repo.name));
|
|
29018
29450
|
const wt = {
|
|
29019
29451
|
id,
|
|
29020
29452
|
repoId,
|
|
29021
|
-
path:
|
|
29453
|
+
path: join24(this.d.paths.worktreesDir, repo.name, dir),
|
|
29022
29454
|
branch: repo.defaultBranch,
|
|
29023
29455
|
kind: "spare",
|
|
29024
29456
|
phase: "reserved",
|
|
@@ -29033,7 +29465,7 @@ class SparePool {
|
|
|
29033
29465
|
const repoId = repo.id;
|
|
29034
29466
|
let cut = () => {};
|
|
29035
29467
|
const added = (async () => {
|
|
29036
|
-
if (
|
|
29468
|
+
if (existsSync27(wt.path))
|
|
29037
29469
|
return;
|
|
29038
29470
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
|
|
29039
29471
|
})();
|
|
@@ -29081,7 +29513,7 @@ class SparePool {
|
|
|
29081
29513
|
}
|
|
29082
29514
|
log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
|
|
29083
29515
|
this.d.carryDraft?.(wt.id, wt.repoId);
|
|
29084
|
-
if (
|
|
29516
|
+
if (existsSync27(wt.path)) {
|
|
29085
29517
|
await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
|
|
29086
29518
|
return;
|
|
29087
29519
|
}
|
|
@@ -29138,12 +29570,12 @@ class SparePool {
|
|
|
29138
29570
|
const running = this.inflight.get(repoId);
|
|
29139
29571
|
await running?.added.catch(() => {});
|
|
29140
29572
|
running?.cut();
|
|
29141
|
-
if (wt.kind !== "spare" || !
|
|
29573
|
+
if (wt.kind !== "spare" || !existsSync27(wt.path))
|
|
29142
29574
|
return null;
|
|
29143
29575
|
}
|
|
29144
29576
|
await this.refreshing.get(repoId);
|
|
29145
29577
|
const repo = this.d.state.requireRepo(repoId);
|
|
29146
|
-
const branch = `toyon/${
|
|
29578
|
+
const branch = `toyon/${basename9(wt.path)}`;
|
|
29147
29579
|
await withRepoLock(repo.path, () => gitOrThrow(wt.path, "switch", "-c", branch));
|
|
29148
29580
|
wt.kind = "worktree";
|
|
29149
29581
|
delete wt.phase;
|
|
@@ -29526,8 +29958,8 @@ class WorktreeService {
|
|
|
29526
29958
|
if (!await isGitRepo(repo.path)) {
|
|
29527
29959
|
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.`);
|
|
29528
29960
|
}
|
|
29529
|
-
const { id, dir } = freeSlot(
|
|
29530
|
-
const wtPath =
|
|
29961
|
+
const { id, dir } = freeSlot(join25(this.d.paths.worktreesDir, repo.name));
|
|
29962
|
+
const wtPath = join25(this.d.paths.worktreesDir, repo.name, dir);
|
|
29531
29963
|
const branch = `toyon/${dir}`;
|
|
29532
29964
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
29533
29965
|
const wt = {
|
|
@@ -29593,7 +30025,7 @@ class WorktreeService {
|
|
|
29593
30025
|
};
|
|
29594
30026
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
29595
30027
|
const restored = (carried.ok ? carried.out.split(`
|
|
29596
|
-
`).filter(Boolean) : []).filter((p) => !
|
|
30028
|
+
`).filter(Boolean) : []).filter((p) => !existsSync28(join25(wt.path, p)));
|
|
29597
30029
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
29598
30030
|
if (applied.ok) {
|
|
29599
30031
|
const ref = await entry();
|
|
@@ -29629,9 +30061,9 @@ class WorktreeService {
|
|
|
29629
30061
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
29630
30062
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
29631
30063
|
let slug = branchSlug(title) || "branch";
|
|
29632
|
-
if (
|
|
30064
|
+
if (existsSync28(join25(this.d.paths.worktreesDir, repo.name, slug)))
|
|
29633
30065
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
29634
|
-
const wtPath =
|
|
30066
|
+
const wtPath = join25(this.d.paths.worktreesDir, repo.name, slug);
|
|
29635
30067
|
await withRepoLock(repo.path, async () => {
|
|
29636
30068
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
29637
30069
|
if (out)
|
|
@@ -29877,7 +30309,7 @@ class WorktreeService {
|
|
|
29877
30309
|
}
|
|
29878
30310
|
async teardown(wt, repo) {
|
|
29879
30311
|
const cmds = repo.config.teardown ?? [];
|
|
29880
|
-
if (cmds.length === 0 || !
|
|
30312
|
+
if (cmds.length === 0 || !existsSync28(wt.path))
|
|
29881
30313
|
return;
|
|
29882
30314
|
for (const cmd of cmds) {
|
|
29883
30315
|
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") });
|
|
@@ -29886,7 +30318,7 @@ class WorktreeService {
|
|
|
29886
30318
|
}
|
|
29887
30319
|
}
|
|
29888
30320
|
async keep(repo, wt) {
|
|
29889
|
-
const index =
|
|
30321
|
+
const index = join25(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
29890
30322
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
29891
30323
|
if (!kept)
|
|
29892
30324
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -29918,16 +30350,16 @@ class WorktreeService {
|
|
|
29918
30350
|
throw new Error(`git worktree remove --force ${wt.path} failed (${r.exit}): ${r.err}`);
|
|
29919
30351
|
if (await deleteTree(wt.path))
|
|
29920
30352
|
return;
|
|
29921
|
-
await run("chmod", ["-R", "u+w", wt.path],
|
|
30353
|
+
await run("chmod", ["-R", "u+w", wt.path], dirname11(wt.path));
|
|
29922
30354
|
if (!await deleteTree(wt.path))
|
|
29923
30355
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
29924
30356
|
}
|
|
29925
30357
|
plansScratch(worktreeId) {
|
|
29926
|
-
return
|
|
30358
|
+
return join25(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
29927
30359
|
}
|
|
29928
30360
|
keepPlans(wt) {
|
|
29929
|
-
const from =
|
|
29930
|
-
if (!
|
|
30361
|
+
const from = join25(wt.path, PLANS_DIR);
|
|
30362
|
+
if (!existsSync28(from))
|
|
29931
30363
|
return;
|
|
29932
30364
|
try {
|
|
29933
30365
|
cpSync(from, this.plansScratch(wt.id), { recursive: true });
|
|
@@ -30015,7 +30447,7 @@ class WorktreeService {
|
|
|
30015
30447
|
}
|
|
30016
30448
|
archivedAttachment(archiveId, file) {
|
|
30017
30449
|
const files = this.archive.chatFiles(archiveId);
|
|
30018
|
-
return files && isAttachmentFile(file) ?
|
|
30450
|
+
return files && isAttachmentFile(file) ? join25(files.attachments, file) : null;
|
|
30019
30451
|
}
|
|
30020
30452
|
archivedGit(archiveId) {
|
|
30021
30453
|
const rec = this.archive.get(archiveId);
|
|
@@ -30058,7 +30490,7 @@ class WorktreeService {
|
|
|
30058
30490
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
30059
30491
|
if (rec.auto)
|
|
30060
30492
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
30061
|
-
const path =
|
|
30493
|
+
const path = existsSync28(old.path) ? join25(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
|
|
30062
30494
|
const branch = await withRepoLock(repo.path, async () => {
|
|
30063
30495
|
if (!await commitOf(repo.path, archiveRef(old.id))) {
|
|
30064
30496
|
throw new UserError(`${old.title}'s kept commits are gone from git`);
|
|
@@ -30095,7 +30527,7 @@ class WorktreeService {
|
|
|
30095
30527
|
...createdBy ? { createdBy } : {}
|
|
30096
30528
|
};
|
|
30097
30529
|
try {
|
|
30098
|
-
this.archive.take(old.id, { ...this.chatFiles(old.id), plans:
|
|
30530
|
+
this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join25(path, PLANS_DIR) });
|
|
30099
30531
|
} catch (e) {
|
|
30100
30532
|
log.warn(old.id, "could not move its chat back out of the archive", e);
|
|
30101
30533
|
}
|
|
@@ -30106,6 +30538,13 @@ class WorktreeService {
|
|
|
30106
30538
|
log.warn(old.id, `could not drop its archive ref: ${dropped.err}`);
|
|
30107
30539
|
this.countsCache.delete(wt.id);
|
|
30108
30540
|
this.launch(wt, repo, repo.path);
|
|
30541
|
+
this.d.runtime.ensureAgent(wt).agent.note({
|
|
30542
|
+
type: "restored",
|
|
30543
|
+
archivedAt: rec.archivedAt,
|
|
30544
|
+
branch,
|
|
30545
|
+
uncommitted: !!kept.snapshot,
|
|
30546
|
+
ts: Date.now()
|
|
30547
|
+
});
|
|
30109
30548
|
if (message)
|
|
30110
30549
|
this.d.runtime.ensureAgent(wt).agent.send(message.text, { attachments: message.attachments });
|
|
30111
30550
|
this.d.hub.emit("archiveChanged", repo.id);
|
|
@@ -30117,12 +30556,12 @@ class WorktreeService {
|
|
|
30117
30556
|
return;
|
|
30118
30557
|
const repo = this.repoOf(rec);
|
|
30119
30558
|
const repoPath = repo?.path ?? rec.repoPath;
|
|
30120
|
-
if (rec.kept &&
|
|
30559
|
+
if (rec.kept && existsSync28(repoPath)) {
|
|
30121
30560
|
const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
|
|
30122
30561
|
if (!r.ok)
|
|
30123
30562
|
log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
|
|
30124
30563
|
}
|
|
30125
|
-
if (rec.worktree.lands?.length &&
|
|
30564
|
+
if (rec.worktree.lands?.length && existsSync28(repoPath))
|
|
30126
30565
|
await dropLandRefs(repoPath, archiveId);
|
|
30127
30566
|
this.archive.delete(archiveId);
|
|
30128
30567
|
this.d.drafts?.drop(archiveId);
|
|
@@ -30222,9 +30661,9 @@ class WorktreeService {
|
|
|
30222
30661
|
}
|
|
30223
30662
|
}
|
|
30224
30663
|
async mirrorBaseIgnore(repo) {
|
|
30225
|
-
const src =
|
|
30226
|
-
const tracked =
|
|
30227
|
-
const mirror =
|
|
30664
|
+
const src = join25(repo.path, ".gitignore");
|
|
30665
|
+
const tracked = existsSync28(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
|
|
30666
|
+
const mirror = existsSync28(src) && !tracked;
|
|
30228
30667
|
const lines = mirror ? readFileSync18(src, "utf8").split(`
|
|
30229
30668
|
`).filter((l) => l.trim() !== "") : [];
|
|
30230
30669
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
@@ -30237,9 +30676,9 @@ class WorktreeService {
|
|
|
30237
30676
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
30238
30677
|
run.finish(0);
|
|
30239
30678
|
}
|
|
30240
|
-
const srcNm =
|
|
30241
|
-
const dstNm =
|
|
30242
|
-
if (
|
|
30679
|
+
const srcNm = join25(depsSource, "node_modules");
|
|
30680
|
+
const dstNm = join25(wt.path, "node_modules");
|
|
30681
|
+
if (existsSync28(srcNm) && !existsSync28(dstNm)) {
|
|
30243
30682
|
const r = await cloneTree(srcNm, dstNm);
|
|
30244
30683
|
if (r.ok)
|
|
30245
30684
|
this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
|
|
@@ -30247,10 +30686,10 @@ class WorktreeService {
|
|
|
30247
30686
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
30248
30687
|
}
|
|
30249
30688
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
30250
|
-
const src =
|
|
30251
|
-
if (!
|
|
30689
|
+
const src = join25(depsSource, f);
|
|
30690
|
+
if (!existsSync28(src) || existsSync28(join25(wt.path, f)))
|
|
30252
30691
|
continue;
|
|
30253
|
-
const r = await run("cp", [src,
|
|
30692
|
+
const r = await run("cp", [src, join25(wt.path, f)], wt.path);
|
|
30254
30693
|
if (r.ok)
|
|
30255
30694
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
30256
30695
|
else
|
|
@@ -30418,12 +30857,24 @@ ${wt.landing.body}` : s;
|
|
|
30418
30857
|
const { wt, repo } = this.landable(worktreeId, "land");
|
|
30419
30858
|
const out = await this.ship(wt.id, "land", async () => {
|
|
30420
30859
|
await this.followBranch(wt);
|
|
30860
|
+
const moved = await this.movedMidTurn(wt);
|
|
30861
|
+
if (moved)
|
|
30862
|
+
return { result: moved };
|
|
30421
30863
|
return this.landOp(wt, repo, message);
|
|
30422
30864
|
});
|
|
30423
30865
|
if (out.result.ok && out.archiveIds)
|
|
30424
30866
|
this.noteLanded(wt, out.result.message, out.archiveIds);
|
|
30425
30867
|
return out;
|
|
30426
30868
|
}
|
|
30869
|
+
async movedMidTurn(wt) {
|
|
30870
|
+
const status = this.d.runtime.agentFor(wt.id)?.status;
|
|
30871
|
+
if (status !== "working" && status !== "waiting")
|
|
30872
|
+
return null;
|
|
30873
|
+
const l = wt.landing;
|
|
30874
|
+
if (!l || l.fingerprint === await treeFingerprint(wt.path))
|
|
30875
|
+
return null;
|
|
30876
|
+
return { ok: false, message: "the agent has changed files since the check: land once its turn ends" };
|
|
30877
|
+
}
|
|
30427
30878
|
noteLanded(wt, message, archiveIds) {
|
|
30428
30879
|
this.d.runtime.ensureAgent(wt).agent.note({ type: "landed", message, archiveIds, ts: Date.now() });
|
|
30429
30880
|
}
|
|
@@ -30695,6 +31146,10 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30695
31146
|
throw new UserError(`${r.name} is held by another tool`);
|
|
30696
31147
|
const repo = this.d.state.requireRepo(r.repoId);
|
|
30697
31148
|
const own = r.wt ? hasOwnBranch(r.wt) : false;
|
|
31149
|
+
const carry = !!r.wt;
|
|
31150
|
+
const status = this.d.runtime.agentFor(worktreeId)?.status;
|
|
31151
|
+
if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0)
|
|
31152
|
+
throw new UserError(`${r.name}'s agent is mid-turn with uncommitted changes: sync when it is done`);
|
|
30698
31153
|
const w = r.wt ? this.landWatch(r.wt) : UNWATCHED;
|
|
30699
31154
|
const result = await this.ship(worktreeId, "sync-main", async () => {
|
|
30700
31155
|
if (r.wt) {
|
|
@@ -30702,7 +31157,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30702
31157
|
if (!fetched.ok)
|
|
30703
31158
|
return fetched;
|
|
30704
31159
|
}
|
|
30705
|
-
const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w));
|
|
31160
|
+
const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w, carry));
|
|
30706
31161
|
if (taken.ok) {
|
|
30707
31162
|
this.headMoved(worktreeId);
|
|
30708
31163
|
if (r.wt)
|
|
@@ -30719,12 +31174,6 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
30719
31174
|
const repo = this.d.state.requireRepo(wt.repoId);
|
|
30720
31175
|
const w = { step: (name) => this.step(worktreeId, name), git: UNWATCHED.git };
|
|
30721
31176
|
return this.ship(worktreeId, "pull-main", async () => {
|
|
30722
|
-
if ((await statusFiles(repo.path)).length > 0) {
|
|
30723
|
-
return {
|
|
30724
|
-
ok: false,
|
|
30725
|
-
message: `${repo.defaultBranch} has uncommitted changes: commit or stash them there first`
|
|
30726
|
-
};
|
|
30727
|
-
}
|
|
30728
31177
|
const result = await this.trunk.pull(repo.id, w);
|
|
30729
31178
|
if (result.ok) {
|
|
30730
31179
|
this.invalidateCounts();
|
|
@@ -31153,7 +31602,7 @@ class ArchiveSweep {
|
|
|
31153
31602
|
// ../daemon/src/index.ts
|
|
31154
31603
|
process.on("unhandledRejection", (e) => log.error("daemon", "unhandled rejection", e));
|
|
31155
31604
|
process.on("uncaughtException", (e) => log.error("daemon", "uncaught exception", e));
|
|
31156
|
-
var here =
|
|
31605
|
+
var here = dirname12(fileURLToPath(import.meta.url));
|
|
31157
31606
|
var {
|
|
31158
31607
|
shellDist: SHELL_DIST,
|
|
31159
31608
|
bridgeJs: BRIDGE_JS,
|
|
@@ -31291,9 +31740,12 @@ var restarter = new Restarter({
|
|
|
31291
31740
|
hub,
|
|
31292
31741
|
state,
|
|
31293
31742
|
runtime,
|
|
31294
|
-
refusal: () => {
|
|
31743
|
+
refusal: async () => {
|
|
31295
31744
|
const can = restartable();
|
|
31296
|
-
|
|
31745
|
+
if (!can.ok)
|
|
31746
|
+
return can.reason;
|
|
31747
|
+
const runs = await replacementRuns();
|
|
31748
|
+
return runs.ok ? null : runs.reason;
|
|
31297
31749
|
},
|
|
31298
31750
|
go: () => {
|
|
31299
31751
|
setTimeout(() => fireAndForget("daemon", shutdown("restart", { respawn: true }), "restart"), 100);
|
|
@@ -31309,6 +31761,7 @@ var update = new UpdateService({
|
|
|
31309
31761
|
restarter,
|
|
31310
31762
|
installed: async () => installedPackage ? readVersion(installedPackage) : null,
|
|
31311
31763
|
latest: latestVersion,
|
|
31764
|
+
registry: npmRegistry,
|
|
31312
31765
|
command: (version) => installCommand(method, version),
|
|
31313
31766
|
install: runInstall,
|
|
31314
31767
|
busy: () => runtime.anyBusy(),
|
|
@@ -31440,7 +31893,12 @@ async function shutdown(signal, opts = {}) {
|
|
|
31440
31893
|
if (opts.respawn) {
|
|
31441
31894
|
await Bun.sleep(250);
|
|
31442
31895
|
log.info("daemon", "starting the replacement");
|
|
31443
|
-
|
|
31896
|
+
try {
|
|
31897
|
+
respawn(paths.logFile);
|
|
31898
|
+
} catch (e) {
|
|
31899
|
+
log.error("daemon", "the replacement did not start; run `toyon` to start it again", e);
|
|
31900
|
+
process.exit(1);
|
|
31901
|
+
}
|
|
31444
31902
|
}
|
|
31445
31903
|
process.exit(0);
|
|
31446
31904
|
}
|