@rynx-ai/daemon 0.1.10 → 0.1.11-beta.2
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/.rynx-plugin/marketplace.json +10 -0
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +37 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +54 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +504 -0
- package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/index.js +7 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
- package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
- package/bundled-plugins/plugins/lark/dist/lark.js +1977 -0
- package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/runtime.js +131316 -0
- package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/settings.js +126958 -0
- package/bundled-plugins/plugins/lark/package.json +57 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +1 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-D9N-AkLX.js +201 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/index.html +13 -0
- package/dist/bundled-plugins.d.ts +15 -0
- package/dist/bundled-plugins.js +134 -0
- package/dist/cli.js +26 -208
- package/dist/control-deps.js +2 -79
- package/dist/daemon-server.d.ts +4 -1
- package/dist/daemon-server.js +167 -57
- package/dist/db.js +145 -124
- package/dist/index-daemon.js +25 -4
- package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
- package/dist/migrations/cleanup-legacy-sessions.js +33 -15
- package/dist/official-plugins.d.ts +2 -0
- package/dist/official-plugins.js +2 -0
- package/dist/plugin-cli.d.ts +1 -3
- package/dist/plugin-cli.js +56 -45
- package/dist/plugin-host-leases.d.ts +4 -0
- package/dist/plugin-host-leases.js +7 -0
- package/dist/plugin-host-rpc.d.ts +42 -4
- package/dist/plugin-host-rpc.js +144 -34
- package/dist/plugin-installer.d.ts +24 -55
- package/dist/plugin-installer.js +202 -415
- package/dist/plugin-management-service.d.ts +30 -23
- package/dist/plugin-management-service.js +290 -105
- package/dist/plugin-marketplace.d.ts +35 -0
- package/dist/plugin-marketplace.js +299 -0
- package/dist/plugin-package.d.ts +22 -24
- package/dist/plugin-package.js +520 -571
- package/dist/plugin-skill-context.d.ts +4 -0
- package/dist/plugin-skill-context.js +35 -0
- package/dist/plugin-store.d.ts +36 -37
- package/dist/plugin-store.js +140 -92
- package/dist/plugin-supervisor.d.ts +45 -125
- package/dist/plugin-supervisor.js +516 -1053
- package/dist/pm2.js +15 -10
- package/dist/runtime-project-store.d.ts +9 -0
- package/dist/runtime-project-store.js +83 -0
- package/dist/session-fallback-workspace.d.ts +22 -0
- package/dist/session-fallback-workspace.js +120 -0
- package/dist/session-fork-store.d.ts +41 -0
- package/dist/session-fork-store.js +288 -0
- package/dist/session-launch-operations.d.ts +14 -2
- package/dist/session-launch-operations.js +34 -2
- package/dist/session-meta-store.d.ts +7 -9
- package/dist/session-meta-store.js +28 -49
- package/dist/setup.js +26 -14
- package/package.json +14 -11
- package/dist/channel-store.d.ts +0 -22
- package/dist/channel-store.js +0 -65
- package/dist/instance-store.d.ts +0 -38
- package/dist/instance-store.js +0 -85
- package/dist/plugin-install-restart.d.ts +0 -13
- package/dist/plugin-install-restart.js +0 -24
- package/dist/plugin-runtime-control.d.ts +0 -2
- package/dist/plugin-runtime-control.js +0 -37
- package/dist/registry.d.ts +0 -10
- package/dist/registry.js +0 -52
package/dist/plugin-cli.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { mkdirSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { createPluginCliInvocationLeaseId, parsePluginCliHostEffect, removePluginRuntimeLease, writePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
4
|
+
import { createPluginCliInvocationLeaseId, removePluginRuntimeLease, writePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
6
5
|
import { db } from "./db.js";
|
|
7
|
-
import {
|
|
6
|
+
import { pluginDataDir, pluginRuntimeCacheDir } from "./plugin-installer.js";
|
|
7
|
+
import { validatePluginPackageSnapshot } from "./plugin-package.js";
|
|
8
|
+
import { getPlugin } from "./plugin-store.js";
|
|
8
9
|
import { loadOrCreateDaemonInstallationIdentitySync } from "./installation-id.js";
|
|
9
10
|
const DEFAULT_TERMINATION_GRACE_MS = 2_000;
|
|
10
11
|
const MAX_TERMINATION_GRACE_MS = 30_000;
|
|
11
|
-
/** Execute one statically declared plugin command
|
|
12
|
+
/** Execute one statically declared, language-neutral plugin command. */
|
|
12
13
|
export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
13
14
|
const hostEnv = options.env ?? process.env;
|
|
14
15
|
const terminationGraceMs = options.terminationGraceMs ?? DEFAULT_TERMINATION_GRACE_MS;
|
|
@@ -21,7 +22,6 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
21
22
|
let terminationReason;
|
|
22
23
|
let forceKillTimer;
|
|
23
24
|
let abortListener;
|
|
24
|
-
const effects = new Map();
|
|
25
25
|
if (options.stdin !== undefined && options.stdio !== "pipe") {
|
|
26
26
|
throw new Error("plugin CLI stdin requires piped stdio");
|
|
27
27
|
}
|
|
@@ -37,6 +37,13 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
37
37
|
if (options.signal?.aborted)
|
|
38
38
|
throw pluginCliAbortError(options.signal.reason);
|
|
39
39
|
const installationIdentity = loadOrCreateDaemonInstallationIdentitySync(hostEnv);
|
|
40
|
+
const verifiedRecord = getPlugin(pluginId);
|
|
41
|
+
if (!verifiedRecord)
|
|
42
|
+
throw new Error(`plugin "${pluginId}" is not installed`);
|
|
43
|
+
const verifiedIntegrity = await validatePluginPackageSnapshot(verifiedRecord.installPath, options.signal ? { signal: options.signal } : {});
|
|
44
|
+
if (verifiedIntegrity !== verifiedRecord.integrity) {
|
|
45
|
+
throw new Error(`plugin package integrity changed for "${verifiedRecord.canonicalId}"`);
|
|
46
|
+
}
|
|
40
47
|
const clearForceKill = () => {
|
|
41
48
|
if (forceKillTimer)
|
|
42
49
|
clearTimeout(forceKillTimer);
|
|
@@ -47,24 +54,14 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
47
54
|
terminationReason ??= reason;
|
|
48
55
|
if (!childIsRunning())
|
|
49
56
|
return;
|
|
50
|
-
|
|
51
|
-
child.kill("SIGTERM");
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
// The close/error event remains authoritative for cleanup.
|
|
55
|
-
}
|
|
57
|
+
signalChildTree(child, "SIGTERM");
|
|
56
58
|
if (forceKillTimer)
|
|
57
59
|
return;
|
|
58
60
|
forceKillTimer = setTimeout(() => {
|
|
59
61
|
forceKillTimer = undefined;
|
|
60
62
|
if (!childIsRunning())
|
|
61
63
|
return;
|
|
62
|
-
|
|
63
|
-
child.kill("SIGKILL");
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// Completion still waits for close/error; no lease is removed early.
|
|
67
|
-
}
|
|
64
|
+
signalChildTree(child, "SIGKILL");
|
|
68
65
|
}, terminationGraceMs);
|
|
69
66
|
forceKillTimer.unref?.();
|
|
70
67
|
};
|
|
@@ -81,14 +78,13 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
81
78
|
const record = getPlugin(pluginId);
|
|
82
79
|
if (!record)
|
|
83
80
|
throw new Error(`plugin "${pluginId}" is not installed`);
|
|
84
|
-
if (
|
|
85
|
-
|
|
81
|
+
if (record.integrity !== verifiedRecord.integrity ||
|
|
82
|
+
record.installPath !== verifiedRecord.installPath ||
|
|
83
|
+
record.updatedAt !== verifiedRecord.updatedAt) {
|
|
84
|
+
throw new Error(`plugin "${pluginId}" changed while its command was being prepared; retry`);
|
|
86
85
|
}
|
|
87
|
-
// `enabled` controls daemon runtime contributions, not explicit CLI calls.
|
|
88
|
-
// Keeping commands available lets a user stop/status a managed service before
|
|
89
|
-
// uninstalling a disabled plugin.
|
|
90
86
|
const command = args[0];
|
|
91
|
-
const declared = record.manifest.
|
|
87
|
+
const declared = record.manifest.commands?.items.find((entry) => entry.id === command);
|
|
92
88
|
if (!command ||
|
|
93
89
|
command === "--help" ||
|
|
94
90
|
command === "-h" ||
|
|
@@ -98,41 +94,40 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
98
94
|
if (!declared) {
|
|
99
95
|
throw new PluginCliUsageError(`plugin "${pluginId}" does not declare command "${command}"\n\n${pluginCommandUsage(record)}`);
|
|
100
96
|
}
|
|
101
|
-
if (!record.manifest.
|
|
102
|
-
throw new Error(`plugin "${pluginId}" declares
|
|
97
|
+
if (!record.manifest.commands) {
|
|
98
|
+
throw new Error(`plugin "${pluginId}" declares no CLI command executable`);
|
|
103
99
|
}
|
|
104
100
|
const packageRoot = realpathSync(record.installPath);
|
|
105
|
-
const cliEntry = realpathSync(path.resolve(packageRoot, record.manifest.
|
|
101
|
+
const cliEntry = realpathSync(path.resolve(packageRoot, record.manifest.commands.command));
|
|
106
102
|
if (!isPathInside(packageRoot, cliEntry) || !statSync(cliEntry).isFile()) {
|
|
107
|
-
throw new Error(`plugin "${pluginId}"
|
|
103
|
+
throw new Error(`plugin "${pluginId}" commands.command resolves outside its package`);
|
|
108
104
|
}
|
|
109
|
-
dataDir =
|
|
105
|
+
dataDir = pluginDataDir(record.marketplaceId, record.pluginId);
|
|
106
|
+
const cacheDir = pluginRuntimeCacheDir(record.marketplaceId, record.pluginId, record.integrity);
|
|
110
107
|
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
111
|
-
|
|
108
|
+
mkdirSync(cacheDir, { recursive: true, mode: 0o700 });
|
|
109
|
+
child = spawn(cliEntry, [...args], {
|
|
112
110
|
cwd: dataDir,
|
|
113
111
|
env: {
|
|
114
|
-
|
|
112
|
+
PATH: hostEnv.PATH,
|
|
113
|
+
HOME: hostEnv.HOME,
|
|
114
|
+
TMPDIR: hostEnv.TMPDIR,
|
|
115
|
+
LANG: hostEnv.LANG,
|
|
115
116
|
RYNX_HOST_INSTALLATION_ID: installationIdentity.installationId,
|
|
116
117
|
RYNX_HOST_INSTALLATION_PROOF_ALGORITHM: installationIdentity.algorithm,
|
|
117
118
|
RYNX_HOST_INSTALLATION_PUBLIC_KEY: installationIdentity.publicKey,
|
|
118
|
-
RYNX_PLUGIN_ID: record.
|
|
119
|
+
RYNX_PLUGIN_ID: record.pluginId,
|
|
120
|
+
RYNX_PLUGIN_INSTALLATION_ID: record.canonicalId,
|
|
119
121
|
RYNX_PLUGIN_PACKAGE_ROOT: packageRoot,
|
|
120
122
|
RYNX_PLUGIN_DATA_DIR: dataDir,
|
|
123
|
+
RYNX_PLUGIN_CACHE_DIR: cacheDir,
|
|
121
124
|
},
|
|
122
125
|
stdio: options.stdio === "pipe"
|
|
123
|
-
? ["pipe", "pipe", "pipe"
|
|
124
|
-
: ["inherit", "inherit", "inherit"
|
|
126
|
+
? ["pipe", "pipe", "pipe"]
|
|
127
|
+
: ["inherit", "inherit", "inherit"],
|
|
128
|
+
detached: process.platform !== "win32",
|
|
125
129
|
});
|
|
126
130
|
completion = new Promise((resolve, reject) => {
|
|
127
|
-
child.on("message", (message) => {
|
|
128
|
-
try {
|
|
129
|
-
const effect = parsePluginCliHostEffect(message);
|
|
130
|
-
effects.set(effect.effect, effect);
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
terminateChild(error instanceof Error ? error : new Error(String(error)));
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
131
|
child.once("error", (error) => {
|
|
137
132
|
if (!childIsRunning()) {
|
|
138
133
|
reject(error);
|
|
@@ -172,7 +167,7 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
172
167
|
leaseId = createPluginCliInvocationLeaseId(child.pid ?? process.pid);
|
|
173
168
|
writePluginRuntimeLease({
|
|
174
169
|
dataDir,
|
|
175
|
-
pluginId: record.
|
|
170
|
+
pluginId: record.pluginId,
|
|
176
171
|
leaseId,
|
|
177
172
|
installPath: packageRoot,
|
|
178
173
|
});
|
|
@@ -193,7 +188,6 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
193
188
|
const code = await completion;
|
|
194
189
|
return {
|
|
195
190
|
code,
|
|
196
|
-
effects: code === 0 ? [...effects.values()] : [],
|
|
197
191
|
...(options.captureOutputBytes !== undefined && stdout && stderr
|
|
198
192
|
? {
|
|
199
193
|
output: {
|
|
@@ -210,6 +204,23 @@ export async function runPluginCliCommand(pluginId, args, options = {}) {
|
|
|
210
204
|
cleanup();
|
|
211
205
|
}
|
|
212
206
|
}
|
|
207
|
+
function signalChildTree(child, signal) {
|
|
208
|
+
if (process.platform !== "win32" && child.pid) {
|
|
209
|
+
try {
|
|
210
|
+
process.kill(-child.pid, signal);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Fall back to the direct child if its process group already exited.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
child.kill(signal);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Completion still waits for close/error; no lease is removed early.
|
|
222
|
+
}
|
|
223
|
+
}
|
|
213
224
|
function pluginCliAbortError(reason) {
|
|
214
225
|
const error = new Error(reason instanceof Error && reason.message ? reason.message : "plugin CLI command aborted");
|
|
215
226
|
error.name = reason instanceof Error && reason.name === "TimeoutError"
|
|
@@ -246,7 +257,7 @@ function captureStream(stream, limit) {
|
|
|
246
257
|
};
|
|
247
258
|
}
|
|
248
259
|
export function pluginCommandUsage(record) {
|
|
249
|
-
const commands = record.manifest.
|
|
260
|
+
const commands = record.manifest.commands?.items ?? [];
|
|
250
261
|
const lines = [`Usage: rynx plugin ${record.name} <command> [args...]`];
|
|
251
262
|
if (commands.length === 0)
|
|
252
263
|
return [...lines, "", "This plugin declares no CLI commands."].join("\n");
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Fixed leases owned by the resident daemon's plugin supervisor. A new daemon
|
|
2
|
+
* may reclaim these only after it has acquired the exclusive RYNX_HOME owner
|
|
3
|
+
* lock and before starting any plugin runners. */
|
|
4
|
+
export declare const PLUGIN_HOST_RUNTIME_LEASE_IDS: readonly ["host-runtime", "host-settings"];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Fixed leases owned by the resident daemon's plugin supervisor. A new daemon
|
|
2
|
+
* may reclaim these only after it has acquired the exclusive RYNX_HOME owner
|
|
3
|
+
* lock and before starting any plugin runners. */
|
|
4
|
+
export const PLUGIN_HOST_RUNTIME_LEASE_IDS = [
|
|
5
|
+
"host-runtime",
|
|
6
|
+
"host-settings",
|
|
7
|
+
];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentCapabilities, type AgentSessionStore, type ConversationRuntime, type SessionRegistry } from "@rynx-ai/core";
|
|
1
|
+
import { type AgentSpec, type AgentCapabilities, type AgentRuntimeId, type AgentSessionStore, type ConversationRuntime, type ResolvedExecutionSnapshot, type SessionRegistry, type SessionWorkspaceSnapshot } from "@rynx-ai/core";
|
|
2
2
|
import { type PluginAgentSummary, type PluginCapability, type PluginHostEvent, type PluginResolvedSessionExecution, type PluginSessionExecutionSnapshot, type PluginSessionInteractionResolveDisposition } from "@rynx-ai/plugin-sdk";
|
|
3
3
|
import type { SessionInteractionResolution } from "@rynx-ai/protocol";
|
|
4
4
|
import { PluginRunnerError } from "@rynx-ai/plugin-runner";
|
|
@@ -12,6 +12,23 @@ export interface PluginAgentCatalogHost {
|
|
|
12
12
|
session: PluginSessionExecutionSnapshot;
|
|
13
13
|
}): Promise<PluginResolvedSessionExecution>;
|
|
14
14
|
}
|
|
15
|
+
/** Mutable launch selectors are accepted only at the Host boundary. The
|
|
16
|
+
* resolver turns them into immutable Session-owned snapshots before a registry
|
|
17
|
+
* row is created; none of these selectors are persisted on the Session. */
|
|
18
|
+
export interface PluginSessionLaunchSelection {
|
|
19
|
+
projectId?: string;
|
|
20
|
+
managedCwd?: string;
|
|
21
|
+
agent?: string;
|
|
22
|
+
config?: AgentSpec;
|
|
23
|
+
provider?: AgentRuntimeId;
|
|
24
|
+
model?: string;
|
|
25
|
+
reasoningEffort?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface PluginResolvedSessionLaunch {
|
|
29
|
+
workspace: SessionWorkspaceSnapshot;
|
|
30
|
+
execution: ResolvedExecutionSnapshot;
|
|
31
|
+
}
|
|
15
32
|
export interface PluginHostServices {
|
|
16
33
|
installationId: string;
|
|
17
34
|
installationProof: {
|
|
@@ -24,16 +41,33 @@ export interface PluginHostServices {
|
|
|
24
41
|
sessionStore: AgentSessionStore;
|
|
25
42
|
sessionRegistry: SessionRegistry;
|
|
26
43
|
agentCatalog: PluginAgentCatalogHost;
|
|
44
|
+
sessionLaunch: {
|
|
45
|
+
resolve(input: PluginSessionLaunchSelection): Promise<PluginResolvedSessionLaunch>;
|
|
46
|
+
};
|
|
27
47
|
interruptSession?(sessionId: string): Promise<boolean>;
|
|
28
48
|
resolveSessionInteraction?(sessionId: string, interactionId: string, resolution: SessionInteractionResolution): Promise<{
|
|
29
49
|
disposition: PluginSessionInteractionResolveDisposition;
|
|
30
50
|
}>;
|
|
51
|
+
sessionFork?(input: {
|
|
52
|
+
sourceSessionId: string;
|
|
53
|
+
operationId: string;
|
|
54
|
+
title?: string;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
sessionId: string;
|
|
57
|
+
disposition: "created" | "replayed";
|
|
58
|
+
}>;
|
|
31
59
|
}
|
|
32
60
|
export interface PluginHostRpcOptions {
|
|
61
|
+
/** Manifest id returned by host.info. */
|
|
33
62
|
pluginId: string;
|
|
63
|
+
/** Marketplace-qualified principal used for ownership and proof scopes. */
|
|
64
|
+
principalId?: string;
|
|
34
65
|
daemonInstanceId: string;
|
|
35
|
-
|
|
66
|
+
capabilities: readonly PluginCapability[];
|
|
36
67
|
services: PluginHostServices;
|
|
68
|
+
/** Canonical plugin-owned root permitted for the Workspace plugin's isolated
|
|
69
|
+
* Session directory. Omitted for every other plugin. */
|
|
70
|
+
managedWorkspaceRoot?: string;
|
|
37
71
|
/** Resolving this promise is the stream's backpressure acknowledgement. */
|
|
38
72
|
emit(event: PluginHostEvent): void | Promise<void>;
|
|
39
73
|
}
|
|
@@ -46,18 +80,22 @@ export interface PluginHostRpcOptions {
|
|
|
46
80
|
*/
|
|
47
81
|
export declare class PluginHostRpc {
|
|
48
82
|
private readonly pluginId;
|
|
83
|
+
private readonly principalId;
|
|
49
84
|
private readonly daemonInstanceId;
|
|
50
|
-
private readonly
|
|
85
|
+
private readonly capabilities;
|
|
51
86
|
private readonly services;
|
|
87
|
+
private readonly managedWorkspaceRoot;
|
|
52
88
|
private readonly emit;
|
|
53
89
|
private readonly activeRuns;
|
|
54
90
|
private readonly activeSessionRuns;
|
|
55
91
|
private readonly activeRunsByOperation;
|
|
56
92
|
private disposed;
|
|
57
93
|
constructor(options: PluginHostRpcOptions);
|
|
58
|
-
call(method: string, params: unknown): Promise<unknown>;
|
|
94
|
+
call(method: string, params: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
59
95
|
dispose(): Promise<void>;
|
|
60
96
|
private ensureSession;
|
|
97
|
+
private resolveManagedWorkspace;
|
|
98
|
+
private sessionEnsureResult;
|
|
61
99
|
private runSession;
|
|
62
100
|
private pumpRun;
|
|
63
101
|
private releaseRun;
|
package/dist/plugin-host-rpc.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AgentRuntimeError, agentSpecSchema, resolvedExecutionSnapshotSchema, sessionWorkspaceSnapshotSchema, SteerController, } from "@rynx-ai/core";
|
|
3
5
|
import { PLUGIN_HOST_MAX_ACTIVE_RUNS, } from "@rynx-ai/plugin-sdk";
|
|
4
6
|
import { PluginRunnerError } from "@rynx-ai/plugin-runner";
|
|
5
|
-
import { ensurePluginSessionLaunch, SessionLaunchOperationConflictError, SessionLaunchOperationTargetDeletedError, } from "./session-launch-operations.js";
|
|
7
|
+
import { ensurePluginSessionLaunch, replayPluginSessionLaunch, SessionLaunchOperationConflictError, SessionLaunchOperationTargetDeletedError, } from "./session-launch-operations.js";
|
|
6
8
|
import { createControlLink } from "./control-link-store.js";
|
|
7
9
|
const MAX_MESSAGE_LENGTH = 1_000_000;
|
|
8
10
|
/** Leaves ample room for the RPC envelope below the runner's 1 MiB line cap. */
|
|
@@ -43,9 +45,11 @@ const METHOD_CAPABILITY = {
|
|
|
43
45
|
*/
|
|
44
46
|
export class PluginHostRpc {
|
|
45
47
|
pluginId;
|
|
48
|
+
principalId;
|
|
46
49
|
daemonInstanceId;
|
|
47
|
-
|
|
50
|
+
capabilities;
|
|
48
51
|
services;
|
|
52
|
+
managedWorkspaceRoot;
|
|
49
53
|
emit;
|
|
50
54
|
activeRuns = new Map();
|
|
51
55
|
activeSessionRuns = new Map();
|
|
@@ -53,14 +57,19 @@ export class PluginHostRpc {
|
|
|
53
57
|
disposed = false;
|
|
54
58
|
constructor(options) {
|
|
55
59
|
this.pluginId = requiredString(options.pluginId, "pluginId", 64);
|
|
60
|
+
this.principalId = requiredString(options.principalId ?? options.pluginId, "principalId", 160);
|
|
56
61
|
this.daemonInstanceId = requiredString(options.daemonInstanceId, "daemonInstanceId", 128);
|
|
57
|
-
this.
|
|
62
|
+
this.capabilities = new Set(options.capabilities);
|
|
58
63
|
this.services = options.services;
|
|
64
|
+
this.managedWorkspaceRoot = options.managedWorkspaceRoot
|
|
65
|
+
? path.resolve(options.managedWorkspaceRoot)
|
|
66
|
+
: undefined;
|
|
59
67
|
this.emit = options.emit;
|
|
60
68
|
}
|
|
61
|
-
async call(method, params) {
|
|
69
|
+
async call(method, params, signal) {
|
|
62
70
|
if (this.disposed)
|
|
63
71
|
throw new PluginHostCallError("HOST_CLOSED", "plugin host is closed");
|
|
72
|
+
throwIfHostCallAborted(signal);
|
|
64
73
|
if (method === "host.info") {
|
|
65
74
|
return {
|
|
66
75
|
pluginId: this.pluginId,
|
|
@@ -71,14 +80,14 @@ export class PluginHostRpc {
|
|
|
71
80
|
publicKey: this.services.installationProof.publicKey,
|
|
72
81
|
},
|
|
73
82
|
protocolVersion: 1,
|
|
74
|
-
|
|
83
|
+
capabilities: [...this.capabilities],
|
|
75
84
|
};
|
|
76
85
|
}
|
|
77
86
|
const capability = METHOD_CAPABILITY[method];
|
|
78
87
|
if (!capability)
|
|
79
88
|
throw new PluginHostCallError("METHOD_NOT_FOUND", `unknown host method: ${method}`);
|
|
80
|
-
if (!this.
|
|
81
|
-
throw new PluginHostCallError("
|
|
89
|
+
if (!this.capabilities.has(capability)) {
|
|
90
|
+
throw new PluginHostCallError("CAPABILITY_NOT_DECLARED", `host method ${method} is missing manifest capability ${capability}`);
|
|
82
91
|
}
|
|
83
92
|
try {
|
|
84
93
|
switch (method) {
|
|
@@ -87,7 +96,7 @@ export class PluginHostRpc {
|
|
|
87
96
|
return {
|
|
88
97
|
algorithm: this.services.installationProof.algorithm,
|
|
89
98
|
publicKey: this.services.installationProof.publicKey,
|
|
90
|
-
signature: this.services.installationProof.sign(this.
|
|
99
|
+
signature: this.services.installationProof.sign(this.principalId, challenge),
|
|
91
100
|
};
|
|
92
101
|
}
|
|
93
102
|
case "agent.catalog.list":
|
|
@@ -97,9 +106,9 @@ export class PluginHostRpc {
|
|
|
97
106
|
case "agent.execution.resolve":
|
|
98
107
|
return await this.resolveAgentExecution(params);
|
|
99
108
|
case "session.ensure":
|
|
100
|
-
return this.ensureSession(params);
|
|
109
|
+
return await this.ensureSession(params);
|
|
101
110
|
case "session.run":
|
|
102
|
-
return await this.runSession(params);
|
|
111
|
+
return await this.runSession(params, signal);
|
|
103
112
|
case "session.steer":
|
|
104
113
|
return this.steerSession(params);
|
|
105
114
|
case "session.interrupt":
|
|
@@ -161,21 +170,79 @@ export class PluginHostRpc {
|
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
}
|
|
164
|
-
ensureSession(params) {
|
|
173
|
+
async ensureSession(params) {
|
|
165
174
|
const input = record(params);
|
|
166
175
|
const scopeId = requiredString(input.scopeId, "scopeId", 512);
|
|
167
176
|
const operationId = requiredString(input.operationId, "operationId", 512);
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
const parsedSelection = sessionLaunchSelection(input.session);
|
|
178
|
+
const selection = parsedSelection.managedCwd
|
|
179
|
+
? {
|
|
180
|
+
...parsedSelection,
|
|
181
|
+
managedCwd: await this.resolveManagedWorkspace(parsedSelection.managedCwd),
|
|
182
|
+
}
|
|
183
|
+
: parsedSelection;
|
|
184
|
+
const requestHash = hashCanonicalJson(selection);
|
|
185
|
+
const replay = replayPluginSessionLaunch({
|
|
186
|
+
pluginId: this.principalId,
|
|
172
187
|
scopeId,
|
|
173
188
|
operationId,
|
|
174
189
|
requestHash,
|
|
175
|
-
session,
|
|
176
190
|
});
|
|
191
|
+
if (replay)
|
|
192
|
+
return this.sessionEnsureResult(replay);
|
|
193
|
+
const resolved = await this.services.sessionLaunch.resolve(selection);
|
|
194
|
+
const ensured = ensurePluginSessionLaunch({
|
|
195
|
+
pluginId: this.principalId,
|
|
196
|
+
scopeId,
|
|
197
|
+
operationId,
|
|
198
|
+
requestHash,
|
|
199
|
+
session: {
|
|
200
|
+
workspace: sessionWorkspaceSnapshotSchema.parse(resolved.workspace),
|
|
201
|
+
execution: resolvedExecutionSnapshotSchema.parse(resolved.execution),
|
|
202
|
+
...(selection.title ? { title: selection.title } : {}),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return this.sessionEnsureResult(ensured);
|
|
206
|
+
}
|
|
207
|
+
async resolveManagedWorkspace(directory) {
|
|
208
|
+
const root = this.managedWorkspaceRoot;
|
|
209
|
+
if (!root) {
|
|
210
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd is reserved for the built-in Workspace plugin");
|
|
211
|
+
}
|
|
212
|
+
if (!path.isAbsolute(directory)) {
|
|
213
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd must be absolute");
|
|
214
|
+
}
|
|
215
|
+
let canonicalRoot;
|
|
216
|
+
let canonicalDirectory;
|
|
217
|
+
try {
|
|
218
|
+
[canonicalRoot, canonicalDirectory] = await Promise.all([
|
|
219
|
+
realpath(root),
|
|
220
|
+
realpath(directory),
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd must be an existing plugin-managed directory");
|
|
225
|
+
}
|
|
226
|
+
const relative = path.relative(canonicalRoot, canonicalDirectory);
|
|
227
|
+
if (relative === ".." ||
|
|
228
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
229
|
+
path.isAbsolute(relative)) {
|
|
230
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd escapes the plugin-managed workspace root");
|
|
231
|
+
}
|
|
232
|
+
return canonicalDirectory;
|
|
233
|
+
}
|
|
234
|
+
sessionEnsureResult(result) {
|
|
235
|
+
const session = this.ownedSession(result.sessionId);
|
|
236
|
+
return {
|
|
237
|
+
...result,
|
|
238
|
+
execution: {
|
|
239
|
+
provider: session.execution.provider,
|
|
240
|
+
model: session.execution.model,
|
|
241
|
+
reasoningEffort: session.execution.reasoningEffort,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
177
244
|
}
|
|
178
|
-
async runSession(params) {
|
|
245
|
+
async runSession(params, signal) {
|
|
179
246
|
const input = record(params);
|
|
180
247
|
const sessionId = requiredSessionId(input);
|
|
181
248
|
const operationId = optionalString(input.operationId, "operationId", 512);
|
|
@@ -191,6 +258,12 @@ export class PluginHostRpc {
|
|
|
191
258
|
}
|
|
192
259
|
const message = requiredText(input.message, "message", MAX_MESSAGE_LENGTH);
|
|
193
260
|
const abort = new AbortController();
|
|
261
|
+
const cancel = () => abort.abort(signal?.reason);
|
|
262
|
+
if (signal?.aborted) {
|
|
263
|
+
cancel();
|
|
264
|
+
throwIfHostCallAborted(signal);
|
|
265
|
+
}
|
|
266
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
194
267
|
const steer = new SteerController();
|
|
195
268
|
const runId = randomUUID();
|
|
196
269
|
const run = {
|
|
@@ -210,23 +283,23 @@ export class PluginHostRpc {
|
|
|
210
283
|
started = await this.services.conversationRuntime.runLive({
|
|
211
284
|
message,
|
|
212
285
|
threadId: sessionId,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
reasoningEffort: optionalString(input.reasoningEffort, "reasoningEffort", 128) ?? session.reasoningEffort,
|
|
216
|
-
cwd: optionalString(input.cwd, "cwd", 16_384),
|
|
217
|
-
agentName: optionalAgentId(input.agent, "agent") ?? session.agent,
|
|
218
|
-
agentSpec: session.config,
|
|
286
|
+
workspace: session.workspace,
|
|
287
|
+
execution: session.execution,
|
|
219
288
|
signal: abort.signal,
|
|
220
289
|
steer,
|
|
221
|
-
developerInstructions: optionalString(input.developerInstructions, "developerInstructions", 200_000),
|
|
222
290
|
});
|
|
223
291
|
}
|
|
224
292
|
catch (error) {
|
|
225
293
|
this.releaseRun(run);
|
|
226
294
|
throw error;
|
|
227
295
|
}
|
|
228
|
-
|
|
296
|
+
finally {
|
|
297
|
+
signal?.removeEventListener("abort", cancel);
|
|
298
|
+
}
|
|
299
|
+
if (signal?.aborted || this.disposed || this.activeRuns.get(runId) !== run) {
|
|
300
|
+
abort.abort(signal?.reason);
|
|
229
301
|
this.releaseRun(run);
|
|
302
|
+
throwIfHostCallAborted(signal);
|
|
230
303
|
throw new PluginHostCallError("HOST_CLOSED", "plugin host is closed");
|
|
231
304
|
}
|
|
232
305
|
// Let the RPC transport write the request response before stream events are
|
|
@@ -438,11 +511,18 @@ export class PluginHostRpc {
|
|
|
438
511
|
}
|
|
439
512
|
async forkSession(params) {
|
|
440
513
|
const input = record(params);
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
this.ownedSession(
|
|
445
|
-
|
|
514
|
+
const sourceSessionId = requiredString(input.sourceSessionId, "sourceSessionId", 512);
|
|
515
|
+
const operationId = requiredString(input.operationId, "operationId", 512);
|
|
516
|
+
const title = optionalString(input.title, "title", 1_000);
|
|
517
|
+
this.ownedSession(sourceSessionId);
|
|
518
|
+
if (!this.services.sessionFork) {
|
|
519
|
+
throw new PluginHostCallError("HOST_CALL_FAILED", "Session fork is unavailable");
|
|
520
|
+
}
|
|
521
|
+
return await this.services.sessionFork({
|
|
522
|
+
sourceSessionId,
|
|
523
|
+
operationId: `${this.principalId}:${operationId}`,
|
|
524
|
+
...(title ? { title } : {}),
|
|
525
|
+
});
|
|
446
526
|
}
|
|
447
527
|
setSessionTitle(params) {
|
|
448
528
|
const input = record(params);
|
|
@@ -466,7 +546,7 @@ export class PluginHostRpc {
|
|
|
466
546
|
this.ownedSession(sessionId);
|
|
467
547
|
const ttlSeconds = optionalPositiveNumber(input.ttlSeconds, "ttlSeconds");
|
|
468
548
|
return createControlLink({
|
|
469
|
-
pluginId: this.
|
|
549
|
+
pluginId: this.principalId,
|
|
470
550
|
sessionId,
|
|
471
551
|
...(ttlSeconds === undefined ? {} : { ttlMs: ttlSeconds * 1_000 }),
|
|
472
552
|
});
|
|
@@ -479,9 +559,24 @@ export class PluginHostRpc {
|
|
|
479
559
|
return session;
|
|
480
560
|
}
|
|
481
561
|
isOwned(session) {
|
|
482
|
-
|
|
562
|
+
const seen = new Set();
|
|
563
|
+
let current = session;
|
|
564
|
+
while (current && !seen.has(current.id)) {
|
|
565
|
+
if (current.source === `plugin:${this.principalId}`)
|
|
566
|
+
return true;
|
|
567
|
+
seen.add(current.id);
|
|
568
|
+
current = current.forkedFromSessionId
|
|
569
|
+
? this.services.sessionRegistry.get(current.forkedFromSessionId)
|
|
570
|
+
: undefined;
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
483
573
|
}
|
|
484
574
|
}
|
|
575
|
+
function throwIfHostCallAborted(signal) {
|
|
576
|
+
if (!signal?.aborted)
|
|
577
|
+
return;
|
|
578
|
+
throw new PluginHostCallError("REQUEST_CANCELLED", "plugin host request was cancelled");
|
|
579
|
+
}
|
|
485
580
|
export class PluginHostCallError extends PluginRunnerError {
|
|
486
581
|
retryable;
|
|
487
582
|
data;
|
|
@@ -512,17 +607,32 @@ function toHostCallError(error) {
|
|
|
512
607
|
}
|
|
513
608
|
return new PluginHostCallError("HOST_CALL_FAILED", error instanceof Error ? error.message : String(error));
|
|
514
609
|
}
|
|
515
|
-
function
|
|
610
|
+
function sessionLaunchSelection(value) {
|
|
516
611
|
const input = record(value);
|
|
517
612
|
assertJsonValue(input, "session");
|
|
613
|
+
const projectId = optionalString(input.projectId, "session.projectId", 512);
|
|
614
|
+
const managedCwd = optionalString(input.managedCwd, "session.managedCwd", 16_384);
|
|
518
615
|
const agent = optionalAgentId(input.agent, "session.agent");
|
|
519
616
|
const config = input.config === undefined ? undefined : agentSpecSchema.parse(input.config);
|
|
617
|
+
const provider = optionalRuntimeValue(input.provider);
|
|
520
618
|
const model = optionalString(input.model, "session.model", 256);
|
|
521
619
|
const reasoningEffort = optionalString(input.reasoningEffort, "session.reasoningEffort", 128);
|
|
522
620
|
const title = optionalString(input.title, "session.title", 1_000);
|
|
621
|
+
if (Number(Boolean(projectId)) + Number(Boolean(managedCwd)) > 1) {
|
|
622
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.projectId and session.managedCwd are mutually exclusive");
|
|
623
|
+
}
|
|
624
|
+
if (Number(Boolean(agent)) +
|
|
625
|
+
Number(Boolean(config)) +
|
|
626
|
+
Number(Boolean(provider)) >
|
|
627
|
+
1) {
|
|
628
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.agent, session.config, and session.provider are mutually exclusive");
|
|
629
|
+
}
|
|
523
630
|
return {
|
|
631
|
+
...(projectId ? { projectId } : {}),
|
|
632
|
+
...(managedCwd ? { managedCwd } : {}),
|
|
524
633
|
...(agent ? { agent } : {}),
|
|
525
634
|
...(config ? { config } : {}),
|
|
635
|
+
...(provider ? { provider } : {}),
|
|
526
636
|
...(model ? { model } : {}),
|
|
527
637
|
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
528
638
|
...(title ? { title } : {}),
|