@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.
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +0 -12
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +3 -1
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +3 -3
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +24 -18
- package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
- package/bundled-plugins/plugins/lark/dist/runtime.js +6043 -6016
- package/bundled-plugins/plugins/lark/dist/settings.js +6003 -5985
- package/bundled-plugins/plugins/lark/package.json +1 -1
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/index.html +2 -2
- package/dist/agent-file.js +0 -1
- package/dist/browser-artifact-management.js +4 -1
- package/dist/chrome-for-testing-http.d.ts +9 -0
- package/dist/chrome-for-testing-http.js +58 -0
- package/dist/chrome-for-testing-release-resolver.js +13 -6
- package/dist/chrome-for-testing-runtime.js +4 -3
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +99 -17
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +87 -4
- package/dist/control-link-store.js +4 -3
- package/dist/daemon-owner.js +4 -7
- package/dist/daemon-server.js +42 -17
- package/dist/db.d.ts +2 -2
- package/dist/db.js +157 -21
- package/dist/headless-browser-host.js +397 -41
- package/dist/lifecycle.d.ts +2 -1
- package/dist/lifecycle.js +4 -1
- package/dist/migrations/cleanup-legacy-sessions.js +4 -3
- package/dist/plugin-cli.d.ts +10 -0
- package/dist/plugin-cli.js +19 -2
- package/dist/plugin-host-rpc.d.ts +48 -9
- package/dist/plugin-host-rpc.js +593 -81
- package/dist/plugin-installer.js +3 -2
- package/dist/plugin-management-service.js +0 -2
- package/dist/plugin-marketplace.js +2 -1
- package/dist/plugin-store.js +2 -4
- package/dist/plugin-supervisor.js +0 -4
- package/dist/pm2.d.ts +2 -0
- package/dist/pm2.js +6 -0
- package/dist/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- package/dist/remote-runtime-connection-manager.js +25 -42
- package/dist/remote-runtime-target-store.d.ts +2 -2
- package/dist/remote-runtime-target-store.js +6 -6
- package/dist/session-emulator-binding-store.js +4 -6
- package/dist/session-fork-store.d.ts +1 -0
- package/dist/session-fork-store.js +85 -30
- package/dist/session-launch-operations.js +6 -5
- package/dist/session-log-store.d.ts +1 -0
- package/dist/session-log-store.js +46 -22
- package/dist/session-meta-store.d.ts +5 -0
- package/dist/session-meta-store.js +25 -2
- package/dist/session-portal-grant-store.d.ts +55 -0
- package/dist/session-portal-grant-store.js +160 -0
- package/dist/session-resource-store.d.ts +24 -6
- package/dist/session-resource-store.js +387 -69
- package/dist/session-timeline-projection-store.d.ts +24 -0
- package/dist/session-timeline-projection-store.js +53 -0
- package/dist/setup-service.d.ts +1 -0
- package/dist/setup-service.js +1 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +317 -10
- package/dist/sqlite.d.ts +22 -0
- package/dist/sqlite.js +57 -0
- package/dist/stdio-epipe-guard.d.ts +1 -1
- package/dist/stdio-epipe-guard.js +1 -1
- package/dist/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +17 -12
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
- /package/bundled-plugins/plugins/lark/ui/assets/{index-D9N-AkLX.js → index-D8Amoe6a.js} +0 -0
package/dist/control-deps.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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()
|
|
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();
|
package/dist/daemon-owner.js
CHANGED
|
@@ -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
|
|
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
|
|
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 (
|
|
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
|
-
}
|
package/dist/daemon-server.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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():
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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();
|