chat-recall 0.7.16 → 0.7.18

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/mcp.js CHANGED
@@ -2034,7 +2034,7 @@ var require_thread_stream = __commonJS({
2034
2034
  var { version: version2 } = require_package();
2035
2035
  var { EventEmitter } = __require("events");
2036
2036
  var { Worker: Worker2 } = __require("worker_threads");
2037
- var { join: join53 } = __require("path");
2037
+ var { join: join54 } = __require("path");
2038
2038
  var { pathToFileURL } = __require("url");
2039
2039
  var { wait } = require_wait();
2040
2040
  var {
@@ -2077,7 +2077,7 @@ var require_thread_stream = __commonJS({
2077
2077
  function createWorker(stream, opts) {
2078
2078
  const { filename, workerData } = opts;
2079
2079
  const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
2080
- const toExecute = bundlerOverrides["thread-stream-worker"] || join53(__dirname, "lib", "worker.js");
2080
+ const toExecute = bundlerOverrides["thread-stream-worker"] || join54(__dirname, "lib", "worker.js");
2081
2081
  const worker = new Worker2(toExecute, {
2082
2082
  ...opts.workerOpts,
2083
2083
  trackUnmanagedFds: false,
@@ -2480,7 +2480,7 @@ var require_transport = __commonJS({
2480
2480
  "use strict";
2481
2481
  var { createRequire: createRequire3 } = __require("module");
2482
2482
  var getCallers = require_caller();
2483
- var { join: join53, isAbsolute: isAbsolute3, sep: sep4 } = __require("node:path");
2483
+ var { join: join54, isAbsolute: isAbsolute3, sep: sep4 } = __require("node:path");
2484
2484
  var sleep = require_atomic_sleep();
2485
2485
  var onExit = require_on_exit_leak_free();
2486
2486
  var ThreadStream = require_thread_stream();
@@ -2543,7 +2543,7 @@ var require_transport = __commonJS({
2543
2543
  throw new Error("only one of target or targets can be specified");
2544
2544
  }
2545
2545
  if (targets) {
2546
- target = bundlerOverrides["pino-worker"] || join53(__dirname, "worker.js");
2546
+ target = bundlerOverrides["pino-worker"] || join54(__dirname, "worker.js");
2547
2547
  options.targets = targets.filter((dest) => dest.target).map((dest) => {
2548
2548
  return {
2549
2549
  ...dest,
@@ -2561,7 +2561,7 @@ var require_transport = __commonJS({
2561
2561
  });
2562
2562
  });
2563
2563
  } else if (pipeline) {
2564
- target = bundlerOverrides["pino-worker"] || join53(__dirname, "worker.js");
2564
+ target = bundlerOverrides["pino-worker"] || join54(__dirname, "worker.js");
2565
2565
  options.pipelines = [pipeline.map((dest) => {
2566
2566
  return {
2567
2567
  ...dest,
@@ -2583,7 +2583,7 @@ var require_transport = __commonJS({
2583
2583
  return origin;
2584
2584
  }
2585
2585
  if (origin === "pino/file") {
2586
- return join53(__dirname, "..", "file.js");
2586
+ return join54(__dirname, "..", "file.js");
2587
2587
  }
2588
2588
  let fixTarget2;
2589
2589
  for (const filePath of callers) {
@@ -3572,7 +3572,7 @@ var require_safe_stable_stringify = __commonJS({
3572
3572
  return circularValue;
3573
3573
  }
3574
3574
  let res = "";
3575
- let join53 = ",";
3575
+ let join54 = ",";
3576
3576
  const originalIndentation = indentation;
3577
3577
  if (Array.isArray(value)) {
3578
3578
  if (value.length === 0) {
@@ -3586,7 +3586,7 @@ var require_safe_stable_stringify = __commonJS({
3586
3586
  indentation += spacer;
3587
3587
  res += `
3588
3588
  ${indentation}`;
3589
- join53 = `,
3589
+ join54 = `,
3590
3590
  ${indentation}`;
3591
3591
  }
3592
3592
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3594,13 +3594,13 @@ ${indentation}`;
3594
3594
  for (; i < maximumValuesToStringify - 1; i++) {
3595
3595
  const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3596
3596
  res += tmp2 !== void 0 ? tmp2 : "null";
3597
- res += join53;
3597
+ res += join54;
3598
3598
  }
3599
3599
  const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
3600
3600
  res += tmp !== void 0 ? tmp : "null";
3601
3601
  if (value.length - 1 > maximumBreadth) {
3602
3602
  const removedKeys = value.length - maximumBreadth - 1;
3603
- res += `${join53}"... ${getItemCount(removedKeys)} not stringified"`;
3603
+ res += `${join54}"... ${getItemCount(removedKeys)} not stringified"`;
3604
3604
  }
3605
3605
  if (spacer !== "") {
3606
3606
  res += `
@@ -3621,7 +3621,7 @@ ${originalIndentation}`;
3621
3621
  let separator = "";
3622
3622
  if (spacer !== "") {
3623
3623
  indentation += spacer;
3624
- join53 = `,
3624
+ join54 = `,
3625
3625
  ${indentation}`;
3626
3626
  whitespace = " ";
3627
3627
  }
@@ -3635,13 +3635,13 @@ ${indentation}`;
3635
3635
  const tmp = stringifyFnReplacer(key3, value, stack, replacer, spacer, indentation);
3636
3636
  if (tmp !== void 0) {
3637
3637
  res += `${separator}${strEscape(key3)}:${whitespace}${tmp}`;
3638
- separator = join53;
3638
+ separator = join54;
3639
3639
  }
3640
3640
  }
3641
3641
  if (keyLength > maximumBreadth) {
3642
3642
  const removedKeys = keyLength - maximumBreadth;
3643
3643
  res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
3644
- separator = join53;
3644
+ separator = join54;
3645
3645
  }
3646
3646
  if (spacer !== "" && separator.length > 1) {
3647
3647
  res = `
@@ -3682,7 +3682,7 @@ ${originalIndentation}`;
3682
3682
  }
3683
3683
  const originalIndentation = indentation;
3684
3684
  let res = "";
3685
- let join53 = ",";
3685
+ let join54 = ",";
3686
3686
  if (Array.isArray(value)) {
3687
3687
  if (value.length === 0) {
3688
3688
  return "[]";
@@ -3695,7 +3695,7 @@ ${originalIndentation}`;
3695
3695
  indentation += spacer;
3696
3696
  res += `
3697
3697
  ${indentation}`;
3698
- join53 = `,
3698
+ join54 = `,
3699
3699
  ${indentation}`;
3700
3700
  }
3701
3701
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
@@ -3703,13 +3703,13 @@ ${indentation}`;
3703
3703
  for (; i < maximumValuesToStringify - 1; i++) {
3704
3704
  const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3705
3705
  res += tmp2 !== void 0 ? tmp2 : "null";
3706
- res += join53;
3706
+ res += join54;
3707
3707
  }
3708
3708
  const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
3709
3709
  res += tmp !== void 0 ? tmp : "null";
3710
3710
  if (value.length - 1 > maximumBreadth) {
3711
3711
  const removedKeys = value.length - maximumBreadth - 1;
3712
- res += `${join53}"... ${getItemCount(removedKeys)} not stringified"`;
3712
+ res += `${join54}"... ${getItemCount(removedKeys)} not stringified"`;
3713
3713
  }
3714
3714
  if (spacer !== "") {
3715
3715
  res += `
@@ -3722,7 +3722,7 @@ ${originalIndentation}`;
3722
3722
  let whitespace = "";
3723
3723
  if (spacer !== "") {
3724
3724
  indentation += spacer;
3725
- join53 = `,
3725
+ join54 = `,
3726
3726
  ${indentation}`;
3727
3727
  whitespace = " ";
3728
3728
  }
@@ -3731,7 +3731,7 @@ ${indentation}`;
3731
3731
  const tmp = stringifyArrayReplacer(key3, value[key3], stack, replacer, spacer, indentation);
3732
3732
  if (tmp !== void 0) {
3733
3733
  res += `${separator}${strEscape(key3)}:${whitespace}${tmp}`;
3734
- separator = join53;
3734
+ separator = join54;
3735
3735
  }
3736
3736
  }
3737
3737
  if (spacer !== "" && separator.length > 1) {
@@ -3789,20 +3789,20 @@ ${originalIndentation}`;
3789
3789
  indentation += spacer;
3790
3790
  let res2 = `
3791
3791
  ${indentation}`;
3792
- const join54 = `,
3792
+ const join55 = `,
3793
3793
  ${indentation}`;
3794
3794
  const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
3795
3795
  let i = 0;
3796
3796
  for (; i < maximumValuesToStringify - 1; i++) {
3797
3797
  const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3798
3798
  res2 += tmp2 !== void 0 ? tmp2 : "null";
3799
- res2 += join54;
3799
+ res2 += join55;
3800
3800
  }
3801
3801
  const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
3802
3802
  res2 += tmp !== void 0 ? tmp : "null";
3803
3803
  if (value.length - 1 > maximumBreadth) {
3804
3804
  const removedKeys = value.length - maximumBreadth - 1;
3805
- res2 += `${join54}"... ${getItemCount(removedKeys)} not stringified"`;
3805
+ res2 += `${join55}"... ${getItemCount(removedKeys)} not stringified"`;
3806
3806
  }
3807
3807
  res2 += `
3808
3808
  ${originalIndentation}`;
@@ -3818,16 +3818,16 @@ ${originalIndentation}`;
3818
3818
  return '"[Object]"';
3819
3819
  }
3820
3820
  indentation += spacer;
3821
- const join53 = `,
3821
+ const join54 = `,
3822
3822
  ${indentation}`;
3823
3823
  let res = "";
3824
3824
  let separator = "";
3825
3825
  let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
3826
3826
  if (isTypedArrayWithEntries(value)) {
3827
- res += stringifyTypedArray(value, join53, maximumBreadth);
3827
+ res += stringifyTypedArray(value, join54, maximumBreadth);
3828
3828
  keys = keys.slice(value.length);
3829
3829
  maximumPropertiesToStringify -= value.length;
3830
- separator = join53;
3830
+ separator = join54;
3831
3831
  }
3832
3832
  if (deterministic) {
3833
3833
  keys = sort(keys, comparator);
@@ -3838,13 +3838,13 @@ ${indentation}`;
3838
3838
  const tmp = stringifyIndent(key3, value[key3], stack, spacer, indentation);
3839
3839
  if (tmp !== void 0) {
3840
3840
  res += `${separator}${strEscape(key3)}: ${tmp}`;
3841
- separator = join53;
3841
+ separator = join54;
3842
3842
  }
3843
3843
  }
3844
3844
  if (keyLength > maximumBreadth) {
3845
3845
  const removedKeys = keyLength - maximumBreadth;
3846
3846
  res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
3847
- separator = join53;
3847
+ separator = join54;
3848
3848
  }
3849
3849
  if (separator !== "") {
3850
3850
  res = `
@@ -11680,6 +11680,7 @@ var init_from_events = __esm({
11680
11680
  // ../engine/dist/transcript/raw.js
11681
11681
  var raw_exports = {};
11682
11682
  __export(raw_exports, {
11683
+ RAW_PARSE_MAX_BYTES: () => RAW_PARSE_MAX_BYTES,
11683
11684
  archiveRawSession: () => archiveRawSession,
11684
11685
  buildRawContainer: () => buildRawContainer,
11685
11686
  containerSrcHash: () => containerSrcHash,
@@ -11979,11 +11980,11 @@ async function archiveRawSession(store2, sessionId) {
11979
11980
  return await store2.putRawSession(sessionId, exp.tool, Math.floor(exp.mtime), gz, size);
11980
11981
  }
11981
11982
  function detectForkPredecessor(sessionPath) {
11982
- const { readFileSync: readFileSync44, readdirSync: readdirSync34 } = requireFs();
11983
- const { dirname: dirname21, join: join53, basename: basename24 } = requirePath();
11983
+ const { readFileSync: readFileSync45, readdirSync: readdirSync34 } = requireFs();
11984
+ const { dirname: dirname21, join: join54, basename: basename24 } = requirePath();
11984
11985
  let text;
11985
11986
  try {
11986
- text = readFileSync44(sessionPath, "utf-8");
11987
+ text = readFileSync45(sessionPath, "utf-8");
11987
11988
  } catch {
11988
11989
  return null;
11989
11990
  }
@@ -12017,7 +12018,7 @@ function detectForkPredecessor(sessionPath) {
12017
12018
  if (!f.endsWith(".jsonl") || f === self || f === "sessions-index.json")
12018
12019
  continue;
12019
12020
  try {
12020
- if (readFileSync44(join53(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
12021
+ if (readFileSync45(join54(dir, f), "utf-8").includes(`"uuid":"${headParent}"`)) {
12021
12022
  return basename24(f, ".jsonl");
12022
12023
  }
12023
12024
  } catch {
@@ -12031,7 +12032,7 @@ function requireFs() {
12031
12032
  function requirePath() {
12032
12033
  return pathMod;
12033
12034
  }
12034
- var MARKER_QUOTE;
12035
+ var RAW_PARSE_MAX_BYTES, MARKER_QUOTE;
12035
12036
  var init_raw = __esm({
12036
12037
  "../engine/dist/transcript/raw.js"() {
12037
12038
  "use strict";
@@ -12039,6 +12040,7 @@ var init_raw = __esm({
12039
12040
  init_claude2();
12040
12041
  init_codex2();
12041
12042
  init_from_events();
12043
+ RAW_PARSE_MAX_BYTES = 40 * 1024 * 1024;
12042
12044
  MARKER_QUOTE = /\[REDACTED:[A-Za-z0-9_-]+\]"/g;
12043
12045
  }
12044
12046
  });
@@ -12195,18 +12197,20 @@ function updateShadow(sessionId, exp, prebuilt) {
12195
12197
  const curHash = containerSrcHash(current);
12196
12198
  if (!prior) {
12197
12199
  current.srcHash = curHash;
12200
+ current.mergeVersion = SHADOW_MERGE_VERSION;
12198
12201
  try {
12199
12202
  writeShadowContainer(tool, rawId, current);
12200
12203
  } catch {
12201
12204
  }
12202
12205
  return { status: "created", sessionId, tool, container: current, recovered: 0, path: path2 };
12203
12206
  }
12204
- if (prior.srcHash && prior.srcHash === curHash) {
12207
+ if (prior.srcHash && prior.srcHash === curHash && prior.mergeVersion === SHADOW_MERGE_VERSION) {
12205
12208
  return { status: "unchanged", sessionId, tool, container: prior, recovered: 0, path: path2 };
12206
12209
  }
12207
12210
  const merged = mergeContainer(prior, current);
12208
- if (merged.status !== "unchanged" || prior.srcHash !== curHash) {
12211
+ if (merged.status !== "unchanged" || prior.srcHash !== curHash || prior.mergeVersion !== SHADOW_MERGE_VERSION) {
12209
12212
  merged.container.srcHash = curHash;
12213
+ merged.container.mergeVersion = SHADOW_MERGE_VERSION;
12210
12214
  try {
12211
12215
  writeShadowContainer(tool, rawId, merged.container);
12212
12216
  } catch {
@@ -12244,7 +12248,7 @@ function shadowUncompressedBytes(tool, rawId) {
12244
12248
  }
12245
12249
  }
12246
12250
  }
12247
- var WHOLE_FILE_TOOLS, STATE_META;
12251
+ var WHOLE_FILE_TOOLS, STATE_META, SHADOW_MERGE_VERSION;
12248
12252
  var init_shadow = __esm({
12249
12253
  "../engine/dist/transcript/shadow.js"() {
12250
12254
  "use strict";
@@ -12252,6 +12256,7 @@ var init_shadow = __esm({
12252
12256
  init_raw();
12253
12257
  WHOLE_FILE_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
12254
12258
  STATE_META = /* @__PURE__ */ new Set(["mode", "permission-mode", "ai-title", "last-prompt", "pr-link", "summary"]);
12259
+ SHADOW_MERGE_VERSION = 2;
12255
12260
  }
12256
12261
  });
12257
12262
 
@@ -19731,6 +19736,11 @@ function markSynced(server, rows) {
19731
19736
  base2.s = s;
19732
19737
  }
19733
19738
  if (r.hash !== void 0) base2.h = r.hash;
19739
+ const completesChunked = cursorFromCaller && r.chunkFinal === true && prev?.k === 1;
19740
+ if (cursorFromCaller && (r.full || completesChunked) && o > 0) base2.F = o;
19741
+ else if (prev?.F !== void 0) base2.F = prev.F;
19742
+ if (cursorFromCaller && r.chunkedHead) base2.k = 1;
19743
+ else if (prev?.k && !completesChunked && !(cursorFromCaller && r.full)) base2.k = 1;
19734
19744
  forServer[r.id] = base2;
19735
19745
  }
19736
19746
  persist(data);
@@ -19787,6 +19797,14 @@ function markFieldFullPassDone(server, field) {
19787
19797
  delete st.force[field.name];
19788
19798
  persistReconcile(data);
19789
19799
  }
19800
+ function forgetFullCoverage(server, sessionId) {
19801
+ const data = load();
19802
+ const row = data[server]?.[sessionId];
19803
+ if (!row || typeof row !== "object") return;
19804
+ delete row.F;
19805
+ delete row.h;
19806
+ persist(data);
19807
+ }
19790
19808
  function markFullResync(server, sessionId) {
19791
19809
  const data = load();
19792
19810
  const forServer = data[server];
@@ -19795,7 +19813,11 @@ function markFullResync(server, sessionId) {
19795
19813
  delete forServer[sessionId];
19796
19814
  persist(data);
19797
19815
  }
19798
- function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
19816
+ function fullCoveredOffset(row) {
19817
+ if (row.F !== void 0) return row.F;
19818
+ return row.h ? row.s ?? 0 : 0;
19819
+ }
19820
+ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly, nowMs = Date.now()) {
19799
19821
  if (!row) return "full";
19800
19822
  if (row.v < extractorVersion) return "full";
19801
19823
  const offset = row.o ?? 0;
@@ -19803,10 +19825,13 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
19803
19825
  if (process.env.CHAT_RECALL_TAIL_APPEND !== "0" && isAppendOnly && fileSize > 0 && offset > 0 && fileSize > (row.s ?? 0)) {
19804
19826
  return "append";
19805
19827
  }
19828
+ if (isAppendOnly && offset > 0 && fileSize > 0 && fileSize === (row.s ?? 0) && !row.k && fullCoveredOffset(row) < offset && nowMs - Math.floor(mtime) >= SETTLE_QUIET_MS) {
19829
+ return "full";
19830
+ }
19806
19831
  if (row.m >= Math.floor(mtime)) return "skip";
19807
19832
  return "full";
19808
19833
  }
19809
- var ledgerPath, cache3, base, diskStamp, rowKey, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache;
19834
+ var ledgerPath, cache3, base, diskStamp, rowKey, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache, SETTLE_QUIET_MS;
19810
19835
  var init_sync_ledger = __esm({
19811
19836
  "src/sync-ledger.ts"() {
19812
19837
  "use strict";
@@ -19826,6 +19851,7 @@ var init_sync_ledger = __esm({
19826
19851
  unackedCursorAttempts = 0;
19827
19852
  reconcilePath = () => join46(getDataDir(), "sync-field-reconcile.json");
19828
19853
  reconcileCache = null;
19854
+ SETTLE_QUIET_MS = 2 * 60 * 60 * 1e3;
19829
19855
  }
19830
19856
  });
19831
19857
 
@@ -19971,7 +19997,7 @@ var init_http = __esm({
19971
19997
  CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19972
19998
  } catch {
19973
19999
  try {
19974
- if (true) CLI_VERSION = "0.7.16";
20000
+ if (true) CLI_VERSION = "0.7.18";
19975
20001
  } catch {
19976
20002
  }
19977
20003
  }
@@ -20469,7 +20495,7 @@ var init_telemetry = __esm({
20469
20495
  init_sync_client();
20470
20496
  cliVersion2 = () => {
20471
20497
  try {
20472
- return true ? "0.7.16" : "dev";
20498
+ return true ? "0.7.18" : "dev";
20473
20499
  } catch {
20474
20500
  return "dev";
20475
20501
  }
@@ -20479,6 +20505,150 @@ var init_telemetry = __esm({
20479
20505
  }
20480
20506
  });
20481
20507
 
20508
+ // src/raw-archive-parts.ts
20509
+ import { createReadStream as createReadStream4, createWriteStream as createWriteStream2, openSync as openSync10, readSync as readSync8, closeSync as closeSync10, rmSync as rmSync5, statSync as statSync42 } from "node:fs";
20510
+ import { tmpdir as tmpdir3 } from "node:os";
20511
+ import { join as join48 } from "node:path";
20512
+ import { randomBytes } from "node:crypto";
20513
+ import { createGzip } from "node:zlib";
20514
+ import { once } from "node:events";
20515
+ import { readFileSync as readFileSync37 } from "node:fs";
20516
+ async function* fileLines(path2) {
20517
+ let rest = "";
20518
+ const decoder = new TextDecoder("utf-8");
20519
+ for await (const chunk of createReadStream4(path2, { highWaterMark: 1024 * 1024 })) {
20520
+ rest += decoder.decode(chunk, { stream: true });
20521
+ let nl = rest.indexOf("\n");
20522
+ while (nl >= 0) {
20523
+ yield rest.slice(0, nl + 1);
20524
+ rest = rest.slice(nl + 1);
20525
+ nl = rest.indexOf("\n");
20526
+ }
20527
+ }
20528
+ rest += decoder.decode();
20529
+ if (rest) yield rest;
20530
+ }
20531
+ function* textLines(text) {
20532
+ let at = 0;
20533
+ while (at < text.length) {
20534
+ const nl = text.indexOf("\n", at);
20535
+ const end = nl < 0 ? text.length : nl + 1;
20536
+ yield text.slice(at, end);
20537
+ at = end;
20538
+ }
20539
+ }
20540
+ async function writeArchiveFile(source, meta, redact, dir = tmpdir3()) {
20541
+ const path2 = join48(dir, `cr-archive-${randomBytes(8).toString("hex")}.json.gz`);
20542
+ const gzip = createGzip({ level: 6 });
20543
+ const out = createWriteStream2(path2, { mode: 384 });
20544
+ const done = once(out, "finish");
20545
+ gzip.pipe(out);
20546
+ let size = 0;
20547
+ const write = async (s) => {
20548
+ size += Buffer.byteLength(s);
20549
+ if (!gzip.write(s)) await once(gzip, "drain");
20550
+ };
20551
+ try {
20552
+ await write(`{"v":1,"tool":${JSON.stringify(meta.tool)},"mtime":${Math.floor(meta.mtime)},"files":[`);
20553
+ for (const [i, f] of source.files.entries()) {
20554
+ await write(`${i ? "," : ""}{"name":${JSON.stringify(f.name)},"text":"`);
20555
+ const whole = () => source.kind === "memory" ? f.text : readFileSync37(f.path, "utf-8");
20556
+ if (!f.name.endsWith(".jsonl")) {
20557
+ await write(escapeJson(redact(whole())));
20558
+ } else {
20559
+ const lines = source.kind === "memory" ? textLines(f.text) : fileLines(f.path);
20560
+ for await (const line of lines) {
20561
+ const nl = line.endsWith("\n");
20562
+ const body2 = nl ? line.slice(0, -1) : line;
20563
+ await write(escapeJson((body2 ? redactJsonLine(body2, redact) : body2) + (nl ? "\n" : "")));
20564
+ }
20565
+ }
20566
+ await write('"}');
20567
+ }
20568
+ await write("]}");
20569
+ gzip.end();
20570
+ await done;
20571
+ } catch (err) {
20572
+ gzip.destroy();
20573
+ out.destroy();
20574
+ rmSync5(path2, { force: true });
20575
+ throw err;
20576
+ }
20577
+ return { path: path2, gzSize: statSync42(path2).size, size };
20578
+ }
20579
+ async function putPart(url, body2) {
20580
+ let last;
20581
+ for (let attempt = 1; attempt <= PART_ATTEMPTS; attempt++) {
20582
+ const ac = new AbortController();
20583
+ const timer = setTimeout(() => ac.abort(), PART_TIMEOUT_MS);
20584
+ try {
20585
+ const r = await fetch(url, { method: "PUT", body: body2, signal: ac.signal });
20586
+ const etag = r.headers.get("etag");
20587
+ if (r.ok && etag) return etag;
20588
+ last = new Error(`part PUT answered ${r.status}`);
20589
+ if (r.status === 403 || r.status === 404) break;
20590
+ } catch (err) {
20591
+ last = err;
20592
+ } finally {
20593
+ clearTimeout(timer);
20594
+ }
20595
+ }
20596
+ throw last instanceof Error ? last : new Error(String(last));
20597
+ }
20598
+ async function uploadArchiveFile(base2, token, args) {
20599
+ const headers = { "content-type": "application/json" };
20600
+ if (token) headers.authorization = `Bearer ${token}`;
20601
+ const post = async (path2, body2) => {
20602
+ const r = await fetch(`${base2}/api/sync/raw-upload/${path2}`, { method: "POST", headers, body: JSON.stringify(body2) });
20603
+ const json = await r.json().catch(() => ({}));
20604
+ return { status: r.status, json };
20605
+ };
20606
+ const start = await post("start", {
20607
+ session_id: args.sessionId,
20608
+ tool: args.tool,
20609
+ mtime: Math.floor(args.mtime),
20610
+ size: args.file.size,
20611
+ gz_size: args.file.gzSize,
20612
+ project_id: args.projectId,
20613
+ project_path: args.projectPath
20614
+ });
20615
+ if (start.status === 501 || start.status === 404) return "unsupported";
20616
+ if (start.status !== 200) throw new Error(`raw upload start answered ${start.status}: ${start.json.error ?? ""}`);
20617
+ if (start.json.result === "unchanged" || start.json.result === "shrink-protected") return start.json.result;
20618
+ const urls = start.json.urls;
20619
+ const partBytes = Number(start.json.part_bytes);
20620
+ const parts = [];
20621
+ const fd = openSync10(args.file.path, "r");
20622
+ try {
20623
+ for (const [i, url] of urls.entries()) {
20624
+ const len = Math.min(partBytes, args.file.gzSize - i * partBytes);
20625
+ const buf = Buffer.allocUnsafe(len);
20626
+ let got = 0;
20627
+ while (got < len) {
20628
+ const n = readSync8(fd, buf, got, len - got, i * partBytes + got);
20629
+ if (n <= 0) break;
20630
+ got += n;
20631
+ }
20632
+ parts.push({ part_number: i + 1, etag: await putPart(url, buf.subarray(0, got)) });
20633
+ }
20634
+ } finally {
20635
+ closeSync10(fd);
20636
+ }
20637
+ const done = await post("complete", { session_id: args.sessionId, upload_id: start.json.upload_id, parts });
20638
+ if (done.status !== 200) throw new Error(`raw upload complete answered ${done.status}: ${done.json.error ?? ""}`);
20639
+ return done.json.result;
20640
+ }
20641
+ var escapeJson, PART_TIMEOUT_MS, PART_ATTEMPTS;
20642
+ var init_raw_archive_parts = __esm({
20643
+ "src/raw-archive-parts.ts"() {
20644
+ "use strict";
20645
+ init_transcript();
20646
+ escapeJson = (s) => JSON.stringify(s).slice(1, -1);
20647
+ PART_TIMEOUT_MS = 12e4;
20648
+ PART_ATTEMPTS = 3;
20649
+ }
20650
+ });
20651
+
20482
20652
  // src/server-probe.ts
20483
20653
  var server_probe_exports = {};
20484
20654
  __export(server_probe_exports, {
@@ -20640,11 +20810,11 @@ __export(auto_update_exports, {
20640
20810
  tarballUrl: () => tarballUrl
20641
20811
  });
20642
20812
  import { execSync as execSync4, execFileSync as execFileSync3 } from "node:child_process";
20643
- import { readFileSync as readFileSync37, existsSync as existsSync45 } from "node:fs";
20813
+ import { readFileSync as readFileSync38, existsSync as existsSync45 } from "node:fs";
20644
20814
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20645
- import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync42, mkdirSync as mkdirSync12 } from "node:fs";
20646
- import { join as join48, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
20647
- import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
20815
+ import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync6, readdirSync as readdirSync31, statSync as statSync43, mkdirSync as mkdirSync12 } from "node:fs";
20816
+ import { join as join49, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
20817
+ import { tmpdir as tmpdir4, homedir as homedir17 } from "node:os";
20648
20818
  import { createHash as createHash10 } from "node:crypto";
20649
20819
  function compareVersions(a, b) {
20650
20820
  const pa = String(a).split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
@@ -20668,7 +20838,7 @@ function newerOf(a, b) {
20668
20838
  function installedVersion() {
20669
20839
  try {
20670
20840
  const here = dirname19(fileURLToPath2(import.meta.url));
20671
- const raw = readFileSync37(join48(here, "..", "package.json"), "utf-8");
20841
+ const raw = readFileSync38(join49(here, "..", "package.json"), "utf-8");
20672
20842
  return JSON.parse(raw).version || null;
20673
20843
  } catch {
20674
20844
  return null;
@@ -20681,7 +20851,7 @@ function planAutoUpdate(base2, caps, ownVersion2, flag) {
20681
20851
  if (compareVersions(rel.version, ownVersion2) <= 0) return { update: false, reason: `already current (own ${ownVersion2} >= server ${rel.version})` };
20682
20852
  return { update: true, reason: `server ${rel.version} > own ${ownVersion2}`, url: tarballUrl(base2, rel.version), fallbackUrl: tarballUrl(base2), sha256: rel.sha256, from: ownVersion2, to: rel.version };
20683
20853
  }
20684
- function sweepStaleStaging(root = tmpdir3(), now = Date.now()) {
20854
+ function sweepStaleStaging(root = tmpdir4(), now = Date.now()) {
20685
20855
  const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
20686
20856
  const COLD_MS = 60 * 60 * 1e3;
20687
20857
  let removed = 0;
@@ -20693,13 +20863,13 @@ function sweepStaleStaging(root = tmpdir3(), now = Date.now()) {
20693
20863
  }
20694
20864
  for (const name of entries) {
20695
20865
  if (!MKDTEMP_SUFFIX.test(name)) continue;
20696
- const dir = join48(root, name);
20866
+ const dir = join49(root, name);
20697
20867
  try {
20698
- const st = statSync42(dir);
20868
+ const st = statSync43(dir);
20699
20869
  if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
20700
20870
  const inside = readdirSync31(dir);
20701
20871
  if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
20702
- rmSync5(dir, { recursive: true, force: true });
20872
+ rmSync6(dir, { recursive: true, force: true });
20703
20873
  removed++;
20704
20874
  } catch {
20705
20875
  }
@@ -20722,14 +20892,14 @@ async function executeAutoUpdate(plan, deps) {
20722
20892
  }
20723
20893
  const got = createHash10("sha256").update(buf).digest("hex");
20724
20894
  if (got !== plan.sha256) return { updated: false, reason: `checksum mismatch (expected ${plan.sha256.slice(0, 12)}\u2026, got ${got.slice(0, 12)}\u2026) \u2014 NOT installing` };
20725
- const dir = mkdtempSync3(join48(tmpdir3(), "cr-update-"));
20895
+ const dir = mkdtempSync3(join49(tmpdir4(), "cr-update-"));
20726
20896
  const discardStaging = () => {
20727
20897
  try {
20728
- rmSync5(dir, { recursive: true, force: true });
20898
+ rmSync6(dir, { recursive: true, force: true });
20729
20899
  } catch {
20730
20900
  }
20731
20901
  };
20732
- const tgz = join48(dir, "chat-recall.tgz");
20902
+ const tgz = join49(dir, "chat-recall.tgz");
20733
20903
  writeFileSync9(tgz, buf);
20734
20904
  if (deps.stopping?.()) {
20735
20905
  discardStaging();
@@ -20809,7 +20979,7 @@ function realInstall(tgz) {
20809
20979
  try {
20810
20980
  npmInstall(npm, ["install", "-g", tgz]);
20811
20981
  } catch {
20812
- npmInstall(npm, ["install", "-g", "--prefix", join48(homedir17(), ".local"), tgz]);
20982
+ npmInstall(npm, ["install", "-g", "--prefix", join49(homedir17(), ".local"), tgz]);
20813
20983
  }
20814
20984
  }
20815
20985
  function acquireUpdateLock() {
@@ -20826,12 +20996,12 @@ function realRestart(platform2) {
20826
20996
  if (c) execSync4(c.cmd, { stdio: "ignore", ...c.shell ? { shell: c.shell } : {} });
20827
20997
  }
20828
20998
  function updateStatePath() {
20829
- const dir = process.env.CHAT_RECALL_DATA_DIR || join48(homedir17(), ".chat-recall");
20830
- return join48(dir, "auto-update-state.json");
20999
+ const dir = process.env.CHAT_RECALL_DATA_DIR || join49(homedir17(), ".chat-recall");
21000
+ return join49(dir, "auto-update-state.json");
20831
21001
  }
20832
21002
  function readUpdateState() {
20833
21003
  try {
20834
- return JSON.parse(readFileSync37(updateStatePath(), "utf8"));
21004
+ return JSON.parse(readFileSync38(updateStatePath(), "utf8"));
20835
21005
  } catch {
20836
21006
  return {};
20837
21007
  }
@@ -20972,9 +21142,9 @@ __export(sync_client_exports, {
20972
21142
  syncIncremental: () => syncIncremental,
20973
21143
  syncSessions: () => syncSessions
20974
21144
  });
20975
- import { join as join49, dirname as dirname20, basename as basename22 } from "node:path";
20976
- import { readFileSync as readFileSync38, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync4, rmSync as rmSync6, chmodSync as chmodSync6, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync43 } from "node:fs";
20977
- import { tmpdir as tmpdir4, hostname as hostname2 } from "node:os";
21145
+ import { join as join50, dirname as dirname20, basename as basename22 } from "node:path";
21146
+ import { readFileSync as readFileSync39, writeFileSync as writeFileSync10, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync4, rmSync as rmSync7, chmodSync as chmodSync6, unlinkSync as unlinkSync3, readdirSync as readdirSync32, statSync as statSync44 } from "node:fs";
21147
+ import { tmpdir as tmpdir5, hostname as hostname2 } from "node:os";
20978
21148
  import { createHash as createHash11 } from "node:crypto";
20979
21149
  import { fileURLToPath as fileURLToPath3 } from "node:url";
20980
21150
  import { gzipSync as gzipSync3 } from "zlib";
@@ -21028,7 +21198,7 @@ function loadAllCredentials() {
21028
21198
  const fromEnv = envCredentials();
21029
21199
  if (fromEnv) return [{ serverUrl: fromEnv.serverUrl, token: fromEnv.token }];
21030
21200
  try {
21031
- const parsed = JSON.parse(readFileSync38(credPath(), "utf-8"));
21201
+ const parsed = JSON.parse(readFileSync39(credPath(), "utf-8"));
21032
21202
  if (Array.isArray(parsed?.targets)) return parsed.targets.filter((t) => t?.serverUrl).map((t) => ({ serverUrl: t.serverUrl, token: t.token || "" }));
21033
21203
  if (parsed?.serverUrl) return [{ serverUrl: parsed.serverUrl, token: parsed.token || "" }];
21034
21204
  } catch {
@@ -21090,11 +21260,11 @@ function _resetTenantSecurityConfigCache() {
21090
21260
  _securityConfigCache.clear();
21091
21261
  }
21092
21262
  function syncConfigCachePath() {
21093
- return join49(getDataDir(), "sync-config-cache.json");
21263
+ return join50(getDataDir(), "sync-config-cache.json");
21094
21264
  }
21095
21265
  function readPersistedSyncConfigs() {
21096
21266
  try {
21097
- return JSON.parse(readFileSync38(syncConfigCachePath(), "utf-8"));
21267
+ return JSON.parse(readFileSync39(syncConfigCachePath(), "utf-8"));
21098
21268
  } catch {
21099
21269
  return {};
21100
21270
  }
@@ -21372,7 +21542,7 @@ async function reconcileFields(opts = {}) {
21372
21542
  }
21373
21543
  return { sessions: fullRefs().length, scanned, pushed, absent, perTarget };
21374
21544
  }
21375
- function reapStaleScanDirs(dir = tmpdir4(), maxAgeMs = 6 * 36e5, now = Date.now()) {
21545
+ function reapStaleScanDirs(dir = tmpdir5(), maxAgeMs = 6 * 36e5, now = Date.now()) {
21376
21546
  let reaped = 0;
21377
21547
  let names;
21378
21548
  try {
@@ -21382,10 +21552,10 @@ function reapStaleScanDirs(dir = tmpdir4(), maxAgeMs = 6 * 36e5, now = Date.now(
21382
21552
  }
21383
21553
  for (const name of names) {
21384
21554
  if (!name.startsWith("cr-batchscan-")) continue;
21385
- const p = join49(dir, name);
21555
+ const p = join50(dir, name);
21386
21556
  try {
21387
- if (now - statSync43(p).mtimeMs < maxAgeMs) continue;
21388
- rmSync6(p, { recursive: true, force: true });
21557
+ if (now - statSync44(p).mtimeMs < maxAgeMs) continue;
21558
+ rmSync7(p, { recursive: true, force: true });
21389
21559
  reaped++;
21390
21560
  } catch {
21391
21561
  }
@@ -21399,7 +21569,7 @@ function batchScanExternal(items, opts = {}) {
21399
21569
  let scanned = 0, slices = 0, scanMs = 0;
21400
21570
  if (items.length === 0) return { findings, scanned, slices, scanMs };
21401
21571
  reapStaleScanDirs();
21402
- let scanDir = mkdtempSync4(join49(tmpdir4(), "cr-batchscan-"));
21572
+ let scanDir = mkdtempSync4(join50(tmpdir5(), "cr-batchscan-"));
21403
21573
  let safeToId = /* @__PURE__ */ new Map();
21404
21574
  let bytesInDir = 0;
21405
21575
  const flushSlice = () => {
@@ -21417,7 +21587,7 @@ function batchScanExternal(items, opts = {}) {
21417
21587
  slices++;
21418
21588
  }
21419
21589
  try {
21420
- rmSync6(scanDir, { recursive: true, force: true });
21590
+ rmSync7(scanDir, { recursive: true, force: true });
21421
21591
  } catch {
21422
21592
  }
21423
21593
  safeToId = /* @__PURE__ */ new Map();
@@ -21430,19 +21600,19 @@ function batchScanExternal(items, opts = {}) {
21430
21600
  if (raw === null) continue;
21431
21601
  const safe = item.id.replace(/[^A-Za-z0-9_-]/g, "");
21432
21602
  safeToId.set(safe, item.id);
21433
- writeFileSync10(join49(scanDir, `${safe}.txt`), raw);
21603
+ writeFileSync10(join50(scanDir, `${safe}.txt`), raw);
21434
21604
  bytesInDir += Buffer.byteLength(raw);
21435
21605
  } catch {
21436
21606
  }
21437
21607
  if (bytesInDir >= maxBytes) {
21438
21608
  flushSlice();
21439
- scanDir = mkdtempSync4(join49(tmpdir4(), "cr-batchscan-"));
21609
+ scanDir = mkdtempSync4(join50(tmpdir5(), "cr-batchscan-"));
21440
21610
  }
21441
21611
  }
21442
21612
  flushSlice();
21443
21613
  } finally {
21444
21614
  try {
21445
- rmSync6(scanDir, { recursive: true, force: true });
21615
+ rmSync7(scanDir, { recursive: true, force: true });
21446
21616
  } catch {
21447
21617
  }
21448
21618
  }
@@ -21686,12 +21856,13 @@ async function syncToTargetInScope(cred, opts = {}) {
21686
21856
  markSynced(base2, rows.map((r) => {
21687
21857
  const off = typeof r.from_offset === "number" ? r.from_offset : 0;
21688
21858
  const hash = shippedHash.get(r.session_id);
21689
- return off > 0 ? { id: r.session_id, mtime: r.mtime, offset: off, size: off, hash, acked: true } : { id: r.session_id, mtime: r.mtime, hash };
21859
+ return off > 0 ? { id: r.session_id, mtime: r.mtime, offset: off, size: off, hash, acked: true, full: true } : { id: r.session_id, mtime: r.mtime, hash };
21690
21860
  }));
21691
21861
  } catch {
21692
21862
  }
21693
21863
  });
21694
21864
  const appendResults = { uploaded: 0, fullResyncNeeded: /* @__PURE__ */ new Set() };
21865
+ const pendingArchives = [];
21695
21866
  const postAppend = async (convs) => {
21696
21867
  const headers = { "content-type": "application/json" };
21697
21868
  if (cred.token) headers.authorization = `Bearer ${cred.token}`;
@@ -21726,8 +21897,10 @@ async function syncToTargetInScope(cred, opts = {}) {
21726
21897
  offset: c.from_offset,
21727
21898
  size: c.from_offset,
21728
21899
  // size = new offset (file grew to here)
21729
- acked: true
21900
+ acked: true,
21730
21901
  // server merged this tail
21902
+ chunkFinal: c.final === true
21903
+ // completes a chunked full sync
21731
21904
  })));
21732
21905
  } catch {
21733
21906
  }
@@ -21759,9 +21932,9 @@ async function syncToTargetInScope(cred, opts = {}) {
21759
21932
  const mtime = conv.mtime;
21760
21933
  try {
21761
21934
  if (staged || !guarded) {
21762
- markSynced(base2, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
21935
+ markSynced(base2, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true, chunkedHead: true }]);
21763
21936
  } else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
21764
- markSynced(base2, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
21937
+ markSynced(base2, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true, chunkedHead: true }]);
21765
21938
  } else {
21766
21939
  markSynced(base2, [{ id, mtime }]);
21767
21940
  }
@@ -21970,6 +22143,14 @@ async function syncToTargetInScope(cred, opts = {}) {
21970
22143
  if (built.srcHash) shippedHash.set(ref.prefixedId, built.srcHash);
21971
22144
  if (built.conv.chunked) await postChunkedHead(built.conv, sessionFileBytes(ref));
21972
22145
  else await convBatch.add(built.conv);
22146
+ if (built.archive) {
22147
+ pendingArchives.push({
22148
+ id: ref.prefixedId,
22149
+ ...built.archive,
22150
+ projectId: built.conv.project_id,
22151
+ projectPath: built.conv.project_path
22152
+ });
22153
+ }
21973
22154
  if (built.findings.length > 0) await findingsBatch.add(built.findings);
21974
22155
  if (upload.sessionMeta) {
21975
22156
  await accumulateKg(extractEntities(built.kgText, { projectPath: built.projectPath, sourceType: "session", sessionId: ref.prefixedId, validFrom: ref.mtime ? new Date(ref.mtime).toISOString().slice(0, 10) : void 0 }), ref.prefixedId);
@@ -22122,6 +22303,30 @@ async function syncToTargetInScope(cred, opts = {}) {
22122
22303
  await kgTripleBatch.drain();
22123
22304
  await findingsBatch.drain();
22124
22305
  await drainInflight();
22306
+ let archivesUnsupported = false;
22307
+ for (const a of pendingArchives) {
22308
+ try {
22309
+ if (!archivesUnsupported) {
22310
+ const r = await uploadArchiveFile(base2, cred.token, {
22311
+ sessionId: a.id,
22312
+ tool: a.tool,
22313
+ mtime: a.mtime,
22314
+ projectId: a.projectId,
22315
+ projectPath: a.projectPath,
22316
+ file: a.file
22317
+ });
22318
+ if (r === "unsupported") archivesUnsupported = true;
22319
+ }
22320
+ } catch (err) {
22321
+ console.error(`[sync] raw archive for ${a.id} not uploaded (tried again when the session is next quiet): ${err instanceof Error ? err.message : err}`);
22322
+ try {
22323
+ forgetFullCoverage(base2, a.id);
22324
+ } catch {
22325
+ }
22326
+ } finally {
22327
+ rmSync7(a.file.path, { force: true });
22328
+ }
22329
+ }
22125
22330
  {
22126
22331
  const sentHashes = loadInventoryHashes(base2);
22127
22332
  const entries = [];
@@ -22227,6 +22432,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
22227
22432
  let includeRaw = opts.includeRaw !== false;
22228
22433
  let includeMeta = opts.includeMeta !== false;
22229
22434
  let srcHash;
22435
+ const wantRaw = opts.includeRaw !== false;
22230
22436
  const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
22231
22437
  let headOffsetEnd = 0;
22232
22438
  let oversizedFindings = null;
@@ -22395,7 +22601,7 @@ async function buildConversationSync(ref, mtime, opts = {}) {
22395
22601
  } else if (opts.scanSecrets) {
22396
22602
  let tmp = null;
22397
22603
  try {
22398
- tmp = join49(tmpdir4(), `cr-scan-${ref.prefixedId.replace(/[^A-Za-z0-9_-]/g, "")}-${mtime}.txt`);
22604
+ tmp = join50(tmpdir5(), `cr-scan-${ref.prefixedId.replace(/[^A-Za-z0-9_-]/g, "")}-${mtime}.txt`);
22399
22605
  writeFileSync10(tmp, rawText);
22400
22606
  for (const f of scanFileForSecrets(tmp, { verifyOnly: opts.verifySecrets, tenantRules: opts.tenantRules })) {
22401
22607
  const verifiedAt = f.verified ? (/* @__PURE__ */ new Date()).toISOString() : null;
@@ -22447,6 +22653,25 @@ async function buildConversationSync(ref, mtime, opts = {}) {
22447
22653
  }
22448
22654
  const envTexts = envelope.messages.filter((m) => m.content?.trim());
22449
22655
  const redactedText = envTexts.map((m) => m.content).join("\n");
22656
+ let archive;
22657
+ if (wantRaw && !raw_b64) {
22658
+ try {
22659
+ const redact = (t) => redactSecrets(t, { force: true, count });
22660
+ let source = null;
22661
+ let archiveMtime = Math.floor(ref.mtime) || 0;
22662
+ let archiveTool = ref.toolId;
22663
+ if (container) {
22664
+ source = { kind: "memory", files: container.files };
22665
+ archiveMtime = container.mtime;
22666
+ archiveTool = container.tool;
22667
+ } else if (oversized) {
22668
+ source = diskArchiveSource(ref);
22669
+ }
22670
+ if (source) archive = { file: await writeArchiveFile(source, { tool: archiveTool, mtime: archiveMtime }, redact), tool: archiveTool, mtime: archiveMtime };
22671
+ } catch (err) {
22672
+ console.error(`[sync] raw archive for ${ref.prefixedId} not written: ${err instanceof Error ? err.message : err}`);
22673
+ }
22674
+ }
22450
22675
  return {
22451
22676
  conv: {
22452
22677
  session_id: ref.prefixedId,
@@ -22478,7 +22703,17 @@ async function buildConversationSync(ref, mtime, opts = {}) {
22478
22703
  redactions: count.redactions,
22479
22704
  findings,
22480
22705
  scanMs,
22481
- srcHash
22706
+ srcHash,
22707
+ archive
22708
+ };
22709
+ }
22710
+ function diskArchiveSource(ref) {
22711
+ if (ref.toolId !== "claude") return null;
22712
+ const groups = resolveSessionContentGroups(ref.rawId);
22713
+ if (groups.length === 0 || groups.some((g) => g.paths.length !== 1)) return null;
22714
+ return {
22715
+ kind: "disk",
22716
+ files: groups.map((g) => ({ name: g.name === "main" ? `${ref.rawId}.jsonl` : g.name, path: g.paths[0] }))
22482
22717
  };
22483
22718
  }
22484
22719
  function parseTailMessages(tool, text) {
@@ -22634,14 +22869,15 @@ var init_sync_client = __esm({
22634
22869
  init_telemetry();
22635
22870
  init_collector_health();
22636
22871
  init_live_session_scan();
22872
+ init_raw_archive_parts();
22637
22873
  init_backends();
22638
22874
  ownVersion = "0.0.0";
22639
22875
  try {
22640
22876
  const dir = dirname20(fileURLToPath3(import.meta.url));
22641
- ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
22877
+ ownVersion = JSON.parse(readFileSync39(join50(dir, "../package.json"), "utf-8")).version || ownVersion;
22642
22878
  } catch {
22643
22879
  try {
22644
- if (true) ownVersion = "0.7.16";
22880
+ if (true) ownVersion = "0.7.18";
22645
22881
  } catch {
22646
22882
  }
22647
22883
  }
@@ -22653,7 +22889,7 @@ var init_sync_client = __esm({
22653
22889
  ]);
22654
22890
  walkProgressForUpload = null;
22655
22891
  lastServerPackSpec = null;
22656
- credPath = () => join49(getDataDir(), "credentials.json");
22892
+ credPath = () => join50(getDataDir(), "credentials.json");
22657
22893
  _securityConfigCache = /* @__PURE__ */ new Map();
22658
22894
  SECURITY_CONFIG_TTL_MS = 5 * 60 * 1e3;
22659
22895
  _syncConfigCache = /* @__PURE__ */ new Map();
@@ -22682,7 +22918,7 @@ __export(client_events_exports, {
22682
22918
  isTelemetryEnabled: () => isTelemetryEnabled,
22683
22919
  reportClientEvent: () => reportClientEvent2
22684
22920
  });
22685
- import { readFileSync as readFileSync39 } from "node:fs";
22921
+ import { readFileSync as readFileSync40 } from "node:fs";
22686
22922
  function scrub(msg) {
22687
22923
  if (!msg) return "";
22688
22924
  const noHome = msg.replace(/\/(?:home|Users)\/[^/\s]+/g, "~");
@@ -22735,7 +22971,7 @@ var init_client_events = __esm({
22735
22971
  init_secret_redactor();
22736
22972
  CLI_VERSION2 = "0.0.0";
22737
22973
  try {
22738
- CLI_VERSION2 = JSON.parse(readFileSync39(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
22974
+ CLI_VERSION2 = JSON.parse(readFileSync40(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION2;
22739
22975
  } catch {
22740
22976
  }
22741
22977
  }
@@ -22749,15 +22985,15 @@ __export(update_notice_exports, {
22749
22985
  refreshUpdateCheck: () => refreshUpdateCheck,
22750
22986
  updateNotice: () => updateNotice2
22751
22987
  });
22752
- import { readFileSync as readFileSync40, writeFileSync as writeFileSync11 } from "node:fs";
22753
- import { join as join50 } from "node:path";
22988
+ import { readFileSync as readFileSync41, writeFileSync as writeFileSync11 } from "node:fs";
22989
+ import { join as join51 } from "node:path";
22754
22990
  function newestKnown(c) {
22755
22991
  const npm = c.npmVersion ?? "0.0.0";
22756
22992
  return compareVersions(npm, c.serverVersion) > 0 ? { version: npm, source: "npm" } : { version: c.serverVersion, source: "server" };
22757
22993
  }
22758
22994
  function readCheck() {
22759
22995
  try {
22760
- const c = JSON.parse(readFileSync40(checkPath(), "utf-8"));
22996
+ const c = JSON.parse(readFileSync41(checkPath(), "utf-8"));
22761
22997
  return c && typeof c.serverVersion === "string" ? c : null;
22762
22998
  } catch {
22763
22999
  return null;
@@ -22851,7 +23087,7 @@ var init_update_notice = __esm({
22851
23087
  init_http();
22852
23088
  CHECK_TTL_MS = 6 * 3600 * 1e3;
22853
23089
  NPM_LATEST_URL = "https://registry.npmjs.org/chat-recall";
22854
- checkPath = () => join50(getDataDir(), "update-check.json");
23090
+ checkPath = () => join51(getDataDir(), "update-check.json");
22855
23091
  printed = false;
22856
23092
  }
22857
23093
  });
@@ -22923,15 +23159,15 @@ __export(service_installer_exports, {
22923
23159
  uninstallService: () => uninstallService
22924
23160
  });
22925
23161
  import { execSync as execSync5 } from "child_process";
22926
- import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
23162
+ import { existsSync as existsSync46, mkdirSync as mkdirSync14, readFileSync as readFileSync42, rmSync as rmSync8, writeFileSync as writeFileSync12 } from "fs";
22927
23163
  import { homedir as homedir18 } from "os";
22928
- import { join as join51 } from "path";
23164
+ import { join as join52 } from "path";
22929
23165
  import { fileURLToPath as fileURLToPath4 } from "url";
22930
23166
  function resolveDaemonPaths() {
22931
23167
  const watchJs = fileURLToPath4(new URL("./watch.js", import.meta.url));
22932
23168
  const node = process.execPath;
22933
- const dataDir = join51(homedir18(), ".chat-recall");
22934
- const logFile = join51(dataDir, "watch.log");
23169
+ const dataDir = join52(homedir18(), ".chat-recall");
23170
+ const logFile = join52(dataDir, "watch.log");
22935
23171
  return { watchJs, node, logFile, dataDir };
22936
23172
  }
22937
23173
  function tryCmd(cmd) {
@@ -22958,12 +23194,12 @@ function refreshServiceDefinition() {
22958
23194
  if (process.platform !== "linux") return false;
22959
23195
  try {
22960
23196
  const { watchJs, node, logFile } = resolveDaemonPaths();
22961
- const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
23197
+ const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
22962
23198
  if (!existsSync46(unitPath)) return false;
22963
- const current = readFileSync41(unitPath, "utf8");
23199
+ const current = readFileSync42(unitPath, "utf8");
22964
23200
  if (!current.includes(watchJs)) return false;
22965
23201
  const wanted = renderSystemdUnit(watchJs, node, logFile);
22966
- if (readFileSync41(unitPath, "utf8") === wanted) return false;
23202
+ if (readFileSync42(unitPath, "utf8") === wanted) return false;
22967
23203
  writeFileSync12(unitPath, wanted);
22968
23204
  execSync5("systemctl --user daemon-reload", { stdio: "ignore" });
22969
23205
  return true;
@@ -22989,8 +23225,8 @@ function installService() {
22989
23225
  );
22990
23226
  }
22991
23227
  function installSystemd(watchJs, node, logFile) {
22992
- const unitDir = join51(homedir18(), ".config", "systemd", "user");
22993
- const unitPath = join51(unitDir, "chat-recall-watch.service");
23228
+ const unitDir = join52(homedir18(), ".config", "systemd", "user");
23229
+ const unitPath = join52(unitDir, "chat-recall-watch.service");
22994
23230
  const unit = renderSystemdUnit(watchJs, node, logFile);
22995
23231
  mkdirSync14(unitDir, { recursive: true });
22996
23232
  writeFileSync12(unitPath, unit);
@@ -23069,8 +23305,8 @@ function renderSystemdUnit(watchJs, node, logFile) {
23069
23305
  }
23070
23306
  function installLaunchd(watchJs, node, logFile) {
23071
23307
  const label = "com.chat-recall.watch";
23072
- const plistDir = join51(homedir18(), "Library", "LaunchAgents");
23073
- const plistPath = join51(plistDir, `${label}.plist`);
23308
+ const plistDir = join52(homedir18(), "Library", "LaunchAgents");
23309
+ const plistPath = join52(plistDir, `${label}.plist`);
23074
23310
  const plist = renderLaunchdPlist(watchJs, node, logFile, label);
23075
23311
  mkdirSync14(plistDir, { recursive: true });
23076
23312
  writeFileSync12(plistPath, plist);
@@ -23126,9 +23362,9 @@ function uninstallService() {
23126
23362
  removed = true;
23127
23363
  } catch {
23128
23364
  }
23129
- const unitPath = join51(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
23365
+ const unitPath = join52(homedir18(), ".config", "systemd", "user", "chat-recall-watch.service");
23130
23366
  if (existsSync46(unitPath)) {
23131
- rmSync7(unitPath, { force: true });
23367
+ rmSync8(unitPath, { force: true });
23132
23368
  removed = true;
23133
23369
  }
23134
23370
  try {
@@ -23138,14 +23374,14 @@ function uninstallService() {
23138
23374
  return removed;
23139
23375
  }
23140
23376
  if (process.platform === "darwin") {
23141
- const plistPath = join51(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
23377
+ const plistPath = join52(homedir18(), "Library", "LaunchAgents", "com.chat-recall.watch.plist");
23142
23378
  try {
23143
23379
  execSync5(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
23144
23380
  } catch {
23145
23381
  }
23146
23382
  let removed = false;
23147
23383
  if (existsSync46(plistPath)) {
23148
- rmSync7(plistPath, { force: true });
23384
+ rmSync8(plistPath, { force: true });
23149
23385
  removed = true;
23150
23386
  }
23151
23387
  return removed;
@@ -23191,15 +23427,15 @@ __export(install_skills_exports, {
23191
23427
  skillsSourceDir: () => skillsSourceDir,
23192
23428
  uninstallSkills: () => uninstallSkills
23193
23429
  });
23194
- import { readdirSync as readdirSync33, existsSync as existsSync47, mkdirSync as mkdirSync15, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync42, statSync as statSync44, realpathSync as realpathSync4 } from "fs";
23430
+ import { readdirSync as readdirSync33, existsSync as existsSync47, mkdirSync as mkdirSync15, rmSync as rmSync9, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync43, statSync as statSync45, realpathSync as realpathSync4 } from "fs";
23195
23431
  import { createHash as createHash12 } from "crypto";
23196
- import { join as join52, basename as basename23 } from "path";
23432
+ import { join as join53, basename as basename23 } from "path";
23197
23433
  function skillsSourceDir() {
23198
- return join52(import.meta.dirname, "..", "skills");
23434
+ return join53(import.meta.dirname, "..", "skills");
23199
23435
  }
23200
23436
  function version() {
23201
23437
  try {
23202
- return JSON.parse(readFileSync42(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
23438
+ return JSON.parse(readFileSync43(new URL("../package.json", import.meta.url), "utf-8")).version || "0.0.0";
23203
23439
  } catch {
23204
23440
  return "0.0.0";
23205
23441
  }
@@ -23208,9 +23444,9 @@ function skillsFingerprint() {
23208
23444
  const src = skillsSourceDir();
23209
23445
  const h = createHash12("sha256");
23210
23446
  for (const name of bundledSkillNames()) {
23211
- for (const rel of filesUnder(join52(src, name)).sort()) {
23447
+ for (const rel of filesUnder(join53(src, name)).sort()) {
23212
23448
  h.update(name + "/" + rel);
23213
- h.update(readFileSync42(join52(src, name, rel)));
23449
+ h.update(readFileSync43(join53(src, name, rel)));
23214
23450
  }
23215
23451
  }
23216
23452
  return h.digest("hex").slice(0, 12);
@@ -23220,7 +23456,7 @@ function filesUnder(dir, prefix = "") {
23220
23456
  for (const e of readdirSync33(dir, { withFileTypes: true })) {
23221
23457
  if (e.name === MARKER) continue;
23222
23458
  const rel = prefix ? `${prefix}/${e.name}` : e.name;
23223
- if (e.isDirectory()) out.push(...filesUnder(join52(dir, e.name), rel));
23459
+ if (e.isDirectory()) out.push(...filesUnder(join53(dir, e.name), rel));
23224
23460
  else out.push(rel);
23225
23461
  }
23226
23462
  return out;
@@ -23231,7 +23467,7 @@ function skillStamp() {
23231
23467
  function bundledSkillNames() {
23232
23468
  const src = skillsSourceDir();
23233
23469
  if (!existsSync47(src)) return [];
23234
- return readdirSync33(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join52(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
23470
+ return readdirSync33(src, { withFileTypes: true }).filter((e) => e.isDirectory() && existsSync47(join53(src, e.name, "SKILL.md"))).map((e) => e.name).sort();
23235
23471
  }
23236
23472
  function realPathOrSelf(p) {
23237
23473
  try {
@@ -23244,7 +23480,7 @@ function claudeSkillTargets() {
23244
23480
  const targets = [];
23245
23481
  const seen = /* @__PURE__ */ new Set();
23246
23482
  for (const home of claudeHomeDirs()) {
23247
- const dir = join52(home, "skills");
23483
+ const dir = join53(home, "skills");
23248
23484
  const key2 = realPathOrSelf(dir);
23249
23485
  if (seen.has(key2)) continue;
23250
23486
  seen.add(key2);
@@ -23255,7 +23491,7 @@ function claudeSkillTargets() {
23255
23491
  dir,
23256
23492
  // Judge each profile on ITS OWN transcripts, not the primary's: a home
23257
23493
  // with no projects/ is a directory someone made, not a Claude install.
23258
- available: existsSync47(join52(home, "projects"))
23494
+ available: existsSync47(join53(home, "projects"))
23259
23495
  });
23260
23496
  }
23261
23497
  if (targets.length === 0) {
@@ -23270,16 +23506,16 @@ function skillTargets() {
23270
23506
  // both produces "skill conflict" duplicate warnings. Every tool we support
23271
23507
  // has its own dir, so per-tool coverage is complete.
23272
23508
  ...claudeSkillTargets(),
23273
- { id: "agy", label: "Antigravity", dir: join52(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
23509
+ { id: "agy", label: "Antigravity", dir: join53(agyBackend.homeDir(), "skills"), available: agyBackend.isAvailable() },
23274
23510
  { id: "codex", label: "Codex", dir: codexBackend.skillsDir(), available: codexBackend.isAvailable() },
23275
23511
  { id: "opencode", label: "OpenCode", dir: opencodeBackend.skillsDir(), available: opencodeBackend.isAvailable() },
23276
23512
  // Cursor DOES have its own dir (~/.cursor/skills). `skills-cursor` next to
23277
23513
  // it is Cursor's bundled set — read-only, never an install target.
23278
- { id: "cursor", label: "Cursor", dir: join52(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
23514
+ { id: "cursor", label: "Cursor", dir: join53(cursorHomeDir(), "skills"), available: cursorBackend.isAvailable() }
23279
23515
  ];
23280
23516
  }
23281
23517
  function isManaged(dir) {
23282
- return existsSync47(join52(dir, MARKER));
23518
+ return existsSync47(join53(dir, MARKER));
23283
23519
  }
23284
23520
  function installSkills(opts = {}) {
23285
23521
  const onlyAvailable = opts.onlyAvailable ?? true;
@@ -23296,15 +23532,15 @@ function installSkills(opts = {}) {
23296
23532
  continue;
23297
23533
  }
23298
23534
  for (const name of names) {
23299
- const dest = join52(t.dir, name);
23535
+ const dest = join53(t.dir, name);
23300
23536
  if (existsSync47(dest) && !isManaged(dest)) {
23301
23537
  skippedUserOwned.push(name);
23302
23538
  continue;
23303
23539
  }
23304
23540
  mkdirSync15(t.dir, { recursive: true });
23305
- rmSync8(dest, { recursive: true, force: true });
23306
- cpSync(join52(src, name), dest, { recursive: true });
23307
- writeFileSync13(join52(dest, MARKER), `${stamp}
23541
+ rmSync9(dest, { recursive: true, force: true });
23542
+ cpSync(join53(src, name), dest, { recursive: true });
23543
+ writeFileSync13(join53(dest, MARKER), `${stamp}
23308
23544
  `);
23309
23545
  installed.push(name);
23310
23546
  }
@@ -23323,9 +23559,9 @@ function uninstallSkills() {
23323
23559
  }
23324
23560
  for (const entry of readdirSync33(t.dir, { withFileTypes: true })) {
23325
23561
  if (!entry.isDirectory()) continue;
23326
- const dir = join52(t.dir, entry.name);
23562
+ const dir = join53(t.dir, entry.name);
23327
23563
  if (isManaged(dir) && (names.has(entry.name) || entry.name.startsWith("chat-recall"))) {
23328
- rmSync8(dir, { recursive: true, force: true });
23564
+ rmSync9(dir, { recursive: true, force: true });
23329
23565
  removed.push(entry.name);
23330
23566
  }
23331
23567
  }
@@ -23340,10 +23576,10 @@ function skillsNeedRefresh() {
23340
23576
  for (const t of skillTargets()) {
23341
23577
  if (!t.available) continue;
23342
23578
  for (const name of names) {
23343
- const marker = join52(t.dir, name, MARKER);
23579
+ const marker = join53(t.dir, name, MARKER);
23344
23580
  if (!existsSync47(marker)) return true;
23345
23581
  try {
23346
- if (readFileSync42(marker, "utf-8").trim() !== cur) return true;
23582
+ if (readFileSync43(marker, "utf-8").trim() !== cur) return true;
23347
23583
  } catch {
23348
23584
  return true;
23349
23585
  }
@@ -23358,11 +23594,11 @@ function skillStatus() {
23358
23594
  let installed = 0;
23359
23595
  let ver = null;
23360
23596
  for (const name of names) {
23361
- const marker = join52(t.dir, name, MARKER);
23597
+ const marker = join53(t.dir, name, MARKER);
23362
23598
  if (existsSync47(marker)) {
23363
23599
  installed++;
23364
23600
  try {
23365
- ver = readFileSync42(marker, "utf-8").trim();
23601
+ ver = readFileSync43(marker, "utf-8").trim();
23366
23602
  } catch {
23367
23603
  }
23368
23604
  }
@@ -29457,9 +29693,9 @@ function openBrowser(url) {
29457
29693
  // src/mcp.ts
29458
29694
  init_client_events();
29459
29695
  init_update_notice();
29460
- import { readFileSync as readFileSync43 } from "node:fs";
29696
+ import { readFileSync as readFileSync44 } from "node:fs";
29461
29697
  setServerVersion(JSON.parse(
29462
- readFileSync43(new URL("../package.json", import.meta.url), "utf-8")
29698
+ readFileSync44(new URL("../package.json", import.meta.url), "utf-8")
29463
29699
  ).version);
29464
29700
  setEventReporter(reportClientEvent2);
29465
29701
  setIndexRunner(runIndexChild2);
@@ -29576,9 +29812,9 @@ var SYNC_TICK_MS = 3 * 6e4;
29576
29812
  function startBackgroundSync() {
29577
29813
  const tick = async (scope) => {
29578
29814
  try {
29579
- const { installedVersion: installedVersion2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
29815
+ const { installedVersion: installedVersion3 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
29580
29816
  const { installedIsNewer: installedIsNewer2, runInstalledSync: runInstalledSync2 } = await Promise.resolve().then(() => (init_sync_delegate(), sync_delegate_exports));
29581
- const installed = installedVersion2();
29817
+ const installed = installedVersion3();
29582
29818
  if (installedIsNewer2(RELAY_VERSION, installed)) {
29583
29819
  const code = await runInstalledSync2(fileURLToPath5(new URL("./cli.js", import.meta.url)));
29584
29820
  if (code !== 0) console.error(`[mcp] sync by the installed CLI ${installed} ended with ${code ?? "no exit code"}`);
@@ -29632,7 +29868,7 @@ function installCrashGuards() {
29632
29868
  reportClientEvent2("mcp_crash", { message: err.stack || err.message });
29633
29869
  });
29634
29870
  }
29635
- var RELAY_VERSION = true ? "0.7.16" : "0.0.0";
29871
+ var RELAY_VERSION = true ? "0.7.18" : "0.0.0";
29636
29872
  function daemonIdleSecs() {
29637
29873
  const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
29638
29874
  if (raw === void 0) return 900;
@@ -29728,8 +29964,25 @@ async function runDaemon() {
29728
29964
  };
29729
29965
  process.on("SIGTERM", shutdown);
29730
29966
  process.on("SIGINT", shutdown);
29967
+ const upgradeTimer = setInterval(() => {
29968
+ const installed = installedVersion2();
29969
+ if (installed && RELAY_VERSION !== "0.0.0" && installed !== RELAY_VERSION) {
29970
+ console.error(`[mcp] v${installed} is installed; daemon v${RELAY_VERSION} is leaving`);
29971
+ clearInterval(upgradeTimer);
29972
+ shutdown();
29973
+ }
29974
+ }, 3e4);
29975
+ upgradeTimer.unref();
29731
29976
  return true;
29732
29977
  }
29978
+ function installedVersion2() {
29979
+ try {
29980
+ const pkg = JSON.parse(readFileSync44(new URL("../package.json", import.meta.url), "utf-8"));
29981
+ return typeof pkg.version === "string" ? pkg.version : null;
29982
+ } catch {
29983
+ return null;
29984
+ }
29985
+ }
29733
29986
  async function main() {
29734
29987
  installCrashGuards();
29735
29988
  if (process.argv.includes("--daemon")) {