@remnic/core 9.3.624 → 9.3.625
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 +18 -16
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +12 -5
- package/dist/access-http.js +10 -9
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +8 -8
- package/dist/access-schema.d.ts +5 -5
- package/dist/{access-service-CBNEKjzN.d.ts → access-service-C_sfOHsX.d.ts} +26 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +7 -7
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- 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-7TPH6UZL.js → chunk-2RHI3FGV.js} +540 -17
- package/dist/chunk-2RHI3FGV.js.map +1 -0
- package/dist/{chunk-GYTVOLNX.js → chunk-3MNBW7R7.js} +2 -2
- package/dist/{chunk-QFQQFX2H.js → chunk-3R2UZV3U.js} +2 -2
- package/dist/{chunk-O4UNM6OR.js → chunk-532VCWYW.js} +2 -2
- package/dist/{chunk-2UFQYU5F.js → chunk-57QXN2CS.js} +2 -2
- package/dist/chunk-7WV3F5DQ.js +22 -0
- package/dist/chunk-7WV3F5DQ.js.map +1 -0
- package/dist/{chunk-RKW6QR7W.js → chunk-AZ4RI3QD.js} +1461 -78
- package/dist/chunk-AZ4RI3QD.js.map +1 -0
- package/dist/{chunk-KQFQ3IS5.js → chunk-F3FY3D3S.js} +43 -7
- package/dist/chunk-F3FY3D3S.js.map +1 -0
- package/dist/{chunk-4R4KTDIE.js → chunk-FPNQF475.js} +1 -1
- package/dist/chunk-FPNQF475.js.map +1 -0
- package/dist/{chunk-UGEBPVNI.js → chunk-GE7Q7KXP.js} +2 -2
- package/dist/{chunk-GLWW3EJQ.js → chunk-KB4MFBF5.js} +3 -3
- package/dist/{chunk-5GOMXHLC.js → chunk-KKTXCFD7.js} +255 -1
- package/dist/chunk-KKTXCFD7.js.map +1 -0
- package/dist/{chunk-FH3PPO42.js → chunk-KVFYTRMV.js} +2 -2
- package/dist/{chunk-BNW5NJJH.js → chunk-LQYTQCXM.js} +2 -2
- package/dist/{chunk-AYHXQR53.js → chunk-MVQN73GT.js} +2 -2
- package/dist/{chunk-ZZPIJPPD.js → chunk-N5RGXWLQ.js} +2 -2
- package/dist/chunk-NDAH7BJ5.js +213 -0
- package/dist/chunk-NDAH7BJ5.js.map +1 -0
- package/dist/{chunk-R3OQGYOU.js → chunk-P2D2MM47.js} +2 -2
- package/dist/{chunk-PSUB67YB.js → chunk-PW6GURU3.js} +2 -2
- package/dist/{chunk-W3BKVM64.js → chunk-QDV6VAD4.js} +2 -2
- package/dist/{chunk-3QSU4NFF.js → chunk-QHXW3LZV.js} +3 -3
- package/dist/{chunk-I6UCUHLK.js → chunk-SHV5Y2WU.js} +182 -3
- package/dist/chunk-SHV5Y2WU.js.map +1 -0
- package/dist/{chunk-OZXVGYGZ.js → chunk-STDAAGH7.js} +2 -2
- package/dist/{chunk-FMGWXIES.js → chunk-TZDSNIRO.js} +5 -5
- package/dist/{chunk-2L54V4ZO.js → chunk-UELS6WWF.js} +2 -2
- package/dist/{chunk-PJGB7XRR.js → chunk-UGHUNQ74.js} +502 -134
- package/dist/chunk-UGHUNQ74.js.map +1 -0
- package/dist/{chunk-FG76RDVI.js → chunk-Y3TMFC6I.js} +136 -4
- package/dist/chunk-Y3TMFC6I.js.map +1 -0
- package/dist/{chunk-BPSGLMQ4.js → chunk-YQNADJCT.js} +2 -2
- package/dist/{cli-Cw729yLf.d.ts → cli-EZv6YE6_.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +23 -21
- 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 +2 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +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-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 +307 -9
- package/dist/index.js +155 -29
- 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 +1 -1
- 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 +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 +8 -7
- package/dist/{orchestrator-CqWOjfgl.d.ts → orchestrator-CEycaY3M.d.ts} +361 -4
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +13 -11
- 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-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 +3 -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 +10 -10
- 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-SLAa_prH.d.ts → semantic-DJR8_DMQ.d.ts} +1 -1
- package/dist/{semantic-consolidation-4HkHWgeI.d.ts → semantic-consolidation-FbhPeJjB.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 +5 -5
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +19 -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/types-D5VRAI04.d.ts +3134 -0
- package/dist/types.d.ts +3 -2862
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +3 -3
- package/package.json +1 -1
- package/src/access-http.ts +167 -0
- package/src/access-mcp.ts +198 -0
- package/src/access-service.ts +65 -0
- package/src/cli.ts +187 -0
- package/src/config.ts +7 -0
- package/src/index.ts +7 -0
- package/src/orchestrator.ts +42 -0
- package/src/storage.ts +106 -0
- package/src/types.ts +5 -0
- package/src/wearables/cleanup.test.ts +134 -0
- package/src/wearables/cleanup.ts +188 -0
- package/src/wearables/cli.test.ts +170 -0
- package/src/wearables/cli.ts +441 -0
- package/src/wearables/config.test.ts +143 -0
- package/src/wearables/config.ts +332 -0
- package/src/wearables/corrections.test.ts +118 -0
- package/src/wearables/corrections.ts +211 -0
- package/src/wearables/day-store.test.ts +143 -0
- package/src/wearables/day-store.ts +238 -0
- package/src/wearables/errors.test.ts +32 -0
- package/src/wearables/errors.ts +29 -0
- package/src/wearables/index.ts +114 -0
- package/src/wearables/memory-gen.test.ts +342 -0
- package/src/wearables/memory-gen.ts +413 -0
- package/src/wearables/pipeline.test.ts +608 -0
- package/src/wearables/pipeline.ts +519 -0
- package/src/wearables/redaction.test.ts +94 -0
- package/src/wearables/redaction.ts +156 -0
- package/src/wearables/registry.test.ts +62 -0
- package/src/wearables/registry.ts +133 -0
- package/src/wearables/service.test.ts +425 -0
- package/src/wearables/service.ts +691 -0
- package/src/wearables/speakers.test.ts +110 -0
- package/src/wearables/speakers.ts +174 -0
- package/src/wearables/storage-io.test.ts +105 -0
- package/src/wearables/sync-state.test.ts +134 -0
- package/src/wearables/sync-state.ts +186 -0
- package/src/wearables/types.ts +285 -0
- package/dist/chunk-4R4KTDIE.js.map +0 -1
- package/dist/chunk-5GOMXHLC.js.map +0 -1
- package/dist/chunk-7TPH6UZL.js.map +0 -1
- package/dist/chunk-FG76RDVI.js.map +0 -1
- package/dist/chunk-I6UCUHLK.js.map +0 -1
- package/dist/chunk-KQFQ3IS5.js.map +0 -1
- package/dist/chunk-PJGB7XRR.js.map +0 -1
- package/dist/chunk-RKW6QR7W.js.map +0 -1
- /package/dist/{chunk-GYTVOLNX.js.map → chunk-3MNBW7R7.js.map} +0 -0
- /package/dist/{chunk-QFQQFX2H.js.map → chunk-3R2UZV3U.js.map} +0 -0
- /package/dist/{chunk-O4UNM6OR.js.map → chunk-532VCWYW.js.map} +0 -0
- /package/dist/{chunk-2UFQYU5F.js.map → chunk-57QXN2CS.js.map} +0 -0
- /package/dist/{chunk-UGEBPVNI.js.map → chunk-GE7Q7KXP.js.map} +0 -0
- /package/dist/{chunk-GLWW3EJQ.js.map → chunk-KB4MFBF5.js.map} +0 -0
- /package/dist/{chunk-FH3PPO42.js.map → chunk-KVFYTRMV.js.map} +0 -0
- /package/dist/{chunk-BNW5NJJH.js.map → chunk-LQYTQCXM.js.map} +0 -0
- /package/dist/{chunk-AYHXQR53.js.map → chunk-MVQN73GT.js.map} +0 -0
- /package/dist/{chunk-ZZPIJPPD.js.map → chunk-N5RGXWLQ.js.map} +0 -0
- /package/dist/{chunk-R3OQGYOU.js.map → chunk-P2D2MM47.js.map} +0 -0
- /package/dist/{chunk-PSUB67YB.js.map → chunk-PW6GURU3.js.map} +0 -0
- /package/dist/{chunk-W3BKVM64.js.map → chunk-QDV6VAD4.js.map} +0 -0
- /package/dist/{chunk-3QSU4NFF.js.map → chunk-QHXW3LZV.js.map} +0 -0
- /package/dist/{chunk-OZXVGYGZ.js.map → chunk-STDAAGH7.js.map} +0 -0
- /package/dist/{chunk-FMGWXIES.js.map → chunk-TZDSNIRO.js.map} +0 -0
- /package/dist/{chunk-2L54V4ZO.js.map → chunk-UELS6WWF.js.map} +0 -0
- /package/dist/{chunk-BPSGLMQ4.js.map → chunk-YQNADJCT.js.map} +0 -0
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
CompoundingEngine,
|
|
24
24
|
defaultTierMigrationCycleBudget
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-UELS6WWF.js";
|
|
26
26
|
import {
|
|
27
27
|
SharedContextManager
|
|
28
28
|
} from "./chunk-DRD2Q7HQ.js";
|
|
@@ -168,7 +168,7 @@ import {
|
|
|
168
168
|
buildEntityRecallSection,
|
|
169
169
|
entityRecentTranscriptLookbackHours,
|
|
170
170
|
readRecentEntityTranscriptEntries
|
|
171
|
-
} from "./chunk-
|
|
171
|
+
} from "./chunk-LQYTQCXM.js";
|
|
172
172
|
import {
|
|
173
173
|
buildEventOrderRecallSection,
|
|
174
174
|
shouldRecallEventOrderEvidence
|
|
@@ -203,7 +203,7 @@ import {
|
|
|
203
203
|
materializeAfterSemanticConsolidation,
|
|
204
204
|
parseConsolidationResponse,
|
|
205
205
|
parseOperatorAwareConsolidationResponse
|
|
206
|
-
} from "./chunk-
|
|
206
|
+
} from "./chunk-3R2UZV3U.js";
|
|
207
207
|
import {
|
|
208
208
|
normalizeReplaySessionKey
|
|
209
209
|
} from "./chunk-2PRQG7PV.js";
|
|
@@ -212,13 +212,13 @@ import {
|
|
|
212
212
|
} from "./chunk-X6IRLNOO.js";
|
|
213
213
|
import {
|
|
214
214
|
searchVerifiedEpisodes
|
|
215
|
-
} from "./chunk-
|
|
215
|
+
} from "./chunk-P2D2MM47.js";
|
|
216
216
|
import {
|
|
217
217
|
ThreadingManager
|
|
218
218
|
} from "./chunk-W4RVMTHR.js";
|
|
219
219
|
import {
|
|
220
220
|
searchVerifiedSemanticRules
|
|
221
|
-
} from "./chunk-
|
|
221
|
+
} from "./chunk-57QXN2CS.js";
|
|
222
222
|
import {
|
|
223
223
|
searchWorkProductLedgerEntries
|
|
224
224
|
} from "./chunk-ZRWB5D4H.js";
|
|
@@ -237,7 +237,7 @@ import {
|
|
|
237
237
|
} from "./chunk-CYEPCZN5.js";
|
|
238
238
|
import {
|
|
239
239
|
NamespaceStorageRouter
|
|
240
|
-
} from "./chunk-
|
|
240
|
+
} from "./chunk-QDV6VAD4.js";
|
|
241
241
|
import {
|
|
242
242
|
namespaceIdentityFromToken
|
|
243
243
|
} from "./chunk-ZFXCQPNO.js";
|
|
@@ -285,6 +285,17 @@ import {
|
|
|
285
285
|
FallbackLlmClient,
|
|
286
286
|
fallbackLlmRuntimeContextFromConfig
|
|
287
287
|
} from "./chunk-KGIGRNR6.js";
|
|
288
|
+
import {
|
|
289
|
+
applyCorrections,
|
|
290
|
+
applyOffTheRecord,
|
|
291
|
+
compileCorrectionRule,
|
|
292
|
+
compileCorrectionRules,
|
|
293
|
+
compileRedactionPatterns,
|
|
294
|
+
correctionsFilePath,
|
|
295
|
+
loadCorrectionsFile,
|
|
296
|
+
redactText,
|
|
297
|
+
saveCorrectionsFile
|
|
298
|
+
} from "./chunk-NDAH7BJ5.js";
|
|
288
299
|
import {
|
|
289
300
|
buildRecallQueryPolicy
|
|
290
301
|
} from "./chunk-LBJBNWS2.js";
|
|
@@ -306,6 +317,10 @@ import {
|
|
|
306
317
|
isAbortError,
|
|
307
318
|
throwIfAborted
|
|
308
319
|
} from "./chunk-PVGDJXVK.js";
|
|
320
|
+
import {
|
|
321
|
+
WearablesInputError,
|
|
322
|
+
describeErrorForOperator
|
|
323
|
+
} from "./chunk-7WV3F5DQ.js";
|
|
309
324
|
import {
|
|
310
325
|
clusterByKey,
|
|
311
326
|
combineNamespaces,
|
|
@@ -317,7 +332,7 @@ import {
|
|
|
317
332
|
} from "./chunk-JGSKJHF7.js";
|
|
318
333
|
import {
|
|
319
334
|
buildXraySnapshot
|
|
320
|
-
} from "./chunk-
|
|
335
|
+
} from "./chunk-YQNADJCT.js";
|
|
321
336
|
import {
|
|
322
337
|
objectiveStateStoreOverrideForNamespace,
|
|
323
338
|
searchObjectiveStateSnapshots
|
|
@@ -338,11 +353,21 @@ import {
|
|
|
338
353
|
ContentHashIndex,
|
|
339
354
|
StorageManager,
|
|
340
355
|
compareEntityTimestamps,
|
|
356
|
+
composeDayTranscriptBody,
|
|
357
|
+
composeDayTranscriptMeta,
|
|
341
358
|
fingerprintEntityStructuredFacts,
|
|
359
|
+
hashTranscriptBody,
|
|
360
|
+
isValidTranscriptDate,
|
|
361
|
+
loadSpeakerRegistry,
|
|
342
362
|
normalizeAttributePairs,
|
|
343
363
|
normalizeEntityName,
|
|
344
|
-
|
|
345
|
-
|
|
364
|
+
parseDayTranscript,
|
|
365
|
+
parseEntityFile,
|
|
366
|
+
resolveSpeaker,
|
|
367
|
+
saveSpeakerRegistry,
|
|
368
|
+
serializeDayTranscript,
|
|
369
|
+
speakerRegistryKey
|
|
370
|
+
} from "./chunk-UGHUNQ74.js";
|
|
346
371
|
import {
|
|
347
372
|
attachCitation,
|
|
348
373
|
hasCitationForTemplate,
|
|
@@ -350,7 +375,7 @@ import {
|
|
|
350
375
|
} from "./chunk-J6A3CX5N.js";
|
|
351
376
|
import {
|
|
352
377
|
confidenceTier
|
|
353
|
-
} from "./chunk-
|
|
378
|
+
} from "./chunk-FPNQF475.js";
|
|
354
379
|
import {
|
|
355
380
|
isActiveMemoryStatus
|
|
356
381
|
} from "./chunk-RULE4VG5.js";
|
|
@@ -395,7 +420,7 @@ import {
|
|
|
395
420
|
} from "./chunk-AC5LO7IU.js";
|
|
396
421
|
|
|
397
422
|
// src/orchestrator.ts
|
|
398
|
-
import
|
|
423
|
+
import path3 from "path";
|
|
399
424
|
import os from "os";
|
|
400
425
|
import { createHash, randomBytes } from "crypto";
|
|
401
426
|
import { existsSync } from "fs";
|
|
@@ -937,6 +962,1300 @@ function getTaxonomyFilePath(memoryDir) {
|
|
|
937
962
|
return path.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
|
|
938
963
|
}
|
|
939
964
|
|
|
965
|
+
// src/wearables/memory-gen.ts
|
|
966
|
+
function memoryStatusForMode(mode) {
|
|
967
|
+
return mode === "auto" ? "active" : "pending_review";
|
|
968
|
+
}
|
|
969
|
+
var WEARABLE_SOURCE_PREFIX = "wearable";
|
|
970
|
+
function wearableSourceLabel(sourceId) {
|
|
971
|
+
return `${WEARABLE_SOURCE_PREFIX}:${sourceId}`;
|
|
972
|
+
}
|
|
973
|
+
function wearableDayTag(date) {
|
|
974
|
+
return `wearable-day:${date}`;
|
|
975
|
+
}
|
|
976
|
+
var IMPORTANCE_RANK = {
|
|
977
|
+
trivial: 0,
|
|
978
|
+
low: 1,
|
|
979
|
+
normal: 2,
|
|
980
|
+
high: 3,
|
|
981
|
+
critical: 4
|
|
982
|
+
};
|
|
983
|
+
var MAX_EXTRACTION_CHUNK_CHARS = 6e3;
|
|
984
|
+
var MIN_CONVERSATION_CHARS = 80;
|
|
985
|
+
function buildExtractionTurns(sourceId, date, conversation, registry) {
|
|
986
|
+
const headerParts = [
|
|
987
|
+
`Wearable transcript (${sourceId}) \u2014 ${date}`,
|
|
988
|
+
conversation.title ? `"${conversation.title}"` : void 0,
|
|
989
|
+
conversation.location ? `at ${conversation.location}` : void 0
|
|
990
|
+
].filter((part) => typeof part === "string");
|
|
991
|
+
const header = `[${headerParts.join(" \u2014 ")}]`;
|
|
992
|
+
const lines = [];
|
|
993
|
+
for (const segment of conversation.segments) {
|
|
994
|
+
const { label } = resolveSpeaker(sourceId, segment, registry);
|
|
995
|
+
lines.push(`${label}: ${segment.text}`);
|
|
996
|
+
}
|
|
997
|
+
const transcript = lines.join("\n");
|
|
998
|
+
if (transcript.trim().length < MIN_CONVERSATION_CHARS) return [];
|
|
999
|
+
const sessionKey = `wearables:${sourceId}:${date}:${conversation.id}`;
|
|
1000
|
+
const timestamp = conversation.startIso;
|
|
1001
|
+
const turns = [];
|
|
1002
|
+
let chunkLines = [];
|
|
1003
|
+
let chunkChars = 0;
|
|
1004
|
+
const flush = () => {
|
|
1005
|
+
if (chunkLines.length === 0) return;
|
|
1006
|
+
turns.push({
|
|
1007
|
+
role: "user",
|
|
1008
|
+
content: `${header}
|
|
1009
|
+
${chunkLines.join("\n")}`,
|
|
1010
|
+
timestamp,
|
|
1011
|
+
sourceValidAt: timestamp,
|
|
1012
|
+
sessionKey
|
|
1013
|
+
});
|
|
1014
|
+
chunkLines = [];
|
|
1015
|
+
chunkChars = 0;
|
|
1016
|
+
};
|
|
1017
|
+
for (const line of transcript.split("\n")) {
|
|
1018
|
+
if (chunkChars + line.length + 1 > MAX_EXTRACTION_CHUNK_CHARS) flush();
|
|
1019
|
+
chunkLines.push(line);
|
|
1020
|
+
chunkChars += line.length + 1;
|
|
1021
|
+
}
|
|
1022
|
+
flush();
|
|
1023
|
+
return turns;
|
|
1024
|
+
}
|
|
1025
|
+
async function generateWearableMemories(sourceId, date, conversations, settings, registry, deps) {
|
|
1026
|
+
const result = {
|
|
1027
|
+
created: 0,
|
|
1028
|
+
skipped: 0,
|
|
1029
|
+
skippedByReason: {},
|
|
1030
|
+
warnings: []
|
|
1031
|
+
};
|
|
1032
|
+
if (settings.memoryMode === "off") return result;
|
|
1033
|
+
const skip = (reason, count = 1) => {
|
|
1034
|
+
result.skipped += count;
|
|
1035
|
+
result.skippedByReason[reason] = (result.skippedByReason[reason] ?? 0) + count;
|
|
1036
|
+
};
|
|
1037
|
+
const candidates = [];
|
|
1038
|
+
const seenContent = /* @__PURE__ */ new Set();
|
|
1039
|
+
for (const conversation of conversations) {
|
|
1040
|
+
const turns = buildExtractionTurns(sourceId, date, conversation, registry);
|
|
1041
|
+
if (turns.length === 0) continue;
|
|
1042
|
+
let extraction;
|
|
1043
|
+
try {
|
|
1044
|
+
extraction = await deps.extract(turns);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
result.warnings.push(
|
|
1047
|
+
`extraction failed for ${sourceId}/${date} (conversation ${conversation.id}): ${describeErrorForOperator(err)} \u2014 the memory pass for this day retries on the next sync`
|
|
1048
|
+
);
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
for (const fact of extraction.facts) {
|
|
1052
|
+
const content = fact.content?.trim();
|
|
1053
|
+
if (!content) {
|
|
1054
|
+
skip("empty");
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (fact.category === "procedure" || fact.category === "reasoning_trace") {
|
|
1058
|
+
skip("unsupported-category");
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (typeof fact.confidence === "number" && fact.confidence < settings.minConfidence) {
|
|
1062
|
+
skip("below-confidence");
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
const importance = scoreImportance(content, fact.category, fact.tags ?? []);
|
|
1066
|
+
if (IMPORTANCE_RANK[importance.level] < IMPORTANCE_RANK[settings.minImportance]) {
|
|
1067
|
+
skip("below-importance");
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const dedupKey = content.toLowerCase();
|
|
1071
|
+
if (seenContent.has(dedupKey)) {
|
|
1072
|
+
skip("duplicate-in-run");
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
seenContent.add(dedupKey);
|
|
1076
|
+
candidates.push({ fact: { ...fact, content }, importance, conversation });
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const novel = [];
|
|
1080
|
+
for (const candidate of candidates) {
|
|
1081
|
+
if (await deps.writer.hasFactContentHash(candidate.fact.content)) {
|
|
1082
|
+
skip("duplicate-existing");
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
novel.push(candidate);
|
|
1086
|
+
}
|
|
1087
|
+
novel.sort((a, b) => {
|
|
1088
|
+
if (a.importance.score > b.importance.score) return -1;
|
|
1089
|
+
if (a.importance.score < b.importance.score) return 1;
|
|
1090
|
+
if (a.fact.content < b.fact.content) return -1;
|
|
1091
|
+
if (a.fact.content > b.fact.content) return 1;
|
|
1092
|
+
return 0;
|
|
1093
|
+
});
|
|
1094
|
+
const cap = settings.maxMemoriesPerDay;
|
|
1095
|
+
const kept = cap > 0 ? novel.slice(0, cap) : novel;
|
|
1096
|
+
if (novel.length > kept.length) {
|
|
1097
|
+
skip("over-day-cap", novel.length - kept.length);
|
|
1098
|
+
}
|
|
1099
|
+
const status = memoryStatusForMode(settings.memoryMode);
|
|
1100
|
+
for (const candidate of kept) {
|
|
1101
|
+
const tags = [
|
|
1102
|
+
.../* @__PURE__ */ new Set([
|
|
1103
|
+
...candidate.fact.tags ?? [],
|
|
1104
|
+
WEARABLE_SOURCE_PREFIX,
|
|
1105
|
+
wearableSourceLabel(sourceId),
|
|
1106
|
+
wearableDayTag(date)
|
|
1107
|
+
])
|
|
1108
|
+
];
|
|
1109
|
+
await deps.writer.writeMemory(candidate.fact.category, candidate.fact.content, {
|
|
1110
|
+
confidence: candidate.fact.confidence,
|
|
1111
|
+
tags,
|
|
1112
|
+
source: wearableSourceLabel(sourceId),
|
|
1113
|
+
importance: candidate.importance,
|
|
1114
|
+
validAt: candidate.conversation.startIso,
|
|
1115
|
+
structuredAttributes: {
|
|
1116
|
+
...candidate.fact.structuredAttributes ?? {},
|
|
1117
|
+
wearableSource: sourceId,
|
|
1118
|
+
wearableDate: date,
|
|
1119
|
+
wearableConversationId: candidate.conversation.id
|
|
1120
|
+
},
|
|
1121
|
+
contentHashSource: candidate.fact.content,
|
|
1122
|
+
status
|
|
1123
|
+
});
|
|
1124
|
+
result.created += 1;
|
|
1125
|
+
}
|
|
1126
|
+
return result;
|
|
1127
|
+
}
|
|
1128
|
+
async function writeDailyDigestMemory(sourceId, date, conversations, settings, registry, writer) {
|
|
1129
|
+
if (settings.memoryMode === "off") return false;
|
|
1130
|
+
if (conversations.length === 0) return false;
|
|
1131
|
+
const lines = conversations.map((conversation) => {
|
|
1132
|
+
const title = conversation.title?.trim() || "Untitled conversation";
|
|
1133
|
+
const speakers = new Set(
|
|
1134
|
+
conversation.segments.map(
|
|
1135
|
+
(segment) => resolveSpeaker(sourceId, segment, registry).label
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1138
|
+
return `- ${title} (${speakers.size} speaker${speakers.size === 1 ? "" : "s"})`;
|
|
1139
|
+
});
|
|
1140
|
+
const content = `Wearable day digest \u2014 ${sourceId}, ${date}: ${conversations.length} recorded conversation${conversations.length === 1 ? "" : "s"}.
|
|
1141
|
+
` + lines.join("\n");
|
|
1142
|
+
if (await writer.hasFactContentHash(content)) return false;
|
|
1143
|
+
await writer.writeMemory("moment", content, {
|
|
1144
|
+
confidence: 0.9,
|
|
1145
|
+
tags: [
|
|
1146
|
+
WEARABLE_SOURCE_PREFIX,
|
|
1147
|
+
wearableSourceLabel(sourceId),
|
|
1148
|
+
wearableDayTag(date),
|
|
1149
|
+
"daily-digest"
|
|
1150
|
+
],
|
|
1151
|
+
source: wearableSourceLabel(sourceId),
|
|
1152
|
+
importance: scoreImportance(content, "moment", ["daily-digest"]),
|
|
1153
|
+
validAt: `${date}T00:00:00.000Z`,
|
|
1154
|
+
structuredAttributes: {
|
|
1155
|
+
wearableSource: sourceId,
|
|
1156
|
+
wearableDate: date
|
|
1157
|
+
},
|
|
1158
|
+
contentHashSource: content,
|
|
1159
|
+
status: memoryStatusForMode(settings.memoryMode),
|
|
1160
|
+
memoryKind: "episode"
|
|
1161
|
+
});
|
|
1162
|
+
return true;
|
|
1163
|
+
}
|
|
1164
|
+
async function importNativeMemories(sourceId, memories, alreadyImportedIds, writer) {
|
|
1165
|
+
let imported = 0;
|
|
1166
|
+
const importedIds = [];
|
|
1167
|
+
const seenContent = /* @__PURE__ */ new Set();
|
|
1168
|
+
for (const memory of memories) {
|
|
1169
|
+
const content = memory.content?.trim();
|
|
1170
|
+
if (!content) continue;
|
|
1171
|
+
if (alreadyImportedIds.has(memory.id)) continue;
|
|
1172
|
+
if (seenContent.has(content) || await writer.hasFactContentHash(content)) {
|
|
1173
|
+
importedIds.push(memory.id);
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
seenContent.add(content);
|
|
1177
|
+
await writer.writeMemory("fact", content, {
|
|
1178
|
+
confidence: 0.6,
|
|
1179
|
+
tags: [
|
|
1180
|
+
.../* @__PURE__ */ new Set([
|
|
1181
|
+
...memory.tags ?? [],
|
|
1182
|
+
WEARABLE_SOURCE_PREFIX,
|
|
1183
|
+
wearableSourceLabel(sourceId),
|
|
1184
|
+
"native-import"
|
|
1185
|
+
])
|
|
1186
|
+
],
|
|
1187
|
+
source: `${wearableSourceLabel(sourceId)}:native`,
|
|
1188
|
+
importance: scoreImportance(content, "fact", memory.tags ?? []),
|
|
1189
|
+
validAt: memory.createdIso,
|
|
1190
|
+
structuredAttributes: {
|
|
1191
|
+
wearableSource: sourceId,
|
|
1192
|
+
wearableNativeId: memory.id
|
|
1193
|
+
},
|
|
1194
|
+
contentHashSource: content,
|
|
1195
|
+
status: "pending_review"
|
|
1196
|
+
});
|
|
1197
|
+
imported += 1;
|
|
1198
|
+
importedIds.push(memory.id);
|
|
1199
|
+
}
|
|
1200
|
+
return { imported, importedIds };
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/wearables/cleanup.ts
|
|
1204
|
+
var MERGE_GAP_MS = 3e4;
|
|
1205
|
+
var FILLER_TOKENS = ["um", "uh", "uhm", "umm", "uhh", "erm", "hmm", "mhm"];
|
|
1206
|
+
var FILLER_PATTERN = new RegExp(
|
|
1207
|
+
// Leading/trailing punctuation around the filler collapses with it so
|
|
1208
|
+
// "Um, so we should" -> "so we should" rather than ", so we should".
|
|
1209
|
+
`(?:^|\\s)(?:${FILLER_TOKENS.join("|")})[,.]?(?=\\s|$)`,
|
|
1210
|
+
"gi"
|
|
1211
|
+
);
|
|
1212
|
+
function cleanConversation(conversation, settings) {
|
|
1213
|
+
let segments = conversation.segments.map((segment) => ({ ...segment }));
|
|
1214
|
+
let droppedSegments = 0;
|
|
1215
|
+
let mergedSegments = 0;
|
|
1216
|
+
if (settings.stripFillers) {
|
|
1217
|
+
for (const segment of segments) {
|
|
1218
|
+
segment.text = stripFillerTokens(segment.text);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
if (settings.collapseRepeats) {
|
|
1222
|
+
for (const segment of segments) {
|
|
1223
|
+
segment.text = collapseImmediateRepeats(segment.text);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
for (const segment of segments) {
|
|
1227
|
+
segment.text = normalizeWhitespace(segment.text);
|
|
1228
|
+
}
|
|
1229
|
+
if (settings.dropLowQuality) {
|
|
1230
|
+
const kept = [];
|
|
1231
|
+
for (const segment of segments) {
|
|
1232
|
+
if (isLowQualitySegment(segment.text)) {
|
|
1233
|
+
droppedSegments += 1;
|
|
1234
|
+
} else {
|
|
1235
|
+
kept.push(segment);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
segments = kept;
|
|
1239
|
+
} else {
|
|
1240
|
+
const kept = segments.filter((segment) => segment.text.length > 0);
|
|
1241
|
+
droppedSegments += segments.length - kept.length;
|
|
1242
|
+
segments = kept;
|
|
1243
|
+
}
|
|
1244
|
+
if (settings.mergeSameSpeaker) {
|
|
1245
|
+
const merged = [];
|
|
1246
|
+
for (const segment of segments) {
|
|
1247
|
+
const previous = merged[merged.length - 1];
|
|
1248
|
+
if (previous && canMerge(previous, segment)) {
|
|
1249
|
+
previous.text = `${previous.text} ${segment.text}`.trim();
|
|
1250
|
+
if (segment.endIso) previous.endIso = segment.endIso;
|
|
1251
|
+
mergedSegments += 1;
|
|
1252
|
+
} else {
|
|
1253
|
+
merged.push(segment);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
segments = merged;
|
|
1257
|
+
}
|
|
1258
|
+
return {
|
|
1259
|
+
conversation: { ...conversation, segments },
|
|
1260
|
+
droppedSegments,
|
|
1261
|
+
mergedSegments
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function canMerge(previous, next) {
|
|
1265
|
+
if (previous.speakerKey !== next.speakerKey) return false;
|
|
1266
|
+
const previousEnd = previous.endIso ? Date.parse(previous.endIso) : NaN;
|
|
1267
|
+
const nextStart = next.startIso ? Date.parse(next.startIso) : NaN;
|
|
1268
|
+
if (Number.isNaN(previousEnd) || Number.isNaN(nextStart)) return true;
|
|
1269
|
+
return nextStart - previousEnd <= MERGE_GAP_MS;
|
|
1270
|
+
}
|
|
1271
|
+
function stripFillerTokens(text) {
|
|
1272
|
+
return normalizeWhitespace(text.replace(FILLER_PATTERN, " "));
|
|
1273
|
+
}
|
|
1274
|
+
function collapseImmediateRepeats(text) {
|
|
1275
|
+
const words = text.split(/\s+/).filter((word) => word.length > 0);
|
|
1276
|
+
if (words.length < 2) return text.trim();
|
|
1277
|
+
const out = [];
|
|
1278
|
+
let index = 0;
|
|
1279
|
+
while (index < words.length) {
|
|
1280
|
+
out.push(words[index]);
|
|
1281
|
+
index += 1;
|
|
1282
|
+
let matched = true;
|
|
1283
|
+
while (matched) {
|
|
1284
|
+
matched = false;
|
|
1285
|
+
for (let size = 4; size >= 1; size--) {
|
|
1286
|
+
if (out.length < size || index + size > words.length) continue;
|
|
1287
|
+
const tail = out.slice(-size).join(" ").toLowerCase();
|
|
1288
|
+
if (!/\p{L}/u.test(tail)) continue;
|
|
1289
|
+
const ahead = words.slice(index, index + size).join(" ").toLowerCase();
|
|
1290
|
+
if (tail === ahead) {
|
|
1291
|
+
index += size;
|
|
1292
|
+
matched = true;
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
return out.join(" ");
|
|
1299
|
+
}
|
|
1300
|
+
function isLowQualitySegment(text) {
|
|
1301
|
+
const trimmed = text.trim();
|
|
1302
|
+
if (trimmed.length === 0) return true;
|
|
1303
|
+
if (/^(.)\1{4,}$/.test(trimmed)) return true;
|
|
1304
|
+
const letters = trimmed.replace(/[^\p{L}\p{N}]/gu, "");
|
|
1305
|
+
if (letters.length === 0) return true;
|
|
1306
|
+
if (trimmed.length >= 12 && letters.length / trimmed.length < 0.3) {
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
const words = trimmed.toLowerCase().split(/\s+/);
|
|
1310
|
+
if (words.length >= 5) {
|
|
1311
|
+
const unique = new Set(words);
|
|
1312
|
+
if (unique.size === 1) return true;
|
|
1313
|
+
}
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
function normalizeWhitespace(text) {
|
|
1317
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// src/wearables/sync-state.ts
|
|
1321
|
+
import { promises as fsPromises } from "fs";
|
|
1322
|
+
import * as path2 from "path";
|
|
1323
|
+
var MAX_TRACKED_NATIVE_IDS = 5e3;
|
|
1324
|
+
var MAX_TRACKED_DAY_HASHES = 800;
|
|
1325
|
+
function syncStateFilePath(memoryDir) {
|
|
1326
|
+
return path2.join(memoryDir, "state", "wearables", "sync.json");
|
|
1327
|
+
}
|
|
1328
|
+
function emptySyncState() {
|
|
1329
|
+
return { version: 1, sources: {} };
|
|
1330
|
+
}
|
|
1331
|
+
async function loadSyncState(memoryDir) {
|
|
1332
|
+
const filePath = syncStateFilePath(memoryDir);
|
|
1333
|
+
let raw;
|
|
1334
|
+
try {
|
|
1335
|
+
raw = await fsPromises.readFile(filePath, "utf-8");
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
if (err.code === "ENOENT") {
|
|
1338
|
+
return emptySyncState();
|
|
1339
|
+
}
|
|
1340
|
+
throw err;
|
|
1341
|
+
}
|
|
1342
|
+
let parsed;
|
|
1343
|
+
try {
|
|
1344
|
+
parsed = JSON.parse(raw);
|
|
1345
|
+
} catch {
|
|
1346
|
+
return emptySyncState();
|
|
1347
|
+
}
|
|
1348
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed) || typeof parsed.sources !== "object" || parsed.sources === null) {
|
|
1349
|
+
return emptySyncState();
|
|
1350
|
+
}
|
|
1351
|
+
return { version: 1, sources: parsed.sources };
|
|
1352
|
+
}
|
|
1353
|
+
async function saveSyncState(memoryDir, state) {
|
|
1354
|
+
const filePath = syncStateFilePath(memoryDir);
|
|
1355
|
+
await fsPromises.mkdir(path2.dirname(filePath), { recursive: true });
|
|
1356
|
+
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
1357
|
+
await fsPromises.writeFile(
|
|
1358
|
+
tmpPath,
|
|
1359
|
+
`${JSON.stringify(state, null, 2)}
|
|
1360
|
+
`,
|
|
1361
|
+
"utf-8"
|
|
1362
|
+
);
|
|
1363
|
+
try {
|
|
1364
|
+
await fsPromises.rename(tmpPath, filePath);
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
await fsPromises.unlink(tmpPath).catch(() => void 0);
|
|
1367
|
+
throw err;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
function updateSourceSyncState(state, sourceId, update) {
|
|
1371
|
+
const previous = state.sources[sourceId];
|
|
1372
|
+
const mergedHashes = {
|
|
1373
|
+
...previous?.dayHashes ?? {},
|
|
1374
|
+
...update.dayHashes
|
|
1375
|
+
};
|
|
1376
|
+
const hashKeys = Object.keys(mergedHashes).sort();
|
|
1377
|
+
while (hashKeys.length > MAX_TRACKED_DAY_HASHES) {
|
|
1378
|
+
const oldest = hashKeys.shift();
|
|
1379
|
+
if (oldest === void 0) break;
|
|
1380
|
+
delete mergedHashes[oldest];
|
|
1381
|
+
}
|
|
1382
|
+
const mergedMemoryHashes = {
|
|
1383
|
+
...previous?.memoryDayHashes ?? {},
|
|
1384
|
+
...update.memoryDayHashes ?? {}
|
|
1385
|
+
};
|
|
1386
|
+
for (const day of update.clearMemoryDays ?? []) {
|
|
1387
|
+
if (!(day in (update.memoryDayHashes ?? {}))) {
|
|
1388
|
+
delete mergedMemoryHashes[day];
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
const memoryHashKeys = Object.keys(mergedMemoryHashes).sort();
|
|
1392
|
+
while (memoryHashKeys.length > MAX_TRACKED_DAY_HASHES) {
|
|
1393
|
+
const oldest = memoryHashKeys.shift();
|
|
1394
|
+
if (oldest === void 0) break;
|
|
1395
|
+
delete mergedMemoryHashes[oldest];
|
|
1396
|
+
}
|
|
1397
|
+
const mergedNativeIds = [
|
|
1398
|
+
...previous?.importedNativeMemoryIds ?? [],
|
|
1399
|
+
...update.importedNativeMemoryIds ?? []
|
|
1400
|
+
];
|
|
1401
|
+
const dedupedNativeIds = [...new Set(mergedNativeIds)];
|
|
1402
|
+
const boundedNativeIds = dedupedNativeIds.length > MAX_TRACKED_NATIVE_IDS ? dedupedNativeIds.slice(dedupedNativeIds.length - MAX_TRACKED_NATIVE_IDS) : dedupedNativeIds;
|
|
1403
|
+
const sortedDays = [...update.days].sort();
|
|
1404
|
+
const latestDay = sortedDays[sortedDays.length - 1];
|
|
1405
|
+
const lastDateSynced = latestDay !== void 0 && (!previous || previous.lastDateSynced < latestDay) ? latestDay : previous?.lastDateSynced ?? latestDay ?? "";
|
|
1406
|
+
return {
|
|
1407
|
+
version: 1,
|
|
1408
|
+
sources: {
|
|
1409
|
+
...state.sources,
|
|
1410
|
+
[sourceId]: {
|
|
1411
|
+
lastSyncAt: update.syncedAt,
|
|
1412
|
+
lastDateSynced,
|
|
1413
|
+
dayHashes: mergedHashes,
|
|
1414
|
+
memoryDayHashes: mergedMemoryHashes,
|
|
1415
|
+
importedNativeMemoryIds: boundedNativeIds
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/wearables/pipeline.ts
|
|
1422
|
+
var MAX_PAGES_PER_DAY = 50;
|
|
1423
|
+
var MAX_NATIVE_PAGES = 20;
|
|
1424
|
+
var DEFAULT_SYNC_DAYS = 2;
|
|
1425
|
+
var MAX_SYNC_DAYS = 90;
|
|
1426
|
+
function dateInTimezone(date, timezone) {
|
|
1427
|
+
try {
|
|
1428
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
1429
|
+
timeZone: timezone,
|
|
1430
|
+
year: "numeric",
|
|
1431
|
+
month: "2-digit",
|
|
1432
|
+
day: "2-digit"
|
|
1433
|
+
}).formatToParts(date);
|
|
1434
|
+
const get = (type) => parts.find((part) => part.type === type)?.value ?? "";
|
|
1435
|
+
return `${get("year")}-${get("month")}-${get("day")}`;
|
|
1436
|
+
} catch {
|
|
1437
|
+
return date.toISOString().slice(0, 10);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
function resolveSyncDates(options, timezone, now) {
|
|
1441
|
+
if (options.date !== void 0) {
|
|
1442
|
+
if (!isValidTranscriptDate(options.date)) {
|
|
1443
|
+
throw new WearablesInputError(
|
|
1444
|
+
`wearables sync: invalid date '${options.date}' \u2014 expected YYYY-MM-DD`
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
return [options.date];
|
|
1448
|
+
}
|
|
1449
|
+
let days = DEFAULT_SYNC_DAYS;
|
|
1450
|
+
if (options.days !== void 0) {
|
|
1451
|
+
if (!Number.isFinite(options.days) || !Number.isInteger(options.days) || options.days < 1 || options.days > MAX_SYNC_DAYS) {
|
|
1452
|
+
throw new WearablesInputError(
|
|
1453
|
+
`wearables sync: invalid days '${options.days}' \u2014 expected an integer between 1 and ${MAX_SYNC_DAYS}`
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
days = options.days;
|
|
1457
|
+
}
|
|
1458
|
+
const dates = [];
|
|
1459
|
+
let cursor = dateInTimezone(now, timezone);
|
|
1460
|
+
for (let count = 0; count < days; count++) {
|
|
1461
|
+
dates.unshift(cursor);
|
|
1462
|
+
cursor = previousIsoDate(cursor);
|
|
1463
|
+
}
|
|
1464
|
+
return dates;
|
|
1465
|
+
}
|
|
1466
|
+
function previousIsoDate(date) {
|
|
1467
|
+
const parsed = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
1468
|
+
parsed.setUTCDate(parsed.getUTCDate() - 1);
|
|
1469
|
+
return parsed.toISOString().slice(0, 10);
|
|
1470
|
+
}
|
|
1471
|
+
async function fetchAllConversationsForDate(connector, date, timezone, signal, warnings) {
|
|
1472
|
+
const conversations = [];
|
|
1473
|
+
let cursor = void 0;
|
|
1474
|
+
for (let page = 0; page < MAX_PAGES_PER_DAY; page++) {
|
|
1475
|
+
const result = await connector.fetchConversations({
|
|
1476
|
+
date,
|
|
1477
|
+
timezone,
|
|
1478
|
+
cursor,
|
|
1479
|
+
signal
|
|
1480
|
+
});
|
|
1481
|
+
conversations.push(...result.conversations);
|
|
1482
|
+
if (!result.nextCursor) return { conversations, partial: false };
|
|
1483
|
+
cursor = result.nextCursor;
|
|
1484
|
+
}
|
|
1485
|
+
warnings.push(
|
|
1486
|
+
`${connector.id}: stopped paginating ${date} after ${MAX_PAGES_PER_DAY} pages \u2014 day may be partially synced (every sync refetches and re-warns until the provider day fits the cap)`
|
|
1487
|
+
);
|
|
1488
|
+
return { conversations, partial: true };
|
|
1489
|
+
}
|
|
1490
|
+
var PARTIAL_DAY_MARKER = "\n*Note: pagination safety cap reached during sync \u2014 this day may be incomplete.*\n";
|
|
1491
|
+
function emptyDayBody(sourceId, date) {
|
|
1492
|
+
return `# ${sourceId} transcript \u2014 ${date}
|
|
1493
|
+
|
|
1494
|
+
_No storable conversation content for this day (all segments were elided or dropped)._
|
|
1495
|
+
`;
|
|
1496
|
+
}
|
|
1497
|
+
function cleanDay(raw, sourceId, settings, config, userRedaction, correctionRules) {
|
|
1498
|
+
const out = {
|
|
1499
|
+
conversations: [],
|
|
1500
|
+
segmentsKept: 0,
|
|
1501
|
+
segmentsDropped: 0,
|
|
1502
|
+
redactions: 0,
|
|
1503
|
+
correctionsApplied: 0
|
|
1504
|
+
};
|
|
1505
|
+
for (const conversation of raw) {
|
|
1506
|
+
let current = conversation;
|
|
1507
|
+
if (config.offTheRecordEnabled) {
|
|
1508
|
+
const otr = applyOffTheRecord(current);
|
|
1509
|
+
current = otr.conversation;
|
|
1510
|
+
out.segmentsDropped += otr.droppedSegments;
|
|
1511
|
+
}
|
|
1512
|
+
const cleaned = cleanConversation(current, settings.cleanup);
|
|
1513
|
+
current = cleaned.conversation;
|
|
1514
|
+
out.segmentsDropped += cleaned.droppedSegments;
|
|
1515
|
+
const segments = current.segments.map((segment) => {
|
|
1516
|
+
let text = segment.text;
|
|
1517
|
+
if (config.redactionEnabled) {
|
|
1518
|
+
const redacted = redactText(text, userRedaction);
|
|
1519
|
+
text = redacted.text;
|
|
1520
|
+
out.redactions += redacted.redactions;
|
|
1521
|
+
}
|
|
1522
|
+
const corrected = applyCorrections(text, correctionRules, sourceId);
|
|
1523
|
+
out.correctionsApplied += corrected.applied;
|
|
1524
|
+
return { ...segment, text: corrected.text };
|
|
1525
|
+
});
|
|
1526
|
+
current = { ...current, segments };
|
|
1527
|
+
if (current.segments.length > 0) {
|
|
1528
|
+
out.conversations.push(current);
|
|
1529
|
+
out.segmentsKept += current.segments.length;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return out;
|
|
1533
|
+
}
|
|
1534
|
+
async function syncWearableSource(connector, settings, config, options, deps) {
|
|
1535
|
+
const now = deps.now ? deps.now() : /* @__PURE__ */ new Date();
|
|
1536
|
+
const timezone = config.timezone ?? defaultTimezone();
|
|
1537
|
+
const dates = resolveSyncDates(options, timezone, now);
|
|
1538
|
+
const summary = {
|
|
1539
|
+
source: connector.id,
|
|
1540
|
+
days: dates,
|
|
1541
|
+
conversations: 0,
|
|
1542
|
+
segmentsKept: 0,
|
|
1543
|
+
segmentsDropped: 0,
|
|
1544
|
+
redactions: 0,
|
|
1545
|
+
correctionsApplied: 0,
|
|
1546
|
+
transcriptsWritten: [],
|
|
1547
|
+
memoriesCreated: 0,
|
|
1548
|
+
memoriesSkipped: 0,
|
|
1549
|
+
nativeMemoriesImported: 0,
|
|
1550
|
+
warnings: []
|
|
1551
|
+
};
|
|
1552
|
+
const registry = await loadSpeakerRegistry(deps.memoryDir);
|
|
1553
|
+
const stateRules = await loadCorrectionsFile(deps.memoryDir);
|
|
1554
|
+
const correctionRules = [
|
|
1555
|
+
...compileCorrectionRules(config.corrections, "wearables.corrections"),
|
|
1556
|
+
...compileCorrectionRules(stateRules, "state corrections")
|
|
1557
|
+
];
|
|
1558
|
+
const userRedaction = compileRedactionPatterns(config.redactionPatterns);
|
|
1559
|
+
let syncState = await loadSyncState(deps.memoryDir);
|
|
1560
|
+
const previousState = syncState.sources[connector.id];
|
|
1561
|
+
const dayHashes = {};
|
|
1562
|
+
const memoryDayHashes = {};
|
|
1563
|
+
const failedMemoryDays = [];
|
|
1564
|
+
const importedNativeIds = [];
|
|
1565
|
+
for (const date of dates) {
|
|
1566
|
+
const fetched = await fetchAllConversationsForDate(
|
|
1567
|
+
connector,
|
|
1568
|
+
date,
|
|
1569
|
+
timezone,
|
|
1570
|
+
options.signal,
|
|
1571
|
+
summary.warnings
|
|
1572
|
+
);
|
|
1573
|
+
const cleaned = cleanDay(
|
|
1574
|
+
fetched.conversations,
|
|
1575
|
+
connector.id,
|
|
1576
|
+
settings,
|
|
1577
|
+
config,
|
|
1578
|
+
userRedaction,
|
|
1579
|
+
correctionRules
|
|
1580
|
+
);
|
|
1581
|
+
summary.conversations += cleaned.conversations.length;
|
|
1582
|
+
summary.segmentsKept += cleaned.segmentsKept;
|
|
1583
|
+
summary.segmentsDropped += cleaned.segmentsDropped;
|
|
1584
|
+
summary.redactions += cleaned.redactions;
|
|
1585
|
+
summary.correctionsApplied += cleaned.correctionsApplied;
|
|
1586
|
+
if (fetched.conversations.length === 0) {
|
|
1587
|
+
const existing = await deps.readDayContentHash(connector.id, date);
|
|
1588
|
+
if (existing !== null) {
|
|
1589
|
+
summary.warnings.push(
|
|
1590
|
+
`${connector.id}: provider returned no conversations for ${date} but a stored transcript exists \u2014 leaving it in place; delete the day file manually if the recordings were intentionally removed upstream`
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
const allElided = cleaned.conversations.length === 0;
|
|
1596
|
+
let body = allElided ? emptyDayBody(connector.id, date) : composeDayTranscriptBody(
|
|
1597
|
+
connector.id,
|
|
1598
|
+
date,
|
|
1599
|
+
timezone,
|
|
1600
|
+
cleaned.conversations,
|
|
1601
|
+
registry
|
|
1602
|
+
);
|
|
1603
|
+
if (fetched.partial && !allElided) {
|
|
1604
|
+
body += PARTIAL_DAY_MARKER;
|
|
1605
|
+
}
|
|
1606
|
+
const bodyHash = hashTranscriptBody(body);
|
|
1607
|
+
const existingHash = await deps.readDayContentHash(connector.id, date);
|
|
1608
|
+
const changed = existingHash !== bodyHash;
|
|
1609
|
+
const shouldWrite = changed && (!allElided || existingHash !== null);
|
|
1610
|
+
if (shouldWrite) {
|
|
1611
|
+
const meta = composeDayTranscriptMeta(
|
|
1612
|
+
connector.id,
|
|
1613
|
+
date,
|
|
1614
|
+
timezone,
|
|
1615
|
+
cleaned.conversations,
|
|
1616
|
+
registry,
|
|
1617
|
+
body,
|
|
1618
|
+
now.toISOString()
|
|
1619
|
+
);
|
|
1620
|
+
await deps.writeDayTranscript(
|
|
1621
|
+
connector.id,
|
|
1622
|
+
date,
|
|
1623
|
+
serializeDayTranscript(meta, body)
|
|
1624
|
+
);
|
|
1625
|
+
summary.transcriptsWritten.push(date);
|
|
1626
|
+
}
|
|
1627
|
+
dayHashes[date] = bodyHash;
|
|
1628
|
+
if (allElided) continue;
|
|
1629
|
+
const memoryPassComplete = previousState?.memoryDayHashes?.[date] === bodyHash;
|
|
1630
|
+
if (settings.memoryMode !== "off" && (changed || options.forceMemories === true || !memoryPassComplete)) {
|
|
1631
|
+
if (!deps.memoryGen) {
|
|
1632
|
+
summary.warnings.push(
|
|
1633
|
+
`${connector.id}: memoryMode is '${settings.memoryMode}' but no extraction engine is available in this context \u2014 transcripts synced, memories skipped`
|
|
1634
|
+
);
|
|
1635
|
+
} else {
|
|
1636
|
+
let passClean = false;
|
|
1637
|
+
try {
|
|
1638
|
+
const generated = await generateWearableMemories(
|
|
1639
|
+
connector.id,
|
|
1640
|
+
date,
|
|
1641
|
+
cleaned.conversations,
|
|
1642
|
+
settings,
|
|
1643
|
+
registry,
|
|
1644
|
+
deps.memoryGen
|
|
1645
|
+
);
|
|
1646
|
+
summary.memoriesCreated += generated.created;
|
|
1647
|
+
summary.memoriesSkipped += generated.skipped;
|
|
1648
|
+
summary.warnings.push(...generated.warnings);
|
|
1649
|
+
passClean = generated.warnings.length === 0;
|
|
1650
|
+
if (config.digestEnabled) {
|
|
1651
|
+
const wrote = await writeDailyDigestMemory(
|
|
1652
|
+
connector.id,
|
|
1653
|
+
date,
|
|
1654
|
+
cleaned.conversations,
|
|
1655
|
+
settings,
|
|
1656
|
+
registry,
|
|
1657
|
+
deps.memoryGen.writer
|
|
1658
|
+
);
|
|
1659
|
+
if (wrote) summary.memoriesCreated += 1;
|
|
1660
|
+
}
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
passClean = false;
|
|
1663
|
+
summary.warnings.push(
|
|
1664
|
+
`${connector.id}: memory pass failed for ${date}: ${describeErrorForOperator(err)} \u2014 retries on the next sync`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
if (passClean) {
|
|
1668
|
+
memoryDayHashes[date] = bodyHash;
|
|
1669
|
+
} else {
|
|
1670
|
+
failedMemoryDays.push(date);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
} else if (settings.memoryMode !== "off" && memoryPassComplete) {
|
|
1674
|
+
memoryDayHashes[date] = bodyHash;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (settings.importNativeMemories === "review" && typeof connector.fetchNativeMemories === "function") {
|
|
1678
|
+
if (!deps.memoryGen) {
|
|
1679
|
+
summary.warnings.push(
|
|
1680
|
+
`${connector.id}: importNativeMemories is enabled but no memory writer is available in this context`
|
|
1681
|
+
);
|
|
1682
|
+
} else {
|
|
1683
|
+
const alreadyImported = new Set(
|
|
1684
|
+
previousState?.importedNativeMemoryIds ?? []
|
|
1685
|
+
);
|
|
1686
|
+
let cursor = void 0;
|
|
1687
|
+
for (let page = 0; page < MAX_NATIVE_PAGES; page++) {
|
|
1688
|
+
const result = await connector.fetchNativeMemories({
|
|
1689
|
+
cursor,
|
|
1690
|
+
signal: options.signal
|
|
1691
|
+
});
|
|
1692
|
+
const imported = await importNativeMemories(
|
|
1693
|
+
connector.id,
|
|
1694
|
+
result.memories,
|
|
1695
|
+
alreadyImported,
|
|
1696
|
+
deps.memoryGen.writer
|
|
1697
|
+
);
|
|
1698
|
+
summary.nativeMemoriesImported += imported.imported;
|
|
1699
|
+
importedNativeIds.push(...imported.importedIds);
|
|
1700
|
+
for (const id of imported.importedIds) alreadyImported.add(id);
|
|
1701
|
+
if (!result.nextCursor) break;
|
|
1702
|
+
cursor = result.nextCursor;
|
|
1703
|
+
if (page === MAX_NATIVE_PAGES - 1) {
|
|
1704
|
+
summary.warnings.push(
|
|
1705
|
+
`${connector.id}: stopped native-memory import after ${MAX_NATIVE_PAGES} pages \u2014 remaining items import on the next sync`
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (summary.transcriptsWritten.length > 0 && deps.afterTranscriptsWritten) {
|
|
1712
|
+
try {
|
|
1713
|
+
await deps.afterTranscriptsWritten();
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
summary.warnings.push(
|
|
1716
|
+
`search reindex failed (transcripts are stored and will index on the next update): ${describeErrorForOperator(err)}`
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
syncState = updateSourceSyncState(syncState, connector.id, {
|
|
1721
|
+
syncedAt: now.toISOString(),
|
|
1722
|
+
days: dates,
|
|
1723
|
+
dayHashes,
|
|
1724
|
+
memoryDayHashes,
|
|
1725
|
+
clearMemoryDays: failedMemoryDays,
|
|
1726
|
+
importedNativeMemoryIds: importedNativeIds
|
|
1727
|
+
});
|
|
1728
|
+
await saveSyncState(deps.memoryDir, syncState);
|
|
1729
|
+
return summary;
|
|
1730
|
+
}
|
|
1731
|
+
function defaultTimezone() {
|
|
1732
|
+
try {
|
|
1733
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
1734
|
+
} catch {
|
|
1735
|
+
return "UTC";
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// src/wearables/registry.ts
|
|
1740
|
+
var registrations = /* @__PURE__ */ new Map();
|
|
1741
|
+
function registerWearableConnector(registration) {
|
|
1742
|
+
if (!registration || typeof registration !== "object") {
|
|
1743
|
+
throw new Error("wearable connector registration must be an object");
|
|
1744
|
+
}
|
|
1745
|
+
if (typeof registration.id !== "string" || registration.id.trim().length === 0) {
|
|
1746
|
+
throw new Error("wearable connector id must be a non-empty string");
|
|
1747
|
+
}
|
|
1748
|
+
if (typeof registration.factory !== "function") {
|
|
1749
|
+
throw new Error(
|
|
1750
|
+
`wearable connector '${registration.id}' must provide a factory function`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
const key = registration.id.trim();
|
|
1754
|
+
if (registrations.has(key)) {
|
|
1755
|
+
throw new Error(`wearable connector '${key}' is already registered`);
|
|
1756
|
+
}
|
|
1757
|
+
registrations.set(key, { ...registration, id: key });
|
|
1758
|
+
}
|
|
1759
|
+
function getWearableConnector(id) {
|
|
1760
|
+
if (typeof id !== "string") return void 0;
|
|
1761
|
+
const key = id.trim();
|
|
1762
|
+
if (key.length === 0) return void 0;
|
|
1763
|
+
return registrations.get(key);
|
|
1764
|
+
}
|
|
1765
|
+
function listWearableConnectors() {
|
|
1766
|
+
return [...registrations.keys()];
|
|
1767
|
+
}
|
|
1768
|
+
function clearWearableConnectors() {
|
|
1769
|
+
registrations.clear();
|
|
1770
|
+
}
|
|
1771
|
+
var BUILT_IN_CONNECTOR_PACKAGES = [
|
|
1772
|
+
{ id: "limitless", suffix: "connector-limitless" },
|
|
1773
|
+
{ id: "bee", suffix: "connector-bee" },
|
|
1774
|
+
{ id: "omi", suffix: "connector-omi" }
|
|
1775
|
+
];
|
|
1776
|
+
var loadFailuresWarned = /* @__PURE__ */ new Set();
|
|
1777
|
+
async function ensureBuiltInWearableConnectors() {
|
|
1778
|
+
for (const entry of BUILT_IN_CONNECTOR_PACKAGES) {
|
|
1779
|
+
if (registrations.has(entry.id)) continue;
|
|
1780
|
+
const specifier = "@remnic/" + entry.suffix;
|
|
1781
|
+
let mod;
|
|
1782
|
+
try {
|
|
1783
|
+
mod = await import(specifier);
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
if (isModuleNotFound(err, specifier)) continue;
|
|
1786
|
+
if (!loadFailuresWarned.has(specifier)) {
|
|
1787
|
+
loadFailuresWarned.add(specifier);
|
|
1788
|
+
log.warn(
|
|
1789
|
+
`wearables: failed to load optional connector package ${specifier}: ${err instanceof Error ? err.message : String(err)}`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
const registration = mod.wearableConnectorRegistration;
|
|
1795
|
+
if (!registration) continue;
|
|
1796
|
+
try {
|
|
1797
|
+
registerWearableConnector(registration);
|
|
1798
|
+
} catch {
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function isModuleNotFound(err, specifier) {
|
|
1803
|
+
if (!err || typeof err !== "object") return false;
|
|
1804
|
+
const code = err.code;
|
|
1805
|
+
if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
const message = err.message;
|
|
1809
|
+
return typeof message === "string" && message.includes(specifier);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/wearables/service.ts
|
|
1813
|
+
function createWearableMemoryWriter(storage) {
|
|
1814
|
+
return {
|
|
1815
|
+
writeMemory: storage.writeMemory.bind(storage),
|
|
1816
|
+
hasFactContentHash: async (content) => {
|
|
1817
|
+
if (await storage.hasFactContentHash(content)) return true;
|
|
1818
|
+
const needle = content.trim();
|
|
1819
|
+
const memories = await storage.readAllMemories();
|
|
1820
|
+
return memories.some(
|
|
1821
|
+
(memory) => typeof memory.frontmatter.source === "string" && memory.frontmatter.source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`) && memory.content.trim() === needle
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
var SOURCE_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
1827
|
+
function assertValidSourceId(source) {
|
|
1828
|
+
if (!SOURCE_ID_PATTERN.test(source)) {
|
|
1829
|
+
throw new WearablesInputError(
|
|
1830
|
+
`invalid source id '${source}' \u2014 expected lowercase letters, digits, and dashes`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
var TRANSCRIPT_SEARCH_DEFAULT_LIMIT = 10;
|
|
1835
|
+
var TRANSCRIPT_SEARCH_MAX_LIMIT = 50;
|
|
1836
|
+
var MEMORY_LIST_DEFAULT_LIMIT = 50;
|
|
1837
|
+
var MEMORY_LIST_MAX_LIMIT = 200;
|
|
1838
|
+
var WearablesService = class {
|
|
1839
|
+
constructor(deps) {
|
|
1840
|
+
this.deps = deps;
|
|
1841
|
+
}
|
|
1842
|
+
deps;
|
|
1843
|
+
get enabled() {
|
|
1844
|
+
return this.deps.config.enabled;
|
|
1845
|
+
}
|
|
1846
|
+
assertEnabled() {
|
|
1847
|
+
if (!this.deps.config.enabled) {
|
|
1848
|
+
throw new WearablesInputError(
|
|
1849
|
+
"wearables are not enabled \u2014 set `wearables.enabled: true` (and configure at least one source) in the plugin config"
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
timezone() {
|
|
1854
|
+
return this.deps.config.timezone ?? defaultTimezone();
|
|
1855
|
+
}
|
|
1856
|
+
enabledSources() {
|
|
1857
|
+
return Object.entries(this.deps.config.sources).filter(
|
|
1858
|
+
([, settings]) => settings.enabled
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
/** Status for every configured source (and connector availability). */
|
|
1862
|
+
async status() {
|
|
1863
|
+
await ensureBuiltInWearableConnectors();
|
|
1864
|
+
const storage = await this.deps.getStorage();
|
|
1865
|
+
const syncState = await loadSyncState(storage.dir);
|
|
1866
|
+
const sources = [];
|
|
1867
|
+
for (const [sourceId, settings] of Object.entries(this.deps.config.sources)) {
|
|
1868
|
+
const registration = getWearableConnector(sourceId);
|
|
1869
|
+
const days = await storage.listWearableTranscriptDays(sourceId).catch(() => []);
|
|
1870
|
+
const state = syncState.sources[sourceId];
|
|
1871
|
+
sources.push({
|
|
1872
|
+
source: sourceId,
|
|
1873
|
+
displayName: registration?.displayName ?? sourceId,
|
|
1874
|
+
enabled: settings.enabled,
|
|
1875
|
+
connectorInstalled: registration !== void 0,
|
|
1876
|
+
memoryMode: settings.memoryMode,
|
|
1877
|
+
lastSyncAt: state?.lastSyncAt ?? null,
|
|
1878
|
+
lastDateSynced: state?.lastDateSynced ?? null,
|
|
1879
|
+
transcriptDays: days.length
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
return {
|
|
1883
|
+
enabled: this.deps.config.enabled,
|
|
1884
|
+
timezone: this.timezone(),
|
|
1885
|
+
sources,
|
|
1886
|
+
connectorsInstalled: listWearableConnectors()
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
/** Run a sync for one source or all enabled sources. */
|
|
1890
|
+
async sync(options) {
|
|
1891
|
+
this.assertEnabled();
|
|
1892
|
+
await ensureBuiltInWearableConnectors();
|
|
1893
|
+
const storage = await this.deps.getStorage();
|
|
1894
|
+
let targets;
|
|
1895
|
+
if (options.source !== void 0) {
|
|
1896
|
+
assertValidSourceId(options.source);
|
|
1897
|
+
const settings = this.deps.config.sources[options.source];
|
|
1898
|
+
if (!settings) {
|
|
1899
|
+
throw new WearablesInputError(
|
|
1900
|
+
`unknown wearable source '${options.source}' \u2014 configured sources: ${Object.keys(this.deps.config.sources).join(", ") || "(none)"}`
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (!settings.enabled) {
|
|
1904
|
+
throw new WearablesInputError(
|
|
1905
|
+
`wearable source '${options.source}' is configured but disabled \u2014 set wearables.sources.${options.source}.enabled: true`
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
targets = [[options.source, settings]];
|
|
1909
|
+
} else {
|
|
1910
|
+
targets = this.enabledSources();
|
|
1911
|
+
if (targets.length === 0) {
|
|
1912
|
+
throw new WearablesInputError(
|
|
1913
|
+
"no wearable sources are enabled \u2014 configure wearables.sources.<id>.enabled: true"
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
const memoryGen = this.deps.extract ? {
|
|
1918
|
+
extract: this.deps.extract,
|
|
1919
|
+
writer: createWearableMemoryWriter(storage)
|
|
1920
|
+
} : null;
|
|
1921
|
+
const summaries = [];
|
|
1922
|
+
for (const [sourceId, settings] of targets) {
|
|
1923
|
+
const registration = getWearableConnector(sourceId);
|
|
1924
|
+
if (!registration) {
|
|
1925
|
+
throw new WearablesInputError(
|
|
1926
|
+
`wearable source '${sourceId}' is enabled but its connector package is not installed.
|
|
1927
|
+
Install it alongside Remnic:
|
|
1928
|
+
npm install @remnic/connector-${sourceId}`
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
const connector = registration.factory({
|
|
1932
|
+
settings,
|
|
1933
|
+
timezone: this.timezone()
|
|
1934
|
+
});
|
|
1935
|
+
const summary = await syncWearableSource(
|
|
1936
|
+
connector,
|
|
1937
|
+
settings,
|
|
1938
|
+
this.deps.config,
|
|
1939
|
+
options,
|
|
1940
|
+
{
|
|
1941
|
+
memoryDir: storage.dir,
|
|
1942
|
+
readDayContentHash: async (source, date) => {
|
|
1943
|
+
const raw = await storage.readWearableDayTranscript(source, date);
|
|
1944
|
+
if (raw === null) return null;
|
|
1945
|
+
return parseDayTranscript(raw)?.meta.contentHash ?? null;
|
|
1946
|
+
},
|
|
1947
|
+
writeDayTranscript: (source, date, serialized) => storage.writeWearableDayTranscript(source, date, serialized),
|
|
1948
|
+
afterTranscriptsWritten: this.deps.reindexSearch,
|
|
1949
|
+
memoryGen
|
|
1950
|
+
}
|
|
1951
|
+
);
|
|
1952
|
+
summaries.push(summary);
|
|
1953
|
+
}
|
|
1954
|
+
return summaries;
|
|
1955
|
+
}
|
|
1956
|
+
/** Verify connectivity/credentials for one source. */
|
|
1957
|
+
async checkAuth(sourceId) {
|
|
1958
|
+
this.assertEnabled();
|
|
1959
|
+
await ensureBuiltInWearableConnectors();
|
|
1960
|
+
assertValidSourceId(sourceId);
|
|
1961
|
+
const settings = this.deps.config.sources[sourceId];
|
|
1962
|
+
if (!settings) {
|
|
1963
|
+
throw new WearablesInputError(`unknown wearable source '${sourceId}'`);
|
|
1964
|
+
}
|
|
1965
|
+
const registration = getWearableConnector(sourceId);
|
|
1966
|
+
if (!registration) {
|
|
1967
|
+
return {
|
|
1968
|
+
ok: false,
|
|
1969
|
+
detail: `connector package @remnic/connector-${sourceId} is not installed`
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
const connector = registration.factory({
|
|
1973
|
+
settings,
|
|
1974
|
+
timezone: this.timezone()
|
|
1975
|
+
});
|
|
1976
|
+
try {
|
|
1977
|
+
return await connector.verifyAuth();
|
|
1978
|
+
} catch (err) {
|
|
1979
|
+
return {
|
|
1980
|
+
ok: false,
|
|
1981
|
+
detail: describeErrorForOperator(err)
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Full transcript(s) for a day. Without `source`, returns every
|
|
1987
|
+
* source that recorded that day, annotated with overlap hints.
|
|
1988
|
+
*/
|
|
1989
|
+
async dayTranscript(date, sourceId) {
|
|
1990
|
+
if (!isValidTranscriptDate(date)) {
|
|
1991
|
+
throw new WearablesInputError(`invalid date '${date}' \u2014 expected YYYY-MM-DD`);
|
|
1992
|
+
}
|
|
1993
|
+
if (sourceId !== void 0) assertValidSourceId(sourceId);
|
|
1994
|
+
const storage = await this.deps.getStorage();
|
|
1995
|
+
const targets = sourceId !== void 0 ? [sourceId] : (await storage.listWearableTranscriptDays()).filter((entry) => entry.date === date).map((entry) => entry.source);
|
|
1996
|
+
const views = [];
|
|
1997
|
+
for (const source of [...new Set(targets)]) {
|
|
1998
|
+
const raw = await storage.readWearableDayTranscript(source, date);
|
|
1999
|
+
if (raw === null) continue;
|
|
2000
|
+
const parsed = parseDayTranscript(raw);
|
|
2001
|
+
views.push({
|
|
2002
|
+
source,
|
|
2003
|
+
date,
|
|
2004
|
+
meta: parsed?.meta ?? null,
|
|
2005
|
+
body: parsed?.body ?? raw,
|
|
2006
|
+
overlapsWith: []
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
for (const view of views) {
|
|
2010
|
+
view.overlapsWith = views.map((other) => other.source).filter((other) => other !== view.source);
|
|
2011
|
+
}
|
|
2012
|
+
return views;
|
|
2013
|
+
}
|
|
2014
|
+
/** List days that have stored transcripts. */
|
|
2015
|
+
async listDays(sourceId) {
|
|
2016
|
+
if (sourceId !== void 0) assertValidSourceId(sourceId);
|
|
2017
|
+
const storage = await this.deps.getStorage();
|
|
2018
|
+
return storage.listWearableTranscriptDays(sourceId);
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Search stored transcripts. Uses the indexed backend when available
|
|
2022
|
+
* and falls back to a bounded substring scan otherwise — the two
|
|
2023
|
+
* paths are distinguishable in the result (`backend`) so callers can
|
|
2024
|
+
* tell "no hits" from "weaker search ran".
|
|
2025
|
+
*/
|
|
2026
|
+
async searchTranscripts(query, options = {}) {
|
|
2027
|
+
const trimmed = query.trim();
|
|
2028
|
+
if (trimmed.length === 0) {
|
|
2029
|
+
throw new WearablesInputError("transcript search requires a non-empty query");
|
|
2030
|
+
}
|
|
2031
|
+
if (options.source !== void 0) assertValidSourceId(options.source);
|
|
2032
|
+
for (const [name, value] of [
|
|
2033
|
+
["from", options.from],
|
|
2034
|
+
["to", options.to]
|
|
2035
|
+
]) {
|
|
2036
|
+
if (value !== void 0 && !isValidTranscriptDate(value)) {
|
|
2037
|
+
throw new WearablesInputError(`invalid ${name} date '${value}' \u2014 expected YYYY-MM-DD`);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
const limit = clampLimit(
|
|
2041
|
+
options.limit,
|
|
2042
|
+
TRANSCRIPT_SEARCH_DEFAULT_LIMIT,
|
|
2043
|
+
TRANSCRIPT_SEARCH_MAX_LIMIT,
|
|
2044
|
+
"limit"
|
|
2045
|
+
);
|
|
2046
|
+
const matchesScope = (source, date) => {
|
|
2047
|
+
if (options.source !== void 0 && source !== options.source) return false;
|
|
2048
|
+
if (options.from !== void 0 && date < options.from) return false;
|
|
2049
|
+
if (options.to !== void 0 && date > options.to) return false;
|
|
2050
|
+
return true;
|
|
2051
|
+
};
|
|
2052
|
+
if (this.deps.searchBackend) {
|
|
2053
|
+
const hits = await this.deps.searchBackend.search(trimmed, limit * 5);
|
|
2054
|
+
if (hits !== null) {
|
|
2055
|
+
const results2 = [];
|
|
2056
|
+
for (const hit of hits) {
|
|
2057
|
+
const located = locateTranscriptPath(hit.path);
|
|
2058
|
+
if (!located) continue;
|
|
2059
|
+
if (!matchesScope(located.source, located.date)) continue;
|
|
2060
|
+
results2.push({
|
|
2061
|
+
source: located.source,
|
|
2062
|
+
date: located.date,
|
|
2063
|
+
score: hit.score,
|
|
2064
|
+
snippet: hit.preview,
|
|
2065
|
+
backend: "indexed"
|
|
2066
|
+
});
|
|
2067
|
+
if (results2.length >= limit) break;
|
|
2068
|
+
}
|
|
2069
|
+
if (results2.length > 0) {
|
|
2070
|
+
return results2;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
const storage = await this.deps.getStorage();
|
|
2075
|
+
const days = await storage.listWearableTranscriptDays(options.source);
|
|
2076
|
+
const needle = trimmed.toLowerCase();
|
|
2077
|
+
const results = [];
|
|
2078
|
+
for (const { source, date } of days) {
|
|
2079
|
+
if (!matchesScope(source, date)) continue;
|
|
2080
|
+
const raw = await storage.readWearableDayTranscript(source, date);
|
|
2081
|
+
if (raw === null) continue;
|
|
2082
|
+
const body = parseDayTranscript(raw)?.body ?? raw;
|
|
2083
|
+
const lower = body.toLowerCase();
|
|
2084
|
+
const index = lower.indexOf(needle);
|
|
2085
|
+
if (index === -1) continue;
|
|
2086
|
+
results.push({
|
|
2087
|
+
source,
|
|
2088
|
+
date,
|
|
2089
|
+
score: 0,
|
|
2090
|
+
snippet: extractSnippet(body, index, needle.length),
|
|
2091
|
+
backend: "scan"
|
|
2092
|
+
});
|
|
2093
|
+
if (results.length >= limit) break;
|
|
2094
|
+
}
|
|
2095
|
+
return results;
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Memories created from wearable transcripts, filterable by source
|
|
2099
|
+
* and/or day. Includes pending_review candidates — the whole point of
|
|
2100
|
+
* review mode is seeing what's queued.
|
|
2101
|
+
*/
|
|
2102
|
+
async transcriptMemories(options = {}) {
|
|
2103
|
+
if (options.date !== void 0 && !isValidTranscriptDate(options.date)) {
|
|
2104
|
+
throw new WearablesInputError(`invalid date '${options.date}' \u2014 expected YYYY-MM-DD`);
|
|
2105
|
+
}
|
|
2106
|
+
if (options.source !== void 0) assertValidSourceId(options.source);
|
|
2107
|
+
const limit = clampLimit(
|
|
2108
|
+
options.limit,
|
|
2109
|
+
MEMORY_LIST_DEFAULT_LIMIT,
|
|
2110
|
+
MEMORY_LIST_MAX_LIMIT,
|
|
2111
|
+
"limit"
|
|
2112
|
+
);
|
|
2113
|
+
const storage = await this.deps.getStorage();
|
|
2114
|
+
const memories = await storage.readAllMemories();
|
|
2115
|
+
const results = [];
|
|
2116
|
+
for (const memory of memories) {
|
|
2117
|
+
const source = memory.frontmatter.source;
|
|
2118
|
+
if (typeof source !== "string" || !source.startsWith(`${WEARABLE_SOURCE_PREFIX}:`)) {
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const attrs = memory.frontmatter.structuredAttributes ?? {};
|
|
2122
|
+
const sourceId = attrs.wearableSource;
|
|
2123
|
+
if (options.source !== void 0) {
|
|
2124
|
+
if (sourceId !== options.source && source !== wearableSourceLabel(options.source) && source !== `${wearableSourceLabel(options.source)}:native`) {
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (options.date !== void 0 && attrs.wearableDate !== options.date) {
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
results.push({
|
|
2132
|
+
id: memory.frontmatter.id,
|
|
2133
|
+
source: sourceId ?? source,
|
|
2134
|
+
date: attrs.wearableDate,
|
|
2135
|
+
conversationId: attrs.wearableConversationId,
|
|
2136
|
+
status: memory.frontmatter.status,
|
|
2137
|
+
content: memory.content,
|
|
2138
|
+
created: memory.frontmatter.created
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
results.sort((a, b) => {
|
|
2142
|
+
if (a.created > b.created) return -1;
|
|
2143
|
+
if (a.created < b.created) return 1;
|
|
2144
|
+
if (a.id < b.id) return -1;
|
|
2145
|
+
if (a.id > b.id) return 1;
|
|
2146
|
+
return 0;
|
|
2147
|
+
});
|
|
2148
|
+
return results.slice(0, limit);
|
|
2149
|
+
}
|
|
2150
|
+
// -- speakers -------------------------------------------------------------
|
|
2151
|
+
async listSpeakers() {
|
|
2152
|
+
const storage = await this.deps.getStorage();
|
|
2153
|
+
return loadSpeakerRegistry(storage.dir);
|
|
2154
|
+
}
|
|
2155
|
+
async setSpeaker(sourceId, speakerKey, name, opts = {}) {
|
|
2156
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
2157
|
+
throw new WearablesInputError("speaker name must be a non-empty string");
|
|
2158
|
+
}
|
|
2159
|
+
if (typeof speakerKey !== "string" || speakerKey.trim().length === 0) {
|
|
2160
|
+
throw new WearablesInputError("speaker key must be a non-empty string");
|
|
2161
|
+
}
|
|
2162
|
+
const storage = await this.deps.getStorage();
|
|
2163
|
+
const registry = await loadSpeakerRegistry(storage.dir);
|
|
2164
|
+
registry.speakers[speakerRegistryKey(sourceId, speakerKey.trim())] = {
|
|
2165
|
+
name: name.trim(),
|
|
2166
|
+
...opts.isSelf === true ? { isSelf: true } : {},
|
|
2167
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2168
|
+
};
|
|
2169
|
+
await saveSpeakerRegistry(storage.dir, registry);
|
|
2170
|
+
return registry;
|
|
2171
|
+
}
|
|
2172
|
+
async setSelfName(name) {
|
|
2173
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
2174
|
+
throw new WearablesInputError("self name must be a non-empty string");
|
|
2175
|
+
}
|
|
2176
|
+
const storage = await this.deps.getStorage();
|
|
2177
|
+
const registry = await loadSpeakerRegistry(storage.dir);
|
|
2178
|
+
registry.selfName = name.trim();
|
|
2179
|
+
await saveSpeakerRegistry(storage.dir, registry);
|
|
2180
|
+
return registry;
|
|
2181
|
+
}
|
|
2182
|
+
async removeSpeaker(sourceId, speakerKey) {
|
|
2183
|
+
const storage = await this.deps.getStorage();
|
|
2184
|
+
const registry = await loadSpeakerRegistry(storage.dir);
|
|
2185
|
+
const key = speakerRegistryKey(sourceId, speakerKey.trim());
|
|
2186
|
+
if (!(key in registry.speakers)) {
|
|
2187
|
+
throw new WearablesInputError(`no speaker override stored for '${key}'`);
|
|
2188
|
+
}
|
|
2189
|
+
delete registry.speakers[key];
|
|
2190
|
+
await saveSpeakerRegistry(storage.dir, registry);
|
|
2191
|
+
return registry;
|
|
2192
|
+
}
|
|
2193
|
+
// -- corrections ----------------------------------------------------------
|
|
2194
|
+
async listCorrections() {
|
|
2195
|
+
const storage = await this.deps.getStorage();
|
|
2196
|
+
return {
|
|
2197
|
+
fromConfig: this.deps.config.corrections,
|
|
2198
|
+
fromState: await loadCorrectionsFile(storage.dir),
|
|
2199
|
+
stateFilePath: correctionsFilePath(storage.dir)
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
async addCorrection(rule) {
|
|
2203
|
+
compileCorrectionRule(rule, "correction");
|
|
2204
|
+
const storage = await this.deps.getStorage();
|
|
2205
|
+
const rules = await loadCorrectionsFile(storage.dir);
|
|
2206
|
+
const duplicate = rules.some(
|
|
2207
|
+
(existing) => existing.match === rule.match && existing.replace === rule.replace && existing.regex === true === (rule.regex === true)
|
|
2208
|
+
);
|
|
2209
|
+
if (duplicate) {
|
|
2210
|
+
throw new WearablesInputError(
|
|
2211
|
+
`an identical correction rule already exists (match: ${JSON.stringify(rule.match)})`
|
|
2212
|
+
);
|
|
2213
|
+
}
|
|
2214
|
+
rules.push(rule);
|
|
2215
|
+
await saveCorrectionsFile(storage.dir, rules);
|
|
2216
|
+
}
|
|
2217
|
+
async removeCorrection(index) {
|
|
2218
|
+
if (!Number.isInteger(index) || index < 0) {
|
|
2219
|
+
throw new WearablesInputError(`invalid correction index '${index}'`);
|
|
2220
|
+
}
|
|
2221
|
+
const storage = await this.deps.getStorage();
|
|
2222
|
+
const rules = await loadCorrectionsFile(storage.dir);
|
|
2223
|
+
if (index >= rules.length) {
|
|
2224
|
+
throw new WearablesInputError(
|
|
2225
|
+
`correction index ${index} is out of range (have ${rules.length} state rule${rules.length === 1 ? "" : "s"})`
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
const [removed] = rules.splice(index, 1);
|
|
2229
|
+
await saveCorrectionsFile(storage.dir, rules);
|
|
2230
|
+
return removed;
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
function clampLimit(value, fallback, max, label) {
|
|
2234
|
+
if (value === void 0) return fallback;
|
|
2235
|
+
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1 || value > max) {
|
|
2236
|
+
throw new WearablesInputError(
|
|
2237
|
+
`invalid ${label} '${value}' \u2014 expected an integer between 1 and ${max}`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return value;
|
|
2241
|
+
}
|
|
2242
|
+
function locateTranscriptPath(hitPath) {
|
|
2243
|
+
const normalized = hitPath.replace(/\\/g, "/");
|
|
2244
|
+
const match = normalized.match(
|
|
2245
|
+
/(?:^|\/)wearables\/([a-z][a-z0-9-]{0,63})\/(\d{4}-\d{2}-\d{2})\.md$/
|
|
2246
|
+
);
|
|
2247
|
+
if (!match) return null;
|
|
2248
|
+
if (!isValidTranscriptDate(match[2])) return null;
|
|
2249
|
+
return { source: match[1], date: match[2] };
|
|
2250
|
+
}
|
|
2251
|
+
function extractSnippet(body, index, matchLength) {
|
|
2252
|
+
const start = Math.max(0, index - 80);
|
|
2253
|
+
const end = Math.min(body.length, index + matchLength + 80);
|
|
2254
|
+
const prefix = start > 0 ? "\u2026" : "";
|
|
2255
|
+
const suffix = end < body.length ? "\u2026" : "";
|
|
2256
|
+
return `${prefix}${body.slice(start, end).replace(/\s+/g, " ").trim()}${suffix}`;
|
|
2257
|
+
}
|
|
2258
|
+
|
|
940
2259
|
// src/orchestrator.ts
|
|
941
2260
|
function dedupeEntitySynthesisEvidenceEntries(entries) {
|
|
942
2261
|
const dedupedEvidenceEntries = [];
|
|
@@ -1072,7 +2391,7 @@ async function qmdStartupCollectionCheckWithTimeout(promise, controller, label)
|
|
|
1072
2391
|
return await Promise.race([checkedPromise, timeoutPromise]);
|
|
1073
2392
|
}
|
|
1074
2393
|
function defaultWorkspaceDir() {
|
|
1075
|
-
return
|
|
2394
|
+
return path3.join(os.homedir(), ".openclaw", "workspace");
|
|
1076
2395
|
}
|
|
1077
2396
|
function sanitizeSessionKeyForFilename(sessionKey) {
|
|
1078
2397
|
const readable = sessionKey.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
@@ -1381,11 +2700,11 @@ function mergeGraphExpandedResults(primary, expanded) {
|
|
|
1381
2700
|
return Array.from(mergedByPath.values());
|
|
1382
2701
|
}
|
|
1383
2702
|
function graphPathRelativeToStorage(storageDir, candidatePath) {
|
|
1384
|
-
const absolutePath =
|
|
1385
|
-
const rel =
|
|
2703
|
+
const absolutePath = path3.isAbsolute(candidatePath) ? candidatePath : path3.resolve(storageDir, candidatePath);
|
|
2704
|
+
const rel = path3.relative(storageDir, absolutePath);
|
|
1386
2705
|
if (!rel || rel === ".") return null;
|
|
1387
2706
|
if (rel.startsWith("..")) return null;
|
|
1388
|
-
return rel.split(
|
|
2707
|
+
return rel.split(path3.sep).join("/");
|
|
1389
2708
|
}
|
|
1390
2709
|
function normalizeGraphActivationScore(score) {
|
|
1391
2710
|
const bounded = Number.isFinite(score) && score > 0 ? score : 0;
|
|
@@ -1527,7 +2846,7 @@ function buildMemoryPathById(allMemsForGraph, storageDir) {
|
|
|
1527
2846
|
for (const mem of allMemsForGraph ?? []) {
|
|
1528
2847
|
const id = mem.frontmatter.id;
|
|
1529
2848
|
if (!id) continue;
|
|
1530
|
-
pathById.set(id,
|
|
2849
|
+
pathById.set(id, path3.relative(storageDir, mem.path));
|
|
1531
2850
|
}
|
|
1532
2851
|
return pathById;
|
|
1533
2852
|
}
|
|
@@ -1535,7 +2854,7 @@ function appendMemoryToGraphContext(options) {
|
|
|
1535
2854
|
if (!Array.isArray(options.allMemsForGraph)) return;
|
|
1536
2855
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1537
2856
|
options.allMemsForGraph.push({
|
|
1538
|
-
path:
|
|
2857
|
+
path: path3.join(options.storageDir, options.memoryRelPath),
|
|
1539
2858
|
content: options.content,
|
|
1540
2859
|
frontmatter: {
|
|
1541
2860
|
id: options.memoryId,
|
|
@@ -1555,16 +2874,16 @@ function resolvePersistedMemoryRelativePath(options) {
|
|
|
1555
2874
|
const persisted = options.pathById.get(options.memoryId);
|
|
1556
2875
|
if (persisted) return persisted;
|
|
1557
2876
|
if (options.category === "correction") {
|
|
1558
|
-
return
|
|
2877
|
+
return path3.join("corrections", `${options.memoryId}.md`);
|
|
1559
2878
|
}
|
|
1560
2879
|
const subtree = options.category === "procedure" ? "procedures" : options.category === "reasoning_trace" ? "reasoning-traces" : "facts";
|
|
1561
2880
|
const idParts = options.memoryId.split("-");
|
|
1562
2881
|
const maybeTimestamp = Number(idParts[1]);
|
|
1563
2882
|
if (Number.isFinite(maybeTimestamp) && maybeTimestamp > 0) {
|
|
1564
2883
|
const day = new Date(maybeTimestamp).toISOString().slice(0, 10);
|
|
1565
|
-
return
|
|
2884
|
+
return path3.join(subtree, day, `${options.memoryId}.md`);
|
|
1566
2885
|
}
|
|
1567
|
-
return
|
|
2886
|
+
return path3.join(subtree, `${options.memoryId}.md`);
|
|
1568
2887
|
}
|
|
1569
2888
|
var Orchestrator = class _Orchestrator {
|
|
1570
2889
|
storage;
|
|
@@ -1683,6 +3002,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
1683
3002
|
consolidationInFlight = false;
|
|
1684
3003
|
consolidationObservers = /* @__PURE__ */ new Set();
|
|
1685
3004
|
qmdMaintenanceTimer = null;
|
|
3005
|
+
wearablesServiceInstance = null;
|
|
1686
3006
|
qmdMaintenancePending = false;
|
|
1687
3007
|
qmdMaintenanceInFlight = false;
|
|
1688
3008
|
lastQmdEmbedAtMs = 0;
|
|
@@ -2001,7 +3321,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2001
3321
|
this.config = config;
|
|
2002
3322
|
this.profiler = new ProfilingCollector({
|
|
2003
3323
|
enabled: config.profilingEnabled,
|
|
2004
|
-
storageDir: config.profilingStorageDir ||
|
|
3324
|
+
storageDir: config.profilingStorageDir || path3.join(config.memoryDir, "profiling"),
|
|
2005
3325
|
maxTraces: config.profilingMaxTraces
|
|
2006
3326
|
});
|
|
2007
3327
|
this.storageRouter = new NamespaceStorageRouter(config);
|
|
@@ -2036,7 +3356,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2036
3356
|
this.compounding = config.compoundingEnabled ? new CompoundingEngine(config, this.storage) : void 0;
|
|
2037
3357
|
this.buffer = new SmartBuffer(config, this.storage);
|
|
2038
3358
|
this.transcript = new TranscriptManager(config);
|
|
2039
|
-
this.conversationIndexDir =
|
|
3359
|
+
this.conversationIndexDir = path3.join(
|
|
2040
3360
|
config.memoryDir,
|
|
2041
3361
|
"conversation-index",
|
|
2042
3362
|
"chunks"
|
|
@@ -2093,7 +3413,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2093
3413
|
this.modelRegistry
|
|
2094
3414
|
);
|
|
2095
3415
|
this.threading = new ThreadingManager(
|
|
2096
|
-
|
|
3416
|
+
path3.join(config.memoryDir, "threads"),
|
|
2097
3417
|
config.threadingGapMinutes
|
|
2098
3418
|
);
|
|
2099
3419
|
this.tmtBuilder = new TmtBuilder(config.memoryDir, {
|
|
@@ -2376,7 +3696,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2376
3696
|
const files = await readdir(wsDir).catch(() => []);
|
|
2377
3697
|
for (const f of files) {
|
|
2378
3698
|
if (!f.startsWith(".compaction-reset-signal-")) continue;
|
|
2379
|
-
const fp =
|
|
3699
|
+
const fp = path3.join(wsDir, f);
|
|
2380
3700
|
const s = await stat(fp).catch(() => null);
|
|
2381
3701
|
if (s && Date.now() - s.mtimeMs >= COMPACTION_SIGNAL_MAX_AGE_MS) {
|
|
2382
3702
|
await unlink(fp).catch(() => {
|
|
@@ -2738,7 +4058,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2738
4058
|
*/
|
|
2739
4059
|
async autoRegisterDaySummaryCron() {
|
|
2740
4060
|
const home = resolveHomeDir();
|
|
2741
|
-
const jobsPath =
|
|
4061
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2742
4062
|
try {
|
|
2743
4063
|
if (!existsSync(jobsPath)) {
|
|
2744
4064
|
log.debug(
|
|
@@ -2762,7 +4082,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2762
4082
|
}
|
|
2763
4083
|
async autoRegisterNightlyGovernanceCron() {
|
|
2764
4084
|
const home = resolveHomeDir();
|
|
2765
|
-
const jobsPath =
|
|
4085
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2766
4086
|
try {
|
|
2767
4087
|
if (!existsSync(jobsPath)) {
|
|
2768
4088
|
log.debug("nightly governance cron: jobs.json not found, skipping auto-register");
|
|
@@ -2784,7 +4104,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2784
4104
|
}
|
|
2785
4105
|
async autoRegisterProceduralMiningCron() {
|
|
2786
4106
|
const home = resolveHomeDir();
|
|
2787
|
-
const jobsPath =
|
|
4107
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2788
4108
|
try {
|
|
2789
4109
|
if (!existsSync(jobsPath)) {
|
|
2790
4110
|
log.debug("procedural mining cron: jobs.json not found, skipping auto-register");
|
|
@@ -2804,7 +4124,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2804
4124
|
}
|
|
2805
4125
|
async autoRegisterContradictionScanCron() {
|
|
2806
4126
|
const home = resolveHomeDir();
|
|
2807
|
-
const jobsPath =
|
|
4127
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2808
4128
|
try {
|
|
2809
4129
|
if (!existsSync(jobsPath)) {
|
|
2810
4130
|
log.debug("contradiction scan cron: jobs.json not found, skipping auto-register");
|
|
@@ -2824,7 +4144,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2824
4144
|
}
|
|
2825
4145
|
async autoRegisterPatternReinforcementCron() {
|
|
2826
4146
|
const home = resolveHomeDir();
|
|
2827
|
-
const jobsPath =
|
|
4147
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2828
4148
|
try {
|
|
2829
4149
|
if (!existsSync(jobsPath)) {
|
|
2830
4150
|
log.debug("pattern reinforcement cron: jobs.json not found, skipping auto-register");
|
|
@@ -2886,7 +4206,7 @@ var Orchestrator = class _Orchestrator {
|
|
|
2886
4206
|
}
|
|
2887
4207
|
async autoRegisterGraphEdgeDecayCron() {
|
|
2888
4208
|
const home = resolveHomeDir();
|
|
2889
|
-
const jobsPath =
|
|
4209
|
+
const jobsPath = path3.join(home, ".openclaw", "cron", "jobs.json");
|
|
2890
4210
|
try {
|
|
2891
4211
|
if (!existsSync(jobsPath)) {
|
|
2892
4212
|
log.debug("graph edge decay cron: jobs.json not found, skipping auto-register");
|
|
@@ -2943,15 +4263,15 @@ ${doc.content}` : doc.content,
|
|
|
2943
4263
|
this.lastFileHygieneRunAtMs = now;
|
|
2944
4264
|
if (hygiene.rotateEnabled) {
|
|
2945
4265
|
for (const rel of hygiene.rotatePaths) {
|
|
2946
|
-
const abs =
|
|
4266
|
+
const abs = path3.isAbsolute(rel) ? rel : path3.join(this.config.workspaceDir, rel);
|
|
2947
4267
|
try {
|
|
2948
4268
|
const raw = await readFile2(abs, "utf-8");
|
|
2949
4269
|
if (raw.length > hygiene.rotateMaxBytes) {
|
|
2950
|
-
const archiveDir =
|
|
4270
|
+
const archiveDir = path3.join(
|
|
2951
4271
|
this.config.workspaceDir,
|
|
2952
4272
|
hygiene.archiveDir
|
|
2953
4273
|
);
|
|
2954
|
-
const base =
|
|
4274
|
+
const base = path3.basename(abs);
|
|
2955
4275
|
const prefix = base.toUpperCase().replace(/\.MD$/i, "").replace(/[^A-Z0-9]+/g, "-") || "FILE";
|
|
2956
4276
|
const { newContent } = await rotateMarkdownFileToArchive({
|
|
2957
4277
|
filePath: abs,
|
|
@@ -2976,8 +4296,8 @@ ${doc.content}` : doc.content,
|
|
|
2976
4296
|
log.warn(w.message);
|
|
2977
4297
|
}
|
|
2978
4298
|
if (hygiene.warningsLogEnabled && warnings.length > 0) {
|
|
2979
|
-
const fp =
|
|
2980
|
-
await mkdir2(
|
|
4299
|
+
const fp = path3.join(this.config.memoryDir, hygiene.warningsLogPath);
|
|
4300
|
+
await mkdir2(path3.dirname(fp), { recursive: true });
|
|
2981
4301
|
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2982
4302
|
const block = `
|
|
2983
4303
|
|
|
@@ -3446,16 +4766,16 @@ ${evidenceText}`
|
|
|
3446
4766
|
const datesToScan = [yesterday, utcToday].filter(
|
|
3447
4767
|
(v, i, a) => a.indexOf(v) === i
|
|
3448
4768
|
);
|
|
3449
|
-
const factsBaseDir =
|
|
4769
|
+
const factsBaseDir = path3.join(storage.dir, "facts");
|
|
3450
4770
|
const MAX_CHARS = 1e5;
|
|
3451
4771
|
const facts = [];
|
|
3452
4772
|
for (const date of datesToScan) {
|
|
3453
|
-
const factsDir =
|
|
4773
|
+
const factsDir = path3.join(factsBaseDir, date);
|
|
3454
4774
|
try {
|
|
3455
4775
|
const entries = await readdir(factsDir, { withFileTypes: true });
|
|
3456
4776
|
for (const entry of entries) {
|
|
3457
4777
|
if (!entry.name.endsWith(".md")) continue;
|
|
3458
|
-
const fullPath =
|
|
4778
|
+
const fullPath = path3.join(factsDir, entry.name);
|
|
3459
4779
|
try {
|
|
3460
4780
|
const raw = await readFile2(fullPath, "utf-8");
|
|
3461
4781
|
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
@@ -3471,7 +4791,7 @@ ${evidenceText}`
|
|
|
3471
4791
|
facts.push({
|
|
3472
4792
|
path: fullPath,
|
|
3473
4793
|
frontmatter: {
|
|
3474
|
-
id: fm.id ||
|
|
4794
|
+
id: fm.id || path3.basename(entry.name, ".md"),
|
|
3475
4795
|
category: fm.category || "fact",
|
|
3476
4796
|
created: fm.created || "unknown",
|
|
3477
4797
|
updated: fm.updated || fm.created || "unknown",
|
|
@@ -3492,13 +4812,13 @@ ${evidenceText}`
|
|
|
3492
4812
|
(a, b) => a.frontmatter.created < b.frontmatter.created ? -1 : 1
|
|
3493
4813
|
);
|
|
3494
4814
|
const hourlySummaries = [];
|
|
3495
|
-
const hourlyBaseDir =
|
|
4815
|
+
const hourlyBaseDir = path3.join(storage.dir, "summaries", "hourly");
|
|
3496
4816
|
try {
|
|
3497
4817
|
const sessionKeys = await readdir(hourlyBaseDir, { withFileTypes: true });
|
|
3498
4818
|
for (const sk of sessionKeys) {
|
|
3499
4819
|
if (!sk.isDirectory()) continue;
|
|
3500
4820
|
for (const date of datesToScan) {
|
|
3501
|
-
const summaryFile =
|
|
4821
|
+
const summaryFile = path3.join(hourlyBaseDir, sk.name, `${date}.md`);
|
|
3502
4822
|
try {
|
|
3503
4823
|
const raw = await readFile2(summaryFile, "utf-8");
|
|
3504
4824
|
if (raw.trim().length > 0) {
|
|
@@ -3596,7 +4916,7 @@ ${evidenceText}`
|
|
|
3596
4916
|
}
|
|
3597
4917
|
async getLastGraphRecallSnapshot(namespace) {
|
|
3598
4918
|
const storage = await this.getStorage(namespace);
|
|
3599
|
-
const snapshotPath =
|
|
4919
|
+
const snapshotPath = path3.join(
|
|
3600
4920
|
storage.dir,
|
|
3601
4921
|
"state",
|
|
3602
4922
|
"last_graph_recall.json"
|
|
@@ -3635,7 +4955,7 @@ ${evidenceText}`
|
|
|
3635
4955
|
}
|
|
3636
4956
|
async getLastIntentSnapshot(namespace) {
|
|
3637
4957
|
const storage = await this.getStorage(namespace);
|
|
3638
|
-
const snapshotPath =
|
|
4958
|
+
const snapshotPath = path3.join(storage.dir, "state", "last_intent.json");
|
|
3639
4959
|
try {
|
|
3640
4960
|
const raw = await readFile2(snapshotPath, "utf-8");
|
|
3641
4961
|
const parsed = JSON.parse(raw);
|
|
@@ -3668,7 +4988,7 @@ ${evidenceText}`
|
|
|
3668
4988
|
}
|
|
3669
4989
|
async getLastQmdRecallSnapshot(namespace) {
|
|
3670
4990
|
const storage = await this.getStorage(namespace);
|
|
3671
|
-
const snapshotPath =
|
|
4991
|
+
const snapshotPath = path3.join(
|
|
3672
4992
|
storage.dir,
|
|
3673
4993
|
"state",
|
|
3674
4994
|
"last_qmd_recall.json"
|
|
@@ -3821,7 +5141,7 @@ ${r.snippet.trim()}
|
|
|
3821
5141
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
3822
5142
|
let total = 0;
|
|
3823
5143
|
for (const entry of entries) {
|
|
3824
|
-
const fullPath =
|
|
5144
|
+
const fullPath = path3.join(dir, entry.name);
|
|
3825
5145
|
if (entry.isDirectory()) {
|
|
3826
5146
|
total += await this.countConversationChunkDocs(fullPath);
|
|
3827
5147
|
continue;
|
|
@@ -4717,7 +6037,7 @@ ${r.snippet.trim()}
|
|
|
4717
6037
|
0
|
|
4718
6038
|
);
|
|
4719
6039
|
seedPaths.push(
|
|
4720
|
-
...seedRelativePaths.map((rel) =>
|
|
6040
|
+
...seedRelativePaths.map((rel) => path3.join(storage.dir, rel))
|
|
4721
6041
|
);
|
|
4722
6042
|
const seedSet = new Set(seedRelativePaths);
|
|
4723
6043
|
const expanded = await this.graphIndexFor(storage).spreadingActivation(
|
|
@@ -4728,7 +6048,7 @@ ${r.snippet.trim()}
|
|
|
4728
6048
|
if (expanded.length === 0) continue;
|
|
4729
6049
|
for (const candidate of expanded.slice(0, perNamespaceExpandedCap)) {
|
|
4730
6050
|
if (seedSet.has(candidate.path)) continue;
|
|
4731
|
-
const memoryPath =
|
|
6051
|
+
const memoryPath = path3.resolve(storage.dir, candidate.path);
|
|
4732
6052
|
const memory = await storage.readMemoryByPath(memoryPath);
|
|
4733
6053
|
if (!memory) continue;
|
|
4734
6054
|
if (isArtifactMemoryPath(memory.path)) continue;
|
|
@@ -4752,7 +6072,7 @@ ${r.snippet.trim()}
|
|
|
4752
6072
|
path: memory.path,
|
|
4753
6073
|
score,
|
|
4754
6074
|
namespace,
|
|
4755
|
-
seed:
|
|
6075
|
+
seed: path3.resolve(storage.dir, candidate.seed),
|
|
4756
6076
|
hopDepth: candidate.hopDepth,
|
|
4757
6077
|
decayedWeight: candidate.decayedWeight,
|
|
4758
6078
|
graphType: candidate.graphType,
|
|
@@ -4773,12 +6093,12 @@ ${r.snippet.trim()}
|
|
|
4773
6093
|
}
|
|
4774
6094
|
async recordLastGraphRecallSnapshot(options) {
|
|
4775
6095
|
try {
|
|
4776
|
-
const snapshotPath =
|
|
6096
|
+
const snapshotPath = path3.join(
|
|
4777
6097
|
options.storage.dir,
|
|
4778
6098
|
"state",
|
|
4779
6099
|
"last_graph_recall.json"
|
|
4780
6100
|
);
|
|
4781
|
-
await mkdir2(
|
|
6101
|
+
await mkdir2(path3.dirname(snapshotPath), { recursive: true });
|
|
4782
6102
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4783
6103
|
const totalSeedCount = options.seedPaths.length;
|
|
4784
6104
|
const totalExpandedCount = options.expandedPaths.length;
|
|
@@ -4812,12 +6132,12 @@ ${r.snippet.trim()}
|
|
|
4812
6132
|
}
|
|
4813
6133
|
async recordLastIntentSnapshot(options) {
|
|
4814
6134
|
try {
|
|
4815
|
-
const snapshotPath =
|
|
6135
|
+
const snapshotPath = path3.join(
|
|
4816
6136
|
options.storage.dir,
|
|
4817
6137
|
"state",
|
|
4818
6138
|
"last_intent.json"
|
|
4819
6139
|
);
|
|
4820
|
-
await mkdir2(
|
|
6140
|
+
await mkdir2(path3.dirname(snapshotPath), { recursive: true });
|
|
4821
6141
|
await writeFile2(
|
|
4822
6142
|
snapshotPath,
|
|
4823
6143
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -4829,12 +6149,12 @@ ${r.snippet.trim()}
|
|
|
4829
6149
|
}
|
|
4830
6150
|
async recordLastQmdRecallSnapshot(options) {
|
|
4831
6151
|
try {
|
|
4832
|
-
const snapshotPath =
|
|
6152
|
+
const snapshotPath = path3.join(
|
|
4833
6153
|
options.storage.dir,
|
|
4834
6154
|
"state",
|
|
4835
6155
|
"last_qmd_recall.json"
|
|
4836
6156
|
);
|
|
4837
|
-
await mkdir2(
|
|
6157
|
+
await mkdir2(path3.dirname(snapshotPath), { recursive: true });
|
|
4838
6158
|
await writeFile2(
|
|
4839
6159
|
snapshotPath,
|
|
4840
6160
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -4849,8 +6169,8 @@ ${r.snippet.trim()}
|
|
|
4849
6169
|
const stateDir = await this.resolveStateDirForNamespace(
|
|
4850
6170
|
options.namespace
|
|
4851
6171
|
);
|
|
4852
|
-
const snapshotPath =
|
|
4853
|
-
await mkdir2(
|
|
6172
|
+
const snapshotPath = path3.join(stateDir, "last_intent.json");
|
|
6173
|
+
await mkdir2(path3.dirname(snapshotPath), { recursive: true });
|
|
4854
6174
|
await writeFile2(
|
|
4855
6175
|
snapshotPath,
|
|
4856
6176
|
JSON.stringify(options.snapshot, null, 2),
|
|
@@ -4862,12 +6182,12 @@ ${r.snippet.trim()}
|
|
|
4862
6182
|
}
|
|
4863
6183
|
async resolveStateDirForNamespace(namespace) {
|
|
4864
6184
|
if (!this.config.namespacesEnabled) {
|
|
4865
|
-
return
|
|
6185
|
+
return path3.join(this.config.memoryDir, "state");
|
|
4866
6186
|
}
|
|
4867
6187
|
if (namespace !== this.config.defaultNamespace) {
|
|
4868
|
-
return
|
|
6188
|
+
return path3.join(this.config.memoryDir, "namespaces", namespace, "state");
|
|
4869
6189
|
}
|
|
4870
|
-
const candidate =
|
|
6190
|
+
const candidate = path3.join(
|
|
4871
6191
|
this.config.memoryDir,
|
|
4872
6192
|
"namespaces",
|
|
4873
6193
|
this.config.defaultNamespace
|
|
@@ -4875,11 +6195,11 @@ ${r.snippet.trim()}
|
|
|
4875
6195
|
try {
|
|
4876
6196
|
const candidateStat = await stat(candidate);
|
|
4877
6197
|
if (candidateStat.isDirectory()) {
|
|
4878
|
-
return
|
|
6198
|
+
return path3.join(candidate, "state");
|
|
4879
6199
|
}
|
|
4880
6200
|
} catch {
|
|
4881
6201
|
}
|
|
4882
|
-
return
|
|
6202
|
+
return path3.join(this.config.memoryDir, "state");
|
|
4883
6203
|
}
|
|
4884
6204
|
buildGraphRecallRankedResults(results, sourceLabelResolver, limit = 64) {
|
|
4885
6205
|
return results.slice(0, limit).map((result) => ({
|
|
@@ -5265,7 +6585,7 @@ ${r.snippet.trim()}
|
|
|
5265
6585
|
const graphExpandedResultPaths = /* @__PURE__ */ new Set();
|
|
5266
6586
|
const graphSourceLabelsForPath = (resultPath) => {
|
|
5267
6587
|
const labels = [];
|
|
5268
|
-
const normalizedPath = resultPath.split(
|
|
6588
|
+
const normalizedPath = resultPath.split(path3.sep).join("/");
|
|
5269
6589
|
const isEntityPath = normalizedPath.startsWith("entities/") || normalizedPath.includes("/entities/");
|
|
5270
6590
|
if (graphBaselinePaths.has(resultPath)) labels.push("baseline");
|
|
5271
6591
|
if (graphExpandedResultPaths.has(resultPath))
|
|
@@ -6580,11 +7900,11 @@ ${formatted}`;
|
|
|
6580
7900
|
if (!this.config.compactionResetEnabled) return null;
|
|
6581
7901
|
const workspaceDir = compactionWorkspaceDir || this.config.workspaceDir || defaultWorkspaceDir();
|
|
6582
7902
|
const safeSessionKey = sanitizeSessionKeyForFilename(effectiveSessionKey);
|
|
6583
|
-
const signalPath =
|
|
7903
|
+
const signalPath = path3.join(
|
|
6584
7904
|
workspaceDir,
|
|
6585
7905
|
`.compaction-reset-signal-${safeSessionKey}`
|
|
6586
7906
|
);
|
|
6587
|
-
const bootPath =
|
|
7907
|
+
const bootPath = path3.join(workspaceDir, "BOOT.md");
|
|
6588
7908
|
try {
|
|
6589
7909
|
const signalStat = await stat(signalPath).catch(() => null);
|
|
6590
7910
|
if (!signalStat) return null;
|
|
@@ -8447,6 +9767,41 @@ _Context: ${topQuestion.context}_`
|
|
|
8447
9767
|
bulkImportWriteNamespace() {
|
|
8448
9768
|
return this.config.defaultNamespace;
|
|
8449
9769
|
}
|
|
9770
|
+
/**
|
|
9771
|
+
* Lazily-constructed wearables service (Limitless / Bee / Omi
|
|
9772
|
+
* transcript ingestion). All wearables surfaces — CLI, MCP tools,
|
|
9773
|
+
* HTTP routes — share this one instance so sync state, search, and
|
|
9774
|
+
* memory writes stay consistent. Writes are pinned to the same
|
|
9775
|
+
* deterministic namespace bulk-import uses.
|
|
9776
|
+
*/
|
|
9777
|
+
getWearablesService() {
|
|
9778
|
+
if (!this.wearablesServiceInstance) {
|
|
9779
|
+
this.wearablesServiceInstance = new WearablesService({
|
|
9780
|
+
config: this.config.wearables,
|
|
9781
|
+
getStorage: async () => await this.getStorageForNamespace(this.bulkImportWriteNamespace()),
|
|
9782
|
+
extract: (turns) => this.extraction.extract(turns),
|
|
9783
|
+
searchBackend: {
|
|
9784
|
+
search: async (query, maxResults) => {
|
|
9785
|
+
if (!this.qmd.isAvailable()) return null;
|
|
9786
|
+
try {
|
|
9787
|
+
const results = await this.qmd.search(query, void 0, maxResults);
|
|
9788
|
+
return results.map((result) => ({
|
|
9789
|
+
path: result.path,
|
|
9790
|
+
score: result.score,
|
|
9791
|
+
preview: result.snippet
|
|
9792
|
+
}));
|
|
9793
|
+
} catch {
|
|
9794
|
+
return null;
|
|
9795
|
+
}
|
|
9796
|
+
}
|
|
9797
|
+
},
|
|
9798
|
+
reindexSearch: async () => {
|
|
9799
|
+
await this.qmd.update();
|
|
9800
|
+
}
|
|
9801
|
+
});
|
|
9802
|
+
}
|
|
9803
|
+
return this.wearablesServiceInstance;
|
|
9804
|
+
}
|
|
8450
9805
|
/**
|
|
8451
9806
|
* Ingest a batch of bulk-import turns (#460). Like ingestReplayBatch, this
|
|
8452
9807
|
* normalizes user/assistant turns into the extraction buffer and awaits
|
|
@@ -9015,7 +10370,7 @@ ${normalized}`).digest("hex");
|
|
|
9015
10370
|
);
|
|
9016
10371
|
this.tierMigrationInFlight = true;
|
|
9017
10372
|
try {
|
|
9018
|
-
const coldStorage = new StorageManager(
|
|
10373
|
+
const coldStorage = new StorageManager(path3.join(storage.dir, "cold"));
|
|
9019
10374
|
const [hotMemories, coldMemories] = await Promise.all([
|
|
9020
10375
|
storage.readAllMemories(),
|
|
9021
10376
|
coldStorage.readAllMemories()
|
|
@@ -10218,7 +11573,7 @@ ${normalized}`).digest("hex");
|
|
|
10218
11573
|
const allMems = allMemsForGraph ?? [];
|
|
10219
11574
|
for (const m of allMems) {
|
|
10220
11575
|
if (m.frontmatter.entityRef === entityRef) {
|
|
10221
|
-
const rel =
|
|
11576
|
+
const rel = path3.relative(storage.dir, m.path);
|
|
10222
11577
|
if (rel !== memoryRelPath) entitySiblings.push(rel);
|
|
10223
11578
|
}
|
|
10224
11579
|
}
|
|
@@ -10518,7 +11873,7 @@ ${normalized}`).digest("hex");
|
|
|
10518
11873
|
}
|
|
10519
11874
|
if (this.config.semanticConsolidationEnabled) {
|
|
10520
11875
|
try {
|
|
10521
|
-
const stateFilePath =
|
|
11876
|
+
const stateFilePath = path3.join(
|
|
10522
11877
|
this.config.memoryDir,
|
|
10523
11878
|
"state",
|
|
10524
11879
|
"semantic-consolidation-last-run.json"
|
|
@@ -10566,7 +11921,7 @@ ${normalized}`).digest("hex");
|
|
|
10566
11921
|
);
|
|
10567
11922
|
}
|
|
10568
11923
|
if (semResult.errors === 0 || semResult.memoriesArchived > 0) {
|
|
10569
|
-
const stateDir =
|
|
11924
|
+
const stateDir = path3.join(this.config.memoryDir, "state");
|
|
10570
11925
|
await mkdir2(stateDir, { recursive: true });
|
|
10571
11926
|
await writeFile2(
|
|
10572
11927
|
stateFilePath,
|
|
@@ -11061,12 +12416,12 @@ ${texts.map((t, i) => `[${i + 1}] ${t}`).join("\n\n")}`;
|
|
|
11061
12416
|
protectedCategories: this.config.lifecycleProtectedCategories
|
|
11062
12417
|
}
|
|
11063
12418
|
};
|
|
11064
|
-
const metricsPath =
|
|
12419
|
+
const metricsPath = path3.join(
|
|
11065
12420
|
storage.dir,
|
|
11066
12421
|
"state",
|
|
11067
12422
|
"lifecycle-metrics.json"
|
|
11068
12423
|
);
|
|
11069
|
-
await mkdir2(
|
|
12424
|
+
await mkdir2(path3.dirname(metricsPath), { recursive: true });
|
|
11070
12425
|
await writeFile2(metricsPath, JSON.stringify(metrics, null, 2), "utf-8");
|
|
11071
12426
|
}
|
|
11072
12427
|
/**
|
|
@@ -11594,7 +12949,7 @@ ${lines.join("\n\n")}`;
|
|
|
11594
12949
|
nsMap = buildMemoryWorthCounterMap(memories);
|
|
11595
12950
|
this.memoryWorthCounterCache.set(ns, { at: nowMs, counters: nsMap });
|
|
11596
12951
|
}
|
|
11597
|
-
for (const [
|
|
12952
|
+
for (const [path4, c] of nsMap) counters.set(path4, c);
|
|
11598
12953
|
} catch (err) {
|
|
11599
12954
|
log.debug("memory-worth: failed to read namespace, skipping", {
|
|
11600
12955
|
namespace: ns,
|
|
@@ -11765,12 +13120,12 @@ ${lines.join("\n\n")}`;
|
|
|
11765
13120
|
*/
|
|
11766
13121
|
semanticDedupScopeFor(targetStorage) {
|
|
11767
13122
|
if (!this.config.namespacesEnabled) return {};
|
|
11768
|
-
const memoryDir =
|
|
11769
|
-
const storageDir =
|
|
13123
|
+
const memoryDir = path3.resolve(this.config.memoryDir);
|
|
13124
|
+
const storageDir = path3.resolve(targetStorage.dir);
|
|
11770
13125
|
if (storageDir === memoryDir) {
|
|
11771
13126
|
return { pathExcludePrefixes: ["namespaces/"] };
|
|
11772
13127
|
}
|
|
11773
|
-
let rel =
|
|
13128
|
+
let rel = path3.relative(memoryDir, storageDir);
|
|
11774
13129
|
if (!rel || rel.startsWith("..")) {
|
|
11775
13130
|
log.debug(
|
|
11776
13131
|
`semantic dedup: target storage dir ${storageDir} is outside memoryDir ${memoryDir}; scoping lookup to absolute path prefix`
|
|
@@ -11789,7 +13144,7 @@ ${lines.join("\n\n")}`;
|
|
|
11789
13144
|
if (hits.length === 0) return [];
|
|
11790
13145
|
const results = [];
|
|
11791
13146
|
for (const hit of hits) {
|
|
11792
|
-
const fullPath =
|
|
13147
|
+
const fullPath = path3.isAbsolute(hit.path) ? hit.path : path3.join(this.config.memoryDir, hit.path);
|
|
11793
13148
|
const memory = await this.storage.readMemoryByPath(fullPath);
|
|
11794
13149
|
if (!memory) continue;
|
|
11795
13150
|
results.push({
|
|
@@ -12432,8 +13787,8 @@ ${lines.join("\n\n")}`;
|
|
|
12432
13787
|
}
|
|
12433
13788
|
namespaceFromStorageDir(storageDir) {
|
|
12434
13789
|
if (!this.config.namespacesEnabled) return this.config.defaultNamespace;
|
|
12435
|
-
const resolvedStorageDir =
|
|
12436
|
-
const resolvedMemoryDir =
|
|
13790
|
+
const resolvedStorageDir = path3.resolve(storageDir);
|
|
13791
|
+
const resolvedMemoryDir = path3.resolve(this.config.memoryDir);
|
|
12437
13792
|
if (resolvedStorageDir === resolvedMemoryDir)
|
|
12438
13793
|
return this.config.defaultNamespace;
|
|
12439
13794
|
const m = resolvedStorageDir.match(/[\\/]namespaces[\\/]([^\\/]+)$/);
|
|
@@ -12473,6 +13828,34 @@ export {
|
|
|
12473
13828
|
saveTaxonomy,
|
|
12474
13829
|
getTaxonomyDir,
|
|
12475
13830
|
getTaxonomyFilePath,
|
|
13831
|
+
memoryStatusForMode,
|
|
13832
|
+
WEARABLE_SOURCE_PREFIX,
|
|
13833
|
+
wearableSourceLabel,
|
|
13834
|
+
wearableDayTag,
|
|
13835
|
+
buildExtractionTurns,
|
|
13836
|
+
generateWearableMemories,
|
|
13837
|
+
writeDailyDigestMemory,
|
|
13838
|
+
importNativeMemories,
|
|
13839
|
+
cleanConversation,
|
|
13840
|
+
stripFillerTokens,
|
|
13841
|
+
collapseImmediateRepeats,
|
|
13842
|
+
isLowQualitySegment,
|
|
13843
|
+
syncStateFilePath,
|
|
13844
|
+
emptySyncState,
|
|
13845
|
+
loadSyncState,
|
|
13846
|
+
saveSyncState,
|
|
13847
|
+
updateSourceSyncState,
|
|
13848
|
+
dateInTimezone,
|
|
13849
|
+
resolveSyncDates,
|
|
13850
|
+
syncWearableSource,
|
|
13851
|
+
defaultTimezone,
|
|
13852
|
+
registerWearableConnector,
|
|
13853
|
+
getWearableConnector,
|
|
13854
|
+
listWearableConnectors,
|
|
13855
|
+
clearWearableConnectors,
|
|
13856
|
+
ensureBuiltInWearableConnectors,
|
|
13857
|
+
WearablesService,
|
|
13858
|
+
locateTranscriptPath,
|
|
12476
13859
|
dedupeEntitySynthesisEvidenceEntries,
|
|
12477
13860
|
defaultWorkspaceDir,
|
|
12478
13861
|
sanitizeSessionKeyForFilename,
|
|
@@ -12502,4 +13885,4 @@ export {
|
|
|
12502
13885
|
resolvePersistedMemoryRelativePath,
|
|
12503
13886
|
Orchestrator
|
|
12504
13887
|
};
|
|
12505
|
-
//# sourceMappingURL=chunk-
|
|
13888
|
+
//# sourceMappingURL=chunk-AZ4RI3QD.js.map
|