@openclawbrain/openclaw 0.3.5 → 0.3.6
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 +57 -4
- package/dist/src/attachment-truth.js +7 -2
- package/dist/src/cli.js +143 -83
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +12 -5
- package/dist/src/openclaw-hook-truth.d.ts +8 -0
- package/dist/src/openclaw-hook-truth.js +134 -28
- package/dist/src/openclaw-plugin-install.d.ts +40 -0
- package/dist/src/openclaw-plugin-install.js +282 -0
- package/dist/src/resolve-activation-root.js +14 -9
- package/dist/src/shadow-extension-proof.d.ts +3 -0
- package/dist/src/shadow-extension-proof.js +23 -23
- package/openclaw.plugin.json +11 -0
- package/package.json +15 -6
package/dist/src/index.js
CHANGED
|
@@ -6148,7 +6148,7 @@ function buildOperatorFindings(report) {
|
|
|
6148
6148
|
findings.push({ severity, code, summary, detail });
|
|
6149
6149
|
};
|
|
6150
6150
|
if (report.hook.desynced) {
|
|
6151
|
-
push("fail", "hook_desynced", "profile hook is
|
|
6151
|
+
push("fail", "hook_desynced", "profile hook is present but not loadable", report.hook.detail);
|
|
6152
6152
|
}
|
|
6153
6153
|
else if (report.attachmentTruth.state === "not_attached") {
|
|
6154
6154
|
push("fail", "current_profile_not_attached", "current profile is not attached", report.attachmentTruth.detail);
|
|
@@ -6393,8 +6393,8 @@ function summarizeOperatorAttachmentTruth(report) {
|
|
|
6393
6393
|
watchOnly: false,
|
|
6394
6394
|
activationRoot: report.activationRoot,
|
|
6395
6395
|
servingSlot: report.active === null ? "none" : "active",
|
|
6396
|
-
detail: report.hook.
|
|
6397
|
-
? "current profile hook files exist, but OpenClaw will not load them until the
|
|
6396
|
+
detail: report.hook.loadability === "blocked"
|
|
6397
|
+
? "current profile hook files exist, but OpenClaw will not load them until the installed hook is repaired"
|
|
6398
6398
|
: "current profile hook is present on the selected OpenClaw home"
|
|
6399
6399
|
};
|
|
6400
6400
|
}
|
|
@@ -6421,7 +6421,7 @@ function summarizeCurrentProfileBrainSummary(input) {
|
|
|
6421
6421
|
return "Brain is not attached to the current Profile.";
|
|
6422
6422
|
}
|
|
6423
6423
|
if (input.hookDesynced) {
|
|
6424
|
-
return "Brain hook files exist, but
|
|
6424
|
+
return "Brain hook files exist, but the installed hook is not loadable yet.";
|
|
6425
6425
|
}
|
|
6426
6426
|
if (input.attachmentState === "unknown") {
|
|
6427
6427
|
return input.watchOnly
|
|
@@ -6603,9 +6603,16 @@ function buildCurrentProfileBrainStatusFromReport(report, policyMode, profileId)
|
|
|
6603
6603
|
noun: "Hook",
|
|
6604
6604
|
scope: report.hook.scope,
|
|
6605
6605
|
openclawHome: report.hook.openclawHome,
|
|
6606
|
+
extensionDir: report.hook.extensionDir,
|
|
6606
6607
|
hookPath: report.hook.hookPath,
|
|
6607
6608
|
runtimeGuardPath: report.hook.runtimeGuardPath,
|
|
6608
6609
|
manifestPath: report.hook.manifestPath,
|
|
6610
|
+
packageJsonPath: report.hook.packageJsonPath,
|
|
6611
|
+
manifestId: report.hook.manifestId,
|
|
6612
|
+
installId: report.hook.installId,
|
|
6613
|
+
packageName: report.hook.packageName,
|
|
6614
|
+
installLayout: report.hook.installLayout,
|
|
6615
|
+
additionalInstallCount: report.hook.additionalInstallCount,
|
|
6609
6616
|
installState: report.hook.installState,
|
|
6610
6617
|
loadability: report.hook.loadability,
|
|
6611
6618
|
loadProof: report.hook.loadProof,
|
|
@@ -6872,4 +6879,4 @@ export { buildPassiveLearningSessionExportFromOpenClawSessionStore, buildPassive
|
|
|
6872
6879
|
export { DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_TIMEOUT_MS, OllamaClient, OllamaClientError, createOllamaClient } from "./ollama-client.js";
|
|
6873
6880
|
export { resolveActivationRoot } from "./resolve-activation-root.js";
|
|
6874
6881
|
export { runDaemonCommand, parseDaemonArgs } from "./daemon.js";
|
|
6875
|
-
//# sourceMappingURL=index.js.map
|
|
6882
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OpenClawBrainInstallLayout } from "./openclaw-plugin-install.js";
|
|
1
2
|
export type OpenClawBrainHookInstallState = "installed" | "not_installed" | "blocked_by_allowlist" | "unverified";
|
|
2
3
|
export type OpenClawBrainHookLoadability = "loadable" | "blocked" | "not_installed" | "unverified";
|
|
3
4
|
export type OpenClawBrainHookLoadProof = "status_probe_ready" | "not_ready";
|
|
@@ -5,9 +6,16 @@ export type OpenClawBrainPluginAllowlistState = "unrestricted" | "allowed" | "bl
|
|
|
5
6
|
export interface OpenClawBrainHookInspection {
|
|
6
7
|
scope: "exact_openclaw_home" | "activation_root_only";
|
|
7
8
|
openclawHome: string | null;
|
|
9
|
+
extensionDir: string | null;
|
|
8
10
|
hookPath: string | null;
|
|
9
11
|
runtimeGuardPath: string | null;
|
|
10
12
|
manifestPath: string | null;
|
|
13
|
+
packageJsonPath: string | null;
|
|
14
|
+
manifestId: string | null;
|
|
15
|
+
installId: string | null;
|
|
16
|
+
packageName: string | null;
|
|
17
|
+
installLayout: OpenClawBrainInstallLayout | null;
|
|
18
|
+
additionalInstallCount: number;
|
|
11
19
|
installState: OpenClawBrainHookInstallState;
|
|
12
20
|
loadability: OpenClawBrainHookLoadability;
|
|
13
21
|
pluginAllowlistState: OpenClawBrainPluginAllowlistState;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { describeOpenClawBrainInstallIdentity, describeOpenClawBrainInstallLayout, findInstalledOpenClawBrainPlugin, getOpenClawBrainKnownPluginIds } from "./openclaw-plugin-install.js";
|
|
3
4
|
function toErrorMessage(error) {
|
|
4
5
|
return error instanceof Error ? error.message : String(error);
|
|
5
6
|
}
|
|
@@ -34,8 +35,49 @@ function shortenPath(fullPath) {
|
|
|
34
35
|
}
|
|
35
36
|
return fullPath;
|
|
36
37
|
}
|
|
38
|
+
function inspectInstalledHookActivationRoot(loaderEntryPath) {
|
|
39
|
+
let content;
|
|
40
|
+
try {
|
|
41
|
+
content = readFileSync(loaderEntryPath, "utf8");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
ready: false,
|
|
46
|
+
detail: `installed loader entry ${shortenPath(loaderEntryPath)} could not be read: ${toErrorMessage(error)}`
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const match = content.match(/const\s+ACTIVATION_ROOT\s*=\s*["'`]([^"'`]+)["'`]/) ??
|
|
50
|
+
content.match(/activationRoot:\s*["'`]([^"'`]+)["'`]/);
|
|
51
|
+
if (!match || !match[1]) {
|
|
52
|
+
return {
|
|
53
|
+
ready: false,
|
|
54
|
+
detail: `installed loader entry ${shortenPath(loaderEntryPath)} does not declare a pinned activation root`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const activationRoot = match[1].trim();
|
|
58
|
+
if (activationRoot === "__ACTIVATION_ROOT__" || activationRoot === "__ACTIVATION_" + "ROOT__") {
|
|
59
|
+
return {
|
|
60
|
+
ready: false,
|
|
61
|
+
detail: `installed loader entry ${shortenPath(loaderEntryPath)} still contains the ACTIVATION_ROOT placeholder; rerun openclawbrain install/attach to pin the runtime hook`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
ready: true,
|
|
66
|
+
detail: `installed loader entry ${shortenPath(loaderEntryPath)} pins activation root ${shortenPath(path.resolve(activationRoot))}`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function describeAdditionalInstallDetail(additionalInstalls) {
|
|
70
|
+
if (additionalInstalls.length === 0) {
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
73
|
+
return `; additional OpenClawBrain installs also exist at ${additionalInstalls
|
|
74
|
+
.map((install) => `${shortenPath(install.extensionDir)} (${describeOpenClawBrainInstallLayout(install.installLayout)}, ${describeOpenClawBrainInstallIdentity(install)})`)
|
|
75
|
+
.join(", ")}`;
|
|
76
|
+
}
|
|
37
77
|
export function inspectOpenClawBrainPluginAllowlist(openclawHome) {
|
|
38
78
|
const { path: openclawJsonPath, config } = readOpenClawJsonConfig(openclawHome);
|
|
79
|
+
const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
|
|
80
|
+
const knownPluginIds = getOpenClawBrainKnownPluginIds(installedPlugin.selectedInstall);
|
|
39
81
|
const plugins = readJsonObjectRecord(config.plugins);
|
|
40
82
|
if (plugins === null) {
|
|
41
83
|
return {
|
|
@@ -55,14 +97,15 @@ export function inspectOpenClawBrainPluginAllowlist(openclawHome) {
|
|
|
55
97
|
detail: `${shortenPath(openclawJsonPath)} has a non-array plugins.allow value, so OpenClawBrain load cannot be proven from config`
|
|
56
98
|
};
|
|
57
99
|
}
|
|
58
|
-
|
|
100
|
+
const matchedPluginId = knownPluginIds.find((pluginId) => plugins.allow.includes(pluginId)) ?? null;
|
|
101
|
+
return matchedPluginId !== null
|
|
59
102
|
? {
|
|
60
103
|
state: "allowed",
|
|
61
|
-
detail: `${shortenPath(openclawJsonPath)} plugins.allow explicitly includes
|
|
104
|
+
detail: `${shortenPath(openclawJsonPath)} plugins.allow explicitly includes ${matchedPluginId}; recognized OpenClawBrain ids are ${knownPluginIds.join(", ")}`
|
|
62
105
|
}
|
|
63
106
|
: {
|
|
64
107
|
state: "blocked",
|
|
65
|
-
detail: `${shortenPath(openclawJsonPath)} plugins.allow excludes
|
|
108
|
+
detail: `${shortenPath(openclawJsonPath)} plugins.allow excludes recognized OpenClawBrain ids ${knownPluginIds.join(", ")}`
|
|
66
109
|
};
|
|
67
110
|
}
|
|
68
111
|
export function inspectOpenClawBrainHookStatus(openclawHome) {
|
|
@@ -70,9 +113,16 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
|
|
|
70
113
|
return {
|
|
71
114
|
scope: "activation_root_only",
|
|
72
115
|
openclawHome: null,
|
|
116
|
+
extensionDir: null,
|
|
73
117
|
hookPath: null,
|
|
74
118
|
runtimeGuardPath: null,
|
|
75
119
|
manifestPath: null,
|
|
120
|
+
packageJsonPath: null,
|
|
121
|
+
manifestId: null,
|
|
122
|
+
installId: null,
|
|
123
|
+
packageName: null,
|
|
124
|
+
installLayout: null,
|
|
125
|
+
additionalInstallCount: 0,
|
|
76
126
|
installState: "unverified",
|
|
77
127
|
loadability: "unverified",
|
|
78
128
|
pluginAllowlistState: "unverified",
|
|
@@ -81,66 +131,122 @@ export function inspectOpenClawBrainHookStatus(openclawHome) {
|
|
|
81
131
|
};
|
|
82
132
|
}
|
|
83
133
|
const resolvedHome = path.resolve(openclawHome);
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
const installedPlugin = findInstalledOpenClawBrainPlugin(resolvedHome);
|
|
135
|
+
if (installedPlugin.selectedInstall === null ||
|
|
136
|
+
installedPlugin.selectedInstall.loaderEntryPath === null ||
|
|
137
|
+
installedPlugin.selectedInstall.runtimeGuardPath === null) {
|
|
138
|
+
const incompleteInstall = installedPlugin.selectedInstall;
|
|
89
139
|
return {
|
|
90
140
|
scope: "exact_openclaw_home",
|
|
91
141
|
openclawHome: resolvedHome,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
142
|
+
extensionDir: incompleteInstall?.extensionDir ?? null,
|
|
143
|
+
hookPath: incompleteInstall?.loaderEntryPath ?? null,
|
|
144
|
+
runtimeGuardPath: incompleteInstall?.runtimeGuardPath ?? null,
|
|
145
|
+
manifestPath: incompleteInstall?.manifestPath ?? null,
|
|
146
|
+
packageJsonPath: incompleteInstall?.packageJsonPath ?? null,
|
|
147
|
+
manifestId: incompleteInstall?.manifestId ?? null,
|
|
148
|
+
installId: incompleteInstall?.installId ?? null,
|
|
149
|
+
packageName: incompleteInstall?.packageName ?? null,
|
|
150
|
+
installLayout: incompleteInstall?.installLayout ?? null,
|
|
151
|
+
additionalInstallCount: installedPlugin.additionalInstalls.length,
|
|
95
152
|
installState: "not_installed",
|
|
96
153
|
loadability: "not_installed",
|
|
97
154
|
pluginAllowlistState: "unverified",
|
|
98
155
|
desynced: false,
|
|
99
|
-
detail:
|
|
156
|
+
detail: incompleteInstall === null
|
|
157
|
+
? `profile hook is not present under ${shortenPath(installedPlugin.extensionsDir)}`
|
|
158
|
+
: `profile hook is incomplete under ${shortenPath(incompleteInstall.extensionDir)} (${describeOpenClawBrainInstallIdentity(incompleteInstall)})`
|
|
100
159
|
};
|
|
101
160
|
}
|
|
161
|
+
const selectedInstall = installedPlugin.selectedInstall;
|
|
162
|
+
const additionalInstallDetail = describeAdditionalInstallDetail(installedPlugin.additionalInstalls);
|
|
102
163
|
const allowlist = inspectOpenClawBrainPluginAllowlist(resolvedHome);
|
|
164
|
+
const layoutLabel = describeOpenClawBrainInstallLayout(selectedInstall.installLayout);
|
|
165
|
+
const identityDetail = describeOpenClawBrainInstallIdentity(selectedInstall);
|
|
166
|
+
const activationRootState = inspectInstalledHookActivationRoot(selectedInstall.loaderEntryPath);
|
|
103
167
|
if (allowlist.state === "blocked") {
|
|
104
168
|
return {
|
|
105
169
|
scope: "exact_openclaw_home",
|
|
106
170
|
openclawHome: resolvedHome,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
171
|
+
extensionDir: selectedInstall.extensionDir,
|
|
172
|
+
hookPath: selectedInstall.loaderEntryPath,
|
|
173
|
+
runtimeGuardPath: selectedInstall.runtimeGuardPath,
|
|
174
|
+
manifestPath: selectedInstall.manifestPath,
|
|
175
|
+
packageJsonPath: selectedInstall.packageJsonPath,
|
|
176
|
+
manifestId: selectedInstall.manifestId,
|
|
177
|
+
installId: selectedInstall.installId,
|
|
178
|
+
packageName: selectedInstall.packageName,
|
|
179
|
+
installLayout: selectedInstall.installLayout,
|
|
180
|
+
additionalInstallCount: installedPlugin.additionalInstalls.length,
|
|
110
181
|
installState: "blocked_by_allowlist",
|
|
111
182
|
loadability: "blocked",
|
|
112
183
|
pluginAllowlistState: allowlist.state,
|
|
113
184
|
desynced: true,
|
|
114
|
-
detail: `profile hook
|
|
115
|
-
|
|
185
|
+
detail: `profile hook is installed via ${layoutLabel} at ${shortenPath(selectedInstall.extensionDir)} (${identityDetail}), but ${allowlist.detail}; ` +
|
|
186
|
+
`the on-disk hook is not loadable and OpenClaw will not load it${additionalInstallDetail}`
|
|
116
187
|
};
|
|
117
188
|
}
|
|
118
189
|
if (allowlist.state === "invalid") {
|
|
119
190
|
return {
|
|
120
191
|
scope: "exact_openclaw_home",
|
|
121
192
|
openclawHome: resolvedHome,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
193
|
+
extensionDir: selectedInstall.extensionDir,
|
|
194
|
+
hookPath: selectedInstall.loaderEntryPath,
|
|
195
|
+
runtimeGuardPath: selectedInstall.runtimeGuardPath,
|
|
196
|
+
manifestPath: selectedInstall.manifestPath,
|
|
197
|
+
packageJsonPath: selectedInstall.packageJsonPath,
|
|
198
|
+
manifestId: selectedInstall.manifestId,
|
|
199
|
+
installId: selectedInstall.installId,
|
|
200
|
+
packageName: selectedInstall.packageName,
|
|
201
|
+
installLayout: selectedInstall.installLayout,
|
|
202
|
+
additionalInstallCount: installedPlugin.additionalInstalls.length,
|
|
125
203
|
installState: "blocked_by_allowlist",
|
|
126
204
|
loadability: "blocked",
|
|
127
205
|
pluginAllowlistState: allowlist.state,
|
|
128
206
|
desynced: true,
|
|
129
|
-
detail: `profile hook
|
|
130
|
-
|
|
207
|
+
detail: `profile hook is installed via ${layoutLabel} at ${shortenPath(selectedInstall.extensionDir)} (${identityDetail}), but ${allowlist.detail}; ` +
|
|
208
|
+
`treat hook-load state as broken until install/attach repairs the config${additionalInstallDetail}`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (!activationRootState.ready) {
|
|
212
|
+
return {
|
|
213
|
+
scope: "exact_openclaw_home",
|
|
214
|
+
openclawHome: resolvedHome,
|
|
215
|
+
extensionDir: selectedInstall.extensionDir,
|
|
216
|
+
hookPath: selectedInstall.loaderEntryPath,
|
|
217
|
+
runtimeGuardPath: selectedInstall.runtimeGuardPath,
|
|
218
|
+
manifestPath: selectedInstall.manifestPath,
|
|
219
|
+
packageJsonPath: selectedInstall.packageJsonPath,
|
|
220
|
+
manifestId: selectedInstall.manifestId,
|
|
221
|
+
installId: selectedInstall.installId,
|
|
222
|
+
packageName: selectedInstall.packageName,
|
|
223
|
+
installLayout: selectedInstall.installLayout,
|
|
224
|
+
additionalInstallCount: installedPlugin.additionalInstalls.length,
|
|
225
|
+
installState: "installed",
|
|
226
|
+
loadability: "blocked",
|
|
227
|
+
pluginAllowlistState: allowlist.state,
|
|
228
|
+
desynced: true,
|
|
229
|
+
detail: `profile hook is installed via ${layoutLabel} at ${shortenPath(selectedInstall.extensionDir)} (${identityDetail}), but ${activationRootState.detail}${additionalInstallDetail}`
|
|
131
230
|
};
|
|
132
231
|
}
|
|
133
232
|
return {
|
|
134
233
|
scope: "exact_openclaw_home",
|
|
135
234
|
openclawHome: resolvedHome,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
235
|
+
extensionDir: selectedInstall.extensionDir,
|
|
236
|
+
hookPath: selectedInstall.loaderEntryPath,
|
|
237
|
+
runtimeGuardPath: selectedInstall.runtimeGuardPath,
|
|
238
|
+
manifestPath: selectedInstall.manifestPath,
|
|
239
|
+
packageJsonPath: selectedInstall.packageJsonPath,
|
|
240
|
+
manifestId: selectedInstall.manifestId,
|
|
241
|
+
installId: selectedInstall.installId,
|
|
242
|
+
packageName: selectedInstall.packageName,
|
|
243
|
+
installLayout: selectedInstall.installLayout,
|
|
244
|
+
additionalInstallCount: installedPlugin.additionalInstalls.length,
|
|
139
245
|
installState: "installed",
|
|
140
246
|
loadability: "loadable",
|
|
141
247
|
pluginAllowlistState: allowlist.state,
|
|
142
248
|
desynced: false,
|
|
143
|
-
detail: `profile hook is installed at ${shortenPath(extensionDir)}`
|
|
249
|
+
detail: `profile hook is installed via ${layoutLabel} at ${shortenPath(selectedInstall.extensionDir)} (${identityDetail})${additionalInstallDetail}`
|
|
144
250
|
};
|
|
145
251
|
}
|
|
146
252
|
export function summarizeOpenClawBrainHookLoad(inspection, statusProbeReady) {
|
|
@@ -151,4 +257,4 @@ export function summarizeOpenClawBrainHookLoad(inspection, statusProbeReady) {
|
|
|
151
257
|
: "not_ready"
|
|
152
258
|
};
|
|
153
259
|
}
|
|
154
|
-
//# sourceMappingURL=openclaw-hook-truth.js.map
|
|
260
|
+
//# sourceMappingURL=openclaw-hook-truth.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type OpenClawBrainInstallLayout = "generated_shadow_extension" | "native_package_plugin";
|
|
2
|
+
export interface OpenClawBrainInstalledPlugin {
|
|
3
|
+
openclawHome: string;
|
|
4
|
+
extensionsDir: string;
|
|
5
|
+
extensionDir: string;
|
|
6
|
+
manifestPath: string;
|
|
7
|
+
packageJsonPath: string;
|
|
8
|
+
loaderEntryPath: string | null;
|
|
9
|
+
runtimeGuardPath: string | null;
|
|
10
|
+
configuredEntries: string[];
|
|
11
|
+
manifestId: string | null;
|
|
12
|
+
installId: string | null;
|
|
13
|
+
packageName: string | null;
|
|
14
|
+
installLayout: OpenClawBrainInstallLayout;
|
|
15
|
+
}
|
|
16
|
+
export interface OpenClawBrainInstallTarget {
|
|
17
|
+
installLayout: OpenClawBrainInstallLayout;
|
|
18
|
+
extensionDir: string;
|
|
19
|
+
hookPath: string;
|
|
20
|
+
writeMode: "pin_native_package" | "write_shadow_extension";
|
|
21
|
+
selectedInstall: OpenClawBrainInstalledPlugin | null;
|
|
22
|
+
additionalInstalls: OpenClawBrainInstalledPlugin[];
|
|
23
|
+
}
|
|
24
|
+
export interface OpenClawBrainInstalledPluginLookup {
|
|
25
|
+
openclawHome: string;
|
|
26
|
+
extensionsDir: string;
|
|
27
|
+
selectedInstall: OpenClawBrainInstalledPlugin | null;
|
|
28
|
+
additionalInstalls: OpenClawBrainInstalledPlugin[];
|
|
29
|
+
}
|
|
30
|
+
export declare const OPENCLAWBRAIN_PLUGIN_ID = "openclawbrain";
|
|
31
|
+
export declare const OPENCLAWBRAIN_SHADOW_PACKAGE_NAME = "openclawbrain";
|
|
32
|
+
export declare const OPENCLAWBRAIN_NATIVE_PACKAGE_NAME = "@openclawbrain/openclaw";
|
|
33
|
+
export declare const OPENCLAWBRAIN_NATIVE_INSTALL_ID = "openclaw";
|
|
34
|
+
export declare function describeOpenClawBrainInstallLayout(installLayout: OpenClawBrainInstallLayout): string;
|
|
35
|
+
export declare function getOpenClawBrainKnownPluginIds(install: OpenClawBrainInstalledPlugin | null | undefined): string[];
|
|
36
|
+
export declare function describeOpenClawBrainInstallIdentity(install: OpenClawBrainInstalledPlugin): string;
|
|
37
|
+
export declare function findInstalledOpenClawBrainPlugin(openclawHome: string, pluginId?: string): OpenClawBrainInstalledPluginLookup;
|
|
38
|
+
export declare function resolveOpenClawBrainInstallTarget(openclawHome: string): OpenClawBrainInstallTarget;
|
|
39
|
+
export declare function pinInstalledOpenClawBrainPluginActivationRoot(loaderEntryPath: string, activationRoot: string): void;
|
|
40
|
+
export declare function resolveOpenClawHomeFromExtensionEntryPath(extensionEntryPath: string): string | null;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const OPENCLAWBRAIN_PLUGIN_ID = "openclawbrain";
|
|
4
|
+
export const OPENCLAWBRAIN_SHADOW_PACKAGE_NAME = "openclawbrain";
|
|
5
|
+
export const OPENCLAWBRAIN_NATIVE_PACKAGE_NAME = "@openclawbrain/openclaw";
|
|
6
|
+
export const OPENCLAWBRAIN_NATIVE_INSTALL_ID = "openclaw";
|
|
7
|
+
function readJsonObject(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
10
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function resolveConfiguredEntries(packageJson) {
|
|
20
|
+
const entries = packageJson?.openclaw?.extensions;
|
|
21
|
+
if (!Array.isArray(entries)) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return entries
|
|
25
|
+
.filter((entry) => typeof entry === "string")
|
|
26
|
+
.map((entry) => entry.trim())
|
|
27
|
+
.filter((entry) => entry.length > 0);
|
|
28
|
+
}
|
|
29
|
+
function resolveLoaderEntryPath(extensionDir, configuredEntries) {
|
|
30
|
+
for (const configuredEntry of configuredEntries) {
|
|
31
|
+
const candidate = path.join(extensionDir, configuredEntry);
|
|
32
|
+
if (existsSync(candidate)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function resolveRuntimeGuardPath(loaderEntryPath) {
|
|
39
|
+
const runtimeGuardPath = path.join(path.dirname(loaderEntryPath), "runtime-guard.js");
|
|
40
|
+
return existsSync(runtimeGuardPath) ? runtimeGuardPath : null;
|
|
41
|
+
}
|
|
42
|
+
function normalizeOptionalString(value) {
|
|
43
|
+
if (typeof value !== "string") {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
48
|
+
}
|
|
49
|
+
function normalizeInstallId(value) {
|
|
50
|
+
const normalized = normalizeOptionalString(value);
|
|
51
|
+
if (normalized === null) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (!normalized.startsWith("@")) {
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
const parts = normalized.split("/");
|
|
58
|
+
return normalizeOptionalString(parts[1] ?? null);
|
|
59
|
+
}
|
|
60
|
+
function listCandidateExtensionDirs(extensionsDir) {
|
|
61
|
+
const candidates = [];
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(extensionsDir, { withFileTypes: true });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.isDirectory()) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const directPath = path.join(extensionsDir, entry.name);
|
|
74
|
+
candidates.push(directPath);
|
|
75
|
+
if (!entry.name.startsWith("@")) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let scopedEntries;
|
|
79
|
+
try {
|
|
80
|
+
scopedEntries = readdirSync(directPath, { withFileTypes: true });
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const scopedEntry of scopedEntries) {
|
|
86
|
+
if (!scopedEntry.isDirectory()) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
candidates.push(path.join(directPath, scopedEntry.name));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return candidates.sort((left, right) => left.localeCompare(right));
|
|
93
|
+
}
|
|
94
|
+
function inferInstallLayout(input) {
|
|
95
|
+
if (input.packageName === OPENCLAWBRAIN_NATIVE_PACKAGE_NAME) {
|
|
96
|
+
return "native_package_plugin";
|
|
97
|
+
}
|
|
98
|
+
if (input.packageName === OPENCLAWBRAIN_SHADOW_PACKAGE_NAME) {
|
|
99
|
+
return "generated_shadow_extension";
|
|
100
|
+
}
|
|
101
|
+
if (input.installId === OPENCLAWBRAIN_NATIVE_INSTALL_ID &&
|
|
102
|
+
input.loaderEntryPath !== null &&
|
|
103
|
+
input.loaderEntryPath.endsWith(path.join("dist", "extension", "index.js"))) {
|
|
104
|
+
return "native_package_plugin";
|
|
105
|
+
}
|
|
106
|
+
if (input.loaderEntryPath !== null &&
|
|
107
|
+
input.loaderEntryPath.endsWith(path.join("dist", "extension", "index.js"))) {
|
|
108
|
+
return "native_package_plugin";
|
|
109
|
+
}
|
|
110
|
+
if (input.loaderEntryPath !== null &&
|
|
111
|
+
path.basename(input.loaderEntryPath) === "index.ts" &&
|
|
112
|
+
(input.installId === OPENCLAWBRAIN_PLUGIN_ID ||
|
|
113
|
+
path.basename(input.extensionDir) === OPENCLAWBRAIN_PLUGIN_ID)) {
|
|
114
|
+
return "generated_shadow_extension";
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function compareInstalledPluginPriority(left, right) {
|
|
119
|
+
const leftCompleteness = Number(left.loaderEntryPath !== null) + Number(left.runtimeGuardPath !== null);
|
|
120
|
+
const rightCompleteness = Number(right.loaderEntryPath !== null) + Number(right.runtimeGuardPath !== null);
|
|
121
|
+
if (leftCompleteness !== rightCompleteness) {
|
|
122
|
+
return rightCompleteness - leftCompleteness;
|
|
123
|
+
}
|
|
124
|
+
const leftLayoutRank = left.installLayout === "native_package_plugin" ? 0 : 1;
|
|
125
|
+
const rightLayoutRank = right.installLayout === "native_package_plugin" ? 0 : 1;
|
|
126
|
+
if (leftLayoutRank !== rightLayoutRank) {
|
|
127
|
+
return leftLayoutRank - rightLayoutRank;
|
|
128
|
+
}
|
|
129
|
+
return left.extensionDir.localeCompare(right.extensionDir);
|
|
130
|
+
}
|
|
131
|
+
function inspectOpenClawBrainPluginInstall(extensionDir, pluginId) {
|
|
132
|
+
const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
|
|
133
|
+
if (!existsSync(manifestPath)) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const manifest = readJsonObject(manifestPath);
|
|
137
|
+
const manifestId = normalizeOptionalString(manifest?.id);
|
|
138
|
+
if (manifestId !== pluginId) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
142
|
+
if (!existsSync(packageJsonPath)) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const packageJson = readJsonObject(packageJsonPath);
|
|
146
|
+
if (packageJson === null) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const configuredEntries = resolveConfiguredEntries(packageJson);
|
|
150
|
+
const loaderEntryPath = resolveLoaderEntryPath(extensionDir, configuredEntries);
|
|
151
|
+
const packageName = typeof packageJson.name === "string" && packageJson.name.trim().length > 0
|
|
152
|
+
? packageJson.name.trim()
|
|
153
|
+
: null;
|
|
154
|
+
const installId = normalizeInstallId(packageName) ?? normalizeInstallId(path.basename(extensionDir));
|
|
155
|
+
const installLayout = inferInstallLayout({
|
|
156
|
+
extensionDir,
|
|
157
|
+
packageName,
|
|
158
|
+
installId,
|
|
159
|
+
loaderEntryPath
|
|
160
|
+
});
|
|
161
|
+
if (installLayout === null) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
extensionDir,
|
|
166
|
+
manifestPath,
|
|
167
|
+
packageJsonPath,
|
|
168
|
+
loaderEntryPath,
|
|
169
|
+
runtimeGuardPath: loaderEntryPath === null ? null : resolveRuntimeGuardPath(loaderEntryPath),
|
|
170
|
+
configuredEntries,
|
|
171
|
+
manifestId,
|
|
172
|
+
installId,
|
|
173
|
+
packageName,
|
|
174
|
+
installLayout
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export function describeOpenClawBrainInstallLayout(installLayout) {
|
|
178
|
+
return installLayout === "native_package_plugin"
|
|
179
|
+
? "native package plugin"
|
|
180
|
+
: "generated shadow extension";
|
|
181
|
+
}
|
|
182
|
+
export function getOpenClawBrainKnownPluginIds(install) {
|
|
183
|
+
const ids = [];
|
|
184
|
+
const push = (value) => {
|
|
185
|
+
const normalized = normalizeOptionalString(value);
|
|
186
|
+
if (normalized === null || ids.includes(normalized)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
ids.push(normalized);
|
|
190
|
+
};
|
|
191
|
+
push(OPENCLAWBRAIN_PLUGIN_ID);
|
|
192
|
+
push(install?.manifestId ?? null);
|
|
193
|
+
push(install?.installId ?? null);
|
|
194
|
+
if (install?.packageName === OPENCLAWBRAIN_NATIVE_PACKAGE_NAME) {
|
|
195
|
+
push(OPENCLAWBRAIN_NATIVE_INSTALL_ID);
|
|
196
|
+
}
|
|
197
|
+
return ids;
|
|
198
|
+
}
|
|
199
|
+
export function describeOpenClawBrainInstallIdentity(install) {
|
|
200
|
+
return [
|
|
201
|
+
`manifest=${install.manifestId ?? OPENCLAWBRAIN_PLUGIN_ID}`,
|
|
202
|
+
`install=${install.installId ?? path.basename(install.extensionDir)}`,
|
|
203
|
+
`package=${install.packageName ?? "unknown"}`
|
|
204
|
+
].join(" ");
|
|
205
|
+
}
|
|
206
|
+
export function findInstalledOpenClawBrainPlugin(openclawHome, pluginId = OPENCLAWBRAIN_PLUGIN_ID) {
|
|
207
|
+
const resolvedOpenclawHome = path.resolve(openclawHome);
|
|
208
|
+
const extensionsDir = path.join(resolvedOpenclawHome, "extensions");
|
|
209
|
+
if (!existsSync(extensionsDir)) {
|
|
210
|
+
return {
|
|
211
|
+
openclawHome: resolvedOpenclawHome,
|
|
212
|
+
extensionsDir,
|
|
213
|
+
selectedInstall: null,
|
|
214
|
+
additionalInstalls: []
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const installs = listCandidateExtensionDirs(extensionsDir)
|
|
218
|
+
.map((extensionDir) => inspectOpenClawBrainPluginInstall(extensionDir, pluginId))
|
|
219
|
+
.filter((install) => install !== null)
|
|
220
|
+
.map((install) => ({
|
|
221
|
+
...install,
|
|
222
|
+
openclawHome: resolvedOpenclawHome,
|
|
223
|
+
extensionsDir
|
|
224
|
+
}))
|
|
225
|
+
.sort(compareInstalledPluginPriority);
|
|
226
|
+
return {
|
|
227
|
+
openclawHome: resolvedOpenclawHome,
|
|
228
|
+
extensionsDir,
|
|
229
|
+
selectedInstall: installs[0] ?? null,
|
|
230
|
+
additionalInstalls: installs.slice(1)
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export function resolveOpenClawBrainInstallTarget(openclawHome) {
|
|
234
|
+
const installedPlugin = findInstalledOpenClawBrainPlugin(openclawHome);
|
|
235
|
+
const selectedInstall = installedPlugin.selectedInstall;
|
|
236
|
+
if (selectedInstall !== null &&
|
|
237
|
+
selectedInstall.installLayout === "native_package_plugin" &&
|
|
238
|
+
selectedInstall.loaderEntryPath !== null &&
|
|
239
|
+
selectedInstall.runtimeGuardPath !== null) {
|
|
240
|
+
return {
|
|
241
|
+
installLayout: selectedInstall.installLayout,
|
|
242
|
+
extensionDir: selectedInstall.extensionDir,
|
|
243
|
+
hookPath: selectedInstall.loaderEntryPath,
|
|
244
|
+
writeMode: "pin_native_package",
|
|
245
|
+
selectedInstall,
|
|
246
|
+
additionalInstalls: installedPlugin.additionalInstalls
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
const extensionDir = path.join(openclawHome, "extensions", OPENCLAWBRAIN_PLUGIN_ID);
|
|
250
|
+
return {
|
|
251
|
+
installLayout: "generated_shadow_extension",
|
|
252
|
+
extensionDir,
|
|
253
|
+
hookPath: path.join(extensionDir, "index.ts"),
|
|
254
|
+
writeMode: "write_shadow_extension",
|
|
255
|
+
selectedInstall,
|
|
256
|
+
additionalInstalls: installedPlugin.additionalInstalls
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
export function pinInstalledOpenClawBrainPluginActivationRoot(loaderEntryPath, activationRoot) {
|
|
260
|
+
const loaderSource = readFileSync(loaderEntryPath, "utf8");
|
|
261
|
+
const pinnedActivationRoot = `const ACTIVATION_ROOT = ${JSON.stringify(activationRoot)};`;
|
|
262
|
+
const nextLoaderSource = loaderSource.replace(/const\s+ACTIVATION_ROOT\s*=\s*["'`][^"'`]*["'`];/, pinnedActivationRoot);
|
|
263
|
+
if (nextLoaderSource === loaderSource) {
|
|
264
|
+
throw new Error(`Installed loader entry ${loaderEntryPath} does not expose a patchable ACTIVATION_ROOT constant`);
|
|
265
|
+
}
|
|
266
|
+
writeFileSync(loaderEntryPath, nextLoaderSource, "utf8");
|
|
267
|
+
}
|
|
268
|
+
export function resolveOpenClawHomeFromExtensionEntryPath(extensionEntryPath) {
|
|
269
|
+
let currentPath = path.resolve(extensionEntryPath);
|
|
270
|
+
while (true) {
|
|
271
|
+
const parentPath = path.dirname(currentPath);
|
|
272
|
+
if (parentPath === currentPath) {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
if (path.basename(parentPath) === "extensions") {
|
|
276
|
+
return path.dirname(parentPath);
|
|
277
|
+
}
|
|
278
|
+
currentPath = parentPath;
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=openclaw-plugin-install.js.map
|