@rynx-ai/daemon 0.1.11-beta.4 → 0.1.11-beta.41

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 (72) hide show
  1. package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
  2. package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
  3. package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
  4. package/bundled-plugins/plugins/lark/dist/host-adapters.js +24 -18
  5. package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
  6. package/bundled-plugins/plugins/lark/dist/runtime.js +6043 -6016
  7. package/bundled-plugins/plugins/lark/dist/settings.js +6003 -5985
  8. package/bundled-plugins/plugins/lark/package.json +1 -1
  9. package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
  10. package/bundled-plugins/plugins/lark/ui/index.html +2 -2
  11. package/dist/agent-file.js +0 -1
  12. package/dist/browser-artifact-management.js +4 -1
  13. package/dist/chrome-for-testing-http.d.ts +9 -0
  14. package/dist/chrome-for-testing-http.js +58 -0
  15. package/dist/chrome-for-testing-release-resolver.js +13 -6
  16. package/dist/chrome-for-testing-runtime.js +4 -3
  17. package/dist/chrome-for-testing-store.d.ts +2 -0
  18. package/dist/chrome-for-testing-store.js +99 -17
  19. package/dist/control-deps.d.ts +6 -2
  20. package/dist/control-deps.js +87 -4
  21. package/dist/control-link-store.js +4 -3
  22. package/dist/daemon-owner.js +4 -7
  23. package/dist/daemon-server.js +42 -17
  24. package/dist/db.d.ts +2 -2
  25. package/dist/db.js +157 -21
  26. package/dist/headless-browser-host.js +397 -41
  27. package/dist/lifecycle.d.ts +2 -1
  28. package/dist/lifecycle.js +4 -1
  29. package/dist/migrations/cleanup-legacy-sessions.js +4 -3
  30. package/dist/plugin-cli.d.ts +10 -0
  31. package/dist/plugin-cli.js +19 -2
  32. package/dist/plugin-host-rpc.d.ts +48 -9
  33. package/dist/plugin-host-rpc.js +593 -81
  34. package/dist/plugin-installer.js +3 -2
  35. package/dist/plugin-management-service.js +0 -2
  36. package/dist/plugin-marketplace.js +2 -1
  37. package/dist/plugin-store.js +2 -4
  38. package/dist/plugin-supervisor.js +0 -4
  39. package/dist/pm2.d.ts +2 -0
  40. package/dist/pm2.js +6 -0
  41. package/dist/remote-runtime-access-store.d.ts +2 -2
  42. package/dist/remote-runtime-access-store.js +6 -9
  43. package/dist/remote-runtime-connection-manager.js +25 -42
  44. package/dist/remote-runtime-target-store.d.ts +2 -2
  45. package/dist/remote-runtime-target-store.js +6 -6
  46. package/dist/session-emulator-binding-store.js +4 -6
  47. package/dist/session-fork-store.d.ts +1 -0
  48. package/dist/session-fork-store.js +85 -30
  49. package/dist/session-launch-operations.js +6 -5
  50. package/dist/session-log-store.d.ts +1 -0
  51. package/dist/session-log-store.js +46 -22
  52. package/dist/session-meta-store.d.ts +5 -0
  53. package/dist/session-meta-store.js +25 -2
  54. package/dist/session-portal-grant-store.d.ts +55 -0
  55. package/dist/session-portal-grant-store.js +160 -0
  56. package/dist/session-resource-store.d.ts +24 -6
  57. package/dist/session-resource-store.js +387 -69
  58. package/dist/session-timeline-projection-store.d.ts +24 -0
  59. package/dist/session-timeline-projection-store.js +53 -0
  60. package/dist/setup-service.d.ts +1 -0
  61. package/dist/setup-service.js +1 -0
  62. package/dist/skills-catalog.d.ts +11 -2
  63. package/dist/skills-catalog.js +317 -10
  64. package/dist/sqlite.d.ts +22 -0
  65. package/dist/sqlite.js +57 -0
  66. package/dist/stdio-epipe-guard.d.ts +1 -1
  67. package/dist/stdio-epipe-guard.js +1 -1
  68. package/dist/system-dependencies.d.ts +22 -0
  69. package/dist/system-dependencies.js +79 -0
  70. package/package.json +17 -12
  71. package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
  72. /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
@@ -6,32 +6,73 @@
6
6
  */
