@psiclawops/hypermem 0.1.0 → 0.5.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/ARCHITECTURE.md +4 -3
- package/README.md +457 -174
- package/dist/background-indexer.d.ts +19 -4
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +329 -17
- package/dist/cache.d.ts +110 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +495 -0
- package/dist/compaction-fence.d.ts +1 -1
- package/dist/compaction-fence.js +1 -1
- package/dist/compositor.d.ts +114 -27
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +1678 -229
- package/dist/content-type-classifier.d.ts +41 -0
- package/dist/content-type-classifier.d.ts.map +1 -0
- package/dist/content-type-classifier.js +181 -0
- package/dist/cross-agent.d.ts +5 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +5 -0
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +6 -2
- package/dist/desired-state-store.d.ts +1 -1
- package/dist/desired-state-store.d.ts.map +1 -1
- package/dist/desired-state-store.js +15 -5
- package/dist/doc-chunk-store.d.ts +26 -1
- package/dist/doc-chunk-store.d.ts.map +1 -1
- package/dist/doc-chunk-store.js +114 -1
- package/dist/doc-chunker.d.ts +1 -1
- package/dist/doc-chunker.js +1 -1
- package/dist/dreaming-promoter.d.ts +86 -0
- package/dist/dreaming-promoter.d.ts.map +1 -0
- package/dist/dreaming-promoter.js +381 -0
- package/dist/episode-store.d.ts +2 -1
- package/dist/episode-store.d.ts.map +1 -1
- package/dist/episode-store.js +4 -4
- package/dist/fact-store.d.ts +19 -1
- package/dist/fact-store.d.ts.map +1 -1
- package/dist/fact-store.js +64 -3
- package/dist/fleet-store.d.ts +1 -1
- package/dist/fleet-store.js +1 -1
- package/dist/fos-mod.d.ts +178 -0
- package/dist/fos-mod.d.ts.map +1 -0
- package/dist/fos-mod.js +416 -0
- package/dist/hybrid-retrieval.d.ts +5 -1
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +7 -3
- package/dist/image-eviction.d.ts +49 -0
- package/dist/image-eviction.d.ts.map +1 -0
- package/dist/image-eviction.js +251 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -43
- package/dist/keystone-scorer.d.ts +51 -0
- package/dist/keystone-scorer.d.ts.map +1 -0
- package/dist/keystone-scorer.js +52 -0
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-lint.d.ts +29 -0
- package/dist/knowledge-lint.d.ts.map +1 -0
- package/dist/knowledge-lint.js +116 -0
- package/dist/knowledge-store.d.ts +1 -1
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +8 -2
- package/dist/library-schema.d.ts +3 -3
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +324 -3
- package/dist/message-store.d.ts +15 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +51 -1
- package/dist/metrics-dashboard.d.ts +114 -0
- package/dist/metrics-dashboard.d.ts.map +1 -0
- package/dist/metrics-dashboard.js +260 -0
- package/dist/obsidian-exporter.d.ts +57 -0
- package/dist/obsidian-exporter.d.ts.map +1 -0
- package/dist/obsidian-exporter.js +274 -0
- package/dist/obsidian-watcher.d.ts +147 -0
- package/dist/obsidian-watcher.d.ts.map +1 -0
- package/dist/obsidian-watcher.js +403 -0
- package/dist/open-domain.d.ts +46 -0
- package/dist/open-domain.d.ts.map +1 -0
- package/dist/open-domain.js +125 -0
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/preservation-gate.d.ts +1 -1
- package/dist/preservation-gate.js +1 -1
- package/dist/proactive-pass.d.ts +63 -0
- package/dist/proactive-pass.d.ts.map +1 -0
- package/dist/proactive-pass.js +239 -0
- package/dist/profiles.d.ts +44 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +227 -0
- package/dist/provider-translator.d.ts +13 -3
- package/dist/provider-translator.d.ts.map +1 -1
- package/dist/provider-translator.js +63 -9
- package/dist/rate-limiter.d.ts +1 -1
- package/dist/rate-limiter.js +1 -1
- package/dist/repair-tool-pairs.d.ts +38 -0
- package/dist/repair-tool-pairs.d.ts.map +1 -0
- package/dist/repair-tool-pairs.js +138 -0
- package/dist/retrieval-policy.d.ts +51 -0
- package/dist/retrieval-policy.d.ts.map +1 -0
- package/dist/retrieval-policy.js +77 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -2
- package/dist/secret-scanner.d.ts +1 -1
- package/dist/secret-scanner.js +1 -1
- package/dist/seed.d.ts +2 -2
- package/dist/seed.js +2 -2
- package/dist/session-flusher.d.ts +53 -0
- package/dist/session-flusher.d.ts.map +1 -0
- package/dist/session-flusher.js +69 -0
- package/dist/session-topic-map.d.ts +41 -0
- package/dist/session-topic-map.d.ts.map +1 -0
- package/dist/session-topic-map.js +77 -0
- package/dist/spawn-context.d.ts +54 -0
- package/dist/spawn-context.d.ts.map +1 -0
- package/dist/spawn-context.js +159 -0
- package/dist/system-store.d.ts +1 -1
- package/dist/system-store.js +1 -1
- package/dist/temporal-store.d.ts +80 -0
- package/dist/temporal-store.d.ts.map +1 -0
- package/dist/temporal-store.js +149 -0
- package/dist/topic-detector.d.ts +35 -0
- package/dist/topic-detector.d.ts.map +1 -0
- package/dist/topic-detector.js +249 -0
- package/dist/topic-store.d.ts +1 -1
- package/dist/topic-store.js +1 -1
- package/dist/topic-synthesizer.d.ts +51 -0
- package/dist/topic-synthesizer.d.ts.map +1 -0
- package/dist/topic-synthesizer.js +315 -0
- package/dist/trigger-registry.d.ts +63 -0
- package/dist/trigger-registry.d.ts.map +1 -0
- package/dist/trigger-registry.js +163 -0
- package/dist/types.d.ts +214 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/vector-store.d.ts +43 -5
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +189 -10
- package/dist/version.d.ts +34 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +34 -0
- package/dist/wiki-page-emitter.d.ts +65 -0
- package/dist/wiki-page-emitter.d.ts.map +1 -0
- package/dist/wiki-page-emitter.js +258 -0
- package/dist/work-store.d.ts +1 -1
- package/dist/work-store.js +1 -1
- package/package.json +15 -5
- package/dist/redis.d.ts +0 -188
- package/dist/redis.d.ts.map +0 -1
- package/dist/redis.js +0 -534
package/dist/profiles.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem configuration profiles
|
|
3
|
+
*
|
|
4
|
+
* Pre-built configs for common deployment patterns. Pass to createHyperMem()
|
|
5
|
+
* directly or use as a base for custom configs via mergeProfile().
|
|
6
|
+
*
|
|
7
|
+
* Profiles:
|
|
8
|
+
* minimal — 64k context, single agent, low resource usage
|
|
9
|
+
* standard — 128k context, fleet default, balanced
|
|
10
|
+
* rich — 200k+ context, multi-agent, full feature set
|
|
11
|
+
*/
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Shared base (fields common across all profiles)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const BASE_CACHE = {
|
|
16
|
+
keyPrefix: 'hm:',
|
|
17
|
+
sessionTTL: 3600,
|
|
18
|
+
historyTTL: 86400,
|
|
19
|
+
};
|
|
20
|
+
const BASE_EMBEDDING = {
|
|
21
|
+
ollamaUrl: 'http://localhost:11434',
|
|
22
|
+
model: 'nomic-embed-text',
|
|
23
|
+
dimensions: 768,
|
|
24
|
+
timeout: 10000,
|
|
25
|
+
batchSize: 32,
|
|
26
|
+
};
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// light — 64k context window, single agent, constrained resources
|
|
29
|
+
//
|
|
30
|
+
// Design intent:
|
|
31
|
+
// - Small local models (Mistral 7B, Phi-3, Llama 3 8B) at 64k context
|
|
32
|
+
// - Single agent — no cross-session context needed
|
|
33
|
+
// - Minimal ACA stack — no dreaming, no background indexing overhead
|
|
34
|
+
// - Low Redis churn — longer flush intervals, shorter history window
|
|
35
|
+
// - outputProfile: 'light' — ~100 token standalone directives, no fleet concepts
|
|
36
|
+
// - No parallel operations — sequential fact extraction only
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
const LIGHT_COMPOSITOR = {
|
|
39
|
+
defaultTokenBudget: 40000, // leaves ~24k for model output at 64k window
|
|
40
|
+
maxHistoryMessages: 200, // keep window tight — small models lose coherence past ~150 msgs
|
|
41
|
+
maxFacts: 15, // surface top facts only, don't swamp the window
|
|
42
|
+
maxCrossSessionContext: 0, // single agent — no cross-session
|
|
43
|
+
maxRecentToolPairs: 2, // minimal tool history
|
|
44
|
+
maxProseToolPairs: 4,
|
|
45
|
+
warmHistoryBudgetFraction: 0.35, // slightly less history, more room for context
|
|
46
|
+
contextWindowReserve: 0.35, // generous reserve — small models need output headroom
|
|
47
|
+
dynamicReserveEnabled: true,
|
|
48
|
+
dynamicReserveTurnHorizon: 3, // shorter horizon — small sessions
|
|
49
|
+
dynamicReserveMax: 0.50,
|
|
50
|
+
keystoneHistoryFraction: 0.1, // light keystone — history window is already small
|
|
51
|
+
keystoneMaxMessages: 5,
|
|
52
|
+
keystoneMinSignificance: 0.7, // higher bar — only high-signal keystone messages
|
|
53
|
+
targetBudgetFraction: 0.50, // Anvil spec: 0.50 for light
|
|
54
|
+
maxTotalTriggerTokens: 1500, // tight trigger ceiling
|
|
55
|
+
outputProfile: 'light', // standalone density directives only, no fleet concepts
|
|
56
|
+
wikiTokenCap: 300, // Anvil spec: 300 for light
|
|
57
|
+
};
|
|
58
|
+
const LIGHT_INDEXER = {
|
|
59
|
+
enabled: true,
|
|
60
|
+
factExtractionMode: 'pattern', // pattern only — tiered extraction is heavier
|
|
61
|
+
topicDormantAfter: '12h', // faster dormancy — small systems don't need long windows
|
|
62
|
+
topicClosedAfter: '48h',
|
|
63
|
+
factDecayRate: 0.05, // slightly faster decay — fewer facts, keep them fresh
|
|
64
|
+
episodeSignificanceThreshold: 0.6, // higher bar — only store meaningful episodes
|
|
65
|
+
periodicInterval: 120000, // 2min — less frequent background work on small systems
|
|
66
|
+
batchSize: 64,
|
|
67
|
+
maxMessagesPerTick: 200,
|
|
68
|
+
};
|
|
69
|
+
export const lightProfile = {
|
|
70
|
+
enabled: true,
|
|
71
|
+
dataDir: './hypermem-data',
|
|
72
|
+
cache: BASE_CACHE,
|
|
73
|
+
compositor: LIGHT_COMPOSITOR,
|
|
74
|
+
indexer: LIGHT_INDEXER,
|
|
75
|
+
embedding: {
|
|
76
|
+
...BASE_EMBEDDING,
|
|
77
|
+
batchSize: 8, // smaller batches — don't spike memory on embed
|
|
78
|
+
timeout: 15000, // more generous timeout — local hardware can be slow
|
|
79
|
+
},
|
|
80
|
+
// dreaming: disabled (default) — don't run background promotion on small systems
|
|
81
|
+
// obsidian: disabled (default)
|
|
82
|
+
};
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// standard — 128k context window, fleet default, balanced
|
|
85
|
+
//
|
|
86
|
+
// Design intent:
|
|
87
|
+
// - Mid-range models (Sonnet, GPT-5-mini, Gemini Flash) at 128k
|
|
88
|
+
// - Small multi-agent setups or single power-user agents
|
|
89
|
+
// - Full ACA stack — dreaming optional, background indexing on
|
|
90
|
+
// - outputProfile: 'standard' — full FOS, no MOD
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
const STANDARD_COMPOSITOR = {
|
|
93
|
+
defaultTokenBudget: 90000,
|
|
94
|
+
maxHistoryMessages: 500,
|
|
95
|
+
maxFacts: 30,
|
|
96
|
+
maxCrossSessionContext: 4000,
|
|
97
|
+
maxRecentToolPairs: 3,
|
|
98
|
+
maxProseToolPairs: 10,
|
|
99
|
+
warmHistoryBudgetFraction: 0.40,
|
|
100
|
+
contextWindowReserve: 0.25,
|
|
101
|
+
dynamicReserveEnabled: true,
|
|
102
|
+
dynamicReserveTurnHorizon: 5,
|
|
103
|
+
dynamicReserveMax: 0.50,
|
|
104
|
+
keystoneHistoryFraction: 0.20,
|
|
105
|
+
keystoneMaxMessages: 15,
|
|
106
|
+
keystoneMinSignificance: 0.5,
|
|
107
|
+
targetBudgetFraction: 0.65, // Anvil spec: 0.65 for standard
|
|
108
|
+
maxTotalTriggerTokens: 4000,
|
|
109
|
+
outputProfile: 'standard', // full FOS, MOD suppressed
|
|
110
|
+
wikiTokenCap: 600, // Anvil spec: 600 for standard
|
|
111
|
+
};
|
|
112
|
+
const STANDARD_INDEXER = {
|
|
113
|
+
enabled: true,
|
|
114
|
+
factExtractionMode: 'tiered',
|
|
115
|
+
topicDormantAfter: '24h',
|
|
116
|
+
topicClosedAfter: '72h',
|
|
117
|
+
factDecayRate: 0.02,
|
|
118
|
+
episodeSignificanceThreshold: 0.5,
|
|
119
|
+
periodicInterval: 60000, // 1min — standard background cadence
|
|
120
|
+
batchSize: 128,
|
|
121
|
+
maxMessagesPerTick: 500,
|
|
122
|
+
};
|
|
123
|
+
export const standardProfile = {
|
|
124
|
+
enabled: true,
|
|
125
|
+
dataDir: './hypermem-data',
|
|
126
|
+
cache: BASE_CACHE,
|
|
127
|
+
compositor: STANDARD_COMPOSITOR,
|
|
128
|
+
indexer: STANDARD_INDEXER,
|
|
129
|
+
embedding: BASE_EMBEDDING,
|
|
130
|
+
};
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// extended — 200k+ context window, multi-agent, full feature set
|
|
133
|
+
//
|
|
134
|
+
// Design intent:
|
|
135
|
+
// - Large context models (Opus, GPT-5.4, Gemini Pro) at 200k+
|
|
136
|
+
// - Council / multi-agent fleet deployments
|
|
137
|
+
// - Full ACA stack including dreaming, background indexing, cross-session
|
|
138
|
+
// - outputProfile: 'full' — FOS + MOD, full spec
|
|
139
|
+
// - Higher keystone threshold — more historical context worth surfacing
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
const EXTENDED_COMPOSITOR = {
|
|
142
|
+
defaultTokenBudget: 160000,
|
|
143
|
+
maxHistoryMessages: 1000,
|
|
144
|
+
maxFacts: 60,
|
|
145
|
+
maxCrossSessionContext: 12000,
|
|
146
|
+
maxRecentToolPairs: 5,
|
|
147
|
+
maxProseToolPairs: 15,
|
|
148
|
+
warmHistoryBudgetFraction: 0.45,
|
|
149
|
+
contextWindowReserve: 0.20,
|
|
150
|
+
dynamicReserveEnabled: true,
|
|
151
|
+
dynamicReserveTurnHorizon: 7,
|
|
152
|
+
dynamicReserveMax: 0.45,
|
|
153
|
+
keystoneHistoryFraction: 0.25,
|
|
154
|
+
keystoneMaxMessages: 30,
|
|
155
|
+
keystoneMinSignificance: 0.4,
|
|
156
|
+
targetBudgetFraction: 0.55, // Anvil spec: 0.55 for extended (history is huge, budget carefully)
|
|
157
|
+
maxTotalTriggerTokens: 10000,
|
|
158
|
+
outputProfile: 'full', // FOS + MOD — full fleet spec
|
|
159
|
+
wikiTokenCap: 800, // Anvil spec: 800 for extended
|
|
160
|
+
};
|
|
161
|
+
const EXTENDED_INDEXER = {
|
|
162
|
+
enabled: true,
|
|
163
|
+
factExtractionMode: 'tiered',
|
|
164
|
+
topicDormantAfter: '48h',
|
|
165
|
+
topicClosedAfter: '168h', // 7 days — long-running council topics stay warm
|
|
166
|
+
factDecayRate: 0.01, // slow decay — preserve more institutional memory
|
|
167
|
+
episodeSignificanceThreshold: 0.4,
|
|
168
|
+
periodicInterval: 30000, // 30s — frequent background work for fleet throughput
|
|
169
|
+
batchSize: 256,
|
|
170
|
+
maxMessagesPerTick: 1000,
|
|
171
|
+
};
|
|
172
|
+
export const fullProfile = {
|
|
173
|
+
enabled: true,
|
|
174
|
+
dataDir: './hypermem-data',
|
|
175
|
+
cache: BASE_CACHE,
|
|
176
|
+
compositor: EXTENDED_COMPOSITOR,
|
|
177
|
+
indexer: EXTENDED_INDEXER,
|
|
178
|
+
embedding: {
|
|
179
|
+
...BASE_EMBEDDING,
|
|
180
|
+
batchSize: 64, // larger batches — more throughput for fleet ingest
|
|
181
|
+
timeout: 8000, // tighter timeout — expect capable hardware
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
// Legacy aliases — kept for backward compat, removed in 1.0
|
|
185
|
+
export const minimalProfile = lightProfile;
|
|
186
|
+
export const extendedProfile = fullProfile;
|
|
187
|
+
export const richProfile = fullProfile;
|
|
188
|
+
export const PROFILES = {
|
|
189
|
+
light: lightProfile,
|
|
190
|
+
standard: standardProfile,
|
|
191
|
+
full: fullProfile,
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Load a named profile.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* const config = getProfile('light');
|
|
198
|
+
* const hm = createHyperMem(config);
|
|
199
|
+
*/
|
|
200
|
+
export function getProfile(name) {
|
|
201
|
+
// backward compat
|
|
202
|
+
if (name === 'extended')
|
|
203
|
+
return structuredClone(fullProfile);
|
|
204
|
+
return structuredClone(PROFILES[name]);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Merge a partial config on top of a named profile.
|
|
208
|
+
* Deep-merges compositor and indexer; top-level fields are replaced.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* const config = mergeProfile('light', {
|
|
212
|
+
* cache: { keyPrefix: 'myapp:' },
|
|
213
|
+
* compositor: { outputProfile: 'standard' }, // upgrade tier
|
|
214
|
+
* });
|
|
215
|
+
*/
|
|
216
|
+
export function mergeProfile(name, overrides) {
|
|
217
|
+
const base = getProfile(name);
|
|
218
|
+
return {
|
|
219
|
+
...base,
|
|
220
|
+
...overrides,
|
|
221
|
+
compositor: { ...base.compositor, ...(overrides.compositor ?? {}) },
|
|
222
|
+
indexer: { ...base.indexer, ...(overrides.indexer ?? {}) },
|
|
223
|
+
embedding: { ...base.embedding, ...(overrides.embedding ?? {}) },
|
|
224
|
+
cache: { ...base.cache, ...(overrides.cache ?? {}) },
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Provider Translator
|
|
3
3
|
*
|
|
4
4
|
* Converts between provider-neutral (NeutralMessage) and provider-specific formats.
|
|
5
5
|
* This is the ONLY place where provider-specific formatting exists.
|
|
@@ -10,12 +10,22 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { NeutralMessage, NeutralToolResult, ProviderMessage } from './types.js';
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Final pair-integrity sweep before provider translation.
|
|
14
|
+
*
|
|
15
|
+
* Invariant: never emit a tool_result unless its matching tool_use/tool_call
|
|
16
|
+
* exists in the immediately prior assistant message with the same ID.
|
|
17
|
+
*
|
|
18
|
+
* If the pair is broken, degrade the orphan tool_result into plain user text
|
|
19
|
+
* so providers never see an invalid tool_result block.
|
|
20
|
+
*/
|
|
21
|
+
export declare function repairToolCallPairs(messages: NeutralMessage[]): NeutralMessage[];
|
|
22
|
+
/**
|
|
23
|
+
* Generate a hypermem-native tool call ID.
|
|
14
24
|
* These are provider-neutral and deterministic within a session.
|
|
15
25
|
*/
|
|
16
26
|
export declare function generateToolCallId(): string;
|
|
17
27
|
/**
|
|
18
|
-
* Convert a provider-specific tool call ID to a
|
|
28
|
+
* Convert a provider-specific tool call ID to a hypermem ID.
|
|
19
29
|
* Deterministic: same input always produces same output.
|
|
20
30
|
*/
|
|
21
31
|
export declare function normalizeToolCallId(providerId: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-translator.d.ts","sourceRoot":"","sources":["../src/provider-translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,cAAc,EAEd,iBAAiB,EACjB,eAAe,EAChB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"provider-translator.d.ts","sourceRoot":"","sources":["../src/provider-translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,cAAc,EAEd,iBAAiB,EACjB,eAAe,EAChB,MAAM,YAAY,CAAC;AAYpB;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,cAAc,EAAE,CA4ChF;AAOD;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAK3C;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI9D;AAID,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,kBAAkB,GAAG,SAAS,CAAC;AAEnF,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,CAOtF;AAgMD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EAAE,EAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,eAAe,EAAE,CAenB;AA8ED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,GACf,cAAc,CAYhB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAQxG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,cAAc,CAOjF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Provider Translator
|
|
3
3
|
*
|
|
4
4
|
* Converts between provider-neutral (NeutralMessage) and provider-specific formats.
|
|
5
5
|
* This is the ONLY place where provider-specific formatting exists.
|
|
@@ -8,11 +8,64 @@
|
|
|
8
8
|
* This eliminates grafting/stripping entirely — tool calls are stored as structured
|
|
9
9
|
* data, and each provider gets the format it expects at send time.
|
|
10
10
|
*/
|
|
11
|
+
function summarizeOrphanToolResult(tr) {
|
|
12
|
+
const toolName = tr.name || 'tool';
|
|
13
|
+
const status = tr.isError ? 'error' : 'result';
|
|
14
|
+
const content = (tr.content || '').replace(/\s+/g, ' ').trim();
|
|
15
|
+
const preview = content.length > 160 ? `${content.slice(0, 157)}...` : content;
|
|
16
|
+
return preview
|
|
17
|
+
? `[${toolName} ${status} omitted: missing matching tool call] ${preview}`
|
|
18
|
+
: `[${toolName} ${status} omitted: missing matching tool call]`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Final pair-integrity sweep before provider translation.
|
|
22
|
+
*
|
|
23
|
+
* Invariant: never emit a tool_result unless its matching tool_use/tool_call
|
|
24
|
+
* exists in the immediately prior assistant message with the same ID.
|
|
25
|
+
*
|
|
26
|
+
* If the pair is broken, degrade the orphan tool_result into plain user text
|
|
27
|
+
* so providers never see an invalid tool_result block.
|
|
28
|
+
*/
|
|
29
|
+
export function repairToolCallPairs(messages) {
|
|
30
|
+
const repaired = [];
|
|
31
|
+
for (const msg of messages) {
|
|
32
|
+
if (msg.role !== 'user' || !msg.toolResults || msg.toolResults.length === 0) {
|
|
33
|
+
repaired.push(msg);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const prev = repaired[repaired.length - 1];
|
|
37
|
+
const validCallIds = new Set(prev?.role === 'assistant' && prev.toolCalls
|
|
38
|
+
? prev.toolCalls.map(tc => tc.id)
|
|
39
|
+
: []);
|
|
40
|
+
const keptResults = msg.toolResults.filter(tr => validCallIds.has(tr.callId));
|
|
41
|
+
const orphanResults = msg.toolResults.filter(tr => !validCallIds.has(tr.callId));
|
|
42
|
+
if (orphanResults.length === 0) {
|
|
43
|
+
repaired.push(msg);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const orphanText = orphanResults.map(summarizeOrphanToolResult).join('\n');
|
|
47
|
+
const mergedText = [msg.textContent, orphanText].filter(Boolean).join('\n');
|
|
48
|
+
if (keptResults.length > 0) {
|
|
49
|
+
repaired.push({
|
|
50
|
+
...msg,
|
|
51
|
+
textContent: mergedText || msg.textContent,
|
|
52
|
+
toolResults: keptResults,
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
repaired.push({
|
|
57
|
+
...msg,
|
|
58
|
+
textContent: mergedText || msg.textContent || '[tool result omitted: missing matching tool call]',
|
|
59
|
+
toolResults: null,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return repaired;
|
|
63
|
+
}
|
|
11
64
|
import { createHash } from 'node:crypto';
|
|
12
65
|
// ─── ID Generation ───────────────────────────────────────────────
|
|
13
66
|
let idCounter = 0;
|
|
14
67
|
/**
|
|
15
|
-
* Generate a
|
|
68
|
+
* Generate a hypermem-native tool call ID.
|
|
16
69
|
* These are provider-neutral and deterministic within a session.
|
|
17
70
|
*/
|
|
18
71
|
export function generateToolCallId() {
|
|
@@ -22,7 +75,7 @@ export function generateToolCallId() {
|
|
|
22
75
|
return `hm_${timestamp}_${counter}`;
|
|
23
76
|
}
|
|
24
77
|
/**
|
|
25
|
-
* Convert a provider-specific tool call ID to a
|
|
78
|
+
* Convert a provider-specific tool call ID to a hypermem ID.
|
|
26
79
|
* Deterministic: same input always produces same output.
|
|
27
80
|
*/
|
|
28
81
|
export function normalizeToolCallId(providerId) {
|
|
@@ -52,8 +105,8 @@ export function detectProvider(providerString) {
|
|
|
52
105
|
* The last system message BEFORE the dynamicBoundary marker gets
|
|
53
106
|
* cache_control: {type: "ephemeral"} to mark the static/dynamic boundary.
|
|
54
107
|
*
|
|
55
|
-
* Static (cacheable): system prompt + identity
|
|
56
|
-
* Dynamic (not cacheable): context block (facts/recall), conversation history
|
|
108
|
+
* Static (cacheable): system prompt + identity + stable output profile prefix
|
|
109
|
+
* Dynamic (not cacheable): context block (facts/recall/recent actions), conversation history
|
|
57
110
|
*
|
|
58
111
|
* This allows Anthropic to cache the static prefix and skip re-tokenizing it.
|
|
59
112
|
*/
|
|
@@ -225,17 +278,18 @@ function toOpenAIResponses(messages) {
|
|
|
225
278
|
* Convert neutral messages to provider-specific format.
|
|
226
279
|
*/
|
|
227
280
|
export function toProviderFormat(messages, provider) {
|
|
281
|
+
const repairedMessages = repairToolCallPairs(messages);
|
|
228
282
|
const providerType = detectProvider(provider);
|
|
229
283
|
switch (providerType) {
|
|
230
284
|
case 'anthropic':
|
|
231
|
-
return toAnthropic(
|
|
285
|
+
return toAnthropic(repairedMessages);
|
|
232
286
|
case 'openai':
|
|
233
|
-
return toOpenAI(
|
|
287
|
+
return toOpenAI(repairedMessages);
|
|
234
288
|
case 'openai-responses':
|
|
235
|
-
return toOpenAIResponses(
|
|
289
|
+
return toOpenAIResponses(repairedMessages);
|
|
236
290
|
default:
|
|
237
291
|
// Default to OpenAI format as it's most widely compatible
|
|
238
|
-
return toOpenAI(
|
|
292
|
+
return toOpenAI(repairedMessages);
|
|
239
293
|
}
|
|
240
294
|
}
|
|
241
295
|
// ─── From Provider Format ────────────────────────────────────────
|
package/dist/rate-limiter.d.ts
CHANGED
package/dist/rate-limiter.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repair-tool-pairs.ts
|
|
3
|
+
*
|
|
4
|
+
* Strips orphaned tool result entries from a pi-agent message array.
|
|
5
|
+
*
|
|
6
|
+
* Background: HyperMem compaction and in-memory trim passes can remove assistant
|
|
7
|
+
* messages that contain tool_use/toolCall blocks without removing the corresponding
|
|
8
|
+
* tool result messages that follow them. Anthropic and Gemini reject these orphaned
|
|
9
|
+
* tool results with a 400 error.
|
|
10
|
+
*
|
|
11
|
+
* This module provides a pure repair function that can be applied at any output
|
|
12
|
+
* boundary to sanitise the message list before it reaches the provider.
|
|
13
|
+
*
|
|
14
|
+
* Supported formats:
|
|
15
|
+
* - pi-agent: role:'toolResult' messages with toolCallId field
|
|
16
|
+
* - Anthropic native: user messages with content blocks of type:'tool_result' and tool_use_id
|
|
17
|
+
*
|
|
18
|
+
* Returns a new array. Does not mutate the input.
|
|
19
|
+
*/
|
|
20
|
+
type AnyMessage = Record<string, unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Repair orphaned tool pairs in a pi-agent / OpenClaw message array.
|
|
23
|
+
*
|
|
24
|
+
* Orphan types handled:
|
|
25
|
+
* 1. role:'toolResult' message whose toolCallId has no matching toolCall/tool_use
|
|
26
|
+
* block in any assistant message in the array.
|
|
27
|
+
* 2. User message whose content contains only type:'tool_result' blocks where all
|
|
28
|
+
* of those blocks reference a tool_use_id that does not appear in any assistant
|
|
29
|
+
* message in the array. (Anthropic-native format.)
|
|
30
|
+
*
|
|
31
|
+
* Also strips orphaned assistant messages that contain ONLY tool_use/toolCall blocks
|
|
32
|
+
* where none of those calls has a corresponding tool result anywhere in the array.
|
|
33
|
+
*
|
|
34
|
+
* Returns a new array (does not mutate input).
|
|
35
|
+
*/
|
|
36
|
+
export declare function repairToolPairs(messages: AnyMessage[]): AnyMessage[];
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=repair-tool-pairs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repair-tool-pairs.d.ts","sourceRoot":"","sources":["../src/repair-tool-pairs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAmHpE"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repair-tool-pairs.ts
|
|
3
|
+
*
|
|
4
|
+
* Strips orphaned tool result entries from a pi-agent message array.
|
|
5
|
+
*
|
|
6
|
+
* Background: HyperMem compaction and in-memory trim passes can remove assistant
|
|
7
|
+
* messages that contain tool_use/toolCall blocks without removing the corresponding
|
|
8
|
+
* tool result messages that follow them. Anthropic and Gemini reject these orphaned
|
|
9
|
+
* tool results with a 400 error.
|
|
10
|
+
*
|
|
11
|
+
* This module provides a pure repair function that can be applied at any output
|
|
12
|
+
* boundary to sanitise the message list before it reaches the provider.
|
|
13
|
+
*
|
|
14
|
+
* Supported formats:
|
|
15
|
+
* - pi-agent: role:'toolResult' messages with toolCallId field
|
|
16
|
+
* - Anthropic native: user messages with content blocks of type:'tool_result' and tool_use_id
|
|
17
|
+
*
|
|
18
|
+
* Returns a new array. Does not mutate the input.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Repair orphaned tool pairs in a pi-agent / OpenClaw message array.
|
|
22
|
+
*
|
|
23
|
+
* Orphan types handled:
|
|
24
|
+
* 1. role:'toolResult' message whose toolCallId has no matching toolCall/tool_use
|
|
25
|
+
* block in any assistant message in the array.
|
|
26
|
+
* 2. User message whose content contains only type:'tool_result' blocks where all
|
|
27
|
+
* of those blocks reference a tool_use_id that does not appear in any assistant
|
|
28
|
+
* message in the array. (Anthropic-native format.)
|
|
29
|
+
*
|
|
30
|
+
* Also strips orphaned assistant messages that contain ONLY tool_use/toolCall blocks
|
|
31
|
+
* where none of those calls has a corresponding tool result anywhere in the array.
|
|
32
|
+
*
|
|
33
|
+
* Returns a new array (does not mutate input).
|
|
34
|
+
*/
|
|
35
|
+
export function repairToolPairs(messages) {
|
|
36
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
37
|
+
return messages;
|
|
38
|
+
// ── Pass 1: Collect all valid tool call IDs from assistant messages ────────
|
|
39
|
+
const validCallIds = new Set();
|
|
40
|
+
for (const msg of messages) {
|
|
41
|
+
if (msg.role !== 'assistant')
|
|
42
|
+
continue;
|
|
43
|
+
// NeutralMessage format: msg.toolCalls[]
|
|
44
|
+
if (Array.isArray(msg.toolCalls)) {
|
|
45
|
+
for (const tc of msg.toolCalls) {
|
|
46
|
+
if (typeof tc.id === 'string' && tc.id)
|
|
47
|
+
validCallIds.add(tc.id);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Content array format: type:'toolCall' or type:'tool_use' blocks
|
|
51
|
+
if (Array.isArray(msg.content)) {
|
|
52
|
+
for (const block of msg.content) {
|
|
53
|
+
if ((block.type === 'toolCall' || block.type === 'tool_use') &&
|
|
54
|
+
typeof block.id === 'string' &&
|
|
55
|
+
block.id) {
|
|
56
|
+
validCallIds.add(block.id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ── Pass 2: Collect all result IDs that have a valid call ─────────────────
|
|
62
|
+
const validResultIds = new Set();
|
|
63
|
+
for (const msg of messages) {
|
|
64
|
+
// pi-agent ToolResultMessage
|
|
65
|
+
if (msg.role === 'toolResult') {
|
|
66
|
+
const id = typeof msg.toolCallId === 'string' ? msg.toolCallId :
|
|
67
|
+
typeof msg.tool_call_id === 'string' ? msg.tool_call_id : '';
|
|
68
|
+
if (id && validCallIds.has(id))
|
|
69
|
+
validResultIds.add(id);
|
|
70
|
+
}
|
|
71
|
+
// Anthropic-native tool_result blocks inside user messages
|
|
72
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
73
|
+
for (const block of msg.content) {
|
|
74
|
+
if (block.type === 'tool_result' && typeof block.tool_use_id === 'string' && block.tool_use_id) {
|
|
75
|
+
if (validCallIds.has(block.tool_use_id))
|
|
76
|
+
validResultIds.add(block.tool_use_id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ── Pass 3: Filter out orphaned messages / blocks ─────────────────────────
|
|
82
|
+
const result = [];
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
// ── pi-agent ToolResultMessage ─────────────────────────────────────────
|
|
85
|
+
if (msg.role === 'toolResult') {
|
|
86
|
+
const id = typeof msg.toolCallId === 'string' ? msg.toolCallId :
|
|
87
|
+
typeof msg.tool_call_id === 'string' ? msg.tool_call_id : '';
|
|
88
|
+
if (!id || !validCallIds.has(id)) {
|
|
89
|
+
// Orphaned — drop
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
result.push(msg);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// ── Anthropic-native: user message with tool_result content blocks ─────
|
|
96
|
+
if (msg.role === 'user' && Array.isArray(msg.content)) {
|
|
97
|
+
const content = msg.content;
|
|
98
|
+
const hasToolResultBlocks = content.some(b => b.type === 'tool_result');
|
|
99
|
+
if (hasToolResultBlocks) {
|
|
100
|
+
const filteredContent = content.filter(block => {
|
|
101
|
+
if (block.type !== 'tool_result')
|
|
102
|
+
return true; // keep non-tool_result blocks
|
|
103
|
+
const toolUseId = typeof block.tool_use_id === 'string' ? block.tool_use_id : '';
|
|
104
|
+
return toolUseId && validCallIds.has(toolUseId);
|
|
105
|
+
});
|
|
106
|
+
// If the message became empty after stripping all orphaned tool_result blocks, skip it
|
|
107
|
+
if (filteredContent.length === 0)
|
|
108
|
+
continue;
|
|
109
|
+
result.push({ ...msg, content: filteredContent });
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ── Assistant message with ONLY unmatched tool_use/toolCall blocks ─────
|
|
114
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
115
|
+
const content = msg.content;
|
|
116
|
+
const toolCallBlocks = content.filter(b => b.type === 'toolCall' || b.type === 'tool_use');
|
|
117
|
+
const nonToolCallBlocks = content.filter(b => b.type !== 'toolCall' && b.type !== 'tool_use');
|
|
118
|
+
// Only strip if the assistant message is purely tool-calls (no text)
|
|
119
|
+
if (toolCallBlocks.length > 0 && nonToolCallBlocks.length === 0) {
|
|
120
|
+
const hasAnyResult = toolCallBlocks.some(b => {
|
|
121
|
+
const id = typeof b.id === 'string' ? b.id : '';
|
|
122
|
+
return id && validResultIds.has(id);
|
|
123
|
+
});
|
|
124
|
+
if (!hasAnyResult) {
|
|
125
|
+
// Pure tool-call block with no paired results — drop
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
result.push(msg);
|
|
131
|
+
}
|
|
132
|
+
const dropped = messages.length - result.length;
|
|
133
|
+
if (dropped > 0) {
|
|
134
|
+
console.log(`[hypermem] repairToolPairs: dropped ${dropped} orphaned message(s) (${messages.length} → ${result.length})`);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=repair-tool-pairs.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Retrieval Policy
|
|
3
|
+
*
|
|
4
|
+
* Single enforced policy layer for scope-based access control during retrieval.
|
|
5
|
+
* Called by the compositor to filter items before they are injected into context.
|
|
6
|
+
*
|
|
7
|
+
* Scope rules:
|
|
8
|
+
* 'agent' (default / null / undefined): allowed if agentId matches OR is null/undefined (global)
|
|
9
|
+
* 'session': allowed if both agentId AND sessionKey match
|
|
10
|
+
* 'user': allowed if agentId matches
|
|
11
|
+
* 'global': always allowed
|
|
12
|
+
* any other value: denied with reason 'ambiguous_scope'
|
|
13
|
+
*/
|
|
14
|
+
export type RetrievalScope = 'agent' | 'session' | 'user' | 'global';
|
|
15
|
+
export interface RetrievalContext {
|
|
16
|
+
agentId: string;
|
|
17
|
+
sessionKey: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ScopeCheckResult {
|
|
20
|
+
allowed: boolean;
|
|
21
|
+
/** One of: 'allowed' | 'scope_filtered' | 'ambiguous_scope' */
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check whether a single item is accessible in the given retrieval context.
|
|
26
|
+
*
|
|
27
|
+
* @param itemScope The scope stored on the item (null/undefined → defaults to 'agent')
|
|
28
|
+
* @param itemAgentId The agentId stored on the item (null/undefined → global)
|
|
29
|
+
* @param itemSessionKey The sessionKey stored on the item (null/undefined → any)
|
|
30
|
+
* @param ctx The requester's retrieval context
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkScope(itemScope: string | null | undefined, itemAgentId: string | null | undefined, itemSessionKey: string | null | undefined, ctx: RetrievalContext): ScopeCheckResult;
|
|
33
|
+
/**
|
|
34
|
+
* Filter an array of items by scope, returning allowed items and a filtered count.
|
|
35
|
+
*
|
|
36
|
+
* Items are expected to have optional `agentId`, `sessionKey`, and `scope` fields.
|
|
37
|
+
* Null/undefined fields are treated as "unset" (permissive for their slot).
|
|
38
|
+
*
|
|
39
|
+
* @param items Array of items to filter
|
|
40
|
+
* @param ctx The requester's retrieval context
|
|
41
|
+
* @returns { allowed: T[], filteredCount: number }
|
|
42
|
+
*/
|
|
43
|
+
export declare function filterByScope<T extends {
|
|
44
|
+
agentId?: string | null;
|
|
45
|
+
sessionKey?: string | null;
|
|
46
|
+
scope?: string | null;
|
|
47
|
+
}>(items: T[], ctx: RetrievalContext): {
|
|
48
|
+
allowed: T[];
|
|
49
|
+
filteredCount: number;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=retrieval-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retrieval-policy.d.ts","sourceRoot":"","sources":["../src/retrieval-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,GAAG,EAAE,gBAAgB,GACpB,gBAAgB,CAqClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS;IACtC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,EACC,KAAK,EAAE,CAAC,EAAE,EACV,GAAG,EAAE,gBAAgB,GACpB;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAczC"}
|