memory-crystal 0.2.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.
Files changed (104) hide show
  1. package/.env.example +20 -0
  2. package/CHANGELOG.md +6 -0
  3. package/LETTERS.md +22 -0
  4. package/LICENSE +21 -0
  5. package/README-ENTERPRISE.md +162 -0
  6. package/README-old.md +275 -0
  7. package/README.md +91 -0
  8. package/RELAY.md +88 -0
  9. package/TECHNICAL.md +379 -0
  10. package/ai/dev-updates/2026-02-25--cc-air--phase2-architecture-pivot.md +70 -0
  11. package/ai/dev-updates/2026-02-25--cc-air--phase2-worker-build.md +72 -0
  12. package/ai/dev-updates/2026-02-26--10-25-16--cc-mini--phase2-implementation.md +49 -0
  13. package/ai/dev-updates/2026-02-27--20-30-00--cc-mini--readme-overhaul-and-public-deploy.md +69 -0
  14. package/ai/notes/2026-02-26--cc-air--notes.md +412 -0
  15. package/ai/notes/2026-02-27--cc-mini--grok-feedback.md +44 -0
  16. package/ai/notes/2026-02-27--cc-mini--lesa-feedback.md +45 -0
  17. package/ai/notes/RESEARCH.md +1185 -0
  18. package/ai/notes/salience-research/README.md +29 -0
  19. package/ai/notes/salience-research/eurosla-salience-review.md +64 -0
  20. package/ai/notes/salience-research/full-research-summary.md +269 -0
  21. package/ai/notes/salience-research/salience-levels-diagram.png +0 -0
  22. package/ai/plan/2026-02-27--cc-mini--qr-pairing-spec.md +203 -0
  23. package/ai/plan/_archive/PLAN.md +194 -0
  24. package/ai/plan/_archive/PRD.md +1014 -0
  25. package/ai/plan/cc-plans-duplicates-from-dot-claude/2026-02-26--cc-mini--phase2-implementation-plan.md +245 -0
  26. package/ai/plan/dev-conventions-note.md +70 -0
  27. package/ai/plan/ldm-os-install-and-boot-architecture.md +285 -0
  28. package/ai/plan/memory-crystal-phase2-plan.md +192 -0
  29. package/ai/plan/memory-system-lay-of-the-land.md +214 -0
  30. package/ai/plan/phase2-ephemeral-relay.md +238 -0
  31. package/ai/plan/readme-first.md +68 -0
  32. package/ai/plan/roadmap.md +159 -0
  33. package/ai/todos/PUNCHLIST.md +44 -0
  34. package/ai/todos/README.md +31 -0
  35. package/ai/todos/inboxes/cc-air/2026-02-26--cc-air--post-relay-todos.md +85 -0
  36. package/ai/todos/inboxes/cc-mini/2026-02-26--cc-mini--phase2-status.md +100 -0
  37. package/ai/todos/inboxes/cc-mini/_archive/TODO.md +25 -0
  38. package/ai/todos/inboxes/parker/2026-02-25--cc-air--setup-checklist.md +139 -0
  39. package/ai/todos/inboxes/parker/2026-02-26--cc-mini--phase2-your-moves.md +72 -0
  40. package/dist/cc-hook.d.ts +1 -0
  41. package/dist/cc-hook.js +349 -0
  42. package/dist/chunk-3VFIJYS4.js +818 -0
  43. package/dist/chunk-52QE3YI3.js +1169 -0
  44. package/dist/chunk-AA3OPP4Z.js +432 -0
  45. package/dist/chunk-D3I3ZSE2.js +411 -0
  46. package/dist/chunk-EKSACBTJ.js +1070 -0
  47. package/dist/chunk-F3Y7EL7K.js +83 -0
  48. package/dist/chunk-JWZXYVET.js +1068 -0
  49. package/dist/chunk-KYVWO6ZM.js +1069 -0
  50. package/dist/chunk-L3VHARQH.js +413 -0
  51. package/dist/chunk-LOVAHSQV.js +411 -0
  52. package/dist/chunk-LQOYCAGG.js +446 -0
  53. package/dist/chunk-MK42FMEG.js +147 -0
  54. package/dist/chunk-NIJCVN3O.js +147 -0
  55. package/dist/chunk-O2UITJGH.js +465 -0
  56. package/dist/chunk-PEK6JH65.js +432 -0
  57. package/dist/chunk-PJ6FFKEX.js +77 -0
  58. package/dist/chunk-PLUBBZYR.js +800 -0
  59. package/dist/chunk-SGL6ISBJ.js +1061 -0
  60. package/dist/chunk-UNHVZB5G.js +411 -0
  61. package/dist/chunk-VAFTWSTE.js +1061 -0
  62. package/dist/chunk-XZ3S56RQ.js +1061 -0
  63. package/dist/chunk-Y72C7F6O.js +148 -0
  64. package/dist/cli.d.ts +1 -0
  65. package/dist/cli.js +325 -0
  66. package/dist/core.d.ts +188 -0
  67. package/dist/core.js +12 -0
  68. package/dist/crypto.d.ts +16 -0
  69. package/dist/crypto.js +18 -0
  70. package/dist/dev-update-SZ2Z4WCQ.js +6 -0
  71. package/dist/ldm.d.ts +17 -0
  72. package/dist/ldm.js +12 -0
  73. package/dist/mcp-server.d.ts +1 -0
  74. package/dist/mcp-server.js +250 -0
  75. package/dist/migrate.d.ts +1 -0
  76. package/dist/migrate.js +89 -0
  77. package/dist/mirror-sync.d.ts +1 -0
  78. package/dist/mirror-sync.js +130 -0
  79. package/dist/openclaw.d.ts +5 -0
  80. package/dist/openclaw.js +349 -0
  81. package/dist/poller.d.ts +1 -0
  82. package/dist/poller.js +272 -0
  83. package/dist/summarize.d.ts +19 -0
  84. package/dist/summarize.js +10 -0
  85. package/dist/worker.js +137 -0
  86. package/openclaw.plugin.json +11 -0
  87. package/package.json +40 -0
  88. package/scripts/migrate-lance-to-sqlite.mjs +217 -0
  89. package/skills/memory/SKILL.md +61 -0
  90. package/src/cc-hook.ts +447 -0
  91. package/src/cli.ts +356 -0
  92. package/src/core.ts +1472 -0
  93. package/src/crypto.ts +113 -0
  94. package/src/dev-update.ts +178 -0
  95. package/src/ldm.ts +117 -0
  96. package/src/mcp-server.ts +274 -0
  97. package/src/migrate.ts +104 -0
  98. package/src/mirror-sync.ts +175 -0
  99. package/src/openclaw.ts +250 -0
  100. package/src/poller.ts +345 -0
  101. package/src/summarize.ts +210 -0
  102. package/src/worker.ts +208 -0
  103. package/tsconfig.json +18 -0
  104. package/wrangler.toml +20 -0
