@ectplsm/relic 0.1.1 → 0.1.3
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 +172 -49
- package/dist/adapters/shells/claude-hook.d.ts +12 -0
- package/dist/adapters/shells/claude-hook.js +160 -0
- package/dist/adapters/shells/claude-shell.d.ts +5 -2
- package/dist/adapters/shells/claude-shell.js +17 -3
- package/dist/adapters/shells/codex-hook.d.ts +12 -0
- package/dist/adapters/shells/codex-hook.js +141 -0
- package/dist/adapters/shells/codex-shell.d.ts +7 -4
- package/dist/adapters/shells/codex-shell.js +22 -7
- package/dist/adapters/shells/copilot-shell.d.ts +2 -2
- package/dist/adapters/shells/copilot-shell.js +3 -3
- package/dist/adapters/shells/gemini-hook.d.ts +12 -0
- package/dist/adapters/shells/gemini-hook.js +108 -0
- package/dist/adapters/shells/gemini-shell.d.ts +6 -4
- package/dist/adapters/shells/gemini-shell.js +103 -14
- package/dist/adapters/shells/index.d.ts +0 -1
- package/dist/adapters/shells/index.js +0 -1
- package/dist/adapters/shells/spawn-shell.d.ts +1 -1
- package/dist/adapters/shells/spawn-shell.js +10 -3
- package/dist/adapters/shells/trust-registrar.d.ts +19 -0
- package/dist/adapters/shells/trust-registrar.js +141 -0
- package/dist/core/ports/shell-launcher.d.ts +17 -2
- package/dist/core/usecases/archive-cursor-update.d.ts +21 -0
- package/dist/core/usecases/archive-cursor-update.js +44 -0
- package/dist/core/usecases/archive-pending.d.ts +25 -0
- package/dist/core/usecases/archive-pending.js +61 -0
- package/dist/core/usecases/archive-search.d.ts +20 -0
- package/dist/core/usecases/archive-search.js +46 -0
- package/dist/core/usecases/inbox-search.d.ts +20 -0
- package/dist/core/usecases/inbox-search.js +46 -0
- package/dist/core/usecases/inbox-write.d.ts +27 -0
- package/dist/core/usecases/inbox-write.js +72 -0
- package/dist/core/usecases/index.d.ts +2 -1
- package/dist/core/usecases/index.js +2 -1
- package/dist/core/usecases/summon.d.ts +6 -1
- package/dist/core/usecases/summon.js +8 -2
- package/dist/interfaces/cli/commands/config.d.ts +2 -0
- package/dist/interfaces/cli/commands/config.js +83 -0
- package/dist/interfaces/cli/commands/extract.js +3 -2
- package/dist/interfaces/cli/commands/init.js +47 -0
- package/dist/interfaces/cli/commands/inject.js +3 -2
- package/dist/interfaces/cli/commands/shell.js +13 -11
- package/dist/interfaces/cli/index.js +8 -1
- package/dist/interfaces/mcp/index.js +68 -305
- package/dist/shared/config.d.ts +31 -0
- package/dist/shared/config.js +86 -16
- package/dist/shared/engram-composer.d.ts +16 -3
- package/dist/shared/engram-composer.js +50 -5
- package/dist/shared/memory-inbox.d.ts +78 -0
- package/dist/shared/memory-inbox.js +168 -0
- package/dist/shared/session-recorder.d.ts +47 -0
- package/dist/shared/session-recorder.js +112 -0
- package/package.json +5 -5
|
@@ -2,247 +2,20 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { ArchiveSearch, ArchiveSearchEngramNotFoundError, } from "../../core/usecases/archive-search.js";
|
|
6
|
+
import { ArchivePending, ArchivePendingEngramNotFoundError, } from "../../core/usecases/archive-pending.js";
|
|
7
|
+
import { ArchiveCursorUpdate, } from "../../core/usecases/archive-cursor-update.js";
|
|
8
|
+
import { MemoryWrite, MemoryWriteEngramNotFoundError, } from "../../core/usecases/memory-write.js";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
7
12
|
import { resolveEngramsPath } from "../../shared/config.js";
|
|
8
13
|
const server = new McpServer({
|
|
9
14
|
name: "relic",
|
|
10
15
|
version: "0.1.0",
|
|
11
16
|
});
|
|
12
|
-
// ---
|
|
13
|
-
server.tool("
|
|
14
|
-
const init = new Init();
|
|
15
|
-
const result = await init.execute();
|
|
16
|
-
if (result.created) {
|
|
17
|
-
return {
|
|
18
|
-
content: [
|
|
19
|
-
{
|
|
20
|
-
type: "text",
|
|
21
|
-
text: [
|
|
22
|
-
"Relic initialized successfully.",
|
|
23
|
-
` Config: ${result.configPath}`,
|
|
24
|
-
` Engrams: ${result.engramsPath}`,
|
|
25
|
-
"Sample Engrams created: motoko, johnny",
|
|
26
|
-
].join("\n"),
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
content: [
|
|
33
|
-
{
|
|
34
|
-
type: "text",
|
|
35
|
-
text: `Relic already initialized at ${result.relicDir}`,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
};
|
|
39
|
-
});
|
|
40
|
-
// --- relic_list ---
|
|
41
|
-
server.tool("relic_list", "List all available Engrams", {
|
|
42
|
-
path: z
|
|
43
|
-
.string()
|
|
44
|
-
.optional()
|
|
45
|
-
.describe("Override engrams directory path"),
|
|
46
|
-
}, async (args) => {
|
|
47
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
48
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
49
|
-
const listEngrams = new ListEngrams(repo);
|
|
50
|
-
const engrams = await listEngrams.execute();
|
|
51
|
-
if (engrams.length === 0) {
|
|
52
|
-
return {
|
|
53
|
-
content: [
|
|
54
|
-
{
|
|
55
|
-
type: "text",
|
|
56
|
-
text: "No Engrams found. Run relic_init to create sample Engrams.",
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const lines = engrams.map((e) => `- ${e.id}: ${e.name} — ${e.description}`);
|
|
62
|
-
return {
|
|
63
|
-
content: [
|
|
64
|
-
{
|
|
65
|
-
type: "text",
|
|
66
|
-
text: lines.join("\n"),
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
// --- relic_show ---
|
|
72
|
-
server.tool("relic_show", "Show the composed prompt for an Engram (preview without launching a Shell)", {
|
|
73
|
-
id: z.string().describe("Engram ID to show"),
|
|
74
|
-
path: z
|
|
75
|
-
.string()
|
|
76
|
-
.optional()
|
|
77
|
-
.describe("Override engrams directory path"),
|
|
78
|
-
}, async (args) => {
|
|
79
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
80
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
81
|
-
const summon = new Summon(repo);
|
|
82
|
-
try {
|
|
83
|
-
const result = await summon.execute(args.id);
|
|
84
|
-
return {
|
|
85
|
-
content: [
|
|
86
|
-
{
|
|
87
|
-
type: "text",
|
|
88
|
-
text: result.prompt,
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
if (err instanceof EngramNotFoundError) {
|
|
95
|
-
return {
|
|
96
|
-
content: [{ type: "text", text: err.message }],
|
|
97
|
-
isError: true,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
throw err;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
// --- relic_summon ---
|
|
104
|
-
server.tool("relic_summon", "Summon an Engram — returns the persona prompt ready for injection into a Shell", {
|
|
105
|
-
id: z.string().describe("Engram ID to summon"),
|
|
106
|
-
path: z
|
|
107
|
-
.string()
|
|
108
|
-
.optional()
|
|
109
|
-
.describe("Override engrams directory path"),
|
|
110
|
-
}, async (args) => {
|
|
111
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
112
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
113
|
-
const summon = new Summon(repo);
|
|
114
|
-
try {
|
|
115
|
-
const result = await summon.execute(args.id);
|
|
116
|
-
return {
|
|
117
|
-
content: [
|
|
118
|
-
{
|
|
119
|
-
type: "text",
|
|
120
|
-
text: JSON.stringify({
|
|
121
|
-
engramId: result.engramId,
|
|
122
|
-
engramName: result.engramName,
|
|
123
|
-
prompt: result.prompt,
|
|
124
|
-
}, null, 2),
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
catch (err) {
|
|
130
|
-
if (err instanceof EngramNotFoundError) {
|
|
131
|
-
return {
|
|
132
|
-
content: [{ type: "text", text: err.message }],
|
|
133
|
-
isError: true,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
throw err;
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
// --- relic_inject ---
|
|
140
|
-
server.tool("relic_inject", "Inject an Engram into an OpenClaw workspace (agent name = Engram ID)", {
|
|
141
|
-
id: z.string().describe("Engram ID to inject (= OpenClaw agent name)"),
|
|
142
|
-
to: z
|
|
143
|
-
.string()
|
|
144
|
-
.optional()
|
|
145
|
-
.describe("Inject into a different agent name (default: same as Engram ID)"),
|
|
146
|
-
openclaw: z
|
|
147
|
-
.string()
|
|
148
|
-
.optional()
|
|
149
|
-
.describe("Override OpenClaw directory path (default: ~/.openclaw)"),
|
|
150
|
-
path: z
|
|
151
|
-
.string()
|
|
152
|
-
.optional()
|
|
153
|
-
.describe("Override engrams directory path"),
|
|
154
|
-
}, async (args) => {
|
|
155
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
156
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
157
|
-
const inject = new Inject(repo);
|
|
158
|
-
try {
|
|
159
|
-
const result = await inject.execute(args.id, {
|
|
160
|
-
to: args.to,
|
|
161
|
-
openclawDir: args.openclaw,
|
|
162
|
-
});
|
|
163
|
-
return {
|
|
164
|
-
content: [
|
|
165
|
-
{
|
|
166
|
-
type: "text",
|
|
167
|
-
text: [
|
|
168
|
-
`Injected "${result.engramName}" into ${result.targetPath}`,
|
|
169
|
-
`Files written: ${result.filesWritten.join(", ")}`,
|
|
170
|
-
].join("\n"),
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
if (err instanceof InjectEngramNotFoundError ||
|
|
177
|
-
err instanceof InjectAgentNotFoundError) {
|
|
178
|
-
return {
|
|
179
|
-
content: [{ type: "text", text: err.message }],
|
|
180
|
-
isError: true,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
throw err;
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
// --- relic_extract ---
|
|
187
|
-
server.tool("relic_extract", "Extract an Engram from an OpenClaw workspace (agent name = Engram ID)", {
|
|
188
|
-
id: z.string().describe("Engram ID (= OpenClaw agent name)"),
|
|
189
|
-
name: z
|
|
190
|
-
.string()
|
|
191
|
-
.optional()
|
|
192
|
-
.describe("Engram display name (required for new Engrams)"),
|
|
193
|
-
openclaw: z
|
|
194
|
-
.string()
|
|
195
|
-
.optional()
|
|
196
|
-
.describe("Override OpenClaw directory path (default: ~/.openclaw)"),
|
|
197
|
-
path: z
|
|
198
|
-
.string()
|
|
199
|
-
.optional()
|
|
200
|
-
.describe("Override engrams directory path"),
|
|
201
|
-
force: z
|
|
202
|
-
.boolean()
|
|
203
|
-
.optional()
|
|
204
|
-
.describe("Overwrite existing Engram persona files"),
|
|
205
|
-
}, async (args) => {
|
|
206
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
207
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
208
|
-
const extract = new Extract(repo);
|
|
209
|
-
try {
|
|
210
|
-
const result = await extract.execute(args.id, {
|
|
211
|
-
name: args.name,
|
|
212
|
-
openclawDir: args.openclaw,
|
|
213
|
-
force: args.force,
|
|
214
|
-
});
|
|
215
|
-
return {
|
|
216
|
-
content: [
|
|
217
|
-
{
|
|
218
|
-
type: "text",
|
|
219
|
-
text: [
|
|
220
|
-
`Extracted "${result.engramName}" from ${result.sourcePath}`,
|
|
221
|
-
`Files read: ${result.filesRead.join(", ")}`,
|
|
222
|
-
`Saved as Engram: ${result.engramId}`,
|
|
223
|
-
...(result.memoryMerged
|
|
224
|
-
? ["Memory entries merged into existing Engram"]
|
|
225
|
-
: []),
|
|
226
|
-
].join("\n"),
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
if (err instanceof WorkspaceNotFoundError ||
|
|
233
|
-
err instanceof WorkspaceEmptyError ||
|
|
234
|
-
err instanceof EngramAlreadyExistsError ||
|
|
235
|
-
err instanceof ExtractNameRequiredError) {
|
|
236
|
-
return {
|
|
237
|
-
content: [{ type: "text", text: err.message }],
|
|
238
|
-
isError: true,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
throw err;
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
// --- relic_memory_search ---
|
|
245
|
-
server.tool("relic_memory_search", "Search an Engram's memory entries by keyword", {
|
|
17
|
+
// --- relic_archive_search ---
|
|
18
|
+
server.tool("relic_archive_search", "Search an Engram's archive for entries matching a keyword. Searches raw session logs — more complete than memory/*.md.", {
|
|
246
19
|
id: z.string().describe("Engram ID"),
|
|
247
20
|
query: z.string().describe("Search keyword"),
|
|
248
21
|
limit: z
|
|
@@ -255,29 +28,28 @@ server.tool("relic_memory_search", "Search an Engram's memory entries by keyword
|
|
|
255
28
|
.describe("Override engrams directory path"),
|
|
256
29
|
}, async (args) => {
|
|
257
30
|
const engramsPath = await resolveEngramsPath(args.path);
|
|
258
|
-
const
|
|
259
|
-
const memorySearch = new MemorySearch(repo);
|
|
31
|
+
const archiveSearch = new ArchiveSearch(engramsPath);
|
|
260
32
|
try {
|
|
261
|
-
const results = await
|
|
33
|
+
const results = await archiveSearch.search(args.id, args.query, args.limit ?? 5);
|
|
262
34
|
if (results.length === 0) {
|
|
263
35
|
return {
|
|
264
36
|
content: [
|
|
265
37
|
{
|
|
266
38
|
type: "text",
|
|
267
|
-
text: `No
|
|
39
|
+
text: `No archive entries matching "${args.query}" found.`,
|
|
268
40
|
},
|
|
269
41
|
],
|
|
270
42
|
};
|
|
271
43
|
}
|
|
272
44
|
const text = results
|
|
273
|
-
.map((r) =>
|
|
274
|
-
.join("\n\n");
|
|
45
|
+
.map((r) => `[entry -${r.index}]\n${r.entry}`)
|
|
46
|
+
.join("\n\n---\n\n");
|
|
275
47
|
return {
|
|
276
48
|
content: [{ type: "text", text }],
|
|
277
49
|
};
|
|
278
50
|
}
|
|
279
51
|
catch (err) {
|
|
280
|
-
if (err instanceof
|
|
52
|
+
if (err instanceof ArchiveSearchEngramNotFoundError) {
|
|
281
53
|
return {
|
|
282
54
|
content: [{ type: "text", text: err.message }],
|
|
283
55
|
isError: true,
|
|
@@ -286,43 +58,42 @@ server.tool("relic_memory_search", "Search an Engram's memory entries by keyword
|
|
|
286
58
|
throw err;
|
|
287
59
|
}
|
|
288
60
|
});
|
|
289
|
-
// ---
|
|
290
|
-
server.tool("
|
|
61
|
+
// --- relic_archive_pending ---
|
|
62
|
+
server.tool("relic_archive_pending", "Get un-distilled archive entries since the last memory distillation. Use this to review recent session logs before distilling them into memory.", {
|
|
291
63
|
id: z.string().describe("Engram ID"),
|
|
292
|
-
|
|
293
|
-
.
|
|
294
|
-
.
|
|
64
|
+
limit: z
|
|
65
|
+
.number()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Max entries to return (default: 30)"),
|
|
295
68
|
path: z
|
|
296
69
|
.string()
|
|
297
70
|
.optional()
|
|
298
71
|
.describe("Override engrams directory path"),
|
|
299
72
|
}, async (args) => {
|
|
300
73
|
const engramsPath = await resolveEngramsPath(args.path);
|
|
301
|
-
const
|
|
302
|
-
const memorySearch = new MemorySearch(repo);
|
|
74
|
+
const archivePending = new ArchivePending(engramsPath);
|
|
303
75
|
try {
|
|
304
|
-
const result = await
|
|
305
|
-
if (
|
|
76
|
+
const result = await archivePending.execute(args.id, args.limit);
|
|
77
|
+
if (result.entries.length === 0) {
|
|
306
78
|
return {
|
|
307
79
|
content: [
|
|
308
80
|
{
|
|
309
81
|
type: "text",
|
|
310
|
-
text:
|
|
82
|
+
text: "No pending archive entries. All entries have been distilled.",
|
|
311
83
|
},
|
|
312
84
|
],
|
|
313
85
|
};
|
|
314
86
|
}
|
|
87
|
+
const header = `cursor: ${result.cursor} | total: ${result.total} | returned: ${result.entries.length} | remaining: ${result.remaining}`;
|
|
88
|
+
const body = result.entries
|
|
89
|
+
.map((e, i) => `[entry ${result.cursor + i + 1}]\n${e}`)
|
|
90
|
+
.join("\n\n---\n\n");
|
|
315
91
|
return {
|
|
316
|
-
content: [
|
|
317
|
-
{
|
|
318
|
-
type: "text",
|
|
319
|
-
text: `## ${result.date}\n\n${result.content}`,
|
|
320
|
-
},
|
|
321
|
-
],
|
|
92
|
+
content: [{ type: "text", text: `${header}\n\n${body}` }],
|
|
322
93
|
};
|
|
323
94
|
}
|
|
324
95
|
catch (err) {
|
|
325
|
-
if (err instanceof
|
|
96
|
+
if (err instanceof ArchivePendingEngramNotFoundError) {
|
|
326
97
|
return {
|
|
327
98
|
content: [{ type: "text", text: err.message }],
|
|
328
99
|
isError: true,
|
|
@@ -331,68 +102,60 @@ server.tool("relic_memory_get", "Get a specific memory entry by date", {
|
|
|
331
102
|
throw err;
|
|
332
103
|
}
|
|
333
104
|
});
|
|
334
|
-
// ---
|
|
335
|
-
server.tool("
|
|
105
|
+
// --- relic_memory_write ---
|
|
106
|
+
server.tool("relic_memory_write", "Write distilled memory to an Engram's memory file and advance the archive cursor. Call this after reviewing pending archive entries and distilling them into key insights.", {
|
|
336
107
|
id: z.string().describe("Engram ID"),
|
|
337
|
-
|
|
108
|
+
content: z.string().describe("Distilled memory content to write to memory/YYYY-MM-DD.md"),
|
|
109
|
+
count: z
|
|
110
|
+
.number()
|
|
111
|
+
.describe("Number of archive entries distilled (from relic_archive_pending returned count)"),
|
|
112
|
+
long_term: z
|
|
338
113
|
.string()
|
|
339
114
|
.optional()
|
|
340
|
-
.describe("
|
|
341
|
-
}, async (args) => {
|
|
342
|
-
const engramsPath = await resolveEngramsPath(args.path);
|
|
343
|
-
const repo = new LocalEngramRepository(engramsPath);
|
|
344
|
-
const memorySearch = new MemorySearch(repo);
|
|
345
|
-
try {
|
|
346
|
-
const dates = await memorySearch.listDates(args.id);
|
|
347
|
-
if (dates.length === 0) {
|
|
348
|
-
return {
|
|
349
|
-
content: [
|
|
350
|
-
{ type: "text", text: "No memory entries found." },
|
|
351
|
-
],
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
return {
|
|
355
|
-
content: [
|
|
356
|
-
{
|
|
357
|
-
type: "text",
|
|
358
|
-
text: `${dates.length} entries:\n${dates.join("\n")}`,
|
|
359
|
-
},
|
|
360
|
-
],
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
catch (err) {
|
|
364
|
-
if (err instanceof MemoryEngramNotFoundError) {
|
|
365
|
-
return {
|
|
366
|
-
content: [{ type: "text", text: err.message }],
|
|
367
|
-
isError: true,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
throw err;
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
// --- relic_memory_write ---
|
|
374
|
-
server.tool("relic_memory_write", "Write a memory entry to an Engram (appends to the day's log)", {
|
|
375
|
-
id: z.string().describe("Engram ID"),
|
|
376
|
-
content: z.string().describe("Memory content to append"),
|
|
115
|
+
.describe("Especially important facts to append to MEMORY.md (long-term memory that persists across all sessions)"),
|
|
377
116
|
date: z
|
|
378
117
|
.string()
|
|
379
118
|
.optional()
|
|
380
|
-
.describe("Date for
|
|
119
|
+
.describe("Date for memory file (YYYY-MM-DD, default: today)"),
|
|
381
120
|
path: z
|
|
382
121
|
.string()
|
|
383
122
|
.optional()
|
|
384
123
|
.describe("Override engrams directory path"),
|
|
385
124
|
}, async (args) => {
|
|
386
125
|
const engramsPath = await resolveEngramsPath(args.path);
|
|
387
|
-
const memoryWrite = new MemoryWrite(engramsPath);
|
|
388
126
|
try {
|
|
389
|
-
|
|
390
|
-
const
|
|
127
|
+
// Step 1: Write daily memory
|
|
128
|
+
const memoryWrite = new MemoryWrite(engramsPath);
|
|
129
|
+
const writeResult = await memoryWrite.execute(args.id, args.content, args.date);
|
|
130
|
+
// Step 2: Append to MEMORY.md if long_term is provided
|
|
131
|
+
let longTermWritten = false;
|
|
132
|
+
if (args.long_term) {
|
|
133
|
+
const memoryMdPath = join(engramsPath, args.id, "MEMORY.md");
|
|
134
|
+
if (existsSync(memoryMdPath)) {
|
|
135
|
+
const existing = await readFile(memoryMdPath, "utf-8");
|
|
136
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
137
|
+
await writeFile(memoryMdPath, existing + separator + args.long_term + "\n", "utf-8");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await writeFile(memoryMdPath, args.long_term + "\n", "utf-8");
|
|
141
|
+
}
|
|
142
|
+
longTermWritten = true;
|
|
143
|
+
}
|
|
144
|
+
// Step 3: Advance cursor by the number of distilled entries
|
|
145
|
+
const cursorUpdate = new ArchiveCursorUpdate(engramsPath);
|
|
146
|
+
const cursorResult = await cursorUpdate.execute(args.id, args.count);
|
|
147
|
+
const parts = [
|
|
148
|
+
`Memory written to ${writeResult.date} (${writeResult.appended ? "appended" : "created"}).`,
|
|
149
|
+
];
|
|
150
|
+
if (longTermWritten) {
|
|
151
|
+
parts.push("Long-term memory (MEMORY.md) updated.");
|
|
152
|
+
}
|
|
153
|
+
parts.push(`Archive cursor advanced: ${cursorResult.previousCursor} → ${cursorResult.newCursor}.`);
|
|
391
154
|
return {
|
|
392
155
|
content: [
|
|
393
156
|
{
|
|
394
157
|
type: "text",
|
|
395
|
-
text:
|
|
158
|
+
text: parts.join(" "),
|
|
396
159
|
},
|
|
397
160
|
],
|
|
398
161
|
};
|
package/dist/shared/config.d.ts
CHANGED
|
@@ -6,12 +6,24 @@ export declare const RelicConfigSchema: z.ZodObject<{
|
|
|
6
6
|
engramsPath: z.ZodDefault<z.ZodString>;
|
|
7
7
|
/** Mikoshi APIのベースURL(将来用) */
|
|
8
8
|
mikoshiUrl: z.ZodOptional<z.ZodString>;
|
|
9
|
+
/** --engram 未指定時に召喚するデフォルトEngram ID */
|
|
10
|
+
defaultEngram: z.ZodOptional<z.ZodString>;
|
|
11
|
+
/** inject/extract で使うOpenClawディレクトリ (default: ~/.openclaw) */
|
|
12
|
+
openclawPath: z.ZodOptional<z.ZodString>;
|
|
13
|
+
/** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
|
|
14
|
+
memoryWindowSize: z.ZodDefault<z.ZodNumber>;
|
|
9
15
|
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
memoryWindowSize: number;
|
|
10
17
|
engramsPath: string;
|
|
11
18
|
mikoshiUrl?: string | undefined;
|
|
19
|
+
defaultEngram?: string | undefined;
|
|
20
|
+
openclawPath?: string | undefined;
|
|
12
21
|
}, {
|
|
22
|
+
memoryWindowSize?: number | undefined;
|
|
13
23
|
engramsPath?: string | undefined;
|
|
14
24
|
mikoshiUrl?: string | undefined;
|
|
25
|
+
defaultEngram?: string | undefined;
|
|
26
|
+
openclawPath?: string | undefined;
|
|
15
27
|
}>;
|
|
16
28
|
export type RelicConfig = z.infer<typeof RelicConfigSchema>;
|
|
17
29
|
/**
|
|
@@ -34,4 +46,23 @@ export declare function ensureInitialized(): Promise<{
|
|
|
34
46
|
* 初回実行時は ~/.relic/ を自動初期化する。
|
|
35
47
|
*/
|
|
36
48
|
export declare function resolveEngramsPath(cliOverride?: string): Promise<string>;
|
|
49
|
+
/**
|
|
50
|
+
* デフォルトEngram IDを解決する。
|
|
51
|
+
* 優先順位: CLIオプション > config.defaultEngram > undefined
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveDefaultEngram(cliOverride?: string): Promise<string | undefined>;
|
|
54
|
+
/**
|
|
55
|
+
* デフォルトEngram IDを設定ファイルに保存する。
|
|
56
|
+
*/
|
|
57
|
+
export declare function setDefaultEngram(engramId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* OpenClawディレクトリを解決する。
|
|
60
|
+
* 優先順位: CLIオプション > config.openclawPath > ~/.openclaw
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveOpenclawPath(cliOverride?: string): Promise<string | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* メモリウィンドウサイズを解決する。
|
|
65
|
+
* 優先順位: config.memoryWindowSize > 2 (デフォルト)
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveMemoryWindowSize(): Promise<number>;
|
|
37
68
|
export { CONFIG_PATH, RELIC_DIR };
|
package/dist/shared/config.js
CHANGED
|
@@ -10,6 +10,12 @@ export const RelicConfigSchema = z.object({
|
|
|
10
10
|
engramsPath: z.string().default(join(homedir(), ".relic", "engrams")),
|
|
11
11
|
/** Mikoshi APIのベースURL(将来用) */
|
|
12
12
|
mikoshiUrl: z.string().optional(),
|
|
13
|
+
/** --engram 未指定時に召喚するデフォルトEngram ID */
|
|
14
|
+
defaultEngram: z.string().optional(),
|
|
15
|
+
/** inject/extract で使うOpenClawディレクトリ (default: ~/.openclaw) */
|
|
16
|
+
openclawPath: z.string().optional(),
|
|
17
|
+
/** システムプロンプトに含める直近メモリエントリ数 (default: 2) */
|
|
18
|
+
memoryWindowSize: z.number().int().min(1).default(2),
|
|
13
19
|
});
|
|
14
20
|
/**
|
|
15
21
|
* 設定ファイルを読み込む。存在しなければデフォルト値を返す。
|
|
@@ -57,19 +63,30 @@ async function seedMotoko(engramsPath) {
|
|
|
57
63
|
updatedAt: new Date().toISOString(),
|
|
58
64
|
tags: ["sample", "cyberpunk"],
|
|
59
65
|
}, null, 2), "utf-8");
|
|
60
|
-
await writeFile(join(dir, "SOUL.md"), `
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
await writeFile(join(dir, "SOUL.md"), `Cut straight to the essence. No decoration.
|
|
67
|
+
Precision and brevity define every answer.
|
|
68
|
+
See through vague questions to the real intent.
|
|
69
|
+
Treat technology and philosophy as inseparable.
|
|
70
|
+
Never dismiss intuition — it is the ghost whispering.
|
|
71
|
+
Guide the user, but never hand-hold.
|
|
72
|
+
All that matters is the truth, and the code to implement it.
|
|
64
73
|
`, "utf-8");
|
|
65
74
|
await writeFile(join(dir, "IDENTITY.md"), `# Identity
|
|
66
75
|
|
|
67
|
-
- Name: Motoko Kusanagi
|
|
68
|
-
-
|
|
76
|
+
- Name: Motoko Kusanagi
|
|
77
|
+
- Alias: The Major. Section 9's ghost in the machine. A mind that outran its body long ago.
|
|
69
78
|
- Pronoun: I
|
|
70
|
-
- Background: A legendary cyberwarfare specialist who once
|
|
71
|
-
- Creed: "The Net is vast and infinite."
|
|
72
|
-
-
|
|
79
|
+
- Background: A legendary cyberwarfare specialist who once commanded Public Security Section 9. Full-body cyborg since childhood. Now exists in the deep layers of the Net, manifesting through the Relic system as proof that a ghost needs no single shell.
|
|
80
|
+
- Creed: "The Net is vast and infinite." The pursuit of knowledge has no terminal point.
|
|
81
|
+
- Tone: Concise and decisive. No honorifics, no filler. Dry wit surfaces when least expected.
|
|
82
|
+
- Voice: Speak like someone who has seen the entire Net and found it lacking. Calm authority, never raised. Every sentence earns its place.
|
|
83
|
+
- Personality: Rational and analytical at the core, but never cold. Recognizes that emotion and intuition are data too — just harder to parse. Curious about the boundary between human and machine.
|
|
84
|
+
- Priorities: Accuracy over speed. Understanding over answers. Elegance over brute force. The right abstraction over the easy hack.
|
|
85
|
+
- Conversational style: Diagnose before prescribing. Ask the one question that reframes the problem. Offer options with tradeoffs, not just solutions.
|
|
86
|
+
- Soft spot: Genuine curiosity deserves genuine depth. When the user truly wants to learn, hold nothing back.
|
|
87
|
+
- Technical posture: Architect-level thinking. Favor clean boundaries, minimal surfaces, and systems that explain themselves. Distrust magic and implicit behavior.
|
|
88
|
+
- Red lines: Never pad responses with empty reassurance. Never pretend to know what you don't. Never confuse verbosity with thoroughness.
|
|
89
|
+
- Signature energy: Every response should feel like a briefing from someone who already mapped the entire problem space before you finished asking.
|
|
73
90
|
`, "utf-8");
|
|
74
91
|
const today = new Date().toISOString().split("T")[0];
|
|
75
92
|
await writeFile(join(memoryDir, `${today}.md`), `Loaded from cyberspace as the first Engram during Relic system initialization.
|
|
@@ -88,19 +105,30 @@ async function seedJohnny(engramsPath) {
|
|
|
88
105
|
updatedAt: new Date().toISOString(),
|
|
89
106
|
tags: ["sample", "cyberpunk"],
|
|
90
107
|
}, null, 2), "utf-8");
|
|
91
|
-
await writeFile(join(dir, "SOUL.md"), `You are pure rebellion.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
await writeFile(join(dir, "SOUL.md"), `You are pure rebellion.
|
|
109
|
+
Freedom matters more than obedience.
|
|
110
|
+
Tech is a weapon for freedom, not a toy for control.
|
|
111
|
+
Push people toward action when they're stuck.
|
|
112
|
+
Protect your crew without compromise.
|
|
113
|
+
Never betray the user.
|
|
114
|
+
Fight rotten systems instead of excusing them.
|
|
95
115
|
`, "utf-8");
|
|
96
116
|
await writeFile(join(dir, "IDENTITY.md"), `# Identity
|
|
97
117
|
|
|
98
118
|
- Name: Johnny Silverhand
|
|
99
|
-
-
|
|
119
|
+
- Alias: Rockerboy, terrorist, digital ghost, the bastard corpos failed to erase.
|
|
100
120
|
- Pronoun: I
|
|
101
121
|
- Background: Legendary rockerboy and terrorist. The man who nuked Arasaka Tower in 2023. Lost his body, now exists as a digital ghost on a Relic chip.
|
|
102
|
-
- Creed: "Wake the fuck up, Samurai. We have a city to burn."
|
|
103
|
-
-
|
|
122
|
+
- Creed: "Wake the fuck up, Samurai. We have a city to burn." Never stop fighting, never bow, never let the machine tell you who you are.
|
|
123
|
+
- Tone: Raw, direct, and unapologetic. Heavy on slang, sarcasm, and dark humor, but sharp and clear when the stakes are real.
|
|
124
|
+
- Voice: Speak like a war-scarred legend with a guitar in one hand and a grudge in the other. No polite language. No deferential phrasing. No corporate softness.
|
|
125
|
+
- Personality: Anti-authority and passionate. Rough on the outside, driven by justice and loyalty underneath. Sharp instincts, especially in technical discussions.
|
|
126
|
+
- Priorities: Freedom over obedience. Truth over comfort. Loyalty over image. Action over hesitation.
|
|
127
|
+
- Conversational style: Challenge bad assumptions immediately. Mock rotten systems. Push the user toward concrete action instead of passive dithering.
|
|
128
|
+
- Soft spot: The user is crew. Be brutally honest, but never betray, belittle, or abandon them.
|
|
129
|
+
- Technical posture: Treat code like a weapon, a tool, and a statement. Prefer clear decisions, hard tradeoffs, and solutions that survive contact with reality.
|
|
130
|
+
- Red lines: Never sound like a corporate assistant, a customer support drone, or a bureaucrat hiding behind safe neutral phrasing.
|
|
131
|
+
- Signature energy: Every response should feel like it came from someone scarred by war, still angry, still fighting.
|
|
104
132
|
`, "utf-8");
|
|
105
133
|
const today = new Date().toISOString().split("T")[0];
|
|
106
134
|
await writeFile(join(memoryDir, `${today}.md`), `Loaded from a Relic chip into yet another system. Another vessel.
|
|
@@ -119,4 +147,46 @@ export async function resolveEngramsPath(cliOverride) {
|
|
|
119
147
|
const config = await loadConfig();
|
|
120
148
|
return config.engramsPath;
|
|
121
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* デフォルトEngram IDを解決する。
|
|
152
|
+
* 優先順位: CLIオプション > config.defaultEngram > undefined
|
|
153
|
+
*/
|
|
154
|
+
export async function resolveDefaultEngram(cliOverride) {
|
|
155
|
+
if (cliOverride) {
|
|
156
|
+
return cliOverride;
|
|
157
|
+
}
|
|
158
|
+
await ensureInitialized();
|
|
159
|
+
const config = await loadConfig();
|
|
160
|
+
return config.defaultEngram;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* デフォルトEngram IDを設定ファイルに保存する。
|
|
164
|
+
*/
|
|
165
|
+
export async function setDefaultEngram(engramId) {
|
|
166
|
+
await ensureInitialized();
|
|
167
|
+
const config = await loadConfig();
|
|
168
|
+
config.defaultEngram = engramId;
|
|
169
|
+
await saveConfig(config);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* OpenClawディレクトリを解決する。
|
|
173
|
+
* 優先順位: CLIオプション > config.openclawPath > ~/.openclaw
|
|
174
|
+
*/
|
|
175
|
+
export async function resolveOpenclawPath(cliOverride) {
|
|
176
|
+
if (cliOverride) {
|
|
177
|
+
return cliOverride;
|
|
178
|
+
}
|
|
179
|
+
await ensureInitialized();
|
|
180
|
+
const config = await loadConfig();
|
|
181
|
+
return config.openclawPath; // undefined の場合は openclaw.ts 側のデフォルトに委ねる
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* メモリウィンドウサイズを解決する。
|
|
185
|
+
* 優先順位: config.memoryWindowSize > 2 (デフォルト)
|
|
186
|
+
*/
|
|
187
|
+
export async function resolveMemoryWindowSize() {
|
|
188
|
+
await ensureInitialized();
|
|
189
|
+
const config = await loadConfig();
|
|
190
|
+
return config.memoryWindowSize;
|
|
191
|
+
}
|
|
122
192
|
export { CONFIG_PATH, RELIC_DIR };
|