@remnic/core 9.3.654 → 9.3.656
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-cli.js +29 -29
- package/dist/access-http.d.ts +4 -4
- package/dist/access-http.js +17 -17
- package/dist/access-mcp.d.ts +4 -4
- package/dist/access-mcp.js +16 -16
- package/dist/access-schema.d.ts +10 -10
- package/dist/{access-service-C8A5hoXJ.d.ts → access-service-D_nbpexW.d.ts} +33 -2
- package/dist/access-service.d.ts +4 -4
- package/dist/access-service.js +15 -15
- 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 +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +3 -3
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.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/{chunk-JMQSYGXS.js → chunk-2BD7DG37.js} +2 -2
- package/dist/{chunk-FVRBLJP6.js → chunk-2MXEVL75.js} +2 -2
- package/dist/{chunk-LJCEWTG3.js → chunk-4UL7VPTD.js} +277 -58
- package/dist/chunk-4UL7VPTD.js.map +1 -0
- package/dist/{chunk-JYN7QNTA.js → chunk-54XF2FY7.js} +17 -17
- package/dist/{chunk-7WEB3FLJ.js → chunk-5PLUC5OB.js} +2 -2
- package/dist/{chunk-JX2RINDR.js → chunk-6G5JEN55.js} +2 -2
- package/dist/{chunk-ZCORQM74.js → chunk-AGJKWOKV.js} +2 -2
- package/dist/{chunk-NE2JBMLN.js → chunk-AZBV4RRY.js} +1 -1
- package/dist/chunk-AZBV4RRY.js.map +1 -0
- package/dist/{chunk-YLZLPVKK.js → chunk-CTAV55JM.js} +344 -1
- package/dist/chunk-CTAV55JM.js.map +1 -0
- package/dist/{chunk-2DSTAWNZ.js → chunk-DIBWFCLA.js} +3 -3
- package/dist/{chunk-NAZWHTYV.js → chunk-DR67OK4E.js} +5 -5
- package/dist/{chunk-XBIACVCO.js → chunk-EC2AYKRX.js} +2 -2
- package/dist/{chunk-JVRPJ7D4.js → chunk-EKQMQQ3U.js} +48 -12
- package/dist/chunk-EKQMQQ3U.js.map +1 -0
- package/dist/{chunk-RGPUQ66K.js → chunk-GCYFUTUC.js} +2 -2
- package/dist/{chunk-JBHXMCYN.js → chunk-GRYAECRV.js} +2 -2
- package/dist/{chunk-BJA6DQOC.js → chunk-GSHW5VVD.js} +5 -5
- package/dist/chunk-GYSYLGNE.js +650 -0
- package/dist/chunk-GYSYLGNE.js.map +1 -0
- package/dist/{chunk-NCGWXCSW.js → chunk-IOZ5WBWD.js} +2 -2
- package/dist/{chunk-QKK64Z6M.js → chunk-JSVFEHLL.js} +7 -5
- package/dist/chunk-JSVFEHLL.js.map +1 -0
- package/dist/{chunk-7LWRCOP7.js → chunk-LZTFCAKE.js} +2 -2
- package/dist/{chunk-2DGQLOOM.js → chunk-M3VYPE2H.js} +1 -1
- package/dist/{chunk-2DGQLOOM.js.map → chunk-M3VYPE2H.js.map} +1 -1
- package/dist/{chunk-6CVI6BP6.js → chunk-NXCK7DO7.js} +2 -2
- package/dist/{chunk-Z5MQI7K2.js → chunk-PEPHBH2W.js} +2 -2
- package/dist/{chunk-PYWNNF2I.js → chunk-QRSKPI62.js} +99 -66
- package/dist/chunk-QRSKPI62.js.map +1 -0
- package/dist/{chunk-XWQ6ERUG.js → chunk-QZRKNA5F.js} +2 -2
- package/dist/{chunk-PS3SYNHP.js → chunk-R5DB26G6.js} +2 -2
- package/dist/{chunk-OL2364SB.js → chunk-RDW5G6DO.js} +1502 -335
- package/dist/chunk-RDW5G6DO.js.map +1 -0
- package/dist/{chunk-YM3LR4LS.js → chunk-SSSXWIBP.js} +5 -5
- package/dist/{chunk-T2C6QJG2.js → chunk-SWDHVH2P.js} +2 -2
- package/dist/{chunk-DBM2BD22.js → chunk-SXYCVRLK.js} +3 -3
- package/dist/{chunk-K6X553JB.js → chunk-TFFZUFEP.js} +7 -5
- package/dist/chunk-TFFZUFEP.js.map +1 -0
- package/dist/{chunk-ENV6RDTD.js → chunk-TIJYQXDI.js} +2 -2
- package/dist/{chunk-BP2EV6W5.js → chunk-VAEAGTEQ.js} +4 -4
- package/dist/{chunk-3RACUBII.js → chunk-WIKMCJUR.js} +2 -2
- package/dist/{chunk-QW6JZO5P.js → chunk-WWMHAMAY.js} +2 -2
- package/dist/{chunk-GPW2E4LN.js → chunk-YEZHZCUO.js} +4 -4
- package/dist/{chunk-5FOCXX5E.js → chunk-YVVQUAOO.js} +3 -3
- package/dist/{chunk-5FOCXX5E.js.map → chunk-YVVQUAOO.js.map} +1 -1
- package/dist/{chunk-3XGWCZ63.js → chunk-YXLT4EMM.js} +2 -2
- package/dist/{chunk-Y2RIIF6H.js → chunk-Z6UDTNY6.js} +2 -2
- package/dist/{cli-uQgvDFNE.d.ts → cli-aYxSuPvP.d.ts} +3 -3
- package/dist/cli.d.ts +5 -5
- package/dist/cli.js +29 -29
- package/dist/compounding/engine.d.ts +1 -1
- 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 +1 -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 +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- 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 +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +3 -3
- package/dist/explicit-cue-recall.js +2 -2
- 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/focused-list-recall.js +2 -2
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +121 -121
- package/dist/index.js +39 -39
- 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/lcm-fallback-read.js +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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- 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 +4 -4
- 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 +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +15 -4
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +14 -14
- package/dist/{orchestrator-B4Y4sWQH.d.ts → orchestrator-D1wcmPNj.d.ts} +17 -14
- package/dist/orchestrator.d.ts +3 -3
- package/dist/orchestrator.js +25 -25
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +5 -1
- package/dist/qmd.js +2 -2
- 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-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-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/resolve-auth-token.d.ts +1 -1
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- 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/schemas.d.ts +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +6 -6
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +6 -6
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +17 -1
- package/dist/search/port.js +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BKd0Pype.d.ts → semantic-consolidation-MWOdNtSE.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 +3 -2
- package/dist/semantic-rule-verifier.js +5 -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 +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/temporal-supersession.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 +1 -1
- 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 +12 -12
- package/dist/{types-BgChEr0M.d.ts → types-CgcCpUrf.d.ts} +51 -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.d.ts +2 -1
- package/dist/verified-recall.js +5 -3
- package/package.json +1 -1
- package/src/access-service-observe-lcm-parity.test.ts +86 -1
- package/src/access-service-observe-scope.test.ts +283 -1
- package/src/access-service-raw-excerpt-read-gate.test.ts +53 -0
- package/src/access-service.ts +391 -93
- package/src/coding/coding-namespace.ts +0 -3
- package/src/config.test.ts +69 -0
- package/src/config.ts +417 -0
- package/src/lcm-fallback-read.ts +2 -6
- package/src/maintenance/namespace-planner.test.ts +1120 -0
- package/src/maintenance/namespace-planner.ts +893 -0
- package/src/namespaces/scope-profiles.test.ts +1074 -0
- package/src/namespaces/scope-profiles.ts +456 -0
- package/src/namespaces/search.test.ts +130 -2
- package/src/namespaces/search.ts +71 -10
- package/src/orchestrator-flush.test.ts +606 -44
- package/src/orchestrator-source-attribution.test.ts +73 -0
- package/src/orchestrator.ts +932 -229
- package/src/qmd-client.test.ts +59 -0
- package/src/qmd.ts +124 -84
- package/src/search/port.ts +16 -0
- package/src/semantic-rule-verifier.ts +13 -6
- package/src/types.ts +64 -0
- package/src/verified-recall.ts +10 -6
- package/dist/chunk-JVRPJ7D4.js.map +0 -1
- package/dist/chunk-K6X553JB.js.map +0 -1
- package/dist/chunk-LJCEWTG3.js.map +0 -1
- package/dist/chunk-MMJANTJX.js +0 -339
- package/dist/chunk-MMJANTJX.js.map +0 -1
- package/dist/chunk-NE2JBMLN.js.map +0 -1
- package/dist/chunk-OL2364SB.js.map +0 -1
- package/dist/chunk-PYWNNF2I.js.map +0 -1
- package/dist/chunk-QKK64Z6M.js.map +0 -1
- package/dist/chunk-YLZLPVKK.js.map +0 -1
- /package/dist/{chunk-JMQSYGXS.js.map → chunk-2BD7DG37.js.map} +0 -0
- /package/dist/{chunk-FVRBLJP6.js.map → chunk-2MXEVL75.js.map} +0 -0
- /package/dist/{chunk-JYN7QNTA.js.map → chunk-54XF2FY7.js.map} +0 -0
- /package/dist/{chunk-7WEB3FLJ.js.map → chunk-5PLUC5OB.js.map} +0 -0
- /package/dist/{chunk-JX2RINDR.js.map → chunk-6G5JEN55.js.map} +0 -0
- /package/dist/{chunk-ZCORQM74.js.map → chunk-AGJKWOKV.js.map} +0 -0
- /package/dist/{chunk-2DSTAWNZ.js.map → chunk-DIBWFCLA.js.map} +0 -0
- /package/dist/{chunk-NAZWHTYV.js.map → chunk-DR67OK4E.js.map} +0 -0
- /package/dist/{chunk-XBIACVCO.js.map → chunk-EC2AYKRX.js.map} +0 -0
- /package/dist/{chunk-RGPUQ66K.js.map → chunk-GCYFUTUC.js.map} +0 -0
- /package/dist/{chunk-JBHXMCYN.js.map → chunk-GRYAECRV.js.map} +0 -0
- /package/dist/{chunk-BJA6DQOC.js.map → chunk-GSHW5VVD.js.map} +0 -0
- /package/dist/{chunk-NCGWXCSW.js.map → chunk-IOZ5WBWD.js.map} +0 -0
- /package/dist/{chunk-7LWRCOP7.js.map → chunk-LZTFCAKE.js.map} +0 -0
- /package/dist/{chunk-6CVI6BP6.js.map → chunk-NXCK7DO7.js.map} +0 -0
- /package/dist/{chunk-Z5MQI7K2.js.map → chunk-PEPHBH2W.js.map} +0 -0
- /package/dist/{chunk-XWQ6ERUG.js.map → chunk-QZRKNA5F.js.map} +0 -0
- /package/dist/{chunk-PS3SYNHP.js.map → chunk-R5DB26G6.js.map} +0 -0
- /package/dist/{chunk-YM3LR4LS.js.map → chunk-SSSXWIBP.js.map} +0 -0
- /package/dist/{chunk-T2C6QJG2.js.map → chunk-SWDHVH2P.js.map} +0 -0
- /package/dist/{chunk-DBM2BD22.js.map → chunk-SXYCVRLK.js.map} +0 -0
- /package/dist/{chunk-ENV6RDTD.js.map → chunk-TIJYQXDI.js.map} +0 -0
- /package/dist/{chunk-BP2EV6W5.js.map → chunk-VAEAGTEQ.js.map} +0 -0
- /package/dist/{chunk-3RACUBII.js.map → chunk-WIKMCJUR.js.map} +0 -0
- /package/dist/{chunk-QW6JZO5P.js.map → chunk-WWMHAMAY.js.map} +0 -0
- /package/dist/{chunk-GPW2E4LN.js.map → chunk-YEZHZCUO.js.map} +0 -0
- /package/dist/{chunk-3XGWCZ63.js.map → chunk-YXLT4EMM.js.map} +0 -0
- /package/dist/{chunk-Y2RIIF6H.js.map → chunk-Z6UDTNY6.js.map} +0 -0
package/src/orchestrator.ts
CHANGED
|
@@ -249,10 +249,12 @@ import {
|
|
|
249
249
|
type HarmonicRetrievalResult,
|
|
250
250
|
} from "./harmonic-retrieval.js";
|
|
251
251
|
import {
|
|
252
|
+
compareVerifiedEpisodeResults,
|
|
252
253
|
searchVerifiedEpisodes,
|
|
253
254
|
type VerifiedEpisodeResult,
|
|
254
255
|
} from "./verified-recall.js";
|
|
255
256
|
import {
|
|
257
|
+
compareVerifiedSemanticRuleResults,
|
|
256
258
|
searchVerifiedSemanticRules,
|
|
257
259
|
type VerifiedSemanticRuleResult,
|
|
258
260
|
} from "./semantic-rule-verifier.js";
|
|
@@ -299,13 +301,16 @@ import {
|
|
|
299
301
|
} from "./conversation-index/backend.js";
|
|
300
302
|
import {
|
|
301
303
|
NamespaceStorageRouter,
|
|
302
|
-
resolveNamespaceStorageRoot,
|
|
303
304
|
} from "./namespaces/storage.js";
|
|
304
305
|
import {
|
|
305
306
|
NamespaceCatalog,
|
|
306
|
-
hasMemoryData,
|
|
307
|
-
type NamespaceRecord,
|
|
308
307
|
} from "./namespaces/catalog.js";
|
|
308
|
+
import {
|
|
309
|
+
planNamespaceMaintenance,
|
|
310
|
+
runNamespaceMaintenanceBatchPlan,
|
|
311
|
+
type NamespaceMaintenancePlan,
|
|
312
|
+
type NamespaceMaintenanceSkipReason,
|
|
313
|
+
} from "./maintenance/namespace-planner.js";
|
|
309
314
|
import {
|
|
310
315
|
namespaceIdentityFromToken,
|
|
311
316
|
namespaceIdentityToken,
|
|
@@ -317,6 +322,11 @@ import {
|
|
|
317
322
|
recallNamespacesForPrincipal,
|
|
318
323
|
resolvePrincipal,
|
|
319
324
|
} from "./namespaces/principal.js";
|
|
325
|
+
import {
|
|
326
|
+
expandScopeProfileReadNamespaces,
|
|
327
|
+
resolveScopeProfilePlan,
|
|
328
|
+
type ResolvedScopeProfilePlan,
|
|
329
|
+
} from "./namespaces/scope-profiles.js";
|
|
320
330
|
import {
|
|
321
331
|
combineNamespaces,
|
|
322
332
|
lcmReadSessionIdsForNamespaces,
|
|
@@ -1767,6 +1777,13 @@ export function resolvePersistedMemoryRelativePath(options: {
|
|
|
1767
1777
|
return path.join(subtree, `${options.memoryId}.md`);
|
|
1768
1778
|
}
|
|
1769
1779
|
|
|
1780
|
+
function qmdMaintenanceSkipReasonForError(error: unknown): NamespaceMaintenanceSkipReason | null {
|
|
1781
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1782
|
+
return /^QMD (?:update|embed) skipped by .*min-interval gate$/.test(message)
|
|
1783
|
+
? "throttled"
|
|
1784
|
+
: null;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1770
1787
|
export class Orchestrator {
|
|
1771
1788
|
readonly storage: StorageManager;
|
|
1772
1789
|
private readonly storageRouter: NamespaceStorageRouter;
|
|
@@ -1879,6 +1896,7 @@ export class Orchestrator {
|
|
|
1879
1896
|
private readonly _peerIdBySession = new Map<string, string>();
|
|
1880
1897
|
private routingRulesStore: RoutingRulesStore | null = null;
|
|
1881
1898
|
private contentHashIndex: ContentHashIndex | null = null;
|
|
1899
|
+
private readonly contentHashIndexesByStorageDir = new Map<string, ContentHashIndex>();
|
|
1882
1900
|
private readonly artifactSourceStatusCache = new WeakMap<
|
|
1883
1901
|
StorageManager,
|
|
1884
1902
|
{
|
|
@@ -1914,6 +1932,7 @@ export class Orchestrator {
|
|
|
1914
1932
|
private qmdMaintenancePending = false;
|
|
1915
1933
|
private qmdMaintenanceInFlight = false;
|
|
1916
1934
|
private lastQmdEmbedAtMs = 0;
|
|
1935
|
+
private lastQmdEmbedAtMsByNamespace = new Map<string, number>();
|
|
1917
1936
|
private lastQmdReprobeAtMs = 0;
|
|
1918
1937
|
private tierMigrationInFlight = false;
|
|
1919
1938
|
private lastTierMigrationRunAtMs = 0;
|
|
@@ -2417,63 +2436,31 @@ export class Orchestrator {
|
|
|
2417
2436
|
}
|
|
2418
2437
|
|
|
2419
2438
|
/**
|
|
2420
|
-
*
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2423
|
-
*
|
|
2424
|
-
*
|
|
2425
|
-
*
|
|
2426
|
-
*
|
|
2427
|
-
* the catalog's namespaces so maintenance keeps dynamic namespaces fresh.
|
|
2428
|
-
* `updateNamespaces`/`embedNamespaces` already trim, dedup, and skip
|
|
2429
|
-
* unavailable/missing collections, so extra names are filtered safely. A catalog
|
|
2430
|
-
* read failure must never break maintenance — fall back to the configured set.
|
|
2439
|
+
* Shared namespace maintenance planner (issue #1500). This extends the
|
|
2440
|
+
* #1499 catalog-union QMD helper into a reusable contract: configured
|
|
2441
|
+
* namespaces are always considered, dynamic catalog namespaces are admitted
|
|
2442
|
+
* only when their live router root still matches real memory data, and branch
|
|
2443
|
+
* namespaces are opt-in. Recurring jobs use the per-cycle budget; startup and
|
|
2444
|
+
* recovery discovery paths use the same safety filters without that cycle
|
|
2445
|
+
* budget so every live namespace is ensured/synced.
|
|
2431
2446
|
*/
|
|
2432
|
-
private async
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
try {
|
|
2438
|
-
const records = await this.namespaceCatalog.listNamespaces();
|
|
2439
|
-
const safeRecords = await Promise.all(
|
|
2440
|
-
records.map(async (record) => {
|
|
2441
|
-
const namespace = record.namespace.trim();
|
|
2442
|
-
if (!namespace || configuredSet.has(namespace)) return null;
|
|
2443
|
-
return (await this.isCatalogedMaintenanceRootLive(record))
|
|
2444
|
-
? namespace
|
|
2445
|
-
: null;
|
|
2446
|
-
}),
|
|
2447
|
-
);
|
|
2448
|
-
cataloged = safeRecords.filter(
|
|
2449
|
-
(namespace): namespace is string => namespace !== null,
|
|
2450
|
-
);
|
|
2451
|
-
} catch {
|
|
2452
|
-
// Best-effort: a catalog read failure must not break QMD maintenance.
|
|
2453
|
-
cataloged = [];
|
|
2454
|
-
}
|
|
2455
|
-
return Array.from(
|
|
2456
|
-
new Set(
|
|
2457
|
-
[...configured, ...cataloged].map((value) => value.trim()).filter(Boolean),
|
|
2458
|
-
),
|
|
2459
|
-
);
|
|
2447
|
+
private async namespaceMaintenancePlan(jobName: string): Promise<NamespaceMaintenancePlan> {
|
|
2448
|
+
return planNamespaceMaintenance(this.config, {
|
|
2449
|
+
jobName,
|
|
2450
|
+
catalog: this.namespaceCatalog,
|
|
2451
|
+
});
|
|
2460
2452
|
}
|
|
2461
2453
|
|
|
2462
|
-
private async
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
return hasMemoryData(liveRoot);
|
|
2474
|
-
} catch {
|
|
2475
|
-
return false;
|
|
2476
|
-
}
|
|
2454
|
+
private async maintenanceNamespaces(
|
|
2455
|
+
jobName = "qmd",
|
|
2456
|
+
budgetMode: "cycle" | "unbounded" = "unbounded",
|
|
2457
|
+
): Promise<string[]> {
|
|
2458
|
+
const plan = await planNamespaceMaintenance(this.config, {
|
|
2459
|
+
jobName,
|
|
2460
|
+
catalog: this.namespaceCatalog,
|
|
2461
|
+
budgetMode,
|
|
2462
|
+
});
|
|
2463
|
+
return plan.namespaces.map((candidate) => candidate.namespace);
|
|
2477
2464
|
}
|
|
2478
2465
|
|
|
2479
2466
|
private buildConfiguredQmdSearchOptions(
|
|
@@ -2501,6 +2488,13 @@ export class Orchestrator {
|
|
|
2501
2488
|
searchOptions?: SearchQueryOptions;
|
|
2502
2489
|
execution?: SearchExecutionOptions;
|
|
2503
2490
|
}): Promise<QmdSearchResult[]> {
|
|
2491
|
+
if (
|
|
2492
|
+
this.config.namespacesEnabled &&
|
|
2493
|
+
options.namespaces !== undefined &&
|
|
2494
|
+
options.namespaces.length === 0
|
|
2495
|
+
) {
|
|
2496
|
+
return [];
|
|
2497
|
+
}
|
|
2504
2498
|
const namespaces = this.config.namespacesEnabled
|
|
2505
2499
|
? Array.from(
|
|
2506
2500
|
new Set(
|
|
@@ -2572,6 +2566,79 @@ export class Orchestrator {
|
|
|
2572
2566
|
|
|
2573
2567
|
invalidateLiveContentHashIndex(): void {
|
|
2574
2568
|
this.contentHashIndex = null;
|
|
2569
|
+
this.contentHashIndexesByStorageDir.clear();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
private async contentHashIndexForStorage(
|
|
2573
|
+
targetStorage: StorageManager,
|
|
2574
|
+
): Promise<ContentHashIndex | null> {
|
|
2575
|
+
if (!this.config.factDeduplicationEnabled) return null;
|
|
2576
|
+
|
|
2577
|
+
if (targetStorage.dir === this.storage.dir) {
|
|
2578
|
+
if (!this.contentHashIndex) {
|
|
2579
|
+
this.contentHashIndex = this.storage.createContentHashIndex();
|
|
2580
|
+
await this.contentHashIndex.load();
|
|
2581
|
+
}
|
|
2582
|
+
return this.contentHashIndex;
|
|
2583
|
+
}
|
|
2584
|
+
|
|
2585
|
+
const cached = this.contentHashIndexesByStorageDir.get(targetStorage.dir);
|
|
2586
|
+
if (cached) return cached;
|
|
2587
|
+
|
|
2588
|
+
const index = targetStorage.createContentHashIndex();
|
|
2589
|
+
await index.load();
|
|
2590
|
+
this.contentHashIndexesByStorageDir.set(targetStorage.dir, index);
|
|
2591
|
+
log.info(
|
|
2592
|
+
`content-hash dedup: loaded ${index.size} hashes for storage ${targetStorage.dir}`,
|
|
2593
|
+
);
|
|
2594
|
+
return index;
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
private async hasContentHashDedup(
|
|
2598
|
+
targetStorage: StorageManager,
|
|
2599
|
+
content: string,
|
|
2600
|
+
): Promise<boolean> {
|
|
2601
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2602
|
+
return index ? index.has(content) : false;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
private async addContentHashDedup(
|
|
2606
|
+
targetStorage: StorageManager,
|
|
2607
|
+
content: string,
|
|
2608
|
+
): Promise<void> {
|
|
2609
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2610
|
+
if (!index) return;
|
|
2611
|
+
index.add(content);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
private async removeContentHashForMemory(
|
|
2615
|
+
targetStorage: StorageManager,
|
|
2616
|
+
memory: MemoryFile,
|
|
2617
|
+
context: string,
|
|
2618
|
+
): Promise<void> {
|
|
2619
|
+
const index = await this.contentHashIndexForStorage(targetStorage);
|
|
2620
|
+
if (!index) return;
|
|
2621
|
+
|
|
2622
|
+
if (memory.frontmatter.contentHash) {
|
|
2623
|
+
index.removeByHash(memory.frontmatter.contentHash);
|
|
2624
|
+
return;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
log.warn(
|
|
2628
|
+
`[${context}] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback - no contentHash in frontmatter`,
|
|
2629
|
+
);
|
|
2630
|
+
index.remove(memory.content);
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
private async saveContentHashIndexes(): Promise<void> {
|
|
2634
|
+
const indexes = new Set<ContentHashIndex>();
|
|
2635
|
+
if (this.contentHashIndex) indexes.add(this.contentHashIndex);
|
|
2636
|
+
for (const index of this.contentHashIndexesByStorageDir.values()) {
|
|
2637
|
+
indexes.add(index);
|
|
2638
|
+
}
|
|
2639
|
+
for (const index of indexes) {
|
|
2640
|
+
await index.save();
|
|
2641
|
+
}
|
|
2575
2642
|
}
|
|
2576
2643
|
|
|
2577
2644
|
constructor(config: PluginConfig) {
|
|
@@ -4341,28 +4408,13 @@ export class Orchestrator {
|
|
|
4341
4408
|
relatedMemoryIds: [canonicalId],
|
|
4342
4409
|
});
|
|
4343
4410
|
if (archiveResult) {
|
|
4344
|
-
// Remove from content-hash index
|
|
4345
|
-
//
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
// Modern memory: frontmatter.contentHash is already a SHA-256
|
|
4352
|
-
// hex string — use removeByHash to avoid double-hashing.
|
|
4353
|
-
this.contentHashIndex.removeByHash(m.frontmatter.contentHash);
|
|
4354
|
-
} else {
|
|
4355
|
-
// Legacy memory written before contentHash was stored on the
|
|
4356
|
-
// frontmatter. Pre-#369 facts were stored without inline
|
|
4357
|
-
// citations, so m.content is the raw fact text and we can
|
|
4358
|
-
// remove the hash directly from the content. This clears
|
|
4359
|
-
// stale dedup entries so the fact can be re-extracted.
|
|
4360
|
-
log.warn(
|
|
4361
|
-
`[semantic-consolidation] removing hash for legacy memory ${m.frontmatter.id ?? "(unknown)"} via content fallback — no contentHash in frontmatter`,
|
|
4362
|
-
);
|
|
4363
|
-
this.contentHashIndex.remove(m.content);
|
|
4364
|
-
}
|
|
4365
|
-
}
|
|
4411
|
+
// Remove from the same storage-scoped content-hash index that
|
|
4412
|
+
// originally deduped this memory.
|
|
4413
|
+
await this.removeContentHashForMemory(
|
|
4414
|
+
targetStorage,
|
|
4415
|
+
m,
|
|
4416
|
+
"semantic-consolidation",
|
|
4417
|
+
);
|
|
4366
4418
|
// Best-effort index cleanup: a failure here (e.g. on-disk index save
|
|
4367
4419
|
// under disk-full) must NOT abort the archival loop and thereby skip
|
|
4368
4420
|
// the catalog write touch below for an already-durable canonical write
|
|
@@ -4416,15 +4468,13 @@ export class Orchestrator {
|
|
|
4416
4468
|
}
|
|
4417
4469
|
}
|
|
4418
4470
|
|
|
4419
|
-
// Save hash
|
|
4420
|
-
if (result.memoriesArchived > 0
|
|
4421
|
-
await this.
|
|
4422
|
-
.
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
),
|
|
4427
|
-
);
|
|
4471
|
+
// Save hash indexes if we modified them.
|
|
4472
|
+
if (result.memoriesArchived > 0) {
|
|
4473
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
4474
|
+
log.warn(
|
|
4475
|
+
`[semantic-consolidation] content-hash index save failed: ${err}`,
|
|
4476
|
+
),
|
|
4477
|
+
);
|
|
4428
4478
|
}
|
|
4429
4479
|
|
|
4430
4480
|
log.info(
|
|
@@ -5767,6 +5817,7 @@ export class Orchestrator {
|
|
|
5767
5817
|
prompt,
|
|
5768
5818
|
sessionKey,
|
|
5769
5819
|
options.namespace?.trim() || undefined,
|
|
5820
|
+
options.principalOverride,
|
|
5770
5821
|
);
|
|
5771
5822
|
} catch (err) {
|
|
5772
5823
|
log.debug(`direct-answer observation setup failed: ${err}`);
|
|
@@ -5834,12 +5885,13 @@ export class Orchestrator {
|
|
|
5834
5885
|
prompt: string,
|
|
5835
5886
|
sessionKey: string,
|
|
5836
5887
|
namespaceOverride: string | undefined,
|
|
5888
|
+
principalOverride: string | undefined,
|
|
5837
5889
|
): void {
|
|
5838
5890
|
const expectedSnapshot = this.lastRecall.get(sessionKey);
|
|
5839
5891
|
if (expectedSnapshot === null) return;
|
|
5840
5892
|
if (expectedSnapshot.plannerMode === "no_recall") return;
|
|
5841
5893
|
|
|
5842
|
-
const principal = resolvePrincipal(sessionKey, this.config);
|
|
5894
|
+
const principal = principalOverride ?? resolvePrincipal(sessionKey, this.config);
|
|
5843
5895
|
// Coding-agent overlay (issue #569) is applied when the session has a
|
|
5844
5896
|
// coding context and there is no explicit namespaceOverride — mirrors
|
|
5845
5897
|
// the main recall path above.
|
|
@@ -5851,9 +5903,29 @@ export class Orchestrator {
|
|
|
5851
5903
|
const observationCodingSelf = observationCodingOverlay
|
|
5852
5904
|
? combineNamespaces(observationPrincipalSelf, observationCodingOverlay.namespace)
|
|
5853
5905
|
: null;
|
|
5906
|
+
const observationScopeProfilePlan =
|
|
5907
|
+
namespaceOverride && canReadNamespace(principal, namespaceOverride, this.config)
|
|
5908
|
+
? null
|
|
5909
|
+
: resolveScopeProfilePlan({
|
|
5910
|
+
config: this.config,
|
|
5911
|
+
principal,
|
|
5912
|
+
codingContext: sessionKey
|
|
5913
|
+
? this.getCodingContextForSession(sessionKey)
|
|
5914
|
+
: null,
|
|
5915
|
+
codingOverlay: observationCodingOverlay,
|
|
5916
|
+
});
|
|
5854
5917
|
let observationNamespaces: string[];
|
|
5855
5918
|
if (namespaceOverride && canReadNamespace(principal, namespaceOverride, this.config)) {
|
|
5856
5919
|
observationNamespaces = [namespaceOverride];
|
|
5920
|
+
} else if (observationScopeProfilePlan) {
|
|
5921
|
+
observationNamespaces = expandScopeProfileReadNamespaces({
|
|
5922
|
+
profilePlan: observationScopeProfilePlan,
|
|
5923
|
+
principalSelfNamespace: observationScopeProfilePlan.baseNamespace,
|
|
5924
|
+
config: this.config,
|
|
5925
|
+
principal,
|
|
5926
|
+
codingOverlay: observationCodingOverlay,
|
|
5927
|
+
legacyRecallNamespaces: recallNamespacesForPrincipal(principal, this.config),
|
|
5928
|
+
});
|
|
5857
5929
|
} else if (observationCodingOverlay && observationCodingSelf) {
|
|
5858
5930
|
// Rule 42 / parity with the main recall path: substitute the self
|
|
5859
5931
|
// namespace within the principal's recall list rather than
|
|
@@ -7470,13 +7542,34 @@ export class Orchestrator {
|
|
|
7470
7542
|
const codingSelfNamespace = codingOverlay
|
|
7471
7543
|
? combineNamespaces(principalSelfNamespace, codingOverlay.namespace)
|
|
7472
7544
|
: null;
|
|
7545
|
+
const scopeProfilePlan = namespaceOverride
|
|
7546
|
+
? null
|
|
7547
|
+
: resolveScopeProfilePlan({
|
|
7548
|
+
config: this.config,
|
|
7549
|
+
principal,
|
|
7550
|
+
codingContext: sessionKey
|
|
7551
|
+
? this.getCodingContextForSession(sessionKey)
|
|
7552
|
+
: null,
|
|
7553
|
+
codingOverlay,
|
|
7554
|
+
});
|
|
7555
|
+
const profileEffectiveNamespace = scopeProfilePlan?.writeNamespace || scopeProfilePlan?.readNamespaces[0];
|
|
7473
7556
|
const selfNamespace =
|
|
7474
7557
|
namespaceOverride ??
|
|
7558
|
+
profileEffectiveNamespace ??
|
|
7475
7559
|
codingSelfNamespace ??
|
|
7476
7560
|
principalSelfNamespace;
|
|
7477
7561
|
let recallNamespaces: string[];
|
|
7478
7562
|
if (namespaceOverride) {
|
|
7479
7563
|
recallNamespaces = [namespaceOverride];
|
|
7564
|
+
} else if (scopeProfilePlan) {
|
|
7565
|
+
recallNamespaces = expandScopeProfileReadNamespaces({
|
|
7566
|
+
profilePlan: scopeProfilePlan,
|
|
7567
|
+
principalSelfNamespace: scopeProfilePlan.baseNamespace,
|
|
7568
|
+
config: this.config,
|
|
7569
|
+
principal,
|
|
7570
|
+
codingOverlay,
|
|
7571
|
+
legacyRecallNamespaces: readableRecallNamespaces,
|
|
7572
|
+
});
|
|
7480
7573
|
} else if (codingOverlay && codingSelfNamespace) {
|
|
7481
7574
|
// Substitute the principal's self namespace with the coding-scoped
|
|
7482
7575
|
// one, and append any read fallbacks (branch→project, PR 3) combined
|
|
@@ -7536,11 +7629,18 @@ export class Orchestrator {
|
|
|
7536
7629
|
// so the prior round's authorization invariant is preserved.
|
|
7537
7630
|
const codingOverlaySelfReadable =
|
|
7538
7631
|
codingOverlay !== null &&
|
|
7539
|
-
|
|
7632
|
+
(scopeProfilePlan
|
|
7633
|
+
? scopeProfilePlan.layers.some((layer) => layer.id === "userProject" && layer.readable)
|
|
7634
|
+
: readableRecallNamespaces.includes(principalSelfNamespace));
|
|
7540
7635
|
let lcmReadNamespaces: string[];
|
|
7541
7636
|
if (namespaceOverride) {
|
|
7542
7637
|
// Explicit namespace already read-authorized above (canReadNamespace gate).
|
|
7543
7638
|
lcmReadNamespaces = [namespaceOverride];
|
|
7639
|
+
} else if (scopeProfilePlan) {
|
|
7640
|
+
// Scope profiles define a layered read stack; LCM-backed evidence uses the
|
|
7641
|
+
// same namespace set as QMD/file recall so team/global/shared observations
|
|
7642
|
+
// are not silently skipped.
|
|
7643
|
+
lcmReadNamespaces = recallNamespaces;
|
|
7544
7644
|
} else if (codingOverlay && codingSelfNamespace && codingOverlaySelfReadable) {
|
|
7545
7645
|
// Self base readable → overlay rows authorized. Read the primary overlay
|
|
7546
7646
|
// key first, then each coding read fallback (project → root), combined with
|
|
@@ -7560,11 +7660,14 @@ export class Orchestrator {
|
|
|
7560
7660
|
// session_id set. Single-user / no-overlay recall passes a single-namespace
|
|
7561
7661
|
// set that collapses to the raw `sessionKey`, so this is `[sessionKey]` —
|
|
7562
7662
|
// byte-for-byte the pre-#1495 single-key behavior.
|
|
7563
|
-
const lcmReadSessionIds =
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7663
|
+
const lcmReadSessionIds =
|
|
7664
|
+
scopeProfilePlan && !sessionKey
|
|
7665
|
+
? []
|
|
7666
|
+
: lcmReadSessionIdsForNamespaces(
|
|
7667
|
+
lcmReadNamespaces,
|
|
7668
|
+
sessionKey,
|
|
7669
|
+
this.config.defaultNamespace,
|
|
7670
|
+
);
|
|
7568
7671
|
// Query an LCM-backed read across the ordered read key set and return the
|
|
7569
7672
|
// FIRST non-empty result (#1505 fallback-namespace unification). The primary
|
|
7570
7673
|
// overlay key is tried first; if a branch-scoped session has no rows under its
|
|
@@ -7582,9 +7685,12 @@ export class Orchestrator {
|
|
|
7582
7685
|
//
|
|
7583
7686
|
// When the set is a single key (single-user / no-overlay / explicit-namespace),
|
|
7584
7687
|
// this is exactly one call — unchanged. `lcmSessionId` is `string | undefined`:
|
|
7585
|
-
// a SESSIONLESS recall yields the single `undefined` key so the read
|
|
7586
|
-
// archive-wide read with no `session_id` filter (pre-#1505 behavior).
|
|
7587
|
-
//
|
|
7688
|
+
// a legacy SESSIONLESS recall yields the single `undefined` key so the read
|
|
7689
|
+
// runs ONE archive-wide read with no `session_id` filter (pre-#1505 behavior).
|
|
7690
|
+
// Hosted scope profiles are stricter: without a session key there is no
|
|
7691
|
+
// namespace-scoped LCM key to query, so the key set stays empty and LCM cannot
|
|
7692
|
+
// bypass the profile read stack via an archive-wide read. NEVER the literal
|
|
7693
|
+
// "default" session id (codex P2).
|
|
7588
7694
|
const firstNonEmptyLcmRead = async <T>(
|
|
7589
7695
|
read: (lcmSessionId: string | undefined) => Promise<T>,
|
|
7590
7696
|
isEmpty: (value: T) => boolean,
|
|
@@ -7782,7 +7888,158 @@ export class Orchestrator {
|
|
|
7782
7888
|
return "";
|
|
7783
7889
|
}
|
|
7784
7890
|
|
|
7785
|
-
const
|
|
7891
|
+
const profileStorageNamespaces = scopeProfilePlan ? recallNamespaces : [selfNamespace];
|
|
7892
|
+
const profileStorages = await Promise.all(
|
|
7893
|
+
profileStorageNamespaces.map((namespace) => this.storageRouter.storageFor(namespace)),
|
|
7894
|
+
);
|
|
7895
|
+
const emptyProfileStorage = new Proxy(
|
|
7896
|
+
{ dir: path.join(this.config.memoryDir, ".empty-scope-profile") } as any,
|
|
7897
|
+
{
|
|
7898
|
+
get(target, prop: string | symbol) {
|
|
7899
|
+
if (prop in target) return target[prop];
|
|
7900
|
+
if (prop === "readProfile") return async () => "";
|
|
7901
|
+
if (
|
|
7902
|
+
prop === "readQuestions" ||
|
|
7903
|
+
prop === "listEntityNames" ||
|
|
7904
|
+
prop === "readContinuityIncidents"
|
|
7905
|
+
)
|
|
7906
|
+
return async () => [];
|
|
7907
|
+
if (
|
|
7908
|
+
prop === "readIdentityAnchor" ||
|
|
7909
|
+
prop === "readIdentityImprovementLoops"
|
|
7910
|
+
)
|
|
7911
|
+
return async () => "";
|
|
7912
|
+
if (prop === "readEntity" || prop === "readMemoryByPath")
|
|
7913
|
+
return async () => null;
|
|
7914
|
+
return async () => [];
|
|
7915
|
+
},
|
|
7916
|
+
},
|
|
7917
|
+
);
|
|
7918
|
+
const profileStorage =
|
|
7919
|
+
profileStorages.length <= 1
|
|
7920
|
+
? profileStorages[0] ?? emptyProfileStorage
|
|
7921
|
+
: new Proxy(profileStorages[0] as any, {
|
|
7922
|
+
get(target, prop: string | symbol) {
|
|
7923
|
+
if (prop === "readProfile") {
|
|
7924
|
+
return async () => {
|
|
7925
|
+
for (const storage of profileStorages) {
|
|
7926
|
+
const profile = await storage.readProfile();
|
|
7927
|
+
if (profile.trim().length > 0) return profile;
|
|
7928
|
+
}
|
|
7929
|
+
return "";
|
|
7930
|
+
};
|
|
7931
|
+
}
|
|
7932
|
+
if (prop === "readQuestions") {
|
|
7933
|
+
return async (...args: any[]) => {
|
|
7934
|
+
const merged: any[] = [];
|
|
7935
|
+
const seen = new Set<string>();
|
|
7936
|
+
const priorityOf = (question: any): number => {
|
|
7937
|
+
const priority = Number(question?.priority ?? 0);
|
|
7938
|
+
return Number.isFinite(priority) ? priority : 0;
|
|
7939
|
+
};
|
|
7940
|
+
for (const storage of profileStorages) {
|
|
7941
|
+
const questions = await (storage.readQuestions as any)(...args);
|
|
7942
|
+
for (const question of questions) {
|
|
7943
|
+
const key = typeof question === "string" ? question : JSON.stringify(question);
|
|
7944
|
+
if (seen.has(key)) continue;
|
|
7945
|
+
seen.add(key);
|
|
7946
|
+
merged.push(question);
|
|
7947
|
+
}
|
|
7948
|
+
}
|
|
7949
|
+
return merged.sort(
|
|
7950
|
+
(left, right) =>
|
|
7951
|
+
priorityOf(right) - priorityOf(left) ||
|
|
7952
|
+
String(left?.id ?? "").localeCompare(String(right?.id ?? "")),
|
|
7953
|
+
);
|
|
7954
|
+
};
|
|
7955
|
+
}
|
|
7956
|
+
if (prop === "readIdentityAnchor") {
|
|
7957
|
+
return async () => {
|
|
7958
|
+
for (const storage of profileStorages) {
|
|
7959
|
+
const anchor = (await storage.readIdentityAnchor()) ?? "";
|
|
7960
|
+
if (anchor.trim().length > 0) return anchor;
|
|
7961
|
+
}
|
|
7962
|
+
return "";
|
|
7963
|
+
};
|
|
7964
|
+
}
|
|
7965
|
+
if (prop === "readIdentityImprovementLoops") {
|
|
7966
|
+
return async () => {
|
|
7967
|
+
const sections: string[] = [];
|
|
7968
|
+
const seen = new Set<string>();
|
|
7969
|
+
for (const storage of profileStorages) {
|
|
7970
|
+
const loops = ((await storage.readIdentityImprovementLoops()) ?? "").trim();
|
|
7971
|
+
if (!loops || seen.has(loops)) continue;
|
|
7972
|
+
seen.add(loops);
|
|
7973
|
+
sections.push(loops);
|
|
7974
|
+
}
|
|
7975
|
+
return sections.join("\n\n");
|
|
7976
|
+
};
|
|
7977
|
+
}
|
|
7978
|
+
if (prop === "readContinuityIncidents") {
|
|
7979
|
+
return async (...args: any[]) => {
|
|
7980
|
+
const limit = typeof args[0] === "number" && Number.isFinite(args[0]) ? Math.max(0, args[0]) : undefined;
|
|
7981
|
+
const incidents: any[] = [];
|
|
7982
|
+
const seen = new Set<string>();
|
|
7983
|
+
const incidentTime = (incident: any): number => {
|
|
7984
|
+
const raw = incident?.updatedAt ?? incident?.openedAt ?? incident?.createdAt;
|
|
7985
|
+
const parsed = typeof raw === "string" ? Date.parse(raw) : Number.NaN;
|
|
7986
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
7987
|
+
};
|
|
7988
|
+
for (const storage of profileStorages) {
|
|
7989
|
+
for (const incident of await (storage.readContinuityIncidents as any)(...args)) {
|
|
7990
|
+
const key = JSON.stringify(incident);
|
|
7991
|
+
if (seen.has(key)) continue;
|
|
7992
|
+
seen.add(key);
|
|
7993
|
+
incidents.push(incident);
|
|
7994
|
+
}
|
|
7995
|
+
}
|
|
7996
|
+
incidents.sort(
|
|
7997
|
+
(left, right) =>
|
|
7998
|
+
incidentTime(right) - incidentTime(left) ||
|
|
7999
|
+
String(left?.id ?? "").localeCompare(String(right?.id ?? "")),
|
|
8000
|
+
);
|
|
8001
|
+
return limit === undefined ? incidents : incidents.slice(0, limit);
|
|
8002
|
+
};
|
|
8003
|
+
}
|
|
8004
|
+
if (prop === "listEntityNames") {
|
|
8005
|
+
return async (...args: any[]) => {
|
|
8006
|
+
const names = new Set<string>();
|
|
8007
|
+
for (const storage of profileStorages) {
|
|
8008
|
+
for (const name of await (storage.listEntityNames as any)(...args)) names.add(name);
|
|
8009
|
+
}
|
|
8010
|
+
return [...names];
|
|
8011
|
+
};
|
|
8012
|
+
}
|
|
8013
|
+
if (prop === "readEntity" || prop === "readMemoryByPath") {
|
|
8014
|
+
return async (...args: any[]) => {
|
|
8015
|
+
for (const storage of profileStorages) {
|
|
8016
|
+
const value = await (storage as any)[prop](...args);
|
|
8017
|
+
if (value) return value;
|
|
8018
|
+
}
|
|
8019
|
+
return null;
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
if (prop === "readAllMemories") {
|
|
8023
|
+
return async (...args: any[]) => {
|
|
8024
|
+
const memories: any[] = [];
|
|
8025
|
+
const seen = new Set<string>();
|
|
8026
|
+
for (const storage of profileStorages) {
|
|
8027
|
+
for (const memory of await (storage.readAllMemories as any)(...args)) {
|
|
8028
|
+
const key = String(memory?.path ?? memory?.frontmatter?.id ?? JSON.stringify(memory));
|
|
8029
|
+
if (seen.has(key)) continue;
|
|
8030
|
+
seen.add(key);
|
|
8031
|
+
memories.push(memory);
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
return memories;
|
|
8035
|
+
};
|
|
8036
|
+
}
|
|
8037
|
+
return target[prop];
|
|
8038
|
+
},
|
|
8039
|
+
});
|
|
8040
|
+
const profileStorageDirs = Array.from(
|
|
8041
|
+
new Set(profileStorages.map((storage) => storage.dir).filter((dir): dir is string => typeof dir === "string" && dir.length > 0)),
|
|
8042
|
+
);
|
|
7786
8043
|
|
|
7787
8044
|
// --- Phase 1: Launch ALL independent data fetches in parallel ---
|
|
7788
8045
|
throwIfRecallAborted(options.abortSignal);
|
|
@@ -7812,6 +8069,14 @@ export class Orchestrator {
|
|
|
7812
8069
|
)
|
|
7813
8070
|
return null;
|
|
7814
8071
|
if (!this.sharedContext) return null;
|
|
8072
|
+
if (
|
|
8073
|
+
scopeProfilePlan &&
|
|
8074
|
+
!(
|
|
8075
|
+
scopeProfilePlan.profile.readOrder.includes("serverShared") &&
|
|
8076
|
+
scopeProfilePlan.readNamespaces.includes(this.config.sharedNamespace)
|
|
8077
|
+
)
|
|
8078
|
+
)
|
|
8079
|
+
return null;
|
|
7815
8080
|
const t0 = Date.now();
|
|
7816
8081
|
const [priorities, roundtable, crossSignals] = await Promise.all([
|
|
7817
8082
|
this.sharedContext.readPriorities(),
|
|
@@ -8121,13 +8386,64 @@ export class Orchestrator {
|
|
|
8121
8386
|
if (!this.config.knowledgeIndexEnabled) return null;
|
|
8122
8387
|
const t0 = Date.now();
|
|
8123
8388
|
try {
|
|
8124
|
-
const
|
|
8125
|
-
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
)
|
|
8129
|
-
|
|
8130
|
-
|
|
8389
|
+
const knowledgeIndexMaxChars =
|
|
8390
|
+
this.getRecallSectionNumber("knowledge-index", "maxChars") ??
|
|
8391
|
+
this.config.knowledgeIndexMaxChars;
|
|
8392
|
+
const knowledgeIndexMaxEntities =
|
|
8393
|
+
this.getRecallSectionNumber("knowledge-index", "maxEntities") ??
|
|
8394
|
+
this.config.knowledgeIndexMaxEntities;
|
|
8395
|
+
const knowledgeIndexOptions = {
|
|
8396
|
+
maxEntities: knowledgeIndexMaxEntities,
|
|
8397
|
+
maxChars: knowledgeIndexMaxChars,
|
|
8398
|
+
};
|
|
8399
|
+
const ki = scopeProfilePlan
|
|
8400
|
+
? await (async () => {
|
|
8401
|
+
const perLayerOptions = {
|
|
8402
|
+
...knowledgeIndexOptions,
|
|
8403
|
+
maxEntities: Number.MAX_SAFE_INTEGER,
|
|
8404
|
+
maxChars: Number.MAX_SAFE_INTEGER,
|
|
8405
|
+
};
|
|
8406
|
+
const results = await Promise.all(
|
|
8407
|
+
profileStorages.map((storage) =>
|
|
8408
|
+
storage.buildKnowledgeIndex(this.config, perLayerOptions),
|
|
8409
|
+
),
|
|
8410
|
+
);
|
|
8411
|
+
const sections = results
|
|
8412
|
+
.map((result) => result.result.trim())
|
|
8413
|
+
.filter((section) => section.length > 0);
|
|
8414
|
+
const maxRows = Math.max(0, Math.floor(knowledgeIndexMaxEntities));
|
|
8415
|
+
const rows: string[] = [];
|
|
8416
|
+
let header: string[] | null = null;
|
|
8417
|
+
for (const section of sections) {
|
|
8418
|
+
const lines = section
|
|
8419
|
+
.split("\n")
|
|
8420
|
+
.map((line) => line.trimEnd())
|
|
8421
|
+
.filter((line) => line.length > 0);
|
|
8422
|
+
const tableHeaderIndex = lines.findIndex((line) =>
|
|
8423
|
+
line.startsWith("| Entity |"),
|
|
8424
|
+
);
|
|
8425
|
+
if (tableHeaderIndex === -1) continue;
|
|
8426
|
+
header ??= lines.slice(0, tableHeaderIndex + 2);
|
|
8427
|
+
for (const row of lines.slice(tableHeaderIndex + 2)) {
|
|
8428
|
+
if (!row.startsWith("|")) continue;
|
|
8429
|
+
if (rows.length >= maxRows) break;
|
|
8430
|
+
rows.push(row);
|
|
8431
|
+
}
|
|
8432
|
+
if (rows.length >= maxRows) break;
|
|
8433
|
+
}
|
|
8434
|
+
const merged =
|
|
8435
|
+
header && rows.length > 0
|
|
8436
|
+
? `${header.join("\n")}\n${rows.join("\n")}\n`
|
|
8437
|
+
: "";
|
|
8438
|
+
return {
|
|
8439
|
+
result: this.truncateRecallSectionToBudget(
|
|
8440
|
+
merged,
|
|
8441
|
+
knowledgeIndexMaxChars,
|
|
8442
|
+
),
|
|
8443
|
+
cached: results.every((result) => result.cached),
|
|
8444
|
+
};
|
|
8445
|
+
})()
|
|
8446
|
+
: await this.storage.buildKnowledgeIndex(this.config, knowledgeIndexOptions);
|
|
8131
8447
|
recordRecallSectionMetric({
|
|
8132
8448
|
section: "ki",
|
|
8133
8449
|
priority: "core",
|
|
@@ -8549,15 +8865,36 @@ export class Orchestrator {
|
|
|
8549
8865
|
return null;
|
|
8550
8866
|
}
|
|
8551
8867
|
|
|
8552
|
-
const
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8868
|
+
const harmonicSearchDirs = scopeProfilePlan ? profileStorageDirs : [this.config.memoryDir];
|
|
8869
|
+
const harmonicResultsByDir = await Promise.all(
|
|
8870
|
+
harmonicSearchDirs.map((memoryDir) =>
|
|
8871
|
+
searchHarmonicRetrieval({
|
|
8872
|
+
memoryDir,
|
|
8873
|
+
abstractionNodeStoreDir: scopeProfilePlan ? undefined : this.config.abstractionNodeStoreDir,
|
|
8874
|
+
query: retrievalQuery,
|
|
8875
|
+
maxResults,
|
|
8876
|
+
sessionKey,
|
|
8877
|
+
anchorsEnabled: this.config.abstractionAnchorsEnabled,
|
|
8878
|
+
abortSignal: harmonicRetrievalAbort.signal,
|
|
8879
|
+
}),
|
|
8880
|
+
),
|
|
8881
|
+
);
|
|
8882
|
+
const harmonicByNodeId = new Map<string, HarmonicRetrievalResult>();
|
|
8883
|
+
for (const result of harmonicResultsByDir.flat()) {
|
|
8884
|
+
const existing = harmonicByNodeId.get(result.node.nodeId);
|
|
8885
|
+
if (!existing || result.score > existing.score) {
|
|
8886
|
+
harmonicByNodeId.set(result.node.nodeId, result);
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
const results = [...harmonicByNodeId.values()]
|
|
8890
|
+
.sort(
|
|
8891
|
+
(left, right) =>
|
|
8892
|
+
right.score - left.score ||
|
|
8893
|
+
right.anchorScore - left.anchorScore ||
|
|
8894
|
+
right.node.recordedAt.localeCompare(left.node.recordedAt) ||
|
|
8895
|
+
left.node.nodeId.localeCompare(right.node.nodeId),
|
|
8896
|
+
)
|
|
8897
|
+
.slice(0, maxResults);
|
|
8561
8898
|
|
|
8562
8899
|
recordRecallSectionMetric({
|
|
8563
8900
|
section: "harmonicRetrieval",
|
|
@@ -8619,11 +8956,28 @@ export class Orchestrator {
|
|
|
8619
8956
|
const VERIFIED_RECALL_TIMEOUT_MS = 15_000;
|
|
8620
8957
|
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
8621
8958
|
const results = await Promise.race([
|
|
8622
|
-
|
|
8623
|
-
memoryDir
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8959
|
+
Promise.all(
|
|
8960
|
+
profileStorageDirs.map((memoryDir) =>
|
|
8961
|
+
searchVerifiedEpisodes({
|
|
8962
|
+
memoryDir,
|
|
8963
|
+
query: retrievalQuery,
|
|
8964
|
+
maxResults,
|
|
8965
|
+
boxRecallDays: this.config.boxRecallDays,
|
|
8966
|
+
}).catch((err) => {
|
|
8967
|
+
log.debug(`verified recall directory scan failed: ${err}`);
|
|
8968
|
+
return [] as VerifiedEpisodeResult[];
|
|
8969
|
+
}),
|
|
8970
|
+
),
|
|
8971
|
+
).then((groups) => {
|
|
8972
|
+
const merged: VerifiedEpisodeResult[] = [];
|
|
8973
|
+
const seen = new Set<string>();
|
|
8974
|
+
for (const result of groups.flat()) {
|
|
8975
|
+
const key = result.box.id || JSON.stringify(result);
|
|
8976
|
+
if (seen.has(key)) continue;
|
|
8977
|
+
seen.add(key);
|
|
8978
|
+
merged.push(result);
|
|
8979
|
+
}
|
|
8980
|
+
return merged.sort(compareVerifiedEpisodeResults).slice(0, maxResults);
|
|
8627
8981
|
}),
|
|
8628
8982
|
new Promise<[]>((resolve) => {
|
|
8629
8983
|
timeoutHandle = setTimeout(
|
|
@@ -8691,10 +9045,27 @@ export class Orchestrator {
|
|
|
8691
9045
|
const VERIFIED_RULES_TIMEOUT_MS = 15_000;
|
|
8692
9046
|
let rulesTimeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
8693
9047
|
const results = await Promise.race([
|
|
8694
|
-
|
|
8695
|
-
memoryDir
|
|
8696
|
-
|
|
8697
|
-
|
|
9048
|
+
Promise.all(
|
|
9049
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9050
|
+
searchVerifiedSemanticRules({
|
|
9051
|
+
memoryDir,
|
|
9052
|
+
query: retrievalQuery,
|
|
9053
|
+
maxResults,
|
|
9054
|
+
}).catch((err) => {
|
|
9055
|
+
log.debug(`verified rules directory scan failed: ${err}`);
|
|
9056
|
+
return [] as VerifiedSemanticRuleResult[];
|
|
9057
|
+
}),
|
|
9058
|
+
),
|
|
9059
|
+
).then((groups) => {
|
|
9060
|
+
const merged: VerifiedSemanticRuleResult[] = [];
|
|
9061
|
+
const seen = new Set<string>();
|
|
9062
|
+
for (const result of groups.flat()) {
|
|
9063
|
+
const key = result.rule.frontmatter.id || result.rule.path || JSON.stringify(result);
|
|
9064
|
+
if (seen.has(key)) continue;
|
|
9065
|
+
seen.add(key);
|
|
9066
|
+
merged.push(result);
|
|
9067
|
+
}
|
|
9068
|
+
return merged.sort(compareVerifiedSemanticRuleResults).slice(0, maxResults);
|
|
8698
9069
|
}),
|
|
8699
9070
|
new Promise<[]>((resolve) => {
|
|
8700
9071
|
rulesTimeoutHandle = setTimeout(
|
|
@@ -8760,13 +9131,33 @@ export class Orchestrator {
|
|
|
8760
9131
|
return null;
|
|
8761
9132
|
}
|
|
8762
9133
|
|
|
8763
|
-
const
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
9134
|
+
const workProductSearchDirs = scopeProfilePlan ? profileStorageDirs : [this.config.memoryDir];
|
|
9135
|
+
const workProductResultsByDir = await Promise.all(
|
|
9136
|
+
workProductSearchDirs.map((memoryDir) =>
|
|
9137
|
+
searchWorkProductLedgerEntries({
|
|
9138
|
+
memoryDir,
|
|
9139
|
+
workProductLedgerDir: scopeProfilePlan ? undefined : this.config.workProductLedgerDir,
|
|
9140
|
+
query: retrievalQuery,
|
|
9141
|
+
maxResults,
|
|
9142
|
+
sessionKey,
|
|
9143
|
+
}),
|
|
9144
|
+
),
|
|
9145
|
+
);
|
|
9146
|
+
const workProductByEntryId = new Map<string, WorkProductLedgerSearchResult>();
|
|
9147
|
+
for (const result of workProductResultsByDir.flat()) {
|
|
9148
|
+
const existing = workProductByEntryId.get(result.entry.entryId);
|
|
9149
|
+
if (!existing || result.score > existing.score) {
|
|
9150
|
+
workProductByEntryId.set(result.entry.entryId, result);
|
|
9151
|
+
}
|
|
9152
|
+
}
|
|
9153
|
+
const results = [...workProductByEntryId.values()]
|
|
9154
|
+
.sort(
|
|
9155
|
+
(left, right) =>
|
|
9156
|
+
right.score - left.score ||
|
|
9157
|
+
right.entry.recordedAt.localeCompare(left.entry.recordedAt) ||
|
|
9158
|
+
left.entry.entryId.localeCompare(right.entry.entryId),
|
|
9159
|
+
)
|
|
9160
|
+
.slice(0, maxResults);
|
|
8770
9161
|
|
|
8771
9162
|
recordRecallSectionMetric({
|
|
8772
9163
|
section: "workProducts",
|
|
@@ -8979,24 +9370,56 @@ export class Orchestrator {
|
|
|
8979
9370
|
this.config.parallelRetrievalEnabled && maxPerAgent > 0
|
|
8980
9371
|
? Promise.all([
|
|
8981
9372
|
shouldRunAgent("direct", retrievalQuery, 0)
|
|
8982
|
-
?
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
9373
|
+
? Promise.all(
|
|
9374
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9375
|
+
runDirectAgent(
|
|
9376
|
+
retrievalQuery,
|
|
9377
|
+
memoryDir,
|
|
9378
|
+
maxPerAgent,
|
|
9379
|
+
).catch((err) => {
|
|
9380
|
+
log.debug(`DirectAgent pre-start failed: ${err}`);
|
|
9381
|
+
return [] as ParallelSearchResult[];
|
|
9382
|
+
}),
|
|
9383
|
+
),
|
|
9384
|
+
).then((groups) => {
|
|
9385
|
+
const merged: ParallelSearchResult[] = [];
|
|
9386
|
+
const seen = new Set<string>();
|
|
9387
|
+
for (const result of groups.flat()) {
|
|
9388
|
+
const key = (result as any).path ?? JSON.stringify(result);
|
|
9389
|
+
if (seen.has(key)) continue;
|
|
9390
|
+
seen.add(key);
|
|
9391
|
+
merged.push(result);
|
|
9392
|
+
}
|
|
9393
|
+
return merged
|
|
9394
|
+
.sort((a, b) => b.score - a.score)
|
|
9395
|
+
.slice(0, maxPerAgent);
|
|
8989
9396
|
})
|
|
8990
9397
|
: Promise.resolve([] as ParallelSearchResult[]),
|
|
8991
9398
|
shouldRunAgent("temporal", retrievalQuery, 0)
|
|
8992
|
-
?
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9399
|
+
? Promise.all(
|
|
9400
|
+
profileStorageDirs.map((memoryDir) =>
|
|
9401
|
+
runTemporalAgent(
|
|
9402
|
+
retrievalQuery,
|
|
9403
|
+
memoryDir,
|
|
9404
|
+
maxPerAgent,
|
|
9405
|
+
queryAwarePrefilter.candidatePaths,
|
|
9406
|
+
).catch((err) => {
|
|
9407
|
+
log.debug(`TemporalAgent pre-start failed for ${memoryDir}: ${err}`);
|
|
9408
|
+
return [] as ParallelSearchResult[];
|
|
9409
|
+
}),
|
|
9410
|
+
),
|
|
9411
|
+
).then((groups) => {
|
|
9412
|
+
const merged: ParallelSearchResult[] = [];
|
|
9413
|
+
const seen = new Set<string>();
|
|
9414
|
+
for (const result of groups.flat()) {
|
|
9415
|
+
const key = (result as any).path ?? JSON.stringify(result);
|
|
9416
|
+
if (seen.has(key)) continue;
|
|
9417
|
+
seen.add(key);
|
|
9418
|
+
merged.push(result);
|
|
9419
|
+
}
|
|
9420
|
+
return merged
|
|
9421
|
+
.sort((a, b) => b.score - a.score)
|
|
9422
|
+
.slice(0, maxPerAgent);
|
|
9000
9423
|
})
|
|
9001
9424
|
: Promise.resolve([] as ParallelSearchResult[]),
|
|
9002
9425
|
])
|
|
@@ -9589,9 +10012,29 @@ export class Orchestrator {
|
|
|
9589
10012
|
) &&
|
|
9590
10013
|
this.config.memoryBoxesEnabled &&
|
|
9591
10014
|
this.config.boxRecallDays > 0
|
|
9592
|
-
?
|
|
9593
|
-
.
|
|
9594
|
-
|
|
10015
|
+
? Promise.all(
|
|
10016
|
+
profileStorages.map((storage) =>
|
|
10017
|
+
this.boxBuilderFor(storage)
|
|
10018
|
+
.readRecentBoxes(this.config.boxRecallDays)
|
|
10019
|
+
.catch(() => [] as BoxFrontmatter[]),
|
|
10020
|
+
),
|
|
10021
|
+
).then((groups) => {
|
|
10022
|
+
const boxes: BoxFrontmatter[] = [];
|
|
10023
|
+
const seen = new Set<string>();
|
|
10024
|
+
for (const box of groups.flat()) {
|
|
10025
|
+
const key = JSON.stringify(box);
|
|
10026
|
+
if (seen.has(key)) continue;
|
|
10027
|
+
seen.add(key);
|
|
10028
|
+
boxes.push(box);
|
|
10029
|
+
}
|
|
10030
|
+
return boxes.sort((a, b) => {
|
|
10031
|
+
const aTime = Date.parse(a.sealedAt ?? "");
|
|
10032
|
+
const bTime = Date.parse(b.sealedAt ?? "");
|
|
10033
|
+
const aRank = Number.isFinite(aTime) ? aTime : 0;
|
|
10034
|
+
const bRank = Number.isFinite(bTime) ? bTime : 0;
|
|
10035
|
+
return bRank - aRank;
|
|
10036
|
+
});
|
|
10037
|
+
})
|
|
9595
10038
|
: Promise.resolve([] as BoxFrontmatter[]),
|
|
9596
10039
|
);
|
|
9597
10040
|
|
|
@@ -12684,18 +13127,57 @@ export class Orchestrator {
|
|
|
12684
13127
|
options.principalOverride.length > 0
|
|
12685
13128
|
? options.principalOverride
|
|
12686
13129
|
: resolvePrincipal(sessionKey, this.config);
|
|
12687
|
-
// Write path —
|
|
12688
|
-
//
|
|
12689
|
-
//
|
|
12690
|
-
//
|
|
12691
|
-
const
|
|
13130
|
+
// Write path — explicit callers still win. Otherwise, an active hosted
|
|
13131
|
+
// scope profile owns the extraction write target so hook-captured turns land
|
|
13132
|
+
// in the same layer that profile recall searches. Without a profile, preserve
|
|
13133
|
+
// the existing coding-agent overlay behavior (issue #569).
|
|
13134
|
+
const explicitWriteNamespace =
|
|
12692
13135
|
typeof options.writeNamespaceOverride === "string" &&
|
|
12693
13136
|
options.writeNamespaceOverride.length > 0
|
|
12694
13137
|
? options.writeNamespaceOverride
|
|
12695
|
-
:
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
13138
|
+
: undefined;
|
|
13139
|
+
const codingContextForWrite = sessionKey
|
|
13140
|
+
? this.getCodingContextForSession(sessionKey)
|
|
13141
|
+
: null;
|
|
13142
|
+
const codingOverlayForWrite = resolveCodingNamespaceOverlay(
|
|
13143
|
+
codingContextForWrite,
|
|
13144
|
+
this.config.codingMode,
|
|
13145
|
+
this.config.defaultNamespace,
|
|
13146
|
+
);
|
|
13147
|
+
const scopeProfileGatePlan = resolveScopeProfilePlan({
|
|
13148
|
+
config: this.config,
|
|
13149
|
+
principal,
|
|
13150
|
+
codingContext: codingContextForWrite,
|
|
13151
|
+
codingOverlay: codingOverlayForWrite,
|
|
13152
|
+
});
|
|
13153
|
+
const scopeProfileWritePlan = explicitWriteNamespace ? null : scopeProfileGatePlan;
|
|
13154
|
+
if (scopeProfileWritePlan) {
|
|
13155
|
+
const selectedLayer = scopeProfileWritePlan.layers.find(
|
|
13156
|
+
(layer) => layer.id === scopeProfileWritePlan.writeLayer,
|
|
13157
|
+
);
|
|
13158
|
+
const writeNamespaceReadable = scopeProfileWritePlan.readNamespaces.includes(
|
|
13159
|
+
scopeProfileWritePlan.writeNamespace,
|
|
13160
|
+
);
|
|
13161
|
+
if (!selectedLayer?.writable || !writeNamespaceReadable) {
|
|
13162
|
+
log.warn(
|
|
13163
|
+
`runExtraction: skipping scope profile ${scopeProfileWritePlan.profileId} because write layer ${scopeProfileWritePlan.writeLayer} is not writable inside the profile read stack`,
|
|
13164
|
+
);
|
|
13165
|
+
await clearBuffer();
|
|
13166
|
+
return {
|
|
13167
|
+
status: "skipped",
|
|
13168
|
+
reason: "scope_profile_no_writable_layer",
|
|
13169
|
+
persistedCount: 0,
|
|
13170
|
+
durableOutputCount: 0,
|
|
13171
|
+
};
|
|
13172
|
+
}
|
|
13173
|
+
}
|
|
13174
|
+
const selfNamespace =
|
|
13175
|
+
explicitWriteNamespace ??
|
|
13176
|
+
scopeProfileWritePlan?.writeNamespace ??
|
|
13177
|
+
this.applyCodingNamespaceOverlay(
|
|
13178
|
+
sessionKey,
|
|
13179
|
+
defaultNamespaceForPrincipal(principal, this.config),
|
|
13180
|
+
);
|
|
12699
13181
|
const storage = await this.storageRouter.storageFor(selfNamespace);
|
|
12700
13182
|
const shouldPersistProcessedFingerprint = targetTurns.some(
|
|
12701
13183
|
(turn) => turn.persistProcessedFingerprint === true,
|
|
@@ -12855,6 +13337,7 @@ export class Orchestrator {
|
|
|
12855
13337
|
// Pass the KNOWN base namespace (NHIdx) so the catalog write touch records the
|
|
12856
13338
|
// real namespace rather than a guess decoded from the storage dir.
|
|
12857
13339
|
selfNamespace,
|
|
13340
|
+
scopeProfileGatePlan,
|
|
12858
13341
|
);
|
|
12859
13342
|
let postPersistMetadataFailed = false;
|
|
12860
13343
|
meta ??= await storage.loadMeta();
|
|
@@ -13337,17 +13820,69 @@ export class Orchestrator {
|
|
|
13337
13820
|
try {
|
|
13338
13821
|
if (this.config.namespacesEnabled) {
|
|
13339
13822
|
// Include cataloged dynamic namespaces, not just the configured set
|
|
13340
|
-
// (NGnei)
|
|
13341
|
-
|
|
13342
|
-
|
|
13823
|
+
// (NGnei), but run through the namespace-aware maintenance planner so
|
|
13824
|
+
// each namespace is budgeted, lock-protected, and status-recorded
|
|
13825
|
+
// independently (issue #1500).
|
|
13826
|
+
const plan = await this.namespaceMaintenancePlan("qmd");
|
|
13343
13827
|
const now = Date.now();
|
|
13344
|
-
|
|
13345
|
-
this.
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13828
|
+
const lastEmbedAtByNamespace =
|
|
13829
|
+
this.lastQmdEmbedAtMsByNamespace ?? (this.lastQmdEmbedAtMsByNamespace = new Map());
|
|
13830
|
+
const dueEmbedNamespaces = (namespaces: string[]): string[] => {
|
|
13831
|
+
if (!this.config.qmdAutoEmbedEnabled) return [];
|
|
13832
|
+
return namespaces.filter(
|
|
13833
|
+
(namespace) =>
|
|
13834
|
+
now - (lastEmbedAtByNamespace.get(namespace) ?? 0) >= this.config.qmdEmbedMinIntervalMs,
|
|
13835
|
+
);
|
|
13836
|
+
};
|
|
13837
|
+
const markEmbedded = (namespaces: string[]): void => {
|
|
13838
|
+
if (namespaces.length === 0) return;
|
|
13839
|
+
for (const namespace of namespaces) {
|
|
13840
|
+
lastEmbedAtByNamespace.set(namespace, now);
|
|
13841
|
+
}
|
|
13349
13842
|
this.lastQmdEmbedAtMs = now;
|
|
13350
|
-
}
|
|
13843
|
+
};
|
|
13844
|
+
await runNamespaceMaintenanceBatchPlan(
|
|
13845
|
+
this.config,
|
|
13846
|
+
plan,
|
|
13847
|
+
async (candidates) => {
|
|
13848
|
+
const namespaces = candidates.map((candidate) => candidate.namespace);
|
|
13849
|
+
const embedNamespaces = dueEmbedNamespaces(namespaces);
|
|
13850
|
+
let result: Awaited<ReturnType<NamespaceSearchRouter["updateNamespacesDetailed"]>>;
|
|
13851
|
+
try {
|
|
13852
|
+
result = await this.namespaceSearchRouter.updateNamespacesDetailed(
|
|
13853
|
+
namespaces,
|
|
13854
|
+
undefined,
|
|
13855
|
+
{ strict: true },
|
|
13856
|
+
);
|
|
13857
|
+
} catch (error) {
|
|
13858
|
+
if (
|
|
13859
|
+
embedNamespaces.length > 0 &&
|
|
13860
|
+
qmdMaintenanceSkipReasonForError(error) === "throttled"
|
|
13861
|
+
) {
|
|
13862
|
+
await this.namespaceSearchRouter.embedNamespaces(embedNamespaces, { strict: true });
|
|
13863
|
+
markEmbedded(embedNamespaces);
|
|
13864
|
+
}
|
|
13865
|
+
throw error;
|
|
13866
|
+
}
|
|
13867
|
+
if (result.backendCount <= 0) {
|
|
13868
|
+
throw new Error("no eligible QMD backend for selected namespaces");
|
|
13869
|
+
}
|
|
13870
|
+
if (result.eligibleNamespaces.length !== namespaces.length) {
|
|
13871
|
+
const eligible = new Set(result.eligibleNamespaces);
|
|
13872
|
+
const missing = namespaces.filter((namespace) => !eligible.has(namespace));
|
|
13873
|
+
throw new Error(`QMD backend ineligible for selected namespaces (${missing.length})`);
|
|
13874
|
+
}
|
|
13875
|
+
if (embedNamespaces.length > 0) {
|
|
13876
|
+
await this.namespaceSearchRouter.embedNamespaces(embedNamespaces, { strict: true });
|
|
13877
|
+
markEmbedded(embedNamespaces);
|
|
13878
|
+
}
|
|
13879
|
+
return { itemCount: result.backendCount };
|
|
13880
|
+
},
|
|
13881
|
+
this.namespaceCatalog,
|
|
13882
|
+
{
|
|
13883
|
+
skipReasonForError: qmdMaintenanceSkipReasonForError,
|
|
13884
|
+
},
|
|
13885
|
+
);
|
|
13351
13886
|
} else {
|
|
13352
13887
|
await this.qmd.update();
|
|
13353
13888
|
const now = Date.now();
|
|
@@ -13373,6 +13908,7 @@ export class Orchestrator {
|
|
|
13373
13908
|
threadIdForExtraction?: string | null,
|
|
13374
13909
|
sourceContext?: { sessionKey?: string; principal?: string; validAt?: string },
|
|
13375
13910
|
baseNamespace?: string,
|
|
13911
|
+
scopeProfileWritePlan?: ResolvedScopeProfilePlan | null,
|
|
13376
13912
|
): Promise<string[]> {
|
|
13377
13913
|
// Inline source attribution (issue #369). When enabled, every extracted
|
|
13378
13914
|
// fact is rewritten to carry a compact provenance tag inside its body so
|
|
@@ -13467,6 +14003,60 @@ export class Orchestrator {
|
|
|
13467
14003
|
"inferred",
|
|
13468
14004
|
"speculative",
|
|
13469
14005
|
] as const;
|
|
14006
|
+
const sharedProfileLayer = scopeProfileWritePlan?.layers.find(
|
|
14007
|
+
(layer) =>
|
|
14008
|
+
layer.id === "serverShared" &&
|
|
14009
|
+
layer.namespace === this.config.sharedNamespace,
|
|
14010
|
+
);
|
|
14011
|
+
const sharedPromotionTarget = scopeProfileWritePlan?.promotionTargets.find(
|
|
14012
|
+
(target) =>
|
|
14013
|
+
target.target === "serverShared" &&
|
|
14014
|
+
target.namespace === this.config.sharedNamespace,
|
|
14015
|
+
);
|
|
14016
|
+
const profileAllowsSharedWrites =
|
|
14017
|
+
!scopeProfileWritePlan ||
|
|
14018
|
+
Boolean(
|
|
14019
|
+
scopeProfileWritePlan.profile.readOrder.includes("serverShared") &&
|
|
14020
|
+
scopeProfileWritePlan.readNamespaces.includes(this.config.sharedNamespace) &&
|
|
14021
|
+
sharedProfileLayer?.readable &&
|
|
14022
|
+
sharedProfileLayer.writable &&
|
|
14023
|
+
sharedPromotionTarget?.authorized,
|
|
14024
|
+
);
|
|
14025
|
+
const profileAutoPromotionAllows = (
|
|
14026
|
+
category: string,
|
|
14027
|
+
confidence: number,
|
|
14028
|
+
): boolean => {
|
|
14029
|
+
if (!scopeProfileWritePlan) return false;
|
|
14030
|
+
const actualTier = confidenceTier(confidence);
|
|
14031
|
+
const actualRank = confidenceTierOrder.indexOf(actualTier);
|
|
14032
|
+
if (actualRank === -1) return false;
|
|
14033
|
+
const autoPromote = scopeProfileWritePlan.profile.autoPromote;
|
|
14034
|
+
if (!autoPromote.enabled) return false;
|
|
14035
|
+
if (!autoPromote.categories.includes(category as any)) return false;
|
|
14036
|
+
const minimumRank = confidenceTierOrder.indexOf(autoPromote.minConfidenceTier);
|
|
14037
|
+
return minimumRank !== -1 && actualRank <= minimumRank;
|
|
14038
|
+
};
|
|
14039
|
+
const sharedAutoPromotionAllows = (
|
|
14040
|
+
category: string,
|
|
14041
|
+
confidence: number,
|
|
14042
|
+
): boolean => {
|
|
14043
|
+
if (!scopeProfileWritePlan) {
|
|
14044
|
+
const actualTier = confidenceTier(confidence);
|
|
14045
|
+
const actualRank = confidenceTierOrder.indexOf(actualTier);
|
|
14046
|
+
if (actualRank === -1) return false;
|
|
14047
|
+
if (!this.config.autoPromoteToSharedEnabled) return false;
|
|
14048
|
+
if (!this.config.autoPromoteToSharedCategories.includes(category as any))
|
|
14049
|
+
return false;
|
|
14050
|
+
const minimumRank = confidenceTierOrder.indexOf(
|
|
14051
|
+
this.config.autoPromoteMinConfidenceTier,
|
|
14052
|
+
);
|
|
14053
|
+
return minimumRank !== -1 && actualRank <= minimumRank;
|
|
14054
|
+
}
|
|
14055
|
+
return (
|
|
14056
|
+
scopeProfileWritePlan.profile.autoPromote.targets.includes("serverShared") &&
|
|
14057
|
+
profileAutoPromotionAllows(category, confidence)
|
|
14058
|
+
);
|
|
14059
|
+
};
|
|
13470
14060
|
const shouldPromoteToShared = (
|
|
13471
14061
|
targetStorage: StorageManager,
|
|
13472
14062
|
category: string,
|
|
@@ -13474,7 +14064,8 @@ export class Orchestrator {
|
|
|
13474
14064
|
): boolean => {
|
|
13475
14065
|
if (
|
|
13476
14066
|
!this.config.namespacesEnabled ||
|
|
13477
|
-
!
|
|
14067
|
+
!profileAllowsSharedWrites ||
|
|
14068
|
+
!sharedAutoPromotionAllows(category, confidence)
|
|
13478
14069
|
)
|
|
13479
14070
|
return false;
|
|
13480
14071
|
if (
|
|
@@ -13482,15 +14073,124 @@ export class Orchestrator {
|
|
|
13482
14073
|
this.config.sharedNamespace
|
|
13483
14074
|
)
|
|
13484
14075
|
return false;
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
14076
|
+
return true;
|
|
14077
|
+
};
|
|
14078
|
+
const promoteMemoryToProfileTargets = async (options: {
|
|
14079
|
+
sourceStorage: StorageManager;
|
|
14080
|
+
category: string;
|
|
14081
|
+
content: string;
|
|
14082
|
+
confidence: number;
|
|
14083
|
+
tags: string[];
|
|
14084
|
+
entityRef?: string;
|
|
14085
|
+
structuredAttributes?: Record<string, string>;
|
|
14086
|
+
sourceMemoryId: string;
|
|
14087
|
+
importance?: ReturnType<typeof scoreImportance>;
|
|
14088
|
+
intentGoal?: string;
|
|
14089
|
+
intentActionType?: string;
|
|
14090
|
+
intentEntityTypes?: string[];
|
|
14091
|
+
memoryKind?: MemoryFrontmatter["memoryKind"];
|
|
14092
|
+
validAt?: string;
|
|
14093
|
+
source: string;
|
|
14094
|
+
}): Promise<void> => {
|
|
14095
|
+
if (
|
|
14096
|
+
!scopeProfileWritePlan ||
|
|
14097
|
+
!profileAutoPromotionAllows(options.category, options.confidence)
|
|
14098
|
+
)
|
|
14099
|
+
return;
|
|
14100
|
+
const autoTargets = new Set(scopeProfileWritePlan.profile.autoPromote.targets);
|
|
14101
|
+
const targets = scopeProfileWritePlan.promotionTargets.filter(
|
|
14102
|
+
(target) =>
|
|
14103
|
+
target.target !== "serverShared" &&
|
|
14104
|
+
autoTargets.has(target.target) &&
|
|
14105
|
+
target.authorized &&
|
|
14106
|
+
target.namespace,
|
|
13491
14107
|
);
|
|
13492
|
-
if (
|
|
13493
|
-
|
|
14108
|
+
if (targets.length === 0) return;
|
|
14109
|
+
const rawContent =
|
|
14110
|
+
citationEnabled && hasCitationForTemplate(options.content, citationTemplate)
|
|
14111
|
+
? stripCitationForTemplate(options.content, citationTemplate)
|
|
14112
|
+
: options.content;
|
|
14113
|
+
const citedContent = applyInlineCitation(rawContent);
|
|
14114
|
+
const sanitizedBase = sanitizeMemoryContent(rawContent);
|
|
14115
|
+
const dedupContent =
|
|
14116
|
+
options.category === "fact" &&
|
|
14117
|
+
options.structuredAttributes &&
|
|
14118
|
+
Object.keys(options.structuredAttributes).length > 0
|
|
14119
|
+
? `${sanitizedBase.text}\n[Attributes: ${normalizeAttributePairs(options.structuredAttributes)}]`
|
|
14120
|
+
: sanitizedBase.text;
|
|
14121
|
+
for (const target of targets) {
|
|
14122
|
+
if (!target.namespace) continue;
|
|
14123
|
+
try {
|
|
14124
|
+
const targetStorage = await this.storageRouter.storageFor(target.namespace);
|
|
14125
|
+
if (targetStorage.dir === options.sourceStorage.dir) continue;
|
|
14126
|
+
if (
|
|
14127
|
+
options.category === "fact" &&
|
|
14128
|
+
(await targetStorage.hasFactContentHash(dedupContent))
|
|
14129
|
+
) {
|
|
14130
|
+
continue;
|
|
14131
|
+
}
|
|
14132
|
+
const promotedId = await targetStorage.writeMemory(
|
|
14133
|
+
options.category as any,
|
|
14134
|
+
citedContent,
|
|
14135
|
+
{
|
|
14136
|
+
confidence: options.confidence,
|
|
14137
|
+
tags: [...options.tags, `${target.target}-promotion`],
|
|
14138
|
+
entityRef: options.entityRef,
|
|
14139
|
+
structuredAttributes: options.structuredAttributes,
|
|
14140
|
+
source: `${options.source}-${target.target}-promotion`,
|
|
14141
|
+
importance: options.importance,
|
|
14142
|
+
lineage: [options.sourceMemoryId],
|
|
14143
|
+
sourceMemoryId: options.sourceMemoryId,
|
|
14144
|
+
intentGoal: options.intentGoal,
|
|
14145
|
+
intentActionType: options.intentActionType,
|
|
14146
|
+
intentEntityTypes: options.intentEntityTypes,
|
|
14147
|
+
memoryKind: options.memoryKind,
|
|
14148
|
+
validAt: options.validAt,
|
|
14149
|
+
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
14150
|
+
},
|
|
14151
|
+
);
|
|
14152
|
+
if (
|
|
14153
|
+
this.config.temporalSupersessionEnabled &&
|
|
14154
|
+
options.category === "fact" &&
|
|
14155
|
+
options.entityRef &&
|
|
14156
|
+
options.structuredAttributes &&
|
|
14157
|
+
Object.keys(options.structuredAttributes).length > 0
|
|
14158
|
+
) {
|
|
14159
|
+
try {
|
|
14160
|
+
await applyTemporalSupersession({
|
|
14161
|
+
storage: targetStorage,
|
|
14162
|
+
newMemoryId: promotedId,
|
|
14163
|
+
entityRef: options.entityRef,
|
|
14164
|
+
structuredAttributes: options.structuredAttributes,
|
|
14165
|
+
createdAt: supersessionOrderingAt(options.validAt),
|
|
14166
|
+
enabled: true,
|
|
14167
|
+
});
|
|
14168
|
+
} catch (profileSupersessionErr) {
|
|
14169
|
+
log.warn(
|
|
14170
|
+
`persistExtraction: ${target.target} promotion temporal supersession failed open for promoted ${promotedId}: ${profileSupersessionErr}`,
|
|
14171
|
+
);
|
|
14172
|
+
}
|
|
14173
|
+
}
|
|
14174
|
+
this.markCatalogWrite(target.namespace, targetStorage.dir);
|
|
14175
|
+
trackPersistedId(targetStorage, promotedId, { includeReturnedIds: false });
|
|
14176
|
+
await this.indexPersistedMemory(targetStorage, promotedId);
|
|
14177
|
+
trackBehaviorSignals(
|
|
14178
|
+
targetStorage,
|
|
14179
|
+
buildBehaviorSignalsForMemory({
|
|
14180
|
+
memoryId: promotedId,
|
|
14181
|
+
category: options.category as any,
|
|
14182
|
+
content: options.content,
|
|
14183
|
+
namespace: target.namespace,
|
|
14184
|
+
confidence: options.confidence,
|
|
14185
|
+
source: "extraction",
|
|
14186
|
+
}),
|
|
14187
|
+
);
|
|
14188
|
+
} catch (err) {
|
|
14189
|
+
log.warn(
|
|
14190
|
+
`persistExtraction: ${target.target} promotion failed open for ${options.sourceMemoryId}: ${err}`,
|
|
14191
|
+
);
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
13494
14194
|
};
|
|
13495
14195
|
const promoteMemoryToShared = async (options: {
|
|
13496
14196
|
sourceStorage: StorageManager;
|
|
@@ -13509,6 +14209,7 @@ export class Orchestrator {
|
|
|
13509
14209
|
validAt?: string;
|
|
13510
14210
|
source: string;
|
|
13511
14211
|
}): Promise<void> => {
|
|
14212
|
+
await promoteMemoryToProfileTargets(options);
|
|
13512
14213
|
if (
|
|
13513
14214
|
!shouldPromoteToShared(
|
|
13514
14215
|
options.sourceStorage,
|
|
@@ -13733,11 +14434,10 @@ export class Orchestrator {
|
|
|
13733
14434
|
intentEntityTypes: options.intentEntityTypes,
|
|
13734
14435
|
memoryKind: options.memoryKind,
|
|
13735
14436
|
validAt: options.validAt,
|
|
13736
|
-
// Index the
|
|
13737
|
-
//
|
|
13738
|
-
//
|
|
13739
|
-
|
|
13740
|
-
contentHashSource: rawContent,
|
|
14437
|
+
// Index the same canonical body used by hasFactContentHash above.
|
|
14438
|
+
// For structured facts this includes the normalized Attributes
|
|
14439
|
+
// suffix, matching StorageManager.writeMemory enrichment.
|
|
14440
|
+
contentHashSource: options.category === "fact" ? dedupContent : rawContent,
|
|
13741
14441
|
},
|
|
13742
14442
|
);
|
|
13743
14443
|
// PR #402 Finding 3 fix: run temporal supersession against the shared
|
|
@@ -14191,7 +14891,7 @@ export class Orchestrator {
|
|
|
14191
14891
|
!routedNamespaceExplicit
|
|
14192
14892
|
) {
|
|
14193
14893
|
const currentNs = this.namespaceFromStorageDir(targetStorage.dir);
|
|
14194
|
-
if (currentNs !== this.config.sharedNamespace) {
|
|
14894
|
+
if (currentNs !== this.config.sharedNamespace && profileAllowsSharedWrites) {
|
|
14195
14895
|
try {
|
|
14196
14896
|
targetStorage = await this.storageRouter.storageFor(
|
|
14197
14897
|
this.config.sharedNamespace,
|
|
@@ -14205,6 +14905,10 @@ export class Orchestrator {
|
|
|
14205
14905
|
`scope-routing: failed to resolve shared namespace storage; writing to session namespace (fail-open): ${scopeRouteErr}`,
|
|
14206
14906
|
);
|
|
14207
14907
|
}
|
|
14908
|
+
} else if (currentNs !== this.config.sharedNamespace) {
|
|
14909
|
+
log.debug(
|
|
14910
|
+
`scope-routing: skipped shared namespace for global fact because active scope profile ${scopeProfileWritePlan?.profileId ?? "none"} does not authorize serverShared writes`,
|
|
14911
|
+
);
|
|
14208
14912
|
}
|
|
14209
14913
|
}
|
|
14210
14914
|
|
|
@@ -14219,9 +14923,20 @@ export class Orchestrator {
|
|
|
14219
14923
|
writeCategory === "procedure"
|
|
14220
14924
|
? buildProcedurePersistBody(fact.content, fact.procedureSteps)
|
|
14221
14925
|
: canonicalContentForHash;
|
|
14222
|
-
|
|
14926
|
+
let exactDuplicate = false;
|
|
14927
|
+
try {
|
|
14928
|
+
exactDuplicate = await this.hasContentHashDedup(
|
|
14929
|
+
targetStorage,
|
|
14930
|
+
contentHashDedupKey,
|
|
14931
|
+
);
|
|
14932
|
+
} catch (err) {
|
|
14933
|
+
log.warn(
|
|
14934
|
+
`content-hash dedup lookup failed for storage ${targetStorage.dir}; writing fact fail-open: ${err}`,
|
|
14935
|
+
);
|
|
14936
|
+
}
|
|
14937
|
+
if (exactDuplicate) {
|
|
14223
14938
|
log.debug(
|
|
14224
|
-
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}…"`,
|
|
14939
|
+
`dedup: skipping duplicate fact "${fact.content.slice(0, 60)}…" in storage ${targetStorage.dir}`,
|
|
14225
14940
|
);
|
|
14226
14941
|
dedupedCount++;
|
|
14227
14942
|
continue;
|
|
@@ -14669,17 +15384,20 @@ export class Orchestrator {
|
|
|
14669
15384
|
validAt: sourceContext?.validAt,
|
|
14670
15385
|
source: extractionWriteSource,
|
|
14671
15386
|
});
|
|
14672
|
-
// Register chunked content in hash index too.
|
|
15387
|
+
// Register chunked content in the target storage hash index too.
|
|
14673
15388
|
// Thread 3 fix: canonicalize by stripping any pre-existing citation
|
|
14674
|
-
// so the stored hash matches what the dedup check computes
|
|
14675
|
-
|
|
14676
|
-
if (this.contentHashIndex) {
|
|
15389
|
+
// so the stored hash matches what the dedup check computes.
|
|
15390
|
+
try {
|
|
14677
15391
|
const canonicalChunkedContent =
|
|
14678
15392
|
citationEnabled &&
|
|
14679
15393
|
hasCitationForTemplate(fact.content, citationTemplate)
|
|
14680
15394
|
? stripCitationForTemplate(fact.content, citationTemplate)
|
|
14681
15395
|
: fact.content;
|
|
14682
|
-
this.
|
|
15396
|
+
await this.addContentHashDedup(targetStorage, canonicalChunkedContent);
|
|
15397
|
+
} catch (err) {
|
|
15398
|
+
log.warn(
|
|
15399
|
+
`content-hash dedup registration failed for chunked memory ${parentId}: ${err}`,
|
|
15400
|
+
);
|
|
14683
15401
|
}
|
|
14684
15402
|
|
|
14685
15403
|
for (const chunk of chunkResult.chunks) {
|
|
@@ -14957,11 +15675,10 @@ export class Orchestrator {
|
|
|
14957
15675
|
intentEntityTypes: inferredIntent?.entityTypes,
|
|
14958
15676
|
});
|
|
14959
15677
|
}
|
|
14960
|
-
// Register in content-hash index after successful
|
|
14961
|
-
// Thread 3 fix: canonicalize by stripping any pre-existing
|
|
14962
|
-
// the stored hash matches what the dedup check computes
|
|
14963
|
-
|
|
14964
|
-
if (this.contentHashIndex) {
|
|
15678
|
+
// Register in the target storage content-hash index after successful
|
|
15679
|
+
// write. Thread 3 fix: canonicalize by stripping any pre-existing
|
|
15680
|
+
// citation so the stored hash matches what the dedup check computes.
|
|
15681
|
+
try {
|
|
14965
15682
|
const canonicalFactContent =
|
|
14966
15683
|
citationEnabled &&
|
|
14967
15684
|
hasCitationForTemplate(fact.content, citationTemplate)
|
|
@@ -14971,7 +15688,11 @@ export class Orchestrator {
|
|
|
14971
15688
|
writeCategory === "procedure"
|
|
14972
15689
|
? buildProcedurePersistBody(fact.content, fact.procedureSteps)
|
|
14973
15690
|
: canonicalFactContent;
|
|
14974
|
-
this.
|
|
15691
|
+
await this.addContentHashDedup(targetStorage, hashRegisterKey);
|
|
15692
|
+
} catch (err) {
|
|
15693
|
+
log.warn(
|
|
15694
|
+
`content-hash dedup registration failed for memory ${memoryId}: ${err}`,
|
|
15695
|
+
);
|
|
14975
15696
|
}
|
|
14976
15697
|
} finally {
|
|
14977
15698
|
// Catalog touch (issue #1499): record AFTER every synchronous
|
|
@@ -15127,12 +15848,10 @@ export class Orchestrator {
|
|
|
15127
15848
|
touchBaseNonFactNamespace();
|
|
15128
15849
|
}
|
|
15129
15850
|
|
|
15130
|
-
// Save content-hash
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
.catch((err) => log.warn(`content-hash index save failed: ${err}`));
|
|
15135
|
-
}
|
|
15851
|
+
// Save any content-hash indexes touched during the batch.
|
|
15852
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
15853
|
+
log.warn(`content-hash index save failed: ${err}`),
|
|
15854
|
+
);
|
|
15136
15855
|
|
|
15137
15856
|
for (const {
|
|
15138
15857
|
storage: targetStorage,
|
|
@@ -16470,27 +17189,13 @@ export class Orchestrator {
|
|
|
16470
17189
|
// All criteria met — archive
|
|
16471
17190
|
const result = await this.storage.archiveMemory(memory);
|
|
16472
17191
|
if (result) {
|
|
16473
|
-
// Remove from content-hash index since it
|
|
16474
|
-
//
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
// hex string — use removeByHash to avoid double-hashing.
|
|
16481
|
-
this.contentHashIndex.removeByHash(memory.frontmatter.contentHash);
|
|
16482
|
-
} else {
|
|
16483
|
-
// Legacy memory written before contentHash was stored on the
|
|
16484
|
-
// frontmatter. Pre-#369 facts were stored without inline
|
|
16485
|
-
// citations, so memory.content is the raw fact text and we can
|
|
16486
|
-
// remove the hash directly from the content. This clears
|
|
16487
|
-
// stale dedup entries so the fact can be re-extracted.
|
|
16488
|
-
log.warn(
|
|
16489
|
-
`[fact-archival] removing hash for legacy memory ${memory.frontmatter.id ?? "(unknown)"} via content fallback — no contentHash in frontmatter`,
|
|
16490
|
-
);
|
|
16491
|
-
this.contentHashIndex.remove(memory.content);
|
|
16492
|
-
}
|
|
16493
|
-
}
|
|
17192
|
+
// Remove from the same storage-scoped content-hash index since it is
|
|
17193
|
+
// no longer in hot search.
|
|
17194
|
+
await this.removeContentHashForMemory(
|
|
17195
|
+
this.storage,
|
|
17196
|
+
memory,
|
|
17197
|
+
"fact-archival",
|
|
17198
|
+
);
|
|
16494
17199
|
await this.embeddingFallback.removeFromIndex(memory.frontmatter.id);
|
|
16495
17200
|
if (
|
|
16496
17201
|
this.config.queryAwareIndexingEnabled &&
|
|
@@ -16508,13 +17213,11 @@ export class Orchestrator {
|
|
|
16508
17213
|
}
|
|
16509
17214
|
}
|
|
16510
17215
|
|
|
16511
|
-
// Save hash
|
|
16512
|
-
if (archivedCount > 0
|
|
16513
|
-
await this.
|
|
16514
|
-
.save
|
|
16515
|
-
|
|
16516
|
-
log.warn(`content-hash index save failed during archival: ${err}`),
|
|
16517
|
-
);
|
|
17216
|
+
// Save hash indexes if we removed any entries.
|
|
17217
|
+
if (archivedCount > 0) {
|
|
17218
|
+
await this.saveContentHashIndexes().catch((err) =>
|
|
17219
|
+
log.warn(`content-hash index save failed during archival: ${err}`),
|
|
17220
|
+
);
|
|
16518
17221
|
}
|
|
16519
17222
|
|
|
16520
17223
|
return archivedCount;
|