chat-recall 0.7.14 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +424 -289
- package/dist/hostile-server.d.ts.map +1 -1
- package/dist/hostile-server.js +10 -1
- package/dist/hostile-server.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +331 -196
- package/dist/scan-worker.js +63 -3
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +17 -4
- package/dist/sync-client.js.map +1 -1
- package/dist/sync-ledger.d.ts +8 -1
- package/dist/sync-ledger.d.ts.map +1 -1
- package/dist/sync-ledger.js +121 -18
- package/dist/sync-ledger.js.map +1 -1
- package/dist/watch.js +373 -236
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -4259,7 +4259,7 @@ var require_pino = __commonJS({
|
|
|
4259
4259
|
messageKey,
|
|
4260
4260
|
errorKey,
|
|
4261
4261
|
nestedKey,
|
|
4262
|
-
base,
|
|
4262
|
+
base: base2,
|
|
4263
4263
|
name,
|
|
4264
4264
|
level: level2,
|
|
4265
4265
|
customLevels,
|
|
@@ -4298,11 +4298,11 @@ var require_pino = __commonJS({
|
|
|
4298
4298
|
[formattersSym]: allFormatters
|
|
4299
4299
|
});
|
|
4300
4300
|
let chindings = "";
|
|
4301
|
-
if (
|
|
4301
|
+
if (base2 !== null) {
|
|
4302
4302
|
if (name === void 0) {
|
|
4303
|
-
chindings = coreChindings(
|
|
4303
|
+
chindings = coreChindings(base2);
|
|
4304
4304
|
} else {
|
|
4305
|
-
chindings = coreChindings(Object.assign({},
|
|
4305
|
+
chindings = coreChindings(Object.assign({}, base2, { name }));
|
|
4306
4306
|
}
|
|
4307
4307
|
}
|
|
4308
4308
|
const time2 = timestamp instanceof Function ? timestamp : timestamp ? epochTime : nullTime;
|
|
@@ -5271,57 +5271,57 @@ function freshDefaults() {
|
|
|
5271
5271
|
team: defaultTeam()
|
|
5272
5272
|
};
|
|
5273
5273
|
}
|
|
5274
|
-
function mergeSources(
|
|
5274
|
+
function mergeSources(base2, partial) {
|
|
5275
5275
|
if (!partial)
|
|
5276
|
-
return
|
|
5276
|
+
return base2;
|
|
5277
5277
|
const legacyGemini = partial.enabled?.gemini;
|
|
5278
5278
|
const inherited = legacyGemini && typeof legacyGemini === "object" ? (({ sessions: _s, plans: _p, ...rest }) => rest)(legacyGemini) : {};
|
|
5279
5279
|
const enabled = {
|
|
5280
|
-
claude: { ...
|
|
5281
|
-
opencode: { ...
|
|
5282
|
-
codex: { ...
|
|
5283
|
-
agy: { ...
|
|
5284
|
-
cursor: { ...
|
|
5285
|
-
shared: { ...
|
|
5286
|
-
common: { ...
|
|
5280
|
+
claude: { ...base2.enabled.claude, ...partial.enabled?.claude ?? {} },
|
|
5281
|
+
opencode: { ...base2.enabled.opencode, ...partial.enabled?.opencode ?? {} },
|
|
5282
|
+
codex: { ...base2.enabled.codex, ...partial.enabled?.codex ?? {} },
|
|
5283
|
+
agy: { ...base2.enabled.agy, ...inherited, ...partial.enabled?.agy ?? {} },
|
|
5284
|
+
cursor: { ...base2.enabled.cursor, ...partial.enabled?.cursor ?? {} },
|
|
5285
|
+
shared: { ...base2.enabled.shared, ...partial.enabled?.shared ?? {} },
|
|
5286
|
+
common: { ...base2.enabled.common, ...partial.enabled?.common ?? {} }
|
|
5287
5287
|
};
|
|
5288
5288
|
return {
|
|
5289
|
-
claudeHome: partial.claudeHome ??
|
|
5290
|
-
geminiHome: partial.geminiHome ??
|
|
5291
|
-
codexHome: partial.codexHome ??
|
|
5292
|
-
opencodeDbPath: partial.opencodeDbPath ??
|
|
5293
|
-
agyHome: partial.agyHome ??
|
|
5294
|
-
cursorHome: partial.cursorHome ??
|
|
5295
|
-
cursorIdeHome: partial.cursorIdeHome ??
|
|
5296
|
-
extraClaudeHomes: partial.extraClaudeHomes ??
|
|
5297
|
-
approvedHomes: partial.approvedHomes ??
|
|
5298
|
-
declinedHomes: partial.declinedHomes ??
|
|
5299
|
-
homesGrandfathered: partial.homesGrandfathered ??
|
|
5289
|
+
claudeHome: partial.claudeHome ?? base2.claudeHome,
|
|
5290
|
+
geminiHome: partial.geminiHome ?? base2.geminiHome,
|
|
5291
|
+
codexHome: partial.codexHome ?? base2.codexHome,
|
|
5292
|
+
opencodeDbPath: partial.opencodeDbPath ?? base2.opencodeDbPath,
|
|
5293
|
+
agyHome: partial.agyHome ?? base2.agyHome,
|
|
5294
|
+
cursorHome: partial.cursorHome ?? base2.cursorHome,
|
|
5295
|
+
cursorIdeHome: partial.cursorIdeHome ?? base2.cursorIdeHome,
|
|
5296
|
+
extraClaudeHomes: partial.extraClaudeHomes ?? base2.extraClaudeHomes,
|
|
5297
|
+
approvedHomes: partial.approvedHomes ?? base2.approvedHomes,
|
|
5298
|
+
declinedHomes: partial.declinedHomes ?? base2.declinedHomes,
|
|
5299
|
+
homesGrandfathered: partial.homesGrandfathered ?? base2.homesGrandfathered,
|
|
5300
5300
|
enabled
|
|
5301
5301
|
};
|
|
5302
5302
|
}
|
|
5303
|
-
function mergePrivacy(
|
|
5303
|
+
function mergePrivacy(base2, partial) {
|
|
5304
5304
|
if (!partial)
|
|
5305
|
-
return
|
|
5306
|
-
return { ...
|
|
5305
|
+
return base2;
|
|
5306
|
+
return { ...base2, ...partial };
|
|
5307
5307
|
}
|
|
5308
|
-
function mergeSync(
|
|
5308
|
+
function mergeSync(base2, partial) {
|
|
5309
5309
|
if (!partial)
|
|
5310
|
-
return
|
|
5310
|
+
return base2;
|
|
5311
5311
|
return {
|
|
5312
|
-
...
|
|
5312
|
+
...base2,
|
|
5313
5313
|
...partial,
|
|
5314
|
-
upload: { ...
|
|
5314
|
+
upload: { ...base2.upload, ...partial.upload ?? {} }
|
|
5315
5315
|
};
|
|
5316
5316
|
}
|
|
5317
|
-
function mergeTeam(
|
|
5317
|
+
function mergeTeam(base2, partial) {
|
|
5318
5318
|
if (!partial)
|
|
5319
|
-
return
|
|
5319
|
+
return base2;
|
|
5320
5320
|
return {
|
|
5321
|
-
...
|
|
5321
|
+
...base2,
|
|
5322
5322
|
...partial,
|
|
5323
|
-
publishAllowed: { ...
|
|
5324
|
-
vault: { ...
|
|
5323
|
+
publishAllowed: { ...base2.publishAllowed, ...partial.publishAllowed ?? {} },
|
|
5324
|
+
vault: { ...base2.vault, ...partial.vault ?? {} }
|
|
5325
5325
|
};
|
|
5326
5326
|
}
|
|
5327
5327
|
function settingsFilePath() {
|
|
@@ -5439,15 +5439,15 @@ function loadSettings() {
|
|
|
5439
5439
|
}
|
|
5440
5440
|
try {
|
|
5441
5441
|
const parsed = JSON.parse(readFileSync3(path2, "utf-8"));
|
|
5442
|
-
const
|
|
5442
|
+
const base2 = freshDefaults();
|
|
5443
5443
|
const result = {
|
|
5444
5444
|
v: SCHEMA_VERSION,
|
|
5445
|
-
embedding: { ...
|
|
5446
|
-
summary: { ...
|
|
5447
|
-
sources: mergeSources(
|
|
5448
|
-
privacy: mergePrivacy(
|
|
5449
|
-
sync: mergeSync(
|
|
5450
|
-
team: mergeTeam(
|
|
5445
|
+
embedding: { ...base2.embedding, ...parsed.embedding ?? {} },
|
|
5446
|
+
summary: { ...base2.summary, ...parsed.summary ?? {} },
|
|
5447
|
+
sources: mergeSources(base2.sources, parsed.sources),
|
|
5448
|
+
privacy: mergePrivacy(base2.privacy, parsed.privacy),
|
|
5449
|
+
sync: mergeSync(base2.sync, parsed.sync),
|
|
5450
|
+
team: mergeTeam(base2.team, parsed.team)
|
|
5451
5451
|
};
|
|
5452
5452
|
if (mtimeMs >= 0)
|
|
5453
5453
|
_settingsCache = { mtimeMs, size, value: structuredClone(result) };
|
|
@@ -5643,10 +5643,10 @@ function legacyAutoSyncedHomes() {
|
|
|
5643
5643
|
{ base: join6(home, ".local", "share", "opencode"), marker: "opencode.db" },
|
|
5644
5644
|
{ base: join6(home, ".cursor"), marker: "chats" }
|
|
5645
5645
|
];
|
|
5646
|
-
for (const { base, marker } of bases) {
|
|
5647
|
-
add(
|
|
5648
|
-
const parent = dirname5(
|
|
5649
|
-
const leaf =
|
|
5646
|
+
for (const { base: base2, marker } of bases) {
|
|
5647
|
+
add(base2, marker);
|
|
5648
|
+
const parent = dirname5(base2);
|
|
5649
|
+
const leaf = base2.slice(parent.length + 1);
|
|
5650
5650
|
try {
|
|
5651
5651
|
for (const e of readdirSync4(parent, { withFileTypes: true })) {
|
|
5652
5652
|
if (!e.isDirectory())
|
|
@@ -5827,17 +5827,17 @@ function cursorHomeDir() {
|
|
|
5827
5827
|
function cursorIdeHomeDir() {
|
|
5828
5828
|
return process.env.CHAT_RECALL_CURSOR_IDE_HOME || sources().cursorIdeHome || (process.platform === "darwin" ? join7(homedir3(), "Library", "Application Support", "Cursor") : join7(homedir3(), ".config", "Cursor"));
|
|
5829
5829
|
}
|
|
5830
|
-
function siblingHomes(
|
|
5830
|
+
function siblingHomes(base2, overridden, suffix) {
|
|
5831
5831
|
const roots = [];
|
|
5832
5832
|
const add = (home) => {
|
|
5833
5833
|
const root = suffix ? join7(home, suffix) : home;
|
|
5834
5834
|
if (existsSync5(root) && !roots.includes(root))
|
|
5835
5835
|
roots.push(root);
|
|
5836
5836
|
};
|
|
5837
|
-
add(
|
|
5837
|
+
add(base2);
|
|
5838
5838
|
if (!overridden) {
|
|
5839
|
-
const parent = dirname6(
|
|
5840
|
-
const leaf = basename3(
|
|
5839
|
+
const parent = dirname6(base2);
|
|
5840
|
+
const leaf = basename3(base2);
|
|
5841
5841
|
try {
|
|
5842
5842
|
for (const entry of readdirSync5(parent, { withFileTypes: true })) {
|
|
5843
5843
|
if (!entry.isDirectory())
|
|
@@ -7108,7 +7108,64 @@ var init_opencode = __esm({
|
|
|
7108
7108
|
});
|
|
7109
7109
|
|
|
7110
7110
|
// ../engine/dist/core/backends/tail-read.js
|
|
7111
|
-
import { openSync, readSync, closeSync, statSync as statSync6 } from "node:fs";
|
|
7111
|
+
import { openSync, readSync, closeSync, statSync as statSync6, fstatSync } from "node:fs";
|
|
7112
|
+
function readAt(fd, pos, len) {
|
|
7113
|
+
const buf = Buffer.alloc(len);
|
|
7114
|
+
let got = 0;
|
|
7115
|
+
while (got < len) {
|
|
7116
|
+
const n = readSync(fd, buf, got, len - got, pos + got);
|
|
7117
|
+
if (n <= 0)
|
|
7118
|
+
break;
|
|
7119
|
+
got += n;
|
|
7120
|
+
}
|
|
7121
|
+
return buf.subarray(0, got);
|
|
7122
|
+
}
|
|
7123
|
+
function linesBeforeOffset(path2, offset) {
|
|
7124
|
+
if (offset <= 0)
|
|
7125
|
+
return 0;
|
|
7126
|
+
let fd = null;
|
|
7127
|
+
try {
|
|
7128
|
+
fd = openSync(path2, "r");
|
|
7129
|
+
const ino = fstatSync(fd).ino;
|
|
7130
|
+
const known = lineCounts.get(path2);
|
|
7131
|
+
let pos = 0;
|
|
7132
|
+
let lines = 0;
|
|
7133
|
+
if (known && known.ino === ino && known.offset <= offset) {
|
|
7134
|
+
const edgeStart2 = Math.max(0, known.offset - EDGE_BYTES);
|
|
7135
|
+
if (readAt(fd, edgeStart2, known.offset - edgeStart2).equals(known.edge)) {
|
|
7136
|
+
pos = known.offset;
|
|
7137
|
+
lines = known.lines;
|
|
7138
|
+
}
|
|
7139
|
+
}
|
|
7140
|
+
const block = Buffer.allocUnsafe(SKIP_SCAN_BYTES);
|
|
7141
|
+
while (pos < offset) {
|
|
7142
|
+
const n = readSync(fd, block, 0, Math.min(SKIP_SCAN_BYTES, offset - pos), pos);
|
|
7143
|
+
if (n <= 0)
|
|
7144
|
+
return void 0;
|
|
7145
|
+
for (let i = block.indexOf(10, 0); i >= 0 && i < n; i = block.indexOf(10, i + 1))
|
|
7146
|
+
lines++;
|
|
7147
|
+
pos += n;
|
|
7148
|
+
}
|
|
7149
|
+
const edgeStart = Math.max(0, offset - EDGE_BYTES);
|
|
7150
|
+
const edge = readAt(fd, edgeStart, offset - edgeStart);
|
|
7151
|
+
if (lineCounts.size >= LINE_COUNT_CACHE_MAX && !lineCounts.has(path2)) {
|
|
7152
|
+
const oldest = lineCounts.keys().next().value;
|
|
7153
|
+
if (oldest !== void 0)
|
|
7154
|
+
lineCounts.delete(oldest);
|
|
7155
|
+
}
|
|
7156
|
+
lineCounts.set(path2, { offset, lines, ino, edge });
|
|
7157
|
+
return lines;
|
|
7158
|
+
} catch {
|
|
7159
|
+
return void 0;
|
|
7160
|
+
} finally {
|
|
7161
|
+
if (fd !== null) {
|
|
7162
|
+
try {
|
|
7163
|
+
closeSync(fd);
|
|
7164
|
+
} catch {
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
}
|
|
7112
7169
|
function readTailFromOffset(path2, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
7113
7170
|
let size = 0;
|
|
7114
7171
|
try {
|
|
@@ -7136,7 +7193,7 @@ function readTailFromOffset(path2, offset, maxBytes = TAIL_READ_MAX_BYTES) {
|
|
|
7136
7193
|
const lastNl = slice.lastIndexOf(10);
|
|
7137
7194
|
if (lastNl >= 0) {
|
|
7138
7195
|
const text = slice.subarray(0, lastNl + 1).toString("utf-8");
|
|
7139
|
-
return { text, newOffset: offset + lastNl + 1 };
|
|
7196
|
+
return { text, newOffset: offset + lastNl + 1, baseLine: linesBeforeOffset(path2, offset) };
|
|
7140
7197
|
}
|
|
7141
7198
|
if (offset + got >= size)
|
|
7142
7199
|
return { text: "", newOffset: offset };
|
|
@@ -7169,12 +7226,15 @@ function findNewline(fd, from, size) {
|
|
|
7169
7226
|
}
|
|
7170
7227
|
return -1;
|
|
7171
7228
|
}
|
|
7172
|
-
var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES;
|
|
7229
|
+
var TAIL_READ_MAX_BYTES, SKIP_SCAN_BYTES, lineCounts, LINE_COUNT_CACHE_MAX, EDGE_BYTES;
|
|
7173
7230
|
var init_tail_read = __esm({
|
|
7174
7231
|
"../engine/dist/core/backends/tail-read.js"() {
|
|
7175
7232
|
"use strict";
|
|
7176
7233
|
TAIL_READ_MAX_BYTES = 16 * 1024 * 1024;
|
|
7177
7234
|
SKIP_SCAN_BYTES = 1024 * 1024;
|
|
7235
|
+
lineCounts = /* @__PURE__ */ new Map();
|
|
7236
|
+
LINE_COUNT_CACHE_MAX = 2048;
|
|
7237
|
+
EDGE_BYTES = 64;
|
|
7178
7238
|
}
|
|
7179
7239
|
});
|
|
7180
7240
|
|
|
@@ -11019,9 +11079,9 @@ async function parseSessionFile(sessionPath, maxMessages = 50) {
|
|
|
11019
11079
|
content.metadata.durationMs += obj.durationMs;
|
|
11020
11080
|
}
|
|
11021
11081
|
if (msgType === "file-history-snapshot") {
|
|
11022
|
-
const
|
|
11023
|
-
if (
|
|
11024
|
-
const backups =
|
|
11082
|
+
const snapshot2 = obj.snapshot;
|
|
11083
|
+
if (snapshot2 && typeof snapshot2 === "object") {
|
|
11084
|
+
const backups = snapshot2.trackedFileBackups;
|
|
11025
11085
|
if (backups && typeof backups === "object") {
|
|
11026
11086
|
for (const filePath2 of Object.keys(backups)) {
|
|
11027
11087
|
filesModified.add(filePath2);
|
|
@@ -13290,8 +13350,8 @@ function serverRulePackVersion() {
|
|
|
13290
13350
|
return serverPack ? serverPack.version || "unversioned" : null;
|
|
13291
13351
|
}
|
|
13292
13352
|
function activeRules() {
|
|
13293
|
-
const
|
|
13294
|
-
return serverPack && serverPack.rules.length > 0 ? [...
|
|
13353
|
+
const base2 = settingsView().rules;
|
|
13354
|
+
return serverPack && serverPack.rules.length > 0 ? [...base2, ...serverPack.rules] : base2;
|
|
13295
13355
|
}
|
|
13296
13356
|
function isRedactionEnabled() {
|
|
13297
13357
|
const env = isEnvRedactionRequested();
|
|
@@ -14154,17 +14214,17 @@ async function collectCode(opts) {
|
|
|
14154
14214
|
return r;
|
|
14155
14215
|
};
|
|
14156
14216
|
log6(`indexing ${ws} + analyses \u2026`);
|
|
14157
|
-
const
|
|
14217
|
+
const base2 = await runMcp(bin, ws, [
|
|
14158
14218
|
call(1, "index_workspace", { path: ws }),
|
|
14159
14219
|
call(2, "status"),
|
|
14160
14220
|
call(3, "get_tree"),
|
|
14161
14221
|
...ANALYSES.map((a, i) => call(10 + i, "analyze", { analysis: a }))
|
|
14162
14222
|
]);
|
|
14163
|
-
const st = parseJson(
|
|
14164
|
-
const tree = parseJson(
|
|
14223
|
+
const st = parseJson(base2, 2, {});
|
|
14224
|
+
const tree = parseJson(base2, 3, []);
|
|
14165
14225
|
const A = {};
|
|
14166
14226
|
ANALYSES.forEach((a, i) => {
|
|
14167
|
-
A[a] = parseJson(
|
|
14227
|
+
A[a] = parseJson(base2, 10 + i, {});
|
|
14168
14228
|
});
|
|
14169
14229
|
const { health, security: sec, duplication: dup, clones, literal_scan: lit, coupling: coup, cycles: cyc, dead_code: dead, unwrap_audit: unwrap, test_coverage: coverage, architecture: arch, crossref, type_drift: typeDrift, db_schema: dbSchema, migration_parity: migParity, manifest_compliance: manifest } = A;
|
|
14170
14230
|
if (tree.length === 0) {
|
|
@@ -19452,38 +19512,100 @@ var init_extractor_version = __esm({
|
|
|
19452
19512
|
});
|
|
19453
19513
|
|
|
19454
19514
|
// src/sync-ledger.ts
|
|
19455
|
-
import { readFileSync as readFileSync34, writeFileSync as writeFileSync7, renameSync as renameSync5, mkdirSync as mkdirSync10 } from "node:fs";
|
|
19515
|
+
import { readFileSync as readFileSync34, writeFileSync as writeFileSync7, renameSync as renameSync5, mkdirSync as mkdirSync10, statSync as statSync40 } from "node:fs";
|
|
19456
19516
|
import { join as join46, dirname as dirname17 } from "node:path";
|
|
19457
19517
|
function toRow(entry) {
|
|
19458
19518
|
if (entry === void 0) return void 0;
|
|
19459
19519
|
return typeof entry === "number" ? { m: entry, v: 0 } : entry;
|
|
19460
19520
|
}
|
|
19461
|
-
function
|
|
19462
|
-
if (cache3) return cache3;
|
|
19521
|
+
function stampOf(path2) {
|
|
19463
19522
|
try {
|
|
19464
|
-
const
|
|
19465
|
-
|
|
19523
|
+
const st = statSync40(path2);
|
|
19524
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
19466
19525
|
} catch {
|
|
19467
|
-
|
|
19526
|
+
return null;
|
|
19468
19527
|
}
|
|
19528
|
+
}
|
|
19529
|
+
function readDisk(path2) {
|
|
19530
|
+
try {
|
|
19531
|
+
const parsed = JSON.parse(readFileSync34(path2, "utf-8"));
|
|
19532
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
19533
|
+
} catch {
|
|
19534
|
+
return {};
|
|
19535
|
+
}
|
|
19536
|
+
}
|
|
19537
|
+
function snapshot(data) {
|
|
19538
|
+
const out = /* @__PURE__ */ new Map();
|
|
19539
|
+
for (const [server, rows] of Object.entries(data)) {
|
|
19540
|
+
for (const [id, entry] of Object.entries(rows)) out.set(rowKey(server, id), JSON.stringify(entry));
|
|
19541
|
+
}
|
|
19542
|
+
return out;
|
|
19543
|
+
}
|
|
19544
|
+
function mergeOnto(disk) {
|
|
19545
|
+
const out = {};
|
|
19546
|
+
const put = (server, id, entry) => {
|
|
19547
|
+
(out[server] ??= {})[id] = entry;
|
|
19548
|
+
};
|
|
19549
|
+
const mine = cache3 ?? {};
|
|
19550
|
+
for (const [server, rows] of Object.entries(disk)) {
|
|
19551
|
+
for (const [id, entry] of Object.entries(rows)) {
|
|
19552
|
+
const k = rowKey(server, id);
|
|
19553
|
+
const own = mine[server]?.[id];
|
|
19554
|
+
const was = base.get(k);
|
|
19555
|
+
if (own === void 0) {
|
|
19556
|
+
if (was === void 0) put(server, id, entry);
|
|
19557
|
+
} else {
|
|
19558
|
+
put(server, id, JSON.stringify(own) === was ? entry : own);
|
|
19559
|
+
}
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
for (const [server, rows] of Object.entries(mine)) {
|
|
19563
|
+
for (const [id, entry] of Object.entries(rows)) {
|
|
19564
|
+
if (disk[server]?.[id] !== void 0) continue;
|
|
19565
|
+
if (JSON.stringify(entry) !== base.get(rowKey(server, id))) put(server, id, entry);
|
|
19566
|
+
}
|
|
19567
|
+
}
|
|
19568
|
+
return out;
|
|
19569
|
+
}
|
|
19570
|
+
function load() {
|
|
19571
|
+
const path2 = ledgerPath();
|
|
19572
|
+
const stamp = stampOf(path2);
|
|
19573
|
+
if (cache3 && stamp === diskStamp) return cache3;
|
|
19574
|
+
const disk = readDisk(path2);
|
|
19575
|
+
const merged = cache3 ? mergeOnto(disk) : disk;
|
|
19576
|
+
if (cache3) {
|
|
19577
|
+
for (const server of Object.keys(cache3)) if (!(server in merged)) delete cache3[server];
|
|
19578
|
+
for (const [server, rows] of Object.entries(merged)) {
|
|
19579
|
+
const target = cache3[server] ?? (cache3[server] = {});
|
|
19580
|
+
for (const id of Object.keys(target)) if (!(id in rows)) delete target[id];
|
|
19581
|
+
Object.assign(target, rows);
|
|
19582
|
+
}
|
|
19583
|
+
} else {
|
|
19584
|
+
cache3 = merged;
|
|
19585
|
+
}
|
|
19586
|
+
base = snapshot(disk);
|
|
19587
|
+
diskStamp = stamp;
|
|
19469
19588
|
return cache3;
|
|
19470
19589
|
}
|
|
19471
|
-
function persistNow(
|
|
19590
|
+
function persistNow() {
|
|
19472
19591
|
const path2 = ledgerPath();
|
|
19473
19592
|
mkdirSync10(dirname17(path2), { recursive: true });
|
|
19474
|
-
|
|
19475
|
-
|
|
19593
|
+
load();
|
|
19594
|
+
const tmp = `${path2}.tmp-${process.pid}`;
|
|
19595
|
+
writeFileSync7(tmp, JSON.stringify(cache3 ?? {}));
|
|
19476
19596
|
renameSync5(tmp, path2);
|
|
19597
|
+
base = snapshot(cache3 ?? {});
|
|
19598
|
+
diskStamp = stampOf(path2);
|
|
19477
19599
|
pendingWrite = false;
|
|
19478
19600
|
}
|
|
19479
|
-
function persist(
|
|
19601
|
+
function persist(_data) {
|
|
19480
19602
|
pendingWrite = true;
|
|
19481
19603
|
if (flushTimer) return;
|
|
19482
19604
|
flushTimer = setTimeout(() => {
|
|
19483
19605
|
flushTimer = null;
|
|
19484
19606
|
if (pendingWrite) {
|
|
19485
19607
|
try {
|
|
19486
|
-
persistNow(
|
|
19608
|
+
persistNow();
|
|
19487
19609
|
} catch {
|
|
19488
19610
|
}
|
|
19489
19611
|
}
|
|
@@ -19517,7 +19639,7 @@ function pruneLedgerTargets(configuredServers) {
|
|
|
19517
19639
|
}
|
|
19518
19640
|
const rows = names.reduce((n, s) => n + Object.keys(orphans[s]).length, 0);
|
|
19519
19641
|
console.error(`[sync] tidied local bookkeeping: ${rows} already-synced marker(s) for ${names.length} server(s) you no longer sync to (${names.join(", ")}) moved to ${side}. Nothing was uploaded there; the file is kept so logging back in re-ships nothing.`);
|
|
19520
|
-
persistNow(
|
|
19642
|
+
persistNow();
|
|
19521
19643
|
return names.length;
|
|
19522
19644
|
}
|
|
19523
19645
|
function flushLedger() {
|
|
@@ -19526,7 +19648,7 @@ function flushLedger() {
|
|
|
19526
19648
|
flushTimer = null;
|
|
19527
19649
|
}
|
|
19528
19650
|
if (!pendingWrite || !cache3) return;
|
|
19529
|
-
persistNow(
|
|
19651
|
+
persistNow();
|
|
19530
19652
|
}
|
|
19531
19653
|
function loadItemVersionsAll() {
|
|
19532
19654
|
if (itemVersionsCache) return itemVersionsCache;
|
|
@@ -19586,6 +19708,7 @@ function persistLedgerData(server, serverData) {
|
|
|
19586
19708
|
const data = load();
|
|
19587
19709
|
data[server] = serverData;
|
|
19588
19710
|
persist(data);
|
|
19711
|
+
flushLedger();
|
|
19589
19712
|
}
|
|
19590
19713
|
function markSynced(server, rows) {
|
|
19591
19714
|
if (rows.length === 0) return;
|
|
@@ -19602,13 +19725,13 @@ function markSynced(server, rows) {
|
|
|
19602
19725
|
const o = (cursorFromCaller ? r.offset : void 0) ?? prevOS.o ?? 0;
|
|
19603
19726
|
const s = (cursorFromCaller ? r.size : void 0) ?? prevOS.s ?? 0;
|
|
19604
19727
|
const ev = extractorVersionForId(r.id);
|
|
19605
|
-
const
|
|
19728
|
+
const base2 = prevF ? { m: Math.floor(r.mtime), v: ev, f: prevF } : { m: Math.floor(r.mtime), v: ev };
|
|
19606
19729
|
if (o > 0 || s > 0) {
|
|
19607
|
-
|
|
19608
|
-
|
|
19730
|
+
base2.o = o;
|
|
19731
|
+
base2.s = s;
|
|
19609
19732
|
}
|
|
19610
|
-
if (r.hash !== void 0)
|
|
19611
|
-
forServer[r.id] =
|
|
19733
|
+
if (r.hash !== void 0) base2.h = r.hash;
|
|
19734
|
+
forServer[r.id] = base2;
|
|
19612
19735
|
}
|
|
19613
19736
|
persist(data);
|
|
19614
19737
|
}
|
|
@@ -19683,7 +19806,7 @@ function syncMode(row, mtime, fileSize, extractorVersion, isAppendOnly) {
|
|
|
19683
19806
|
if (row.m >= Math.floor(mtime)) return "skip";
|
|
19684
19807
|
return "full";
|
|
19685
19808
|
}
|
|
19686
|
-
var ledgerPath, cache3, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache;
|
|
19809
|
+
var ledgerPath, cache3, base, diskStamp, rowKey, LEDGER_FLUSH_MS, pendingWrite, flushTimer, itemVersionsPath, itemVersionsCache, inventoryPath, unackedCursorAttempts, reconcilePath, reconcileCache;
|
|
19687
19810
|
var init_sync_ledger = __esm({
|
|
19688
19811
|
"src/sync-ledger.ts"() {
|
|
19689
19812
|
"use strict";
|
|
@@ -19691,6 +19814,9 @@ var init_sync_ledger = __esm({
|
|
|
19691
19814
|
init_extractor_version();
|
|
19692
19815
|
ledgerPath = () => join46(getDataDir(), "sync-ledger.json");
|
|
19693
19816
|
cache3 = null;
|
|
19817
|
+
base = /* @__PURE__ */ new Map();
|
|
19818
|
+
diskStamp = null;
|
|
19819
|
+
rowKey = (server, id) => `${server}\0${id}`;
|
|
19694
19820
|
LEDGER_FLUSH_MS = 2e3;
|
|
19695
19821
|
pendingWrite = false;
|
|
19696
19822
|
flushTimer = null;
|
|
@@ -19729,7 +19855,7 @@ var init_sync_fields = __esm({
|
|
|
19729
19855
|
});
|
|
19730
19856
|
|
|
19731
19857
|
// ../engine/dist/core/index-lock.js
|
|
19732
|
-
import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as openSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync35, rmSync as rmSync4, statSync as
|
|
19858
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync11, openSync as openSync9, writeFileSync as writeFileSync8, readFileSync as readFileSync35, rmSync as rmSync4, statSync as statSync41, closeSync as closeSync9 } from "fs";
|
|
19733
19859
|
import { dirname as dirname18, join as join47 } from "path";
|
|
19734
19860
|
function acquireIndexLock(opts) {
|
|
19735
19861
|
const lockPath = lockFilePath(opts.file);
|
|
@@ -19779,7 +19905,7 @@ function lockFilePath(file = ".compact.lock") {
|
|
|
19779
19905
|
}
|
|
19780
19906
|
function stealStaleLock(lockPath, staleAfterMs) {
|
|
19781
19907
|
try {
|
|
19782
|
-
const s =
|
|
19908
|
+
const s = statSync41(lockPath);
|
|
19783
19909
|
const age = Date.now() - s.mtimeMs;
|
|
19784
19910
|
let payload;
|
|
19785
19911
|
try {
|
|
@@ -19845,7 +19971,7 @@ var init_http = __esm({
|
|
|
19845
19971
|
CLI_VERSION = JSON.parse(readFileSync36(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
19846
19972
|
} catch {
|
|
19847
19973
|
try {
|
|
19848
|
-
if (true) CLI_VERSION = "0.7.
|
|
19974
|
+
if (true) CLI_VERSION = "0.7.16";
|
|
19849
19975
|
} catch {
|
|
19850
19976
|
}
|
|
19851
19977
|
}
|
|
@@ -20343,7 +20469,7 @@ var init_telemetry = __esm({
|
|
|
20343
20469
|
init_sync_client();
|
|
20344
20470
|
cliVersion2 = () => {
|
|
20345
20471
|
try {
|
|
20346
|
-
return true ? "0.7.
|
|
20472
|
+
return true ? "0.7.16" : "dev";
|
|
20347
20473
|
} catch {
|
|
20348
20474
|
return "dev";
|
|
20349
20475
|
}
|
|
@@ -20366,11 +20492,11 @@ __export(server_probe_exports, {
|
|
|
20366
20492
|
function probeOk(r) {
|
|
20367
20493
|
return r.kind === "ok" && r.apiVersion >= 2;
|
|
20368
20494
|
}
|
|
20369
|
-
async function probeServer(
|
|
20495
|
+
async function probeServer(base2, opts = {}) {
|
|
20370
20496
|
const f = opts.fetchImpl || fetch;
|
|
20371
20497
|
let res;
|
|
20372
20498
|
try {
|
|
20373
|
-
res = await f(`${
|
|
20499
|
+
res = await f(`${base2.replace(/\/+$/, "")}/api/capabilities`, {
|
|
20374
20500
|
signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
|
|
20375
20501
|
});
|
|
20376
20502
|
} catch (err) {
|
|
@@ -20407,34 +20533,34 @@ async function probeServer(base, opts = {}) {
|
|
|
20407
20533
|
raw: parsed
|
|
20408
20534
|
};
|
|
20409
20535
|
}
|
|
20410
|
-
function probeAdvice(r,
|
|
20536
|
+
function probeAdvice(r, base2) {
|
|
20411
20537
|
switch (r.kind) {
|
|
20412
20538
|
case "ok":
|
|
20413
20539
|
return r.apiVersion >= 2 ? `apiVersion ${r.apiVersion}${r.edition ? `, edition ${r.edition}` : ""}` : `apiVersion ${r.apiVersion} is too old for this CLI \u2014 upgrade the server`;
|
|
20414
20540
|
case "moved":
|
|
20415
|
-
return `HTTP ${r.status} \u2014 nothing is served at ${
|
|
20541
|
+
return `HTTP ${r.status} \u2014 nothing is served at ${base2}. If the host moved, re-point it: \`chat-recall login <new-server-url>\``;
|
|
20416
20542
|
case "unauthorized":
|
|
20417
|
-
return `HTTP ${r.status} \u2014 ${
|
|
20543
|
+
return `HTTP ${r.status} \u2014 ${base2} rejected the credential. Re-login: \`chat-recall login ` + base2 + "`";
|
|
20418
20544
|
case "server_error":
|
|
20419
|
-
return `HTTP ${r.status} \u2014 ${
|
|
20545
|
+
return `HTTP ${r.status} \u2014 ${base2} is up but failing. Nothing to fix locally; check the server.`;
|
|
20420
20546
|
case "not_api":
|
|
20421
|
-
return `${
|
|
20547
|
+
return `${base2} answered ${r.status} with something that is not the chat-recall API (${JSON.stringify(r.snippet)}) \u2014 check the URL, a proxy or a captive portal`;
|
|
20422
20548
|
case "unreachable":
|
|
20423
20549
|
switch (r.reason) {
|
|
20424
20550
|
case "dns":
|
|
20425
|
-
return `cannot resolve the host in ${
|
|
20551
|
+
return `cannot resolve the host in ${base2} (${r.error}) \u2014 check the URL for a typo, and your DNS or VPN`;
|
|
20426
20552
|
case "refused":
|
|
20427
|
-
return `${
|
|
20553
|
+
return `${base2} resolves but refused the connection (${r.error}) \u2014 the server is not running, or the port is wrong`;
|
|
20428
20554
|
case "tls":
|
|
20429
|
-
return `the TLS certificate at ${
|
|
20555
|
+
return `the TLS certificate at ${base2} was rejected (${r.error}) \u2014 expired, self-signed, or issued for another host. A corporate proxy that re-signs traffic does this.`;
|
|
20430
20556
|
case "timeout":
|
|
20431
|
-
return `${
|
|
20557
|
+
return `${base2} did not answer in time (${r.error}) \u2014 a firewall or proxy that drops traffic silently looks exactly like this`;
|
|
20432
20558
|
case "reset":
|
|
20433
|
-
return `the connection to ${
|
|
20559
|
+
return `the connection to ${base2} was cut (${r.error}) \u2014 often a proxy or a captive portal`;
|
|
20434
20560
|
case "bad_port":
|
|
20435
|
-
return `${
|
|
20561
|
+
return `${base2} names a port that Node and every browser refuse to open (the WHATWG bad-port list) \u2014 use the server's real port`;
|
|
20436
20562
|
default:
|
|
20437
|
-
return `no HTTP response from ${
|
|
20563
|
+
return `no HTTP response from ${base2} (${r.error}) \u2014 check the URL, DNS, or that the server is running`;
|
|
20438
20564
|
}
|
|
20439
20565
|
}
|
|
20440
20566
|
}
|
|
@@ -20467,8 +20593,8 @@ function describeCause(err) {
|
|
|
20467
20593
|
const code = e.code;
|
|
20468
20594
|
if (typeof code === "string" && code && !parts.includes(code)) parts.push(code);
|
|
20469
20595
|
}
|
|
20470
|
-
const
|
|
20471
|
-
return parts.length ? `${
|
|
20596
|
+
const base2 = err instanceof Error ? err.message : String(err);
|
|
20597
|
+
return parts.length ? `${base2}: ${parts.join(" \u2192 ")}` : base2;
|
|
20472
20598
|
}
|
|
20473
20599
|
function describeUnreachable(err) {
|
|
20474
20600
|
const reason = classifyUnreachable(err);
|
|
@@ -20516,7 +20642,7 @@ __export(auto_update_exports, {
|
|
|
20516
20642
|
import { execSync as execSync4, execFileSync as execFileSync3 } from "node:child_process";
|
|
20517
20643
|
import { readFileSync as readFileSync37, existsSync as existsSync45 } from "node:fs";
|
|
20518
20644
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
20519
|
-
import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as
|
|
20645
|
+
import { writeFileSync as writeFileSync9, mkdtempSync as mkdtempSync3, rmSync as rmSync5, readdirSync as readdirSync31, statSync as statSync42, mkdirSync as mkdirSync12 } from "node:fs";
|
|
20520
20646
|
import { join as join48, dirname as dirname19, sep as sep3, posix as pathPosix, win32 as pathWin32 } from "node:path";
|
|
20521
20647
|
import { tmpdir as tmpdir3, homedir as homedir17 } from "node:os";
|
|
20522
20648
|
import { createHash as createHash10 } from "node:crypto";
|
|
@@ -20548,12 +20674,12 @@ function installedVersion() {
|
|
|
20548
20674
|
return null;
|
|
20549
20675
|
}
|
|
20550
20676
|
}
|
|
20551
|
-
function planAutoUpdate(
|
|
20677
|
+
function planAutoUpdate(base2, caps, ownVersion2, flag) {
|
|
20552
20678
|
const rel = caps.cli;
|
|
20553
20679
|
if (!rel || !rel.version || !rel.sha256) return { update: false, reason: "server advertises no CLI release" };
|
|
20554
20680
|
if (!isAutoUpdateEnabled(caps.edition, flag)) return { update: false, reason: `auto-update disabled (edition ${caps.edition ?? "?"})` };
|
|
20555
20681
|
if (compareVersions(rel.version, ownVersion2) <= 0) return { update: false, reason: `already current (own ${ownVersion2} >= server ${rel.version})` };
|
|
20556
|
-
return { update: true, reason: `server ${rel.version} > own ${ownVersion2}`, url: tarballUrl(
|
|
20682
|
+
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 };
|
|
20557
20683
|
}
|
|
20558
20684
|
function sweepStaleStaging(root = tmpdir3(), now = Date.now()) {
|
|
20559
20685
|
const MKDTEMP_SUFFIX = /^cr-update-.{6}$/s;
|
|
@@ -20569,7 +20695,7 @@ function sweepStaleStaging(root = tmpdir3(), now = Date.now()) {
|
|
|
20569
20695
|
if (!MKDTEMP_SUFFIX.test(name)) continue;
|
|
20570
20696
|
const dir = join48(root, name);
|
|
20571
20697
|
try {
|
|
20572
|
-
const st =
|
|
20698
|
+
const st = statSync42(dir);
|
|
20573
20699
|
if (!st.isDirectory() || now - st.mtimeMs < COLD_MS) continue;
|
|
20574
20700
|
const inside = readdirSync31(dir);
|
|
20575
20701
|
if (inside.length > 1 || inside.length === 1 && inside[0] !== "chat-recall.tgz") continue;
|
|
@@ -20733,20 +20859,20 @@ function clearUpdateFailures() {
|
|
|
20733
20859
|
function stopAutoUpdates() {
|
|
20734
20860
|
stopRequested = true;
|
|
20735
20861
|
}
|
|
20736
|
-
async function runAutoUpdate(
|
|
20862
|
+
async function runAutoUpdate(base2, authHeaders, ownVersion2, deps) {
|
|
20737
20863
|
let caps;
|
|
20738
20864
|
try {
|
|
20739
|
-
const res = await fetchWithTimeout(`${
|
|
20865
|
+
const res = await fetchWithTimeout(`${base2.replace(/\/+$/, "")}/api/capabilities`, { headers: authHeaders });
|
|
20740
20866
|
if (!res.ok) return { updated: false, reason: `capabilities HTTP ${res.status}` };
|
|
20741
20867
|
caps = await res.json();
|
|
20742
20868
|
} catch (e) {
|
|
20743
20869
|
return { updated: false, reason: `capabilities unreachable: ${e instanceof Error ? e.message : e}` };
|
|
20744
20870
|
}
|
|
20745
20871
|
const effectiveOwn = newerOf(ownVersion2, installedVersion());
|
|
20746
|
-
const plan = planAutoUpdate(
|
|
20872
|
+
const plan = planAutoUpdate(base2, caps, effectiveOwn, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
20747
20873
|
if (!plan.update) {
|
|
20748
|
-
if (/disabled|no CLI release/.test(plan.reason) && !reportedStandingSkip.has(
|
|
20749
|
-
reportedStandingSkip.add(
|
|
20874
|
+
if (/disabled|no CLI release/.test(plan.reason) && !reportedStandingSkip.has(base2)) {
|
|
20875
|
+
reportedStandingSkip.add(base2);
|
|
20750
20876
|
try {
|
|
20751
20877
|
const { reportClientEvent: reportClientEvent3 } = await Promise.resolve().then(() => (init_client_events(), client_events_exports));
|
|
20752
20878
|
reportClientEvent3("auto_update_skipped", { message: `${plan.reason} (server offers ${caps.cli?.version ?? "?"}, this device runs ${effectiveOwn})` });
|
|
@@ -20802,7 +20928,7 @@ var init_auto_update = __esm({
|
|
|
20802
20928
|
"use strict";
|
|
20803
20929
|
init_http();
|
|
20804
20930
|
init_index_lock();
|
|
20805
|
-
tarballUrl = (
|
|
20931
|
+
tarballUrl = (base2, version2) => `${base2.replace(/\/+$/, "")}/install/${version2 ? `chat-recall-${version2}.tgz` : "chat-recall.tgz"}`;
|
|
20806
20932
|
GIVE_UP_AFTER = 3;
|
|
20807
20933
|
reportedStandingSkip = /* @__PURE__ */ new Set();
|
|
20808
20934
|
STOPPING_REASON = "not installed: this process is shutting down";
|
|
@@ -20847,7 +20973,7 @@ __export(sync_client_exports, {
|
|
|
20847
20973
|
syncSessions: () => syncSessions
|
|
20848
20974
|
});
|
|
20849
20975
|
import { join as join49, dirname as dirname20, basename as basename22 } from "node:path";
|
|
20850
|
-
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
|
|
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";
|
|
20851
20977
|
import { tmpdir as tmpdir4, hostname as hostname2 } from "node:os";
|
|
20852
20978
|
import { createHash as createHash11 } from "node:crypto";
|
|
20853
20979
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -20910,15 +21036,15 @@ function loadAllCredentials() {
|
|
|
20910
21036
|
return [];
|
|
20911
21037
|
}
|
|
20912
21038
|
async function fetchTenantSecurityConfig(cred) {
|
|
20913
|
-
const
|
|
20914
|
-
const cached = _securityConfigCache.get(
|
|
21039
|
+
const base2 = cred.serverUrl.replace(/\/+$/, "");
|
|
21040
|
+
const cached = _securityConfigCache.get(base2);
|
|
20915
21041
|
if (cached && Date.now() - cached.fetchedAt < SECURITY_CONFIG_TTL_MS) return cached.config;
|
|
20916
21042
|
const headers = { "content-type": "application/json" };
|
|
20917
21043
|
if (cred.token) headers.authorization = `Bearer ${cred.token}`;
|
|
20918
21044
|
try {
|
|
20919
21045
|
const [rulesRes, settingsRes] = await Promise.all([
|
|
20920
|
-
fetchWithTimeout(`${
|
|
20921
|
-
fetchWithTimeout(`${
|
|
21046
|
+
fetchWithTimeout(`${base2}/api/secrets/rules`, { headers }),
|
|
21047
|
+
fetchWithTimeout(`${base2}/api/teams/security-config`, { headers }).catch(() => null)
|
|
20922
21048
|
]);
|
|
20923
21049
|
let tenantRules = [];
|
|
20924
21050
|
let rulePackVersion = null;
|
|
@@ -20943,18 +21069,18 @@ async function fetchTenantSecurityConfig(cred) {
|
|
|
20943
21069
|
const body2 = await settingsRes.json().catch(() => ({}));
|
|
20944
21070
|
if (typeof body2.verifySecrets === "boolean") verifySecrets = body2.verifySecrets;
|
|
20945
21071
|
if (typeof body2.telemetry === "boolean") {
|
|
20946
|
-
setTelemetryEligible(
|
|
21072
|
+
setTelemetryEligible(base2, body2.telemetry);
|
|
20947
21073
|
try {
|
|
20948
21074
|
const prior = readCollectorHealth()?.telemetryEligible ?? {};
|
|
20949
21075
|
updateCollectorHealth({
|
|
20950
|
-
telemetryEligible: { ...prior, [
|
|
21076
|
+
telemetryEligible: { ...prior, [base2]: { allowed: body2.telemetry, at: Date.now() } }
|
|
20951
21077
|
});
|
|
20952
21078
|
} catch {
|
|
20953
21079
|
}
|
|
20954
21080
|
}
|
|
20955
21081
|
}
|
|
20956
21082
|
const config2 = { tenantRules, verifySecrets, rulePackVersion };
|
|
20957
|
-
_securityConfigCache.set(
|
|
21083
|
+
_securityConfigCache.set(base2, { fetchedAt: Date.now(), config: config2 });
|
|
20958
21084
|
return config2;
|
|
20959
21085
|
} catch {
|
|
20960
21086
|
return { tenantRules: [], verifySecrets: true, rulePackVersion: null };
|
|
@@ -20973,18 +21099,18 @@ function readPersistedSyncConfigs() {
|
|
|
20973
21099
|
return {};
|
|
20974
21100
|
}
|
|
20975
21101
|
}
|
|
20976
|
-
function persistSyncConfig(
|
|
21102
|
+
function persistSyncConfig(base2, config2) {
|
|
20977
21103
|
try {
|
|
20978
21104
|
const all = readPersistedSyncConfigs();
|
|
20979
|
-
all[
|
|
21105
|
+
all[base2] = { fetchedAt: Date.now(), config: config2 };
|
|
20980
21106
|
mkdirSync13(dirname20(syncConfigCachePath()), { recursive: true });
|
|
20981
21107
|
writeFileSync10(syncConfigCachePath(), JSON.stringify(all, null, 2));
|
|
20982
21108
|
} catch {
|
|
20983
21109
|
}
|
|
20984
21110
|
}
|
|
20985
21111
|
async function fetchTenantSyncConfig(cred) {
|
|
20986
|
-
const
|
|
20987
|
-
const cached = _syncConfigCache.get(
|
|
21112
|
+
const base2 = cred.serverUrl.replace(/\/+$/, "");
|
|
21113
|
+
const cached = _syncConfigCache.get(base2);
|
|
20988
21114
|
if (cached && Date.now() - cached.fetchedAt < SECURITY_CONFIG_TTL_MS) return cached.config;
|
|
20989
21115
|
const headers = { "content-type": "application/json" };
|
|
20990
21116
|
if (cred.token) headers.authorization = `Bearer ${cred.token}`;
|
|
@@ -20992,7 +21118,7 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
20992
21118
|
try {
|
|
20993
21119
|
const sources2 = discoverSessionSources();
|
|
20994
21120
|
if (sources2.length > 0) {
|
|
20995
|
-
await fetchWithTimeout(`${
|
|
21121
|
+
await fetchWithTimeout(`${base2}/api/sync-config/sources`, {
|
|
20996
21122
|
method: "POST",
|
|
20997
21123
|
headers,
|
|
20998
21124
|
body: JSON.stringify({ device: deviceLabel(), sources: sources2 })
|
|
@@ -21000,12 +21126,12 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
21000
21126
|
}
|
|
21001
21127
|
} catch {
|
|
21002
21128
|
}
|
|
21003
|
-
const res = await fetchWithTimeout(`${
|
|
21129
|
+
const res = await fetchWithTimeout(`${base2}/api/sync-config`, { headers });
|
|
21004
21130
|
if (res.status === 404) {
|
|
21005
|
-
_syncConfigCache.set(
|
|
21131
|
+
_syncConfigCache.set(base2, { fetchedAt: Date.now(), config: EMPTY_SYNC_CONFIG });
|
|
21006
21132
|
return EMPTY_SYNC_CONFIG;
|
|
21007
21133
|
}
|
|
21008
|
-
if (!res.ok) return lastKnownSyncConfig(
|
|
21134
|
+
if (!res.ok) return lastKnownSyncConfig(base2, `HTTP ${res.status}`);
|
|
21009
21135
|
const body2 = await res.json().catch(() => ({}));
|
|
21010
21136
|
const config2 = {
|
|
21011
21137
|
excludeTools: Array.isArray(body2.excludeTools) ? body2.excludeTools.filter((t) => typeof t === "string") : [],
|
|
@@ -21042,22 +21168,22 @@ async function fetchTenantSyncConfig(cred) {
|
|
|
21042
21168
|
if (decided > 0) console.error(`[sync] applied ${decided} folder decision(s) from the dashboard`);
|
|
21043
21169
|
} catch {
|
|
21044
21170
|
}
|
|
21045
|
-
_syncConfigCache.set(
|
|
21046
|
-
persistSyncConfig(
|
|
21171
|
+
_syncConfigCache.set(base2, { fetchedAt: Date.now(), config: config2 });
|
|
21172
|
+
persistSyncConfig(base2, config2);
|
|
21047
21173
|
return config2;
|
|
21048
21174
|
} catch (err) {
|
|
21049
21175
|
if (isSyncRulesRefusal(err)) throw err;
|
|
21050
|
-
return lastKnownSyncConfig(
|
|
21176
|
+
return lastKnownSyncConfig(base2, err instanceof Error ? err.message : "request failed");
|
|
21051
21177
|
}
|
|
21052
21178
|
}
|
|
21053
|
-
function lastKnownSyncConfig(
|
|
21054
|
-
const remembered = _syncConfigCache.get(
|
|
21179
|
+
function lastKnownSyncConfig(base2, reason) {
|
|
21180
|
+
const remembered = _syncConfigCache.get(base2) ?? readPersistedSyncConfigs()[base2];
|
|
21055
21181
|
if (remembered) {
|
|
21056
21182
|
console.error(`[sync] could not read server-side sync rules (${reason}) \u2014 applying the last known set from ${new Date(remembered.fetchedAt).toISOString()}`);
|
|
21057
21183
|
return remembered.config;
|
|
21058
21184
|
}
|
|
21059
21185
|
const refusal = new Error(
|
|
21060
|
-
`Cannot read the server-side sync rules from ${
|
|
21186
|
+
`Cannot read the server-side sync rules from ${base2} (${reason}), and this machine has never successfully read them. Refusing to sync: uploading without knowing your exclusions could ship a project you told the dashboard to hold back. Fix the server or retry.`
|
|
21061
21187
|
);
|
|
21062
21188
|
refusal[SYNC_RULES_REFUSAL] = true;
|
|
21063
21189
|
throw refusal;
|
|
@@ -21178,25 +21304,25 @@ async function syncSessions(opts = {}) {
|
|
|
21178
21304
|
${errors.join("\n ")}`);
|
|
21179
21305
|
return { ...agg, perTarget };
|
|
21180
21306
|
}
|
|
21181
|
-
async function postFieldBatch(
|
|
21307
|
+
async function postFieldBatch(base2, token, rows) {
|
|
21182
21308
|
const headers = { "content-type": "application/json" };
|
|
21183
21309
|
if (token) headers.authorization = `Bearer ${token}`;
|
|
21184
21310
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
21185
21311
|
for (let attempt = 0; ; attempt++) {
|
|
21186
|
-
const res = await fetchWithTimeout(`${
|
|
21312
|
+
const res = await fetchWithTimeout(`${base2}/api/sync`, { method: "POST", headers, body: JSON.stringify({ fields: rows }) }, 6e4);
|
|
21187
21313
|
if (res.ok) return;
|
|
21188
21314
|
const retryable = res.status === 429 || res.status >= 500;
|
|
21189
|
-
if (!retryable || attempt >= 5) throw new Error(`${
|
|
21315
|
+
if (!retryable || attempt >= 5) throw new Error(`${base2}: fields batch HTTP ${res.status} ${await res.text().catch(() => "")}`);
|
|
21190
21316
|
const ra = Number(res.headers.get("retry-after")) || 0;
|
|
21191
21317
|
await sleep(ra > 0 ? ra * 1e3 : [500, 1500, 4e3, 8e3, 15e3][Math.min(attempt, 4)]);
|
|
21192
21318
|
}
|
|
21193
21319
|
}
|
|
21194
21320
|
async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
|
|
21195
|
-
const
|
|
21196
|
-
const ledger = getSyncedRows(
|
|
21321
|
+
const base2 = cred.serverUrl.replace(/\/+$/, "");
|
|
21322
|
+
const ledger = getSyncedRows(base2);
|
|
21197
21323
|
let pushed = 0, absent = 0, scanned = 0;
|
|
21198
21324
|
for (const field of SYNC_FIELDS) {
|
|
21199
|
-
const needFull = !!opts.force || fieldNeedsFullPass(
|
|
21325
|
+
const needFull = !!opts.force || fieldNeedsFullPass(base2, field);
|
|
21200
21326
|
const candidates2 = needFull ? fullRefs() : opts.convRefs ?? fullRefs();
|
|
21201
21327
|
const send = [];
|
|
21202
21328
|
const mtimeById = /* @__PURE__ */ new Map();
|
|
@@ -21212,15 +21338,15 @@ async function reconcileFieldsForTarget(cred, fullRefs, opts = {}) {
|
|
|
21212
21338
|
}
|
|
21213
21339
|
for (let i = 0; i < send.length; i += 200) {
|
|
21214
21340
|
const chunk = send.slice(i, i + 200);
|
|
21215
|
-
await postFieldBatch(
|
|
21216
|
-
markFieldCoverage(
|
|
21341
|
+
await postFieldBatch(base2, cred.token, chunk);
|
|
21342
|
+
markFieldCoverage(base2, field, chunk.map((r) => ({ id: r.session_id, present: true, mtime: mtimeById.get(r.session_id) ?? 0 })));
|
|
21217
21343
|
pushed += chunk.length;
|
|
21218
21344
|
}
|
|
21219
21345
|
if (absentIds.length) {
|
|
21220
|
-
markFieldCoverage(
|
|
21346
|
+
markFieldCoverage(base2, field, absentIds.map((id) => ({ id, present: false, mtime: mtimeById.get(id) ?? 0 })));
|
|
21221
21347
|
absent += absentIds.length;
|
|
21222
21348
|
}
|
|
21223
|
-
if (needFull) markFieldFullPassDone(
|
|
21349
|
+
if (needFull) markFieldFullPassDone(base2, field);
|
|
21224
21350
|
}
|
|
21225
21351
|
return { pushed, absent, scanned };
|
|
21226
21352
|
}
|
|
@@ -21258,7 +21384,7 @@ function reapStaleScanDirs(dir = tmpdir4(), maxAgeMs = 6 * 36e5, now = Date.now(
|
|
|
21258
21384
|
if (!name.startsWith("cr-batchscan-")) continue;
|
|
21259
21385
|
const p = join49(dir, name);
|
|
21260
21386
|
try {
|
|
21261
|
-
if (now -
|
|
21387
|
+
if (now - statSync43(p).mtimeMs < maxAgeMs) continue;
|
|
21262
21388
|
rmSync6(p, { recursive: true, force: true });
|
|
21263
21389
|
reaped++;
|
|
21264
21390
|
} catch {
|
|
@@ -21337,25 +21463,25 @@ function serverIngestConcurrency(serverUrl) {
|
|
|
21337
21463
|
return advertisedIngestConc.get(serverUrl.replace(/\/$/, "")) ?? null;
|
|
21338
21464
|
}
|
|
21339
21465
|
async function verifyServerApi(serverUrl, minApi) {
|
|
21340
|
-
const
|
|
21341
|
-
const seen = capsOk.get(
|
|
21466
|
+
const base2 = serverUrl.replace(/\/$/, "");
|
|
21467
|
+
const seen = capsOk.get(base2);
|
|
21342
21468
|
if (seen !== void 0 && Date.now() - seen < CAPS_TTL_MS) return true;
|
|
21343
21469
|
const { probeServer: probeServer2, probeAdvice: probeAdvice2 } = await Promise.resolve().then(() => (init_server_probe(), server_probe_exports));
|
|
21344
|
-
const probe = await probeServer2(
|
|
21470
|
+
const probe = await probeServer2(base2, {
|
|
21345
21471
|
fetchImpl: ((url, init) => fetchWithTimeout(String(url), init ?? {}))
|
|
21346
21472
|
});
|
|
21347
|
-
if (probe.kind !== "ok") throw new Error(probeAdvice2(probe,
|
|
21473
|
+
if (probe.kind !== "ok") throw new Error(probeAdvice2(probe, base2));
|
|
21348
21474
|
const caps = probe.raw;
|
|
21349
21475
|
if ((caps.apiVersion ?? 0) < minApi) {
|
|
21350
21476
|
throw new Error(`server too old: apiVersion ${caps.apiVersion ?? "none"} < required ${minApi} \u2014 update the server image before syncing`);
|
|
21351
21477
|
}
|
|
21352
21478
|
const adv = caps.limits?.ingestConcurrencyPerTenant;
|
|
21353
21479
|
if (typeof adv === "number" && Number.isFinite(adv) && adv >= 1) {
|
|
21354
|
-
advertisedIngestConc.set(
|
|
21480
|
+
advertisedIngestConc.set(base2, Math.floor(adv));
|
|
21355
21481
|
}
|
|
21356
|
-
if (caps.limits?.acceptsGzipBody === true) advertisedGzipBody.add(
|
|
21357
|
-
else advertisedGzipBody.delete(
|
|
21358
|
-
capsOk.set(
|
|
21482
|
+
if (caps.limits?.acceptsGzipBody === true) advertisedGzipBody.add(base2);
|
|
21483
|
+
else advertisedGzipBody.delete(base2);
|
|
21484
|
+
capsOk.set(base2, Date.now());
|
|
21359
21485
|
return true;
|
|
21360
21486
|
}
|
|
21361
21487
|
async function syncToTarget(cred, opts = {}) {
|
|
@@ -21420,7 +21546,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21420
21546
|
const ac = new AbortController();
|
|
21421
21547
|
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
21422
21548
|
try {
|
|
21423
|
-
const res = await fetch(`${
|
|
21549
|
+
const res = await fetch(`${base2}/api/sync`, {
|
|
21424
21550
|
method: "POST",
|
|
21425
21551
|
// No token ⇒ no auth header: a no-auth local server (AUTH_PROVIDER=none)
|
|
21426
21552
|
// accepts it as the single 'default' tenant.
|
|
@@ -21436,26 +21562,26 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21436
21562
|
acceptedUploads.set(cred.serverUrl, (acceptedUploads.get(cred.serverUrl) ?? 0) + 1);
|
|
21437
21563
|
const body3 = await res.json().catch(() => ({}));
|
|
21438
21564
|
if (typeof body3.telemetry === "boolean") {
|
|
21439
|
-
setTelemetryEligible(
|
|
21565
|
+
setTelemetryEligible(base2, body3.telemetry);
|
|
21440
21566
|
try {
|
|
21441
21567
|
const prior = readCollectorHealth()?.telemetryEligible ?? {};
|
|
21442
21568
|
updateCollectorHealth({
|
|
21443
|
-
telemetryEligible: { ...prior, [
|
|
21569
|
+
telemetryEligible: { ...prior, [base2]: { allowed: body3.telemetry, at: Date.now() } }
|
|
21444
21570
|
});
|
|
21445
21571
|
} catch {
|
|
21446
21572
|
}
|
|
21447
21573
|
}
|
|
21448
21574
|
const adv = body3.limits?.ingestConcurrencyPerTenant;
|
|
21449
21575
|
if (typeof adv === "number" && Number.isFinite(adv) && adv >= 1) {
|
|
21450
|
-
setServerIngestConcurrency(
|
|
21576
|
+
setServerIngestConcurrency(base2, Math.floor(adv));
|
|
21451
21577
|
}
|
|
21452
21578
|
if (body3.cli && body3.cli.version) {
|
|
21453
21579
|
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21454
21580
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21455
|
-
const plan = planAutoUpdate2(
|
|
21581
|
+
const plan = planAutoUpdate2(base2, { cli: body3.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21456
21582
|
sweepStaleStaging2();
|
|
21457
21583
|
if (plan.update) {
|
|
21458
|
-
void runAutoUpdate2(
|
|
21584
|
+
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21459
21585
|
});
|
|
21460
21586
|
}
|
|
21461
21587
|
}
|
|
@@ -21464,7 +21590,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21464
21590
|
const retryable = res.status === 429 || res.status >= 500;
|
|
21465
21591
|
const text = await res.text().catch(() => "");
|
|
21466
21592
|
if (res.status === 401 || res.status === 403) {
|
|
21467
|
-
throw classedError("auth", `server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${
|
|
21593
|
+
throw classedError("auth", `server rejected this machine's device token (HTTP ${res.status} \u2014 revoked?). Reconnect with: chat-recall login ${base2}`);
|
|
21468
21594
|
}
|
|
21469
21595
|
if (res.status === 402) {
|
|
21470
21596
|
let detail = text;
|
|
@@ -21485,7 +21611,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21485
21611
|
const retryAfterS = Number(res.headers.get("retry-after"));
|
|
21486
21612
|
const waitMs = res.status === 429 && retryAfterS > 0 ? retryAfterS * 1e3 : RETRY_DELAYS_MS[attempt];
|
|
21487
21613
|
const why = res.status === 524 ? ` \u2014 the proxy cut the request at its time limit; the batch is too big for one request` : "";
|
|
21488
|
-
console.error(`[sync] HTTP ${res.status} from ${
|
|
21614
|
+
console.error(`[sync] HTTP ${res.status} from ${base2}${why} \u2014 backing off ${Math.round(waitMs / 1e3)}s (attempt ${attempt + 1})`);
|
|
21489
21615
|
await sleep(waitMs);
|
|
21490
21616
|
continue;
|
|
21491
21617
|
} catch (err) {
|
|
@@ -21553,11 +21679,11 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21553
21679
|
}
|
|
21554
21680
|
};
|
|
21555
21681
|
};
|
|
21556
|
-
const
|
|
21557
|
-
const ledger = opts.useLedger ? getSyncedRows(
|
|
21682
|
+
const base2 = cred.serverUrl.replace(/\/$/, "");
|
|
21683
|
+
const ledger = opts.useLedger ? getSyncedRows(base2) : null;
|
|
21558
21684
|
const convBatch = makeBatcher("conversations", 25, false, (rows) => {
|
|
21559
21685
|
try {
|
|
21560
|
-
markSynced(
|
|
21686
|
+
markSynced(base2, rows.map((r) => {
|
|
21561
21687
|
const off = typeof r.from_offset === "number" ? r.from_offset : 0;
|
|
21562
21688
|
const hash = shippedHash.get(r.session_id);
|
|
21563
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 };
|
|
@@ -21572,7 +21698,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21572
21698
|
const ac = new AbortController();
|
|
21573
21699
|
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
21574
21700
|
try {
|
|
21575
|
-
const res = await fetch(`${
|
|
21701
|
+
const res = await fetch(`${base2}/api/sync`, {
|
|
21576
21702
|
method: "POST",
|
|
21577
21703
|
headers,
|
|
21578
21704
|
body: JSON.stringify({ conversations: convs }),
|
|
@@ -21583,10 +21709,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21583
21709
|
if (body2.cli && body2.cli.version) {
|
|
21584
21710
|
const { planAutoUpdate: planAutoUpdate2, runAutoUpdate: runAutoUpdate2, sweepStaleStaging: sweepStaleStaging2 } = await Promise.resolve().then(() => (init_auto_update(), auto_update_exports));
|
|
21585
21711
|
const authHeaders = cred.token ? { authorization: `Bearer ${cred.token}` } : {};
|
|
21586
|
-
const plan = planAutoUpdate2(
|
|
21712
|
+
const plan = planAutoUpdate2(base2, { cli: body2.cli }, ownVersion, process.env.CHAT_RECALL_AUTO_UPDATE);
|
|
21587
21713
|
sweepStaleStaging2();
|
|
21588
21714
|
if (plan.update) {
|
|
21589
|
-
void runAutoUpdate2(
|
|
21715
|
+
void runAutoUpdate2(base2, authHeaders, ownVersion).catch(() => {
|
|
21590
21716
|
});
|
|
21591
21717
|
}
|
|
21592
21718
|
}
|
|
@@ -21594,7 +21720,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21594
21720
|
const acked = convs.filter((c) => !fullNeeded.has(c.session_id));
|
|
21595
21721
|
if (acked.length > 0) {
|
|
21596
21722
|
try {
|
|
21597
|
-
markSynced(
|
|
21723
|
+
markSynced(base2, acked.map((c) => ({
|
|
21598
21724
|
id: c.session_id,
|
|
21599
21725
|
mtime: c.mtime,
|
|
21600
21726
|
offset: c.from_offset,
|
|
@@ -21612,13 +21738,14 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21612
21738
|
clearTimeout(timer);
|
|
21613
21739
|
}
|
|
21614
21740
|
};
|
|
21615
|
-
const postChunkedHead = async (
|
|
21741
|
+
const postChunkedHead = async (head, fileSize) => {
|
|
21742
|
+
const conv = { ...head, chunk_head: true };
|
|
21616
21743
|
const headers = { "content-type": "application/json" };
|
|
21617
21744
|
if (cred.token) headers.authorization = `Bearer ${cred.token}`;
|
|
21618
21745
|
const ac = new AbortController();
|
|
21619
21746
|
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
21620
21747
|
try {
|
|
21621
|
-
const res = await fetch(`${
|
|
21748
|
+
const res = await fetch(`${base2}/api/sync`, {
|
|
21622
21749
|
method: "POST",
|
|
21623
21750
|
headers,
|
|
21624
21751
|
body: JSON.stringify({ conversations: [conv] }),
|
|
@@ -21628,14 +21755,15 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21628
21755
|
const body2 = await res.json().catch(() => ({}));
|
|
21629
21756
|
const id = conv.session_id;
|
|
21630
21757
|
const guarded = (body2.shrink_guarded ?? []).find((g) => g.session_id === id);
|
|
21758
|
+
const staged = (body2.rebuild_staged ?? []).includes(id);
|
|
21631
21759
|
const mtime = conv.mtime;
|
|
21632
21760
|
try {
|
|
21633
|
-
if (!guarded) {
|
|
21634
|
-
markSynced(
|
|
21761
|
+
if (staged || !guarded) {
|
|
21762
|
+
markSynced(base2, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
|
|
21635
21763
|
} else if (typeof guarded.o === "number" && guarded.o > 0 && guarded.o <= fileSize) {
|
|
21636
|
-
markSynced(
|
|
21764
|
+
markSynced(base2, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
|
|
21637
21765
|
} else {
|
|
21638
|
-
markSynced(
|
|
21766
|
+
markSynced(base2, [{ id, mtime }]);
|
|
21639
21767
|
}
|
|
21640
21768
|
} catch {
|
|
21641
21769
|
}
|
|
@@ -21701,7 +21829,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21701
21829
|
const m = process.memoryUsage();
|
|
21702
21830
|
console.error(`[sync-trace] ${msg} \xB7 heap=${(m.heapUsed / 1048576).toFixed(0)}MB rss=${(m.rss / 1048576).toFixed(0)}MB`);
|
|
21703
21831
|
} : null;
|
|
21704
|
-
trace?.(`walk start: ${slice.length} ref(s) \u2192 ${
|
|
21832
|
+
trace?.(`walk start: ${slice.length} ref(s) \u2192 ${base2}`);
|
|
21705
21833
|
if (upload.sessionMeta) {
|
|
21706
21834
|
try {
|
|
21707
21835
|
await reconcileFieldsForTarget(
|
|
@@ -21716,7 +21844,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21716
21844
|
{ convRefs: slice }
|
|
21717
21845
|
);
|
|
21718
21846
|
} catch (e) {
|
|
21719
|
-
console.error(`[sync] field reconcile (${
|
|
21847
|
+
console.error(`[sync] field reconcile (${base2}): ${e instanceof Error ? e.message : e}`);
|
|
21720
21848
|
}
|
|
21721
21849
|
}
|
|
21722
21850
|
trace?.("field reconcile done");
|
|
@@ -21758,7 +21886,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21758
21886
|
}
|
|
21759
21887
|
trace?.("conversation walk start");
|
|
21760
21888
|
const walkStartedAt = Date.now();
|
|
21761
|
-
const firstWalk = (ledger ?? getSyncedRows(
|
|
21889
|
+
const firstWalk = (ledger ?? getSyncedRows(base2)).size === 0;
|
|
21762
21890
|
reportWalkProgress({ done: 0, total: slice.length, startedAt: walkStartedAt, complete: false, first: firstWalk });
|
|
21763
21891
|
walkProgressForUpload = { done: 0, total: slice.length, complete: false };
|
|
21764
21892
|
let traceN = 0;
|
|
@@ -21798,7 +21926,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21798
21926
|
continue;
|
|
21799
21927
|
} else {
|
|
21800
21928
|
try {
|
|
21801
|
-
markSynced(
|
|
21929
|
+
markSynced(base2, [{ id: ref.prefixedId, mtime: ref.mtime }]);
|
|
21802
21930
|
} catch {
|
|
21803
21931
|
}
|
|
21804
21932
|
skipped++;
|
|
@@ -21821,7 +21949,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21821
21949
|
const off = getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId) ?? 0;
|
|
21822
21950
|
const priorCursor = ackRow?.s ?? 0;
|
|
21823
21951
|
const safeToAck = off > 0 && off <= priorCursor;
|
|
21824
|
-
markSynced(
|
|
21952
|
+
markSynced(base2, [safeToAck ? { id: ref.prefixedId, mtime: ref.mtime, offset: off, size: off, hash: built.srcHash, acked: true } : { id: ref.prefixedId, mtime: ref.mtime, hash: built.srcHash }]);
|
|
21825
21953
|
} catch {
|
|
21826
21954
|
}
|
|
21827
21955
|
}
|
|
@@ -21831,7 +21959,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21831
21959
|
if (!built) {
|
|
21832
21960
|
if (ledger) {
|
|
21833
21961
|
try {
|
|
21834
|
-
markSynced(
|
|
21962
|
+
markSynced(base2, [{ id: ref.prefixedId, mtime: ref.mtime }]);
|
|
21835
21963
|
} catch {
|
|
21836
21964
|
}
|
|
21837
21965
|
}
|
|
@@ -21888,7 +22016,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21888
22016
|
walked++;
|
|
21889
22017
|
if (walked % 250 === 0) console.error(`[sync] ${walked}/${slice.length} sessions\u2026`);
|
|
21890
22018
|
}
|
|
21891
|
-
const itemVersions = loadItemVersions(
|
|
22019
|
+
const itemVersions = loadItemVersions(base2);
|
|
21892
22020
|
const seenTools = /* @__PURE__ */ new Set();
|
|
21893
22021
|
const seenItemKeys = /* @__PURE__ */ new Map();
|
|
21894
22022
|
const registry = buildSourceRegistry();
|
|
@@ -21982,7 +22110,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21982
22110
|
await appendFlush();
|
|
21983
22111
|
for (const id of appendResults.fullResyncNeeded) {
|
|
21984
22112
|
try {
|
|
21985
|
-
markFullResync(
|
|
22113
|
+
markFullResync(base2, id);
|
|
21986
22114
|
} catch {
|
|
21987
22115
|
}
|
|
21988
22116
|
}
|
|
@@ -21995,7 +22123,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
21995
22123
|
await findingsBatch.drain();
|
|
21996
22124
|
await drainInflight();
|
|
21997
22125
|
{
|
|
21998
|
-
const sentHashes = loadInventoryHashes(
|
|
22126
|
+
const sentHashes = loadInventoryHashes(base2);
|
|
21999
22127
|
const entries = [];
|
|
22000
22128
|
const hashes = {};
|
|
22001
22129
|
for (const [sourceType, ids] of inventory) {
|
|
@@ -22009,7 +22137,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
22009
22137
|
if (entries.length > 0) {
|
|
22010
22138
|
try {
|
|
22011
22139
|
await post({ inventory: entries });
|
|
22012
|
-
saveInventoryHashes(
|
|
22140
|
+
saveInventoryHashes(base2, hashes);
|
|
22013
22141
|
} catch (err) {
|
|
22014
22142
|
console.error(`[sync] toolkit inventory not sent (it is retried next sync): ${err instanceof Error ? err.message : err}`);
|
|
22015
22143
|
}
|
|
@@ -22020,7 +22148,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
22020
22148
|
const rec = {};
|
|
22021
22149
|
for (const t of seenTools) rec[t] = extractorVersionForTool(t);
|
|
22022
22150
|
for (const [k, v] of seenItemKeys) rec[k] = v;
|
|
22023
|
-
saveItemVersions(
|
|
22151
|
+
saveItemVersions(base2, rec);
|
|
22024
22152
|
} catch {
|
|
22025
22153
|
}
|
|
22026
22154
|
}
|
|
@@ -22033,9 +22161,9 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
22033
22161
|
}
|
|
22034
22162
|
if (tombstones.length > 0) {
|
|
22035
22163
|
await post({ tombstones });
|
|
22036
|
-
const data = getLedgerData(
|
|
22164
|
+
const data = getLedgerData(base2);
|
|
22037
22165
|
for (const t of tombstones) delete data[t.session_id];
|
|
22038
|
-
persistLedgerData(
|
|
22166
|
+
persistLedgerData(base2, data);
|
|
22039
22167
|
}
|
|
22040
22168
|
}
|
|
22041
22169
|
if (opts.prune) await post({ prune_empty_sessions: true });
|
|
@@ -22363,8 +22491,13 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
22363
22491
|
const backend = getBackendForId(ref.prefixedId);
|
|
22364
22492
|
if (!backend?.readFromOffset) return null;
|
|
22365
22493
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
22366
|
-
const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
|
|
22494
|
+
const { text: tailText, newOffset, skippedBytes, baseLine } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
|
|
22367
22495
|
if (newOffset <= fromOffset) return null;
|
|
22496
|
+
const fileSize = backend.fileSize?.(ref.prefixedId) ?? 0;
|
|
22497
|
+
const position = {
|
|
22498
|
+
...typeof baseLine === "number" ? { base_line: baseLine } : {},
|
|
22499
|
+
final: fileSize > 0 && newOffset >= fileSize
|
|
22500
|
+
};
|
|
22368
22501
|
const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
|
|
22369
22502
|
const textMessages = messages.filter((m) => m.role !== "summary" && m.content?.trim());
|
|
22370
22503
|
const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
|
|
@@ -22377,6 +22510,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
22377
22510
|
append: true,
|
|
22378
22511
|
base_offset: fromOffset,
|
|
22379
22512
|
from_offset: newOffset,
|
|
22513
|
+
...position,
|
|
22380
22514
|
redacted_text: "",
|
|
22381
22515
|
envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
|
|
22382
22516
|
mtime: Math.floor(ref.mtime) || 0
|
|
@@ -22423,6 +22557,7 @@ async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
22423
22557
|
// equals its stored synced-through offset.
|
|
22424
22558
|
from_offset: newOffset,
|
|
22425
22559
|
// where it ends → new synced-through offset.
|
|
22560
|
+
...position,
|
|
22426
22561
|
redacted_text: redactedText,
|
|
22427
22562
|
envelope,
|
|
22428
22563
|
mtime: Math.floor(ref.mtime) || 0
|
|
@@ -22506,7 +22641,7 @@ var init_sync_client = __esm({
|
|
|
22506
22641
|
ownVersion = JSON.parse(readFileSync38(join49(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
22507
22642
|
} catch {
|
|
22508
22643
|
try {
|
|
22509
|
-
if (true) ownVersion = "0.7.
|
|
22644
|
+
if (true) ownVersion = "0.7.16";
|
|
22510
22645
|
} catch {
|
|
22511
22646
|
}
|
|
22512
22647
|
}
|
|
@@ -22665,9 +22800,9 @@ async function refreshUpdateCheck(force = false) {
|
|
|
22665
22800
|
const { loadAllCredentials: loadAllCredentials2 } = await Promise.resolve().then(() => (init_sync_client(), sync_client_exports));
|
|
22666
22801
|
for (const cred of loadAllCredentials2()) {
|
|
22667
22802
|
if (!cred.serverUrl) continue;
|
|
22668
|
-
const
|
|
22803
|
+
const base2 = cred.serverUrl.replace(/\/+$/, "");
|
|
22669
22804
|
try {
|
|
22670
|
-
const res = await fetchWithTimeout(`${
|
|
22805
|
+
const res = await fetchWithTimeout(`${base2}/api/capabilities`, {
|
|
22671
22806
|
headers: cred.token ? { authorization: `Bearer ${cred.token}` } : {}
|
|
22672
22807
|
}, 5e3);
|
|
22673
22808
|
if (!res.ok) continue;
|
|
@@ -23056,7 +23191,7 @@ __export(install_skills_exports, {
|
|
|
23056
23191
|
skillsSourceDir: () => skillsSourceDir,
|
|
23057
23192
|
uninstallSkills: () => uninstallSkills
|
|
23058
23193
|
});
|
|
23059
|
-
import { readdirSync as readdirSync33, existsSync as existsSync47, mkdirSync as mkdirSync15, rmSync as rmSync8, cpSync, writeFileSync as writeFileSync13, readFileSync as readFileSync42, statSync as
|
|
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";
|
|
23060
23195
|
import { createHash as createHash12 } from "crypto";
|
|
23061
23196
|
import { join as join52, basename as basename23 } from "path";
|
|
23062
23197
|
function skillsSourceDir() {
|
|
@@ -23297,18 +23432,18 @@ function usableDir(dir) {
|
|
|
23297
23432
|
if (!isDir(dir)) {
|
|
23298
23433
|
const missing = [];
|
|
23299
23434
|
let cur = dir;
|
|
23300
|
-
let
|
|
23435
|
+
let base2 = false;
|
|
23301
23436
|
while (missing.length < MAX_MISSING_LEVELS) {
|
|
23302
23437
|
const parent = dirname(cur);
|
|
23303
23438
|
if (parent === cur) break;
|
|
23304
23439
|
missing.push(cur);
|
|
23305
23440
|
if (isDir(parent)) {
|
|
23306
|
-
|
|
23441
|
+
base2 = true;
|
|
23307
23442
|
break;
|
|
23308
23443
|
}
|
|
23309
23444
|
cur = parent;
|
|
23310
23445
|
}
|
|
23311
|
-
if (!
|
|
23446
|
+
if (!base2) return false;
|
|
23312
23447
|
for (let i = missing.length - 1; i >= 0; i--) {
|
|
23313
23448
|
try {
|
|
23314
23449
|
mkdirSync(missing[i]);
|
|
@@ -29497,7 +29632,7 @@ function installCrashGuards() {
|
|
|
29497
29632
|
reportClientEvent2("mcp_crash", { message: err.stack || err.message });
|
|
29498
29633
|
});
|
|
29499
29634
|
}
|
|
29500
|
-
var RELAY_VERSION = true ? "0.7.
|
|
29635
|
+
var RELAY_VERSION = true ? "0.7.16" : "0.0.0";
|
|
29501
29636
|
function daemonIdleSecs() {
|
|
29502
29637
|
const raw = process.env.CHAT_RECALL_DAEMON_IDLE_SECS;
|
|
29503
29638
|
if (raw === void 0) return 900;
|