@threadbase-sh/streamer 1.27.1 → 1.27.2

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
@@ -99922,8 +99922,8 @@ var require_pattern = __commonJS({
99922
99922
  }
99923
99923
  exports2.endsWithSlashGlobStar = endsWithSlashGlobStar;
99924
99924
  function isAffectDepthOfReadingPattern(pattern) {
99925
- const basename11 = path2.basename(pattern);
99926
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename11);
99925
+ const basename12 = path2.basename(pattern);
99926
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename12);
99927
99927
  }
99928
99928
  exports2.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
99929
99929
  function expandPatternsWithBraceExpansion(patterns2) {
@@ -123750,14 +123750,14 @@ __export(prompt_exports, {
123750
123750
  interactivePermissionModePrompt: () => interactivePermissionModePrompt,
123751
123751
  interactivePrompt: () => interactivePrompt
123752
123752
  });
123753
- var import_node_process2, import_promises18, interactivePrompt, interactivePermissionModePrompt;
123753
+ var import_node_process2, import_promises20, interactivePrompt, interactivePermissionModePrompt;
123754
123754
  var init_prompt = __esm({
123755
123755
  "src/lifecycle/prompt.ts"() {
123756
123756
  "use strict";
123757
123757
  import_node_process2 = require("process");
123758
- import_promises18 = require("readline/promises");
123758
+ import_promises20 = require("readline/promises");
123759
123759
  interactivePrompt = async ({ prodPort, suggestedAltPort, prodActive }) => {
123760
- const rl = (0, import_promises18.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123760
+ const rl = (0, import_promises20.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123761
123761
  try {
123762
123762
  if (!prodActive) {
123763
123763
  import_node_process2.stdout.write(
@@ -123792,7 +123792,7 @@ The supervised prod streamer is already holding port ${prodPort}.
123792
123792
  }
123793
123793
  };
123794
123794
  interactivePermissionModePrompt = async () => {
123795
- const rl = (0, import_promises18.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123795
+ const rl = (0, import_promises20.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123796
123796
  try {
123797
123797
  import_node_process2.stdout.write(
123798
123798
  "\nWhich permission mode should spawned Claude Code sessions use?\n [a] acceptEdits \u2014 auto-approve file edits, still prompt for shell commands (default)\n [m] manual \u2014 prompt for every edit and command\n"
@@ -128528,35 +128528,38 @@ var flexsearch_bundle_module_min_default = { Index: N, Document: U, Worker: S, r
128528
128528
 
128529
128529
  // node_modules/@threadbase-sh/scanner/dist/index.js
128530
128530
  var import_fs5 = require("fs");
128531
- var import_promises4 = require("fs/promises");
128532
- var import_os4 = require("os");
128533
128531
  var import_path3 = require("path");
128534
- var import_fast_glob = __toESM(require_out4(), 1);
128535
- var import_fs6 = require("fs");
128536
- var import_promises5 = require("fs/promises");
128537
- var import_path4 = require("path");
128538
128532
  var import_readline = require("readline");
128539
- var import_fast_glob2 = __toESM(require_out4(), 1);
128540
- var import_promises6 = require("fs/promises");
128533
+ var import_path4 = require("path");
128534
+ var import_fs6 = require("fs");
128535
+ var import_promises4 = require("fs/promises");
128536
+ var import_os4 = require("os");
128541
128537
  var import_path5 = require("path");
128538
+ var import_fast_glob = __toESM(require_out4(), 1);
128542
128539
  var import_fs7 = require("fs");
128540
+ var import_promises5 = require("fs/promises");
128543
128541
  var import_path6 = require("path");
128544
128542
  var import_readline2 = require("readline");
128543
+ var import_fast_glob2 = __toESM(require_out4(), 1);
128544
+ var import_promises6 = require("fs/promises");
128545
128545
  var import_events2 = require("events");
128546
128546
  var import_fs8 = require("fs");
128547
128547
  var import_os5 = require("os");
128548
128548
  var import_path7 = require("path");
128549
- var import_crypto2 = require("crypto");
128549
+ var import_path8 = require("path");
128550
128550
  var import_fs9 = require("fs");
128551
+ var import_promises7 = require("timers/promises");
128551
128552
  var import_fs10 = require("fs");
128552
- var import_readline3 = require("readline");
128553
- var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
128553
+ var import_promises8 = require("timers/promises");
128554
+ var import_crypto2 = require("crypto");
128554
128555
  var import_fs11 = require("fs");
128555
- var import_path8 = require("path");
128556
- var import_promises7 = require("fs/promises");
128557
128556
  var import_fs12 = require("fs");
128557
+ var import_readline3 = require("readline");
128558
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
128558
128559
  var import_fs13 = require("fs");
128559
128560
  var import_path9 = require("path");
128561
+ var import_promises9 = require("fs/promises");
128562
+ var import_path10 = require("path");
128560
128563
 
128561
128564
  // node_modules/@threadbase-sh/scanner/node_modules/chokidar/esm/index.js
128562
128565
  var import_fs3 = require("fs");
@@ -128711,10 +128714,10 @@ var ReaddirpStream = class extends import_node_stream.Readable {
128711
128714
  }
128712
128715
  async _formatEntry(dirent, path2) {
128713
128716
  let entry;
128714
- const basename11 = this._isDirent ? dirent.name : dirent;
128717
+ const basename12 = this._isDirent ? dirent.name : dirent;
128715
128718
  try {
128716
- const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2, basename11));
128717
- entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename: basename11 };
128719
+ const fullPath = (0, import_node_path5.resolve)((0, import_node_path5.join)(path2, basename12));
128720
+ entry = { path: (0, import_node_path5.relative)(this._root, fullPath), fullPath, basename: basename12 };
128718
128721
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
128719
128722
  } catch (err) {
128720
128723
  this._onError(err);
@@ -129253,9 +129256,9 @@ var NodeFsHandler = class {
129253
129256
  _watchWithNodeFs(path2, listener) {
129254
129257
  const opts = this.fsw.options;
129255
129258
  const directory = sysPath.dirname(path2);
129256
- const basename11 = sysPath.basename(path2);
129259
+ const basename12 = sysPath.basename(path2);
129257
129260
  const parent = this.fsw._getWatchedDir(directory);
129258
- parent.add(basename11);
129261
+ parent.add(basename12);
129259
129262
  const absolutePath = sysPath.resolve(path2);
129260
129263
  const options = {
129261
129264
  persistent: opts.persistent
@@ -129265,7 +129268,7 @@ var NodeFsHandler = class {
129265
129268
  let closer;
129266
129269
  if (opts.usePolling) {
129267
129270
  const enableBin = opts.interval !== opts.binaryInterval;
129268
- options.interval = enableBin && isBinaryPath(basename11) ? opts.binaryInterval : opts.interval;
129271
+ options.interval = enableBin && isBinaryPath(basename12) ? opts.binaryInterval : opts.interval;
129269
129272
  closer = setFsWatchFileListener(path2, absolutePath, options, {
129270
129273
  listener,
129271
129274
  rawEmitter: this.fsw._emitRaw
@@ -129288,10 +129291,10 @@ var NodeFsHandler = class {
129288
129291
  return;
129289
129292
  }
129290
129293
  const dirname17 = sysPath.dirname(file2);
129291
- const basename11 = sysPath.basename(file2);
129294
+ const basename12 = sysPath.basename(file2);
129292
129295
  const parent = this.fsw._getWatchedDir(dirname17);
129293
129296
  let prevStats = stats;
129294
- if (parent.has(basename11))
129297
+ if (parent.has(basename12))
129295
129298
  return;
129296
129299
  const listener = async (path2, newStats) => {
129297
129300
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -129316,9 +129319,9 @@ var NodeFsHandler = class {
129316
129319
  prevStats = newStats2;
129317
129320
  }
129318
129321
  } catch (error51) {
129319
- this.fsw._remove(dirname17, basename11);
129322
+ this.fsw._remove(dirname17, basename12);
129320
129323
  }
129321
- } else if (parent.has(basename11)) {
129324
+ } else if (parent.has(basename12)) {
129322
129325
  const at2 = newStats.atimeMs;
129323
129326
  const mt2 = newStats.mtimeMs;
129324
129327
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -130514,6 +130517,435 @@ var SearchIndexer = class {
130514
130517
  getLogger2().trace("indexer: cleared");
130515
130518
  }
130516
130519
  };
130520
+ var CLAUDE_CODE_PROVIDER = "claude-code";
130521
+ var CODEX_CLI_PROVIDER = "codex-cli";
130522
+ function initialReducerState() {
130523
+ return {
130524
+ sessionId: "",
130525
+ sessionName: "",
130526
+ latestTimestamp: "",
130527
+ cwd: "",
130528
+ teamName: "",
130529
+ model: null,
130530
+ messageCount: 0,
130531
+ lastMessageSender: "user",
130532
+ isTeammate: false,
130533
+ firstUserSeen: false,
130534
+ firstMessage: null,
130535
+ lastMessage: null,
130536
+ lastPrompt: "",
130537
+ pageMessageCount: 0,
130538
+ toolNames: [],
130539
+ previewParts: [],
130540
+ snippetParts: [],
130541
+ previewLength: 0,
130542
+ snippetLength: 0,
130543
+ badJsonLines: 0
130544
+ };
130545
+ }
130546
+ function reduceLine(state, entry, tier) {
130547
+ if (entry.cwd && !state.cwd) state.cwd = entry.cwd;
130548
+ if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId;
130549
+ if (entry.slug && !state.sessionName) state.sessionName = entry.slug;
130550
+ if (entry.teamName && !state.teamName) state.teamName = entry.teamName;
130551
+ if (entry.timestamp) {
130552
+ const ts2 = entry.timestamp;
130553
+ if (!state.latestTimestamp || ts2 > state.latestTimestamp) state.latestTimestamp = ts2;
130554
+ }
130555
+ const type = entry.type;
130556
+ if (type === "last-prompt") {
130557
+ if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt;
130558
+ return;
130559
+ }
130560
+ if (type !== "user" && type !== "assistant") return;
130561
+ if (entry.isMeta) return;
130562
+ const msg = entry.message;
130563
+ if (state.model === null && msg?.model) state.model = msg.model;
130564
+ if (type === "user" && !state.firstUserSeen) {
130565
+ state.firstUserSeen = true;
130566
+ if (isTeammateContent(msg?.content)) state.isTeammate = true;
130567
+ }
130568
+ const content = extractTextContent(msg?.content);
130569
+ const hasToolUseResult = type === "user" && entry.toolUseResult != null;
130570
+ const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);
130571
+ const toolSet = new Set(state.toolNames);
130572
+ collectToolNames(msg?.content, toolSet);
130573
+ state.toolNames = Array.from(toolSet);
130574
+ const toolUseBlocks = extractToolUseBlocks(msg?.content);
130575
+ const thinking = type === "assistant" ? extractThinking(msg?.content) : null;
130576
+ const hasThinking = !!(thinking?.content || thinking?.signature);
130577
+ if (content || isOnlyToolResult || toolUseBlocks.length > 0 || hasThinking) {
130578
+ state.pageMessageCount++;
130579
+ }
130580
+ if (content || isOnlyToolResult) {
130581
+ state.messageCount++;
130582
+ state.lastMessageSender = type;
130583
+ if (content) {
130584
+ const ts2 = entry.timestamp || "";
130585
+ if (!state.firstMessage) state.firstMessage = { text: content.slice(0, 200), timestamp: ts2 };
130586
+ state.lastMessage = { text: content.slice(0, 200), timestamp: ts2 };
130587
+ if (state.previewLength < tier.previewMax) {
130588
+ state.previewParts.push(content);
130589
+ state.previewLength += content.length;
130590
+ }
130591
+ if (state.snippetLength < tier.snippetMax) {
130592
+ const remaining = tier.snippetMax - state.snippetLength;
130593
+ const chunk = content.length > remaining ? content.slice(0, remaining) : content;
130594
+ state.snippetParts.push(chunk);
130595
+ state.snippetLength += chunk.length;
130596
+ }
130597
+ }
130598
+ }
130599
+ }
130600
+ function finalizeMeta(state, filePath, account, tier) {
130601
+ if (state.messageCount === 0) return null;
130602
+ const isSubagent = filePath.includes("/subagents/");
130603
+ let parentSessionId = null;
130604
+ if (isSubagent) {
130605
+ const uuidDir = (0, import_path4.dirname)((0, import_path4.dirname)(filePath));
130606
+ parentSessionId = (0, import_path4.join)((0, import_path4.dirname)(uuidDir), `${(0, import_path4.basename)(uuidDir)}.jsonl`);
130607
+ }
130608
+ const projectPath = state.cwd;
130609
+ return {
130610
+ id: filePath,
130611
+ filePath,
130612
+ provider: CLAUDE_CODE_PROVIDER,
130613
+ sessionId: state.sessionId || (0, import_path4.basename)(filePath, ".jsonl"),
130614
+ sessionName: state.sessionName,
130615
+ projectPath,
130616
+ projectName: getShortProjectName(projectPath),
130617
+ account,
130618
+ timestamp: state.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
130619
+ messageCount: state.messageCount,
130620
+ lastMessageSender: state.lastMessageSender,
130621
+ preview: state.previewParts.join(" ").slice(0, tier.previewMax),
130622
+ contentSnippet: state.snippetParts.join(" "),
130623
+ gitBranch: null,
130624
+ model: state.model,
130625
+ isSubagent,
130626
+ parentSessionId,
130627
+ isTeammate: state.isTeammate,
130628
+ teamName: state.teamName || null,
130629
+ toolNames: state.toolNames,
130630
+ firstMessage: state.firstMessage,
130631
+ lastMessage: state.lastMessage,
130632
+ lastPrompt: state.lastPrompt || void 0
130633
+ };
130634
+ }
130635
+ function getShortProjectName(fullPath) {
130636
+ const parts = fullPath.split("/").filter(Boolean);
130637
+ return parts.slice(-3).join("/");
130638
+ }
130639
+ var SYSTEM_TAGS = [
130640
+ "system-reminder",
130641
+ "command-name",
130642
+ "command-message",
130643
+ "command-args",
130644
+ "ide_selection",
130645
+ "ide_opened_file",
130646
+ "local-command-stdout",
130647
+ "local-command-caveat",
130648
+ "retrieval_status",
130649
+ "task_id",
130650
+ "task_type",
130651
+ "task-id",
130652
+ "task-notification",
130653
+ "fast_mode_info",
130654
+ "persisted-output",
130655
+ "tool_use_error",
130656
+ "user-prompt-submit-hook",
130657
+ "thinking",
130658
+ "ask_user",
130659
+ "teammate-message"
130660
+ ];
130661
+ var SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join("|")})[^>]*>[\\s\\S]*?<\\/\\1>`, "g");
130662
+ function cleanSystemTags(text) {
130663
+ return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
130664
+ }
130665
+ async function parseMeta(filePath, account, tier) {
130666
+ const log8 = getLogger2();
130667
+ log8.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
130668
+ const state = initialReducerState();
130669
+ const fileStream = (0, import_fs5.createReadStream)(filePath);
130670
+ const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
130671
+ try {
130672
+ for await (const line of rl) {
130673
+ if (!line.trim()) continue;
130674
+ let entry;
130675
+ try {
130676
+ entry = JSON.parse(line);
130677
+ } catch {
130678
+ state.badJsonLines++;
130679
+ continue;
130680
+ }
130681
+ reduceLine(state, entry, tier);
130682
+ }
130683
+ } catch (err) {
130684
+ log8.warn({ filePath, err }, "parseMeta: read failed");
130685
+ return null;
130686
+ }
130687
+ if (state.badJsonLines > 0) {
130688
+ log8.warn(
130689
+ { filePath, badJsonLines: state.badJsonLines },
130690
+ "parseMeta: skipped malformed JSON lines"
130691
+ );
130692
+ }
130693
+ const meta3 = finalizeMeta(state, filePath, account, tier);
130694
+ if (!meta3) log8.trace({ filePath }, "parseMeta: no messages");
130695
+ return meta3;
130696
+ }
130697
+ async function parseConversation(filePath, account) {
130698
+ const log8 = getLogger2();
130699
+ log8.trace({ filePath, account }, "parseConversation: start");
130700
+ const messages = [];
130701
+ let badJsonLines = 0;
130702
+ const textParts = [];
130703
+ const turnDurations = [];
130704
+ const state = initialConvState();
130705
+ const fileStream = (0, import_fs5.createReadStream)(filePath);
130706
+ const rl = (0, import_readline.createInterface)({ input: fileStream, crlfDelay: Infinity });
130707
+ try {
130708
+ for await (const line of rl) {
130709
+ if (!line.trim()) continue;
130710
+ let entry;
130711
+ try {
130712
+ entry = JSON.parse(line);
130713
+ } catch {
130714
+ badJsonLines++;
130715
+ continue;
130716
+ }
130717
+ if (entry.type === "system" && entry.subtype === "turn_duration" && typeof entry.durationMs === "number") {
130718
+ turnDurations.push({
130719
+ durationMs: entry.durationMs,
130720
+ messageCount: entry.messageCount || 0,
130721
+ uuid: entry.uuid
130722
+ });
130723
+ continue;
130724
+ }
130725
+ const message = reduceConvLine(state, entry);
130726
+ if (message) {
130727
+ messages.push(message);
130728
+ if (message.text) textParts.push(message.text);
130729
+ }
130730
+ }
130731
+ } catch (err) {
130732
+ log8.warn({ filePath, err }, "parseConversation: read failed");
130733
+ return null;
130734
+ }
130735
+ if (badJsonLines > 0) {
130736
+ log8.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
130737
+ }
130738
+ if (messages.length === 0) {
130739
+ log8.trace({ filePath }, "parseConversation: no messages");
130740
+ return null;
130741
+ }
130742
+ log8.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
130743
+ applyTeamInfo(messages, state);
130744
+ return {
130745
+ id: filePath,
130746
+ filePath,
130747
+ projectPath: state.cwd,
130748
+ projectName: getShortProjectName2(state.cwd),
130749
+ sessionId: state.sessionId || (0, import_path3.basename)(filePath, ".jsonl"),
130750
+ sessionName: state.sessionName,
130751
+ messages,
130752
+ fullText: textParts.join(" "),
130753
+ timestamp: state.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
130754
+ messageCount: messages.length,
130755
+ account,
130756
+ turnDurations: turnDurations.length > 0 ? turnDurations : void 0,
130757
+ lastPrompt: state.lastPrompt || void 0
130758
+ };
130759
+ }
130760
+ function extractTextContent(content) {
130761
+ if (!content) return "";
130762
+ if (typeof content === "string") return cleanSystemTags(content);
130763
+ if (Array.isArray(content)) {
130764
+ return content.map((item) => {
130765
+ if (typeof item === "string") return item;
130766
+ if (item?.type === "text" && item?.text) return item.text;
130767
+ if (item?.type === "tool_result" && typeof item?.content === "string") return item.content;
130768
+ return "";
130769
+ }).filter(Boolean).map(cleanSystemTags).join(" ");
130770
+ }
130771
+ return "";
130772
+ }
130773
+ function extractToolUseNames(content) {
130774
+ if (!Array.isArray(content)) return [];
130775
+ return content.filter((item) => item?.type === "tool_use" && item?.name).map((item) => item.name);
130776
+ }
130777
+ function extractToolUseBlocks(content) {
130778
+ if (!Array.isArray(content)) return [];
130779
+ return content.filter((item) => item?.type === "tool_use" && item?.name && item?.id).map((item) => ({
130780
+ id: item.id,
130781
+ name: item.name,
130782
+ input: item.input || {}
130783
+ }));
130784
+ }
130785
+ var TOOL_NAME_TO_TYPE = {
130786
+ Edit: "edit",
130787
+ Write: "write",
130788
+ Read: "read",
130789
+ Bash: "bash",
130790
+ Grep: "grep",
130791
+ Glob: "glob",
130792
+ Agent: "taskAgent",
130793
+ TaskCreate: "taskCreate",
130794
+ TaskUpdate: "taskUpdate"
130795
+ };
130796
+ function extractToolResultBlocks(content, pendingToolUses) {
130797
+ if (!Array.isArray(content)) return [];
130798
+ return content.filter((item) => item?.type === "tool_result" && item?.tool_use_id).map((item) => {
130799
+ const toolName = pendingToolUses.get(item.tool_use_id)?.name ?? "";
130800
+ return {
130801
+ toolUseId: item.tool_use_id,
130802
+ type: TOOL_NAME_TO_TYPE[toolName] ?? "generic",
130803
+ content: typeof item.content === "string" ? { text: item.content } : item.content ?? {},
130804
+ isError: typeof item.is_error === "boolean" ? item.is_error : void 0
130805
+ };
130806
+ });
130807
+ }
130808
+ function collectToolNames(content, toolSet) {
130809
+ if (!Array.isArray(content)) return;
130810
+ for (const item of content) {
130811
+ if (item?.type === "tool_use" && item?.name) {
130812
+ toolSet.add(item.name);
130813
+ }
130814
+ }
130815
+ }
130816
+ function isOnlyToolResultContent(content) {
130817
+ if (!Array.isArray(content)) return false;
130818
+ return content.length > 0 && content.every((item) => item?.type === "tool_result");
130819
+ }
130820
+ function isTeammateContent(content) {
130821
+ const raw2 = typeof content === "string" ? content : Array.isArray(content) ? content.map(
130822
+ (item) => typeof item === "string" ? item : item?.type === "text" ? item.text ?? "" : ""
130823
+ ).join("") : "";
130824
+ return raw2.includes("<teammate-message");
130825
+ }
130826
+ function extractThinking(content) {
130827
+ if (!Array.isArray(content)) return { content: "", signature: "" };
130828
+ const blocks = content.filter((item) => item?.type === "thinking");
130829
+ return {
130830
+ content: blocks.map((b2) => b2.thinking).filter(Boolean).join("\n\n"),
130831
+ signature: blocks.map((b2) => b2.signature).filter(Boolean).join("")
130832
+ };
130833
+ }
130834
+ function hasImageBlocks(content) {
130835
+ if (!Array.isArray(content)) return false;
130836
+ return content.some(
130837
+ (item) => item?.type === "image" && (item?.source?.type === "base64" || item?.file?.base64 !== void 0)
130838
+ );
130839
+ }
130840
+ function parseTeammateMessageTag(content) {
130841
+ const match2 = content.match(/<teammate-message\s+([^>]*)>/);
130842
+ if (!match2) return null;
130843
+ const attrs = match2[1];
130844
+ const id = attrs.match(/teammate_id="([^"]*)"/)?.[1];
130845
+ if (!id) return null;
130846
+ const summary = attrs.match(/summary="([^"]*)"/)?.[1];
130847
+ const color = attrs.match(/color="([^"]*)"/)?.[1];
130848
+ return { teammateId: id, summary, color };
130849
+ }
130850
+ function getShortProjectName2(fullPath) {
130851
+ const parts = fullPath.split("/").filter(Boolean);
130852
+ return parts.slice(-3).join("/");
130853
+ }
130854
+ function initialConvState() {
130855
+ return {
130856
+ cwd: "",
130857
+ sessionId: "",
130858
+ sessionName: "",
130859
+ latestTimestamp: "",
130860
+ lastPrompt: "",
130861
+ pendingToolUses: {},
130862
+ teamInfo: {}
130863
+ };
130864
+ }
130865
+ function reduceConvLine(state, entry) {
130866
+ if (entry.cwd && !state.cwd) state.cwd = entry.cwd;
130867
+ if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId;
130868
+ if (entry.slug && !state.sessionName) state.sessionName = entry.slug;
130869
+ if (entry.timestamp) {
130870
+ const ts2 = entry.timestamp;
130871
+ if (!state.latestTimestamp || ts2 > state.latestTimestamp) state.latestTimestamp = ts2;
130872
+ }
130873
+ const type = entry.type;
130874
+ if (type === "last-prompt") {
130875
+ if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt;
130876
+ return null;
130877
+ }
130878
+ if (type !== "user" && type !== "assistant") return null;
130879
+ if (entry.isMeta) return null;
130880
+ const msg = entry.message;
130881
+ const toolUseBlocks = extractToolUseBlocks(msg?.content);
130882
+ for (const block of toolUseBlocks) state.pendingToolUses[block.id] = block;
130883
+ const hasToolUseResult = type === "user" && entry.toolUseResult != null;
130884
+ const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);
130885
+ const content = extractTextContent(msg?.content);
130886
+ const thinking = type === "assistant" ? extractThinking(msg?.content) : null;
130887
+ const hasThinking = !!(thinking?.content || thinking?.signature);
130888
+ if (!(content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking)) return null;
130889
+ const metadata = {};
130890
+ if (msg?.model) metadata.model = msg.model;
130891
+ if (msg?.stop_reason !== void 0) metadata.stopReason = msg.stop_reason;
130892
+ if (entry.gitBranch) metadata.gitBranch = entry.gitBranch;
130893
+ if (entry.version) metadata.version = entry.version;
130894
+ const usage = msg?.usage;
130895
+ if (usage) {
130896
+ if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;
130897
+ if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;
130898
+ if (usage.cache_read_input_tokens) metadata.cacheReadTokens = usage.cache_read_input_tokens;
130899
+ if (usage.cache_creation_input_tokens)
130900
+ metadata.cacheCreationTokens = usage.cache_creation_input_tokens;
130901
+ }
130902
+ const toolUseNames = extractToolUseNames(msg?.content);
130903
+ if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;
130904
+ if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;
130905
+ if (isToolResultOnly) {
130906
+ const pending = new Map(Object.entries(state.pendingToolUses));
130907
+ const toolResultBlocks = extractToolResultBlocks(msg?.content, pending);
130908
+ if (toolResultBlocks.length > 0) {
130909
+ metadata.toolResults = toolResultBlocks;
130910
+ for (const block of toolResultBlocks) delete state.pendingToolUses[block.toolUseId];
130911
+ }
130912
+ }
130913
+ if (entry.teamName) {
130914
+ metadata.teamName = entry.teamName;
130915
+ if (!state.teamInfo[metadata.teamName] && content) {
130916
+ const info = parseTeammateMessageTag(content);
130917
+ if (info) state.teamInfo[metadata.teamName] = info;
130918
+ }
130919
+ }
130920
+ const thinkingContent = thinking?.content || void 0;
130921
+ const thinkingSignature = thinking?.signature || void 0;
130922
+ const hasMetadata = Object.keys(metadata).length > 0;
130923
+ return {
130924
+ role: type,
130925
+ text: content || "",
130926
+ timestamp: entry.timestamp || "",
130927
+ uuid: entry.uuid || void 0,
130928
+ metadata: hasMetadata ? metadata : void 0,
130929
+ isToolResult: isToolResultOnly || void 0,
130930
+ isThinking: thinkingContent || thinkingSignature ? true : void 0,
130931
+ thinkingContent,
130932
+ thinkingSignature,
130933
+ parentUuid: entry.parentUuid !== void 0 ? entry.parentUuid : void 0,
130934
+ requestId: type === "assistant" ? entry.requestId : void 0,
130935
+ promptId: type === "user" ? entry.promptId : void 0,
130936
+ isSidechain: typeof entry.isSidechain === "boolean" ? entry.isSidechain : void 0,
130937
+ permissionMode: type === "user" ? entry.permissionMode : void 0,
130938
+ hasImages: hasImageBlocks(msg?.content) || void 0,
130939
+ attachment: entry.attachment !== void 0 ? entry.attachment : void 0
130940
+ };
130941
+ }
130942
+ function applyTeamInfo(messages, state) {
130943
+ if (Object.keys(state.teamInfo).length === 0) return;
130944
+ for (const m2 of messages) {
130945
+ const name = m2.metadata?.teamName;
130946
+ if (name && state.teamInfo[name] && m2.metadata) m2.metadata.teamInfo = state.teamInfo[name];
130947
+ }
130948
+ }
130517
130949
  var SIDECAR_VERSION = 1;
130518
130950
  function sidecarPath(jsonlPath) {
130519
130951
  return `${jsonlPath}.idx.json`;
@@ -130539,7 +130971,7 @@ function buildSidecar(meta3, cursor, updatedAt) {
130539
130971
  }
130540
130972
  function writeSidecar(jsonlPath, sidecar) {
130541
130973
  try {
130542
- (0, import_fs5.writeFileSync)(sidecarPath(jsonlPath), JSON.stringify(sidecar, null, 2));
130974
+ (0, import_fs6.writeFileSync)(sidecarPath(jsonlPath), JSON.stringify(sidecar, null, 2));
130543
130975
  } catch (err) {
130544
130976
  getLogger2().warn({ jsonlPath, err }, "sidecar: write failed");
130545
130977
  }
@@ -130549,13 +130981,13 @@ function resolveConfigDir(configDir2) {
130549
130981
  return configDir2.replace(/^~/, (0, import_os4.homedir)());
130550
130982
  }
130551
130983
  function getProjectsDir(profile) {
130552
- return (0, import_path3.join)(resolveConfigDir(profile.configDir), "projects");
130984
+ return (0, import_path5.join)(resolveConfigDir(profile.configDir), "projects");
130553
130985
  }
130554
130986
  async function detectDefaultProfile() {
130555
130987
  return {
130556
130988
  id: "default",
130557
130989
  label: "Default",
130558
- configDir: (0, import_path3.join)((0, import_os4.homedir)(), ".claude"),
130990
+ configDir: (0, import_path5.join)((0, import_os4.homedir)(), ".claude"),
130559
130991
  enabled: true,
130560
130992
  emoji: "\u{1F916}"
130561
130993
  };
@@ -130564,7 +130996,7 @@ async function loadProfiles(configPath) {
130564
130996
  const log8 = getLogger2();
130565
130997
  try {
130566
130998
  const resolved = resolveConfigDir(configPath);
130567
- const data = await (0, import_promises4.readFile)((0, import_path3.join)(resolved, PROFILES_FILE), "utf-8");
130999
+ const data = await (0, import_promises4.readFile)((0, import_path5.join)(resolved, PROFILES_FILE), "utf-8");
130568
131000
  const profiles = JSON.parse(data);
130569
131001
  log8.debug({ configPath, count: profiles.length }, "profiles: loaded");
130570
131002
  return profiles;
@@ -130574,34 +131006,6 @@ async function loadProfiles(configPath) {
130574
131006
  return [defaultProfile];
130575
131007
  }
130576
131008
  }
130577
- var SYSTEM_TAGS = [
130578
- "system-reminder",
130579
- "command-name",
130580
- "command-message",
130581
- "command-args",
130582
- "ide_selection",
130583
- "ide_opened_file",
130584
- "local-command-stdout",
130585
- "local-command-caveat",
130586
- "retrieval_status",
130587
- "task_id",
130588
- "task_type",
130589
- "task-id",
130590
- "task-notification",
130591
- "fast_mode_info",
130592
- "persisted-output",
130593
- "tool_use_error",
130594
- "user-prompt-submit-hook",
130595
- "thinking",
130596
- "ask_user",
130597
- "teammate-message"
130598
- ];
130599
- var SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join("|")})[^>]*>[\\s\\S]*?<\\/\\1>`, "g");
130600
- function cleanSystemTags(text) {
130601
- return text.replace(SYSTEM_TAG_RE, "").replace(/[^\S\n]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
130602
- }
130603
- var CLAUDE_CODE_PROVIDER = "claude-code";
130604
- var CODEX_CLI_PROVIDER = "codex-cli";
130605
131009
  var CodexCliProvider = class {
130606
131010
  name = CODEX_CLI_PROVIDER;
130607
131011
  async discover(roots) {
@@ -130734,7 +131138,7 @@ function reduceCodexEntry(acc, entry, tier) {
130734
131138
  }
130735
131139
  function finalizeCodexMeta(acc, filePath, account, tier) {
130736
131140
  if (acc.messageCount === 0) return null;
130737
- const sessionId = acc.sessionId || (0, import_path4.basename)(filePath, ".jsonl");
131141
+ const sessionId = acc.sessionId || (0, import_path6.basename)(filePath, ".jsonl");
130738
131142
  const projectPath = acc.cwd;
130739
131143
  const kind = acc.lastAssistant === null && acc.toolNames.length > 0 ? "task" : "conversation";
130740
131144
  return {
@@ -130746,7 +131150,7 @@ function finalizeCodexMeta(acc, filePath, account, tier) {
130746
131150
  sessionId,
130747
131151
  sessionName: "",
130748
131152
  projectPath,
130749
- projectName: getShortProjectName(projectPath),
131153
+ projectName: getShortProjectName3(projectPath),
130750
131154
  account,
130751
131155
  timestamp: acc.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
130752
131156
  messageCount: acc.messageCount,
@@ -130765,7 +131169,7 @@ function finalizeCodexMeta(acc, filePath, account, tier) {
130765
131169
  lastPrompt: acc.lastUser?.text || void 0
130766
131170
  };
130767
131171
  }
130768
- function getShortProjectName(fullPath) {
131172
+ function getShortProjectName3(fullPath) {
130769
131173
  return fullPath.split("/").filter(Boolean).slice(-3).join("/");
130770
131174
  }
130771
131175
  async function parseCodexConversation(filePath, account) {
@@ -130776,7 +131180,7 @@ async function parseCodexConversation(filePath, account) {
130776
131180
  let cwd = "";
130777
131181
  let latestTimestamp = "";
130778
131182
  let lastUserText = "";
130779
- const rl = (0, import_readline.createInterface)({ input: (0, import_fs6.createReadStream)(filePath), crlfDelay: Infinity });
131183
+ const rl = (0, import_readline2.createInterface)({ input: (0, import_fs7.createReadStream)(filePath), crlfDelay: Infinity });
130780
131184
  try {
130781
131185
  for await (const line of rl) {
130782
131186
  if (!line.trim()) continue;
@@ -130813,8 +131217,8 @@ async function parseCodexConversation(filePath, account) {
130813
131217
  id: filePath,
130814
131218
  filePath,
130815
131219
  projectPath: cwd,
130816
- projectName: getShortProjectName(cwd),
130817
- sessionId: sessionId || (0, import_path4.basename)(filePath, ".jsonl"),
131220
+ projectName: getShortProjectName3(cwd),
131221
+ sessionId: sessionId || (0, import_path6.basename)(filePath, ".jsonl"),
130818
131222
  sessionName: "",
130819
131223
  messages,
130820
131224
  fullText: textParts.join(" "),
@@ -130886,404 +131290,6 @@ async function discoverJsonlFiles(dirs, onProgress) {
130886
131290
  log8.debug({ totalFiles: results.length, dirs: dirs.length }, "discovery: complete");
130887
131291
  return results;
130888
131292
  }
130889
- function initialConvState() {
130890
- return {
130891
- cwd: "",
130892
- sessionId: "",
130893
- sessionName: "",
130894
- latestTimestamp: "",
130895
- lastPrompt: "",
130896
- pendingToolUses: {},
130897
- teamInfo: {}
130898
- };
130899
- }
130900
- function reduceConvLine(state, entry) {
130901
- if (entry.cwd && !state.cwd) state.cwd = entry.cwd;
130902
- if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId;
130903
- if (entry.slug && !state.sessionName) state.sessionName = entry.slug;
130904
- if (entry.timestamp) {
130905
- const ts2 = entry.timestamp;
130906
- if (!state.latestTimestamp || ts2 > state.latestTimestamp) state.latestTimestamp = ts2;
130907
- }
130908
- const type = entry.type;
130909
- if (type === "last-prompt") {
130910
- if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt;
130911
- return null;
130912
- }
130913
- if (type !== "user" && type !== "assistant") return null;
130914
- if (entry.isMeta) return null;
130915
- const msg = entry.message;
130916
- const toolUseBlocks = extractToolUseBlocks(msg?.content);
130917
- for (const block of toolUseBlocks) state.pendingToolUses[block.id] = block;
130918
- const hasToolUseResult = type === "user" && entry.toolUseResult != null;
130919
- const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);
130920
- const content = extractTextContent(msg?.content);
130921
- const thinking = type === "assistant" ? extractThinking(msg?.content) : null;
130922
- const hasThinking = !!(thinking?.content || thinking?.signature);
130923
- if (!(content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking)) return null;
130924
- const metadata = {};
130925
- if (msg?.model) metadata.model = msg.model;
130926
- if (msg?.stop_reason !== void 0) metadata.stopReason = msg.stop_reason;
130927
- if (entry.gitBranch) metadata.gitBranch = entry.gitBranch;
130928
- if (entry.version) metadata.version = entry.version;
130929
- const usage = msg?.usage;
130930
- if (usage) {
130931
- if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;
130932
- if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;
130933
- if (usage.cache_read_input_tokens) metadata.cacheReadTokens = usage.cache_read_input_tokens;
130934
- if (usage.cache_creation_input_tokens)
130935
- metadata.cacheCreationTokens = usage.cache_creation_input_tokens;
130936
- }
130937
- const toolUseNames = extractToolUseNames(msg?.content);
130938
- if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;
130939
- if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;
130940
- if (isToolResultOnly) {
130941
- const pending = new Map(Object.entries(state.pendingToolUses));
130942
- const toolResultBlocks = extractToolResultBlocks(msg?.content, pending);
130943
- if (toolResultBlocks.length > 0) metadata.toolResults = toolResultBlocks;
130944
- }
130945
- if (entry.teamName) {
130946
- metadata.teamName = entry.teamName;
130947
- if (!state.teamInfo[metadata.teamName] && content) {
130948
- const info = parseTeammateMessageTag(content);
130949
- if (info) state.teamInfo[metadata.teamName] = info;
130950
- }
130951
- }
130952
- const thinkingContent = thinking?.content || void 0;
130953
- const thinkingSignature = thinking?.signature || void 0;
130954
- const hasMetadata = Object.keys(metadata).length > 0;
130955
- return {
130956
- role: type,
130957
- text: content || "",
130958
- timestamp: entry.timestamp || "",
130959
- uuid: entry.uuid || void 0,
130960
- metadata: hasMetadata ? metadata : void 0,
130961
- isToolResult: isToolResultOnly || void 0,
130962
- isThinking: thinkingContent || thinkingSignature ? true : void 0,
130963
- thinkingContent,
130964
- thinkingSignature,
130965
- parentUuid: entry.parentUuid !== void 0 ? entry.parentUuid : void 0,
130966
- requestId: type === "assistant" ? entry.requestId : void 0,
130967
- promptId: type === "user" ? entry.promptId : void 0,
130968
- isSidechain: typeof entry.isSidechain === "boolean" ? entry.isSidechain : void 0,
130969
- permissionMode: type === "user" ? entry.permissionMode : void 0,
130970
- hasImages: hasImageBlocks(msg?.content) || void 0,
130971
- attachment: entry.attachment !== void 0 ? entry.attachment : void 0
130972
- };
130973
- }
130974
- function applyTeamInfo(messages, state) {
130975
- if (Object.keys(state.teamInfo).length === 0) return;
130976
- for (const m2 of messages) {
130977
- const name = m2.metadata?.teamName;
130978
- if (name && state.teamInfo[name] && m2.metadata) m2.metadata.teamInfo = state.teamInfo[name];
130979
- }
130980
- }
130981
- async function parseMeta(filePath, account, tier) {
130982
- const log8 = getLogger2();
130983
- log8.trace({ filePath, account, tier: tier.name }, "parseMeta: start");
130984
- const state = initialReducerState();
130985
- const fileStream = (0, import_fs7.createReadStream)(filePath);
130986
- const rl = (0, import_readline2.createInterface)({ input: fileStream, crlfDelay: Infinity });
130987
- try {
130988
- for await (const line of rl) {
130989
- if (!line.trim()) continue;
130990
- let entry;
130991
- try {
130992
- entry = JSON.parse(line);
130993
- } catch {
130994
- state.badJsonLines++;
130995
- continue;
130996
- }
130997
- reduceLine(state, entry, tier);
130998
- }
130999
- } catch (err) {
131000
- log8.warn({ filePath, err }, "parseMeta: read failed");
131001
- return null;
131002
- }
131003
- if (state.badJsonLines > 0) {
131004
- log8.warn(
131005
- { filePath, badJsonLines: state.badJsonLines },
131006
- "parseMeta: skipped malformed JSON lines"
131007
- );
131008
- }
131009
- const meta3 = finalizeMeta(state, filePath, account, tier);
131010
- if (!meta3) log8.trace({ filePath }, "parseMeta: no messages");
131011
- return meta3;
131012
- }
131013
- async function parseConversation(filePath, account) {
131014
- const log8 = getLogger2();
131015
- log8.trace({ filePath, account }, "parseConversation: start");
131016
- const messages = [];
131017
- let badJsonLines = 0;
131018
- const textParts = [];
131019
- const turnDurations = [];
131020
- const state = initialConvState();
131021
- const fileStream = (0, import_fs7.createReadStream)(filePath);
131022
- const rl = (0, import_readline2.createInterface)({ input: fileStream, crlfDelay: Infinity });
131023
- try {
131024
- for await (const line of rl) {
131025
- if (!line.trim()) continue;
131026
- let entry;
131027
- try {
131028
- entry = JSON.parse(line);
131029
- } catch {
131030
- badJsonLines++;
131031
- continue;
131032
- }
131033
- if (entry.type === "system" && entry.subtype === "turn_duration" && typeof entry.durationMs === "number") {
131034
- turnDurations.push({
131035
- durationMs: entry.durationMs,
131036
- messageCount: entry.messageCount || 0,
131037
- uuid: entry.uuid
131038
- });
131039
- continue;
131040
- }
131041
- const message = reduceConvLine(state, entry);
131042
- if (message) {
131043
- messages.push(message);
131044
- if (message.text) textParts.push(message.text);
131045
- }
131046
- }
131047
- } catch (err) {
131048
- log8.warn({ filePath, err }, "parseConversation: read failed");
131049
- return null;
131050
- }
131051
- if (badJsonLines > 0) {
131052
- log8.warn({ filePath, badJsonLines }, "parseConversation: skipped malformed JSON lines");
131053
- }
131054
- if (messages.length === 0) {
131055
- log8.trace({ filePath }, "parseConversation: no messages");
131056
- return null;
131057
- }
131058
- log8.debug({ filePath, messageCount: messages.length }, "parseConversation: complete");
131059
- applyTeamInfo(messages, state);
131060
- return {
131061
- id: filePath,
131062
- filePath,
131063
- projectPath: state.cwd,
131064
- projectName: getShortProjectName2(state.cwd),
131065
- sessionId: state.sessionId || (0, import_path6.basename)(filePath, ".jsonl"),
131066
- sessionName: state.sessionName,
131067
- messages,
131068
- fullText: textParts.join(" "),
131069
- timestamp: state.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
131070
- messageCount: messages.length,
131071
- account,
131072
- turnDurations: turnDurations.length > 0 ? turnDurations : void 0,
131073
- lastPrompt: state.lastPrompt || void 0
131074
- };
131075
- }
131076
- function extractTextContent(content) {
131077
- if (!content) return "";
131078
- if (typeof content === "string") return cleanSystemTags(content);
131079
- if (Array.isArray(content)) {
131080
- return content.map((item) => {
131081
- if (typeof item === "string") return item;
131082
- if (item?.type === "text" && item?.text) return item.text;
131083
- if (item?.type === "tool_result" && typeof item?.content === "string") return item.content;
131084
- return "";
131085
- }).filter(Boolean).map(cleanSystemTags).join(" ");
131086
- }
131087
- return "";
131088
- }
131089
- function extractToolUseNames(content) {
131090
- if (!Array.isArray(content)) return [];
131091
- return content.filter((item) => item?.type === "tool_use" && item?.name).map((item) => item.name);
131092
- }
131093
- function extractToolUseBlocks(content) {
131094
- if (!Array.isArray(content)) return [];
131095
- return content.filter((item) => item?.type === "tool_use" && item?.name && item?.id).map((item) => ({
131096
- id: item.id,
131097
- name: item.name,
131098
- input: item.input || {}
131099
- }));
131100
- }
131101
- var TOOL_NAME_TO_TYPE = {
131102
- Edit: "edit",
131103
- Write: "write",
131104
- Read: "read",
131105
- Bash: "bash",
131106
- Grep: "grep",
131107
- Glob: "glob",
131108
- Agent: "taskAgent",
131109
- TaskCreate: "taskCreate",
131110
- TaskUpdate: "taskUpdate"
131111
- };
131112
- function extractToolResultBlocks(content, pendingToolUses) {
131113
- if (!Array.isArray(content)) return [];
131114
- return content.filter((item) => item?.type === "tool_result" && item?.tool_use_id).map((item) => {
131115
- const toolName = pendingToolUses.get(item.tool_use_id)?.name ?? "";
131116
- return {
131117
- toolUseId: item.tool_use_id,
131118
- type: TOOL_NAME_TO_TYPE[toolName] ?? "generic",
131119
- content: typeof item.content === "string" ? { text: item.content } : item.content ?? {},
131120
- isError: typeof item.is_error === "boolean" ? item.is_error : void 0
131121
- };
131122
- });
131123
- }
131124
- function collectToolNames(content, toolSet) {
131125
- if (!Array.isArray(content)) return;
131126
- for (const item of content) {
131127
- if (item?.type === "tool_use" && item?.name) {
131128
- toolSet.add(item.name);
131129
- }
131130
- }
131131
- }
131132
- function isOnlyToolResultContent(content) {
131133
- if (!Array.isArray(content)) return false;
131134
- return content.length > 0 && content.every((item) => item?.type === "tool_result");
131135
- }
131136
- function isTeammateContent(content) {
131137
- const raw2 = typeof content === "string" ? content : Array.isArray(content) ? content.map(
131138
- (item) => typeof item === "string" ? item : item?.type === "text" ? item.text ?? "" : ""
131139
- ).join("") : "";
131140
- return raw2.includes("<teammate-message");
131141
- }
131142
- function extractThinking(content) {
131143
- if (!Array.isArray(content)) return { content: "", signature: "" };
131144
- const blocks = content.filter((item) => item?.type === "thinking");
131145
- return {
131146
- content: blocks.map((b2) => b2.thinking).filter(Boolean).join("\n\n"),
131147
- signature: blocks.map((b2) => b2.signature).filter(Boolean).join("")
131148
- };
131149
- }
131150
- function hasImageBlocks(content) {
131151
- if (!Array.isArray(content)) return false;
131152
- return content.some(
131153
- (item) => item?.type === "image" && (item?.source?.type === "base64" || item?.file?.base64 !== void 0)
131154
- );
131155
- }
131156
- function parseTeammateMessageTag(content) {
131157
- const match2 = content.match(/<teammate-message\s+([^>]*)>/);
131158
- if (!match2) return null;
131159
- const attrs = match2[1];
131160
- const id = attrs.match(/teammate_id="([^"]*)"/)?.[1];
131161
- if (!id) return null;
131162
- const summary = attrs.match(/summary="([^"]*)"/)?.[1];
131163
- const color = attrs.match(/color="([^"]*)"/)?.[1];
131164
- return { teammateId: id, summary, color };
131165
- }
131166
- function getShortProjectName2(fullPath) {
131167
- const parts = fullPath.split("/").filter(Boolean);
131168
- return parts.slice(-3).join("/");
131169
- }
131170
- function initialReducerState() {
131171
- return {
131172
- sessionId: "",
131173
- sessionName: "",
131174
- latestTimestamp: "",
131175
- cwd: "",
131176
- teamName: "",
131177
- model: null,
131178
- messageCount: 0,
131179
- lastMessageSender: "user",
131180
- isTeammate: false,
131181
- firstUserSeen: false,
131182
- firstMessage: null,
131183
- lastMessage: null,
131184
- lastPrompt: "",
131185
- pageMessageCount: 0,
131186
- toolNames: [],
131187
- previewParts: [],
131188
- snippetParts: [],
131189
- previewLength: 0,
131190
- snippetLength: 0,
131191
- badJsonLines: 0
131192
- };
131193
- }
131194
- function reduceLine(state, entry, tier) {
131195
- if (entry.cwd && !state.cwd) state.cwd = entry.cwd;
131196
- if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId;
131197
- if (entry.slug && !state.sessionName) state.sessionName = entry.slug;
131198
- if (entry.teamName && !state.teamName) state.teamName = entry.teamName;
131199
- if (entry.timestamp) {
131200
- const ts2 = entry.timestamp;
131201
- if (!state.latestTimestamp || ts2 > state.latestTimestamp) state.latestTimestamp = ts2;
131202
- }
131203
- const type = entry.type;
131204
- if (type === "last-prompt") {
131205
- if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt;
131206
- return;
131207
- }
131208
- if (type !== "user" && type !== "assistant") return;
131209
- if (entry.isMeta) return;
131210
- const msg = entry.message;
131211
- if (state.model === null && msg?.model) state.model = msg.model;
131212
- if (type === "user" && !state.firstUserSeen) {
131213
- state.firstUserSeen = true;
131214
- if (isTeammateContent(msg?.content)) state.isTeammate = true;
131215
- }
131216
- const content = extractTextContent(msg?.content);
131217
- const hasToolUseResult = type === "user" && entry.toolUseResult != null;
131218
- const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);
131219
- const toolSet = new Set(state.toolNames);
131220
- collectToolNames(msg?.content, toolSet);
131221
- state.toolNames = Array.from(toolSet);
131222
- const toolUseBlocks = extractToolUseBlocks(msg?.content);
131223
- const thinking = type === "assistant" ? extractThinking(msg?.content) : null;
131224
- const hasThinking = !!(thinking?.content || thinking?.signature);
131225
- if (content || isOnlyToolResult || toolUseBlocks.length > 0 || hasThinking) {
131226
- state.pageMessageCount++;
131227
- }
131228
- if (content || isOnlyToolResult) {
131229
- state.messageCount++;
131230
- state.lastMessageSender = type;
131231
- if (content) {
131232
- const ts2 = entry.timestamp || "";
131233
- if (!state.firstMessage) state.firstMessage = { text: content.slice(0, 200), timestamp: ts2 };
131234
- state.lastMessage = { text: content.slice(0, 200), timestamp: ts2 };
131235
- if (state.previewLength < tier.previewMax) {
131236
- state.previewParts.push(content);
131237
- state.previewLength += content.length;
131238
- }
131239
- if (state.snippetLength < tier.snippetMax) {
131240
- const remaining = tier.snippetMax - state.snippetLength;
131241
- const chunk = content.length > remaining ? content.slice(0, remaining) : content;
131242
- state.snippetParts.push(chunk);
131243
- state.snippetLength += chunk.length;
131244
- }
131245
- }
131246
- }
131247
- }
131248
- function finalizeMeta(state, filePath, account, tier) {
131249
- if (state.messageCount === 0) return null;
131250
- const isSubagent = filePath.includes("/subagents/");
131251
- let parentSessionId = null;
131252
- if (isSubagent) {
131253
- const uuidDir = (0, import_path5.dirname)((0, import_path5.dirname)(filePath));
131254
- parentSessionId = (0, import_path5.join)((0, import_path5.dirname)(uuidDir), `${(0, import_path5.basename)(uuidDir)}.jsonl`);
131255
- }
131256
- const projectPath = state.cwd;
131257
- return {
131258
- id: filePath,
131259
- filePath,
131260
- provider: CLAUDE_CODE_PROVIDER,
131261
- sessionId: state.sessionId || (0, import_path5.basename)(filePath, ".jsonl"),
131262
- sessionName: state.sessionName,
131263
- projectPath,
131264
- projectName: getShortProjectName3(projectPath),
131265
- account,
131266
- timestamp: state.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
131267
- messageCount: state.messageCount,
131268
- lastMessageSender: state.lastMessageSender,
131269
- preview: state.previewParts.join(" ").slice(0, tier.previewMax),
131270
- contentSnippet: state.snippetParts.join(" "),
131271
- gitBranch: null,
131272
- model: state.model,
131273
- isSubagent,
131274
- parentSessionId,
131275
- isTeammate: state.isTeammate,
131276
- teamName: state.teamName || null,
131277
- toolNames: state.toolNames,
131278
- firstMessage: state.firstMessage,
131279
- lastMessage: state.lastMessage,
131280
- lastPrompt: state.lastPrompt || void 0
131281
- };
131282
- }
131283
- function getShortProjectName3(fullPath) {
131284
- const parts = fullPath.split("/").filter(Boolean);
131285
- return parts.slice(-3).join("/");
131286
- }
131287
131293
  var ThreadbaseProvider = class {
131288
131294
  name = CLAUDE_CODE_PROVIDER;
131289
131295
  // Roots are passed as "<projectsDir>\0<account>" so the scanner can carry the
@@ -131352,32 +131358,219 @@ var LRUCache = class {
131352
131358
  return this.map.size;
131353
131359
  }
131354
131360
  };
131361
+ var YIELD_EVERY_LINES = 500;
131362
+ async function tailReduce(filePath, startOffset, startLine, state, tier) {
131363
+ const stream = (0, import_fs10.createReadStream)(filePath, { start: startOffset, encoding: "utf8" });
131364
+ let buffer = "";
131365
+ let offset = startOffset;
131366
+ let line = startLine;
131367
+ let parsedLines = 0;
131368
+ let sinceYield = 0;
131369
+ for await (const chunk of stream) {
131370
+ buffer += chunk;
131371
+ let nl;
131372
+ while ((nl = buffer.indexOf("\n")) >= 0) {
131373
+ const lineWithNewline = buffer.slice(0, nl + 1);
131374
+ const text = lineWithNewline.trimEnd();
131375
+ buffer = buffer.slice(nl + 1);
131376
+ if (text.length > 0) {
131377
+ try {
131378
+ reduceLine(state, JSON.parse(text), tier);
131379
+ } catch {
131380
+ state.badJsonLines++;
131381
+ }
131382
+ parsedLines++;
131383
+ }
131384
+ offset += Buffer.byteLength(lineWithNewline, "utf8");
131385
+ line++;
131386
+ if (++sinceYield >= YIELD_EVERY_LINES) {
131387
+ sinceYield = 0;
131388
+ await (0, import_promises8.setImmediate)();
131389
+ }
131390
+ }
131391
+ }
131392
+ return { newOffset: offset, newLine: line, parsedLines, badJsonLines: state.badJsonLines };
131393
+ }
131394
+ var CHECKPOINT_INTERVAL = 500;
131395
+ async function streamMessages(filePath, startOffset, startLine, state, onMessage, onEntry) {
131396
+ const stream = (0, import_fs9.createReadStream)(filePath, { start: startOffset, encoding: "utf8" });
131397
+ let buffer = "";
131398
+ let offset = startOffset;
131399
+ let line = startLine;
131400
+ let sinceYield = 0;
131401
+ for await (const chunk of stream) {
131402
+ buffer += chunk;
131403
+ let nl;
131404
+ while ((nl = buffer.indexOf("\n")) >= 0) {
131405
+ const lineWithNewline = buffer.slice(0, nl + 1);
131406
+ const text = lineWithNewline.trimEnd();
131407
+ buffer = buffer.slice(nl + 1);
131408
+ offset += Buffer.byteLength(lineWithNewline, "utf8");
131409
+ line += 1;
131410
+ if (++sinceYield >= YIELD_EVERY_LINES) {
131411
+ sinceYield = 0;
131412
+ await (0, import_promises7.setImmediate)();
131413
+ }
131414
+ if (text.length === 0) continue;
131415
+ let entry;
131416
+ try {
131417
+ entry = JSON.parse(text);
131418
+ } catch {
131419
+ continue;
131420
+ }
131421
+ if (onEntry?.(entry)) continue;
131422
+ const message = reduceConvLine(state, entry);
131423
+ if (message && onMessage(message, offset, line)) {
131424
+ stream.destroy();
131425
+ return { offset, line };
131426
+ }
131427
+ }
131428
+ }
131429
+ return { offset, line };
131430
+ }
131431
+ async function buildCheckpoints(filePath, interval = CHECKPOINT_INTERVAL, from = null) {
131432
+ const checkpoints = [];
131433
+ const state = from ? from.state : initialConvState();
131434
+ let index = from ? from.messageIndex : 0;
131435
+ await streamMessages(
131436
+ filePath,
131437
+ from?.byteOffset ?? 0,
131438
+ from?.lineNumber ?? 0,
131439
+ state,
131440
+ (_msg, nextOffset, nextLine) => {
131441
+ index += 1;
131442
+ if (index % interval === 0) {
131443
+ checkpoints.push({
131444
+ messageIndex: index,
131445
+ byteOffset: nextOffset,
131446
+ lineNumber: nextLine,
131447
+ state: structuredClone(state)
131448
+ });
131449
+ }
131450
+ return false;
131451
+ }
131452
+ );
131453
+ return checkpoints;
131454
+ }
131455
+ async function readPage(filePath, total, options, floor) {
131456
+ const beforeIndex = options.beforeIndex ?? total;
131457
+ const fromIndex = Math.max(0, beforeIndex - options.limit);
131458
+ const state = floor ? structuredClone(floor.state) : initialConvState();
131459
+ const startOffset = floor ? floor.byteOffset : 0;
131460
+ const startLine = floor ? floor.lineNumber : 0;
131461
+ let index = floor ? floor.messageIndex : 0;
131462
+ const window2 = [];
131463
+ await streamMessages(filePath, startOffset, startLine, state, (message) => {
131464
+ const current = index;
131465
+ index += 1;
131466
+ if (current >= fromIndex && current < beforeIndex) window2.push(message);
131467
+ return index >= beforeIndex;
131468
+ });
131469
+ applyTeamInfo(window2, state);
131470
+ return { messages: window2, total, fromIndex };
131471
+ }
131472
+ async function foldTail(filePath, resume) {
131473
+ const messages = [];
131474
+ const textParts = [];
131475
+ const turnDurations = [];
131476
+ const end = await streamMessages(
131477
+ filePath,
131478
+ resume.offset,
131479
+ resume.line,
131480
+ resume.state,
131481
+ (message) => {
131482
+ messages.push(message);
131483
+ if (message.text) textParts.push(message.text);
131484
+ return false;
131485
+ },
131486
+ (entry) => {
131487
+ if (entry.type === "system" && entry.subtype === "turn_duration" && typeof entry.durationMs === "number") {
131488
+ turnDurations.push({
131489
+ durationMs: entry.durationMs,
131490
+ messageCount: entry.messageCount || 0,
131491
+ uuid: entry.uuid
131492
+ });
131493
+ return true;
131494
+ }
131495
+ return false;
131496
+ }
131497
+ );
131498
+ return { messages, textParts, turnDurations, end };
131499
+ }
131500
+ function assemble(filePath, account, messages, fullText, turnDurations, state) {
131501
+ return {
131502
+ id: filePath,
131503
+ filePath,
131504
+ projectPath: state.cwd,
131505
+ projectName: getShortProjectName2(state.cwd),
131506
+ sessionId: state.sessionId || (0, import_path8.basename)(filePath, ".jsonl"),
131507
+ sessionName: state.sessionName,
131508
+ messages,
131509
+ fullText,
131510
+ timestamp: state.latestTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
131511
+ messageCount: messages.length,
131512
+ account,
131513
+ turnDurations: turnDurations.length > 0 ? turnDurations : void 0,
131514
+ lastPrompt: state.lastPrompt || void 0
131515
+ };
131516
+ }
131517
+ async function parseConversationResumable(filePath, account) {
131518
+ const resume = { state: initialConvState(), offset: 0, line: 0 };
131519
+ const { messages, textParts, turnDurations, end } = await foldTail(filePath, resume);
131520
+ if (messages.length === 0) return null;
131521
+ applyTeamInfo(messages, resume.state);
131522
+ const conversation = assemble(
131523
+ filePath,
131524
+ account,
131525
+ messages,
131526
+ textParts.join(" "),
131527
+ turnDurations,
131528
+ resume.state
131529
+ );
131530
+ return { conversation, resume: { state: resume.state, offset: end.offset, line: end.line } };
131531
+ }
131532
+ async function extendConversation(previous, resume, filePath, account) {
131533
+ const { messages: fresh, textParts, turnDurations, end } = await foldTail(filePath, resume);
131534
+ const messages = fresh.length > 0 ? previous.messages.concat(fresh) : previous.messages;
131535
+ applyTeamInfo(messages, resume.state);
131536
+ const fullText = textParts.length === 0 ? previous.fullText : previous.fullText ? `${previous.fullText} ${textParts.join(" ")}` : textParts.join(" ");
131537
+ const allTurnDurations = (previous.turnDurations ?? []).concat(turnDurations);
131538
+ const conversation = assemble(
131539
+ filePath,
131540
+ account,
131541
+ messages,
131542
+ fullText,
131543
+ allTurnDurations,
131544
+ resume.state
131545
+ );
131546
+ return { conversation, resume: { state: resume.state, offset: end.offset, line: end.line } };
131547
+ }
131355
131548
  var FP_BYTES = 4096;
131356
131549
  function fingerprint(filePath, size) {
131357
131550
  const hash2 = (0, import_crypto2.createHash)("sha1");
131358
131551
  hash2.update(String(size));
131359
- const fd = (0, import_fs9.openSync)(filePath, "r");
131552
+ const fd = (0, import_fs11.openSync)(filePath, "r");
131360
131553
  try {
131361
131554
  const head = Buffer.alloc(Math.min(FP_BYTES, size));
131362
131555
  if (head.length > 0) {
131363
- (0, import_fs9.readSync)(fd, head, 0, head.length, 0);
131556
+ (0, import_fs11.readSync)(fd, head, 0, head.length, 0);
131364
131557
  hash2.update(head);
131365
131558
  }
131366
131559
  if (size > FP_BYTES) {
131367
131560
  const tailLen = Math.min(FP_BYTES, size);
131368
131561
  const tail = Buffer.alloc(tailLen);
131369
- (0, import_fs9.readSync)(fd, tail, 0, tailLen, size - tailLen);
131562
+ (0, import_fs11.readSync)(fd, tail, 0, tailLen, size - tailLen);
131370
131563
  hash2.update(tail);
131371
131564
  }
131372
131565
  } finally {
131373
- (0, import_fs9.closeSync)(fd);
131566
+ (0, import_fs11.closeSync)(fd);
131374
131567
  }
131375
131568
  return hash2.digest("hex");
131376
131569
  }
131377
131570
  function classify(filePath, existing) {
131378
131571
  let stat42;
131379
131572
  try {
131380
- const s3 = (0, import_fs9.statSync)(filePath);
131573
+ const s3 = (0, import_fs11.statSync)(filePath);
131381
131574
  stat42 = { size: s3.size, mtimeMs: s3.mtimeMs };
131382
131575
  } catch {
131383
131576
  return { change: "vanished" };
@@ -131407,7 +131600,7 @@ async function parseMetaWithProvider(provider, filePath, account, tier) {
131407
131600
  const log8 = getLogger2();
131408
131601
  const acc = provider.createEmptyAccumulator();
131409
131602
  const rl = (0, import_readline3.createInterface)({
131410
- input: (0, import_fs10.createReadStream)(filePath),
131603
+ input: (0, import_fs12.createReadStream)(filePath),
131411
131604
  crlfDelay: Infinity
131412
131605
  });
131413
131606
  try {
@@ -131630,7 +131823,7 @@ function hasColumn(db, table, column) {
131630
131823
  }
131631
131824
  function openDatabase(dbPath) {
131632
131825
  if (dbPath !== ":memory:") {
131633
- (0, import_fs11.mkdirSync)((0, import_path8.dirname)(dbPath), { recursive: true });
131826
+ (0, import_fs13.mkdirSync)((0, import_path9.dirname)(dbPath), { recursive: true });
131634
131827
  }
131635
131828
  const db = new import_better_sqlite3.default(dbPath);
131636
131829
  db.pragma("journal_mode = WAL");
@@ -131656,7 +131849,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
131656
131849
  for (const projectDir of resolved.entries) {
131657
131850
  let dirStat;
131658
131851
  try {
131659
- dirStat = await (0, import_promises7.stat)(projectDir);
131852
+ dirStat = await (0, import_promises9.stat)(projectDir);
131660
131853
  } catch {
131661
131854
  scannedDirs.remove(projectDir);
131662
131855
  continue;
@@ -131694,7 +131887,7 @@ async function discoverJsonlFilesGated(dirs, files, scannedDirs, options = {}) {
131694
131887
  async function resolveProjectDirs(projectsDir, scannedDirs) {
131695
131888
  let rootStat;
131696
131889
  try {
131697
- rootStat = await (0, import_promises7.stat)(projectsDir);
131890
+ rootStat = await (0, import_promises9.stat)(projectsDir);
131698
131891
  } catch {
131699
131892
  return null;
131700
131893
  }
@@ -131704,7 +131897,7 @@ async function resolveProjectDirs(projectsDir, scannedDirs) {
131704
131897
  }
131705
131898
  let entries;
131706
131899
  try {
131707
- entries = (await (0, import_promises7.readdir)(projectsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => joinPath(projectsDir, e.name));
131900
+ entries = (await (0, import_promises9.readdir)(projectsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => joinPath(projectsDir, e.name));
131708
131901
  } catch {
131709
131902
  return null;
131710
131903
  }
@@ -131717,98 +131910,6 @@ function dirnameOf(filePath) {
131717
131910
  function joinPath(dir, name) {
131718
131911
  return dir.endsWith("/") ? `${dir}${name}` : `${dir}/${name}`;
131719
131912
  }
131720
- async function tailReduce(filePath, startOffset, startLine, state, tier) {
131721
- const stream = (0, import_fs12.createReadStream)(filePath, { start: startOffset, encoding: "utf8" });
131722
- let buffer = "";
131723
- let offset = startOffset;
131724
- let line = startLine;
131725
- let parsedLines = 0;
131726
- for await (const chunk of stream) {
131727
- buffer += chunk;
131728
- let nl;
131729
- while ((nl = buffer.indexOf("\n")) >= 0) {
131730
- const lineWithNewline = buffer.slice(0, nl + 1);
131731
- const text = lineWithNewline.trimEnd();
131732
- buffer = buffer.slice(nl + 1);
131733
- if (text.length > 0) {
131734
- try {
131735
- reduceLine(state, JSON.parse(text), tier);
131736
- } catch {
131737
- state.badJsonLines++;
131738
- }
131739
- parsedLines++;
131740
- }
131741
- offset += Buffer.byteLength(lineWithNewline, "utf8");
131742
- line++;
131743
- }
131744
- }
131745
- return { newOffset: offset, newLine: line, parsedLines, badJsonLines: state.badJsonLines };
131746
- }
131747
- var CHECKPOINT_INTERVAL = 500;
131748
- async function streamMessages(filePath, startOffset, startLine, state, onMessage) {
131749
- const stream = (0, import_fs13.createReadStream)(filePath, { start: startOffset, encoding: "utf8" });
131750
- let buffer = "";
131751
- let offset = startOffset;
131752
- let line = startLine;
131753
- for await (const chunk of stream) {
131754
- buffer += chunk;
131755
- let nl;
131756
- while ((nl = buffer.indexOf("\n")) >= 0) {
131757
- const lineWithNewline = buffer.slice(0, nl + 1);
131758
- const text = lineWithNewline.trimEnd();
131759
- buffer = buffer.slice(nl + 1);
131760
- offset += Buffer.byteLength(lineWithNewline, "utf8");
131761
- line += 1;
131762
- if (text.length === 0) continue;
131763
- let entry;
131764
- try {
131765
- entry = JSON.parse(text);
131766
- } catch {
131767
- continue;
131768
- }
131769
- const message = reduceConvLine(state, entry);
131770
- if (message && onMessage(message, offset, line)) {
131771
- stream.destroy();
131772
- return;
131773
- }
131774
- }
131775
- }
131776
- }
131777
- async function buildCheckpoints(filePath, interval = CHECKPOINT_INTERVAL) {
131778
- const checkpoints = [];
131779
- const state = initialConvState();
131780
- let index = 0;
131781
- await streamMessages(filePath, 0, 0, state, (_msg, nextOffset, nextLine) => {
131782
- index += 1;
131783
- if (index % interval === 0) {
131784
- checkpoints.push({
131785
- messageIndex: index,
131786
- byteOffset: nextOffset,
131787
- lineNumber: nextLine,
131788
- state: structuredClone(state)
131789
- });
131790
- }
131791
- return false;
131792
- });
131793
- return checkpoints;
131794
- }
131795
- async function readPage(filePath, total, options, floor) {
131796
- const beforeIndex = options.beforeIndex ?? total;
131797
- const fromIndex = Math.max(0, beforeIndex - options.limit);
131798
- const state = floor ? structuredClone(floor.state) : initialConvState();
131799
- const startOffset = floor ? floor.byteOffset : 0;
131800
- const startLine = floor ? floor.lineNumber : 0;
131801
- let index = floor ? floor.messageIndex : 0;
131802
- const window2 = [];
131803
- await streamMessages(filePath, startOffset, startLine, state, (message) => {
131804
- const current = index;
131805
- index += 1;
131806
- if (current >= fromIndex && current < beforeIndex) window2.push(message);
131807
- return index >= beforeIndex;
131808
- });
131809
- applyTeamInfo(window2, state);
131810
- return { messages: window2, total, fromIndex };
131811
- }
131812
131913
  var CheckpointsRepo = class {
131813
131914
  constructor(db) {
131814
131915
  this.db = db;
@@ -131828,6 +131929,22 @@ var CheckpointsRepo = class {
131828
131929
  });
131829
131930
  tx();
131830
131931
  }
131932
+ // Insert checkpoints without touching existing rows. Appends never invalidate
131933
+ // the chain covering the immutable prefix (Kafka sparse-index style); rows are
131934
+ // only ever removed on truncation/replace or deletion.
131935
+ append(sourcePath, checkpoints) {
131936
+ const tx = this.db.transaction(() => {
131937
+ const insert = this.db.prepare(
131938
+ `INSERT INTO message_checkpoints
131939
+ (source_path, message_index, byte_offset, line_number, parser_state)
131940
+ VALUES (?, ?, ?, ?, ?)`
131941
+ );
131942
+ for (const c of checkpoints) {
131943
+ insert.run(sourcePath, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));
131944
+ }
131945
+ });
131946
+ tx();
131947
+ }
131831
131948
  // The latest checkpoint at or before `messageIndex`, or null if none (read
131832
131949
  // from the file start). Lets a page seek to the nearest prior anchor.
131833
131950
  floor(sourcePath, messageIndex) {
@@ -131839,6 +131956,17 @@ var CheckpointsRepo = class {
131839
131956
  ).get(sourcePath, messageIndex);
131840
131957
  return row ? toCheckpoint(row) : null;
131841
131958
  }
131959
+ // The highest-index checkpoint for a file, or null if none. The resume point
131960
+ // for extending the chain after an append.
131961
+ last(sourcePath) {
131962
+ const row = this.db.prepare(
131963
+ `SELECT message_index, byte_offset, line_number, parser_state
131964
+ FROM message_checkpoints
131965
+ WHERE source_path = ?
131966
+ ORDER BY message_index DESC LIMIT 1`
131967
+ ).get(sourcePath);
131968
+ return row ? toCheckpoint(row) : null;
131969
+ }
131842
131970
  count(sourcePath) {
131843
131971
  return this.db.prepare("SELECT COUNT(*) AS n FROM message_checkpoints WHERE source_path = ?").get(sourcePath).n;
131844
131972
  }
@@ -131870,7 +131998,7 @@ var ConversationFilesRepo = class {
131870
131998
  const info = this.db.prepare(
131871
131999
  `INSERT INTO conversation_files (absolute_path, parent_dir, file_name, account)
131872
132000
  VALUES (?, ?, ?, ?)`
131873
- ).run(absolutePath, (0, import_path9.dirname)(absolutePath), (0, import_path9.basename)(absolutePath), account);
132001
+ ).run(absolutePath, (0, import_path10.dirname)(absolutePath), (0, import_path10.basename)(absolutePath), account);
131874
132002
  return Number(info.lastInsertRowid);
131875
132003
  }
131876
132004
  // Advance the cursor + persisted reducer state after a successful index pass.
@@ -132221,6 +132349,9 @@ var PersistentEngine = class {
132221
132349
  // restart just means the first few post-restart scans don't force an early
132222
132350
  // backstop pass, which is harmless (watermarks themselves persist in the DB).
132223
132351
  scanCount = 0;
132352
+ // In-flight checkpoint build/extension per file, so concurrent getPage
132353
+ // callers share one stream instead of each walking the file.
132354
+ checkpointBuilds = /* @__PURE__ */ new Map();
132224
132355
  constructor(dbPath, options = {}) {
132225
132356
  this.db = openDatabase(dbPath);
132226
132357
  this.files = new ConversationFilesRepo(this.db);
@@ -132273,7 +132404,7 @@ var PersistentEngine = class {
132273
132404
  const batch = discovered.slice(i, i + BATCH_SIZE);
132274
132405
  const results = await Promise.all(
132275
132406
  batch.map(async ({ filePath, account, provider }) => {
132276
- const meta3 = await this.indexFile(
132407
+ const { meta: meta3 } = await this.indexFile(
132277
132408
  filePath,
132278
132409
  account,
132279
132410
  tier.name,
@@ -132308,7 +132439,9 @@ var PersistentEngine = class {
132308
132439
  // unchanged → return the stored summary; appended → resume the fold and read
132309
132440
  // only new bytes; reindex/force → fold from offset 0. Writes the summary +
132310
132441
  // cursor + reducer state in one transaction so a crash never leaves a
132311
- // half-written row or an over-advanced cursor.
132442
+ // half-written row or an over-advanced cursor. Returns the classification
132443
+ // alongside the meta so callers (refreshFile) can keep, extend, or evict
132444
+ // their own per-file caches without re-stat'ing the file (racy) themselves.
132312
132445
  async indexFile(filePath, account, tierName, customTiers, resolveGitBranch, force = false, provider) {
132313
132446
  const log8 = getLogger2();
132314
132447
  const tier = resolveTier(tierName, customTiers);
@@ -132316,13 +132449,21 @@ var PersistentEngine = class {
132316
132449
  const { change, stat: stat42 } = classify(filePath, existing);
132317
132450
  if (change === "vanished" || !stat42) {
132318
132451
  this.markDeleted(filePath);
132319
- return null;
132452
+ return { meta: null, change: "vanished" };
132320
132453
  }
132321
132454
  if (change === "unchanged" && !force) {
132322
- return this.conversations.getBySourcePath(filePath);
132455
+ return { meta: this.conversations.getBySourcePath(filePath), change };
132323
132456
  }
132324
132457
  if (provider && provider.name !== CLAUDE_CODE_PROVIDER) {
132325
- return this.indexFileWithProvider(provider, filePath, account, tier, stat42, resolveGitBranch);
132458
+ const meta22 = await this.indexFileWithProvider(
132459
+ provider,
132460
+ filePath,
132461
+ account,
132462
+ tier,
132463
+ stat42,
132464
+ resolveGitBranch
132465
+ );
132466
+ return { meta: meta22, change };
132326
132467
  }
132327
132468
  const resume = change === "appended" && !force && existing?.reducer_state;
132328
132469
  const state = resume ? JSON.parse(existing.reducer_state) : initialReducerState();
@@ -132333,12 +132474,12 @@ var PersistentEngine = class {
132333
132474
  result = await tailReduce(filePath, startOffset, startLine, state, tier);
132334
132475
  } catch (err) {
132335
132476
  log8.warn({ filePath, err }, "persistent: tail read failed");
132336
- return null;
132477
+ return { meta: null, change };
132337
132478
  }
132338
132479
  const meta3 = finalizeMeta(state, filePath, account, tier);
132339
132480
  if (!meta3) {
132340
132481
  this.markDeleted(filePath);
132341
- return null;
132482
+ return { meta: null, change };
132342
132483
  }
132343
132484
  meta3.gitBranch = resolveGitBranch(meta3.projectPath);
132344
132485
  const fp = stat42.size > 0 ? fingerprint(filePath, stat42.size) : null;
@@ -132346,7 +132487,7 @@ var PersistentEngine = class {
132346
132487
  const upsert = this.db.transaction(() => {
132347
132488
  this.conversations.upsert(fileId, meta3, state.pageMessageCount);
132348
132489
  this.fts.upsert(meta3);
132349
- this.checkpoints.remove(filePath);
132490
+ if (!resume) this.checkpoints.remove(filePath);
132350
132491
  this.files.updateCursor(fileId, {
132351
132492
  sizeBytes: stat42.size,
132352
132493
  mtimeMs: stat42.mtimeMs,
@@ -132379,7 +132520,7 @@ var PersistentEngine = class {
132379
132520
  { filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta3.messageCount },
132380
132521
  "persistent: indexed file"
132381
132522
  );
132382
- return meta3;
132523
+ return { meta: meta3, change };
132383
132524
  }
132384
132525
  // Index a non-Threadbase provider file: full reparse from offset 0 through the
132385
132526
  // provider's reducer/finalize, then the same upsert + FTS write + cursor bump
@@ -132479,15 +132620,34 @@ var PersistentEngine = class {
132479
132620
  return { messages: messages.slice(fromIndex2, beforeIndex2), total: total2, fromIndex: fromIndex2 };
132480
132621
  }
132481
132622
  const total = this.conversations.pageMessageCount(filePath);
132482
- if (total > CHECKPOINT_INTERVAL && this.checkpoints.count(filePath) === 0) {
132483
- const built = await buildCheckpoints(filePath);
132484
- if (built.length > 0) this.checkpoints.replaceAll(filePath, built);
132485
- }
132623
+ await this.ensureCheckpoints(filePath, total);
132486
132624
  const beforeIndex = options.beforeIndex ?? total;
132487
132625
  const fromIndex = Math.max(0, beforeIndex - options.limit);
132488
132626
  const floor = this.checkpoints.floor(filePath, fromIndex);
132489
132627
  return readPage(filePath, total, options, floor);
132490
132628
  }
132629
+ // Build or extend the checkpoint chain so it covers `total` messages. Cold
132630
+ // file → full build; a file that grew → extend from the last persisted
132631
+ // checkpoint (reads only past its offset, never the prefix). Single-flighted
132632
+ // per path: concurrent getPage callers await the same build instead of
132633
+ // streaming the file in parallel.
132634
+ ensureCheckpoints(filePath, total) {
132635
+ if (total <= CHECKPOINT_INTERVAL) return Promise.resolve();
132636
+ const inFlight = this.checkpointBuilds.get(filePath);
132637
+ if (inFlight) return inFlight;
132638
+ const build2 = (async () => {
132639
+ const last = this.checkpoints.last(filePath);
132640
+ if (last && total < last.messageIndex + CHECKPOINT_INTERVAL) return;
132641
+ const fresh = await buildCheckpoints(filePath, CHECKPOINT_INTERVAL, last);
132642
+ if (fresh.length === 0) return;
132643
+ if (last) this.checkpoints.append(filePath, fresh);
132644
+ else this.checkpoints.replaceAll(filePath, fresh);
132645
+ })().finally(() => {
132646
+ if (this.checkpointBuilds.get(filePath) === build2) this.checkpointBuilds.delete(filePath);
132647
+ });
132648
+ this.checkpointBuilds.set(filePath, build2);
132649
+ return build2;
132650
+ }
132491
132651
  };
132492
132652
  var EXCLUDED_SEGMENTS2 = ["/memory/", "/tool-results/"];
132493
132653
  var FileWatcher = class {
@@ -132586,6 +132746,8 @@ function defaultDbPath() {
132586
132746
  }
132587
132747
  var ConversationScanner = class {
132588
132748
  metadataCache = /* @__PURE__ */ new Map();
132749
+ // Parsed conversations plus (persistent claude-code entries only) the resume
132750
+ // point that lets refreshFile extend them in place when the file grows.
132589
132751
  conversationLRU;
132590
132752
  // session_id is NOT unique, so this maps a sessionId to every active meta that
132591
132753
  // carries it. Resolution picks deterministically (newest timestamp, then path
@@ -132617,7 +132779,9 @@ var ConversationScanner = class {
132617
132779
  // can't hit a closed DB (the watch-mode half of Bug #4).
132618
132780
  inFlightReconcile = null;
132619
132781
  constructor(options) {
132620
- this.conversationLRU = new LRUCache(options?.conversationCacheSize ?? 5);
132782
+ this.conversationLRU = new LRUCache(
132783
+ options?.conversationCacheSize ?? 5
132784
+ );
132621
132785
  if (options?.persistent === false) {
132622
132786
  this.dbPath = null;
132623
132787
  this.sidecarEnabled = false;
@@ -132874,7 +133038,7 @@ var ConversationScanner = class {
132874
133038
  const cached3 = this.conversationLRU.get(id);
132875
133039
  if (cached3) {
132876
133040
  log8.debug({ id }, "getConversation: cache hit");
132877
- return cached3;
133041
+ return cached3.conversation;
132878
133042
  }
132879
133043
  const meta3 = this.persistent ? this.engine().getByIdOrSession(id) : this.metadataCache.get(id) ?? this.resolveSessionId(id);
132880
133044
  if (!meta3) {
@@ -132883,9 +133047,14 @@ var ConversationScanner = class {
132883
133047
  }
132884
133048
  log8.debug({ id, filePath: meta3.filePath }, "getConversation: cache miss, parsing");
132885
133049
  try {
133050
+ if (this.persistent && meta3.provider !== CODEX_CLI_PROVIDER) {
133051
+ const parsed = await parseConversationResumable(meta3.filePath, meta3.account);
133052
+ if (parsed) this.conversationLRU.set(id, parsed);
133053
+ return parsed?.conversation ?? null;
133054
+ }
132886
133055
  const conversation = meta3.provider === CODEX_CLI_PROVIDER ? await parseCodexConversation(meta3.filePath, meta3.account) : await parseConversation(meta3.filePath, meta3.account);
132887
133056
  if (conversation) {
132888
- this.conversationLRU.set(id, conversation);
133057
+ this.conversationLRU.set(id, { conversation });
132889
133058
  }
132890
133059
  return conversation;
132891
133060
  } catch (err) {
@@ -132957,20 +133126,30 @@ var ConversationScanner = class {
132957
133126
  // not seen before. Returns the fresh ConversationMeta, or null when the file
132958
133127
  // no longer parses (missing/empty) — in which case any prior entry for it is
132959
133128
  // dropped from all indexes.
132960
- async refreshFile(filePath, account) {
133129
+ //
133130
+ // Single-flighted per path: concurrent callers (stacked client retries, a
133131
+ // watcher tick racing a caller) await the one in-flight refresh instead of
133132
+ // each re-reading the file.
133133
+ refreshesInFlight = /* @__PURE__ */ new Map();
133134
+ refreshFile(filePath, account) {
133135
+ const inFlight = this.refreshesInFlight.get(filePath);
133136
+ if (inFlight) return inFlight;
133137
+ const refresh = this.doRefreshFile(filePath, account).finally(() => {
133138
+ if (this.refreshesInFlight.get(filePath) === refresh) {
133139
+ this.refreshesInFlight.delete(filePath);
133140
+ }
133141
+ });
133142
+ this.refreshesInFlight.set(filePath, refresh);
133143
+ return refresh;
133144
+ }
133145
+ async doRefreshFile(filePath, account) {
132961
133146
  const log8 = getLogger2();
132962
133147
  if (this.persistent) {
132963
133148
  const engine = this.engine();
132964
133149
  const previous2 = engine.getByIdOrSession(filePath);
132965
133150
  const resolvedAccount2 = account ?? previous2?.account ?? "default";
132966
- const evict2 = (m2) => {
132967
- if (!m2) return;
132968
- this.conversationLRU.delete(m2.id);
132969
- this.conversationLRU.delete(m2.sessionId);
132970
- };
132971
- evict2(previous2);
132972
133151
  const provider = await this.resolveProviderForFile(filePath, previous2);
132973
- const meta22 = await engine.indexFile(
133152
+ const { meta: meta22, change } = await engine.indexFile(
132974
133153
  filePath,
132975
133154
  resolvedAccount2,
132976
133155
  this.lastTier.name,
@@ -132979,8 +133158,19 @@ var ConversationScanner = class {
132979
133158
  false,
132980
133159
  provider
132981
133160
  );
132982
- evict2(meta22);
132983
- log8.debug({ filePath, kept: !!meta22 }, "refreshFile: updated persistent index");
133161
+ const cacheKeys = /* @__PURE__ */ new Set();
133162
+ for (const m2 of [previous2, meta22]) {
133163
+ if (m2) {
133164
+ cacheKeys.add(m2.id);
133165
+ cacheKeys.add(m2.sessionId);
133166
+ }
133167
+ }
133168
+ if (!meta22 || change === "reindex" || change === "vanished") {
133169
+ for (const key of cacheKeys) this.conversationLRU.delete(key);
133170
+ } else if (change === "appended") {
133171
+ await this.extendCachedConversations(cacheKeys, filePath, meta22.account);
133172
+ }
133173
+ log8.debug({ filePath, change, kept: !!meta22 }, "refreshFile: updated persistent index");
132984
133174
  return meta22;
132985
133175
  }
132986
133176
  const previous = this.metadataCache.get(filePath);
@@ -133024,6 +133214,39 @@ var ConversationScanner = class {
133024
133214
  );
133025
133215
  return meta3;
133026
133216
  }
133217
+ // Advance every cached parse of an appended file by folding only the new
133218
+ // bytes through the conversation reducer — the in-memory analogue of the
133219
+ // persisted metadata fold. Entries without resume state (Codex) and entries
133220
+ // whose extension fails are evicted so the next read re-parses from scratch.
133221
+ async extendCachedConversations(cacheKeys, filePath, account) {
133222
+ const wrappers = /* @__PURE__ */ new Map();
133223
+ for (const key of cacheKeys) {
133224
+ const wrapper = this.conversationLRU.get(key);
133225
+ if (!wrapper) continue;
133226
+ const keys = wrappers.get(wrapper) ?? [];
133227
+ keys.push(key);
133228
+ wrappers.set(wrapper, keys);
133229
+ }
133230
+ for (const [wrapper, keys] of wrappers) {
133231
+ if (!wrapper.resume) {
133232
+ for (const key of keys) this.conversationLRU.delete(key);
133233
+ continue;
133234
+ }
133235
+ try {
133236
+ const extended = await extendConversation(
133237
+ wrapper.conversation,
133238
+ wrapper.resume,
133239
+ filePath,
133240
+ account
133241
+ );
133242
+ wrapper.conversation = extended.conversation;
133243
+ wrapper.resume = extended.resume;
133244
+ } catch (err) {
133245
+ getLogger2().warn({ filePath, err }, "refreshFile: cache extension failed, evicting");
133246
+ for (const key of keys) this.conversationLRU.delete(key);
133247
+ }
133248
+ }
133249
+ }
133027
133250
  getMetadataCache() {
133028
133251
  if (this.persistent) {
133029
133252
  const map2 = /* @__PURE__ */ new Map();
@@ -133290,10 +133513,10 @@ async function search(query, options, scanner) {
133290
133513
  // src/server.ts
133291
133514
  var import_events3 = require("events");
133292
133515
  var import_fs24 = require("fs");
133293
- var import_promises15 = require("fs/promises");
133516
+ var import_promises17 = require("fs/promises");
133294
133517
  var import_http = require("http");
133295
133518
  var import_os10 = require("os");
133296
- var import_path21 = require("path");
133519
+ var import_path22 = require("path");
133297
133520
  var import_readline4 = require("readline");
133298
133521
 
133299
133522
  // src/agent/agent-client.ts
@@ -133382,7 +133605,7 @@ function readAgentConfig(env = process.env) {
133382
133605
  }
133383
133606
 
133384
133607
  // src/agent/conversation-writer.ts
133385
- var import_promises8 = require("fs/promises");
133608
+ var import_promises10 = require("fs/promises");
133386
133609
  var import_node_path6 = require("path");
133387
133610
  function createConversationWriter(opts) {
133388
133611
  const { baseDir } = opts;
@@ -133392,7 +133615,7 @@ function createConversationWriter(opts) {
133392
133615
  throw new Error("ConversationWriter: refusing to write empty assistant turn");
133393
133616
  }
133394
133617
  const file2 = (0, import_node_path6.join)(baseDir, `${args.sessionId}.jsonl`);
133395
- await (0, import_promises8.mkdir)((0, import_node_path6.dirname)(file2), { recursive: true });
133618
+ await (0, import_promises10.mkdir)((0, import_node_path6.dirname)(file2), { recursive: true });
133396
133619
  const record2 = {
133397
133620
  role: "assistant",
133398
133621
  turnId: args.turnId,
@@ -133402,7 +133625,7 @@ function createConversationWriter(opts) {
133402
133625
  };
133403
133626
  const line = `${JSON.stringify(record2)}
133404
133627
  `;
133405
- await (0, import_promises8.appendFile)(file2, line, { encoding: "utf8" });
133628
+ await (0, import_promises10.appendFile)(file2, line, { encoding: "utf8" });
133406
133629
  }
133407
133630
  };
133408
133631
  }
@@ -136383,8 +136606,8 @@ var createHonoApp = (deps, upgradeWebSocket) => {
136383
136606
  init_auth();
136384
136607
 
136385
136608
  // src/browse.ts
136386
- var import_promises9 = require("fs/promises");
136387
- var import_path10 = require("path");
136609
+ var import_promises11 = require("fs/promises");
136610
+ var import_path11 = require("path");
136388
136611
  var BrowsePathNotFoundError = class extends Error {
136389
136612
  constructor(message) {
136390
136613
  super(message);
@@ -136392,20 +136615,20 @@ var BrowsePathNotFoundError = class extends Error {
136392
136615
  }
136393
136616
  };
136394
136617
  async function resolveBrowsePath(browseRoot, relativePath) {
136395
- const normalizedRoot = (0, import_path10.resolve)(browseRoot);
136618
+ const normalizedRoot = (0, import_path11.resolve)(browseRoot);
136396
136619
  let sanitized;
136397
136620
  if (process.platform !== "win32" && relativePath.startsWith("/") && relativePath.length > 1 && relativePath.includes("/", 1)) {
136398
136621
  sanitized = relativePath;
136399
136622
  } else {
136400
136623
  sanitized = relativePath.replace(/^[/\\]+/, "");
136401
136624
  }
136402
- const target = sanitized ? (0, import_path10.resolve)(normalizedRoot, sanitized) : normalizedRoot;
136403
- const rootPrefix = normalizedRoot.endsWith(import_path10.sep) ? normalizedRoot : `${normalizedRoot}${import_path10.sep}`;
136625
+ const target = sanitized ? (0, import_path11.resolve)(normalizedRoot, sanitized) : normalizedRoot;
136626
+ const rootPrefix = normalizedRoot.endsWith(import_path11.sep) ? normalizedRoot : `${normalizedRoot}${import_path11.sep}`;
136404
136627
  if (!target.startsWith(rootPrefix) && target !== normalizedRoot) {
136405
136628
  throw new Error("Path outside browse root");
136406
136629
  }
136407
136630
  try {
136408
- await (0, import_promises9.realpath)(target);
136631
+ await (0, import_promises11.realpath)(target);
136409
136632
  } catch (err) {
136410
136633
  if (err?.code === "ENOENT") {
136411
136634
  throw new BrowsePathNotFoundError(`Path not found: ${target}`);
@@ -136415,37 +136638,37 @@ async function resolveBrowsePath(browseRoot, relativePath) {
136415
136638
  return target;
136416
136639
  }
136417
136640
  async function listDirectories(absolutePath) {
136418
- const entries = await (0, import_promises9.readdir)(absolutePath, { withFileTypes: true });
136641
+ const entries = await (0, import_promises11.readdir)(absolutePath, { withFileTypes: true });
136419
136642
  return entries.filter((e) => e.isDirectory()).map((e) => ({ name: e.name })).sort((a, b2) => a.name.localeCompare(b2.name));
136420
136643
  }
136421
136644
  async function createDirectory(parentAbsolutePath, name) {
136422
136645
  if (name.includes("/") || name.includes("\\") || name === ".." || name === ".") {
136423
136646
  throw new Error("Invalid directory name");
136424
136647
  }
136425
- const target = (0, import_path10.join)(parentAbsolutePath, name);
136648
+ const target = (0, import_path11.join)(parentAbsolutePath, name);
136426
136649
  try {
136427
- const s3 = await (0, import_promises9.stat)(target);
136650
+ const s3 = await (0, import_promises11.stat)(target);
136428
136651
  if (s3.isDirectory()) throw new Error("Directory already exists");
136429
136652
  } catch (err) {
136430
136653
  if (err.code !== "ENOENT") throw err;
136431
136654
  }
136432
- await (0, import_promises9.mkdir)(target);
136655
+ await (0, import_promises11.mkdir)(target);
136433
136656
  return target;
136434
136657
  }
136435
136658
 
136436
136659
  // src/conversation-cache.ts
136437
136660
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
136438
136661
  var import_fs16 = require("fs");
136439
- var import_path12 = require("path");
136662
+ var import_path13 = require("path");
136440
136663
 
136441
136664
  // src/db/sqlite-migrate.ts
136442
136665
  var import_fs14 = require("fs");
136443
- var import_path11 = require("path");
136666
+ var import_path12 = require("path");
136444
136667
  var import_url6 = require("url");
136445
136668
  var import_meta = {};
136446
136669
  function getMigrationsDir() {
136447
136670
  if (typeof import_meta !== "undefined" && import_meta.url) {
136448
- return (0, import_path11.dirname)((0, import_url6.fileURLToPath)(import_meta.url));
136671
+ return (0, import_path12.dirname)((0, import_url6.fileURLToPath)(import_meta.url));
136449
136672
  }
136450
136673
  return __dirname;
136451
136674
  }
@@ -136457,7 +136680,7 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
136457
136680
  `;
136458
136681
  function runSqliteMigrations(db, migrationsDir) {
136459
136682
  db.exec(SCHEMA_MIGRATIONS_SQL);
136460
- const dir = migrationsDir ?? (0, import_path11.join)(getMigrationsDir(), "migrations");
136683
+ const dir = migrationsDir ?? (0, import_path12.join)(getMigrationsDir(), "migrations");
136461
136684
  const files = (0, import_fs14.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
136462
136685
  const appliedRows = db.prepare("SELECT id FROM schema_migrations").all();
136463
136686
  const appliedSet = new Set(appliedRows.map((r) => r.id));
@@ -136469,7 +136692,7 @@ function runSqliteMigrations(db, migrationsDir) {
136469
136692
  skipped.push(file2);
136470
136693
  continue;
136471
136694
  }
136472
- const sql = (0, import_fs14.readFileSync)((0, import_path11.join)(dir, file2), "utf-8");
136695
+ const sql = (0, import_fs14.readFileSync)((0, import_path12.join)(dir, file2), "utf-8");
136473
136696
  const tx = db.transaction(() => {
136474
136697
  db.exec(sql);
136475
136698
  recordApplied.run(file2, (/* @__PURE__ */ new Date()).toISOString());
@@ -136814,7 +137037,7 @@ var ConversationCache = class _ConversationCache {
136814
137037
  return this.classifyAgentFile(filePath, s3.mtimeMs, s3.size);
136815
137038
  }
136816
137039
  static open(dbPath, tailSize = 10, migrationsDir, options) {
136817
- (0, import_fs16.mkdirSync)((0, import_path12.dirname)(dbPath), { recursive: true });
137040
+ (0, import_fs16.mkdirSync)((0, import_path13.dirname)(dbPath), { recursive: true });
136818
137041
  const db = new import_better_sqlite32.default(dbPath);
136819
137042
  db.pragma("journal_mode = WAL");
136820
137043
  db.pragma("foreign_keys = ON");
@@ -137395,12 +137618,12 @@ function getDbConfig() {
137395
137618
 
137396
137619
  // src/db/migrations.ts
137397
137620
  var import_fs17 = require("fs");
137398
- var import_path13 = require("path");
137621
+ var import_path14 = require("path");
137399
137622
  var import_url7 = require("url");
137400
137623
  var import_meta2 = {};
137401
137624
  function getMigrationsDir2() {
137402
137625
  if (import_meta2.url) {
137403
- return (0, import_path13.dirname)((0, import_url7.fileURLToPath)(import_meta2.url));
137626
+ return (0, import_path14.dirname)((0, import_url7.fileURLToPath)(import_meta2.url));
137404
137627
  }
137405
137628
  return __dirname;
137406
137629
  }
@@ -137416,11 +137639,11 @@ async function runMigrations2(pool2, migrationsDir) {
137416
137639
  "SELECT name FROM _migrations ORDER BY name"
137417
137640
  );
137418
137641
  const appliedSet = new Set(applied.map((r) => r.name));
137419
- const dir = migrationsDir ?? (0, import_path13.join)(getMigrationsDir2(), "pg-migrations");
137642
+ const dir = migrationsDir ?? (0, import_path14.join)(getMigrationsDir2(), "pg-migrations");
137420
137643
  const files = (0, import_fs17.readdirSync)(dir).filter((f2) => f2.endsWith(".sql")).sort();
137421
137644
  for (const file2 of files) {
137422
137645
  if (appliedSet.has(file2)) continue;
137423
- const sql = (0, import_fs17.readFileSync)((0, import_path13.join)(dir, file2), "utf-8");
137646
+ const sql = (0, import_fs17.readFileSync)((0, import_path14.join)(dir, file2), "utf-8");
137424
137647
  await pool2.query(sql);
137425
137648
  await pool2.query("INSERT INTO _migrations (name) VALUES ($1)", [file2]);
137426
137649
  }
@@ -137657,18 +137880,18 @@ async function recordUpload(pool2, instanceId, row) {
137657
137880
  // src/handlers/handleListProjects.ts
137658
137881
  var import_fs18 = require("fs");
137659
137882
  var import_os7 = require("os");
137660
- var import_path14 = require("path");
137883
+ var import_path15 = require("path");
137661
137884
  function decodeProjectPath(dirName) {
137662
137885
  return dirName.replace(/-/g, "/");
137663
137886
  }
137664
137887
  function handleListProjects(url2, res) {
137665
137888
  const limit = Math.max(1, parseInt(url2.searchParams.get("limit") ?? "50", 10) || 50);
137666
137889
  const offset = Math.max(0, parseInt(url2.searchParams.get("offset") ?? "0", 10) || 0);
137667
- const projectsDir = (0, import_path14.join)((0, import_os7.homedir)(), ".claude", "projects");
137890
+ const projectsDir = (0, import_path15.join)((0, import_os7.homedir)(), ".claude", "projects");
137668
137891
  let entries;
137669
137892
  try {
137670
137893
  entries = (0, import_fs18.readdirSync)(projectsDir).map((dirName) => {
137671
- const fullPath = (0, import_path14.join)(projectsDir, dirName);
137894
+ const fullPath = (0, import_path15.join)(projectsDir, dirName);
137672
137895
  let mtime = 0;
137673
137896
  try {
137674
137897
  mtime = (0, import_fs18.statSync)(fullPath).mtimeMs;
@@ -137690,20 +137913,20 @@ function handleListProjects(url2, res) {
137690
137913
  }
137691
137914
 
137692
137915
  // src/live-session-manager.ts
137693
- var import_path18 = require("path");
137916
+ var import_path19 = require("path");
137694
137917
 
137695
137918
  // src/codex-pty-runner.ts
137696
137919
  var import_headless = __toESM(require_xterm_headless(), 1);
137697
137920
  var import_crypto6 = require("crypto");
137698
137921
  var import_fs20 = require("fs");
137699
- var import_path16 = require("path");
137922
+ var import_path17 = require("path");
137700
137923
  init_logger();
137701
137924
 
137702
137925
  // src/platform.ts
137703
137926
  var import_child_process = require("child_process");
137704
137927
  var import_fs19 = require("fs");
137705
137928
  var import_os8 = require("os");
137706
- var import_path15 = require("path");
137929
+ var import_path16 = require("path");
137707
137930
  var isWindows2 = (0, import_os8.platform)() === "win32";
137708
137931
  var _claudeExe;
137709
137932
  function resolveClaudeExe() {
@@ -137722,9 +137945,9 @@ function resolveClaudeExe() {
137722
137945
  } catch {
137723
137946
  }
137724
137947
  const candidates = [
137725
- (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "claude.exe"),
137726
- (0, import_path15.join)(
137727
- process.env.LOCALAPPDATA ?? (0, import_path15.join)((0, import_os8.homedir)(), "AppData", "Local"),
137948
+ (0, import_path16.join)((0, import_os8.homedir)(), ".local", "bin", "claude.exe"),
137949
+ (0, import_path16.join)(
137950
+ process.env.LOCALAPPDATA ?? (0, import_path16.join)((0, import_os8.homedir)(), "AppData", "Local"),
137728
137951
  "Microsoft",
137729
137952
  "WindowsApps",
137730
137953
  "claude.exe"
@@ -137751,7 +137974,7 @@ function resolveClaudeExe() {
137751
137974
  const candidates = [
137752
137975
  "/opt/homebrew/bin/claude",
137753
137976
  "/usr/local/bin/claude",
137754
- (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "claude")
137977
+ (0, import_path16.join)((0, import_os8.homedir)(), ".local", "bin", "claude")
137755
137978
  ];
137756
137979
  for (const p2 of candidates) {
137757
137980
  if ((0, import_fs19.existsSync)(p2)) {
@@ -137780,9 +138003,9 @@ function resolveCodexExe() {
137780
138003
  } catch {
137781
138004
  }
137782
138005
  const candidates = [
137783
- (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex.exe"),
137784
- (0, import_path15.join)(
137785
- process.env.LOCALAPPDATA ?? (0, import_path15.join)((0, import_os8.homedir)(), "AppData", "Local"),
138006
+ (0, import_path16.join)((0, import_os8.homedir)(), ".local", "bin", "codex.exe"),
138007
+ (0, import_path16.join)(
138008
+ process.env.LOCALAPPDATA ?? (0, import_path16.join)((0, import_os8.homedir)(), "AppData", "Local"),
137786
138009
  "Microsoft",
137787
138010
  "WindowsApps",
137788
138011
  "codex.exe"
@@ -137809,7 +138032,7 @@ function resolveCodexExe() {
137809
138032
  const candidates = [
137810
138033
  "/opt/homebrew/bin/codex",
137811
138034
  "/usr/local/bin/codex",
137812
- (0, import_path15.join)((0, import_os8.homedir)(), ".local", "bin", "codex")
138035
+ (0, import_path16.join)((0, import_os8.homedir)(), ".local", "bin", "codex")
137813
138036
  ];
137814
138037
  for (const p2 of candidates) {
137815
138038
  if ((0, import_fs19.existsSync)(p2)) {
@@ -137905,7 +138128,7 @@ var CodexPtyRunner = class {
137905
138128
  }
137906
138129
  async doStart(sessionId, options) {
137907
138130
  const nodePty = await loadPty();
137908
- const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
138131
+ const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
137909
138132
  const proc = nodePty.spawn(
137910
138133
  resolveCodexExe(),
137911
138134
  ["resume", sessionId, "--cd", options.projectPath, "--no-alt-screen"],
@@ -137950,7 +138173,7 @@ var CodexPtyRunner = class {
137950
138173
  async startFresh(options) {
137951
138174
  const nodePty = await loadPty();
137952
138175
  const sessionId = (0, import_crypto6.randomUUID)();
137953
- const projectName = options.projectName ?? (0, import_path16.basename)(options.projectPath);
138176
+ const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
137954
138177
  const args = ["--cd", options.projectPath, "--no-alt-screen"];
137955
138178
  if (options.systemPrompt) {
137956
138179
  args.push(options.systemPrompt);
@@ -138280,7 +138503,7 @@ function stripAnsi(str) {
138280
138503
  var import_headless2 = __toESM(require_xterm_headless(), 1);
138281
138504
  var import_crypto7 = require("crypto");
138282
138505
  var import_fs21 = require("fs");
138283
- var import_path17 = require("path");
138506
+ var import_path18 = require("path");
138284
138507
  init_logger();
138285
138508
 
138286
138509
  // src/services/questions/detectPermissionGate.ts
@@ -138632,7 +138855,7 @@ var PTYManager = class {
138632
138855
  }
138633
138856
  async doStart(sessionId, options) {
138634
138857
  const nodePty = await loadPty2();
138635
- const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138858
+ const projectName = options.projectName ?? (0, import_path18.basename)(options.projectPath);
138636
138859
  const proc = nodePty.spawn(
138637
138860
  resolveClaudeExe(),
138638
138861
  [
@@ -138683,7 +138906,7 @@ var PTYManager = class {
138683
138906
  async startFresh(options) {
138684
138907
  const nodePty = await loadPty2();
138685
138908
  const sessionId = (0, import_crypto7.randomUUID)();
138686
- const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138909
+ const projectName = options.projectName ?? (0, import_path18.basename)(options.projectPath);
138687
138910
  const args = [
138688
138911
  "--permission-mode",
138689
138912
  options.permissionMode ?? "acceptEdits",
@@ -139255,7 +139478,7 @@ var LiveSessionManager = class {
139255
139478
  const runner = this.runners.get(provider);
139256
139479
  if (runner) return runner;
139257
139480
  const err = new Error(
139258
- `Live ${provider} sessions are not implemented yet for ${(0, import_path18.basename)(projectPath)}`
139481
+ `Live ${provider} sessions are not implemented yet for ${(0, import_path19.basename)(projectPath)}`
139259
139482
  );
139260
139483
  err.statusCode = 501;
139261
139484
  throw err;
@@ -139322,7 +139545,7 @@ var PairTokenStore = class {
139322
139545
  // src/process-discovery.ts
139323
139546
  var import_child_process2 = require("child_process");
139324
139547
  var import_os9 = require("os");
139325
- var import_path19 = require("path");
139548
+ var import_path20 = require("path");
139326
139549
  async function discoverClaudeProcesses() {
139327
139550
  if ((0, import_os9.platform)() === "win32") return discoverWindows();
139328
139551
  return discoverUnix();
@@ -139341,7 +139564,7 @@ async function discoverUnix() {
139341
139564
  return {
139342
139565
  pid,
139343
139566
  projectPath: cwd,
139344
- projectName: (0, import_path19.basename)(cwd),
139567
+ projectName: (0, import_path20.basename)(cwd),
139345
139568
  branch: await readGitBranch2(cwd),
139346
139569
  conversationId,
139347
139570
  startedAt
@@ -139363,7 +139586,7 @@ async function discoverWindows() {
139363
139586
  return {
139364
139587
  pid,
139365
139588
  projectPath: info.cwd,
139366
- projectName: (0, import_path19.basename)(info.cwd),
139589
+ projectName: (0, import_path20.basename)(info.cwd),
139367
139590
  branch: await readGitBranch2(info.cwd),
139368
139591
  conversationId: extractResumeId(info.args),
139369
139592
  startedAt: info.startedAt
@@ -139444,7 +139667,7 @@ async function getProcessInfoWindows(pid) {
139444
139667
  const startedAt = /* @__PURE__ */ new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);
139445
139668
  if (Number.isNaN(startedAt.getTime())) return null;
139446
139669
  const exePath = parts[3] ?? "";
139447
- const cwd = exePath ? (0, import_path19.dirname)(exePath) : "";
139670
+ const cwd = exePath ? (0, import_path20.dirname)(exePath) : "";
139448
139671
  return { cwd, args, startedAt };
139449
139672
  } catch {
139450
139673
  return null;
@@ -139486,11 +139709,11 @@ function seal(plaintext, recipientPublicKeyBase64) {
139486
139709
  // node_modules/chokidar/index.js
139487
139710
  var import_node_events2 = require("events");
139488
139711
  var import_node_fs7 = require("fs");
139489
- var import_promises12 = require("fs/promises");
139712
+ var import_promises14 = require("fs/promises");
139490
139713
  var sp2 = __toESM(require("path"), 1);
139491
139714
 
139492
139715
  // node_modules/readdirp/index.js
139493
- var import_promises10 = require("fs/promises");
139716
+ var import_promises12 = require("fs/promises");
139494
139717
  var import_node_path9 = require("path");
139495
139718
  var import_node_stream2 = require("stream");
139496
139719
  var EntryTypes2 = {
@@ -139571,7 +139794,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139571
139794
  const { root, type } = opts;
139572
139795
  this._fileFilter = normalizeFilter2(opts.fileFilter);
139573
139796
  this._directoryFilter = normalizeFilter2(opts.directoryFilter);
139574
- const statMethod = opts.lstat ? import_promises10.lstat : import_promises10.stat;
139797
+ const statMethod = opts.lstat ? import_promises12.lstat : import_promises12.stat;
139575
139798
  if (wantBigintFsStats2) {
139576
139799
  this._stat = (path2) => statMethod(path2, { bigint: true });
139577
139800
  } else {
@@ -139642,7 +139865,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139642
139865
  async _exploreDir(path2, depth) {
139643
139866
  let files;
139644
139867
  try {
139645
- files = await (0, import_promises10.readdir)(path2, this._rdOptions);
139868
+ files = await (0, import_promises12.readdir)(path2, this._rdOptions);
139646
139869
  } catch (error51) {
139647
139870
  this._onError(error51);
139648
139871
  }
@@ -139650,10 +139873,10 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139650
139873
  }
139651
139874
  async _formatEntry(dirent, path2) {
139652
139875
  let entry;
139653
- const basename11 = this._isDirent ? dirent.name : dirent;
139876
+ const basename12 = this._isDirent ? dirent.name : dirent;
139654
139877
  try {
139655
- const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename11));
139656
- entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename11 };
139878
+ const fullPath = (0, import_node_path9.resolve)((0, import_node_path9.join)(path2, basename12));
139879
+ entry = { path: (0, import_node_path9.relative)(this._root, fullPath), fullPath, basename: basename12 };
139657
139880
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
139658
139881
  } catch (err) {
139659
139882
  this._onError(err);
@@ -139680,8 +139903,8 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139680
139903
  if (stats && stats.isSymbolicLink()) {
139681
139904
  const full = entry.fullPath;
139682
139905
  try {
139683
- const entryRealPath = await (0, import_promises10.realpath)(full);
139684
- const entryRealPathStats = await (0, import_promises10.lstat)(entryRealPath);
139906
+ const entryRealPath = await (0, import_promises12.realpath)(full);
139907
+ const entryRealPathStats = await (0, import_promises12.lstat)(entryRealPath);
139685
139908
  if (entryRealPathStats.isFile()) {
139686
139909
  return "file";
139687
139910
  }
@@ -139724,7 +139947,7 @@ function readdirp2(root, options = {}) {
139724
139947
 
139725
139948
  // node_modules/chokidar/handler.js
139726
139949
  var import_node_fs6 = require("fs");
139727
- var import_promises11 = require("fs/promises");
139950
+ var import_promises13 = require("fs/promises");
139728
139951
  var import_node_os3 = require("os");
139729
139952
  var sp = __toESM(require("path"), 1);
139730
139953
  var STR_DATA2 = "data";
@@ -139751,7 +139974,7 @@ var EVENTS2 = {
139751
139974
  };
139752
139975
  var EV2 = EVENTS2;
139753
139976
  var THROTTLE_MODE_WATCH2 = "watch";
139754
- var statMethods2 = { lstat: import_promises11.lstat, stat: import_promises11.stat };
139977
+ var statMethods2 = { lstat: import_promises13.lstat, stat: import_promises13.stat };
139755
139978
  var KEY_LISTENERS2 = "listeners";
139756
139979
  var KEY_ERR2 = "errHandlers";
139757
139980
  var KEY_RAW2 = "rawEmitters";
@@ -140108,7 +140331,7 @@ var setFsWatchListener2 = (path2, fullPath, options, handlers) => {
140108
140331
  cont.watcherUnusable = true;
140109
140332
  if (isWindows3 && error51.code === "EPERM") {
140110
140333
  try {
140111
- const fd = await (0, import_promises11.open)(path2, "r");
140334
+ const fd = await (0, import_promises13.open)(path2, "r");
140112
140335
  await fd.close();
140113
140336
  broadcastErr(error51);
140114
140337
  } catch (err) {
@@ -140194,9 +140417,9 @@ var NodeFsHandler2 = class {
140194
140417
  _watchWithNodeFs(path2, listener) {
140195
140418
  const opts = this.fsw.options;
140196
140419
  const directory = sp.dirname(path2);
140197
- const basename11 = sp.basename(path2);
140420
+ const basename12 = sp.basename(path2);
140198
140421
  const parent = this.fsw._getWatchedDir(directory);
140199
- parent.add(basename11);
140422
+ parent.add(basename12);
140200
140423
  const absolutePath = sp.resolve(path2);
140201
140424
  const options = {
140202
140425
  persistent: opts.persistent
@@ -140206,7 +140429,7 @@ var NodeFsHandler2 = class {
140206
140429
  let closer;
140207
140430
  if (opts.usePolling) {
140208
140431
  const enableBin = opts.interval !== opts.binaryInterval;
140209
- options.interval = enableBin && isBinaryPath2(basename11) ? opts.binaryInterval : opts.interval;
140432
+ options.interval = enableBin && isBinaryPath2(basename12) ? opts.binaryInterval : opts.interval;
140210
140433
  closer = setFsWatchFileListener2(path2, absolutePath, options, {
140211
140434
  listener,
140212
140435
  rawEmitter: this.fsw._emitRaw
@@ -140229,17 +140452,17 @@ var NodeFsHandler2 = class {
140229
140452
  return;
140230
140453
  }
140231
140454
  const dirname17 = sp.dirname(file2);
140232
- const basename11 = sp.basename(file2);
140455
+ const basename12 = sp.basename(file2);
140233
140456
  const parent = this.fsw._getWatchedDir(dirname17);
140234
140457
  let prevStats = stats;
140235
- if (parent.has(basename11))
140458
+ if (parent.has(basename12))
140236
140459
  return;
140237
140460
  const listener = async (path2, newStats) => {
140238
140461
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH2, file2, 5))
140239
140462
  return;
140240
140463
  if (!newStats || newStats.mtimeMs === 0) {
140241
140464
  try {
140242
- const newStats2 = await (0, import_promises11.stat)(file2);
140465
+ const newStats2 = await (0, import_promises13.stat)(file2);
140243
140466
  if (this.fsw.closed)
140244
140467
  return;
140245
140468
  const at2 = newStats2.atimeMs;
@@ -140257,9 +140480,9 @@ var NodeFsHandler2 = class {
140257
140480
  prevStats = newStats2;
140258
140481
  }
140259
140482
  } catch (error51) {
140260
- this.fsw._remove(dirname17, basename11);
140483
+ this.fsw._remove(dirname17, basename12);
140261
140484
  }
140262
- } else if (parent.has(basename11)) {
140485
+ } else if (parent.has(basename12)) {
140263
140486
  const at2 = newStats.atimeMs;
140264
140487
  const mt2 = newStats.mtimeMs;
140265
140488
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -140294,7 +140517,7 @@ var NodeFsHandler2 = class {
140294
140517
  this.fsw._incrReadyCount();
140295
140518
  let linkPath;
140296
140519
  try {
140297
- linkPath = await (0, import_promises11.realpath)(path2);
140520
+ linkPath = await (0, import_promises13.realpath)(path2);
140298
140521
  } catch (e) {
140299
140522
  this.fsw._emitReady();
140300
140523
  return true;
@@ -140443,7 +140666,7 @@ var NodeFsHandler2 = class {
140443
140666
  let closer;
140444
140667
  if (stats.isDirectory()) {
140445
140668
  const absPath = sp.resolve(path2);
140446
- const targetPath = follow ? await (0, import_promises11.realpath)(path2) : path2;
140669
+ const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
140447
140670
  if (this.fsw.closed)
140448
140671
  return;
140449
140672
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -140453,7 +140676,7 @@ var NodeFsHandler2 = class {
140453
140676
  this.fsw._symlinkPaths.set(absPath, targetPath);
140454
140677
  }
140455
140678
  } else if (stats.isSymbolicLink()) {
140456
- const targetPath = follow ? await (0, import_promises11.realpath)(path2) : path2;
140679
+ const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
140457
140680
  if (this.fsw.closed)
140458
140681
  return;
140459
140682
  const parent = sp.dirname(wh.watchPath);
@@ -140613,7 +140836,7 @@ var DirEntry2 = class {
140613
140836
  return;
140614
140837
  const dir = this.path;
140615
140838
  try {
140616
- await (0, import_promises12.readdir)(dir);
140839
+ await (0, import_promises14.readdir)(dir);
140617
140840
  } catch (err) {
140618
140841
  if (this._removeWatcher) {
140619
140842
  this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
@@ -140963,7 +141186,7 @@ var FSWatcher2 = class extends import_node_events2.EventEmitter {
140963
141186
  const fullPath = opts.cwd ? sp2.join(opts.cwd, path2) : path2;
140964
141187
  let stats2;
140965
141188
  try {
140966
- stats2 = await (0, import_promises12.stat)(fullPath);
141189
+ stats2 = await (0, import_promises14.stat)(fullPath);
140967
141190
  } catch (err) {
140968
141191
  }
140969
141192
  if (!stats2 || this.closed)
@@ -141217,7 +141440,7 @@ var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
141217
141440
 
141218
141441
  // src/services/conversations/conversationWatcher.ts
141219
141442
  var import_fs22 = require("fs");
141220
- var import_promises13 = require("fs/promises");
141443
+ var import_promises15 = require("fs/promises");
141221
141444
  var ConversationWatcher = class {
141222
141445
  files = /* @__PURE__ */ new Map();
141223
141446
  directories = /* @__PURE__ */ new Map();
@@ -141321,12 +141544,12 @@ var ConversationWatcher = class {
141321
141544
  entry.reading = true;
141322
141545
  try {
141323
141546
  for (; ; ) {
141324
- const st2 = await (0, import_promises13.stat)(filePath);
141547
+ const st2 = await (0, import_promises15.stat)(filePath);
141325
141548
  if (st2.size <= entry.offset) break;
141326
141549
  const readFrom = entry.offset;
141327
141550
  const bytesToRead = st2.size - readFrom;
141328
141551
  const buf = Buffer.alloc(bytesToRead);
141329
- const fh = await (0, import_promises13.open)(filePath, "r");
141552
+ const fh = await (0, import_promises15.open)(filePath, "r");
141330
141553
  try {
141331
141554
  await fh.read(buf, 0, bytesToRead, readFrom);
141332
141555
  } finally {
@@ -141775,9 +141998,9 @@ function discoveredToResponse(d, conversationId) {
141775
141998
 
141776
141999
  // src/uploads.ts
141777
142000
  var import_crypto9 = require("crypto");
141778
- var import_promises14 = require("fs/promises");
142001
+ var import_promises16 = require("fs/promises");
141779
142002
  var import_heic_convert = __toESM(require_heic_convert(), 1);
141780
- var import_path20 = require("path");
142003
+ var import_path21 = require("path");
141781
142004
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
141782
142005
  var MAX_BYTES = 25 * 1024 * 1024;
141783
142006
  var HEIC_MIMES = /* @__PURE__ */ new Set(["image/heic", "image/heif"]);
@@ -141810,10 +142033,10 @@ async function saveUploadFile(input) {
141810
142033
  }
141811
142034
  const id = `up_${(0, import_crypto9.randomBytes)(8).toString("hex")}`;
141812
142035
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
141813
- const dir = (0, import_path20.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
141814
- await (0, import_promises14.mkdir)(dir, { recursive: true });
141815
- const filePath = (0, import_path20.join)(dir, `${Date.now()}-${id}-${safeName}`);
141816
- await (0, import_promises14.writeFile)(filePath, buffer);
142036
+ const dir = (0, import_path21.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
142037
+ await (0, import_promises16.mkdir)(dir, { recursive: true });
142038
+ const filePath = (0, import_path21.join)(dir, `${Date.now()}-${id}-${safeName}`);
142039
+ await (0, import_promises16.writeFile)(filePath, buffer);
141817
142040
  return {
141818
142041
  id,
141819
142042
  filePath,
@@ -142313,11 +142536,11 @@ var StreamerServer = class {
142313
142536
  this.disableDb = config2.disableDb ?? false;
142314
142537
  this.scannerPersistenceDisabled = config2.scannerPersistent === false;
142315
142538
  this.scanProfiles = config2.scanProfiles;
142316
- this.codexRoots = config2.codexRoots ?? [(0, import_path21.join)((0, import_os10.homedir)(), ".codex", "sessions")];
142539
+ this.codexRoots = config2.codexRoots ?? [(0, import_path22.join)((0, import_os10.homedir)(), ".codex", "sessions")];
142317
142540
  this.ptyGracePeriodMs = config2.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
142318
142541
  this.defaultSystemPrompt = config2.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;
142319
142542
  this.defaultPermissionMode = config2.defaultPermissionMode ?? loadDefaultPermissionMode() ?? "acceptEdits";
142320
- this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path21.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142543
+ this.cacheDir = config2.cacheDir ?? loadCacheDir() ?? (0, import_path22.join)((0, import_os10.homedir)(), ".threadbase", "cache");
142321
142544
  this.tailSize = config2.tailSize ?? loadTailSize() ?? 10;
142322
142545
  this.directoryDebounceMs = parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ?? config2.directoryScanDebounceMs ?? 1e3;
142323
142546
  this.markScannerStaleDebounced = debounce(() => {
@@ -142328,7 +142551,7 @@ var StreamerServer = class {
142328
142551
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
142329
142552
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
142330
142553
  if (rawRoot) {
142331
- (0, import_promises15.realpath)(rawRoot).then((resolved) => {
142554
+ (0, import_promises17.realpath)(rawRoot).then((resolved) => {
142332
142555
  this.browseRoot = resolved;
142333
142556
  if (this.verbose) this.log.info(`Browse root: ${resolved}`, { browseRoot: resolved });
142334
142557
  }).catch(() => {
@@ -142484,7 +142707,7 @@ var StreamerServer = class {
142484
142707
  temporalClient,
142485
142708
  taskQueue: agentConfig.temporal.taskQueue
142486
142709
  });
142487
- const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations");
142710
+ const conversationsBaseDir = agentConfig.conversationsDir || (0, import_path22.join)((0, import_path22.dirname)(this.cacheDir), "conversations");
142488
142711
  conversationWriter = createConversationWriter({
142489
142712
  baseDir: conversationsBaseDir
142490
142713
  });
@@ -142708,7 +142931,7 @@ var StreamerServer = class {
142708
142931
  });
142709
142932
  try {
142710
142933
  this.cache = ConversationCache.open(
142711
- (0, import_path21.join)(this.cacheDir, "cache.db"),
142934
+ (0, import_path22.join)(this.cacheDir, "cache.db"),
142712
142935
  this.tailSize,
142713
142936
  void 0,
142714
142937
  {
@@ -142734,11 +142957,11 @@ var StreamerServer = class {
142734
142957
  if (this.scanProfiles && this.scanProfiles.length > 0) {
142735
142958
  for (const profile of this.scanProfiles) {
142736
142959
  if (profile.enabled) {
142737
- this.fileWatcher.watchDirectory((0, import_path21.join)(profile.configDir, "projects"));
142960
+ this.fileWatcher.watchDirectory((0, import_path22.join)(profile.configDir, "projects"));
142738
142961
  }
142739
142962
  }
142740
142963
  } else {
142741
- this.fileWatcher.watchDirectory((0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects"));
142964
+ this.fileWatcher.watchDirectory((0, import_path22.join)((0, import_os10.homedir)(), ".claude", "projects"));
142742
142965
  }
142743
142966
  } catch (err) {
142744
142967
  const message = err instanceof Error ? err.message : String(err);
@@ -143292,16 +143515,16 @@ var StreamerServer = class {
143292
143515
  return scanner;
143293
143516
  }
143294
143517
  findJsonlPath(uuid3) {
143295
- const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects");
143518
+ const projectsDir = (0, import_path22.join)((0, import_os10.homedir)(), ".claude", "projects");
143296
143519
  if (!(0, import_fs24.existsSync)(projectsDir)) return null;
143297
143520
  const filename = `${uuid3}.jsonl`;
143298
143521
  for (const dir of (0, import_fs24.readdirSync)(projectsDir)) {
143299
- const fp = (0, import_path21.join)(projectsDir, dir, filename);
143522
+ const fp = (0, import_path22.join)(projectsDir, dir, filename);
143300
143523
  if ((0, import_fs24.existsSync)(fp)) return fp;
143301
- const projectDir = (0, import_path21.join)(projectsDir, dir);
143524
+ const projectDir = (0, import_path22.join)(projectsDir, dir);
143302
143525
  try {
143303
143526
  for (const sub of (0, import_fs24.readdirSync)(projectDir)) {
143304
- const subagentPath = (0, import_path21.join)(projectDir, sub, "subagents", filename);
143527
+ const subagentPath = (0, import_path22.join)(projectDir, sub, "subagents", filename);
143305
143528
  if ((0, import_fs24.existsSync)(subagentPath)) return subagentPath;
143306
143529
  }
143307
143530
  } catch {
@@ -144094,7 +144317,7 @@ var StreamerServer = class {
144094
144317
  sessionStore: this.sessionStore,
144095
144318
  // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true
144096
144319
  agentClient: this.agentClient,
144097
- conversationsDir: this.cacheDir ? (0, import_path21.join)((0, import_path21.dirname)(this.cacheDir), "conversations") : "",
144320
+ conversationsDir: this.cacheDir ? (0, import_path22.join)((0, import_path22.dirname)(this.cacheDir), "conversations") : "",
144098
144321
  agentConfig: this.agentConfig
144099
144322
  });
144100
144323
  json2(res, result.status, result.body);
@@ -144235,9 +144458,9 @@ var StreamerServer = class {
144235
144458
  // was passed to Claude via --session-id so the filename matches from the start.
144236
144459
  watchForJsonl(sessionId, projectPath) {
144237
144460
  const encoded = projectPath.replace(/[/\\:.]/g, "-");
144238
- const projectsDir = (0, import_path21.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
144461
+ const projectsDir = (0, import_path22.join)((0, import_os10.homedir)(), ".claude", "projects", encoded);
144239
144462
  const expectedFile = `${sessionId}.jsonl`;
144240
- const filePath = (0, import_path21.join)(projectsDir, expectedFile);
144463
+ const filePath = (0, import_path22.join)(projectsDir, expectedFile);
144241
144464
  const deadline = Date.now() + 12e4;
144242
144465
  let watcher = null;
144243
144466
  const cleanup = () => {
@@ -144259,8 +144482,8 @@ var StreamerServer = class {
144259
144482
  if (!resolvedFilePath && (0, import_fs24.existsSync)(projectsDir)) {
144260
144483
  try {
144261
144484
  const now = Date.now();
144262
- 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];
144263
- if (recent) resolvedFilePath = (0, import_path21.join)(projectsDir, recent.f);
144485
+ const recent = (0, import_fs24.readdirSync)(projectsDir).filter((f2) => f2.endsWith(".jsonl")).map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path22.join)(projectsDir, f2)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).sort((a, b2) => b2.mtime - a.mtime)[0];
144486
+ if (recent) resolvedFilePath = (0, import_path22.join)(projectsDir, recent.f);
144264
144487
  } catch {
144265
144488
  }
144266
144489
  }
@@ -144311,7 +144534,7 @@ var StreamerServer = class {
144311
144534
  watchForCodexRollout(sessionId, projectPath) {
144312
144535
  const deadline = Date.now() + 12e4;
144313
144536
  const now = /* @__PURE__ */ new Date();
144314
- const dateDir = (0, import_path21.join)(
144537
+ const dateDir = (0, import_path22.join)(
144315
144538
  String(now.getFullYear()),
144316
144539
  String(now.getMonth() + 1).padStart(2, "0"),
144317
144540
  String(now.getDate()).padStart(2, "0")
@@ -144352,7 +144575,7 @@ var StreamerServer = class {
144352
144575
  this.sessionStore.listManaged().filter((s3) => s3.id !== sessionId && s3.boundConversationId != null).map((s3) => s3.boundConversationId)
144353
144576
  );
144354
144577
  for (const root of this.codexRoots) {
144355
- const sessionsDir = (0, import_path21.join)(root, dateDir);
144578
+ const sessionsDir = (0, import_path22.join)(root, dateDir);
144356
144579
  if (!(0, import_fs24.existsSync)(sessionsDir)) continue;
144357
144580
  let candidateFiles;
144358
144581
  try {
@@ -144361,9 +144584,9 @@ var StreamerServer = class {
144361
144584
  continue;
144362
144585
  }
144363
144586
  const nowMs = Date.now();
144364
- 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);
144587
+ const recentCandidates = candidateFiles.map((f2) => ({ f: f2, mtime: (0, import_fs24.statSync)((0, import_path22.join)(sessionsDir, f2)).mtimeMs })).filter(({ mtime }) => nowMs - mtime < 1e4).sort((a, b2) => b2.mtime - a.mtime);
144365
144588
  for (const { f: f2 } of recentCandidates) {
144366
- const candidatePath = (0, import_path21.join)(sessionsDir, f2);
144589
+ const candidatePath = (0, import_path22.join)(sessionsDir, f2);
144367
144590
  const match2 = matchesProjectPath(candidatePath);
144368
144591
  if (!match2) continue;
144369
144592
  if (boundElsewhere.has(match2.id)) continue;
@@ -144817,7 +145040,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
144817
145040
  var import_node_crypto4 = require("crypto");
144818
145041
  var import_node_fs8 = require("fs");
144819
145042
  var import_node_path10 = require("path");
144820
- var import_promises16 = require("stream/promises");
145043
+ var import_promises18 = require("stream/promises");
144821
145044
 
144822
145045
  // src/updater/manifest.ts
144823
145046
  init_zod();
@@ -144892,7 +145115,7 @@ async function downloadAndVerify(opts) {
144892
145115
  }
144893
145116
  });
144894
145117
  try {
144895
- await (0, import_promises16.pipeline)(res.body.pipeThrough(measured), out);
145118
+ await (0, import_promises18.pipeline)(res.body.pipeThrough(measured), out);
144896
145119
  } catch (err) {
144897
145120
  (0, import_node_fs8.rmSync)(targetPath, { force: true });
144898
145121
  throw err;
@@ -145084,7 +145307,7 @@ var import_node_stream3 = __toESM(require("stream"), 1);
145084
145307
  var import_node_string_decoder = require("string_decoder");
145085
145308
  var import_node_path14 = __toESM(require("path"), 1);
145086
145309
  var import_node_fs11 = __toESM(require("fs"), 1);
145087
- var import_path22 = require("path");
145310
+ var import_path23 = require("path");
145088
145311
  var import_events5 = require("events");
145089
145312
  var import_assert = __toESM(require("assert"), 1);
145090
145313
  var import_buffer2 = require("buffer");
@@ -145094,9 +145317,9 @@ var import_node_path15 = require("path");
145094
145317
  var import_node_path16 = require("path");
145095
145318
  var import_fs26 = __toESM(require("fs"), 1);
145096
145319
  var import_fs27 = __toESM(require("fs"), 1);
145097
- var import_path23 = __toESM(require("path"), 1);
145098
- var import_node_path17 = require("path");
145099
145320
  var import_path24 = __toESM(require("path"), 1);
145321
+ var import_node_path17 = require("path");
145322
+ var import_path25 = __toESM(require("path"), 1);
145100
145323
  var import_node_fs12 = __toESM(require("fs"), 1);
145101
145324
  var import_node_assert = __toESM(require("assert"), 1);
145102
145325
  var import_node_crypto5 = require("crypto");
@@ -145106,7 +145329,7 @@ var import_fs28 = __toESM(require("fs"), 1);
145106
145329
  var import_node_fs14 = __toESM(require("fs"), 1);
145107
145330
  var import_node_path19 = __toESM(require("path"), 1);
145108
145331
  var import_node_fs15 = __toESM(require("fs"), 1);
145109
- var import_promises17 = __toESM(require("fs/promises"), 1);
145332
+ var import_promises19 = __toESM(require("fs/promises"), 1);
145110
145333
  var import_node_path20 = __toESM(require("path"), 1);
145111
145334
  var import_node_path21 = require("path");
145112
145335
  var import_node_fs16 = __toESM(require("fs"), 1);
@@ -146461,11 +146684,11 @@ var vn = (s3) => {
146461
146684
  };
146462
146685
  var Qi = (s3, t2) => {
146463
146686
  let e = new Map(t2.map((n) => [ut(n), true])), i = s3.filter, r = (n, o = "") => {
146464
- let h = o || (0, import_path22.parse)(n).root || ".", a;
146687
+ let h = o || (0, import_path23.parse)(n).root || ".", a;
146465
146688
  if (n === h) a = false;
146466
146689
  else {
146467
146690
  let l = e.get(n);
146468
- a = l !== void 0 ? l : r((0, import_path22.dirname)(n), h);
146691
+ a = l !== void 0 ? l : r((0, import_path23.dirname)(n), h);
146469
146692
  }
146470
146693
  return e.set(n, a), a;
146471
146694
  };
@@ -146585,7 +146808,7 @@ var de = class extends A {
146585
146808
  let [o, h] = ce(this.path);
146586
146809
  o && typeof h == "string" && (this.path = h, r = o);
146587
146810
  }
146588
- 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 });
146811
+ 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_path24.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 });
146589
146812
  let n = this.statCache.get(this.absolute);
146590
146813
  n ? this[si](n) : this[ss]();
146591
146814
  }
@@ -146644,7 +146867,7 @@ var de = class extends A {
146644
146867
  }
146645
146868
  [sr](t2) {
146646
146869
  if (!this.stat) throw new Error("cannot create link entry without stat");
146647
- this.type = "Link", this.linkpath = f(import_path23.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
146870
+ this.type = "Link", this.linkpath = f(import_path24.default.relative(this.cwd, t2)), this.stat.size = 0, this[fe](), this.end();
146648
146871
  }
146649
146872
  [er]() {
146650
146873
  if (!this.stat) throw new Error("cannot create file entry without stat");
@@ -147058,7 +147281,7 @@ var wt = class extends A {
147058
147281
  return typeof t2 == "string" ? this[ci](t2) : this[or](t2), this.flowing;
147059
147282
  }
147060
147283
  [or](t2) {
147061
- let e = f(import_path24.default.resolve(this.cwd, t2.path));
147284
+ let e = f(import_path25.default.resolve(this.cwd, t2.path));
147062
147285
  if (!this.filter(t2.path, t2)) t2.resume();
147063
147286
  else {
147064
147287
  let i = new pi(t2.path, e);
@@ -147067,7 +147290,7 @@ var wt = class extends A {
147067
147290
  this[Ft]();
147068
147291
  }
147069
147292
  [ci](t2) {
147070
- let e = f(import_path24.default.resolve(this.cwd, t2));
147293
+ let e = f(import_path25.default.resolve(this.cwd, t2));
147071
147294
  this[W].push(new pi(t2, e)), this[Ft]();
147072
147295
  }
147073
147296
  [ds](t2) {
@@ -147338,7 +147561,7 @@ var gr = (s3, t2, e) => {
147338
147561
  E2 ? e(E2) : x2 && a ? Es(x2, o, h, (Le) => S2(Le)) : n ? import_node_fs15.default.chmod(s3, r, e) : e();
147339
147562
  };
147340
147563
  if (s3 === d) return no(s3, S2);
147341
- if (l) return import_promises17.default.mkdir(s3, { mode: r, recursive: true }).then((E2) => S2(null, E2 ?? void 0), S2);
147564
+ if (l) return import_promises19.default.mkdir(s3, { mode: r, recursive: true }).then((E2) => S2(null, E2 ?? void 0), S2);
147342
147565
  let D2 = f(import_node_path20.default.relative(d, s3)).split("/");
147343
147566
  Ss(d, D2, r, c, d, void 0, S2);
147344
147567
  };