oomi-ai 0.2.40 → 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 +145 -26
- package/lib/personaRuntimeSupervisor.js +76 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/persona-app/src/App.css +4 -8
- package/templates/persona-app/src/pages/HomePage.tsx +9 -9
- package/templates/persona-app/src/spatial.ts +1 -1
- package/templates/persona-app/vendor/webspatial/FORK.md +1 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js +100 -229
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js.map +1 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js +100 -229
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js.map +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';
|
|
@@ -39,7 +40,9 @@ const LEGACY_WEBSPATIAL_TEMPLATE_FILE_RULES = [
|
|
|
39
40
|
relativePath: path.join('src', 'spatial.ts'),
|
|
40
41
|
shouldReplace: (content) =>
|
|
41
42
|
!content.includes('WEBSPATIAL_FORK_REPOSITORY') ||
|
|
42
|
-
!content.includes('configurePersonaScene')
|
|
43
|
+
!content.includes('configurePersonaScene') ||
|
|
44
|
+
content.includes('WEBSPATIAL_FORK_COMMIT = "b2746721e4fe6b4f86dac0ea55938074eea00cda"') ||
|
|
45
|
+
content.includes('WEBSPATIAL_FORK_COMMIT = "8904ac8fec48fe49ee14d1739237bd1afb2894fe"'),
|
|
43
46
|
},
|
|
44
47
|
{
|
|
45
48
|
relativePath: path.join('src', 'main.tsx'),
|
|
@@ -57,21 +60,44 @@ const LEGACY_WEBSPATIAL_TEMPLATE_FILE_RULES = [
|
|
|
57
60
|
{
|
|
58
61
|
relativePath: path.join('src', 'pages', 'HomePage.tsx'),
|
|
59
62
|
shouldReplace: (content) =>
|
|
60
|
-
content.includes('Open Spatial Scene') &&
|
|
61
|
-
content.includes('Open Scene Route')
|
|
63
|
+
(content.includes('Open Spatial Scene') &&
|
|
64
|
+
content.includes('Open Scene Route')) ||
|
|
65
|
+
(content.includes('Launch Spatial Surface') &&
|
|
66
|
+
content.includes('Open Browser Preview') &&
|
|
67
|
+
content.includes('Focused surface') &&
|
|
68
|
+
!content.includes('sceneMode')) ||
|
|
69
|
+
content.includes('enable-xr-monitor={sceneMode || undefined}') ||
|
|
70
|
+
content.includes('enable-xr={sceneMode || undefined}') ||
|
|
71
|
+
(
|
|
72
|
+
content.includes('persona-panel persona-runtime" enable-xr') ||
|
|
73
|
+
content.includes('persona-button" onClick={openPersonaScene} enable-xr') ||
|
|
74
|
+
content.includes('persona-link" to="/scene" target="_blank" enable-xr') ||
|
|
75
|
+
content.includes('persona-card" enable-xr style={xrStyle(')
|
|
76
|
+
),
|
|
62
77
|
},
|
|
63
78
|
{
|
|
64
79
|
relativePath: path.join('src', 'pages', 'ScenePage.tsx'),
|
|
65
80
|
shouldReplace: (content) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
(
|
|
82
|
+
!content.includes('enable-xr-monitor') &&
|
|
83
|
+
content.includes(
|
|
84
|
+
'This route is intentionally separate so WebSpatial scene launching has a dedicated',
|
|
85
|
+
)
|
|
86
|
+
) ||
|
|
87
|
+
(
|
|
88
|
+
content.includes('Awaiting AndroidXR interaction') &&
|
|
89
|
+
content.includes('Interaction Console') &&
|
|
90
|
+
content.includes('Fork-backed proof points')
|
|
69
91
|
),
|
|
70
92
|
},
|
|
71
93
|
{
|
|
72
94
|
relativePath: path.join('src', 'App.css'),
|
|
73
95
|
shouldReplace: (content) =>
|
|
74
|
-
content.includes('.scene-panel') && !content.includes('.scene-interaction-grid')
|
|
96
|
+
(content.includes('.scene-panel') && !content.includes('.scene-interaction-grid')) ||
|
|
97
|
+
content.includes('html.is-spatial .persona-runtime {') ||
|
|
98
|
+
content.includes('html.is-spatial .persona-scene-root {') ||
|
|
99
|
+
content.includes('html.is-spatial .persona-button,') ||
|
|
100
|
+
content.includes('html.is-spatial .persona-card,'),
|
|
75
101
|
},
|
|
76
102
|
{
|
|
77
103
|
relativePath: 'vite.config.ts',
|
|
@@ -173,9 +199,96 @@ function normalizePositiveInteger(value) {
|
|
|
173
199
|
return Math.floor(parsed);
|
|
174
200
|
}
|
|
175
201
|
|
|
176
|
-
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() {
|
|
177
250
|
const value = String(process.env.OOMI_PERSONA_BIND_HOST || '').trim();
|
|
178
|
-
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';
|
|
179
292
|
}
|
|
180
293
|
|
|
181
294
|
function trimString(value) {
|
|
@@ -729,23 +842,29 @@ export async function waitForPersonaRuntime({
|
|
|
729
842
|
throw new Error(`Timed out waiting for persona runtime healthcheck: ${message}`);
|
|
730
843
|
}
|
|
731
844
|
|
|
732
|
-
export function buildLocalPersonaRuntime({
|
|
733
|
-
localPort,
|
|
734
|
-
healthPath,
|
|
735
|
-
}) {
|
|
736
|
-
const port = Number(localPort);
|
|
737
|
-
if (!Number.isFinite(port) || port <= 0) {
|
|
738
|
-
throw new Error('Local port is required.');
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
+
}
|
|
749
868
|
|
|
750
869
|
export function defaultPersonaWorkspaceRoot() {
|
|
751
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
package/package.json
CHANGED
|
@@ -105,6 +105,10 @@ code {
|
|
|
105
105
|
gap: 24px;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
.persona-scene-root {
|
|
109
|
+
position: relative;
|
|
110
|
+
}
|
|
111
|
+
|
|
108
112
|
.persona-header,
|
|
109
113
|
.persona-grid,
|
|
110
114
|
.scene-interaction-grid,
|
|
@@ -594,19 +598,11 @@ html.is-spatial body {
|
|
|
594
598
|
background: transparent;
|
|
595
599
|
}
|
|
596
600
|
|
|
597
|
-
html.is-spatial .persona-button,
|
|
598
|
-
html.is-spatial .persona-link,
|
|
599
601
|
html.is-spatial .scene-chip,
|
|
600
602
|
html.is-spatial .scene-material-chip {
|
|
601
603
|
--xr-background-material: regular;
|
|
602
604
|
}
|
|
603
605
|
|
|
604
|
-
html.is-spatial .persona-runtime {
|
|
605
|
-
transform: translateZ(20px) rotateX(10deg);
|
|
606
|
-
transform-origin: top right;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
html.is-spatial .persona-card,
|
|
610
606
|
html.is-spatial .scene-ops-card,
|
|
611
607
|
html.is-spatial .scene-interaction-card,
|
|
612
608
|
html.is-spatial .scene-proof-card,
|
|
@@ -47,9 +47,9 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
47
47
|
}, []);
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
|
-
<main className=
|
|
50
|
+
<main className={`persona-shell${sceneMode ? " persona-scene-root" : ""}`}>
|
|
51
51
|
<section className="persona-header">
|
|
52
|
-
<div className="persona-panel persona-hero"
|
|
52
|
+
<div className="persona-panel persona-hero">
|
|
53
53
|
<div>
|
|
54
54
|
<p className="persona-eyebrow">{sceneMode ? "Spatial Persona Surface" : "WebSpatial Persona"}</p>
|
|
55
55
|
<h1 className="persona-title">{personaConfig.name}</h1>
|
|
@@ -59,15 +59,15 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
59
59
|
<div className="persona-actions">
|
|
60
60
|
{!sceneMode ? (
|
|
61
61
|
<>
|
|
62
|
-
<button className="persona-button" onClick={openPersonaScene}
|
|
62
|
+
<button className="persona-button" onClick={openPersonaScene}>
|
|
63
63
|
Launch Spatial Surface
|
|
64
64
|
</button>
|
|
65
|
-
<Link className="persona-link" to="/scene" target="_blank"
|
|
65
|
+
<Link className="persona-link" to="/scene" target="_blank">
|
|
66
66
|
Open Spatial Preview
|
|
67
67
|
</Link>
|
|
68
68
|
</>
|
|
69
69
|
) : (
|
|
70
|
-
<Link className="persona-button" to="/"
|
|
70
|
+
<Link className="persona-button" to="/">
|
|
71
71
|
Return To Browser View
|
|
72
72
|
</Link>
|
|
73
73
|
)}
|
|
@@ -89,7 +89,7 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
89
89
|
</div>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
|
-
<aside className="persona-panel persona-runtime"
|
|
92
|
+
<aside className="persona-panel persona-runtime">
|
|
93
93
|
<p className="persona-eyebrow">Runtime</p>
|
|
94
94
|
<div className="runtime-list">
|
|
95
95
|
<div className="runtime-row">
|
|
@@ -117,7 +117,7 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
117
117
|
</section>
|
|
118
118
|
|
|
119
119
|
<section className="persona-grid">
|
|
120
|
-
<article className="persona-panel persona-card"
|
|
120
|
+
<article className="persona-panel persona-card">
|
|
121
121
|
<h2>What Good XR Feels Like</h2>
|
|
122
122
|
<p>
|
|
123
123
|
Treat the generated persona site like a spatial work surface. The XR route should keep
|
|
@@ -131,7 +131,7 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
131
131
|
</p>
|
|
132
132
|
</article>
|
|
133
133
|
|
|
134
|
-
<article className="persona-panel persona-card"
|
|
134
|
+
<article className="persona-panel persona-card">
|
|
135
135
|
<h2>Editing Notes</h2>
|
|
136
136
|
<ul className="persona-note-list">
|
|
137
137
|
{personaNotes.map(note => (
|
|
@@ -140,7 +140,7 @@ export function HomePage({ sceneMode = false }: HomePageProps) {
|
|
|
140
140
|
</ul>
|
|
141
141
|
</article>
|
|
142
142
|
|
|
143
|
-
<article className="persona-panel persona-card persona-scene-card"
|
|
143
|
+
<article className="persona-panel persona-card persona-scene-card">
|
|
144
144
|
<h2>Scene Contract</h2>
|
|
145
145
|
<p>
|
|
146
146
|
The page should import WebSpatial helpers, self-configure the scene, and keep using
|
|
@@ -3,7 +3,7 @@ import { initScene, version as webSpatialSdkVersion } from "@webspatial/react-sd
|
|
|
3
3
|
|
|
4
4
|
export const PERSONA_SCENE_NAME = "personaScene";
|
|
5
5
|
export const WEBSPATIAL_FORK_REPOSITORY = "https://github.com/zill4/webspatial-sdk";
|
|
6
|
-
export const WEBSPATIAL_FORK_COMMIT = "
|
|
6
|
+
export const WEBSPATIAL_FORK_COMMIT = "ac4bd47eb14a894ffef34a4044ddd0bbd47f3e72";
|
|
7
7
|
|
|
8
8
|
type SpatialWindow = Window & {
|
|
9
9
|
webspatialBridge?: unknown;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
This persona template vendors the AndroidXR-capable WebSpatial runtime packages from:
|
|
2
2
|
|
|
3
3
|
- Repository: https://github.com/zill4/webspatial-sdk
|
|
4
|
-
- Commit:
|
|
4
|
+
- Commit: ac4bd47eb14a894ffef34a4044ddd0bbd47f3e72
|
|
5
5
|
|
|
6
6
|
Oomi uses these vendored packages for persona runtimes so OpenClaw installs the AndroidXR-enabled fork instead of the stock npm release.
|