@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.
- package/README.md +65 -0
- package/agent-forge.json +11 -0
- package/dist/capability.d.ts +182 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +2565 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +36 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +154 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/assistant-card.d.ts +31 -0
- package/dist/memory/assistant-card.d.ts.map +1 -0
- package/dist/memory/assistant-card.js +108 -0
- package/dist/memory/assistant-card.js.map +1 -0
- package/dist/memory/candidates.d.ts +65 -0
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/memory/candidates.js +100 -0
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/code-memory.d.ts +89 -0
- package/dist/memory/code-memory.d.ts.map +1 -0
- package/dist/memory/code-memory.js +104 -0
- package/dist/memory/code-memory.js.map +1 -0
- package/dist/memory/compaction-sequencer.d.ts +63 -0
- package/dist/memory/compaction-sequencer.d.ts.map +1 -0
- package/dist/memory/compaction-sequencer.js +129 -0
- package/dist/memory/compaction-sequencer.js.map +1 -0
- package/dist/memory/continuation.d.ts +44 -0
- package/dist/memory/continuation.d.ts.map +1 -0
- package/dist/memory/continuation.js +49 -0
- package/dist/memory/continuation.js.map +1 -0
- package/dist/memory/curation.d.ts +58 -0
- package/dist/memory/curation.d.ts.map +1 -0
- package/dist/memory/curation.js +68 -0
- package/dist/memory/curation.js.map +1 -0
- package/dist/memory/egress-policy.d.ts +50 -0
- package/dist/memory/egress-policy.d.ts.map +1 -0
- package/dist/memory/egress-policy.js +71 -0
- package/dist/memory/egress-policy.js.map +1 -0
- package/dist/memory/embedding-provider.d.ts +70 -0
- package/dist/memory/embedding-provider.d.ts.map +1 -0
- package/dist/memory/embedding-provider.js +164 -0
- package/dist/memory/embedding-provider.js.map +1 -0
- package/dist/memory/embedding-reranker.d.ts +56 -0
- package/dist/memory/embedding-reranker.d.ts.map +1 -0
- package/dist/memory/embedding-reranker.js +109 -0
- package/dist/memory/embedding-reranker.js.map +1 -0
- package/dist/memory/foundation.d.ts +168 -0
- package/dist/memory/foundation.d.ts.map +1 -0
- package/dist/memory/foundation.js +487 -0
- package/dist/memory/foundation.js.map +1 -0
- package/dist/memory/host-module-import.d.ts +25 -0
- package/dist/memory/host-module-import.d.ts.map +1 -0
- package/dist/memory/host-module-import.js +41 -0
- package/dist/memory/host-module-import.js.map +1 -0
- package/dist/memory/ledger.d.ts +58 -0
- package/dist/memory/ledger.d.ts.map +1 -0
- package/dist/memory/ledger.js +315 -0
- package/dist/memory/ledger.js.map +1 -0
- package/dist/memory/lifecycle.d.ts +124 -0
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +201 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/memory/memory-network.d.ts +55 -0
- package/dist/memory/memory-network.d.ts.map +1 -0
- package/dist/memory/memory-network.js +70 -0
- package/dist/memory/memory-network.js.map +1 -0
- package/dist/memory/model-cache-hygiene.d.ts +18 -0
- package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
- package/dist/memory/model-cache-hygiene.js +38 -0
- package/dist/memory/model-cache-hygiene.js.map +1 -0
- package/dist/memory/preference-disambiguator.d.ts +43 -0
- package/dist/memory/preference-disambiguator.d.ts.map +1 -0
- package/dist/memory/preference-disambiguator.js +81 -0
- package/dist/memory/preference-disambiguator.js.map +1 -0
- package/dist/memory/preference-lifecycle.d.ts +66 -0
- package/dist/memory/preference-lifecycle.d.ts.map +1 -0
- package/dist/memory/preference-lifecycle.js +129 -0
- package/dist/memory/preference-lifecycle.js.map +1 -0
- package/dist/memory/preference-promotion.d.ts +87 -0
- package/dist/memory/preference-promotion.d.ts.map +1 -0
- package/dist/memory/preference-promotion.js +102 -0
- package/dist/memory/preference-promotion.js.map +1 -0
- package/dist/memory/preference-resolver.d.ts +44 -0
- package/dist/memory/preference-resolver.d.ts.map +1 -0
- package/dist/memory/preference-resolver.js +107 -0
- package/dist/memory/preference-resolver.js.map +1 -0
- package/dist/memory/purge-journal.d.ts +76 -0
- package/dist/memory/purge-journal.d.ts.map +1 -0
- package/dist/memory/purge-journal.js +130 -0
- package/dist/memory/purge-journal.js.map +1 -0
- package/dist/memory/purge.d.ts +90 -0
- package/dist/memory/purge.d.ts.map +1 -0
- package/dist/memory/purge.js +138 -0
- package/dist/memory/purge.js.map +1 -0
- package/dist/memory/recall-agent.d.ts +84 -0
- package/dist/memory/recall-agent.d.ts.map +1 -0
- package/dist/memory/recall-agent.js +199 -0
- package/dist/memory/recall-agent.js.map +1 -0
- package/dist/memory/recall-index.d.ts +87 -0
- package/dist/memory/recall-index.d.ts.map +1 -0
- package/dist/memory/recall-index.js +222 -0
- package/dist/memory/recall-index.js.map +1 -0
- package/dist/memory/recall-packet.d.ts +121 -0
- package/dist/memory/recall-packet.d.ts.map +1 -0
- package/dist/memory/recall-packet.js +156 -0
- package/dist/memory/recall-packet.js.map +1 -0
- package/dist/memory/scheduler-api.d.ts +99 -0
- package/dist/memory/scheduler-api.d.ts.map +1 -0
- package/dist/memory/scheduler-api.js +93 -0
- package/dist/memory/scheduler-api.js.map +1 -0
- package/dist/memory/scheduler.d.ts +55 -0
- package/dist/memory/scheduler.d.ts.map +1 -0
- package/dist/memory/scheduler.js +91 -0
- package/dist/memory/scheduler.js.map +1 -0
- package/dist/memory/store.d.ts +107 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +208 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/suite-memory.d.ts +208 -0
- package/dist/memory/suite-memory.d.ts.map +1 -0
- package/dist/memory/suite-memory.js +288 -0
- package/dist/memory/suite-memory.js.map +1 -0
- package/dist/memory/transfer.d.ts +142 -0
- package/dist/memory/transfer.d.ts.map +1 -0
- package/dist/memory/transfer.js +210 -0
- package/dist/memory/transfer.js.map +1 -0
- package/dist/memory/vector-index.d.ts +39 -0
- package/dist/memory/vector-index.d.ts.map +1 -0
- package/dist/memory/vector-index.js +136 -0
- package/dist/memory/vector-index.js.map +1 -0
- package/dist/memory/write-budget.d.ts +33 -0
- package/dist/memory/write-budget.d.ts.map +1 -0
- package/dist/memory/write-budget.js +45 -0
- package/dist/memory/write-budget.js.map +1 -0
- package/dist/testing/memory-testkit.d.ts +149 -0
- package/dist/testing/memory-testkit.d.ts.map +1 -0
- package/dist/testing/memory-testkit.js +438 -0
- package/dist/testing/memory-testkit.js.map +1 -0
- package/dist/utils/sync-sleep.d.ts +2 -0
- package/dist/utils/sync-sleep.d.ts.map +1 -0
- package/dist/utils/sync-sleep.js +11 -0
- package/dist/utils/sync-sleep.js.map +1 -0
- package/package.json +56 -0
- package/plugin.json +10 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — minimal bounded recall index (1C.3).
|
|
3
|
+
*
|
|
4
|
+
* Deterministic anchor filtering over the 1C.1b store: time, scenes, projects,
|
|
5
|
+
* tasks, and facets. Anchors are validated BEFORE querying (锚点校验);
|
|
6
|
+
* narrowing is a pure query merge; results are sorted deterministically
|
|
7
|
+
* (recency desc, memoryId asc tie-break) and paginated with limit/offset.
|
|
8
|
+
* No Memory Network, vector retrieval, or semantic reranking.
|
|
9
|
+
*/
|
|
10
|
+
import type { MemoryScopeV1 } from "./foundation.ts";
|
|
11
|
+
import { type MemorySuiteFilterStatsV1 } from "./foundation.ts";
|
|
12
|
+
import type { CommittedMemoryV1, MemoryStoreV1 } from "./store.ts";
|
|
13
|
+
export type { MemoryScopeV1 };
|
|
14
|
+
export interface MemoryRecallQueryV1 {
|
|
15
|
+
readonly owner: string;
|
|
16
|
+
readonly scope?: MemoryScopeV1;
|
|
17
|
+
/**
|
|
18
|
+
* Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible in
|
|
19
|
+
* this suite are recalled; legacy (suiteId-less) atoms are skipped
|
|
20
|
+
* fail-safe and counted on the page. This is a boundary, not an anchor —
|
|
21
|
+
* {@link narrowMemoryRecallQuery} never widens or merges it.
|
|
22
|
+
*/
|
|
23
|
+
readonly suiteId?: string;
|
|
24
|
+
/** Inclusive ISO-8601 bounds on the atom's `occurredAt`. */
|
|
25
|
+
readonly timeRange?: {
|
|
26
|
+
readonly from?: string;
|
|
27
|
+
readonly to?: string;
|
|
28
|
+
};
|
|
29
|
+
readonly scenes?: readonly string[];
|
|
30
|
+
readonly projects?: readonly string[];
|
|
31
|
+
readonly tasks?: readonly string[];
|
|
32
|
+
readonly facets?: readonly {
|
|
33
|
+
readonly namespace: string;
|
|
34
|
+
readonly key?: string;
|
|
35
|
+
readonly value?: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** Page size. Default 20, maximum 100. */
|
|
38
|
+
readonly limit?: number;
|
|
39
|
+
readonly offset?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Exact-lookup fast path (记忆系统设计 §5.2): when set, the index locates
|
|
42
|
+
* this memoryId directly — attention/state/score filters do not apply.
|
|
43
|
+
*/
|
|
44
|
+
readonly memoryIdEquals?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Anchor validation happens before any query runs: malformed time bounds,
|
|
48
|
+
* empty anchor entries, or out-of-range paging are explicit errors instead of
|
|
49
|
+
* silently empty results.
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateMemoryRecallQuery(query: MemoryRecallQueryV1): void;
|
|
52
|
+
/** Pure narrowing: merges extra anchors into the query without running it. */
|
|
53
|
+
export declare function narrowMemoryRecallQuery(query: MemoryRecallQueryV1, extra: {
|
|
54
|
+
readonly scope?: MemoryScopeV1;
|
|
55
|
+
readonly timeRange?: {
|
|
56
|
+
readonly from?: string;
|
|
57
|
+
readonly to?: string;
|
|
58
|
+
};
|
|
59
|
+
readonly scenes?: readonly string[];
|
|
60
|
+
readonly projects?: readonly string[];
|
|
61
|
+
readonly tasks?: readonly string[];
|
|
62
|
+
readonly facets?: readonly {
|
|
63
|
+
readonly namespace: string;
|
|
64
|
+
readonly key?: string;
|
|
65
|
+
readonly value?: string;
|
|
66
|
+
}[];
|
|
67
|
+
}): MemoryRecallQueryV1;
|
|
68
|
+
export interface MemoryRecallPageV1 {
|
|
69
|
+
readonly items: readonly CommittedMemoryV1[];
|
|
70
|
+
readonly total: number;
|
|
71
|
+
readonly offset: number;
|
|
72
|
+
readonly limit: number;
|
|
73
|
+
/** True when the page came from the exact-lookup fast path (§5.2). */
|
|
74
|
+
readonly lookupUsed: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Present only when the query carried a suiteId: how many legacy and
|
|
77
|
+
* foreign-suite records the suite read boundary excluded (设计 §11).
|
|
78
|
+
*/
|
|
79
|
+
readonly suiteFilter?: MemorySuiteFilterStatsV1;
|
|
80
|
+
}
|
|
81
|
+
/** Deterministic bounded recall index over a 1C.1b store. */
|
|
82
|
+
export declare function createMemoryRecallIndex(options: {
|
|
83
|
+
readonly store: MemoryStoreV1;
|
|
84
|
+
}): {
|
|
85
|
+
query(query: MemoryRecallQueryV1): MemoryRecallPageV1;
|
|
86
|
+
};
|
|
87
|
+
//# sourceMappingURL=recall-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-index.d.ts","sourceRoot":"","sources":["../../src/memory/recall-index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — minimal bounded recall index (1C.3).\n *\n * Deterministic anchor filtering over the 1C.1b store: time, scenes, projects,\n * tasks, and facets. Anchors are validated BEFORE querying (锚点校验);\n * narrowing is a pure query merge; results are sorted deterministically\n * (recency desc, memoryId asc tie-break) and paginated with limit/offset.\n * No Memory Network, vector retrieval, or semantic reranking.\n */\nimport type { MemoryScopeV1 } from \"./foundation.ts\";\nimport { type MemorySuiteFilterStatsV1, memorySuiteVisibility } from \"./foundation.ts\";\nimport type { CommittedMemoryV1, MemoryStoreV1 } from \"./store.ts\";\n\nexport type { MemoryScopeV1 };\n\nexport interface MemoryRecallQueryV1 {\n\treadonly owner: string;\n\treadonly scope?: MemoryScopeV1;\n\t/**\n\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible in\n\t * this suite are recalled; legacy (suiteId-less) atoms are skipped\n\t * fail-safe and counted on the page. This is a boundary, not an anchor —\n\t * {@link narrowMemoryRecallQuery} never widens or merges it.\n\t */\n\treadonly suiteId?: string;\n\t/** Inclusive ISO-8601 bounds on the atom's `occurredAt`. */\n\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\treadonly scenes?: readonly string[];\n\treadonly projects?: readonly string[];\n\treadonly tasks?: readonly string[];\n\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t/** Page size. Default 20, maximum 100. */\n\treadonly limit?: number;\n\treadonly offset?: number;\n\t/**\n\t * Exact-lookup fast path (记忆系统设计 §5.2): when set, the index locates\n\t * this memoryId directly — attention/state/score filters do not apply.\n\t */\n\treadonly memoryIdEquals?: string;\n}\n\nconst DEFAULT_LIMIT = 20;\nconst MAX_LIMIT = 100;\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\nfunction assertIsoTimestamp(value: unknown, label: string): asserts value is string {\n\tassertNonEmptyString(value, label);\n\tif (Number.isNaN(Date.parse(value))) throw new Error(`${label} must be an ISO-8601 timestamp`);\n}\n\nfunction assertStringArray(value: unknown, label: string): asserts value is readonly string[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tfor (const [index, item] of value.entries()) {\n\t\tassertNonEmptyString(item, `${label}[${index}]`);\n\t}\n}\n\n/**\n * Anchor validation happens before any query runs: malformed time bounds,\n * empty anchor entries, or out-of-range paging are explicit errors instead of\n * silently empty results.\n */\nexport function validateMemoryRecallQuery(query: MemoryRecallQueryV1): void {\n\tassertNonEmptyString(query.owner, \"Recall query.owner\");\n\tif (query.suiteId !== undefined && (typeof query.suiteId !== \"string\" || query.suiteId.trim() === \"\")) {\n\t\tthrow new Error(\"Recall query.suiteId must be a non-empty string\");\n\t}\n\tif (query.scope !== undefined && ![\"session\", \"cycle\", \"long-term\"].includes(query.scope)) {\n\t\tthrow new Error(`Recall query.scope is invalid: ${String(query.scope)}`);\n\t}\n\tif (query.timeRange !== undefined) {\n\t\tif (query.timeRange.from !== undefined) assertIsoTimestamp(query.timeRange.from, \"Recall query.timeRange.from\");\n\t\tif (query.timeRange.to !== undefined) assertIsoTimestamp(query.timeRange.to, \"Recall query.timeRange.to\");\n\t\tif (\n\t\t\tquery.timeRange.from !== undefined &&\n\t\t\tquery.timeRange.to !== undefined &&\n\t\t\tDate.parse(query.timeRange.from) > Date.parse(query.timeRange.to)\n\t\t) {\n\t\t\tthrow new Error(\"Recall query.timeRange.from must not be after timeRange.to\");\n\t\t}\n\t}\n\tfor (const key of [\"scenes\", \"projects\", \"tasks\"] as const) {\n\t\tconst value = query[key];\n\t\tif (value !== undefined) {\n\t\t\tassertStringArray(value, `Recall query.${key}`);\n\t\t\tif (value.length === 0) throw new Error(`Recall query.${key} must not be an empty array`);\n\t\t}\n\t}\n\tif (query.facets !== undefined) {\n\t\tif (!Array.isArray(query.facets)) throw new Error(\"Recall query.facets must be an array\");\n\t\tfor (const [index, facet] of query.facets.entries()) {\n\t\t\tconst label = `Recall query.facets[${index}]`;\n\t\t\tif (facet === null || typeof facet !== \"object\" || Array.isArray(facet)) {\n\t\t\t\tthrow new Error(`${label} must be a plain object`);\n\t\t\t}\n\t\t\tconst record = facet as Record<string, unknown>;\n\t\t\tassertNonEmptyString(record.namespace, `${label}.namespace`);\n\t\t\tif (record.key !== undefined) assertNonEmptyString(record.key, `${label}.key`);\n\t\t\tif (record.value !== undefined) assertNonEmptyString(record.value, `${label}.value`);\n\t\t}\n\t}\n\tif (\n\t\tquery.limit !== undefined &&\n\t\t(!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > MAX_LIMIT)\n\t) {\n\t\tthrow new Error(`Recall query.limit must be a safe integer between 1 and ${MAX_LIMIT}`);\n\t}\n\tif (query.offset !== undefined && (!Number.isSafeInteger(query.offset) || query.offset < 0)) {\n\t\tthrow new Error(\"Recall query.offset must be a non-negative safe integer\");\n\t}\n}\n\n/** Pure narrowing: merges extra anchors into the query without running it. */\nexport function narrowMemoryRecallQuery(\n\tquery: MemoryRecallQueryV1,\n\textra: {\n\t\treadonly scope?: MemoryScopeV1;\n\t\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\t\treadonly scenes?: readonly string[];\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t},\n): MemoryRecallQueryV1 {\n\tconst nonEmpty = (list: readonly string[] | undefined): readonly string[] | undefined =>\n\t\tlist !== undefined && list.length > 0 ? [...list] : undefined;\n\tconst scenes = nonEmpty([...(query.scenes ?? []), ...(extra.scenes ?? [])]);\n\tconst projects = nonEmpty([...(query.projects ?? []), ...(extra.projects ?? [])]);\n\tconst tasks = nonEmpty([...(query.tasks ?? []), ...(extra.tasks ?? [])]);\n\tconst facets = [...(query.facets ?? []), ...(extra.facets ?? [])];\n\tconst timeRange =\n\t\tquery.timeRange === undefined && extra.timeRange === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tfrom: query.timeRange?.from ?? extra.timeRange?.from,\n\t\t\t\t\tto: query.timeRange?.to ?? extra.timeRange?.to,\n\t\t\t\t};\n\treturn {\n\t\t...query,\n\t\t...(extra.scope === undefined ? {} : { scope: extra.scope }),\n\t\t...(timeRange === undefined ? {} : { timeRange }),\n\t\t...(scenes === undefined ? {} : { scenes }),\n\t\t...(projects === undefined ? {} : { projects }),\n\t\t...(tasks === undefined ? {} : { tasks }),\n\t\t...(facets.length === 0 ? {} : { facets }),\n\t};\n}\n\nfunction matchesAny(haystack: readonly string[] | undefined, needles: readonly string[]): boolean {\n\t// v8 ignore next -- 锚点校验先于匹配执行,空数组已被拒绝\n\tif (needles.length === 0) return true;\n\tif (!haystack) return false;\n\treturn needles.some((needle) => haystack.includes(needle));\n}\n\nfunction facetMatches(\n\tfacet: { readonly namespace: string; readonly key?: string; readonly value?: string },\n\tatomFacet: { readonly namespace: string; readonly key: string; readonly value: string },\n): boolean {\n\tif (facet.namespace !== atomFacet.namespace) return false;\n\tif (facet.key !== undefined && facet.key !== atomFacet.key) return false;\n\tif (facet.value !== undefined && facet.value !== atomFacet.value) return false;\n\treturn true;\n}\n\nfunction matchesAnchors(\n\trecord: CommittedMemoryV1,\n\tquery: Pick<MemoryRecallQueryV1, \"timeRange\" | \"scenes\" | \"projects\" | \"tasks\" | \"facets\">,\n): boolean {\n\tconst atom = record.atom;\n\tconst occurred = Date.parse(atom.occurredAt);\n\tif (query.timeRange) {\n\t\tif (query.timeRange.from !== undefined && occurred < Date.parse(query.timeRange.from)) return false;\n\t\tif (query.timeRange.to !== undefined && occurred > Date.parse(query.timeRange.to)) return false;\n\t}\n\tif (query.scenes !== undefined && !matchesAny(atom.applicability?.scenes, query.scenes)) return false;\n\tif (query.projects !== undefined) {\n\t\t// v8 ignore next -- projectRefs 是必填字段\n\t\tconst pool = [...(atom.projectRefs ?? []), ...(atom.applicability?.projects ?? [])];\n\t\tif (!matchesAny(pool, query.projects)) return false;\n\t}\n\tif (query.tasks !== undefined) {\n\t\t// v8 ignore next -- subjectRefs 是必填字段\n\t\tconst pool = [...(atom.subjectRefs ?? []), ...(atom.applicability?.tasks ?? [])];\n\t\tif (!matchesAny(pool, query.tasks)) return false;\n\t}\n\tif (query.facets !== undefined && query.facets.length > 0) {\n\t\tconst atomFacets = atom.facets;\n\t\tconst everyAnchorSatisfied = query.facets.every((anchor) =>\n\t\t\tatomFacets.some((atomFacet) => facetMatches(anchor, atomFacet)),\n\t\t);\n\t\tif (!everyAnchorSatisfied) return false;\n\t}\n\treturn true;\n}\n\nexport interface MemoryRecallPageV1 {\n\treadonly items: readonly CommittedMemoryV1[];\n\treadonly total: number;\n\treadonly offset: number;\n\treadonly limit: number;\n\t/** True when the page came from the exact-lookup fast path (§5.2). */\n\treadonly lookupUsed: boolean;\n\t/**\n\t * Present only when the query carried a suiteId: how many legacy and\n\t * foreign-suite records the suite read boundary excluded (设计 §11).\n\t */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n}\n\n/** Deterministic bounded recall index over a 1C.1b store. */\nexport function createMemoryRecallIndex(options: { readonly store: MemoryStoreV1 }): {\n\tquery(query: MemoryRecallQueryV1): MemoryRecallPageV1;\n} {\n\tconst store = options.store;\n\treturn {\n\t\tquery(query: MemoryRecallQueryV1): MemoryRecallPageV1 {\n\t\t\t// Exact-lookup fast path: a memoryId hit bypasses anchors, ordering,\n\t\t\t// and scoring entirely (§5.2) but keeps owner scoping AND the suite\n\t\t\t// read boundary — a legacy or foreign-suite atom is never resurrected\n\t\t\t// through exact lookup.\n\t\t\tif (query.memoryIdEquals !== undefined) {\n\t\t\t\tconst found = store.get(query.memoryIdEquals, { owner: query.owner });\n\t\t\t\tif (query.suiteId === undefined) {\n\t\t\t\t\tconst items = found === undefined ? [] : [found];\n\t\t\t\t\treturn Object.freeze({\n\t\t\t\t\t\titems: Object.freeze(items),\n\t\t\t\t\t\ttotal: items.length,\n\t\t\t\t\t\toffset: query.offset ?? 0,\n\t\t\t\t\t\tlimit: query.limit ?? DEFAULT_LIMIT,\n\t\t\t\t\t\tlookupUsed: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tlet suiteFilter: MemorySuiteFilterStatsV1 = Object.freeze({ legacySkipped: 0, foreignSuiteSkipped: 0 });\n\t\t\t\tlet items: readonly CommittedMemoryV1[] = [];\n\t\t\t\tif (found !== undefined) {\n\t\t\t\t\tconst visibility = memorySuiteVisibility(found.atom, query.suiteId);\n\t\t\t\t\tif (visibility === \"visible\") {\n\t\t\t\t\t\titems = [found];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsuiteFilter = Object.freeze({\n\t\t\t\t\t\t\tlegacySkipped: visibility === \"legacy\" ? 1 : 0,\n\t\t\t\t\t\t\tforeignSuiteSkipped: visibility === \"foreign-suite\" ? 1 : 0,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Object.freeze({\n\t\t\t\t\titems: Object.freeze(items),\n\t\t\t\t\ttotal: items.length,\n\t\t\t\t\toffset: query.offset ?? 0,\n\t\t\t\t\tlimit: query.limit ?? DEFAULT_LIMIT,\n\t\t\t\t\tlookupUsed: true,\n\t\t\t\t\tsuiteFilter,\n\t\t\t\t});\n\t\t\t}\n\t\t\tvalidateMemoryRecallQuery(query);\n\t\t\tconst limit = query.limit ?? DEFAULT_LIMIT;\n\t\t\tconst offset = query.offset ?? 0;\n\t\t\t// Suite-scoped reads go through listForSuite so excluded-record counts\n\t\t\t// cover the owner's full ledger, not just the anchor-matched slice.\n\t\t\tconst suitePage =\n\t\t\t\tquery.suiteId === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: store.listForSuite({ owner: query.owner, suiteId: query.suiteId }, query.scope);\n\t\t\tconst matches = (suitePage === undefined ? store.list({ owner: query.owner }, query.scope) : suitePage.records)\n\t\t\t\t.filter((record) => matchesAnchors(record, query))\n\t\t\t\t// Deterministic order: most recent first, memoryId as tie-break.\n\t\t\t\t.sort((left, right) => {\n\t\t\t\t\tconst byTime = Date.parse(right.atom.occurredAt) - Date.parse(left.atom.occurredAt);\n\t\t\t\t\tif (byTime !== 0) return byTime;\n\t\t\t\t\t// v8 ignore next -- memoryId 是 store 主键恒唯一,相等路径不可达\n\t\t\t\t\treturn left.atom.memoryId < right.atom.memoryId ? -1 : left.atom.memoryId > right.atom.memoryId ? 1 : 0;\n\t\t\t\t});\n\t\t\tconst total = matches.length;\n\t\t\tconst items = matches.slice(offset, offset + limit);\n\t\t\treturn Object.freeze({\n\t\t\t\titems: Object.freeze(items),\n\t\t\t\ttotal,\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\tlookupUsed: false,\n\t\t\t\t...(suitePage === undefined ? {} : { suiteFilter: suitePage.filter }),\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { memorySuiteVisibility } from "./foundation.js";
|
|
2
|
+
const DEFAULT_LIMIT = 20;
|
|
3
|
+
const MAX_LIMIT = 100;
|
|
4
|
+
function assertNonEmptyString(value, label) {
|
|
5
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
6
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
7
|
+
}
|
|
8
|
+
function assertIsoTimestamp(value, label) {
|
|
9
|
+
assertNonEmptyString(value, label);
|
|
10
|
+
if (Number.isNaN(Date.parse(value)))
|
|
11
|
+
throw new Error(`${label} must be an ISO-8601 timestamp`);
|
|
12
|
+
}
|
|
13
|
+
function assertStringArray(value, label) {
|
|
14
|
+
if (!Array.isArray(value))
|
|
15
|
+
throw new Error(`${label} must be an array`);
|
|
16
|
+
for (const [index, item] of value.entries()) {
|
|
17
|
+
assertNonEmptyString(item, `${label}[${index}]`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Anchor validation happens before any query runs: malformed time bounds,
|
|
22
|
+
* empty anchor entries, or out-of-range paging are explicit errors instead of
|
|
23
|
+
* silently empty results.
|
|
24
|
+
*/
|
|
25
|
+
export function validateMemoryRecallQuery(query) {
|
|
26
|
+
assertNonEmptyString(query.owner, "Recall query.owner");
|
|
27
|
+
if (query.suiteId !== undefined && (typeof query.suiteId !== "string" || query.suiteId.trim() === "")) {
|
|
28
|
+
throw new Error("Recall query.suiteId must be a non-empty string");
|
|
29
|
+
}
|
|
30
|
+
if (query.scope !== undefined && !["session", "cycle", "long-term"].includes(query.scope)) {
|
|
31
|
+
throw new Error(`Recall query.scope is invalid: ${String(query.scope)}`);
|
|
32
|
+
}
|
|
33
|
+
if (query.timeRange !== undefined) {
|
|
34
|
+
if (query.timeRange.from !== undefined)
|
|
35
|
+
assertIsoTimestamp(query.timeRange.from, "Recall query.timeRange.from");
|
|
36
|
+
if (query.timeRange.to !== undefined)
|
|
37
|
+
assertIsoTimestamp(query.timeRange.to, "Recall query.timeRange.to");
|
|
38
|
+
if (query.timeRange.from !== undefined &&
|
|
39
|
+
query.timeRange.to !== undefined &&
|
|
40
|
+
Date.parse(query.timeRange.from) > Date.parse(query.timeRange.to)) {
|
|
41
|
+
throw new Error("Recall query.timeRange.from must not be after timeRange.to");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const key of ["scenes", "projects", "tasks"]) {
|
|
45
|
+
const value = query[key];
|
|
46
|
+
if (value !== undefined) {
|
|
47
|
+
assertStringArray(value, `Recall query.${key}`);
|
|
48
|
+
if (value.length === 0)
|
|
49
|
+
throw new Error(`Recall query.${key} must not be an empty array`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (query.facets !== undefined) {
|
|
53
|
+
if (!Array.isArray(query.facets))
|
|
54
|
+
throw new Error("Recall query.facets must be an array");
|
|
55
|
+
for (const [index, facet] of query.facets.entries()) {
|
|
56
|
+
const label = `Recall query.facets[${index}]`;
|
|
57
|
+
if (facet === null || typeof facet !== "object" || Array.isArray(facet)) {
|
|
58
|
+
throw new Error(`${label} must be a plain object`);
|
|
59
|
+
}
|
|
60
|
+
const record = facet;
|
|
61
|
+
assertNonEmptyString(record.namespace, `${label}.namespace`);
|
|
62
|
+
if (record.key !== undefined)
|
|
63
|
+
assertNonEmptyString(record.key, `${label}.key`);
|
|
64
|
+
if (record.value !== undefined)
|
|
65
|
+
assertNonEmptyString(record.value, `${label}.value`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (query.limit !== undefined &&
|
|
69
|
+
(!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > MAX_LIMIT)) {
|
|
70
|
+
throw new Error(`Recall query.limit must be a safe integer between 1 and ${MAX_LIMIT}`);
|
|
71
|
+
}
|
|
72
|
+
if (query.offset !== undefined && (!Number.isSafeInteger(query.offset) || query.offset < 0)) {
|
|
73
|
+
throw new Error("Recall query.offset must be a non-negative safe integer");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Pure narrowing: merges extra anchors into the query without running it. */
|
|
77
|
+
export function narrowMemoryRecallQuery(query, extra) {
|
|
78
|
+
const nonEmpty = (list) => list !== undefined && list.length > 0 ? [...list] : undefined;
|
|
79
|
+
const scenes = nonEmpty([...(query.scenes ?? []), ...(extra.scenes ?? [])]);
|
|
80
|
+
const projects = nonEmpty([...(query.projects ?? []), ...(extra.projects ?? [])]);
|
|
81
|
+
const tasks = nonEmpty([...(query.tasks ?? []), ...(extra.tasks ?? [])]);
|
|
82
|
+
const facets = [...(query.facets ?? []), ...(extra.facets ?? [])];
|
|
83
|
+
const timeRange = query.timeRange === undefined && extra.timeRange === undefined
|
|
84
|
+
? undefined
|
|
85
|
+
: {
|
|
86
|
+
from: query.timeRange?.from ?? extra.timeRange?.from,
|
|
87
|
+
to: query.timeRange?.to ?? extra.timeRange?.to,
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
...query,
|
|
91
|
+
...(extra.scope === undefined ? {} : { scope: extra.scope }),
|
|
92
|
+
...(timeRange === undefined ? {} : { timeRange }),
|
|
93
|
+
...(scenes === undefined ? {} : { scenes }),
|
|
94
|
+
...(projects === undefined ? {} : { projects }),
|
|
95
|
+
...(tasks === undefined ? {} : { tasks }),
|
|
96
|
+
...(facets.length === 0 ? {} : { facets }),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function matchesAny(haystack, needles) {
|
|
100
|
+
// v8 ignore next -- 锚点校验先于匹配执行,空数组已被拒绝
|
|
101
|
+
if (needles.length === 0)
|
|
102
|
+
return true;
|
|
103
|
+
if (!haystack)
|
|
104
|
+
return false;
|
|
105
|
+
return needles.some((needle) => haystack.includes(needle));
|
|
106
|
+
}
|
|
107
|
+
function facetMatches(facet, atomFacet) {
|
|
108
|
+
if (facet.namespace !== atomFacet.namespace)
|
|
109
|
+
return false;
|
|
110
|
+
if (facet.key !== undefined && facet.key !== atomFacet.key)
|
|
111
|
+
return false;
|
|
112
|
+
if (facet.value !== undefined && facet.value !== atomFacet.value)
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
function matchesAnchors(record, query) {
|
|
117
|
+
const atom = record.atom;
|
|
118
|
+
const occurred = Date.parse(atom.occurredAt);
|
|
119
|
+
if (query.timeRange) {
|
|
120
|
+
if (query.timeRange.from !== undefined && occurred < Date.parse(query.timeRange.from))
|
|
121
|
+
return false;
|
|
122
|
+
if (query.timeRange.to !== undefined && occurred > Date.parse(query.timeRange.to))
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (query.scenes !== undefined && !matchesAny(atom.applicability?.scenes, query.scenes))
|
|
126
|
+
return false;
|
|
127
|
+
if (query.projects !== undefined) {
|
|
128
|
+
// v8 ignore next -- projectRefs 是必填字段
|
|
129
|
+
const pool = [...(atom.projectRefs ?? []), ...(atom.applicability?.projects ?? [])];
|
|
130
|
+
if (!matchesAny(pool, query.projects))
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (query.tasks !== undefined) {
|
|
134
|
+
// v8 ignore next -- subjectRefs 是必填字段
|
|
135
|
+
const pool = [...(atom.subjectRefs ?? []), ...(atom.applicability?.tasks ?? [])];
|
|
136
|
+
if (!matchesAny(pool, query.tasks))
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (query.facets !== undefined && query.facets.length > 0) {
|
|
140
|
+
const atomFacets = atom.facets;
|
|
141
|
+
const everyAnchorSatisfied = query.facets.every((anchor) => atomFacets.some((atomFacet) => facetMatches(anchor, atomFacet)));
|
|
142
|
+
if (!everyAnchorSatisfied)
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
/** Deterministic bounded recall index over a 1C.1b store. */
|
|
148
|
+
export function createMemoryRecallIndex(options) {
|
|
149
|
+
const store = options.store;
|
|
150
|
+
return {
|
|
151
|
+
query(query) {
|
|
152
|
+
// Exact-lookup fast path: a memoryId hit bypasses anchors, ordering,
|
|
153
|
+
// and scoring entirely (§5.2) but keeps owner scoping AND the suite
|
|
154
|
+
// read boundary — a legacy or foreign-suite atom is never resurrected
|
|
155
|
+
// through exact lookup.
|
|
156
|
+
if (query.memoryIdEquals !== undefined) {
|
|
157
|
+
const found = store.get(query.memoryIdEquals, { owner: query.owner });
|
|
158
|
+
if (query.suiteId === undefined) {
|
|
159
|
+
const items = found === undefined ? [] : [found];
|
|
160
|
+
return Object.freeze({
|
|
161
|
+
items: Object.freeze(items),
|
|
162
|
+
total: items.length,
|
|
163
|
+
offset: query.offset ?? 0,
|
|
164
|
+
limit: query.limit ?? DEFAULT_LIMIT,
|
|
165
|
+
lookupUsed: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
let suiteFilter = Object.freeze({ legacySkipped: 0, foreignSuiteSkipped: 0 });
|
|
169
|
+
let items = [];
|
|
170
|
+
if (found !== undefined) {
|
|
171
|
+
const visibility = memorySuiteVisibility(found.atom, query.suiteId);
|
|
172
|
+
if (visibility === "visible") {
|
|
173
|
+
items = [found];
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
suiteFilter = Object.freeze({
|
|
177
|
+
legacySkipped: visibility === "legacy" ? 1 : 0,
|
|
178
|
+
foreignSuiteSkipped: visibility === "foreign-suite" ? 1 : 0,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return Object.freeze({
|
|
183
|
+
items: Object.freeze(items),
|
|
184
|
+
total: items.length,
|
|
185
|
+
offset: query.offset ?? 0,
|
|
186
|
+
limit: query.limit ?? DEFAULT_LIMIT,
|
|
187
|
+
lookupUsed: true,
|
|
188
|
+
suiteFilter,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
validateMemoryRecallQuery(query);
|
|
192
|
+
const limit = query.limit ?? DEFAULT_LIMIT;
|
|
193
|
+
const offset = query.offset ?? 0;
|
|
194
|
+
// Suite-scoped reads go through listForSuite so excluded-record counts
|
|
195
|
+
// cover the owner's full ledger, not just the anchor-matched slice.
|
|
196
|
+
const suitePage = query.suiteId === undefined
|
|
197
|
+
? undefined
|
|
198
|
+
: store.listForSuite({ owner: query.owner, suiteId: query.suiteId }, query.scope);
|
|
199
|
+
const matches = (suitePage === undefined ? store.list({ owner: query.owner }, query.scope) : suitePage.records)
|
|
200
|
+
.filter((record) => matchesAnchors(record, query))
|
|
201
|
+
// Deterministic order: most recent first, memoryId as tie-break.
|
|
202
|
+
.sort((left, right) => {
|
|
203
|
+
const byTime = Date.parse(right.atom.occurredAt) - Date.parse(left.atom.occurredAt);
|
|
204
|
+
if (byTime !== 0)
|
|
205
|
+
return byTime;
|
|
206
|
+
// v8 ignore next -- memoryId 是 store 主键恒唯一,相等路径不可达
|
|
207
|
+
return left.atom.memoryId < right.atom.memoryId ? -1 : left.atom.memoryId > right.atom.memoryId ? 1 : 0;
|
|
208
|
+
});
|
|
209
|
+
const total = matches.length;
|
|
210
|
+
const items = matches.slice(offset, offset + limit);
|
|
211
|
+
return Object.freeze({
|
|
212
|
+
items: Object.freeze(items),
|
|
213
|
+
total,
|
|
214
|
+
offset,
|
|
215
|
+
limit,
|
|
216
|
+
lookupUsed: false,
|
|
217
|
+
...(suitePage === undefined ? {} : { suiteFilter: suitePage.filter }),
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=recall-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-index.js","sourceRoot":"","sources":["../../src/memory/recall-index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — minimal bounded recall index (1C.3).\n *\n * Deterministic anchor filtering over the 1C.1b store: time, scenes, projects,\n * tasks, and facets. Anchors are validated BEFORE querying (锚点校验);\n * narrowing is a pure query merge; results are sorted deterministically\n * (recency desc, memoryId asc tie-break) and paginated with limit/offset.\n * No Memory Network, vector retrieval, or semantic reranking.\n */\nimport type { MemoryScopeV1 } from \"./foundation.ts\";\nimport { type MemorySuiteFilterStatsV1, memorySuiteVisibility } from \"./foundation.ts\";\nimport type { CommittedMemoryV1, MemoryStoreV1 } from \"./store.ts\";\n\nexport type { MemoryScopeV1 };\n\nexport interface MemoryRecallQueryV1 {\n\treadonly owner: string;\n\treadonly scope?: MemoryScopeV1;\n\t/**\n\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible in\n\t * this suite are recalled; legacy (suiteId-less) atoms are skipped\n\t * fail-safe and counted on the page. This is a boundary, not an anchor —\n\t * {@link narrowMemoryRecallQuery} never widens or merges it.\n\t */\n\treadonly suiteId?: string;\n\t/** Inclusive ISO-8601 bounds on the atom's `occurredAt`. */\n\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\treadonly scenes?: readonly string[];\n\treadonly projects?: readonly string[];\n\treadonly tasks?: readonly string[];\n\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t/** Page size. Default 20, maximum 100. */\n\treadonly limit?: number;\n\treadonly offset?: number;\n\t/**\n\t * Exact-lookup fast path (记忆系统设计 §5.2): when set, the index locates\n\t * this memoryId directly — attention/state/score filters do not apply.\n\t */\n\treadonly memoryIdEquals?: string;\n}\n\nconst DEFAULT_LIMIT = 20;\nconst MAX_LIMIT = 100;\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\nfunction assertIsoTimestamp(value: unknown, label: string): asserts value is string {\n\tassertNonEmptyString(value, label);\n\tif (Number.isNaN(Date.parse(value))) throw new Error(`${label} must be an ISO-8601 timestamp`);\n}\n\nfunction assertStringArray(value: unknown, label: string): asserts value is readonly string[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tfor (const [index, item] of value.entries()) {\n\t\tassertNonEmptyString(item, `${label}[${index}]`);\n\t}\n}\n\n/**\n * Anchor validation happens before any query runs: malformed time bounds,\n * empty anchor entries, or out-of-range paging are explicit errors instead of\n * silently empty results.\n */\nexport function validateMemoryRecallQuery(query: MemoryRecallQueryV1): void {\n\tassertNonEmptyString(query.owner, \"Recall query.owner\");\n\tif (query.suiteId !== undefined && (typeof query.suiteId !== \"string\" || query.suiteId.trim() === \"\")) {\n\t\tthrow new Error(\"Recall query.suiteId must be a non-empty string\");\n\t}\n\tif (query.scope !== undefined && ![\"session\", \"cycle\", \"long-term\"].includes(query.scope)) {\n\t\tthrow new Error(`Recall query.scope is invalid: ${String(query.scope)}`);\n\t}\n\tif (query.timeRange !== undefined) {\n\t\tif (query.timeRange.from !== undefined) assertIsoTimestamp(query.timeRange.from, \"Recall query.timeRange.from\");\n\t\tif (query.timeRange.to !== undefined) assertIsoTimestamp(query.timeRange.to, \"Recall query.timeRange.to\");\n\t\tif (\n\t\t\tquery.timeRange.from !== undefined &&\n\t\t\tquery.timeRange.to !== undefined &&\n\t\t\tDate.parse(query.timeRange.from) > Date.parse(query.timeRange.to)\n\t\t) {\n\t\t\tthrow new Error(\"Recall query.timeRange.from must not be after timeRange.to\");\n\t\t}\n\t}\n\tfor (const key of [\"scenes\", \"projects\", \"tasks\"] as const) {\n\t\tconst value = query[key];\n\t\tif (value !== undefined) {\n\t\t\tassertStringArray(value, `Recall query.${key}`);\n\t\t\tif (value.length === 0) throw new Error(`Recall query.${key} must not be an empty array`);\n\t\t}\n\t}\n\tif (query.facets !== undefined) {\n\t\tif (!Array.isArray(query.facets)) throw new Error(\"Recall query.facets must be an array\");\n\t\tfor (const [index, facet] of query.facets.entries()) {\n\t\t\tconst label = `Recall query.facets[${index}]`;\n\t\t\tif (facet === null || typeof facet !== \"object\" || Array.isArray(facet)) {\n\t\t\t\tthrow new Error(`${label} must be a plain object`);\n\t\t\t}\n\t\t\tconst record = facet as Record<string, unknown>;\n\t\t\tassertNonEmptyString(record.namespace, `${label}.namespace`);\n\t\t\tif (record.key !== undefined) assertNonEmptyString(record.key, `${label}.key`);\n\t\t\tif (record.value !== undefined) assertNonEmptyString(record.value, `${label}.value`);\n\t\t}\n\t}\n\tif (\n\t\tquery.limit !== undefined &&\n\t\t(!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > MAX_LIMIT)\n\t) {\n\t\tthrow new Error(`Recall query.limit must be a safe integer between 1 and ${MAX_LIMIT}`);\n\t}\n\tif (query.offset !== undefined && (!Number.isSafeInteger(query.offset) || query.offset < 0)) {\n\t\tthrow new Error(\"Recall query.offset must be a non-negative safe integer\");\n\t}\n}\n\n/** Pure narrowing: merges extra anchors into the query without running it. */\nexport function narrowMemoryRecallQuery(\n\tquery: MemoryRecallQueryV1,\n\textra: {\n\t\treadonly scope?: MemoryScopeV1;\n\t\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\t\treadonly scenes?: readonly string[];\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t},\n): MemoryRecallQueryV1 {\n\tconst nonEmpty = (list: readonly string[] | undefined): readonly string[] | undefined =>\n\t\tlist !== undefined && list.length > 0 ? [...list] : undefined;\n\tconst scenes = nonEmpty([...(query.scenes ?? []), ...(extra.scenes ?? [])]);\n\tconst projects = nonEmpty([...(query.projects ?? []), ...(extra.projects ?? [])]);\n\tconst tasks = nonEmpty([...(query.tasks ?? []), ...(extra.tasks ?? [])]);\n\tconst facets = [...(query.facets ?? []), ...(extra.facets ?? [])];\n\tconst timeRange =\n\t\tquery.timeRange === undefined && extra.timeRange === undefined\n\t\t\t? undefined\n\t\t\t: {\n\t\t\t\t\tfrom: query.timeRange?.from ?? extra.timeRange?.from,\n\t\t\t\t\tto: query.timeRange?.to ?? extra.timeRange?.to,\n\t\t\t\t};\n\treturn {\n\t\t...query,\n\t\t...(extra.scope === undefined ? {} : { scope: extra.scope }),\n\t\t...(timeRange === undefined ? {} : { timeRange }),\n\t\t...(scenes === undefined ? {} : { scenes }),\n\t\t...(projects === undefined ? {} : { projects }),\n\t\t...(tasks === undefined ? {} : { tasks }),\n\t\t...(facets.length === 0 ? {} : { facets }),\n\t};\n}\n\nfunction matchesAny(haystack: readonly string[] | undefined, needles: readonly string[]): boolean {\n\t// v8 ignore next -- 锚点校验先于匹配执行,空数组已被拒绝\n\tif (needles.length === 0) return true;\n\tif (!haystack) return false;\n\treturn needles.some((needle) => haystack.includes(needle));\n}\n\nfunction facetMatches(\n\tfacet: { readonly namespace: string; readonly key?: string; readonly value?: string },\n\tatomFacet: { readonly namespace: string; readonly key: string; readonly value: string },\n): boolean {\n\tif (facet.namespace !== atomFacet.namespace) return false;\n\tif (facet.key !== undefined && facet.key !== atomFacet.key) return false;\n\tif (facet.value !== undefined && facet.value !== atomFacet.value) return false;\n\treturn true;\n}\n\nfunction matchesAnchors(\n\trecord: CommittedMemoryV1,\n\tquery: Pick<MemoryRecallQueryV1, \"timeRange\" | \"scenes\" | \"projects\" | \"tasks\" | \"facets\">,\n): boolean {\n\tconst atom = record.atom;\n\tconst occurred = Date.parse(atom.occurredAt);\n\tif (query.timeRange) {\n\t\tif (query.timeRange.from !== undefined && occurred < Date.parse(query.timeRange.from)) return false;\n\t\tif (query.timeRange.to !== undefined && occurred > Date.parse(query.timeRange.to)) return false;\n\t}\n\tif (query.scenes !== undefined && !matchesAny(atom.applicability?.scenes, query.scenes)) return false;\n\tif (query.projects !== undefined) {\n\t\t// v8 ignore next -- projectRefs 是必填字段\n\t\tconst pool = [...(atom.projectRefs ?? []), ...(atom.applicability?.projects ?? [])];\n\t\tif (!matchesAny(pool, query.projects)) return false;\n\t}\n\tif (query.tasks !== undefined) {\n\t\t// v8 ignore next -- subjectRefs 是必填字段\n\t\tconst pool = [...(atom.subjectRefs ?? []), ...(atom.applicability?.tasks ?? [])];\n\t\tif (!matchesAny(pool, query.tasks)) return false;\n\t}\n\tif (query.facets !== undefined && query.facets.length > 0) {\n\t\tconst atomFacets = atom.facets;\n\t\tconst everyAnchorSatisfied = query.facets.every((anchor) =>\n\t\t\tatomFacets.some((atomFacet) => facetMatches(anchor, atomFacet)),\n\t\t);\n\t\tif (!everyAnchorSatisfied) return false;\n\t}\n\treturn true;\n}\n\nexport interface MemoryRecallPageV1 {\n\treadonly items: readonly CommittedMemoryV1[];\n\treadonly total: number;\n\treadonly offset: number;\n\treadonly limit: number;\n\t/** True when the page came from the exact-lookup fast path (§5.2). */\n\treadonly lookupUsed: boolean;\n\t/**\n\t * Present only when the query carried a suiteId: how many legacy and\n\t * foreign-suite records the suite read boundary excluded (设计 §11).\n\t */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n}\n\n/** Deterministic bounded recall index over a 1C.1b store. */\nexport function createMemoryRecallIndex(options: { readonly store: MemoryStoreV1 }): {\n\tquery(query: MemoryRecallQueryV1): MemoryRecallPageV1;\n} {\n\tconst store = options.store;\n\treturn {\n\t\tquery(query: MemoryRecallQueryV1): MemoryRecallPageV1 {\n\t\t\t// Exact-lookup fast path: a memoryId hit bypasses anchors, ordering,\n\t\t\t// and scoring entirely (§5.2) but keeps owner scoping AND the suite\n\t\t\t// read boundary — a legacy or foreign-suite atom is never resurrected\n\t\t\t// through exact lookup.\n\t\t\tif (query.memoryIdEquals !== undefined) {\n\t\t\t\tconst found = store.get(query.memoryIdEquals, { owner: query.owner });\n\t\t\t\tif (query.suiteId === undefined) {\n\t\t\t\t\tconst items = found === undefined ? [] : [found];\n\t\t\t\t\treturn Object.freeze({\n\t\t\t\t\t\titems: Object.freeze(items),\n\t\t\t\t\t\ttotal: items.length,\n\t\t\t\t\t\toffset: query.offset ?? 0,\n\t\t\t\t\t\tlimit: query.limit ?? DEFAULT_LIMIT,\n\t\t\t\t\t\tlookupUsed: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tlet suiteFilter: MemorySuiteFilterStatsV1 = Object.freeze({ legacySkipped: 0, foreignSuiteSkipped: 0 });\n\t\t\t\tlet items: readonly CommittedMemoryV1[] = [];\n\t\t\t\tif (found !== undefined) {\n\t\t\t\t\tconst visibility = memorySuiteVisibility(found.atom, query.suiteId);\n\t\t\t\t\tif (visibility === \"visible\") {\n\t\t\t\t\t\titems = [found];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsuiteFilter = Object.freeze({\n\t\t\t\t\t\t\tlegacySkipped: visibility === \"legacy\" ? 1 : 0,\n\t\t\t\t\t\t\tforeignSuiteSkipped: visibility === \"foreign-suite\" ? 1 : 0,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn Object.freeze({\n\t\t\t\t\titems: Object.freeze(items),\n\t\t\t\t\ttotal: items.length,\n\t\t\t\t\toffset: query.offset ?? 0,\n\t\t\t\t\tlimit: query.limit ?? DEFAULT_LIMIT,\n\t\t\t\t\tlookupUsed: true,\n\t\t\t\t\tsuiteFilter,\n\t\t\t\t});\n\t\t\t}\n\t\t\tvalidateMemoryRecallQuery(query);\n\t\t\tconst limit = query.limit ?? DEFAULT_LIMIT;\n\t\t\tconst offset = query.offset ?? 0;\n\t\t\t// Suite-scoped reads go through listForSuite so excluded-record counts\n\t\t\t// cover the owner's full ledger, not just the anchor-matched slice.\n\t\t\tconst suitePage =\n\t\t\t\tquery.suiteId === undefined\n\t\t\t\t\t? undefined\n\t\t\t\t\t: store.listForSuite({ owner: query.owner, suiteId: query.suiteId }, query.scope);\n\t\t\tconst matches = (suitePage === undefined ? store.list({ owner: query.owner }, query.scope) : suitePage.records)\n\t\t\t\t.filter((record) => matchesAnchors(record, query))\n\t\t\t\t// Deterministic order: most recent first, memoryId as tie-break.\n\t\t\t\t.sort((left, right) => {\n\t\t\t\t\tconst byTime = Date.parse(right.atom.occurredAt) - Date.parse(left.atom.occurredAt);\n\t\t\t\t\tif (byTime !== 0) return byTime;\n\t\t\t\t\t// v8 ignore next -- memoryId 是 store 主键恒唯一,相等路径不可达\n\t\t\t\t\treturn left.atom.memoryId < right.atom.memoryId ? -1 : left.atom.memoryId > right.atom.memoryId ? 1 : 0;\n\t\t\t\t});\n\t\t\tconst total = matches.length;\n\t\t\tconst items = matches.slice(offset, offset + limit);\n\t\t\treturn Object.freeze({\n\t\t\t\titems: Object.freeze(items),\n\t\t\t\ttotal,\n\t\t\t\toffset,\n\t\t\t\tlimit,\n\t\t\t\tlookupUsed: false,\n\t\t\t\t...(suitePage === undefined ? {} : { suiteFilter: suitePage.filter }),\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — recall budgets and bounded recall packet (1C.5a).
|
|
3
|
+
*
|
|
4
|
+
* Three-layer budgets (candidateBudget / modelInspectionLimit /
|
|
5
|
+
* finalResultLimit) plus host hard limits govern every recall. Resolution
|
|
6
|
+
* rules are frozen (记忆系统设计.md §6.4):
|
|
7
|
+
* - Caller-provided values may only TIGHTEN limits. Negative, non-finite,
|
|
8
|
+
* non-integer, or over-hard-limit caller values are explicitly rejected —
|
|
9
|
+
* never silently clamped.
|
|
10
|
+
* - Only Profile/Policy default values may be clamped down to host limits,
|
|
11
|
+
* and every such adjustment is recorded with its reason.
|
|
12
|
+
* - Hitting any limit yields `omittedCount`, `truncated: true` with a
|
|
13
|
+
* `truncationScope` and narrowing hints — candidates are never silently
|
|
14
|
+
* passed to the main agent.
|
|
15
|
+
* V1 recall is deterministic: no model inspection happens in this slice, the
|
|
16
|
+
* inspection limit still bounds the deterministic stage.
|
|
17
|
+
*/
|
|
18
|
+
import type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from "./foundation.ts";
|
|
19
|
+
export interface MemoryRecallBudgetsV1 {
|
|
20
|
+
readonly candidateBudget: number;
|
|
21
|
+
readonly modelInspectionLimit: number;
|
|
22
|
+
readonly finalResultLimit: number;
|
|
23
|
+
readonly maxRelationHops: number;
|
|
24
|
+
readonly maxModelCalls: number;
|
|
25
|
+
readonly maxOutputTokens: number;
|
|
26
|
+
readonly maxOutputBytes: number;
|
|
27
|
+
readonly maxRounds: number;
|
|
28
|
+
}
|
|
29
|
+
export type MemoryRecallBudgetContributionV1 = Partial<MemoryRecallBudgetsV1>;
|
|
30
|
+
export declare const MEMORY_RECALL_BUDGET_FIELDS: readonly ["candidateBudget", "modelInspectionLimit", "finalResultLimit", "maxRelationHops", "maxModelCalls", "maxOutputTokens", "maxOutputBytes", "maxRounds"];
|
|
31
|
+
export type MemoryRecallBudgetField = (typeof MEMORY_RECALL_BUDGET_FIELDS)[number];
|
|
32
|
+
export interface MemoryRecallBudgetsResolution {
|
|
33
|
+
readonly budgets: MemoryRecallBudgetsV1;
|
|
34
|
+
/** Recorded default-clamp decisions (Policy defaults narrowed by host limits). */
|
|
35
|
+
readonly adjustments: readonly string[];
|
|
36
|
+
/** Caller contributions that were accepted. */
|
|
37
|
+
readonly callerTightened: readonly MemoryRecallBudgetField[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the effective budgets. Resolution priority: caller request (tighten
|
|
41
|
+
* only) → Policy defaults (clamped to host limits, adjustments recorded) →
|
|
42
|
+
* host hard limits. Caller values over the host limit are explicitly rejected.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveMemoryRecallBudgets(input: {
|
|
45
|
+
readonly requested?: MemoryRecallBudgetContributionV1;
|
|
46
|
+
readonly policyDefaults: MemoryRecallBudgetsV1;
|
|
47
|
+
readonly hostLimits: MemoryRecallBudgetsV1;
|
|
48
|
+
}): MemoryRecallBudgetsResolution;
|
|
49
|
+
export interface MemoryRecallPacketBudgetUsageV1 {
|
|
50
|
+
readonly candidateCount: number;
|
|
51
|
+
readonly modelInspectedCount: number;
|
|
52
|
+
readonly finalResultCount: number;
|
|
53
|
+
}
|
|
54
|
+
export type MemoryRecallTruncationScopeV1 = "none" | "operation" | "round" | "final_packet";
|
|
55
|
+
export interface MemoryRecallPacketErrorV1 {
|
|
56
|
+
readonly code: string;
|
|
57
|
+
readonly message: string;
|
|
58
|
+
readonly stage: "authorize" | "anchor" | "query" | "model" | "validate" | "serialize" | "purge";
|
|
59
|
+
readonly retryable: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface MemoryRecallPacketV1 {
|
|
62
|
+
readonly operationId: string;
|
|
63
|
+
readonly status: "completed" | "empty" | "needs_narrowing" | "failed";
|
|
64
|
+
readonly facts: readonly {
|
|
65
|
+
readonly memoryId: string;
|
|
66
|
+
readonly contentRevision: string;
|
|
67
|
+
readonly statement: string;
|
|
68
|
+
readonly confidence: number;
|
|
69
|
+
}[];
|
|
70
|
+
/** Query plan facts (设计 §6.3): what the recall actually did. */
|
|
71
|
+
readonly queryPlan: {
|
|
72
|
+
readonly lookupUsed: boolean;
|
|
73
|
+
readonly filtersApplied: readonly string[];
|
|
74
|
+
/** Version of the deterministic scorer that ordered the facts. */
|
|
75
|
+
readonly scorerVersion: string;
|
|
76
|
+
};
|
|
77
|
+
/** Model-call attempts spent (V1 deterministic: 0 or 1). */
|
|
78
|
+
readonly attempts: number;
|
|
79
|
+
readonly narrowingHints: readonly string[];
|
|
80
|
+
readonly omittedCount: number;
|
|
81
|
+
readonly truncated: boolean;
|
|
82
|
+
readonly truncationScope: MemoryRecallTruncationScopeV1;
|
|
83
|
+
/** Present only on failed packets — failures resolve as structured packets, never throw. */
|
|
84
|
+
readonly error?: MemoryRecallPacketErrorV1;
|
|
85
|
+
/**
|
|
86
|
+
* Present only when the recall query carried a suiteId: how many legacy and
|
|
87
|
+
* foreign-suite records the suite read boundary excluded (方案系统设计
|
|
88
|
+
* §6.1/§11, 读取边界惰性可观测).
|
|
89
|
+
*/
|
|
90
|
+
readonly suiteFilter?: MemorySuiteFilterStatsV1;
|
|
91
|
+
readonly budget: {
|
|
92
|
+
readonly candidateBudget: number;
|
|
93
|
+
readonly modelInspectionLimit: number;
|
|
94
|
+
readonly finalResultLimit: number;
|
|
95
|
+
readonly used: {
|
|
96
|
+
readonly candidateCount: number;
|
|
97
|
+
readonly modelInspectedCount: number;
|
|
98
|
+
readonly finalResultCount: number;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
export interface MemoryRecallPacketInputV1 {
|
|
103
|
+
readonly operationId: string;
|
|
104
|
+
readonly candidates: readonly MemoryAtomV1[];
|
|
105
|
+
readonly budgets: MemoryRecallBudgetsV1;
|
|
106
|
+
/** Deterministic statement projector; defaults to the payload JSON. */
|
|
107
|
+
readonly buildStatement?: (atom: MemoryAtomV1) => string;
|
|
108
|
+
/** Suite read-boundary skip counters; present only on suite-scoped recalls. */
|
|
109
|
+
readonly suiteFilter?: MemorySuiteFilterStatsV1;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Builds the bounded recall packet from deterministic candidates:
|
|
113
|
+
* 1. candidates over `candidateBudget` → `needs_narrowing` with hints,
|
|
114
|
+
* omittedCount = full candidate count (nothing is passed on).
|
|
115
|
+
* 2. `modelInspectionLimit` bounds the inspected stage (V1 inspects
|
|
116
|
+
* deterministically); overflow is omitted with `round` scope.
|
|
117
|
+
* 3. `finalResultLimit` bounds the packet facts; overflow is omitted with
|
|
118
|
+
* `final_packet` scope.
|
|
119
|
+
*/
|
|
120
|
+
export declare function buildMemoryRecallPacketV1(input: MemoryRecallPacketInputV1): MemoryRecallPacketV1;
|
|
121
|
+
//# sourceMappingURL=recall-packet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-packet.d.ts","sourceRoot":"","sources":["../../src/memory/recall-packet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — recall budgets and bounded recall packet (1C.5a).\n *\n * Three-layer budgets (candidateBudget / modelInspectionLimit /\n * finalResultLimit) plus host hard limits govern every recall. Resolution\n * rules are frozen (记忆系统设计.md §6.4):\n * - Caller-provided values may only TIGHTEN limits. Negative, non-finite,\n * non-integer, or over-hard-limit caller values are explicitly rejected —\n * never silently clamped.\n * - Only Profile/Policy default values may be clamped down to host limits,\n * and every such adjustment is recorded with its reason.\n * - Hitting any limit yields `omittedCount`, `truncated: true` with a\n * `truncationScope` and narrowing hints — candidates are never silently\n * passed to the main agent.\n * V1 recall is deterministic: no model inspection happens in this slice, the\n * inspection limit still bounds the deterministic stage.\n */\nimport type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\n\nexport interface MemoryRecallBudgetsV1 {\n\treadonly candidateBudget: number;\n\treadonly modelInspectionLimit: number;\n\treadonly finalResultLimit: number;\n\treadonly maxRelationHops: number;\n\treadonly maxModelCalls: number;\n\treadonly maxOutputTokens: number;\n\treadonly maxOutputBytes: number;\n\treadonly maxRounds: number;\n}\n\nexport type MemoryRecallBudgetContributionV1 = Partial<MemoryRecallBudgetsV1>;\n\nexport const MEMORY_RECALL_BUDGET_FIELDS = [\n\t\"candidateBudget\",\n\t\"modelInspectionLimit\",\n\t\"finalResultLimit\",\n\t\"maxRelationHops\",\n\t\"maxModelCalls\",\n\t\"maxOutputTokens\",\n\t\"maxOutputBytes\",\n\t\"maxRounds\",\n] as const;\n\nexport type MemoryRecallBudgetField = (typeof MEMORY_RECALL_BUDGET_FIELDS)[number];\n\nfunction assertBudgetValue(value: unknown, field: MemoryRecallBudgetField, label: string): asserts value is number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || !Number.isSafeInteger(value) || value < 1) {\n\t\tthrow new Error(`${label}.${field} must be a positive safe integer`);\n\t}\n}\n\nexport interface MemoryRecallBudgetsResolution {\n\treadonly budgets: MemoryRecallBudgetsV1;\n\t/** Recorded default-clamp decisions (Policy defaults narrowed by host limits). */\n\treadonly adjustments: readonly string[];\n\t/** Caller contributions that were accepted. */\n\treadonly callerTightened: readonly MemoryRecallBudgetField[];\n}\n\n/**\n * Resolves the effective budgets. Resolution priority: caller request (tighten\n * only) → Policy defaults (clamped to host limits, adjustments recorded) →\n * host hard limits. Caller values over the host limit are explicitly rejected.\n */\nexport function resolveMemoryRecallBudgets(input: {\n\treadonly requested?: MemoryRecallBudgetContributionV1;\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n}): MemoryRecallBudgetsResolution {\n\tconst adjustments: string[] = [];\n\tconst callerTightened: MemoryRecallBudgetField[] = [];\n\tconst budgets = {} as Record<MemoryRecallBudgetField, number>;\n\tfor (const field of MEMORY_RECALL_BUDGET_FIELDS) {\n\t\tconst requested = input.requested?.[field];\n\t\tif (requested !== undefined) {\n\t\t\tassertBudgetValue(requested, field, \"Requested recall budgets\");\n\t\t\tconst hardLimit = input.hostLimits[field];\n\t\t\tif (requested > hardLimit) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Requested recall budgets.${field} (${requested}) exceeds the host hard limit (${hardLimit}); callers may only tighten limits`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tbudgets[field] = requested;\n\t\t\tcallerTightened.push(field);\n\t\t\tcontinue;\n\t\t}\n\t\tconst hardLimit = input.hostLimits[field];\n\t\tconst policyDefault = input.policyDefaults[field];\n\t\tassertBudgetValue(policyDefault, field, \"Policy default recall budgets\");\n\t\tassertBudgetValue(hardLimit, field, \"Host limit recall budgets\");\n\t\tif (policyDefault > hardLimit) {\n\t\t\tadjustments.push(`Policy default ${field}=${policyDefault} clamped to host limit ${hardLimit}`);\n\t\t\tbudgets[field] = hardLimit;\n\t\t} else {\n\t\t\tbudgets[field] = policyDefault;\n\t\t}\n\t}\n\treturn {\n\t\tbudgets: Object.freeze(budgets),\n\t\tadjustments: Object.freeze(adjustments),\n\t\tcallerTightened: Object.freeze(callerTightened),\n\t};\n}\n\nexport interface MemoryRecallPacketBudgetUsageV1 {\n\treadonly candidateCount: number;\n\treadonly modelInspectedCount: number;\n\treadonly finalResultCount: number;\n}\n\nexport type MemoryRecallTruncationScopeV1 = \"none\" | \"operation\" | \"round\" | \"final_packet\";\n\nexport interface MemoryRecallPacketErrorV1 {\n\treadonly code: string;\n\treadonly message: string;\n\treadonly stage: \"authorize\" | \"anchor\" | \"query\" | \"model\" | \"validate\" | \"serialize\" | \"purge\";\n\treadonly retryable: boolean;\n}\n\nexport interface MemoryRecallPacketV1 {\n\treadonly operationId: string;\n\treadonly status: \"completed\" | \"empty\" | \"needs_narrowing\" | \"failed\";\n\treadonly facts: readonly {\n\t\treadonly memoryId: string;\n\t\treadonly contentRevision: string;\n\t\treadonly statement: string;\n\t\treadonly confidence: number;\n\t}[];\n\t/** Query plan facts (设计 §6.3): what the recall actually did. */\n\treadonly queryPlan: {\n\t\treadonly lookupUsed: boolean;\n\t\treadonly filtersApplied: readonly string[];\n\t\t/** Version of the deterministic scorer that ordered the facts. */\n\t\treadonly scorerVersion: string;\n\t};\n\t/** Model-call attempts spent (V1 deterministic: 0 or 1). */\n\treadonly attempts: number;\n\treadonly narrowingHints: readonly string[];\n\treadonly omittedCount: number;\n\treadonly truncated: boolean;\n\treadonly truncationScope: MemoryRecallTruncationScopeV1;\n\t/** Present only on failed packets — failures resolve as structured packets, never throw. */\n\treadonly error?: MemoryRecallPacketErrorV1;\n\t/**\n\t * Present only when the recall query carried a suiteId: how many legacy and\n\t * foreign-suite records the suite read boundary excluded (方案系统设计\n\t * §6.1/§11, 读取边界惰性可观测).\n\t */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\treadonly budget: {\n\t\treadonly candidateBudget: number;\n\t\treadonly modelInspectionLimit: number;\n\t\treadonly finalResultLimit: number;\n\t\treadonly used: {\n\t\t\treadonly candidateCount: number;\n\t\t\treadonly modelInspectedCount: number;\n\t\t\treadonly finalResultCount: number;\n\t\t};\n\t};\n}\n\nfunction defaultStatement(atom: MemoryAtomV1): string {\n\treturn JSON.stringify(atom.payload);\n}\n\nexport interface MemoryRecallPacketInputV1 {\n\treadonly operationId: string;\n\treadonly candidates: readonly MemoryAtomV1[];\n\treadonly budgets: MemoryRecallBudgetsV1;\n\t/** Deterministic statement projector; defaults to the payload JSON. */\n\treadonly buildStatement?: (atom: MemoryAtomV1) => string;\n\t/** Suite read-boundary skip counters; present only on suite-scoped recalls. */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n}\n\n/**\n * Builds the bounded recall packet from deterministic candidates:\n * 1. candidates over `candidateBudget` → `needs_narrowing` with hints,\n * omittedCount = full candidate count (nothing is passed on).\n * 2. `modelInspectionLimit` bounds the inspected stage (V1 inspects\n * deterministically); overflow is omitted with `round` scope.\n * 3. `finalResultLimit` bounds the packet facts; overflow is omitted with\n * `final_packet` scope.\n */\nexport function buildMemoryRecallPacketV1(input: MemoryRecallPacketInputV1): MemoryRecallPacketV1 {\n\tconst budgets = input.budgets;\n\tconst buildStatement = input.buildStatement ?? defaultStatement;\n\n\tif (input.candidates.length > budgets.candidateBudget) {\n\t\treturn Object.freeze({\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"needs_narrowing\" as const,\n\t\t\tfacts: Object.freeze([]),\n\t\t\tnarrowingHints: Object.freeze([\n\t\t\t\t`Candidate count ${input.candidates.length} exceeds candidateBudget ${budgets.candidateBudget}`,\n\t\t\t\t\"Add time, scene, project, task, or facet anchors to narrow the query\",\n\t\t\t]),\n\t\t\tomittedCount: input.candidates.length,\n\t\t\ttruncated: true,\n\t\t\ttruncationScope: \"round\" as const,\n\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\t\tattempts: 0,\n\t\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\n\t\t\tbudget: Object.freeze({\n\t\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\t\tused: Object.freeze({\n\t\t\t\t\tcandidateCount: input.candidates.length,\n\t\t\t\t\tmodelInspectedCount: 0,\n\t\t\t\t\tfinalResultCount: 0,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n\n\tconst inspected = input.candidates.slice(0, budgets.modelInspectionLimit);\n\tconst factsSource = inspected.slice(0, budgets.finalResultLimit);\n\tconst facts = factsSource.map((atom) => ({\n\t\tmemoryId: atom.memoryId,\n\t\tcontentRevision: atom.contentRevision,\n\t\tstatement: buildStatement(atom),\n\t\tconfidence: atom.confidence,\n\t}));\n\tconst omittedFromFinal = inspected.length - facts.length;\n\tconst omittedFromInspection = input.candidates.length - inspected.length;\n\tconst omittedCount = omittedFromFinal + omittedFromInspection;\n\n\tif (input.candidates.length === 0) {\n\t\treturn Object.freeze({\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"empty\" as const,\n\t\t\tfacts: Object.freeze([]),\n\t\t\tnarrowingHints: Object.freeze([]),\n\t\t\tomittedCount: 0,\n\t\t\ttruncated: false,\n\t\t\ttruncationScope: \"none\" as const,\n\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\t\tattempts: 0,\n\t\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\n\t\t\tbudget: Object.freeze({\n\t\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\t\tused: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),\n\t\t\t}),\n\t\t});\n\t}\n\n\tconst status: MemoryRecallPacketV1[\"status\"] = facts.length > 0 ? \"completed\" : \"empty\";\n\treturn Object.freeze({\n\t\toperationId: input.operationId,\n\t\tstatus,\n\t\tfacts: Object.freeze(facts),\n\t\tnarrowingHints: Object.freeze([]),\n\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\tattempts: 0,\n\t\tomittedCount,\n\t\ttruncated: omittedCount > 0,\n\t\ttruncationScope:\n\t\t\tomittedCount === 0\n\t\t\t\t? (\"none\" as const)\n\t\t\t\t: omittedFromInspection > 0\n\t\t\t\t\t? (\"round\" as const)\n\t\t\t\t\t: (\"final_packet\" as const),\n\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\n\t\tbudget: Object.freeze({\n\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\tused: Object.freeze({\n\t\t\t\tcandidateCount: input.candidates.length,\n\t\t\t\tmodelInspectedCount: inspected.length,\n\t\t\t\tfinalResultCount: facts.length,\n\t\t\t}),\n\t\t}),\n\t});\n}\n"]}
|