@rynx-ai/daemon 0.1.10 → 0.1.11-beta.10

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 (156) hide show
  1. package/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
  2. package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +25 -0
  3. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
  4. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +56 -0
  5. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
  6. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
  7. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
  8. package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
  9. package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
  10. package/bundled-plugins/plugins/lark/dist/host-adapters.js +506 -0
  11. package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
  12. package/bundled-plugins/plugins/lark/dist/index.js +7 -0
  13. package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
  14. package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
  15. package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
  16. package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
  17. package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
  18. package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
  19. package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
  20. package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
  21. package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
  22. package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
  23. package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
  24. package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
  25. package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
  26. package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
  27. package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
  28. package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
  29. package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
  30. package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
  31. package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
  32. package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
  33. package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
  34. package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
  35. package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
  36. package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
  37. package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
  38. package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
  39. package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
  40. package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
  41. package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
  42. package/bundled-plugins/plugins/lark/dist/lark.js +1975 -0
  43. package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
  44. package/bundled-plugins/plugins/lark/dist/runtime.js +131328 -0
  45. package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
  46. package/bundled-plugins/plugins/lark/dist/settings.js +126971 -0
  47. package/bundled-plugins/plugins/lark/package.json +57 -0
  48. package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
  49. package/bundled-plugins/plugins/lark/ui/assets/index-D8Amoe6a.js +201 -0
  50. package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  51. package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  52. package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  53. package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  54. package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  55. package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  56. package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  57. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  58. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  59. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  60. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  61. package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  62. package/bundled-plugins/plugins/lark/ui/index.html +13 -0
  63. package/dist/agent-file.js +0 -1
  64. package/dist/browser-artifact-management.d.ts +4 -1
  65. package/dist/browser-artifact-management.js +18 -10
  66. package/dist/browser-artifacts.d.ts +1 -0
  67. package/dist/browser-artifacts.js +1 -0
  68. package/dist/bundled-plugins.d.ts +15 -0
  69. package/dist/bundled-plugins.js +134 -0
  70. package/dist/chrome-for-testing-runtime.d.ts +27 -0
  71. package/dist/chrome-for-testing-runtime.js +193 -0
  72. package/dist/chrome-for-testing-store.d.ts +2 -0
  73. package/dist/chrome-for-testing-store.js +9 -0
  74. package/dist/control-deps.d.ts +6 -2
  75. package/dist/control-deps.js +88 -82
  76. package/dist/daemon-server.d.ts +7 -1
  77. package/dist/daemon-server.js +310 -87
  78. package/dist/db.js +181 -124
  79. package/dist/entry-path.d.ts +3 -0
  80. package/dist/entry-path.js +12 -0
  81. package/dist/index-daemon.js +3 -66
  82. package/dist/lifecycle.d.ts +12 -0
  83. package/dist/lifecycle.js +16 -0
  84. package/dist/maintenance-lease.d.ts +39 -0
  85. package/dist/maintenance-lease.js +250 -0
  86. package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
  87. package/dist/migrations/cleanup-legacy-sessions.js +33 -15
  88. package/dist/official-plugins.d.ts +2 -0
  89. package/dist/official-plugins.js +2 -0
  90. package/dist/plugin-cli.d.ts +1 -3
  91. package/dist/plugin-cli.js +56 -45
  92. package/dist/plugin-host-leases.d.ts +4 -0
  93. package/dist/plugin-host-leases.js +7 -0
  94. package/dist/plugin-host-rpc.d.ts +73 -6
  95. package/dist/plugin-host-rpc.js +354 -81
  96. package/dist/plugin-installer.d.ts +24 -55
  97. package/dist/plugin-installer.js +258 -412
  98. package/dist/plugin-management-service.d.ts +30 -23
  99. package/dist/plugin-management-service.js +288 -105
  100. package/dist/plugin-marketplace.d.ts +35 -0
  101. package/dist/plugin-marketplace.js +299 -0
  102. package/dist/plugin-package.d.ts +22 -24
  103. package/dist/plugin-package.js +520 -571
  104. package/dist/plugin-skill-context.d.ts +4 -0
  105. package/dist/plugin-skill-context.js +35 -0
  106. package/dist/plugin-store.d.ts +36 -37
  107. package/dist/plugin-store.js +138 -92
  108. package/dist/plugin-supervisor.d.ts +50 -125
  109. package/dist/plugin-supervisor.js +678 -1030
  110. package/dist/pm2.d.ts +11 -2
  111. package/dist/pm2.js +130 -28
  112. package/dist/process.d.ts +12 -0
  113. package/dist/process.js +91 -0
  114. package/dist/runtime-project-store.d.ts +9 -0
  115. package/dist/runtime-project-store.js +83 -0
  116. package/dist/session-fallback-workspace.d.ts +22 -0
  117. package/dist/session-fallback-workspace.js +120 -0
  118. package/dist/session-fork-store.d.ts +41 -0
  119. package/dist/session-fork-store.js +288 -0
  120. package/dist/session-launch-operations.d.ts +23 -2
  121. package/dist/session-launch-operations.js +48 -7
  122. package/dist/session-meta-store.d.ts +12 -9
  123. package/dist/session-meta-store.js +50 -49
  124. package/dist/session-portal-grant-store.d.ts +29 -0
  125. package/dist/session-portal-grant-store.js +84 -0
  126. package/dist/setup-service.d.ts +21 -0
  127. package/dist/setup-service.js +39 -0
  128. package/dist/skills-catalog.d.ts +11 -2
  129. package/dist/skills-catalog.js +307 -7
  130. package/dist/system-dependencies.d.ts +26 -0
  131. package/dist/system-dependencies.js +149 -0
  132. package/package.json +36 -20
  133. package/dist/browser-cli-args.d.ts +0 -20
  134. package/dist/browser-cli-args.js +0 -100
  135. package/dist/channel-store.d.ts +0 -22
  136. package/dist/channel-store.js +0 -65
  137. package/dist/cli.d.ts +0 -2
  138. package/dist/cli.js +0 -732
  139. package/dist/control-client.d.ts +0 -122
  140. package/dist/control-client.js +0 -855
  141. package/dist/control-endpoint.d.ts +0 -14
  142. package/dist/control-endpoint.js +0 -124
  143. package/dist/desktop-browser-host-client.d.ts +0 -35
  144. package/dist/desktop-browser-host-client.js +0 -415
  145. package/dist/instance-store.d.ts +0 -38
  146. package/dist/instance-store.js +0 -85
  147. package/dist/plugin-install-restart.d.ts +0 -13
  148. package/dist/plugin-install-restart.js +0 -24
  149. package/dist/plugin-runtime-control.d.ts +0 -2
  150. package/dist/plugin-runtime-control.js +0 -37
  151. package/dist/registry.d.ts +0 -10
  152. package/dist/registry.js +0 -52
  153. package/dist/setup.d.ts +0 -20
  154. package/dist/setup.js +0 -374
  155. package/dist/update.d.ts +0 -25
  156. package/dist/update.js +0 -133
