oomi-ai 0.2.42 → 0.2.47
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/bin/oomi-ai.js +564 -101
- package/lib/personaApiClient.js +13 -4
- package/lib/personaRuntimeManager.js +13 -20
- package/lib/personaRuntimeProcess.js +41 -1
- package/lib/personaRuntimeSupervisor.js +77 -26
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/persona-app/oomi.runtime.json +1 -1
- package/templates/persona-app/package.json +3 -3
- package/templates/persona-app/template.json +1 -1
package/bin/oomi-ai.js
CHANGED
|
@@ -14,35 +14,37 @@ import { startPersonaJobPoller } from '../lib/personaJobPoller.js';
|
|
|
14
14
|
import { startPersonaRuntimeSupervisor } from '../lib/personaRuntimeSupervisor.js';
|
|
15
15
|
import { executePersonaJob, extractPersonaJobPayload } from '../lib/personaJobExecutor.js';
|
|
16
16
|
import { inferSpokenMetadataFromContent, normalizeSpokenMetadata } from '../lib/spokenMetadata.js';
|
|
17
|
-
import {
|
|
18
|
-
resolveOpenclawBridgeLiveLogPath,
|
|
19
|
-
resolveOpenclawBridgeLockPath,
|
|
20
|
-
resolveOpenclawBridgeStatePath,
|
|
21
|
-
resolveOpenclawBridgeStatusPath,
|
|
22
|
-
resolveOpenclawConfigCandidates,
|
|
23
|
-
resolveOpenclawHome,
|
|
24
|
-
resolveOpenclawIdentityPath,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
import {
|
|
18
|
+
resolveOpenclawBridgeLiveLogPath,
|
|
19
|
+
resolveOpenclawBridgeLockPath,
|
|
20
|
+
resolveOpenclawBridgeStatePath,
|
|
21
|
+
resolveOpenclawBridgeStatusPath,
|
|
22
|
+
resolveOpenclawConfigCandidates,
|
|
23
|
+
resolveOpenclawHome,
|
|
24
|
+
resolveOpenclawIdentityPath,
|
|
25
|
+
resolveOpenclawLegacyPersonasDir,
|
|
26
|
+
resolveOpenclawProfilePath,
|
|
27
|
+
resolveOpenclawSkillsDir,
|
|
28
|
+
resolveOpenclawUpdateStatePath,
|
|
29
|
+
resolveOpenclawWorkspaceRoot,
|
|
30
|
+
} from '../lib/openclawPaths.js';
|
|
30
31
|
import {
|
|
31
32
|
applyOpenclawProfile,
|
|
32
33
|
buildOomiDevLocalProfile,
|
|
33
34
|
readOpenclawProfile,
|
|
34
35
|
writeOpenclawProfile,
|
|
35
36
|
} from '../lib/openclawProfile.js';
|
|
36
|
-
import {
|
|
37
|
-
buildLocalPersonaRuntime,
|
|
38
|
-
defaultPersonaWorkspaceRoot,
|
|
39
|
-
installPersonaWorkspace,
|
|
40
|
-
isPersonaWorkspaceProcessRunning,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
import {
|
|
38
|
+
buildLocalPersonaRuntime,
|
|
39
|
+
defaultPersonaWorkspaceRoot,
|
|
40
|
+
installPersonaWorkspace,
|
|
41
|
+
isPersonaWorkspaceProcessRunning,
|
|
42
|
+
resolvePersonaHealthPath,
|
|
43
|
+
resolvePersonaDevCommand,
|
|
44
|
+
startPersonaWorkspace,
|
|
45
|
+
stopPersonaWorkspace,
|
|
46
|
+
waitForPersonaRuntime,
|
|
47
|
+
} from '../lib/personaRuntimeProcess.js';
|
|
46
48
|
import {
|
|
47
49
|
destroyManagedPersonaRuntime,
|
|
48
50
|
getManagedPersonaRuntimeStatus,
|
|
@@ -50,13 +52,17 @@ import {
|
|
|
50
52
|
slugifyPersonaName,
|
|
51
53
|
stopManagedPersonaRuntime,
|
|
52
54
|
} from '../lib/personaRuntimeManager.js';
|
|
55
|
+
import {
|
|
56
|
+
readPersonaRuntimeState,
|
|
57
|
+
resolvePersonaWorkspacePath,
|
|
58
|
+
} from '../lib/personaRuntimeRegistry.js';
|
|
53
59
|
import { startLocalGatewayAgentServer } from '../lib/openclawDevGateway.js';
|
|
54
60
|
import { ensureSessionBridge, flushSessionQueue, flushWaitingForConnect, forwardFrameToSession } from './sessionBridgeState.js';
|
|
55
61
|
|
|
56
62
|
const MARKER_START = '<oomi-agent-instructions>';
|
|
57
63
|
const MARKER_END = '</oomi-agent-instructions>';
|
|
58
64
|
|
|
59
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(
|
|
65
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
60
66
|
const UPDATE_STATE_FILE = resolveOpenclawUpdateStatePath();
|
|
61
67
|
const DEFAULT_UPDATE_CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
62
68
|
const DEFAULT_UPDATE_CHECK_TIMEOUT_MS = 1200;
|
|
@@ -231,10 +237,12 @@ Commands:
|
|
|
231
237
|
Replay an assistant chat.final frame through spoken-metadata normalization.
|
|
232
238
|
openclaw debug tts-pipeline
|
|
233
239
|
Replay an assistant chat.final through local backend voice handling.
|
|
234
|
-
openclaw debug local-gateway-agent
|
|
235
|
-
Run a tiny local OpenClaw gateway/agent for Docker dev testing.
|
|
236
|
-
openclaw debug persona-runtime
|
|
237
|
-
Scaffold, launch, and stop a managed persona runtime locally.
|
|
240
|
+
openclaw debug local-gateway-agent
|
|
241
|
+
Run a tiny local OpenClaw gateway/agent for Docker dev testing.
|
|
242
|
+
openclaw debug persona-runtime
|
|
243
|
+
Scaffold, launch, and stop a managed persona runtime locally.
|
|
244
|
+
openclaw refresh
|
|
245
|
+
Restart the bridge and running managed persona runtimes after an oomi-ai update.
|
|
238
246
|
|
|
239
247
|
openclaw pair
|
|
240
248
|
Pair this OpenClaw host with Oomi and start bridge (single command).
|
|
@@ -320,18 +328,20 @@ Common flags:
|
|
|
320
328
|
--out PATH Output directory for scaffolded persona app
|
|
321
329
|
--template-version V Scaffold template version (default: v1)
|
|
322
330
|
--force Overwrite files in an existing output directory
|
|
323
|
-
--force-install Reinstall persona workspace dependencies before launch
|
|
324
|
-
--
|
|
325
|
-
--no-
|
|
331
|
+
--force-install Reinstall persona workspace dependencies before launch
|
|
332
|
+
--include-stopped Relaunch managed persona runtimes even if not currently marked running
|
|
333
|
+
--no-sync Skip backend sync (for create)
|
|
334
|
+
--no-create Do not create a managed persona record if one does not already exist
|
|
326
335
|
--local-port N Local runtime port for persona runtime callbacks
|
|
327
336
|
--endpoint URL Runtime endpoint for persona runtime callbacks
|
|
328
337
|
--entry-url URL Viewer URL to register for a launched persona runtime
|
|
329
|
-
--health-path PATH Runtime health path (default:
|
|
338
|
+
--health-path PATH Runtime health path override (default: workspace-specific)
|
|
330
339
|
--healthcheck-url URL Runtime healthcheck URL override
|
|
331
|
-
--transport TEXT Runtime transport label (default: local, relay when --entry-url is used)
|
|
340
|
+
--transport TEXT Runtime transport label (default: local, relay when --entry-url is used)
|
|
332
341
|
--workspace-root PATH Persona workspace root (default: OPENCLAW_WORKSPACE/personas)
|
|
333
|
-
--restart Restart an existing managed persona runtime before launch
|
|
334
|
-
--
|
|
342
|
+
--restart Restart an existing managed persona runtime before launch
|
|
343
|
+
--skip-version-check Skip checking npm for the latest oomi-ai version during refresh
|
|
344
|
+
--started-at ISO Start timestamp override
|
|
335
345
|
--observed-at ISO Heartbeat timestamp override
|
|
336
346
|
--completed-at ISO Completion timestamp override
|
|
337
347
|
--code TEXT Error code for fail callbacks
|
|
@@ -829,16 +839,183 @@ function resolvePersonaEntryUrl(flags = {}) {
|
|
|
829
839
|
return String(flags['entry-url'] || '').trim();
|
|
830
840
|
}
|
|
831
841
|
|
|
832
|
-
function resolvePersonaLaunchTransport(flags = {}) {
|
|
833
|
-
const explicitTransport = String(flags.transport || '').trim();
|
|
834
|
-
if (explicitTransport) {
|
|
835
|
-
return explicitTransport;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
return resolvePersonaEntryUrl(flags) ? 'relay' : 'local';
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
+
function resolvePersonaLaunchTransport(flags = {}) {
|
|
843
|
+
const explicitTransport = String(flags.transport || '').trim();
|
|
844
|
+
if (explicitTransport) {
|
|
845
|
+
return explicitTransport;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
return resolvePersonaEntryUrl(flags) ? 'relay' : 'local';
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function listPersonaWorkspaceRoots(workspaceRoot = defaultPersonaWorkspaceRoot()) {
|
|
852
|
+
const roots = [
|
|
853
|
+
String(workspaceRoot || '').trim(),
|
|
854
|
+
String(resolveOpenclawLegacyPersonasDir() || '').trim(),
|
|
855
|
+
].filter(Boolean);
|
|
856
|
+
|
|
857
|
+
const deduped = [];
|
|
858
|
+
const seen = new Set();
|
|
859
|
+
for (const root of roots) {
|
|
860
|
+
const resolved = path.resolve(root);
|
|
861
|
+
if (seen.has(resolved) || !fs.existsSync(resolved)) {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
seen.add(resolved);
|
|
865
|
+
deduped.push(resolved);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return deduped;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function collectManagedPersonaRefreshTargets({
|
|
872
|
+
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
873
|
+
includeStopped = false,
|
|
874
|
+
} = {}) {
|
|
875
|
+
const targets = [];
|
|
876
|
+
const seenWorkspacePaths = new Set();
|
|
877
|
+
const seenSlugs = new Set();
|
|
878
|
+
|
|
879
|
+
for (const root of listPersonaWorkspaceRoots(workspaceRoot)) {
|
|
880
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
881
|
+
for (const entry of entries) {
|
|
882
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const workspacePath = path.join(root, entry.name);
|
|
887
|
+
let dedupeKey = workspacePath;
|
|
888
|
+
try {
|
|
889
|
+
dedupeKey = fs.realpathSync(workspacePath);
|
|
890
|
+
} catch {
|
|
891
|
+
dedupeKey = path.resolve(workspacePath);
|
|
892
|
+
}
|
|
893
|
+
if (seenWorkspacePaths.has(dedupeKey)) {
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
seenWorkspacePaths.add(dedupeKey);
|
|
897
|
+
|
|
898
|
+
const state = readPersonaRuntimeState(workspacePath);
|
|
899
|
+
if (!state || Object.keys(state).length === 0) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const slug = String(state.slug || entry.name || '').trim();
|
|
904
|
+
if (!slug || seenSlugs.has(slug)) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const pid = normalizePid(state.pid);
|
|
909
|
+
const processRunning = pid ? isPersonaWorkspaceProcessRunning(pid) : false;
|
|
910
|
+
const runtimeStatus = String(state.status || '').trim().toLowerCase();
|
|
911
|
+
if (!includeStopped && !processRunning && runtimeStatus !== 'running') {
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
seenSlugs.add(slug);
|
|
916
|
+
targets.push({
|
|
917
|
+
slug,
|
|
918
|
+
workspacePath,
|
|
919
|
+
state,
|
|
920
|
+
processRunning,
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
return targets.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function normalizeBackendPersonaRefreshRecord(rawPersona) {
|
|
929
|
+
if (!rawPersona || typeof rawPersona !== 'object') {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const slug = String(
|
|
934
|
+
rawPersona.slug ||
|
|
935
|
+
rawPersona.id ||
|
|
936
|
+
rawPersona.personaId ||
|
|
937
|
+
''
|
|
938
|
+
).trim();
|
|
939
|
+
if (!slug) {
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return {
|
|
944
|
+
slug,
|
|
945
|
+
name: String(rawPersona.name || slug).trim() || slug,
|
|
946
|
+
description: String(rawPersona.description || rawPersona.summary || rawPersona.name || slug).trim() || slug,
|
|
947
|
+
templateVersion: String(rawPersona.promptTemplateVersion || 'v1').trim() || 'v1',
|
|
948
|
+
templateType: String(rawPersona.templateType || '').trim(),
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function resolveExistingWorkspacePathForSlug(slug, workspaceRoot = defaultPersonaWorkspaceRoot()) {
|
|
953
|
+
for (const root of listPersonaWorkspaceRoots(workspaceRoot)) {
|
|
954
|
+
const workspacePath = resolvePersonaWorkspacePath({
|
|
955
|
+
workspaceRoot: root,
|
|
956
|
+
slug,
|
|
957
|
+
});
|
|
958
|
+
if (fs.existsSync(workspacePath)) {
|
|
959
|
+
return workspacePath;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return '';
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async function discoverBackendLinkedPersonaRefreshTargets({
|
|
966
|
+
client,
|
|
967
|
+
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
968
|
+
existingTargets = [],
|
|
969
|
+
logger = null,
|
|
970
|
+
} = {}) {
|
|
971
|
+
if (!client || typeof client.listPersonas !== 'function') {
|
|
972
|
+
return [];
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const targetsBySlug = new Map(
|
|
976
|
+
Array.isArray(existingTargets)
|
|
977
|
+
? existingTargets.map((target) => [String(target?.slug || '').trim(), target]).filter(([slug]) => slug)
|
|
978
|
+
: [],
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const payload = await client.listPersonas();
|
|
982
|
+
const backendPersonas = Array.isArray(payload?.personas) ? payload.personas : [];
|
|
983
|
+
const discoveredTargets = [];
|
|
984
|
+
|
|
985
|
+
for (const rawPersona of backendPersonas) {
|
|
986
|
+
const backendPersona = normalizeBackendPersonaRefreshRecord(rawPersona);
|
|
987
|
+
if (!backendPersona || targetsBySlug.has(backendPersona.slug)) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const workspacePath = resolveExistingWorkspacePathForSlug(backendPersona.slug, workspaceRoot);
|
|
991
|
+
if (!workspacePath) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
const state = readPersonaRuntimeState(workspacePath);
|
|
995
|
+
const target = {
|
|
996
|
+
slug: backendPersona.slug,
|
|
997
|
+
workspacePath,
|
|
998
|
+
state: {
|
|
999
|
+
...state,
|
|
1000
|
+
slug: backendPersona.slug,
|
|
1001
|
+
name: backendPersona.name,
|
|
1002
|
+
description: backendPersona.description,
|
|
1003
|
+
templateVersion: backendPersona.templateVersion,
|
|
1004
|
+
},
|
|
1005
|
+
processRunning: false,
|
|
1006
|
+
};
|
|
1007
|
+
discoveredTargets.push(target);
|
|
1008
|
+
targetsBySlug.set(backendPersona.slug, target);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (discoveredTargets.length > 0) {
|
|
1012
|
+
logger?.(`Added ${discoveredTargets.length} backend-linked persona runtime target${discoveredTargets.length === 1 ? '' : 's'} from workspace discovery.`);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return discoveredTargets;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function findExistingManagedPersona(client, slug) {
|
|
842
1019
|
try {
|
|
843
1020
|
return await client.getPersona({ slug });
|
|
844
1021
|
} catch (error) {
|
|
@@ -1114,31 +1291,81 @@ async function runManagedPersonaJobExecution({
|
|
|
1114
1291
|
}
|
|
1115
1292
|
}
|
|
1116
1293
|
|
|
1117
|
-
function
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1294
|
+
function resolvePersonaRuntimeWorkspacePath({
|
|
1295
|
+
slug = '',
|
|
1296
|
+
workspacePath = '',
|
|
1297
|
+
workspaceRoot = '',
|
|
1298
|
+
} = {}) {
|
|
1299
|
+
const explicitWorkspacePath = String(workspacePath || '').trim();
|
|
1300
|
+
if (explicitWorkspacePath) {
|
|
1301
|
+
return path.resolve(explicitWorkspacePath);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
const safeSlug = String(slug || '').trim();
|
|
1305
|
+
if (!safeSlug) {
|
|
1306
|
+
return '';
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const safeWorkspaceRoot = String(workspaceRoot || defaultPersonaWorkspaceRoot()).trim();
|
|
1310
|
+
if (!safeWorkspaceRoot) {
|
|
1311
|
+
return '';
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
return resolvePersonaWorkspacePath({
|
|
1315
|
+
workspaceRoot: safeWorkspaceRoot,
|
|
1316
|
+
slug: safeSlug,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function resolvePersonaRuntimeInput(flags = {}, defaults = {}, options = {}) {
|
|
1321
|
+
const localPort = parseOptionalPositiveInteger(flags['local-port'] || flags.localPort || defaults.localPort);
|
|
1322
|
+
const endpoint = String(flags.endpoint || defaults.endpoint || '').trim();
|
|
1323
|
+
const explicitHealthPath = String(flags['health-path'] || defaults.healthPath || '').trim();
|
|
1324
|
+
const healthcheckUrl = String(flags['healthcheck-url'] || defaults.healthcheckUrl || '').trim();
|
|
1325
|
+
const transport = String(flags.transport || defaults.transport || 'local').trim() || 'local';
|
|
1326
|
+
const workspacePath = resolvePersonaRuntimeWorkspacePath({
|
|
1327
|
+
slug: options.slug || defaults.slug || flags.slug,
|
|
1328
|
+
workspacePath:
|
|
1329
|
+
options.workspacePath ||
|
|
1330
|
+
defaults.workspacePath ||
|
|
1331
|
+
flags['workspace-path'] ||
|
|
1332
|
+
flags.workspacePath,
|
|
1333
|
+
workspaceRoot:
|
|
1334
|
+
options.workspaceRoot ||
|
|
1335
|
+
defaults.workspaceRoot ||
|
|
1336
|
+
flags['workspace-root'] ||
|
|
1337
|
+
flags.workspaceRoot,
|
|
1338
|
+
});
|
|
1339
|
+
const healthPath = explicitHealthPath ||
|
|
1340
|
+
resolvePersonaHealthPath({
|
|
1341
|
+
workspacePath,
|
|
1342
|
+
fallback: '/oomi.health.json',
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
if (endpoint) {
|
|
1346
|
+
return {
|
|
1347
|
+
endpoint,
|
|
1348
|
+
healthcheckUrl: healthcheckUrl || `${endpoint.replace(/\/$/, '')}${healthPath}`,
|
|
1128
1349
|
localPort,
|
|
1129
1350
|
transport,
|
|
1130
1351
|
};
|
|
1131
1352
|
}
|
|
1132
1353
|
|
|
1133
|
-
if (!localPort) {
|
|
1134
|
-
throw new Error('Runtime endpoint or local port is required.');
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
localPort,
|
|
1139
|
-
healthPath,
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1354
|
+
if (!localPort) {
|
|
1355
|
+
throw new Error('Runtime endpoint or local port is required.');
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const runtime = buildLocalPersonaRuntime({
|
|
1359
|
+
localPort,
|
|
1360
|
+
healthPath,
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
return {
|
|
1364
|
+
...runtime,
|
|
1365
|
+
endpoint: runtime.reachableEndpoint || runtime.endpoint,
|
|
1366
|
+
localEndpoint: runtime.endpoint,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1142
1369
|
|
|
1143
1370
|
function parseIsoTimestamp(rawValue, label) {
|
|
1144
1371
|
const value = String(rawValue || '').trim();
|
|
@@ -1175,12 +1402,19 @@ function printStructuredResult(result, asJson = false) {
|
|
|
1175
1402
|
}
|
|
1176
1403
|
}
|
|
1177
1404
|
|
|
1178
|
-
async function handlePersonaRuntimeRegisterCommand(slug, flags = {}) {
|
|
1179
|
-
const client = createCliPersonaApiClient(flags);
|
|
1180
|
-
const runtime = resolvePersonaRuntimeInput(
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1405
|
+
async function handlePersonaRuntimeRegisterCommand(slug, flags = {}) {
|
|
1406
|
+
const client = createCliPersonaApiClient(flags);
|
|
1407
|
+
const runtime = resolvePersonaRuntimeInput(
|
|
1408
|
+
flags,
|
|
1409
|
+
{},
|
|
1410
|
+
{
|
|
1411
|
+
slug,
|
|
1412
|
+
workspaceRoot: resolvePersonaWorkspaceRoot(flags),
|
|
1413
|
+
},
|
|
1414
|
+
);
|
|
1415
|
+
const payload = await client.registerRuntime({
|
|
1416
|
+
slug,
|
|
1417
|
+
endpoint: runtime.endpoint,
|
|
1184
1418
|
healthcheckUrl: runtime.healthcheckUrl,
|
|
1185
1419
|
localPort: runtime.localPort,
|
|
1186
1420
|
transport: runtime.transport,
|
|
@@ -1189,12 +1423,19 @@ async function handlePersonaRuntimeRegisterCommand(slug, flags = {}) {
|
|
|
1189
1423
|
printStructuredResult(payload, isTruthyFlag(flags.json));
|
|
1190
1424
|
}
|
|
1191
1425
|
|
|
1192
|
-
async function handlePersonaHeartbeatCommand(slug, flags = {}) {
|
|
1193
|
-
const client = createCliPersonaApiClient(flags);
|
|
1194
|
-
const runtime = resolvePersonaRuntimeInput(
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1426
|
+
async function handlePersonaHeartbeatCommand(slug, flags = {}) {
|
|
1427
|
+
const client = createCliPersonaApiClient(flags);
|
|
1428
|
+
const runtime = resolvePersonaRuntimeInput(
|
|
1429
|
+
flags,
|
|
1430
|
+
{},
|
|
1431
|
+
{
|
|
1432
|
+
slug,
|
|
1433
|
+
workspaceRoot: resolvePersonaWorkspaceRoot(flags),
|
|
1434
|
+
},
|
|
1435
|
+
);
|
|
1436
|
+
const payload = await client.heartbeatRuntime({
|
|
1437
|
+
slug,
|
|
1438
|
+
endpoint: runtime.endpoint,
|
|
1198
1439
|
healthcheckUrl: runtime.healthcheckUrl,
|
|
1199
1440
|
localPort: runtime.localPort,
|
|
1200
1441
|
transport: runtime.transport,
|
|
@@ -1231,16 +1472,22 @@ async function handlePersonaJobStartCommand(jobId, flags = {}) {
|
|
|
1231
1472
|
printStructuredResult(payload, isTruthyFlag(flags.json));
|
|
1232
1473
|
}
|
|
1233
1474
|
|
|
1234
|
-
async function handlePersonaJobSucceedCommand(jobId, flags = {}) {
|
|
1235
|
-
const client = createCliPersonaApiClient(flags);
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1475
|
+
async function handlePersonaJobSucceedCommand(jobId, flags = {}) {
|
|
1476
|
+
const client = createCliPersonaApiClient(flags);
|
|
1477
|
+
const workspacePath = String(flags['workspace-path'] || flags.workspacePath || '').trim();
|
|
1478
|
+
if (!workspacePath) {
|
|
1479
|
+
throw new Error('Workspace path is required. Use --workspace-path.');
|
|
1480
|
+
}
|
|
1481
|
+
const runtime = resolvePersonaRuntimeInput(
|
|
1482
|
+
flags,
|
|
1483
|
+
{},
|
|
1484
|
+
{
|
|
1485
|
+
workspacePath,
|
|
1486
|
+
},
|
|
1487
|
+
);
|
|
1488
|
+
|
|
1489
|
+
const payload = await client.succeedJob({
|
|
1490
|
+
jobId,
|
|
1244
1491
|
workspacePath,
|
|
1245
1492
|
localPort: runtime.localPort,
|
|
1246
1493
|
transport: runtime.transport,
|
|
@@ -1408,6 +1655,212 @@ async function handlePersonaDeleteCommand(slug, flags = {}) {
|
|
|
1408
1655
|
});
|
|
1409
1656
|
printStructuredResult(result, isTruthyFlag(flags.json));
|
|
1410
1657
|
}
|
|
1658
|
+
|
|
1659
|
+
async function restartManagedPersonaRefreshTargets(flags = {}, options = {}) {
|
|
1660
|
+
const workspaceRoot = resolvePersonaWorkspaceRoot(flags);
|
|
1661
|
+
const localTargets = collectManagedPersonaRefreshTargets({
|
|
1662
|
+
workspaceRoot,
|
|
1663
|
+
includeStopped: isTruthyFlag(flags['include-stopped']),
|
|
1664
|
+
});
|
|
1665
|
+
const targetsBySlug = new Map(localTargets.map((target) => [target.slug, target]));
|
|
1666
|
+
const targets = [...localTargets];
|
|
1667
|
+
const results = [];
|
|
1668
|
+
const logger = options.logger || null;
|
|
1669
|
+
|
|
1670
|
+
let client = null;
|
|
1671
|
+
let registrationError = '';
|
|
1672
|
+
try {
|
|
1673
|
+
client = createCliPersonaApiClient(flags);
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
registrationError = error instanceof Error ? error.message : String(error);
|
|
1676
|
+
logger?.(`Persona backend registration unavailable: ${registrationError}`);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
if (client) {
|
|
1680
|
+
try {
|
|
1681
|
+
const backendTargets = await discoverBackendLinkedPersonaRefreshTargets({
|
|
1682
|
+
client,
|
|
1683
|
+
workspaceRoot,
|
|
1684
|
+
existingTargets: targets,
|
|
1685
|
+
logger,
|
|
1686
|
+
});
|
|
1687
|
+
for (const target of backendTargets) {
|
|
1688
|
+
targetsBySlug.set(target.slug, target);
|
|
1689
|
+
targets.push(target);
|
|
1690
|
+
}
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
logger?.(`Backend persona discovery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
logger?.(
|
|
1697
|
+
`Discovered ${targets.length} managed persona runtime${targets.length == 1 ? '' : 's'} to refresh.`,
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
for (const target of targets) {
|
|
1701
|
+
const state = target.state && typeof target.state === 'object' ? target.state : {};
|
|
1702
|
+
logger?.(
|
|
1703
|
+
`Refreshing persona ${target.slug} (previous port: ${String(state.localPort || 'unknown')}).`,
|
|
1704
|
+
);
|
|
1705
|
+
const launchResult = await launchManagedPersonaRuntime({
|
|
1706
|
+
slug: target.slug,
|
|
1707
|
+
name: String(state.name || target.slug).trim() || target.slug,
|
|
1708
|
+
description: String(state.description || state.name || target.slug).trim() || target.slug,
|
|
1709
|
+
workspaceRoot,
|
|
1710
|
+
templateVersion: String(state.templateVersion || 'v1').trim() || 'v1',
|
|
1711
|
+
forceInstall: isTruthyFlag(flags['force-install']),
|
|
1712
|
+
restart: true,
|
|
1713
|
+
logFilePath: String(state.logFilePath || '').trim(),
|
|
1714
|
+
entryUrl: '',
|
|
1715
|
+
transport: 'local',
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
let registration = null;
|
|
1719
|
+
if (client) {
|
|
1720
|
+
try {
|
|
1721
|
+
logger?.(`Re-registering runtime for ${launchResult.slug} at ${launchResult.runtime.endpoint}.`);
|
|
1722
|
+
registration = await client.registerRuntime({
|
|
1723
|
+
slug: launchResult.slug,
|
|
1724
|
+
endpoint: launchResult.runtime.endpoint,
|
|
1725
|
+
healthcheckUrl: launchResult.runtime.healthcheckUrl,
|
|
1726
|
+
localPort: launchResult.runtime.localPort,
|
|
1727
|
+
transport: launchResult.runtime.transport,
|
|
1728
|
+
startedAt: new Date().toISOString(),
|
|
1729
|
+
});
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
registration = {
|
|
1732
|
+
ok: false,
|
|
1733
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1734
|
+
};
|
|
1735
|
+
logger?.(`Runtime registration failed for ${launchResult.slug}: ${registration.error}`);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
logger?.(
|
|
1740
|
+
`Persona ${launchResult.slug} refreshed on port ${launchResult.runtime.localPort} (${launchResult.runtime.endpoint}).`,
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
results.push({
|
|
1744
|
+
slug: launchResult.slug,
|
|
1745
|
+
workspacePath: launchResult.workspacePath,
|
|
1746
|
+
localPort: launchResult.runtime.localPort,
|
|
1747
|
+
endpoint: launchResult.runtime.endpoint,
|
|
1748
|
+
healthcheckUrl: launchResult.runtime.healthcheckUrl,
|
|
1749
|
+
registration,
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
return {
|
|
1754
|
+
workspaceRoot,
|
|
1755
|
+
targets,
|
|
1756
|
+
results,
|
|
1757
|
+
registrationError,
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function refreshBridgeForUpdate(flags = {}, options = {}) {
|
|
1762
|
+
const logger = options.logger || null;
|
|
1763
|
+
if (process.platform === 'darwin') {
|
|
1764
|
+
const launchdStatus = readBridgeLaunchdStatus();
|
|
1765
|
+
if (launchdStatus.installed) {
|
|
1766
|
+
logger?.(`Restarting launchd-managed bridge ${launchdStatus.target}.`);
|
|
1767
|
+
await stopBridgeLaunchdService();
|
|
1768
|
+
startBridgeLaunchdService();
|
|
1769
|
+
incrementBridgeMetric('bridge_restart_count');
|
|
1770
|
+
return {
|
|
1771
|
+
restarted: true,
|
|
1772
|
+
mode: 'service',
|
|
1773
|
+
target: launchdStatus.target,
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const running = findRunningBridgeProcess();
|
|
1779
|
+
if (!running) {
|
|
1780
|
+
logger?.('No bridge process is currently running; skipping bridge restart.');
|
|
1781
|
+
return {
|
|
1782
|
+
restarted: false,
|
|
1783
|
+
mode: 'process',
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
logger?.(`Restarting bridge process ${running.pid}.`);
|
|
1788
|
+
const stopResult = await stopBridgeProcesses();
|
|
1789
|
+
if (Array.isArray(stopResult.stillAlive) && stopResult.stillAlive.length > 0) {
|
|
1790
|
+
throw new Error(`Failed to stop bridge processes: ${stopResult.stillAlive.join(', ')}`);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const detachedResult = startBridgeDetachedProcess(flags);
|
|
1794
|
+
incrementBridgeMetric('bridge_restart_count');
|
|
1795
|
+
return {
|
|
1796
|
+
restarted: true,
|
|
1797
|
+
mode: 'process',
|
|
1798
|
+
pid: detachedResult.pid,
|
|
1799
|
+
alreadyRunning: Boolean(detachedResult.alreadyRunning),
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
async function handleOpenclawRefreshCommand(flags = {}) {
|
|
1804
|
+
const jsonOutput = isTruthyFlag(flags.json);
|
|
1805
|
+
const logProgress = jsonOutput ? null : (message) => console.log(`[refresh] ${message}`);
|
|
1806
|
+
const currentVersion = currentPackageVersion();
|
|
1807
|
+
let latestVersion = '';
|
|
1808
|
+
if (!isTruthyFlag(flags['skip-version-check'])) {
|
|
1809
|
+
logProgress?.(`Checking npm for the latest oomi-ai version (installed: ${currentVersion || 'unknown'}).`);
|
|
1810
|
+
latestVersion = await fetchLatestPublishedVersion('oomi-ai');
|
|
1811
|
+
if (latestVersion && compareVersions(currentVersion, latestVersion) < 0) {
|
|
1812
|
+
throw new Error(
|
|
1813
|
+
`Installed oomi-ai ${currentVersion} is behind npm ${latestVersion}. Update first, then rerun: oomi openclaw refresh`
|
|
1814
|
+
);
|
|
1815
|
+
}
|
|
1816
|
+
if (latestVersion) {
|
|
1817
|
+
logProgress?.(`Latest published version is ${latestVersion}.`);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
logProgress?.('Refreshing managed persona runtimes.');
|
|
1822
|
+
const personaRefresh = await restartManagedPersonaRefreshTargets(flags, {
|
|
1823
|
+
logger: logProgress,
|
|
1824
|
+
});
|
|
1825
|
+
logProgress?.('Refreshing bridge process.');
|
|
1826
|
+
const bridgeRefresh = await refreshBridgeForUpdate(flags, {
|
|
1827
|
+
logger: logProgress,
|
|
1828
|
+
});
|
|
1829
|
+
const payload = {
|
|
1830
|
+
ok: true,
|
|
1831
|
+
currentVersion,
|
|
1832
|
+
latestVersion: latestVersion || currentVersion,
|
|
1833
|
+
personas: {
|
|
1834
|
+
discovered: personaRefresh.targets.length,
|
|
1835
|
+
restarted: personaRefresh.results.length,
|
|
1836
|
+
registrationError: personaRefresh.registrationError || null,
|
|
1837
|
+
results: personaRefresh.results,
|
|
1838
|
+
},
|
|
1839
|
+
bridge: bridgeRefresh,
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
if (jsonOutput) {
|
|
1843
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
console.log(`Oomi refresh complete (${payload.currentVersion})`);
|
|
1848
|
+
console.log(`Personas restarted: ${payload.personas.restarted}/${payload.personas.discovered}`);
|
|
1849
|
+
if (payload.personas.registrationError) {
|
|
1850
|
+
console.log(`Backend registration skipped: ${payload.personas.registrationError}`);
|
|
1851
|
+
}
|
|
1852
|
+
payload.personas.results.forEach((result) => {
|
|
1853
|
+
console.log(`- ${result.slug}: ${result.endpoint}`);
|
|
1854
|
+
if (result.registration && result.registration.ok === false) {
|
|
1855
|
+
console.log(` registration: ${result.registration.error}`);
|
|
1856
|
+
}
|
|
1857
|
+
});
|
|
1858
|
+
if (payload.bridge.restarted) {
|
|
1859
|
+
console.log(`Bridge restarted (${payload.bridge.mode}).`);
|
|
1860
|
+
} else {
|
|
1861
|
+
console.log('Bridge not running; no restart performed.');
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1411
1864
|
|
|
1412
1865
|
function resolveOpenclawConfigPath() {
|
|
1413
1866
|
const candidates = resolveOpenclawConfigCandidates();
|
|
@@ -5339,15 +5792,20 @@ async function main() {
|
|
|
5339
5792
|
return;
|
|
5340
5793
|
}
|
|
5341
5794
|
|
|
5342
|
-
if (command === 'openclaw' && subcommand === 'debug') {
|
|
5343
|
-
await handleOpenclawDebugCommand(args.positionals[0], args.flags);
|
|
5344
|
-
return;
|
|
5345
|
-
}
|
|
5346
|
-
|
|
5347
|
-
if (command === '
|
|
5348
|
-
await
|
|
5349
|
-
return;
|
|
5350
|
-
}
|
|
5795
|
+
if (command === 'openclaw' && subcommand === 'debug') {
|
|
5796
|
+
await handleOpenclawDebugCommand(args.positionals[0], args.flags);
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5800
|
+
if (command === 'openclaw' && subcommand === 'refresh') {
|
|
5801
|
+
await handleOpenclawRefreshCommand(args.flags);
|
|
5802
|
+
return;
|
|
5803
|
+
}
|
|
5804
|
+
|
|
5805
|
+
if (command === 'personas' && subcommand === 'sync') {
|
|
5806
|
+
await syncPersonas({ backendUrl: args.flags['backend-url'], root: args.flags.root });
|
|
5807
|
+
return;
|
|
5808
|
+
}
|
|
5351
5809
|
|
|
5352
5810
|
if (command === 'personas' && subcommand === 'create') {
|
|
5353
5811
|
const id = args.positionals[0];
|
|
@@ -5504,8 +5962,13 @@ export {
|
|
|
5504
5962
|
runBridgeCallbackSafely,
|
|
5505
5963
|
extractGatewayRequestMeta,
|
|
5506
5964
|
extractGatewayResponseMeta,
|
|
5507
|
-
isServiceManagedBridgeStart,
|
|
5508
|
-
isGatewayRunStartedFrame,
|
|
5509
|
-
isBridgeWorkerCommand,
|
|
5510
|
-
parsePositiveInteger,
|
|
5511
|
-
|
|
5965
|
+
isServiceManagedBridgeStart,
|
|
5966
|
+
isGatewayRunStartedFrame,
|
|
5967
|
+
isBridgeWorkerCommand,
|
|
5968
|
+
parsePositiveInteger,
|
|
5969
|
+
collectManagedPersonaRefreshTargets,
|
|
5970
|
+
discoverBackendLinkedPersonaRefreshTargets,
|
|
5971
|
+
normalizeBackendPersonaRefreshRecord,
|
|
5972
|
+
resolvePersonaRuntimeInput,
|
|
5973
|
+
resolveExistingWorkspacePathForSlug,
|
|
5974
|
+
};
|
package/lib/personaApiClient.js
CHANGED
|
@@ -100,10 +100,19 @@ export function createPersonaApiClient({
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
return {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
return {
|
|
104
|
+
listPersonas() {
|
|
105
|
+
return getJson({
|
|
106
|
+
fetchImpl,
|
|
107
|
+
backendUrl: resolvedBackendUrl,
|
|
108
|
+
deviceToken: resolvedDeviceToken,
|
|
109
|
+
path: '/v1/personas',
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
getPersona({
|
|
114
|
+
slug,
|
|
115
|
+
}) {
|
|
107
116
|
const safeSlug = trimString(slug);
|
|
108
117
|
if (!safeSlug) {
|
|
109
118
|
throw new Error('Persona slug is required.');
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
defaultPersonaWorkspaceRoot,
|
|
9
9
|
installPersonaWorkspace,
|
|
10
10
|
isPersonaWorkspaceProcessRunning,
|
|
11
|
+
resolvePersonaHealthPath,
|
|
11
12
|
resolvePersonaDevCommand,
|
|
12
13
|
syncLegacyWebSpatialScaffoldFiles,
|
|
13
14
|
syncVendoredWebSpatialPackages,
|
|
@@ -143,20 +144,12 @@ export function slugifyPersonaName(name) {
|
|
|
143
144
|
return normalized;
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
function resolveHealthPath(workspacePath) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
try {
|
|
153
|
-
const parsed = JSON.parse(fs.readFileSync(runtimeConfigPath, 'utf8'));
|
|
154
|
-
const healthPath = trimString(parsed?.healthPath);
|
|
155
|
-
return healthPath || '/oomi.health.json';
|
|
156
|
-
} catch {
|
|
157
|
-
return '/oomi.health.json';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
147
|
+
function resolveHealthPath(workspacePath) {
|
|
148
|
+
return resolvePersonaHealthPath({
|
|
149
|
+
workspacePath,
|
|
150
|
+
fallback: '/oomi.health.json',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
160
153
|
|
|
161
154
|
function workspaceNeedsScaffold(workspacePath) {
|
|
162
155
|
return !fs.existsSync(path.join(workspacePath, 'package.json'));
|
|
@@ -270,12 +263,12 @@ export async function launchManagedPersonaRuntime({
|
|
|
270
263
|
workspacePath,
|
|
271
264
|
templateVersion,
|
|
272
265
|
});
|
|
273
|
-
const installed = await ensureWorkspaceInstall({
|
|
274
|
-
workspacePath,
|
|
275
|
-
forceInstall,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const healthPath =
|
|
266
|
+
const installed = await ensureWorkspaceInstall({
|
|
267
|
+
workspacePath,
|
|
268
|
+
forceInstall,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const healthPath = resolveHealthPath(workspacePath);
|
|
279
272
|
const preferredPort = previousState.localPort || scaffoldInfo.defaultPort;
|
|
280
273
|
const expectedDevCommand = resolvePersonaDevCommand({
|
|
281
274
|
workspacePath,
|
|
@@ -30,12 +30,25 @@ const WEBSPATIAL_VENDOR_ROOT = path.join(
|
|
|
30
30
|
);
|
|
31
31
|
const VENDORED_WEBSPATIAL_CORE_SPEC = 'file:./vendor/webspatial/core-sdk';
|
|
32
32
|
const VENDORED_WEBSPATIAL_REACT_SPEC = 'file:./vendor/webspatial/react-sdk';
|
|
33
|
+
const WEBSPATIAL_RUNTIME_BASE_PATH = '/webspatial/avp';
|
|
33
34
|
const WEBSPATIAL_TEMPLATE_DEV_DEPENDENCIES = [
|
|
34
35
|
'@webspatial/builder',
|
|
35
36
|
'@webspatial/platform-visionos',
|
|
36
37
|
'@webspatial/vite-plugin',
|
|
37
38
|
];
|
|
38
39
|
const LEGACY_WEBSPATIAL_TEMPLATE_FILE_RULES = [
|
|
40
|
+
{
|
|
41
|
+
relativePath: 'oomi.runtime.json',
|
|
42
|
+
shouldReplace: (content) =>
|
|
43
|
+
content.includes('"renderMode": "webspatial"') &&
|
|
44
|
+
content.includes('"healthPath": "/oomi.health.json"'),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
relativePath: 'package.json',
|
|
48
|
+
shouldReplace: (content) =>
|
|
49
|
+
content.includes('vite --host 127.0.0.1 --port 4789') ||
|
|
50
|
+
content.includes('vite preview --host 127.0.0.1 --port 4789'),
|
|
51
|
+
},
|
|
39
52
|
{
|
|
40
53
|
relativePath: path.join('src', 'spatial.ts'),
|
|
41
54
|
shouldReplace: (content) =>
|
|
@@ -295,6 +308,21 @@ function trimString(value) {
|
|
|
295
308
|
return typeof value === 'string' ? value.trim() : '';
|
|
296
309
|
}
|
|
297
310
|
|
|
311
|
+
function normalizePersonaHealthPath(healthPath, runtimeConfig = {}) {
|
|
312
|
+
const safeHealthPath = trimString(healthPath) || '/oomi.health.json';
|
|
313
|
+
const renderMode = trimString(runtimeConfig?.renderMode).toLowerCase();
|
|
314
|
+
if (renderMode !== 'webspatial') {
|
|
315
|
+
return safeHealthPath;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (safeHealthPath.startsWith(`${WEBSPATIAL_RUNTIME_BASE_PATH}/`)) {
|
|
319
|
+
return safeHealthPath;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const normalizedSuffix = safeHealthPath.startsWith('/') ? safeHealthPath : `/${safeHealthPath}`;
|
|
323
|
+
return `${WEBSPATIAL_RUNTIME_BASE_PATH}${normalizedSuffix}`;
|
|
324
|
+
}
|
|
325
|
+
|
|
298
326
|
function readPersonaConfigLiteral(source, key) {
|
|
299
327
|
if (!source) {
|
|
300
328
|
return '';
|
|
@@ -664,6 +692,17 @@ export function resolvePersonaDevEnvironment({
|
|
|
664
692
|
return {};
|
|
665
693
|
}
|
|
666
694
|
|
|
695
|
+
export function resolvePersonaHealthPath({
|
|
696
|
+
workspacePath,
|
|
697
|
+
fallback = '/oomi.health.json',
|
|
698
|
+
} = {}) {
|
|
699
|
+
const runtimeConfig = readPersonaRuntimeConfig(workspacePath);
|
|
700
|
+
return normalizePersonaHealthPath(
|
|
701
|
+
trimString(runtimeConfig?.healthPath) || fallback,
|
|
702
|
+
runtimeConfig,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
667
706
|
export function startPersonaWorkspace({
|
|
668
707
|
workspacePath,
|
|
669
708
|
logFilePath,
|
|
@@ -855,6 +894,7 @@ export function buildLocalPersonaRuntime({
|
|
|
855
894
|
const reachableHost = resolvePersonaReachableHost({ bindHost });
|
|
856
895
|
const endpoint = `http://127.0.0.1:${port}`;
|
|
857
896
|
const reachableEndpoint = `http://${formatPersonaRuntimeHostForUrl(reachableHost)}:${port}`;
|
|
897
|
+
const normalizedHealthPath = healthPath || '/oomi.health.json';
|
|
858
898
|
return {
|
|
859
899
|
transport: 'local',
|
|
860
900
|
endpoint,
|
|
@@ -862,7 +902,7 @@ export function buildLocalPersonaRuntime({
|
|
|
862
902
|
bindHost,
|
|
863
903
|
reachableHost,
|
|
864
904
|
localPort: port,
|
|
865
|
-
healthcheckUrl: `${endpoint}${
|
|
905
|
+
healthcheckUrl: `${endpoint}${normalizedHealthPath}`,
|
|
866
906
|
};
|
|
867
907
|
}
|
|
868
908
|
|
|
@@ -5,7 +5,12 @@ import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
|
|
|
5
5
|
import { createPersonaApiClient } from './personaApiClient.js';
|
|
6
6
|
import { launchManagedPersonaRuntime } from './personaRuntimeManager.js';
|
|
7
7
|
import { readPersonaRuntimeState, updatePersonaRuntimeState } from './personaRuntimeRegistry.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
buildLocalPersonaRuntime,
|
|
10
|
+
isPersonaWorkspaceProcessRunning,
|
|
11
|
+
resolvePersonaDevCommand,
|
|
12
|
+
resolvePersonaHealthPath,
|
|
13
|
+
} from './personaRuntimeProcess.js';
|
|
9
14
|
|
|
10
15
|
function trimString(value) {
|
|
11
16
|
return typeof value === 'string' ? value.trim() : '';
|
|
@@ -17,20 +22,6 @@ function wait(ms) {
|
|
|
17
22
|
});
|
|
18
23
|
}
|
|
19
24
|
|
|
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
|
-
|
|
34
25
|
function listWorkspacePaths(workspaceRoot) {
|
|
35
26
|
const roots = [trimString(workspaceRoot), trimString(resolveOpenclawLegacyPersonasDir())]
|
|
36
27
|
.filter(Boolean)
|
|
@@ -100,7 +91,10 @@ async function reconcileWorkspace({
|
|
|
100
91
|
const localRuntime = runtime.localPort
|
|
101
92
|
? buildLocalPersonaRuntime({
|
|
102
93
|
localPort: runtime.localPort,
|
|
103
|
-
healthPath:
|
|
94
|
+
healthPath: resolvePersonaHealthPath({
|
|
95
|
+
workspacePath,
|
|
96
|
+
fallback: '/oomi.health.json',
|
|
97
|
+
}),
|
|
104
98
|
})
|
|
105
99
|
: null;
|
|
106
100
|
|
|
@@ -166,29 +160,86 @@ async function reconcileWorkspace({
|
|
|
166
160
|
|
|
167
161
|
const healthy = await healthcheckOk(effectiveRuntime.healthcheckUrl);
|
|
168
162
|
if (!healthy) {
|
|
169
|
-
|
|
163
|
+
if (!autoRestart) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const recovered = await launchManagedPersonaRuntime({
|
|
169
|
+
slug,
|
|
170
|
+
name: trimString(state.name) || slug,
|
|
171
|
+
description: trimString(state.description) || trimString(state.name) || slug,
|
|
172
|
+
workspaceRoot,
|
|
173
|
+
templateVersion: trimString(state.templateVersion) || 'v1',
|
|
174
|
+
forceInstall: false,
|
|
175
|
+
restart: true,
|
|
176
|
+
logFilePath: trimString(state.logFilePath),
|
|
177
|
+
entryUrl: '',
|
|
178
|
+
transport: 'local',
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
effectiveRuntime = {
|
|
182
|
+
slug,
|
|
183
|
+
endpoint: recovered.runtime.endpoint,
|
|
184
|
+
localEndpoint: recovered.runtime.localEndpoint || recovered.localRuntime.endpoint,
|
|
185
|
+
reachableEndpoint: recovered.runtime.reachableEndpoint || recovered.localRuntime.reachableEndpoint,
|
|
186
|
+
healthcheckUrl: recovered.runtime.healthcheckUrl,
|
|
187
|
+
transport: recovered.runtime.transport,
|
|
188
|
+
localPort: recovered.runtime.localPort,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await client.registerRuntime({
|
|
192
|
+
slug,
|
|
193
|
+
endpoint: effectiveRuntime.endpoint,
|
|
194
|
+
healthcheckUrl: effectiveRuntime.healthcheckUrl,
|
|
195
|
+
localPort: effectiveRuntime.localPort,
|
|
196
|
+
transport: effectiveRuntime.transport,
|
|
197
|
+
startedAt: new Date().toISOString(),
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.warn?.(
|
|
201
|
+
`[persona-runtime] unhealthy runtime restart failed for ${slug}: ${
|
|
202
|
+
error instanceof Error ? error.message : String(error)
|
|
203
|
+
}`
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!healthy) {
|
|
210
|
+
const recoveredHealthy = await healthcheckOk(effectiveRuntime.healthcheckUrl);
|
|
211
|
+
if (!recoveredHealthy) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
170
214
|
}
|
|
171
215
|
|
|
172
216
|
if (localRuntime) {
|
|
173
|
-
const
|
|
217
|
+
const refreshedLocalRuntime = buildLocalPersonaRuntime({
|
|
218
|
+
localPort: effectiveRuntime.localPort,
|
|
219
|
+
healthPath: resolvePersonaHealthPath({
|
|
220
|
+
workspacePath,
|
|
221
|
+
fallback: '/oomi.health.json',
|
|
222
|
+
}),
|
|
223
|
+
});
|
|
224
|
+
const desiredEndpoint = refreshedLocalRuntime.reachableEndpoint || runtime.endpoint;
|
|
174
225
|
const endpointChanged = desiredEndpoint && desiredEndpoint !== effectiveRuntime.endpoint;
|
|
175
|
-
const localEndpointChanged =
|
|
176
|
-
const reachableEndpointChanged =
|
|
226
|
+
const localEndpointChanged = refreshedLocalRuntime.endpoint !== effectiveRuntime.localEndpoint;
|
|
227
|
+
const reachableEndpointChanged = refreshedLocalRuntime.reachableEndpoint !== effectiveRuntime.reachableEndpoint;
|
|
177
228
|
|
|
178
229
|
if (endpointChanged || localEndpointChanged || reachableEndpointChanged) {
|
|
179
230
|
effectiveRuntime = {
|
|
180
231
|
...effectiveRuntime,
|
|
181
232
|
endpoint: desiredEndpoint,
|
|
182
|
-
localEndpoint:
|
|
183
|
-
reachableEndpoint:
|
|
233
|
+
localEndpoint: refreshedLocalRuntime.endpoint,
|
|
234
|
+
reachableEndpoint: refreshedLocalRuntime.reachableEndpoint,
|
|
184
235
|
};
|
|
185
236
|
updatePersonaRuntimeState(workspacePath, {
|
|
186
237
|
endpoint: desiredEndpoint,
|
|
187
238
|
entryUrl: desiredEndpoint,
|
|
188
|
-
localEndpoint:
|
|
189
|
-
reachableEndpoint:
|
|
190
|
-
bindHost:
|
|
191
|
-
reachableHost:
|
|
239
|
+
localEndpoint: refreshedLocalRuntime.endpoint,
|
|
240
|
+
reachableEndpoint: refreshedLocalRuntime.reachableEndpoint,
|
|
241
|
+
bindHost: refreshedLocalRuntime.bindHost,
|
|
242
|
+
reachableHost: refreshedLocalRuntime.reachableHost,
|
|
192
243
|
});
|
|
193
244
|
|
|
194
245
|
try {
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"install:clean": "npm install",
|
|
8
|
-
"dev": "vite --host
|
|
9
|
-
"dev:avp": "cross-env XR_ENV=avp vite --host
|
|
8
|
+
"dev": "vite --host 0.0.0.0 --port 4789 --strictPort",
|
|
9
|
+
"dev:avp": "cross-env XR_ENV=avp vite --host 0.0.0.0 --port 4789 --strictPort",
|
|
10
10
|
"build": "vite build && cross-env XR_ENV=avp vite build",
|
|
11
|
-
"preview": "vite preview --host
|
|
11
|
+
"preview": "vite preview --host 0.0.0.0 --port 4789 --strictPort",
|
|
12
12
|
"lint": "eslint ."
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|