@joshuaswarren/openclaw-engram 9.0.26 → 9.0.27
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 +2 -1
- package/dist/causal-trajectory-graph-HQLYU3SO.js +60 -0
- package/dist/causal-trajectory-graph-HQLYU3SO.js.map +1 -0
- package/dist/chunk-BNBG2XP6.js +321 -0
- package/dist/chunk-BNBG2XP6.js.map +1 -0
- package/dist/index.js +344 -652
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ AI agents forget everything between conversations. Engram fixes that.
|
|
|
32
32
|
- **Memory OS features** — Graph recall, temporal memory tree, lifecycle policy, compounding, shared context, memory boxes, and identity continuity can be enabled progressively as your install grows.
|
|
33
33
|
- **Benchmark-first roadmap** — Engram now has an evaluation harness with live shadow recall recording and a CI benchmark delta gate, so memory improvements can be measured and regression-checked instead of argued from anecdotes.
|
|
34
34
|
- **Objective-state recall** — Engram can now store normalized file, process, and tool outcomes and, when `objectiveStateRecallEnabled` is enabled, inject the most relevant objective-state snapshots back into recall context as a separate `Objective State` section.
|
|
35
|
-
- **Causal trajectory
|
|
35
|
+
- **Causal trajectory graph foundation** — Engram can now persist typed `goal -> action -> observation -> outcome -> follow-up` chains when `causalTrajectoryMemoryEnabled` is enabled and, with `actionGraphRecallEnabled`, emit deterministic action-conditioned edges into the causal graph for later trajectory-aware retrieval.
|
|
36
36
|
- **Zero-config start** — Install, add an API key, restart. Engram works out of the box with sensible defaults and progressively unlocks advanced features as you enable them.
|
|
37
37
|
|
|
38
38
|
## Quick Start
|
|
@@ -186,6 +186,7 @@ Key settings:
|
|
|
186
186
|
| `objectiveStateStoreDir` | `{memoryDir}/state/objective-state` | Root directory for objective-state snapshots |
|
|
187
187
|
| `causalTrajectoryMemoryEnabled` | `false` | Enable the causal-trajectory memory foundation for typed causal chains |
|
|
188
188
|
| `causalTrajectoryStoreDir` | `{memoryDir}/state/causal-trajectories` | Root directory for causal-trajectory records |
|
|
189
|
+
| `actionGraphRecallEnabled` | `false` | Write action-conditioned causal-stage edges from typed trajectory records into the causal graph |
|
|
189
190
|
|
|
190
191
|
Full reference: [Config Reference](docs/config-reference.md)
|
|
191
192
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// openclaw-engram: Local-first memory plugin
|
|
2
|
+
import {
|
|
3
|
+
appendEdge
|
|
4
|
+
} from "./chunk-BNBG2XP6.js";
|
|
5
|
+
|
|
6
|
+
// src/causal-trajectory-graph.ts
|
|
7
|
+
function causalTrajectoryGraphNodeId(trajectoryId, stage) {
|
|
8
|
+
return `causal-trajectory/${trajectoryId}#${stage}`;
|
|
9
|
+
}
|
|
10
|
+
function buildCausalTrajectoryGraphEdges(record) {
|
|
11
|
+
const edges = [
|
|
12
|
+
{
|
|
13
|
+
from: causalTrajectoryGraphNodeId(record.trajectoryId, "goal"),
|
|
14
|
+
to: causalTrajectoryGraphNodeId(record.trajectoryId, "action"),
|
|
15
|
+
type: "causal",
|
|
16
|
+
weight: 1,
|
|
17
|
+
label: "goal_to_action",
|
|
18
|
+
ts: record.recordedAt
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
from: causalTrajectoryGraphNodeId(record.trajectoryId, "action"),
|
|
22
|
+
to: causalTrajectoryGraphNodeId(record.trajectoryId, "observation"),
|
|
23
|
+
type: "causal",
|
|
24
|
+
weight: 1,
|
|
25
|
+
label: "action_to_observation",
|
|
26
|
+
ts: record.recordedAt
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
from: causalTrajectoryGraphNodeId(record.trajectoryId, "observation"),
|
|
30
|
+
to: causalTrajectoryGraphNodeId(record.trajectoryId, "outcome"),
|
|
31
|
+
type: "causal",
|
|
32
|
+
weight: 1,
|
|
33
|
+
label: `observation_to_outcome:${record.outcomeKind}`,
|
|
34
|
+
ts: record.recordedAt
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
if (record.followUpSummary) {
|
|
38
|
+
edges.push({
|
|
39
|
+
from: causalTrajectoryGraphNodeId(record.trajectoryId, "outcome"),
|
|
40
|
+
to: causalTrajectoryGraphNodeId(record.trajectoryId, "follow_up"),
|
|
41
|
+
type: "causal",
|
|
42
|
+
weight: 1,
|
|
43
|
+
label: "outcome_to_follow_up",
|
|
44
|
+
ts: record.recordedAt
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return edges;
|
|
48
|
+
}
|
|
49
|
+
async function appendCausalTrajectoryGraphEdges(options) {
|
|
50
|
+
const edges = buildCausalTrajectoryGraphEdges(options.record);
|
|
51
|
+
for (const edge of edges) {
|
|
52
|
+
await appendEdge(options.memoryDir, edge);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export {
|
|
56
|
+
appendCausalTrajectoryGraphEdges,
|
|
57
|
+
buildCausalTrajectoryGraphEdges,
|
|
58
|
+
causalTrajectoryGraphNodeId
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=causal-trajectory-graph-HQLYU3SO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/causal-trajectory-graph.ts"],"sourcesContent":["import { appendEdge, type GraphEdge } from \"./graph.js\";\nimport type { CausalTrajectoryRecord } from \"./causal-trajectory.js\";\n\nexport type CausalTrajectoryGraphStage = \"goal\" | \"action\" | \"observation\" | \"outcome\" | \"follow_up\";\n\nexport function causalTrajectoryGraphNodeId(trajectoryId: string, stage: CausalTrajectoryGraphStage): string {\n return `causal-trajectory/${trajectoryId}#${stage}`;\n}\n\nexport function buildCausalTrajectoryGraphEdges(record: CausalTrajectoryRecord): GraphEdge[] {\n const edges: GraphEdge[] = [\n {\n from: causalTrajectoryGraphNodeId(record.trajectoryId, \"goal\"),\n to: causalTrajectoryGraphNodeId(record.trajectoryId, \"action\"),\n type: \"causal\",\n weight: 1.0,\n label: \"goal_to_action\",\n ts: record.recordedAt,\n },\n {\n from: causalTrajectoryGraphNodeId(record.trajectoryId, \"action\"),\n to: causalTrajectoryGraphNodeId(record.trajectoryId, \"observation\"),\n type: \"causal\",\n weight: 1.0,\n label: \"action_to_observation\",\n ts: record.recordedAt,\n },\n {\n from: causalTrajectoryGraphNodeId(record.trajectoryId, \"observation\"),\n to: causalTrajectoryGraphNodeId(record.trajectoryId, \"outcome\"),\n type: \"causal\",\n weight: 1.0,\n label: `observation_to_outcome:${record.outcomeKind}`,\n ts: record.recordedAt,\n },\n ];\n\n if (record.followUpSummary) {\n edges.push({\n from: causalTrajectoryGraphNodeId(record.trajectoryId, \"outcome\"),\n to: causalTrajectoryGraphNodeId(record.trajectoryId, \"follow_up\"),\n type: \"causal\",\n weight: 1.0,\n label: \"outcome_to_follow_up\",\n ts: record.recordedAt,\n });\n }\n\n return edges;\n}\n\nexport async function appendCausalTrajectoryGraphEdges(options: {\n memoryDir: string;\n record: CausalTrajectoryRecord;\n}): Promise<void> {\n const edges = buildCausalTrajectoryGraphEdges(options.record);\n for (const edge of edges) {\n await appendEdge(options.memoryDir, edge);\n }\n}\n"],"mappings":";;;;;;AAKO,SAAS,4BAA4B,cAAsB,OAA2C;AAC3G,SAAO,qBAAqB,YAAY,IAAI,KAAK;AACnD;AAEO,SAAS,gCAAgC,QAA6C;AAC3F,QAAM,QAAqB;AAAA,IACzB;AAAA,MACE,MAAM,4BAA4B,OAAO,cAAc,MAAM;AAAA,MAC7D,IAAI,4BAA4B,OAAO,cAAc,QAAQ;AAAA,MAC7D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,IAAI,OAAO;AAAA,IACb;AAAA,IACA;AAAA,MACE,MAAM,4BAA4B,OAAO,cAAc,QAAQ;AAAA,MAC/D,IAAI,4BAA4B,OAAO,cAAc,aAAa;AAAA,MAClE,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,IAAI,OAAO;AAAA,IACb;AAAA,IACA;AAAA,MACE,MAAM,4BAA4B,OAAO,cAAc,aAAa;AAAA,MACpE,IAAI,4BAA4B,OAAO,cAAc,SAAS;AAAA,MAC9D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO,0BAA0B,OAAO,WAAW;AAAA,MACnD,IAAI,OAAO;AAAA,IACb;AAAA,EACF;AAEA,MAAI,OAAO,iBAAiB;AAC1B,UAAM,KAAK;AAAA,MACT,MAAM,4BAA4B,OAAO,cAAc,SAAS;AAAA,MAChE,IAAI,4BAA4B,OAAO,cAAc,WAAW;AAAA,MAChE,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,IAAI,OAAO;AAAA,IACb,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,eAAsB,iCAAiC,SAGrC;AAChB,QAAM,QAAQ,gCAAgC,QAAQ,MAAM;AAC5D,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,QAAQ,WAAW,IAAI;AAAA,EAC1C;AACF;","names":[]}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// openclaw-engram: Local-first memory plugin
|
|
2
|
+
|
|
3
|
+
// src/graph.ts
|
|
4
|
+
import { mkdir, appendFile, readFile } from "fs/promises";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
var CAUSAL_PHRASES = [
|
|
7
|
+
"as a result",
|
|
8
|
+
"led to",
|
|
9
|
+
"because of",
|
|
10
|
+
"therefore",
|
|
11
|
+
"caused",
|
|
12
|
+
"because"
|
|
13
|
+
];
|
|
14
|
+
function graphsDir(memoryDir) {
|
|
15
|
+
return path.join(memoryDir, "state", "graphs");
|
|
16
|
+
}
|
|
17
|
+
function graphFilePath(memoryDir, type) {
|
|
18
|
+
return path.join(graphsDir(memoryDir), `${type}.jsonl`);
|
|
19
|
+
}
|
|
20
|
+
async function ensureGraphsDir(memoryDir) {
|
|
21
|
+
await mkdir(graphsDir(memoryDir), { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
async function appendEdge(memoryDir, edge) {
|
|
24
|
+
await ensureGraphsDir(memoryDir);
|
|
25
|
+
const line = JSON.stringify(edge) + "\n";
|
|
26
|
+
await appendFile(graphFilePath(memoryDir, edge.type), line, "utf8");
|
|
27
|
+
}
|
|
28
|
+
async function readEdges(memoryDir, type) {
|
|
29
|
+
const filePath = graphFilePath(memoryDir, type);
|
|
30
|
+
try {
|
|
31
|
+
const raw = await readFile(filePath, "utf8");
|
|
32
|
+
const edges = [];
|
|
33
|
+
for (const line of raw.split("\n")) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
if (!trimmed) continue;
|
|
36
|
+
try {
|
|
37
|
+
edges.push(JSON.parse(trimmed));
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return edges;
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function readAllEdges(memoryDir, config) {
|
|
47
|
+
const parts = await Promise.all([
|
|
48
|
+
config.entityGraphEnabled ? readEdges(memoryDir, "entity") : Promise.resolve([]),
|
|
49
|
+
config.timeGraphEnabled ? readEdges(memoryDir, "time") : Promise.resolve([]),
|
|
50
|
+
config.causalGraphEnabled ? readEdges(memoryDir, "causal") : Promise.resolve([])
|
|
51
|
+
]);
|
|
52
|
+
return parts.flat();
|
|
53
|
+
}
|
|
54
|
+
function isValidGraphEdge(raw, expectedType) {
|
|
55
|
+
if (!raw || typeof raw !== "object") return false;
|
|
56
|
+
const edge = raw;
|
|
57
|
+
return edge.type === expectedType && typeof edge.from === "string" && edge.from.length > 0 && typeof edge.to === "string" && edge.to.length > 0 && typeof edge.weight === "number" && Number.isFinite(edge.weight) && typeof edge.label === "string" && typeof edge.ts === "string";
|
|
58
|
+
}
|
|
59
|
+
async function analyzeGraphHealth(memoryDir, options) {
|
|
60
|
+
const enabledTypes = [];
|
|
61
|
+
if (options?.entityGraphEnabled !== false) enabledTypes.push("entity");
|
|
62
|
+
if (options?.timeGraphEnabled !== false) enabledTypes.push("time");
|
|
63
|
+
if (options?.causalGraphEnabled !== false) enabledTypes.push("causal");
|
|
64
|
+
const files = [];
|
|
65
|
+
const globalNodes = /* @__PURE__ */ new Set();
|
|
66
|
+
for (const type of enabledTypes) {
|
|
67
|
+
const filePath = graphFilePath(memoryDir, type);
|
|
68
|
+
let exists = true;
|
|
69
|
+
let totalLines = 0;
|
|
70
|
+
let validEdges = 0;
|
|
71
|
+
let corruptLines = 0;
|
|
72
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
73
|
+
try {
|
|
74
|
+
const raw = await readFile(filePath, "utf8");
|
|
75
|
+
for (const line of raw.split("\n")) {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
if (!trimmed) continue;
|
|
78
|
+
totalLines += 1;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(trimmed);
|
|
81
|
+
if (!isValidGraphEdge(parsed, type)) {
|
|
82
|
+
corruptLines += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
validEdges += 1;
|
|
86
|
+
nodes.add(parsed.from);
|
|
87
|
+
nodes.add(parsed.to);
|
|
88
|
+
globalNodes.add(parsed.from);
|
|
89
|
+
globalNodes.add(parsed.to);
|
|
90
|
+
} catch {
|
|
91
|
+
corruptLines += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
exists = false;
|
|
96
|
+
}
|
|
97
|
+
files.push({
|
|
98
|
+
type,
|
|
99
|
+
filePath,
|
|
100
|
+
exists,
|
|
101
|
+
totalLines,
|
|
102
|
+
validEdges,
|
|
103
|
+
corruptLines,
|
|
104
|
+
uniqueNodes: nodes.size
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const totals = files.reduce(
|
|
108
|
+
(acc, item) => {
|
|
109
|
+
acc.totalLines += item.totalLines;
|
|
110
|
+
acc.validEdges += item.validEdges;
|
|
111
|
+
acc.corruptLines += item.corruptLines;
|
|
112
|
+
return acc;
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
totalLines: 0,
|
|
116
|
+
validEdges: 0,
|
|
117
|
+
corruptLines: 0,
|
|
118
|
+
uniqueNodes: globalNodes.size
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
totals.uniqueNodes = globalNodes.size;
|
|
122
|
+
const report = {
|
|
123
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
|
+
enabledTypes,
|
|
125
|
+
totals,
|
|
126
|
+
files
|
|
127
|
+
};
|
|
128
|
+
if (options?.includeRepairGuidance === true) {
|
|
129
|
+
const guidance = [];
|
|
130
|
+
if (totals.corruptLines > 0) {
|
|
131
|
+
guidance.push("Corrupt graph lines detected: back up memory/state/graphs, then rebuild graphs from clean memory replay/extraction runs.");
|
|
132
|
+
}
|
|
133
|
+
if (totals.validEdges === 0) {
|
|
134
|
+
guidance.push("No valid edges detected yet: run normal extraction traffic (or replay ingestion) to seed graph files.");
|
|
135
|
+
}
|
|
136
|
+
if (guidance.length > 0) report.repairGuidance = guidance;
|
|
137
|
+
}
|
|
138
|
+
return report;
|
|
139
|
+
}
|
|
140
|
+
function detectCausalPhrase(text) {
|
|
141
|
+
const lower = text.toLowerCase();
|
|
142
|
+
for (const phrase of CAUSAL_PHRASES) {
|
|
143
|
+
if (lower.includes(phrase)) return phrase;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
var GraphIndex = class {
|
|
148
|
+
memoryDir;
|
|
149
|
+
cfg;
|
|
150
|
+
constructor(memoryDir, cfg) {
|
|
151
|
+
this.memoryDir = memoryDir;
|
|
152
|
+
this.cfg = cfg;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Called after a memory is written to disk.
|
|
156
|
+
*
|
|
157
|
+
* @param memoryPath - relative path from memoryDir (e.g. "facts/2026-02-22/abc.md")
|
|
158
|
+
* @param entityRef - entityRef frontmatter field (if any)
|
|
159
|
+
* @param content - full memory text (for causal detection)
|
|
160
|
+
* @param created - ISO timestamp of this memory
|
|
161
|
+
* @param threadId - current thread ID (for time graph)
|
|
162
|
+
* @param recentInThread - paths of the N most-recent memories in this thread (for time graph)
|
|
163
|
+
* @param entitySiblings - paths of other memories that share the same entityRef (for entity graph)
|
|
164
|
+
*/
|
|
165
|
+
async onMemoryWritten(opts) {
|
|
166
|
+
if (!this.cfg.multiGraphMemoryEnabled) return;
|
|
167
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
168
|
+
try {
|
|
169
|
+
if (this.cfg.entityGraphEnabled && opts.entityRef && opts.entitySiblings?.length) {
|
|
170
|
+
const siblings = opts.entitySiblings.slice(0, this.cfg.maxEntityGraphEdgesPerMemory);
|
|
171
|
+
for (const sibling of siblings) {
|
|
172
|
+
await appendEdge(this.memoryDir, {
|
|
173
|
+
from: opts.memoryPath,
|
|
174
|
+
to: sibling,
|
|
175
|
+
type: "entity",
|
|
176
|
+
weight: 1,
|
|
177
|
+
label: opts.entityRef,
|
|
178
|
+
ts
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (this.cfg.timeGraphEnabled && opts.threadId && opts.recentInThread?.length) {
|
|
183
|
+
const predecessor = opts.recentInThread[opts.recentInThread.length - 1];
|
|
184
|
+
if (predecessor && predecessor !== opts.memoryPath) {
|
|
185
|
+
await appendEdge(this.memoryDir, {
|
|
186
|
+
from: predecessor,
|
|
187
|
+
to: opts.memoryPath,
|
|
188
|
+
type: "time",
|
|
189
|
+
weight: 1,
|
|
190
|
+
label: opts.threadId,
|
|
191
|
+
ts
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (this.cfg.causalGraphEnabled && opts.causalPredecessor) {
|
|
196
|
+
const phrase = detectCausalPhrase(opts.content);
|
|
197
|
+
if (phrase) {
|
|
198
|
+
await appendEdge(this.memoryDir, {
|
|
199
|
+
from: opts.causalPredecessor,
|
|
200
|
+
to: opts.memoryPath,
|
|
201
|
+
type: "causal",
|
|
202
|
+
weight: 1,
|
|
203
|
+
label: phrase,
|
|
204
|
+
ts
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
const { log } = await import("./logger-KPTKTANX.js");
|
|
210
|
+
log.warn(`[graph] onMemoryWritten error: ${err}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Spreading activation BFS (SYNAPSE-inspired).
|
|
215
|
+
*
|
|
216
|
+
* Starting from `seeds`, traverse the combined graph for up to `maxSteps` hops.
|
|
217
|
+
* Each candidate gets an activation score = edge.weight × decay^hop.
|
|
218
|
+
* Returns top-N candidate paths sorted by descending activation score.
|
|
219
|
+
*
|
|
220
|
+
* @param seeds - initial memory paths to expand from (e.g. QMD top results)
|
|
221
|
+
* @param maxSteps - max BFS hops (from config: maxGraphTraversalSteps)
|
|
222
|
+
* @returns Array of {path, score} sorted descending, not including seed paths
|
|
223
|
+
*/
|
|
224
|
+
async spreadingActivation(seeds, maxSteps) {
|
|
225
|
+
if (!this.cfg.multiGraphMemoryEnabled) return [];
|
|
226
|
+
const steps = maxSteps ?? this.cfg.maxGraphTraversalSteps;
|
|
227
|
+
const decay = this.cfg.graphActivationDecay;
|
|
228
|
+
try {
|
|
229
|
+
const allEdges = await readAllEdges(this.memoryDir, {
|
|
230
|
+
entityGraphEnabled: this.cfg.entityGraphEnabled,
|
|
231
|
+
timeGraphEnabled: this.cfg.timeGraphEnabled,
|
|
232
|
+
causalGraphEnabled: this.cfg.causalGraphEnabled
|
|
233
|
+
});
|
|
234
|
+
const adj = /* @__PURE__ */ new Map();
|
|
235
|
+
for (const edge of allEdges) {
|
|
236
|
+
if (!adj.has(edge.from)) adj.set(edge.from, []);
|
|
237
|
+
adj.get(edge.from).push(edge);
|
|
238
|
+
if (edge.type !== "causal") {
|
|
239
|
+
if (!adj.has(edge.to)) adj.set(edge.to, []);
|
|
240
|
+
adj.get(edge.to).push({ ...edge, from: edge.to, to: edge.from });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const seedSet = new Set(seeds);
|
|
244
|
+
const scores = /* @__PURE__ */ new Map();
|
|
245
|
+
const provenance = /* @__PURE__ */ new Map();
|
|
246
|
+
const visited = new Set(seeds);
|
|
247
|
+
const queue = seeds.map((s) => [s, 0, s]);
|
|
248
|
+
while (queue.length > 0) {
|
|
249
|
+
const [node, hop, sourceSeed] = queue.shift();
|
|
250
|
+
if (hop >= steps) continue;
|
|
251
|
+
const edges = adj.get(node) ?? [];
|
|
252
|
+
for (const edge of edges) {
|
|
253
|
+
const neighbor = edge.to === node ? edge.from : edge.to;
|
|
254
|
+
const score = edge.weight * Math.pow(decay, hop + 1);
|
|
255
|
+
if (!seedSet.has(neighbor)) {
|
|
256
|
+
const existing = scores.get(neighbor) ?? 0;
|
|
257
|
+
scores.set(neighbor, existing + score);
|
|
258
|
+
const prev = provenance.get(neighbor);
|
|
259
|
+
if (!prev || hop + 1 < prev.hopDepth || hop + 1 === prev.hopDepth && score > prev.decayedWeight) {
|
|
260
|
+
provenance.set(neighbor, {
|
|
261
|
+
seed: sourceSeed,
|
|
262
|
+
hopDepth: hop + 1,
|
|
263
|
+
decayedWeight: score,
|
|
264
|
+
graphType: edge.type
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!visited.has(neighbor)) {
|
|
269
|
+
visited.add(neighbor);
|
|
270
|
+
queue.push([neighbor, hop + 1, sourceSeed]);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (this.cfg.graphLateralInhibitionEnabled && scores.size > 1) {
|
|
275
|
+
const inhibited = applyLateralInhibition(scores, {
|
|
276
|
+
beta: this.cfg.graphLateralInhibitionBeta,
|
|
277
|
+
topM: this.cfg.graphLateralInhibitionTopM
|
|
278
|
+
});
|
|
279
|
+
for (const [k, v] of inhibited) {
|
|
280
|
+
scores.set(k, v);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return Array.from(scores.entries()).map(([p, score]) => ({
|
|
284
|
+
path: p,
|
|
285
|
+
score,
|
|
286
|
+
seed: provenance.get(p)?.seed ?? "",
|
|
287
|
+
hopDepth: provenance.get(p)?.hopDepth ?? 0,
|
|
288
|
+
decayedWeight: provenance.get(p)?.decayedWeight ?? 0,
|
|
289
|
+
graphType: provenance.get(p)?.graphType ?? "entity"
|
|
290
|
+
})).sort((a, b) => b.score - a.score);
|
|
291
|
+
} catch (err) {
|
|
292
|
+
const { log } = await import("./logger-KPTKTANX.js");
|
|
293
|
+
log.warn(`[graph] spreadingActivation error: ${err}`);
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
function applyLateralInhibition(scores, opts) {
|
|
299
|
+
const { beta, topM } = opts;
|
|
300
|
+
if (beta === 0 || topM === 0) return new Map(scores);
|
|
301
|
+
const sorted = Array.from(scores.entries()).sort((a, b) => b[1] - a[1]);
|
|
302
|
+
const topCompetitors = sorted.slice(0, topM);
|
|
303
|
+
const result = /* @__PURE__ */ new Map();
|
|
304
|
+
for (const [node, u] of scores) {
|
|
305
|
+
let inhibition = 0;
|
|
306
|
+
for (const [, uK] of topCompetitors) {
|
|
307
|
+
if (uK > u) {
|
|
308
|
+
inhibition += uK - u;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
result.set(node, Math.max(0, u - beta * inhibition));
|
|
312
|
+
}
|
|
313
|
+
return result;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export {
|
|
317
|
+
appendEdge,
|
|
318
|
+
analyzeGraphHealth,
|
|
319
|
+
GraphIndex
|
|
320
|
+
};
|
|
321
|
+
//# sourceMappingURL=chunk-BNBG2XP6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graph.ts"],"sourcesContent":["/**\n * Multi-Graph Memory (MAGMA/SYNAPSE-inspired, v8.2)\n *\n * Maintains three typed edge graphs:\n * entity.jsonl — memories sharing a named entity (entityRef)\n * time.jsonl — consecutive memories in the same thread/session\n * causal.jsonl — memories linked by causal language heuristics\n *\n * Stored under `<memoryDir>/state/graphs/`.\n * All writes are fail-open: errors are caught/logged, never thrown.\n */\n\nimport { mkdir, appendFile, readFile } from \"node:fs/promises\";\nimport * as path from \"path\";\n\nexport type GraphType = \"entity\" | \"time\" | \"causal\";\n\nexport interface GraphEdge {\n from: string; // relative memory path (e.g. \"facts/2026-02-22/abc.md\")\n to: string; // relative memory path\n type: GraphType;\n weight: number; // 1.0 default, decay applied during traversal\n label: string; // entity name, threadId, or matched causal phrase\n ts: string; // ISO timestamp of edge creation\n}\n\nexport interface GraphConfig {\n multiGraphMemoryEnabled: boolean;\n entityGraphEnabled: boolean;\n timeGraphEnabled: boolean;\n causalGraphEnabled: boolean;\n maxGraphTraversalSteps: number;\n graphActivationDecay: number;\n maxEntityGraphEdgesPerMemory: number;\n graphLateralInhibitionEnabled: boolean;\n graphLateralInhibitionBeta: number;\n graphLateralInhibitionTopM: number;\n}\n\n// Causal signal phrases — order matters (most specific first)\nexport const CAUSAL_PHRASES = [\n \"as a result\",\n \"led to\",\n \"because of\",\n \"therefore\",\n \"caused\",\n \"because\",\n];\n\nexport function graphsDir(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"graphs\");\n}\n\nexport function graphFilePath(memoryDir: string, type: GraphType): string {\n return path.join(graphsDir(memoryDir), `${type}.jsonl`);\n}\n\nexport async function ensureGraphsDir(memoryDir: string): Promise<void> {\n await mkdir(graphsDir(memoryDir), { recursive: true });\n}\n\nexport async function appendEdge(memoryDir: string, edge: GraphEdge): Promise<void> {\n await ensureGraphsDir(memoryDir);\n const line = JSON.stringify(edge) + \"\\n\";\n await appendFile(graphFilePath(memoryDir, edge.type), line, \"utf8\");\n}\n\n/**\n * Read all edges of a given type from the JSONL file.\n * Returns [] if the file doesn't exist or is corrupt (fail-open).\n */\nexport async function readEdges(memoryDir: string, type: GraphType): Promise<GraphEdge[]> {\n const filePath = graphFilePath(memoryDir, type);\n try {\n const raw = await readFile(filePath, \"utf8\");\n const edges: GraphEdge[] = [];\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n edges.push(JSON.parse(trimmed) as GraphEdge);\n } catch {\n // skip corrupt lines\n }\n }\n return edges;\n } catch {\n return [];\n }\n}\n\n/**\n * Read edges from all enabled graph types.\n */\nexport async function readAllEdges(\n memoryDir: string,\n config: Pick<GraphConfig, \"entityGraphEnabled\" | \"timeGraphEnabled\" | \"causalGraphEnabled\">,\n): Promise<GraphEdge[]> {\n const parts: GraphEdge[][] = await Promise.all([\n config.entityGraphEnabled ? readEdges(memoryDir, \"entity\") : Promise.resolve([]),\n config.timeGraphEnabled ? readEdges(memoryDir, \"time\") : Promise.resolve([]),\n config.causalGraphEnabled ? readEdges(memoryDir, \"causal\") : Promise.resolve([]),\n ]);\n return parts.flat();\n}\n\nexport interface GraphHealthFileStats {\n type: GraphType;\n filePath: string;\n exists: boolean;\n totalLines: number;\n validEdges: number;\n corruptLines: number;\n uniqueNodes: number;\n}\n\nexport interface GraphHealthReport {\n generatedAt: string;\n enabledTypes: GraphType[];\n totals: {\n totalLines: number;\n validEdges: number;\n corruptLines: number;\n uniqueNodes: number;\n };\n files: GraphHealthFileStats[];\n repairGuidance?: string[];\n}\n\nfunction isValidGraphEdge(raw: unknown, expectedType: GraphType): raw is GraphEdge {\n if (!raw || typeof raw !== \"object\") return false;\n const edge = raw as Record<string, unknown>;\n return (\n edge.type === expectedType &&\n typeof edge.from === \"string\" && edge.from.length > 0 &&\n typeof edge.to === \"string\" && edge.to.length > 0 &&\n typeof edge.weight === \"number\" && Number.isFinite(edge.weight) &&\n typeof edge.label === \"string\" &&\n typeof edge.ts === \"string\"\n );\n}\n\nexport async function analyzeGraphHealth(\n memoryDir: string,\n options?: {\n entityGraphEnabled?: boolean;\n timeGraphEnabled?: boolean;\n causalGraphEnabled?: boolean;\n includeRepairGuidance?: boolean;\n },\n): Promise<GraphHealthReport> {\n const enabledTypes: GraphType[] = [];\n if (options?.entityGraphEnabled !== false) enabledTypes.push(\"entity\");\n if (options?.timeGraphEnabled !== false) enabledTypes.push(\"time\");\n if (options?.causalGraphEnabled !== false) enabledTypes.push(\"causal\");\n\n const files: GraphHealthFileStats[] = [];\n const globalNodes = new Set<string>();\n\n for (const type of enabledTypes) {\n const filePath = graphFilePath(memoryDir, type);\n let exists = true;\n let totalLines = 0;\n let validEdges = 0;\n let corruptLines = 0;\n const nodes = new Set<string>();\n\n try {\n const raw = await readFile(filePath, \"utf8\");\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n totalLines += 1;\n try {\n const parsed = JSON.parse(trimmed) as unknown;\n if (!isValidGraphEdge(parsed, type)) {\n corruptLines += 1;\n continue;\n }\n validEdges += 1;\n nodes.add(parsed.from);\n nodes.add(parsed.to);\n globalNodes.add(parsed.from);\n globalNodes.add(parsed.to);\n } catch {\n corruptLines += 1;\n }\n }\n } catch {\n exists = false;\n }\n\n files.push({\n type,\n filePath,\n exists,\n totalLines,\n validEdges,\n corruptLines,\n uniqueNodes: nodes.size,\n });\n }\n\n const totals = files.reduce(\n (acc, item) => {\n acc.totalLines += item.totalLines;\n acc.validEdges += item.validEdges;\n acc.corruptLines += item.corruptLines;\n return acc;\n },\n {\n totalLines: 0,\n validEdges: 0,\n corruptLines: 0,\n uniqueNodes: globalNodes.size,\n },\n );\n totals.uniqueNodes = globalNodes.size;\n\n const report: GraphHealthReport = {\n generatedAt: new Date().toISOString(),\n enabledTypes,\n totals,\n files,\n };\n\n if (options?.includeRepairGuidance === true) {\n const guidance: string[] = [];\n if (totals.corruptLines > 0) {\n guidance.push(\"Corrupt graph lines detected: back up memory/state/graphs, then rebuild graphs from clean memory replay/extraction runs.\");\n }\n if (totals.validEdges === 0) {\n guidance.push(\"No valid edges detected yet: run normal extraction traffic (or replay ingestion) to seed graph files.\");\n }\n if (guidance.length > 0) report.repairGuidance = guidance;\n }\n\n return report;\n}\n\n/**\n * Detect causal signal phrases in text. Returns the first matched phrase, or null.\n */\nexport function detectCausalPhrase(text: string): string | null {\n const lower = text.toLowerCase();\n for (const phrase of CAUSAL_PHRASES) {\n if (lower.includes(phrase)) return phrase;\n }\n return null;\n}\n\n/**\n * GraphIndex — builds and updates the three memory graphs.\n *\n * Usage (orchestrator):\n * this.graphIndex = new GraphIndex(config.memoryDir, config);\n *\n * // After each memory write:\n * await this.graphIndex.onMemoryWritten(memoryPath, frontmatter, threadId, recentInThread);\n */\nexport class GraphIndex {\n private readonly memoryDir: string;\n private readonly cfg: GraphConfig;\n\n constructor(memoryDir: string, cfg: GraphConfig) {\n this.memoryDir = memoryDir;\n this.cfg = cfg;\n }\n\n /**\n * Called after a memory is written to disk.\n *\n * @param memoryPath - relative path from memoryDir (e.g. \"facts/2026-02-22/abc.md\")\n * @param entityRef - entityRef frontmatter field (if any)\n * @param content - full memory text (for causal detection)\n * @param created - ISO timestamp of this memory\n * @param threadId - current thread ID (for time graph)\n * @param recentInThread - paths of the N most-recent memories in this thread (for time graph)\n * @param entitySiblings - paths of other memories that share the same entityRef (for entity graph)\n */\n async onMemoryWritten(opts: {\n memoryPath: string;\n entityRef?: string;\n content: string;\n created: string;\n threadId?: string;\n recentInThread?: string[];\n entitySiblings?: string[];\n causalPredecessor?: string;\n }): Promise<void> {\n if (!this.cfg.multiGraphMemoryEnabled) return;\n const ts = new Date().toISOString();\n\n try {\n // Entity graph\n if (this.cfg.entityGraphEnabled && opts.entityRef && opts.entitySiblings?.length) {\n const siblings = opts.entitySiblings.slice(0, this.cfg.maxEntityGraphEdgesPerMemory);\n for (const sibling of siblings) {\n await appendEdge(this.memoryDir, {\n from: opts.memoryPath,\n to: sibling,\n type: \"entity\",\n weight: 1.0,\n label: opts.entityRef,\n ts,\n });\n }\n }\n\n // Time graph — link to most recent memory in same thread\n if (this.cfg.timeGraphEnabled && opts.threadId && opts.recentInThread?.length) {\n const predecessor = opts.recentInThread[opts.recentInThread.length - 1];\n if (predecessor && predecessor !== opts.memoryPath) {\n await appendEdge(this.memoryDir, {\n from: predecessor,\n to: opts.memoryPath,\n type: \"time\",\n weight: 1.0,\n label: opts.threadId,\n ts,\n });\n }\n }\n\n // Causal graph\n if (this.cfg.causalGraphEnabled && opts.causalPredecessor) {\n const phrase = detectCausalPhrase(opts.content);\n if (phrase) {\n await appendEdge(this.memoryDir, {\n from: opts.causalPredecessor,\n to: opts.memoryPath,\n type: \"causal\",\n weight: 1.0,\n label: phrase,\n ts,\n });\n }\n }\n } catch (err) {\n // Fail-open: graph write errors must never surface to caller\n const { log } = await import(\"./logger.js\");\n log.warn(`[graph] onMemoryWritten error: ${err}`);\n }\n }\n\n /**\n * Spreading activation BFS (SYNAPSE-inspired).\n *\n * Starting from `seeds`, traverse the combined graph for up to `maxSteps` hops.\n * Each candidate gets an activation score = edge.weight × decay^hop.\n * Returns top-N candidate paths sorted by descending activation score.\n *\n * @param seeds - initial memory paths to expand from (e.g. QMD top results)\n * @param maxSteps - max BFS hops (from config: maxGraphTraversalSteps)\n * @returns Array of {path, score} sorted descending, not including seed paths\n */\n async spreadingActivation(\n seeds: string[],\n maxSteps?: number,\n ): Promise<Array<{\n path: string;\n score: number;\n seed: string;\n hopDepth: number;\n decayedWeight: number;\n graphType: \"entity\" | \"time\" | \"causal\";\n }>> {\n if (!this.cfg.multiGraphMemoryEnabled) return [];\n const steps = maxSteps ?? this.cfg.maxGraphTraversalSteps;\n const decay = this.cfg.graphActivationDecay;\n\n try {\n const allEdges = await readAllEdges(this.memoryDir, {\n entityGraphEnabled: this.cfg.entityGraphEnabled,\n timeGraphEnabled: this.cfg.timeGraphEnabled,\n causalGraphEnabled: this.cfg.causalGraphEnabled,\n });\n\n // Build adjacency index: from → edges, to → edges (bidirectional for entity/time, directional for causal)\n const adj = new Map<string, GraphEdge[]>();\n for (const edge of allEdges) {\n if (!adj.has(edge.from)) adj.set(edge.from, []);\n adj.get(edge.from)!.push(edge);\n // Entity and time edges are bidirectional\n if (edge.type !== \"causal\") {\n if (!adj.has(edge.to)) adj.set(edge.to, []);\n adj.get(edge.to)!.push({ ...edge, from: edge.to, to: edge.from });\n }\n }\n\n const seedSet = new Set(seeds);\n const scores = new Map<string, number>(); // candidate path → accumulated activation score\n const provenance = new Map<\n string,\n { seed: string; hopDepth: number; decayedWeight: number; graphType: \"entity\" | \"time\" | \"causal\" }\n >();\n const visited = new Set<string>(seeds);\n\n // BFS queue: [nodePath, hop, seedPath]\n const queue: Array<[string, number, string]> = seeds.map((s) => [s, 0, s]);\n\n while (queue.length > 0) {\n const [node, hop, sourceSeed] = queue.shift()!;\n if (hop >= steps) continue;\n\n const edges = adj.get(node) ?? [];\n for (const edge of edges) {\n const neighbor = edge.to === node ? edge.from : edge.to;\n const score = edge.weight * Math.pow(decay, hop + 1);\n\n if (!seedSet.has(neighbor)) {\n const existing = scores.get(neighbor) ?? 0;\n scores.set(neighbor, existing + score);\n\n const prev = provenance.get(neighbor);\n if (\n !prev ||\n hop + 1 < prev.hopDepth ||\n (hop + 1 === prev.hopDepth && score > prev.decayedWeight)\n ) {\n provenance.set(neighbor, {\n seed: sourceSeed,\n hopDepth: hop + 1,\n decayedWeight: score,\n graphType: edge.type,\n });\n }\n }\n\n if (!visited.has(neighbor)) {\n visited.add(neighbor);\n queue.push([neighbor, hop + 1, sourceSeed]);\n }\n }\n }\n\n // Apply lateral inhibition if enabled (Synapse-inspired competitive suppression)\n if (this.cfg.graphLateralInhibitionEnabled && scores.size > 1) {\n const inhibited = applyLateralInhibition(scores, {\n beta: this.cfg.graphLateralInhibitionBeta,\n topM: this.cfg.graphLateralInhibitionTopM,\n });\n for (const [k, v] of inhibited) {\n scores.set(k, v);\n }\n }\n\n return Array.from(scores.entries())\n .map(([p, score]) => ({\n path: p,\n score,\n seed: provenance.get(p)?.seed ?? \"\",\n hopDepth: provenance.get(p)?.hopDepth ?? 0,\n decayedWeight: provenance.get(p)?.decayedWeight ?? 0,\n graphType: provenance.get(p)?.graphType ?? \"entity\",\n }))\n .sort((a, b) => b.score - a.score);\n } catch (err) {\n const { log } = await import(\"./logger.js\");\n log.warn(`[graph] spreadingActivation error: ${err}`);\n return [];\n }\n }\n}\n\n/**\n * Lateral inhibition (Synapse-inspired).\n *\n * For each node, the top-M higher-activation competitors exert inhibition\n * proportional to their activation difference. Output is clamped to [0, ∞).\n *\n * No sigmoid is applied here — downstream `normalizeGraphActivationScore`\n * already applies x/(1+x) soft squash, so adding a sigmoid would double-\n * normalize and cap graph influence at ~50%.\n *\n * Formula: u_hat_i = max(0, u_i - beta * sum_{k in top-M where u_k > u_i}(u_k - u_i))\n *\n * When beta=0 or topM=0, returns original scores unchanged (no-op).\n */\nexport function applyLateralInhibition(\n scores: Map<string, number>,\n opts: { beta: number; topM: number },\n): Map<string, number> {\n const { beta, topM } = opts;\n if (beta === 0 || topM === 0) return new Map(scores);\n\n const sorted = Array.from(scores.entries()).sort((a, b) => b[1] - a[1]);\n const topCompetitors = sorted.slice(0, topM);\n\n const result = new Map<string, number>();\n for (const [node, u] of scores) {\n let inhibition = 0;\n for (const [, uK] of topCompetitors) {\n if (uK > u) {\n inhibition += uK - u;\n }\n }\n result.set(node, Math.max(0, u - beta * inhibition));\n }\n\n return result;\n}\n"],"mappings":";;;AAYA,SAAS,OAAO,YAAY,gBAAgB;AAC5C,YAAY,UAAU;AA2Bf,IAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,UAAU,WAA2B;AACnD,SAAY,UAAK,WAAW,SAAS,QAAQ;AAC/C;AAEO,SAAS,cAAc,WAAmB,MAAyB;AACxE,SAAY,UAAK,UAAU,SAAS,GAAG,GAAG,IAAI,QAAQ;AACxD;AAEA,eAAsB,gBAAgB,WAAkC;AACtE,QAAM,MAAM,UAAU,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD;AAEA,eAAsB,WAAW,WAAmB,MAAgC;AAClF,QAAM,gBAAgB,SAAS;AAC/B,QAAM,OAAO,KAAK,UAAU,IAAI,IAAI;AACpC,QAAM,WAAW,cAAc,WAAW,KAAK,IAAI,GAAG,MAAM,MAAM;AACpE;AAMA,eAAsB,UAAU,WAAmB,MAAuC;AACxF,QAAM,WAAW,cAAc,WAAW,IAAI;AAC9C,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,UAAU,MAAM;AAC3C,UAAM,QAAqB,CAAC;AAC5B,eAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAS;AACd,UAAI;AACF,cAAM,KAAK,KAAK,MAAM,OAAO,CAAc;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAKA,eAAsB,aACpB,WACA,QACsB;AACtB,QAAM,QAAuB,MAAM,QAAQ,IAAI;AAAA,IAC7C,OAAO,qBAAqB,UAAU,WAAW,QAAQ,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,IAC/E,OAAO,mBAAmB,UAAU,WAAW,MAAM,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,IAC3E,OAAO,qBAAqB,UAAU,WAAW,QAAQ,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EACjF,CAAC;AACD,SAAO,MAAM,KAAK;AACpB;AAyBA,SAAS,iBAAiB,KAAc,cAA2C;AACjF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,OAAO;AACb,SACE,KAAK,SAAS,gBACd,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,KACpD,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,KAChD,OAAO,KAAK,WAAW,YAAY,OAAO,SAAS,KAAK,MAAM,KAC9D,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,OAAO;AAEvB;AAEA,eAAsB,mBACpB,WACA,SAM4B;AAC5B,QAAM,eAA4B,CAAC;AACnC,MAAI,SAAS,uBAAuB,MAAO,cAAa,KAAK,QAAQ;AACrE,MAAI,SAAS,qBAAqB,MAAO,cAAa,KAAK,MAAM;AACjE,MAAI,SAAS,uBAAuB,MAAO,cAAa,KAAK,QAAQ;AAErE,QAAM,QAAgC,CAAC;AACvC,QAAM,cAAc,oBAAI,IAAY;AAEpC,aAAW,QAAQ,cAAc;AAC/B,UAAM,WAAW,cAAc,WAAW,IAAI;AAC9C,QAAI,SAAS;AACb,QAAI,aAAa;AACjB,QAAI,aAAa;AACjB,QAAI,eAAe;AACnB,UAAM,QAAQ,oBAAI,IAAY;AAE9B,QAAI;AACF,YAAM,MAAM,MAAM,SAAS,UAAU,MAAM;AAC3C,iBAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,cAAM,UAAU,KAAK,KAAK;AAC1B,YAAI,CAAC,QAAS;AACd,sBAAc;AACd,YAAI;AACF,gBAAM,SAAS,KAAK,MAAM,OAAO;AACjC,cAAI,CAAC,iBAAiB,QAAQ,IAAI,GAAG;AACnC,4BAAgB;AAChB;AAAA,UACF;AACA,wBAAc;AACd,gBAAM,IAAI,OAAO,IAAI;AACrB,gBAAM,IAAI,OAAO,EAAE;AACnB,sBAAY,IAAI,OAAO,IAAI;AAC3B,sBAAY,IAAI,OAAO,EAAE;AAAA,QAC3B,QAAQ;AACN,0BAAgB;AAAA,QAClB;AAAA,MACF;AAAA,IACF,QAAQ;AACN,eAAS;AAAA,IACX;AAEA,UAAM,KAAK;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,MAAM;AAAA,IACrB,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB,CAAC,KAAK,SAAS;AACb,UAAI,cAAc,KAAK;AACvB,UAAI,cAAc,KAAK;AACvB,UAAI,gBAAgB,KAAK;AACzB,aAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,aAAa,YAAY;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,cAAc,YAAY;AAEjC,QAAM,SAA4B;AAAA,IAChC,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,SAAS,0BAA0B,MAAM;AAC3C,UAAM,WAAqB,CAAC;AAC5B,QAAI,OAAO,eAAe,GAAG;AAC3B,eAAS,KAAK,0HAA0H;AAAA,IAC1I;AACA,QAAI,OAAO,eAAe,GAAG;AAC3B,eAAS,KAAK,uGAAuG;AAAA,IACvH;AACA,QAAI,SAAS,SAAS,EAAG,QAAO,iBAAiB;AAAA,EACnD;AAEA,SAAO;AACT;AAKO,SAAS,mBAAmB,MAA6B;AAC9D,QAAM,QAAQ,KAAK,YAAY;AAC/B,aAAW,UAAU,gBAAgB;AACnC,QAAI,MAAM,SAAS,MAAM,EAAG,QAAO;AAAA,EACrC;AACA,SAAO;AACT;AAWO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EACA;AAAA,EAEjB,YAAY,WAAmB,KAAkB;AAC/C,SAAK,YAAY;AACjB,SAAK,MAAM;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,gBAAgB,MASJ;AAChB,QAAI,CAAC,KAAK,IAAI,wBAAyB;AACvC,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAElC,QAAI;AAEF,UAAI,KAAK,IAAI,sBAAsB,KAAK,aAAa,KAAK,gBAAgB,QAAQ;AAChF,cAAM,WAAW,KAAK,eAAe,MAAM,GAAG,KAAK,IAAI,4BAA4B;AACnF,mBAAW,WAAW,UAAU;AAC9B,gBAAM,WAAW,KAAK,WAAW;AAAA,YAC/B,MAAM,KAAK;AAAA,YACX,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO,KAAK;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAGA,UAAI,KAAK,IAAI,oBAAoB,KAAK,YAAY,KAAK,gBAAgB,QAAQ;AAC7E,cAAM,cAAc,KAAK,eAAe,KAAK,eAAe,SAAS,CAAC;AACtE,YAAI,eAAe,gBAAgB,KAAK,YAAY;AAClD,gBAAM,WAAW,KAAK,WAAW;AAAA,YAC/B,MAAM;AAAA,YACN,IAAI,KAAK;AAAA,YACT,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO,KAAK;AAAA,YACZ;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAGA,UAAI,KAAK,IAAI,sBAAsB,KAAK,mBAAmB;AACzD,cAAM,SAAS,mBAAmB,KAAK,OAAO;AAC9C,YAAI,QAAQ;AACV,gBAAM,WAAW,KAAK,WAAW;AAAA,YAC/B,MAAM,KAAK;AAAA,YACX,IAAI,KAAK;AAAA,YACT,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,OAAO;AAAA,YACP;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AAEZ,YAAM,EAAE,IAAI,IAAI,MAAM,OAAO,sBAAa;AAC1C,UAAI,KAAK,kCAAkC,GAAG,EAAE;AAAA,IAClD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,oBACJ,OACA,UAQE;AACF,QAAI,CAAC,KAAK,IAAI,wBAAyB,QAAO,CAAC;AAC/C,UAAM,QAAQ,YAAY,KAAK,IAAI;AACnC,UAAM,QAAQ,KAAK,IAAI;AAEvB,QAAI;AACF,YAAM,WAAW,MAAM,aAAa,KAAK,WAAW;AAAA,QAClD,oBAAoB,KAAK,IAAI;AAAA,QAC7B,kBAAkB,KAAK,IAAI;AAAA,QAC3B,oBAAoB,KAAK,IAAI;AAAA,MAC/B,CAAC;AAGD,YAAM,MAAM,oBAAI,IAAyB;AACzC,iBAAW,QAAQ,UAAU;AAC3B,YAAI,CAAC,IAAI,IAAI,KAAK,IAAI,EAAG,KAAI,IAAI,KAAK,MAAM,CAAC,CAAC;AAC9C,YAAI,IAAI,KAAK,IAAI,EAAG,KAAK,IAAI;AAE7B,YAAI,KAAK,SAAS,UAAU;AAC1B,cAAI,CAAC,IAAI,IAAI,KAAK,EAAE,EAAG,KAAI,IAAI,KAAK,IAAI,CAAC,CAAC;AAC1C,cAAI,IAAI,KAAK,EAAE,EAAG,KAAK,EAAE,GAAG,MAAM,MAAM,KAAK,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,QAClE;AAAA,MACF;AAEA,YAAM,UAAU,IAAI,IAAI,KAAK;AAC7B,YAAM,SAAS,oBAAI,IAAoB;AACvC,YAAM,aAAa,oBAAI,IAGrB;AACF,YAAM,UAAU,IAAI,IAAY,KAAK;AAGrC,YAAM,QAAyC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;AAEzE,aAAO,MAAM,SAAS,GAAG;AACvB,cAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,MAAM;AAC5C,YAAI,OAAO,MAAO;AAElB,cAAM,QAAQ,IAAI,IAAI,IAAI,KAAK,CAAC;AAChC,mBAAW,QAAQ,OAAO;AACxB,gBAAM,WAAW,KAAK,OAAO,OAAO,KAAK,OAAO,KAAK;AACrD,gBAAM,QAAQ,KAAK,SAAS,KAAK,IAAI,OAAO,MAAM,CAAC;AAEnD,cAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,kBAAM,WAAW,OAAO,IAAI,QAAQ,KAAK;AACzC,mBAAO,IAAI,UAAU,WAAW,KAAK;AAErC,kBAAM,OAAO,WAAW,IAAI,QAAQ;AACpC,gBACE,CAAC,QACD,MAAM,IAAI,KAAK,YACd,MAAM,MAAM,KAAK,YAAY,QAAQ,KAAK,eAC3C;AACA,yBAAW,IAAI,UAAU;AAAA,gBACvB,MAAM;AAAA,gBACN,UAAU,MAAM;AAAA,gBAChB,eAAe;AAAA,gBACf,WAAW,KAAK;AAAA,cAClB,CAAC;AAAA,YACH;AAAA,UACF;AAEA,cAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,oBAAQ,IAAI,QAAQ;AACpB,kBAAM,KAAK,CAAC,UAAU,MAAM,GAAG,UAAU,CAAC;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAGA,UAAI,KAAK,IAAI,iCAAiC,OAAO,OAAO,GAAG;AAC7D,cAAM,YAAY,uBAAuB,QAAQ;AAAA,UAC/C,MAAM,KAAK,IAAI;AAAA,UACf,MAAM,KAAK,IAAI;AAAA,QACjB,CAAC;AACD,mBAAW,CAAC,GAAG,CAAC,KAAK,WAAW;AAC9B,iBAAO,IAAI,GAAG,CAAC;AAAA,QACjB;AAAA,MACF;AAEA,aAAO,MAAM,KAAK,OAAO,QAAQ,CAAC,EAC/B,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO;AAAA,QACpB,MAAM;AAAA,QACN;AAAA,QACA,MAAM,WAAW,IAAI,CAAC,GAAG,QAAQ;AAAA,QACjC,UAAU,WAAW,IAAI,CAAC,GAAG,YAAY;AAAA,QACzC,eAAe,WAAW,IAAI,CAAC,GAAG,iBAAiB;AAAA,QACnD,WAAW,WAAW,IAAI,CAAC,GAAG,aAAa;AAAA,MAC7C,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,IACrC,SAAS,KAAK;AACZ,YAAM,EAAE,IAAI,IAAI,MAAM,OAAO,sBAAa;AAC1C,UAAI,KAAK,sCAAsC,GAAG,EAAE;AACpD,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAgBO,SAAS,uBACd,QACA,MACqB;AACrB,QAAM,EAAE,MAAM,KAAK,IAAI;AACvB,MAAI,SAAS,KAAK,SAAS,EAAG,QAAO,IAAI,IAAI,MAAM;AAEnD,QAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACtE,QAAM,iBAAiB,OAAO,MAAM,GAAG,IAAI;AAE3C,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,CAAC,MAAM,CAAC,KAAK,QAAQ;AAC9B,QAAI,aAAa;AACjB,eAAW,CAAC,EAAE,EAAE,KAAK,gBAAgB;AACnC,UAAI,KAAK,GAAG;AACV,sBAAc,KAAK;AAAA,MACrB;AAAA,IACF;AACA,WAAO,IAAI,MAAM,KAAK,IAAI,GAAG,IAAI,OAAO,UAAU,CAAC;AAAA,EACrD;AAEA,SAAO;AACT;","names":[]}
|