toyon 0.12.0 → 0.13.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.
Files changed (28) hide show
  1. package/dist/bridge.js +1 -1
  2. package/dist/cli.js +19 -25
  3. package/dist/daemon.js +1048 -821
  4. package/dist/shell/assets/{Editor-ofBOHWJt.js → Editor-CUcgZuoM.js} +3 -3
  5. package/dist/shell/assets/{XTerm-C_Gv5uo2.js → XTerm-V6sBDIe-.js} +1 -1
  6. package/dist/shell/assets/{cssMode-LqxC-sXw.js → cssMode-CSyiAvIn.js} +1 -1
  7. package/dist/shell/assets/{freemarker2-DUqPZFAL.js → freemarker2-C4-FNK3V.js} +1 -1
  8. package/dist/shell/assets/{grammar-CFa79rtH.js → grammar-DzHb7e7O.js} +1 -1
  9. package/dist/shell/assets/{handlebars-CF9us5mA.js → handlebars-DcjVaOCM.js} +1 -1
  10. package/dist/shell/assets/{html-SO575qLW.js → html-Csl8cogA.js} +1 -1
  11. package/dist/shell/assets/{htmlMode-XUsZRqmU.js → htmlMode-rewe9UaO.js} +1 -1
  12. package/dist/shell/assets/index-Bkn7zNFw.js +267 -0
  13. package/dist/shell/assets/index-iVaPr_If.css +1 -0
  14. package/dist/shell/assets/{javascript-4ER2NhcO.js → javascript--pKV-E-G.js} +1 -1
  15. package/dist/shell/assets/{jsonMode-D-UOjsYN.js → jsonMode-zPVc0E6l.js} +1 -1
  16. package/dist/shell/assets/{liquid-CMXc0SdF.js → liquid-jZT5xQG-.js} +1 -1
  17. package/dist/shell/assets/{lspLanguageFeatures-D4Z0LyWk.js → lspLanguageFeatures-BuiqKs90.js} +1 -1
  18. package/dist/shell/assets/{mdx-Dc4E4gPJ.js → mdx-DwnR-ia2.js} +1 -1
  19. package/dist/shell/assets/{python-DhCnlsga.js → python-DU3pl9Zu.js} +1 -1
  20. package/dist/shell/assets/{razor-CGRu_Zyc.js → razor-BzVUPqHs.js} +1 -1
  21. package/dist/shell/assets/{tsMode-BoDg-EAl.js → tsMode-ONmZhHHZ.js} +1 -1
  22. package/dist/shell/assets/{xml-Dl50xhA0.js → xml-BXW7X3BT.js} +1 -1
  23. package/dist/shell/assets/{yaml-CoqiVhBi.js → yaml-DFE79ex1.js} +1 -1
  24. package/dist/shell/index.html +17 -10
  25. package/dist/shell/manifest.json +1 -1
  26. package/package.json +1 -1
  27. package/dist/shell/assets/index-BYJYiHrs.css +0 -1
  28. 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 writeFileSync12 } from "fs";
4
- import { dirname as dirname13, join as join30 } from "path";
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 = 100;
6605
+ var PROTOCOL_VERSION = 105;
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: "signed-out", name, line: "Tailscale is disconnected; run `tailscale up`, then this again" };
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.12.0",
8531
+ version: "0.13.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 = [...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
- "After your last edits the preview threw this in the user's browser:",
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 existsSync10, readFileSync as readFileSync9 } from "fs";
15792
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
15231
15793
  import { appendFile } from "fs/promises";
15232
- import { join as join8 } from "path";
15794
+ import { join as join9 } from "path";
15233
15795
  function transcriptPathFor(transcriptsDir, worktreeId) {
15234
- return join8(transcriptsDir, `${worktreeId}.jsonl`);
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 existsSync10(this.path) ? parseTranscript(readFileSync9(this.path, "utf8"), this.tag) : [];
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
- held = false;
16134
+ holds = 0;
16135
+ get held() {
16136
+ return this.holds > 0;
16137
+ }
16127
16138
  conn = null;
16128
16139
  connecting = null;
16129
16140
  opening = null;
@@ -16316,14 +16327,14 @@ class AcpSession {
16316
16327
  fireAndForget(this.d.worktreeId, this.drain(), "agent drain");
16317
16328
  }
16318
16329
  hold() {
16319
- this.held = true;
16330
+ this.holds++;
16320
16331
  let released = false;
16321
16332
  return () => {
16322
16333
  if (released)
16323
16334
  return;
16324
16335
  released = true;
16325
- this.held = false;
16326
- if (!this.running && this.queue.length > 0)
16336
+ this.holds--;
16337
+ if (!this.held && !this.running && this.queue.length > 0)
16327
16338
  fireAndForget(this.d.worktreeId, this.drain(), "agent drain");
16328
16339
  };
16329
16340
  }
@@ -19714,32 +19725,83 @@ function makePlanner(agents) {
19714
19725
  return async (request, cwd, agentId) => parsePlan(await askFreshAgent(agents, agentId, cwd, PLAN_SYSTEM, planPrompt(request), { quick: "prefer" }));
19715
19726
  }
19716
19727
 
19728
+ // ../daemon/src/agent/transcriptForward.ts
19729
+ import { readdirSync as readdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
19730
+ import { join as join12 } from "path";
19731
+ function forwardLine(line) {
19732
+ if (!line.includes('"page-error"'))
19733
+ return null;
19734
+ let entry;
19735
+ try {
19736
+ entry = JSON.parse(line);
19737
+ } catch {
19738
+ return null;
19739
+ }
19740
+ const e = entry.event;
19741
+ if (e?.type !== "page-error" || Array.isArray(e.errors) || typeof e.message !== "string")
19742
+ return null;
19743
+ const { message, ...rest } = e;
19744
+ return JSON.stringify({ ...entry, event: { ...rest, errors: [message] } });
19745
+ }
19746
+ function forwardText(text) {
19747
+ let changed = 0;
19748
+ const lines = text.split(`
19749
+ `).map((line) => {
19750
+ const next = forwardLine(line);
19751
+ if (next === null)
19752
+ return line;
19753
+ changed++;
19754
+ return next;
19755
+ });
19756
+ return { text: changed ? lines.join(`
19757
+ `) : text, changed };
19758
+ }
19759
+ function forwardTranscripts(dir) {
19760
+ let files = 0;
19761
+ let rows = 0;
19762
+ for (const name of readdirSync5(dir)) {
19763
+ if (!name.endsWith(".jsonl"))
19764
+ continue;
19765
+ const path = join12(dir, name);
19766
+ const { text, changed } = forwardText(readFileSync11(path, "utf8"));
19767
+ if (!changed)
19768
+ continue;
19769
+ const tmp = `${path}.tmp`;
19770
+ writeFileSync6(tmp, text);
19771
+ renameSync2(tmp, path);
19772
+ files++;
19773
+ rows += changed;
19774
+ }
19775
+ if (files)
19776
+ log.info("transcripts", `rewrote ${rows} page-error row(s) in ${files} transcript(s) to the list shape`);
19777
+ }
19778
+
19717
19779
  // ../daemon/src/core/assets.ts
19718
- import { existsSync as existsSync14, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "fs";
19719
- import { join as join12, resolve as resolve5 } from "path";
19780
+ import { existsSync as existsSync14, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync5 } from "fs";
19781
+ import { join as join13, resolve as resolve5 } from "path";
19720
19782
  function locateAssets(here, env = process.env) {
19721
- const packaged = existsSync14(join12(here, "shell", "index.html"));
19783
+ const packaged = existsSync14(join13(here, "shell", "index.html"));
19722
19784
  return {
19723
- shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join12(here, "shell") : join12(here, "../../shell/dist")),
19724
- bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join12(here, "bridge.js") : join12(here, "../../bridge/dist/bridge.js")),
19725
- helperStub: packaged ? join12(here, "helper-stub") : join12(here, "../dist/helper-stub"),
19726
- cliEntry: packaged ? join12(here, "cli.js") : join12(here, "../../cli/src/cli.ts"),
19785
+ shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join13(here, "shell") : join13(here, "../../shell/dist")),
19786
+ bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join13(here, "bridge.js") : join13(here, "../../bridge/dist/bridge.js")),
19787
+ helperStub: packaged ? join13(here, "helper-stub") : join13(here, "../dist/helper-stub"),
19788
+ cliEntry: packaged ? join13(here, "cli.js") : join13(here, "../../cli/src/cli.ts"),
19727
19789
  sourceRoot: packaged ? null : resolve5(here, "../../.."),
19728
- packageJson: packaged ? join12(here, "..", "package.json") : null
19790
+ packageJson: packaged ? join13(here, "..", "package.json") : null
19729
19791
  };
19730
19792
  }
19731
19793
  var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
