oomi-ai 0.2.49 → 0.3.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.
Files changed (88) hide show
  1. package/README.md +227 -463
  2. package/agent_instructions.md +244 -234
  3. package/bin/oomi-ai.js +4028 -5797
  4. package/bin/sessionBridgeState.js +78 -78
  5. package/lib/openclawPaths.js +70 -71
  6. package/lib/openclawProfile.js +216 -216
  7. package/lib/personaApiClient.js +133 -303
  8. package/lib/spokenMetadata.js +137 -137
  9. package/openclaw.extension.js +341 -341
  10. package/openclaw.plugin.json +17 -17
  11. package/package.json +59 -59
  12. package/persona-app/README.md +27 -0
  13. package/persona-app/registry/v1.json +63 -0
  14. package/persona-app/schema/persona-app.v1.schema.json +90 -0
  15. package/skills/oomi/SKILL.md +165 -182
  16. package/skills/oomi/agent_instructions.md +99 -80
  17. package/lib/channelPluginClient.js +0 -119
  18. package/lib/openclawDevGateway.js +0 -384
  19. package/lib/personaJobExecutor.js +0 -139
  20. package/lib/personaJobPoller.js +0 -112
  21. package/lib/personaPortAllocator.js +0 -36
  22. package/lib/personaRuntimeManager.js +0 -496
  23. package/lib/personaRuntimeProcess.js +0 -924
  24. package/lib/personaRuntimeRegistry.js +0 -67
  25. package/lib/personaRuntimeSupervisor.js +0 -330
  26. package/lib/scaffold.js +0 -108
  27. package/lib/template.js +0 -45
  28. package/skills/oomi/config.json +0 -3
  29. package/skills/oomi/scripts/get_avatar_capabilities.py +0 -40
  30. package/skills/oomi/scripts/get_data.py +0 -49
  31. package/skills/oomi/scripts/install_agent_instructions.py +0 -78
  32. package/skills/oomi/scripts/send_goal.py +0 -53
  33. package/skills/oomi/scripts/sync.py +0 -46
  34. package/skills/oomi/setup.py +0 -41
  35. package/templates/persona-app/.env.example +0 -8
  36. package/templates/persona-app/README.md +0 -47
  37. package/templates/persona-app/eslint.config.js +0 -28
  38. package/templates/persona-app/index.html +0 -18
  39. package/templates/persona-app/oomi.runtime.json +0 -13
  40. package/templates/persona-app/package.json +0 -44
  41. package/templates/persona-app/persona/brief.md +0 -14
  42. package/templates/persona-app/persona.json +0 -14
  43. package/templates/persona-app/public/manifest.webmanifest +0 -8
  44. package/templates/persona-app/public/oomi.health.json +0 -6
  45. package/templates/persona-app/src/App.css +0 -379
  46. package/templates/persona-app/src/App.tsx +0 -17
  47. package/templates/persona-app/src/index.css +0 -53
  48. package/templates/persona-app/src/main.tsx +0 -23
  49. package/templates/persona-app/src/pages/HomePage.tsx +0 -127
  50. package/templates/persona-app/src/pages/ScenePage.tsx +0 -158
  51. package/templates/persona-app/src/persona/config.ts +0 -6
  52. package/templates/persona-app/src/persona/notes.ts +0 -9
  53. package/templates/persona-app/src/spatial.ts +0 -82
  54. package/templates/persona-app/src/vite-env.d.ts +0 -3
  55. package/templates/persona-app/template.json +0 -13
  56. package/templates/persona-app/tsconfig.app.json +0 -23
  57. package/templates/persona-app/tsconfig.json +0 -7
  58. package/templates/persona-app/tsconfig.node.json +0 -21
  59. package/templates/persona-app/vendor/webspatial/FORK.md +0 -6
  60. package/templates/persona-app/vendor/webspatial/core-sdk/LICENSE +0 -21
  61. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.d.ts +0 -906
  62. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js +0 -75
  63. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js.map +0 -1
  64. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.d.ts +0 -906
  65. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js +0 -3131
  66. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js.map +0 -1
  67. package/templates/persona-app/vendor/webspatial/core-sdk/package.json +0 -45
  68. package/templates/persona-app/vendor/webspatial/react-sdk/LICENSE +0 -21
  69. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.d.ts +0 -365
  70. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js +0 -4167
  71. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js.map +0 -1
  72. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.d.ts +0 -82
  73. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js +0 -66
  74. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js.map +0 -1
  75. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.d.ts +0 -2
  76. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js +0 -18
  77. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js.map +0 -1
  78. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.d.ts +0 -5
  79. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js +0 -66
  80. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js.map +0 -1
  81. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.d.ts +0 -1
  82. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js +0 -18
  83. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js.map +0 -1
  84. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.d.ts +0 -365
  85. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js +0 -4207
  86. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js.map +0 -1
  87. package/templates/persona-app/vendor/webspatial/react-sdk/package.json +0 -94
  88. package/templates/persona-app/vite.config.ts +0 -31
