oomi-ai 0.2.39 → 0.2.40

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.
Files changed (45) hide show
  1. package/README.md +5 -3
  2. package/lib/personaRuntimeManager.js +21 -17
  3. package/lib/personaRuntimeProcess.js +392 -49
  4. package/lib/scaffold.js +14 -14
  5. package/openclaw.plugin.json +1 -1
  6. package/package.json +10 -8
  7. package/templates/persona-app/package.json +6 -4
  8. package/templates/persona-app/src/App.css +564 -79
  9. package/templates/persona-app/src/App.tsx +2 -2
  10. package/templates/persona-app/src/main.tsx +13 -0
  11. package/templates/persona-app/src/pages/HomePage.tsx +120 -39
  12. package/templates/persona-app/src/pages/ScenePage.tsx +2 -15
  13. package/templates/persona-app/src/persona/notes.ts +3 -1
  14. package/templates/persona-app/src/spatial.ts +82 -0
  15. package/templates/persona-app/template.json +1 -1
  16. package/templates/persona-app/vendor/webspatial/FORK.md +6 -0
  17. package/templates/persona-app/vendor/webspatial/core-sdk/LICENSE +21 -0
  18. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.d.ts +906 -0
  19. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js +75 -0
  20. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js.map +1 -0
  21. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.d.ts +906 -0
  22. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js +3131 -0
  23. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js.map +1 -0
  24. package/templates/persona-app/vendor/webspatial/core-sdk/package.json +45 -0
  25. package/templates/persona-app/vendor/webspatial/react-sdk/LICENSE +21 -0
  26. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.d.ts +365 -0
  27. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js +4296 -0
  28. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js.map +1 -0
  29. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.d.ts +82 -0
  30. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js +66 -0
  31. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js.map +1 -0
  32. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.d.ts +2 -0
  33. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js +18 -0
  34. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js.map +1 -0
  35. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.d.ts +5 -0
  36. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js +66 -0
  37. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js.map +1 -0
  38. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.d.ts +1 -0
  39. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js +18 -0
  40. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js.map +1 -0
  41. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.d.ts +365 -0
  42. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js +4336 -0
  43. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js.map +1 -0
  44. package/templates/persona-app/vendor/webspatial/react-sdk/package.json +94 -0
  45. package/templates/persona-app/vite.config.ts +13 -0
package/README.md CHANGED
@@ -4,9 +4,11 @@ OpenClaw channel plugin and bridge tooling for Oomi managed chat and voice.
4
4
 
5
5
  ## Current Focus
6
6
 
7
- `0.2.39` keeps the persona automation lane, adds a stable local persona runtime manager, upgrades the Docker dev harness from a package simulator to a real OpenClaw runtime, and introduces a shared OpenClaw profile contract so local onboarding, Docker bootstrap, and future hosted agents use the same setup model:
8
- - WebSpatial-based persona scaffolding for generated Oomi apps
9
- - a high-level `oomi personas create-managed` command for agent-driven persona creation
7
+ `0.2.40` keeps the persona automation lane, adds a stable local persona runtime manager, upgrades the Docker dev harness from a package simulator to a real OpenClaw runtime, and introduces a shared OpenClaw profile contract so local onboarding, Docker bootstrap, and future hosted agents use the same setup model:
8
+ - WebSpatial-based persona scaffolding for generated Oomi apps
9
+ - vendored AndroidXR WebSpatial fork sync for persona runtimes, including the npm-safe package metadata needed by OpenClaw installs
10
+ - managed persona runtime installs now detect vendored WebSpatial drift and reinstall automatically so existing workspaces pick up XR runtime fixes
11
+ - a high-level `oomi personas create-managed` command for agent-driven persona creation
10
12
  - a stable `oomi personas launch-managed` flow for local persona hosting under `OPENCLAW_WORKSPACE/personas`
11
13
  - a matching `oomi personas delete` flow that stops managed runtimes and removes the persona workspace from the OpenClaw machine
12
14
  - shared OpenClaw path handling for isolated local or containerized dev roots