19732
19794
  function pruneAssets(shellDist) {
19733
- const dir = join12(shellDist, "assets");
19795
+ const dir = join13(shellDist, "assets");
19734
19796
  let names;
19735
19797
  try {
19736
- names = readdirSync5(dir);
19798
+ names = readdirSync6(dir);
19737
19799
  } catch {
19738
19800
  return 0;
19739
19801
  }
19740
19802
  const found = [];
19741
19803
  for (const name of names) {
19742
- const path = join12(dir, name);
19804
+ const path = join13(dir, name);
19743
19805
  try {
19744
19806
  found.push({ path, mtime: statSync5(path).mtimeMs });
19745
19807
  } catch {}
@@ -20281,7 +20343,7 @@ class GrantCodes {
20281
20343
  import { createHash as createHash3 } from "crypto";
20282
20344
  import { existsSync as existsSync16 } from "fs";
20283
20345
  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 join13 } from "path";
20346
+ import { basename as basename4, dirname as dirname5, join as join14 } from "path";
20285
20347
  var EXECUTABLE = "Toyon";
20286
20348
  function helperWanted(where) {
20287
20349
  return where.platform === "darwin" && !where.cloud && where.port === DAEMON_DEFAULT_PORT && where.home === undefined;
@@ -20364,19 +20426,19 @@ class Helper {
20364
20426
  return "skipped";
20365
20427
  const stubHash = createHash3("sha256").update(await readFile2(spec.stub)).digest("hex").slice(0, 16);
20366
20428
  const { plist, script } = helperFiles(spec, stubHash);
20367
- const contents = join13(spec.dir, "Contents");
20368
- const resources = join13(contents, "Resources");
20369
- const stub = join13(contents, "MacOS", EXECUTABLE);
20370
- const scriptFile = join13(resources, "launch.sh");
20429
+ const contents = join14(spec.dir, "Contents");
20430
+ const resources = join14(contents, "Resources");
20431
+ const stub = join14(contents, "MacOS", EXECUTABLE);
20432
+ const scriptFile = join14(resources, "launch.sh");
20371
20433
  if (existsSync16(stub) && existsSync16(scriptFile) && await readFile2(scriptFile, "utf8") === script)
20372
20434
  return "kept";
20373
- await mkdir2(join13(contents, "MacOS"), { recursive: true });
20435
+ await mkdir2(join14(contents, "MacOS"), { recursive: true });
20374
20436
  await mkdir2(resources, { recursive: true });
20375
- await writeFile2(join13(contents, "Info.plist"), plist);
20437
+ await writeFile2(join14(contents, "Info.plist"), plist);
20376
20438
  await writeFile2(scriptFile, script, { mode: 493 });
20377
20439
  await copyFile3(spec.stub, stub);
20378
20440
  await chmod(stub, 493);
20379
- if (!existsSync16(join13(resources, "AppIcon.icns")))
20441
+ if (!existsSync16(join14(resources, "AppIcon.icns")))
20380
20442
  await this.drawIcon(resources);
20381
20443
  await this.run(["xattr", "-cr", spec.dir]);
20382
20444
  if (!await this.run(["codesign", "--force", "--deep", "-s", "-", spec.dir])) {
@@ -20387,16 +20449,16 @@ class Helper {
20387
20449
  }
20388
20450
  async drawIcon(resources) {
20389
20451
  const svg = this.spec.icon;
20390
- const tmp = join13(resources, "iconset.tmp");
20391
- const set = join13(tmp, "AppIcon.iconset");
20452
+ const tmp = join14(resources, "iconset.tmp");
20453
+ const set = join14(tmp, "AppIcon.iconset");
20392
20454
  await mkdir2(set, { recursive: true });
20393
20455
  await this.run(["qlmanage", "-t", "-s", "1024", "-o", tmp, svg]);
20394
- const big = join13(tmp, `${basename4(svg)}.png`);
20456
+ const big = join14(tmp, `${basename4(svg)}.png`);
20395
20457
  if (existsSync16(big)) {
20396
20458
  for (const size of [16, 32, 64, 128, 256, 512, 1024]) {
20397
- await this.run(["sips", "-z", String(size), String(size), big, "--out", join13(set, `icon_${size}x${size}.png`)]);
20459
+ await this.run(["sips", "-z", String(size), String(size), big, "--out", join14(set, `icon_${size}x${size}.png`)]);
20398
20460
  }
20399
- await this.run(["iconutil", "-c", "icns", set, "-o", join13(resources, "AppIcon.icns")]);
20461
+ await this.run(["iconutil", "-c", "icns", set, "-o", join14(resources, "AppIcon.icns")]);
20400
20462
  }
20401
20463
  await rm(tmp, { recursive: true, force: true });
20402
20464
  }
@@ -20620,7 +20682,7 @@ class Origins {
20620
20682
  }
20621
20683
 
20622
20684
  // ../daemon/src/core/remoteSwitch.ts
20623
- import { writeFileSync as writeFileSync6 } from "fs";
20685
+ import { writeFileSync as writeFileSync7 } from "fs";
20624
20686
  class RemoteSwitch {
20625
20687
  d;
20626
20688
  constructor(d) {
@@ -20653,7 +20715,7 @@ class RemoteSwitch {
20653
20715
  const accepted = acceptRemote(view, this.d.policy);
20654
20716
  if (typeof accepted === "string")
20655
20717
  throw new UserError(accepted);
20656
- writeFileSync6(this.d.file, `${JSON.stringify(accepted, null, 2)}
20718
+ writeFileSync7(this.d.file, `${JSON.stringify(accepted, null, 2)}
20657
20719
  `);
20658
20720
  return this.live(accepted);
20659
20721
  }
@@ -20835,7 +20897,7 @@ class SelfWatch {
20835
20897
 
20836
20898
  // ../daemon/src/core/state.ts
20837
20899
  import { randomBytes as randomBytes6 } from "crypto";
20838
- import { existsSync as existsSync17, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "fs";
20900
+ import { existsSync as existsSync17, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
20839
20901
  var TRUSTED_MAX = 16;
20840
20902
  var DAEMON_GROUPS = "daemon";
20841
20903
  var GROUPS_SAVE_MS = 250;
@@ -20848,7 +20910,7 @@ function loadState(paths) {
20848
20910
  return structuredClone(empty);
20849
20911
  let raw;
20850
20912
  try {
20851
- raw = readFileSync11(STATE_FILE, "utf8");
20913
+ raw = readFileSync12(STATE_FILE, "utf8");
20852
20914
  } catch (e) {
20853
20915
  log.error("state", `cannot read ${STATE_FILE}; starting empty`, e);
20854
20916
  return structuredClone(empty);
@@ -20859,7 +20921,7 @@ function loadState(paths) {
20859
20921
  } catch (e) {
20860
20922
  const backup = `${STATE_FILE}.corrupt-${Date.now()}`;
20861
20923
  try {
20862
- writeFileSync7(backup, raw);
20924
+ writeFileSync8(backup, raw);
20863
20925
  } catch (be) {
20864
20926
  log.warn("state", "could not write the corrupt-state backup", be);
20865
20927
  }
@@ -20890,16 +20952,16 @@ function loadState(paths) {
20890
20952
  }
20891
20953
  function saveState(paths, state) {
20892
20954
  const tmp = `${paths.stateFile}.tmp`;
20893
- writeFileSync7(tmp, JSON.stringify(state, null, 2));
20894
- renameSync2(tmp, paths.stateFile);
20955
+ writeFileSync8(tmp, JSON.stringify(state, null, 2));
20956
+ renameSync3(tmp, paths.stateFile);
20895
20957
  }
20896
20958
  function loadOrCreateToken(paths) {
20897
20959
  ensureDirs(paths);
20898
20960
  const TOKEN_FILE = paths.tokenFile;
20899
20961
  if (existsSync17(TOKEN_FILE))
20900
- return readFileSync11(TOKEN_FILE, "utf8").trim();
20962
+ return readFileSync12(TOKEN_FILE, "utf8").trim();
20901
20963
  const token = randomBytes6(32).toString("hex");
20902
- writeFileSync7(TOKEN_FILE, token, { mode: 384 });
20964
+ writeFileSync8(TOKEN_FILE, token, { mode: 384 });
20903
20965
  return token;
20904
20966
  }
20905
20967
 
@@ -20985,6 +21047,10 @@ class StateStore {
20985
21047
  this.save();
20986
21048
  }
20987
21049
  removeWorktree(id) {
21050
+ const wt = this.state.worktrees.find((w) => w.id === id);
21051
+ const repo = wt && wt.kind !== "spare" ? this.repo(wt.repoId) : undefined;
21052
+ if (wt && repo)
21053
+ repo.promptedAt = Math.max(repo.promptedAt ?? 0, sentAt(wt));
20988
21054
  this.state.worktrees = this.state.worktrees.filter((w) => w.id !== id);
20989
21055
  delete this.state.sessions[id];
20990
21056
  if (this.state.seen)
@@ -21168,7 +21234,7 @@ class StateStore {
21168
21234
 
21169
21235
  // ../daemon/src/core/tailnet.ts
21170
21236
  var PHONE_OS = new Set(["android", "ios"]);
21171
- function tailnetPeers(json) {
21237
+ function tailnetListed(json) {
21172
21238
  let st;
21173
21239
  try {
21174
21240
  st = JSON.parse(json);
@@ -21183,12 +21249,31 @@ function tailnetPeers(json) {
21183
21249
  continue;
21184
21250
  const host = dnsName(p.DNSName);
21185
21251
  const name = p.HostName || host.split(".")[0] || "a device";
21186
- peers.push({ name, host, ip: p.TailscaleIPs?.[0] ?? null, os: (p.OS ?? "").toLowerCase() });
21252
+ peers.push({
21253
+ name,
21254
+ host,
21255
+ ip: p.TailscaleIPs?.[0] ?? null,
21256
+ os: (p.OS ?? "").toLowerCase(),
21257
+ online: p.Online === true
21258
+ });
21187
21259
  }
21188
- return peers;
21260
+ return { here: st.Self?.Online !== false, peers };
21189
21261
  }
21190
21262
  var isPhone = (p) => PHONE_OS.has(p.os);
21263
+ function loginUrl(json) {
21264
+ try {
21265
+ const st = JSON.parse(json);
21266
+ return typeof st?.AuthURL === "string" && st.AuthURL !== "" ? st.AuthURL : null;
21267
+ } catch {
21268
+ return null;
21269
+ }
21270
+ }
21271
+ var lastLine2 = (r) => (r.err.trim() || r.out.trim()).split(`
21272
+ `).at(-1) ?? "tailscale failed";
21273
+ var LOGIN_URL_WAIT_MS = 4000;
21274
+ var LOGIN_URL_POLL_MS = 300;
21191
21275
  var PROBE_TIMEOUT_MS2 = 2000;
21276
+ var ANSWERED_RECENTLY_MS = 3 * 60000;
21192
21277
  var probe = async (origin, named) => {
21193
21278
  try {
21194
21279
  const r = await fetch(`${origin}/health`, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2) });
@@ -21205,6 +21290,8 @@ var probe = async (origin, named) => {
21205
21290
  var PING_TIMEOUT = "2s";
21206
21291
  var pingArgs = (ip) => ["ping", "--until-direct=false", "--c", "1", "--timeout", PING_TIMEOUT, ip];
21207
21292
  var STATUS_FRESH_MS = 5000;
21293
+ var VIEW_FRESH_MS = 4000;
21294
+ var VIEW_STALE_MAX_MS = 60000;
21208
21295
  var READINESS_EVERY_MS = 60000;
21209
21296
  class Tailnet {
21210
21297
  d;
@@ -21212,6 +21299,9 @@ class Tailnet {
21212
21299
  statusAt = 0;
21213
21300
  status = null;
21214
21301
  timer = null;
21302
+ answered = new Map;
21303
+ lastView = null;
21304
+ viewing = null;
21215
21305
  constructor(d) {
21216
21306
  this.d = d;
21217
21307
  }
@@ -21229,7 +21319,9 @@ class Tailnet {
21229
21319
  return this.readinessNow;
21230
21320
  }
21231
21321
  async refresh() {
21232
- const json = await this.statusJson(true);
21322
+ return this.noteReadiness(await this.statusJson(true));
21323
+ }
21324
+ noteReadiness(json) {
21233
21325
  const next = this.d.ts === null ? {
21234
21326
  state: "not-installed",
21235
21327
  name: null,
@@ -21241,6 +21333,32 @@ class Tailnet {
21241
21333
  this.d.hub.emit("tailscaleChanged");
21242
21334
  return next;
21243
21335
  }
21336
+ async up() {
21337
+ const ts = this.d.ts;
21338
+ if (ts === null)
21339
+ return "Tailscale is not installed here";
21340
+ const r = await ts(["up"]);
21341
+ await this.refresh();
21342
+ return r.ok ? null : lastLine2(r);
21343
+ }
21344
+ async login() {
21345
+ const ts = this.d.ts;
21346
+ if (ts === null)
21347
+ return { why: "Tailscale is not installed here" };
21348
+ fireAndForget("tailscale", ts(["login"]), "tailscale login");
21349
+ const sleep = this.d.sleep ?? ((ms) => Bun.sleep(ms));
21350
+ const now = this.d.now ?? Date.now;
21351
+ const until = now() + LOGIN_URL_WAIT_MS;
21352
+ for (;; ) {
21353
+ const json = await this.statusJson(true);
21354
+ const url = json === null ? null : loginUrl(json);
21355
+ if (url !== null)
21356
+ return { url };
21357
+ if (now() >= until)
21358
+ return { why: "Tailscale did not offer a sign-in link; run `tailscale login` in a terminal" };
21359
+ await sleep(LOGIN_URL_POLL_MS);
21360
+ }
21361
+ }
21244
21362
  statusJson(fresh = false) {
21245
21363
  const ts = this.d.ts;
21246
21364
  if (ts === null)
@@ -21252,26 +21370,50 @@ class Tailnet {
21252
21370
  }
21253
21371
  return this.status;
21254
21372
  }
21255
- async peers() {
21373
+ async listed() {
21256
21374
  const json = await this.statusJson();
21257
- return json === null ? null : tailnetPeers(json);
21375
+ this.noteReadiness(json);
21376
+ return json === null ? null : tailnetListed(json);
21258
21377
  }
21259
- async phones() {
21378
+ async view() {
21379
+ const now = (this.d.now ?? Date.now)();
21380
+ const last = this.lastView;
21381
+ if (last && now - last.at < VIEW_FRESH_MS)
21382
+ return last.view;
21383
+ if (this.viewing === null) {
21384
+ this.viewing = this.makeView().finally(() => {
21385
+ this.viewing = null;
21386
+ });
21387
+ }
21388
+ if (last && now - last.at < VIEW_STALE_MAX_MS) {
21389
+ fireAndForget("tailnet", this.viewing, "tailnet view");
21390
+ return last.view;
21391
+ }
21392
+ return this.viewing;
21393
+ }
21394
+ async makeView() {
21260
21395
  const ts = this.d.ts;
21261
- const peers = await this.peers();
21262
- if (peers === null || ts === null)
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)
21396
+ const listed = await this.listed();
21397
+ if (listed === null || ts === null)
21272
21398
  return null;
21273
21399
  const health = this.d.health ?? probe;
21274
- return Promise.all(peers.filter((p) => !isPhone(p) && p.host !== "").map(async (p) => ({ name: p.name, host: p.host, toyon: await health(`https://${p.host}`, true) })));
21400
+ const now = (this.d.now ?? Date.now)();
21401
+ const devices = await Promise.all(listed.peers.map(async (p) => {
21402
+ const kind = isPhone(p) ? "phone" : "machine";
21403
+ const ask = listed.here;
21404
+ const [reached, got] = await Promise.all([
21405
+ ask && p.ip !== null ? ts(pingArgs(p.ip)).then((r) => r.ok) : false,
21406
+ ask && kind === "machine" && p.host !== "" ? health(`https://${p.host}`, true) : null
21407
+ ]);
21408
+ if (got)
21409
+ this.answered.set(p.host, { machine: got.machine, at: now });
21410
+ const last = kind === "machine" ? this.answered.get(p.host) : undefined;
21411
+ const toyon = got ? { machine: got.machine, answering: true } : last && now - last.at < ANSWERED_RECENTLY_MS ? { machine: last.machine, answering: false } : null;
21412
+ return { name: p.name, host: p.host, kind, reached, toyon };
21413
+ }));
21414
+ const view = { here: listed.here, devices };
21415
+ this.lastView = { view, at: now };
21416
+ return view;
21275
21417
  }
21276
21418
  isToyon(origin) {
21277
21419
  return (this.d.health ?? probe)(origin, false).then((r) => r !== null);
@@ -21279,7 +21421,7 @@ class Tailnet {
21279
21421
  }
21280
21422
 
21281
21423
  // ../daemon/src/design/service.ts
21282
- import { join as join14 } from "path";
21424
+ import { join as join15 } from "path";
21283
21425
 
21284
21426
  // ../daemon/src/design/scan.ts
21285
21427
  var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
@@ -21729,7 +21871,7 @@ class DesignService {
21729
21871
  async function readAll(root, paths) {
21730
21872
  const out = [];
21731
21873
  for (const path of paths) {
21732
- const file = Bun.file(join14(root, path));
21874
+ const file = Bun.file(join15(root, path));
21733
21875
  if (file.size === 0 || file.size > MAX_FILE_BYTES)
21734
21876
  continue;
21735
21877
  try {
@@ -21844,14 +21986,14 @@ function typicalUse(classes) {
21844
21986
  var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
21845
21987
  async function loadProjectTypescript(root) {
21846
21988
  try {
21847
- return await import(join14(root, "node_modules", "typescript", "lib", "typescript.js"));
21989
+ return await import(join15(root, "node_modules", "typescript", "lib", "typescript.js"));
21848
21990
  } catch {
21849
21991
  return null;
21850
21992
  }
21851
21993
  }
21852
21994
 
21853
21995
  // ../daemon/src/drafts/store.ts
21854
- import { existsSync as existsSync18, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
21996
+ import { existsSync as existsSync18, readFileSync as readFileSync13, renameSync as renameSync4, writeFileSync as writeFileSync9 } from "fs";
21855
21997
  var SAVE_DELAY_MS = 1000;
21856
21998
 
21857
21999
  class DraftStore {
@@ -21991,7 +22133,7 @@ function readRecord(file) {
21991
22133
  if (!existsSync18(file))
21992
22134
  return [];
21993
22135
  try {
21994
- const raw = JSON.parse(readFileSync12(file, "utf8"));
22136
+ const raw = JSON.parse(readFileSync13(file, "utf8"));
21995
22137
  return raw && typeof raw === "object" ? Object.entries(raw) : [];
21996
22138
  } catch (e) {
21997
22139
  log.warn("drafts", `skipped an unreadable ${file}`, e);
@@ -22001,8 +22143,8 @@ function readRecord(file) {
22001
22143
  function writeAtomic(file, value) {
22002
22144
  const tmp = `${file}.tmp`;
22003
22145
  try {
22004
- writeFileSync8(tmp, JSON.stringify(value));
22005
- renameSync3(tmp, file);
22146
+ writeFileSync9(tmp, JSON.stringify(value));
22147
+ renameSync4(tmp, file);
22006
22148
  } catch (e) {
22007
22149
  log.error("drafts", `could not write ${file}`, e);
22008
22150
  }
@@ -22251,10 +22393,10 @@ import { basename as basename5, resolve as resolve7 } from "path";
22251
22393
  import { existsSync as existsSync19, statSync as statSync6 } from "fs";
22252
22394
  import { readdir } from "fs/promises";
22253
22395
  import { homedir as homedir8 } from "os";
22254
- import { dirname as dirname6, join as join15 } from "path";
22396
+ import { dirname as dirname6, join as join16 } from "path";
22255
22397
  var MAX_ENTRIES = 40;
22256
22398
  function expandTilde(raw) {
22257
- return raw === "~" || raw.startsWith("~/") ? join15(homedir8(), raw.slice(1)) : raw;
22399
+ return raw === "~" || raw.startsWith("~/") ? join16(homedir8(), raw.slice(1)) : raw;
22258
22400
  }
22259
22401
  function collapseTilde(abs) {
22260
22402
  const home = homedir8();
@@ -22265,7 +22407,7 @@ function holdsNothing(names) {
22265
22407
  }
22266
22408
  async function describeFolder(abs) {
22267
22409
  const path = collapseTilde(abs);
22268
- if (existsSync19(join15(abs, ".git")))
22410
+ if (existsSync19(join16(abs, ".git")))
22269
22411
  return { path, kind: "project" };
22270
22412
  try {
22271
22413
  return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
@@ -22282,7 +22424,7 @@ function describe2(typed, parent) {
22282
22424
  return {
22283
22425
  exists: existsSync19(typed),
22284
22426
  isDir,
22285
- isRepo: isDir && existsSync19(join15(typed, ".git")),
22427
+ isRepo: isDir && existsSync19(join16(typed, ".git")),
22286
22428
  parentExists: existsSync19(parent)
22287
22429
  };
22288
22430
  }
@@ -22307,8 +22449,8 @@ async function browsePath(raw) {
22307
22449
  continue;
22308
22450
  if (!name.toLowerCase().startsWith(prefix))
22309
22451
  continue;
22310
- const abs = join15(dir, name);
22311
- entries.push({ path: collapseTilde(abs), name, isRepo: existsSync19(join15(abs, ".git")) });
22452
+ const abs = join16(dir, name);
22453
+ entries.push({ path: collapseTilde(abs), name, isRepo: existsSync19(join16(abs, ".git")) });
22312
22454
  }
22313
22455
  entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
22314
22456
  return { entries: entries.slice(0, MAX_ENTRIES), target };
@@ -22422,7 +22564,7 @@ function withFetchLock(repoPath, fn) {
22422
22564
  var fileLockKey = (absPath) => `file:${absPath}`;
22423
22565
 
22424
22566
  // ../daemon/src/git/status.ts
22425
- import { join as join16 } from "path";
22567
+ import { join as join17 } from "path";
22426
22568
  async function treeFingerprint(worktreePath) {
22427
22569
  const [head, diff, untracked] = await Promise.all([
22428
22570
  git(worktreePath, "rev-parse", "HEAD"),
@@ -22432,7 +22574,7 @@ async function treeFingerprint(worktreePath) {
22432
22574
  const others = await Promise.all(untracked.out.split(`
22433
22575
  `).filter(Boolean).map(async (p) => {
22434
22576
  try {
22435
- const f = Bun.file(join16(worktreePath, p));
22577
+ const f = Bun.file(join17(worktreePath, p));
22436
22578
  const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
22437
22579
  return `${p}:${body}`;
22438
22580
  } catch {
@@ -22517,7 +22659,7 @@ async function numstat(worktreePath, ...range) {
22517
22659
  }
22518
22660
  async function untrackedLines(worktreePath, file) {
22519
22661
  try {
22520
- const f = Bun.file(join16(worktreePath, file));
22662
+ const f = Bun.file(join17(worktreePath, file));
22521
22663
  if (f.size > 2000000)
22522
22664
  return {};
22523
22665
  const buf = new Uint8Array(await f.arrayBuffer());
@@ -23283,7 +23425,7 @@ function prNumberOf(url) {
23283
23425
 
23284
23426
  // ../daemon/src/preview/errors.ts
23285
23427
  var KEPT = 5;
23286
- var NOTED_MAX = 3;
23428
+ var ROWS_MAX = 3;
23287
23429
  var TWICE_MS = 1000;
23288
23430
 
23289
23431
  class PageErrorService {
@@ -23301,18 +23443,28 @@ class PageErrorService {
23301
23443
  of(worktreeId) {
23302
23444
  let h = this.held.get(worktreeId);
23303
23445
  if (!h) {
23304
- h = { recent: [], writes: false, noted: new Set };
23446
+ h = { recent: [], running: false, noted: new Set, rows: 0 };
23305
23447
  this.held.set(worktreeId, h);
23306
23448
  }
23307
23449
  return h;
23308
23450
  }
23309
23451
  heard(worktreeId, event) {
23310
23452
  const h = this.of(worktreeId);
23311
- if (event.type === "turn-start" || event.type === "user-message" || event.type === "fix-asked") {
23312
- h.writes = false;
23313
- } else if ((event.type === "tool-start" || event.type === "tool-update" && event.kind) && isEditTool({ name: event.name ?? "", kind: event.kind }) && !h.writes) {
23314
- h.writes = true;
23315
- h.noted = new Set;
23453
+ switch (event.type) {
23454
+ case "user-message":
23455
+ case "fix-asked":
23456
+ h.noted = new Set;
23457
+ h.rows = 0;
23458
+ break;
23459
+ case "turn-start":
23460
+ h.running = true;
23461
+ break;
23462
+ case "turn-end":
23463
+ case "agent-error":
23464
+ case "agent-auth-required":
23465
+ h.running = false;
23466
+ this.flush(worktreeId, h, event.ts);
23467
+ break;
23316
23468
  }
23317
23469
  }
23318
23470
  report(worktreeId, message) {
@@ -23328,13 +23480,20 @@ class PageErrorService {
23328
23480
  h.recent.push({ message, ts: now });
23329
23481
  if (h.recent.length > KEPT)
23330
23482
  h.recent.splice(0, h.recent.length - KEPT);
23331
- if (!h.writes || h.noted.has(message) || h.noted.size >= NOTED_MAX)
23483
+ if (!h.running)
23484
+ this.flush(worktreeId, h, now);
23485
+ }
23486
+ flush(worktreeId, h, ts) {
23487
+ const fresh = [...new Set(h.recent.map((e) => e.message))].filter((m) => !h.noted.has(m));
23488
+ if (fresh.length === 0 || h.rows >= ROWS_MAX)
23332
23489
  return;
23333
23490
  const agent = this.d.runtime.agentFor(worktreeId);
23334
23491
  if (!agent)
23335
23492
  return;
23336
- h.noted.add(message);
23337
- agent.note({ type: "page-error", message, ts: now });
23493
+ for (const m of fresh)
23494
+ h.noted.add(m);
23495
+ h.rows++;
23496
+ agent.note({ type: "page-error", errors: fresh, ts });
23338
23497
  }
23339
23498
  loaded(worktreeId) {
23340
23499
  const h = this.held.get(worktreeId);
@@ -23598,9 +23757,9 @@ class AfterLand {
23598
23757
  }
23599
23758
 
23600
23759
  // ../daemon/src/repos/registry.ts
23601
- import { existsSync as existsSync23, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
23760
+ import { existsSync as existsSync23, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
23602
23761
  import { homedir as homedir10 } from "os";
23603
- import { basename as basename7, dirname as dirname8, join as join20 } from "path";
23762
+ import { basename as basename7, dirname as dirname8, join as join21 } from "path";
23604
23763
 
23605
23764
  // ../daemon/src/git/base.ts
23606
23765
  async function resolveBase(repoPath, defaultBranch, route) {
@@ -23611,8 +23770,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
23611
23770
  }
23612
23771
 
23613
23772
  // ../daemon/src/repos/config.ts
23614
- import { closeSync, existsSync as existsSync20, openSync as openSync2, readdirSync as readdirSync6, readFileSync as readFileSync13, readSync, statSync as statSync7 } from "fs";
23615
- import { join as join17 } from "path";
23773
+ import { closeSync, existsSync as existsSync20, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, readSync, statSync as statSync7 } from "fs";
23774
+ import { join as join18 } from "path";
23616
23775
 
23617
23776
  // ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
23618
23777
  function createScanner(text, ignoreTrivia = false) {
@@ -24976,7 +25135,7 @@ function applyEdits(text, edits) {
24976
25135
 
24977
25136
  // ../daemon/src/repos/config.ts
24978
25137
  var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
24979
- var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync20(join17(repoPath, rel)));
25138
+ var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync20(join18(repoPath, rel)));
24980
25139
  var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
24981
25140
  function parseSettings(text) {
24982
25141
  const errors = [];
@@ -25001,7 +25160,7 @@ function readConfigFile(repoPath) {
25001
25160
  for (const rel of files) {
25002
25161
  let raw;
25003
25162
  try {
25004
- raw = parseSettings(readFileSync13(join17(repoPath, rel), "utf8"));
25163
+ raw = parseSettings(readFileSync14(join18(repoPath, rel), "utf8"));
25005
25164
  } catch (e) {
25006
25165
  return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
25007
25166
  }
@@ -25060,12 +25219,12 @@ function configTarget(repoPath, made) {
25060
25219
  }
25061
25220
  function configBody(repoPath, rel, config) {
25062
25221
  const place = PLACES.find((p) => p.local === rel);
25063
- const abs = place && join17(repoPath, place.shared);
25222
+ const abs = place && join18(repoPath, place.shared);
25064
25223
  if (!abs || !existsSync20(abs))
25065
25224
  return config;
25066
25225
  let shared;
25067
25226
  try {
25068
- shared = parseSettings(readFileSync13(abs, "utf8"));
25227
+ shared = parseSettings(readFileSync14(abs, "utf8"));
25069
25228
  } catch {
25070
25229
  return config;
25071
25230
  }
@@ -25078,7 +25237,7 @@ function configText(repoPath, rel, config) {
25078
25237
  let text;
25079
25238
  let was;
25080
25239
  try {
25081
- text = readFileSync13(join17(repoPath, rel), "utf8");
25240
+ text = readFileSync14(join18(repoPath, rel), "utf8");
25082
25241
  was = parseSettings(text);
25083
25242
  } catch {
25084
25243
  return fresh;
@@ -25108,9 +25267,9 @@ function detectConfig(repoPath) {
25108
25267
  return { config: explicit.config, needsSetup: false };
25109
25268
  if (explicit)
25110
25269
  log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
25111
- const pkgPath = join17(repoPath, "package.json");
25270
+ const pkgPath = join18(repoPath, "package.json");
25112
25271
  if (existsSync20(pkgPath)) {
25113
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
25272
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
25114
25273
  const scripts = pkg.scripts ?? {};
25115
25274
  const runner = detectRunner(repoPath, pkg.packageManager);
25116
25275
  const pages = detectPages(repoPath, scripts, runner);
@@ -25131,7 +25290,7 @@ function detectConfig(repoPath) {
25131
25290
  };
25132
25291
  }
25133
25292
  }
25134
- if (existsSync20(join17(repoPath, "start.sh"))) {
25293
+ if (existsSync20(join18(repoPath, "start.sh"))) {
25135
25294
  return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
25136
25295
  }
25137
25296
  const assumed = assumeNothingToRun(repoPath);
@@ -25174,24 +25333,24 @@ function assumeNothingToRun(repoPath) {
25174
25333
  return marker && !servesPage(repoPath) ? marker : undefined;
25175
25334
  }
25176
25335
  function markerIn(repoPath) {
25177
- const named = NO_PAGE_MARKERS.find((f) => existsSync20(join17(repoPath, f)));
25336
+ const named = NO_PAGE_MARKERS.find((f) => existsSync20(join18(repoPath, f)));
25178
25337
  if (named)
25179
25338
  return named;
25180
25339
  let entries;
25181
25340
  try {
25182
- entries = readdirSync6(repoPath);
25341
+ entries = readdirSync7(repoPath);
25183
25342
  } catch {
25184
25343
  return;
25185
25344
  }
25186
25345
  return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
25187
25346
  }
25188
25347
  function servesPage(repoPath) {
25189
- if (WEB_FILES.some((f) => existsSync20(join17(repoPath, f))))
25348
+ if (WEB_FILES.some((f) => existsSync20(join18(repoPath, f))))
25190
25349
  return true;
25191
- if (/^web:/m.test(head(join17(repoPath, "Procfile"))))
25350
+ if (/^web:/m.test(head(join18(repoPath, "Procfile"))))
25192
25351
  return true;
25193
25352
  for (const [files, names] of WEB_DEPENDENCIES) {
25194
- const text = files.map((f) => head(join17(repoPath, f))).join(`
25353
+ const text = files.map((f) => head(join18(repoPath, f))).join(`
25195
25354
  `);
25196
25355
  if (text.trim() && names.some((n) => mentions(text, n)))
25197
25356
  return true;
@@ -25201,14 +25360,14 @@ function servesPage(repoPath) {
25201
25360
  function frontEndBeside(repoPath) {
25202
25361
  let dirs;
25203
25362
  try {
25204
- dirs = readdirSync6(repoPath, { withFileTypes: true });
25363
+ dirs = readdirSync7(repoPath, { withFileTypes: true });
25205
25364
  } catch {
25206
25365
  return false;
25207
25366
  }
25208
25367
  return dirs.some((d) => {
25209
25368
  if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
25210
25369
  return false;
25211
- const pkg = head(join17(repoPath, d.name, "package.json"));
25370
+ const pkg = head(join18(repoPath, d.name, "package.json"));
25212
25371
  if (!pkg)
25213
25372
  return false;
25214
25373
  try {
@@ -25264,11 +25423,11 @@ function isLocalDatabase(url) {
25264
25423
  }
25265
25424
  function detectServices(repoPath) {
25266
25425
  const out = {};
25267
- const compose = COMPOSE_FILES.find((f) => existsSync20(join17(repoPath, f)));
25426
+ const compose = COMPOSE_FILES.find((f) => existsSync20(join18(repoPath, f)));
25268
25427
  if (compose)
25269
25428
  out.compose = compose;
25270
25429
  for (const file of ENV_FILES) {
25271
- const text = head(join17(repoPath, file));
25430
+ const text = head(join18(repoPath, file));
25272
25431
  if (!text)
25273
25432
  continue;
25274
25433
  for (const name of DB_URL_NAMES) {
@@ -25292,7 +25451,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
25292
25451
  return;
25293
25452
  let dir = false;
25294
25453
  try {
25295
- dir = statSync7(join17(repoPath, clean)).isDirectory();
25454
+ dir = statSync7(join18(repoPath, clean)).isDirectory();
25296
25455
  } catch {
25297
25456
  return;
25298
25457
  }
@@ -25303,25 +25462,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
25303
25462
  const t = tokens[i];
25304
25463
  if (t === "cd") {
25305
25464
  const dir = tokens[i + 1];
25306
- if (dir && !dir.startsWith("-") && existsSync20(join17(repoPath, dir)))
25465
+ if (dir && !dir.startsWith("-") && existsSync20(join18(repoPath, dir)))
25307
25466
  add(dir);
25308
25467
  continue;
25309
25468
  }
25310
25469
  if (t.startsWith("-") || t.startsWith("$"))
25311
25470
  continue;
25312
25471
  const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
25313
- if (mod?.[1] && existsSync20(join17(repoPath, mod[1]))) {
25472
+ if (mod?.[1] && existsSync20(join18(repoPath, mod[1]))) {
25314
25473
  add(mod[1]);
25315
25474
  continue;
25316
25475
  }
25317
25476
  const rel = t.replace(/^\.\//, "");
25318
25477
  if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
25319
25478
  continue;
25320
- if (!existsSync20(join17(repoPath, rel)))
25479
+ if (!existsSync20(join18(repoPath, rel)))
25321
25480
  continue;
25322
25481
  add(rel);
25323
25482
  if (depth > 0 && rel.endsWith(".sh")) {
25324
- for (const p of inferPaths(head(join17(repoPath, rel)), repoPath, depth - 1))
25483
+ for (const p of inferPaths(head(join18(repoPath, rel)), repoPath, depth - 1))
25325
25484
  out.add(p);
25326
25485
  }
25327
25486
  }
@@ -25344,31 +25503,31 @@ function procCommand(runner, script, body) {
25344
25503
  return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
25345
25504
  }
25346
25505
  function detectPages(repoPath, scripts, runner) {
25347
- if (!scripts.build || !existsSync20(join17(repoPath, "functions")))
25506
+ if (!scripts.build || !existsSync20(join18(repoPath, "functions")))
25348
25507
  return null;
25349
- const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join17(repoPath, f)).find((f) => existsSync20(f));
25508
+ const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) => existsSync20(f));
25350
25509
  if (!cfg)
25351
25510
  return null;
25352
- const out = readFileSync13(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
25511
+ const out = readFileSync14(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
25353
25512
  if (!out)
25354
25513
  return null;
25355
25514
  return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
25356
25515
  }
25357
25516
  function detectRunner(repoPath, packageManager) {
25358
- if (existsSync20(join17(repoPath, "bun.lock")) || existsSync20(join17(repoPath, "bun.lockb")))
25517
+ if (existsSync20(join18(repoPath, "bun.lock")) || existsSync20(join18(repoPath, "bun.lockb")))
25359
25518
  return "bun";
25360
- if (existsSync20(join17(repoPath, "pnpm-lock.yaml")))
25519
+ if (existsSync20(join18(repoPath, "pnpm-lock.yaml")))
25361
25520
  return "pnpm";
25362
- if (existsSync20(join17(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
25521
+ if (existsSync20(join18(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
25363
25522
  return "yarn";
25364
25523
  return "npm";
25365
25524
  }
25366
25525
 
25367
25526
  // ../daemon/src/repos/create.ts
25368
- import { existsSync as existsSync21, readdirSync as readdirSync7, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
25527
+ import { existsSync as existsSync21, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
25369
25528
  import { appendFile as appendFile2, mkdir as mkdir3, rm as rm2 } from "fs/promises";
25370
25529
  import { homedir as homedir9 } from "os";
25371
- import { dirname as dirname7, join as join18, resolve as resolve8 } from "path";
25530
+ import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
25372
25531
  function isInside(dir, parent) {
25373
25532
  const p = parent.replace(/\/+$/, "");
25374
25533
  return dir === p || dir.startsWith(`${p}/`);
@@ -25379,31 +25538,28 @@ function planProject(opts, home = homedir9()) {
25379
25538
  throw new UserError(nameError);
25380
25539
  const first = firstProjectsFolder(opts.parent, home);
25381
25540
  const parent = first ?? resolveParent(opts.parent);
25382
- const dir = join18(parent, opts.name.trim());
25541
+ const dir = join19(parent, opts.name.trim());
25383
25542
  if (existsSync21(dir))
25384
25543
  throw new UserError(`${dir} already exists`);
25385
25544
  return { parent, dir, makeParent: first !== null };
25386
25545
  }
25387
25546
  function firstProjectsFolder(raw, home) {
25388
25547
  const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
25389
- if (typed !== join18(home, PROJECTS_FOLDER) || existsSync21(typed) || !existsSync21(home))
25548
+ if (typed !== join19(home, PROJECTS_FOLDER) || existsSync21(typed) || !existsSync21(home))
25390
25549
  return null;
25391
- return join18(realpathSync2(home), PROJECTS_FOLDER);
25550
+ return join19(realpathSync2(home), PROJECTS_FOLDER);
25392
25551
  }
25393
25552
  function planInPlace(opts) {
25394
25553
  const name = opts.name.trim();
25395
25554
  if (!name || name === "." || name === ".." || name.includes("/")) {
25396
25555
  throw new UserError(`${opts.name} is not a folder name`);
25397
25556
  }
25398
- const typed = join18(resolveParent(opts.parent), name);
25557
+ const typed = join19(resolveParent(opts.parent), name);
25399
25558
  if (!existsSync21(typed) || !statSync8(typed).isDirectory())
25400
25559
  throw new UserError(`${typed} is not a folder`);
25401
25560
  const dir = realpathSync2(typed);
25402
- if (existsSync21(join18(dir, ".git")))
25561
+ if (existsSync21(join19(dir, ".git")))
25403
25562
  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
25563
  return { parent: dirname7(dir), dir, makeParent: false };
25408
25564
  }
25409
25565
  function resolveParent(raw) {
@@ -25423,7 +25579,7 @@ async function createRepoDir(opts, home = homedir9()) {
25423
25579
  await mkdir3(parent);
25424
25580
  await mkdir3(dir);
25425
25581
  try {
25426
- await initWithEmptyCommit(dir);
25582
+ await initWithFirstCommit(dir);
25427
25583
  } catch (e) {
25428
25584
  await undoCreate(dir);
25429
25585
  throw e;
@@ -25434,7 +25590,7 @@ async function initRepoInPlace(opts) {
25434
25590
  const { dir } = planInPlace(opts);
25435
25591
  await requireGitIdentity(dir);
25436
25592
  try {
25437
- await initWithEmptyCommit(dir);
25593
+ await initWithFirstCommit(dir);
25438
25594
  } catch (e) {
25439
25595
  await undoInit(dir);
25440
25596
  throw e;
@@ -25445,7 +25601,7 @@ var LOCAL_EXCLUDES = [
25445
25601
  ".DS_Store",
25446
25602
  "node_modules/"
25447
25603
  ];
25448
- async function initWithEmptyCommit(dir) {
25604
+ async function initWithFirstCommit(dir) {
25449
25605
  await ok(git(dir, "init", "-q"), "git init");
25450
25606
  const exclude = await git(dir, "rev-parse", "--git-path", "info/exclude");
25451
25607
  if (!exclude.ok)
@@ -25456,7 +25612,8 @@ async function initWithEmptyCommit(dir) {
25456
25612
  ${LOCAL_EXCLUDES.join(`
25457
25613
  `)}
25458
25614
  `);
25459
- await ok(git(dir, "-c", "commit.gpgsign=false", "commit", "--allow-empty", "-qm", "initial commit"), "git commit");
25615
+ await ok(git(dir, "add", "-A"), "git add");
25616
+ await ok(git(dir, "-c", "commit.gpgsign=false", "commit", "--no-verify", "--allow-empty", "-qm", "initial commit"), "git commit");
25460
25617
  }
25461
25618
  async function cloneInto(plan, name, url, opts = {}) {
25462
25619
  if (!url)
@@ -25490,13 +25647,13 @@ async function undoCreate(dir) {
25490
25647
  }
25491
25648
  async function undoInit(dir) {
25492
25649
  try {
25493
- await rm2(join18(dir, ".git"), { recursive: true, force: true });
25650
+ await rm2(join19(dir, ".git"), { recursive: true, force: true });
25494
25651
  } catch (e) {
25495
25652
  log.warn(dir, "could not take back a half-made .git", e);
25496
25653
  }
25497
25654
  }
25498
25655
  async function unmakeProject(dir, made) {
25499
- const target = made === "folder" ? dir : join18(dir, ".git");
25656
+ const target = made === "folder" ? dir : join19(dir, ".git");
25500
25657
  try {
25501
25658
  await rm2(target, { recursive: true, force: true });
25502
25659
  } catch (e) {
@@ -25527,7 +25684,7 @@ async function ok(p, what) {
25527
25684
 
25528
25685
  // ../daemon/src/repos/watcher.ts
25529
25686
  import { existsSync as existsSync22, watch } from "fs";
25530
- import { basename as basename6, join as join19 } from "path";
25687
+ import { basename as basename6, join as join20 } from "path";
25531
25688
  function watchDefaultBranch(repoPath, branch, onMove) {
25532
25689
  const last = { local: null, upstream: null };
25533
25690
  const read = async () => {
@@ -25580,13 +25737,13 @@ function watchDefaultBranch(repoPath, branch, onMove) {
25580
25737
  ]);
25581
25738
  if (stopped)
25582
25739
  return;
25583
- const gitDir = common.ok && common.out ? common.out : join19(repoPath, ".git");
25740
+ const gitDir = common.ok && common.out ? common.out : join20(repoPath, ".git");
25584
25741
  tryWatch(gitDir);
25585
- tryWatch(join19(gitDir, "refs", "heads"));
25742
+ tryWatch(join20(gitDir, "refs", "heads"));
25586
25743
  if (!remote.ok || !remote.out)
25587
25744
  return;
25588
- const remotes = join19(gitDir, "refs", "remotes");
25589
- const mine = join19(remotes, remote.out);
25745
+ const remotes = join20(gitDir, "refs", "remotes");
25746
+ const mine = join20(remotes, remote.out);
25590
25747
  let inner = null;
25591
25748
  let mid = null;
25592
25749
  const armInner = () => {
@@ -25602,7 +25759,7 @@ function watchDefaultBranch(repoPath, branch, onMove) {
25602
25759
  }) : null;
25603
25760
  armInner();
25604
25761
  };
25605
- tryWatch(join19(gitDir, "refs"), (_event, filename) => {
25762
+ tryWatch(join20(gitDir, "refs"), (_event, filename) => {
25606
25763
  if (!filename || filename === "remotes")
25607
25764
  armMid();
25608
25765
  schedule();
@@ -25631,7 +25788,7 @@ function watchWorktreeDir(repoPath, onChange) {
25631
25788
  }, 300);
25632
25789
  };
25633
25790
  const arm = (commonDir) => {
25634
- const dir = join19(commonDir, "worktrees");
25791
+ const dir = join20(commonDir, "worktrees");
25635
25792
  const armInner = () => {
25636
25793
  inner?.close();
25637
25794
  inner = null;
@@ -25674,7 +25831,7 @@ function watchWorktreeDir(repoPath, onChange) {
25674
25831
  function watchConfigFile(repoPath, onChange) {
25675
25832
  const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
25676
25833
  const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename6(rel)));
25677
- const dir = join19(repoPath, CONFIG_DIR);
25834
+ const dir = join20(repoPath, CONFIG_DIR);
25678
25835
  let timer = null;
25679
25836
  let outer = null;
25680
25837
  let inner = null;
@@ -25811,7 +25968,7 @@ class RepoRegistry {
25811
25968
  return `${repo.name} is set up now`;
25812
25969
  if (main.promptedAt || main.lastTurn)
25813
25970
  return `${repo.name} has a chat now`;
25814
- if (existsSync23(join20(repo.path, ".claude")))
25971
+ if (existsSync23(join21(repo.path, ".claude")))
25815
25972
  return `an agent has already run in ${repo.name}`;
25816
25973
  if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
25817
25974
  return `${repo.name} has files in it now`;
@@ -25821,9 +25978,13 @@ class RepoRegistry {
25821
25978
  return null;
25822
25979
  }
25823
25980
  refuseIfManaged(dir) {
25824
- const managed = [...this.d.state.repos, ...this.d.state.worktrees].some((r) => isInside(dir, r.path));
25825
- if (managed)
25981
+ const records = [...this.d.state.repos, ...this.d.state.worktrees];
25982
+ if (records.some((r) => isInside(dir, r.path))) {
25826
25983
  throw new UserError(`${dir} is inside a project Toyon already manages`);
25984
+ }
25985
+ if (records.some((r) => isInside(r.path, dir))) {
25986
+ throw new UserError(`${dir} holds projects Toyon already manages`);
25987
+ }
25827
25988
  }
25828
25989
  get pending() {
25829
25990
  return [...this.imports.values()].map((i) => i.pending).sort((a, b) => a.startedAt - b.startedAt);
@@ -25962,10 +26123,10 @@ class RepoRegistry {
25962
26123
  throw new UserError(current.reason);
25963
26124
  const was = configTarget(repo.path, !!repo.made);
25964
26125
  const rel = configSibling(was, kind);
25965
- const moved = rel !== was && existsSync23(join20(repo.path, was));
26126
+ const moved = rel !== was && existsSync23(join21(repo.path, was));
25966
26127
  if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
25967
26128
  try {
25968
- rmSync4(join20(repo.path, was));
26129
+ rmSync4(join21(repo.path, was));
25969
26130
  } catch (e) {
25970
26131
  log.warn(repoId, `could not remove ${was}`, e);
25971
26132
  }
@@ -25974,10 +26135,10 @@ class RepoRegistry {
25974
26135
  }
25975
26136
  if (isLocalConfigFile(rel))
25976
26137
  await excludeFromGit(repo.path, rel);
25977
- const file = join20(repo.path, rel);
26138
+ const file = join21(repo.path, rel);
25978
26139
  try {
25979
26140
  mkdirSync7(dirname8(file), { recursive: true });
25980
- writeFileSync9(file, configText(repo.path, rel, config));
26141
+ writeFileSync10(file, configText(repo.path, rel, config));
25981
26142
  } catch (e) {
25982
26143
  log.warn(repoId, `could not write ${rel}`, e);
25983
26144
  }
@@ -26078,7 +26239,7 @@ class RepoRegistry {
26078
26239
  }
26079
26240
  placeConfig(repo) {
26080
26241
  const rel = configTarget(repo.path, !!repo.made);
26081
- if (isLocalConfigFile(rel) && existsSync23(join20(repo.path, rel))) {
26242
+ if (isLocalConfigFile(rel) && existsSync23(join21(repo.path, rel))) {
26082
26243
  fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
26083
26244
  }
26084
26245
  if (repo.configFile === rel)
@@ -26170,7 +26331,7 @@ class RepoRegistry {
26170
26331
  }
26171
26332
 
26172
26333
  // ../daemon/src/routes/service.ts
26173
- import { join as join21 } from "path";
26334
+ import { join as join22 } from "path";
26174
26335
 
26175
26336
  // ../daemon/src/routes/fileRouters.ts
26176
26337
  var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
@@ -27370,7 +27531,7 @@ class RouteService {
27370
27531
  return mergeRoutes(fileRoutes(paths, roots), code);
27371
27532
  }
27372
27533
  async cachedRead(root, path, had, kept, decode) {
27373
- const abs = join21(root, path);
27534
+ const abs = join22(root, path);
27374
27535
  const stat = await this.fs.stat(abs);
27375
27536
  if (!stat)
27376
27537
  return null;
@@ -27395,7 +27556,7 @@ class RouteService {
27395
27556
  }
27396
27557
  }
27397
27558
  async hash(root, file) {
27398
- const abs = join21(root, file);
27559
+ const abs = join22(root, file);
27399
27560
  const stat = await this.fs.stat(abs);
27400
27561
  if (!stat)
27401
27562
  return null;
@@ -27657,7 +27818,7 @@ function isAlive(pid) {
27657
27818
  }
27658
27819
 
27659
27820
  // ../daemon/src/runtime/bridge-script.ts
27660
- import { existsSync as existsSync24, readFileSync as readFileSync14, statSync as statSync9 } from "fs";
27821
+ import { existsSync as existsSync24, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
27661
27822
  var LEARNED_MAX = 8;
27662
27823
 
27663
27824
  class BridgeScript {
@@ -27696,7 +27857,7 @@ class BridgeScript {
27696
27857
  this.stamp = stamp;
27697
27858
  const origins = [...this.origins, ...this.learned];
27698
27859
  this.cache = `window.__toyonShellOrigins=${JSON.stringify(origins)};
27699
- ${readFileSync14(this.path, "utf8")}`;
27860
+ ${readFileSync15(this.path, "utf8")}`;
27700
27861
  return this.cache;
27701
27862
  }
27702
27863
  }
@@ -27720,9 +27881,9 @@ import { homedir as homedir12, hostname as hostname2 } from "os";
27720
27881
  import { randomBytes as randomBytes7 } from "crypto";
27721
27882
 
27722
27883
  // ../daemon/src/agent/config.ts
27723
- import { existsSync as existsSync25, readFileSync as readFileSync15 } from "fs";
27884
+ import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
27724
27885
  import { homedir as homedir11 } from "os";
27725
- import { join as join22 } from "path";
27886
+ import { join as join23 } from "path";
27726
27887
  function agentConfigFiles(spec, repoPath, home = homedir11()) {
27727
27888
  const file = (id, label, path) => ({
27728
27889
  id,
@@ -27732,30 +27893,30 @@ function agentConfigFiles(spec, repoPath, home = homedir11()) {
27732
27893
  });
27733
27894
  if (spec.id === "claude") {
27734
27895
  return [
27735
- file("user-config", "user config (MCP servers, projects)", join22(home, ".claude.json")),
27736
- file("user-settings", "user settings (permissions, hooks)", join22(home, ".claude", "settings.json")),
27737
- ...repoPath ? [file("project-mcp", "project MCP servers", join22(repoPath, ".mcp.json"))] : [],
27738
- ...repoPath ? [file("project-settings", "project settings", join22(repoPath, ".claude", "settings.json"))] : []
27896
+ file("user-config", "user config (MCP servers, projects)", join23(home, ".claude.json")),
27897
+ file("user-settings", "user settings (permissions, hooks)", join23(home, ".claude", "settings.json")),
27898
+ ...repoPath ? [file("project-mcp", "project MCP servers", join23(repoPath, ".mcp.json"))] : [],
27899
+ ...repoPath ? [file("project-settings", "project settings", join23(repoPath, ".claude", "settings.json"))] : []
27739
27900
  ];
27740
27901
  }
27741
27902
  if (spec.id === "codex")
27742
- return [file("user-config", "config", join22(home, ".codex", "config.toml"))];
27903
+ return [file("user-config", "config", join23(home, ".codex", "config.toml"))];
27743
27904
  return [];
27744
27905
  }
27745
27906
  function describeAgentConfig(spec, repoPath, home = homedir11()) {
27746
27907
  const files = agentConfigFiles(spec, repoPath, home);
27747
27908
  const servers = [];
27748
27909
  if (spec.id === "claude") {
27749
- const user = readJson(join22(home, ".claude.json"));
27910
+ const user = readJson(join23(home, ".claude.json"));
27750
27911
  servers.push(...serversOf(user?.mcpServers, "user"));
27751
27912
  if (repoPath) {
27752
27913
  const projects = user?.projects;
27753
27914
  const mine = projects?.[repoPath];
27754
27915
  servers.push(...serversOf(mine?.mcpServers, "local"));
27755
- servers.push(...serversOf(readJson(join22(repoPath, ".mcp.json"))?.mcpServers, "project"));
27916
+ servers.push(...serversOf(readJson(join23(repoPath, ".mcp.json"))?.mcpServers, "project"));
27756
27917
  }
27757
27918
  } else if (spec.id === "codex") {
27758
- servers.push(...codexServers(join22(home, ".codex", "config.toml")));
27919
+ servers.push(...codexServers(join23(home, ".codex", "config.toml")));
27759
27920
  }
27760
27921
  return { agent: spec.id, files, servers };
27761
27922
  }
@@ -27763,7 +27924,7 @@ function readJson(path) {
27763
27924
  if (!existsSync25(path))
27764
27925
  return null;
27765
27926
  try {
27766
- const v = JSON.parse(readFileSync15(path, "utf8"));
27927
+ const v = JSON.parse(readFileSync16(path, "utf8"));
27767
27928
  return v && typeof v === "object" && !Array.isArray(v) ? v : null;
27768
27929
  } catch (e) {
27769
27930
  log.warn("agent-config", `${path} is not valid JSON`, e);
@@ -27785,7 +27946,7 @@ function codexServers(path) {
27785
27946
  return [];
27786
27947
  const out = [];
27787
27948
  let current = null;
27788
- for (const raw of readFileSync15(path, "utf8").split(`
27949
+ for (const raw of readFileSync16(path, "utf8").split(`
27789
27950
  `)) {
27790
27951
  const line = raw.trim();
27791
27952
  const header = /^\[mcp_servers\.("?)([^\]"]+)\1\]$/.exec(line);
@@ -28579,6 +28740,9 @@ var handlers = {
28579
28740
  const { result } = await s.worktrees.sync(msg.worktreeId);
28580
28741
  await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
28581
28742
  },
28743
+ "unqueue-sync"(msg, _ctx, s) {
28744
+ s.worktrees.unqueueSync(msg.worktreeId);
28745
+ },
28582
28746
  async "pull-main"(msg, ctx, s) {
28583
28747
  const result = await s.worktrees.pull(msg.worktreeId);
28584
28748
  await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message));
@@ -28954,7 +29118,7 @@ async function dispatch(msg, ctx, s) {
28954
29118
  // ../daemon/src/server/http.ts
28955
29119
  import { existsSync as existsSync26 } from "fs";
28956
29120
  import { hostname } from "os";
28957
- import { join as join23 } from "path";
29121
+ import { join as join24 } from "path";
28958
29122
 
28959
29123
  // ../daemon/src/server/cors.ts
28960
29124
  var ALLOW_HEADERS = "authorization, content-type";
@@ -29006,7 +29170,7 @@ function pairingRoutes(d) {
29006
29170
  const body = state === "let-in" ? { state, token: d.token } : { state };
29007
29171
  return json(body, headers);
29008
29172
  }
29009
- if (!url.pathname.match(/^\/(knocks|knock\/[A-Za-z0-9_-]+\/answer|phones|tailnet|remote|machines)$/))
29173
+ if (!url.pathname.match(/^\/(knocks|knock\/[A-Za-z0-9_-]+\/answer|tailnet|tailscale|remote|machines)$/))
29010
29174
  return;
29011
29175
  if (!d.authed(req))
29012
29176
  return unauthorized();
@@ -29027,14 +29191,18 @@ function pairingRoutes(d) {
29027
29191
  d.knocks.answer(answered, body.letIn);
29028
29192
  return new Response(null, { status: 204 });
29029
29193
  }
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
29194
  if (url.pathname === "/tailnet" && req.method === "GET") {
29035
- const body = await d.tailnet.machines();
29195
+ const body = await d.tailnet.view();
29036
29196
  return json(body);
29037
29197
  }
29198
+ if (url.pathname === "/tailscale" && req.method === "POST") {
29199
+ const body = await req.json().catch(() => ({}));
29200
+ if (body.up === true)
29201
+ return json({ refused: await d.tailnet.up() });
29202
+ if (body.login === true)
29203
+ return json(await d.tailnet.login());
29204
+ return new Response("say up or login", { status: 400 });
29205
+ }
29038
29206
  if (url.pathname === "/remote" && req.method === "POST") {
29039
29207
  const body = await req.json().catch(() => ({}));
29040
29208
  try {
@@ -29285,7 +29453,7 @@ function createFetch(opts) {
29285
29453
  });
29286
29454
  }
29287
29455
  const rel = url.pathname === "/" ? "/index.html" : url.pathname;
29288
- const file = join23(opts.shellDist, rel.replaceAll("..", ""));
29456
+ const file = join24(opts.shellDist, rel.replaceAll("..", ""));
29289
29457
  const hashed = rel.startsWith("/assets/");
29290
29458
  if (existsSync26(file) && Bun.file(file).size > 0) {
29291
29459
  if (rel === "/manifest.json") {
@@ -29306,7 +29474,7 @@ function createFetch(opts) {
29306
29474
  }
29307
29475
  if (hashed)
29308
29476
  return new Response("not found", { status: 404 });
29309
- const index = join23(opts.shellDist, "index.html");
29477
+ const index = join24(opts.shellDist, "index.html");
29310
29478
  if (existsSync26(index))
29311
29479
  return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE2 } });
29312
29480
  return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
@@ -29681,7 +29849,7 @@ function startServer(opts) {
29681
29849
  bootstrap: helloFrame,
29682
29850
  manifestColors: () => {
29683
29851
  const { surface0, surface1 } = s.themes.current().colors;
29684
- return { bar: surface1, ground: surface0 };
29852
+ return { bar: surface0, ground: surface1 };
29685
29853
  },
29686
29854
  open: (path) => s.opens.open(path),
29687
29855
  restart: (now) => s.restarter.request({ now }),
@@ -29799,16 +29967,16 @@ function startServer(opts) {
29799
29967
  }
29800
29968
 
29801
29969
  // ../daemon/src/themes/store.ts
29802
- import { existsSync as existsSync27, readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync10, writeFileSync as writeFileSync10 } from "fs";
29970
+ import { existsSync as existsSync27, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync10, writeFileSync as writeFileSync11 } from "fs";
29803
29971
  import { homedir as homedir13 } from "os";
29804
- import { basename as basename8, dirname as dirname9, join as join24, resolve as resolve9 } from "path";
29972
+ import { basename as basename8, dirname as dirname9, join as join25, resolve as resolve9 } from "path";
29805
29973
  var home = homedir13();
29806
29974
  var defaultExtensionDirs = [
29807
- join24(home, ".vscode", "extensions"),
29808
- join24(home, ".vscode-insiders", "extensions"),
29809
- join24(home, ".vscode-oss", "extensions"),
29810
- join24(home, ".cursor", "extensions"),
29811
- join24(home, ".windsurf", "extensions"),
29975
+ join25(home, ".vscode", "extensions"),
29976
+ join25(home, ".vscode-insiders", "extensions"),
29977
+ join25(home, ".vscode-oss", "extensions"),
29978
+ join25(home, ".cursor", "extensions"),
29979
+ join25(home, ".windsurf", "extensions"),
29812
29980
  "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
29813
29981
  "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
29814
29982
  "/Applications/Cursor.app/Contents/Resources/app/extensions",
@@ -29892,14 +30060,14 @@ class ThemeStore {
29892
30060
  name: json?.name ?? base,
29893
30061
  source: "file"
29894
30062
  });
29895
- writeFileSync10(join24(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
30063
+ writeFileSync11(join25(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
29896
30064
  `);
29897
30065
  this.load();
29898
30066
  return theme;
29899
30067
  }
29900
30068
  }
29901
30069
  function readJsonc(file) {
29902
- return parse6(readFileSync16(file, "utf8"), [], { allowTrailingComma: true });
30070
+ return parse6(readFileSync17(file, "utf8"), [], { allowTrailingComma: true });
29903
30071
  }
29904
30072
  function readVscodeTheme(file, depth = 0) {
29905
30073
  const json = readJsonc(file);
@@ -29927,7 +30095,7 @@ function loadDir(dir) {
29927
30095
  for (const f of readdirSync8(dir)) {
29928
30096
  if (!/\.jsonc?$/i.test(f))
29929
30097
  continue;
29930
- const file = join24(dir, f);
30098
+ const file = join25(dir, f);
29931
30099
  const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
29932
30100
  try {
29933
30101
  const json = readJsonc(file);
@@ -29953,21 +30121,21 @@ function discover(dir) {
29953
30121
  return out;
29954
30122
  }
29955
30123
  for (const name of entries) {
29956
- const extDir = join24(dir, name);
29957
- const pkgFile = join24(extDir, "package.json");
30124
+ const extDir = join25(dir, name);
30125
+ const pkgFile = join25(extDir, "package.json");
29958
30126
  try {
29959
30127
  if (!statSync10(extDir).isDirectory() || !existsSync27(pkgFile))
29960
30128
  continue;
29961
- const pkg = JSON.parse(readFileSync16(pkgFile, "utf8"));
30129
+ const pkg = JSON.parse(readFileSync17(pkgFile, "utf8"));
29962
30130
  const themes = pkg.contributes?.themes;
29963
30131
  if (!Array.isArray(themes))
29964
30132
  continue;
29965
30133
  const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
29966
30134
  let nls = {};
29967
- const nlsFile = join24(extDir, "package.nls.json");
30135
+ const nlsFile = join25(extDir, "package.nls.json");
29968
30136
  if (existsSync27(nlsFile)) {
29969
30137
  try {
29970
- nls = JSON.parse(readFileSync16(nlsFile, "utf8"));
30138
+ nls = JSON.parse(readFileSync17(nlsFile, "utf8"));
29971
30139
  } catch {}
29972
30140
  }
29973
30141
  for (const c of themes) {
@@ -30379,9 +30547,9 @@ class BackendShare {
30379
30547
 
30380
30548
  // ../daemon/src/worktrees/cache.ts
30381
30549
  import { createHash as createHash4 } from "crypto";
30382
- import { existsSync as existsSync28, readFileSync as readFileSync17 } from "fs";
30550
+ import { existsSync as existsSync28, readFileSync as readFileSync18 } from "fs";
30383
30551
  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 join25 } from "path";
30552
+ import { join as join26 } from "path";
30385
30553
 
30386
30554
  // ../daemon/src/core/clone.ts
30387
30555
  import { mkdir as mkdir4, rm as rm3 } from "fs/promises";
@@ -30412,7 +30580,7 @@ var STALE_MS = 30 * 24 * 3600000;
30412
30580
  var TMP = ".tmp-";
30413
30581
  var MANIFEST = "manifest.json";
30414
30582
  function defaultTools(dir) {
30415
- const has = (...files) => files.some((f) => existsSync28(join25(dir, f)));
30583
+ const has = (...files) => files.some((f) => existsSync28(join26(dir, f)));
30416
30584
  const out = [];
30417
30585
  if (has("bun.lock", "bun.lockb"))
30418
30586
  out.push("bun --version");
@@ -30425,7 +30593,7 @@ function defaultTools(dir) {
30425
30593
  return out;
30426
30594
  }
30427
30595
  function withSidecars(root, rel) {
30428
- return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync28(join25(root, n)));
30596
+ return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync28(join26(root, n)));
30429
30597
  }
30430
30598
  var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
30431
30599
  async function listEntries(familyDir) {
@@ -30440,8 +30608,8 @@ async function listEntries(familyDir) {
30440
30608
  const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
30441
30609
  if (!m?.[1] || !m[2])
30442
30610
  continue;
30443
- const dir = join25(familyDir, name);
30444
- if (!existsSync28(join25(dir, MANIFEST)))
30611
+ const dir = join26(familyDir, name);
30612
+ if (!existsSync28(join26(dir, MANIFEST)))
30445
30613
  continue;
30446
30614
  out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
30447
30615
  }
@@ -30471,7 +30639,7 @@ class ArtifactCache {
30471
30639
  try {
30472
30640
  const family = await this.family(wt, repo, policy);
30473
30641
  const base = await this.baseCommit(wt, repo);
30474
- const entries = await listEntries(join25(this.d.paths.cacheDir, repo.id, family.key));
30642
+ const entries = await listEntries(join26(this.d.paths.cacheDir, repo.id, family.key));
30475
30643
  const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
30476
30644
  if (!entry) {
30477
30645
  say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
@@ -30479,14 +30647,14 @@ class ArtifactCache {
30479
30647
  }
30480
30648
  const restored = [];
30481
30649
  for (const rel of policy.paths) {
30482
- if (existsSync28(join25(wt.path, rel)))
30650
+ if (existsSync28(join26(wt.path, rel)))
30483
30651
  continue;
30484
30652
  const names = withSidecars(entry.dir, rel);
30485
30653
  if (names.length === 0)
30486
30654
  continue;
30487
30655
  let whole = true;
30488
30656
  for (const name of names) {
30489
- const r = await (this.d.clone ?? cloneTree)(join25(entry.dir, name), join25(wt.path, name));
30657
+ const r = await (this.d.clone ?? cloneTree)(join26(entry.dir, name), join26(wt.path, name));
30490
30658
  if (r.ok)
30491
30659
  continue;
30492
30660
  say(`cache: could not restore ${name}: ${r.error}`);
@@ -30520,12 +30688,12 @@ class ArtifactCache {
30520
30688
  let tmp;
30521
30689
  let slot;
30522
30690
  try {
30523
- const present = policy.paths.filter((rel) => existsSync28(join25(wt.path, rel)));
30691
+ const present = policy.paths.filter((rel) => existsSync28(join26(wt.path, rel)));
30524
30692
  if (present.length === 0)
30525
30693
  return;
30526
30694
  const family = await this.family(wt, repo, policy);
30527
30695
  const base = await this.baseCommit(wt, repo);
30528
- const familyDir = join25(this.d.paths.cacheDir, repo.id, family.key);
30696
+ const familyDir = join26(this.d.paths.cacheDir, repo.id, family.key);
30529
30697
  const key = `${familyDir}/${base.slice(0, 12)}`;
30530
30698
  const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
30531
30699
  if (kept || this.making.has(key)) {
@@ -30536,21 +30704,21 @@ class ArtifactCache {
30536
30704
  this.making.add(key);
30537
30705
  const at = Date.now();
30538
30706
  const name = entryName(base, at);
30539
- tmp = join25(familyDir, `${TMP}${name}`);
30707
+ tmp = join26(familyDir, `${TMP}${name}`);
30540
30708
  await mkdir5(tmp, { recursive: true });
30541
30709
  let how = "clonefile";
30542
30710
  for (const rel of present) {
30543
30711
  for (const n of withSidecars(wt.path, rel)) {
30544
- const r = await (this.d.clone ?? cloneTree)(join25(wt.path, n), join25(tmp, n));
30712
+ const r = await (this.d.clone ?? cloneTree)(join26(wt.path, n), join26(tmp, n));
30545
30713
  if (!r.ok)
30546
30714
  throw new Error(`could not keep ${n}: ${r.error}`);
30547
30715
  how = r.how;
30548
30716
  }
30549
30717
  }
30550
30718
  const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
30551
- await writeFile3(join25(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
30719
+ await writeFile3(join26(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
30552
30720
  `);
30553
- await rename(tmp, join25(familyDir, name));
30721
+ await rename(tmp, join26(familyDir, name));
30554
30722
  tmp = undefined;
30555
30723
  log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
30556
30724
  await this.prune(familyDir);
@@ -30578,7 +30746,7 @@ class ArtifactCache {
30578
30746
  return;
30579
30747
  }
30580
30748
  for (const repoId of repoIds) {
30581
- const repoDir = join25(root, repoId);
30749
+ const repoDir = join26(root, repoId);
30582
30750
  if (!this.d.state.repo(repoId)) {
30583
30751
  await rm4(repoDir, { recursive: true, force: true });
30584
30752
  continue;
@@ -30590,7 +30758,7 @@ class ArtifactCache {
30590
30758
  continue;
30591
30759
  }
30592
30760
  for (const family of families) {
30593
- const familyDir = join25(repoDir, family);
30761
+ const familyDir = join26(repoDir, family);
30594
30762
  let names;
30595
30763
  try {
30596
30764
  names = await readdir2(familyDir);
@@ -30599,7 +30767,7 @@ class ArtifactCache {
30599
30767
  }
30600
30768
  for (const n of names)
30601
30769
  if (n.startsWith(TMP))
30602
- await rm4(join25(familyDir, n), { recursive: true, force: true });
30770
+ await rm4(join26(familyDir, n), { recursive: true, force: true });
30603
30771
  const entries = await listEntries(familyDir);
30604
30772
  let newest = 0;
30605
30773
  for (const e of entries) {
@@ -30619,7 +30787,7 @@ class ArtifactCache {
30619
30787
  for (const f of policy.key) {
30620
30788
  h.update(`\x00${f}\x00`);
30621
30789
  try {
30622
- h.update(readFileSync17(join25(wt.path, f)));
30790
+ h.update(readFileSync18(join26(wt.path, f)));
30623
30791
  } catch {
30624
30792
  h.update("(absent)");
30625
30793
  }
@@ -30824,7 +30992,7 @@ function lastPageErrors(entries) {
30824
30992
  for (let i = entries.length - 1;i >= 0; i--) {
30825
30993
  const e = entries[i].event;
30826
30994
  if (e.type === "page-error") {
30827
- errors.unshift(e.message);
30995
+ errors.unshift(...e.errors);
30828
30996
  continue;
30829
30997
  }
30830
30998
  if (e.type === "user-message" || e.type === "fix-asked")
@@ -31837,9 +32005,9 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
31837
32005
  }
31838
32006
 
31839
32007
  // ../daemon/src/worktrees/service.ts
31840
- import { cpSync, existsSync as existsSync33, readFileSync as readFileSync19, rmSync as rmSync8 } from "fs";
32008
+ import { cpSync, existsSync as existsSync33, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
31841
32009
  import { rm as rm5 } from "fs/promises";
31842
- import { dirname as dirname12, join as join29 } from "path";
32010
+ import { dirname as dirname12, join as join30 } from "path";
31843
32011
 
31844
32012
  // ../daemon/src/git/archive.ts
31845
32013
  import { rmSync as rmSync5 } from "fs";
@@ -32415,7 +32583,7 @@ function compareUrl(remoteUrl, base, branch) {
32415
32583
 
32416
32584
  // ../daemon/src/git/turn.ts
32417
32585
  import { copyFileSync, existsSync as existsSync30, rmSync as rmSync6 } from "fs";
32418
- import { join as join26 } from "path";
32586
+ import { join as join27 } from "path";
32419
32587
  var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
32420
32588
  var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
32421
32589
  var queue = new Map;
@@ -32433,7 +32601,7 @@ async function treeNow(wtPath, head, dirty) {
32433
32601
  const dir = await git(wtPath, "rev-parse", "--absolute-git-dir");
32434
32602
  if (!dir.ok || !dir.out)
32435
32603
  return null;
32436
- index = join26(dir.out, "toyon-turn-index");
32604
+ index = join27(dir.out, "toyon-turn-index");
32437
32605
  }
32438
32606
  const env = { GIT_INDEX_FILE: index };
32439
32607
  const writeTree = async () => {
@@ -32456,7 +32624,7 @@ async function treeNow(wtPath, head, dirty) {
32456
32624
  }
32457
32625
  synced.delete(wtPath);
32458
32626
  if (!existsSync30(index)) {
32459
- const own = join26(index, "..", "index");
32627
+ const own = join27(index, "..", "index");
32460
32628
  if (existsSync30(own))
32461
32629
  copyFileSync(own, index);
32462
32630
  else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
@@ -32538,8 +32706,8 @@ async function dropTurnRefs(repoPath, worktreeId) {
32538
32706
  }
32539
32707
 
32540
32708
  // ../daemon/src/worktrees/archive.ts
32541
- import { existsSync as existsSync31, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync18, renameSync as renameSync4, rmSync as rmSync7, writeFileSync as writeFileSync11 } from "fs";
32542
- import { basename as basename10, dirname as dirname11, join as join27 } from "path";
32709
+ import { existsSync as existsSync31, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync19, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
32710
+ import { basename as basename10, dirname as dirname11, join as join28 } from "path";
32543
32711
  class WorktreeArchive {
32544
32712
  dir;
32545
32713
  records = new Map;
@@ -32551,7 +32719,7 @@ class WorktreeArchive {
32551
32719
  if (!existsSync31(file))
32552
32720
  continue;
32553
32721
  try {
32554
- this.records.set(id, JSON.parse(readFileSync18(file, "utf8")));
32722
+ this.records.set(id, JSON.parse(readFileSync19(file, "utf8")));
32555
32723
  } catch (e) {
32556
32724
  log.warn("archive", `skipped an unreadable record: ${file}`, e);
32557
32725
  }
@@ -32566,7 +32734,7 @@ class WorktreeArchive {
32566
32734
  move(from.transcript, at.transcript);
32567
32735
  move(from.attachments, at.attachments);
32568
32736
  move(from.plans, at.plans);
32569
- writeFileSync11(at.record, JSON.stringify(rec, null, 2));
32737
+ writeFileSync12(at.record, JSON.stringify(rec, null, 2));
32570
32738
  this.records.set(rec.worktree.id, rec);
32571
32739
  return { transcript: at.transcript, attachments: at.attachments, plans: at.plans };
32572
32740
  }
@@ -32595,7 +32763,7 @@ class WorktreeArchive {
32595
32763
  const rec = this.records.get(id);
32596
32764
  if (!rec)
32597
32765
  return;
32598
- writeFileSync11(this.filesOf(id).record, JSON.stringify(rec, null, 2));
32766
+ writeFileSync12(this.filesOf(id).record, JSON.stringify(rec, null, 2));
32599
32767
  }
32600
32768
  chatFiles(id) {
32601
32769
  if (!this.records.has(id))
@@ -32606,24 +32774,24 @@ class WorktreeArchive {
32606
32774
  planFile(id, rel) {
32607
32775
  if (!this.records.has(id) || !isPlanPath(rel))
32608
32776
  return null;
32609
- const file = join27(this.filesOf(id).plans, basename10(rel));
32777
+ const file = join28(this.filesOf(id).plans, basename10(rel));
32610
32778
  if (!existsSync31(file))
32611
32779
  return null;
32612
32780
  try {
32613
- return readFileSync18(file, "utf8");
32781
+ return readFileSync19(file, "utf8");
32614
32782
  } catch (e) {
32615
32783
  log.warn(id, `could not read its archived plan ${rel}`, e);
32616
32784
  return null;
32617
32785
  }
32618
32786
  }
32619
32787
  filesOf(id) {
32620
- const dir = join27(this.dir, id);
32788
+ const dir = join28(this.dir, id);
32621
32789
  return {
32622
32790
  dir,
32623
- record: join27(dir, "record.json"),
32624
- transcript: join27(dir, "transcript.jsonl"),
32625
- attachments: join27(dir, "attachments"),
32626
- plans: join27(dir, "plans")
32791
+ record: join28(dir, "record.json"),
32792
+ transcript: join28(dir, "transcript.jsonl"),
32793
+ attachments: join28(dir, "attachments"),
32794
+ plans: join28(dir, "plans")
32627
32795
  };
32628
32796
  }
32629
32797
  }
@@ -32650,7 +32818,7 @@ function summarize(r, repoId, transcript) {
32650
32818
  function lastUsage(transcript) {
32651
32819
  if (!existsSync31(transcript))
32652
32820
  return null;
32653
- const lines = readFileSync18(transcript, "utf8").split(`
32821
+ const lines = readFileSync19(transcript, "utf8").split(`
32654
32822
  `);
32655
32823
  for (let i = lines.length - 1;i >= 0; i--) {
32656
32824
  if (!lines[i]?.includes('"usage"'))
@@ -32666,7 +32834,7 @@ function lastUsage(transcript) {
32666
32834
  function firstPrompt(transcript) {
32667
32835
  if (!existsSync31(transcript))
32668
32836
  return;
32669
- for (const line of readFileSync18(transcript, "utf8").split(`
32837
+ for (const line of readFileSync19(transcript, "utf8").split(`
32670
32838
  `)) {
32671
32839
  if (!line.includes('"user-message"'))
32672
32840
  continue;
@@ -32683,7 +32851,7 @@ function move(from, to) {
32683
32851
  return;
32684
32852
  mkdirSync8(dirname11(to), { recursive: true });
32685
32853
  rmSync7(to, { recursive: true, force: true });
32686
- renameSync4(from, to);
32854
+ renameSync5(from, to);
32687
32855
  }
32688
32856
 
32689
32857
  // ../daemon/src/worktrees/archivedGit.ts
@@ -32851,7 +33019,7 @@ async function fillLandMarks(jobs, read, width = 8) {
32851
33019
 
32852
33020
  // ../daemon/src/worktrees/spare.ts
32853
33021
  import { existsSync as existsSync32 } from "fs";
32854
- import { basename as basename11, join as join28 } from "path";
33022
+ import { basename as basename11, join as join29 } from "path";
32855
33023
  class SparePool {
32856
33024
  d;
32857
33025
  inflight = new Map;
@@ -32915,11 +33083,11 @@ class SparePool {
32915
33083
  return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
32916
33084
  }
32917
33085
  async record(repoId, repo) {
32918
- const { id, dir } = freeSlot(join28(this.d.paths.worktreesDir, repo.name));
33086
+ const { id, dir } = freeSlot(join29(this.d.paths.worktreesDir, repo.name));
32919
33087
  const wt = {
32920
33088
  id,
32921
33089
  repoId,
32922
- path: join28(this.d.paths.worktreesDir, repo.name, dir),
33090
+ path: join29(this.d.paths.worktreesDir, repo.name, dir),
32923
33091
  branch: repo.defaultBranch,
32924
33092
  kind: "spare",
32925
33093
  phase: "reserved",
@@ -33308,6 +33476,7 @@ class WorktreeService {
33308
33476
  spare;
33309
33477
  runs;
33310
33478
  ops = new Map;
33479
+ syncQueued = new Set;
33311
33480
  trunk;
33312
33481
  archive;
33313
33482
  archiving = new Map;
@@ -33379,6 +33548,8 @@ class WorktreeService {
33379
33548
  if (wt && prompt)
33380
33549
  this.scheduleNaming(wt, prompt, wt.variant);
33381
33550
  }
33551
+ if (turn.end !== "asking")
33552
+ this.syncAfterTurn(worktreeId);
33382
33553
  if (turn.end !== "done" && turn.end !== "stopped")
33383
33554
  return;
33384
33555
  for (const p of d.runtime.get(worktreeId)?.procs?.states() ?? []) {
@@ -33446,8 +33617,8 @@ class WorktreeService {
33446
33617
  if (!await isGitRepo(repo.path)) {
33447
33618
  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
33619
  }
33449
- const { id, dir } = freeSlot(join29(this.d.paths.worktreesDir, repo.name));
33450
- const wtPath = join29(this.d.paths.worktreesDir, repo.name, dir);
33620
+ const { id, dir } = freeSlot(join30(this.d.paths.worktreesDir, repo.name));
33621
+ const wtPath = join30(this.d.paths.worktreesDir, repo.name, dir);
33451
33622
  const branch = `toyon/${dir}`;
33452
33623
  await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
33453
33624
  const wt = {
@@ -33524,7 +33695,7 @@ class WorktreeService {
33524
33695
  };
33525
33696
  const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
33526
33697
  const restored = (carried.ok ? carried.out.split(`
33527
- `).filter(Boolean) : []).filter((p) => !existsSync33(join29(wt.path, p)));
33698
+ `).filter(Boolean) : []).filter((p) => !existsSync33(join30(wt.path, p)));
33528
33699
  const applied = await git(wt.path, "stash", "apply", sha);
33529
33700
  if (applied.ok) {
33530
33701
  const ref = await entry();
@@ -33564,9 +33735,9 @@ class WorktreeService {
33564
33735
  const branch = kind === "pr" ? `pr/${number}` : ref;
33565
33736
  const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
33566
33737
  let slug = branchSlug(title) || "branch";
33567
- if (existsSync33(join29(this.d.paths.worktreesDir, repo.name, slug)))
33738
+ if (existsSync33(join30(this.d.paths.worktreesDir, repo.name, slug)))
33568
33739
  slug = `${slug}-${shortId().slice(0, 4)}`;
33569
- const wtPath = join29(this.d.paths.worktreesDir, repo.name, slug);
33740
+ const wtPath = join30(this.d.paths.worktreesDir, repo.name, slug);
33570
33741
  await withRepoLock(repo.path, async () => {
33571
33742
  const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
33572
33743
  if (out)
@@ -33768,6 +33939,7 @@ class WorktreeService {
33768
33939
  }
33769
33940
  async takeDown(wt, archive, reason) {
33770
33941
  this.going.add(wt.id);
33942
+ this.syncQueued.delete(wt.id);
33771
33943
  try {
33772
33944
  return await this.removeAndRecord(wt, archive, reason);
33773
33945
  } finally {
@@ -33799,7 +33971,10 @@ class WorktreeService {
33799
33971
  return k;
33800
33972
  });
33801
33973
  const sessionId = this.d.state.session(worktreeId);
33974
+ const placed = repo.promptedAt;
33802
33975
  this.d.state.removeWorktree(worktreeId);
33976
+ if (repo.promptedAt !== placed)
33977
+ this.d.hub.emit("reposChanged");
33803
33978
  releasePort(wt.proxyPort);
33804
33979
  let archived = null;
33805
33980
  await dropTurnRefs(repo.path, worktreeId);
@@ -33824,7 +33999,7 @@ class WorktreeService {
33824
33999
  }
33825
34000
  }
33826
34001
  async keep(repo, wt) {
33827
- const index = join29(this.d.paths.archiveDir, `${wt.id}.index`);
34002
+ const index = join30(this.d.paths.archiveDir, `${wt.id}.index`);
33828
34003
  const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
33829
34004
  if (!kept)
33830
34005
  log.warn(wt.id, "could not keep its git state");
@@ -33861,10 +34036,10 @@ class WorktreeService {
33861
34036
  log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
33862
34037
  }
33863
34038
  plansScratch(worktreeId) {
33864
- return join29(this.d.paths.archiveDir, `${worktreeId}.plans`);
34039
+ return join30(this.d.paths.archiveDir, `${worktreeId}.plans`);
33865
34040
  }
33866
34041
  keepPlans(wt) {
33867
- const from = join29(wt.path, PLANS_DIR);
34042
+ const from = join30(wt.path, PLANS_DIR);
33868
34043
  if (!existsSync33(from))
33869
34044
  return;
33870
34045
  try {
@@ -33931,6 +34106,24 @@ class WorktreeService {
33931
34106
  });
33932
34107
  this.archiveChat(wt, repo, kept, sessionId);
33933
34108
  }
34109
+ fillSentStamps() {
34110
+ let filled = 0;
34111
+ for (const repo of this.d.state.repos) {
34112
+ if (repo.promptedAt !== undefined)
34113
+ continue;
34114
+ let at = 0;
34115
+ for (const rec of this.archive.recordsOf(repo))
34116
+ at = Math.max(at, sentAt(rec.worktree));
34117
+ if (at === 0)
34118
+ continue;
34119
+ repo.promptedAt = at;
34120
+ filled++;
34121
+ }
34122
+ if (filled === 0)
34123
+ return;
34124
+ this.d.state.save();
34125
+ log.info("daemon", `placed ${filled} projects by what their archives were sent`);
34126
+ }
33934
34127
  async fillLandMarks() {
33935
34128
  const jobs = [];
33936
34129
  const rows = new Set;
@@ -33986,7 +34179,7 @@ class WorktreeService {
33986
34179
  }
33987
34180
  archivedAttachment(archiveId, file) {
33988
34181
  const files = this.archive.chatFiles(archiveId);
33989
- return files && isAttachmentFile(file) ? join29(files.attachments, file) : null;
34182
+ return files && isAttachmentFile(file) ? join30(files.attachments, file) : null;
33990
34183
  }
33991
34184
  archivedGit(archiveId) {
33992
34185
  const rec = this.archive.get(archiveId);
@@ -34041,7 +34234,7 @@ class WorktreeService {
34041
34234
  throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
34042
34235
  if (rec.auto)
34043
34236
  log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
34044
- const path = existsSync33(old.path) ? join29(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
34237
+ const path = existsSync33(old.path) ? join30(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
34045
34238
  const branch = await withRepoLock(repo.path, async () => {
34046
34239
  if (!await commitOf(repo.path, archiveRef(old.id))) {
34047
34240
  throw new UserError(`${old.title}'s kept commits are gone from git`);
@@ -34079,7 +34272,7 @@ class WorktreeService {
34079
34272
  ...createdBy ? { createdBy } : {}
34080
34273
  };
34081
34274
  try {
34082
- this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join29(path, PLANS_DIR) });
34275
+ this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join30(path, PLANS_DIR) });
34083
34276
  } catch (e) {
34084
34277
  log.warn(old.id, "could not move its chat back out of the archive", e);
34085
34278
  }
@@ -34221,10 +34414,10 @@ class WorktreeService {
34221
34414
  }
34222
34415
  }
34223
34416
  async mirrorBaseIgnore(repo) {
34224
- const src = join29(repo.path, ".gitignore");
34417
+ const src = join30(repo.path, ".gitignore");
34225
34418
  const tracked = existsSync33(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
34226
34419
  const mirror = existsSync33(src) && !tracked;
34227
- const lines = mirror ? readFileSync19(src, "utf8").split(`
34420
+ const lines = mirror ? readFileSync20(src, "utf8").split(`
34228
34421
  `).filter((l) => l.trim() !== "") : [];
34229
34422
  await excludeBlock(repo.path, BASE_IGNORE, lines);
34230
34423
  }
@@ -34236,8 +34429,8 @@ class WorktreeService {
34236
34429
  await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
34237
34430
  run.finish(0);
34238
34431
  }
34239
- const srcNm = join29(depsSource, "node_modules");
34240
- const dstNm = join29(wt.path, "node_modules");
34432
+ const srcNm = join30(depsSource, "node_modules");
34433
+ const dstNm = join30(wt.path, "node_modules");
34241
34434
  if (existsSync33(srcNm) && !existsSync33(dstNm)) {
34242
34435
  const r = await cloneTree(srcNm, dstNm);
34243
34436
  if (r.ok)
@@ -34246,10 +34439,10 @@ class WorktreeService {
34246
34439
  log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
34247
34440
  }
34248
34441
  for (const f of LOCAL_CONFIG_FILES) {
34249
- const src = join29(depsSource, f);
34250
- if (!existsSync33(src) || existsSync33(join29(wt.path, f)))
34442
+ const src = join30(depsSource, f);
34443
+ if (!existsSync33(src) || existsSync33(join30(wt.path, f)))
34251
34444
  continue;
34252
- const r = await run("cp", [src, join29(wt.path, f)], wt.path);
34445
+ const r = await run("cp", [src, join30(wt.path, f)], wt.path);
34253
34446
  if (r.ok)
34254
34447
  this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
34255
34448
  else
@@ -34502,6 +34695,9 @@ ${wt.landing.body}` : s;
34502
34695
  this.ops.delete(id);
34503
34696
  release?.();
34504
34697
  this.d.hub.emit("worktreesChanged");
34698
+ const status = this.d.runtime.agentFor(id)?.status;
34699
+ if (status !== "working" && status !== "waiting")
34700
+ this.syncAfterTurn(id);
34505
34701
  }
34506
34702
  }
34507
34703
  step(id, name) {
@@ -34775,8 +34971,11 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
34775
34971
  const own = r.wt ? hasOwnBranch(r.wt) : false;
34776
34972
  const carry = !!r.wt;
34777
34973
  const status = this.d.runtime.agentFor(worktreeId)?.status;
34778
- if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0)
34779
- throw new UserError(`${r.name}'s agent is mid-turn with uncommitted changes: sync when it is done`);
34974
+ if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0) {
34975
+ this.syncQueued.add(worktreeId);
34976
+ this.d.hub.emit("worktreesChanged");
34977
+ return { result: { ok: true, message: `${r.name} syncs from ${r.base} once the turn ends` }, base: r.base };
34978
+ }
34780
34979
  const w = r.wt ? this.landWatch(r.wt) : UNWATCHED;
34781
34980
  const result = await this.ship(worktreeId, "sync-main", async () => {
34782
34981
  if (r.wt) {
@@ -34794,6 +34993,31 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
34794
34993
  });
34795
34994
  return { result: r.wt ? this.fixShip(r.wt, result, r.base) : result, base: r.base };
34796
34995
  }
34996
+ unqueueSync(worktreeId) {
34997
+ if (!this.syncQueued.delete(worktreeId))
34998
+ return;
34999
+ this.d.hub.emit("worktreesChanged");
35000
+ }
35001
+ syncAfterTurn(worktreeId) {
35002
+ if (!this.syncQueued.has(worktreeId))
35003
+ return;
35004
+ if (this.ops.has(worktreeId))
35005
+ return;
35006
+ this.syncQueued.delete(worktreeId);
35007
+ const release = this.d.runtime.agentFor(worktreeId)?.hold();
35008
+ this.d.hub.emit("worktreesChanged");
35009
+ fireAndForget(worktreeId, (async () => {
35010
+ try {
35011
+ const { result } = await this.sync(worktreeId);
35012
+ if (!result.ok && !result.asked)
35013
+ this.d.hub.emit("failed", worktreeId, result.message);
35014
+ } catch (e) {
35015
+ this.d.hub.emit("failed", worktreeId, e instanceof Error ? e.message : String(e));
35016
+ } finally {
35017
+ release?.();
35018
+ }
35019
+ })(), "sync after the turn");
35020
+ }
34797
35021
  async pull(worktreeId) {
34798
35022
  const wt = this.d.state.requireWorktree(worktreeId);
34799
35023
  if (!isMain(wt))
@@ -35152,7 +35376,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
35152
35376
  usage: this.usageFor(wt.id),
35153
35377
  transcript: transcriptPathFor(this.d.paths.transcriptsDir, wt.id),
35154
35378
  sessionId: this.d.state.session(wt.id),
35155
- shipping: this.ops.get(wt.id)
35379
+ shipping: this.ops.get(wt.id),
35380
+ syncQueued: this.syncQueued.has(wt.id) || undefined
35156
35381
  };
35157
35382
  });
35158
35383
  }
@@ -35279,16 +35504,17 @@ if (pruned > 0)
35279
35504
  var managed = await loadManaged();
35280
35505
  var paths = makePaths();
35281
35506
  ensureDirs(paths);
35507
+ forwardTranscripts(paths.transcriptsDir);
35282
35508
  var token = loadOrCreateToken(paths);
35283
35509
  var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
35284
- var helperApp = join30(paths.home, DAEMON_FILES.helper);
35510
+ var helperApp = join31(paths.home, DAEMON_FILES.helper);
35285
35511
  var helper = helperWanted({ platform: process.platform, cloud: cloud.enabled, port, home: process.env.TOYON_HOME }) ? new Helper({
35286
35512
  dir: helperApp,
35287
35513
  stub: HELPER_STUB,
35288
35514
  bun: process.execPath,
35289
35515
  cli: CLI_ENTRY,
35290
- log: join30(paths.home, DAEMON_FILES.launcherLog),
35291
- icon: join30(SHELL_DIST, "icon.svg"),
35516
+ log: join31(paths.home, DAEMON_FILES.launcherLog),
35517
+ icon: join31(SHELL_DIST, "icon.svg"),
35292
35518
  version: package_default.version
35293
35519
  }) : null;
35294
35520
  if (helper) {
@@ -35505,6 +35731,7 @@ var keepAwake = new KeepAwake({
35505
35731
  answerable: () => setting.get() !== null
35506
35732
  });
35507
35733
  await worktrees.fillLandMarks();
35734
+ worktrees.fillSentStamps();
35508
35735
  var {
35509
35736
  server,
35510
35737
  branded,
@@ -35581,7 +35808,7 @@ hub.on("remoteChanged", () => {
35581
35808
  fireAndForget("remote", tailnet.refresh(), "ask Tailscale");
35582
35809
  });
35583
35810
  fireAndForget("remote", tailnet.start(), "ask Tailscale");
35584
- writeFileSync12(paths.pidFile, `${process.pid}
35811
+ writeFileSync13(paths.pidFile, `${process.pid}
35585
35812
  `);
35586
35813
  var stopLagSampler = startLagSampler();
35587
35814
  await runtime.reclaimOrphans();