@@ -1,59 +1,59 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { generateKeyPairSync } from 'node:crypto';
4
-
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { generateKeyPairSync } from 'node:crypto';
4
+
5
5
  const OPENCLAW_PROFILE_VERSION = 1;
6
6
  const DEFAULT_PROFILE_PRESET = 'oomi-dev-local';
7
7
  const DEFAULT_SESSION_KEY = 'agent:main:webchat:channel:oomi';
8
8
  const DEFAULT_GATEWAY_PORT = 18789;
9
9
  const DEFAULT_PLUGIN_TRUST_MODE = 'auto-discovery';
10
10
  const DEFAULT_MODEL_AUTH_MODE = 'oomi-managed';
11
-
12
- function trimString(value) {
13
- return typeof value === 'string' ? value.trim() : '';
14
- }
15
-
16
- function ensureDir(dirPath) {
17
- fs.mkdirSync(dirPath, { recursive: true });
18
- }
19
-
20
- function readJsonSafe(filePath, fallback = {}) {
21
- if (!fs.existsSync(filePath)) return fallback;
22
- try {
23
- return JSON.parse(fs.readFileSync(filePath, 'utf8'));
24
- } catch {
25
- return fallback;
26
- }
27
- }
28
-
29
- function writeJson(filePath, value) {
30
- ensureDir(path.dirname(filePath));
31
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
32
- }
33
-
34
- function normalizeBoolean(value, fallback = false) {
35
- if (typeof value === 'boolean') return value;
36
- const normalized = trimString(value).toLowerCase();
37
- if (!normalized) return fallback;
38
- if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
39
- if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
40
- return fallback;
41
- }
42
-
43
- function normalizePositiveInteger(value, fallback) {
44
- const numeric = Number.parseInt(String(value || ''), 10);
45
- if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
46
- return numeric;
47
- }
48
-
49
- function sanitizeProfileId(value) {
50
- const normalized = trimString(value)
51
- .toLowerCase()
52
- .replace(/[^a-z0-9-]+/g, '-')
53
- .replace(/^-+|-+$/g, '');
54
- return normalized || DEFAULT_PROFILE_PRESET;
55
- }
56
-
11
+
12
+ function trimString(value) {
13
+ return typeof value === 'string' ? value.trim() : '';
14
+ }
15
+
16
+ function ensureDir(dirPath) {
17
+ fs.mkdirSync(dirPath, { recursive: true });
18
+ }
19
+
20
+ function readJsonSafe(filePath, fallback = {}) {
21
+ if (!fs.existsSync(filePath)) return fallback;
22
+ try {
23
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
24
+ } catch {
25
+ return fallback;
26
+ }
27
+ }
28
+
29
+ function writeJson(filePath, value) {
30
+ ensureDir(path.dirname(filePath));
31
+ fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
32
+ }
33
+
34
+ function normalizeBoolean(value, fallback = false) {
35
+ if (typeof value === 'boolean') return value;
36
+ const normalized = trimString(value).toLowerCase();
37
+ if (!normalized) return fallback;
38
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
39
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
40
+ return fallback;
41
+ }
42
+
43
+ function normalizePositiveInteger(value, fallback) {
44
+ const numeric = Number.parseInt(String(value || ''), 10);
45
+ if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
46
+ return numeric;
47
+ }
48
+
49
+ function sanitizeProfileId(value) {
50
+ const normalized = trimString(value)
51
+ .toLowerCase()
52
+ .replace(/[^a-z0-9-]+/g, '-')
53
+ .replace(/^-+|-+$/g, '');
54
+ return normalized || DEFAULT_PROFILE_PRESET;
55
+ }
56
+
57
57
  function normalizePluginTrustMode(value) {
58
58
  return trimString(value) === 'plugins.allow' ? 'plugins.allow' : DEFAULT_PLUGIN_TRUST_MODE;
59
59
  }
