@threadbase-sh/streamer 1.23.1 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;
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");
138449
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
  }
@@ -138695,6 +139061,10 @@ var PTYManager = class {
138695
139061
  // to a given input or fell silent. Reset on dispose().
138696
139062
  chunkIndex = /* @__PURE__ */ new Map();
138697
139063
  lastChunkAt = /* @__PURE__ */ new Map();
139064
+ // In-flight start()/startFresh() calls keyed by sessionId. A second
139065
+ // concurrent resume for the same session (double-tap, client retry) awaits
139066
+ // the first call's promise instead of spawning a duplicate PTY (CRITICAL #3).
139067
+ startPromises = /* @__PURE__ */ new Map();
138698
139068
  constructor(options = {}) {
138699
139069
  this.onOutput = options.onOutput;
138700
139070
  this.onStatusChange = options.onStatusChange;
@@ -138717,7 +139087,18 @@ var PTYManager = class {
138717
139087
  // custom-API-key — are cleared by the seeded ~/.claude.json in
138718
139088
  // docker/entrypoint.sh.) startFresh() uses the same flag for the same reason.
138719
139089
  async start(sessionId, options) {
138720
- const nodePty = await loadPty();
139090
+ const existing = this.sessions.get(sessionId);
139091
+ if (existing) return toPublicSession2(existing);
139092
+ const inFlight = this.startPromises.get(sessionId);
139093
+ if (inFlight) return inFlight;
139094
+ const promise2 = this.doStart(sessionId, options).finally(() => {
139095
+ this.startPromises.delete(sessionId);
139096
+ });
139097
+ this.startPromises.set(sessionId, promise2);
139098
+ return promise2;
139099
+ }
139100
+ async doStart(sessionId, options) {
139101
+ const nodePty = await loadPty2();
138721
139102
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138722
139103
  const proc = nodePty.spawn(
138723
139104
  resolveClaudeExe(),
@@ -138739,6 +139120,7 @@ var PTYManager = class {
138739
139120
  );
138740
139121
  const session = {
138741
139122
  id: sessionId,
139123
+ provider: CLAUDE_CODE_PROVIDER2,
138742
139124
  projectPath: options.projectPath,
138743
139125
  projectName,
138744
139126
  branch: options.branch ?? "",
@@ -138749,7 +139131,7 @@ var PTYManager = class {
138749
139131
  lastOutput: "",
138750
139132
  process: proc,
138751
139133
  outputBuffer: Buffer.alloc(0),
138752
- screen: createScreen()
139134
+ screen: createScreen2()
138753
139135
  };
138754
139136
  this.sessions.set(sessionId, session);
138755
139137
  this.pendingReady.add(sessionId);
@@ -138760,13 +139142,13 @@ var PTYManager = class {
138760
139142
  this.pendingReady.delete(sessionId);
138761
139143
  this.handleExit(sessionId, exitCode);
138762
139144
  });
138763
- return toPublicSession(session);
139145
+ return toPublicSession2(session);
138764
139146
  }
138765
139147
  // Start a brand-new Claude session. A stable UUID is generated here and passed
138766
139148
  // to Claude via --session-id so the JSONL filename matches from the start.
138767
139149
  // onReady fires once Claude reaches its first prompt (waiting_input).
138768
139150
  async startFresh(options) {
138769
- const nodePty = await loadPty();
139151
+ const nodePty = await loadPty2();
138770
139152
  const sessionId = (0, import_crypto6.randomUUID)();
138771
139153
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138772
139154
  const args = [
@@ -138789,6 +139171,7 @@ var PTYManager = class {
138789
139171
  });
138790
139172
  const session = {
138791
139173
  id: sessionId,
139174
+ provider: CLAUDE_CODE_PROVIDER2,
138792
139175
  projectPath: options.projectPath,
138793
139176
  projectName,
138794
139177
  branch: "",
@@ -138799,7 +139182,7 @@ var PTYManager = class {
138799
139182
  lastOutput: "",
138800
139183
  process: proc,
138801
139184
  outputBuffer: Buffer.alloc(0),
138802
- screen: createScreen()
139185
+ screen: createScreen2()
138803
139186
  };
138804
139187
  this.sessions.set(sessionId, session);
138805
139188
  this.pendingReady.add(sessionId);
@@ -138810,7 +139193,7 @@ var PTYManager = class {
138810
139193
  this.pendingReady.delete(sessionId);
138811
139194
  this.handleExit(sessionId, exitCode);
138812
139195
  });
138813
- return toPublicSession(session);
139196
+ return toPublicSession2(session);
138814
139197
  }
138815
139198
  // Write raw key bytes directly to the PTY without bracketed-paste wrapping.
138816
139199
  // Use for control sequences (arrow keys, Enter) that must not be quoted.
@@ -138822,10 +139205,10 @@ var PTYManager = class {
138822
139205
  }
138823
139206
  if (session.status === "waiting_input") {
138824
139207
  session.status = "running";
138825
- this.onStatusChange?.(toPublicSession(session));
139208
+ this.onStatusChange?.(toPublicSession2(session));
138826
139209
  }
138827
139210
  this.log.info(
138828
- `[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes(keys)}`,
139211
+ `[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes2(keys)}`,
138829
139212
  { event: "pty.keys_write", sessionId, byteLen: keys.length }
138830
139213
  );
138831
139214
  session.process.write(keys);
@@ -138857,7 +139240,7 @@ var PTYManager = class {
138857
139240
  }
138858
139241
  if (session.status === "waiting_input") {
138859
139242
  session.status = "running";
138860
- this.onStatusChange?.(toPublicSession(session));
139243
+ this.onStatusChange?.(toPublicSession2(session));
138861
139244
  }
138862
139245
  this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
138863
139246
  session.lastActivityAt = /* @__PURE__ */ new Date();
@@ -138870,13 +139253,13 @@ var PTYManager = class {
138870
139253
  writeSubmit(sessionId, session, input, path2, promptCount) {
138871
139254
  const pasteBytes = buildPasteBytes(input);
138872
139255
  this.log.info(
138873
- `[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes(pasteBytes)}`,
139256
+ `[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes2(pasteBytes)}`,
138874
139257
  {
138875
139258
  event: "pty.input_write",
138876
139259
  sessionId,
138877
139260
  promptCount,
138878
139261
  byteLen: pasteBytes.length,
138879
- digest: digestBytes(pasteBytes),
139262
+ digest: digestBytes2(pasteBytes),
138880
139263
  path: path2,
138881
139264
  phase: "paste"
138882
139265
  }
@@ -138891,13 +139274,13 @@ var PTYManager = class {
138891
139274
  event: "pty.input_write",
138892
139275
  sessionId,
138893
139276
  promptCount,
138894
- byteLen: SUBMIT_BYTES.length,
139277
+ byteLen: SUBMIT_BYTES2.length,
138895
139278
  digest: "\\r",
138896
139279
  path: path2,
138897
139280
  phase: "submit"
138898
139281
  }
138899
139282
  );
138900
- current.process.write(SUBMIT_BYTES);
139283
+ current.process.write(SUBMIT_BYTES2);
138901
139284
  }, SUBMIT_DELAY_MS);
138902
139285
  }
138903
139286
  // Drain any inputs that were sent while the session was still pendingReady,
@@ -138955,7 +139338,7 @@ var PTYManager = class {
138955
139338
  session.completedAt = /* @__PURE__ */ new Date();
138956
139339
  session.screen.dispose();
138957
139340
  this.sessions.delete(sessionId);
138958
- this.onStatusChange?.(toPublicSession(session));
139341
+ this.onStatusChange?.(toPublicSession2(session));
138959
139342
  }
138960
139343
  getOutput(sessionId) {
138961
139344
  const session = this.sessions.get(sessionId);
@@ -138986,13 +139369,13 @@ var PTYManager = class {
138986
139369
  }
138987
139370
  getSession(sessionId) {
138988
139371
  const session = this.sessions.get(sessionId);
138989
- return session ? toPublicSession(session) : null;
139372
+ return session ? toPublicSession2(session) : null;
138990
139373
  }
138991
139374
  hasSession(sessionId) {
138992
139375
  return this.sessions.has(sessionId);
138993
139376
  }
138994
139377
  listSessions() {
138995
- return Array.from(this.sessions.values()).map(toPublicSession);
139378
+ return Array.from(this.sessions.values()).map(toPublicSession2);
138996
139379
  }
138997
139380
  dispose() {
138998
139381
  for (const session of this.sessions.values()) {
@@ -139024,7 +139407,7 @@ var PTYManager = class {
139024
139407
  this.lastChunkAt.set(sessionId, now);
139025
139408
  const gapMs = last == null ? 0 : now - last;
139026
139409
  this.log.info(
139027
- `[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes(data)}`,
139410
+ `[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes2(data)}`,
139028
139411
  {
139029
139412
  event: "pty.chunk",
139030
139413
  sessionId,
@@ -139033,17 +139416,17 @@ var PTYManager = class {
139033
139416
  gapMs,
139034
139417
  status: session.status,
139035
139418
  pendingReady: this.pendingReady.has(sessionId),
139036
- digest: digestBytes(data)
139419
+ digest: digestBytes2(data)
139037
139420
  }
139038
139421
  );
139039
139422
  session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
139040
- if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
139423
+ if (session.outputBuffer.length > OUTPUT_BUFFER_MAX2) {
139041
139424
  session.outputBuffer = session.outputBuffer.subarray(
139042
- session.outputBuffer.length - OUTPUT_BUFFER_MAX
139425
+ session.outputBuffer.length - OUTPUT_BUFFER_MAX2
139043
139426
  );
139044
139427
  }
139045
139428
  session.screen.write(data);
139046
- const stripped = stripAnsi(data);
139429
+ const stripped = stripAnsi2(data);
139047
139430
  session.lastOutput = stripped;
139048
139431
  const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m2) => stripped.includes(m2));
139049
139432
  if (session.status === "running" && matchedMarker) {
@@ -139146,11 +139529,11 @@ var PTYManager = class {
139146
139529
  reason,
139147
139530
  elapsedMs
139148
139531
  });
139149
- this.onStatusChange?.(toPublicSession(session));
139532
+ this.onStatusChange?.(toPublicSession2(session));
139150
139533
  if (this.pendingReady.has(sessionId)) {
139151
139534
  this.pendingReady.delete(sessionId);
139152
139535
  this.flushQueuedInputs(sessionId);
139153
- this.onReady?.(toPublicSession(session));
139536
+ this.onReady?.(toPublicSession2(session));
139154
139537
  }
139155
139538
  }
139156
139539
  handleExit(sessionId, exitCode) {
@@ -139160,13 +139543,13 @@ var PTYManager = class {
139160
139543
  session.status = "idle";
139161
139544
  const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
139162
139545
  if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
139163
- if (!(0, import_fs20.existsSync)(session.projectPath)) {
139546
+ if (!(0, import_fs21.existsSync)(session.projectPath)) {
139164
139547
  session.failureReason = `Project directory not found: ${session.projectPath}`;
139165
139548
  } else {
139166
139549
  session.failureReason = `Process exited immediately (code ${exitCode}). Check that the Claude binary is installed and accessible.`;
139167
139550
  }
139168
139551
  }
139169
- this.onStatusChange?.(toPublicSession(session));
139552
+ this.onStatusChange?.(toPublicSession2(session));
139170
139553
  session.screen.dispose();
139171
139554
  this.sessions.delete(sessionId);
139172
139555
  this.queuedInputs.delete(sessionId);
@@ -139176,9 +139559,10 @@ var PTYManager = class {
139176
139559
  this.shellPromptOpen.delete(sessionId);
139177
139560
  }
139178
139561
  };
139179
- function toPublicSession(s3) {
139562
+ function toPublicSession2(s3) {
139180
139563
  return {
139181
139564
  id: s3.id,
139565
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
139182
139566
  projectPath: s3.projectPath,
139183
139567
  projectName: s3.projectName,
139184
139568
  branch: s3.branch,
@@ -139192,10 +139576,306 @@ function toPublicSession(s3) {
139192
139576
  ...s3.filePath != null && { filePath: s3.filePath }
139193
139577
  };
139194
139578
  }
139195
- function stripAnsi(str) {
139579
+ function stripAnsi2(str) {
139196
139580
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
139197
139581
  }
139198
139582
 
139583
+ // src/live-session-manager.ts
139584
+ var LiveSessionManager = class {
139585
+ runners;
139586
+ constructor(options = {}) {
139587
+ this.runners = /* @__PURE__ */ new Map([
139588
+ [CLAUDE_CODE_PROVIDER2, new PTYManager(options)],
139589
+ [CODEX_CLI_PROVIDER2, new CodexPtyRunner(options)]
139590
+ ]);
139591
+ }
139592
+ async start(sessionId, options) {
139593
+ const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
139594
+ const runner = this.assertSupportedProvider(provider, options.projectPath);
139595
+ return runner.start(sessionId, options);
139596
+ }
139597
+ async startFresh(options) {
139598
+ const provider = options.provider ?? CLAUDE_CODE_PROVIDER2;
139599
+ const runner = this.assertSupportedProvider(provider, options.projectPath);
139600
+ return runner.startFresh(options);
139601
+ }
139602
+ sendInput(sessionId, input) {
139603
+ return this.runnerFor(sessionId).sendInput(sessionId, input);
139604
+ }
139605
+ sendKeys(sessionId, keys) {
139606
+ this.runnerFor(sessionId).sendKeys(sessionId, keys);
139607
+ }
139608
+ cancel(sessionId) {
139609
+ this.runnerFor(sessionId).cancel(sessionId);
139610
+ }
139611
+ killPid(pid) {
139612
+ for (const runner of this.runners.values()) {
139613
+ runner.killPid(pid);
139614
+ }
139615
+ }
139616
+ // putOnHold tolerates an unknown sessionId (PTYManager.putOnHold is a no-op
139617
+ // when the session isn't in its map), so — unlike the other session-keyed
139618
+ // methods — route to the owning runner when found, otherwise broadcast to
139619
+ // every runner rather than throwing; this matches the pre-extraction
139620
+ // behavior of delegating straight through with no existence check.
139621
+ putOnHold(sessionId) {
139622
+ for (const runner of this.runners.values()) {
139623
+ if (runner.hasSession(sessionId) || runner.getSession(sessionId)) {
139624
+ runner.putOnHold(sessionId);
139625
+ return;
139626
+ }
139627
+ }
139628
+ for (const runner of this.runners.values()) {
139629
+ runner.putOnHold(sessionId);
139630
+ }
139631
+ }
139632
+ getOutput(sessionId) {
139633
+ return this.runnerFor(sessionId).getOutput(sessionId);
139634
+ }
139635
+ getOutputLines(sessionId, maxLines) {
139636
+ return this.runnerFor(sessionId).getOutputLines(sessionId, maxLines);
139637
+ }
139638
+ getSession(sessionId) {
139639
+ for (const runner of this.runners.values()) {
139640
+ const session = runner.getSession(sessionId);
139641
+ if (session) return session;
139642
+ }
139643
+ return null;
139644
+ }
139645
+ hasSession(sessionId) {
139646
+ for (const runner of this.runners.values()) {
139647
+ if (runner.hasSession(sessionId)) return true;
139648
+ }
139649
+ return false;
139650
+ }
139651
+ listSessions() {
139652
+ return Array.from(this.runners.values()).flatMap((runner) => runner.listSessions());
139653
+ }
139654
+ dispose() {
139655
+ for (const runner of this.runners.values()) {
139656
+ runner.dispose();
139657
+ }
139658
+ }
139659
+ // Look up which runner owns a session. Only one runner exists today, so
139660
+ // this is a linear scan across hasSession()/getSession() rather than a
139661
+ // separate session→provider index — see task-1-brief.md.
139662
+ runnerFor(sessionId) {
139663
+ for (const runner of this.runners.values()) {
139664
+ if (runner.hasSession(sessionId) || runner.getSession(sessionId)) return runner;
139665
+ }
139666
+ throw new Error(`Session not found: ${sessionId}`);
139667
+ }
139668
+ assertSupportedProvider(provider, projectPath) {
139669
+ const runner = this.runners.get(provider);
139670
+ if (runner) return runner;
139671
+ const err = new Error(
139672
+ `Live ${provider} sessions are not implemented yet for ${(0, import_path18.basename)(projectPath)}`
139673
+ );
139674
+ err.statusCode = 501;
139675
+ throw err;
139676
+ }
139677
+ };
139678
+
139679
+ // src/server.ts
139680
+ init_logger();
139681
+
139682
+ // src/pair-store.ts
139683
+ var import_crypto7 = require("crypto");
139684
+ var DEFAULT_TTL_SECONDS = 180;
139685
+ var SWEEP_INTERVAL_MS = 6e4;
139686
+ var PairTokenStore = class {
139687
+ current = null;
139688
+ ttlMs;
139689
+ sweepTimer = null;
139690
+ constructor(opts = {}) {
139691
+ this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
139692
+ if (opts.autoSweep !== false) {
139693
+ this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
139694
+ this.sweepTimer.unref?.();
139695
+ }
139696
+ }
139697
+ mint() {
139698
+ const token = `pt_${(0, import_crypto7.randomBytes)(16).toString("hex")}`;
139699
+ const expiresAt = Date.now() + this.ttlMs;
139700
+ this.current = { token, expiresAt, used: false };
139701
+ return {
139702
+ token,
139703
+ expiresAt,
139704
+ expiresInSeconds: Math.floor(this.ttlMs / 1e3)
139705
+ };
139706
+ }
139707
+ consume(token) {
139708
+ const record2 = this.current;
139709
+ if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
139710
+ if (Date.now() > record2.expiresAt) {
139711
+ this.current = null;
139712
+ return { ok: false, reason: "expired" };
139713
+ }
139714
+ if (record2.used) return { ok: false, reason: "used" };
139715
+ record2.used = true;
139716
+ return { ok: true };
139717
+ }
139718
+ peek() {
139719
+ return this.current;
139720
+ }
139721
+ clear() {
139722
+ this.current = null;
139723
+ }
139724
+ sweep() {
139725
+ if (this.current && Date.now() > this.current.expiresAt) {
139726
+ this.current = null;
139727
+ }
139728
+ }
139729
+ dispose() {
139730
+ if (this.sweepTimer) clearInterval(this.sweepTimer);
139731
+ this.sweepTimer = null;
139732
+ this.current = null;
139733
+ }
139734
+ };
139735
+
139736
+ // src/process-discovery.ts
139737
+ var import_child_process2 = require("child_process");
139738
+ var import_os9 = require("os");
139739
+ var import_path19 = require("path");
139740
+ async function discoverClaudeProcesses() {
139741
+ if ((0, import_os9.platform)() === "win32") return discoverWindows();
139742
+ return discoverUnix();
139743
+ }
139744
+ async function discoverUnix() {
139745
+ const pids = await getPidsUnix();
139746
+ const results = await Promise.all(
139747
+ pids.map(async (pid) => {
139748
+ try {
139749
+ const [cwd, args, startedAt] = await Promise.all([
139750
+ getProcessCwdUnix(pid),
139751
+ getProcessArgsUnix(pid),
139752
+ getProcessStartTimeUnix(pid)
139753
+ ]);
139754
+ const conversationId = extractResumeId(args);
139755
+ return {
139756
+ pid,
139757
+ projectPath: cwd,
139758
+ projectName: (0, import_path19.basename)(cwd),
139759
+ branch: await readGitBranch2(cwd),
139760
+ conversationId,
139761
+ startedAt
139762
+ };
139763
+ } catch {
139764
+ return null;
139765
+ }
139766
+ })
139767
+ );
139768
+ return results.filter((r) => r !== null);
139769
+ }
139770
+ async function discoverWindows() {
139771
+ const pids = await getPidsWindows();
139772
+ const results = await Promise.all(
139773
+ pids.map(async (pid) => {
139774
+ try {
139775
+ const info = await getProcessInfoWindows(pid);
139776
+ if (!info) return null;
139777
+ return {
139778
+ pid,
139779
+ projectPath: info.cwd,
139780
+ projectName: (0, import_path19.basename)(info.cwd),
139781
+ branch: await readGitBranch2(info.cwd),
139782
+ conversationId: extractResumeId(info.args),
139783
+ startedAt: info.startedAt
139784
+ };
139785
+ } catch {
139786
+ return null;
139787
+ }
139788
+ })
139789
+ );
139790
+ return results.filter((r) => r !== null);
139791
+ }
139792
+ function run(cmd, args, opts = {}) {
139793
+ return new Promise((resolve6, reject) => {
139794
+ (0, import_child_process2.execFile)(
139795
+ cmd,
139796
+ args,
139797
+ { windowsHide: isWindows2, encoding: "utf-8", timeout: opts.timeout ?? 5e3, cwd: opts.cwd },
139798
+ (err, stdout2) => {
139799
+ if (err) reject(err);
139800
+ else resolve6(stdout2);
139801
+ }
139802
+ );
139803
+ });
139804
+ }
139805
+ async function getPidsUnix() {
139806
+ try {
139807
+ const output = await run("pgrep", ["-x", "claude"]);
139808
+ return output.trim().split("\n").filter(Boolean).map((s3) => Number.parseInt(s3, 10));
139809
+ } catch {
139810
+ return [];
139811
+ }
139812
+ }
139813
+ async function getProcessCwdUnix(pid) {
139814
+ const output = await run("lsof", ["-p", String(pid), "-a", "-d", "cwd", "-Fn"]);
139815
+ const match2 = output.match(/n(.+)/);
139816
+ return match2?.[1] ?? "";
139817
+ }
139818
+ async function getProcessArgsUnix(pid) {
139819
+ return (await run("ps", ["-p", String(pid), "-o", "args="])).trim();
139820
+ }
139821
+ async function getProcessStartTimeUnix(pid) {
139822
+ const raw2 = (await run("ps", ["-p", String(pid), "-o", "lstart="])).trim();
139823
+ const d = new Date(raw2);
139824
+ return Number.isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d;
139825
+ }
139826
+ async function getPidsWindows() {
139827
+ try {
139828
+ const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
139829
+ return output.trim().split("\n").filter(Boolean).map((line) => {
139830
+ const parts = line.split(",");
139831
+ return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
139832
+ }).filter((pid) => pid > 0);
139833
+ } catch {
139834
+ return [];
139835
+ }
139836
+ }
139837
+ async function getProcessInfoWindows(pid) {
139838
+ try {
139839
+ const output = await run("wmic", [
139840
+ "process",
139841
+ "where",
139842
+ `ProcessId=${pid}`,
139843
+ "get",
139844
+ "CommandLine,CreationDate,ExecutablePath",
139845
+ "/FORMAT:CSV"
139846
+ ]);
139847
+ const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
139848
+ if (lines.length < 2) return null;
139849
+ const parts = lines[1].split(",");
139850
+ const args = parts[1] ?? "";
139851
+ const creationDate = parts[2] ?? "";
139852
+ const year = creationDate.slice(0, 4);
139853
+ const month = creationDate.slice(4, 6);
139854
+ const day = creationDate.slice(6, 8);
139855
+ const hour = creationDate.slice(8, 10);
139856
+ const min = creationDate.slice(10, 12);
139857
+ const sec = creationDate.slice(12, 14);
139858
+ const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
139859
+ if (Number.isNaN(startedAt.getTime())) return null;
139860
+ const exePath = parts[3] ?? "";
139861
+ const cwd = exePath ? (0, import_path19.dirname)(exePath) : "";
139862
+ return { cwd, args, startedAt };
139863
+ } catch {
139864
+ return null;
139865
+ }
139866
+ }
139867
+ function extractResumeId(args) {
139868
+ const match2 = args.match(/--resume\s+(\S+)/);
139869
+ return match2?.[1] ?? null;
139870
+ }
139871
+ async function readGitBranch2(dir) {
139872
+ try {
139873
+ return (await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 3e3 })).trim();
139874
+ } catch {
139875
+ return "";
139876
+ }
139877
+ }
139878
+
139199
139879
  // src/seal.ts
139200
139880
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
139201
139881
  var import_tweetnacl_util = __toESM(require_nacl_util(), 1);
@@ -139384,10 +140064,10 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139384
140064
  }
139385
140065
  async _formatEntry(dirent, path2) {
139386
140066
  let entry;
139387
- const basename9 = this._isDirent ? dirent.name : dirent;
140067
+ const basename11 = this._isDirent ? dirent.name : dirent;
139388
140068
  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 };
140069
+ const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename11));
140070
+ entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename11 };
139391
140071
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
139392
140072
  } catch (err) {
139393
140073
  this._onError(err);
@@ -139928,9 +140608,9 @@ var NodeFsHandler2 = class {
139928
140608
  _watchWithNodeFs(path2, listener) {
139929
140609
  const opts = this.fsw.options;
139930
140610
  const directory = sp.dirname(path2);
139931
- const basename9 = sp.basename(path2);
140611
+ const basename11 = sp.basename(path2);
139932
140612
  const parent = this.fsw._getWatchedDir(directory);
139933
- parent.add(basename9);
140613
+ parent.add(basename11);
139934
140614
  const absolutePath = sp.resolve(path2);
139935
140615
  const options = {
139936
140616
  persistent: opts.persistent
@@ -139940,7 +140620,7 @@ var NodeFsHandler2 = class {
139940
140620
  let closer;
139941
140621
  if (opts.usePolling) {
139942
140622
  const enableBin = opts.interval !== opts.binaryInterval;
139943
- options.interval = enableBin && isBinaryPath2(basename9) ? opts.binaryInterval : opts.interval;
140623
+ options.interval = enableBin && isBinaryPath2(basename11) ? opts.binaryInterval : opts.interval;
139944
140624
  closer = setFsWatchFileListener2(path2, absolutePath, options, {
139945
140625
  listener,
139946
140626
  rawEmitter: this.fsw._emitRaw
@@ -139963,10 +140643,10 @@ var NodeFsHandler2 = class {
139963
140643
  return;
139964
140644
  }
139965
140645
  const dirname17 = sp.dirname(file2);
139966
- const basename9 = sp.basename(file2);
140646
+ const basename11 = sp.basename(file2);
139967
140647
  const parent = this.fsw._getWatchedDir(dirname17);
139968
140648
  let prevStats = stats;
139969
- if (parent.has(basename9))
140649
+ if (parent.has(basename11))
139970
140650
  return;
139971
140651
  const listener = async (path2, newStats) => {
139972
140652
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH2, file2, 5))
@@ -139991,9 +140671,9 @@ var NodeFsHandler2 = class {
139991
140671
  prevStats = newStats2;
139992
140672
  }
139993
140673
  } catch (error51) {
139994
- this.fsw._remove(dirname17, basename9);
140674
+ this.fsw._remove(dirname17, basename11);
139995
140675
  }
139996
- } else if (parent.has(basename9)) {
140676
+ } else if (parent.has(basename11)) {
139997
140677
  const at2 = newStats.atimeMs;
139998
140678
  const mt2 = newStats.mtimeMs;
139999
140679
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -140950,7 +141630,7 @@ function watch2(paths, options = {}) {
140950
141630
  var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
140951
141631
 
140952
141632
  // src/services/conversations/conversationWatcher.ts
140953
- var import_fs21 = require("fs");
141633
+ var import_fs22 = require("fs");
140954
141634
  var import_promises12 = require("fs/promises");
140955
141635
  var ConversationWatcher = class {
140956
141636
  files = /* @__PURE__ */ new Map();
@@ -140971,7 +141651,7 @@ var ConversationWatcher = class {
140971
141651
  if (this.files.has(filePath)) return;
140972
141652
  let offset;
140973
141653
  try {
140974
- offset = (0, import_fs21.statSync)(filePath).size;
141654
+ offset = (0, import_fs22.statSync)(filePath).size;
140975
141655
  } catch {
140976
141656
  offset = 0;
140977
141657
  }
@@ -141080,14 +141760,14 @@ var ConversationWatcher = class {
141080
141760
  };
141081
141761
 
141082
141762
  // src/services/conversations/pruneAgentConversations.ts
141083
- var import_fs22 = require("fs");
141763
+ var import_fs23 = require("fs");
141084
141764
  function pruneAgentConversations(cache) {
141085
141765
  const db = cache.getDatabase();
141086
141766
  const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
141087
141767
  let pruned = 0;
141088
141768
  let missing = 0;
141089
141769
  for (const row of rows) {
141090
- if (!(0, import_fs22.existsSync)(row.file_path)) {
141770
+ if (!(0, import_fs23.existsSync)(row.file_path)) {
141091
141771
  missing += 1;
141092
141772
  continue;
141093
141773
  }
@@ -141396,6 +142076,7 @@ function managedToResponse(s3, ptyAttached) {
141396
142076
  return {
141397
142077
  id: s3.id,
141398
142078
  conversationId: s3.id,
142079
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
141399
142080
  status: s3.status,
141400
142081
  projectPath: s3.projectPath,
141401
142082
  projectName: s3.projectName,
@@ -141421,13 +142102,15 @@ function managedToResponse(s3, ptyAttached) {
141421
142102
  ...s3.failureReason != null && { failureReason: s3.failureReason },
141422
142103
  ...s3.resumedFromConversationId != null && {
141423
142104
  resumedFromConversationId: s3.resumedFromConversationId
141424
- }
142105
+ },
142106
+ ...s3.boundConversationId != null && { boundConversationId: s3.boundConversationId }
141425
142107
  };
141426
142108
  }
141427
142109
  function discoveredToResponse(d, conversationId) {
141428
142110
  return {
141429
142111
  id: conversationId,
141430
142112
  conversationId,
142113
+ provider: CLAUDE_CODE_PROVIDER2,
141431
142114
  status: "idle",
141432
142115
  projectPath: d.projectPath,
141433
142116
  projectName: d.projectName,
@@ -141443,10 +142126,10 @@ function discoveredToResponse(d, conversationId) {
141443
142126
  }
141444
142127
 
141445
142128
  // src/uploads.ts
141446
- var import_crypto7 = require("crypto");
142129
+ var import_crypto8 = require("crypto");
141447
142130
  var import_promises13 = require("fs/promises");
141448
142131
  var import_heic_convert = __toESM(require_heic_convert(), 1);
141449
- var import_path18 = require("path");
142132
+ var import_path20 = require("path");
141450
142133
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
141451
142134
  var MAX_BYTES = 25 * 1024 * 1024;
141452
142135
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -141477,11 +142160,11 @@ async function saveUploadFile(input) {
141477
142160
  mimeType = "image/jpeg";
141478
142161
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
141479
142162
  }
141480
- const id = `up_${(0, import_crypto7.randomBytes)(8).toString("hex")}`;
142163
+ const id = `up_${(0, import_crypto8.randomBytes)(8).toString("hex")}`;
141481
142164
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
141482
- const dir = (0, import_path18.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
142165
+ const dir = (0, import_path20.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
141483
142166
  await (0, import_promises13.mkdir)(dir, { recursive: true });
141484
- const filePath = (0, import_path18.join)(dir, `${Date.now()}-${id}-${safeName}`);
142167
+ const filePath = (0, import_path20.join)(dir, `${Date.now()}-${id}-${safeName}`);
141485
142168
  await (0, import_promises13.writeFile)(filePath, buffer);
141486
142169
  return {
141487
142170
  id,
@@ -141994,10 +142677,10 @@ var StreamerServer = class {
141994
142677
  this.verbose = config2.verbose ?? false;
141995
142678
  this.disableDb = config2.disableDb ?? false;
141996
142679
  this.scanProfiles = config2.scanProfiles;
141997
- this.codexRoots = config2.codexRoots ?? [(0, import_path19.join)((0, import_os10.homedir)(), ".codex", "sessions")];
142680
+ this.codexRoots = config2.codexRoots ?? [(0, import_path21.join)((0, import_os10.homedir)(), ".codex", "sessions")];
141998
142681
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
141999
142682
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
142000
- this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path19.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142683
+ this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path21.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142001
142684
  this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
142002
142685
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
142003
142686
  this.markScannerStaleDebounced = debounce(() => {
@@ -142081,7 +142764,7 @@ var StreamerServer = class {
142081
142764
  });
142082
142765
  }
142083
142766
  });
142084
- this.ptyManager = new PTYManager({
142767
+ this.ptyManager = new LiveSessionManager({
142085
142768
  logger: getLogger("pty"),
142086
142769
  onOutput: (sessionId, data) => {
142087
142770
  this.wsHub.broadcast({ type: "terminal_output", sessionId, data });
@@ -142162,7 +142845,7 @@ var StreamerServer = class {
142162
142845
  temporalClient,
142163
142846
  taskQueue: agentConfig.temporal.taskQueue
142164
142847
  });
142165
- const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path19.join)((0, import_path19.dirname)(this.cacheDir), "conversations");
142848
+ const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations");
142166
142849
  conversationWriter = createConversationWriter({
142167
142850
  baseDir: conversationsBaseDir
142168
142851
  });
@@ -142384,7 +143067,7 @@ var StreamerServer = class {
142384
143067
  });
142385
143068
  try {
142386
143069
  this.cache = ConversationCache.open(
142387
- (0, import_path19.join)(this.cacheDir, "cache.db"),
143070
+ (0, import_path21.join)(this.cacheDir, "cache.db"),
142388
143071
  this.tailSize,
142389
143072
  void 0,
142390
143073
  {
@@ -142410,18 +143093,19 @@ var StreamerServer = class {
142410
143093
  if (this.scanProfiles && this.scanProfiles.length > 0) {
142411
143094
  for (const profile of this.scanProfiles) {
142412
143095
  if (profile.enabled) {
142413
- this.fileWatcher.watchDirectory((0, import_path19.join)(profile.configDir, "projects"));
143096
+ this.fileWatcher.watchDirectory((0, import_path21.join)(profile.configDir, "projects"));
142414
143097
  }
142415
143098
  }
142416
143099
  } else {
142417
- this.fileWatcher.watchDirectory((0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects"));
143100
+ this.fileWatcher.watchDirectory((0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects"));
142418
143101
  }
142419
143102
  } catch (err) {
142420
143103
  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
- });
143104
+ const abiMismatch = message.includes("NODE_MODULE_VERSION") || message.includes("was compiled against a different Node.js version");
143105
+ this.log.error(
143106
+ `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})`,
143107
+ { error: message, abiMismatch, event: "cache.open_failed" }
143108
+ );
142425
143109
  }
142426
143110
  const warmupScanner = new ConversationScanner();
142427
143111
  this.allScanners.add(warmupScanner);
@@ -142908,17 +143592,17 @@ var StreamerServer = class {
142908
143592
  return this.getScanner();
142909
143593
  }
142910
143594
  findJsonlPath(uuid3) {
142911
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects");
142912
- if (!(0, import_fs23.existsSync)(projectsDir)) return null;
143595
+ const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects");
143596
+ if (!(0, import_fs24.existsSync)(projectsDir)) return null;
142913
143597
  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);
143598
+ for (const dir of (0, import_fs24.readdirSync)(projectsDir)) {
143599
+ const fp = (0, import_path21.join)(projectsDir, dir, filename);
143600
+ if ((0, import_fs24.existsSync)(fp)) return fp;
143601
+ const projectDir = (0, import_path21.join)(projectsDir, dir);
142918
143602
  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;
143603
+ for (const sub of (0, import_fs24.readdirSync)(projectDir)) {
143604
+ const subagentPath = (0, import_path21.join)(projectDir, sub, "subagents", filename);
143605
+ if ((0, import_fs24.existsSync)(subagentPath)) return subagentPath;
142922
143606
  }
142923
143607
  } catch {
142924
143608
  }
@@ -142927,7 +143611,7 @@ var StreamerServer = class {
142927
143611
  }
142928
143612
  async readCwdFromJsonl(filePath) {
142929
143613
  return new Promise((resolve6) => {
142930
- const rl = (0, import_readline4.createInterface)({ input: (0, import_fs23.createReadStream)(filePath), crlfDelay: Infinity });
143614
+ const rl = (0, import_readline4.createInterface)({ input: (0, import_fs24.createReadStream)(filePath), crlfDelay: Infinity });
142931
143615
  let found = false;
142932
143616
  rl.on("line", (line) => {
142933
143617
  if (found) return;
@@ -142984,7 +143668,7 @@ var StreamerServer = class {
142984
143668
  if (!conv.filePath) return false;
142985
143669
  let mtimeMs = null;
142986
143670
  try {
142987
- mtimeMs = (0, import_fs23.statSync)(conv.filePath).mtimeMs;
143671
+ mtimeMs = (0, import_fs24.statSync)(conv.filePath).mtimeMs;
142988
143672
  } catch {
142989
143673
  return false;
142990
143674
  }
@@ -143227,7 +143911,7 @@ var StreamerServer = class {
143227
143911
  handleGetSession(sessionId, res) {
143228
143912
  const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
143229
143913
  if (session) {
143230
- if (!(0, import_fs23.existsSync)(session.projectPath)) {
143914
+ if (!(0, import_fs24.existsSync)(session.projectPath)) {
143231
143915
  session.failureReason = `Project directory not found: ${session.projectPath}`;
143232
143916
  }
143233
143917
  json2(res, 200, session);
@@ -143267,7 +143951,10 @@ var StreamerServer = class {
143267
143951
  json2(res, 400, { error: "Could not determine project path" });
143268
143952
  return;
143269
143953
  }
143954
+ const cachedConvMeta = this.cache?.getMetaById(sessionId);
143955
+ const provider = conv?.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER2;
143270
143956
  const session = await this.ptyManager.start(sessionId, {
143957
+ provider,
143271
143958
  projectPath,
143272
143959
  projectName: body.projectName,
143273
143960
  branch: body.branch
@@ -143617,7 +144304,7 @@ var StreamerServer = class {
143617
144304
  sessionStore: this.sessionStore,
143618
144305
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
143619
144306
  agentClient: this.agentClient,
143620
- conversationsDir: this.cacheDir ? (0, import_path19.join)((0, import_path19.dirname)(this.cacheDir), "conversations") : "",
144307
+ conversationsDir: this.cacheDir ? (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations") : "",
143621
144308
  agentConfig: this.agentConfig
143622
144309
  });
143623
144310
  json2(res, result.status, result.body);
@@ -143626,6 +144313,13 @@ var StreamerServer = class {
143626
144313
  }
143627
144314
  return;
143628
144315
  }
144316
+ const body = await readBody(req);
144317
+ const { path: relativePath, provider: requestedProvider, systemPrompt: clientPrompt } = body;
144318
+ if (requestedProvider !== void 0 && !isProviderName(requestedProvider)) {
144319
+ json2(res, 400, { error: "Invalid provider" });
144320
+ return;
144321
+ }
144322
+ const provider = requestedProvider ?? CLAUDE_CODE_PROVIDER2;
143629
144323
  if (!this.browseRoot) {
143630
144324
  json2(res, 403, {
143631
144325
  error: "File browsing not configured. Set browseRoot on the server.",
@@ -143633,8 +144327,6 @@ var StreamerServer = class {
143633
144327
  });
143634
144328
  return;
143635
144329
  }
143636
- const body = await readBody(req);
143637
- const { path: relativePath, systemPrompt: clientPrompt } = body;
143638
144330
  if (typeof relativePath !== "string") {
143639
144331
  json2(res, 400, { error: "Missing path field" });
143640
144332
  return;
@@ -143655,21 +144347,27 @@ var StreamerServer = class {
143655
144347
  ].filter(Boolean);
143656
144348
  try {
143657
144349
  const session = await this.ptyManager.startFresh({
144350
+ provider,
143658
144351
  projectPath: resolvedPath,
143659
144352
  projectName: body.projectName,
143660
144353
  systemPrompt: systemPromptParts.join("\n")
143661
144354
  });
143662
144355
  this.sessionStore.addManaged(session);
143663
144356
  json2(res, 202, { id: session.id, status: "pending" });
143664
- this.watchForJsonl(session.id, resolvedPath);
144357
+ if (provider === CODEX_CLI_PROVIDER2) {
144358
+ this.watchForCodexRollout(session.id, resolvedPath);
144359
+ } else {
144360
+ this.watchForJsonl(session.id, resolvedPath);
144361
+ }
143665
144362
  this.broadcastOrUnicastSessionList(req);
143666
144363
  } catch (err) {
143667
144364
  const message = err instanceof Error ? err.message : "Failed to start session";
144365
+ const statusCode = typeof err.statusCode === "number" ? err.statusCode : 500;
143668
144366
  this.log.error(`[start] failed to start session: ${message}`, {
143669
144367
  event: "session.start_failed",
143670
144368
  error: message
143671
144369
  });
143672
- json2(res, 500, { error: message });
144370
+ json2(res, statusCode, { error: message });
143673
144371
  }
143674
144372
  }
143675
144373
  // ─── Project linking ─────────────────────────────────────────────
@@ -143722,9 +144420,9 @@ var StreamerServer = class {
143722
144420
  // was passed to Claude via --session-id so the filename matches from the start.
143723
144421
  watchForJsonl(sessionId, projectPath) {
143724
144422
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
143725
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
144423
+ const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
143726
144424
  const expectedFile = `${sessionId}.jsonl`;
143727
- const filePath = (0, import_path19.join)(projectsDir, expectedFile);
144425
+ const filePath = (0, import_path21.join)(projectsDir, expectedFile);
143728
144426
  const deadline = Date.now() + 12e4;
143729
144427
  let watcher = null;
143730
144428
  const cleanup = () => {
@@ -143742,12 +144440,12 @@ var StreamerServer = class {
143742
144440
  cleanup();
143743
144441
  return;
143744
144442
  }
143745
- let resolvedFilePath = (0, import_fs23.existsSync)(filePath) ? filePath : null;
143746
- if (!resolvedFilePath && (0, import_fs23.existsSync)(projectsDir)) {
144443
+ let resolvedFilePath = (0, import_fs24.existsSync)(filePath) ? filePath : null;
144444
+ if (!resolvedFilePath && (0, import_fs24.existsSync)(projectsDir)) {
143747
144445
  try {
143748
144446
  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);
144447
+ 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];
144448
+ if (recent) resolvedFilePath = (0, import_path21.join)(projectsDir, recent.f);
143751
144449
  } catch {
143752
144450
  }
143753
144451
  }
@@ -143756,7 +144454,7 @@ var StreamerServer = class {
143756
144454
  this.sessionFileMap.set(sessionId, resolvedFilePath);
143757
144455
  this.fileWatcher.watch(resolvedFilePath);
143758
144456
  try {
143759
- const existing = (0, import_fs23.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
144457
+ const existing = (0, import_fs24.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
143760
144458
  if (existing.length > 0) {
143761
144459
  this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
143762
144460
  for (const line of existing) {
@@ -143782,11 +144480,126 @@ var StreamerServer = class {
143782
144480
  if (this.sessionFileMap.has(sessionId)) return;
143783
144481
  try {
143784
144482
  require("fs").mkdirSync(projectsDir, { recursive: true });
143785
- watcher = (0, import_fs23.watch)(projectsDir, tryWire);
144483
+ watcher = (0, import_fs24.watch)(projectsDir, tryWire);
143786
144484
  watcher.on("error", cleanup);
143787
144485
  } catch {
143788
144486
  }
143789
144487
  }
144488
+ // Codex-equivalent of watchForJsonl(). Differs because Codex has no
144489
+ // filename-encoded session id (it assigns its own persisted id) and its
144490
+ // rollout files live under a date-nested directory
144491
+ // (~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl) that Codex creates
144492
+ // itself — it may not exist yet when this function is first called, so we
144493
+ // poll rather than fs.watch a not-yet-existent directory. Per Phase 0
144494
+ // findings, the rollout file appears within ~1s of process spawn (after
144495
+ // any directory-trust gate is cleared), well before any user input.
144496
+ watchForCodexRollout(sessionId, projectPath) {
144497
+ const deadline = Date.now() + 12e4;
144498
+ const now = /* @__PURE__ */ new Date();
144499
+ const dateDir = (0, import_path21.join)(
144500
+ String(now.getFullYear()),
144501
+ String(now.getMonth() + 1).padStart(2, "0"),
144502
+ String(now.getDate()).padStart(2, "0")
144503
+ );
144504
+ const sessionStartedAtMs = (this.sessionStore.getManaged(sessionId)?.startedAt?.getTime() ?? Date.now()) - 5e3;
144505
+ let intervalHandle = null;
144506
+ const cleanup = () => {
144507
+ if (intervalHandle) clearInterval(intervalHandle);
144508
+ intervalHandle = null;
144509
+ };
144510
+ const matchesProjectPath = (candidatePath) => {
144511
+ try {
144512
+ const firstLine = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n", 1)[0];
144513
+ if (!firstLine) return null;
144514
+ const parsed = JSON.parse(firstLine);
144515
+ if (parsed?.type !== "session_meta") return null;
144516
+ const payload = parsed.payload ?? {};
144517
+ if (payload.cwd !== projectPath) return null;
144518
+ if (typeof payload.id !== "string") return null;
144519
+ const createdIso = payload.timestamp ?? parsed.timestamp;
144520
+ const createdAtMs = typeof createdIso === "string" ? Date.parse(createdIso) : Number.NaN;
144521
+ if (Number.isNaN(createdAtMs) || createdAtMs < sessionStartedAtMs) return null;
144522
+ return { id: payload.id, createdAtMs };
144523
+ } catch {
144524
+ return null;
144525
+ }
144526
+ };
144527
+ const tryWire = () => {
144528
+ if (!this.ptyManager.hasSession(sessionId)) {
144529
+ cleanup();
144530
+ return;
144531
+ }
144532
+ if (Date.now() > deadline) {
144533
+ cleanup();
144534
+ return;
144535
+ }
144536
+ const boundElsewhere = new Set(
144537
+ this.sessionStore.listManaged().filter((s3) => s3.id !== sessionId && s3.boundConversationId != null).map((s3) => s3.boundConversationId)
144538
+ );
144539
+ for (const root of this.codexRoots) {
144540
+ const sessionsDir = (0, import_path21.join)(root, dateDir);
144541
+ if (!(0, import_fs24.existsSync)(sessionsDir)) continue;
144542
+ let candidateFiles;
144543
+ try {
144544
+ candidateFiles = (0, import_fs24.readdirSync)(sessionsDir).filter((f2) => f2.endsWith(".jsonl"));
144545
+ } catch {
144546
+ continue;
144547
+ }
144548
+ const nowMs = Date.now();
144549
+ 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);
144550
+ for (const { f: f2 } of recentCandidates) {
144551
+ const candidatePath = (0, import_path21.join)(sessionsDir, f2);
144552
+ const match2 = matchesProjectPath(candidatePath);
144553
+ if (!match2) continue;
144554
+ if (boundElsewhere.has(match2.id)) continue;
144555
+ const codexSessionId = match2.id;
144556
+ cleanup();
144557
+ this.sessionStore.updateManaged(sessionId, { boundConversationId: codexSessionId });
144558
+ this.sessionFileMap.set(sessionId, candidatePath);
144559
+ this.fileWatcher.watch(candidatePath);
144560
+ try {
144561
+ const existing = (0, import_fs24.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
144562
+ if (existing.length > 0) {
144563
+ this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
144564
+ for (const line of existing) {
144565
+ this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
144566
+ }
144567
+ }
144568
+ } catch {
144569
+ }
144570
+ if (this.scannerReady) {
144571
+ this.scannerStale = true;
144572
+ } else {
144573
+ this.scanner = null;
144574
+ }
144575
+ this.linkSessionToProject(sessionId, projectPath, candidatePath);
144576
+ this.cache?.markAsStreamer(sessionId);
144577
+ const resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());
144578
+ if (resp) {
144579
+ this.wsHub.broadcast({ type: "session_update", session: resp });
144580
+ }
144581
+ this.log.info(
144582
+ `[startFresh] bound Codex rollout for ${sessionId}`,
144583
+ {
144584
+ event: "session.codex_rollout_bound",
144585
+ sessionId,
144586
+ boundConversationId: codexSessionId,
144587
+ filePath: candidatePath
144588
+ },
144589
+ "pino"
144590
+ );
144591
+ return;
144592
+ }
144593
+ }
144594
+ };
144595
+ tryWire();
144596
+ if (!intervalHandle && Date.now() <= deadline) {
144597
+ const alreadyBound = this.sessionStore.getManaged(sessionId)?.boundConversationId != null;
144598
+ if (!alreadyBound) {
144599
+ intervalHandle = setInterval(tryWire, 250);
144600
+ }
144601
+ }
144602
+ }
143790
144603
  async handleBrowse(url2, res) {
143791
144604
  if (!this.browseRoot) {
143792
144605
  json2(res, 403, {
@@ -143870,7 +144683,7 @@ var StreamerServer = class {
143870
144683
  };
143871
144684
  function classifyResumability(cwd) {
143872
144685
  if (!cwd) return { resumable: true };
143873
- if ((0, import_fs23.existsSync)(cwd)) return { resumable: true };
144686
+ if ((0, import_fs24.existsSync)(cwd)) return { resumable: true };
143874
144687
  const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
143875
144688
  return {
143876
144689
  resumable: false,
@@ -144450,13 +145263,13 @@ var import_node_fs17 = require("fs");
144450
145263
 
144451
145264
  // node_modules/tar/dist/esm/index.min.js
144452
145265
  var import_events4 = __toESM(require("events"), 1);
144453
- var import_fs24 = __toESM(require("fs"), 1);
145266
+ var import_fs25 = __toESM(require("fs"), 1);
144454
145267
  var import_node_events3 = require("events");
144455
145268
  var import_node_stream3 = __toESM(require("stream"), 1);
144456
145269
  var import_node_string_decoder = require("string_decoder");
144457
145270
  var import_node_path14 = __toESM(require("path"), 1);
144458
145271
  var import_node_fs11 = __toESM(require("fs"), 1);
144459
- var import_path20 = require("path");
145272
+ var import_path22 = require("path");
144460
145273
  var import_events5 = require("events");
144461
145274
  var import_assert = __toESM(require("assert"), 1);
144462
145275
  var import_buffer = require("buffer");
@@ -144464,17 +145277,17 @@ var Ps = __toESM(require("zlib"), 1);
144464
145277
  var import_zlib = __toESM(require("zlib"), 1);
144465
145278
  var import_node_path15 = require("path");
144466
145279
  var import_node_path16 = require("path");
144467
- var import_fs25 = __toESM(require("fs"), 1);
144468
145280
  var import_fs26 = __toESM(require("fs"), 1);
144469
- var import_path21 = __toESM(require("path"), 1);
145281
+ var import_fs27 = __toESM(require("fs"), 1);
145282
+ var import_path23 = __toESM(require("path"), 1);
144470
145283
  var import_node_path17 = require("path");
144471
- var import_path22 = __toESM(require("path"), 1);
145284
+ var import_path24 = __toESM(require("path"), 1);
144472
145285
  var import_node_fs12 = __toESM(require("fs"), 1);
144473
145286
  var import_node_assert = __toESM(require("assert"), 1);
144474
145287
  var import_node_crypto5 = require("crypto");
144475
145288
  var import_node_fs13 = __toESM(require("fs"), 1);
144476
145289
  var import_node_path18 = __toESM(require("path"), 1);
144477
- var import_fs27 = __toESM(require("fs"), 1);
145290
+ var import_fs28 = __toESM(require("fs"), 1);
144478
145291
  var import_node_fs14 = __toESM(require("fs"), 1);
144479
145292
  var import_node_path19 = __toESM(require("path"), 1);
144480
145293
  var import_node_fs15 = __toESM(require("fs"), 1);
@@ -144826,7 +145639,7 @@ var A = class extends import_node_events3.EventEmitter {
144826
145639
  return Wr;
144827
145640
  }
144828
145641
  };
144829
- var Jr = import_fs24.default.writev;
145642
+ var Jr = import_fs25.default.writev;
144830
145643
  var ht = /* @__PURE__ */ Symbol("_autoClose");
144831
145644
  var H = /* @__PURE__ */ Symbol("_close");
144832
145645
  var te = /* @__PURE__ */ Symbol("_ended");
@@ -144881,7 +145694,7 @@ var _t = class extends A {
144881
145694
  throw new TypeError("this is a readable stream");
144882
145695
  }
144883
145696
  [at]() {
144884
- import_fs24.default.open(this[U2], "r", (t2, e) => this[Ht](t2, e));
145697
+ import_fs25.default.open(this[U2], "r", (t2, e) => this[Ht](t2, e));
144885
145698
  }
144886
145699
  [Ht](t2, e) {
144887
145700
  t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[zt]());
@@ -144894,7 +145707,7 @@ var _t = class extends A {
144894
145707
  this[j] = true;
144895
145708
  let t2 = this[vi]();
144896
145709
  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));
145710
+ import_fs25.default.read(this[u2], t2, 0, t2.length, null, (e, i, r) => this[Ii](e, i, r));
144898
145711
  }
144899
145712
  }
144900
145713
  [Ii](t2, e, i) {
@@ -144903,7 +145716,7 @@ var _t = class extends A {
144903
145716
  [H]() {
144904
145717
  if (this[ht] && typeof this[u2] == "number") {
144905
145718
  let t2 = this[u2];
144906
- this[u2] = void 0, import_fs24.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145719
+ this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
144907
145720
  }
144908
145721
  }
144909
145722
  [Ut](t2) {
@@ -144931,7 +145744,7 @@ var Be = class extends _t {
144931
145744
  [at]() {
144932
145745
  let t2 = true;
144933
145746
  try {
144934
- this[Ht](null, import_fs24.default.openSync(this[U2], "r")), t2 = false;
145747
+ this[Ht](null, import_fs25.default.openSync(this[U2], "r")), t2 = false;
144935
145748
  } finally {
144936
145749
  t2 && this[H]();
144937
145750
  }
@@ -144942,7 +145755,7 @@ var Be = class extends _t {
144942
145755
  if (!this[j]) {
144943
145756
  this[j] = true;
144944
145757
  do {
144945
- let e = this[vi](), i = e.length === 0 ? 0 : import_fs24.default.readSync(this[u2], e, 0, e.length, null);
145758
+ let e = this[vi](), i = e.length === 0 ? 0 : import_fs25.default.readSync(this[u2], e, 0, e.length, null);
144946
145759
  if (!this[ki](i, e)) break;
144947
145760
  } while (true);
144948
145761
  this[j] = false;
@@ -144955,7 +145768,7 @@ var Be = class extends _t {
144955
145768
  [H]() {
144956
145769
  if (this[ht] && typeof this[u2] == "number") {
144957
145770
  let t2 = this[u2];
144958
- this[u2] = void 0, import_fs24.default.closeSync(t2), this.emit("close");
145771
+ this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
144959
145772
  }
144960
145773
  }
144961
145774
  };
@@ -144997,7 +145810,7 @@ var et = class extends import_events4.default {
144997
145810
  this[H](), this[gt] = true, this.emit("error", t2);
144998
145811
  }
144999
145812
  [at]() {
145000
- import_fs24.default.open(this[U2], this[tt], this[ie], (t2, e) => this[Ht](t2, e));
145813
+ import_fs25.default.open(this[U2], this[tt], this[ie], (t2, e) => this[Ht](t2, e));
145001
145814
  }
145002
145815
  [Ht](t2, e) {
145003
145816
  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 +145822,7 @@ var et = class extends import_events4.default {
145009
145822
  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
145823
  }
145011
145824
  [ve](t2) {
145012
- import_fs24.default.write(this[u2], t2, 0, t2.length, this[ot], (e, i) => this[Pt](e, i));
145825
+ import_fs25.default.write(this[u2], t2, 0, t2.length, this[ot], (e, i) => this[Pt](e, i));
145013
145826
  }
145014
145827
  [Pt](t2, e) {
145015
145828
  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 +145838,7 @@ var et = class extends import_events4.default {
145025
145838
  [H]() {
145026
145839
  if (this[ht] && typeof this[u2] == "number") {
145027
145840
  let t2 = this[u2];
145028
- this[u2] = void 0, import_fs24.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145841
+ this[u2] = void 0, import_fs25.default.close(t2, (e) => e ? this.emit("error", e) : this.emit("close"));
145029
145842
  }
145030
145843
  }
145031
145844
  };
@@ -145033,24 +145846,24 @@ var Wt = class extends et {
145033
145846
  [at]() {
145034
145847
  let t2;
145035
145848
  if (this[Me] && this[tt] === "r+") try {
145036
- t2 = import_fs24.default.openSync(this[U2], this[tt], this[ie]);
145849
+ t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
145037
145850
  } catch (e) {
145038
145851
  if (e?.code === "ENOENT") return this[tt] = "w", this[at]();
145039
145852
  throw e;
145040
145853
  }
145041
- else t2 = import_fs24.default.openSync(this[U2], this[tt], this[ie]);
145854
+ else t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
145042
145855
  this[Ht](null, t2);
145043
145856
  }
145044
145857
  [H]() {
145045
145858
  if (this[ht] && typeof this[u2] == "number") {
145046
145859
  let t2 = this[u2];
145047
- this[u2] = void 0, import_fs24.default.closeSync(t2), this.emit("close");
145860
+ this[u2] = void 0, import_fs25.default.closeSync(t2), this.emit("close");
145048
145861
  }
145049
145862
  }
145050
145863
  [ve](t2) {
145051
145864
  let e = true;
145052
145865
  try {
145053
- this[Pt](null, import_fs24.default.writeSync(this[u2], t2, 0, t2.length, this[ot])), e = false;
145866
+ this[Pt](null, import_fs25.default.writeSync(this[u2], t2, 0, t2.length, this[ot])), e = false;
145054
145867
  } finally {
145055
145868
  if (e) try {
145056
145869
  this[H]();
@@ -145833,11 +146646,11 @@ var vn = (s3) => {
145833
146646
  };
145834
146647
  var Qi = (s3, t2) => {
145835
146648
  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;
146649
+ let h = o || (0, import_path22.parse)(n).root || ".", a;
145837
146650
  if (n === h) a = false;
145838
146651
  else {
145839
146652
  let l = e.get(n);
145840
- a = l !== void 0 ? l : r((0, import_path20.dirname)(n), h);
146653
+ a = l !== void 0 ? l : r((0, import_path22.dirname)(n), h);
145841
146654
  }
145842
146655
  return e.set(n, a), a;
145843
146656
  };
@@ -145957,7 +146770,7 @@ var de = class extends A {
145957
146770
  let [o, h] = ce(this.path);
145958
146771
  o && typeof h == "string" && (this.path = h, r = o);
145959
146772
  }
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 });
146773
+ 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
146774
  let n = this.statCache.get(this.absolute);
145962
146775
  n ? this[si](n) : this[ss]();
145963
146776
  }
@@ -145968,7 +146781,7 @@ var de = class extends A {
145968
146781
  return t2 === "error" && (this.#t = true), super.emit(t2, ...e);
145969
146782
  }
145970
146783
  [ss]() {
145971
- import_fs26.default.lstat(this.absolute, (t2, e) => {
146784
+ import_fs27.default.lstat(this.absolute, (t2, e) => {
145972
146785
  if (t2) return this.emit("error", t2);
145973
146786
  this[si](e);
145974
146787
  });
@@ -146006,7 +146819,7 @@ var de = class extends A {
146006
146819
  this.path.slice(-1) !== "/" && (this.path += "/"), this.stat.size = 0, this[fe](), this.end();
146007
146820
  }
146008
146821
  [is]() {
146009
- import_fs26.default.readlink(this.absolute, (t2, e) => {
146822
+ import_fs27.default.readlink(this.absolute, (t2, e) => {
146010
146823
  if (t2) return this.emit("error", t2);
146011
146824
  this[ns](e);
146012
146825
  });
@@ -146016,7 +146829,7 @@ var de = class extends A {
146016
146829
  }
146017
146830
  [sr](t2) {
146018
146831
  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();
146832
+ this.type = "Link", this.linkpath = f(import_path23.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
146020
146833
  }
146021
146834
  [er]() {
146022
146835
  if (!this.stat) throw new Error("cannot create file entry without stat");
@@ -146029,7 +146842,7 @@ var de = class extends A {
146029
146842
  this[os]();
146030
146843
  }
146031
146844
  [os]() {
146032
- import_fs26.default.open(this.absolute, "r", (t2, e) => {
146845
+ import_fs27.default.open(this.absolute, "r", (t2, e) => {
146033
146846
  if (t2) return this.emit("error", t2);
146034
146847
  this[hs](e);
146035
146848
  });
@@ -146044,14 +146857,14 @@ var de = class extends A {
146044
146857
  [ii]() {
146045
146858
  let { fd: t2, buf: e, offset: i, length: r, pos: n } = this;
146046
146859
  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) => {
146860
+ import_fs27.default.read(t2, e, i, r, n, (o, h) => {
146048
146861
  if (o) return this[pt](() => this.emit("error", o));
146049
146862
  this[rs](h);
146050
146863
  });
146051
146864
  }
146052
146865
  [pt](t2 = () => {
146053
146866
  }) {
146054
- this.fd !== void 0 && import_fs26.default.close(this.fd, t2);
146867
+ this.fd !== void 0 && import_fs27.default.close(this.fd, t2);
146055
146868
  }
146056
146869
  [rs](t2) {
146057
146870
  if (t2 <= 0 && this.remain > 0) {
@@ -146086,20 +146899,20 @@ var de = class extends A {
146086
146899
  var ni = class extends de {
146087
146900
  sync = true;
146088
146901
  [ss]() {
146089
- this[si](import_fs26.default.lstatSync(this.absolute));
146902
+ this[si](import_fs27.default.lstatSync(this.absolute));
146090
146903
  }
146091
146904
  [is]() {
146092
- this[ns](import_fs26.default.readlinkSync(this.absolute));
146905
+ this[ns](import_fs27.default.readlinkSync(this.absolute));
146093
146906
  }
146094
146907
  [os]() {
146095
- this[hs](import_fs26.default.openSync(this.absolute, "r"));
146908
+ this[hs](import_fs27.default.openSync(this.absolute, "r"));
146096
146909
  }
146097
146910
  [ii]() {
146098
146911
  let t2 = true;
146099
146912
  try {
146100
146913
  let { fd: e, buf: i, offset: r, length: n, pos: o } = this;
146101
146914
  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);
146915
+ let h = import_fs27.default.readSync(e, i, r, n, o);
146103
146916
  this[rs](h), t2 = false;
146104
146917
  } finally {
146105
146918
  if (t2) try {
@@ -146114,7 +146927,7 @@ var ni = class extends de {
146114
146927
  }
146115
146928
  [pt](t2 = () => {
146116
146929
  }) {
146117
- this.fd !== void 0 && import_fs26.default.closeSync(this.fd), t2();
146930
+ this.fd !== void 0 && import_fs27.default.closeSync(this.fd), t2();
146118
146931
  }
146119
146932
  };
146120
146933
  var oi = class extends A {
@@ -146430,7 +147243,7 @@ var wt = class extends A {
146430
147243
  return typeof t2 == "string" ? this[ci](t2) : this[or](t2), this.flowing;
146431
147244
  }
146432
147245
  [or](t2) {
146433
- let e = f(import_path22.default.resolve(this.cwd, t2.path));
147246
+ let e = f(import_path24.default.resolve(this.cwd, t2.path));
146434
147247
  if (!this.filter(t2.path, t2)) t2.resume();
146435
147248
  else {
146436
147249
  let i = new pi(t2.path, e);
@@ -146439,13 +147252,13 @@ var wt = class extends A {
146439
147252
  this[Ft]();
146440
147253
  }
146441
147254
  [ci](t2) {
146442
- let e = f(import_path22.default.resolve(this.cwd, t2));
147255
+ let e = f(import_path24.default.resolve(this.cwd, t2));
146443
147256
  this[W].push(new pi(t2, e)), this[Ft]();
146444
147257
  }
146445
147258
  [ds](t2) {
146446
147259
  t2.pending = true, this[G2] += 1;
146447
147260
  let e = this.follow ? "stat" : "lstat";
146448
- import_fs25.default[e](t2.absolute, (i, r) => {
147261
+ import_fs26.default[e](t2.absolute, (i, r) => {
146449
147262
  t2.pending = false, this[G2] -= 1, i ? this.emit("error", i) : this[li](t2, r);
146450
147263
  });
146451
147264
  }
@@ -146459,7 +147272,7 @@ var wt = class extends A {
146459
147272
  this[Ft]();
146460
147273
  }
146461
147274
  [ms](t2) {
146462
- t2.pending = true, this[G2] += 1, import_fs25.default.readdir(t2.absolute, (e, i) => {
147275
+ t2.pending = true, this[G2] += 1, import_fs26.default.readdir(t2.absolute, (e, i) => {
146463
147276
  if (t2.pending = false, this[G2] -= 1, e) return this.emit("error", e);
146464
147277
  this[fi](t2, i);
146465
147278
  });
@@ -146560,10 +147373,10 @@ var kt = class extends wt {
146560
147373
  }
146561
147374
  [ds](t2) {
146562
147375
  let e = this.follow ? "statSync" : "lstatSync";
146563
- this[li](t2, import_fs25.default[e](t2.absolute));
147376
+ this[li](t2, import_fs26.default[e](t2.absolute));
146564
147377
  }
146565
147378
  [ms](t2) {
146566
- this[fi](t2, import_fs25.default.readdirSync(t2.absolute));
147379
+ this[fi](t2, import_fs26.default.readdirSync(t2.absolute));
146567
147380
  }
146568
147381
  [di](t2) {
146569
147382
  let e = t2.entry, i = this.zip;
@@ -146614,8 +147427,8 @@ var Qn = K2(Vn, $n, Xn, qn, (s3, t2) => {
146614
147427
  });
146615
147428
  var Jn = process.env.__FAKE_PLATFORM__ || process.platform;
146616
147429
  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;
147430
+ var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs28.default.constants;
147431
+ var Rr = Number(process.env.__FAKE_FS_O_FILENAME__) || import_fs28.default.constants.UV_FS_O_FILEMAP || 0;
146619
147432
  var jn = Er && !!Rr;
146620
147433
  var to = 512 * 1024;
146621
147434
  var eo = Rr | Sr | wr | yr;
@@ -147739,6 +148552,13 @@ program2.command("serve").description("Start the streamer server").option("-p, -
147739
148552
  "Run in multi-agent mode (PTY mode unreachable in this process)",
147740
148553
  false
147741
148554
  ).action(async (opts) => {
148555
+ try {
148556
+ const { checkSqliteAbi: checkSqliteAbi2 } = await Promise.resolve().then(() => (init_check_sqlite_abi(), check_sqlite_abi_exports));
148557
+ checkSqliteAbi2();
148558
+ } catch (err) {
148559
+ log7.error(err instanceof Error ? err.message : String(err), void 0, "console");
148560
+ process.exit(1);
148561
+ }
147742
148562
  if (opts.multiAgentFlow) {
147743
148563
  process.env.MULTI_AGENT_FLOW = "true";
147744
148564
  }
@@ -147836,12 +148656,12 @@ program2.command("cache").description("Manage the local SQLite conversation cach
147836
148656
  "Cache directory (default: ~/.threadbase/cache)",
147837
148657
  `${process.env.HOME}/.threadbase/cache`
147838
148658
  ).action((opts) => {
147839
- const { rmSync: rmSync6, existsSync: existsSync11 } = require("fs");
148659
+ const { rmSync: rmSync6, existsSync: existsSync12 } = require("fs");
147840
148660
  const { join: join26 } = require("path");
147841
148661
  const dbPath = join26(opts.cacheDir, "cache.db");
147842
148662
  for (const suffix of ["", "-shm", "-wal"]) {
147843
148663
  const f2 = dbPath + suffix;
147844
- if (existsSync11(f2)) {
148664
+ if (existsSync12(f2)) {
147845
148665
  rmSync6(f2);
147846
148666
  log7.info(`Deleted ${f2}`, { path: f2 }, "console");
147847
148667
  }