@remnic/core 9.15.2 → 9.16.0
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 +8 -8
- package/dist/access-admin-ops-surface.js +7 -7
- package/dist/access-boundary.d.ts +8 -8
- package/dist/access-boundary.js +7 -7
- package/dist/access-cli.js +31 -30
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +8 -8
- package/dist/access-http.js +11 -11
- package/dist/access-identity-continuity-surface.d.ts +6 -6
- package/dist/access-identity-continuity-surface.js +7 -7
- package/dist/access-lcm-surface.d.ts +8 -8
- package/dist/access-lcm-surface.js +7 -7
- package/dist/access-mcp.d.ts +8 -8
- package/dist/access-mcp.js +10 -10
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-B9e5GnyZ.d.ts → access-namespace-preflight-DwhEFYy3.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +7 -7
- package/dist/access-observe-write-surface.d.ts +8 -8
- package/dist/access-observe-write-surface.js +7 -7
- package/dist/access-operations-batch.js +8 -8
- package/dist/access-operations.d.ts +11 -11
- package/dist/access-operations.js +9 -9
- package/dist/access-recall-concurrency.d.ts +8 -8
- package/dist/access-recall-concurrency.js +7 -7
- package/dist/access-recall-response.d.ts +8 -8
- package/dist/access-recall-response.js +7 -7
- package/dist/access-recall-surface.d.ts +8 -8
- package/dist/access-recall-surface.js +7 -7
- package/dist/access-schema.d.ts +76 -76
- package/dist/{access-service-DgmJd_A-.d.ts → access-service-CSq1Wp2Z.d.ts} +6 -6
- package/dist/access-service.d.ts +8 -8
- package/dist/access-service.js +7 -7
- package/dist/access-surface-catalog.d.ts +8 -8
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +6 -6
- package/dist/briefing.d.ts +2 -2
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-g_dfT6oL.d.ts → catalog-B9uga4sG.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-IRX5UCGN.js → chunk-2OH3ZRYH.js} +3 -3
- package/dist/{chunk-YXTSUIXC.js → chunk-5EKOBRCA.js} +6 -1
- package/dist/chunk-5EKOBRCA.js.map +1 -0
- package/dist/{chunk-42DJZZUB.js → chunk-7LXSPIVG.js} +2 -2
- package/dist/{chunk-47WFEKT7.js → chunk-7PK4W4RN.js} +2 -2
- package/dist/{chunk-O7ACJAHR.js → chunk-7YJLCNPF.js} +2 -2
- package/dist/{chunk-6EJL2O3G.js → chunk-AFH7PIFU.js} +2 -2
- package/dist/{chunk-JN4N2TMQ.js → chunk-ANGUOE2N.js} +7 -7
- package/dist/{chunk-GQOKO77V.js → chunk-AWT7SCWQ.js} +2 -2
- package/dist/{chunk-PXJEL55X.js → chunk-BIRPR5N7.js} +2 -2
- package/dist/{chunk-NAY25BGS.js → chunk-BTK2PIFL.js} +6 -6
- package/dist/{chunk-SYCVU556.js → chunk-C2RMQRPN.js} +3 -3
- package/dist/{chunk-T4EAQPQQ.js → chunk-G2WCVFDC.js} +2 -2
- package/dist/{chunk-UJIBXOJA.js → chunk-GEDBTSYU.js} +5 -5
- package/dist/{chunk-CXFVZ3PK.js → chunk-GRJ5UOFO.js} +84 -658
- package/dist/chunk-GRJ5UOFO.js.map +1 -0
- package/dist/chunk-GSQASASV.js +429 -0
- package/dist/chunk-GSQASASV.js.map +1 -0
- package/dist/{chunk-WIKOHCQZ.js → chunk-I6ODVWQB.js} +2 -2
- package/dist/{chunk-H4MDUPER.js → chunk-IZHXSLO7.js} +2 -2
- package/dist/{chunk-RRS5CDB6.js → chunk-JOCXI2O5.js} +2 -2
- package/dist/{chunk-EEUZQQRJ.js → chunk-KGKYTCDO.js} +2 -2
- package/dist/{chunk-JTW4TGPV.js → chunk-L3HAUO7K.js} +2 -2
- package/dist/{chunk-P53HGVJS.js → chunk-LMVAIZR5.js} +2 -2
- package/dist/{chunk-KLXG4BBO.js → chunk-M6TZQDTX.js} +2 -2
- package/dist/{chunk-FJ5IQ3SI.js → chunk-N7DK67GZ.js} +1 -1
- package/dist/chunk-N7DK67GZ.js.map +1 -0
- package/dist/{chunk-U6QWUFSM.js → chunk-OXT5TQDS.js} +2 -2
- package/dist/{chunk-T4OTDMX4.js → chunk-QDTEH6BS.js} +2 -2
- package/dist/{chunk-5MDE5MIC.js → chunk-SESF64QR.js} +2 -2
- package/dist/{chunk-4E5H4FPC.js → chunk-TUNI4GB3.js} +3 -3
- package/dist/{chunk-XJT6LNLY.js → chunk-V6LYAXGW.js} +834 -25
- package/dist/chunk-V6LYAXGW.js.map +1 -0
- package/dist/{chunk-5HFIHWOF.js → chunk-VLIY55Q5.js} +17 -17
- package/dist/{chunk-BNH2VVWP.js → chunk-WIS5JTJR.js} +2 -2
- package/dist/{chunk-OOGGUXAG.js → chunk-WQ2I6ODB.js} +2 -2
- package/dist/{cli-BXCd2cgK.d.ts → cli-DYLGXxuu.d.ts} +5 -5
- package/dist/cli.d.ts +10 -10
- package/dist/cli.js +25 -24
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/engine.js +3 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +693 -503
- package/dist/index.js +70 -45
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +8 -8
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +3 -3
- package/dist/namespaces/migrate.js +4 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +2 -2
- package/dist/operator-toolkit.js +9 -8
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +10 -7
- package/dist/orchestration/maintenance.js +7 -6
- package/dist/{orchestrator-BHfWtvEJ.d.ts → orchestrator-BHx-DWCa.d.ts} +7 -5
- package/dist/orchestrator.d.ts +6 -6
- package/dist/orchestrator.js +31 -30
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-CBYcnmDC.d.ts → pipeline-DDHSE876.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-BIq-jrV4.d.ts → qmd-C4WVh6BM.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -2
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/{scope-profiles-DDno-uvy.d.ts → scope-profiles-EqMXwzCz.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-7RIJFDwY.d.ts → semantic-consolidation-CYpvwmQc.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-DkphZiOJ.d.ts → storage-Dxd7zVZH.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-2q_MRFcM.d.ts → types-OUCJALBK.d.ts} +98 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/activity/config.test.ts +113 -0
- package/src/activity/config.ts +111 -0
- package/src/activity/digest.ts +2 -2
- package/src/activity/index.ts +6 -0
- package/src/activity/pipeline.test.ts +379 -0
- package/src/activity/pipeline.ts +223 -0
- package/src/activity/reindex.test.ts +97 -0
- package/src/activity/reindex.ts +40 -0
- package/src/activity/runner.test.ts +452 -0
- package/src/activity/runner.ts +237 -0
- package/src/activity/scheduler.test.ts +303 -0
- package/src/activity/scheduler.ts +147 -0
- package/src/activity/source-client.test.ts +208 -0
- package/src/activity/source-client.ts +159 -0
- package/src/activity/sync-registration.test.ts +134 -0
- package/src/activity/sync-registration.ts +139 -0
- package/src/activity/types.ts +17 -0
- package/src/config.test.ts +19 -0
- package/src/config.ts +3 -0
- package/src/orchestration/maintenance.test.ts +42 -42
- package/src/orchestration/maintenance.ts +37 -5
- package/src/orchestration/orchestrator-helpers.ts +36 -1
- package/src/orchestration/recall-internal.ts +6 -6
- package/src/orchestration/recall-search-pipeline.ts +15 -7
- package/src/orchestrator-background-writes.test.ts +2 -2
- package/src/orchestrator.ts +3 -3
- package/src/types.ts +4 -1
- package/dist/chunk-CXFVZ3PK.js.map +0 -1
- package/dist/chunk-FJ5IQ3SI.js.map +0 -1
- package/dist/chunk-XJT6LNLY.js.map +0 -1
- package/dist/chunk-YXTSUIXC.js.map +0 -1
- /package/dist/{chunk-IRX5UCGN.js.map → chunk-2OH3ZRYH.js.map} +0 -0
- /package/dist/{chunk-42DJZZUB.js.map → chunk-7LXSPIVG.js.map} +0 -0
- /package/dist/{chunk-47WFEKT7.js.map → chunk-7PK4W4RN.js.map} +0 -0
- /package/dist/{chunk-O7ACJAHR.js.map → chunk-7YJLCNPF.js.map} +0 -0
- /package/dist/{chunk-6EJL2O3G.js.map → chunk-AFH7PIFU.js.map} +0 -0
- /package/dist/{chunk-JN4N2TMQ.js.map → chunk-ANGUOE2N.js.map} +0 -0
- /package/dist/{chunk-GQOKO77V.js.map → chunk-AWT7SCWQ.js.map} +0 -0
- /package/dist/{chunk-PXJEL55X.js.map → chunk-BIRPR5N7.js.map} +0 -0
- /package/dist/{chunk-NAY25BGS.js.map → chunk-BTK2PIFL.js.map} +0 -0
- /package/dist/{chunk-SYCVU556.js.map → chunk-C2RMQRPN.js.map} +0 -0
- /package/dist/{chunk-T4EAQPQQ.js.map → chunk-G2WCVFDC.js.map} +0 -0
- /package/dist/{chunk-UJIBXOJA.js.map → chunk-GEDBTSYU.js.map} +0 -0
- /package/dist/{chunk-WIKOHCQZ.js.map → chunk-I6ODVWQB.js.map} +0 -0
- /package/dist/{chunk-H4MDUPER.js.map → chunk-IZHXSLO7.js.map} +0 -0
- /package/dist/{chunk-RRS5CDB6.js.map → chunk-JOCXI2O5.js.map} +0 -0
- /package/dist/{chunk-EEUZQQRJ.js.map → chunk-KGKYTCDO.js.map} +0 -0
- /package/dist/{chunk-JTW4TGPV.js.map → chunk-L3HAUO7K.js.map} +0 -0
- /package/dist/{chunk-P53HGVJS.js.map → chunk-LMVAIZR5.js.map} +0 -0
- /package/dist/{chunk-KLXG4BBO.js.map → chunk-M6TZQDTX.js.map} +0 -0
- /package/dist/{chunk-U6QWUFSM.js.map → chunk-OXT5TQDS.js.map} +0 -0
- /package/dist/{chunk-T4OTDMX4.js.map → chunk-QDTEH6BS.js.map} +0 -0
- /package/dist/{chunk-5MDE5MIC.js.map → chunk-SESF64QR.js.map} +0 -0
- /package/dist/{chunk-4E5H4FPC.js.map → chunk-TUNI4GB3.js.map} +0 -0
- /package/dist/{chunk-5HFIHWOF.js.map → chunk-VLIY55Q5.js.map} +0 -0
- /package/dist/{chunk-BNH2VVWP.js.map → chunk-WIS5JTJR.js.map} +0 -0
- /package/dist/{chunk-OOGGUXAG.js.map → chunk-WQ2I6ODB.js.map} +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
activityDayWindow,
|
|
9
|
+
activityDigestPath,
|
|
10
|
+
composeActivityDigestBody,
|
|
11
|
+
composeActivityDigestMeta,
|
|
12
|
+
isValidActivityDate,
|
|
13
|
+
serializeActivityDigest,
|
|
14
|
+
} from "./digest.js";
|
|
15
|
+
import type { ActivityStore } from "./store.js";
|
|
16
|
+
import type { ActivitySnapshot, ActivitySourceClient } from "./types.js";
|
|
17
|
+
|
|
18
|
+
const MAX_SYNC_PAGES = 10_000;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sync cursors are scoped per (machine, local day): each date resumes its own
|
|
22
|
+
* pagination independently, so a multi-day window never inherits an earlier
|
|
23
|
+
* day's cursor (which could skip backfill). Encoded into the store's single
|
|
24
|
+
* key column with a NUL separator that cannot occur in a machine label.
|
|
25
|
+
*/
|
|
26
|
+
export function activityCursorKey(machine: string, date: string): string {
|
|
27
|
+
return `${machine}\u0000${date}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Serializes the list -> compose -> write digest section per (memoryDir, date)
|
|
31
|
+
// so two sources syncing the same day cannot lose-update each other's digest:
|
|
32
|
+
// the last writer in the chain lists every row present and wins.
|
|
33
|
+
const digestLocks = new Map<string, Promise<unknown>>();
|
|
34
|
+
|
|
35
|
+
function withDigestLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
|
|
36
|
+
const prior = digestLocks.get(key) ?? Promise.resolve();
|
|
37
|
+
const next = prior.then(fn, fn);
|
|
38
|
+
const tail = next.then(
|
|
39
|
+
() => undefined,
|
|
40
|
+
() => undefined,
|
|
41
|
+
);
|
|
42
|
+
digestLocks.set(key, tail);
|
|
43
|
+
void tail.then(() => {
|
|
44
|
+
if (digestLocks.get(key) === tail) digestLocks.delete(key);
|
|
45
|
+
});
|
|
46
|
+
return next;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ActivitySyncOptions {
|
|
50
|
+
date: string;
|
|
51
|
+
timezone: string;
|
|
52
|
+
memoryDir: string;
|
|
53
|
+
store: ActivityStore;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
/** Runaway-pagination guard; defaults to MAX_SYNC_PAGES. */
|
|
56
|
+
maxPages?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Search-index refresh, run after a digest is (re)written so the durable
|
|
59
|
+
* markdown becomes discoverable (AGENTS.md rule 31; the digest lives in the
|
|
60
|
+
* QMD collection root but bypasses the extraction->persist->index pipeline).
|
|
61
|
+
* Host-agnostic: the caller injects the core index seam (SearchBackend.update).
|
|
62
|
+
* Best-effort — a failure never fails the sync (rows/digest are durable and
|
|
63
|
+
* index on the next update); it surfaces as `reindexError`.
|
|
64
|
+
*/
|
|
65
|
+
afterWrites?: (signal?: AbortSignal) => Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ActivitySyncResult {
|
|
69
|
+
machine: string;
|
|
70
|
+
fetched: number;
|
|
71
|
+
inserted: number;
|
|
72
|
+
duplicates: number;
|
|
73
|
+
cursor: string | null;
|
|
74
|
+
digestWritten: boolean;
|
|
75
|
+
/** Set when the post-write reindex threw; the sync still succeeded. */
|
|
76
|
+
reindexError?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function snapshotForMachine(snapshot: ActivitySnapshot, machine: string): ActivitySnapshot {
|
|
80
|
+
return {
|
|
81
|
+
machine,
|
|
82
|
+
capturedAtUtc: snapshot.capturedAtUtc,
|
|
83
|
+
app: snapshot.app,
|
|
84
|
+
windowTitle: snapshot.windowTitle,
|
|
85
|
+
...(snapshot.browserUrl === undefined ? {} : { browserUrl: snapshot.browserUrl }),
|
|
86
|
+
text: snapshot.text,
|
|
87
|
+
textSource: snapshot.textSource,
|
|
88
|
+
contentHash: snapshot.contentHash,
|
|
89
|
+
...(snapshot.simhash === undefined ? {} : { simhash: snapshot.simhash }),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function writeDigestIfChanged(memoryDir: string, date: string, serialized: string): Promise<boolean> {
|
|
94
|
+
const target = activityDigestPath(memoryDir, date);
|
|
95
|
+
try {
|
|
96
|
+
if ((await readFile(target, "utf8")) === serialized) return false;
|
|
97
|
+
} catch (error: unknown) {
|
|
98
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") throw error;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
102
|
+
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
103
|
+
await writeFile(temporary, serialized, { encoding: "utf8", mode: 0o600 });
|
|
104
|
+
await rename(temporary, target);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Pull one source's local-day snapshots and persist its cursor only after every
|
|
110
|
+
* page and the derived digest are durable. Replays after a partial failure are
|
|
111
|
+
* safe because ActivityStore deduplicates an exact captured snapshot.
|
|
112
|
+
*/
|
|
113
|
+
export async function syncActivitySource(
|
|
114
|
+
source: ActivitySourceClient,
|
|
115
|
+
options: ActivitySyncOptions,
|
|
116
|
+
): Promise<ActivitySyncResult> {
|
|
117
|
+
if (!isValidActivityDate(options.date)) {
|
|
118
|
+
throw new RangeError(`Invalid activity date "${options.date}"; expected a real YYYY-MM-DD day.`);
|
|
119
|
+
}
|
|
120
|
+
if (source.machineLabel.trim().length === 0) {
|
|
121
|
+
throw new RangeError("activity source machine label must not be empty");
|
|
122
|
+
}
|
|
123
|
+
const maxPages = options.maxPages ?? MAX_SYNC_PAGES;
|
|
124
|
+
if (!Number.isInteger(maxPages) || maxPages < 1) {
|
|
125
|
+
throw new RangeError("activity sync maxPages must be a positive integer");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const cursorKey = activityCursorKey(source.machineLabel, options.date);
|
|
129
|
+
let cursor = options.store.getCursor(cursorKey);
|
|
130
|
+
let pages = 0;
|
|
131
|
+
let fetched = 0;
|
|
132
|
+
let inserted = 0;
|
|
133
|
+
let duplicates = 0;
|
|
134
|
+
const seenCursors = new Set<string>();
|
|
135
|
+
let completed = false;
|
|
136
|
+
|
|
137
|
+
while (pages < maxPages) {
|
|
138
|
+
options.signal?.throwIfAborted();
|
|
139
|
+
const page = await source.fetchSnapshots({
|
|
140
|
+
date: options.date,
|
|
141
|
+
timezone: options.timezone,
|
|
142
|
+
cursor,
|
|
143
|
+
signal: options.signal,
|
|
144
|
+
});
|
|
145
|
+
pages += 1;
|
|
146
|
+
fetched += page.snapshots.length;
|
|
147
|
+
|
|
148
|
+
for (const snapshot of page.snapshots) {
|
|
149
|
+
// Persist every row keyed by its own timestamp: an out-of-window snapshot
|
|
150
|
+
// (replay file, timezone-boundary bug) is durable under its own day and
|
|
151
|
+
// never lost, while the day digest below is a window query so it never
|
|
152
|
+
// pollutes options.date. A malformed instant is rejected loudly here.
|
|
153
|
+
const result = options.store.insertSnapshot(snapshotForMachine(snapshot, source.machineLabel));
|
|
154
|
+
if (result.inserted) inserted += 1;
|
|
155
|
+
else duplicates += 1;
|
|
156
|
+
}
|
|
157
|
+
if (page.nextCursor === null) {
|
|
158
|
+
completed = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (typeof page.nextCursor !== "string" || page.nextCursor.length === 0) {
|
|
162
|
+
throw new TypeError("activity source returned an invalid cursor");
|
|
163
|
+
}
|
|
164
|
+
if (seenCursors.has(page.nextCursor)) {
|
|
165
|
+
throw new Error("activity source returned a repeated cursor");
|
|
166
|
+
}
|
|
167
|
+
seenCursors.add(page.nextCursor);
|
|
168
|
+
cursor = page.nextCursor;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Only a loop that ran out of its page budget without seeing a terminal
|
|
172
|
+
// null cursor is a runaway; completing on the final allowed page is normal.
|
|
173
|
+
if (!completed) {
|
|
174
|
+
throw new Error(`activity source exceeded ${maxPages} pages`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Re-check abort before the durable write section: stop() may have fired after
|
|
178
|
+
// the last page returned, or while this date waited behind digestLocks. An
|
|
179
|
+
// aborted tick must not compose/write a digest or advance the cursor.
|
|
180
|
+
options.signal?.throwIfAborted();
|
|
181
|
+
const digestWritten = await withDigestLock(`${options.memoryDir}\u0000${options.date}`, async () => {
|
|
182
|
+
options.signal?.throwIfAborted();
|
|
183
|
+
const { startUtc, endUtc } = activityDayWindow(options.date, options.timezone);
|
|
184
|
+
const snapshots = options.store.listSnapshotsForDay(null, startUtc, endUtc);
|
|
185
|
+
const body = composeActivityDigestBody(options.date, options.timezone, snapshots);
|
|
186
|
+
const machines = [...new Set(snapshots.map((snapshot) => snapshot.machine))].sort((a, b) =>
|
|
187
|
+
a < b ? -1 : a > b ? 1 : 0,
|
|
188
|
+
);
|
|
189
|
+
const serialized = serializeActivityDigest(composeActivityDigestMeta(options.date, machines, snapshots, body), body);
|
|
190
|
+
return writeDigestIfChanged(options.memoryDir, options.date, serialized);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
options.signal?.throwIfAborted();
|
|
194
|
+
options.store.setCursor(cursorKey, cursor);
|
|
195
|
+
|
|
196
|
+
// Rows and digest are durable and the cursor has advanced; refresh the search
|
|
197
|
+
// index so the new digest is discoverable (rule 31). Best-effort: a failure
|
|
198
|
+
// is reported, not thrown — the data indexes on the next update.
|
|
199
|
+
let reindexError: string | undefined;
|
|
200
|
+
if (digestWritten && options.afterWrites !== undefined) {
|
|
201
|
+
try {
|
|
202
|
+
await options.afterWrites(options.signal);
|
|
203
|
+
} catch (error: unknown) {
|
|
204
|
+
// reindexError is part of the exported ActivitySyncResult. Keep our
|
|
205
|
+
// controlled QMD strict messages, but reduce opaque backend errors (which
|
|
206
|
+
// can embed absolute paths / loader stacks) to a sanitized name+code.
|
|
207
|
+
reindexError =
|
|
208
|
+
error instanceof Error && error.message.startsWith("QMD ")
|
|
209
|
+
? error.message
|
|
210
|
+
: displayErrorDetail(error) || "reindex failed";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
machine: source.machineLabel,
|
|
216
|
+
fetched,
|
|
217
|
+
inserted,
|
|
218
|
+
duplicates,
|
|
219
|
+
cursor,
|
|
220
|
+
digestWritten,
|
|
221
|
+
...(reindexError === undefined ? {} : { reindexError }),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { refreshActivityIndex, type ActivityIndexRefresher } from "./reindex.js";
|
|
5
|
+
|
|
6
|
+
function refresher(overrides: Partial<ActivityIndexRefresher>, calls: string[]): ActivityIndexRefresher {
|
|
7
|
+
return {
|
|
8
|
+
async update() {
|
|
9
|
+
calls.push("update");
|
|
10
|
+
},
|
|
11
|
+
...overrides,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test("refreshActivityIndex prefers the forced+strict collection refresh", async () => {
|
|
16
|
+
const calls: string[] = [];
|
|
17
|
+
const qmd = refresher(
|
|
18
|
+
{
|
|
19
|
+
async updateStrict() {
|
|
20
|
+
calls.push("updateStrict");
|
|
21
|
+
},
|
|
22
|
+
async updateCollectionStrict(collection: string) {
|
|
23
|
+
calls.push(`updateCollectionStrict:${collection}`);
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
calls,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
await refreshActivityIndex(qmd, "openclaw-engram");
|
|
30
|
+
assert.deepEqual(calls, ["updateCollectionStrict:openclaw-engram"], "forces past the fail-open min-interval gate");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("refreshActivityIndex falls back to updateStrict, then update", async () => {
|
|
34
|
+
const strictCalls: string[] = [];
|
|
35
|
+
await refreshActivityIndex(
|
|
36
|
+
refresher(
|
|
37
|
+
{
|
|
38
|
+
async updateStrict() {
|
|
39
|
+
strictCalls.push("updateStrict");
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
strictCalls,
|
|
43
|
+
),
|
|
44
|
+
"openclaw-engram",
|
|
45
|
+
);
|
|
46
|
+
assert.deepEqual(strictCalls, ["updateStrict"], "uses strict global refresh when no collection-strict path exists");
|
|
47
|
+
|
|
48
|
+
const plainCalls: string[] = [];
|
|
49
|
+
await refreshActivityIndex(refresher({}, plainCalls), "openclaw-engram");
|
|
50
|
+
assert.deepEqual(plainCalls, ["update"], "falls back to plain update when no strict path exists");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("refreshActivityIndex skips collection-strict for an empty collection name", async () => {
|
|
54
|
+
const calls: string[] = [];
|
|
55
|
+
await refreshActivityIndex(
|
|
56
|
+
refresher(
|
|
57
|
+
{
|
|
58
|
+
async updateStrict() {
|
|
59
|
+
calls.push("updateStrict");
|
|
60
|
+
},
|
|
61
|
+
async updateCollectionStrict(collection: string) {
|
|
62
|
+
calls.push(`updateCollectionStrict:${collection}`);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
calls,
|
|
66
|
+
),
|
|
67
|
+
" ",
|
|
68
|
+
);
|
|
69
|
+
assert.deepEqual(calls, ["updateStrict"], "no collection -> strict global refresh, never a blank-collection call");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("refreshActivityIndex propagates a strict-refresh failure (no fake success)", async () => {
|
|
73
|
+
const calls: string[] = [];
|
|
74
|
+
const qmd = refresher(
|
|
75
|
+
{
|
|
76
|
+
async updateCollectionStrict() {
|
|
77
|
+
calls.push("updateCollectionStrict");
|
|
78
|
+
throw new Error("QMD update skipped by per-collection failure backoff");
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
calls,
|
|
82
|
+
);
|
|
83
|
+
await assert.rejects(refreshActivityIndex(qmd, "openclaw-engram"), /failure backoff/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("refreshActivityIndex forwards the abort signal to the backend refresh", async () => {
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
let seenSignal: AbortSignal | undefined;
|
|
89
|
+
const qmd: ActivityIndexRefresher = {
|
|
90
|
+
async update() {},
|
|
91
|
+
async updateCollectionStrict(_collection, execution) {
|
|
92
|
+
seenSignal = execution?.signal;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
await refreshActivityIndex(qmd, "openclaw-engram", controller.signal);
|
|
96
|
+
assert.equal(seenSignal, controller.signal, "the tick's abort signal reaches the QMD refresh");
|
|
97
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forced, non-fake search-index refresh for direct activity digest writes
|
|
3
|
+
* (issue #1899; AGENTS.md rule 31). The digest is written straight to the QMD
|
|
4
|
+
* collection root, bypassing the extraction->persist->index pipeline, so it must
|
|
5
|
+
* explicitly trigger a refresh — and that refresh must actually run.
|
|
6
|
+
*
|
|
7
|
+
* Ordinary `SearchBackend.update()` is fail-open: it silently returns when the
|
|
8
|
+
* backend is unavailable, in failure backoff, or inside its min-interval gate.
|
|
9
|
+
* A freshly written digest hit by that gate would stay unsearchable until an
|
|
10
|
+
* unrelated reindex, and the per-day digest-unchanged guard means the next tick
|
|
11
|
+
* won't retry. So prefer the forced/strict contracts: `updateCollectionStrict`
|
|
12
|
+
* bypasses the min-interval gate (force) AND throws on a genuine failure instead
|
|
13
|
+
* of reporting a fake success, then `updateStrict`, then plain `update`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { SearchExecutionOptions } from "../search/port.js";
|
|
17
|
+
|
|
18
|
+
/** The subset of the search backend the activity reindex needs. */
|
|
19
|
+
export interface ActivityIndexRefresher {
|
|
20
|
+
update(execution?: SearchExecutionOptions): Promise<void>;
|
|
21
|
+
updateStrict?(execution?: SearchExecutionOptions): Promise<void>;
|
|
22
|
+
updateCollectionStrict?(collection: string, execution?: SearchExecutionOptions): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function refreshActivityIndex(
|
|
26
|
+
qmd: ActivityIndexRefresher,
|
|
27
|
+
collection: string,
|
|
28
|
+
signal?: AbortSignal,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
const execution = signal === undefined ? undefined : { signal };
|
|
31
|
+
if (typeof qmd.updateCollectionStrict === "function" && collection.trim().length > 0) {
|
|
32
|
+
await qmd.updateCollectionStrict(collection, execution);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (typeof qmd.updateStrict === "function") {
|
|
36
|
+
await qmd.updateStrict(execution);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await qmd.update(execution);
|
|
40
|
+
}
|