@rynx-ai/daemon 0.1.10 → 0.1.11-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +25 -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 +56 -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 +506 -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 +1975 -0
- package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/runtime.js +131328 -0
- package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/settings.js +126971 -0
- package/bundled-plugins/plugins/lark/package.json +57 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-CGUVAMsv.css +1 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-D8Amoe6a.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/agent-file.js +0 -1
- package/dist/browser-artifact-management.d.ts +4 -1
- package/dist/browser-artifact-management.js +18 -10
- package/dist/browser-artifacts.d.ts +1 -0
- package/dist/browser-artifacts.js +1 -0
- package/dist/bundled-plugins.d.ts +15 -0
- package/dist/bundled-plugins.js +134 -0
- package/dist/chrome-for-testing-runtime.d.ts +27 -0
- package/dist/chrome-for-testing-runtime.js +193 -0
- package/dist/chrome-for-testing-store.d.ts +2 -0
- package/dist/chrome-for-testing-store.js +9 -0
- package/dist/control-deps.d.ts +6 -2
- package/dist/control-deps.js +88 -82
- package/dist/daemon-server.d.ts +7 -1
- package/dist/daemon-server.js +310 -87
- package/dist/db.js +181 -124
- package/dist/entry-path.d.ts +3 -0
- package/dist/entry-path.js +12 -0
- package/dist/index-daemon.js +3 -66
- package/dist/lifecycle.d.ts +12 -0
- package/dist/lifecycle.js +16 -0
- package/dist/maintenance-lease.d.ts +39 -0
- package/dist/maintenance-lease.js +250 -0
- 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 +73 -6
- package/dist/plugin-host-rpc.js +354 -81
- package/dist/plugin-installer.d.ts +24 -55
- package/dist/plugin-installer.js +258 -412
- package/dist/plugin-management-service.d.ts +30 -23
- package/dist/plugin-management-service.js +288 -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 +138 -92
- package/dist/plugin-supervisor.d.ts +50 -125
- package/dist/plugin-supervisor.js +678 -1030
- package/dist/pm2.d.ts +11 -2
- package/dist/pm2.js +130 -28
- package/dist/process.d.ts +12 -0
- package/dist/process.js +91 -0
- 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 +23 -2
- package/dist/session-launch-operations.js +48 -7
- package/dist/session-meta-store.d.ts +12 -9
- package/dist/session-meta-store.js +50 -49
- package/dist/session-portal-grant-store.d.ts +29 -0
- package/dist/session-portal-grant-store.js +84 -0
- 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 -7
- package/dist/system-dependencies.d.ts +26 -0
- package/dist/system-dependencies.js +149 -0
- package/package.json +36 -20
- package/dist/browser-cli-args.d.ts +0 -20
- package/dist/browser-cli-args.js +0 -100
- package/dist/channel-store.d.ts +0 -22
- package/dist/channel-store.js +0 -65
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -732
- 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/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/setup.d.ts +0 -20
- package/dist/setup.js +0 -374
- package/dist/update.d.ts +0 -25
- package/dist/update.js +0 -133
package/dist/plugin-host-rpc.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { realpath, stat } 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";
|
|
9
|
+
import { issueSessionPortalTicket } from "./session-portal-grant-store.js";
|
|
7
10
|
const MAX_MESSAGE_LENGTH = 1_000_000;
|
|
8
11
|
/** Leaves ample room for the RPC envelope below the runner's 1 MiB line cap. */
|
|
9
12
|
export const MAX_PLUGIN_HOST_EVENT_BYTES = 512 * 1024;
|
|
@@ -11,31 +14,8 @@ const MAX_PLUGIN_RUN_FINAL_RESPONSE_BYTES = 64 * 1024;
|
|
|
11
14
|
const MAX_DELTA_CHUNK_CODE_UNITS = 64 * 1024;
|
|
12
15
|
const SESSION_INTERACTION_RESOLUTION_MAX_BYTES = 128 * 1024;
|
|
13
16
|
const AGENT_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
14
|
-
const METHOD_CAPABILITY = {
|
|
15
|
-
"installation.proof.sign": "installation.proof",
|
|
16
|
-
"agent.catalog.list": "agent.catalog.read",
|
|
17
|
-
"agent.catalog.get": "agent.catalog.read",
|
|
18
|
-
"agent.execution.resolve": "agent.catalog.read",
|
|
19
|
-
"session.ensure": "session.provision",
|
|
20
|
-
"session.run": "conversation.run",
|
|
21
|
-
"session.steer": "conversation.run",
|
|
22
|
-
"session.interrupt": "session.cancel",
|
|
23
|
-
"session.interaction.resolve": "conversation.run",
|
|
24
|
-
"session.models": "runtime.catalog.read",
|
|
25
|
-
"session.binding.get": "session.binding.read",
|
|
26
|
-
"session.binding.list": "session.binding.read",
|
|
27
|
-
"session.binding.find": "session.binding.read",
|
|
28
|
-
"session.goal.get": "session.goal.read",
|
|
29
|
-
"session.goal.set": "session.goal.write",
|
|
30
|
-
"session.goal.clear": "session.goal.write",
|
|
31
|
-
"session.fork": "session.fork",
|
|
32
|
-
"session.meta.get": "session.meta.read",
|
|
33
|
-
"session.meta.list": "session.meta.read",
|
|
34
|
-
"session.meta.setTitle": "session.meta.title.write",
|
|
35
|
-
"control.link.issue": "control-link.issue",
|
|
36
|
-
};
|
|
37
17
|
/**
|
|
38
|
-
* One authenticated plugin's
|
|
18
|
+
* One authenticated plugin's Host RPC surface.
|
|
39
19
|
*
|
|
40
20
|
* This adapter is deliberately the only place that maps public string methods
|
|
41
21
|
* to trusted runtime objects. The runner receives neither this class nor its
|
|
@@ -43,43 +23,53 @@ const METHOD_CAPABILITY = {
|
|
|
43
23
|
*/
|
|
44
24
|
export class PluginHostRpc {
|
|
45
25
|
pluginId;
|
|
26
|
+
principalId;
|
|
46
27
|
daemonInstanceId;
|
|
47
|
-
|
|
28
|
+
productVersion;
|
|
29
|
+
cliVersion;
|
|
48
30
|
services;
|
|
31
|
+
managedWorkspaceRoot;
|
|
49
32
|
emit;
|
|
33
|
+
disposeForceAfterMs;
|
|
50
34
|
activeRuns = new Map();
|
|
51
35
|
activeSessionRuns = new Map();
|
|
52
36
|
activeRunsByOperation = new Map();
|
|
53
37
|
disposed = false;
|
|
38
|
+
disposePromise;
|
|
54
39
|
constructor(options) {
|
|
55
40
|
this.pluginId = requiredString(options.pluginId, "pluginId", 64);
|
|
41
|
+
this.principalId = requiredString(options.principalId ?? options.pluginId, "principalId", 160);
|
|
56
42
|
this.daemonInstanceId = requiredString(options.daemonInstanceId, "daemonInstanceId", 128);
|
|
57
|
-
this.
|
|
43
|
+
this.productVersion = requiredString(options.productVersion, "productVersion", 128);
|
|
44
|
+
this.cliVersion = requiredString(options.cliVersion, "cliVersion", 128);
|
|
58
45
|
this.services = options.services;
|
|
46
|
+
this.managedWorkspaceRoot = options.managedWorkspaceRoot
|
|
47
|
+
? path.resolve(options.managedWorkspaceRoot)
|
|
48
|
+
: undefined;
|
|
59
49
|
this.emit = options.emit;
|
|
50
|
+
this.disposeForceAfterMs = options.disposeForceAfterMs ?? 10_000;
|
|
60
51
|
}
|
|
61
|
-
async call(method, params) {
|
|
52
|
+
async call(method, params, signal) {
|
|
62
53
|
if (this.disposed)
|
|
63
54
|
throw new PluginHostCallError("HOST_CLOSED", "plugin host is closed");
|
|
55
|
+
throwIfHostCallAborted(signal);
|
|
64
56
|
if (method === "host.info") {
|
|
65
57
|
return {
|
|
66
58
|
pluginId: this.pluginId,
|
|
67
59
|
installationId: this.services.installationId,
|
|
68
60
|
daemonInstanceId: this.daemonInstanceId,
|
|
61
|
+
productVersion: this.productVersion,
|
|
62
|
+
cliVersion: this.cliVersion,
|
|
69
63
|
installationProof: {
|
|
70
64
|
algorithm: this.services.installationProof.algorithm,
|
|
71
65
|
publicKey: this.services.installationProof.publicKey,
|
|
72
66
|
},
|
|
73
67
|
protocolVersion: 1,
|
|
74
|
-
|
|
68
|
+
...(this.services.sessionPortal
|
|
69
|
+
? { sessionPortal: { ...this.services.sessionPortal } }
|
|
70
|
+
: {}),
|
|
75
71
|
};
|
|
76
72
|
}
|
|
77
|
-
const capability = METHOD_CAPABILITY[method];
|
|
78
|
-
if (!capability)
|
|
79
|
-
throw new PluginHostCallError("METHOD_NOT_FOUND", `unknown host method: ${method}`);
|
|
80
|
-
if (!this.grants.has(capability)) {
|
|
81
|
-
throw new PluginHostCallError("CAPABILITY_DENIED", `host method ${method} requires capability ${capability}`);
|
|
82
|
-
}
|
|
83
73
|
try {
|
|
84
74
|
switch (method) {
|
|
85
75
|
case "installation.proof.sign": {
|
|
@@ -87,7 +77,7 @@ export class PluginHostRpc {
|
|
|
87
77
|
return {
|
|
88
78
|
algorithm: this.services.installationProof.algorithm,
|
|
89
79
|
publicKey: this.services.installationProof.publicKey,
|
|
90
|
-
signature: this.services.installationProof.sign(this.
|
|
80
|
+
signature: this.services.installationProof.sign(this.principalId, challenge),
|
|
91
81
|
};
|
|
92
82
|
}
|
|
93
83
|
case "agent.catalog.list":
|
|
@@ -97,9 +87,9 @@ export class PluginHostRpc {
|
|
|
97
87
|
case "agent.execution.resolve":
|
|
98
88
|
return await this.resolveAgentExecution(params);
|
|
99
89
|
case "session.ensure":
|
|
100
|
-
return this.ensureSession(params);
|
|
90
|
+
return await this.ensureSession(params);
|
|
101
91
|
case "session.run":
|
|
102
|
-
return await this.runSession(params);
|
|
92
|
+
return await this.runSession(params, signal);
|
|
103
93
|
case "session.steer":
|
|
104
94
|
return this.steerSession(params);
|
|
105
95
|
case "session.interrupt":
|
|
@@ -133,6 +123,16 @@ export class PluginHostRpc {
|
|
|
133
123
|
return this.setSessionTitle(params);
|
|
134
124
|
case "control.link.issue":
|
|
135
125
|
return this.issueControlLink(params);
|
|
126
|
+
case "session.portal.plugin.issue": {
|
|
127
|
+
const access = requiredSessionPortalAccess(params);
|
|
128
|
+
return this.issueSessionPortalLink("plugin_sessions", access);
|
|
129
|
+
}
|
|
130
|
+
case "session.portal.session.issue": {
|
|
131
|
+
const input = record(params);
|
|
132
|
+
const sessionId = requiredSessionId(input);
|
|
133
|
+
this.ownedSession(sessionId);
|
|
134
|
+
return this.issueSessionPortalLink("single_session", requiredSessionPortalAccess(input), sessionId);
|
|
135
|
+
}
|
|
136
136
|
default:
|
|
137
137
|
throw new PluginHostCallError("METHOD_NOT_FOUND", `unknown host method: ${method}`);
|
|
138
138
|
}
|
|
@@ -142,44 +142,174 @@ export class PluginHostRpc {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
async dispose() {
|
|
145
|
-
if (this.
|
|
146
|
-
return;
|
|
145
|
+
if (this.disposePromise)
|
|
146
|
+
return this.disposePromise;
|
|
147
147
|
this.disposed = true;
|
|
148
148
|
const runs = [...this.activeRuns.values()];
|
|
149
|
-
this.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
run.
|
|
149
|
+
this.disposePromise = (async () => {
|
|
150
|
+
const stopped = await Promise.allSettled(runs.map(async (run) => {
|
|
151
|
+
if (this.activeRuns.get(run.runId) !== run)
|
|
152
|
+
return;
|
|
153
|
+
run.interruptRequested = true;
|
|
154
|
+
const graceful = run.interruptResult ?? (this.services.interruptSession
|
|
155
|
+
? Promise.resolve().then(() => this.services.interruptSession(run.sessionId))
|
|
156
|
+
: Promise.resolve().then(() => {
|
|
157
|
+
run.abort.abort();
|
|
158
|
+
return false;
|
|
159
|
+
}));
|
|
160
|
+
const authoritative = forceStoppedInterrupt(graceful, this.disposeForceAfterMs, () => this.services.terminateSession?.(run.sessionId) ?? Promise.resolve(false));
|
|
161
|
+
run.interruptResult = authoritative;
|
|
162
|
+
if (!(await authoritative)) {
|
|
163
|
+
throw new Error(`could not prove Session ${run.sessionId} stopped`);
|
|
164
|
+
}
|
|
165
|
+
this.releaseRun(run);
|
|
166
|
+
}));
|
|
167
|
+
const failures = stopped
|
|
168
|
+
.filter((result) => result.status === "rejected")
|
|
169
|
+
.map((result) => result.reason);
|
|
170
|
+
if (failures.length > 0) {
|
|
171
|
+
throw new AggregateError(failures, "plugin Host could not stop every active run");
|
|
161
172
|
}
|
|
162
|
-
}
|
|
173
|
+
})();
|
|
174
|
+
return this.disposePromise;
|
|
175
|
+
}
|
|
176
|
+
async ensureSession(params) {
|
|
177
|
+
return this.withAdmission(() => this.ensureSessionAdmitted(params));
|
|
163
178
|
}
|
|
164
|
-
|
|
179
|
+
async ensureSessionAdmitted(params) {
|
|
165
180
|
const input = record(params);
|
|
166
181
|
const scopeId = requiredString(input.scopeId, "scopeId", 512);
|
|
167
182
|
const operationId = requiredString(input.operationId, "operationId", 512);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
const parsedSelection = sessionLaunchSelection(input.session);
|
|
184
|
+
if (parsedSelection.managedCwd && !this.managedWorkspaceRoot) {
|
|
185
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd is reserved for the built-in Workspace plugin");
|
|
186
|
+
}
|
|
187
|
+
const rawRequestHash = hashCanonicalJson(parsedSelection);
|
|
188
|
+
const replay = replayPluginSessionLaunch({
|
|
189
|
+
pluginId: this.principalId,
|
|
190
|
+
scopeId,
|
|
191
|
+
operationId,
|
|
192
|
+
requestHash: rawRequestHash,
|
|
193
|
+
allowRequestHashMismatch: true,
|
|
194
|
+
});
|
|
195
|
+
if (replay)
|
|
196
|
+
return this.sessionEnsureResult(replay);
|
|
197
|
+
const selection = parsedSelection.managedCwd
|
|
198
|
+
? {
|
|
199
|
+
...parsedSelection,
|
|
200
|
+
managedCwd: await this.resolveManagedWorkspace(parsedSelection.managedCwd),
|
|
201
|
+
}
|
|
202
|
+
: parsedSelection.workspace
|
|
203
|
+
? {
|
|
204
|
+
...parsedSelection,
|
|
205
|
+
workspace: await this.resolveWorkspaceSnapshot(parsedSelection.workspace),
|
|
206
|
+
}
|
|
207
|
+
: parsedSelection;
|
|
208
|
+
const requestHash = hashCanonicalJson(selection);
|
|
209
|
+
const canonicalReplay = replayPluginSessionLaunch({
|
|
210
|
+
pluginId: this.principalId,
|
|
172
211
|
scopeId,
|
|
173
212
|
operationId,
|
|
174
213
|
requestHash,
|
|
175
|
-
session,
|
|
176
214
|
});
|
|
215
|
+
if (canonicalReplay)
|
|
216
|
+
return this.sessionEnsureResult(canonicalReplay);
|
|
217
|
+
const resolved = await this.services.sessionLaunch.resolve(selection);
|
|
218
|
+
const ensured = ensurePluginSessionLaunch({
|
|
219
|
+
pluginId: this.principalId,
|
|
220
|
+
scopeId,
|
|
221
|
+
operationId,
|
|
222
|
+
requestHash,
|
|
223
|
+
rawRequestHash,
|
|
224
|
+
session: {
|
|
225
|
+
workspace: sessionWorkspaceSnapshotSchema.parse(resolved.workspace),
|
|
226
|
+
execution: resolvedExecutionSnapshotSchema.parse(resolved.execution),
|
|
227
|
+
...(selection.title ? { title: selection.title } : {}),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
return this.sessionEnsureResult(ensured);
|
|
231
|
+
}
|
|
232
|
+
async resolveManagedWorkspace(directory) {
|
|
233
|
+
const root = this.managedWorkspaceRoot;
|
|
234
|
+
if (!root) {
|
|
235
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd is reserved for the built-in Workspace plugin");
|
|
236
|
+
}
|
|
237
|
+
if (!path.isAbsolute(directory)) {
|
|
238
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd must be absolute");
|
|
239
|
+
}
|
|
240
|
+
let canonicalRoot;
|
|
241
|
+
let canonicalDirectory;
|
|
242
|
+
try {
|
|
243
|
+
[canonicalRoot, canonicalDirectory] = await Promise.all([
|
|
244
|
+
realpath(root),
|
|
245
|
+
realpath(directory),
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd must be an existing plugin-managed directory");
|
|
250
|
+
}
|
|
251
|
+
const relative = path.relative(canonicalRoot, canonicalDirectory);
|
|
252
|
+
if (relative === ".." ||
|
|
253
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
254
|
+
path.isAbsolute(relative)) {
|
|
255
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.managedCwd escapes the plugin-managed workspace root");
|
|
256
|
+
}
|
|
257
|
+
return canonicalDirectory;
|
|
258
|
+
}
|
|
259
|
+
async resolveWorkspaceSnapshot(workspace) {
|
|
260
|
+
const directories = [workspace.cwd, ...workspace.allowedDirs];
|
|
261
|
+
if (directories.some((directory) => !path.isAbsolute(directory))) {
|
|
262
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.workspace paths must be absolute");
|
|
263
|
+
}
|
|
264
|
+
let canonical;
|
|
265
|
+
try {
|
|
266
|
+
canonical = await Promise.all(directories.map(async (directory) => {
|
|
267
|
+
const resolved = await realpath(directory);
|
|
268
|
+
if (!(await stat(resolved)).isDirectory()) {
|
|
269
|
+
throw new Error("not a directory");
|
|
270
|
+
}
|
|
271
|
+
return resolved;
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.workspace paths must be existing directories");
|
|
276
|
+
}
|
|
277
|
+
const parsed = sessionWorkspaceSnapshotSchema.safeParse({
|
|
278
|
+
cwd: canonical[0],
|
|
279
|
+
allowedDirs: canonical.slice(1),
|
|
280
|
+
});
|
|
281
|
+
if (!parsed.success) {
|
|
282
|
+
throw new PluginHostCallError("INVALID_PARAMS", `invalid session.workspace: ${parsed.error.issues[0]?.message ?? "invalid snapshot"}`);
|
|
283
|
+
}
|
|
284
|
+
return parsed.data;
|
|
285
|
+
}
|
|
286
|
+
sessionEnsureResult(result) {
|
|
287
|
+
const session = this.ownedSession(result.sessionId);
|
|
288
|
+
return {
|
|
289
|
+
...result,
|
|
290
|
+
execution: {
|
|
291
|
+
provider: session.execution.provider,
|
|
292
|
+
model: session.execution.model,
|
|
293
|
+
reasoningEffort: session.execution.reasoningEffort,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async runSession(params, signal) {
|
|
298
|
+
return this.withAdmission(() => this.runSessionAdmitted(params, signal));
|
|
177
299
|
}
|
|
178
|
-
async
|
|
300
|
+
async runSessionAdmitted(params, signal) {
|
|
179
301
|
const input = record(params);
|
|
180
302
|
const sessionId = requiredSessionId(input);
|
|
181
303
|
const operationId = optionalString(input.operationId, "operationId", 512);
|
|
182
|
-
const
|
|
304
|
+
const modelProvided = input.model !== undefined;
|
|
305
|
+
const requestedModel = input.model === null || input.model === ""
|
|
306
|
+
? null
|
|
307
|
+
: optionalString(input.model, "model", 256);
|
|
308
|
+
const reasoningEffortProvided = input.reasoningEffort !== undefined;
|
|
309
|
+
const requestedReasoningEffort = input.reasoningEffort === null || input.reasoningEffort === ""
|
|
310
|
+
? null
|
|
311
|
+
: optionalString(input.reasoningEffort, "reasoningEffort", 64);
|
|
312
|
+
let session = this.ownedSession(sessionId);
|
|
183
313
|
if (operationId && this.activeRunsByOperation.has(operationId)) {
|
|
184
314
|
throw new PluginHostCallError("RUN_ALREADY_ACTIVE", `operation already has an active run: ${operationId}`, true);
|
|
185
315
|
}
|
|
@@ -190,7 +320,26 @@ export class PluginHostRpc {
|
|
|
190
320
|
throw new PluginHostCallError("RUN_LIMIT", "plugin has too many active session runs", true);
|
|
191
321
|
}
|
|
192
322
|
const message = requiredText(input.message, "message", MAX_MESSAGE_LENGTH);
|
|
323
|
+
const nextModel = modelProvided ? requestedModel ?? null : session.execution.model;
|
|
324
|
+
const nextReasoningEffort = reasoningEffortProvided
|
|
325
|
+
? requestedReasoningEffort ?? null
|
|
326
|
+
: session.execution.reasoningEffort;
|
|
327
|
+
if (nextModel !== session.execution.model ||
|
|
328
|
+
nextReasoningEffort !== session.execution.reasoningEffort) {
|
|
329
|
+
await this.services.sessionExecution.update({
|
|
330
|
+
sessionId,
|
|
331
|
+
model: nextModel,
|
|
332
|
+
reasoningEffort: nextReasoningEffort,
|
|
333
|
+
});
|
|
334
|
+
session = this.ownedSession(sessionId);
|
|
335
|
+
}
|
|
193
336
|
const abort = new AbortController();
|
|
337
|
+
const cancel = () => abort.abort(signal?.reason);
|
|
338
|
+
if (signal?.aborted) {
|
|
339
|
+
cancel();
|
|
340
|
+
throwIfHostCallAborted(signal);
|
|
341
|
+
}
|
|
342
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
194
343
|
const steer = new SteerController();
|
|
195
344
|
const runId = randomUUID();
|
|
196
345
|
const run = {
|
|
@@ -210,23 +359,23 @@ export class PluginHostRpc {
|
|
|
210
359
|
started = await this.services.conversationRuntime.runLive({
|
|
211
360
|
message,
|
|
212
361
|
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,
|
|
362
|
+
workspace: session.workspace,
|
|
363
|
+
execution: session.execution,
|
|
219
364
|
signal: abort.signal,
|
|
220
365
|
steer,
|
|
221
|
-
developerInstructions: optionalString(input.developerInstructions, "developerInstructions", 200_000),
|
|
222
366
|
});
|
|
223
367
|
}
|
|
224
368
|
catch (error) {
|
|
225
369
|
this.releaseRun(run);
|
|
226
370
|
throw error;
|
|
227
371
|
}
|
|
228
|
-
|
|
372
|
+
finally {
|
|
373
|
+
signal?.removeEventListener("abort", cancel);
|
|
374
|
+
}
|
|
375
|
+
if (signal?.aborted || this.disposed || this.activeRuns.get(runId) !== run) {
|
|
376
|
+
abort.abort(signal?.reason);
|
|
229
377
|
this.releaseRun(run);
|
|
378
|
+
throwIfHostCallAborted(signal);
|
|
230
379
|
throw new PluginHostCallError("HOST_CLOSED", "plugin host is closed");
|
|
231
380
|
}
|
|
232
381
|
// Let the RPC transport write the request response before stream events are
|
|
@@ -338,6 +487,10 @@ export class PluginHostRpc {
|
|
|
338
487
|
async interruptRun(params) {
|
|
339
488
|
const input = record(params);
|
|
340
489
|
const run = this.activeRunByIdentity(input);
|
|
490
|
+
const forceAfterMs = optionalPositiveNumber(input.forceAfterMs, "forceAfterMs");
|
|
491
|
+
if (forceAfterMs !== undefined && forceAfterMs > 30_000) {
|
|
492
|
+
throw new PluginHostCallError("INVALID_PARAMS", "forceAfterMs must not exceed 30000");
|
|
493
|
+
}
|
|
341
494
|
if (run.interruptResult) {
|
|
342
495
|
return { interrupted: await run.interruptResult };
|
|
343
496
|
}
|
|
@@ -345,7 +498,7 @@ export class PluginHostRpc {
|
|
|
345
498
|
// the authoritative stop is pending wait for its result: true suppresses
|
|
346
499
|
// them, while false/error lets genuine native progress/terminal continue.
|
|
347
500
|
run.interruptRequested = true;
|
|
348
|
-
const
|
|
501
|
+
const graceful = this.services.interruptSession
|
|
349
502
|
// ConversationRuntime wires AbortSignal to this same native operation.
|
|
350
503
|
// Invoke it directly once so this boolean is the authoritative result;
|
|
351
504
|
// aborting first would trigger a duplicate, unobserved interrupt.
|
|
@@ -356,6 +509,9 @@ export class PluginHostRpc {
|
|
|
356
509
|
// effort and cannot prove that native execution stopped.
|
|
357
510
|
return false;
|
|
358
511
|
});
|
|
512
|
+
const interruptResult = forceAfterMs === undefined
|
|
513
|
+
? graceful
|
|
514
|
+
: forceStoppedInterrupt(graceful, forceAfterMs, () => this.services.terminateSession?.(run.sessionId) ?? Promise.resolve(false));
|
|
359
515
|
run.interruptResult = interruptResult;
|
|
360
516
|
try {
|
|
361
517
|
const interrupted = await interruptResult;
|
|
@@ -437,12 +593,40 @@ export class PluginHostRpc {
|
|
|
437
593
|
return await resolve(sessionId, interactionId, resolution);
|
|
438
594
|
}
|
|
439
595
|
async forkSession(params) {
|
|
596
|
+
return this.withAdmission(() => this.forkSessionAdmitted(params));
|
|
597
|
+
}
|
|
598
|
+
async forkSessionAdmitted(params) {
|
|
440
599
|
const input = record(params);
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
this.ownedSession(
|
|
445
|
-
|
|
600
|
+
const sourceSessionId = requiredString(input.sourceSessionId, "sourceSessionId", 512);
|
|
601
|
+
const operationId = requiredString(input.operationId, "operationId", 512);
|
|
602
|
+
const title = optionalString(input.title, "title", 1_000);
|
|
603
|
+
this.ownedSession(sourceSessionId);
|
|
604
|
+
if (!this.services.sessionFork) {
|
|
605
|
+
throw new PluginHostCallError("HOST_CALL_FAILED", "Session fork is unavailable");
|
|
606
|
+
}
|
|
607
|
+
return await this.services.sessionFork({
|
|
608
|
+
sourceSessionId,
|
|
609
|
+
operationId: `${this.principalId}:${operationId}`,
|
|
610
|
+
...(title ? { title } : {}),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
reserveAdmission() {
|
|
614
|
+
const reservation = this.services.admissionReserve?.();
|
|
615
|
+
if (reservation)
|
|
616
|
+
return reservation;
|
|
617
|
+
if (!this.services.admissionReserve && this.services.admissionOpen?.() !== false) {
|
|
618
|
+
return { release() { } };
|
|
619
|
+
}
|
|
620
|
+
throw new PluginHostCallError("HOST_CALL_FAILED", "daemon maintenance is in progress", true);
|
|
621
|
+
}
|
|
622
|
+
async withAdmission(operation) {
|
|
623
|
+
const reservation = this.reserveAdmission();
|
|
624
|
+
try {
|
|
625
|
+
return await operation();
|
|
626
|
+
}
|
|
627
|
+
finally {
|
|
628
|
+
reservation.release();
|
|
629
|
+
}
|
|
446
630
|
}
|
|
447
631
|
setSessionTitle(params) {
|
|
448
632
|
const input = record(params);
|
|
@@ -466,11 +650,22 @@ export class PluginHostRpc {
|
|
|
466
650
|
this.ownedSession(sessionId);
|
|
467
651
|
const ttlSeconds = optionalPositiveNumber(input.ttlSeconds, "ttlSeconds");
|
|
468
652
|
return createControlLink({
|
|
469
|
-
pluginId: this.
|
|
653
|
+
pluginId: this.principalId,
|
|
470
654
|
sessionId,
|
|
471
655
|
...(ttlSeconds === undefined ? {} : { ttlMs: ttlSeconds * 1_000 }),
|
|
472
656
|
});
|
|
473
657
|
}
|
|
658
|
+
issueSessionPortalLink(scope, access, sessionId) {
|
|
659
|
+
if (!this.services.sessionPortal) {
|
|
660
|
+
throw new PluginHostCallError("HOST_CALL_FAILED", "the Session Portal is not configured");
|
|
661
|
+
}
|
|
662
|
+
return issueSessionPortalTicket({
|
|
663
|
+
pluginId: this.principalId,
|
|
664
|
+
scope,
|
|
665
|
+
access,
|
|
666
|
+
...(sessionId ? { sessionId } : {}),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
474
669
|
ownedSession(sessionId) {
|
|
475
670
|
const session = this.services.sessionRegistry.get(sessionId);
|
|
476
671
|
if (!session || !this.isOwned(session)) {
|
|
@@ -479,9 +674,24 @@ export class PluginHostRpc {
|
|
|
479
674
|
return session;
|
|
480
675
|
}
|
|
481
676
|
isOwned(session) {
|
|
482
|
-
|
|
677
|
+
const seen = new Set();
|
|
678
|
+
let current = session;
|
|
679
|
+
while (current && !seen.has(current.id)) {
|
|
680
|
+
if (current.source === `plugin:${this.principalId}`)
|
|
681
|
+
return true;
|
|
682
|
+
seen.add(current.id);
|
|
683
|
+
current = current.forkedFromSessionId
|
|
684
|
+
? this.services.sessionRegistry.get(current.forkedFromSessionId)
|
|
685
|
+
: undefined;
|
|
686
|
+
}
|
|
687
|
+
return false;
|
|
483
688
|
}
|
|
484
689
|
}
|
|
690
|
+
function throwIfHostCallAborted(signal) {
|
|
691
|
+
if (!signal?.aborted)
|
|
692
|
+
return;
|
|
693
|
+
throw new PluginHostCallError("REQUEST_CANCELLED", "plugin host request was cancelled");
|
|
694
|
+
}
|
|
485
695
|
export class PluginHostCallError extends PluginRunnerError {
|
|
486
696
|
retryable;
|
|
487
697
|
data;
|
|
@@ -512,22 +722,53 @@ function toHostCallError(error) {
|
|
|
512
722
|
}
|
|
513
723
|
return new PluginHostCallError("HOST_CALL_FAILED", error instanceof Error ? error.message : String(error));
|
|
514
724
|
}
|
|
515
|
-
function
|
|
725
|
+
function sessionLaunchSelection(value) {
|
|
516
726
|
const input = record(value);
|
|
517
727
|
assertJsonValue(input, "session");
|
|
728
|
+
const projectId = optionalString(input.projectId, "session.projectId", 512);
|
|
729
|
+
const managedCwd = optionalString(input.managedCwd, "session.managedCwd", 16_384);
|
|
730
|
+
const workspace = optionalSessionWorkspace(input.workspace);
|
|
518
731
|
const agent = optionalAgentId(input.agent, "session.agent");
|
|
519
732
|
const config = input.config === undefined ? undefined : agentSpecSchema.parse(input.config);
|
|
520
|
-
const
|
|
733
|
+
const provider = optionalRuntimeValue(input.provider);
|
|
734
|
+
const modelProvided = input.model !== undefined;
|
|
735
|
+
const model = input.model === null || input.model === ""
|
|
736
|
+
? null
|
|
737
|
+
: optionalString(input.model, "session.model", 256);
|
|
521
738
|
const reasoningEffort = optionalString(input.reasoningEffort, "session.reasoningEffort", 128);
|
|
522
739
|
const title = optionalString(input.title, "session.title", 1_000);
|
|
740
|
+
if (Number(Boolean(projectId)) + Number(Boolean(managedCwd)) + Number(Boolean(workspace)) > 1) {
|
|
741
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.projectId, session.managedCwd, and session.workspace are mutually exclusive");
|
|
742
|
+
}
|
|
743
|
+
if (Number(Boolean(agent)) +
|
|
744
|
+
Number(Boolean(config)) +
|
|
745
|
+
Number(Boolean(provider)) >
|
|
746
|
+
1) {
|
|
747
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.agent, session.config, and session.provider are mutually exclusive");
|
|
748
|
+
}
|
|
523
749
|
return {
|
|
750
|
+
...(projectId ? { projectId } : {}),
|
|
751
|
+
...(managedCwd ? { managedCwd } : {}),
|
|
752
|
+
...(workspace ? { workspace } : {}),
|
|
524
753
|
...(agent ? { agent } : {}),
|
|
525
754
|
...(config ? { config } : {}),
|
|
526
|
-
...(
|
|
755
|
+
...(provider ? { provider } : {}),
|
|
756
|
+
...(modelProvided ? { model: model ?? null } : {}),
|
|
527
757
|
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
528
758
|
...(title ? { title } : {}),
|
|
529
759
|
};
|
|
530
760
|
}
|
|
761
|
+
function optionalSessionWorkspace(value) {
|
|
762
|
+
if (value === undefined)
|
|
763
|
+
return undefined;
|
|
764
|
+
const input = record(value);
|
|
765
|
+
const cwd = requiredPathString(input.cwd, "session.workspace.cwd", 16_384);
|
|
766
|
+
if (!Array.isArray(input.allowedDirs) || input.allowedDirs.length === 0) {
|
|
767
|
+
throw new PluginHostCallError("INVALID_PARAMS", "session.workspace.allowedDirs must be a non-empty string array");
|
|
768
|
+
}
|
|
769
|
+
const allowedDirs = input.allowedDirs.map((directory, index) => requiredPathString(directory, `session.workspace.allowedDirs[${index}]`, 16_384));
|
|
770
|
+
return { cwd, allowedDirs };
|
|
771
|
+
}
|
|
531
772
|
function sessionExecutionSnapshot(value) {
|
|
532
773
|
const input = record(value);
|
|
533
774
|
const runtime = optionalRuntimeValue(input.runtime);
|
|
@@ -544,6 +785,12 @@ function sessionExecutionSnapshot(value) {
|
|
|
544
785
|
function requiredSessionId(params) {
|
|
545
786
|
return requiredString(record(params).sessionId, "sessionId", 512);
|
|
546
787
|
}
|
|
788
|
+
function requiredSessionPortalAccess(params) {
|
|
789
|
+
const access = record(params).access;
|
|
790
|
+
if (access === "read_only" || access === "read_write")
|
|
791
|
+
return access;
|
|
792
|
+
throw new PluginHostCallError("INVALID_PARAMS", "access must be read_only or read_write");
|
|
793
|
+
}
|
|
547
794
|
function sessionInteractionResolution(value) {
|
|
548
795
|
const input = record(value);
|
|
549
796
|
const keys = Object.keys(input);
|
|
@@ -608,6 +855,15 @@ function requiredString(value, label, maxLength) {
|
|
|
608
855
|
}
|
|
609
856
|
return value.trim();
|
|
610
857
|
}
|
|
858
|
+
function requiredPathString(value, label, maxLength) {
|
|
859
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
860
|
+
throw new PluginHostCallError("INVALID_PARAMS", `${label} is required`);
|
|
861
|
+
}
|
|
862
|
+
if (value.length > maxLength) {
|
|
863
|
+
throw new PluginHostCallError("INVALID_PARAMS", `${label} is too long`);
|
|
864
|
+
}
|
|
865
|
+
return value;
|
|
866
|
+
}
|
|
611
867
|
function requiredText(value, label, maxLength) {
|
|
612
868
|
if (typeof value !== "string" || !value.trim()) {
|
|
613
869
|
throw new PluginHostCallError("INVALID_PARAMS", `${label} is required`);
|
|
@@ -648,6 +904,23 @@ function optionalPositiveNumber(value, label) {
|
|
|
648
904
|
}
|
|
649
905
|
return value;
|
|
650
906
|
}
|
|
907
|
+
async function forceStoppedInterrupt(graceful, forceAfterMs, terminate) {
|
|
908
|
+
let timer;
|
|
909
|
+
const graceExpired = new Promise((resolve) => {
|
|
910
|
+
timer = setTimeout(() => resolve(false), forceAfterMs);
|
|
911
|
+
timer.unref?.();
|
|
912
|
+
});
|
|
913
|
+
try {
|
|
914
|
+
if (await Promise.race([graceful.catch(() => false), graceExpired])) {
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
finally {
|
|
919
|
+
if (timer)
|
|
920
|
+
clearTimeout(timer);
|
|
921
|
+
}
|
|
922
|
+
return terminate();
|
|
923
|
+
}
|
|
651
924
|
function assertJsonValue(value, label, seen = new Set()) {
|
|
652
925
|
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
653
926
|
return;
|