toyon 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/CHANGELOG.md +27 -0
  2. package/dist/bridge.js +1 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/daemon.js +606 -720
  5. package/dist/shell/assets/{Editor-jjf6IGvI.js → Editor-BATywBIx.js} +3 -3
  6. package/dist/shell/assets/{XTerm-ClBPMY5e.js → XTerm-So8-Vafj.js} +1 -1
  7. package/dist/shell/assets/{cssMode-CgTk4Slg.js → cssMode-DlHcrkXj.js} +1 -1
  8. package/dist/shell/assets/{freemarker2-B4Eirl4Y.js → freemarker2-D3xOnVxc.js} +1 -1
  9. package/dist/shell/assets/{grammar-E8nWrhy3.js → grammar-RZJ5oG33.js} +1 -1
  10. package/dist/shell/assets/{handlebars-C6uEGz8T.js → handlebars-UbIG7080.js} +1 -1
  11. package/dist/shell/assets/{html-tDAdsEYh.js → html-D92eLK6U.js} +1 -1
  12. package/dist/shell/assets/{htmlMode-DXGTr81h.js → htmlMode-DsZIJzoj.js} +1 -1
  13. package/dist/shell/assets/index-BvYzBRq_.css +1 -0
  14. package/dist/shell/assets/index-CHQwm2uN.js +268 -0
  15. package/dist/shell/assets/{javascript-BeM3ebwd.js → javascript-DUkgFtV2.js} +1 -1
  16. package/dist/shell/assets/{jsonMode-CXStIFU4.js → jsonMode-BiIooPg9.js} +1 -1
  17. package/dist/shell/assets/{liquid-2UNKoWGQ.js → liquid-DFFjZ5TH.js} +1 -1
  18. package/dist/shell/assets/{lspLanguageFeatures-G4xsCwWV.js → lspLanguageFeatures-C7Ug4qkg.js} +1 -1
  19. package/dist/shell/assets/{mdx-CoF6St9y.js → mdx-DdjSgAuG.js} +1 -1
  20. package/dist/shell/assets/{python-rULGuAEe.js → python-DPUF4OK9.js} +1 -1
  21. package/dist/shell/assets/{razor-0I41g6kD.js → razor-IRYJ0pOA.js} +1 -1
  22. package/dist/shell/assets/{tsMode-CyMsmAXC.js → tsMode-CljE7MPO.js} +1 -1
  23. package/dist/shell/assets/{xml-BNhTd1tm.js → xml-D8Zv3Gd0.js} +1 -1
  24. package/dist/shell/assets/{yaml-Cp69rkq5.js → yaml-Dxx9PL0B.js} +1 -1
  25. package/dist/shell/index.html +2 -2
  26. package/package.json +1 -1
  27. package/dist/shell/assets/index-1FLxRByi.js +0 -267
  28. package/dist/shell/assets/index-D-4W2x1P.css +0 -1
package/dist/daemon.js CHANGED
@@ -11,14 +11,10 @@ var TOOL_SEARCH = "ToolSearch";
11
11
  var ASK_TOOL = "AskUserQuestion";
12
12
  var TOYON_MCP_SERVER = "toyon";
13
13
  var HANDOFF_TOOL = "handoff";
14
- var PREVIEW_TOOL = "preview";
15
14
 
16
15
  // ../shared/src/agent-tools.ts
17
16
  var EDIT_KINDS = new Set(["edit", "delete", "move", "execute"]);
18
17
  var EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]);
19
- function isEditTool(ev) {
20
- return ev.kind ? EDIT_KINDS.has(ev.kind) : EDIT_TOOLS.has(ev.name);
21
- }
22
18
  var WRITE_KINDS = new Set(["edit", "delete", "move"]);
23
19
  var WRITE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
