@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.
Files changed (261) hide show
  1. package/dist/access-cli.js +18 -16
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +12 -5
  4. package/dist/access-http.js +10 -9
  5. package/dist/access-mcp.d.ts +5 -5
  6. package/dist/access-mcp.js +8 -8
  7. package/dist/access-schema.d.ts +5 -5
  8. package/dist/{access-service-CBNEKjzN.d.ts → access-service-C_sfOHsX.d.ts} +26 -3
  9. package/dist/access-service.d.ts +5 -5
  10. package/dist/access-service.js +7 -7
  11. package/dist/action-confidence.d.ts +1 -1
  12. package/dist/active-memory-bridge.d.ts +1 -1
  13. package/dist/active-recall.d.ts +1 -1
  14. package/dist/active-recall.js +2 -1
  15. package/dist/active-recall.js.map +1 -1
  16. package/dist/behavior-learner.d.ts +1 -1
  17. package/dist/behavior-signals.d.ts +1 -1
  18. package/dist/bootstrap.d.ts +4 -4
  19. package/dist/briefing.d.ts +1 -1
  20. package/dist/briefing.js +3 -3
  21. package/dist/buffer-surprise-report.d.ts +1 -1
  22. package/dist/buffer.d.ts +1 -1
  23. package/dist/calibration.d.ts +1 -1
  24. package/dist/causal-behavior.d.ts +1 -1
  25. package/dist/causal-consolidation.d.ts +1 -1
  26. package/dist/causal-consolidation.js +4 -4
  27. package/dist/{chunk-7TPH6UZL.js → chunk-2RHI3FGV.js} +540 -17
  28. package/dist/chunk-2RHI3FGV.js.map +1 -0
  29. package/dist/{chunk-GYTVOLNX.js → chunk-3MNBW7R7.js} +2 -2
  30. package/dist/{chunk-QFQQFX2H.js → chunk-3R2UZV3U.js} +2 -2
  31. package/dist/{chunk-O4UNM6OR.js → chunk-532VCWYW.js} +2 -2
  32. package/dist/{chunk-2UFQYU5F.js → chunk-57QXN2CS.js} +2 -2
  33. package/dist/chunk-7WV3F5DQ.js +22 -0
  34. package/dist/chunk-7WV3F5DQ.js.map +1 -0
  35. package/dist/{chunk-RKW6QR7W.js → chunk-AZ4RI3QD.js} +1461 -78
  36. package/dist/chunk-AZ4RI3QD.js.map +1 -0
  37. package/dist/{chunk-KQFQ3IS5.js → chunk-F3FY3D3S.js} +43 -7
  38. package/dist/chunk-F3FY3D3S.js.map +1 -0
  39. package/dist/{chunk-4R4KTDIE.js → chunk-FPNQF475.js} +1 -1
  40. package/dist/chunk-FPNQF475.js.map +1 -0
  41. package/dist/{chunk-UGEBPVNI.js → chunk-GE7Q7KXP.js} +2 -2
  42. package/dist/{chunk-GLWW3EJQ.js → chunk-KB4MFBF5.js} +3 -3
  43. package/dist/{chunk-5GOMXHLC.js → chunk-KKTXCFD7.js} +255 -1
  44. package/dist/chunk-KKTXCFD7.js.map +1 -0
  45. package/dist/{chunk-FH3PPO42.js → chunk-KVFYTRMV.js} +2 -2
  46. package/dist/{chunk-BNW5NJJH.js → chunk-LQYTQCXM.js} +2 -2
  47. package/dist/{chunk-AYHXQR53.js → chunk-MVQN73GT.js} +2 -2
  48. package/dist/{chunk-ZZPIJPPD.js → chunk-N5RGXWLQ.js} +2 -2
  49. package/dist/chunk-NDAH7BJ5.js +213 -0
  50. package/dist/chunk-NDAH7BJ5.js.map +1 -0
  51. package/dist/{chunk-R3OQGYOU.js → chunk-P2D2MM47.js} +2 -2
  52. package/dist/{chunk-PSUB67YB.js → chunk-PW6GURU3.js} +2 -2
  53. package/dist/{chunk-W3BKVM64.js → chunk-QDV6VAD4.js} +2 -2
  54. package/dist/{chunk-3QSU4NFF.js → chunk-QHXW3LZV.js} +3 -3
  55. package/dist/{chunk-I6UCUHLK.js → chunk-SHV5Y2WU.js} +182 -3
  56. package/dist/chunk-SHV5Y2WU.js.map +1 -0
  57. package/dist/{chunk-OZXVGYGZ.js → chunk-STDAAGH7.js} +2 -2
  58. package/dist/{chunk-FMGWXIES.js → chunk-TZDSNIRO.js} +5 -5
  59. package/dist/{chunk-2L54V4ZO.js → chunk-UELS6WWF.js} +2 -2
  60. package/dist/{chunk-PJGB7XRR.js → chunk-UGHUNQ74.js} +502 -134
  61. package/dist/chunk-UGHUNQ74.js.map +1 -0
  62. package/dist/{chunk-FG76RDVI.js → chunk-Y3TMFC6I.js} +136 -4
  63. package/dist/chunk-Y3TMFC6I.js.map +1 -0
  64. package/dist/{chunk-BPSGLMQ4.js → chunk-YQNADJCT.js} +2 -2
  65. package/dist/{cli-Cw729yLf.d.ts → cli-EZv6YE6_.d.ts} +3 -3
  66. package/dist/cli.d.ts +6 -6
  67. package/dist/cli.js +23 -21
  68. package/dist/compounding/engine.d.ts +1 -1
  69. package/dist/compounding/engine.js +3 -3
  70. package/dist/compounding/preference-consolidator.d.ts +1 -1
  71. package/dist/compression-optimizer.d.ts +1 -1
  72. package/dist/config.d.ts +1 -1
  73. package/dist/config.js +2 -1
  74. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  75. package/dist/connectors/codex-materialize-runner.js +3 -3
  76. package/dist/connectors/codex-materialize.d.ts +1 -1
  77. package/dist/connectors/index.d.ts +1 -1
  78. package/dist/connectors/index.js +3 -3
  79. package/dist/consolidation-provenance-check.d.ts +1 -1
  80. package/dist/consolidation-undo.d.ts +1 -1
  81. package/dist/contradiction/index.d.ts +2 -2
  82. package/dist/conversation-index/backend.d.ts +1 -1
  83. package/dist/conversation-index/chunker.d.ts +1 -1
  84. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  85. package/dist/conversation-index/indexer.d.ts +1 -1
  86. package/dist/conversation-index/search.d.ts +1 -1
  87. package/dist/day-summary.d.ts +1 -1
  88. package/dist/delinearize.d.ts +1 -1
  89. package/dist/direct-answer-wiring.d.ts +1 -1
  90. package/dist/direct-answer.d.ts +1 -1
  91. package/dist/embedding-fallback.d.ts +1 -1
  92. package/dist/enrichment/index.d.ts +1 -1
  93. package/dist/entity-retrieval.d.ts +1 -1
  94. package/dist/entity-retrieval.js +3 -3
  95. package/dist/entity-schema.d.ts +1 -1
  96. package/dist/explicit-capture.d.ts +4 -4
  97. package/dist/extraction-judge-telemetry.d.ts +1 -1
  98. package/dist/extraction-judge-training.d.ts +1 -1
  99. package/dist/extraction-judge.d.ts +1 -1
  100. package/dist/extraction.d.ts +1 -1
  101. package/dist/fallback-llm.d.ts +1 -1
  102. package/dist/identity-continuity.d.ts +1 -1
  103. package/dist/importance.d.ts +1 -1
  104. package/dist/index.d.ts +307 -9
  105. package/dist/index.js +155 -29
  106. package/dist/index.js.map +1 -1
  107. package/dist/intent.d.ts +1 -1
  108. package/dist/lcm/engine.d.ts +1 -1
  109. package/dist/lcm/index.d.ts +1 -1
  110. package/dist/lcm/tools.d.ts +1 -1
  111. package/dist/lifecycle.d.ts +1 -1
  112. package/dist/live-connectors-runner.d.ts +1 -1
  113. package/dist/local-llm.d.ts +1 -1
  114. package/dist/maintenance/memory-governance.d.ts +1 -1
  115. package/dist/maintenance/memory-governance.js +3 -3
  116. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  117. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  118. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  119. package/dist/memory-action-policy.d.ts +1 -1
  120. package/dist/memory-cache.d.ts +1 -1
  121. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  122. package/dist/memory-projection-store.d.ts +1 -1
  123. package/dist/memory-provenance.d.ts +1 -1
  124. package/dist/memory-worth-outcomes.d.ts +1 -1
  125. package/dist/models-json.d.ts +1 -1
  126. package/dist/namespaces/migrate.d.ts +1 -1
  127. package/dist/namespaces/migrate.js +4 -4
  128. package/dist/namespaces/principal.d.ts +1 -1
  129. package/dist/namespaces/search.d.ts +1 -1
  130. package/dist/namespaces/storage.d.ts +1 -1
  131. package/dist/namespaces/storage.js +3 -3
  132. package/dist/native-knowledge.d.ts +1 -1
  133. package/dist/operator-toolkit.d.ts +1 -1
  134. package/dist/operator-toolkit.js +8 -7
  135. package/dist/{orchestrator-CqWOjfgl.d.ts → orchestrator-CEycaY3M.d.ts} +361 -4
  136. package/dist/orchestrator.d.ts +4 -4
  137. package/dist/orchestrator.js +13 -11
  138. package/dist/patterns-cli.d.ts +1 -1
  139. package/dist/policy-runtime.d.ts +1 -1
  140. package/dist/qmd-recall-cache.d.ts +1 -1
  141. package/dist/qmd.d.ts +1 -1
  142. package/dist/recall-disclosure-escalation.d.ts +1 -1
  143. package/dist/recall-explain-renderer.d.ts +1 -1
  144. package/dist/recall-explain-renderer.js +3 -3
  145. package/dist/recall-planner-llm.d.ts +1 -1
  146. package/dist/recall-state.d.ts +1 -1
  147. package/dist/recall-tag-filter.d.ts +1 -1
  148. package/dist/recall-xray-cli.d.ts +1 -1
  149. package/dist/recall-xray-cli.js +4 -4
  150. package/dist/recall-xray-renderer.d.ts +1 -1
  151. package/dist/recall-xray-renderer.js +3 -3
  152. package/dist/recall-xray.d.ts +1 -1
  153. package/dist/recall-xray.js +2 -2
  154. package/dist/resolve-auth-token.d.ts +1 -1
  155. package/dist/resume-bundles.js +3 -2
  156. package/dist/retrieval-agents.d.ts +1 -1
  157. package/dist/retrieval-tiers.d.ts +1 -1
  158. package/dist/routing/engine.d.ts +1 -1
  159. package/dist/routing/store.d.ts +1 -1
  160. package/dist/schemas.d.ts +10 -10
  161. package/dist/search/embed-helper.d.ts +1 -1
  162. package/dist/search/factory.d.ts +1 -1
  163. package/dist/search/index.d.ts +1 -1
  164. package/dist/search/lancedb-backend.d.ts +1 -1
  165. package/dist/search/meilisearch-backend.d.ts +1 -1
  166. package/dist/search/noop-backend.d.ts +1 -1
  167. package/dist/search/orama-backend.d.ts +1 -1
  168. package/dist/search/port.d.ts +1 -1
  169. package/dist/search/remote-backend.d.ts +1 -1
  170. package/dist/{semantic-SLAa_prH.d.ts → semantic-DJR8_DMQ.d.ts} +1 -1
  171. package/dist/{semantic-consolidation-4HkHWgeI.d.ts → semantic-consolidation-FbhPeJjB.d.ts} +1 -1
  172. package/dist/semantic-consolidation.d.ts +2 -2
  173. package/dist/semantic-consolidation.js +4 -4
  174. package/dist/semantic-rule-promotion.js +3 -3
  175. package/dist/semantic-rule-verifier.d.ts +1 -1
  176. package/dist/semantic-rule-verifier.js +3 -3
  177. package/dist/session-observer-bands.d.ts +1 -1
  178. package/dist/session-observer-state.d.ts +1 -1
  179. package/dist/shared-context/manager.d.ts +5 -5
  180. package/dist/signal.d.ts +1 -1
  181. package/dist/storage.d.ts +19 -1
  182. package/dist/storage.js +2 -2
  183. package/dist/summarizer.d.ts +1 -1
  184. package/dist/summary-snapshot.d.ts +1 -1
  185. package/dist/temporal-supersession.d.ts +1 -1
  186. package/dist/temporal-validity.d.ts +1 -1
  187. package/dist/threading.d.ts +1 -1
  188. package/dist/tier-migration.d.ts +1 -1
  189. package/dist/tier-routing.d.ts +1 -1
  190. package/dist/topics.d.ts +1 -1
  191. package/dist/transcript.d.ts +1 -1
  192. package/dist/types-D5VRAI04.d.ts +3134 -0
  193. package/dist/types.d.ts +3 -2862
  194. package/dist/types.js +1 -1
  195. package/dist/utility-runtime.d.ts +1 -1
  196. package/dist/verified-recall.js +3 -3
  197. package/package.json +1 -1
  198. package/src/access-http.ts +167 -0
  199. package/src/access-mcp.ts +198 -0
  200. package/src/access-service.ts +65 -0
  201. package/src/cli.ts +187 -0
  202. package/src/config.ts +7 -0
  203. package/src/index.ts +7 -0
  204. package/src/orchestrator.ts +42 -0
  205. package/src/storage.ts +106 -0
  206. package/src/types.ts +5 -0
  207. package/src/wearables/cleanup.test.ts +134 -0
  208. package/src/wearables/cleanup.ts +188 -0
  209. package/src/wearables/cli.test.ts +170 -0
  210. package/src/wearables/cli.ts +441 -0
  211. package/src/wearables/config.test.ts +143 -0
  212. package/src/wearables/config.ts +332 -0
  213. package/src/wearables/corrections.test.ts +118 -0
  214. package/src/wearables/corrections.ts +211 -0
  215. package/src/wearables/day-store.test.ts +143 -0
  216. package/src/wearables/day-store.ts +238 -0
  217. package/src/wearables/errors.test.ts +32 -0
  218. package/src/wearables/errors.ts +29 -0
  219. package/src/wearables/index.ts +114 -0
  220. package/src/wearables/memory-gen.test.ts +342 -0
  221. package/src/wearables/memory-gen.ts +413 -0
  222. package/src/wearables/pipeline.test.ts +608 -0
  223. package/src/wearables/pipeline.ts +519 -0
  224. package/src/wearables/redaction.test.ts +94 -0
  225. package/src/wearables/redaction.ts +156 -0
  226. package/src/wearables/registry.test.ts +62 -0
  227. package/src/wearables/registry.ts +133 -0
  228. package/src/wearables/service.test.ts +425 -0
  229. package/src/wearables/service.ts +691 -0
  230. package/src/wearables/speakers.test.ts +110 -0
  231. package/src/wearables/speakers.ts +174 -0
  232. package/src/wearables/storage-io.test.ts +105 -0
  233. package/src/wearables/sync-state.test.ts +134 -0
  234. package/src/wearables/sync-state.ts +186 -0
  235. package/src/wearables/types.ts +285 -0
  236. package/dist/chunk-4R4KTDIE.js.map +0 -1
  237. package/dist/chunk-5GOMXHLC.js.map +0 -1
  238. package/dist/chunk-7TPH6UZL.js.map +0 -1
  239. package/dist/chunk-FG76RDVI.js.map +0 -1
  240. package/dist/chunk-I6UCUHLK.js.map +0 -1
  241. package/dist/chunk-KQFQ3IS5.js.map +0 -1
  242. package/dist/chunk-PJGB7XRR.js.map +0 -1
  243. package/dist/chunk-RKW6QR7W.js.map +0 -1
  244. /package/dist/{chunk-GYTVOLNX.js.map → chunk-3MNBW7R7.js.map} +0 -0
  245. /package/dist/{chunk-QFQQFX2H.js.map → chunk-3R2UZV3U.js.map} +0 -0
  246. /package/dist/{chunk-O4UNM6OR.js.map → chunk-532VCWYW.js.map} +0 -0
  247. /package/dist/{chunk-2UFQYU5F.js.map → chunk-57QXN2CS.js.map} +0 -0
  248. /package/dist/{chunk-UGEBPVNI.js.map → chunk-GE7Q7KXP.js.map} +0 -0
  249. /package/dist/{chunk-GLWW3EJQ.js.map → chunk-KB4MFBF5.js.map} +0 -0
  250. /package/dist/{chunk-FH3PPO42.js.map → chunk-KVFYTRMV.js.map} +0 -0
  251. /package/dist/{chunk-BNW5NJJH.js.map → chunk-LQYTQCXM.js.map} +0 -0
  252. /package/dist/{chunk-AYHXQR53.js.map → chunk-MVQN73GT.js.map} +0 -0
  253. /package/dist/{chunk-ZZPIJPPD.js.map → chunk-N5RGXWLQ.js.map} +0 -0
  254. /package/dist/{chunk-R3OQGYOU.js.map → chunk-P2D2MM47.js.map} +0 -0
  255. /package/dist/{chunk-PSUB67YB.js.map → chunk-PW6GURU3.js.map} +0 -0
  256. /package/dist/{chunk-W3BKVM64.js.map → chunk-QDV6VAD4.js.map} +0 -0
  257. /package/dist/{chunk-3QSU4NFF.js.map → chunk-QHXW3LZV.js.map} +0 -0
  258. /package/dist/{chunk-OZXVGYGZ.js.map → chunk-STDAAGH7.js.map} +0 -0
  259. /package/dist/{chunk-FMGWXIES.js.map → chunk-TZDSNIRO.js.map} +0 -0
  260. /package/dist/{chunk-2L54V4ZO.js.map → chunk-UELS6WWF.js.map} +0 -0
  261. /package/dist/{chunk-BPSGLMQ4.js.map → chunk-YQNADJCT.js.map} +0 -0