@@ -61,87 +61,87 @@ function normalizePluginTrustMode(value) {
61
61
  function normalizeModelAuthMode(value) {
62
62
  return trimString(value) === 'provider-env' ? 'provider-env' : DEFAULT_MODEL_AUTH_MODE;
63
63
  }
64
-
65
- function resolveConfigPath(openclawHome, explicitPath = '') {
66
- const targetPath = trimString(explicitPath);
67
- if (targetPath) return path.resolve(targetPath);
68
-
69
- const candidates = [
70
- path.join(openclawHome, 'clawdbot.json'),
71
- path.join(openclawHome, 'openclaw.json'),
72
- ];
73
- const existing = candidates.find((candidate) => fs.existsSync(candidate));
74
- return existing || candidates[1];
75
- }
76
-
77
- function ensureDeviceIdentity(identityPath, deviceId) {
78
- if (!trimString(deviceId) || fs.existsSync(identityPath)) return false;
79
- const { publicKey, privateKey } = generateKeyPairSync('ed25519');
80
- writeJson(identityPath, {
81
- version: 1,
82
- deviceId,
83
- publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
84
- privateKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
85
- });
86
- return true;
87
- }
88
-
89
- function clearPluginAllowList(config) {
90
- const nextConfig = { ...(config || {}) };
91
- const plugins = typeof nextConfig.plugins === 'object' && nextConfig.plugins ? { ...nextConfig.plugins } : {};
92
- if (Object.prototype.hasOwnProperty.call(plugins, 'allow')) {
93
- delete plugins.allow;
94
- }
95
- nextConfig.plugins = plugins;
96
- return nextConfig;
97
- }
98
-
99
- function ensurePluginAllowList(config) {
100
- const nextConfig = { ...(config || {}) };
101
- const plugins = typeof nextConfig.plugins === 'object' && nextConfig.plugins ? { ...nextConfig.plugins } : {};
102
- plugins.allow = ['oomi-ai'];
103
- nextConfig.plugins = plugins;
104
- return nextConfig;
105
- }
106
-
107
- function applyGatewayConfig(config, gateway) {
108
- const nextConfig = { ...(config || {}) };
109
- const nextGateway = typeof nextConfig.gateway === 'object' && nextConfig.gateway ? { ...nextConfig.gateway } : {};
110
- nextGateway.port = normalizePositiveInteger(gateway?.port, DEFAULT_GATEWAY_PORT);
111
- nextGateway.mode = trimString(gateway?.mode) || 'local';
112
- nextGateway.bind = trimString(gateway?.bind) || 'loopback';
113
- nextGateway.auth = {
114
- mode: trimString(gateway?.auth?.mode) || 'token',
115
- token: trimString(gateway?.auth?.token),
116
- };
117
- nextGateway.tailscale = {
118
- mode: 'off',
119
- resetOnExit: false,
120
- };
121
- nextConfig.gateway = nextGateway;
122
- return nextConfig;
123
- }
124
-
64
+
65
+ function resolveConfigPath(openclawHome, explicitPath = '') {
66
+ const targetPath = trimString(explicitPath);
67
+ if (targetPath) return path.resolve(targetPath);
68
+
69
+ const candidates = [
70
+ path.join(openclawHome, 'openclaw.json'),
71
+ path.join(openclawHome, 'clawdbot.json'),
72
+ ];
73
+ const existing = candidates.find((candidate) => fs.existsSync(candidate));
74
+ return existing || candidates[1];
75
+ }
76
+
77
+ function ensureDeviceIdentity(identityPath, deviceId) {
78
+ if (!trimString(deviceId) || fs.existsSync(identityPath)) return false;
79
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519');
80
+ writeJson(identityPath, {
81
+ version: 1,
82
+ deviceId,
83
+ publicKeyPem: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
84
+ privateKeyPem: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
85
+ });
86
+ return true;
87
+ }
88
+
89
+ function clearPluginAllowList(config) {
90
+ const nextConfig = { ...(config || {}) };
91
+ const plugins = typeof nextConfig.plugins === 'object' && nextConfig.plugins ? { ...nextConfig.plugins } : {};
92
+ if (Object.prototype.hasOwnProperty.call(plugins, 'allow')) {
93
+ delete plugins.allow;
94
+ }
95
+ nextConfig.plugins = plugins;
96
+ return nextConfig;
97
+ }
98
+
99
+ function ensurePluginAllowList(config) {
100
+ const nextConfig = { ...(config || {}) };
101
+ const plugins = typeof nextConfig.plugins === 'object' && nextConfig.plugins ? { ...nextConfig.plugins } : {};
102
+ plugins.allow = ['oomi-ai'];
103
+ nextConfig.plugins = plugins;
104
+ return nextConfig;
105
+ }
106
+
107
+ function applyGatewayConfig(config, gateway) {
108
+ const nextConfig = { ...(config || {}) };
109
+ const nextGateway = typeof nextConfig.gateway === 'object' && nextConfig.gateway ? { ...nextConfig.gateway } : {};
110
+ nextGateway.port = normalizePositiveInteger(gateway?.port, DEFAULT_GATEWAY_PORT);
111
+ nextGateway.mode = trimString(gateway?.mode) || 'local';
112
+ nextGateway.bind = trimString(gateway?.bind) || 'loopback';
113
+ nextGateway.auth = {
114
+ mode: trimString(gateway?.auth?.mode) || 'token',
115
+ token: trimString(gateway?.auth?.token),
116
+ };
117
+ nextGateway.tailscale = {
118
+ mode: 'off',
119
+ resetOnExit: false,
120
+ };
121
+ nextConfig.gateway = nextGateway;
122
+ return nextConfig;
123
+ }
124
+
125
125
  function applyOomiChannelConfig(config, oomiChannel) {
126
- const nextConfig = { ...(config || {}) };
127
- const channels = typeof nextConfig.channels === 'object' && nextConfig.channels ? { ...nextConfig.channels } : {};
128
- const oomi = typeof channels.oomi === 'object' && channels.oomi ? { ...channels.oomi } : {};
129
- const accounts = typeof oomi.accounts === 'object' && oomi.accounts ? { ...oomi.accounts } : {};
130
- const defaultAccount = typeof accounts.default === 'object' && accounts.default ? { ...accounts.default } : {};
131
-
132
- accounts.default = {
133
- enabled: true,
134
- requestTimeoutMs: normalizePositiveInteger(oomiChannel?.requestTimeoutMs, 15000),
135
- ...defaultAccount,
136
- backendUrl: trimString(oomiChannel?.backendUrl),
137
- deviceToken: trimString(oomiChannel?.deviceToken),
138
- defaultSessionKey: trimString(oomiChannel?.defaultSessionKey) || DEFAULT_SESSION_KEY,
139
- };
140
-
141
- oomi.defaultAccountId = 'default';
142
- oomi.accounts = accounts;
143
- channels.oomi = oomi;
144
- nextConfig.channels = channels;
126
+ const nextConfig = { ...(config || {}) };
127
+ const channels = typeof nextConfig.channels === 'object' && nextConfig.channels ? { ...nextConfig.channels } : {};
128
+ const oomi = typeof channels.oomi === 'object' && channels.oomi ? { ...channels.oomi } : {};
129
+ const accounts = typeof oomi.accounts === 'object' && oomi.accounts ? { ...oomi.accounts } : {};
130
+ const defaultAccount = typeof accounts.default === 'object' && accounts.default ? { ...accounts.default } : {};
131
+
132
+ accounts.default = {
133
+ enabled: true,
134
+ requestTimeoutMs: normalizePositiveInteger(oomiChannel?.requestTimeoutMs, 15000),
135
+ ...defaultAccount,
136
+ backendUrl: trimString(oomiChannel?.backendUrl),
137
+ deviceToken: trimString(oomiChannel?.deviceToken),
138
+ defaultSessionKey: trimString(oomiChannel?.defaultSessionKey) || DEFAULT_SESSION_KEY,
139
+ };
140
+
141
+ oomi.defaultAccountId = 'default';
142
+ oomi.accounts = accounts;
143
+ channels.oomi = oomi;
144
+ nextConfig.channels = channels;
145
145
  return nextConfig;
146
146
  }
147
147
 
@@ -159,7 +159,7 @@ function removeOomiChannelConfig(config) {
159
159
  }
160
160
  return nextConfig;
161
161
  }
