@mearl/client 2.20.6 → 2.22.0
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/README.md +153 -3
- package/dist/actionTimeouts.js +1 -1
- package/dist/artifacts.d.ts +1 -1
- package/dist/browserRegistry.d.ts +1 -1
- package/dist/check.d.ts +1 -1
- package/dist/cli.js +14 -8
- package/dist/commands.d.ts +23 -14
- package/dist/commands.js +24 -4
- package/dist/{generated-app-profile-definition.d.ts → generated/app-profile-definition.d.ts} +2 -2
- package/dist/{generated-app-profile-runtime-types.d.ts → generated/app-profile-runtime-types.d.ts} +2 -2
- package/dist/{generated-browser-action-protocol.d.ts → generated/browser-action-protocol.d.ts} +11 -1
- package/dist/{generated-browser-action-protocol.js → generated/browser-action-protocol.js} +3 -0
- package/dist/generated/plugin-diagnostics.d.ts +110 -0
- package/dist/generated/plugin-diagnostics.js +1 -0
- package/dist/generated/plugin-execution.d.ts +27 -0
- package/dist/generated/plugin-execution.js +1 -0
- package/dist/generated/plugin-models.d.ts +64 -0
- package/dist/generated/plugin-models.js +1 -0
- package/dist/generated/plugin-pages.d.ts +76 -0
- package/dist/generated/plugin-pages.js +59 -0
- package/dist/generated/plugin-recovery.d.ts +32 -0
- package/dist/generated/plugin-recovery.js +1 -0
- package/dist/generated/plugin-state.d.ts +50 -0
- package/dist/generated/plugin-state.js +1 -0
- package/dist/generated/plugin-tasks.d.ts +74 -0
- package/dist/generated/plugin-tasks.js +7 -0
- package/dist/generated/toolbox-icon.d.ts +10 -0
- package/dist/generated/toolbox-icon.js +37 -0
- package/dist/index.d.ts +15 -4
- package/dist/index.js +7 -1
- package/dist/pageEvalArguments.d.ts +1 -1
- package/dist/pageEvalArguments.js +1 -1
- package/dist/plugin-task-host.d.ts +1 -0
- package/dist/plugin-task-host.js +14 -0
- package/dist/plugin-worker.d.ts +1 -0
- package/dist/plugin-worker.js +17 -0
- package/dist/plugin.d.ts +1 -65
- package/dist/plugin.js +1 -391
- package/dist/pluginCliOutput.d.ts +2 -0
- package/dist/pluginCliOutput.js +7 -0
- package/dist/pluginModelSessionWorker.d.ts +23 -0
- package/dist/pluginModelSessionWorker.js +60 -0
- package/dist/plugins/clients/pluginHostConnection.d.ts +35 -0
- package/dist/plugins/clients/pluginHostConnection.js +256 -0
- package/dist/plugins/clients/pluginModels.d.ts +17 -0
- package/dist/plugins/clients/pluginModels.js +47 -0
- package/dist/plugins/clients/pluginPages.d.ts +21 -0
- package/dist/plugins/clients/pluginPages.js +46 -0
- package/dist/plugins/clients/pluginState.d.ts +26 -0
- package/dist/plugins/clients/pluginState.js +28 -0
- package/dist/plugins/clients/pluginTasks.d.ts +51 -0
- package/dist/plugins/clients/pluginTasks.js +181 -0
- package/dist/{officialPluginInstaller.js → plugins/officialPluginInstaller.js} +1 -3
- package/dist/{pluginPageBundle.js → plugins/pluginPageBundle.js} +74 -17
- package/dist/{pluginRegistry.d.ts → plugins/pluginRegistry.d.ts} +3 -4
- package/dist/{pluginRegistry.js → plugins/pluginRegistry.js} +16 -15
- package/dist/plugins/pluginTaskProtocol.d.ts +47 -0
- package/dist/plugins/pluginTaskProtocol.js +1 -0
- package/dist/plugins/pluginTaskWire.d.ts +4 -0
- package/dist/plugins/pluginTaskWire.js +42 -0
- package/dist/plugins/runtime/pluginCommandWorker.d.ts +2 -0
- package/dist/plugins/runtime/pluginCommandWorker.js +174 -0
- package/dist/plugins/runtime/pluginDiagnostics.d.ts +26 -0
- package/dist/plugins/runtime/pluginDiagnostics.js +309 -0
- package/dist/plugins/runtime/pluginExecution.d.ts +21 -0
- package/dist/plugins/runtime/pluginExecution.js +43 -0
- package/dist/plugins/runtime/pluginExecutor.d.ts +29 -0
- package/dist/plugins/runtime/pluginExecutor.js +223 -0
- package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +55 -0
- package/dist/plugins/runtime/pluginExecutorProtocol.js +47 -0
- package/dist/plugins/runtime/pluginExecutorSupervisor.d.ts +4 -0
- package/dist/plugins/runtime/pluginExecutorSupervisor.js +63 -0
- package/dist/plugins/runtime/pluginHost.d.ts +20 -0
- package/dist/plugins/runtime/pluginHost.js +53 -0
- package/dist/plugins/runtime/pluginHostFiles.d.ts +1 -0
- package/dist/plugins/runtime/pluginHostFiles.js +21 -0
- package/dist/plugins/runtime/pluginHostPaths.d.ts +11 -0
- package/dist/plugins/runtime/pluginHostPaths.js +22 -0
- package/dist/plugins/runtime/pluginHostServer.d.ts +7 -0
- package/dist/plugins/runtime/pluginHostServer.js +252 -0
- package/dist/plugins/runtime/pluginModelCatalog.d.ts +14 -0
- package/dist/plugins/runtime/pluginModelCatalog.js +51 -0
- package/dist/plugins/runtime/pluginModelSessions.d.ts +16 -0
- package/dist/plugins/runtime/pluginModelSessions.js +234 -0
- package/dist/plugins/runtime/pluginPageManager.d.ts +57 -0
- package/dist/plugins/runtime/pluginPageManager.js +877 -0
- package/dist/plugins/runtime/pluginProcessTree.d.ts +2 -0
- package/dist/plugins/runtime/pluginProcessTree.js +31 -0
- package/dist/plugins/runtime/pluginRevision.d.ts +14 -0
- package/dist/plugins/runtime/pluginRevision.js +227 -0
- package/dist/plugins/runtime/pluginRuntimeCleanup.d.ts +2 -0
- package/dist/plugins/runtime/pluginRuntimeCleanup.js +55 -0
- package/dist/plugins/runtime/pluginStateManager.d.ts +24 -0
- package/dist/plugins/runtime/pluginStateManager.js +132 -0
- package/dist/plugins/runtime/pluginStateStore.d.ts +57 -0
- package/dist/plugins/runtime/pluginStateStore.js +396 -0
- package/dist/plugins/runtime/pluginTaskManager.d.ts +92 -0
- package/dist/plugins/runtime/pluginTaskManager.js +1080 -0
- package/dist/plugins/runtime/pluginTaskStore.d.ts +53 -0
- package/dist/plugins/runtime/pluginTaskStore.js +319 -0
- package/dist/plugins/runtime/pythonPlugin.d.ts +31 -0
- package/dist/{pythonPlugin.js → plugins/runtime/pythonPlugin.js} +236 -31
- package/dist/plugins/sdk/plugin.d.ts +118 -0
- package/dist/plugins/sdk/plugin.js +672 -0
- package/dist/{pluginAppProfiles.d.ts → plugins/sdk/pluginAppProfiles.d.ts} +1 -1
- package/dist/{pluginAppProfiles.js → plugins/sdk/pluginAppProfiles.js} +2 -2
- package/dist/plugins/sdk/pluginModelDefinition.d.ts +6 -0
- package/dist/plugins/sdk/pluginModelDefinition.js +75 -0
- package/dist/plugins/sdk/pluginPageDefinition.d.ts +3 -0
- package/dist/plugins/sdk/pluginPageDefinition.js +62 -0
- package/dist/plugins/sdk/pluginRecoveryDefinition.d.ts +3 -0
- package/dist/plugins/sdk/pluginRecoveryDefinition.js +56 -0
- package/dist/plugins/sdk/pluginStateDefinition.d.ts +4 -0
- package/dist/plugins/sdk/pluginStateDefinition.js +68 -0
- package/dist/runtimePaths.d.ts +4 -0
- package/dist/runtimePaths.js +8 -0
- package/dist/socket.d.ts +2 -2
- package/dist/socket.js +10 -6
- package/dist/unified.d.ts +3 -2
- package/dist/unified.js +83 -2
- package/package.json +7 -3
- package/dist/pythonPlugin.d.ts +0 -20
- /package/dist/{generated-app-profile-definition.js → generated/app-profile-definition.js} +0 -0
- /package/dist/{generated-app-profile-runtime-types.js → generated/app-profile-runtime-types.js} +0 -0
- /package/dist/{officialPluginCatalog.d.ts → plugins/officialPluginCatalog.d.ts} +0 -0
- /package/dist/{officialPluginCatalog.js → plugins/officialPluginCatalog.js} +0 -0
- /package/dist/{officialPluginInstaller.d.ts → plugins/officialPluginInstaller.d.ts} +0 -0
- /package/dist/{pluginPageBundle.d.ts → plugins/pluginPageBundle.d.ts} +0 -0
- /package/dist/{pluginSource.d.ts → plugins/pluginSource.d.ts} +0 -0
- /package/dist/{pluginSource.js → plugins/pluginSource.js} +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { closeSync, mkdirSync, openSync } from 'node:fs';
|
|
3
|
+
import { createConnection } from 'node:net';
|
|
4
|
+
import { PLUGIN_TASK_HOST_PATH } from '../../runtimePaths.js';
|
|
5
|
+
import { claimRecord, isProcessOwnerAlive, processIdentity, readRecord, removeInvalidRecord, removeRecordIfPid, rotateLogIfNeeded, } from '@mearl/daemon-core';
|
|
6
|
+
import { pluginHostPaths } from '../runtime/pluginHostPaths.js';
|
|
7
|
+
import { PLUGIN_TASK_PROTOCOL_VERSION } from '../../generated/plugin-tasks.js';
|
|
8
|
+
import { readPluginTaskMessages, writePluginTaskMessage } from '../pluginTaskWire.js';
|
|
9
|
+
import { pluginExecutionError, serializePluginExecutionError, } from '../runtime/pluginExecutorProtocol.js';
|
|
10
|
+
const starting = new Map();
|
|
11
|
+
async function ensureHost(runtimePath) {
|
|
12
|
+
const paths = pluginHostPaths();
|
|
13
|
+
let work = starting.get(paths.registry);
|
|
14
|
+
if (!work) {
|
|
15
|
+
work = (async () => {
|
|
16
|
+
const lease = readRecord(paths.lease);
|
|
17
|
+
if (lease && isProcessOwnerAlive(lease)) {
|
|
18
|
+
if (lease.protocolVersion !== PLUGIN_TASK_PROTOCOL_VERSION)
|
|
19
|
+
throw new Error('Plugin execution service protocol differs; stop the service before updating');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const lock = `${paths.lease}.start`;
|
|
23
|
+
const existing = readRecord(lock);
|
|
24
|
+
if (existing && isProcessOwnerAlive(existing))
|
|
25
|
+
return;
|
|
26
|
+
if (existing)
|
|
27
|
+
removeRecordIfPid(lock, existing.pid);
|
|
28
|
+
else
|
|
29
|
+
removeInvalidRecord(lock);
|
|
30
|
+
if (!claimRecord(lock, { pid: process.pid, processIdentity: processIdentity(process.pid) }))
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const owner = readRecord(paths.lease);
|
|
34
|
+
if (owner && isProcessOwnerAlive(owner))
|
|
35
|
+
return;
|
|
36
|
+
mkdirSync(paths.home, { recursive: true, mode: 0o700 });
|
|
37
|
+
rotateLogIfNeeded(paths.log);
|
|
38
|
+
const log = openSync(paths.log, 'a', 0o600);
|
|
39
|
+
try {
|
|
40
|
+
const child = spawn(process.execPath, [runtimePath], {
|
|
41
|
+
detached: true,
|
|
42
|
+
stdio: ['ignore', log, log],
|
|
43
|
+
windowsHide: true,
|
|
44
|
+
});
|
|
45
|
+
await new Promise((resolve, reject) => {
|
|
46
|
+
child.once('spawn', resolve);
|
|
47
|
+
child.once('error', reject);
|
|
48
|
+
});
|
|
49
|
+
child.unref();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
closeSync(log);
|
|
53
|
+
}
|
|
54
|
+
const deadline = Date.now() + 10000;
|
|
55
|
+
while (Date.now() < deadline) {
|
|
56
|
+
const ready = await new Promise(resolve => {
|
|
57
|
+
const probe = createConnection(paths.socket);
|
|
58
|
+
const finish = (value) => {
|
|
59
|
+
probe.destroy();
|
|
60
|
+
resolve(value);
|
|
61
|
+
};
|
|
62
|
+
probe.once('connect', () => finish(true));
|
|
63
|
+
probe.once('error', () => finish(false));
|
|
64
|
+
probe.setTimeout(200, () => finish(false));
|
|
65
|
+
});
|
|
66
|
+
if (ready)
|
|
67
|
+
return;
|
|
68
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
69
|
+
}
|
|
70
|
+
throw new Error('Plugin execution service startup timed out');
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
removeRecordIfPid(lock, process.pid);
|
|
74
|
+
}
|
|
75
|
+
})().finally(() => starting.delete(paths.registry));
|
|
76
|
+
starting.set(paths.registry, work);
|
|
77
|
+
}
|
|
78
|
+
await work;
|
|
79
|
+
}
|
|
80
|
+
/** One connection owns caller scope, discovery and bridge lifetime. */
|
|
81
|
+
export class PluginHostConnection {
|
|
82
|
+
configuration;
|
|
83
|
+
get hasInvocationBridge() {
|
|
84
|
+
return Boolean(this.configuration.invoke);
|
|
85
|
+
}
|
|
86
|
+
connection;
|
|
87
|
+
socket;
|
|
88
|
+
opening = new Set();
|
|
89
|
+
generation = 0;
|
|
90
|
+
constructor(configuration = {}) {
|
|
91
|
+
this.configuration = configuration;
|
|
92
|
+
}
|
|
93
|
+
async connect(generation) {
|
|
94
|
+
const check = () => {
|
|
95
|
+
if (generation !== this.generation)
|
|
96
|
+
throw Object.assign(new Error('Plugin task client was disconnected'), {
|
|
97
|
+
code: 'PLUGIN_TASK_CLIENT_DISCONNECTED',
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const connectOnce = () => {
|
|
101
|
+
check();
|
|
102
|
+
return this.open(this.configuration.socketPath ?? pluginHostPaths().socket, generation);
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
return await connectOnce();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
check();
|
|
109
|
+
if (this.configuration.autoStart === false || this.configuration.socketPath)
|
|
110
|
+
throw error;
|
|
111
|
+
const code = error.code;
|
|
112
|
+
if (!['ENOENT', 'ECONNREFUSED', 'PLUGIN_TASK_CONNECTION_LOST'].includes(code ?? ''))
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
await ensureHost(this.configuration.runtimePath ?? PLUGIN_TASK_HOST_PATH);
|
|
116
|
+
const deadline = Date.now() + 10000;
|
|
117
|
+
let failure;
|
|
118
|
+
do {
|
|
119
|
+
check();
|
|
120
|
+
try {
|
|
121
|
+
return await connectOnce();
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
failure = error;
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
126
|
+
}
|
|
127
|
+
} while (Date.now() < deadline);
|
|
128
|
+
throw new Error('Plugin execution service did not become ready; inspect its service.log', {
|
|
129
|
+
cause: failure,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
async open(socketPath, generation) {
|
|
133
|
+
const socket = createConnection(socketPath);
|
|
134
|
+
this.opening.add(socket);
|
|
135
|
+
const pending = new Map();
|
|
136
|
+
let sequence = 0;
|
|
137
|
+
const fail = (error) => {
|
|
138
|
+
for (const request of pending.values()) {
|
|
139
|
+
clearTimeout(request.timer);
|
|
140
|
+
request.reject(error);
|
|
141
|
+
}
|
|
142
|
+
pending.clear();
|
|
143
|
+
};
|
|
144
|
+
socket.on('error', fail);
|
|
145
|
+
socket.once('close', () => {
|
|
146
|
+
fail(Object.assign(new Error('Plugin execution service connection was lost'), {
|
|
147
|
+
code: 'PLUGIN_TASK_CONNECTION_LOST',
|
|
148
|
+
}));
|
|
149
|
+
if (this.socket === socket) {
|
|
150
|
+
this.connection = undefined;
|
|
151
|
+
this.socket = undefined;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
const request = (method, input = {}, timeoutMs = 15000) => new Promise((resolve, reject) => {
|
|
155
|
+
const id = ++sequence;
|
|
156
|
+
const timer = setTimeout(() => {
|
|
157
|
+
pending.delete(id);
|
|
158
|
+
reject(Object.assign(new Error('Plugin task request timed out; the task may still be running'), { code: 'PLUGIN_TASK_REQUEST_TIMEOUT' }));
|
|
159
|
+
}, timeoutMs);
|
|
160
|
+
pending.set(id, { resolve, reject, timer });
|
|
161
|
+
void writePluginTaskMessage(socket, { type: 'request', id, method, input }).catch(error => {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
pending.delete(id);
|
|
164
|
+
reject(error);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
readPluginTaskMessages(socket, message => {
|
|
168
|
+
if (message.type === 'response') {
|
|
169
|
+
const call = pending.get(message.id);
|
|
170
|
+
if (!call)
|
|
171
|
+
return;
|
|
172
|
+
clearTimeout(call.timer);
|
|
173
|
+
pending.delete(message.id);
|
|
174
|
+
if (message.error)
|
|
175
|
+
call.reject(pluginExecutionError(message.error));
|
|
176
|
+
else
|
|
177
|
+
call.resolve(message.result);
|
|
178
|
+
}
|
|
179
|
+
else if (message.type === 'invoke') {
|
|
180
|
+
void Promise.resolve()
|
|
181
|
+
.then(() => {
|
|
182
|
+
if (!this.configuration.invoke)
|
|
183
|
+
throw new Error('Plugin task client has no invocation bridge');
|
|
184
|
+
return this.configuration.invoke(message.action, message.payload, message.options);
|
|
185
|
+
})
|
|
186
|
+
.then(result => writePluginTaskMessage(socket, { type: 'invoke-result', id: message.id, result }), error => writePluginTaskMessage(socket, {
|
|
187
|
+
type: 'invoke-result',
|
|
188
|
+
id: message.id,
|
|
189
|
+
error: serializePluginExecutionError(error),
|
|
190
|
+
}))
|
|
191
|
+
.catch(() => { });
|
|
192
|
+
}
|
|
193
|
+
else
|
|
194
|
+
socket.destroy(new Error('Invalid plugin task response direction'));
|
|
195
|
+
});
|
|
196
|
+
try {
|
|
197
|
+
await new Promise((resolve, reject) => {
|
|
198
|
+
const timer = setTimeout(() => {
|
|
199
|
+
socket.destroy();
|
|
200
|
+
reject(new Error('Plugin task connection timed out'));
|
|
201
|
+
}, 2000);
|
|
202
|
+
socket.once('connect', () => {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
resolve();
|
|
205
|
+
});
|
|
206
|
+
socket.once('error', error => {
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
reject(error);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
const scope = {
|
|
212
|
+
access: this.configuration.access ?? 'public',
|
|
213
|
+
source: this.configuration.source ?? 'sdk',
|
|
214
|
+
bridge: this.configuration.invoke ? 'caller' : 'service',
|
|
215
|
+
...(this.configuration.plugin ? { plugin: this.configuration.plugin } : {}),
|
|
216
|
+
...(this.configuration.scopeId ? { scopeId: this.configuration.scopeId } : {}),
|
|
217
|
+
};
|
|
218
|
+
const status = await request('hello', {
|
|
219
|
+
...scope,
|
|
220
|
+
protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
|
|
221
|
+
});
|
|
222
|
+
if (status.protocolVersion !== PLUGIN_TASK_PROTOCOL_VERSION)
|
|
223
|
+
throw new Error('Plugin task protocol version mismatch');
|
|
224
|
+
if (generation !== this.generation || socket.destroyed)
|
|
225
|
+
throw new Error('Plugin task client was disconnected');
|
|
226
|
+
this.socket = socket;
|
|
227
|
+
return { socket, request, status };
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
socket.destroy();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
this.opening.delete(socket);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async request(method, input = {}, timeoutMs) {
|
|
238
|
+
if (!this.connection) {
|
|
239
|
+
const attempt = this.connect(this.generation);
|
|
240
|
+
this.connection = attempt;
|
|
241
|
+
void attempt.catch(() => {
|
|
242
|
+
if (this.connection === attempt)
|
|
243
|
+
this.connection = undefined;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return (await this.connection).request(method, input, timeoutMs);
|
|
247
|
+
}
|
|
248
|
+
disconnect() {
|
|
249
|
+
this.generation++;
|
|
250
|
+
this.socket?.destroy();
|
|
251
|
+
for (const socket of this.opening)
|
|
252
|
+
socket.destroy();
|
|
253
|
+
this.socket = undefined;
|
|
254
|
+
this.connection = undefined;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PluginHostClientOptions } from './pluginHostConnection.js';
|
|
2
|
+
import { PluginTaskClient } from './pluginTasks.js';
|
|
3
|
+
import type { PluginTaskStartOptions, PluginTaskWaitOptions } from './pluginTasks.js';
|
|
4
|
+
import type { PluginModelRequest, PluginModelResult, PluginModelCapability, PluginModelList } from '../../generated/plugin-models.js';
|
|
5
|
+
import type { PluginTaskStartResult } from '../../generated/plugin-tasks.js';
|
|
6
|
+
export type * from '../../generated/plugin-models.js';
|
|
7
|
+
export declare class PluginModelClient {
|
|
8
|
+
readonly tasks: PluginTaskClient;
|
|
9
|
+
constructor(options?: PluginHostClientOptions);
|
|
10
|
+
capabilities(): Promise<{
|
|
11
|
+
providers: PluginModelCapability[];
|
|
12
|
+
}>;
|
|
13
|
+
list(provider?: string): Promise<PluginModelList>;
|
|
14
|
+
start(plugin: string, request: PluginModelRequest, options?: PluginTaskStartOptions): Promise<PluginTaskStartResult>;
|
|
15
|
+
run(plugin: string, request: PluginModelRequest, options?: PluginTaskStartOptions & Pick<PluginTaskWaitOptions, 'signal' | 'onProgress'>): Promise<PluginModelResult>;
|
|
16
|
+
disconnect(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PluginTaskClient } from './pluginTasks.js';
|
|
2
|
+
export class PluginModelClient {
|
|
3
|
+
tasks;
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.tasks = new PluginTaskClient(options);
|
|
6
|
+
}
|
|
7
|
+
capabilities() {
|
|
8
|
+
return this.tasks.connection.request('model.capabilities');
|
|
9
|
+
}
|
|
10
|
+
list(provider = 'codex') {
|
|
11
|
+
return this.tasks.connection.request('model.list', { provider }, 20000);
|
|
12
|
+
}
|
|
13
|
+
async start(plugin, request, options = {}) {
|
|
14
|
+
const { cwd = process.cwd(), environment = process.env, executionTimeoutMs, idempotencyKey, ...routing } = options;
|
|
15
|
+
return this.tasks.connection.request('start', {
|
|
16
|
+
kind: 'model',
|
|
17
|
+
plugin,
|
|
18
|
+
command: 'model',
|
|
19
|
+
input: { request },
|
|
20
|
+
cwd,
|
|
21
|
+
environment: Object.fromEntries(Object.entries(environment).filter((entry) => entry[1] !== undefined)),
|
|
22
|
+
options: routing,
|
|
23
|
+
executionTimeoutMs,
|
|
24
|
+
idempotencyKey,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async run(plugin, request, options = {}) {
|
|
28
|
+
const { signal, onProgress, ...execution } = options;
|
|
29
|
+
signal?.throwIfAborted();
|
|
30
|
+
const started = await this.start(plugin, request, execution);
|
|
31
|
+
const cancel = () => {
|
|
32
|
+
void this.tasks.cancel(started.taskId).catch(() => { });
|
|
33
|
+
};
|
|
34
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
35
|
+
try {
|
|
36
|
+
if (signal?.aborted)
|
|
37
|
+
cancel();
|
|
38
|
+
return (await this.tasks.wait(started.taskId, { onProgress }));
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
signal?.removeEventListener('abort', cancel);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
disconnect() {
|
|
45
|
+
this.tasks.disconnect();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginHostConnection } from './pluginHostConnection.js';
|
|
2
|
+
import type { UnifiedClientOptions, UnifiedInvokeOptions } from '../../unified.js';
|
|
3
|
+
import type { PluginPageInstance, PluginPageEvents, PluginPageOperation } from '../../generated/plugin-pages.js';
|
|
4
|
+
export type { PluginPageInstance, PluginPageEvents, PluginPageRuntime, PluginPageActivation, PluginPageTarget, PluginPageOperation, } from '../../generated/plugin-pages.js';
|
|
5
|
+
export declare class PluginPageClient {
|
|
6
|
+
private readonly connection?;
|
|
7
|
+
private readonly client?;
|
|
8
|
+
constructor(configuration?: UnifiedClientOptions, connection?: PluginHostConnection | undefined);
|
|
9
|
+
call(plugin: string, operation: PluginPageOperation, input?: Record<string, unknown>, options?: UnifiedInvokeOptions): Promise<any>;
|
|
10
|
+
attach(plugin: string, input: Record<string, unknown>, options?: UnifiedInvokeOptions): Promise<PluginPageInstance>;
|
|
11
|
+
get(plugin: string, instanceId: string): Promise<PluginPageInstance>;
|
|
12
|
+
list(plugin: string): Promise<{
|
|
13
|
+
instances: PluginPageInstance[];
|
|
14
|
+
}>;
|
|
15
|
+
send(plugin: string, instanceId: string, message: unknown, options?: {
|
|
16
|
+
timeoutSec?: number;
|
|
17
|
+
}): Promise<unknown>;
|
|
18
|
+
detach(plugin: string, instanceId: string): Promise<PluginPageInstance>;
|
|
19
|
+
events(plugin: string, instanceId: string, cursor?: number, waitMs?: number): Promise<PluginPageEvents>;
|
|
20
|
+
disconnect(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { UnifiedClient } from '../../unified.js';
|
|
2
|
+
export class PluginPageClient {
|
|
3
|
+
connection;
|
|
4
|
+
client;
|
|
5
|
+
constructor(configuration = {}, connection) {
|
|
6
|
+
this.connection = connection;
|
|
7
|
+
if (!connection)
|
|
8
|
+
this.client = new UnifiedClient(configuration);
|
|
9
|
+
}
|
|
10
|
+
call(plugin, operation, input = {}, options = {}) {
|
|
11
|
+
if (this.connection)
|
|
12
|
+
return this.connection.request('page.call', {
|
|
13
|
+
plugin,
|
|
14
|
+
input: { ...input, operation },
|
|
15
|
+
options,
|
|
16
|
+
execution: {
|
|
17
|
+
cwd: process.cwd(),
|
|
18
|
+
environment: Object.fromEntries(Object.entries(process.env).filter(entry => entry[1] !== undefined)),
|
|
19
|
+
},
|
|
20
|
+
}, Math.max(120000, typeof input.timeoutSec === 'number' && Number.isFinite(input.timeoutSec)
|
|
21
|
+
? (Math.min(300, input.timeoutSec) + 5) * 1000
|
|
22
|
+
: 0));
|
|
23
|
+
return this.client.invoke('plugin_page', { ...input, plugin, operation }, options);
|
|
24
|
+
}
|
|
25
|
+
attach(plugin, input, options = {}) {
|
|
26
|
+
return this.call(plugin, 'attach', input, options);
|
|
27
|
+
}
|
|
28
|
+
get(plugin, instanceId) {
|
|
29
|
+
return this.call(plugin, 'get', { instanceId });
|
|
30
|
+
}
|
|
31
|
+
list(plugin) {
|
|
32
|
+
return this.call(plugin, 'list');
|
|
33
|
+
}
|
|
34
|
+
send(plugin, instanceId, message, options = {}) {
|
|
35
|
+
return this.call(plugin, 'send', { instanceId, message, ...options }, options);
|
|
36
|
+
}
|
|
37
|
+
detach(plugin, instanceId) {
|
|
38
|
+
return this.call(plugin, 'detach', { instanceId });
|
|
39
|
+
}
|
|
40
|
+
events(plugin, instanceId, cursor = 0, waitMs = 0) {
|
|
41
|
+
return this.call(plugin, 'events', { instanceId, cursor, waitMs });
|
|
42
|
+
}
|
|
43
|
+
disconnect() {
|
|
44
|
+
this.client?.disconnect();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PluginHostConnection } from './pluginHostConnection.js';
|
|
2
|
+
import type { PluginHostClientOptions } from './pluginHostConnection.js';
|
|
3
|
+
import type { PluginOptions } from '../sdk/plugin.js';
|
|
4
|
+
import type { PluginStateOperation, PluginStateSnapshot, PluginStateList, PluginStateTransaction } from '../../generated/plugin-state.js';
|
|
5
|
+
export type * from '../../generated/plugin-state.js';
|
|
6
|
+
export declare class PluginStateClient {
|
|
7
|
+
private readonly options;
|
|
8
|
+
readonly connection: PluginHostConnection;
|
|
9
|
+
constructor(options?: PluginHostClientOptions);
|
|
10
|
+
call(plugin: string, collection: string, operation: PluginStateOperation, input?: Record<string, unknown>, options?: PluginOptions): Promise<any>;
|
|
11
|
+
get(plugin: string, collection: string, key: string, input?: {
|
|
12
|
+
instanceId?: string;
|
|
13
|
+
}): Promise<PluginStateSnapshot>;
|
|
14
|
+
getMany(plugin: string, collection: string, keys: string[], input?: {
|
|
15
|
+
instanceId?: string;
|
|
16
|
+
}): Promise<PluginStateList>;
|
|
17
|
+
list(plugin: string, collection: string, input?: Record<string, unknown>): Promise<PluginStateList>;
|
|
18
|
+
transact(plugin: string, collection: string, transaction: PluginStateTransaction, input?: {
|
|
19
|
+
instanceId?: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
revision: number;
|
|
22
|
+
schemaVersion: number;
|
|
23
|
+
reused?: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
disconnect(): void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PluginHostConnection } from './pluginHostConnection.js';
|
|
2
|
+
export class PluginStateClient {
|
|
3
|
+
options;
|
|
4
|
+
connection;
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.connection = options.connection ?? new PluginHostConnection(options);
|
|
8
|
+
}
|
|
9
|
+
call(plugin, collection, operation, input = {}, options = {}) {
|
|
10
|
+
return this.connection.request('state.call', { plugin, input: { ...input, collection, operation }, options }, 30000);
|
|
11
|
+
}
|
|
12
|
+
get(plugin, collection, key, input = {}) {
|
|
13
|
+
return this.call(plugin, collection, 'get', { ...input, key });
|
|
14
|
+
}
|
|
15
|
+
getMany(plugin, collection, keys, input = {}) {
|
|
16
|
+
return this.call(plugin, collection, 'get-many', { ...input, keys });
|
|
17
|
+
}
|
|
18
|
+
list(plugin, collection, input = {}) {
|
|
19
|
+
return this.call(plugin, collection, 'list', input);
|
|
20
|
+
}
|
|
21
|
+
transact(plugin, collection, transaction, input = {}) {
|
|
22
|
+
return this.call(plugin, collection, 'transact', { ...input, transaction });
|
|
23
|
+
}
|
|
24
|
+
disconnect() {
|
|
25
|
+
if (!this.options.connection)
|
|
26
|
+
this.connection.disconnect();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PluginHostConnection } from './pluginHostConnection.js';
|
|
2
|
+
import type { PluginHostClientOptions, HostStatus as PluginHostStatus } from './pluginHostConnection.js';
|
|
3
|
+
import type { PluginTaskCheckpoint } from '../../generated/plugin-recovery.js';
|
|
4
|
+
import type { PluginDiagnosticOptions, PluginDiagnosticReport } from '../../generated/plugin-diagnostics.js';
|
|
5
|
+
import type { PluginTask, PluginTaskEvents, PluginTaskListResult, PluginTaskResultChunk, PluginTaskStartResult } from '../../generated/plugin-tasks.js';
|
|
6
|
+
import type { PluginInvocation, PluginOptions } from '../sdk/plugin.js';
|
|
7
|
+
export type { PluginTask, PluginTaskEvents, PluginTaskListResult, PluginTaskStartResult };
|
|
8
|
+
export interface PluginTaskStartOptions extends PluginOptions {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
environment?: NodeJS.ProcessEnv;
|
|
11
|
+
executionTimeoutMs?: number;
|
|
12
|
+
idempotencyKey?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface PluginTaskWaitOptions {
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
readResult?: boolean;
|
|
18
|
+
onProgress?: (progress: PluginTask['progress'], task: PluginTask) => void;
|
|
19
|
+
}
|
|
20
|
+
export type PluginTaskClientOptions = PluginHostClientOptions;
|
|
21
|
+
/** Background execution stays on this host; disconnecting a caller never cancels its tasks. */
|
|
22
|
+
export declare class PluginTaskClient {
|
|
23
|
+
private readonly configuration;
|
|
24
|
+
readonly connection: PluginHostConnection;
|
|
25
|
+
constructor(configuration?: PluginTaskClientOptions);
|
|
26
|
+
private request;
|
|
27
|
+
status(): Promise<PluginHostStatus>;
|
|
28
|
+
diagnose(options?: PluginDiagnosticOptions): Promise<PluginDiagnosticReport>;
|
|
29
|
+
checkpoint(taskId: string): Promise<PluginTaskCheckpoint | null>;
|
|
30
|
+
resume(taskId: string, options?: {
|
|
31
|
+
environment?: NodeJS.ProcessEnv;
|
|
32
|
+
revision?: 'original' | 'current';
|
|
33
|
+
}): Promise<PluginTaskStartResult>;
|
|
34
|
+
start(plugin: string, invocation: PluginInvocation, options?: PluginTaskStartOptions): Promise<PluginTaskStartResult>;
|
|
35
|
+
get(taskId: string): Promise<PluginTask>;
|
|
36
|
+
list(activeOnly?: boolean, page?: {
|
|
37
|
+
cursor?: string;
|
|
38
|
+
limit?: number;
|
|
39
|
+
}): Promise<PluginTaskListResult>;
|
|
40
|
+
cancel(taskId: string): Promise<PluginTask>;
|
|
41
|
+
events(taskId: string, cursor?: number, waitMs?: number): Promise<PluginTaskEvents>;
|
|
42
|
+
resultChunk(taskId: string, index: number): Promise<PluginTaskResultChunk>;
|
|
43
|
+
result(taskId: string): Promise<unknown>;
|
|
44
|
+
subscribe(taskId: string, options?: {
|
|
45
|
+
cursor?: number;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}): AsyncGenerator<PluginTaskEvents>;
|
|
48
|
+
wait(taskId: string, options?: PluginTaskWaitOptions): Promise<unknown>;
|
|
49
|
+
stopHost(): Promise<void>;
|
|
50
|
+
disconnect(): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { PluginHostConnection } from './pluginHostConnection.js';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { isProcessOwnerAlive, readRecord } from '@mearl/daemon-core';
|
|
4
|
+
import { pluginHostPaths } from '../runtime/pluginHostPaths.js';
|
|
5
|
+
import { UnifiedClient, decodeBrowserTarget } from '../../unified.js';
|
|
6
|
+
import { isPluginTaskActive } from '../../generated/plugin-tasks.js';
|
|
7
|
+
import { pluginExecutionError } from '../runtime/pluginExecutorProtocol.js';
|
|
8
|
+
function localWait(promise, signal, timeoutMs = Infinity) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const abort = () => {
|
|
11
|
+
cleanup();
|
|
12
|
+
reject(signal?.reason ?? new Error('Plugin task wait aborted'));
|
|
13
|
+
};
|
|
14
|
+
const timer = Number.isFinite(timeoutMs)
|
|
15
|
+
? setTimeout(() => {
|
|
16
|
+
cleanup();
|
|
17
|
+
reject(Object.assign(new Error('Waiting for the plugin task timed out; execution continues'), { code: 'PLUGIN_TASK_WAIT_TIMEOUT' }));
|
|
18
|
+
}, Math.max(0, timeoutMs))
|
|
19
|
+
: undefined;
|
|
20
|
+
const cleanup = () => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
signal?.removeEventListener('abort', abort);
|
|
23
|
+
};
|
|
24
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
25
|
+
promise.then(value => {
|
|
26
|
+
cleanup();
|
|
27
|
+
resolve(value);
|
|
28
|
+
}, error => {
|
|
29
|
+
cleanup();
|
|
30
|
+
reject(error);
|
|
31
|
+
});
|
|
32
|
+
if (signal?.aborted)
|
|
33
|
+
abort();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Background execution stays on this host; disconnecting a caller never cancels its tasks. */
|
|
37
|
+
export class PluginTaskClient {
|
|
38
|
+
configuration;
|
|
39
|
+
connection;
|
|
40
|
+
constructor(configuration = {}) {
|
|
41
|
+
this.configuration = configuration;
|
|
42
|
+
this.connection = configuration.connection ?? new PluginHostConnection(configuration);
|
|
43
|
+
}
|
|
44
|
+
request(method, input = {}, timeoutMs) {
|
|
45
|
+
return this.connection.request(method, input, timeoutMs);
|
|
46
|
+
}
|
|
47
|
+
status() {
|
|
48
|
+
return this.request('status');
|
|
49
|
+
}
|
|
50
|
+
diagnose(options = {}) {
|
|
51
|
+
return this.request('diagnose', { ...options });
|
|
52
|
+
}
|
|
53
|
+
checkpoint(taskId) {
|
|
54
|
+
return this.request('checkpoint', { taskId });
|
|
55
|
+
}
|
|
56
|
+
resume(taskId, options = {}) {
|
|
57
|
+
return this.request('resume', {
|
|
58
|
+
taskId,
|
|
59
|
+
revision: options.revision,
|
|
60
|
+
environment: Object.fromEntries(Object.entries(options.environment ?? process.env).filter((entry) => entry[1] !== undefined)),
|
|
61
|
+
}, 45000);
|
|
62
|
+
}
|
|
63
|
+
async start(plugin, invocation, options = {}) {
|
|
64
|
+
const { cwd = process.cwd(), environment = process.env, executionTimeoutMs, idempotencyKey, ...routing } = options;
|
|
65
|
+
if (!this.connection.hasInvocationBridge &&
|
|
66
|
+
routing.browser &&
|
|
67
|
+
!decodeBrowserTarget(routing.browser)) {
|
|
68
|
+
const client = new UnifiedClient();
|
|
69
|
+
try {
|
|
70
|
+
const selection = await client.invoke('browser_select_browser', { browser: routing.browser }, routing);
|
|
71
|
+
if (!selection?.selected?.browserId)
|
|
72
|
+
throw new Error('Cannot resolve plugin task browser target');
|
|
73
|
+
routing.browser = selection.selected.browserId;
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
client.disconnect();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return this.request('start', {
|
|
80
|
+
plugin,
|
|
81
|
+
command: invocation.command,
|
|
82
|
+
input: invocation.input,
|
|
83
|
+
cwd,
|
|
84
|
+
environment: Object.fromEntries(Object.entries(environment).filter((entry) => entry[1] !== undefined)),
|
|
85
|
+
options: routing,
|
|
86
|
+
executionTimeoutMs,
|
|
87
|
+
idempotencyKey,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
get(taskId) {
|
|
91
|
+
return this.request('get', { taskId });
|
|
92
|
+
}
|
|
93
|
+
list(activeOnly = false, page = {}) {
|
|
94
|
+
return this.request('list', { activeOnly, ...page });
|
|
95
|
+
}
|
|
96
|
+
cancel(taskId) {
|
|
97
|
+
return this.request('cancel', { taskId });
|
|
98
|
+
}
|
|
99
|
+
events(taskId, cursor = 0, waitMs = 0) {
|
|
100
|
+
return this.request('events', { taskId, cursor, waitMs }, waitMs + 15000);
|
|
101
|
+
}
|
|
102
|
+
resultChunk(taskId, index) {
|
|
103
|
+
return this.request('result', { taskId, index });
|
|
104
|
+
}
|
|
105
|
+
async result(taskId) {
|
|
106
|
+
const task = await this.get(taskId);
|
|
107
|
+
if (task.state !== 'success' || !task.result)
|
|
108
|
+
throw new Error('Plugin task has no successful result');
|
|
109
|
+
const parts = [];
|
|
110
|
+
for (let index = 0; index < task.result.chunks; index++) {
|
|
111
|
+
const chunk = await this.resultChunk(taskId, index);
|
|
112
|
+
if (chunk.taskId !== taskId ||
|
|
113
|
+
chunk.index !== index ||
|
|
114
|
+
chunk.sha256 !== task.result.sha256 ||
|
|
115
|
+
chunk.bytes !== task.result.bytes ||
|
|
116
|
+
chunk.chunks !== task.result.chunks)
|
|
117
|
+
throw new Error('Plugin task result chunk identity mismatch');
|
|
118
|
+
parts.push(Buffer.from(chunk.data, 'base64'));
|
|
119
|
+
}
|
|
120
|
+
const bytes = Buffer.concat(parts);
|
|
121
|
+
if (bytes.length !== task.result.bytes ||
|
|
122
|
+
createHash('sha256').update(bytes).digest('hex') !== task.result.sha256)
|
|
123
|
+
throw new Error('Plugin task result integrity check failed');
|
|
124
|
+
return JSON.parse(bytes.toString('utf8')).value;
|
|
125
|
+
}
|
|
126
|
+
async *subscribe(taskId, options = {}) {
|
|
127
|
+
let cursor = options.cursor ?? 0;
|
|
128
|
+
while (true) {
|
|
129
|
+
options.signal?.throwIfAborted();
|
|
130
|
+
const page = await localWait(this.events(taskId, cursor, 25000), options.signal);
|
|
131
|
+
options.signal?.throwIfAborted();
|
|
132
|
+
yield page;
|
|
133
|
+
cursor = page.nextCursor;
|
|
134
|
+
if (!isPluginTaskActive(page.task.state) && cursor >= page.task.sequence)
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async wait(taskId, options = {}) {
|
|
139
|
+
if (options.timeoutMs !== undefined &&
|
|
140
|
+
(!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1))
|
|
141
|
+
throw new Error('Invalid plugin task wait timeout');
|
|
142
|
+
const deadline = options.timeoutMs === undefined ? Infinity : Date.now() + options.timeoutMs;
|
|
143
|
+
let cursor = 0;
|
|
144
|
+
let lastSequence = -1;
|
|
145
|
+
while (true) {
|
|
146
|
+
options.signal?.throwIfAborted();
|
|
147
|
+
if (Date.now() >= deadline)
|
|
148
|
+
throw Object.assign(new Error('Waiting for the plugin task timed out; execution continues'), { code: 'PLUGIN_TASK_WAIT_TIMEOUT' });
|
|
149
|
+
const page = await localWait(this.events(taskId, cursor, Math.min(1000, Math.max(0, deadline - Date.now()))), options.signal, deadline - Date.now());
|
|
150
|
+
options.signal?.throwIfAborted();
|
|
151
|
+
cursor = page.nextCursor;
|
|
152
|
+
if (page.task.progress && page.task.sequence !== lastSequence)
|
|
153
|
+
options.onProgress?.(page.task.progress, page.task);
|
|
154
|
+
lastSequence = page.task.sequence;
|
|
155
|
+
if (page.task.state === 'success')
|
|
156
|
+
return options.readResult === false
|
|
157
|
+
? page.task
|
|
158
|
+
: localWait(this.result(taskId), options.signal, deadline - Date.now());
|
|
159
|
+
if (!isPluginTaskActive(page.task.state))
|
|
160
|
+
throw pluginExecutionError(page.task.error ?? { message: `Plugin task ended with ${page.task.state}` });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async stopHost() {
|
|
164
|
+
const original = await this.status();
|
|
165
|
+
await this.request('shutdown');
|
|
166
|
+
this.disconnect();
|
|
167
|
+
const deadline = Date.now() + 15000;
|
|
168
|
+
while (true) {
|
|
169
|
+
const lease = readRecord(pluginHostPaths().lease);
|
|
170
|
+
if (!lease || lease.epoch !== original.host.epoch || !isProcessOwnerAlive(lease))
|
|
171
|
+
return;
|
|
172
|
+
if (Date.now() >= deadline)
|
|
173
|
+
throw new Error('Plugin execution service did not finish stopping');
|
|
174
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
disconnect() {
|
|
178
|
+
if (!this.configuration.connection)
|
|
179
|
+
this.connection.disconnect();
|
|
180
|
+
}
|
|
181
|
+
}
|