chat-recall 0.5.30 → 0.5.31

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
@@ -11224,19 +11224,6 @@ var init_codex2 = __esm({
11224
11224
  // ../engine/dist/core/backends/agy.js
11225
11225
  import { existsSync as existsSync13, readdirSync as readdirSync9, readFileSync as readFileSync8, statSync as statSync11 } from "fs";
11226
11226
  import { basename as basename11, dirname as dirname11, join as join14 } from "path";
11227
- function getFallbackProjectPath(homeDir) {
11228
- try {
11229
- const settingsPath2 = join14(homeDir, "settings.json");
11230
- if (existsSync13(settingsPath2)) {
11231
- const json = JSON.parse(readFileSync8(settingsPath2, "utf-8"));
11232
- if (Array.isArray(json?.trustedWorkspaces) && json.trustedWorkspaces[0]) {
11233
- return json.trustedWorkspaces[0];
11234
- }
11235
- }
11236
- } catch {
11237
- }
11238
- return "";
11239
- }
11240
11227
  function agyTranscriptPath(logsDir) {
11241
11228
  const full = join14(logsDir, "transcript_full.jsonl");
11242
11229
  if (existsSync13(full))
@@ -11249,8 +11236,8 @@ function agyTranscriptPath(logsDir) {
11249
11236
  function resolveAgyProject(logsDir, homeDir) {
11250
11237
  const deriveFrom = agyTranscriptPath(logsDir);
11251
11238
  if (!deriveFrom)
11252
- return getFallbackProjectPath(homeDir);
11253
- return extractProjectPathFromTranscript(deriveFrom, homeDir) || getFallbackProjectPath(homeDir);
11239
+ return "";
11240
+ return extractProjectPathFromTranscript(deriveFrom, homeDir);
11254
11241
  }
11255
11242
  function extractProjectPathFromTranscript(path, homeDir) {
11256
11243
  let content;
@@ -18804,7 +18791,7 @@ var init_extractor_version = __esm({
18804
18791
  command: 1
18805
18792
  };
18806
18793
  TOOL_EXTRACTOR_BUMP = {
18807
- agy: 2,
18794
+ agy: 3,
18808
18795
  claude: 1,
18809
18796
  codex: 1
18810
18797
  };
@@ -19233,7 +19220,7 @@ var init_http = __esm({
19233
19220
  CLI_VERSION = JSON.parse(readFileSync33(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
19234
19221
  } catch {
19235
19222
  try {
19236
- if (true) CLI_VERSION = "0.5.30";
19223
+ if (true) CLI_VERSION = "0.5.31";
19237
19224
  } catch {
19238
19225
  }
19239
19226
  }
@@ -19579,9 +19566,9 @@ function cooldownFor(failures) {
19579
19566
  function breakerState(serverUrl, now = Date.now()) {
19580
19567
  const s = state.get(key(serverUrl));
19581
19568
  if (!s || s.openUntil <= now) {
19582
- return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "" };
19569
+ return { open: false, retryInMs: 0, failures: s?.failures ?? 0, lastError: s?.lastError ?? "", tripped: false };
19583
19570
  }
19584
- return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError };
19571
+ return { open: true, retryInMs: s.openUntil - now, failures: s.failures, lastError: s.lastError, tripped: false };
19585
19572
  }
19586
19573
  function noteTargetSuccess(serverUrl) {
19587
19574
  state.delete(key(serverUrl));
@@ -19593,11 +19580,11 @@ function noteTargetFailure(serverUrl, error, now = Date.now()) {
19593
19580
  const lastError = error.slice(0, 200);
19594
19581
  if (failures < TRIP_AFTER_FAILURES) {
19595
19582
  state.set(k, { failures, openUntil: 0, lastError });
19596
- return { open: false, retryInMs: 0, failures, lastError };
19583
+ return { open: false, retryInMs: 0, failures, lastError, tripped: false };
19597
19584
  }
19598
19585
  const cooldown = cooldownFor(failures);
19599
19586
  state.set(k, { failures, openUntil: now + cooldown, lastError });
19600
- return { open: true, retryInMs: cooldown, failures, lastError };
19587
+ return { open: true, retryInMs: cooldown, failures, lastError, tripped: failures === TRIP_AFTER_FAILURES };
19601
19588
  }
19602
19589
  function breakerNotice(serverUrl, v) {
19603
19590
  if (!v.open) return null;
@@ -19724,6 +19711,14 @@ async function flush() {
19724
19711
  }
19725
19712
  }));
19726
19713
  }
19714
+ function stableMark(value) {
19715
+ try {
19716
+ const { createHash: createHash17 } = __require("node:crypto");
19717
+ return createHash17("sha256").update(value).digest("hex").slice(0, 12);
19718
+ } catch {
19719
+ return "";
19720
+ }
19721
+ }
19727
19722
  function hashHost() {
19728
19723
  try {
19729
19724
  const { createHash: createHash17 } = __require("node:crypto");
@@ -19741,7 +19736,7 @@ var init_telemetry = __esm({
19741
19736
  init_sync_client();
19742
19737
  cliVersion = () => {
19743
19738
  try {
19744
- return true ? "0.5.30" : "dev";
19739
+ return true ? "0.5.31" : "dev";
19745
19740
  } catch {
19746
19741
  return "dev";
19747
19742
  }
@@ -20168,7 +20163,17 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
20168
20163
  }
20169
20164
  const effectiveOwn = newerOf(ownVersion3, installedVersion());
20170
20165
  const plan = planAutoUpdate(base2, caps, effectiveOwn, process.env.CHAT_RECALL_AUTO_UPDATE);
20171
- if (!plan.update) return { updated: false, reason: plan.reason };
20166
+ if (!plan.update) {
20167
+ if (/disabled|no CLI release/.test(plan.reason) && !reportedStandingSkip.has(base2)) {
20168
+ reportedStandingSkip.add(base2);
20169
+ try {
20170
+ const { reportClientEvent: reportClientEvent2 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
20171
+ reportClientEvent2("auto_update_skipped", { message: `${plan.reason} (server offers ${caps.cli?.version ?? "?"}, this device runs ${effectiveOwn})` });
20172
+ } catch {
20173
+ }
20174
+ }
20175
+ return { updated: false, reason: plan.reason };
20176
+ }
20172
20177
  const result = await executeAutoUpdate(plan, {
20173
20178
  download: deps?.download ?? realDownload,
20174
20179
  install: deps?.install ?? realInstall,
@@ -20184,12 +20189,13 @@ async function runAutoUpdate(base2, authHeaders, ownVersion3, deps) {
20184
20189
  }
20185
20190
  return result;
20186
20191
  }
20187
- var tarballUrl;
20192
+ var tarballUrl, reportedStandingSkip;
20188
20193
  var init_auto_update = __esm({
20189
20194
  "src/auto-update.ts"() {
20190
20195
  "use strict";
20191
20196
  init_http();
20192
20197
  tarballUrl = (base2) => `${base2.replace(/\/+$/, "")}/install/chat-recall.tgz`;
20198
+ reportedStandingSkip = /* @__PURE__ */ new Set();
20193
20199
  }
20194
20200
  });
20195
20201
 
@@ -20200,17 +20206,32 @@ import { tmpdir as tmpdir3, hostname as hostname2 } from "node:os";
20200
20206
  import { createHash as createHash12 } from "node:crypto";
20201
20207
  import { fileURLToPath as fileURLToPath3 } from "node:url";
20202
20208
  import { gzipSync as gzipSync3 } from "zlib";
20203
- function classifyError(msg) {
20209
+ function classedError(errorClass, message) {
20210
+ const e = new Error(message);
20211
+ Object.defineProperty(e, ERROR_CLASS, { value: errorClass, enumerable: false });
20212
+ return e;
20213
+ }
20214
+ function taggedClass(err) {
20215
+ const v = err?.[ERROR_CLASS];
20216
+ return typeof v === "string" && v ? v : null;
20217
+ }
20218
+ function isStandingFailure(err) {
20219
+ const c = taggedClass(err);
20220
+ return c !== null && STANDING_FAILURE.has(c);
20221
+ }
20222
+ function classifyError(msg, err) {
20223
+ const tagged = taggedClass(err);
20224
+ if (tagged) return tagged;
20204
20225
  const m = msg.toLowerCase();
20205
20226
  if (m.includes("429")) return "rate_limited";
20206
20227
  if (m.includes("402")) return "payment_required";
20207
20228
  if (m.includes("401") || m.includes("403") || m.includes("token")) return "auth";
20208
- if (m.includes("timed out") || m.includes("etimedout")) return "timeout";
20229
+ if (m.includes("timed out") || m.includes("etimedout") || m.includes("aborted due to timeout")) return "timeout";
20209
20230
  if (m.includes("econnrefused")) return "refused";
20210
20231
  if (m.includes("enotfound") || m.includes("dns")) return "dns";
20211
20232
  if (m.includes("fetch failed") || m.includes("econnreset")) return "network";
20212
- if (m.includes("plain http") || m.includes("https")) return "insecure_transport";
20213
- if (/5\d\d/.test(m)) return "server_error";
20233
+ if (m.includes("over plain http") || m.includes("unsupported scheme")) return "insecure_transport";
20234
+ if (/\b5\d\d\b/.test(m)) return "server_error";
20214
20235
  return "other";
20215
20236
  }
20216
20237
  function loadAllCredentials() {
@@ -20397,17 +20418,23 @@ async function syncSessions(opts = {}) {
20397
20418
  let agg = null;
20398
20419
  const errors = [];
20399
20420
  const skippedTargets = [];
20421
+ const perTarget = {};
20400
20422
  for (const cred of targets) {
20401
20423
  const verdict = breakerState(cred.serverUrl);
20402
20424
  if (verdict.open) {
20403
20425
  const notice = breakerNotice(cred.serverUrl, verdict);
20404
20426
  if (notice) console.error(`[sync] ${notice}`);
20405
20427
  skippedTargets.push(cred.serverUrl);
20428
+ perTarget[cred.serverUrl] = {
20429
+ ok: false,
20430
+ error: notice ?? `in failure cooldown for ~${Math.max(1, Math.round(verdict.retryInMs / 6e4))}m`
20431
+ };
20406
20432
  continue;
20407
20433
  }
20408
20434
  try {
20409
20435
  const r = await syncToTarget(cred, opts);
20410
20436
  noteTargetSuccess(cred.serverUrl);
20437
+ perTarget[cred.serverUrl] = { ok: true };
20411
20438
  agg = agg ? {
20412
20439
  uploaded: agg.uploaded + r.uploaded,
20413
20440
  skipped: agg.skipped + r.skipped,
@@ -20424,14 +20451,20 @@ async function syncSessions(opts = {}) {
20424
20451
  } catch (e) {
20425
20452
  const msg = e instanceof Error ? e.message : String(e);
20426
20453
  errors.push(`${cred.serverUrl}: ${msg}`);
20454
+ perTarget[cred.serverUrl] = { ok: false, error: msg };
20427
20455
  const next = noteTargetFailure(cred.serverUrl, msg);
20428
20456
  const notice = breakerNotice(cred.serverUrl, next);
20429
20457
  if (notice) console.error(`[sync] ${notice}`);
20430
20458
  record({
20431
- kind: next.open ? "breaker_trip" : "target_failure",
20459
+ // `tripped`, not `open`: an open breaker re-opens on every attempt until
20460
+ // a success clears it, so `open` counted elapsed time. Every failure is
20461
+ // still reported — as `target_failure`, carrying the streak in
20462
+ // `failures` — so nothing becomes invisible, it just stops being counted
20463
+ // as a fresh incident.
20464
+ kind: next.tripped ? "breaker_trip" : "target_failure",
20432
20465
  failures: next.failures,
20433
20466
  retryInMs: next.retryInMs,
20434
- errorClass: classifyError(msg),
20467
+ errorClass: classifyError(msg, e),
20435
20468
  local: isLocalHost(cred.serverUrl) ? 1 : 0
20436
20469
  });
20437
20470
  }
@@ -20462,7 +20495,7 @@ async function syncSessions(opts = {}) {
20462
20495
  ${errors.join("\n ")}`);
20463
20496
  if (errors.length > 0) console.error(`[sync] ${errors.length} target(s) failed (others succeeded):
20464
20497
  ${errors.join("\n ")}`);
20465
- return agg;
20498
+ return { ...agg, perTarget };
20466
20499
  }
20467
20500
  async function postFieldBatch(base2, token, rows) {
20468
20501
  const headers = { "content-type": "application/json" };
@@ -20622,7 +20655,7 @@ async function syncToTarget(cred, opts = {}) {
20622
20655
  }
20623
20656
  async function syncToTargetInScope(cred, opts = {}) {
20624
20657
  const risk = transportRisk(cred.serverUrl);
20625
- if (risk) throw new Error(risk);
20658
+ if (risk) throw classedError("insecure_transport", risk);
20626
20659
  const sync = loadSettings().sync;
20627
20660
  const serverCfg = await fetchTenantSyncConfig(cred);
20628
20661
  const excludeTools = /* @__PURE__ */ new Set([...sync?.excludeTools ?? [], ...serverCfg.excludeTools]);
@@ -20722,7 +20755,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20722
20755
  const retryable = res.status === 429 || res.status >= 500;
20723
20756
  const text = await res.text().catch(() => "");
20724
20757
  if (res.status === 401 || res.status === 403) {
20725
- throw new Error(`server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
20758
+ throw classedError("auth", `server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
20726
20759
  }
20727
20760
  if (res.status === 402) {
20728
20761
  let detail = text;
@@ -20736,7 +20769,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20736
20769
  ${body2.upgradeUrl}`;
20737
20770
  } catch {
20738
20771
  }
20739
- throw new Error(`sync paused by the server: ${detail}`);
20772
+ throw classedError("payment_required", `sync paused by the server: ${detail}`);
20740
20773
  }
20741
20774
  if (!retryable) throw new Error(`sync failed: HTTP ${res.status} ${text}`);
20742
20775
  if (attempt >= RETRY_DELAYS_MS.length) throw new Error(`sync failed after ${attempt + 1} attempts: HTTP ${res.status} ${text}`);
@@ -20746,7 +20779,7 @@ async function syncToTargetInScope(cred, opts = {}) {
20746
20779
  await sleep(waitMs);
20747
20780
  continue;
20748
20781
  } catch (err) {
20749
- const fatal = err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
20782
+ const fatal = isStandingFailure(err) || err instanceof Error && /HTTP 4\d\d|sync failed after/.test(err.message);
20750
20783
  if (fatal || attempt >= RETRY_DELAYS_MS.length) throw err;
20751
20784
  console.error(`[sync] upload attempt ${attempt + 1} failed (${err instanceof Error ? err.message : err}) \u2014 retrying in ${RETRY_DELAYS_MS[attempt] / 1e3}s`);
20752
20785
  await sleep(RETRY_DELAYS_MS[attempt]);
@@ -21285,7 +21318,12 @@ async function buildConversationSync(ref, mtime, opts = {}) {
21285
21318
  const containerBytes = built ? built.files.reduce((n, f) => n + Buffer.byteLength(f.text), 0) : 0;
21286
21319
  if (containerBytes > FULL_BUILD_MAX_BYTES) {
21287
21320
  console.error(`[sync] ${ref.prefixedId} is ${(containerBytes / 1048576).toFixed(0)}MB (over the ${(FULL_BUILD_MAX_BYTES / 1048576).toFixed(0)}MB ceiling) \u2014 shipping without the raw archive and derived data to keep the walk in memory`);
21288
- record({ kind: "oversized_session", mb: Math.round(containerBytes / 1048576), tool: ref.toolId });
21321
+ record({
21322
+ kind: "oversized_session",
21323
+ mb: Math.round(containerBytes / 1048576),
21324
+ tool: ref.toolId,
21325
+ dedupeKey: stableMark(ref.prefixedId)
21326
+ });
21289
21327
  includeRaw = false;
21290
21328
  includeMeta = false;
21291
21329
  }
@@ -21596,7 +21634,7 @@ async function syncIncremental(opts = {}) {
21596
21634
  lock.release();
21597
21635
  }
21598
21636
  }
21599
- var ownVersion, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
21637
+ var ownVersion, ERROR_CLASS, STANDING_FAILURE, walkProgressForUpload, lastServerPackSpec, credPath, _securityConfigCache, SECURITY_CONFIG_TTL_MS, _syncConfigCache, EMPTY_SYNC_CONFIG, SYNC_RULES_REFUSAL, hashPath, MAX_DERIVED_ROW_BYTES, isLocalHost, CAPS_TTL_MS, capsOk, advertisedIngestConc, advertisedGzipBody, FULL_BUILD_MAX_BYTES, FULL_BUILD_TAIL_BYTES, BATCH_SCAN_MAX_BYTES;
21600
21638
  var init_sync_client = __esm({
21601
21639
  "src/sync-client.ts"() {
21602
21640
  "use strict";
@@ -21642,10 +21680,16 @@ var init_sync_client = __esm({
21642
21680
  ownVersion = JSON.parse(readFileSync37(join44(dir, "../package.json"), "utf-8")).version || ownVersion;
21643
21681
  } catch {
21644
21682
  try {
21645
- if (true) ownVersion = "0.5.30";
21683
+ if (true) ownVersion = "0.5.31";
21646
21684
  } catch {
21647
21685
  }
21648
21686
  }
21687
+ ERROR_CLASS = "__chatRecallErrorClass";
21688
+ STANDING_FAILURE = /* @__PURE__ */ new Set([
21689
+ "auth",
21690
+ "payment_required",
21691
+ "insecure_transport"
21692
+ ]);
21649
21693
  walkProgressForUpload = null;
21650
21694
  lastServerPackSpec = null;
21651
21695
  credPath = () => join44(getDataDir(), "credentials.json");
@@ -24430,7 +24474,7 @@ try {
24430
24474
  ownVersion2 = JSON.parse(readFileSync41(join50(dir, "../package.json"), "utf-8")).version || ownVersion2;
24431
24475
  } catch {
24432
24476
  try {
24433
- if (true) ownVersion2 = "0.5.30";
24477
+ if (true) ownVersion2 = "0.5.31";
24434
24478
  } catch {
24435
24479
  }
24436
24480
  }
@@ -24775,7 +24819,7 @@ async function runCollectorMigration(opts) {
24775
24819
  init_sync_ledger();
24776
24820
  init_collector_health();
24777
24821
  import { existsSync as existsSync50, readFileSync as readFileSync46 } from "fs";
24778
- var CLI_VERSION3 = true ? "0.5.30" : "0.0.0";
24822
+ var CLI_VERSION3 = true ? "0.5.31" : "0.0.0";
24779
24823
  var DEBOUNCE_MS = 5e3;
24780
24824
  var CLAUDE_DIR = claudeBackend.projectsDir();
24781
24825
  var PLANS_DIR = claudeBackend.plansDir();
@@ -24812,7 +24856,9 @@ async function shipToServer(trigger) {
24812
24856
  `Sync (${trigger}): ${result.uploaded} session(s) pushed, ${result.redactions} secret(s) redacted, ${result.skipped} skipped, ${result.items} items, ${result.links} links, ${result.derived} derived, ${result.kgEntities} KG entities, ${result.kgTriples} KG triples`
24813
24857
  );
24814
24858
  }
24815
- for (const server of syncTargetUrls()) noteSyncOutcome(server, true);
24859
+ for (const [server, outcome] of Object.entries(result.perTarget ?? {})) {
24860
+ noteSyncOutcome(server, outcome.ok, outcome.error);
24861
+ }
24816
24862
  } catch (err) {
24817
24863
  const msg = err instanceof Error ? err.message : String(err);
24818
24864
  daemonLog.error(`Sync failed (${trigger}, will retry next flush): ${msg.slice(0, 200)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-recall",
3
- "version": "0.5.30",
3
+ "version": "0.5.31",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/munhq/chat-recall.git"