@remnic/core 9.45.4 → 9.45.5
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/access-admin-ops-surface.d.ts +5 -5
- package/dist/access-admin-ops-surface.js +20 -19
- package/dist/access-authorization-probe.d.ts +5 -5
- package/dist/access-authorization-probe.js +21 -20
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +20 -19
- package/dist/access-cli.js +60 -58
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +5 -5
- package/dist/access-extraction-force-flush.js +20 -19
- package/dist/access-http-lcm-compaction.d.ts +5 -5
- package/dist/access-http-lifecycle-flush.d.ts +5 -5
- package/dist/access-http-offline-stream.d.ts +5 -5
- package/dist/access-http-offline-stream.js +1 -1
- package/dist/access-http-query.js +20 -19
- package/dist/access-http.d.ts +5 -6
- package/dist/access-http.js +32 -31
- package/dist/access-identity-continuity-surface.d.ts +4 -4
- package/dist/access-identity-continuity-surface.js +20 -19
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +20 -19
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +26 -25
- package/dist/access-namespace-preflight.js +20 -19
- package/dist/access-observe-write-surface.d.ts +5 -5
- package/dist/access-observe-write-surface.js +20 -19
- package/dist/access-offline-manifest.d.ts +4 -4
- package/dist/access-operations-batch.js +21 -20
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +25 -24
- package/dist/access-recall-concurrency.d.ts +5 -5
- package/dist/access-recall-concurrency.js +20 -19
- package/dist/access-recall-response.d.ts +5 -5
- package/dist/access-recall-response.js +20 -19
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +20 -19
- package/dist/access-schema.d.ts +84 -84
- package/dist/{access-service-CsU-ljFD.d.ts → access-service-CNN89xyn.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +5 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +20 -19
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -2
- package/dist/buffer.d.ts +1 -1
- package/dist/buffer.js +4 -4
- package/dist/causal-consolidation.js +4 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-7SHJHPTY.js → chunk-2FSTB6BJ.js} +2 -2
- package/dist/{chunk-ULVR5KG7.js → chunk-2X2T2UVE.js} +2 -2
- package/dist/{chunk-QVSBTCVP.js → chunk-3COYXTGM.js} +2 -2
- package/dist/{chunk-XTAUXTDO.js → chunk-4FBVAGV3.js} +2 -2
- package/dist/{chunk-R5RFK3CB.js → chunk-5QJQHBLE.js} +2 -2
- package/dist/{chunk-YMAQDHQM.js → chunk-6ISC4AVB.js} +2 -2
- package/dist/{chunk-7CITSYSH.js → chunk-7T4TOGMS.js} +2 -2
- package/dist/{chunk-ZMAYKGPN.js → chunk-7YQ6ESEA.js} +12 -41
- package/dist/chunk-7YQ6ESEA.js.map +1 -0
- package/dist/{chunk-TYISDI6B.js → chunk-A4IJCD2Z.js} +2 -2
- package/dist/{chunk-JH6BPLOP.js → chunk-AC5OWJLV.js} +4 -4
- package/dist/{chunk-CLB32OHM.js → chunk-AMAUBUKY.js} +2 -2
- package/dist/{chunk-WGP57SR3.js → chunk-CK5KOVHW.js} +3 -2
- package/dist/{chunk-WGP57SR3.js.map → chunk-CK5KOVHW.js.map} +1 -1
- package/dist/{chunk-XSFHWBQO.js → chunk-CX4ZUMC6.js} +7 -61
- package/dist/{chunk-XSFHWBQO.js.map → chunk-CX4ZUMC6.js.map} +1 -1
- package/dist/{chunk-UQWIKLQK.js → chunk-DAJPRXBR.js} +6 -6
- package/dist/{chunk-U2VLVBBH.js → chunk-E5XQIWAL.js} +2 -2
- package/dist/{chunk-NTOIP2E6.js → chunk-ERZ2R42X.js} +2 -2
- package/dist/{chunk-5CEECY6P.js → chunk-FIK3CQNA.js} +3 -3
- package/dist/{chunk-EJHEWCGQ.js → chunk-FR27HBBA.js} +2 -2
- package/dist/{chunk-FZP32336.js → chunk-FUCYDRE5.js} +43 -21
- package/dist/chunk-FUCYDRE5.js.map +1 -0
- package/dist/{chunk-P7NGM24P.js → chunk-GFCVUY6G.js} +2 -2
- package/dist/{chunk-DYVMXPCF.js → chunk-GODSPTOT.js} +41 -41
- package/dist/{chunk-AJQ57VV3.js → chunk-GRUYA45P.js} +4 -4
- package/dist/chunk-GRUYA45P.js.map +1 -0
- package/dist/{chunk-2MRHJNWL.js → chunk-GUAL4PQH.js} +2 -2
- package/dist/{chunk-EITZHSX6.js → chunk-HC3PBX23.js} +2 -2
- package/dist/{chunk-NUEQKLNB.js → chunk-JZH4LWHB.js} +2 -2
- package/dist/{chunk-7RZJWZ46.js → chunk-LEPOP46W.js} +5 -5
- package/dist/{chunk-TQ4GD737.js → chunk-LPYDOLSM.js} +2 -2
- package/dist/{chunk-TN76AKO3.js → chunk-MO4HSXHZ.js} +2 -2
- package/dist/{chunk-WQIHRA5F.js → chunk-N3LL5NEU.js} +2 -2
- package/dist/{chunk-D3I2MCN7.js → chunk-NEF7LR5J.js} +3 -3
- package/dist/{chunk-C26TE27X.js → chunk-QR6G5KF4.js} +3 -3
- package/dist/{chunk-ZI32MKDW.js → chunk-QUENPGWZ.js} +18 -21
- package/dist/chunk-QUENPGWZ.js.map +1 -0
- package/dist/{chunk-5SNZSWWR.js → chunk-QVJPH7SH.js} +21 -21
- package/dist/{chunk-MDISBSYA.js → chunk-R3KHKU7M.js} +2 -2
- package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
- package/dist/{chunk-KXEFRMCF.js → chunk-RNHVVMFN.js} +2 -2
- package/dist/{chunk-QCECHYZL.js → chunk-RVO6TCTU.js} +6 -6
- package/dist/chunk-S2HLHAZ4.js +16 -0
- package/dist/chunk-S2HLHAZ4.js.map +1 -0
- package/dist/{chunk-ZFGZ6PZ2.js → chunk-UW66OZTD.js} +2 -2
- package/dist/{chunk-BGHFOD5A.js → chunk-V2TNYF6Q.js} +3 -3
- package/dist/chunk-VL35ZFSZ.js +102 -0
- package/dist/chunk-VL35ZFSZ.js.map +1 -0
- package/dist/{chunk-Y7HRYCNW.js → chunk-W2V7UESE.js} +2 -2
- package/dist/{chunk-DSUAAZZX.js → chunk-WZ3TOIDW.js} +2 -2
- package/dist/{chunk-7YERSTN7.js → chunk-XAGYS7X6.js} +7 -7
- package/dist/{chunk-VOUOLGIP.js → chunk-YRFAYOFQ.js} +11 -13
- package/dist/chunk-YRFAYOFQ.js.map +1 -0
- package/dist/{chunk-6NNNURLK.js → chunk-Z54EM5YZ.js} +4 -4
- package/dist/{cli-BBHDtG36.d.ts → cli-BHBswmUa.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +43 -42
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -2
- package/dist/connectors/codex-materialize-runner.js +3 -2
- package/dist/connectors/index.js +3 -2
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/convergence-refresh.d.ts +1 -1
- package/dist/convergence-refresh.js +5 -4
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +12 -11
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +3 -2
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/external-wiki-access.d.ts +5 -5
- package/dist/external-wiki-access.js +23 -22
- package/dist/external-wiki-cli.js +4 -90
- package/dist/external-wiki-cli.js.map +1 -1
- package/dist/external-wiki-mcp-tools.d.ts +5 -5
- package/dist/external-wiki-search.js +2 -2
- package/dist/external-wiki.d.ts +1 -5
- package/dist/external-wiki.js +1 -3
- package/dist/extraction.js +8 -7
- package/dist/{first-start-migration-F2QJD66E.js → first-start-migration-ZSHEFEI3.js} +10 -12
- package/dist/{first-start-migration-F2QJD66E.js.map → first-start-migration-ZSHEFEI3.js.map} +1 -1
- package/dist/index.d.ts +688 -687
- package/dist/index.js +81 -78
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +2 -2
- package/dist/maintenance/memory-governance.js +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +4 -3
- package/dist/{maintenance-D2mveyCd.d.ts → maintenance-CoRikzIt.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-snapshot.d.ts +10 -0
- package/dist/memory-snapshot.js +8 -0
- package/dist/memory-snapshot.js.map +1 -0
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -10
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -2
- package/dist/operator-doctor-corpus.js +13 -12
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +17 -16
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +8 -7
- package/dist/{orchestrator-DJYlwiUv.d.ts → orchestrator-BJeq7Pw1.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +61 -59
- package/dist/qmd.js +4 -4
- package/dist/schemas.d.ts +76 -76
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +8 -8
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +3 -2
- package/dist/semantic-rule-verifier.js +3 -2
- package/dist/{service-BeDxSqE-.d.ts → service-DWC4tq7p.d.ts} +1 -1
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +7 -6
- package/dist/{storage-DfjxusTE.d.ts → storage-DPGjB-8e.d.ts} +4 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +2 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-migration.js +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +3 -2
- package/package.json +2 -2
- package/src/access-http-offline-stream.ts +1 -0
- package/src/access-http.ts +0 -57
- package/src/access-operations-batch.ts +1 -1
- package/src/access-service-observe-lcm-parity.test.ts +45 -20
- package/src/access-service.ts +3 -4
- package/src/external-wiki-cli.ts +28 -20
- package/src/external-wiki-search.test.ts +68 -2
- package/src/external-wiki.ts +12 -38
- package/src/index.ts +1 -5
- package/src/maintenance/first-start-migration.ts +11 -11
- package/src/memory-snapshot.ts +15 -0
- package/src/storage-contract/cache-coherence.test.ts +99 -0
- package/src/storage-fact-hash-index-defer.test.ts +14 -7
- package/src/storage.ts +47 -48
- package/src/testing/subjects/first-start-tier-migration.test.ts +75 -0
- package/src/tier-migration.ts +10 -12
- package/dist/chunk-AJQ57VV3.js.map +0 -1
- package/dist/chunk-FZP32336.js.map +0 -1
- package/dist/chunk-VOUOLGIP.js.map +0 -1
- package/dist/chunk-ZI32MKDW.js.map +0 -1
- package/dist/chunk-ZMAYKGPN.js.map +0 -1
- /package/dist/{chunk-7SHJHPTY.js.map → chunk-2FSTB6BJ.js.map} +0 -0
- /package/dist/{chunk-ULVR5KG7.js.map → chunk-2X2T2UVE.js.map} +0 -0
- /package/dist/{chunk-QVSBTCVP.js.map → chunk-3COYXTGM.js.map} +0 -0
- /package/dist/{chunk-XTAUXTDO.js.map → chunk-4FBVAGV3.js.map} +0 -0
- /package/dist/{chunk-R5RFK3CB.js.map → chunk-5QJQHBLE.js.map} +0 -0
- /package/dist/{chunk-YMAQDHQM.js.map → chunk-6ISC4AVB.js.map} +0 -0
- /package/dist/{chunk-7CITSYSH.js.map → chunk-7T4TOGMS.js.map} +0 -0
- /package/dist/{chunk-TYISDI6B.js.map → chunk-A4IJCD2Z.js.map} +0 -0
- /package/dist/{chunk-JH6BPLOP.js.map → chunk-AC5OWJLV.js.map} +0 -0
- /package/dist/{chunk-CLB32OHM.js.map → chunk-AMAUBUKY.js.map} +0 -0
- /package/dist/{chunk-UQWIKLQK.js.map → chunk-DAJPRXBR.js.map} +0 -0
- /package/dist/{chunk-U2VLVBBH.js.map → chunk-E5XQIWAL.js.map} +0 -0
- /package/dist/{chunk-NTOIP2E6.js.map → chunk-ERZ2R42X.js.map} +0 -0
- /package/dist/{chunk-5CEECY6P.js.map → chunk-FIK3CQNA.js.map} +0 -0
- /package/dist/{chunk-EJHEWCGQ.js.map → chunk-FR27HBBA.js.map} +0 -0
- /package/dist/{chunk-P7NGM24P.js.map → chunk-GFCVUY6G.js.map} +0 -0
- /package/dist/{chunk-DYVMXPCF.js.map → chunk-GODSPTOT.js.map} +0 -0
- /package/dist/{chunk-2MRHJNWL.js.map → chunk-GUAL4PQH.js.map} +0 -0
- /package/dist/{chunk-EITZHSX6.js.map → chunk-HC3PBX23.js.map} +0 -0
- /package/dist/{chunk-NUEQKLNB.js.map → chunk-JZH4LWHB.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-LEPOP46W.js.map} +0 -0
- /package/dist/{chunk-TQ4GD737.js.map → chunk-LPYDOLSM.js.map} +0 -0
- /package/dist/{chunk-TN76AKO3.js.map → chunk-MO4HSXHZ.js.map} +0 -0
- /package/dist/{chunk-WQIHRA5F.js.map → chunk-N3LL5NEU.js.map} +0 -0
- /package/dist/{chunk-D3I2MCN7.js.map → chunk-NEF7LR5J.js.map} +0 -0
- /package/dist/{chunk-C26TE27X.js.map → chunk-QR6G5KF4.js.map} +0 -0
- /package/dist/{chunk-5SNZSWWR.js.map → chunk-QVJPH7SH.js.map} +0 -0
- /package/dist/{chunk-MDISBSYA.js.map → chunk-R3KHKU7M.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
- /package/dist/{chunk-KXEFRMCF.js.map → chunk-RNHVVMFN.js.map} +0 -0
- /package/dist/{chunk-QCECHYZL.js.map → chunk-RVO6TCTU.js.map} +0 -0
- /package/dist/{chunk-ZFGZ6PZ2.js.map → chunk-UW66OZTD.js.map} +0 -0
- /package/dist/{chunk-BGHFOD5A.js.map → chunk-V2TNYF6Q.js.map} +0 -0
- /package/dist/{chunk-Y7HRYCNW.js.map → chunk-W2V7UESE.js.map} +0 -0
- /package/dist/{chunk-DSUAAZZX.js.map → chunk-WZ3TOIDW.js.map} +0 -0
- /package/dist/{chunk-7YERSTN7.js.map → chunk-XAGYS7X6.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-Z54EM5YZ.js.map} +0 -0
package/src/access-http.ts
CHANGED
|
@@ -3424,63 +3424,6 @@ export class EngramAccessHttpServer {
|
|
|
3424
3424
|
res.end(body);
|
|
3425
3425
|
}
|
|
3426
3426
|
|
|
3427
|
-
private async respondOfflineSnapshotStream(
|
|
3428
|
-
res: ServerResponse,
|
|
3429
|
-
snapshot: Awaited<ReturnType<EngramAccessService["offlineSyncSnapshotStream"]>>,
|
|
3430
|
-
): Promise<void> {
|
|
3431
|
-
res.statusCode = 200;
|
|
3432
|
-
res.setHeader("content-type", "application/x-ndjson; charset=utf-8");
|
|
3433
|
-
res.setHeader("cache-control", "no-store");
|
|
3434
|
-
const cid = correlationIdStore.getStore();
|
|
3435
|
-
if (cid) {
|
|
3436
|
-
res.setHeader("x-request-id", cid);
|
|
3437
|
-
}
|
|
3438
|
-
const waitForDrainOrClose = async (): Promise<boolean> => new Promise((resolve, reject) => {
|
|
3439
|
-
const cleanup = () => {
|
|
3440
|
-
res.off("drain", onDrain);
|
|
3441
|
-
res.off("close", onClose);
|
|
3442
|
-
res.off("error", onError);
|
|
3443
|
-
};
|
|
3444
|
-
const onDrain = () => {
|
|
3445
|
-
cleanup();
|
|
3446
|
-
resolve(true);
|
|
3447
|
-
};
|
|
3448
|
-
const onClose = () => {
|
|
3449
|
-
cleanup();
|
|
3450
|
-
resolve(false);
|
|
3451
|
-
};
|
|
3452
|
-
const onError = (error: Error) => {
|
|
3453
|
-
cleanup();
|
|
3454
|
-
reject(error);
|
|
3455
|
-
};
|
|
3456
|
-
res.once("drain", onDrain);
|
|
3457
|
-
res.once("close", onClose);
|
|
3458
|
-
res.once("error", onError);
|
|
3459
|
-
});
|
|
3460
|
-
const writeLine = async (payload: unknown): Promise<boolean> => {
|
|
3461
|
-
if (res.destroyed || res.writableEnded) return false;
|
|
3462
|
-
if (res.write(`${JSON.stringify(payload)}\n`)) return true;
|
|
3463
|
-
if (res.destroyed || res.writableEnded) return false;
|
|
3464
|
-
return waitForDrainOrClose();
|
|
3465
|
-
};
|
|
3466
|
-
if (!await writeLine({
|
|
3467
|
-
type: "snapshot",
|
|
3468
|
-
namespace: snapshot.namespace,
|
|
3469
|
-
format: snapshot.format,
|
|
3470
|
-
schemaVersion: snapshot.schemaVersion,
|
|
3471
|
-
createdAt: snapshot.createdAt,
|
|
3472
|
-
sourceId: snapshot.sourceId,
|
|
3473
|
-
includeTranscripts: snapshot.includeTranscripts,
|
|
3474
|
-
deletions: snapshot.deletions,
|
|
3475
|
-
})) return;
|
|
3476
|
-
for await (const file of snapshot.files) {
|
|
3477
|
-
if (!await writeLine({ type: "file", file })) return;
|
|
3478
|
-
}
|
|
3479
|
-
if (!res.destroyed && !res.writableEnded) {
|
|
3480
|
-
res.end();
|
|
3481
|
-
}
|
|
3482
|
-
}
|
|
3483
|
-
|
|
3484
3427
|
private respondBinary(
|
|
3485
3428
|
res: ServerResponse,
|
|
3486
3429
|
status: number,
|
|
@@ -163,7 +163,7 @@ defineOperation({ name: "chatgpt_memory_inspector", description: "Memory inspect
|
|
|
163
163
|
if (input.currentContextScopes !== undefined) ii.currentContextScopes = input.currentContextScopes as string[];
|
|
164
164
|
if (input.allowUnverifiedPreview !== undefined) ii.allowUnverifiedPreview = input.allowUnverifiedPreview as boolean;
|
|
165
165
|
const rsk = ii.sessionKey ?? (ctx.authenticatedPrincipal ? "remnic:chatgpt-memory-inspector:" + randomUUID() : undefined);
|
|
166
|
-
const xr = await ctx.service.recallXray({ query: ii.query, sessionKey: rsk, namespace: ii.namespace, currentContextScopes: ii.currentContextScopes, authenticatedPrincipal: ctx.authenticatedPrincipal, sourceConnector: ctx.sourceConnector, mode: "full", disclosure: "chunk", includeRecall: true, ...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}) });
|
|
166
|
+
const xr = await ctx.service.recallXray({ query: ii.query, sessionKey: rsk, namespace: ii.namespace, currentContextScopes: ii.currentContextScopes, authenticatedPrincipal: ctx.authenticatedPrincipal, ...(ctx.sourceConnector ? { sourceConnector: ctx.sourceConnector } : {}), mode: "full", disclosure: "chunk", includeRecall: true, ...(ctx.abortSignal ? { abortSignal: ctx.abortSignal } : {}) });
|
|
167
167
|
const x = xr.snapshotFound === true ? (xr.snapshot ?? null) : null;
|
|
168
168
|
const r = xr.recall ?? { query: ii.query, namespace: ii.namespace ?? x?.namespace ?? "global", context: "", count: 0, memoryIds: [], results: [], fallbackUsed: false, sourcesUsed: [], disclosure: "chunk" as const };
|
|
169
169
|
const ac = await ctx.service.actionConfidence(buildChatGptMemoryInspectorActionRequest(ii, r, x));
|
|
@@ -910,19 +910,28 @@ test("#2128: post-flush retained cleanup is best effort and receives lifecycle g
|
|
|
910
910
|
}]);
|
|
911
911
|
});
|
|
912
912
|
|
|
913
|
-
test("#2128: post-flush retained cleanup stops on abort or deadline", async () => {
|
|
913
|
+
test("#2128: post-flush retained cleanup stops on abort or deadline", async (t) => {
|
|
914
914
|
const probe = makeParityProbe(withSelfPolicyPrefix("pi-geek"));
|
|
915
|
-
(
|
|
916
|
-
|
|
917
|
-
|
|
915
|
+
let cleanupStarted: (() => void) | undefined;
|
|
916
|
+
const cleanupCalls: Array<{
|
|
917
|
+
sessionKey: string;
|
|
918
|
+
ownerPrincipal: string | undefined;
|
|
919
|
+
options: { abortSignal?: AbortSignal; deadlineMs?: number } | undefined;
|
|
920
|
+
}> = [];
|
|
921
|
+
Object.defineProperty(probe.orch, "buffer", {
|
|
922
|
+
configurable: true,
|
|
923
|
+
value: {
|
|
924
|
+
clearRetainedTurnsForSession: async (
|
|
918
925
|
sessionKey: string,
|
|
919
926
|
ownerPrincipal?: string,
|
|
920
927
|
options?: { abortSignal?: AbortSignal; deadlineMs?: number },
|
|
921
|
-
)
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
928
|
+
) => {
|
|
929
|
+
cleanupCalls.push({ sessionKey, ownerPrincipal, options });
|
|
930
|
+
cleanupStarted?.();
|
|
931
|
+
return new Promise<void>(() => {});
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
});
|
|
926
935
|
const service = new EngramAccessService(probe.orch);
|
|
927
936
|
const abortController = new AbortController();
|
|
928
937
|
setTimeout(() => abortController.abort(), 5).unref();
|
|
@@ -941,19 +950,35 @@ test("#2128: post-flush retained cleanup stops on abort or deadline", async () =
|
|
|
941
950
|
]),
|
|
942
951
|
/extraction force-flush aborted/,
|
|
943
952
|
);
|
|
953
|
+
assert.deepEqual(cleanupCalls[0], {
|
|
954
|
+
sessionKey: "pi-geek:cleanup-abort",
|
|
955
|
+
ownerPrincipal: "pi-geek",
|
|
956
|
+
options: { abortSignal: abortController.signal, deadlineMs: undefined },
|
|
957
|
+
});
|
|
944
958
|
|
|
959
|
+
const cleanupStartedPromise = new Promise<void>((resolve) => {
|
|
960
|
+
cleanupStarted = resolve;
|
|
961
|
+
});
|
|
962
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 0 });
|
|
963
|
+
const deadlineFlush = service.extractionForceFlush({
|
|
964
|
+
sessionKey: "pi-geek:cleanup-deadline",
|
|
965
|
+
authenticatedPrincipal: "pi-geek",
|
|
966
|
+
deadlineMs: 5,
|
|
967
|
+
});
|
|
968
|
+
await Promise.race([
|
|
969
|
+
cleanupStartedPromise,
|
|
970
|
+
new Promise<never>((_resolve, reject) =>
|
|
971
|
+
setImmediate(() => reject(new Error("deadline cleanup did not start"))),
|
|
972
|
+
),
|
|
973
|
+
]);
|
|
974
|
+
assert.deepEqual(cleanupCalls[1], {
|
|
975
|
+
sessionKey: "pi-geek:cleanup-deadline",
|
|
976
|
+
ownerPrincipal: "pi-geek",
|
|
977
|
+
options: { abortSignal: undefined, deadlineMs: 5 },
|
|
978
|
+
});
|
|
979
|
+
t.mock.timers.tick(5);
|
|
945
980
|
await assert.rejects(
|
|
946
|
-
|
|
947
|
-
Promise.race([
|
|
948
|
-
service.extractionForceFlush({
|
|
949
|
-
sessionKey: "pi-geek:cleanup-deadline",
|
|
950
|
-
authenticatedPrincipal: "pi-geek",
|
|
951
|
-
deadlineMs: Date.now() + 5,
|
|
952
|
-
}),
|
|
953
|
-
new Promise<never>((_resolve, reject) =>
|
|
954
|
-
setTimeout(() => reject(new Error("deadline cleanup test timed out")), 100),
|
|
955
|
-
),
|
|
956
|
-
]),
|
|
981
|
+
deadlineFlush,
|
|
957
982
|
/replay extraction deadline exceeded \(retained_turn_cleanup\)/,
|
|
958
983
|
);
|
|
959
984
|
});
|
package/src/access-service.ts
CHANGED
|
@@ -2386,7 +2386,9 @@ export class EngramAccessService {
|
|
|
2386
2386
|
} else if (extraction.degradedReason?.includes("buffer unreadable")) {
|
|
2387
2387
|
scopedDegradedReason = "daemon extraction pipeline degraded; extraction buffer unreadable";
|
|
2388
2388
|
} else {
|
|
2389
|
-
scopedDegradedReason =
|
|
2389
|
+
scopedDegradedReason =
|
|
2390
|
+
`${extraction.bufferedSessionCount} buffered session(s), ` +
|
|
2391
|
+
`${extraction.pendingTurnCount} turn(s) pending extraction`;
|
|
2390
2392
|
}
|
|
2391
2393
|
}
|
|
2392
2394
|
const visibleExtraction =
|
|
@@ -2394,9 +2396,6 @@ export class EngramAccessService {
|
|
|
2394
2396
|
? {
|
|
2395
2397
|
...extraction,
|
|
2396
2398
|
lastExtractionAt: null,
|
|
2397
|
-
bufferedSessionCount: 0,
|
|
2398
|
-
pendingTurnCount: 0,
|
|
2399
|
-
oldestBufferedTurnAgeMs: null,
|
|
2400
2399
|
degradedReason: scopedDegradedReason,
|
|
2401
2400
|
}
|
|
2402
2401
|
: extraction;
|
package/src/external-wiki-cli.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ExternalWikiRoot, searchExternalWikis } from "./external-wiki-search.js";
|
|
1
|
+
import { type ExternalWikiRoot, type ExternalWikiSearchResult, searchExternalWikis } from "./external-wiki-search.js";
|
|
2
2
|
|
|
3
3
|
interface ExternalWikiCliIo {
|
|
4
4
|
readonly stdout: { write(chunk: string): unknown };
|
|
@@ -13,28 +13,25 @@ export async function runExternalWikiCliCommand(
|
|
|
13
13
|
args: readonly string[],
|
|
14
14
|
io: ExternalWikiCliIo
|
|
15
15
|
): Promise<number> {
|
|
16
|
-
if (args
|
|
16
|
+
if (args[0] === "help" || args[0] === "--help" || args[0] === "-h") {
|
|
17
17
|
io.stdout.write(USAGE);
|
|
18
18
|
return 0;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
io.stderr.write(`external-wiki: unknown command '${args[0]}'\n${USAGE}`);
|
|
22
|
-
return 2;
|
|
23
|
-
}
|
|
20
|
+
const searchArgs = args[0] === "search" ? args.slice(1) : args;
|
|
24
21
|
|
|
25
22
|
let limit: number | undefined;
|
|
26
23
|
let wikiId: string | undefined;
|
|
27
24
|
let maxCharsPerHit: number | undefined;
|
|
28
25
|
let json = false;
|
|
29
26
|
const queryParts: string[] = [];
|
|
30
|
-
for (let index =
|
|
31
|
-
const argument =
|
|
27
|
+
for (let index = 0; index < searchArgs.length; index += 1) {
|
|
28
|
+
const argument = searchArgs[index];
|
|
32
29
|
if (argument === "--json") {
|
|
33
30
|
json = true;
|
|
34
31
|
continue;
|
|
35
32
|
}
|
|
36
33
|
if (argument === "--limit" || argument === "--wiki-id" || argument === "--max-chars-per-hit") {
|
|
37
|
-
const value =
|
|
34
|
+
const value = searchArgs[index + 1];
|
|
38
35
|
if (value === undefined || value.startsWith("--")) {
|
|
39
36
|
io.stderr.write(`external-wiki: ${argument} requires a value\n`);
|
|
40
37
|
return 2;
|
|
@@ -68,25 +65,36 @@ export async function runExternalWikiCliCommand(
|
|
|
68
65
|
|
|
69
66
|
const query = queryParts.join(" ").trim();
|
|
70
67
|
if (query.length === 0) {
|
|
71
|
-
io.stderr.write(
|
|
72
|
-
return
|
|
68
|
+
io.stderr.write("external-wiki: provide a search query\n");
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
const enabled = roots.filter((root) => root.enabled !== false);
|
|
72
|
+
if (enabled.length === 0) {
|
|
73
|
+
io.stderr.write("external-wiki: no enabled wiki roots configured\n");
|
|
74
|
+
return 1;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
let result: ExternalWikiSearchResult;
|
|
78
|
+
try {
|
|
79
|
+
result = await searchExternalWikis(enabled, { query, limit, wikiId, maxCharsPerHit });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
io.stderr.write(`external-wiki: ${error instanceof Error ? error.message : "search failed"}\n`);
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
76
84
|
if (json) {
|
|
77
85
|
io.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
78
86
|
return 0;
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
io.stdout.write(`External wiki results for "${result.query}" (${result.count})\n`);
|
|
82
|
-
for (const hit of result.hits) {
|
|
83
|
-
const citation = hit.citations[0];
|
|
84
|
-
const location = citation ? `${citation.path}:${citation.lineStart}-${citation.lineEnd}` : hit.path;
|
|
85
|
-
io.stdout.write(`${hit.rank}. [${hit.wikiId}] ${hit.title} (${location})\n`);
|
|
86
|
-
io.stdout.write(` ${hit.snippet.replace(/\s+/g, " ").trim()}\n`);
|
|
87
|
-
}
|
|
88
89
|
if (result.degradedWikiIds.length > 0) {
|
|
89
|
-
io.stderr.write(`
|
|
90
|
+
io.stderr.write(`Warning: degraded roots: ${result.degradedWikiIds.join(", ")}\n`);
|
|
91
|
+
}
|
|
92
|
+
if (result.hits.length === 0) {
|
|
93
|
+
io.stdout.write("No results.\n");
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
for (const hit of result.hits) {
|
|
97
|
+
io.stdout.write(`[${hit.wikiId}] ${hit.path} (score ${hit.score})\n${hit.snippet}\n\n`);
|
|
90
98
|
}
|
|
91
99
|
return 0;
|
|
92
100
|
}
|
|
@@ -257,7 +257,7 @@ test("external wiki search fails open to filesystem candidates when a dedicated
|
|
|
257
257
|
}
|
|
258
258
|
});
|
|
259
259
|
|
|
260
|
-
test("external wiki CLI
|
|
260
|
+
test("external wiki CLI preserves text contracts and supports cited JSON", async () => {
|
|
261
261
|
const root = await createWiki("cli", {
|
|
262
262
|
"INDEX.md": "- [[wiki/agents|Agent Planning]] - planner fan-out with citations\n",
|
|
263
263
|
"wiki/agents.md": "# Agent Planning\n\nPlanner fan-out uses cited retrieval evidence.\n",
|
|
@@ -286,7 +286,7 @@ test("external wiki CLI searches multi-word queries and renders cited JSON or te
|
|
|
286
286
|
stderr: { write: () => undefined },
|
|
287
287
|
});
|
|
288
288
|
assert.equal(textCode, 0);
|
|
289
|
-
assert.match(textOutput.join(""), /\[cli\]
|
|
289
|
+
assert.match(textOutput.join(""), /\[cli\] wiki\/agents\.md \(score [\d.]+\)/);
|
|
290
290
|
|
|
291
291
|
const invalidErrors: string[] = [];
|
|
292
292
|
const invalidCode = await runExternalWikiCliCommand([root], ["search", "--limit", "0", "query"], {
|
|
@@ -295,6 +295,72 @@ test("external wiki CLI searches multi-word queries and renders cited JSON or te
|
|
|
295
295
|
});
|
|
296
296
|
assert.equal(invalidCode, 2);
|
|
297
297
|
assert.match(invalidErrors.join(""), /--limit must be an integer from 1 to 20/);
|
|
298
|
+
|
|
299
|
+
const noQueryErrors: string[] = [];
|
|
300
|
+
const noQueryCode = await runExternalWikiCliCommand([root], ["search"], {
|
|
301
|
+
stdout: { write: () => undefined },
|
|
302
|
+
stderr: { write: (chunk) => noQueryErrors.push(chunk) },
|
|
303
|
+
});
|
|
304
|
+
assert.equal(noQueryCode, 1);
|
|
305
|
+
assert.equal(noQueryErrors.join(""), "external-wiki: provide a search query\n");
|
|
306
|
+
|
|
307
|
+
const disabledErrors: string[] = [];
|
|
308
|
+
const disabledCode = await runExternalWikiCliCommand([{ ...root, enabled: false }], ["search", "planner"], {
|
|
309
|
+
stdout: { write: () => undefined },
|
|
310
|
+
stderr: { write: (chunk) => disabledErrors.push(chunk) },
|
|
311
|
+
});
|
|
312
|
+
assert.equal(disabledCode, 1);
|
|
313
|
+
assert.equal(disabledErrors.join(""), "external-wiki: no enabled wiki roots configured\n");
|
|
314
|
+
|
|
315
|
+
const emptyOutput: string[] = [];
|
|
316
|
+
const emptyCode = await runExternalWikiCliCommand([root], ["search", "unmatched-token"], {
|
|
317
|
+
stdout: { write: (chunk) => emptyOutput.push(chunk) },
|
|
318
|
+
stderr: { write: () => undefined },
|
|
319
|
+
});
|
|
320
|
+
assert.equal(emptyCode, 0);
|
|
321
|
+
assert.equal(emptyOutput.join(""), "No results.\n");
|
|
322
|
+
|
|
323
|
+
const searchErrors: string[] = [];
|
|
324
|
+
const searchErrorCode = await runExternalWikiCliCommand([root], ["search", "planner", "--wiki-id", "missing"], {
|
|
325
|
+
stdout: { write: () => undefined },
|
|
326
|
+
stderr: { write: (chunk) => searchErrors.push(chunk) },
|
|
327
|
+
});
|
|
328
|
+
assert.equal(searchErrorCode, 1);
|
|
329
|
+
assert.equal(searchErrors.join(""), "external-wiki: unknown external wiki: missing\n");
|
|
330
|
+
|
|
331
|
+
const legacyOutput: string[] = [];
|
|
332
|
+
const legacyCode = await runExternalWikiCliCommand([root], ["planner", "fan-out"], {
|
|
333
|
+
stdout: { write: (chunk) => legacyOutput.push(chunk) },
|
|
334
|
+
stderr: { write: () => undefined },
|
|
335
|
+
});
|
|
336
|
+
assert.equal(legacyCode, 0);
|
|
337
|
+
assert.match(legacyOutput.join(""), /^\[cli\] wiki\/agents\.md \(score [\d.]+\)/);
|
|
338
|
+
|
|
339
|
+
const degradedErrors: string[] = [];
|
|
340
|
+
const degradedCode = await runExternalWikiCliCommand(
|
|
341
|
+
[root, { ...root, id: "broken", rootDir: path.join(root.rootDir, "missing") }],
|
|
342
|
+
["search", "planner"],
|
|
343
|
+
{
|
|
344
|
+
stdout: { write: () => undefined },
|
|
345
|
+
stderr: { write: (chunk) => degradedErrors.push(chunk) },
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
assert.equal(degradedCode, 0);
|
|
349
|
+
assert.equal(degradedErrors.join(""), "Warning: degraded roots: broken\n");
|
|
350
|
+
|
|
351
|
+
const allDegradedOutput: string[] = [];
|
|
352
|
+
const allDegradedErrors: string[] = [];
|
|
353
|
+
const allDegradedCode = await runExternalWikiCliCommand(
|
|
354
|
+
[{ ...root, id: "broken", rootDir: path.join(root.rootDir, "missing") }],
|
|
355
|
+
["search", "planner"],
|
|
356
|
+
{
|
|
357
|
+
stdout: { write: (chunk) => allDegradedOutput.push(chunk) },
|
|
358
|
+
stderr: { write: (chunk) => allDegradedErrors.push(chunk) },
|
|
359
|
+
}
|
|
360
|
+
);
|
|
361
|
+
assert.equal(allDegradedCode, 0);
|
|
362
|
+
assert.equal(allDegradedOutput.join(""), "No results.\n");
|
|
363
|
+
assert.equal(allDegradedErrors.join(""), "Warning: degraded roots: broken\n");
|
|
298
364
|
} finally {
|
|
299
365
|
await removeWikis([root]);
|
|
300
366
|
}
|
package/src/external-wiki.ts
CHANGED
|
@@ -170,9 +170,18 @@ function normalizeCatalogPath(target: string, config: ExternalWikiRoot, base: Ca
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
const pagesDir = path.posix.normalize(config.pagesDir);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
let relative: string;
|
|
174
|
+
if (base === "index") {
|
|
175
|
+
const baseDir = path.posix.dirname(config.indexFile);
|
|
176
|
+
const rootRelativeTarget = path.posix.normalize(path.posix.join(baseDir, withoutFragment));
|
|
177
|
+
relative = path.posix.relative(pagesDir, rootRelativeTarget);
|
|
178
|
+
} else {
|
|
179
|
+
const pagesPrefix = pagesDir === "." ? "" : `${pagesDir}/`;
|
|
180
|
+
const stripped = pagesPrefix.length > 0 && withoutFragment.startsWith(pagesPrefix)
|
|
181
|
+
? withoutFragment.slice(pagesPrefix.length)
|
|
182
|
+
: withoutFragment;
|
|
183
|
+
relative = path.posix.normalize(stripped);
|
|
184
|
+
}
|
|
176
185
|
if (
|
|
177
186
|
relative === ".." ||
|
|
178
187
|
relative.startsWith("../") ||
|
|
@@ -416,38 +425,3 @@ export async function readExternalWikiPage(
|
|
|
416
425
|
bytes: page.bytes,
|
|
417
426
|
};
|
|
418
427
|
}
|
|
419
|
-
|
|
420
|
-
export async function runExternalWikiCliCommand(
|
|
421
|
-
roots: readonly ExternalWikiRoot[],
|
|
422
|
-
rest: readonly string[],
|
|
423
|
-
io: { stdout: NodeJS.WritableStream; stderr: NodeJS.WritableStream },
|
|
424
|
-
): Promise<number> {
|
|
425
|
-
const { searchExternalWikis } = await import("./external-wiki-search.js");
|
|
426
|
-
const query = rest.filter((a) => !a.startsWith("-")).join(" ").trim();
|
|
427
|
-
if (!query) {
|
|
428
|
-
io.stderr.write("external-wiki: provide a search query\n");
|
|
429
|
-
return 1;
|
|
430
|
-
}
|
|
431
|
-
const enabled = roots.filter((r) => r.enabled !== false);
|
|
432
|
-
if (enabled.length === 0) {
|
|
433
|
-
io.stderr.write("external-wiki: no enabled wiki roots configured\n");
|
|
434
|
-
return 1;
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
const result = await searchExternalWikis(enabled, { query });
|
|
438
|
-
if (result.hits.length === 0) {
|
|
439
|
-
io.stdout.write("No results.\n");
|
|
440
|
-
return 0;
|
|
441
|
-
}
|
|
442
|
-
for (const hit of result.hits) {
|
|
443
|
-
io.stdout.write(`[${hit.wikiId}] ${hit.path} (score ${hit.score})\n${hit.snippet}\n\n`);
|
|
444
|
-
}
|
|
445
|
-
if (result.degradedWikiIds.length > 0) {
|
|
446
|
-
io.stderr.write(`Warning: degraded roots: ${result.degradedWikiIds.join(", ")}\n`);
|
|
447
|
-
}
|
|
448
|
-
return 0;
|
|
449
|
-
} catch (error) {
|
|
450
|
-
io.stderr.write(`external-wiki: ${error instanceof Error ? error.message : "search failed"}\n`);
|
|
451
|
-
return 1;
|
|
452
|
-
}
|
|
453
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
* This package has ZERO OpenClaw imports — it can be consumed by
|
|
10
10
|
* any host adapter (CLI, HTTP server, MCP server, etc.).
|
|
11
11
|
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* import { Orchestrator, parseConfig } from "@remnic/core";
|
|
14
|
-
* const config = parseConfig({ memoryDir: "/tmp/mem" });
|
|
15
|
-
* const orch = new Orchestrator(config);
|
|
16
|
-
* await orch.initialize();
|
|
17
12
|
*/
|
|
18
13
|
|
|
19
14
|
// ---------------------------------------------------------------------------
|
|
@@ -29,6 +24,7 @@ export { resolvePluginEntry, type PluginEntryResolverOptions } from "./plugin-en
|
|
|
29
24
|
export { parseConfig, isOpenaiApiKeyDisabled, resolveEnvVars } from "./config.js";
|
|
30
25
|
export { resolveRemnicConfigRecord } from "./config-record.js";
|
|
31
26
|
export * from "./external-wiki.js";
|
|
27
|
+
export * from "./external-wiki-cli.js";
|
|
32
28
|
export {
|
|
33
29
|
parseFlexibleIsoTimestamp,
|
|
34
30
|
parseIsoOffsetTimestamp,
|
|
@@ -278,7 +278,7 @@ export async function runFirstStartMigration(
|
|
|
278
278
|
qmd,
|
|
279
279
|
hotCollection: options.hotCollection ?? config.qmdCollection ?? "openclaw-engram",
|
|
280
280
|
coldCollection: options.coldCollection ?? config.qmdColdCollection ?? "openclaw-engram-cold",
|
|
281
|
-
|
|
281
|
+
})
|
|
282
282
|
: null;
|
|
283
283
|
const coldCollection = options.coldCollection ?? config.qmdColdCollection ?? "openclaw-engram-cold";
|
|
284
284
|
|
|
@@ -287,17 +287,17 @@ export async function runFirstStartMigration(
|
|
|
287
287
|
return abortedResult(candidateCount, demotedCount, failureCount);
|
|
288
288
|
}
|
|
289
289
|
try {
|
|
290
|
-
|
|
291
|
-
await executor.migrateMemory({
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
const result = executor
|
|
291
|
+
? await executor.migrateMemory({
|
|
292
|
+
memory,
|
|
293
|
+
fromTier: "hot",
|
|
294
|
+
toTier: "cold",
|
|
295
|
+
reason: "first-start-lifecycle-migration",
|
|
296
|
+
})
|
|
297
|
+
: await storage.migrateMemoryToTier(memory, "cold");
|
|
298
|
+
if (result.changed) {
|
|
299
|
+
demotedCount += 1;
|
|
299
300
|
}
|
|
300
|
-
demotedCount += 1;
|
|
301
301
|
} catch {
|
|
302
302
|
const targetPath = storage.buildTierMemoryPath(memory, "cold");
|
|
303
303
|
const [targetExists, sourceExists] = await Promise.all([
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MemoryFile } from "./types.js";
|
|
2
|
+
|
|
3
|
+
function sortSnapshotObject(_key: string, candidate: unknown): unknown {
|
|
4
|
+
if (candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) return candidate;
|
|
5
|
+
return Object.keys(candidate)
|
|
6
|
+
.sort()
|
|
7
|
+
.reduce<Record<string, unknown>>((sorted, key) => {
|
|
8
|
+
sorted[key] = (candidate as Record<string, unknown>)[key];
|
|
9
|
+
return sorted;
|
|
10
|
+
}, {});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createMemorySnapshot(memory: MemoryFile): string {
|
|
14
|
+
return JSON.stringify([memory.frontmatter, memory.content], sortSnapshotObject);
|
|
15
|
+
}
|
|
@@ -435,3 +435,102 @@ test("citation path lookup resolves collection-prefixed QMD paths", async () =>
|
|
|
435
435
|
await rm(dir, { recursive: true, force: true });
|
|
436
436
|
}
|
|
437
437
|
});
|
|
438
|
+
test("memory snapshots ignore object key order", async () => {
|
|
439
|
+
const { createMemorySnapshot } = await import("../memory-snapshot.js");
|
|
440
|
+
type SnapshotInput = Parameters<typeof createMemorySnapshot>[0];
|
|
441
|
+
const first = {
|
|
442
|
+
content: "payload",
|
|
443
|
+
frontmatter: { z: 1, nested: { y: 2, x: 3 } },
|
|
444
|
+
} as unknown as SnapshotInput;
|
|
445
|
+
const second = {
|
|
446
|
+
frontmatter: { nested: { x: 3, y: 2 }, z: 1 },
|
|
447
|
+
content: "payload",
|
|
448
|
+
} as unknown as SnapshotInput;
|
|
449
|
+
assert.equal(createMemorySnapshot(first), createMemorySnapshot(second));
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("tier move: caller-only mutation is not persisted by moveMemoryToPath", async () => {
|
|
453
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-caller-mut-"));
|
|
454
|
+
try {
|
|
455
|
+
resetStaticCaches();
|
|
456
|
+
const storage = new StorageManager(dir);
|
|
457
|
+
await storage.ensureDirectories();
|
|
458
|
+
|
|
459
|
+
const { id } = await storage.writeMemory("fact", "original disk content");
|
|
460
|
+
const memories = await storage.readAllMemories();
|
|
461
|
+
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
462
|
+
assert.ok(memory, "memory must exist");
|
|
463
|
+
|
|
464
|
+
memory.content = "caller mutated unpersisted content";
|
|
465
|
+
|
|
466
|
+
const targetPath = storage.buildTierMemoryPath(memory, "cold");
|
|
467
|
+
await storage.moveMemoryToPath(memory, targetPath);
|
|
468
|
+
|
|
469
|
+
const onDisk = await readFile(targetPath, "utf8");
|
|
470
|
+
assert.ok(onDisk.includes("original disk content"), "Moved file on disk must retain original content");
|
|
471
|
+
assert.ok(!onDisk.includes("caller mutated unpersisted content"), "Moved file on disk must not carry caller's unpersisted content mutation");
|
|
472
|
+
} finally {
|
|
473
|
+
resetStaticCaches();
|
|
474
|
+
await rm(dir, { recursive: true, force: true });
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("tier move: stale on-disk mutation fails snapshot check during move", async () => {
|
|
479
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-stale-disk-"));
|
|
480
|
+
try {
|
|
481
|
+
resetStaticCaches();
|
|
482
|
+
const storage = new StorageManager(dir);
|
|
483
|
+
await storage.ensureDirectories();
|
|
484
|
+
|
|
485
|
+
const { id } = await storage.writeMemory("fact", "unmodified content");
|
|
486
|
+
const memories = await storage.readAllMemories();
|
|
487
|
+
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
488
|
+
assert.ok(memory, "memory must exist");
|
|
489
|
+
|
|
490
|
+
const diskContent = await readFile(memory.path, "utf8");
|
|
491
|
+
await writeFile(memory.path, diskContent.replace("unmodified content", "external edit on disk"));
|
|
492
|
+
|
|
493
|
+
const targetPath = storage.buildTierMemoryPath(memory, "cold");
|
|
494
|
+
await assert.rejects(
|
|
495
|
+
async () => {
|
|
496
|
+
await storage.moveMemoryToPath(memory, targetPath);
|
|
497
|
+
},
|
|
498
|
+
(err: Error) => {
|
|
499
|
+
return err.message.includes("changed before its tier move");
|
|
500
|
+
}
|
|
501
|
+
);
|
|
502
|
+
} finally {
|
|
503
|
+
resetStaticCaches();
|
|
504
|
+
await rm(dir, { recursive: true, force: true });
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("tier move: cold-to-hot move invalidates cold cache and cannot remain in readAllColdMemories", async () => {
|
|
509
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-cold-to-hot-"));
|
|
510
|
+
try {
|
|
511
|
+
resetStaticCaches();
|
|
512
|
+
const storage = new StorageManager(dir);
|
|
513
|
+
await storage.ensureDirectories();
|
|
514
|
+
|
|
515
|
+
const { id } = await storage.writeMemory("fact", "demoted to cold then promoted");
|
|
516
|
+
const memories = await storage.readAllMemories();
|
|
517
|
+
const memory = memories.find((m) => m.frontmatter.id === id);
|
|
518
|
+
assert.ok(memory, "memory must exist");
|
|
519
|
+
|
|
520
|
+
const { targetPath: coldPath } = await storage.migrateMemoryToTier(memory, "cold");
|
|
521
|
+
|
|
522
|
+
const coldMemories = await storage.readAllColdMemories();
|
|
523
|
+
const coldMem = coldMemories.find((m) => m.frontmatter.id === id);
|
|
524
|
+
assert.ok(coldMem, "memory must be present in cold tier read");
|
|
525
|
+
|
|
526
|
+
const { changed } = await storage.migrateMemoryToTier(coldMem, "hot");
|
|
527
|
+
assert.equal(changed, true, "migration from cold to hot must succeed");
|
|
528
|
+
|
|
529
|
+
const coldMemoriesAfter = await storage.readAllColdMemories();
|
|
530
|
+
const foundInCold = coldMemoriesAfter.some((m) => m.frontmatter.id === id);
|
|
531
|
+
assert.equal(foundInCold, false, "promoted memory must not remain in readAllColdMemories cache");
|
|
532
|
+
} finally {
|
|
533
|
+
resetStaticCaches();
|
|
534
|
+
await rm(dir, { recursive: true, force: true });
|
|
535
|
+
}
|
|
536
|
+
});
|