24
20
  function isWriteTool(ev) {
@@ -6602,11 +6598,9 @@ ${text.slice(text.length - (CONTEXT_CHARS - HEAD_CHARS))}`;
6602
6598
  }
6603
6599
 
6604
6600
  // ../shared/src/protocol/limits.ts
6605
- var PROTOCOL_VERSION = 109;
6601
+ var PROTOCOL_VERSION = 112;
6606
6602
  var PAGE_ERROR_MAX_CHARS = 2000;
6607
6603
  var PAGE_TITLE_MAX_CHARS = 1000;
6608
- var RENDER_ERRORS_MAX = 10;
6609
- var PAGE_URL_MAX_CHARS = 2048;
6610
6604
  var FILE_MAX_CHARS = 5000000;
6611
6605
  var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
6612
6606
  var IMAGE_MAX_EDGE = 2576;
@@ -6962,6 +6956,7 @@ var clientMsgSchema = discriminatedUnion("t", [
6962
6956
  object({ t: literal("continue"), worktreeId: id }),
6963
6957
  object({ t: literal("reveal"), worktreeId: id, path: relPath.optional() }),
6964
6958
  object({ t: literal("reveal-path"), path: relPath }),
6959
+ object({ t: literal("reveal-attachment"), worktreeId: id, file: string2().min(1).max(120) }),
6965
6960
  object({ t: literal("stop-agent"), worktreeId: id }),
6966
6961
  object({ t: literal("seen"), worktreeId: id }),
6967
6962
  object({ t: literal("mark-unread"), worktreeId: id }),
@@ -6978,17 +6973,6 @@ var clientMsgSchema = discriminatedUnion("t", [
6978
6973
  object({ t: literal("forget-visit"), repoId: id, path: routePath }),
6979
6974
  object({ t: literal("page-error"), worktreeId: id, message: string2().min(1).max(PAGE_ERROR_MAX_CHARS) }),
6980
6975
  object({ t: literal("page-loaded"), worktreeId: id }),
6981
- object({ t: literal("fix-page"), worktreeId: id }),
6982
- object({
6983
- t: literal("rendered"),
6984
- worktreeId: id,
6985
- id: string2().min(1).max(64),
6986
- ok: boolean2(),
6987
- reason: string2().max(200).optional(),
6988
- url: string2().max(PAGE_URL_MAX_CHARS).optional(),
6989
- title: pageTitle.optional(),
6990
- errors: array(string2().max(PAGE_ERROR_MAX_CHARS)).max(RENDER_ERRORS_MAX)
6991
- }),
6992
6976
  object({ t: literal("pick-variant"), worktreeId: id }),
6993
6977
  object({
6994
6978
  t: literal("unqueue"),
@@ -7060,6 +7044,7 @@ var clientMsgSchema = discriminatedUnion("t", [
7060
7044
  }),
7061
7045
  object({ t: literal("handoff-ask"), worktreeId: id, repoId: id, text: prompt.optional() }),
7062
7046
  object({ t: literal("agent-logout"), agent: id }),
7047
+ object({ t: literal("agent-check"), agent: id }),
7063
7048
  object({ t: literal("agent-config"), agent: id, repoId: id.optional() }),
7064
7049
  object({ t: literal("reveal-agent-file"), agent: id, file: string2().max(64), repoId: id.optional() }),
7065
7050
  object({ t: literal("import-theme"), name: string2().max(300), source: string2().max(2000000) }),
@@ -8529,7 +8514,7 @@ async function turnRemoteOff(ts, daemonPort, file, was) {
8529
8514
  // ../daemon/package.json
8530
8515
  var package_default = {
8531
8516
  name: "@toyon/daemon",
8532
- version: "0.15.0",
8517
+ version: "0.16.0",
8533
8518
  private: true,
8534
8519
  type: "module",
8535
8520
  main: "src/index.ts",
@@ -8545,6 +8530,10 @@ var package_default = {
8545
8530
  }
8546
8531
  };
8547
8532
 
8533
+ // ../daemon/src/agent/acp/transport.ts
8534
+ import { spawn } from "child_process";
8535
+ import { Readable, Writable } from "stream";
8536
+
8548
8537
  // ../../node_modules/.bun/@agentclientprotocol+sdk@1.4.0+ab3ed8e9421bfd7b/node_modules/@agentclientprotocol/sdk/dist/schema/index.js
8549
8538
  var AGENT_METHODS = {
8550
8539
  initialize: "initialize",
@@ -12621,16 +12610,6 @@ var legacyClientNotificationMethods = new Set([
12621
12610
  CLIENT_METHODS.elicitation_complete
12622
12611
  ]);
12623
12612
 
12624
- // ../daemon/src/core/errors.ts
12625
- class UserError extends Error {
12626
- delivered;
12627
- constructor(message, about = {}) {
12628
- super(message);
12629
- this.name = "UserError";
12630
- this.delivered = about.delivered ?? false;
12631
- }
12632
- }
12633
-
12634
12613
  // ../daemon/src/core/log.ts
12635
12614
  var LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
12636
12615
  function envLevel() {
@@ -12676,131 +12655,6 @@ function fireAndForget(tag, p, what = "background task") {
12676
12655
  p.catch((e) => log.warn(tag, `${what} failed`, e));
12677
12656
  }
12678
12657
 
12679
- // ../daemon/src/agent/acp/authstatus.ts
12680
- var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
12681
- var KINDS = new Set(["none", "api_key", "account", "external", "gateway"]);
12682
- function supportsLogout(caps) {
12683
- return !!caps?.auth?.logout;
12684
- }
12685
- var str = (v) => typeof v === "string" && v.trim() ? v : undefined;
12686
- function parseAuthStatus(params) {
12687
- const status = params?.authStatus;
12688
- if (!status || typeof status !== "object")
12689
- return null;
12690
- const kind = status.kind;
12691
- if (!KINDS.has(kind))
12692
- return null;
12693
- const account = status.account ?? undefined;
12694
- const fields = account ? {
12695
- ...str(account.email) ? { email: str(account.email) } : {},
12696
- ...str(account.organization) ? { organization: str(account.organization) } : {},
12697
- ...str(account.plan) ? { plan: str(account.plan) } : {}
12698
- } : {};
12699
- return {
12700
- kind,
12701
- label: str(status.label) ?? (kind === "none" ? "not logged in" : "logged in"),
12702
- ...str(status.detail) ? { detail: str(status.detail) } : {},
12703
- ...Object.keys(fields).length ? { account: fields } : {}
12704
- };
12705
- }
12706
-
12707
- // ../daemon/src/agent/accounts.ts
12708
- class AgentAccounts {
12709
- d;
12710
- known = new Map;
12711
- busy = new Map;
12712
- onChange = null;
12713
- constructor(d) {
12714
- this.d = d;
12715
- }
12716
- observe(agentId, o) {
12717
- const before = this.known.get(agentId) ?? { canLogout: false, status: undefined };
12718
- const entry = { ...before };
12719
- if (o.canLogout !== undefined)
12720
- entry.canLogout = o.canLogout;
12721
- if (o.status)
12722
- entry.status = o.status;
12723
- this.known.set(agentId, entry);
12724
- if (before.canLogout !== entry.canLogout || !same(before.status, entry.status))
12725
- this.onChange?.();
12726
- }
12727
- observeLimits(agentId, limits) {
12728
- const before = this.known.get(agentId) ?? { canLogout: false, status: undefined };
12729
- const entry = { ...before, limits };
12730
- this.known.set(agentId, entry);
12731
- if (!sameLimits(before.limits, limits))
12732
- this.onChange?.();
12733
- }
12734
- describe(infos) {
12735
- return infos.map((info) => {
12736
- const k = this.known.get(info.id);
12737
- if (!k)
12738
- return info;
12739
- return {
12740
- ...info,
12741
- ...k.status ? { auth: k.status } : {},
12742
- ...k.canLogout ? { canLogout: true } : {},
12743
- ...k.limits ? { limits: k.limits } : {}
12744
- };
12745
- });
12746
- }
12747
- logout(agentId) {
12748
- const running = this.busy.get(agentId);
12749
- if (running)
12750
- return running;
12751
- const p = this.runLogout(agentId).finally(() => this.busy.delete(agentId));
12752
- this.busy.set(agentId, p);
12753
- return p;
12754
- }
12755
- async runLogout(agentId) {
12756
- const spec = this.d.require(agentId);
12757
- let pushed = null;
12758
- let signingOut = false;
12759
- const app = client({ name: "toyon" }).onNotification(AUTH_STATUS_UPDATE_METHOD, parseAuthStatus, (c) => {
12760
- if (c.params && signingOut)
12761
- pushed = c.params;
12762
- });
12763
- const link = await this.d.connect(app, spec);
12764
- try {
12765
- const init = await link.conn.agent.request(methods.agent.initialize, {
12766
- protocolVersion: PROTOCOL_VERSION2,
12767
- clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
12768
- clientInfo: { name: "toyon", version: "0" }
12769
- });
12770
- if (!supportsLogout(init.agentCapabilities))
12771
- throw new UserError(`${spec.name} cannot be signed out from here`);
12772
- signingOut = true;
12773
- await link.conn.agent.request(methods.agent.logout, {});
12774
- const entry = this.known.get(agentId);
12775
- this.known.set(agentId, { canLogout: true, ...pushed ? { status: pushed } : {} });
12776
- if (!entry || !same(entry.status, pushed ?? undefined))
12777
- this.onChange?.();
12778
- log.info("agents", `${agentId} signed out`);
12779
- } catch (e) {
12780
- if (e instanceof UserError)
12781
- throw e;
12782
- throw new UserError(`${spec.name} could not be signed out: ${e instanceof Error ? e.message : String(e)}`);
12783
- } finally {
12784
- await link.kill();
12785
- }
12786
- }
12787
- }
12788
- function sameLimits(a, b) {
12789
- if (!a || !b)
12790
- return a === b;
12791
- const win = (x) => x ? `${x.used}@${x.resetsAt}` : "";
12792
- return a.status === b.status && a.binding === b.binding && win(a.windows.five_hour) === win(b.windows.five_hour) && win(a.windows.seven_day) === win(b.windows.seven_day);
12793
- }
12794
- function same(a, b) {
12795
- if (!a || !b)
12796
- return a === b;
12797
- return a.kind === b.kind && a.label === b.label && a.detail === b.detail && a.account?.email === b.account?.email && a.account?.organization === b.account?.organization && a.account?.plan === b.account?.plan;
12798
- }
12799
-
12800
- // ../daemon/src/agent/acp/transport.ts
12801
- import { spawn } from "child_process";
12802
- import { Readable, Writable } from "stream";
12803
-
12804
12658
  // ../daemon/src/runtime/kill.ts
12805
12659
  var KILL_GRACE_MS = 3000;
12806
12660
  var EXIT_GRACE_MS = 200;
@@ -12904,6 +12758,16 @@ import { createHash } from "crypto";
12904
12758
  import { copyFile as copyFile2, mkdir, readFile, stat, writeFile } from "fs/promises";
12905
12759
  import { join as join2 } from "path";
12906
12760
 
12761
+ // ../daemon/src/core/errors.ts
12762
+ class UserError extends Error {
12763
+ delivered;
12764
+ constructor(message, about = {}) {
12765
+ super(message);
12766
+ this.name = "UserError";
12767
+ this.delivered = about.delivered ?? false;
12768
+ }
12769
+ }
12770
+
12907
12771
  // ../daemon/src/agent/uploads.ts
12908
12772
  import { randomBytes } from "crypto";
12909
12773
  import { mkdirSync, readdirSync, rmSync as rmSync2 } from "fs";
@@ -13188,13 +13052,7 @@ class AttachmentStore {
13188
13052
  const { n: _n, ...pick } = ref;
13189
13053
  return pick;
13190
13054
  }
13191
- let path = null;
13192
- for (const at of [this.fileFor(worktreeId, ref.file), archived?.(ref.file) ?? null]) {
13193
- if (at && await Bun.file(at).exists()) {
13194
- path = at;
13195
- break;
13196
- }
13197
- }
13055
+ const path = await this.locate(worktreeId, ref.file, archived);
13198
13056
  if (!path)
13199
13057
  return null;
13200
13058
  if (ref.kind === "paste") {
@@ -13212,6 +13070,13 @@ class AttachmentStore {
13212
13070
  const { name, bytes, width, height } = ref;
13213
13071
  return { kind: "image", upload, name, mimeType, bytes, width, height };
13214
13072
  }
13073
+ async locate(worktreeId, file, archived) {
13074
+ for (const at of [this.fileFor(worktreeId, file), archived?.(file) ?? null]) {
13075
+ if (at && await Bun.file(at).exists())
13076
+ return at;
13077
+ }
13078
+ return null;
13079
+ }
13215
13080
  async write(worktreeId, file, fill) {
13216
13081
  const wtDir = attachmentsDirFor(this.dir, worktreeId);
13217
13082
  const path = join2(wtDir, file);
@@ -13596,6 +13461,37 @@ async function readCapped(req, max) {
13596
13461
  return Buffer.concat(chunks).toString("utf8");
13597
13462
  }
13598
13463
 
13464
+ // ../daemon/src/agent/acp/authstatus.ts
13465
+ var AUTH_STATUS_UPDATE_METHOD = "_auth/status_update";
13466
+ var KINDS = new Set(["none", "api_key", "account", "external", "gateway"]);
13467
+ function supportsLogout(caps) {
13468
+ return !!caps?.auth?.logout;
13469
+ }
13470
+ var str = (v) => typeof v === "string" && v.trim() ? v : undefined;
13471
+ function parseAuthStatus(params) {
13472
+ const status = params?.authStatus;
13473
+ if (!status || typeof status !== "object")
13474
+ return null;
13475
+ const kind = status.kind;
13476
+ if (!KINDS.has(kind))
13477
+ return null;
13478
+ const account = status.account ?? undefined;
13479
+ const fields = account ? {
13480
+ ...str(account.email) ? { email: str(account.email) } : {},
13481
+ ...str(account.organization) ? { organization: str(account.organization) } : {},
13482
+ ...str(account.plan) ? { plan: str(account.plan) } : {}
13483
+ } : {};
13484
+ return {
13485
+ kind,
13486
+ label: str(status.label) ?? (kind === "none" ? "not logged in" : "logged in"),
13487
+ ...str(status.detail) ? { detail: str(status.detail) } : {},
13488
+ ...Object.keys(fields).length ? { account: fields } : {}
13489
+ };
13490
+ }
13491
+ function isAuthRequired(e) {
13492
+ return e instanceof RequestError && e.code === -32000;
13493
+ }
13494
+
13599
13495
  // ../daemon/src/agent/acp/options.ts
13600
13496
  var OPTION_FIELDS = { model: "model", thought_level: "effort" };
13601
13497
  var OPTION_CATEGORIES = Object.keys(OPTION_FIELDS);
@@ -13648,37 +13544,125 @@ function currentValues(options) {
13648
13544
  return out;
13649
13545
  }
13650
13546
 
13651
- // ../daemon/src/agent/probe.ts
13652
- class OptionProbe {
13547
+ // ../daemon/src/agent/readiness.ts
13548
+ var CHECK_COOLDOWN_MS = 30000;
13549
+
13550
+ class AgentReadiness {
13653
13551
  d;
13654
- tried = new Set;
13552
+ known = new Map;
13553
+ inflight = new Map;
13554
+ logouts = new Map;
13555
+ onChange = null;
13556
+ now;
13655
13557
  constructor(d) {
13656
13558
  this.d = d;
13559
+ this.now = d.now ?? Date.now;
13657
13560
  }
13658
- async missing() {
13561
+ entry(agentId) {
13562
+ let e = this.known.get(agentId);
13563
+ if (!e) {
13564
+ e = { canLogout: false };
13565
+ this.known.set(agentId, e);
13566
+ }
13567
+ return e;
13568
+ }
13569
+ observe(agentId, o) {
13570
+ const e = this.entry(agentId);
13571
+ const before = { ...e };
13572
+ if (o.canLogout !== undefined)
13573
+ e.canLogout = o.canLogout;
13574
+ if (o.status) {
13575
+ e.status = o.status;
13576
+ if (o.status.kind !== "none")
13577
+ delete e.failure;
13578
+ }
13579
+ if (before.canLogout !== e.canLogout || !same(before.status, e.status) || before.failure !== e.failure) {
13580
+ this.onChange?.();
13581
+ }
13582
+ }
13583
+ observeLimits(agentId, limits) {
13584
+ const e = this.entry(agentId);
13585
+ const before = e.limits;
13586
+ e.limits = limits;
13587
+ if (!sameLimits(before, limits))
13588
+ this.onChange?.();
13589
+ }
13590
+ describe() {
13591
+ return this.d.infos().map((info) => {
13592
+ const e = this.known.get(info.id);
13593
+ const models = this.d.options(info.id, "model");
13594
+ const efforts = this.d.options(info.id, "thought_level");
13595
+ return {
13596
+ ...info,
13597
+ ...models.length > 0 ? { models } : {},
13598
+ ...efforts.length > 0 ? { efforts } : {},
13599
+ ...this.d.quick(info.id) ? { quick: true } : {},
13600
+ ...e?.status ? { auth: e.status } : {},
13601
+ ...e?.canLogout ? { canLogout: true } : {},
13602
+ ...e?.limits ? { limits: e.limits } : {},
13603
+ ...e?.failure ? { checkFailed: e.failure } : {}
13604
+ };
13605
+ });
13606
+ }
13607
+ positive(agentId) {
13608
+ const e = this.known.get(agentId);
13609
+ return this.d.options(agentId, "model").length > 0 && !e?.failure && e?.status?.kind !== "none";
13610
+ }
13611
+ check(agentId) {
13612
+ const info = this.d.infos().find((a) => a.id === agentId);
13613
+ if (!info?.available)
13614
+ return Promise.resolve();
13615
+ const running = this.inflight.get(agentId);
13616
+ if (running)
13617
+ return running;
13618
+ if (this.positive(agentId))
13619
+ return Promise.resolve();
13620
+ const e = this.known.get(agentId);
13621
+ if (e?.checkedAt !== undefined && this.now() - e.checkedAt < CHECK_COOLDOWN_MS)
13622
+ return Promise.resolve();
13623
+ const p = this.run(agentId).finally(() => {
13624
+ if (this.inflight.get(agentId) === p)
13625
+ this.inflight.delete(agentId);
13626
+ });
13627
+ this.inflight.set(agentId, p);
13628
+ return p;
13629
+ }
13630
+ async checkMissing() {
13659
13631
  for (const a of this.d.infos()) {
13660
- if (!a.available || this.tried.has(a.id) || this.d.known(a.id))
13661
- continue;
13662
- this.tried.add(a.id);
13663
- await this.probe(a.id);
13632
+ if (a.available && this.d.options(a.id, "model").length === 0)
13633
+ await this.check(a.id);
13664
13634
  }
13665
13635
  }
13666
- async probe(agentId) {
13667
- const tag = `probe:${agentId}`;
13668
- const app = client({ name: "toyon" }).onNotification(methods.client.session.update, () => {});
13636
+ async run(agentId) {
13637
+ const tag = `check:${agentId}`;
13638
+ const found = {};
13639
+ let spec;
13640
+ try {
13641
+ spec = this.d.require(agentId);
13642
+ } catch (e) {
13643
+ this.finish(agentId, firstLine(e), found);
13644
+ return;
13645
+ }
13646
+ const app = client({ name: "toyon" }).onNotification(methods.client.session.update, () => {}).onNotification(AUTH_STATUS_UPDATE_METHOD, parseAuthStatus, (c) => {
13647
+ if (c.params)
13648
+ found.status = c.params;
13649
+ });
13669
13650
  let link;
13670
13651
  try {
13671
- link = await this.d.connect(app, this.d.require(agentId));
13652
+ link = await this.d.connect(app, spec);
13672
13653
  } catch (e) {
13673
- log.warn(tag, "could not start the agent to read its models", e);
13654
+ log.warn(tag, "could not start the agent", e);
13655
+ this.finish(agentId, firstLine(e), found);
13674
13656
  return;
13675
13657
  }
13658
+ let failure;
13676
13659
  try {
13677
13660
  const init = await link.conn.agent.request(methods.agent.initialize, {
13678
13661
  protocolVersion: PROTOCOL_VERSION2,
13679
13662
  clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
13680
13663
  clientInfo: { name: "toyon", version: "0" }
13681
13664
  });
13665
+ found.canLogout = supportsLogout(init.agentCapabilities);
13682
13666
  const s = await link.conn.agent.request(methods.agent.session.new, { cwd: this.d.cwd, mcpServers: [] });
13683
13667
  for (const [category, opt] of readOptions(s.configOptions))
13684
13668
  this.d.learned(agentId, category, opt.choices);
@@ -13688,18 +13672,97 @@ class OptionProbe {
13688
13672
  });
13689
13673
  }
13690
13674
  } catch (e) {
13691
- log.info(tag, `could not read its models before a worktree runs it: ${e instanceof Error ? e.message : String(e)}`);
13675
+ failure = isAuthRequired(e) ? spec.loginHint : firstLine(e);
13676
+ log.info(tag, `could not read its models: ${failure}`);
13692
13677
  } finally {
13693
13678
  link.conn.close();
13694
13679
  await link.kill();
13695
13680
  }
13681
+ this.finish(agentId, failure, found);
13682
+ }
13683
+ finish(agentId, failure, found) {
13684
+ const e = this.entry(agentId);
13685
+ const before = { ...e };
13686
+ if (found.canLogout !== undefined)
13687
+ e.canLogout = found.canLogout;
13688
+ if (found.status)
13689
+ e.status = found.status;
13690
+ e.failure = failure;
13691
+ e.checkedAt = this.now();
13692
+ if (before.canLogout !== e.canLogout || !same(before.status, e.status) || before.failure !== e.failure) {
13693
+ this.onChange?.();
13694
+ }
13695
+ }
13696
+ logout(agentId) {
13697
+ const running = this.logouts.get(agentId);
13698
+ if (running)
13699
+ return running;
13700
+ const prior = this.inflight.get(agentId) ?? Promise.resolve();
13701
+ const p = prior.then(() => this.runLogout(agentId)).finally(() => {
13702
+ this.logouts.delete(agentId);
13703
+ this.inflight.delete(agentId);
13704
+ });
13705
+ this.logouts.set(agentId, p);
13706
+ this.inflight.set(agentId, p.catch(() => {
13707
+ return;
13708
+ }));
13709
+ return p;
13710
+ }
13711
+ async runLogout(agentId) {
13712
+ const spec = this.d.require(agentId);
13713
+ let pushed = null;
13714
+ let signingOut = false;
13715
+ const app = client({ name: "toyon" }).onNotification(AUTH_STATUS_UPDATE_METHOD, parseAuthStatus, (c) => {
13716
+ if (c.params && signingOut)
13717
+ pushed = c.params;
13718
+ });
13719
+ const link = await this.d.connect(app, spec);
13720
+ try {
13721
+ const init = await link.conn.agent.request(methods.agent.initialize, {
13722
+ protocolVersion: PROTOCOL_VERSION2,
13723
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
13724
+ clientInfo: { name: "toyon", version: "0" }
13725
+ });
13726
+ if (!supportsLogout(init.agentCapabilities))
13727
+ throw new UserError(`${spec.name} cannot be signed out from here`);
13728
+ signingOut = true;
13729
+ await link.conn.agent.request(methods.agent.logout, {});
13730
+ const before = this.known.get(agentId);
13731
+ this.known.set(agentId, { canLogout: true, ...pushed ? { status: pushed } : {} });
13732
+ if (!before || !same(before.status, pushed ?? undefined) || before.failure !== undefined)
13733
+ this.onChange?.();
13734
+ log.info("agents", `${agentId} signed out`);
13735
+ } catch (e) {
13736
+ if (e instanceof UserError)
13737
+ throw e;
13738
+ throw new UserError(`${spec.name} could not be signed out: ${e instanceof Error ? e.message : String(e)}`);
13739
+ } finally {
13740
+ await link.kill();
13741
+ }
13696
13742
  }
13697
13743
  }
13744
+ function firstLine(e) {
13745
+ const details = e instanceof RequestError ? e.data?.details : undefined;
13746
+ const message = typeof details === "string" ? details : e instanceof Error ? e.message : String(e);
13747
+ return message.replace(/^Internal error: /, "").split(`
13748
+ `)[0] ?? message;
13749
+ }
13750
+ function sameLimits(a, b) {
13751
+ if (!a || !b)
13752
+ return a === b;
13753
+ const win = (x) => x ? `${x.used}@${x.resetsAt}` : "";
13754
+ return a.status === b.status && a.binding === b.binding && win(a.windows.five_hour) === win(b.windows.five_hour) && win(a.windows.seven_day) === win(b.windows.seven_day);
13755
+ }
13756
+ function same(a, b) {
13757
+ if (!a || !b)
13758
+ return a === b;
13759
+ return a.kind === b.kind && a.label === b.label && a.detail === b.detail && a.account?.email === b.account?.email && a.account?.organization === b.account?.organization && a.account?.plan === b.account?.plan;
13760
+ }
13698
13761
 
13699
13762
  // ../daemon/src/agent/registry.ts
13700
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
13763
+ import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
13701
13764
  import { homedir as homedir3 } from "os";
13702
- import { dirname as dirname3, isAbsolute, join as join6, resolve as resolve3 } from "path";
13765
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve as resolve3 } from "path";
13703
13766
 
13704
13767
  // ../daemon/src/core/cloud.ts
13705
13768
  var enabled = process.env.TOYON_CLOUD === "1";
@@ -14181,9 +14244,9 @@ function opencodeEnv() {
14181
14244
  }
14182
14245
 
14183
14246
  // ../daemon/src/agent/sandbox.ts
14184
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "fs";
14247
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, renameSync, writeFileSync as writeFileSync4 } from "fs";
14185
14248
  import { homedir as homedir2, tmpdir } from "os";
14186
- import { join as join5, resolve as resolve2 } from "path";
14249
+ import { join as join6, resolve as resolve2 } from "path";
14187
14250
 
14188
14251
  // ../daemon/src/core/paths.ts
14189
14252
  import { mkdirSync as mkdirSync2 } from "fs";
@@ -14308,6 +14371,76 @@ function within2(path, root) {
14308
14371
  return path === root || path.startsWith(root + sep);
14309
14372
  }
14310
14373
 
14374
+ // ../daemon/src/agent/planDoc.ts
14375
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
14376
+ import { basename as basename2, isAbsolute, join as join5, relative, sep as sep2 } from "path";
14377
+ var PLANS_DIR = join5(".toyon", "plans");
14378
+ var PLAN_NAME = /^[A-Za-z0-9][\w.-]*\.md$/;
14379
+ function isPlanPath(rel) {
14380
+ return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
14381
+ }
14382
+ function ownPlanPath(cwd, file) {
14383
+ const dir = canonical(join5(cwd, PLANS_DIR));
14384
+ const abs = canonical(isAbsolute(file) ? file : join5(cwd, file));
14385
+ if (!within2(abs, dir) || relative(dir, abs).includes(sep2))
14386
+ return null;
14387
+ return PLAN_NAME.test(basename2(abs)) ? join5(PLANS_DIR, basename2(abs)) : null;
14388
+ }
14389
+ async function excludePlans(cwd) {
14390
+ await excludeFromGit(cwd, `${PLANS_DIR}/`);
14391
+ }
14392
+ function lastNumber(dir) {
14393
+ if (!existsSync7(dir))
14394
+ return 0;
14395
+ let max = 0;
14396
+ for (const name of readdirSync3(dir)) {
14397
+ const n = Number(/^(\d+)\.md$/.exec(name)?.[1]);
14398
+ if (n > max)
14399
+ max = n;
14400
+ }
14401
+ return max;
14402
+ }
14403
+ function sameAsLast(cwd, rel, markdown) {
14404
+ try {
14405
+ return readFileSync6(join5(cwd, rel), "utf8").trim() === markdown.trim();
14406
+ } catch (e) {
14407
+ log.warn(cwd, `could not read ${rel}`, e);
14408
+ return false;
14409
+ }
14410
+ }
14411
+ async function writePlanDoc(cwd, markdown) {
14412
+ const dir = join5(cwd, PLANS_DIR);
14413
+ const last = lastNumber(dir);
14414
+ if (last > 0) {
14415
+ const newest = join5(PLANS_DIR, `${last}.md`);
14416
+ if (sameAsLast(cwd, newest, markdown))
14417
+ return newest;
14418
+ }
14419
+ const rel = join5(PLANS_DIR, `${last + 1}.md`);
14420
+ try {
14421
+ mkdirSync4(dir, { recursive: true });
14422
+ writeFileSync3(join5(cwd, rel), markdown.endsWith(`
14423
+ `) ? markdown : `${markdown}
14424
+ `);
14425
+ } catch (e) {
14426
+ log.warn(cwd, `could not write ${rel}; the plan stays on the card`, e);
14427
+ return null;
14428
+ }
14429
+ await excludePlans(cwd);
14430
+ return rel;
14431
+ }
14432
+ function planEdited(cwd, rel, proposed) {
14433
+ const file = join5(cwd, rel);
14434
+ if (!existsSync7(file))
14435
+ return false;
14436
+ try {
14437
+ return readFileSync6(file, "utf8").trim() !== proposed.trim();
14438
+ } catch (e) {
14439
+ log.warn(cwd, `could not read ${rel}`, e);
14440
+ return false;
14441
+ }
14442
+ }
14443
+
14311
14444
  // ../daemon/src/agent/sandbox.ts
14312
14445
  var CACHE_DIRS = [".bun", ".npm", ".cache", ".yarn", ".pnpm-store", "Library/Caches"].map((d) => resolve2(homedir2(), d));
14313
14446
  var AGENT_SETTINGS = [".claude", ".codex", ".opencode", "opencode.json", "opencode.jsonc"];
@@ -14336,9 +14469,10 @@ async function prepareLaunch(cwd, spec) {
14336
14469
  log.warn(cwd, `agent ${spec.id} runs without an OS sandbox`);
14337
14470
  return { bounds, env: setup?.env ?? {} };
14338
14471
  }
14339
- var SETTINGS_REL = join5(".claude", "settings.local.json");
14472
+ var SETTINGS_REL = join6(".claude", "settings.local.json");
14340
14473
  function claudeLocalSettings(b) {
14341
14474
  return {
14475
+ plansDirectory: PLANS_DIR,
14342
14476
  sandbox: {
14343
14477
  enabled: true,
14344
14478
  failIfUnavailable: true,
@@ -14350,12 +14484,12 @@ function claudeLocalSettings(b) {
14350
14484
  };
14351
14485
  }
14352
14486
  async function writeClaudeLocalSettings(cwd, b) {
14353
- const file = join5(cwd, SETTINGS_REL);
14354
- mkdirSync4(join5(cwd, ".claude"), { recursive: true });
14487
+ const file = join6(cwd, SETTINGS_REL);
14488
+ mkdirSync5(join6(cwd, ".claude"), { recursive: true });
14355
14489
  let existing = {};
14356
- if (existsSync7(file)) {
14490
+ if (existsSync8(file)) {
14357
14491
  try {
14358
- const parsed = JSON.parse(readFileSync6(file, "utf8"));
14492
+ const parsed = JSON.parse(readFileSync7(file, "utf8"));
14359
14493
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
14360
14494
  existing = parsed;
14361
14495
  } catch (e) {
@@ -14368,13 +14502,20 @@ async function writeClaudeLocalSettings(cwd, b) {
14368
14502
  const perms = existing.permissions && typeof existing.permissions === "object" ? existing.permissions : {};
14369
14503
  const theirs = Array.isArray(perms.deny) ? perms.deny.filter((r) => typeof r === "string") : [];
14370
14504
  const deny = uniq2([...theirs, ...ours.permissions.deny]);
14371
- const next = { ...existing, sandbox: ours.sandbox, permissions: { ...perms, ...ours.permissions, deny } };
14505
+ const next = {
14506
+ ...existing,
14507
+ sandbox: ours.sandbox,
14508
+ permissions: { ...perms, ...ours.permissions, deny },
14509
+ plansDirectory: ours.plansDirectory
14510
+ };
14372
14511
  const tmp = `${file}.tmp`;
14373
- writeFileSync3(tmp, `${JSON.stringify(next, null, 2)}
14512
+ writeFileSync4(tmp, `${JSON.stringify(next, null, 2)}
14374
14513
  `);
14375
14514
  renameSync(tmp, file);
14376
- if (existsSync7(join5(cwd, ".git")))
14515
+ if (existsSync8(join6(cwd, ".git"))) {
14377
14516
  await excludeFromGit(cwd, SETTINGS_REL);
14517
+ await excludePlans(cwd);
14518
+ }
14378
14519
  }
14379
14520
 
14380
14521
  // ../daemon/src/agent/registry.ts
@@ -14433,7 +14574,7 @@ var BUILTIN_AGENTS = [
14433
14574
  systemPrompt: "prompt-prefix",
14434
14575
  modes: { plan: "plan", build: "build" },
14435
14576
  quickModel: opencodeQuickModel,
14436
- loginHint: "OpenCode has no provider logged in; run `opencode auth login` in the terminal"
14577
+ loginHint: "OpenCode has no provider logged in; run opencode auth login in the terminal"
14437
14578
  }
14438
14579
  ];
