toyon 0.7.0 → 0.8.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 +4 -1
  4. package/dist/daemon.js +677 -386
  5. package/dist/shell/assets/{Editor-Duhn2F88.js → Editor-CFH2QbSg.js} +3 -3
  6. package/dist/shell/assets/XTerm-C3aMkE_w.js +58 -0
  7. package/dist/shell/assets/{cssMode-CaBWxaDj.js → cssMode-dc6AdHsZ.js} +1 -1
  8. package/dist/shell/assets/{freemarker2-Qj5oMLk2.js → freemarker2-CxtmNsLB.js} +1 -1
  9. package/dist/shell/assets/{grammar-YjPoOd1d.js → grammar-B-X0FPRk.js} +1 -1
  10. package/dist/shell/assets/{handlebars-N_jFNLBH.js → handlebars-DVzmQ0KM.js} +1 -1
  11. package/dist/shell/assets/{html-CuTufuXa.js → html-CqqXL9ke.js} +1 -1
  12. package/dist/shell/assets/{htmlMode-DzHlP1s-.js → htmlMode-Bg-Em0rL.js} +1 -1
  13. package/dist/shell/assets/index-BskWflhx.css +1 -0
  14. package/dist/shell/assets/index-C8QP4sgP.js +255 -0
  15. package/dist/shell/assets/{javascript-DBXFKiXL.js → javascript-W-C4G7BB.js} +1 -1
  16. package/dist/shell/assets/{jsonMode-DVynVZZ-.js → jsonMode-De4ajMpV.js} +1 -1
  17. package/dist/shell/assets/{liquid-CPflmua8.js → liquid-BSUg_7LQ.js} +1 -1
  18. package/dist/shell/assets/{lspLanguageFeatures-Bw23HdhQ.js → lspLanguageFeatures-BImXYajo.js} +1 -1
  19. package/dist/shell/assets/{mdx-BIo7iUIS.js → mdx-B9A7RorP.js} +1 -1
  20. package/dist/shell/assets/{python-w7xWVunv.js → python-BD9sq3DF.js} +1 -1
  21. package/dist/shell/assets/{razor-C_h6ql0l.js → razor-DGQVRh5-.js} +1 -1
  22. package/dist/shell/assets/{tsMode-BD-oIhVs.js → tsMode-DudQlgN_.js} +1 -1
  23. package/dist/shell/assets/{xml-BDJjSxEs.js → xml-BI6-GyBn.js} +1 -1
  24. package/dist/shell/assets/{yaml-BXfVW0R6.js → yaml-BEUvyYtP.js} +1 -1
  25. package/dist/shell/index.html +2 -2
  26. package/package.json +1 -1
  27. package/dist/shell/assets/XTerm-CF8bkkwi.js +0 -40
  28. package/dist/shell/assets/index-BWSk_4yc.js +0 -250
  29. package/dist/shell/assets/index-C01XmhdL.css +0 -1
package/dist/daemon.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // ../daemon/src/index.ts
3
3
  import { rmSync as rmSync6, writeFileSync as writeFileSync10 } from "fs";
4
- import { dirname as dirname11 } from "path";
4
+ import { dirname as dirname12 } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
 
7
7
  // ../shared/src/agent-tools.ts
@@ -6469,7 +6469,7 @@ var PAIR_TTL_MS = 120000;
6469
6469
  var pairLink = (host, code) => `https://${host}/#pair=${code}`;
6470
6470
 
6471
6471
  // ../shared/src/protocol/limits.ts
6472
- var PROTOCOL_VERSION = 80;
6472
+ var PROTOCOL_VERSION = 82;
6473
6473
  var FILE_MAX_CHARS = 5000000;
6474
6474
  var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
6475
6475
  var IMAGE_MAX_EDGE = 2576;
