@remnic/core 9.3.716 → 9.3.718
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-boundary.d.ts +5 -5
- package/dist/access-boundary.js +10 -9
- package/dist/access-cli.js +26 -25
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +14 -13
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +13 -12
- package/dist/access-operations-batch.js +11 -10
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +12 -11
- package/dist/{access-service-BW8cm_uU.d.ts → access-service-CZbnt0t2.d.ts} +4 -4
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +9 -8
- package/dist/access-surface-catalog.d.ts +5 -5
- 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 +4 -4
- package/dist/briefing.d.ts +3 -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/capabilities.d.ts +1 -1
- package/dist/{catalog-CbR0CsUw.d.ts → catalog-CEYlMs9X.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-JEIROMLX.js → chunk-2COL35PK.js} +95 -9
- package/dist/chunk-2COL35PK.js.map +1 -0
- package/dist/{chunk-XZTUMWKQ.js → chunk-2PTBLS2S.js} +2 -2
- package/dist/{chunk-2Y6LMOQQ.js → chunk-2VHMVWPU.js} +2 -2
- package/dist/{chunk-6UU43ZGD.js → chunk-4KZGLDJJ.js} +2 -2
- package/dist/{chunk-K44MIN6D.js → chunk-5RHM43BJ.js} +3 -3
- package/dist/{chunk-MECU6WSH.js → chunk-72VDBALG.js} +18 -18
- package/dist/{chunk-UY4JMEAK.js → chunk-7EEVXW7X.js} +2 -2
- package/dist/{chunk-XCTDHF7U.js → chunk-A5TUPZ3N.js} +33 -1
- package/dist/chunk-A5TUPZ3N.js.map +1 -0
- package/dist/{chunk-WI4QX7GH.js → chunk-A66YRSOS.js} +2 -2
- package/dist/{chunk-CTAHKIG6.js → chunk-ANI5DUAE.js} +10 -8
- package/dist/{chunk-CTAHKIG6.js.map → chunk-ANI5DUAE.js.map} +1 -1
- package/dist/{chunk-O62P2LFL.js → chunk-APUJIMDM.js} +2 -2
- package/dist/{chunk-MPHDVHIB.js → chunk-DIFU33GU.js} +2 -2
- package/dist/{chunk-DXGS5YOQ.js → chunk-EH27LHML.js} +2 -2
- package/dist/{chunk-BNOAB5HH.js → chunk-F3PVXKHP.js} +78 -1442
- package/dist/chunk-F3PVXKHP.js.map +1 -0
- package/dist/{chunk-6QUPUTT7.js → chunk-HLIR3RJ7.js} +6 -10
- package/dist/{chunk-6QUPUTT7.js.map → chunk-HLIR3RJ7.js.map} +1 -1
- package/dist/{chunk-SHRRWOVY.js → chunk-J6BH2OBA.js} +64 -21
- package/dist/chunk-J6BH2OBA.js.map +1 -0
- package/dist/{chunk-6FUJ2VYY.js → chunk-JAKYZGV4.js} +3 -3
- package/dist/{chunk-3VUPWDSE.js → chunk-JIZGYQYH.js} +4 -4
- package/dist/{chunk-QOIONSOI.js → chunk-KRGC4EDX.js} +2 -2
- package/dist/{chunk-AJOIM6UR.js → chunk-L5ASZA2F.js} +2 -2
- package/dist/{chunk-I56LKMF7.js → chunk-LXSG4L55.js} +2 -2
- package/dist/{chunk-MSVSP5VO.js → chunk-N3DUH6OO.js} +1 -1
- package/dist/chunk-N3DUH6OO.js.map +1 -0
- package/dist/{chunk-SQ4LU27I.js → chunk-NFRBADNR.js} +195 -80
- package/dist/chunk-NFRBADNR.js.map +1 -0
- package/dist/chunk-OJU73BUX.js +1688 -0
- package/dist/chunk-OJU73BUX.js.map +1 -0
- package/dist/{chunk-JVEMUX6L.js → chunk-OZ2KQ7HF.js} +2 -2
- package/dist/{chunk-SYDHFFAW.js → chunk-P5IPFZVH.js} +5 -5
- package/dist/{chunk-GM3BMWKR.js → chunk-REWQAFSA.js} +2 -2
- package/dist/{chunk-V7GDTQ6I.js → chunk-S35FX6MU.js} +2 -2
- package/dist/{chunk-MTHNJCOJ.js → chunk-S3OPGGZB.js} +2 -2
- package/dist/{chunk-IHMT6XTY.js → chunk-U5H7WHTS.js} +6 -4
- package/dist/{chunk-IHMT6XTY.js.map → chunk-U5H7WHTS.js.map} +1 -1
- package/dist/{chunk-RBHANGVX.js → chunk-UCJ5CDTK.js} +6 -6
- package/dist/{chunk-HHLWSWWF.js → chunk-URM3HW2R.js} +2 -2
- package/dist/{chunk-VO4UICII.js → chunk-YTZLBBEB.js} +2 -2
- package/dist/{chunk-OC56UWV5.js → chunk-YYOK4GVF.js} +2 -2
- package/dist/{cli-Dpivy32W.d.ts → cli-1orcdXOY.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +28 -27
- 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 +4 -4
- 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/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +42 -41
- package/dist/index.js.map +1 -1
- 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/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 +5 -5
- 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 +2 -2
- 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 +2 -2
- 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 +11 -9
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +8 -5
- package/dist/{orchestrator-vygo2YIM.d.ts → orchestrator-kOxredSr.d.ts} +22 -5
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +17 -16
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.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-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/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/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-DSjMDZ1u.d.ts → semantic-consolidation-B58v3S-a.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 +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/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-D9dwzU_x.d.ts → types-ACmDOScJ.d.ts} +36 -0
- 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/package.json +2 -2
- package/src/access-service.ts +56 -41
- package/src/briefing.ts +32 -0
- package/src/config.ts +32 -0
- package/src/correction/index.ts +24 -0
- package/src/correction/passive-capture.test.ts +500 -0
- package/src/correction/passive-capture.ts +335 -0
- package/src/correction/passive-correction-detector.test.ts +263 -0
- package/src/correction/passive-correction-detector.ts +325 -0
- package/src/correction/passive-correction-notifications.ts +123 -0
- package/src/maintenance/namespace-planner.ts +3 -10
- package/src/orchestrator.ts +231 -73
- package/src/scopes/scope-plan.test.ts +168 -1
- package/src/scopes/scope-plan.ts +163 -0
- package/src/types.ts +36 -0
- package/dist/chunk-BNOAB5HH.js.map +0 -1
- package/dist/chunk-JEIROMLX.js.map +0 -1
- package/dist/chunk-MSVSP5VO.js.map +0 -1
- package/dist/chunk-SHRRWOVY.js.map +0 -1
- package/dist/chunk-SQ4LU27I.js.map +0 -1
- package/dist/chunk-XCTDHF7U.js.map +0 -1
- /package/dist/{chunk-XZTUMWKQ.js.map → chunk-2PTBLS2S.js.map} +0 -0
- /package/dist/{chunk-2Y6LMOQQ.js.map → chunk-2VHMVWPU.js.map} +0 -0
- /package/dist/{chunk-6UU43ZGD.js.map → chunk-4KZGLDJJ.js.map} +0 -0
- /package/dist/{chunk-K44MIN6D.js.map → chunk-5RHM43BJ.js.map} +0 -0
- /package/dist/{chunk-MECU6WSH.js.map → chunk-72VDBALG.js.map} +0 -0
- /package/dist/{chunk-UY4JMEAK.js.map → chunk-7EEVXW7X.js.map} +0 -0
- /package/dist/{chunk-WI4QX7GH.js.map → chunk-A66YRSOS.js.map} +0 -0
- /package/dist/{chunk-O62P2LFL.js.map → chunk-APUJIMDM.js.map} +0 -0
- /package/dist/{chunk-MPHDVHIB.js.map → chunk-DIFU33GU.js.map} +0 -0
- /package/dist/{chunk-DXGS5YOQ.js.map → chunk-EH27LHML.js.map} +0 -0
- /package/dist/{chunk-6FUJ2VYY.js.map → chunk-JAKYZGV4.js.map} +0 -0
- /package/dist/{chunk-3VUPWDSE.js.map → chunk-JIZGYQYH.js.map} +0 -0
- /package/dist/{chunk-QOIONSOI.js.map → chunk-KRGC4EDX.js.map} +0 -0
- /package/dist/{chunk-AJOIM6UR.js.map → chunk-L5ASZA2F.js.map} +0 -0
- /package/dist/{chunk-I56LKMF7.js.map → chunk-LXSG4L55.js.map} +0 -0
- /package/dist/{chunk-JVEMUX6L.js.map → chunk-OZ2KQ7HF.js.map} +0 -0
- /package/dist/{chunk-SYDHFFAW.js.map → chunk-P5IPFZVH.js.map} +0 -0
- /package/dist/{chunk-GM3BMWKR.js.map → chunk-REWQAFSA.js.map} +0 -0
- /package/dist/{chunk-V7GDTQ6I.js.map → chunk-S35FX6MU.js.map} +0 -0
- /package/dist/{chunk-MTHNJCOJ.js.map → chunk-S3OPGGZB.js.map} +0 -0
- /package/dist/{chunk-RBHANGVX.js.map → chunk-UCJ5CDTK.js.map} +0 -0
- /package/dist/{chunk-HHLWSWWF.js.map → chunk-URM3HW2R.js.map} +0 -0
- /package/dist/{chunk-VO4UICII.js.map → chunk-YTZLBBEB.js.map} +0 -0
- /package/dist/{chunk-OC56UWV5.js.map → chunk-YYOK4GVF.js.map} +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* correction/passive-correction-detector.ts — morphology-aware heuristic detector
|
|
3
|
+
* for corrections expressed passively in conversation turns (issue #1581).
|
|
4
|
+
*
|
|
5
|
+
* Users correct their agents constantly in conversation: "no, we renamed that
|
|
6
|
+
* service", "I don't use Vim anymore", "the deadline moved to Friday". This
|
|
7
|
+
* module detects those corrections from turn text — NO extra LLM call — so the
|
|
8
|
+
* passive-capture module can route them to the Correction Contract (#1580).
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - Pure function: `detectPassiveCorrections(turns)` → `PassiveCorrection[]`.
|
|
12
|
+
* - Only USER turns are scanned (assistants don't correct their own memory).
|
|
13
|
+
* - Morphology-aware: matches conjugations/variants per AGENTS.md intent
|
|
14
|
+
* guardrail ("heuristics must be morphology-aware and precedence-tested").
|
|
15
|
+
* - Anti-fixture guards reject: hypotheticals, third-party corrections,
|
|
16
|
+
* quoted-speech corrections, and self-resolving corrections.
|
|
17
|
+
*
|
|
18
|
+
* The detector is deliberately CONSERVATIVE — it prefers false negatives
|
|
19
|
+
* (missing a correction) over false positives (flooding the review queue).
|
|
20
|
+
* The nightly contradiction scan and the interactive correction chat (#1583)
|
|
21
|
+
* catch what passive detection misses.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Types
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export type PassiveCorrectionPolarity = "update" | "retract" | "stop_storing";
|
|
29
|
+
|
|
30
|
+
export interface PassiveCorrection {
|
|
31
|
+
/** What the user says is wrong ("Redis", "the deadline", "my editor preference"). */
|
|
32
|
+
targetHint: string;
|
|
33
|
+
/** The new truth, or "" for pure retractions. */
|
|
34
|
+
correctedAssertion: string;
|
|
35
|
+
polarity: PassiveCorrectionPolarity;
|
|
36
|
+
/** `[m:xxxx]` handle references found verbatim in the turn (#1582). */
|
|
37
|
+
handles: string[];
|
|
38
|
+
/** Heuristic confidence in [0, 1] — influences auto-apply gating. */
|
|
39
|
+
confidence: number;
|
|
40
|
+
/** The verbatim source text that triggered the detection. */
|
|
41
|
+
sourceExcerpt: string;
|
|
42
|
+
/** Index of the turn in the input array that produced this correction. */
|
|
43
|
+
turnIndex: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DetectorTurn {
|
|
47
|
+
role: "user" | "assistant" | "other";
|
|
48
|
+
content: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Pattern definitions
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
interface CorrectionPattern {
|
|
56
|
+
polarity: PassiveCorrectionPolarity;
|
|
57
|
+
/** Regex MUST be case-insensitive and use word boundaries where appropriate. */
|
|
58
|
+
regex: RegExp;
|
|
59
|
+
/** Confidence assigned when this pattern is the sole signal. */
|
|
60
|
+
confidence: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Strong correction signals — these phrases reliably indicate the user is
|
|
65
|
+
* correcting a stored memory about their own world. Each is morphology-aware
|
|
66
|
+
* (covers present/progressive/past tenses and common variants).
|
|
67
|
+
*/
|
|
68
|
+
const PATTERNS: readonly CorrectionPattern[] = [
|
|
69
|
+
// ── update: new fact replaces / corrects an old one ──────────────────────
|
|
70
|
+
{
|
|
71
|
+
polarity: "update",
|
|
72
|
+
// "actually, it's X not Y" / "actually we use X now"
|
|
73
|
+
regex: /\bactually[,\s]+(?:it'?s|we|the|i|our|my|\[m:)/i,
|
|
74
|
+
confidence: 0.85,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
polarity: "update",
|
|
78
|
+
// "we switched/switching/switched to X"
|
|
79
|
+
regex: /\bwe\b\s*(?:(?:'re|'ve|are|have)\s+)?(?:switch(?:ed|ing)?|migrat(?:ed|ing)?|mov(?:ed|ing)?|chang(?:ed|ing)?)\s+(?:to|over to|from)\b/i,
|
|
80
|
+
confidence: 0.9,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
polarity: "update",
|
|
84
|
+
// "we renamed X to Y" / "i renamed"
|
|
85
|
+
regex: /\b(?:we|i)\s+renam(?:ed|ing)\b/i,
|
|
86
|
+
confidence: 0.9,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
polarity: "update",
|
|
90
|
+
// "it's now X" / "the deadline moved to X" / "the date changed to X"
|
|
91
|
+
regex: /\b(?:it'?s now|the\s+(?:deadline|date|meeting|standup|review)\s+(?:moved|chang(?:ed|ing))?\s*to)\b/i,
|
|
92
|
+
confidence: 0.85,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
polarity: "update",
|
|
96
|
+
// "no, " at the start of a turn or clause — strong correction signal
|
|
97
|
+
regex: /(?:^|\.\s+|\n)\s*no[,\s]+(?:we|i|it|the|our|my|that)\b/i,
|
|
98
|
+
confidence: 0.8,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
polarity: "update",
|
|
102
|
+
// "no longer" / "not anymore" + an activity
|
|
103
|
+
regex: /\b(?:no longer|not anymore)\b/i,
|
|
104
|
+
confidence: 0.8,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
polarity: "update",
|
|
108
|
+
// Standalone "outdated" — catches "are both outdated", "is outdated", etc.
|
|
109
|
+
regex: /\boutdated\b/i,
|
|
110
|
+
confidence: 0.75,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
polarity: "update",
|
|
114
|
+
// "that's outdated" / "that's old"
|
|
115
|
+
regex: /\bthat'?s\s+(?:outdated|old|no longer (?:true|correct|accurate))\b/i,
|
|
116
|
+
confidence: 0.85,
|
|
117
|
+
},
|
|
118
|
+
// ── retract: user says a fact is simply wrong ────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
polarity: "retract",
|
|
121
|
+
regex: /\bthat'?s\s+(?:wrong|not right|incorrect|not true|false)\b/i,
|
|
122
|
+
confidence: 0.85,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
polarity: "retract",
|
|
126
|
+
// "we don't use X anymore" / "I don't use X anymore"
|
|
127
|
+
regex: /\b(?:we|i)\s+don'?t\s+use\s+\S+\s+anymore\b/i,
|
|
128
|
+
confidence: 0.9,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
polarity: "retract",
|
|
132
|
+
regex: /\bforget about\b/i,
|
|
133
|
+
confidence: 0.8,
|
|
134
|
+
},
|
|
135
|
+
// ── stop_storing: user wants the agent to stop a behavior ────────────────
|
|
136
|
+
{
|
|
137
|
+
polarity: "stop_storing",
|
|
138
|
+
regex: /\bstop\s+(?:suggesting|bringing\s+up|recommending|reminding\s+me\s+(?:about)?)\b/i,
|
|
139
|
+
confidence: 0.9,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
polarity: "stop_storing",
|
|
143
|
+
regex: /\b(?:don'?t|never)\s+(?:mention|suggest|recommend|store|bring\s+up)\b/i,
|
|
144
|
+
confidence: 0.85,
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Anti-fixture guards
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Hypothetical markers — if present near the correction signal, the turn is
|
|
154
|
+
* discussing a hypothetical scenario, not correcting a stored fact.
|
|
155
|
+
*/
|
|
156
|
+
const HYPOTHETICAL_REGEX =
|
|
157
|
+
/\b(?:if\s+(?:we|you|i|they)\s+(?:ever|were|could|might|should|decide)|what\s+if|suppose|hypothetically|in\s+(?:theory|principle)|let'?s\s+say|imagine\s+if)\b/i;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Third-party correction markers — the user is correcting someone else, not
|
|
161
|
+
* their agent's memory of their own world.
|
|
162
|
+
*/
|
|
163
|
+
const THIRD_PARTY_REGEX =
|
|
164
|
+
/\b(?:tell\s+\w+\s+(?:he|she|they|you)'?re?\s+wrong|he'?s\s+wrong\s+about|she'?s\s+wrong\s+about|they'?re\s+wrong\s+about|correct\s+\w+\s+on)\b/i;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Tool-output correction — the user is saying a tool's output is wrong, not
|
|
168
|
+
* that their stored memory is wrong.
|
|
169
|
+
*/
|
|
170
|
+
const TOOL_OUTPUT_REGEX =
|
|
171
|
+
/\b(?:the\s+(?:output|result|response|error|log)|your\s+(?:output|result|response|code|answer))\s+(?:is\s+)?(?:wrong|incorrect|bad|off)\b/i;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Self-resolving correction — the user corrects themselves within the same
|
|
175
|
+
* turn ("actually wait, no, the original was right"). These turn back and
|
|
176
|
+
* cancel out.
|
|
177
|
+
*/
|
|
178
|
+
const SELF_RESOLVE_REGEX =
|
|
179
|
+
/(?:actually|wait|no)[,\s]+(?:never\s*mind|forget\s*(?:that|it)|the\s+(?:original|first)\s+(?:was|is)\s+(?:right|correct)|scratch\s+that|disregard)/i;
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Helpers
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
/** Extract `[m:xxxx]` handle references from text (#1582). */
|
|
186
|
+
export function extractHandles(text: string): string[] {
|
|
187
|
+
const matches = text.match(/\[m:[0-9a-fA-F]{4,}\]/g);
|
|
188
|
+
return matches ?? [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Extract a target hint from the matched correction text. Returns a short
|
|
193
|
+
* phrase (≤80 chars) the planner can use to locate the affected memory.
|
|
194
|
+
*/
|
|
195
|
+
function extractTargetHint(text: string, match: RegExpMatchArray): string {
|
|
196
|
+
const matchStart = match.index ?? 0;
|
|
197
|
+
// Take a window around the match: 20 chars before to 60 chars after.
|
|
198
|
+
const start = Math.max(0, matchStart - 20);
|
|
199
|
+
const end = Math.min(text.length, matchStart + match[0].length + 60);
|
|
200
|
+
const window = text.slice(start, end).replace(/\s+/g, " ").trim();
|
|
201
|
+
return window.length > 80 ? window.slice(0, 77) + "..." : window;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extract the corrected assertion — the new truth the user is expressing.
|
|
206
|
+
* For "update" polarity, this is the full turn text (the planner searches for
|
|
207
|
+
* the old fact and drafts a replacement). For "retract", it's "" (no
|
|
208
|
+
* replacement). For "stop_storing", it's the behavior to suppress.
|
|
209
|
+
*/
|
|
210
|
+
function extractCorrectedAssertion(
|
|
211
|
+
text: string,
|
|
212
|
+
polarity: PassiveCorrectionPolarity,
|
|
213
|
+
): string {
|
|
214
|
+
if (polarity === "retract") return "";
|
|
215
|
+
// For update/stop_storing, the full turn IS the assertion (truncated).
|
|
216
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
217
|
+
return trimmed.length > 200 ? trimmed.slice(0, 197) + "..." : trimmed;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check whether a regex match at the given index is within quotation marks.
|
|
222
|
+
* Used to reject corrections that are actually quoted speech (someone else's
|
|
223
|
+
* words the user is relaying, not the user correcting their own memory).
|
|
224
|
+
*/
|
|
225
|
+
function isWithinQuotes(text: string, matchIndex: number): boolean {
|
|
226
|
+
// Only DOUBLE quotes delimit quoted speech. Single quotes (ASCII apostrophe
|
|
227
|
+
// and smart single quotes) are deliberately NOT treated as delimiters: in
|
|
228
|
+
// English they are overwhelmingly contractions/possessives
|
|
229
|
+
// ("I don't think we switched"), which would falsely mark a
|
|
230
|
+
// correction as quoted speech and suppress a valid detection (false
|
|
231
|
+
// negative). A contraction before the correction phrase must not flip the
|
|
232
|
+
// detector into quoted mode.
|
|
233
|
+
let inQuote = false;
|
|
234
|
+
for (let i = 0; i < matchIndex && i < text.length; i++) {
|
|
235
|
+
const c = text[i];
|
|
236
|
+
if (c === '"' || c === '\u201c' || c === '\u201d') {
|
|
237
|
+
inQuote = !inQuote;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return inQuote;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Check whether any anti-fixture guard fires on the turn text.
|
|
245
|
+
* Returns the guard name that blocked detection, or null if clean.
|
|
246
|
+
*/
|
|
247
|
+
function detectAntiFixture(text: string): string | null {
|
|
248
|
+
if (SELF_RESOLVE_REGEX.test(text)) return "self_resolving";
|
|
249
|
+
if (HYPOTHETICAL_REGEX.test(text)) return "hypothetical";
|
|
250
|
+
if (THIRD_PARTY_REGEX.test(text)) return "third_party";
|
|
251
|
+
if (TOOL_OUTPUT_REGEX.test(text)) return "tool_output";
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Main entry point
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Detect passive corrections in a sequence of conversation turns.
|
|
261
|
+
*
|
|
262
|
+
* Only USER turns are scanned — assistant turns don't express corrections to
|
|
263
|
+
* the agent's stored memory. Each user turn is checked against the correction
|
|
264
|
+
* patterns; anti-fixture guards reject turns that look like hypotheticals,
|
|
265
|
+
* third-party corrections, tool-output complaints, or self-resolving double-
|
|
266
|
+
* corrections.
|
|
267
|
+
*
|
|
268
|
+
* A single turn may produce multiple corrections if different patterns match
|
|
269
|
+
* different parts of the text. Dedup by (turnIndex, targetHint) so overlapping
|
|
270
|
+
* patterns on the same phrase (e.g. "actually, we don't use Redis anymore"
|
|
271
|
+
* matching both update + retract) produce one correction.
|
|
272
|
+
*/
|
|
273
|
+
export function detectPassiveCorrections(
|
|
274
|
+
turns: readonly DetectorTurn[],
|
|
275
|
+
): PassiveCorrection[] {
|
|
276
|
+
const results: PassiveCorrection[] = [];
|
|
277
|
+
const seen = new Set<string>();
|
|
278
|
+
|
|
279
|
+
for (let i = 0; i < turns.length; i++) {
|
|
280
|
+
const turn = turns[i];
|
|
281
|
+
if (turn.role !== "user") continue;
|
|
282
|
+
const content = turn.content?.trim();
|
|
283
|
+
if (!content) continue;
|
|
284
|
+
|
|
285
|
+
// Anti-fixture guards take precedence — one guard kills ALL corrections
|
|
286
|
+
// from this turn (a hypothetical turn can't contain a real correction).
|
|
287
|
+
const guard = detectAntiFixture(content);
|
|
288
|
+
if (guard) continue;
|
|
289
|
+
|
|
290
|
+
const handles = extractHandles(content);
|
|
291
|
+
|
|
292
|
+
for (const pattern of PATTERNS) {
|
|
293
|
+
const match = pattern.regex.exec(content);
|
|
294
|
+
if (!match) continue;
|
|
295
|
+
|
|
296
|
+
// Reject corrections that appear within quotation marks — the user is
|
|
297
|
+
// relaying someone else's words, not correcting their own stored memory.
|
|
298
|
+
if (match.index !== undefined && isWithinQuotes(content, match.index)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const targetHint = extractTargetHint(content, match);
|
|
303
|
+
const correctedAssertion = extractCorrectedAssertion(content, pattern.polarity);
|
|
304
|
+
// Polarity-agnostic: "actually, we don't use Redis anymore" matches
|
|
305
|
+
// both the update ("actually") and retract ("don't use ... anymore")
|
|
306
|
+
// patterns with the same targetHint — emit one correction, not two
|
|
307
|
+
// (review: "deduplicate overlapping passive-correction cues").
|
|
308
|
+
const dedupKey = `${i}:${targetHint}`;
|
|
309
|
+
if (seen.has(dedupKey)) continue;
|
|
310
|
+
seen.add(dedupKey);
|
|
311
|
+
|
|
312
|
+
results.push({
|
|
313
|
+
targetHint,
|
|
314
|
+
correctedAssertion,
|
|
315
|
+
polarity: pattern.polarity,
|
|
316
|
+
handles,
|
|
317
|
+
confidence: pattern.confidence,
|
|
318
|
+
sourceExcerpt: content.slice(0, 200),
|
|
319
|
+
turnIndex: i,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return results;
|
|
325
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* correction/passive-correction-notifications.ts — JSONL notification queue
|
|
3
|
+
* for auto-applied passive corrections (issue #1581).
|
|
4
|
+
*
|
|
5
|
+
* When passive capture auto-applies a correction, it enqueues a one-line
|
|
6
|
+
* notification here: `✎ Memory updated: <summary> (auto-applied (plan <planId>))`.
|
|
7
|
+
* The plan id is referenced (not a revert command) because `remnic correct`
|
|
8
|
+
* has no `--revert` flag — corrections are page-versioned for manual revert.
|
|
9
|
+
* The next session-start briefing drains the queue ONCE (temp-file-then-rename,
|
|
10
|
+
* rule 54) and clears it, so a notification is shown exactly once.
|
|
11
|
+
*
|
|
12
|
+
* File location: `<storageDir>/state/corrections/notifications.jsonl`
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { mkdir, readFile, rename, writeFile, unlink } from "node:fs/promises";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { serializeMutations } from "../utils/serialize-mutations.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Types
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface PassiveCorrectionNotification {
|
|
24
|
+
planId: string;
|
|
25
|
+
/** One-line human-readable summary of what changed. */
|
|
26
|
+
summary: string;
|
|
27
|
+
/** Undo command the user can run. */
|
|
28
|
+
undoCommand: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Path
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
function notificationsPath(storageDir: string): string {
|
|
37
|
+
return path.join(storageDir, "state", "corrections", "notifications.jsonl");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Enqueue
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Append a notification line to the JSONL queue. Atomic (rule 54): the line
|
|
46
|
+
* is written to a temp file then renamed over the real file. Multiple
|
|
47
|
+
* notifications accumulate across calls — the queue is drained, not replaced.
|
|
48
|
+
*/
|
|
49
|
+
export async function enqueuePassiveCorrectionNotification(
|
|
50
|
+
storageDir: string,
|
|
51
|
+
notification: PassiveCorrectionNotification,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const filePath = notificationsPath(storageDir);
|
|
54
|
+
await serializeMutations(`passive-notification:${filePath}`, async () => {
|
|
55
|
+
// Read existing content (if any) so we accumulate rather than overwrite.
|
|
56
|
+
let existing = "";
|
|
57
|
+
try {
|
|
58
|
+
existing = await readFile(filePath, "utf-8");
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
61
|
+
if (code !== "ENOENT") throw err;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
65
|
+
const line = `${JSON.stringify(notification)}\n`;
|
|
66
|
+
const content = existing + line;
|
|
67
|
+
|
|
68
|
+
const tmp = `${filePath}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
69
|
+
await writeFile(tmp, content, "utf-8");
|
|
70
|
+
await rename(tmp, filePath);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Drain (read + clear — exactly once)
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read all pending notifications and atomically clear the file. Returns the
|
|
80
|
+
* notifications in insertion order. If the file does not exist, returns [].
|
|
81
|
+
*
|
|
82
|
+
* Drain-once semantics: the file is deleted after reading, so calling this
|
|
83
|
+
* twice in a row returns notifications only the first time.
|
|
84
|
+
*/
|
|
85
|
+
export async function drainPassiveCorrectionNotifications(
|
|
86
|
+
storageDir: string,
|
|
87
|
+
): Promise<PassiveCorrectionNotification[]> {
|
|
88
|
+
const filePath = notificationsPath(storageDir);
|
|
89
|
+
return serializeMutations(`passive-notification:${filePath}`, async () => {
|
|
90
|
+
let raw: string;
|
|
91
|
+
try {
|
|
92
|
+
raw = await readFile(filePath, "utf-8");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
95
|
+
if (code === "ENOENT") return [];
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const notifications: PassiveCorrectionNotification[] = [];
|
|
100
|
+
for (const line of raw.split("\n")) {
|
|
101
|
+
const trimmed = line.trim();
|
|
102
|
+
if (!trimmed) continue;
|
|
103
|
+
try {
|
|
104
|
+
notifications.push(JSON.parse(trimmed) as PassiveCorrectionNotification);
|
|
105
|
+
} catch {
|
|
106
|
+
// Skip malformed lines (rule 34 — best-effort, never crash a briefing).
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Clear the file atomically (delete is atomic enough for this use case —
|
|
112
|
+
// the worst case is a duplicate notification, never a lost one, because
|
|
113
|
+
// the read already captured the content).
|
|
114
|
+
try {
|
|
115
|
+
await unlink(filePath);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
118
|
+
if (code !== "ENOENT") throw err;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return notifications;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
@@ -9,6 +9,7 @@ import { namespaceIdentityToken } from "../namespaces/identity.js";
|
|
|
9
9
|
import { resolveNamespaceStorageRoot } from "../namespaces/storage.js";
|
|
10
10
|
import { displayErrorDetail } from "../runtime/better-sqlite.js";
|
|
11
11
|
import type { PluginConfig } from "../types.js";
|
|
12
|
+
import { getConfiguredNamespaces } from "../scopes/scope-plan.js";
|
|
12
13
|
|
|
13
14
|
export type NamespaceMaintenanceJobName = string;
|
|
14
15
|
|
|
@@ -110,15 +111,7 @@ export function __setNamespaceMaintenanceFsForTest(overrides: Partial<typeof nam
|
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
return Array.from(
|
|
115
|
-
new Set(
|
|
116
|
-
[config.defaultNamespace, config.sharedNamespace, ...config.namespacePolicies.map((policy) => policy.name)]
|
|
117
|
-
.map((value) => value.trim())
|
|
118
|
-
.filter(Boolean)
|
|
119
|
-
)
|
|
120
|
-
);
|
|
121
|
-
}
|
|
114
|
+
|
|
122
115
|
|
|
123
116
|
function inferConfiguredKind(config: PluginConfig, namespace: string): NamespaceKind {
|
|
124
117
|
if (namespace === config.defaultNamespace.trim()) return "default";
|
|
@@ -219,7 +212,7 @@ export async function planNamespaceMaintenance(
|
|
|
219
212
|
// once per configured name. The #1500 contract is "namespaces disabled:
|
|
220
213
|
// maintain the current default storage only," so collapse to the default.
|
|
221
214
|
const configured = config.namespacesEnabled
|
|
222
|
-
?
|
|
215
|
+
? getConfiguredNamespaces(config)
|
|
223
216
|
: [config.defaultNamespace.trim()].filter(Boolean);
|
|
224
217
|
const byNamespace = new Map<string, NamespaceMaintenanceCandidate>();
|
|
225
218
|
const skipped: NamespaceMaintenanceSkippedNamespace[] = [];
|