oomi-ai 0.2.49 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +227 -463
- package/agent_instructions.md +244 -234
- package/bin/oomi-ai.js +4028 -5797
- package/bin/sessionBridgeState.js +78 -78
- package/lib/openclawPaths.js +70 -71
- package/lib/openclawProfile.js +216 -216
- package/lib/personaApiClient.js +133 -303
- package/lib/spokenMetadata.js +137 -137
- package/openclaw.extension.js +341 -341
- package/openclaw.plugin.json +17 -17
- package/package.json +59 -59
- package/persona-app/README.md +27 -0
- package/persona-app/registry/v1.json +63 -0
- package/persona-app/schema/persona-app.v1.schema.json +90 -0
- package/skills/oomi/SKILL.md +165 -182
- package/skills/oomi/agent_instructions.md +99 -80
- package/lib/channelPluginClient.js +0 -119
- package/lib/openclawDevGateway.js +0 -384
- package/lib/personaJobExecutor.js +0 -139
- package/lib/personaJobPoller.js +0 -112
- package/lib/personaPortAllocator.js +0 -36
- package/lib/personaRuntimeManager.js +0 -496
- package/lib/personaRuntimeProcess.js +0 -924
- package/lib/personaRuntimeRegistry.js +0 -67
- package/lib/personaRuntimeSupervisor.js +0 -330
- package/lib/scaffold.js +0 -108
- package/lib/template.js +0 -45
- package/skills/oomi/config.json +0 -3
- package/skills/oomi/scripts/get_avatar_capabilities.py +0 -40
- package/skills/oomi/scripts/get_data.py +0 -49
- package/skills/oomi/scripts/install_agent_instructions.py +0 -78
- package/skills/oomi/scripts/send_goal.py +0 -53
- package/skills/oomi/scripts/sync.py +0 -46
- package/skills/oomi/setup.py +0 -41
- package/templates/persona-app/.env.example +0 -8
- package/templates/persona-app/README.md +0 -47
- package/templates/persona-app/eslint.config.js +0 -28
- package/templates/persona-app/index.html +0 -18
- package/templates/persona-app/oomi.runtime.json +0 -13
- package/templates/persona-app/package.json +0 -44
- package/templates/persona-app/persona/brief.md +0 -14
- package/templates/persona-app/persona.json +0 -14
- package/templates/persona-app/public/manifest.webmanifest +0 -8
- package/templates/persona-app/public/oomi.health.json +0 -6
- package/templates/persona-app/src/App.css +0 -379
- package/templates/persona-app/src/App.tsx +0 -17
- package/templates/persona-app/src/index.css +0 -53
- package/templates/persona-app/src/main.tsx +0 -23
- package/templates/persona-app/src/pages/HomePage.tsx +0 -127
- package/templates/persona-app/src/pages/ScenePage.tsx +0 -158
- package/templates/persona-app/src/persona/config.ts +0 -6
- package/templates/persona-app/src/persona/notes.ts +0 -9
- package/templates/persona-app/src/spatial.ts +0 -82
- package/templates/persona-app/src/vite-env.d.ts +0 -3
- package/templates/persona-app/template.json +0 -13
- package/templates/persona-app/tsconfig.app.json +0 -23
- package/templates/persona-app/tsconfig.json +0 -7
- package/templates/persona-app/tsconfig.node.json +0 -21
- package/templates/persona-app/vendor/webspatial/FORK.md +0 -6
- package/templates/persona-app/vendor/webspatial/core-sdk/LICENSE +0 -21
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.d.ts +0 -906
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js +0 -75
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/iife/index.global.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.d.ts +0 -906
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js +0 -3131
- package/templates/persona-app/vendor/webspatial/core-sdk/dist/index.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/core-sdk/package.json +0 -45
- package/templates/persona-app/vendor/webspatial/react-sdk/LICENSE +0 -21
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.d.ts +0 -365
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js +0 -4167
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/default/index.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.d.ts +0 -82
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js +0 -66
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.d.ts +0 -2
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js +0 -18
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-dev-runtime.web.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.d.ts +0 -5
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js +0 -66
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.d.ts +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js +0 -18
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/jsx/jsx-runtime.web.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.d.ts +0 -365
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js +0 -4207
- package/templates/persona-app/vendor/webspatial/react-sdk/dist/web/index.js.map +0 -1
- package/templates/persona-app/vendor/webspatial/react-sdk/package.json +0 -94
- package/templates/persona-app/vite.config.ts +0 -31
package/lib/personaJobPoller.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { createChannelPluginClient } from './channelPluginClient.js';
|
|
2
|
-
|
|
3
|
-
function wait(ms) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
setTimeout(resolve, ms);
|
|
6
|
-
});
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function failureCodeFor(error) {
|
|
10
|
-
if (error && typeof error === 'object' && typeof error.code === 'string' && error.code.trim()) {
|
|
11
|
-
return error.code.trim();
|
|
12
|
-
}
|
|
13
|
-
return 'persona_job_delivery_failed';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function startPersonaJobPoller({
|
|
17
|
-
backendUrl,
|
|
18
|
-
deviceToken,
|
|
19
|
-
onMessage,
|
|
20
|
-
fetchImpl = globalThis.fetch,
|
|
21
|
-
metadataType = 'persona_job',
|
|
22
|
-
pollIntervalMs = 3000,
|
|
23
|
-
idleIntervalMs = 3000,
|
|
24
|
-
logger = console,
|
|
25
|
-
}) {
|
|
26
|
-
if (typeof onMessage !== 'function') {
|
|
27
|
-
throw new Error('onMessage callback is required.');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const client = createChannelPluginClient({
|
|
31
|
-
backendUrl,
|
|
32
|
-
deviceToken,
|
|
33
|
-
fetchImpl,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
let stopped = false;
|
|
37
|
-
let activeLoop = null;
|
|
38
|
-
|
|
39
|
-
async function loop() {
|
|
40
|
-
while (!stopped) {
|
|
41
|
-
try {
|
|
42
|
-
const payload = await client.pollMessages({
|
|
43
|
-
limit: 10,
|
|
44
|
-
metadataType,
|
|
45
|
-
});
|
|
46
|
-
const messages = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
47
|
-
|
|
48
|
-
if (messages.length === 0) {
|
|
49
|
-
await wait(idleIntervalMs);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const message of messages) {
|
|
54
|
-
if (stopped) break;
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
await onMessage(message);
|
|
58
|
-
await client.ackMessage({
|
|
59
|
-
messageId: message?.messageId,
|
|
60
|
-
outcome: 'delivered',
|
|
61
|
-
});
|
|
62
|
-
} catch (error) {
|
|
63
|
-
const messageId = typeof message?.messageId === 'string' ? message.messageId : '';
|
|
64
|
-
try {
|
|
65
|
-
if (messageId) {
|
|
66
|
-
await client.ackMessage({
|
|
67
|
-
messageId,
|
|
68
|
-
outcome: 'failed',
|
|
69
|
-
failureCode: failureCodeFor(error),
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
} catch (ackError) {
|
|
73
|
-
logger.error?.(
|
|
74
|
-
`[persona-jobs] failed to ack message ${messageId || 'unknown'}: ${
|
|
75
|
-
ackError instanceof Error ? ackError.message : String(ackError)
|
|
76
|
-
}`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
logger.error?.(
|
|
81
|
-
`[persona-jobs] execution failed for ${messageId || 'unknown'}: ${
|
|
82
|
-
error instanceof Error ? error.message : String(error)
|
|
83
|
-
}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (stopped) {
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
await wait(pollIntervalMs);
|
|
92
|
-
} catch (error) {
|
|
93
|
-
logger.error?.(
|
|
94
|
-
`[persona-jobs] poll failed: ${error instanceof Error ? error.message : String(error)}`
|
|
95
|
-
);
|
|
96
|
-
if (stopped) {
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
await wait(idleIntervalMs);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
activeLoop = loop();
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
stop() {
|
|
108
|
-
stopped = true;
|
|
109
|
-
},
|
|
110
|
-
completed: activeLoop,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import net from 'node:net';
|
|
2
|
-
|
|
3
|
-
function listenOnce({ port, host }) {
|
|
4
|
-
return new Promise((resolve) => {
|
|
5
|
-
const server = net.createServer();
|
|
6
|
-
server.unref();
|
|
7
|
-
|
|
8
|
-
server.once('error', () => {
|
|
9
|
-
resolve(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
server.listen({ port, host }, () => {
|
|
13
|
-
server.close(() => resolve(true));
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function findAvailablePort({
|
|
19
|
-
preferredPort,
|
|
20
|
-
host = '127.0.0.1',
|
|
21
|
-
basePort = 4789,
|
|
22
|
-
maxAttempts = 50,
|
|
23
|
-
} = {}) {
|
|
24
|
-
const preferred = Number(preferredPort);
|
|
25
|
-
const startPort = Number.isFinite(preferred) && preferred > 0 ? Math.floor(preferred) : Math.floor(basePort);
|
|
26
|
-
|
|
27
|
-
for (let offset = 0; offset < maxAttempts; offset += 1) {
|
|
28
|
-
const port = startPort + offset;
|
|
29
|
-
const available = await listenOnce({ port, host });
|
|
30
|
-
if (available) {
|
|
31
|
-
return port;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
throw new Error(`Unable to find an available port starting at ${startPort}.`);
|
|
36
|
-
}
|
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { findAvailablePort } from './personaPortAllocator.js';
|
|
5
|
-
import { resolveOpenclawLegacyPersonasDir } from './openclawPaths.js';
|
|
6
|
-
import {
|
|
7
|
-
buildLocalPersonaRuntime,
|
|
8
|
-
defaultPersonaWorkspaceRoot,
|
|
9
|
-
installPersonaWorkspace,
|
|
10
|
-
isPersonaWorkspaceProcessRunning,
|
|
11
|
-
resolvePersonaHealthPath,
|
|
12
|
-
resolvePersonaDevCommand,
|
|
13
|
-
syncLegacyWebSpatialScaffoldFiles,
|
|
14
|
-
syncVendoredWebSpatialPackages,
|
|
15
|
-
startPersonaWorkspace,
|
|
16
|
-
stopPersonaWorkspace,
|
|
17
|
-
waitForPersonaRuntime,
|
|
18
|
-
} from './personaRuntimeProcess.js';
|
|
19
|
-
import {
|
|
20
|
-
readPersonaRuntimeState,
|
|
21
|
-
resolvePersonaRuntimeLogPath,
|
|
22
|
-
resolvePersonaRuntimeStatePath,
|
|
23
|
-
resolvePersonaWorkspacePath,
|
|
24
|
-
updatePersonaRuntimeState,
|
|
25
|
-
} from './personaRuntimeRegistry.js';
|
|
26
|
-
import { scaffoldPersonaApp } from './scaffold.js';
|
|
27
|
-
|
|
28
|
-
function trimString(value) {
|
|
29
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function samePath(a, b) {
|
|
33
|
-
return path.resolve(String(a || '')) === path.resolve(String(b || ''));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function pathExists(targetPath) {
|
|
37
|
-
return Boolean(targetPath) && fs.existsSync(targetPath);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function ensureDir(dirPath) {
|
|
41
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function ensureDirectoryLink(linkPath, targetPath) {
|
|
45
|
-
if (!linkPath || !targetPath || samePath(linkPath, targetPath) || pathExists(linkPath)) {
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
ensureDir(path.dirname(linkPath));
|
|
50
|
-
try {
|
|
51
|
-
fs.symlinkSync(
|
|
52
|
-
targetPath,
|
|
53
|
-
linkPath,
|
|
54
|
-
process.platform === 'win32' ? 'junction' : 'dir'
|
|
55
|
-
);
|
|
56
|
-
return true;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function resolveManagedPersonaWorkspacePaths({
|
|
63
|
-
slug,
|
|
64
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
65
|
-
}) {
|
|
66
|
-
const canonicalWorkspaceRoot = path.resolve(workspaceRoot);
|
|
67
|
-
const canonicalWorkspacePath = resolvePersonaWorkspacePath({
|
|
68
|
-
workspaceRoot: canonicalWorkspaceRoot,
|
|
69
|
-
slug,
|
|
70
|
-
});
|
|
71
|
-
const legacyWorkspaceRoot = path.resolve(resolveOpenclawLegacyPersonasDir());
|
|
72
|
-
const legacyWorkspacePath = samePath(canonicalWorkspaceRoot, legacyWorkspaceRoot)
|
|
73
|
-
? ''
|
|
74
|
-
: resolvePersonaWorkspacePath({
|
|
75
|
-
workspaceRoot: legacyWorkspaceRoot,
|
|
76
|
-
slug,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
canonicalWorkspaceRoot,
|
|
81
|
-
canonicalWorkspacePath,
|
|
82
|
-
legacyWorkspaceRoot: legacyWorkspacePath ? legacyWorkspaceRoot : '',
|
|
83
|
-
legacyWorkspacePath,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function resolveManagedPersonaWorkspace({
|
|
88
|
-
slug,
|
|
89
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
90
|
-
}) {
|
|
91
|
-
const paths = resolveManagedPersonaWorkspacePaths({ slug, workspaceRoot });
|
|
92
|
-
ensureDir(paths.canonicalWorkspaceRoot);
|
|
93
|
-
|
|
94
|
-
let migratedFromLegacy = false;
|
|
95
|
-
let canonicalProxyCreated = false;
|
|
96
|
-
let legacyProxyCreated = false;
|
|
97
|
-
|
|
98
|
-
if (!pathExists(paths.canonicalWorkspacePath) && pathExists(paths.legacyWorkspacePath)) {
|
|
99
|
-
try {
|
|
100
|
-
fs.renameSync(paths.legacyWorkspacePath, paths.canonicalWorkspacePath);
|
|
101
|
-
migratedFromLegacy = true;
|
|
102
|
-
} catch {
|
|
103
|
-
canonicalProxyCreated = ensureDirectoryLink(
|
|
104
|
-
paths.canonicalWorkspacePath,
|
|
105
|
-
paths.legacyWorkspacePath
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (pathExists(paths.canonicalWorkspacePath) && paths.legacyWorkspacePath && !pathExists(paths.legacyWorkspacePath)) {
|
|
111
|
-
legacyProxyCreated = ensureDirectoryLink(
|
|
112
|
-
paths.legacyWorkspacePath,
|
|
113
|
-
paths.canonicalWorkspacePath
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const workspacePath = pathExists(paths.canonicalWorkspacePath)
|
|
118
|
-
? paths.canonicalWorkspacePath
|
|
119
|
-
: (pathExists(paths.legacyWorkspacePath) ? paths.legacyWorkspacePath : paths.canonicalWorkspacePath);
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
...paths,
|
|
123
|
-
workspacePath,
|
|
124
|
-
editableWorkspacePath: pathExists(paths.canonicalWorkspacePath)
|
|
125
|
-
? paths.canonicalWorkspacePath
|
|
126
|
-
: workspacePath,
|
|
127
|
-
migratedFromLegacy,
|
|
128
|
-
canonicalProxyCreated,
|
|
129
|
-
legacyProxyCreated,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function slugifyPersonaName(name) {
|
|
134
|
-
const normalized = trimString(name)
|
|
135
|
-
.toLowerCase()
|
|
136
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
137
|
-
.replace(/^-+|-+$/g, '')
|
|
138
|
-
.replace(/-{2,}/g, '-');
|
|
139
|
-
|
|
140
|
-
if (!normalized) {
|
|
141
|
-
throw new Error('Persona name must include at least one letter or number.');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return normalized;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function resolveHealthPath(workspacePath) {
|
|
148
|
-
return resolvePersonaHealthPath({
|
|
149
|
-
workspacePath,
|
|
150
|
-
fallback: '/oomi.health.json',
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function workspaceNeedsScaffold(workspacePath) {
|
|
155
|
-
return !fs.existsSync(path.join(workspacePath, 'package.json'));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function ensureWorkspaceScaffold({
|
|
159
|
-
slug,
|
|
160
|
-
name,
|
|
161
|
-
description,
|
|
162
|
-
workspacePath,
|
|
163
|
-
templateVersion,
|
|
164
|
-
}) {
|
|
165
|
-
if (!workspaceNeedsScaffold(workspacePath)) {
|
|
166
|
-
return {
|
|
167
|
-
scaffolded: false,
|
|
168
|
-
workspacePath,
|
|
169
|
-
healthPath: resolveHealthPath(workspacePath),
|
|
170
|
-
defaultPort: 4789,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const scaffoldResult = scaffoldPersonaApp({
|
|
175
|
-
slug,
|
|
176
|
-
name,
|
|
177
|
-
description,
|
|
178
|
-
outDir: workspacePath,
|
|
179
|
-
templateVersion,
|
|
180
|
-
force: false,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
scaffolded: true,
|
|
185
|
-
workspacePath,
|
|
186
|
-
healthPath: scaffoldResult.healthPath,
|
|
187
|
-
defaultPort: scaffoldResult.defaultPort,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function ensureWorkspaceInstall({
|
|
192
|
-
workspacePath,
|
|
193
|
-
forceInstall = false,
|
|
194
|
-
}) {
|
|
195
|
-
const packageSyncChanged = syncVendoredWebSpatialPackages({ workspacePath });
|
|
196
|
-
syncLegacyWebSpatialScaffoldFiles({ workspacePath });
|
|
197
|
-
const nodeModulesPath = path.join(workspacePath, 'node_modules');
|
|
198
|
-
if (!forceInstall && fs.existsSync(nodeModulesPath) && !packageSyncChanged) {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
await installPersonaWorkspace({ workspacePath });
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function buildRuntimeRegistration({
|
|
207
|
-
localRuntime,
|
|
208
|
-
entryUrl,
|
|
209
|
-
transport,
|
|
210
|
-
}) {
|
|
211
|
-
const safeEntryUrl = trimString(entryUrl);
|
|
212
|
-
if (safeEntryUrl) {
|
|
213
|
-
return {
|
|
214
|
-
endpoint: safeEntryUrl,
|
|
215
|
-
transport: trimString(transport) || 'relay',
|
|
216
|
-
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
217
|
-
localPort: localRuntime.localPort,
|
|
218
|
-
localEndpoint: localRuntime.endpoint,
|
|
219
|
-
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
endpoint: localRuntime.reachableEndpoint || localRuntime.endpoint,
|
|
225
|
-
transport: trimString(transport) || localRuntime.transport,
|
|
226
|
-
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
227
|
-
localPort: localRuntime.localPort,
|
|
228
|
-
localEndpoint: localRuntime.endpoint,
|
|
229
|
-
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export async function launchManagedPersonaRuntime({
|
|
234
|
-
slug,
|
|
235
|
-
name,
|
|
236
|
-
description,
|
|
237
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
238
|
-
templateVersion = 'v1',
|
|
239
|
-
forceInstall = false,
|
|
240
|
-
restart = false,
|
|
241
|
-
logFilePath = '',
|
|
242
|
-
entryUrl = '',
|
|
243
|
-
transport = '',
|
|
244
|
-
} = {}) {
|
|
245
|
-
const safeName = trimString(name);
|
|
246
|
-
const safeDescription = trimString(description) || safeName;
|
|
247
|
-
if (!safeName) {
|
|
248
|
-
throw new Error('Persona name is required.');
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const safeSlug = trimString(slug) || slugifyPersonaName(safeName);
|
|
252
|
-
const workspaceResolution = resolveManagedPersonaWorkspace({
|
|
253
|
-
slug: safeSlug,
|
|
254
|
-
workspaceRoot,
|
|
255
|
-
});
|
|
256
|
-
const workspacePath = workspaceResolution.workspacePath;
|
|
257
|
-
const previousState = readPersonaRuntimeState(workspacePath);
|
|
258
|
-
|
|
259
|
-
const scaffoldInfo = await ensureWorkspaceScaffold({
|
|
260
|
-
slug: safeSlug,
|
|
261
|
-
name: safeName,
|
|
262
|
-
description: safeDescription,
|
|
263
|
-
workspacePath,
|
|
264
|
-
templateVersion,
|
|
265
|
-
});
|
|
266
|
-
const installed = await ensureWorkspaceInstall({
|
|
267
|
-
workspacePath,
|
|
268
|
-
forceInstall,
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const healthPath = resolveHealthPath(workspacePath);
|
|
272
|
-
const preferredPort = previousState.localPort || scaffoldInfo.defaultPort;
|
|
273
|
-
const expectedDevCommand = resolvePersonaDevCommand({
|
|
274
|
-
workspacePath,
|
|
275
|
-
localPort: preferredPort,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
let reusingRunningProcess = false;
|
|
279
|
-
if (
|
|
280
|
-
!restart &&
|
|
281
|
-
Number.isInteger(previousState.pid) &&
|
|
282
|
-
isPersonaWorkspaceProcessRunning(previousState.pid, {
|
|
283
|
-
workspacePath,
|
|
284
|
-
expectedCommand: expectedDevCommand,
|
|
285
|
-
localPort: preferredPort,
|
|
286
|
-
})
|
|
287
|
-
) {
|
|
288
|
-
try {
|
|
289
|
-
await waitForPersonaRuntime({
|
|
290
|
-
healthcheckUrl: previousState.healthcheckUrl || buildLocalPersonaRuntime({
|
|
291
|
-
localPort: preferredPort,
|
|
292
|
-
healthPath,
|
|
293
|
-
}).healthcheckUrl,
|
|
294
|
-
timeoutMs: 4000,
|
|
295
|
-
intervalMs: 500,
|
|
296
|
-
});
|
|
297
|
-
reusingRunningProcess = true;
|
|
298
|
-
} catch {
|
|
299
|
-
reusingRunningProcess = false;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (restart && Number.isInteger(previousState.pid) && isPersonaWorkspaceProcessRunning(previousState.pid)) {
|
|
304
|
-
await stopPersonaWorkspace({ pid: previousState.pid });
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const localPort = reusingRunningProcess
|
|
308
|
-
? previousState.localPort
|
|
309
|
-
: await findAvailablePort({
|
|
310
|
-
preferredPort,
|
|
311
|
-
});
|
|
312
|
-
const localRuntime = buildLocalPersonaRuntime({
|
|
313
|
-
localPort,
|
|
314
|
-
healthPath,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
let processInfo = {
|
|
318
|
-
pid: Number.isInteger(previousState.pid) ? previousState.pid : null,
|
|
319
|
-
logFilePath: trimString(previousState.logFilePath) || resolvePersonaRuntimeLogPath(workspacePath),
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
if (!reusingRunningProcess) {
|
|
323
|
-
processInfo = startPersonaWorkspace({
|
|
324
|
-
workspacePath,
|
|
325
|
-
logFilePath: logFilePath || resolvePersonaRuntimeLogPath(workspacePath),
|
|
326
|
-
localPort,
|
|
327
|
-
});
|
|
328
|
-
await waitForPersonaRuntime({
|
|
329
|
-
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const registration = buildRuntimeRegistration({
|
|
334
|
-
localRuntime,
|
|
335
|
-
entryUrl,
|
|
336
|
-
transport,
|
|
337
|
-
});
|
|
338
|
-
const runtimeState = updatePersonaRuntimeState(workspacePath, {
|
|
339
|
-
slug: safeSlug,
|
|
340
|
-
name: safeName,
|
|
341
|
-
description: safeDescription,
|
|
342
|
-
workspacePath,
|
|
343
|
-
templateVersion,
|
|
344
|
-
localPort: localRuntime.localPort,
|
|
345
|
-
localEndpoint: localRuntime.endpoint,
|
|
346
|
-
reachableEndpoint: localRuntime.reachableEndpoint,
|
|
347
|
-
bindHost: localRuntime.bindHost,
|
|
348
|
-
reachableHost: localRuntime.reachableHost,
|
|
349
|
-
endpoint: registration.endpoint,
|
|
350
|
-
entryUrl: registration.endpoint,
|
|
351
|
-
transport: registration.transport,
|
|
352
|
-
healthcheckUrl: localRuntime.healthcheckUrl,
|
|
353
|
-
pid: processInfo.pid,
|
|
354
|
-
logFilePath: processInfo.logFilePath,
|
|
355
|
-
status: 'running',
|
|
356
|
-
lastStartedAt: new Date().toISOString(),
|
|
357
|
-
devCommand: resolvePersonaDevCommand({ workspacePath, localPort }),
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
return {
|
|
361
|
-
ok: true,
|
|
362
|
-
slug: safeSlug,
|
|
363
|
-
workspacePath,
|
|
364
|
-
editableWorkspacePath: workspaceResolution.editableWorkspacePath,
|
|
365
|
-
compatibilityWorkspacePath: workspaceResolution.legacyWorkspacePath || '',
|
|
366
|
-
migratedFromLegacy: workspaceResolution.migratedFromLegacy,
|
|
367
|
-
scaffolded: scaffoldInfo.scaffolded,
|
|
368
|
-
installed,
|
|
369
|
-
reusedRunningProcess: reusingRunningProcess,
|
|
370
|
-
runtime: registration,
|
|
371
|
-
localRuntime,
|
|
372
|
-
state: runtimeState,
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export function getManagedPersonaRuntimeStatus({
|
|
377
|
-
slug,
|
|
378
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
379
|
-
}) {
|
|
380
|
-
const safeSlug = trimString(slug);
|
|
381
|
-
if (!safeSlug) {
|
|
382
|
-
throw new Error('Persona slug is required.');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const workspaceResolution = resolveManagedPersonaWorkspace({
|
|
386
|
-
slug: safeSlug,
|
|
387
|
-
workspaceRoot,
|
|
388
|
-
});
|
|
389
|
-
const workspacePath = workspaceResolution.workspacePath;
|
|
390
|
-
const state = readPersonaRuntimeState(workspacePath);
|
|
391
|
-
const pid = Number.isInteger(state.pid) ? state.pid : null;
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
slug: safeSlug,
|
|
395
|
-
workspaceRoot: workspaceResolution.canonicalWorkspaceRoot,
|
|
396
|
-
workspacePath,
|
|
397
|
-
editableWorkspacePath: workspaceResolution.editableWorkspacePath,
|
|
398
|
-
compatibilityWorkspacePath: workspaceResolution.legacyWorkspacePath || '',
|
|
399
|
-
workspaceExists: fs.existsSync(workspacePath),
|
|
400
|
-
runtimeStatePath: resolvePersonaRuntimeStatePath(workspacePath),
|
|
401
|
-
processRunning: pid ? isPersonaWorkspaceProcessRunning(pid) : false,
|
|
402
|
-
migratedFromLegacy: workspaceResolution.migratedFromLegacy,
|
|
403
|
-
canonicalProxyCreated: workspaceResolution.canonicalProxyCreated,
|
|
404
|
-
legacyProxyCreated: workspaceResolution.legacyProxyCreated,
|
|
405
|
-
state,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
export async function stopManagedPersonaRuntime({
|
|
410
|
-
slug,
|
|
411
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
412
|
-
}) {
|
|
413
|
-
const status = getManagedPersonaRuntimeStatus({ slug, workspaceRoot });
|
|
414
|
-
if (!status.workspaceExists) {
|
|
415
|
-
return {
|
|
416
|
-
ok: true,
|
|
417
|
-
stopped: false,
|
|
418
|
-
missingWorkspace: true,
|
|
419
|
-
workspacePath: status.workspacePath,
|
|
420
|
-
state: status.state,
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const pid = Number.isInteger(status.state.pid) ? status.state.pid : null;
|
|
425
|
-
if (!pid) {
|
|
426
|
-
const nextState = updatePersonaRuntimeState(status.workspacePath, {
|
|
427
|
-
status: 'stopped',
|
|
428
|
-
stoppedAt: new Date().toISOString(),
|
|
429
|
-
});
|
|
430
|
-
return {
|
|
431
|
-
ok: true,
|
|
432
|
-
stopped: false,
|
|
433
|
-
state: nextState,
|
|
434
|
-
workspacePath: status.workspacePath,
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
await stopPersonaWorkspace({ pid });
|
|
439
|
-
const nextState = updatePersonaRuntimeState(status.workspacePath, {
|
|
440
|
-
status: 'stopped',
|
|
441
|
-
stoppedAt: new Date().toISOString(),
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
ok: true,
|
|
446
|
-
stopped: true,
|
|
447
|
-
state: nextState,
|
|
448
|
-
workspacePath: status.workspacePath,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function ensureWorkspacePathWithinRoot(workspacePath, workspaceRoot) {
|
|
453
|
-
const resolvedWorkspacePath = path.resolve(workspacePath);
|
|
454
|
-
const resolvedRoot = path.resolve(workspaceRoot);
|
|
455
|
-
const relative = path.relative(resolvedRoot, resolvedWorkspacePath);
|
|
456
|
-
if (
|
|
457
|
-
relative === '' ||
|
|
458
|
-
relative.startsWith('..') ||
|
|
459
|
-
path.isAbsolute(relative)
|
|
460
|
-
) {
|
|
461
|
-
throw new Error(`Refusing to delete workspace outside persona root: ${resolvedWorkspacePath}`);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
export async function destroyManagedPersonaRuntime({
|
|
466
|
-
slug,
|
|
467
|
-
workspaceRoot = defaultPersonaWorkspaceRoot(),
|
|
468
|
-
}) {
|
|
469
|
-
const status = getManagedPersonaRuntimeStatus({ slug, workspaceRoot });
|
|
470
|
-
if (!status.workspaceExists) {
|
|
471
|
-
return {
|
|
472
|
-
ok: true,
|
|
473
|
-
deleted: false,
|
|
474
|
-
missingWorkspace: true,
|
|
475
|
-
workspacePath: status.workspacePath,
|
|
476
|
-
state: status.state,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const stopResult = await stopManagedPersonaRuntime({
|
|
481
|
-
slug,
|
|
482
|
-
workspaceRoot,
|
|
483
|
-
});
|
|
484
|
-
ensureWorkspacePathWithinRoot(status.workspacePath, status.workspaceRoot || workspaceRoot);
|
|
485
|
-
fs.rmSync(status.workspacePath, { recursive: true, force: true });
|
|
486
|
-
if (status.compatibilityWorkspacePath && pathExists(status.compatibilityWorkspacePath)) {
|
|
487
|
-
fs.rmSync(status.compatibilityWorkspacePath, { recursive: true, force: true });
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
return {
|
|
491
|
-
ok: true,
|
|
492
|
-
deleted: true,
|
|
493
|
-
stopped: Boolean(stopResult.stopped),
|
|
494
|
-
workspacePath: status.workspacePath,
|
|
495
|
-
};
|
|
496
|
-
}
|