ai-whisper 0.5.8 → 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);
@@ -5951,7 +5952,7 @@ function createLiveSessionRuntime(input) {
5951
5952
  input.interactiveSession.sendLocalMessage(CLEAR_LINE2);
5952
5953
  }
5953
5954
  let inputLineBuffer = "";
5954
- function feedLineBufferedInput(data) {
5955
+ async function feedLineBufferedInput(data) {
5955
5956
  for (const char of data) {
5956
5957
  if (char === "") {
5957
5958
  if (inputLineBuffer.length > 0) {
@@ -5970,7 +5971,7 @@ function createLiveSessionRuntime(input) {
5970
5971
  const completed = inputLineBuffer;
5971
5972
  inputLineBuffer = "";
5972
5973
  const session = input.interactiveSession;
5973
- if (completed.length > 0 && session.echoUserInput) {
5974
+ if (completed.length > 0) {
5974
5975
  const cols = ttyCols(input.stdout);
5975
5976
  const len = Array.from(completed).length;
5976
5977
  const plainRows = Math.max(1, Math.ceil((STRIPE_COLS + len) / cols));
@@ -5978,13 +5979,18 @@ function createLiveSessionRuntime(input) {
5978
5979
  if (plainRows > 1) erase += `\x1B[${plainRows - 1}A`;
5979
5980
  erase += "\x1B[J";
5980
5981
  input.stdout.write(erase);
5981
- session.echoUserInput(completed, cols);
5982
+ if (await session.tryConsumeLocalCommand?.(completed)) {
5983
+ continue;
5984
+ }
5985
+ if (session.echoUserInput) {
5986
+ session.echoUserInput(completed, cols);
5987
+ } else {
5988
+ input.stdout.write("\n");
5989
+ }
5990
+ input.interactiveSession.writeUserInput(completed);
5982
5991
  } else {
5983
5992
  input.stdout.write("\n");
5984
5993
  }
5985
- if (completed.length > 0) {
5986
- input.interactiveSession.writeUserInput(completed);
5987
- }
5988
5994
  continue;
5989
5995
  }
5990
5996
  if (char === "\b" || char === "\x7F") {
@@ -5999,6 +6005,10 @@ function createLiveSessionRuntime(input) {
5999
6005
  }
6000
6006
  }
6001
6007
  async function processChunk(raw) {
6008
+ if (overlayPush) {
6009
+ overlayPush(raw);
6010
+ return;
6011
+ }
6002
6012
  const normalized = normalizeTerminalInput({
6003
6013
  raw,
6004
6014
  state: inputState
@@ -6054,7 +6064,7 @@ function createLiveSessionRuntime(input) {
6054
6064
  clearRelayPreview();
6055
6065
  if (decision.kind === "passthrough") {
6056
6066
  if (input.lineBufferedInput) {
6057
- feedLineBufferedInput(decision.data);
6067
+ await feedLineBufferedInput(decision.data);
6058
6068
  } else {
6059
6069
  input.interactiveSession.writeUserInput(decision.data);
6060
6070
  }
@@ -6141,6 +6151,42 @@ function createLiveSessionRuntime(input) {
6141
6151
  }
6142
6152
  }
6143
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
+ },
6144
6190
  async stop() {
6145
6191
  clearRelayPreview();
6146
6192
  setMountedRawMode(Boolean(previousRawMode));
@@ -7321,10 +7367,21 @@ function isProtocolCompatible(theirs, ours = PROTOCOL_VERSION) {
7321
7367
  // ../../node_modules/.pnpm/@ai-ezio+harness@file+..+ai-ezio+packages+harness/node_modules/@ai-ezio/harness/dist/turn-gate.js
7322
7368
  var TurnGate = class {
7323
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
+ }
7324
7375
  /** Resolves with a release function once every earlier acquirer released. */
7325
7376
  acquire() {
7377
+ this.heldCount += 1;
7326
7378
  let release;
7327
- const held = new Promise((r) => release = r);
7379
+ const held = new Promise((r) => {
7380
+ release = () => {
7381
+ this.heldCount -= 1;
7382
+ r();
7383
+ };
7384
+ });
7328
7385
  const turn = this.tail.then(() => release);
7329
7386
  this.tail = this.tail.then(() => held);
7330
7387
  return turn;
@@ -7352,6 +7409,12 @@ var EngineExitedError = class extends Error {
7352
7409
  this.name = "EngineExitedError";
7353
7410
  }
7354
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
+ };
7355
7418
  var CompactTimeoutError = class extends Error {
7356
7419
  constructor(ms) {
7357
7420
  super(`compact control timed out after ${ms}ms (no compacted event)`);
@@ -7368,6 +7431,13 @@ var Session = class {
7368
7431
  closed = false;
7369
7432
  exitHandlers = [];
7370
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();
7371
7441
  _transcriptPath;
7372
7442
  /** The `HAX_TRANSCRIPT` mirror path this session was started with, if any.
7373
7443
  * Consumers (ezio CLI, ai-whisper) read it here rather than re-deriving the
@@ -7499,25 +7569,40 @@ var Session = class {
7499
7569
  }
7500
7570
  /** Spawn hax, pump events, and gate on the `ready` protocol version. */
7501
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) {
7502
7579
  this._transcriptPath = options.transcriptPath;
7503
- const spawned = spawnHax(options);
7580
+ const gen = this.generation;
7581
+ const spawned = (this.options.spawn ?? spawnHax)(options);
7504
7582
  this.spawned = spawned;
7505
7583
  spawned.child.on("exit", (code, signal) => {
7584
+ if (gen !== this.generation)
7585
+ return;
7506
7586
  for (const handler of this.exitHandlers)
7507
7587
  handler({ code, signal });
7508
7588
  });
7509
- const transport = new FdTransport(spawned.eventStream, spawned.controlStream);
7589
+ const transport = (this.options.transportFactory ?? ((e, c) => new FdTransport(e, c)))(spawned.eventStream, spawned.controlStream);
7510
7590
  this.transport = transport;
7511
- void (async () => {
7591
+ this.pumpDone = (async () => {
7512
7592
  try {
7513
- for await (const event of transport.events())
7593
+ for await (const event of transport.events()) {
7594
+ if (gen !== this.generation)
7595
+ continue;
7514
7596
  this.deliver(event);
7597
+ }
7515
7598
  } finally {
7516
- this.ended = true;
7517
- while (this.idleHooks.length)
7518
- this.idleHooks.shift()();
7519
- while (this.waiters.length || this.exclusiveWaiters.length)
7520
- 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
+ }
7521
7606
  }
7522
7607
  })();
7523
7608
  const ready = await this.waitForEvent("ready");
@@ -7689,6 +7774,47 @@ var Session = class {
7689
7774
  throw new Error("unexpected event");
7690
7775
  return { turnId: e.turnId, content: e.content };
7691
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
+ }
7692
7818
  /** Start a fresh conversation; resolves once the engine is idle again.
7693
7819
  * Gate-serialized like every turn initiator (M11). */
7694
7820
  async newConversation() {
@@ -7926,6 +8052,122 @@ function createAutoCompactDriver(opts) {
7926
8052
  };
7927
8053
  }
7928
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
+
7929
8171
  // ../adapter-ai-ezio/dist/ai-ezio-engine.js
7930
8172
  var defaultCreateEngineSession = ({ onEvent }) => new Session({ onEvent });
7931
8173
 
@@ -8209,11 +8451,11 @@ async function callHostRehydration(host) {
8209
8451
  }
8210
8452
 
8211
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
8212
- import { readFileSync as readFileSync3 } from "node:fs";
8213
- import { join as join11 } from "node:path";
8454
+ import { readFileSync as readFileSync4 } from "node:fs";
8455
+ import { join as join12 } from "node:path";
8214
8456
  function configPath(env = process.env) {
8215
- const base = env.XDG_CONFIG_HOME?.trim() || join11(env.HOME ?? "", ".config");
8216
- 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");
8217
8459
  }
8218
8460
  function parseConfig(text) {
8219
8461
  if (!text || !text.trim())
@@ -8233,7 +8475,7 @@ function parseConfig(text) {
8233
8475
  }
8234
8476
  function loadConfig2(env = process.env) {
8235
8477
  try {
8236
- return parseConfig(readFileSync3(configPath(env), "utf8"));
8478
+ return parseConfig(readFileSync4(configPath(env), "utf8"));
8237
8479
  } catch {
8238
8480
  return { servers: [], toolPolicy: {}, hostPrivateTools: [] };
8239
8481
  }
@@ -8401,8 +8643,8 @@ function fmtTokens(n) {
8401
8643
  return `${Math.floor((n + 512 * 1024) / (1024 * 1024))}M`;
8402
8644
  }
8403
8645
  function truncate(s, max = ARG_MAX) {
8404
- const oneLine = s.replace(/\s+/g, " ").trim();
8405
- 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;
8406
8648
  }
8407
8649
  function createMountedRenderer(input) {
8408
8650
  const utf8 = input.utf8 ?? true;
@@ -8545,6 +8787,9 @@ ${prompt}`);
8545
8787
  renderPrompt,
8546
8788
  handle(event) {
8547
8789
  switch (event.type) {
8790
+ case "ready":
8791
+ bannerRendered = false;
8792
+ break;
8548
8793
  case "status":
8549
8794
  if (!bannerRendered) {
8550
8795
  renderBanner(event.provider, event.model, event.effort ?? "");
@@ -8596,6 +8841,580 @@ ${RED}\u258C ${event.message}${RESET2}`);
8596
8841
  };
8597
8842
  }
8598
8843
 
8844
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/skills.js
8845
+ import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
8846
+ import { join as join13 } from "node:path";
8847
+ function configBase(env) {
8848
+ return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join13(env.home, ".config");
8849
+ }
8850
+ function skillDirs(env) {
8851
+ const base = configBase(env);
8852
+ return [
8853
+ { source: "project", path: join13(env.cwd, ".agents", "skills"), engineVisible: true },
8854
+ {
8855
+ source: "ai-ezio-global",
8856
+ path: join13(base, "ai-ezio", "skills"),
8857
+ // Engine-visible as of M4: ai-ezio sets HAX_EXTRA_SKILLS_DIR to this dir
8858
+ // on launch, so hax injects these skills into the model prompt.
8859
+ engineVisible: true
8860
+ },
8861
+ { source: "hax-global", path: join13(base, "hax", "skills"), engineVisible: true }
8862
+ ];
8863
+ }
8864
+ function parseSkillDescription(md) {
8865
+ const text = md.replace(/^/, "");
8866
+ if (!/^---\r?\n/.test(text))
8867
+ return null;
8868
+ const end = text.indexOf("\n---", 4);
8869
+ if (end === -1)
8870
+ return null;
8871
+ const front = text.slice(text.indexOf("\n") + 1, end);
8872
+ for (const raw of front.split(/\r?\n/)) {
8873
+ const m = raw.match(/^description:\s*(.*)$/);
8874
+ if (m) {
8875
+ let value = (m[1] ?? "").trim();
8876
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
8877
+ value = value.slice(1, -1);
8878
+ }
8879
+ return value.length > 0 ? value : null;
8880
+ }
8881
+ }
8882
+ return null;
8883
+ }
8884
+ function discoverSkills(env, fs2) {
8885
+ const byName = /* @__PURE__ */ new Map();
8886
+ for (const dir of skillDirs(env)) {
8887
+ if (!fs2.isDirectory(dir.path))
8888
+ continue;
8889
+ for (const name of fs2.listDirs(dir.path)) {
8890
+ if (byName.has(name))
8891
+ continue;
8892
+ const skillMdPath = join13(dir.path, name, "SKILL.md");
8893
+ const md = fs2.readFile(skillMdPath);
8894
+ if (md === null)
8895
+ continue;
8896
+ byName.set(name, {
8897
+ name,
8898
+ description: parseSkillDescription(md),
8899
+ skillMdPath,
8900
+ source: dir.source,
8901
+ engineVisible: dir.engineVisible
8902
+ });
8903
+ }
8904
+ }
8905
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
8906
+ }
8907
+ function nodeSkillFs() {
8908
+ return {
8909
+ isDirectory: (path) => {
8910
+ try {
8911
+ return statSync3(path).isDirectory();
8912
+ } catch {
8913
+ return false;
8914
+ }
8915
+ },
8916
+ listDirs: (path) => {
8917
+ try {
8918
+ return readdirSync2(path, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
8919
+ } catch {
8920
+ return [];
8921
+ }
8922
+ },
8923
+ readFile: (path) => {
8924
+ try {
8925
+ return readFileSync5(path, "utf8");
8926
+ } catch {
8927
+ return null;
8928
+ }
8929
+ }
8930
+ };
8931
+ }
8932
+
8933
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/clipboard.js
8934
+ import { spawn as spawn6 } from "node:child_process";
8935
+ function tryCopy(argv, text, spawnFn) {
8936
+ return new Promise((resolve2, reject) => {
8937
+ const child = spawnFn(argv[0], argv.slice(1), { stdio: ["pipe", "ignore", "ignore"] });
8938
+ child.on("error", reject);
8939
+ child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${argv[0]} exited ${code}`)));
8940
+ child.stdin?.end(text);
8941
+ });
8942
+ }
8943
+ function makeClipboard(platform, spawnFn = spawn6) {
8944
+ const candidates = platform === "darwin" ? [["pbcopy"]] : [["wl-copy"], ["xclip", "-selection", "clipboard"]];
8945
+ return async (text) => {
8946
+ let lastErr = new Error("no clipboard tool available");
8947
+ for (const argv of candidates) {
8948
+ try {
8949
+ await tryCopy(argv, text, spawnFn);
8950
+ return;
8951
+ } catch (e) {
8952
+ lastErr = e;
8953
+ }
8954
+ }
8955
+ throw lastErr;
8956
+ };
8957
+ }
8958
+
8959
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/transcript-view.js
8960
+ import { join as join14 } from "node:path";
8961
+ async function showTranscript(deps) {
8962
+ const text = deps.path ? deps.readText(deps.path) : void 0;
8963
+ if (text === void 0 || text === "") {
8964
+ deps.write("\x1B[2m\u2500 no transcript yet \u2500\x1B[0m\n");
8965
+ return;
8966
+ }
8967
+ if (!deps.interactive) {
8968
+ deps.write(text.endsWith("\n") ? text : `${text}
8969
+ `);
8970
+ return;
8971
+ }
8972
+ deps.suspendRaw();
8973
+ try {
8974
+ await deps.spawnPager(deps.path);
8975
+ } catch {
8976
+ deps.write(text.endsWith("\n") ? text : `${text}
8977
+ `);
8978
+ } finally {
8979
+ deps.restoreRaw();
8980
+ }
8981
+ }
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
+
9134
+ // ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/slash.js
9135
+ var NAME_RE = /^[a-zA-Z][\w-]*$/;
9136
+ function classifyLine(line, known) {
9137
+ if (!line.startsWith("/"))
9138
+ return { kind: "submit" };
9139
+ if (line.includes("\n"))
9140
+ return { kind: "submit" };
9141
+ const body = line.slice(1);
9142
+ const ws = body.search(/\s/);
9143
+ const rawName = ws === -1 ? body : body.slice(0, ws);
9144
+ const args = ws === -1 ? "" : body.slice(ws).trim();
9145
+ if (rawName === "")
9146
+ return { kind: "submit" };
9147
+ if (!NAME_RE.test(rawName))
9148
+ return { kind: "submit" };
9149
+ const name = rawName.toLowerCase();
9150
+ return known.has(name) ? { kind: "command", name, args } : { kind: "unknown", name };
9151
+ }
9152
+ function renderHelp(ctx, cmds) {
9153
+ for (const c of cmds)
9154
+ ctx.write(` /${c.name} ${c.summary}
9155
+ `);
9156
+ ctx.write("\nshortcuts: Enter submit \xB7 Alt+Enter newline \xB7 paste multiline \xB7 Ctrl-C interrupt \xB7 Ctrl-D exit\n");
9157
+ }
9158
+ function formatUsage(u) {
9159
+ if (!u)
9160
+ return null;
9161
+ const parts = [];
9162
+ if (u.contextTokens !== void 0)
9163
+ parts.push(`context ${u.contextTokens}`);
9164
+ if (u.outputTokens !== void 0)
9165
+ parts.push(`output ${u.outputTokens}`);
9166
+ if (u.cachedTokens !== void 0)
9167
+ parts.push(`cached ${u.cachedTokens}`);
9168
+ if (u.contextLimit !== void 0)
9169
+ parts.push(`limit ${u.contextLimit}`);
9170
+ if (u.contextTokens !== void 0 && u.contextLimit !== void 0 && u.contextLimit > 0) {
9171
+ parts.push(`${Math.round(u.contextTokens / u.contextLimit * 100)}%`);
9172
+ }
9173
+ return parts.length ? parts.join(" \xB7 ") : null;
9174
+ }
9175
+ function builtinCommands(listCommands) {
9176
+ return [
9177
+ {
9178
+ name: "help",
9179
+ summary: "list commands and keyboard shortcuts",
9180
+ run: (ctx) => renderHelp(ctx, listCommands())
9181
+ },
9182
+ {
9183
+ name: "new",
9184
+ aliases: ["clear"],
9185
+ summary: "start a new conversation",
9186
+ run: async (ctx) => {
9187
+ ctx.recorder?.noteNewConversation();
9188
+ await ctx.session.newConversation();
9189
+ ctx.write("\u2014 new conversation \u2014\n");
9190
+ }
9191
+ },
9192
+ {
9193
+ name: "status",
9194
+ summary: "show provider, model, and effort",
9195
+ run: async (ctx) => {
9196
+ const s = await ctx.session.status();
9197
+ const effort = s.effort ? ` \xB7 ${s.effort}` : "";
9198
+ ctx.write(`${s.provider} \xB7 ${s.model}${effort}
9199
+ `);
9200
+ }
9201
+ },
9202
+ {
9203
+ name: "skills",
9204
+ summary: "list discovered skills",
9205
+ run: (ctx) => {
9206
+ const skills = ctx.skills();
9207
+ if (skills.length === 0) {
9208
+ ctx.write("(no skills found)\n");
9209
+ return;
9210
+ }
9211
+ for (const s of skills)
9212
+ ctx.write(` ${s.name} \xB7 ${s.source}
9213
+ `);
9214
+ }
9215
+ },
9216
+ {
9217
+ name: "copy",
9218
+ summary: "copy the last response to the clipboard",
9219
+ run: async (ctx) => {
9220
+ const text = ctx.lastContent();
9221
+ if (text === "") {
9222
+ ctx.write("no response to copy\n");
9223
+ return;
9224
+ }
9225
+ try {
9226
+ await ctx.clipboard(text);
9227
+ ctx.write(`copied ${Buffer.byteLength(text, "utf8")} bytes
9228
+ `);
9229
+ } catch (e) {
9230
+ ctx.write(`clipboard unavailable: ${e.message}
9231
+ `);
9232
+ }
9233
+ }
9234
+ },
9235
+ {
9236
+ name: "usage",
9237
+ summary: "show the last turn's token usage",
9238
+ run: (ctx) => {
9239
+ const formatted = formatUsage(ctx.lastUsage());
9240
+ ctx.write(formatted ? `${formatted}
9241
+ ` : "no usage yet\n");
9242
+ }
9243
+ },
9244
+ {
9245
+ name: "transcript",
9246
+ summary: "view the model-perspective transcript (same as Ctrl+T)",
9247
+ run: async (ctx) => {
9248
+ if (!ctx.showTranscript) {
9249
+ ctx.write("transcript unavailable\n");
9250
+ return;
9251
+ }
9252
+ await ctx.showTranscript();
9253
+ }
9254
+ },
9255
+ {
9256
+ name: "compact",
9257
+ summary: "summarize old history and free context",
9258
+ run: async (ctx) => {
9259
+ if (!ctx.compactor) {
9260
+ ctx.write("compaction unavailable\n");
9261
+ return;
9262
+ }
9263
+ const out = await ctx.compactor.compactNow();
9264
+ if (out.kind === "skipped" && out.reason === "in-progress") {
9265
+ ctx.write("compaction already in progress\n");
9266
+ }
9267
+ }
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
+ },
9300
+ {
9301
+ name: "quit",
9302
+ aliases: ["exit"],
9303
+ summary: "exit ezio",
9304
+ run: () => {
9305
+ }
9306
+ // the controller maps /quit to the exit outcome before run()
9307
+ }
9308
+ ];
9309
+ }
9310
+ var SlashController = class {
9311
+ ctx;
9312
+ /** name OR alias → command (so classifyLine's `known` set is keys()). */
9313
+ byKey = /* @__PURE__ */ new Map();
9314
+ constructor(ctx, opts) {
9315
+ this.ctx = ctx;
9316
+ const exclude = new Set(opts?.excludeCommands ?? []);
9317
+ for (const cmd of builtinCommands(() => this.summaries())) {
9318
+ if (exclude.has(cmd.name))
9319
+ continue;
9320
+ this.register(cmd);
9321
+ }
9322
+ }
9323
+ /** Register (or override) a command and its aliases. Last registration wins
9324
+ * per key: any command that already owns this command's NAME is fully evicted
9325
+ * (all of its keys removed) so an override replaces it cleanly; an alias key
9326
+ * collision is resolved key-by-key (the alias now points at the new command,
9327
+ * but the prior owner keeps its other keys). */
9328
+ register(cmd) {
9329
+ const displaced = this.byKey.get(cmd.name);
9330
+ if (displaced) {
9331
+ for (const [k, v] of this.byKey)
9332
+ if (v === displaced)
9333
+ this.byKey.delete(k);
9334
+ }
9335
+ this.byKey.set(cmd.name, cmd);
9336
+ for (const a of cmd.aliases ?? [])
9337
+ this.byKey.set(a, cmd);
9338
+ }
9339
+ /** Deduped canonical command list for /help. A command is listed only if it
9340
+ * still OWNS its own name key — this filters out any command reachable only
9341
+ * through a stolen alias key (e.g. another command claimed its name), so
9342
+ * /help never shows a stale entry. */
9343
+ summaries() {
9344
+ const seen = /* @__PURE__ */ new Set();
9345
+ const out = [];
9346
+ for (const cmd of this.byKey.values()) {
9347
+ if (seen.has(cmd))
9348
+ continue;
9349
+ seen.add(cmd);
9350
+ if (this.byKey.get(cmd.name) !== cmd)
9351
+ continue;
9352
+ out.push({ name: cmd.name, summary: cmd.summary });
9353
+ }
9354
+ return out;
9355
+ }
9356
+ async handle(line) {
9357
+ const c = classifyLine(line, new Set(this.byKey.keys()));
9358
+ if (c.kind === "submit")
9359
+ return { action: "submit", text: line };
9360
+ if (c.kind === "unknown") {
9361
+ this.ctx.write(`unknown command: /${c.name}. type /help for the list.
9362
+ `);
9363
+ return { action: "handled" };
9364
+ }
9365
+ const cmd = this.byKey.get(c.name);
9366
+ if (!cmd)
9367
+ return { action: "handled" };
9368
+ if (cmd.name === "quit")
9369
+ return { action: "exit" };
9370
+ try {
9371
+ await cmd.run(this.ctx, c.args);
9372
+ } catch (e) {
9373
+ this.ctx.write(`/${c.name} failed: ${e.message}
9374
+ `);
9375
+ }
9376
+ return { action: "handled" };
9377
+ }
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
+ }
9417
+
8599
9418
  // ../adapter-ai-ezio/dist/mid-composition-shape.js
8600
9419
  var DRAFTING_PREFIXES = [
8601
9420
  "let's draft",
@@ -8632,6 +9451,11 @@ function isMidCompositionShape(message) {
8632
9451
  }
8633
9452
 
8634
9453
  // ../adapter-ai-ezio/dist/create-ai-ezio-live-session.js
9454
+ import { spawn as spawn7 } from "node:child_process";
9455
+ import { randomUUID as randomUUID3 } from "node:crypto";
9456
+ import { readFileSync as readFileSync6 } from "node:fs";
9457
+ import { tmpdir } from "node:os";
9458
+ import { join as join15 } from "node:path";
8635
9459
  var defaultBuildAutoCompact = ({ session, host, write }) => {
8636
9460
  const { compaction } = loadConfig();
8637
9461
  return createAutoCompactDriver({
@@ -8649,16 +9473,40 @@ function createAiEzioLiveSession(input) {
8649
9473
  const create = input.createEngineSession ?? defaultCreateEngineSession;
8650
9474
  const buildCompact = input.buildAutoCompact ?? defaultBuildAutoCompact;
8651
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
+ }));
8652
9487
  let session = null;
8653
9488
  let driver = null;
8654
9489
  let sawTurn = false;
8655
9490
  let pendingContent = null;
9491
+ let lastContent = "";
9492
+ let lastUsage;
9493
+ let slash = null;
9494
+ let inTurn = false;
8656
9495
  const outputHandlers = [];
8657
9496
  const turnFinishedHandlers = [];
8658
9497
  const fidelityDecisionHandlers = [];
8659
9498
  const exitHandlers = [];
8660
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
+ });
8661
9508
  const onEvent = (event) => {
9509
+ rename.noteEvent(event);
8662
9510
  const compacting = driver?.compacting() ?? false;
8663
9511
  if (!compacting)
8664
9512
  switch (event.type) {
@@ -8667,8 +9515,13 @@ function createAiEzioLiveSession(input) {
8667
9515
  for (const h of outputHandlers)
8668
9516
  h(event.text);
8669
9517
  break;
9518
+ case "assistant_turn_started":
9519
+ inTurn = true;
9520
+ break;
8670
9521
  case "assistant_turn_finished":
8671
9522
  sawTurn = true;
9523
+ lastContent = event.content;
9524
+ lastUsage = event.usage;
8672
9525
  if (pendingContent !== null) {
8673
9526
  const superseded = pendingContent;
8674
9527
  for (const h of fidelityDecisionHandlers)
@@ -8681,6 +9534,7 @@ function createAiEzioLiveSession(input) {
8681
9534
  pendingContent = event.content;
8682
9535
  break;
8683
9536
  case "idle":
9537
+ inTurn = false;
8684
9538
  if (sawTurn && pendingContent !== null) {
8685
9539
  const candidate = pendingContent;
8686
9540
  if (isMidCompositionShape(candidate)) {
@@ -8722,8 +9576,87 @@ function createAiEzioLiveSession(input) {
8722
9576
  for (const h of exitHandlers)
8723
9577
  h();
8724
9578
  });
8725
- await session.start();
9579
+ const transcriptPath = join15(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
9580
+ await session.start({ transcriptPath });
8726
9581
  await host.start(session);
9582
+ const skillEnv = {
9583
+ cwd: process.cwd(),
9584
+ home: process.env.HOME ?? "",
9585
+ xdgConfigHome: process.env.XDG_CONFIG_HOME
9586
+ };
9587
+ const skillFs = nodeSkillFs();
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
+ });
9619
+ const slashCtx = {
9620
+ write: (s) => input.stdout.write(s),
9621
+ session: {
9622
+ newConversation: async () => {
9623
+ await sessionFacet.newConversation();
9624
+ rename.noteNewConversation();
9625
+ },
9626
+ status: () => sessionFacet.status()
9627
+ },
9628
+ ...driver ? { compactor: { compactNow: () => driver.compactNow() } } : {},
9629
+ lastContent: () => lastContent,
9630
+ lastUsage: () => lastUsage,
9631
+ skills: () => discoverSkills(skillEnv, skillFs).map((s) => ({
9632
+ name: s.name,
9633
+ source: s.source,
9634
+ description: s.description
9635
+ })),
9636
+ clipboard: input.clipboard ?? makeClipboard(process.platform, spawn7),
9637
+ showTranscript: () => showTranscript({
9638
+ path: sessionFacet.transcriptPath ?? "",
9639
+ readText: (p) => {
9640
+ try {
9641
+ return readFileSync6(p, "utf8");
9642
+ } catch {
9643
+ return void 0;
9644
+ }
9645
+ },
9646
+ interactive: false,
9647
+ spawnPager: () => Promise.resolve(),
9648
+ suspendRaw: () => {
9649
+ },
9650
+ restoreRaw: () => {
9651
+ },
9652
+ write: (s) => input.stdout.write(s)
9653
+ }),
9654
+ currentSessionId: () => rename.currentSessionId(),
9655
+ getSessionTitle: () => rename.getSessionTitle(),
9656
+ setSessionTitle: (t) => rename.setSessionTitle(t),
9657
+ resume: resumeThunk
9658
+ };
9659
+ slash = new SlashController(slashCtx, { excludeCommands: ["quit"] });
8727
9660
  },
8728
9661
  async stop() {
8729
9662
  await host.stop();
@@ -8731,11 +9664,18 @@ function createAiEzioLiveSession(input) {
8731
9664
  session = null;
8732
9665
  },
8733
9666
  writeUserInput(data) {
9667
+ inTurn = true;
8734
9668
  session?.submit(data);
8735
9669
  },
8736
9670
  interrupt() {
8737
9671
  session?.interrupt();
8738
9672
  },
9673
+ async tryConsumeLocalCommand(line) {
9674
+ if (!slash)
9675
+ return false;
9676
+ const outcome = await slash.handle(line);
9677
+ return outcome.action !== "submit";
9678
+ },
8739
9679
  echoUserInput(text, cols) {
8740
9680
  renderer.echoUserInput(text, cols);
8741
9681
  },
@@ -8802,7 +9742,10 @@ function createProviderForTarget(target) {
8802
9742
  }
8803
9743
  function createInteractiveSessionForTarget(input) {
8804
9744
  if (input.target === "ezio") {
8805
- return createAiEzioLiveSession({ stdout: input.stdout });
9745
+ return createAiEzioLiveSession({
9746
+ stdout: input.stdout,
9747
+ ...input.runInteractiveOverlay !== void 0 ? { runInteractiveOverlay: input.runInteractiveOverlay } : {}
9748
+ });
8806
9749
  }
8807
9750
  const baseExecArgs = getInteractiveSessionExecArgsForTarget(input.target, input.turnEvents);
8808
9751
  const execArgs = [...baseExecArgs, ...input.passthroughArgs ?? []];