@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/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 blocked by OpenClaw config desync", report.hook.detail);
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.installState === "blocked_by_allowlist"
6397
- ? "current profile hook files exist, but OpenClaw will not load them until the plugin allowlist/config desync is repaired"
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 OpenClaw config currently blocks them from loading.";
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 { existsSync, readFileSync } from "node:fs";
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
- return plugins.allow.includes("openclawbrain")
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 openclawbrain`
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 openclawbrain`
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 extensionDir = path.join(resolvedHome, "extensions", "openclawbrain");
85
- const hookPath = path.join(extensionDir, "index.ts");
86
- const runtimeGuardPath = path.join(extensionDir, "runtime-guard.js");
87
- const manifestPath = path.join(extensionDir, "openclaw.plugin.json");
88
- if (!(existsSync(hookPath) && existsSync(runtimeGuardPath) && existsSync(manifestPath))) {
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
- hookPath,
93
- runtimeGuardPath,
94
- manifestPath,
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: `profile hook is not present at ${shortenPath(extensionDir)}`
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
- hookPath,
108
- runtimeGuardPath,
109
- manifestPath,
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 files exist at ${shortenPath(extensionDir)}, but ${allowlist.detail}; ` +
115
- "the on-disk hook is desynced and OpenClaw will not load it"
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
- hookPath,
123
- runtimeGuardPath,
124
- manifestPath,
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 files exist at ${shortenPath(extensionDir)}, but ${allowlist.detail}; ` +
130
- "treat hook-load state as broken until install/attach repairs the config"
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
- hookPath,
137
- runtimeGuardPath,
138
- manifestPath,
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