@rynx-ai/daemon 0.1.11-beta.52 → 0.1.11-beta.53
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/package.json +1 -1
- package/dist/bundled-plugins.js +2 -5
- package/dist/daemon-server.js +1 -10
- package/dist/plugin-host-leases.d.ts +10 -3
- package/dist/plugin-host-leases.js +21 -7
- package/dist/plugin-installer.d.ts +1 -0
- package/dist/plugin-installer.js +1 -1
- package/dist/plugin-maintenance-entry.d.ts +1 -0
- package/dist/plugin-maintenance-entry.js +19 -0
- package/dist/plugin-maintenance.d.ts +21 -0
- package/dist/plugin-maintenance.js +189 -0
- package/dist/plugin-maintenance.mjs +15495 -0
- package/dist/plugin-management-service.js +1 -0
- package/dist/plugin-management.d.ts +41 -0
- package/dist/plugin-management.js +84 -0
- package/dist/plugin-supervisor.d.ts +3 -1
- package/dist/plugin-supervisor.js +115 -31
- package/package.json +19 -11
|
@@ -286,6 +286,7 @@ export class PluginManagementService {
|
|
|
286
286
|
function toManagementItem(record, settingsAvailable = false, updateState = "unknown") {
|
|
287
287
|
return {
|
|
288
288
|
id: record.canonicalId,
|
|
289
|
+
digest: record.integrity,
|
|
289
290
|
pluginId: record.pluginId,
|
|
290
291
|
marketplaceId: record.marketplaceId,
|
|
291
292
|
version: record.version,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PluginMarketplaceItem } from "@rynx-ai/protocol/plugin-management";
|
|
2
|
+
import { type PluginMarketplaceRecord } from "./plugin-marketplace.js";
|
|
3
|
+
export declare function toMarketplaceItem(record: PluginMarketplaceRecord): PluginMarketplaceItem;
|
|
4
|
+
/** Hold the host's OS lock across prepare, confirmation and commit. No plugin code runs. */
|
|
5
|
+
export declare function openOfflinePluginManagement(): Promise<{
|
|
6
|
+
listResidentPlugins(): Promise<{
|
|
7
|
+
runtimeState: "absent" | "stopped";
|
|
8
|
+
runtimeError: undefined;
|
|
9
|
+
digest?: string;
|
|
10
|
+
id: string;
|
|
11
|
+
pluginId: string;
|
|
12
|
+
marketplaceId: string;
|
|
13
|
+
version: string;
|
|
14
|
+
source: import("@rynx-ai/protocol/plugin-management").PluginManagementSource;
|
|
15
|
+
state: import("@rynx-ai/protocol/plugin-management").PluginManagementState;
|
|
16
|
+
updateState: import("@rynx-ai/protocol/plugin-management").PluginManagementUpdateState;
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
hasSettingsUi: boolean;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}[]>;
|
|
21
|
+
listResidentPluginMarketplaces(): Promise<PluginMarketplaceItem[]>;
|
|
22
|
+
addResidentPluginMarketplace(input: {
|
|
23
|
+
source: string;
|
|
24
|
+
alias?: string;
|
|
25
|
+
}): Promise<PluginMarketplaceItem>;
|
|
26
|
+
refreshResidentPluginMarketplace(id: string): Promise<PluginMarketplaceItem>;
|
|
27
|
+
removeResidentPluginMarketplace(id: string): Promise<boolean>;
|
|
28
|
+
setResidentPluginEnabled(id: string, enabled: boolean): Promise<import("@rynx-ai/protocol/plugin-management").PluginManagementItem>;
|
|
29
|
+
uninstallResidentPlugin(id: string): Promise<{
|
|
30
|
+
uninstalled: true;
|
|
31
|
+
pluginId: string;
|
|
32
|
+
}>;
|
|
33
|
+
prepareResidentPluginInstallation(input: import("@rynx-ai/protocol/plugin-management").PluginInstallPrepareInput, options?: {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
} | undefined): Promise<import("@rynx-ai/protocol/plugin-management").PluginInstallPreparation>;
|
|
36
|
+
commitResidentPluginInstallation(token: string, input: import("@rynx-ai/protocol/plugin-management").PluginInstallCommitInput, options?: {
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
} | undefined): Promise<import("@rynx-ai/protocol/plugin-management").PluginInstallCommitResult>;
|
|
39
|
+
cancelResidentPluginInstallation(token: string): Promise<boolean>;
|
|
40
|
+
close(): Promise<void>;
|
|
41
|
+
}>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { acquireDaemonOwner } from "./daemon-owner.js";
|
|
2
|
+
import { pluginDataDir } from "./plugin-installer.js";
|
|
3
|
+
import { reclaimPluginHostLeases } from "./plugin-host-leases.js";
|
|
4
|
+
import { getPlugin } from "./plugin-store.js";
|
|
5
|
+
import { PluginManagementService } from "./plugin-management-service.js";
|
|
6
|
+
import { addPluginMarketplace, listPluginMarketplaces, refreshPluginMarketplace, removePluginMarketplace, } from "./plugin-marketplace.js";
|
|
7
|
+
export function toMarketplaceItem(record) {
|
|
8
|
+
return {
|
|
9
|
+
id: record.marketplaceId,
|
|
10
|
+
source: record.sourceType,
|
|
11
|
+
builtIn: record.builtIn,
|
|
12
|
+
plugins: record.catalog.plugins.map((plugin) => ({ id: plugin.id })),
|
|
13
|
+
resolvedRevision: record.resolvedRevision,
|
|
14
|
+
refreshedAt: record.refreshedAt,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Hold the host's OS lock across prepare, confirmation and commit. No plugin code runs. */
|
|
18
|
+
export async function openOfflinePluginManagement() {
|
|
19
|
+
// Importing stores does not open the DB. Ownership must precede its first access.
|
|
20
|
+
const owner = await acquireDaemonOwner();
|
|
21
|
+
const service = new PluginManagementService();
|
|
22
|
+
const reclaim = (id) => {
|
|
23
|
+
const record = getPlugin(id);
|
|
24
|
+
if (record)
|
|
25
|
+
reclaimPluginHostLeases(pluginDataDir(record.marketplaceId, record.pluginId));
|
|
26
|
+
};
|
|
27
|
+
const preparedIdentities = new Map();
|
|
28
|
+
return {
|
|
29
|
+
async listResidentPlugins() {
|
|
30
|
+
return service.list().plugins.map((plugin) => ({
|
|
31
|
+
...plugin,
|
|
32
|
+
runtimeState: plugin.runtimeState === "absent" ? "absent" : "stopped",
|
|
33
|
+
runtimeError: undefined,
|
|
34
|
+
}));
|
|
35
|
+
},
|
|
36
|
+
async listResidentPluginMarketplaces() {
|
|
37
|
+
return listPluginMarketplaces().map(toMarketplaceItem);
|
|
38
|
+
},
|
|
39
|
+
async addResidentPluginMarketplace(input) {
|
|
40
|
+
return toMarketplaceItem(await addPluginMarketplace(input.source, { alias: input.alias }));
|
|
41
|
+
},
|
|
42
|
+
async refreshResidentPluginMarketplace(id) {
|
|
43
|
+
return toMarketplaceItem(await refreshPluginMarketplace(id));
|
|
44
|
+
},
|
|
45
|
+
async removeResidentPluginMarketplace(id) {
|
|
46
|
+
return removePluginMarketplace(id);
|
|
47
|
+
},
|
|
48
|
+
async setResidentPluginEnabled(id, enabled) {
|
|
49
|
+
const plugin = await service.setEnabled(id, enabled);
|
|
50
|
+
if (!plugin)
|
|
51
|
+
throw new Error(`plugin "${id}" not found`);
|
|
52
|
+
return plugin;
|
|
53
|
+
},
|
|
54
|
+
async uninstallResidentPlugin(id) {
|
|
55
|
+
reclaim(id);
|
|
56
|
+
if (!await service.uninstall(id))
|
|
57
|
+
throw new Error(`plugin "${id}" not found`);
|
|
58
|
+
return { uninstalled: true, pluginId: id };
|
|
59
|
+
},
|
|
60
|
+
async prepareResidentPluginInstallation(...args) {
|
|
61
|
+
const prepared = await service.prepareInstallation(...args);
|
|
62
|
+
preparedIdentities.set(prepared.token, prepared.artifact.id);
|
|
63
|
+
return prepared;
|
|
64
|
+
},
|
|
65
|
+
async commitResidentPluginInstallation(...args) {
|
|
66
|
+
// Preparations expose the canonical id without running the plugin.
|
|
67
|
+
const identity = preparedIdentities.get(args[0]);
|
|
68
|
+
if (identity)
|
|
69
|
+
reclaim(identity);
|
|
70
|
+
return service.commitInstallation(...args);
|
|
71
|
+
},
|
|
72
|
+
async cancelResidentPluginInstallation(token) {
|
|
73
|
+
return service.cancelPreparation(token);
|
|
74
|
+
},
|
|
75
|
+
async close() {
|
|
76
|
+
try {
|
|
77
|
+
service.dispose();
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await owner.release();
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -22,7 +22,7 @@ export interface PluginSupervisorOptions {
|
|
|
22
22
|
daemonInstanceId?: string;
|
|
23
23
|
productVersion?: string;
|
|
24
24
|
cliVersion?: string;
|
|
25
|
-
verifyPackage?: (record: RunnablePluginRecord) => Promise<void>;
|
|
25
|
+
verifyPackage?: (record: RunnablePluginRecord, signal?: AbortSignal) => Promise<void>;
|
|
26
26
|
}
|
|
27
27
|
export interface PluginRuntimeStatusSnapshot {
|
|
28
28
|
plugins: Array<{
|
|
@@ -70,6 +70,8 @@ export declare class PluginSupervisor {
|
|
|
70
70
|
statusSnapshot(): PluginRuntimeStatusSnapshot;
|
|
71
71
|
dispose(): Promise<void>;
|
|
72
72
|
private upsertState;
|
|
73
|
+
private cancelStartup;
|
|
74
|
+
private scheduleStart;
|
|
73
75
|
private startState;
|
|
74
76
|
private startSettingsState;
|
|
75
77
|
private stopSettingsState;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { writePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
4
4
|
import { spawnPluginRunner, } from "@rynx-ai/plugin-runner";
|
|
5
5
|
import { MAX_PLUGIN_HOST_EVENT_BYTES, PluginHostRpc, } from "./plugin-host-rpc.js";
|
|
6
6
|
import { pluginDataDir, pluginRuntimeCacheDir, } from "./plugin-installer.js";
|
|
7
|
-
import { PLUGIN_HOST_RUNTIME_LEASE_IDS } from "./plugin-host-leases.js";
|
|
7
|
+
import { PLUGIN_HOST_RUNTIME_LEASE_IDS, reclaimPluginHostLeases, removePluginHostLease } from "./plugin-host-leases.js";
|
|
8
8
|
import { isBuiltInPluginMarketplace } from "./plugin-marketplace.js";
|
|
9
9
|
import { validatePluginPackageSnapshot } from "./plugin-package.js";
|
|
10
10
|
import { setPluginRuntimeState, } from "./plugin-store.js";
|
|
@@ -59,8 +59,11 @@ export class PluginSupervisor {
|
|
|
59
59
|
await Promise.allSettled(starts);
|
|
60
60
|
}
|
|
61
61
|
async ensurePluginRuntime(record) {
|
|
62
|
+
const epoch = this.states.get(record.canonicalId)?.managementEpoch ?? 0;
|
|
62
63
|
return await this.runSerialized(record.canonicalId, async () => {
|
|
63
64
|
const state = this.upsertState(record);
|
|
65
|
+
if (state.managementEpoch !== epoch)
|
|
66
|
+
return state.state;
|
|
64
67
|
if (!record.enabled) {
|
|
65
68
|
await this.stopState(state);
|
|
66
69
|
return "stopped";
|
|
@@ -70,6 +73,7 @@ export class PluginSupervisor {
|
|
|
70
73
|
setPluginRuntimeState(record.canonicalId, "absent");
|
|
71
74
|
return "absent";
|
|
72
75
|
}
|
|
76
|
+
state.stopping = false;
|
|
73
77
|
if (!state.runner)
|
|
74
78
|
await this.startState(state);
|
|
75
79
|
return state.state;
|
|
@@ -77,6 +81,7 @@ export class PluginSupervisor {
|
|
|
77
81
|
}
|
|
78
82
|
/** Strict cutover: stop current generation, then start the supplied generation. */
|
|
79
83
|
async reloadPluginRuntime(record) {
|
|
84
|
+
this.cancelStartup(record.canonicalId);
|
|
80
85
|
return await this.runSerialized(record.canonicalId, async () => {
|
|
81
86
|
const state = this.upsertState(record);
|
|
82
87
|
await this.stopState(state);
|
|
@@ -93,6 +98,7 @@ export class PluginSupervisor {
|
|
|
93
98
|
});
|
|
94
99
|
}
|
|
95
100
|
async stopPluginRuntime(identity) {
|
|
101
|
+
this.cancelStartup(identity);
|
|
96
102
|
await this.runSerialized(identity, async () => {
|
|
97
103
|
const state = this.states.get(identity);
|
|
98
104
|
if (state)
|
|
@@ -100,10 +106,13 @@ export class PluginSupervisor {
|
|
|
100
106
|
});
|
|
101
107
|
}
|
|
102
108
|
async changeEnabled(record, enabled, mutateRegistry) {
|
|
109
|
+
if (!enabled)
|
|
110
|
+
this.cancelStartup(record.canonicalId);
|
|
103
111
|
return await this.runSerialized(record.canonicalId, async () => {
|
|
104
112
|
const state = this.upsertState(record);
|
|
105
113
|
if (!enabled) {
|
|
106
114
|
await this.stopState(state);
|
|
115
|
+
await this.stopSettingsState(state);
|
|
107
116
|
let updated;
|
|
108
117
|
try {
|
|
109
118
|
updated = mutateRegistry();
|
|
@@ -111,7 +120,7 @@ export class PluginSupervisor {
|
|
|
111
120
|
catch (error) {
|
|
112
121
|
state.stopping = false;
|
|
113
122
|
if (state.record.enabled)
|
|
114
|
-
|
|
123
|
+
this.scheduleStart(state);
|
|
115
124
|
throw error;
|
|
116
125
|
}
|
|
117
126
|
state.record = updated;
|
|
@@ -120,11 +129,12 @@ export class PluginSupervisor {
|
|
|
120
129
|
const updated = mutateRegistry();
|
|
121
130
|
state.record = updated;
|
|
122
131
|
state.stopping = false;
|
|
123
|
-
|
|
132
|
+
this.scheduleStart(state);
|
|
124
133
|
return state.state;
|
|
125
134
|
});
|
|
126
135
|
}
|
|
127
136
|
async cutoverInstallation(identity, commitRegistry) {
|
|
137
|
+
this.cancelStartup(identity);
|
|
128
138
|
return await this.runSerialized(identity, async () => {
|
|
129
139
|
const current = this.states.get(identity);
|
|
130
140
|
if (current) {
|
|
@@ -139,7 +149,7 @@ export class PluginSupervisor {
|
|
|
139
149
|
if (current) {
|
|
140
150
|
current.stopping = false;
|
|
141
151
|
if (current.record.enabled)
|
|
142
|
-
|
|
152
|
+
this.scheduleStart(current);
|
|
143
153
|
}
|
|
144
154
|
throw error;
|
|
145
155
|
}
|
|
@@ -147,11 +157,12 @@ export class PluginSupervisor {
|
|
|
147
157
|
state.record = installed;
|
|
148
158
|
state.stopping = false;
|
|
149
159
|
if (installed.enabled)
|
|
150
|
-
|
|
160
|
+
this.scheduleStart(state);
|
|
151
161
|
return state.state;
|
|
152
162
|
});
|
|
153
163
|
}
|
|
154
164
|
async uninstallInstallation(identity, removeRegistry) {
|
|
165
|
+
this.cancelStartup(identity);
|
|
155
166
|
return await this.runSerialized(identity, async () => {
|
|
156
167
|
const state = this.states.get(identity);
|
|
157
168
|
if (state) {
|
|
@@ -166,14 +177,14 @@ export class PluginSupervisor {
|
|
|
166
177
|
if (state) {
|
|
167
178
|
state.stopping = false;
|
|
168
179
|
if (state.record.enabled)
|
|
169
|
-
|
|
180
|
+
this.scheduleStart(state);
|
|
170
181
|
}
|
|
171
182
|
throw error;
|
|
172
183
|
}
|
|
173
184
|
if (!removed && state) {
|
|
174
185
|
state.stopping = false;
|
|
175
186
|
if (state.record.enabled)
|
|
176
|
-
|
|
187
|
+
this.scheduleStart(state);
|
|
177
188
|
}
|
|
178
189
|
if (removed)
|
|
179
190
|
this.states.delete(identity);
|
|
@@ -181,23 +192,36 @@ export class PluginSupervisor {
|
|
|
181
192
|
});
|
|
182
193
|
}
|
|
183
194
|
async settingsCall(identity, expectedGeneration, method, params, options = {}) {
|
|
195
|
+
const epoch = this.states.get(identity)?.managementEpoch;
|
|
184
196
|
return await this.runSerialized(identity, async () => {
|
|
185
197
|
const state = this.states.get(identity);
|
|
186
198
|
if (!state)
|
|
187
199
|
throw new Error(`plugin "${identity}" is not installed`);
|
|
200
|
+
if (state.managementEpoch !== epoch)
|
|
201
|
+
throw new Error("plugin settings call cancelled by management operation");
|
|
188
202
|
if (state.record.integrity !== expectedGeneration) {
|
|
189
203
|
throw new Error("plugin settings UI generation is stale");
|
|
190
204
|
}
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
state.settingsCallAbort = controller;
|
|
207
|
+
try {
|
|
208
|
+
const runner = await this.startSettingsState(state);
|
|
209
|
+
if (!runner.handshake.features.settings) {
|
|
210
|
+
throw new Error(`plugin "${identity}" does not advertise a settings background`);
|
|
211
|
+
}
|
|
212
|
+
const result = await runner.client.settingsCall(method, params, {
|
|
213
|
+
signal: options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal,
|
|
214
|
+
});
|
|
215
|
+
await state.runner?.client.emitHostEvent("plugin.settings.changed", {
|
|
216
|
+
method,
|
|
217
|
+
...(params === undefined ? {} : { params }),
|
|
218
|
+
}).catch(() => undefined);
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
if (state.settingsCallAbort === controller)
|
|
223
|
+
state.settingsCallAbort = undefined;
|
|
224
|
+
}
|
|
201
225
|
});
|
|
202
226
|
}
|
|
203
227
|
supportsSettings(identity) {
|
|
@@ -219,6 +243,8 @@ export class PluginSupervisor {
|
|
|
219
243
|
if (this.disposePromise)
|
|
220
244
|
return this.disposePromise;
|
|
221
245
|
this.disposed = true;
|
|
246
|
+
for (const identity of this.states.keys())
|
|
247
|
+
this.cancelStartup(identity);
|
|
222
248
|
const attempt = (async () => {
|
|
223
249
|
const stopped = await Promise.allSettled([...this.states.values()].map((state) => this.runSerialized(state.record.canonicalId, async () => {
|
|
224
250
|
await this.stopState(state);
|
|
@@ -251,12 +277,31 @@ export class PluginSupervisor {
|
|
|
251
277
|
generation: 0,
|
|
252
278
|
restartFailures: 0,
|
|
253
279
|
stopping: false,
|
|
280
|
+
managementEpoch: 0,
|
|
254
281
|
};
|
|
255
282
|
this.states.set(record.canonicalId, state);
|
|
256
283
|
return state;
|
|
257
284
|
}
|
|
285
|
+
cancelStartup(identity) {
|
|
286
|
+
const state = this.states.get(identity);
|
|
287
|
+
if (!state)
|
|
288
|
+
return;
|
|
289
|
+
state.stopping = true;
|
|
290
|
+
state.managementEpoch += 1;
|
|
291
|
+
state.settingsCallAbort?.abort(new Error("plugin settings call cancelled by management operation"));
|
|
292
|
+
if (state.restartTimer)
|
|
293
|
+
clearTimeout(state.restartTimer);
|
|
294
|
+
state.restartTimer = undefined;
|
|
295
|
+
state.startup?.abort(new Error("plugin startup cancelled by management operation"));
|
|
296
|
+
}
|
|
297
|
+
scheduleStart(state) {
|
|
298
|
+
void this.runSerialized(state.record.canonicalId, () => this.startState(state)).catch((error) => {
|
|
299
|
+
this.failState(state, error);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
258
302
|
async startState(state) {
|
|
259
303
|
if (this.disposed
|
|
304
|
+
|| state.stopping
|
|
260
305
|
|| state.runner
|
|
261
306
|
|| blockingCleanupError(state)
|
|
262
307
|
|| !state.record.enabled)
|
|
@@ -278,10 +323,18 @@ export class PluginSupervisor {
|
|
|
278
323
|
const cacheDir = pluginRuntimeCacheDir(state.record.marketplaceId, state.record.pluginId, state.record.integrity);
|
|
279
324
|
let runner;
|
|
280
325
|
let hostRpc;
|
|
326
|
+
const startup = new AbortController();
|
|
327
|
+
state.startup = startup;
|
|
328
|
+
let ownsLease = false;
|
|
329
|
+
const startedAt = Date.now();
|
|
330
|
+
this.log({ level: "info", event: "plugin-startup-begin", pluginId: state.record.canonicalId });
|
|
281
331
|
try {
|
|
282
|
-
await this.verifyPackage(state.record);
|
|
332
|
+
await this.verifyPackage(state.record, startup.signal);
|
|
333
|
+
startup.signal.throwIfAborted();
|
|
283
334
|
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
284
335
|
mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
|
|
336
|
+
reclaimPluginHostLeases(dataDir, RUNTIME_LEASE_ID);
|
|
337
|
+
ownsLease = true;
|
|
285
338
|
writePluginRuntimeLease({
|
|
286
339
|
dataDir,
|
|
287
340
|
pluginId: state.record.pluginId,
|
|
@@ -308,6 +361,7 @@ export class PluginSupervisor {
|
|
|
308
361
|
});
|
|
309
362
|
state.hostRpc = hostRpc;
|
|
310
363
|
runner = await this.spawnRunner({
|
|
364
|
+
signal: startup.signal,
|
|
311
365
|
pluginId: state.record.pluginId,
|
|
312
366
|
pluginVersion: state.record.version,
|
|
313
367
|
installationId: state.record.canonicalId,
|
|
@@ -370,9 +424,23 @@ export class PluginSupervisor {
|
|
|
370
424
|
this.failState(state, error);
|
|
371
425
|
return;
|
|
372
426
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
427
|
+
if (ownsLease)
|
|
428
|
+
removePluginHostLease(dataDir, RUNTIME_LEASE_ID);
|
|
429
|
+
if (startup.signal.aborted) {
|
|
430
|
+
state.state = "stopped";
|
|
431
|
+
state.error = undefined;
|
|
432
|
+
setPluginRuntimeState(state.record.canonicalId, "stopped");
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
this.failState(state, error);
|
|
436
|
+
this.scheduleRestart(state);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
if (state.startup === startup)
|
|
441
|
+
state.startup = undefined;
|
|
442
|
+
this.log({ level: "info", event: "plugin-startup-end", pluginId: state.record.canonicalId,
|
|
443
|
+
message: `${state.state}; elapsedMs=${Date.now() - startedAt}` });
|
|
376
444
|
}
|
|
377
445
|
}
|
|
378
446
|
async startSettingsState(state) {
|
|
@@ -393,10 +461,18 @@ export class PluginSupervisor {
|
|
|
393
461
|
const cacheDir = pluginRuntimeCacheDir(state.record.marketplaceId, state.record.pluginId, state.record.integrity);
|
|
394
462
|
let runner;
|
|
395
463
|
let hostRpc;
|
|
464
|
+
const startup = new AbortController();
|
|
465
|
+
state.startup = startup;
|
|
466
|
+
let ownsLease = false;
|
|
467
|
+
const startedAt = Date.now();
|
|
468
|
+
this.log({ level: "info", event: "plugin-startup-begin", pluginId: state.record.canonicalId });
|
|
396
469
|
try {
|
|
397
|
-
await this.verifyPackage(state.record);
|
|
470
|
+
await this.verifyPackage(state.record, startup.signal);
|
|
471
|
+
startup.signal.throwIfAborted();
|
|
398
472
|
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
399
473
|
mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
|
|
474
|
+
reclaimPluginHostLeases(dataDir, SETTINGS_LEASE_ID);
|
|
475
|
+
ownsLease = true;
|
|
400
476
|
writePluginRuntimeLease({
|
|
401
477
|
dataDir,
|
|
402
478
|
pluginId: state.record.pluginId,
|
|
@@ -422,6 +498,7 @@ export class PluginSupervisor {
|
|
|
422
498
|
},
|
|
423
499
|
});
|
|
424
500
|
runner = await this.spawnRunner({
|
|
501
|
+
signal: startup.signal,
|
|
425
502
|
pluginId: state.record.pluginId,
|
|
426
503
|
pluginVersion: state.record.version,
|
|
427
504
|
installationId: state.record.canonicalId,
|
|
@@ -493,9 +570,16 @@ export class PluginSupervisor {
|
|
|
493
570
|
state.settingsCleanupError = error;
|
|
494
571
|
throw error;
|
|
495
572
|
}
|
|
496
|
-
|
|
573
|
+
if (ownsLease)
|
|
574
|
+
removePluginHostLease(dataDir, SETTINGS_LEASE_ID);
|
|
497
575
|
throw error;
|
|
498
576
|
}
|
|
577
|
+
finally {
|
|
578
|
+
if (state.startup === startup)
|
|
579
|
+
state.startup = undefined;
|
|
580
|
+
this.log({ level: "info", event: "plugin-settings-startup-end", pluginId: state.record.canonicalId,
|
|
581
|
+
message: `elapsedMs=${Date.now() - startedAt}` });
|
|
582
|
+
}
|
|
499
583
|
}
|
|
500
584
|
async stopSettingsState(state) {
|
|
501
585
|
const runner = state.settingsRunner;
|
|
@@ -530,7 +614,7 @@ export class PluginSupervisor {
|
|
|
530
614
|
throw failure;
|
|
531
615
|
}
|
|
532
616
|
state.settingsCleanupError = undefined;
|
|
533
|
-
|
|
617
|
+
removePluginHostLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
|
|
534
618
|
}
|
|
535
619
|
async handleSettingsExit(state, runner, hostRpc, exit) {
|
|
536
620
|
if (state.settingsRunner !== runner)
|
|
@@ -565,7 +649,7 @@ export class PluginSupervisor {
|
|
|
565
649
|
return;
|
|
566
650
|
}
|
|
567
651
|
state.settingsCleanupError = undefined;
|
|
568
|
-
|
|
652
|
+
removePluginHostLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
|
|
569
653
|
if (!this.disposed) {
|
|
570
654
|
this.log({
|
|
571
655
|
level: "warn",
|
|
@@ -618,7 +702,7 @@ export class PluginSupervisor {
|
|
|
618
702
|
throw failure;
|
|
619
703
|
}
|
|
620
704
|
state.runtimeCleanupError = undefined;
|
|
621
|
-
|
|
705
|
+
removePluginHostLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
622
706
|
state.health = undefined;
|
|
623
707
|
state.error = undefined;
|
|
624
708
|
state.state = state.record.manifest.runtime ? "stopped" : "absent";
|
|
@@ -650,7 +734,7 @@ export class PluginSupervisor {
|
|
|
650
734
|
return;
|
|
651
735
|
}
|
|
652
736
|
state.runtimeCleanupError = undefined;
|
|
653
|
-
|
|
737
|
+
removePluginHostLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
654
738
|
if (state.stopping || this.disposed)
|
|
655
739
|
return;
|
|
656
740
|
const stable = state.startedAt && Date.now() - state.startedAt >= STABLE_RESET_MS;
|
|
@@ -751,7 +835,7 @@ export class PluginSupervisor {
|
|
|
751
835
|
return;
|
|
752
836
|
}
|
|
753
837
|
state.runtimeCleanupError = undefined;
|
|
754
|
-
|
|
838
|
+
removePluginHostLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
|
|
755
839
|
if (state.stopping || this.disposed)
|
|
756
840
|
return;
|
|
757
841
|
this.failState(state, error);
|
|
@@ -783,8 +867,8 @@ function isUnprovenProcessCleanup(error) {
|
|
|
783
867
|
function healthState(health) {
|
|
784
868
|
return health.state;
|
|
785
869
|
}
|
|
786
|
-
async function verifyInstalledPackage(record) {
|
|
787
|
-
const integrity = await validatePluginPackageSnapshot(record.installPath);
|
|
870
|
+
async function verifyInstalledPackage(record, signal) {
|
|
871
|
+
const integrity = await validatePluginPackageSnapshot(record.installPath, { signal });
|
|
788
872
|
if (integrity !== record.integrity) {
|
|
789
873
|
throw new Error(`plugin package integrity changed for "${record.canonicalId}"`);
|
|
790
874
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/daemon",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.53",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -43,6 +43,14 @@
|
|
|
43
43
|
"./internal/process": {
|
|
44
44
|
"types": "./dist/process.d.ts",
|
|
45
45
|
"default": "./dist/process.js"
|
|
46
|
+
},
|
|
47
|
+
"./plugin-management": {
|
|
48
|
+
"types": "./dist/plugin-management.d.ts",
|
|
49
|
+
"default": "./dist/plugin-management.js"
|
|
50
|
+
},
|
|
51
|
+
"./plugin-maintenance": {
|
|
52
|
+
"types": "./dist/plugin-maintenance.d.ts",
|
|
53
|
+
"default": "./dist/plugin-maintenance.js"
|
|
46
54
|
}
|
|
47
55
|
},
|
|
48
56
|
"dependencies": {
|
|
@@ -52,21 +60,21 @@
|
|
|
52
60
|
"tar": "^7.5.19",
|
|
53
61
|
"undici": "^7.28.0",
|
|
54
62
|
"ws": "^8.21.0",
|
|
55
|
-
"@rynx-ai/
|
|
56
|
-
"@rynx-ai/
|
|
57
|
-
"@rynx-ai/plugin-runner": "0.1.11-beta.
|
|
58
|
-
"@rynx-ai/plugin-sdk": "0.1.11-beta.
|
|
59
|
-
"@rynx-ai/protocol": "0.1.11-beta.
|
|
60
|
-
"@rynx-ai/remote-runtime-client": "0.1.11-beta.
|
|
61
|
-
"@rynx-ai/server": "0.1.11-beta.
|
|
63
|
+
"@rynx-ai/core": "0.1.11-beta.53",
|
|
64
|
+
"@rynx-ai/emulator": "0.1.11-beta.53",
|
|
65
|
+
"@rynx-ai/plugin-runner": "0.1.11-beta.53",
|
|
66
|
+
"@rynx-ai/plugin-sdk": "0.1.11-beta.53",
|
|
67
|
+
"@rynx-ai/protocol": "0.1.11-beta.53",
|
|
68
|
+
"@rynx-ai/remote-runtime-client": "0.1.11-beta.53",
|
|
69
|
+
"@rynx-ai/server": "0.1.11-beta.53"
|
|
62
70
|
},
|
|
63
71
|
"devDependencies": {
|
|
64
72
|
"@types/ws": "^8.18.1",
|
|
65
|
-
"@rynx-ai/browser-cdp": "0.1.11-beta.
|
|
66
|
-
"@rynx-ai/plugin-channel-lark": "0.1.11-beta.
|
|
73
|
+
"@rynx-ai/browser-cdp": "0.1.11-beta.53",
|
|
74
|
+
"@rynx-ai/plugin-channel-lark": "0.1.11-beta.53"
|
|
67
75
|
},
|
|
68
76
|
"scripts": {
|
|
69
|
-
"build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node scripts/prepare-agent-browser.mjs && node ../../scripts/stage-bundled-plugins.mjs",
|
|
77
|
+
"build": "rm -rf dist bundled-plugins && tsc -p tsconfig.json && chmod +x dist/index-daemon.js && node scripts/prepare-agent-browser.mjs && node ../../scripts/stage-bundled-plugins.mjs && node scripts/build-plugin-maintenance.mjs",
|
|
70
78
|
"dev": "tsx watch --tsconfig ../../tsconfig.json src/index-daemon.ts"
|
|
71
79
|
}
|
|
72
80
|
}
|