@remnic/plugin-openclaw 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,278 @@
1
+ import {
2
+ assertIsoRecordedAt,
3
+ assertString,
4
+ countRecallTokenOverlap,
5
+ isRecord,
6
+ normalizeRecallTokens,
7
+ recordStoreDay,
8
+ topicOverlapScore
9
+ } from "./chunk-JGEKL3WH.js";
10
+ import {
11
+ listJsonFiles,
12
+ readJsonFile
13
+ } from "./chunk-5LE4HTVL.js";
14
+ import {
15
+ log
16
+ } from "./chunk-DMGIUDBO.js";
17
+
18
+ // ../remnic-core/src/causal-chain.ts
19
+ import path from "path";
20
+ import { mkdir, readFile, writeFile } from "fs/promises";
21
+ import { createHash } from "crypto";
22
+ var STITCH_WEIGHTS = {
23
+ followUpToGoal: 4,
24
+ outcomeToGoal: 2,
25
+ entityOverlap: 3,
26
+ tagOverlap: 1.5,
27
+ temporalProximity: 1
28
+ };
29
+ function validateCausalEdge(raw) {
30
+ if (!isRecord(raw)) throw new Error("CausalEdge must be an object");
31
+ if (raw.schemaVersion !== 1) throw new Error("CausalEdge.schemaVersion must be 1");
32
+ const edgeType = assertString(raw.edgeType, "edgeType");
33
+ const validEdgeTypes = ["follow_up_to_goal", "retry", "continuation", "correction"];
34
+ if (!validEdgeTypes.includes(edgeType)) {
35
+ throw new Error(`CausalEdge.edgeType must be one of ${validEdgeTypes.join(", ")}`);
36
+ }
37
+ const stitchMethod = assertString(raw.stitchMethod, "stitchMethod");
38
+ const validMethods = ["lexical", "entity", "temporal", "explicit"];
39
+ if (!validMethods.includes(stitchMethod)) {
40
+ throw new Error(`CausalEdge.stitchMethod must be one of ${validMethods.join(", ")}`);
41
+ }
42
+ const confidence = typeof raw.confidence === "number" ? raw.confidence : 0;
43
+ if (confidence < 0 || confidence > 1) throw new Error("CausalEdge.confidence must be in [0, 1]");
44
+ return {
45
+ schemaVersion: 1,
46
+ edgeId: assertString(raw.edgeId, "edgeId"),
47
+ fromTrajectoryId: assertString(raw.fromTrajectoryId, "fromTrajectoryId"),
48
+ toTrajectoryId: assertString(raw.toTrajectoryId, "toTrajectoryId"),
49
+ edgeType,
50
+ confidence,
51
+ stitchMethod,
52
+ createdAt: assertIsoRecordedAt(assertString(raw.createdAt, "createdAt")),
53
+ metadata: isRecord(raw.metadata) ? Object.fromEntries(
54
+ Object.entries(raw.metadata).filter(
55
+ (entry) => typeof entry[1] === "string"
56
+ )
57
+ ) : void 0
58
+ };
59
+ }
60
+ function makeEdgeId(fromId, toId) {
61
+ const digest = createHash("sha256").update(`${fromId}\0${toId}`).digest("hex").slice(0, 12);
62
+ return `edge-${digest}`;
63
+ }
64
+ function resolveChainsDir(memoryDir, causalTrajectoryStoreDir) {
65
+ const root = causalTrajectoryStoreDir ? path.join(memoryDir, causalTrajectoryStoreDir) : path.join(memoryDir, "state", "causal-trajectories");
66
+ return path.join(root, "chains");
67
+ }
68
+ function chainIndexPath(chainsDir) {
69
+ return path.join(chainsDir, "chain-index.json");
70
+ }
71
+ function edgeFilePath(chainsDir, edge) {
72
+ const day = recordStoreDay(edge.createdAt);
73
+ return path.join(chainsDir, "edges", day, `${edge.edgeId}.json`);
74
+ }
75
+ async function readChainIndex(chainsDir) {
76
+ try {
77
+ const raw = JSON.parse(await readFile(chainIndexPath(chainsDir), "utf8"));
78
+ return {
79
+ outgoing: isRecord(raw.outgoing) ? raw.outgoing : {},
80
+ incoming: isRecord(raw.incoming) ? raw.incoming : {},
81
+ edges: isRecord(raw.edges) ? raw.edges : {},
82
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
83
+ };
84
+ } catch {
85
+ return { outgoing: {}, incoming: {}, edges: {}, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
86
+ }
87
+ }
88
+ async function writeChainIndex(chainsDir, index) {
89
+ await mkdir(chainsDir, { recursive: true });
90
+ index.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
91
+ await writeFile(chainIndexPath(chainsDir), JSON.stringify(index, null, 2), "utf8");
92
+ }
93
+ async function persistEdge(chainsDir, edge) {
94
+ const filePath = edgeFilePath(chainsDir, edge);
95
+ await mkdir(path.dirname(filePath), { recursive: true });
96
+ await writeFile(filePath, JSON.stringify(edge, null, 2), "utf8");
97
+ return filePath;
98
+ }
99
+ function addEdgeToIndex(index, edge) {
100
+ if (!index.outgoing[edge.fromTrajectoryId]) {
101
+ index.outgoing[edge.fromTrajectoryId] = [];
102
+ }
103
+ if (!index.outgoing[edge.fromTrajectoryId].includes(edge.edgeId)) {
104
+ index.outgoing[edge.fromTrajectoryId].push(edge.edgeId);
105
+ }
106
+ if (!index.incoming[edge.toTrajectoryId]) {
107
+ index.incoming[edge.toTrajectoryId] = [];
108
+ }
109
+ if (!index.incoming[edge.toTrajectoryId].includes(edge.edgeId)) {
110
+ index.incoming[edge.toTrajectoryId].push(edge.edgeId);
111
+ }
112
+ index.edges[edge.edgeId] = edge;
113
+ }
114
+ function scoreStitchCandidate(newTrajectory, candidate) {
115
+ let score = 0;
116
+ let dominantMethod = "lexical";
117
+ let maxComponent = 0;
118
+ const newFollowUpTokens = new Set(
119
+ normalizeRecallTokens(newTrajectory.followUpSummary ?? "", [])
120
+ );
121
+ const candidateGoalTokens = normalizeRecallTokens(candidate.goal, []);
122
+ if (newFollowUpTokens.size > 0 && candidateGoalTokens.length > 0) {
123
+ const overlap = countRecallTokenOverlap(newFollowUpTokens, candidate.goal, []);
124
+ const normalized = overlap / Math.max(newFollowUpTokens.size, candidateGoalTokens.length);
125
+ const component = normalized * STITCH_WEIGHTS.followUpToGoal;
126
+ score += component;
127
+ if (component > maxComponent) {
128
+ maxComponent = component;
129
+ dominantMethod = "lexical";
130
+ }
131
+ }
132
+ const candidateFollowUpTokens = new Set(
133
+ normalizeRecallTokens(candidate.followUpSummary ?? "", [])
134
+ );
135
+ const newGoalTokens = normalizeRecallTokens(newTrajectory.goal, []);
136
+ if (candidateFollowUpTokens.size > 0 && newGoalTokens.length > 0) {
137
+ const overlap = countRecallTokenOverlap(candidateFollowUpTokens, newTrajectory.goal, []);
138
+ const normalized = overlap / Math.max(candidateFollowUpTokens.size, newGoalTokens.length);
139
+ const component = normalized * 3;
140
+ score += component;
141
+ if (component > maxComponent) {
142
+ maxComponent = component;
143
+ dominantMethod = "lexical";
144
+ }
145
+ }
146
+ const newOutcomeTokens = new Set(
147
+ normalizeRecallTokens(newTrajectory.outcomeSummary, [])
148
+ );
149
+ if (newOutcomeTokens.size > 0) {
150
+ const overlap = countRecallTokenOverlap(newOutcomeTokens, candidate.goal, []);
151
+ const normalized = overlap / Math.max(newOutcomeTokens.size, candidateGoalTokens.length || 1);
152
+ const component = normalized * STITCH_WEIGHTS.outcomeToGoal;
153
+ score += component;
154
+ if (component > maxComponent) {
155
+ maxComponent = component;
156
+ dominantMethod = "lexical";
157
+ }
158
+ }
159
+ const newEntities = newTrajectory.entityRefs ?? [];
160
+ const candidateEntities = candidate.entityRefs ?? [];
161
+ if (newEntities.length > 0 && candidateEntities.length > 0) {
162
+ const entityJaccard = topicOverlapScore(newEntities, candidateEntities);
163
+ const component = entityJaccard * STITCH_WEIGHTS.entityOverlap;
164
+ score += component;
165
+ if (component > maxComponent) {
166
+ maxComponent = component;
167
+ dominantMethod = "entity";
168
+ }
169
+ }
170
+ const newTags = newTrajectory.tags ?? [];
171
+ const candidateTags = candidate.tags ?? [];
172
+ if (newTags.length > 0 && candidateTags.length > 0) {
173
+ const tagJaccard = topicOverlapScore(newTags, candidateTags);
174
+ const component = tagJaccard * STITCH_WEIGHTS.tagOverlap;
175
+ score += component;
176
+ if (component > maxComponent) {
177
+ maxComponent = component;
178
+ dominantMethod = "lexical";
179
+ }
180
+ }
181
+ const newMs = Date.parse(newTrajectory.recordedAt);
182
+ const candidateMs = Date.parse(candidate.recordedAt);
183
+ if (Number.isFinite(newMs) && Number.isFinite(candidateMs)) {
184
+ const gapHours = Math.abs(newMs - candidateMs) / 36e5;
185
+ const proximity = 1 / (1 + gapHours / 24);
186
+ const component = proximity * STITCH_WEIGHTS.temporalProximity;
187
+ score += component;
188
+ if (component > maxComponent) {
189
+ maxComponent = component;
190
+ dominantMethod = "temporal";
191
+ }
192
+ }
193
+ let edgeType = "continuation";
194
+ const goalTokens = new Set(normalizeRecallTokens(newTrajectory.goal, []));
195
+ const candidateGoalSet = new Set(candidateGoalTokens);
196
+ const goalOverlap = [...goalTokens].filter((t) => candidateGoalSet.has(t)).length;
197
+ const goalSimilarity = goalTokens.size > 0 ? goalOverlap / goalTokens.size : 0;
198
+ if (goalSimilarity > 0.7 && candidate.outcomeKind === "failure") {
199
+ edgeType = "retry";
200
+ } else if (goalSimilarity > 0.7 && newTrajectory.outcomeKind !== candidate.outcomeKind) {
201
+ edgeType = "correction";
202
+ } else if (newFollowUpTokens.size > 0 && candidateGoalTokens.length > 0) {
203
+ const followUpGoalOverlap = countRecallTokenOverlap(newFollowUpTokens, candidate.goal, []);
204
+ if (followUpGoalOverlap > 0) {
205
+ edgeType = "follow_up_to_goal";
206
+ }
207
+ }
208
+ return { trajectory: candidate, score, edgeType, stitchMethod: dominantMethod };
209
+ }
210
+ async function readRecentTrajectories(memoryDir, causalTrajectoryStoreDir, currentSessionKey, lookbackDays) {
211
+ const root = causalTrajectoryStoreDir ? path.join(memoryDir, causalTrajectoryStoreDir) : path.join(memoryDir, "state", "causal-trajectories");
212
+ const trajectoriesDir = path.join(root, "trajectories");
213
+ const files = await listJsonFiles(trajectoriesDir).catch(() => []);
214
+ if (files.length === 0) return [];
215
+ const cutoff = Date.now() - lookbackDays * 864e5;
216
+ const results = [];
217
+ for (const filePath of files) {
218
+ try {
219
+ const raw = await readJsonFile(filePath);
220
+ if (!isRecord(raw)) continue;
221
+ const sessionKey = typeof raw.sessionKey === "string" ? raw.sessionKey : "";
222
+ if (sessionKey === currentSessionKey) continue;
223
+ const recordedAt = typeof raw.recordedAt === "string" ? raw.recordedAt : "";
224
+ const ms = Date.parse(recordedAt);
225
+ if (!Number.isFinite(ms) || ms < cutoff) continue;
226
+ results.push(raw);
227
+ } catch {
228
+ }
229
+ }
230
+ return results;
231
+ }
232
+ async function stitchCausalChain(options) {
233
+ const { memoryDir, causalTrajectoryStoreDir, newTrajectory, config: stitchConfig } = options;
234
+ const chainsDir = resolveChainsDir(memoryDir, causalTrajectoryStoreDir);
235
+ const candidates = await readRecentTrajectories(
236
+ memoryDir,
237
+ causalTrajectoryStoreDir,
238
+ newTrajectory.sessionKey,
239
+ stitchConfig.lookbackDays
240
+ );
241
+ if (candidates.length === 0) return [];
242
+ const scored = candidates.map((c) => scoreStitchCandidate(newTrajectory, c)).filter((s) => s.score >= stitchConfig.minScore).sort((a, b) => b.score - a.score).slice(0, stitchConfig.maxEdgesPerTrajectory);
243
+ if (scored.length === 0) return [];
244
+ const index = await readChainIndex(chainsDir);
245
+ const newEdges = [];
246
+ for (const candidate of scored) {
247
+ const edgeId = makeEdgeId(candidate.trajectory.trajectoryId, newTrajectory.trajectoryId);
248
+ if (index.edges[edgeId]) continue;
249
+ const edge = {
250
+ schemaVersion: 1,
251
+ edgeId,
252
+ fromTrajectoryId: candidate.trajectory.trajectoryId,
253
+ toTrajectoryId: newTrajectory.trajectoryId,
254
+ edgeType: candidate.edgeType,
255
+ confidence: Math.min(1, candidate.score / 10),
256
+ stitchMethod: candidate.stitchMethod,
257
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
258
+ };
259
+ addEdgeToIndex(index, edge);
260
+ await persistEdge(chainsDir, edge);
261
+ newEdges.push(edge);
262
+ }
263
+ if (newEdges.length > 0) {
264
+ await writeChainIndex(chainsDir, index);
265
+ log.debug(`[cmc] stitched ${newEdges.length} causal edge(s) for trajectory ${newTrajectory.trajectoryId}`);
266
+ }
267
+ return newEdges;
268
+ }
269
+
270
+ export {
271
+ validateCausalEdge,
272
+ makeEdgeId,
273
+ resolveChainsDir,
274
+ readChainIndex,
275
+ writeChainIndex,
276
+ scoreStitchCandidate,
277
+ stitchCausalChain
278
+ };