toyon 0.7.0 → 0.8.1

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