@ectplsm/relic 0.1.4 → 0.2.1
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 +244 -111
- package/dist/core/usecases/extract.d.ts +21 -22
- package/dist/core/usecases/extract.js +80 -122
- package/dist/core/usecases/index.d.ts +3 -3
- package/dist/core/usecases/index.js +3 -3
- package/dist/core/usecases/inject.d.ts +24 -4
- package/dist/core/usecases/inject.js +73 -20
- 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 +280 -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,147 +1,111 @@
|
|
|
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
|
|
22
|
-
const sourcePath =
|
|
16
|
+
async inspectPersona(agentName, options) {
|
|
17
|
+
const sourcePath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
23
18
|
if (!existsSync(sourcePath)) {
|
|
24
19
|
throw new WorkspaceNotFoundError(sourcePath);
|
|
25
20
|
}
|
|
26
|
-
const { files
|
|
27
|
-
|
|
28
|
-
|
|
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);
|
|
21
|
+
const { files } = await this.readFiles(sourcePath);
|
|
22
|
+
const existing = await this.repository.get(agentName);
|
|
23
|
+
if (!existing) {
|
|
48
24
|
return {
|
|
49
|
-
engramId,
|
|
50
|
-
engramName:
|
|
25
|
+
engramId: agentName,
|
|
26
|
+
engramName: options?.name ?? agentName,
|
|
51
27
|
sourcePath,
|
|
52
|
-
|
|
53
|
-
|
|
28
|
+
existing: false,
|
|
29
|
+
name: "missing",
|
|
30
|
+
soul: "missing",
|
|
31
|
+
identity: "missing",
|
|
32
|
+
overwriteRequired: false,
|
|
54
33
|
};
|
|
55
34
|
}
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
const requestedName = options?.name ?? existing.meta.name;
|
|
36
|
+
const name = requestedName === existing.meta.name ? "same" : "different";
|
|
37
|
+
const soul = this.comparePersonaFile(existing.files.soul, files.soul);
|
|
38
|
+
const identity = this.comparePersonaFile(existing.files.identity, files.identity);
|
|
39
|
+
return {
|
|
40
|
+
engramId: agentName,
|
|
41
|
+
engramName: existing.meta.name,
|
|
42
|
+
sourcePath,
|
|
43
|
+
existing: true,
|
|
44
|
+
name,
|
|
45
|
+
soul,
|
|
46
|
+
identity,
|
|
47
|
+
overwriteRequired: name === "different" ||
|
|
48
|
+
soul === "different" ||
|
|
49
|
+
identity === "different",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async execute(agentName, options) {
|
|
53
|
+
const sourcePath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
54
|
+
if (!existsSync(sourcePath)) {
|
|
55
|
+
throw new WorkspaceNotFoundError(sourcePath);
|
|
56
|
+
}
|
|
57
|
+
const existing = await this.repository.get(agentName);
|
|
58
|
+
if (existing && !options?.force) {
|
|
59
|
+
throw new AlreadyExtractedError(agentName);
|
|
60
|
+
}
|
|
61
|
+
const { files, filesRead } = await this.readFiles(sourcePath);
|
|
62
|
+
if (filesRead.length === 0) {
|
|
63
|
+
throw new WorkspaceEmptyError(sourcePath);
|
|
60
64
|
}
|
|
61
65
|
const now = new Date().toISOString();
|
|
62
|
-
const engram =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
const engram = existing && options?.force
|
|
67
|
+
? {
|
|
68
|
+
meta: {
|
|
69
|
+
...existing.meta,
|
|
70
|
+
name: options?.name ?? existing.meta.name,
|
|
71
|
+
updatedAt: now,
|
|
72
|
+
},
|
|
73
|
+
// Force extract only replaces persona files from Claw.
|
|
74
|
+
files: {
|
|
75
|
+
...existing.files,
|
|
76
|
+
soul: files.soul ?? existing.files.soul,
|
|
77
|
+
identity: files.identity ?? existing.files.identity,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
: {
|
|
81
|
+
meta: {
|
|
82
|
+
id: agentName,
|
|
83
|
+
name: options?.name ?? agentName,
|
|
84
|
+
description: `Extracted from OpenClaw workspace (${agentName})`,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now,
|
|
87
|
+
tags: ["extracted", "openclaw"],
|
|
88
|
+
},
|
|
89
|
+
files,
|
|
90
|
+
};
|
|
73
91
|
await this.repository.save(engram);
|
|
74
92
|
return {
|
|
75
|
-
engramId,
|
|
76
|
-
engramName,
|
|
93
|
+
engramId: agentName,
|
|
94
|
+
engramName: engram.meta.name,
|
|
77
95
|
sourcePath,
|
|
78
96
|
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
97
|
};
|
|
112
|
-
await this.repository.save(updatedEngram);
|
|
113
|
-
return memoryMerged;
|
|
114
98
|
}
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
99
|
+
comparePersonaFile(currentContent, incomingContent) {
|
|
100
|
+
if (currentContent === undefined || incomingContent === undefined) {
|
|
101
|
+
return "missing";
|
|
132
102
|
}
|
|
133
|
-
|
|
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;
|
|
103
|
+
return currentContent === incomingContent ? "same" : "different";
|
|
140
104
|
}
|
|
141
105
|
async readFiles(sourcePath) {
|
|
142
106
|
const files = {};
|
|
143
107
|
const filesRead = [];
|
|
144
|
-
for (const [key, filename] of Object.entries(
|
|
108
|
+
for (const [key, filename] of Object.entries(RELIC_FILE_MAP)) {
|
|
145
109
|
const filePath = join(sourcePath, filename);
|
|
146
110
|
if (existsSync(filePath)) {
|
|
147
111
|
files[key] = await readFile(filePath, "utf-8");
|
|
@@ -176,15 +140,9 @@ export class WorkspaceEmptyError extends Error {
|
|
|
176
140
|
this.name = "WorkspaceEmptyError";
|
|
177
141
|
}
|
|
178
142
|
}
|
|
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 {
|
|
143
|
+
export class AlreadyExtractedError extends Error {
|
|
186
144
|
constructor(id) {
|
|
187
|
-
super(`
|
|
188
|
-
this.name = "
|
|
145
|
+
super(`Engram "${id}" already exists. Re-run with "--force" option to overwrite local persona files from the Claw workspace.`);
|
|
146
|
+
this.name = "AlreadyExtractedError";
|
|
189
147
|
}
|
|
190
148
|
}
|
|
@@ -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 InjectPersonaFileDiff, type InjectPersonaDiffResult, type InjectResult, } from "./inject.js";
|
|
5
|
+
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, type ExtractPersonaFileDiff, type ExtractPersonaDiffResult, 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";
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export type InjectPersonaFileDiff = "missing" | "same" | "different" | "skipped";
|
|
3
|
+
export interface InjectPersonaDiffResult {
|
|
4
|
+
engramId: string;
|
|
5
|
+
engramName: string;
|
|
6
|
+
targetPath: string;
|
|
7
|
+
soul: InjectPersonaFileDiff;
|
|
8
|
+
identity: InjectPersonaFileDiff;
|
|
9
|
+
overwriteRequired: boolean;
|
|
10
|
+
}
|
|
2
11
|
export interface InjectResult {
|
|
3
12
|
engramId: string;
|
|
4
13
|
engramName: string;
|
|
@@ -8,21 +17,32 @@ export interface InjectResult {
|
|
|
8
17
|
/**
|
|
9
18
|
* Inject — EngramのファイルをOpenClawワークスペースに注入する
|
|
10
19
|
*
|
|
11
|
-
*
|
|
20
|
+
* OpenClawではエージェントごとに workspace-<name>/ を使い、
|
|
21
|
+
* デフォルト(main)エージェントのみ workspace/ を使う。
|
|
12
22
|
* memoryEntries はOpenClaw側の管理に委ねるため注入しない。
|
|
13
23
|
*/
|
|
14
24
|
export declare class Inject {
|
|
15
25
|
private readonly repository;
|
|
16
26
|
constructor(repository: EngramRepository);
|
|
27
|
+
inspectPersona(engramId: string, options?: {
|
|
28
|
+
openclawDir?: string;
|
|
29
|
+
mergeIdentity?: boolean;
|
|
30
|
+
}): Promise<InjectPersonaDiffResult>;
|
|
17
31
|
execute(engramId: string, options?: {
|
|
18
|
-
to?: string;
|
|
19
32
|
openclawDir?: string;
|
|
33
|
+
mergeIdentity?: boolean;
|
|
20
34
|
}): Promise<InjectResult>;
|
|
35
|
+
private loadInjectTarget;
|
|
36
|
+
private resolveInjectedContent;
|
|
37
|
+
private compareTargetFile;
|
|
21
38
|
private writeFiles;
|
|
22
39
|
}
|
|
23
40
|
export declare class InjectEngramNotFoundError extends Error {
|
|
24
41
|
constructor(id: string);
|
|
25
42
|
}
|
|
26
|
-
export declare class
|
|
27
|
-
constructor(
|
|
43
|
+
export declare class InjectClawDirNotFoundError extends Error {
|
|
44
|
+
constructor(path: string);
|
|
45
|
+
}
|
|
46
|
+
export declare class InjectWorkspaceNotFoundError extends Error {
|
|
47
|
+
constructor(engramId: string);
|
|
28
48
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
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 {
|
|
@@ -13,17 +14,25 @@ export class Inject {
|
|
|
13
14
|
constructor(repository) {
|
|
14
15
|
this.repository = repository;
|
|
15
16
|
}
|
|
17
|
+
async inspectPersona(engramId, options) {
|
|
18
|
+
const { engram, targetPath } = await this.loadInjectTarget(engramId, options);
|
|
19
|
+
const mergeIdentity = options?.mergeIdentity ?? false;
|
|
20
|
+
const soul = await this.compareTargetFile(join(targetPath, INJECT_FILE_MAP.soul), this.resolveInjectedContent("soul", engram.files, mergeIdentity));
|
|
21
|
+
const identity = mergeIdentity
|
|
22
|
+
? "skipped"
|
|
23
|
+
: await this.compareTargetFile(join(targetPath, INJECT_FILE_MAP.identity), this.resolveInjectedContent("identity", engram.files, mergeIdentity));
|
|
24
|
+
return {
|
|
25
|
+
engramId: engram.meta.id,
|
|
26
|
+
engramName: engram.meta.name,
|
|
27
|
+
targetPath,
|
|
28
|
+
soul,
|
|
29
|
+
identity,
|
|
30
|
+
overwriteRequired: soul === "different" || identity === "different",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
16
33
|
async execute(engramId, options) {
|
|
17
|
-
const engram = await this.
|
|
18
|
-
|
|
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);
|
|
34
|
+
const { engram, targetPath } = await this.loadInjectTarget(engramId, options);
|
|
35
|
+
const filesWritten = await this.writeFiles(targetPath, engram.files, options?.mergeIdentity ?? false);
|
|
27
36
|
return {
|
|
28
37
|
engramId: engram.meta.id,
|
|
29
38
|
engramName: engram.meta.name,
|
|
@@ -31,10 +40,48 @@ export class Inject {
|
|
|
31
40
|
filesWritten,
|
|
32
41
|
};
|
|
33
42
|
}
|
|
34
|
-
async
|
|
43
|
+
async loadInjectTarget(engramId, options) {
|
|
44
|
+
const engram = await this.repository.get(engramId);
|
|
45
|
+
if (!engram) {
|
|
46
|
+
throw new InjectEngramNotFoundError(engramId);
|
|
47
|
+
}
|
|
48
|
+
if (options?.openclawDir && !existsSync(options.openclawDir)) {
|
|
49
|
+
throw new InjectClawDirNotFoundError(options.openclawDir);
|
|
50
|
+
}
|
|
51
|
+
const targetPath = resolveWorkspacePath(engramId, options?.openclawDir);
|
|
52
|
+
if (!existsSync(targetPath)) {
|
|
53
|
+
throw new InjectWorkspaceNotFoundError(engramId);
|
|
54
|
+
}
|
|
55
|
+
return { engram, targetPath };
|
|
56
|
+
}
|
|
57
|
+
resolveInjectedContent(key, files, mergeIdentity) {
|
|
58
|
+
if (mergeIdentity && key === "identity") {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
let content = files[key];
|
|
62
|
+
if (mergeIdentity && key === "soul" && files.identity) {
|
|
63
|
+
content = content + "\n" + files.identity;
|
|
64
|
+
}
|
|
65
|
+
return content;
|
|
66
|
+
}
|
|
67
|
+
async compareTargetFile(filePath, expectedContent) {
|
|
68
|
+
if (expectedContent === undefined) {
|
|
69
|
+
return "skipped";
|
|
70
|
+
}
|
|
71
|
+
if (!existsSync(filePath)) {
|
|
72
|
+
return "missing";
|
|
73
|
+
}
|
|
74
|
+
const currentContent = await readFile(filePath, "utf-8");
|
|
75
|
+
return currentContent === expectedContent ? "same" : "different";
|
|
76
|
+
}
|
|
77
|
+
async writeFiles(targetPath, files, mergeIdentity) {
|
|
35
78
|
const written = [];
|
|
36
|
-
for (const [key, filename] of Object.entries(
|
|
37
|
-
|
|
79
|
+
for (const [key, filename] of Object.entries(INJECT_FILE_MAP)) {
|
|
80
|
+
// --merge-identity: skip IDENTITY.md (merged into SOUL.md below)
|
|
81
|
+
if (mergeIdentity && key === "identity") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const content = this.resolveInjectedContent(key, files, mergeIdentity);
|
|
38
85
|
if (content !== undefined) {
|
|
39
86
|
await writeFile(join(targetPath, filename), content, "utf-8");
|
|
40
87
|
written.push(filename);
|
|
@@ -49,9 +96,15 @@ export class InjectEngramNotFoundError extends Error {
|
|
|
49
96
|
this.name = "InjectEngramNotFoundError";
|
|
50
97
|
}
|
|
51
98
|
}
|
|
52
|
-
export class
|
|
53
|
-
constructor(
|
|
54
|
-
super(`
|
|
55
|
-
this.name = "
|
|
99
|
+
export class InjectClawDirNotFoundError extends Error {
|
|
100
|
+
constructor(path) {
|
|
101
|
+
super(`Claw directory not found at ${path}`);
|
|
102
|
+
this.name = "InjectClawDirNotFoundError";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export class InjectWorkspaceNotFoundError extends Error {
|
|
106
|
+
constructor(engramId) {
|
|
107
|
+
super(`OpenClaw agent "${engramId}" has not been created yet. Run "openclaw agents add ${engramId}" first, then try again.`);
|
|
108
|
+
this.name = "InjectWorkspaceNotFoundError";
|
|
56
109
|
}
|
|
57
110
|
}
|
|
@@ -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
|
}
|