toyon 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/cli.js +64 -14
  2. package/dist/daemon.js +781 -190
  3. package/dist/shell/assets/{Editor-DAJni9OF.js → Editor-DL0q1Bu5.js} +161 -161
  4. package/dist/shell/assets/{XTerm-dug6M6yd.js → XTerm-C9YH4Lh2.js} +1 -1
  5. package/dist/shell/assets/{cssMode-B5iiwdXn.js → cssMode-Et22hXpa.js} +1 -1
  6. package/dist/shell/assets/{freemarker2-CdVcBNE9.js → freemarker2-Clz20PcM.js} +1 -1
  7. package/dist/shell/assets/{grammar-Cfxx1yRn.js → grammar-CPvdB3az.js} +1 -1
  8. package/dist/shell/assets/{handlebars-CW5EFZC2.js → handlebars-B4nvXAx8.js} +1 -1
  9. package/dist/shell/assets/{html-B8gP1xu-.js → html-DPIGgBLK.js} +1 -1
  10. package/dist/shell/assets/{htmlMode-C6I99y8u.js → htmlMode-C2LaVuEX.js} +1 -1
  11. package/dist/shell/assets/index-2QusTgw-.js +262 -0
  12. package/dist/shell/assets/index-TaJLoLSd.css +1 -0
  13. package/dist/shell/assets/{javascript-C_3_LFln.js → javascript-CnwJfTbN.js} +1 -1
  14. package/dist/shell/assets/{jsonMode-CD5xJEUk.js → jsonMode-8znCTiqR.js} +1 -1
  15. package/dist/shell/assets/{liquid-CJw93Zxn.js → liquid-Bdm-1CUh.js} +1 -1
  16. package/dist/shell/assets/{lspLanguageFeatures-wMR368iN.js → lspLanguageFeatures-B1DMU7yH.js} +1 -1
  17. package/dist/shell/assets/{mdx-BJ6yMgc3.js → mdx-Ktcqo9Jf.js} +1 -1
  18. package/dist/shell/assets/{python-BWGkxFUr.js → python-QX--d8LR.js} +1 -1
  19. package/dist/shell/assets/{razor-BV9pdU1A.js → razor-C0LrXteG.js} +1 -1
  20. package/dist/shell/assets/{tsMode-CN8OfYAX.js → tsMode-BtYCp6db.js} +1 -1
  21. package/dist/shell/assets/{xml-DBGLqvw7.js → xml-BXbaQ9MG.js} +1 -1
  22. package/dist/shell/assets/{yaml-dH0C6Dth.js → yaml-C6FhaU_C.js} +1 -1
  23. package/dist/shell/index.html +7 -2
  24. package/package.json +1 -1
  25. package/dist/shell/assets/index-BRSL9cyD.css +0 -1
  26. package/dist/shell/assets/index-D_PsyNmp.js +0 -266
package/dist/daemon.js CHANGED
@@ -58,6 +58,7 @@ function applyLog(lines, proc, line, retract = 0) {
58
58
  return out;
59
59
  return [...out, { proc, line }];
60
60
  }
61
+ var KEEP_AWAKE_MODES = ["use", "always", "off"];
61
62
 
62
63
  // ../shared/src/worktree-caps.ts
