@rynx-ai/daemon 0.1.11-beta.2 → 0.1.11-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/bundled-plugins/plugins/lark/dist/runtime.js +2 -5
  2. package/bundled-plugins/plugins/lark/dist/settings.js +2 -2
  3. package/bundled-plugins/plugins/lark/package.json +1 -1
  4. package/dist/browser-artifact-management.d.ts +4 -1
  5. package/dist/browser-artifact-management.js +18 -10
  6. package/dist/browser-artifacts.d.ts +1 -0
  7. package/dist/browser-artifacts.js +1 -0
  8. package/dist/chrome-for-testing-runtime.d.ts +27 -0
  9. package/dist/chrome-for-testing-runtime.js +193 -0
  10. package/dist/chrome-for-testing-store.d.ts +2 -0
  11. package/dist/chrome-for-testing-store.js +9 -0
  12. package/dist/daemon-server.d.ts +4 -1
  13. package/dist/daemon-server.js +137 -32
  14. package/dist/db.js +2 -0
  15. package/dist/entry-path.d.ts +3 -0
  16. package/dist/entry-path.js +12 -0
  17. package/dist/index-daemon.js +3 -87
  18. package/dist/lifecycle.d.ts +12 -0
  19. package/dist/lifecycle.js +16 -0
  20. package/dist/maintenance-lease.d.ts +39 -0
  21. package/dist/maintenance-lease.js +250 -0
  22. package/dist/plugin-host-rpc.d.ts +19 -1
  23. package/dist/plugin-host-rpc.js +163 -23
  24. package/dist/plugin-installer.js +59 -0
  25. package/dist/plugin-supervisor.d.ts +5 -0
  26. package/dist/plugin-supervisor.js +218 -29
  27. package/dist/pm2.d.ts +11 -2
  28. package/dist/pm2.js +116 -19
  29. package/dist/process.d.ts +12 -0
  30. package/dist/process.js +91 -0
  31. package/dist/session-launch-operations.d.ts +10 -1
  32. package/dist/session-launch-operations.js +15 -6
  33. package/dist/setup-service.d.ts +20 -0
  34. package/dist/setup-service.js +38 -0
  35. package/package.json +33 -20
  36. package/dist/browser-cli-args.d.ts +0 -20
  37. package/dist/browser-cli-args.js +0 -100
  38. package/dist/cli.d.ts +0 -2
  39. package/dist/cli.js +0 -550
  40. package/dist/control-client.d.ts +0 -122
  41. package/dist/control-client.js +0 -855
  42. package/dist/control-endpoint.d.ts +0 -14
  43. package/dist/control-endpoint.js +0 -124
  44. package/dist/desktop-browser-host-client.d.ts +0 -35
  45. package/dist/desktop-browser-host-client.js +0 -415
  46. package/dist/setup.d.ts +0 -20
  47. package/dist/setup.js +0 -386
  48. package/dist/update.d.ts +0 -25
  49. package/dist/update.js +0 -133
@@ -180,6 +180,8 @@ export async function uninstallPlugin(identity, onProgress = () => { }, options
180
180
  throw new Error(`bundled plugin "${record.canonicalId}" cannot be uninstalled; disable it instead`);
181
181
  }
182
182
  assertNoActiveLeases(record);
183
+ if (options.purgeData)
184
+ assertNoOwnedSessions(record);
183
185
  return removePlugin(record.canonicalId);
184
186
  }).immediate();
185
187
  if (!removed)
@@ -193,9 +195,37 @@ export async function uninstallPlugin(identity, onProgress = () => { }, options
193
195
  onProgress(`uninstalled plugin "${record.canonicalId}"`);
194
196
  return true;
195
197
  }
