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.
@@ -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
- return {
229
- endpoint: localRuntime.endpoint,
230
- transport: trimString(transport) || localRuntime.transport,
231
- healthcheckUrl: localRuntime.healthcheckUrl,
232
- localPort: localRuntime.localPort,
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
- let reusingRunningProcess = false;
278
- if (!restart && Number.isInteger(previousState.pid) && isPersonaWorkspaceProcessRunning(previousState.pid)) {
279
- try {
280
- await waitForPersonaRuntime({
281
- healthcheckUrl: previousState.healthcheckUrl || buildLocalPersonaRuntime({
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
- endpoint: registration.endpoint,
338
- entryUrl: registration.endpoint,
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
- !content.includes('enable-xr-monitor') &&
67
- content.includes(
68
- 'This route is intentionally separate so WebSpatial scene launching has a dedicated',
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 resolvePersonaBindHost() {
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 || '127.0.0.1';
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 endpoint = `http://127.0.0.1:${port}`;
742
- return {
743
- transport: 'local',
744
- endpoint,
745
- localPort: port,
746
- healthcheckUrl: `${endpoint}${healthPath}`,
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: state.devCommand,
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,
@@ -2,7 +2,7 @@
2
2
  "id": "oomi-ai",
3
3
  "name": "Oomi Channel Plugin",
4
4
  "description": "Managed Oomi channel integration for OpenClaw.",
5
- "version": "0.2.40",
5
+ "version": "0.2.42",
6
6
  "author": "Oomi",
7
7
  "license": "MIT",
8
8
  "openclawVersion": ">=0.5.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oomi-ai",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Oomi OpenClaw channel plugin and bridge tooling",
5
5
  "bin": {
6
6
  "oomi": "bin/oomi-ai.js"
@@ -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="persona-shell" enable-xr-monitor={sceneMode || undefined}>
50
+ <main className={`persona-shell${sceneMode ? " persona-scene-root" : ""}`}>
51
51
  <section className="persona-header">
52
- <div className="persona-panel persona-hero" enable-xr style={xrStyle(22, "translucent")}>
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} enable-xr style={xrStyle(52, "regular")}>
62
+ <button className="persona-button" onClick={openPersonaScene}>
63
63
  Launch Spatial Surface
64
64
  </button>
65
- <Link className="persona-link" to="/scene" target="_blank" enable-xr style={xrStyle(92, "thin")}>
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="/" enable-xr style={xrStyle(52, "regular")}>
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" enable-xr style={xrStyle(72, "thin")}>
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" enable-xr style={xrStyle(34, "translucent")}>
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" enable-xr style={xrStyle(74, "thin")}>
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" enable-xr style={xrStyle(118, "regular")}>
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 = "b2746721e4fe6b4f86dac0ea55938074eea00cda";
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: 8904ac8fec48fe49ee14d1739237bd1afb2894fe
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.