@@ -0,0 +1,425 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import * as path from "node:path";
5
+ import { test } from "node:test";
6
+
7
+ import {
8
+ composeDayTranscriptBody,
9
+ composeDayTranscriptMeta,
10
+ serializeDayTranscript,
11
+ } from "./day-store.js";
12
+ import { emptySpeakerRegistry } from "./speakers.js";
13
+ import {
14
+ createWearableMemoryWriter,
15
+ locateTranscriptPath,
16
+ WearablesService,
17
+ type WearableStorageIo,
18
+ } from "./service.js";
19
+ import { defaultWearablesConfig, defaultWearableSourceSettings } from "./config.js";
20
+ import type { WearableConversation, WearablesConfig } from "./types.js";
21
+
22
+ function makeStorage(memoryDir: string): WearableStorageIo & {
23
+ files: Map<string, string>;
24
+ memories: Array<{
25
+ path: string;
26
+ frontmatter: {
27
+ id: string;
28
+ source: string;
29
+ created: string;
30
+ tags: string[];
31
+ status?: string;
32
+ structuredAttributes?: Record<string, string>;
33
+ };
34
+ content: string;
35
+ }>;
36
+ } {
37
+ const files = new Map<string, string>();
38
+ const storage = {
39
+ dir: memoryDir,
40
+ files,
41
+ memories: [] as Array<{
42
+ path: string;
43
+ frontmatter: {
44
+ id: string;
45
+ source: string;
46
+ created: string;
47
+ tags: string[];
48
+ status?: string;
49
+ structuredAttributes?: Record<string, string>;
50
+ };
51
+ content: string;
52
+ }>,
53
+ async writeWearableDayTranscript(sourceId: string, date: string, serialized: string) {
54
+ files.set(`${sourceId}/${date}`, serialized);
55
+ },
56
+ async readWearableDayTranscript(sourceId: string, date: string) {
57
+ return files.get(`${sourceId}/${date}`) ?? null;
58
+ },
59
+ async listWearableTranscriptDays(sourceId?: string) {
60
+ return [...files.keys()]
61
+ .map((key) => {
62
+ const [source, date] = key.split("/");
63
+ return { source, date };
64
+ })
65
+ .filter((entry) => sourceId === undefined || entry.source === sourceId)
66
+ .sort((a, b) => (a.date > b.date ? -1 : a.date < b.date ? 1 : 0));
67
+ },
68
+ async readAllMemories() {
69
+ return storage.memories;
70
+ },
71
+ async writeMemory() {
72
+ return "mem-1";
73
+ },
74
+ async hasFactContentHash() {
75
+ return false;
76
+ },
77
+ };
78
+ return storage;
79
+ }
80
+
81
+ function storeDay(
82
+ storage: ReturnType<typeof makeStorage>,
83
+ sourceId: string,
84
+ date: string,
85
+ texts: string[],
86
+ ): void {
87
+ const registry = emptySpeakerRegistry();
88
+ const conversations: WearableConversation[] = [
89
+ {
90
+ id: `${sourceId}-${date}`,
91
+ source: sourceId,
92
+ title: "Stored conversation",
93
+ startIso: `${date}T10:00:00.000Z`,
94
+ endIso: `${date}T10:30:00.000Z`,
95
+ segments: texts.map((text, index) => ({
96
+ speakerKey: index % 2 === 0 ? "user" : "guest",
97
+ isWearer: index % 2 === 0,
98
+ text,
99
+ })),
100
+ },
101
+ ];
102
+ const body = composeDayTranscriptBody(sourceId, date, "UTC", conversations, registry);
103
+ const meta = composeDayTranscriptMeta(
104
+ sourceId,
105
+ date,
106
+ "UTC",
107
+ conversations,
108
+ registry,
109
+ body,
110
+ "2026-06-11T01:00:00.000Z",
111
+ );
112
+ storage.files.set(`${sourceId}/${date}`, serializeDayTranscript(meta, body));
113
+ }
114
+
115
+ function makeService(
116
+ storage: WearableStorageIo,
117
+ configOverrides: Partial<WearablesConfig> = {},
118
+ ): WearablesService {
119
+ return new WearablesService({
120
+ config: { ...defaultWearablesConfig(), enabled: true, ...configOverrides },
121
+ getStorage: async () => storage,
122
+ extract: null,
123
+ searchBackend: null,
124
+ });
125
+ }
126
+
127
+ test("locateTranscriptPath maps index hits back to source/date", () => {
128
+ assert.deepEqual(
129
+ locateTranscriptPath("/memory/wearables/limitless/2026-06-10.md"),
130
+ { source: "limitless", date: "2026-06-10" },
131
+ );
132
+ assert.deepEqual(
133
+ locateTranscriptPath("wearables\\bee\\2026-06-10.md"),
134
+ { source: "bee", date: "2026-06-10" },
135
+ );
136
+ assert.equal(locateTranscriptPath("/memory/facts/2026/06/10/fact-1.md"), null);
137
+ assert.equal(locateTranscriptPath("/memory/wearables/limitless/2026-13-40.md"), null);
138
+ });
139
+
140
+ test("dayTranscript returns all sources for a day with overlap hints", async () => {
141
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
142
+ try {
143
+ const storage = makeStorage(dir);
144
+ storeDay(storage, "limitless", "2026-06-10", ["Morning planning talk about the launch."]);
145
+ storeDay(storage, "bee", "2026-06-10", ["Same day captured by the bracelet too."]);
146
+ const service = makeService(storage);
147
+ const views = await service.dayTranscript("2026-06-10");
148
+ assert.equal(views.length, 2);
149
+ const limitless = views.find((view) => view.source === "limitless");
150
+ assert.ok(limitless);
151
+ assert.deepEqual(limitless.overlapsWith, ["bee"]);
152
+ assert.match(limitless.body, /Morning planning talk/);
153
+
154
+ const scoped = await service.dayTranscript("2026-06-10", "bee");
155
+ assert.equal(scoped.length, 1);
156
+ assert.deepEqual(scoped[0].overlapsWith, []);
157
+
158
+ await assert.rejects(service.dayTranscript("junk"), /invalid date/);
159
+ } finally {
160
+ rmSync(dir, { recursive: true, force: true });
161
+ }
162
+ });
163
+
164
+ test("searchTranscripts falls back to a bounded scan and scopes by source/date", async () => {
165
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
166
+ try {
167
+ const storage = makeStorage(dir);
168
+ storeDay(storage, "limitless", "2026-06-09", ["We discussed the solar panel quote."]);
169
+ storeDay(storage, "limitless", "2026-06-10", ["Talked about the solar warranty terms."]);
170
+ storeDay(storage, "bee", "2026-06-10", ["Solar again, captured by bee."]);
171
+ const service = makeService(storage);
172
+
173
+ const all = await service.searchTranscripts("solar");
174
+ assert.equal(all.length, 3);
175
+ assert.ok(all.every((result) => result.backend === "scan"));
176
+
177
+ const scoped = await service.searchTranscripts("solar", {
178
+ source: "limitless",
179
+ from: "2026-06-10",
180
+ });
181
+ assert.equal(scoped.length, 1);
182
+ assert.equal(scoped[0].date, "2026-06-10");
183
+ assert.match(scoped[0].snippet, /warranty/);
184
+
185
+ await assert.rejects(service.searchTranscripts(" "), /non-empty/);
186
+ await assert.rejects(service.searchTranscripts("x", { from: "junk" }), /invalid from/);
187
+ await assert.rejects(service.searchTranscripts("x", { limit: 0 }), /invalid limit/);
188
+ } finally {
189
+ rmSync(dir, { recursive: true, force: true });
190
+ }
191
+ });
192
+
193
+ test("searchTranscripts prefers the indexed backend and filters hits to transcripts", async () => {
194
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
195
+ try {
196
+ const storage = makeStorage(dir);
197
+ storeDay(storage, "limitless", "2026-06-10", ["Indexed content."]);
198
+ const service = new WearablesService({
199
+ config: { ...defaultWearablesConfig(), enabled: true },
200
+ getStorage: async () => storage,
201
+ extract: null,
202
+ searchBackend: {
203
+ async search() {
204
+ return [
205
+ { path: "/memory/wearables/limitless/2026-06-10.md", score: 0.9, preview: "Indexed content." },
206
+ { path: "/memory/facts/2026/06/10/fact-1.md", score: 0.8, preview: "A fact, not a transcript." },
207
+ ];
208
+ },
209
+ },
210
+ });
211
+ const results = await service.searchTranscripts("indexed");
212
+ assert.equal(results.length, 1);
213
+ assert.equal(results[0].backend, "indexed");
214
+ assert.equal(results[0].source, "limitless");
215
+ } finally {
216
+ rmSync(dir, { recursive: true, force: true });
217
+ }
218
+ });
219
+
220
+ test("zero in-scope indexed hits fall back to the bounded scan", async () => {
221
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
222
+ try {
223
+ const storage = makeStorage(dir);
224
+ storeDay(storage, "limitless", "2026-06-10", ["The solar quote came in under budget."]);
225
+ const service = new WearablesService({
226
+ config: { ...defaultWearablesConfig(), enabled: true },
227
+ getStorage: async () => storage,
228
+ extract: null,
229
+ searchBackend: {
230
+ async search() {
231
+ // The index returned hits, but they're all ordinary memory
232
+ // files — transcripts were crowded out of the top results.
233
+ return [
234
+ { path: "/memory/facts/2026/06/10/fact-1.md", score: 0.9, preview: "solar memory" },
235
+ { path: "/memory/facts/2026/06/10/fact-2.md", score: 0.8, preview: "solar memory 2" },
236
+ ];
237
+ },
238
+ },
239
+ });
240
+ const results = await service.searchTranscripts("solar");
241
+ assert.equal(results.length, 1);
242
+ assert.equal(results[0].backend, "scan");
243
+ assert.equal(results[0].source, "limitless");
244
+ } finally {
245
+ rmSync(dir, { recursive: true, force: true });
246
+ }
247
+ });
248
+
249
+ test("transcriptMemories filters by wearable source and day", async () => {
250
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
251
+ try {
252
+ const storage = makeStorage(dir);
253
+ storage.memories.push(
254
+ {
255
+ path: "facts/a.md",
256
+ frontmatter: {
257
+ id: "fact-1",
258
+ source: "wearable:limitless",
259
+ created: "2026-06-10T16:00:00.000Z",
260
+ tags: ["wearable"],
261
+ status: "pending_review",
262
+ structuredAttributes: {
263
+ wearableSource: "limitless",
264
+ wearableDate: "2026-06-10",
265
+ wearableConversationId: "c1",
266
+ },
267
+ },
268
+ content: "Launch moved to September 12.",
269
+ },
270
+ {
271
+ path: "facts/b.md",
272
+ frontmatter: {
273
+ id: "fact-2",
274
+ source: "wearable:bee:native",
275
+ created: "2026-06-09T16:00:00.000Z",
276
+ tags: ["wearable"],
277
+ structuredAttributes: { wearableSource: "bee", wearableNativeId: "n1" },
278
+ },
279
+ content: "Provider-extracted fact.",
280
+ },
281
+ {
282
+ path: "facts/c.md",
283
+ frontmatter: {
284
+ id: "fact-3",
285
+ source: "extraction",
286
+ created: "2026-06-10T10:00:00.000Z",
287
+ tags: [],
288
+ },
289
+ content: "Ordinary live-session memory.",
290
+ },
291
+ );
292
+ const service = makeService(storage);
293
+
294
+ const all = await service.transcriptMemories();
295
+ assert.deepEqual(
296
+ all.map((memory) => memory.id),
297
+ ["fact-1", "fact-2"],
298
+ "only wearable-derived memories, newest first",
299
+ );
300
+
301
+ const limitlessOnly = await service.transcriptMemories({ source: "limitless" });
302
+ assert.deepEqual(limitlessOnly.map((memory) => memory.id), ["fact-1"]);
303
+
304
+ const beeOnly = await service.transcriptMemories({ source: "bee" });
305
+ assert.deepEqual(beeOnly.map((memory) => memory.id), ["fact-2"]);
306
+
307
+ const byDay = await service.transcriptMemories({ date: "2026-06-10" });
308
+ assert.deepEqual(byDay.map((memory) => memory.id), ["fact-1"]);
309
+
310
+ await assert.rejects(service.transcriptMemories({ date: "junk" }), /invalid date/);
311
+ } finally {
312
+ rmSync(dir, { recursive: true, force: true });
313
+ }
314
+ });
315
+
316
+ test("the wearable memory writer dedups non-fact categories by content scan", async () => {
317
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
318
+ try {
319
+ const storage = makeStorage(dir);
320
+ storage.memories.push({
321
+ path: "facts/digest.md",
322
+ frontmatter: {
323
+ id: "moment-1",
324
+ source: "wearable:limitless",
325
+ created: "2026-06-10T16:00:00.000Z",
326
+ tags: ["wearable", "daily-digest"],
327
+ structuredAttributes: { wearableSource: "limitless", wearableDate: "2026-06-10" },
328
+ },
329
+ content: "Wearable day digest — limitless, 2026-06-10: 2 recorded conversations.",
330
+ });
331
+ const writer = createWearableMemoryWriter(storage);
332
+ // The fact hash index (always false in this fake) misses moments —
333
+ // the wearable-scoped content scan must catch the duplicate.
334
+ assert.equal(
335
+ await writer.hasFactContentHash(
336
+ "Wearable day digest — limitless, 2026-06-10: 2 recorded conversations.",
337
+ ),
338
+ true,
339
+ );
340
+ assert.equal(await writer.hasFactContentHash("Novel digest content."), false);
341
+ } finally {
342
+ rmSync(dir, { recursive: true, force: true });
343
+ }
344
+ });
345
+
346
+ test("malformed source ids reject as input errors before storage reads", async () => {
347
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
348
+ try {
349
+ const service = makeService(makeStorage(dir));
350
+ await assert.rejects(service.dayTranscript("2026-06-10", "../x"), /invalid source id/);
351
+ await assert.rejects(service.listDays("Bad Source"), /invalid source id/);
352
+ await assert.rejects(
353
+ service.searchTranscripts("solar", { source: "../escape" }),
354
+ /invalid source id/,
355
+ );
356
+ await assert.rejects(
357
+ service.transcriptMemories({ source: " " }),
358
+ /invalid source id/,
359
+ );
360
+ await assert.rejects(service.sync({ source: "../escape" }), /invalid source id/);
361
+ await assert.rejects(service.checkAuth("../escape"), /invalid source id/);
362
+ } finally {
363
+ rmSync(dir, { recursive: true, force: true });
364
+ }
365
+ });
366
+
367
+ test("sync validates source selection before touching connectors", async () => {
368
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
369
+ try {
370
+ const storage = makeStorage(dir);
371
+ const service = makeService(storage, {
372
+ sources: {
373
+ limitless: { ...defaultWearableSourceSettings(), enabled: false },
374
+ },
375
+ });
376
+ await assert.rejects(service.sync({ source: "nope" }), /unknown wearable source/);
377
+ await assert.rejects(service.sync({ source: "limitless" }), /disabled/);
378
+ await assert.rejects(service.sync({}), /no wearable sources are enabled/);
379
+
380
+ const disabled = new WearablesService({
381
+ config: { ...defaultWearablesConfig(), enabled: false },
382
+ getStorage: async () => storage,
383
+ extract: null,
384
+ searchBackend: null,
385
+ });
386
+ await assert.rejects(disabled.sync({}), /wearables are not enabled/);
387
+ } finally {
388
+ rmSync(dir, { recursive: true, force: true });
389
+ }
390
+ });
391
+
392
+ test("speaker and correction management round-trips through the service", async () => {
393
+ const dir = mkdtempSync(path.join(tmpdir(), "remnic-service-"));
394
+ try {
395
+ const service = makeService(makeStorage(dir));
396
+
397
+ await service.setSelfName("Jordan");
398
+ await service.setSpeaker("bee", "0", "Jordan", { isSelf: true });
399
+ await service.setSpeaker("limitless", "Speaker 2", "Alex Sample");
400
+ let registry = await service.listSpeakers();
401
+ assert.equal(registry.selfName, "Jordan");
402
+ assert.equal(registry.speakers["limitless:Speaker 2"].name, "Alex Sample");
403
+ assert.equal(registry.speakers["bee:0"].isSelf, true);
404
+
405
+ registry = await service.removeSpeaker("limitless", "Speaker 2");
406
+ assert.equal(registry.speakers["limitless:Speaker 2"], undefined);
407
+ await assert.rejects(service.removeSpeaker("limitless", "Speaker 2"), /no speaker override/);
408
+ await assert.rejects(service.setSpeaker("bee", "1", " "), /non-empty/);
409
+
410
+ await service.addCorrection({ match: "remnick", replace: "Remnic" });
411
+ await assert.rejects(
412
+ service.addCorrection({ match: "remnick", replace: "Remnic" }),
413
+ /identical correction rule/,
414
+ );
415
+ let corrections = await service.listCorrections();
416
+ assert.equal(corrections.fromState.length, 1);
417
+ const removed = await service.removeCorrection(0);
418
+ assert.equal(removed.match, "remnick");
419
+ corrections = await service.listCorrections();
420
+ assert.equal(corrections.fromState.length, 0);
421
+ await assert.rejects(service.removeCorrection(5), /out of range/);
422
+ } finally {
423
+ rmSync(dir, { recursive: true, force: true });
424
+ }
425
+ });