@@ -6,13 +6,15 @@ import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
6
6
  import {
7
7
  buildLocalPersonaRuntime,
8
8
  defaultPersonaWorkspaceRoot,
9
- installPersonaWorkspace,
10
- isPersonaWorkspaceProcessRunning,
11
- resolvePersonaDevCommand,
12
- startPersonaWorkspace,
13
- stopPersonaWorkspace,
14
- waitForPersonaRuntime,
15
- } from './personaRuntimeProcess.js';
9
+ installPersonaWorkspace,
10
+ isPersonaWorkspaceProcessRunning,
11
+ resolvePersonaDevCommand,
12
+ syncLegacyWebSpatialScaffoldFiles,
13
+ syncVendoredWebSpatialPackages,
14
+ startPersonaWorkspace,
15
+ stopPersonaWorkspace,
16
+ waitForPersonaRuntime,
17
+ } from './personaRuntimeProcess.js';
16
18
  import {
17
19
  readPersonaRuntimeState,
18
20
  resolvePersonaRuntimeLogPath,
@@ -193,16 +195,18 @@ async function ensureWorkspaceScaffold({
193
195
  };
194
196
  }
195
197
 
196
- async function ensureWorkspaceInstall({
197
- workspacePath,
198
- forceInstall = false,
199
- }) {
200
- const nodeModulesPath = path.join(workspacePath, 'node_modules');
201
- if (!forceInstall && fs.existsSync(nodeModulesPath)) {
202
- return false;
203
- }
204
-
205
- await installPersonaWorkspace({ workspacePath });
198
+ async function ensureWorkspaceInstall({
199
+ workspacePath,
200
+ forceInstall = false,
201
+ }) {
202
+ const packageSyncChanged = syncVendoredWebSpatialPackages({ workspacePath });
203
+ syncLegacyWebSpatialScaffoldFiles({ workspacePath });
204
+ const nodeModulesPath = path.join(workspacePath, 'node_modules');
205
+ if (!forceInstall && fs.existsSync(nodeModulesPath) && !packageSyncChanged) {
206
+ return false;
207
+ }
208
+
209
+ await installPersonaWorkspace({ workspacePath });
206
210
  return true;
207
211
  }
208
212
 
@@ -1,16 +1,155 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
3
  import { spawn, spawnSync } from 'node:child_process';
4
-
5
- import { resolveOpenclawPersonasDir } from './openclawPaths.js';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import { resolveOpenclawPersonasDir } from './openclawPaths.js';
7
+ import {
8
+ DEFAULT_TEMPLATE_ID,
9
+ DEFAULT_TEMPLATE_VERSION,
10
+ renderTemplateFile,
11
+ resolveTemplateRoot,
12
+ } from './template.js';
13
+
14
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
+ const PERSONA_TEMPLATE_ROOT = resolveTemplateRoot(
16
+ DEFAULT_TEMPLATE_ID,
17
+ DEFAULT_TEMPLATE_VERSION,
18
+ );
19
+ const PERSONA_TEMPLATE_PACKAGE_JSON_PATH = path.join(PERSONA_TEMPLATE_ROOT, 'package.json');
20
+ const PERSONA_TEMPLATE_PACKAGE_JSON = fs.existsSync(PERSONA_TEMPLATE_PACKAGE_JSON_PATH)
21
+ ? JSON.parse(fs.readFileSync(PERSONA_TEMPLATE_PACKAGE_JSON_PATH, 'utf8'))
22
+ : {};
23
+ const WEBSPATIAL_VENDOR_ROOT = path.join(
24
+ PACKAGE_ROOT,
25
+ 'templates',
26
+ 'persona-app',
27
+ 'vendor',
28
+ 'webspatial',
29
+ );
30
+ const VENDORED_WEBSPATIAL_CORE_SPEC = 'file:./vendor/webspatial/core-sdk';
31
+ const VENDORED_WEBSPATIAL_REACT_SPEC = 'file:./vendor/webspatial/react-sdk';
32
+ const WEBSPATIAL_TEMPLATE_DEV_DEPENDENCIES = [
33
+ '@webspatial/builder',
34
+ '@webspatial/platform-visionos',
35
+ '@webspatial/vite-plugin',
36
+ ];
37
+ const LEGACY_WEBSPATIAL_TEMPLATE_FILE_RULES = [
38
+ {
39
+ relativePath: path.join('src', 'spatial.ts'),
40
+ shouldReplace: (content) =>
41
+ !content.includes('WEBSPATIAL_FORK_REPOSITORY') ||
42
+ !content.includes('configurePersonaScene'),
43
+ },
44
+ {
45
+ relativePath: path.join('src', 'main.tsx'),
46
+ shouldReplace: (content) =>
47
+ !content.includes('snapdom') &&
48
+ !content.includes('html2canvas') &&
49
+ content.includes('createRoot(document.getElementById("root")!)'),
50
+ },
51
+ {
52
+ relativePath: path.join('src', 'App.tsx'),
53
+ shouldReplace: (content) =>
54
+ content.includes('<Route path="/" element={<HomePage />} />') ||
55
+ content.includes('<Route path="/scene" element={<ScenePage />} />'),
56
+ },
57
+ {
58
+ relativePath: path.join('src', 'pages', 'HomePage.tsx'),
59
+ shouldReplace: (content) =>
60
+ content.includes('Open Spatial Scene') &&
61
+ content.includes('Open Scene Route'),
62
+ },
63
+ {
64
+ relativePath: path.join('src', 'pages', 'ScenePage.tsx'),
65
+ shouldReplace: (content) =>
66
+ !content.includes('enable-xr-monitor') &&
67
+ content.includes(
68
+ 'This route is intentionally separate so WebSpatial scene launching has a dedicated',
69
+ ),
70
+ },
71
+ {
72
+ relativePath: path.join('src', 'App.css'),
73
+ shouldReplace: (content) =>
74
+ content.includes('.scene-panel') && !content.includes('.scene-interaction-grid'),
75
+ },
76
+ {
77
+ relativePath: 'vite.config.ts',
78
+ shouldReplace: (content) =>
79
+ content.includes('webSpatial()') && !content.includes('optimizeDeps'),
80
+ },
81
+ ];
6
82
 
7
83
  function resolveNpmCommand() {
8
84
  return process.platform === 'win32' ? 'npm.cmd' : 'npm';
9
85
  }
10
86
 
11
- function ensureDir(dirPath) {
12
- fs.mkdirSync(dirPath, { recursive: true });
13
- }
87
+ function ensureDir(dirPath) {
88
+ fs.mkdirSync(dirPath, { recursive: true });
89
+ }
90
+
91
+ function readJsonFile(filePath) {
92
+ if (!fs.existsSync(filePath)) {
93
+ return null;
94
+ }
95
+
96
+ try {
97
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function copyDirectory(sourcePath, targetPath) {
104
+ ensureDir(targetPath);
105
+ let changed = false;
106
+ const sourceEntries = fs.readdirSync(sourcePath, { withFileTypes: true });
107
+ const sourceEntryNames = new Set(sourceEntries.map((entry) => entry.name));
108
+ const existingTargetEntries = fs.readdirSync(targetPath, { withFileTypes: true });
109
+
110
+ for (const entry of existingTargetEntries) {
111
+ if (sourceEntryNames.has(entry.name)) {
112
+ continue;
113
+ }
114
+ fs.rmSync(path.join(targetPath, entry.name), { recursive: true, force: true });
115
+ changed = true;
116
+ }
117
+
118
+ for (const entry of sourceEntries) {
119
+ const sourceEntryPath = path.join(sourcePath, entry.name);
120
+ const targetEntryPath = path.join(targetPath, entry.name);
121
+ const targetExists = fs.existsSync(targetEntryPath);
122
+
123
+ if (entry.isDirectory()) {
124
+ if (targetExists && !fs.statSync(targetEntryPath).isDirectory()) {
125
+ fs.rmSync(targetEntryPath, { recursive: true, force: true });
126
+ changed = true;
127
+ }
128
+ if (copyDirectory(sourceEntryPath, targetEntryPath)) {
129
+ changed = true;
130
+ }
131
+ continue;
132
+ }
133
+
134
+ if (targetExists && fs.statSync(targetEntryPath).isDirectory()) {
135
+ fs.rmSync(targetEntryPath, { recursive: true, force: true });
136
+ changed = true;
137
+ }
138
+
139
+ const sourceBuffer = fs.readFileSync(sourceEntryPath);
140
+ const targetBuffer = fs.existsSync(targetEntryPath)
141
+ ? fs.readFileSync(targetEntryPath)
142
+ : null;
143
+ if (targetBuffer && sourceBuffer.equals(targetBuffer)) {
144
+ continue;
145
+ }
146
+
147
+ fs.copyFileSync(sourceEntryPath, targetEntryPath);
148
+ changed = true;
149
+ }
150
+
151
+ return changed;
152
+ }
14
153
 
15
154
  function quoteWindowsCommandPart(value) {
16
155
  const text = String(value);
@@ -43,6 +182,191 @@ function trimString(value) {
43
182
  return typeof value === 'string' ? value.trim() : '';
44
183
  }
45
184
 
185
+ function readPersonaConfigLiteral(source, key) {
186
+ if (!source) {
187
+ return '';
188
+ }
189
+
190
+ const match = source.match(new RegExp(`${key}:\\s*"([^"]*)"`, 'u'));
191
+ return trimString(match?.[1] || '');
192
+ }
193
+
194
+ function resolvePersonaTemplateVariables(workspacePath) {
195
+ const personaConfigSource = fs.existsSync(path.join(workspacePath, 'src', 'persona', 'config.ts'))
196
+ ? fs.readFileSync(path.join(workspacePath, 'src', 'persona', 'config.ts'), 'utf8')
197
+ : '';
198
+ const personaJson = readJsonFile(path.join(workspacePath, 'persona.json')) || {};
199
+ const runtimeConfig = readPersonaRuntimeConfig(workspacePath);
200
+ const slug =
201
+ readPersonaConfigLiteral(personaConfigSource, 'slug') ||
202
+ trimString(personaJson.id) ||
203
+ path.basename(path.resolve(workspacePath));
204
+ const name =
205
+ readPersonaConfigLiteral(personaConfigSource, 'name') ||
206
+ trimString(personaJson.name) ||
207
+ slug;
208
+ const description =
209
+ readPersonaConfigLiteral(personaConfigSource, 'description') ||
210
+ trimString(personaJson.summary) ||
211
+ name;
212
+ const templateVersion =
213
+ readPersonaConfigLiteral(personaConfigSource, 'templateVersion') ||
214
+ trimString(personaJson.promptTemplateVersion) ||
215
+ trimString(runtimeConfig.templateVersion) ||
216
+ DEFAULT_TEMPLATE_VERSION;
217
+
218
+ return {
219
+ __OOMI_PERSONA_SLUG__: slug,
220
+ __OOMI_PERSONA_NAME__: name,
221
+ __OOMI_PERSONA_DESCRIPTION__: description,
222
+ __OOMI_TEMPLATE_VERSION__: templateVersion,
223
+ };
224
+ }
225
+
226
+ function renderPersonaTemplateFile({ workspacePath, relativePath }) {
227
+ const sourcePath = path.join(PERSONA_TEMPLATE_ROOT, relativePath);
228
+ const content = fs.readFileSync(sourcePath, 'utf8');
229
+ return renderTemplateFile(content, resolvePersonaTemplateVariables(workspacePath));
230
+ }
231
+
232
+ function readPersonaRuntimeConfig(workspacePath) {
233
+ if (!workspacePath) {
234
+ return {};
235
+ }
236
+
237
+ const runtimeConfigPath = path.join(workspacePath, 'oomi.runtime.json');
238
+ if (!fs.existsSync(runtimeConfigPath)) {
239
+ return {};
240
+ }
241
+
242
+ try {
243
+ const parsed = JSON.parse(fs.readFileSync(runtimeConfigPath, 'utf8'));
244
+ return parsed && typeof parsed === 'object' ? parsed : {};
245
+ } catch {
246
+ return {};
247
+ }
248
+ }
249
+
250
+ function isWebSpatialRuntime(workspacePath) {
251
+ const runtimeConfig = readPersonaRuntimeConfig(workspacePath);
252
+ return trimString(runtimeConfig?.renderMode).toLowerCase() === 'webspatial';
253
+ }
254
+
255
+ export function syncVendoredWebSpatialPackages({
256
+ workspacePath,
257
+ } = {}) {
258
+ if (!workspacePath || !isWebSpatialRuntime(workspacePath)) {
259
+ return false;
260
+ }
261
+
262
+ const packageJsonPath = path.join(workspacePath, 'package.json');
263
+ if (!fs.existsSync(packageJsonPath)) {
264
+ return false;
265
+ }
266
+
267
+ const vendorTargetRoot = path.join(workspacePath, 'vendor', 'webspatial');
268
+ let changed = false;
269
+ if (fs.existsSync(WEBSPATIAL_VENDOR_ROOT)) {
270
+ changed = copyDirectory(WEBSPATIAL_VENDOR_ROOT, vendorTargetRoot) || changed;
271
+ }
272
+
273
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
274
+ const dependencies =
275
+ packageJson.dependencies && typeof packageJson.dependencies === 'object'
276
+ ? packageJson.dependencies
277
+ : {};
278
+ const devDependencies =
279
+ packageJson.devDependencies && typeof packageJson.devDependencies === 'object'
280
+ ? packageJson.devDependencies
281
+ : {};
282
+ const scripts =
283
+ packageJson.scripts && typeof packageJson.scripts === 'object'
284
+ ? packageJson.scripts
285
+ : {};
286
+ if (dependencies['@webspatial/core-sdk'] !== VENDORED_WEBSPATIAL_CORE_SPEC) {
287
+ dependencies['@webspatial/core-sdk'] = VENDORED_WEBSPATIAL_CORE_SPEC;
288
+ changed = true;
289
+ }
290
+
291
+ if (dependencies['@webspatial/react-sdk'] !== VENDORED_WEBSPATIAL_REACT_SPEC) {
292
+ dependencies['@webspatial/react-sdk'] = VENDORED_WEBSPATIAL_REACT_SPEC;
293
+ changed = true;
294
+ }
295
+
296
+ if (!dependencies['@zumer/snapdom']) {
297
+ dependencies['@zumer/snapdom'] = '^1.9.14';
298
+ changed = true;
299
+ }
300
+
301
+ if (!dependencies['html2canvas']) {
302
+ dependencies['html2canvas'] = '^1.4.1';
303
+ changed = true;
304
+ }
305
+
306
+ for (const dependencyName of WEBSPATIAL_TEMPLATE_DEV_DEPENDENCIES) {
307
+ const expectedVersion = trimString(
308
+ PERSONA_TEMPLATE_PACKAGE_JSON?.devDependencies?.[dependencyName],
309
+ );
310
+ if (!expectedVersion) {
311
+ continue;
312
+ }
313
+ if (devDependencies[dependencyName] !== expectedVersion) {
314
+ devDependencies[dependencyName] = expectedVersion;
315
+ changed = true;
316
+ }
317
+ }
318
+
319
+ for (const scriptName of ['dev:avp', 'build']) {
320
+ const expectedScript = trimString(PERSONA_TEMPLATE_PACKAGE_JSON?.scripts?.[scriptName]);
321
+ if (!expectedScript || trimString(scripts[scriptName])) {
322
+ continue;
323
+ }
324
+ scripts[scriptName] = expectedScript;
325
+ changed = true;
326
+ }
327
+
328
+ if (changed) {
329
+ packageJson.dependencies = dependencies;
330
+ packageJson.devDependencies = devDependencies;
331
+ packageJson.scripts = scripts;
332
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
333
+ }
334
+
335
+ return changed;
336
+ }
337
+
338
+ export function syncLegacyWebSpatialScaffoldFiles({
339
+ workspacePath,
340
+ } = {}) {
341
+ if (!workspacePath || !isWebSpatialRuntime(workspacePath)) {
342
+ return false;
343
+ }
344
+
345
+ let changed = false;
346
+ for (const rule of LEGACY_WEBSPATIAL_TEMPLATE_FILE_RULES) {
347
+ const targetPath = path.join(workspacePath, rule.relativePath);
348
+ const targetExists = fs.existsSync(targetPath);
349
+ const currentContent = targetExists ? fs.readFileSync(targetPath, 'utf8') : '';
350
+ if (targetExists && !rule.shouldReplace(currentContent)) {
351
+ continue;
352
+ }
353
+
354
+ const renderedContent = renderPersonaTemplateFile({
355
+ workspacePath,
356
+ relativePath: rule.relativePath,
357
+ });
358
+ if (currentContent === renderedContent) {
359
+ continue;
360
+ }
361
+
362
+ ensureDir(path.dirname(targetPath));
363
+ fs.writeFileSync(targetPath, renderedContent, 'utf8');
364
+ changed = true;
365
+ }
366
+
367
+ return changed;
368
+ }
369
+
46
370
  function readProcessCommandLine(pid) {
47
371
  const safePid = normalizePositiveInteger(pid);
48
372
  if (!safePid) {
@@ -176,25 +500,28 @@ function runProcess({ command, args, cwd }) {
176
500
  });
177
501
  }
178
502
 
179
- export async function installPersonaWorkspace({
180
- workspacePath,
181
- }) {
182
- if (!workspacePath) {
183
- throw new Error('Workspace path is required.');
184
- }
185
-
186
- await runProcess({
187
- command: resolveNpmCommand(),
188
- args: ['install', '--silent', '--no-fund', '--no-audit'],
503
+ export async function installPersonaWorkspace({
504
+ workspacePath,
505
+ }) {
506
+ if (!workspacePath) {
507
+ throw new Error('Workspace path is required.');
508
+ }
509
+
510
+ syncVendoredWebSpatialPackages({ workspacePath });
511
+ syncLegacyWebSpatialScaffoldFiles({ workspacePath });
512
+
513
+ await runProcess({
514
+ command: resolveNpmCommand(),
515
+ args: ['install', '--silent', '--no-fund', '--no-audit'],
189
516
  cwd: workspacePath,
190
517
  });
191
518
  }
192
519
 
193
- export function resolvePersonaDevCommand({
194
- workspacePath,
195
- localPort,
196
- }) {
197
- const directCommand = resolveDirectViteCommand({ workspacePath, localPort });
520
+ export function resolvePersonaDevCommand({
521
+ workspacePath,
522
+ localPort,
523
+ }) {
524
+ const directCommand = resolveDirectViteCommand({ workspacePath, localPort });
198
525
  if (directCommand) {
199
526
  return directCommand;
200
527
  }
@@ -206,15 +533,29 @@ export function resolvePersonaDevCommand({
206
533
  }
207
534
  return {
208
535
  command: resolveNpmCommand(),
209
- args,
210
- };
211
- }
212
-
213
- export function startPersonaWorkspace({
214
- workspacePath,
215
- logFilePath,
216
- env = {},
217
- localPort,
536
+ args,
537
+ };
538
+ }
539
+
540
+ export function resolvePersonaDevEnvironment({
541
+ workspacePath,
542
+ } = {}) {
543
+ const runtimeConfig = readPersonaRuntimeConfig(workspacePath);
544
+ const renderMode = trimString(runtimeConfig?.renderMode).toLowerCase();
545
+ if (renderMode === 'webspatial') {
546
+ return {
547
+ XR_ENV: 'avp',
548
+ };
549
+ }
550
+
551
+ return {};
552
+ }
553
+
554
+ export function startPersonaWorkspace({
555
+ workspacePath,
556
+ logFilePath,
557
+ env = {},
558
+ localPort,
218
559
  }) {
219
560
  if (!workspacePath) {
220
561
  throw new Error('Workspace path is required.');
@@ -238,15 +579,16 @@ export function startPersonaWorkspace({
238
579
  )} >> "${resolvedLogFilePath.replace(/"/g, '""')}" 2>&1`;
239
580
  const child = spawn(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', shellCommand], {
240
581
  cwd: workspacePath,
241
- detached: true,
242
- stdio: 'ignore',
243
- shell: false,
244
- windowsHide: true,
245
- env: {
246
- ...process.env,
247
- ...env,
248
- },
249
- });
582
+ detached: true,
583
+ stdio: 'ignore',
584
+ shell: false,
585
+ windowsHide: true,
586
+ env: {
587
+ ...process.env,
588
+ ...resolvePersonaDevEnvironment({ workspacePath }),
589
+ ...env,
590
+ },
591
+ });
250
592
 
251
593
  child.unref();
252
594
 
@@ -265,15 +607,16 @@ export function startPersonaWorkspace({
265
607
  try {
266
608
  child = spawn(devCommand.command, devCommand.args, {
267
609
  cwd: workspacePath,
268
- detached: true,
269
- stdio: ['ignore', output, output],
270
- shell: false,
271
- windowsHide: true,
272
- env: {
273
- ...process.env,
274
- ...env,
275
- },
276
- });
610
+ detached: true,
611
+ stdio: ['ignore', output, output],
612
+ shell: false,
613
+ windowsHide: true,
614
+ env: {
615
+ ...process.env,
616
+ ...resolvePersonaDevEnvironment({ workspacePath }),
617
+ ...env,
618
+ },
619
+ });
277
620
  } finally {
278
621
  fs.closeSync(output);
279
622
  }
package/lib/scaffold.js CHANGED
@@ -82,9 +82,9 @@ export function scaffoldPersonaApp({
82
82
  }
83
83
 
84
84
  const resolvedOutDir = path.resolve(outDir);
85
- const templateRoot = resolveTemplateRoot(templateId, templateVersion);
86
- const descriptor = readTemplateDescriptor(templateRoot);
87
- const variables = {
85
+ const templateRoot = resolveTemplateRoot(templateId, templateVersion);
86
+ const descriptor = readTemplateDescriptor(templateRoot);
87
+ const variables = {
88
88
  __OOMI_PERSONA_SLUG__: safeSlug,
89
89
  __OOMI_PERSONA_NAME__: safeName,
90
90
  __OOMI_PERSONA_DESCRIPTION__: safeDescription,
@@ -95,14 +95,14 @@ export function scaffoldPersonaApp({
95
95
  copyTemplateTree(templateRoot, resolvedOutDir, variables);
96
96
 
97
97
  return {
98
- ok: true,
99
- templateId,
100
- templateVersion,
101
- slug: safeSlug,
102
- outDir: resolvedOutDir,
103
- startCommand: `cd ${resolvedOutDir} && npm install && npm run dev`,
104
- healthPath: descriptor.healthPath,
105
- editableZones: descriptor.editableZones,
106
- defaultPort: descriptor.defaultPort,
107
- };
108
- }
98
+ ok: true,
99
+ templateId,
100
+ templateVersion,
101
+ slug: safeSlug,
102
+ outDir: resolvedOutDir,
103
+ startCommand: `cd ${resolvedOutDir} && npm install && ${descriptor.startCommand || 'npm run dev'}`,
104
+ healthPath: descriptor.healthPath,
105
+ editableZones: descriptor.editableZones,
106
+ defaultPort: descriptor.defaultPort,
107
+ };
108
+ }
@@ -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.39",
5
+ "version": "0.2.40",
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.39",
3
+ "version": "0.2.40",
4
4
  "description": "Oomi OpenClaw channel plugin and bridge tooling",
5
5
  "bin": {
6
6
  "oomi": "bin/oomi-ai.js"
@@ -58,10 +58,12 @@
58
58
  "bin/sessionBridgeState.js",
59
59
  "lib",
60
60
  "openclaw.plugin.json",
61
- "openclaw.extension.js",
62
- "agent_instructions.md",
63
- "README.md",
64
- "skills/oomi",
65
- "templates/persona-app"
66
- ]
67
- }
61
+ "openclaw.extension.js",
62
+ "agent_instructions.md",
63
+ "README.md",
64
+ "skills/oomi",
65
+ "templates/persona-app",
66
+ "templates/persona-app/vendor/webspatial/core-sdk/dist",
67
+ "templates/persona-app/vendor/webspatial/react-sdk/dist"
68
+ ]
69
+ }
@@ -12,8 +12,10 @@
12
12
  "lint": "eslint ."
13
13
  },
14
14
  "dependencies": {
15
- "@webspatial/core-sdk": "^0.1.16",
16
- "@webspatial/react-sdk": "^0.1.16",
15
+ "@webspatial/core-sdk": "file:./vendor/webspatial/core-sdk",
16
+ "@webspatial/react-sdk": "file:./vendor/webspatial/react-sdk",
17
+ "@zumer/snapdom": "^1.9.14",
18
+ "html2canvas": "^1.4.1",
17
19
  "react": "^19.0.0",
18
20
  "react-dom": "^19.0.0",
19
21
  "react-router-dom": "^7.4.0",
@@ -24,8 +26,8 @@
24
26
  "@types/react": "^19.0.10",
25
27
  "@types/react-dom": "^19.0.4",
26
28
  "@vitejs/plugin-react": "^4.3.4",
27
- "@webspatial/builder": "^0.1.16",
28
- "@webspatial/platform-visionos": "^0.1.16",
29
+ "@webspatial/builder": "^1.2.1",
30
+ "@webspatial/platform-visionos": "^1.2.1",
29
31
  "@webspatial/vite-plugin": "^0.1.7",
30
32
  "cross-env": "^7.0.3",
31
33
  "dotenv-cli": "^8.0.0",