@threadbase-sh/streamer 1.23.0 → 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
 
@@ -137289,7 +137335,10 @@ var ConversationCache = class _ConversationCache {
137289
137335
  model = excluded.model,
137290
137336
  account = excluded.account,
137291
137337
  branch = excluded.branch,
137292
- message_count = excluded.message_count,
137338
+ -- message_count is incremented by live tailing but recounted from
137339
+ -- scratch by a scanner rescan; a stale rescan must not carry a live
137340
+ -- session's count backwards, so take the max instead of overwriting.
137341
+ message_count = MAX(conversation_meta.message_count, excluded.message_count),
137293
137342
  last_activity = excluded.last_activity,
137294
137343
  first_message = excluded.first_message,
137295
137344
  last_message = excluded.last_message,
@@ -137566,6 +137615,7 @@ var ConversationCache = class _ConversationCache {
137566
137615
  fileSize = s3.size;
137567
137616
  } catch {
137568
137617
  }
137618
+ const seq = ++this.tailSeq;
137569
137619
  this.stmts.upsertFull.run({
137570
137620
  id,
137571
137621
  file_path: m2.filePath,
@@ -137580,7 +137630,7 @@ var ConversationCache = class _ConversationCache {
137580
137630
  first_message: m2.firstMessage ? JSON.stringify(m2.firstMessage) : null,
137581
137631
  last_message: m2.lastMessage ? JSON.stringify(m2.lastMessage) : null,
137582
137632
  preview: m2.preview ?? null,
137583
- updated_at: 0,
137633
+ updated_at: seq,
137584
137634
  mtime_ms: mtimeMs,
137585
137635
  file_size: fileSize,
137586
137636
  provider: m2.provider ?? CLAUDE_CODE_PROVIDER2
@@ -137797,9 +137847,25 @@ var ConversationCache = class _ConversationCache {
137797
137847
  this.fileIndex.clear();
137798
137848
  }
137799
137849
  }
137800
- invalidateByFilePath(filePath) {
137850
+ /**
137851
+ * Drop the cached row for a file. Two callers with opposite intent:
137852
+ * - a directory-watch "change" event (the file was appended to) — pass
137853
+ * `skipIfTailed: true`. A cached tail means the row is being actively
137854
+ * maintained from the file's real content by the live-tail
137855
+ * (updateFromLines) or warm-up path, fresher than any scanner-derived
137856
+ * view. Both watchers fire on the same append with no ordering guarantee;
137857
+ * without this guard the invalidate can land after the tail write and wipe
137858
+ * the just-cached row, flickering the conversation out of
137859
+ * /api/conversations on nearly every message (CRITICAL #2). The debounced
137860
+ * rescan still re-derives metadata, so skipping the eager drop loses
137861
+ * nothing.
137862
+ * - a genuine unlink (the file is gone) — leave `skipIfTailed` false so the
137863
+ * row is always removed, otherwise a deleted session ghosts in the cache.
137864
+ */
137865
+ invalidateByFilePath(filePath, opts) {
137801
137866
  const row = this.stmts.getIdByFilePath.get(filePath);
137802
137867
  if (!row) return null;
137868
+ if (opts?.skipIfTailed && this.stmts.hasTail.get(row.id)) return null;
137803
137869
  this.invalidate(row.id);
137804
137870
  return row.id;
137805
137871
  }
@@ -138162,67 +138228,15 @@ function handleListProjects(url2, res) {
138162
138228
  res.end(JSON.stringify({ projects: page, total }));
138163
138229
  }
138164
138230
 
138165
- // src/server.ts
138166
- init_logger();
138231
+ // src/live-session-manager.ts
138232
+ var import_path18 = require("path");
138167
138233
 
138168
- // src/pair-store.ts
138234
+ // src/codex-pty-runner.ts
138235
+ var import_headless = __toESM(require_xterm_headless(), 1);
138169
138236
  var import_crypto5 = require("crypto");
138170
- var DEFAULT_TTL_SECONDS = 180;
138171
- var SWEEP_INTERVAL_MS = 6e4;
138172
- var PairTokenStore = class {
138173
- current = null;
138174
- ttlMs;
138175
- sweepTimer = null;
138176
- constructor(opts = {}) {
138177
- this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1e3;
138178
- if (opts.autoSweep !== false) {
138179
- this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
138180
- this.sweepTimer.unref?.();
138181
- }
138182
- }
138183
- mint() {
138184
- const token = `pt_${(0, import_crypto5.randomBytes)(16).toString("hex")}`;
138185
- const expiresAt = Date.now() + this.ttlMs;
138186
- this.current = { token, expiresAt, used: false };
138187
- return {
138188
- token,
138189
- expiresAt,
138190
- expiresInSeconds: Math.floor(this.ttlMs / 1e3)
138191
- };
138192
- }
138193
- consume(token) {
138194
- const record2 = this.current;
138195
- if (!record2 || record2.token !== token) return { ok: false, reason: "unknown" };
138196
- if (Date.now() > record2.expiresAt) {
138197
- this.current = null;
138198
- return { ok: false, reason: "expired" };
138199
- }
138200
- if (record2.used) return { ok: false, reason: "used" };
138201
- record2.used = true;
138202
- return { ok: true };
138203
- }
138204
- peek() {
138205
- return this.current;
138206
- }
138207
- clear() {
138208
- this.current = null;
138209
- }
138210
- sweep() {
138211
- if (this.current && Date.now() > this.current.expiresAt) {
138212
- this.current = null;
138213
- }
138214
- }
138215
- dispose() {
138216
- if (this.sweepTimer) clearInterval(this.sweepTimer);
138217
- this.sweepTimer = null;
138218
- this.current = null;
138219
- }
138220
- };
138221
-
138222
- // src/process-discovery.ts
138223
- var import_child_process2 = require("child_process");
138224
- var import_os9 = require("os");
138237
+ var import_fs20 = require("fs");
138225
138238
  var import_path16 = require("path");
138239
+ init_logger();
138226
138240
 
138227
138241
  // src/platform.ts
138228
138242
  var import_child_process = require("child_process");
@@ -138288,151 +138302,523 @@ function resolveClaudeExe() {
138288
138302
  _claudeExe = "claude";
138289
138303
  return _claudeExe;
138290
138304
  }
138291
-
138292
- // src/process-discovery.ts
138293
- async function discoverClaudeProcesses() {
138294
- if ((0, import_os9.platform)() === "win32") return discoverWindows();
138295
- return discoverUnix();
138296
- }
138297
- async function discoverUnix() {
138298
- const pids = await getPidsUnix();
138299
- const results = await Promise.all(
138300
- pids.map(async (pid) => {
138301
- try {
138302
- const [cwd, args, startedAt] = await Promise.all([
138303
- getProcessCwdUnix(pid),
138304
- getProcessArgsUnix(pid),
138305
- getProcessStartTimeUnix(pid)
138306
- ]);
138307
- const conversationId = extractResumeId(args);
138308
- return {
138309
- pid,
138310
- projectPath: cwd,
138311
- projectName: (0, import_path16.basename)(cwd),
138312
- branch: await readGitBranch2(cwd),
138313
- conversationId,
138314
- startedAt
138315
- };
138316
- } catch {
138317
- 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;
138318
138318
  }
138319
- })
138320
- );
138321
- return results.filter((r) => r !== null);
138322
- }
138323
- async function discoverWindows() {
138324
- const pids = await getPidsWindows();
138325
- const results = await Promise.all(
138326
- pids.map(async (pid) => {
138327
- try {
138328
- const info = await getProcessInfoWindows(pid);
138329
- if (!info) return null;
138330
- return {
138331
- pid,
138332
- projectPath: info.cwd,
138333
- projectName: (0, import_path16.basename)(info.cwd),
138334
- branch: await readGitBranch2(info.cwd),
138335
- conversationId: extractResumeId(info.args),
138336
- startedAt: info.startedAt
138337
- };
138338
- } catch {
138339
- 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;
138340
138334
  }
138341
- })
138342
- );
138343
- return results.filter((r) => r !== null);
138344
- }
138345
- function run(cmd, args, opts = {}) {
138346
- return new Promise((resolve6, reject) => {
138347
- (0, import_child_process2.execFile)(
138348
- cmd,
138349
- args,
138350
- { windowsHide: isWindows2, encoding: "utf-8", timeout: opts.timeout ?? 5e3, cwd: opts.cwd },
138351
- (err, stdout2) => {
138352
- if (err) reject(err);
138353
- 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;
138354
138345
  }
138355
- );
138356
- });
138357
- }
138358
- async function getPidsUnix() {
138359
- try {
138360
- const output = await run("pgrep", ["-x", "claude"]);
138361
- return output.trim().split("\n").filter(Boolean).map((s3) => Number.parseInt(s3, 10));
138362
- } catch {
138363
- 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
+ }
138364
138359
  }
138360
+ _codexExe = "codex";
138361
+ return _codexExe;
138365
138362
  }
138366
- async function getProcessCwdUnix(pid) {
138367
- const output = await run("lsof", ["-p", String(pid), "-a", "-d", "cwd", "-Fn"]);
138368
- const match2 = output.match(/n(.+)/);
138369
- return match2?.[1] ?? "";
138370
- }
138371
- async function getProcessArgsUnix(pid) {
138372
- return (await run("ps", ["-p", String(pid), "-o", "args="])).trim();
138373
- }
138374
- async function getProcessStartTimeUnix(pid) {
138375
- const raw2 = (await run("ps", ["-p", String(pid), "-o", "lstart="])).trim();
138376
- const d = new Date(raw2);
138377
- return Number.isNaN(d.getTime()) ? /* @__PURE__ */ new Date() : d;
138378
- }
138379
- async function getPidsWindows() {
138380
- try {
138381
- const output = await run("tasklist", ["/FI", "IMAGENAME eq claude.exe", "/FO", "CSV", "/NH"]);
138382
- return output.trim().split("\n").filter(Boolean).map((line) => {
138383
- const parts = line.split(",");
138384
- return Number.parseInt(parts[1]?.replace(/"/g, "") ?? "0", 10);
138385
- }).filter((pid) => pid > 0);
138386
- } catch {
138387
- return [];
138388
- }
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)}`;
138389
138377
  }
138390
- async function getProcessInfoWindows(pid) {
138378
+ var pty = null;
138379
+ async function loadPty() {
138380
+ if (pty) return pty;
138391
138381
  try {
138392
- const output = await run("wmic", [
138393
- "process",
138394
- "where",
138395
- `ProcessId=${pid}`,
138396
- "get",
138397
- "CommandLine,CreationDate,ExecutablePath",
138398
- "/FORMAT:CSV"
138399
- ]);
138400
- const lines = output.trim().split(/\r?\n/).filter((l) => l.trim().length > 0);
138401
- if (lines.length < 2) return null;
138402
- const parts = lines[1].split(",");
138403
- const args = parts[1] ?? "";
138404
- const creationDate = parts[2] ?? "";
138405
- const year = creationDate.slice(0, 4);
138406
- const month = creationDate.slice(4, 6);
138407
- const day = creationDate.slice(6, 8);
138408
- const hour = creationDate.slice(8, 10);
138409
- const min = creationDate.slice(10, 12);
138410
- const sec = creationDate.slice(12, 14);
138411
- const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
138412
- if (Number.isNaN(startedAt.getTime())) return null;
138413
- const exePath = parts[3] ?? "";
138414
- const cwd = exePath ? (0, import_path16.dirname)(exePath) : "";
138415
- return { cwd, args, startedAt };
138416
- } catch {
138417
- 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
+ );
138418
138389
  }
138419
138390
  }
138420
- function extractResumeId(args) {
138421
- const match2 = args.match(/--resume\s+(\S+)/);
138422
- 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
+ });
138423
138398
  }
138424
- async function readGitBranch2(dir) {
138425
- try {
138426
- return (await run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir, timeout: 3e3 })).trim();
138427
- } catch {
138428
- 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;
138429
138580
  }
138581
+ // Write the input as plain bytes (no bracketed-paste wrap — Phase 0
138582
+ // confirmed Codex accepts plain keystrokes), then submit \r after a short
138583
+ // delay so Codex's TUI gets an event-loop tick to process the input first.
138584
+ writeSubmit(sessionId, session, input, path2, promptCount) {
138585
+ this.log.info(
138586
+ `[codex.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${input.length} digest=${digestBytes(input)}`,
138587
+ {
138588
+ event: "codex.input_write",
138589
+ sessionId,
138590
+ promptCount,
138591
+ byteLen: input.length,
138592
+ digest: digestBytes(input),
138593
+ path: path2,
138594
+ phase: "input"
138595
+ }
138596
+ );
138597
+ session.process.write(input);
138598
+ setTimeout(() => {
138599
+ const current = this.sessions.get(sessionId);
138600
+ if (!current || current !== session) return;
138601
+ this.log.info(
138602
+ `[codex.input.submit] ${sessionId.slice(0, 8)} promptCount=${promptCount} digest=\\r`,
138603
+ {
138604
+ event: "codex.input_write",
138605
+ sessionId,
138606
+ promptCount,
138607
+ byteLen: SUBMIT_BYTES.length,
138608
+ digest: "\\r",
138609
+ path: path2,
138610
+ phase: "submit"
138611
+ }
138612
+ );
138613
+ current.process.write(SUBMIT_BYTES);
138614
+ }, CODEX_SUBMIT_DELAY_MS);
138615
+ }
138616
+ // Drain any inputs sent while the session was still pendingReady, writing
138617
+ // them in arrival order now that Codex is Ready.
138618
+ flushQueuedInputs(sessionId) {
138619
+ const queue = this.queuedInputs.get(sessionId);
138620
+ if (!queue || queue.length === 0) return;
138621
+ this.queuedInputs.delete(sessionId);
138622
+ const session = this.sessions.get(sessionId);
138623
+ if (!session) return;
138624
+ this.log.info(
138625
+ `[codex.flush] ${sessionId.slice(0, 8)} flushing ${queue.length} queued input(s)`,
138626
+ {
138627
+ event: "codex.flush_queued",
138628
+ sessionId,
138629
+ queueLen: queue.length
138630
+ }
138631
+ );
138632
+ queue.forEach((input, i) => {
138633
+ const writeAt = i * CODEX_SUBMIT_DELAY_MS * 2;
138634
+ if (writeAt === 0) {
138635
+ this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
138636
+ } else {
138637
+ setTimeout(() => {
138638
+ const current = this.sessions.get(sessionId);
138639
+ if (!current || current !== session) return;
138640
+ this.writeSubmit(sessionId, session, input, "flush", session.promptCount);
138641
+ }, writeAt);
138642
+ }
138643
+ });
138644
+ }
138645
+ // SIGINT produces a clean exitCode=0 exit (Phase 0 — confirmed).
138646
+ cancel(sessionId) {
138647
+ const session = this.sessions.get(sessionId);
138648
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138649
+ session.process.kill("SIGINT");
138650
+ }
138651
+ killPid(pid) {
138652
+ try {
138653
+ process.kill(pid, "SIGTERM");
138654
+ } catch {
138655
+ }
138656
+ }
138657
+ // Kill the PTY and mark the session idle. Mirrors PTYManager.putOnHold.
138658
+ putOnHold(sessionId) {
138659
+ const session = this.sessions.get(sessionId);
138660
+ if (!session) return;
138661
+ this.pendingReady.delete(sessionId);
138662
+ this.queuedInputs.delete(sessionId);
138663
+ this.trustGateAnswered.delete(sessionId);
138664
+ try {
138665
+ session.process.kill("SIGINT");
138666
+ } catch {
138667
+ }
138668
+ session.status = "idle";
138669
+ session.completedAt = /* @__PURE__ */ new Date();
138670
+ session.screen.dispose();
138671
+ this.sessions.delete(sessionId);
138672
+ this.onStatusChange?.(toPublicSession(session));
138673
+ }
138674
+ getOutput(sessionId) {
138675
+ const session = this.sessions.get(sessionId);
138676
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138677
+ return session.outputBuffer.toString("utf-8");
138678
+ }
138679
+ // Render the last `maxLines` rows of the session's screen in true on-screen
138680
+ // order — same flush-then-read technique as PTYManager.getOutputLines.
138681
+ async getOutputLines(sessionId, maxLines) {
138682
+ const session = this.sessions.get(sessionId);
138683
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
138684
+ await new Promise((resolve6) => session.screen.write("", () => resolve6()));
138685
+ const buf = session.screen.buffer.active;
138686
+ const lines = [];
138687
+ for (let y2 = 0; y2 < buf.length; y2++) {
138688
+ lines.push(buf.getLine(y2)?.translateToString(true) ?? "");
138689
+ }
138690
+ while (lines.length > 0 && lines[lines.length - 1] === "") {
138691
+ lines.pop();
138692
+ }
138693
+ return lines.slice(-maxLines);
138694
+ }
138695
+ getSession(sessionId) {
138696
+ const session = this.sessions.get(sessionId);
138697
+ return session ? toPublicSession(session) : null;
138698
+ }
138699
+ hasSession(sessionId) {
138700
+ return this.sessions.has(sessionId);
138701
+ }
138702
+ listSessions() {
138703
+ return Array.from(this.sessions.values()).map(toPublicSession);
138704
+ }
138705
+ dispose() {
138706
+ for (const session of this.sessions.values()) {
138707
+ try {
138708
+ session.process.kill();
138709
+ } catch {
138710
+ }
138711
+ session.screen.dispose();
138712
+ }
138713
+ this.sessions.clear();
138714
+ this.pendingReady.clear();
138715
+ this.queuedInputs.clear();
138716
+ this.trustGateAnswered.clear();
138717
+ }
138718
+ handleOutput(sessionId, data) {
138719
+ const session = this.sessions.get(sessionId);
138720
+ if (!session) return;
138721
+ const chunk = Buffer.from(data, "utf-8");
138722
+ session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
138723
+ if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
138724
+ session.outputBuffer = session.outputBuffer.subarray(
138725
+ session.outputBuffer.length - OUTPUT_BUFFER_MAX
138726
+ );
138727
+ }
138728
+ session.screen.write(data);
138729
+ session.lastOutput = stripAnsi(data);
138730
+ this.onOutput?.(sessionId, data);
138731
+ this.detectReady(sessionId, session).catch((err) => {
138732
+ this.log.warn("[codex.ready_detect] failed", {
138733
+ event: "codex.ready_detect_failed",
138734
+ sessionId,
138735
+ err
138736
+ });
138737
+ });
138738
+ }
138739
+ // Renders the session's headless screen and checks for the directory-trust
138740
+ // gate (answered once, debounced) and the "Ready" status-bar text. Only
138741
+ // transitions to waiting_input / fires onReady when the rendered status
138742
+ // line literally contains "Ready" — `›` alone (visible during "Starting")
138743
+ // is NOT a valid readiness signal (Phase 0).
138744
+ async detectReady(sessionId, session) {
138745
+ if (session.status !== "running" || !this.pendingReady.has(sessionId)) return;
138746
+ const lines = await this.getOutputLines(sessionId, PTY_ROWS);
138747
+ const screenText = lines.join("\n");
138748
+ if (CODEX_TRUST_GATE_REGEX.test(screenText)) {
138749
+ if (!this.trustGateAnswered.has(sessionId)) {
138750
+ this.trustGateAnswered.add(sessionId);
138751
+ this.log.info(`[codex.trust_gate] ${sessionId.slice(0, 8)} auto-answering`, {
138752
+ event: "codex.trust_gate",
138753
+ sessionId
138754
+ });
138755
+ session.process.write("\r");
138756
+ }
138757
+ return;
138758
+ }
138759
+ const lastNonBlank2 = [...lines].reverse().find((l) => l.trim() !== "") ?? "";
138760
+ if (!lastNonBlank2.includes(CODEX_PROMPT_READY_TEXT)) return;
138761
+ this.markReady(sessionId, session);
138762
+ }
138763
+ markReady(sessionId, session) {
138764
+ session.lastActivityAt = /* @__PURE__ */ new Date();
138765
+ session.status = "waiting_input";
138766
+ this.log.info(`[codex.ready] ${sessionId.slice(0, 8)}`, {
138767
+ event: "codex.ready",
138768
+ sessionId
138769
+ });
138770
+ this.onStatusChange?.(toPublicSession(session));
138771
+ if (this.pendingReady.has(sessionId)) {
138772
+ this.pendingReady.delete(sessionId);
138773
+ this.flushQueuedInputs(sessionId);
138774
+ this.onReady?.(toPublicSession(session));
138775
+ }
138776
+ }
138777
+ handleExit(sessionId, exitCode) {
138778
+ const session = this.sessions.get(sessionId);
138779
+ if (!session) return;
138780
+ session.completedAt = /* @__PURE__ */ new Date();
138781
+ session.status = "idle";
138782
+ const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
138783
+ if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
138784
+ if (!(0, import_fs20.existsSync)(session.projectPath)) {
138785
+ session.failureReason = `Project directory not found: ${session.projectPath}`;
138786
+ } else {
138787
+ session.failureReason = `Codex process exited immediately (code ${exitCode}).`;
138788
+ }
138789
+ }
138790
+ this.onStatusChange?.(toPublicSession(session));
138791
+ session.screen.dispose();
138792
+ this.sessions.delete(sessionId);
138793
+ this.queuedInputs.delete(sessionId);
138794
+ this.trustGateAnswered.delete(sessionId);
138795
+ }
138796
+ };
138797
+ function toPublicSession(s3) {
138798
+ return {
138799
+ id: s3.id,
138800
+ provider: s3.provider ?? CODEX_CLI_PROVIDER2,
138801
+ projectPath: s3.projectPath,
138802
+ projectName: s3.projectName,
138803
+ branch: s3.branch,
138804
+ status: s3.status,
138805
+ startedAt: s3.startedAt,
138806
+ completedAt: s3.completedAt,
138807
+ promptCount: s3.promptCount,
138808
+ lastOutput: s3.lastOutput,
138809
+ ...s3.failureReason != null && { failureReason: s3.failureReason },
138810
+ ...s3.lastActivityAt != null && { lastActivityAt: s3.lastActivityAt },
138811
+ ...s3.filePath != null && { filePath: s3.filePath }
138812
+ };
138813
+ }
138814
+ function stripAnsi(str) {
138815
+ return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
138430
138816
  }
138431
138817
 
138432
138818
  // src/pty-manager.ts
138433
- var import_headless = __toESM(require_xterm_headless(), 1);
138819
+ var import_headless2 = __toESM(require_xterm_headless(), 1);
138434
138820
  var import_crypto6 = require("crypto");
138435
- var import_fs20 = require("fs");
138821
+ var import_fs21 = require("fs");
138436
138822
  var import_path17 = require("path");
138437
138823
  init_logger();
138438
138824
 
@@ -138593,28 +138979,28 @@ function detectShellPrompt(lines) {
138593
138979
  }
138594
138980
 
138595
138981
  // src/pty-manager.ts
138596
- var OUTPUT_BUFFER_MAX = 65536;
138597
- var PTY_COLS = 120;
138598
- var PTY_ROWS = 40;
138599
- 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;
138600
138986
  var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
138601
138987
  var PROMPT_MARKER_FALLBACK_MS = 1e4;
138602
138988
  function buildPasteBytes(input) {
138603
138989
  return `\x1B[200~${input}\x1B[201~`;
138604
138990
  }
138605
- var SUBMIT_BYTES = "\r";
138991
+ var SUBMIT_BYTES2 = "\r";
138606
138992
  var SUBMIT_DELAY_MS = 16;
138607
- function digestBytes(s3) {
138993
+ function digestBytes2(s3) {
138608
138994
  const escaped = s3.replace(new RegExp(String.fromCharCode(27), "g"), "\\x1b").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\t/g, "\\t");
138609
138995
  if (escaped.length <= 200) return escaped;
138610
138996
  return `${escaped.slice(0, 100)}\u2026[${escaped.length - 200}B omitted]\u2026${escaped.slice(-100)}`;
138611
138997
  }
138612
- var pty = null;
138613
- async function loadPty() {
138614
- if (pty) return pty;
138998
+ var pty2 = null;
138999
+ async function loadPty2() {
139000
+ if (pty2) return pty2;
138615
139001
  try {
138616
- pty = await import("node-pty");
138617
- return pty;
139002
+ pty2 = await import("node-pty");
139003
+ return pty2;
138618
139004
  } catch (err) {
138619
139005
  throw new Error(
138620
139006
  `node-pty is required for PTY management but failed to load. Ensure it is installed: npm install node-pty
@@ -138622,11 +139008,11 @@ Original error: ${err}`
138622
139008
  );
138623
139009
  }
138624
139010
  }
138625
- function createScreen() {
138626
- return new import_headless.Terminal({
138627
- cols: PTY_COLS,
138628
- rows: PTY_ROWS,
138629
- scrollback: SCREEN_SCROLLBACK,
139011
+ function createScreen2() {
139012
+ return new import_headless2.Terminal({
139013
+ cols: PTY_COLS2,
139014
+ rows: PTY_ROWS2,
139015
+ scrollback: SCREEN_SCROLLBACK2,
138630
139016
  allowProposedApi: true
138631
139017
  });
138632
139018
  }
@@ -138675,6 +139061,10 @@ var PTYManager = class {
138675
139061
  // to a given input or fell silent. Reset on dispose().
138676
139062
  chunkIndex = /* @__PURE__ */ new Map();
138677
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();
138678
139068
  constructor(options = {}) {
138679
139069
  this.onOutput = options.onOutput;
138680
139070
  this.onStatusChange = options.onStatusChange;
@@ -138697,7 +139087,18 @@ var PTYManager = class {
138697
139087
  // custom-API-key — are cleared by the seeded ~/.claude.json in
138698
139088
  // docker/entrypoint.sh.) startFresh() uses the same flag for the same reason.
138699
139089
  async start(sessionId, options) {
138700
- 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();
138701
139102
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138702
139103
  const proc = nodePty.spawn(
138703
139104
  resolveClaudeExe(),
@@ -138719,6 +139120,7 @@ var PTYManager = class {
138719
139120
  );
138720
139121
  const session = {
138721
139122
  id: sessionId,
139123
+ provider: CLAUDE_CODE_PROVIDER2,
138722
139124
  projectPath: options.projectPath,
138723
139125
  projectName,
138724
139126
  branch: options.branch ?? "",
@@ -138729,7 +139131,7 @@ var PTYManager = class {
138729
139131
  lastOutput: "",
138730
139132
  process: proc,
138731
139133
  outputBuffer: Buffer.alloc(0),
138732
- screen: createScreen()
139134
+ screen: createScreen2()
138733
139135
  };
138734
139136
  this.sessions.set(sessionId, session);
138735
139137
  this.pendingReady.add(sessionId);
@@ -138740,13 +139142,13 @@ var PTYManager = class {
138740
139142
  this.pendingReady.delete(sessionId);
138741
139143
  this.handleExit(sessionId, exitCode);
138742
139144
  });
138743
- return toPublicSession(session);
139145
+ return toPublicSession2(session);
138744
139146
  }
138745
139147
  // Start a brand-new Claude session. A stable UUID is generated here and passed
138746
139148
  // to Claude via --session-id so the JSONL filename matches from the start.
138747
139149
  // onReady fires once Claude reaches its first prompt (waiting_input).
138748
139150
  async startFresh(options) {
138749
- const nodePty = await loadPty();
139151
+ const nodePty = await loadPty2();
138750
139152
  const sessionId = (0, import_crypto6.randomUUID)();
138751
139153
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138752
139154
  const args = [
@@ -138769,6 +139171,7 @@ var PTYManager = class {
138769
139171
  });
138770
139172
  const session = {
138771
139173
  id: sessionId,
139174
+ provider: CLAUDE_CODE_PROVIDER2,
138772
139175
  projectPath: options.projectPath,
138773
139176
  projectName,
138774
139177
  branch: "",
@@ -138779,7 +139182,7 @@ var PTYManager = class {
138779
139182
  lastOutput: "",
138780
139183
  process: proc,
138781
139184
  outputBuffer: Buffer.alloc(0),
138782
- screen: createScreen()
139185
+ screen: createScreen2()
138783
139186
  };
138784
139187
  this.sessions.set(sessionId, session);
138785
139188
  this.pendingReady.add(sessionId);
@@ -138790,7 +139193,7 @@ var PTYManager = class {
138790
139193
  this.pendingReady.delete(sessionId);
138791
139194
  this.handleExit(sessionId, exitCode);
138792
139195
  });
138793
- return toPublicSession(session);
139196
+ return toPublicSession2(session);
138794
139197
  }
138795
139198
  // Write raw key bytes directly to the PTY without bracketed-paste wrapping.
138796
139199
  // Use for control sequences (arrow keys, Enter) that must not be quoted.
@@ -138802,10 +139205,10 @@ var PTYManager = class {
138802
139205
  }
138803
139206
  if (session.status === "waiting_input") {
138804
139207
  session.status = "running";
138805
- this.onStatusChange?.(toPublicSession(session));
139208
+ this.onStatusChange?.(toPublicSession2(session));
138806
139209
  }
138807
139210
  this.log.info(
138808
- `[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)}`,
138809
139212
  { event: "pty.keys_write", sessionId, byteLen: keys.length }
138810
139213
  );
138811
139214
  session.process.write(keys);
@@ -138837,7 +139240,7 @@ var PTYManager = class {
138837
139240
  }
138838
139241
  if (session.status === "waiting_input") {
138839
139242
  session.status = "running";
138840
- this.onStatusChange?.(toPublicSession(session));
139243
+ this.onStatusChange?.(toPublicSession2(session));
138841
139244
  }
138842
139245
  this.writeSubmit(sessionId, session, input, "direct", session.promptCount + 1);
138843
139246
  session.lastActivityAt = /* @__PURE__ */ new Date();
@@ -138850,13 +139253,13 @@ var PTYManager = class {
138850
139253
  writeSubmit(sessionId, session, input, path2, promptCount) {
138851
139254
  const pasteBytes = buildPasteBytes(input);
138852
139255
  this.log.info(
138853
- `[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)}`,
138854
139257
  {
138855
139258
  event: "pty.input_write",
138856
139259
  sessionId,
138857
139260
  promptCount,
138858
139261
  byteLen: pasteBytes.length,
138859
- digest: digestBytes(pasteBytes),
139262
+ digest: digestBytes2(pasteBytes),
138860
139263
  path: path2,
138861
139264
  phase: "paste"
138862
139265
  }
@@ -138871,13 +139274,13 @@ var PTYManager = class {
138871
139274
  event: "pty.input_write",
138872
139275
  sessionId,
138873
139276
  promptCount,
138874
- byteLen: SUBMIT_BYTES.length,
139277
+ byteLen: SUBMIT_BYTES2.length,
138875
139278
  digest: "\\r",
138876
139279
  path: path2,
138877
139280
  phase: "submit"
138878
139281
  }
138879
139282
  );
138880
- current.process.write(SUBMIT_BYTES);
139283
+ current.process.write(SUBMIT_BYTES2);
138881
139284
  }, SUBMIT_DELAY_MS);
138882
139285
  }
138883
139286
  // Drain any inputs that were sent while the session was still pendingReady,
@@ -138935,7 +139338,7 @@ var PTYManager = class {
138935
139338
  session.completedAt = /* @__PURE__ */ new Date();
138936
139339
  session.screen.dispose();
138937
139340
  this.sessions.delete(sessionId);
138938
- this.onStatusChange?.(toPublicSession(session));
139341
+ this.onStatusChange?.(toPublicSession2(session));
138939
139342
  }
138940
139343
  getOutput(sessionId) {
138941
139344
  const session = this.sessions.get(sessionId);
@@ -138966,13 +139369,13 @@ var PTYManager = class {
138966
139369
  }
138967
139370
  getSession(sessionId) {
138968
139371
  const session = this.sessions.get(sessionId);
138969
- return session ? toPublicSession(session) : null;
139372
+ return session ? toPublicSession2(session) : null;
138970
139373
  }
138971
139374
  hasSession(sessionId) {
138972
139375
  return this.sessions.has(sessionId);
138973
139376
  }
138974
139377
  listSessions() {
138975
- return Array.from(this.sessions.values()).map(toPublicSession);
139378
+ return Array.from(this.sessions.values()).map(toPublicSession2);
138976
139379
  }
138977
139380
  dispose() {
138978
139381
  for (const session of this.sessions.values()) {
@@ -139004,7 +139407,7 @@ var PTYManager = class {
139004
139407
  this.lastChunkAt.set(sessionId, now);
139005
139408
  const gapMs = last == null ? 0 : now - last;
139006
139409
  this.log.info(
139007
- `[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)}`,
139008
139411
  {
139009
139412
  event: "pty.chunk",
139010
139413
  sessionId,
@@ -139013,17 +139416,17 @@ var PTYManager = class {
139013
139416
  gapMs,
139014
139417
  status: session.status,
139015
139418
  pendingReady: this.pendingReady.has(sessionId),
139016
- digest: digestBytes(data)
139419
+ digest: digestBytes2(data)
139017
139420
  }
139018
139421
  );
139019
139422
  session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);
139020
- if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {
139423
+ if (session.outputBuffer.length > OUTPUT_BUFFER_MAX2) {
139021
139424
  session.outputBuffer = session.outputBuffer.subarray(
139022
- session.outputBuffer.length - OUTPUT_BUFFER_MAX
139425
+ session.outputBuffer.length - OUTPUT_BUFFER_MAX2
139023
139426
  );
139024
139427
  }
139025
139428
  session.screen.write(data);
139026
- const stripped = stripAnsi(data);
139429
+ const stripped = stripAnsi2(data);
139027
139430
  session.lastOutput = stripped;
139028
139431
  const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m2) => stripped.includes(m2));
139029
139432
  if (session.status === "running" && matchedMarker) {
@@ -139126,11 +139529,11 @@ var PTYManager = class {
139126
139529
  reason,
139127
139530
  elapsedMs
139128
139531
  });
139129
- this.onStatusChange?.(toPublicSession(session));
139532
+ this.onStatusChange?.(toPublicSession2(session));
139130
139533
  if (this.pendingReady.has(sessionId)) {
139131
139534
  this.pendingReady.delete(sessionId);
139132
139535
  this.flushQueuedInputs(sessionId);
139133
- this.onReady?.(toPublicSession(session));
139536
+ this.onReady?.(toPublicSession2(session));
139134
139537
  }
139135
139538
  }
139136
139539
  handleExit(sessionId, exitCode) {
@@ -139140,13 +139543,13 @@ var PTYManager = class {
139140
139543
  session.status = "idle";
139141
139544
  const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();
139142
139545
  if (exitCode !== 0 && elapsedMs < 2e3 && session.lastOutput === "") {
139143
- if (!(0, import_fs20.existsSync)(session.projectPath)) {
139546
+ if (!(0, import_fs21.existsSync)(session.projectPath)) {
139144
139547
  session.failureReason = `Project directory not found: ${session.projectPath}`;
139145
139548
  } else {
139146
139549
  session.failureReason = `Process exited immediately (code ${exitCode}). Check that the Claude binary is installed and accessible.`;
139147
139550
  }
139148
139551
  }
139149
- this.onStatusChange?.(toPublicSession(session));
139552
+ this.onStatusChange?.(toPublicSession2(session));
139150
139553
  session.screen.dispose();
139151
139554
  this.sessions.delete(sessionId);
139152
139555
  this.queuedInputs.delete(sessionId);
@@ -139156,9 +139559,10 @@ var PTYManager = class {
139156
139559
  this.shellPromptOpen.delete(sessionId);
139157
139560
  }
139158
139561
  };
139159
- function toPublicSession(s3) {
139562
+ function toPublicSession2(s3) {
139160
139563
  return {
139161
139564
  id: s3.id,
139565
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
139162
139566
  projectPath: s3.projectPath,
139163
139567
  projectName: s3.projectName,
139164
139568
  branch: s3.branch,
@@ -139172,10 +139576,306 @@ function toPublicSession(s3) {
139172
139576
  ...s3.filePath != null && { filePath: s3.filePath }
139173
139577
  };
139174
139578
  }
139175
- function stripAnsi(str) {
139579
+ function stripAnsi2(str) {
139176
139580
  return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
139177
139581
  }
139178
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
+
139179
139879
  // src/seal.ts
139180
139880
  var import_tweetnacl = __toESM(require_nacl_fast(), 1);
139181
139881
  var import_tweetnacl_util = __toESM(require_nacl_util(), 1);
@@ -139364,10 +140064,10 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139364
140064
  }
139365
140065
  async _formatEntry(dirent, path2) {
139366
140066
  let entry;
139367
- const basename9 = this._isDirent ? dirent.name : dirent;
140067
+ const basename11 = this._isDirent ? dirent.name : dirent;
139368
140068
  try {
139369
- const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename9));
139370
- 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 };
139371
140071
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
139372
140072
  } catch (err) {
139373
140073
  this._onError(err);
@@ -139908,9 +140608,9 @@ var NodeFsHandler2 = class {
139908
140608
  _watchWithNodeFs(path2, listener) {
139909
140609
  const opts = this.fsw.options;
139910
140610
  const directory = sp.dirname(path2);
139911
- const basename9 = sp.basename(path2);
140611
+ const basename11 = sp.basename(path2);
139912
140612
  const parent = this.fsw._getWatchedDir(directory);
139913
- parent.add(basename9);
140613
+ parent.add(basename11);
139914
140614
  const absolutePath = sp.resolve(path2);
139915
140615
  const options = {
139916
140616
  persistent: opts.persistent
@@ -139920,7 +140620,7 @@ var NodeFsHandler2 = class {
139920
140620
  let closer;
139921
140621
  if (opts.usePolling) {
139922
140622
  const enableBin = opts.interval !== opts.binaryInterval;
139923
- options.interval = enableBin && isBinaryPath2(basename9) ? opts.binaryInterval : opts.interval;
140623
+ options.interval = enableBin && isBinaryPath2(basename11) ? opts.binaryInterval : opts.interval;
139924
140624
  closer = setFsWatchFileListener2(path2, absolutePath, options, {
139925
140625
  listener,
139926
140626
  rawEmitter: this.fsw._emitRaw
@@ -139943,10 +140643,10 @@ var NodeFsHandler2 = class {
139943
140643
  return;
139944
140644
  }
139945
140645
  const dirname17 = sp.dirname(file2);
139946
- const basename9 = sp.basename(file2);
140646
+ const basename11 = sp.basename(file2);
139947
140647
  const parent = this.fsw._getWatchedDir(dirname17);
139948
140648
  let prevStats = stats;
139949
- if (parent.has(basename9))
140649
+ if (parent.has(basename11))
139950
140650
  return;
139951
140651
  const listener = async (path2, newStats) => {
139952
140652
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH2, file2, 5))
@@ -139971,9 +140671,9 @@ var NodeFsHandler2 = class {
139971
140671
  prevStats = newStats2;
139972
140672
  }
139973
140673
  } catch (error51) {
139974
- this.fsw._remove(dirname17, basename9);
140674
+ this.fsw._remove(dirname17, basename11);
139975
140675
  }
139976
- } else if (parent.has(basename9)) {
140676
+ } else if (parent.has(basename11)) {
139977
140677
  const at2 = newStats.atimeMs;
139978
140678
  const mt2 = newStats.mtimeMs;
139979
140679
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -140930,7 +141630,7 @@ function watch2(paths, options = {}) {
140930
141630
  var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
140931
141631
 
140932
141632
  // src/services/conversations/conversationWatcher.ts
140933
- var import_fs21 = require("fs");
141633
+ var import_fs22 = require("fs");
140934
141634
  var import_promises12 = require("fs/promises");
140935
141635
  var ConversationWatcher = class {
140936
141636
  files = /* @__PURE__ */ new Map();
@@ -140951,7 +141651,7 @@ var ConversationWatcher = class {
140951
141651
  if (this.files.has(filePath)) return;
140952
141652
  let offset;
140953
141653
  try {
140954
- offset = (0, import_fs21.statSync)(filePath).size;
141654
+ offset = (0, import_fs22.statSync)(filePath).size;
140955
141655
  } catch {
140956
141656
  offset = 0;
140957
141657
  }
@@ -141060,14 +141760,14 @@ var ConversationWatcher = class {
141060
141760
  };
141061
141761
 
141062
141762
  // src/services/conversations/pruneAgentConversations.ts
141063
- var import_fs22 = require("fs");
141763
+ var import_fs23 = require("fs");
141064
141764
  function pruneAgentConversations(cache) {
141065
141765
  const db = cache.getDatabase();
141066
141766
  const rows = db.prepare("SELECT id, file_path FROM conversation_meta").all();
141067
141767
  let pruned = 0;
141068
141768
  let missing = 0;
141069
141769
  for (const row of rows) {
141070
- if (!(0, import_fs22.existsSync)(row.file_path)) {
141770
+ if (!(0, import_fs23.existsSync)(row.file_path)) {
141071
141771
  missing += 1;
141072
141772
  continue;
141073
141773
  }
@@ -141376,6 +142076,7 @@ function managedToResponse(s3, ptyAttached) {
141376
142076
  return {
141377
142077
  id: s3.id,
141378
142078
  conversationId: s3.id,
142079
+ provider: s3.provider ?? CLAUDE_CODE_PROVIDER2,
141379
142080
  status: s3.status,
141380
142081
  projectPath: s3.projectPath,
141381
142082
  projectName: s3.projectName,
@@ -141401,13 +142102,15 @@ function managedToResponse(s3, ptyAttached) {
141401
142102
  ...s3.failureReason != null && { failureReason: s3.failureReason },
141402
142103
  ...s3.resumedFromConversationId != null && {
141403
142104
  resumedFromConversationId: s3.resumedFromConversationId
141404
- }
142105
+ },
142106
+ ...s3.boundConversationId != null && { boundConversationId: s3.boundConversationId }
141405
142107
  };
141406
142108
  }
141407
142109
  function discoveredToResponse(d, conversationId) {
141408
142110
  return {
141409
142111
  id: conversationId,
141410
142112
  conversationId,
142113
+ provider: CLAUDE_CODE_PROVIDER2,
141411
142114
  status: "idle",
141412
142115
  projectPath: d.projectPath,
141413
142116
  projectName: d.projectName,
@@ -141423,10 +142126,10 @@ function discoveredToResponse(d, conversationId) {
141423
142126
  }
141424
142127
 
141425
142128
  // src/uploads.ts
141426
- var import_crypto7 = require("crypto");
142129
+ var import_crypto8 = require("crypto");
141427
142130
  var import_promises13 = require("fs/promises");
141428
142131
  var import_heic_convert = __toESM(require_heic_convert(), 1);
141429
- var import_path18 = require("path");
142132
+ var import_path20 = require("path");
141430
142133
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
141431
142134
  var MAX_BYTES = 25 * 1024 * 1024;
141432
142135
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -141457,11 +142160,11 @@ async function saveUploadFile(input) {
141457
142160
  mimeType = "image/jpeg";
141458
142161
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
141459
142162
  }
141460
- const id = `up_${(0, import_crypto7.randomBytes)(8).toString("hex")}`;
142163
+ const id = `up_${(0, import_crypto8.randomBytes)(8).toString("hex")}`;
141461
142164
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
141462
- 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);
141463
142166
  await (0, import_promises13.mkdir)(dir, { recursive: true });
141464
- const filePath = (0, import_path18.join)(dir, `${Date.now()}-${id}-${safeName}`);
142167
+ const filePath = (0, import_path20.join)(dir, `${Date.now()}-${id}-${safeName}`);
141465
142168
  await (0, import_promises13.writeFile)(filePath, buffer);
141466
142169
  return {
141467
142170
  id,
@@ -141974,10 +142677,10 @@ var StreamerServer = class {
141974
142677
  this.verbose = config2.verbose ?? false;
141975
142678
  this.disableDb = config2.disableDb ?? false;
141976
142679
  this.scanProfiles = config2.scanProfiles;
141977
- 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")];
141978
142681
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
141979
142682
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
141980
- 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");
141981
142684
  this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
141982
142685
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
141983
142686
  this.markScannerStaleDebounced = debounce(() => {
@@ -142044,7 +142747,7 @@ var StreamerServer = class {
142044
142747
  }
142045
142748
  },
142046
142749
  onConversationChanged: (filePath) => {
142047
- this.cache?.invalidateByFilePath(filePath);
142750
+ this.cache?.invalidateByFilePath(filePath, { skipIfTailed: true });
142048
142751
  this.markScannerStaleDebounced();
142049
142752
  this.log.debug?.(`Scanner invalidated by directory event: ${filePath}`, {
142050
142753
  filePath,
@@ -142061,7 +142764,7 @@ var StreamerServer = class {
142061
142764
  });
142062
142765
  }
142063
142766
  });
142064
- this.ptyManager = new PTYManager({
142767
+ this.ptyManager = new LiveSessionManager({
142065
142768
  logger: getLogger("pty"),
142066
142769
  onOutput: (sessionId, data) => {
142067
142770
  this.wsHub.broadcast({ type: "terminal_output", sessionId, data });
@@ -142142,7 +142845,7 @@ var StreamerServer = class {
142142
142845
  temporalClient,
142143
142846
  taskQueue: agentConfig.temporal.taskQueue
142144
142847
  });
142145
- 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");
142146
142849
  conversationWriter = createConversationWriter({
142147
142850
  baseDir: conversationsBaseDir
142148
142851
  });
@@ -142364,7 +143067,7 @@ var StreamerServer = class {
142364
143067
  });
142365
143068
  try {
142366
143069
  this.cache = ConversationCache.open(
142367
- (0, import_path19.join)(this.cacheDir, "cache.db"),
143070
+ (0, import_path21.join)(this.cacheDir, "cache.db"),
142368
143071
  this.tailSize,
142369
143072
  void 0,
142370
143073
  {
@@ -142390,18 +143093,19 @@ var StreamerServer = class {
142390
143093
  if (this.scanProfiles && this.scanProfiles.length > 0) {
142391
143094
  for (const profile of this.scanProfiles) {
142392
143095
  if (profile.enabled) {
142393
- this.fileWatcher.watchDirectory((0, import_path19.join)(profile.configDir, "projects"));
143096
+ this.fileWatcher.watchDirectory((0, import_path21.join)(profile.configDir, "projects"));
142394
143097
  }
142395
143098
  }
142396
143099
  } else {
142397
- 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"));
142398
143101
  }
142399
143102
  } catch (err) {
142400
143103
  const message = err instanceof Error ? err.message : String(err);
142401
- this.log.warn(`ConversationCache failed to open (running without cache): ${message}`, {
142402
- error: message,
142403
- event: "cache.open_failed"
142404
- });
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
+ );
142405
143109
  }
142406
143110
  const warmupScanner = new ConversationScanner();
142407
143111
  this.allScanners.add(warmupScanner);
@@ -142888,17 +143592,17 @@ var StreamerServer = class {
142888
143592
  return this.getScanner();
142889
143593
  }
142890
143594
  findJsonlPath(uuid3) {
142891
- const projectsDir = (0, import_path19.join)((0, import_os10.homedir)(), ".claude", "projects");
142892
- 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;
142893
143597
  const filename = `${uuid3}.jsonl`;
142894
- for (const dir of (0, import_fs23.readdirSync)(projectsDir)) {
142895
- const fp = (0, import_path19.join)(projectsDir, dir, filename);
142896
- if ((0, import_fs23.existsSync)(fp)) return fp;
142897
- 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);
142898
143602
  try {
142899
- for (const sub of (0, import_fs23.readdirSync)(projectDir)) {
142900
- const subagentPath = (0, import_path19.join)(projectDir, sub, "subagents", filename);
142901
- 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;
142902
143606
  }
142903
143607
  } catch {
142904
143608
  }
@@ -142907,7 +143611,7 @@ var StreamerServer = class {
142907
143611
  }
142908
143612
  async readCwdFromJsonl(filePath) {
142909
143613
  return new Promise((resolve6) => {
142910
- 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 });
142911
143615
  let found = false;
142912
143616
  rl.on("line", (line) => {
142913
143617
  if (found) return;
@@ -142964,7 +143668,7 @@ var StreamerServer = class {
142964
143668
  if (!conv.filePath) return false;
142965
143669
  let mtimeMs = null;
142966
143670
  try {
142967
- mtimeMs = (0, import_fs23.statSync)(conv.filePath).mtimeMs;
143671
+ mtimeMs = (0, import_fs24.statSync)(conv.filePath).mtimeMs;
142968
143672
  } catch {
142969
143673
  return false;
142970
143674
  }
@@ -143207,7 +143911,7 @@ var StreamerServer = class {
143207
143911
  handleGetSession(sessionId, res) {
143208
143912
  const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
143209
143913
  if (session) {
143210
- if (!(0, import_fs23.existsSync)(session.projectPath)) {
143914
+ if (!(0, import_fs24.existsSync)(session.projectPath)) {
143211
143915
  session.failureReason = `Project directory not found: ${session.projectPath}`;
143212
143916
  }
143213
143917
  json2(res, 200, session);
@@ -143247,7 +143951,10 @@ var StreamerServer = class {
143247
143951
  json2(res, 400, { error: "Could not determine project path" });
143248
143952
  return;
143249
143953
  }
143954
+ const cachedConvMeta = this.cache?.getMetaById(sessionId);
143955
+ const provider = conv?.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER2;
143250
143956
  const session = await this.ptyManager.start(sessionId, {
143957
+ provider,
143251
143958
  projectPath,
143252
143959
  projectName: body.projectName,
143253
143960
  branch: body.branch
@@ -143597,7 +144304,7 @@ var StreamerServer = class {
143597
144304
  sessionStore: this.sessionStore,
143598
144305
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
143599
144306
  agentClient: this.agentClient,
143600
- 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") : "",
143601
144308
  agentConfig: this.agentConfig
143602
144309
  });
143603
144310
  json2(res, result.status, result.body);
@@ -143606,6 +144313,13 @@ var StreamerServer = class {
143606
144313
  }
143607
144314
  return;
143608
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;
143609
144323
  if (!this.browseRoot) {
143610
144324
  json2(res, 403, {
143611
144325
  error: "File browsing not configured. Set browseRoot on the server.",
@@ -143613,8 +144327,6 @@ var StreamerServer = class {
143613
144327
  });
143614
144328
  return;
143615
144329
  }
143616
- const body = await readBody(req);
143617
- const { path: relativePath, systemPrompt: clientPrompt } = body;
143618
144330
  if (typeof relativePath !== "string") {
143619
144331
  json2(res, 400, { error: "Missing path field" });
143620
144332
  return;
@@ -143635,21 +144347,27 @@ var StreamerServer = class {
143635
144347
  ].filter(Boolean);
143636
144348
  try {
143637
144349
  const session = await this.ptyManager.startFresh({
144350
+ provider,
143638
144351
  projectPath: resolvedPath,
143639
144352
  projectName: body.projectName,
143640
144353
  systemPrompt: systemPromptParts.join("\n")
143641
144354
  });
143642
144355
  this.sessionStore.addManaged(session);
143643
144356
  json2(res, 202, { id: session.id, status: "pending" });
143644
- 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
+ }
143645
144362
  this.broadcastOrUnicastSessionList(req);
143646
144363
  } catch (err) {
143647
144364
  const message = err instanceof Error ? err.message : "Failed to start session";
144365
+ const statusCode = typeof err.statusCode === "number" ? err.statusCode : 500;
143648
144366
  this.log.error(`[start] failed to start session: ${message}`, {
143649
144367
  event: "session.start_failed",
143650
144368
  error: message
143651
144369
  });
143652
- json2(res, 500, { error: message });
144370
+ json2(res, statusCode, { error: message });
143653
144371
  }
143654
144372
  }
143655
144373
  // ─── Project linking ─────────────────────────────────────────────
@@ -143702,9 +144420,9 @@ var StreamerServer = class {
143702
144420
  // was passed to Claude via --session-id so the filename matches from the start.
143703
144421
  watchForJsonl(sessionId, projectPath) {
143704
144422
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
143705
- 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);
143706
144424
  const expectedFile = `${sessionId}.jsonl`;
143707
- const filePath = (0, import_path19.join)(projectsDir, expectedFile);
144425
+ const filePath = (0, import_path21.join)(projectsDir, expectedFile);
143708
144426
  const deadline = Date.now() + 12e4;
143709
144427
  let watcher = null;
143710
144428
  const cleanup = () => {
@@ -143722,12 +144440,12 @@ var StreamerServer = class {
143722
144440
  cleanup();
143723
144441
  return;
143724
144442
  }
143725
- let resolvedFilePath = (0, import_fs23.existsSync)(filePath) ? filePath : null;
143726
- 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)) {
143727
144445
  try {
143728
144446
  const now = Date.now();
143729
- 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];
143730
- 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);
143731
144449
  } catch {
143732
144450
  }
143733
144451
  }
@@ -143736,7 +144454,7 @@ var StreamerServer = class {
143736
144454
  this.sessionFileMap.set(sessionId, resolvedFilePath);
143737
144455
  this.fileWatcher.watch(resolvedFilePath);
143738
144456
  try {
143739
- 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);
143740
144458
  if (existing.length > 0) {
143741
144459
  this.wsHub.broadcast({ type: "conversation_events", sessionId, lines: existing });
143742
144460
  for (const line of existing) {
@@ -143762,11 +144480,126 @@ var StreamerServer = class {
143762
144480
  if (this.sessionFileMap.has(sessionId)) return;
143763
144481
  try {
143764
144482
  require("fs").mkdirSync(projectsDir, { recursive: true });
143765
- watcher = (0, import_fs23.watch)(projectsDir, tryWire);
144483
+ watcher = (0, import_fs24.watch)(projectsDir, tryWire);
143766
144484
  watcher.on("error", cleanup);
143767
144485
  } catch {
143768
144486
  }
143769
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
+ }
143770
144603
  async handleBrowse(url2, res) {
143771
144604
  if (!this.browseRoot) {
143772
144605
  json2(res, 403, {
@@ -143850,7 +144683,7 @@ var StreamerServer = class {
143850
144683
  };
143851
144684
  function classifyResumability(cwd) {
143852
144685
  if (!cwd) return { resumable: true };
143853
- if ((0, import_fs23.existsSync)(cwd)) return { resumable: true };
144686
+ if ((0, import_fs24.existsSync)(cwd)) return { resumable: true };
143854
144687
  const ranInWorktree = /\/\.worktrees\//.test(cwd) || /\/\.claude\/worktrees\//.test(cwd);
143855
144688
  return {
143856
144689
  resumable: false,
@@ -144430,13 +145263,13 @@ var import_node_fs17 = require("fs");
144430
145263
 
144431
145264
  // node_modules/tar/dist/esm/index.min.js
144432
145265
  var import_events4 = __toESM(require("events"), 1);
144433
- var import_fs24 = __toESM(require("fs"), 1);
145266
+ var import_fs25 = __toESM(require("fs"), 1);
144434
145267
  var import_node_events3 = require("events");
144435
145268
  var import_node_stream3 = __toESM(require("stream"), 1);
144436
145269
  var import_node_string_decoder = require("string_decoder");
144437
145270
  var import_node_path14 = __toESM(require("path"), 1);
144438
145271
  var import_node_fs11 = __toESM(require("fs"), 1);
144439
- var import_path20 = require("path");
145272
+ var import_path22 = require("path");
144440
145273
  var import_events5 = require("events");
144441
145274
  var import_assert = __toESM(require("assert"), 1);
144442
145275
  var import_buffer = require("buffer");
@@ -144444,17 +145277,17 @@ var Ps = __toESM(require("zlib"), 1);
144444
145277
  var import_zlib = __toESM(require("zlib"), 1);
144445
145278
  var import_node_path15 = require("path");
144446
145279
  var import_node_path16 = require("path");
144447
- var import_fs25 = __toESM(require("fs"), 1);
144448
145280
  var import_fs26 = __toESM(require("fs"), 1);
144449
- var import_path21 = __toESM(require("path"), 1);
145281
+ var import_fs27 = __toESM(require("fs"), 1);
145282
+ var import_path23 = __toESM(require("path"), 1);
144450
145283
  var import_node_path17 = require("path");
144451
- var import_path22 = __toESM(require("path"), 1);
145284
+ var import_path24 = __toESM(require("path"), 1);
144452
145285
  var import_node_fs12 = __toESM(require("fs"), 1);
144453
145286
  var import_node_assert = __toESM(require("assert"), 1);
144454
145287
  var import_node_crypto5 = require("crypto");
144455
145288
  var import_node_fs13 = __toESM(require("fs"), 1);
144456
145289
  var import_node_path18 = __toESM(require("path"), 1);
144457
- var import_fs27 = __toESM(require("fs"), 1);
145290
+ var import_fs28 = __toESM(require("fs"), 1);
144458
145291
  var import_node_fs14 = __toESM(require("fs"), 1);
144459
145292
  var import_node_path19 = __toESM(require("path"), 1);
144460
145293
  var import_node_fs15 = __toESM(require("fs"), 1);
@@ -144806,7 +145639,7 @@ var A = class extends import_node_events3.EventEmitter {
144806
145639
  return Wr;
144807
145640
  }
144808
145641
  };
144809
- var Jr = import_fs24.default.writev;
145642
+ var Jr = import_fs25.default.writev;
144810
145643
  var ht = /* @__PURE__ */ Symbol("_autoClose");
144811
145644
  var H = /* @__PURE__ */ Symbol("_close");
144812
145645
  var te = /* @__PURE__ */ Symbol("_ended");
@@ -144861,7 +145694,7 @@ var _t = class extends A {
144861
145694
  throw new TypeError("this is a readable stream");
144862
145695
  }
144863
145696
  [at]() {
144864
- 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));
144865
145698
  }
144866
145699
  [Ht](t2, e) {
144867
145700
  t2 ? this[Ut](t2) : (this[u2] = e, this.emit("open", e), this[zt]());
@@ -144874,7 +145707,7 @@ var _t = class extends A {
144874
145707
  this[j] = true;
144875
145708
  let t2 = this[vi]();
144876
145709
  if (t2.length === 0) return process.nextTick(() => this[Ii](null, 0, t2));
144877
- 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));
144878
145711
  }
144879
145712
  }
144880
145713
  [Ii](t2, e, i) {
@@ -144883,7 +145716,7 @@ var _t = class extends A {
144883
145716
  [H]() {
144884
145717
  if (this[ht] && typeof this[u2] == "number") {
144885
145718
  let t2 = this[u2];
144886
- 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"));
144887
145720
  }
144888
145721
  }
144889
145722
  [Ut](t2) {
@@ -144911,7 +145744,7 @@ var Be = class extends _t {
144911
145744
  [at]() {
144912
145745
  let t2 = true;
144913
145746
  try {
144914
- 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;
144915
145748
  } finally {
144916
145749
  t2 && this[H]();
144917
145750
  }
@@ -144922,7 +145755,7 @@ var Be = class extends _t {
144922
145755
  if (!this[j]) {
144923
145756
  this[j] = true;
144924
145757
  do {
144925
- 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);
144926
145759
  if (!this[ki](i, e)) break;
144927
145760
  } while (true);
144928
145761
  this[j] = false;
@@ -144935,7 +145768,7 @@ var Be = class extends _t {
144935
145768
  [H]() {
144936
145769
  if (this[ht] && typeof this[u2] == "number") {
144937
145770
  let t2 = this[u2];
144938
- 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");
144939
145772
  }
144940
145773
  }
144941
145774
  };
@@ -144977,7 +145810,7 @@ var et = class extends import_events4.default {
144977
145810
  this[H](), this[gt] = true, this.emit("error", t2);
144978
145811
  }
144979
145812
  [at]() {
144980
- 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));
144981
145814
  }
144982
145815
  [Ht](t2, e) {
144983
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]());
@@ -144989,7 +145822,7 @@ var et = class extends import_events4.default {
144989
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);
144990
145823
  }
144991
145824
  [ve](t2) {
144992
- 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));
144993
145826
  }
144994
145827
  [Pt](t2, e) {
144995
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"))));
@@ -145005,7 +145838,7 @@ var et = class extends import_events4.default {
145005
145838
  [H]() {
145006
145839
  if (this[ht] && typeof this[u2] == "number") {
145007
145840
  let t2 = this[u2];
145008
- 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"));
145009
145842
  }
145010
145843
  }
145011
145844
  };
@@ -145013,24 +145846,24 @@ var Wt = class extends et {
145013
145846
  [at]() {
145014
145847
  let t2;
145015
145848
  if (this[Me] && this[tt] === "r+") try {
145016
- t2 = import_fs24.default.openSync(this[U2], this[tt], this[ie]);
145849
+ t2 = import_fs25.default.openSync(this[U2], this[tt], this[ie]);
145017
145850
  } catch (e) {
145018
145851
  if (e?.code === "ENOENT") return this[tt] = "w", this[at]();
145019
145852
  throw e;
145020
145853
  }
145021
- 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]);
145022
145855
  this[Ht](null, t2);
145023
145856
  }
145024
145857
  [H]() {
145025
145858
  if (this[ht] && typeof this[u2] == "number") {
145026
145859
  let t2 = this[u2];
145027
- 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");
145028
145861
  }
145029
145862
  }
145030
145863
  [ve](t2) {
145031
145864
  let e = true;
145032
145865
  try {
145033
- 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;
145034
145867
  } finally {
145035
145868
  if (e) try {
145036
145869
  this[H]();
@@ -145813,11 +146646,11 @@ var vn = (s3) => {
145813
146646
  };
145814
146647
  var Qi = (s3, t2) => {
145815
146648
  let e = new Map(t2.map((n) => [ut(n), true])), i = s3.filter, r = (n, o = "") => {
145816
- let h = o || (0, import_path20.parse)(n).root || ".", a;
146649
+ let h = o || (0, import_path22.parse)(n).root || ".", a;
145817
146650
  if (n === h) a = false;
145818
146651
  else {
145819
146652
  let l = e.get(n);
145820
- 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);
145821
146654
  }
145822
146655
  return e.set(n, a), a;
145823
146656
  };
@@ -145937,7 +146770,7 @@ var de = class extends A {
145937
146770
  let [o, h] = ce(this.path);
145938
146771
  o && typeof h == "string" && (this.path = h, r = o);
145939
146772
  }
145940
- 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 });
145941
146774
  let n = this.statCache.get(this.absolute);
145942
146775
  n ? this[si](n) : this[ss]();
145943
146776
  }
@@ -145948,7 +146781,7 @@ var de = class extends A {
145948
146781
  return t2 === "error" && (this.#t = true), super.emit(t2, ...e);
145949
146782
  }
145950
146783
  [ss]() {
145951
- import_fs26.default.lstat(this.absolute, (t2, e) => {
146784
+ import_fs27.default.lstat(this.absolute, (t2, e) => {
145952
146785
  if (t2) return this.emit("error", t2);
145953
146786
  this[si](e);
145954
146787
  });
@@ -145986,7 +146819,7 @@ var de = class extends A {
145986
146819
  this.path.slice(-1) !== "/" && (this.path += "/"), this.stat.size = 0, this[fe](), this.end();
145987
146820
  }
145988
146821
  [is]() {
145989
- import_fs26.default.readlink(this.absolute, (t2, e) => {
146822
+ import_fs27.default.readlink(this.absolute, (t2, e) => {
145990
146823
  if (t2) return this.emit("error", t2);
145991
146824
  this[ns](e);
145992
146825
  });
@@ -145996,7 +146829,7 @@ var de = class extends A {
145996
146829
  }
145997
146830
  [sr](t2) {
145998
146831
  if (!this.stat) throw new Error("cannot create link entry without stat");
145999
- 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();
146000
146833
  }
146001
146834
  [er]() {
146002
146835
  if (!this.stat) throw new Error("cannot create file entry without stat");
@@ -146009,7 +146842,7 @@ var de = class extends A {
146009
146842
  this[os]();
146010
146843
  }
146011
146844
  [os]() {
146012
- import_fs26.default.open(this.absolute, "r", (t2, e) => {
146845
+ import_fs27.default.open(this.absolute, "r", (t2, e) => {
146013
146846
  if (t2) return this.emit("error", t2);
146014
146847
  this[hs](e);
146015
146848
  });
@@ -146024,14 +146857,14 @@ var de = class extends A {
146024
146857
  [ii]() {
146025
146858
  let { fd: t2, buf: e, offset: i, length: r, pos: n } = this;
146026
146859
  if (t2 === void 0 || e === void 0) throw new Error("cannot read file without first opening");
146027
- import_fs26.default.read(t2, e, i, r, n, (o, h) => {
146860
+ import_fs27.default.read(t2, e, i, r, n, (o, h) => {
146028
146861
  if (o) return this[pt](() => this.emit("error", o));
146029
146862
  this[rs](h);
146030
146863
  });
146031
146864
  }
146032
146865
  [pt](t2 = () => {
146033
146866
  }) {
146034
- this.fd !== void 0 && import_fs26.default.close(this.fd, t2);
146867
+ this.fd !== void 0 && import_fs27.default.close(this.fd, t2);
146035
146868
  }
146036
146869
  [rs](t2) {
146037
146870
  if (t2 <= 0 && this.remain > 0) {
@@ -146066,20 +146899,20 @@ var de = class extends A {
146066
146899
  var ni = class extends de {
146067
146900
  sync = true;
146068
146901
  [ss]() {
146069
- this[si](import_fs26.default.lstatSync(this.absolute));
146902
+ this[si](import_fs27.default.lstatSync(this.absolute));
146070
146903
  }
146071
146904
  [is]() {
146072
- this[ns](import_fs26.default.readlinkSync(this.absolute));
146905
+ this[ns](import_fs27.default.readlinkSync(this.absolute));
146073
146906
  }
146074
146907
  [os]() {
146075
- this[hs](import_fs26.default.openSync(this.absolute, "r"));
146908
+ this[hs](import_fs27.default.openSync(this.absolute, "r"));
146076
146909
  }
146077
146910
  [ii]() {
146078
146911
  let t2 = true;
146079
146912
  try {
146080
146913
  let { fd: e, buf: i, offset: r, length: n, pos: o } = this;
146081
146914
  if (e === void 0 || i === void 0) throw new Error("fd and buf must be set in READ method");
146082
- let h = import_fs26.default.readSync(e, i, r, n, o);
146915
+ let h = import_fs27.default.readSync(e, i, r, n, o);
146083
146916
  this[rs](h), t2 = false;
146084
146917
  } finally {
146085
146918
  if (t2) try {
@@ -146094,7 +146927,7 @@ var ni = class extends de {
146094
146927
  }
146095
146928
  [pt](t2 = () => {
146096
146929
  }) {
146097
- this.fd !== void 0 && import_fs26.default.closeSync(this.fd), t2();
146930
+ this.fd !== void 0 && import_fs27.default.closeSync(this.fd), t2();
146098
146931
  }
146099
146932
  };
146100
146933
  var oi = class extends A {
@@ -146410,7 +147243,7 @@ var wt = class extends A {
146410
147243
  return typeof t2 == "string" ? this[ci](t2) : this[or](t2), this.flowing;
146411
147244
  }
146412
147245
  [or](t2) {
146413
- let e = f(import_path22.default.resolve(this.cwd, t2.path));
147246
+ let e = f(import_path24.default.resolve(this.cwd, t2.path));
146414
147247
  if (!this.filter(t2.path, t2)) t2.resume();
146415
147248
  else {
146416
147249
  let i = new pi(t2.path, e);
@@ -146419,13 +147252,13 @@ var wt = class extends A {
146419
147252
  this[Ft]();
146420
147253
  }
146421
147254
  [ci](t2) {
146422
- let e = f(import_path22.default.resolve(this.cwd, t2));
147255
+ let e = f(import_path24.default.resolve(this.cwd, t2));
146423
147256
  this[W].push(new pi(t2, e)), this[Ft]();
146424
147257
  }
146425
147258
  [ds](t2) {
146426
147259
  t2.pending = true, this[G2] += 1;
146427
147260
  let e = this.follow ? "stat" : "lstat";
146428
- import_fs25.default[e](t2.absolute, (i, r) => {
147261
+ import_fs26.default[e](t2.absolute, (i, r) => {
146429
147262
  t2.pending = false, this[G2] -= 1, i ? this.emit("error", i) : this[li](t2, r);
146430
147263
  });
146431
147264
  }
@@ -146439,7 +147272,7 @@ var wt = class extends A {
146439
147272
  this[Ft]();
146440
147273
  }
146441
147274
  [ms](t2) {
146442
- 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) => {
146443
147276
  if (t2.pending = false, this[G2] -= 1, e) return this.emit("error", e);
146444
147277
  this[fi](t2, i);
146445
147278
  });
@@ -146540,10 +147373,10 @@ var kt = class extends wt {
146540
147373
  }
146541
147374
  [ds](t2) {
146542
147375
  let e = this.follow ? "statSync" : "lstatSync";
146543
- this[li](t2, import_fs25.default[e](t2.absolute));
147376
+ this[li](t2, import_fs26.default[e](t2.absolute));
146544
147377
  }
146545
147378
  [ms](t2) {
146546
- this[fi](t2, import_fs25.default.readdirSync(t2.absolute));
147379
+ this[fi](t2, import_fs26.default.readdirSync(t2.absolute));
146547
147380
  }
146548
147381
  [di](t2) {
146549
147382
  let e = t2.entry, i = this.zip;
@@ -146594,8 +147427,8 @@ var Qn = K2(Vn, $n, Xn, qn, (s3, t2) => {
146594
147427
  });
146595
147428
  var Jn = process.env.__FAKE_PLATFORM__ || process.platform;
146596
147429
  var Er = Jn === "win32";
146597
- var { O_CREAT: wr, O_NOFOLLOW: mr, O_TRUNC: Sr, O_WRONLY: yr } = import_fs27.default.constants;
146598
- 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;
146599
147432
  var jn = Er && !!Rr;
146600
147433
  var to = 512 * 1024;
146601
147434
  var eo = Rr | Sr | wr | yr;
@@ -147719,6 +148552,13 @@ program2.command("serve").description("Start the streamer server").option("-p, -
147719
148552
  "Run in multi-agent mode (PTY mode unreachable in this process)",
147720
148553
  false
147721
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
+ }
147722
148562
  if (opts.multiAgentFlow) {
147723
148563
  process.env.MULTI_AGENT_FLOW = "true";
147724
148564
  }
@@ -147816,12 +148656,12 @@ program2.command("cache").description("Manage the local SQLite conversation cach
147816
148656
  "Cache directory (default: ~/.threadbase/cache)",
147817
148657
  `${process.env.HOME}/.threadbase/cache`
147818
148658
  ).action((opts) => {
147819
- const { rmSync: rmSync6, existsSync: existsSync11 } = require("fs");
148659
+ const { rmSync: rmSync6, existsSync: existsSync12 } = require("fs");
147820
148660
  const { join: join26 } = require("path");
147821
148661
  const dbPath = join26(opts.cacheDir, "cache.db");
147822
148662
  for (const suffix of ["", "-shm", "-wal"]) {
147823
148663
  const f2 = dbPath + suffix;
147824
- if (existsSync11(f2)) {
148664
+ if (existsSync12(f2)) {
147825
148665
  rmSync6(f2);
147826
148666
  log7.info(`Deleted ${f2}`, { path: f2 }, "console");
147827
148667
  }