162
-
162
+
163
163
  export function buildOomiDevLocalProfile(options = {}) {
164
164
  const profileId = sanitizeProfileId(options.profileId || options.id || DEFAULT_PROFILE_PRESET);
165
165
  const pluginTrustMode = normalizePluginTrustMode(options.pluginTrustMode);
@@ -167,99 +167,99 @@ export function buildOomiDevLocalProfile(options = {}) {
167
167
  const modelAuthMode = normalizeModelAuthMode(options.modelAuthMode);
168
168
 
169
169
  return {
170
- version: OPENCLAW_PROFILE_VERSION,
171
- preset: DEFAULT_PROFILE_PRESET,
172
- profileId,
173
- label: trimString(options.label) || 'Oomi Local Dev',
174
- workspace: {
175
- root: trimString(options.workspaceRoot),
176
- },
177
- device: {
178
- id: trimString(options.deviceId),
179
- },
180
- gateway: {
181
- port: normalizePositiveInteger(options.gatewayPort, DEFAULT_GATEWAY_PORT),
182
- mode: 'local',
183
- bind: 'loopback',
184
- auth: {
185
- mode: 'token',
186
- token: trimString(options.gatewayToken),
187
- },
188
- },
189
- oomiChannel: {
190
- enabled: enableOomiChannel,
191
- backendUrl: trimString(options.backendUrl),
192
- deviceToken: trimString(options.deviceToken),
193
- defaultSessionKey: trimString(options.defaultSessionKey) || DEFAULT_SESSION_KEY,
194
- requestTimeoutMs: normalizePositiveInteger(options.requestTimeoutMs, 15000),
195
- pluginTrustMode,
196
- },
170
+ version: OPENCLAW_PROFILE_VERSION,
171
+ preset: trimString(options.preset) || DEFAULT_PROFILE_PRESET,
172
+ profileId,
173
+ label: trimString(options.label) || 'Oomi Local Dev',
174
+ workspace: {
175
+ root: trimString(options.workspaceRoot),
176
+ },
177
+ device: {
178
+ id: trimString(options.deviceId),
179
+ },
180
+ gateway: {
181
+ port: normalizePositiveInteger(options.gatewayPort, DEFAULT_GATEWAY_PORT),
182
+ mode: 'local',
183
+ bind: 'loopback',
184
+ auth: {
185
+ mode: 'token',
186
+ token: trimString(options.gatewayToken),
187
+ },
188
+ },
189
+ oomiChannel: {
190
+ enabled: enableOomiChannel,
191
+ backendUrl: trimString(options.backendUrl),
192
+ deviceToken: trimString(options.deviceToken),
193
+ defaultSessionKey: trimString(options.defaultSessionKey) || DEFAULT_SESSION_KEY,
194
+ requestTimeoutMs: normalizePositiveInteger(options.requestTimeoutMs, 15000),
195
+ pluginTrustMode,
196
+ },
197
197
  model: {
198
198
  preset: trimString(options.modelPreset || 'openrouter-free'),
199
199
  authMode: modelAuthMode,
200
200
  },
201
201
  };
202
202
  }
203
-
204
- export function readOpenclawProfile(filePath) {
205
- const profilePath = path.resolve(filePath);
206
- return readJsonSafe(profilePath, null);
207
- }
208
-
209
- export function writeOpenclawProfile(filePath, profile) {
210
- const profilePath = path.resolve(filePath);
211
- writeJson(profilePath, profile);
212
- return profilePath;
213
- }
214
-
215
- export function applyOpenclawProfile({
216
- profile,
217
- openclawHome,
218
- configPath = '',
219
- identityPath = '',
220
- ensureIdentity = true,
221
- } = {}) {
222
- if (!profile || typeof profile !== 'object') {
223
- throw new Error('OpenClaw profile is required.');
224
- }
225
-
226
- const homeRoot = path.resolve(trimString(openclawHome) || process.cwd());
227
- const resolvedConfigPath = resolveConfigPath(homeRoot, configPath);
228
- const resolvedIdentityPath = path.resolve(
229
- trimString(identityPath) || path.join(homeRoot, 'identity', 'device.json')
230
- );
231
-
232
- let nextConfig = readJsonSafe(resolvedConfigPath, {});
233
- nextConfig = applyGatewayConfig(nextConfig, profile.gateway);
234
-
235
- const pluginTrustMode = normalizePluginTrustMode(profile?.oomiChannel?.pluginTrustMode);
236
- nextConfig =
237
- pluginTrustMode === 'plugins.allow'
238
- ? ensurePluginAllowList(nextConfig)
239
- : clearPluginAllowList(nextConfig);
240
-
203
+
204
+ export function readOpenclawProfile(filePath) {
205
+ const profilePath = path.resolve(filePath);
206
+ return readJsonSafe(profilePath, null);
207
+ }
208
+
209
+ export function writeOpenclawProfile(filePath, profile) {
210
+ const profilePath = path.resolve(filePath);
211
+ writeJson(profilePath, profile);
212
+ return profilePath;
213
+ }
214
+
215
+ export function applyOpenclawProfile({
216
+ profile,
217
+ openclawHome,
218
+ configPath = '',
219
+ identityPath = '',
220
+ ensureIdentity = true,
221
+ } = {}) {
222
+ if (!profile || typeof profile !== 'object') {
223
+ throw new Error('OpenClaw profile is required.');
224
+ }
225
+
226
+ const homeRoot = path.resolve(trimString(openclawHome) || process.cwd());
227
+ const resolvedConfigPath = resolveConfigPath(homeRoot, configPath);
228
+ const resolvedIdentityPath = path.resolve(
229
+ trimString(identityPath) || path.join(homeRoot, 'identity', 'device.json')
230
+ );
231
+
232
+ let nextConfig = readJsonSafe(resolvedConfigPath, {});
233
+ nextConfig = applyGatewayConfig(nextConfig, profile.gateway);
234
+
235
+ const pluginTrustMode = normalizePluginTrustMode(profile?.oomiChannel?.pluginTrustMode);
236
+ nextConfig =
237
+ pluginTrustMode === 'plugins.allow'
238
+ ? ensurePluginAllowList(nextConfig)
239
+ : clearPluginAllowList(nextConfig);
240
+
241
241
  if (normalizeBoolean(profile?.oomiChannel?.enabled, false)) {
242
242
  nextConfig = applyOomiChannelConfig(nextConfig, profile.oomiChannel);
243
243
  } else {
244
244
  nextConfig = removeOomiChannelConfig(nextConfig);
245
245
  }
246
-
247
- writeJson(resolvedConfigPath, nextConfig);
248
-
249
- const identityCreated =
250
- ensureIdentity && trimString(profile?.device?.id)
251
- ? ensureDeviceIdentity(resolvedIdentityPath, profile.device.id)
252
- : false;
253
-
254
- return {
255
- ok: true,
256
- profileId: sanitizeProfileId(profile.profileId),
257
- preset: trimString(profile.preset) || DEFAULT_PROFILE_PRESET,
258
- configPath: resolvedConfigPath,
259
- identityPath: resolvedIdentityPath,
260
- openclawHome: homeRoot,
261
- pluginTrustMode,
262
- oomiChannelEnabled: normalizeBoolean(profile?.oomiChannel?.enabled, false),
263
- identityCreated,
264
- };
265
- }
246
+
247
+ writeJson(resolvedConfigPath, nextConfig);
248
+
249
+ const identityCreated =
250
+ ensureIdentity && trimString(profile?.device?.id)
251
+ ? ensureDeviceIdentity(resolvedIdentityPath, profile.device.id)
252
+ : false;
253
+
254
+ return {
255
+ ok: true,
256
+ profileId: sanitizeProfileId(profile.profileId),
257
+ preset: trimString(profile.preset) || DEFAULT_PROFILE_PRESET,
258
+ configPath: resolvedConfigPath,
259
+ identityPath: resolvedIdentityPath,
260
+ openclawHome: homeRoot,
261
+ pluginTrustMode,
262
+ oomiChannelEnabled: normalizeBoolean(profile?.oomiChannel?.enabled, false),
263
+ identityCreated,
264
+ };
265
+ }