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
package/README.md CHANGED
@@ -4,10 +4,12 @@ OpenClaw channel plugin and bridge tooling for Oomi managed chat and voice.
4
4
 
5
5
  ## Current Focus
6
6
 
7
- `0.2.38` 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
10
- - a stable `oomi personas launch-managed` flow for local persona hosting under `~/.openclaw/personas`
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
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
13
15
  - versioned `oomi openclaw profile init|apply` commands for deterministic local/dev or hosted setup flows
@@ -288,7 +290,7 @@ The local harness uses the `openrouter-free` preset for direct-provider smoke. I
288
290
  Use the scaffold flow when OpenClaw needs to build a managed persona app that will live inside Oomi:
289
291
 
290
292
  ```bash
291
- oomi personas scaffold market-analyst --name "Market Analyst" --description "Private app for reviewing my broker positions and risk." --out ~/.openclaw/personas/market-analyst
293
+ oomi personas scaffold market-analyst --name "Market Analyst" --description "Private app for reviewing my broker positions and risk." --out ~/.openclaw/workspace/personas/market-analyst
292
294
  ```
293
295
 
294
296
  Use:
@@ -307,7 +309,7 @@ oomi personas delete market-analyst
307
309
  oomi personas runtime-register market-analyst --local-port 4789
308
310
  oomi personas heartbeat market-analyst --local-port 4789
309
311
  oomi persona-jobs start pj_123
310
- oomi persona-jobs succeed pj_123 --workspace-path ~/.openclaw/personas/market-analyst --local-port 4789
312
+ oomi persona-jobs succeed pj_123 --workspace-path ~/.openclaw/workspace/personas/market-analyst --local-port 4789
311
313
  oomi persona-jobs fail pj_123 --code JOB_FAILED --message "Scaffold generation failed."
312
314
  ```
313
315
 
@@ -325,14 +327,22 @@ If you want to explicitly host or reuse the persona app on the OpenClaw machine
325
327
  oomi personas launch-managed cooking-persona --entry-url https://your-relay.example/oomi/cooking-persona
326
328
  ```
327
329
 
328
- This command:
329
-
330
- - reuses `~/.openclaw/personas/<slug>` as the stable workspace
331
- - scaffolds only when the workspace is missing
332
- - installs dependencies only when needed or forced
333
- - allocates or reuses a free local port
334
- - starts or reuses the local runtime
335
- - registers the runtime URL back to Oomi unless `--no-register` is set
330
+ This command:
331
+
332
+ - reuses `OPENCLAW_WORKSPACE/personas/<slug>` as the stable workspace
333
+ - scaffolds only when the workspace is missing
334
+ - installs dependencies only when needed or forced
335
+ - allocates or reuses a free local port
336
+ - starts or reuses the local runtime
337
+ - registers the runtime URL back to Oomi unless `--no-register` is set
338
+
339
+ For existing managed personas that are already open in Oomi, the safe edit flow is:
340
+
341
+ ```bash
342
+ oomi personas status <slug> --json
343
+ ```
344
+
345
+ The agent should use `editableWorkspacePath` from that output as the authoritative directory for reads, edits, and verification. `compatibilityWorkspacePath` is only a fallback for older installs.
336
346
 
337
347
  ## Bridge Health States
338
348
 
@@ -209,6 +209,15 @@ When generating a managed persona app for Oomi:
209
209
  4. Preserve the scaffolded WebSpatial/Vite shell, `public/oomi.health.json`, `oomi.runtime.json`, and `public/manifest.webmanifest`.
210
210
  5. After customization, start the app and register the runtime with Oomi using the current runtime contract.
211
211
 
