@threadbase-sh/streamer 1.23.1 → 1.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -26621,7 +26621,7 @@ var require_transport = __commonJS({
26621
26621
  "node_modules/pino/lib/transport.js"(exports2, module2) {
26622
26622
  "use strict";
26623
26623
  var { createRequire } = require("module");
26624
- var { existsSync: existsSync11 } = require("fs");
26624
+ var { existsSync: existsSync12 } = require("fs");
26625
26625
  var getCallers = require_caller();
26626
26626
  var { join: join26, isAbsolute: isAbsolute3, sep: sep2 } = require("path");
26627
26627
  var { fileURLToPath: fileURLToPath3 } = require("url");
@@ -26695,7 +26695,7 @@ var require_transport = __commonJS({
26695
26695
  return false;
26696
26696
  }
26697
26697
  }
26698
- return isAbsolute3(path2) && !existsSync11(path2);
26698
+ return isAbsolute3(path2) && !existsSync12(path2);
26699
26699
  }
26700
26700
  function stripQuotes(value) {
26701
26701
  const first = value[0];
@@ -100885,8 +100885,8 @@ var require_pattern = __commonJS({
100885
100885
  }
100886
100886
  exports2.endsWithSlashGlobStar = endsWithSlashGlobStar;
100887
100887
  function isAffectDepthOfReadingPattern(pattern) {
100888
- const basename9 = path2.basename(pattern);
100889
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename9);
100888
+ const basename11 = path2.basename(pattern);
100889
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename11);
100890
100890
  }
100891
100891
  exports2.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
100892
100892
  function expandPatternsWithBraceExpansion(patterns2) {
@@ -124663,6 +124663,50 @@ var init_platform = __esm({
124663
124663
  }
124664
124664
  });
124665
124665
 
124666
+ // src/db/check-sqlite-abi.ts
124667
+ var check_sqlite_abi_exports = {};
124668
+ __export(check_sqlite_abi_exports, {
124669
+ SqliteAbiError: () => SqliteAbiError,
124670
+ checkSqliteAbi: () => checkSqliteAbi,
124671
+ isAbiMismatch: () => isAbiMismatch
124672
+ });
124673
+ function isAbiMismatch(message) {
124674
+ return ABI_MARKERS.some((m2) => message.includes(m2));
124675
+ }
124676
+ function checkSqliteAbi() {
124677
+ try {
124678
+ const Database3 = require("better-sqlite3");
124679
+ const db = new Database3(":memory:");
124680
+ db.close();
124681
+ } catch (err) {
124682
+ const message = err instanceof Error ? err.message : String(err);
124683
+ if (isAbiMismatch(message)) {
124684
+ throw new SqliteAbiError(message);
124685
+ }
124686
+ throw err;
124687
+ }
124688
+ }
124689
+ var REBUILD_HINT, SqliteAbiError, ABI_MARKERS;
124690
+ var init_check_sqlite_abi = __esm({
124691
+ "src/db/check-sqlite-abi.ts"() {
124692
+ "use strict";
124693
+ REBUILD_HINT = "npm rebuild better-sqlite3";
124694
+ SqliteAbiError = class extends Error {
124695
+ constructor(cause) {
124696
+ super(
124697
+ `better-sqlite3 native module failed to load \u2014 likely a Node ABI mismatch (node_modules was built against a different Node version).
124698
+ Running Node: ${process.version} (NODE_MODULE_VERSION ${process.versions.modules})
124699
+ Fix: ${REBUILD_HINT}
124700
+ (or 'npm ci' after any dependency-affecting pull)
124701
+ Underlying error: ${cause}`
124702
+ );
124703
+ this.name = "SqliteAbiError";
124704
+ }
124705
+ };
124706
+ ABI_MARKERS = ["NODE_MODULE_VERSION", "was compiled against a different Node.js version"];
124707
+ }
124708
+ });
124709
+
124666
124710
  // src/lifecycle/prefs.ts
124667
124711
  function readPrefs() {
124668
124712
  const path2 = prefsPath();
@@ -129594,10 +129638,10 @@ var ReaddirpStream = class extends import_node_stream.Readable {
129594
129638
  }
129595
129639
  async _formatEntry(dirent, path2) {
129596
129640
  let entry;
129597
- const basename9 = this._isDirent ? dirent.name : dirent;
129641
+ const basename11 = this._isDirent ? dirent.name : dirent;
129598
129642
  try {
129599
- const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2, basename9));
129600
- entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename: basename9 };
129643
+ const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2, basename11));
129644
+ entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename: basename11 };
129601
129645
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
129602
129646
  } catch (err) {
129603
129647
  this._onError(err);
@@ -130136,9 +130180,9 @@ var NodeFsHandler = class {
130136
130180
  _watchWithNodeFs(path2, listener) {
130137
130181
  const opts = this.fsw.options;
130138
130182
  const directory = sysPath.dirname(path2);
130139
- const basename9 = sysPath.basename(path2);
130183
+ const basename11 = sysPath.basename(path2);
130140
130184
  const parent = this.fsw._getWatchedDir(directory);
130141
- parent.add(basename9);
130185
+ parent.add(basename11);
130142
130186
  const absolutePath = sysPath.resolve(path2);
130143
130187
  const options = {
130144
130188
  persistent: opts.persistent
@@ -130148,7 +130192,7 @@ var NodeFsHandler = class {
130148
130192
  let closer;
130149
130193
  if (opts.usePolling) {
130150
130194
  const enableBin = opts.interval !== opts.binaryInterval;
130151
- options.interval = enableBin && isBinaryPath(basename9) ? opts.binaryInterval : opts.interval;
130195
+ options.interval = enableBin && isBinaryPath(basename11) ? opts.binaryInterval : opts.interval;
130152
130196
  closer = setFsWatchFileListener(path2, absolutePath, options, {
130153
130197
  listener,
130154
130198
  rawEmitter: this.fsw._emitRaw
@@ -130171,10 +130215,10 @@ var NodeFsHandler = class {
130171
130215
  return;
130172
130216
  }
130173
130217
  const dirname17 = sysPath.dirname(file2);
130174
- const basename9 = sysPath.basename(file2);
130218
+ const basename11 = sysPath.basename(file2);
130175
130219
  const parent = this.fsw._getWatchedDir(dirname17);
130176
130220
  let prevStats = stats;
130177
- if (parent.has(basename9))
130221
+ if (parent.has(basename11))
130178
130222
  return;
130179
130223
  const listener = async (path2, newStats) => {
130180
130224
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -130199,9 +130243,9 @@ var NodeFsHandler = class {
130199
130243
  prevStats = newStats2;
130200
130244
  }
130201
130245
  } catch (error51) {
130202
- this.fsw._remove(dirname17, basename9);
130246
+ this.fsw._remove(dirname17, basename11);
130203
130247
  }
130204
- } else if (parent.has(basename9)) {
130248
+ } else if (parent.has(basename11)) {
130205
130249
  const at2 = newStats.atimeMs;
130206
130250
  const mt2 = newStats.mtimeMs;
130207
130251
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -133973,11 +134017,11 @@ async function search(query, options, scanner) {
133973
134017
 
133974
134018
  // src/server.ts
133975
134019
  var import_events3 = require("events");
133976
- var import_fs23 = require("fs");
134020
+ var import_fs24 = require("fs");
133977
134021
  var import_promises14 = require("fs/promises");
133978
134022
  var import_http = require("http");
133979
134023
  var import_os10 = require("os");
133980
- var import_path19 = require("path");
134024
+ var import_path21 = require("path");
133981
134025
  var import_readline4 = require("readline");
133982
134026
 
133983
134027
  // src/agent/agent-client.ts
@@ -137104,8 +137148,10 @@ function runSqliteMigrations(db, migrationsDir) {
137104
137148
  // src/providers.ts
137105
137149
  var CLAUDE_CODE_PROVIDER2 = "claude-code";
137106
137150
  var CODEX_CLI_PROVIDER2 = "codex-cli";
137107
- function isProviderResumable(provider, availabilityResumable) {
137108
- if (provider === CODEX_CLI_PROVIDER2) return false;
137151
+ function isProviderName(value) {
137152
+ return value === CLAUDE_CODE_PROVIDER2 || value === CODEX_CLI_PROVIDER2;
137153
+ }
137154
+ function isProviderResumable(_provider, availabilityResumable) {
137109
137155
  return availabilityResumable;
137110
137156
  }
137111
137157
 
@@ -138182,67 +138228,15 @@ function handleListProjects(url2, res) {
138182
138228
  res.end(JSON.stringify({ projects: page, total }));
138183
138229
  }
138184
138230
 
138185
- // src/server.ts
138186
- init_logger();
138231
+ // src/live-session-manager.ts
138232
+ var import_path18 = require("path");
138187
138233
 
138188
- // src/pair-store.ts
138234
+ // src/codex-pty-runner.ts
138235
+ var import_headless = __toESM(require_xterm_headless(), 1);
138189
138236
  var import_crypto5 = require("crypto");
138190
- var DEFAULT_TTL_SECONDS = 180;
138191
- var SWEEP_INTERVAL_MS = 6e4;
138192
- var PairTokenStore = class {
138193
- current = null;
138194
- ttlMs;
138195
- sweepTimer = null;
138196
- constructor(opts = {}) {
138197
- this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
138198
- if (opts.autoSweep !== false) {
138199
- this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
138200
- this.sweepTimer.unref?.();
138201
- }
138202
- }
138203
- mint() {
138204
- const token = `pt_${(0, import_crypto5.randomBytes)(16).toString("hex")}`;
138205
- const expiresAt = Date.now() + this.ttlMs;
138206
- this.current = { token, expiresAt, used: false };
138207
- return {
138208
- token,
138209
- expiresAt,
138210
- expiresInSeconds: Math.floor(this.ttlMs / 1e3)
138211
- };
138212
- }
138213
- consume(token) {
138214
- const record2 = this.current;
138215
- if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
138216
- if (Date.now() > record2.expiresAt) {
138217
- this.current = null;
138218
- return { ok: false, reason: "expired" };
138219
- }
138220
- if (record2.used) return { ok: false, reason: "used" };
138221
- record2.used = true;
138222
- return { ok: true };
138223
- }
138224
- peek() {
138225
- return this.current;
138226
- }
138227
- clear() {
138228
- this.current = null;
138229
- }
138230
- sweep() {
138231
- if (this.current && Date.now() > this.current.expiresAt) {
138232
- this.current = null;
138233
- }
138234
- }
138235
- dispose() {
138236
- if (this.sweepTimer) clearInterval(this.sweepTimer);
138237
- this.sweepTimer = null;
138238
- this.current = null;
138239
- }
138240
- };
138241
-
138242
- // src/process-discovery.ts
138243
- var import_child_process2 = require("child_process");
138244
- var import_os9 = require("os");
138237
+ var import_fs20 = require("fs");
138245
138238
  var import_path16 = require("path");
138239
+ init_logger();
138246
138240
 
138247
138241
  // src/platform.ts
138248
138242
  var import_child_process = require("child_process");
@@ -138308,151 +138302,523 @@ function resolveClaudeExe() {
138308
138302
  _claudeExe = "claude";
138309
138303
  return _claudeExe;
138310
138304
  }
138311
-
138312
- // src/process-discovery.ts
138313
- async function discoverClaudeProcesses() {
138314
- if ((0, import_os9.platform)() === "win32") return discoverWindows();
138315
- return discoverUnix();
138316
- }
138317
- async function discoverUnix() {
138318
- const pids = await getPidsUnix();
138319
- const results = await Promise.all(
138320
- pids.map(async (pid) => {
138321
- try {
138322
- const [cwd, args, startedAt] = await Promise.all([
138323
- getProcessCwdUnix(pid),
138324
- getProcessArgsUnix(pid),
138325
- getProcessStartTimeUnix(pid)
138326
- ]);
138327
- const conversationId = extractResumeId(args);
138328
- return {
138329
- pid,
138330
- projectPath: cwd,
138331
- projectName: (0, import_path16.basename)(cwd),
138332
- branch: await readGitBranch2(cwd),
138333
- conversationId,
138334
- startedAt
138335
- };
138336
- } catch {
138337
- return null;
138305
+ var _codexExe;
138306
+ function resolveCodexExe() {
138307
+ if (_codexExe !== void 0) return _codexExe;
138308
+ if (isWindows2) {
138309
+ try {
138310
+ const found = (0, import_child_process.execFileSync)("where.exe", ["codex"], {
138311
+ encoding: "utf-8",
138312
+ windowsHide: true,
138313
+ timeout: 3e3
138314
+ }).trim().split("\n")[0].trim();
138315
+ if (found) {
138316
+ _codexExe = found;
138317
+ return _codexExe;
138338
138318
  }
138339
- })
138340
- );
138341
- return results.filter((r) => r !== null);
138342
- }
138343
- async function discoverWindows() {
138344
- const pids = await getPidsWindows();
138345
- const results = await Promise.all(
138346
- pids.map(async (pid) => {
138347
- try {
138348
- const info = await getProcessInfoWindows(pid);
138349
- if (!info) return null;
138350
- return {
138351
- pid,
138352
- projectPath: info.cwd,
138353
- projectName: (0, import_path16.basename)(info.cwd),
138354
- branch: await readGitBranch2(info.cwd),
138355
- conversationId: extractResumeId(info.args),
138356
- startedAt: info.startedAt
138357
- };
138358
- } catch {
138359
- return null;
138319
+ } catch {
138320
+ }
138321
+ const candidates = [
138322
+ (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex.exe"),
138323
+ (0, import_path15.join)(
138324
+ process.env.LOCALAPPDATA ?? (0, import_path15.join)((0, import_os8.homedir)(), "AppData", "Local"),
138325
+ "Microsoft",
138326
+ "WindowsApps",
138327
+ "codex.exe"
138328
+ )
138329
+ ];
138330
+ for (const p2 of candidates) {
138331
+ if ((0, import_fs19.existsSync)(p2)) {
138332
+ _codexExe = p2;
138333
+ return _codexExe;
138360
138334
  }
138361
- })
138362
- );
138363
- return results.filter((r) => r !== null);
138364
- }
138365
- function run(cmd, args, opts = {}) {
138366
- return new Promise((resolve6, reject) => {
138367
- (0, import_child_process2.execFile)(
138368
- cmd,
138369
- args,
138370
- { windowsHide: isWindows2, encoding: "utf-8", timeout: opts.timeout ?? 5e3, cwd: opts.cwd },
138371
- (err, stdout2) => {
138372
- if (err) reject(err);
138373
- else resolve6(stdout2);
138335
+ }
138336
+ } else {
138337
+ try {
138338
+ const found = (0, import_child_process.execFileSync)("/usr/bin/which", ["codex"], {
138339
+ encoding: "utf-8",
138340
+ timeout: 3e3
138341
+ }).trim().split("\n")[0].trim();
138342
+ if (found && (0, import_fs19.existsSync)(found)) {
138343
+ _codexExe = found;
138344
+ return _codexExe;
138374
138345
  }
138375
- );
138376
- });
138377
- }
138378
- async function getPidsUnix() {
138379
- try {
138380
- const output = await run("pgrep", ["-x", "claude"]);
138381
- return output.trim().split("\n").filter(Boolean).map((s3) => Number.parseInt(s3, 10));
138382
- } catch {
138383
- return [];
138346
+ } catch {
138347
+ }
138348
+ const candidates = [
138349
+ "/opt/homebrew/bin/codex",
138350
+ "/usr/local/bin/codex",
138351
+ (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex")
138352
+ ];
138353
+ for (const p2 of candidates) {
138354
+ if ((0, import_fs19.existsSync)(p2)) {
138355
+ _codexExe = p2;
138356
+ return _codexExe;
138357
+ }
138358
+ }
138384
138359
  }
138360
+ _codexExe = "codex";
138361
+ return _codexExe;
138385
138362
  }
138386
- async function getProcessCwdUnix(pid) {
138387
- const output = await run("lsof", ["-p", String(pid), "-a", "-d", "cwd", "-Fn"]);
138388
- const match2 = output.match(/n(.+)/);
138389
- return match2?.[1] ?? "";
138390
- }
138391
- async function getProcessArgsUnix(pid) {
138392
- return (await run("ps", ["-p", String(pid), "-o", "args="])).trim();
138393
- }
138394
- async function getProcessStartTimeUnix(pid) {
138395
- const raw2 = (await run("ps", ["-p", String(pid), "-o", "lstart="])).trim();
138396
- const d = new Date(raw2);
138397
- return Number.isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d;
138398
- }
138399
- async function getPidsWindows() {
138400
- try {
138401
- const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
138402
- return output.trim().split("\n").filter(Boolean).map((line) => {
138403
- const parts = line.split(",");
138404
- return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
138405
- }).filter((pid) => pid > 0);
138406
- } catch {
138407
- return [];
138408
- }
138363
+
138364
+ // src/codex-pty-runner.ts
138365
+ var OUTPUT_BUFFER_MAX = 65536;
138366
+ var PTY_COLS = 120;
138367
+ var PTY_ROWS = 40;
138368
+ var SCREEN_SCROLLBACK = 1e3;
138369
+ var CODEX_PROMPT_READY_TEXT = "Ready";
138370
+ var CODEX_TRUST_GATE_REGEX = /trust the contents/i;
138371
+ var SUBMIT_BYTES = "\r";
138372
+ var CODEX_SUBMIT_DELAY_MS = 16;
138373
+ function digestBytes(s3) {
138374
+ const escaped = s3.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
138375
+ if (escaped.length <= 200) return escaped;
138376
+ return `${escaped.slice(0, 100)}\u2026[${escaped.length - 200}B omitted]\u2026${escaped.slice(-100)}`;
138409
138377
  }
138410
- async function getProcessInfoWindows(pid) {
138378
+ var pty = null;
138379
+ async function loadPty() {
138380
+ if (pty) return pty;
138411
138381
  try {
138412
- const output = await run("wmic", [
138413
- "process",
138414
- "where",
138415
- `ProcessId=${pid}`,
138416
- "get",
138417
- "CommandLine,CreationDate,ExecutablePath",
138418
- "/FORMAT:CSV"
138419
- ]);
138420
- const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
138421
- if (lines.length < 2) return null;
138422
- const parts = lines[1].split(",");
138423
- const args = parts[1] ?? "";
138424
- const creationDate = parts[2] ?? "";
138425
- const year = creationDate.slice(0, 4);
138426
- const month = creationDate.slice(4, 6);
138427
- const day = creationDate.slice(6, 8);
138428
- const hour = creationDate.slice(8, 10);
138429
- const min = creationDate.slice(10, 12);
138430
- const sec = creationDate.slice(12, 14);
138431
- const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
138432
- if (Number.isNaN(startedAt.getTime())) return null;
138433
- const exePath = parts[3] ?? "";
138434
- const cwd = exePath ? (0, import_path16.dirname)(exePath) : "";
138435
- return { cwd, args, startedAt };
138436
- } catch {
138437
- return null;
138382
+ pty = await import("node-pty");
138383
+ return pty;
138384
+ } catch (err) {
138385
+ throw new Error(
138386
+ `node-pty is required for PTY management but failed to load. Ensure it is installed: npm install node-pty
138387
+ Original error: ${err}`
138388
+ );
138438
138389
  }
138439
138390
  }
138440
- function extractResumeId(args) {
138441
- const match2 = args.match(/--resume\s+(\S+)/);
138442
- return match2?.[1] ?? null;
138391
+ function createScreen() {
138392
+ return new import_headless.Terminal({
138393
+ cols: PTY_COLS,
138394
+ rows: PTY_ROWS,
138395
+ scrollback: SCREEN_SCROLLBACK,
138396
+ allowProposedApi: true
138397
+ });
138443
138398
  }
138444
- async function readGitBranch2(dir) {
138445
- try {
138446
- return (await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 3e3 })).trim();
138447
- } catch {
138448
- return "";
138399
+ var CodexPtyRunner = class {
138400
+ sessions = /* @__PURE__ */ new Map();
138401
+ onOutput;
138402
+ onStatusChange;
138403
+ onReady;
138404
+ // Accepted for shape-compatibility with PTYManagerOptions; Codex has no
138405
+ // detected equivalent yet (Phase 0) — never invoked.
138406
+ onPermissionChange;
138407
+ onLiveQuestion;
138408
+ onLiveQuestionGone;
138409
+ log;
138410
+ // Tracks sessions whose PTY has spawned but Codex hasn't yet reached its
138411
+ // "Ready" status bar — i.e. onReady hasn't fired.
138412
+ pendingReady = /* @__PURE__ */ new Set();
138413
+ // Inputs received via sendInput() while the session was still pendingReady.
138414
+ // Flushed in arrival order once Codex reaches Ready.
138415
+ queuedInputs = /* @__PURE__ */ new Map();
138416
+ // Per-session debounce so the directory-trust gate's \r is only written once.
138417
+ trustGateAnswered = /* @__PURE__ */ new Set();
138418
+ // In-flight start()/startFresh() calls keyed by sessionId. A second
138419
+ // concurrent resume for the same session (double-tap, client retry) awaits
138420
+ // the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
138421
+ startPromises = /* @__PURE__ */ new Map();
138422
+ constructor(options = {}) {
138423
+ this.onOutput = options.onOutput;
138424
+ this.onStatusChange = options.onStatusChange;
138425
+ this.onReady = options.onReady;
138426
+ this.onPermissionChange = options.onPermissionChange;
138427
+ this.onLiveQuestion = options.onLiveQuestion;
138428
+ this.onLiveQuestionGone = options.onLiveQuestionGone;
138429
+ this.log = options.logger ?? getLogger("codex-pty");
138430
+ }
138431
+ // Resume an existing Codex session. sessionId is the Codex-persisted
138432
+ // session_meta.payload.id (Phase 0, Section 8) — Codex has no fresh-session
138433
+ // equivalent of --session-id, so start() always means "resume".
138434
+ async start(sessionId, options) {
138435
+ const existing = this.sessions.get(sessionId);
138436
+ if (existing) return toPublicSession(existing);
138437
+ const inFlight = this.startPromises.get(sessionId);
138438
+ if (inFlight) return inFlight;
138439
+ const promise2 = this.doStart(sessionId, options).finally(() => {
138440
+ this.startPromises.delete(sessionId);
138441
+ });
138442
+ this.startPromises.set(sessionId, promise2);
138443
+ return promise2;
138444
+ }
138445
+ async doStart(sessionId, options) {
138446
+ const nodePty = await loadPty();
138447
+ const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
138448
+ const proc = nodePty.spawn(
138449
+ resolveCodexExe(),
138450
+ ["resume", sessionId, "--cd", options.projectPath, "--no-alt-screen"],
138451
+ {
138452
+ name: "xterm-256color",
138453
+ cols: PTY_COLS,
138454
+ rows: PTY_ROWS,
138455
+ cwd: options.projectPath,
138456
+ env: process.env
138457
+ }
138458
+ );
138459
+ const session = {
138460
+ id: sessionId,
138461
+ provider: CODEX_CLI_PROVIDER2,
138462
+ projectPath: options.projectPath,
138463
+ projectName,
138464
+ branch: options.branch ?? "",
138465
+ status: "running",
138466
+ startedAt: /* @__PURE__ */ new Date(),
138467
+ completedAt: null,
138468
+ promptCount: 0,
138469
+ lastOutput: "",
138470
+ process: proc,
138471
+ outputBuffer: Buffer.alloc(0),
138472
+ screen: createScreen()
138473
+ };
138474
+ this.sessions.set(sessionId, session);
138475
+ this.pendingReady.add(sessionId);
138476
+ proc.onData((data) => {
138477
+ this.handleOutput(sessionId, data);
138478
+ });
138479
+ proc.onExit(({ exitCode }) => {
138480
+ this.pendingReady.delete(sessionId);
138481
+ this.handleExit(sessionId, exitCode);
138482
+ });
138483
+ return toPublicSession(session);
138484
+ }
138485
+ // Start a brand-new Codex session. Codex has no --session-id equivalent for
138486
+ // a fresh launch — it assigns its own id, discovered later (Task 3's
138487
+ // binding logic). This runner generates a local placeholder id for the
138488
+ // ManagedSession handle only.
138489
+ async startFresh(options) {
138490
+ const nodePty = await loadPty();
138491
+ const sessionId = (0, import_crypto5.randomUUID)();
138492
+ const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
138493
+ const args = ["--cd", options.projectPath, "--no-alt-screen"];
138494
+ if (options.systemPrompt) {
138495
+ args.push(options.systemPrompt);
138496
+ }
138497
+ const proc = nodePty.spawn(resolveCodexExe(), args, {
138498
+ name: "xterm-256color",
138499
+ cols: PTY_COLS,
138500
+ rows: PTY_ROWS,
138501
+ cwd: options.projectPath,
138502
+ env: process.env
138503
+ });
138504
+ const session = {
138505
+ id: sessionId,
138506
+ provider: CODEX_CLI_PROVIDER2,
138507
+ projectPath: options.projectPath,
138508
+ projectName,
138509
+ branch: "",
138510
+ status: "running",
138511
+ startedAt: /* @__PURE__ */ new Date(),
138512
+ completedAt: null,
138513
+ promptCount: 0,
138514
+ lastOutput: "",
138515
+ process: proc,
138516
+ outputBuffer: Buffer.alloc(0),
138517
+ screen: createScreen()
138518
+ };
138519
+ this.sessions.set(sessionId, session);
138520
+ this.pendingReady.add(sessionId);
138521
+ proc.onData((data) => {
138522
+ this.handleOutput(sessionId, data);
138523
+ });
138524
+ proc.onExit(({ exitCode }) => {
138525
+ this.pendingReady.delete(sessionId);
138526
+ this.handleExit(sessionId, exitCode);
138527
+ });
138528
+ return toPublicSession(session);
138529
+ }
138530
+ // Write raw key bytes directly to the PTY, same as PTYManager.sendKeys.
138531
+ sendKeys(sessionId, keys) {
138532
+ const session = this.sessions.get(sessionId);
138533
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138534
+ if (session.status === "idle") {
138535
+ throw new Error(`Session is idle (no active PTY): ${sessionId}`);
138536
+ }
138537
+ if (session.status === "waiting_input") {
138538
+ session.status = "running";
138539
+ this.onStatusChange?.(toPublicSession(session));
138540
+ }
138541
+ this.log.info(
138542
+ `[codex.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes(keys)}`,
138543
+ { event: "codex.keys_write", sessionId, byteLen: keys.length }
138544
+ );
138545
+ session.process.write(keys);
138546
+ session.lastActivityAt = /* @__PURE__ */ new Date();
138547
+ }
138548
+ sendInput(sessionId, input) {
138549
+ const session = this.sessions.get(sessionId);
138550
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138551
+ if (session.status === "idle") {
138552
+ throw new Error(`Session is idle (no active PTY): ${sessionId}`);
138553
+ }
138554
+ if (this.pendingReady.has(sessionId)) {
138555
+ const queue = this.queuedInputs.get(sessionId) ?? [];
138556
+ queue.push(input);
138557
+ this.queuedInputs.set(sessionId, queue);
138558
+ session.lastActivityAt = /* @__PURE__ */ new Date();
138559
+ session.promptCount++;
138560
+ this.log.warn(
138561
+ `[codex.input.queued] ${sessionId.slice(0, 8)} promptCount=${session.promptCount} queueLen=${queue.length}`,
138562
+ {
138563
+ event: "codex.input_queued",
138564
+ sessionId,
138565
+ promptCount: session.promptCount,
138566
+ queueLen: queue.length,
138567
+ inputLen: input.length
138568
+ }
138569
+ );
138570
+ return session.promptCount;
138571
+ }
138572
+ if (session.status === "waiting_input") {
138573
+ session.status = "running";
138574
+ this.onStatusChange?.(toPublicSession(session));
138575
+ }
138576
+ this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
138577
+ session.lastActivityAt = /* @__PURE__ */ new Date();
138578
+ session.promptCount++;
138579
+ return session.promptCount;
138449
138580
  }
138581
+ // Write the input as plain bytes (no bracketed-paste wrap — Phase 0
138582
+ // confirmed Codex accepts plain keystrokes), then submit \r after a short
138583
+ // delay so Codex's TUI gets an event-loop tick to process the input first.
138584
+ writeSubmit(sessionId, session, input, path2, promptCount) {
138585
+ this.log.info(
138586
+ `[codex.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${input.length} digest=${digestBytes(input)}`,
138587
+ {
138588
+ event: "codex.input_write",
138589
+ sessionId,
138590
+ promptCount,
138591
+ byteLen: input.length,
138592
+ digest: digestBytes(input),
138593
+ path: path2,
138594
+ phase: "input"
138595
+ }
138596
+ );
138597
+ session.process.write(input);
138598
+ setTimeout(() => {
138599
+ const current = this.sessions.get(sessionId);
138600
+ if (!current || current !== session) return;
138601
+ this.log.info(
138602
+ `[codex.input.submit] ${sessionId.slice(0, 8)} promptCount=${promptCount} digest=\\r`,
138603
+ {
138604
+ event: "codex.input_write",
138605
+ sessionId,
138606
+ promptCount,
138607
+ byteLen: SUBMIT_BYTES.length,
138608
+ digest: "\\r",
138609
+ path: path2,
138610
+ phase: "submit"
138611
+ }
138612
+ );
138613
+ current.process.write(SUBMIT_BYTES);
138614
+ }, CODEX_SUBMIT_DELAY_MS);
138615
+ }
138616
+ // Drain any inputs sent while the session was still pendingReady, writing
138617
+ // them in arrival order now that Codex is Ready.
138618
+ flushQueuedInputs(sessionId) {
138619
+ const queue = this.queuedInputs.get(sessionId);
138620
+ if (!queue || queue.length === 0) return;
138621
+ this.queuedInputs.delete(sessionId);
138622
+ const session = this.sessions.get(sessionId);
138623
+ if (!session) return;
138624
+ this.log.info(
138625
+ `[codex.flush] ${sessionId.slice(0, 8)} flushing ${queue.length} queued input(s)`,
138626
+ {
138627
+ event: "codex.flush_queued",
138628
+ sessionId,
138629
+ queueLen: queue.length
138630
+ }
138631
+ );
138632
+ queue.forEach((input, i) => {
138633
+ const writeAt = i * CODEX_SUBMIT_DELAY_MS * 2;
138634
+ if (writeAt === 0) {
138635
+ this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
138636
+ } else {
138637
+ setTimeout(() => {
138638
+ const current = this.sessions.get(sessionId);
138639
+ if (!current || current !== session) return;
138640
+ this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
138641
+ }, writeAt);
138642
+ }
138643
+ });
138644
+ }
138645
+ // SIGINT produces a clean exitCode=0 exit (Phase 0 — confirmed).
138646
+ cancel(sessionId) {
138647
+ const session = this.sessions.get(sessionId);
138648
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138649
+ session.process.kill("SIGINT");
138650
+ }
138651
+ killPid(pid) {
138652
+ try {
138653
+ process.kill(pid, "SIGTERM");
138654
+ } catch {
138655
+ }
138656
+ }
138657
+ // Kill the PTY and mark the session idle. Mirrors PTYManager.putOnHold.
138658
+ putOnHold(sessionId) {
138659
+ const session = this.sessions.get(sessionId);
138660
+ if (!session) return;
138661
+ this.pendingReady.delete(sessionId);
138662
+ this.queuedInputs.delete(sessionId);
138663
+ this.trustGateAnswered.delete(sessionId);
138664
+ try {
138665
+ session.process.kill("SIGINT");
138666
+ } catch {
138667
+ }
138668
+ session.status = "idle";
138669
+ session.completedAt = /* @__PURE__ */ new Date();
138670
+ session.screen.dispose();
138671
+ this.sessions.delete(sessionId);
138672
+ this.onStatusChange?.(toPublicSession(session));
138673
+ }
138674
+ getOutput(sessionId) {
138675
+ const session = this.sessions.get(sessionId);
138676
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138677
+ return session.outputBuffer.toString("utf-8");
138678
+ }
138679
+ // Render the last `maxLines` rows of the session's screen in true on-screen
138680
+ // order — same flush-then-read technique as PTYManager.getOutputLines.
138681
+ async getOutputLines(sessionId, maxLines) {
138682
+ const session = this.sessions.get(sessionId);
138683
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138684
+ await new Promise((resolve6) => session.screen.write("", () => resolve6()));
138685
+ const buf = session.screen.buffer.active;
138686
+ const lines = [];
138687
+ for (let y2 = 0; y2 < buf.length; y2++) {
138688
+ lines.push(buf.getLine(y2)?.translateToString(true) ?? "");
138689
+ }
138690
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
138691
+ lines.pop();
138692
+ }
138693
+ return lines.slice(-maxLines);
138694
+ }
138695
+ getSession(sessionId) {
138696
+ const session = this.sessions.get(sessionId);
138697
+ return session ? toPublicSession(session) : null;
138698
+ }
138699
+ hasSession(sessionId) {
138700
+ return this.sessions.has(sessionId);
138701
+ }
138702
+ listSessions() {
138703
+ return Array.from(this.sessions.values()).map(toPublicSession);
138704
+ }
138705
+ dispose() {
138706
+ for (const session of this.sessions.values()) {
138707
+ try {
138708
+ session.process.kill();
138709
+ } catch {
138710
+ }
138711
+ session.screen.dispose();
138712
+ }
138713
+ this.sessions.clear();
138714
+ this.pendingReady.clear();
138715
+ this.queuedInputs.clear();
138716
+ this.trustGateAnswered.clear();
138717
+ }
138718
+ handleOutput(sessionId, data) {
138719
+ const session = this.sessions.get(sessionId);
138720
+ if (!session) return;
138721
+ const chunk = Buffer.from(data, "utf-8");
138722
+ session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
138723
+ if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
138724
+ session.outputBuffer = session.outputBuffer.subarray(
138725
+ session.outputBuffer.length - OUTPUT_BUFFER_MAX
138726
+ );
138727
+ }
138728
+ session.screen.write(data);
138729
+ session.lastOutput = stripAnsi(data);
138730
+ this.onOutput?.(sessionId, data);
138731
+ this.detectReady(sessionId, session).catch((err) => {
138732
+ this.log.warn("[codex.ready_detect] failed", {
138733
+ event: "codex.ready_detect_failed",
138734
+ sessionId,
138735
+ err
138736
+ });
138737
+ });
138738
+ }
138739
+ // Renders the session's headless screen and checks for the directory-trust
138740
+ // gate (answered once, debounced) and the "Ready" status-bar text. Only
138741
+ // transitions to waiting_input / fires onReady when the rendered status
138742
+ // line literally contains "Ready" — `›` alone (visible during "Starting")
138743
+ // is NOT a valid readiness signal (Phase 0).
138744
+ async detectReady(sessionId, session) {
138745
+ if (session.status !== "running" || !this.pendingReady.has(sessionId)) return;
138746
+ const lines = await this.getOutputLines(sessionId, PTY_ROWS);
138747
+ const screenText = lines.join("\n");
138748
+ if (CODEX_TRUST_GATE_REGEX.test(screenText)) {
138749
+ if (!this.trustGateAnswered.has(sessionId)) {
138750
+ this.trustGateAnswered.add(sessionId);
138751
+ this.log.info(`[codex.trust_gate] ${sessionId.slice(0, 8)} auto-answering`, {
138752
+ event: "codex.trust_gate",
138753
+ sessionId
138754
+ });
138755
+ session.process.write("\r");
138756
+ }
138757
+ return;
138758
+ }
138759
+ const lastNonBlank2 = [...lines].reverse().find((l) => l.trim() !== "") ?? "";
138760
+ if (!lastNonBlank2.includes(CODEX_PROMPT_READY_TEXT)) return;
138761
+ this.markReady(sessionId, session);
138762
+ }
138763
+ markReady(sessionId, session) {
138764
+ session.lastActivityAt = /* @__PURE__ */ new Date();
138765
+ session.status = "waiting_input";
138766
+ this.log.info(`[codex.ready] ${sessionId.slice(0, 8)}`, {
138767
+ event: "codex.ready",
138768
+ sessionId
138769
+ });
138770
+ this.onStatusChange?.(toPublicSession(session));
138771
+ if (this.pendingReady.has(sessionId)) {
138772
+ this.pendingReady.delete(sessionId);
138773
+ this.flushQueuedInputs(sessionId);
138774
+ this.onReady?.(toPublicSession(session));
138775
+ }
138776
+ }
138777
+ handleExit(sessionId, exitCode) {
138778
+ const session = this.sessions.get(sessionId);
138779
+ if (!session) return;
138780
+ session.completedAt = /* @__PURE__ */ new Date();
138781
+ session.status = "idle";
138782
+ const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
138783
+ if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
138784
+ if (!(0, import_fs20.existsSync)(session.projectPath)) {
138785
+ session.failureReason = `Project directory not found: ${session.projectPath}`;
138786
+ } else {
138787
+ session.failureReason = `Codex process exited immediately (code ${exitCode}).`;
138788
+ }
138789
+ }
138790
+ this.onStatusChange?.(toPublicSession(session));
138791
+ session.screen.dispose();
138792
+ this.sessions.delete(sessionId);
138793
+ this.queuedInputs.delete(sessionId);
138794
+ this.trustGateAnswered.delete(sessionId);
138795
+ }
138796
+ };
138797
+ function toPublicSession(s3) {
138798
+ return {
138799
+ id: s3.id,
138800
+ provider: s3.provider ?? CODEX_CLI_PROVIDER2,
138801
+ projectPath: s3.projectPath,
138802
+ projectName: s3.projectName,
138803
+ branch: s3.branch,
138804
+ status: s3.status,
138805
+ startedAt: s3.startedAt,
138806
+ completedAt: s3.completedAt,
138807
+ promptCount: s3.promptCount,
138808
+ lastOutput: s3.lastOutput,
138809
+ ...s3.failureReason != null && { failureReason: s3.failureReason },
138810
+ ...s3.lastActivityAt != null && { lastActivityAt: s3.lastActivityAt },
138811
+ ...s3.filePath != null && { filePath: s3.filePath }
138812
+ };
138813
+ }
138814
+ function stripAnsi(str) {
138815
+ return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
138450
138816
  }
138451
138817
 
138452
138818
  // src/pty-manager.ts
138453
- var import_headless = __toESM(require_xterm_headless(), 1);
138819
+ var import_headless2 = __toESM(require_xterm_headless(), 1);
138454
138820
  var import_crypto6 = require("crypto");
138455
- var import_fs20 = require("fs");
138821
+ var import_fs21 = require("fs");
138456
138822
  var import_path17 = require("path");
138457
138823
  init_logger();
138458
138824
 
@@ -138613,28 +138979,28 @@ function detectShellPrompt(lines) {
138613
138979
  }
138614
138980
 
138615
138981
  // src/pty-manager.ts
138616
- var OUTPUT_BUFFER_MAX = 65536;
138617
- var PTY_COLS = 120;
138618
- var PTY_ROWS = 40;
138619
- var SCREEN_SCROLLBACK = 1e3;
138982
+ var OUTPUT_BUFFER_MAX2 = 65536;
138983
+ var PTY_COLS2 = 120;
138984
+ var PTY_ROWS2 = 40;
138985
+ var SCREEN_SCROLLBACK2 = 1e3;
138620
138986
  var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
138621
138987
  var PROMPT_MARKER_FALLBACK_MS = 1e4;
138622
138988
  function buildPasteBytes(input) {
138623
138989
  return `\x1B[200~${input}\x1B[201~`;
138624
138990
  }
138625
- var SUBMIT_BYTES = "\r";
138991
+ var SUBMIT_BYTES2 = "\r";
138626
138992
  var SUBMIT_DELAY_MS = 16;
138627
- function digestBytes(s3) {
138993
+ function digestBytes2(s3) {
138628
138994
  const escaped = s3.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
138629
138995
  if (escaped.length <= 200) return escaped;
138630
138996
  return `${escaped.slice(0, 100)}\u2026[${escaped.length - 200}B omitted]\u2026${escaped.slice(-100)}`;
138631
138997
  }
138632
- var pty = null;
138633
- async function loadPty() {
138634
- if (pty) return pty;
138998
+ var pty2 = null;
138999
+ async function loadPty2() {
139000
+ if (pty2) return pty2;
138635
139001
  try {
138636
- pty = await import("node-pty");
138637
- return pty;
139002
+ pty2 = await import("node-pty");
139003
+ return pty2;
138638
139004
  } catch (err) {
138639
139005
  throw new Error(
138640
139006
  `node-pty is required for PTY management but failed to load. Ensure it is installed: npm install node-pty
@@ -138642,11 +139008,11 @@ Original error: ${err}`
138642
139008
  );
138643
139009
  }
138644
139010
  }
138645
- function createScreen() {
138646
- return new import_headless.Terminal({
138647
- cols: PTY_COLS,
138648
- rows: PTY_ROWS,
138649
- scrollback: SCREEN_SCROLLBACK,
139011
+ function createScreen2() {
139012
+ return new import_headless2.Terminal({
139013
+ cols: PTY_COLS2,
139014
+ rows: PTY_ROWS2,
139015
+ scrollback: SCREEN_SCROLLBACK2,
138650
139016
  allowProposedApi: true
138651
139017
  });
138652
139018
  }
@@ -138655,6 +139021,11 @@ function buildSpawnEnv() {
138655
139021
  if (env.CLAUDE_API_KEY) {
138656
139022
  env.ANTHROPIC_API_KEY = env.CLAUDE_API_KEY;
138657
139023
  }
139024
+ for (const key of Object.keys(env)) {
139025
+ if (key === "CLAUDECODE" || key.startsWith("CLAUDE_CODE_")) {
139026
+ delete env[key];
139027
+ }
139028
+ }
138658
139029
  return env;
138659
139030
  }
138660
139031
  var PTYManager = class {
@@ -138695,6 +139066,10 @@ var PTYManager = class {
138695
139066
  // to a given input or fell silent. Reset on dispose().
138696
139067
  chunkIndex = /* @__PURE__ */ new Map();
138697
139068
  lastChunkAt = /* @__PURE__ */ new Map();
139069
+ // In-flight start()/startFresh() calls keyed by sessionId. A second
139070
+ // concurrent resume for the same session (double-tap, client retry) awaits
139071
+ // the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
139072
+ startPromises = /* @__PURE__ */ new Map();
138698
139073
  constructor(options = {}) {
138699
139074
  this.onOutput = options.onOutput;
138700
139075
  this.onStatusChange = options.onStatusChange;
@@ -138717,7 +139092,18 @@ var PTYManager = class {
138717
139092
  // custom-API-key — are cleared by the seeded ~/.claude.json in
138718
139093
  // docker/entrypoint.sh.) startFresh() uses the same flag for the same reason.
138719
139094
  async start(sessionId, options) {
138720
- const nodePty = await loadPty();
139095
+ const existing = this.sessions.get(sessionId);
139096
+ if (existing) return toPublicSession2(existing);
139097
+ const inFlight = this.startPromises.get(sessionId);
139098
+ if (inFlight) return inFlight;
139099
+ const promise2 = this.doStart(sessionId, options).finally(() => {
139100
+ this.startPromises.delete(sessionId);
139101
+ });
139102
+ this.startPromises.set(sessionId, promise2);
139103
+ return promise2;
139104
+ }
139105
+ async doStart(sessionId, options) {
139106
+ const nodePty = await loadPty2();
138721
139107
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138722
139108
  const proc = nodePty.spawn(
138723
139109
  resolveClaudeExe(),
@@ -138739,6 +139125,7 @@ var PTYManager = class {
138739
139125
  );
138740
139126
  const session = {
138741
139127
  id: sessionId,
139128
+ provider: CLAUDE_CODE_PROVIDER2,
138742
139129
  projectPath: options.projectPath,
138743
139130
  projectName,
138744
139131
  branch: options.branch ?? "",
@@ -138749,7 +139136,7 @@ var PTYManager = class {
138749
139136
  lastOutput: "",
138750
139137
  process: proc,
138751
139138
  outputBuffer: Buffer.alloc(0),
138752
- screen: createScreen()
139139
+ screen: createScreen2()
138753
139140
  };
138754
139141
  this.sessions.set(sessionId, session);
138755
139142
  this.pendingReady.add(sessionId);
@@ -138760,13 +139147,13 @@ var PTYManager = class {
138760
139147
  this.pendingReady.delete(sessionId);
138761
139148
  this.handleExit(sessionId, exitCode);
138762
139149
  });
138763
- return toPublicSession(session);
139150
+ return toPublicSession2(session);
138764
139151
  }
138765
139152
  // Start a brand-new Claude session. A stable UUID is generated here and passed
138766
139153
  // to Claude via --session-id so the JSONL filename matches from the start.
138767
139154
  // onReady fires once Claude reaches its first prompt (waiting_input).
138768
139155
  async startFresh(options) {
138769
- const nodePty = await loadPty();
139156
+ const nodePty = await loadPty2();
138770
139157
  const sessionId = (0, import_crypto6.randomUUID)();
138771
139158
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138772
139159
  const args = [
@@ -138789,6 +139176,7 @@ var PTYManager = class {
138789
139176
  });
138790
139177
  const session = {
138791
139178
  id: sessionId,
139179
+ provider: CLAUDE_CODE_PROVIDER2,
138792
139180
  projectPath: options.projectPath,
138793
139181
  projectName,
138794
139182
  branch: "",
@@ -138799,7 +139187,7 @@ var PTYManager = class {
138799
139187
  lastOutput: "",
138800
139188
  process: proc,
138801
139189
  outputBuffer: Buffer.alloc(0),
138802
- screen: createScreen()
139190
+ screen: createScreen2()
138803
139191
  };
138804
139192
  this.sessions.set(sessionId, session);
138805
139193
  this.pendingReady.add(sessionId);
@@ -138810,7 +139198,7 @@ var PTYManager = class {
138810
139198
  this.pendingReady.delete(sessionId);
138811
139199
  this.handleExit(sessionId, exitCode);
138812
139200
  });
138813
- return toPublicSession(session);
139201
+ return toPublicSession2(session);
138814
139202
  }
138815
139203
  // Write raw key bytes directly to the PTY without bracketed-paste wrapping.
138816
139204
  // Use for control sequences (arrow keys, Enter) that must not be quoted.
@@ -138822,10 +139210,10 @@ var PTYManager = class {
138822
139210
  }
138823
139211
  if (session.status === "waiting_input") {
138824
139212
  session.status = "running";
138825
- this.onStatusChange?.(toPublicSession(session));
139213
+ this.onStatusChange?.(toPublicSession2(session));
138826
139214
  }
138827
139215
  this.log.info(
138828
- `[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes(keys)}`,
139216
+ `[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes2(keys)}`,
138829
139217
  { event: "pty.keys_write", sessionId, byteLen: keys.length }
138830
139218
  );
138831
139219
  session.process.write(keys);
@@ -138857,7 +139245,7 @@ var PTYManager = class {
138857
139245
  }
138858
139246
  if (session.status === "waiting_input") {
138859
139247
  session.status = "running";
138860
- this.onStatusChange?.(toPublicSession(session));
139248
+ this.onStatusChange?.(toPublicSession2(session));
138861
139249
  }
138862
139250
  this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
138863
139251
  session.lastActivityAt = /* @__PURE__ */ new Date();
@@ -138870,13 +139258,13 @@ var PTYManager = class {
138870
139258
  writeSubmit(sessionId, session, input, path2, promptCount) {
138871
139259
  const pasteBytes = buildPasteBytes(input);
138872
139260
  this.log.info(
138873
- `[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes(pasteBytes)}`,
139261
+ `[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes2(pasteBytes)}`,
138874
139262
  {
138875
139263
  event: "pty.input_write",
138876
139264
  sessionId,
138877
139265
  promptCount,
138878
139266
  byteLen: pasteBytes.length,
138879
- digest: digestBytes(pasteBytes),
139267
+ digest: digestBytes2(pasteBytes),
138880
139268
  path: path2,
138881
139269
  phase: "paste"
138882
139270
  }
@@ -138891,13 +139279,13 @@ var PTYManager = class {
138891
139279
  event: "pty.input_write",
138892
139280
  sessionId,
138893
139281
  promptCount,
138894
- byteLen: SUBMIT_BYTES.length,
139282
+ byteLen: SUBMIT_BYTES2.length,
138895
139283
  digest: "\\r",
138896
139284
  path: path2,
138897
139285
  phase: "submit"
138898
139286
  }
138899
139287
  );
138900
- current.process.write(SUBMIT_BYTES);
139288
+ current.process.write(SUBMIT_BYTES2);
138901
139289
  }, SUBMIT_DELAY_MS);
138902
139290
  }
138903
139291
  // Drain any inputs that were sent while the session was still pendingReady,
@@ -138955,7 +139343,7 @@ var PTYManager = class {
138955
139343
  session.completedAt = /* @__PURE__ */ new Date();
138956
139344
  session.screen.dispose();
138957
139345
  this.sessions.delete(sessionId);
138958
- this.onStatusChange?.(toPublicSession(session));
139346
+ this.onStatusChange?.(toPublicSession2(session));
138959
139347
  }
138960
139348
  getOutput(sessionId) {
138961
139349
  const session = this.sessions.get(sessionId);
@@ -138986,13 +139374,13 @@ var PTYManager = class {
138986
139374
  }
138987
139375
  getSession(sessionId) {
138988
139376
  const session = this.sessions.get(sessionId);
138989
- return session ? toPublicSession(session) : null;
139377
+ return session ? toPublicSession2(session) : null;
138990
139378
  }
138991
139379
  hasSession(sessionId) {
138992
139380
  return this.sessions.has(sessionId);
138993
139381
  }
138994
139382
  listSessions() {
138995
- return Array.from(this.sessions.values()).map(toPublicSession);
139383
+ return Array.from(this.sessions.values()).map(toPublicSession2);
138996
139384
  }
138997
139385
  dispose() {
138998
139386
  for (const session of this.sessions.values()) {
@@ -139024,7 +139412,7 @@ var PTYManager = class {
139024
139412
  this.lastChunkAt.set(sessionId, now);
139025
139413
  const gapMs = last == null ? 0 : now - last;
139026
139414
  this.log.info(
139027
- `[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes(data)}`,
139415
+ `[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes2(data)}`,
139028
139416
  {
139029
139417
  event: "pty.chunk",
139030
139418
  sessionId,
@@ -139033,17 +139421,17 @@ var PTYManager = class {
139033
139421
  gapMs,
139034
139422
  status: session.status,
139035
139423
  pendingReady: this.pendingReady.has(sessionId),
139036
- digest: digestBytes(data)
139424
+ digest: digestBytes2(data)
139037
139425
  }
139038
139426
  );
139039
139427
  session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
139040
- if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
139428
+ if (session.outputBuffer.length > OUTPUT_BUFFER_MAX2) {
139041
139429
  session.outputBuffer = session.outputBuffer.subarray(
139042
- session.outputBuffer.length - OUTPUT_BUFFER_MAX
139430
+ session.outputBuffer.length - OUTPUT_BUFFER_MAX2
139043
139431
  );
139044
139432
  }
139045
139433
  session.screen.write(data);
139046
- const stripped = stripAnsi(data);
139434
+ const stripped = stripAnsi2(data);
139047
139435
  session.lastOutput = stripped;
139048
139436
  const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m2) => stripped.includes(m2));
139049
139437
  if (session.status === "running" && matchedMarker) {
@@ -139146,11 +139534,11 @@ var PTYManager = class {
139146
139534
  reason,
139147
139535
  elapsedMs
139148
139536
  });
139149
- this.onStatusChange?.(toPublicSession(session));
139537
+ this.onStatusChange?.(toPublicSession2(session));
139150
139538
  if (this.pendingReady.has(sessionId)) {
139151
139539
  this.pendingReady.delete(sessionId);
139152
139540
  this.flushQueuedInputs(sessionId);
139153
- this.onReady?.(toPublicSession(session));
139541
+ this.onReady?.(toPublicSession2(session));
139154
139542
  }
139155
139543
  }
139156
139544
  handleExit(sessionId, exitCode) {
@@ -139160,13 +139548,13 @@ var PTYManager = class {
139160
139548
  session.status = "idle";
139161
139549
  const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
139162
139550
  if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
139163
- if (!(0, import_fs20.existsSync)(session.projectPath)) {
139551
+ if (!(0, import_fs21.existsSync)(session.projectPath)) {
139164
139552
  session.failureReason = `Project directory not found: ${session.projectPath}`;
139165
139553
  } else {
139166
139554
  session.failureReason = `Process exited immediately (code ${exitCode}). Check that the Claude binary is installed and accessible.`;
139167
139555
  }
139168
139556
  }
139169
- this.onStatusChange?.(toPublicSession(session));
139557
+ this.onStatusChange?.(toPublicSession2(session));
139170
139558
  session.screen.dispose();
139171
139559
  this.sessions.delete(sessionId);
139172
139560
  this.queuedInputs.delete(sessionId);
@@ -139176,9 +139564,10 @@ var PTYManager = class {
139176
139564
  this.shellPromptOpen.delete(sessionId);
139177
139565
  }
139178
139566
  };
139179
- function toPublicSession(s3) {
139567
+ function toPublicSession2(s3) {
139180
139568
  return {
139181
139569
  id: s3.id,
139570
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
139182
139571
  projectPath: s3.projectPath,
139183
139572
  projectName: s3.projectName,
139184
139573
  branch: s3.branch,
@@ -139192,10 +139581,306 @@ function toPublicSession(s3) {
139192
139581
  ...s3.filePath != null && { filePath: s3.filePath }
139193
139582
  };
139194
139583
  }
139195
- function stripAnsi(str) {
139584
+ function stripAnsi2(str) {
139196
139585
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
139197
139586
  }
139198
139587
 
139588
+ // src/live-session-manager.ts
139589
+ var LiveSessionManager = class {
139590
+ runners;
139591
+ constructor(options = {}) {
139592
+ this.runners = /* @__PURE__ */ new Map([
139593
+ [CLAUDE_CODE_PROVIDER2, new PTYManager(options)],
139594
+ [CODEX_CLI_PROVIDER2, new CodexPtyRunner(options)]
139595
+ ]);
139596
+ }
139597
+ async start(sessionId, options) {
139598
+ const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
139599
+ const runner = this.assertSupportedProvider(provider, options.projectPath);
139600
+ return runner.start(sessionId, options);
139601
+ }
139602
+ async startFresh(options) {
139603
+ const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
139604
+ const runner = this.assertSupportedProvider(provider, options.projectPath);
139605
+ return runner.startFresh(options);
139606
+ }
139607
+ sendInput(sessionId, input) {
139608
+ return this.runnerFor(sessionId).sendInput(sessionId, input);
139609
+ }
139610
+ sendKeys(sessionId, keys) {
139611
+ this.runnerFor(sessionId).sendKeys(sessionId, keys);
139612
+ }
139613
+ cancel(sessionId) {
139614
+ this.runnerFor(sessionId).cancel(sessionId);
139615
+ }
139616
+ killPid(pid) {
139617
+ for (const runner of this.runners.values()) {
139618
+ runner.killPid(pid);
139619
+ }
139620
+ }
139621
+ // putOnHold tolerates an unknown sessionId (PTYManager.putOnHold is a no-op
139622
+ // when the session isn't in its map), so — unlike the other session-keyed
139623
+ // methods — route to the owning runner when found, otherwise broadcast to
139624
+ // every runner rather than throwing; this matches the pre-extraction
139625
+ // behavior of delegating straight through with no existence check.
139626
+ putOnHold(sessionId) {
139627
+ for (const runner of this.runners.values()) {
139628
+ if (runner.hasSession(sessionId) || runner.getSession(sessionId)) {
139629
+ runner.putOnHold(sessionId);
139630
+ return;
139631
+ }
139632
+ }
139633
+ for (const runner of this.runners.values()) {
139634
+ runner.putOnHold(sessionId);
139635
+ }
139636
+ }
139637
+ getOutput(sessionId) {
139638
+ return this.runnerFor(sessionId).getOutput(sessionId);
139639
+ }
139640
+ getOutputLines(sessionId, maxLines) {
139641
+ return this.runnerFor(sessionId).getOutputLines(sessionId, maxLines);
139642
+ }
139643
+ getSession(sessionId) {
139644
+ for (const runner of this.runners.values()) {
139645
+ const session = runner.getSession(sessionId);
139646
+ if (session) return session;
139647
+ }
139648
+ return null;
139649
+ }
139650
+ hasSession(sessionId) {
139651
+ for (const runner of this.runners.values()) {
139652
+ if (runner.hasSession(sessionId)) return true;
139653
+ }
139654
+ return false;
139655
+ }
139656
+ listSessions() {
139657
+ return Array.from(this.runners.values()).flatMap((runner) => runner.listSessions());
139658
+ }
139659
+ dispose() {
139660
+ for (const runner of this.runners.values()) {
139661
+ runner.dispose();
139662
+ }
139663
+ }
139664
+ // Look up which runner owns a session. Only one runner exists today, so
139665
+ // this is a linear scan across hasSession()/getSession() rather than a
139666
+ // separate session→provider index — see task-1-brief.md.
139667
+ runnerFor(sessionId) {
139668
+ for (const runner of this.runners.values()) {
139669
+ if (runner.hasSession(sessionId) || runner.getSession(sessionId)) return runner;
139670
+ }
139671
+ throw new Error(`Session not found: ${sessionId}`);
139672
+ }
139673
+ assertSupportedProvider(provider, projectPath) {
139674
+ const runner = this.runners.get(provider);
139675
+ if (runner) return runner;
139676
+ const err = new Error(
139677
+ `Live ${provider} sessions are not implemented yet for ${(0, import_path18.basename)(projectPath)}`
139678
+ );
139679
+ err.statusCode = 501;
139680
+ throw err;
139681
+ }
139682
+ };
139683
+
139684
+ // src/server.ts
139685
+ init_logger();
139686
+
139687
+ // src/pair-store.ts
139688
+ var import_crypto7 = require("crypto");
139689
+ var DEFAULT_TTL_SECONDS = 180;
139690
+ var SWEEP_INTERVAL_MS = 6e4;
139691
+ var PairTokenStore = class {
139692
+ current = null;
139693
+ ttlMs;
139694
+ sweepTimer = null;
139695
+ constructor(opts = {}) {
139696
+ this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
139697
+ if (opts.autoSweep !== false) {
139698
+ this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
139699
+ this.sweepTimer.unref?.();
139700
+ }
139701
+ }
139702
+ mint() {
139703
+ const token = `pt_${(0, import_crypto7.randomBytes)(16).toString("hex")}`;
139704
+ const expiresAt = Date.now() + this.ttlMs;
139705
+ this.current = { token, expiresAt, used: false };
139706
+ return {
139707
+ token,
139708
+ expiresAt,
139709
+ expiresInSeconds: Math.floor(this.ttlMs / 1e3)
139710
+ };
139711
+ }
139712
+ consume(token) {
139713
+ const record2 = this.current;
139714
+ if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
139715
+ if (Date.now() > record2.expiresAt) {
139716
+ this.current = null;
139717
+ return { ok: false, reason: "expired" };
139718
+ }
139719
+ if (record2.used) return { ok: false, reason: "used" };
139720
+ record2.used = true;
139721
+ return { ok: true };
139722
+ }
139723
+ peek() {
139724
+ return this.current;
139725
+ }
139726
+ clear() {
139727
+ this.current = null;
139728
+ }
139729
+ sweep() {
139730
+ if (this.current && Date.now() > this.current.expiresAt) {
139731
+ this.current = null;
139732
+ }
139733
+ }
139734
+ dispose() {
139735
+ if (this.sweepTimer) clearInterval(this.sweepTimer);
139736
+ this.sweepTimer = null;
139737
+ this.current = null;
139738
+ }
139739
+ };
139740
+
139741
+ // src/process-discovery.ts
139742
+ var import_child_process2 = require("child_process");
139743
+ var import_os9 = require("os");
139744
+ var import_path19 = require("path");
139745
+ async function discoverClaudeProcesses() {
139746
+ if ((0, import_os9.platform)() === "win32") return discoverWindows();
139747
+ return discoverUnix();
139748
+ }
139749
+ async function discoverUnix() {
139750
+ const pids = await getPidsUnix();
139751
+ const results = await Promise.all(
139752
+ pids.map(async (pid) => {
139753
+ try {
139754
+ const [cwd, args, startedAt] = await Promise.all([
139755
+ getProcessCwdUnix(pid),
139756
+ getProcessArgsUnix(pid),
139757
+ getProcessStartTimeUnix(pid)
139758
+ ]);
139759
+ const conversationId = extractResumeId(args);
139760
+ return {
139761
+ pid,
139762
+ projectPath: cwd,
139763
+ projectName: (0, import_path19.basename)(cwd),
139764
+ branch: await readGitBranch2(cwd),
139765
+ conversationId,
139766
+ startedAt
139767
+ };
139768
+ } catch {
139769
+ return null;
139770
+ }
139771
+ })
139772
+ );
139773
+ return results.filter((r) => r !== null);
139774
+ }
139775
+ async function discoverWindows() {
139776
+ const pids = await getPidsWindows();
139777
+ const results = await Promise.all(
139778
+ pids.map(async (pid) => {
139779
+ try {
139780
+ const info = await getProcessInfoWindows(pid);
139781
+ if (!info) return null;
139782
+ return {
139783
+ pid,
139784
+ projectPath: info.cwd,
139785
+ projectName: (0, import_path19.basename)(info.cwd),
139786
+ branch: await readGitBranch2(info.cwd),
139787
+ conversationId: extractResumeId(info.args),
139788
+ startedAt: info.startedAt
139789
+ };
139790
+ } catch {
139791
+ return null;
139792
+ }
139793
+ })
139794
+ );
139795
+ return results.filter((r) => r !== null);
139796
+ }
139797
+ function run(cmd, args, opts = {}) {
139798
+ return new Promise((resolve6, reject) => {
139799
+ (0, import_child_process2.execFile)(
139800
+ cmd,
139801
+ args,
139802
+ { windowsHide: isWindows2, encoding: "utf-8", timeout: opts.timeout ?? 5e3, cwd: opts.cwd },
139803
+ (err, stdout2) => {
139804
+ if (err) reject(err);
139805
+ else resolve6(stdout2);
139806
+ }
139807
+ );
139808
+ });
139809
+ }
139810
+ async function getPidsUnix() {
139811
+ try {
139812
+ const output = await run("pgrep", ["-x", "claude"]);
139813
+ return output.trim().split("\n").filter(Boolean).map((s3) => Number.parseInt(s3, 10));
139814
+ } catch {
139815
+ return [];
139816
+ }
139817
+ }
139818
+ async function getProcessCwdUnix(pid) {
139819
+ const output = await run("lsof", ["-p", String(pid), "-a", "-d", "cwd", "-Fn"]);
139820
+ const match2 = output.match(/n(.+)/);
139821
+ return match2?.[1] ?? "";
139822
+ }
139823
+ async function getProcessArgsUnix(pid) {
139824
+ return (await run("ps", ["-p", String(pid), "-o", "args="])).trim();
139825
+ }
139826
+ async function getProcessStartTimeUnix(pid) {
139827
+ const raw2 = (await run("ps", ["-p", String(pid), "-o", "lstart="])).trim();
139828
+ const d = new Date(raw2);
139829
+ return Number.isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d;
139830
+ }
139831
+ async function getPidsWindows() {
139832
+ try {
139833
+ const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
139834
+ return output.trim().split("\n").filter(Boolean).map((line) => {
139835
+ const parts = line.split(",");
139836
+ return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
139837
+ }).filter((pid) => pid > 0);
139838
+ } catch {
139839
+ return [];
139840
+ }
139841
+ }
139842
+ async function getProcessInfoWindows(pid) {
139843
+ try {
139844
+ const output = await run("wmic", [
139845
+ "process",
139846
+ "where",
139847
+ `ProcessId=${pid}`,
139848
+ "get",
139849
+ "CommandLine,CreationDate,ExecutablePath",
139850
+ "/FORMAT:CSV"
139851
+ ]);
139852
+ const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
139853
+ if (lines.length < 2) return null;
139854
+ const parts = lines[1].split(",");
139855
+ const args = parts[1] ?? "";
139856
+ const creationDate = parts[2] ?? "";
139857
+ const year = creationDate.slice(0, 4);
139858
+ const month = creationDate.slice(4, 6);
139859
+ const day = creationDate.slice(6, 8);
139860
+ const hour = creationDate.slice(8, 10);
139861
+ const min = creationDate.slice(10, 12);
139862
+ const sec = creationDate.slice(12, 14);
139863
+ const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
139864
+ if (Number.isNaN(startedAt.getTime())) return null;
139865
+ const exePath = parts[3] ?? "";
139866
+ const cwd = exePath ? (0, import_path19.dirname)(exePath) : "";
139867
+ return { cwd, args, startedAt };
139868
+ } catch {
139869
+ return null;
139870
+ }
139871
+ }
139872
+ function extractResumeId(args) {
139873
+ const match2 = args.match(/--resume\s+(\S+)/);
139874
+ return match2?.[1] ?? null;
139875
+ }
139876
+ async function readGitBranch2(dir) {
139877
+ try {
139878
+ return (await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 3e3 })).trim();
139879
+ } catch {
139880
+ return "";
139881
+ }
139882
+ }
139883
+
139199
139884
  // src/seal.ts
139200
139885
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
139201
139886
  var import_tweetnacl_util = __toESM(require_nacl_util(), 1);
@@ -139384,10 +140069,10 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139384
140069
  }
139385
140070
  async _formatEntry(dirent, path2) {
139386
140071
  let entry;
139387
- const basename9 = this._isDirent ? dirent.name : dirent;
140072
+ const basename11 = this._isDirent ? dirent.name : dirent;
139388
140073
  try {
139389
- const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename9));
139390
- entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename9 };
140074
+ const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename11));
140075
+ entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename11 };
139391
140076
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
139392
140077
  } catch (err) {
139393
140078
  this._onError(err);
@@ -139928,9 +140613,9 @@ var NodeFsHandler2 = class {
139928
140613
  _watchWithNodeFs(path2, listener) {
139929
140614
  const opts = this.fsw.options;
139930
140615
  const directory = sp.dirname(path2);
139931
- const basename9 = sp.basename(path2);
140616
+ const basename11 = sp.basename(path2);
139932
140617
  const parent = this.fsw._getWatchedDir(directory);
139933
- parent.add(basename9);
140618
+ parent.add(basename11);
139934
140619
  const absolutePath = sp.resolve(path2);
139935
140620
  const options = {
139936
140621
  persistent: opts.persistent
@@ -139940,7 +140625,7 @@ var NodeFsHandler2 = class {
139940
140625
  let closer;
139941
140626
  if (opts.usePolling) {
139942
140627
  const enableBin = opts.interval !== opts.binaryInterval;
139943
- options.interval = enableBin && isBinaryPath2(basename9) ? opts.binaryInterval : opts.interval;
140628
+ options.interval = enableBin && isBinaryPath2(basename11) ? opts.binaryInterval : opts.interval;
139944
140629
  closer = setFsWatchFileListener2(path2, absolutePath, options, {
139945
140630
  listener,
139946
140631
  rawEmitter: this.fsw._emitRaw
@@ -139963,10 +140648,10 @@ var NodeFsHandler2 = class {
139963
140648
  return;
139964
140649
  }
139965
140650
  const dirname17 = sp.dirname(file2);
139966
- const basename9 = sp.basename(file2);
140651
+ const basename11 = sp.basename(file2);
139967
140652
  const parent = this.fsw._getWatchedDir(dirname17);
139968
140653
  let prevStats = stats;
139969
- if (parent.has(basename9))
140654
+ if (parent.has(basename11))
139970
140655
  return;
139971
140656
  const listener = async (path2, newStats) => {
139972
140657
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH2, file2, 5))
@@ -139991,9 +140676,9 @@ var NodeFsHandler2 = class {
139991
140676
  prevStats = newStats2;
139992
140677
  }
139993
140678
  } catch (error51) {
139994
- this.fsw._remove(dirname17, basename9);
140679
+ this.fsw._remove(dirname17, basename11);
139995
140680
  }
139996
- } else if (parent.has(basename9)) {
140681
+ } else if (parent.has(basename11)) {
139997
140682
  const at2 = newStats.atimeMs;
139998
140683
  const mt2 = newStats.mtimeMs;
139999
140684
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -140950,7 +141635,7 @@ function watch2(paths, options = {}) {
140950
141635
  var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
140951
141636
 
140952
141637
  // src/services/conversations/conversationWatcher.ts
140953
- var import_fs21 = require("fs");
141638
+ var import_fs22 = require("fs");
140954
141639
  var import_promises12 = require("fs/promises");
140955
141640
  var ConversationWatcher = class {
140956
141641
  files = /* @__PURE__ */ new Map();
@@ -140971,7 +141656,7 @@ var ConversationWatcher = class {
140971
141656
  if (this.files.has(filePath)) return;
140972
141657
  let offset;
140973
141658
  try {
140974
- offset = (0, import_fs21.statSync)(filePath).size;
141659
+ offset = (0, import_fs22.statSync)(filePath).size;
140975
141660
  } catch {
140976
141661
  offset = 0;
140977
141662
  }
@@ -141080,14 +141765,14 @@ var ConversationWatcher = class {
141080
141765
  };
141081
141766
 
141082
141767
  // src/services/conversations/pruneAgentConversations.ts
141083
- var import_fs22 = require("fs");
141768
+ var import_fs23 = require("fs");
141084
141769
  function pruneAgentConversations(cache) {
141085
141770
  const db = cache.getDatabase();
141086
141771
  const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
141087
141772
  let pruned = 0;
141088
141773
  let missing = 0;
141089
141774
  for (const row of rows) {
141090
- if (!(0, import_fs22.existsSync)(row.file_path)) {
141775
+ if (!(0, import_fs23.existsSync)(row.file_path)) {
141091
141776
  missing += 1;
141092
141777
  continue;
141093
141778
  }
@@ -141396,6 +142081,7 @@ function managedToResponse(s3, ptyAttached) {
141396
142081
  return {
141397
142082
  id: s3.id,
141398
142083
  conversationId: s3.id,
142084
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
141399
142085
  status: s3.status,
141400
142086
  projectPath: s3.projectPath,
141401
142087
  projectName: s3.projectName,
@@ -141421,13 +142107,15 @@ function managedToResponse(s3, ptyAttached) {
141421
142107
  ...s3.failureReason != null && { failureReason: s3.failureReason },
141422
142108
  ...s3.resumedFromConversationId != null && {
141423
142109
  resumedFromConversationId: s3.resumedFromConversationId
141424
- }
142110
+ },
142111
+ ...s3.boundConversationId != null && { boundConversationId: s3.boundConversationId }
141425
142112
  };
141426
142113
  }
141427
142114
  function discoveredToResponse(d, conversationId) {
141428
142115
  return {
141429
142116
  id: conversationId,
141430
142117
  conversationId,
142118
+ provider: CLAUDE_CODE_PROVIDER2,
141431
142119
  status: "idle",
141432
142120
  projectPath: d.projectPath,
141433
142121
  projectName: d.projectName,
@@ -141443,10 +142131,10 @@ function discoveredToResponse(d, conversationId) {
141443
142131
  }
141444
142132
 
141445
142133
  // src/uploads.ts
141446
- var import_crypto7 = require("crypto");
142134
+ var import_crypto8 = require("crypto");
141447
142135
  var import_promises13 = require("fs/promises");
141448
142136
  var import_heic_convert = __toESM(require_heic_convert(), 1);
141449
- var import_path18 = require("path");
142137
+ var import_path20 = require("path");
141450
142138
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
141451
142139
  var MAX_BYTES = 25 * 1024 * 1024;
141452
142140
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -141477,11 +142165,11 @@ async function saveUploadFile(input) {
141477
142165
  mimeType = "image/jpeg";
141478
142166
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
141479
142167
  }
141480
- const id = `up_${(0, import_crypto7.randomBytes)(8).toString("hex")}`;
142168
+ const id = `up_${(0, import_crypto8.randomBytes)(8).toString("hex")}`;
141481
142169
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
141482
- const dir = (0, import_path18.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
142170
+ const dir = (0, import_path20.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
141483
142171
  await (0, import_promises13.mkdir)(dir, { recursive: true });
141484
- const filePath = (0, import_path18.join)(dir, `${Date.now()}-${id}-${safeName}`);
142172
+ const filePath = (0, import_path20.join)(dir, `${Date.now()}-${id}-${safeName}`);
141485
142173
  await (0, import_promises13.writeFile)(filePath, buffer);
141486
142174
  return {
141487
142175
  id,
@@ -141994,10 +142682,10 @@ var StreamerServer = class {
141994
142682
  this.verbose = config2.verbose ?? false;
141995
142683
  this.disableDb = config2.disableDb ?? false;
141996
142684
  this.scanProfiles = config2.scanProfiles;
141997
- this.codexRoots = config2.codexRoots ?? [(0, import_path19.join)((0, import_os10.homedir)(), ".codex", "sessions")];
142685
+ this.codexRoots = config2.codexRoots ?? [(0, import_path21.join)((0, import_os10.homedir)(), ".codex", "sessions")];
141998
142686
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
141999
142687
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
142000
- this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path19.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142688
+ this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path21.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142001
142689
  this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
142002
142690
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
142003
142691
  this.markScannerStaleDebounced = debounce(() => {
@@ -142081,7 +142769,7 @@ var StreamerServer = class {
142081
142769
  });
142082
142770
  }
142083
142771
  });
142084
- this.ptyManager = new PTYManager({
142772
+ this.ptyManager = new LiveSessionManager({
142085
142773
  logger: getLogger("pty"),
142086
142774
  onOutput: (sessionId, data) => {
142087
142775
  this.wsHub.broadcast({ type: "terminal_output", sessionId, data });
@@ -142162,7 +142850,7 @@ var StreamerServer = class {
142162
142850
  temporalClient,
142163
142851
  taskQueue: agentConfig.temporal.taskQueue
142164
142852
  });
142165
- const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path19.join)((0, import_path19.dirname)(this.cacheDir), "conversations");
142853
+ const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations");
142166
142854
  conversationWriter = createConversationWriter({
142167
142855
  baseDir: conversationsBaseDir
142168
142856
  });
@@ -142384,7 +143072,7 @@ var StreamerServer = class {
142384
143072
  });
142385
143073
  try {
142386
143074
  this.cache = ConversationCache.open(
142387
- (0, import_path19.join)(this.cacheDir, "cache.db"),
143075
+ (0, import_path21.join)(this.cacheDir, "cache.db"),
142388
143076
  this.tailSize,
142389
143077
  void 0,
142390
143078
  {
@@ -142410,18 +143098,19 @@ var StreamerServer = class {
142410
143098
  if (this.scanProfiles && this.scanProfiles.length > 0) {
142411
143099
  for (const profile of this.scanProfiles) {
142412
143100
  if (profile.enabled) {
142413
- this.fileWatcher.watchDirectory((0, import_path19.join)(profile.configDir, "projects"));
143101
+ this.fileWatcher.watchDirectory((0, import_path21.join)(profile.configDir, "projects"));
142414
143102
  }
142415
143103
  }
142416
143104
  } else {
142417
- this.fileWatcher.watchDirectory((0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects"));
143105
+ this.fileWatcher.watchDirectory((0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects"));
142418
143106
  }
142419
143107
  } catch (err) {
142420
143108
  const message = err instanceof Error ? err.message : String(err);
142421
- this.log.warn(`ConversationCache failed to open (running without cache): ${message}`, {
142422
- error: message,
142423
- event: "cache.open_failed"
142424
- });
143109
+ const abiMismatch = message.includes("NODE_MODULE_VERSION") || message.includes("was compiled against a different Node.js version");
143110
+ this.log.error(
143111
+ `ConversationCache failed to open \u2014 running WITHOUT cache; /api/conversations, /api/conversations/count and /project-chats will 500.` + (abiMismatch ? ` Fix: npm rebuild better-sqlite3` : "") + ` (${message})`,
143112
+ { error: message, abiMismatch, event: "cache.open_failed" }
143113
+ );
142425
143114
  }
142426
143115
  const warmupScanner = new ConversationScanner();
142427
143116
  this.allScanners.add(warmupScanner);
@@ -142908,17 +143597,17 @@ var StreamerServer = class {
142908
143597
  return this.getScanner();
142909
143598
  }
142910
143599
  findJsonlPath(uuid3) {
142911
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects");
142912
- if (!(0, import_fs23.existsSync)(projectsDir)) return null;
143600
+ const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects");
143601
+ if (!(0, import_fs24.existsSync)(projectsDir)) return null;
142913
143602
  const filename = `${uuid3}.jsonl`;
142914
- for (const dir of (0, import_fs23.readdirSync)(projectsDir)) {
142915
- const fp = (0, import_path19.join)(projectsDir, dir, filename);
142916
- if ((0, import_fs23.existsSync)(fp)) return fp;
142917
- const projectDir = (0, import_path19.join)(projectsDir, dir);
143603
+ for (const dir of (0, import_fs24.readdirSync)(projectsDir)) {
143604
+ const fp = (0, import_path21.join)(projectsDir, dir, filename);
143605
+ if ((0, import_fs24.existsSync)(fp)) return fp;
143606
+ const projectDir = (0, import_path21.join)(projectsDir, dir);
142918
143607
  try {
142919
- for (const sub of (0, import_fs23.readdirSync)(projectDir)) {
142920
- const subagentPath = (0, import_path19.join)(projectDir, sub, "subagents", filename);
142921
- if ((0, import_fs23.existsSync)(subagentPath)) return subagentPath;
143608
+ for (const sub of (0, import_fs24.readdirSync)(projectDir)) {
143609
+ const subagentPath = (0, import_path21.join)(projectDir, sub, "subagents", filename);
143610
+ if ((0, import_fs24.existsSync)(subagentPath)) return subagentPath;
142922
143611
  }
142923
143612
  } catch {
142924
143613
  }
@@ -142927,7 +143616,7 @@ var StreamerServer = class {
142927
143616
  }
142928
143617
  async readCwdFromJsonl(filePath) {
142929
143618
  return new Promise((resolve6) => {
142930
- const rl = (0, import_readline4.createInterface)({ input: (0, import_fs23.createReadStream)(filePath), crlfDelay: Infinity });
143619
+ const rl = (0, import_readline4.createInterface)({ input: (0, import_fs24.createReadStream)(filePath), crlfDelay: Infinity });
142931
143620
  let found = false;
142932
143621
  rl.on("line", (line) => {
142933
143622
  if (found) return;
@@ -142984,7 +143673,7 @@ var StreamerServer = class {
142984
143673
  if (!conv.filePath) return false;
142985
143674
  let mtimeMs = null;
142986
143675
  try {
142987
- mtimeMs = (0, import_fs23.statSync)(conv.filePath).mtimeMs;
143676
+ mtimeMs = (0, import_fs24.statSync)(conv.filePath).mtimeMs;
142988
143677
  } catch {
142989
143678
  return false;
142990
143679
  }
@@ -143227,7 +143916,7 @@ var StreamerServer = class {
143227
143916
  handleGetSession(sessionId, res) {
143228
143917
  const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
143229
143918
  if (session) {
143230
- if (!(0, import_fs23.existsSync)(session.projectPath)) {
143919
+ if (!(0, import_fs24.existsSync)(session.projectPath)) {
143231
143920
  session.failureReason = `Project directory not found: ${session.projectPath}`;
143232
143921
  }
143233
143922
  json2(res, 200, session);
@@ -143267,7 +143956,10 @@ var StreamerServer = class {
143267
143956
  json2(res, 400, { error: "Could not determine project path" });
143268
143957
  return;
143269
143958
  }
143959
+ const cachedConvMeta = this.cache?.getMetaById(sessionId);
143960
+ const provider = conv?.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER2;
143270
143961
  const session = await this.ptyManager.start(sessionId, {
143962
+ provider,
143271
143963
  projectPath,
143272
143964
  projectName: body.projectName,
143273
143965
  branch: body.branch
@@ -143617,7 +144309,7 @@ var StreamerServer = class {
143617
144309
  sessionStore: this.sessionStore,
143618
144310
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
143619
144311
  agentClient: this.agentClient,
143620
- conversationsDir: this.cacheDir ? (0, import_path19.join)((0, import_path19.dirname)(this.cacheDir), "conversations") : "",
144312
+ conversationsDir: this.cacheDir ? (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations") : "",
143621
144313
  agentConfig: this.agentConfig
143622
144314
  });
143623
144315
  json2(res, result.status, result.body);
@@ -143626,6 +144318,13 @@ var StreamerServer = class {
143626
144318
  }
143627
144319
  return;
143628
144320
  }
144321
+ const body = await readBody(req);
144322
+ const { path: relativePath, provider: requestedProvider, systemPrompt: clientPrompt } = body;
144323
+ if (requestedProvider !== void 0 && !isProviderName(requestedProvider)) {
144324
+ json2(res, 400, { error: "Invalid provider" });
144325
+ return;
144326
+ }
144327
+ const provider = requestedProvider ?? CLAUDE_CODE_PROVIDER2;
143629
144328
  if (!this.browseRoot) {
143630
144329
  json2(res, 403, {
143631
144330
  error: "File browsing not configured. Set browseRoot on the server.",
@@ -143633,8 +144332,6 @@ var StreamerServer = class {
143633
144332
  });
143634
144333
  return;
143635
144334
  }
143636
- const body = await readBody(req);
143637
- const { path: relativePath, systemPrompt: clientPrompt } = body;
143638
144335
  if (typeof relativePath !== "string") {
143639
144336
  json2(res, 400, { error: "Missing path field" });
143640
144337
  return;
@@ -143655,21 +144352,27 @@ var StreamerServer = class {
143655
144352
  ].filter(Boolean);
143656
144353
  try {
143657
144354
  const session = await this.ptyManager.startFresh({
144355
+ provider,
143658
144356
  projectPath: resolvedPath,
143659
144357
  projectName: body.projectName,
143660
144358
  systemPrompt: systemPromptParts.join("\n")
143661
144359
  });
143662
144360
  this.sessionStore.addManaged(session);
143663
144361
  json2(res, 202, { id: session.id, status: "pending" });
143664
- this.watchForJsonl(session.id, resolvedPath);
144362
+ if (provider === CODEX_CLI_PROVIDER2) {
144363
+ this.watchForCodexRollout(session.id, resolvedPath);
144364
+ } else {
144365
+ this.watchForJsonl(session.id, resolvedPath);
144366
+ }
143665
144367
  this.broadcastOrUnicastSessionList(req);
143666
144368
  } catch (err) {
143667
144369
  const message = err instanceof Error ? err.message : "Failed to start session";
144370
+ const statusCode = typeof err.statusCode === "number" ? err.statusCode : 500;
143668
144371
  this.log.error(`[start] failed to start session: ${message}`, {
143669
144372
  event: "session.start_failed",
143670
144373
  error: message
143671
144374
  });
143672
- json2(res, 500, { error: message });
144375
+ json2(res, statusCode, { error: message });
143673
144376
  }
143674
144377
  }
143675
144378
  // ─── Project linking ─────────────────────────────────────────────
@@ -143722,9 +144425,9 @@ var StreamerServer = class {
143722
144425
  // was passed to Claude via --session-id so the filename matches from the start.
143723
144426
  watchForJsonl(sessionId, projectPath) {
143724
144427
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
143725
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
144428
+ const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
143726
144429
  const expectedFile = `${sessionId}.jsonl`;
143727
- const filePath = (0, import_path19.join)(projectsDir, expectedFile);
144430
+ const filePath = (0, import_path21.join)(projectsDir, expectedFile);
143728
144431
  const deadline = Date.now() + 12e4;
143729
144432
  let watcher = null;
143730
144433
  const cleanup = () => {
@@ -143742,12 +144445,12 @@ var StreamerServer = class {
143742
144445
  cleanup();
143743
144446
  return;
143744
144447
  }
143745
- let resolvedFilePath = (0, import_fs23.existsSync)(filePath) ? filePath : null;
143746
- if (!resolvedFilePath && (0, import_fs23.existsSync)(projectsDir)) {
144448
+ let resolvedFilePath = (0, import_fs24.existsSync)(filePath) ? filePath : null;
144449
+ if (!resolvedFilePath && (0, import_fs24.existsSync)(projectsDir)) {
143747
144450
  try {
143748
144451
  const now = Date.now();
143749
- const recent = (0, import_fs23.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs23.statSync)((0, import_path19.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).sort((a, b2) => b2.mtime - a.mtime)[0];
143750
- if (recent) resolvedFilePath = (0, import_path19.join)(projectsDir, recent.f);
144452
+ const recent = (0, import_fs24.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path21.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).sort((a, b2) => b2.mtime - a.mtime)[0];
144453
+ if (recent) resolvedFilePath = (0, import_path21.join)(projectsDir, recent.f);
143751
144454
  } catch {
143752
144455
  }
143753
144456
  }
@@ -143756,7 +144459,7 @@ var StreamerServer = class {
143756
144459
  this.sessionFileMap.set(sessionId, resolvedFilePath);
143757
144460
  this.fileWatcher.watch(resolvedFilePath);
143758
144461
  try {
143759
- const existing = (0, import_fs23.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
144462
+ const existing = (0, import_fs24.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
143760
144463
  if (existing.length > 0) {
143761
144464
  this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
143762
144465
  for (const line of existing) {
@@ -143782,11 +144485,126 @@ var StreamerServer = class {
143782
144485
  if (this.sessionFileMap.has(sessionId)) return;
143783
144486
  try {
143784
144487
  require("fs").mkdirSync(projectsDir, { recursive: true });
143785
- watcher = (0, import_fs23.watch)(projectsDir, tryWire);
144488
+ watcher = (0, import_fs24.watch)(projectsDir, tryWire);
143786
144489
  watcher.on("error", cleanup);
143787
144490
  } catch {
143788
144491
  }
143789
144492
  }
144493
+ // Codex-equivalent of watchForJsonl(). Differs because Codex has no
144494
+ // filename-encoded session id (it assigns its own persisted id) and its
144495
+ // rollout files live under a date-nested directory
144496
+ // (~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl) that Codex creates
144497
+ // itself — it may not exist yet when this function is first called, so we
144498
+ // poll rather than fs.watch a not-yet-existent directory. Per Phase 0
144499
+ // findings, the rollout file appears within ~1s of process spawn (after
144500
+ // any directory-trust gate is cleared), well before any user input.
144501
+ watchForCodexRollout(sessionId, projectPath) {
144502
+ const deadline = Date.now() + 12e4;
144503
+ const now = /* @__PURE__ */ new Date();
144504
+ const dateDir = (0, import_path21.join)(
144505
+ String(now.getFullYear()),
144506
+ String(now.getMonth() + 1).padStart(2, "0"),
144507
+ String(now.getDate()).padStart(2, "0")
144508
+ );
144509
+ const sessionStartedAtMs = (this.sessionStore.getManaged(sessionId)?.startedAt?.getTime() ?? Date.now()) - 5e3;
144510
+ let intervalHandle = null;
144511
+ const cleanup = () => {
144512
+ if (intervalHandle) clearInterval(intervalHandle);
144513
+ intervalHandle = null;
144514
+ };
144515
+ const matchesProjectPath = (candidatePath) => {
144516
+ try {
144517
+ const firstLine = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
144518
+ if (!firstLine) return null;
144519
+ const parsed = JSON.parse(firstLine);
144520
+ if (parsed?.type !== "session_meta") return null;
144521
+ const payload = parsed.payload ?? {};
144522
+ if (payload.cwd !== projectPath) return null;
144523
+ if (typeof payload.id !== "string") return null;
144524
+ const createdIso = payload.timestamp ?? parsed.timestamp;
144525
+ const createdAtMs = typeof createdIso === "string" ? Date.parse(createdIso) : Number.NaN;
144526
+ if (Number.isNaN(createdAtMs) || createdAtMs < sessionStartedAtMs) return null;
144527
+ return { id: payload.id, createdAtMs };
144528
+ } catch {
144529
+ return null;
144530
+ }
144531
+ };
144532
+ const tryWire = () => {
144533
+ if (!this.ptyManager.hasSession(sessionId)) {
144534
+ cleanup();
144535
+ return;
144536
+ }
144537
+ if (Date.now() > deadline) {
144538
+ cleanup();
144539
+ return;
144540
+ }
144541
+ const boundElsewhere = new Set(
144542
+ this.sessionStore.listManaged().filter((s3) => s3.id !== sessionId && s3.boundConversationId != null).map((s3) => s3.boundConversationId)
144543
+ );
144544
+ for (const root of this.codexRoots) {
144545
+ const sessionsDir = (0, import_path21.join)(root, dateDir);
144546
+ if (!(0, import_fs24.existsSync)(sessionsDir)) continue;
144547
+ let candidateFiles;
144548
+ try {
144549
+ candidateFiles = (0, import_fs24.readdirSync)(sessionsDir).filter((f2) => f2.endsWith(".jsonl"));
144550
+ } catch {
144551
+ continue;
144552
+ }
144553
+ const nowMs = Date.now();
144554
+ const recentCandidates = candidateFiles.map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path21.join)(sessionsDir, f2)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b2) => b2.mtime - a.mtime);
144555
+ for (const { f: f2 } of recentCandidates) {
144556
+ const candidatePath = (0, import_path21.join)(sessionsDir, f2);
144557
+ const match2 = matchesProjectPath(candidatePath);
144558
+ if (!match2) continue;
144559
+ if (boundElsewhere.has(match2.id)) continue;
144560
+ const codexSessionId = match2.id;
144561
+ cleanup();
144562
+ this.sessionStore.updateManaged(sessionId, { boundConversationId: codexSessionId });
144563
+ this.sessionFileMap.set(sessionId, candidatePath);
144564
+ this.fileWatcher.watch(candidatePath);
144565
+ try {
144566
+ const existing = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
144567
+ if (existing.length > 0) {
144568
+ this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
144569
+ for (const line of existing) {
144570
+ this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
144571
+ }
144572
+ }
144573
+ } catch {
144574
+ }
144575
+ if (this.scannerReady) {
144576
+ this.scannerStale = true;
144577
+ } else {
144578
+ this.scanner = null;
144579
+ }
144580
+ this.linkSessionToProject(sessionId, projectPath, candidatePath);
144581
+ this.cache?.markAsStreamer(sessionId);
144582
+ const resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());
144583
+ if (resp) {
144584
+ this.wsHub.broadcast({ type: "session_update", session: resp });
144585
+ }
144586
+ this.log.info(
144587
+ `[startFresh] bound Codex rollout for ${sessionId}`,
144588
+ {
144589
+ event: "session.codex_rollout_bound",
144590
+ sessionId,
144591
+ boundConversationId: codexSessionId,
144592
+ filePath: candidatePath
144593
+ },
144594
+ "pino"
144595
+ );
144596
+ return;
144597
+ }
144598
+ }
144599
+ };
144600
+ tryWire();
144601
+ if (!intervalHandle && Date.now() <= deadline) {
144602
+ const alreadyBound = this.sessionStore.getManaged(sessionId)?.boundConversationId != null;
144603
+ if (!alreadyBound) {
144604
+ intervalHandle = setInterval(tryWire, 250);
144605
+ }
144606
+ }
144607
+ }
143790
144608
  async handleBrowse(url2, res) {
143791
144609
  if (!this.browseRoot) {
143792
144610
  json2(res, 403, {
@@ -143870,7 +144688,7 @@ var StreamerServer = class {
143870
144688
  };
143871
144689
  function classifyResumability(cwd) {
143872
144690
  if (!cwd) return { resumable: true };
143873
- if ((0, import_fs23.existsSync)(cwd)) return { resumable: true };
144691
+ if ((0, import_fs24.existsSync)(cwd)) return { resumable: true };
143874
144692
  const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
143875
144693
  return {
143876
144694
  resumable: false,
@@ -144450,13 +145268,13 @@ var import_node_fs17 = require("fs");
144450
145268
 
144451
145269
  // node_modules/tar/dist/esm/index.min.js
144452
145270
  var import_events4 = __toESM(require("events"), 1);
144453
- var import_fs24 = __toESM(require("fs"), 1);
145271
+ var import_fs25 = __toESM(require("fs"), 1);
144454
145272
  var import_node_events3 = require("events");
144455
145273
  var import_node_stream3 = __toESM(require("stream"), 1);
144456
145274
  var import_node_string_decoder = require("string_decoder");
144457
145275
  var import_node_path14 = __toESM(require("path"), 1);
144458
145276
  var import_node_fs11 = __toESM(require("fs"), 1);
144459
- var import_path20 = require("path");
145277
+ var import_path22 = require("path");
144460
145278
  var import_events5 = require("events");
144461
145279
  var import_assert = __toESM(require("assert"), 1);
144462
145280
  var import_buffer = require("buffer");
@@ -144464,17 +145282,17 @@ var Ps = __toESM(require("zlib"), 1);
144464
145282
  var import_zlib = __toESM(require("zlib"), 1);
144465
145283
  var import_node_path15 = require("path");
144466
145284
  var import_node_path16 = require("path");
144467
- var import_fs25 = __toESM(require("fs"), 1);
144468
145285
  var import_fs26 = __toESM(require("fs"), 1);
144469
- var import_path21 = __toESM(require("path"), 1);
145286
+ var import_fs27 = __toESM(require("fs"), 1);
145287
+ var import_path23 = __toESM(require("path"), 1);
144470
145288
  var import_node_path17 = require("path");
144471
- var import_path22 = __toESM(require("path"), 1);
145289
+ var import_path24 = __toESM(require("path"), 1);
144472
145290
  var import_node_fs12 = __toESM(require("fs"), 1);
144473
145291
  var import_node_assert = __toESM(require("assert"), 1);
144474
145292
  var import_node_crypto5 = require("crypto");
144475
145293
  var import_node_fs13 = __toESM(require("fs"), 1);
144476
145294
  var import_node_path18 = __toESM(require("path"), 1);
144477
- var import_fs27 = __toESM(require("fs"), 1);
145295
+ var import_fs28 = __toESM(require("fs"), 1);
144478
145296
  var import_node_fs14 = __toESM(require("fs"), 1);
144479
145297
  var import_node_path19 = __toESM(require("path"), 1);
144480
145298
  var import_node_fs15 = __toESM(require("fs"), 1);
@@ -144826,7 +145644,7 @@ var A = class extends import_node_events3.EventEmitter {
144826
145644
  return Wr;
144827
145645
  }
144828
145646
  };
144829
- var Jr = import_fs24.default.writev;
145647
+ var Jr = import_fs25.default.writev;
144830
145648
  var ht = /* @__PURE__ */ Symbol("_autoClose");
144831
145649
  var H = /* @__PURE__ */ Symbol("_close");
144832
145650
  var te = /* @__PURE__ */ Symbol("_ended");
@@ -144881,7 +145699,7 @@ var _t = class extends A {
144881
145699
  throw new TypeError("this is a readable stream");
144882
145700
  }
144883
145701
  [at]() {
144884
- import_fs24.default.open(this[U2], "r", (t2, e) => this[Ht](t2, e));
145702
+ import_fs25.default.open(this[U2], "r", (t2, e) => this[Ht](t2, e));
144885
145703
  }
144886
145704
  [Ht](t2, e) {
144887
145705
  t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[zt]());
@@ -144894,7 +145712,7 @@ var _t = class extends A {
144894
145712
  this[j] = true;
144895
145713
  let t2 = this[vi]();
144896
145714
  if (t2.length === 0) return process.nextTick(() => this[Ii](null, 0, t2));
144897
- import_fs24.default.read(this[u2], t2, 0, t2.length, null, (e, i, r) => this[Ii](e, i, r));
145715
+ import_fs25.default.read(this[u2], t2, 0, t2.length, null, (e, i, r) => this[Ii](e, i, r));
144898
145716
  }
144899
145717
  }
144900
145718
  [Ii](t2, e, i) {
@@ -144903,7 +145721,7 @@ var _t = class extends A {
144903
145721
  [H]() {
144904
145722
  if (this[ht] && typeof this[u2] == "number") {
144905
145723
  let t2 = this[u2];
144906
- this[u2] = void 0, import_fs24.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145724
+ this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
144907
145725
  }
144908
145726
  }
144909
145727
  [Ut](t2) {
@@ -144931,7 +145749,7 @@ var Be = class extends _t {
144931
145749
  [at]() {
144932
145750
  let t2 = true;
144933
145751
  try {
144934
- this[Ht](null, import_fs24.default.openSync(this[U2], "r")), t2 = false;
145752
+ this[Ht](null, import_fs25.default.openSync(this[U2], "r")), t2 = false;
144935
145753
  } finally {
144936
145754
  t2 && this[H]();
144937
145755
  }
@@ -144942,7 +145760,7 @@ var Be = class extends _t {
144942
145760
  if (!this[j]) {
144943
145761
  this[j] = true;
144944
145762
  do {
144945
- let e = this[vi](), i = e.length === 0 ? 0 : import_fs24.default.readSync(this[u2], e, 0, e.length, null);
145763
+ let e = this[vi](), i = e.length === 0 ? 0 : import_fs25.default.readSync(this[u2], e, 0, e.length, null);
144946
145764
  if (!this[ki](i, e)) break;
144947
145765
  } while (true);
144948
145766
  this[j] = false;
@@ -144955,7 +145773,7 @@ var Be = class extends _t {
144955
145773
  [H]() {
144956
145774
  if (this[ht] && typeof this[u2] == "number") {
144957
145775
  let t2 = this[u2];
144958
- this[u2] = void 0, import_fs24.default.closeSync(t2), this.emit("close");
145776
+ this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
144959
145777
  }
144960
145778
  }
144961
145779
  };
@@ -144997,7 +145815,7 @@ var et = class extends import_events4.default {
144997
145815
  this[H](), this[gt] = true, this.emit("error", t2);
144998
145816
  }
144999
145817
  [at]() {
145000
- import_fs24.default.open(this[U2], this[tt], this[ie], (t2, e) => this[Ht](t2, e));
145818
+ import_fs25.default.open(this[U2], this[tt], this[ie], (t2, e) => this[Ht](t2, e));
145001
145819
  }
145002
145820
  [Ht](t2, e) {
145003
145821
  this[Me] && this[tt] === "r+" && t2 && t2.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[gt] || this[Ai]());
@@ -145009,7 +145827,7 @@ var et = class extends import_events4.default {
145009
145827
  return typeof t2 == "string" && (t2 = Buffer.from(t2, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[u2] === void 0 || this[gt] || this[Y2].length ? (this[Y2].push(t2), this[ke] = true, false) : (this[gt] = true, this[ve](t2), true);
145010
145828
  }
145011
145829
  [ve](t2) {
145012
- import_fs24.default.write(this[u2], t2, 0, t2.length, this[ot], (e, i) => this[Pt](e, i));
145830
+ import_fs25.default.write(this[u2], t2, 0, t2.length, this[ot], (e, i) => this[Pt](e, i));
145013
145831
  }
145014
145832
  [Pt](t2, e) {
145015
145833
  t2 ? this[Ut](t2) : (this[ot] !== void 0 && typeof e == "number" && (this[ot] += e), this[Y2].length ? this[Ai]() : (this[gt] = false, this[te] && !this[Ni] ? (this[Ni] = true, this[H](), this.emit("finish")) : this[ke] && (this[ke] = false, this.emit("drain"))));
@@ -145025,7 +145843,7 @@ var et = class extends import_events4.default {
145025
145843
  [H]() {
145026
145844
  if (this[ht] && typeof this[u2] == "number") {
145027
145845
  let t2 = this[u2];
145028
- this[u2] = void 0, import_fs24.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145846
+ this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145029
145847
  }
145030
145848
  }
145031
145849
  };
@@ -145033,24 +145851,24 @@ var Wt = class extends et {
145033
145851
  [at]() {
145034
145852
  let t2;
145035
145853
  if (this[Me] && this[tt] === "r+") try {
145036
- t2 = import_fs24.default.openSync(this[U2], this[tt], this[ie]);
145854
+ t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
145037
145855
  } catch (e) {
145038
145856
  if (e?.code === "ENOENT") return this[tt] = "w", this[at]();
145039
145857
  throw e;
145040
145858
  }
145041
- else t2 = import_fs24.default.openSync(this[U2], this[tt], this[ie]);
145859
+ else t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
145042
145860
  this[Ht](null, t2);
145043
145861
  }
145044
145862
  [H]() {
145045
145863
  if (this[ht] && typeof this[u2] == "number") {
145046
145864
  let t2 = this[u2];
145047
- this[u2] = void 0, import_fs24.default.closeSync(t2), this.emit("close");
145865
+ this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
145048
145866
  }
145049
145867
  }
145050
145868
  [ve](t2) {
145051
145869
  let e = true;
145052
145870
  try {
145053
- this[Pt](null, import_fs24.default.writeSync(this[u2], t2, 0, t2.length, this[ot])), e = false;
145871
+ this[Pt](null, import_fs25.default.writeSync(this[u2], t2, 0, t2.length, this[ot])), e = false;
145054
145872
  } finally {
145055
145873
  if (e) try {
145056
145874
  this[H]();
@@ -145833,11 +146651,11 @@ var vn = (s3) => {
145833
146651
  };
145834
146652
  var Qi = (s3, t2) => {
145835
146653
  let e = new Map(t2.map((n) => [ut(n), true])), i = s3.filter, r = (n, o = "") => {
145836
- let h = o || (0, import_path20.parse)(n).root || ".", a;
146654
+ let h = o || (0, import_path22.parse)(n).root || ".", a;
145837
146655
  if (n === h) a = false;
145838
146656
  else {
145839
146657
  let l = e.get(n);
145840
- a = l !== void 0 ? l : r((0, import_path20.dirname)(n), h);
146658
+ a = l !== void 0 ? l : r((0, import_path22.dirname)(n), h);
145841
146659
  }
145842
146660
  return e.set(n, a), a;
145843
146661
  };
@@ -145957,7 +146775,7 @@ var de = class extends A {
145957
146775
  let [o, h] = ce(this.path);
145958
146776
  o && typeof h == "string" && (this.path = h, r = o);
145959
146777
  }
145960
- this.win32 = !!i.win32 || process.platform === "win32", this.win32 && (this.path = Qs(this.path.replaceAll(/\\/g, "/")), t2 = t2.replaceAll(/\\/g, "/")), this.absolute = f(i.absolute || import_path21.default.resolve(this.cwd, t2)), this.path === "" && (this.path = "./"), r && this.warn("TAR_ENTRY_INFO", `stripping ${r} from absolute path`, { entry: this, path: r + this.path });
146778
+ this.win32 = !!i.win32 || process.platform === "win32", this.win32 && (this.path = Qs(this.path.replaceAll(/\\/g, "/")), t2 = t2.replaceAll(/\\/g, "/")), this.absolute = f(i.absolute || import_path23.default.resolve(this.cwd, t2)), this.path === "" && (this.path = "./"), r && this.warn("TAR_ENTRY_INFO", `stripping ${r} from absolute path`, { entry: this, path: r + this.path });
145961
146779
  let n = this.statCache.get(this.absolute);
145962
146780
  n ? this[si](n) : this[ss]();
145963
146781
  }
@@ -145968,7 +146786,7 @@ var de = class extends A {
145968
146786
  return t2 === "error" && (this.#t = true), super.emit(t2, ...e);
145969
146787
  }
145970
146788
  [ss]() {
145971
- import_fs26.default.lstat(this.absolute, (t2, e) => {
146789
+ import_fs27.default.lstat(this.absolute, (t2, e) => {
145972
146790
  if (t2) return this.emit("error", t2);
145973
146791
  this[si](e);
145974
146792
  });
@@ -146006,7 +146824,7 @@ var de = class extends A {
146006
146824
  this.path.slice(-1) !== "/" && (this.path += "/"), this.stat.size = 0, this[fe](), this.end();
146007
146825
  }
146008
146826
  [is]() {
146009
- import_fs26.default.readlink(this.absolute, (t2, e) => {
146827
+ import_fs27.default.readlink(this.absolute, (t2, e) => {
146010
146828
  if (t2) return this.emit("error", t2);
146011
146829
  this[ns](e);
146012
146830
  });
@@ -146016,7 +146834,7 @@ var de = class extends A {
146016
146834
  }
146017
146835
  [sr](t2) {
146018
146836
  if (!this.stat) throw new Error("cannot create link entry without stat");
146019
- this.type = "Link", this.linkpath = f(import_path21.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
146837
+ this.type = "Link", this.linkpath = f(import_path23.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
146020
146838
  }
146021
146839
  [er]() {
146022
146840
  if (!this.stat) throw new Error("cannot create file entry without stat");
@@ -146029,7 +146847,7 @@ var de = class extends A {
146029
146847
  this[os]();
146030
146848
  }
146031
146849
  [os]() {
146032
- import_fs26.default.open(this.absolute, "r", (t2, e) => {
146850
+ import_fs27.default.open(this.absolute, "r", (t2, e) => {
146033
146851
  if (t2) return this.emit("error", t2);
146034
146852
  this[hs](e);
146035
146853
  });
@@ -146044,14 +146862,14 @@ var de = class extends A {
146044
146862
  [ii]() {
146045
146863
  let { fd: t2, buf: e, offset: i, length: r, pos: n } = this;
146046
146864
  if (t2 === void 0 || e === void 0) throw new Error("cannot read file without first opening");
146047
- import_fs26.default.read(t2, e, i, r, n, (o, h) => {
146865
+ import_fs27.default.read(t2, e, i, r, n, (o, h) => {
146048
146866
  if (o) return this[pt](() => this.emit("error", o));
146049
146867
  this[rs](h);
146050
146868
  });
146051
146869
  }
146052
146870
  [pt](t2 = () => {
146053
146871
  }) {
146054
- this.fd !== void 0 && import_fs26.default.close(this.fd, t2);
146872
+ this.fd !== void 0 && import_fs27.default.close(this.fd, t2);
146055
146873
  }
146056
146874
  [rs](t2) {
146057
146875
  if (t2 <= 0 && this.remain > 0) {
@@ -146086,20 +146904,20 @@ var de = class extends A {
146086
146904
  var ni = class extends de {
146087
146905
  sync = true;
146088
146906
  [ss]() {
146089
- this[si](import_fs26.default.lstatSync(this.absolute));
146907
+ this[si](import_fs27.default.lstatSync(this.absolute));
146090
146908
  }
146091
146909
  [is]() {
146092
- this[ns](import_fs26.default.readlinkSync(this.absolute));
146910
+ this[ns](import_fs27.default.readlinkSync(this.absolute));
146093
146911
  }
146094
146912
  [os]() {
146095
- this[hs](import_fs26.default.openSync(this.absolute, "r"));
146913
+ this[hs](import_fs27.default.openSync(this.absolute, "r"));
146096
146914
  }
146097
146915
  [ii]() {
146098
146916
  let t2 = true;
146099
146917
  try {
146100
146918
  let { fd: e, buf: i, offset: r, length: n, pos: o } = this;
146101
146919
  if (e === void 0 || i === void 0) throw new Error("fd and buf must be set in READ method");
146102
- let h = import_fs26.default.readSync(e, i, r, n, o);
146920
+ let h = import_fs27.default.readSync(e, i, r, n, o);
146103
146921
  this[rs](h), t2 = false;
146104
146922
  } finally {
146105
146923
  if (t2) try {
@@ -146114,7 +146932,7 @@ var ni = class extends de {
146114
146932
  }
146115
146933
  [pt](t2 = () => {
146116
146934
  }) {
146117
- this.fd !== void 0 && import_fs26.default.closeSync(this.fd), t2();
146935
+ this.fd !== void 0 && import_fs27.default.closeSync(this.fd), t2();
146118
146936
  }
146119
146937
  };
146120
146938
  var oi = class extends A {
@@ -146430,7 +147248,7 @@ var wt = class extends A {
146430
147248
  return typeof t2 == "string" ? this[ci](t2) : this[or](t2), this.flowing;
146431
147249
  }
146432
147250
  [or](t2) {
146433
- let e = f(import_path22.default.resolve(this.cwd, t2.path));
147251
+ let e = f(import_path24.default.resolve(this.cwd, t2.path));
146434
147252
  if (!this.filter(t2.path, t2)) t2.resume();
146435
147253
  else {
146436
147254
  let i = new pi(t2.path, e);
@@ -146439,13 +147257,13 @@ var wt = class extends A {
146439
147257
  this[Ft]();
146440
147258
  }
146441
147259
  [ci](t2) {
146442
- let e = f(import_path22.default.resolve(this.cwd, t2));
147260
+ let e = f(import_path24.default.resolve(this.cwd, t2));
146443
147261
  this[W].push(new pi(t2, e)), this[Ft]();
146444
147262
  }
146445
147263
  [ds](t2) {
146446
147264
  t2.pending = true, this[G2] += 1;
146447
147265
  let e = this.follow ? "stat" : "lstat";
146448
- import_fs25.default[e](t2.absolute, (i, r) => {
147266
+ import_fs26.default[e](t2.absolute, (i, r) => {
146449
147267
  t2.pending = false, this[G2] -= 1, i ? this.emit("error", i) : this[li](t2, r);
146450
147268
  });
146451
147269
  }
@@ -146459,7 +147277,7 @@ var wt = class extends A {
146459
147277
  this[Ft]();
146460
147278
  }
146461
147279
  [ms](t2) {
146462
- t2.pending = true, this[G2] += 1, import_fs25.default.readdir(t2.absolute, (e, i) => {
147280
+ t2.pending = true, this[G2] += 1, import_fs26.default.readdir(t2.absolute, (e, i) => {
146463
147281
  if (t2.pending = false, this[G2] -= 1, e) return this.emit("error", e);
146464
147282
  this[fi](t2, i);
146465
147283
  });
@@ -146560,10 +147378,10 @@ var kt = class extends wt {
146560
147378
  }
146561
147379
  [ds](t2) {
146562
147380
  let e = this.follow ? "statSync" : "lstatSync";
146563
- this[li](t2, import_fs25.default[e](t2.absolute));
147381
+ this[li](t2, import_fs26.default[e](t2.absolute));
146564
147382
  }
146565
147383
  [ms](t2) {
146566
- this[fi](t2, import_fs25.default.readdirSync(t2.absolute));
147384
+ this[fi](t2, import_fs26.default.readdirSync(t2.absolute));
146567
147385
  }
146568
147386
  [di](t2) {
146569
147387
  let e = t2.entry, i = this.zip;
@@ -146614,8 +147432,8 @@ var Qn = K2(Vn, $n, Xn, qn, (s3, t2) => {
146614
147432
  });
146615
147433
  var Jn = process.env.__FAKE_PLATFORM__ || process.platform;
146616
147434
  var Er = Jn === "win32";
146617
- var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs27.default.constants;
146618
- var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs27.default.constants.UV_FS_O_FILEMAP || 0;
147435
+ var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs28.default.constants;
147436
+ var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs28.default.constants.UV_FS_O_FILEMAP || 0;
146619
147437
  var jn = Er && !!Rr;
146620
147438
  var to = 512 * 1024;
146621
147439
  var eo = Rr | Sr | wr | yr;
@@ -147739,6 +148557,13 @@ program2.command("serve").description("Start the streamer server").option("-p, -
147739
148557
  "Run in multi-agent mode (PTY mode unreachable in this process)",
147740
148558
  false
147741
148559
  ).action(async (opts) => {
148560
+ try {
148561
+ const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
148562
+ checkSqliteAbi2();
148563
+ } catch (err) {
148564
+ log7.error(err instanceof Error ? err.message : String(err), void 0, "console");
148565
+ process.exit(1);
148566
+ }
147742
148567
  if (opts.multiAgentFlow) {
147743
148568
  process.env.MULTI_AGENT_FLOW = "true";
147744
148569
  }
@@ -147836,12 +148661,12 @@ program2.command("cache").description("Manage the local SQLite conversation cach
147836
148661
  "Cache directory (default: ~/.threadbase/cache)",
147837
148662
  `${process.env.HOME}/.threadbase/cache`
147838
148663
  ).action((opts) => {
147839
- const { rmSync: rmSync6, existsSync: existsSync11 } = require("fs");
148664
+ const { rmSync: rmSync6, existsSync: existsSync12 } = require("fs");
147840
148665
  const { join: join26 } = require("path");
147841
148666
  const dbPath = join26(opts.cacheDir, "cache.db");
147842
148667
  for (const suffix of ["", "-shm", "-wal"]) {
147843
148668
  const f2 = dbPath + suffix;
147844
- if (existsSync11(f2)) {
148669
+ if (existsSync12(f2)) {
147845
148670
  rmSync6(f2);
147846
148671
  log7.info(`Deleted ${f2}`, { path: f2 }, "console");
147847
148672
  }