toyon 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/cli.js +16 -1
  2. package/dist/daemon.js +201 -34
  3. package/dist/shell/assets/{Editor-CFH2QbSg.js → Editor-CrfFlcLA.js} +4 -4
  4. package/dist/shell/assets/{XTerm-C3aMkE_w.js → XTerm-CsuyFSMK.js} +1 -1
  5. package/dist/shell/assets/{cssMode-dc6AdHsZ.js → cssMode-CWkICf31.js} +1 -1
  6. package/dist/shell/assets/{freemarker2-CxtmNsLB.js → freemarker2-C2fP12sE.js} +1 -1
  7. package/dist/shell/assets/{grammar-B-X0FPRk.js → grammar-5S_Kl1e8.js} +1 -1
  8. package/dist/shell/assets/{handlebars-DVzmQ0KM.js → handlebars-CkRyqZGC.js} +1 -1
  9. package/dist/shell/assets/{html-CqqXL9ke.js → html-C9iJKWKA.js} +1 -1
  10. package/dist/shell/assets/{htmlMode-Bg-Em0rL.js → htmlMode-Dps-6NJd.js} +1 -1
  11. package/dist/shell/assets/index-BKyLPsvB.js +258 -0
  12. package/dist/shell/assets/index-DIDGdnCY.css +1 -0
  13. package/dist/shell/assets/{javascript-W-C4G7BB.js → javascript-DM5EiBY7.js} +1 -1
  14. package/dist/shell/assets/{jsonMode-De4ajMpV.js → jsonMode-ampRCTsW.js} +1 -1
  15. package/dist/shell/assets/{liquid-BSUg_7LQ.js → liquid-CEUat1ZS.js} +1 -1
  16. package/dist/shell/assets/{lspLanguageFeatures-BImXYajo.js → lspLanguageFeatures-ChupARNW.js} +1 -1
  17. package/dist/shell/assets/{mdx-B9A7RorP.js → mdx-DAglTq4w.js} +1 -1
  18. package/dist/shell/assets/{python-BD9sq3DF.js → python-DOPtAG-1.js} +1 -1
  19. package/dist/shell/assets/{razor-DGQVRh5-.js → razor-qRDOBjWc.js} +1 -1
  20. package/dist/shell/assets/{tsMode-DudQlgN_.js → tsMode-BD49NPBt.js} +1 -1
  21. package/dist/shell/assets/{xml-BI6-GyBn.js → xml-p5Js1H7t.js} +1 -1
  22. package/dist/shell/assets/{yaml-BEUvyYtP.js → yaml-CctH9qfh.js} +1 -1
  23. package/dist/shell/index.html +2 -2
  24. package/package.json +1 -1
  25. package/dist/shell/assets/index-BskWflhx.css +0 -1
  26. package/dist/shell/assets/index-C8QP4sgP.js +0 -255
