claude-yes 1.154.0 → 1.155.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.
@@ -74,6 +74,16 @@ clis:
74
74
  # gap across a line-wrap; `\s+`/`\s*` tolerate wraps inside the phrase.
75
75
  - pattern: 'API Error:[\s\S]{0,40}Response\s+stalled\s+mid-\s*stream'
76
76
  flags: i
77
+ # "API Error: Connection closed mid-response. The response above may be
78
+ # incomplete." — the HTTP/SSE connection dropped before the response
79
+ # finished. Same class as the stalled-mid-stream case (transient, the
80
+ # partial answer is already on screen), so retry with backoff instead of
81
+ # wedging. Same "API Error:" chrome anchor keeps an agent merely
82
+ # *discussing* a dropped connection from self-triggering a retry;
83
+ # `[\s\S]{0,40}` spans the prefix→phrase gap across a line-wrap and
84
+ # `\s+`/`\s*` tolerate wraps inside the phrase.
85
+ - pattern: 'API Error:[\s\S]{0,40}Connection\s+closed\s+mid-\s*response'
86
+ flags: i
77
87
  # Any 5xx API error (e.g. "API Error: 529 Overloaded", "API Error: 503 …",
78
88
  # or a 529 rendered as a raw JSON error blob) is server-side + transient —
79
89
  # retry with backoff regardless of the wording. Anchored to "API Error" so
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-U3AE4u72.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CQZuynAm.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-Dam11bMz.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-5mRGfioE.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-CQZuynAm.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-DGqHb9YC.js";
4
+ import "./pidStore-fqXqTKkh.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-5mRGfioE.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-B-ToHoJ2.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DGqHb9YC.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -481,7 +481,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
481
481
  const rawArg = process.argv[2];
482
482
  const managerCommands = !invokedCliName(process.argv);
483
483
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
484
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DLvC6kto.js");
484
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BmxH2HjH.js");
485
485
  if (isHelpFlag && process.argv.length === 3) {
486
486
  cmdHelp(managerCommands);
487
487
  process.exit(0);
@@ -514,7 +514,7 @@ if (config.useRust) {
514
514
  }
515
515
  }
516
516
  if (rustBinary) {
517
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CmM_zBrM.js");
517
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-BNoM41LQ.js");
518
518
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
519
519
  if (config.verbose) {
520
520
  console.log(`[rust] Using binary: ${rustBinary}`);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-U3AE4u72.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CQZuynAm.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-B-ToHoJ2.js";
3
+ import "./versionChecker-DGqHb9YC.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -1,9 +1,9 @@
1
- import "./ts-U3AE4u72.js";
1
+ import "./ts-CQZuynAm.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-B-ToHoJ2.js";
3
+ import "./versionChecker-DGqHb9YC.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dam11bMz.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-5mRGfioE.js";
7
7
  import { i as resolveSpawnCwd } from "./workspaceConfig-DT3cCBZK.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-BPFBe7R5.js.map
144
+ //# sourceMappingURL=schedule-TJEVE-qs.js.map
@@ -1,16 +1,16 @@
1
- import "./ts-U3AE4u72.js";
1
+ import "./ts-CQZuynAm.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-B-ToHoJ2.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DGqHb9YC.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import { a as updateGlobalPidStatus } from "./globalPidIndex-DlmmJlO8.js";
6
6
  import { t as pgidForWrapper } from "./reaper-C-eWAxIj.js";
7
7
  import "./configShared-CEyhl0WH.js";
8
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dam11bMz.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-5mRGfioE.js";
9
9
  import "./e2e-Bfw7qL9O.js";
10
10
  import "./webrtcLink-CBZkZ-LT.js";
11
11
  import "./remotes-DBCvpp3B.js";
12
12
  import { i as resolveSpawnCwd, r as isProvisionAllowed, t as getProvisionRoot } from "./workspaceConfig-DT3cCBZK.js";
13
- import { E as writeToIpc, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, v as renderRawLog, w as snapshotStatus } from "./subcommands-C4tMi9NX.js";
13
+ import { E as writeToIpc, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, v as renderRawLog, w as snapshotStatus } from "./subcommands-DKL_x1ZC.js";
14
14
  import yargs from "yargs";
15
15
  import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
16
16
  import { homedir, hostname, userInfo } from "os";
@@ -985,6 +985,36 @@ Options:
985
985
  return new Response(e.message, { status: 404 });
986
986
  }
987
987
  }
