toyon 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bridge.js +1 -1
- package/dist/cli.js +48 -66
- package/dist/daemon.js +1108 -845
- package/dist/shell/assets/{Editor-ofBOHWJt.js → Editor-Gzhl3Oqu.js} +3 -3
- package/dist/shell/assets/{XTerm-C_Gv5uo2.js → XTerm-D6TL52yQ.js} +1 -1
- package/dist/shell/assets/{cssMode-LqxC-sXw.js → cssMode-BfNm0a39.js} +1 -1
- package/dist/shell/assets/{freemarker2-DUqPZFAL.js → freemarker2-DhiwR6KJ.js} +1 -1
- package/dist/shell/assets/{grammar-CFa79rtH.js → grammar-thv9InXF.js} +1 -1
- package/dist/shell/assets/{handlebars-CF9us5mA.js → handlebars-BbkhCZXb.js} +1 -1
- package/dist/shell/assets/{html-SO575qLW.js → html-yzhYlR33.js} +1 -1
- package/dist/shell/assets/{htmlMode-XUsZRqmU.js → htmlMode-DPICAUY8.js} +1 -1
- package/dist/shell/assets/index-BYBd8f7P.css +1 -0
- package/dist/shell/assets/index-DJBEbneL.js +267 -0
- package/dist/shell/assets/{javascript-4ER2NhcO.js → javascript-D9eBjR4g.js} +1 -1
- package/dist/shell/assets/{jsonMode-D-UOjsYN.js → jsonMode-cNulMi5B.js} +1 -1
- package/dist/shell/assets/{liquid-CMXc0SdF.js → liquid-C_SKIx1n.js} +1 -1
- package/dist/shell/assets/{lspLanguageFeatures-D4Z0LyWk.js → lspLanguageFeatures-DNU8BRHj.js} +1 -1
- package/dist/shell/assets/{mdx-Dc4E4gPJ.js → mdx-Bt4opbQO.js} +1 -1
- package/dist/shell/assets/{python-DhCnlsga.js → python-Crb-EuXe.js} +1 -1
- package/dist/shell/assets/{razor-CGRu_Zyc.js → razor-D3BlkbYO.js} +1 -1
- package/dist/shell/assets/{tsMode-BoDg-EAl.js → tsMode-BYuAS95U.js} +1 -1
- package/dist/shell/assets/{xml-Dl50xhA0.js → xml-G5xafDqw.js} +1 -1
- package/dist/shell/assets/{yaml-CoqiVhBi.js → yaml-CRp1x3nR.js} +1 -1
- package/dist/shell/index.html +17 -10
- package/dist/shell/manifest.json +1 -1
- package/package.json +1 -1
- package/dist/shell/assets/index-BYJYiHrs.css +0 -1
- package/dist/shell/assets/index-Brx-QOIQ.js +0 -266
package/dist/daemon.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../daemon/src/index.ts
|
|
3
|
-
import { rmSync as rmSync9, writeFileSync as
|
|
4
|
-
import { dirname as dirname13, join as
|
|
3
|
+
import { rmSync as rmSync9, writeFileSync as writeFileSync13 } from "fs";
|
|
4
|
+
import { dirname as dirname13, join as join31 } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
7
7
|
// ../shared/src/protocol/events.ts
|
|
@@ -358,10 +358,13 @@ var DAEMON_FILES = {
|
|
|
358
358
|
};
|
|
359
359
|
function machineLabel(hostname, remote) {
|
|
360
360
|
const of = (name) => name.split(".")[0]?.toLowerCase() ?? "";
|
|
361
|
-
if (remote && isTailnetName(remote.host))
|
|
361
|
+
if (remote && (isTailnetName(remote.host) || isFlyName(remote.host)))
|
|
362
362
|
return of(remote.host) || of(hostname);
|
|
363
363
|
return of(hostname) || "toyon";
|
|
364
364
|
}
|
|
365
|
+
function isFlyName(host) {
|
|
366
|
+
return host.toLowerCase().endsWith(".fly.dev");
|
|
367
|
+
}
|
|
365
368
|
function isShellOrigin(origin) {
|
|
366
369
|
let url;
|
|
367
370
|
try {
|
|
@@ -6599,7 +6602,7 @@ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
|
|
|
6599
6602
|
}
|
|
6600
6603
|
|
|
6601
6604
|
// ../shared/src/protocol/limits.ts
|
|
6602
|
-
var PROTOCOL_VERSION =
|
|
6605
|
+
var PROTOCOL_VERSION = 107;
|
|
6603
6606
|
var PAGE_ERROR_MAX_CHARS = 2000;
|
|
6604
6607
|
var PAGE_TITLE_MAX_CHARS = 1000;
|
|
6605
6608
|
var RENDER_ERRORS_MAX = 10;
|
|
@@ -6924,6 +6927,7 @@ var clientMsgSchema = discriminatedUnion("t", [
|
|
|
6924
6927
|
object({ t: literal("commit"), worktreeId: id, message: string2().max(5000) }),
|
|
6925
6928
|
object({ t: literal("graft"), targetId: id, sourceIds: array(id).min(1).max(20) }),
|
|
6926
6929
|
object({ t: literal("sync-main"), worktreeId: id }),
|
|
6930
|
+
object({ t: literal("unqueue-sync"), worktreeId: id }),
|
|
6927
6931
|
object({ t: literal("run-after-land"), repoId: id }),
|
|
6928
6932
|
object({ t: literal("restart-daemon") }),
|
|
6929
6933
|
object({ t: literal("forget-machine"), origin: string2().max(2048) }),
|
|
@@ -8406,7 +8410,7 @@ function readinessOf(json) {
|
|
|
8406
8410
|
line: "this machine is waiting to be approved in the Tailscale admin console"
|
|
8407
8411
|
};
|
|
8408
8412
|
case "Stopped":
|
|
8409
|
-
return { state: "
|
|
8413
|
+
return { state: "disconnected", name, line: "Tailscale is disconnected; run `tailscale up`, then this again" };
|
|
8410
8414
|
default:
|
|
8411
8415
|
return {
|
|
8412
8416
|
state: "not-running",
|
|
@@ -8524,7 +8528,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
|
|
|
8524
8528
|
// ../daemon/package.json
|
|
8525
8529
|
var package_default = {
|
|
8526
8530
|
name: "@toyon/daemon",
|
|
8527
|
-
version: "0.
|
|
8531
|
+
version: "0.14.0",
|
|
8528
8532
|
private: true,
|
|
8529
8533
|
type: "module",
|
|
8530
8534
|
main: "src/index.ts",
|
|
@@ -14848,6 +14852,562 @@ function unformatOutput(output) {
|
|
|
14848
14852
|
|
|
14849
14853
|
// ../daemon/src/agent/policy.ts
|
|
14850
14854
|
import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
|
|
14855
|
+
|
|
14856
|
+
// ../daemon/src/agent/acp/background.ts
|
|
14857
|
+
import { existsSync as existsSync10, statSync as statSync4 } from "fs";
|
|
14858
|
+
import { open as open2 } from "fs/promises";
|
|
14859
|
+
import { homedir as homedir5 } from "os";
|
|
14860
|
+
import { basename as basename3, dirname as dirname4, join as join8 } from "path";
|
|
14861
|
+
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
14862
|
+
function backgroundStart(input, output) {
|
|
14863
|
+
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
14864
|
+
if (flag !== true)
|
|
14865
|
+
return null;
|
|
14866
|
+
const m = STARTED.exec(output);
|
|
14867
|
+
return m ? { taskId: m[1], file: m[2] } : null;
|
|
14868
|
+
}
|
|
14869
|
+
function spawnDetached(output) {
|
|
14870
|
+
return output.includes("Async agent launched successfully");
|
|
14871
|
+
}
|
|
14872
|
+
function claudeConfigDir() {
|
|
14873
|
+
return process.env.CLAUDE_CONFIG_DIR || join8(homedir5(), ".claude");
|
|
14874
|
+
}
|
|
14875
|
+
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
14876
|
+
const tasks = dirname4(file);
|
|
14877
|
+
if (basename3(tasks) !== "tasks")
|
|
14878
|
+
return null;
|
|
14879
|
+
const session = dirname4(tasks);
|
|
14880
|
+
const path = join8(configDir, "projects", basename3(dirname4(session)), `${basename3(session)}.jsonl`);
|
|
14881
|
+
return existsSync10(path) ? path : null;
|
|
14882
|
+
}
|
|
14883
|
+
function taskEnd(text, toolId) {
|
|
14884
|
+
const at = text.indexOf(`<tool-use-id>${toolId}</tool-use-id>`);
|
|
14885
|
+
if (at === -1)
|
|
14886
|
+
return null;
|
|
14887
|
+
const tail = text.slice(at, at + 2000);
|
|
14888
|
+
const status = /<status>(\w+)<\/status>/.exec(tail)?.[1] ?? "completed";
|
|
14889
|
+
const exit = /exit code (\d+)/.exec(tail)?.[1];
|
|
14890
|
+
return { status, exit: exit === undefined ? null : Number(exit) };
|
|
14891
|
+
}
|
|
14892
|
+
var POLL_MS = 500;
|
|
14893
|
+
var DEFAULT_BASH_TIMEOUT_MS = 120000;
|
|
14894
|
+
var GRACE_MS = 60000;
|
|
14895
|
+
var READ_CHUNK = 1 << 20;
|
|
14896
|
+
var CARRY = 4000;
|
|
14897
|
+
|
|
14898
|
+
class BackgroundTasks {
|
|
14899
|
+
deps;
|
|
14900
|
+
watched = new Map;
|
|
14901
|
+
constructor(deps) {
|
|
14902
|
+
this.deps = deps;
|
|
14903
|
+
}
|
|
14904
|
+
get size() {
|
|
14905
|
+
return this.watched.size;
|
|
14906
|
+
}
|
|
14907
|
+
start(toolId, start, input) {
|
|
14908
|
+
const logPath = sessionLogFor(start.file, this.deps.configDir);
|
|
14909
|
+
if (!logPath)
|
|
14910
|
+
return false;
|
|
14911
|
+
const timeout = input?.timeout;
|
|
14912
|
+
const ceiling = (typeof timeout === "number" ? timeout : DEFAULT_BASH_TIMEOUT_MS) + (this.deps.graceMs ?? GRACE_MS);
|
|
14913
|
+
const w = {
|
|
14914
|
+
toolId,
|
|
14915
|
+
file: start.file,
|
|
14916
|
+
log: logPath,
|
|
14917
|
+
read: 0,
|
|
14918
|
+
logRead: statSync4(logPath).size,
|
|
14919
|
+
carry: "",
|
|
14920
|
+
decoder: new TextDecoder,
|
|
14921
|
+
text: "",
|
|
14922
|
+
truncated: false,
|
|
14923
|
+
ceiling: Date.now() + ceiling,
|
|
14924
|
+
timer: null
|
|
14925
|
+
};
|
|
14926
|
+
this.watched.set(toolId, w);
|
|
14927
|
+
this.schedule(w);
|
|
14928
|
+
return true;
|
|
14929
|
+
}
|
|
14930
|
+
endAll(reason) {
|
|
14931
|
+
for (const w of [...this.watched.values()]) {
|
|
14932
|
+
fireAndForget("background", this.finish(w, null, reason, true), `ending ${w.toolId}`);
|
|
14933
|
+
}
|
|
14934
|
+
}
|
|
14935
|
+
schedule(w) {
|
|
14936
|
+
w.timer = setTimeout(() => this.poll(w), this.deps.pollMs ?? POLL_MS);
|
|
14937
|
+
w.timer.unref?.();
|
|
14938
|
+
}
|
|
14939
|
+
async poll(w) {
|
|
14940
|
+
w.timer = null;
|
|
14941
|
+
try {
|
|
14942
|
+
await this.drain(w);
|
|
14943
|
+
const fresh = await readSince(w.log, w.logRead);
|
|
14944
|
+
w.logRead += fresh.length;
|
|
14945
|
+
const text = w.carry + fresh.toString("utf8");
|
|
14946
|
+
w.carry = text.slice(-CARRY);
|
|
14947
|
+
const end = fresh.length > 0 ? taskEnd(text, w.toolId) : null;
|
|
14948
|
+
if (end)
|
|
14949
|
+
return this.finish(w, end);
|
|
14950
|
+
if (Date.now() >= w.ceiling)
|
|
14951
|
+
return this.finish(w, null, "no word of its end; the row stops watching");
|
|
14952
|
+
} catch (e) {
|
|
14953
|
+
log.warn("background", `watching ${w.toolId} failed`, e);
|
|
14954
|
+
}
|
|
14955
|
+
if (this.watched.has(w.toolId))
|
|
14956
|
+
this.schedule(w);
|
|
14957
|
+
}
|
|
14958
|
+
async drain(w) {
|
|
14959
|
+
const fresh = await readSince(w.file, w.read);
|
|
14960
|
+
w.read += fresh.length;
|
|
14961
|
+
if (fresh.length === 0 || w.truncated)
|
|
14962
|
+
return;
|
|
14963
|
+
const chunk = w.decoder.decode(fresh, { stream: true });
|
|
14964
|
+
const take = chunk.slice(0, OUTPUT_CAP - w.text.length);
|
|
14965
|
+
w.text += take;
|
|
14966
|
+
if (take.length < chunk.length)
|
|
14967
|
+
w.truncated = true;
|
|
14968
|
+
if (take)
|
|
14969
|
+
this.deps.note({ type: "tool-delta", toolId: w.toolId, text: take });
|
|
14970
|
+
}
|
|
14971
|
+
async finish(w, end, note, killed = false) {
|
|
14972
|
+
if (!this.watched.delete(w.toolId))
|
|
14973
|
+
return;
|
|
14974
|
+
if (w.timer)
|
|
14975
|
+
clearTimeout(w.timer);
|
|
14976
|
+
try {
|
|
14977
|
+
await this.drain(w);
|
|
14978
|
+
} catch (e) {
|
|
14979
|
+
log.warn("background", `reading the last of ${w.toolId} failed`, e);
|
|
14980
|
+
}
|
|
14981
|
+
const exit = end ? end.exit ?? (end.status === "completed" ? 0 : null) : null;
|
|
14982
|
+
const lines = [formatOutput(w.text, exit ?? 0, w.truncated)];
|
|
14983
|
+
if (end && exit === null)
|
|
14984
|
+
lines.push(end.status);
|
|
14985
|
+
if (note)
|
|
14986
|
+
lines.push(note);
|
|
14987
|
+
this.deps.note({
|
|
14988
|
+
type: "tool-end",
|
|
14989
|
+
toolId: w.toolId,
|
|
14990
|
+
output: lines.filter(Boolean).join(`
|
|
14991
|
+
`),
|
|
14992
|
+
isError: killed || end !== null && (exit !== 0 || end.status !== "completed")
|
|
14993
|
+
});
|
|
14994
|
+
this.deps.onEnd?.();
|
|
14995
|
+
}
|
|
14996
|
+
}
|
|
14997
|
+
async function readSince(path, offset) {
|
|
14998
|
+
let fh;
|
|
14999
|
+
try {
|
|
15000
|
+
fh = await open2(path, "r");
|
|
15001
|
+
} catch (e) {
|
|
15002
|
+
if (e.code === "ENOENT")
|
|
15003
|
+
return Buffer.alloc(0);
|
|
15004
|
+
throw e;
|
|
15005
|
+
}
|
|
15006
|
+
try {
|
|
15007
|
+
const { size } = await fh.stat();
|
|
15008
|
+
if (size <= offset)
|
|
15009
|
+
return Buffer.alloc(0);
|
|
15010
|
+
const buf = Buffer.alloc(Math.min(size - offset, READ_CHUNK));
|
|
15011
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, offset);
|
|
15012
|
+
return buf.subarray(0, bytesRead);
|
|
15013
|
+
} finally {
|
|
15014
|
+
await fh.close();
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
|
|
15018
|
+
// ../daemon/src/agent/acp/diff.ts
|
|
15019
|
+
function diffOps(a, b) {
|
|
15020
|
+
return diffSeq(a, b).map((op) => ({ mark: op.mark, text: op.value }));
|
|
15021
|
+
}
|
|
15022
|
+
function unifiedDiff(oldText, newText, context = 3) {
|
|
15023
|
+
const a = oldText === "" ? [] : oldText.split(`
|
|
15024
|
+
`);
|
|
15025
|
+
const b = newText === "" ? [] : newText.split(`
|
|
15026
|
+
`);
|
|
15027
|
+
const ops = diffOps(a, b);
|
|
15028
|
+
const oldNo = [];
|
|
15029
|
+
const newNo = [];
|
|
15030
|
+
let o = 1;
|
|
15031
|
+
let n = 1;
|
|
15032
|
+
for (const op of ops) {
|
|
15033
|
+
oldNo.push(o);
|
|
15034
|
+
newNo.push(n);
|
|
15035
|
+
if (op.mark !== "+")
|
|
15036
|
+
o++;
|
|
15037
|
+
if (op.mark !== "-")
|
|
15038
|
+
n++;
|
|
15039
|
+
}
|
|
15040
|
+
const out = [];
|
|
15041
|
+
let k = 0;
|
|
15042
|
+
while (k < ops.length) {
|
|
15043
|
+
if (ops[k]?.mark === " ") {
|
|
15044
|
+
k++;
|
|
15045
|
+
continue;
|
|
15046
|
+
}
|
|
15047
|
+
const start = k;
|
|
15048
|
+
let end = k;
|
|
15049
|
+
let j = k;
|
|
15050
|
+
while (j < ops.length) {
|
|
15051
|
+
if (ops[j]?.mark !== " ") {
|
|
15052
|
+
end = j;
|
|
15053
|
+
j++;
|
|
15054
|
+
continue;
|
|
15055
|
+
}
|
|
15056
|
+
let run = 0;
|
|
15057
|
+
while (j + run < ops.length && ops[j + run]?.mark === " ")
|
|
15058
|
+
run++;
|
|
15059
|
+
if (run > context * 2 || j + run >= ops.length)
|
|
15060
|
+
break;
|
|
15061
|
+
j += run;
|
|
15062
|
+
}
|
|
15063
|
+
const from = Math.max(0, start - context);
|
|
15064
|
+
const to = Math.min(ops.length, end + context + 1);
|
|
15065
|
+
const body = ops.slice(from, to);
|
|
15066
|
+
const oldCount = body.filter((op) => op.mark !== "+").length;
|
|
15067
|
+
const newCount = body.filter((op) => op.mark !== "-").length;
|
|
15068
|
+
out.push(`@@ -${oldNo[from]},${oldCount} +${newNo[from]},${newCount} @@`, ...body.map((op) => `${op.mark}${op.text}`));
|
|
15069
|
+
k = to;
|
|
15070
|
+
}
|
|
15071
|
+
return out.join(`
|
|
15072
|
+
`);
|
|
15073
|
+
}
|
|
15074
|
+
|
|
15075
|
+
// ../daemon/src/agent/acp/map.ts
|
|
15076
|
+
function spawnOf(meta) {
|
|
15077
|
+
const claude = asRecord2(asRecord2(meta).claudeCode);
|
|
15078
|
+
const parentToolId = typeof claude.parentToolUseId === "string" ? claude.parentToolUseId : undefined;
|
|
15079
|
+
const codex = asRecord2(asRecord2(meta).codex).subagent;
|
|
15080
|
+
const spawns = claude.toolName === "Agent" || claude.toolName === "Task";
|
|
15081
|
+
const subagent = spawns || claude.subagent === true || Object.keys(asRecord2(codex)).length > 0;
|
|
15082
|
+
return { ...parentToolId ? { parentToolId } : {}, ...subagent ? { subagent: true } : {} };
|
|
15083
|
+
}
|
|
15084
|
+
function asRecord2(v) {
|
|
15085
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
15086
|
+
}
|
|
15087
|
+
var NETWORK_ASK = "SandboxNetworkAccess";
|
|
15088
|
+
function isNetworkAsk(update) {
|
|
15089
|
+
return update.name === NETWORK_ASK || asRecord2(asRecord2(update._meta).claudeCode).toolName === NETWORK_ASK;
|
|
15090
|
+
}
|
|
15091
|
+
function isToolSearch(update) {
|
|
15092
|
+
return update.name === TOOL_SEARCH || asRecord2(asRecord2(update._meta).claudeCode).toolName === TOOL_SEARCH;
|
|
15093
|
+
}
|
|
15094
|
+
function toolSearchQuery(rawInput) {
|
|
15095
|
+
const query = asRecord2(rawInput).query;
|
|
15096
|
+
if (typeof query !== "string")
|
|
15097
|
+
return "";
|
|
15098
|
+
const names = /^select:(.*)$/.exec(query.trim());
|
|
15099
|
+
if (!names)
|
|
15100
|
+
return query.trim();
|
|
15101
|
+
return (names[1] ?? "").split(",").map((s) => s.trim()).filter(Boolean).join(", ");
|
|
15102
|
+
}
|
|
15103
|
+
var TOYON_TOOL = new RegExp(`^(?:mcp__${TOYON_MCP_SERVER}__|${TOYON_MCP_SERVER}/)([A-Za-z0-9_-]+)$`);
|
|
15104
|
+
function toyonTool(update) {
|
|
15105
|
+
const meta = asRecord2(asRecord2(update._meta).claudeCode).toolName;
|
|
15106
|
+
for (const spelled of [update.name ?? "", typeof meta === "string" ? meta : ""]) {
|
|
15107
|
+
const m = TOYON_TOOL.exec(spelled);
|
|
15108
|
+
if (m)
|
|
15109
|
+
return m[1];
|
|
15110
|
+
}
|
|
15111
|
+
return null;
|
|
15112
|
+
}
|
|
15113
|
+
function toyonTitle(tool, rawInput, title) {
|
|
15114
|
+
if (tool !== HANDOFF_TOOL)
|
|
15115
|
+
return title && toyonTool({ name: title }) === null ? title : tool;
|
|
15116
|
+
const project = asRecord2(rawInput).project;
|
|
15117
|
+
return typeof project === "string" && project.trim() ? `Continue in ${project.trim()}` : "Continue in another project";
|
|
15118
|
+
}
|
|
15119
|
+
function heading(update) {
|
|
15120
|
+
const name = typeof update.name === "string" ? update.name : "";
|
|
15121
|
+
const host = asRecord2(update.rawInput).host;
|
|
15122
|
+
if (isNetworkAsk(update) && typeof host === "string")
|
|
15123
|
+
return { name: "", title: host, kind: "fetch" };
|
|
15124
|
+
if (isToolSearch(update)) {
|
|
15125
|
+
return { name: TOOL_SEARCH, title: toolSearchQuery(update.rawInput) || TOOL_SEARCH, kind: "search" };
|
|
15126
|
+
}
|
|
15127
|
+
const own = toyonTool(update);
|
|
15128
|
+
if (own)
|
|
15129
|
+
return { name: own, title: toyonTitle(own, update.rawInput, update.title), kind: "other" };
|
|
15130
|
+
return { name, title: update.title, ...update.kind ? { kind: update.kind } : {} };
|
|
15131
|
+
}
|
|
15132
|
+
function endOfAsk(req, res, memos) {
|
|
15133
|
+
const memo = memos.get(req.toolCall.toolCallId);
|
|
15134
|
+
if (req.toolCall.name !== NETWORK_ASK || !memo || memo.ended)
|
|
15135
|
+
return null;
|
|
15136
|
+
const outcome = res.outcome;
|
|
15137
|
+
const picked = outcome.outcome === "selected" ? req.options.find((o) => o.optionId === outcome.optionId) : undefined;
|
|
15138
|
+
const allowed = picked?.kind === "allow_once" || picked?.kind === "allow_always";
|
|
15139
|
+
memo.ended = true;
|
|
15140
|
+
return {
|
|
15141
|
+
type: "tool-end",
|
|
15142
|
+
toolId: req.toolCall.toolCallId,
|
|
15143
|
+
output: allowed ? "allowed" : "refused",
|
|
15144
|
+
isError: !allowed
|
|
15145
|
+
};
|
|
15146
|
+
}
|
|
15147
|
+
function release(toolId, memo) {
|
|
15148
|
+
const held = memo.held;
|
|
15149
|
+
if (!held)
|
|
15150
|
+
return null;
|
|
15151
|
+
delete memo.held;
|
|
15152
|
+
if (!emptyInput(memo.input))
|
|
15153
|
+
memo.writing = false;
|
|
15154
|
+
return memo.ended ? null : { type: "tool-update", toolId, ...held };
|
|
15155
|
+
}
|
|
15156
|
+
function preempted(memos) {
|
|
15157
|
+
for (const memo of memos.values()) {
|
|
15158
|
+
if (!memo.ended && memo.writing && !memo.parent)
|
|
15159
|
+
memo.cut = true;
|
|
15160
|
+
}
|
|
15161
|
+
}
|
|
15162
|
+
function abandoned(memos, parent, opening = false) {
|
|
15163
|
+
const out = [];
|
|
15164
|
+
for (const [toolId, memo] of memos) {
|
|
15165
|
+
if (memo.ended || !memo.writing || memo.parent !== parent)
|
|
15166
|
+
continue;
|
|
15167
|
+
if (opening && memo.held && !memo.cut) {
|
|
15168
|
+
const sent = release(toolId, memo);
|
|
15169
|
+
if (sent)
|
|
15170
|
+
out.push(sent);
|
|
15171
|
+
continue;
|
|
15172
|
+
}
|
|
15173
|
+
memo.ended = true;
|
|
15174
|
+
out.push({ type: "tool-end", toolId });
|
|
15175
|
+
}
|
|
15176
|
+
return out;
|
|
15177
|
+
}
|
|
15178
|
+
function mapUpdate(update, memos, tag, sink, background) {
|
|
15179
|
+
switch (update.sessionUpdate) {
|
|
15180
|
+
case "agent_message_chunk": {
|
|
15181
|
+
const { parentToolId } = spawnOf(update._meta);
|
|
15182
|
+
const out = abandoned(memos, parentToolId);
|
|
15183
|
+
const text = textOf(update.content, tag, "message");
|
|
15184
|
+
if (text === null)
|
|
15185
|
+
return out;
|
|
15186
|
+
out.push(parentToolId ? { type: "tool-delta", toolId: parentToolId, text } : { type: "text-delta", text, ...update.messageId ? { messageId: update.messageId } : {} });
|
|
15187
|
+
return out;
|
|
15188
|
+
}
|
|
15189
|
+
case "agent_thought_chunk": {
|
|
15190
|
+
const { parentToolId } = spawnOf(update._meta);
|
|
15191
|
+
const out = abandoned(memos, parentToolId);
|
|
15192
|
+
if (parentToolId)
|
|
15193
|
+
return out;
|
|
15194
|
+
const text = textOf(update.content, tag, "thought");
|
|
15195
|
+
if (text !== null)
|
|
15196
|
+
out.push({ type: "thinking-delta", text });
|
|
15197
|
+
return out;
|
|
15198
|
+
}
|
|
15199
|
+
case "tool_call": {
|
|
15200
|
+
const spawn = spawnOf(update._meta);
|
|
15201
|
+
const out = abandoned(memos, spawn.parentToolId, true);
|
|
15202
|
+
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
15203
|
+
const head = heading(update);
|
|
15204
|
+
const memo = {
|
|
15205
|
+
...head,
|
|
15206
|
+
input,
|
|
15207
|
+
content: update.content ?? [],
|
|
15208
|
+
rawOutput: update.rawOutput,
|
|
15209
|
+
ended: false,
|
|
15210
|
+
writing: writingCall(head, input),
|
|
15211
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
15212
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
15213
|
+
};
|
|
15214
|
+
memos.set(update.toolCallId, memo);
|
|
15215
|
+
out.push({
|
|
15216
|
+
type: "tool-start",
|
|
15217
|
+
toolId: update.toolCallId,
|
|
15218
|
+
name: memo.name,
|
|
15219
|
+
input,
|
|
15220
|
+
...memo.kind ? { kind: memo.kind } : {},
|
|
15221
|
+
title: memo.title,
|
|
15222
|
+
...spawn
|
|
15223
|
+
});
|
|
15224
|
+
if (update.status === "completed" || update.status === "failed")
|
|
15225
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15226
|
+
return out;
|
|
15227
|
+
}
|
|
15228
|
+
case "tool_call_update": {
|
|
15229
|
+
let memo = memos.get(update.toolCallId);
|
|
15230
|
+
const out = [];
|
|
15231
|
+
if (!memo) {
|
|
15232
|
+
const spawn = spawnOf(update._meta);
|
|
15233
|
+
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
15234
|
+
memo = {
|
|
15235
|
+
name: update.name ?? "",
|
|
15236
|
+
title: update.title ?? update.name ?? "tool",
|
|
15237
|
+
...update.kind ? { kind: update.kind } : {},
|
|
15238
|
+
input,
|
|
15239
|
+
content: [],
|
|
15240
|
+
ended: false,
|
|
15241
|
+
writing: writingCall({ name: update.name ?? "", kind: update.kind ?? undefined }, input),
|
|
15242
|
+
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
15243
|
+
...spawn.subagent ? { spawns: true } : {}
|
|
15244
|
+
};
|
|
15245
|
+
memos.set(update.toolCallId, memo);
|
|
15246
|
+
out.push({
|
|
15247
|
+
type: "tool-start",
|
|
15248
|
+
toolId: update.toolCallId,
|
|
15249
|
+
name: memo.name,
|
|
15250
|
+
input,
|
|
15251
|
+
...memo.kind ? { kind: memo.kind } : {},
|
|
15252
|
+
title: memo.title,
|
|
15253
|
+
...spawn
|
|
15254
|
+
});
|
|
15255
|
+
}
|
|
15256
|
+
const refined = { ...memo.held };
|
|
15257
|
+
const title = memo.name === TOOL_SEARCH ? toolSearchQuery(update.rawInput) : memo.name === HANDOFF_TOOL ? update.rawInput !== undefined ? toyonTitle(HANDOFF_TOOL, update.rawInput, update.title) : undefined : update.title;
|
|
15258
|
+
if (title && title !== memo.title)
|
|
15259
|
+
refined.title = memo.title = title;
|
|
15260
|
+
if (update.name && update.name !== memo.name && toyonTool(update) !== memo.name)
|
|
15261
|
+
refined.name = memo.name = update.name;
|
|
15262
|
+
if (update.kind && update.kind !== memo.kind)
|
|
15263
|
+
refined.kind = memo.kind = update.kind;
|
|
15264
|
+
if (update.rawInput !== undefined)
|
|
15265
|
+
refined.input = memo.input = update.rawInput;
|
|
15266
|
+
if (update.rawInput !== undefined && update.content === undefined && update.status === undefined) {
|
|
15267
|
+
if (out.length === 0)
|
|
15268
|
+
memo.held = refined;
|
|
15269
|
+
return out;
|
|
15270
|
+
}
|
|
15271
|
+
delete memo.held;
|
|
15272
|
+
if (!emptyInput(memo.input))
|
|
15273
|
+
memo.writing = false;
|
|
15274
|
+
if (Object.keys(refined).length > 0 && !memo.ended && out.length === 0) {
|
|
15275
|
+
out.push({ type: "tool-update", toolId: update.toolCallId, ...refined });
|
|
15276
|
+
}
|
|
15277
|
+
if (update.content)
|
|
15278
|
+
memo.content = update.content;
|
|
15279
|
+
if (update.rawOutput !== undefined)
|
|
15280
|
+
memo.rawOutput = update.rawOutput;
|
|
15281
|
+
if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
|
|
15282
|
+
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15283
|
+
}
|
|
15284
|
+
return out;
|
|
15285
|
+
}
|
|
15286
|
+
case "config_option_update": {
|
|
15287
|
+
const values = currentValues(readOptions(update.configOptions));
|
|
15288
|
+
return Object.keys(values).length > 0 ? [{ type: "session-info", sessionId: "", ...values }] : [];
|
|
15289
|
+
}
|
|
15290
|
+
case "usage_update": {
|
|
15291
|
+
const cost = update.cost && update.cost.currency === "USD" ? update.cost.amount : undefined;
|
|
15292
|
+
return [
|
|
15293
|
+
{
|
|
15294
|
+
type: "usage",
|
|
15295
|
+
used: update.used,
|
|
15296
|
+
size: update.size,
|
|
15297
|
+
...cost !== undefined ? { cost } : {},
|
|
15298
|
+
ts: Date.now()
|
|
15299
|
+
}
|
|
15300
|
+
];
|
|
15301
|
+
}
|
|
15302
|
+
default:
|
|
15303
|
+
log.debug(tag, `acp: ignoring ${update.sessionUpdate}`);
|
|
15304
|
+
return [];
|
|
15305
|
+
}
|
|
15306
|
+
}
|
|
15307
|
+
function textOf(content, tag, what) {
|
|
15308
|
+
if (content.type === "text" && typeof content.text === "string")
|
|
15309
|
+
return content.text;
|
|
15310
|
+
log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
|
|
15311
|
+
return null;
|
|
15312
|
+
}
|
|
15313
|
+
function endOf(toolId, memo, status, sink, background) {
|
|
15314
|
+
memo.ended = true;
|
|
15315
|
+
const output = summarizeToolOutput(memo.content, memo.rawOutput);
|
|
15316
|
+
if (status === "completed" && memo.kind === "execute" && background) {
|
|
15317
|
+
const start = backgroundStart(memo.input, output);
|
|
15318
|
+
if (start && background(toolId, start, memo.input))
|
|
15319
|
+
return { type: "tool-update", toolId, background: true };
|
|
15320
|
+
}
|
|
15321
|
+
const images = sink ? toolImages(memo.content, sink) : [];
|
|
15322
|
+
const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
|
|
15323
|
+
return {
|
|
15324
|
+
type: "tool-end",
|
|
15325
|
+
toolId,
|
|
15326
|
+
output,
|
|
15327
|
+
isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
|
|
15328
|
+
...detached ? { detached: true } : {},
|
|
15329
|
+
...images.length ? { images } : {}
|
|
15330
|
+
};
|
|
15331
|
+
}
|
|
15332
|
+
function wholeText(content, rawOutput) {
|
|
15333
|
+
const parts = content.flatMap((c) => c.type === "content" && c.content.type === "text" ? [c.content.text] : []);
|
|
15334
|
+
if (typeof rawOutput === "string")
|
|
15335
|
+
parts.push(rawOutput);
|
|
15336
|
+
return parts.join(`
|
|
15337
|
+
`);
|
|
15338
|
+
}
|
|
15339
|
+
function toolImages(content, sink) {
|
|
15340
|
+
const out = [];
|
|
15341
|
+
for (const c of content) {
|
|
15342
|
+
if (c.type !== "content" || c.content.type !== "image")
|
|
15343
|
+
continue;
|
|
15344
|
+
const img = toolImage(c.content.data, c.content.mimeType);
|
|
15345
|
+
if (!img)
|
|
15346
|
+
continue;
|
|
15347
|
+
sink(img.ref.file, img.bytes);
|
|
15348
|
+
out.push(img.ref);
|
|
15349
|
+
}
|
|
15350
|
+
return out;
|
|
15351
|
+
}
|
|
15352
|
+
function exitCodeOf(rawOutput) {
|
|
15353
|
+
const exit = rawOutput?.metadata?.exit;
|
|
15354
|
+
return typeof exit === "number" ? exit : 0;
|
|
15355
|
+
}
|
|
15356
|
+
function summarizeToolOutput(content, rawOutput) {
|
|
15357
|
+
const parts = [];
|
|
15358
|
+
let pictured = false;
|
|
15359
|
+
for (const c of content) {
|
|
15360
|
+
if (c.type === "content") {
|
|
15361
|
+
if (c.content.type === "text")
|
|
15362
|
+
parts.push(c.content.text);
|
|
15363
|
+
else if (c.content.type === "image")
|
|
15364
|
+
pictured = true;
|
|
15365
|
+
} else if (c.type === "diff") {
|
|
15366
|
+
const body = unifiedDiff(c.oldText ?? "", c.newText);
|
|
15367
|
+
if (body)
|
|
15368
|
+
parts.push(`\`\`\`diff
|
|
15369
|
+
${body}
|
|
15370
|
+
\`\`\``);
|
|
15371
|
+
}
|
|
15372
|
+
}
|
|
15373
|
+
if (parts.length > 0)
|
|
15374
|
+
return truncate(parts.join(`
|
|
15375
|
+
`));
|
|
15376
|
+
if (pictured)
|
|
15377
|
+
return "";
|
|
15378
|
+
if (typeof rawOutput === "string")
|
|
15379
|
+
return truncate(rawOutput);
|
|
15380
|
+
if (rawOutput && typeof rawOutput === "object") {
|
|
15381
|
+
const rec = rawOutput;
|
|
15382
|
+
for (const k of ["formatted_output", "output", "stdout", "text"])
|
|
15383
|
+
if (typeof rec[k] === "string")
|
|
15384
|
+
return truncate(rec[k]);
|
|
15385
|
+
return truncate(JSON.stringify(rawOutput, null, 1));
|
|
15386
|
+
}
|
|
15387
|
+
return "";
|
|
15388
|
+
}
|
|
15389
|
+
function truncate(s, max = 4000) {
|
|
15390
|
+
return s.length > max ? `${s.slice(0, max)}
|
|
15391
|
+
\u2026 (${s.length - max} more chars)` : s;
|
|
15392
|
+
}
|
|
15393
|
+
function mapCommands(cmds) {
|
|
15394
|
+
const seen = new Set;
|
|
15395
|
+
return cmds.filter((c) => {
|
|
15396
|
+
if (typeof c.name !== "string" || c.name.length === 0 || c.name.length > 120 || seen.has(c.name))
|
|
15397
|
+
return false;
|
|
15398
|
+
seen.add(c.name);
|
|
15399
|
+
return true;
|
|
15400
|
+
}).slice(0, 300).map((c) => ({
|
|
15401
|
+
name: c.name,
|
|
15402
|
+
description: c.description ?? "",
|
|
15403
|
+
...c.input?.hint ? { hint: c.input.hint } : {}
|
|
15404
|
+
}));
|
|
15405
|
+
}
|
|
15406
|
+
function mapStopReason(r) {
|
|
15407
|
+
return r === "cancelled" ? "interrupted" : r;
|
|
15408
|
+
}
|
|
15409
|
+
|
|
15410
|
+
// ../daemon/src/agent/policy.ts
|
|
14851
15411
|
var NON_WRITE_KINDS = new Set(["execute", "read", "search", "fetch", "think"]);
|
|
14852
15412
|
function requestedPaths(req) {
|
|
14853
15413
|
const raw = [];
|
|
@@ -14876,6 +15436,8 @@ function decide(req, bounds, cwd, mode = "auto", sandboxed = true) {
|
|
|
14876
15436
|
const commandAsks = kind === "execute" && !sandboxed;
|
|
14877
15437
|
if (kind === "switch_mode")
|
|
14878
15438
|
return { kind: "prompt" };
|
|
15439
|
+
if (toyonTool(req.toolCall) !== null)
|
|
15440
|
+
return { kind: "allow" };
|
|
14879
15441
|
const asks = mode !== "auto";
|
|
14880
15442
|
if (paths.length === 0) {
|
|
14881
15443
|
if (kind === "execute")
|
|
@@ -15014,7 +15576,7 @@ function fixWhy(f) {
|
|
|
15014
15576
|
}
|
|
15015
15577
|
function pageFixPrompt(errors) {
|
|
15016
15578
|
return [
|
|
15017
|
-
"
|
|
15579
|
+
"Since your last edits the preview threw this in the user's browser:",
|
|
15018
15580
|
"",
|
|
15019
15581
|
...errors.map((e) => `- ${e}`),
|
|
15020
15582
|
"",
|
|
@@ -15227,11 +15789,11 @@ ${a.text}
|
|
|
15227
15789
|
}
|
|
15228
15790
|
|
|
15229
15791
|
// ../daemon/src/agent/transcript.ts
|
|
15230
|
-
import { existsSync as
|
|
15792
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
|
|
15231
15793
|
import { appendFile } from "fs/promises";
|
|
15232
|
-
import { join as
|
|
15794
|
+
import { join as join9 } from "path";
|
|
15233
15795
|
function transcriptPathFor(transcriptsDir, worktreeId) {
|
|
15234
|
-
return
|
|
15796
|
+
return join9(transcriptsDir, `${worktreeId}.jsonl`);
|
|
15235
15797
|
}
|
|
15236
15798
|
function coalesce(entries) {
|
|
15237
15799
|
const out = [];
|
|
@@ -15285,7 +15847,7 @@ class Transcript {
|
|
|
15285
15847
|
this.seq = (this.entries.at(-1)?.seq ?? -1) + 1;
|
|
15286
15848
|
}
|
|
15287
15849
|
read() {
|
|
15288
|
-
return
|
|
15850
|
+
return existsSync11(this.path) ? parseTranscript(readFileSync9(this.path, "utf8"), this.tag) : [];
|
|
15289
15851
|
}
|
|
15290
15852
|
append(event) {
|
|
15291
15853
|
const entry = { seq: this.seq++, event };
|
|
@@ -15382,168 +15944,6 @@ async function cleanUp(ctx, a, sessionId) {
|
|
|
15382
15944
|
await ctx.request(methods.agent.session.delete, { sessionId }).catch(quietly("delete"));
|
|
15383
15945
|
}
|
|
15384
15946
|
|
|
15385
|
-
// ../daemon/src/agent/acp/background.ts
|
|
15386
|
-
import { existsSync as existsSync11, statSync as statSync4 } from "fs";
|
|
15387
|
-
import { open as open2 } from "fs/promises";
|
|
15388
|
-
import { homedir as homedir5 } from "os";
|
|
15389
|
-
import { basename as basename3, dirname as dirname4, join as join9 } from "path";
|
|
15390
|
-
var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
|
|
15391
|
-
function backgroundStart(input, output) {
|
|
15392
|
-
const flag = input && typeof input === "object" ? input.run_in_background : undefined;
|
|
15393
|
-
if (flag !== true)
|
|
15394
|
-
return null;
|
|
15395
|
-
const m = STARTED.exec(output);
|
|
15396
|
-
return m ? { taskId: m[1], file: m[2] } : null;
|
|
15397
|
-
}
|
|
15398
|
-
function spawnDetached(output) {
|
|
15399
|
-
return output.includes("Async agent launched successfully");
|
|
15400
|
-
}
|
|
15401
|
-
function claudeConfigDir() {
|
|
15402
|
-
return process.env.CLAUDE_CONFIG_DIR || join9(homedir5(), ".claude");
|
|
15403
|
-
}
|
|
15404
|
-
function sessionLogFor(file, configDir = claudeConfigDir()) {
|
|
15405
|
-
const tasks = dirname4(file);
|
|
15406
|
-
if (basename3(tasks) !== "tasks")
|
|
15407
|
-
return null;
|
|
15408
|
-
const session = dirname4(tasks);
|
|
15409
|
-
const path = join9(configDir, "projects", basename3(dirname4(session)), `${basename3(session)}.jsonl`);
|
|
15410
|
-
return existsSync11(path) ? path : null;
|
|
15411
|
-
}
|
|
15412
|
-
function taskEnd(text, toolId) {
|
|
15413
|
-
const at = text.indexOf(`<tool-use-id>${toolId}</tool-use-id>`);
|
|
15414
|
-
if (at === -1)
|
|
15415
|
-
return null;
|
|
15416
|
-
const tail = text.slice(at, at + 2000);
|
|
15417
|
-
const status = /<status>(\w+)<\/status>/.exec(tail)?.[1] ?? "completed";
|
|
15418
|
-
const exit = /exit code (\d+)/.exec(tail)?.[1];
|
|
15419
|
-
return { status, exit: exit === undefined ? null : Number(exit) };
|
|
15420
|
-
}
|
|
15421
|
-
var POLL_MS = 500;
|
|
15422
|
-
var DEFAULT_BASH_TIMEOUT_MS = 120000;
|
|
15423
|
-
var GRACE_MS = 60000;
|
|
15424
|
-
var READ_CHUNK = 1 << 20;
|
|
15425
|
-
var CARRY = 4000;
|
|
15426
|
-
|
|
15427
|
-
class BackgroundTasks {
|
|
15428
|
-
deps;
|
|
15429
|
-
watched = new Map;
|
|
15430
|
-
constructor(deps) {
|
|
15431
|
-
this.deps = deps;
|
|
15432
|
-
}
|
|
15433
|
-
get size() {
|
|
15434
|
-
return this.watched.size;
|
|
15435
|
-
}
|
|
15436
|
-
start(toolId, start, input) {
|
|
15437
|
-
const logPath = sessionLogFor(start.file, this.deps.configDir);
|
|
15438
|
-
if (!logPath)
|
|
15439
|
-
return false;
|
|
15440
|
-
const timeout = input?.timeout;
|
|
15441
|
-
const ceiling = (typeof timeout === "number" ? timeout : DEFAULT_BASH_TIMEOUT_MS) + (this.deps.graceMs ?? GRACE_MS);
|
|
15442
|
-
const w = {
|
|
15443
|
-
toolId,
|
|
15444
|
-
file: start.file,
|
|
15445
|
-
log: logPath,
|
|
15446
|
-
read: 0,
|
|
15447
|
-
logRead: statSync4(logPath).size,
|
|
15448
|
-
carry: "",
|
|
15449
|
-
decoder: new TextDecoder,
|
|
15450
|
-
text: "",
|
|
15451
|
-
truncated: false,
|
|
15452
|
-
ceiling: Date.now() + ceiling,
|
|
15453
|
-
timer: null
|
|
15454
|
-
};
|
|
15455
|
-
this.watched.set(toolId, w);
|
|
15456
|
-
this.schedule(w);
|
|
15457
|
-
return true;
|
|
15458
|
-
}
|
|
15459
|
-
endAll(reason) {
|
|
15460
|
-
for (const w of [...this.watched.values()]) {
|
|
15461
|
-
fireAndForget("background", this.finish(w, null, reason, true), `ending ${w.toolId}`);
|
|
15462
|
-
}
|
|
15463
|
-
}
|
|
15464
|
-
schedule(w) {
|
|
15465
|
-
w.timer = setTimeout(() => this.poll(w), this.deps.pollMs ?? POLL_MS);
|
|
15466
|
-
w.timer.unref?.();
|
|
15467
|
-
}
|
|
15468
|
-
async poll(w) {
|
|
15469
|
-
w.timer = null;
|
|
15470
|
-
try {
|
|
15471
|
-
await this.drain(w);
|
|
15472
|
-
const fresh = await readSince(w.log, w.logRead);
|
|
15473
|
-
w.logRead += fresh.length;
|
|
15474
|
-
const text = w.carry + fresh.toString("utf8");
|
|
15475
|
-
w.carry = text.slice(-CARRY);
|
|
15476
|
-
const end = fresh.length > 0 ? taskEnd(text, w.toolId) : null;
|
|
15477
|
-
if (end)
|
|
15478
|
-
return this.finish(w, end);
|
|
15479
|
-
if (Date.now() >= w.ceiling)
|
|
15480
|
-
return this.finish(w, null, "no word of its end; the row stops watching");
|
|
15481
|
-
} catch (e) {
|
|
15482
|
-
log.warn("background", `watching ${w.toolId} failed`, e);
|
|
15483
|
-
}
|
|
15484
|
-
if (this.watched.has(w.toolId))
|
|
15485
|
-
this.schedule(w);
|
|
15486
|
-
}
|
|
15487
|
-
async drain(w) {
|
|
15488
|
-
const fresh = await readSince(w.file, w.read);
|
|
15489
|
-
w.read += fresh.length;
|
|
15490
|
-
if (fresh.length === 0 || w.truncated)
|
|
15491
|
-
return;
|
|
15492
|
-
const chunk = w.decoder.decode(fresh, { stream: true });
|
|
15493
|
-
const take = chunk.slice(0, OUTPUT_CAP - w.text.length);
|
|
15494
|
-
w.text += take;
|
|
15495
|
-
if (take.length < chunk.length)
|
|
15496
|
-
w.truncated = true;
|
|
15497
|
-
if (take)
|
|
15498
|
-
this.deps.note({ type: "tool-delta", toolId: w.toolId, text: take });
|
|
15499
|
-
}
|
|
15500
|
-
async finish(w, end, note, killed = false) {
|
|
15501
|
-
if (!this.watched.delete(w.toolId))
|
|
15502
|
-
return;
|
|
15503
|
-
if (w.timer)
|
|
15504
|
-
clearTimeout(w.timer);
|
|
15505
|
-
try {
|
|
15506
|
-
await this.drain(w);
|
|
15507
|
-
} catch (e) {
|
|
15508
|
-
log.warn("background", `reading the last of ${w.toolId} failed`, e);
|
|
15509
|
-
}
|
|
15510
|
-
const exit = end ? end.exit ?? (end.status === "completed" ? 0 : null) : null;
|
|
15511
|
-
const lines = [formatOutput(w.text, exit ?? 0, w.truncated)];
|
|
15512
|
-
if (end && exit === null)
|
|
15513
|
-
lines.push(end.status);
|
|
15514
|
-
if (note)
|
|
15515
|
-
lines.push(note);
|
|
15516
|
-
this.deps.note({
|
|
15517
|
-
type: "tool-end",
|
|
15518
|
-
toolId: w.toolId,
|
|
15519
|
-
output: lines.filter(Boolean).join(`
|
|
15520
|
-
`),
|
|
15521
|
-
isError: killed || end !== null && (exit !== 0 || end.status !== "completed")
|
|
15522
|
-
});
|
|
15523
|
-
this.deps.onEnd?.();
|
|
15524
|
-
}
|
|
15525
|
-
}
|
|
15526
|
-
async function readSince(path, offset) {
|
|
15527
|
-
let fh;
|
|
15528
|
-
try {
|
|
15529
|
-
fh = await open2(path, "r");
|
|
15530
|
-
} catch (e) {
|
|
15531
|
-
if (e.code === "ENOENT")
|
|
15532
|
-
return Buffer.alloc(0);
|
|
15533
|
-
throw e;
|
|
15534
|
-
}
|
|
15535
|
-
try {
|
|
15536
|
-
const { size } = await fh.stat();
|
|
15537
|
-
if (size <= offset)
|
|
15538
|
-
return Buffer.alloc(0);
|
|
15539
|
-
const buf = Buffer.alloc(Math.min(size - offset, READ_CHUNK));
|
|
15540
|
-
const { bytesRead } = await fh.read(buf, 0, buf.length, offset);
|
|
15541
|
-
return buf.subarray(0, bytesRead);
|
|
15542
|
-
} finally {
|
|
15543
|
-
await fh.close();
|
|
15544
|
-
}
|
|
15545
|
-
}
|
|
15546
|
-
|
|
15547
15947
|
// ../daemon/src/agent/acp/elicit.ts
|
|
15548
15948
|
var CODEX_META = "codex";
|
|
15549
15949
|
var CLAUDE_CUSTOM_KEY = /^(question_\d+)_custom$/;
|
|
@@ -15657,398 +16057,6 @@ function toContent(form, answers) {
|
|
|
15657
16057
|
return out;
|
|
15658
16058
|
}
|
|
15659
16059
|
|
|
15660
|
-
// ../daemon/src/agent/acp/diff.ts
|
|
15661
|
-
function diffOps(a, b) {
|
|
15662
|
-
return diffSeq(a, b).map((op) => ({ mark: op.mark, text: op.value }));
|
|
15663
|
-
}
|
|
15664
|
-
function unifiedDiff(oldText, newText, context = 3) {
|
|
15665
|
-
const a = oldText === "" ? [] : oldText.split(`
|
|
15666
|
-
`);
|
|
15667
|
-
const b = newText === "" ? [] : newText.split(`
|
|
15668
|
-
`);
|
|
15669
|
-
const ops = diffOps(a, b);
|
|
15670
|
-
const oldNo = [];
|
|
15671
|
-
const newNo = [];
|
|
15672
|
-
let o = 1;
|
|
15673
|
-
let n = 1;
|
|
15674
|
-
for (const op of ops) {
|
|
15675
|
-
oldNo.push(o);
|
|
15676
|
-
newNo.push(n);
|
|
15677
|
-
if (op.mark !== "+")
|
|
15678
|
-
o++;
|
|
15679
|
-
if (op.mark !== "-")
|
|
15680
|
-
n++;
|
|
15681
|
-
}
|
|
15682
|
-
const out = [];
|
|
15683
|
-
let k = 0;
|
|
15684
|
-
while (k < ops.length) {
|
|
15685
|
-
if (ops[k]?.mark === " ") {
|
|
15686
|
-
k++;
|
|
15687
|
-
continue;
|
|
15688
|
-
}
|
|
15689
|
-
const start = k;
|
|
15690
|
-
let end = k;
|
|
15691
|
-
let j = k;
|
|
15692
|
-
while (j < ops.length) {
|
|
15693
|
-
if (ops[j]?.mark !== " ") {
|
|
15694
|
-
end = j;
|
|
15695
|
-
j++;
|
|
15696
|
-
continue;
|
|
15697
|
-
}
|
|
15698
|
-
let run = 0;
|
|
15699
|
-
while (j + run < ops.length && ops[j + run]?.mark === " ")
|
|
15700
|
-
run++;
|
|
15701
|
-
if (run > context * 2 || j + run >= ops.length)
|
|
15702
|
-
break;
|
|
15703
|
-
j += run;
|
|
15704
|
-
}
|
|
15705
|
-
const from = Math.max(0, start - context);
|
|
15706
|
-
const to = Math.min(ops.length, end + context + 1);
|
|
15707
|
-
const body = ops.slice(from, to);
|
|
15708
|
-
const oldCount = body.filter((op) => op.mark !== "+").length;
|
|
15709
|
-
const newCount = body.filter((op) => op.mark !== "-").length;
|
|
15710
|
-
out.push(`@@ -${oldNo[from]},${oldCount} +${newNo[from]},${newCount} @@`, ...body.map((op) => `${op.mark}${op.text}`));
|
|
15711
|
-
k = to;
|
|
15712
|
-
}
|
|
15713
|
-
return out.join(`
|
|
15714
|
-
`);
|
|
15715
|
-
}
|
|
15716
|
-
|
|
15717
|
-
// ../daemon/src/agent/acp/map.ts
|
|
15718
|
-
function spawnOf(meta) {
|
|
15719
|
-
const claude = asRecord2(asRecord2(meta).claudeCode);
|
|
15720
|
-
const parentToolId = typeof claude.parentToolUseId === "string" ? claude.parentToolUseId : undefined;
|
|
15721
|
-
const codex = asRecord2(asRecord2(meta).codex).subagent;
|
|
15722
|
-
const spawns = claude.toolName === "Agent" || claude.toolName === "Task";
|
|
15723
|
-
const subagent = spawns || claude.subagent === true || Object.keys(asRecord2(codex)).length > 0;
|
|
15724
|
-
return { ...parentToolId ? { parentToolId } : {}, ...subagent ? { subagent: true } : {} };
|
|
15725
|
-
}
|
|
15726
|
-
function asRecord2(v) {
|
|
15727
|
-
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
15728
|
-
}
|
|
15729
|
-
var NETWORK_ASK = "SandboxNetworkAccess";
|
|
15730
|
-
function isNetworkAsk(update) {
|
|
15731
|
-
return update.name === NETWORK_ASK || asRecord2(asRecord2(update._meta).claudeCode).toolName === NETWORK_ASK;
|
|
15732
|
-
}
|
|
15733
|
-
function isToolSearch(update) {
|
|
15734
|
-
return update.name === TOOL_SEARCH || asRecord2(asRecord2(update._meta).claudeCode).toolName === TOOL_SEARCH;
|
|
15735
|
-
}
|
|
15736
|
-
function toolSearchQuery(rawInput) {
|
|
15737
|
-
const query = asRecord2(rawInput).query;
|
|
15738
|
-
if (typeof query !== "string")
|
|
15739
|
-
return "";
|
|
15740
|
-
const names = /^select:(.*)$/.exec(query.trim());
|
|
15741
|
-
if (!names)
|
|
15742
|
-
return query.trim();
|
|
15743
|
-
return (names[1] ?? "").split(",").map((s) => s.trim()).filter(Boolean).join(", ");
|
|
15744
|
-
}
|
|
15745
|
-
var TOYON_TOOL = new RegExp(`^(?:mcp__${TOYON_MCP_SERVER}__|${TOYON_MCP_SERVER}/)([A-Za-z0-9_-]+)$`);
|
|
15746
|
-
function toyonTool(update) {
|
|
15747
|
-
const meta = asRecord2(asRecord2(update._meta).claudeCode).toolName;
|
|
15748
|
-
for (const spelled of [update.name ?? "", typeof meta === "string" ? meta : ""]) {
|
|
15749
|
-
const m = TOYON_TOOL.exec(spelled);
|
|
15750
|
-
if (m)
|
|
15751
|
-
return m[1];
|
|
15752
|
-
}
|
|
15753
|
-
return null;
|
|
15754
|
-
}
|
|
15755
|
-
function toyonTitle(tool, rawInput, title) {
|
|
15756
|
-
if (tool !== HANDOFF_TOOL)
|
|
15757
|
-
return title && toyonTool({ name: title }) === null ? title : tool;
|
|
15758
|
-
const project = asRecord2(rawInput).project;
|
|
15759
|
-
return typeof project === "string" && project.trim() ? `Continue in ${project.trim()}` : "Continue in another project";
|
|
15760
|
-
}
|
|
15761
|
-
function heading(update) {
|
|
15762
|
-
const name = typeof update.name === "string" ? update.name : "";
|
|
15763
|
-
const host = asRecord2(update.rawInput).host;
|
|
15764
|
-
if (isNetworkAsk(update) && typeof host === "string")
|
|
15765
|
-
return { name: "", title: host, kind: "fetch" };
|
|
15766
|
-
if (isToolSearch(update)) {
|
|
15767
|
-
return { name: TOOL_SEARCH, title: toolSearchQuery(update.rawInput) || TOOL_SEARCH, kind: "search" };
|
|
15768
|
-
}
|
|
15769
|
-
const own = toyonTool(update);
|
|
15770
|
-
if (own)
|
|
15771
|
-
return { name: own, title: toyonTitle(own, update.rawInput, update.title), kind: "other" };
|
|
15772
|
-
return { name, title: update.title, ...update.kind ? { kind: update.kind } : {} };
|
|
15773
|
-
}
|
|
15774
|
-
function endOfAsk(req, res, memos) {
|
|
15775
|
-
const memo = memos.get(req.toolCall.toolCallId);
|
|
15776
|
-
if (req.toolCall.name !== NETWORK_ASK || !memo || memo.ended)
|
|
15777
|
-
return null;
|
|
15778
|
-
const outcome = res.outcome;
|
|
15779
|
-
const picked = outcome.outcome === "selected" ? req.options.find((o) => o.optionId === outcome.optionId) : undefined;
|
|
15780
|
-
const allowed = picked?.kind === "allow_once" || picked?.kind === "allow_always";
|
|
15781
|
-
memo.ended = true;
|
|
15782
|
-
return {
|
|
15783
|
-
type: "tool-end",
|
|
15784
|
-
toolId: req.toolCall.toolCallId,
|
|
15785
|
-
output: allowed ? "allowed" : "refused",
|
|
15786
|
-
isError: !allowed
|
|
15787
|
-
};
|
|
15788
|
-
}
|
|
15789
|
-
function release(toolId, memo) {
|
|
15790
|
-
const held = memo.held;
|
|
15791
|
-
if (!held)
|
|
15792
|
-
return null;
|
|
15793
|
-
delete memo.held;
|
|
15794
|
-
if (!emptyInput(memo.input))
|
|
15795
|
-
memo.writing = false;
|
|
15796
|
-
return memo.ended ? null : { type: "tool-update", toolId, ...held };
|
|
15797
|
-
}
|
|
15798
|
-
function preempted(memos) {
|
|
15799
|
-
for (const memo of memos.values()) {
|
|
15800
|
-
if (!memo.ended && memo.writing && !memo.parent)
|
|
15801
|
-
memo.cut = true;
|
|
15802
|
-
}
|
|
15803
|
-
}
|
|
15804
|
-
function abandoned(memos, parent, opening = false) {
|
|
15805
|
-
const out = [];
|
|
15806
|
-
for (const [toolId, memo] of memos) {
|
|
15807
|
-
if (memo.ended || !memo.writing || memo.parent !== parent)
|
|
15808
|
-
continue;
|
|
15809
|
-
if (opening && memo.held && !memo.cut) {
|
|
15810
|
-
const sent = release(toolId, memo);
|
|
15811
|
-
if (sent)
|
|
15812
|
-
out.push(sent);
|
|
15813
|
-
continue;
|
|
15814
|
-
}
|
|
15815
|
-
memo.ended = true;
|
|
15816
|
-
out.push({ type: "tool-end", toolId });
|
|
15817
|
-
}
|
|
15818
|
-
return out;
|
|
15819
|
-
}
|
|
15820
|
-
function mapUpdate(update, memos, tag, sink, background) {
|
|
15821
|
-
switch (update.sessionUpdate) {
|
|
15822
|
-
case "agent_message_chunk": {
|
|
15823
|
-
const { parentToolId } = spawnOf(update._meta);
|
|
15824
|
-
const out = abandoned(memos, parentToolId);
|
|
15825
|
-
const text = textOf(update.content, tag, "message");
|
|
15826
|
-
if (text === null)
|
|
15827
|
-
return out;
|
|
15828
|
-
out.push(parentToolId ? { type: "tool-delta", toolId: parentToolId, text } : { type: "text-delta", text, ...update.messageId ? { messageId: update.messageId } : {} });
|
|
15829
|
-
return out;
|
|
15830
|
-
}
|
|
15831
|
-
case "agent_thought_chunk": {
|
|
15832
|
-
const { parentToolId } = spawnOf(update._meta);
|
|
15833
|
-
const out = abandoned(memos, parentToolId);
|
|
15834
|
-
if (parentToolId)
|
|
15835
|
-
return out;
|
|
15836
|
-
const text = textOf(update.content, tag, "thought");
|
|
15837
|
-
if (text !== null)
|
|
15838
|
-
out.push({ type: "thinking-delta", text });
|
|
15839
|
-
return out;
|
|
15840
|
-
}
|
|
15841
|
-
case "tool_call": {
|
|
15842
|
-
const spawn = spawnOf(update._meta);
|
|
15843
|
-
const out = abandoned(memos, spawn.parentToolId, true);
|
|
15844
|
-
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
15845
|
-
const head = heading(update);
|
|
15846
|
-
const memo = {
|
|
15847
|
-
...head,
|
|
15848
|
-
input,
|
|
15849
|
-
content: update.content ?? [],
|
|
15850
|
-
rawOutput: update.rawOutput,
|
|
15851
|
-
ended: false,
|
|
15852
|
-
writing: writingCall(head, input),
|
|
15853
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
15854
|
-
...spawn.subagent ? { spawns: true } : {}
|
|
15855
|
-
};
|
|
15856
|
-
memos.set(update.toolCallId, memo);
|
|
15857
|
-
out.push({
|
|
15858
|
-
type: "tool-start",
|
|
15859
|
-
toolId: update.toolCallId,
|
|
15860
|
-
name: memo.name,
|
|
15861
|
-
input,
|
|
15862
|
-
...memo.kind ? { kind: memo.kind } : {},
|
|
15863
|
-
title: memo.title,
|
|
15864
|
-
...spawn
|
|
15865
|
-
});
|
|
15866
|
-
if (update.status === "completed" || update.status === "failed")
|
|
15867
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15868
|
-
return out;
|
|
15869
|
-
}
|
|
15870
|
-
case "tool_call_update": {
|
|
15871
|
-
let memo = memos.get(update.toolCallId);
|
|
15872
|
-
const out = [];
|
|
15873
|
-
if (!memo) {
|
|
15874
|
-
const spawn = spawnOf(update._meta);
|
|
15875
|
-
const input = update.rawInput ?? { locations: update.locations ?? [] };
|
|
15876
|
-
memo = {
|
|
15877
|
-
name: update.name ?? "",
|
|
15878
|
-
title: update.title ?? update.name ?? "tool",
|
|
15879
|
-
...update.kind ? { kind: update.kind } : {},
|
|
15880
|
-
input,
|
|
15881
|
-
content: [],
|
|
15882
|
-
ended: false,
|
|
15883
|
-
writing: writingCall({ name: update.name ?? "", kind: update.kind ?? undefined }, input),
|
|
15884
|
-
...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
|
|
15885
|
-
...spawn.subagent ? { spawns: true } : {}
|
|
15886
|
-
};
|
|
15887
|
-
memos.set(update.toolCallId, memo);
|
|
15888
|
-
out.push({
|
|
15889
|
-
type: "tool-start",
|
|
15890
|
-
toolId: update.toolCallId,
|
|
15891
|
-
name: memo.name,
|
|
15892
|
-
input,
|
|
15893
|
-
...memo.kind ? { kind: memo.kind } : {},
|
|
15894
|
-
title: memo.title,
|
|
15895
|
-
...spawn
|
|
15896
|
-
});
|
|
15897
|
-
}
|
|
15898
|
-
const refined = { ...memo.held };
|
|
15899
|
-
const title = memo.name === TOOL_SEARCH ? toolSearchQuery(update.rawInput) : memo.name === HANDOFF_TOOL ? update.rawInput !== undefined ? toyonTitle(HANDOFF_TOOL, update.rawInput, update.title) : undefined : update.title;
|
|
15900
|
-
if (title && title !== memo.title)
|
|
15901
|
-
refined.title = memo.title = title;
|
|
15902
|
-
if (update.name && update.name !== memo.name && toyonTool(update) !== memo.name)
|
|
15903
|
-
refined.name = memo.name = update.name;
|
|
15904
|
-
if (update.kind && update.kind !== memo.kind)
|
|
15905
|
-
refined.kind = memo.kind = update.kind;
|
|
15906
|
-
if (update.rawInput !== undefined)
|
|
15907
|
-
refined.input = memo.input = update.rawInput;
|
|
15908
|
-
if (update.rawInput !== undefined && update.content === undefined && update.status === undefined) {
|
|
15909
|
-
if (out.length === 0)
|
|
15910
|
-
memo.held = refined;
|
|
15911
|
-
return out;
|
|
15912
|
-
}
|
|
15913
|
-
delete memo.held;
|
|
15914
|
-
if (!emptyInput(memo.input))
|
|
15915
|
-
memo.writing = false;
|
|
15916
|
-
if (Object.keys(refined).length > 0 && !memo.ended && out.length === 0) {
|
|
15917
|
-
out.push({ type: "tool-update", toolId: update.toolCallId, ...refined });
|
|
15918
|
-
}
|
|
15919
|
-
if (update.content)
|
|
15920
|
-
memo.content = [...memo.content, ...update.content];
|
|
15921
|
-
if (update.rawOutput !== undefined)
|
|
15922
|
-
memo.rawOutput = update.rawOutput;
|
|
15923
|
-
if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
|
|
15924
|
-
out.push(endOf(update.toolCallId, memo, update.status, sink, background));
|
|
15925
|
-
}
|
|
15926
|
-
return out;
|
|
15927
|
-
}
|
|
15928
|
-
case "config_option_update": {
|
|
15929
|
-
const values = currentValues(readOptions(update.configOptions));
|
|
15930
|
-
return Object.keys(values).length > 0 ? [{ type: "session-info", sessionId: "", ...values }] : [];
|
|
15931
|
-
}
|
|
15932
|
-
case "usage_update": {
|
|
15933
|
-
const cost = update.cost && update.cost.currency === "USD" ? update.cost.amount : undefined;
|
|
15934
|
-
return [
|
|
15935
|
-
{
|
|
15936
|
-
type: "usage",
|
|
15937
|
-
used: update.used,
|
|
15938
|
-
size: update.size,
|
|
15939
|
-
...cost !== undefined ? { cost } : {},
|
|
15940
|
-
ts: Date.now()
|
|
15941
|
-
}
|
|
15942
|
-
];
|
|
15943
|
-
}
|
|
15944
|
-
default:
|
|
15945
|
-
log.debug(tag, `acp: ignoring ${update.sessionUpdate}`);
|
|
15946
|
-
return [];
|
|
15947
|
-
}
|
|
15948
|
-
}
|
|
15949
|
-
function textOf(content, tag, what) {
|
|
15950
|
-
if (content.type === "text" && typeof content.text === "string")
|
|
15951
|
-
return content.text;
|
|
15952
|
-
log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
|
|
15953
|
-
return null;
|
|
15954
|
-
}
|
|
15955
|
-
function endOf(toolId, memo, status, sink, background) {
|
|
15956
|
-
memo.ended = true;
|
|
15957
|
-
const output = summarizeToolOutput(memo.content, memo.rawOutput);
|
|
15958
|
-
if (status === "completed" && memo.kind === "execute" && background) {
|
|
15959
|
-
const start = backgroundStart(memo.input, output);
|
|
15960
|
-
if (start && background(toolId, start, memo.input))
|
|
15961
|
-
return { type: "tool-update", toolId, background: true };
|
|
15962
|
-
}
|
|
15963
|
-
const images = sink ? toolImages(memo.content, sink) : [];
|
|
15964
|
-
const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
|
|
15965
|
-
return {
|
|
15966
|
-
type: "tool-end",
|
|
15967
|
-
toolId,
|
|
15968
|
-
output,
|
|
15969
|
-
isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
|
|
15970
|
-
...detached ? { detached: true } : {},
|
|
15971
|
-
...images.length ? { images } : {}
|
|
15972
|
-
};
|
|
15973
|
-
}
|
|
15974
|
-
function wholeText(content, rawOutput) {
|
|
15975
|
-
const parts = content.flatMap((c) => c.type === "content" && c.content.type === "text" ? [c.content.text] : []);
|
|
15976
|
-
if (typeof rawOutput === "string")
|
|
15977
|
-
parts.push(rawOutput);
|
|
15978
|
-
return parts.join(`
|
|
15979
|
-
`);
|
|
15980
|
-
}
|
|
15981
|
-
function toolImages(content, sink) {
|
|
15982
|
-
const out = [];
|
|
15983
|
-
for (const c of content) {
|
|
15984
|
-
if (c.type !== "content" || c.content.type !== "image")
|
|
15985
|
-
continue;
|
|
15986
|
-
const img = toolImage(c.content.data, c.content.mimeType);
|
|
15987
|
-
if (!img)
|
|
15988
|
-
continue;
|
|
15989
|
-
sink(img.ref.file, img.bytes);
|
|
15990
|
-
out.push(img.ref);
|
|
15991
|
-
}
|
|
15992
|
-
return out;
|
|
15993
|
-
}
|
|
15994
|
-
function exitCodeOf(rawOutput) {
|
|
15995
|
-
const exit = rawOutput?.metadata?.exit;
|
|
15996
|
-
return typeof exit === "number" ? exit : 0;
|
|
15997
|
-
}
|
|
15998
|
-
function summarizeToolOutput(content, rawOutput) {
|
|
15999
|
-
const parts = [];
|
|
16000
|
-
let pictured = false;
|
|
16001
|
-
for (const c of content) {
|
|
16002
|
-
if (c.type === "content") {
|
|
16003
|
-
if (c.content.type === "text")
|
|
16004
|
-
parts.push(c.content.text);
|
|
16005
|
-
else if (c.content.type === "image")
|
|
16006
|
-
pictured = true;
|
|
16007
|
-
} else if (c.type === "diff") {
|
|
16008
|
-
const body = unifiedDiff(c.oldText ?? "", c.newText);
|
|
16009
|
-
if (body)
|
|
16010
|
-
parts.push(`\`\`\`diff
|
|
16011
|
-
${body}
|
|
16012
|
-
\`\`\``);
|
|
16013
|
-
}
|
|
16014
|
-
}
|
|
16015
|
-
if (parts.length > 0)
|
|
16016
|
-
return truncate(parts.join(`
|
|
16017
|
-
`));
|
|
16018
|
-
if (pictured)
|
|
16019
|
-
return "";
|
|
16020
|
-
if (typeof rawOutput === "string")
|
|
16021
|
-
return truncate(rawOutput);
|
|
16022
|
-
if (rawOutput && typeof rawOutput === "object") {
|
|
16023
|
-
const rec = rawOutput;
|
|
16024
|
-
for (const k of ["formatted_output", "output", "stdout", "text"])
|
|
16025
|
-
if (typeof rec[k] === "string")
|
|
16026
|
-
return truncate(rec[k]);
|
|
16027
|
-
return truncate(JSON.stringify(rawOutput, null, 1));
|
|
16028
|
-
}
|
|
16029
|
-
return "";
|
|
16030
|
-
}
|
|
16031
|
-
function truncate(s, max = 4000) {
|
|
16032
|
-
return s.length > max ? `${s.slice(0, max)}
|
|
16033
|
-
\u2026 (${s.length - max} more chars)` : s;
|
|
16034
|
-
}
|
|
16035
|
-
function mapCommands(cmds) {
|
|
16036
|
-
const seen = new Set;
|
|
16037
|
-
return cmds.filter((c) => {
|
|
16038
|
-
if (typeof c.name !== "string" || c.name.length === 0 || c.name.length > 120 || seen.has(c.name))
|
|
16039
|
-
return false;
|
|
16040
|
-
seen.add(c.name);
|
|
16041
|
-
return true;
|
|
16042
|
-
}).slice(0, 300).map((c) => ({
|
|
16043
|
-
name: c.name,
|
|
16044
|
-
description: c.description ?? "",
|
|
16045
|
-
...c.input?.hint ? { hint: c.input.hint } : {}
|
|
16046
|
-
}));
|
|
16047
|
-
}
|
|
16048
|
-
function mapStopReason(r) {
|
|
16049
|
-
return r === "cancelled" ? "interrupted" : r;
|
|
16050
|
-
}
|
|
16051
|
-
|
|
16052
16060
|
// ../daemon/src/agent/acp/ratelimit.ts
|
|
16053
16061
|
var RATE_LIMIT_META = "_claude/rateLimit";
|
|
16054
16062
|
var STATUSES = new Set(["allowed", "allowed_warning", "rejected"]);
|
|
@@ -16123,7 +16131,10 @@ class AcpSession {
|
|
|
16123
16131
|
running = false;
|
|
16124
16132
|
interrupted = false;
|
|
16125
16133
|
stopped = false;
|
|
16126
|
-
|
|
16134
|
+
holds = 0;
|
|
16135
|
+
get held() {
|
|
16136
|
+
return this.holds > 0;
|
|
16137
|
+
}
|
|
16127
16138
|
conn = null;
|
|
16128
16139
|
connecting = null;
|
|
16129
16140
|
opening = null;
|
|
@@ -16171,15 +16182,30 @@ class AcpSession {
|
|
|
16171
16182
|
this.emit({ type: "tool-end", toolId, output: output.join(`
|
|
16172
16183
|
`), isError: true });
|
|
16173
16184
|
}
|
|
16174
|
-
|
|
16185
|
+
const going = new Set;
|
|
16186
|
+
for (const { event } of this.log.entries) {
|
|
16175
16187
|
this.trackHandoff(event);
|
|
16188
|
+
if (event.type === "handoff-go")
|
|
16189
|
+
going.add(event.id);
|
|
16190
|
+
else if (event.type === "handoff" || event.type === "handoff-declined")
|
|
16191
|
+
going.delete(event.id);
|
|
16192
|
+
}
|
|
16193
|
+
for (const id of going) {
|
|
16194
|
+
this.emit({
|
|
16195
|
+
type: "handoff-declined",
|
|
16196
|
+
id,
|
|
16197
|
+
outcome: "failed",
|
|
16198
|
+
reason: "Toyon stopped while the worktree was being made",
|
|
16199
|
+
ts: Date.now()
|
|
16200
|
+
});
|
|
16201
|
+
}
|
|
16176
16202
|
if (this.openHandoffs.size > 0)
|
|
16177
16203
|
this.syncStatus();
|
|
16178
16204
|
}
|
|
16179
16205
|
trackHandoff(event) {
|
|
16180
16206
|
if (event.type === "handoff-proposed")
|
|
16181
16207
|
return this.openHandoffs.add(event.id).size > 0;
|
|
16182
|
-
if (event.type === "handoff" || event.type === "handoff-declined")
|
|
16208
|
+
if (event.type === "handoff-go" || event.type === "handoff" || event.type === "handoff-declined")
|
|
16183
16209
|
return this.openHandoffs.delete(event.id);
|
|
16184
16210
|
return false;
|
|
16185
16211
|
}
|
|
@@ -16316,14 +16342,14 @@ class AcpSession {
|
|
|
16316
16342
|
fireAndForget(this.d.worktreeId, this.drain(), "agent drain");
|
|
16317
16343
|
}
|
|
16318
16344
|
hold() {
|
|
16319
|
-
this.
|
|
16345
|
+
this.holds++;
|
|
16320
16346
|
let released = false;
|
|
16321
16347
|
return () => {
|
|
16322
16348
|
if (released)
|
|
16323
16349
|
return;
|
|
16324
16350
|
released = true;
|
|
16325
|
-
this.
|
|
16326
|
-
if (!this.running && this.queue.length > 0)
|
|
16351
|
+
this.holds--;
|
|
16352
|
+
if (!this.held && !this.running && this.queue.length > 0)
|
|
16327
16353
|
fireAndForget(this.d.worktreeId, this.drain(), "agent drain");
|
|
16328
16354
|
};
|
|
16329
16355
|
}
|
|
@@ -18630,6 +18656,8 @@ class RuntimeRegistry {
|
|
|
18630
18656
|
}
|
|
18631
18657
|
if (this.settingUp.has(id))
|
|
18632
18658
|
return;
|
|
18659
|
+
if (wt.kind === "spare" && wt.phase !== undefined && wt.phase !== "ready")
|
|
18660
|
+
return;
|
|
18633
18661
|
const repo = this.deps.state.repos.find((r) => r.id === wt.repoId);
|
|
18634
18662
|
if (repo)
|
|
18635
18663
|
await this.start(wt, repo);
|
|
@@ -19424,7 +19452,7 @@ function openAskOf(entries) {
|
|
|
19424
19452
|
open.delete(e.id);
|
|
19425
19453
|
else if (e.type === "handoff-proposed")
|
|
19426
19454
|
open.set(e.id, `Continue in ${e.repo.name}?`);
|
|
19427
|
-
else if (e.type === "handoff" || e.type === "handoff-declined")
|
|
19455
|
+
else if (e.type === "handoff-go" || e.type === "handoff" || e.type === "handoff-declined")
|
|
19428
19456
|
open.delete(e.id);
|
|
19429
19457
|
}
|
|
19430
19458
|
const last = [...open.values()].at(-1)?.trim();
|
|
@@ -19714,32 +19742,83 @@ function makePlanner(agents) {
|
|
|
19714
19742
|
return async (request, cwd, agentId) => parsePlan(await askFreshAgent(agents, agentId, cwd, PLAN_SYSTEM, planPrompt(request), { quick: "prefer" }));
|
|
19715
19743
|
}
|
|
19716
19744
|
|
|
19745
|
+
// ../daemon/src/agent/transcriptForward.ts
|
|
19746
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
19747
|
+
import { join as join12 } from "path";
|
|
19748
|
+
function forwardLine(line) {
|
|
19749
|
+
if (!line.includes('"page-error"'))
|
|
19750
|
+
return null;
|
|
19751
|
+
let entry;
|
|
19752
|
+
try {
|
|
19753
|
+
entry = JSON.parse(line);
|
|
19754
|
+
} catch {
|
|
19755
|
+
return null;
|
|
19756
|
+
}
|
|
19757
|
+
const e = entry.event;
|
|
19758
|
+
if (e?.type !== "page-error" || Array.isArray(e.errors) || typeof e.message !== "string")
|
|
19759
|
+
return null;
|
|
19760
|
+
const { message, ...rest } = e;
|
|
19761
|
+
return JSON.stringify({ ...entry, event: { ...rest, errors: [message] } });
|
|
19762
|
+
}
|
|
19763
|
+
function forwardText(text) {
|
|
19764
|
+
let changed = 0;
|
|
19765
|
+
const lines = text.split(`
|
|
19766
|
+
`).map((line) => {
|
|
19767
|
+
const next = forwardLine(line);
|
|
19768
|
+
if (next === null)
|
|
19769
|
+
return line;
|
|
19770
|
+
changed++;
|
|
19771
|
+
return next;
|
|
19772
|
+
});
|
|
19773
|
+
return { text: changed ? lines.join(`
|
|
19774
|
+
`) : text, changed };
|
|
19775
|
+
}
|
|
19776
|
+
function forwardTranscripts(dir) {
|
|
19777
|
+
let files = 0;
|
|
19778
|
+
let rows = 0;
|
|
19779
|
+
for (const name of readdirSync5(dir)) {
|
|
19780
|
+
if (!name.endsWith(".jsonl"))
|
|
19781
|
+
continue;
|
|
19782
|
+
const path = join12(dir, name);
|
|
19783
|
+
const { text, changed } = forwardText(readFileSync11(path, "utf8"));
|
|
19784
|
+
if (!changed)
|
|
19785
|
+
continue;
|
|
19786
|
+
const tmp = `${path}.tmp`;
|
|
19787
|
+
writeFileSync6(tmp, text);
|
|
19788
|
+
renameSync2(tmp, path);
|
|
19789
|
+
files++;
|
|
19790
|
+
rows += changed;
|
|
19791
|
+
}
|
|
19792
|
+
if (files)
|
|
19793
|
+
log.info("transcripts", `rewrote ${rows} page-error row(s) in ${files} transcript(s) to the list shape`);
|
|
19794
|
+
}
|
|
19795
|
+
|
|
19717
19796
|
// ../daemon/src/core/assets.ts
|
|
19718
|
-
import { existsSync as existsSync14, readdirSync as
|
|
19719
|
-
import { join as
|
|
19797
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync5 } from "fs";
|
|
19798
|
+
import { join as join13, resolve as resolve5 } from "path";
|
|
19720
19799
|
function locateAssets(here, env = process.env) {
|
|
19721
|
-
const packaged = existsSync14(
|
|
19800
|
+
const packaged = existsSync14(join13(here, "shell", "index.html"));
|
|
19722
19801
|
return {
|
|
19723
|
-
shellDist: env.TOYON_SHELL_DIST ?? (packaged ?
|
|
19724
|
-
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ?
|
|
19725
|
-
helperStub: packaged ?
|
|
19726
|
-
cliEntry: packaged ?
|
|
19802
|
+
shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join13(here, "shell") : join13(here, "../../shell/dist")),
|
|
19803
|
+
bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join13(here, "bridge.js") : join13(here, "../../bridge/dist/bridge.js")),
|
|
19804
|
+
helperStub: packaged ? join13(here, "helper-stub") : join13(here, "../dist/helper-stub"),
|
|
19805
|
+
cliEntry: packaged ? join13(here, "cli.js") : join13(here, "../../cli/src/cli.ts"),
|
|
19727
19806
|
sourceRoot: packaged ? null : resolve5(here, "../../.."),
|
|
19728
|
-
packageJson: packaged ?
|
|
19807
|
+
packageJson: packaged ? join13(here, "..", "package.json") : null
|
|
19729
19808
|
};
|
|
19730
19809
|
}
|
|
19731
19810
|
var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
|
|
19732
19811
|
function pruneAssets(shellDist) {
|
|
19733
|
-
const dir =
|
|
19812
|
+
const dir = join13(shellDist, "assets");
|
|
19734
19813
|
let names;
|
|
19735
19814
|
try {
|
|
19736
|
-
names =
|
|
19815
|
+
names = readdirSync6(dir);
|
|
19737
19816
|
} catch {
|
|
19738
19817
|
return 0;
|
|
19739
19818
|
}
|
|
19740
19819
|
const found = [];
|
|
19741
19820
|
for (const name of names) {
|
|
19742
|
-
const path =
|
|
19821
|
+
const path = join13(dir, name);
|
|
19743
19822
|
try {
|
|
19744
19823
|
found.push({ path, mtime: statSync5(path).mtimeMs });
|
|
19745
19824
|
} catch {}
|
|
@@ -20281,7 +20360,7 @@ class GrantCodes {
|
|
|
20281
20360
|
import { createHash as createHash3 } from "crypto";
|
|
20282
20361
|
import { existsSync as existsSync16 } from "fs";
|
|
20283
20362
|
import { chmod, copyFile as copyFile3, mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
20284
|
-
import { basename as basename4, dirname as dirname5, join as
|
|
20363
|
+
import { basename as basename4, dirname as dirname5, join as join14 } from "path";
|
|
20285
20364
|
var EXECUTABLE = "Toyon";
|
|
20286
20365
|
function helperWanted(where) {
|
|
20287
20366
|
return where.platform === "darwin" && !where.cloud && where.port === DAEMON_DEFAULT_PORT && where.home === undefined;
|
|
@@ -20364,19 +20443,19 @@ class Helper {
|
|
|
20364
20443
|
return "skipped";
|
|
20365
20444
|
const stubHash = createHash3("sha256").update(await readFile2(spec.stub)).digest("hex").slice(0, 16);
|
|
20366
20445
|
const { plist, script } = helperFiles(spec, stubHash);
|
|
20367
|
-
const contents =
|
|
20368
|
-
const resources =
|
|
20369
|
-
const stub =
|
|
20370
|
-
const scriptFile =
|
|
20446
|
+
const contents = join14(spec.dir, "Contents");
|
|
20447
|
+
const resources = join14(contents, "Resources");
|
|
20448
|
+
const stub = join14(contents, "MacOS", EXECUTABLE);
|
|
20449
|
+
const scriptFile = join14(resources, "launch.sh");
|
|
20371
20450
|
if (existsSync16(stub) && existsSync16(scriptFile) && await readFile2(scriptFile, "utf8") === script)
|
|
20372
20451
|
return "kept";
|
|
20373
|
-
await mkdir2(
|
|
20452
|
+
await mkdir2(join14(contents, "MacOS"), { recursive: true });
|
|
20374
20453
|
await mkdir2(resources, { recursive: true });
|
|
20375
|
-
await writeFile2(
|
|
20454
|
+
await writeFile2(join14(contents, "Info.plist"), plist);
|
|
20376
20455
|
await writeFile2(scriptFile, script, { mode: 493 });
|
|
20377
20456
|
await copyFile3(spec.stub, stub);
|
|
20378
20457
|
await chmod(stub, 493);
|
|
20379
|
-
if (!existsSync16(
|
|
20458
|
+
if (!existsSync16(join14(resources, "AppIcon.icns")))
|
|
20380
20459
|
await this.drawIcon(resources);
|
|
20381
20460
|
await this.run(["xattr", "-cr", spec.dir]);
|
|
20382
20461
|
if (!await this.run(["codesign", "--force", "--deep", "-s", "-", spec.dir])) {
|
|
@@ -20387,16 +20466,16 @@ class Helper {
|
|
|
20387
20466
|
}
|
|
20388
20467
|
async drawIcon(resources) {
|
|
20389
20468
|
const svg = this.spec.icon;
|
|
20390
|
-
const tmp =
|
|
20391
|
-
const set =
|
|
20469
|
+
const tmp = join14(resources, "iconset.tmp");
|
|
20470
|
+
const set = join14(tmp, "AppIcon.iconset");
|
|
20392
20471
|
await mkdir2(set, { recursive: true });
|
|
20393
20472
|
await this.run(["qlmanage", "-t", "-s", "1024", "-o", tmp, svg]);
|
|
20394
|
-
const big =
|
|
20473
|
+
const big = join14(tmp, `${basename4(svg)}.png`);
|
|
20395
20474
|
if (existsSync16(big)) {
|
|
20396
20475
|
for (const size of [16, 32, 64, 128, 256, 512, 1024]) {
|
|
20397
|
-
await this.run(["sips", "-z", String(size), String(size), big, "--out",
|
|
20476
|
+
await this.run(["sips", "-z", String(size), String(size), big, "--out", join14(set, `icon_${size}x${size}.png`)]);
|
|
20398
20477
|
}
|
|
20399
|
-
await this.run(["iconutil", "-c", "icns", set, "-o",
|
|
20478
|
+
await this.run(["iconutil", "-c", "icns", set, "-o", join14(resources, "AppIcon.icns")]);
|
|
20400
20479
|
}
|
|
20401
20480
|
await rm(tmp, { recursive: true, force: true });
|
|
20402
20481
|
}
|
|
@@ -20620,7 +20699,7 @@ class Origins {
|
|
|
20620
20699
|
}
|
|
20621
20700
|
|
|
20622
20701
|
// ../daemon/src/core/remoteSwitch.ts
|
|
20623
|
-
import { writeFileSync as
|
|
20702
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
20624
20703
|
class RemoteSwitch {
|
|
20625
20704
|
d;
|
|
20626
20705
|
constructor(d) {
|
|
@@ -20653,7 +20732,7 @@ class RemoteSwitch {
|
|
|
20653
20732
|
const accepted = acceptRemote(view, this.d.policy);
|
|
20654
20733
|
if (typeof accepted === "string")
|
|
20655
20734
|
throw new UserError(accepted);
|
|
20656
|
-
|
|
20735
|
+
writeFileSync7(this.d.file, `${JSON.stringify(accepted, null, 2)}
|
|
20657
20736
|
`);
|
|
20658
20737
|
return this.live(accepted);
|
|
20659
20738
|
}
|
|
@@ -20835,7 +20914,7 @@ class SelfWatch {
|
|
|
20835
20914
|
|
|
20836
20915
|
// ../daemon/src/core/state.ts
|
|
20837
20916
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
20838
|
-
import { existsSync as existsSync17, readFileSync as
|
|
20917
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
20839
20918
|
var TRUSTED_MAX = 16;
|
|
20840
20919
|
var DAEMON_GROUPS = "daemon";
|
|
20841
20920
|
var GROUPS_SAVE_MS = 250;
|
|
@@ -20848,7 +20927,7 @@ function loadState(paths) {
|
|
|
20848
20927
|
return structuredClone(empty);
|
|
20849
20928
|
let raw;
|
|
20850
20929
|
try {
|
|
20851
|
-
raw =
|
|
20930
|
+
raw = readFileSync12(STATE_FILE, "utf8");
|
|
20852
20931
|
} catch (e) {
|
|
20853
20932
|
log.error("state", `cannot read ${STATE_FILE}; starting empty`, e);
|
|
20854
20933
|
return structuredClone(empty);
|
|
@@ -20859,7 +20938,7 @@ function loadState(paths) {
|
|
|
20859
20938
|
} catch (e) {
|
|
20860
20939
|
const backup = `${STATE_FILE}.corrupt-${Date.now()}`;
|
|
20861
20940
|
try {
|
|
20862
|
-
|
|
20941
|
+
writeFileSync8(backup, raw);
|
|
20863
20942
|
} catch (be) {
|
|
20864
20943
|
log.warn("state", "could not write the corrupt-state backup", be);
|
|
20865
20944
|
}
|
|
@@ -20890,16 +20969,16 @@ function loadState(paths) {
|
|
|
20890
20969
|
}
|
|
20891
20970
|
function saveState(paths, state) {
|
|
20892
20971
|
const tmp = `${paths.stateFile}.tmp`;
|
|
20893
|
-
|
|
20894
|
-
|
|
20972
|
+
writeFileSync8(tmp, JSON.stringify(state, null, 2));
|
|
20973
|
+
renameSync3(tmp, paths.stateFile);
|
|
20895
20974
|
}
|
|
20896
20975
|
function loadOrCreateToken(paths) {
|
|
20897
20976
|
ensureDirs(paths);
|
|
20898
20977
|
const TOKEN_FILE = paths.tokenFile;
|
|
20899
20978
|
if (existsSync17(TOKEN_FILE))
|
|
20900
|
-
return
|
|
20979
|
+
return readFileSync12(TOKEN_FILE, "utf8").trim();
|
|
20901
20980
|
const token = randomBytes6(32).toString("hex");
|
|
20902
|
-
|
|
20981
|
+
writeFileSync8(TOKEN_FILE, token, { mode: 384 });
|
|
20903
20982
|
return token;
|
|
20904
20983
|
}
|
|
20905
20984
|
|
|
@@ -20985,6 +21064,10 @@ class StateStore {
|
|
|
20985
21064
|
this.save();
|
|
20986
21065
|
}
|
|
20987
21066
|
removeWorktree(id) {
|
|
21067
|
+
const wt = this.state.worktrees.find((w) => w.id === id);
|
|
21068
|
+
const repo = wt && wt.kind !== "spare" ? this.repo(wt.repoId) : undefined;
|
|
21069
|
+
if (wt && repo)
|
|
21070
|
+
repo.promptedAt = Math.max(repo.promptedAt ?? 0, sentAt(wt));
|
|
20988
21071
|
this.state.worktrees = this.state.worktrees.filter((w) => w.id !== id);
|
|
20989
21072
|
delete this.state.sessions[id];
|
|
20990
21073
|
if (this.state.seen)
|
|
@@ -21168,7 +21251,7 @@ class StateStore {
|
|
|
21168
21251
|
|
|
21169
21252
|
// ../daemon/src/core/tailnet.ts
|
|
21170
21253
|
var PHONE_OS = new Set(["android", "ios"]);
|
|
21171
|
-
function
|
|
21254
|
+
function tailnetListed(json) {
|
|
21172
21255
|
let st;
|
|
21173
21256
|
try {
|
|
21174
21257
|
st = JSON.parse(json);
|
|
@@ -21183,12 +21266,31 @@ function tailnetPeers(json) {
|
|
|
21183
21266
|
continue;
|
|
21184
21267
|
const host = dnsName(p.DNSName);
|
|
21185
21268
|
const name = p.HostName || host.split(".")[0] || "a device";
|
|
21186
|
-
peers.push({
|
|
21269
|
+
peers.push({
|
|
21270
|
+
name,
|
|
21271
|
+
host,
|
|
21272
|
+
ip: p.TailscaleIPs?.[0] ?? null,
|
|
21273
|
+
os: (p.OS ?? "").toLowerCase(),
|
|
21274
|
+
online: p.Online === true
|
|
21275
|
+
});
|
|
21187
21276
|
}
|
|
21188
|
-
return peers;
|
|
21277
|
+
return { here: st.Self?.Online !== false, peers };
|
|
21189
21278
|
}
|
|
21190
21279
|
var isPhone = (p) => PHONE_OS.has(p.os);
|
|
21280
|
+
function loginUrl(json) {
|
|
21281
|
+
try {
|
|
21282
|
+
const st = JSON.parse(json);
|
|
21283
|
+
return typeof st?.AuthURL === "string" && st.AuthURL !== "" ? st.AuthURL : null;
|
|
21284
|
+
} catch {
|
|
21285
|
+
return null;
|
|
21286
|
+
}
|
|
21287
|
+
}
|
|
21288
|
+
var lastLine2 = (r) => (r.err.trim() || r.out.trim()).split(`
|
|
21289
|
+
`).at(-1) ?? "tailscale failed";
|
|
21290
|
+
var LOGIN_URL_WAIT_MS = 4000;
|
|
21291
|
+
var LOGIN_URL_POLL_MS = 300;
|
|
21191
21292
|
var PROBE_TIMEOUT_MS2 = 2000;
|
|
21293
|
+
var ANSWERED_RECENTLY_MS = 3 * 60000;
|
|
21192
21294
|
var probe = async (origin, named) => {
|
|
21193
21295
|
try {
|
|
21194
21296
|
const r = await fetch(`${origin}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2) });
|
|
@@ -21205,6 +21307,8 @@ var probe = async (origin, named) => {
|
|
|
21205
21307
|
var PING_TIMEOUT = "2s";
|
|
21206
21308
|
var pingArgs = (ip) => ["ping", "--until-direct=false", "--c", "1", "--timeout", PING_TIMEOUT, ip];
|
|
21207
21309
|
var STATUS_FRESH_MS = 5000;
|
|
21310
|
+
var VIEW_FRESH_MS = 4000;
|
|
21311
|
+
var VIEW_STALE_MAX_MS = 60000;
|
|
21208
21312
|
var READINESS_EVERY_MS = 60000;
|
|
21209
21313
|
class Tailnet {
|
|
21210
21314
|
d;
|
|
@@ -21212,6 +21316,9 @@ class Tailnet {
|
|
|
21212
21316
|
statusAt = 0;
|
|
21213
21317
|
status = null;
|
|
21214
21318
|
timer = null;
|
|
21319
|
+
answered = new Map;
|
|
21320
|
+
lastView = null;
|
|
21321
|
+
viewing = null;
|
|
21215
21322
|
constructor(d) {
|
|
21216
21323
|
this.d = d;
|
|
21217
21324
|
}
|
|
@@ -21229,7 +21336,9 @@ class Tailnet {
|
|
|
21229
21336
|
return this.readinessNow;
|
|
21230
21337
|
}
|
|
21231
21338
|
async refresh() {
|
|
21232
|
-
|
|
21339
|
+
return this.noteReadiness(await this.statusJson(true));
|
|
21340
|
+
}
|
|
21341
|
+
noteReadiness(json) {
|
|
21233
21342
|
const next = this.d.ts === null ? {
|
|
21234
21343
|
state: "not-installed",
|
|
21235
21344
|
name: null,
|
|
@@ -21241,6 +21350,32 @@ class Tailnet {
|
|
|
21241
21350
|
this.d.hub.emit("tailscaleChanged");
|
|
21242
21351
|
return next;
|
|
21243
21352
|
}
|
|
21353
|
+
async up() {
|
|
21354
|
+
const ts = this.d.ts;
|
|
21355
|
+
if (ts === null)
|
|
21356
|
+
return "Tailscale is not installed here";
|
|
21357
|
+
const r = await ts(["up"]);
|
|
21358
|
+
await this.refresh();
|
|
21359
|
+
return r.ok ? null : lastLine2(r);
|
|
21360
|
+
}
|
|
21361
|
+
async login() {
|
|
21362
|
+
const ts = this.d.ts;
|
|
21363
|
+
if (ts === null)
|
|
21364
|
+
return { why: "Tailscale is not installed here" };
|
|
21365
|
+
fireAndForget("tailscale", ts(["login"]), "tailscale login");
|
|
21366
|
+
const sleep = this.d.sleep ?? ((ms) => Bun.sleep(ms));
|
|
21367
|
+
const now = this.d.now ?? Date.now;
|
|
21368
|
+
const until = now() + LOGIN_URL_WAIT_MS;
|
|
21369
|
+
for (;; ) {
|
|
21370
|
+
const json = await this.statusJson(true);
|
|
21371
|
+
const url = json === null ? null : loginUrl(json);
|
|
21372
|
+
if (url !== null)
|
|
21373
|
+
return { url };
|
|
21374
|
+
if (now() >= until)
|
|
21375
|
+
return { why: "Tailscale did not offer a sign-in link; run `tailscale login` in a terminal" };
|
|
21376
|
+
await sleep(LOGIN_URL_POLL_MS);
|
|
21377
|
+
}
|
|
21378
|
+
}
|
|
21244
21379
|
statusJson(fresh = false) {
|
|
21245
21380
|
const ts = this.d.ts;
|
|
21246
21381
|
if (ts === null)
|
|
@@ -21252,26 +21387,50 @@ class Tailnet {
|
|
|
21252
21387
|
}
|
|
21253
21388
|
return this.status;
|
|
21254
21389
|
}
|
|
21255
|
-
async
|
|
21390
|
+
async listed() {
|
|
21256
21391
|
const json = await this.statusJson();
|
|
21257
|
-
|
|
21392
|
+
this.noteReadiness(json);
|
|
21393
|
+
return json === null ? null : tailnetListed(json);
|
|
21258
21394
|
}
|
|
21259
|
-
async
|
|
21395
|
+
async view() {
|
|
21396
|
+
const now = (this.d.now ?? Date.now)();
|
|
21397
|
+
const last = this.lastView;
|
|
21398
|
+
if (last && now - last.at < VIEW_FRESH_MS)
|
|
21399
|
+
return last.view;
|
|
21400
|
+
if (this.viewing === null) {
|
|
21401
|
+
this.viewing = this.makeView().finally(() => {
|
|
21402
|
+
this.viewing = null;
|
|
21403
|
+
});
|
|
21404
|
+
}
|
|
21405
|
+
if (last && now - last.at < VIEW_STALE_MAX_MS) {
|
|
21406
|
+
fireAndForget("tailnet", this.viewing, "tailnet view");
|
|
21407
|
+
return last.view;
|
|
21408
|
+
}
|
|
21409
|
+
return this.viewing;
|
|
21410
|
+
}
|
|
21411
|
+
async makeView() {
|
|
21260
21412
|
const ts = this.d.ts;
|
|
21261
|
-
const
|
|
21262
|
-
if (
|
|
21263
|
-
return null;
|
|
21264
|
-
return Promise.all(peers.filter(isPhone).map(async (p) => ({
|
|
21265
|
-
name: p.name,
|
|
21266
|
-
online: p.ip !== null && (await ts(pingArgs(p.ip))).ok
|
|
21267
|
-
})));
|
|
21268
|
-
}
|
|
21269
|
-
async machines() {
|
|
21270
|
-
const peers = await this.peers();
|
|
21271
|
-
if (peers === null)
|
|
21413
|
+
const listed = await this.listed();
|
|
21414
|
+
if (listed === null || ts === null)
|
|
21272
21415
|
return null;
|
|
21273
21416
|
const health = this.d.health ?? probe;
|
|
21274
|
-
|
|
21417
|
+
const now = (this.d.now ?? Date.now)();
|
|
21418
|
+
const devices = await Promise.all(listed.peers.map(async (p) => {
|
|
21419
|
+
const kind = isPhone(p) ? "phone" : "machine";
|
|
21420
|
+
const ask = listed.here;
|
|
21421
|
+
const [reached, got] = await Promise.all([
|
|
21422
|
+
ask && p.ip !== null ? ts(pingArgs(p.ip)).then((r) => r.ok) : false,
|
|
21423
|
+
ask && kind === "machine" && p.host !== "" ? health(`https://${p.host}`, true) : null
|
|
21424
|
+
]);
|
|
21425
|
+
if (got)
|
|
21426
|
+
this.answered.set(p.host, { machine: got.machine, at: now });
|
|
21427
|
+
const last = kind === "machine" ? this.answered.get(p.host) : undefined;
|
|
21428
|
+
const toyon = got ? { machine: got.machine, answering: true } : last && now - last.at < ANSWERED_RECENTLY_MS ? { machine: last.machine, answering: false } : null;
|
|
21429
|
+
return { name: p.name, host: p.host, kind, reached, toyon };
|
|
21430
|
+
}));
|
|
21431
|
+
const view = { here: listed.here, devices };
|
|
21432
|
+
this.lastView = { view, at: now };
|
|
21433
|
+
return view;
|
|
21275
21434
|
}
|
|
21276
21435
|
isToyon(origin) {
|
|
21277
21436
|
return (this.d.health ?? probe)(origin, false).then((r) => r !== null);
|
|
@@ -21279,7 +21438,7 @@ class Tailnet {
|
|
|
21279
21438
|
}
|
|
21280
21439
|
|
|
21281
21440
|
// ../daemon/src/design/service.ts
|
|
21282
|
-
import { join as
|
|
21441
|
+
import { join as join15 } from "path";
|
|
21283
21442
|
|
|
21284
21443
|
// ../daemon/src/design/scan.ts
|
|
21285
21444
|
var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
|
|
@@ -21729,7 +21888,7 @@ class DesignService {
|
|
|
21729
21888
|
async function readAll(root, paths) {
|
|
21730
21889
|
const out = [];
|
|
21731
21890
|
for (const path of paths) {
|
|
21732
|
-
const file = Bun.file(
|
|
21891
|
+
const file = Bun.file(join15(root, path));
|
|
21733
21892
|
if (file.size === 0 || file.size > MAX_FILE_BYTES)
|
|
21734
21893
|
continue;
|
|
21735
21894
|
try {
|
|
@@ -21844,14 +22003,14 @@ function typicalUse(classes) {
|
|
|
21844
22003
|
var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
|
|
21845
22004
|
async function loadProjectTypescript(root) {
|
|
21846
22005
|
try {
|
|
21847
|
-
return await import(
|
|
22006
|
+
return await import(join15(root, "node_modules", "typescript", "lib", "typescript.js"));
|
|
21848
22007
|
} catch {
|
|
21849
22008
|
return null;
|
|
21850
22009
|
}
|
|
21851
22010
|
}
|
|
21852
22011
|
|
|
21853
22012
|
// ../daemon/src/drafts/store.ts
|
|
21854
|
-
import { existsSync as existsSync18, readFileSync as
|
|
22013
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, renameSync as renameSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
21855
22014
|
var SAVE_DELAY_MS = 1000;
|
|
21856
22015
|
|
|
21857
22016
|
class DraftStore {
|
|
@@ -21991,7 +22150,7 @@ function readRecord(file) {
|
|
|
21991
22150
|
if (!existsSync18(file))
|
|
21992
22151
|
return [];
|
|
21993
22152
|
try {
|
|
21994
|
-
const raw = JSON.parse(
|
|
22153
|
+
const raw = JSON.parse(readFileSync13(file, "utf8"));
|
|
21995
22154
|
return raw && typeof raw === "object" ? Object.entries(raw) : [];
|
|
21996
22155
|
} catch (e) {
|
|
21997
22156
|
log.warn("drafts", `skipped an unreadable ${file}`, e);
|
|
@@ -22001,8 +22160,8 @@ function readRecord(file) {
|
|
|
22001
22160
|
function writeAtomic(file, value) {
|
|
22002
22161
|
const tmp = `${file}.tmp`;
|
|
22003
22162
|
try {
|
|
22004
|
-
|
|
22005
|
-
|
|
22163
|
+
writeFileSync9(tmp, JSON.stringify(value));
|
|
22164
|
+
renameSync4(tmp, file);
|
|
22006
22165
|
} catch (e) {
|
|
22007
22166
|
log.error("drafts", `could not write ${file}`, e);
|
|
22008
22167
|
}
|
|
@@ -22251,10 +22410,10 @@ import { basename as basename5, resolve as resolve7 } from "path";
|
|
|
22251
22410
|
import { existsSync as existsSync19, statSync as statSync6 } from "fs";
|
|
22252
22411
|
import { readdir } from "fs/promises";
|
|
22253
22412
|
import { homedir as homedir8 } from "os";
|
|
22254
|
-
import { dirname as dirname6, join as
|
|
22413
|
+
import { dirname as dirname6, join as join16 } from "path";
|
|
22255
22414
|
var MAX_ENTRIES = 40;
|
|
22256
22415
|
function expandTilde(raw) {
|
|
22257
|
-
return raw === "~" || raw.startsWith("~/") ?
|
|
22416
|
+
return raw === "~" || raw.startsWith("~/") ? join16(homedir8(), raw.slice(1)) : raw;
|
|
22258
22417
|
}
|
|
22259
22418
|
function collapseTilde(abs) {
|
|
22260
22419
|
const home = homedir8();
|
|
@@ -22265,7 +22424,7 @@ function holdsNothing(names) {
|
|
|
22265
22424
|
}
|
|
22266
22425
|
async function describeFolder(abs) {
|
|
22267
22426
|
const path = collapseTilde(abs);
|
|
22268
|
-
if (existsSync19(
|
|
22427
|
+
if (existsSync19(join16(abs, ".git")))
|
|
22269
22428
|
return { path, kind: "project" };
|
|
22270
22429
|
try {
|
|
22271
22430
|
return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
|
|
@@ -22282,7 +22441,7 @@ function describe2(typed, parent) {
|
|
|
22282
22441
|
return {
|
|
22283
22442
|
exists: existsSync19(typed),
|
|
22284
22443
|
isDir,
|
|
22285
|
-
isRepo: isDir && existsSync19(
|
|
22444
|
+
isRepo: isDir && existsSync19(join16(typed, ".git")),
|
|
22286
22445
|
parentExists: existsSync19(parent)
|
|
22287
22446
|
};
|
|
22288
22447
|
}
|
|
@@ -22307,8 +22466,8 @@ async function browsePath(raw) {
|
|
|
22307
22466
|
continue;
|
|
22308
22467
|
if (!name.toLowerCase().startsWith(prefix))
|
|
22309
22468
|
continue;
|
|
22310
|
-
const abs =
|
|
22311
|
-
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync19(
|
|
22469
|
+
const abs = join16(dir, name);
|
|
22470
|
+
entries.push({ path: collapseTilde(abs), name, isRepo: existsSync19(join16(abs, ".git")) });
|
|
22312
22471
|
}
|
|
22313
22472
|
entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
|
|
22314
22473
|
return { entries: entries.slice(0, MAX_ENTRIES), target };
|
|
@@ -22422,7 +22581,7 @@ function withFetchLock(repoPath, fn) {
|
|
|
22422
22581
|
var fileLockKey = (absPath) => `file:${absPath}`;
|
|
22423
22582
|
|
|
22424
22583
|
// ../daemon/src/git/status.ts
|
|
22425
|
-
import { join as
|
|
22584
|
+
import { join as join17 } from "path";
|
|
22426
22585
|
async function treeFingerprint(worktreePath) {
|
|
22427
22586
|
const [head, diff, untracked] = await Promise.all([
|
|
22428
22587
|
git(worktreePath, "rev-parse", "HEAD"),
|
|
@@ -22432,7 +22591,7 @@ async function treeFingerprint(worktreePath) {
|
|
|
22432
22591
|
const others = await Promise.all(untracked.out.split(`
|
|
22433
22592
|
`).filter(Boolean).map(async (p) => {
|
|
22434
22593
|
try {
|
|
22435
|
-
const f = Bun.file(
|
|
22594
|
+
const f = Bun.file(join17(worktreePath, p));
|
|
22436
22595
|
const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
|
|
22437
22596
|
return `${p}:${body}`;
|
|
22438
22597
|
} catch {
|
|
@@ -22517,7 +22676,7 @@ async function numstat(worktreePath, ...range) {
|
|
|
22517
22676
|
}
|
|
22518
22677
|
async function untrackedLines(worktreePath, file) {
|
|
22519
22678
|
try {
|
|
22520
|
-
const f = Bun.file(
|
|
22679
|
+
const f = Bun.file(join17(worktreePath, file));
|
|
22521
22680
|
if (f.size > 2000000)
|
|
22522
22681
|
return {};
|
|
22523
22682
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
@@ -22596,9 +22755,14 @@ async function behindUpstream(worktreePath) {
|
|
|
22596
22755
|
const r = await git(worktreePath, "rev-list", "--count", "HEAD..@{upstream}");
|
|
22597
22756
|
return r.ok ? Number(r.out) || 0 : null;
|
|
22598
22757
|
}
|
|
22599
|
-
async function aheadUpstream(worktreePath) {
|
|
22758
|
+
async function aheadUpstream(worktreePath, branch) {
|
|
22600
22759
|
const r = await git(worktreePath, "rev-list", "--count", "@{upstream}..HEAD");
|
|
22601
|
-
|
|
22760
|
+
if (r.ok)
|
|
22761
|
+
return Number(r.out) || 0;
|
|
22762
|
+
if (!branch)
|
|
22763
|
+
return null;
|
|
22764
|
+
const o = await git(worktreePath, "rev-list", "--count", `origin/${branch}..HEAD`);
|
|
22765
|
+
return o.ok ? Number(o.out) || 0 : null;
|
|
22602
22766
|
}
|
|
22603
22767
|
async function aheadBehind(worktreePath, defaultBr) {
|
|
22604
22768
|
const [a, b] = await Promise.all([
|
|
@@ -23283,7 +23447,7 @@ function prNumberOf(url) {
|
|
|
23283
23447
|
|
|
23284
23448
|
// ../daemon/src/preview/errors.ts
|
|
23285
23449
|
var KEPT = 5;
|
|
23286
|
-
var
|
|
23450
|
+
var ROWS_MAX = 3;
|
|
23287
23451
|
var TWICE_MS = 1000;
|
|
23288
23452
|
|
|
23289
23453
|
class PageErrorService {
|
|
@@ -23301,18 +23465,28 @@ class PageErrorService {
|
|
|
23301
23465
|
of(worktreeId) {
|
|
23302
23466
|
let h = this.held.get(worktreeId);
|
|
23303
23467
|
if (!h) {
|
|
23304
|
-
h = { recent: [],
|
|
23468
|
+
h = { recent: [], running: false, noted: new Set, rows: 0 };
|
|
23305
23469
|
this.held.set(worktreeId, h);
|
|
23306
23470
|
}
|
|
23307
23471
|
return h;
|
|
23308
23472
|
}
|
|
23309
23473
|
heard(worktreeId, event) {
|
|
23310
23474
|
const h = this.of(worktreeId);
|
|
23311
|
-
|
|
23312
|
-
|
|
23313
|
-
|
|
23314
|
-
|
|
23315
|
-
|
|
23475
|
+
switch (event.type) {
|
|
23476
|
+
case "user-message":
|
|
23477
|
+
case "fix-asked":
|
|
23478
|
+
h.noted = new Set;
|
|
23479
|
+
h.rows = 0;
|
|
23480
|
+
break;
|
|
23481
|
+
case "turn-start":
|
|
23482
|
+
h.running = true;
|
|
23483
|
+
break;
|
|
23484
|
+
case "turn-end":
|
|
23485
|
+
case "agent-error":
|
|
23486
|
+
case "agent-auth-required":
|
|
23487
|
+
h.running = false;
|
|
23488
|
+
this.flush(worktreeId, h, event.ts);
|
|
23489
|
+
break;
|
|
23316
23490
|
}
|
|
23317
23491
|
}
|
|
23318
23492
|
report(worktreeId, message) {
|
|
@@ -23328,13 +23502,20 @@ class PageErrorService {
|
|
|
23328
23502
|
h.recent.push({ message, ts: now });
|
|
23329
23503
|
if (h.recent.length > KEPT)
|
|
23330
23504
|
h.recent.splice(0, h.recent.length - KEPT);
|
|
23331
|
-
if (!h.
|
|
23505
|
+
if (!h.running)
|
|
23506
|
+
this.flush(worktreeId, h, now);
|
|
23507
|
+
}
|
|
23508
|
+
flush(worktreeId, h, ts) {
|
|
23509
|
+
const fresh = [...new Set(h.recent.map((e) => e.message))].filter((m) => !h.noted.has(m));
|
|
23510
|
+
if (fresh.length === 0 || h.rows >= ROWS_MAX)
|
|
23332
23511
|
return;
|
|
23333
23512
|
const agent = this.d.runtime.agentFor(worktreeId);
|
|
23334
23513
|
if (!agent)
|
|
23335
23514
|
return;
|
|
23336
|
-
|
|
23337
|
-
|
|
23515
|
+
for (const m of fresh)
|
|
23516
|
+
h.noted.add(m);
|
|
23517
|
+
h.rows++;
|
|
23518
|
+
agent.note({ type: "page-error", errors: fresh, ts });
|
|
23338
23519
|
}
|
|
23339
23520
|
loaded(worktreeId) {
|
|
23340
23521
|
const h = this.held.get(worktreeId);
|
|
@@ -23598,9 +23779,9 @@ class AfterLand {
|
|
|
23598
23779
|
}
|
|
23599
23780
|
|
|
23600
23781
|
// ../daemon/src/repos/registry.ts
|
|
23601
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as
|
|
23782
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
23602
23783
|
import { homedir as homedir10 } from "os";
|
|
23603
|
-
import { basename as basename7, dirname as dirname8, join as
|
|
23784
|
+
import { basename as basename7, dirname as dirname8, join as join21 } from "path";
|
|
23604
23785
|
|
|
23605
23786
|
// ../daemon/src/git/base.ts
|
|
23606
23787
|
async function resolveBase(repoPath, defaultBranch, route) {
|
|
@@ -23611,8 +23792,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
|
|
|
23611
23792
|
}
|
|
23612
23793
|
|
|
23613
23794
|
// ../daemon/src/repos/config.ts
|
|
23614
|
-
import { closeSync, existsSync as existsSync20, openSync as openSync2, readdirSync as
|
|
23615
|
-
import { join as
|
|
23795
|
+
import { closeSync, existsSync as existsSync20, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, readSync, statSync as statSync7 } from "fs";
|
|
23796
|
+
import { join as join18 } from "path";
|
|
23616
23797
|
|
|
23617
23798
|
// ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
23618
23799
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -24976,7 +25157,7 @@ function applyEdits(text, edits) {
|
|
|
24976
25157
|
|
|
24977
25158
|
// ../daemon/src/repos/config.ts
|
|
24978
25159
|
var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
|
|
24979
|
-
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync20(
|
|
25160
|
+
var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync20(join18(repoPath, rel)));
|
|
24980
25161
|
var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
24981
25162
|
function parseSettings(text) {
|
|
24982
25163
|
const errors = [];
|
|
@@ -25001,7 +25182,7 @@ function readConfigFile(repoPath) {
|
|
|
25001
25182
|
for (const rel of files) {
|
|
25002
25183
|
let raw;
|
|
25003
25184
|
try {
|
|
25004
|
-
raw = parseSettings(
|
|
25185
|
+
raw = parseSettings(readFileSync14(join18(repoPath, rel), "utf8"));
|
|
25005
25186
|
} catch (e) {
|
|
25006
25187
|
return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
25007
25188
|
}
|
|
@@ -25060,12 +25241,12 @@ function configTarget(repoPath, made) {
|
|
|
25060
25241
|
}
|
|
25061
25242
|
function configBody(repoPath, rel, config) {
|
|
25062
25243
|
const place = PLACES.find((p) => p.local === rel);
|
|
25063
|
-
const abs = place &&
|
|
25244
|
+
const abs = place && join18(repoPath, place.shared);
|
|
25064
25245
|
if (!abs || !existsSync20(abs))
|
|
25065
25246
|
return config;
|
|
25066
25247
|
let shared;
|
|
25067
25248
|
try {
|
|
25068
|
-
shared = parseSettings(
|
|
25249
|
+
shared = parseSettings(readFileSync14(abs, "utf8"));
|
|
25069
25250
|
} catch {
|
|
25070
25251
|
return config;
|
|
25071
25252
|
}
|
|
@@ -25078,7 +25259,7 @@ function configText(repoPath, rel, config) {
|
|
|
25078
25259
|
let text;
|
|
25079
25260
|
let was;
|
|
25080
25261
|
try {
|
|
25081
|
-
text =
|
|
25262
|
+
text = readFileSync14(join18(repoPath, rel), "utf8");
|
|
25082
25263
|
was = parseSettings(text);
|
|
25083
25264
|
} catch {
|
|
25084
25265
|
return fresh;
|
|
@@ -25108,9 +25289,9 @@ function detectConfig(repoPath) {
|
|
|
25108
25289
|
return { config: explicit.config, needsSetup: false };
|
|
25109
25290
|
if (explicit)
|
|
25110
25291
|
log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
|
|
25111
|
-
const pkgPath =
|
|
25292
|
+
const pkgPath = join18(repoPath, "package.json");
|
|
25112
25293
|
if (existsSync20(pkgPath)) {
|
|
25113
|
-
const pkg = JSON.parse(
|
|
25294
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
25114
25295
|
const scripts = pkg.scripts ?? {};
|
|
25115
25296
|
const runner = detectRunner(repoPath, pkg.packageManager);
|
|
25116
25297
|
const pages = detectPages(repoPath, scripts, runner);
|
|
@@ -25131,7 +25312,7 @@ function detectConfig(repoPath) {
|
|
|
25131
25312
|
};
|
|
25132
25313
|
}
|
|
25133
25314
|
}
|
|
25134
|
-
if (existsSync20(
|
|
25315
|
+
if (existsSync20(join18(repoPath, "start.sh"))) {
|
|
25135
25316
|
return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
|
|
25136
25317
|
}
|
|
25137
25318
|
const assumed = assumeNothingToRun(repoPath);
|
|
@@ -25174,24 +25355,24 @@ function assumeNothingToRun(repoPath) {
|
|
|
25174
25355
|
return marker && !servesPage(repoPath) ? marker : undefined;
|
|
25175
25356
|
}
|
|
25176
25357
|
function markerIn(repoPath) {
|
|
25177
|
-
const named = NO_PAGE_MARKERS.find((f) => existsSync20(
|
|
25358
|
+
const named = NO_PAGE_MARKERS.find((f) => existsSync20(join18(repoPath, f)));
|
|
25178
25359
|
if (named)
|
|
25179
25360
|
return named;
|
|
25180
25361
|
let entries;
|
|
25181
25362
|
try {
|
|
25182
|
-
entries =
|
|
25363
|
+
entries = readdirSync7(repoPath);
|
|
25183
25364
|
} catch {
|
|
25184
25365
|
return;
|
|
25185
25366
|
}
|
|
25186
25367
|
return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
25187
25368
|
}
|
|
25188
25369
|
function servesPage(repoPath) {
|
|
25189
|
-
if (WEB_FILES.some((f) => existsSync20(
|
|
25370
|
+
if (WEB_FILES.some((f) => existsSync20(join18(repoPath, f))))
|
|
25190
25371
|
return true;
|
|
25191
|
-
if (/^web:/m.test(head(
|
|
25372
|
+
if (/^web:/m.test(head(join18(repoPath, "Procfile"))))
|
|
25192
25373
|
return true;
|
|
25193
25374
|
for (const [files, names] of WEB_DEPENDENCIES) {
|
|
25194
|
-
const text = files.map((f) => head(
|
|
25375
|
+
const text = files.map((f) => head(join18(repoPath, f))).join(`
|
|
25195
25376
|
`);
|
|
25196
25377
|
if (text.trim() && names.some((n) => mentions(text, n)))
|
|
25197
25378
|
return true;
|
|
@@ -25201,14 +25382,14 @@ function servesPage(repoPath) {
|
|
|
25201
25382
|
function frontEndBeside(repoPath) {
|
|
25202
25383
|
let dirs;
|
|
25203
25384
|
try {
|
|
25204
|
-
dirs =
|
|
25385
|
+
dirs = readdirSync7(repoPath, { withFileTypes: true });
|
|
25205
25386
|
} catch {
|
|
25206
25387
|
return false;
|
|
25207
25388
|
}
|
|
25208
25389
|
return dirs.some((d) => {
|
|
25209
25390
|
if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
|
|
25210
25391
|
return false;
|
|
25211
|
-
const pkg = head(
|
|
25392
|
+
const pkg = head(join18(repoPath, d.name, "package.json"));
|
|
25212
25393
|
if (!pkg)
|
|
25213
25394
|
return false;
|
|
25214
25395
|
try {
|
|
@@ -25264,11 +25445,11 @@ function isLocalDatabase(url) {
|
|
|
25264
25445
|
}
|
|
25265
25446
|
function detectServices(repoPath) {
|
|
25266
25447
|
const out = {};
|
|
25267
|
-
const compose = COMPOSE_FILES.find((f) => existsSync20(
|
|
25448
|
+
const compose = COMPOSE_FILES.find((f) => existsSync20(join18(repoPath, f)));
|
|
25268
25449
|
if (compose)
|
|
25269
25450
|
out.compose = compose;
|
|
25270
25451
|
for (const file of ENV_FILES) {
|
|
25271
|
-
const text = head(
|
|
25452
|
+
const text = head(join18(repoPath, file));
|
|
25272
25453
|
if (!text)
|
|
25273
25454
|
continue;
|
|
25274
25455
|
for (const name of DB_URL_NAMES) {
|
|
@@ -25292,7 +25473,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
25292
25473
|
return;
|
|
25293
25474
|
let dir = false;
|
|
25294
25475
|
try {
|
|
25295
|
-
dir = statSync7(
|
|
25476
|
+
dir = statSync7(join18(repoPath, clean)).isDirectory();
|
|
25296
25477
|
} catch {
|
|
25297
25478
|
return;
|
|
25298
25479
|
}
|
|
@@ -25303,25 +25484,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
|
|
|
25303
25484
|
const t = tokens[i];
|
|
25304
25485
|
if (t === "cd") {
|
|
25305
25486
|
const dir = tokens[i + 1];
|
|
25306
|
-
if (dir && !dir.startsWith("-") && existsSync20(
|
|
25487
|
+
if (dir && !dir.startsWith("-") && existsSync20(join18(repoPath, dir)))
|
|
25307
25488
|
add(dir);
|
|
25308
25489
|
continue;
|
|
25309
25490
|
}
|
|
25310
25491
|
if (t.startsWith("-") || t.startsWith("$"))
|
|
25311
25492
|
continue;
|
|
25312
25493
|
const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
|
|
25313
|
-
if (mod?.[1] && existsSync20(
|
|
25494
|
+
if (mod?.[1] && existsSync20(join18(repoPath, mod[1]))) {
|
|
25314
25495
|
add(mod[1]);
|
|
25315
25496
|
continue;
|
|
25316
25497
|
}
|
|
25317
25498
|
const rel = t.replace(/^\.\//, "");
|
|
25318
25499
|
if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
|
|
25319
25500
|
continue;
|
|
25320
|
-
if (!existsSync20(
|
|
25501
|
+
if (!existsSync20(join18(repoPath, rel)))
|
|
25321
25502
|
continue;
|
|
25322
25503
|
add(rel);
|
|
25323
25504
|
if (depth > 0 && rel.endsWith(".sh")) {
|
|
25324
|
-
for (const p of inferPaths(head(
|
|
25505
|
+
for (const p of inferPaths(head(join18(repoPath, rel)), repoPath, depth - 1))
|
|
25325
25506
|
out.add(p);
|
|
25326
25507
|
}
|
|
25327
25508
|
}
|
|
@@ -25344,31 +25525,31 @@ function procCommand(runner, script, body) {
|
|
|
25344
25525
|
return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
|
|
25345
25526
|
}
|
|
25346
25527
|
function detectPages(repoPath, scripts, runner) {
|
|
25347
|
-
if (!scripts.build || !existsSync20(
|
|
25528
|
+
if (!scripts.build || !existsSync20(join18(repoPath, "functions")))
|
|
25348
25529
|
return null;
|
|
25349
|
-
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) =>
|
|
25530
|
+
const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) => existsSync20(f));
|
|
25350
25531
|
if (!cfg)
|
|
25351
25532
|
return null;
|
|
25352
|
-
const out =
|
|
25533
|
+
const out = readFileSync14(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
|
|
25353
25534
|
if (!out)
|
|
25354
25535
|
return null;
|
|
25355
25536
|
return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
|
|
25356
25537
|
}
|
|
25357
25538
|
function detectRunner(repoPath, packageManager) {
|
|
25358
|
-
if (existsSync20(
|
|
25539
|
+
if (existsSync20(join18(repoPath, "bun.lock")) || existsSync20(join18(repoPath, "bun.lockb")))
|
|
25359
25540
|
return "bun";
|
|
25360
|
-
if (existsSync20(
|
|
25541
|
+
if (existsSync20(join18(repoPath, "pnpm-lock.yaml")))
|
|
25361
25542
|
return "pnpm";
|
|
25362
|
-
if (existsSync20(
|
|
25543
|
+
if (existsSync20(join18(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
|
|
25363
25544
|
return "yarn";
|
|
25364
25545
|
return "npm";
|
|
25365
25546
|
}
|
|
25366
25547
|
|
|
25367
25548
|
// ../daemon/src/repos/create.ts
|
|
25368
|
-
import { existsSync as existsSync21,
|
|
25549
|
+
import { existsSync as existsSync21, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
|
|
25369
25550
|
import { appendFile as appendFile2, mkdir as mkdir3, rm as rm2 } from "fs/promises";
|
|
25370
25551
|
import { homedir as homedir9 } from "os";
|
|
25371
|
-
import { dirname as dirname7, join as
|
|
25552
|
+
import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
|
|
25372
25553
|
function isInside(dir, parent) {
|
|
25373
25554
|
const p = parent.replace(/\/+$/, "");
|
|
25374
25555
|
return dir === p || dir.startsWith(`${p}/`);
|
|
@@ -25379,31 +25560,28 @@ function planProject(opts, home = homedir9()) {
|
|
|
25379
25560
|
throw new UserError(nameError);
|
|
25380
25561
|
const first = firstProjectsFolder(opts.parent, home);
|
|
25381
25562
|
const parent = first ?? resolveParent(opts.parent);
|
|
25382
|
-
const dir =
|
|
25563
|
+
const dir = join19(parent, opts.name.trim());
|
|
25383
25564
|
if (existsSync21(dir))
|
|
25384
25565
|
throw new UserError(`${dir} already exists`);
|
|
25385
25566
|
return { parent, dir, makeParent: first !== null };
|
|
25386
25567
|
}
|
|
25387
25568
|
function firstProjectsFolder(raw, home) {
|
|
25388
25569
|
const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
|
|
25389
|
-
if (typed !==
|
|
25570
|
+
if (typed !== join19(home, PROJECTS_FOLDER) || existsSync21(typed) || !existsSync21(home))
|
|
25390
25571
|
return null;
|
|
25391
|
-
return
|
|
25572
|
+
return join19(realpathSync2(home), PROJECTS_FOLDER);
|
|
25392
25573
|
}
|
|
25393
25574
|
function planInPlace(opts) {
|
|
25394
25575
|
const name = opts.name.trim();
|
|
25395
25576
|
if (!name || name === "." || name === ".." || name.includes("/")) {
|
|
25396
25577
|
throw new UserError(`${opts.name} is not a folder name`);
|
|
25397
25578
|
}
|
|
25398
|
-
const typed =
|
|
25579
|
+
const typed = join19(resolveParent(opts.parent), name);
|
|
25399
25580
|
if (!existsSync21(typed) || !statSync8(typed).isDirectory())
|
|
25400
25581
|
throw new UserError(`${typed} is not a folder`);
|
|
25401
25582
|
const dir = realpathSync2(typed);
|
|
25402
|
-
if (existsSync21(
|
|
25583
|
+
if (existsSync21(join19(dir, ".git")))
|
|
25403
25584
|
throw new UserError(`${dir} is already a project; open it instead`);
|
|
25404
|
-
if (!holdsNothing(readdirSync7(dir))) {
|
|
25405
|
-
throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
|
|
25406
|
-
}
|
|
25407
25585
|
return { parent: dirname7(dir), dir, makeParent: false };
|
|
25408
25586
|
}
|
|
25409
25587
|
function resolveParent(raw) {
|
|
@@ -25423,7 +25601,7 @@ async function createRepoDir(opts, home = homedir9()) {
|
|
|
25423
25601
|
await mkdir3(parent);
|
|
25424
25602
|
await mkdir3(dir);
|
|
25425
25603
|
try {
|
|
25426
|
-
await
|
|
25604
|
+
await initWithFirstCommit(dir);
|
|
25427
25605
|
} catch (e) {
|
|
25428
25606
|
await undoCreate(dir);
|
|
25429
25607
|
throw e;
|
|
@@ -25434,7 +25612,7 @@ async function initRepoInPlace(opts) {
|
|
|
25434
25612
|
const { dir } = planInPlace(opts);
|
|
25435
25613
|
await requireGitIdentity(dir);
|
|
25436
25614
|
try {
|
|
25437
|
-
await
|
|
25615
|
+
await initWithFirstCommit(dir);
|
|
25438
25616
|
} catch (e) {
|
|
25439
25617
|
await undoInit(dir);
|
|
25440
25618
|
throw e;
|
|
@@ -25445,7 +25623,7 @@ var LOCAL_EXCLUDES = [
|
|
|
25445
25623
|
".DS_Store",
|
|
25446
25624
|
"node_modules/"
|
|
25447
25625
|
];
|
|
25448
|
-
async function
|
|
25626
|
+
async function initWithFirstCommit(dir) {
|
|
25449
25627
|
await ok(git(dir, "init", "-q"), "git init");
|
|
25450
25628
|
const exclude = await git(dir, "rev-parse", "--git-path", "info/exclude");
|
|
25451
25629
|
if (!exclude.ok)
|
|
@@ -25456,7 +25634,8 @@ async function initWithEmptyCommit(dir) {
|
|
|
25456
25634
|
${LOCAL_EXCLUDES.join(`
|
|
25457
25635
|
`)}
|
|
25458
25636
|
`);
|
|
25459
|
-
await ok(git(dir, "
|
|
25637
|
+
await ok(git(dir, "add", "-A"), "git add");
|
|
25638
|
+
await ok(git(dir, "-c", "commit.gpgsign=false", "commit", "--no-verify", "--allow-empty", "-qm", "initial commit"), "git commit");
|
|
25460
25639
|
}
|
|
25461
25640
|
async function cloneInto(plan, name, url, opts = {}) {
|
|
25462
25641
|
if (!url)
|
|
@@ -25490,13 +25669,13 @@ async function undoCreate(dir) {
|
|
|
25490
25669
|
}
|
|
25491
25670
|
async function undoInit(dir) {
|
|
25492
25671
|
try {
|
|
25493
|
-
await rm2(
|
|
25672
|
+
await rm2(join19(dir, ".git"), { recursive: true, force: true });
|
|
25494
25673
|
} catch (e) {
|
|
25495
25674
|
log.warn(dir, "could not take back a half-made .git", e);
|
|
25496
25675
|
}
|
|
25497
25676
|
}
|
|
25498
25677
|
async function unmakeProject(dir, made) {
|
|
25499
|
-
const target = made === "folder" ? dir :
|
|
25678
|
+
const target = made === "folder" ? dir : join19(dir, ".git");
|
|
25500
25679
|
try {
|
|
25501
25680
|
await rm2(target, { recursive: true, force: true });
|
|
25502
25681
|
} catch (e) {
|
|
@@ -25527,7 +25706,7 @@ async function ok(p, what) {
|
|
|
25527
25706
|
|
|
25528
25707
|
// ../daemon/src/repos/watcher.ts
|
|
25529
25708
|
import { existsSync as existsSync22, watch } from "fs";
|
|
25530
|
-
import { basename as basename6, join as
|
|
25709
|
+
import { basename as basename6, join as join20 } from "path";
|
|
25531
25710
|
function watchDefaultBranch(repoPath, branch, onMove) {
|
|
25532
25711
|
const last = { local: null, upstream: null };
|
|
25533
25712
|
const read = async () => {
|
|
@@ -25580,13 +25759,13 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
25580
25759
|
]);
|
|
25581
25760
|
if (stopped)
|
|
25582
25761
|
return;
|
|
25583
|
-
const gitDir = common.ok && common.out ? common.out :
|
|
25762
|
+
const gitDir = common.ok && common.out ? common.out : join20(repoPath, ".git");
|
|
25584
25763
|
tryWatch(gitDir);
|
|
25585
|
-
tryWatch(
|
|
25764
|
+
tryWatch(join20(gitDir, "refs", "heads"));
|
|
25586
25765
|
if (!remote.ok || !remote.out)
|
|
25587
25766
|
return;
|
|
25588
|
-
const remotes =
|
|
25589
|
-
const mine =
|
|
25767
|
+
const remotes = join20(gitDir, "refs", "remotes");
|
|
25768
|
+
const mine = join20(remotes, remote.out);
|
|
25590
25769
|
let inner = null;
|
|
25591
25770
|
let mid = null;
|
|
25592
25771
|
const armInner = () => {
|
|
@@ -25602,7 +25781,7 @@ function watchDefaultBranch(repoPath, branch, onMove) {
|
|
|
25602
25781
|
}) : null;
|
|
25603
25782
|
armInner();
|
|
25604
25783
|
};
|
|
25605
|
-
tryWatch(
|
|
25784
|
+
tryWatch(join20(gitDir, "refs"), (_event, filename) => {
|
|
25606
25785
|
if (!filename || filename === "remotes")
|
|
25607
25786
|
armMid();
|
|
25608
25787
|
schedule();
|
|
@@ -25631,7 +25810,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
25631
25810
|
}, 300);
|
|
25632
25811
|
};
|
|
25633
25812
|
const arm = (commonDir) => {
|
|
25634
|
-
const dir =
|
|
25813
|
+
const dir = join20(commonDir, "worktrees");
|
|
25635
25814
|
const armInner = () => {
|
|
25636
25815
|
inner?.close();
|
|
25637
25816
|
inner = null;
|
|
@@ -25674,7 +25853,7 @@ function watchWorktreeDir(repoPath, onChange) {
|
|
|
25674
25853
|
function watchConfigFile(repoPath, onChange) {
|
|
25675
25854
|
const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
|
|
25676
25855
|
const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename6(rel)));
|
|
25677
|
-
const dir =
|
|
25856
|
+
const dir = join20(repoPath, CONFIG_DIR);
|
|
25678
25857
|
let timer = null;
|
|
25679
25858
|
let outer = null;
|
|
25680
25859
|
let inner = null;
|
|
@@ -25811,7 +25990,7 @@ class RepoRegistry {
|
|
|
25811
25990
|
return `${repo.name} is set up now`;
|
|
25812
25991
|
if (main.promptedAt || main.lastTurn)
|
|
25813
25992
|
return `${repo.name} has a chat now`;
|
|
25814
|
-
if (existsSync23(
|
|
25993
|
+
if (existsSync23(join21(repo.path, ".claude")))
|
|
25815
25994
|
return `an agent has already run in ${repo.name}`;
|
|
25816
25995
|
if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
|
|
25817
25996
|
return `${repo.name} has files in it now`;
|
|
@@ -25821,9 +26000,13 @@ class RepoRegistry {
|
|
|
25821
26000
|
return null;
|
|
25822
26001
|
}
|
|
25823
26002
|
refuseIfManaged(dir) {
|
|
25824
|
-
const
|
|
25825
|
-
if (
|
|
26003
|
+
const records = [...this.d.state.repos, ...this.d.state.worktrees];
|
|
26004
|
+
if (records.some((r) => isInside(dir, r.path))) {
|
|
25826
26005
|
throw new UserError(`${dir} is inside a project Toyon already manages`);
|
|
26006
|
+
}
|
|
26007
|
+
if (records.some((r) => isInside(r.path, dir))) {
|
|
26008
|
+
throw new UserError(`${dir} holds projects Toyon already manages`);
|
|
26009
|
+
}
|
|
25827
26010
|
}
|
|
25828
26011
|
get pending() {
|
|
25829
26012
|
return [...this.imports.values()].map((i) => i.pending).sort((a, b) => a.startedAt - b.startedAt);
|
|
@@ -25962,10 +26145,10 @@ class RepoRegistry {
|
|
|
25962
26145
|
throw new UserError(current.reason);
|
|
25963
26146
|
const was = configTarget(repo.path, !!repo.made);
|
|
25964
26147
|
const rel = configSibling(was, kind);
|
|
25965
|
-
const moved = rel !== was && existsSync23(
|
|
26148
|
+
const moved = rel !== was && existsSync23(join21(repo.path, was));
|
|
25966
26149
|
if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
|
|
25967
26150
|
try {
|
|
25968
|
-
rmSync4(
|
|
26151
|
+
rmSync4(join21(repo.path, was));
|
|
25969
26152
|
} catch (e) {
|
|
25970
26153
|
log.warn(repoId, `could not remove ${was}`, e);
|
|
25971
26154
|
}
|
|
@@ -25974,10 +26157,10 @@ class RepoRegistry {
|
|
|
25974
26157
|
}
|
|
25975
26158
|
if (isLocalConfigFile(rel))
|
|
25976
26159
|
await excludeFromGit(repo.path, rel);
|
|
25977
|
-
const file =
|
|
26160
|
+
const file = join21(repo.path, rel);
|
|
25978
26161
|
try {
|
|
25979
26162
|
mkdirSync7(dirname8(file), { recursive: true });
|
|
25980
|
-
|
|
26163
|
+
writeFileSync10(file, configText(repo.path, rel, config));
|
|
25981
26164
|
} catch (e) {
|
|
25982
26165
|
log.warn(repoId, `could not write ${rel}`, e);
|
|
25983
26166
|
}
|
|
@@ -26078,7 +26261,7 @@ class RepoRegistry {
|
|
|
26078
26261
|
}
|
|
26079
26262
|
placeConfig(repo) {
|
|
26080
26263
|
const rel = configTarget(repo.path, !!repo.made);
|
|
26081
|
-
if (isLocalConfigFile(rel) && existsSync23(
|
|
26264
|
+
if (isLocalConfigFile(rel) && existsSync23(join21(repo.path, rel))) {
|
|
26082
26265
|
fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
|
|
26083
26266
|
}
|
|
26084
26267
|
if (repo.configFile === rel)
|
|
@@ -26170,7 +26353,7 @@ class RepoRegistry {
|
|
|
26170
26353
|
}
|
|
26171
26354
|
|
|
26172
26355
|
// ../daemon/src/routes/service.ts
|
|
26173
|
-
import { join as
|
|
26356
|
+
import { join as join22 } from "path";
|
|
26174
26357
|
|
|
26175
26358
|
// ../daemon/src/routes/fileRouters.ts
|
|
26176
26359
|
var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
|
|
@@ -27370,7 +27553,7 @@ class RouteService {
|
|
|
27370
27553
|
return mergeRoutes(fileRoutes(paths, roots), code);
|
|
27371
27554
|
}
|
|
27372
27555
|
async cachedRead(root, path, had, kept, decode) {
|
|
27373
|
-
const abs =
|
|
27556
|
+
const abs = join22(root, path);
|
|
27374
27557
|
const stat = await this.fs.stat(abs);
|
|
27375
27558
|
if (!stat)
|
|
27376
27559
|
return null;
|
|
@@ -27395,7 +27578,7 @@ class RouteService {
|
|
|
27395
27578
|
}
|
|
27396
27579
|
}
|
|
27397
27580
|
async hash(root, file) {
|
|
27398
|
-
const abs =
|
|
27581
|
+
const abs = join22(root, file);
|
|
27399
27582
|
const stat = await this.fs.stat(abs);
|
|
27400
27583
|
if (!stat)
|
|
27401
27584
|
return null;
|
|
@@ -27657,7 +27840,7 @@ function isAlive(pid) {
|
|
|
27657
27840
|
}
|
|
27658
27841
|
|
|
27659
27842
|
// ../daemon/src/runtime/bridge-script.ts
|
|
27660
|
-
import { existsSync as existsSync24, readFileSync as
|
|
27843
|
+
import { existsSync as existsSync24, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
|
|
27661
27844
|
var LEARNED_MAX = 8;
|
|
27662
27845
|
|
|
27663
27846
|
class BridgeScript {
|
|
@@ -27696,7 +27879,7 @@ class BridgeScript {
|
|
|
27696
27879
|
this.stamp = stamp;
|
|
27697
27880
|
const origins = [...this.origins, ...this.learned];
|
|
27698
27881
|
this.cache = `window.__toyonShellOrigins=${JSON.stringify(origins)};
|
|
27699
|
-
${
|
|
27882
|
+
${readFileSync15(this.path, "utf8")}`;
|
|
27700
27883
|
return this.cache;
|
|
27701
27884
|
}
|
|
27702
27885
|
}
|
|
@@ -27720,9 +27903,9 @@ import { homedir as homedir12, hostname as hostname2 } from "os";
|
|
|
27720
27903
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
27721
27904
|
|
|
27722
27905
|
// ../daemon/src/agent/config.ts
|
|
27723
|
-
import { existsSync as existsSync25, readFileSync as
|
|
27906
|
+
import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
|
|
27724
27907
|
import { homedir as homedir11 } from "os";
|
|
27725
|
-
import { join as
|
|
27908
|
+
import { join as join23 } from "path";
|
|
27726
27909
|
function agentConfigFiles(spec, repoPath, home = homedir11()) {
|
|
27727
27910
|
const file = (id, label, path) => ({
|
|
27728
27911
|
id,
|
|
@@ -27732,30 +27915,30 @@ function agentConfigFiles(spec, repoPath, home = homedir11()) {
|
|
|
27732
27915
|
});
|
|
27733
27916
|
if (spec.id === "claude") {
|
|
27734
27917
|
return [
|
|
27735
|
-
file("user-config", "user config (MCP servers, projects)",
|
|
27736
|
-
file("user-settings", "user settings (permissions, hooks)",
|
|
27737
|
-
...repoPath ? [file("project-mcp", "project MCP servers",
|
|
27738
|
-
...repoPath ? [file("project-settings", "project settings",
|
|
27918
|
+
file("user-config", "user config (MCP servers, projects)", join23(home, ".claude.json")),
|
|
27919
|
+
file("user-settings", "user settings (permissions, hooks)", join23(home, ".claude", "settings.json")),
|
|
27920
|
+
...repoPath ? [file("project-mcp", "project MCP servers", join23(repoPath, ".mcp.json"))] : [],
|
|
27921
|
+
...repoPath ? [file("project-settings", "project settings", join23(repoPath, ".claude", "settings.json"))] : []
|
|
27739
27922
|
];
|
|
27740
27923
|
}
|
|
27741
27924
|
if (spec.id === "codex")
|
|
27742
|
-
return [file("user-config", "config",
|
|
27925
|
+
return [file("user-config", "config", join23(home, ".codex", "config.toml"))];
|
|
27743
27926
|
return [];
|
|
27744
27927
|
}
|
|
27745
27928
|
function describeAgentConfig(spec, repoPath, home = homedir11()) {
|
|
27746
27929
|
const files = agentConfigFiles(spec, repoPath, home);
|
|
27747
27930
|
const servers = [];
|
|
27748
27931
|
if (spec.id === "claude") {
|
|
27749
|
-
const user = readJson(
|
|
27932
|
+
const user = readJson(join23(home, ".claude.json"));
|
|
27750
27933
|
servers.push(...serversOf(user?.mcpServers, "user"));
|
|
27751
27934
|
if (repoPath) {
|
|
27752
27935
|
const projects = user?.projects;
|
|
27753
27936
|
const mine = projects?.[repoPath];
|
|
27754
27937
|
servers.push(...serversOf(mine?.mcpServers, "local"));
|
|
27755
|
-
servers.push(...serversOf(readJson(
|
|
27938
|
+
servers.push(...serversOf(readJson(join23(repoPath, ".mcp.json"))?.mcpServers, "project"));
|
|
27756
27939
|
}
|
|
27757
27940
|
} else if (spec.id === "codex") {
|
|
27758
|
-
servers.push(...codexServers(
|
|
27941
|
+
servers.push(...codexServers(join23(home, ".codex", "config.toml")));
|
|
27759
27942
|
}
|
|
27760
27943
|
return { agent: spec.id, files, servers };
|
|
27761
27944
|
}
|
|
@@ -27763,7 +27946,7 @@ function readJson(path) {
|
|
|
27763
27946
|
if (!existsSync25(path))
|
|
27764
27947
|
return null;
|
|
27765
27948
|
try {
|
|
27766
|
-
const v = JSON.parse(
|
|
27949
|
+
const v = JSON.parse(readFileSync16(path, "utf8"));
|
|
27767
27950
|
return v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
27768
27951
|
} catch (e) {
|
|
27769
27952
|
log.warn("agent-config", `${path} is not valid JSON`, e);
|
|
@@ -27785,7 +27968,7 @@ function codexServers(path) {
|
|
|
27785
27968
|
return [];
|
|
27786
27969
|
const out = [];
|
|
27787
27970
|
let current = null;
|
|
27788
|
-
for (const raw of
|
|
27971
|
+
for (const raw of readFileSync16(path, "utf8").split(`
|
|
27789
27972
|
`)) {
|
|
27790
27973
|
const line = raw.trim();
|
|
27791
27974
|
const header = /^\[mcp_servers\.("?)([^\]"]+)\1\]$/.exec(line);
|
|
@@ -28579,6 +28762,9 @@ var handlers = {
|
|
|
28579
28762
|
const { result } = await s.worktrees.sync(msg.worktreeId);
|
|
28580
28763
|
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
|
|
28581
28764
|
},
|
|
28765
|
+
"unqueue-sync"(msg, _ctx, s) {
|
|
28766
|
+
s.worktrees.unqueueSync(msg.worktreeId);
|
|
28767
|
+
},
|
|
28582
28768
|
async "pull-main"(msg, ctx, s) {
|
|
28583
28769
|
const result = await s.worktrees.pull(msg.worktreeId);
|
|
28584
28770
|
await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message));
|
|
@@ -28800,10 +28986,12 @@ var handlers = {
|
|
|
28800
28986
|
const followUp = msg.attachments?.length ? { attachments: msg.attachments } : undefined;
|
|
28801
28987
|
s.runtime.agentFor(msg.worktreeId)?.answer(msg.askId, { kind: "answers", answers: msg.answers }, followUp);
|
|
28802
28988
|
});
|
|
28989
|
+
s.worktrees.markPrompted(msg.worktreeId);
|
|
28803
28990
|
},
|
|
28804
28991
|
"agent-decide"(msg, _ctx, s) {
|
|
28805
28992
|
requireRun(s, msg.worktreeId);
|
|
28806
28993
|
s.runtime.agentFor(msg.worktreeId)?.answer(msg.askId, { kind: "choice", choiceId: msg.choiceId });
|
|
28994
|
+
s.worktrees.markPrompted(msg.worktreeId);
|
|
28807
28995
|
},
|
|
28808
28996
|
async "handoff-answer"(msg, _ctx, s) {
|
|
28809
28997
|
requireRun(s, msg.worktreeId);
|
|
@@ -28897,8 +29085,8 @@ var handlers = {
|
|
|
28897
29085
|
ctx.reply({ t: "refs", repoId: msg.repoId, query: msg.query, refs });
|
|
28898
29086
|
},
|
|
28899
29087
|
async "search-chats"(msg, ctx, s) {
|
|
28900
|
-
const { hits, truncated } = await s.chats.search(msg.repoId, msg.query);
|
|
28901
|
-
ctx.reply({ t: "chat-hits", repoId: msg.repoId, query: msg.query, hits, truncated });
|
|
29088
|
+
const { hits, truncated, asks } = await s.chats.search(msg.repoId, msg.query);
|
|
29089
|
+
ctx.reply({ t: "chat-hits", repoId: msg.repoId, query: msg.query, hits, truncated, asks });
|
|
28902
29090
|
},
|
|
28903
29091
|
async "open-ref"(msg, _ctx, s) {
|
|
28904
29092
|
await s.worktrees.openRef(msg.repoId, msg.kind, msg.ref, { createdBy: msg.clientId, pr: msg.pr });
|
|
@@ -28954,7 +29142,7 @@ async function dispatch(msg, ctx, s) {
|
|
|
28954
29142
|
// ../daemon/src/server/http.ts
|
|
28955
29143
|
import { existsSync as existsSync26 } from "fs";
|
|
28956
29144
|
import { hostname } from "os";
|
|
28957
|
-
import { join as
|
|
29145
|
+
import { join as join24 } from "path";
|
|
28958
29146
|
|
|
28959
29147
|
// ../daemon/src/server/cors.ts
|
|
28960
29148
|
var ALLOW_HEADERS = "authorization, content-type";
|
|
@@ -29006,7 +29194,7 @@ function pairingRoutes(d) {
|
|
|
29006
29194
|
const body = state === "let-in" ? { state, token: d.token } : { state };
|
|
29007
29195
|
return json(body, headers);
|
|
29008
29196
|
}
|
|
29009
|
-
if (!url.pathname.match(/^\/(knocks|knock\/[A-Za-z0-9_-]+\/answer|
|
|
29197
|
+
if (!url.pathname.match(/^\/(knocks|knock\/[A-Za-z0-9_-]+\/answer|tailnet|tailscale|remote|machines)$/))
|
|
29010
29198
|
return;
|
|
29011
29199
|
if (!d.authed(req))
|
|
29012
29200
|
return unauthorized();
|
|
@@ -29027,14 +29215,18 @@ function pairingRoutes(d) {
|
|
|
29027
29215
|
d.knocks.answer(answered, body.letIn);
|
|
29028
29216
|
return new Response(null, { status: 204 });
|
|
29029
29217
|
}
|
|
29030
|
-
if (url.pathname === "/phones" && req.method === "GET") {
|
|
29031
|
-
const body = remote && isTailnetName(remote.host) ? await d.tailnet.phones() : null;
|
|
29032
|
-
return json(body);
|
|
29033
|
-
}
|
|
29034
29218
|
if (url.pathname === "/tailnet" && req.method === "GET") {
|
|
29035
|
-
const body = await d.tailnet.
|
|
29219
|
+
const body = await d.tailnet.view();
|
|
29036
29220
|
return json(body);
|
|
29037
29221
|
}
|
|
29222
|
+
if (url.pathname === "/tailscale" && req.method === "POST") {
|
|
29223
|
+
const body = await req.json().catch(() => ({}));
|
|
29224
|
+
if (body.up === true)
|
|
29225
|
+
return json({ refused: await d.tailnet.up() });
|
|
29226
|
+
if (body.login === true)
|
|
29227
|
+
return json(await d.tailnet.login());
|
|
29228
|
+
return new Response("say up or login", { status: 400 });
|
|
29229
|
+
}
|
|
29038
29230
|
if (url.pathname === "/remote" && req.method === "POST") {
|
|
29039
29231
|
const body = await req.json().catch(() => ({}));
|
|
29040
29232
|
try {
|
|
@@ -29285,7 +29477,7 @@ function createFetch(opts) {
|
|
|
29285
29477
|
});
|
|
29286
29478
|
}
|
|
29287
29479
|
const rel = url.pathname === "/" ? "/index.html" : url.pathname;
|
|
29288
|
-
const file =
|
|
29480
|
+
const file = join24(opts.shellDist, rel.replaceAll("..", ""));
|
|
29289
29481
|
const hashed = rel.startsWith("/assets/");
|
|
29290
29482
|
if (existsSync26(file) && Bun.file(file).size > 0) {
|
|
29291
29483
|
if (rel === "/manifest.json") {
|
|
@@ -29306,7 +29498,7 @@ function createFetch(opts) {
|
|
|
29306
29498
|
}
|
|
29307
29499
|
if (hashed)
|
|
29308
29500
|
return new Response("not found", { status: 404 });
|
|
29309
|
-
const index =
|
|
29501
|
+
const index = join24(opts.shellDist, "index.html");
|
|
29310
29502
|
if (existsSync26(index))
|
|
29311
29503
|
return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE2 } });
|
|
29312
29504
|
return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
|
|
@@ -29681,7 +29873,7 @@ function startServer(opts) {
|
|
|
29681
29873
|
bootstrap: helloFrame,
|
|
29682
29874
|
manifestColors: () => {
|
|
29683
29875
|
const { surface0, surface1 } = s.themes.current().colors;
|
|
29684
|
-
return { bar:
|
|
29876
|
+
return { bar: surface0, ground: surface1 };
|
|
29685
29877
|
},
|
|
29686
29878
|
open: (path) => s.opens.open(path),
|
|
29687
29879
|
restart: (now) => s.restarter.request({ now }),
|
|
@@ -29799,16 +29991,16 @@ function startServer(opts) {
|
|
|
29799
29991
|
}
|
|
29800
29992
|
|
|
29801
29993
|
// ../daemon/src/themes/store.ts
|
|
29802
|
-
import { existsSync as existsSync27, readdirSync as readdirSync8, readFileSync as
|
|
29994
|
+
import { existsSync as existsSync27, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
29803
29995
|
import { homedir as homedir13 } from "os";
|
|
29804
|
-
import { basename as basename8, dirname as dirname9, join as
|
|
29996
|
+
import { basename as basename8, dirname as dirname9, join as join25, resolve as resolve9 } from "path";
|
|
29805
29997
|
var home = homedir13();
|
|
29806
29998
|
var defaultExtensionDirs = [
|
|
29807
|
-
|
|
29808
|
-
|
|
29809
|
-
|
|
29810
|
-
|
|
29811
|
-
|
|
29999
|
+
join25(home, ".vscode", "extensions"),
|
|
30000
|
+
join25(home, ".vscode-insiders", "extensions"),
|
|
30001
|
+
join25(home, ".vscode-oss", "extensions"),
|
|
30002
|
+
join25(home, ".cursor", "extensions"),
|
|
30003
|
+
join25(home, ".windsurf", "extensions"),
|
|
29812
30004
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
|
|
29813
30005
|
"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
|
|
29814
30006
|
"/Applications/Cursor.app/Contents/Resources/app/extensions",
|
|
@@ -29892,14 +30084,14 @@ class ThemeStore {
|
|
|
29892
30084
|
name: json?.name ?? base,
|
|
29893
30085
|
source: "file"
|
|
29894
30086
|
});
|
|
29895
|
-
|
|
30087
|
+
writeFileSync11(join25(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
|
|
29896
30088
|
`);
|
|
29897
30089
|
this.load();
|
|
29898
30090
|
return theme;
|
|
29899
30091
|
}
|
|
29900
30092
|
}
|
|
29901
30093
|
function readJsonc(file) {
|
|
29902
|
-
return parse6(
|
|
30094
|
+
return parse6(readFileSync17(file, "utf8"), [], { allowTrailingComma: true });
|
|
29903
30095
|
}
|
|
29904
30096
|
function readVscodeTheme(file, depth = 0) {
|
|
29905
30097
|
const json = readJsonc(file);
|
|
@@ -29927,7 +30119,7 @@ function loadDir(dir) {
|
|
|
29927
30119
|
for (const f of readdirSync8(dir)) {
|
|
29928
30120
|
if (!/\.jsonc?$/i.test(f))
|
|
29929
30121
|
continue;
|
|
29930
|
-
const file =
|
|
30122
|
+
const file = join25(dir, f);
|
|
29931
30123
|
const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
|
|
29932
30124
|
try {
|
|
29933
30125
|
const json = readJsonc(file);
|
|
@@ -29953,21 +30145,21 @@ function discover(dir) {
|
|
|
29953
30145
|
return out;
|
|
29954
30146
|
}
|
|
29955
30147
|
for (const name of entries) {
|
|
29956
|
-
const extDir =
|
|
29957
|
-
const pkgFile =
|
|
30148
|
+
const extDir = join25(dir, name);
|
|
30149
|
+
const pkgFile = join25(extDir, "package.json");
|
|
29958
30150
|
try {
|
|
29959
30151
|
if (!statSync10(extDir).isDirectory() || !existsSync27(pkgFile))
|
|
29960
30152
|
continue;
|
|
29961
|
-
const pkg = JSON.parse(
|
|
30153
|
+
const pkg = JSON.parse(readFileSync17(pkgFile, "utf8"));
|
|
29962
30154
|
const themes = pkg.contributes?.themes;
|
|
29963
30155
|
if (!Array.isArray(themes))
|
|
29964
30156
|
continue;
|
|
29965
30157
|
const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
|
|
29966
30158
|
let nls = {};
|
|
29967
|
-
const nlsFile =
|
|
30159
|
+
const nlsFile = join25(extDir, "package.nls.json");
|
|
29968
30160
|
if (existsSync27(nlsFile)) {
|
|
29969
30161
|
try {
|
|
29970
|
-
nls = JSON.parse(
|
|
30162
|
+
nls = JSON.parse(readFileSync17(nlsFile, "utf8"));
|
|
29971
30163
|
} catch {}
|
|
29972
30164
|
}
|
|
29973
30165
|
for (const c of themes) {
|
|
@@ -30379,9 +30571,9 @@ class BackendShare {
|
|
|
30379
30571
|
|
|
30380
30572
|
// ../daemon/src/worktrees/cache.ts
|
|
30381
30573
|
import { createHash as createHash4 } from "crypto";
|
|
30382
|
-
import { existsSync as existsSync28, readFileSync as
|
|
30574
|
+
import { existsSync as existsSync28, readFileSync as readFileSync18 } from "fs";
|
|
30383
30575
|
import { mkdir as mkdir5, readdir as readdir2, rename, rm as rm4, stat as stat4, utimes, writeFile as writeFile3 } from "fs/promises";
|
|
30384
|
-
import { join as
|
|
30576
|
+
import { join as join26 } from "path";
|
|
30385
30577
|
|
|
30386
30578
|
// ../daemon/src/core/clone.ts
|
|
30387
30579
|
import { mkdir as mkdir4, rm as rm3 } from "fs/promises";
|
|
@@ -30412,7 +30604,7 @@ var STALE_MS = 30 * 24 * 3600000;
|
|
|
30412
30604
|
var TMP = ".tmp-";
|
|
30413
30605
|
var MANIFEST = "manifest.json";
|
|
30414
30606
|
function defaultTools(dir) {
|
|
30415
|
-
const has = (...files) => files.some((f) => existsSync28(
|
|
30607
|
+
const has = (...files) => files.some((f) => existsSync28(join26(dir, f)));
|
|
30416
30608
|
const out = [];
|
|
30417
30609
|
if (has("bun.lock", "bun.lockb"))
|
|
30418
30610
|
out.push("bun --version");
|
|
@@ -30425,7 +30617,7 @@ function defaultTools(dir) {
|
|
|
30425
30617
|
return out;
|
|
30426
30618
|
}
|
|
30427
30619
|
function withSidecars(root, rel) {
|
|
30428
|
-
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync28(
|
|
30620
|
+
return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync28(join26(root, n)));
|
|
30429
30621
|
}
|
|
30430
30622
|
var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
|
|
30431
30623
|
async function listEntries(familyDir) {
|
|
@@ -30440,8 +30632,8 @@ async function listEntries(familyDir) {
|
|
|
30440
30632
|
const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
|
|
30441
30633
|
if (!m?.[1] || !m[2])
|
|
30442
30634
|
continue;
|
|
30443
|
-
const dir =
|
|
30444
|
-
if (!existsSync28(
|
|
30635
|
+
const dir = join26(familyDir, name);
|
|
30636
|
+
if (!existsSync28(join26(dir, MANIFEST)))
|
|
30445
30637
|
continue;
|
|
30446
30638
|
out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
|
|
30447
30639
|
}
|
|
@@ -30471,7 +30663,7 @@ class ArtifactCache {
|
|
|
30471
30663
|
try {
|
|
30472
30664
|
const family = await this.family(wt, repo, policy);
|
|
30473
30665
|
const base = await this.baseCommit(wt, repo);
|
|
30474
|
-
const entries = await listEntries(
|
|
30666
|
+
const entries = await listEntries(join26(this.d.paths.cacheDir, repo.id, family.key));
|
|
30475
30667
|
const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
|
|
30476
30668
|
if (!entry) {
|
|
30477
30669
|
say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
|
|
@@ -30479,14 +30671,14 @@ class ArtifactCache {
|
|
|
30479
30671
|
}
|
|
30480
30672
|
const restored = [];
|
|
30481
30673
|
for (const rel of policy.paths) {
|
|
30482
|
-
if (existsSync28(
|
|
30674
|
+
if (existsSync28(join26(wt.path, rel)))
|
|
30483
30675
|
continue;
|
|
30484
30676
|
const names = withSidecars(entry.dir, rel);
|
|
30485
30677
|
if (names.length === 0)
|
|
30486
30678
|
continue;
|
|
30487
30679
|
let whole = true;
|
|
30488
30680
|
for (const name of names) {
|
|
30489
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
30681
|
+
const r = await (this.d.clone ?? cloneTree)(join26(entry.dir, name), join26(wt.path, name));
|
|
30490
30682
|
if (r.ok)
|
|
30491
30683
|
continue;
|
|
30492
30684
|
say(`cache: could not restore ${name}: ${r.error}`);
|
|
@@ -30520,12 +30712,12 @@ class ArtifactCache {
|
|
|
30520
30712
|
let tmp;
|
|
30521
30713
|
let slot;
|
|
30522
30714
|
try {
|
|
30523
|
-
const present = policy.paths.filter((rel) => existsSync28(
|
|
30715
|
+
const present = policy.paths.filter((rel) => existsSync28(join26(wt.path, rel)));
|
|
30524
30716
|
if (present.length === 0)
|
|
30525
30717
|
return;
|
|
30526
30718
|
const family = await this.family(wt, repo, policy);
|
|
30527
30719
|
const base = await this.baseCommit(wt, repo);
|
|
30528
|
-
const familyDir =
|
|
30720
|
+
const familyDir = join26(this.d.paths.cacheDir, repo.id, family.key);
|
|
30529
30721
|
const key = `${familyDir}/${base.slice(0, 12)}`;
|
|
30530
30722
|
const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
|
|
30531
30723
|
if (kept || this.making.has(key)) {
|
|
@@ -30536,21 +30728,21 @@ class ArtifactCache {
|
|
|
30536
30728
|
this.making.add(key);
|
|
30537
30729
|
const at = Date.now();
|
|
30538
30730
|
const name = entryName(base, at);
|
|
30539
|
-
tmp =
|
|
30731
|
+
tmp = join26(familyDir, `${TMP}${name}`);
|
|
30540
30732
|
await mkdir5(tmp, { recursive: true });
|
|
30541
30733
|
let how = "clonefile";
|
|
30542
30734
|
for (const rel of present) {
|
|
30543
30735
|
for (const n of withSidecars(wt.path, rel)) {
|
|
30544
|
-
const r = await (this.d.clone ?? cloneTree)(
|
|
30736
|
+
const r = await (this.d.clone ?? cloneTree)(join26(wt.path, n), join26(tmp, n));
|
|
30545
30737
|
if (!r.ok)
|
|
30546
30738
|
throw new Error(`could not keep ${n}: ${r.error}`);
|
|
30547
30739
|
how = r.how;
|
|
30548
30740
|
}
|
|
30549
30741
|
}
|
|
30550
30742
|
const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
|
|
30551
|
-
await writeFile3(
|
|
30743
|
+
await writeFile3(join26(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
|
|
30552
30744
|
`);
|
|
30553
|
-
await rename(tmp,
|
|
30745
|
+
await rename(tmp, join26(familyDir, name));
|
|
30554
30746
|
tmp = undefined;
|
|
30555
30747
|
log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
|
|
30556
30748
|
await this.prune(familyDir);
|
|
@@ -30578,7 +30770,7 @@ class ArtifactCache {
|
|
|
30578
30770
|
return;
|
|
30579
30771
|
}
|
|
30580
30772
|
for (const repoId of repoIds) {
|
|
30581
|
-
const repoDir =
|
|
30773
|
+
const repoDir = join26(root, repoId);
|
|
30582
30774
|
if (!this.d.state.repo(repoId)) {
|
|
30583
30775
|
await rm4(repoDir, { recursive: true, force: true });
|
|
30584
30776
|
continue;
|
|
@@ -30590,7 +30782,7 @@ class ArtifactCache {
|
|
|
30590
30782
|
continue;
|
|
30591
30783
|
}
|
|
30592
30784
|
for (const family of families) {
|
|
30593
|
-
const familyDir =
|
|
30785
|
+
const familyDir = join26(repoDir, family);
|
|
30594
30786
|
let names;
|
|
30595
30787
|
try {
|
|
30596
30788
|
names = await readdir2(familyDir);
|
|
@@ -30599,7 +30791,7 @@ class ArtifactCache {
|
|
|
30599
30791
|
}
|
|
30600
30792
|
for (const n of names)
|
|
30601
30793
|
if (n.startsWith(TMP))
|
|
30602
|
-
await rm4(
|
|
30794
|
+
await rm4(join26(familyDir, n), { recursive: true, force: true });
|
|
30603
30795
|
const entries = await listEntries(familyDir);
|
|
30604
30796
|
let newest = 0;
|
|
30605
30797
|
for (const e of entries) {
|
|
@@ -30619,7 +30811,7 @@ class ArtifactCache {
|
|
|
30619
30811
|
for (const f of policy.key) {
|
|
30620
30812
|
h.update(`\x00${f}\x00`);
|
|
30621
30813
|
try {
|
|
30622
|
-
h.update(
|
|
30814
|
+
h.update(readFileSync18(join26(wt.path, f)));
|
|
30623
30815
|
} catch {
|
|
30624
30816
|
h.update("(absent)");
|
|
30625
30817
|
}
|
|
@@ -30647,6 +30839,7 @@ var CHAT_HITS_MAX = 200;
|
|
|
30647
30839
|
var MIN_QUERY = 2;
|
|
30648
30840
|
var SNIPPET = 160;
|
|
30649
30841
|
var BEFORE = 50;
|
|
30842
|
+
var ASK = 500;
|
|
30650
30843
|
var bunFs2 = {
|
|
30651
30844
|
async stat(path) {
|
|
30652
30845
|
try {
|
|
@@ -30718,6 +30911,9 @@ function searchRows(rows, terms, chat, limit = CHAT_HITS_PER_WORKTREE) {
|
|
|
30718
30911
|
}
|
|
30719
30912
|
return { hits, more: false };
|
|
30720
30913
|
}
|
|
30914
|
+
function firstAsk(rows) {
|
|
30915
|
+
return rows.find((r) => r.role === "user")?.text.slice(0, ASK);
|
|
30916
|
+
}
|
|
30721
30917
|
function termRanges(lower, terms) {
|
|
30722
30918
|
const ranges = [];
|
|
30723
30919
|
for (const term of terms) {
|
|
@@ -30739,7 +30935,7 @@ class ChatSearch {
|
|
|
30739
30935
|
this.d.state.requireRepo(repoId);
|
|
30740
30936
|
const terms = needleTerms(query);
|
|
30741
30937
|
if (terms.length === 0)
|
|
30742
|
-
return { hits: [], truncated: false };
|
|
30938
|
+
return { hits: [], truncated: false, asks: {} };
|
|
30743
30939
|
const chats = [
|
|
30744
30940
|
...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) })),
|
|
30745
30941
|
...this.d.archivedChats(repoId).map((a) => ({ id: a.id, archived: true, path: a.transcript }))
|
|
@@ -30747,13 +30943,23 @@ class ChatSearch {
|
|
|
30747
30943
|
const read = new Set;
|
|
30748
30944
|
const found = await Promise.all(chats.map(async (c) => {
|
|
30749
30945
|
const rows = await this.rowsOf(c.id, c.archived, c.path, read);
|
|
30750
|
-
return
|
|
30946
|
+
return {
|
|
30947
|
+
id: c.id,
|
|
30948
|
+
...searchRows(rows, terms, { worktreeId: c.id, archived: c.archived }),
|
|
30949
|
+
ask: firstAsk(rows)
|
|
30950
|
+
};
|
|
30751
30951
|
}));
|
|
30752
30952
|
for (const key of this.said.keys())
|
|
30753
30953
|
if (!read.has(key))
|
|
30754
30954
|
this.said.delete(key);
|
|
30755
30955
|
const all = found.filter((f) => f.hits.length > 0).sort((a, b) => b.hits[0].ts - a.hits[0].ts).flatMap((f) => f.hits);
|
|
30756
|
-
|
|
30956
|
+
const hits = all.slice(0, CHAT_HITS_MAX);
|
|
30957
|
+
const kept = new Set(hits.map((h) => h.worktreeId));
|
|
30958
|
+
const asks = {};
|
|
30959
|
+
for (const f of found)
|
|
30960
|
+
if (f.ask && kept.has(f.id))
|
|
30961
|
+
asks[f.id] = f.ask;
|
|
30962
|
+
return { hits, truncated: all.length > CHAT_HITS_MAX || found.some((f) => f.more), asks };
|
|
30757
30963
|
}
|
|
30758
30964
|
async rowsOf(id, archived, path, read) {
|
|
30759
30965
|
const live = archived ? undefined : this.d.live(id);
|
|
@@ -30824,7 +31030,7 @@ function lastPageErrors(entries) {
|
|
|
30824
31030
|
for (let i = entries.length - 1;i >= 0; i--) {
|
|
30825
31031
|
const e = entries[i].event;
|
|
30826
31032
|
if (e.type === "page-error") {
|
|
30827
|
-
errors.unshift(e.
|
|
31033
|
+
errors.unshift(...e.errors);
|
|
30828
31034
|
continue;
|
|
30829
31035
|
}
|
|
30830
31036
|
if (e.type === "user-message" || e.type === "fix-asked")
|
|
@@ -30939,7 +31145,7 @@ function openProposals(entries) {
|
|
|
30939
31145
|
for (const { event } of entries) {
|
|
30940
31146
|
if (event.type === "handoff-proposed")
|
|
30941
31147
|
open.set(event.id, event);
|
|
30942
|
-
else if (event.type === "handoff" || event.type === "handoff-declined")
|
|
31148
|
+
else if (event.type === "handoff-go" || event.type === "handoff" || event.type === "handoff-declined")
|
|
30943
31149
|
open.delete(event.id);
|
|
30944
31150
|
}
|
|
30945
31151
|
return [...open.values()];
|
|
@@ -30958,7 +31164,6 @@ function proposedThisTurn(entries) {
|
|
|
30958
31164
|
|
|
30959
31165
|
class HandoffService {
|
|
30960
31166
|
d;
|
|
30961
|
-
settling = new Set;
|
|
30962
31167
|
constructor(d) {
|
|
30963
31168
|
this.d = d;
|
|
30964
31169
|
d.hub.on("landed", (worktreeId) => this.landed(worktreeId));
|
|
@@ -31071,13 +31276,11 @@ class HandoffService {
|
|
|
31071
31276
|
if (!openProposals(entries).some((p) => p.id === id)) {
|
|
31072
31277
|
return log.debug(worktreeId, `answer for a handoff that already closed (${id})`);
|
|
31073
31278
|
}
|
|
31074
|
-
if (this.settling.has(id))
|
|
31075
|
-
return;
|
|
31076
31279
|
if (!go) {
|
|
31077
31280
|
agent.note({ type: "handoff-declined", id, ts: Date.now() });
|
|
31078
31281
|
return;
|
|
31079
31282
|
}
|
|
31080
|
-
|
|
31283
|
+
agent.note({ type: "handoff-go", id, ts: Date.now() });
|
|
31081
31284
|
try {
|
|
31082
31285
|
const repo = this.d.state.repo(proposed.repo.id);
|
|
31083
31286
|
if (!repo)
|
|
@@ -31103,8 +31306,6 @@ class HandoffService {
|
|
|
31103
31306
|
const reason = e instanceof Error ? e.message : String(e);
|
|
31104
31307
|
agent.note({ type: "handoff-declined", id, outcome: "failed", reason, ts: Date.now() });
|
|
31105
31308
|
throw e;
|
|
31106
|
-
} finally {
|
|
31107
|
-
this.settling.delete(id);
|
|
31108
31309
|
}
|
|
31109
31310
|
}
|
|
31110
31311
|
async ask(worktreeId, repoId, text) {
|
|
@@ -31837,9 +32038,9 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
|
|
|
31837
32038
|
}
|
|
31838
32039
|
|
|
31839
32040
|
// ../daemon/src/worktrees/service.ts
|
|
31840
|
-
import { cpSync, existsSync as existsSync33, readFileSync as
|
|
32041
|
+
import { cpSync, existsSync as existsSync33, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
|
|
31841
32042
|
import { rm as rm5 } from "fs/promises";
|
|
31842
|
-
import { dirname as dirname12, join as
|
|
32043
|
+
import { dirname as dirname12, join as join30 } from "path";
|
|
31843
32044
|
|
|
31844
32045
|
// ../daemon/src/git/archive.ts
|
|
31845
32046
|
import { rmSync as rmSync5 } from "fs";
|
|
@@ -32415,7 +32616,7 @@ function compareUrl(remoteUrl, base, branch) {
|
|
|
32415
32616
|
|
|
32416
32617
|
// ../daemon/src/git/turn.ts
|
|
32417
32618
|
import { copyFileSync, existsSync as existsSync30, rmSync as rmSync6 } from "fs";
|
|
32418
|
-
import { join as
|
|
32619
|
+
import { join as join27 } from "path";
|
|
32419
32620
|
var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
|
|
32420
32621
|
var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
|
|
32421
32622
|
var queue = new Map;
|
|
@@ -32433,7 +32634,7 @@ async function treeNow(wtPath, head, dirty) {
|
|
|
32433
32634
|
const dir = await git(wtPath, "rev-parse", "--absolute-git-dir");
|
|
32434
32635
|
if (!dir.ok || !dir.out)
|
|
32435
32636
|
return null;
|
|
32436
|
-
index =
|
|
32637
|
+
index = join27(dir.out, "toyon-turn-index");
|
|
32437
32638
|
}
|
|
32438
32639
|
const env = { GIT_INDEX_FILE: index };
|
|
32439
32640
|
const writeTree = async () => {
|
|
@@ -32456,7 +32657,7 @@ async function treeNow(wtPath, head, dirty) {
|
|
|
32456
32657
|
}
|
|
32457
32658
|
synced.delete(wtPath);
|
|
32458
32659
|
if (!existsSync30(index)) {
|
|
32459
|
-
const own =
|
|
32660
|
+
const own = join27(index, "..", "index");
|
|
32460
32661
|
if (existsSync30(own))
|
|
32461
32662
|
copyFileSync(own, index);
|
|
32462
32663
|
else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
|
|
@@ -32538,8 +32739,8 @@ async function dropTurnRefs(repoPath, worktreeId) {
|
|
|
32538
32739
|
}
|
|
32539
32740
|
|
|
32540
32741
|
// ../daemon/src/worktrees/archive.ts
|
|
32541
|
-
import { existsSync as existsSync31, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as
|
|
32542
|
-
import { basename as basename10, dirname as dirname11, join as
|
|
32742
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync19, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
32743
|
+
import { basename as basename10, dirname as dirname11, join as join28 } from "path";
|
|
32543
32744
|
class WorktreeArchive {
|
|
32544
32745
|
dir;
|
|
32545
32746
|
records = new Map;
|
|
@@ -32551,7 +32752,7 @@ class WorktreeArchive {
|
|
|
32551
32752
|
if (!existsSync31(file))
|
|
32552
32753
|
continue;
|
|
32553
32754
|
try {
|
|
32554
|
-
this.records.set(id, JSON.parse(
|
|
32755
|
+
this.records.set(id, JSON.parse(readFileSync19(file, "utf8")));
|
|
32555
32756
|
} catch (e) {
|
|
32556
32757
|
log.warn("archive", `skipped an unreadable record: ${file}`, e);
|
|
32557
32758
|
}
|
|
@@ -32566,7 +32767,7 @@ class WorktreeArchive {
|
|
|
32566
32767
|
move(from.transcript, at.transcript);
|
|
32567
32768
|
move(from.attachments, at.attachments);
|
|
32568
32769
|
move(from.plans, at.plans);
|
|
32569
|
-
|
|
32770
|
+
writeFileSync12(at.record, JSON.stringify(rec, null, 2));
|
|
32570
32771
|
this.records.set(rec.worktree.id, rec);
|
|
32571
32772
|
return { transcript: at.transcript, attachments: at.attachments, plans: at.plans };
|
|
32572
32773
|
}
|
|
@@ -32595,7 +32796,7 @@ class WorktreeArchive {
|
|
|
32595
32796
|
const rec = this.records.get(id);
|
|
32596
32797
|
if (!rec)
|
|
32597
32798
|
return;
|
|
32598
|
-
|
|
32799
|
+
writeFileSync12(this.filesOf(id).record, JSON.stringify(rec, null, 2));
|
|
32599
32800
|
}
|
|
32600
32801
|
chatFiles(id) {
|
|
32601
32802
|
if (!this.records.has(id))
|
|
@@ -32606,24 +32807,24 @@ class WorktreeArchive {
|
|
|
32606
32807
|
planFile(id, rel) {
|
|
32607
32808
|
if (!this.records.has(id) || !isPlanPath(rel))
|
|
32608
32809
|
return null;
|
|
32609
|
-
const file =
|
|
32810
|
+
const file = join28(this.filesOf(id).plans, basename10(rel));
|
|
32610
32811
|
if (!existsSync31(file))
|
|
32611
32812
|
return null;
|
|
32612
32813
|
try {
|
|
32613
|
-
return
|
|
32814
|
+
return readFileSync19(file, "utf8");
|
|
32614
32815
|
} catch (e) {
|
|
32615
32816
|
log.warn(id, `could not read its archived plan ${rel}`, e);
|
|
32616
32817
|
return null;
|
|
32617
32818
|
}
|
|
32618
32819
|
}
|
|
32619
32820
|
filesOf(id) {
|
|
32620
|
-
const dir =
|
|
32821
|
+
const dir = join28(this.dir, id);
|
|
32621
32822
|
return {
|
|
32622
32823
|
dir,
|
|
32623
|
-
record:
|
|
32624
|
-
transcript:
|
|
32625
|
-
attachments:
|
|
32626
|
-
plans:
|
|
32824
|
+
record: join28(dir, "record.json"),
|
|
32825
|
+
transcript: join28(dir, "transcript.jsonl"),
|
|
32826
|
+
attachments: join28(dir, "attachments"),
|
|
32827
|
+
plans: join28(dir, "plans")
|
|
32627
32828
|
};
|
|
32628
32829
|
}
|
|
32629
32830
|
}
|
|
@@ -32650,7 +32851,7 @@ function summarize(r, repoId, transcript) {
|
|
|
32650
32851
|
function lastUsage(transcript) {
|
|
32651
32852
|
if (!existsSync31(transcript))
|
|
32652
32853
|
return null;
|
|
32653
|
-
const lines =
|
|
32854
|
+
const lines = readFileSync19(transcript, "utf8").split(`
|
|
32654
32855
|
`);
|
|
32655
32856
|
for (let i = lines.length - 1;i >= 0; i--) {
|
|
32656
32857
|
if (!lines[i]?.includes('"usage"'))
|
|
@@ -32666,7 +32867,7 @@ function lastUsage(transcript) {
|
|
|
32666
32867
|
function firstPrompt(transcript) {
|
|
32667
32868
|
if (!existsSync31(transcript))
|
|
32668
32869
|
return;
|
|
32669
|
-
for (const line of
|
|
32870
|
+
for (const line of readFileSync19(transcript, "utf8").split(`
|
|
32670
32871
|
`)) {
|
|
32671
32872
|
if (!line.includes('"user-message"'))
|
|
32672
32873
|
continue;
|
|
@@ -32683,7 +32884,7 @@ function move(from, to) {
|
|
|
32683
32884
|
return;
|
|
32684
32885
|
mkdirSync8(dirname11(to), { recursive: true });
|
|
32685
32886
|
rmSync7(to, { recursive: true, force: true });
|
|
32686
|
-
|
|
32887
|
+
renameSync5(from, to);
|
|
32687
32888
|
}
|
|
32688
32889
|
|
|
32689
32890
|
// ../daemon/src/worktrees/archivedGit.ts
|
|
@@ -32851,7 +33052,7 @@ async function fillLandMarks(jobs, read, width = 8) {
|
|
|
32851
33052
|
|
|
32852
33053
|
// ../daemon/src/worktrees/spare.ts
|
|
32853
33054
|
import { existsSync as existsSync32 } from "fs";
|
|
32854
|
-
import { basename as basename11, join as
|
|
33055
|
+
import { basename as basename11, join as join29 } from "path";
|
|
32855
33056
|
class SparePool {
|
|
32856
33057
|
d;
|
|
32857
33058
|
inflight = new Map;
|
|
@@ -32915,11 +33116,11 @@ class SparePool {
|
|
|
32915
33116
|
return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
|
|
32916
33117
|
}
|
|
32917
33118
|
async record(repoId, repo) {
|
|
32918
|
-
const { id, dir } = freeSlot(
|
|
33119
|
+
const { id, dir } = freeSlot(join29(this.d.paths.worktreesDir, repo.name));
|
|
32919
33120
|
const wt = {
|
|
32920
33121
|
id,
|
|
32921
33122
|
repoId,
|
|
32922
|
-
path:
|
|
33123
|
+
path: join29(this.d.paths.worktreesDir, repo.name, dir),
|
|
32923
33124
|
branch: repo.defaultBranch,
|
|
32924
33125
|
kind: "spare",
|
|
32925
33126
|
phase: "reserved",
|
|
@@ -32939,6 +33140,7 @@ class SparePool {
|
|
|
32939
33140
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
|
|
32940
33141
|
})();
|
|
32941
33142
|
const done = (async () => {
|
|
33143
|
+
this.d.runtime.markSetup(wt.id, true);
|
|
32942
33144
|
try {
|
|
32943
33145
|
await added;
|
|
32944
33146
|
if (after) {
|
|
@@ -32968,6 +33170,7 @@ class SparePool {
|
|
|
32968
33170
|
} catch (e) {
|
|
32969
33171
|
await this.rollBack(wt, e);
|
|
32970
33172
|
} finally {
|
|
33173
|
+
this.d.runtime.markSetup(wt.id, false);
|
|
32971
33174
|
if (this.inflight.get(repoId)?.added === added)
|
|
32972
33175
|
this.inflight.delete(repoId);
|
|
32973
33176
|
}
|
|
@@ -33308,6 +33511,7 @@ class WorktreeService {
|
|
|
33308
33511
|
spare;
|
|
33309
33512
|
runs;
|
|
33310
33513
|
ops = new Map;
|
|
33514
|
+
syncQueued = new Set;
|
|
33311
33515
|
trunk;
|
|
33312
33516
|
archive;
|
|
33313
33517
|
archiving = new Map;
|
|
@@ -33379,6 +33583,8 @@ class WorktreeService {
|
|
|
33379
33583
|
if (wt && prompt)
|
|
33380
33584
|
this.scheduleNaming(wt, prompt, wt.variant);
|
|
33381
33585
|
}
|
|
33586
|
+
if (turn.end !== "asking")
|
|
33587
|
+
this.syncAfterTurn(worktreeId);
|
|
33382
33588
|
if (turn.end !== "done" && turn.end !== "stopped")
|
|
33383
33589
|
return;
|
|
33384
33590
|
for (const p of d.runtime.get(worktreeId)?.procs?.states() ?? []) {
|
|
@@ -33429,7 +33635,7 @@ class WorktreeService {
|
|
|
33429
33635
|
claimed.effort = opts.effort;
|
|
33430
33636
|
if (profile !== undefined && profile !== resolveRun(repo, claimed).profile) {
|
|
33431
33637
|
claimed.profile = profile;
|
|
33432
|
-
this.restartProcs(claimed
|
|
33638
|
+
this.restartProcs(claimed);
|
|
33433
33639
|
}
|
|
33434
33640
|
const carried = await this.carryMain(repo, claimed, !!opts.carry);
|
|
33435
33641
|
this.d.state.save();
|
|
@@ -33446,8 +33652,8 @@ class WorktreeService {
|
|
|
33446
33652
|
if (!await isGitRepo(repo.path)) {
|
|
33447
33653
|
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.`);
|
|
33448
33654
|
}
|
|
33449
|
-
const { id, dir } = freeSlot(
|
|
33450
|
-
const wtPath =
|
|
33655
|
+
const { id, dir } = freeSlot(join30(this.d.paths.worktreesDir, repo.name));
|
|
33656
|
+
const wtPath = join30(this.d.paths.worktreesDir, repo.name, dir);
|
|
33451
33657
|
const branch = `toyon/${dir}`;
|
|
33452
33658
|
await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
|
|
33453
33659
|
const wt = {
|
|
@@ -33524,7 +33730,7 @@ class WorktreeService {
|
|
|
33524
33730
|
};
|
|
33525
33731
|
const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
|
|
33526
33732
|
const restored = (carried.ok ? carried.out.split(`
|
|
33527
|
-
`).filter(Boolean) : []).filter((p) => !existsSync33(
|
|
33733
|
+
`).filter(Boolean) : []).filter((p) => !existsSync33(join30(wt.path, p)));
|
|
33528
33734
|
const applied = await git(wt.path, "stash", "apply", sha);
|
|
33529
33735
|
if (applied.ok) {
|
|
33530
33736
|
const ref = await entry();
|
|
@@ -33564,9 +33770,9 @@ class WorktreeService {
|
|
|
33564
33770
|
const branch = kind === "pr" ? `pr/${number}` : ref;
|
|
33565
33771
|
const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
|
|
33566
33772
|
let slug = branchSlug(title) || "branch";
|
|
33567
|
-
if (existsSync33(
|
|
33773
|
+
if (existsSync33(join30(this.d.paths.worktreesDir, repo.name, slug)))
|
|
33568
33774
|
slug = `${slug}-${shortId().slice(0, 4)}`;
|
|
33569
|
-
const wtPath =
|
|
33775
|
+
const wtPath = join30(this.d.paths.worktreesDir, repo.name, slug);
|
|
33570
33776
|
await withRepoLock(repo.path, async () => {
|
|
33571
33777
|
const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
|
|
33572
33778
|
if (out)
|
|
@@ -33681,8 +33887,8 @@ class WorktreeService {
|
|
|
33681
33887
|
throw new UserError(`no profile "${name}" in ${repo.configFile}`);
|
|
33682
33888
|
return name;
|
|
33683
33889
|
}
|
|
33684
|
-
restartProcs(wt
|
|
33685
|
-
fireAndForget(wt.id, this.d.runtime.stopProcs(wt.id).then(() => this.d.runtime.
|
|
33890
|
+
restartProcs(wt) {
|
|
33891
|
+
fireAndForget(wt.id, this.d.runtime.stopProcs(wt.id).then(() => this.d.runtime.wake(wt.id)), "profile restart");
|
|
33686
33892
|
}
|
|
33687
33893
|
setProfile(worktreeId, name) {
|
|
33688
33894
|
const { wt, repo } = this.d.state.requireWorktreeWithRepo(worktreeId);
|
|
@@ -33694,7 +33900,7 @@ class WorktreeService {
|
|
|
33694
33900
|
wt.profile = profile;
|
|
33695
33901
|
this.d.state.save();
|
|
33696
33902
|
this.d.hub.emit("worktreesChanged");
|
|
33697
|
-
this.restartProcs(wt
|
|
33903
|
+
this.restartProcs(wt);
|
|
33698
33904
|
}
|
|
33699
33905
|
setMode(worktreeId, mode) {
|
|
33700
33906
|
if (!this.d.state.setMode(worktreeId, mode))
|
|
@@ -33768,6 +33974,7 @@ class WorktreeService {
|
|
|
33768
33974
|
}
|
|
33769
33975
|
async takeDown(wt, archive, reason) {
|
|
33770
33976
|
this.going.add(wt.id);
|
|
33977
|
+
this.syncQueued.delete(wt.id);
|
|
33771
33978
|
try {
|
|
33772
33979
|
return await this.removeAndRecord(wt, archive, reason);
|
|
33773
33980
|
} finally {
|
|
@@ -33799,7 +34006,10 @@ class WorktreeService {
|
|
|
33799
34006
|
return k;
|
|
33800
34007
|
});
|
|
33801
34008
|
const sessionId = this.d.state.session(worktreeId);
|
|
34009
|
+
const placed = repo.promptedAt;
|
|
33802
34010
|
this.d.state.removeWorktree(worktreeId);
|
|
34011
|
+
if (repo.promptedAt !== placed)
|
|
34012
|
+
this.d.hub.emit("reposChanged");
|
|
33803
34013
|
releasePort(wt.proxyPort);
|
|
33804
34014
|
let archived = null;
|
|
33805
34015
|
await dropTurnRefs(repo.path, worktreeId);
|
|
@@ -33824,7 +34034,7 @@ class WorktreeService {
|
|
|
33824
34034
|
}
|
|
33825
34035
|
}
|
|
33826
34036
|
async keep(repo, wt) {
|
|
33827
|
-
const index =
|
|
34037
|
+
const index = join30(this.d.paths.archiveDir, `${wt.id}.index`);
|
|
33828
34038
|
const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
|
|
33829
34039
|
if (!kept)
|
|
33830
34040
|
log.warn(wt.id, "could not keep its git state");
|
|
@@ -33861,10 +34071,10 @@ class WorktreeService {
|
|
|
33861
34071
|
log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
|
|
33862
34072
|
}
|
|
33863
34073
|
plansScratch(worktreeId) {
|
|
33864
|
-
return
|
|
34074
|
+
return join30(this.d.paths.archiveDir, `${worktreeId}.plans`);
|
|
33865
34075
|
}
|
|
33866
34076
|
keepPlans(wt) {
|
|
33867
|
-
const from =
|
|
34077
|
+
const from = join30(wt.path, PLANS_DIR);
|
|
33868
34078
|
if (!existsSync33(from))
|
|
33869
34079
|
return;
|
|
33870
34080
|
try {
|
|
@@ -33931,6 +34141,24 @@ class WorktreeService {
|
|
|
33931
34141
|
});
|
|
33932
34142
|
this.archiveChat(wt, repo, kept, sessionId);
|
|
33933
34143
|
}
|
|
34144
|
+
fillSentStamps() {
|
|
34145
|
+
let filled = 0;
|
|
34146
|
+
for (const repo of this.d.state.repos) {
|
|
34147
|
+
if (repo.promptedAt !== undefined)
|
|
34148
|
+
continue;
|
|
34149
|
+
let at = 0;
|
|
34150
|
+
for (const rec of this.archive.recordsOf(repo))
|
|
34151
|
+
at = Math.max(at, sentAt(rec.worktree));
|
|
34152
|
+
if (at === 0)
|
|
34153
|
+
continue;
|
|
34154
|
+
repo.promptedAt = at;
|
|
34155
|
+
filled++;
|
|
34156
|
+
}
|
|
34157
|
+
if (filled === 0)
|
|
34158
|
+
return;
|
|
34159
|
+
this.d.state.save();
|
|
34160
|
+
log.info("daemon", `placed ${filled} projects by what their archives were sent`);
|
|
34161
|
+
}
|
|
33934
34162
|
async fillLandMarks() {
|
|
33935
34163
|
const jobs = [];
|
|
33936
34164
|
const rows = new Set;
|
|
@@ -33986,7 +34214,7 @@ class WorktreeService {
|
|
|
33986
34214
|
}
|
|
33987
34215
|
archivedAttachment(archiveId, file) {
|
|
33988
34216
|
const files = this.archive.chatFiles(archiveId);
|
|
33989
|
-
return files && isAttachmentFile(file) ?
|
|
34217
|
+
return files && isAttachmentFile(file) ? join30(files.attachments, file) : null;
|
|
33990
34218
|
}
|
|
33991
34219
|
archivedGit(archiveId) {
|
|
33992
34220
|
const rec = this.archive.get(archiveId);
|
|
@@ -34041,7 +34269,7 @@ class WorktreeService {
|
|
|
34041
34269
|
throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
|
|
34042
34270
|
if (rec.auto)
|
|
34043
34271
|
log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
|
|
34044
|
-
const path = existsSync33(old.path) ?
|
|
34272
|
+
const path = existsSync33(old.path) ? join30(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
|
|
34045
34273
|
const branch = await withRepoLock(repo.path, async () => {
|
|
34046
34274
|
if (!await commitOf(repo.path, archiveRef(old.id))) {
|
|
34047
34275
|
throw new UserError(`${old.title}'s kept commits are gone from git`);
|
|
@@ -34079,7 +34307,7 @@ class WorktreeService {
|
|
|
34079
34307
|
...createdBy ? { createdBy } : {}
|
|
34080
34308
|
};
|
|
34081
34309
|
try {
|
|
34082
|
-
this.archive.take(old.id, { ...this.chatFiles(old.id), plans:
|
|
34310
|
+
this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join30(path, PLANS_DIR) });
|
|
34083
34311
|
} catch (e) {
|
|
34084
34312
|
log.warn(old.id, "could not move its chat back out of the archive", e);
|
|
34085
34313
|
}
|
|
@@ -34221,10 +34449,10 @@ class WorktreeService {
|
|
|
34221
34449
|
}
|
|
34222
34450
|
}
|
|
34223
34451
|
async mirrorBaseIgnore(repo) {
|
|
34224
|
-
const src =
|
|
34452
|
+
const src = join30(repo.path, ".gitignore");
|
|
34225
34453
|
const tracked = existsSync33(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
|
|
34226
34454
|
const mirror = existsSync33(src) && !tracked;
|
|
34227
|
-
const lines = mirror ?
|
|
34455
|
+
const lines = mirror ? readFileSync20(src, "utf8").split(`
|
|
34228
34456
|
`).filter((l) => l.trim() !== "") : [];
|
|
34229
34457
|
await excludeBlock(repo.path, BASE_IGNORE, lines);
|
|
34230
34458
|
}
|
|
@@ -34236,8 +34464,8 @@ class WorktreeService {
|
|
|
34236
34464
|
await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
|
|
34237
34465
|
run.finish(0);
|
|
34238
34466
|
}
|
|
34239
|
-
const srcNm =
|
|
34240
|
-
const dstNm =
|
|
34467
|
+
const srcNm = join30(depsSource, "node_modules");
|
|
34468
|
+
const dstNm = join30(wt.path, "node_modules");
|
|
34241
34469
|
if (existsSync33(srcNm) && !existsSync33(dstNm)) {
|
|
34242
34470
|
const r = await cloneTree(srcNm, dstNm);
|
|
34243
34471
|
if (r.ok)
|
|
@@ -34246,10 +34474,10 @@ class WorktreeService {
|
|
|
34246
34474
|
log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
|
|
34247
34475
|
}
|
|
34248
34476
|
for (const f of LOCAL_CONFIG_FILES) {
|
|
34249
|
-
const src =
|
|
34250
|
-
if (!existsSync33(src) || existsSync33(
|
|
34477
|
+
const src = join30(depsSource, f);
|
|
34478
|
+
if (!existsSync33(src) || existsSync33(join30(wt.path, f)))
|
|
34251
34479
|
continue;
|
|
34252
|
-
const r = await run("cp", [src,
|
|
34480
|
+
const r = await run("cp", [src, join30(wt.path, f)], wt.path);
|
|
34253
34481
|
if (r.ok)
|
|
34254
34482
|
this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
|
|
34255
34483
|
else
|
|
@@ -34502,6 +34730,9 @@ ${wt.landing.body}` : s;
|
|
|
34502
34730
|
this.ops.delete(id);
|
|
34503
34731
|
release?.();
|
|
34504
34732
|
this.d.hub.emit("worktreesChanged");
|
|
34733
|
+
const status = this.d.runtime.agentFor(id)?.status;
|
|
34734
|
+
if (status !== "working" && status !== "waiting")
|
|
34735
|
+
this.syncAfterTurn(id);
|
|
34505
34736
|
}
|
|
34506
34737
|
}
|
|
34507
34738
|
step(id, name) {
|
|
@@ -34529,7 +34760,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
34529
34760
|
return { result: fetched };
|
|
34530
34761
|
if (wt.pr?.state === "open") {
|
|
34531
34762
|
const dirty = (await statusFiles(wt.path)).length > 0;
|
|
34532
|
-
const missing = dirty || (await aheadUpstream(wt.path) ?? 0) > 0;
|
|
34763
|
+
const missing = dirty || (await aheadUpstream(wt.path, wt.branch) ?? 0) > 0;
|
|
34533
34764
|
if (!missing)
|
|
34534
34765
|
return { result: await mergePr(wt.path, wt.pr.number, policy.merge, w) };
|
|
34535
34766
|
const committed = await this.commitIfDirty(wt, message);
|
|
@@ -34775,8 +35006,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
34775
35006
|
const own = r.wt ? hasOwnBranch(r.wt) : false;
|
|
34776
35007
|
const carry = !!r.wt;
|
|
34777
35008
|
const status = this.d.runtime.agentFor(worktreeId)?.status;
|
|
34778
|
-
if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0)
|
|
34779
|
-
|
|
35009
|
+
if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0) {
|
|
35010
|
+
this.syncQueued.add(worktreeId);
|
|
35011
|
+
this.d.hub.emit("worktreesChanged");
|
|
35012
|
+
return { result: { ok: true, message: `${r.name} syncs from ${r.base} once the turn ends` }, base: r.base };
|
|
35013
|
+
}
|
|
34780
35014
|
const w = r.wt ? this.landWatch(r.wt) : UNWATCHED;
|
|
34781
35015
|
const result = await this.ship(worktreeId, "sync-main", async () => {
|
|
34782
35016
|
if (r.wt) {
|
|
@@ -34794,6 +35028,31 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
34794
35028
|
});
|
|
34795
35029
|
return { result: r.wt ? this.fixShip(r.wt, result, r.base) : result, base: r.base };
|
|
34796
35030
|
}
|
|
35031
|
+
unqueueSync(worktreeId) {
|
|
35032
|
+
if (!this.syncQueued.delete(worktreeId))
|
|
35033
|
+
return;
|
|
35034
|
+
this.d.hub.emit("worktreesChanged");
|
|
35035
|
+
}
|
|
35036
|
+
syncAfterTurn(worktreeId) {
|
|
35037
|
+
if (!this.syncQueued.has(worktreeId))
|
|
35038
|
+
return;
|
|
35039
|
+
if (this.ops.has(worktreeId))
|
|
35040
|
+
return;
|
|
35041
|
+
this.syncQueued.delete(worktreeId);
|
|
35042
|
+
const release = this.d.runtime.agentFor(worktreeId)?.hold();
|
|
35043
|
+
this.d.hub.emit("worktreesChanged");
|
|
35044
|
+
fireAndForget(worktreeId, (async () => {
|
|
35045
|
+
try {
|
|
35046
|
+
const { result } = await this.sync(worktreeId);
|
|
35047
|
+
if (!result.ok && !result.asked)
|
|
35048
|
+
this.d.hub.emit("failed", worktreeId, result.message);
|
|
35049
|
+
} catch (e) {
|
|
35050
|
+
this.d.hub.emit("failed", worktreeId, e instanceof Error ? e.message : String(e));
|
|
35051
|
+
} finally {
|
|
35052
|
+
release?.();
|
|
35053
|
+
}
|
|
35054
|
+
})(), "sync after the turn");
|
|
35055
|
+
}
|
|
34797
35056
|
async pull(worktreeId) {
|
|
34798
35057
|
const wt = this.d.state.requireWorktree(worktreeId);
|
|
34799
35058
|
if (!isMain(wt))
|
|
@@ -34933,9 +35192,10 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
34933
35192
|
return { ...ab, ...unpushed === undefined ? {} : { unpushed }, dirty };
|
|
34934
35193
|
}
|
|
34935
35194
|
async unpushed(id, path) {
|
|
34936
|
-
|
|
35195
|
+
const wt = this.d.state.worktree(id);
|
|
35196
|
+
if (wt?.pr?.state !== "open")
|
|
34937
35197
|
return;
|
|
34938
|
-
return await aheadUpstream(path) ?? undefined;
|
|
35198
|
+
return await aheadUpstream(path, wt.branch) ?? undefined;
|
|
34939
35199
|
}
|
|
34940
35200
|
async freshCounts(worktreeId) {
|
|
34941
35201
|
if (this.going.has(worktreeId))
|
|
@@ -35152,7 +35412,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
|
|
|
35152
35412
|
usage: this.usageFor(wt.id),
|
|
35153
35413
|
transcript: transcriptPathFor(this.d.paths.transcriptsDir, wt.id),
|
|
35154
35414
|
sessionId: this.d.state.session(wt.id),
|
|
35155
|
-
shipping: this.ops.get(wt.id)
|
|
35415
|
+
shipping: this.ops.get(wt.id),
|
|
35416
|
+
syncQueued: this.syncQueued.has(wt.id) || undefined
|
|
35156
35417
|
};
|
|
35157
35418
|
});
|
|
35158
35419
|
}
|
|
@@ -35279,16 +35540,17 @@ if (pruned > 0)
|
|
|
35279
35540
|
var managed = await loadManaged();
|
|
35280
35541
|
var paths = makePaths();
|
|
35281
35542
|
ensureDirs(paths);
|
|
35543
|
+
forwardTranscripts(paths.transcriptsDir);
|
|
35282
35544
|
var token = loadOrCreateToken(paths);
|
|
35283
35545
|
var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
|
|
35284
|
-
var helperApp =
|
|
35546
|
+
var helperApp = join31(paths.home, DAEMON_FILES.helper);
|
|
35285
35547
|
var helper = helperWanted({ platform: process.platform, cloud: cloud.enabled, port, home: process.env.TOYON_HOME }) ? new Helper({
|
|
35286
35548
|
dir: helperApp,
|
|
35287
35549
|
stub: HELPER_STUB,
|
|
35288
35550
|
bun: process.execPath,
|
|
35289
35551
|
cli: CLI_ENTRY,
|
|
35290
|
-
log:
|
|
35291
|
-
icon:
|
|
35552
|
+
log: join31(paths.home, DAEMON_FILES.launcherLog),
|
|
35553
|
+
icon: join31(SHELL_DIST, "icon.svg"),
|
|
35292
35554
|
version: package_default.version
|
|
35293
35555
|
}) : null;
|
|
35294
35556
|
if (helper) {
|
|
@@ -35505,6 +35767,7 @@ var keepAwake = new KeepAwake({
|
|
|
35505
35767
|
answerable: () => setting.get() !== null
|
|
35506
35768
|
});
|
|
35507
35769
|
await worktrees.fillLandMarks();
|
|
35770
|
+
worktrees.fillSentStamps();
|
|
35508
35771
|
var {
|
|
35509
35772
|
server,
|
|
35510
35773
|
branded,
|
|
@@ -35581,7 +35844,7 @@ hub.on("remoteChanged", () => {
|
|
|
35581
35844
|
fireAndForget("remote", tailnet.refresh(), "ask Tailscale");
|
|
35582
35845
|
});
|
|
35583
35846
|
fireAndForget("remote", tailnet.start(), "ask Tailscale");
|
|
35584
|
-
|
|
35847
|
+
writeFileSync13(paths.pidFile, `${process.pid}
|
|
35585
35848
|
`);
|
|
35586
35849
|
var stopLagSampler = startLagSampler();
|
|
35587
35850
|
await runtime.reclaimOrphans();
|