toyon 0.12.0 → 0.14.0

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