oomi-ai 0.2.41 → 0.2.42
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/lib/personaRuntimeManager.js +57 -38
- package/lib/personaRuntimeProcess.js +113 -19
- package/lib/personaRuntimeSupervisor.js +76 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -210,28 +210,32 @@ async function ensureWorkspaceInstall({
|
|
|
210
210
|
return true;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
function buildRuntimeRegistration({
|
|
214
|
-
localRuntime,
|
|
215
|
-
entryUrl,
|
|
216
|
-
transport,
|
|
217
|
-
}) {
|
|
218
|
-
const safeEntryUrl = trimString(entryUrl);
|
|
219
|
-
if (safeEntryUrl) {
|
|
220
|
-
return {
|
|
221
|
-
endpoint: safeEntryUrl,
|
|
222
|
-
transport: trimString(transport) || 'relay',
|
|
223
|
-
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
224
|
-
localPort: localRuntime.localPort,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
213
|
+
function buildRuntimeRegistration({
|
|
214
|
+
localRuntime,
|
|
215
|
+
entryUrl,
|
|
216
|
+
transport,
|
|
217
|
+
}) {
|
|
218
|
+
const safeEntryUrl = trimString(entryUrl);
|
|
219
|
+
if (safeEntryUrl) {
|
|
220
|
+
return {
|
|
221
|
+
endpoint: safeEntryUrl,
|
|
222
|
+
transport: trimString(transport) || 'relay',
|
|
223
|
+
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
224
|
+
localPort: localRuntime.localPort,
|
|
225
|
+
localEndpoint: localRuntime.endpoint,
|
|
226
|
+
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
endpoint: localRuntime.reachableEndpoint || localRuntime.endpoint,
|
|
232
|
+
transport: trimString(transport) || localRuntime.transport,
|
|
233
|
+
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
234
|
+
localPort: localRuntime.localPort,
|
|
235
|
+
localEndpoint: localRuntime.endpoint,
|
|
236
|
+
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
235
239
|
|
|
236
240
|
export async function launchManagedPersonaRuntime({
|
|
237
241
|
slug,
|
|
@@ -272,13 +276,25 @@ export async function launchManagedPersonaRuntime({
|
|
|
272
276
|
});
|
|
273
277
|
|
|
274
278
|
const healthPath = scaffoldInfo.healthPath || resolveHealthPath(workspacePath);
|
|
275
|
-
const preferredPort = previousState.localPort || scaffoldInfo.defaultPort;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
279
|
+
const preferredPort = previousState.localPort || scaffoldInfo.defaultPort;
|
|
280
|
+
const expectedDevCommand = resolvePersonaDevCommand({
|
|
281
|
+
workspacePath,
|
|
282
|
+
localPort: preferredPort,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
let reusingRunningProcess = false;
|
|
286
|
+
if (
|
|
287
|
+
!restart &&
|
|
288
|
+
Number.isInteger(previousState.pid) &&
|
|
289
|
+
isPersonaWorkspaceProcessRunning(previousState.pid, {
|
|
290
|
+
workspacePath,
|
|
291
|
+
expectedCommand: expectedDevCommand,
|
|
292
|
+
localPort: preferredPort,
|
|
293
|
+
})
|
|
294
|
+
) {
|
|
295
|
+
try {
|
|
296
|
+
await waitForPersonaRuntime({
|
|
297
|
+
healthcheckUrl: previousState.healthcheckUrl || buildLocalPersonaRuntime({
|
|
282
298
|
localPort: preferredPort,
|
|
283
299
|
healthPath,
|
|
284
300
|
}).healthcheckUrl,
|
|
@@ -326,24 +342,27 @@ export async function launchManagedPersonaRuntime({
|
|
|
326
342
|
entryUrl,
|
|
327
343
|
transport,
|
|
328
344
|
});
|
|
329
|
-
const runtimeState = updatePersonaRuntimeState(workspacePath, {
|
|
345
|
+
const runtimeState = updatePersonaRuntimeState(workspacePath, {
|
|
330
346
|
slug: safeSlug,
|
|
331
347
|
name: safeName,
|
|
332
348
|
description: safeDescription,
|
|
333
349
|
workspacePath,
|
|
334
350
|
templateVersion,
|
|
335
|
-
localPort: localRuntime.localPort,
|
|
336
|
-
localEndpoint: localRuntime.endpoint,
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
localPort: localRuntime.localPort,
|
|
352
|
+
localEndpoint: localRuntime.endpoint,
|
|
353
|
+
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
354
|
+
bindHost: localRuntime.bindHost,
|
|
355
|
+
reachableHost: localRuntime.reachableHost,
|
|
356
|
+
endpoint: registration.endpoint,
|
|
357
|
+
entryUrl: registration.endpoint,
|
|
339
358
|
transport: registration.transport,
|
|
340
359
|
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
341
360
|
pid: processInfo.pid,
|
|
342
361
|
logFilePath: processInfo.logFilePath,
|
|
343
|
-
status: 'running',
|
|
344
|
-
lastStartedAt: new Date().toISOString(),
|
|
345
|
-
devCommand: resolvePersonaDevCommand({ workspacePath, localPort }),
|
|
346
|
-
});
|
|
362
|
+
status: 'running',
|
|
363
|
+
lastStartedAt: new Date().toISOString(),
|
|
364
|
+
devCommand: resolvePersonaDevCommand({ workspacePath, localPort }),
|
|
365
|
+
});
|
|
347
366
|
|
|
348
367
|
return {
|
|
349
368
|
ok: true,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { spawn, spawnSync } from 'node:child_process';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -198,9 +199,96 @@ function normalizePositiveInteger(value) {
|
|
|
198
199
|
return Math.floor(parsed);
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
function
|
|
202
|
+
function isWildcardHost(host) {
|
|
203
|
+
const normalized = String(host || '').trim().toLowerCase();
|
|
204
|
+
return normalized === '0.0.0.0' || normalized === '::' || normalized === '[::]';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isLoopbackHost(host) {
|
|
208
|
+
const normalized = String(host || '').trim().toLowerCase();
|
|
209
|
+
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1' || normalized === '[::1]';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isPrivateIpv4(address) {
|
|
213
|
+
if (!/^\d+\.\d+\.\d+\.\d+$/u.test(address)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const [first, second] = address.split('.').map((segment) => Number(segment));
|
|
218
|
+
if (first === 10) return true;
|
|
219
|
+
if (first === 192 && second === 168) return true;
|
|
220
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function scorePersonaNetworkCandidate(candidate) {
|
|
225
|
+
let score = 0;
|
|
226
|
+
const name = String(candidate.name || '').toLowerCase();
|
|
227
|
+
const address = String(candidate.address || '');
|
|
228
|
+
|
|
229
|
+
if (isPrivateIpv4(address)) score += 40;
|
|
230
|
+
if (address.startsWith('192.168.')) score += 8;
|
|
231
|
+
if (address.startsWith('10.')) score += 6;
|
|
232
|
+
if (/ethernet|wi-?fi|wlan|en\d|eth\d/iu.test(name)) score += 12;
|
|
233
|
+
if (/hyper-v|vethernet|wsl|docker|vmware|virtualbox|tailscale|loopback|bridge/iu.test(name)) score -= 30;
|
|
234
|
+
if (address.startsWith('169.254.')) score -= 100;
|
|
235
|
+
return score;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function formatPersonaRuntimeHostForUrl(host) {
|
|
239
|
+
const safeHost = String(host || '').trim();
|
|
240
|
+
if (!safeHost) {
|
|
241
|
+
return '127.0.0.1';
|
|
242
|
+
}
|
|
243
|
+
if (safeHost.includes(':') && !safeHost.startsWith('[')) {
|
|
244
|
+
return `[${safeHost}]`;
|
|
245
|
+
}
|
|
246
|
+
return safeHost;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function resolvePersonaBindHost() {
|
|
202
250
|
const value = String(process.env.OOMI_PERSONA_BIND_HOST || '').trim();
|
|
203
|
-
return value || '
|
|
251
|
+
return value || '0.0.0.0';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function resolvePersonaReachableHost({
|
|
255
|
+
bindHost = resolvePersonaBindHost(),
|
|
256
|
+
env = process.env,
|
|
257
|
+
networkInterfaces = os.networkInterfaces(),
|
|
258
|
+
} = {}) {
|
|
259
|
+
const explicit = String(env.OOMI_PERSONA_PUBLIC_HOST || '').trim();
|
|
260
|
+
if (explicit) {
|
|
261
|
+
return explicit;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const safeBindHost = String(bindHost || '').trim();
|
|
265
|
+
if (safeBindHost && !isWildcardHost(safeBindHost) && !isLoopbackHost(safeBindHost)) {
|
|
266
|
+
return safeBindHost;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const candidates = [];
|
|
270
|
+
for (const [name, entries] of Object.entries(networkInterfaces || {})) {
|
|
271
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
272
|
+
if (!entry || entry.internal || entry.family !== 'IPv4') {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const address = String(entry.address || '').trim();
|
|
277
|
+
if (!address || isLoopbackHost(address) || address.startsWith('169.254.')) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
candidates.push({
|
|
282
|
+
name,
|
|
283
|
+
address,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const winner = candidates
|
|
289
|
+
.sort((left, right) => scorePersonaNetworkCandidate(right) - scorePersonaNetworkCandidate(left))[0];
|
|
290
|
+
|
|
291
|
+
return winner?.address || '127.0.0.1';
|
|
204
292
|
}
|
|
205
293
|
|
|
206
294
|
function trimString(value) {
|
|
@@ -754,23 +842,29 @@ export async function waitForPersonaRuntime({
|
|
|
754
842
|
throw new Error(`Timed out waiting for persona runtime healthcheck: ${message}`);
|
|
755
843
|
}
|
|
756
844
|
|
|
757
|
-
export function buildLocalPersonaRuntime({
|
|
758
|
-
localPort,
|
|
759
|
-
healthPath,
|
|
760
|
-
}) {
|
|
761
|
-
const port = Number(localPort);
|
|
762
|
-
if (!Number.isFinite(port) || port <= 0) {
|
|
763
|
-
throw new Error('Local port is required.');
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
845
|
+
export function buildLocalPersonaRuntime({
|
|
846
|
+
localPort,
|
|
847
|
+
healthPath,
|
|
848
|
+
}) {
|
|
849
|
+
const port = Number(localPort);
|
|
850
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
851
|
+
throw new Error('Local port is required.');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const bindHost = resolvePersonaBindHost();
|
|
855
|
+
const reachableHost = resolvePersonaReachableHost({ bindHost });
|
|
856
|
+
const endpoint = `http://127.0.0.1:${port}`;
|
|
857
|
+
const reachableEndpoint = `http://${formatPersonaRuntimeHostForUrl(reachableHost)}:${port}`;
|
|
858
|
+
return {
|
|
859
|
+
transport: 'local',
|
|
860
|
+
endpoint,
|
|
861
|
+
reachableEndpoint,
|
|
862
|
+
bindHost,
|
|
863
|
+
reachableHost,
|
|
864
|
+
localPort: port,
|
|
865
|
+
healthcheckUrl: `${endpoint}${healthPath}`,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
774
868
|
|
|
775
869
|
export function defaultPersonaWorkspaceRoot() {
|
|
776
870
|
return resolveOpenclawPersonasDir();
|
|
@@ -4,8 +4,8 @@ import path from 'node:path';
|
|
|
4
4
|
import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
|
|
5
5
|
import { createPersonaApiClient } from './personaApiClient.js';
|
|
6
6
|
import { launchManagedPersonaRuntime } from './personaRuntimeManager.js';
|
|
7
|
-
import { readPersonaRuntimeState } from './personaRuntimeRegistry.js';
|
|
8
|
-
import { isPersonaWorkspaceProcessRunning } from './personaRuntimeProcess.js';
|
|
7
|
+
import { readPersonaRuntimeState, updatePersonaRuntimeState } from './personaRuntimeRegistry.js';
|
|
8
|
+
import { buildLocalPersonaRuntime, isPersonaWorkspaceProcessRunning, resolvePersonaDevCommand } from './personaRuntimeProcess.js';
|
|
9
9
|
|
|
10
10
|
function trimString(value) {
|
|
11
11
|
return typeof value === 'string' ? value.trim() : '';
|
|
@@ -17,6 +17,20 @@ function wait(ms) {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function resolveHealthPath(healthcheckUrl) {
|
|
21
|
+
const safeUrl = trimString(healthcheckUrl);
|
|
22
|
+
if (!safeUrl) {
|
|
23
|
+
return '/oomi.health.json';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const parsed = new URL(safeUrl);
|
|
28
|
+
return `${parsed.pathname || '/oomi.health.json'}${parsed.search || ''}`;
|
|
29
|
+
} catch {
|
|
30
|
+
return '/oomi.health.json';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
function listWorkspacePaths(workspaceRoot) {
|
|
21
35
|
const roots = [trimString(workspaceRoot), trimString(resolveOpenclawLegacyPersonasDir())]
|
|
22
36
|
.filter(Boolean)
|
|
@@ -76,14 +90,29 @@ async function reconcileWorkspace({
|
|
|
76
90
|
const runtime = {
|
|
77
91
|
slug,
|
|
78
92
|
endpoint: trimString(state.endpoint || state.entryUrl || state.localEndpoint),
|
|
93
|
+
localEndpoint: trimString(state.localEndpoint),
|
|
94
|
+
reachableEndpoint: trimString(state.reachableEndpoint),
|
|
79
95
|
healthcheckUrl: trimString(state.healthcheckUrl),
|
|
80
96
|
transport: trimString(state.transport) || 'local',
|
|
81
97
|
localPort: Number.isFinite(Number(state.localPort)) ? Number(state.localPort) : null,
|
|
82
98
|
};
|
|
83
99
|
|
|
100
|
+
const localRuntime = runtime.localPort
|
|
101
|
+
? buildLocalPersonaRuntime({
|
|
102
|
+
localPort: runtime.localPort,
|
|
103
|
+
healthPath: resolveHealthPath(runtime.healthcheckUrl),
|
|
104
|
+
})
|
|
105
|
+
: null;
|
|
106
|
+
|
|
107
|
+
const expectedDevCommand = runtime.localPort
|
|
108
|
+
? resolvePersonaDevCommand({
|
|
109
|
+
workspacePath,
|
|
110
|
+
localPort: runtime.localPort,
|
|
111
|
+
})
|
|
112
|
+
: state.devCommand;
|
|
84
113
|
const processRunning = isPersonaWorkspaceProcessRunning(state.pid, {
|
|
85
114
|
workspacePath,
|
|
86
|
-
expectedCommand:
|
|
115
|
+
expectedCommand: expectedDevCommand,
|
|
87
116
|
localPort: runtime.localPort,
|
|
88
117
|
});
|
|
89
118
|
|
|
@@ -110,6 +139,8 @@ async function reconcileWorkspace({
|
|
|
110
139
|
effectiveRuntime = {
|
|
111
140
|
slug,
|
|
112
141
|
endpoint: launchResult.runtime.endpoint,
|
|
142
|
+
localEndpoint: launchResult.runtime.localEndpoint || launchResult.localRuntime.endpoint,
|
|
143
|
+
reachableEndpoint: launchResult.runtime.reachableEndpoint || launchResult.localRuntime.reachableEndpoint,
|
|
113
144
|
healthcheckUrl: launchResult.runtime.healthcheckUrl,
|
|
114
145
|
transport: launchResult.runtime.transport,
|
|
115
146
|
localPort: launchResult.runtime.localPort,
|
|
@@ -138,6 +169,48 @@ async function reconcileWorkspace({
|
|
|
138
169
|
return;
|
|
139
170
|
}
|
|
140
171
|
|
|
172
|
+
if (localRuntime) {
|
|
173
|
+
const desiredEndpoint = localRuntime.reachableEndpoint || runtime.endpoint;
|
|
174
|
+
const endpointChanged = desiredEndpoint && desiredEndpoint !== effectiveRuntime.endpoint;
|
|
175
|
+
const localEndpointChanged = localRuntime.endpoint !== effectiveRuntime.localEndpoint;
|
|
176
|
+
const reachableEndpointChanged = localRuntime.reachableEndpoint !== effectiveRuntime.reachableEndpoint;
|
|
177
|
+
|
|
178
|
+
if (endpointChanged || localEndpointChanged || reachableEndpointChanged) {
|
|
179
|
+
effectiveRuntime = {
|
|
180
|
+
...effectiveRuntime,
|
|
181
|
+
endpoint: desiredEndpoint,
|
|
182
|
+
localEndpoint: localRuntime.endpoint,
|
|
183
|
+
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
184
|
+
};
|
|
185
|
+
updatePersonaRuntimeState(workspacePath, {
|
|
186
|
+
endpoint: desiredEndpoint,
|
|
187
|
+
entryUrl: desiredEndpoint,
|
|
188
|
+
localEndpoint: localRuntime.endpoint,
|
|
189
|
+
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
190
|
+
bindHost: localRuntime.bindHost,
|
|
191
|
+
reachableHost: localRuntime.reachableHost,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
await client.registerRuntime({
|
|
196
|
+
slug,
|
|
197
|
+
endpoint: effectiveRuntime.endpoint,
|
|
198
|
+
healthcheckUrl: effectiveRuntime.healthcheckUrl,
|
|
199
|
+
localPort: effectiveRuntime.localPort,
|
|
200
|
+
transport: effectiveRuntime.transport,
|
|
201
|
+
startedAt: new Date().toISOString(),
|
|
202
|
+
});
|
|
203
|
+
} catch (error) {
|
|
204
|
+
logger.warn?.(
|
|
205
|
+
`[persona-runtime] registration refresh failed for ${slug}: ${
|
|
206
|
+
error instanceof Error ? error.message : String(error)
|
|
207
|
+
}`
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
141
214
|
try {
|
|
142
215
|
await client.heartbeatRuntime({
|
|
143
216
|
slug,
|
package/openclaw.plugin.json
CHANGED