oomi-ai 0.2.29 → 0.2.39

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.
@@ -0,0 +1,265 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { generateKeyPairSync } from 'node:crypto';
4
+
5
+ const OPENCLAW_PROFILE_VERSION = 1;
6
+ const DEFAULT_PROFILE_PRESET = 'oomi-dev-local';
7
+ const DEFAULT_SESSION_KEY = 'agent:main:webchat:channel:oomi';
8
+ const DEFAULT_GATEWAY_PORT = 18789;
9
+ const DEFAULT_PLUGIN_TRUST_MODE = 'auto-discovery';
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
+
57
+ function normalizePluginTrustMode(value) {
58
+ return trimString(value) === 'plugins.allow' ? 'plugins.allow' : DEFAULT_PLUGIN_TRUST_MODE;
59
+ }
60
+
61
+ function normalizeModelAuthMode(value) {
62
+ return trimString(value) === 'provider-env' ? 'provider-env' : DEFAULT_MODEL_AUTH_MODE;
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
+
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;
145
+ return nextConfig;
146
+ }
147
+
148
+ function removeOomiChannelConfig(config) {
149
+ const nextConfig = { ...(config || {}) };
150
+ const channels =
151
+ typeof nextConfig.channels === 'object' && nextConfig.channels ? { ...nextConfig.channels } : {};
152
+ if (Object.prototype.hasOwnProperty.call(channels, 'oomi')) {
153
+ delete channels.oomi;
154
+ }
155
+ if (Object.keys(channels).length > 0) {
156
+ nextConfig.channels = channels;
157
+ } else if (Object.prototype.hasOwnProperty.call(nextConfig, 'channels')) {
158
+ delete nextConfig.channels;
159
+ }
160
+ return nextConfig;
161
+ }
162
+
163
+ export function buildOomiDevLocalProfile(options = {}) {
164
+ const profileId = sanitizeProfileId(options.profileId || options.id || DEFAULT_PROFILE_PRESET);
165
+ const pluginTrustMode = normalizePluginTrustMode(options.pluginTrustMode);
166
+ const enableOomiChannel = normalizeBoolean(options.enableOomiChannel, Boolean(trimString(options.deviceToken)));
167
+ const modelAuthMode = normalizeModelAuthMode(options.modelAuthMode);
168
+
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
+ },
197
+ model: {
198
+ preset: trimString(options.modelPreset || 'openrouter-free'),
199
+ authMode: modelAuthMode,
200
+ },
201
+ };
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
+
241
+ if (normalizeBoolean(profile?.oomiChannel?.enabled, false)) {
242
+ nextConfig = applyOomiChannelConfig(nextConfig, profile.oomiChannel);
243
+ } else {
244
+ nextConfig = removeOomiChannelConfig(nextConfig);
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
+ }