212
+ When editing an existing managed persona that is already open in Oomi:
213
+
214
+ 1. Do not ask the user to find the app path manually if Oomi already selected the persona tab for you.
215
+ 2. First run `oomi personas status <slug> --json`.
216
+ 3. Use `editableWorkspacePath` from that command as the authoritative directory for reads, edits, and verification.
217
+ 4. Treat `compatibilityWorkspacePath` only as a fallback or migration clue.
218
+ 5. Preserve the scaffolded WebSpatial shell and runtime health files unless the user explicitly asks for a deeper structural change.
219
+ 6. Do not claim the persona changed unless you have verified the file contents changed in `editableWorkspacePath` or the runtime reflects the update.
220
+
212
221
  When executing a structured persona job from Oomi:
213
222
 
214
223
  1. Prefer `oomi persona-jobs execute --message-file <job.json>` when the backend has already produced a machine-readable job payload.
package/bin/oomi-ai.js CHANGED
@@ -329,7 +329,7 @@ Common flags:
329
329
  --health-path PATH Runtime health path (default: /oomi.health.json)
330
330
  --healthcheck-url URL Runtime healthcheck URL override
331
331
  --transport TEXT Runtime transport label (default: local, relay when --entry-url is used)
332
- --workspace-root PATH Persona workspace root (default: ~/.openclaw/personas)
332
+ --workspace-root PATH Persona workspace root (default: OPENCLAW_WORKSPACE/personas)
333
333
  --restart Restart an existing managed persona runtime before launch
334
334
  --started-at ISO Start timestamp override
335
335
  --observed-at ISO Heartbeat timestamp override
@@ -15,11 +15,11 @@ export function resolveOpenclawHome() {
15
15
  return path.join(os.homedir(), '.openclaw');
16
16
  }
17
17
 
18
- export function resolveOpenclawWorkspaceRoot() {
19
- const explicitWorkspace = trimString(process.env.OPENCLAW_WORKSPACE);
20
- if (explicitWorkspace) {
21
- return path.resolve(explicitWorkspace);
22
- }
18
+ export function resolveOpenclawWorkspaceRoot() {
19
+ const explicitWorkspace = trimString(process.env.OPENCLAW_WORKSPACE);
20
+ if (explicitWorkspace) {
21
+ return path.resolve(explicitWorkspace);
22
+ }
23
23
 
24
24
  const openclawHome = resolveOpenclawHome();
25
25
  const managedWorkspace = path.join(openclawHome, 'workspace');
@@ -27,12 +27,16 @@ export function resolveOpenclawWorkspaceRoot() {
27
27
  return managedWorkspace;
28
28
  }
29
29
 
30
- return openclawHome;
31
- }
32
-
33
- export function resolveOpenclawPath(...parts) {
34
- return path.join(resolveOpenclawHome(), ...parts);
35
- }
30
+ return openclawHome;
31
+ }
32
+
33
+ export function resolveOpenclawLegacyPersonasDir() {
34
+ return resolveOpenclawPath('personas');
35
+ }
36
+
37
+ export function resolveOpenclawPath(...parts) {
38
+ return path.join(resolveOpenclawHome(), ...parts);
39
+ }
36
40
 
