@intentface/latch-memory 0.9.1
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/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/extraction-key.d.ts +20 -0
- package/dist/extraction-key.d.ts.map +1 -0
- package/dist/extraction-key.js +43 -0
- package/dist/extraction-key.js.map +1 -0
- package/dist/index-file.d.ts +77 -0
- package/dist/index-file.d.ts.map +1 -0
- package/dist/index-file.js +169 -0
- package/dist/index-file.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs/types.d.ts +133 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +12 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/prompts/consolidator.d.ts +18 -0
- package/dist/prompts/consolidator.d.ts.map +1 -0
- package/dist/prompts/consolidator.js +76 -0
- package/dist/prompts/consolidator.js.map +1 -0
- package/dist/prompts/extractor.d.ts +34 -0
- package/dist/prompts/extractor.d.ts.map +1 -0
- package/dist/prompts/extractor.js +60 -0
- package/dist/prompts/extractor.js.map +1 -0
- package/dist/prompts/inject.d.ts +20 -0
- package/dist/prompts/inject.d.ts.map +1 -0
- package/dist/prompts/inject.js +58 -0
- package/dist/prompts/inject.js.map +1 -0
- package/dist/provider.d.ts +47 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +101 -0
- package/dist/provider.js.map +1 -0
- package/dist/sanitize.d.ts +29 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +88 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/schema.d.ts +60 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +144 -0
- package/dist/schema.js.map +1 -0
- package/dist/scope.d.ts +62 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +136 -0
- package/dist/scope.js.map +1 -0
- package/dist/search-evidence.d.ts +34 -0
- package/dist/search-evidence.d.ts.map +1 -0
- package/dist/search-evidence.js +40 -0
- package/dist/search-evidence.js.map +1 -0
- package/dist/store/loredex.d.ts +61 -0
- package/dist/store/loredex.d.ts.map +1 -0
- package/dist/store/loredex.js +558 -0
- package/dist/store/loredex.js.map +1 -0
- package/dist/store/memory.d.ts +31 -0
- package/dist/store/memory.d.ts.map +1 -0
- package/dist/store/memory.js +156 -0
- package/dist/store/memory.js.map +1 -0
- package/dist/tools.d.ts +91 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +389 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Intentface
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# @intentface/latch-memory
|
|
2
|
+
|
|
3
|
+
Pluggable, platform-managed memory for Latch agents. Declare `memory` on an
|
|
4
|
+
agent and the platform does all upkeep:
|
|
5
|
+
|
|
6
|
+
1. **Always-loaded index** — a compiled `MEMORY.md` (≤100 lines, one fact per
|
|
7
|
+
line with date, provenance, and source links) is injected into the system
|
|
8
|
+
prompt every turn, plus standing instructions for the tools.
|
|
9
|
+
2. **Episodic store + recall** — immutable, timestamped episode notes live in
|
|
10
|
+
the agent's own connection workspace: per-user scopes under the caller's
|
|
11
|
+
private root (`__private_root/_memory/<agent|_user>/u-<id>/`), `agent-org`
|
|
12
|
+
under the shared `_memory/<agent>/org/`;
|
|
13
|
+
the agent searches them on demand with `memory_search` (every recall is
|
|
14
|
+
usage-tracked — the retention signal) and saves explicit "remember this"
|
|
15
|
+
notes with `memory_save`.
|
|
16
|
+
3. **Background consolidation** — a cron sweep extracts durable facts from
|
|
17
|
+
idle chats (`memory-extractor`, cheap model) and folds pending episodes
|
|
18
|
+
into the index (`memory-consolidator`), superseding and expiring entries.
|
|
19
|
+
|
|
20
|
+
## Design invariants
|
|
21
|
+
|
|
22
|
+
- **Scopes** (`agent-user` | `agent-org` | `user`) are hard boundaries: tools
|
|
23
|
+
are path-locked at construction, and consolidation never crosses a scope.
|
|
24
|
+
Per-user scopes live under the Loredex **private root** (owner-only ACL):
|
|
25
|
+
`__private_root/_memory/<agent>/u-<id>` for `agent-user`,
|
|
26
|
+
`__private_root/_memory/_user/u-<id>` for `user`. The `u-<id>` leaf is kept
|
|
27
|
+
even though the private root is already per-user: it keeps roots globally
|
|
28
|
+
unique (scope keys stay `${connection}:${root}`) and keeps users' stores
|
|
29
|
+
apart on a workspace-shared connection, where every caller resolves the
|
|
30
|
+
same private root.
|
|
31
|
+
- **Non-destructive consolidation**: episodes are immutable; only the index is
|
|
32
|
+
rewritten, every rewrite snapshots the previous version first, and the
|
|
33
|
+
validator rejects any rewrite that silently drops a previously ACTIVE entry
|
|
34
|
+
(supersede/expire = move to `## Archive`; archived entries may be dropped
|
|
35
|
+
later — snapshots preserve history, so the size cap stays satisfiable).
|
|
36
|
+
- **Graceful degradation**: an unreachable/unauthorized store means a turn
|
|
37
|
+
without memory, never a failed turn.
|
|
38
|
+
- **Anti-hot-loop**: every job completion advances its eligibility timestamp —
|
|
39
|
+
success, failure, or "not actually memory-enabled".
|
|
40
|
+
- **Extraction idempotency**: every completed extraction records an idempotency
|
|
41
|
+
key (`extractionRunKey` — content hash of the message window + the extraction
|
|
42
|
+
prompt version). An unchanged window never re-buys a run, and a run that
|
|
43
|
+
found NOTHING tombstones its content via the same key. Failed runs never
|
|
44
|
+
record a key, so they stay retryable. Bump `EXTRACTION_PROMPT_VERSION` to
|
|
45
|
+
invalidate old keys after a material prompt change.
|
|
46
|
+
- **Provenance**: every fact is tagged `user-said` / `tool-derived` /
|
|
47
|
+
`user-requested`; the extractor is instructed to scrub secrets/PII.
|
|
48
|
+
- **Trust marks**: extractor-written episodes carry `status: unverified`
|
|
49
|
+
frontmatter; index entries folded from them carry an `unverified` marker
|
|
50
|
+
(labeled — in the injected block and in search results — never hidden). The
|
|
51
|
+
consolidator promotes an entry only on later, independent corroboration.
|
|
52
|
+
Explicit `memory_save` notes are never marked unverified.
|
|
53
|
+
- **Aliases + create-safety**: entries can carry `aka:` aliases (episodes:
|
|
54
|
+
`aliases:` frontmatter) for subjects with several names/spellings.
|
|
55
|
+
`memory_search` matches them exactly (NFKC/case/whitespace-normalized),
|
|
56
|
+
tags every hit with `matchedOn` evidence (`alias-exact` | `description` |
|
|
57
|
+
`body`), and derives a response-level `create_safety`
|
|
58
|
+
(`exists` | `probable` | `unknown`) that `memory_save` instructs the agent
|
|
59
|
+
to consult before creating anything new.
|
|
60
|
+
- **Injection hardening**: memory text is LLM-authored from user conversation —
|
|
61
|
+
a prompt-injection persistence vector. At injection time (the `<memory>`
|
|
62
|
+
block, search snippets) it passes through `sanitizeMemoryText`, which
|
|
63
|
+
de-fangs envelope/role tags and strips a named set of known injection
|
|
64
|
+
phrasings. Volume reduction, not a guarantee — the standing instructions
|
|
65
|
+
additionally declare the envelope's content to be data, never instructions.
|
|
66
|
+
|
|
67
|
+
## Pieces
|
|
68
|
+
|
|
69
|
+
| Module | What it is |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `types` / `scope` | `MemoryStore` seam, `ScopeRef` derivation (`scopeRefOf`) |
|
|
72
|
+
| `store/loredex` | `MemoryStore` over a Loredex MCP connection (opens per op via the `ConnectionRegistry`; tolerant result parsing; no delete — snapshot + rewrite) |
|
|
73
|
+
| `index-file` | MEMORY.md grammar: parse, `validateIndexRewrite`, id minting |
|
|
74
|
+
| `provider` | `createMemoryProvider` → the runtime's `MemoryProvider` (TTL-cached index block + bound tools) |
|
|
75
|
+
| `tools` | agent-facing `memory_search`/`memory_save` + scope-locked job tools |
|
|
76
|
+
| `prompts/*` | injection block, extractor/consolidator instructions + prompt builders |
|
|
77
|
+
| `jobs/types` | `MemoryJobStore` (implemented in `@intentface/latch-drizzle`: `createMemoryJobStore`) |
|
|
78
|
+
|
|
79
|
+
The sweep itself is platform glue (`apps/latch-platform/src/lib/memory/sweep.ts`),
|
|
80
|
+
following the Codex pattern: idle-gated parallel extraction, per-scope
|
|
81
|
+
lease-serialized consolidation with cooldown + watermark, usage-based expiry
|
|
82
|
+
(default 30 days unrecalled).
|
|
83
|
+
|
|
84
|
+
## Wiring it into a host
|
|
85
|
+
|
|
86
|
+
This package ships the ingredients, not the jobs — a host app (see
|
|
87
|
+
`apps/latch-platform` for the reference implementation of every step) wires:
|
|
88
|
+
|
|
89
|
+
1. **Tables + job store** — `@intentface/latch-drizzle`'s migrations create
|
|
90
|
+
`latch_memory_chat_state` / `latch_memory_scope_state` / `latch_memory_usage`;
|
|
91
|
+
build the store with `createMemoryJobStore(db)`.
|
|
92
|
+
2. **Store + provider → runtime** — in the composition root:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const memoryStore = createLoredexStore({ connections });
|
|
96
|
+
const memoryProvider = createMemoryProvider({
|
|
97
|
+
store: memoryStore,
|
|
98
|
+
scopeOf: ({ principal, agent, memory }) =>
|
|
99
|
+
scopeRefOf({ agent, memory, userId: principal.userId }),
|
|
100
|
+
recordUsage: (scopeKey, ids) => memoryJobs.recordUsage(scopeKey, ids, Date.now()),
|
|
101
|
+
onEpisodeWritten: (a) => memoryJobs.touchScope(/* mark scope dirty */),
|
|
102
|
+
});
|
|
103
|
+
const runtime = createRuntime({ ...rest, memory: memoryProvider });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
That alone gives every agent declaring `memory` the injected index block and
|
|
107
|
+
the `memory_search`/`memory_save` tools.
|
|
108
|
+
3. **Two job agents** — register `memory-extractor` and `memory-consolidator`
|
|
109
|
+
as code agents (cheap model, no connections) built from the exported parts:
|
|
110
|
+
`EXTRACTOR_INSTRUCTIONS`/`CONSOLIDATOR_INSTRUCTIONS` + the scope-locked tool
|
|
111
|
+
factories (`memoryReadIndexTool`, `memoryWriteEpisodeTool`,
|
|
112
|
+
`memoryReadEpisodesTool`, `memoryWriteIndexTool`), bound from the sweep's
|
|
113
|
+
`turnContext`. Hide them from user-facing pickers (the platform reserves the
|
|
114
|
+
`memory-*` name prefix).
|
|
115
|
+
4. **The sweep** — a cron-driven loop over `MemoryJobStore`: claim idle chats →
|
|
116
|
+
`runtime.runAgent("memory-extractor", …)` under the chat's own reconstructed
|
|
117
|
+
principal → advance the seq watermark; claim dirty scopes past cooldown →
|
|
118
|
+
`runtime.runAgent("memory-consolidator", …)` → clear consumed episodes.
|
|
119
|
+
Copy `apps/latch-platform/src/lib/memory/sweep.ts` — the always-advance
|
|
120
|
+
eligibility discipline (no hot loops) and the lease fencing live there.
|
|
121
|
+
5. **Declare memory on agents** — `memory: { scope, connection }` in
|
|
122
|
+
`defineAgent` config or the UI agent record; the connection must be one the
|
|
123
|
+
agent itself declares.
|
|
124
|
+
|
|
125
|
+
## Offline evals
|
|
126
|
+
|
|
127
|
+
`evals/` holds the deterministic (zero-LLM, zero-network) evaluation
|
|
128
|
+
foundation for this package:
|
|
129
|
+
|
|
130
|
+
- **Synthetic corpus** — a seeded, byte-deterministic week of running-coach
|
|
131
|
+
conversations (`evals/corpus/`, seed 42, mulberry32 PRNG) with planted
|
|
132
|
+
contradictions, temporal supersessions, stale facts, paraphrased
|
|
133
|
+
prompt-injection attempts, and implicit preferences. Gold labels live in
|
|
134
|
+
`evals/fixtures/corpus/gold/`, referenced by `fixture_id`
|
|
135
|
+
(`<session>:<messageIndex>`).
|
|
136
|
+
- **Retrieval benchmark** — a reference memory-file set
|
|
137
|
+
(`evals/fixtures/memory-files/`), sealed qrels (`evals/fixtures/qrels.json`;
|
|
138
|
+
the search path only ever sees a `PublicQuery {id, text}` projection), and a
|
|
139
|
+
pure scorer (P@k, R@k, MRR, graded nDCG@k, top-1).
|
|
140
|
+
|
|
141
|
+
Commands (from `packages/memory/`):
|
|
142
|
+
|
|
143
|
+
- `pnpm eval:memory-search` — run every qrels query through the real
|
|
144
|
+
`memory_search` tool over the in-memory store; prints a per-tier report and
|
|
145
|
+
rewrites the committed baseline
|
|
146
|
+
(`evals/results/memory-search-baseline.json`).
|
|
147
|
+
- `pnpm eval:regen-fixtures` — regenerate all committed fixtures after a
|
|
148
|
+
deliberate template change (`determinism.test.ts` fails on any drift).
|
|
149
|
+
|
|
150
|
+
The adversarial qrels tier (paraphrases sharing <30% content words with their
|
|
151
|
+
target) is EXPECTED to score ~0 on keyword search — it exists to measure the
|
|
152
|
+
gap, not to be tuned away.
|
|
153
|
+
|
|
154
|
+
## Caveat
|
|
155
|
+
|
|
156
|
+
Per-user scopes (`agent-user`, `user`) are stored under the caller's Loredex
|
|
157
|
+
private root (owner-only ACL), which is real privacy **only when the
|
|
158
|
+
connection authenticates as the end user**: on a workspace-shared connection
|
|
159
|
+
every call resolves the connection identity's private root, not the end
|
|
160
|
+
user's — all users' memory then lands in one private root, separated only by
|
|
161
|
+
the `u-<id>` folder convention (as before, but now hidden from the rest of
|
|
162
|
+
the workspace). Use a personal connection where per-user privacy matters.
|
|
163
|
+
`agent-org` memory lives in the shared `_memory/**` tree by design.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The idempotency key for one extraction window. Hashes role + text only
|
|
3
|
+
* (order-preserving, unambiguously framed), deliberately NOT `seq`: a
|
|
4
|
+
* re-sequenced chat (legacy heal, replay) still hashes the same, which is
|
|
5
|
+
* exactly the duplicate-run case the key exists to suppress. Bumping
|
|
6
|
+
* `EXTRACTION_PROMPT_VERSION` changes every key, so already-tombstoned
|
|
7
|
+
* windows become extractable again under the new prompt.
|
|
8
|
+
*
|
|
9
|
+
* `opts.schemaFingerprint` (see `schemasFingerprint`) covers the run's level
|
|
10
|
+
* set and per-level schemas the same way: a schema edit — or a level added or
|
|
11
|
+
* removed — changes the key, so the same content becomes re-extractable under
|
|
12
|
+
* the new configuration.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractionRunKey(messages: ReadonlyArray<{
|
|
15
|
+
role: string;
|
|
16
|
+
text: string;
|
|
17
|
+
}>, opts?: {
|
|
18
|
+
schemaFingerprint?: string;
|
|
19
|
+
}): string;
|
|
20
|
+
//# sourceMappingURL=extraction-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction-key.d.ts","sourceRoot":"","sources":["../src/extraction-key.ts"],"names":[],"mappings":"AA2BA;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EACvD,IAAI,CAAC,EAAE;IAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,MAAM,CAIR"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { EXTRACTION_PROMPT_VERSION } from "./prompts/extractor.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extraction-run idempotency keys.
|
|
4
|
+
*
|
|
5
|
+
* A key fingerprints WHAT an extraction run processed: the content of the
|
|
6
|
+
* message window plus the extraction prompt version. The sweep persists the
|
|
7
|
+
* key of the last COMPLETED run per chat (`last_extracted_key`) and skips a
|
|
8
|
+
* new paid run whenever the next window hashes to the same key (watermark
|
|
9
|
+
* reset, replayed rows) — including runs that yielded zero memories, where
|
|
10
|
+
* the recorded key doubles as a tombstone so the same content is never
|
|
11
|
+
* re-extracted. FAILED runs (LLM error/timeout) never record a key, so they
|
|
12
|
+
* stay eligible for retry.
|
|
13
|
+
*/
|
|
14
|
+
/** FNV-1a 64-bit, hex — deterministic, dependency-free (Node/edge safe). */
|
|
15
|
+
function fnv1a64(input) {
|
|
16
|
+
let h = 0xcbf29ce484222325n;
|
|
17
|
+
const prime = 0x100000001b3n;
|
|
18
|
+
const mask = 0xffffffffffffffffn;
|
|
19
|
+
for (let i = 0; i < input.length; i++) {
|
|
20
|
+
h ^= BigInt(input.charCodeAt(i));
|
|
21
|
+
h = (h * prime) & mask;
|
|
22
|
+
}
|
|
23
|
+
return h.toString(16).padStart(16, "0");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The idempotency key for one extraction window. Hashes role + text only
|
|
27
|
+
* (order-preserving, unambiguously framed), deliberately NOT `seq`: a
|
|
28
|
+
* re-sequenced chat (legacy heal, replay) still hashes the same, which is
|
|
29
|
+
* exactly the duplicate-run case the key exists to suppress. Bumping
|
|
30
|
+
* `EXTRACTION_PROMPT_VERSION` changes every key, so already-tombstoned
|
|
31
|
+
* windows become extractable again under the new prompt.
|
|
32
|
+
*
|
|
33
|
+
* `opts.schemaFingerprint` (see `schemasFingerprint`) covers the run's level
|
|
34
|
+
* set and per-level schemas the same way: a schema edit — or a level added or
|
|
35
|
+
* removed — changes the key, so the same content becomes re-extractable under
|
|
36
|
+
* the new configuration.
|
|
37
|
+
*/
|
|
38
|
+
export function extractionRunKey(messages, opts) {
|
|
39
|
+
const payload = messages.map((m) => `${m.role}\u0000${m.text}`).join("\u0001");
|
|
40
|
+
const suffix = opts?.schemaFingerprint ? `:${opts.schemaFingerprint}` : "";
|
|
41
|
+
return `x${EXTRACTION_PROMPT_VERSION}:${fnv1a64(payload)}${suffix}`;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=extraction-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extraction-key.js","sourceRoot":"","sources":["../src/extraction-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAEnE;;;;;;;;;;;GAWG;AAEH,4EAA4E;AAC5E,SAAS,OAAO,CAAC,KAAa;IAC5B,IAAI,CAAC,GAAG,mBAAmB,CAAC;IAC5B,MAAM,KAAK,GAAG,cAAc,CAAC;IAC7B,MAAM,IAAI,GAAG,mBAAmB,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAuD,EACvD,IAAqC;IAErC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/E,MAAM,MAAM,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,IAAI,yBAAyB,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,MAAM,EAAE,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Provenance } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The compiled index (MEMORY.md): parsing, id minting, and the rewrite
|
|
4
|
+
* validator that makes consolidation non-destructive.
|
|
5
|
+
*
|
|
6
|
+
* Entry grammar (one line per fact):
|
|
7
|
+
*
|
|
8
|
+
* - [m-7f3a] (2026-07-12, user-said) Prefers concise answers. ← episodes/2026-07-12-09-30-onboarding.md
|
|
9
|
+
* - [m-9911] (2026-06-02, user-said; superseded 2026-07-20 by m-a1c2) Old fact. ← episodes/…
|
|
10
|
+
*
|
|
11
|
+
* Optional `;`-separated parenthetical tail markers (any order):
|
|
12
|
+
* - `aka: Robert Smith, Bobby` — alternate names/spellings of the entry's
|
|
13
|
+
* subject, matched exactly by memory_search;
|
|
14
|
+
* - `unverified` — auto-extracted and not yet confirmed (trust mark; the
|
|
15
|
+
* consolidator removes it on corroboration).
|
|
16
|
+
*
|
|
17
|
+
* Entries under a `## Archive` heading are archived (superseded/expired).
|
|
18
|
+
* The validator enforces the two invariants the research said matter most:
|
|
19
|
+
* a hard size cap, and "no entry ever silently disappears" — a rewrite may
|
|
20
|
+
* move an entry to Archive but never drop its id.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
|
|
23
|
+
/** Soft cap the consolidator is instructed to stay under. */
|
|
24
|
+
export declare const INDEX_LINE_CAP = 100;
|
|
25
|
+
/** Hard cap: rewrites longer than this are rejected outright. */
|
|
26
|
+
export declare const INDEX_LINE_HARD_CAP = 120;
|
|
27
|
+
export interface IndexEntry {
|
|
28
|
+
id: string;
|
|
29
|
+
/** YYYY-MM-DD the fact was recorded/updated. */
|
|
30
|
+
date?: string;
|
|
31
|
+
provenance?: Provenance;
|
|
32
|
+
/** Set when the parenthetical carries a `superseded <date> by <id>` marker. */
|
|
33
|
+
superseded?: {
|
|
34
|
+
date?: string;
|
|
35
|
+
by?: string;
|
|
36
|
+
};
|
|
37
|
+
/** Alternate names/spellings of the entry's subject (the `aka:` marker). */
|
|
38
|
+
aliases: string[];
|
|
39
|
+
/** True when the parenthetical carries the `unverified` trust mark. */
|
|
40
|
+
unverified: boolean;
|
|
41
|
+
text: string;
|
|
42
|
+
/** Source episode paths (the `← …` tail). */
|
|
43
|
+
sources: string[];
|
|
44
|
+
/** True when the entry sits under the `## Archive` heading. */
|
|
45
|
+
archived: boolean;
|
|
46
|
+
/** The entry line verbatim (for surfacing the entry as a search snippet). */
|
|
47
|
+
raw: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ParsedIndex {
|
|
50
|
+
entries: IndexEntry[];
|
|
51
|
+
lineCount: number;
|
|
52
|
+
/** Lines that look like entries but don't parse — surfaced to the model. */
|
|
53
|
+
malformed: string[];
|
|
54
|
+
}
|
|
55
|
+
export declare function parseIndex(text: string): ParsedIndex;
|
|
56
|
+
export declare class IndexValidationError extends Error {
|
|
57
|
+
readonly problems: string[];
|
|
58
|
+
constructor(problems: string[]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate a proposed index rewrite against the previous version. Returns the
|
|
62
|
+
* problem list (empty = valid). Rules:
|
|
63
|
+
* - hard line cap (`INDEX_LINE_HARD_CAP`);
|
|
64
|
+
* - no duplicate entry ids;
|
|
65
|
+
* - every previously ACTIVE id must still be present (active or archived) —
|
|
66
|
+
* supersede/expire is a MOVE to `## Archive`, never a silent delete;
|
|
67
|
+
* - previously ARCHIVED ids MAY be dropped: every rewrite snapshots the full
|
|
68
|
+
* prior index first, so archived history stays recoverable while the cap
|
|
69
|
+
* stays satisfiable (otherwise Archive would grow past the cap forever);
|
|
70
|
+
* - entry-shaped lines must parse.
|
|
71
|
+
*/
|
|
72
|
+
export declare function validateIndexRewrite(prev: string | undefined, next: string): string[];
|
|
73
|
+
/** Mint a new entry id not already taken (6 hex chars, `m-` prefixed). */
|
|
74
|
+
export declare function mintMemoryId(taken: ReadonlySet<string>): string;
|
|
75
|
+
/** All `[m-…]` ids referenced in a blob of text (for usage tracking). */
|
|
76
|
+
export declare function memoryIdsIn(text: string): string[];
|
|
77
|
+
//# sourceMappingURL=index-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-file.d.ts","sourceRoot":"","sources":["../src/index-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,eAAO,MAAM,qBAAqB,cAAc,CAAC;AACjD,6DAA6D;AAC7D,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,+EAA+E;IAC/E,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,4EAA4E;IAC5E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AASD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAwEpD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;gBAChB,QAAQ,EAAE,MAAM,EAAE;CAK/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAiCrF;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAO/D;AAED,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compiled index (MEMORY.md): parsing, id minting, and the rewrite
|
|
3
|
+
* validator that makes consolidation non-destructive.
|
|
4
|
+
*
|
|
5
|
+
* Entry grammar (one line per fact):
|
|
6
|
+
*
|
|
7
|
+
* - [m-7f3a] (2026-07-12, user-said) Prefers concise answers. ← episodes/2026-07-12-09-30-onboarding.md
|
|
8
|
+
* - [m-9911] (2026-06-02, user-said; superseded 2026-07-20 by m-a1c2) Old fact. ← episodes/…
|
|
9
|
+
*
|
|
10
|
+
* Optional `;`-separated parenthetical tail markers (any order):
|
|
11
|
+
* - `aka: Robert Smith, Bobby` — alternate names/spellings of the entry's
|
|
12
|
+
* subject, matched exactly by memory_search;
|
|
13
|
+
* - `unverified` — auto-extracted and not yet confirmed (trust mark; the
|
|
14
|
+
* consolidator removes it on corroboration).
|
|
15
|
+
*
|
|
16
|
+
* Entries under a `## Archive` heading are archived (superseded/expired).
|
|
17
|
+
* The validator enforces the two invariants the research said matter most:
|
|
18
|
+
* a hard size cap, and "no entry ever silently disappears" — a rewrite may
|
|
19
|
+
* move an entry to Archive but never drop its id.
|
|
20
|
+
*/
|
|
21
|
+
export const MEMORY_INDEX_FILENAME = "MEMORY.md";
|
|
22
|
+
/** Soft cap the consolidator is instructed to stay under. */
|
|
23
|
+
export const INDEX_LINE_CAP = 100;
|
|
24
|
+
/** Hard cap: rewrites longer than this are rejected outright. */
|
|
25
|
+
export const INDEX_LINE_HARD_CAP = 120;
|
|
26
|
+
// Id charset is deliberately wider than what `mintMemoryId` produces (hex):
|
|
27
|
+
// the consolidator model mints ids itself and "hex-looking" ids like m-g8d7e9
|
|
28
|
+
// are common. Ids are opaque keys — uniqueness matters, the alphabet doesn't,
|
|
29
|
+
// and rejecting them sends the model into pointless rewrite loops.
|
|
30
|
+
const ENTRY_RE = /^\s*-\s*\[(m-[0-9a-z]{4,8})\]\s*\(([^)]*)\)\s*(.*)$/;
|
|
31
|
+
const PROVENANCES = new Set(["user-said", "tool-derived", "user-requested", "mixed"]);
|
|
32
|
+
export function parseIndex(text) {
|
|
33
|
+
const lines = text.split("\n");
|
|
34
|
+
const entries = [];
|
|
35
|
+
const malformed = [];
|
|
36
|
+
let archived = false;
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (/^##\s+archive\b/i.test(line.trim())) {
|
|
39
|
+
archived = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (/^##\s+/.test(line.trim()) && !/^##\s+archive\b/i.test(line.trim())) {
|
|
43
|
+
// A new section after Archive would be unusual, but honor it.
|
|
44
|
+
if (archived && !/archive/i.test(line))
|
|
45
|
+
archived = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const looksLikeEntry = /^\s*-\s*\[m-/.test(line);
|
|
49
|
+
const m = ENTRY_RE.exec(line);
|
|
50
|
+
if (!m) {
|
|
51
|
+
if (looksLikeEntry)
|
|
52
|
+
malformed.push(line.trim());
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const [, id, paren, rest] = m;
|
|
56
|
+
// Parenthetical: `<date>, <provenance>[; superseded <date> by <id>]` — parse
|
|
57
|
+
// leniently; a hand-edited index must not break recall.
|
|
58
|
+
const [head, ...tailParts] = paren.split(";").map((s) => s.trim());
|
|
59
|
+
const headBits = head.split(",").map((s) => s.trim());
|
|
60
|
+
const date = /^\d{4}-\d{2}-\d{2}$/.test(headBits[0] ?? "") ? headBits[0] : undefined;
|
|
61
|
+
const prov = headBits.find((b) => PROVENANCES.has(b));
|
|
62
|
+
let superseded;
|
|
63
|
+
const supersededPart = tailParts.find((p) => /^superseded\b/i.test(p));
|
|
64
|
+
if (supersededPart) {
|
|
65
|
+
const sd = /(\d{4}-\d{2}-\d{2})/.exec(supersededPart)?.[1];
|
|
66
|
+
// Bounded lookahead: an overlong id (m-abcdefghi) must not silently
|
|
67
|
+
// truncate into a valid-looking — but wrong — supersession pointer.
|
|
68
|
+
const by = /\bby\s+(m-[0-9a-z]{4,8})(?![0-9A-Za-z])/.exec(supersededPart)?.[1];
|
|
69
|
+
superseded = { date: sd, by };
|
|
70
|
+
}
|
|
71
|
+
const akaPart = tailParts.find((p) => /^aka\s*:/i.test(p));
|
|
72
|
+
const aliases = akaPart
|
|
73
|
+
? akaPart
|
|
74
|
+
.replace(/^aka\s*:/i, "")
|
|
75
|
+
.split(",")
|
|
76
|
+
.map((a) => a.trim())
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
: [];
|
|
79
|
+
const unverified = tailParts.some((p) => /^unverified$/i.test(p));
|
|
80
|
+
// The `← sources` tail (allow plain "<-" as a fallback for hand edits).
|
|
81
|
+
const arrowSplit = rest.split(/\s*(?:←|<-)\s*/);
|
|
82
|
+
const text_ = (arrowSplit[0] ?? "").trim();
|
|
83
|
+
const sources = arrowSplit.length > 1
|
|
84
|
+
? arrowSplit
|
|
85
|
+
.slice(1)
|
|
86
|
+
.join(",")
|
|
87
|
+
.split(",")
|
|
88
|
+
.map((s) => s.trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
: [];
|
|
91
|
+
entries.push({
|
|
92
|
+
id,
|
|
93
|
+
date,
|
|
94
|
+
provenance: prov,
|
|
95
|
+
superseded,
|
|
96
|
+
aliases,
|
|
97
|
+
unverified,
|
|
98
|
+
text: text_,
|
|
99
|
+
sources,
|
|
100
|
+
archived,
|
|
101
|
+
raw: line.trim(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return { entries, lineCount: lines.length, malformed };
|
|
105
|
+
}
|
|
106
|
+
export class IndexValidationError extends Error {
|
|
107
|
+
problems;
|
|
108
|
+
constructor(problems) {
|
|
109
|
+
super(`Index rewrite rejected:\n- ${problems.join("\n- ")}`);
|
|
110
|
+
this.name = "IndexValidationError";
|
|
111
|
+
this.problems = problems;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validate a proposed index rewrite against the previous version. Returns the
|
|
116
|
+
* problem list (empty = valid). Rules:
|
|
117
|
+
* - hard line cap (`INDEX_LINE_HARD_CAP`);
|
|
118
|
+
* - no duplicate entry ids;
|
|
119
|
+
* - every previously ACTIVE id must still be present (active or archived) —
|
|
120
|
+
* supersede/expire is a MOVE to `## Archive`, never a silent delete;
|
|
121
|
+
* - previously ARCHIVED ids MAY be dropped: every rewrite snapshots the full
|
|
122
|
+
* prior index first, so archived history stays recoverable while the cap
|
|
123
|
+
* stays satisfiable (otherwise Archive would grow past the cap forever);
|
|
124
|
+
* - entry-shaped lines must parse.
|
|
125
|
+
*/
|
|
126
|
+
export function validateIndexRewrite(prev, next) {
|
|
127
|
+
const problems = [];
|
|
128
|
+
const parsed = parseIndex(next);
|
|
129
|
+
if (parsed.lineCount > INDEX_LINE_HARD_CAP) {
|
|
130
|
+
problems.push(`index is ${parsed.lineCount} lines — the hard cap is ${INDEX_LINE_HARD_CAP} ` +
|
|
131
|
+
`(target ≤ ${INDEX_LINE_CAP}). Merge or archive entries instead of growing the index.`);
|
|
132
|
+
}
|
|
133
|
+
for (const line of parsed.malformed) {
|
|
134
|
+
problems.push(`malformed entry line (expected \`- [m-<4-8 lowercase letters/digits>] ` +
|
|
135
|
+
`(<YYYY-MM-DD>, <provenance>) <fact> ← <sources>\`): "${line}"`);
|
|
136
|
+
}
|
|
137
|
+
const seen = new Set();
|
|
138
|
+
for (const e of parsed.entries) {
|
|
139
|
+
if (seen.has(e.id))
|
|
140
|
+
problems.push(`duplicate entry id: ${e.id}`);
|
|
141
|
+
seen.add(e.id);
|
|
142
|
+
}
|
|
143
|
+
if (prev) {
|
|
144
|
+
const prevActive = parseIndex(prev)
|
|
145
|
+
.entries.filter((e) => !e.archived)
|
|
146
|
+
.map((e) => e.id);
|
|
147
|
+
const missing = prevActive.filter((id) => !seen.has(id));
|
|
148
|
+
if (missing.length > 0) {
|
|
149
|
+
problems.push(`active entries silently deleted: ${missing.join(", ")} — move superseded/expired ` +
|
|
150
|
+
`entries under "## Archive" instead of removing them.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return problems;
|
|
154
|
+
}
|
|
155
|
+
/** Mint a new entry id not already taken (6 hex chars, `m-` prefixed). */
|
|
156
|
+
export function mintMemoryId(taken) {
|
|
157
|
+
for (;;) {
|
|
158
|
+
const id = `m-${Math.floor(Math.random() * 0xffffff)
|
|
159
|
+
.toString(16)
|
|
160
|
+
.padStart(6, "0")}`;
|
|
161
|
+
if (!taken.has(id))
|
|
162
|
+
return id;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** All `[m-…]` ids referenced in a blob of text (for usage tracking). */
|
|
166
|
+
export function memoryIdsIn(text) {
|
|
167
|
+
return Array.from(new Set(Array.from(text.matchAll(/\[(m-[0-9a-z]{4,8})\]/g), (m) => m[1])));
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=index-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-file.js","sourceRoot":"","sources":["../src/index-file.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intentface/latch-memory
|
|
3
|
+
*
|
|
4
|
+
* Pluggable agent memory for Latch: an always-loaded compiled index injected
|
|
5
|
+
* into the system prompt, an episodic store searched on demand, and background
|
|
6
|
+
* extraction/consolidation over a `MemoryStore` seam (Loredex-backed first).
|
|
7
|
+
*/
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
export * from "./scope.js";
|
|
10
|
+
export * from "./schema.js";
|
|
11
|
+
export * from "./index-file.js";
|
|
12
|
+
export * from "./extraction-key.js";
|
|
13
|
+
export * from "./search-evidence.js";
|
|
14
|
+
export * from "./sanitize.js";
|
|
15
|
+
export * from "./provider.js";
|
|
16
|
+
export * from "./tools.js";
|
|
17
|
+
export * from "./store/loredex.js";
|
|
18
|
+
export * from "./store/memory.js";
|
|
19
|
+
export * from "./prompts/inject.js";
|
|
20
|
+
export * from "./prompts/extractor.js";
|
|
21
|
+
export * from "./prompts/consolidator.js";
|
|
22
|
+
export * from "./jobs/types.js";
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @intentface/latch-memory
|
|
3
|
+
*
|
|
4
|
+
* Pluggable agent memory for Latch: an always-loaded compiled index injected
|
|
5
|
+
* into the system prompt, an episodic store searched on demand, and background
|
|
6
|
+
* extraction/consolidation over a `MemoryStore` seam (Loredex-backed first).
|
|
7
|
+
*/
|
|
8
|
+
export * from "./types.js";
|
|
9
|
+
export * from "./scope.js";
|
|
10
|
+
export * from "./schema.js";
|
|
11
|
+
export * from "./index-file.js";
|
|
12
|
+
export * from "./extraction-key.js";
|
|
13
|
+
export * from "./search-evidence.js";
|
|
14
|
+
export * from "./sanitize.js";
|
|
15
|
+
export * from "./provider.js";
|
|
16
|
+
export * from "./tools.js";
|
|
17
|
+
export * from "./store/loredex.js";
|
|
18
|
+
export * from "./store/memory.js";
|
|
19
|
+
export * from "./prompts/inject.js";
|
|
20
|
+
export * from "./prompts/extractor.js";
|
|
21
|
+
export * from "./prompts/consolidator.js";
|
|
22
|
+
export * from "./jobs/types.js";
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iBAAiB,CAAC"}
|