@@ -6542,6 +6542,7 @@ var elementTraitsSchema = object({
6542
6542
  // ../shared/src/protocol/events.ts
6543
6543
  var SHELL_TOOL = "shell";
6544
6544
  var CHECK_TOOL = "check";
6545
+ var TOOL_SEARCH = "ToolSearch";
6545
6546
 
6546
6547
  // ../shared/src/protocol/ws.ts
6547
6548
  var id = string2().min(1).max(200);
@@ -6793,7 +6794,7 @@ var clientMsgSchema = discriminatedUnion("t", [
6793
6794
  object({ t: literal("search"), worktreeId: id, query: string2().max(500) }),
6794
6795
  object({ t: literal("find-element"), worktreeId: id, seq, element: elementTraitsSchema }),
6795
6796
  object({ t: literal("design-scan"), worktreeId: id }),
6796
- object({ t: literal("discard-file"), worktreeId: id, path: relPath }),
6797
+ object({ t: literal("discard-files"), worktreeId: id, paths: array(relPath).min(1).max(500) }),
6797
6798
  object({ t: literal("judge"), worktreeId: id, note: string2().max(500).optional() }),
6798
6799
  object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
6799
6800
  object({ t: literal("stop-agent"), worktreeId: id }),
@@ -6905,6 +6906,18 @@ function parseClientMsg(raw) {
6905
6906
  return { ok: false, reason: issueReason(r.error, "invalid message") };
6906
6907
  }
6907
6908
 
6909
+ // ../shared/src/query.ts
6910
+ var TERM = /["\u201C\u201D]([^"\u201C\u201D]*)["\u201C\u201D]?|(\S+)/g;
6911
+ function queryTerms(query) {
6912
+ const out = new Set;
6913
+ for (const m of query.matchAll(TERM)) {
6914
+ const term = (m[1] ?? m[2] ?? "").replace(/\s+/g, " ").trim().toLowerCase();
6915
+ if (term)
6916
+ out.add(term);
6917
+ }
6918
+ return [...out];
6919
+ }
6920
+
6908
6921
  // ../shared/src/restart.ts
6909
6922
  var RESTART_NOW = "now";
6910
6923
 
@@ -7869,6 +7882,15 @@ function installCommand(method, version) {
7869
7882
  return ["bun", "add", "-g", `toyon@${version}`];
7870
7883
  return null;
7871
7884
  }
7885
+ function registryHost(registry) {
7886
+ if (registry === null || registry === "")
7887
+ return "the npm registry";
7888
+ try {
7889
+ return new URL(registry).host;
7890
+ } catch {
7891
+ return registry;
7892
+ }
7893
+ }
7872
7894
  function newer(a, b) {
7873
7895
  const pa = parseVersion(a);
7874
7896
  const pb = parseVersion(b);
@@ -8141,7 +8163,7 @@ function safeUser() {
8141
8163
  // ../daemon/package.json
8142
8164
  var package_default = {
8143
8165
  name: "@toyon/daemon",
8144
- version: "0.7.0",
8166
+ version: "0.8.0",
8145
8167
  private: true,
8146
8168
  type: "module",
8147
8169
  main: "src/index.ts",
@@ -13424,7 +13446,7 @@ var BUILTIN_AGENTS = [
13424
13446
  name: "Claude Code",
13425
13447
  short: "Claude",
13426
13448
  builtin: true,
13427
- run: { kind: "npm-bin", pkg: "@agentclientprotocol/claude-agent-acp", version: "0.75.1", bin: "claude-agent-acp" },
13449
+ run: { kind: "npm-bin", pkg: "@agentclientprotocol/claude-agent-acp", version: "0.84.0", bin: "claude-agent-acp" },
13428
13450
  ...cloud.enabled ? { env: { NO_BROWSER: "1" } } : {},
13429
13451
  terminalLogins: {
13430
13452
  "claude-login": { args: ["--cli", "auth", "login", "--claudeai"], env: { NO_BROWSER: "1" }, plan: true }
@@ -13443,7 +13465,7 @@ var BUILTIN_AGENTS = [
13443
13465
  id: "codex",
13444
13466
  name: "Codex",
13445
13467
  builtin: true,
13446
- run: { kind: "npm-bin", pkg: "@agentclientprotocol/codex-acp", version: "1.10.0", bin: "codex-acp" },
13468
+ run: { kind: "npm-bin", pkg: "@agentclientprotocol/codex-acp", version: "2.0.1", bin: "codex-acp" },
13447
13469
  env: {
13448
13470
  INITIAL_AGENT_MODE: "agent",
13449
13471
  CODEX_CONFIG: JSON.stringify({ features: { default_mode_request_user_input: true } }),
@@ -13460,7 +13482,7 @@ var BUILTIN_AGENTS = [
13460
13482
  id: "opencode",
13461
13483
  name: "OpenCode",
13462
13484
  builtin: true,
13463
- run: { kind: "npm-native", pkg: "opencode", version: "1.18.30", bin: "bin/opencode", args: ["acp"] },
13485
+ run: { kind: "npm-native", pkg: "opencode", version: "1.18.33", bin: "bin/opencode", args: ["acp"] },
13464
13486
  onDemand: true,
13465
13487
  confinement: "toyon-sandbox",
13466
13488
  stateDirs: [".local/share/opencode", ".local/state/opencode", ".cache/opencode"],
@@ -13725,6 +13747,29 @@ function modeAfterPlan(optionName) {
13725
13747
  return /manual/i.test(optionName) ? "ask" : "auto";
13726
13748
  }
13727
13749
 
13750
+ // ../daemon/src/agent/output.ts
13751
+ var OUTPUT_CAP = 200000;
13752
+ function formatOutput(text, exit, truncated, ceiling) {
13753
+ const body = text.replace(/\n+$/, "");
13754
+ const notes = [];
13755
+ if (truncated)
13756
+ notes.push(`output cut at ${Math.round(OUTPUT_CAP / 1000)} KB`);
13757
+ if (exit === "timeout")
13758
+ notes.push(ceiling ? `gave up after ${describeDuration(ceiling)}` : "gave up at the ceiling");
13759
+ else if (typeof exit === "string")
13760
+ notes.push(`killed (${exit})`);
13761
+ else if (exit !== 0)
13762
+ notes.push(`exit ${exit}`);
13763
+ const parts = [];
13764
+ if (body.trim())
13765
+ parts.push(`\`\`\`
13766
+ ${body}
13767
+ \`\`\``);
13768
+ parts.push(...notes);
13769
+ return parts.join(`
13770
+ `);
13771
+ }
13772
+
13728
13773
  // ../daemon/src/agent/policy.ts
13729
13774
  import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
13730
13775
  var NON_WRITE_KINDS = new Set(["execute", "read", "search", "fetch", "think"]);
@@ -14087,9 +14132,168 @@ async function cleanUp(ctx, a, sessionId) {
14087
14132
  await ctx.request(methods.agent.session.delete, { sessionId }).catch(quietly("delete"));
14088
14133
  }
14089
14134
 
14135
+ // ../daemon/src/agent/acp/background.ts
14136
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
14137
+ import { open as open2 } from "fs/promises";
14138
+ import { homedir as homedir4 } from "os";
14139
+ import { basename as basename2, dirname as dirname4, join as join7 } from "path";
14140
+ var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
14141
+ function backgroundStart(input, output) {
14142
+ const flag = input && typeof input === "object" ? input.run_in_background : undefined;
14143
+ if (flag !== true)
14144
+ return null;
14145
+ const m = STARTED.exec(output);
14146
+ return m ? { taskId: m[1], file: m[2] } : null;
14147
+ }
14148
+ function claudeConfigDir() {
14149
+ return process.env.CLAUDE_CONFIG_DIR || join7(homedir4(), ".claude");
14150
+ }
14151
+ function sessionLogFor(file, configDir = claudeConfigDir()) {
14152
+ const tasks = dirname4(file);
14153
+ if (basename2(tasks) !== "tasks")
14154
+ return null;
14155
+ const session = dirname4(tasks);
14156
+ const path = join7(configDir, "projects", basename2(dirname4(session)), `${basename2(session)}.jsonl`);
14157
+ return existsSync8(path) ? path : null;
14158
+ }
14159
+ function taskEnd(text, toolId) {
14160
+ const at = text.indexOf(`<tool-use-id>${toolId}</tool-use-id>`);
14161
+ if (at === -1)
14162
+ return null;
14163
+ const tail = text.slice(at, at + 2000);
14164
+ const status = /<status>(\w+)<\/status>/.exec(tail)?.[1] ?? "completed";
14165
+ const exit = /exit code (\d+)/.exec(tail)?.[1];
14166
+ return { status, exit: exit === undefined ? null : Number(exit) };
14167
+ }
14168
+ var POLL_MS = 500;
14169
+ var DEFAULT_BASH_TIMEOUT_MS = 120000;
14170
+ var GRACE_MS = 60000;
14171
+ var READ_CHUNK = 1 << 20;
14172
+ var CARRY = 4000;
14173
+
14174
+ class BackgroundTasks {
14175
+ deps;
14176
+ watched = new Map;
14177
+ constructor(deps) {
14178
+ this.deps = deps;
14179
+ }
14180
+ get size() {
14181
+ return this.watched.size;
14182
+ }
14183
+ start(toolId, start, input) {
14184
+ const logPath = sessionLogFor(start.file, this.deps.configDir);
14185
+ if (!logPath)
14186
+ return false;
14187
+ const timeout = input?.timeout;
14188
+ const ceiling = (typeof timeout === "number" ? timeout : DEFAULT_BASH_TIMEOUT_MS) + (this.deps.graceMs ?? GRACE_MS);
14189
+ const w = {
14190
+ toolId,
14191
+ file: start.file,
14192
+ log: logPath,
14193
+ read: 0,
14194
+ logRead: statSync3(logPath).size,
14195
+ carry: "",
14196
+ decoder: new TextDecoder,
14197
+ text: "",
14198
+ truncated: false,
14199
+ ceiling: Date.now() + ceiling,
14200
+ timer: null
14201
+ };
14202
+ this.watched.set(toolId, w);
14203
+ this.schedule(w);
14204
+ return true;
14205
+ }
14206
+ endAll(reason) {
14207
+ for (const w of [...this.watched.values()]) {
14208
+ fireAndForget("background", this.finish(w, null, reason, true), `ending ${w.toolId}`);
14209
+ }
14210
+ }
14211
+ schedule(w) {
14212
+ w.timer = setTimeout(() => this.poll(w), this.deps.pollMs ?? POLL_MS);
14213
+ w.timer.unref?.();
14214
+ }
14215
+ async poll(w) {
14216
+ w.timer = null;
14217
+ try {
14218
+ await this.drain(w);
14219
+ const fresh = await readSince(w.log, w.logRead);
14220
+ w.logRead += fresh.length;
14221
+ const text = w.carry + fresh.toString("utf8");
14222
+ w.carry = text.slice(-CARRY);
14223
+ const end = fresh.length > 0 ? taskEnd(text, w.toolId) : null;
14224
+ if (end)
14225
+ return this.finish(w, end);
14226
+ if (Date.now() >= w.ceiling)
14227
+ return this.finish(w, null, "no word of its end; the row stops watching");
14228
+ } catch (e) {
14229
+ log.warn("background", `watching ${w.toolId} failed`, e);
14230
+ }
14231
+ if (this.watched.has(w.toolId))
14232
+ this.schedule(w);
14233
+ }
14234
+ async drain(w) {
14235
+ const fresh = await readSince(w.file, w.read);
14236
+ w.read += fresh.length;
14237
+ if (fresh.length === 0 || w.truncated)
14238
+ return;
14239
+ const chunk = w.decoder.decode(fresh, { stream: true });
14240
+ const take = chunk.slice(0, OUTPUT_CAP - w.text.length);
14241
+ w.text += take;
14242
+ if (take.length < chunk.length)
14243
+ w.truncated = true;
14244
+ if (take)
14245
+ this.deps.note({ type: "tool-delta", toolId: w.toolId, text: take });
14246
+ }
14247
+ async finish(w, end, note, killed = false) {
14248
+ if (!this.watched.delete(w.toolId))
14249
+ return;
14250
+ if (w.timer)
14251
+ clearTimeout(w.timer);
14252
+ try {
14253
+ await this.drain(w);
14254
+ } catch (e) {
14255
+ log.warn("background", `reading the last of ${w.toolId} failed`, e);
14256
+ }
14257
+ const exit = end ? end.exit ?? (end.status === "completed" ? 0 : null) : null;
14258
+ const lines = [formatOutput(w.text, exit ?? 0, w.truncated)];
14259
+ if (end && exit === null)
14260
+ lines.push(end.status);
14261
+ if (note)
14262
+ lines.push(note);
14263
+ this.deps.note({
14264
+ type: "tool-end",
14265
+ toolId: w.toolId,
14266
+ output: lines.filter(Boolean).join(`
14267
+ `),
14268
+ isError: killed || end !== null && (exit !== 0 || end.status !== "completed")
14269
+ });
14270
+ this.deps.onEnd?.();
14271
+ }
14272
+ }
14273
+ async function readSince(path, offset) {
14274
+ let fh;
14275
+ try {
14276
+ fh = await open2(path, "r");
14277
+ } catch (e) {
14278
+ if (e.code === "ENOENT")
14279
+ return Buffer.alloc(0);
14280
+ throw e;
14281
+ }
14282
+ try {
14283
+ const { size } = await fh.stat();
14284
+ if (size <= offset)
14285
+ return Buffer.alloc(0);
14286
+ const buf = Buffer.alloc(Math.min(size - offset, READ_CHUNK));
14287
+ const { bytesRead } = await fh.read(buf, 0, buf.length, offset);
14288
+ return buf.subarray(0, bytesRead);
14289
+ } finally {
14290
+ await fh.close();
14291
+ }
14292
+ }
14293
+
14090
14294
  // ../daemon/src/agent/acp/elicit.ts
14091
- var CUSTOM_ANSWER_META = "_askUserQuestionCustomAnswer";
14092
14295
  var CODEX_META = "codex";
14296
+ var CLAUDE_CUSTOM_KEY = /^(question_\d+)_custom$/;
14093
14297
  var OPTION_PREVIEW_META = "_claude/askUserQuestionOption";
14094
14298
  function field(value, key) {
14095
14299
  return value && typeof value === "object" ? value[key] : undefined;
@@ -14100,12 +14304,12 @@ function meta2(value, key) {
14100
14304
  function text(value) {
14101
14305
  return typeof value === "string" && value ? value : undefined;
14102
14306
  }
14103
- function customAnswerFor(prop) {
14104
- const claude = text(field(meta2(prop, CUSTOM_ANSWER_META), "questionId"));
14105
- if (claude)
14106
- return claude;
14307
+ function customAnswerFor(key, prop, keys) {
14107
14308
  const codex = meta2(prop, CODEX_META);
14108
- return field(codex, "isOtherAnswer") === true ? text(field(codex, "questionId")) ?? null : null;
14309
+ if (field(codex, "role") === "user_note")
14310
+ return text(field(codex, "questionId")) ?? null;
14311
+ const owner = CLAUDE_CUSTOM_KEY.exec(key)?.[1];
14312
+ return owner && keys.has(owner) ? owner : null;
14109
14313
  }
14110
14314
  function optionsOf(titled) {
14111
14315
  return titled.map((o) => {
@@ -14148,26 +14352,28 @@ function parseForm(req) {
14148
14352
  if (!props)
14149
14353
  return null;
14150
14354
  const entries = Object.entries(props);
14355
+ const keys = new Set(Object.keys(props));
14151
14356
  const required = new Set(req.requestedSchema.required ?? []);
14152
14357
  const notes = new Map;
14153
14358
  for (const [key, prop] of entries) {
14154
- const owner = customAnswerFor(prop);
14359
+ const owner = customAnswerFor(key, prop, keys);
14155
14360
  if (owner)
14156
14361
  notes.set(owner, { key, label: text(field(prop, "title")) ?? "Other" });
14157
14362
  }
14158
14363
  const questions = [];
14159
14364
  const fields = [];
14160
14365
  for (const [key, prop] of entries) {
14161
- if (customAnswerFor(prop))
14366
+ if (customAnswerFor(key, prop, keys))
14162
14367
  continue;
14163
14368
  const select = selectOf(prop);
14164
14369
  if (!select)
14165
14370
  return null;
14166
14371
  const note = notes.get(key);
14167
- const header = text(field(prop, "title"));
14372
+ const swapped = meta2(prop, CODEX_META) !== undefined;
14373
+ const header = text(field(prop, swapped ? "description" : "title"));
14168
14374
  questions.push({
14169
14375
  id: key,
14170
- text: text(field(prop, "description")) ?? "",
14376
+ text: text(field(prop, swapped ? "title" : "description")) ?? "",
14171
14377
  ...header ? { header } : {},
14172
14378
  options: select.options,
14173
14379
  ...select.multi ? { multi: true } : {},
@@ -14193,8 +14399,7 @@ function toContent(form, answers) {
14193
14399
  const note = answer.note?.trim();
14194
14400
  if (!note || !field.note)
14195
14401
  return;
14196
- const labels = chosen.map((v) => q.options.find((o) => o.value === v)?.label ?? v);
14197
- out[field.note] = labels.length > 0 ? `${labels.join(", ")}: ${note}` : note;
14402
+ out[field.note] = note;
14198
14403
  });
14199
14404
  return out;
14200
14405
  }
@@ -14271,7 +14476,6 @@ var NETWORK_ASK = "SandboxNetworkAccess";
14271
14476
  function isNetworkAsk(update) {
14272
14477
  return update.name === NETWORK_ASK || asRecord(asRecord(update._meta).claudeCode).toolName === NETWORK_ASK;
14273
14478
  }
14274
- var TOOL_SEARCH = "ToolSearch";
14275
14479
  function isToolSearch(update) {
14276
14480
  return update.name === TOOL_SEARCH || asRecord(asRecord(update._meta).claudeCode).toolName === TOOL_SEARCH;
14277
14481
  }
@@ -14319,7 +14523,7 @@ function abandoned(memos, parent) {
14319
14523
  }
14320
14524
  return out;
14321
14525
  }
14322
- function mapUpdate(update, memos, tag, sink) {
14526
+ function mapUpdate(update, memos, tag, sink, background) {
14323
14527
  switch (update.sessionUpdate) {
14324
14528
  case "agent_message_chunk": {
14325
14529
  const { parentToolId } = spawnOf(update._meta);
@@ -14347,6 +14551,7 @@ function mapUpdate(update, memos, tag, sink) {
14347
14551
  const head = heading(update);
14348
14552
  const memo = {
14349
14553
  ...head,
14554
+ input,
14350
14555
  content: update.content ?? [],
14351
14556
  rawOutput: update.rawOutput,
14352
14557
  ended: false,
@@ -14364,7 +14569,7 @@ function mapUpdate(update, memos, tag, sink) {
14364
14569
  ...spawn
14365
14570
  });
14366
14571
  if (update.status === "completed" || update.status === "failed")
14367
- out.push(endOf(update.toolCallId, memo, update.status, sink));
14572
+ out.push(endOf(update.toolCallId, memo, update.status, sink, background));
14368
14573
  return out;
14369
14574
  }
14370
14575
  case "tool_call_update": {
@@ -14377,6 +14582,7 @@ function mapUpdate(update, memos, tag, sink) {
14377
14582
  name: update.name ?? "",
14378
14583
  title: update.title ?? update.name ?? "tool",
14379
14584
  ...update.kind ? { kind: update.kind } : {},
14585
+ input,
14380
14586
  content: [],
14381
14587
  ended: false,
14382
14588
  writing: isWrittenKind(update.kind ?? undefined) && emptyInput(input),
@@ -14407,7 +14613,7 @@ function mapUpdate(update, memos, tag, sink) {
14407
14613
  if (update.kind && update.kind !== memo.kind)
14408
14614
  refined.kind = memo.kind = update.kind;
14409
14615
  if (update.rawInput !== undefined) {
14410
- refined.input = update.rawInput;
14616
+ refined.input = memo.input = update.rawInput;
14411
14617
  if (!emptyInput(update.rawInput))
14412
14618
  memo.writing = false;
14413
14619
  }
@@ -14418,7 +14624,7 @@ function mapUpdate(update, memos, tag, sink) {
14418
14624
  if (update.rawOutput !== undefined)
14419
14625
  memo.rawOutput = update.rawOutput;
14420
14626
  if ((update.status === "completed" || update.status === "failed") && !memo.ended) {
14421
- out.push(endOf(update.toolCallId, memo, update.status, sink));
14627
+ out.push(endOf(update.toolCallId, memo, update.status, sink, background));
14422
14628
  }
14423
14629
  return out;
14424
14630
  }
@@ -14449,13 +14655,19 @@ function textOf(content, tag, what) {
14449
14655
  log.debug(tag, `acp: dropping non-text ${what} block (${content.type})`);
14450
14656
  return null;
14451
14657
  }
14452
- function endOf(toolId, memo, status, sink) {
14658
+ function endOf(toolId, memo, status, sink, background) {
14453
14659
  memo.ended = true;
14660
+ const output = summarizeToolOutput(memo.content, memo.rawOutput);
14661
+ if (status === "completed" && memo.kind === "execute" && background) {
14662
+ const start = backgroundStart(memo.input, output);
14663
+ if (start && background(toolId, start, memo.input))
14664
+ return { type: "tool-update", toolId, background: true };
14665
+ }
14454
14666
  const images = sink ? toolImages(memo.content, sink) : [];
14455
14667
  return {
14456
14668
  type: "tool-end",
14457
14669
  toolId,
14458
- output: summarizeToolOutput(memo.content, memo.rawOutput),
14670
+ output,
14459
14671
  isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
14460
14672
  ...images.length ? { images } : {}
14461
14673
  };
@@ -14566,6 +14778,7 @@ class AcpSession {
14566
14778
  live = null;
14567
14779
  reaper = null;
14568
14780
  own = null;
14781
+ tasks;
14569
14782
  asking = 0;
14570
14783
  asks = new Map;
14571
14784
  log;
@@ -14575,6 +14788,12 @@ class AcpSession {
14575
14788
  this.d = d;
14576
14789
  this.commandList = d.seedCommands?.() ?? [];
14577
14790
  this.log = new Transcript(transcriptPathFor(d.transcriptsDir, d.worktreeId), d.worktreeId);
14791
+ this.tasks = new BackgroundTasks({
14792
+ note: (e) => this.emit(e),
14793
+ onEnd: () => this.maybeArmReaper(),
14794
+ ...d.taskPollMs !== undefined ? { pollMs: d.taskPollMs } : {},
14795
+ ...d.claudeConfigDir !== undefined ? { configDir: d.claudeConfigDir } : {}
14796
+ });
14578
14797
  this.seq = nextNumbers(this.log.entries.map(({ event }) => event.type === "user-message" ? event.attachments : undefined));
14579
14798
  const open = new Set;
14580
14799
  for (const { event } of this.log.entries) {
@@ -14585,6 +14804,20 @@ class AcpSession {
14585
14804
  }
14586
14805
  for (const id of open)
14587
14806
  this.emit({ type: "agent-ask-end", id, outcome: "expired", ts: Date.now() });
14807
+ const held = new Map;
14808
+ for (const { event } of this.log.entries) {
14809
+ if (event.type === "tool-update" && event.background)
14810
+ held.set(event.toolId, "");
14811
+ else if (event.type === "tool-delta" && held.has(event.toolId))
14812
+ held.set(event.toolId, held.get(event.toolId) + event.text);
14813
+ else if (event.type === "tool-end")
14814
+ held.delete(event.toolId);
14815
+ }
14816
+ for (const [toolId, text] of held) {
14817
+ const output = [formatOutput(text, 0, false), "ended with the daemon that was watching it"].filter(Boolean);
14818
+ this.emit({ type: "tool-end", toolId, output: output.join(`
14819
+ `), isError: true });
14820
+ }
14588
14821
  }
14589
14822
  get commands() {
14590
14823
  return this.commandList;
@@ -14983,6 +15216,7 @@ class AcpSession {
14983
15216
  this.conn = null;
14984
15217
  this.live = null;
14985
15218
  this.cancelAsks();
15219
+ this.tasks.endAll("the agent stopped, and the command with it");
14986
15220
  this.endOwn("interrupted");
14987
15221
  }
14988
15222
  });
@@ -15155,7 +15389,7 @@ class AcpSession {
15155
15389
  }
15156
15390
  if (params.update.sessionUpdate === "config_option_update")
15157
15391
  this.absorb(live, params.update.configOptions);
15158
- const events = mapUpdate(params.update, live.tools, this.d.worktreeId, (file, bytes) => fireAndForget(this.d.worktreeId, this.d.attachments.putToolImage(this.d.worktreeId, file, bytes), "tool image"));
15392
+ const events = mapUpdate(params.update, live.tools, this.d.worktreeId, (file, bytes) => fireAndForget(this.d.worktreeId, this.d.attachments.putToolImage(this.d.worktreeId, file, bytes), "tool image"), (toolId, start, input) => this.tasks.start(toolId, start, input));
15159
15393
  if (!this.running)
15160
15394
  this.trackOwn(events);
15161
15395
  for (const ev of events) {
@@ -15296,7 +15530,7 @@ class AcpSession {
15296
15530
  for (const e of events) {
15297
15531
  if (e.type === "tool-start")
15298
15532
  this.own.open.add(e.toolId);
15299
- else if (e.type === "tool-end")
15533
+ else if (e.type === "tool-end" || e.type === "tool-update" && e.background)
15300
15534
  this.own.open.delete(e.toolId);
15301
15535
  }
15302
15536
  if (this.own.settle)
@@ -15318,8 +15552,11 @@ class AcpSession {
15318
15552
  this.syncStatus();
15319
15553
  this.maybeArmReaper();
15320
15554
  }
15555
+ quiet() {
15556
+ return !this.running && !this.own && !this.asking && this.asks.size === 0 && this.tasks.size === 0;
15557
+ }
15321
15558
  maybeArmReaper() {
15322
- if (!this.running && !this.own && !this.asking && this.asks.size === 0 && this.conn && !this.stopped)
15559
+ if (this.quiet() && this.conn && !this.stopped)
15323
15560
  this.armReaper();
15324
15561
  }
15325
15562
  armReaper() {
@@ -15327,7 +15564,7 @@ class AcpSession {
15327
15564
  const idleMs = this.d.idleMs ?? DEFAULT_IDLE_MS;
15328
15565
  const t = setTimeout(() => {
15329
15566
  this.reaper = null;
15330
- if (this.running || this.own || this.asking || this.asks.size > 0 || !this.conn)
15567
+ if (!this.quiet() || !this.conn)
15331
15568
  return;
15332
15569
  fireAndForget(this.d.worktreeId, this.dropConn(`idle for ${Math.round(idleMs / 60000)} min`), "reap agent");
15333
15570
  }, idleMs);
@@ -15341,6 +15578,7 @@ class AcpSession {
15341
15578
  }
15342
15579
  async dropConn(why) {
15343
15580
  this.cancelAsks();
15581
+ this.tasks.endAll("the agent stopped, and the command with it");
15344
15582
  const conn = this.conn;
15345
15583
  this.conn = null;
15346
15584
  this.live = null;
@@ -15413,15 +15651,15 @@ function sameCommands(a, b) {
15413
15651
  }
15414
15652
 
15415
15653
  // ../daemon/src/agent/planDoc.ts
15416
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
15417
- import { join as join7 } from "path";
15418
- var PLANS_DIR = join7(".toyon", "plans");
15654
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
15655
+ import { join as join8 } from "path";
15656
+ var PLANS_DIR = join8(".toyon", "plans");
15419
15657
  var PLAN_NAME = /^(\d+)\.md$/;
15420
15658
  function isPlanPath(rel) {
15421
15659
  return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
15422
15660
  }
15423
15661
  function nextNumber(dir) {
15424
- if (!existsSync8(dir))
15662
+ if (!existsSync9(dir))
15425
15663
  return 1;
15426
15664
  let max = 0;
15427
15665
  for (const name of readdirSync2(dir)) {
@@ -15432,11 +15670,11 @@ function nextNumber(dir) {
15432
15670
  return max + 1;
15433
15671
  }
15434
15672
  async function writePlanDoc(cwd, markdown) {
15435
- const dir = join7(cwd, PLANS_DIR);
15436
- const rel = join7(PLANS_DIR, `${nextNumber(dir)}.md`);
15673
+ const dir = join8(cwd, PLANS_DIR);
15674
+ const rel = join8(PLANS_DIR, `${nextNumber(dir)}.md`);
15437
15675
  try {
15438
15676
  mkdirSync5(dir, { recursive: true });
15439
- writeFileSync4(join7(cwd, rel), markdown.endsWith(`
15677
+ writeFileSync4(join8(cwd, rel), markdown.endsWith(`
15440
15678
  `) ? markdown : `${markdown}
15441
15679
  `);
15442
15680
  } catch (e) {
@@ -15447,8 +15685,8 @@ async function writePlanDoc(cwd, markdown) {
15447
15685
  return rel;
15448
15686
  }
15449
15687
  function planEdited(cwd, rel, proposed) {
15450
- const file = join7(cwd, rel);
15451
- if (!existsSync8(file))
15688
+ const file = join8(cwd, rel);
15689
+ if (!existsSync9(file))
15452
15690
  return false;
15453
15691
  try {
15454
15692
  return readFileSync8(file, "utf8").trim() !== proposed.trim();
@@ -15459,7 +15697,7 @@ function planEdited(cwd, rel, proposed) {
15459
15697
  }
15460
15698
 
15461
15699
  // ../daemon/src/runtime/forward.ts
15462
- var POLL_MS = 100;
15700
+ var POLL_MS2 = 100;
15463
15701
  var HOLD_MS = 60000;
15464
15702
  var PENDING_CAP = 1 << 20;
15465
15703
  function keep(chunk) {
@@ -15547,7 +15785,7 @@ function startForward(opts) {
15547
15785
  return connectUp(conn, t);
15548
15786
  if (!opts.coming() || Date.now() >= deadline)
15549
15787
  return closePair(conn);
15550
- await Bun.sleep(POLL_MS);
15788
+ await Bun.sleep(POLL_MS2);
15551
15789
  }
15552
15790
  };
15553
15791
  const listener = Bun.listen({
@@ -15906,7 +16144,7 @@ function expandEnv(env, vars) {
15906
16144
 
15907
16145
  // ../daemon/src/core/remote.ts
15908
16146
  import { createHmac, timingSafeEqual } from "crypto";
15909
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
16147
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
15910
16148
  function loadRemote(file, env = process.env, managed = MANAGED_DEFAULTS) {
15911
16149
  if (env.TOYON_CLOUD === "1") {
15912
16150
  const host = (env.TOYON_PUBLIC_HOST ?? "").toLowerCase();
@@ -15919,7 +16157,7 @@ function loadRemote(file, env = process.env, managed = MANAGED_DEFAULTS) {
15919
16157
  throw new Error(`TOYON_PREVIEWS: ${why}`);
15920
16158
  return { host, previews, front: "edge" };
15921
16159
  }
15922
- if (!existsSync9(file))
16160
+ if (!existsSync10(file))
15923
16161
  return null;
15924
16162
  if (managed.remote === "off") {
15925
16163
  log.warn("remote", `${file} is ignored: remote access is turned off by your organization's policy`);
@@ -17526,8 +17764,8 @@ class RuntimeRegistry {
17526
17764
 
17527
17765
  // ../daemon/src/worktrees/naming.ts
17528
17766
  import { randomBytes } from "crypto";
17529
- import { existsSync as existsSync10 } from "fs";
17530
- import { join as join8 } from "path";
17767
+ import { existsSync as existsSync11 } from "fs";
17768
+ import { join as join9 } from "path";
17531
17769
  function shortId() {
17532
17770
  return randomBytes(5).toString("hex");
17533
17771
  }
@@ -17535,7 +17773,7 @@ function freeSlot(parent) {
17535
17773
  for (;; ) {
17536
17774
  const id = shortId();
17537
17775
  const dir = `wt-${id.slice(0, 4)}`;
17538
- if (!existsSync10(join8(parent, dir)))
17776
+ if (!existsSync11(join9(parent, dir)))
17539
17777
  return { id, dir };
17540
17778
  }
17541
17779
  }
@@ -17949,20 +18187,20 @@ function makePlanner(agents) {
17949
18187
  }
17950
18188
 
17951
18189
  // ../daemon/src/core/assets.ts
17952
- import { existsSync as existsSync11, readdirSync as readdirSync3, rmSync, statSync as statSync3 } from "fs";
17953
- import { join as join9, resolve as resolve5 } from "path";
18190
+ import { existsSync as existsSync12, readdirSync as readdirSync3, rmSync, statSync as statSync4 } from "fs";
18191
+ import { join as join10, resolve as resolve5 } from "path";
17954
18192
  function locateAssets(here, env = process.env) {
17955
- const packaged = existsSync11(join9(here, "shell", "index.html"));
18193
+ const packaged = existsSync12(join10(here, "shell", "index.html"));
17956
18194
  return {
17957
- shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join9(here, "shell") : join9(here, "../../shell/dist")),
17958
- bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join9(here, "bridge.js") : join9(here, "../../bridge/dist/bridge.js")),
18195
+ shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join10(here, "shell") : join10(here, "../../shell/dist")),
18196
+ bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join10(here, "bridge.js") : join10(here, "../../bridge/dist/bridge.js")),
17959
18197
  sourceRoot: packaged ? null : resolve5(here, "../../.."),
17960
- packageJson: packaged ? join9(here, "..", "package.json") : null
18198
+ packageJson: packaged ? join10(here, "..", "package.json") : null
17961
18199
  };
17962
18200
  }
17963
18201
  var KEEP_MS = 3 * 24 * 60 * 60 * 1000;
17964
18202
  function pruneAssets(shellDist) {
17965
- const dir = join9(shellDist, "assets");
18203
+ const dir = join10(shellDist, "assets");
17966
18204
  let names;
17967
18205
  try {
17968
18206
  names = readdirSync3(dir);
@@ -17971,9 +18209,9 @@ function pruneAssets(shellDist) {
17971
18209
  }
17972
18210
  const found = [];
17973
18211
  for (const name of names) {
17974
- const path = join9(dir, name);
18212
+ const path = join10(dir, name);
17975
18213
  try {
17976
- found.push({ path, mtime: statSync3(path).mtimeMs });
18214
+ found.push({ path, mtime: statSync4(path).mtimeMs });
17977
18215
  } catch {}
17978
18216
  }
17979
18217
  if (found.length === 0)
@@ -17992,8 +18230,8 @@ function pruneAssets(shellDist) {
17992
18230
  }
17993
18231
 
17994
18232
  // ../daemon/src/core/dialog.ts
17995
- import { existsSync as existsSync12 } from "fs";
17996
- import { homedir as homedir4 } from "os";
18233
+ import { existsSync as existsSync13 } from "fs";
18234
+ import { homedir as homedir5 } from "os";
17997
18235
  var PROMPTS = {
17998
18236
  location: "Choose where the new project goes",
17999
18237
  open: "Choose a project folder to open"
@@ -18009,8 +18247,8 @@ function folderDialog() {
18009
18247
  let child = null;
18010
18248
  let cancelled = false;
18011
18249
  const ask = async (start, purpose) => {
18012
- const typed = start === "~" || start.startsWith("~/") ? `${homedir4()}${start.slice(1)}` : start;
18013
- const from = typed.startsWith("/") && existsSync12(typed) ? typed : homedir4();
18250
+ const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
18251
+ const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
18014
18252
  cancelled = false;
18015
18253
  const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
18016
18254
  stdout: "pipe",
@@ -18476,12 +18714,23 @@ class IdleExit {
18476
18714
  // ../daemon/src/core/restart.ts
18477
18715
  import { spawn as spawn3 } from "child_process";
18478
18716
  import { openSync } from "fs";
18717
+ import { homedir as homedir6 } from "os";
18479
18718
  function restartable(env = process.env) {
18480
18719
  if (env.TOYON_WORKTREE !== undefined) {
18481
18720
  return { ok: false, reason: "this daemon runs as a toyon process; restart it from its terminal tab" };
18482
18721
  }
18483
18722
  return { ok: true };
18484
18723
  }
18724
+ async function replacementRuns() {
18725
+ const r = await run(process.execPath, ["--version"], homedir6());
18726
+ if (r.ok && /^\d+\.\d+\.\d+/.test(r.out))
18727
+ return { ok: true };
18728
+ const why = r.err !== "" ? r.err : `exit ${r.exit}`;
18729
+ return {
18730
+ ok: false,
18731
+ reason: `the bun Toyon runs on did not start (${process.execPath}: ${why}); an install may still be writing it`
18732
+ };
18733
+ }
18485
18734
  function respawn(logFile) {
18486
18735
  const logFd = openSync(logFile, "a");
18487
18736
  const child = spawn3(process.execPath, process.argv.slice(1), {
@@ -18508,8 +18757,8 @@ class Restarter {
18508
18757
  d.hub.on("agentStatus", settle);
18509
18758
  d.hub.on("worktreesChanged", settle);
18510
18759
  }
18511
- request(opts = {}) {
18512
- const refused = this.d.refusal();
18760
+ async request(opts = {}) {
18761
+ const refused = await this.d.refusal();
18513
18762
  if (refused)
18514
18763
  return refused;
18515
18764
  this.asked = true;
@@ -18626,14 +18875,14 @@ class SelfWatch {
18626
18875
 
18627
18876
  // ../daemon/src/core/state.ts
18628
18877
  import { randomBytes as randomBytes2 } from "crypto";
18629
- import { existsSync as existsSync13, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
18878
+ import { existsSync as existsSync14, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync5 } from "fs";
18630
18879
  var GROUPS_SAVE_MS = 250;
18631
18880
  var SEEN_WORKTREES = 50;
18632
18881
  var empty = { repos: [], worktrees: [], sessions: {} };
18633
18882
  function loadState(paths) {
18634
18883
  ensureDirs(paths);
18635
18884
  const STATE_FILE = paths.stateFile;
18636
- if (!existsSync13(STATE_FILE))
18885
+ if (!existsSync14(STATE_FILE))
18637
18886
  return structuredClone(empty);
18638
18887
  let raw;
18639
18888
  try {
@@ -18685,7 +18934,7 @@ function saveState(paths, state) {
18685
18934
  function loadOrCreateToken(paths) {
18686
18935
  ensureDirs(paths);
18687
18936
  const TOKEN_FILE = paths.tokenFile;
18688
- if (existsSync13(TOKEN_FILE))
18937
+ if (existsSync14(TOKEN_FILE))
18689
18938
  return readFileSync10(TOKEN_FILE, "utf8").trim();
18690
18939
  const token = randomBytes2(32).toString("hex");
18691
18940
  writeFileSync5(TOKEN_FILE, token, { mode: 384 });
@@ -18901,7 +19150,7 @@ class StateStore {
18901
19150
  }
18902
19151
 
18903
19152
  // ../daemon/src/design/service.ts
18904
- import { join as join10 } from "path";
19153
+ import { join as join11 } from "path";
18905
19154
 
18906
19155
  // ../daemon/src/design/scan.ts
18907
19156
  var COLOR = /^(#|rgba?\(|hsla?\(|oklch\(|oklab\(|lab\(|lch\(|color\(|color-mix\(|light-dark\()/i;
@@ -19351,7 +19600,7 @@ class DesignService {
19351
19600
  async function readAll(root, paths) {
19352
19601
  const out = [];
19353
19602
  for (const path of paths) {
19354
- const file = Bun.file(join10(root, path));
19603
+ const file = Bun.file(join11(root, path));
19355
19604
  if (file.size === 0 || file.size > MAX_FILE_BYTES)
19356
19605
  continue;
19357
19606
  try {
@@ -19466,14 +19715,14 @@ function typicalUse(classes) {
19466
19715
  var normal = (s) => s.replace(/[-_]/g, "").toLowerCase();
19467
19716
  async function loadProjectTypescript(root) {
19468
19717
  try {
19469
- return await import(join10(root, "node_modules", "typescript", "lib", "typescript.js"));
19718
+ return await import(join11(root, "node_modules", "typescript", "lib", "typescript.js"));
19470
19719
  } catch {
19471
19720
  return null;
19472
19721
  }
19473
19722
  }
19474
19723
 
19475
19724
  // ../daemon/src/drafts/store.ts
19476
- import { existsSync as existsSync14, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
19725
+ import { existsSync as existsSync15, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
19477
19726
  var SAVE_DELAY_MS = 1000;
19478
19727
 
19479
19728
  class DraftStore {
@@ -19482,7 +19731,7 @@ class DraftStore {
19482
19731
  saveTimer = null;
19483
19732
  constructor(d) {
19484
19733
  this.d = d;
19485
- if (!existsSync14(d.file))
19734
+ if (!existsSync15(d.file))
19486
19735
  return;
19487
19736
  try {
19488
19737
  const raw = JSON.parse(readFileSync11(d.file, "utf8"));
@@ -19557,7 +19806,6 @@ class DraftStore {
19557
19806
  }
19558
19807
 
19559
19808
  // ../daemon/src/exec/service.ts
19560
- var OUTPUT_CAP = 200000;
19561
19809
  var TIMEOUT_MS = 10 * 60000;
19562
19810
  var DRAIN_GRACE_MS = 500;
19563
19811
  var BACKGROUND_POLL_MS = 250;
@@ -19780,42 +20028,22 @@ class ExecService {
19780
20028
  await Bun.sleep(BACKGROUND_POLL_MS);
19781
20029
  }
19782
20030
  }
19783
- function formatOutput(text, exit, truncated, ceiling) {
19784
- const body = text.replace(/\n+$/, "");
19785
- const notes = [];
19786
- if (truncated)
19787
- notes.push(`output cut at ${Math.round(OUTPUT_CAP / 1000)} KB`);
19788
- if (exit === "timeout")
19789
- notes.push(ceiling ? `gave up after ${describeDuration(ceiling)}` : "gave up at the ceiling");
19790
- else if (typeof exit === "string")
19791
- notes.push(`killed (${exit})`);
19792
- else if (exit !== 0)
19793
- notes.push(`exit ${exit}`);
19794
- const parts = [];
19795
- if (body.trim())
19796
- parts.push(`\`\`\`
19797
- ${body}
19798
- \`\`\``);
19799
- parts.push(...notes);
19800
- return parts.join(`
19801
- `);
19802
- }
19803
20031
 
19804
20032
  // ../daemon/src/files/open.ts
19805
20033
  import { stat as stat2 } from "fs/promises";
19806
- import { basename as basename2, resolve as resolve7 } from "path";
20034
+ import { basename as basename3, resolve as resolve7 } from "path";
19807
20035
 
19808
20036
  // ../daemon/src/repos/browse.ts
19809
- import { existsSync as existsSync15, statSync as statSync4 } from "fs";
20037
+ import { existsSync as existsSync16, statSync as statSync5 } from "fs";
19810
20038
  import { readdir } from "fs/promises";
19811
- import { homedir as homedir5 } from "os";
19812
- import { dirname as dirname4, join as join11 } from "path";
20039
+ import { homedir as homedir7 } from "os";
20040
+ import { dirname as dirname5, join as join12 } from "path";
19813
20041
  var MAX_ENTRIES = 40;
19814
20042
  function expandTilde(raw) {
19815
- return raw === "~" || raw.startsWith("~/") ? join11(homedir5(), raw.slice(1)) : raw;
20043
+ return raw === "~" || raw.startsWith("~/") ? join12(homedir7(), raw.slice(1)) : raw;
19816
20044
  }
19817
20045
  function collapseTilde(abs) {
19818
- const home = homedir5();
20046
+ const home = homedir7();
19819
20047
  return abs === home || abs.startsWith(`${home}/`) ? `~${abs.slice(home.length)}` : abs;
19820
20048
  }
19821
20049
  function holdsNothing(names) {
@@ -19823,7 +20051,7 @@ function holdsNothing(names) {
19823
20051
  }
19824
20052
  async function describeFolder(abs) {
19825
20053
  const path = collapseTilde(abs);
19826
- if (existsSync15(join11(abs, ".git")))
20054
+ if (existsSync16(join12(abs, ".git")))
19827
20055
  return { path, kind: "project" };
19828
20056
  try {
19829
20057
  return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
@@ -19835,13 +20063,13 @@ var NOWHERE = { exists: false, isDir: false, isRepo: false, parentExists: false
19835
20063
  function describe2(typed, parent) {
19836
20064
  let isDir = false;
19837
20065
  try {
19838
- isDir = statSync4(typed).isDirectory();
20066
+ isDir = statSync5(typed).isDirectory();
19839
20067
  } catch {}
19840
20068
  return {
19841
- exists: existsSync15(typed),
20069
+ exists: existsSync16(typed),
19842
20070
  isDir,
19843
- isRepo: isDir && existsSync15(join11(typed, ".git")),
19844
- parentExists: existsSync15(parent)
20071
+ isRepo: isDir && existsSync16(join12(typed, ".git")),
20072
+ parentExists: existsSync16(parent)
19845
20073
  };
19846
20074
  }
19847
20075
  async function browsePath(raw) {
@@ -19849,7 +20077,7 @@ async function browsePath(raw) {
19849
20077
  if (!typed.startsWith("/"))
19850
20078
  return { entries: [], target: NOWHERE };
19851
20079
  const listing = typed.endsWith("/");
19852
- const dir = listing ? typed : dirname4(typed);
20080
+ const dir = listing ? typed : dirname5(typed);
19853
20081
  const prefix = listing ? "" : typed.slice(dir.length).replace(/^\//, "").toLowerCase();
19854
20082
  const target = describe2(listing ? typed.replace(/\/+$/, "") : typed, dir);
19855
20083
  let names;
@@ -19865,8 +20093,8 @@ async function browsePath(raw) {
19865
20093
  continue;
19866
20094
  if (!name.toLowerCase().startsWith(prefix))
19867
20095
  continue;
19868
- const abs = join11(dir, name);
19869
- entries.push({ path: collapseTilde(abs), name, isRepo: existsSync15(join11(abs, ".git")) });
20096
+ const abs = join12(dir, name);
20097
+ entries.push({ path: collapseTilde(abs), name, isRepo: existsSync16(join12(abs, ".git")) });
19870
20098
  }
19871
20099
  entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
19872
20100
  return { entries: entries.slice(0, MAX_ENTRIES), target };
@@ -19977,7 +20205,7 @@ function withRepoLock(repoPath, fn) {
19977
20205
  var fileLockKey = (absPath) => `file:${absPath}`;
19978
20206
 
19979
20207
  // ../daemon/src/git/status.ts
19980
- import { join as join12 } from "path";
20208
+ import { join as join13 } from "path";
19981
20209
  async function treeFingerprint(worktreePath) {
19982
20210
  const [head, diff, untracked] = await Promise.all([
19983
20211
  git(worktreePath, "rev-parse", "HEAD"),
@@ -19987,7 +20215,7 @@ async function treeFingerprint(worktreePath) {
19987
20215
  const others = await Promise.all(untracked.out.split(`
19988
20216
  `).filter(Boolean).map(async (p) => {
19989
20217
  try {
19990
- const f = Bun.file(join12(worktreePath, p));
20218
+ const f = Bun.file(join13(worktreePath, p));
19991
20219
  const body = f.size > 2000000 ? String(f.size) : Bun.hash(await f.arrayBuffer()).toString(36);
19992
20220
  return `${p}:${body}`;
19993
20221
  } catch {
@@ -20038,7 +20266,7 @@ async function numstat(worktreePath, ...range) {
20038
20266
  }
20039
20267
  async function untrackedLines(worktreePath, file) {
20040
20268
  try {
20041
- const f = Bun.file(join12(worktreePath, file));
20269
+ const f = Bun.file(join13(worktreePath, file));
20042
20270
  if (f.size > 2000000)
20043
20271
  return {};
20044
20272
  const buf = new Uint8Array(await f.arrayBuffer());
@@ -20662,7 +20890,7 @@ class OpenService {
20662
20890
  return hit ? { kind: "file", worktreeId: hit.id, path: path.slice(hit.root.length + 1) } : null;
20663
20891
  }
20664
20892
  async grant(path, size, mtimeMs) {
20665
- const name = basename2(path);
20893
+ const name = basename3(path);
20666
20894
  if (this.d.refused.some((r) => within2(path, canonical(resolve7(r))))) {
20667
20895
  throw new UserError(`${name} is inside Toyon's own folder, which is not for editing`);
20668
20896
  }
@@ -20867,17 +21095,28 @@ class AfterLand {
20867
21095
  return;
20868
21096
  }
20869
21097
  this.running.add(repoId);
20870
- const done = this.exec(repo, commands).finally(() => {
20871
- this.running.delete(repoId);
20872
- if (this.again.delete(repoId))
20873
- this.run(repoId);
20874
- });
21098
+ if (this.d.self.building(true))
21099
+ this.d.hub.emit("selfChanged");
21100
+ const done = this.runs(repo, commands).finally(() => this.running.delete(repoId));
20875
21101
  fireAndForget(repo.id, done, "afterLand");
20876
21102
  }
20877
- async exec(repo, commands) {
20878
- await this.d.settled?.();
20879
- if (this.d.self.building(true))
21103
+ async runs(repo, commands) {
21104
+ let failure;
21105
+ let notice;
21106
+ do {
21107
+ await this.d.settled?.();
21108
+ ({ failure, notice } = await this.exec(repo, commands));
21109
+ } while (this.again.delete(repo.id));
21110
+ const onNotice = this.d.self.reports(repo);
21111
+ if (this.d.self.building(false, failure))
20880
21112
  this.d.hub.emit("selfChanged");
21113
+ if (notice !== undefined && !onNotice) {
21114
+ const main = this.mainOf(repo);
21115
+ if (main)
21116
+ this.d.hub.emit("failed", main.id, notice);
21117
+ }
21118
+ }
21119
+ async exec(repo, commands) {
20881
21120
  let failure;
20882
21121
  let notice;
20883
21122
  for (const command of commands) {
@@ -20897,14 +21136,7 @@ class AfterLand {
20897
21136
  log.warn(repo.id, `afterLand stopped: ${command} exited ${code}`);
20898
21137
  break;
20899
21138
  }
20900
- const onNotice = this.d.self.reports(repo);
20901
- if (this.d.self.building(false, failure))
20902
- this.d.hub.emit("selfChanged");
20903
- if (notice !== undefined && !onNotice) {
20904
- const main = this.mainOf(repo);
20905
- if (main)
20906
- this.d.hub.emit("failed", main.id, notice);
20907
- }
21139
+ return { failure, notice };
20908
21140
  }
20909
21141
  tellMain(repo, line, retract) {
20910
21142
  const main = this.mainOf(repo);
@@ -20917,9 +21149,9 @@ class AfterLand {
20917
21149
  }
20918
21150
 
20919
21151
  // ../daemon/src/repos/registry.ts
20920
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
20921
- import { homedir as homedir7 } from "os";
20922
- import { basename as basename4, dirname as dirname6, join as join16 } from "path";
21152
+ import { existsSync as existsSync20, mkdirSync as mkdirSync6, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
21153
+ import { homedir as homedir9 } from "os";
21154
+ import { basename as basename5, dirname as dirname7, join as join17 } from "path";
20923
21155
 
20924
21156
  // ../daemon/src/git/base.ts
20925
21157
  async function resolveBase(repoPath, defaultBranch, route) {
@@ -20930,8 +21162,8 @@ async function resolveBase(repoPath, defaultBranch, route) {
20930
21162
  }
20931
21163
 
20932
21164
  // ../daemon/src/repos/config.ts
20933
- import { closeSync, existsSync as existsSync16, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync12, readSync, statSync as statSync5 } from "fs";
20934
- import { join as join13 } from "path";
21165
+ import { closeSync, existsSync as existsSync17, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync12, readSync, statSync as statSync6 } from "fs";
21166
+ import { join as join14 } from "path";
20935
21167
 
20936
21168
  // ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
20937
21169
  function createScanner(text, ignoreTrivia = false) {
@@ -22295,7 +22527,7 @@ function applyEdits(text, edits) {
22295
22527
 
22296
22528
  // ../daemon/src/repos/config.ts
22297
22529
  var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
22298
- var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync16(join13(repoPath, rel)));
22530
+ var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync17(join14(repoPath, rel)));
22299
22531
  var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
22300
22532
  function parseSettings(text) {
22301
22533
  const errors = [];
@@ -22320,7 +22552,7 @@ function readConfigFile(repoPath) {
22320
22552
  for (const rel of files) {
22321
22553
  let raw;
22322
22554
  try {
22323
- raw = parseSettings(readFileSync12(join13(repoPath, rel), "utf8"));
22555
+ raw = parseSettings(readFileSync12(join14(repoPath, rel), "utf8"));
22324
22556
  } catch (e) {
22325
22557
  return { ok: false, reason: `${rel} is not valid JSON: ${e instanceof Error ? e.message : String(e)}` };
22326
22558
  }
@@ -22379,8 +22611,8 @@ function configTarget(repoPath, made) {
22379
22611
  }
22380
22612
  function configBody(repoPath, rel, config) {
22381
22613
  const place = PLACES.find((p) => p.local === rel);
22382
- const abs = place && join13(repoPath, place.shared);
22383
- if (!abs || !existsSync16(abs))
22614
+ const abs = place && join14(repoPath, place.shared);
22615
+ if (!abs || !existsSync17(abs))
22384
22616
  return config;
22385
22617
  let shared;
22386
22618
  try {
@@ -22397,7 +22629,7 @@ function configText(repoPath, rel, config) {
22397
22629
  let text;
22398
22630
  let was;
22399
22631
  try {
22400
- text = readFileSync12(join13(repoPath, rel), "utf8");
22632
+ text = readFileSync12(join14(repoPath, rel), "utf8");
22401
22633
  was = parseSettings(text);
22402
22634
  } catch {
22403
22635
  return fresh;
@@ -22427,8 +22659,8 @@ function detectConfig(repoPath) {
22427
22659
  return { config: explicit.config, needsSetup: false };
22428
22660
  if (explicit)
22429
22661
  log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
22430
- const pkgPath = join13(repoPath, "package.json");
22431
- if (existsSync16(pkgPath)) {
22662
+ const pkgPath = join14(repoPath, "package.json");
22663
+ if (existsSync17(pkgPath)) {
22432
22664
  const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
22433
22665
  const scripts = pkg.scripts ?? {};
22434
22666
  const runner = detectRunner(repoPath, pkg.packageManager);
@@ -22450,7 +22682,7 @@ function detectConfig(repoPath) {
22450
22682
  };
22451
22683
  }
22452
22684
  }
22453
- if (existsSync16(join13(repoPath, "start.sh"))) {
22685
+ if (existsSync17(join14(repoPath, "start.sh"))) {
22454
22686
  return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
22455
22687
  }
22456
22688
  const assumed = assumeNothingToRun(repoPath);
@@ -22493,7 +22725,7 @@ function assumeNothingToRun(repoPath) {
22493
22725
  return marker && !servesPage(repoPath) ? marker : undefined;
22494
22726
  }
22495
22727
  function markerIn(repoPath) {
22496
- const named = NO_PAGE_MARKERS.find((f) => existsSync16(join13(repoPath, f)));
22728
+ const named = NO_PAGE_MARKERS.find((f) => existsSync17(join14(repoPath, f)));
22497
22729
  if (named)
22498
22730
  return named;
22499
22731
  let entries;
@@ -22505,12 +22737,12 @@ function markerIn(repoPath) {
22505
22737
  return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
22506
22738
  }
22507
22739
  function servesPage(repoPath) {
22508
- if (WEB_FILES.some((f) => existsSync16(join13(repoPath, f))))
22740
+ if (WEB_FILES.some((f) => existsSync17(join14(repoPath, f))))
22509
22741
  return true;
22510
- if (/^web:/m.test(head(join13(repoPath, "Procfile"))))
22742
+ if (/^web:/m.test(head(join14(repoPath, "Procfile"))))
22511
22743
  return true;
22512
22744
  for (const [files, names] of WEB_DEPENDENCIES) {
22513
- const text = files.map((f) => head(join13(repoPath, f))).join(`
22745
+ const text = files.map((f) => head(join14(repoPath, f))).join(`
22514
22746
  `);
22515
22747
  if (text.trim() && names.some((n) => mentions(text, n)))
22516
22748
  return true;
@@ -22527,7 +22759,7 @@ function frontEndBeside(repoPath) {
22527
22759
  return dirs.some((d) => {
22528
22760
  if (!d.isDirectory() || d.name.startsWith(".") || NOT_A_PACKAGE.has(d.name))
22529
22761
  return false;
22530
- const pkg = head(join13(repoPath, d.name, "package.json"));
22762
+ const pkg = head(join14(repoPath, d.name, "package.json"));
22531
22763
  if (!pkg)
22532
22764
  return false;
22533
22765
  try {
@@ -22542,7 +22774,7 @@ function mentions(text, name) {
22542
22774
  return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i").test(text);
22543
22775
  }
22544
22776
  function head(path) {
22545
- if (!existsSync16(path))
22777
+ if (!existsSync17(path))
22546
22778
  return "";
22547
22779
  try {
22548
22780
  const fd = openSync2(path, "r");
@@ -22583,11 +22815,11 @@ function isLocalDatabase(url) {
22583
22815
  }
22584
22816
  function detectServices(repoPath) {
22585
22817
  const out = {};
22586
- const compose = COMPOSE_FILES.find((f) => existsSync16(join13(repoPath, f)));
22818
+ const compose = COMPOSE_FILES.find((f) => existsSync17(join14(repoPath, f)));
22587
22819
  if (compose)
22588
22820
  out.compose = compose;
22589
22821
  for (const file of ENV_FILES) {
22590
- const text = head(join13(repoPath, file));
22822
+ const text = head(join14(repoPath, file));
22591
22823
  if (!text)
22592
22824
  continue;
22593
22825
  for (const name of DB_URL_NAMES) {
@@ -22611,7 +22843,7 @@ function inferPaths(cmd, repoPath, depth = 1) {
22611
22843
  return;
22612
22844
  let dir = false;
22613
22845
  try {
22614
- dir = statSync5(join13(repoPath, clean)).isDirectory();
22846
+ dir = statSync6(join14(repoPath, clean)).isDirectory();
22615
22847
  } catch {
22616
22848
  return;
22617
22849
  }
@@ -22622,25 +22854,25 @@ function inferPaths(cmd, repoPath, depth = 1) {
22622
22854
  const t = tokens[i];
22623
22855
  if (t === "cd") {
22624
22856
  const dir = tokens[i + 1];
22625
- if (dir && !dir.startsWith("-") && existsSync16(join13(repoPath, dir)))
22857
+ if (dir && !dir.startsWith("-") && existsSync17(join14(repoPath, dir)))
22626
22858
  add(dir);
22627
22859
  continue;
22628
22860
  }
22629
22861
  if (t.startsWith("-") || t.startsWith("$"))
22630
22862
  continue;
22631
22863
  const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
22632
- if (mod?.[1] && existsSync16(join13(repoPath, mod[1]))) {
22864
+ if (mod?.[1] && existsSync17(join14(repoPath, mod[1]))) {
22633
22865
  add(mod[1]);
22634
22866
  continue;
22635
22867
  }
22636
22868
  const rel = t.replace(/^\.\//, "");
22637
22869
  if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
22638
22870
  continue;
22639
- if (!existsSync16(join13(repoPath, rel)))
22871
+ if (!existsSync17(join14(repoPath, rel)))
22640
22872
  continue;
22641
22873
  add(rel);
22642
22874
  if (depth > 0 && rel.endsWith(".sh")) {
22643
- for (const p of inferPaths(head(join13(repoPath, rel)), repoPath, depth - 1))
22875
+ for (const p of inferPaths(head(join14(repoPath, rel)), repoPath, depth - 1))
22644
22876
  out.add(p);
22645
22877
  }
22646
22878
  }
@@ -22663,9 +22895,9 @@ function procCommand(runner, script, body) {
22663
22895
  return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
22664
22896
  }
22665
22897
  function detectPages(repoPath, scripts, runner) {
22666
- if (!scripts.build || !existsSync16(join13(repoPath, "functions")))
22898
+ if (!scripts.build || !existsSync17(join14(repoPath, "functions")))
22667
22899
  return null;
22668
- const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join13(repoPath, f)).find((f) => existsSync16(f));
22900
+ const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join14(repoPath, f)).find((f) => existsSync17(f));
22669
22901
  if (!cfg)
22670
22902
  return null;
22671
22903
  const out = readFileSync12(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
@@ -22674,68 +22906,68 @@ function detectPages(repoPath, scripts, runner) {
22674
22906
  return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
22675
22907
  }
22676
22908
  function detectRunner(repoPath, packageManager) {
22677
- if (existsSync16(join13(repoPath, "bun.lock")) || existsSync16(join13(repoPath, "bun.lockb")))
22909
+ if (existsSync17(join14(repoPath, "bun.lock")) || existsSync17(join14(repoPath, "bun.lockb")))
22678
22910
  return "bun";
22679
- if (existsSync16(join13(repoPath, "pnpm-lock.yaml")))
22911
+ if (existsSync17(join14(repoPath, "pnpm-lock.yaml")))
22680
22912
  return "pnpm";
22681
- if (existsSync16(join13(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
22913
+ if (existsSync17(join14(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
22682
22914
  return "yarn";
22683
22915
  return "npm";
22684
22916
  }
22685
22917
 
22686
22918
  // ../daemon/src/repos/create.ts
22687
- import { existsSync as existsSync17, readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync6 } from "fs";
22919
+ import { existsSync as existsSync18, readdirSync as readdirSync5, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
22688
22920
  import { appendFile as appendFile2, mkdir as mkdir2, rm } from "fs/promises";
22689
- import { homedir as homedir6 } from "os";
22690
- import { dirname as dirname5, join as join14, resolve as resolve8 } from "path";
22921
+ import { homedir as homedir8 } from "os";
22922
+ import { dirname as dirname6, join as join15, resolve as resolve8 } from "path";
22691
22923
  function isInside(dir, parent) {
22692
22924
  const p = parent.replace(/\/+$/, "");
22693
22925
  return dir === p || dir.startsWith(`${p}/`);
22694
22926
  }
22695
- function planProject(opts, home = homedir6()) {
22927
+ function planProject(opts, home = homedir8()) {
22696
22928
  const nameError = projectNameError(opts.name);
22697
22929
  if (nameError)
22698
22930
  throw new UserError(nameError);
22699
22931
  const first = firstProjectsFolder(opts.parent, home);
22700
22932
  const parent = first ?? resolveParent(opts.parent);
22701
- const dir = join14(parent, opts.name.trim());
22702
- if (existsSync17(dir))
22933
+ const dir = join15(parent, opts.name.trim());
22934
+ if (existsSync18(dir))
22703
22935
  throw new UserError(`${dir} already exists`);
22704
22936
  return { parent, dir, makeParent: first !== null };
22705
22937
  }
22706
22938
  function firstProjectsFolder(raw, home) {
22707
22939
  const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
22708
- if (typed !== join14(home, PROJECTS_FOLDER) || existsSync17(typed) || !existsSync17(home))
22940
+ if (typed !== join15(home, PROJECTS_FOLDER) || existsSync18(typed) || !existsSync18(home))
22709
22941
  return null;
22710
- return join14(realpathSync2(home), PROJECTS_FOLDER);
22942
+ return join15(realpathSync2(home), PROJECTS_FOLDER);
22711
22943
  }
22712
22944
  function planInPlace(opts) {
22713
22945
  const name = opts.name.trim();
22714
22946
  if (!name || name === "." || name === ".." || name.includes("/")) {
22715
22947
  throw new UserError(`${opts.name} is not a folder name`);
22716
22948
  }
22717
- const typed = join14(resolveParent(opts.parent), name);
22718
- if (!existsSync17(typed) || !statSync6(typed).isDirectory())
22949
+ const typed = join15(resolveParent(opts.parent), name);
22950
+ if (!existsSync18(typed) || !statSync7(typed).isDirectory())
22719
22951
  throw new UserError(`${typed} is not a folder`);
22720
22952
  const dir = realpathSync2(typed);
22721
- if (existsSync17(join14(dir, ".git")))
22953
+ if (existsSync18(join15(dir, ".git")))
22722
22954
  throw new UserError(`${dir} is already a project; open it instead`);
22723
22955
  if (!holdsNothing(readdirSync5(dir))) {
22724
22956
  throw new UserError(`${dir} is not empty; only an empty folder can become a project where it is`);
22725
22957
  }
22726
- return { parent: dirname5(dir), dir, makeParent: false };
22958
+ return { parent: dirname6(dir), dir, makeParent: false };
22727
22959
  }
22728
22960
  function resolveParent(raw) {
22729
22961
  const typed = expandTilde(raw.trim());
22730
22962
  if (!typed.startsWith("/"))
22731
22963
  throw new UserError(`${raw} is not a folder path`);
22732
- if (!existsSync17(typed))
22964
+ if (!existsSync18(typed))
22733
22965
  throw new UserError(`${raw} does not exist; make it first`);
22734
- if (!statSync6(typed).isDirectory())
22966
+ if (!statSync7(typed).isDirectory())
22735
22967
  throw new UserError(`${raw} is not a folder`);
22736
22968
  return realpathSync2(typed);
22737
22969
  }
22738
- async function createRepoDir(opts, home = homedir6()) {
22970
+ async function createRepoDir(opts, home = homedir8()) {
22739
22971
  const { parent, dir, makeParent } = planProject(opts, home);
22740
22972
  await requireGitIdentity(makeParent ? home : parent);
22741
22973
  if (makeParent)
@@ -22770,7 +23002,7 @@ async function initWithEmptyCommit(dir) {
22770
23002
  if (!exclude.ok)
22771
23003
  throw new UserError(`git rev-parse failed: ${exclude.err}`);
22772
23004
  const excludePath = resolve8(dir, exclude.out);
22773
- await mkdir2(dirname5(excludePath), { recursive: true });
23005
+ await mkdir2(dirname6(excludePath), { recursive: true });
22774
23006
  await appendFile2(excludePath, `
22775
23007
  ${LOCAL_EXCLUDES.join(`
22776
23008
  `)}
@@ -22809,13 +23041,13 @@ async function undoCreate(dir) {
22809
23041
  }
22810
23042
  async function undoInit(dir) {
22811
23043
  try {
22812
- await rm(join14(dir, ".git"), { recursive: true, force: true });
23044
+ await rm(join15(dir, ".git"), { recursive: true, force: true });
22813
23045
  } catch (e) {
22814
23046
  log.warn(dir, "could not take back a half-made .git", e);
22815
23047
  }
22816
23048
  }
22817
23049
  async function unmakeProject(dir, made) {
22818
- const target = made === "folder" ? dir : join14(dir, ".git");
23050
+ const target = made === "folder" ? dir : join15(dir, ".git");
22819
23051
  try {
22820
23052
  await rm(target, { recursive: true, force: true });
22821
23053
  } catch (e) {
@@ -22830,8 +23062,8 @@ async function hasGitIdentity(cwd) {
22830
23062
  return !!email.out && !!name.out;
22831
23063
  }
22832
23064
  async function setGitIdentity(identity) {
22833
- await ok(git(homedir6(), "config", "--global", "user.name", identity.name.trim()), "git config");
22834
- await ok(git(homedir6(), "config", "--global", "user.email", identity.email.trim()), "git config");
23065
+ await ok(git(homedir8(), "config", "--global", "user.name", identity.name.trim()), "git config");
23066
+ await ok(git(homedir8(), "config", "--global", "user.email", identity.email.trim()), "git config");
22835
23067
  }
22836
23068
  async function requireGitIdentity(cwd) {
22837
23069
  if (!await hasGitIdentity(cwd)) {
@@ -22845,8 +23077,8 @@ async function ok(p, what) {
22845
23077
  }
22846
23078
 
22847
23079
  // ../daemon/src/repos/watcher.ts
22848
- import { existsSync as existsSync18, watch } from "fs";
22849
- import { basename as basename3, join as join15 } from "path";
23080
+ import { existsSync as existsSync19, watch } from "fs";
23081
+ import { basename as basename4, join as join16 } from "path";
22850
23082
  function watchDefaultBranch(repoPath, branch, onMove) {
22851
23083
  const last = { local: null, upstream: null };
22852
23084
  const read = async () => {
@@ -22899,29 +23131,29 @@ function watchDefaultBranch(repoPath, branch, onMove) {
22899
23131
  ]);
22900
23132
  if (stopped)
22901
23133
  return;
22902
- const gitDir = common.ok && common.out ? common.out : join15(repoPath, ".git");
23134
+ const gitDir = common.ok && common.out ? common.out : join16(repoPath, ".git");
22903
23135
  tryWatch(gitDir);
22904
- tryWatch(join15(gitDir, "refs", "heads"));
23136
+ tryWatch(join16(gitDir, "refs", "heads"));
22905
23137
  if (!remote.ok || !remote.out)
22906
23138
  return;
22907
- const remotes = join15(gitDir, "refs", "remotes");
22908
- const mine = join15(remotes, remote.out);
23139
+ const remotes = join16(gitDir, "refs", "remotes");
23140
+ const mine = join16(remotes, remote.out);
22909
23141
  let inner = null;
22910
23142
  let mid = null;
22911
23143
  const armInner = () => {
22912
23144
  inner?.close();
22913
- inner = existsSync18(mine) ? tryWatch(mine) : null;
23145
+ inner = existsSync19(mine) ? tryWatch(mine) : null;
22914
23146
  };
22915
23147
  const armMid = () => {
22916
23148
  mid?.close();
22917
- mid = existsSync18(remotes) ? tryWatch(remotes, (_event, filename) => {
23149
+ mid = existsSync19(remotes) ? tryWatch(remotes, (_event, filename) => {
22918
23150
  if (!filename || filename === remote.out)
22919
23151
  armInner();
22920
23152
  schedule();
22921
23153
  }) : null;
22922
23154
  armInner();
22923
23155
  };
22924
- tryWatch(join15(gitDir, "refs"), (_event, filename) => {
23156
+ tryWatch(join16(gitDir, "refs"), (_event, filename) => {
22925
23157
  if (!filename || filename === "remotes")
22926
23158
  armMid();
22927
23159
  schedule();
@@ -22950,7 +23182,7 @@ function watchWorktreeDir(repoPath, onChange) {
22950
23182
  }, 300);
22951
23183
  };
22952
23184
  const arm = (commonDir) => {
22953
- const dir = join15(commonDir, "worktrees");
23185
+ const dir = join16(commonDir, "worktrees");
22954
23186
  const armInner = () => {
22955
23187
  inner?.close();
22956
23188
  inner = null;
@@ -22992,8 +23224,8 @@ function watchWorktreeDir(repoPath, onChange) {
22992
23224
  }
22993
23225
  function watchConfigFile(repoPath, onChange) {
22994
23226
  const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
22995
- const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename3(rel)));
22996
- const dir = join15(repoPath, CONFIG_DIR);
23227
+ const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename4(rel)));
23228
+ const dir = join16(repoPath, CONFIG_DIR);
22997
23229
  let timer = null;
22998
23230
  let outer = null;
22999
23231
  let inner = null;
@@ -23008,7 +23240,7 @@ function watchConfigFile(repoPath, onChange) {
23008
23240
  const armInner = () => {
23009
23241
  inner?.close();
23010
23242
  inner = null;
23011
- if (!existsSync18(dir))
23243
+ if (!existsSync19(dir))
23012
23244
  return;
23013
23245
  try {
23014
23246
  inner = watch(dir, (_event, filename) => {
@@ -23062,7 +23294,7 @@ class RepoRegistry {
23062
23294
  }
23063
23295
  async boot() {
23064
23296
  const { state } = this.d;
23065
- const gone = state.worktrees.filter((wt) => !existsSync19(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
23297
+ const gone = state.worktrees.filter((wt) => !existsSync20(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
23066
23298
  for (const wt of gone)
23067
23299
  await this.d.worktrees.forgetGone(wt);
23068
23300
  for (const repo of state.repos) {
@@ -23107,7 +23339,7 @@ class RepoRegistry {
23107
23339
  }
23108
23340
  }
23109
23341
  gitIdentity() {
23110
- this.identity ??= hasGitIdentity(homedir7());
23342
+ this.identity ??= hasGitIdentity(homedir9());
23111
23343
  return this.identity;
23112
23344
  }
23113
23345
  async unmake(repoId) {
@@ -23130,7 +23362,7 @@ class RepoRegistry {
23130
23362
  return `${repo.name} is set up now`;
23131
23363
  if (main.promptedAt || main.lastTurn)
23132
23364
  return `${repo.name} has a chat now`;
23133
- if (existsSync19(join16(repo.path, ".claude")))
23365
+ if (existsSync20(join17(repo.path, ".claude")))
23134
23366
  return `an agent has already run in ${repo.name}`;
23135
23367
  if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
23136
23368
  return `${repo.name} has files in it now`;
@@ -23207,7 +23439,7 @@ class RepoRegistry {
23207
23439
  }
23208
23440
  async register(rawPath, made) {
23209
23441
  const path = expandTilde(rawPath);
23210
- if (!existsSync19(path))
23442
+ if (!existsSync20(path))
23211
23443
  throw new UserError(`${path} does not exist`);
23212
23444
  if (!await isGitRepo(path))
23213
23445
  throw new UserError(`${path} is not a git repository`);
@@ -23221,7 +23453,7 @@ class RepoRegistry {
23221
23453
  const repo = {
23222
23454
  id: shortId(),
23223
23455
  path: root,
23224
- name: basename4(root),
23456
+ name: basename5(root),
23225
23457
  defaultBranch: await defaultBranch(root),
23226
23458
  config: detected.config,
23227
23459
  configFile: configTarget(root, !!made),
@@ -23281,10 +23513,10 @@ class RepoRegistry {
23281
23513
  throw new UserError(current.reason);
23282
23514
  const was = configTarget(repo.path, !!repo.made);
23283
23515
  const rel = configSibling(was, kind);
23284
- const moved = rel !== was && existsSync19(join16(repo.path, was));
23516
+ const moved = rel !== was && existsSync20(join17(repo.path, was));
23285
23517
  if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
23286
23518
  try {
23287
- rmSync2(join16(repo.path, was));
23519
+ rmSync2(join17(repo.path, was));
23288
23520
  } catch (e) {
23289
23521
  log.warn(repoId, `could not remove ${was}`, e);
23290
23522
  }
@@ -23293,9 +23525,9 @@ class RepoRegistry {
23293
23525
  }
23294
23526
  if (isLocalConfigFile(rel))
23295
23527
  await excludeFromGit(repo.path, rel);
23296
- const file = join16(repo.path, rel);
23528
+ const file = join17(repo.path, rel);
23297
23529
  try {
23298
- mkdirSync6(dirname6(file), { recursive: true });
23530
+ mkdirSync6(dirname7(file), { recursive: true });
23299
23531
  writeFileSync7(file, configText(repo.path, rel, config));
23300
23532
  } catch (e) {
23301
23533
  log.warn(repoId, `could not write ${rel}`, e);
@@ -23397,7 +23629,7 @@ class RepoRegistry {
23397
23629
  }
23398
23630
  placeConfig(repo) {
23399
23631
  const rel = configTarget(repo.path, !!repo.made);
23400
- if (isLocalConfigFile(rel) && existsSync19(join16(repo.path, rel))) {
23632
+ if (isLocalConfigFile(rel) && existsSync20(join17(repo.path, rel))) {
23401
23633
  fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
23402
23634
  }
23403
23635
  if (repo.configFile === rel)
@@ -23489,7 +23721,7 @@ class RepoRegistry {
23489
23721
  }
23490
23722
 
23491
23723
  // ../daemon/src/routes/service.ts
23492
- import { join as join17 } from "path";
23724
+ import { join as join18 } from "path";
23493
23725
 
23494
23726
  // ../daemon/src/routes/fileRouters.ts
23495
23727
  var SKIP = /(^|\/)(node_modules|dist|build|out|coverage|\.next|\.nuxt|\.output|\.svelte-kit|\.vercel|\.netlify)\//;
@@ -24689,7 +24921,7 @@ class RouteService {
24689
24921
  return mergeRoutes(fileRoutes(paths, roots), code);
24690
24922
  }
24691
24923
  async cachedRead(root, path, had, kept, decode) {
24692
- const abs = join17(root, path);
24924
+ const abs = join18(root, path);
24693
24925
  const stat = await this.fs.stat(abs);
24694
24926
  if (!stat)
24695
24927
  return null;
@@ -24714,7 +24946,7 @@ class RouteService {
24714
24946
  }
24715
24947
  }
24716
24948
  async hash(root, file) {
24717
- const abs = join17(root, file);
24949
+ const abs = join18(root, file);
24718
24950
  const stat = await this.fs.stat(abs);
24719
24951
  if (!stat)
24720
24952
  return null;
@@ -24976,7 +25208,7 @@ function isAlive(pid) {
24976
25208
  }
24977
25209
 
24978
25210
  // ../daemon/src/runtime/bridge-script.ts
24979
- import { existsSync as existsSync20, readFileSync as readFileSync13, statSync as statSync7 } from "fs";
25211
+ import { existsSync as existsSync21, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
24980
25212
  var LEARNED_MAX = 8;
24981
25213
 
24982
25214
  class BridgeScript {
@@ -25006,9 +25238,9 @@ class BridgeScript {
25006
25238
  this.cache = null;
25007
25239
  }
25008
25240
  get() {
25009
- if (!existsSync20(this.path))
25241
+ if (!existsSync21(this.path))
25010
25242
  return "// toyon bridge not built";
25011
- const s = statSync7(this.path);
25243
+ const s = statSync8(this.path);
25012
25244
  const stamp = `${s.mtimeMs}:${s.size}`;
25013
25245
  if (this.cache && stamp === this.stamp)
25014
25246
  return this.cache;
@@ -25033,7 +25265,7 @@ function servedLocally(origin) {
25033
25265
  }
25034
25266
 
25035
25267
  // ../daemon/src/server/ws.ts
25036
- import { homedir as homedir9 } from "os";
25268
+ import { homedir as homedir11 } from "os";
25037
25269
 
25038
25270
  // ../daemon/src/core/pair.ts
25039
25271
  import { randomBytes as randomBytes3 } from "crypto";
@@ -25056,47 +25288,47 @@ class PairCodes {
25056
25288
  }
25057
25289
 
25058
25290
  // ../daemon/src/agent/config.ts
25059
- import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
25060
- import { homedir as homedir8 } from "os";
25061
- import { join as join18 } from "path";
25062
- function agentConfigFiles(spec, repoPath, home = homedir8()) {
25291
+ import { existsSync as existsSync22, readFileSync as readFileSync14 } from "fs";
25292
+ import { homedir as homedir10 } from "os";
25293
+ import { join as join19 } from "path";
25294
+ function agentConfigFiles(spec, repoPath, home = homedir10()) {
25063
25295
  const file = (id, label, path) => ({
25064
25296
  id,
25065
25297
  label,
25066
25298
  path,
25067
- exists: existsSync21(path)
25299
+ exists: existsSync22(path)
25068
25300
  });
25069
25301
  if (spec.id === "claude") {
25070
25302
  return [
25071
- file("user-config", "user config (MCP servers, projects)", join18(home, ".claude.json")),
25072
- file("user-settings", "user settings (permissions, hooks)", join18(home, ".claude", "settings.json")),
25073
- ...repoPath ? [file("project-mcp", "project MCP servers", join18(repoPath, ".mcp.json"))] : [],
25074
- ...repoPath ? [file("project-settings", "project settings", join18(repoPath, ".claude", "settings.json"))] : []
25303
+ file("user-config", "user config (MCP servers, projects)", join19(home, ".claude.json")),
25304
+ file("user-settings", "user settings (permissions, hooks)", join19(home, ".claude", "settings.json")),
25305
+ ...repoPath ? [file("project-mcp", "project MCP servers", join19(repoPath, ".mcp.json"))] : [],
25306
+ ...repoPath ? [file("project-settings", "project settings", join19(repoPath, ".claude", "settings.json"))] : []
25075
25307
  ];
25076
25308
  }
25077
25309
  if (spec.id === "codex")
25078
- return [file("user-config", "config", join18(home, ".codex", "config.toml"))];
25310
+ return [file("user-config", "config", join19(home, ".codex", "config.toml"))];
25079
25311
  return [];
25080
25312
  }
25081
- function describeAgentConfig(spec, repoPath, home = homedir8()) {
25313
+ function describeAgentConfig(spec, repoPath, home = homedir10()) {
25082
25314
  const files = agentConfigFiles(spec, repoPath, home);
25083
25315
  const servers = [];
25084
25316
  if (spec.id === "claude") {
25085
- const user = readJson(join18(home, ".claude.json"));
25317
+ const user = readJson(join19(home, ".claude.json"));
25086
25318
  servers.push(...serversOf(user?.mcpServers, "user"));
25087
25319
  if (repoPath) {
25088
25320
  const projects = user?.projects;
25089
25321
  const mine = projects?.[repoPath];
25090
25322
  servers.push(...serversOf(mine?.mcpServers, "local"));
25091
- servers.push(...serversOf(readJson(join18(repoPath, ".mcp.json"))?.mcpServers, "project"));
25323
+ servers.push(...serversOf(readJson(join19(repoPath, ".mcp.json"))?.mcpServers, "project"));
25092
25324
  }
25093
25325
  } else if (spec.id === "codex") {
25094
- servers.push(...codexServers(join18(home, ".codex", "config.toml")));
25326
+ servers.push(...codexServers(join19(home, ".codex", "config.toml")));
25095
25327
  }
25096
25328
  return { agent: spec.id, files, servers };
25097
25329
  }
25098
25330
  function readJson(path) {
25099
- if (!existsSync21(path))
25331
+ if (!existsSync22(path))
25100
25332
  return null;
25101
25333
  try {
25102
25334
  const v = JSON.parse(readFileSync14(path, "utf8"));
@@ -25117,7 +25349,7 @@ function serversOf(raw, scope) {
25117
25349
  });
25118
25350
  }
25119
25351
  function codexServers(path) {
25120
- if (!existsSync21(path))
25352
+ if (!existsSync22(path))
25121
25353
  return [];
25122
25354
  const out = [];
25123
25355
  let current = null;
@@ -25883,8 +26115,8 @@ var handlers = {
25883
26115
  s.afterLand.run(msg.repoId);
25884
26116
  ctx.reply({ t: "self", self: s.self.get() });
25885
26117
  },
25886
- "restart-daemon"(_msg, _ctx, s) {
25887
- const refused = s.restarter.request();
26118
+ async "restart-daemon"(_msg, _ctx, s) {
26119
+ const refused = await s.restarter.request();
25888
26120
  if (refused)
25889
26121
  throw new UserError(refused);
25890
26122
  },
@@ -25945,10 +26177,14 @@ var handlers = {
25945
26177
  reveal(msg, _ctx, s) {
25946
26178
  s.files.reveal(msg.worktreeId, msg.path);
25947
26179
  },
25948
- async "discard-file"(msg, _ctx, s) {
25949
- await s.files.discard(msg.worktreeId, msg.path);
25950
- s.hub.emit("filesChanged", msg.worktreeId);
25951
- s.landing.recheck(msg.worktreeId);
26180
+ async "discard-files"(msg, _ctx, s) {
26181
+ try {
26182
+ for (const path of msg.paths)
26183
+ await s.files.discard(msg.worktreeId, path);
26184
+ } finally {
26185
+ s.hub.emit("filesChanged", msg.worktreeId);
26186
+ s.landing.recheck(msg.worktreeId);
26187
+ }
25952
26188
  },
25953
26189
  async judge(msg, _ctx, s) {
25954
26190
  await s.landing.judge(msg.worktreeId, msg.note);
@@ -26168,8 +26404,8 @@ async function dispatch(msg, ctx, s) {
26168
26404
  }
26169
26405
 
26170
26406
  // ../daemon/src/server/http.ts
26171
- import { existsSync as existsSync22 } from "fs";
26172
- import { join as join19 } from "path";
26407
+ import { existsSync as existsSync23 } from "fs";
26408
+ import { join as join20 } from "path";
26173
26409
  var IMMUTABLE = "private, max-age=31536000, immutable";
26174
26410
  var NO_STORE = "no-store";
26175
26411
  function createFetch(opts) {
@@ -26245,7 +26481,7 @@ function createFetch(opts) {
26245
26481
  return new Response("unauthorized", { status: 401 });
26246
26482
  if (req.method === "GET")
26247
26483
  return Response.json(opts.restartWait(), { headers: { "cache-control": NO_STORE } });
26248
- const refused = opts.restart(url.searchParams.has(RESTART_NOW));
26484
+ const refused = await opts.restart(url.searchParams.has(RESTART_NOW));
26249
26485
  return refused ? new Response(refused, { status: 409 }) : new Response(null, { status: 202 });
26250
26486
  }
26251
26487
  if (url.pathname === "/pair" && req.method === "POST") {
@@ -26312,7 +26548,7 @@ function createFetch(opts) {
26312
26548
  const places = worktreeId && file && !extra ? [opts.attachments.fileFor(worktreeId, file), opts.archivedAttachment(worktreeId, file)] : [];
26313
26549
  if (worktreeId && file)
26314
26550
  await opts.attachments.whenWritten(worktreeId, file);
26315
- const path = places.find((p) => !!p && existsSync22(p));
26551
+ const path = places.find((p) => !!p && existsSync23(p));
26316
26552
  if (!path)
26317
26553
  return new Response("not found", { status: 404 });
26318
26554
  return new Response(Bun.file(path), { headers: { "cache-control": IMMUTABLE } });
@@ -26343,15 +26579,15 @@ function createFetch(opts) {
26343
26579
  });
26344
26580
  }
26345
26581
  const rel = url.pathname === "/" ? "/index.html" : url.pathname;
26346
- const file = join19(opts.shellDist, rel.replaceAll("..", ""));
26582
+ const file = join20(opts.shellDist, rel.replaceAll("..", ""));
26347
26583
  const hashed = rel.startsWith("/assets/");
26348
- if (existsSync22(file) && Bun.file(file).size > 0) {
26584
+ if (existsSync23(file) && Bun.file(file).size > 0) {
26349
26585
  return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
26350
26586
  }
26351
26587
  if (hashed)
26352
26588
  return new Response("not found", { status: 404 });
26353
- const index = join19(opts.shellDist, "index.html");
26354
- if (existsSync22(index))
26589
+ const index = join20(opts.shellDist, "index.html");
26590
+ if (existsSync23(index))
26355
26591
  return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE } });
26356
26592
  return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
26357
26593
  };
@@ -26592,6 +26828,7 @@ function startServer(opts) {
26592
26828
  t: "hello",
26593
26829
  version,
26594
26830
  install: s.update.install(),
26831
+ registry: await s.update.registry(),
26595
26832
  protocol: PROTOCOL_VERSION,
26596
26833
  repos: s.state.repos,
26597
26834
  rows: await s.worktrees.rows({ quick: true }),
@@ -26601,7 +26838,7 @@ function startServer(opts) {
26601
26838
  agents: agentInfos(),
26602
26839
  defaultAgent: s.agents.defaultId(s.state.defaultAgent),
26603
26840
  agentChosen: s.state.defaultAgent !== undefined,
26604
- home: homedir9(),
26841
+ home: homedir11(),
26605
26842
  folderDialog: process.platform === "darwin" && !cloud.enabled,
26606
26843
  remote: opts.remote && { host: opts.remote.host, previews: opts.remote.previews },
26607
26844
  paired: s.state.paired,
@@ -26752,16 +26989,16 @@ function startServer(opts) {
26752
26989
  }
26753
26990
 
26754
26991
  // ../daemon/src/themes/store.ts
26755
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
26756
- import { homedir as homedir10 } from "os";
26757
- import { basename as basename5, dirname as dirname7, join as join20, resolve as resolve9 } from "path";
26758
- var home = homedir10();
26992
+ import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync9, writeFileSync as writeFileSync8 } from "fs";
26993
+ import { homedir as homedir12 } from "os";
26994
+ import { basename as basename6, dirname as dirname8, join as join21, resolve as resolve9 } from "path";
26995
+ var home = homedir12();
26759
26996
  var defaultExtensionDirs = [
26760
- join20(home, ".vscode", "extensions"),
26761
- join20(home, ".vscode-insiders", "extensions"),
26762
- join20(home, ".vscode-oss", "extensions"),
26763
- join20(home, ".cursor", "extensions"),
26764
- join20(home, ".windsurf", "extensions"),
26997
+ join21(home, ".vscode", "extensions"),
26998
+ join21(home, ".vscode-insiders", "extensions"),
26999
+ join21(home, ".vscode-oss", "extensions"),
27000
+ join21(home, ".cursor", "extensions"),
27001
+ join21(home, ".windsurf", "extensions"),
26765
27002
  "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions",
26766
27003
  "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/extensions",
26767
27004
  "/Applications/Cursor.app/Contents/Resources/app/extensions",
@@ -26839,13 +27076,13 @@ class ThemeStore {
26839
27076
  if (json && typeof json === "object" && typeof json.include === "string") {
26840
27077
  throw new ThemeImportError("this theme file uses `include`; drop the whole extension folder's theme into ~/.toyon/themes or install it in VS Code and rescan");
26841
27078
  }
26842
- const base = slug(basename5(name).replace(/\.(json|jsonc)$/i, ""));
27079
+ const base = slug(basename6(name).replace(/\.(json|jsonc)$/i, ""));
26843
27080
  const theme = vscodeToTheme(json, {
26844
27081
  id: `file:${base}`,
26845
27082
  name: json?.name ?? base,
26846
27083
  source: "file"
26847
27084
  });
26848
- writeFileSync8(join20(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
27085
+ writeFileSync8(join21(this.themesDir, `${base}.json`), `${JSON.stringify(theme, null, 2)}
26849
27086
  `);
26850
27087
  this.load();
26851
27088
  return theme;
@@ -26860,7 +27097,7 @@ function readVscodeTheme(file, depth = 0) {
26860
27097
  throw new ThemeImportError(`${file}: not a JSON object`);
26861
27098
  const t = json;
26862
27099
  if (typeof t.include === "string" && depth < 5) {
26863
- const parent = readVscodeTheme(resolve9(dirname7(file), t.include), depth + 1);
27100
+ const parent = readVscodeTheme(resolve9(dirname8(file), t.include), depth + 1);
26864
27101
  return {
26865
27102
  ...parent,
26866
27103
  ...t,
@@ -26874,13 +27111,13 @@ function readVscodeTheme(file, depth = 0) {
26874
27111
  return t;
26875
27112
  }
26876
27113
  function loadDir(dir) {
26877
- if (!existsSync23(dir))
27114
+ if (!existsSync24(dir))
26878
27115
  return [];
26879
27116
  const out = [];
26880
27117
  for (const f of readdirSync6(dir)) {
26881
27118
  if (!/\.jsonc?$/i.test(f))
26882
27119
  continue;
26883
- const file = join20(dir, f);
27120
+ const file = join21(dir, f);
26884
27121
  const id = `file:${slug(f.replace(/\.(json|jsonc)$/i, ""))}`;
26885
27122
  try {
26886
27123
  const json = readJsonc(file);
@@ -26896,7 +27133,7 @@ function loadDir(dir) {
26896
27133
  return out;
26897
27134
  }
26898
27135
  function discover(dir) {
26899
- if (!existsSync23(dir))
27136
+ if (!existsSync24(dir))
26900
27137
  return [];
26901
27138
  const out = [];
26902
27139
  let entries;
@@ -26906,10 +27143,10 @@ function discover(dir) {
26906
27143
  return out;
26907
27144
  }
26908
27145
  for (const name of entries) {
26909
- const extDir = join20(dir, name);
26910
- const pkgFile = join20(extDir, "package.json");
27146
+ const extDir = join21(dir, name);
27147
+ const pkgFile = join21(extDir, "package.json");
26911
27148
  try {
26912
- if (!statSync8(extDir).isDirectory() || !existsSync23(pkgFile))
27149
+ if (!statSync9(extDir).isDirectory() || !existsSync24(pkgFile))
26913
27150
  continue;
26914
27151
  const pkg = JSON.parse(readFileSync15(pkgFile, "utf8"));
26915
27152
  const themes = pkg.contributes?.themes;
@@ -26917,8 +27154,8 @@ function discover(dir) {
26917
27154
  continue;
26918
27155
  const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
26919
27156
  let nls = {};
26920
- const nlsFile = join20(extDir, "package.nls.json");
26921
- if (existsSync23(nlsFile)) {
27157
+ const nlsFile = join21(extDir, "package.nls.json");
27158
+ if (existsSync24(nlsFile)) {
26922
27159
  try {
26923
27160
  nls = JSON.parse(readFileSync15(nlsFile, "utf8"));
26924
27161
  } catch {}
@@ -26945,11 +27182,10 @@ function discover(dir) {
26945
27182
  }
26946
27183
 
26947
27184
  // ../daemon/src/update/infra.ts
26948
- import { homedir as homedir11 } from "os";
27185
+ import { homedir as homedir13 } from "os";
26949
27186
  async function latestVersion() {
26950
- const config = await run("npm", ["config", "get", "registry"], homedir11());
26951
- const registry = config.ok && config.out !== "" ? config.out : "the npm registry";
26952
- const r = await run("npm", ["view", "toyon", "version", "--json"], homedir11());
27187
+ const registry = await npmRegistry();
27188
+ const r = await run("npm", ["view", "toyon", "version", "--json"], homedir13());
26953
27189
  if (!r.ok)
26954
27190
  return { version: null, registry };
26955
27191
  try {
@@ -26959,12 +27195,16 @@ async function latestVersion() {
26959
27195
  return { version: null, registry };
26960
27196
  }
26961
27197
  }
27198
+ async function npmRegistry() {
27199
+ const config = await run("npm", ["config", "get", "registry"], homedir13());
27200
+ return config.ok && config.out !== "" ? config.out : "the npm registry";
27201
+ }
26962
27202
  var LOG_POINTER = /complete log of this run/i;
26963
27203
  async function runInstall(command) {
26964
27204
  const [cmd, ...args] = command;
26965
27205
  if (!cmd)
26966
27206
  return { ok: false, line: "nothing to run" };
26967
- const r = await runLive(cmd, args, homedir11());
27207
+ const r = await runLive(cmd, args, homedir13());
26968
27208
  const lines = `${r.out}
26969
27209
  ${r.err}`.split(`
26970
27210
  `).map((l) => l.trim()).filter((l) => l !== "" && !LOG_POINTER.test(l));
@@ -26987,12 +27227,16 @@ var CHECK_EVERY_MS = 6 * 60 * 60000;
26987
27227
  var FIRST_CHECK_MS = 2 * 60000;
26988
27228
  var SETTLED_MS = 2 * 60000;
26989
27229
  var RETRY_AFTER_MS = 24 * 60 * 60000;
27230
+ var INSTALL_SETTLE_MS = 60000;
26990
27231
 
26991
27232
  class UpdateService {
26992
27233
  d;
26993
27234
  installed = null;
26994
27235
  latest = null;
26995
27236
  unreachable = null;
27237
+ registryName = null;
27238
+ seen = null;
27239
+ seenAt = 0;
26996
27240
  installing = false;
26997
27241
  failed = null;
26998
27242
  wanted = false;
@@ -27026,17 +27270,33 @@ class UpdateService {
27026
27270
  await this.refresh();
27027
27271
  await this.auto();
27028
27272
  }
27029
- async refresh() {
27273
+ async refresh(settled = false) {
27030
27274
  const v = await this.d.installed();
27031
27275
  if (v === null)
27032
27276
  return;
27277
+ if (!settled && v !== this.d.running) {
27278
+ if (v !== this.seen) {
27279
+ this.seen = v;
27280
+ this.seenAt = this.now();
27281
+ return;
27282
+ }
27283
+ if (this.now() - this.seenAt < INSTALL_SETTLE_MS)
27284
+ return;
27285
+ }
27033
27286
  this.installed = v === this.d.running ? null : v;
27034
27287
  this.announce();
27035
27288
  }
27289
+ async registry() {
27290
+ if (this.d.method === "none" || this.d.managedBy)
27291
+ return null;
27292
+ this.registryName ??= await this.d.registry();
27293
+ return this.registryName;
27294
+ }
27036
27295
  async check() {
27037
27296
  if (this.d.method === "none" || this.d.managedBy)
27038
27297
  return;
27039
27298
  const answer = await this.d.latest();
27299
+ this.registryName = answer.registry;
27040
27300
  if (answer.version === null) {
27041
27301
  this.unreachable = answer.registry;
27042
27302
  return;
@@ -27066,8 +27326,9 @@ class UpdateService {
27066
27326
  await this.check();
27067
27327
  if (this.unreachable)
27068
27328
  throw new UserError(`Could not reach ${this.unreachable}`);
27069
- if (this.target() === null)
27070
- throw new UserError(`Toyon ${this.d.running} is the newest version`);
27329
+ if (this.target() === null) {
27330
+ throw new UserError(`Toyon ${this.d.running} is the newest version ${registryHost(this.registryName)} lists`);
27331
+ }
27071
27332
  }
27072
27333
  install() {
27073
27334
  return this.d.method;
@@ -27088,7 +27349,12 @@ class UpdateService {
27088
27349
  };
27089
27350
  }
27090
27351
  status() {
27091
- return { managedBy: this.d.managedBy, unreachable: this.unreachable, latest: this.latest };
27352
+ return {
27353
+ managedBy: this.d.managedBy,
27354
+ registry: this.d.method === "none" || this.d.managedBy ? null : this.registryName,
27355
+ unreachable: this.unreachable,
27356
+ latest: this.latest
27357
+ };
27092
27358
  }
27093
27359
  refuseManaged() {
27094
27360
  if (this.d.managedBy === "policy")
@@ -27149,10 +27415,10 @@ class UpdateService {
27149
27415
  this.announce();
27150
27416
  return;
27151
27417
  }
27152
- await this.refresh();
27418
+ await this.refresh(true);
27153
27419
  }
27154
27420
  this.wanted = false;
27155
- const refused = this.d.restarter.request();
27421
+ const refused = await this.d.restarter.request();
27156
27422
  if (refused) {
27157
27423
  this.failed = { version: target ?? this.d.running, line: refused, command: "toyon restart" };
27158
27424
  log.warn("update", `restart refused: ${refused}`);
@@ -27296,16 +27562,16 @@ class BackendShare {
27296
27562
 
27297
27563
  // ../daemon/src/worktrees/cache.ts
27298
27564
  import { createHash as createHash3 } from "crypto";
27299
- import { existsSync as existsSync24, readFileSync as readFileSync16 } from "fs";
27565
+ import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
27300
27566
  import { mkdir as mkdir4, readdir as readdir2, rename, rm as rm3, stat as stat3, utimes, writeFile as writeFile2 } from "fs/promises";
27301
- import { join as join21 } from "path";
27567
+ import { join as join22 } from "path";
27302
27568
 
27303
27569
  // ../daemon/src/core/clone.ts
27304
27570
  import { mkdir as mkdir3, rm as rm2 } from "fs/promises";
27305
- import { dirname as dirname8 } from "path";
27571
+ import { dirname as dirname9 } from "path";
27306
27572
  async function cloneTree(src, dst) {
27307
27573
  const started = Date.now();
27308
- await mkdir3(dirname8(dst), { recursive: true });
27574
+ await mkdir3(dirname9(dst), { recursive: true });
27309
27575
  const attempts = [
27310
27576
  ["clonefile", ["-Rc", src, dst]],
27311
27577
  ["reflink", ["-R", "--reflink=auto", src, dst]],
@@ -27313,7 +27579,7 @@ async function cloneTree(src, dst) {
27313
27579
  ];
27314
27580
  let error = "";
27315
27581
  for (const [how, args] of attempts) {
27316
- const r = await run("cp", args, dirname8(dst));
27582
+ const r = await run("cp", args, dirname9(dst));
27317
27583
  if (r.ok)
27318
27584
  return { ok: true, how, ms: Date.now() - started };
27319
27585
  error = r.err.split(`
@@ -27329,7 +27595,7 @@ var STALE_MS = 30 * 24 * 3600000;
27329
27595
  var TMP = ".tmp-";
27330
27596
  var MANIFEST = "manifest.json";
27331
27597
  function defaultTools(dir) {
27332
- const has = (...files) => files.some((f) => existsSync24(join21(dir, f)));
27598
+ const has = (...files) => files.some((f) => existsSync25(join22(dir, f)));
27333
27599
  const out = [];
27334
27600
  if (has("bun.lock", "bun.lockb"))
27335
27601
  out.push("bun --version");
@@ -27342,7 +27608,7 @@ function defaultTools(dir) {
27342
27608
  return out;
27343
27609
  }
27344
27610
  function withSidecars(root, rel) {
27345
- return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync24(join21(root, n)));
27611
+ return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync25(join22(root, n)));
27346
27612
  }
27347
27613
  var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
27348
27614
  async function listEntries(familyDir) {
@@ -27357,8 +27623,8 @@ async function listEntries(familyDir) {
27357
27623
  const m = /^([0-9a-f]{12})-([0-9a-z]+)$/.exec(name);
27358
27624
  if (!m?.[1] || !m[2])
27359
27625
  continue;
27360
- const dir = join21(familyDir, name);
27361
- if (!existsSync24(join21(dir, MANIFEST)))
27626
+ const dir = join22(familyDir, name);
27627
+ if (!existsSync25(join22(dir, MANIFEST)))
27362
27628
  continue;
27363
27629
  out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
27364
27630
  }
@@ -27388,7 +27654,7 @@ class ArtifactCache {
27388
27654
  try {
27389
27655
  const family = await this.family(wt, repo, policy);
27390
27656
  const base = await this.baseCommit(wt, repo);
27391
- const entries = await listEntries(join21(this.d.paths.cacheDir, repo.id, family.key));
27657
+ const entries = await listEntries(join22(this.d.paths.cacheDir, repo.id, family.key));
27392
27658
  const entry = entries.find((e) => e.base === base.slice(0, 12)) ?? entries[0];
27393
27659
  if (!entry) {
27394
27660
  say(`cache: nothing kept for these lockfiles and tools on ${family.platform}`);
@@ -27396,14 +27662,14 @@ class ArtifactCache {
27396
27662
  }
27397
27663
  const restored = [];
27398
27664
  for (const rel of policy.paths) {
27399
- if (existsSync24(join21(wt.path, rel)))
27665
+ if (existsSync25(join22(wt.path, rel)))
27400
27666
  continue;
27401
27667
  const names = withSidecars(entry.dir, rel);
27402
27668
  if (names.length === 0)
27403
27669
  continue;
27404
27670
  let whole = true;
27405
27671
  for (const name of names) {
27406
- const r = await (this.d.clone ?? cloneTree)(join21(entry.dir, name), join21(wt.path, name));
27672
+ const r = await (this.d.clone ?? cloneTree)(join22(entry.dir, name), join22(wt.path, name));
27407
27673
  if (r.ok)
27408
27674
  continue;
27409
27675
  say(`cache: could not restore ${name}: ${r.error}`);
@@ -27437,12 +27703,12 @@ class ArtifactCache {
27437
27703
  let tmp;
27438
27704
  let slot;
27439
27705
  try {
27440
- const present = policy.paths.filter((rel) => existsSync24(join21(wt.path, rel)));
27706
+ const present = policy.paths.filter((rel) => existsSync25(join22(wt.path, rel)));
27441
27707
  if (present.length === 0)
27442
27708
  return;
27443
27709
  const family = await this.family(wt, repo, policy);
27444
27710
  const base = await this.baseCommit(wt, repo);
27445
- const familyDir = join21(this.d.paths.cacheDir, repo.id, family.key);
27711
+ const familyDir = join22(this.d.paths.cacheDir, repo.id, family.key);
27446
27712
  const key = `${familyDir}/${base.slice(0, 12)}`;
27447
27713
  const kept = (await listEntries(familyDir)).find((e) => e.base === base.slice(0, 12));
27448
27714
  if (kept || this.making.has(key)) {
@@ -27453,21 +27719,21 @@ class ArtifactCache {
27453
27719
  this.making.add(key);
27454
27720
  const at = Date.now();
27455
27721
  const name = entryName(base, at);
27456
- tmp = join21(familyDir, `${TMP}${name}`);
27722
+ tmp = join22(familyDir, `${TMP}${name}`);
27457
27723
  await mkdir4(tmp, { recursive: true });
27458
27724
  let how = "clonefile";
27459
27725
  for (const rel of present) {
27460
27726
  for (const n of withSidecars(wt.path, rel)) {
27461
- const r = await (this.d.clone ?? cloneTree)(join21(wt.path, n), join21(tmp, n));
27727
+ const r = await (this.d.clone ?? cloneTree)(join22(wt.path, n), join22(tmp, n));
27462
27728
  if (!r.ok)
27463
27729
  throw new Error(`could not keep ${n}: ${r.error}`);
27464
27730
  how = r.how;
27465
27731
  }
27466
27732
  }
27467
27733
  const manifest = { at, base, platform: family.platform, tools: family.tools, paths: present };
27468
- await writeFile2(join21(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
27734
+ await writeFile2(join22(tmp, MANIFEST), `${JSON.stringify(manifest, null, 2)}
27469
27735
  `);
27470
- await rename(tmp, join21(familyDir, name));
27736
+ await rename(tmp, join22(familyDir, name));
27471
27737
  tmp = undefined;
27472
27738
  log.info(worktreeId, `cache: kept ${list(present)} for ${base.slice(0, 7)} via ${how} in ${Date.now() - started}ms`);
27473
27739
  await this.prune(familyDir);
@@ -27495,7 +27761,7 @@ class ArtifactCache {
27495
27761
  return;
27496
27762
  }
27497
27763
  for (const repoId of repoIds) {
27498
- const repoDir = join21(root, repoId);
27764
+ const repoDir = join22(root, repoId);
27499
27765
  if (!this.d.state.repo(repoId)) {
27500
27766
  await rm3(repoDir, { recursive: true, force: true });
27501
27767
  continue;
@@ -27507,7 +27773,7 @@ class ArtifactCache {
27507
27773
  continue;
27508
27774
  }
27509
27775
  for (const family of families) {
27510
- const familyDir = join21(repoDir, family);
27776
+ const familyDir = join22(repoDir, family);
27511
27777
  let names;
27512
27778
  try {
27513
27779
  names = await readdir2(familyDir);
@@ -27516,7 +27782,7 @@ class ArtifactCache {
27516
27782
  }
27517
27783
  for (const n of names)
27518
27784
  if (n.startsWith(TMP))
27519
- await rm3(join21(familyDir, n), { recursive: true, force: true });
27785
+ await rm3(join22(familyDir, n), { recursive: true, force: true });
27520
27786
  const entries = await listEntries(familyDir);
27521
27787
  let newest = 0;
27522
27788
  for (const e of entries) {
@@ -27536,7 +27802,7 @@ class ArtifactCache {
27536
27802
  for (const f of policy.key) {
27537
27803
  h.update(`\x00${f}\x00`);
27538
27804
  try {
27539
- h.update(readFileSync16(join21(wt.path, f)));
27805
+ h.update(readFileSync16(join22(wt.path, f)));
27540
27806
  } catch {
27541
27807
  h.update("(absent)");
27542
27808
  }
@@ -27602,11 +27868,9 @@ function saidRows(entries) {
27602
27868
  }
27603
27869
  return out;
27604
27870
  }
27605
- function needleWords(query) {
27606
- const needle = flat(query).toLowerCase();
27607
- if (needle.length < MIN_QUERY)
27608
- return [];
27609
- return [...new Set(needle.split(" "))];
27871
+ function needleTerms(query) {
27872
+ const terms = queryTerms(query);
27873
+ return terms.join(" ").length < MIN_QUERY ? [] : terms;
27610
27874
  }
27611
27875
  function snippet(text, ranges) {
27612
27876
  const first = Math.min(...ranges.map(([at]) => at));
@@ -27622,13 +27886,13 @@ function snippet(text, ranges) {
27622
27886
  }
27623
27887
  return { text: `${head}${text.slice(start, end)}${tail}`, match };
27624
27888
  }
27625
- function searchRows(rows, words, chat, limit = CHAT_HITS_PER_WORKTREE) {
27889
+ function searchRows(rows, terms, chat, limit = CHAT_HITS_PER_WORKTREE) {
27626
27890
  const hits = [];
27627
- if (words.length === 0)
27891
+ if (terms.length === 0)
27628
27892
  return { hits, more: false };
27629
27893
  for (let i = rows.length - 1;i >= 0; i--) {
27630
27894
  const row = rows[i];
27631
- const ranges = wordRanges(row.lower, words);
27895
+ const ranges = termRanges(row.lower, terms);
27632
27896
  if (!ranges)
27633
27897
  continue;
27634
27898
  if (hits.length === limit)
@@ -27637,13 +27901,13 @@ function searchRows(rows, words, chat, limit = CHAT_HITS_PER_WORKTREE) {
27637
27901
  }
27638
27902
  return { hits, more: false };
27639
27903
  }
27640
- function wordRanges(lower, words) {
27904
+ function termRanges(lower, terms) {
27641
27905
  const ranges = [];
27642
- for (const word of words) {
27643
- const at = lower.indexOf(word);
27906
+ for (const term of terms) {
27907
+ const at = lower.indexOf(term);
27644
27908
  if (at < 0)
27645
27909
  return null;
27646
- ranges.push([at, word.length]);
27910
+ ranges.push([at, term.length]);
27647
27911
  }
27648
27912
  return ranges;
27649
27913
  }
@@ -27656,8 +27920,8 @@ class ChatSearch {
27656
27920
  }
27657
27921
  async search(repoId, query) {
27658
27922
  this.d.state.requireRepo(repoId);
27659
- const words = needleWords(query);
27660
- if (words.length === 0)
27923
+ const terms = needleTerms(query);
27924
+ if (terms.length === 0)
27661
27925
  return { hits: [], truncated: false };
27662
27926
  const chats = [
27663
27927
  ...this.d.state.worktrees.filter((w) => w.repoId === repoId && w.kind === "worktree").map((w) => ({ id: w.id, archived: false, path: this.d.transcriptPath(w.id) })),
@@ -27666,7 +27930,7 @@ class ChatSearch {
27666
27930
  const read = new Set;
27667
27931
  const found = await Promise.all(chats.map(async (c) => {
27668
27932
  const rows = await this.rowsOf(c.id, c.archived, c.path, read);
27669
- return searchRows(rows, words, { worktreeId: c.id, archived: c.archived });
27933
+ return searchRows(rows, terms, { worktreeId: c.id, archived: c.archived });
27670
27934
  }));
27671
27935
  for (const key of this.said.keys())
27672
27936
  if (!read.has(key))
@@ -27946,7 +28210,7 @@ async function recentSubjects(path, defaultBr) {
27946
28210
  }
27947
28211
 
27948
28212
  // ../daemon/src/worktrees/prs.ts
27949
- var POLL_MS2 = 3 * 60000;
28213
+ var POLL_MS3 = 3 * 60000;
27950
28214
 
27951
28215
  class PrService {
27952
28216
  d;
@@ -27955,7 +28219,7 @@ class PrService {
27955
28219
  constructor(d) {
27956
28220
  this.d = d;
27957
28221
  d.hub.on("repoTick", (repoId) => this.refreshRepo(repoId));
27958
- this.timer = setInterval(() => this.refreshAll(), d.everyMs ?? POLL_MS2);
28222
+ this.timer = setInterval(() => this.refreshAll(), d.everyMs ?? POLL_MS3);
27959
28223
  this.timer.unref?.();
27960
28224
  }
27961
28225
  stop() {
@@ -28057,7 +28321,7 @@ async function mergedBranches(repoPath, defaultBr) {
28057
28321
 
28058
28322
  // ../daemon/src/worktrees/discover.ts
28059
28323
  import { createHash as createHash4 } from "crypto";
28060
- import { basename as basename6 } from "path";
28324
+ import { basename as basename7 } from "path";
28061
28325
 
28062
28326
  // ../daemon/src/git/worktrees.ts
28063
28327
  function parseWorktreeList(out) {
@@ -28146,7 +28410,7 @@ function subtractKnown(repoId, listed, known) {
28146
28410
  rows.push({
28147
28411
  id: discoveredId(g.path),
28148
28412
  repoId,
28149
- name: g.branch ?? basename6(g.path),
28413
+ name: g.branch ?? basename7(g.path),
28150
28414
  path: g.path,
28151
28415
  ...g.branch ? { branch: g.branch } : {},
28152
28416
  ...g.locked ? { locked: true } : {},
@@ -28308,9 +28572,9 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
28308
28572
  }
28309
28573
 
28310
28574
  // ../daemon/src/worktrees/service.ts
28311
- import { cpSync, existsSync as existsSync27, readFileSync as readFileSync18, rmSync as rmSync5 } from "fs";
28575
+ import { cpSync, existsSync as existsSync28, readFileSync as readFileSync18, rmSync as rmSync5 } from "fs";
28312
28576
  import { rm as rm4 } from "fs/promises";
28313
- import { dirname as dirname10, join as join24 } from "path";
28577
+ import { dirname as dirname11, join as join25 } from "path";
28314
28578
 
28315
28579
  // ../daemon/src/git/archive.ts
28316
28580
  import { rmSync as rmSync3 } from "fs";
@@ -28463,12 +28727,31 @@ async function requireClean(worktreePath) {
28463
28727
  }
28464
28728
  return null;
28465
28729
  }
28466
- async function requireMainReady(repoPath, defaultBr) {
28730
+ function overwritten(text) {
28731
+ const files = [];
28732
+ let listing = false;
28733
+ for (const line of text.split(`
28734
+ `)) {
28735
+ if (/would be overwritten by (merge|checkout):\s*$/.test(line)) {
28736
+ listing = true;
28737
+ } else if (listing && line.startsWith("\t")) {
28738
+ files.push(line.trim());
28739
+ } else {
28740
+ listing = false;
28741
+ }
28742
+ }
28743
+ return files;
28744
+ }
28745
+ function inTheWay(defaultBr, what, files) {
28746
+ const shown = files.length > 3 ? `${files.slice(0, 3).join(", ")} and ${files.length - 3} more` : files.join(", ");
28747
+ return `${defaultBr} has uncommitted files the ${what} would overwrite (${shown}): commit or stash them there first`;
28748
+ }
28749
+ async function requireMainReady(repoPath, defaultBr, clean) {
28467
28750
  const current = await git(repoPath, "branch", "--show-current");
28468
28751
  if (current.out !== defaultBr) {
28469
28752
  return { ok: false, message: `main checkout is on '${current.out}', not ${defaultBr}; switch it first` };
28470
28753
  }
28471
- if ((await statusFiles(repoPath)).length > 0) {
28754
+ if (clean && (await statusFiles(repoPath)).length > 0) {
28472
28755
  return { ok: false, message: `${defaultBr} has uncommitted changes: commit or stash them there first` };
28473
28756
  }
28474
28757
  return null;
@@ -28508,14 +28791,18 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
28508
28791
  const { ahead } = await aheadBehind(worktreePath, defaultBr);
28509
28792
  if (ahead === 0)
28510
28793
  return { ok: false, message: `nothing to merge: no commits ahead of ${defaultBr}` };
28511
- const mErr = await requireMainReady(repoPath, defaultBr);
28794
+ const mErr = await requireMainReady(repoPath, defaultBr, method === "squash");
28512
28795
  if (mErr)
28513
28796
  return mErr;
28514
28797
  if (method === "rebase") {
28515
28798
  w.step(`moving ${defaultBr} onto the branch`);
28516
28799
  const ff = await w.git(repoPath, ["merge", "--ff-only", branch]);
28517
- if (!ff.ok)
28800
+ if (!ff.ok) {
28801
+ const files = overwritten(ff.text);
28802
+ if (files.length)
28803
+ return { ok: false, message: inTheWay(defaultBr, "landing", files) };
28518
28804
  return { ok: false, message: `${branch} is not a fast-forward of ${defaultBr}: take ${defaultBr} in first` };
28805
+ }
28519
28806
  return { ok: true, message: `${defaultBr} moved onto ${branch}` };
28520
28807
  }
28521
28808
  if (method === "squash") {
@@ -28534,8 +28821,12 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
28534
28821
  }
28535
28822
  w.step(`merging into ${defaultBr}`);
28536
28823
  const m = await w.git(repoPath, ["merge", "--no-ff", "--no-edit", branch]);
28537
- if (!m.ok)
28824
+ if (!m.ok) {
28825
+ const files = overwritten(m.text);
28826
+ if (files.length)
28827
+ return { ok: false, message: inTheWay(defaultBr, "landing", files) };
28538
28828
  return mergeFailure(repoPath, m, `merge conflicts with ${defaultBr}: take ${defaultBr} in first`);
28829
+ }
28539
28830
  return { ok: true, message: `merged ${branch} into ${defaultBr}` };
28540
28831
  }
28541
28832
  async function squashMessage(worktreePath, defaultBr, suggested) {
@@ -28561,22 +28852,11 @@ async function fastForwardFetched(repoPath, defaultBr) {
28561
28852
  }
28562
28853
  if (behind === 0)
28563
28854
  return { ok: true, moved: false, message: "already up to date with origin" };
28564
- if ((await statusFiles(repoPath)).length > 0) {
28565
- return {
28566
- ok: false,
28567
- stale: "dirty",
28568
- message: `${defaultBr} has uncommitted changes: commit or stash them there first`
28569
- };
28570
- }
28571
28855
  const m = await git(repoPath, "merge", "--ff-only", "@{upstream}");
28572
28856
  if (!m.ok) {
28573
- const clobber = /overwritten by merge|would be overwritten/.test(`${m.out}
28857
+ const files = overwritten(`${m.out}
28574
28858
  ${m.err}`);
28575
- return clobber ? {
28576
- ok: false,
28577
- stale: "dirty",
28578
- message: `${defaultBr} here has edits the pull would overwrite; commit or stash them first`
28579
- } : { ok: false, stale: "diverged", message: `${defaultBr} has diverged from origin; reconcile it in a terminal` };
28859
+ return files.length ? { ok: false, stale: "dirty", message: inTheWay(defaultBr, "pull", files) } : { ok: false, stale: "diverged", message: `${defaultBr} has diverged from origin; reconcile it in a terminal` };
28580
28860
  }
28581
28861
  return { ok: true, moved: true, message: `pulled ${behind} commit(s) from origin` };
28582
28862
  }
@@ -28753,8 +29033,8 @@ function compareUrl(remoteUrl, base, branch) {
28753
29033
  }
28754
29034
 
28755
29035
  // ../daemon/src/worktrees/archive.ts
28756
- import { existsSync as existsSync25, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
28757
- import { basename as basename7, dirname as dirname9, join as join22 } from "path";
29036
+ import { existsSync as existsSync26, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "fs";
29037
+ import { basename as basename8, dirname as dirname10, join as join23 } from "path";
28758
29038
  class WorktreeArchive {
28759
29039
  dir;
28760
29040
  records = new Map;
@@ -28763,7 +29043,7 @@ class WorktreeArchive {
28763
29043
  mkdirSync7(dir, { recursive: true });
28764
29044
  for (const id of readdirSync7(dir)) {
28765
29045
  const file = this.filesOf(id).record;
28766
- if (!existsSync25(file))
29046
+ if (!existsSync26(file))
28767
29047
  continue;
28768
29048
  try {
28769
29049
  this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
@@ -28812,8 +29092,8 @@ class WorktreeArchive {
28812
29092
  planFile(id, rel) {
28813
29093
  if (!this.records.has(id) || !isPlanPath(rel))
28814
29094
  return null;
28815
- const file = join22(this.filesOf(id).plans, basename7(rel));
28816
- if (!existsSync25(file))
29095
+ const file = join23(this.filesOf(id).plans, basename8(rel));
29096
+ if (!existsSync26(file))
28817
29097
  return null;
28818
29098
  try {
28819
29099
  return readFileSync17(file, "utf8");
@@ -28823,13 +29103,13 @@ class WorktreeArchive {
28823
29103
  }
28824
29104
  }
28825
29105
  filesOf(id) {
28826
- const dir = join22(this.dir, id);
29106
+ const dir = join23(this.dir, id);
28827
29107
  return {
28828
29108
  dir,
28829
- record: join22(dir, "record.json"),
28830
- transcript: join22(dir, "transcript.jsonl"),
28831
- attachments: join22(dir, "attachments"),
28832
- plans: join22(dir, "plans")
29109
+ record: join23(dir, "record.json"),
29110
+ transcript: join23(dir, "transcript.jsonl"),
29111
+ attachments: join23(dir, "attachments"),
29112
+ plans: join23(dir, "plans")
28833
29113
  };
28834
29114
  }
28835
29115
  }
@@ -28848,12 +29128,13 @@ function summarize(r, repoId, transcript) {
28848
29128
  ...r.sessionId ? { sessionId: r.sessionId } : {},
28849
29129
  ...r.kept?.snapshot ? { uncommitted: true, ...r.kept.dirty ? { dirty: r.kept.dirty } : {} } : {},
28850
29130
  ...r.worktree.landed ? { landed: true } : {},
29131
+ ...r.worktree.lands?.length ? { landedAt: r.worktree.lands[r.worktree.lands.length - 1]?.at } : {},
28851
29132
  ...r.cost !== undefined ? { cost: r.cost } : {},
28852
29133
  ...r.auto ? { auto: r.auto } : {}
28853
29134
  };
28854
29135
  }
28855
29136
  function lastUsage(transcript) {
28856
- if (!existsSync25(transcript))
29137
+ if (!existsSync26(transcript))
28857
29138
  return null;
28858
29139
  const lines = readFileSync17(transcript, "utf8").split(`
28859
29140
  `);
@@ -28869,7 +29150,7 @@ function lastUsage(transcript) {
28869
29150
  return null;
28870
29151
  }
28871
29152
  function firstPrompt(transcript) {
28872
- if (!existsSync25(transcript))
29153
+ if (!existsSync26(transcript))
28873
29154
  return;
28874
29155
  for (const line of readFileSync17(transcript, "utf8").split(`
28875
29156
  `)) {
@@ -28884,9 +29165,9 @@ function firstPrompt(transcript) {
28884
29165
  return;
28885
29166
  }
28886
29167
  function move(from, to) {
28887
- if (!existsSync25(from))
29168
+ if (!existsSync26(from))
28888
29169
  return;
28889
- mkdirSync7(dirname9(to), { recursive: true });
29170
+ mkdirSync7(dirname10(to), { recursive: true });
28890
29171
  rmSync4(to, { recursive: true, force: true });
28891
29172
  renameSync4(from, to);
28892
29173
  }
@@ -28949,8 +29230,8 @@ class ArchivedGit {
28949
29230
  }
28950
29231
 
28951
29232
  // ../daemon/src/worktrees/spare.ts
28952
- import { existsSync as existsSync26 } from "fs";
28953
- import { basename as basename8, join as join23 } from "path";
29233
+ import { existsSync as existsSync27 } from "fs";
29234
+ import { basename as basename9, join as join24 } from "path";
28954
29235
  class SparePool {
28955
29236
  d;
28956
29237
  inflight = new Map;
@@ -28969,7 +29250,7 @@ class SparePool {
28969
29250
  if (!text.trim())
28970
29251
  return;
28971
29252
  const wt = this.d.state.worktree(boxId);
28972
- if (wt?.kind !== "spare" || !existsSync26(wt.path))
29253
+ if (wt?.kind !== "spare" || !existsSync27(wt.path))
28973
29254
  return;
28974
29255
  const agent = this.d.runtime.ensureAgent(wt).agent;
28975
29256
  if (agent.runningAgent === null)
@@ -29014,11 +29295,11 @@ class SparePool {
29014
29295
  return this.record(repoId, repo).then((wt) => this.warmUp(wt, repo, {}));
29015
29296
  }
29016
29297
  async record(repoId, repo) {
29017
- const { id, dir } = freeSlot(join23(this.d.paths.worktreesDir, repo.name));
29298
+ const { id, dir } = freeSlot(join24(this.d.paths.worktreesDir, repo.name));
29018
29299
  const wt = {
29019
29300
  id,
29020
29301
  repoId,
29021
- path: join23(this.d.paths.worktreesDir, repo.name, dir),
29302
+ path: join24(this.d.paths.worktreesDir, repo.name, dir),
29022
29303
  branch: repo.defaultBranch,
29023
29304
  kind: "spare",
29024
29305
  phase: "reserved",
@@ -29033,7 +29314,7 @@ class SparePool {
29033
29314
  const repoId = repo.id;
29034
29315
  let cut = () => {};
29035
29316
  const added = (async () => {
29036
- if (existsSync26(wt.path))
29317
+ if (existsSync27(wt.path))
29037
29318
  return;
29038
29319
  await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
29039
29320
  })();
@@ -29081,7 +29362,7 @@ class SparePool {
29081
29362
  }
29082
29363
  log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
29083
29364
  this.d.carryDraft?.(wt.id, wt.repoId);
29084
- if (existsSync26(wt.path)) {
29365
+ if (existsSync27(wt.path)) {
29085
29366
  await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
29086
29367
  return;
29087
29368
  }
@@ -29138,12 +29419,12 @@ class SparePool {
29138
29419
  const running = this.inflight.get(repoId);
29139
29420
  await running?.added.catch(() => {});
29140
29421
  running?.cut();
29141
- if (wt.kind !== "spare" || !existsSync26(wt.path))
29422
+ if (wt.kind !== "spare" || !existsSync27(wt.path))
29142
29423
  return null;
29143
29424
  }
29144
29425
  await this.refreshing.get(repoId);
29145
29426
  const repo = this.d.state.requireRepo(repoId);
29146
- const branch = `toyon/${basename8(wt.path)}`;
29427
+ const branch = `toyon/${basename9(wt.path)}`;
29147
29428
  await withRepoLock(repo.path, () => gitOrThrow(wt.path, "switch", "-c", branch));
29148
29429
  wt.kind = "worktree";
29149
29430
  delete wt.phase;
@@ -29526,8 +29807,8 @@ class WorktreeService {
29526
29807
  if (!await isGitRepo(repo.path)) {
29527
29808
  throw new UserError(`git no longer reads ${repo.name} as a checkout, so there is nothing to start a worktree from. If \`git config core.bare\` says true there, \`git config --unset core.bare\` puts it back.`);
29528
29809
  }
29529
- const { id, dir } = freeSlot(join24(this.d.paths.worktreesDir, repo.name));
29530
- const wtPath = join24(this.d.paths.worktreesDir, repo.name, dir);
29810
+ const { id, dir } = freeSlot(join25(this.d.paths.worktreesDir, repo.name));
29811
+ const wtPath = join25(this.d.paths.worktreesDir, repo.name, dir);
29531
29812
  const branch = `toyon/${dir}`;
29532
29813
  await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--no-track", "-b", branch, wtPath, baseOf(repo)));
29533
29814
  const wt = {
@@ -29593,7 +29874,7 @@ class WorktreeService {
29593
29874
  };
29594
29875
  const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
29595
29876
  const restored = (carried.ok ? carried.out.split(`
29596
- `).filter(Boolean) : []).filter((p) => !existsSync27(join24(wt.path, p)));
29877
+ `).filter(Boolean) : []).filter((p) => !existsSync28(join25(wt.path, p)));
29597
29878
  const applied = await git(wt.path, "stash", "apply", sha);
29598
29879
  if (applied.ok) {
29599
29880
  const ref = await entry();
@@ -29629,9 +29910,9 @@ class WorktreeService {
29629
29910
  const branch = kind === "pr" ? `pr/${number}` : ref;
29630
29911
  const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
29631
29912
  let slug = branchSlug(title) || "branch";
29632
- if (existsSync27(join24(this.d.paths.worktreesDir, repo.name, slug)))
29913
+ if (existsSync28(join25(this.d.paths.worktreesDir, repo.name, slug)))
29633
29914
  slug = `${slug}-${shortId().slice(0, 4)}`;
29634
- const wtPath = join24(this.d.paths.worktreesDir, repo.name, slug);
29915
+ const wtPath = join25(this.d.paths.worktreesDir, repo.name, slug);
29635
29916
  await withRepoLock(repo.path, async () => {
29636
29917
  const out = (await listWorktrees(repo.path)).find((w) => w.branch === branch);
29637
29918
  if (out)
@@ -29877,7 +30158,7 @@ class WorktreeService {
29877
30158
  }
29878
30159
  async teardown(wt, repo) {
29879
30160
  const cmds = repo.config.teardown ?? [];
29880
- if (cmds.length === 0 || !existsSync27(wt.path))
30161
+ if (cmds.length === 0 || !existsSync28(wt.path))
29881
30162
  return;
29882
30163
  for (const cmd of cmds) {
29883
30164
  const code = await runSetup(cmd, wt.path, (line) => this.d.hub.emit("log", wt.id, "setup", line), worktreeEnv(wt, repo), { timeoutMs: timeoutFor(repo.config, "setup") });
@@ -29886,7 +30167,7 @@ class WorktreeService {
29886
30167
  }
29887
30168
  }
29888
30169
  async keep(repo, wt) {
29889
- const index = join24(this.d.paths.archiveDir, `${wt.id}.index`);
30170
+ const index = join25(this.d.paths.archiveDir, `${wt.id}.index`);
29890
30171
  const kept = await keepState(repo.path, wt.path, archiveRef(wt.id), index) ?? await this.keepUnlinked(repo, wt);
29891
30172
  if (!kept)
29892
30173
  log.warn(wt.id, "could not keep its git state");
@@ -29918,16 +30199,16 @@ class WorktreeService {
29918
30199
  throw new Error(`git worktree remove --force ${wt.path} failed (${r.exit}): ${r.err}`);
29919
30200
  if (await deleteTree(wt.path))
29920
30201
  return;
29921
- await run("chmod", ["-R", "u+w", wt.path], dirname10(wt.path));
30202
+ await run("chmod", ["-R", "u+w", wt.path], dirname11(wt.path));
29922
30203
  if (!await deleteTree(wt.path))
29923
30204
  log.warn(wt.id, `its directory could not be deleted and stays at ${wt.path}`);
29924
30205
  }
29925
30206
  plansScratch(worktreeId) {
29926
- return join24(this.d.paths.archiveDir, `${worktreeId}.plans`);
30207
+ return join25(this.d.paths.archiveDir, `${worktreeId}.plans`);
29927
30208
  }
29928
30209
  keepPlans(wt) {
29929
- const from = join24(wt.path, PLANS_DIR);
29930
- if (!existsSync27(from))
30210
+ const from = join25(wt.path, PLANS_DIR);
30211
+ if (!existsSync28(from))
29931
30212
  return;
29932
30213
  try {
29933
30214
  cpSync(from, this.plansScratch(wt.id), { recursive: true });
@@ -30015,7 +30296,7 @@ class WorktreeService {
30015
30296
  }
30016
30297
  archivedAttachment(archiveId, file) {
30017
30298
  const files = this.archive.chatFiles(archiveId);
30018
- return files && isAttachmentFile(file) ? join24(files.attachments, file) : null;
30299
+ return files && isAttachmentFile(file) ? join25(files.attachments, file) : null;
30019
30300
  }
30020
30301
  archivedGit(archiveId) {
30021
30302
  const rec = this.archive.get(archiveId);
@@ -30058,7 +30339,7 @@ class WorktreeService {
30058
30339
  throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
30059
30340
  if (rec.auto)
30060
30341
  log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
30061
- const path = existsSync27(old.path) ? join24(dirname10(old.path), freeSlot(dirname10(old.path)).dir) : old.path;
30342
+ const path = existsSync28(old.path) ? join25(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
30062
30343
  const branch = await withRepoLock(repo.path, async () => {
30063
30344
  if (!await commitOf(repo.path, archiveRef(old.id))) {
30064
30345
  throw new UserError(`${old.title}'s kept commits are gone from git`);
@@ -30095,7 +30376,7 @@ class WorktreeService {
30095
30376
  ...createdBy ? { createdBy } : {}
30096
30377
  };
30097
30378
  try {
30098
- this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join24(path, PLANS_DIR) });
30379
+ this.archive.take(old.id, { ...this.chatFiles(old.id), plans: join25(path, PLANS_DIR) });
30099
30380
  } catch (e) {
30100
30381
  log.warn(old.id, "could not move its chat back out of the archive", e);
30101
30382
  }
@@ -30106,6 +30387,13 @@ class WorktreeService {
30106
30387
  log.warn(old.id, `could not drop its archive ref: ${dropped.err}`);
30107
30388
  this.countsCache.delete(wt.id);
30108
30389
  this.launch(wt, repo, repo.path);
30390
+ this.d.runtime.ensureAgent(wt).agent.note({
30391
+ type: "restored",
30392
+ archivedAt: rec.archivedAt,
30393
+ branch,
30394
+ uncommitted: !!kept.snapshot,
30395
+ ts: Date.now()
30396
+ });
30109
30397
  if (message)
30110
30398
  this.d.runtime.ensureAgent(wt).agent.send(message.text, { attachments: message.attachments });
30111
30399
  this.d.hub.emit("archiveChanged", repo.id);
@@ -30117,12 +30405,12 @@ class WorktreeService {
30117
30405
  return;
30118
30406
  const repo = this.repoOf(rec);
30119
30407
  const repoPath = repo?.path ?? rec.repoPath;
30120
- if (rec.kept && existsSync27(repoPath)) {
30408
+ if (rec.kept && existsSync28(repoPath)) {
30121
30409
  const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
30122
30410
  if (!r.ok)
30123
30411
  log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
30124
30412
  }
30125
- if (rec.worktree.lands?.length && existsSync27(repoPath))
30413
+ if (rec.worktree.lands?.length && existsSync28(repoPath))
30126
30414
  await dropLandRefs(repoPath, archiveId);
30127
30415
  this.archive.delete(archiveId);
30128
30416
  this.d.drafts?.drop(archiveId);
@@ -30222,9 +30510,9 @@ class WorktreeService {
30222
30510
  }
30223
30511
  }
30224
30512
  async mirrorBaseIgnore(repo) {
30225
- const src = join24(repo.path, ".gitignore");
30226
- const tracked = existsSync27(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
30227
- const mirror = existsSync27(src) && !tracked;
30513
+ const src = join25(repo.path, ".gitignore");
30514
+ const tracked = existsSync28(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
30515
+ const mirror = existsSync28(src) && !tracked;
30228
30516
  const lines = mirror ? readFileSync18(src, "utf8").split(`
30229
30517
  `).filter((l) => l.trim() !== "") : [];
30230
30518
  await excludeBlock(repo.path, BASE_IGNORE, lines);
@@ -30237,9 +30525,9 @@ class WorktreeService {
30237
30525
  await this.d.cache.restore(wt, repo, (line) => this.d.hub.emit("log", wt.id, "setup", line));
30238
30526
  run.finish(0);
30239
30527
  }
30240
- const srcNm = join24(depsSource, "node_modules");
30241
- const dstNm = join24(wt.path, "node_modules");
30242
- if (existsSync27(srcNm) && !existsSync27(dstNm)) {
30528
+ const srcNm = join25(depsSource, "node_modules");
30529
+ const dstNm = join25(wt.path, "node_modules");
30530
+ if (existsSync28(srcNm) && !existsSync28(dstNm)) {
30243
30531
  const r = await cloneTree(srcNm, dstNm);
30244
30532
  if (r.ok)
30245
30533
  this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
@@ -30247,10 +30535,10 @@ class WorktreeService {
30247
30535
  log.warn(wt.id, "could not copy node_modules from the base checkout", r.error);
30248
30536
  }
30249
30537
  for (const f of LOCAL_CONFIG_FILES) {
30250
- const src = join24(depsSource, f);
30251
- if (!existsSync27(src) || existsSync27(join24(wt.path, f)))
30538
+ const src = join25(depsSource, f);
30539
+ if (!existsSync28(src) || existsSync28(join25(wt.path, f)))
30252
30540
  continue;
30253
- const r = await run("cp", [src, join24(wt.path, f)], wt.path);
30541
+ const r = await run("cp", [src, join25(wt.path, f)], wt.path);
30254
30542
  if (r.ok)
30255
30543
  this.d.hub.emit("log", wt.id, "setup", `copied ${f}`);
30256
30544
  else
@@ -30719,12 +31007,6 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
30719
31007
  const repo = this.d.state.requireRepo(wt.repoId);
30720
31008
  const w = { step: (name) => this.step(worktreeId, name), git: UNWATCHED.git };
30721
31009
  return this.ship(worktreeId, "pull-main", async () => {
30722
- if ((await statusFiles(repo.path)).length > 0) {
30723
- return {
30724
- ok: false,
30725
- message: `${repo.defaultBranch} has uncommitted changes: commit or stash them there first`
30726
- };
30727
- }
30728
31010
  const result = await this.trunk.pull(repo.id, w);
30729
31011
  if (result.ok) {
30730
31012
  this.invalidateCounts();
@@ -31153,7 +31435,7 @@ class ArchiveSweep {
31153
31435
  // ../daemon/src/index.ts
31154
31436
  process.on("unhandledRejection", (e) => log.error("daemon", "unhandled rejection", e));
31155
31437
  process.on("uncaughtException", (e) => log.error("daemon", "uncaught exception", e));
31156
- var here = dirname11(fileURLToPath(import.meta.url));
31438
+ var here = dirname12(fileURLToPath(import.meta.url));
31157
31439
  var {
31158
31440
  shellDist: SHELL_DIST,
31159
31441
  bridgeJs: BRIDGE_JS,
@@ -31291,9 +31573,12 @@ var restarter = new Restarter({
31291
31573
  hub,
31292
31574
  state,
31293
31575
  runtime,
31294
- refusal: () => {
31576
+ refusal: async () => {
31295
31577
  const can = restartable();
31296
- return can.ok ? null : can.reason;
31578
+ if (!can.ok)
31579
+ return can.reason;
31580
+ const runs = await replacementRuns();
31581
+ return runs.ok ? null : runs.reason;
31297
31582
  },
31298
31583
  go: () => {
31299
31584
  setTimeout(() => fireAndForget("daemon", shutdown("restart", { respawn: true }), "restart"), 100);
@@ -31309,6 +31594,7 @@ var update = new UpdateService({
31309
31594
  restarter,
31310
31595
  installed: async () => installedPackage ? readVersion(installedPackage) : null,
31311
31596
  latest: latestVersion,
31597
+ registry: npmRegistry,
31312
31598
  command: (version) => installCommand(method, version),
31313
31599
  install: runInstall,
31314
31600
  busy: () => runtime.anyBusy(),
@@ -31440,7 +31726,12 @@ async function shutdown(signal, opts = {}) {
31440
31726
  if (opts.respawn) {
31441
31727
  await Bun.sleep(250);
31442
31728
  log.info("daemon", "starting the replacement");
31443
- respawn(paths.logFile);
31729
+ try {
31730
+ respawn(paths.logFile);
31731
+ } catch (e) {
31732
+ log.error("daemon", "the replacement did not start; run `toyon` to start it again", e);
31733
+ process.exit(1);
31734
+ }
31444
31735
  }
31445
31736
  process.exit(0);
31446
31737
  }