@ectplsm/relic 0.1.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.
- package/LICENCE.md +21 -0
- package/README.md +324 -0
- package/dist/adapters/local/index.d.ts +1 -0
- package/dist/adapters/local/index.js +1 -0
- package/dist/adapters/local/local-engram-repository.d.ts +28 -0
- package/dist/adapters/local/local-engram-repository.js +130 -0
- package/dist/adapters/shells/claude-shell.d.ts +13 -0
- package/dist/adapters/shells/claude-shell.js +33 -0
- package/dist/adapters/shells/codex-shell.d.ts +14 -0
- package/dist/adapters/shells/codex-shell.js +34 -0
- package/dist/adapters/shells/copilot-shell.d.ts +14 -0
- package/dist/adapters/shells/copilot-shell.js +43 -0
- package/dist/adapters/shells/gemini-shell.d.ts +14 -0
- package/dist/adapters/shells/gemini-shell.js +43 -0
- package/dist/adapters/shells/index.d.ts +4 -0
- package/dist/adapters/shells/index.js +4 -0
- package/dist/adapters/shells/override-preamble.d.ts +8 -0
- package/dist/adapters/shells/override-preamble.js +19 -0
- package/dist/adapters/shells/spawn-shell.d.ts +16 -0
- package/dist/adapters/shells/spawn-shell.js +47 -0
- package/dist/core/entities/engram.d.ts +180 -0
- package/dist/core/entities/engram.js +67 -0
- package/dist/core/entities/index.d.ts +1 -0
- package/dist/core/entities/index.js +1 -0
- package/dist/core/ports/engram-repository.d.ts +17 -0
- package/dist/core/ports/engram-repository.js +1 -0
- package/dist/core/ports/index.d.ts +2 -0
- package/dist/core/ports/index.js +1 -0
- package/dist/core/ports/shell-launcher.d.ts +27 -0
- package/dist/core/ports/shell-launcher.js +1 -0
- package/dist/core/usecases/extract.d.ts +49 -0
- package/dist/core/usecases/extract.js +190 -0
- package/dist/core/usecases/index.d.ts +8 -0
- package/dist/core/usecases/index.js +8 -0
- package/dist/core/usecases/init.d.ts +19 -0
- package/dist/core/usecases/init.js +19 -0
- package/dist/core/usecases/inject.d.ts +28 -0
- package/dist/core/usecases/inject.js +57 -0
- package/dist/core/usecases/list-engrams.d.ts +10 -0
- package/dist/core/usecases/list-engrams.js +12 -0
- package/dist/core/usecases/memory-search.d.ts +23 -0
- package/dist/core/usecases/memory-search.js +59 -0
- package/dist/core/usecases/memory-write.d.ts +20 -0
- package/dist/core/usecases/memory-write.js +47 -0
- package/dist/core/usecases/summon.d.ts +23 -0
- package/dist/core/usecases/summon.js +31 -0
- package/dist/core/usecases/sync.d.ts +40 -0
- package/dist/core/usecases/sync.js +94 -0
- package/dist/interfaces/cli/commands/extract.d.ts +2 -0
- package/dist/interfaces/cli/commands/extract.js +41 -0
- package/dist/interfaces/cli/commands/init.d.ts +2 -0
- package/dist/interfaces/cli/commands/init.js +19 -0
- package/dist/interfaces/cli/commands/inject.d.ts +2 -0
- package/dist/interfaces/cli/commands/inject.js +33 -0
- package/dist/interfaces/cli/commands/list.d.ts +2 -0
- package/dist/interfaces/cli/commands/list.js +30 -0
- package/dist/interfaces/cli/commands/shell.d.ts +2 -0
- package/dist/interfaces/cli/commands/shell.js +69 -0
- package/dist/interfaces/cli/commands/show.d.ts +2 -0
- package/dist/interfaces/cli/commands/show.js +26 -0
- package/dist/interfaces/cli/commands/sync.d.ts +2 -0
- package/dist/interfaces/cli/commands/sync.js +91 -0
- package/dist/interfaces/cli/index.d.ts +2 -0
- package/dist/interfaces/cli/index.js +22 -0
- package/dist/interfaces/mcp/index.d.ts +2 -0
- package/dist/interfaces/mcp/index.js +418 -0
- package/dist/shared/config.d.ts +37 -0
- package/dist/shared/config.js +122 -0
- package/dist/shared/engram-composer.d.ts +18 -0
- package/dist/shared/engram-composer.js +48 -0
- package/dist/shared/openclaw.d.ts +9 -0
- package/dist/shared/openclaw.js +21 -0
- package/package.json +44 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
4
|
+
import { FILE_MAP, MEMORY_DIR, resolveAgentPath } from "../../shared/openclaw.js";
|
|
5
|
+
/**
|
|
6
|
+
* Extract — OpenClawワークスペースからEngramを作成する
|
|
7
|
+
*
|
|
8
|
+
* agent名 = Engram ID の規約に基づき、agents/<engramId>/agent/ から読み取る。
|
|
9
|
+
*
|
|
10
|
+
* 既存Engramがある場合:
|
|
11
|
+
* - --force なし → memoryエントリのみマージ(persona部分は変更しない)
|
|
12
|
+
* - --force あり → persona部分を上書き + memoryをマージ
|
|
13
|
+
* 既存Engramがない場合:
|
|
14
|
+
* - 新規作成(全ファイル含む)
|
|
15
|
+
*/
|
|
16
|
+
export class Extract {
|
|
17
|
+
repository;
|
|
18
|
+
constructor(repository) {
|
|
19
|
+
this.repository = repository;
|
|
20
|
+
}
|
|
21
|
+
async execute(engramId, options) {
|
|
22
|
+
const sourcePath = resolveAgentPath(engramId, options?.openclawDir);
|
|
23
|
+
if (!existsSync(sourcePath)) {
|
|
24
|
+
throw new WorkspaceNotFoundError(sourcePath);
|
|
25
|
+
}
|
|
26
|
+
const { files, filesRead } = await this.readFiles(sourcePath);
|
|
27
|
+
if (filesRead.length === 0) {
|
|
28
|
+
throw new WorkspaceEmptyError(sourcePath);
|
|
29
|
+
}
|
|
30
|
+
const existing = await this.repository.get(engramId);
|
|
31
|
+
if (existing) {
|
|
32
|
+
if (options?.force) {
|
|
33
|
+
// --force: persona上書き + memoryマージ
|
|
34
|
+
const merged = await this.mergeAndSave(existing, files, options.name ?? existing.meta.name);
|
|
35
|
+
return {
|
|
36
|
+
engramId,
|
|
37
|
+
engramName: options.name ?? existing.meta.name,
|
|
38
|
+
sourcePath,
|
|
39
|
+
filesRead,
|
|
40
|
+
memoryMerged: merged,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// --forceなし: memoryのみマージ
|
|
44
|
+
if (!files.memoryEntries || Object.keys(files.memoryEntries).length === 0) {
|
|
45
|
+
throw new EngramAlreadyExistsError(engramId);
|
|
46
|
+
}
|
|
47
|
+
const merged = await this.mergeMemoryOnly(engramId, files.memoryEntries);
|
|
48
|
+
return {
|
|
49
|
+
engramId,
|
|
50
|
+
engramName: existing.meta.name,
|
|
51
|
+
sourcePath,
|
|
52
|
+
filesRead,
|
|
53
|
+
memoryMerged: merged,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// 新規作成 — nameは必須
|
|
57
|
+
const engramName = options?.name;
|
|
58
|
+
if (!engramName) {
|
|
59
|
+
throw new ExtractNameRequiredError(engramId);
|
|
60
|
+
}
|
|
61
|
+
const now = new Date().toISOString();
|
|
62
|
+
const engram = {
|
|
63
|
+
meta: {
|
|
64
|
+
id: engramId,
|
|
65
|
+
name: engramName,
|
|
66
|
+
description: `Extracted from OpenClaw agent (${engramId})`,
|
|
67
|
+
createdAt: now,
|
|
68
|
+
updatedAt: now,
|
|
69
|
+
tags: ["extracted", "openclaw"],
|
|
70
|
+
},
|
|
71
|
+
files,
|
|
72
|
+
};
|
|
73
|
+
await this.repository.save(engram);
|
|
74
|
+
return {
|
|
75
|
+
engramId,
|
|
76
|
+
engramName,
|
|
77
|
+
sourcePath,
|
|
78
|
+
filesRead,
|
|
79
|
+
memoryMerged: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* --force時: persona上書き + memoryマージ
|
|
84
|
+
*/
|
|
85
|
+
async mergeAndSave(existing, newFiles, engramName) {
|
|
86
|
+
const mergedFiles = { ...newFiles };
|
|
87
|
+
// memoryEntriesをマージ
|
|
88
|
+
let memoryMerged = false;
|
|
89
|
+
if (newFiles.memoryEntries) {
|
|
90
|
+
mergedFiles.memoryEntries = { ...existing.files.memoryEntries };
|
|
91
|
+
for (const [date, content] of Object.entries(newFiles.memoryEntries)) {
|
|
92
|
+
const existingContent = mergedFiles.memoryEntries?.[date];
|
|
93
|
+
if (existingContent) {
|
|
94
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
95
|
+
mergedFiles.memoryEntries[date] = existingContent + separator + content;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
mergedFiles.memoryEntries = mergedFiles.memoryEntries ?? {};
|
|
99
|
+
mergedFiles.memoryEntries[date] = content;
|
|
100
|
+
}
|
|
101
|
+
memoryMerged = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const updatedEngram = {
|
|
105
|
+
meta: {
|
|
106
|
+
...existing.meta,
|
|
107
|
+
name: engramName,
|
|
108
|
+
updatedAt: new Date().toISOString(),
|
|
109
|
+
},
|
|
110
|
+
files: mergedFiles,
|
|
111
|
+
};
|
|
112
|
+
await this.repository.save(updatedEngram);
|
|
113
|
+
return memoryMerged;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* --forceなし時: memoryエントリのみ追記(repositoryのAPIを使用)
|
|
117
|
+
*/
|
|
118
|
+
async mergeMemoryOnly(engramId, newEntries) {
|
|
119
|
+
const existing = await this.repository.get(engramId);
|
|
120
|
+
if (!existing)
|
|
121
|
+
return false;
|
|
122
|
+
const mergedEntries = { ...existing.files.memoryEntries };
|
|
123
|
+
for (const [date, content] of Object.entries(newEntries)) {
|
|
124
|
+
const existingContent = mergedEntries[date];
|
|
125
|
+
if (existingContent) {
|
|
126
|
+
const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
|
|
127
|
+
mergedEntries[date] = existingContent + separator + content;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
mergedEntries[date] = content;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const updatedEngram = {
|
|
134
|
+
...existing,
|
|
135
|
+
meta: { ...existing.meta, updatedAt: new Date().toISOString() },
|
|
136
|
+
files: { ...existing.files, memoryEntries: mergedEntries },
|
|
137
|
+
};
|
|
138
|
+
await this.repository.save(updatedEngram);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
async readFiles(sourcePath) {
|
|
142
|
+
const files = {};
|
|
143
|
+
const filesRead = [];
|
|
144
|
+
for (const [key, filename] of Object.entries(FILE_MAP)) {
|
|
145
|
+
const filePath = join(sourcePath, filename);
|
|
146
|
+
if (existsSync(filePath)) {
|
|
147
|
+
files[key] = await readFile(filePath, "utf-8");
|
|
148
|
+
filesRead.push(filename);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const memoryDir = join(sourcePath, MEMORY_DIR);
|
|
152
|
+
if (existsSync(memoryDir)) {
|
|
153
|
+
const entries = await readdir(memoryDir);
|
|
154
|
+
const mdFiles = entries.filter((f) => f.endsWith(".md")).sort();
|
|
155
|
+
if (mdFiles.length > 0) {
|
|
156
|
+
files.memoryEntries = {};
|
|
157
|
+
for (const mdFile of mdFiles) {
|
|
158
|
+
const date = mdFile.replace(/\.md$/, "");
|
|
159
|
+
files.memoryEntries[date] = await readFile(join(memoryDir, mdFile), "utf-8");
|
|
160
|
+
filesRead.push(`${MEMORY_DIR}/${mdFile}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { files: files, filesRead };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export class WorkspaceNotFoundError extends Error {
|
|
168
|
+
constructor(path) {
|
|
169
|
+
super(`OpenClaw workspace not found at ${path}`);
|
|
170
|
+
this.name = "WorkspaceNotFoundError";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export class WorkspaceEmptyError extends Error {
|
|
174
|
+
constructor(path) {
|
|
175
|
+
super(`No workspace files found at ${path}`);
|
|
176
|
+
this.name = "WorkspaceEmptyError";
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export class EngramAlreadyExistsError extends Error {
|
|
180
|
+
constructor(id) {
|
|
181
|
+
super(`Engram "${id}" already exists. Use --force to overwrite.`);
|
|
182
|
+
this.name = "EngramAlreadyExistsError";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export class ExtractNameRequiredError extends Error {
|
|
186
|
+
constructor(id) {
|
|
187
|
+
super(`No existing Engram "${id}" found. --name is required for new Engrams.`);
|
|
188
|
+
this.name = "ExtractNameRequiredError";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Summon, EngramNotFoundError, type SummonResult } from "./summon.js";
|
|
2
|
+
export { ListEngrams } from "./list-engrams.js";
|
|
3
|
+
export { Init, type InitResult } from "./init.js";
|
|
4
|
+
export { Inject, InjectEngramNotFoundError, InjectAgentNotFoundError, type InjectResult, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, EngramAlreadyExistsError, ExtractNameRequiredError, type ExtractResult, } from "./extract.js";
|
|
6
|
+
export { MemorySearch, MemoryEngramNotFoundError, type MemorySearchResult, } from "./memory-search.js";
|
|
7
|
+
export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteResult, } from "./memory-write.js";
|
|
8
|
+
export { Sync, SyncAgentsDirNotFoundError, type SyncTarget, type SyncInitialResult, } from "./sync.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Summon, EngramNotFoundError } from "./summon.js";
|
|
2
|
+
export { ListEngrams } from "./list-engrams.js";
|
|
3
|
+
export { Init } from "./init.js";
|
|
4
|
+
export { Inject, InjectEngramNotFoundError, InjectAgentNotFoundError, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, EngramAlreadyExistsError, ExtractNameRequiredError, } from "./extract.js";
|
|
6
|
+
export { MemorySearch, MemoryEngramNotFoundError, } from "./memory-search.js";
|
|
7
|
+
export { MemoryWrite, MemoryWriteEngramNotFoundError, } from "./memory-write.js";
|
|
8
|
+
export { Sync, SyncAgentsDirNotFoundError, } from "./sync.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface InitResult {
|
|
2
|
+
/** 新規作成されたか、既に存在していたか */
|
|
3
|
+
created: boolean;
|
|
4
|
+
/** ~/.relic ディレクトリパス */
|
|
5
|
+
relicDir: string;
|
|
6
|
+
/** config.json パス */
|
|
7
|
+
configPath: string;
|
|
8
|
+
/** engrams ディレクトリパス */
|
|
9
|
+
engramsPath: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Init — ~/.relic/ を初期化する
|
|
13
|
+
*
|
|
14
|
+
* `relic init` で明示的に呼ばれるほか、
|
|
15
|
+
* 他のコマンド実行時にも resolveEngramsPath() 経由で自動的に呼ばれる。
|
|
16
|
+
*/
|
|
17
|
+
export declare class Init {
|
|
18
|
+
execute(): Promise<InitResult>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ensureInitialized, loadConfig, RELIC_DIR, CONFIG_PATH } from "../../shared/config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Init — ~/.relic/ を初期化する
|
|
4
|
+
*
|
|
5
|
+
* `relic init` で明示的に呼ばれるほか、
|
|
6
|
+
* 他のコマンド実行時にも resolveEngramsPath() 経由で自動的に呼ばれる。
|
|
7
|
+
*/
|
|
8
|
+
export class Init {
|
|
9
|
+
async execute() {
|
|
10
|
+
const { created } = await ensureInitialized();
|
|
11
|
+
const config = await loadConfig();
|
|
12
|
+
return {
|
|
13
|
+
created,
|
|
14
|
+
relicDir: RELIC_DIR,
|
|
15
|
+
configPath: CONFIG_PATH,
|
|
16
|
+
engramsPath: config.engramsPath,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export interface InjectResult {
|
|
3
|
+
engramId: string;
|
|
4
|
+
engramName: string;
|
|
5
|
+
targetPath: string;
|
|
6
|
+
filesWritten: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Inject — EngramのファイルをOpenClawワークスペースに注入する
|
|
10
|
+
*
|
|
11
|
+
* agent名 = Engram ID の規約に基づき、agents/<engramId>/agent/ に書き込む。
|
|
12
|
+
* memoryEntries はOpenClaw側の管理に委ねるため注入しない。
|
|
13
|
+
*/
|
|
14
|
+
export declare class Inject {
|
|
15
|
+
private readonly repository;
|
|
16
|
+
constructor(repository: EngramRepository);
|
|
17
|
+
execute(engramId: string, options?: {
|
|
18
|
+
to?: string;
|
|
19
|
+
openclawDir?: string;
|
|
20
|
+
}): Promise<InjectResult>;
|
|
21
|
+
private writeFiles;
|
|
22
|
+
}
|
|
23
|
+
export declare class InjectEngramNotFoundError extends Error {
|
|
24
|
+
constructor(id: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class InjectAgentNotFoundError extends Error {
|
|
27
|
+
constructor(engramId: string, path: string);
|
|
28
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { writeFile } from "node:fs/promises";
|
|
4
|
+
import { FILE_MAP, resolveAgentPath } from "../../shared/openclaw.js";
|
|
5
|
+
/**
|
|
6
|
+
* Inject — EngramのファイルをOpenClawワークスペースに注入する
|
|
7
|
+
*
|
|
8
|
+
* agent名 = Engram ID の規約に基づき、agents/<engramId>/agent/ に書き込む。
|
|
9
|
+
* memoryEntries はOpenClaw側の管理に委ねるため注入しない。
|
|
10
|
+
*/
|
|
11
|
+
export class Inject {
|
|
12
|
+
repository;
|
|
13
|
+
constructor(repository) {
|
|
14
|
+
this.repository = repository;
|
|
15
|
+
}
|
|
16
|
+
async execute(engramId, options) {
|
|
17
|
+
const engram = await this.repository.get(engramId);
|
|
18
|
+
if (!engram) {
|
|
19
|
+
throw new InjectEngramNotFoundError(engramId);
|
|
20
|
+
}
|
|
21
|
+
const agentName = options?.to ?? engramId;
|
|
22
|
+
const targetPath = resolveAgentPath(agentName, options?.openclawDir);
|
|
23
|
+
if (!existsSync(targetPath)) {
|
|
24
|
+
throw new InjectAgentNotFoundError(agentName, targetPath);
|
|
25
|
+
}
|
|
26
|
+
const filesWritten = await this.writeFiles(targetPath, engram.files);
|
|
27
|
+
return {
|
|
28
|
+
engramId: engram.meta.id,
|
|
29
|
+
engramName: engram.meta.name,
|
|
30
|
+
targetPath,
|
|
31
|
+
filesWritten,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async writeFiles(targetPath, files) {
|
|
35
|
+
const written = [];
|
|
36
|
+
for (const [key, filename] of Object.entries(FILE_MAP)) {
|
|
37
|
+
const content = files[key];
|
|
38
|
+
if (content !== undefined) {
|
|
39
|
+
await writeFile(join(targetPath, filename), content, "utf-8");
|
|
40
|
+
written.push(filename);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return written;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class InjectEngramNotFoundError extends Error {
|
|
47
|
+
constructor(id) {
|
|
48
|
+
super(`Engram "${id}" not found`);
|
|
49
|
+
this.name = "InjectEngramNotFoundError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export class InjectAgentNotFoundError extends Error {
|
|
53
|
+
constructor(engramId, path) {
|
|
54
|
+
super(`OpenClaw agent "${engramId}" not found at ${path}. The agent must exist before injecting.`);
|
|
55
|
+
this.name = "InjectAgentNotFoundError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EngramMeta } from "../entities/engram.js";
|
|
2
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
3
|
+
/**
|
|
4
|
+
* ListEngrams — Mikoshiに安置された全Engramの一覧を取得する
|
|
5
|
+
*/
|
|
6
|
+
export declare class ListEngrams {
|
|
7
|
+
private readonly repository;
|
|
8
|
+
constructor(repository: EngramRepository);
|
|
9
|
+
execute(): Promise<EngramMeta[]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export interface MemorySearchResult {
|
|
3
|
+
date: string;
|
|
4
|
+
content: string;
|
|
5
|
+
/** Matched lines (for keyword search) */
|
|
6
|
+
matchedLines: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* MemorySearch — Engramのメモリエントリをキーワード検索する
|
|
10
|
+
*/
|
|
11
|
+
export declare class MemorySearch {
|
|
12
|
+
private readonly repository;
|
|
13
|
+
constructor(repository: EngramRepository);
|
|
14
|
+
search(engramId: string, query: string, limit?: number): Promise<MemorySearchResult[]>;
|
|
15
|
+
get(engramId: string, date: string): Promise<{
|
|
16
|
+
date: string;
|
|
17
|
+
content: string;
|
|
18
|
+
} | null>;
|
|
19
|
+
listDates(engramId: string): Promise<string[]>;
|
|
20
|
+
}
|
|
21
|
+
export declare class MemoryEngramNotFoundError extends Error {
|
|
22
|
+
constructor(id: string);
|
|
23
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemorySearch — Engramのメモリエントリをキーワード検索する
|
|
3
|
+
*/
|
|
4
|
+
export class MemorySearch {
|
|
5
|
+
repository;
|
|
6
|
+
constructor(repository) {
|
|
7
|
+
this.repository = repository;
|
|
8
|
+
}
|
|
9
|
+
async search(engramId, query, limit = 5) {
|
|
10
|
+
const engram = await this.repository.get(engramId);
|
|
11
|
+
if (!engram) {
|
|
12
|
+
throw new MemoryEngramNotFoundError(engramId);
|
|
13
|
+
}
|
|
14
|
+
if (!engram.files.memoryEntries) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const queryLower = query.toLowerCase();
|
|
18
|
+
const results = [];
|
|
19
|
+
// Search entries from newest to oldest
|
|
20
|
+
const entries = Object.entries(engram.files.memoryEntries).sort(([a], [b]) => b.localeCompare(a));
|
|
21
|
+
for (const [date, content] of entries) {
|
|
22
|
+
const lines = content.split("\n");
|
|
23
|
+
const matchedLines = lines.filter((line) => line.toLowerCase().includes(queryLower));
|
|
24
|
+
if (matchedLines.length > 0) {
|
|
25
|
+
results.push({ date, content, matchedLines });
|
|
26
|
+
}
|
|
27
|
+
if (results.length >= limit)
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
32
|
+
async get(engramId, date) {
|
|
33
|
+
const engram = await this.repository.get(engramId);
|
|
34
|
+
if (!engram) {
|
|
35
|
+
throw new MemoryEngramNotFoundError(engramId);
|
|
36
|
+
}
|
|
37
|
+
const content = engram.files.memoryEntries?.[date];
|
|
38
|
+
if (!content) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return { date, content };
|
|
42
|
+
}
|
|
43
|
+
async listDates(engramId) {
|
|
44
|
+
const engram = await this.repository.get(engramId);
|
|
45
|
+
if (!engram) {
|
|
46
|
+
throw new MemoryEngramNotFoundError(engramId);
|
|
47
|
+
}
|
|
48
|
+
if (!engram.files.memoryEntries) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return Object.keys(engram.files.memoryEntries).sort((a, b) => b.localeCompare(a));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export class MemoryEngramNotFoundError extends Error {
|
|
55
|
+
constructor(id) {
|
|
56
|
+
super(`Engram "${id}" not found`);
|
|
57
|
+
this.name = "MemoryEngramNotFoundError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface MemoryWriteResult {
|
|
2
|
+
engramId: string;
|
|
3
|
+
date: string;
|
|
4
|
+
appended: boolean;
|
|
5
|
+
path: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* MemoryWrite — Engramのメモリエントリに追記する
|
|
9
|
+
*
|
|
10
|
+
* 指定日付の memory/YYYY-MM-DD.md に内容を append する。
|
|
11
|
+
* ファイルが存在しない場合は新規作成する。
|
|
12
|
+
*/
|
|
13
|
+
export declare class MemoryWrite {
|
|
14
|
+
private readonly engramsPath;
|
|
15
|
+
constructor(engramsPath: string);
|
|
16
|
+
execute(engramId: string, content: string, date?: string): Promise<MemoryWriteResult>;
|
|
17
|
+
}
|
|
18
|
+
export declare class MemoryWriteEngramNotFoundError extends Error {
|
|
19
|
+
constructor(id: string);
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
4
|
+
/**
|
|
5
|
+
* MemoryWrite — Engramのメモリエントリに追記する
|
|
6
|
+
*
|
|
7
|
+
* 指定日付の memory/YYYY-MM-DD.md に内容を append する。
|
|
8
|
+
* ファイルが存在しない場合は新規作成する。
|
|
9
|
+
*/
|
|
10
|
+
export class MemoryWrite {
|
|
11
|
+
engramsPath;
|
|
12
|
+
constructor(engramsPath) {
|
|
13
|
+
this.engramsPath = engramsPath;
|
|
14
|
+
}
|
|
15
|
+
async execute(engramId, content, date) {
|
|
16
|
+
const resolvedDate = date ?? new Date().toISOString().split("T")[0];
|
|
17
|
+
const engramDir = join(this.engramsPath, engramId);
|
|
18
|
+
if (!existsSync(engramDir)) {
|
|
19
|
+
throw new MemoryWriteEngramNotFoundError(engramId);
|
|
20
|
+
}
|
|
21
|
+
const memoryDir = join(engramDir, "memory");
|
|
22
|
+
await mkdir(memoryDir, { recursive: true });
|
|
23
|
+
const filePath = join(memoryDir, `${resolvedDate}.md`);
|
|
24
|
+
let appended = false;
|
|
25
|
+
if (existsSync(filePath)) {
|
|
26
|
+
const existing = await readFile(filePath, "utf-8");
|
|
27
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
28
|
+
await writeFile(filePath, existing + separator + content + "\n", "utf-8");
|
|
29
|
+
appended = true;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await writeFile(filePath, content + "\n", "utf-8");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
engramId,
|
|
36
|
+
date: resolvedDate,
|
|
37
|
+
appended,
|
|
38
|
+
path: filePath,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class MemoryWriteEngramNotFoundError extends Error {
|
|
43
|
+
constructor(id) {
|
|
44
|
+
super(`Engram "${id}" not found`);
|
|
45
|
+
this.name = "MemoryWriteEngramNotFoundError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export interface SummonResult {
|
|
3
|
+
/** Engram ID */
|
|
4
|
+
engramId: string;
|
|
5
|
+
/** Engram表示名 */
|
|
6
|
+
engramName: string;
|
|
7
|
+
/** Shell注入用に結合されたプロンプトテキスト */
|
|
8
|
+
prompt: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Summon — EngramをMikoshiから召喚し、Shell注入用テキストを生成する
|
|
12
|
+
*
|
|
13
|
+
* これがRELICの中核オペレーション。
|
|
14
|
+
* Engramの取得 → Markdown結合 → 注入可能なプロンプト生成 を行う。
|
|
15
|
+
*/
|
|
16
|
+
export declare class Summon {
|
|
17
|
+
private readonly repository;
|
|
18
|
+
constructor(repository: EngramRepository);
|
|
19
|
+
execute(engramId: string): Promise<SummonResult>;
|
|
20
|
+
}
|
|
21
|
+
export declare class EngramNotFoundError extends Error {
|
|
22
|
+
constructor(id: string);
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { composeEngram } from "../../shared/engram-composer.js";
|
|
2
|
+
/**
|
|
3
|
+
* Summon — EngramをMikoshiから召喚し、Shell注入用テキストを生成する
|
|
4
|
+
*
|
|
5
|
+
* これがRELICの中核オペレーション。
|
|
6
|
+
* Engramの取得 → Markdown結合 → 注入可能なプロンプト生成 を行う。
|
|
7
|
+
*/
|
|
8
|
+
export class Summon {
|
|
9
|
+
repository;
|
|
10
|
+
constructor(repository) {
|
|
11
|
+
this.repository = repository;
|
|
12
|
+
}
|
|
13
|
+
async execute(engramId) {
|
|
14
|
+
const engram = await this.repository.get(engramId);
|
|
15
|
+
if (!engram) {
|
|
16
|
+
throw new EngramNotFoundError(engramId);
|
|
17
|
+
}
|
|
18
|
+
const prompt = composeEngram(engram.files);
|
|
19
|
+
return {
|
|
20
|
+
engramId: engram.meta.id,
|
|
21
|
+
engramName: engram.meta.name,
|
|
22
|
+
prompt,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export class EngramNotFoundError extends Error {
|
|
27
|
+
constructor(id) {
|
|
28
|
+
super(`Engram "${id}" not found in Mikoshi`);
|
|
29
|
+
this.name = "EngramNotFoundError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export interface SyncTarget {
|
|
3
|
+
engramId: string;
|
|
4
|
+
agentPath: string;
|
|
5
|
+
hasEngram: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SyncInitialResult {
|
|
8
|
+
injected: string[];
|
|
9
|
+
extracted: string[];
|
|
10
|
+
targets: SyncTarget[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Sync — OpenClawのagentsディレクトリをスキャンし、
|
|
14
|
+
* 一致するEngramをinject / memoryをextractする。
|
|
15
|
+
*
|
|
16
|
+
* 初回スキャン後は、呼び出し側がファイル監視を行い、
|
|
17
|
+
* 変更検知時に syncMemory() を呼ぶ。
|
|
18
|
+
*/
|
|
19
|
+
export declare class Sync {
|
|
20
|
+
private readonly repository;
|
|
21
|
+
private readonly inject;
|
|
22
|
+
private readonly extract;
|
|
23
|
+
constructor(repository: EngramRepository);
|
|
24
|
+
/**
|
|
25
|
+
* OpenClawのagentsディレクトリをスキャンし、
|
|
26
|
+
* Engramが存在するagentにはinject、全agentからmemoryをextractする。
|
|
27
|
+
*/
|
|
28
|
+
initialSync(openclawDir?: string): Promise<SyncInitialResult>;
|
|
29
|
+
/**
|
|
30
|
+
* 特定agentのmemoryを同期(ファイル変更検知時に呼ばれる)
|
|
31
|
+
*/
|
|
32
|
+
syncMemory(engramId: string, openclawDir?: string): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* agentsディレクトリをスキャンしてターゲット一覧を返す
|
|
35
|
+
*/
|
|
36
|
+
private scanAgents;
|
|
37
|
+
}
|
|
38
|
+
export declare class SyncAgentsDirNotFoundError extends Error {
|
|
39
|
+
constructor(path: string);
|
|
40
|
+
}
|