oomi-ai 0.2.38 → 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 (49) hide show
  1. package/README.md +24 -14
  2. package/agent_instructions.md +9 -0
  3. package/bin/oomi-ai.js +1 -1
  4. package/lib/openclawPaths.js +27 -18
  5. package/lib/personaRuntimeManager.js +181 -61
  6. package/lib/personaRuntimeProcess.js +392 -49
  7. package/lib/personaRuntimeSupervisor.js +20 -7
  8. package/lib/scaffold.js +14 -14
  9. package/openclaw.plugin.json +1 -1
  10. package/package.json +10 -8
  11. package/templates/persona-app/package.json +6 -4
  12. package/templates/persona-app/src/App.css +564 -79
  13. package/templates/persona-app/src/App.tsx +2 -2
  14. package/templates/persona-app/src/main.tsx +13 -0
  15. package/templates/persona-app/src/pages/HomePage.tsx +120 -39
  16. package/templates/persona-app/src/pages/ScenePage.tsx +2 -15
  17. package/templates/persona-app/src/persona/notes.ts +3 -1
  18. package/templates/persona-app/src/spatial.ts +82 -0
  19. package/templates/persona-app/template.json +1 -1
  20. package/templates/persona-app/vendor/webspatial/FORK.md +6 -0
  21. package/templates/persona-app/vendor/webspatial/core-sdk/LICENSE +21 -0
  22. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.d.ts +906 -0
  23. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js +75 -0
  24. package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js.map +1 -0
  25. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.d.ts +906 -0
  26. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js +3131 -0
  27. package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js.map +1 -0
  28. package/templates/persona-app/vendor/webspatial/core-sdk/package.json +45 -0
  29. package/templates/persona-app/vendor/webspatial/react-sdk/LICENSE +21 -0
  30. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.d.ts +365 -0
  31. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js +4296 -0
  32. package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js.map +1 -0
  33. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.d.ts +82 -0
  34. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js +66 -0
  35. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js.map +1 -0
  36. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.d.ts +2 -0
  37. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js +18 -0
  38. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js.map +1 -0
  39. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.d.ts +5 -0
  40. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js +66 -0
  41. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js.map +1 -0
  42. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.d.ts +1 -0
  43. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js +18 -0
  44. package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js.map +1 -0
  45. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.d.ts +365 -0
  46. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js +4336 -0
  47. package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js.map +1 -0
  48. package/templates/persona-app/vendor/webspatial/react-sdk/package.json +94 -0
  49. package/templates/persona-app/vite.config.ts +13 -0
@@ -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
  }
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
4
5
  import { createPersonaApiClient } from './personaApiClient.js';
5
6
  import { launchManagedPersonaRuntime } from './personaRuntimeManager.js';
6
7
  import { readPersonaRuntimeState } from './personaRuntimeRegistry.js';
@@ -17,15 +18,27 @@ function wait(ms) {
17
18
  }
18
19
 
19
20
  function listWorkspacePaths(workspaceRoot) {
20
- const safeRoot = trimString(workspaceRoot);
21
- if (!safeRoot || !fs.existsSync(safeRoot)) {
22
- return [];
21
+ const roots = [trimString(workspaceRoot), trimString(resolveOpenclawLegacyPersonasDir())]
22
+ .filter(Boolean)
23
+ .filter((root, index, values) => values.findIndex((candidate) => path.resolve(candidate) === path.resolve(root)) === index)
24
+ .filter((root) => fs.existsSync(root));
25
+
26
+ const workspacePaths = new Set();
27
+ for (const root of roots) {
28
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
29
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
30
+ const candidatePath = path.join(root, entry.name);
31
+ let dedupeKey = candidatePath;
32
+ try {
33
+ dedupeKey = fs.realpathSync(candidatePath);
34
+ } catch {
35
+ // fall back to the visible path when the real path is unavailable
36
+ }
37
+ workspacePaths.add(dedupeKey);
38
+ }
23
39
  }
24
40
 
25
- return fs
26
- .readdirSync(safeRoot, { withFileTypes: true })
27
- .filter((entry) => entry.isDirectory())
28
- .map((entry) => path.join(safeRoot, entry.name));
41
+ return Array.from(workspacePaths);
29
42
  }
30
43
 
31
44
  async function healthcheckOk(url) {
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.38",
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.38",
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",