@remnic/core 9.3.629 → 9.3.631
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 +15 -13
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -4
- package/dist/access-http.js +7 -6
- package/dist/access-mcp.d.ts +5 -4
- package/dist/access-mcp.js +6 -5
- package/dist/{access-service-BdThkfIE.d.ts → access-service-C9_EpVHd.d.ts} +2 -2
- package/dist/access-service.d.ts +5 -4
- package/dist/access-service.js +5 -4
- 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/auto-sync-RFADEHIQ.js +75 -0
- package/dist/auto-sync-RFADEHIQ.js.map +1 -0
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -3
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -2
- 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 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-532VCWYW.js → chunk-242XFZ36.js} +2 -2
- package/dist/{chunk-XXO5TI3B.js → chunk-32U3N7H5.js} +3 -3
- package/dist/{chunk-KB4MFBF5.js → chunk-3RDYU3JS.js} +3 -3
- package/dist/{chunk-57QXN2CS.js → chunk-4S3N6HFG.js} +2 -2
- package/dist/{chunk-OLNNOHBC.js → chunk-5PT5I6JQ.js} +20 -14
- package/dist/{chunk-OLNNOHBC.js.map → chunk-5PT5I6JQ.js.map} +1 -1
- package/dist/{chunk-GE7Q7KXP.js → chunk-7A2QKUUA.js} +2 -2
- package/dist/{chunk-KKTXCFD7.js → chunk-7H5WCPBS.js} +95 -11
- package/dist/{chunk-KKTXCFD7.js.map → chunk-7H5WCPBS.js.map} +1 -1
- package/dist/{chunk-3MNBW7R7.js → chunk-C4KKM62E.js} +2 -2
- package/dist/{chunk-NKCW223V.js → chunk-CMN5AWAZ.js} +2 -2
- package/dist/{chunk-JXHMAQYT.js → chunk-DOBJH4I6.js} +4 -4
- package/dist/{chunk-TZDSNIRO.js → chunk-IFVFQRZ2.js} +5 -5
- package/dist/{chunk-LQYTQCXM.js → chunk-JCLECECB.js} +2 -2
- package/dist/chunk-KVDUDYEN.js +1164 -0
- package/dist/chunk-KVDUDYEN.js.map +1 -0
- package/dist/{chunk-QDV6VAD4.js → chunk-LEG7XWS2.js} +2 -2
- package/dist/chunk-M7XQSUBB.js +280 -0
- package/dist/chunk-M7XQSUBB.js.map +1 -0
- package/dist/{chunk-N5RGXWLQ.js → chunk-PUEAEQSN.js} +2 -2
- package/dist/{chunk-UGHUNQ74.js → chunk-QYGIQ5NM.js} +212 -417
- package/dist/chunk-QYGIQ5NM.js.map +1 -0
- package/dist/{chunk-JKCDQBDW.js → chunk-UXFOGILU.js} +2 -2
- package/dist/{chunk-MVQN73GT.js → chunk-VTR3MNYF.js} +2 -2
- package/dist/{chunk-KVFYTRMV.js → chunk-W25I7G6U.js} +2 -2
- package/dist/{chunk-3GLCUPXP.js → chunk-WLZBVYC6.js} +192 -889
- package/dist/chunk-WLZBVYC6.js.map +1 -0
- package/dist/{chunk-3R2UZV3U.js → chunk-X7EJF46S.js} +2 -2
- package/dist/{chunk-54KDA6UK.js → chunk-XG4NAWAV.js} +3 -3
- package/dist/{chunk-P2D2MM47.js → chunk-YROCXMCK.js} +2 -2
- package/dist/{cli-DAsHklrf.d.ts → cli-CuVEQWKr.d.ts} +3 -3
- package/dist/cli.d.ts +6 -5
- package/dist/cli.js +18 -17
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -2
- 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 -2
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -2
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +4 -4
- 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 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +4 -3
- 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 +8 -8
- package/dist/index.js +49 -45
- 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 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -3
- package/dist/mcp-memory-inspector-app.d.ts +5 -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 +4 -3
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -2
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +7 -6
- package/dist/{orchestrator-BexeSJ2j.d.ts → orchestrator-CoqytbK_.d.ts} +102 -10
- package/dist/orchestrator.d.ts +4 -3
- package/dist/orchestrator.js +12 -10
- 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 +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- 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/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-PwkzNfdK.d.ts → semantic-consolidation-BPs6BURk.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +3 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +3 -2
- 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 +38 -2
- package/dist/storage.js +6 -3
- 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/{types-BCF2wqKa.d.ts → types-CpMPD8xl.d.ts} +59 -11
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -2
- package/package.json +1 -1
- package/src/orchestrator.ts +74 -0
- package/src/storage.ts +100 -0
- package/src/wearables/auto-sync.test.ts +181 -0
- package/src/wearables/auto-sync.ts +129 -0
- package/src/wearables/cli.ts +6 -0
- package/src/wearables/config.test.ts +90 -11
- package/src/wearables/config.ts +113 -11
- package/src/wearables/memory-gen.test.ts +416 -1
- package/src/wearables/memory-gen.ts +381 -23
- package/src/wearables/pipeline.test.ts +396 -5
- package/src/wearables/pipeline.ts +174 -22
- package/src/wearables/service.test.ts +172 -0
- package/src/wearables/service.ts +84 -3
- package/src/wearables/storage-io.test.ts +81 -0
- package/src/wearables/trust.test.ts +123 -0
- package/src/wearables/trust.ts +168 -0
- package/src/wearables/types.ts +57 -9
- package/dist/chunk-3GLCUPXP.js.map +0 -1
- package/dist/chunk-UGHUNQ74.js.map +0 -1
- /package/dist/{chunk-532VCWYW.js.map → chunk-242XFZ36.js.map} +0 -0
- /package/dist/{chunk-XXO5TI3B.js.map → chunk-32U3N7H5.js.map} +0 -0
- /package/dist/{chunk-KB4MFBF5.js.map → chunk-3RDYU3JS.js.map} +0 -0
- /package/dist/{chunk-57QXN2CS.js.map → chunk-4S3N6HFG.js.map} +0 -0
- /package/dist/{chunk-GE7Q7KXP.js.map → chunk-7A2QKUUA.js.map} +0 -0
- /package/dist/{chunk-3MNBW7R7.js.map → chunk-C4KKM62E.js.map} +0 -0
- /package/dist/{chunk-NKCW223V.js.map → chunk-CMN5AWAZ.js.map} +0 -0
- /package/dist/{chunk-JXHMAQYT.js.map → chunk-DOBJH4I6.js.map} +0 -0
- /package/dist/{chunk-TZDSNIRO.js.map → chunk-IFVFQRZ2.js.map} +0 -0
- /package/dist/{chunk-LQYTQCXM.js.map → chunk-JCLECECB.js.map} +0 -0
- /package/dist/{chunk-QDV6VAD4.js.map → chunk-LEG7XWS2.js.map} +0 -0
- /package/dist/{chunk-N5RGXWLQ.js.map → chunk-PUEAEQSN.js.map} +0 -0
- /package/dist/{chunk-JKCDQBDW.js.map → chunk-UXFOGILU.js.map} +0 -0
- /package/dist/{chunk-MVQN73GT.js.map → chunk-VTR3MNYF.js.map} +0 -0
- /package/dist/{chunk-KVFYTRMV.js.map → chunk-W25I7G6U.js.map} +0 -0
- /package/dist/{chunk-3R2UZV3U.js.map → chunk-X7EJF46S.js.map} +0 -0
- /package/dist/{chunk-54KDA6UK.js.map → chunk-XG4NAWAV.js.map} +0 -0
- /package/dist/{chunk-P2D2MM47.js.map → chunk-YROCXMCK.js.map} +0 -0
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
writeDailyDigestMemory,
|
|
37
37
|
type WearableMemoryGenDeps,
|
|
38
38
|
} from "./memory-gen.js";
|
|
39
|
+
import { tokenizeDayBody, type CorroborationContext } from "./trust.js";
|
|
39
40
|
import { applyOffTheRecord, compileRedactionPatterns, redactText } from "./redaction.js";
|
|
40
41
|
import { loadSpeakerRegistry } from "./speakers.js";
|
|
41
42
|
import {
|
|
@@ -51,10 +52,13 @@ import type {
|
|
|
51
52
|
WearablesConfig,
|
|
52
53
|
} from "./types.js";
|
|
53
54
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Pathological-provider backstop on pagination loops. Day fetches are
|
|
57
|
+
* intentionally UNLIMITED for real data — a full day must never be
|
|
58
|
+
* truncated — so runaway protection comes from cursor-cycle detection
|
|
59
|
+
* plus this far-out-of-band ceiling, not from a content-sized cap.
|
|
60
|
+
*/
|
|
61
|
+
const PAGE_SAFETY_CEILING = 10_000;
|
|
58
62
|
/** Default lookback window (today + yesterday) for unscoped syncs. */
|
|
59
63
|
const DEFAULT_SYNC_DAYS = 2;
|
|
60
64
|
const MAX_SYNC_DAYS = 90;
|
|
@@ -80,14 +84,36 @@ export interface WearableSyncDeps {
|
|
|
80
84
|
date: string,
|
|
81
85
|
serialized: string,
|
|
82
86
|
): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Optional hook fired once after the sync wrote ANYTHING the search
|
|
89
|
+
* index should see: day transcripts, created memories, in-place
|
|
90
|
+
* promotions, or native imports. (A cross-source invalidation can
|
|
91
|
+
* promote memories on a run with zero transcript writes — the index
|
|
92
|
+
* must still refresh.)
|
|
93
|
+
*/
|
|
94
|
+
afterWrites?(): Promise<void>;
|
|
85
95
|
/**
|
|
86
96
|
* Memory-generation dependencies, or null when no extraction engine
|
|
87
97
|
* is available in this context (transcripts still sync; memory
|
|
88
98
|
* creation is skipped with a warning when the mode wanted it).
|
|
89
99
|
*/
|
|
90
100
|
memoryGen: WearableMemoryGenDeps | null;
|
|
101
|
+
/**
|
|
102
|
+
* Same-day transcript bodies from OTHER sources, for cross-device
|
|
103
|
+
* corroboration in smart mode. Absent disables the boost.
|
|
104
|
+
*/
|
|
105
|
+
readOtherSourceDayBodies?(
|
|
106
|
+
date: string,
|
|
107
|
+
excludeSource: string,
|
|
108
|
+
): Promise<Map<string, string>>;
|
|
109
|
+
/**
|
|
110
|
+
* Existing memories usable as support evidence: status "active" or
|
|
111
|
+
* "pending_review" (explicit allow-list — a borderline fact observed
|
|
112
|
+
* again on a later day is repetition signal, and the +0.10 boost is
|
|
113
|
+
* how it earns promotion). Rejected/quarantined/superseded/archived/
|
|
114
|
+
* forgotten rows are never support evidence.
|
|
115
|
+
*/
|
|
116
|
+
listSupportMemories?(): Promise<Array<{ id: string; content: string }>>;
|
|
91
117
|
/** Clock injection for tests. */
|
|
92
118
|
now?: () => Date;
|
|
93
119
|
}
|
|
@@ -163,23 +189,42 @@ async function fetchAllConversationsForDate(
|
|
|
163
189
|
signal: AbortSignal | undefined,
|
|
164
190
|
warnings: string[],
|
|
165
191
|
): Promise<{ conversations: WearableConversation[]; partial: boolean }> {
|
|
166
|
-
|
|
192
|
+
// Keyed by conversation id: a looping or overlapping provider can
|
|
193
|
+
// re-serve rows it already returned, and appending blindly would
|
|
194
|
+
// store the same conversation twice in the day file (Cursor review
|
|
195
|
+
// on PR #1464). Map keeps first-seen order; a re-served id replaces
|
|
196
|
+
// its entry in place, so the provider's LATEST version of a
|
|
197
|
+
// conversation wins — exactly what the current-day refresh wants.
|
|
198
|
+
const byId = new Map<string, WearableConversation>();
|
|
167
199
|
let cursor: string | null | undefined = undefined;
|
|
168
|
-
|
|
200
|
+
const seenCursors = new Set<string>();
|
|
201
|
+
const collect = () => [...byId.values()];
|
|
202
|
+
for (let page = 0; page < PAGE_SAFETY_CEILING; page++) {
|
|
169
203
|
const result = await connector.fetchConversations({
|
|
170
204
|
date,
|
|
171
205
|
timezone,
|
|
172
206
|
cursor,
|
|
173
207
|
signal,
|
|
174
208
|
});
|
|
175
|
-
|
|
176
|
-
|
|
209
|
+
for (const conversation of result.conversations) {
|
|
210
|
+
byId.set(conversation.id, conversation);
|
|
211
|
+
}
|
|
212
|
+
if (!result.nextCursor) return { conversations: collect(), partial: false };
|
|
213
|
+
// A repeated cursor means the provider's pagination is looping —
|
|
214
|
+
// following it again would refetch the same page forever.
|
|
215
|
+
if (seenCursors.has(result.nextCursor)) {
|
|
216
|
+
warnings.push(
|
|
217
|
+
`${connector.id}: provider pagination repeated cursor on ${date} — stopped to avoid an infinite loop; day may be partially synced (every sync refetches and re-warns while the provider misbehaves)`,
|
|
218
|
+
);
|
|
219
|
+
return { conversations: collect(), partial: true };
|
|
220
|
+
}
|
|
221
|
+
seenCursors.add(result.nextCursor);
|
|
177
222
|
cursor = result.nextCursor;
|
|
178
223
|
}
|
|
179
224
|
warnings.push(
|
|
180
|
-
`${connector.id}: stopped paginating ${date} after ${
|
|
225
|
+
`${connector.id}: stopped paginating ${date} after the ${PAGE_SAFETY_CEILING}-page safety ceiling — day may be partially synced (every sync refetches and re-warns while this persists)`,
|
|
181
226
|
);
|
|
182
|
-
return { conversations, partial: true };
|
|
227
|
+
return { conversations: collect(), partial: true };
|
|
183
228
|
}
|
|
184
229
|
|
|
185
230
|
/** Visible marker appended to day files whose fetch hit the page cap. */
|
|
@@ -276,6 +321,8 @@ export async function syncWearableSource(
|
|
|
276
321
|
correctionsApplied: 0,
|
|
277
322
|
transcriptsWritten: [],
|
|
278
323
|
memoriesCreated: 0,
|
|
324
|
+
memoriesPromoted: 0,
|
|
325
|
+
memoriesDemoted: 0,
|
|
279
326
|
memoriesSkipped: 0,
|
|
280
327
|
nativeMemoriesImported: 0,
|
|
281
328
|
warnings: [],
|
|
@@ -380,6 +427,9 @@ export async function syncWearableSource(
|
|
|
380
427
|
|
|
381
428
|
if (allElided) continue;
|
|
382
429
|
|
|
430
|
+
const needsSmartContext =
|
|
431
|
+
settings.memoryMode === "smart" || settings.importNativeMemories === "smart";
|
|
432
|
+
|
|
383
433
|
// The memory pass runs when the day changed, when forced, or when
|
|
384
434
|
// the last pass for this exact content didn't complete cleanly —
|
|
385
435
|
// a sync that stored the transcript but failed mid-memory-write
|
|
@@ -406,18 +456,31 @@ export async function syncWearableSource(
|
|
|
406
456
|
// re-runs the day.
|
|
407
457
|
let passClean = false;
|
|
408
458
|
try {
|
|
459
|
+
const corroboration = needsSmartContext
|
|
460
|
+
? await buildCorroborationContext(connector.id, date, deps)
|
|
461
|
+
: undefined;
|
|
462
|
+
const dayMemoryGen: WearableMemoryGenDeps = {
|
|
463
|
+
...deps.memoryGen,
|
|
464
|
+
...(corroboration !== undefined ? { corroboration } : {}),
|
|
465
|
+
};
|
|
409
466
|
const generated = await generateWearableMemories(
|
|
410
467
|
connector.id,
|
|
411
468
|
date,
|
|
412
469
|
cleaned.conversations,
|
|
413
470
|
settings,
|
|
414
471
|
registry,
|
|
415
|
-
|
|
472
|
+
dayMemoryGen,
|
|
416
473
|
);
|
|
417
474
|
summary.memoriesCreated += generated.created;
|
|
475
|
+
summary.memoriesPromoted += generated.promoted;
|
|
476
|
+
summary.memoriesDemoted += generated.demoted;
|
|
418
477
|
summary.memoriesSkipped += generated.skipped;
|
|
419
478
|
summary.warnings.push(...generated.warnings);
|
|
420
|
-
|
|
479
|
+
// Degraded-but-complete passes (e.g. judge unavailable) still
|
|
480
|
+
// record completion — only an aborted extraction should force
|
|
481
|
+
// the day to re-run on the next sync (Cursor review on PR
|
|
482
|
+
// #1462).
|
|
483
|
+
passClean = generated.completed;
|
|
421
484
|
if (config.digestEnabled) {
|
|
422
485
|
const wrote = await writeDailyDigestMemory(
|
|
423
486
|
connector.id,
|
|
@@ -448,7 +511,7 @@ export async function syncWearableSource(
|
|
|
448
511
|
}
|
|
449
512
|
|
|
450
513
|
if (
|
|
451
|
-
settings.importNativeMemories
|
|
514
|
+
settings.importNativeMemories !== "off" &&
|
|
452
515
|
typeof connector.fetchNativeMemories === "function"
|
|
453
516
|
) {
|
|
454
517
|
if (!deps.memoryGen) {
|
|
@@ -459,8 +522,29 @@ export async function syncWearableSource(
|
|
|
459
522
|
const alreadyImported = new Set(
|
|
460
523
|
previousState?.importedNativeMemoryIds ?? [],
|
|
461
524
|
);
|
|
525
|
+
// Native memories carry no day, so same-day cross-device
|
|
526
|
+
// corroboration does not apply to them — scoring a provider fact
|
|
527
|
+
// against an arbitrary day's tokens would be wrong-day evidence
|
|
528
|
+
// (Cursor review on PR #1462). They keep only the day-independent
|
|
529
|
+
// existing-memory support boost.
|
|
530
|
+
const nativeCorroboration =
|
|
531
|
+
settings.importNativeMemories === "smart"
|
|
532
|
+
? {
|
|
533
|
+
otherSourceDayTokens: new Map<string, Set<string>>(),
|
|
534
|
+
existingMemories: deps.listSupportMemories
|
|
535
|
+
? await deps.listSupportMemories()
|
|
536
|
+
: [],
|
|
537
|
+
}
|
|
538
|
+
: undefined;
|
|
539
|
+
const nativeMemoryGen: WearableMemoryGenDeps = {
|
|
540
|
+
...deps.memoryGen,
|
|
541
|
+
...(nativeCorroboration !== undefined
|
|
542
|
+
? { corroboration: nativeCorroboration }
|
|
543
|
+
: {}),
|
|
544
|
+
};
|
|
462
545
|
let cursor: string | null | undefined = undefined;
|
|
463
|
-
|
|
546
|
+
const seenNativeCursors = new Set<string>();
|
|
547
|
+
for (let page = 0; page < PAGE_SAFETY_CEILING; page++) {
|
|
464
548
|
const result = await connector.fetchNativeMemories({
|
|
465
549
|
cursor,
|
|
466
550
|
signal: options.signal,
|
|
@@ -469,28 +553,43 @@ export async function syncWearableSource(
|
|
|
469
553
|
connector.id,
|
|
470
554
|
result.memories,
|
|
471
555
|
alreadyImported,
|
|
472
|
-
|
|
556
|
+
settings,
|
|
557
|
+
nativeMemoryGen,
|
|
473
558
|
);
|
|
559
|
+
summary.warnings.push(...imported.warnings);
|
|
474
560
|
summary.nativeMemoriesImported += imported.imported;
|
|
475
561
|
importedNativeIds.push(...imported.importedIds);
|
|
476
562
|
for (const id of imported.importedIds) alreadyImported.add(id);
|
|
477
563
|
if (!result.nextCursor) break;
|
|
564
|
+
if (seenNativeCursors.has(result.nextCursor)) {
|
|
565
|
+
summary.warnings.push(
|
|
566
|
+
`${connector.id}: provider pagination repeated cursor during native-memory import — stopped to avoid an infinite loop; remaining items import on the next sync`,
|
|
567
|
+
);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
seenNativeCursors.add(result.nextCursor);
|
|
478
571
|
cursor = result.nextCursor;
|
|
479
|
-
if (page ===
|
|
572
|
+
if (page === PAGE_SAFETY_CEILING - 1) {
|
|
480
573
|
summary.warnings.push(
|
|
481
|
-
`${connector.id}: stopped native-memory import
|
|
574
|
+
`${connector.id}: stopped native-memory import at the ${PAGE_SAFETY_CEILING}-page safety ceiling — remaining items import on the next sync`,
|
|
482
575
|
);
|
|
483
576
|
}
|
|
484
577
|
}
|
|
485
578
|
}
|
|
486
579
|
}
|
|
487
580
|
|
|
488
|
-
|
|
581
|
+
const wroteAnything =
|
|
582
|
+
summary.transcriptsWritten.length > 0 ||
|
|
583
|
+
summary.memoriesCreated > 0 ||
|
|
584
|
+
summary.memoriesPromoted > 0 ||
|
|
585
|
+
summary.memoriesDemoted > 0 ||
|
|
586
|
+
summary.nativeMemoriesImported > 0;
|
|
587
|
+
if (wroteAnything && deps.afterWrites) {
|
|
489
588
|
try {
|
|
490
|
-
await deps.
|
|
589
|
+
await deps.afterWrites();
|
|
491
590
|
} catch (err) {
|
|
492
591
|
summary.warnings.push(
|
|
493
|
-
`search reindex failed (
|
|
592
|
+
`search reindex failed (writes are stored and will index on the next update): ${describeErrorForOperator(err)}`,
|
|
494
593
|
);
|
|
495
594
|
}
|
|
496
595
|
}
|
|
@@ -505,11 +604,64 @@ export async function syncWearableSource(
|
|
|
505
604
|
clearMemoryDays: failedMemoryDays,
|
|
506
605
|
importedNativeMemoryIds: importedNativeIds,
|
|
507
606
|
});
|
|
607
|
+
// New same-day evidence invalidates OTHER sources' memory-pass
|
|
608
|
+
// completion for the days this source just (re)wrote: their next
|
|
609
|
+
// sync re-scores with this transcript available, and the promotion
|
|
610
|
+
// path upgrades earlier borderline writes in place (Cursor review on
|
|
611
|
+
// PR #1462).
|
|
612
|
+
if (summary.transcriptsWritten.length > 0) {
|
|
613
|
+
const cleared: typeof syncState.sources = {};
|
|
614
|
+
for (const [otherId, otherState] of Object.entries(syncState.sources)) {
|
|
615
|
+
if (otherId === connector.id || otherState.memoryDayHashes === undefined) {
|
|
616
|
+
cleared[otherId] = otherState;
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
const memoryDays = { ...otherState.memoryDayHashes };
|
|
620
|
+
let touched = false;
|
|
621
|
+
for (const date of summary.transcriptsWritten) {
|
|
622
|
+
if (date in memoryDays) {
|
|
623
|
+
delete memoryDays[date];
|
|
624
|
+
touched = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
cleared[otherId] = touched
|
|
628
|
+
? { ...otherState, memoryDayHashes: memoryDays }
|
|
629
|
+
: otherState;
|
|
630
|
+
}
|
|
631
|
+
syncState = { version: 1, sources: cleared };
|
|
632
|
+
}
|
|
508
633
|
await saveSyncState(deps.memoryDir, syncState);
|
|
509
634
|
|
|
510
635
|
return summary;
|
|
511
636
|
}
|
|
512
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Assemble smart-mode corroboration evidence: other sources' same-day
|
|
640
|
+
* transcript tokens + existing active memories. The memory list loads
|
|
641
|
+
* fresh per day (not per run) so facts written on earlier days of a
|
|
642
|
+
* multi-day backfill are visible as support evidence on later days —
|
|
643
|
+
* the underlying readAllMemories is cached in storage and invalidated
|
|
644
|
+
* by writes, so the per-day refresh is cheap (Cursor review on PR
|
|
645
|
+
* #1462).
|
|
646
|
+
*/
|
|
647
|
+
async function buildCorroborationContext(
|
|
648
|
+
sourceId: string,
|
|
649
|
+
date: string,
|
|
650
|
+
deps: WearableSyncDeps,
|
|
651
|
+
): Promise<CorroborationContext> {
|
|
652
|
+
const otherSourceDayTokens = new Map<string, Set<string>>();
|
|
653
|
+
if (deps.readOtherSourceDayBodies) {
|
|
654
|
+
const bodies = await deps.readOtherSourceDayBodies(date, sourceId);
|
|
655
|
+
for (const [otherSource, body] of bodies) {
|
|
656
|
+
otherSourceDayTokens.set(otherSource, tokenizeDayBody(body));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
const existingMemories = deps.listSupportMemories
|
|
660
|
+
? await deps.listSupportMemories()
|
|
661
|
+
: [];
|
|
662
|
+
return { otherSourceDayTokens, existingMemories };
|
|
663
|
+
}
|
|
664
|
+
|
|
513
665
|
export function defaultTimezone(): string {
|
|
514
666
|
try {
|
|
515
667
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -74,6 +74,33 @@ function makeStorage(memoryDir: string): WearableStorageIo & {
|
|
|
74
74
|
async hasFactContentHash() {
|
|
75
75
|
return false;
|
|
76
76
|
},
|
|
77
|
+
async findWearableMemoryByContent(content: string) {
|
|
78
|
+
const needle = content.trim();
|
|
79
|
+
const match = storage.memories.find(
|
|
80
|
+
(memory) =>
|
|
81
|
+
memory.frontmatter.source.startsWith("wearable:") &&
|
|
82
|
+
memory.content.trim() === needle,
|
|
83
|
+
);
|
|
84
|
+
return match
|
|
85
|
+
? { id: match.frontmatter.id, status: match.frontmatter.status }
|
|
86
|
+
: null;
|
|
87
|
+
},
|
|
88
|
+
async promoteWearableMemory(id: string) {
|
|
89
|
+
const match = storage.memories.find((memory) => memory.frontmatter.id === id);
|
|
90
|
+
if (!match || match.frontmatter.status !== "pending_review") return false;
|
|
91
|
+
match.frontmatter.status = "active";
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
async demoteWearableMemory(id: string, attrs: Record<string, string>) {
|
|
95
|
+
const match = storage.memories.find((memory) => memory.frontmatter.id === id);
|
|
96
|
+
if (!match || match.frontmatter.status !== "pending_review") return false;
|
|
97
|
+
match.frontmatter.status = "rejected";
|
|
98
|
+
match.frontmatter.structuredAttributes = {
|
|
99
|
+
...(match.frontmatter.structuredAttributes ?? {}),
|
|
100
|
+
...attrs,
|
|
101
|
+
};
|
|
102
|
+
return true;
|
|
103
|
+
},
|
|
77
104
|
};
|
|
78
105
|
return storage;
|
|
79
106
|
}
|
|
@@ -313,6 +340,151 @@ test("transcriptMemories filters by wearable source and day", async () => {
|
|
|
313
340
|
}
|
|
314
341
|
});
|
|
315
342
|
|
|
343
|
+
test("support corpus includes pending_review rows and excludes terminal statuses", async () => {
|
|
344
|
+
const { registerWearableConnector, clearWearableConnectors } = await import("./registry.js");
|
|
345
|
+
const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
|
|
346
|
+
const borderlineFact =
|
|
347
|
+
"The launch moved to September twelfth after the vendor call.";
|
|
348
|
+
const makeRow = (
|
|
349
|
+
id: string,
|
|
350
|
+
status: string | undefined,
|
|
351
|
+
content: string,
|
|
352
|
+
archivedAt?: string,
|
|
353
|
+
) => ({
|
|
354
|
+
path: `facts/${id}.md`,
|
|
355
|
+
frontmatter: {
|
|
356
|
+
id,
|
|
357
|
+
source: "wearable:limitless",
|
|
358
|
+
created: "2026-06-09T16:00:00.000Z",
|
|
359
|
+
tags: ["wearable"],
|
|
360
|
+
...(status !== undefined ? { status } : {}),
|
|
361
|
+
...(archivedAt !== undefined ? { archivedAt } : {}),
|
|
362
|
+
structuredAttributes: { wearableSource: "limitless" },
|
|
363
|
+
},
|
|
364
|
+
content,
|
|
365
|
+
});
|
|
366
|
+
const runSmartSync = async (
|
|
367
|
+
rows: ReturnType<typeof makeRow>[],
|
|
368
|
+
): Promise<Record<string, unknown>> => {
|
|
369
|
+
const storage = makeStorage(mkdtempSync(path.join(tmpdir(), "remnic-service-mem-")));
|
|
370
|
+
storage.memories.push(...rows);
|
|
371
|
+
const writes: Array<{ options: Record<string, unknown> }> = [];
|
|
372
|
+
storage.writeMemory = (async (
|
|
373
|
+
_category: string,
|
|
374
|
+
_content: string,
|
|
375
|
+
options: Record<string, unknown>,
|
|
376
|
+
) => {
|
|
377
|
+
writes.push({ options });
|
|
378
|
+
return `mem-${writes.length}`;
|
|
379
|
+
}) as WearableStorageIo["writeMemory"];
|
|
380
|
+
try {
|
|
381
|
+
registerWearableConnector({
|
|
382
|
+
id: "testsource",
|
|
383
|
+
displayName: "Test Source",
|
|
384
|
+
factory: () => ({
|
|
385
|
+
id: "testsource",
|
|
386
|
+
displayName: "Test Source",
|
|
387
|
+
verifyAuth: async () => ({ ok: true }),
|
|
388
|
+
fetchConversations: async () => ({
|
|
389
|
+
conversations: [
|
|
390
|
+
{
|
|
391
|
+
id: "c1",
|
|
392
|
+
source: "testsource",
|
|
393
|
+
startIso: "2026-06-10T15:00:00.000Z",
|
|
394
|
+
endIso: "2026-06-10T15:30:00.000Z",
|
|
395
|
+
segments: [
|
|
396
|
+
{ speakerKey: "user", isWearer: true, text: "We are moving the launch to September twelfth after that vendor call wrapped up." },
|
|
397
|
+
{ speakerKey: "guest", speakerName: "guest", text: "Confirmed, the vendor is aligned on the September date for the launch." },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
nextCursor: null,
|
|
402
|
+
}),
|
|
403
|
+
}),
|
|
404
|
+
});
|
|
405
|
+
const service = new WearablesService({
|
|
406
|
+
config: {
|
|
407
|
+
...defaultWearablesConfig(),
|
|
408
|
+
enabled: true,
|
|
409
|
+
digestEnabled: false,
|
|
410
|
+
sources: {
|
|
411
|
+
testsource: { ...defaultWearableSourceSettings(), enabled: true, memoryMode: "smart" },
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
getStorage: async () => storage,
|
|
415
|
+
// Borderline: 0.75 * 0.8 = 0.6 — active only with +0.10 support.
|
|
416
|
+
extract: async () => ({
|
|
417
|
+
facts: [{ category: "fact", content: borderlineFact, confidence: 0.75, tags: [] }],
|
|
418
|
+
profileUpdates: [],
|
|
419
|
+
entities: [],
|
|
420
|
+
questions: [],
|
|
421
|
+
}),
|
|
422
|
+
searchBackend: null,
|
|
423
|
+
});
|
|
424
|
+
await service.sync({ date: "2026-06-10" });
|
|
425
|
+
assert.equal(writes.length, 1);
|
|
426
|
+
return writes[0].options;
|
|
427
|
+
} finally {
|
|
428
|
+
clearWearableConnectors();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
// A pending_review row with matching content IS support evidence.
|
|
434
|
+
// (Similar wording, not identical — identical content would be
|
|
435
|
+
// consumed by the duplicate-existing dedup before scoring.)
|
|
436
|
+
const supported = await runSmartSync([
|
|
437
|
+
makeRow(
|
|
438
|
+
"pending-1",
|
|
439
|
+
"pending_review",
|
|
440
|
+
"The launch moved to September twelfth after the vendor call, noted earlier.",
|
|
441
|
+
),
|
|
442
|
+
]);
|
|
443
|
+
assert.equal(supported.status, "active");
|
|
444
|
+
assert.equal(
|
|
445
|
+
(supported.structuredAttributes as Record<string, string>).supportingMemoryId,
|
|
446
|
+
"pending-1",
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Terminal statuses with the same content are NOT support evidence.
|
|
450
|
+
const similar =
|
|
451
|
+
"The launch moved to September twelfth after the vendor call, noted earlier.";
|
|
452
|
+
const unsupported = await runSmartSync([
|
|
453
|
+
makeRow("rejected-1", "rejected", similar),
|
|
454
|
+
makeRow("quarantined-1", "quarantined", similar),
|
|
455
|
+
makeRow("superseded-1", "superseded", similar),
|
|
456
|
+
makeRow("archived-1", "archived", similar),
|
|
457
|
+
makeRow("forgotten-1", "forgotten", similar),
|
|
458
|
+
// Archived via archivedAt with NO explicit status — the
|
|
459
|
+
// canonical inferMemoryStatus must resolve this to archived.
|
|
460
|
+
makeRow("archived-implicit-1", undefined, similar, "2026-06-09T00:00:00.000Z"),
|
|
461
|
+
]);
|
|
462
|
+
assert.equal(unsupported.status, "pending_review");
|
|
463
|
+
assert.equal(
|
|
464
|
+
(unsupported.structuredAttributes as Record<string, string>).supportingMemoryId,
|
|
465
|
+
undefined,
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
// Content matching ONLY through the "[Attributes: ...]" enrichment
|
|
469
|
+
// suffix is not corroboration — the suffix is stripped before
|
|
470
|
+
// token matching, so attribute metadata never grants the boost.
|
|
471
|
+
const suffixOnly = await runSmartSync([
|
|
472
|
+
makeRow(
|
|
473
|
+
"pending-2",
|
|
474
|
+
"pending_review",
|
|
475
|
+
"Unrelated note about quarterly budget planning.\n[Attributes: context: launch moved to September twelfth after the vendor call]",
|
|
476
|
+
),
|
|
477
|
+
]);
|
|
478
|
+
assert.equal(suffixOnly.status, "pending_review");
|
|
479
|
+
assert.equal(
|
|
480
|
+
(suffixOnly.structuredAttributes as Record<string, string>).supportingMemoryId,
|
|
481
|
+
undefined,
|
|
482
|
+
);
|
|
483
|
+
} finally {
|
|
484
|
+
rmSync(dir, { recursive: true, force: true });
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
316
488
|
test("the wearable memory writer dedups non-fact categories by content scan", async () => {
|
|
317
489
|
const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
|
|
318
490
|
try {
|
package/src/wearables/service.ts
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
compileCorrectionRule,
|
|
13
13
|
} from "./corrections.js";
|
|
14
14
|
import { describeErrorForOperator, WearablesInputError } from "./errors.js";
|
|
15
|
+
import { inferMemoryStatus } from "../memory-lifecycle-ledger-utils.js";
|
|
15
16
|
import { isValidTranscriptDate, parseDayTranscript } from "./day-store.js";
|
|
17
|
+
import { stripAttributesSuffix } from "../storage.js";
|
|
18
|
+
import type { MemoryFrontmatter } from "../types.js";
|
|
16
19
|
import type { WearableMemoryGenDeps } from "./memory-gen.js";
|
|
17
20
|
import { WEARABLE_SOURCE_PREFIX, wearableSourceLabel } from "./memory-gen.js";
|
|
18
21
|
import {
|
|
@@ -65,6 +68,8 @@ export interface WearableStorageIo {
|
|
|
65
68
|
created: string;
|
|
66
69
|
tags: string[];
|
|
67
70
|
status?: string;
|
|
71
|
+
/** Archival timestamp — rows with this set are not support. */
|
|
72
|
+
archivedAt?: string;
|
|
68
73
|
structuredAttributes?: Record<string, string>;
|
|
69
74
|
};
|
|
70
75
|
content: string;
|
|
@@ -72,6 +77,18 @@ export interface WearableStorageIo {
|
|
|
72
77
|
>;
|
|
73
78
|
writeMemory: WearableMemoryGenDeps["writer"]["writeMemory"];
|
|
74
79
|
hasFactContentHash(content: string): Promise<boolean>;
|
|
80
|
+
findWearableMemoryByContent(
|
|
81
|
+
content: string,
|
|
82
|
+
): Promise<{ id: string; status: string | undefined } | null>;
|
|
83
|
+
promoteWearableMemory(
|
|
84
|
+
id: string,
|
|
85
|
+
attributeUpdates: Record<string, string>,
|
|
86
|
+
confidence?: number,
|
|
87
|
+
): Promise<boolean>;
|
|
88
|
+
demoteWearableMemory(
|
|
89
|
+
id: string,
|
|
90
|
+
attributeUpdates: Record<string, string>,
|
|
91
|
+
): Promise<boolean>;
|
|
75
92
|
}
|
|
76
93
|
|
|
77
94
|
export interface WearableSearchBackend {
|
|
@@ -87,6 +104,12 @@ export interface WearablesServiceDeps {
|
|
|
87
104
|
getStorage(): Promise<WearableStorageIo>;
|
|
88
105
|
/** Extraction hook; null when no engine is available. */
|
|
89
106
|
extract: WearableMemoryGenDeps["extract"] | null;
|
|
107
|
+
/**
|
|
108
|
+
* LLM-as-judge hook for smart memoryMode (the orchestrator wires the
|
|
109
|
+
* existing extraction judge here). Absent degrades smart mode to
|
|
110
|
+
* confidence x sourceTrust + corroboration scoring.
|
|
111
|
+
*/
|
|
112
|
+
judgeFacts?: WearableMemoryGenDeps["judgeFacts"];
|
|
90
113
|
/** Search backend (QMD); null disables indexed search. */
|
|
91
114
|
searchBackend: WearableSearchBackend | null;
|
|
92
115
|
/** Fired after transcript writes so the search index refreshes. */
|
|
@@ -136,15 +159,25 @@ export function createWearableMemoryWriter(
|
|
|
136
159
|
): WearableMemoryGenDeps["writer"] {
|
|
137
160
|
return {
|
|
138
161
|
writeMemory: storage.writeMemory.bind(storage),
|
|
162
|
+
findWearableMemoryByContent: async (content: string) =>
|
|
163
|
+
(await storage.findWearableMemoryByContent(content)) as
|
|
164
|
+
| { id: string; status: import("../types.js").MemoryStatus | undefined }
|
|
165
|
+
| null,
|
|
166
|
+
promoteWearableMemory: storage.promoteWearableMemory.bind(storage),
|
|
167
|
+
demoteWearableMemory: storage.demoteWearableMemory.bind(storage),
|
|
139
168
|
hasFactContentHash: async (content: string) => {
|
|
140
169
|
if (await storage.hasFactContentHash(content)) return true;
|
|
141
|
-
|
|
170
|
+
// Compare with the "[Attributes: ...]" enrichment suffix removed
|
|
171
|
+
// on BOTH sides — stored wearable bodies carry it, callers pass
|
|
172
|
+
// raw fact text. Without the strip, digest/candidate dedup never
|
|
173
|
+
// matched attribute-bearing memories.
|
|
174
|
+
const needle = stripAttributesSuffix(content);
|
|
142
175
|
const memories = await storage.readAllMemories();
|
|
143
176
|
return memories.some(
|
|
144
177
|
(memory) =>
|
|
145
178
|
typeof memory.frontmatter.source === "string" &&
|
|
146
179
|
memory.frontmatter.source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`) &&
|
|
147
|
-
memory.content
|
|
180
|
+
stripAttributesSuffix(memory.content) === needle,
|
|
148
181
|
);
|
|
149
182
|
},
|
|
150
183
|
};
|
|
@@ -263,6 +296,9 @@ export class WearablesService {
|
|
|
263
296
|
? {
|
|
264
297
|
extract: this.deps.extract,
|
|
265
298
|
writer: createWearableMemoryWriter(storage),
|
|
299
|
+
...(this.deps.judgeFacts !== undefined
|
|
300
|
+
? { judgeFacts: this.deps.judgeFacts }
|
|
301
|
+
: {}),
|
|
266
302
|
}
|
|
267
303
|
: null;
|
|
268
304
|
|
|
@@ -293,8 +329,53 @@ export class WearablesService {
|
|
|
293
329
|
},
|
|
294
330
|
writeDayTranscript: (source, date, serialized) =>
|
|
295
331
|
storage.writeWearableDayTranscript(source, date, serialized),
|
|
296
|
-
|
|
332
|
+
afterWrites: this.deps.reindexSearch,
|
|
297
333
|
memoryGen,
|
|
334
|
+
// Cross-device corroboration evidence (smart mode): other
|
|
335
|
+
// sources' stored transcripts for the same day...
|
|
336
|
+
readOtherSourceDayBodies: async (date, excludeSource) => {
|
|
337
|
+
const bodies = new Map<string, string>();
|
|
338
|
+
const days = await storage.listWearableTranscriptDays();
|
|
339
|
+
for (const entry of days) {
|
|
340
|
+
if (entry.date !== date || entry.source === excludeSource) continue;
|
|
341
|
+
if (bodies.size >= 4) break;
|
|
342
|
+
const raw = await storage.readWearableDayTranscript(entry.source, entry.date);
|
|
343
|
+
if (raw === null) continue;
|
|
344
|
+
bodies.set(entry.source, parseDayTranscript(raw)?.body ?? raw);
|
|
345
|
+
}
|
|
346
|
+
return bodies;
|
|
347
|
+
},
|
|
348
|
+
// ...and existing memories for the support boost. Status
|
|
349
|
+
// resolves through the canonical inferMemoryStatus so rows
|
|
350
|
+
// archived via `archivedAt` (or an archive/ path) without an
|
|
351
|
+
// explicit status never count. Explicit allow-list: active
|
|
352
|
+
// rows AND pending_review rows — a borderline fact observed
|
|
353
|
+
// again on a later day is repetition signal and the support
|
|
354
|
+
// boost is how it earns promotion. Rejected/quarantined/
|
|
355
|
+
// superseded/archived/forgotten rows never count (CLAUDE.md
|
|
356
|
+
// rule 53). Bodies feed token matching with the
|
|
357
|
+
// "[Attributes: ...]" enrichment suffix stripped — attribute
|
|
358
|
+
// metadata must never grant corroboration.
|
|
359
|
+
listSupportMemories: async () => {
|
|
360
|
+
const memories = await storage.readAllMemories();
|
|
361
|
+
const support: Array<{ id: string; content: string }> = [];
|
|
362
|
+
for (const memory of memories) {
|
|
363
|
+
// WearableStorageIo narrows MemoryFrontmatter for
|
|
364
|
+
// testability; production hands us the real thing.
|
|
365
|
+
const status = inferMemoryStatus(
|
|
366
|
+
memory.frontmatter as MemoryFrontmatter,
|
|
367
|
+
memory.path,
|
|
368
|
+
);
|
|
369
|
+
if (status !== "active" && status !== "pending_review") {
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
support.push({
|
|
373
|
+
id: memory.frontmatter.id,
|
|
374
|
+
content: stripAttributesSuffix(memory.content),
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return support;
|
|
378
|
+
},
|
|
298
379
|
},
|
|
299
380
|
);
|
|
300
381
|
summaries.push(summary);
|