package/dist/cli.js CHANGED
@@ -5158,7 +5158,7 @@ function parseVersion(v) {
5158
5158
  // package.json
5159
5159
  var package_default = {
5160
5160
  name: "toyon",
5161
- version: "0.8.0",
5161
+ version: "0.8.1",
5162
5162
  description: "Toyon is where coding agents work on your web app while you check each change by using it. Every chat gets its own running copy with a live preview.",
5163
5163
  license: "MIT",
5164
5164
  type: "module",
@@ -6181,6 +6181,19 @@ function policyLines(m, daemon) {
6181
6181
  }
6182
6182
  return lines;
6183
6183
  }
6184
+ function agentLines(agents) {
6185
+ const level = agents.filter((a) => a.installed === a.pinned);
6186
+ const lines = [];
6187
+ if (level.length > 0)
6188
+ lines.push(line(true, "agents", level.map((a) => `${a.id} ${a.installed}`).join(", ")));
6189
+ for (const a of agents) {
6190
+ if (a.installed === a.pinned)
6191
+ continue;
6192
+ const why = a.error ? `: ${a.error}` : "; it may still be installing";
6193
+ lines.push(line(false, "agents", a.installed === null ? `${a.id} ${a.pinned} is not installed${why}` : `${a.id} runs ${a.installed}, not the pinned ${a.pinned}${why}. \`toyon restart\` tries again`));
6194
+ }
6195
+ return lines;
6196
+ }
6184
6197
  async function doctor() {
6185
6198
  const lines = [];
6186
6199
  lines.push(line(true, "cli", `toyon ${package_default.version}, bun ${Bun.version}, ${process.platform} ${process.arch}`));
@@ -6209,6 +6222,8 @@ async function doctor() {
6209
6222
  const { total, running } = h.worktrees;
6210
6223
  lines.push(line(true, "running", `${running} of ${total} worktrees started; the rest start when opened`));
6211
6224
  }
6225
+ if (h.agents)
6226
+ lines.push(...agentLines(h.agents));
6212
6227
  if (version !== package_default.version) {
6213
6228
  lines.push(line(false, "version", `cli is ${package_default.version} and the daemon is ${version}; \`toyon restart\` brings them level`));
6214
6229
  }
package/dist/daemon.js CHANGED
@@ -6469,7 +6469,7 @@ var PAIR_TTL_MS = 120000;
6469
6469
  var pairLink = (host, code) => `https://${host}/#pair=${code}`;
6470
6470
 
6471
6471
  // ../shared/src/protocol/limits.ts
6472
- var PROTOCOL_VERSION = 82;
6472
+ var PROTOCOL_VERSION = 83;
6473
6473
  var FILE_MAX_CHARS = 5000000;
6474
6474
  var IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
6475
6475
  var IMAGE_MAX_EDGE = 2576;
@@ -7882,15 +7882,6 @@ function installCommand(method, version) {
7882
7882
  return ["bun", "add", "-g", `toyon@${version}`];
7883
7883
  return null;
7884
7884
  }
7885
- function registryHost(registry) {
7886
- if (registry === null || registry === "")
7887
- return "the npm registry";
7888
- try {
7889
- return new URL(registry).host;
7890
- } catch {
7891
- return registry;
7892
- }
7893
- }
7894
7885
  function newer(a, b) {
7895
7886
  const pa = parseVersion(a);
7896
7887
  const pb = parseVersion(b);
@@ -8163,7 +8154,7 @@ function safeUser() {
8163
8154
  // ../daemon/package.json
8164
8155
  var package_default = {
8165
8156
  name: "@toyon/daemon",
8166
- version: "0.8.0",
8157
+ version: "0.8.1",
8167
8158
  private: true,
8168
8159
  type: "module",
8169
8160
  main: "src/index.ts",
@@ -13667,6 +13658,19 @@ class AgentRegistry {
13667
13658
  await this.install(id);
13668
13659
  }
13669
13660
  }
13661
+ adapters() {
13662
+ const out = [];
13663
+ for (const spec of this.list()) {
13664
+ if (spec.run.kind === "command" || !this.packageName(spec))
13665
+ continue;
13666
+ const installed = this.installed(spec) ? this.installedVersion(spec) : null;
13667
+ const error = this.installErrors.get(spec.id);
13668
+ if (spec.onDemand && installed === null && !error)
13669
+ continue;
13670
+ out.push({ id: spec.id, installed, pinned: spec.run.version, ...error ? { error } : {} });
13671
+ }
13672
+ return out;
13673
+ }
13670
13674
  infos() {
13671
13675
  return this.list().map((spec) => {
13672
13676
  const reason = this.unavailable(spec);
@@ -14145,6 +14149,9 @@ function backgroundStart(input, output) {
14145
14149
  const m = STARTED.exec(output);
14146
14150
  return m ? { taskId: m[1], file: m[2] } : null;
14147
14151
  }
14152
+ function spawnDetached(output) {
14153
+ return output.includes("Async agent launched successfully");
14154
+ }
14148
14155
  function claudeConfigDir() {
14149
14156
  return process.env.CLAUDE_CONFIG_DIR || join7(homedir4(), ".claude");
14150
14157
  }
@@ -14466,7 +14473,8 @@ function spawnOf(meta) {
14466
14473
  const claude = asRecord(asRecord(meta).claudeCode);
14467
14474
  const parentToolId = typeof claude.parentToolUseId === "string" ? claude.parentToolUseId : undefined;
14468
14475
  const codex = asRecord(asRecord(meta).codex).subagent;
14469
- const subagent = claude.subagent === true || Object.keys(asRecord(codex)).length > 0;
14476
+ const spawns = claude.toolName === "Agent" || claude.toolName === "Task";
14477
+ const subagent = spawns || claude.subagent === true || Object.keys(asRecord(codex)).length > 0;
14470
14478
  return { ...parentToolId ? { parentToolId } : {}, ...subagent ? { subagent: true } : {} };
14471
14479
  }
14472
14480
  function asRecord(v) {
@@ -14556,7 +14564,8 @@ function mapUpdate(update, memos, tag, sink, background) {
14556
14564
  rawOutput: update.rawOutput,
14557
14565
  ended: false,
14558
14566
  writing: isWrittenKind(head.kind) && emptyInput(input),
14559
- ...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
14567
+ ...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
14568
+ ...spawn.subagent ? { spawns: true } : {}
14560
14569
  };
14561
14570
  memos.set(update.toolCallId, memo);
14562
14571
  out.push({
@@ -14586,7 +14595,8 @@ function mapUpdate(update, memos, tag, sink, background) {
14586
14595
  content: [],
14587
14596
  ended: false,
14588
14597
  writing: isWrittenKind(update.kind ?? undefined) && emptyInput(input),
14589
- ...spawn.parentToolId ? { parent: spawn.parentToolId } : {}
14598
+ ...spawn.parentToolId ? { parent: spawn.parentToolId } : {},
14599
+ ...spawn.subagent ? { spawns: true } : {}
14590
14600
  };
14591
14601
  memos.set(update.toolCallId, memo);
14592
14602
  out.push({
@@ -14664,14 +14674,23 @@ function endOf(toolId, memo, status, sink, background) {
14664
14674
  return { type: "tool-update", toolId, background: true };
14665
14675
  }
14666
14676
  const images = sink ? toolImages(memo.content, sink) : [];
14677
+ const detached = status === "completed" && !!memo.spawns && spawnDetached(wholeText(memo.content, memo.rawOutput));
14667
14678
  return {
14668
14679
  type: "tool-end",
14669
14680
  toolId,
14670
14681
  output,
14671
14682
  isError: status === "failed" || exitCodeOf(memo.rawOutput) > 0,
14683
+ ...detached ? { detached: true } : {},
14672
14684
  ...images.length ? { images } : {}
14673
14685
  };
14674
14686
  }
14687
+ function wholeText(content, rawOutput) {
14688
+ const parts = content.flatMap((c) => c.type === "content" && c.content.type === "text" ? [c.content.text] : []);
14689
+ if (typeof rawOutput === "string")
14690
+ parts.push(rawOutput);
14691
+ return parts.join(`
14692
+ `);
14693
+ }
14675
14694
  function toolImages(content, sink) {
14676
14695
  const out = [];
14677
14696
  for (const c of content) {
@@ -15267,7 +15286,8 @@ class AcpSession {
15267
15286
  sessionId,
15268
15287
  cwd: this.d.cwd,
15269
15288
  mcpServers: [],
15270
- additionalDirectories
15289
+ additionalDirectories,
15290
+ ...conn.spec.systemPrompt === "meta-append" ? { _meta: { systemPrompt: { append: SYSTEM_APPEND } } } : {}
15271
15291
  });
15272
15292
  modes = r.modes;
15273
15293
  configOptions = r.configOptions;
@@ -26123,8 +26143,8 @@ var handlers = {
26123
26143
  "update-now"(_msg, _ctx, s) {
26124
26144
  return s.update.updateNow();
26125
26145
  },
26126
- "check-update"(_msg, _ctx, s) {
26127
- return s.update.checkNow();
26146
+ async "check-update"(_msg, ctx, s) {
26147
+ ctx.reply({ t: "update-checked", check: await s.update.checkNow() });
26128
26148
  },
26129
26149
  async commit(msg, ctx, s) {
26130
26150
  const result = await s.worktrees.commit(msg.worktreeId, msg.message);
@@ -26695,6 +26715,7 @@ function startServer(opts) {
26695
26715
  },
26696
26716
  costs: s.idle.costs(),
26697
26717
  updates: s.update.status(),
26718
+ agents: s.agents.adapters(),
26698
26719
  sockets: [...sockets].map((ws) => ({ subs: [...ws.data.subs], sent: ws.data.sent, bytes: ws.data.bytes }))
26699
26720
  });
26700
26721
  let statusesInFlight = false;
@@ -27295,16 +27316,20 @@ class UpdateService {
27295
27316
  async check() {
27296
27317
  if (this.d.method === "none" || this.d.managedBy)
27297
27318
  return;
27319
+ await this.ask();
27320
+ }
27321
+ async ask() {
27298
27322
  const answer = await this.d.latest();
27299
27323
  this.registryName = answer.registry;
27300
27324
  if (answer.version === null) {
27301
27325
  this.unreachable = answer.registry;
27302
- return;
27326
+ return { registry: answer.registry, latest: null };
27303
27327
  }
27304
27328
  this.unreachable = null;
27305
27329
  this.latest = newer(answer.version, this.d.running) ? answer.version : null;
27306
27330
  this.announce();
27307
27331
  await this.auto();
27332
+ return { registry: answer.registry, latest: answer.version };
27308
27333
  }
27309
27334
  async updateNow() {
27310
27335
  this.refuseManaged();
@@ -27321,14 +27346,17 @@ class UpdateService {
27321
27346
  async checkNow() {
27322
27347
  this.refuseManaged();
27323
27348
  if (this.d.method === "none") {
27324
- throw new UserError(`Toyon ${this.d.running} runs from a checkout, which does not update itself`);
27349
+ const answer = await this.d.latest();
27350
+ return { registry: answer.registry, latest: answer.version };
27325
27351
  }
27326
- await this.check();
27327
- if (this.unreachable)
27328
- throw new UserError(`Could not reach ${this.unreachable}`);
27329
- if (this.target() === null) {
27330
- throw new UserError(`Toyon ${this.d.running} is the newest version ${registryHost(this.registryName)} lists`);
27352
+ const answer = await this.ask();
27353
+ const target = this.target();
27354
+ if (target !== null && !this.wanted && !(this.needsInstall() && this.d.command(target) === null)) {
27355
+ this.failed = null;
27356
+ this.wanted = true;
27357
+ fireAndForget("update", this.proceed(), "update");
27331
27358
  }
27359
+ return answer;
27332
27360
  }
27333
27361
  install() {
27334
27362
  return this.d.method;
@@ -27983,9 +28011,14 @@ class LandingService {
27983
28011
  d.hub.on("agentStatus", (id, status) => {
27984
28012
  this.note(id, status);
27985
28013
  if (status === "working")
27986
- this.clear(id);
28014
+ this.turnStarts(id);
27987
28015
  });
27988
28016
  }
28017
+ turnStarts(worktreeId) {
28018
+ if (!this.d.state.worktree(worktreeId)?.landing?.ready)
28019
+ return this.clear(worktreeId);
28020
+ this.judging.delete(worktreeId);
28021
+ }
27989
28022
  note(worktreeId, status) {
27990
28023
  if (status === "working" || status === "waiting")
27991
28024
  this.busy.add(worktreeId);
@@ -28011,14 +28044,69 @@ class LandingService {
28011
28044
  const wt = this.d.state.worktree(worktreeId);
28012
28045
  if (!wt || !canLand(wt))
28013
28046
  return;
28014
- if (turn.end !== "done")
28015
- return this.clear(worktreeId);
28047
+ const held = await this.standing(wt);
28048
+ if (turn.end !== "done") {
28049
+ if (!held)
28050
+ this.clear(worktreeId);
28051
+ return;
28052
+ }
28016
28053
  if (!await this.hasWork(wt)) {
28017
28054
  this.clear(worktreeId);
28018
28055
  return this.recapAnswer(wt, turn);
28019
28056
  }
28057
+ if (held)
28058
+ return this.reword(wt, turn, held);
28020
28059
  await this.run(wt, { at: turn.at, ask: true, quiet: false });
28021
28060
  }
28061
+ async standing(wt) {
28062
+ const l = wt.landing;
28063
+ if (!l?.ready)
28064
+ return;
28065
+ return l.fingerprint === await treeFingerprint(wt.path) ? l : undefined;
28066
+ }
28067
+ async reword(wt, turn, held) {
28068
+ if (!this.d.judge)
28069
+ return;
28070
+ const worktreeId = wt.id;
28071
+ const repo = this.d.state.requireRepo(wt.repoId);
28072
+ this.judging.set(worktreeId, turn.at);
28073
+ const started = Date.now();
28074
+ const entries = this.d.transcript(worktreeId);
28075
+ const prompt = landPrompt({
28076
+ title: wt.title,
28077
+ firstAsk: firstAskOf(entries),
28078
+ turns: turnsSince(entries, 0),
28079
+ diffStat: await diffSummary(wt.path, baseOf(repo)),
28080
+ recentSubjects: await recentSubjects(wt.path, baseOf(repo))
28081
+ });
28082
+ let verdict = null;
28083
+ try {
28084
+ verdict = await this.d.judge(wt, prompt);
28085
+ } catch (e) {
28086
+ log.warn(worktreeId, "landing verdict kept its words: the question went unanswered", e);
28087
+ }
28088
+ if (this.stopping || this.judging.get(worktreeId) !== turn.at)
28089
+ return;
28090
+ this.judging.delete(worktreeId);
28091
+ const row = this.d.state.worktree(worktreeId);
28092
+ const now = row?.landing;
28093
+ if (!verdict || !row || !now?.ready || now.fingerprint !== held.fingerprint)
28094
+ return;
28095
+ const { why: _was, stale: _moved, ...rest } = now;
28096
+ const caveat = await this.sharedMigration(wt, repo);
28097
+ const doubt = [!verdict.ready && verdict.why ? verdict.why : "", caveat ?? ""].filter(Boolean).join(". ");
28098
+ const landing = { ...rest, at: turn.at, ...doubt ? { why: doubt } : {} };
28099
+ if (!landing.subject && verdict.subject) {
28100
+ landing.subject = verdict.subject;
28101
+ if (verdict.body)
28102
+ landing.body = verdict.body;
28103
+ delete landing.unanswered;
28104
+ }
28105
+ if (row.lastTurn?.at === turn.at && verdict.recap)
28106
+ row.lastTurn.recap = { at: Date.now(), text: verdict.recap };
28107
+ log.info(worktreeId, `landing: tree unchanged, check not run again${doubt ? ", doubted" : ""}, ${Date.now() - started}ms`);
28108
+ this.d.worktrees.setLanding(worktreeId, landing);
28109
+ }
28022
28110
  async recapAnswer(wt, turn) {
28023
28111
  if (!this.d.recap)
28024
28112
  return;
@@ -28742,9 +28830,12 @@ function overwritten(text) {
28742
28830
  }
28743
28831
  return files;
28744
28832
  }
28833
+ var listed = (files) => files.length > 3 ? `${files.slice(0, 3).join(", ")} and ${files.length - 3} more` : files.join(", ");
28745
28834
  function inTheWay(defaultBr, what, files) {
28746
- const shown = files.length > 3 ? `${files.slice(0, 3).join(", ")} and ${files.length - 3} more` : files.join(", ");
28747
- return `${defaultBr} has uncommitted files the ${what} would overwrite (${shown}): commit or stash them there first`;
28835
+ return `${defaultBr} has uncommitted files the ${what} would overwrite (${listed(files)}): commit or stash them there first`;
28836
+ }
28837
+ function untrackedInTheWay(defaultBr, files) {
28838
+ return `${defaultBr} has files that are untracked here (${listed(files)}): move or delete them, then sync`;
28748
28839
  }
28749
28840
  async function requireMainReady(repoPath, defaultBr, clean) {
28750
28841
  const current = await git(repoPath, "branch", "--show-current");
@@ -28756,18 +28847,75 @@ async function requireMainReady(repoPath, defaultBr, clean) {
28756
28847
  }
28757
28848
  return null;
28758
28849
  }
28759
- async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED) {
28760
- const cErr = await requireClean(worktreePath);
28761
- if (cErr)
28762
- return cErr;
28850
+ var CARRIED = "refs/worktree/toyon/carried";
28851
+ async function setAside(worktreePath) {
28852
+ const head = await git(worktreePath, "rev-parse", "HEAD");
28853
+ const made = await git(worktreePath, "stash", "create");
28854
+ if (!head.ok || !made.ok) {
28855
+ return { ok: false, message: `could not set the uncommitted changes aside: ${made.err.slice(0, 200)}` };
28856
+ }
28857
+ if (!made.out)
28858
+ return null;
28859
+ const kept = await git(worktreePath, "update-ref", CARRIED, made.out);
28860
+ const cleared = kept.ok ? await git(worktreePath, "reset", "--hard", "-q") : kept;
28861
+ if (!cleared.ok) {
28862
+ return { ok: false, message: `could not set the uncommitted changes aside: ${cleared.err.slice(0, 200)}` };
28863
+ }
28864
+ return { sha: made.out, head: head.out };
28865
+ }
28866
+ async function putBack(worktreePath, defaultBr, aside, taken) {
28867
+ let clash = null;
28868
+ if (taken.ok) {
28869
+ const applied = await git(worktreePath, "stash", "apply", aside.sha);
28870
+ if (applied.ok) {
28871
+ await git(worktreePath, "update-ref", "-d", CARRIED);
28872
+ return taken;
28873
+ }
28874
+ const unmerged = await git(worktreePath, "diff", "--name-only", "--diff-filter=U");
28875
+ clash = unmerged.out.split(`
28876
+ `).filter(Boolean);
28877
+ await git(worktreePath, "reset", "--hard", "-q", aside.head);
28878
+ }
28879
+ const back = await git(worktreePath, "stash", "apply", "--index", aside.sha);
28880
+ if (!back.ok) {
28881
+ return {
28882
+ ok: false,
28883
+ message: `sync stopped with the uncommitted changes set aside: git stash apply ${CARRIED} in the worktree brings them back`
28884
+ };
28885
+ }
28886
+ await git(worktreePath, "update-ref", "-d", CARRIED);
28887
+ if (!clash)
28888
+ return taken;
28889
+ const named = clash.length ? ` (${listed(clash)})` : "";
28890
+ return {
28891
+ ok: false,
28892
+ message: `the uncommitted changes here conflict with ${defaultBr}${named}: commit them first, then sync`
28893
+ };
28894
+ }
28895
+ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED, carry = false) {
28896
+ if (!carry) {
28897
+ const cErr = await requireClean(worktreePath);
28898
+ if (cErr)
28899
+ return cErr;
28900
+ }
28763
28901
  const { behind } = await aheadBehind(worktreePath, defaultBr);
28764
28902
  if (behind === 0)
28765
28903
  return { ok: true, message: `already up to date with ${defaultBr}` };
28904
+ const aside = carry ? await setAside(worktreePath) : null;
28905
+ if (aside && "message" in aside)
28906
+ return aside;
28907
+ const taken = await bringIn(worktreePath, defaultBr, own, behind, w);
28908
+ return aside ? putBack(worktreePath, defaultBr, aside, taken) : taken;
28909
+ }
28910
+ async function bringIn(worktreePath, defaultBr, own, behind, w) {
28766
28911
  if (own) {
28767
28912
  w.step(`rebasing onto ${defaultBr}`);
28768
28913
  const r = await w.git(worktreePath, ["rebase", defaultBr]);
28769
28914
  if (!r.ok) {
28770
28915
  await git(worktreePath, "rebase", "--abort");
28916
+ const files = overwritten(r.text);
28917
+ if (files.length)
28918
+ return { ok: false, message: untrackedInTheWay(defaultBr, files) };
28771
28919
  const ask = `rebase onto ${defaultBr} conflicts: ask the agent to bring ${defaultBr} in and resolve them`;
28772
28920
  return {
28773
28921
  ok: false,
@@ -28780,6 +28928,9 @@ async function takeMainIn(worktreePath, defaultBr, own, w = UNWATCHED) {
28780
28928
  w.step(`merging ${defaultBr} in`);
28781
28929
  const m = await w.git(worktreePath, ["merge", "--no-edit", defaultBr]);
28782
28930
  if (!m.ok) {
28931
+ const files = overwritten(m.text);
28932
+ if (files.length)
28933
+ return { ok: false, message: untrackedInTheWay(defaultBr, files) };
28783
28934
  return mergeFailure(worktreePath, m, `sync conflicts with ${defaultBr}: ask the agent to merge ${defaultBr} and resolve them`);
28784
28935
  }
28785
28936
  return { ok: true, message: `synced ${behind} commit(s) from ${defaultBr}` };
@@ -30706,12 +30857,24 @@ ${wt.landing.body}` : s;
30706
30857
  const { wt, repo } = this.landable(worktreeId, "land");
30707
30858
  const out = await this.ship(wt.id, "land", async () => {
30708
30859
  await this.followBranch(wt);
30860
+ const moved = await this.movedMidTurn(wt);
30861
+ if (moved)
30862
+ return { result: moved };
30709
30863
  return this.landOp(wt, repo, message);
30710
30864
  });
30711
30865
  if (out.result.ok && out.archiveIds)
30712
30866
  this.noteLanded(wt, out.result.message, out.archiveIds);
30713
30867
  return out;
30714
30868
  }
30869
+ async movedMidTurn(wt) {
30870
+ const status = this.d.runtime.agentFor(wt.id)?.status;
30871
+ if (status !== "working" && status !== "waiting")
30872
+ return null;
30873
+ const l = wt.landing;
30874
+ if (!l || l.fingerprint === await treeFingerprint(wt.path))
30875
+ return null;
30876
+ return { ok: false, message: "the agent has changed files since the check: land once its turn ends" };
30877
+ }
30715
30878
  noteLanded(wt, message, archiveIds) {
30716
30879
  this.d.runtime.ensureAgent(wt).agent.note({ type: "landed", message, archiveIds, ts: Date.now() });
30717
30880
  }
@@ -30983,6 +31146,10 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
30983
31146
  throw new UserError(`${r.name} is held by another tool`);
30984
31147
  const repo = this.d.state.requireRepo(r.repoId);
30985
31148
  const own = r.wt ? hasOwnBranch(r.wt) : false;
31149
+ const carry = !!r.wt;
31150
+ const status = this.d.runtime.agentFor(worktreeId)?.status;
31151
+ if (carry && (status === "working" || status === "waiting") && (await statusFiles(r.path)).length > 0)
31152
+ throw new UserError(`${r.name}'s agent is mid-turn with uncommitted changes: sync when it is done`);
30986
31153
  const w = r.wt ? this.landWatch(r.wt) : UNWATCHED;
30987
31154
  const result = await this.ship(worktreeId, "sync-main", async () => {
30988
31155
  if (r.wt) {
@@ -30990,7 +31157,7 @@ ${wt.landing.body}` : wt.landing.subject : undefined;
30990
31157
  if (!fetched.ok)
30991
31158
  return fetched;
30992
31159
  }
30993
- const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w));
31160
+ const taken = await withRepoLock(repo.path, () => takeMainIn(r.path, r.base, own, w, carry));
30994
31161
  if (taken.ok) {
30995
31162
  this.headMoved(worktreeId);
30996
31163
  if (r.wt)