7
7
  import { readFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
9
- import { listAgentSpecs, loadAgentSpec, rynxHome } from "@rynx-ai/core";
9
+ import { listAgentSpecs, loadAgentSpec, rynxHome, scanLocalRuntimesInWorker, } from "@rynx-ai/core";
10
+ import { listRuntimeModels } from "@rynx-ai/server";
10
11
  import { createEmulatorControlDeps } from "@rynx-ai/emulator";
11
12
  import { removeAgentSpec, writeAgentSpec } from "./agent-file.js";
12
- import { getCatalogSkill, installCatalogSkill, listCatalogSkills, readCatalogSkillFile, removeCatalogSkill, } from "./skills-catalog.js";
13
+ import { getCatalogSkill, importLocalCatalogSkills, importCatalogSkillsFromArchive, installCatalogSkill, listCatalogSkills, readCatalogSkillFile, removeCatalogSkill, scanLocalCatalogSkillCandidates, } from "./skills-catalog.js";
13
14
  import { createSessionMeta, getSessionMeta, listSessionMetas, removeSessionMeta, setSessionTitle, } from "./session-meta-store.js";
14
15
  import { consumeControlLink } from "./control-link-store.js";
15
- export function buildControlDeps() {
16
+ const RUNTIME_OPTIONS_CACHE_MS = 30_000;
17
+ export function buildControlDeps(config) {
18
+ let runtimeOptionsCache;
19
+ let runtimeOptionsInFlight;
20
+ const listAgentRuntimeOptions = async (options = {}) => {
21
+ const now = Date.now();
22
+ if (!options.refresh && runtimeOptionsCache && runtimeOptionsCache.expiresAt > now) {
23
+ return runtimeOptionsCache.value;
24
+ }
25
+ if (!options.refresh && runtimeOptionsInFlight)
26
+ return runtimeOptionsInFlight;
27
+ const scan = async () => {
28
+ const runtimes = await scanLocalRuntimesInWorker();
29
+ const value = await Promise.all(runtimes.map(async (runtime) => {
30
+ return {
31
+ id: runtime.id,
32
+ name: runtime.displayName,
33
+ binary: runtime.binary,
34
+ ...(runtime.executablePath ? { executablePath: runtime.executablePath } : {}),
35
+ ...(runtime.version ? { version: runtime.version } : {}),
36
+ available: runtime.available,
37
+ models: await listControlAgentModels(config, runtime.id),
38
+ };
39
+ }));
40
+ runtimeOptionsCache = { expiresAt: Date.now() + RUNTIME_OPTIONS_CACHE_MS, value };
41
+ return value;
42
+ };
43
+ runtimeOptionsInFlight = scan();
44
+ try {
45
+ return await runtimeOptionsInFlight;
46
+ }
47
+ finally {
48
+ runtimeOptionsInFlight = undefined;
49
+ }
50
+ };
16
51
  return {
17
52
  // ── declarative agents ───────────────────────────────────────────────
18
53
  listAgents: async () => (await listAgentSpecs()).map((spec) => ({
19
54
  id: spec.id,
20
55
  name: spec.name,
56
+ description: spec.description,
21
57
  runtime: spec.executor?.runtime,
22
- model: spec.executor?.model,
58
+ ...(spec.executor?.model ? { model: spec.executor.model } : {}),
59
+ skillCount: spec.skills?.length ?? 0,
23
60
  })),
24
61
  getAgent: (id) => loadAgentSpec(id),
25
62
  writeAgent: (id, spec) => {
26
63
  writeAgentSpec(id, spec);
27
64
  },
28
65
  removeAgent: (id) => removeAgentSpec(id),
66
+ listAgentRuntimeOptions,
29
67
  // ── skills catalog ───────────────────────────────────────────────────
30
68
  listSkills: () => listCatalogSkills(),
31
69
  getSkill: (name) => getCatalogSkill(name),
32
70
  readSkillFile: (name, file) => readCatalogSkillFile(name, file),
33
71
  installSkill: (recipe) => installCatalogSkill(recipe),
34
72
  removeSkill: (name) => removeCatalogSkill(name),
73
+ scanLocalSkills: (runtime) => scanLocalCatalogSkillCandidates(runtime),
74
+ importLocalSkills: (runtime, keys) => importLocalCatalogSkills(runtime, keys),
75
+ importSkillArchive: (filename, contentBase64) => importCatalogSkillsFromArchive(filename, contentBase64),
35
76
  // ── emulator proxy (single seam: the emulator package owns the surface) ─
36
77
  emulator: createEmulatorControlDeps(),
37
78
  // ── control-plane sessions (meta persisted; status held by the server) ─
@@ -44,6 +85,48 @@ export function buildControlDeps() {
44
85
  tailLogs: (instanceId, lines) => tailLogs(instanceId, lines),
45
86
  };
46
87
  }
88
+ /** Project a Runtime-native model catalog into the control-plane option shape. */
89
+ export async function listControlAgentModels(config, runtime) {
90
+ const catalog = await listRuntimeModels(config, runtime);
91
+ return (catalog?.data ?? [])
92
+ .filter((model) => model.hidden !== true)
93
+ .map((model) => ({
94
+ id: model.model || model.id,
95
+ label: model.displayName || model.model || model.id,
96
+ ...(model.description ? { description: model.description } : {}),
97
+ ...(model.isDefault ? { isDefault: true } : {}),
98
+ ...(model.defaultReasoningEffort
99
+ ? { defaultReasoningEffort: model.defaultReasoningEffort }
100
+ : {}),
101
+ reasoningOptions: model.supportedReasoningEfforts?.length
102
+ ? model.supportedReasoningEfforts.map((effort) => ({
103
+ value: effort.reasoningEffort,
104
+ label: reasoningLabel(effort.reasoningEffort),
105
+ ...(effort.description ? { description: effort.description } : {}),
106
+ }))
107
+ : fallbackReasoningOptions(runtime, model.model || model.id),
108
+ }));
109
+ }
110
+ function fallbackReasoningOptions(runtime, model) {
111
+ const values = runtime === "claude"
112
+ ? model.toLowerCase().includes("opus")
113
+ ? ["low", "medium", "high", "xhigh"]
114
+ : ["low", "medium", "high"]
115
+ : ["none", "minimal", "low", "medium", "high", "xhigh"];
116
+ return values.map((value) => ({ value, label: reasoningLabel(value) }));
117
+ }
118
+ function reasoningLabel(value) {
119
+ return {
120
+ none: "None",
121
+ minimal: "Minimal",
122
+ low: "Low",
123
+ medium: "Medium",
124
+ high: "High",
125
+ xhigh: "Extra high",
126
+ max: "Max",
127
+ ultra: "Ultra",
128
+ }[value] ?? value;
129
+ }
47
130
  /** Tail the daemon's merged pm2 logs, best-effort filtered by instance id. */
48
131
  async function tailLogs(instanceId, lines) {
49
132
  const dir = join(rynxHome(), "logs");
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { db } from "./db.js";
3
+ import { transaction } from "./sqlite.js";
3
4
  const DEFAULT_TTL_MS = 5 * 60_000;
4
5
  const MAX_TTL_MS = 15 * 60_000;
5
6
  /** Create a short-lived, one-use link into the generic session console. */
@@ -23,7 +24,7 @@ export function consumeControlLink(tokenInput) {
23
24
  if (!token || token.length > 512)
24
25
  return null;
25
26
  const now = new Date().toISOString();
26
- const consume = db().transaction(() => {
27
+ const consume = transaction(db(), () => {
27
28
  const row = db()
28
29
  .prepare(`SELECT session_id
29
30
  FROM control_links
@@ -39,9 +40,9 @@ export function consumeControlLink(tokenInput) {
39
40
  return consume.immediate();
40
41
  }
41
42
  export function pruneControlLinks(now = new Date().toISOString()) {
42
- return db()
43
+ return Number(db()
43
44
  .prepare("DELETE FROM control_links WHERE expires_at <= ? OR used_at IS NOT NULL")
44
- .run(now).changes;
45
+ .run(now).changes);
45
46
  }
46
47
  function required(value, label) {
47
48
  const normalized = value?.trim();
@@ -1,8 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
4
5
  import { rynxHome } from "@rynx-ai/core";
5
- import Database from "better-sqlite3";
6
+ import { hasSqlitePrimaryCode, SQLITE_BUSY, SQLITE_LOCKED } from "./sqlite.js";
6
7
  const LOCK_FILE = "daemon-owner.sqlite";
7
8
  const OWNER_FILE = "daemon-owner.json";
8
9
  /**
@@ -18,16 +19,15 @@ export async function acquireDaemonOwner(env = process.env) {
18
19
  const lockPath = path.join(home, LOCK_FILE);
19
20
  const ownerFile = path.join(home, OWNER_FILE);
20
21
  await mkdir(home, { recursive: true, mode: 0o700 });
21
- const connection = new Database(lockPath, { timeout: 0 });
22
+ const connection = new DatabaseSync(lockPath, { timeout: 0 });
22
23
  try {
23
- connection.pragma("busy_timeout = 0");
24
24
  connection.exec("BEGIN EXCLUSIVE");
25
25
  // Force this transaction to hold a write lock even on a brand-new file.
26
26
  connection.exec("CREATE TABLE IF NOT EXISTS daemon_owner_guard (id INTEGER PRIMARY KEY)");
27
27
  }
28
28
  catch (error) {
29
29
  connection.close();
30
- if (hasCode(error, "SQLITE_BUSY") || hasCode(error, "SQLITE_LOCKED")) {
30
+ if (hasSqlitePrimaryCode(error, SQLITE_BUSY) || hasSqlitePrimaryCode(error, SQLITE_LOCKED)) {
31
31
  throw await ownerConflictError(home, ownerFile);
32
32
  }
33
33
  throw error;
@@ -99,6 +99,3 @@ function rollbackAndClose(connection) {
99
99
  connection.close();
100
100
  }
101
101
  }
102
- function hasCode(error, code) {
103
- return typeof error === "object" && error !== null && "code" in error && error.code === code;
104
- }
@@ -17,9 +17,10 @@ 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, MachineSessionServiceFailure, listRuntimeModels, RemoteRuntimeDispatcher, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
20
+ import { createDaemonRuntimeHost, createDesktopFirstSessionBrowserHost, createRunnerSessionTerminalHost, createSessionRuntimeServices, DesktopBrowserHostAbsentError, DesktopBrowserHostRegistry, DirectRuntimeConnectionHub, DirectRuntimeBrowserInspectHostError, MachineSessionService, MachineSessionServiceFailure, RemoteRuntimeDispatcher, SessionPendingInputIndex, SessionBrowserService, SessionBrowserServiceError, SessionBrowserSurfaceCoordinator, SessionEmulatorService, startServer, startDirectRuntimeServer, } from "@rynx-ai/server";
21
+ import { authorizeSessionPortalGrant, inspectSessionPortalGrant, onSessionPortalGrantRevoked, redeemSessionPortalTicket, revokeEmbeddedWritableSessionPortalGrants, } 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";
25
26
  import { MaintenanceLeaseStore } from "./maintenance-lease.js";
@@ -49,7 +50,7 @@ import { SqliteSessionResourceStore } from "./session-resource-store.js";
49
50
  import { createSessionFallbackWorkspace, discardSessionFallbackWorkspace, } from "./session-fallback-workspace.js";
50
51
  import { SessionBrowserCapabilityStore } from "./session-browser-capability-store.js";
51
52
  import { SqliteSessionEmulatorBindingStore } from "./session-emulator-binding-store.js";
52
- import { sessionRegistry } from "./session-meta-store.js";
53
+ import { sessionBelongsToPlugin, sessionRegistry } from "./session-meta-store.js";
53
54
  import { sessionPendingMessageStore } from "./session-pending-message-store.js";
54
55
  import { RemoteRuntimeAccessStore } from "./remote-runtime-access-store.js";
55
56
  import { DaemonRemoteRuntimeAdmin } from "./remote-runtime-admin.js";
@@ -68,13 +69,7 @@ const require = createRequire(import.meta.url);
68
69
  const DAEMON_PACKAGE_VERSION = readDaemonPackageVersion();
69
70
  async function resolveSessionExecutionSnapshot(input) {
70
71
  return {
71
- ...await resolveExecutionSnapshot({
72
- ...input,
73
- resolveDefaultModel: async (provider) => {
74
- const models = await listRuntimeModels(input.config, provider);
75
- return models?.data.find((candidate) => candidate.isDefault)?.model;
76
- },
77
- }),
72
+ ...await resolveExecutionSnapshot(input),
78
73
  pluginSkills: snapshotEnabledPluginSkillSources(),
79
74
  };
80
75
  }
@@ -280,7 +275,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
280
275
  const directConfig = directHostingSupported
281
276
  ? await loadDirectConfig()
282
277
  : undefined;
283
- const control = buildControlDeps();
278
+ const control = buildControlDeps(appConfig);
284
279
  const installationIdentity = await loadOrCreateDaemonInstallationIdentity();
285
280
  const directEncryptionIdentity = directHostingSupported
286
281
  ? await loadOrCreateDaemonDirectRuntimeEncryptionIdentity()
@@ -299,21 +294,26 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
299
294
  "semantic.session.list.v1",
300
295
  "semantic.session.snapshot.v1",
301
296
  "semantic.session.events.v1",
302
- "semantic.session.terminal.v1",
297
+ "semantic.session.terminal.v2",
303
298
  "semantic.session.terminal-start.v1",
304
299
  "semantic.session.interrupt.v1",
305
300
  "semantic.session.agent-options.v1",
306
301
  "semantic.session.launch-options.v2",
302
+ "semantic.session.execution-settings.v1",
307
303
  "semantic.session.create.v1",
308
304
  "semantic.session.fork.v1",
309
305
  "semantic.session.message.v1",
310
- "semantic.session.resources.v1",
306
+ "semantic.session.resources.v2",
307
+ "semantic.session.files.v1",
308
+ "semantic.session.workspace-files.v1",
311
309
  "semantic.session.message-queue.v1",
312
310
  "semantic.session.delete.v1",
313
311
  "semantic.session.interaction.v1",
314
312
  "semantic.project.manage.v1",
315
313
  "semantic.provider-cli.status.v1",
316
- ...(runtimeBrowserSupported ? ["semantic.browser.v1"] : []),
314
+ ...(runtimeBrowserSupported
315
+ ? ["semantic.browser.v1", "semantic.browser.request-headers.v1"]
316
+ : []),
317
317
  ...(runtimeBrowserSupported && directHostingSupported
318
318
  ? [
319
319
  RUNTIME_BROWSER_SURFACE_SEMANTIC_CAPABILITY,
@@ -389,6 +389,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
389
389
  };
390
390
  };
391
391
  let machineSessions;
392
+ let sessionBrowsers;
392
393
  const pluginRuntime = {
393
394
  bindHostServices: (services) => pluginSupervisor.bindHostServices({
394
395
  ...services,
@@ -398,6 +399,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
398
399
  publicKey: installationIdentity.publicKey,
399
400
  sign: installationIdentity.sign,
400
401
  },
402
+ sessionBrowserRequestHeaders: sessionBrowsers,
401
403
  sessionLaunch: {
402
404
  resolve: async (selection) => {
403
405
  const workspace = await resolveSessionWorkspace(runtimeProjects, selection.projectId, selection.managedCwd, selection.workspace);
@@ -410,7 +412,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
410
412
  ...((selection.provider || (!selection.agent && !selection.config))
411
413
  ? { provider: selection.provider ?? appConfig.DEFAULT_RUNTIME }
412
414
  : {}),
413
- ...(selection.model ? { model: selection.model } : {}),
415
+ ...(selection.model !== undefined ? { model: selection.model } : {}),
414
416
  ...(selection.reasoningEffort
415
417
  ? { reasoningEffort: selection.reasoningEffort }
416
418
  : {}),
@@ -419,6 +421,11 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
419
421
  };
420
422
  },
421
423
  },
424
+ sessionExecution: {
425
+ update: async (input) => {
426
+ await machineSessions.updateExecutionSettings(input);
427
+ },
428
+ },
422
429
  sessionFork: (input) => machineSessions.fork(input),
423
430
  admissionOpen,
424
431
  admissionReserve: reserveAdmission,
@@ -431,6 +438,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
431
438
  const directConnections = new DirectRuntimeConnectionHub();
432
439
  const sessionLog = new SqliteSessionLogStore();
433
440
  const sessionResources = new SqliteSessionResourceStore();
441
+ const sessionPendingInputs = new SessionPendingInputIndex();
434
442
  const sessionForks = new SqliteSessionForkStore();
435
443
  const runtimeProjects = new SqliteRuntimeProjectStore();
436
444
  const providerClis = new ProviderCliService();
@@ -443,15 +451,21 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
443
451
  const sessionRuntimeServices = createSessionRuntimeServices({
444
452
  config: appConfig,
445
453
  sessionLog,
454
+ pendingInputs: sessionPendingInputs,
455
+ onPendingInputPersisted: (input) => sessionResources.markMessageMirrored(input.sessionId, input.clientMessageId, {
456
+ responseId: input.responseId,
457
+ messageItemId: input.messageItemId,
458
+ }),
446
459
  sessionContextProvider: browserRunnerSessionContexts,
447
460
  admissionOpen,
448
461
  admissionReserve: reserveAdmission,
462
+ materializeGeneratedImage: (input) => sessionResources.materializeGeneratedImage(input),
449
463
  onMirrorError: (error, sessionId) => warn(`Session mirror failed for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`),
450
464
  });
451
465
  const sessionBrowserHost = appBrowserHostSupervisor
452
466
  ? createSupervisedAppSessionBrowserHost(durableDesktopBrowserHost, appBrowserHostSupervisor)
453
467
  : createDesktopFirstSessionBrowserHost(durableDesktopBrowserHost, createHeadlessBrowserHost());
454
- const sessionBrowsers = new SessionBrowserService({
468
+ sessionBrowsers = new SessionBrowserService({
455
469
  host: sessionBrowserHost,
456
470
  sessions: {
457
471
  exists: async (sessionId) => Boolean(sessionRegistry.get(sessionId) ??
@@ -552,18 +566,21 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
552
566
  list: () => control.listAgents(),
553
567
  },
554
568
  pendingMessages: sessionPendingMessageStore,
569
+ pendingInputs: sessionPendingInputs,
555
570
  resources: sessionResources,
556
571
  forks: sessionForks,
557
572
  execution: {
558
573
  runtime: sessionRuntimeServices.conversationRuntime,
559
- resolve: ({ agent, provider, model, reasoningEffort, cwd }) => resolveSessionExecutionSnapshot({
574
+ resolve: ({ agent, provider, model, reasoningEffort, permissionPreset, cwd }) => resolveSessionExecutionSnapshot({
560
575
  config: appConfig,
561
576
  ...(agent ? { agentName: agent } : {}),
562
577
  ...(provider ? { provider } : {}),
563
578
  ...(model ? { model } : {}),
564
579
  ...(reasoningEffort ? { reasoningEffort } : {}),
580
+ ...(permissionPreset ? { permissionPreset } : {}),
565
581
  cwd,
566
582
  }),
583
+ listModels: (provider) => listControlAgentModels(appConfig, provider),
567
584
  runner: sessionRuntimeServices.runnerManager,
568
585
  },
569
586
  workspace: {
@@ -763,6 +780,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
763
780
  status: async () => asDaemonChromeInspectionStatus(await initializedChromeInspectionManager.status()),
764
781
  configure: async (input) => asDaemonChromeInspectionStatus(await initializedChromeInspectionManager.configure(input)),
765
782
  };
783
+ revokeEmbeddedWritableSessionPortalGrants();
766
784
  server = await startServer({
767
785
  config: appConfig,
768
786
  control,
@@ -794,6 +812,13 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, onShut
794
812
  registry: desktopBrowserHosts,
795
813
  daemonInstanceId,
796
814
  },
815
+ sessionPortalAuthorization: {
816
+ redeem: (ticket, embedOrigin) => redeemSessionPortalTicket(ticket, new Date(), embedOrigin),
817
+ authorize: authorizeSessionPortalGrant,
818
+ inspect: inspectSessionPortalGrant,
819
+ subscribeRevocations: onSessionPortalGrantRevoked,
820
+ ownsSession: sessionBelongsToPlugin,
821
+ },
797
822
  });
798
823
  baseShutdown = server.shutdown.bind(server);
799
824
  if (directConfig && directEncryptionIdentity) {
package/dist/db.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import Database from "better-sqlite3";
1
+ import { DatabaseSync } from "node:sqlite";
2
2
  /** Absolute path to the SQLite file (override via `RYNX_DB`, else `~/.rynx/rynx.db`). */
3
3
  export declare function dbPath(): string;
4
4
  /** Open (or reuse) the database at the current path, migrating + importing on first open. */
5
- export declare function db(): Database.Database;
5
+ export declare function db(): DatabaseSync;
package/dist/db.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * `~/.rynx/rynx.db` — the daemon's SQLite store. Concerns kept apart:
3
- * - `plugin_installations` — source-qualified manifests, capabilities, and immutable packages.
3
+ * - `plugin_installations` — source-qualified manifests and immutable packages.
4
4
  * - `plugin_marketplaces` — catalog registrations; catalog data is never executable.
5
5
  * - `plugin_session_launch_operations` — durable idempotency/tombstones.
6
6
  * - `control_links` — generic, one-use links to the local session console.
7
+ * - `session_portal_tickets` / `session_portal_grants` — hashed, scoped,
8
+ * browser Session Portal authorization.
7
9
  * - `runtime_pairing_claims` / `runtime_client_grants` — one-use enrollment
8
10
  * claims and the durable client identities they issue for direct runtimes.
9
11
  * - `runtime_targets` / `runtime_direct_routes` / `runtime_pending_enrollments`
@@ -16,8 +18,9 @@
16
18
  */
17
19
  import { chmodSync, mkdirSync } from "node:fs";
18
20
  import { dirname, isAbsolute, join, resolve } from "node:path";
21
+ import { DatabaseSync } from "node:sqlite";
19
22
  import { rynxHome } from "@rynx-ai/core";
20
- import Database from "better-sqlite3";
23
+ import { transaction } from "./sqlite.js";
21
24
  const connections = new Map();
22
25
  /** Absolute path to the SQLite file (override via `RYNX_DB`, else `~/.rynx/rynx.db`). */
23
26
  export function dbPath() {
@@ -34,9 +37,7 @@ export function db() {
34
37
  if (cached)
35
38
  return cached;
36
39
  mkdirSync(dirname(path), { recursive: true });
37
- const conn = new Database(path);
38
- conn.pragma("busy_timeout = 5000");
39
- conn.pragma("foreign_keys = ON");
40
+ const conn = new DatabaseSync(path, { timeout: 5000, enableForeignKeyConstraints: true });
40
41
  migrate(conn);
41
42
  // Plugin state and daemon credentials are private.
42
43
  try {
@@ -86,7 +87,6 @@ function migrate(conn) {
86
87
  install_path TEXT NOT NULL,
87
88
  manifest TEXT NOT NULL,
88
89
  integrity TEXT NOT NULL,
89
- capabilities TEXT NOT NULL,
90
90
  runtime_state TEXT,
91
91
  runtime_error TEXT,
92
92
  UNIQUE(marketplace_id, plugin_id)
@@ -100,6 +100,7 @@ function migrate(conn) {
100
100
  status TEXT NOT NULL,
101
101
  data TEXT NOT NULL,
102
102
  created_by TEXT,
103
+ parent_tool_call_id TEXT,
103
104
  created_at INTEGER NOT NULL,
104
105
  UNIQUE(session_id, position)
105
106
  );
@@ -109,8 +110,9 @@ function migrate(conn) {
109
110
  session_id TEXT NOT NULL,
110
111
  client_upload_id TEXT NOT NULL,
111
112
  upload_id TEXT NOT NULL UNIQUE,
113
+ kind TEXT NOT NULL DEFAULT 'image' CHECK(kind IN ('image', 'file')),
112
114
  filename TEXT NOT NULL,
113
- media_type TEXT NOT NULL CHECK(media_type IN ('image/png', 'image/jpeg', 'image/webp')),
115
+ media_type TEXT NOT NULL,
114
116
  byte_length INTEGER NOT NULL,
115
117
  uploaded_bytes INTEGER NOT NULL DEFAULT 0,
116
118
  sha256 TEXT,
@@ -141,6 +143,9 @@ function migrate(conn) {
141
143
  'failed_not_started'
142
144
  )),
143
145
  error TEXT,
146
+ response_id TEXT,
147
+ message_item_id TEXT,
148
+ execution_snapshot TEXT,
144
149
  created_at INTEGER NOT NULL,
145
150
  updated_at INTEGER NOT NULL,
146
151
  PRIMARY KEY(session_id, client_message_id)
@@ -245,6 +250,50 @@ function migrate(conn) {
245
250
  );
246
251
  CREATE INDEX IF NOT EXISTS idx_control_links_expires
247
252
  ON control_links(expires_at);
253
+ CREATE TABLE IF NOT EXISTS session_portal_tickets (
254
+ ticket_hash TEXT PRIMARY KEY,
255
+ plugin_id TEXT NOT NULL,
256
+ scope TEXT NOT NULL CHECK(scope IN ('plugin_sessions', 'single_session')),
257
+ access TEXT NOT NULL CHECK(access IN ('read_only', 'read_write')),
258
+ session_id TEXT,
259
+ created_at TEXT NOT NULL,
260
+ expires_at TEXT NOT NULL,
261
+ used_at TEXT,
262
+ CHECK(
263
+ (scope = 'plugin_sessions' AND session_id IS NULL)
264
+ OR (scope = 'single_session' AND session_id IS NOT NULL)
265
+ )
266
+ );
267
+ CREATE INDEX IF NOT EXISTS idx_session_portal_tickets_expires
268
+ ON session_portal_tickets(expires_at);
269
+ CREATE TABLE IF NOT EXISTS session_portal_grants (
270
+ portal_id TEXT PRIMARY KEY,
271
+ secret_hash TEXT NOT NULL,
272
+ plugin_id TEXT NOT NULL,
273
+ scope TEXT NOT NULL CHECK(scope IN ('plugin_sessions', 'single_session')),
274
+ access TEXT NOT NULL CHECK(access IN ('read_only', 'read_write')),
275
+ session_id TEXT,
276
+ created_at TEXT NOT NULL,
277
+ expires_at TEXT NOT NULL,
278
+ revoked_at TEXT,
279
+ CHECK(
280
+ (scope = 'plugin_sessions' AND session_id IS NULL)
281
+ OR (scope = 'single_session' AND session_id IS NOT NULL)
282
+ )
283
+ );
284
+ CREATE INDEX IF NOT EXISTS idx_session_portal_grants_expires
285
+ ON session_portal_grants(expires_at);
286
+ CREATE TABLE IF NOT EXISTS session_timeline_projections (
287
+ plugin_id TEXT NOT NULL,
288
+ session_id TEXT NOT NULL,
289
+ projection_id TEXT NOT NULL,
290
+ request_hash TEXT NOT NULL,
291
+ response_id TEXT NOT NULL,
292
+ item_id TEXT NOT NULL UNIQUE,
293
+ state TEXT NOT NULL DEFAULT 'pending' CHECK(state IN ('pending', 'completed')),
294
+ created_at TEXT NOT NULL,
295
+ PRIMARY KEY(plugin_id, session_id, projection_id)
296
+ );
248
297
  CREATE TABLE IF NOT EXISTS runtime_pairing_claims (
249
298
  claim_id TEXT PRIMARY KEY,
250
299
  secret_hash TEXT NOT NULL,
@@ -308,7 +357,8 @@ function migrate(conn) {
308
357
  CREATE INDEX IF NOT EXISTS idx_runtime_pending_enrollments_expires
309
358
  ON runtime_pending_enrollments(expires_at);
310
359
  `);
311
- migratePluginCapabilities(conn);
360
+ migrateSessionResourceFiles(conn);
361
+ migratePluginCapabilityDeclarations(conn);
312
362
  migrateSessionSnapshots(conn);
313
363
  // Breaking plugin-runtime cutover: remove obsolete core-owned state.
314
364
  conn.exec(`
@@ -318,6 +368,104 @@ function migrate(conn) {
318
368
  addColumnIfMissing(conn, "plugin_marketplaces", "built_in", "INTEGER NOT NULL DEFAULT 0");
319
369
  addColumnIfMissing(conn, "sessions", "forked_from_session_id", "TEXT");
320
370
  addColumnIfMissing(conn, "plugin_session_launch_operations", "raw_request_hash", "TEXT");
371
+ addColumnIfMissing(conn, "session_message_operations", "response_id", "TEXT");
372
+ addColumnIfMissing(conn, "session_message_operations", "message_item_id", "TEXT");
373
+ addColumnIfMissing(conn, "session_message_operations", "execution_snapshot", "TEXT");
374
+ addColumnIfMissing(conn, "session_items", "parent_tool_call_id", "TEXT");
375
+ addColumnIfMissing(conn, "session_portal_tickets", "grant_handle", "TEXT");
376
+ addColumnIfMissing(conn, "session_portal_tickets", "embed_origin", "TEXT");
377
+ addColumnIfMissing(conn, "session_portal_grants", "grant_handle", "TEXT");
378
+ addColumnIfMissing(conn, "session_portal_grants", "embed_origin", "TEXT");
379
+ addColumnIfMissing(conn, "session_timeline_projections", "state", "TEXT NOT NULL DEFAULT 'pending'");
380
+ conn.exec(`
381
+ CREATE INDEX IF NOT EXISTS idx_session_portal_tickets_grant_handle
382
+ ON session_portal_tickets(plugin_id, grant_handle);
383
+ CREATE INDEX IF NOT EXISTS idx_session_portal_grants_grant_handle
384
+ ON session_portal_grants(plugin_id, grant_handle);
385
+ `);
386
+ }
387
+ /** Rebuild the original image-only table without losing committed resources. */
388
+ function migrateSessionResourceFiles(conn) {
389
+ const columns = conn
390
+ .prepare("PRAGMA table_info(session_resources)")
391
+ .all();
392
+ if (columns.some((column) => column.name === "kind"))
393
+ return;
394
+ conn.exec("PRAGMA foreign_keys = OFF");
395
+ try {
396
+ transaction(conn, () => {
397
+ conn.exec(`
398
+ ALTER TABLE session_message_operation_resources
399
+ RENAME TO session_message_operation_resources_image_v1;
400
+ ALTER TABLE session_resources RENAME TO session_resources_image_v1;
401
+
402
+ CREATE TABLE session_resources (
403
+ id TEXT PRIMARY KEY,
404
+ session_id TEXT NOT NULL,
405
+ client_upload_id TEXT NOT NULL,
406
+ upload_id TEXT NOT NULL UNIQUE,
407
+ kind TEXT NOT NULL DEFAULT 'image' CHECK(kind IN ('image', 'file')),
408
+ filename TEXT NOT NULL,
409
+ media_type TEXT NOT NULL,
410
+ byte_length INTEGER NOT NULL,
411
+ uploaded_bytes INTEGER NOT NULL DEFAULT 0,
412
+ sha256 TEXT,
413
+ width INTEGER,
414
+ height INTEGER,
415
+ state TEXT NOT NULL DEFAULT 'uploading'
416
+ CHECK(state IN ('uploading', 'staged', 'committed')),
417
+ message_item_id TEXT,
418
+ created_at INTEGER NOT NULL,
419
+ committed_at INTEGER,
420
+ UNIQUE(session_id, client_upload_id)
421
+ );
422
+ INSERT INTO session_resources (
423
+ id, session_id, client_upload_id, upload_id, kind, filename, media_type,
424
+ byte_length, uploaded_bytes, sha256, width, height, state,
425
+ message_item_id, created_at, committed_at
426
+ )
427
+ SELECT
428
+ id, session_id, client_upload_id, upload_id, 'image', filename, media_type,
429
+ byte_length, uploaded_bytes, sha256, width, height, state,
430
+ message_item_id, created_at, committed_at
431
+ FROM session_resources_image_v1;
432
+
433
+ CREATE TABLE session_message_operation_resources (
434
+ session_id TEXT NOT NULL,
435
+ client_message_id TEXT NOT NULL,
436
+ resource_id TEXT NOT NULL UNIQUE REFERENCES session_resources(id) ON DELETE CASCADE,
437
+ PRIMARY KEY(session_id, client_message_id, resource_id),
438
+ FOREIGN KEY(session_id, client_message_id)
439
+ REFERENCES session_message_operations(session_id, client_message_id)
440
+ ON DELETE CASCADE
441
+ );
442
+ INSERT INTO session_message_operation_resources
443
+ SELECT * FROM session_message_operation_resources_image_v1;
444
+
445
+ DROP TABLE session_message_operation_resources_image_v1;
446
+ DROP TABLE session_resources_image_v1;
447
+ CREATE INDEX idx_session_resources_session
448
+ ON session_resources(session_id, created_at);
449
+ CREATE INDEX idx_session_resources_message
450
+ ON session_resources(message_item_id);
451
+ `);
452
+ })();
453
+ }
454
+ finally {
455
+ conn.exec("PRAGMA foreign_keys = ON");
456
+ }
457
+ }
458
+ /** Remove the inert permission-declaration columns left by older installations. */
459
+ function migratePluginCapabilityDeclarations(conn) {
460
+ const columns = conn
461
+ .prepare("PRAGMA table_info(plugin_installations)")
462
+ .all();
463
+ const names = new Set(columns.map((column) => column.name));
464
+ for (const legacyColumn of ["capabilities", "grants"]) {
465
+ if (names.has(legacyColumn)) {
466
+ conn.exec(`ALTER TABLE plugin_installations DROP COLUMN ${legacyColumn}`);
467
+ }
468
+ }
321
469
  }
322
470
  /**
323
471
  * Breaking Session cutover: the previous rows contain mutable Agent/Provider
@@ -330,7 +478,7 @@ function migrateSessionSnapshots(conn) {
330
478
  const names = new Set(columns.map((column) => column.name));
331
479
  if (names.has("workspace") && names.has("execution"))
332
480
  return;
333
- conn.transaction(() => {
481
+ transaction(conn, () => {
334
482
  conn.exec(`
335
483
  INSERT OR IGNORE INTO session_snapshot_cleanup (session_id)
336
484
  SELECT id FROM sessions;
@@ -358,18 +506,6 @@ function migrateSessionSnapshots(conn) {
358
506
  `);
359
507
  }).immediate();
360
508
  }
361
- function migratePluginCapabilities(conn) {
362
- const columns = conn
363
- .prepare("PRAGMA table_info(plugin_installations)")
364
- .all();
365
- if (columns.some((column) => column.name === "capabilities"))
366
- return;
367
- if (columns.some((column) => column.name === "grants")) {
368
- conn.exec("ALTER TABLE plugin_installations RENAME COLUMN grants TO capabilities");
369
- return;
370
- }
371
- conn.exec("ALTER TABLE plugin_installations ADD COLUMN capabilities TEXT NOT NULL DEFAULT '[]'");
372
- }
373
509
  /** Idempotently add a column to an existing table (no-op if already present). */
374
510
  function addColumnIfMissing(conn, table, column, ddl) {
375
511
  const cols = conn.prepare(`PRAGMA table_info(${table})`).all();