@memorycrystal/crystal-memory 0.7.4

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/handler.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * LEGACY HOOK HANDLER
3
+ * -------------------
4
+ * This file is the legacy entry point for older OpenClaw hook systems that
5
+ * used the `openclaw-hook.json` format (schemaVersion 1).
6
+ *
7
+ * It works by spawning child processes to run capture-hook.js and recall-hook.js.
8
+ *
9
+ * For modern OpenClaw plugin API (api.registerHook), use `index.js` instead.
10
+ * `index.js` is the canonical entry point referenced by `openclaw.plugin.json`.
11
+ *
12
+ * Do NOT delete this file — it may still be used by older OpenClaw installations
13
+ * or systems that invoke hooks directly via openclaw-hook.json.
14
+ */
15
+ const childProcess = require("node:child_process");
16
+ const fs = require("node:fs");
17
+ const path = require("node:path");
18
+
19
+ const PLUGIN_ROOT = path.resolve(__dirname);
20
+ const HANDLER_MANIFEST = path.join(PLUGIN_ROOT, "openclaw-hook.json");
21
+ const CAPTURE_HOOK = path.join(PLUGIN_ROOT, "capture-hook.js");
22
+ const RECALL_HOOK = path.join(PLUGIN_ROOT, "recall-hook.js");
23
+
24
+ const readJson = (filePath) => {
25
+ const data = fs.readFileSync(filePath, "utf8");
26
+ return JSON.parse(data);
27
+ };
28
+
29
+ const manifest = readJson(HANDLER_MANIFEST);
30
+
31
+ const status = () => ({
32
+ name: manifest.name,
33
+ version: manifest.version,
34
+ state: "ready",
35
+ timestamp: new Date().toISOString(),
36
+ });
37
+
38
+ const parseCaptureResult = (rawOutput) => {
39
+ if (!rawOutput) {
40
+ return { attempted: 0, saved: 0 };
41
+ }
42
+ try {
43
+ return JSON.parse(rawOutput);
44
+ } catch {
45
+ return { attempted: 0, saved: 0 };
46
+ }
47
+ };
48
+
49
+ const parseRecallResult = (rawOutput) => {
50
+ if (!rawOutput) {
51
+ return { injectionBlock: "", memories: [] };
52
+ }
53
+ try {
54
+ const parsed = JSON.parse(rawOutput);
55
+ if (!parsed || typeof parsed !== "object") {
56
+ return { injectionBlock: String(rawOutput), memories: [] };
57
+ }
58
+ if (typeof parsed.injectionBlock === "string") {
59
+ return {
60
+ injectionBlock: parsed.injectionBlock,
61
+ memories: Array.isArray(parsed.memories) ? parsed.memories : [],
62
+ };
63
+ }
64
+ } catch {
65
+ return { injectionBlock: String(rawOutput).trim(), memories: [] };
66
+ }
67
+ return { injectionBlock: "", memories: [] };
68
+ };
69
+
70
+ const captureHooks = new Set([
71
+ "message",
72
+ "turn",
73
+ "postTurn",
74
+ "post_turn",
75
+ "message_received",
76
+ "llm_output",
77
+ "llm-output",
78
+ "llmOutput",
79
+ ]);
80
+
81
+ const recallHooks = new Set(["pre-response", "before_model_resolve", "beforeModelResolve"]);
82
+
83
+ const firstString = (...values) => {
84
+ for (const value of values) {
85
+ if (typeof value === "string" && value.trim().length > 0) {
86
+ return value.trim();
87
+ }
88
+ }
89
+ return "";
90
+ };
91
+
92
+ const resolveChannelKey = (payload = {}) => {
93
+ const provider = firstString(
94
+ payload.messageProvider,
95
+ payload.provider,
96
+ payload.context?.provider,
97
+ "openclaw"
98
+ );
99
+ const workspaceId = firstString(payload.context?.workspaceId, payload.workspaceId);
100
+ const guildId = firstString(payload.context?.guildId, payload.guildId);
101
+ const channelId = firstString(
102
+ payload.context?.channelId,
103
+ payload.channelId,
104
+ payload.channel
105
+ );
106
+ const threadId = firstString(payload.context?.threadId, payload.threadId);
107
+ const parts = [provider, workspaceId, guildId, channelId, threadId].filter(Boolean);
108
+ return parts.length > 1 ? parts.join(":") : provider;
109
+ };
110
+
111
+ const runCaptureHook = (payload = {}) => {
112
+ const input = JSON.stringify({
113
+ userMessage: payload.userMessage ?? "",
114
+ agentResponse: payload.agentResponse ?? payload.response ?? "",
115
+ channel: resolveChannelKey(payload),
116
+ sessionId: payload.sessionId ?? "",
117
+ });
118
+ const result = childProcess.spawnSync(process.execPath, [CAPTURE_HOOK], {
119
+ encoding: "utf8",
120
+ input,
121
+ });
122
+ if (result.error || result.status !== 0) {
123
+ const stderr = result.stderr?.toString() || result.error?.message || "";
124
+ console.error(stderr);
125
+ return {
126
+ ok: false,
127
+ message: "Memory Crystal hook failed",
128
+ error: true,
129
+ stderr,
130
+ };
131
+ }
132
+ const capture = parseCaptureResult((result.stdout || "").trim());
133
+ return {
134
+ ok: true,
135
+ message: "Memory Crystal capture hook executed.",
136
+ payload: {
137
+ hook: "crystal-capture",
138
+ inputSummary: capture,
139
+ rawPayload: payload,
140
+ },
141
+ };
142
+ };
143
+
144
+ const runRecallHook = (payload = {}) => {
145
+ const query =
146
+ typeof payload.query === "string"
147
+ ? payload.query
148
+ : typeof payload.userMessage === "string"
149
+ ? payload.userMessage
150
+ : typeof payload.message === "string"
151
+ ? payload.message
152
+ : "";
153
+ const result = childProcess.spawnSync(process.execPath, [RECALL_HOOK], {
154
+ encoding: "utf8",
155
+ input: JSON.stringify({
156
+ query,
157
+ channel: resolveChannelKey(payload),
158
+ sessionId: payload.sessionId ?? "",
159
+ }),
160
+ });
161
+ if (result.error || result.status !== 0) {
162
+ const stderr = result.stderr?.toString() || result.error?.message || "";
163
+ console.error(stderr);
164
+ return {
165
+ ok: false,
166
+ message: "Memory Crystal hook failed",
167
+ error: true,
168
+ stderr,
169
+ };
170
+ }
171
+ const recall = parseRecallResult((result.stdout || "").trim());
172
+ const injection = recall.injectionBlock;
173
+ return {
174
+ ok: true,
175
+ message: "Memory Crystal recall hook executed.",
176
+ payload: {
177
+ hook: "crystal-recall",
178
+ memories: recall.memories,
179
+ injection,
180
+ },
181
+ injection,
182
+ };
183
+ };
184
+
185
+ const runHook = async (hookName, payload = {}) => {
186
+ if (captureHooks.has(hookName)) {
187
+ return runCaptureHook(payload);
188
+ }
189
+
190
+ if (recallHooks.has(hookName)) {
191
+ return runRecallHook(payload);
192
+ }
193
+
194
+ if (hookName === "startup") {
195
+ return {
196
+ ok: true,
197
+ message: "Memory Crystal startup hook executed.",
198
+ payload,
199
+ };
200
+ }
201
+
202
+ console.error(`[crystal-handler] Unsupported hook name: ${hookName}`);
203
+ return {
204
+ ok: false,
205
+ message: `Memory Crystal hook '${hookName}' is not recognized. Expected: startup, message, turn, postTurn, post_turn, pre-response, message_received, llm_output, llm-output, llmOutput, before_model_resolve, beforeModelResolve.`,
206
+ payload: {
207
+ ...payload,
208
+ supportedHooks: [
209
+ "startup",
210
+ "message",
211
+ "turn",
212
+ "postTurn",
213
+ "post_turn",
214
+ "pre-response",
215
+ "message_received",
216
+ "llm_output",
217
+ "llm-output",
218
+ "llmOutput",
219
+ "before_model_resolve",
220
+ "beforeModelResolve",
221
+ ],
222
+ },
223
+ stderr: `Unsupported hook name: ${hookName}`,
224
+ error: true,
225
+ };
226
+ };
227
+
228
+ module.exports = {
229
+ manifest,
230
+ status,
231
+ startup: async (payload) => runHook("startup", payload),
232
+ postTurn: async (payload) => runHook("postTurn", payload),
233
+ post_turn: async (payload) => runHook("postTurn", payload),
234
+ message: async (payload) => runHook("message", payload),
235
+ turn: async (payload) => runHook("turn", payload),
236
+ "pre-response": async (payload) => runHook("pre-response", payload),
237
+ before_model_resolve: async (payload) => runHook("before_model_resolve", payload),
238
+ message_received: async (payload) => runHook("message_received", payload),
239
+ llm_output: async (payload) => runHook("llm_output", payload),
240
+ "llm-output": async (payload) => runHook("llm_output", payload),
241
+ llmOutput: async (payload) => runHook("llmOutput", payload),
242
+ beforeModelResolve: async (payload) => runHook("beforeModelResolve", payload),
243
+ };
244
+
245
+ if (require.main === module) {
246
+ console.log(JSON.stringify(status(), null, 2));
247
+ }