63
64
  function isMain(wt) {
@@ -119,7 +120,7 @@ function archiveReason(wt, f) {
119
120
  return null;
120
121
  if (unitsAhead(wt, f.rows) < ARCHIVE_KEEP_RECENT)
121
122
  return null;
122
- const what = lost ? "a sibling landed" : wt.landed ? "landed" : "no changes";
123
+ const what = lost ? "sibling landed" : wt.landed ? "landed" : "no changes";
123
124
  return `${what}, ${UNOPENED}${duration(f.afterMs)}`;
124
125
  }
125
126
  var UNOPENED = "not opened in ";
@@ -6473,12 +6474,25 @@ function describeManaged(p) {
6473
6474
  return out;
6474
6475
  }
6475
6476
 
6477
+ // ../shared/src/output.ts
6478
+ var CONTEXT_CHARS = 6000;
6479
+ var HEAD_CHARS = 1500;
6480
+ function clipOutput(text) {
6481
+ if (text.length <= CONTEXT_CHARS)
6482
+ return text;
6483
+ const cut = text.length - CONTEXT_CHARS;
6484
+ return `${text.slice(0, HEAD_CHARS)}
6485
+ [${cut} characters cut here]
6486
+ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
6487
+ }
6488
+
6476
6489
  // ../shared/src/pair.ts
6477
6490
  var PAIR_TTL_MS = 120000;
6478
6491
  var pairLink = (host, code) => `https://${host}/#pair=${code}`;
6492
+ var isTailnetName = (host) => host.endsWith(".ts.net");
6479
6493
 
6480
6494
  // ../shared/src/protocol/limits.ts
6481
- var PROTOCOL_VERSION = 85;
6495
+ var PROTOCOL_VERSION = 89;
6482
6496
  var FILE_MAX_CHARS = 5000000;
6483
6497
  var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
6484
6498
  var IMAGE_MAX_EDGE = 2576;
@@ -6784,7 +6798,9 @@ var clientMsgSchema = discriminatedUnion("t", [
6784
6798
  object({
6785
6799
  t: literal("adopt-worktree"),
6786
6800
  clientId: string2().max(64).optional(),
6787
- worktreeId: id
6801
+ worktreeId: id,
6802
+ message: object({ text: prose, attachments }).optional(),
6803
+ boxId
6788
6804
  }),
6789
6805
  object({ t: literal("git-status"), worktreeId: id }),
6790
6806
  object({
@@ -6830,6 +6846,8 @@ var clientMsgSchema = discriminatedUnion("t", [
6830
6846
  object({ t: literal("design-scan"), worktreeId: id }),
6831
6847
  object({ t: literal("discard-files"), worktreeId: id, paths: array(relPath).min(1).max(500) }),
6832
6848
  object({ t: literal("judge"), worktreeId: id, note: string2().max(500).optional() }),
6849
+ object({ t: literal("fix-preview"), worktreeId: id, clientId: string2().max(64).optional() }),
6850
+ object({ t: literal("fix"), worktreeId: id, toolId: string2().min(1).max(200) }),
6833
6851
  object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
6834
6852
  object({ t: literal("stop-agent"), worktreeId: id }),
6835
6853
  object({ t: literal("seen"), worktreeId: id }),
@@ -6888,6 +6906,7 @@ var clientMsgSchema = discriminatedUnion("t", [
6888
6906
  object({ t: literal("set-theme"), prefs: themePrefsSchema }),
6889
6907
  object({ t: literal("zone"), tz: string2().max(100) }),
6890
6908
  object({ t: literal("set-default-agent"), agent: id }),
6909
+ object({ t: literal("set-keep-awake"), mode: _enum(KEEP_AWAKE_MODES) }),
6891
6910
  object({ t: literal("install-agent"), agent: id }),
6892
6911
  object({
6893
6912
  t: literal("agent-auth"),
@@ -8201,7 +8220,7 @@ function safeUser() {
8201
8220
  // ../daemon/package.json
8202
8221
  var package_default = {
8203
8222
  name: "@toyon/daemon",
8204
- version: "0.9.0",
8223
+ version: "0.10.0",
8205
8224
  private: true,
8206
8225
  type: "module",
8207
8226
  main: "src/index.ts",
@@ -13214,8 +13233,9 @@ async function runLive(cmd, args, cwd, opts = {}) {
13214
13233
  }
13215
13234
  }
13216
13235
  function watchHooks(stream, onHook) {
13236
+ let failed;
13217
13237
  if (!stream)
13218
- return;
13238
+ return () => failed;
13219
13239
  const dec = new TextDecoder;
13220
13240
  let buf = "";
13221
13241
  const open = new Map;
@@ -13236,11 +13256,15 @@ function watchHooks(stream, onHook) {
13236
13256
  if (ev.event === "child_start" && ev.child_class === "hook" && ev.hook_name) {
13237
13257
  open.set(ev.child_id, ev.hook_name);
13238
13258
  onHook(ev.hook_name);
13239
- } else if (ev.event === "child_exit" && open.delete(ev.child_id)) {
13259
+ } else if (ev.event === "child_exit" && open.has(ev.child_id)) {
13260
+ if (ev.code)
13261
+ failed = open.get(ev.child_id);
13262
+ open.delete(ev.child_id);
13240
13263
  onHook(open.size > 0 ? [...open.values()].pop() : undefined);
13241
13264
  }
13242
13265
  }
13243
13266
  });
13267
+ return () => failed;
13244
13268
  }
13245
13269
  function runWatched(cmd, args, cwd, opts = {}) {
13246
13270
  const started = Date.now();
@@ -13285,8 +13309,7 @@ function runWatched(cmd, args, cwd, opts = {}) {
13285
13309
  };
13286
13310
  if (child.pid)
13287
13311
  opts.onSpawn?.(child.pid, kill);
13288
- if (opts.onHook)
13289
- watchHooks(child.stdio[3], opts.onHook);
13312
+ const failedHook = opts.onHook ? watchHooks(child.stdio[3], opts.onHook) : undefined;
13290
13313
  if (opts.timeoutMs) {
13291
13314
  timer = setTimeout(() => {
13292
13315
  timedOut = true;
@@ -13322,7 +13345,8 @@ function runWatched(cmd, args, cwd, opts = {}) {
13322
13345
  err: err.join("").trim(),
13323
13346
  exit,
13324
13347
  text,
13325
- ...timedOut && opts.timeoutMs ? { ceilingMs: opts.timeoutMs } : {}
13348
+ ...timedOut && opts.timeoutMs ? { ceilingMs: opts.timeoutMs } : {},
13349
+ ...failedHook?.() ? { hook: failedHook() } : {}
13326
13350
  });
13327
13351
  });
13328
13352
  });
@@ -14051,6 +14075,12 @@ ${body}
14051
14075
  return parts.join(`
14052
14076
  `);
14053
14077
  }
14078
+ function unformatOutput(output) {
14079
+ if (!output.startsWith("```\n"))
14080
+ return "";
14081
+ const close = output.lastIndexOf("\n```");
14082
+ return close < 4 ? "" : output.slice(4, close);
14083
+ }
14054
14084
 
14055
14085
  // ../daemon/src/agent/policy.ts
14056
14086
  import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
@@ -14159,6 +14189,7 @@ var SYSTEM_APPEND = [
14159
14189
  "Never run `git commit` unless the user explicitly asks you to, and never offer to, through the question tool or otherwise; leave changes uncommitted for the user to review and commit themselves.",
14160
14190
  "Keep the scope tight: do the asked task well, then stop. A follow-up the user might want later is a sentence in your reply, not a question.",
14161
14191
  "When the work is done and the only thing left is whether to do something you recommend, do not end your turn on a prose offer waiting for a yes. Ask it with your question tool when you have one: one question, two options with the recommended one first, each saying what it changes, and typed text allowed so the answer can carry instructions. The card is the first thing the user reads, before any prose of yours, so the question itself states what you propose and why in a sentence or two; a question that is only the yes-or-no leaves them guessing what it is. Ask this way only where you would otherwise have stopped and waited; never to confirm work already asked for.",
14192
+ "An answer to a question of yours can carry typed text beside the option picked, and the text outranks the pick. Text that doubts the pick, asks you something or says the user cannot decide is not a yes: answer it in prose and change nothing until they have decided. Text that only adds detail to the pick is an instruction for carrying it out.",
14162
14193
  "When referencing a file in chat, use a Markdown link whose target is its absolute path, with an optional `:line`; Toyon opens it in the built-in editor, whether it is in this worktree or anywhere else on this machine. A folder, a URL route or a command is not a file: name it in backticks, never as a link.",
14163
14194
  "`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists.",
14164
14195
  'Toyon previews the project in a browser panel by running the commands in its settings file, .toyon/settings.json (or toyon.json at the repo root, in a repo that keeps it there), shaped like {"setup": ["bun install"], "run": {"web": "bun run dev --port $PORT"}, "check": "bun run check", "land": {"route": "merge"}}: setup runs once in every new worktree, teardown (optional) runs once when a worktree is removed, every command in run keeps running and must listen on $PORT, which toyon sets differently for each worktree, check (optional) runs after each of your turns and must pass before the work can land, and land (optional) says how the user lands work on main: route "merge" (onto main here), "push" (onto main here, then pushed) or "pr" (a pull request); method "merge", "squash" or "rebase" for how the commits arrive on main; automerge true when GitHub should merge the pull request itself; timeouts (optional) gives setup, check and commit each a ceiling as a duration like "30m" (ten minutes each when unset), for a suite that runs longer. A settings.local.json beside it (toyon.local.json at the root) holds one person\'s overrides and is never committed. Both files may carry comments and trailing commas; leave any you find.',
@@ -14166,6 +14197,113 @@ var SYSTEM_APPEND = [
14166
14197
  "When you scaffold a project, write its .gitignore (dependencies, build output, local env files) before installing anything, so an install never leaves thousands of untracked files for the user to wade through or commit.",
14167
14198
  "When you scaffold a project or change how it installs or starts, finish by updating the settings file Toyon already reads, or writing .toyon/settings.json when there is none, so the preview can run it."
14168
14199
  ].join(" ");
14200
+ function failedRun(f) {
14201
+ switch (f.kind) {
14202
+ case "hook":
14203
+ case "check":
14204
+ case "command":
14205
+ return f;
14206
+ case "conflict":
14207
+ return f.step;
14208
+ case "preview":
14209
+ return;
14210
+ }
14211
+ }
14212
+ function fixPrompt(f) {
14213
+ switch (f.kind) {
14214
+ case "hook":
14215
+ return hookFixPrompt(f.hook);
14216
+ case "conflict":
14217
+ return conflictFixPrompt(f.base, f.how, !!f.step);
14218
+ case "check":
14219
+ return checkFixPrompt(f.command);
14220
+ case "command":
14221
+ return commandFixPrompt(f.command);
14222
+ case "preview":
14223
+ return procFixPrompt(f.procs, f.log);
14224
+ }
14225
+ }
14226
+ var WHY_COMMAND_CHARS = 60;
14227
+ function fixWhy(f) {
14228
+ switch (f.kind) {
14229
+ case "hook":
14230
+ return `the ${f.hook} hook refused the ${f.hook.includes("push") ? "push" : "commit"}`;
14231
+ case "conflict":
14232
+ return f.how === "rebase" ? `the branch needs a rebase onto ${f.base}` : `the branch needs ${f.base} merged in`;
14233
+ case "check":
14234
+ return "the check failed";
14235
+ case "command": {
14236
+ const line = f.command.split(`
14237
+ `)[0] ?? "";
14238
+ return `\`${line.length > WHY_COMMAND_CHARS ? `${line.slice(0, WHY_COMMAND_CHARS)}...` : line}\` failed`;
14239
+ }
14240
+ case "preview":
14241
+ return "the dev server is not reachable";
14242
+ }
14243
+ }
14244
+ function failureContext(f) {
14245
+ const run = failedRun(f);
14246
+ if (!run)
14247
+ return;
14248
+ const shown = clipOutput(run.text.replace(/\n+$/, ""));
14249
+ const what = f.kind === "command" ? "The command that failed" : "What Toyon ran";
14250
+ return `${what}, and what it printed:
14251
+ $ ${run.command}${shown ? `
14252
+ ${shown}` : `
14253
+ (it printed nothing)`}`;
14254
+ }
14255
+ function hookFixPrompt(hook) {
14256
+ return [
14257
+ `The ${hook} hook refused that, and what it printed is attached below.`,
14258
+ "Fix what it complains about, inside this worktree.",
14259
+ "Do not skip the hook, do not edit it or its configuration to get past it, and do not commit: Toyon runs the check when your turn ends, and the user lands from there."
14260
+ ].join(" ");
14261
+ }
14262
+ function conflictFixPrompt(base, how, shown) {
14263
+ const ask = how === "rebase" ? `Rebase this branch onto ${base} and resolve the conflicts, keeping any uncommitted changes, then verify the app still works.` : `Merge ${base} into this branch, resolve the conflicts and commit the merge, then verify the app still works.`;
14264
+ return shown ? `${ask} What git printed when Toyon tried is attached below.` : ask;
14265
+ }
14266
+ function checkFixPrompt(command) {
14267
+ return [
14268
+ `The repo's check, \`${command}\`, failed, and what it printed is attached below.`,
14269
+ "Fix what it reports, inside this worktree.",
14270
+ "Do not weaken the check or its configuration to get past it. Toyon runs it again when your turn ends."
14271
+ ].join(" ");
14272
+ }
14273
+ function commandFixPrompt(command) {
14274
+ return [
14275
+ `The user ran \`${command}\` and it failed; what it printed is attached below.`,
14276
+ "Find the cause and fix it, inside this worktree.",
14277
+ "If the cause is not in this worktree, say what it is instead of changing anything."
14278
+ ].join(" ");
14279
+ }
14280
+ var PROC_TAIL = 40;
14281
+ function procFixPrompt(procs, log) {
14282
+ const bad = procs.filter((p) => p.status === "crashed" || p.status === "unreachable");
14283
+ const lines = bad.map((p) => `- \`${p.name}\`: \`${p.command}\`, started with PORT=${p.port}. ${procDiagnosis(p)}`);
14284
+ const tail = log.slice(-PROC_TAIL).map((l) => `[${l.proc}] ${l.line}`).join(`
14285
+ `);
14286
+ return [
14287
+ "The dev server in this worktree is not reachable, so the preview is empty.",
14288
+ "",
14289
+ ...lines,
14290
+ "",
14291
+ tail ? `Last output:
14292
+ \`\`\`
14293
+ ${tail}
14294
+ \`\`\`` : "It produced no output.",
14295
+ "",
14296
+ "Find the cause and fix it. The command must run in the foreground and listen on the port in the PORT environment variable, which Toyon sets differently for each worktree. If the tool takes its port from a flag instead (Vite does), make the app read PORT, for Vite `server.port: Number(process.env.PORT)` with `strictPort: true`, or add the flag to the start command in toyon's settings file. When your turn ends toyon restarts the process and checks again."
14297
+ ].join(`
14298
+ `);
14299
+ }
14300
+ function procDiagnosis(p) {
14301
+ if (p.detail)
14302
+ return p.detail;
14303
+ if (p.status === "crashed")
14304
+ return p.exitCode != null ? `It exited with code ${p.exitCode}.` : "It crashed.";
14305
+ return "It never answered on that port.";
14306
+ }
14169
14307
  function ambientBlock(paragraphs) {
14170
14308
  const said = paragraphs.filter((p) => !!p);
14171
14309
  if (said.length === 0)
@@ -14185,7 +14323,7 @@ function previewContext(p) {
14185
14323
  line = "Toyon is setting this worktree up and starts the preview when that is done; the next message will say where it answers.";
14186
14324
  break;
14187
14325
  case "parked":
14188
- line = "The preview is off: this worktree came back from the archive, and Toyon starts its dev servers when you first write a file or when the user asks for them. Nothing answers until then.";
14326
+ line = "The preview is off: Toyon has not started this worktree's dev servers, and starts them when you first write a file or when the user asks for them. Nothing answers until then.";
14189
14327
  break;
14190
14328
  case "starting":
14191
14329
  line = `Toyon is starting the preview; it answers${at} once it is up.`;
@@ -14822,12 +14960,18 @@ function release(toolId, memo) {
14822
14960
  memo.writing = false;
14823
14961
  return memo.ended ? null : { type: "tool-update", toolId, ...held };
14824
14962
  }
14963
+ function preempted(memos) {
14964
+ for (const memo of memos.values()) {
14965
+ if (!memo.ended && memo.writing && !memo.parent)
14966
+ memo.cut = true;
14967
+ }
14968
+ }
14825
14969
  function abandoned(memos, parent, opening = false) {
14826
14970
  const out = [];
14827
14971
  for (const [toolId, memo] of memos) {
14828
14972
  if (memo.ended || !memo.writing || memo.parent !== parent)
14829
14973
  continue;
14830
- if (opening && memo.held) {
14974
+ if (opening && memo.held && !memo.cut) {
14831
14975
  const sent = release(toolId, memo);
14832
14976
  if (sent)
14833
14977
  out.push(sent);
@@ -15107,6 +15251,7 @@ class AcpSession {
15107
15251
  held = false;
15108
15252
  conn = null;
15109
15253
  connecting = null;
15254
+ opening = null;
15110
15255
  live = null;
15111
15256
  reaper = null;
15112
15257
  own = null;
@@ -15243,13 +15388,18 @@ class AcpSession {
15243
15388
  send(text, opts = {}) {
15244
15389
  if (this.stopped)
15245
15390
  return log.warn(this.d.worktreeId, "send after close dropped");
15246
- for (const ask of [...this.asks.values()])
15247
- ask.settle("skipped");
15391
+ if (!opts.asked)
15392
+ for (const ask of [...this.asks.values()])
15393
+ ask.settle("skipped");
15248
15394
  this.post(text, opts);
15249
15395
  }
15250
15396
  post(text, opts = {}) {
15251
- const { context, attachments } = opts;
15397
+ const { context, attachments, asked } = opts;
15252
15398
  const item = { text, context, ...attachments?.length ? { attachments } : {} };
15399
+ if (asked) {
15400
+ this.emit({ type: "fix-asked", text, ts: Date.now(), ...asked });
15401
+ item.recorded = { attachments: [] };
15402
+ }
15253
15403
  this.d.attachments.uploads.hold(uploadIds(attachments));
15254
15404
  if ((this.running || this.own) && !this.interrupted && !this.held && this.live?.conn.steering) {
15255
15405
  return fireAndForget(this.d.worktreeId, this.steer(item), "agent steer");
@@ -15305,7 +15455,10 @@ class AcpSession {
15305
15455
  }
15306
15456
  this.clearReaper();
15307
15457
  log.warn(this.d.worktreeId, "steered message started a turn of the agent's own");
15458
+ return;
15308
15459
  }
15460
+ if (this.live === live)
15461
+ preempted(live.tools);
15309
15462
  }
15310
15463
  unsteer(item) {
15311
15464
  const at = this.steered.indexOf(item);
@@ -15609,8 +15762,18 @@ class AcpSession {
15609
15762
  }
15610
15763
  async ensureLive(why) {
15611
15764
  const conn = await this.ensureConn(why);
15765
+ if (this.opening?.conn === conn)
15766
+ return this.opening.live;
15612
15767
  if (this.live?.conn === conn)
15613
15768
  return this.live;
15769
+ const live = this.openLive(conn).finally(() => {
15770
+ if (this.opening?.live === live)
15771
+ this.opening = null;
15772
+ });
15773
+ this.opening = { conn, live };
15774
+ return live;
15775
+ }
15776
+ async openLive(conn) {
15614
15777
  const additionalDirectories = conn.bounds.gitDir ? [conn.bounds.gitDir] : [];
15615
15778
  let sessionId = this.d.getSessionId();
15616
15779
  let modes;
@@ -15651,7 +15814,7 @@ class AcpSession {
15651
15814
  this.emit({ type: "session-info", sessionId, ...currentValues(options) });
15652
15815
  this.learn(options);
15653
15816
  const modeOption = modes ? null : readModeOption(configOptions);
15654
- this.live = {
15817
+ const live = {
15655
15818
  conn,
15656
15819
  sessionId,
15657
15820
  prefixPending: !resumed && conn.spec.systemPrompt === "prompt-prefix",
@@ -15661,13 +15824,14 @@ class AcpSession {
15661
15824
  modeConfigId: modeOption?.id ?? null,
15662
15825
  options
15663
15826
  };
15664
- await this.applyMode(this.live);
15665
- await this.applyOption(this.live, "model");
15666
- await this.applyOption(this.live, "thought_level");
15827
+ this.live = live;
15828
+ await this.applyMode(live);
15829
+ await this.applyOption(live, "model");
15830
+ await this.applyOption(live, "thought_level");
15667
15831
  const pushed = conn.commands.get(sessionId);
15668
15832
  if (pushed)
15669
15833
  this.setCommands(pushed);
15670
- return this.live;
15834
+ return live;
15671
15835
  }
15672
15836
  mode() {
15673
15837
  return this.d.mode?.() ?? DEFAULT_PERMISSION_MODE;
@@ -15767,7 +15931,7 @@ class AcpSession {
15767
15931
  if (!memo || !held)
15768
15932
  return;
15769
15933
  const t = setTimeout(() => {
15770
- if (this.live !== live || memo.held !== held || !(this.running || this.own))
15934
+ if (this.live !== live || memo.held !== held || memo.cut || !(this.running || this.own))
15771
15935
  return;
15772
15936
  const sent = release(toolId, memo);
15773
15937
  if (sent)
@@ -17517,6 +17681,18 @@ class RuntimeRegistry {
17517
17681
  return true;
17518
17682
  return false;
17519
17683
  }
17684
+ demand() {
17685
+ if (this.holds.size > 0)
17686
+ return "working";
17687
+ let waiting = false;
17688
+ for (const rt of this.runtimes.values()) {
17689
+ if (rt.agent.status === "working")
17690
+ return "working";
17691
+ if (rt.agent.status === "waiting")
17692
+ waiting = true;
17693
+ }
17694
+ return waiting ? "waiting" : null;
17695
+ }
17520
17696
  markSetup(id, on) {
17521
17697
  if (on)
17522
17698
  this.settingUp.add(id);
@@ -18193,18 +18369,20 @@ function clip(text, max = CLIP) {
18193
18369
  const line = text.replace(/\s+/g, " ").trim();
18194
18370
  return line.length > max ? `${line.slice(0, max - 1)}\u2026` : line;
18195
18371
  }
18196
- function slice(asks, ts) {
18197
- return { asks, reply: "", edits: 0, toolErrors: 0, newestTs: ts };
18372
+ function slice(asks, fixes, ts) {
18373
+ return { asks, fixes, reply: "", edits: 0, toolErrors: 0, newestTs: ts };
18198
18374
  }
18199
18375
  function turnsSince(entries, seenAt) {
18200
18376
  const turns = [];
18201
18377
  let pending = [];
18378
+ let pendingFixes = [];
18202
18379
  let open = null;
18203
18380
  const writes = new Set;
18204
18381
  const refusals = new Map;
18205
18382
  const start = (ts) => {
18206
- const t = slice(pending, ts);
18383
+ const t = slice(pending, pendingFixes, ts);
18207
18384
  pending = [];
18385
+ pendingFixes = [];
18208
18386
  turns.push(t);
18209
18387
  return t;
18210
18388
  };
@@ -18212,12 +18390,16 @@ function turnsSince(entries, seenAt) {
18212
18390
  if (open && "ts" in e)
18213
18391
  open.newestTs = Math.max(open.newestTs, e.ts);
18214
18392
  switch (e.type) {
18215
- case "user-message":
18216
- if (open && open.stop === undefined && open.error === undefined && !open.auth)
18217
- open.asks.push(e.text);
18218
- else
18219
- pending.push(e.text);
18393
+ case "user-message": {
18394
+ const into = open && open.stop === undefined && open.error === undefined && !open.auth ? open : null;
18395
+ (into?.asks ?? pending).push(e.text);
18220
18396
  break;
18397
+ }
18398
+ case "fix-asked": {
18399
+ const into = open && open.stop === undefined && open.error === undefined && !open.auth ? open : null;
18400
+ (into?.fixes ?? pendingFixes).push(e.why);
18401
+ break;
18402
+ }
18221
18403
  case "turn-start":
18222
18404
  open = start(e.ts);
18223
18405
  break;
@@ -18378,6 +18560,8 @@ function turnBlock(t) {
18378
18560
  const lines = [];
18379
18561
  if (t.asks.length)
18380
18562
  lines.push(`User asked: ${clip(t.asks.join(" / "), 400)}`);
18563
+ if (t.fixes.length)
18564
+ lines.push(`Toyon, not the user, asked for a fix: ${clip(t.fixes.join(" / "), 400)}`);
18381
18565
  if (t.reply.trim())
18382
18566
  lines.push(`Agent ended with: ${clip(t.reply, 1000)}`);
18383
18567
  return lines.join(`
@@ -18617,93 +18801,8 @@ function pruneAssets(shellDist) {
18617
18801
  return gone;
18618
18802
  }
18619
18803
 
18620
- // ../daemon/src/core/dialog.ts
18621
- import { existsSync as existsSync13 } from "fs";
18622
- import { homedir as homedir5 } from "os";
18623
- var PROMPTS = {
18624
- location: "Choose where the new project goes",
18625
- open: "Choose a project folder to open"
18626
- };
18627
- var SCRIPT = [
18628
- "on run argv",
18629
- "activate",
18630
- "POSIX path of (choose folder with prompt (item 1 of argv) default location (POSIX file (item 2 of argv)))",
18631
- "end run"
18632
- ];
18633
- function folderDialog() {
18634
- let open = null;
18635
- let child = null;
18636
- let cancelled = false;
18637
- const ask = async (start, purpose) => {
18638
- const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
18639
- const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
18640
- cancelled = false;
18641
- const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
18642
- stdout: "pipe",
18643
- stderr: "pipe",
18644
- stdin: "ignore"
18645
- });
18646
- child = p;
18647
- const [out, err, code] = await Promise.all([
18648
- new Response(p.stdout).text(),
18649
- new Response(p.stderr).text(),
18650
- p.exited
18651
- ]);
18652
- child = null;
18653
- if (code === 0)
18654
- return out.trim().replace(/\/+$/, "") || "/";
18655
- if (cancelled || err.includes("(-128)"))
18656
- return null;
18657
- throw new UserError(`the Finder dialog did not open: ${err.trim()}`);
18658
- };
18659
- return {
18660
- choose(start, purpose) {
18661
- if (process.platform !== "darwin") {
18662
- return Promise.reject(new UserError("choosing a folder in Finder is only available on macOS"));
18663
- }
18664
- if (!open) {
18665
- open = ask(start, purpose).finally(() => {
18666
- open = null;
18667
- });
18668
- }
18669
- return open;
18670
- },
18671
- cancel() {
18672
- if (!child)
18673
- return;
18674
- cancelled = true;
18675
- child.kill();
18676
- }
18677
- };
18678
- }
18679
-
18680
- // ../daemon/src/core/hub.ts
18681
- class Hub {
18682
- listeners = new Map;
18683
- on(event, fn) {
18684
- let set = this.listeners.get(event);
18685
- if (!set) {
18686
- set = new Set;
18687
- this.listeners.set(event, set);
18688
- }
18689
- set.add(fn);
18690
- return () => set?.delete(fn);
18691
- }
18692
- emit(event, ...args) {
18693
- const set = this.listeners.get(event);
18694
- if (!set)
18695
- return;
18696
- for (const fn of set) {
18697
- try {
18698
- const r = fn(...args);
18699
- if (r instanceof Promise)
18700
- r.catch((e) => log.error("hub", `async listener for ${String(event)} rejected`, e));
18701
- } catch (e) {
18702
- log.error("hub", `listener for ${String(event)} threw`, e);
18703
- }
18704
- }
18705
- }
18706
- }
18804
+ // ../daemon/src/core/awake.ts
18805
+ import { spawn as spawn3 } from "child_process";
18707
18806
 
18708
18807
  // ../daemon/src/runtime/idle.ts
18709
18808
  var LOCAL_SLEEP_MS = 2 * 60 * 60000;
@@ -19011,6 +19110,208 @@ function humanMs(ms) {
19011
19110
  return `${Math.round(ms / 1000)} s`;
19012
19111
  }
19013
19112
 
19113
+ // ../daemon/src/core/awake.ts
19114
+ var WAIT_MS = 30 * 60000;
19115
+ function idleSleepAssertion(raw, platform = process.platform) {
19116
+ if (raw === "off" || platform !== "darwin")
19117
+ return null;
19118
+ return () => {
19119
+ const child = spawn3("/usr/bin/caffeinate", ["-i", "-w", String(process.pid)], { stdio: "ignore" });
19120
+ child.on("error", (e) => log.warn("awake", "caffeinate did not start, so the Mac may sleep mid-turn", e));
19121
+ child.unref();
19122
+ return () => {
19123
+ child.kill();
19124
+ };
19125
+ };
19126
+ }
19127
+
19128
+ class KeepAwake {
19129
+ d;
19130
+ release = null;
19131
+ waitingAt = null;
19132
+ remote = 0;
19133
+ remoteLeftAt = null;
19134
+ timer = null;
19135
+ gone = false;
19136
+ waitMs;
19137
+ now;
19138
+ constructor(d) {
19139
+ this.d = d;
19140
+ this.waitMs = d.waitMs ?? WAIT_MS;
19141
+ this.now = d.now ?? Date.now;
19142
+ d.hub.on("agentStatus", (_id, status) => {
19143
+ if (status === "waiting")
19144
+ this.waitingAt = this.now();
19145
+ this.reconsider();
19146
+ });
19147
+ d.hub.on("holdsChanged", () => this.reconsider());
19148
+ d.hub.on("keepAwakeChanged", () => this.reconsider());
19149
+ this.reconsider();
19150
+ }
19151
+ setting() {
19152
+ return this.d.assert ? this.d.mode() : null;
19153
+ }
19154
+ remoteShells(n) {
19155
+ if (n === this.remote)
19156
+ return;
19157
+ if (n === 0)
19158
+ this.remoteLeftAt = this.now();
19159
+ this.remote = n;
19160
+ this.reconsider();
19161
+ }
19162
+ stop() {
19163
+ this.gone = true;
19164
+ this.disarm();
19165
+ this.hold(false);
19166
+ }
19167
+ reconsider() {
19168
+ if (this.gone || !this.d.assert)
19169
+ return;
19170
+ this.disarm();
19171
+ const mode = this.d.mode();
19172
+ const demand = this.d.demand();
19173
+ if (demand !== "waiting" || !this.d.answerable)
19174
+ this.waitingAt = null;
19175
+ else
19176
+ this.waitingAt ??= this.now();
19177
+ if (mode === "off") {
19178
+ this.hold(false);
19179
+ return;
19180
+ }
19181
+ if (mode === "always" || demand === "working" || this.remote > 0) {
19182
+ this.hold(true);
19183
+ return;
19184
+ }
19185
+ const since = Math.max(this.waitingAt ?? Number.NEGATIVE_INFINITY, this.remoteLeftAt ?? Number.NEGATIVE_INFINITY);
19186
+ const left = since + this.waitMs - this.now();
19187
+ if (left > 0) {
19188
+ this.hold(true);
19189
+ this.arm(left);
19190
+ return;
19191
+ }
19192
+ if (this.release && Number.isFinite(since)) {
19193
+ log.info("awake", `nobody has answered or connected for ${humanMs(this.waitMs)}; the machine may sleep again`);
19194
+ }
19195
+ this.hold(false);
19196
+ }
19197
+ hold(on) {
19198
+ if (on === (this.release !== null))
19199
+ return;
19200
+ if (on) {
19201
+ this.release = this.d.assert?.() ?? null;
19202
+ } else {
19203
+ this.release?.();
19204
+ this.release = null;
19205
+ }
19206
+ log.debug("awake", on ? "holding off idle sleep" : "idle sleep allowed again");
19207
+ }
19208
+ arm(ms) {
19209
+ const set = this.d.setTimer ?? ((fn, delay) => {
19210
+ const t = setTimeout(fn, delay);
19211
+ t.unref?.();
19212
+ return t;
19213
+ });
19214
+ this.timer = set(() => {
19215
+ this.timer = null;
19216
+ this.reconsider();
19217
+ }, ms);
19218
+ }
19219
+ disarm() {
19220
+ if (!this.timer)
19221
+ return;
19222
+ (this.d.clearTimer ?? ((t) => clearTimeout(t)))(this.timer);
19223
+ this.timer = null;
19224
+ }
19225
+ }
19226
+
19227
+ // ../daemon/src/core/dialog.ts
19228
+ import { existsSync as existsSync13 } from "fs";
19229
+ import { homedir as homedir5 } from "os";
19230
+ var PROMPTS = {
19231
+ location: "Choose where the new project goes",
19232
+ open: "Choose a project folder to open"
19233
+ };
19234
+ var SCRIPT = [
19235
+ "on run argv",
19236
+ "activate",
19237
+ "POSIX path of (choose folder with prompt (item 1 of argv) default location (POSIX file (item 2 of argv)))",
19238
+ "end run"
19239
+ ];
19240
+ function folderDialog() {
19241
+ let open = null;
19242
+ let child = null;
19243
+ let cancelled = false;
19244
+ const ask = async (start, purpose) => {
19245
+ const typed = start === "~" || start.startsWith("~/") ? `${homedir5()}${start.slice(1)}` : start;
19246
+ const from = typed.startsWith("/") && existsSync13(typed) ? typed : homedir5();
19247
+ cancelled = false;
19248
+ const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
19249
+ stdout: "pipe",
19250
+ stderr: "pipe",
19251
+ stdin: "ignore"
19252
+ });
19253
+ child = p;
19254
+ const [out, err, code] = await Promise.all([
19255
+ new Response(p.stdout).text(),
19256
+ new Response(p.stderr).text(),
19257
+ p.exited
19258
+ ]);
19259
+ child = null;
19260
+ if (code === 0)
19261
+ return out.trim().replace(/\/+$/, "") || "/";
19262
+ if (cancelled || err.includes("(-128)"))
19263
+ return null;
19264
+ throw new UserError(`the Finder dialog did not open: ${err.trim()}`);
19265
+ };
19266
+ return {
19267
+ choose(start, purpose) {
19268
+ if (process.platform !== "darwin") {
19269
+ return Promise.reject(new UserError("choosing a folder in Finder is only available on macOS"));
19270
+ }
19271
+ if (!open) {
19272
+ open = ask(start, purpose).finally(() => {
19273
+ open = null;
19274
+ });
19275
+ }
19276
+ return open;
19277
+ },
19278
+ cancel() {
19279
+ if (!child)
19280
+ return;
19281
+ cancelled = true;
19282
+ child.kill();
19283
+ }
19284
+ };
19285
+ }
19286
+
19287
+ // ../daemon/src/core/hub.ts
19288
+ class Hub {
19289
+ listeners = new Map;
19290
+ on(event, fn) {
19291
+ let set = this.listeners.get(event);
19292
+ if (!set) {
19293
+ set = new Set;
19294
+ this.listeners.set(event, set);
19295
+ }
19296
+ set.add(fn);
19297
+ return () => set?.delete(fn);
19298
+ }
19299
+ emit(event, ...args) {
19300
+ const set = this.listeners.get(event);
19301
+ if (!set)
19302
+ return;
19303
+ for (const fn of set) {
19304
+ try {
19305
+ const r = fn(...args);
19306
+ if (r instanceof Promise)
19307
+ r.catch((e) => log.error("hub", `async listener for ${String(event)} rejected`, e));
19308
+ } catch (e) {
19309
+ log.error("hub", `listener for ${String(event)} threw`, e);
19310
+ }
19311
+ }
19312
+ }
19313
+ }
19314
+
19014
19315
  // ../daemon/src/core/idleExit.ts
19015
19316
  var STOP_AFTER_MS = 30 * 60000;
19016
19317
  function stopAfterFrom(raw, opts) {
@@ -19100,7 +19401,7 @@ class IdleExit {
19100
19401
  }
19101
19402
 
19102
19403
  // ../daemon/src/core/restart.ts
19103
- import { spawn as spawn3 } from "child_process";
19404
+ import { spawn as spawn4 } from "child_process";
19104
19405
  import { openSync } from "fs";
19105
19406
  import { homedir as homedir6 } from "os";
19106
19407
  function restartable(env = process.env) {
@@ -19121,7 +19422,7 @@ async function replacementRuns() {
19121
19422
  }
19122
19423
  function respawn(logFile) {
19123
19424
  const logFd = openSync(logFile, "a");
19124
- const child = spawn3(process.execPath, process.argv.slice(1), {
19425
+ const child = spawn4(process.execPath, process.argv.slice(1), {
19125
19426
  detached: true,
19126
19427
  stdio: ["ignore", logFd, logFd],
19127
19428
  env: { ...process.env }
@@ -19506,6 +19807,16 @@ class StateStore {
19506
19807
  delete this.state.defaultAgent;
19507
19808
  this.save();
19508
19809
  }
19810
+ get keepAwake() {
19811
+ return this.state.keepAwake ?? "use";
19812
+ }
19813
+ setKeepAwake(mode) {
19814
+ if (mode === "use")
19815
+ delete this.state.keepAwake;
19816
+ else
19817
+ this.state.keepAwake = mode;
19818
+ this.save();
19819
+ }
19509
19820
  get theme() {
19510
19821
  return this.state.theme;
19511
19822
  }
@@ -20315,12 +20626,13 @@ class ExecService {
20315
20626
  if (opts.quiet)
20316
20627
  agent.note(start);
20317
20628
  agent.note({ type: "tool-end", toolId, output: `could not run: ${reason}`, isError: true });
20318
- return Promise.resolve({ exit: reason, text: "" });
20629
+ return Promise.resolve({ exit: reason, text: "", toolId });
20319
20630
  }
20320
20631
  const pid = proc.pid;
20321
20632
  let killing;
20322
20633
  const running = {
20323
20634
  stop: () => {
20635
+ running.stopping = true;
20324
20636
  killing ??= killGroup(pid, groupGone(pid)).then((signal) => {
20325
20637
  if (signal)
20326
20638
  running.signal = signal;
@@ -20337,7 +20649,8 @@ class ExecService {
20337
20649
  this.deps.runtime.hold(worktreeId, `exec:${toolId}`);
20338
20650
  this.deps.runtime.noteGroup(worktreeId, `exec:${toolId}`, pid);
20339
20651
  opts.onSpawn?.(pid, () => fireAndForget(worktreeId, running.stop(), `exec ${toolId} stopped`));
20340
- return this.collect(worktreeId, toolId, proc, running, agent, ceiling, opts.quiet ? start : undefined);
20652
+ const kind = name === CHECK_TOOL ? "check" : "command";
20653
+ return this.collect(worktreeId, toolId, proc, running, agent, ceiling, kind, opts.quiet ? start : undefined);
20341
20654
  }
20342
20655
  async watch(worktreeId, command, run) {
20343
20656
  const agent = this.agentFor(worktreeId);
@@ -20375,16 +20688,17 @@ class ExecService {
20375
20688
  this.untrack(worktreeId, toolId);
20376
20689
  }
20377
20690
  if (!up && r.exit === 0)
20378
- return { ...r, shown: false };
20691
+ return { ...r, shown: false, toolId };
20379
20692
  if (!up)
20380
20693
  agent.note(start);
20381
20694
  agent.note({
20382
20695
  type: "tool-end",
20383
20696
  toolId,
20384
20697
  output: formatOutput(text, r.exit, truncated, r.ceilingMs),
20385
- isError: r.exit !== 0
20698
+ isError: r.exit !== 0,
20699
+ ...r.hook && typeof r.exit === "number" && r.exit !== 0 ? { fixable: { kind: "hook", hook: r.hook } } : {}
20386
20700
  });
20387
- return { ...r, shown: true };
20701
+ return { ...r, shown: true, toolId };
20388
20702
  }
20389
20703
  async stop(worktreeId, toolId) {
20390
20704
  const byTool = this.running.get(worktreeId);
@@ -20417,7 +20731,7 @@ class ExecService {
20417
20731
  this.deps.runtime.release(worktreeId, `exec:${toolId}`);
20418
20732
  this.deps.runtime.forgetGroup(worktreeId, pgid);
20419
20733
  }
20420
- async collect(worktreeId, toolId, proc, running, agent, ceiling, heldStart) {
20734
+ async collect(worktreeId, toolId, proc, running, agent, ceiling, kind, heldStart) {
20421
20735
  let text = "";
20422
20736
  let truncated = false;
20423
20737
  const live = !heldStart;
@@ -20455,7 +20769,13 @@ class ExecService {
20455
20769
  log.warn(worktreeId, "exec: reading output failed", e);
20456
20770
  }
20457
20771
  const pgid = proc.pid;
20458
- const end = (how) => agent.note({ type: "tool-end", toolId, output: formatOutput(text, how, truncated, ceiling), isError: how !== 0 });
20772
+ const end = (how) => agent.note({
20773
+ type: "tool-end",
20774
+ toolId,
20775
+ output: formatOutput(text, how, truncated, ceiling),
20776
+ isError: how !== 0,
20777
+ ...typeof how === "number" && how !== 0 && !running.stopping && !running.timedOut ? { fixable: { kind } } : {}
20778
+ });
20459
20779
  const lingers = running.signal === undefined && groupAlive(pgid);
20460
20780
  if (!lingers)
20461
20781
  this.settle(worktreeId, toolId, pgid);
@@ -20466,18 +20786,18 @@ class ExecService {
20466
20786
  }
20467
20787
  if (lingers)
20468
20788
  fireAndForget(worktreeId, this.linger(running, pgid).then(() => this.settle(worktreeId, toolId, pgid)), `exec ${toolId} left running`);
20469
- return { exit: running.timedOut ? "timeout" : exit, text };
20789
+ return { exit: running.timedOut ? "timeout" : exit, text, toolId };
20470
20790
  }
20471
20791
  if (!lingers) {
20472
20792
  end(running.timedOut ? "timeout" : exit);
20473
- return { exit: running.timedOut ? "timeout" : exit, text };
20793
+ return { exit: running.timedOut ? "timeout" : exit, text, toolId };
20474
20794
  }
20475
20795
  agent.note({ type: "tool-update", toolId, background: true });
20476
20796
  fireAndForget(worktreeId, this.linger(running, pgid).then(() => {
20477
20797
  this.settle(worktreeId, toolId, pgid);
20478
20798
  end(running.timedOut ? "timeout" : running.signal ?? exit);
20479
20799
  }), `exec ${toolId} in the background`);
20480
- return { exit, text };
20800
+ return { exit, text, toolId };
20481
20801
  }
20482
20802
  async linger(running, pgid) {
20483
20803
  while (running.signal === undefined && groupAlive(pgid))
@@ -20586,7 +20906,7 @@ function looksBinary(text) {
20586
20906
  }
20587
20907
 
20588
20908
  // ../daemon/src/files/service.ts
20589
- import { spawn as spawn4 } from "child_process";
20909
+ import { spawn as spawn5 } from "child_process";
20590
20910
  import { stat as stat2, unlink } from "fs/promises";
20591
20911
 
20592
20912
  // ../daemon/src/git/blame.ts
@@ -21285,7 +21605,7 @@ class FileService {
21285
21605
  revealPath(target) {
21286
21606
  if (process.platform !== "darwin")
21287
21607
  throw new UserError("reveal is only available on macOS");
21288
- const child = spawn4("open", ["-R", target], { stdio: "ignore" });
21608
+ const child = spawn5("open", ["-R", target], { stdio: "ignore" });
21289
21609
  child.on("error", () => {});
21290
21610
  child.unref();
21291
21611
  }
@@ -25750,6 +26070,49 @@ class PairCodes {
25750
26070
  }
25751
26071
  }
25752
26072
 
26073
+ // ../daemon/src/core/tailnet.ts
26074
+ var PHONE_OS = new Set(["android", "ios"]);
26075
+ function tailnetPhones(json) {
26076
+ let st;
26077
+ try {
26078
+ st = JSON.parse(json);
26079
+ } catch {
26080
+ return null;
26081
+ }
26082
+ if (st === null || typeof st !== "object")
26083
+ return null;
26084
+ const phones = [];
26085
+ for (const p of Object.values(st.Peer ?? {})) {
26086
+ if (!p || !PHONE_OS.has((p.OS ?? "").toLowerCase()))
26087
+ continue;
26088
+ const name = p.HostName || (p.DNSName ?? "").split(".")[0] || "a phone";
26089
+ phones.push({ name, ip: p.TailscaleIPs?.[0] ?? null });
26090
+ }
26091
+ return phones;
26092
+ }
26093
+ var PING_TIMEOUT = "2s";
26094
+ function cli(bin) {
26095
+ return async (args) => {
26096
+ try {
26097
+ const p = Bun.spawn([bin, ...args], { stdin: "ignore", stdout: "pipe", stderr: "ignore", timeout: 5000 });
26098
+ const out = await new Response(p.stdout).text();
26099
+ return { ok: await p.exited === 0, out };
26100
+ } catch {
26101
+ return { ok: false, out: "" };
26102
+ }
26103
+ };
26104
+ }
26105
+ var ping = (ip) => ["ping", "--until-direct=false", "--c", "1", "--timeout", PING_TIMEOUT, ip];
26106
+ async function readTailnetPhones(run = ((bin) => bin ? cli(bin) : null)(Bun.which("tailscale"))) {
26107
+ if (run === null)
26108
+ return null;
26109
+ const st = await run(["status", "--json"]);
26110
+ const listed = st.ok ? tailnetPhones(st.out) : null;
26111
+ if (listed === null)
26112
+ return null;
26113
+ return Promise.all(listed.map(async ({ name, ip }) => ({ name, online: ip !== null && (await run(ping(ip))).ok })));
26114
+ }
26115
+
25753
26116
  // ../daemon/src/server/handlers.ts
25754
26117
  import { randomBytes as randomBytes5 } from "crypto";
25755
26118
 
@@ -26540,8 +26903,10 @@ var handlers = {
26540
26903
  async "delete-archived"(msg, _ctx, s) {
26541
26904
  await s.worktrees.deleteArchived(msg.archiveId);
26542
26905
  },
26543
- async "adopt-worktree"(msg, _ctx, s) {
26544
- await s.worktrees.adopt(msg.worktreeId, msg.clientId);
26906
+ async "adopt-worktree"(msg, ctx, s) {
26907
+ await sending(s, ctx, msg, msg.message?.attachments, async () => {
26908
+ await s.worktrees.adopt(msg.worktreeId, msg.clientId, msg.message);
26909
+ });
26545
26910
  },
26546
26911
  async "git-status"(msg, ctx, s) {
26547
26912
  await gitStatus(s, ctx, msg.worktreeId);
@@ -26587,21 +26952,17 @@ var handlers = {
26587
26952
  },
26588
26953
  async land(msg, ctx, s) {
26589
26954
  const { result } = await s.worktrees.land(msg.worktreeId, msg.message);
26590
- const suggestion = !result.ok && result.conflict ? "Bring main into this branch and resolve the conflicts, then verify the app still works." : undefined;
26591
26955
  if (result.ok && (result.pr || s.state.worktree(msg.worktreeId)?.pr)) {
26592
26956
  fireAndForget(msg.worktreeId, s.prs.refresh(msg.worktreeId), "pr refresh");
26593
26957
  }
26594
26958
  await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, {
26595
26959
  url: result.url,
26596
- ...suggestion ? { suggestion } : {}
26960
+ ...result.asked ? { asked: true } : {}
26597
26961
  }));
26598
26962
  },
26599
26963
  async "sync-main"(msg, ctx, s) {
26600
- const { result, base } = await s.worktrees.sync(msg.worktreeId);
26601
- const prompt = !result.ok && result.conflict && s.state.worktree(msg.worktreeId);
26602
- await notify(s, ctx, msg.worktreeId, prompt ? shipped(msg.worktreeId, false, `sync conflicts with ${base}: prompt prefilled in chat`, {
26603
- suggestion: `Merge ${base} into this branch and resolve the conflicts, then verify the app still works.`
26604
- }) : shipped(msg.worktreeId, result.ok, result.message));
26964
+ const { result } = await s.worktrees.sync(msg.worktreeId);
26965
+ await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
26605
26966
  },
26606
26967
  async "pull-main"(msg, ctx, s) {
26607
26968
  const result = await s.worktrees.pull(msg.worktreeId);
@@ -26630,7 +26991,7 @@ var handlers = {
26630
26991
  },
26631
26992
  async commit(msg, ctx, s) {
26632
26993
  const result = await s.worktrees.commit(msg.worktreeId, msg.message);
26633
- await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message));
26994
+ await notify(s, ctx, msg.worktreeId, shipped(msg.worktreeId, result.ok, result.message, result.asked ? { asked: true } : {}));
26634
26995
  },
26635
26996
  async graft(msg, ctx, s) {
26636
26997
  const { target } = await s.worktrees.graft(msg.targetId, msg.sourceIds);
@@ -26705,6 +27066,12 @@ var handlers = {
26705
27066
  async judge(msg, _ctx, s) {
26706
27067
  await s.landing.judge(msg.worktreeId, msg.note);
26707
27068
  },
27069
+ async "fix-preview"(msg, _ctx, s) {
27070
+ await s.worktrees.fixPreview(msg.worktreeId, msg.clientId);
27071
+ },
27072
+ fix(msg, _ctx, s) {
27073
+ s.fix.press(msg.worktreeId, msg.toolId);
27074
+ },
26708
27075
  async "write-file"(msg, ctx, s) {
26709
27076
  const head = { t: "file-written", worktreeId: msg.worktreeId, path: msg.path, seq: msg.seq };
26710
27077
  try {
@@ -26835,6 +27202,10 @@ var handlers = {
26835
27202
  s.state.setDefaultAgent(msg.agent);
26836
27203
  s.hub.emit("agentsChanged");
26837
27204
  },
27205
+ "set-keep-awake"(msg, _ctx, s) {
27206
+ s.state.setKeepAwake(msg.mode);
27207
+ s.hub.emit("keepAwakeChanged");
27208
+ },
26838
27209
  "rescan-themes"(_msg, _ctx, s) {
26839
27210
  s.themes.load();
26840
27211
  s.hub.emit("themesChanged");
@@ -27010,7 +27381,8 @@ function createFetch(opts) {
27010
27381
  dropped: new Set,
27011
27382
  sent: 0,
27012
27383
  bytes: 0,
27013
- view: null
27384
+ view: null,
27385
+ ...remoteShell ? { remote: true } : {}
27014
27386
  };
27015
27387
  const headers = authed && remote && remoteShell ? { "set-cookie": grantCookie(grant, remote.host) } : undefined;
27016
27388
  if (srv.upgrade(req, { data, headers }))
@@ -27046,6 +27418,13 @@ function createFetch(opts) {
27046
27418
  const body = { code, url: pairLink(remote.host, code), ms };
27047
27419
  return Response.json(body, { headers: { "cache-control": NO_STORE } });
27048
27420
  }
27421
+ if (url.pathname === "/pair/phones" && req.method === "GET") {
27422
+ if (!sameSecret(req.headers.get("authorization"), `Bearer ${opts.token}`)) {
27423
+ return new Response("unauthorized", { status: 401 });
27424
+ }
27425
+ const phones = remote && isTailnetName(remote.host) ? await opts.phones() : null;
27426
+ return Response.json(phones, { headers: { "cache-control": NO_STORE } });
27427
+ }
27049
27428
  if (url.pathname === "/pair/redeem" && req.method === "POST") {
27050
27429
  if (!remote || !remoteShell)
27051
27430
  return new Response("not found", { status: 404 });
@@ -27166,6 +27545,13 @@ function createFetch(opts) {
27166
27545
  const file = join21(opts.shellDist, rel.replaceAll("..", ""));
27167
27546
  const hashed = rel.startsWith("/assets/");
27168
27547
  if (existsSync23(file) && Bun.file(file).size > 0) {
27548
+ if (rel === "/manifest.json") {
27549
+ const { bar, ground } = opts.manifestColors();
27550
+ const manifest = { ...await Bun.file(file).json(), theme_color: bar, background_color: ground };
27551
+ return Response.json(manifest, {
27552
+ headers: { "content-type": "application/manifest+json", "cache-control": NO_STORE }
27553
+ });
27554
+ }
27169
27555
  return new Response(Bun.file(file), { headers: { "cache-control": hashed ? IMMUTABLE : NO_STORE } });
27170
27556
  }
27171
27557
  if (hashed)
@@ -27202,13 +27588,17 @@ function startServer(opts) {
27202
27588
  const { services: s, token, version } = opts;
27203
27589
  const sockets = new Set;
27204
27590
  const linked = new Set;
27591
+ const counted = () => {
27592
+ s.idleExit.clients(linked.size);
27593
+ s.keepAwake.remoteShells([...linked].filter((ws) => ws.data.remote || ws.data.preview).length);
27594
+ };
27205
27595
  const link = (ws) => {
27206
27596
  linked.add(ws);
27207
- s.idleExit.clients(linked.size);
27597
+ counted();
27208
27598
  };
27209
27599
  const unlink = (ws) => {
27210
27600
  if (linked.delete(ws))
27211
- s.idleExit.clients(linked.size);
27601
+ counted();
27212
27602
  };
27213
27603
  const raw = (ws, json) => {
27214
27604
  try {
@@ -27417,6 +27807,7 @@ function startServer(opts) {
27417
27807
  agentChosen: s.state.defaultAgent !== undefined
27418
27808
  });
27419
27809
  s.hub.on("agentsChanged", () => broadcast(agentsMsg()));
27810
+ s.hub.on("keepAwakeChanged", () => broadcast({ t: "keep-awake", mode: s.keepAwake.setting() }));
27420
27811
  s.hub.on("selfChanged", () => broadcast({ t: "self", self: s.self.get() }));
27421
27812
  s.hub.on("failed", (worktreeId, message) => broadcast({ t: "error", message, worktreeId }));
27422
27813
  s.hub.on("updateChanged", () => broadcast({ t: "update", update: s.update.get() }));
@@ -27442,6 +27833,7 @@ function startServer(opts) {
27442
27833
  agentChosen: s.state.defaultAgent !== undefined,
27443
27834
  home: homedir11(),
27444
27835
  folderDialog: process.platform === "darwin" && !cloud.enabled,
27836
+ keepAwake: s.keepAwake.setting(),
27445
27837
  remote: opts.remote && { host: opts.remote.host, previews: opts.remote.previews },
27446
27838
  paired: s.state.paired,
27447
27839
  gitIdentity: await s.repos.gitIdentity(),
@@ -27474,6 +27866,10 @@ function startServer(opts) {
27474
27866
  managed: { source: opts.managed.source, hash: opts.managed.hash },
27475
27867
  preview: (id) => s.runtime.get(id)?.proxy?.handler ?? null,
27476
27868
  bootstrap: helloFrame,
27869
+ manifestColors: () => {
27870
+ const { surface0, surface1 } = s.themes.current().colors;
27871
+ return { bar: surface1, ground: surface0 };
27872
+ },
27477
27873
  open: (path) => s.opens.open(path),
27478
27874
  restart: (now) => s.restarter.request({ now }),
27479
27875
  restartWait: () => ({ waiting: s.restarter.waitingOn(), asking: s.restarter.asking() }),
@@ -27481,7 +27877,8 @@ function startServer(opts) {
27481
27877
  onPaired: () => {
27482
27878
  s.state.notePaired();
27483
27879
  broadcast({ t: "paired" });
27484
- }
27880
+ },
27881
+ phones: () => readTailnetPhones()
27485
27882
  }),
27486
27883
  websocket: {
27487
27884
  async open(ws) {
@@ -28577,6 +28974,92 @@ class ChatSearch {
28577
28974
  }
28578
28975
  }
28579
28976
 
28977
+ // ../daemon/src/worktrees/fix.ts
28978
+ var RESPONSE = {
28979
+ hook: { does: "ask" },
28980
+ conflict: { does: "ask" },
28981
+ check: { does: "ask", once: true },
28982
+ command: { does: "offer" },
28983
+ preview: { does: "offer" }
28984
+ };
28985
+ function lastAsked(entries) {
28986
+ for (let i = entries.length - 1;i >= 0; i--) {
28987
+ const e = entries[i].event;
28988
+ if (e.type === "fix-asked")
28989
+ return e.kind;
28990
+ if (e.type === "user-message")
28991
+ return;
28992
+ }
28993
+ return;
28994
+ }
28995
+ function askedFor(entries, toolId) {
28996
+ return entries.some(({ event }) => event.type === "fix-asked" && event.toolId === toolId);
28997
+ }
28998
+ function failureOf(entries, toolId) {
28999
+ let command;
29000
+ for (const { event } of entries) {
29001
+ if (event.type === "tool-start" && event.toolId === toolId) {
29002
+ const input = event.input;
29003
+ command = typeof input?.command === "string" ? input.command : undefined;
29004
+ } else if (event.type === "tool-end" && event.toolId === toolId) {
29005
+ if (!event.fixable || command === undefined)
29006
+ return null;
29007
+ const run = { toolId, command, text: unformatOutput(event.output ?? "") };
29008
+ if (event.fixable.kind === "hook")
29009
+ return { kind: "hook", hook: event.fixable.hook ?? "git", ...run };
29010
+ return { kind: event.fixable.kind, ...run };
29011
+ }
29012
+ }
29013
+ return null;
29014
+ }
29015
+
29016
+ class FixService {
29017
+ d;
29018
+ constructor(d) {
29019
+ this.d = d;
29020
+ d.hub.on("checkFailed", (id, run) => {
29021
+ this.report(id, { kind: "check", ...run });
29022
+ });
29023
+ }
29024
+ report(worktreeId, failure, opts = {}) {
29025
+ const response = RESPONSE[failure.kind];
29026
+ if (response.does === "offer" && !opts.pressed)
29027
+ return false;
29028
+ const wt = this.d.state.worktree(worktreeId);
29029
+ if (!wt || isMain(wt))
29030
+ return false;
29031
+ const agent = this.d.runtime.agentFor(worktreeId);
29032
+ if (!agent)
29033
+ return false;
29034
+ const entries = agent.transcript();
29035
+ if (response.once && !opts.pressed && lastAsked(entries) === failure.kind)
29036
+ return false;
29037
+ const toolId = failedRun(failure)?.toolId;
29038
+ if (toolId && askedFor(entries, toolId))
29039
+ return false;
29040
+ const context = failureContext(failure);
29041
+ agent.send(fixPrompt(failure), {
29042
+ asked: { kind: failure.kind, why: fixWhy(failure), ...toolId ? { toolId } : {} },
29043
+ ...context ? { context: [context] } : {}
29044
+ });
29045
+ return true;
29046
+ }
29047
+ press(worktreeId, toolId) {
29048
+ this.d.state.requireWorktree(worktreeId);
29049
+ const agent = this.d.runtime.agentFor(worktreeId);
29050
+ if (!agent)
29051
+ throw new UserError("worktree still starting; try again in a moment");
29052
+ const entries = agent.transcript();
29053
+ const failure = failureOf(entries, toolId);
29054
+ if (!failure)
29055
+ throw new UserError("nothing to fix on that row");
29056
+ if (askedFor(entries, toolId))
29057
+ return;
29058
+ if (!this.report(worktreeId, failure, { pressed: true }))
29059
+ throw new UserError("nothing runs on main");
29060
+ }
29061
+ }
29062
+
28580
29063
  // ../daemon/src/worktrees/landing.ts
28581
29064
  var TAIL_CHARS = 400;
28582
29065
  var DIFF_CHARS = 2000;
@@ -28590,6 +29073,7 @@ class LandingService {
28590
29073
  constructor(d) {
28591
29074
  this.d = d;
28592
29075
  d.hub.on("turnSettled", (id, turn) => fireAndForget(id, this.settle(id, turn), "landing verdict"));
29076
+ d.hub.on("messageRefused", (id, said) => fireAndForget(id, this.judge(id, `The commit-msg hook refused the last message, saying: ${said}`), "landing message after a refusal"));
28593
29077
  d.hub.on("agentStatus", (id, status) => {
28594
29078
  this.note(id, status);
28595
29079
  if (status === "working")
@@ -28760,6 +29244,7 @@ class LandingService {
28760
29244
  let check = "none";
28761
29245
  let checkTail;
28762
29246
  let waited = 0;
29247
+ let ran;
28763
29248
  const command = repo.config.check?.trim();
28764
29249
  if (command) {
28765
29250
  const ceiling = timeoutFor(repo.config, "check");
@@ -28783,6 +29268,7 @@ class LandingService {
28783
29268
  run.finish(r.exit);
28784
29269
  if (!live())
28785
29270
  return;
29271
+ ran = r;
28786
29272
  check = r.exit === 0 ? "pass" : "fail";
28787
29273
  if (r.exit === "timeout") {
28788
29274
  checkTail = [`gave up after ${describeDuration(ceiling)}`, tail(r.text)].filter(Boolean).join(`
@@ -28838,6 +29324,8 @@ class LandingService {
28838
29324
  record.recap = { at: Date.now(), text: verdict.recap };
28839
29325
  log.info(worktreeId, `landing: check ${check}${landing.why ? ", doubted" : ""}${opts.ask ? "" : ", check only"}, ${Date.now() - started}ms${waited > 100 ? ` (waited ${waited}ms for a check slot)` : ""}`);
28840
29326
  this.d.worktrees.setLanding(worktreeId, landing);
29327
+ if (command && check === "fail" && ran && typeof ran.exit === "number" && !opts.quiet)
29328
+ this.d.hub.emit("checkFailed", worktreeId, { toolId: ran.toolId, command, text: ran.text });
28841
29329
  }
28842
29330
  async sharedMigration(wt, repo) {
28843
29331
  if (!repo.services || keepsCopiesApart(repo.config))
@@ -29242,7 +29730,7 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
29242
29730
  }
29243
29731
 
29244
29732
  // ../daemon/src/worktrees/service.ts
29245
- import { cpSync, existsSync as existsSync29, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
29733
+ import { cpSync, existsSync as existsSync30, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
29246
29734
  import { rm as rm4 } from "fs/promises";
29247
29735
  import { dirname as dirname11, join as join27 } from "path";
29248
29736
 
@@ -29368,7 +29856,11 @@ async function dropLandRefs(repoPath, worktreeId) {
29368
29856
  }
29369
29857
 
29370
29858
  // ../daemon/src/git/land.ts
29859
+ import { existsSync as existsSync26 } from "fs";
29371
29860
  var STEP_TIMEOUT_MS = 10 * 60000;
29861
+ function rowOf(r) {
29862
+ return r.shown && r.toolId && r.command ? { toolId: r.toolId, command: r.command, text: r.text } : undefined;
29863
+ }
29372
29864
  var stepRun = (cwd, args, onText, signal, opts = {}) => runWatched(GIT, args, cwd, { env: NO_PROMPT, onText, signal, ...opts, timeoutMs: opts.timeoutMs ?? STEP_TIMEOUT_MS });
29373
29865
  var UNWATCHED = {
29374
29866
  step() {},
@@ -29381,6 +29873,14 @@ function refused(what, r, n = 200) {
29381
29873
  }
29382
29874
  return `${what}: ${r.shown ? "what git and its hooks printed is on the chat" : r.err.slice(0, n)}`;
29383
29875
  }
29876
+ function stepRefused(what, r, n = 200) {
29877
+ const row = typeof r.exit === "number" && r.hook ? rowOf(r) : undefined;
29878
+ return {
29879
+ ok: false,
29880
+ message: refused(what, r, n),
29881
+ ...row && r.hook ? { hook: { ...row, name: r.hook } } : {}
29882
+ };
29883
+ }
29384
29884
  async function commitWorktree(worktreePath, message, w = UNWATCHED) {
29385
29885
  if ((await statusFiles(worktreePath)).length === 0)
29386
29886
  return { ok: false, message: "nothing to commit" };
@@ -29388,7 +29888,7 @@ async function commitWorktree(worktreePath, message, w = UNWATCHED) {
29388
29888
  w.step("committing");
29389
29889
  const c = await w.git(worktreePath, ["commit", "-m", message]);
29390
29890
  if (!c.ok)
29391
- return { ok: false, message: refused("commit refused", c) };
29891
+ return stepRefused("commit refused", c);
29392
29892
  return { ok: true, message: `committed: ${message}` };
29393
29893
  }
29394
29894
  async function requireClean(worktreePath) {
@@ -29474,7 +29974,27 @@ async function putBack(worktreePath, defaultBr, aside, taken) {
29474
29974
  message: `the uncommitted changes here conflict with ${defaultBr}${named}: commit them first, then sync`
29475
29975
  };
29476
29976
  }
29977
+ var MIDWAY = [
29978
+ ["MERGE_HEAD", "merge"],
29979
+ ["rebase-merge", "rebase"],
29980
+ ["rebase-apply", "rebase"],
29981
+ ["CHERRY_PICK_HEAD", "cherry-pick"],
29982
+ ["REVERT_HEAD", "revert"]
29983
+ ];
29984
+ async function midway(worktreePath) {
29985
+ const args = MIDWAY.flatMap(([file]) => ["--git-path", file]);
29986
+ const r = await git(worktreePath, "rev-parse", "--path-format=absolute", ...args);
29987
+ if (!r.ok)
29988
+ return null;
29989
+ const at = r.out.split(`
29990
+ `).findIndex((file) => file && existsSync26(file));
29991
+ return MIDWAY[at]?.[1] ?? null;
29992
+ }
29477
29993
  async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
29994
+ const unfinished = await midway(worktreePath);
29995
+ if (unfinished) {
29996
+ return { ok: false, message: `a ${unfinished} is unfinished here: finish or abort it first` };
29997
+ }
29478
29998
  if (!carry) {
29479
29999
  const cErr = await requireClean(worktreePath);
29480
30000
  if (cErr)
@@ -29489,8 +30009,19 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = f
29489
30009
  const taken = await bringIn(worktreePath, defaultBr, own, behind, w);
29490
30010
  return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
29491
30011
  }
30012
+ async function requireLinear(worktreePath, base) {
30013
+ const merges = await git(worktreePath, "rev-list", "--merges", "--count", `${base}..HEAD`);
30014
+ if (!merges.ok || merges.out === "0")
30015
+ return null;
30016
+ return {
30017
+ ok: false,
30018
+ conflict: "rebase",
30019
+ message: `the branch holds a merge commit, which landing by rebase would put on ${base}: ask the agent to rebase onto ${base} first`
30020
+ };
30021
+ }
29492
30022
  async function bringIn(worktreePath, defaultBr, own, behind, w) {
29493
- if (own) {
30023
+ const merges = own ? await git(worktreePath, "rev-list", "--merges", "--count", `${defaultBr}..HEAD`) : null;
30024
+ if (own && merges?.out === "0") {
29494
30025
  w.step(`rebasing onto ${defaultBr}`);
29495
30026
  const r = await w.git(worktreePath, ["rebase", defaultBr]);
29496
30027
  if (!r.ok) {
@@ -29498,10 +30029,12 @@ async function bringIn(worktreePath, defaultBr, own, behind, w) {
29498
30029
  const files = overwritten(r.text);
29499
30030
  if (files.length)
29500
30031
  return { ok: false, message: untrackedInTheWay(defaultBr, files) };
29501
- const ask = `rebase onto ${defaultBr} conflicts: ask the agent to bring ${defaultBr} in and resolve them`;
30032
+ const ask = `rebase onto ${defaultBr} conflicts: ask the agent to rebase onto ${defaultBr} and resolve them`;
30033
+ const step = rowOf(r);
29502
30034
  return {
29503
30035
  ok: false,
29504
- conflict: true,
30036
+ conflict: "rebase",
30037
+ ...step ? { step } : {},
29505
30038
  message: r.shown ? `${ask}; the conflicts are on the chat` : `${ask} (${r.text.slice(0, 200)})`
29506
30039
  };
29507
30040
  }
@@ -29548,7 +30081,7 @@ async function landLocally(worktreePath, branch, repoPath, defaultBr, method, me
29548
30081
  const c = await w.git(repoPath, ["commit", "-m", message]);
29549
30082
  if (!c.ok) {
29550
30083
  await git(repoPath, "reset", "--hard", "HEAD");
29551
- return { ok: false, message: refused("squash commit refused", c) };
30084
+ return stepRefused("squash commit refused", c);
29552
30085
  }
29553
30086
  return { ok: true, message: `squashed ${branch} onto ${defaultBr}` };
29554
30087
  }
@@ -29607,7 +30140,7 @@ async function landingCommit(worktreePath, branch, base, method, message, own, w
29607
30140
  const c = await w.git(worktreePath, ["commit", "-m", message]);
29608
30141
  if (!c.ok) {
29609
30142
  await git(worktreePath, "reset", "--soft", head.out);
29610
- return { ok: false, message: refused("squash commit refused", c) };
30143
+ return stepRefused("squash commit refused", c);
29611
30144
  }
29612
30145
  const sha = await git(worktreePath, "rev-parse", "HEAD");
29613
30146
  return { ok: true, sha: sha.out, message: `squashed ${branch} onto ${base}` };
@@ -29620,15 +30153,15 @@ async function landingCommit(worktreePath, branch, base, method, message, own, w
29620
30153
  w.step(`squashing onto ${base}`);
29621
30154
  const s = await w.git(worktreePath, ["merge", "--squash", branch]);
29622
30155
  if (!s.ok)
29623
- return { ok: false, message: refused("squash refused", s) };
30156
+ return stepRefused("squash refused", s);
29624
30157
  const c = await w.git(worktreePath, ["commit", "-m", message]);
29625
30158
  if (!c.ok)
29626
- return { ok: false, message: refused("squash commit refused", c) };
30159
+ return stepRefused("squash commit refused", c);
29627
30160
  } else {
29628
30161
  w.step(`merging into ${base}`);
29629
30162
  const m = await w.git(worktreePath, ["merge", "--no-ff", "-m", `Merge branch '${branch}'`, branch]);
29630
30163
  if (!m.ok)
29631
- return { ok: false, message: refused("merge refused", m) };
30164
+ return stepRefused("merge refused", m);
29632
30165
  }
29633
30166
  const sha = await git(worktreePath, "rev-parse", "HEAD");
29634
30167
  return { ok: true, sha: sha.out, message: `${method === "squash" ? "squashed" : "merged"} ${branch} onto ${base}` };
@@ -29655,15 +30188,17 @@ async function pushLanding(worktreePath, sha, defaultBr, w = UNWATCHED) {
29655
30188
  if (p.ok)
29656
30189
  return { ok: true, message: `pushed ${defaultBr} to ${tracked.remote}` };
29657
30190
  const moved = /\[rejected\]|fetch first|non-fast-forward|cannot lock ref/.test(p.err);
29658
- return moved ? { ok: false, moved: true, message: `${defaultBr} on ${tracked.remote} moved while landing` } : { ok: false, message: refused("push failed", p, 300) };
30191
+ return moved ? { ok: false, moved: true, message: `${defaultBr} on ${tracked.remote} moved while landing` } : stepRefused("push failed", p, 300);
29659
30192
  }
29660
30193
  async function mergeFailure(cwd, m, conflictMessage) {
29661
30194
  const conflict = /CONFLICT|Automatic merge failed/.test(m.text);
29662
30195
  if (conflict) {
29663
30196
  await git(cwd, "merge", "--abort");
30197
+ const step = rowOf(m);
29664
30198
  return {
29665
30199
  ok: false,
29666
- conflict: true,
30200
+ conflict: "merge",
30201
+ ...step ? { step } : {},
29667
30202
  message: m.shown ? `${conflictMessage}; the conflicts are on the chat` : `${conflictMessage} (${m.text.slice(0, 200)})`
29668
30203
  };
29669
30204
  }
@@ -29694,7 +30229,7 @@ async function pushBranch(worktreePath, branch, w = UNWATCHED) {
29694
30229
  push = await w.git(worktreePath, args);
29695
30230
  }
29696
30231
  if (!push.ok)
29697
- return { ok: false, message: refused("push failed", push, 300) };
30232
+ return stepRefused("push failed", push, 300);
29698
30233
  return { ok: true, message: `pushed ${branch}` };
29699
30234
  }
29700
30235
  async function goneFromOrigin(worktreePath, branch) {
@@ -29766,7 +30301,7 @@ function compareUrl(remoteUrl, base, branch) {
29766
30301
  }
29767
30302
 
29768
30303
  // ../daemon/src/git/turn.ts
29769
- import { copyFileSync, existsSync as existsSync26, rmSync as rmSync5 } from "fs";
30304
+ import { copyFileSync, existsSync as existsSync27, rmSync as rmSync5 } from "fs";
29770
30305
  import { join as join24 } from "path";
29771
30306
  var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
29772
30307
  var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
@@ -29792,7 +30327,7 @@ async function treeNow(wtPath, head, dirty) {
29792
30327
  const tree = await run(GIT, ["write-tree"], wtPath, env);
29793
30328
  return tree.ok && tree.out ? tree.out : null;
29794
30329
  };
29795
- if (!existsSync26(index))
30330
+ if (!existsSync27(index))
29796
30331
  was = undefined;
29797
30332
  const paths = dirty && !dirty.some((f) => /[RC]/.test(f.xy)) ? dirty.map((f) => f.path) : null;
29798
30333
  if (was && paths && was.head === head) {
@@ -29807,9 +30342,9 @@ async function treeNow(wtPath, head, dirty) {
29807
30342
  }
29808
30343
  }
29809
30344
  synced.delete(wtPath);
29810
- if (!existsSync26(index)) {
30345
+ if (!existsSync27(index)) {
29811
30346
  const own = join24(index, "..", "index");
29812
- if (existsSync26(own))
30347
+ if (existsSync27(own))
29813
30348
  copyFileSync(own, index);
29814
30349
  else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
29815
30350
  return null;
@@ -29890,7 +30425,7 @@ async function dropTurnRefs(repoPath, worktreeId) {
29890
30425
  }
29891
30426
 
29892
30427
  // ../daemon/src/worktrees/archive.ts
29893
- import { existsSync as existsSync27, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
30428
+ import { existsSync as existsSync28, mkdirSync as mkdirSync8, readdirSync as readdirSync8, readFileSync as readFileSync17, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
29894
30429
  import { basename as basename8, dirname as dirname10, join as join25 } from "path";
29895
30430
  class WorktreeArchive {
29896
30431
  dir;
@@ -29900,7 +30435,7 @@ class WorktreeArchive {
29900
30435
  mkdirSync8(dir, { recursive: true });
29901
30436
  for (const id of readdirSync8(dir)) {
29902
30437
  const file = this.filesOf(id).record;
29903
- if (!existsSync27(file))
30438
+ if (!existsSync28(file))
29904
30439
  continue;
29905
30440
  try {
29906
30441
  this.records.set(id, JSON.parse(readFileSync17(file, "utf8")));
@@ -29950,7 +30485,7 @@ class WorktreeArchive {
29950
30485
  if (!this.records.has(id) || !isPlanPath(rel))
29951
30486
  return null;
29952
30487
  const file = join25(this.filesOf(id).plans, basename8(rel));
29953
- if (!existsSync27(file))
30488
+ if (!existsSync28(file))
29954
30489
  return null;
29955
30490
  try {
29956
30491
  return readFileSync17(file, "utf8");
@@ -29991,7 +30526,7 @@ function summarize(r, repoId, transcript) {
29991
30526
  };
29992
30527
  }
29993
30528
  function lastUsage(transcript) {
29994
- if (!existsSync27(transcript))
30529
+ if (!existsSync28(transcript))
29995
30530
  return null;
29996
30531
  const lines = readFileSync17(transcript, "utf8").split(`
29997
30532
  `);
@@ -30007,7 +30542,7 @@ function lastUsage(transcript) {
30007
30542
  return null;
30008
30543
  }
30009
30544
  function firstPrompt(transcript) {
30010
- if (!existsSync27(transcript))
30545
+ if (!existsSync28(transcript))
30011
30546
  return;
30012
30547
  for (const line of readFileSync17(transcript, "utf8").split(`
30013
30548
  `)) {
@@ -30022,7 +30557,7 @@ function firstPrompt(transcript) {
30022
30557
  return;
30023
30558
  }
30024
30559
  function move(from, to) {
30025
- if (!existsSync27(from))
30560
+ if (!existsSync28(from))
30026
30561
  return;
30027
30562
  mkdirSync8(dirname10(to), { recursive: true });
30028
30563
  rmSync6(to, { recursive: true, force: true });
@@ -30087,7 +30622,7 @@ class ArchivedGit {
30087
30622
  }
30088
30623
 
30089
30624
  // ../daemon/src/worktrees/spare.ts
30090
- import { existsSync as existsSync28 } from "fs";
30625
+ import { existsSync as existsSync29 } from "fs";
30091
30626
  import { basename as basename9, join as join26 } from "path";
30092
30627
  class SparePool {
30093
30628
  d;
@@ -30107,7 +30642,7 @@ class SparePool {
30107
30642
  if (!text.trim())
30108
30643
  return;
30109
30644
  const wt = this.d.state.worktree(boxId);
30110
- if (wt?.kind !== "spare" || !existsSync28(wt.path))
30645
+ if (wt?.kind !== "spare" || !existsSync29(wt.path))
30111
30646
  return;
30112
30647
  const agent = this.d.runtime.ensureAgent(wt).agent;
30113
30648
  if (agent.runningAgent === null)
@@ -30171,7 +30706,7 @@ class SparePool {
30171
30706
  const repoId = repo.id;
30172
30707
  let cut = () => {};
30173
30708
  const added = (async () => {
30174
- if (existsSync28(wt.path))
30709
+ if (existsSync29(wt.path))
30175
30710
  return;
30176
30711
  await withRepoLock(repo.path, () => gitOrThrow(repo.path, "worktree", "add", "--detach", wt.path, baseOf(repo)));
30177
30712
  })();
@@ -30219,7 +30754,7 @@ class SparePool {
30219
30754
  }
30220
30755
  log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
30221
30756
  this.d.carryDraft?.(wt.id, wt.repoId);
30222
- if (existsSync28(wt.path)) {
30757
+ if (existsSync29(wt.path)) {
30223
30758
  await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
30224
30759
  return;
30225
30760
  }
@@ -30276,7 +30811,7 @@ class SparePool {
30276
30811
  const running = this.inflight.get(repoId);
30277
30812
  await running?.added.catch(() => {});
30278
30813
  running?.cut();
30279
- if (wt.kind !== "spare" || !existsSync28(wt.path))
30814
+ if (wt.kind !== "spare" || !existsSync29(wt.path))
30280
30815
  return null;
30281
30816
  }
30282
30817
  await this.refreshing.get(repoId);
@@ -30513,6 +31048,7 @@ function stopOf(prev, next) {
30513
31048
  // ../daemon/src/worktrees/service.ts
30514
31049
  var LOCAL_CONFIG_FILES = [".env", ".env.local", ".env.development", ".env.development.local", ".dev.vars"];
30515
31050
  var BASE_IGNORE = "base .gitignore";
31051
+ var REFUSAL_CHARS = 400;
30516
31052
  async function deleteTree(path) {
30517
31053
  try {
30518
31054
  await rm4(path, { recursive: true, force: true });
@@ -30730,7 +31266,7 @@ class WorktreeService {
30730
31266
  };
30731
31267
  const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
30732
31268
  const restored = (carried.ok ? carried.out.split(`
30733
- `).filter(Boolean) : []).filter((p) => !existsSync29(join27(wt.path, p)));
31269
+ `).filter(Boolean) : []).filter((p) => !existsSync30(join27(wt.path, p)));
30734
31270
  const applied = await git(wt.path, "stash", "apply", sha);
30735
31271
  if (applied.ok) {
30736
31272
  const ref = await entry();
@@ -30766,7 +31302,7 @@ class WorktreeService {
30766
31302
  const branch = kind === "pr" ? `pr/${number}` : ref;
30767
31303
  const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
30768
31304
  let slug = branchSlug(title) || "branch";
30769
- if (existsSync29(join27(this.d.paths.worktreesDir, repo.name, slug)))
31305
+ if (existsSync30(join27(this.d.paths.worktreesDir, repo.name, slug)))
30770
31306
  slug = `${slug}-${shortId().slice(0, 4)}`;
30771
31307
  const wtPath = join27(this.d.paths.worktreesDir, repo.name, slug);
30772
31308
  await withRepoLock(repo.path, async () => {
@@ -30811,7 +31347,7 @@ class WorktreeService {
30811
31347
  throw new UserError("that worktree is gone, or Toyon already has it");
30812
31348
  return found;
30813
31349
  }
30814
- async adopt(worktreeId, createdBy) {
31350
+ async adopt(worktreeId, createdBy, message) {
30815
31351
  const r = this.readable(worktreeId);
30816
31352
  if (!r)
30817
31353
  throw new UserError("that worktree is gone");
@@ -30840,12 +31376,18 @@ class WorktreeService {
30840
31376
  title: found.name,
30841
31377
  createdAt: Date.now(),
30842
31378
  agent: this.d.agents.require(this.d.state.defaultAgent ?? DEFAULT_AGENT_ID).id,
31379
+ parked: true,
31380
+ ...message ? { promptedAt: Date.now() } : {},
30843
31381
  ...createdBy ? { createdBy } : {}
30844
31382
  };
30845
31383
  return rec;
30846
31384
  });
30847
31385
  this.invalidateDiscovered();
30848
31386
  this.launch(wt, repo, repo.path, { setupCommands: false });
31387
+ const { agent } = this.d.runtime.ensureAgent(wt);
31388
+ agent.note({ type: "adopted", branch: wt.branch, ts: Date.now() });
31389
+ if (message)
31390
+ agent.send(message.text, { attachments: message.attachments });
30849
31391
  return wt;
30850
31392
  }
30851
31393
  async ownProcs(worktreeId, names) {
@@ -31015,7 +31557,7 @@ class WorktreeService {
31015
31557
  }
31016
31558
  async teardown(wt, repo) {
31017
31559
  const cmds = repo.config.teardown ?? [];
31018
- if (cmds.length === 0 || !existsSync29(wt.path))
31560
+ if (cmds.length === 0 || !existsSync30(wt.path))
31019
31561
  return;
31020
31562
  for (const cmd of cmds) {
31021
31563
  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") });
@@ -31065,7 +31607,7 @@ class WorktreeService {
31065
31607
  }
31066
31608
  keepPlans(wt) {
31067
31609
  const from = join27(wt.path, PLANS_DIR);
31068
- if (!existsSync29(from))
31610
+ if (!existsSync30(from))
31069
31611
  return;
31070
31612
  try {
31071
31613
  cpSync(from, this.plansScratch(wt.id), { recursive: true });
@@ -31196,7 +31738,7 @@ class WorktreeService {
31196
31738
  throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
31197
31739
  if (rec.auto)
31198
31740
  log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
31199
- const path = existsSync29(old.path) ? join27(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
31741
+ const path = existsSync30(old.path) ? join27(dirname11(old.path), freeSlot(dirname11(old.path)).dir) : old.path;
31200
31742
  const branch = await withRepoLock(repo.path, async () => {
31201
31743
  if (!await commitOf(repo.path, archiveRef(old.id))) {
31202
31744
  throw new UserError(`${old.title}'s kept commits are gone from git`);
@@ -31263,12 +31805,12 @@ class WorktreeService {
31263
31805
  return;
31264
31806
  const repo = this.repoOf(rec);
31265
31807
  const repoPath = repo?.path ?? rec.repoPath;
31266
- if (rec.kept && existsSync29(repoPath)) {
31808
+ if (rec.kept && existsSync30(repoPath)) {
31267
31809
  const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
31268
31810
  if (!r.ok)
31269
31811
  log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
31270
31812
  }
31271
- if (rec.worktree.lands?.length && existsSync29(repoPath))
31813
+ if (rec.worktree.lands?.length && existsSync30(repoPath))
31272
31814
  await dropLandRefs(repoPath, archiveId);
31273
31815
  this.archive.delete(archiveId);
31274
31816
  this.d.drafts?.drop(archiveId);
@@ -31378,8 +31920,8 @@ class WorktreeService {
31378
31920
  }
31379
31921
  async mirrorBaseIgnore(repo) {
31380
31922
  const src = join27(repo.path, ".gitignore");
31381
- const tracked = existsSync29(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
31382
- const mirror = existsSync29(src) && !tracked;
31923
+ const tracked = existsSync30(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
31924
+ const mirror = existsSync30(src) && !tracked;
31383
31925
  const lines = mirror ? readFileSync18(src, "utf8").split(`
31384
31926
  `).filter((l) => l.trim() !== "") : [];
31385
31927
  await excludeBlock(repo.path, BASE_IGNORE, lines);
@@ -31394,7 +31936,7 @@ class WorktreeService {
31394
31936
  }
31395
31937
  const srcNm = join27(depsSource, "node_modules");
31396
31938
  const dstNm = join27(wt.path, "node_modules");
31397
- if (existsSync29(srcNm) && !existsSync29(dstNm)) {
31939
+ if (existsSync30(srcNm) && !existsSync30(dstNm)) {
31398
31940
  const r = await cloneTree(srcNm, dstNm);
31399
31941
  if (r.ok)
31400
31942
  this.d.hub.emit("log", wt.id, "setup", `deps via ${r.how} in ${r.ms}ms`);
@@ -31403,7 +31945,7 @@ class WorktreeService {
31403
31945
  }
31404
31946
  for (const f of LOCAL_CONFIG_FILES) {
31405
31947
  const src = join27(depsSource, f);
31406
- if (!existsSync29(src) || existsSync29(join27(wt.path, f)))
31948
+ if (!existsSync30(src) || existsSync30(join27(wt.path, f)))
31407
31949
  continue;
31408
31950
  const r = await run("cp", [src, join27(wt.path, f)], wt.path);
31409
31951
  if (r.ok)
@@ -31536,13 +32078,15 @@ ${wt.landing.body}` : s;
31536
32078
  const handle = commit ? this.runs.begin(wt.id, "commit", { timeoutMs: ceiling }) : null;
31537
32079
  const run = (onText, signal) => stepRun(cwd, args, onText, signal, {
31538
32080
  timeoutMs: ceiling,
31539
- ...handle ? { onSpawn: handle.spawned, onHook: (hook) => handle.stage(hook ? `${hook} hook` : undefined) } : {}
32081
+ ...handle ? { onSpawn: handle.spawned } : {},
32082
+ onHook: (hook) => handle?.stage(hook ? `${hook} hook` : undefined)
31540
32083
  });
31541
32084
  const watched = async () => {
31542
32085
  if (!this.d.watch)
31543
32086
  return run(() => {});
32087
+ const command = `${where} ${commandLine(args)}`;
31544
32088
  try {
31545
- return await this.d.watch(wt.id, `${where} ${commandLine(args)}`, run);
32089
+ return { ...await this.d.watch(wt.id, command, run), command };
31546
32090
  } catch (e) {
31547
32091
  log.warn(wt.id, `landing step runs unwatched: ${e instanceof Error ? e.message : e}`);
31548
32092
  return run(() => {});
@@ -31580,7 +32124,35 @@ ${wt.landing.body}` : s;
31580
32124
  });
31581
32125
  if (out.result.ok && out.archiveIds)
31582
32126
  this.noteLanded(wt, out.result.message, out.archiveIds);
31583
- return out;
32127
+ return { ...out, result: this.fixShip(wt, out.result, baseOf(repo)) };
32128
+ }
32129
+ fixShip(wt, result, base) {
32130
+ if (result.ok)
32131
+ return result;
32132
+ if (result.hook?.name === "commit-msg") {
32133
+ this.d.hub.emit("messageRefused", wt.id, result.hook.text.trim().slice(-REFUSAL_CHARS));
32134
+ return { ...result, message: "the commit-msg hook refused the message: another is being written" };
32135
+ }
32136
+ const { hook } = result;
32137
+ const failure = hook ? { kind: "hook", hook: hook.name, toolId: hook.toolId, command: hook.command, text: hook.text } : result.conflict ? { kind: "conflict", base, how: result.conflict, ...result.step ? { step: result.step } : {} } : null;
32138
+ return failure && this.d.fix.report(wt.id, failure) ? { ...result, asked: true } : result;
32139
+ }
32140
+ async fixPreview(worktreeId, clientId) {
32141
+ const wt = this.d.state.requireWorktree(worktreeId);
32142
+ const failure = {
32143
+ kind: "preview",
32144
+ procs: this.d.runtime.get(worktreeId)?.procs?.states() ?? [],
32145
+ log: this.d.runtime.recentLogs(worktreeId)
32146
+ };
32147
+ if (!isLead(wt)) {
32148
+ if (!this.d.fix.report(worktreeId, failure, { pressed: true }))
32149
+ throw new UserError("worktree still starting; try again in a moment");
32150
+ return;
32151
+ }
32152
+ await this.create(wt.repoId, fixPrompt(failure), {
32153
+ ...clientId ? { createdBy: clientId } : {},
32154
+ ...isProvisional(wt) ? { worktreeId } : {}
32155
+ });
31584
32156
  }
31585
32157
  async movedMidTurn(wt) {
31586
32158
  const status = this.d.runtime.agentFor(wt.id)?.status;
@@ -31709,11 +32281,14 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
31709
32281
  if (committed && !committed.ok)
31710
32282
  return committed;
31711
32283
  committedHere = committed !== null;
32284
+ const method = policy.merge ?? DEFAULT_MERGE_METHOD;
32285
+ const merged = own && method === "rebase" ? await requireLinear(wt.path, base) : null;
32286
+ if (merged)
32287
+ return merged;
31712
32288
  const taken = await takeMainIn(wt.path, base, own, w);
31713
32289
  if (!taken.ok)
31714
32290
  return taken;
31715
32291
  const mark = await landingMark(wt.path, base);
31716
- const method = policy.merge ?? DEFAULT_MERGE_METHOD;
31717
32292
  const squash = method === "squash" ? await squashMessage(wt.path, base, suggested) : "";
31718
32293
  const landed = await landLocally(wt.path, wt.branch, repo.path, repo.defaultBranch, method, squash, w);
31719
32294
  if (!landed.ok)
@@ -31744,6 +32319,9 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
31744
32319
  return { result: { ok: false, message: `nothing to land: no commits ahead of ${base}` } };
31745
32320
  let sha = "";
31746
32321
  let already = false;
32322
+ const merged = own && method === "rebase" ? await requireLinear(wt.path, base) : null;
32323
+ if (merged)
32324
+ return { result: merged };
31747
32325
  for (let attempt = 0;; attempt++) {
31748
32326
  const fetched = await this.fetchBase(wt, repo, w);
31749
32327
  if (!fetched.ok)
@@ -31881,7 +32459,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
31881
32459
  }
31882
32460
  return taken;
31883
32461
  });
31884
- return { result, base: r.base };
32462
+ return { result: r.wt ? this.fixShip(r.wt, result, r.base) : result, base: r.base };
31885
32463
  }
31886
32464
  async pull(worktreeId) {
31887
32465
  const wt = this.d.state.requireWorktree(worktreeId);
@@ -31903,7 +32481,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
31903
32481
  const m = message.trim();
31904
32482
  if (!m)
31905
32483
  throw new UserError("commit message required");
31906
- return this.ship(wt.id, "commit", () => this.commitRecorded(wt, m));
32484
+ const base = baseOf(this.d.state.requireRepo(wt.repoId));
32485
+ return this.fixShip(wt, await this.ship(wt.id, "commit", () => this.commitRecorded(wt, m)), base);
31907
32486
  }
31908
32487
  setEmpty(wt, empty) {
31909
32488
  if (wt.empty === empty)
@@ -32397,6 +32976,7 @@ var drafts = new DraftStore({
32397
32976
  var runs = new RunService({ state, hub });
32398
32977
  var exec = new ExecService({ state, runtime, shipping: (id) => worktrees.shippingOf(id) });
32399
32978
  var cache = new ArtifactCache({ paths, state, hub });
32979
+ var fix = new FixService({ state, hub, runtime });
32400
32980
  var worktrees = new WorktreeService({
32401
32981
  state,
32402
32982
  hub,
@@ -32406,7 +32986,8 @@ var worktrees = new WorktreeService({
32406
32986
  drafts,
32407
32987
  cache,
32408
32988
  watch: (id, command, run) => exec.watch(id, command, run),
32409
- runs
32989
+ runs,
32990
+ fix
32410
32991
  });
32411
32992
  new BackendShare({ state, hub, runtime, own: (id, names) => worktrees.ownProcs(id, names) });
32412
32993
  var turns = new TurnService({
@@ -32506,6 +33087,13 @@ var idleExit = new IdleExit({
32506
33087
  },
32507
33088
  afterMs: stopAfter
32508
33089
  });
33090
+ var keepAwake = new KeepAwake({
33091
+ hub,
33092
+ demand: () => runtime.demand(),
33093
+ assert: idleSleepAssertion(process.env.TOYON_KEEP_AWAKE),
33094
+ mode: () => state.keepAwake,
33095
+ answerable: remote !== null
33096
+ });
32509
33097
  var { branded, stop: stopServer } = startServer({
32510
33098
  port,
32511
33099
  token,
@@ -32527,6 +33115,7 @@ var { branded, stop: stopServer } = startServer({
32527
33115
  runtime,
32528
33116
  idle,
32529
33117
  idleExit,
33118
+ keepAwake,
32530
33119
  exec,
32531
33120
  runs,
32532
33121
  refs,
@@ -32534,6 +33123,7 @@ var { branded, stop: stopServer } = startServer({
32534
33123
  drafts,
32535
33124
  prs,
32536
33125
  landing,
33126
+ fix,
32537
33127
  themes,
32538
33128
  agents,
32539
33129
  accounts,
@@ -32606,6 +33196,7 @@ async function shutdown(signal, opts = {}) {
32606
33196
  shuttingDown = true;
32607
33197
  log.info("daemon", `${signal}: stopping dev servers`);
32608
33198
  idleExit.stop();
33199
+ keepAwake.stop();
32609
33200
  idle.shutdown();
32610
33201
  landing.stop();
32611
33202
  runs.stopWatching();