@ectplsm/relic 0.3.2 → 0.5.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 +1 -1
- package/README.md +20 -13
- package/dist/adapters/mikoshi/client.d.ts +21 -0
- package/dist/adapters/mikoshi/client.js +84 -0
- package/dist/core/ports/index.d.ts +1 -0
- package/dist/core/ports/mikoshi.d.ts +937 -0
- package/dist/core/ports/mikoshi.js +209 -0
- package/dist/core/sync/crypto.d.ts +55 -0
- package/dist/core/sync/crypto.js +189 -0
- package/dist/core/sync/memory-hash.d.ts +35 -0
- package/dist/core/sync/memory-hash.js +61 -0
- package/dist/core/sync/memory-merge.d.ts +13 -0
- package/dist/core/sync/memory-merge.js +84 -0
- package/dist/core/sync/normalize.d.ts +10 -0
- package/dist/core/sync/normalize.js +21 -0
- package/dist/core/sync/persona-hash.d.ts +15 -0
- package/dist/core/sync/persona-hash.js +36 -0
- package/dist/core/usecases/archive-cursor-update.d.ts +6 -0
- package/dist/core/usecases/archive-cursor-update.js +18 -0
- package/dist/core/usecases/archive-pending.d.ts +18 -1
- package/dist/core/usecases/archive-pending.js +37 -2
- package/dist/core/usecases/claw-pull.d.ts +44 -0
- package/dist/core/usecases/claw-pull.js +117 -0
- package/dist/core/usecases/extract.d.ts +0 -1
- package/dist/core/usecases/extract.js +13 -27
- package/dist/core/usecases/index.d.ts +11 -3
- package/dist/core/usecases/index.js +10 -2
- package/dist/core/usecases/inject.d.ts +1 -0
- package/dist/core/usecases/inject.js +2 -0
- package/dist/core/usecases/memory-write.d.ts +5 -0
- package/dist/core/usecases/memory-write.js +26 -16
- package/dist/core/usecases/mikoshi-clone.d.ts +29 -0
- package/dist/core/usecases/mikoshi-clone.js +94 -0
- package/dist/core/usecases/mikoshi-download.d.ts +29 -0
- package/dist/core/usecases/mikoshi-download.js +94 -0
- package/dist/core/usecases/mikoshi-memory-pull.d.ts +38 -0
- package/dist/core/usecases/mikoshi-memory-pull.js +142 -0
- package/dist/core/usecases/mikoshi-memory-push.d.ts +34 -0
- package/dist/core/usecases/mikoshi-memory-push.js +105 -0
- package/dist/core/usecases/mikoshi-memory-sync.d.ts +36 -0
- package/dist/core/usecases/mikoshi-memory-sync.js +175 -0
- package/dist/core/usecases/mikoshi-pull.d.ts +44 -0
- package/dist/core/usecases/mikoshi-pull.js +124 -0
- package/dist/core/usecases/mikoshi-push.d.ts +39 -0
- package/dist/core/usecases/mikoshi-push.js +127 -0
- package/dist/core/usecases/mikoshi-status.d.ts +37 -0
- package/dist/core/usecases/mikoshi-status.js +92 -0
- package/dist/core/usecases/sync.d.ts +0 -5
- package/dist/core/usecases/sync.js +23 -61
- package/dist/interfaces/cli/commands/claw.js +134 -85
- package/dist/interfaces/cli/commands/config.js +60 -1
- package/dist/interfaces/cli/commands/create.js +17 -16
- package/dist/interfaces/cli/commands/init.js +11 -10
- package/dist/interfaces/cli/commands/list.js +9 -7
- package/dist/interfaces/cli/commands/migrate.js +8 -7
- package/dist/interfaces/cli/commands/mikoshi.d.ts +2 -0
- package/dist/interfaces/cli/commands/mikoshi.js +565 -0
- package/dist/interfaces/cli/commands/refresh-samples.js +10 -9
- package/dist/interfaces/cli/index.js +2 -0
- package/dist/interfaces/cli/output.d.ts +5 -0
- package/dist/interfaces/cli/output.js +16 -0
- package/dist/interfaces/cli/spinner.d.ts +13 -0
- package/dist/interfaces/cli/spinner.js +47 -0
- package/dist/interfaces/mcp/index.js +109 -38
- package/dist/shared/config.d.ts +25 -5
- package/dist/shared/config.js +33 -5
- package/dist/shared/engram-composer.d.ts +1 -1
- package/dist/shared/engram-composer.js +9 -5
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona hash canonicalization — mikoshi-sync.md 準拠
|
|
3
|
+
*
|
|
4
|
+
* 正規化ルール:
|
|
5
|
+
* - UTF-8 BOM 除去
|
|
6
|
+
* - 改行を \n に統一
|
|
7
|
+
* - 末尾 \n を strip
|
|
8
|
+
*
|
|
9
|
+
* Canonical payload:
|
|
10
|
+
* header \n SOUL.md \n byteLen \n content \n IDENTITY.md \n byteLen \n content
|
|
11
|
+
*
|
|
12
|
+
* 出力: "sha256:<64 lowercase hex>"
|
|
13
|
+
* soul / identity どちらかが欠落なら null
|
|
14
|
+
*/
|
|
15
|
+
export declare function computePersonaHash(soul: string | undefined, identity: string | undefined): string | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { normalizeText } from "./normalize.js";
|
|
3
|
+
const HEADER = "mikoshi.persona.v1";
|
|
4
|
+
/**
|
|
5
|
+
* Persona hash canonicalization — mikoshi-sync.md 準拠
|
|
6
|
+
*
|
|
7
|
+
* 正規化ルール:
|
|
8
|
+
* - UTF-8 BOM 除去
|
|
9
|
+
* - 改行を \n に統一
|
|
10
|
+
* - 末尾 \n を strip
|
|
11
|
+
*
|
|
12
|
+
* Canonical payload:
|
|
13
|
+
* header \n SOUL.md \n byteLen \n content \n IDENTITY.md \n byteLen \n content
|
|
14
|
+
*
|
|
15
|
+
* 出力: "sha256:<64 lowercase hex>"
|
|
16
|
+
* soul / identity どちらかが欠落なら null
|
|
17
|
+
*/
|
|
18
|
+
export function computePersonaHash(soul, identity) {
|
|
19
|
+
if (!soul || !identity)
|
|
20
|
+
return null;
|
|
21
|
+
const normSoul = normalizeText(soul, { stripTrailingNewlines: true });
|
|
22
|
+
const normIdentity = normalizeText(identity, { stripTrailingNewlines: true });
|
|
23
|
+
const soulBytes = Buffer.byteLength(normSoul, "utf-8");
|
|
24
|
+
const identityBytes = Buffer.byteLength(normIdentity, "utf-8");
|
|
25
|
+
const payload = [
|
|
26
|
+
HEADER,
|
|
27
|
+
"SOUL.md",
|
|
28
|
+
String(soulBytes),
|
|
29
|
+
normSoul,
|
|
30
|
+
"IDENTITY.md",
|
|
31
|
+
String(identityBytes),
|
|
32
|
+
normIdentity,
|
|
33
|
+
].join("\n");
|
|
34
|
+
const hex = createHash("sha256").update(payload, "utf-8").digest("hex");
|
|
35
|
+
return `sha256:${hex}`;
|
|
36
|
+
}
|
|
@@ -16,6 +16,12 @@ export declare class ArchiveCursorUpdate {
|
|
|
16
16
|
execute(engramId: string, count: number): Promise<ArchiveCursorUpdateResult>;
|
|
17
17
|
private readCursor;
|
|
18
18
|
}
|
|
19
|
+
export declare class ArchiveCursorAdvanceOverflowError extends Error {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly requestedCount: number;
|
|
22
|
+
readonly availablePending: number;
|
|
23
|
+
constructor(id: string, requestedCount: number, availablePending: number);
|
|
24
|
+
}
|
|
19
25
|
export declare class ArchiveCursorUpdateEngramNotFoundError extends Error {
|
|
20
26
|
constructor(id: string);
|
|
21
27
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { ArchivePending } from "./archive-pending.js";
|
|
4
5
|
/**
|
|
5
6
|
* ArchiveCursorUpdate — archive.cursor を指定件数分だけ進める
|
|
6
7
|
*
|
|
@@ -19,6 +20,11 @@ export class ArchiveCursorUpdate {
|
|
|
19
20
|
}
|
|
20
21
|
const cursorPath = join(engramDir, "archive.cursor");
|
|
21
22
|
const previousCursor = await this.readCursor(cursorPath);
|
|
23
|
+
const archivePending = new ArchivePending(this.engramsPath);
|
|
24
|
+
const pending = await archivePending.execute(engramId, Number.MAX_SAFE_INTEGER);
|
|
25
|
+
if (count > pending.entries.length) {
|
|
26
|
+
throw new ArchiveCursorAdvanceOverflowError(engramId, count, pending.entries.length);
|
|
27
|
+
}
|
|
22
28
|
const newCursor = previousCursor + count;
|
|
23
29
|
await writeFile(cursorPath, String(newCursor), "utf-8");
|
|
24
30
|
return { previousCursor, newCursor };
|
|
@@ -36,6 +42,18 @@ export class ArchiveCursorUpdate {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
}
|
|
45
|
+
export class ArchiveCursorAdvanceOverflowError extends Error {
|
|
46
|
+
id;
|
|
47
|
+
requestedCount;
|
|
48
|
+
availablePending;
|
|
49
|
+
constructor(id, requestedCount, availablePending) {
|
|
50
|
+
super(`Engram "${id}" cursor advance overflow (${requestedCount} > ${availablePending}). Refusing to move archive.cursor.`);
|
|
51
|
+
this.id = id;
|
|
52
|
+
this.requestedCount = requestedCount;
|
|
53
|
+
this.availablePending = availablePending;
|
|
54
|
+
this.name = "ArchiveCursorAdvanceOverflowError";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
39
57
|
export class ArchiveCursorUpdateEngramNotFoundError extends Error {
|
|
40
58
|
constructor(id) {
|
|
41
59
|
super(`Engram "${id}" archive not found`);
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
export interface ArchivePendingEntry {
|
|
2
|
+
/** archive.md 先頭行から抽出した日付 */
|
|
3
|
+
date: string | null;
|
|
4
|
+
/** archive.md 先頭行の summary 部分 */
|
|
5
|
+
summary: string | null;
|
|
6
|
+
/** エントリ全文 */
|
|
7
|
+
raw: string;
|
|
8
|
+
/** 先頭行を除いた本文 */
|
|
9
|
+
body: string;
|
|
10
|
+
}
|
|
1
11
|
export interface ArchivePendingResult {
|
|
2
12
|
/** 未蒸留エントリ(cursor以降) */
|
|
3
|
-
entries:
|
|
13
|
+
entries: ArchivePendingEntry[];
|
|
4
14
|
/** 現在のcursor位置 */
|
|
5
15
|
cursor: number;
|
|
6
16
|
/** archive内の総エントリ数 */
|
|
@@ -8,6 +18,12 @@ export interface ArchivePendingResult {
|
|
|
8
18
|
/** 今回返さなかった残りの未蒸留エントリ数 */
|
|
9
19
|
remaining: number;
|
|
10
20
|
}
|
|
21
|
+
export declare class ArchiveCursorCorruptedError extends Error {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly cursor: number;
|
|
24
|
+
readonly total: number;
|
|
25
|
+
constructor(id: string, cursor: number, total: number);
|
|
26
|
+
}
|
|
11
27
|
/**
|
|
12
28
|
* ArchivePending — archive.md の未蒸留エントリを取得する
|
|
13
29
|
*
|
|
@@ -20,6 +36,7 @@ export declare class ArchivePending {
|
|
|
20
36
|
execute(engramId: string, limit?: number): Promise<ArchivePendingResult>;
|
|
21
37
|
private readCursor;
|
|
22
38
|
}
|
|
39
|
+
export declare function parseArchiveEntry(raw: string): ArchivePendingEntry;
|
|
23
40
|
export declare class ArchivePendingEngramNotFoundError extends Error {
|
|
24
41
|
constructor(id: string);
|
|
25
42
|
}
|
|
@@ -2,7 +2,20 @@ import { join } from "node:path";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
const ENTRY_SEPARATOR = /\n---\n/;
|
|
5
|
-
const DEFAULT_LIMIT =
|
|
5
|
+
const DEFAULT_LIMIT = 100;
|
|
6
|
+
const ENTRY_HEADER_PATTERN = /^(\d{4}-\d{2}-\d{2}) \| ?(.*)$/;
|
|
7
|
+
export class ArchiveCursorCorruptedError extends Error {
|
|
8
|
+
id;
|
|
9
|
+
cursor;
|
|
10
|
+
total;
|
|
11
|
+
constructor(id, cursor, total) {
|
|
12
|
+
super(`Engram "${id}" archive cursor is invalid (${cursor} > ${total}). Repair archive.cursor before distilling.`);
|
|
13
|
+
this.id = id;
|
|
14
|
+
this.cursor = cursor;
|
|
15
|
+
this.total = total;
|
|
16
|
+
this.name = "ArchiveCursorCorruptedError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
6
19
|
/**
|
|
7
20
|
* ArchivePending — archive.md の未蒸留エントリを取得する
|
|
8
21
|
*
|
|
@@ -29,11 +42,14 @@ export class ArchivePending {
|
|
|
29
42
|
.map((e) => e.trim())
|
|
30
43
|
.filter((e) => e.length > 0);
|
|
31
44
|
const cursor = await this.readCursor(engramDir);
|
|
45
|
+
if (cursor > allEntries.length) {
|
|
46
|
+
throw new ArchiveCursorCorruptedError(engramId, cursor, allEntries.length);
|
|
47
|
+
}
|
|
32
48
|
const pendingEntries = allEntries.slice(cursor);
|
|
33
49
|
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
34
50
|
const returned = pendingEntries.slice(0, effectiveLimit);
|
|
35
51
|
return {
|
|
36
|
-
entries: returned,
|
|
52
|
+
entries: returned.map(parseArchiveEntry),
|
|
37
53
|
cursor,
|
|
38
54
|
total: allEntries.length,
|
|
39
55
|
remaining: pendingEntries.length - returned.length,
|
|
@@ -53,6 +69,25 @@ export class ArchivePending {
|
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
}
|
|
72
|
+
export function parseArchiveEntry(raw) {
|
|
73
|
+
const trimmed = raw.trim();
|
|
74
|
+
const [header = "", ...rest] = trimmed.split("\n");
|
|
75
|
+
const match = header.match(ENTRY_HEADER_PATTERN);
|
|
76
|
+
if (!match) {
|
|
77
|
+
return {
|
|
78
|
+
date: null,
|
|
79
|
+
summary: null,
|
|
80
|
+
raw: trimmed,
|
|
81
|
+
body: trimmed,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
date: match[1],
|
|
86
|
+
summary: match[2] || null,
|
|
87
|
+
raw: trimmed,
|
|
88
|
+
body: rest.join("\n").trim(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
56
91
|
export class ArchivePendingEngramNotFoundError extends Error {
|
|
57
92
|
constructor(id) {
|
|
58
93
|
super(`Engram "${id}" archive not found`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
export type ClawPullPersonaFileDiff = "missing" | "same" | "different";
|
|
3
|
+
export interface ClawPullDiff {
|
|
4
|
+
soulDiff: ClawPullPersonaFileDiff;
|
|
5
|
+
identityDiff: ClawPullPersonaFileDiff;
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
remoteSoul?: string;
|
|
8
|
+
remoteIdentity?: string;
|
|
9
|
+
}
|
|
10
|
+
export type ClawPullOutcome = "pulled" | "already_synced";
|
|
11
|
+
export interface ClawPullResult {
|
|
12
|
+
outcome: ClawPullOutcome;
|
|
13
|
+
engramId: string;
|
|
14
|
+
engramName: string;
|
|
15
|
+
sourcePath: string;
|
|
16
|
+
diff?: ClawPullDiff;
|
|
17
|
+
}
|
|
18
|
+
export declare class ClawPullEngramNotFoundError extends Error {
|
|
19
|
+
readonly engramId: string;
|
|
20
|
+
constructor(engramId: string);
|
|
21
|
+
}
|
|
22
|
+
export declare class ClawPullWorkspaceNotFoundError extends Error {
|
|
23
|
+
readonly path: string;
|
|
24
|
+
constructor(path: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class ClawPullPersonaMissingError extends Error {
|
|
27
|
+
readonly path: string;
|
|
28
|
+
constructor(path: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class ClawPull {
|
|
31
|
+
private readonly localRepo;
|
|
32
|
+
constructor(localRepo: EngramRepository);
|
|
33
|
+
/**
|
|
34
|
+
* Phase 1: read workspace + diff. Does not write anything yet.
|
|
35
|
+
*
|
|
36
|
+
* Returns an `apply` closure when there are changes to write.
|
|
37
|
+
*/
|
|
38
|
+
check(agentName: string, options?: {
|
|
39
|
+
openclawDir?: string;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
result: ClawPullResult;
|
|
42
|
+
apply?: () => Promise<void>;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { RELIC_FILE_MAP, resolveWorkspacePath } from "../../shared/openclaw.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Errors
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export class ClawPullEngramNotFoundError extends Error {
|
|
9
|
+
engramId;
|
|
10
|
+
constructor(engramId) {
|
|
11
|
+
super(`Engram "${engramId}" not found locally. Use "relic claw pull" to create or update it.`);
|
|
12
|
+
this.engramId = engramId;
|
|
13
|
+
this.name = "ClawPullEngramNotFoundError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class ClawPullWorkspaceNotFoundError extends Error {
|
|
17
|
+
path;
|
|
18
|
+
constructor(path) {
|
|
19
|
+
super(`Claw workspace not found at ${path}`);
|
|
20
|
+
this.path = path;
|
|
21
|
+
this.name = "ClawPullWorkspaceNotFoundError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export class ClawPullPersonaMissingError extends Error {
|
|
25
|
+
path;
|
|
26
|
+
constructor(path) {
|
|
27
|
+
super(`No persona files (SOUL.md, IDENTITY.md) found in ${path}`);
|
|
28
|
+
this.path = path;
|
|
29
|
+
this.name = "ClawPullPersonaMissingError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Usecase
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export class ClawPull {
|
|
36
|
+
localRepo;
|
|
37
|
+
constructor(localRepo) {
|
|
38
|
+
this.localRepo = localRepo;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Phase 1: read workspace + diff. Does not write anything yet.
|
|
42
|
+
*
|
|
43
|
+
* Returns an `apply` closure when there are changes to write.
|
|
44
|
+
*/
|
|
45
|
+
async check(agentName, options) {
|
|
46
|
+
const sourcePath = resolveWorkspacePath(agentName, options?.openclawDir);
|
|
47
|
+
if (!existsSync(sourcePath)) {
|
|
48
|
+
throw new ClawPullWorkspaceNotFoundError(sourcePath);
|
|
49
|
+
}
|
|
50
|
+
// 1. Local Engram must exist
|
|
51
|
+
const local = await this.localRepo.get(agentName);
|
|
52
|
+
if (!local) {
|
|
53
|
+
throw new ClawPullEngramNotFoundError(agentName);
|
|
54
|
+
}
|
|
55
|
+
// 2. Read persona files from workspace
|
|
56
|
+
const remoteSoul = await readOptionalFile(join(sourcePath, RELIC_FILE_MAP.soul));
|
|
57
|
+
const remoteIdentity = await readOptionalFile(join(sourcePath, RELIC_FILE_MAP.identity));
|
|
58
|
+
if (!remoteSoul && !remoteIdentity) {
|
|
59
|
+
throw new ClawPullPersonaMissingError(sourcePath);
|
|
60
|
+
}
|
|
61
|
+
// 3. Diff
|
|
62
|
+
const soulDiff = diffFile(local.files.soul, remoteSoul);
|
|
63
|
+
const identityDiff = diffFile(local.files.identity, remoteIdentity);
|
|
64
|
+
if (soulDiff !== "different" && identityDiff !== "different") {
|
|
65
|
+
return {
|
|
66
|
+
result: {
|
|
67
|
+
outcome: "already_synced",
|
|
68
|
+
engramId: agentName,
|
|
69
|
+
engramName: local.meta.name,
|
|
70
|
+
sourcePath,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const diff = {
|
|
75
|
+
soulDiff,
|
|
76
|
+
identityDiff,
|
|
77
|
+
sourcePath,
|
|
78
|
+
remoteSoul,
|
|
79
|
+
remoteIdentity,
|
|
80
|
+
};
|
|
81
|
+
// 4. Apply closure
|
|
82
|
+
const apply = async () => {
|
|
83
|
+
const fresh = await this.localRepo.get(agentName);
|
|
84
|
+
if (!fresh)
|
|
85
|
+
throw new ClawPullEngramNotFoundError(agentName);
|
|
86
|
+
const updatedFiles = { ...fresh.files };
|
|
87
|
+
if (soulDiff === "different" && remoteSoul)
|
|
88
|
+
updatedFiles.soul = remoteSoul;
|
|
89
|
+
if (identityDiff === "different" && remoteIdentity)
|
|
90
|
+
updatedFiles.identity = remoteIdentity;
|
|
91
|
+
await this.localRepo.save({
|
|
92
|
+
meta: { ...fresh.meta, updatedAt: new Date().toISOString() },
|
|
93
|
+
files: updatedFiles,
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
return { result: { outcome: "pulled", engramId: agentName, engramName: local.meta.name, sourcePath, diff }, apply };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Helpers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
async function readOptionalFile(filePath) {
|
|
103
|
+
if (!existsSync(filePath))
|
|
104
|
+
return undefined;
|
|
105
|
+
return readFile(filePath, "utf-8");
|
|
106
|
+
}
|
|
107
|
+
function diffFile(localContent, remoteContent) {
|
|
108
|
+
if (!remoteContent)
|
|
109
|
+
return "missing";
|
|
110
|
+
if (!localContent)
|
|
111
|
+
return "different";
|
|
112
|
+
return normalizeContent(localContent) === normalizeContent(remoteContent) ? "same" : "different";
|
|
113
|
+
}
|
|
114
|
+
/** Trim trailing whitespace so minor formatting differences don't cause false diffs. */
|
|
115
|
+
function normalizeContent(content) {
|
|
116
|
+
return content.trimEnd();
|
|
117
|
+
}
|
|
@@ -55,7 +55,7 @@ export class Extract {
|
|
|
55
55
|
throw new WorkspaceNotFoundError(sourcePath);
|
|
56
56
|
}
|
|
57
57
|
const existing = await this.repository.get(agentName);
|
|
58
|
-
if (existing
|
|
58
|
+
if (existing) {
|
|
59
59
|
throw new AlreadyExtractedError(agentName);
|
|
60
60
|
}
|
|
61
61
|
const { files, filesRead } = await this.readFiles(sourcePath);
|
|
@@ -63,31 +63,17 @@ export class Extract {
|
|
|
63
63
|
throw new WorkspaceEmptyError(sourcePath);
|
|
64
64
|
}
|
|
65
65
|
const now = new Date().toISOString();
|
|
66
|
-
const engram =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
};
|
|
66
|
+
const engram = {
|
|
67
|
+
meta: {
|
|
68
|
+
id: agentName,
|
|
69
|
+
name: options?.name ?? agentName,
|
|
70
|
+
description: `Extracted from OpenClaw workspace (${agentName})`,
|
|
71
|
+
createdAt: now,
|
|
72
|
+
updatedAt: now,
|
|
73
|
+
tags: ["extracted", "openclaw"],
|
|
74
|
+
},
|
|
75
|
+
files,
|
|
76
|
+
};
|
|
91
77
|
await this.repository.save(engram);
|
|
92
78
|
return {
|
|
93
79
|
engramId: agentName,
|
|
@@ -142,7 +128,7 @@ export class WorkspaceEmptyError extends Error {
|
|
|
142
128
|
}
|
|
143
129
|
export class AlreadyExtractedError extends Error {
|
|
144
130
|
constructor(id) {
|
|
145
|
-
super(`Engram "${id}" already exists.
|
|
131
|
+
super(`Engram "${id}" already exists. Use "relic claw pull" to update it.`);
|
|
146
132
|
this.name = "AlreadyExtractedError";
|
|
147
133
|
}
|
|
148
134
|
}
|
|
@@ -5,7 +5,15 @@ export { MigrateEngrams, type MigrateEngramsResult, } from "./migrate-engrams.js
|
|
|
5
5
|
export { RefreshSamples, type RefreshSamplesResult, } from "./refresh-samples.js";
|
|
6
6
|
export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, type InjectPersonaFileDiff, type InjectPersonaDiffResult, type InjectResult, } from "./inject.js";
|
|
7
7
|
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, type ExtractPersonaFileDiff, type ExtractPersonaDiffResult, type ExtractResult, } from "./extract.js";
|
|
8
|
-
export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteResult, } from "./memory-write.js";
|
|
8
|
+
export { MemoryWrite, MemoryWriteEngramNotFoundError, type MemoryWriteBatchItem, type MemoryWriteResult, } from "./memory-write.js";
|
|
9
|
+
export { ClawPull, ClawPullEngramNotFoundError, ClawPullWorkspaceNotFoundError, ClawPullPersonaMissingError, type ClawPullResult, type ClawPullDiff, } from "./claw-pull.js";
|
|
9
10
|
export { Sync, SyncOpenclawDirNotFoundError, type SyncTarget, type SyncResult, type SyncInitialResult, } from "./sync.js";
|
|
10
|
-
export { ArchivePending, ArchivePendingEngramNotFoundError, type ArchivePendingResult, } from "./archive-pending.js";
|
|
11
|
-
export { ArchiveCursorUpdate, ArchiveCursorUpdateEngramNotFoundError, type ArchiveCursorUpdateResult, } from "./archive-cursor-update.js";
|
|
11
|
+
export { ArchivePending, ArchiveCursorCorruptedError, ArchivePendingEngramNotFoundError, type ArchivePendingEntry, type ArchivePendingResult, } from "./archive-pending.js";
|
|
12
|
+
export { ArchiveCursorUpdate, ArchiveCursorAdvanceOverflowError, ArchiveCursorUpdateEngramNotFoundError, type ArchiveCursorUpdateResult, } from "./archive-cursor-update.js";
|
|
13
|
+
export { MikoshiStatus, MikoshiStatusEngramNotFoundError, MikoshiStatusCloudNotFoundError, type MikoshiStatusResult, } from "./mikoshi-status.js";
|
|
14
|
+
export { MikoshiPush, MikoshiPushEngramNotFoundError, MikoshiPushPersonaHashError, MikoshiPushPersonaConflictError, type MikoshiPushResult, type MikoshiPushApplyResult, } from "./mikoshi-push.js";
|
|
15
|
+
export { MikoshiDownload, MikoshiDownloadAlreadyExistsError, MikoshiDownloadCloudNotFoundError, MikoshiDownloadPersonaMissingError, type MikoshiDownloadResult, } from "./mikoshi-download.js";
|
|
16
|
+
export { MikoshiPull, MikoshiPullEngramNotFoundError, MikoshiPullCloudNotFoundError, MikoshiPullPersonaMissingError, type MikoshiPullResult, type MikoshiPullDiff, } from "./mikoshi-pull.js";
|
|
17
|
+
export { MikoshiMemoryPush, MikoshiMemoryPushEngramNotFoundError, MikoshiMemoryPushNoFilesError, MikoshiMemoryPushCloudNotFoundError, type MikoshiMemoryPushResult, } from "./mikoshi-memory-push.js";
|
|
18
|
+
export { MikoshiMemoryPull, MikoshiMemoryPullEngramNotFoundError, MikoshiMemoryPullCloudNotFoundError, MikoshiMemoryPullDecryptError, type MikoshiMemoryPullResult, type MikoshiMemoryPullDiff, } from "./mikoshi-memory-pull.js";
|
|
19
|
+
export { MikoshiMemorySync, MikoshiMemorySyncEngramNotFoundError, MikoshiMemorySyncCloudNotFoundError, MikoshiMemorySyncDecryptError, type MikoshiMemorySyncResult, } from "./mikoshi-memory-sync.js";
|
|
@@ -6,6 +6,14 @@ export { RefreshSamples, } from "./refresh-samples.js";
|
|
|
6
6
|
export { Inject, InjectEngramNotFoundError, InjectClawDirNotFoundError, InjectWorkspaceNotFoundError, } from "./inject.js";
|
|
7
7
|
export { Extract, WorkspaceNotFoundError, WorkspaceEmptyError, AlreadyExtractedError, } from "./extract.js";
|
|
8
8
|
export { MemoryWrite, MemoryWriteEngramNotFoundError, } from "./memory-write.js";
|
|
9
|
+
export { ClawPull, ClawPullEngramNotFoundError, ClawPullWorkspaceNotFoundError, ClawPullPersonaMissingError, } from "./claw-pull.js";
|
|
9
10
|
export { Sync, SyncOpenclawDirNotFoundError, } from "./sync.js";
|
|
10
|
-
export { ArchivePending, ArchivePendingEngramNotFoundError, } from "./archive-pending.js";
|
|
11
|
-
export { ArchiveCursorUpdate, ArchiveCursorUpdateEngramNotFoundError, } from "./archive-cursor-update.js";
|
|
11
|
+
export { ArchivePending, ArchiveCursorCorruptedError, ArchivePendingEngramNotFoundError, } from "./archive-pending.js";
|
|
12
|
+
export { ArchiveCursorUpdate, ArchiveCursorAdvanceOverflowError, ArchiveCursorUpdateEngramNotFoundError, } from "./archive-cursor-update.js";
|
|
13
|
+
export { MikoshiStatus, MikoshiStatusEngramNotFoundError, MikoshiStatusCloudNotFoundError, } from "./mikoshi-status.js";
|
|
14
|
+
export { MikoshiPush, MikoshiPushEngramNotFoundError, MikoshiPushPersonaHashError, MikoshiPushPersonaConflictError, } from "./mikoshi-push.js";
|
|
15
|
+
export { MikoshiDownload, MikoshiDownloadAlreadyExistsError, MikoshiDownloadCloudNotFoundError, MikoshiDownloadPersonaMissingError, } from "./mikoshi-download.js";
|
|
16
|
+
export { MikoshiPull, MikoshiPullEngramNotFoundError, MikoshiPullCloudNotFoundError, MikoshiPullPersonaMissingError, } from "./mikoshi-pull.js";
|
|
17
|
+
export { MikoshiMemoryPush, MikoshiMemoryPushEngramNotFoundError, MikoshiMemoryPushNoFilesError, MikoshiMemoryPushCloudNotFoundError, } from "./mikoshi-memory-push.js";
|
|
18
|
+
export { MikoshiMemoryPull, MikoshiMemoryPullEngramNotFoundError, MikoshiMemoryPullCloudNotFoundError, MikoshiMemoryPullDecryptError, } from "./mikoshi-memory-pull.js";
|
|
19
|
+
export { MikoshiMemorySync, MikoshiMemorySyncEngramNotFoundError, MikoshiMemorySyncCloudNotFoundError, MikoshiMemorySyncDecryptError, } from "./mikoshi-memory-sync.js";
|
|
@@ -17,6 +17,7 @@ export class Inject {
|
|
|
17
17
|
async inspectPersona(engramId, options) {
|
|
18
18
|
const { engram, targetPath } = await this.loadInjectTarget(engramId, options);
|
|
19
19
|
const mergeIdentity = options?.mergeIdentity ?? false;
|
|
20
|
+
const targetExists = existsSync(targetPath);
|
|
20
21
|
const soul = await this.compareTargetFile(join(targetPath, INJECT_FILE_MAP.soul), this.resolveInjectedContent("soul", engram.files, mergeIdentity));
|
|
21
22
|
const identity = mergeIdentity
|
|
22
23
|
? "skipped"
|
|
@@ -25,6 +26,7 @@ export class Inject {
|
|
|
25
26
|
engramId: engram.meta.id,
|
|
26
27
|
engramName: engram.meta.name,
|
|
27
28
|
targetPath,
|
|
29
|
+
targetExists,
|
|
28
30
|
soul,
|
|
29
31
|
identity,
|
|
30
32
|
overwriteRequired: soul === "different" || identity === "different",
|
|
@@ -4,6 +4,10 @@ export interface MemoryWriteResult {
|
|
|
4
4
|
appended: boolean;
|
|
5
5
|
path: string;
|
|
6
6
|
}
|
|
7
|
+
export interface MemoryWriteBatchItem {
|
|
8
|
+
date: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}
|
|
7
11
|
/**
|
|
8
12
|
* MemoryWrite — Engramのメモリエントリに追記する
|
|
9
13
|
*
|
|
@@ -14,6 +18,7 @@ export declare class MemoryWrite {
|
|
|
14
18
|
private readonly engramsPath;
|
|
15
19
|
constructor(engramsPath: string);
|
|
16
20
|
execute(engramId: string, content: string, date?: string): Promise<MemoryWriteResult>;
|
|
21
|
+
executeBatch(engramId: string, writes: MemoryWriteBatchItem[]): Promise<MemoryWriteResult[]>;
|
|
17
22
|
}
|
|
18
23
|
export declare class MemoryWriteEngramNotFoundError extends Error {
|
|
19
24
|
constructor(id: string);
|
|
@@ -14,29 +14,39 @@ export class MemoryWrite {
|
|
|
14
14
|
}
|
|
15
15
|
async execute(engramId, content, date) {
|
|
16
16
|
const resolvedDate = date ?? new Date().toISOString().split("T")[0];
|
|
17
|
+
const [result] = await this.executeBatch(engramId, [
|
|
18
|
+
{ date: resolvedDate, content },
|
|
19
|
+
]);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
async executeBatch(engramId, writes) {
|
|
17
23
|
const engramDir = join(this.engramsPath, engramId);
|
|
18
24
|
if (!existsSync(engramDir)) {
|
|
19
25
|
throw new MemoryWriteEngramNotFoundError(engramId);
|
|
20
26
|
}
|
|
21
27
|
const memoryDir = join(engramDir, "memory");
|
|
22
28
|
await mkdir(memoryDir, { recursive: true });
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const results = [];
|
|
30
|
+
for (const write of writes) {
|
|
31
|
+
const filePath = join(memoryDir, `${write.date}.md`);
|
|
32
|
+
let appended = false;
|
|
33
|
+
if (existsSync(filePath)) {
|
|
34
|
+
const existing = await readFile(filePath, "utf-8");
|
|
35
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
36
|
+
await writeFile(filePath, existing + separator + write.content + "\n", "utf-8");
|
|
37
|
+
appended = true;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
await writeFile(filePath, write.content + "\n", "utf-8");
|
|
41
|
+
}
|
|
42
|
+
results.push({
|
|
43
|
+
engramId,
|
|
44
|
+
date: write.date,
|
|
45
|
+
appended,
|
|
46
|
+
path: filePath,
|
|
47
|
+
});
|
|
33
48
|
}
|
|
34
|
-
return
|
|
35
|
-
engramId,
|
|
36
|
-
date: resolvedDate,
|
|
37
|
-
appended,
|
|
38
|
-
path: filePath,
|
|
39
|
-
};
|
|
49
|
+
return results;
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
export class MemoryWriteEngramNotFoundError extends Error {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EngramRepository } from "../ports/engram-repository.js";
|
|
2
|
+
import type { MikoshiClient } from "../ports/mikoshi.js";
|
|
3
|
+
export interface MikoshiCloneResult {
|
|
4
|
+
engramId: string;
|
|
5
|
+
engramName: string;
|
|
6
|
+
cloudEngramId: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class MikoshiCloneAlreadyExistsError extends Error {
|
|
9
|
+
readonly engramId: string;
|
|
10
|
+
constructor(engramId: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class MikoshiCloneCloudNotFoundError extends Error {
|
|
13
|
+
readonly sourceEngramId: string;
|
|
14
|
+
constructor(sourceEngramId: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class MikoshiClonePersonaMissingError extends Error {
|
|
17
|
+
readonly cloudEngramId: string;
|
|
18
|
+
constructor(cloudEngramId: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class MikoshiClone {
|
|
21
|
+
private readonly localRepo;
|
|
22
|
+
private readonly mikoshi;
|
|
23
|
+
constructor(localRepo: EngramRepository, mikoshi: MikoshiClient);
|
|
24
|
+
/**
|
|
25
|
+
* Clone a remote Engram from Mikoshi to local.
|
|
26
|
+
* Fails if the local Engram already exists.
|
|
27
|
+
*/
|
|
28
|
+
execute(engramId: string): Promise<MikoshiCloneResult>;
|
|
29
|
+
}
|