@rynx-ai/daemon 0.1.11-beta.2 → 0.1.11-beta.21
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/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +20 -18
- package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
- package/bundled-plugins/plugins/lark/dist/runtime.js +6046 -6022
- package/bundled-plugins/plugins/lark/dist/settings.js +6006 -5988
- package/bundled-plugins/plugins/lark/package.json +1 -1
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/index.html +2 -2
- package/dist/agent-file.js +0 -1
- package/dist/browser-artifact-management.d.ts +4 -1
- package/dist/browser-artifact-management.js +22 -11
- package/dist/browser-artifacts.d.ts +1 -0
- package/dist/browser-artifacts.js +1 -0
- package/dist/chrome-for-testing-runtime.d.ts +27 -0
- package/dist/chrome-for-testing-runtime.js +194 -0
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +37 -6
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +87 -4
- package/dist/control-link-store.js +4 -3
- package/dist/daemon-owner.js +4 -7
- package/dist/daemon-server.d.ts +4 -1
- package/dist/daemon-server.js +158 -45
- package/dist/db.d.ts +2 -2
- package/dist/db.js +55 -20
- package/dist/entry-path.d.ts +3 -0
- package/dist/entry-path.js +12 -0
- package/dist/index-daemon.js +3 -87
- package/dist/lifecycle.d.ts +13 -0
- package/dist/lifecycle.js +19 -0
- package/dist/maintenance-lease.d.ts +39 -0
- package/dist/maintenance-lease.js +250 -0
- package/dist/migrations/cleanup-legacy-sessions.js +4 -3
- package/dist/plugin-cli.d.ts +10 -0
- package/dist/plugin-cli.js +19 -2
- package/dist/plugin-host-rpc.d.ts +35 -6
- package/dist/plugin-host-rpc.js +222 -59
- package/dist/plugin-installer.js +62 -2
- package/dist/plugin-management-service.js +0 -2
- package/dist/plugin-marketplace.js +2 -1
- package/dist/plugin-store.js +2 -4
- package/dist/plugin-supervisor.d.ts +5 -0
- package/dist/plugin-supervisor.js +218 -33
- package/dist/pm2.d.ts +11 -2
- package/dist/pm2.js +116 -19
- package/dist/process.d.ts +12 -0
- package/dist/process.js +91 -0
- package/dist/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- package/dist/remote-runtime-target-store.d.ts +2 -2
- package/dist/remote-runtime-target-store.js +6 -6
- package/dist/session-emulator-binding-store.js +4 -6
- package/dist/session-fork-store.js +24 -8
- package/dist/session-launch-operations.d.ts +10 -1
- package/dist/session-launch-operations.js +21 -11
- package/dist/session-log-store.js +2 -1
- package/dist/session-meta-store.d.ts +5 -0
- package/dist/session-meta-store.js +25 -2
- package/dist/session-portal-grant-store.d.ts +29 -0
- package/dist/session-portal-grant-store.js +85 -0
- package/dist/session-resource-store.js +3 -2
- package/dist/setup-service.d.ts +21 -0
- package/dist/setup-service.js +39 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +307 -10
- package/dist/sqlite.d.ts +22 -0
- package/dist/sqlite.js +57 -0
- package/dist/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +37 -21
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
- package/dist/browser-cli-args.d.ts +0 -20
- package/dist/browser-cli-args.js +0 -100
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -550
- package/dist/control-client.d.ts +0 -122
- package/dist/control-client.js +0 -855
- package/dist/control-endpoint.d.ts +0 -14
- package/dist/control-endpoint.js +0 -124
- package/dist/desktop-browser-host-client.d.ts +0 -35
- package/dist/desktop-browser-host-client.js +0 -415
- package/dist/setup.d.ts +0 -20
- package/dist/setup.js +0 -386
- package/dist/update.d.ts +0 -25
- package/dist/update.js +0 -133
- /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
package/dist/plugin-installer.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { MARKETPLACE_ID_PATTERN, pluginCliInvocationLeasePid, readPluginRuntimeLeases, removePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
5
5
|
import { rynxHome } from "@rynx-ai/core";
|
|
6
6
|
import { db } from "./db.js";
|
|
7
|
+
import { transaction } from "./sqlite.js";
|
|
7
8
|
import { isBuiltInPluginMarketplace } from "./plugin-marketplace.js";
|
|
8
9
|
import { detectPluginSource, inspectPluginPackage, materializePluginPackage, } from "./plugin-package.js";
|
|
9
10
|
import { canonicalPluginId, getPlugin, removePlugin, upsertPlugin, } from "./plugin-store.js";
|
|
@@ -117,7 +118,7 @@ export async function preparePluginInstallation(spec, onProgress = () => { }, op
|
|
|
117
118
|
promotedByThisTransaction = true;
|
|
118
119
|
}
|
|
119
120
|
promotedPath = realpathSync(target);
|
|
120
|
-
db()
|
|
121
|
+
transaction(db(), () => {
|
|
121
122
|
const current = getPlugin(canonicalId);
|
|
122
123
|
if (!samePluginGeneration(existing, current)) {
|
|
123
124
|
throw new Error(`plugin "${canonicalId}" changed concurrently; retry`);
|
|
@@ -172,7 +173,7 @@ export async function preparePluginInstallation(spec, onProgress = () => { }, op
|
|
|
172
173
|
}
|
|
173
174
|
export async function uninstallPlugin(identity, onProgress = () => { }, options = {}) {
|
|
174
175
|
let record;
|
|
175
|
-
const removed = db()
|
|
176
|
+
const removed = transaction(db(), () => {
|
|
176
177
|
record = getPlugin(identity);
|
|
177
178
|
if (!record)
|
|
178
179
|
return false;
|
|
@@ -180,6 +181,8 @@ export async function uninstallPlugin(identity, onProgress = () => { }, options
|
|
|
180
181
|
throw new Error(`bundled plugin "${record.canonicalId}" cannot be uninstalled; disable it instead`);
|
|
181
182
|
}
|
|
182
183
|
assertNoActiveLeases(record);
|
|
184
|
+
if (options.purgeData)
|
|
185
|
+
assertNoOwnedSessions(record);
|
|
183
186
|
return removePlugin(record.canonicalId);
|
|
184
187
|
}).immediate();
|
|
185
188
|
if (!removed)
|
|
@@ -193,9 +196,37 @@ export async function uninstallPlugin(identity, onProgress = () => { }, options
|
|
|
193
196
|
onProgress(`uninstalled plugin "${record.canonicalId}"`);
|
|
194
197
|
return true;
|
|
195
198
|
}
|
|
199
|
+
function assertNoOwnedSessions(record) {
|
|
200
|
+
const row = db()
|
|
201
|
+
.prepare(`WITH RECURSIVE owned(id) AS (
|
|
202
|
+
SELECT session_id
|
|
203
|
+
FROM plugin_session_launch_operations
|
|
204
|
+
WHERE plugin_id = ?
|
|
205
|
+
UNION
|
|
206
|
+
SELECT id FROM sessions WHERE source = ?
|
|
207
|
+
UNION
|
|
208
|
+
SELECT sessions.id
|
|
209
|
+
FROM sessions
|
|
210
|
+
JOIN owned ON sessions.forked_from_session_id = owned.id
|
|
211
|
+
UNION
|
|
212
|
+
SELECT session_fork_operations.target_session_id
|
|
213
|
+
FROM session_fork_operations
|
|
214
|
+
JOIN owned ON session_fork_operations.source_session_id = owned.id
|
|
215
|
+
)
|
|
216
|
+
SELECT COUNT(*) AS count
|
|
217
|
+
FROM sessions
|
|
218
|
+
JOIN owned ON sessions.id = owned.id`)
|
|
219
|
+
.get(record.canonicalId, `plugin:${record.canonicalId}`);
|
|
220
|
+
if (row.count > 0) {
|
|
221
|
+
throw new Error(`cannot purge plugin "${record.canonicalId}": ${row.count} plugin-owned Session(s) still exist`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
196
224
|
export function pruneInactivePluginInstallations() {
|
|
197
225
|
const { cacheRoot } = ensureStorageRoots();
|
|
198
226
|
const active = new Set(listActivePaths().map(canonicalFilesystemPath));
|
|
227
|
+
for (const leased of listActiveRuntimeLeasePaths(cacheRoot)) {
|
|
228
|
+
active.add(leased);
|
|
229
|
+
}
|
|
199
230
|
const removed = [];
|
|
200
231
|
for (const marketplace of safeDirectories(cacheRoot)) {
|
|
201
232
|
for (const plugin of safeDirectories(join(cacheRoot, marketplace))) {
|
|
@@ -327,6 +358,35 @@ function listActivePaths() {
|
|
|
327
358
|
}
|
|
328
359
|
return paths;
|
|
329
360
|
}
|
|
361
|
+
function listActiveRuntimeLeasePaths(cacheRoot) {
|
|
362
|
+
const dataRoot = join(rynxHome(), "plugins", "data");
|
|
363
|
+
const active = [];
|
|
364
|
+
for (const marketplace of safeDirectories(dataRoot)) {
|
|
365
|
+
for (const plugin of safeDirectories(join(dataRoot, marketplace))) {
|
|
366
|
+
const generationRoot = join(cacheRoot, marketplace, plugin);
|
|
367
|
+
const managedGenerations = new Set(safeDirectories(generationRoot).map((generation) => canonicalFilesystemPath(join(generationRoot, generation))));
|
|
368
|
+
if (managedGenerations.size === 0)
|
|
369
|
+
continue;
|
|
370
|
+
const dataDir = join(dataRoot, marketplace, plugin);
|
|
371
|
+
for (const lease of readPluginRuntimeLeases(dataDir)) {
|
|
372
|
+
// A lease is plugin-owned input. It may pin only the same plugin
|
|
373
|
+
// namespace from which it was read and only one exact host-owned,
|
|
374
|
+
// immutable generation directory.
|
|
375
|
+
if (lease.pluginId !== plugin)
|
|
376
|
+
continue;
|
|
377
|
+
const pid = pluginCliInvocationLeasePid(lease.leaseId);
|
|
378
|
+
if (pid !== undefined && !isProcessAlive(pid)) {
|
|
379
|
+
removePluginRuntimeLease(dataDir, lease.leaseId);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
const installPath = canonicalFilesystemPath(lease.installPath);
|
|
383
|
+
if (managedGenerations.has(installPath))
|
|
384
|
+
active.push(installPath);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return active;
|
|
389
|
+
}
|
|
330
390
|
function safeProgress(callback) {
|
|
331
391
|
return (line) => {
|
|
332
392
|
try {
|
|
@@ -295,7 +295,6 @@ function toManagementItem(record, settingsAvailable = false, updateState = "unkn
|
|
|
295
295
|
enabled: record.enabled,
|
|
296
296
|
runtimeState: record.manifest.runtime ? record.runtimeState : "absent",
|
|
297
297
|
...(record.runtimeError ? { runtimeError: record.runtimeError } : {}),
|
|
298
|
-
capabilities: [...record.manifest.capabilities],
|
|
299
298
|
hasSettingsUi: settingsAvailable,
|
|
300
299
|
updatedAt: record.updatedAt,
|
|
301
300
|
};
|
|
@@ -322,7 +321,6 @@ function toInstallPreparation(token, expiresAtMs, prepared) {
|
|
|
322
321
|
subdirectory: prepared.requestedSource.subdir ?? ".",
|
|
323
322
|
},
|
|
324
323
|
digest: prepared.integrity,
|
|
325
|
-
capabilities: [...prepared.manifest.capabilities],
|
|
326
324
|
hasRuntime: Boolean(prepared.manifest.runtime),
|
|
327
325
|
hasSettingsUi: Boolean(prepared.manifest.settings),
|
|
328
326
|
...(prepared.manifest.runtime ? {
|
|
@@ -3,6 +3,7 @@ import { isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
|
3
3
|
import { MARKETPLACE_ID_PATTERN, parsePluginMarketplace, } from "@rynx-ai/plugin-sdk";
|
|
4
4
|
import { rynxHome } from "@rynx-ai/core";
|
|
5
5
|
import { db } from "./db.js";
|
|
6
|
+
import { transaction } from "./sqlite.js";
|
|
6
7
|
import { detectPluginSource, materializePluginPackage, parsePluginSourceSpec, pluginSourceFingerprint, } from "./plugin-package.js";
|
|
7
8
|
export function listPluginMarketplaces() {
|
|
8
9
|
return db().prepare("SELECT * FROM plugin_marketplaces ORDER BY marketplace_id").all().map(toRecord);
|
|
@@ -27,7 +28,7 @@ export async function addPluginMarketplace(spec, options = {}) {
|
|
|
27
28
|
throw new Error(`built-in marketplace "${marketplaceId}" is managed by Rynx`);
|
|
28
29
|
}
|
|
29
30
|
const now = new Date().toISOString();
|
|
30
|
-
db()
|
|
31
|
+
transaction(db(), () => {
|
|
31
32
|
const binding = db().prepare(`SELECT source_fingerprint AS sourceFingerprint
|
|
32
33
|
FROM plugin_marketplace_bindings
|
|
33
34
|
WHERE marketplace_id = ?`).get(marketplaceId);
|
package/dist/plugin-store.js
CHANGED
|
@@ -57,11 +57,11 @@ export function upsertPlugin(rec) {
|
|
|
57
57
|
db().prepare(`INSERT INTO plugin_installations (
|
|
58
58
|
canonical_id, marketplace_id, plugin_id, source_type, requested_source,
|
|
59
59
|
resolved_revision, source_fingerprint, version, enabled, installed_at,
|
|
60
|
-
updated_at, install_path, manifest, integrity,
|
|
60
|
+
updated_at, install_path, manifest, integrity, runtime_state, runtime_error
|
|
61
61
|
) VALUES (
|
|
62
62
|
@canonicalId, @marketplaceId, @pluginId, @source, @requestedSource,
|
|
63
63
|
@resolvedRevision, @sourceFingerprint, @version, @enabled, @now,
|
|
64
|
-
@now, @installPath, @manifest, @integrity,
|
|
64
|
+
@now, @installPath, @manifest, @integrity, 'stopped', NULL
|
|
65
65
|
)
|
|
66
66
|
ON CONFLICT(canonical_id) DO UPDATE SET
|
|
67
67
|
source_type = excluded.source_type,
|
|
@@ -74,7 +74,6 @@ export function upsertPlugin(rec) {
|
|
|
74
74
|
install_path = excluded.install_path,
|
|
75
75
|
manifest = excluded.manifest,
|
|
76
76
|
integrity = excluded.integrity,
|
|
77
|
-
capabilities = excluded.capabilities,
|
|
78
77
|
runtime_state = 'stopped',
|
|
79
78
|
runtime_error = NULL`).run({
|
|
80
79
|
canonicalId: rec.canonicalId,
|
|
@@ -90,7 +89,6 @@ export function upsertPlugin(rec) {
|
|
|
90
89
|
installPath: rec.installPath,
|
|
91
90
|
manifest: JSON.stringify(rec.manifest),
|
|
92
91
|
integrity: rec.integrity,
|
|
93
|
-
capabilities: JSON.stringify(rec.manifest.capabilities),
|
|
94
92
|
});
|
|
95
93
|
}
|
|
96
94
|
export function setPluginEnabled(identity, enabled) {
|
|
@@ -20,6 +20,8 @@ export interface PluginSupervisorOptions {
|
|
|
20
20
|
restartMaxMs?: number;
|
|
21
21
|
healthPollMs?: number;
|
|
22
22
|
daemonInstanceId?: string;
|
|
23
|
+
productVersion?: string;
|
|
24
|
+
cliVersion?: string;
|
|
23
25
|
verifyPackage?: (record: RunnablePluginRecord) => Promise<void>;
|
|
24
26
|
}
|
|
25
27
|
export interface PluginRuntimeStatusSnapshot {
|
|
@@ -42,12 +44,15 @@ export declare class PluginSupervisor {
|
|
|
42
44
|
private readonly spawnRunner;
|
|
43
45
|
private readonly log;
|
|
44
46
|
private readonly daemonInstanceId;
|
|
47
|
+
private readonly productVersion;
|
|
48
|
+
private readonly cliVersion;
|
|
45
49
|
private readonly restartBaseMs;
|
|
46
50
|
private readonly restartMaxMs;
|
|
47
51
|
private readonly healthPollMs;
|
|
48
52
|
private readonly verifyPackage;
|
|
49
53
|
private services?;
|
|
50
54
|
private disposed;
|
|
55
|
+
private disposePromise?;
|
|
51
56
|
constructor(records: readonly RunnablePluginRecord[], options?: PluginSupervisorOptions);
|
|
52
57
|
bindHostServices(services: PluginHostServices): void;
|
|
53
58
|
startPluginRuntimes(): Promise<void>;
|
|
@@ -23,17 +23,22 @@ export class PluginSupervisor {
|
|
|
23
23
|
spawnRunner;
|
|
24
24
|
log;
|
|
25
25
|
daemonInstanceId;
|
|
26
|
+
productVersion;
|
|
27
|
+
cliVersion;
|
|
26
28
|
restartBaseMs;
|
|
27
29
|
restartMaxMs;
|
|
28
30
|
healthPollMs;
|
|
29
31
|
verifyPackage;
|
|
30
32
|
services;
|
|
31
33
|
disposed = false;
|
|
34
|
+
disposePromise;
|
|
32
35
|
constructor(records, options = {}) {
|
|
33
36
|
this.services = options.services;
|
|
34
37
|
this.spawnRunner = options.spawnRunner ?? spawnPluginRunner;
|
|
35
38
|
this.log = options.log ?? (() => { });
|
|
36
39
|
this.daemonInstanceId = options.daemonInstanceId ?? randomUUID();
|
|
40
|
+
this.productVersion = options.productVersion ?? "development";
|
|
41
|
+
this.cliVersion = options.cliVersion ?? "development";
|
|
37
42
|
this.restartBaseMs = options.restartBaseMs ?? DEFAULT_RESTART_BASE_MS;
|
|
38
43
|
this.restartMaxMs = Math.min(options.restartMaxMs ?? MAX_RESTART_DELAY_MS, MAX_RESTART_DELAY_MS);
|
|
39
44
|
this.healthPollMs = options.healthPollMs ?? DEFAULT_HEALTH_POLL_MS;
|
|
@@ -211,13 +216,27 @@ export class PluginSupervisor {
|
|
|
211
216
|
};
|
|
212
217
|
}
|
|
213
218
|
async dispose() {
|
|
214
|
-
if (this.
|
|
215
|
-
return;
|
|
219
|
+
if (this.disposePromise)
|
|
220
|
+
return this.disposePromise;
|
|
216
221
|
this.disposed = true;
|
|
217
|
-
|
|
218
|
-
await this.
|
|
219
|
-
|
|
220
|
-
|
|
222
|
+
const attempt = (async () => {
|
|
223
|
+
const stopped = await Promise.allSettled([...this.states.values()].map((state) => this.runSerialized(state.record.canonicalId, async () => {
|
|
224
|
+
await this.stopState(state);
|
|
225
|
+
await this.stopSettingsState(state);
|
|
226
|
+
})));
|
|
227
|
+
const failures = stopped
|
|
228
|
+
.filter((result) => result.status === "rejected")
|
|
229
|
+
.map((result) => result.reason);
|
|
230
|
+
if (failures.length > 0) {
|
|
231
|
+
throw new AggregateError(failures, "plugin supervisor could not stop every runtime");
|
|
232
|
+
}
|
|
233
|
+
})();
|
|
234
|
+
this.disposePromise = attempt;
|
|
235
|
+
void attempt.catch(() => {
|
|
236
|
+
if (this.disposePromise === attempt)
|
|
237
|
+
this.disposePromise = undefined;
|
|
238
|
+
});
|
|
239
|
+
return attempt;
|
|
221
240
|
}
|
|
222
241
|
upsertState(record) {
|
|
223
242
|
const current = this.states.get(record.canonicalId);
|
|
@@ -237,7 +256,10 @@ export class PluginSupervisor {
|
|
|
237
256
|
return state;
|
|
238
257
|
}
|
|
239
258
|
async startState(state) {
|
|
240
|
-
if (this.disposed
|
|
259
|
+
if (this.disposed
|
|
260
|
+
|| state.runner
|
|
261
|
+
|| blockingCleanupError(state)
|
|
262
|
+
|| !state.record.enabled)
|
|
241
263
|
return;
|
|
242
264
|
const runtime = state.record.manifest.runtime;
|
|
243
265
|
if (!runtime) {
|
|
@@ -270,7 +292,8 @@ export class PluginSupervisor {
|
|
|
270
292
|
pluginId: state.record.pluginId,
|
|
271
293
|
principalId: state.record.canonicalId,
|
|
272
294
|
daemonInstanceId: this.daemonInstanceId,
|
|
273
|
-
|
|
295
|
+
productVersion: this.productVersion,
|
|
296
|
+
cliVersion: this.cliVersion,
|
|
274
297
|
services: this.services,
|
|
275
298
|
...(state.record.pluginId === "workspace"
|
|
276
299
|
&& isBuiltInPluginMarketplace(state.record.marketplaceId)
|
|
@@ -300,7 +323,6 @@ export class PluginSupervisor {
|
|
|
300
323
|
role: "runtime",
|
|
301
324
|
dataDir,
|
|
302
325
|
cacheDir,
|
|
303
|
-
capabilities: state.record.manifest.capabilities,
|
|
304
326
|
handleHostCall: (method, params, signal) => hostRpc.call(method, params, signal),
|
|
305
327
|
onStderr: (line) => this.log({
|
|
306
328
|
level: "info",
|
|
@@ -333,8 +355,21 @@ export class PluginSupervisor {
|
|
|
333
355
|
});
|
|
334
356
|
}
|
|
335
357
|
catch (error) {
|
|
336
|
-
|
|
358
|
+
try {
|
|
359
|
+
await hostRpc?.dispose();
|
|
360
|
+
}
|
|
361
|
+
catch (cleanupError) {
|
|
362
|
+
const failure = new AggregateError([error, cleanupError], "plugin runtime startup failed and active-run cleanup was unproven");
|
|
363
|
+
state.runtimeCleanupError = failure;
|
|
364
|
+
this.failState(state, failure);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
337
367
|
state.hostRpc = undefined;
|
|
368
|
+
if (isUnprovenProcessCleanup(error)) {
|
|
369
|
+
state.runtimeCleanupError = error;
|
|
370
|
+
this.failState(state, error);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
338
373
|
removePluginRuntimeLease(dataDir, RUNTIME_LEASE_ID);
|
|
339
374
|
this.failState(state, error);
|
|
340
375
|
this.scheduleRestart(state);
|
|
@@ -343,6 +378,9 @@ export class PluginSupervisor {
|
|
|
343
378
|
async startSettingsState(state) {
|
|
344
379
|
if (this.disposed)
|
|
345
380
|
throw new Error("plugin supervisor is disposed");
|
|
381
|
+
const cleanupError = blockingCleanupError(state);
|
|
382
|
+
if (cleanupError)
|
|
383
|
+
throw cleanupError;
|
|
346
384
|
if (state.settingsRunner)
|
|
347
385
|
return state.settingsRunner;
|
|
348
386
|
const background = state.record.manifest.settings?.background;
|
|
@@ -369,7 +407,8 @@ export class PluginSupervisor {
|
|
|
369
407
|
pluginId: state.record.pluginId,
|
|
370
408
|
principalId: state.record.canonicalId,
|
|
371
409
|
daemonInstanceId: this.daemonInstanceId,
|
|
372
|
-
|
|
410
|
+
productVersion: this.productVersion,
|
|
411
|
+
cliVersion: this.cliVersion,
|
|
373
412
|
services: this.services,
|
|
374
413
|
...(state.record.pluginId === "workspace"
|
|
375
414
|
&& isBuiltInPluginMarketplace(state.record.marketplaceId)
|
|
@@ -398,7 +437,6 @@ export class PluginSupervisor {
|
|
|
398
437
|
role: "settings",
|
|
399
438
|
dataDir,
|
|
400
439
|
cacheDir,
|
|
401
|
-
capabilities: state.record.manifest.capabilities,
|
|
402
440
|
handleHostCall: (method, params, signal) => hostRpc.call(method, params, signal),
|
|
403
441
|
onStderr: (line) => this.log({
|
|
404
442
|
level: "info",
|
|
@@ -429,8 +467,32 @@ export class PluginSupervisor {
|
|
|
429
467
|
return runner;
|
|
430
468
|
}
|
|
431
469
|
catch (error) {
|
|
432
|
-
|
|
433
|
-
|
|
470
|
+
const cleanupErrors = [];
|
|
471
|
+
if (runner) {
|
|
472
|
+
try {
|
|
473
|
+
await runner.close();
|
|
474
|
+
}
|
|
475
|
+
catch (cleanupError) {
|
|
476
|
+
state.settingsRunner = runner;
|
|
477
|
+
cleanupErrors.push(cleanupError);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
try {
|
|
481
|
+
await hostRpc?.dispose();
|
|
482
|
+
}
|
|
483
|
+
catch (cleanupError) {
|
|
484
|
+
state.settingsHostRpc = hostRpc;
|
|
485
|
+
cleanupErrors.push(cleanupError);
|
|
486
|
+
}
|
|
487
|
+
if (cleanupErrors.length > 0) {
|
|
488
|
+
const failure = new AggregateError([error, ...cleanupErrors], "plugin settings startup failed and cleanup was unproven");
|
|
489
|
+
state.settingsCleanupError = failure;
|
|
490
|
+
throw failure;
|
|
491
|
+
}
|
|
492
|
+
if (isUnprovenProcessCleanup(error)) {
|
|
493
|
+
state.settingsCleanupError = error;
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
434
496
|
removePluginRuntimeLease(dataDir, SETTINGS_LEASE_ID);
|
|
435
497
|
throw error;
|
|
436
498
|
}
|
|
@@ -438,20 +500,71 @@ export class PluginSupervisor {
|
|
|
438
500
|
async stopSettingsState(state) {
|
|
439
501
|
const runner = state.settingsRunner;
|
|
440
502
|
const hostRpc = state.settingsHostRpc;
|
|
441
|
-
state.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
503
|
+
if (state.settingsCleanupError && !runner && !hostRpc) {
|
|
504
|
+
throw state.settingsCleanupError;
|
|
505
|
+
}
|
|
506
|
+
const errors = [];
|
|
507
|
+
if (runner) {
|
|
508
|
+
try {
|
|
509
|
+
await runner.close();
|
|
510
|
+
if (state.settingsRunner === runner)
|
|
511
|
+
state.settingsRunner = undefined;
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
errors.push(error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (hostRpc) {
|
|
518
|
+
try {
|
|
519
|
+
await hostRpc.dispose();
|
|
520
|
+
if (state.settingsHostRpc === hostRpc)
|
|
521
|
+
state.settingsHostRpc = undefined;
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
errors.push(error);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (errors.length > 0) {
|
|
528
|
+
const failure = cleanupFailure(errors, "plugin settings cleanup was unproven");
|
|
529
|
+
state.settingsCleanupError = failure;
|
|
530
|
+
throw failure;
|
|
531
|
+
}
|
|
532
|
+
state.settingsCleanupError = undefined;
|
|
445
533
|
removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
|
|
446
534
|
}
|
|
447
535
|
async handleSettingsExit(state, runner, hostRpc, exit) {
|
|
448
536
|
if (state.settingsRunner !== runner)
|
|
449
537
|
return;
|
|
538
|
+
try {
|
|
539
|
+
await runner.terminateTree();
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
state.settingsCleanupError = error;
|
|
543
|
+
this.log({
|
|
544
|
+
level: "error",
|
|
545
|
+
event: "plugin-settings-exited",
|
|
546
|
+
pluginId: state.record.canonicalId,
|
|
547
|
+
message: error instanceof Error ? error.message : String(error),
|
|
548
|
+
});
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
450
551
|
state.settingsRunner = undefined;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
552
|
+
try {
|
|
553
|
+
await hostRpc.dispose();
|
|
554
|
+
if (state.settingsHostRpc === hostRpc)
|
|
555
|
+
state.settingsHostRpc = undefined;
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
state.settingsCleanupError = error;
|
|
559
|
+
this.log({
|
|
560
|
+
level: "error",
|
|
561
|
+
event: "plugin-settings-exited",
|
|
562
|
+
pluginId: state.record.canonicalId,
|
|
563
|
+
message: error instanceof Error ? error.message : String(error),
|
|
564
|
+
});
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
state.settingsCleanupError = undefined;
|
|
455
568
|
removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
|
|
456
569
|
if (!this.disposed) {
|
|
457
570
|
this.log({
|
|
@@ -473,10 +586,38 @@ export class PluginSupervisor {
|
|
|
473
586
|
clearTimeout(state.healthTimer);
|
|
474
587
|
state.healthTimer = undefined;
|
|
475
588
|
const runner = state.runner;
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
589
|
+
const hostRpc = state.hostRpc;
|
|
590
|
+
if (state.runtimeCleanupError && !runner && !hostRpc) {
|
|
591
|
+
throw state.runtimeCleanupError;
|
|
592
|
+
}
|
|
593
|
+
const errors = [];
|
|
594
|
+
if (runner) {
|
|
595
|
+
try {
|
|
596
|
+
await runner.close();
|
|
597
|
+
if (state.runner === runner)
|
|
598
|
+
state.runner = undefined;
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
errors.push(error);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (hostRpc) {
|
|
605
|
+
try {
|
|
606
|
+
await hostRpc.dispose();
|
|
607
|
+
if (state.hostRpc === hostRpc)
|
|
608
|
+
state.hostRpc = undefined;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
errors.push(error);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (errors.length > 0) {
|
|
615
|
+
const failure = cleanupFailure(errors, "plugin runtime cleanup was unproven");
|
|
616
|
+
state.runtimeCleanupError = failure;
|
|
617
|
+
this.failState(state, failure);
|
|
618
|
+
throw failure;
|
|
619
|
+
}
|
|
620
|
+
state.runtimeCleanupError = undefined;
|
|
480
621
|
removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
481
622
|
state.health = undefined;
|
|
482
623
|
state.error = undefined;
|
|
@@ -489,10 +630,26 @@ export class PluginSupervisor {
|
|
|
489
630
|
if (state.healthTimer)
|
|
490
631
|
clearTimeout(state.healthTimer);
|
|
491
632
|
state.healthTimer = undefined;
|
|
633
|
+
try {
|
|
634
|
+
await runner.terminateTree();
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
state.runtimeCleanupError = error;
|
|
638
|
+
this.failState(state, error);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
492
641
|
state.runner = undefined;
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
642
|
+
try {
|
|
643
|
+
await state.hostRpc?.dispose();
|
|
644
|
+
state.hostRpc = undefined;
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
state.runtimeCleanupError = error;
|
|
648
|
+
if (!state.stopping && !this.disposed)
|
|
649
|
+
this.failState(state, error);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
state.runtimeCleanupError = undefined;
|
|
496
653
|
removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
497
654
|
if (state.stopping || this.disposed)
|
|
498
655
|
return;
|
|
@@ -571,13 +728,29 @@ export class PluginSupervisor {
|
|
|
571
728
|
async failRunningState(state, runner, error) {
|
|
572
729
|
if (state.runner !== runner)
|
|
573
730
|
return;
|
|
574
|
-
state.runner = undefined;
|
|
575
731
|
if (state.healthTimer)
|
|
576
732
|
clearTimeout(state.healthTimer);
|
|
577
733
|
state.healthTimer = undefined;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
734
|
+
try {
|
|
735
|
+
await runner.close();
|
|
736
|
+
}
|
|
737
|
+
catch (cleanupError) {
|
|
738
|
+
state.runtimeCleanupError = cleanupError;
|
|
739
|
+
this.failState(state, cleanupError);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
state.runner = undefined;
|
|
743
|
+
try {
|
|
744
|
+
await state.hostRpc?.dispose();
|
|
745
|
+
state.hostRpc = undefined;
|
|
746
|
+
}
|
|
747
|
+
catch (cleanupError) {
|
|
748
|
+
state.runtimeCleanupError = cleanupError;
|
|
749
|
+
if (!state.stopping && !this.disposed)
|
|
750
|
+
this.failState(state, cleanupError);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
state.runtimeCleanupError = undefined;
|
|
581
754
|
removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
582
755
|
if (state.stopping || this.disposed)
|
|
583
756
|
return;
|
|
@@ -595,6 +768,18 @@ export class PluginSupervisor {
|
|
|
595
768
|
return current;
|
|
596
769
|
}
|
|
597
770
|
}
|
|
771
|
+
function cleanupFailure(errors, message) {
|
|
772
|
+
return errors.length === 1 ? errors[0] : new AggregateError(errors, message);
|
|
773
|
+
}
|
|
774
|
+
function blockingCleanupError(state) {
|
|
775
|
+
return state.runtimeCleanupError ?? state.settingsCleanupError;
|
|
776
|
+
}
|
|
777
|
+
function isUnprovenProcessCleanup(error) {
|
|
778
|
+
return Boolean(error
|
|
779
|
+
&& typeof error === "object"
|
|
780
|
+
&& "code" in error
|
|
781
|
+
&& error.code === "PROCESS_TREE_STUCK");
|
|
782
|
+
}
|
|
598
783
|
function healthState(health) {
|
|
599
784
|
return health.state;
|
|
600
785
|
}
|
package/dist/pm2.d.ts
CHANGED
|
@@ -8,12 +8,21 @@ export declare function formatControlUrl(host: string, port: number): string;
|
|
|
8
8
|
* entry from scratch (delete + start) so config flags are always fresh; the
|
|
9
9
|
* `force === false` path short-circuits when it's already online.
|
|
10
10
|
*/
|
|
11
|
-
export declare function startDaemon({ force, quiet }: {
|
|
11
|
+
export declare function startDaemon({ force, quiet, launchEnvironment, }: {
|
|
12
12
|
force: boolean;
|
|
13
13
|
quiet?: boolean;
|
|
14
|
+
launchEnvironment?: NodeJS.ProcessEnv;
|
|
14
15
|
}): number;
|
|
15
16
|
export declare function isPm2DaemonOnline(): boolean;
|
|
16
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Stop the daemon and terminate its dedicated pm2 supervisor.
|
|
19
|
+
*
|
|
20
|
+
* PM2_HOME lives below RYNX_HOME, so this supervisor cannot own unrelated
|
|
21
|
+
* applications. Leaving it alive across an npm replacement is unsafe: the
|
|
22
|
+
* daemon process container path is resolved inside the old pm2 package. Once
|
|
23
|
+
* npm swaps @rynx-ai/daemon, that cached path may no longer exist and every
|
|
24
|
+
* subsequent `rynx start` enters a restart loop before our code can run.
|
|
25
|
+
*/
|
|
17
26
|
export declare function stopDaemon(): number;
|
|
18
27
|
/** Print a one-line status; exit 0 only when online. */
|
|
19
28
|
export declare function statusDaemon(): number;
|