988
+ if (req.method === "POST" && p === "/api/restart") {
989
+ let body;
990
+ try {
991
+ body = await req.json();
992
+ } catch {
993
+ return new Response("invalid JSON body", { status: 400 });
994
+ }
995
+ const keyword = body.keyword;
996
+ if (!keyword || typeof keyword !== "string") return new Response("missing keyword", { status: 400 });
997
+ try {
998
+ const record = await resolveOne(keyword, defaultOpts({ all: true }));
999
+ const args = ["restart", String(record.pid)];
1000
+ if (body.fresh) args.push("--fresh");
1001
+ Bun.spawn(["agent-yes", ...args], {
1002
+ cwd: record.cwd,
1003
+ detached: true,
1004
+ stdio: [
1005
+ "ignore",
1006
+ "ignore",
1007
+ "ignore"
1008
+ ]
1009
+ }).unref();
1010
+ return Response.json({
1011
+ ok: true,
1012
+ pid: record.pid
1013
+ });
1014
+ } catch (e) {
1015
+ return new Response(e.message, { status: 404 });
1016
+ }
1017
+ }
988
1018
  const resizeM = /^\/api\/resize\/(.+)$/.exec(p);
989
1019
  if (req.method === "POST" && resizeM) {
990
1020
  const keyword = decodeURIComponent(resizeM[1]);
@@ -1055,10 +1085,11 @@ Options:
1055
1085
  const prompt = String(body.prompt ?? "");
1056
1086
  let cwd;
1057
1087
  let provisioned = null;
1058
- const fork = body.fork && typeof body.fork.fromCwd === "string" && typeof body.fork.branch === "string" ? {
1059
- fromCwd: body.fork.fromCwd,
1060
- branch: body.fork.branch
1088
+ const fork = body.fork && typeof body.fork.fromCwd === "string" && body.fork.fromCwd.trim() && typeof body.fork.branch === "string" && body.fork.branch.trim() ? {
1089
+ fromCwd: body.fork.fromCwd.trim(),
1090
+ branch: body.fork.branch.trim()
1061
1091
  } : null;
1092
+ if (body.fork != null && !fork) return new Response("fork requires a non-empty fromCwd and branch", { status: 400 });
1062
1093
  const from = typeof body.from === "string" ? body.from.trim() : "";
1063
1094
  if (fork) {
1064
1095
  let prov;
@@ -1272,4 +1303,4 @@ Options:
1272
1303
 
1273
1304
  //#endregion
1274
1305
  export { cmdServe };
1275
- //# sourceMappingURL=serve-BYc2J-2F.js.map
1306
+ //# sourceMappingURL=serve-CqTdebcG.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-BYc2J-2F.js");
35
+ const { cmdServe } = await import("./serve-CqTdebcG.js");
36
36
  return cmdServe([
37
37
  "install",
38
38
  "--share",
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
42
42
 
43
43
  //#endregion
44
44
  export { cmdSetup };
45
- //# sourceMappingURL=setup-D9emVJYz.js.map
45
+ //# sourceMappingURL=setup-B3Vf8zfq.js.map
@@ -4,6 +4,6 @@ import "./configShared-CEyhl0WH.js";
4
4
  import "./e2e-Bfw7qL9O.js";
5
5
  import "./webrtcLink-CBZkZ-LT.js";
6
6
  import "./remotes-DBCvpp3B.js";
7
- import { C as runSubcommand, E as writeToIpc, S as resolveResumeArgs, T as stopTipForCli, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as snapshotStatus, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-C4tMi9NX.js";
7
+ import { C as runSubcommand, E as writeToIpc, S as resolveResumeArgs, T as stopTipForCli, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as snapshotStatus, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-DKL_x1ZC.js";
8
8
 
9
9
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -554,15 +554,15 @@ async function runSubcommand(argv) {
554
554
  case "restart": return await cmdRestart(rest);
555
555
  case "note": return await cmdNote(rest);
556
556
  case "serve": {
557
- const { cmdServe } = await import("./serve-BYc2J-2F.js");
557
+ const { cmdServe } = await import("./serve-CqTdebcG.js");
558
558
  return cmdServe(rest);
559
559
  }
560
560
  case "setup": {
561
- const { cmdSetup } = await import("./setup-D9emVJYz.js");
561
+ const { cmdSetup } = await import("./setup-B3Vf8zfq.js");
562
562
  return cmdSetup(rest);
563
563
  }
564
564
  case "schedule": {
565
- const { cmdSchedule } = await import("./schedule-BPFBe7R5.js");
565
+ const { cmdSchedule } = await import("./schedule-TJEVE-qs.js");
566
566
  return cmdSchedule(rest);
567
567
  }
568
568
  case "remote": {
@@ -2710,4 +2710,4 @@ async function cmdResultSet(rest) {
2710
2710
 
2711
2711
  //#endregion
2712
2712
  export { runSubcommand as C, writeToIpc as E, resolveResumeArgs as S, stopTipForCli as T, readPtysize as _, cursorAbs as a, resolveOne as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLog as v, snapshotStatus as w, resolveReadWindow as x, renderRawLogLines as y };
2713
- //# sourceMappingURL=subcommands-C4tMi9NX.js.map
2713
+ //# sourceMappingURL=subcommands-DKL_x1ZC.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-B-ToHoJ2.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DGqHb9YC.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
5
5
  import { t as PidStore } from "./pidStore-fqXqTKkh.js";
@@ -1802,4 +1802,4 @@ function sleep(ms) {
1802
1802
 
1803
1803
  //#endregion
1804
1804
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1805
- //# sourceMappingURL=ts-U3AE4u72.js.map
1805
+ //# sourceMappingURL=ts-CQZuynAm.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "claude-yes";
10
- var version = "1.154.0";
10
+ var version = "1.155.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-B-ToHoJ2.js.map
218
+ //# sourceMappingURL=versionChecker-DGqHb9YC.js.map
package/lab/ui/index.html CHANGED
@@ -1386,6 +1386,9 @@
1386
1386
  >
1387
1387
  <div class="rmenusep"></div>
1388
1388
  <button class="rmenuitem rmenuact" id="stopAgent" type="button">⏹ Stop agent</button>
1389
+ <button class="rmenuitem rmenuact" id="restartAgent" type="button">
1390
+ ↻ Restart (resume)
1391
+ </button>
1389
1392
  <button class="rmenuitem rmenuact danger" id="killAgent" type="button">
1390
1393
  ✕ Force-kill (SIGKILL)
1391
1394
  </button>
@@ -2803,6 +2806,20 @@
2803
2806
  await send("\r");
2804
2807
  }
2805
2808
 
2809
+ // Restart — stop (if live) + relaunch resuming the session, server-side via
2810
+ // POST /api/restart (the `ay restart` flow). The server runs it detached, so
2811
+ // it's decoupled from this agent's lifecycle — no prompt typed into the agent.
2812
+ async function restartAgent() {
2813
+ const e = sel ? entries.find((x) => x._key === sel) : null;
2814
+ if (!e) return;
2815
+ const who = `pid ${e.pid} ${cliLabel(e) || ident(e) || ""}`.trim();
2816
+ if (!confirm(`Restart ${who}?\nStops it (if live) and relaunches resuming the session.`))
2817
+ return;
2818
+ txFor(e)
2819
+ .post("/api/restart", { keyword: String(e.pid) })
2820
+ .catch(() => {});
2821
+ }
2822
+
2806
2823
  // ---- On-screen key bar (mobile) -------------------------------------
2807
2824
  // A phone keyboard has no Esc/Tab/Ctrl/arrows, so agent TUIs (claude,
2808
2825
  // codex, vim…) are otherwise undriveable. The bar posts raw byte
@@ -2994,6 +3011,7 @@
2994
3011
  }
2995
3012
  if (cb) cb.addEventListener("change", () => setPerfHud(cb.checked));
2996
3013
  $("stopAgent")?.addEventListener("click", () => (closePanel(), stopAgent(false)));
3014
+ $("restartAgent")?.addEventListener("click", () => (closePanel(), restartAgent()));
2997
3015
  $("killAgent")?.addEventListener("click", () => (closePanel(), stopAgent(true)));
2998
3016
  // Font stepper — stays open so you can tap repeatedly; persists + refits.
2999
3017
  $("fontDown")?.addEventListener("click", () => setTermFontSize(termFontSize - 1));
@@ -3766,6 +3784,18 @@
3766
3784
  return first ? first.entry : null;
3767
3785
  }
3768
3786
 
3787
+ // The fork SOURCE — strictly the agent that was open when the omnibox
3788
+ // launched, never the top-result fallback that omniAnchor() uses. "Fork
3789
+ // current branch" is meaningless without a concrete current branch: forking
3790
+ // whatever happens to rank first silently forks an unrelated agent (often the
3791
+ // console's own repo), landing the worktree in the wrong tree. So a fork is
3792
+ // offered ONLY when a real agent is selected AND it has live git info
3793
+ // (forkWorktree needs a worktree with a branch/origin).
3794
+ function omniForkSource() {
3795
+ const cur = omniReturnSel ? entries.find((x) => x._key === omniReturnSel) : null;
3796
+ return cur && cur.cwd && cur.git && cur.git.branch ? cur : null;
3797
+ }
3798
+
3769
3799
  // Which agent the highlighted candidate should preview in the background: an
3770
3800
  // agent row → that agent; the spawn row / an empty list → the launch-time
3771
3801
  // agent (so the backdrop shows the spawn target's cwd).
@@ -3840,8 +3870,8 @@
3840
3870
  return `<div class="omni-row omni-spawn${sc}" data-i="${i}">
3841
3871
  <span class="dot active"></span>
3842
3872
  <div class="omni-main">
3843
- <div class="omni-title">⑂ Fork current branch &amp; run <span style="opacity:.55">(carries uncommitted work)</span></div>
3844
- <div class="omni-sub">${esc(anchor?.cwd || "?")} &nbsp;→&nbsp; new branch <b>${esc(r.branch)}</b> &nbsp;·&nbsp; ⏎ to edit &amp; launch</div>
3873
+ <div class="omni-title">⑂ Fork <b>${esc(r.srcBranch)}</b> &amp; run <span style="opacity:.55">(carries uncommitted work)</span></div>
3874
+ <div class="omni-sub">${esc(r.fromCwd)} &nbsp;→&nbsp; new branch <b>${esc(r.branch)}</b> &nbsp;·&nbsp; ⏎ to edit &amp; launch</div>
3845
3875
  </div></div>`;
3846
3876
  }
3847
3877
  if (r.kind === "spawncwd") {
@@ -3882,9 +3912,19 @@
3882
3912
  omniRows = hits.map((entry) => ({ kind: "agent", entry }));
3883
3913
  if (q) {
3884
3914
  omniRows.push({ kind: "spawn" });
3885
- const anchor = omniAnchor();
3886
- if (anchor?.cwd)
3887
- omniRows.push({ kind: "fork", branch: forkSlug(q, anchor?.git?.branch) });
3915
+ // Fork is pinned to the open agent (omniForkSource), and the row carries
3916
+ // the exact source it will fork — cwd, branch, cli, room — so the launch
3917
+ // can't drift to a different anchor between render and activate.
3918
+ const forkSrc = omniForkSource();
3919
+ if (forkSrc)
3920
+ omniRows.push({
3921
+ kind: "fork",
3922
+ branch: forkSlug(q, forkSrc.git.branch),
3923
+ srcBranch: forkSrc.git.branch,
3924
+ fromCwd: forkSrc.cwd,
3925
+ cli: forkSrc.cli,
3926
+ room: forkSrc._room,
3927
+ });
3888
3928
  omniRows.push({ kind: "spawncwd" });
3889
3929
  }
3890
3930
  omniIdx = 0;
@@ -3962,7 +4002,10 @@
3962
4002
  return;
3963
4003
  }
3964
4004
  if (row && row.kind === "fork") {
3965
- // Editable at launch (auto-slug is the default); carries the anchor's WIP.
4005
+ // Editable at launch (auto-slug is the default); carries the source's WIP.
4006
+ // Fork from the source PINNED on the row (row.fromCwd), not a freshly
4007
+ // re-resolved anchor — the anchor can drift as the list refreshes, but the
4008
+ // branch we fork from must be the one the row was built for.
3966
4009
  const branch = prompt(
3967
4010
  "Fork to a new branch (carries your uncommitted work):",
3968
4011
  row.branch,
@@ -3971,11 +4014,11 @@
3971
4014
  closeOmni(false);
3972
4015
  await spawnAndSelect(
3973
4016
  {
3974
- cli: anchor?.cli || "claude",
4017
+ cli: row.cli || "claude",
3975
4018
  prompt: q || undefined,
3976
- fork: { fromCwd: anchor?.cwd, branch: branch.trim() },
4019
+ fork: { fromCwd: row.fromCwd, branch: branch.trim() },
3977
4020
  },
3978
- anchor?._room,
4021
+ row.room,
3979
4022
  );
3980
4023
  return;
3981
4024
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.154.0",
3
+ "version": "1.155.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
package/ts/serve.ts CHANGED
@@ -1414,6 +1414,39 @@ export async function cmdServe(rest: string[]): Promise<number> {
1414
1414
  }
1415
1415
  }
1416
1416
 
1417
+ // POST /api/restart body {keyword, fresh?} — the console-native `ay restart`:
1418
+ // stop the agent (if live) then relaunch it RESUMING its session. Restart is a
1419
+ // multi-second flow (graceful /exit → wait for exit → relaunch) that must
1420
+ // OUTLIVE this request and must NOT be a child of the agent it restarts — so we
1421
+ // kick it off as a detached `agent-yes restart` and return immediately; the
1422
+ // console sees the old→new pid swap over /api/ls/subscribe. This is why restart
1423
+ // is a real server action and not a prompt typed into the agent.
1424
+ if (req.method === "POST" && p === "/api/restart") {
1425
+ let body: { keyword?: string; fresh?: boolean };
1426
+ try {
1427
+ body = (await req.json()) as typeof body;
1428
+ } catch {
1429
+ return new Response("invalid JSON body", { status: 400 });
1430
+ }
1431
+ const keyword = body.keyword;
1432
+ if (!keyword || typeof keyword !== "string")
1433
+ return new Response("missing keyword", { status: 400 });
1434
+ try {
1435
+ const record = await resolveOne(keyword, defaultOpts({ all: true }));
1436
+ const args = ["restart", String(record.pid)];
1437
+ if (body.fresh) args.push("--fresh");
1438
+ const child = Bun.spawn(["agent-yes", ...args], {
1439
+ cwd: record.cwd,
1440
+ detached: true,
1441
+ stdio: ["ignore", "ignore", "ignore"],
1442
+ });
1443
+ child.unref();
1444
+ return Response.json({ ok: true, pid: record.pid });
1445
+ } catch (e) {
1446
+ return new Response((e as Error).message, { status: 404 });
1447
+ }
1448
+ }
1449
+
1417
1450
  // POST /api/resize/:keyword body {cols, rows} — drive the agent's PTY size.
1418
1451
  // Mirrors `ay attach`: write ~/.agent-yes/winsize/<pid> then SIGWINCH; the
1419
1452
  // agent's resize listener picks it up and reflows its TUI to that width.
@@ -1518,9 +1551,20 @@ export async function cmdServe(rest: string[]): Promise<number> {
1518
1551
  let cwd: string;
1519
1552
  let provisioned: { action: string; folder: string } | null = null;
1520
1553
  const fork =
1521
- body.fork && typeof body.fork.fromCwd === "string" && typeof body.fork.branch === "string"
1522
- ? { fromCwd: body.fork.fromCwd, branch: body.fork.branch }
1554
+ body.fork &&
1555
+ typeof body.fork.fromCwd === "string" &&
1556
+ body.fork.fromCwd.trim() &&
1557
+ typeof body.fork.branch === "string" &&
1558
+ body.fork.branch.trim()
1559
+ ? { fromCwd: body.fork.fromCwd.trim(), branch: body.fork.branch.trim() }
1523
1560
  : null;
1561
+ // A fork request that didn't resolve to a valid source must fail LOUDLY, not
1562
+ // silently fall through to the plain-`cwd` branch below — that would spawn
1563
+ // the agent in the workspace root (≈ where `ay serve` runs) instead of a
1564
+ // worktree off the intended branch, which looks like the agent "ignoring"
1565
+ // the fork. An empty/garbled fork object is a client bug; surface it as 400.
1566
+ if (body.fork != null && !fork)
1567
+ return new Response("fork requires a non-empty fromCwd and branch", { status: 400 });
1524
1568
  const from = typeof body.from === "string" ? body.from.trim() : "";
1525
1569
  if (fork) {
1526
1570
  // Fork the anchor agent's branch (carrying its WIP) into a new sibling
@@ -1,8 +0,0 @@
1
- import "./ts-U3AE4u72.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-B-ToHoJ2.js";
4
- import "./pidStore-fqXqTKkh.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Dam11bMz.js";
7
-
8
- export { SUPPORTED_CLIS };