14439
14580
  function installErrorLine(stderr) {
@@ -14520,21 +14661,21 @@ class AgentRegistry {
14520
14661
  const name = this.packageName(spec);
14521
14662
  if (!name || spec.run.kind === "command")
14522
14663
  return null;
14523
- const pkgDir = join6(this.dir, spec.id, "node_modules", name);
14664
+ const pkgDir = join7(this.dir, spec.id, "node_modules", name);
14524
14665
  if (spec.run.kind === "npm-native") {
14525
- const bin = join6(pkgDir, spec.run.bin);
14526
- return existsSync8(bin) ? bin : null;
14666
+ const bin = join7(pkgDir, spec.run.bin);
14667
+ return existsSync9(bin) ? bin : null;
14527
14668
  }
14528
- const pkgJson = join6(pkgDir, "package.json");
14529
- if (!existsSync8(pkgJson))
14669
+ const pkgJson = join7(pkgDir, "package.json");
14670
+ if (!existsSync9(pkgJson))
14530
14671
  return null;
14531
14672
  try {
14532
- const meta = JSON.parse(readFileSync7(pkgJson, "utf8"));
14673
+ const meta = JSON.parse(readFileSync8(pkgJson, "utf8"));
14533
14674
  const rel = typeof meta.bin === "string" ? meta.bin : meta.bin?.[spec.run.bin];
14534
14675
  if (!rel)
14535
14676
  return null;
14536
- const script = join6(dirname3(pkgJson), rel);
14537
- return existsSync8(script) ? script : null;
14677
+ const script = join7(dirname3(pkgJson), rel);
14678
+ return existsSync9(script) ? script : null;
14538
14679
  } catch (e) {
14539
14680
  log.warn("agents", `cannot read ${pkgJson}`, e);
14540
14681
  return null;
@@ -14547,7 +14688,7 @@ class AgentRegistry {
14547
14688
  const boxed = spec.confinement === "none" ? null : this.sandbox.problem();
14548
14689
  if (spec.run.kind === "command") {
14549
14690
  const cmd = spec.run.command;
14550
- const found = isAbsolute(cmd) ? existsSync8(cmd) : Bun.which(cmd);
14691
+ const found = isAbsolute2(cmd) ? existsSync9(cmd) : Bun.which(cmd);
14551
14692
  return { reason: found ? boxed : `${cmd} not found on PATH` };
14552
14693
  }
14553
14694
  if (!this.packageName(spec))
@@ -14594,11 +14735,11 @@ class AgentRegistry {
14594
14735
  return Promise.resolve();
14595
14736
  }
14596
14737
  const { version } = spec.run;
14597
- const dir = join6(this.dir, id);
14738
+ const dir = join7(this.dir, id);
14598
14739
  const p = (async () => {
14599
14740
  try {
14600
- mkdirSync5(dir, { recursive: true });
14601
- writeFileSync4(join6(dir, "package.json"), `${JSON.stringify({ name: `toyon-agent-${id}`, private: true, dependencies: { [pkg]: version } }, null, 2)}
14741
+ mkdirSync6(dir, { recursive: true });
14742
+ writeFileSync5(join7(dir, "package.json"), `${JSON.stringify({ name: `toyon-agent-${id}`, private: true, dependencies: { [pkg]: version } }, null, 2)}
14602
14743
  `);
14603
14744
  log.info("agents", `installing ${pkg}@${version} for ${id}`);
14604
14745
  const r = await this.installer(dir, pkg, version);
@@ -14629,9 +14770,9 @@ class AgentRegistry {
14629
14770
  const name = this.packageName(spec);
14630
14771
  if (!name)
14631
14772
  return null;
14632
- const pkgJson = join6(this.dir, spec.id, "node_modules", name, "package.json");
14773
+ const pkgJson = join7(this.dir, spec.id, "node_modules", name, "package.json");
14633
14774
  try {
14634
- return JSON.parse(readFileSync7(pkgJson, "utf8")).version ?? null;
14775
+ return JSON.parse(readFileSync8(pkgJson, "utf8")).version ?? null;
14635
14776
  } catch {
14636
14777
  return null;
14637
14778
  }
@@ -14684,7 +14825,8 @@ class AgentRegistry {
14684
14825
  ...this.installing.has(spec.id) ? { installing: true } : {},
14685
14826
  ...install !== undefined ? { install } : {},
14686
14827
  ...spec.onDemand ? { onDemand: true } : {},
14687
- sandboxed: spec.confinement !== "none"
14828
+ sandboxed: spec.confinement !== "none",
14829
+ loginHint: spec.loginHint
14688
14830
  };
14689
14831
  });
14690
14832
  }
@@ -14752,7 +14894,7 @@ function managedSpecs(specs, managed) {
14752
14894
  return managed.agents === null ? kept : kept.filter((s) => managed.agents?.includes(s.id));
14753
14895
  }
14754
14896
  function loadAgentRegistry(agentsFile, agentsDir, sandbox, managed = MANAGED_DEFAULTS) {
14755
- const specs = managed.customAgents && existsSync8(agentsFile) ? parseAgentsFile(readFileSync7(agentsFile, "utf8"), BUILTIN_AGENTS) : BUILTIN_AGENTS;
14897
+ const specs = managed.customAgents && existsSync9(agentsFile) ? parseAgentsFile(readFileSync8(agentsFile, "utf8"), BUILTIN_AGENTS) : BUILTIN_AGENTS;
14756
14898
  return new AgentRegistry(managedSpecs(specs, managed), agentsDir, undefined, undefined, sandbox);
14757
14899
  }
14758
14900
 
@@ -14785,19 +14927,19 @@ function orderPlanOptions(options) {
14785
14927
  }
14786
14928
 
14787
14929
  // ../daemon/src/agent/origins.ts
14788
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
14930
+ import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
14789
14931
  import { homedir as homedir4 } from "os";
14790
- import { basename as basename2, join as join7 } from "path";
14932
+ import { basename as basename3, join as join8 } from "path";
14791
14933
  function commandFolders(spec, cwd, home = homedir4()) {
14792
14934
  if (spec.id === "claude") {
14793
14935
  return [
14794
- { origin: "project", skills: join7(cwd, ".claude", "skills"), commands: join7(cwd, ".claude", "commands") },
14795
- { origin: "user", skills: join7(home, ".claude", "skills"), commands: join7(home, ".claude", "commands") },
14796
- ...pluginPaths(spec.meta).map((p) => ({ origin: "plugin", skills: join7(p, "skills"), commands: join7(p, "commands") }))
14936
+ { origin: "project", skills: join8(cwd, ".claude", "skills"), commands: join8(cwd, ".claude", "commands") },
14937
+ { origin: "user", skills: join8(home, ".claude", "skills"), commands: join8(home, ".claude", "commands") },
14938
+ ...pluginPaths(spec.meta).map((p) => ({ origin: "plugin", skills: join8(p, "skills"), commands: join8(p, "commands") }))
14797
14939
  ];
14798
14940
  }
14799
14941
  if (spec.id === "codex")
14800
- return [{ origin: "user", commands: join7(home, ".codex", "prompts") }];
14942
+ return [{ origin: "user", commands: join8(home, ".codex", "prompts") }];
14801
14943
  return [];
14802
14944
  }
14803
14945
  function readCommandOrigins(folders) {
@@ -14837,23 +14979,23 @@ function pluginPaths(meta) {
14837
14979
  }
14838
14980
  function skillNames(dir) {
14839
14981
  return entries2(dir).flatMap((e) => {
14840
- const file = join7(dir, e, "SKILL.md");
14841
- if (!existsSync9(file))
14982
+ const file = join8(dir, e, "SKILL.md");
14983
+ if (!existsSync10(file))
14842
14984
  return [];
14843
14985
  return [frontMatterName(file) ?? e];
14844
14986
  });
14845
14987
  }
14846
14988
  function commandNames(dir) {
14847
14989
  return entries2(dir).flatMap((e) => {
14848
- const path = join7(dir, e);
14990
+ const path = join8(dir, e);
14849
14991
  if (e.endsWith(".md"))
14850
- return [basename2(e, ".md")];
14992
+ return [basename3(e, ".md")];
14851
14993
  return isDir(path) ? commandNames(path) : [];
14852
14994
  });
14853
14995
  }
14854
14996
  function frontMatterName(file) {
14855
14997
  try {
14856
- const text = readFileSync8(file, "utf8");
14998
+ const text = readFileSync9(file, "utf8");
14857
14999
  if (!text.startsWith("---"))
14858
15000
  return null;
14859
15001
  const end = text.indexOf(`
@@ -14867,10 +15009,10 @@ function frontMatterName(file) {
14867
15009
  }
14868
15010
  }
14869
15011
  function entries2(dir) {
14870
- if (!existsSync9(dir))
15012
+ if (!existsSync10(dir))
14871
15013
  return [];
14872
15014
  try {
14873
- return readdirSync3(dir).filter((e) => !e.startsWith("."));
15015
+ return readdirSync4(dir).filter((e) => !e.startsWith("."));
14874
15016
  } catch (e) {
14875
15017
  log.warn("origins", `${dir} could not be listed`, e);
14876
15018
  return [];
@@ -14914,13 +15056,13 @@ function unformatOutput(output) {
14914
15056
  }
14915
15057
 
14916
15058
  // ../daemon/src/agent/policy.ts
14917
- import { isAbsolute as isAbsolute2, resolve as resolve4 } from "path";
15059
+ import { isAbsolute as isAbsolute3, resolve as resolve4 } from "path";
14918
15060
 
14919
15061
  // ../daemon/src/agent/acp/background.ts
14920
- import { existsSync as existsSync10, statSync as statSync4 } from "fs";
15062
+ import { existsSync as existsSync11, statSync as statSync4 } from "fs";
14921
15063
  import { open as open2 } from "fs/promises";
14922
15064
  import { homedir as homedir5 } from "os";
14923
- import { basename as basename3, dirname as dirname4, join as join8 } from "path";
15065
+ import { basename as basename4, dirname as dirname4, join as join9 } from "path";
14924
15066
  var STARTED = /Command running in background with ID: (\S+)\. Output is being written to: (\S+)\./;
14925
15067
  function backgroundStart(input, output) {
14926
15068
  const flag = input && typeof input === "object" ? input.run_in_background : undefined;
@@ -14933,15 +15075,15 @@ function spawnDetached(output) {
14933
15075
  return output.includes("Async agent launched successfully");
14934
15076
  }
14935
15077
  function claudeConfigDir() {
14936
- return process.env.CLAUDE_CONFIG_DIR || join8(homedir5(), ".claude");
15078
+ return process.env.CLAUDE_CONFIG_DIR || join9(homedir5(), ".claude");
14937
15079
  }
14938
15080
  function sessionLogFor(file, configDir = claudeConfigDir()) {
14939
15081
  const tasks = dirname4(file);
14940
- if (basename3(tasks) !== "tasks")
15082
+ if (basename4(tasks) !== "tasks")
14941
15083
  return null;
14942
15084
  const session = dirname4(tasks);
14943
- const path = join8(configDir, "projects", basename3(dirname4(session)), `${basename3(session)}.jsonl`);
14944
- return existsSync10(path) ? path : null;
15085
+ const path = join9(configDir, "projects", basename4(dirname4(session)), `${basename4(session)}.jsonl`);
15086
+ return existsSync11(path) ? path : null;
14945
15087
  }
14946
15088
  function taskEnd(text, toolId) {
14947
15089
  const at = text.indexOf(`<tool-use-id>${toolId}</tool-use-id>`);
@@ -15520,7 +15662,7 @@ function decide(req, bounds, cwd, mode = "auto", sandboxed = true) {
15520
15662
  return { kind: "allow" };
15521
15663
  }
15522
15664
  for (const raw of paths) {
15523
- const target = canonical(isAbsolute2(raw) ? raw : resolve4(cwd, raw));
15665
+ const target = canonical(isAbsolute3(raw) ? raw : resolve4(cwd, raw));
15524
15666
  if (bounds.denyRead.some((d) => within2(target, d))) {
15525
15667
  return {
15526
15668
  kind: "reject",
@@ -15586,7 +15728,6 @@ var SYSTEM_APPEND = [
15586
15728
  "`@some/path` in a message means read that file or directory first; `@changes` means this worktree's uncommitted files, which `git status` lists.",
15587
15729
  '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.',
15588
15730
  "Toyon runs those commands itself in every worktree, and the user watches the result live in a preview beside this chat, so never start a dev server or open a browser of your own to check your work. Each message says where this worktree's preview answers; fetch a page from there when you want to see it.",
15589
- "To see whether a page renders without throwing, call the `preview` tool with its path when you have one: a browser the user has open loads it beside what they see and reports what it threw.",
15590
15731
  "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.",
15591
15732
  "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."
15592
15733
  ].join(" ");
@@ -15599,7 +15740,6 @@ function failedRun(f) {
15599
15740
  case "conflict":
15600
15741
  return f.step;
15601
15742
  case "preview":
15602
- case "page":
15603
15743
  return;
15604
15744
  }
15605
15745
  }
@@ -15615,8 +15755,6 @@ function fixPrompt(f) {
15615
15755
  return commandFixPrompt(f.command);
15616
15756
  case "preview":
15617
15757
  return procFixPrompt(f.procs, f.log);
15618
- case "page":
15619
- return pageFixPrompt(f.errors);
15620
15758
  }
15621
15759
  }
15622
15760
  var WHY_COMMAND_CHARS = 60;
@@ -15635,20 +15773,8 @@ function fixWhy(f) {
15635
15773
  }
15636
15774
  case "preview":
15637
15775
  return "the dev server is not reachable";
15638
- case "page":
15639
- return "the preview threw";
15640
15776
  }
15641
15777
  }
15642
- function pageFixPrompt(errors) {
15643
- return [
15644
- "Since your last edits the preview threw this in the user's browser:",
15645
- "",
15646
- ...errors.map((e) => `- ${e}`),
15647
- "",
15648
- "Find the cause and fix it, inside this worktree. If your change did not cause it, say what did instead of changing anything."
15649
- ].join(`
15650
- `);
15651
- }
15652
15778
  function failureContext(f) {
15653
15779
  const run = failedRun(f);
15654
15780
  if (!run)
@@ -15854,11 +15980,11 @@ ${a.text}
15854
15980
  }
15855
15981
 
15856
15982
  // ../daemon/src/agent/transcript.ts
15857
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
15983
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
15858
15984
  import { appendFile } from "fs/promises";
15859
- import { join as join9 } from "path";
15985
+ import { join as join10 } from "path";
15860
15986
  function transcriptPathFor(transcriptsDir, worktreeId) {
15861
- return join9(transcriptsDir, `${worktreeId}.jsonl`);
15987
+ return join10(transcriptsDir, `${worktreeId}.jsonl`);
15862
15988
  }
15863
15989
  function coalesce(entries) {
15864
15990
  const out = [];
@@ -15912,7 +16038,7 @@ class Transcript {
15912
16038
  this.seq = (this.entries.at(-1)?.seq ?? -1) + 1;
15913
16039
  }
15914
16040
  read() {
15915
- return existsSync11(this.path) ? parseTranscript(readFileSync9(this.path, "utf8"), this.tag) : [];
16041
+ return existsSync12(this.path) ? parseTranscript(readFileSync10(this.path, "utf8"), this.tag) : [];
15916
16042
  }
15917
16043
  append(event) {
15918
16044
  const entry = { seq: this.seq++, event };
@@ -16685,12 +16811,7 @@ class AcpSession {
16685
16811
  this.emit({ type: "turn-end", stopReason: mapStopReason(res.stopReason), ts: Date.now() });
16686
16812
  }
16687
16813
  contextFor(item) {
16688
- return ambientBlock([
16689
- ...item.context ?? [],
16690
- this.d.preview?.(),
16691
- item.asked === "page" ? undefined : this.d.pageErrors?.(),
16692
- ...this.landedSince()
16693
- ]);
16814
+ return ambientBlock([...item.context ?? [], this.d.preview?.(), this.d.pageErrors?.(), ...this.landedSince()]);
16694
16815
  }
16695
16816
  landedSince() {
16696
16817
  const entries = this.log.entries;
@@ -17004,7 +17125,8 @@ class AcpSession {
17004
17125
  const plan = params.toolCall.kind === "switch_mode";
17005
17126
  const parts = permissionParts(params);
17006
17127
  const detail = parts.detail ?? "";
17007
- const planPath = plan ? await this.d.onPlan?.(detail) ?? null : null;
17128
+ const ownFile = plan ? stringField(params.toolCall.rawInput, "planFilePath") : undefined;
17129
+ const planPath = plan ? await this.d.onPlan?.(detail, ownFile) ?? null : null;
17008
17130
  const offered = plan ? orderPlanOptions(params.options) : params.options.filter((o) => o.kind !== "allow_always");
17009
17131
  const choices = offered.map((o) => ({ id: o.optionId, name: o.name, kind: o.kind }));
17010
17132
  return this.openAsk((id) => ({
@@ -17175,9 +17297,6 @@ class AcpSession {
17175
17297
  this.noteProcess(conn.link, false);
17176
17298
  }
17177
17299
  }
17178
- function isAuthRequired(e) {
17179
- return e instanceof RequestError && e.code === -32000;
17180
- }
17181
17300
  function takesHttpMcp(init) {
17182
17301
  const http = init.agentCapabilities?.mcpCapabilities?.http;
17183
17302
  return http === true || typeof http === "object" && http !== null;
@@ -17254,66 +17373,6 @@ function sameCommands(a, b) {
17254
17373
  return a.length === b.length && a.every((c, i) => c.name === b[i]?.name && c.description === b[i]?.description && c.hint === b[i]?.hint && c.origin === b[i]?.origin);
17255
17374
  }
17256
17375
 
17257
- // ../daemon/src/agent/planDoc.ts
17258
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
17259
- import { join as join10 } from "path";
17260
- var PLANS_DIR = join10(".toyon", "plans");
17261
- var PLAN_NAME = /^(\d+)\.md$/;
17262
- function isPlanPath(rel) {
17263
- return rel.startsWith(`${PLANS_DIR}/`) && PLAN_NAME.test(rel.slice(PLANS_DIR.length + 1));
17264
- }
17265
- function lastNumber(dir) {
17266
- if (!existsSync12(dir))
17267
- return 0;
17268
- let max = 0;
17269
- for (const name of readdirSync4(dir)) {
17270
- const n = Number(PLAN_NAME.exec(name)?.[1]);
17271
- if (n > max)
17272
- max = n;
17273
- }
17274
- return max;
17275
- }
17276
- function sameAsLast(cwd, rel, markdown) {
17277
- try {
17278
- return readFileSync10(join10(cwd, rel), "utf8").trim() === markdown.trim();
17279
- } catch (e) {
17280
- log.warn(cwd, `could not read ${rel}`, e);
17281
- return false;
17282
- }
17283
- }
17284
- async function writePlanDoc(cwd, markdown) {
17285
- const dir = join10(cwd, PLANS_DIR);
17286
- const last = lastNumber(dir);
17287
- if (last > 0) {
17288
- const newest = join10(PLANS_DIR, `${last}.md`);
17289
- if (sameAsLast(cwd, newest, markdown))
17290
- return newest;
17291
- }
17292
- const rel = join10(PLANS_DIR, `${last + 1}.md`);
17293
- try {
17294
- mkdirSync6(dir, { recursive: true });
17295
- writeFileSync5(join10(cwd, rel), markdown.endsWith(`
17296
- `) ? markdown : `${markdown}
17297
- `);
17298
- } catch (e) {
17299
- log.warn(cwd, `could not write ${rel}; the plan stays on the card`, e);
17300
- return null;
17301
- }
17302
- await excludeFromGit(cwd, `${PLANS_DIR}/`);
17303
- return rel;
17304
- }
17305
- function planEdited(cwd, rel, proposed) {
17306
- const file = join10(cwd, rel);
17307
- if (!existsSync12(file))
17308
- return false;
17309
- try {
17310
- return readFileSync10(file, "utf8").trim() !== proposed.trim();
17311
- } catch (e) {
17312
- log.warn(cwd, `could not read ${rel}`, e);
17313
- return false;
17314
- }
17315
- }
17316
-
17317
17376
  // ../daemon/src/runtime/forward.ts
17318
17377
  var POLL_MS2 = 100;
17319
17378
  var HOLD_MS = 60000;
@@ -18527,8 +18586,8 @@ function defaultAgent(wt, d, preview, onProcess) {
18527
18586
  onEvent: (event, seq) => d.hub.emit("agent", wt.id, seq, event),
18528
18587
  onStatus: (status) => d.hub.emit("agentStatus", wt.id, status),
18529
18588
  beforeTurn: async () => d.turnStarting?.(wt.id),
18530
- onAuth: (agentId, o) => d.accounts?.observe(agentId, o),
18531
- onLimits: (agentId, limits) => d.accounts?.observeLimits(agentId, limits),
18589
+ onAuth: (agentId, o) => d.readiness?.observe(agentId, o),
18590
+ onLimits: (agentId, limits) => d.readiness?.observeLimits(agentId, limits),
18532
18591
  seedCommands: () => d.state.cachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId),
18533
18592
  onCommandsLearned: (commands) => d.state.setCachedCommands(d.state.requireWorktree(wt.id).agent ?? "", wt.repoId, commands),
18534
18593
  commandOrigins: () => readCommandOrigins(commandFolders(spec(), wt.path)),
@@ -18537,13 +18596,14 @@ function defaultAgent(wt, d, preview, onProcess) {
18537
18596
  if (d.state.setMode(wt.id, mode))
18538
18597
  d.hub.emit("worktreesChanged");
18539
18598
  },
18540
- onPlan: async (markdown) => {
18599
+ onPlan: async (markdown, ownFile) => {
18541
18600
  const w = d.state.worktree(wt.id);
18542
18601
  if (w && !w.planned) {
18543
18602
  w.planned = true;
18544
18603
  d.state.save();
18545
18604
  }
18546
- const path = markdown.trim() ? await writePlanDoc(wt.path, markdown) : null;
18605
+ const own = ownFile ? ownPlanPath(wt.path, ownFile) : null;
18606
+ const path = own ?? (markdown.trim() ? await writePlanDoc(wt.path, markdown) : null);
18547
18607
  const fresh = path ? d.state.worktree(wt.id) : undefined;
18548
18608
  if (fresh && path && fresh.plan !== path) {
18549
18609
  fresh.plan = path;
@@ -19836,10 +19896,12 @@ function makePlanner(agents) {
19836
19896
  }
19837
19897
 
19838
19898
  // ../daemon/src/agent/transcriptForward.ts
19839
- import { readdirSync as readdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
19899
+ import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "fs";
19840
19900
  import { join as join12 } from "path";
19901
+ var FORWARD_STEP = "1";
19902
+ var MARK = '"page';
19841
19903
  function forwardLine(line) {
19842
- if (!line.includes('"page-error"'))
19904
+ if (!line.includes(MARK))
19843
19905
  return null;
19844
19906
  let entry;
19845
19907
  try {
@@ -19848,31 +19910,35 @@ function forwardLine(line) {
19848
19910
  return null;
19849
19911
  }
19850
19912
  const e = entry.event;
19851
- if (e?.type !== "page-error" || Array.isArray(e.errors) || typeof e.message !== "string")
19852
- return null;
19853
- const { message, ...rest } = e;
19854
- return JSON.stringify({ ...entry, event: { ...rest, errors: [message] } });
19913
+ if (e?.type === "page-error")
19914
+ return { drop: true };
19915
+ if (e?.type === "fix-asked" && e.kind === "page")
19916
+ return { line: JSON.stringify({ ...entry, event: { ...e, kind: "command" } }) };
19917
+ return null;
19855
19918
  }
19856
19919
  function forwardText(text) {
19857
19920
  let changed = 0;
19858
- const lines = text.split(`
19859
- `).map((line) => {
19921
+ const lines = [];
19922
+ for (const line of text.split(`
19923
+ `)) {
19860
19924
  const next = forwardLine(line);
19861
19925
  if (next === null)
19862
- return line;
19863
- changed++;
19864
- return next;
19865
- });
19926
+ lines.push(line);
19927
+ else {
19928
+ changed++;
19929
+ if ("line" in next)
19930
+ lines.push(next.line);
19931
+ }
19932
+ }
19866
19933
  return { text: changed ? lines.join(`
19867
19934
  `) : text, changed };
19868
19935
  }
19869
- function forwardTranscripts(dir) {
19936
+ function forwardTranscripts(dirs, stamp) {
19937
+ if (readStamp(stamp) === FORWARD_STEP)
19938
+ return;
19870
19939
  let files = 0;
19871
19940
  let rows = 0;
19872
- for (const name of readdirSync5(dir)) {
19873
- if (!name.endsWith(".jsonl"))
19874
- continue;
19875
- const path = join12(dir, name);
19941
+ for (const path of dirs.flatMap(transcriptsUnder)) {
19876
19942
  const { text, changed } = forwardText(readFileSync11(path, "utf8"));
19877
19943
  if (!changed)
19878
19944
  continue;
@@ -19882,15 +19948,36 @@ function forwardTranscripts(dir) {
19882
19948
  files++;
19883
19949
  rows += changed;
19884
19950
  }
19951
+ writeFileSync6(stamp, FORWARD_STEP);
19885
19952
  if (files)
19886
- log.info("transcripts", `rewrote ${rows} page-error row(s) in ${files} transcript(s) to the list shape`);
19953
+ log.info("transcripts", `moved ${rows} row(s) in ${files} transcript(s) past the preview-threw row`);
19954
+ }
19955
+ function readStamp(stamp) {
19956
+ try {
19957
+ return readFileSync11(stamp, "utf8").trim();
19958
+ } catch {
19959
+ return null;
19960
+ }
19961
+ }
19962
+ function transcriptsUnder(dir) {
19963
+ const found = [];
19964
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
19965
+ if (entry.isFile() && entry.name.endsWith(".jsonl"))
19966
+ found.push(join12(dir, entry.name));
19967
+ else if (entry.isDirectory()) {
19968
+ const inner = join12(dir, entry.name, "transcript.jsonl");
19969
+ if (existsSync14(inner))
19970
+ found.push(inner);
19971
+ }
19972
+ }
19973
+ return found;
19887
19974
  }
19888
19975
 
19889
19976
  // ../daemon/src/core/assets.ts
19890
- import { existsSync as existsSync14, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync5 } from "fs";
19977
+ import { existsSync as existsSync15, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync5 } from "fs";
19891
19978
  import { join as join13, resolve as resolve5 } from "path";
19892
19979
  function locateAssets(here, env = process.env) {
19893
- const packaged = existsSync14(join13(here, "shell", "index.html"));
19980
+ const packaged = existsSync15(join13(here, "shell", "index.html"));
19894
19981
  return {
19895
19982
  shellDist: env.TOYON_SHELL_DIST ?? (packaged ? join13(here, "shell") : join13(here, "../../shell/dist")),
19896
19983
  bridgeJs: env.TOYON_BRIDGE_JS ?? (packaged ? join13(here, "bridge.js") : join13(here, "../../bridge/dist/bridge.js")),
@@ -20357,7 +20444,7 @@ class KeepAwake {
20357
20444
  }
20358
20445
 
20359
20446
  // ../daemon/src/core/dialog.ts
20360
- import { existsSync as existsSync15 } from "fs";
20447
+ import { existsSync as existsSync16 } from "fs";
20361
20448
  import { homedir as homedir6 } from "os";
20362
20449
  var PROMPTS = {
20363
20450
  location: "Choose where the new project goes",
@@ -20375,7 +20462,7 @@ function folderDialog() {
20375
20462
  let cancelled = false;
20376
20463
  const ask = async (start, purpose) => {
20377
20464
  const typed = start === "~" || start.startsWith("~/") ? `${homedir6()}${start.slice(1)}` : start;
20378
- const from = typed.startsWith("/") && existsSync15(typed) ? typed : homedir6();
20465
+ const from = typed.startsWith("/") && existsSync16(typed) ? typed : homedir6();
20379
20466
  cancelled = false;
20380
20467
  const p = Bun.spawn(["osascript", ...SCRIPT.flatMap((line) => ["-e", line]), PROMPTS[purpose], from], {
20381
20468
  stdout: "pipe",
@@ -20452,9 +20539,9 @@ class GrantCodes {
20452
20539
 
20453
20540
  // ../daemon/src/core/helper.ts
20454
20541
  import { createHash as createHash3 } from "crypto";
20455
- import { existsSync as existsSync16 } from "fs";
20542
+ import { existsSync as existsSync17 } from "fs";
20456
20543
  import { chmod, copyFile as copyFile3, mkdir as mkdir2, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
20457
- import { basename as basename4, dirname as dirname5, join as join14 } from "path";
20544
+ import { basename as basename5, dirname as dirname5, join as join14 } from "path";
20458
20545
  var EXECUTABLE = "Toyon";
20459
20546
  function helperWanted(where) {
20460
20547
  return where.platform === "darwin" && !where.cloud && where.port === DAEMON_DEFAULT_PORT && where.home === undefined;
@@ -20533,7 +20620,7 @@ class Helper {
20533
20620
  }
20534
20621
  async write() {
20535
20622
  const { spec } = this;
20536
- if (!existsSync16(spec.stub))
20623
+ if (!existsSync17(spec.stub))
20537
20624
  return "skipped";
20538
20625
  const stubHash = createHash3("sha256").update(await readFile2(spec.stub)).digest("hex").slice(0, 16);
20539
20626
  const { plist, script } = helperFiles(spec, stubHash);
@@ -20541,7 +20628,7 @@ class Helper {
20541
20628
  const resources = join14(contents, "Resources");
20542
20629
  const stub = join14(contents, "MacOS", EXECUTABLE);
20543
20630
  const scriptFile = join14(resources, "launch.sh");
20544
- if (existsSync16(stub) && existsSync16(scriptFile) && await readFile2(scriptFile, "utf8") === script)
20631
+ if (existsSync17(stub) && existsSync17(scriptFile) && await readFile2(scriptFile, "utf8") === script)
20545
20632
  return "kept";
20546
20633
  await mkdir2(join14(contents, "MacOS"), { recursive: true });
20547
20634
  await mkdir2(resources, { recursive: true });
@@ -20549,7 +20636,7 @@ class Helper {
20549
20636
  await writeFile2(scriptFile, script, { mode: 493 });
20550
20637
  await copyFile3(spec.stub, stub);
20551
20638
  await chmod(stub, 493);
20552
- if (!existsSync16(join14(resources, "AppIcon.icns")))
20639
+ if (!existsSync17(join14(resources, "AppIcon.icns")))
20553
20640
  await this.drawIcon(resources);
20554
20641
  await this.run(["xattr", "-cr", spec.dir]);
20555
20642
  if (!await this.run(["codesign", "--force", "--deep", "-s", "-", spec.dir])) {
@@ -20564,8 +20651,8 @@ class Helper {
20564
20651
  const set = join14(tmp, "AppIcon.iconset");
20565
20652
  await mkdir2(set, { recursive: true });
20566
20653
  await this.run(["qlmanage", "-t", "-s", "1024", "-o", tmp, svg]);
20567
- const big = join14(tmp, `${basename4(svg)}.png`);
20568
- if (existsSync16(big)) {
20654
+ const big = join14(tmp, `${basename5(svg)}.png`);
20655
+ if (existsSync17(big)) {
20569
20656
  for (const size of [16, 32, 64, 128, 256, 512, 1024]) {
20570
20657
  await this.run(["sips", "-z", String(size), String(size), big, "--out", join14(set, `icon_${size}x${size}.png`)]);
20571
20658
  }
@@ -21008,7 +21095,7 @@ class SelfWatch {
21008
21095
 
21009
21096
  // ../daemon/src/core/state.ts
21010
21097
  import { randomBytes as randomBytes6 } from "crypto";
21011
- import { existsSync as existsSync17, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
21098
+ import { existsSync as existsSync18, readFileSync as readFileSync12, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
21012
21099
  var TRUSTED_MAX = 16;
21013
21100
  var DAEMON_GROUPS = "daemon";
21014
21101
  var GROUPS_SAVE_MS = 250;
@@ -21017,7 +21104,7 @@ var empty = { repos: [], worktrees: [], sessions: {} };
21017
21104
  function loadState(paths) {
21018
21105
  ensureDirs(paths);
21019
21106
  const STATE_FILE = paths.stateFile;
21020
- if (!existsSync17(STATE_FILE))
21107
+ if (!existsSync18(STATE_FILE))
21021
21108
  return structuredClone(empty);
21022
21109
  let raw;
21023
21110
  try {
@@ -21069,7 +21156,7 @@ function saveState(paths, state) {
21069
21156
  function loadOrCreateToken(paths) {
21070
21157
  ensureDirs(paths);
21071
21158
  const TOKEN_FILE = paths.tokenFile;
21072
- if (existsSync17(TOKEN_FILE))
21159
+ if (existsSync18(TOKEN_FILE))
21073
21160
  return readFileSync12(TOKEN_FILE, "utf8").trim();
21074
21161
  const token = randomBytes6(32).toString("hex");
21075
21162
  writeFileSync8(TOKEN_FILE, token, { mode: 384 });
@@ -22104,7 +22191,7 @@ async function loadProjectTypescript(root) {
22104
22191
  }
22105
22192
 
22106
22193
  // ../daemon/src/drafts/store.ts
22107
- import { existsSync as existsSync18, readFileSync as readFileSync13, renameSync as renameSync4, writeFileSync as writeFileSync9 } from "fs";
22194
+ import { existsSync as existsSync19, readFileSync as readFileSync13, renameSync as renameSync4, writeFileSync as writeFileSync9 } from "fs";
22108
22195
  var SAVE_DELAY_MS = 1000;
22109
22196
 
22110
22197
  class DraftStore {
@@ -22241,7 +22328,7 @@ class DraftStore {
22241
22328
  }
22242
22329
  }
22243
22330
  function readRecord(file) {
22244
- if (!existsSync18(file))
22331
+ if (!existsSync19(file))
22245
22332
  return [];
22246
22333
  try {
22247
22334
  const raw = JSON.parse(readFileSync13(file, "utf8"));
@@ -22498,10 +22585,10 @@ class ExecService {
22498
22585
 
22499
22586
  // ../daemon/src/files/open.ts
22500
22587
  import { stat as stat3 } from "fs/promises";
22501
- import { basename as basename5, resolve as resolve7 } from "path";
22588
+ import { basename as basename6, resolve as resolve7 } from "path";
22502
22589
 
22503
22590
  // ../daemon/src/repos/browse.ts
22504
- import { existsSync as existsSync19, statSync as statSync6 } from "fs";
22591
+ import { existsSync as existsSync20, statSync as statSync6 } from "fs";
22505
22592
  import { readdir } from "fs/promises";
22506
22593
  import { homedir as homedir8 } from "os";
22507
22594
  import { dirname as dirname6, join as join16 } from "path";
@@ -22518,7 +22605,7 @@ function holdsNothing(names) {
22518
22605
  }
22519
22606
  async function describeFolder(abs) {
22520
22607
  const path = collapseTilde(abs);
22521
- if (existsSync19(join16(abs, ".git")))
22608
+ if (existsSync20(join16(abs, ".git")))
22522
22609
  return { path, kind: "project" };
22523
22610
  try {
22524
22611
  return { path, kind: holdsNothing(await readdir(abs)) ? "empty" : "folder" };
@@ -22533,10 +22620,10 @@ function describe2(typed, parent) {
22533
22620
  isDir = statSync6(typed).isDirectory();
22534
22621
  } catch {}
22535
22622
  return {
22536
- exists: existsSync19(typed),
22623
+ exists: existsSync20(typed),
22537
22624
  isDir,
22538
- isRepo: isDir && existsSync19(join16(typed, ".git")),
22539
- parentExists: existsSync19(parent)
22625
+ isRepo: isDir && existsSync20(join16(typed, ".git")),
22626
+ parentExists: existsSync20(parent)
22540
22627
  };
22541
22628
  }
22542
22629
  async function browsePath(raw) {
@@ -22561,7 +22648,7 @@ async function browsePath(raw) {
22561
22648
  if (!name.toLowerCase().startsWith(prefix))
22562
22649
  continue;
22563
22650
  const abs = join16(dir, name);
22564
- entries.push({ path: collapseTilde(abs), name, isRepo: existsSync19(join16(abs, ".git")) });
22651
+ entries.push({ path: collapseTilde(abs), name, isRepo: existsSync20(join16(abs, ".git")) });
22565
22652
  }
22566
22653
  entries.sort((a, b) => a.isRepo !== b.isRepo ? a.isRepo ? -1 : 1 : a.name.localeCompare(b.name));
22567
22654
  return { entries: entries.slice(0, MAX_ENTRIES), target };
@@ -23410,7 +23497,7 @@ class OpenService {
23410
23497
  return hit ? { kind: "file", worktreeId: hit.id, path: path.slice(hit.root.length + 1) } : null;
23411
23498
  }
23412
23499
  async grant(path, size, mtimeMs) {
23413
- const name = basename5(path);
23500
+ const name = basename6(path);
23414
23501
  if (this.d.refused.some((r) => within2(path, canonical(resolve7(r))))) {
23415
23502
  throw new UserError(`${name} is inside Toyon's own folder, which is not for editing`);
23416
23503
  }
@@ -23630,7 +23717,6 @@ function prNumberOf(url) {
23630
23717
 
23631
23718
  // ../daemon/src/preview/errors.ts
23632
23719
  var KEPT = 5;
23633
- var ROWS_MAX = 3;
23634
23720
  var TWICE_MS = 1000;
23635
23721
 
23636
23722
  class PageErrorService {
@@ -23638,75 +23724,35 @@ class PageErrorService {
23638
23724
  held = new Map;
23639
23725
  constructor(d) {
23640
23726
  this.d = d;
23641
- d.hub.on("agent", (worktreeId, _seq, event) => this.heard(worktreeId, event));
23642
23727
  d.hub.on("worktreesChanged", () => {
23643
23728
  for (const id of this.held.keys())
23644
23729
  if (!d.known(id))
23645
23730
  this.held.delete(id);
23646
23731
  });
23647
23732
  }
23648
- of(worktreeId) {
23649
- let h = this.held.get(worktreeId);
23650
- if (!h) {
23651
- h = { recent: [], running: false, noted: new Set, rows: 0 };
23652
- this.held.set(worktreeId, h);
23653
- }
23654
- return h;
23655
- }
23656
- heard(worktreeId, event) {
23657
- const h = this.of(worktreeId);
23658
- switch (event.type) {
23659
- case "user-message":
23660
- case "fix-asked":
23661
- h.noted = new Set;
23662
- h.rows = 0;
23663
- break;
23664
- case "turn-start":
23665
- h.running = true;
23666
- break;
23667
- case "turn-end":
23668
- case "agent-error":
23669
- case "agent-auth-required":
23670
- h.running = false;
23671
- this.flush(worktreeId, h, event.ts);
23672
- break;
23673
- }
23674
- }
23675
23733
  report(worktreeId, message) {
23676
23734
  if (!this.d.known(worktreeId)) {
23677
23735
  log.debug("pages", `an error from unknown worktree ${worktreeId} was dropped`);
23678
23736
  return;
23679
23737
  }
23680
- const h = this.of(worktreeId);
23738
+ let h = this.held.get(worktreeId);
23739
+ if (!h) {
23740
+ h = [];
23741
+ this.held.set(worktreeId, h);
23742
+ }
23681
23743
  const now = this.d.now?.() ?? Date.now();
23682
- const last = h.recent.at(-1);
23744
+ const last = h.at(-1);
23683
23745
  if (last && last.message === message && now - last.ts < TWICE_MS)
23684
23746
  return;
23685
- h.recent.push({ message, ts: now });
23686
- if (h.recent.length > KEPT)
23687
- h.recent.splice(0, h.recent.length - KEPT);
23688
- if (!h.running)
23689
- this.flush(worktreeId, h, now);
23690
- }
23691
- flush(worktreeId, h, ts) {
23692
- const fresh = [...new Set(h.recent.map((e) => e.message))].filter((m) => !h.noted.has(m));
23693
- if (fresh.length === 0 || h.rows >= ROWS_MAX)
23694
- return;
23695
- const agent = this.d.runtime.agentFor(worktreeId);
23696
- if (!agent)
23697
- return;
23698
- for (const m of fresh)
23699
- h.noted.add(m);
23700
- h.rows++;
23701
- agent.note({ type: "page-error", errors: fresh, ts });
23747
+ h.push({ message, ts: now });
23748
+ if (h.length > KEPT)
23749
+ h.splice(0, h.length - KEPT);
23702
23750
  }
23703
23751
  loaded(worktreeId) {
23704
- const h = this.held.get(worktreeId);
23705
- if (h)
23706
- h.recent = [];
23752
+ this.held.delete(worktreeId);
23707
23753
  }
23708
23754
  recent(worktreeId) {
23709
- return this.held.get(worktreeId)?.recent.map((e) => e.message) ?? [];
23755
+ return this.held.get(worktreeId)?.map((e) => e.message) ?? [];
23710
23756
  }
23711
23757
  ambient(worktreeId) {
23712
23758
  const errors = this.recent(worktreeId);
@@ -23718,122 +23764,6 @@ ${errors.map((e) => `- ${e}`).join(`
23718
23764
  }
23719
23765
  }
23720
23766
 
23721
- // ../daemon/src/preview/render.ts
23722
- import { randomUUID as randomUUID2 } from "crypto";
23723
- var WAIT_MS2 = 1e4;
23724
- var PATH_MAX = 2048;
23725
- var refused = (text) => ({ text, isError: true });
23726
-
23727
- class RenderService {
23728
- d;
23729
- courier = () => false;
23730
- pending = new Map;
23731
- out = new Set;
23732
- constructor(d) {
23733
- this.d = d;
23734
- }
23735
- tool() {
23736
- return {
23737
- name: PREVIEW_TOOL,
23738
- description: "See whether a page of this worktree's preview renders without throwing. Pass `path` (for example `/about`): a browser the user has open loads it beside what they are looking at and reports the uncaught errors and unhandled rejections it threw in the moment after load. With no path, reports what the preview has thrown since it last loaded, without rendering. It does not describe what the page looks like, and it is not a substitute for the user's own checks.",
23739
- inputSchema: {
23740
- type: "object",
23741
- properties: {
23742
- path: {
23743
- type: "string",
23744
- description: "the route to render, starting with `/`; absent to report without rendering"
23745
- }
23746
- },
23747
- additionalProperties: false
23748
- }
23749
- };
23750
- }
23751
- async call(worktreeId, args) {
23752
- const a = args && typeof args === "object" && !Array.isArray(args) ? args : {};
23753
- if (a.path !== undefined && typeof a.path !== "string")
23754
- return refused("`path` is a string: a route, like `/about`.");
23755
- const path = a.path?.trim();
23756
- if (path !== undefined && (!/^(?:\/(?![/\\])|[?#])/.test(path) || path.length > PATH_MAX)) {
23757
- return refused("`path` is a route of the preview, starting with `/`, like `/about`.");
23758
- }
23759
- const standing = this.d.runtime.previewStanding(worktreeId);
23760
- if (!standing)
23761
- return refused("This project has nothing to run, so there is no page to render.");
23762
- if (standing.status !== "running")
23763
- return refused(previewContext(standing) ?? "The preview is not running.");
23764
- if (path === undefined)
23765
- return { text: this.held(worktreeId) };
23766
- if (this.out.has(worktreeId))
23767
- return refused("A render is already under way; call again when it has returned.");
23768
- const result = await this.render(worktreeId, path);
23769
- if (!result)
23770
- return refused(`No browser has this worktree open right now, so nothing can render it. ${this.held(worktreeId)}`);
23771
- if (!result.ok)
23772
- return refused(`Could not render ${path}: ${result.reason ?? "the page did not report"}.`);
23773
- return { text: this.report(path, result) };
23774
- }
23775
- render(worktreeId, path) {
23776
- const id = randomUUID2();
23777
- return new Promise((resolve) => {
23778
- const wait = this.d.waitMs ?? WAIT_MS2;
23779
- const timer = setTimeout(() => settle({
23780
- worktreeId,
23781
- id,
23782
- ok: false,
23783
- reason: `the page did not report within ${Math.round(wait / 1000)}s`,
23784
- errors: []
23785
- }), wait);
23786
- const settle = (r) => {
23787
- if (!this.pending.delete(id))
23788
- return;
23789
- clearTimeout(timer);
23790
- this.out.delete(worktreeId);
23791
- resolve(r);
23792
- };
23793
- this.pending.set(id, { worktreeId, settle });
23794
- this.out.add(worktreeId);
23795
- if (!this.courier(worktreeId, { t: "render", worktreeId, id, path }))
23796
- settle(null);
23797
- });
23798
- }
23799
- rendered(msg) {
23800
- const p = this.pending.get(msg.id);
23801
- if (!p || p.worktreeId !== msg.worktreeId) {
23802
- log.debug("render", `an answer for no render out on ${msg.worktreeId} was dropped`);
23803
- return;
23804
- }
23805
- p.settle(msg);
23806
- }
23807
- held(worktreeId) {
23808
- const errors = this.d.pageErrors.recent(worktreeId);
23809
- if (errors.length === 0)
23810
- return "The preview has thrown nothing since it last loaded.";
23811
- return `The preview has thrown since it last loaded:
23812
- ${errors.map((e) => `- ${e}`).join(`
23813
- `)}`;
23814
- }
23815
- report(path, r) {
23816
- const title = r.title ? ` (title "${r.title}")` : "";
23817
- const landed = r.url ? pathOf(r.url) : null;
23818
- const elsewhere = landed && landed !== path ? ` It landed on ${landed}.` : "";
23819
- const head = `Rendered ${path} in the user's browser${title}.${elsewhere}`;
23820
- if (r.errors.length === 0)
23821
- return `${head} No errors in the moment after load.`;
23822
- const n = r.errors.length;
23823
- return `${head} ${n} ${n === 1 ? "error" : "errors"} in the moment after load:
23824
- ${r.errors.map((e) => `- ${e}`).join(`
23825
- `)}`;
23826
- }
23827
- }
23828
- function pathOf(url) {
23829
- try {
23830
- const u = new URL(url);
23831
- return `${u.pathname}${u.search}`;
23832
- } catch {
23833
- return null;
23834
- }
23835
- }
23836
-
23837
23767
  // ../daemon/src/runtime/setup.ts
23838
23768
  var SETUP_RING = 64 * 1024;
23839
23769
  var SETUP_COLS = 120;
@@ -23962,9 +23892,9 @@ class AfterLand {
23962
23892
  }
23963
23893
 
23964
23894
  // ../daemon/src/repos/registry.ts
23965
- import { existsSync as existsSync23, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
23895
+ import { existsSync as existsSync24, mkdirSync as mkdirSync7, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
23966
23896
  import { homedir as homedir10 } from "os";
23967
- import { basename as basename7, dirname as dirname8, join as join21 } from "path";
23897
+ import { basename as basename8, dirname as dirname8, join as join21 } from "path";
23968
23898
 
23969
23899
  // ../daemon/src/git/base.ts
23970
23900
  async function resolveBase(repoPath, defaultBranch, route) {
@@ -23975,7 +23905,7 @@ async function resolveBase(repoPath, defaultBranch, route) {
23975
23905
  }
23976
23906
 
23977
23907
  // ../daemon/src/repos/config.ts
23978
- import { closeSync, existsSync as existsSync20, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, readSync, statSync as statSync7 } from "fs";
23908
+ import { closeSync, existsSync as existsSync21, openSync as openSync2, readdirSync as readdirSync7, readFileSync as readFileSync14, readSync, statSync as statSync7 } from "fs";
23979
23909
  import { join as join18 } from "path";
23980
23910
 
23981
23911
  // ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
@@ -25340,7 +25270,7 @@ function applyEdits(text, edits) {
25340
25270
 
25341
25271
  // ../daemon/src/repos/config.ts
25342
25272
  var PLACES = [CONFIG_FILES.folder, CONFIG_FILES.root];
25343
- var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync20(join18(repoPath, rel)));
25273
+ var present = (repoPath, place) => [place.shared, place.local].filter((rel) => existsSync21(join18(repoPath, rel)));
25344
25274
  var isObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25345
25275
  function parseSettings(text) {
25346
25276
  const errors = [];
@@ -25425,7 +25355,7 @@ function configTarget(repoPath, made) {
25425
25355
  function configBody(repoPath, rel, config) {
25426
25356
  const place = PLACES.find((p) => p.local === rel);
25427
25357
  const abs = place && join18(repoPath, place.shared);
25428
- if (!abs || !existsSync20(abs))
25358
+ if (!abs || !existsSync21(abs))
25429
25359
  return config;
25430
25360
  let shared;
25431
25361
  try {
@@ -25473,7 +25403,7 @@ function detectConfig(repoPath) {
25473
25403
  if (explicit)
25474
25404
  log.warn("config", `${repoPath}: ${explicit.reason}; detecting instead`);
25475
25405
  const pkgPath = join18(repoPath, "package.json");
25476
- if (existsSync20(pkgPath)) {
25406
+ if (existsSync21(pkgPath)) {
25477
25407
  const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
25478
25408
  const scripts = pkg.scripts ?? {};
25479
25409
  const runner = detectRunner(repoPath, pkg.packageManager);
@@ -25495,7 +25425,7 @@ function detectConfig(repoPath) {
25495
25425
  };
25496
25426
  }
25497
25427
  }
25498
- if (existsSync20(join18(repoPath, "start.sh"))) {
25428
+ if (existsSync21(join18(repoPath, "start.sh"))) {
25499
25429
  return { config: { run: { app: "./start.sh" } }, needsSetup: true, from: "start.sh" };
25500
25430
  }
25501
25431
  const assumed = assumeNothingToRun(repoPath);
@@ -25538,7 +25468,7 @@ function assumeNothingToRun(repoPath) {
25538
25468
  return marker && !servesPage(repoPath) ? marker : undefined;
25539
25469
  }
25540
25470
  function markerIn(repoPath) {
25541
- const named = NO_PAGE_MARKERS.find((f) => existsSync20(join18(repoPath, f)));
25471
+ const named = NO_PAGE_MARKERS.find((f) => existsSync21(join18(repoPath, f)));
25542
25472
  if (named)
25543
25473
  return named;
25544
25474
  let entries;
@@ -25550,7 +25480,7 @@ function markerIn(repoPath) {
25550
25480
  return entries.find((f) => NO_PAGE_EXTENSIONS.some((ext) => f.endsWith(ext)));
25551
25481
  }
25552
25482
  function servesPage(repoPath) {
25553
- if (WEB_FILES.some((f) => existsSync20(join18(repoPath, f))))
25483
+ if (WEB_FILES.some((f) => existsSync21(join18(repoPath, f))))
25554
25484
  return true;
25555
25485
  if (/^web:/m.test(head(join18(repoPath, "Procfile"))))
25556
25486
  return true;
@@ -25587,7 +25517,7 @@ function mentions(text, name) {
25587
25517
  return new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`, "i").test(text);
25588
25518
  }
25589
25519
  function head(path) {
25590
- if (!existsSync20(path))
25520
+ if (!existsSync21(path))
25591
25521
  return "";
25592
25522
  try {
25593
25523
  const fd = openSync2(path, "r");
@@ -25628,7 +25558,7 @@ function isLocalDatabase(url) {
25628
25558
  }
25629
25559
  function detectServices(repoPath) {
25630
25560
  const out = {};
25631
- const compose = COMPOSE_FILES.find((f) => existsSync20(join18(repoPath, f)));
25561
+ const compose = COMPOSE_FILES.find((f) => existsSync21(join18(repoPath, f)));
25632
25562
  if (compose)
25633
25563
  out.compose = compose;
25634
25564
  for (const file of ENV_FILES) {
@@ -25667,21 +25597,21 @@ function inferPaths(cmd, repoPath, depth = 1) {
25667
25597
  const t = tokens[i];
25668
25598
  if (t === "cd") {
25669
25599
  const dir = tokens[i + 1];
25670
- if (dir && !dir.startsWith("-") && existsSync20(join18(repoPath, dir)))
25600
+ if (dir && !dir.startsWith("-") && existsSync21(join18(repoPath, dir)))
25671
25601
  add(dir);
25672
25602
  continue;
25673
25603
  }
25674
25604
  if (t.startsWith("-") || t.startsWith("$"))
25675
25605
  continue;
25676
25606
  const mod = /^([A-Za-z_]\w*)(?:\.\w+)*:\w+$/.exec(t);
25677
- if (mod?.[1] && existsSync20(join18(repoPath, mod[1]))) {
25607
+ if (mod?.[1] && existsSync21(join18(repoPath, mod[1]))) {
25678
25608
  add(mod[1]);
25679
25609
  continue;
25680
25610
  }
25681
25611
  const rel = t.replace(/^\.\//, "");
25682
25612
  if (rel.startsWith("/") || rel.startsWith("..") || rel.includes("="))
25683
25613
  continue;
25684
- if (!existsSync20(join18(repoPath, rel)))
25614
+ if (!existsSync21(join18(repoPath, rel)))
25685
25615
  continue;
25686
25616
  add(rel);
25687
25617
  if (depth > 0 && rel.endsWith(".sh")) {
@@ -25708,9 +25638,9 @@ function procCommand(runner, script, body) {
25708
25638
  return `${base}${runner === "npm" ? " --" : ""} ${flag}`;
25709
25639
  }
25710
25640
  function detectPages(repoPath, scripts, runner) {
25711
- if (!scripts.build || !existsSync20(join18(repoPath, "functions")))
25641
+ if (!scripts.build || !existsSync21(join18(repoPath, "functions")))
25712
25642
  return null;
25713
- const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) => existsSync20(f));
25643
+ const cfg = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"].map((f) => join18(repoPath, f)).find((f) => existsSync21(f));
25714
25644
  if (!cfg)
25715
25645
  return null;
25716
25646
  const out = readFileSync14(cfg, "utf8").match(/pages_build_output_dir"?\s*[:=]\s*"([^"]+)"/)?.[1];
@@ -25719,17 +25649,17 @@ function detectPages(repoPath, scripts, runner) {
25719
25649
  return { web: `${runner} run build && npx wrangler pages dev ${out} --ip 127.0.0.1 --port $PORT` };
25720
25650
  }
25721
25651
  function detectRunner(repoPath, packageManager) {
25722
- if (existsSync20(join18(repoPath, "bun.lock")) || existsSync20(join18(repoPath, "bun.lockb")))
25652
+ if (existsSync21(join18(repoPath, "bun.lock")) || existsSync21(join18(repoPath, "bun.lockb")))
25723
25653
  return "bun";
25724
- if (existsSync20(join18(repoPath, "pnpm-lock.yaml")))
25654
+ if (existsSync21(join18(repoPath, "pnpm-lock.yaml")))
25725
25655
  return "pnpm";
25726
- if (existsSync20(join18(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
25656
+ if (existsSync21(join18(repoPath, "yarn.lock")) || typeof packageManager === "string" && packageManager.startsWith("yarn"))
25727
25657
  return "yarn";
25728
25658
  return "npm";
25729
25659
  }
25730
25660
 
25731
25661
  // ../daemon/src/repos/create.ts
25732
- import { existsSync as existsSync21, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
25662
+ import { existsSync as existsSync22, realpathSync as realpathSync2, statSync as statSync8 } from "fs";
25733
25663
  import { appendFile as appendFile2, mkdir as mkdir3, rm as rm2 } from "fs/promises";
25734
25664
  import { homedir as homedir9 } from "os";
25735
25665
  import { dirname as dirname7, join as join19, resolve as resolve8 } from "path";
@@ -25744,13 +25674,13 @@ function planProject(opts, home = homedir9()) {
25744
25674
  const first = firstProjectsFolder(opts.parent, home);
25745
25675
  const parent = first ?? resolveParent(opts.parent);
25746
25676
  const dir = join19(parent, opts.name.trim());
25747
- if (existsSync21(dir))
25677
+ if (existsSync22(dir))
25748
25678
  throw new UserError(`${dir} already exists`);
25749
25679
  return { parent, dir, makeParent: first !== null };
25750
25680
  }
25751
25681
  function firstProjectsFolder(raw, home) {
25752
25682
  const typed = expandTilde(raw.trim()).replace(/\/+$/, "");
25753
- if (typed !== join19(home, PROJECTS_FOLDER) || existsSync21(typed) || !existsSync21(home))
25683
+ if (typed !== join19(home, PROJECTS_FOLDER) || existsSync22(typed) || !existsSync22(home))
25754
25684
  return null;
25755
25685
  return join19(realpathSync2(home), PROJECTS_FOLDER);
25756
25686
  }
@@ -25760,10 +25690,10 @@ function planInPlace(opts) {
25760
25690
  throw new UserError(`${opts.name} is not a folder name`);
25761
25691
  }
25762
25692
  const typed = join19(resolveParent(opts.parent), name);
25763
- if (!existsSync21(typed) || !statSync8(typed).isDirectory())
25693
+ if (!existsSync22(typed) || !statSync8(typed).isDirectory())
25764
25694
  throw new UserError(`${typed} is not a folder`);
25765
25695
  const dir = realpathSync2(typed);
25766
- if (existsSync21(join19(dir, ".git")))
25696
+ if (existsSync22(join19(dir, ".git")))
25767
25697
  throw new UserError(`${dir} is already a project; open it instead`);
25768
25698
  return { parent: dirname7(dir), dir, makeParent: false };
25769
25699
  }
@@ -25771,7 +25701,7 @@ function resolveParent(raw) {
25771
25701
  const typed = expandTilde(raw.trim());
25772
25702
  if (!typed.startsWith("/"))
25773
25703
  throw new UserError(`${raw} is not a folder path`);
25774
- if (!existsSync21(typed))
25704
+ if (!existsSync22(typed))
25775
25705
  throw new UserError(`${raw} does not exist; make it first`);
25776
25706
  if (!statSync8(typed).isDirectory())
25777
25707
  throw new UserError(`${raw} is not a folder`);
@@ -25888,8 +25818,8 @@ async function ok(p, what) {
25888
25818
  }
25889
25819
 
25890
25820
  // ../daemon/src/repos/watcher.ts
25891
- import { existsSync as existsSync22, watch } from "fs";
25892
- import { basename as basename6, join as join20 } from "path";
25821
+ import { existsSync as existsSync23, watch } from "fs";
25822
+ import { basename as basename7, join as join20 } from "path";
25893
25823
  function watchDefaultBranch(repoPath, branch, onMove) {
25894
25824
  const last = { local: null, upstream: null };
25895
25825
  const read = async () => {
@@ -25953,11 +25883,11 @@ function watchDefaultBranch(repoPath, branch, onMove) {
25953
25883
  let mid = null;
25954
25884
  const armInner = () => {
25955
25885
  inner?.close();
25956
- inner = existsSync22(mine) ? tryWatch(mine) : null;
25886
+ inner = existsSync23(mine) ? tryWatch(mine) : null;
25957
25887
  };
25958
25888
  const armMid = () => {
25959
25889
  mid?.close();
25960
- mid = existsSync22(remotes) ? tryWatch(remotes, (_event, filename) => {
25890
+ mid = existsSync23(remotes) ? tryWatch(remotes, (_event, filename) => {
25961
25891
  if (!filename || filename === remote.out)
25962
25892
  armInner();
25963
25893
  schedule();
@@ -26035,7 +25965,7 @@ function watchWorktreeDir(repoPath, onChange) {
26035
25965
  }
26036
25966
  function watchConfigFile(repoPath, onChange) {
26037
25967
  const atRoot = new Set([CONFIG_FILES.root.shared, CONFIG_FILES.root.local, CONFIG_DIR]);
26038
- const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename6(rel)));
25968
+ const inDir = new Set([CONFIG_FILES.folder.shared, CONFIG_FILES.folder.local].map((rel) => basename7(rel)));
26039
25969
  const dir = join20(repoPath, CONFIG_DIR);
26040
25970
  let timer = null;
26041
25971
  let outer = null;
@@ -26051,7 +25981,7 @@ function watchConfigFile(repoPath, onChange) {
26051
25981
  const armInner = () => {
26052
25982
  inner?.close();
26053
25983
  inner = null;
26054
- if (!existsSync22(dir))
25984
+ if (!existsSync23(dir))
26055
25985
  return;
26056
25986
  try {
26057
25987
  inner = watch(dir, (_event, filename) => {
@@ -26105,7 +26035,7 @@ class RepoRegistry {
26105
26035
  }
26106
26036
  async boot() {
26107
26037
  const { state } = this.d;
26108
- const gone = state.worktrees.filter((wt) => !existsSync23(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
26038
+ const gone = state.worktrees.filter((wt) => !existsSync24(wt.path) || !state.repos.some((r) => r.id === wt.repoId));
26109
26039
  for (const wt of gone)
26110
26040
  await this.d.worktrees.forgetGone(wt);
26111
26041
  for (const repo of state.repos) {
@@ -26173,7 +26103,7 @@ class RepoRegistry {
26173
26103
  return `${repo.name} is set up now`;
26174
26104
  if (main.promptedAt || main.lastTurn)
26175
26105
  return `${repo.name} has a chat now`;
26176
- if (existsSync23(join21(repo.path, ".claude")))
26106
+ if (existsSync24(join21(repo.path, ".claude")))
26177
26107
  return `an agent has already run in ${repo.name}`;
26178
26108
  if ((await statusFiles(repo.path)).length > 0 || !await treeEmpty(repo.path)) {
26179
26109
  return `${repo.name} has files in it now`;
@@ -26254,7 +26184,7 @@ class RepoRegistry {
26254
26184
  }
26255
26185
  async register(rawPath, made) {
26256
26186
  const path = expandTilde(rawPath);
26257
- if (!existsSync23(path))
26187
+ if (!existsSync24(path))
26258
26188
  throw new UserError(`${path} does not exist`);
26259
26189
  if (!await isGitRepo(path))
26260
26190
  throw new UserError(`${path} is not a git repository`);
@@ -26268,7 +26198,7 @@ class RepoRegistry {
26268
26198
  const repo = {
26269
26199
  id: shortId(),
26270
26200
  path: root,
26271
- name: basename7(root),
26201
+ name: basename8(root),
26272
26202
  defaultBranch: await defaultBranch(root),
26273
26203
  config: detected.config,
26274
26204
  configFile: configTarget(root, !!made),
@@ -26328,7 +26258,7 @@ class RepoRegistry {
26328
26258
  throw new UserError(current.reason);
26329
26259
  const was = configTarget(repo.path, !!repo.made);
26330
26260
  const rel = configSibling(was, kind);
26331
- const moved = rel !== was && existsSync23(join21(repo.path, was));
26261
+ const moved = rel !== was && existsSync24(join21(repo.path, was));
26332
26262
  if (moved && !(kind === "local" && await isTracked(repo.path, was))) {
26333
26263
  try {
26334
26264
  rmSync4(join21(repo.path, was));
@@ -26444,7 +26374,7 @@ class RepoRegistry {
26444
26374
  }
26445
26375
  placeConfig(repo) {
26446
26376
  const rel = configTarget(repo.path, !!repo.made);
26447
- if (isLocalConfigFile(rel) && existsSync23(join21(repo.path, rel))) {
26377
+ if (isLocalConfigFile(rel) && existsSync24(join21(repo.path, rel))) {
26448
26378
  fireAndForget(repo.id, excludeFromGit(repo.path, rel), "exclude local settings");
26449
26379
  }
26450
26380
  if (repo.configFile === rel)
@@ -28023,7 +27953,7 @@ function isAlive(pid) {
28023
27953
  }
28024
27954
 
28025
27955
  // ../daemon/src/runtime/bridge-script.ts
28026
- import { existsSync as existsSync24, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
27956
+ import { existsSync as existsSync25, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
28027
27957
  var LEARNED_MAX = 8;
28028
27958
 
28029
27959
  class BridgeScript {
@@ -28053,7 +27983,7 @@ class BridgeScript {
28053
27983
  this.cache = null;
28054
27984
  }
28055
27985
  get() {
28056
- if (!existsSync24(this.path))
27986
+ if (!existsSync25(this.path))
28057
27987
  return "// toyon bridge not built";
28058
27988
  const s = statSync9(this.path);
28059
27989
  const stamp = `${s.mtimeMs}:${s.size}`;
@@ -28086,7 +28016,7 @@ import { homedir as homedir12, hostname as hostname2 } from "os";
28086
28016
  import { randomBytes as randomBytes7 } from "crypto";
28087
28017
 
28088
28018
  // ../daemon/src/agent/config.ts
28089
- import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
28019
+ import { existsSync as existsSync26, readFileSync as readFileSync16 } from "fs";
28090
28020
  import { homedir as homedir11 } from "os";
28091
28021
  import { join as join23 } from "path";
28092
28022
  function agentConfigFiles(spec, repoPath, home = homedir11()) {
@@ -28094,7 +28024,7 @@ function agentConfigFiles(spec, repoPath, home = homedir11()) {
28094
28024
  id,
28095
28025
  label,
28096
28026
  path,
28097
- exists: existsSync25(path)
28027
+ exists: existsSync26(path)
28098
28028
  });
28099
28029
  if (spec.id === "claude") {
28100
28030
  return [
@@ -28126,7 +28056,7 @@ function describeAgentConfig(spec, repoPath, home = homedir11()) {
28126
28056
  return { agent: spec.id, files, servers };
28127
28057
  }
28128
28058
  function readJson(path) {
28129
- if (!existsSync25(path))
28059
+ if (!existsSync26(path))
28130
28060
  return null;
28131
28061
  try {
28132
28062
  const v = JSON.parse(readFileSync16(path, "utf8"));
@@ -28147,7 +28077,7 @@ function serversOf(raw, scope) {
28147
28077
  });
28148
28078
  }
28149
28079
  function codexServers(path) {
28150
- if (!existsSync25(path))
28080
+ if (!existsSync26(path))
28151
28081
  return [];
28152
28082
  const out = [];
28153
28083
  let current = null;
@@ -28780,12 +28710,6 @@ var handlers = {
28780
28710
  "page-loaded"(msg, _ctx, s) {
28781
28711
  s.pageErrors.loaded(msg.worktreeId);
28782
28712
  },
28783
- "fix-page"(msg, _ctx, s) {
28784
- s.fix.pressPage(msg.worktreeId);
28785
- },
28786
- rendered(msg, _ctx, s) {
28787
- s.render.rendered(msg);
28788
- },
28789
28713
  async chat(msg, ctx, s) {
28790
28714
  await sending(s, ctx, msg, msg.attachments, () => s.worktrees.send(msg.worktreeId, {
28791
28715
  text: msg.text,
@@ -29045,6 +28969,12 @@ var handlers = {
29045
28969
  async "reveal-path"(msg, _ctx, s) {
29046
28970
  s.files.revealPath(await s.opens.where(msg.path));
29047
28971
  },
28972
+ async "reveal-attachment"(msg, _ctx, s) {
28973
+ const path = await s.attachments.locate(msg.worktreeId, msg.file, (file) => s.worktrees.archivedAttachment(msg.worktreeId, file));
28974
+ if (!path)
28975
+ throw new UserError("that attachment is no longer on this machine");
28976
+ s.files.revealPath(path);
28977
+ },
29048
28978
  async "discard-files"(msg, _ctx, s) {
29049
28979
  try {
29050
28980
  for (const path of msg.paths)
@@ -29201,9 +29131,12 @@ var handlers = {
29201
29131
  s.files.revealPath(file.path);
29202
29132
  },
29203
29133
  async "agent-logout"(msg, _ctx, s) {
29204
- await s.accounts.logout(msg.agent);
29134
+ await s.readiness.logout(msg.agent);
29205
29135
  s.hub.emit("agentsChanged");
29206
29136
  },
29137
+ "agent-check"(msg, _ctx, s) {
29138
+ fireAndForget("agents", s.readiness.check(msg.agent), "agent check");
29139
+ },
29207
29140
  "set-default-agent"(msg, _ctx, s) {
29208
29141
  s.agents.require(msg.agent);
29209
29142
  s.state.setDefaultAgent(msg.agent);
@@ -29326,7 +29259,7 @@ async function dispatch(msg, ctx, s) {
29326
29259
  }
29327
29260
 
29328
29261
  // ../daemon/src/server/http.ts
29329
- import { existsSync as existsSync26 } from "fs";
29262
+ import { existsSync as existsSync27 } from "fs";
29330
29263
  import { hostname } from "os";
29331
29264
  import { join as join24 } from "path";
29332
29265
 
@@ -29631,7 +29564,7 @@ function createFetch(opts) {
29631
29564
  return new Response("unauthorized", { status: 401 });
29632
29565
  const id = url.pathname.slice("/uploads/".length);
29633
29566
  const path = opts.attachments.uploads.path(id);
29634
- if (!path || !existsSync26(path))
29567
+ if (!path || !existsSync27(path))
29635
29568
  return new Response("not found", { status: 404 });
29636
29569
  return new Response(Bun.file(path), {
29637
29570
  headers: { "cache-control": IMMUTABLE, ...attachedHeaders(opts.attachments.uploads.imageType(id)) }
@@ -29644,9 +29577,14 @@ function createFetch(opts) {
29644
29577
  const places = worktreeId && file && !extra ? [opts.attachments.fileFor(worktreeId, file), opts.archivedAttachment(worktreeId, file)] : [];
29645
29578
  if (worktreeId && file)
29646
29579
  await opts.attachments.whenWritten(worktreeId, file);
29647
- const path = places.find((p) => !!p && existsSync26(p));
29580
+ const path = places.find((p) => !!p && existsSync27(p));
29648
29581
  if (!path)
29649
29582
  return new Response("not found", { status: 404 });
29583
+ if (url.searchParams.has("where")) {
29584
+ return new Response(path, {
29585
+ headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" }
29586
+ });
29587
+ }
29650
29588
  return new Response(Bun.file(path), {
29651
29589
  headers: { "cache-control": IMMUTABLE, ...attachedHeaders(drawnType(file ?? "")) }
29652
29590
  });
@@ -29679,7 +29617,7 @@ function createFetch(opts) {
29679
29617
  const rel = url.pathname === "/" ? "/index.html" : url.pathname;
29680
29618
  const file = join24(opts.shellDist, rel.replaceAll("..", ""));
29681
29619
  const hashed = rel.startsWith("/assets/");
29682
- if (existsSync26(file) && Bun.file(file).size > 0) {
29620
+ if (existsSync27(file) && Bun.file(file).size > 0) {
29683
29621
  if (rel === "/manifest.json") {
29684
29622
  const { bar, ground } = opts.manifestColors();
29685
29623
  const manifest = { ...await Bun.file(file).json(), theme_color: bar, background_color: ground };
@@ -29699,7 +29637,7 @@ function createFetch(opts) {
29699
29637
  if (hashed)
29700
29638
  return new Response("not found", { status: 404 });
29701
29639
  const index = join24(opts.shellDist, "index.html");
29702
- if (existsSync26(index))
29640
+ if (existsSync27(index))
29703
29641
  return new Response(Bun.file(index), { headers: { "cache-control": NO_STORE2 } });
29704
29642
  return new Response("Toyon daemon running; shell not built (run: bun run build)", { status: 200 });
29705
29643
  }
@@ -29774,14 +29712,6 @@ function startServer(opts) {
29774
29712
  }
29775
29713
  };
29776
29714
  const send = (ws, msg) => raw(ws, JSON.stringify(msg));
29777
- s.render.courier = (worktreeId, msg) => {
29778
- const shells = [...sockets].filter((ws) => ws.data.authed && !ws.data.preview);
29779
- const ws = shells.find((w) => w.data.view === worktreeId) ?? shells.find((w) => w.data.subs.has(worktreeId));
29780
- if (!ws)
29781
- return false;
29782
- send(ws, msg);
29783
- return true;
29784
- };
29785
29715
  const broadcast = (msg) => {
29786
29716
  const json = JSON.stringify(msg);
29787
29717
  for (const ws of sockets)
@@ -29959,20 +29889,9 @@ function startServer(opts) {
29959
29889
  };
29960
29890
  s.hub.on("themesChanged", themesChanged);
29961
29891
  themesChanged();
29962
- const agentInfos = () => s.accounts.describe(s.agents.infos()).map((a) => {
29963
- const models = s.state.cachedOptions(a.id, "model");
29964
- const efforts = s.state.cachedOptions(a.id, "thought_level");
29965
- const spec = s.agents.get(a.id);
29966
- return {
29967
- ...a,
29968
- ...models.length > 0 ? { models } : {},
29969
- ...efforts.length > 0 ? { efforts } : {},
29970
- ...spec && !lacksQuickModel(spec, s.state) ? { quick: true } : {}
29971
- };
29972
- });
29973
29892
  const agentsMsg = () => ({
29974
29893
  t: "agents",
29975
- agents: agentInfos(),
29894
+ agents: s.readiness.describe(),
29976
29895
  defaultAgent: s.agents.defaultId(s.state.defaultAgent),
29977
29896
  agentChosen: s.state.defaultAgent !== undefined
29978
29897
  });
@@ -30010,7 +29929,7 @@ function startServer(opts) {
30010
29929
  trunks,
30011
29930
  themes: s.themes.themes,
30012
29931
  themePrefs: s.themes.prefs,
30013
- agents: agentInfos(),
29932
+ agents: s.readiness.describe(),
30014
29933
  defaultAgent: s.agents.defaultId(s.state.defaultAgent),
30015
29934
  agentChosen: s.state.defaultAgent !== undefined,
30016
29935
  home: homedir12(),
@@ -30193,9 +30112,9 @@ function startServer(opts) {
30193
30112
  }
30194
30113
 
30195
30114
  // ../daemon/src/themes/store.ts
30196
- import { existsSync as existsSync27, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync10, writeFileSync as writeFileSync11 } from "fs";
30115
+ import { existsSync as existsSync28, readdirSync as readdirSync8, readFileSync as readFileSync17, statSync as statSync10, writeFileSync as writeFileSync11 } from "fs";
30197
30116
  import { homedir as homedir13 } from "os";
30198
- import { basename as basename8, dirname as dirname9, join as join25, resolve as resolve9 } from "path";
30117
+ import { basename as basename9, dirname as dirname9, join as join25, resolve as resolve9 } from "path";
30199
30118
  var home = homedir13();
30200
30119
  var defaultExtensionDirs = [
30201
30120
  join25(home, ".vscode", "extensions"),
@@ -30280,7 +30199,7 @@ class ThemeStore {
30280
30199
  if (json && typeof json === "object" && typeof json.include === "string") {
30281
30200
  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");
30282
30201
  }
30283
- const base = slug(basename8(name).replace(/\.(json|jsonc)$/i, ""));
30202
+ const base = slug(basename9(name).replace(/\.(json|jsonc)$/i, ""));
30284
30203
  const theme = vscodeToTheme(json, {
30285
30204
  id: `file:${base}`,
30286
30205
  name: json?.name ?? base,
@@ -30315,7 +30234,7 @@ function readVscodeTheme(file, depth = 0) {
30315
30234
  return t;
30316
30235
  }
30317
30236
  function loadDir(dir) {
30318
- if (!existsSync27(dir))
30237
+ if (!existsSync28(dir))
30319
30238
  return [];
30320
30239
  const out = [];
30321
30240
  for (const f of readdirSync8(dir)) {
@@ -30337,7 +30256,7 @@ function loadDir(dir) {
30337
30256
  return out;
30338
30257
  }
30339
30258
  function discover(dir) {
30340
- if (!existsSync27(dir))
30259
+ if (!existsSync28(dir))
30341
30260
  return [];
30342
30261
  const out = [];
30343
30262
  let entries;
@@ -30350,7 +30269,7 @@ function discover(dir) {
30350
30269
  const extDir = join25(dir, name);
30351
30270
  const pkgFile = join25(extDir, "package.json");
30352
30271
  try {
30353
- if (!statSync10(extDir).isDirectory() || !existsSync27(pkgFile))
30272
+ if (!statSync10(extDir).isDirectory() || !existsSync28(pkgFile))
30354
30273
  continue;
30355
30274
  const pkg = JSON.parse(readFileSync17(pkgFile, "utf8"));
30356
30275
  const themes = pkg.contributes?.themes;
@@ -30359,7 +30278,7 @@ function discover(dir) {
30359
30278
  const ext = `${pkg.publisher ?? "local"}.${pkg.name ?? name}`;
30360
30279
  let nls = {};
30361
30280
  const nlsFile = join25(extDir, "package.nls.json");
30362
- if (existsSync27(nlsFile)) {
30281
+ if (existsSync28(nlsFile)) {
30363
30282
  try {
30364
30283
  nls = JSON.parse(readFileSync17(nlsFile, "utf8"));
30365
30284
  } catch {}
@@ -30773,7 +30692,7 @@ class BackendShare {
30773
30692
 
30774
30693
  // ../daemon/src/worktrees/cache.ts
30775
30694
  import { createHash as createHash4 } from "crypto";
30776
- import { existsSync as existsSync28, readFileSync as readFileSync18 } from "fs";
30695
+ import { existsSync as existsSync29, readFileSync as readFileSync18 } from "fs";
30777
30696
  import { mkdir as mkdir5, readdir as readdir2, rename, rm as rm4, stat as stat4, utimes, writeFile as writeFile3 } from "fs/promises";
30778
30697
  import { join as join26 } from "path";
30779
30698
 
@@ -30806,7 +30725,7 @@ var STALE_MS = 30 * 24 * 3600000;
30806
30725
  var TMP = ".tmp-";
30807
30726
  var MANIFEST = "manifest.json";
30808
30727
  function defaultTools(dir) {
30809
- const has = (...files) => files.some((f) => existsSync28(join26(dir, f)));
30728
+ const has = (...files) => files.some((f) => existsSync29(join26(dir, f)));
30810
30729
  const out = [];
30811
30730
  if (has("bun.lock", "bun.lockb"))
30812
30731
  out.push("bun --version");
@@ -30819,7 +30738,7 @@ function defaultTools(dir) {
30819
30738
  return out;
30820
30739
  }
30821
30740
  function withSidecars(root, rel) {
30822
- return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync28(join26(root, n)));
30741
+ return [rel, ...SIDECARS.map((s) => rel + s)].filter((n) => existsSync29(join26(root, n)));
30823
30742
  }
30824
30743
  var entryName = (base, at) => `${base.slice(0, 12)}-${at.toString(36)}`;
30825
30744
  async function listEntries(familyDir) {
@@ -30835,7 +30754,7 @@ async function listEntries(familyDir) {
30835
30754
  if (!m?.[1] || !m[2])
30836
30755
  continue;
30837
30756
  const dir = join26(familyDir, name);
30838
- if (!existsSync28(join26(dir, MANIFEST)))
30757
+ if (!existsSync29(join26(dir, MANIFEST)))
30839
30758
  continue;
30840
30759
  out.push({ dir, base: m[1], at: Number.parseInt(m[2], 36) });
30841
30760
  }
@@ -30873,7 +30792,7 @@ class ArtifactCache {
30873
30792
  }
30874
30793
  const restored = [];
30875
30794
  for (const rel of policy.paths) {
30876
- if (existsSync28(join26(wt.path, rel)))
30795
+ if (existsSync29(join26(wt.path, rel)))
30877
30796
  continue;
30878
30797
  const names = withSidecars(entry.dir, rel);
30879
30798
  if (names.length === 0)
@@ -30914,7 +30833,7 @@ class ArtifactCache {
30914
30833
  let tmp;
30915
30834
  let slot;
30916
30835
  try {
30917
- const present = policy.paths.filter((rel) => existsSync28(join26(wt.path, rel)));
30836
+ const present = policy.paths.filter((rel) => existsSync29(join26(wt.path, rel)));
30918
30837
  if (present.length === 0)
30919
30838
  return;
30920
30839
  const family = await this.family(wt, repo, policy);
@@ -31198,8 +31117,7 @@ var RESPONSE = {
31198
31117
  conflict: { does: "ask" },
31199
31118
  check: { does: "ask", once: true },
31200
31119
  command: { does: "offer" },
31201
- preview: { does: "offer" },
31202
- page: { does: "offer" }
31120
+ preview: { does: "offer" }
31203
31121
  };
31204
31122
  function lastAsked(entries) {
31205
31123
  for (let i = entries.length - 1;i >= 0; i--) {
@@ -31217,7 +31135,7 @@ function askedFor(entries, toolId) {
31217
31135
  function lastStop(entries) {
31218
31136
  for (let i = entries.length - 1;i >= 0; i--) {
31219
31137
  const e = entries[i].event;
31220
- if (e.type === "usage" || e.type === "session-info" || e.type === "page-error")
31138
+ if (e.type === "usage" || e.type === "session-info")
31221
31139
  continue;
31222
31140
  if (e.type === "turn-end" && e.stopReason === "interrupted")
31223
31141
  return { kind: "stopped" };
@@ -31227,21 +31145,6 @@ function lastStop(entries) {
31227
31145
  }
31228
31146
  return null;
31229
31147
  }
31230
- function lastPageErrors(entries) {
31231
- const errors = [];
31232
- for (let i = entries.length - 1;i >= 0; i--) {
31233
- const e = entries[i].event;
31234
- if (e.type === "page-error") {
31235
- errors.unshift(...e.errors);
31236
- continue;
31237
- }
31238
- if (e.type === "user-message" || e.type === "fix-asked")
31239
- break;
31240
- if (e.type === "tool-start" && isEditTool({ name: e.name, kind: e.kind }))
31241
- break;
31242
- }
31243
- return errors;
31244
- }
31245
31148
  function failureOf(entries, toolId) {
31246
31149
  let command;
31247
31150
  for (const { event } of entries) {
@@ -31306,17 +31209,6 @@ class FixService {
31306
31209
  if (!this.report(worktreeId, failure, { pressed: true }))
31307
31210
  throw new UserError("nothing runs on main");
31308
31211
  }
31309
- pressPage(worktreeId) {
31310
- this.d.state.requireWorktree(worktreeId);
31311
- const agent = this.d.runtime.agentFor(worktreeId);
31312
- if (!agent)
31313
- throw new UserError("worktree still starting; try again in a moment");
31314
- const errors = lastPageErrors(agent.transcript());
31315
- if (errors.length === 0)
31316
- throw new UserError("nothing the preview threw to fix");
31317
- if (!this.report(worktreeId, { kind: "page", errors }, { pressed: true }))
31318
- throw new UserError("nothing runs on main");
31319
- }
31320
31212
  goOn(worktreeId) {
31321
31213
  const wt = this.d.state.requireWorktree(worktreeId);
31322
31214
  if (isMain(wt))
@@ -31336,12 +31228,12 @@ class FixService {
31336
31228
  }
31337
31229
 
31338
31230
  // ../daemon/src/worktrees/handoff.ts
31339
- import { randomUUID as randomUUID3 } from "crypto";
31231
+ import { randomUUID as randomUUID2 } from "crypto";
31340
31232
  var MESSAGE_MAX = 20000;
31341
31233
  var TITLE_MAX2 = 200;
31342
31234
  var PROJECT_MAX = 200;
31343
31235
  var PER_TURN_MAX = 3;
31344
- var refused2 = (text) => ({ text, isError: true });
31236
+ var refused = (text) => ({ text, isError: true });
31345
31237
  function openProposals(entries) {
31346
31238
  const open = new Map;
31347
31239
  for (const { event } of entries) {
@@ -31424,7 +31316,7 @@ class HandoffService {
31424
31316
  const a = args && typeof args === "object" && !Array.isArray(args) ? args : null;
31425
31317
  const shaped = a && typeof a.project === "string" && typeof a.message === "string" && (a.title === undefined || typeof a.title === "string");
31426
31318
  if (!shaped)
31427
- return refused2("handoff takes { project: string, message: string, title?: string }.");
31319
+ return refused("handoff takes { project: string, message: string, title?: string }.");
31428
31320
  return this.propose(worktreeId, { project: a.project, message: a.message, ...a.title ? { title: a.title } : {} }, "agent");
31429
31321
  }
31430
31322
  propose(worktreeId, p, by) {
@@ -31432,31 +31324,31 @@ class HandoffService {
31432
31324
  if (!wt)
31433
31325
  throw new UserError("that chat is gone");
31434
31326
  if (p.project.length > PROJECT_MAX)
31435
- return refused2(`The project name is over ${PROJECT_MAX} characters.`);
31327
+ return refused(`The project name is over ${PROJECT_MAX} characters.`);
31436
31328
  if ((p.title ?? "").length > TITLE_MAX2)
31437
- return refused2(`The title is over ${TITLE_MAX2} characters; a few words.`);
31329
+ return refused(`The title is over ${TITLE_MAX2} characters; a few words.`);
31438
31330
  if (p.message.length > MESSAGE_MAX) {
31439
- return refused2(`The message is over ${MESSAGE_MAX} characters; cut it to what the other agent needs.`);
31331
+ return refused(`The message is over ${MESSAGE_MAX} characters; cut it to what the other agent needs.`);
31440
31332
  }
31441
31333
  const target = this.resolve(worktreeId, p.project);
31442
31334
  if ("refused" in target)
31443
- return refused2(target.refused);
31335
+ return refused(target.refused);
31444
31336
  const message = p.message.trim();
31445
31337
  if (!message)
31446
- return refused2("Write the message the other project's agent should start from.");
31338
+ return refused("Write the message the other project's agent should start from.");
31447
31339
  const agent = this.d.runtime.ensureAgent(wt).agent;
31448
31340
  const entries = agent.transcript();
31449
31341
  if (openProposals(entries).length > 0) {
31450
- return refused2("A handoff is already waiting for the person on this chat; finish your turn.");
31342
+ return refused("A handoff is already waiting for the person on this chat; finish your turn.");
31451
31343
  }
31452
31344
  if (by === "agent" && proposedThisTurn(entries) >= PER_TURN_MAX) {
31453
- return refused2("Three handoffs were proposed this turn already; finish your turn and let the person answer.");
31345
+ return refused("Three handoffs were proposed this turn already; finish your turn and let the person answer.");
31454
31346
  }
31455
31347
  const title = p.title?.trim();
31456
31348
  const { repo } = target;
31457
31349
  agent.note({
31458
31350
  type: "handoff-proposed",
31459
- id: randomUUID3(),
31351
+ id: randomUUID2(),
31460
31352
  repo: { id: repo.id, name: repo.name, path: repo.path },
31461
31353
  message,
31462
31354
  ...title ? { title } : {},
@@ -32029,7 +31921,7 @@ async function mergedBranches(repoPath, defaultBr) {
32029
31921
 
32030
31922
  // ../daemon/src/worktrees/discover.ts
32031
31923
  import { createHash as createHash5 } from "crypto";
32032
- import { basename as basename9 } from "path";
31924
+ import { basename as basename10 } from "path";
32033
31925
 
32034
31926
  // ../daemon/src/git/worktrees.ts
32035
31927
  function parseWorktreeList(out) {
@@ -32118,7 +32010,7 @@ function subtractKnown(repoId, listed, known) {
32118
32010
  rows.push({
32119
32011
  id: discoveredId(g.path),
32120
32012
  repoId,
32121
- name: g.branch ?? basename9(g.path),
32013
+ name: g.branch ?? basename10(g.path),
32122
32014
  path: g.path,
32123
32015
  ...g.branch ? { branch: g.branch } : {},
32124
32016
  ...g.locked ? { locked: true } : {},
@@ -32280,7 +32172,7 @@ function rankRefs({ refs, merged, prs, query, defaultBranch, rowIdForPath }) {
32280
32172
  }
32281
32173
 
32282
32174
  // ../daemon/src/worktrees/service.ts
32283
- import { cpSync, existsSync as existsSync33, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
32175
+ import { cpSync, existsSync as existsSync34, readFileSync as readFileSync20, rmSync as rmSync8 } from "fs";
32284
32176
  import { rm as rm5 } from "fs/promises";
32285
32177
  import { dirname as dirname12, join as join30 } from "path";
32286
32178
 
@@ -32406,7 +32298,7 @@ async function dropLandRefs(repoPath, worktreeId) {
32406
32298
  }
32407
32299
 
32408
32300
  // ../daemon/src/git/land.ts
32409
- import { existsSync as existsSync29 } from "fs";
32301
+ import { existsSync as existsSync30 } from "fs";
32410
32302
  var STEP_TIMEOUT_MS = 10 * 60000;
32411
32303
  function rowOf(r) {
32412
32304
  return r.shown && r.toolId && r.command ? { toolId: r.toolId, command: r.command, text: r.text } : undefined;
@@ -32416,7 +32308,7 @@ var UNWATCHED = {
32416
32308
  step() {},
32417
32309
  git: (cwd, args) => stepRun(cwd, args)
32418
32310
  };
32419
- function refused3(what, r, n = 200) {
32311
+ function refused2(what, r, n = 200) {
32420
32312
  if (r.exit === "timeout") {
32421
32313
  const after = describeDuration(r.ceilingMs ?? STEP_TIMEOUT_MS);
32422
32314
  return `${what} gave up after ${after}${r.shown ? "; what it printed is on the chat" : ""}`;
@@ -32427,7 +32319,7 @@ function stepRefused(what, r, n = 200) {
32427
32319
  const row = typeof r.exit === "number" && r.hook ? rowOf(r) : undefined;
32428
32320
  return {
32429
32321
  ok: false,
32430
- message: refused3(what, r, n),
32322
+ message: refused2(what, r, n),
32431
32323
  ...row && r.hook ? { hook: { ...row, name: r.hook } } : {}
32432
32324
  };
32433
32325
  }
@@ -32537,7 +32429,7 @@ async function midway(worktreePath) {
32537
32429
  if (!r.ok)
32538
32430
  return null;
32539
32431
  const at = r.out.split(`
32540
- `).findIndex((file) => file && existsSync29(file));
32432
+ `).findIndex((file) => file && existsSync30(file));
32541
32433
  return MIDWAY[at]?.[1] ?? null;
32542
32434
  }
32543
32435
  async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
@@ -32791,7 +32683,7 @@ async function fetchBase(worktreePath, repo, w = UNWATCHED) {
32791
32683
  w.step(`fetching ${base}`);
32792
32684
  const f = await w.git(worktreePath, ["fetch", "--quiet", tracked.remote, tracked.merge]);
32793
32685
  if (!f.ok)
32794
- return { ok: false, message: refused3("fetch failed", f) };
32686
+ return { ok: false, message: refused2("fetch failed", f) };
32795
32687
  return { ok: true, message: `fetched ${base}` };
32796
32688
  }
32797
32689
  async function pushBranch(worktreePath, branch, w = UNWATCHED) {
@@ -32881,7 +32773,7 @@ function compareUrl(remoteUrl, base, branch) {
32881
32773
  }
32882
32774
 
32883
32775
  // ../daemon/src/git/turn.ts
32884
- import { copyFileSync, existsSync as existsSync30, rmSync as rmSync6 } from "fs";
32776
+ import { copyFileSync, existsSync as existsSync31, rmSync as rmSync6 } from "fs";
32885
32777
  import { join as join27 } from "path";
32886
32778
  var turnRef = (worktreeId, which) => `refs/toyon/turns/${worktreeId}/${which}`;
32887
32779
  var SNAPSHOT_SUBJECT2 = "toyon: tree at a send";
@@ -32907,7 +32799,7 @@ async function treeNow(wtPath, head, dirty) {
32907
32799
  const tree = await run(GIT, ["write-tree"], wtPath, env);
32908
32800
  return tree.ok && tree.out ? tree.out : null;
32909
32801
  };
32910
- if (!existsSync30(index))
32802
+ if (!existsSync31(index))
32911
32803
  was = undefined;
32912
32804
  const paths = dirty && !dirty.some((f) => /[RC]/.test(f.xy)) ? dirty.map((f) => f.path) : null;
32913
32805
  if (was && paths && was.head === head) {
@@ -32922,9 +32814,9 @@ async function treeNow(wtPath, head, dirty) {
32922
32814
  }
32923
32815
  }
32924
32816
  synced.delete(wtPath);
32925
- if (!existsSync30(index)) {
32817
+ if (!existsSync31(index)) {
32926
32818
  const own = join27(index, "..", "index");
32927
- if (existsSync30(own))
32819
+ if (existsSync31(own))
32928
32820
  copyFileSync(own, index);
32929
32821
  else if (!(await run(GIT, ["read-tree", "HEAD"], wtPath, env)).ok)
32930
32822
  return null;
@@ -33005,8 +32897,8 @@ async function dropTurnRefs(repoPath, worktreeId) {
33005
32897
  }
33006
32898
 
33007
32899
  // ../daemon/src/worktrees/archive.ts
33008
- import { existsSync as existsSync31, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync19, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
33009
- import { basename as basename10, dirname as dirname11, join as join28 } from "path";
32900
+ import { existsSync as existsSync32, mkdirSync as mkdirSync8, readdirSync as readdirSync9, readFileSync as readFileSync19, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
32901
+ import { basename as basename11, dirname as dirname11, join as join28 } from "path";
33010
32902
  class WorktreeArchive {
33011
32903
  dir;
33012
32904
  records = new Map;
@@ -33015,7 +32907,7 @@ class WorktreeArchive {
33015
32907
  mkdirSync8(dir, { recursive: true });
33016
32908
  for (const id of readdirSync9(dir)) {
33017
32909
  const file = this.filesOf(id).record;
33018
- if (!existsSync31(file))
32910
+ if (!existsSync32(file))
33019
32911
  continue;
33020
32912
  try {
33021
32913
  this.records.set(id, JSON.parse(readFileSync19(file, "utf8")));
@@ -33073,8 +32965,8 @@ class WorktreeArchive {
33073
32965
  planFile(id, rel) {
33074
32966
  if (!this.records.has(id) || !isPlanPath(rel))
33075
32967
  return null;
33076
- const file = join28(this.filesOf(id).plans, basename10(rel));
33077
- if (!existsSync31(file))
32968
+ const file = join28(this.filesOf(id).plans, basename11(rel));
32969
+ if (!existsSync32(file))
33078
32970
  return null;
33079
32971
  try {
33080
32972
  return readFileSync19(file, "utf8");
@@ -33115,7 +33007,7 @@ function summarize(r, repoId, transcript) {
33115
33007
  };
33116
33008
  }
33117
33009
  function lastUsage(transcript) {
33118
- if (!existsSync31(transcript))
33010
+ if (!existsSync32(transcript))
33119
33011
  return null;
33120
33012
  const lines = readFileSync19(transcript, "utf8").split(`
33121
33013
  `);
@@ -33131,7 +33023,7 @@ function lastUsage(transcript) {
33131
33023
  return null;
33132
33024
  }
33133
33025
  function firstPrompt(transcript) {
33134
- if (!existsSync31(transcript))
33026
+ if (!existsSync32(transcript))
33135
33027
  return;
33136
33028
  for (const line of readFileSync19(transcript, "utf8").split(`
33137
33029
  `)) {
@@ -33146,7 +33038,7 @@ function firstPrompt(transcript) {
33146
33038
  return;
33147
33039
  }
33148
33040
  function move(from, to) {
33149
- if (!existsSync31(from))
33041
+ if (!existsSync32(from))
33150
33042
  return;
33151
33043
  mkdirSync8(dirname11(to), { recursive: true });
33152
33044
  rmSync7(to, { recursive: true, force: true });
@@ -33317,8 +33209,8 @@ async function fillLandMarks(jobs, read, width = 8) {
33317
33209
  }
33318
33210
 
33319
33211
  // ../daemon/src/worktrees/spare.ts
33320
- import { existsSync as existsSync32 } from "fs";
33321
- import { basename as basename11, join as join29 } from "path";
33212
+ import { existsSync as existsSync33 } from "fs";
33213
+ import { basename as basename12, join as join29 } from "path";
33322
33214
  class SparePool {
33323
33215
  d;
33324
33216
  inflight = new Map;
@@ -33337,7 +33229,7 @@ class SparePool {
33337
33229
  if (!text.trim())
33338
33230
  return;
33339
33231
  const wt = this.d.state.worktree(boxId);
33340
- if (wt?.kind !== "spare" || !existsSync32(wt.path))
33232
+ if (wt?.kind !== "spare" || !existsSync33(wt.path))
33341
33233
  return;
33342
33234
  const agent = this.d.runtime.ensureAgent(wt).agent;
33343
33235
  if (agent.runningAgent === null)
@@ -33401,7 +33293,7 @@ class SparePool {
33401
33293
  const repoId = repo.id;
33402
33294
  let cut = () => {};
33403
33295
  const added = (async () => {
33404
- if (existsSync32(wt.path))
33296
+ if (existsSync33(wt.path))
33405
33297
  return;
33406
33298
  const run = this.d.runs.begin(wt.id, "setup", {
33407
33299
  timeoutMs: timeoutFor(repo.config, "setup"),
@@ -33460,7 +33352,7 @@ class SparePool {
33460
33352
  }
33461
33353
  log.warn(wt.repoId, "spare warm-up failed; rolling back", e);
33462
33354
  this.d.carryDraft?.(wt.id, wt.repoId);
33463
- if (existsSync32(wt.path)) {
33355
+ if (existsSync33(wt.path)) {
33464
33356
  await this.d.discard(wt.id).catch((re) => log.warn(wt.repoId, "spare rollback failed", re));
33465
33357
  return;
33466
33358
  }
@@ -33519,7 +33411,7 @@ class SparePool {
33519
33411
  wt.kind = "worktree";
33520
33412
  delete wt.phase;
33521
33413
  delete wt.lockfile;
33522
- wt.branch = `toyon/${basename11(wt.path)}`;
33414
+ wt.branch = `toyon/${basename12(wt.path)}`;
33523
33415
  wt.title = title;
33524
33416
  wt.createdAt = Date.now();
33525
33417
  wt.promptedAt = wt.createdAt;
@@ -33532,7 +33424,7 @@ class SparePool {
33532
33424
  try {
33533
33425
  await running?.added;
33534
33426
  running?.cut();
33535
- if (!existsSync32(wt.path))
33427
+ if (!existsSync33(wt.path))
33536
33428
  throw new Error("the worktree's directory is gone");
33537
33429
  await this.refreshing.get(repoId);
33538
33430
  await prepare(wt);
@@ -33560,7 +33452,7 @@ class SparePool {
33560
33452
  for (const k of Object.keys(stamps))
33561
33453
  delete wt[k];
33562
33454
  delete wt.promptedAt;
33563
- if (!existsSync32(wt.path)) {
33455
+ if (!existsSync33(wt.path)) {
33564
33456
  await this.rollBack(wt, e);
33565
33457
  return;
33566
33458
  }
@@ -33645,7 +33537,7 @@ class Trunk {
33645
33537
  const f = await withFetchLock(repo.path, () => w.git(repo.path, ["fetch", "--quiet"]));
33646
33538
  this.noteFetch(repo.id, f);
33647
33539
  if (!f.ok)
33648
- return { ok: false, message: refused3("fetch failed", f) };
33540
+ return { ok: false, message: refused2("fetch failed", f) };
33649
33541
  this.d.invalidateCounts();
33650
33542
  return this.follow(main.id);
33651
33543
  }
@@ -34040,7 +33932,7 @@ class WorktreeService {
34040
33932
  };
34041
33933
  const carried = await git(repo.path, "ls-tree", "-r", "--name-only", `${sha}^3`);
34042
33934
  const restored = (carried.ok ? carried.out.split(`
34043
- `).filter(Boolean) : []).filter((p) => !existsSync33(join30(wt.path, p)));
33935
+ `).filter(Boolean) : []).filter((p) => !existsSync34(join30(wt.path, p)));
34044
33936
  const applied = await git(wt.path, "stash", "apply", sha);
34045
33937
  if (applied.ok) {
34046
33938
  const ref = await entry();
@@ -34080,7 +33972,7 @@ class WorktreeService {
34080
33972
  const branch = kind === "pr" ? `pr/${number}` : ref;
34081
33973
  const title = kind === "pr" ? `pr-${number}` : cleanTitle2(ref) || "branch";
34082
33974
  let slug = branchSlug(title) || "branch";
34083
- if (existsSync33(join30(this.d.paths.worktreesDir, repo.name, slug)))
33975
+ if (existsSync34(join30(this.d.paths.worktreesDir, repo.name, slug)))
34084
33976
  slug = `${slug}-${shortId().slice(0, 4)}`;
34085
33977
  const wtPath = join30(this.d.paths.worktreesDir, repo.name, slug);
34086
33978
  await withRepoLock(repo.path, async () => {
@@ -34348,7 +34240,7 @@ class WorktreeService {
34348
34240
  }
34349
34241
  async teardown(wt, repo) {
34350
34242
  const cmds = repo.config.teardown ?? [];
34351
- if (cmds.length === 0 || !existsSync33(wt.path))
34243
+ if (cmds.length === 0 || !existsSync34(wt.path))
34352
34244
  return;
34353
34245
  for (const cmd of cmds) {
34354
34246
  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") });
@@ -34398,7 +34290,7 @@ class WorktreeService {
34398
34290
  }
34399
34291
  keepPlans(wt) {
34400
34292
  const from = join30(wt.path, PLANS_DIR);
34401
- if (!existsSync33(from))
34293
+ if (!existsSync34(from))
34402
34294
  return;
34403
34295
  try {
34404
34296
  cpSync(from, this.plansScratch(wt.id), { recursive: true });
@@ -34592,7 +34484,7 @@ class WorktreeService {
34592
34484
  throw new UserError(`${old.title} was archived without its commits, so there is nothing to restore`);
34593
34485
  if (rec.auto)
34594
34486
  log.info(old.id, `restoring a worktree that archived itself (${rec.auto})`);
34595
- const path = existsSync33(old.path) ? join30(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
34487
+ const path = existsSync34(old.path) ? join30(dirname12(old.path), freeSlot(dirname12(old.path)).dir) : old.path;
34596
34488
  const { variant: _variant, ...rest } = old;
34597
34489
  const wt = {
34598
34490
  ...rest,
@@ -34706,12 +34598,12 @@ class WorktreeService {
34706
34598
  return;
34707
34599
  const repo = this.repoOf(rec);
34708
34600
  const repoPath = repo?.path ?? rec.repoPath;
34709
- if (rec.kept && existsSync33(repoPath)) {
34601
+ if (rec.kept && existsSync34(repoPath)) {
34710
34602
  const r = await git(repoPath, "update-ref", "-d", archiveRef(archiveId));
34711
34603
  if (!r.ok)
34712
34604
  log.warn(archiveId, `could not drop its archive ref: ${r.err}`);
34713
34605
  }
34714
- if (rec.worktree.lands?.length && existsSync33(repoPath))
34606
+ if (rec.worktree.lands?.length && existsSync34(repoPath))
34715
34607
  await dropLandRefs(repoPath, archiveId);
34716
34608
  this.archive.delete(archiveId);
34717
34609
  this.d.drafts?.drop(archiveId);
@@ -34820,8 +34712,8 @@ class WorktreeService {
34820
34712
  }
34821
34713
  async mirrorBaseIgnore(repo) {
34822
34714
  const src = join30(repo.path, ".gitignore");
34823
- const tracked = existsSync33(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
34824
- const mirror = existsSync33(src) && !tracked;
34715
+ const tracked = existsSync34(src) && (await git(repo.path, "ls-files", "--", ".gitignore")).out !== "";
34716
+ const mirror = existsSync34(src) && !tracked;
34825
34717
  const lines = mirror ? readFileSync20(src, "utf8").split(`
34826
34718
  `).filter((l) => l.trim() !== "") : [];
34827
34719
  await excludeBlock(repo.path, BASE_IGNORE, lines);
@@ -34836,7 +34728,7 @@ class WorktreeService {
34836
34728
  }
34837
34729
  const srcNm = join30(depsSource, "node_modules");
34838
34730
  const dstNm = join30(wt.path, "node_modules");
34839
- if (existsSync33(srcNm) && !existsSync33(dstNm)) {
34731
+ if (existsSync34(srcNm) && !existsSync34(dstNm)) {
34840
34732
  const run = this.runs.begin(wt.id, "setup", { timeoutMs: ceiling, stage: "copying dependencies" });
34841
34733
  const r = await cloneTree(srcNm, dstNm);
34842
34734
  run.finish(0);
@@ -34847,7 +34739,7 @@ class WorktreeService {
34847
34739
  }
34848
34740
  for (const f of LOCAL_CONFIG_FILES) {
34849
34741
  const src = join30(depsSource, f);
34850
- if (!existsSync33(src) || existsSync33(join30(wt.path, f)))
34742
+ if (!existsSync34(src) || existsSync34(join30(wt.path, f)))
34851
34743
  continue;
34852
34744
  const r = await run("cp", [src, join30(wt.path, f)], wt.path);
34853
34745
  if (r.ok)
@@ -35322,7 +35214,8 @@ ${wt.landing.body}` : wt.landing.subject : undefined);
35322
35214
  const r = await git(wt.path, "reset", "--keep", base);
35323
35215
  if (!r.ok)
35324
35216
  log.warn(wt.id, `could not restart ${wt.branch} from ${base}: ${r.err}`);
35325
- await this.headMoved(wt.id);
35217
+ this.counts.stale(wt.id);
35218
+ await this.counts.refresh([wt.id]);
35326
35219
  }
35327
35220
  async fetchBase(wt, repo, w) {
35328
35221
  if (!baseIsRemote(repo))
@@ -35915,7 +35808,7 @@ if (pruned > 0)
35915
35808
  var managed = await loadManaged();
35916
35809
  var paths = makePaths();
35917
35810
  ensureDirs(paths);
35918
- forwardTranscripts(paths.transcriptsDir);
35811
+ forwardTranscripts([paths.transcriptsDir, paths.archiveDir], join31(paths.home, "transcripts-forwarded"));
35919
35812
  var token = loadOrCreateToken(paths);
35920
35813
  var port = Number(process.env.TOYON_PORT ?? DAEMON_DEFAULT_PORT);
35921
35814
  var helperApp = join31(paths.home, DAEMON_FILES.helper);
@@ -35956,43 +35849,39 @@ await linuxSandbox.refresh();
35956
35849
  var agents = loadAgentRegistry(paths.agentsFile, paths.agentsDir, linuxSandbox, managed.policy);
35957
35850
  if (state.defaultAgent !== undefined && !agents.get(state.defaultAgent))
35958
35851
  state.clearDefaultAgent();
35959
- var probe2 = new OptionProbe({
35852
+ var readiness2 = new AgentReadiness({
35960
35853
  infos: () => agents.infos(),
35961
35854
  require: (id) => agents.require(id),
35962
- connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `probe:${spec.id}`),
35855
+ connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `check:${spec.id}`),
35963
35856
  cwd: paths.scratchDir,
35964
- known: (id) => state.cachedOptions(id, "model").length > 0,
35857
+ options: (id, category) => state.cachedOptions(id, category),
35965
35858
  learned: (id, category, choices) => {
35966
35859
  if (state.setCachedOptions(id, category, choices))
35967
35860
  hub.emit("agentsChanged");
35861
+ },
35862
+ quick: (id) => {
35863
+ const spec = agents.get(id);
35864
+ return !!spec && !lacksQuickModel(spec, state);
35968
35865
  }
35969
35866
  });
35867
+ readiness2.onChange = () => hub.emit("agentsChanged");
35970
35868
  agents.onChange = () => {
35971
35869
  hub.emit("agentsChanged");
35972
- fireAndForget("agents", probe2.missing(), "read agent models");
35870
+ fireAndForget("agents", readiness2.checkMissing(), "read agent models");
35973
35871
  };
35974
- var accounts = new AgentAccounts({
35975
- require: (id) => agents.require(id),
35976
- connect: async (app, spec) => spawnAcp(app, agents.launch(spec, await prepareLaunch(paths.scratchDir, spec)), paths.scratchDir, `auth:${spec.id}`)
35977
- });
35978
- accounts.onChange = () => hub.emit("agentsChanged");
35979
35872
  var uploads = new UploadStore(paths.uploadsDir);
35980
35873
  var attachments2 = new AttachmentStore(paths.attachmentsDir, uploads);
35981
35874
  var mcpPort = port;
35982
35875
  var tools = new ToolSet;
35983
35876
  var mcp = new ToyonMcp({ url: (id) => `http://127.0.0.1:${mcpPort}/mcp/${id}`, version: package_default.version, tools });
35984
- var pageErrors = new PageErrorService({
35985
- hub,
35986
- runtime: { agentFor: (id) => runtime.agentFor(id) },
35987
- known: (id) => state.worktree(id) !== undefined
35988
- });
35877
+ var pageErrors = new PageErrorService({ hub, known: (id) => state.worktree(id) !== undefined });
35989
35878
  var runtime = new RuntimeRegistry({
35990
35879
  hub,
35991
35880
  state,
35992
35881
  paths,
35993
35882
  agents,
35994
35883
  managed: managed.policy,
35995
- accounts,
35884
+ readiness: readiness2,
35996
35885
  attachments: attachments2,
35997
35886
  bridgeScript: () => bridge.get(),
35998
35887
  pageErrors: (id) => pageErrors.ambient(id),
@@ -36035,8 +35924,6 @@ var handoff2 = new HandoffService({
36035
35924
  create: (repoId, prompt, opts) => worktrees.create(repoId, prompt, opts)
36036
35925
  });
36037
35926
  tools.add(handoff2);
36038
- var render = new RenderService({ runtime, pageErrors });
36039
- tools.add(render);
36040
35927
  var turns = new TurnService({
36041
35928
  state,
36042
35929
  hub,
@@ -36195,12 +36082,11 @@ var {
36195
36082
  landing,
36196
36083
  fix,
36197
36084
  pageErrors,
36198
- render,
36199
36085
  handoff: handoff2,
36200
36086
  mcp,
36201
36087
  themes,
36202
36088
  agents,
36203
- accounts,
36089
+ readiness: readiness2,
36204
36090
  attachments: attachments2,
36205
36091
  self,
36206
36092
  afterLand,
@@ -36231,7 +36117,7 @@ idle.boot();
36231
36117
  runs.boot();
36232
36118
  landing.boot();
36233
36119
  fireAndForget("cache", cache.sweep(), "cache sweep");
36234
- fireAndForget("agents", agents.installMissing().then(() => probe2.missing()), "agent adapter install");
36120
+ fireAndForget("agents", agents.installMissing().then(() => readiness2.checkMissing()), "agent adapter install");
36235
36121
  var repoArg = process.argv[2];
36236
36122
  if (repoArg) {
36237
36123
  try {