chat-recall 0.7.15 → 0.7.17

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/watch.js CHANGED
@@ -6963,18 +6963,20 @@ function updateShadow(sessionId, exp, prebuilt) {
6963
6963
  const curHash = containerSrcHash(current);
6964
6964
  if (!prior) {
6965
6965
  current.srcHash = curHash;
6966
+ current.mergeVersion = SHADOW_MERGE_VERSION;
6966
6967
  try {
6967
6968
  writeShadowContainer(tool, rawId, current);
6968
6969
  } catch {
6969
6970
  }
6970
6971
  return { status: "created", sessionId, tool, container: current, recovered: 0, path };
6971
6972
  }
6972
- if (prior.srcHash && prior.srcHash === curHash) {
6973
+ if (prior.srcHash && prior.srcHash === curHash && prior.mergeVersion === SHADOW_MERGE_VERSION) {
6973
6974
  return { status: "unchanged", sessionId, tool, container: prior, recovered: 0, path };
6974
6975
  }
6975
6976
  const merged = mergeContainer(prior, current);
6976
- if (merged.status !== "unchanged" || prior.srcHash !== curHash) {
6977
+ if (merged.status !== "unchanged" || prior.srcHash !== curHash || prior.mergeVersion !== SHADOW_MERGE_VERSION) {
6977
6978
  merged.container.srcHash = curHash;
6979
+ merged.container.mergeVersion = SHADOW_MERGE_VERSION;
6978
6980
  try {
6979
6981
  writeShadowContainer(tool, rawId, merged.container);
6980
6982
  } catch {
@@ -7067,7 +7069,7 @@ function shadowSizeOnDisk(tool, rawId) {
7067
7069
  return 0;
7068
7070
  }
7069
7071
  }
7070
- var WHOLE_FILE_TOOLS, STATE_META, lastLiveFingerprint;
7072
+ var WHOLE_FILE_TOOLS, STATE_META, SHADOW_MERGE_VERSION, lastLiveFingerprint;
7071
7073
  var init_shadow = __esm({
7072
7074
  "../engine/dist/transcript/shadow.js"() {
7073
7075
  "use strict";
@@ -7075,6 +7077,7 @@ var init_shadow = __esm({
7075
7077
  init_raw();
7076
7078
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
7077
7079
  STATE_META = /* @__PURE__ */ new Set(["mode", "permission-mode", "ai-title", "last-prompt", "pr-link", "summary"]);
7080
+ SHADOW_MERGE_VERSION = 2;
7078
7081
  lastLiveFingerprint = /* @__PURE__ */ new Map();
7079
7082
  }
7080
7083
  });
@@ -7973,7 +7976,64 @@ var init_first_prompt = __esm({
7973
7976
  });
7974
7977
 
7975
7978
  // ../engine/dist/core/backends/tail-read.js
7976
- import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync6 } from "node:fs";
7979
+ import { openSync as openSync5, readSync as readSync5, closeSync as closeSync5, statSync as statSync6, fstatSync } from "node:fs";
7980
+ function readAt(fd, pos, len) {
7981
+ const buf = Buffer.alloc(len);
7982
+ let got = 0;
7983
+ while (got < len) {
7984
+ const n = readSync5(fd, buf, got, len - got, pos + got);
7985
+ if (n <= 0)
7986
+ break;
7987
+ got += n;
7988
+ }
7989
+ return buf.subarray(0, got);
7990
+ }
7991
+ function linesBeforeOffset(path, offset) {
7992
+ if (offset <= 0)
7993
+ return 0;
7994
+ let fd = null;
7995
+ try {
7996
+ fd = openSync5(path, "r");
7997
+ const ino = fstatSync(fd).ino;
7998
+ const known = lineCounts.get(path);
7999
+ let pos = 0;
8000
+ let lines = 0;
8001
+ if (known && known.ino === ino && known.offset <= offset) {
8002
+ const edgeStart2 = Math.max(0, known.offset - EDGE_BYTES);
8003
+ if (readAt(fd, edgeStart2, known.offset - edgeStart2).equals(known.edge)) {
8004
+ pos = known.offset;
8005
+ lines = known.lines;
8006
+ }
8007
+ }
8008
+ const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
8009
+ while (pos < offset) {
8010
+ const n = readSync5(fd, block, 0, Math.min(SKIP_SCAN_BYTES, offset - pos), pos);
8011
+ if (n <= 0)
8012
+ return void 0;
8013
+ for (let i = block.indexOf(10, 0); i >= 0 && i < n; i = block.indexOf(10, i + 1))
8014
+ lines++;
8015
+ pos += n;
8016
+ }
8017
+ const edgeStart = Math.max(0, offset - EDGE_BYTES);
8018
+ const edge = readAt(fd, edgeStart, offset - edgeStart);
8019
+ if (lineCounts.size >= LINE_COUNT_CACHE_MAX && !lineCounts.has(path)) {
8020
+ const oldest = lineCounts.keys().next().value;
8021
+ if (oldest !== void 0)
8022
+ lineCounts.delete(oldest);
8023
+ }
8024
+ lineCounts.set(path, { offset, lines, ino, edge });
8025
+ return lines;
8026
+ } catch {
8027
+ return void 0;
8028
+ } finally {
8029
+ if (fd !== null) {
8030
+ try {
8031
+ closeSync5(fd);
8032
+ } catch {
8033
+ }
8034
+ }
8035
+ }
8036
+ }
7977
8037
  function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
7978
8038
  let size = 0;
7979
8039
  try {
@@ -8001,7 +8061,7 @@ function readTailFromOffset(path, offset, maxBytes = TAIL_READ_MAX_BYTES) {
8001
8061
  const lastNl = slice.lastIndexOf(10);
8002
8062
  if (lastNl >= 0) {
8003
8063
  const text = slice.subarray(0, lastNl + 1).toString("utf-8");
8004
- return { text, newOffset: offset + lastNl + 1 };
8064
+ return { text, newOffset: offset + lastNl + 1, baseLine: linesBeforeOffset(path, offset) };
8005
8065
  }
8006
8066
  if (offset + got >= size)
8007
8067
  return { text: "", newOffset: offset };
@@ -8034,12 +8094,15 @@ function findNewline(fd, from, size) {
8034
8094
  }
8035
8095
  return -1;
8036
8096
  }
8037
- var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
8097
+ var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES, lineCounts, LINE_COUNT_CACHE_MAX, EDGE_BYTES;
8038
8098
  var init_tail_read = __esm({
8039
8099
  "../engine/dist/core/backends/tail-read.js"() {
8040
8100
  "use strict";
8041
8101
  TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
8042
8102
  SKIP_SCAN_BYTES = 1024 * 1024;
8103
+ lineCounts = /* @__PURE__ */ new Map();
8104
+ LINE_COUNT_CACHE_MAX = 2048;
8105
+ EDGE_BYTES = 64;
8043
8106
  }
8044
8107
  });
8045
8108
 
@@ -18269,6 +18332,7 @@ function persistLedgerData(server, serverData) {
18269
18332
  const data = load();
18270
18333
  data[server] = serverData;
18271
18334
  persist(data);
18335
+ flushLedger();
18272
18336
  }
18273
18337
  function unackedCursorAttemptCount() {
18274
18338
  return unackedCursorAttempts;
@@ -18551,7 +18615,7 @@ var init_http = __esm({
18551
18615
  CLI_VERSION = JSON.parse(readFileSync32(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
18552
18616
  } catch {
18553
18617
  try {
18554
- if (true) CLI_VERSION = "0.7.15";
18618
+ if (true) CLI_VERSION = "0.7.17";
18555
18619
  } catch {
18556
18620
  }
18557
18621
  }
@@ -19045,7 +19109,7 @@ var init_telemetry = __esm({
19045
19109
  init_sync_client();
19046
19110
  cliVersion = () => {
19047
19111
  try {
19048
- return true ? "0.7.15" : "dev";
19112
+ return true ? "0.7.17" : "dev";
19049
19113
  } catch {
19050
19114
  return "dev";
19051
19115
  }
@@ -20284,7 +20348,8 @@ async function syncToTargetInScope(cred, opts = {}) {
20284
20348
  clearTimeout(timer);
20285
20349
  }
20286
20350
  };
20287
- const postChunkedHead = async (conv, fileSize) => {
20351
+ const postChunkedHead = async (head, fileSize) => {
20352
+ const conv = { ...head, chunk_head: true };
20288
20353
  const headers = { "content-type": "application/json" };
20289
20354
  if (cred.token) headers.authorization = `Bearer ${cred.token}`;
20290
20355
  const ac = new AbortController();
@@ -20300,9 +20365,10 @@ async function syncToTargetInScope(cred, opts = {}) {
20300
20365
  const body = await res.json().catch(() => ({}));
20301
20366
  const id = conv.session_id;
20302
20367
  const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
20368
+ const staged = (body.rebuild_staged ?? []).includes(id);
20303
20369
  const mtime = conv.mtime;
20304
20370
  try {
20305
- if (!guarded) {
20371
+ if (staged || !guarded) {
20306
20372
  markSynced(base3, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
20307
20373
  } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
20308
20374
  markSynced(base3, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
@@ -21035,8 +21101,13 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21035
21101
  const backend = getBackendForId(ref.prefixedId);
21036
21102
  if (!backend?.readFromOffset) return null;
21037
21103
  const mapPath = opts.mapPath ?? ((p) => p);
21038
- const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
21104
+ const { text: tailText, newOffset, skippedBytes, baseLine } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
21039
21105
  if (newOffset <= fromOffset) return null;
21106
+ const fileSize = backend.fileSize?.(ref.prefixedId) ?? 0;
21107
+ const position = {
21108
+ ...typeof baseLine === "number" ? { base_line: baseLine } : {},
21109
+ final: fileSize > 0 && newOffset >= fileSize
21110
+ };
21040
21111
  const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
21041
21112
  const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
21042
21113
  const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
@@ -21049,6 +21120,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21049
21120
  append: true,
21050
21121
  base_offset: fromOffset,
21051
21122
  from_offset: newOffset,
21123
+ ...position,
21052
21124
  redacted_text: "",
21053
21125
  envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
21054
21126
  mtime: Math.floor(ref.mtime) || 0
@@ -21095,6 +21167,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
21095
21167
  // equals its stored synced-through offset.
21096
21168
  from_offset: newOffset,
21097
21169
  // where it ends → new synced-through offset.
21170
+ ...position,
21098
21171
  redacted_text: redactedText,
21099
21172
  envelope,
21100
21173
  mtime: Math.floor(ref.mtime) || 0
@@ -21178,7 +21251,7 @@ var init_sync_client = __esm({
21178
21251
  ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
21179
21252
  } catch {
21180
21253
  try {
21181
- if (true) ownVersion = "0.7.15";
21254
+ if (true) ownVersion = "0.7.17";
21182
21255
  } catch {
21183
21256
  }
21184
21257
  }
@@ -24315,7 +24388,7 @@ try {
24315
24388
  ownVersion2 = JSON.parse(readFileSync39(join48(dir, "../package.json"), "utf-8")).version || ownVersion2;
24316
24389
  } catch {
24317
24390
  try {
24318
- if (true) ownVersion2 = "0.7.15";
24391
+ if (true) ownVersion2 = "0.7.17";
24319
24392
  } catch {
24320
24393
  }
24321
24394
  }
@@ -24700,7 +24773,7 @@ async function runCollectorMigration(opts) {
24700
24773
  init_sync_ledger();
24701
24774
  init_collector_health();
24702
24775
  import { existsSync as existsSync50, readFileSync as readFileSync45, statSync as statSync46 } from "fs";
24703
- var CLI_VERSION3 = true ? "0.7.15" : "0.0.0";
24776
+ var CLI_VERSION3 = true ? "0.7.17" : "0.0.0";
24704
24777
  var DEBOUNCE_MS = 5e3;
24705
24778
  var PLANS_DIR = claudeBackend.plansDir();
24706
24779
  var TASKS_DIR = claudeBackend.tasksDir();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.7.15",
3
+ "version": "0.7.17",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"