@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
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { closeSync, mkdirSync, openSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { CLIENT_VERSION } from '../runtimePaths.js';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { findOfficialPlugin } from './officialPluginCatalog.js';
|
|
6
6
|
import { findPluginRegistration, pluginDirectory } from './pluginRegistry.js';
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
const CLIENT_VERSION = require('../package.json').version;
|
|
9
7
|
const INSTALL_TIMEOUT_MS = 180_000;
|
|
10
8
|
const INSTALL_LOCK_STALE_MS = 5 * 60_000;
|
|
11
9
|
const MAX_INSTALL_OUTPUT = 1024 * 1024;
|
|
@@ -29,9 +29,7 @@ const PAGE_BRIDGE_SCRIPT = String.raw `(() => {
|
|
|
29
29
|
let port = null;
|
|
30
30
|
let requestSequence = 0;
|
|
31
31
|
const pending = new Map();
|
|
32
|
-
const pageEventListeners = new Map();
|
|
33
32
|
const contextListeners = new Set();
|
|
34
|
-
let subscriptionSequence = 0;
|
|
35
33
|
let resolveReady;
|
|
36
34
|
const ready = new Promise(resolve => { resolveReady = resolve; });
|
|
37
35
|
|
|
@@ -87,17 +85,24 @@ const PAGE_BRIDGE_SCRIPT = String.raw `(() => {
|
|
|
87
85
|
};
|
|
88
86
|
|
|
89
87
|
const subscribe = async (source, target, listener) => {
|
|
90
|
-
if (source !== 'page' || !target ||
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
if (source !== 'page' || !target || typeof target.instanceId !== 'string' ||
|
|
89
|
+
!/^pi_[0-9a-f-]{36}$/i.test(target.instanceId) || typeof listener !== 'function') throw new Error('Invalid page event subscription');
|
|
90
|
+
let stopped = false, timer, cursor = target.cursor ?? 0;
|
|
91
|
+
const poll = async (waitMs = 0) => {
|
|
92
|
+
let delay = 0;
|
|
93
|
+
try {
|
|
94
|
+
const page = await invoke('host.page.events', { instanceId: target.instanceId, cursor, waitMs });
|
|
95
|
+
if (stopped) return;
|
|
96
|
+
const metadata = { url: page.instance.target.url, instance: page.instance, gap: page.gap };
|
|
97
|
+
if (page.gap) listener({ type: 'resync' }, metadata);
|
|
98
|
+
for (const event of page.events) { if (stopped) return; listener(event, metadata); }
|
|
99
|
+
cursor = page.nextCursor;
|
|
100
|
+
if (page.instance.state !== 'active') return;
|
|
101
|
+
} catch (error) { if (stopped) return; delay = 500; listener({ type: 'connection-error', error: { message: error.message } }); }
|
|
102
|
+
if (!stopped) timer = setTimeout(() => { void poll(25000); }, delay);
|
|
100
103
|
};
|
|
104
|
+
await poll();
|
|
105
|
+
return () => { stopped = true; clearTimeout(timer); };
|
|
101
106
|
};
|
|
102
107
|
|
|
103
108
|
const handleExternalLinkClick = event => {
|
|
@@ -404,7 +409,62 @@ const PAGE_BRIDGE_SCRIPT = String.raw `(() => {
|
|
|
404
409
|
if (context) listener(context);
|
|
405
410
|
return () => contextListeners.delete(listener);
|
|
406
411
|
};
|
|
407
|
-
const
|
|
412
|
+
const readTaskResult = async taskId => {
|
|
413
|
+
const task = await invoke('host.task.get', { taskId });
|
|
414
|
+
const reference = task.result;
|
|
415
|
+
if (task.state !== 'success' || !reference || !Number.isSafeInteger(reference.bytes) ||
|
|
416
|
+
reference.bytes < 1 || reference.bytes > 64 * 1024 * 1024 ||
|
|
417
|
+
!Number.isSafeInteger(reference.chunks) || reference.chunks < 1 || reference.chunks > 1024 ||
|
|
418
|
+
!/^[a-f0-9]{64}$/.test(reference.sha256)) throw new Error('Invalid task result reference');
|
|
419
|
+
const data = new Uint8Array(reference.bytes);
|
|
420
|
+
let offset = 0;
|
|
421
|
+
for (let index = 0; index < reference.chunks; index++) {
|
|
422
|
+
const chunk = await invoke('host.task.result', { taskId, index });
|
|
423
|
+
if (chunk.taskId !== taskId || chunk.index !== index || chunk.sha256 !== reference.sha256 ||
|
|
424
|
+
chunk.bytes !== reference.bytes || chunk.chunks !== reference.chunks) throw new Error('Task result chunk identity mismatch');
|
|
425
|
+
const binary = atob(chunk.data);
|
|
426
|
+
if (offset + binary.length > data.length) throw new Error('Task result size mismatch');
|
|
427
|
+
for (let i = 0; i < binary.length; i++) data[offset++] = binary.charCodeAt(i);
|
|
428
|
+
}
|
|
429
|
+
const hash = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', data)),
|
|
430
|
+
value => value.toString(16).padStart(2, '0')).join('');
|
|
431
|
+
if (offset !== reference.bytes || hash !== reference.sha256) throw new Error('Task result integrity check failed');
|
|
432
|
+
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(data)).value;
|
|
433
|
+
};
|
|
434
|
+
const waitTask = async (taskId, options = {}) => {
|
|
435
|
+
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs < 1)) throw new Error('Invalid task wait timeout');
|
|
436
|
+
const deadline = options.timeoutMs === undefined ? Infinity : Date.now() + options.timeoutMs;
|
|
437
|
+
let cursor = 0, lastSequence = -1;
|
|
438
|
+
for (;;) {
|
|
439
|
+
options.signal?.throwIfAborted();
|
|
440
|
+
const remaining = deadline - Date.now();
|
|
441
|
+
if (remaining <= 0) throw Object.assign(new Error('Waiting for the task timed out; execution continues'), { code: 'PLUGIN_TASK_WAIT_TIMEOUT' });
|
|
442
|
+
let page;
|
|
443
|
+
try {
|
|
444
|
+
page = await invoke('host.task.events', { taskId, cursor, waitMs: Math.min(1000, remaining) });
|
|
445
|
+
options.onConnectionChange?.(false);
|
|
446
|
+
} catch (error) { options.onConnectionChange?.(true); throw error; }
|
|
447
|
+
options.signal?.throwIfAborted();
|
|
448
|
+
const task = page.task;
|
|
449
|
+
cursor = page.nextCursor;
|
|
450
|
+
if (task.progress && task.sequence !== lastSequence) options.onProgress?.(task.progress, task);
|
|
451
|
+
lastSequence = task.sequence;
|
|
452
|
+
if (task.state === 'success') return readTaskResult(taskId);
|
|
453
|
+
if (!['queued', 'running', 'cancelling'].includes(task.state))
|
|
454
|
+
throw Object.assign(new Error(task.error?.message || 'Plugin task ended with ' + task.state), { code: task.error?.code });
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
const listTasks = async (options = {}) => {
|
|
458
|
+
const tasks = [];
|
|
459
|
+
let cursor;
|
|
460
|
+
do {
|
|
461
|
+
const page = await invoke('host.task.list', { activeOnly: options.activeOnly !== false, ...(cursor ? { cursor } : {}) });
|
|
462
|
+
tasks.push(...page.tasks);
|
|
463
|
+
cursor = page.nextCursor;
|
|
464
|
+
} while (cursor);
|
|
465
|
+
return { tasks };
|
|
466
|
+
};
|
|
467
|
+
const api = { invoke, subscribe, readTaskResult, waitTask, listTasks, onContextChange, ready: async () => { await ready; return context; }, runAgentTask, setAgentGuides, setAgentPanelVisible, setQueryParam };
|
|
408
468
|
const ui = globalThis.__mearlToolboxUi;
|
|
409
469
|
if (ui !== undefined) {
|
|
410
470
|
if (!ui || typeof ui !== 'object' || ui.runtime !== 'preact') {
|
|
@@ -448,10 +508,7 @@ const PAGE_BRIDGE_SCRIPT = String.raw `(() => {
|
|
|
448
508
|
port = nextPort;
|
|
449
509
|
port.onmessage = responseEvent => {
|
|
450
510
|
const response = responseEvent.data;
|
|
451
|
-
|
|
452
|
-
pageEventListeners.get(response.subscriptionId)?.(response.event, response.metadata);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
511
|
+
|
|
455
512
|
if (response?.type === 'context') {
|
|
456
513
|
applyContext(response.context);
|
|
457
514
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AppProfileContribution } from '
|
|
1
|
+
import { type AppProfileContribution } from '../generated/app-profile-definition.js';
|
|
2
2
|
export declare class PluginError extends Error {
|
|
3
3
|
readonly code: string;
|
|
4
4
|
constructor(code: string, message: string);
|
|
@@ -8,9 +8,6 @@ export interface PluginToolboxSandboxPage {
|
|
|
8
8
|
runtime?: 'preact';
|
|
9
9
|
entry: string;
|
|
10
10
|
commands: string[];
|
|
11
|
-
lifecycle?: {
|
|
12
|
-
onNavigation: string;
|
|
13
|
-
};
|
|
14
11
|
}
|
|
15
12
|
export interface PluginToolboxLocale {
|
|
16
13
|
title?: string;
|
|
@@ -38,6 +35,7 @@ export interface PluginPageBundleRegistration {
|
|
|
38
35
|
}
|
|
39
36
|
export interface PluginRegistration {
|
|
40
37
|
entry: string;
|
|
38
|
+
hasPublicCommands?: boolean;
|
|
41
39
|
description?: string;
|
|
42
40
|
managedBy?: string;
|
|
43
41
|
toolbox?: PluginToolboxContribution;
|
|
@@ -52,6 +50,7 @@ export interface PluginRegistration {
|
|
|
52
50
|
}
|
|
53
51
|
export declare function pluginRegistrationNeedsRefresh(registration: PluginRegistration, includePage?: boolean): boolean;
|
|
54
52
|
export interface PluginRegistrationOptions {
|
|
53
|
+
hasPublicCommands?: boolean;
|
|
55
54
|
managedBy?: string;
|
|
56
55
|
toolbox?: PluginToolboxContribution;
|
|
57
56
|
appProfiles?: AppProfileContribution;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { validateAppProfileContribution, } from '
|
|
1
|
+
import { validateAppProfileContribution, } from '../generated/app-profile-definition.js';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { parseToolboxIcon } from '../generated/toolbox-icon.js';
|
|
3
4
|
import { closeSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
|
-
import { COMMAND_MAP } from '
|
|
7
|
+
import { COMMAND_MAP } from '../commands.js';
|
|
7
8
|
import { buildPluginPageBundle, PLUGIN_PAGE_CHUNK_BYTES, PLUGIN_PAGE_RUNTIME_HASH, } from './pluginPageBundle.js';
|
|
8
9
|
import { pluginFileVersion } from './pluginSource.js';
|
|
9
10
|
const RESERVED_NAMES = new Set([
|
|
@@ -134,8 +135,8 @@ export function normalizePluginToolboxContribution(value) {
|
|
|
134
135
|
if (description && description.length > 240) {
|
|
135
136
|
throw new Error('toolbox.description 不能超过 240 个字符');
|
|
136
137
|
}
|
|
137
|
-
if (icon && (
|
|
138
|
-
throw new Error('toolbox.icon
|
|
138
|
+
if (icon && !parseToolboxIcon(icon)) {
|
|
139
|
+
throw new Error('toolbox.icon 必须是图标名称、SVG、图片 Data URL 或 HTTP(S) 图片地址,最多 32768 个字符');
|
|
139
140
|
}
|
|
140
141
|
let locales;
|
|
141
142
|
if (candidate.locales !== undefined) {
|
|
@@ -209,17 +210,8 @@ export function normalizePluginToolboxContribution(value) {
|
|
|
209
210
|
entry: pageEntry,
|
|
210
211
|
commands,
|
|
211
212
|
};
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
if (!lifecycle ||
|
|
215
|
-
typeof lifecycle !== 'object' ||
|
|
216
|
-
Array.isArray(lifecycle) ||
|
|
217
|
-
typeof lifecycle.onNavigation !== 'string' ||
|
|
218
|
-
!commands.includes(lifecycle.onNavigation)) {
|
|
219
|
-
throw new Error('toolbox.page.lifecycle.onNavigation 必须是 page.commands 中的命令名称');
|
|
220
|
-
}
|
|
221
|
-
page.lifecycle = { onNavigation: lifecycle.onNavigation };
|
|
222
|
-
}
|
|
213
|
+
if ('lifecycle' in pageCandidate)
|
|
214
|
+
throw new Error('toolbox.page.lifecycle is unsupported; declare pageRuntime and activation rules');
|
|
223
215
|
let agent;
|
|
224
216
|
if (candidate.agent !== undefined) {
|
|
225
217
|
if (!candidate.agent || typeof candidate.agent !== 'object' || Array.isArray(candidate.agent)) {
|
|
@@ -309,6 +301,9 @@ export function registerPlugin(name, target, description, options = {}) {
|
|
|
309
301
|
}
|
|
310
302
|
const record = {
|
|
311
303
|
entry,
|
|
304
|
+
...(options.hasPublicCommands !== undefined
|
|
305
|
+
? { hasPublicCommands: options.hasPublicCommands }
|
|
306
|
+
: {}),
|
|
312
307
|
source: {
|
|
313
308
|
entry: entryVersion,
|
|
314
309
|
...(options.profileSources ? { profiles: options.profileSources } : {}),
|
|
@@ -339,6 +334,7 @@ export function unregisterPlugin(name) {
|
|
|
339
334
|
const file = join(pluginDirectory(), `${name}.json`);
|
|
340
335
|
try {
|
|
341
336
|
rmSync(file);
|
|
337
|
+
rmSync(join(pluginDirectory(), '.host', 'installations', `${name}.json`), { force: true });
|
|
342
338
|
removePluginPageBundles(name);
|
|
343
339
|
return true;
|
|
344
340
|
}
|
|
@@ -365,6 +361,8 @@ export function findPluginRegistration(name) {
|
|
|
365
361
|
const record = JSON.parse(content);
|
|
366
362
|
if (typeof record?.entry !== 'string' || !isAbsolute(record.entry))
|
|
367
363
|
throw new Error('invalid entry');
|
|
364
|
+
if (record.hasPublicCommands !== undefined && typeof record.hasPublicCommands !== 'boolean')
|
|
365
|
+
throw new Error('invalid public commands');
|
|
368
366
|
const description = typeof record.description === 'string' && record.description.trim()
|
|
369
367
|
? record.description.trim()
|
|
370
368
|
: undefined;
|
|
@@ -396,6 +394,9 @@ export function findPluginRegistration(name) {
|
|
|
396
394
|
}
|
|
397
395
|
return {
|
|
398
396
|
entry: record.entry,
|
|
397
|
+
...(record.hasPublicCommands !== undefined
|
|
398
|
+
? { hasPublicCommands: record.hasPublicCommands }
|
|
399
|
+
: {}),
|
|
399
400
|
...(description ? { description } : {}),
|
|
400
401
|
...(managedBy ? { managedBy } : {}),
|
|
401
402
|
...(toolbox ? { toolbox } : {}),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PluginExecutionError, PluginInvocationSource } from '../generated/plugin-execution.js';
|
|
2
|
+
import type { PluginOptions } from './sdk/plugin.js';
|
|
3
|
+
export interface PluginTaskScope {
|
|
4
|
+
access: 'public' | 'toolbox' | 'runtime';
|
|
5
|
+
plugin?: string;
|
|
6
|
+
source: PluginInvocationSource;
|
|
7
|
+
bridge: 'service' | 'caller';
|
|
8
|
+
scopeId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PluginTaskStartRequest {
|
|
11
|
+
plugin: string;
|
|
12
|
+
command: string;
|
|
13
|
+
kind?: 'command' | 'model';
|
|
14
|
+
input: Record<string, unknown>;
|
|
15
|
+
cwd: string;
|
|
16
|
+
environment: Record<string, string>;
|
|
17
|
+
options: PluginOptions;
|
|
18
|
+
executionTimeoutMs?: number;
|
|
19
|
+
idempotencyKey?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PluginTaskResumeRequest {
|
|
22
|
+
taskId: string;
|
|
23
|
+
environment: Record<string, string>;
|
|
24
|
+
revision?: 'original' | 'current';
|
|
25
|
+
}
|
|
26
|
+
export type PluginTaskWireMessage = {
|
|
27
|
+
type: 'request';
|
|
28
|
+
id: number;
|
|
29
|
+
method: string;
|
|
30
|
+
input: Record<string, unknown>;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'response';
|
|
33
|
+
id: number;
|
|
34
|
+
result?: unknown;
|
|
35
|
+
error?: PluginExecutionError;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'invoke';
|
|
38
|
+
id: number;
|
|
39
|
+
action: string;
|
|
40
|
+
payload: Record<string, unknown>;
|
|
41
|
+
options: PluginOptions;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'invoke-result';
|
|
44
|
+
id: number;
|
|
45
|
+
result?: unknown;
|
|
46
|
+
error?: PluginExecutionError;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Socket } from 'node:net';
|
|
2
|
+
import type { PluginTaskWireMessage } from './pluginTaskProtocol.js';
|
|
3
|
+
export declare function readPluginTaskMessages(socket: Socket, receive: (message: PluginTaskWireMessage) => void): void;
|
|
4
|
+
export declare function writePluginTaskMessage(socket: Socket, message: PluginTaskWireMessage): Promise<void>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
2
|
+
const MAX_MESSAGE_BYTES = 96 * 1024 * 1024;
|
|
3
|
+
export function readPluginTaskMessages(socket, receive) {
|
|
4
|
+
const decoder = new StringDecoder('utf8');
|
|
5
|
+
let buffer = '';
|
|
6
|
+
socket.on('data', chunk => {
|
|
7
|
+
try {
|
|
8
|
+
buffer += decoder.write(chunk);
|
|
9
|
+
let end;
|
|
10
|
+
while ((end = buffer.indexOf('\n')) >= 0) {
|
|
11
|
+
const line = buffer.slice(0, end);
|
|
12
|
+
buffer = buffer.slice(end + 1);
|
|
13
|
+
if (Buffer.byteLength(line) > MAX_MESSAGE_BYTES)
|
|
14
|
+
throw new Error('Plugin host message is too large');
|
|
15
|
+
const message = JSON.parse(line);
|
|
16
|
+
if (!message || typeof message !== 'object' || !Number.isSafeInteger(message.id))
|
|
17
|
+
throw new Error('Invalid plugin host message');
|
|
18
|
+
receive(message);
|
|
19
|
+
}
|
|
20
|
+
if (Buffer.byteLength(buffer) > MAX_MESSAGE_BYTES)
|
|
21
|
+
throw new Error('Plugin host message buffer exceeded its limit');
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
socket.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function writePluginTaskMessage(socket, message) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
try {
|
|
31
|
+
const text = JSON.stringify(message) + '\n';
|
|
32
|
+
if (Buffer.byteLength(text) > MAX_MESSAGE_BYTES)
|
|
33
|
+
throw new Error('Plugin host message is too large');
|
|
34
|
+
if (socket.destroyed)
|
|
35
|
+
throw new Error('Plugin host connection is closed');
|
|
36
|
+
socket.write(text, error => (error ? reject(error) : resolve()));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
reject(error);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { pluginRecoverySignature } from '../sdk/pluginRecoveryDefinition.js';
|
|
2
|
+
import { loadPlugin, findPluginRegistration, invokeRegisteredPlugin, invokeRegisteredToolboxPlugin, invokeRegisteredRuntimePlugin, invokeRegisteredRecoveryPlugin, } from '../sdk/plugin.js';
|
|
3
|
+
import { pluginFileVersion } from '../pluginSource.js';
|
|
4
|
+
import { MAX_PLUGIN_RESULT_BYTES, pluginExecutionError, serializePluginExecutionError, } from './pluginExecutorProtocol.js';
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
let sequence = 0;
|
|
8
|
+
let started = false;
|
|
9
|
+
let completed = false;
|
|
10
|
+
function send(event) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
if (!process.connected || !process.send) {
|
|
13
|
+
reject(new Error('Plugin executor disconnected'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
process.send(event, error => (error ? reject(error) : resolve()));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function run(message) {
|
|
20
|
+
try {
|
|
21
|
+
const registration = message.registration ?? findPluginRegistration(message.plugin);
|
|
22
|
+
if (registration?.entry !== message.entry ||
|
|
23
|
+
pluginFileVersion(message.entry) !== message.entryVersion)
|
|
24
|
+
throw Object.assign(new Error('Plugin source changed before execution'), {
|
|
25
|
+
code: 'PLUGIN_SOURCE_CHANGED',
|
|
26
|
+
});
|
|
27
|
+
const invokePlugin = message.recovery
|
|
28
|
+
? invokeRegisteredRecoveryPlugin
|
|
29
|
+
: message.access === 'runtime'
|
|
30
|
+
? invokeRegisteredRuntimePlugin
|
|
31
|
+
: message.access === 'toolbox'
|
|
32
|
+
? invokeRegisteredToolboxPlugin
|
|
33
|
+
: invokeRegisteredPlugin;
|
|
34
|
+
const declaration = message.describe
|
|
35
|
+
? await loadPlugin(message.entry, {
|
|
36
|
+
options: {},
|
|
37
|
+
progress: () => { },
|
|
38
|
+
invoke: async () => {
|
|
39
|
+
throw new Error('Host actions are unavailable during runtime declaration');
|
|
40
|
+
},
|
|
41
|
+
}, message.plugin)
|
|
42
|
+
: undefined;
|
|
43
|
+
const migration = message.migration
|
|
44
|
+
? await loadPlugin(message.entry, {
|
|
45
|
+
options: {},
|
|
46
|
+
signal: controller.signal,
|
|
47
|
+
progress: () => { },
|
|
48
|
+
invoke: async () => {
|
|
49
|
+
throw Object.assign(new Error('Checkpoint migration cannot invoke host actions or plugins'), { code: 'PLUGIN_CHECKPOINT_MIGRATION_HOST_CALL' });
|
|
50
|
+
},
|
|
51
|
+
}, message.plugin)
|
|
52
|
+
: undefined;
|
|
53
|
+
if (migration) {
|
|
54
|
+
const definitions = migration.taskRecovery?.commands;
|
|
55
|
+
const definition = definitions && Object.hasOwn(definitions, message.invocation.command)
|
|
56
|
+
? definitions[message.invocation.command]
|
|
57
|
+
: undefined;
|
|
58
|
+
if (!definition ||
|
|
59
|
+
!message.expectedRecovery ||
|
|
60
|
+
pluginRecoverySignature(definition) !== message.expectedRecovery)
|
|
61
|
+
throw Object.assign(new Error('Migration declaration differs from the admitted target format'), { code: 'PLUGIN_RECOVERY_DECLARATION_CHANGED' });
|
|
62
|
+
}
|
|
63
|
+
const value = migration
|
|
64
|
+
? await migration.runMigration(message.invocation)
|
|
65
|
+
: declaration
|
|
66
|
+
? {
|
|
67
|
+
pageRuntime: declaration.pageRuntime,
|
|
68
|
+
stateStorage: declaration.stateStorage,
|
|
69
|
+
taskRecovery: declaration.taskRecovery,
|
|
70
|
+
}
|
|
71
|
+
: message.model
|
|
72
|
+
? await (await import('@mearl/agent-runtime/codex')).runCodexModel(message.model, {
|
|
73
|
+
signal: controller.signal,
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
onDiagnostic: metadata => {
|
|
76
|
+
void send({ type: 'diagnostic', metadata }).catch(() => process.exit(1));
|
|
77
|
+
},
|
|
78
|
+
onDelta: (delta, index, metadata) => {
|
|
79
|
+
void send({
|
|
80
|
+
type: 'progress',
|
|
81
|
+
progress: {
|
|
82
|
+
message: 'model.delta',
|
|
83
|
+
data: { delta, index, ...metadata, partial: true },
|
|
84
|
+
},
|
|
85
|
+
}).catch(() => process.exit(1));
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
: await invokePlugin(message.plugin, message.invocation, {
|
|
89
|
+
invocation: Object.freeze({
|
|
90
|
+
...message.identity,
|
|
91
|
+
host: Object.freeze(message.identity.host),
|
|
92
|
+
}),
|
|
93
|
+
...(message.registration
|
|
94
|
+
? {
|
|
95
|
+
resolveRegistration: (name) => name === message.plugin
|
|
96
|
+
? message.registration
|
|
97
|
+
: findPluginRegistration(name),
|
|
98
|
+
}
|
|
99
|
+
: {}),
|
|
100
|
+
invoke: (action, payload = {}, options = {}, onProgress) => {
|
|
101
|
+
controller.signal.throwIfAborted();
|
|
102
|
+
const id = ++sequence;
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
pending.set(id, { resolve, reject, onProgress });
|
|
105
|
+
void send({
|
|
106
|
+
type: 'invoke',
|
|
107
|
+
id,
|
|
108
|
+
action,
|
|
109
|
+
payload,
|
|
110
|
+
options,
|
|
111
|
+
...(onProgress ? { stream: true } : {}),
|
|
112
|
+
}).catch(error => {
|
|
113
|
+
pending.delete(id);
|
|
114
|
+
reject(error);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
}, {
|
|
119
|
+
...message.options,
|
|
120
|
+
signal: controller.signal,
|
|
121
|
+
source: message.identity.source,
|
|
122
|
+
onProgress: progress => {
|
|
123
|
+
void send({ type: 'progress', progress }).catch(() => process.exit(1));
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
// Business results use a separate lossless channel; diagnostics never supply the result.
|
|
127
|
+
const json = JSON.stringify({ value });
|
|
128
|
+
if (Buffer.byteLength(json) > (message.migration ? 64 * 1024 : MAX_PLUGIN_RESULT_BYTES))
|
|
129
|
+
throw Object.assign(new Error('Plugin result exceeds the execution result limit'), {
|
|
130
|
+
code: 'PLUGIN_RESULT_TOO_LARGE',
|
|
131
|
+
});
|
|
132
|
+
await send({ type: 'result', json });
|
|
133
|
+
completed = true;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
await send({ type: 'error', error: serializePluginExecutionError(error) }).catch(() => { });
|
|
137
|
+
completed = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export function handlePluginWorkerMessage(message) {
|
|
141
|
+
if (message.type === 'start') {
|
|
142
|
+
if (started)
|
|
143
|
+
return;
|
|
144
|
+
started = true;
|
|
145
|
+
void run(message);
|
|
146
|
+
}
|
|
147
|
+
else if (message.type === 'cancel') {
|
|
148
|
+
if (completed)
|
|
149
|
+
return;
|
|
150
|
+
const error = pluginExecutionError(message.error);
|
|
151
|
+
controller.abort(error);
|
|
152
|
+
for (const request of pending.values())
|
|
153
|
+
request.reject(error);
|
|
154
|
+
pending.clear();
|
|
155
|
+
}
|
|
156
|
+
else if (message.type === 'invoke-progress') {
|
|
157
|
+
const request = pending.get(message.id);
|
|
158
|
+
try {
|
|
159
|
+
request?.onProgress?.(message.progress);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
pending.delete(message.id);
|
|
163
|
+
request?.reject(error instanceof Error ? error : new Error(String(error)));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (message.type === 'response') {
|
|
167
|
+
const request = pending.get(message.id);
|
|
168
|
+
pending.delete(message.id);
|
|
169
|
+
if (message.error)
|
|
170
|
+
request?.reject(pluginExecutionError(message.error));
|
|
171
|
+
else
|
|
172
|
+
request?.resolve(message.result);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PluginModelDiagnostic } from '../../generated/plugin-models.js';
|
|
2
|
+
import type { PluginDiagnosticFailure, PluginDiagnosticOptions, PluginDiagnosticPage, PluginDiagnosticPageBinding, PluginDiagnosticReport } from '../../generated/plugin-diagnostics.js';
|
|
3
|
+
import type { PluginTaskRecord } from './pluginTaskStore.js';
|
|
4
|
+
import type { PluginTaskScope } from '../pluginTaskProtocol.js';
|
|
5
|
+
import type { PluginExecutionHost } from '../../generated/plugin-execution.js';
|
|
6
|
+
export declare const MAX_PLUGIN_DIAGNOSTIC_BYTES: number;
|
|
7
|
+
export declare function normalizeDiagnosticOptions(value: unknown): PluginDiagnosticOptions;
|
|
8
|
+
export declare function diagnosticFailure(value: {
|
|
9
|
+
code?: string;
|
|
10
|
+
message?: string;
|
|
11
|
+
} | undefined, options: PluginDiagnosticOptions): PluginDiagnosticFailure | undefined;
|
|
12
|
+
/** A worker can publish only this small operational vocabulary. */
|
|
13
|
+
export declare function normalizeModelDiagnostic(value: unknown, previous?: PluginModelDiagnostic): PluginModelDiagnostic;
|
|
14
|
+
export declare function diagnosticBinding(value: {
|
|
15
|
+
instanceId: string;
|
|
16
|
+
target: {
|
|
17
|
+
browserSessionId: string;
|
|
18
|
+
tabId: number;
|
|
19
|
+
frameId: string | number;
|
|
20
|
+
documentId: string;
|
|
21
|
+
};
|
|
22
|
+
}): PluginDiagnosticPageBinding;
|
|
23
|
+
export declare function buildDiagnosticReport(host: PluginExecutionHost, scope: PluginTaskScope, options: PluginDiagnosticOptions, scan: {
|
|
24
|
+
records: PluginTaskRecord[];
|
|
25
|
+
limited: boolean;
|
|
26
|
+
}, snapshots: PluginDiagnosticPage[]): PluginDiagnosticReport;
|