198
+ function assertNoOwnedSessions(record) {
199
+ const row = db()
200
+ .prepare(`WITH RECURSIVE owned(id) AS (
201
+ SELECT session_id
202
+ FROM plugin_session_launch_operations
203
+ WHERE plugin_id = ?
204
+ UNION
205
+ SELECT id FROM sessions WHERE source = ?
206
+ UNION
207
+ SELECT sessions.id
208
+ FROM sessions
209
+ JOIN owned ON sessions.forked_from_session_id = owned.id
210
+ UNION
211
+ SELECT session_fork_operations.target_session_id
212
+ FROM session_fork_operations
213
+ JOIN owned ON session_fork_operations.source_session_id = owned.id
214
+ )
215
+ SELECT COUNT(*) AS count
216
+ FROM sessions
217
+ JOIN owned ON sessions.id = owned.id`)
218
+ .get(record.canonicalId, `plugin:${record.canonicalId}`);
219
+ if (row.count > 0) {
220
+ throw new Error(`cannot purge plugin "${record.canonicalId}": ${row.count} plugin-owned Session(s) still exist`);
221
+ }
222
+ }
196
223
  export function pruneInactivePluginInstallations() {
197
224
  const { cacheRoot } = ensureStorageRoots();
198
225
  const active = new Set(listActivePaths().map(canonicalFilesystemPath));
226
+ for (const leased of listActiveRuntimeLeasePaths(cacheRoot)) {
227
+ active.add(leased);
228
+ }
199
229
  const removed = [];
200
230
  for (const marketplace of safeDirectories(cacheRoot)) {
201
231
  for (const plugin of safeDirectories(join(cacheRoot, marketplace))) {
@@ -327,6 +357,35 @@ function listActivePaths() {
327
357
  }
328
358
  return paths;
329
359
  }
360
+ function listActiveRuntimeLeasePaths(cacheRoot) {
361
+ const dataRoot = join(rynxHome(), "plugins", "data");
362
+ const active = [];
363
+ for (const marketplace of safeDirectories(dataRoot)) {
364
+ for (const plugin of safeDirectories(join(dataRoot, marketplace))) {
365
+ const generationRoot = join(cacheRoot, marketplace, plugin);
366
+ const managedGenerations = new Set(safeDirectories(generationRoot).map((generation) => canonicalFilesystemPath(join(generationRoot, generation))));
367
+ if (managedGenerations.size === 0)
368
+ continue;
369
+ const dataDir = join(dataRoot, marketplace, plugin);
370
+ for (const lease of readPluginRuntimeLeases(dataDir)) {
371
+ // A lease is plugin-owned input. It may pin only the same plugin
372
+ // namespace from which it was read and only one exact host-owned,
373
+ // immutable generation directory.
374
+ if (lease.pluginId !== plugin)
375
+ continue;
376
+ const pid = pluginCliInvocationLeasePid(lease.leaseId);
377
+ if (pid !== undefined && !isProcessAlive(pid)) {
378
+ removePluginRuntimeLease(dataDir, lease.leaseId);
379
+ continue;
380
+ }
381
+ const installPath = canonicalFilesystemPath(lease.installPath);
382
+ if (managedGenerations.has(installPath))
383
+ active.push(installPath);
384
+ }
385
+ }
386
+ }
387
+ return active;
388
+ }
330
389
  function safeProgress(callback) {
331
390
  return (line) => {
332
391
  try {
@@ -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.disposed)
215
- return;
219
+ if (this.disposePromise)
220
+ return this.disposePromise;
216
221
  this.disposed = true;
217
- await Promise.allSettled([...this.states.values()].map((state) => this.runSerialized(state.record.canonicalId, async () => {
218
- await this.stopState(state);
219
- await this.stopSettingsState(state);
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 || state.runner || !state.record.enabled)
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,6 +292,8 @@ export class PluginSupervisor {
270
292
  pluginId: state.record.pluginId,
271
293
  principalId: state.record.canonicalId,
272
294
  daemonInstanceId: this.daemonInstanceId,
295
+ productVersion: this.productVersion,
296
+ cliVersion: this.cliVersion,
273
297
  capabilities: state.record.manifest.capabilities,
274
298
  services: this.services,
275
299
  ...(state.record.pluginId === "workspace"
@@ -333,8 +357,21 @@ export class PluginSupervisor {
333
357
  });
334
358
  }
335
359
  catch (error) {
336
- await hostRpc?.dispose().catch(() => undefined);
360
+ try {
361
+ await hostRpc?.dispose();
362
+ }
363
+ catch (cleanupError) {
364
+ const failure = new AggregateError([error, cleanupError], "plugin runtime startup failed and active-run cleanup was unproven");
365
+ state.runtimeCleanupError = failure;
366
+ this.failState(state, failure);
367
+ return;
368
+ }
337
369
  state.hostRpc = undefined;
370
+ if (isUnprovenProcessCleanup(error)) {
371
+ state.runtimeCleanupError = error;
372
+ this.failState(state, error);
373
+ return;
374
+ }
338
375
  removePluginRuntimeLease(dataDir, RUNTIME_LEASE_ID);
339
376
  this.failState(state, error);
340
377
  this.scheduleRestart(state);
@@ -343,6 +380,9 @@ export class PluginSupervisor {
343
380
  async startSettingsState(state) {
344
381
  if (this.disposed)
345
382
  throw new Error("plugin supervisor is disposed");
383
+ const cleanupError = blockingCleanupError(state);
384
+ if (cleanupError)
385
+ throw cleanupError;
346
386
  if (state.settingsRunner)
347
387
  return state.settingsRunner;
348
388
  const background = state.record.manifest.settings?.background;
@@ -369,6 +409,8 @@ export class PluginSupervisor {
369
409
  pluginId: state.record.pluginId,
370
410
  principalId: state.record.canonicalId,
371
411
  daemonInstanceId: this.daemonInstanceId,
412
+ productVersion: this.productVersion,
413
+ cliVersion: this.cliVersion,
372
414
  capabilities: state.record.manifest.capabilities,
373
415
  services: this.services,
374
416
  ...(state.record.pluginId === "workspace"
@@ -429,8 +471,32 @@ export class PluginSupervisor {
429
471
  return runner;
430
472
  }
431
473
  catch (error) {
432
- await runner?.close().catch(() => undefined);
433
- await hostRpc?.dispose().catch(() => undefined);
474
+ const cleanupErrors = [];
475
+ if (runner) {
476
+ try {
477
+ await runner.close();
478
+ }
479
+ catch (cleanupError) {
480
+ state.settingsRunner = runner;
481
+ cleanupErrors.push(cleanupError);
482
+ }
483
+ }
484
+ try {
485
+ await hostRpc?.dispose();
486
+ }
487
+ catch (cleanupError) {
488
+ state.settingsHostRpc = hostRpc;
489
+ cleanupErrors.push(cleanupError);
490
+ }
491
+ if (cleanupErrors.length > 0) {
492
+ const failure = new AggregateError([error, ...cleanupErrors], "plugin settings startup failed and cleanup was unproven");
493
+ state.settingsCleanupError = failure;
494
+ throw failure;
495
+ }
496
+ if (isUnprovenProcessCleanup(error)) {
497
+ state.settingsCleanupError = error;
498
+ throw error;
499
+ }
434
500
  removePluginRuntimeLease(dataDir, SETTINGS_LEASE_ID);
435
501
  throw error;
436
502
  }
@@ -438,20 +504,71 @@ export class PluginSupervisor {
438
504
  async stopSettingsState(state) {
439
505
  const runner = state.settingsRunner;
440
506
  const hostRpc = state.settingsHostRpc;
441
- state.settingsRunner = undefined;
442
- state.settingsHostRpc = undefined;
443
- await runner?.close().catch(() => undefined);
444
- await hostRpc?.dispose().catch(() => undefined);
507
+ if (state.settingsCleanupError && !runner && !hostRpc) {
508
+ throw state.settingsCleanupError;
509
+ }
510
+ const errors = [];
511
+ if (runner) {
512
+ try {
513
+ await runner.close();
514
+ if (state.settingsRunner === runner)
515
+ state.settingsRunner = undefined;
516
+ }
517
+ catch (error) {
518
+ errors.push(error);
519
+ }
520
+ }
521
+ if (hostRpc) {
522
+ try {
523
+ await hostRpc.dispose();
524
+ if (state.settingsHostRpc === hostRpc)
525
+ state.settingsHostRpc = undefined;
526
+ }
527
+ catch (error) {
528
+ errors.push(error);
529
+ }
530
+ }
531
+ if (errors.length > 0) {
532
+ const failure = cleanupFailure(errors, "plugin settings cleanup was unproven");
533
+ state.settingsCleanupError = failure;
534
+ throw failure;
535
+ }
536
+ state.settingsCleanupError = undefined;
445
537
  removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
446
538
  }
447
539
  async handleSettingsExit(state, runner, hostRpc, exit) {
448
540
  if (state.settingsRunner !== runner)
449
541
  return;
542
+ try {
543
+ await runner.terminateTree();
544
+ }
545
+ catch (error) {
546
+ state.settingsCleanupError = error;
547
+ this.log({
548
+ level: "error",
549
+ event: "plugin-settings-exited",
550
+ pluginId: state.record.canonicalId,
551
+ message: error instanceof Error ? error.message : String(error),
552
+ });
553
+ return;
554
+ }
450
555
  state.settingsRunner = undefined;
451
- if (state.settingsHostRpc === hostRpc)
452
- state.settingsHostRpc = undefined;
453
- await runner.terminateTree().catch(() => undefined);
454
- await hostRpc.dispose().catch(() => undefined);
556
+ try {
557
+ await hostRpc.dispose();
558
+ if (state.settingsHostRpc === hostRpc)
559
+ state.settingsHostRpc = undefined;
560
+ }
561
+ catch (error) {
562
+ state.settingsCleanupError = error;
563
+ this.log({
564
+ level: "error",
565
+ event: "plugin-settings-exited",
566
+ pluginId: state.record.canonicalId,
567
+ message: error instanceof Error ? error.message : String(error),
568
+ });
569
+ return;
570
+ }
571
+ state.settingsCleanupError = undefined;
455
572
  removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), SETTINGS_LEASE_ID);
456
573
  if (!this.disposed) {
457
574
  this.log({
@@ -473,10 +590,38 @@ export class PluginSupervisor {
473
590
  clearTimeout(state.healthTimer);
474
591
  state.healthTimer = undefined;
475
592
  const runner = state.runner;
476
- state.runner = undefined;
477
- await runner?.close().catch(() => undefined);
478
- await state.hostRpc?.dispose().catch(() => undefined);
479
- state.hostRpc = undefined;
593
+ const hostRpc = state.hostRpc;
594
+ if (state.runtimeCleanupError && !runner && !hostRpc) {
595
+ throw state.runtimeCleanupError;
596
+ }
597
+ const errors = [];
598
+ if (runner) {
599
+ try {
600
+ await runner.close();
601
+ if (state.runner === runner)
602
+ state.runner = undefined;
603
+ }
604
+ catch (error) {
605
+ errors.push(error);
606
+ }
607
+ }
608
+ if (hostRpc) {
609
+ try {
610
+ await hostRpc.dispose();
611
+ if (state.hostRpc === hostRpc)
612
+ state.hostRpc = undefined;
613
+ }
614
+ catch (error) {
615
+ errors.push(error);
616
+ }
617
+ }
618
+ if (errors.length > 0) {
619
+ const failure = cleanupFailure(errors, "plugin runtime cleanup was unproven");
620
+ state.runtimeCleanupError = failure;
621
+ this.failState(state, failure);
622
+ throw failure;
623
+ }
624
+ state.runtimeCleanupError = undefined;
480
625
  removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
481
626
  state.health = undefined;
482
627
  state.error = undefined;
@@ -489,10 +634,26 @@ export class PluginSupervisor {
489
634
  if (state.healthTimer)
490
635
  clearTimeout(state.healthTimer);
491
636
  state.healthTimer = undefined;
637
+ try {
638
+ await runner.terminateTree();
639
+ }
640
+ catch (error) {
641
+ state.runtimeCleanupError = error;
642
+ this.failState(state, error);
643
+ return;
644
+ }
492
645
  state.runner = undefined;
493
- await runner.terminateTree().catch(() => undefined);
494
- await state.hostRpc?.dispose().catch(() => undefined);
495
- state.hostRpc = undefined;
646
+ try {
647
+ await state.hostRpc?.dispose();
648
+ state.hostRpc = undefined;
649
+ }
650
+ catch (error) {
651
+ state.runtimeCleanupError = error;
652
+ if (!state.stopping && !this.disposed)
653
+ this.failState(state, error);
654
+ return;
655
+ }
656
+ state.runtimeCleanupError = undefined;
496
657
  removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
497
658
  if (state.stopping || this.disposed)
498
659
  return;
@@ -571,13 +732,29 @@ export class PluginSupervisor {
571
732
  async failRunningState(state, runner, error) {
572
733
  if (state.runner !== runner)
573
734
  return;
574
- state.runner = undefined;
575
735
  if (state.healthTimer)
576
736
  clearTimeout(state.healthTimer);
577
737
  state.healthTimer = undefined;
578
- await runner.close().catch(() => undefined);
579
- await state.hostRpc?.dispose().catch(() => undefined);
580
- state.hostRpc = undefined;
738
+ try {
739
+ await runner.close();
740
+ }
741
+ catch (cleanupError) {
742
+ state.runtimeCleanupError = cleanupError;
743
+ this.failState(state, cleanupError);
744
+ return;
745
+ }
746
+ state.runner = undefined;
747
+ try {
748
+ await state.hostRpc?.dispose();
749
+ state.hostRpc = undefined;
750
+ }
751
+ catch (cleanupError) {
752
+ state.runtimeCleanupError = cleanupError;
753
+ if (!state.stopping && !this.disposed)
754
+ this.failState(state, cleanupError);
755
+ return;
756
+ }
757
+ state.runtimeCleanupError = undefined;
581
758
  removePluginRuntimeLease(pluginDataDir(state.record.marketplaceId, state.record.pluginId), RUNTIME_LEASE_ID);
582
759
  if (state.stopping || this.disposed)
583
760
  return;
@@ -595,6 +772,18 @@ export class PluginSupervisor {
595
772
  return current;
596
773
  }
597
774
  }
775
+ function cleanupFailure(errors, message) {
776
+ return errors.length === 1 ? errors[0] : new AggregateError(errors, message);
777
+ }
778
+ function blockingCleanupError(state) {
779
+ return state.runtimeCleanupError ?? state.settingsCleanupError;
780
+ }
781
+ function isUnprovenProcessCleanup(error) {
782
+ return Boolean(error
783
+ && typeof error === "object"
784
+ && "code" in error
785
+ && error.code === "PROCESS_TREE_STUCK");
786
+ }
598
787
  function healthState(health) {
599
788
  return health.state;
600
789
  }
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
- /** Stop and remove the daemon from pm2's process list. */
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;