37
41
  export function resolveOpenclawConfigCandidates() {
38
42
  return [
@@ -41,13 +45,18 @@ export function resolveOpenclawConfigCandidates() {
41
45
  ];
42
46
  }
43
47
 
44
- export function resolveOpenclawSkillsDir() {
45
- return resolveOpenclawPath('skills');
46
- }
47
-
48
- export function resolveOpenclawPersonasDir() {
49
- return resolveOpenclawPath('personas');
50
- }
48
+ export function resolveOpenclawSkillsDir() {
49
+ return resolveOpenclawPath('skills');
50
+ }
51
+
52
+ export function resolveOpenclawPersonasDir() {
53
+ const explicitPersonas = trimString(process.env.OPENCLAW_PERSONAS_DIR);
54
+ if (explicitPersonas) {
55
+ return path.resolve(explicitPersonas);
56
+ }
57
+
58
+ return path.join(resolveOpenclawWorkspaceRoot(), 'personas');
59
+ }
51
60
 
52
61
  export function resolveOpenclawIdentityPath() {
53
62
  return resolveOpenclawPath('identity', 'device.json');
@@ -1,17 +1,20 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
-
4
- import { findAvailablePort } from './personaPortAllocator.js';
5
- import {
6
- buildLocalPersonaRuntime,
7
- defaultPersonaWorkspaceRoot,
8
- installPersonaWorkspace,
9
- isPersonaWorkspaceProcessRunning,
10
- resolvePersonaDevCommand,
11
- startPersonaWorkspace,
12
- stopPersonaWorkspace,
13
- waitForPersonaRuntime,
14
- } from './personaRuntimeProcess.js';
3
+
4
+ import { findAvailablePort } from './personaPortAllocator.js';
5
+ import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
6
+ import {
7
+ buildLocalPersonaRuntime,
8
+ defaultPersonaWorkspaceRoot,
9
+ installPersonaWorkspace,
10
+ isPersonaWorkspaceProcessRunning,
11
+ resolvePersonaDevCommand,
12
+ syncLegacyWebSpatialScaffoldFiles,
13
+ syncVendoredWebSpatialPackages,
14
+ startPersonaWorkspace,
15
+ stopPersonaWorkspace,
16
+ waitForPersonaRuntime,
17
+ } from './personaRuntimeProcess.js';
15
18
  import {
16
19
  readPersonaRuntimeState,
17
20
  resolvePersonaRuntimeLogPath,
@@ -21,9 +24,110 @@ import {
21
24
  } from './personaRuntimeRegistry.js';
22
25
  import { scaffoldPersonaApp } from './scaffold.js';
23
26
 
24
- function trimString(value) {
25
- return typeof value === 'string' ? value.trim() : '';
26
- }
27
+ function trimString(value) {
28
+ return typeof value === 'string' ? value.trim() : '';
29
+ }
30
+
31
+ function samePath(a, b) {
32
+ return path.resolve(String(a || '')) === path.resolve(String(b || ''));
33
+ }
34
+
35
+ function pathExists(targetPath) {
36
+ return Boolean(targetPath) && fs.existsSync(targetPath);
37
+ }
38
+
39
+ function ensureDir(dirPath) {
40
+ fs.mkdirSync(dirPath, { recursive: true });
41
+ }
42
+
43
+ function ensureDirectoryLink(linkPath, targetPath) {
44
+ if (!linkPath || !targetPath || samePath(linkPath, targetPath) || pathExists(linkPath)) {
45
+ return false;
46
+ }
47
+
48
+ ensureDir(path.dirname(linkPath));
49
+ try {
50
+ fs.symlinkSync(
51
+ targetPath,
52
+ linkPath,
53
+ process.platform === 'win32' ? 'junction' : 'dir'
54
+ );
55
+ return true;
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
61
+ function resolveManagedPersonaWorkspacePaths({
62
+ slug,
63
+ workspaceRoot = defaultPersonaWorkspaceRoot(),
64
+ }) {
65
+ const canonicalWorkspaceRoot = path.resolve(workspaceRoot);
66
+ const canonicalWorkspacePath = resolvePersonaWorkspacePath({
67
+ workspaceRoot: canonicalWorkspaceRoot,
68
+ slug,
69
+ });
70
+ const legacyWorkspaceRoot = path.resolve(resolveOpenclawLegacyPersonasDir());
71
+ const legacyWorkspacePath = samePath(canonicalWorkspaceRoot, legacyWorkspaceRoot)
72
+ ? ''
73
+ : resolvePersonaWorkspacePath({
74
+ workspaceRoot: legacyWorkspaceRoot,
75
+ slug,
76
+ });
77
+
78
+ return {
79
+ canonicalWorkspaceRoot,
80
+ canonicalWorkspacePath,
81
+ legacyWorkspaceRoot: legacyWorkspacePath ? legacyWorkspaceRoot : '',
82
+ legacyWorkspacePath,
83
+ };
84
+ }
85
+
86
+ function resolveManagedPersonaWorkspace({
87
+ slug,
88
+ workspaceRoot = defaultPersonaWorkspaceRoot(),
89
+ }) {
90
+ const paths = resolveManagedPersonaWorkspacePaths({ slug, workspaceRoot });
91
+ ensureDir(paths.canonicalWorkspaceRoot);
92
+
93
+ let migratedFromLegacy = false;
94
+ let canonicalProxyCreated = false;
95
+ let legacyProxyCreated = false;
96
+
97
+ if (!pathExists(paths.canonicalWorkspacePath) && pathExists(paths.legacyWorkspacePath)) {
98
+ try {
99
+ fs.renameSync(paths.legacyWorkspacePath, paths.canonicalWorkspacePath);
100
+ migratedFromLegacy = true;
101
+ } catch {
102
+ canonicalProxyCreated = ensureDirectoryLink(
103
+ paths.canonicalWorkspacePath,
104
+ paths.legacyWorkspacePath
105
+ );
106
+ }
107
+ }
108
+
109
+ if (pathExists(paths.canonicalWorkspacePath) && paths.legacyWorkspacePath && !pathExists(paths.legacyWorkspacePath)) {
110
+ legacyProxyCreated = ensureDirectoryLink(
111
+ paths.legacyWorkspacePath,
112
+ paths.canonicalWorkspacePath
113
+ );
114
+ }
115
+
116
+ const workspacePath = pathExists(paths.canonicalWorkspacePath)
117
+ ? paths.canonicalWorkspacePath
118
+ : (pathExists(paths.legacyWorkspacePath) ? paths.legacyWorkspacePath : paths.canonicalWorkspacePath);
119
+
120
+ return {
121
+ ...paths,
122
+ workspacePath,
123
+ editableWorkspacePath: pathExists(paths.canonicalWorkspacePath)
124
+ ? paths.canonicalWorkspacePath
125
+ : workspacePath,
126
+ migratedFromLegacy,
127
+ canonicalProxyCreated,
128
+ legacyProxyCreated,
129
+ };
130
+ }
27
131
 
28
132
  export function slugifyPersonaName(name) {
29
133
  const normalized = trimString(name)
@@ -91,16 +195,18 @@ async function ensureWorkspaceScaffold({
91
195
  };
92
196
  }
93
197
 
94
- async function ensureWorkspaceInstall({
95
- workspacePath,
96
- forceInstall = false,
97
- }) {
98
- const nodeModulesPath = path.join(workspacePath, 'node_modules');
99
- if (!forceInstall && fs.existsSync(nodeModulesPath)) {
100
- return false;
101
- }
102
-
103
- 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 });
104
210
  return true;
105
211
  }
106
212
 
@@ -143,14 +249,15 @@ export async function launchManagedPersonaRuntime({
143
249
  const safeDescription = trimString(description) || safeName;
144
250
  if (!safeName) {
145
251
  throw new Error('Persona name is required.');
146
- }
147
-
148
- const safeSlug = trimString(slug) || slugifyPersonaName(safeName);
149
- const workspacePath = resolvePersonaWorkspacePath({
150
- workspaceRoot,
151
- slug: safeSlug,
152
- });
153
- const previousState = readPersonaRuntimeState(workspacePath);
252
+ }
253
+
254
+ const safeSlug = trimString(slug) || slugifyPersonaName(safeName);
255
+ const workspaceResolution = resolveManagedPersonaWorkspace({
256
+ slug: safeSlug,
257
+ workspaceRoot,
258
+ });
259
+ const workspacePath = workspaceResolution.workspacePath;
260
+ const previousState = readPersonaRuntimeState(workspacePath);
154
261
 
155
262
  const scaffoldInfo = await ensureWorkspaceScaffold({
156
263
  slug: safeSlug,
@@ -238,13 +345,16 @@ export async function launchManagedPersonaRuntime({
238
345
  devCommand: resolvePersonaDevCommand({ workspacePath, localPort }),
239
346
  });
240
347
 
241
- return {
242
- ok: true,
243
- slug: safeSlug,
244
- workspacePath,
245
- scaffolded: scaffoldInfo.scaffolded,
246
- installed,
247
- reusedRunningProcess: reusingRunningProcess,
348
+ return {
349
+ ok: true,
350
+ slug: safeSlug,
351
+ workspacePath,
352
+ editableWorkspacePath: workspaceResolution.editableWorkspacePath,
353
+ compatibilityWorkspacePath: workspaceResolution.legacyWorkspacePath || '',
354
+ migratedFromLegacy: workspaceResolution.migratedFromLegacy,
355
+ scaffolded: scaffoldInfo.scaffolded,
356
+ installed,
357
+ reusedRunningProcess: reusingRunningProcess,
248
358
  runtime: registration,
249
359
  localRuntime,
250
360
  state: runtimeState,
@@ -256,26 +366,33 @@ export function getManagedPersonaRuntimeStatus({
256
366
  workspaceRoot = defaultPersonaWorkspaceRoot(),
257
367
  }) {
258
368
  const safeSlug = trimString(slug);
259
- if (!safeSlug) {
260
- throw new Error('Persona slug is required.');
261
- }
262
-
263
- const workspacePath = resolvePersonaWorkspacePath({
264
- workspaceRoot,
265
- slug: safeSlug,
266
- });
267
- const state = readPersonaRuntimeState(workspacePath);
268
- const pid = Number.isInteger(state.pid) ? state.pid : null;
269
-
270
- return {
271
- slug: safeSlug,
272
- workspacePath,
273
- workspaceExists: fs.existsSync(workspacePath),
274
- runtimeStatePath: resolvePersonaRuntimeStatePath(workspacePath),
275
- processRunning: pid ? isPersonaWorkspaceProcessRunning(pid) : false,
276
- state,
277
- };
278
- }
369
+ if (!safeSlug) {
370
+ throw new Error('Persona slug is required.');
371
+ }
372
+
373
+ const workspaceResolution = resolveManagedPersonaWorkspace({
374
+ slug: safeSlug,
375
+ workspaceRoot,
376
+ });
377
+ const workspacePath = workspaceResolution.workspacePath;
378
+ const state = readPersonaRuntimeState(workspacePath);
379
+ const pid = Number.isInteger(state.pid) ? state.pid : null;
380
+
381
+ return {
382
+ slug: safeSlug,
383
+ workspaceRoot: workspaceResolution.canonicalWorkspaceRoot,
384
+ workspacePath,
385
+ editableWorkspacePath: workspaceResolution.editableWorkspacePath,
386
+ compatibilityWorkspacePath: workspaceResolution.legacyWorkspacePath || '',
387
+ workspaceExists: fs.existsSync(workspacePath),
388
+ runtimeStatePath: resolvePersonaRuntimeStatePath(workspacePath),
389
+ processRunning: pid ? isPersonaWorkspaceProcessRunning(pid) : false,
390
+ migratedFromLegacy: workspaceResolution.migratedFromLegacy,
391
+ canonicalProxyCreated: workspaceResolution.canonicalProxyCreated,
392
+ legacyProxyCreated: workspaceResolution.legacyProxyCreated,
393
+ state,
394
+ };
395
+ }
279
396
 
280
397
  export async function stopManagedPersonaRuntime({
281
398
  slug,
@@ -352,8 +469,11 @@ export async function destroyManagedPersonaRuntime({
352
469
  slug,
353
470
  workspaceRoot,
354
471
  });
355
- ensureWorkspacePathWithinRoot(status.workspacePath, workspaceRoot);
472
+ ensureWorkspacePathWithinRoot(status.workspacePath, status.workspaceRoot || workspaceRoot);
356
473
  fs.rmSync(status.workspacePath, { recursive: true, force: true });
474
+ if (status.compatibilityWorkspacePath && pathExists(status.compatibilityWorkspacePath)) {
475
+ fs.rmSync(status.compatibilityWorkspacePath, { recursive: true, force: true });
476
+ }
357
477
 
358
478
  return {
359
479
  ok: true,