@ai-agent-forge/plugin-memory 0.85.0

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 (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1,149 @@
1
+ import type { CapabilityAPI, JsonValue } from "@agent-forge/plugin-sdk";
2
+ import { type MemoryCandidateMachineV1 } from "../memory/candidates.ts";
3
+ import { createMemoryCompactionSequencer } from "../memory/compaction-sequencer.ts";
4
+ import { type MemoryContinuationIssuerV1 } from "../memory/continuation.ts";
5
+ import { type MemoryCurationAgentV1 } from "../memory/curation.ts";
6
+ import type { MemoryEmbeddingProviderV1 } from "../memory/embedding-provider.ts";
7
+ import type { MemoryRerankerV1 } from "../memory/embedding-reranker.ts";
8
+ import type { MemoryAtomV1, MemoryScopeV1 } from "../memory/foundation.ts";
9
+ import { type MemoryLifecycleManagerV1, type MemoryLifecyclePolicyV1 } from "../memory/lifecycle.ts";
10
+ import { type MemoryPurgeGateV1 } from "../memory/purge.ts";
11
+ import { type MemoryPurgeJournalV1 } from "../memory/purge-journal.ts";
12
+ import { type MemoryRecallAgentV1 } from "../memory/recall-agent.ts";
13
+ import { createMemoryRecallIndex, type MemoryRecallPageV1, type MemoryRecallQueryV1 } from "../memory/recall-index.ts";
14
+ import { type MemorySchedulerV1 } from "../memory/scheduler.ts";
15
+ import { type MemoryStoreV1, type RetentionModeRegistryV1 } from "../memory/store.ts";
16
+ import type { MemoryVectorIndexV1 } from "../memory/vector-index.ts";
17
+ export interface MemoryTestKitOptions {
18
+ readonly now?: () => number;
19
+ readonly retentionRegistry?: RetentionModeRegistryV1;
20
+ readonly lifecyclePolicy?: MemoryLifecyclePolicyV1;
21
+ }
22
+ export interface MemoryTestKitAtomSeed {
23
+ readonly owner?: string;
24
+ readonly scope?: MemoryScopeV1;
25
+ readonly retentionMode?: string;
26
+ readonly memoryId?: string;
27
+ readonly observationId?: string;
28
+ readonly project?: string;
29
+ readonly task?: string;
30
+ readonly scene?: string;
31
+ readonly occurredAt: string;
32
+ readonly payload?: JsonValue;
33
+ }
34
+ export interface MemoryTestKit {
35
+ readonly store: MemoryStoreV1;
36
+ readonly machine: MemoryCandidateMachineV1;
37
+ readonly sequencer: ReturnType<typeof createMemoryCompactionSequencer>;
38
+ readonly index: ReturnType<typeof createMemoryRecallIndex>;
39
+ readonly lifecycle: MemoryLifecycleManagerV1;
40
+ readonly scheduler: MemorySchedulerV1;
41
+ readonly curation: MemoryCurationAgentV1;
42
+ readonly continuation: MemoryContinuationIssuerV1;
43
+ readonly purgeGate: MemoryPurgeGateV1;
44
+ readonly purgeJournal: MemoryPurgeJournalV1;
45
+ readonly recallAgent: MemoryRecallAgentV1;
46
+ /** Builds a validated deterministic atom from a seed. */
47
+ makeAtom(seed: MemoryTestKitAtomSeed): MemoryAtomV1;
48
+ /** Convenience: curation-commit an atom and return its memoryId. */
49
+ commit(atom: MemoryAtomV1): string;
50
+ query(query: MemoryRecallQueryV1): MemoryRecallPageV1;
51
+ }
52
+ export declare function createMemoryTestKit(options?: MemoryTestKitOptions): MemoryTestKit;
53
+ /** Repeats `run` up to `times` and requires every result to deep-equal the first. */
54
+ export declare function repeatMemoryTestKit<T>(times: number, run: () => Promise<T>): Promise<{
55
+ readonly baseline: T;
56
+ readonly repeats: number;
57
+ }>;
58
+ /**
59
+ * In-memory fake of the LanceDB vector index (memory/vector-index.ts 同契约):
60
+ * KNN 平扫平方 L2 (score = 1/(1+距离), 与真组件同口径), FTS 按词元包含匹配
61
+ * (score = 1/(60+rank+1), 与真组件同口径)。确定性: 排序并列按插入序打破。
62
+ */
63
+ export declare function createInMemoryMemoryVectorIndex(options: {
64
+ readonly dimensions: number;
65
+ }): MemoryVectorIndexV1;
66
+ /**
67
+ * Deterministic mock vector components for the capability's vector-channel
68
+ * injection seam: the FNV token-hash mock embedder plus a scripted or
69
+ * lexical-overlap reranker over an in-memory fake index. No network, no real
70
+ * models, fully deterministic.
71
+ */
72
+ export interface MockMemoryVectorComponentsOptions {
73
+ readonly dimensions?: number;
74
+ /** Explicit rerank scoring; default = deterministic lexical-overlap reranker. */
75
+ readonly rerank?: (query: string, docs: readonly string[]) => readonly number[];
76
+ /** true → no reranker at all (精排不可用路径: 跳过精排, RRF 序)。 */
77
+ readonly rerankerUnavailable?: boolean;
78
+ }
79
+ export declare function createMockMemoryVectorComponents(options?: MockMemoryVectorComponentsOptions): {
80
+ readonly embedProvider: MemoryEmbeddingProviderV1;
81
+ readonly reranker: MemoryRerankerV1 | undefined;
82
+ readonly vectorIndex: MemoryVectorIndexV1;
83
+ };
84
+ /** Deterministic reranker mock: score = count of query tokens present in the doc. */
85
+ export declare function createLexicalMockReranker(): MemoryRerankerV1;
86
+ /**
87
+ * 显式向量表 mock 组件 (2.4d6 S3 语义合并测试用): 每条文本的向量由测试直接
88
+ * 指定 (绕过 FNV token hash — 合并阈值的余弦判定需要可控向量), 搭配
89
+ * in-memory fake index 与缺省词元重排器, 零网络零真实模型 (架构宪法测试
90
+ * 门禁)。dimensions 取自向量表首个声明向量; 未声明向量的文本显式抛错 —
91
+ * 测试必须登记每一条会被 embed 的内容与查询 (含召回 query), 保证确定性。
92
+ */
93
+ export declare function createScriptedMockVectorComponents(vectors: {
94
+ readonly [text: string]: readonly number[];
95
+ }): {
96
+ readonly embedProvider: MemoryEmbeddingProviderV1;
97
+ readonly reranker: MemoryRerankerV1 | undefined;
98
+ readonly vectorIndex: MemoryVectorIndexV1;
99
+ };
100
+ /** A loop hook captured from a capability registration (测试侧驱动用). */
101
+ export interface MemoryCapabilityLoopHookCaptureV1 {
102
+ readonly position: string;
103
+ readonly handler: (messages: unknown) => Promise<unknown>;
104
+ }
105
+ /**
106
+ * Minimal capture api for driving `createCapability` / its vector-component
107
+ * test variant at the factory level (mirrors the runtime tool/command surface:
108
+ * registerTool, registerCommand, publish, logger, capabilities, session,
109
+ * lifecycle, registerLoopHook).
110
+ */
111
+ export interface MemoryCapabilityTestApiV1 {
112
+ readonly api: CapabilityAPI;
113
+ /** Registered tool by name (undefined when the capability did not register it). */
114
+ tool(name: string): {
115
+ readonly execute: (first: unknown, second?: unknown) => Promise<unknown>;
116
+ } | undefined;
117
+ /** Registered command by name (undefined when the capability did not register it). */
118
+ command(name: string): {
119
+ readonly execute: (args: string) => Promise<unknown>;
120
+ } | undefined;
121
+ /** logger.warn messages captured for degraded-channel assertions. */
122
+ readonly warnings: readonly string[];
123
+ /** logger.info messages captured for off/enabling declaration assertions. */
124
+ readonly infos: readonly string[];
125
+ /** logger.debug messages captured for bounded-skip assertions (§2.2 配对失败). */
126
+ readonly debugs: readonly string[];
127
+ /** Appends a suite binding entry; resolveSessionDomain reads the LAST one. */
128
+ bindSuite(suiteId: string): void;
129
+ /**
130
+ * Appends one message entry (assistant/user/toolResult shape is the test's
131
+ * responsibility) to the branch; returns the entry id.
132
+ */
133
+ appendMessageEntry(message: unknown): string;
134
+ /** The registered loop hook for a position (undefined when not registered). */
135
+ loopHook(position: string): MemoryCapabilityLoopHookCaptureV1 | undefined;
136
+ /**
137
+ * Dispatches a committed lifecycle event to every registered observer of
138
+ * id@version (test-side stand-in for the host lifecycle dispatcher).
139
+ */
140
+ dispatchLifecycleEvent(event: {
141
+ readonly id: string;
142
+ readonly version?: number;
143
+ readonly data: unknown;
144
+ }): Promise<void>;
145
+ }
146
+ export declare function createMemoryCapabilityTestApi(options?: {
147
+ readonly suiteId?: string;
148
+ }): MemoryCapabilityTestApiV1;
149
+ //# sourceMappingURL=memory-testkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-testkit.d.ts","sourceRoot":"","sources":["../../src/testing/memory-testkit.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Deterministic memory testkit (1C.8a) — wires the 1C.1–1C.7 Foundation pieces\n * (atom validator, store, candidate machine, recall index, lifecycle manager,\n * scheduler, curation, continuation, purge gate/journal) into one reusable,\n * clock-injectable fixture. Acceptance: the 1C.7b closed loop repeats 20 times\n * with identical results through this kit.\n */\nimport { deepStrictEqual } from \"node:assert\";\nimport type {\n\tCapabilityAPI,\n\tJsonValue,\n\tLifecycleEvent,\n\tLifecycleEventDefinition,\n\tLifecycleEventReference,\n\tLifecycleObserveHandler,\n} from \"@agent-forge/plugin-sdk\";\nimport { createMemoryCandidateMachine, type MemoryCandidateMachineV1 } from \"../memory/candidates.ts\";\nimport { createMemoryCompactionSequencer } from \"../memory/compaction-sequencer.ts\";\nimport { createMemoryContinuationIssuer, type MemoryContinuationIssuerV1 } from \"../memory/continuation.ts\";\nimport { createMemoryCurationAgent, type MemoryCurationAgentV1 } from \"../memory/curation.ts\";\nimport type { MemoryEmbeddingProviderV1 } from \"../memory/embedding-provider.ts\";\nimport { createDeterministicMockEmbeddingProvider } from \"../memory/embedding-provider.ts\";\nimport type { MemoryRerankerV1 } from \"../memory/embedding-reranker.ts\";\nimport type { MemoryAtomV1, MemoryScopeV1 } from \"../memory/foundation.ts\";\nimport { buildMemoryAtomV1 } from \"../memory/foundation.ts\";\nimport {\n\tcreateMemoryLifecycleManager,\n\ttype MemoryLifecycleManagerV1,\n\ttype MemoryLifecyclePolicyV1,\n} from \"../memory/lifecycle.ts\";\nimport {\n\tcreateMemoryPurgeGate,\n\tcreateMemoryReplicaRegistry,\n\ttype MemoryPurgeGateV1,\n\ttype MemoryReplicaRegistryV1,\n} from \"../memory/purge.ts\";\nimport { createMemoryPurgeJournal, type MemoryPurgeJournalV1 } from \"../memory/purge-journal.ts\";\nimport { createMemoryRecallAgent, type MemoryRecallAgentV1 } from \"../memory/recall-agent.ts\";\nimport { createMemoryRecallIndex, type MemoryRecallPageV1, type MemoryRecallQueryV1 } from \"../memory/recall-index.ts\";\nimport { createMemoryScheduler, type MemorySchedulerV1 } from \"../memory/scheduler.ts\";\nimport {\n\tcreateFirstPartyRetentionRegistry,\n\tcreateMemoryStore,\n\ttype MemoryStoreV1,\n\ttype RetentionModeRegistryV1,\n} from \"../memory/store.ts\";\nimport type { MemoryVectorIndexEntryV1, MemoryVectorIndexV1 } from \"../memory/vector-index.ts\";\n\nexport interface MemoryTestKitOptions {\n\treadonly now?: () => number;\n\treadonly retentionRegistry?: RetentionModeRegistryV1;\n\treadonly lifecyclePolicy?: MemoryLifecyclePolicyV1;\n}\n\nexport interface MemoryTestKitAtomSeed {\n\treadonly owner?: string;\n\treadonly scope?: MemoryScopeV1;\n\treadonly retentionMode?: string;\n\treadonly memoryId?: string;\n\treadonly observationId?: string;\n\treadonly project?: string;\n\treadonly task?: string;\n\treadonly scene?: string;\n\treadonly occurredAt: string;\n\treadonly payload?: JsonValue;\n}\n\nexport interface MemoryTestKit {\n\treadonly store: MemoryStoreV1;\n\treadonly machine: MemoryCandidateMachineV1;\n\treadonly sequencer: ReturnType<typeof createMemoryCompactionSequencer>;\n\treadonly index: ReturnType<typeof createMemoryRecallIndex>;\n\treadonly lifecycle: MemoryLifecycleManagerV1;\n\treadonly scheduler: MemorySchedulerV1;\n\treadonly curation: MemoryCurationAgentV1;\n\treadonly continuation: MemoryContinuationIssuerV1;\n\treadonly purgeGate: MemoryPurgeGateV1;\n\treadonly purgeJournal: MemoryPurgeJournalV1;\n\treadonly recallAgent: MemoryRecallAgentV1;\n\t/** Builds a validated deterministic atom from a seed. */\n\tmakeAtom(seed: MemoryTestKitAtomSeed): MemoryAtomV1;\n\t/** Convenience: curation-commit an atom and return its memoryId. */\n\tcommit(atom: MemoryAtomV1): string;\n\tquery(query: MemoryRecallQueryV1): MemoryRecallPageV1;\n}\n\nexport function createMemoryTestKit(options: MemoryTestKitOptions = {}): MemoryTestKit {\n\tconst now = options.now ?? (() => Date.now());\n\tconst retentionRegistry = options.retentionRegistry ?? createFirstPartyRetentionRegistry();\n\tconst store = createMemoryStore({ retentionRegistry, now });\n\tconst machine = createMemoryCandidateMachine({ store, now });\n\tconst sequencer = createMemoryCompactionSequencer({ store });\n\tconst index = createMemoryRecallIndex({ store });\n\tconst lifecyclePolicy: MemoryLifecyclePolicyV1 = options.lifecyclePolicy ?? {\n\t\tpolicyVersion: \"lifecycle@1\",\n\t\thalfLifeMs: Object.freeze({\n\t\t\tsession: 1 * 24 * 60 * 60 * 1000,\n\t\t\tcycle: 7 * 24 * 60 * 60 * 1000,\n\t\t\t\"long-term\": 30 * 24 * 60 * 60 * 1000,\n\t\t}),\n\t\tstaleAfterMs: Object.freeze({\n\t\t\tsession: 2 * 24 * 60 * 60 * 1000,\n\t\t\tcycle: 14 * 24 * 60 * 60 * 1000,\n\t\t\t\"long-term\": 60 * 24 * 60 * 60 * 1000,\n\t\t}),\n\t\tarchiveAfterMs: Object.freeze({\n\t\t\tsession: 7 * 24 * 60 * 60 * 1000,\n\t\t\tcycle: 60 * 24 * 60 * 60 * 1000,\n\t\t\t\"long-term\": 180 * 24 * 60 * 60 * 1000,\n\t\t}),\n\t\tbaselineAttention: 0.5,\n\t};\n\tconst lifecycle = createMemoryLifecycleManager({ policy: lifecyclePolicy, now });\n\tconst scheduler = createMemoryScheduler({ now });\n\tconst curation = createMemoryCurationAgent({ store });\n\tconst continuation = createMemoryContinuationIssuer();\n\tconst replicaRegistry: MemoryReplicaRegistryV1 = createMemoryReplicaRegistry();\n\treplicaRegistry.register({\n\t\treplicaId: \"canonical-1\",\n\t\tkind: \"canonical\",\n\t\townerScope: \"*\",\n\t\tcontractVersion: \"agent-forge/memory@1\",\n\t\thealthy: true,\n\t});\n\tconst purgeGate = createMemoryPurgeGate({ registry: replicaRegistry, now });\n\tconst purgeJournal = createMemoryPurgeJournal();\n\tconst recallAgent = createMemoryRecallAgent({\n\t\tscheduler,\n\t\tindex,\n\t\tpolicyDefaults: {\n\t\t\tcandidateBudget: 1000,\n\t\t\tmodelInspectionLimit: 20,\n\t\t\tfinalResultLimit: 10,\n\t\t\tmaxRelationHops: 2,\n\t\t\tmaxModelCalls: 3,\n\t\t\tmaxOutputTokens: 4000,\n\t\t\tmaxOutputBytes: 16_000,\n\t\t\tmaxRounds: 3,\n\t\t},\n\t\thostLimits: {\n\t\t\tcandidateBudget: 5000,\n\t\t\tmodelInspectionLimit: 100,\n\t\t\tfinalResultLimit: 100,\n\t\t\tmaxRelationHops: 5,\n\t\t\tmaxModelCalls: 10,\n\t\t\tmaxOutputTokens: 100_000,\n\t\t\tmaxOutputBytes: 1_000_000,\n\t\t\tmaxRounds: 10,\n\t\t},\n\t\tnow,\n\t});\n\n\tlet sequence = 0;\n\tconst makeAtom = (seed: MemoryTestKitAtomSeed): MemoryAtomV1 => {\n\t\tsequence += 1;\n\t\treturn buildMemoryAtomV1({\n\t\t\tmemoryId: seed.memoryId ?? `mem-${String(sequence).padStart(4, \"0\")}`,\n\t\t\tcontractVersion: \"agent-forge/memory@1\",\n\t\t\tschemaVersion: 1,\n\t\t\tscope: seed.scope ?? \"long-term\",\n\t\t\tretentionMode: seed.retentionMode ?? \"permanent\",\n\t\t\towner: seed.owner ?? \"code-memory\",\n\t\t\tprofileId: \"coding-agent\",\n\t\t\tretentionPolicyVersion: \"retention@1\",\n\t\t\tmemoryKind: \"fact\",\n\t\t\tpayload: seed.payload ?? { sequence },\n\t\t\toccurredAt: seed.occurredAt,\n\t\t\trecordedAt: seed.occurredAt,\n\t\t\tsourceRefs: [{ kind: \"entry\", id: `entry-${sequence}` }],\n\t\t\tobservationId: seed.observationId ?? `obs-${sequence}`,\n\t\t\tsessionRefs: [],\n\t\t\tagentInstanceRefs: [],\n\t\t\tprojectRefs: seed.project ? [seed.project] : [],\n\t\t\tsubjectRefs: seed.task ? [seed.task] : [],\n\t\t\tfacets: [],\n\t\t\trelations: [],\n\t\t\tconfidence: 1,\n\t\t\timportance: 0.5,\n\t\t\tevidenceClass: \"explicit\",\n\t\t\tcontentRevision: `c-${sequence}`,\n\t\t\twriteReason: \"test\",\n\t\t});\n\t};\n\n\tconst commit = (atom: MemoryAtomV1): string => {\n\t\tstore.commit(atom);\n\t\treturn atom.memoryId;\n\t};\n\n\tconst query = (query: MemoryRecallQueryV1): MemoryRecallPageV1 => index.query(query);\n\n\treturn {\n\t\tstore,\n\t\tmachine,\n\t\tsequencer,\n\t\tindex,\n\t\tlifecycle,\n\t\tscheduler,\n\t\tcuration,\n\t\tcontinuation,\n\t\tpurgeGate,\n\t\tpurgeJournal,\n\t\trecallAgent,\n\t\tmakeAtom,\n\t\tcommit,\n\t\tquery,\n\t};\n}\n\n/** Repeats `run` up to `times` and requires every result to deep-equal the first. */\nexport async function repeatMemoryTestKit<T>(\n\ttimes: number,\n\trun: () => Promise<T>,\n): Promise<{ readonly baseline: T; readonly repeats: number }> {\n\tlet baseline: T | undefined;\n\tlet repeats = 0;\n\tfor (let index = 0; index < times; index += 1) {\n\t\tconst result = await run();\n\t\tif (index === 0) {\n\t\t\tbaseline = result;\n\t\t\tcontinue;\n\t\t}\n\t\t// node:assert deep compare keeps the kit free of test-runner imports\n\t\t// (plugin-src boundary gate allows node: builtins only); vitest renders\n\t\t// the AssertionError like any other thrown assertion.\n\t\tdeepStrictEqual(result, baseline);\n\t\trepeats += 1;\n\t}\n\treturn { baseline: baseline as T, repeats };\n}\n\n// ---------------------------------------------------------------------------\n// 向量混合检索 mock 注入点 (架构宪法: 默认测试只允许 deterministic mock AI)。\n// 供 capabilities/memory.ts 的 createCapabilityWithVectorComponents 注入,\n// 不触真实 transformers/lancedb。\n// ---------------------------------------------------------------------------\n\nfunction tokenizeForFakeIndex(text: string): readonly string[] {\n\treturn text\n\t\t.toLowerCase()\n\t\t.split(/[^\\p{L}\\p{N}]+/u)\n\t\t.filter((token) => token !== \"\");\n}\n\n/**\n * In-memory fake of the LanceDB vector index (memory/vector-index.ts 同契约):\n * KNN 平扫平方 L2 (score = 1/(1+距离), 与真组件同口径), FTS 按词元包含匹配\n * (score = 1/(60+rank+1), 与真组件同口径)。确定性: 排序并列按插入序打破。\n */\nexport function createInMemoryMemoryVectorIndex(options: { readonly dimensions: number }): MemoryVectorIndexV1 {\n\tconst dimensions = options.dimensions;\n\tconst rows = new Map<string, MemoryVectorIndexEntryV1>();\n\tconst insertOrder: string[] = [];\n\tconst bodyOf = (entry: MemoryVectorIndexEntryV1): string => `${entry.statement} ${entry.tags.join(\" \")}`;\n\treturn {\n\t\treplicaId: \"memory-vector-index\",\n\t\tupsert: async (entries) => {\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.vector.length !== dimensions) {\n\t\t\t\t\tthrow new Error(`dimension mismatch for ${entry.memoryId}`);\n\t\t\t\t}\n\t\t\t\tif (!rows.has(entry.memoryId)) insertOrder.push(entry.memoryId);\n\t\t\t\trows.set(entry.memoryId, entry);\n\t\t\t}\n\t\t},\n\t\tremove: async (memoryIds) => {\n\t\t\tfor (const memoryId of memoryIds) {\n\t\t\t\trows.delete(memoryId);\n\t\t\t\tconst position = insertOrder.indexOf(memoryId);\n\t\t\t\tif (position >= 0) insertOrder.splice(position, 1);\n\t\t\t}\n\t\t},\n\t\tqueryKnn: async (vector, query) => {\n\t\t\tif (vector.length !== dimensions) throw new Error(\"query vector dimension mismatch\");\n\t\t\tconst hits = insertOrder\n\t\t\t\t.map((memoryId) => {\n\t\t\t\t\tconst entry = rows.get(memoryId);\n\t\t\t\t\tif (entry === undefined || entry.owner !== query.owner) return undefined;\n\t\t\t\t\tlet sumSquares = 0;\n\t\t\t\t\tfor (const [axis, value] of vector.entries()) {\n\t\t\t\t\t\tconst delta = value - (entry.vector[axis] ?? 0);\n\t\t\t\t\t\tsumSquares += delta * delta;\n\t\t\t\t\t}\n\t\t\t\t\treturn { memoryId, score: 1 / (1 + sumSquares) };\n\t\t\t\t})\n\t\t\t\t.filter((hit): hit is { memoryId: string; score: number } => hit !== undefined);\n\t\t\thits.sort((left, right) => right.score - left.score);\n\t\t\treturn hits.slice(0, query.limit);\n\t\t},\n\t\tqueryFts: async (queryText, query) => {\n\t\t\tconst queryTokens = tokenizeForFakeIndex(queryText);\n\t\t\tif (queryTokens.length === 0) return [];\n\t\t\tconst matched = insertOrder\n\t\t\t\t.map((memoryId) => {\n\t\t\t\t\tconst entry = rows.get(memoryId);\n\t\t\t\t\tif (entry === undefined || entry.owner !== query.owner) return undefined;\n\t\t\t\t\tconst bodyTokens = new Set(tokenizeForFakeIndex(bodyOf(entry)));\n\t\t\t\t\tconst hits = queryTokens.filter((token) => bodyTokens.has(token)).length;\n\t\t\t\t\treturn hits > 0 ? { memoryId, hits } : undefined;\n\t\t\t\t})\n\t\t\t\t.filter((hit): hit is { memoryId: string; hits: number } => hit !== undefined);\n\t\t\t// 命中词元数降序, 并列按插入序; 打分与真组件同口径 (RRF_K=60)。\n\t\t\tmatched.sort((left, right) => right.hits - left.hits);\n\t\t\treturn matched\n\t\t\t\t.slice(0, query.limit)\n\t\t\t\t.map((hit, rank) => ({ memoryId: hit.memoryId, score: 1 / (60 + rank + 1) }));\n\t\t},\n\t\tlistMemoryIds: async () => new Set(rows.keys()),\n\t\tcount: async () => rows.size,\n\t\tgetStoredModelId: async () => {\n\t\t\tconst first = insertOrder.find((memoryId) => rows.has(memoryId));\n\t\t\treturn first === undefined ? undefined : rows.get(first)?.modelId;\n\t\t},\n\t\tclose: async () => {},\n\t};\n}\n\n/**\n * Deterministic mock vector components for the capability's vector-channel\n * injection seam: the FNV token-hash mock embedder plus a scripted or\n * lexical-overlap reranker over an in-memory fake index. No network, no real\n * models, fully deterministic.\n */\nexport interface MockMemoryVectorComponentsOptions {\n\treadonly dimensions?: number;\n\t/** Explicit rerank scoring; default = deterministic lexical-overlap reranker. */\n\treadonly rerank?: (query: string, docs: readonly string[]) => readonly number[];\n\t/** true → no reranker at all (精排不可用路径: 跳过精排, RRF 序)。 */\n\treadonly rerankerUnavailable?: boolean;\n}\n\nexport function createMockMemoryVectorComponents(options: MockMemoryVectorComponentsOptions = {}): {\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n} {\n\tconst embedProvider = createDeterministicMockEmbeddingProvider({ dimensions: options.dimensions ?? 8 });\n\tconst vectorIndex = createInMemoryMemoryVectorIndex({ dimensions: embedProvider.dimensions });\n\tconst scriptedRerank = options.rerank;\n\tconst reranker: MemoryRerankerV1 | undefined = options.rerankerUnavailable\n\t\t? undefined\n\t\t: {\n\t\t\t\tmodelId: \"mock-reranker@1\",\n\t\t\t\trerank:\n\t\t\t\t\tscriptedRerank === undefined\n\t\t\t\t\t\t? createLexicalMockReranker().rerank\n\t\t\t\t\t\t: async (query, docs) => scriptedRerank(query, docs),\n\t\t\t};\n\treturn { embedProvider, reranker, vectorIndex };\n}\n\n/** Deterministic reranker mock: score = count of query tokens present in the doc. */\nexport function createLexicalMockReranker(): MemoryRerankerV1 {\n\treturn {\n\t\tmodelId: \"mock-reranker@1\",\n\t\trerank: async (query, docs) => {\n\t\t\tconst queryTokens = new Set(tokenizeForFakeIndex(query));\n\t\t\treturn docs.map((doc) => {\n\t\t\t\tlet overlap = 0;\n\t\t\t\tfor (const token of tokenizeForFakeIndex(doc)) {\n\t\t\t\t\tif (queryTokens.has(token)) overlap += 1;\n\t\t\t\t}\n\t\t\t\treturn overlap;\n\t\t\t});\n\t\t},\n\t};\n}\n\n/**\n * 显式向量表 mock 组件 (2.4d6 S3 语义合并测试用): 每条文本的向量由测试直接\n * 指定 (绕过 FNV token hash — 合并阈值的余弦判定需要可控向量), 搭配\n * in-memory fake index 与缺省词元重排器, 零网络零真实模型 (架构宪法测试\n * 门禁)。dimensions 取自向量表首个声明向量; 未声明向量的文本显式抛错 —\n * 测试必须登记每一条会被 embed 的内容与查询 (含召回 query), 保证确定性。\n */\nexport function createScriptedMockVectorComponents(vectors: { readonly [text: string]: readonly number[] }): {\n\treadonly embedProvider: MemoryEmbeddingProviderV1;\n\treadonly reranker: MemoryRerankerV1 | undefined;\n\treadonly vectorIndex: MemoryVectorIndexV1;\n} {\n\tconst table = new Map<string, readonly number[]>(Object.entries(vectors));\n\tconst firstVector = table.values().next();\n\tif (firstVector.done || firstVector.value.length === 0) {\n\t\tthrow new Error(\"createScriptedMockVectorComponents requires at least one declared vector\");\n\t}\n\tconst dimensions = firstVector.value.length;\n\tconst embedProvider: MemoryEmbeddingProviderV1 = {\n\t\tmodelId: \"mock-embedding-scripted@1\",\n\t\tdimensions,\n\t\tembed: async (texts) =>\n\t\t\ttexts.map((text) => {\n\t\t\t\tconst vector = table.get(text);\n\t\t\t\tif (vector === undefined) throw new Error(`no scripted vector declared for text: ${text}`);\n\t\t\t\treturn vector;\n\t\t\t}),\n\t};\n\treturn {\n\t\tembedProvider,\n\t\treranker: createLexicalMockReranker(),\n\t\tvectorIndex: createInMemoryMemoryVectorIndex({ dimensions }),\n\t};\n}\n\n/** A loop hook captured from a capability registration (测试侧驱动用). */\nexport interface MemoryCapabilityLoopHookCaptureV1 {\n\treadonly position: string;\n\treadonly handler: (messages: unknown) => Promise<unknown>;\n}\n\n/**\n * Minimal capture api for driving `createCapability` / its vector-component\n * test variant at the factory level (mirrors the runtime tool/command surface:\n * registerTool, registerCommand, publish, logger, capabilities, session,\n * lifecycle, registerLoopHook).\n */\nexport interface MemoryCapabilityTestApiV1 {\n\treadonly api: CapabilityAPI;\n\t/** Registered tool by name (undefined when the capability did not register it). */\n\ttool(name: string): { readonly execute: (first: unknown, second?: unknown) => Promise<unknown> } | undefined;\n\t/** Registered command by name (undefined when the capability did not register it). */\n\tcommand(name: string): { readonly execute: (args: string) => Promise<unknown> } | undefined;\n\t/** logger.warn messages captured for degraded-channel assertions. */\n\treadonly warnings: readonly string[];\n\t/** logger.info messages captured for off/enabling declaration assertions. */\n\treadonly infos: readonly string[];\n\t/** logger.debug messages captured for bounded-skip assertions (§2.2 配对失败). */\n\treadonly debugs: readonly string[];\n\t/** Appends a suite binding entry; resolveSessionDomain reads the LAST one. */\n\tbindSuite(suiteId: string): void;\n\t/**\n\t * Appends one message entry (assistant/user/toolResult shape is the test's\n\t * responsibility) to the branch; returns the entry id.\n\t */\n\tappendMessageEntry(message: unknown): string;\n\t/** The registered loop hook for a position (undefined when not registered). */\n\tloopHook(position: string): MemoryCapabilityLoopHookCaptureV1 | undefined;\n\t/**\n\t * Dispatches a committed lifecycle event to every registered observer of\n\t * id@version (test-side stand-in for the host lifecycle dispatcher).\n\t */\n\tdispatchLifecycleEvent(event: {\n\t\treadonly id: string;\n\t\treadonly version?: number;\n\t\treadonly data: unknown;\n\t}): Promise<void>;\n}\n\nexport function createMemoryCapabilityTestApi(options: { readonly suiteId?: string } = {}): MemoryCapabilityTestApiV1 {\n\tconst warnings: string[] = [];\n\tconst infos: string[] = [];\n\tconst debugs: string[] = [];\n\tconst registered = new Map<string, { readonly execute: (first: unknown, second?: unknown) => Promise<unknown> }>();\n\tconst commands = new Map<string, { readonly execute: (args: string) => Promise<unknown> }>();\n\tconst entries: { id: string; type: string; customType?: string; data?: unknown; message?: unknown }[] = [];\n\tconst loopHooks: MemoryCapabilityLoopHookCaptureV1[] = [];\n\t// Lifecycle stand-in: definitions by id@version plus observer lists, enough\n\t// for capabilities that define catalog events and register observe handlers.\n\tlet deliverySequence = 0;\n\tconst lifecycleDefinitions = new Map<string, LifecycleEventDefinition<unknown>>();\n\tconst lifecycleObservers = new Map<string, ((event: LifecycleEvent<unknown>) => unknown)[]>();\n\tconst lifecycleKeyOf = (id: string, version: number): string => `${id}@${version}`;\n\tconst lifecycleReferenceOf = (\n\t\tevent: LifecycleEventDefinition<unknown> | LifecycleEventReference | string,\n\t\tversion?: number,\n\t): { readonly id: string; readonly version: number } => {\n\t\tif (typeof event === \"string\") return { id: event, version: version ?? 1 };\n\t\treturn { id: event.id, version: event.version };\n\t};\n\tconst registerLifecycleObserver = (\n\t\tevent: LifecycleEventDefinition<unknown> | LifecycleEventReference | string,\n\t\thandler: LifecycleObserveHandler<unknown>,\n\t\tversion?: number,\n\t): { dispose: () => void } => {\n\t\tconst reference = lifecycleReferenceOf(event, version);\n\t\tconst key = lifecycleKeyOf(reference.id, reference.version);\n\t\tconst list = lifecycleObservers.get(key) ?? [];\n\t\tlist.push(handler);\n\t\tlifecycleObservers.set(key, list);\n\t\treturn { dispose: () => {} };\n\t};\n\tconst lifecycle = {\n\t\tdefine: (definition: LifecycleEventDefinition<unknown>): LifecycleEventDefinition<unknown> => {\n\t\t\tconst key = lifecycleKeyOf(definition.id, definition.version);\n\t\t\tif (lifecycleDefinitions.has(key)) {\n\t\t\t\tthrow new Error(`Lifecycle event is already defined: ${definition.id}@${definition.version}`);\n\t\t\t}\n\t\t\tlifecycleDefinitions.set(key, definition);\n\t\t\treturn definition;\n\t\t},\n\t\tget: (id: string, version: number): LifecycleEventDefinition<unknown> => {\n\t\t\tconst definition = lifecycleDefinitions.get(lifecycleKeyOf(id, version));\n\t\t\tif (definition === undefined) throw new Error(`Lifecycle event is not defined: ${id}@${version}`);\n\t\t\treturn definition;\n\t\t},\n\t\tregisterObserve: registerLifecycleObserver,\n\t\tregisterTransform: (): { dispose: () => void } => {\n\t\t\tthrow new Error(\"lifecycle transform is not available in this fixture\");\n\t\t},\n\t\tregisterExecute: (): { dispose: () => void } => {\n\t\t\tthrow new Error(\"lifecycle execute is not available in this fixture\");\n\t\t},\n\t\tregisterAfterCommit: registerLifecycleObserver,\n\t};\n\tlet messageSequence = 0;\n\tif (options.suiteId !== undefined) {\n\t\tentries.push({ id: \"binding-0\", type: \"custom\", customType: \"suite\", data: { suiteId: options.suiteId } });\n\t}\n\tconst api = {\n\t\tregisterTool: (definition: { name: string; execute: (first: unknown, second?: unknown) => Promise<unknown> }) => {\n\t\t\tregistered.set(definition.name, definition);\n\t\t\treturn { dispose: async () => {} };\n\t\t},\n\t\tregisterCommand: (definition: { name: string; execute: (args: string) => Promise<unknown> }) => {\n\t\t\tcommands.set(definition.name, definition);\n\t\t\treturn { dispose: async () => {} };\n\t\t},\n\t\tregisterLoopHook: (position: string, handler: (messages: unknown) => Promise<unknown>) => {\n\t\t\tloopHooks.push({ position, handler });\n\t\t\treturn { dispose: async () => {} };\n\t\t},\n\t\tpublish: async (event: unknown) => ({ id: \"test-event\", source: \"test\", timestamp: 0, ...(event as object) }),\n\t\tlogger: {\n\t\t\tdebug: (message: string) => {\n\t\t\t\tdebugs.push(message);\n\t\t\t},\n\t\t\tinfo: (message: string) => {\n\t\t\t\tinfos.push(message);\n\t\t\t},\n\t\t\twarn: (message: string) => {\n\t\t\t\twarnings.push(message);\n\t\t\t},\n\t\t\terror: () => {},\n\t\t},\n\t\tcapabilities: {\n\t\t\tprovide: () => ({ dispose: () => {} }),\n\t\t\trequire: () => {\n\t\t\t\tthrow new Error(\"capability require is not available in this fixture\");\n\t\t\t},\n\t\t\tlist: () => [],\n\t\t},\n\t\tsession: {\n\t\t\tgetSessionId: () => \"memory-test-session\",\n\t\t\tgetBranchEntries: () => entries,\n\t\t},\n\t\tlifecycle,\n\t} as unknown as CapabilityAPI;\n\treturn {\n\t\tapi,\n\t\ttool: (name) => registered.get(name),\n\t\tcommand: (name) => commands.get(name),\n\t\twarnings,\n\t\tinfos,\n\t\tdebugs,\n\t\tbindSuite: (suiteId) => {\n\t\t\tentries.push({ id: `binding-${entries.length}`, type: \"custom\", customType: \"suite\", data: { suiteId } });\n\t\t},\n\t\tappendMessageEntry: (message) => {\n\t\t\tmessageSequence += 1;\n\t\t\tconst id = `message-${messageSequence}`;\n\t\t\tentries.push({ id, type: \"message\", message });\n\t\t\treturn id;\n\t\t},\n\t\tloopHook: (position) => loopHooks.find((hook) => hook.position === position),\n\t\tdispatchLifecycleEvent: async (event) => {\n\t\t\tconst version = event.version ?? 1;\n\t\t\tconst list = lifecycleObservers.get(lifecycleKeyOf(event.id, version)) ?? [];\n\t\t\tdeliverySequence += 1;\n\t\t\tconst lifecycleEvent: LifecycleEvent<unknown> = {\n\t\t\t\tid: event.id,\n\t\t\t\tdeliveryId: `test-delivery-${deliverySequence}`,\n\t\t\t\ttype: event.id,\n\t\t\t\tversion,\n\t\t\t\tphase: \"committed\",\n\t\t\t\tsource: \"test\",\n\t\t\t\ttimestamp: 0,\n\t\t\t\tdata: event.data,\n\t\t\t};\n\t\t\tfor (const handler of list) await handler(lifecycleEvent);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Deterministic memory testkit (1C.8a) — wires the 1C.1–1C.7 Foundation pieces
3
+ * (atom validator, store, candidate machine, recall index, lifecycle manager,
4
+ * scheduler, curation, continuation, purge gate/journal) into one reusable,
5
+ * clock-injectable fixture. Acceptance: the 1C.7b closed loop repeats 20 times
6
+ * with identical results through this kit.
7
+ */
8
+ import { deepStrictEqual } from "node:assert";
9
+ import { createMemoryCandidateMachine } from "../memory/candidates.js";
10
+ import { createMemoryCompactionSequencer } from "../memory/compaction-sequencer.js";
11
+ import { createMemoryContinuationIssuer } from "../memory/continuation.js";
12
+ import { createMemoryCurationAgent } from "../memory/curation.js";
13
+ import { createDeterministicMockEmbeddingProvider } from "../memory/embedding-provider.js";
14
+ import { buildMemoryAtomV1 } from "../memory/foundation.js";
15
+ import { createMemoryLifecycleManager, } from "../memory/lifecycle.js";
16
+ import { createMemoryPurgeGate, createMemoryReplicaRegistry, } from "../memory/purge.js";
17
+ import { createMemoryPurgeJournal } from "../memory/purge-journal.js";
18
+ import { createMemoryRecallAgent } from "../memory/recall-agent.js";
19
+ import { createMemoryRecallIndex } from "../memory/recall-index.js";
20
+ import { createMemoryScheduler } from "../memory/scheduler.js";
21
+ import { createFirstPartyRetentionRegistry, createMemoryStore, } from "../memory/store.js";
22
+ export function createMemoryTestKit(options = {}) {
23
+ const now = options.now ?? (() => Date.now());
24
+ const retentionRegistry = options.retentionRegistry ?? createFirstPartyRetentionRegistry();
25
+ const store = createMemoryStore({ retentionRegistry, now });
26
+ const machine = createMemoryCandidateMachine({ store, now });
27
+ const sequencer = createMemoryCompactionSequencer({ store });
28
+ const index = createMemoryRecallIndex({ store });
29
+ const lifecyclePolicy = options.lifecyclePolicy ?? {
30
+ policyVersion: "lifecycle@1",
31
+ halfLifeMs: Object.freeze({
32
+ session: 1 * 24 * 60 * 60 * 1000,
33
+ cycle: 7 * 24 * 60 * 60 * 1000,
34
+ "long-term": 30 * 24 * 60 * 60 * 1000,
35
+ }),
36
+ staleAfterMs: Object.freeze({
37
+ session: 2 * 24 * 60 * 60 * 1000,
38
+ cycle: 14 * 24 * 60 * 60 * 1000,
39
+ "long-term": 60 * 24 * 60 * 60 * 1000,
40
+ }),
41
+ archiveAfterMs: Object.freeze({
42
+ session: 7 * 24 * 60 * 60 * 1000,
43
+ cycle: 60 * 24 * 60 * 60 * 1000,
44
+ "long-term": 180 * 24 * 60 * 60 * 1000,
45
+ }),
46
+ baselineAttention: 0.5,
47
+ };
48
+ const lifecycle = createMemoryLifecycleManager({ policy: lifecyclePolicy, now });
49
+ const scheduler = createMemoryScheduler({ now });
50
+ const curation = createMemoryCurationAgent({ store });
51
+ const continuation = createMemoryContinuationIssuer();
52
+ const replicaRegistry = createMemoryReplicaRegistry();
53
+ replicaRegistry.register({
54
+ replicaId: "canonical-1",
55
+ kind: "canonical",
56
+ ownerScope: "*",
57
+ contractVersion: "agent-forge/memory@1",
58
+ healthy: true,
59
+ });
60
+ const purgeGate = createMemoryPurgeGate({ registry: replicaRegistry, now });
61
+ const purgeJournal = createMemoryPurgeJournal();
62
+ const recallAgent = createMemoryRecallAgent({
63
+ scheduler,
64
+ index,
65
+ policyDefaults: {
66
+ candidateBudget: 1000,
67
+ modelInspectionLimit: 20,
68
+ finalResultLimit: 10,
69
+ maxRelationHops: 2,
70
+ maxModelCalls: 3,
71
+ maxOutputTokens: 4000,
72
+ maxOutputBytes: 16_000,
73
+ maxRounds: 3,
74
+ },
75
+ hostLimits: {
76
+ candidateBudget: 5000,
77
+ modelInspectionLimit: 100,
78
+ finalResultLimit: 100,
79
+ maxRelationHops: 5,
80
+ maxModelCalls: 10,
81
+ maxOutputTokens: 100_000,
82
+ maxOutputBytes: 1_000_000,
83
+ maxRounds: 10,
84
+ },
85
+ now,
86
+ });
87
+ let sequence = 0;
88
+ const makeAtom = (seed) => {
89
+ sequence += 1;
90
+ return buildMemoryAtomV1({
91
+ memoryId: seed.memoryId ?? `mem-${String(sequence).padStart(4, "0")}`,
92
+ contractVersion: "agent-forge/memory@1",
93
+ schemaVersion: 1,
94
+ scope: seed.scope ?? "long-term",
95
+ retentionMode: seed.retentionMode ?? "permanent",
96
+ owner: seed.owner ?? "code-memory",
97
+ profileId: "coding-agent",
98
+ retentionPolicyVersion: "retention@1",
99
+ memoryKind: "fact",
100
+ payload: seed.payload ?? { sequence },
101
+ occurredAt: seed.occurredAt,
102
+ recordedAt: seed.occurredAt,
103
+ sourceRefs: [{ kind: "entry", id: `entry-${sequence}` }],
104
+ observationId: seed.observationId ?? `obs-${sequence}`,
105
+ sessionRefs: [],
106
+ agentInstanceRefs: [],
107
+ projectRefs: seed.project ? [seed.project] : [],
108
+ subjectRefs: seed.task ? [seed.task] : [],
109
+ facets: [],
110
+ relations: [],
111
+ confidence: 1,
112
+ importance: 0.5,
113
+ evidenceClass: "explicit",
114
+ contentRevision: `c-${sequence}`,
115
+ writeReason: "test",
116
+ });
117
+ };
118
+ const commit = (atom) => {
119
+ store.commit(atom);
120
+ return atom.memoryId;
121
+ };
122
+ const query = (query) => index.query(query);
123
+ return {
124
+ store,
125
+ machine,
126
+ sequencer,
127
+ index,
128
+ lifecycle,
129
+ scheduler,
130
+ curation,
131
+ continuation,
132
+ purgeGate,
133
+ purgeJournal,
134
+ recallAgent,
135
+ makeAtom,
136
+ commit,
137
+ query,
138
+ };
139
+ }
140
+ /** Repeats `run` up to `times` and requires every result to deep-equal the first. */
141
+ export async function repeatMemoryTestKit(times, run) {
142
+ let baseline;
143
+ let repeats = 0;
144
+ for (let index = 0; index < times; index += 1) {
145
+ const result = await run();
146
+ if (index === 0) {
147
+ baseline = result;
148
+ continue;
149
+ }
150
+ // node:assert deep compare keeps the kit free of test-runner imports
151
+ // (plugin-src boundary gate allows node: builtins only); vitest renders
152
+ // the AssertionError like any other thrown assertion.
153
+ deepStrictEqual(result, baseline);
154
+ repeats += 1;
155
+ }
156
+ return { baseline: baseline, repeats };
157
+ }
158
+ // ---------------------------------------------------------------------------
159
+ // 向量混合检索 mock 注入点 (架构宪法: 默认测试只允许 deterministic mock AI)。
160
+ // 供 capabilities/memory.ts 的 createCapabilityWithVectorComponents 注入,
161
+ // 不触真实 transformers/lancedb。
162
+ // ---------------------------------------------------------------------------
163
+ function tokenizeForFakeIndex(text) {
164
+ return text
165
+ .toLowerCase()
166
+ .split(/[^\p{L}\p{N}]+/u)
167
+ .filter((token) => token !== "");
168
+ }
169
+ /**
170
+ * In-memory fake of the LanceDB vector index (memory/vector-index.ts 同契约):
171
+ * KNN 平扫平方 L2 (score = 1/(1+距离), 与真组件同口径), FTS 按词元包含匹配
172
+ * (score = 1/(60+rank+1), 与真组件同口径)。确定性: 排序并列按插入序打破。
173
+ */
174
+ export function createInMemoryMemoryVectorIndex(options) {
175
+ const dimensions = options.dimensions;
176
+ const rows = new Map();
177
+ const insertOrder = [];
178
+ const bodyOf = (entry) => `${entry.statement} ${entry.tags.join(" ")}`;
179
+ return {
180
+ replicaId: "memory-vector-index",
181
+ upsert: async (entries) => {
182
+ for (const entry of entries) {
183
+ if (entry.vector.length !== dimensions) {
184
+ throw new Error(`dimension mismatch for ${entry.memoryId}`);
185
+ }
186
+ if (!rows.has(entry.memoryId))
187
+ insertOrder.push(entry.memoryId);
188
+ rows.set(entry.memoryId, entry);
189
+ }
190
+ },
191
+ remove: async (memoryIds) => {
192
+ for (const memoryId of memoryIds) {
193
+ rows.delete(memoryId);
194
+ const position = insertOrder.indexOf(memoryId);
195
+ if (position >= 0)
196
+ insertOrder.splice(position, 1);
197
+ }
198
+ },
199
+ queryKnn: async (vector, query) => {
200
+ if (vector.length !== dimensions)
201
+ throw new Error("query vector dimension mismatch");
202
+ const hits = insertOrder
203
+ .map((memoryId) => {
204
+ const entry = rows.get(memoryId);
205
+ if (entry === undefined || entry.owner !== query.owner)
206
+ return undefined;
207
+ let sumSquares = 0;
208
+ for (const [axis, value] of vector.entries()) {
209
+ const delta = value - (entry.vector[axis] ?? 0);
210
+ sumSquares += delta * delta;
211
+ }
212
+ return { memoryId, score: 1 / (1 + sumSquares) };
213
+ })
214
+ .filter((hit) => hit !== undefined);
215
+ hits.sort((left, right) => right.score - left.score);
216
+ return hits.slice(0, query.limit);
217
+ },
218
+ queryFts: async (queryText, query) => {
219
+ const queryTokens = tokenizeForFakeIndex(queryText);
220
+ if (queryTokens.length === 0)
221
+ return [];
222
+ const matched = insertOrder
223
+ .map((memoryId) => {
224
+ const entry = rows.get(memoryId);
225
+ if (entry === undefined || entry.owner !== query.owner)
226
+ return undefined;
227
+ const bodyTokens = new Set(tokenizeForFakeIndex(bodyOf(entry)));
228
+ const hits = queryTokens.filter((token) => bodyTokens.has(token)).length;
229
+ return hits > 0 ? { memoryId, hits } : undefined;
230
+ })
231
+ .filter((hit) => hit !== undefined);
232
+ // 命中词元数降序, 并列按插入序; 打分与真组件同口径 (RRF_K=60)。
233
+ matched.sort((left, right) => right.hits - left.hits);
234
+ return matched
235
+ .slice(0, query.limit)
236
+ .map((hit, rank) => ({ memoryId: hit.memoryId, score: 1 / (60 + rank + 1) }));
237
+ },
238
+ listMemoryIds: async () => new Set(rows.keys()),
239
+ count: async () => rows.size,
240
+ getStoredModelId: async () => {
241
+ const first = insertOrder.find((memoryId) => rows.has(memoryId));
242
+ return first === undefined ? undefined : rows.get(first)?.modelId;
243
+ },
244
+ close: async () => { },
245
+ };
246
+ }
247
+ export function createMockMemoryVectorComponents(options = {}) {
248
+ const embedProvider = createDeterministicMockEmbeddingProvider({ dimensions: options.dimensions ?? 8 });
249
+ const vectorIndex = createInMemoryMemoryVectorIndex({ dimensions: embedProvider.dimensions });
250
+ const scriptedRerank = options.rerank;
251
+ const reranker = options.rerankerUnavailable
252
+ ? undefined
253
+ : {
254
+ modelId: "mock-reranker@1",
255
+ rerank: scriptedRerank === undefined
256
+ ? createLexicalMockReranker().rerank
257
+ : async (query, docs) => scriptedRerank(query, docs),
258
+ };
259
+ return { embedProvider, reranker, vectorIndex };
260
+ }
261
+ /** Deterministic reranker mock: score = count of query tokens present in the doc. */
262
+ export function createLexicalMockReranker() {
263
+ return {
264
+ modelId: "mock-reranker@1",
265
+ rerank: async (query, docs) => {
266
+ const queryTokens = new Set(tokenizeForFakeIndex(query));
267
+ return docs.map((doc) => {
268
+ let overlap = 0;
269
+ for (const token of tokenizeForFakeIndex(doc)) {
270
+ if (queryTokens.has(token))
271
+ overlap += 1;
272
+ }
273
+ return overlap;
274
+ });
275
+ },
276
+ };
277
+ }
278
+ /**
279
+ * 显式向量表 mock 组件 (2.4d6 S3 语义合并测试用): 每条文本的向量由测试直接
280
+ * 指定 (绕过 FNV token hash — 合并阈值的余弦判定需要可控向量), 搭配
281
+ * in-memory fake index 与缺省词元重排器, 零网络零真实模型 (架构宪法测试
282
+ * 门禁)。dimensions 取自向量表首个声明向量; 未声明向量的文本显式抛错 —
283
+ * 测试必须登记每一条会被 embed 的内容与查询 (含召回 query), 保证确定性。
284
+ */
285
+ export function createScriptedMockVectorComponents(vectors) {
286
+ const table = new Map(Object.entries(vectors));
287
+ const firstVector = table.values().next();
288
+ if (firstVector.done || firstVector.value.length === 0) {
289
+ throw new Error("createScriptedMockVectorComponents requires at least one declared vector");
290
+ }
291
+ const dimensions = firstVector.value.length;
292
+ const embedProvider = {
293
+ modelId: "mock-embedding-scripted@1",
294
+ dimensions,
295
+ embed: async (texts) => texts.map((text) => {
296
+ const vector = table.get(text);
297
+ if (vector === undefined)
298
+ throw new Error(`no scripted vector declared for text: ${text}`);
299
+ return vector;
300
+ }),
301
+ };
302
+ return {
303
+ embedProvider,
304
+ reranker: createLexicalMockReranker(),
305
+ vectorIndex: createInMemoryMemoryVectorIndex({ dimensions }),
306
+ };
307
+ }
308
+ export function createMemoryCapabilityTestApi(options = {}) {
309
+ const warnings = [];
310
+ const infos = [];
311
+ const debugs = [];
312
+ const registered = new Map();
313
+ const commands = new Map();
314
+ const entries = [];
315
+ const loopHooks = [];
316
+ // Lifecycle stand-in: definitions by id@version plus observer lists, enough
317
+ // for capabilities that define catalog events and register observe handlers.
318
+ let deliverySequence = 0;
319
+ const lifecycleDefinitions = new Map();
320
+ const lifecycleObservers = new Map();
321
+ const lifecycleKeyOf = (id, version) => `${id}@${version}`;
322
+ const lifecycleReferenceOf = (event, version) => {
323
+ if (typeof event === "string")
324
+ return { id: event, version: version ?? 1 };
325
+ return { id: event.id, version: event.version };
326
+ };
327
+ const registerLifecycleObserver = (event, handler, version) => {
328
+ const reference = lifecycleReferenceOf(event, version);
329
+ const key = lifecycleKeyOf(reference.id, reference.version);
330
+ const list = lifecycleObservers.get(key) ?? [];
331
+ list.push(handler);
332
+ lifecycleObservers.set(key, list);
333
+ return { dispose: () => { } };
334
+ };
335
+ const lifecycle = {
336
+ define: (definition) => {
337
+ const key = lifecycleKeyOf(definition.id, definition.version);
338
+ if (lifecycleDefinitions.has(key)) {
339
+ throw new Error(`Lifecycle event is already defined: ${definition.id}@${definition.version}`);
340
+ }
341
+ lifecycleDefinitions.set(key, definition);
342
+ return definition;
343
+ },
344
+ get: (id, version) => {
345
+ const definition = lifecycleDefinitions.get(lifecycleKeyOf(id, version));
346
+ if (definition === undefined)
347
+ throw new Error(`Lifecycle event is not defined: ${id}@${version}`);
348
+ return definition;
349
+ },
350
+ registerObserve: registerLifecycleObserver,
351
+ registerTransform: () => {
352
+ throw new Error("lifecycle transform is not available in this fixture");
353
+ },
354
+ registerExecute: () => {
355
+ throw new Error("lifecycle execute is not available in this fixture");
356
+ },
357
+ registerAfterCommit: registerLifecycleObserver,
358
+ };
359
+ let messageSequence = 0;
360
+ if (options.suiteId !== undefined) {
361
+ entries.push({ id: "binding-0", type: "custom", customType: "suite", data: { suiteId: options.suiteId } });
362
+ }
363
+ const api = {
364
+ registerTool: (definition) => {
365
+ registered.set(definition.name, definition);
366
+ return { dispose: async () => { } };
367
+ },
368
+ registerCommand: (definition) => {
369
+ commands.set(definition.name, definition);
370
+ return { dispose: async () => { } };
371
+ },
372
+ registerLoopHook: (position, handler) => {
373
+ loopHooks.push({ position, handler });
374
+ return { dispose: async () => { } };
375
+ },
376
+ publish: async (event) => ({ id: "test-event", source: "test", timestamp: 0, ...event }),
377
+ logger: {
378
+ debug: (message) => {
379
+ debugs.push(message);
380
+ },
381
+ info: (message) => {
382
+ infos.push(message);
383
+ },
384
+ warn: (message) => {
385
+ warnings.push(message);
386
+ },
387
+ error: () => { },
388
+ },
389
+ capabilities: {
390
+ provide: () => ({ dispose: () => { } }),
391
+ require: () => {
392
+ throw new Error("capability require is not available in this fixture");
393
+ },
394
+ list: () => [],
395
+ },
396
+ session: {
397
+ getSessionId: () => "memory-test-session",
398
+ getBranchEntries: () => entries,
399
+ },
400
+ lifecycle,
401
+ };
402
+ return {
403
+ api,
404
+ tool: (name) => registered.get(name),
405
+ command: (name) => commands.get(name),
406
+ warnings,
407
+ infos,
408
+ debugs,
409
+ bindSuite: (suiteId) => {
410
+ entries.push({ id: `binding-${entries.length}`, type: "custom", customType: "suite", data: { suiteId } });
411
+ },
412
+ appendMessageEntry: (message) => {
413
+ messageSequence += 1;
414
+ const id = `message-${messageSequence}`;
415
+ entries.push({ id, type: "message", message });
416
+ return id;
417
+ },
418
+ loopHook: (position) => loopHooks.find((hook) => hook.position === position),
419
+ dispatchLifecycleEvent: async (event) => {
420
+ const version = event.version ?? 1;
421
+ const list = lifecycleObservers.get(lifecycleKeyOf(event.id, version)) ?? [];
422
+ deliverySequence += 1;
423
+ const lifecycleEvent = {
424
+ id: event.id,
425
+ deliveryId: `test-delivery-${deliverySequence}`,
426
+ type: event.id,
427
+ version,
428
+ phase: "committed",
429
+ source: "test",
430
+ timestamp: 0,
431
+ data: event.data,
432
+ };
433
+ for (const handler of list)
434
+ await handler(lifecycleEvent);
435
+ },
436
+ };
437
+ }
438
+ //# sourceMappingURL=memory-testkit.js.map