ai-whisper 0.5.9 → 0.6.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.
@@ -5914,6 +5914,7 @@ function createLiveSessionRuntime(input) {
5914
5914
  let relayPreviewVisible = false;
5915
5915
  let inputState = {};
5916
5916
  let pausedInputDepth = 0;
5917
+ let overlayPush = null;
5917
5918
  function setMountedRawMode(mode) {
5918
5919
  if (canManageRawMode) {
5919
5920
  ttyStdin.setRawMode?.(mode);
@@ -6004,6 +6005,10 @@ function createLiveSessionRuntime(input) {
6004
6005
  }
6005
6006
  }
6006
6007
  async function processChunk(raw) {
6008
+ if (overlayPush) {
6009
+ overlayPush(raw);
6010
+ return;
6011
+ }
6007
6012
  const normalized = normalizeTerminalInput({
6008
6013
  raw,
6009
6014
  state: inputState
@@ -6146,6 +6151,42 @@ function createLiveSessionRuntime(input) {
6146
6151
  }
6147
6152
  }
6148
6153
  },
6154
+ async runInteractiveOverlay(run) {
6155
+ pausedInputDepth += 1;
6156
+ setMountedRawMode(true);
6157
+ const buf = [];
6158
+ const wakeHolder = { fn: null };
6159
+ let done = false;
6160
+ overlayPush = (chunk) => {
6161
+ buf.push(chunk);
6162
+ wakeHolder.fn?.();
6163
+ };
6164
+ const keys = (async function* () {
6165
+ for (; ; ) {
6166
+ if (buf.length) {
6167
+ yield buf.shift();
6168
+ continue;
6169
+ }
6170
+ if (done) return;
6171
+ await new Promise((r) => wakeHolder.fn = r);
6172
+ }
6173
+ })();
6174
+ try {
6175
+ await run({
6176
+ keys,
6177
+ write: (s) => input.stdout.write(s),
6178
+ setRawMode: (on) => setMountedRawMode(on)
6179
+ });
6180
+ } finally {
6181
+ done = true;
6182
+ const fn = wakeHolder.fn;
6183
+ wakeHolder.fn = null;
6184
+ fn?.();
6185
+ overlayPush = null;
6186
+ pausedInputDepth = Math.max(0, pausedInputDepth - 1);
6187
+ setMountedRawMode(true);
6188
+ }
6189
+ },
6149
6190
  async stop() {
6150
6191
  clearRelayPreview();
6151
6192
  setMountedRawMode(Boolean(previousRawMode));
@@ -7326,10 +7367,21 @@ function isProtocolCompatible(theirs, ours = PROTOCOL_VERSION) {
7326
7367
  // ../../node_modules/.pnpm/@ai-ezio+harness@file+..+ai-ezio+packages+harness/node_modules/@ai-ezio/harness/dist/turn-gate.js
7327
7368
  var TurnGate = class {
7328
7369
  tail = Promise.resolve();
7370
+ heldCount = 0;
7371
+ /** True while any acquirer (queued or active) is outstanding. */
7372
+ get held() {
7373
+ return this.heldCount > 0;
7374
+ }
7329
7375
  /** Resolves with a release function once every earlier acquirer released. */
7330
7376
  acquire() {
7377
+ this.heldCount += 1;
7331
7378
  let release;
7332
- const held = new Promise((r) => release = r);
7379
+ const held = new Promise((r) => {
7380
+ release = () => {
7381
+ this.heldCount -= 1;
7382
+ r();
7383
+ };
7384
+ });
7333
7385
  const turn = this.tail.then(() => release);
7334
7386
  this.tail = this.tail.then(() => held);
7335
7387
  return turn;
@@ -7357,6 +7409,12 @@ var EngineExitedError = class extends Error {
7357
7409
  this.name = "EngineExitedError";
7358
7410
  }
7359
7411
  };
7412
+ var EngineBusyError = class extends Error {
7413
+ constructor(message = "cannot resume while a turn is in flight") {
7414
+ super(message);
7415
+ this.name = "EngineBusyError";
7416
+ }
7417
+ };
7360
7418
  var CompactTimeoutError = class extends Error {
7361
7419
  constructor(ms) {
7362
7420
  super(`compact control timed out after ${ms}ms (no compacted event)`);
@@ -7373,6 +7431,13 @@ var Session = class {
7373
7431
  closed = false;
7374
7432
  exitHandlers = [];
7375
7433
  ready;
7434
+ /** Bumped on every spawn (start + resume). The pump closure captures its own
7435
+ * generation; a stale generation's events and EOF are dropped, so a closed
7436
+ * child cannot corrupt a freshly-respawned session. */
7437
+ generation = 0;
7438
+ /** Resolves when the CURRENT pump's `for await` loop has fully unwound
7439
+ * (its `finally` ran). `resume()` awaits this to sequence teardown. */
7440
+ pumpDone = Promise.resolve();
7376
7441
  _transcriptPath;
7377
7442
  /** The `HAX_TRANSCRIPT` mirror path this session was started with, if any.
7378
7443
  * Consumers (ezio CLI, ai-whisper) read it here rather than re-deriving the
@@ -7504,25 +7569,40 @@ var Session = class {
7504
7569
  }
7505
7570
  /** Spawn hax, pump events, and gate on the `ready` protocol version. */
7506
7571
  async start(options = {}) {
7572
+ return this.spawnAndPump(options);
7573
+ }
7574
+ /** Spawn the child, launch the (generation-stamped) event pump, and resolve
7575
+ * the `ready` version gate. Shared by start() and resume() so the pump is
7576
+ * stamped in exactly one place. Captures the CURRENT generation — callers that
7577
+ * need a fresh generation (resume) bump it before calling. */
7578
+ async spawnAndPump(options) {
7507
7579
  this._transcriptPath = options.transcriptPath;
7508
- const spawned = spawnHax(options);
7580
+ const gen = this.generation;
7581
+ const spawned = (this.options.spawn ?? spawnHax)(options);
7509
7582
  this.spawned = spawned;
7510
7583
  spawned.child.on("exit", (code, signal) => {
7584
+ if (gen !== this.generation)
7585
+ return;
7511
7586
  for (const handler of this.exitHandlers)
7512
7587
  handler({ code, signal });
7513
7588
  });
7514
- const transport = new FdTransport(spawned.eventStream, spawned.controlStream);
7589
+ const transport = (this.options.transportFactory ?? ((e, c) => new FdTransport(e, c)))(spawned.eventStream, spawned.controlStream);
7515
7590
  this.transport = transport;
7516
- void (async () => {
7591
+ this.pumpDone = (async () => {
7517
7592
  try {
7518
- for await (const event of transport.events())
7593
+ for await (const event of transport.events()) {
7594
+ if (gen !== this.generation)
7595
+ continue;
7519
7596
  this.deliver(event);
7597
+ }
7520
7598
  } finally {
7521
- this.ended = true;
7522
- while (this.idleHooks.length)
7523
- this.idleHooks.shift()();
7524
- while (this.waiters.length || this.exclusiveWaiters.length)
7525
- this.deliver(null);
7599
+ if (gen === this.generation) {
7600
+ this.ended = true;
7601
+ while (this.idleHooks.length)
7602
+ this.idleHooks.shift()();
7603
+ while (this.waiters.length || this.exclusiveWaiters.length)
7604
+ this.deliver(null);
7605
+ }
7526
7606
  }
7527
7607
  })();
7528
7608
  const ready = await this.waitForEvent("ready");
@@ -7694,6 +7774,47 @@ var Session = class {
7694
7774
  throw new Error("unexpected event");
7695
7775
  return { turnId: e.turnId, content: e.content };
7696
7776
  }
7777
+ /** Switch this session to a past one: tear down the current hax child, reset
7778
+ * lifecycle state, and respawn headless hax with `--resume=ID` plus the prior
7779
+ * options. The constructor-bound onEvent stays attached. Rejects (engine left
7780
+ * closed) on spawn/protocol failure. Refuses while a turn holds the gate. */
7781
+ async resume(sessionId, options = {}) {
7782
+ if (this.gate.held) {
7783
+ throw new EngineBusyError();
7784
+ }
7785
+ const release = await this.gate.acquire();
7786
+ try {
7787
+ const dying = this.pumpDone;
7788
+ this.generation += 1;
7789
+ this.close();
7790
+ await dying;
7791
+ this.resetLifecycleLatches();
7792
+ const args = [...options.args ?? [], `--resume=${sessionId}`];
7793
+ return await this.spawnAndPump({
7794
+ ...options,
7795
+ args,
7796
+ transcriptPath: options.transcriptPath ?? this._transcriptPath
7797
+ });
7798
+ } finally {
7799
+ release();
7800
+ }
7801
+ }
7802
+ /** Clear the close()/EOF latches and any parked stream state so the same
7803
+ * Session object is reusable across a respawn. Safe only between turns (resume
7804
+ * holds the gate, so no waiters are outstanding). */
7805
+ resetLifecycleLatches() {
7806
+ this.closed = false;
7807
+ this.ended = false;
7808
+ this.ready = void 0;
7809
+ this.queue.length = 0;
7810
+ this.waiters.length = 0;
7811
+ this.exclusiveQueue.length = 0;
7812
+ this.exclusiveWaiters.length = 0;
7813
+ this.idleHooks.length = 0;
7814
+ this.swallowNextIdle = false;
7815
+ this.staleCompactsPending = 0;
7816
+ this.cycleInternal = false;
7817
+ }
7697
7818
  /** Start a fresh conversation; resolves once the engine is idle again.
7698
7819
  * Gate-serialized like every turn initiator (M11). */
7699
7820
  async newConversation() {
@@ -7931,6 +8052,122 @@ function createAutoCompactDriver(opts) {
7931
8052
  };
7932
8053
  }
7933
8054
 
8055
+ // ../../node_modules/.pnpm/@ai-ezio+harness@file+..+ai-ezio+packages+harness/node_modules/@ai-ezio/harness/dist/session-titles.js
8056
+ import { join as join11 } from "node:path";
8057
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
8058
+ import { dirname as dirname4 } from "node:path";
8059
+ var nodeTitleFs = {
8060
+ readFileSync: (p) => {
8061
+ try {
8062
+ return readFileSync3(p, "utf8");
8063
+ } catch {
8064
+ return void 0;
8065
+ }
8066
+ },
8067
+ writeFileSync: (p, d) => writeFileSync3(p, d),
8068
+ renameSync: (from, to) => renameSync(from, to),
8069
+ mkdirSync: (dir) => void mkdirSync4(dir, { recursive: true })
8070
+ };
8071
+ function defaultTitleStorePath(env = process.env) {
8072
+ const base = env.XDG_STATE_HOME || join11(env.HOME ?? "", ".local", "state");
8073
+ return join11(base, "ai-ezio", "session-titles.json");
8074
+ }
8075
+ function createSessionTitleStore(opts = {}) {
8076
+ const filePath = opts.filePath ?? defaultTitleStorePath();
8077
+ const fs2 = opts.fs ?? nodeTitleFs;
8078
+ const read = () => {
8079
+ const raw = fs2.readFileSync(filePath);
8080
+ if (!raw)
8081
+ return {};
8082
+ try {
8083
+ const parsed = JSON.parse(raw);
8084
+ return parsed && typeof parsed === "object" ? parsed : {};
8085
+ } catch {
8086
+ return {};
8087
+ }
8088
+ };
8089
+ return {
8090
+ getTitle: (id) => {
8091
+ const rec = read()[id];
8092
+ return rec && typeof rec.title === "string" ? rec.title : void 0;
8093
+ },
8094
+ setTitle: (id, title) => {
8095
+ const t = title.trim();
8096
+ if (t === "")
8097
+ return;
8098
+ const all = read();
8099
+ all[id] = { title: t, updatedAt: clockNow() };
8100
+ fs2.mkdirSync(dirname4(filePath));
8101
+ const tmp = `${filePath}.tmp`;
8102
+ fs2.writeFileSync(tmp, JSON.stringify(all, null, " "));
8103
+ fs2.renameSync(tmp, filePath);
8104
+ },
8105
+ loadTitles: () => {
8106
+ const all = read();
8107
+ const map = /* @__PURE__ */ new Map();
8108
+ for (const [id, rec] of Object.entries(all)) {
8109
+ if (rec && typeof rec.title === "string")
8110
+ map.set(id, rec.title);
8111
+ }
8112
+ return map;
8113
+ }
8114
+ };
8115
+ }
8116
+ function clockNow() {
8117
+ return Date.now();
8118
+ }
8119
+ function normalizeId(id) {
8120
+ return id && id !== "unknown" ? id : void 0;
8121
+ }
8122
+ function createRenameController(deps) {
8123
+ let currentId;
8124
+ let pendingTitle;
8125
+ let statusRequested = false;
8126
+ const requestStatusOnce = () => {
8127
+ if (statusRequested)
8128
+ return;
8129
+ statusRequested = true;
8130
+ deps.requestStatus();
8131
+ };
8132
+ const setId = (id) => {
8133
+ const next = normalizeId(id);
8134
+ if (!next || next === currentId)
8135
+ return;
8136
+ currentId = next;
8137
+ if (pendingTitle !== void 0) {
8138
+ deps.store.setTitle(next, pendingTitle);
8139
+ pendingTitle = void 0;
8140
+ }
8141
+ };
8142
+ return {
8143
+ currentSessionId: () => currentId,
8144
+ getSessionTitle: () => pendingTitle ?? (currentId ? deps.store.getTitle(currentId) : void 0),
8145
+ setSessionTitle: (title) => {
8146
+ const t = title.trim();
8147
+ if (t === "")
8148
+ return;
8149
+ if (currentId)
8150
+ deps.store.setTitle(currentId, t);
8151
+ else
8152
+ pendingTitle = t;
8153
+ },
8154
+ noteEvent: (event) => {
8155
+ if (event.type === "ready" || event.type === "status") {
8156
+ statusRequested = false;
8157
+ setId(event.sessionId);
8158
+ } else if (event.type === "idle" && currentId === void 0) {
8159
+ requestStatusOnce();
8160
+ }
8161
+ },
8162
+ noteNewConversation: () => {
8163
+ pendingTitle = void 0;
8164
+ currentId = void 0;
8165
+ statusRequested = false;
8166
+ requestStatusOnce();
8167
+ }
8168
+ };
8169
+ }
8170
+
7934
8171
  // ../adapter-ai-ezio/dist/ai-ezio-engine.js
7935
8172
  var defaultCreateEngineSession = ({ onEvent }) => new Session({ onEvent });
7936
8173
 
@@ -8214,11 +8451,11 @@ async function callHostRehydration(host) {
8214
8451
  }
8215
8452
 
8216
8453
  // ../../node_modules/.pnpm/@ai-ezio+mcp-host@file+..+ai-ezio+packages+mcp-host_zod@3.25.76/node_modules/@ai-ezio/mcp-host/dist/config.js
8217
- import { readFileSync as readFileSync3 } from "node:fs";
8218
- import { join as join11 } from "node:path";
8454
+ import { readFileSync as readFileSync4 } from "node:fs";
8455
+ import { join as join12 } from "node:path";
8219
8456
  function configPath(env = process.env) {
8220
- const base = env.XDG_CONFIG_HOME?.trim() || join11(env.HOME ?? "", ".config");
8221
- return join11(base, "ai-ezio", "mcp.json");
8457
+ const base = env.XDG_CONFIG_HOME?.trim() || join12(env.HOME ?? "", ".config");
8458
+ return join12(base, "ai-ezio", "mcp.json");
8222
8459
  }
8223
8460
  function parseConfig(text) {
8224
8461
  if (!text || !text.trim())
@@ -8238,7 +8475,7 @@ function parseConfig(text) {
8238
8475
  }
8239
8476
  function loadConfig2(env = process.env) {
8240
8477
  try {
8241
- return parseConfig(readFileSync3(configPath(env), "utf8"));
8478
+ return parseConfig(readFileSync4(configPath(env), "utf8"));
8242
8479
  } catch {
8243
8480
  return { servers: [], toolPolicy: {}, hostPrivateTools: [] };
8244
8481
  }
@@ -8406,8 +8643,8 @@ function fmtTokens(n) {
8406
8643
  return `${Math.floor((n + 512 * 1024) / (1024 * 1024))}M`;
8407
8644
  }
8408
8645
  function truncate(s, max = ARG_MAX) {
8409
- const oneLine = s.replace(/\s+/g, " ").trim();
8410
- return oneLine.length > max ? `${oneLine.slice(0, max - 1)}\u2026` : oneLine;
8646
+ const oneLine2 = s.replace(/\s+/g, " ").trim();
8647
+ return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
8411
8648
  }
8412
8649
  function createMountedRenderer(input) {
8413
8650
  const utf8 = input.utf8 ?? true;
@@ -8550,6 +8787,9 @@ ${prompt}`);
8550
8787
  renderPrompt,
8551
8788
  handle(event) {
8552
8789
  switch (event.type) {
8790
+ case "ready":
8791
+ bannerRendered = false;
8792
+ break;
8553
8793
  case "status":
8554
8794
  if (!bannerRendered) {
8555
8795
  renderBanner(event.provider, event.model, event.effort ?? "");
@@ -8602,23 +8842,23 @@ ${RED}\u258C ${event.message}${RESET2}`);
8602
8842
  }
8603
8843
 
8604
8844
  // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/skills.js
8605
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
8606
- import { join as join12 } from "node:path";
8845
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
8846
+ import { join as join13 } from "node:path";
8607
8847
  function configBase(env) {
8608
- return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join12(env.home, ".config");
8848
+ return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join13(env.home, ".config");
8609
8849
  }
8610
8850
  function skillDirs(env) {
8611
8851
  const base = configBase(env);
8612
8852
  return [
8613
- { source: "project", path: join12(env.cwd, ".agents", "skills"), engineVisible: true },
8853
+ { source: "project", path: join13(env.cwd, ".agents", "skills"), engineVisible: true },
8614
8854
  {
8615
8855
  source: "ai-ezio-global",
8616
- path: join12(base, "ai-ezio", "skills"),
8856
+ path: join13(base, "ai-ezio", "skills"),
8617
8857
  // Engine-visible as of M4: ai-ezio sets HAX_EXTRA_SKILLS_DIR to this dir
8618
8858
  // on launch, so hax injects these skills into the model prompt.
8619
8859
  engineVisible: true
8620
8860
  },
8621
- { source: "hax-global", path: join12(base, "hax", "skills"), engineVisible: true }
8861
+ { source: "hax-global", path: join13(base, "hax", "skills"), engineVisible: true }
8622
8862
  ];
8623
8863
  }
8624
8864
  function parseSkillDescription(md) {
@@ -8649,7 +8889,7 @@ function discoverSkills(env, fs2) {
8649
8889
  for (const name of fs2.listDirs(dir.path)) {
8650
8890
  if (byName.has(name))
8651
8891
  continue;
8652
- const skillMdPath = join12(dir.path, name, "SKILL.md");
8892
+ const skillMdPath = join13(dir.path, name, "SKILL.md");
8653
8893
  const md = fs2.readFile(skillMdPath);
8654
8894
  if (md === null)
8655
8895
  continue;
@@ -8682,7 +8922,7 @@ function nodeSkillFs() {
8682
8922
  },
8683
8923
  readFile: (path) => {
8684
8924
  try {
8685
- return readFileSync4(path, "utf8");
8925
+ return readFileSync5(path, "utf8");
8686
8926
  } catch {
8687
8927
  return null;
8688
8928
  }
@@ -8717,7 +8957,7 @@ function makeClipboard(platform, spawnFn = spawn6) {
8717
8957
  }
8718
8958
 
8719
8959
  // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/transcript-view.js
8720
- import { join as join13 } from "node:path";
8960
+ import { join as join14 } from "node:path";
8721
8961
  async function showTranscript(deps) {
8722
8962
  const text = deps.path ? deps.readText(deps.path) : void 0;
8723
8963
  if (text === void 0 || text === "") {
@@ -8740,6 +8980,157 @@ async function showTranscript(deps) {
8740
8980
  }
8741
8981
  }
8742
8982
 
8983
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/resume-picker.js
8984
+ var PAGE_SIZE = 15;
8985
+ function parseSessions(json) {
8986
+ let raw;
8987
+ try {
8988
+ raw = JSON.parse(json);
8989
+ } catch {
8990
+ return [];
8991
+ }
8992
+ if (!Array.isArray(raw))
8993
+ return [];
8994
+ const rows = [];
8995
+ for (const e of raw) {
8996
+ if (!e || typeof e !== "object")
8997
+ continue;
8998
+ const o = e;
8999
+ if (typeof o.id !== "string" || o.id === "")
9000
+ continue;
9001
+ rows.push({
9002
+ id: o.id,
9003
+ mtime: typeof o.mtime === "number" ? o.mtime : 0,
9004
+ firstPrompt: typeof o.firstPrompt === "string" ? o.firstPrompt : null
9005
+ });
9006
+ }
9007
+ return rows;
9008
+ }
9009
+ function formatRelativeTime(mtimeSec, nowMs) {
9010
+ const diff = Math.max(0, Math.floor(nowMs / 1e3) - mtimeSec);
9011
+ if (diff < 60)
9012
+ return "just now";
9013
+ if (diff < 3600)
9014
+ return `${Math.floor(diff / 60)}m ago`;
9015
+ if (diff < 86400)
9016
+ return `${Math.floor(diff / 3600)}h ago`;
9017
+ return `${Math.floor(diff / 86400)}d ago`;
9018
+ }
9019
+ function formatRow(row, nowMs, title) {
9020
+ const age = formatRelativeTime(row.mtime, nowMs);
9021
+ const label = (title ?? row.firstPrompt ?? "(no prompt)").replace(/\s+/g, " ").trim();
9022
+ const clamped = label.length > 70 ? `${label.slice(0, 69)}\u2026` : label;
9023
+ return `${age} \xB7 ${clamped}`;
9024
+ }
9025
+ function decodeChunk(s) {
9026
+ if (s === "\x1B[A" || s === "\x1BOA" || s === "k")
9027
+ return "up";
9028
+ if (s === "\x1B[B" || s === "\x1BOB" || s === "j")
9029
+ return "down";
9030
+ if (s === "\r" || s === "\n")
9031
+ return "enter";
9032
+ if (s === "\x1B" || s === "" || s === "" || s === "q")
9033
+ return "cancel";
9034
+ if (s === "[")
9035
+ return "pageprev";
9036
+ if (s === "]")
9037
+ return "pagenext";
9038
+ if (s === "")
9039
+ return "toggleall";
9040
+ return "other";
9041
+ }
9042
+ function applyKey(state, token) {
9043
+ const { index, count, pageSize, showAll } = state;
9044
+ const pageStart = Math.floor(index / pageSize) * pageSize;
9045
+ const pageEnd = Math.min(count - 1, pageStart + pageSize - 1);
9046
+ const keep = { index, showAll };
9047
+ switch (token) {
9048
+ case "up":
9049
+ return { index: Math.max(showAll ? 0 : pageStart, index - 1), showAll };
9050
+ case "down":
9051
+ return { index: Math.min(showAll ? count - 1 : pageEnd, index + 1), showAll };
9052
+ case "pageprev":
9053
+ return !showAll && pageStart > 0 ? { index: pageStart - pageSize, showAll } : keep;
9054
+ case "pagenext":
9055
+ return !showAll && pageStart + pageSize < count ? { index: pageStart + pageSize, showAll } : keep;
9056
+ case "toggleall":
9057
+ return { index, showAll: !showAll };
9058
+ case "enter":
9059
+ return { index, showAll, done: "select" };
9060
+ case "cancel":
9061
+ return { index, showAll, done: "cancel" };
9062
+ default:
9063
+ return keep;
9064
+ }
9065
+ }
9066
+ var HINTS_PAGED = "\u2191/\u2193 move \xB7 [ ] page \xB7 Ctrl+A all \xB7 Enter select \xB7 Esc cancel";
9067
+ var HINTS_SINGLE = "\u2191/\u2193 move \xB7 Ctrl+A all \xB7 Enter select \xB7 Esc cancel";
9068
+ var HINTS_ALL = "\u2191/\u2193 move \xB7 Ctrl+A pages \xB7 Enter select \xB7 Esc cancel";
9069
+ function renderView(rows, state, nowMs, titles) {
9070
+ const { index, count, pageSize, showAll } = state;
9071
+ const pageCount = Math.max(1, Math.ceil(count / pageSize));
9072
+ const page = Math.floor(index / pageSize);
9073
+ const sliceStart = showAll ? 0 : page * pageSize;
9074
+ const sliceEnd = showAll ? count : Math.min(count, page * pageSize + pageSize);
9075
+ let header;
9076
+ let footer;
9077
+ if (showAll) {
9078
+ header = `Resume a session (showing all ${count} sessions)`;
9079
+ footer = HINTS_ALL;
9080
+ } else if (pageCount === 1) {
9081
+ header = `Resume a session (${count} session${count === 1 ? "" : "s"})`;
9082
+ footer = HINTS_SINGLE;
9083
+ } else {
9084
+ header = `Resume a session (page ${page + 1}/${pageCount} \xB7 ${count} sessions)`;
9085
+ footer = HINTS_PAGED;
9086
+ }
9087
+ const lines = [header];
9088
+ for (let i = sliceStart; i < sliceEnd; i++) {
9089
+ lines.push(oneLine(rows[i], i, index, nowMs, titles));
9090
+ }
9091
+ lines.push(footer);
9092
+ return `${lines.join("\n")}
9093
+ `;
9094
+ }
9095
+ function oneLine(row, i, selected, nowMs, titles) {
9096
+ const cursor = i === selected ? "\x1B[36m\u276F\x1B[0m" : " ";
9097
+ const n = `${i + 1}.`;
9098
+ const body = formatRow(row, nowMs, titles?.get(row.id));
9099
+ const text = i === selected ? `\x1B[1m${body}\x1B[0m` : `\x1B[2m${body}\x1B[0m`;
9100
+ return `${cursor} ${n} ${text}`;
9101
+ }
9102
+ async function runResumePicker(deps) {
9103
+ const rows = parseSessions(await deps.listSessions());
9104
+ if (rows.length === 0)
9105
+ return void 0;
9106
+ let state = { index: 0, count: rows.length, pageSize: PAGE_SIZE, showAll: false };
9107
+ let prevLines = 0;
9108
+ const draw = (first) => {
9109
+ const view = renderView(rows, state, deps.now(), deps.titles);
9110
+ const reset = first ? "" : `\x1B[${prevLines}A\r\x1B[0J`;
9111
+ deps.write(reset + view);
9112
+ prevLines = (view.match(/\n/g) ?? []).length;
9113
+ };
9114
+ deps.setRawMode?.(true);
9115
+ try {
9116
+ draw(true);
9117
+ for await (const chunk of deps.keys) {
9118
+ const token = decodeChunk(typeof chunk === "string" ? chunk : String(chunk));
9119
+ const r = applyKey(state, token);
9120
+ state = { ...state, index: r.index, showAll: r.showAll };
9121
+ if (r.done === "cancel")
9122
+ return void 0;
9123
+ if (r.done === "select")
9124
+ return rows[state.index]?.id;
9125
+ draw(false);
9126
+ }
9127
+ return void 0;
9128
+ } finally {
9129
+ deps.setRawMode?.(false);
9130
+ deps.write("\n");
9131
+ }
9132
+ }
9133
+
8743
9134
  // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/slash.js
8744
9135
  var NAME_RE = /^[a-zA-Z][\w-]*$/;
8745
9136
  function classifyLine(line, known) {
@@ -8875,6 +9266,37 @@ function builtinCommands(listCommands) {
8875
9266
  }
8876
9267
  }
8877
9268
  },
9269
+ {
9270
+ name: "rename",
9271
+ summary: "set a friendly title for this session (shown in /resume)",
9272
+ run: (ctx, args) => {
9273
+ if (!ctx.setSessionTitle) {
9274
+ ctx.write("rename unavailable\n");
9275
+ return;
9276
+ }
9277
+ const title = args.trim();
9278
+ if (title === "") {
9279
+ const current = ctx.getSessionTitle?.();
9280
+ ctx.write(current ? `${current}
9281
+ ` : "no title set \xB7 usage: /rename <text>\n");
9282
+ return;
9283
+ }
9284
+ ctx.setSessionTitle(title);
9285
+ ctx.write(`\u2014 renamed to "${title}" \u2014
9286
+ `);
9287
+ }
9288
+ },
9289
+ {
9290
+ name: "resume",
9291
+ summary: "switch to a past session in this folder",
9292
+ run: async (ctx) => {
9293
+ if (!ctx.resume) {
9294
+ ctx.write("resume unavailable\n");
9295
+ return;
9296
+ }
9297
+ await ctx.resume();
9298
+ }
9299
+ },
8878
9300
  {
8879
9301
  name: "quit",
8880
9302
  aliases: ["exit"],
@@ -8954,6 +9376,44 @@ var SlashController = class {
8954
9376
  return { action: "handled" };
8955
9377
  }
8956
9378
  };
9379
+ async function runResumeFlow(deps) {
9380
+ if (deps.isBusy()) {
9381
+ deps.write("finish or interrupt the current turn first\n");
9382
+ return;
9383
+ }
9384
+ const titles = deps.titles();
9385
+ const active = deps.currentSessionId();
9386
+ const rows = parseSessions(await deps.listSessions()).filter((r) => r.id !== active);
9387
+ if (rows.length === 0) {
9388
+ deps.write("no other sessions in this folder\n");
9389
+ return;
9390
+ }
9391
+ let chosen;
9392
+ await deps.runOverlay(async (io) => {
9393
+ chosen = await runResumePicker({
9394
+ listSessions: async () => JSON.stringify(rows),
9395
+ // already parsed+filtered; re-serialize for the picker
9396
+ keys: io.keys,
9397
+ write: io.write,
9398
+ now: deps.now,
9399
+ setRawMode: io.setRawMode,
9400
+ titles
9401
+ });
9402
+ });
9403
+ if (!chosen)
9404
+ return;
9405
+ try {
9406
+ await deps.resume(chosen);
9407
+ } catch (e) {
9408
+ if (e.name === "EngineBusyError") {
9409
+ deps.write("finish or interrupt the current turn first\n");
9410
+ return;
9411
+ }
9412
+ deps.write(`resume failed: ${e.message}
9413
+ `);
9414
+ deps.onFatal();
9415
+ }
9416
+ }
8957
9417
 
8958
9418
  // ../adapter-ai-ezio/dist/mid-composition-shape.js
8959
9419
  var DRAFTING_PREFIXES = [
@@ -8993,9 +9453,9 @@ function isMidCompositionShape(message) {
8993
9453
  // ../adapter-ai-ezio/dist/create-ai-ezio-live-session.js
8994
9454
  import { spawn as spawn7 } from "node:child_process";
8995
9455
  import { randomUUID as randomUUID3 } from "node:crypto";
8996
- import { readFileSync as readFileSync5 } from "node:fs";
9456
+ import { readFileSync as readFileSync6 } from "node:fs";
8997
9457
  import { tmpdir } from "node:os";
8998
- import { join as join14 } from "node:path";
9458
+ import { join as join15 } from "node:path";
8999
9459
  var defaultBuildAutoCompact = ({ session, host, write }) => {
9000
9460
  const { compaction } = loadConfig();
9001
9461
  return createAutoCompactDriver({
@@ -9013,6 +9473,17 @@ function createAiEzioLiveSession(input) {
9013
9473
  const create = input.createEngineSession ?? defaultCreateEngineSession;
9014
9474
  const buildCompact = input.buildAutoCompact ?? defaultBuildAutoCompact;
9015
9475
  const host = input.mcpHost ?? loadMcpHost({ mode: "mounted", cwd: process.cwd() });
9476
+ const titleStore = input.titleStore ?? createSessionTitleStore();
9477
+ const listSessions = input.listSessions ?? (() => new Promise((resolve2) => {
9478
+ let out = "";
9479
+ const child = spawn7(resolveHaxBinary(), ["--list-sessions"], {
9480
+ cwd: process.cwd(),
9481
+ stdio: ["ignore", "pipe", "ignore"]
9482
+ });
9483
+ child.stdout?.on("data", (d) => void (out += d.toString("utf8")));
9484
+ child.on("error", () => resolve2("[]"));
9485
+ child.on("exit", (code) => resolve2(code === 0 ? out : "[]"));
9486
+ }));
9016
9487
  let session = null;
9017
9488
  let driver = null;
9018
9489
  let sawTurn = false;
@@ -9020,12 +9491,22 @@ function createAiEzioLiveSession(input) {
9020
9491
  let lastContent = "";
9021
9492
  let lastUsage;
9022
9493
  let slash = null;
9494
+ let inTurn = false;
9023
9495
  const outputHandlers = [];
9024
9496
  const turnFinishedHandlers = [];
9025
9497
  const fidelityDecisionHandlers = [];
9026
9498
  const exitHandlers = [];
9027
9499
  const renderer = createMountedRenderer({ stdout: input.stdout });
9500
+ const rename = createRenameController({
9501
+ store: titleStore,
9502
+ // Deferred for the same reason as standalone: noteEvent runs inside the
9503
+ // onEvent tee before the event reaches waiters; queueMicrotask runs status()
9504
+ // after the settling idle is routed to the in-flight turn.
9505
+ requestStatus: () => queueMicrotask(() => void session?.status().catch(() => {
9506
+ }))
9507
+ });
9028
9508
  const onEvent = (event) => {
9509
+ rename.noteEvent(event);
9029
9510
  const compacting = driver?.compacting() ?? false;
9030
9511
  if (!compacting)
9031
9512
  switch (event.type) {
@@ -9034,6 +9515,9 @@ function createAiEzioLiveSession(input) {
9034
9515
  for (const h of outputHandlers)
9035
9516
  h(event.text);
9036
9517
  break;
9518
+ case "assistant_turn_started":
9519
+ inTurn = true;
9520
+ break;
9037
9521
  case "assistant_turn_finished":
9038
9522
  sawTurn = true;
9039
9523
  lastContent = event.content;
@@ -9050,6 +9534,7 @@ function createAiEzioLiveSession(input) {
9050
9534
  pendingContent = event.content;
9051
9535
  break;
9052
9536
  case "idle":
9537
+ inTurn = false;
9053
9538
  if (sawTurn && pendingContent !== null) {
9054
9539
  const candidate = pendingContent;
9055
9540
  if (isMidCompositionShape(candidate)) {
@@ -9091,7 +9576,7 @@ function createAiEzioLiveSession(input) {
9091
9576
  for (const h of exitHandlers)
9092
9577
  h();
9093
9578
  });
9094
- const transcriptPath = join14(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
9579
+ const transcriptPath = join15(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
9095
9580
  await session.start({ transcriptPath });
9096
9581
  await host.start(session);
9097
9582
  const skillEnv = {
@@ -9101,9 +9586,45 @@ function createAiEzioLiveSession(input) {
9101
9586
  };
9102
9587
  const skillFs = nodeSkillFs();
9103
9588
  const sessionFacet = session;
9589
+ const resumeThunk = () => runResumeFlow({
9590
+ write: (s) => input.stdout.write(s),
9591
+ isBusy: () => inTurn,
9592
+ listSessions,
9593
+ titles: () => titleStore.loadTitles(),
9594
+ currentSessionId: () => rename.currentSessionId(),
9595
+ runOverlay: async (run) => {
9596
+ if (!input.runInteractiveOverlay) {
9597
+ input.stdout.write("resume unavailable in this host\n");
9598
+ return;
9599
+ }
9600
+ await input.runInteractiveOverlay(run);
9601
+ },
9602
+ resume: async (id) => {
9603
+ if (sessionFacet.transcriptPath !== void 0) {
9604
+ await session.resume(id, { transcriptPath: sessionFacet.transcriptPath });
9605
+ } else {
9606
+ await session.resume(id);
9607
+ }
9608
+ await host.start(session);
9609
+ },
9610
+ // Spec §4: a failed respawn leaves the engine closed — report (in
9611
+ // runResumeFlow) and exit cleanly. Firing the adapter's exit handlers is
9612
+ // the "normal engine-exit path" for a mounted pane.
9613
+ onFatal: () => {
9614
+ for (const h of exitHandlers)
9615
+ h();
9616
+ },
9617
+ now: input.now ?? (() => Date.now())
9618
+ });
9104
9619
  const slashCtx = {
9105
9620
  write: (s) => input.stdout.write(s),
9106
- session: sessionFacet,
9621
+ session: {
9622
+ newConversation: async () => {
9623
+ await sessionFacet.newConversation();
9624
+ rename.noteNewConversation();
9625
+ },
9626
+ status: () => sessionFacet.status()
9627
+ },
9107
9628
  ...driver ? { compactor: { compactNow: () => driver.compactNow() } } : {},
9108
9629
  lastContent: () => lastContent,
9109
9630
  lastUsage: () => lastUsage,
@@ -9117,7 +9638,7 @@ function createAiEzioLiveSession(input) {
9117
9638
  path: sessionFacet.transcriptPath ?? "",
9118
9639
  readText: (p) => {
9119
9640
  try {
9120
- return readFileSync5(p, "utf8");
9641
+ return readFileSync6(p, "utf8");
9121
9642
  } catch {
9122
9643
  return void 0;
9123
9644
  }
@@ -9129,7 +9650,11 @@ function createAiEzioLiveSession(input) {
9129
9650
  restoreRaw: () => {
9130
9651
  },
9131
9652
  write: (s) => input.stdout.write(s)
9132
- })
9653
+ }),
9654
+ currentSessionId: () => rename.currentSessionId(),
9655
+ getSessionTitle: () => rename.getSessionTitle(),
9656
+ setSessionTitle: (t) => rename.setSessionTitle(t),
9657
+ resume: resumeThunk
9133
9658
  };
9134
9659
  slash = new SlashController(slashCtx, { excludeCommands: ["quit"] });
9135
9660
  },
@@ -9139,6 +9664,7 @@ function createAiEzioLiveSession(input) {
9139
9664
  session = null;
9140
9665
  },
9141
9666
  writeUserInput(data) {
9667
+ inTurn = true;
9142
9668
  session?.submit(data);
9143
9669
  },
9144
9670
  interrupt() {
@@ -9216,7 +9742,10 @@ function createProviderForTarget(target) {
9216
9742
  }
9217
9743
  function createInteractiveSessionForTarget(input) {
9218
9744
  if (input.target === "ezio") {
9219
- return createAiEzioLiveSession({ stdout: input.stdout });
9745
+ return createAiEzioLiveSession({
9746
+ stdout: input.stdout,
9747
+ ...input.runInteractiveOverlay !== void 0 ? { runInteractiveOverlay: input.runInteractiveOverlay } : {}
9748
+ });
9220
9749
  }
9221
9750
  const baseExecArgs = getInteractiveSessionExecArgsForTarget(input.target, input.turnEvents);
9222
9751
  const execArgs = [...baseExecArgs, ...input.passthroughArgs ?? []];