@ectplsm/relic 0.1.3 → 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.
- package/README.md +261 -167
- package/dist/adapters/shells/claude-hook.d.ts +6 -3
- package/dist/adapters/shells/claude-hook.js +13 -10
- package/dist/adapters/shells/claude-shell.js +4 -2
- package/dist/adapters/shells/codex-hook.d.ts +6 -3
- package/dist/adapters/shells/codex-hook.js +13 -10
- package/dist/adapters/shells/codex-shell.js +4 -2
- package/dist/adapters/shells/gemini-hook.d.ts +6 -3
- package/dist/adapters/shells/gemini-hook.js +13 -10
- package/dist/adapters/shells/gemini-shell.js +4 -2
- package/dist/core/usecases/extract.d.ts +5 -23
- package/dist/core/usecases/extract.js +19 -115
- package/dist/core/usecases/index.d.ts +3 -3
- package/dist/core/usecases/index.js +3 -3
- package/dist/core/usecases/inject.d.ts +8 -3
- package/dist/core/usecases/inject.js +31 -12
- package/dist/core/usecases/sync.d.ts +44 -20
- package/dist/core/usecases/sync.js +182 -56
- package/dist/interfaces/cli/commands/claw.d.ts +2 -0
- package/dist/interfaces/cli/commands/claw.js +149 -0
- package/dist/interfaces/cli/commands/config.js +6 -6
- package/dist/interfaces/cli/commands/extract.js +7 -12
- package/dist/interfaces/cli/commands/inject.js +2 -2
- package/dist/interfaces/cli/commands/sync.js +24 -68
- package/dist/interfaces/cli/index.js +2 -6
- package/dist/interfaces/mcp/index.js +14 -0
- package/dist/shared/config.d.ts +7 -7
- package/dist/shared/config.js +18 -58
- package/dist/shared/engram-composer.js +5 -3
- package/dist/shared/openclaw.d.ts +25 -3
- package/dist/shared/openclaw.js +48 -4
- package/package.json +3 -2
- package/templates/engrams/johnny/IDENTITY.md +25 -0
- package/templates/engrams/johnny/SOUL.md +35 -0
- package/templates/engrams/motoko/IDENTITY.md +25 -0
- package/templates/engrams/motoko/SOUL.md +38 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export declare const CODEX_HOOK_SCRIPT_PATH: string;
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
3
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
4
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeCodexHookScript(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Codex CLI の Stop フックを settings.json に登録する。
|
|
6
9
|
* 既にセットアップ済みの場合はスキップ。
|
|
7
10
|
*/
|
|
8
11
|
export declare function setupCodexHook(): void;
|
|
@@ -17,8 +17,8 @@ const HOOK_SCRIPT = `#!/usr/bin/env node
|
|
|
17
17
|
// Relic Stop hook for Codex CLI
|
|
18
18
|
// Automatically logs each conversation turn to the Engram archive.
|
|
19
19
|
// Receives Stop hook JSON on stdin.
|
|
20
|
-
const { appendFileSync, existsSync, readFileSync } = require("node:fs");
|
|
21
|
-
const { join } = require("node:path");
|
|
20
|
+
const { appendFileSync, existsSync, mkdirSync, readFileSync } = require("node:fs");
|
|
21
|
+
const { join, dirname } = require("node:path");
|
|
22
22
|
const { homedir } = require("node:os");
|
|
23
23
|
|
|
24
24
|
let raw = "";
|
|
@@ -31,7 +31,7 @@ process.stdin.on("end", () => {
|
|
|
31
31
|
if (!engramId) process.exit(0);
|
|
32
32
|
|
|
33
33
|
const archivePath = join(homedir(), ".relic", "engrams", engramId, "archive.md");
|
|
34
|
-
|
|
34
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
35
35
|
|
|
36
36
|
// Codex Stop hook は last_assistant_message を直接提供する
|
|
37
37
|
const lastResponse = (input.last_assistant_message || "").trim();
|
|
@@ -80,16 +80,19 @@ process.stdin.on("end", () => {
|
|
|
80
80
|
});
|
|
81
81
|
`;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* - ~/.codex/hooks.json に Stop フックを登録
|
|
86
|
-
* 既にセットアップ済みの場合はスキップ。
|
|
83
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
84
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
87
85
|
*/
|
|
88
|
-
export function
|
|
89
|
-
// 1. フックスクリプトを生成
|
|
86
|
+
export function writeCodexHookScript() {
|
|
90
87
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
91
88
|
writeFileSync(CODEX_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
|
|
92
|
-
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Codex CLI の Stop フックを settings.json に登録する。
|
|
92
|
+
* 既にセットアップ済みの場合はスキップ。
|
|
93
|
+
*/
|
|
94
|
+
export function setupCodexHook() {
|
|
95
|
+
// ~/.codex/hooks.json に Stop フックを登録
|
|
93
96
|
const codexDir = join(homedir(), ".codex");
|
|
94
97
|
mkdirSync(codexDir, { recursive: true });
|
|
95
98
|
let hooksConfig = {};
|
|
@@ -2,7 +2,7 @@ import { exec } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { spawnShell } from "./spawn-shell.js";
|
|
4
4
|
import { wrapWithOverride } from "./override-preamble.js";
|
|
5
|
-
import { setupCodexHook, isCodexHookSetup } from "./codex-hook.js";
|
|
5
|
+
import { setupCodexHook, isCodexHookSetup, writeCodexHookScript } from "./codex-hook.js";
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
7
|
/**
|
|
8
8
|
* Codex CLI アダプター
|
|
@@ -29,7 +29,9 @@ export class CodexShell {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
async launch(prompt, options) {
|
|
32
|
-
//
|
|
32
|
+
// フックスクリプトを毎回最新に更新
|
|
33
|
+
writeCodexHookScript();
|
|
34
|
+
// hooks.json への登録は初回のみ
|
|
33
35
|
if (!isCodexHookSetup()) {
|
|
34
36
|
console.log("Setting up Codex CLI Stop hook (first run only)...");
|
|
35
37
|
setupCodexHook();
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export declare const GEMINI_HOOK_SCRIPT_PATH: string;
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
3
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
4
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeGeminiHookScript(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Gemini CLI の AfterAgent フックを settings.json に登録する。
|
|
6
9
|
* 既にセットアップ済みの場合はスキップ。
|
|
7
10
|
*/
|
|
8
11
|
export declare function setupGeminiHook(): void;
|
|
@@ -15,8 +15,8 @@ const HOOK_SCRIPT = `#!/usr/bin/env node
|
|
|
15
15
|
// Relic AfterAgent hook for Gemini CLI
|
|
16
16
|
// Automatically logs each conversation turn to the Engram archive.
|
|
17
17
|
// Receives AfterAgentInput JSON on stdin.
|
|
18
|
-
const { appendFileSync, existsSync } = require("node:fs");
|
|
19
|
-
const { join } = require("node:path");
|
|
18
|
+
const { appendFileSync, existsSync, mkdirSync } = require("node:fs");
|
|
19
|
+
const { join, dirname } = require("node:path");
|
|
20
20
|
const { homedir } = require("node:os");
|
|
21
21
|
|
|
22
22
|
let raw = "";
|
|
@@ -33,7 +33,7 @@ process.stdin.on("end", () => {
|
|
|
33
33
|
if (!prompt && !response) process.exit(0);
|
|
34
34
|
|
|
35
35
|
const archivePath = join(homedir(), ".relic", "engrams", engramId, "archive.md");
|
|
36
|
-
|
|
36
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
37
37
|
|
|
38
38
|
const date = new Date().toISOString().split("T")[0];
|
|
39
39
|
const summary = prompt.slice(0, 80).replace(/\\n/g, " ");
|
|
@@ -46,16 +46,19 @@ process.stdin.on("end", () => {
|
|
|
46
46
|
});
|
|
47
47
|
`;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* - ~/.gemini/settings.json に AfterAgent フックを登録
|
|
52
|
-
* 既にセットアップ済みの場合はスキップ。
|
|
49
|
+
* フックスクリプトを最新の内容で書き出す。
|
|
50
|
+
* 毎回呼ばれ、ソース変更がデプロイされることを保証する。
|
|
53
51
|
*/
|
|
54
|
-
export function
|
|
55
|
-
// 1. フックスクリプトを生成
|
|
52
|
+
export function writeGeminiHookScript() {
|
|
56
53
|
mkdirSync(HOOKS_DIR, { recursive: true });
|
|
57
54
|
writeFileSync(GEMINI_HOOK_SCRIPT_PATH, HOOK_SCRIPT, { encoding: "utf-8", mode: 0o755 });
|
|
58
|
-
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gemini CLI の AfterAgent フックを settings.json に登録する。
|
|
58
|
+
* 既にセットアップ済みの場合はスキップ。
|
|
59
|
+
*/
|
|
60
|
+
export function setupGeminiHook() {
|
|
61
|
+
// ~/.gemini/settings.json にフックを登録
|
|
59
62
|
const geminiDir = join(homedir(), ".gemini");
|
|
60
63
|
mkdirSync(geminiDir, { recursive: true });
|
|
61
64
|
let settings = {};
|
|
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { homedir, tmpdir } from "node:os";
|
|
7
7
|
import { spawnShell, writeTempPrompt } from "./spawn-shell.js";
|
|
8
|
-
import { setupGeminiHook, isGeminiHookSetup } from "./gemini-hook.js";
|
|
8
|
+
import { setupGeminiHook, isGeminiHookSetup, writeGeminiHookScript } from "./gemini-hook.js";
|
|
9
9
|
const execAsync = promisify(exec);
|
|
10
10
|
const RELIC_DIR = join(homedir(), ".relic");
|
|
11
11
|
const GEMINI_DEFAULT_CACHE = join(RELIC_DIR, "gemini-system-default.md");
|
|
@@ -97,7 +97,9 @@ export class GeminiShell {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
async launch(prompt, options) {
|
|
100
|
-
// 1.
|
|
100
|
+
// 1. フックスクリプトを毎回最新に更新
|
|
101
|
+
writeGeminiHookScript();
|
|
102
|
+
// settings.json への登録は初回のみ
|
|
101
103
|
if (!isGeminiHookSetup()) {
|
|
102
104
|
console.log("Setting up Gemini AfterAgent hook (first run only)...");
|
|
103
105
|
setupGeminiHook();
|
|
@@ -4,35 +4,20 @@ export interface ExtractResult {
|
|
|
4
4
|
engramName: string;
|
|
5
5
|
sourcePath: string;
|
|
6
6
|
filesRead: string[];
|
|
7
|
-
memoryMerged: boolean;
|
|
8
7
|
}
|
|
9
8
|
/**
|
|
10
|
-
* Extract — OpenClawワークスペースからEngram
|
|
9
|
+
* Extract — OpenClawワークスペースからEngramを新規作成する
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 既存Engramがある場合:
|
|
15
|
-
* - --force なし → memoryエントリのみマージ(persona部分は変更しない)
|
|
16
|
-
* - --force あり → persona部分を上書き + memoryをマージ
|
|
17
|
-
* 既存Engramがない場合:
|
|
18
|
-
* - 新規作成(全ファイル含む)
|
|
11
|
+
* 初回取り込み専用。Engramが既に存在する場合はエラーを返す。
|
|
12
|
+
* Relicが真のデータソースであり、extractは初期インポートのみを担う。
|
|
19
13
|
*/
|
|
20
14
|
export declare class Extract {
|
|
21
15
|
private readonly repository;
|
|
22
16
|
constructor(repository: EngramRepository);
|
|
23
|
-
execute(
|
|
17
|
+
execute(agentName: string, options?: {
|
|
24
18
|
name?: string;
|
|
25
19
|
openclawDir?: string;
|
|
26
|
-
force?: boolean;
|
|
27
20
|
}): Promise<ExtractResult>;
|
|
28
|
-
/**
|
|
29
|
-
* --force時: persona上書き + memoryマージ
|
|
30
|
-
*/
|
|
31
|
-
private mergeAndSave;
|
|
32
|
-
/**
|
|
33
|
-
* --forceなし時: memoryエントリのみ追記(repositoryのAPIを使用)
|
|
34
|
-
*/
|
|
35
|
-
private mergeMemoryOnly;
|
|
36
21
|
private readFiles;
|
|
37
22
|
}
|
|
38
23
|
export declare class WorkspaceNotFoundError extends Error {
|
|
@@ -41,9 +26,6 @@ export declare class WorkspaceNotFoundError extends Error {
|
|
|
41
26
|
export declare class WorkspaceEmptyError extends Error {
|
|
42
27
|
constructor(path: string);
|
|
43
28
|
}
|
|
44
|
-
export declare class
|
|
45
|
-
constructor(id: string);
|
|
46
|
-
}
|
|
47
|
-
export declare class ExtractNameRequiredError extends Error {
|
|
29
|
+
export declare class AlreadyExtractedError extends Error {
|
|
48
30
|
constructor(id: string);
|
|
49
31
|
}
|
|
@@ -1,69 +1,39 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile, readdir } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { RELIC_FILE_MAP, MEMORY_DIR, resolveWorkspacePath } from "../../shared/openclaw.js";
|
|
5
5
|
/**
|
|
6
|
-
* Extract — OpenClawワークスペースからEngram
|
|
6
|
+
* Extract — OpenClawワークスペースからEngramを新規作成する
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 既存Engramがある場合:
|
|
11
|
-
* - --force なし → memoryエントリのみマージ(persona部分は変更しない)
|
|
12
|
-
* - --force あり → persona部分を上書き + memoryをマージ
|
|
13
|
-
* 既存Engramがない場合:
|
|
14
|
-
* - 新規作成(全ファイル含む)
|
|
8
|
+
* 初回取り込み専用。Engramが既に存在する場合はエラーを返す。
|
|
9
|
+
* Relicが真のデータソースであり、extractは初期インポートのみを担う。
|
|
15
10
|
*/
|
|
16
11
|
export class Extract {
|
|
17
12
|
repository;
|
|
18
13
|
constructor(repository) {
|
|
19
14
|
this.repository = repository;
|
|
20
15
|
}
|
|
21
|
-
async execute(
|
|
22
|
-
const sourcePath =
|
|
16
|
+
async execute(agentName, options) {
|
|
17
|
+
const sourcePath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
23
18
|
if (!existsSync(sourcePath)) {
|
|
24
19
|
throw new WorkspaceNotFoundError(sourcePath);
|
|
25
20
|
}
|
|
21
|
+
// 既存Engramがあればエラー — Relic側が真のデータソース
|
|
22
|
+
const existing = await this.repository.get(agentName);
|
|
23
|
+
if (existing) {
|
|
24
|
+
throw new AlreadyExtractedError(agentName);
|
|
25
|
+
}
|
|
26
26
|
const { files, filesRead } = await this.readFiles(sourcePath);
|
|
27
27
|
if (filesRead.length === 0) {
|
|
28
28
|
throw new WorkspaceEmptyError(sourcePath);
|
|
29
29
|
}
|
|
30
|
-
const
|
|
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
|
-
}
|
|
30
|
+
const engramName = options?.name ?? agentName;
|
|
61
31
|
const now = new Date().toISOString();
|
|
62
32
|
const engram = {
|
|
63
33
|
meta: {
|
|
64
|
-
id:
|
|
34
|
+
id: agentName,
|
|
65
35
|
name: engramName,
|
|
66
|
-
description: `Extracted from OpenClaw
|
|
36
|
+
description: `Extracted from OpenClaw workspace (${agentName})`,
|
|
67
37
|
createdAt: now,
|
|
68
38
|
updatedAt: now,
|
|
69
39
|
tags: ["extracted", "openclaw"],
|
|
@@ -72,76 +42,16 @@ export class Extract {
|
|
|
72
42
|
};
|
|
73
43
|
await this.repository.save(engram);
|
|
74
44
|
return {
|
|
75
|
-
engramId,
|
|
45
|
+
engramId: agentName,
|
|
76
46
|
engramName,
|
|
77
47
|
sourcePath,
|
|
78
48
|
filesRead,
|
|
79
|
-
memoryMerged: false,
|
|
80
49
|
};
|
|
81
50
|
}
|
|
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
51
|
async readFiles(sourcePath) {
|
|
142
52
|
const files = {};
|
|
143
53
|
const filesRead = [];
|
|
144
|
-
for (const [key, filename] of Object.entries(
|
|
54
|
+
for (const [key, filename] of Object.entries(RELIC_FILE_MAP)) {
|
|
145
55
|
const filePath = join(sourcePath, filename);
|
|
146
56
|
if (existsSync(filePath)) {
|
|
147
57
|
files[key] = await readFile(filePath, "utf-8");
|
|
@@ -176,15 +86,9 @@ export class WorkspaceEmptyError extends Error {
|
|
|
176
86
|
this.name = "WorkspaceEmptyError";
|
|
177
87
|
}
|
|
178
88
|
}
|
|
179
|
-
export class
|
|
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 {
|
|
89
|
+
export class AlreadyExtractedError extends Error {
|
|
186
90
|
constructor(id) {
|
|
187
|
-
super(`
|
|
188
|
-
this.name = "
|
|
91
|
+
super(`Engram "${id}" already exists. Relic is the source of truth — use "relic claw inject" to push changes.`);
|
|
92
|
+
this.name = "AlreadyExtractedError";
|
|
189
93
|
}
|
|
190
94
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { Summon, EngramNotFoundError, type SummonResult } from "./summon.js";
|
|
2
2
|
export { ListEngrams } from "./list-engrams.js";
|
|
3
3
|
export { Init, type InitResult } from "./init.js";
|
|
4
|
-
export { Inject, InjectEngramNotFoundError,
|
|
5
|
-
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError,
|
|
4
|
+
export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, type InjectResult, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, type ExtractResult, } from "./extract.js";
|
|
6
6
|
export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteResult, } from "./memory-write.js";
|
|
7
|
-
export { Sync,
|
|
7
|
+
export { Sync, SyncOpenclawDirNotFoundError, type SyncTarget, type SyncResult, type SyncInitialResult, } from "./sync.js";
|
|
8
8
|
export { ArchivePending, ArchivePendingEngramNotFoundError, type ArchivePendingResult, } from "./archive-pending.js";
|
|
9
9
|
export { ArchiveCursorUpdate, ArchiveCursorUpdateEngramNotFoundError, type ArchiveCursorUpdateResult, } from "./archive-cursor-update.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { Summon, EngramNotFoundError } from "./summon.js";
|
|
2
2
|
export { ListEngrams } from "./list-engrams.js";
|
|
3
3
|
export { Init } from "./init.js";
|
|
4
|
-
export { Inject, InjectEngramNotFoundError,
|
|
5
|
-
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError,
|
|
4
|
+
export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, } from "./extract.js";
|
|
6
6
|
export { MemoryWrite, MemoryWriteEngramNotFoundError, } from "./memory-write.js";
|
|
7
|
-
export { Sync,
|
|
7
|
+
export { Sync, SyncOpenclawDirNotFoundError, } from "./sync.js";
|
|
8
8
|
export { ArchivePending, ArchivePendingEngramNotFoundError, } from "./archive-pending.js";
|
|
9
9
|
export { ArchiveCursorUpdate, ArchiveCursorUpdateEngramNotFoundError, } from "./archive-cursor-update.js";
|
|
@@ -8,7 +8,8 @@ export interface InjectResult {
|
|
|
8
8
|
/**
|
|
9
9
|
* Inject — EngramのファイルをOpenClawワークスペースに注入する
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* OpenClawではエージェントごとに workspace-<name>/ を使い、
|
|
12
|
+
* デフォルト(main)エージェントのみ workspace/ を使う。
|
|
12
13
|
* memoryEntries はOpenClaw側の管理に委ねるため注入しない。
|
|
13
14
|
*/
|
|
14
15
|
export declare class Inject {
|
|
@@ -17,12 +18,16 @@ export declare class Inject {
|
|
|
17
18
|
execute(engramId: string, options?: {
|
|
18
19
|
to?: string;
|
|
19
20
|
openclawDir?: string;
|
|
21
|
+
mergeIdentity?: boolean;
|
|
20
22
|
}): Promise<InjectResult>;
|
|
21
23
|
private writeFiles;
|
|
22
24
|
}
|
|
23
25
|
export declare class InjectEngramNotFoundError extends Error {
|
|
24
26
|
constructor(id: string);
|
|
25
27
|
}
|
|
26
|
-
export declare class
|
|
27
|
-
constructor(
|
|
28
|
+
export declare class InjectClawDirNotFoundError extends Error {
|
|
29
|
+
constructor(path: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class InjectWorkspaceNotFoundError extends Error {
|
|
32
|
+
constructor(engramId: string);
|
|
28
33
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { INJECT_FILE_MAP, resolveWorkspacePath } from "../../shared/openclaw.js";
|
|
5
5
|
/**
|
|
6
6
|
* Inject — EngramのファイルをOpenClawワークスペースに注入する
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* OpenClawではエージェントごとに workspace-<name>/ を使い、
|
|
9
|
+
* デフォルト(main)エージェントのみ workspace/ を使う。
|
|
9
10
|
* memoryEntries はOpenClaw側の管理に委ねるため注入しない。
|
|
10
11
|
*/
|
|
11
12
|
export class Inject {
|
|
@@ -18,12 +19,16 @@ export class Inject {
|
|
|
18
19
|
if (!engram) {
|
|
19
20
|
throw new InjectEngramNotFoundError(engramId);
|
|
20
21
|
}
|
|
22
|
+
// ベースディレクトリ(--dir)の存在チェック
|
|
23
|
+
if (options?.openclawDir && !existsSync(options.openclawDir)) {
|
|
24
|
+
throw new InjectClawDirNotFoundError(options.openclawDir);
|
|
25
|
+
}
|
|
21
26
|
const agentName = options?.to ?? engramId;
|
|
22
|
-
const targetPath =
|
|
27
|
+
const targetPath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
23
28
|
if (!existsSync(targetPath)) {
|
|
24
|
-
throw new
|
|
29
|
+
throw new InjectWorkspaceNotFoundError(agentName);
|
|
25
30
|
}
|
|
26
|
-
const filesWritten = await this.writeFiles(targetPath, engram.files);
|
|
31
|
+
const filesWritten = await this.writeFiles(targetPath, engram.files, options?.mergeIdentity ?? false);
|
|
27
32
|
return {
|
|
28
33
|
engramId: engram.meta.id,
|
|
29
34
|
engramName: engram.meta.name,
|
|
@@ -31,10 +36,18 @@ export class Inject {
|
|
|
31
36
|
filesWritten,
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
|
-
async writeFiles(targetPath, files) {
|
|
39
|
+
async writeFiles(targetPath, files, mergeIdentity) {
|
|
35
40
|
const written = [];
|
|
36
|
-
for (const [key, filename] of Object.entries(
|
|
37
|
-
|
|
41
|
+
for (const [key, filename] of Object.entries(INJECT_FILE_MAP)) {
|
|
42
|
+
// --merge-identity: skip IDENTITY.md (merged into SOUL.md below)
|
|
43
|
+
if (mergeIdentity && key === "identity") {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
let content = files[key];
|
|
47
|
+
// --merge-identity: append IDENTITY.md content to SOUL.md
|
|
48
|
+
if (mergeIdentity && key === "soul" && files.identity) {
|
|
49
|
+
content = content + "\n" + files.identity;
|
|
50
|
+
}
|
|
38
51
|
if (content !== undefined) {
|
|
39
52
|
await writeFile(join(targetPath, filename), content, "utf-8");
|
|
40
53
|
written.push(filename);
|
|
@@ -49,9 +62,15 @@ export class InjectEngramNotFoundError extends Error {
|
|
|
49
62
|
this.name = "InjectEngramNotFoundError";
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
|
-
export class
|
|
53
|
-
constructor(
|
|
54
|
-
super(`
|
|
55
|
-
this.name = "
|
|
65
|
+
export class InjectClawDirNotFoundError extends Error {
|
|
66
|
+
constructor(path) {
|
|
67
|
+
super(`Claw directory not found at ${path}`);
|
|
68
|
+
this.name = "InjectClawDirNotFoundError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export class InjectWorkspaceNotFoundError extends Error {
|
|
72
|
+
constructor(engramId) {
|
|
73
|
+
super(`OpenClaw agent "${engramId}" has not been created yet. Run "openclaw agents add ${engramId}" first, then try again.`);
|
|
74
|
+
this.name = "InjectWorkspaceNotFoundError";
|
|
56
75
|
}
|
|
57
76
|
}
|
|
@@ -1,40 +1,64 @@
|
|
|
1
1
|
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
2
|
export interface SyncTarget {
|
|
3
3
|
engramId: string;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
workspacePath: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SyncResult {
|
|
7
|
+
engramId: string;
|
|
8
|
+
memoryFilesMerged: number;
|
|
9
|
+
memoryIndexMerged: boolean;
|
|
10
|
+
userMerged: boolean;
|
|
6
11
|
}
|
|
7
12
|
export interface SyncInitialResult {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
targets: SyncTarget[];
|
|
13
|
+
synced: SyncResult[];
|
|
14
|
+
skipped: string[];
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
|
-
* Sync — OpenClaw
|
|
14
|
-
* 一致するEngramをinject / memoryをextractする。
|
|
17
|
+
* Sync — Relic Engram と OpenClaw workspace 間で memory を双方向マージする。
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
19
|
+
* 対象: 同名の engram/agent が両方に存在するペアのみ。
|
|
20
|
+
* マージ対象: memory/*.md と MEMORY.md
|
|
21
|
+
* マージ結果は両方に書き戻される。
|
|
18
22
|
*/
|
|
19
23
|
export declare class Sync {
|
|
20
24
|
private readonly repository;
|
|
21
|
-
private readonly
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
private readonly engramsPath;
|
|
26
|
+
constructor(repository: EngramRepository, engramsPath: string);
|
|
27
|
+
execute(openclawDir?: string): Promise<SyncInitialResult>;
|
|
28
|
+
/**
|
|
29
|
+
* 1ペア分の memory マージ(inject 後の自動 sync 等でも使用)
|
|
30
|
+
*/
|
|
31
|
+
syncPair(target: SyncTarget): Promise<SyncResult>;
|
|
32
|
+
/**
|
|
33
|
+
* memory/*.md を双方向マージ
|
|
34
|
+
*/
|
|
35
|
+
private mergeMemoryEntries;
|
|
36
|
+
/**
|
|
37
|
+
* MEMORY.md を双方向マージ
|
|
38
|
+
*/
|
|
39
|
+
private mergeMemoryIndex;
|
|
40
|
+
/**
|
|
41
|
+
* 単一ファイルの双方向マージ(MEMORY.md, USER.md 等)
|
|
42
|
+
*/
|
|
43
|
+
private mergeSingleFile;
|
|
44
|
+
/**
|
|
45
|
+
* 2つのテキスト内容をマージする。
|
|
46
|
+
* 重複行を除外しつつ、両方の内容を結合する。
|
|
47
|
+
*/
|
|
48
|
+
private mergeContents;
|
|
24
49
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Engramが存在するagentにはinject、全agentからmemoryをextractする。
|
|
50
|
+
* memory/ ディレクトリから日付 → 内容のマップを読む
|
|
27
51
|
*/
|
|
28
|
-
|
|
52
|
+
private readMemoryDir;
|
|
29
53
|
/**
|
|
30
|
-
*
|
|
54
|
+
* 同名の engram/agent が両方に存在するペアを返す
|
|
31
55
|
*/
|
|
32
|
-
|
|
56
|
+
private scanMatchingPairs;
|
|
33
57
|
/**
|
|
34
|
-
*
|
|
58
|
+
* 全 workspace のエージェント名一覧
|
|
35
59
|
*/
|
|
36
|
-
private
|
|
60
|
+
private scanAllWorkspaces;
|
|
37
61
|
}
|
|
38
|
-
export declare class
|
|
62
|
+
export declare class SyncOpenclawDirNotFoundError extends Error {
|
|
39
63
|
constructor(path: string);
|
|
40
64
|
}
|