@@ -6,26 +6,31 @@
6
6
  * session registry. Keeping that wiring here avoids a second app-specific fork.
7
7
  */
8
8
  import { randomBytes, randomUUID } from "node:crypto";
9
- import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
9
+ import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
10
10
  import { createRequire } from "node:module";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { dirname, join } from "node:path";
13
13
  import WebSocket from "ws";
14
- import { loadConfig, rynxHome } from "@rynx-ai/core";
14
+ import { CloseableAdmissionGate, loadConfig, resolveExecutionSnapshot, rynxHome, sessionWorkspaceSnapshotSchema, } from "@rynx-ai/core";
15
15
  import { LOCAL_MANAGEMENT_PROTOCOL, } from "@rynx-ai/protocol/control";
16
16
  import { REMOTE_RUNTIME_CORE_PROTOCOL } from "@rynx-ai/protocol/remote-runtime";
17
17
  import { RUNTIME_BROWSER_INSPECT_MAX_MESSAGE_BYTES, RUNTIME_BROWSER_INSPECT_SEMANTIC_CAPABILITY, } from "@rynx-ai/protocol/runtime-browser-inspect";
18
18
  import { RUNTIME_BROWSER_SURFACE_SEMANTIC_CAPABILITY } from "@rynx-ai/protocol/runtime-browser-surface";
19
19
  import { RUNTIME_EMULATOR_SURFACE_SEMANTIC_CAPABILITY } from "@rynx-ai/protocol/runtime-emulator-surface";
