chat-recall 0.6.9 → 0.7.1
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/README.md +2 -2
- package/dist/auto-update.d.ts +34 -0
- package/dist/auto-update.d.ts.map +1 -1
- package/dist/auto-update.js +51 -8
- package/dist/auto-update.js.map +1 -1
- package/dist/cli.js +1448 -1661
- package/dist/cli.js.map +1 -1
- package/dist/escalate.js +2 -2
- package/dist/escalate.js.map +1 -1
- package/dist/mcp-relay.js +76 -13
- package/dist/mcp-relay.js.map +1 -1
- package/dist/mcp.js +2573 -2223
- package/dist/mcp.js.map +1 -1
- package/dist/relay-lifecycle.d.ts +69 -0
- package/dist/relay-lifecycle.d.ts.map +1 -0
- package/dist/relay-lifecycle.js +117 -0
- package/dist/relay-lifecycle.js.map +1 -0
- package/dist/repair.d.ts +2 -0
- package/dist/repair.d.ts.map +1 -1
- package/dist/repair.js +23 -3
- package/dist/repair.js.map +1 -1
- package/dist/scan-worker.js +48 -18
- package/dist/service-installer.d.ts.map +1 -1
- package/dist/service-installer.js +7 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/sync-client.d.ts +38 -5
- package/dist/sync-client.d.ts.map +1 -1
- package/dist/sync-client.js +191 -58
- package/dist/sync-client.js.map +1 -1
- package/dist/sync-supervisor.d.ts +43 -0
- package/dist/sync-supervisor.d.ts.map +1 -0
- package/dist/sync-supervisor.js +84 -0
- package/dist/sync-supervisor.js.map +1 -0
- package/dist/watch.js +753 -998
- package/package.json +1 -1
- package/skills/chat-recall/SKILL.md +2 -1
- package/skills/chat-recall-memory/SKILL.md +15 -0
package/dist/sync-client.d.ts
CHANGED
|
@@ -229,12 +229,36 @@ export interface UnchangedConversation {
|
|
|
229
229
|
unchanged: true;
|
|
230
230
|
srcHash: string;
|
|
231
231
|
}
|
|
232
|
-
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
232
|
+
/** Build bytes (see sessionBuildBytes) beyond which a FULL sync must not
|
|
233
|
+
* materialize the session. A larger session ships in chunks instead: the head
|
|
234
|
+
* chunk as its FULL sync, then the rest as appends.
|
|
235
|
+
*
|
|
236
|
+
* The in-memory build holds the container many times over (export, container,
|
|
237
|
+
* shadow merge, parse, redacted envelope, joined raw text, and a copy in the
|
|
238
|
+
* scan worker). Measured on a 42 MB container: 840 MB peak RSS in full, 218 MB
|
|
239
|
+
* in chunks, for the same 2,392 messages, under a unit with MemoryHigh=1G and
|
|
240
|
+
* a resting RSS near 400 MB. At about 18 bytes per container byte, 24 MB
|
|
241
|
+
* stays under the limit. */
|
|
237
242
|
export declare const FULL_BUILD_MAX_BYTES: number;
|
|
243
|
+
/** Transcript bytes beyond which the derived computations (diff, outcome,
|
|
244
|
+
* commits, markers) are skipped. They run in the scan worker and read the
|
|
245
|
+
* whole transcript; a 353 MB session with 100 MB JSON lines OOMed the walk. */
|
|
246
|
+
export declare const DERIVED_MAX_BYTES: number;
|
|
247
|
+
/** Bytes of transcript one chunk of a chunked sync carries: the head of an
|
|
248
|
+
* oversized FULL sync, and each append. */
|
|
249
|
+
export declare const SYNC_CHUNK_BYTES: number;
|
|
250
|
+
/**
|
|
251
|
+
* Secret findings for a whole transcript, read one chunk at a time.
|
|
252
|
+
*
|
|
253
|
+
* A chunked session never holds its full text, so it cannot use the one-pass
|
|
254
|
+
* scan of the in-memory build. Line numbers are absolute: each chunk's
|
|
255
|
+
* findings are shifted by the lines of the chunks before it. A line longer
|
|
256
|
+
* than a chunk is skipped by the reader, and it counts as one line.
|
|
257
|
+
*/
|
|
258
|
+
export declare function scanTranscriptFindingsChunked(ref: SessionRef, tenantRules?: Array<{
|
|
259
|
+
name: string;
|
|
260
|
+
regex: string;
|
|
261
|
+
}>): Promise<BuiltConversation['findings']>;
|
|
238
262
|
/** Ceiling on PRE-REDACTION session text materialized on disk at any one moment
|
|
239
263
|
* by the optional external-detector batch scan. The batch dir is flushed and
|
|
240
264
|
* deleted every time it crosses this, so a SIGKILL/OOM mid-scan can strand at
|
|
@@ -244,6 +268,14 @@ export declare const FULL_BUILD_MAX_BYTES: number;
|
|
|
244
268
|
export declare const BATCH_SCAN_MAX_BYTES: number;
|
|
245
269
|
/** Bytes an append-only session occupies on disk (0 for non-AO backends). */
|
|
246
270
|
export declare function sessionFileBytes(ref: SessionRef): number;
|
|
271
|
+
/**
|
|
272
|
+
* Bytes a FULL build of an append-only session loads (0 for non-AO backends):
|
|
273
|
+
* the larger of the main transcript and the shadow container, which also holds
|
|
274
|
+
* the subagent transcripts and any history an upstream rewrite dropped. The
|
|
275
|
+
* main file alone under-counts: a 19 MB transcript built from a 42 MB shadow
|
|
276
|
+
* and peaked at 840 MB RSS.
|
|
277
|
+
*/
|
|
278
|
+
export declare function sessionBuildBytes(ref: SessionRef): number;
|
|
247
279
|
export declare function buildConversationSync(ref: SessionRef, mtime: number, opts?: {
|
|
248
280
|
mapPath?: (p: string) => string;
|
|
249
281
|
includeRaw?: boolean;
|
|
@@ -288,6 +320,7 @@ export declare function buildConversationTail(ref: SessionRef, fromOffset: numbe
|
|
|
288
320
|
redactions: number;
|
|
289
321
|
newOffset: number;
|
|
290
322
|
findings: BuiltConversation['findings'];
|
|
323
|
+
bytes: number;
|
|
291
324
|
} | null>;
|
|
292
325
|
/**
|
|
293
326
|
* Per-session derived payload: the four compute kinds the server's deep-dive
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../src/sync-client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../src/sync-client.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAoF,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,4CAA4C,CAAC;AAUrL,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,0CAA0C,CAAC;AA2ExG,yDAAyD;AACzD,wBAAgB,YAAY,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAI7E;AAgBD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGvD;AAED,MAAM,MAAM,YAAY,GACpB,cAAc,GAAG,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GACpE,KAAK,GAAG,SAAS,GAAG,oBAAoB,GAAG,cAAc,GAAG,OAAO,CAAC;AAExE;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAG,YAAY,CAqBtE;AAOD,OAAO,4CAA4C,CAAC;AAMpD,MAAM,WAAW,WAAW;IAAG,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;CAAE;AAElE;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAoBpD;AAED,wBAAgB,kBAAkB,IAAI,WAAW,EAAE,CAelD;AAwGD,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD;AAuBD,UAAU,gBAAgB;IAAG,YAAY,EAAE,MAAM,EAAE,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE;AAkCpI,iBAAe,qBAAqB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2EjF;AAED;;4EAE4E;AAC5E,eAAO,MAAM,8BAA8B,8BAAwB,CAAC;AAuCpE,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAED,oDAAoD;AACpD,wEAAwE;AACxE,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAEpD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAM5D;AAMD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC3C;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAA0C;AAyB/F,wBAAsB,YAAY,CAAC,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CA+GvN;AAED,MAAM,WAAW,eAAe;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;AA+ErK;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAc9F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAiB,EAAE,QAAQ,SAAe,EAAE,GAAG,SAAa,GAAG,MAAM,CAc3G;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EACvD,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,cAAc,EAAE,CAAC;CAClE,GACL;IAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAwD3F;AAgBD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAE7E;AAED,0EAA0E;AAC1E,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExE;AA2tCD;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB;qGACiG;IACjG,QAAQ,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACpI,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf;;;qFAGiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;0EAE0E;AAC1E,MAAM,WAAW,qBAAqB;IAAG,SAAS,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;CAAE;AAE5E;;;;;;;;;6BAS6B;AAC7B,eAAO,MAAM,oBAAoB,QAC2D,CAAC;AAC7F;;gFAEgF;AAChF,eAAO,MAAM,iBAAiB,QAC2D,CAAC;AAC1F;4CAC4C;AAC5C,eAAO,MAAM,gBAAgB,QAAkB,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,UAAU,EACf,WAAW,CAAC,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAwBxC;AAED;;;;;gEAKgE;AAChE,eAAO,MAAM,oBAAoB,QAC0D,CAAC;AAE5F,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAIxD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAIzD;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzL;;kFAE8E;IAC9E,mBAAmB,CAAC,EAAE,WAAW,EAAE,CAAC;IACpC;;;;0EAIsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,OAAO,CAAC,iBAAiB,GAAG,qBAAqB,GAAG,IAAI,CAAC,CA4W3D;AAeD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAO,GACnG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAgFlJ;AAED;;;;;;GAMG;AACH;;;;;;;;GAQG;AACH;;;;yCAIyC;AACzC,MAAM,WAAW,QAAQ;IAAG,OAAO,EAAE,gBAAgB,GAAG,QAAQ,GAAG,WAAW,CAAA;CAAE;AAEhF,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,CAAC,IAAI,QAAQ,CAElE;AAED,wBAAsB,eAAe,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAAG,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC,CA4C/G"}
|
package/dist/sync-client.js
CHANGED
|
@@ -45,7 +45,7 @@ import { dropFuzzyFindings } from '@chat-recall/engine/core/secret-precision.js'
|
|
|
45
45
|
import { loadSettings, saveSettings, isProjectSyncable } from '@chat-recall/engine/core/settings.js';
|
|
46
46
|
import { getDataDir } from '@chat-recall/engine/core/paths.js';
|
|
47
47
|
import { listAvailableBackends } from '@chat-recall/engine/core/tool-backend.js';
|
|
48
|
-
import { parseTranscript, trimTranscriptForSync, TRANSCRIPT_VERSION, gzipContainer, mapContainerText, buildRawContainer, containerSrcHash, parseTranscriptFromContainer, updateShadow } from '@chat-recall/engine/transcript/index.js';
|
|
48
|
+
import { parseTranscript, trimTranscriptForSync, TRANSCRIPT_VERSION, gzipContainer, mapContainerText, buildRawContainer, containerSrcHash, parseTranscriptFromContainer, updateShadow, shadowUncompressedBytes } from '@chat-recall/engine/transcript/index.js';
|
|
49
49
|
import { parseClaudeTranscriptText } from '@chat-recall/engine/transcript/claude.js';
|
|
50
50
|
import { parseCodexTranscriptText } from '@chat-recall/engine/transcript/codex.js';
|
|
51
51
|
import { getBackendForId, getBackend } from '@chat-recall/engine/core/tool-backend.js';
|
|
@@ -1466,17 +1466,68 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
1466
1466
|
clearTimeout(timer);
|
|
1467
1467
|
}
|
|
1468
1468
|
};
|
|
1469
|
+
// ── Chunked head (oversized FULL) ────────────────────────────────
|
|
1470
|
+
// The head chunk of an oversized session ships alone, and its response is
|
|
1471
|
+
// read. When the server already holds a fuller copy, its shrink guard keeps
|
|
1472
|
+
// that copy and reports the offset it is synced through. The ledger then
|
|
1473
|
+
// records that offset, so the next tick appends from where the server is.
|
|
1474
|
+
// Recording the head's end instead makes every append miss the server's
|
|
1475
|
+
// offset, answer full_resync_needed, and send the head again, forever.
|
|
1476
|
+
const postChunkedHead = async (conv, fileSize) => {
|
|
1477
|
+
const headers = { 'content-type': 'application/json' };
|
|
1478
|
+
if (cred.token)
|
|
1479
|
+
headers.authorization = `Bearer ${cred.token}`;
|
|
1480
|
+
const ac = new AbortController();
|
|
1481
|
+
const timer = setTimeout(() => ac.abort(new Error(`upload timed out after ${UPLOAD_TIMEOUT_MS}ms`)), UPLOAD_TIMEOUT_MS);
|
|
1482
|
+
try {
|
|
1483
|
+
const res = await fetch(`${base}/api/sync`, {
|
|
1484
|
+
method: 'POST', headers, body: JSON.stringify({ conversations: [conv] }), signal: ac.signal,
|
|
1485
|
+
});
|
|
1486
|
+
if (!res.ok)
|
|
1487
|
+
throw new Error(`chunked head sync failed: HTTP ${res.status} ${await res.text().catch(() => '')}`);
|
|
1488
|
+
const body = await res.json().catch(() => ({}));
|
|
1489
|
+
const id = conv.session_id;
|
|
1490
|
+
const guarded = (body.shrink_guarded ?? []).find((g) => g.session_id === id);
|
|
1491
|
+
const mtime = conv.mtime;
|
|
1492
|
+
try {
|
|
1493
|
+
if (!guarded) {
|
|
1494
|
+
markSynced(base, [{ id, mtime, offset: conv.from_offset, size: conv.from_offset, acked: true }]);
|
|
1495
|
+
}
|
|
1496
|
+
else if (typeof guarded.o === 'number' && guarded.o > 0 && guarded.o <= fileSize) {
|
|
1497
|
+
markSynced(base, [{ id, mtime, offset: guarded.o, size: guarded.o, acked: true }]);
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
// The server holds more than this file has: the file was truncated in
|
|
1501
|
+
// place. The server keeps the fuller copy; stop here at this mtime.
|
|
1502
|
+
markSynced(base, [{ id, mtime }]);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
catch { /* ledger — never fail an upload over it */ }
|
|
1506
|
+
appendResults.uploaded++;
|
|
1507
|
+
}
|
|
1508
|
+
finally {
|
|
1509
|
+
clearTimeout(timer);
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1469
1512
|
let appendBatch = [];
|
|
1513
|
+
let appendBatchBytes = 0;
|
|
1470
1514
|
const APPEND_BATCH_SIZE = 50;
|
|
1515
|
+
// Chunks of a chunked session are up to SYNC_CHUNK_BYTES each, so the batch
|
|
1516
|
+
// is bounded by bytes as well as by count.
|
|
1517
|
+
const APPEND_BATCH_MAX_BYTES = SYNC_CHUNK_BYTES;
|
|
1471
1518
|
const appendFlush = async () => {
|
|
1472
1519
|
if (appendBatch.length === 0)
|
|
1473
1520
|
return;
|
|
1474
1521
|
const batch = appendBatch;
|
|
1475
1522
|
appendBatch = [];
|
|
1523
|
+
appendBatchBytes = 0;
|
|
1476
1524
|
await postAppend(batch);
|
|
1477
1525
|
};
|
|
1478
|
-
const appendAdd = async (conv) => {
|
|
1526
|
+
const appendAdd = async (conv, bytes = 0) => {
|
|
1527
|
+
if (appendBatch.length > 0 && appendBatchBytes + bytes > APPEND_BATCH_MAX_BYTES)
|
|
1528
|
+
await appendFlush();
|
|
1479
1529
|
appendBatch.push(conv);
|
|
1530
|
+
appendBatchBytes += bytes;
|
|
1480
1531
|
if (appendBatch.length >= APPEND_BATCH_SIZE)
|
|
1481
1532
|
await appendFlush();
|
|
1482
1533
|
};
|
|
@@ -1643,7 +1694,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
1643
1694
|
// Oversized sessions are excluded: exportRawSession would materialize the
|
|
1644
1695
|
// whole file (hundreds of MB — this loop is what OOM-killed the daemon).
|
|
1645
1696
|
// Their bounded tail still gets the builtin scan in buildConversationSync.
|
|
1646
|
-
const toScan = slice.filter((ref) => modeOf(ref) === 'full' &&
|
|
1697
|
+
const toScan = slice.filter((ref) => modeOf(ref) === 'full' && sessionBuildBytes(ref) <= FULL_BUILD_MAX_BYTES);
|
|
1647
1698
|
trace?.(`batch secret scan start: ${toScan.length} session(s), ≤${(BATCH_SCAN_MAX_BYTES / 1048576).toFixed(0)}MB per slice`);
|
|
1648
1699
|
const batch = batchScanExternal(toScan.map((ref) => ({
|
|
1649
1700
|
id: ref.prefixedId,
|
|
@@ -1743,12 +1794,23 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
1743
1794
|
const ack = ledger.get(ref.prefixedId);
|
|
1744
1795
|
try {
|
|
1745
1796
|
const tail = await buildConversationTail(ref, ack.o ?? 0, { mapPath, tenantRules });
|
|
1746
|
-
|
|
1797
|
+
// A chunked session cannot fall through to FULL: its FULL sync is the
|
|
1798
|
+
// head chunk again, so the appends would reach the same finding and
|
|
1799
|
+
// loop. It ships the tail, then the whole file's findings, scanned one
|
|
1800
|
+
// chunk at a time, as the session's replacement set.
|
|
1801
|
+
const chunked = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
1802
|
+
if (tail && tail.findings.length > 0 && !chunked) {
|
|
1747
1803
|
promotedForFindings = true; // fall through to FULL (do NOT continue)
|
|
1748
1804
|
}
|
|
1749
1805
|
else if (tail) {
|
|
1750
1806
|
redactions += tail.redactions;
|
|
1751
|
-
await appendAdd(tail.conv);
|
|
1807
|
+
await appendAdd(tail.conv, tail.bytes);
|
|
1808
|
+
if (tail.findings.length > 0) {
|
|
1809
|
+
await appendFlush(); // the session row must exist before its findings
|
|
1810
|
+
const all = await scanTranscriptFindingsChunked(ref, tenantRules);
|
|
1811
|
+
if (all.length > 0)
|
|
1812
|
+
await findingsBatch.add(all);
|
|
1813
|
+
}
|
|
1752
1814
|
walked++;
|
|
1753
1815
|
if (walked % 250 === 0)
|
|
1754
1816
|
console.error(`[sync] ${walked}/${slice.length} sessions…`);
|
|
@@ -1851,7 +1913,10 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
1851
1913
|
// the mtime-only skip on a later tick). Undefined for the oversized-tail path.
|
|
1852
1914
|
if (built.srcHash)
|
|
1853
1915
|
shippedHash.set(ref.prefixedId, built.srcHash);
|
|
1854
|
-
|
|
1916
|
+
if (built.conv.chunked)
|
|
1917
|
+
await postChunkedHead(built.conv, sessionFileBytes(ref));
|
|
1918
|
+
else
|
|
1919
|
+
await convBatch.add(built.conv);
|
|
1855
1920
|
// Ship this session's secret findings as one group (server replaces wholesale).
|
|
1856
1921
|
if (built.findings.length > 0)
|
|
1857
1922
|
await findingsBatch.add(built.findings);
|
|
@@ -1866,7 +1931,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
1866
1931
|
// excluded: replay/outcome re-read the WHOLE file (readEvents), and a
|
|
1867
1932
|
// 353MB session with 100MB+ single JSON lines OOMed the walk right after
|
|
1868
1933
|
// its (bounded) conversation shipped.
|
|
1869
|
-
if (upload.sessionMeta && sessionFileBytes(ref) <=
|
|
1934
|
+
if (upload.sessionMeta && sessionFileBytes(ref) <= DERIVED_MAX_BYTES) {
|
|
1870
1935
|
try {
|
|
1871
1936
|
const count = { redactions: 0 };
|
|
1872
1937
|
// OFF THE MAIN THREAD. This is the heaviest work in the walk: replay,
|
|
@@ -2118,14 +2183,64 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
2118
2183
|
scanned, scanMs: Math.round(scanMs),
|
|
2119
2184
|
};
|
|
2120
2185
|
}
|
|
2121
|
-
/**
|
|
2122
|
-
*
|
|
2123
|
-
*
|
|
2124
|
-
*
|
|
2125
|
-
*
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2186
|
+
/** Build bytes (see sessionBuildBytes) beyond which a FULL sync must not
|
|
2187
|
+
* materialize the session. A larger session ships in chunks instead: the head
|
|
2188
|
+
* chunk as its FULL sync, then the rest as appends.
|
|
2189
|
+
*
|
|
2190
|
+
* The in-memory build holds the container many times over (export, container,
|
|
2191
|
+
* shadow merge, parse, redacted envelope, joined raw text, and a copy in the
|
|
2192
|
+
* scan worker). Measured on a 42 MB container: 840 MB peak RSS in full, 218 MB
|
|
2193
|
+
* in chunks, for the same 2,392 messages, under a unit with MemoryHigh=1G and
|
|
2194
|
+
* a resting RSS near 400 MB. At about 18 bytes per container byte, 24 MB
|
|
2195
|
+
* stays under the limit. */
|
|
2196
|
+
export const FULL_BUILD_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_FULL_BUILD_MAX_MB || '24', 10)) * 1024 * 1024;
|
|
2197
|
+
/** Transcript bytes beyond which the derived computations (diff, outcome,
|
|
2198
|
+
* commits, markers) are skipped. They run in the scan worker and read the
|
|
2199
|
+
* whole transcript; a 353 MB session with 100 MB JSON lines OOMed the walk. */
|
|
2200
|
+
export const DERIVED_MAX_BYTES = Math.max(8, parseInt(process.env.CHAT_RECALL_DERIVED_MAX_MB || '64', 10)) * 1024 * 1024;
|
|
2201
|
+
/** Bytes of transcript one chunk of a chunked sync carries: the head of an
|
|
2202
|
+
* oversized FULL sync, and each append. */
|
|
2203
|
+
export const SYNC_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
2204
|
+
/**
|
|
2205
|
+
* Secret findings for a whole transcript, read one chunk at a time.
|
|
2206
|
+
*
|
|
2207
|
+
* A chunked session never holds its full text, so it cannot use the one-pass
|
|
2208
|
+
* scan of the in-memory build. Line numbers are absolute: each chunk's
|
|
2209
|
+
* findings are shifted by the lines of the chunks before it. A line longer
|
|
2210
|
+
* than a chunk is skipped by the reader, and it counts as one line.
|
|
2211
|
+
*/
|
|
2212
|
+
export async function scanTranscriptFindingsChunked(ref, tenantRules) {
|
|
2213
|
+
const backend = getBackendForId(ref.prefixedId);
|
|
2214
|
+
const findings = [];
|
|
2215
|
+
if (!backend?.readFromOffset)
|
|
2216
|
+
return findings;
|
|
2217
|
+
let offset = 0;
|
|
2218
|
+
let lineBase = 0;
|
|
2219
|
+
for (;;) {
|
|
2220
|
+
const chunk = await backend.readFromOffset(ref.prefixedId, offset, SYNC_CHUNK_BYTES);
|
|
2221
|
+
if (chunk.newOffset <= offset)
|
|
2222
|
+
break;
|
|
2223
|
+
offset = chunk.newOffset;
|
|
2224
|
+
if (chunk.skippedBytes) {
|
|
2225
|
+
lineBase += 1;
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
try {
|
|
2229
|
+
for (const f of scanTextForFindings(chunk.text)) {
|
|
2230
|
+
findings.push({ session_id: ref.prefixedId, detector: 'builtin', rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
2231
|
+
}
|
|
2232
|
+
if (tenantRules && tenantRules.length > 0) {
|
|
2233
|
+
for (const f of scanTenantRules(chunk.text, tenantRules)) {
|
|
2234
|
+
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: lineBase + f.line, preview: f.preview });
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
catch { /* best-effort */ }
|
|
2239
|
+
for (let i = chunk.text.indexOf('\n'); i >= 0; i = chunk.text.indexOf('\n', i + 1))
|
|
2240
|
+
lineBase++;
|
|
2241
|
+
}
|
|
2242
|
+
return dropFuzzyFindings(findings, (f) => ({ detector: f.detector, rule: f.rule }));
|
|
2243
|
+
}
|
|
2129
2244
|
/** Ceiling on PRE-REDACTION session text materialized on disk at any one moment
|
|
2130
2245
|
* by the optional external-detector batch scan. The batch dir is flushed and
|
|
2131
2246
|
* deleted every time it crosses this, so a SIGKILL/OOM mid-scan can strand at
|
|
@@ -2140,20 +2255,34 @@ export function sessionFileBytes(ref) {
|
|
|
2140
2255
|
return 0;
|
|
2141
2256
|
return backend.fileSize?.(ref.prefixedId) ?? 0;
|
|
2142
2257
|
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Bytes a FULL build of an append-only session loads (0 for non-AO backends):
|
|
2260
|
+
* the larger of the main transcript and the shadow container, which also holds
|
|
2261
|
+
* the subagent transcripts and any history an upstream rewrite dropped. The
|
|
2262
|
+
* main file alone under-counts: a 19 MB transcript built from a 42 MB shadow
|
|
2263
|
+
* and peaked at 840 MB RSS.
|
|
2264
|
+
*/
|
|
2265
|
+
export function sessionBuildBytes(ref) {
|
|
2266
|
+
const main = sessionFileBytes(ref);
|
|
2267
|
+
if (main === 0)
|
|
2268
|
+
return 0;
|
|
2269
|
+
return Math.max(main, shadowUncompressedBytes(ref.toolId, ref.rawId));
|
|
2270
|
+
}
|
|
2143
2271
|
export async function buildConversationSync(ref, mtime, opts = {}) {
|
|
2144
2272
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
2145
2273
|
let includeRaw = opts.includeRaw !== false;
|
|
2146
2274
|
let includeMeta = opts.includeMeta !== false;
|
|
2147
2275
|
let srcHash;
|
|
2148
|
-
// Oversized
|
|
2149
|
-
//
|
|
2150
|
-
//
|
|
2151
|
-
//
|
|
2152
|
-
//
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
let
|
|
2276
|
+
// Oversized transcript: ship it in chunks. This FULL sync carries the HEAD
|
|
2277
|
+
// chunk (the first SYNC_CHUNK_BYTES, cut at a line end) with from_offset at
|
|
2278
|
+
// the end of that chunk. The ledger records that offset, so syncMode sees the
|
|
2279
|
+
// file as larger than the cursor and every later tick APPENDs the next chunk
|
|
2280
|
+
// until the server holds the whole transcript. The raw archive (a whole-file
|
|
2281
|
+
// export) is dropped; telemetry stays, because parseSessionFile reads line by
|
|
2282
|
+
// line.
|
|
2283
|
+
const oversized = sessionBuildBytes(ref) > FULL_BUILD_MAX_BYTES;
|
|
2284
|
+
let headOffsetEnd = 0;
|
|
2285
|
+
let oversizedFindings = null;
|
|
2157
2286
|
// Parse the transcript live — this is the single canonical parse; the
|
|
2158
2287
|
// envelope and the redacted text both come from it. When the shadow path
|
|
2159
2288
|
// runs (normal-size sessions) it also yields the fullest raw container, which
|
|
@@ -2165,16 +2294,20 @@ export async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
2165
2294
|
if (!backend?.readFromOffset)
|
|
2166
2295
|
return null;
|
|
2167
2296
|
try {
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2297
|
+
// A first line longer than a chunk is skipped by the reader; read on
|
|
2298
|
+
// from past it so the head carries real messages.
|
|
2299
|
+
let head = await backend.readFromOffset(ref.prefixedId, 0, SYNC_CHUNK_BYTES);
|
|
2300
|
+
for (let hops = 0; head.skippedBytes && hops < 8; hops++) {
|
|
2301
|
+
head = await backend.readFromOffset(ref.prefixedId, head.newOffset, SYNC_CHUNK_BYTES);
|
|
2302
|
+
}
|
|
2303
|
+
headOffsetEnd = head.newOffset;
|
|
2304
|
+
const messages = parseTailMessages(ref.toolId, head.text);
|
|
2172
2305
|
if (messages.length > 0)
|
|
2173
2306
|
transcript = { messages, subagents: [] };
|
|
2174
2307
|
}
|
|
2175
|
-
catch { /* unreadable
|
|
2308
|
+
catch { /* unreadable head */ }
|
|
2176
2309
|
includeRaw = false; // exportRawSession would read the whole file
|
|
2177
|
-
|
|
2310
|
+
oversizedFindings = await scanTranscriptFindingsChunked(ref, opts.tenantRules);
|
|
2178
2311
|
// NB: oversized sessions are NOT shadowed — a full export would OOM the
|
|
2179
2312
|
// walk (the very reason for the tail path). A session truncated FROM
|
|
2180
2313
|
// oversized down to normal falls back to a fresh 'created' shadow; the
|
|
@@ -2297,9 +2430,6 @@ export async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
2297
2430
|
// which is the same behaviour as an un-indexed session.
|
|
2298
2431
|
let projectPath = ref.projectPath;
|
|
2299
2432
|
const meta = { messageCount: textMessages.length };
|
|
2300
|
-
// Flag tail-only FULL syncs so the UI can badge partial history.
|
|
2301
|
-
if (oversized)
|
|
2302
|
-
meta.truncated = true;
|
|
2303
2433
|
// Single-prompt invocations (batch/bot runs) carry a flag so the UI can
|
|
2304
2434
|
// badge them and lists can de-emphasize them.
|
|
2305
2435
|
if (textMessages.filter((m) => m.role === 'user').length <= 1)
|
|
@@ -2456,23 +2586,10 @@ export async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
2456
2586
|
}
|
|
2457
2587
|
scanMs = performance.now() - t0;
|
|
2458
2588
|
}
|
|
2459
|
-
// Oversized sessions:
|
|
2460
|
-
// the container path
|
|
2461
|
-
if (
|
|
2462
|
-
|
|
2463
|
-
try {
|
|
2464
|
-
for (const f of scanTextForFindings(tailText)) {
|
|
2465
|
-
findings.push({ session_id: ref.prefixedId, detector: 'builtin', rule: f.rule, line: f.line, preview: f.preview });
|
|
2466
|
-
}
|
|
2467
|
-
if (opts.tenantRules && opts.tenantRules.length > 0) {
|
|
2468
|
-
for (const f of scanTenantRules(tailText, opts.tenantRules)) {
|
|
2469
|
-
findings.push({ session_id: ref.prefixedId, detector: f.detector, rule: f.rule, line: f.line, preview: f.preview });
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
catch { /* best-effort */ }
|
|
2474
|
-
scanMs = performance.now() - t0;
|
|
2475
|
-
}
|
|
2589
|
+
// Oversized sessions: the whole file was scanned chunk by chunk above —
|
|
2590
|
+
// the container path never ran for them.
|
|
2591
|
+
if (oversizedFindings)
|
|
2592
|
+
findings.push(...oversizedFindings);
|
|
2476
2593
|
// High-precision default: drop fuzzy/low-precision detector findings
|
|
2477
2594
|
// (generic-api-key, Box, URI, …) before shipping so the Security view isn't
|
|
2478
2595
|
// buried in false positives. Opt back in with CHAT_RECALL_INCLUDE_FUZZY=1.
|
|
@@ -2529,7 +2646,10 @@ export async function buildConversationSync(ref, mtime, opts = {}) {
|
|
|
2529
2646
|
// (convBatch onFlush uses this, not a recomputed size) so the next
|
|
2530
2647
|
// append's base_offset matches exactly — no spurious continuity misses on
|
|
2531
2648
|
// an actively-growing session. undefined for non-append-only backends.
|
|
2532
|
-
from_offset: oversized ?
|
|
2649
|
+
from_offset: oversized ? headOffsetEnd : getBackendForId(ref.prefixedId)?.fileSize?.(ref.rawId),
|
|
2650
|
+
// The head of a chunked session. The server reports back when its shrink
|
|
2651
|
+
// guard keeps a fuller stored copy, so the client appends from there.
|
|
2652
|
+
...(oversized ? { chunked: true } : {}),
|
|
2533
2653
|
meta,
|
|
2534
2654
|
mtime,
|
|
2535
2655
|
},
|
|
@@ -2573,15 +2693,27 @@ export async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
2573
2693
|
if (!backend?.readFromOffset)
|
|
2574
2694
|
return null;
|
|
2575
2695
|
const mapPath = opts.mapPath ?? ((p) => p);
|
|
2576
|
-
const { text: tailText, newOffset } = await backend.readFromOffset(ref.prefixedId, fromOffset);
|
|
2577
|
-
if (
|
|
2578
|
-
return null;
|
|
2579
|
-
const messages = parseTailMessages(ref.toolId, tailText);
|
|
2580
|
-
if (messages.length === 0)
|
|
2696
|
+
const { text: tailText, newOffset, skippedBytes } = await backend.readFromOffset(ref.prefixedId, fromOffset, SYNC_CHUNK_BYTES);
|
|
2697
|
+
if (newOffset <= fromOffset)
|
|
2581
2698
|
return null;
|
|
2699
|
+
// A window with bytes but nothing to show (tool noise, a skipped line longer
|
|
2700
|
+
// than a chunk) still has to move the server's synced-through offset, or the
|
|
2701
|
+
// cursor reads the same window on every tick and the rest of a chunked
|
|
2702
|
+
// session never ships. The server merges an empty tail and advances `o`.
|
|
2703
|
+
const messages = skippedBytes ? [] : parseTailMessages(ref.toolId, tailText);
|
|
2582
2704
|
const textMessages = messages.filter((m) => m.role !== 'summary' && m.content?.trim());
|
|
2583
|
-
|
|
2584
|
-
|
|
2705
|
+
const hasContent = textMessages.length > 0 || messages.some((m) => m.toolCalls?.length);
|
|
2706
|
+
if (!hasContent) {
|
|
2707
|
+
return {
|
|
2708
|
+
conv: {
|
|
2709
|
+
session_id: ref.prefixedId, tool: ref.toolId, project_path: mapPath(ref.projectPath),
|
|
2710
|
+
append: true, base_offset: fromOffset, from_offset: newOffset, redacted_text: '',
|
|
2711
|
+
envelope: { v: TRANSCRIPT_VERSION, messages: [], subagents: [] },
|
|
2712
|
+
mtime: Math.floor(ref.mtime) || 0,
|
|
2713
|
+
},
|
|
2714
|
+
redactions: 0, newOffset, findings: [], bytes: 0,
|
|
2715
|
+
};
|
|
2716
|
+
}
|
|
2585
2717
|
// Skip chat-recall's OWN internal prompts in the tail (same gate as the
|
|
2586
2718
|
// full builder). A tail that is ONLY an internal prompt ships nothing.
|
|
2587
2719
|
const firstUser = textMessages.find((m) => m.role === 'user');
|
|
@@ -2631,6 +2763,7 @@ export async function buildConversationTail(ref, fromOffset, opts = {}) {
|
|
|
2631
2763
|
redactions: count.redactions,
|
|
2632
2764
|
newOffset,
|
|
2633
2765
|
findings,
|
|
2766
|
+
bytes: Buffer.byteLength(tailText),
|
|
2634
2767
|
};
|
|
2635
2768
|
}
|
|
2636
2769
|
export function isSyncSkip(r) {
|