@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.
@@ -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 { removePluginRuntimeLease, writePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
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
- await this.startState(state);
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
- await this.startState(state);
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
- await this.startState(current);
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
- await this.startState(state);
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
- await this.startState(state);
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
- await this.startState(state);
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 runner = await this.startSettingsState(state);
192
- if (!runner.handshake.features.settings) {
193
- throw new Error(`plugin "${identity}" does not advertise a settings background`);
194
- }
195
- const result = await runner.client.settingsCall(method, params, options);
196
- await state.runner?.client.emitHostEvent("plugin.settings.changed", {
197
- method,
198
- ...(params === undefined ? {} : { params }),
199
- }).catch(() => undefined);
200
- return result;
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
- removePluginRuntimeLease(dataDir, RUNTIME_LEASE_ID);
374
- this.failState(state, error);
375
- this.scheduleRestart(state);
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
- removePluginRuntimeLease(dataDir, SETTINGS_LEASE_ID);
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
- removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
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
- removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
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
- removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
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
- removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
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
- removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
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.52",
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/emulator": "0.1.11-beta.52",
56
- "@rynx-ai/core": "0.1.11-beta.52",
57
- "@rynx-ai/plugin-runner": "0.1.11-beta.52",
58
- "@rynx-ai/plugin-sdk": "0.1.11-beta.52",
59
- "@rynx-ai/protocol": "0.1.11-beta.52",
60
- "@rynx-ai/remote-runtime-client": "0.1.11-beta.52",
61
- "@rynx-ai/server": "0.1.11-beta.52"
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.52",
66
- "@rynx-ai/plugin-channel-lark": "0.1.11-beta.52"
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
  }