20
- import { createDaemonRuntimeHost, createDesktopFirstSessionBrowserHost, createRunnerSessionTerminalHost, createSessionRuntimeServices, DesktopBrowserHostAbsentError, DesktopBrowserHostRegistry, DirectRuntimeConnectionHub, DirectRuntimeBrowserInspectHostError, MachineSessionService, RemoteRuntimeDispatcher, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
20
+ import { createDaemonRuntimeHost, createDesktopFirstSessionBrowserHost, createRunnerSessionTerminalHost, createSessionRuntimeServices, DesktopBrowserHostAbsentError, DesktopBrowserHostRegistry, DirectRuntimeConnectionHub, DirectRuntimeBrowserInspectHostError, MachineSessionService, MachineSessionServiceFailure, RemoteRuntimeDispatcher, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
21
+ import { authorizeSessionPortalGrant, redeemSessionPortalTicket, } from "./session-portal-grant-store.js";
21
22
  import { APP_BROWSER_HOST_BOOTSTRAP_TYPE, AppBrowserHostSupervisor, parseAppBrowserHostLaunchConfig, } from "./app-browser-host-supervisor.js";
22
- import { buildControlDeps } from "./control-deps.js";
23
+ import { buildControlDeps, listControlAgentModels } from "./control-deps.js";
23
24
  import { createBrowserArtifactManagementService } from "./browser-artifact-management.js";
24
25
  import { createDaemonBuildStatus } from "./daemon-build-status.js";
26
+ import { MaintenanceLeaseStore } from "./maintenance-lease.js";
25
27
  import { BrowserRunnerSessionContextProvider } from "./browser-runner-session-context.js";
28
+ import { removeLegacyPluginSkillSnapshot, snapshotEnabledPluginSkillSources, } from "./plugin-skill-context.js";
29
+ import { installedDaemonVersion } from "./entry-path.js";
26
30
  import { isChromeForTestingHostSupported } from "./chrome-for-testing-store.js";
27
31
  import { ChromeInspectionManager, } from "./chrome-inspection-manager.js";
28
32
  import { createHeadlessBrowserHost } from "./headless-browser-host.js";
33
+ import { db } from "./db.js";
29
34
  import { acquireDaemonOwner } from "./daemon-owner.js";
30
35
  import { DaemonDirectRuntimeAuthenticator } from "./direct-runtime-authenticator.js";
31
36
  import { DurableEmbeddedBrowserProfileHost } from "./embedded-browser-profile-cleanup.js";
@@ -34,16 +39,18 @@ import { isDaemonDirectRuntimeHostingSupported, loadOrCreateDaemonDirectRuntimeE
34
39
  import { cleanupLegacySessions } from "./migrations/cleanup-legacy-sessions.js";
35
40
  import { createMaintenanceManagementService } from "./maintenance-management.js";
36
41
  import { PluginManagementService } from "./plugin-management-service.js";
42
+ import { addPluginMarketplace, listPluginMarketplaces, refreshPluginMarketplace, removePluginMarketplace, } from "./plugin-marketplace.js";
37
43
  import { PluginSupervisor } from "./plugin-supervisor.js";
38
44
  import { pruneInactivePluginInstallations } from "./plugin-installer.js";
39
45
  import { runPluginCliCommand } from "./plugin-cli.js";
40
- import { getPlugin, isRunnablePluginRecord, listEnabledPlugins } from "./plugin-store.js";
41
- import { loadPlugins, resolveChannelInstances } from "./registry.js";
46
+ import { listPlugins } from "./plugin-store.js";
42
47
  import { SqliteSessionLogStore } from "./session-log-store.js";
48
+ import { SqliteSessionForkStore } from "./session-fork-store.js";
43
49
  import { SqliteSessionResourceStore } from "./session-resource-store.js";
50
+ import { createSessionFallbackWorkspace, discardSessionFallbackWorkspace, } from "./session-fallback-workspace.js";
44
51
  import { SessionBrowserCapabilityStore } from "./session-browser-capability-store.js";
45
52
  import { SqliteSessionEmulatorBindingStore } from "./session-emulator-binding-store.js";
46
- import { sessionRegistry } from "./session-meta-store.js";
53
+ import { sessionBelongsToPlugin, sessionRegistry } from "./session-meta-store.js";
47
54
  import { sessionPendingMessageStore } from "./session-pending-message-store.js";
48
55
  import { RemoteRuntimeAccessStore } from "./remote-runtime-access-store.js";
49
56
  import { DaemonRemoteRuntimeAdmin } from "./remote-runtime-admin.js";
@@ -52,6 +59,7 @@ import { DaemonRuntimeConnectionManager } from "./remote-runtime-connection-mana
52
59
  import { DaemonRuntimeTargetControl } from "./remote-runtime-target-control.js";
53
60
  import { RemoteRuntimeTargetStore } from "./remote-runtime-target-store.js";
54
61
  import { ProviderCliService } from "./provider-cli-service.js";
62
+ import { SqliteRuntimeProjectStore } from "./runtime-project-store.js";
55
63
  const PLUGIN_CLI_OUTPUT_MAX_BYTES = 256 * 1024;
56
64
  const BROWSER_INSPECT_OPEN_TIMEOUT_MS = 10_000;
57
65
  const BROWSER_INSPECT_CLOSE_TIMEOUT_MS = 1_000;
@@ -59,8 +67,72 @@ const BROWSER_INSPECT_MAX_QUEUED_BYTES = 32 * 1024 * 1024;
59
67
  const DIRECT_RUNTIME_HOSTING_UNSUPPORTED = "Direct Runtime hosting is supported only on macOS and Linux until protected server-key storage is available on other operating systems";
60
68
  const require = createRequire(import.meta.url);
61
69
  const DAEMON_PACKAGE_VERSION = readDaemonPackageVersion();
70
+ async function resolveSessionExecutionSnapshot(input) {
71
+ return {
72
+ ...await resolveExecutionSnapshot(input),
73
+ pluginSkills: snapshotEnabledPluginSkillSources(),
74
+ };
75
+ }
76
+ async function resolveSessionWorkspace(projects, projectId, managedCwd, workspace) {
77
+ if (workspace) {
78
+ return sessionWorkspaceSnapshotSchema.parse(workspace);
79
+ }
80
+ if (projectId) {
81
+ const project = await projects.get(projectId);
82
+ if (!project) {
83
+ throw new MachineSessionServiceFailure("not_found", "project not found");
84
+ }
85
+ await assertWorkspaceDirectories(project.directories);
86
+ return sessionWorkspaceSnapshotSchema.parse({
87
+ cwd: project.primary,
88
+ allowedDirs: [
89
+ project.primary,
90
+ ...project.directories.filter((directory) => directory !== project.primary),
91
+ ],
92
+ });
93
+ }
94
+ if (managedCwd) {
95
+ let canonical;
96
+ try {
97
+ canonical = await realpath(managedCwd);
98
+ await assertWorkspaceDirectories([canonical]);
99
+ }
100
+ catch (error) {
101
+ if (error instanceof MachineSessionServiceFailure)
102
+ throw error;
103
+ throw new MachineSessionServiceFailure("failed_precondition", "Session working directory is unavailable");
104
+ }
105
+ return sessionWorkspaceSnapshotSchema.parse({
106
+ cwd: canonical,
107
+ allowedDirs: [canonical],
108
+ });
109
+ }
110
+ return await createSessionFallbackWorkspace();
111
+ }
112
+ async function assertWorkspaceDirectories(directories) {
113
+ try {
114
+ await Promise.all(directories.map(async (directory) => {
115
+ if (!(await stat(directory)).isDirectory())
116
+ throw new Error(directory);
117
+ }));
118
+ }
119
+ catch {
120
+ throw new MachineSessionServiceFailure("failed_precondition", "one or more Project directories are unavailable");
121
+ }
122
+ }
123
+ function toMarketplaceItem(record) {
124
+ return {
125
+ id: record.marketplaceId,
126
+ source: record.sourceType,
127
+ builtIn: record.builtIn,
128
+ plugins: record.catalog.plugins.map((plugin) => ({ id: plugin.id })),
129
+ resolvedRevision: record.resolvedRevision,
130
+ refreshedAt: record.refreshedAt,
131
+ };
132
+ }
62
133
  export function daemonActivityBlocksShutdown(activity) {
63
- return activity.runningTurns > 0 || activity.pendingInteractions > 0;
134
+ return activity.runningTurns > 0
135
+ || activity.pendingInteractions > 0;
64
136
  }
65
137
  function defaultWarn(message) {
66
138
  console.warn(JSON.stringify({ level: "warn", type: "channel-registry", msg: message }));
@@ -179,18 +251,31 @@ async function removeControlEndpointForCurrentProcess() {
179
251
  // Best effort only; stale endpoint files are ignored by the client fallback.
180
252
  }
181
253
  }
182
- export async function startRynxDaemonServer({ config, warn = defaultWarn, writeEndpoint = writeControlEndpoint, acquireOwner = acquireDaemonOwner, loadDirectConfig = loadDirectRuntimeConfig, startDirectServer = startDirectRuntimeServer, createChromeInspectionManager = (options) => ChromeInspectionManager.create(options), createBrowserHostSupervisor = (options) => new AppBrowserHostSupervisor(options), } = {}) {
254
+ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShutdownComplete, writeEndpoint = writeControlEndpoint, acquireOwner = acquireDaemonOwner, preparePlugins, loadDirectConfig = loadDirectRuntimeConfig, startDirectServer = startDirectRuntimeServer, createChromeInspectionManager = (options) => ChromeInspectionManager.create(options), createBrowserHostSupervisor = (options) => new AppBrowserHostSupervisor(options), } = {}) {
183
255
  const owner = await acquireOwner();
184
256
  let ownerManagedByServer = false;
185
257
  try {
258
+ await preparePlugins?.();
186
259
  const appConfig = config ?? loadConfig();
260
+ const admissionGate = new CloseableAdmissionGate();
261
+ let shutdownAccepted = false;
262
+ const maintenanceLease = new MaintenanceLeaseStore({
263
+ // Expiry restores the gate primitive for a process that outlives its
264
+ // upgrader. The independent accepted-shutdown fence below still prevents
265
+ // new work after an irreversible server shutdown has begun.
266
+ onExpired: () => admissionGate.reopen(),
267
+ });
268
+ const admissionOpen = () => !shutdownAccepted && !maintenanceLease.isActive();
269
+ const reserveAdmission = () => !admissionOpen()
270
+ ? undefined
271
+ : admissionGate.tryReserve();
187
272
  const appBrowserHostLaunch = parseAppBrowserHostLaunchConfig();
188
273
  const directHostingSupported = isDaemonDirectRuntimeHostingSupported();
189
274
  const runtimeBrowserSupported = appBrowserHostLaunch !== undefined || isChromeForTestingHostSupported();
190
275
  const directConfig = directHostingSupported
191
276
  ? await loadDirectConfig()
192
277
  : undefined;
193
- const control = buildControlDeps();
278
+ const control = buildControlDeps(appConfig);
194
279
  const installationIdentity = await loadOrCreateDaemonInstallationIdentity();
195
280
  const directEncryptionIdentity = directHostingSupported
196
281
  ? await loadOrCreateDaemonDirectRuntimeEncryptionIdentity()
@@ -214,12 +299,15 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
214
299
  "semantic.session.interrupt.v1",
215
300
  "semantic.session.agent-options.v1",
216
301
  "semantic.session.launch-options.v2",
302
+ "semantic.session.execution-settings.v1",
217
303
  "semantic.session.create.v1",
304
+ "semantic.session.fork.v1",
218
305
  "semantic.session.message.v1",
219
306
  "semantic.session.resources.v1",
220
307
  "semantic.session.message-queue.v1",
221
308
  "semantic.session.delete.v1",
222
309
  "semantic.session.interaction.v1",
310
+ "semantic.project.manage.v1",
223
311
  "semantic.provider-cli.status.v1",
224
312
  ...(runtimeBrowserSupported ? ["semantic.browser.v1"] : []),
225
313
  ...(runtimeBrowserSupported && directHostingSupported
@@ -259,8 +347,11 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
259
347
  catch (error) {
260
348
  warn(`plugin package cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
261
349
  }
262
- const pluginSupervisor = new PluginSupervisor(listEnabledPlugins(), {
350
+ const pluginSupervisor = new PluginSupervisor(listPlugins(), {
263
351
  daemonInstanceId,
352
+ productVersion: controlEndpointDistributionMetadata().productVersion,
353
+ cliVersion: boundedEnvironmentMetadata(process.env.RYNX_CLI_VERSION, 128)
354
+ ?? installedDaemonVersion(),
264
355
  log: (entry) => {
265
356
  const output = entry.level === "error"
266
357
  ? console.error
@@ -270,39 +361,14 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
270
361
  output(JSON.stringify(entry));
271
362
  },
272
363
  });
273
- const pluginManagement = new PluginManagementService();
364
+ const pluginManagement = new PluginManagementService(undefined, {
365
+ supervisor: pluginSupervisor,
366
+ });
274
367
  const browserArtifactManagement = createBrowserArtifactManagementService();
275
368
  const maintenanceManagement = createMaintenanceManagementService({
276
369
  codexStorePath: appConfig.AGENT_SESSION_STORE_PATH,
277
370
  });
278
- await loadPlugins(pluginSupervisor, warn);
279
371
  logLegacyCleanup(appConfig);
280
- const reloadPluginRuntime = async (pluginId) => {
281
- const record = getPlugin(pluginId);
282
- if (!record)
283
- throw new Error(`plugin "${pluginId}" is not installed`);
284
- if (!record.enabled)
285
- throw new Error(`plugin "${pluginId}" is disabled`);
286
- if (!isRunnablePluginRecord(record)) {
287
- throw new Error(`plugin "${pluginId}" must be reinstalled before its runtime can start`);
288
- }
289
- const status = await pluginSupervisor.reloadPluginRuntime(record);
290
- await loadPlugins(pluginSupervisor, warn);
291
- return { pluginId, status };
292
- };
293
- const ensurePluginRuntime = async (pluginId) => {
294
- const record = getPlugin(pluginId);
295
- if (!record)
296
- throw new Error(`plugin "${pluginId}" is not installed`);
297
- if (!record.enabled)
298
- throw new Error(`plugin "${pluginId}" is disabled`);
299
- if (!isRunnablePluginRecord(record)) {
300
- throw new Error(`plugin "${pluginId}" must be reinstalled before its runtime can start`);
301
- }
302
- const status = await pluginSupervisor.ensurePluginRuntime(record);
303
- await loadPlugins(pluginSupervisor, warn);
304
- return { pluginId, status };
305
- };
306
372
  const invokePluginCli = async ({ pluginId, args, stdin }, { signal }) => {
307
373
  const result = await runPluginCliCommand(pluginId, args, {
308
374
  stdio: "pipe",
@@ -310,14 +376,6 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
310
376
  captureOutputBytes: PLUGIN_CLI_OUTPUT_MAX_BYTES,
311
377
  signal,
312
378
  });
313
- if (result.code === 0) {
314
- const reload = result.effects.some((effect) => effect.effect === "runtime.reload");
315
- const ensure = result.effects.some((effect) => effect.effect === "runtime.ensure");
316
- if (reload)
317
- await reloadPluginRuntime(pluginId);
318
- else if (ensure)
319
- await ensurePluginRuntime(pluginId);
320
- }
321
379
  const output = result.output;
322
380
  return {
323
381
  code: result.code,
@@ -326,6 +384,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
326
384
  truncated: Boolean(output?.stdoutTruncated || output?.stderrTruncated),
327
385
  };
328
386
  };
387
+ let machineSessions;
329
388
  const pluginRuntime = {
330
389
  bindHostServices: (services) => pluginSupervisor.bindHostServices({
331
390
  ...services,
@@ -335,10 +394,37 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
335
394
  publicKey: installationIdentity.publicKey,
336
395
  sign: installationIdentity.sign,
337
396
  },
397
+ sessionLaunch: {
398
+ resolve: async (selection) => {
399
+ const workspace = await resolveSessionWorkspace(runtimeProjects, selection.projectId, selection.managedCwd, selection.workspace);
400
+ return {
401
+ workspace,
402
+ execution: await resolveSessionExecutionSnapshot({
403
+ config: appConfig,
404
+ ...(selection.agent ? { agentName: selection.agent } : {}),
405
+ ...(selection.config ? { spec: selection.config } : {}),
406
+ ...((selection.provider || (!selection.agent && !selection.config))
407
+ ? { provider: selection.provider ?? appConfig.DEFAULT_RUNTIME }
408
+ : {}),
409
+ ...(selection.model !== undefined ? { model: selection.model } : {}),
410
+ ...(selection.reasoningEffort
411
+ ? { reasoningEffort: selection.reasoningEffort }
412
+ : {}),
413
+ cwd: workspace.cwd,
414
+ }),
415
+ };
416
+ },
417
+ },
418
+ sessionExecution: {
419
+ update: async (input) => {
420
+ await machineSessions.updateExecutionSettings(input);
421
+ },
422
+ },
423
+ sessionFork: (input) => machineSessions.fork(input),
424
+ admissionOpen,
425
+ admissionReserve: reserveAdmission,
338
426
  }),
339
427
  startPluginRuntimes: pluginSupervisor.startPluginRuntimes.bind(pluginSupervisor),
340
- reloadPluginRuntime,
341
- ensurePluginRuntime,
342
428
  statusSnapshot: pluginSupervisor.statusSnapshot.bind(pluginSupervisor),
343
429
  dispose: pluginSupervisor.dispose.bind(pluginSupervisor),
344
430
  };
@@ -346,6 +432,8 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
346
432
  const directConnections = new DirectRuntimeConnectionHub();
347
433
  const sessionLog = new SqliteSessionLogStore();
348
434
  const sessionResources = new SqliteSessionResourceStore();
435
+ const sessionForks = new SqliteSessionForkStore();
436
+ const runtimeProjects = new SqliteRuntimeProjectStore();
349
437
  const providerClis = new ProviderCliService();
350
438
  const sessionBrowserCapabilities = new SessionBrowserCapabilityStore();
351
439
  const durableDesktopBrowserHost = new DurableEmbeddedBrowserProfileHost(desktopBrowserHosts, { warn });
@@ -357,16 +445,21 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
357
445
  config: appConfig,
358
446
  sessionLog,
359
447
  sessionContextProvider: browserRunnerSessionContexts,
448
+ admissionOpen,
449
+ admissionReserve: reserveAdmission,
360
450
  onMirrorError: (error, sessionId) => warn(`Session mirror failed for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`),
361
451
  });
452
+ const sessionBrowserHost = appBrowserHostSupervisor
453
+ ? createSupervisedAppSessionBrowserHost(durableDesktopBrowserHost, appBrowserHostSupervisor)
454
+ : createDesktopFirstSessionBrowserHost(durableDesktopBrowserHost, createHeadlessBrowserHost());
362
455
  const sessionBrowsers = new SessionBrowserService({
363
- host: appBrowserHostSupervisor
364
- ? createSupervisedAppSessionBrowserHost(durableDesktopBrowserHost, appBrowserHostSupervisor)
365
- : createDesktopFirstSessionBrowserHost(durableDesktopBrowserHost, createHeadlessBrowserHost()),
456
+ host: sessionBrowserHost,
366
457
  sessions: {
367
458
  exists: async (sessionId) => Boolean(sessionRegistry.get(sessionId) ??
368
459
  await sessionRuntimeServices.sessionStore.get(sessionId)),
369
460
  },
461
+ }, {
462
+ admissionReserve: reserveAdmission,
370
463
  });
371
464
  const sessionEmulators = control.emulator
372
465
  ? new SessionEmulatorService({
@@ -422,14 +515,35 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
422
515
  sessionEmulators?.cleanupSession(sessionId),
423
516
  ]);
424
517
  },
425
- afterDelete: (sessionId) => {
518
+ afterDelete: async (sessionId) => {
426
519
  sessionBrowserCapabilities.revoke(sessionId);
520
+ removeLegacyPluginSkillSnapshot(sessionId);
521
+ await sessionRuntimeServices.sessionStore.delete(sessionId);
427
522
  },
428
523
  };
429
- const machineSessions = new MachineSessionService({
524
+ const deletedSessions = db()
525
+ .prepare("SELECT session_id AS sessionId FROM session_snapshot_cleanup ORDER BY session_id")
526
+ .all();
527
+ for (const { sessionId } of deletedSessions) {
528
+ try {
529
+ await Promise.all([
530
+ sessionBrowsers.cleanupSession(sessionId),
531
+ sessionEmulators?.cleanupSession(sessionId),
532
+ ]);
533
+ sessionResources.deleteSession(sessionId);
534
+ removeLegacyPluginSkillSnapshot(sessionId);
535
+ await sessionRuntimeServices.sessionStore.delete(sessionId);
536
+ db().prepare("DELETE FROM session_snapshot_cleanup WHERE session_id = ?").run(sessionId);
537
+ }
538
+ catch (error) {
539
+ warn(`Legacy Session artifact cleanup will retry for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
540
+ }
541
+ }
542
+ machineSessions = new MachineSessionService({
430
543
  registry: sessionRegistry,
431
544
  log: sessionLog,
432
545
  events: sessionRuntimeServices.sessionBus,
546
+ publishEvent: sessionRuntimeServices.publishSessionEvent,
433
547
  runtimeState: sessionRuntimeServices.sessionRuntimeIndex,
434
548
  interruption: {
435
549
  interrupt: (sessionId) => sessionRuntimeServices.runnerManager.interruptLiveSession(sessionId),
@@ -440,29 +554,131 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
440
554
  },
441
555
  pendingMessages: sessionPendingMessageStore,
442
556
  resources: sessionResources,
557
+ forks: sessionForks,
443
558
  execution: {
444
559
  runtime: sessionRuntimeServices.conversationRuntime,
560
+ resolve: ({ agent, provider, model, reasoningEffort, permissionPreset, cwd }) => resolveSessionExecutionSnapshot({
561
+ config: appConfig,
562
+ ...(agent ? { agentName: agent } : {}),
563
+ ...(provider ? { provider } : {}),
564
+ ...(model ? { model } : {}),
565
+ ...(reasoningEffort ? { reasoningEffort } : {}),
566
+ ...(permissionPreset ? { permissionPreset } : {}),
567
+ cwd,
568
+ }),
569
+ listModels: (provider) => listControlAgentModels(appConfig, provider),
445
570
  runner: sessionRuntimeServices.runnerManager,
446
- sessionStore: sessionRuntimeServices.sessionStore,
447
571
  },
572
+ workspace: {
573
+ resolve: (projectId) => resolveSessionWorkspace(runtimeProjects, projectId),
574
+ discardUnbound: discardSessionFallbackWorkspace,
575
+ },
576
+ }, {
577
+ admissionOpen,
578
+ admissionReserve: reserveAdmission,
448
579
  });
449
580
  const directAuthenticator = new DaemonDirectRuntimeAuthenticator(installationIdentity, remoteRuntime, remoteRuntimeAccess);
450
581
  const sessionTerminals = createRunnerSessionTerminalHost({
451
582
  runnerManager: sessionRuntimeServices.runnerManager,
583
+ admissionReserve: reserveAdmission,
452
584
  resolveLiveSession: async (sessionId) => {
453
- const bound = await sessionRuntimeServices.sessionStore.get(sessionId);
454
585
  const registered = sessionRegistry.get(sessionId);
455
- const cwd = bound?.cwd ?? registered?.config?.osEnv?.cwd;
456
- if ((!bound && !registered) ||
457
- !cwd ||
586
+ if (!registered ||
458
587
  !sessionRuntimeServices.runnerManager.hasLiveSession(sessionId)) {
459
588
  return undefined;
460
589
  }
461
- return { cwd };
590
+ return { cwd: registered.workspace.cwd };
462
591
  },
463
592
  });
464
- let shutdownAccepted = false;
465
593
  let requestServerShutdown;
594
+ let shutdownAttempt;
595
+ let shutdownAttemptLeaseToken;
596
+ const shutdownIfIdle = (maintenanceLeaseToken) => {
597
+ if (maintenanceLease.isActive()) {
598
+ if (!maintenanceLeaseToken || !maintenanceLease.isHeldBy(maintenanceLeaseToken)) {
599
+ return Promise.reject(new Error("a different maintenance lease is already active"));
600
+ }
601
+ // The previous daemon may have persisted the lease and exited after
602
+ // its 202 response was lost. A replacement process holding the same
603
+ // token must perform its own close/drain/shutdown; returning accepted
604
+ // without scheduling this PID would strand the updater waiting for a
605
+ // process that can never exit.
606
+ }
607
+ if (shutdownAccepted) {
608
+ if (maintenanceLeaseToken) {
609
+ maintenanceLease.acquire(maintenanceLeaseToken);
610
+ return Promise.resolve({
611
+ outcome: "accepted",
612
+ maintenanceLease: "active",
613
+ });
614
+ }
615
+ return Promise.resolve({ outcome: "accepted" });
616
+ }
617
+ if (shutdownAttempt) {
618
+ if (maintenanceLeaseToken === shutdownAttemptLeaseToken) {
619
+ return shutdownAttempt;
620
+ }
621
+ return Promise.reject(new Error("another idle shutdown check is already in progress"));
622
+ }
623
+ shutdownAttemptLeaseToken = maintenanceLeaseToken;
624
+ const attempt = (async () => {
625
+ await admissionGate.closeAndDrain();
626
+ const runtimeActivity = sessionRuntimeServices.sessionRuntimeIndex.activitySnapshot();
627
+ const activity = {
628
+ ...runtimeActivity,
629
+ // A terminal submission keeps its own admission reservation until the
630
+ // first response or native rotation is visible. Failed process-tree
631
+ // cleanup releases the reservation but remains pinned here, so an
632
+ // idle check returns busy instead of hanging or killing hidden work.
633
+ runningTurns: runtimeActivity.runningTurns
634
+ + sessionRuntimeServices.runnerManager.pendingTerminalInputCount(),
635
+ activeTerminals: sessionTerminals.activeTerminalCount(),
636
+ activeBrowsers: sessionBrowsers.activeBrowserCount(),
637
+ };
638
+ if (daemonActivityBlocksShutdown(activity)) {
639
+ admissionGate.reopen();
640
+ return { outcome: "busy", activity };
641
+ }
642
+ const shutdown = requestServerShutdown;
643
+ if (!shutdown) {
644
+ admissionGate.reopen();
645
+ throw new Error("idle daemon shutdown was requested before server lifecycle was ready");
646
+ }
647
+ const acquiredLease = maintenanceLeaseToken
648
+ ? maintenanceLease.acquire(maintenanceLeaseToken)
649
+ : false;
650
+ shutdownAccepted = true;
651
+ try {
652
+ void shutdown().catch((error) => {
653
+ shutdownAccepted = false;
654
+ if (acquiredLease && maintenanceLeaseToken) {
655
+ maintenanceLease.release(maintenanceLeaseToken);
656
+ }
657
+ admissionGate.reopen();
658
+ warn(`idle daemon shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
659
+ });
660
+ }
661
+ catch (error) {
662
+ shutdownAccepted = false;
663
+ if (acquiredLease && maintenanceLeaseToken) {
664
+ maintenanceLease.release(maintenanceLeaseToken);
665
+ }
666
+ admissionGate.reopen();
667
+ throw error;
668
+ }
669
+ return maintenanceLeaseToken
670
+ ? { outcome: "accepted", maintenanceLease: "active" }
671
+ : { outcome: "accepted" };
672
+ })();
673
+ shutdownAttempt = attempt;
674
+ void attempt.finally(() => {
675
+ if (shutdownAttempt === attempt) {
676
+ shutdownAttempt = undefined;
677
+ shutdownAttemptLeaseToken = undefined;
678
+ }
679
+ }).catch(() => undefined);
680
+ return attempt;
681
+ };
466
682
  const daemonManagement = {
467
683
  managementToken,
468
684
  identity: () => ({
@@ -471,6 +687,14 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
471
687
  publicKey: installationIdentity.publicKey,
472
688
  }),
473
689
  buildStatus: daemonBuildStatus,
690
+ releaseMaintenanceLease: (token) => ({
691
+ outcome: (() => {
692
+ const outcome = maintenanceLease.release(token);
693
+ if (outcome === "released")
694
+ admissionGate.reopen();
695
+ return outcome;
696
+ })(),
697
+ }),
474
698
  pluginManagement: {
475
699
  listPlugins: () => pluginManagement.list(),
476
700
  setPluginEnabled: (pluginId, enabled) => pluginManagement.setEnabled(pluginId, enabled),
@@ -478,34 +702,25 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
478
702
  prepareInstallation: (input, context) => pluginManagement.prepareInstallation(input, context),
479
703
  commitInstallation: (token, input, context) => pluginManagement.commitInstallation(token, input, context),
480
704
  cancelPreparation: (token) => pluginManagement.cancelPreparation(token),
705
+ settingsUi: (pluginId) => pluginManagement.settingsUi(pluginId),
706
+ settingsUiAsset: (pluginId, assetGeneration, assetPath) => pluginManagement.settingsUiAsset(pluginId, assetGeneration, assetPath),
707
+ callSettings: (pluginId, generation, method, params, context) => pluginManagement.callSettings(pluginId, generation, method, params, context),
481
708
  dispose: () => pluginManagement.dispose(),
482
709
  },
710
+ pluginMarketplaces: {
711
+ list: () => listPluginMarketplaces().map(toMarketplaceItem),
712
+ add: async (input, context) => toMarketplaceItem(await addPluginMarketplace(input.source, {
713
+ ...(input.alias ? { alias: input.alias } : {}),
714
+ signal: context.signal,
715
+ })),
716
+ refresh: async (id, context) => toMarketplaceItem(await refreshPluginMarketplace(id, {
717
+ signal: context.signal,
718
+ })),
719
+ remove: (id) => removePluginMarketplace(id),
720
+ },
483
721
  browserArtifacts: browserArtifactManagement,
484
722
  maintenance: maintenanceManagement,
485
- shutdownIfIdle: () => {
486
- if (shutdownAccepted)
487
- return { outcome: "accepted" };
488
- const activity = {
489
- ...sessionRuntimeServices.sessionRuntimeIndex.activitySnapshot(),
490
- activeTerminals: sessionTerminals.activeTerminalCount(),
491
- activeBrowsers: sessionBrowsers.activeBrowserCount(),
492
- };
493
- if (daemonActivityBlocksShutdown(activity)) {
494
- return { outcome: "busy", activity };
495
- }
496
- shutdownAccepted = true;
497
- const shutdown = requestServerShutdown;
498
- if (!shutdown) {
499
- shutdownAccepted = false;
500
- throw new Error("idle daemon shutdown was requested before server lifecycle was ready");
501
- }
502
- // Start admission fences in this event-loop turn, before another
503
- // request or Direct dispatch can be accepted.
504
- void shutdown().catch((error) => {
505
- warn(`idle daemon shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
506
- });
507
- return { outcome: "accepted" };
508
- },
723
+ shutdownIfIdle,
509
724
  invokePluginCli,
510
725
  };
511
726
  const remoteRuntimeDispatcher = new RemoteRuntimeDispatcher({
@@ -516,6 +731,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
516
731
  terminals: sessionTerminals,
517
732
  browsers: sessionBrowsers,
518
733
  providerClis,
734
+ projects: runtimeProjects,
519
735
  onError: (error) => warn(`Remote Runtime dispatch failed: ${error instanceof Error ? error.message : String(error)}`),
520
736
  });
521
737
  const remoteRuntimeAdmin = directConfig && directEncryptionIdentity
@@ -552,7 +768,6 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
552
768
  };
553
769
  server = await startServer({
554
770
  config: appConfig,
555
- loadInstances: () => resolveChannelInstances(warn),
556
771
  control,
557
772
  sessionLog,
558
773
  sessionRegistry,
@@ -564,6 +779,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
564
779
  runtimeTargetControl,
565
780
  runtimeConnectionResolver: runtimeConnectionManager,
566
781
  localSessionResources: machineSessions,
782
+ localMachineSessions: machineSessions,
567
783
  providerClis,
568
784
  sessionEmulators,
569
785
  sessionLifecycle: sessionResourceLifecycle,
@@ -581,6 +797,11 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
581
797
  registry: desktopBrowserHosts,
582
798
  daemonInstanceId,
583
799
  },
800
+ sessionPortalAuthorization: {
801
+ redeem: redeemSessionPortalTicket,
802
+ authorize: authorizeSessionPortalGrant,
803
+ ownsSession: sessionBelongsToPlugin,
804
+ },
584
805
  });
585
806
  baseShutdown = server.shutdown.bind(server);
586
807
  if (directConfig && directEncryptionIdentity) {
@@ -743,8 +964,10 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
743
964
  // A failed resource shutdown may leave plugin transports alive. Keep
744
965
  // the process-scoped owner lease in that fail-closed state; it will be
745
966
  // released by the OS when the process exits.
746
- if (shutdownComplete)
967
+ if (shutdownComplete) {
747
968
  await owner.release();
969
+ onShutdownComplete?.();
970
+ }
748
971
  }
749
972
  })();
750
973
  return shutdownPromise;