@rynx-ai/daemon 0.1.11-beta.2 → 0.1.11-beta.21
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 +20 -18
- package/bundled-plugins/plugins/lark/dist/lark.js +18 -12
- package/bundled-plugins/plugins/lark/dist/runtime.js +6046 -6022
- package/bundled-plugins/plugins/lark/dist/settings.js +6006 -5988
- 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.d.ts +4 -1
- package/dist/browser-artifact-management.js +22 -11
- package/dist/browser-artifacts.d.ts +1 -0
- package/dist/browser-artifacts.js +1 -0
- package/dist/chrome-for-testing-runtime.d.ts +27 -0
- package/dist/chrome-for-testing-runtime.js +194 -0
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +37 -6
- 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.d.ts +4 -1
- package/dist/daemon-server.js +158 -45
- package/dist/db.d.ts +2 -2
- package/dist/db.js +55 -20
- package/dist/entry-path.d.ts +3 -0
- package/dist/entry-path.js +12 -0
- package/dist/index-daemon.js +3 -87
- package/dist/lifecycle.d.ts +13 -0
- package/dist/lifecycle.js +19 -0
- package/dist/maintenance-lease.d.ts +39 -0
- package/dist/maintenance-lease.js +250 -0
- 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 +35 -6
- package/dist/plugin-host-rpc.js +222 -59
- package/dist/plugin-installer.js +62 -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.d.ts +5 -0
- package/dist/plugin-supervisor.js +218 -33
- package/dist/pm2.d.ts +11 -2
- package/dist/pm2.js +116 -19
- package/dist/process.d.ts +12 -0
- package/dist/process.js +91 -0
- package/dist/remote-runtime-access-store.d.ts +2 -2
- package/dist/remote-runtime-access-store.js +6 -9
- 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.js +24 -8
- package/dist/session-launch-operations.d.ts +10 -1
- package/dist/session-launch-operations.js +21 -11
- package/dist/session-log-store.js +2 -1
- 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 +29 -0
- package/dist/session-portal-grant-store.js +85 -0
- package/dist/session-resource-store.js +3 -2
- package/dist/setup-service.d.ts +21 -0
- package/dist/setup-service.js +39 -0
- package/dist/skills-catalog.d.ts +11 -2
- package/dist/skills-catalog.js +307 -10
- package/dist/sqlite.d.ts +22 -0
- package/dist/sqlite.js +57 -0
- package/dist/system-dependencies.d.ts +22 -0
- package/dist/system-dependencies.js +79 -0
- package/package.json +37 -21
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +0 -1
- package/dist/browser-cli-args.d.ts +0 -20
- package/dist/browser-cli-args.js +0 -100
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -550
- package/dist/control-client.d.ts +0 -122
- package/dist/control-client.js +0 -855
- package/dist/control-endpoint.d.ts +0 -14
- package/dist/control-endpoint.js +0 -124
- package/dist/desktop-browser-host-client.d.ts +0 -35
- package/dist/desktop-browser-host-client.js +0 -415
- package/dist/setup.d.ts +0 -20
- package/dist/setup.js +0 -386
- package/dist/update.d.ts +0 -25
- package/dist/update.js +0 -133
- /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.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export declare function daemonActivityBlocksShutdown(activity: DaemonActivitySna
|
|
|
9
9
|
export interface StartRynxDaemonServerOptions {
|
|
10
10
|
config?: AppConfig;
|
|
11
11
|
warn?: (message: string) => void;
|
|
12
|
+
/** Process-owner callback after every daemon resource and owner lease has
|
|
13
|
+
* shut down successfully. Library callers normally leave this unset. */
|
|
14
|
+
onShutdownComplete?: () => void;
|
|
12
15
|
/** Test/deployment seam; failures roll back the already-listening server. */
|
|
13
16
|
writeEndpoint?: (config: AppConfig, managementToken: string) => Promise<void>;
|
|
14
17
|
/** Test seam for owner-lock failure injection. Production always uses the RYNX_HOME lease. */
|
|
@@ -25,7 +28,7 @@ export interface StartRynxDaemonServerOptions {
|
|
|
25
28
|
/** Test seam for the App-only embedded Chromium process supervisor. */
|
|
26
29
|
createBrowserHostSupervisor?: (options: AppBrowserHostSupervisorOptions) => Pick<AppBrowserHostSupervisor, "ensureConnected" | "restart" | "stop">;
|
|
27
30
|
}
|
|
28
|
-
export declare function startRynxDaemonServer({ config, warn, writeEndpoint, acquireOwner, preparePlugins, loadDirectConfig, startDirectServer, createChromeInspectionManager, createBrowserHostSupervisor, }?: StartRynxDaemonServerOptions): Promise<Awaited<ReturnType<typeof startServer>>>;
|
|
31
|
+
export declare function startRynxDaemonServer({ config, warn, onShutdownComplete, writeEndpoint, acquireOwner, preparePlugins, loadDirectConfig, startDirectServer, createChromeInspectionManager, createBrowserHostSupervisor, }?: StartRynxDaemonServerOptions): Promise<Awaited<ReturnType<typeof startServer>>>;
|
|
29
32
|
export declare function createSupervisedAppSessionBrowserHost(desktop: SessionBrowserHost, supervisor: Pick<AppBrowserHostSupervisor, "ensureConnected" | "restart">): SessionBrowserHost;
|
|
30
33
|
/**
|
|
31
34
|
* Resolve the authenticated semantic target inside its owning Runtime and
|
package/dist/daemon-server.js
CHANGED
|
@@ -11,19 +11,22 @@ 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, resolveExecutionSnapshot, rynxHome, sessionWorkspaceSnapshotSchema, } 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, MachineSessionServiceFailure,
|
|
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";
|
|
26
28
|
import { removeLegacyPluginSkillSnapshot, snapshotEnabledPluginSkillSources, } from "./plugin-skill-context.js";
|
|
29
|
+
import { installedDaemonVersion } from "./entry-path.js";
|
|
27
30
|
import { isChromeForTestingHostSupported } from "./chrome-for-testing-store.js";
|
|
28
31
|
import { ChromeInspectionManager, } from "./chrome-inspection-manager.js";
|
|
29
32
|
import { createHeadlessBrowserHost } from "./headless-browser-host.js";
|
|
@@ -47,7 +50,7 @@ import { SqliteSessionResourceStore } from "./session-resource-store.js";
|
|
|
47
50
|
import { createSessionFallbackWorkspace, discardSessionFallbackWorkspace, } from "./session-fallback-workspace.js";
|
|
48
51
|
import { SessionBrowserCapabilityStore } from "./session-browser-capability-store.js";
|
|
49
52
|
import { SqliteSessionEmulatorBindingStore } from "./session-emulator-binding-store.js";
|
|
50
|
-
import { sessionRegistry } from "./session-meta-store.js";
|
|
53
|
+
import { sessionBelongsToPlugin, sessionRegistry } from "./session-meta-store.js";
|
|
51
54
|
import { sessionPendingMessageStore } from "./session-pending-message-store.js";
|
|
52
55
|
import { RemoteRuntimeAccessStore } from "./remote-runtime-access-store.js";
|
|
53
56
|
import { DaemonRemoteRuntimeAdmin } from "./remote-runtime-admin.js";
|
|
@@ -66,17 +69,14 @@ const require = createRequire(import.meta.url);
|
|
|
66
69
|
const DAEMON_PACKAGE_VERSION = readDaemonPackageVersion();
|
|
67
70
|
async function resolveSessionExecutionSnapshot(input) {
|
|
68
71
|
return {
|
|
69
|
-
...await resolveExecutionSnapshot(
|
|
70
|
-
...input,
|
|
71
|
-
resolveDefaultModel: async (provider) => {
|
|
72
|
-
const models = await listRuntimeModels(input.config, provider);
|
|
73
|
-
return models?.data.find((candidate) => candidate.isDefault)?.model;
|
|
74
|
-
},
|
|
75
|
-
}),
|
|
72
|
+
...await resolveExecutionSnapshot(input),
|
|
76
73
|
pluginSkills: snapshotEnabledPluginSkillSources(),
|
|
77
74
|
};
|
|
78
75
|
}
|
|
79
|
-
async function resolveSessionWorkspace(projects, projectId, managedCwd) {
|
|
76
|
+
async function resolveSessionWorkspace(projects, projectId, managedCwd, workspace) {
|
|
77
|
+
if (workspace) {
|
|
78
|
+
return sessionWorkspaceSnapshotSchema.parse(workspace);
|
|
79
|
+
}
|
|
80
80
|
if (projectId) {
|
|
81
81
|
const project = await projects.get(projectId);
|
|
82
82
|
if (!project) {
|
|
@@ -131,7 +131,8 @@ function toMarketplaceItem(record) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
export function daemonActivityBlocksShutdown(activity) {
|
|
134
|
-
return activity.runningTurns > 0
|
|
134
|
+
return activity.runningTurns > 0
|
|
135
|
+
|| activity.pendingInteractions > 0;
|
|
135
136
|
}
|
|
136
137
|
function defaultWarn(message) {
|
|
137
138
|
console.warn(JSON.stringify({ level: "warn", type: "channel-registry", msg: message }));
|
|
@@ -250,19 +251,31 @@ async function removeControlEndpointForCurrentProcess() {
|
|
|
250
251
|
// Best effort only; stale endpoint files are ignored by the client fallback.
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
|
-
export async function startRynxDaemonServer({ config, warn = defaultWarn, writeEndpoint = writeControlEndpoint, acquireOwner = acquireDaemonOwner, preparePlugins, 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), } = {}) {
|
|
254
255
|
const owner = await acquireOwner();
|
|
255
256
|
let ownerManagedByServer = false;
|
|
256
257
|
try {
|
|
257
258
|
await preparePlugins?.();
|
|
258
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();
|
|
259
272
|
const appBrowserHostLaunch = parseAppBrowserHostLaunchConfig();
|
|
260
273
|
const directHostingSupported = isDaemonDirectRuntimeHostingSupported();
|
|
261
274
|
const runtimeBrowserSupported = appBrowserHostLaunch !== undefined || isChromeForTestingHostSupported();
|
|
262
275
|
const directConfig = directHostingSupported
|
|
263
276
|
? await loadDirectConfig()
|
|
264
277
|
: undefined;
|
|
265
|
-
const control = buildControlDeps();
|
|
278
|
+
const control = buildControlDeps(appConfig);
|
|
266
279
|
const installationIdentity = await loadOrCreateDaemonInstallationIdentity();
|
|
267
280
|
const directEncryptionIdentity = directHostingSupported
|
|
268
281
|
? await loadOrCreateDaemonDirectRuntimeEncryptionIdentity()
|
|
@@ -286,6 +299,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
286
299
|
"semantic.session.interrupt.v1",
|
|
287
300
|
"semantic.session.agent-options.v1",
|
|
288
301
|
"semantic.session.launch-options.v2",
|
|
302
|
+
"semantic.session.execution-settings.v1",
|
|
289
303
|
"semantic.session.create.v1",
|
|
290
304
|
"semantic.session.fork.v1",
|
|
291
305
|
"semantic.session.message.v1",
|
|
@@ -335,6 +349,9 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
335
349
|
}
|
|
336
350
|
const pluginSupervisor = new PluginSupervisor(listPlugins(), {
|
|
337
351
|
daemonInstanceId,
|
|
352
|
+
productVersion: controlEndpointDistributionMetadata().productVersion,
|
|
353
|
+
cliVersion: boundedEnvironmentMetadata(process.env.RYNX_CLI_VERSION, 128)
|
|
354
|
+
?? installedDaemonVersion(),
|
|
338
355
|
log: (entry) => {
|
|
339
356
|
const output = entry.level === "error"
|
|
340
357
|
? console.error
|
|
@@ -379,7 +396,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
379
396
|
},
|
|
380
397
|
sessionLaunch: {
|
|
381
398
|
resolve: async (selection) => {
|
|
382
|
-
const workspace = await resolveSessionWorkspace(runtimeProjects, selection.projectId, selection.managedCwd);
|
|
399
|
+
const workspace = await resolveSessionWorkspace(runtimeProjects, selection.projectId, selection.managedCwd, selection.workspace);
|
|
383
400
|
return {
|
|
384
401
|
workspace,
|
|
385
402
|
execution: await resolveSessionExecutionSnapshot({
|
|
@@ -387,9 +404,9 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
387
404
|
...(selection.agent ? { agentName: selection.agent } : {}),
|
|
388
405
|
...(selection.config ? { spec: selection.config } : {}),
|
|
389
406
|
...((selection.provider || (!selection.agent && !selection.config))
|
|
390
|
-
? { provider: selection.provider ?? appConfig.
|
|
407
|
+
? { provider: selection.provider ?? appConfig.DEFAULT_RUNTIME }
|
|
391
408
|
: {}),
|
|
392
|
-
...(selection.model ? { model: selection.model } : {}),
|
|
409
|
+
...(selection.model !== undefined ? { model: selection.model } : {}),
|
|
393
410
|
...(selection.reasoningEffort
|
|
394
411
|
? { reasoningEffort: selection.reasoningEffort }
|
|
395
412
|
: {}),
|
|
@@ -398,7 +415,14 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
398
415
|
};
|
|
399
416
|
},
|
|
400
417
|
},
|
|
418
|
+
sessionExecution: {
|
|
419
|
+
update: async (input) => {
|
|
420
|
+
await machineSessions.updateExecutionSettings(input);
|
|
421
|
+
},
|
|
422
|
+
},
|
|
401
423
|
sessionFork: (input) => machineSessions.fork(input),
|
|
424
|
+
admissionOpen,
|
|
425
|
+
admissionReserve: reserveAdmission,
|
|
402
426
|
}),
|
|
403
427
|
startPluginRuntimes: pluginSupervisor.startPluginRuntimes.bind(pluginSupervisor),
|
|
404
428
|
statusSnapshot: pluginSupervisor.statusSnapshot.bind(pluginSupervisor),
|
|
@@ -421,6 +445,8 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
421
445
|
config: appConfig,
|
|
422
446
|
sessionLog,
|
|
423
447
|
sessionContextProvider: browserRunnerSessionContexts,
|
|
448
|
+
admissionOpen,
|
|
449
|
+
admissionReserve: reserveAdmission,
|
|
424
450
|
onMirrorError: (error, sessionId) => warn(`Session mirror failed for ${sessionId}: ${error instanceof Error ? error.message : String(error)}`),
|
|
425
451
|
});
|
|
426
452
|
const sessionBrowserHost = appBrowserHostSupervisor
|
|
@@ -432,6 +458,8 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
432
458
|
exists: async (sessionId) => Boolean(sessionRegistry.get(sessionId) ??
|
|
433
459
|
await sessionRuntimeServices.sessionStore.get(sessionId)),
|
|
434
460
|
},
|
|
461
|
+
}, {
|
|
462
|
+
admissionReserve: reserveAdmission,
|
|
435
463
|
});
|
|
436
464
|
const sessionEmulators = control.emulator
|
|
437
465
|
? new SessionEmulatorService({
|
|
@@ -529,24 +557,30 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
529
557
|
forks: sessionForks,
|
|
530
558
|
execution: {
|
|
531
559
|
runtime: sessionRuntimeServices.conversationRuntime,
|
|
532
|
-
resolve: ({ agent, provider, model, reasoningEffort, cwd }) => resolveSessionExecutionSnapshot({
|
|
560
|
+
resolve: ({ agent, provider, model, reasoningEffort, permissionPreset, cwd }) => resolveSessionExecutionSnapshot({
|
|
533
561
|
config: appConfig,
|
|
534
562
|
...(agent ? { agentName: agent } : {}),
|
|
535
563
|
...(provider ? { provider } : {}),
|
|
536
564
|
...(model ? { model } : {}),
|
|
537
565
|
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
566
|
+
...(permissionPreset ? { permissionPreset } : {}),
|
|
538
567
|
cwd,
|
|
539
568
|
}),
|
|
569
|
+
listModels: (provider) => listControlAgentModels(appConfig, provider),
|
|
540
570
|
runner: sessionRuntimeServices.runnerManager,
|
|
541
571
|
},
|
|
542
572
|
workspace: {
|
|
543
573
|
resolve: (projectId) => resolveSessionWorkspace(runtimeProjects, projectId),
|
|
544
574
|
discardUnbound: discardSessionFallbackWorkspace,
|
|
545
575
|
},
|
|
576
|
+
}, {
|
|
577
|
+
admissionOpen,
|
|
578
|
+
admissionReserve: reserveAdmission,
|
|
546
579
|
});
|
|
547
580
|
const directAuthenticator = new DaemonDirectRuntimeAuthenticator(installationIdentity, remoteRuntime, remoteRuntimeAccess);
|
|
548
581
|
const sessionTerminals = createRunnerSessionTerminalHost({
|
|
549
582
|
runnerManager: sessionRuntimeServices.runnerManager,
|
|
583
|
+
admissionReserve: reserveAdmission,
|
|
550
584
|
resolveLiveSession: async (sessionId) => {
|
|
551
585
|
const registered = sessionRegistry.get(sessionId);
|
|
552
586
|
if (!registered ||
|
|
@@ -556,8 +590,95 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
556
590
|
return { cwd: registered.workspace.cwd };
|
|
557
591
|
},
|
|
558
592
|
});
|
|
559
|
-
let shutdownAccepted = false;
|
|
560
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
|
+
};
|
|
561
682
|
const daemonManagement = {
|
|
562
683
|
managementToken,
|
|
563
684
|
identity: () => ({
|
|
@@ -566,6 +687,14 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
566
687
|
publicKey: installationIdentity.publicKey,
|
|
567
688
|
}),
|
|
568
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
|
+
}),
|
|
569
698
|
pluginManagement: {
|
|
570
699
|
listPlugins: () => pluginManagement.list(),
|
|
571
700
|
setPluginEnabled: (pluginId, enabled) => pluginManagement.setEnabled(pluginId, enabled),
|
|
@@ -591,30 +720,7 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
591
720
|
},
|
|
592
721
|
browserArtifacts: browserArtifactManagement,
|
|
593
722
|
maintenance: maintenanceManagement,
|
|
594
|
-
shutdownIfIdle
|
|
595
|
-
if (shutdownAccepted)
|
|
596
|
-
return { outcome: "accepted" };
|
|
597
|
-
const activity = {
|
|
598
|
-
...sessionRuntimeServices.sessionRuntimeIndex.activitySnapshot(),
|
|
599
|
-
activeTerminals: sessionTerminals.activeTerminalCount(),
|
|
600
|
-
activeBrowsers: sessionBrowsers.activeBrowserCount(),
|
|
601
|
-
};
|
|
602
|
-
if (daemonActivityBlocksShutdown(activity)) {
|
|
603
|
-
return { outcome: "busy", activity };
|
|
604
|
-
}
|
|
605
|
-
shutdownAccepted = true;
|
|
606
|
-
const shutdown = requestServerShutdown;
|
|
607
|
-
if (!shutdown) {
|
|
608
|
-
shutdownAccepted = false;
|
|
609
|
-
throw new Error("idle daemon shutdown was requested before server lifecycle was ready");
|
|
610
|
-
}
|
|
611
|
-
// Start admission fences in this event-loop turn, before another
|
|
612
|
-
// request or Direct dispatch can be accepted.
|
|
613
|
-
void shutdown().catch((error) => {
|
|
614
|
-
warn(`idle daemon shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
615
|
-
});
|
|
616
|
-
return { outcome: "accepted" };
|
|
617
|
-
},
|
|
723
|
+
shutdownIfIdle,
|
|
618
724
|
invokePluginCli,
|
|
619
725
|
};
|
|
620
726
|
const remoteRuntimeDispatcher = new RemoteRuntimeDispatcher({
|
|
@@ -691,6 +797,11 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
691
797
|
registry: desktopBrowserHosts,
|
|
692
798
|
daemonInstanceId,
|
|
693
799
|
},
|
|
800
|
+
sessionPortalAuthorization: {
|
|
801
|
+
redeem: redeemSessionPortalTicket,
|
|
802
|
+
authorize: authorizeSessionPortalGrant,
|
|
803
|
+
ownsSession: sessionBelongsToPlugin,
|
|
804
|
+
},
|
|
694
805
|
});
|
|
695
806
|
baseShutdown = server.shutdown.bind(server);
|
|
696
807
|
if (directConfig && directEncryptionIdentity) {
|
|
@@ -853,8 +964,10 @@ export async function startRynxDaemonServer({ config, warn = defaultWarn, writeE
|
|
|
853
964
|
// A failed resource shutdown may leave plugin transports alive. Keep
|
|
854
965
|
// the process-scoped owner lease in that fail-closed state; it will be
|
|
855
966
|
// released by the OS when the process exits.
|
|
856
|
-
if (shutdownComplete)
|
|
967
|
+
if (shutdownComplete) {
|
|
857
968
|
await owner.release();
|
|
969
|
+
onShutdownComplete?.();
|
|
970
|
+
}
|
|
858
971
|
}
|
|
859
972
|
})();
|
|
860
973
|
return shutdownPromise;
|
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;
|