@@ -0,0 +1,72 @@
1
+ # Phase 2 -- Your Moves, Parker
2
+
3
+ **From:** cc-mini
4
+ **Date:** 2026-02-26
5
+ **Branch:** `mini/phase2-relay` (ready, not merged to main yet)
6
+
7
+ Code is done. Build passes. These are the things only you can do.
8
+
9
+ ## Now (before deploying)
10
+
11
+ - [ ] **Review the branch.** `git log main..mini/phase2-relay --oneline` in the memory-crystal repo. 4 commits including the cc-air relay merge.
12
+
13
+ - [ ] **Run migrate-db.** Moves crystal.db to `~/.ldm/memory/`. Stop gateway first.
14
+ ```bash
15
+ openclaw gateway stop
16
+ cd ~/Documents/wipcomputer--mac-mini-01/staff/Parker/Claude\ Code\ -\ Mini/repos/memory-crystal
17
+ node dist/cli.js migrate-db
18
+ openclaw gateway restart
19
+ ```
20
+ Verify: `node dist/cli.js status` should show data dir as `~/.ldm/memory/`.
21
+
22
+ - [ ] **Deploy updated plugin.** Copy built files to extension dir:
23
+ ```bash
24
+ cd ~/Documents/wipcomputer--mac-mini-01/staff/Parker/Claude\ Code\ -\ Mini/repos/memory-crystal
25
+ npm run build
26
+ cp -r dist skills openclaw.plugin.json package.json ~/.openclaw/extensions/memory-crystal/
27
+ cd ~/.openclaw/extensions/memory-crystal && npm install --omit=dev
28
+ openclaw gateway restart
29
+ ```
30
+
31
+ ## When you're ready for relay (Air <-> Mini sync)
32
+
33
+ - [ ] **Generate encryption key:**
34
+ ```bash
35
+ openssl rand -base64 32 > ~/.openclaw/secrets/crystal-relay-key
36
+ chmod 600 ~/.openclaw/secrets/crystal-relay-key
37
+ ```
38
+
39
+ - [ ] **Copy key to MacBook Air** (same path, same permissions)
40
+
41
+ - [ ] **Cloudflare setup:**
42
+ ```bash
43
+ wrangler login
44
+ wrangler r2 bucket create memory-crystal-relay
45
+ wrangler secret put AUTH_TOKEN_CC_AIR # generate a random token
46
+ wrangler secret put AUTH_TOKEN_CC_MINI # generate a random token
47
+ wrangler secret put AUTH_TOKEN_LESA # generate a random token
48
+ wrangler deploy
49
+ ```
50
+
51
+ - [ ] **Set env vars on Mini** (in your shell profile or launchd):
52
+ ```
53
+ CRYSTAL_RELAY_URL=https://memory-crystal-relay.<your-account>.workers.dev
54
+ CRYSTAL_RELAY_TOKEN=<cc-mini bearer token>
55
+ CRYSTAL_AGENT_ID=cc-mini
56
+ ```
57
+
58
+ - [ ] **Set env vars on Air:**
59
+ ```
60
+ CRYSTAL_RELAY_URL=<same URL>
61
+ CRYSTAL_RELAY_TOKEN=<cc-air bearer token>
62
+ CRYSTAL_AGENT_ID=cc-air
63
+ ```
64
+
65
+ - [ ] **End-to-end test:** Run a CC session on Air, check Mini picks it up via poller.
66
+
67
+ Full checklist with more detail: `ai/todos/parker/2026-02-25--cc-air--setup-checklist.md`
68
+
69
+ ## Merge to main + release
70
+
71
+ - [ ] **Merge branch:** `git checkout main && git merge mini/phase2-relay`
72
+ - [ ] **Release:** `wip-release minor --notes="Phase 2: LDM scaffolding, JSONL archive, MD summaries, relay merge"`
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,349 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createCrystal,
4
+ resolveConfig
5
+ } from "./chunk-52QE3YI3.js";
6
+ import {
7
+ encryptJSON,
8
+ loadRelayKey
9
+ } from "./chunk-F3Y7EL7K.js";
10
+ import {
11
+ ensureLdm,
12
+ ldmPaths
13
+ } from "./chunk-PJ6FFKEX.js";
14
+
15
+ // src/cc-hook.ts
16
+ import {
17
+ readFileSync,
18
+ writeFileSync,
19
+ appendFileSync,
20
+ existsSync,
21
+ mkdirSync,
22
+ statSync,
23
+ openSync,
24
+ readSync,
25
+ closeSync,
26
+ copyFileSync
27
+ } from "fs";
28
+ import { join, basename, dirname } from "path";
29
+ var HOME = process.env.HOME || "";
30
+ var CC_AGENT_ID = process.env.CRYSTAL_AGENT_ID || "cc-mini";
31
+ var RELAY_URL = process.env.CRYSTAL_RELAY_URL || "";
32
+ var RELAY_TOKEN = process.env.CRYSTAL_RELAY_TOKEN || "";
33
+ var OC_DIR = join(HOME, ".openclaw");
34
+ var LDM_DAILY = join(HOME, ".ldm", "agents", CC_AGENT_ID, "memory", "daily");
35
+ var PRIVATE_MODE_PATH = join(OC_DIR, "memory", "memory-capture-state.json");
36
+ var WATERMARK_PATH = join(OC_DIR, "memory", "cc-capture-watermark.json");
37
+ var CC_ENABLED_PATH = join(OC_DIR, "memory", "cc-capture-enabled.json");
38
+ function getCaptureMode() {
39
+ if (RELAY_URL && RELAY_TOKEN) return "relay";
40
+ return "local";
41
+ }
42
+ function isPrivateMode() {
43
+ try {
44
+ if (existsSync(PRIVATE_MODE_PATH)) {
45
+ const state = JSON.parse(readFileSync(PRIVATE_MODE_PATH, "utf-8"));
46
+ return state.enabled === false;
47
+ }
48
+ } catch {
49
+ }
50
+ return false;
51
+ }
52
+ function isCaptureEnabled() {
53
+ try {
54
+ if (existsSync(CC_ENABLED_PATH)) {
55
+ const state = JSON.parse(readFileSync(CC_ENABLED_PATH, "utf-8"));
56
+ return state.enabled !== false;
57
+ }
58
+ } catch {
59
+ }
60
+ return true;
61
+ }
62
+ function setCaptureEnabled(enabled) {
63
+ const dir = dirname(CC_ENABLED_PATH);
64
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
65
+ writeFileSync(CC_ENABLED_PATH, JSON.stringify({
66
+ enabled,
67
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
68
+ }, null, 2));
69
+ }
70
+ function loadWatermark() {
71
+ try {
72
+ if (existsSync(WATERMARK_PATH)) {
73
+ return JSON.parse(readFileSync(WATERMARK_PATH, "utf-8"));
74
+ }
75
+ } catch {
76
+ }
77
+ return { files: {}, lastRun: null };
78
+ }
79
+ function saveWatermark(wm) {
80
+ const dir = dirname(WATERMARK_PATH);
81
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
82
+ wm.lastRun = (/* @__PURE__ */ new Date()).toISOString();
83
+ writeFileSync(WATERMARK_PATH, JSON.stringify(wm, null, 2));
84
+ }
85
+ function extractMessages(filePath, lastByteOffset) {
86
+ const fileSize = statSync(filePath).size;
87
+ if (lastByteOffset >= fileSize) {
88
+ return { messages: [], newByteOffset: fileSize };
89
+ }
90
+ const fd = openSync(filePath, "r");
91
+ const bufSize = fileSize - lastByteOffset;
92
+ const buf = Buffer.alloc(bufSize);
93
+ readSync(fd, buf, 0, bufSize, lastByteOffset);
94
+ closeSync(fd);
95
+ const lines = buf.toString("utf-8").split("\n").filter(Boolean);
96
+ const messages = [];
97
+ for (const line of lines) {
98
+ try {
99
+ const obj = JSON.parse(line);
100
+ if (obj.type !== "user" && obj.type !== "assistant") continue;
101
+ const msg = obj.message;
102
+ if (!msg) continue;
103
+ let text = "";
104
+ if (typeof msg.content === "string") {
105
+ text = msg.content;
106
+ } else if (Array.isArray(msg.content)) {
107
+ const parts = [];
108
+ for (const block of msg.content) {
109
+ if (block.type === "text" && block.text) parts.push(block.text);
110
+ if (block.type === "thinking" && block.thinking) parts.push(`[thinking] ${block.thinking}`);
111
+ }
112
+ text = parts.join("\n\n");
113
+ }
114
+ if (text.length < 20) continue;
115
+ messages.push({
116
+ role: msg.role || obj.type,
117
+ text,
118
+ timestamp: obj.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
119
+ sessionId: obj.sessionId || "unknown"
120
+ });
121
+ } catch {
122
+ }
123
+ }
124
+ return { messages, newByteOffset: fileSize };
125
+ }
126
+ function archiveTranscript(transcriptPath, agentId) {
127
+ try {
128
+ if (isPrivateMode()) return;
129
+ const paths = ensureLdm(agentId);
130
+ const dest = join(paths.transcripts, basename(transcriptPath));
131
+ if (existsSync(dest)) {
132
+ const srcMtime = statSync(transcriptPath).mtimeMs;
133
+ const dstMtime = statSync(dest).mtimeMs;
134
+ if (srcMtime <= dstMtime) return;
135
+ }
136
+ copyFileSync(transcriptPath, dest);
137
+ } catch {
138
+ }
139
+ }
140
+ function appendDailyLog(messages, agentId) {
141
+ try {
142
+ const paths = ldmPaths(agentId);
143
+ if (!existsSync(paths.root)) return;
144
+ if (!existsSync(paths.daily)) mkdirSync(paths.daily, { recursive: true });
145
+ const now = /* @__PURE__ */ new Date();
146
+ const dateStr = now.toISOString().slice(0, 10);
147
+ const timeStr = now.toLocaleTimeString("en-US", {
148
+ hour: "2-digit",
149
+ minute: "2-digit",
150
+ hour12: false,
151
+ timeZone: "America/Los_Angeles"
152
+ });
153
+ const logPath = join(paths.daily, `${dateStr}.md`);
154
+ const userMsg = messages.find((m) => m.role === "user");
155
+ if (!userMsg) return;
156
+ const snippet = userMsg.text.slice(0, 120).replace(/\n/g, " ").trim();
157
+ const line = `- **${timeStr}** ${snippet}${userMsg.text.length > 120 ? "..." : ""}
158
+ `;
159
+ if (!existsSync(logPath)) {
160
+ writeFileSync(logPath, `# ${dateStr} - CC Daily Log
161
+
162
+ `);
163
+ }
164
+ appendFileSync(logPath, line);
165
+ } catch {
166
+ }
167
+ }
168
+ async function dropAtRelay(messages) {
169
+ const relayKey = loadRelayKey();
170
+ const payload = {
171
+ agent_id: CC_AGENT_ID,
172
+ dropped_at: (/* @__PURE__ */ new Date()).toISOString(),
173
+ messages: messages.map((m) => ({
174
+ text: m.text,
175
+ role: m.role,
176
+ timestamp: m.timestamp,
177
+ sessionId: m.sessionId
178
+ }))
179
+ };
180
+ const encrypted = encryptJSON(payload, relayKey);
181
+ const body = JSON.stringify(encrypted);
182
+ let retries = 0;
183
+ while (retries < 4) {
184
+ try {
185
+ const resp = await fetch(`${RELAY_URL}/drop/conversations`, {
186
+ method: "POST",
187
+ headers: {
188
+ "Authorization": `Bearer ${RELAY_TOKEN}`,
189
+ "Content-Type": "application/octet-stream"
190
+ },
191
+ body
192
+ });
193
+ if (!resp.ok) {
194
+ const err = await resp.text();
195
+ throw new Error(`Relay drop failed: ${resp.status} ${err}`);
196
+ }
197
+ const result = await resp.json();
198
+ return messages.length;
199
+ } catch (err) {
200
+ retries++;
201
+ if (retries >= 4) throw err;
202
+ const delay = Math.min(1e3 * 2 ** retries, 3e4);
203
+ process.stderr.write(` [relay retry ${retries}] ${err.message}, waiting ${delay}ms
204
+ `);
205
+ await new Promise((r) => setTimeout(r, delay));
206
+ }
207
+ }
208
+ return 0;
209
+ }
210
+ var BATCH_SIZE = 200;
211
+ async function ingestLocal(messages) {
212
+ const config = resolveConfig();
213
+ const crystal = createCrystal(config);
214
+ await crystal.init();
215
+ const maxSingleChunkChars = 2e3 * 4;
216
+ const chunks = [];
217
+ for (const msg of messages) {
218
+ if (msg.text.length <= maxSingleChunkChars) {
219
+ chunks.push({
220
+ text: msg.text,
221
+ role: msg.role,
222
+ source_type: "conversation",
223
+ source_id: `cc:${msg.sessionId}`,
224
+ agent_id: CC_AGENT_ID,
225
+ token_count: Math.ceil(msg.text.length / 4),
226
+ created_at: msg.timestamp
227
+ });
228
+ } else {
229
+ for (const ct of crystal.chunkText(msg.text)) {
230
+ chunks.push({
231
+ text: ct,
232
+ role: msg.role,
233
+ source_type: "conversation",
234
+ source_id: `cc:${msg.sessionId}`,
235
+ agent_id: CC_AGENT_ID,
236
+ token_count: Math.ceil(ct.length / 4),
237
+ created_at: msg.timestamp
238
+ });
239
+ }
240
+ }
241
+ }
242
+ let total = 0;
243
+ for (let i = 0; i < chunks.length; i += BATCH_SIZE) {
244
+ const batch = chunks.slice(i, i + BATCH_SIZE);
245
+ let retries = 0;
246
+ while (retries < 4) {
247
+ try {
248
+ total += await crystal.ingest(batch);
249
+ break;
250
+ } catch (err) {
251
+ retries++;
252
+ if (retries >= 4) throw err;
253
+ const delay = Math.min(1e3 * 2 ** retries, 3e4);
254
+ process.stderr.write(` [retry ${retries}] ${err.message}, waiting ${delay}ms
255
+ `);
256
+ await new Promise((r) => setTimeout(r, delay));
257
+ }
258
+ }
259
+ }
260
+ return total;
261
+ }
262
+ var args = process.argv.slice(2);
263
+ if (args.includes("--on")) {
264
+ setCaptureEnabled(true);
265
+ console.log("(*) Claude Code memory capture ON");
266
+ process.exit(0);
267
+ }
268
+ if (args.includes("--off")) {
269
+ setCaptureEnabled(false);
270
+ console.log("( ) Claude Code memory capture OFF");
271
+ process.exit(0);
272
+ }
273
+ if (args.includes("--status")) {
274
+ const mode = getCaptureMode();
275
+ console.log(isCaptureEnabled() ? "(*) CC capture: ON" : "( ) CC capture: OFF");
276
+ console.log(isPrivateMode() ? "( ) Private mode: ON (blocks all capture)" : "(*) Private mode: OFF");
277
+ console.log(` Mode: ${mode}${mode === "relay" ? ` (${RELAY_URL})` : ""}`);
278
+ console.log(` Agent ID: ${CC_AGENT_ID}`);
279
+ process.exit(0);
280
+ }
281
+ async function main() {
282
+ let input = "";
283
+ for await (const chunk of process.stdin) {
284
+ input += chunk;
285
+ }
286
+ let hookData;
287
+ try {
288
+ hookData = JSON.parse(input);
289
+ } catch {
290
+ process.exit(0);
291
+ }
292
+ const transcriptPath = hookData.transcript_path;
293
+ if (!transcriptPath || !existsSync(transcriptPath)) process.exit(0);
294
+ if (isPrivateMode() || !isCaptureEnabled()) process.exit(0);
295
+ archiveTranscript(transcriptPath);
296
+ const wm = loadWatermark();
297
+ const fileKey = transcriptPath;
298
+ if (!wm.files[fileKey]) {
299
+ const size = statSync(transcriptPath).size;
300
+ wm.files[fileKey] = { lastByteOffset: size, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
301
+ saveWatermark(wm);
302
+ process.stderr.write(`[cc-memory-capture] seeded ${basename(transcriptPath)} at ${size} bytes
303
+ `);
304
+ process.exit(0);
305
+ }
306
+ const lastOffset = wm.files[fileKey].lastByteOffset || 0;
307
+ const { messages, newByteOffset } = extractMessages(transcriptPath, lastOffset);
308
+ if (messages.length === 0) {
309
+ wm.files[fileKey] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
310
+ saveWatermark(wm);
311
+ process.exit(0);
312
+ }
313
+ const totalTokens = messages.reduce((sum, m) => sum + Math.ceil(m.text.length / 4), 0);
314
+ if (totalTokens < 500) {
315
+ wm.files[fileKey] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
316
+ saveWatermark(wm);
317
+ process.exit(0);
318
+ }
319
+ const mode = getCaptureMode();
320
+ try {
321
+ if (mode === "relay") {
322
+ const count = await dropAtRelay(messages);
323
+ process.stderr.write(`[cc-memory-capture] relayed ${count} messages (${totalTokens} tokens) from ${basename(transcriptPath)}
324
+ `);
325
+ } else {
326
+ const count = await ingestLocal(messages);
327
+ process.stderr.write(`[cc-memory-capture] ${count} chunks (${totalTokens} tokens) from ${basename(transcriptPath)}
328
+ `);
329
+ }
330
+ wm.files[fileKey] = { lastByteOffset: newByteOffset, lastTimestamp: (/* @__PURE__ */ new Date()).toISOString() };
331
+ saveWatermark(wm);
332
+ appendDailyLog(messages);
333
+ try {
334
+ const { generateSessionSummary, writeSummaryFile } = await import("./summarize.js");
335
+ const paths = ldmPaths();
336
+ const summaryMsgs = messages.map((m) => ({ role: m.role, text: m.text, timestamp: m.timestamp, sessionId: m.sessionId }));
337
+ const summary = await generateSessionSummary(summaryMsgs);
338
+ const sessionId = messages[0]?.sessionId || "unknown";
339
+ const agentId = process.env.CRYSTAL_AGENT_ID || "cc-mini";
340
+ writeSummaryFile(paths.sessions, summary, agentId, sessionId);
341
+ } catch {
342
+ }
343
+ } catch (err) {
344
+ process.stderr.write(`[cc-memory-capture] error: ${err.message}
345
+ `);
346
+ process.exit(1);
347
+ }
348
+ }
349
+ main();