@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.
Files changed (130) hide show
  1. package/README.md +153 -3
  2. package/dist/actionTimeouts.js +1 -1
  3. package/dist/artifacts.d.ts +1 -1
  4. package/dist/browserRegistry.d.ts +1 -1
  5. package/dist/check.d.ts +1 -1
  6. package/dist/cli.js +14 -8
  7. package/dist/commands.d.ts +23 -14
  8. package/dist/commands.js +24 -4
  9. package/dist/{generated-app-profile-definition.d.ts → generated/app-profile-definition.d.ts} +2 -2
  10. package/dist/{generated-app-profile-runtime-types.d.ts → generated/app-profile-runtime-types.d.ts} +2 -2
  11. package/dist/{generated-browser-action-protocol.d.ts → generated/browser-action-protocol.d.ts} +11 -1
  12. package/dist/{generated-browser-action-protocol.js → generated/browser-action-protocol.js} +3 -0
  13. package/dist/generated/plugin-diagnostics.d.ts +110 -0
  14. package/dist/generated/plugin-diagnostics.js +1 -0
  15. package/dist/generated/plugin-execution.d.ts +27 -0
  16. package/dist/generated/plugin-execution.js +1 -0
  17. package/dist/generated/plugin-models.d.ts +64 -0
  18. package/dist/generated/plugin-models.js +1 -0
  19. package/dist/generated/plugin-pages.d.ts +76 -0
  20. package/dist/generated/plugin-pages.js +59 -0
  21. package/dist/generated/plugin-recovery.d.ts +32 -0
  22. package/dist/generated/plugin-recovery.js +1 -0
  23. package/dist/generated/plugin-state.d.ts +50 -0
  24. package/dist/generated/plugin-state.js +1 -0
  25. package/dist/generated/plugin-tasks.d.ts +74 -0
  26. package/dist/generated/plugin-tasks.js +7 -0
  27. package/dist/generated/toolbox-icon.d.ts +10 -0
  28. package/dist/generated/toolbox-icon.js +37 -0
  29. package/dist/index.d.ts +15 -4
  30. package/dist/index.js +7 -1
  31. package/dist/pageEvalArguments.d.ts +1 -1
  32. package/dist/pageEvalArguments.js +1 -1
  33. package/dist/plugin-task-host.d.ts +1 -0
  34. package/dist/plugin-task-host.js +14 -0
  35. package/dist/plugin-worker.d.ts +1 -0
  36. package/dist/plugin-worker.js +17 -0
  37. package/dist/plugin.d.ts +1 -65
  38. package/dist/plugin.js +1 -391
  39. package/dist/pluginCliOutput.d.ts +2 -0
  40. package/dist/pluginCliOutput.js +7 -0
  41. package/dist/pluginModelSessionWorker.d.ts +23 -0
  42. package/dist/pluginModelSessionWorker.js +60 -0
  43. package/dist/plugins/clients/pluginHostConnection.d.ts +35 -0
  44. package/dist/plugins/clients/pluginHostConnection.js +256 -0
  45. package/dist/plugins/clients/pluginModels.d.ts +17 -0
  46. package/dist/plugins/clients/pluginModels.js +47 -0
  47. package/dist/plugins/clients/pluginPages.d.ts +21 -0
  48. package/dist/plugins/clients/pluginPages.js +46 -0
  49. package/dist/plugins/clients/pluginState.d.ts +26 -0
  50. package/dist/plugins/clients/pluginState.js +28 -0
  51. package/dist/plugins/clients/pluginTasks.d.ts +51 -0
  52. package/dist/plugins/clients/pluginTasks.js +181 -0
  53. package/dist/{officialPluginInstaller.js → plugins/officialPluginInstaller.js} +1 -3
  54. package/dist/{pluginPageBundle.js → plugins/pluginPageBundle.js} +74 -17
  55. package/dist/{pluginRegistry.d.ts → plugins/pluginRegistry.d.ts} +3 -4
  56. package/dist/{pluginRegistry.js → plugins/pluginRegistry.js} +16 -15
  57. package/dist/plugins/pluginTaskProtocol.d.ts +47 -0
  58. package/dist/plugins/pluginTaskProtocol.js +1 -0
  59. package/dist/plugins/pluginTaskWire.d.ts +4 -0
  60. package/dist/plugins/pluginTaskWire.js +42 -0
  61. package/dist/plugins/runtime/pluginCommandWorker.d.ts +2 -0
  62. package/dist/plugins/runtime/pluginCommandWorker.js +174 -0
  63. package/dist/plugins/runtime/pluginDiagnostics.d.ts +26 -0
  64. package/dist/plugins/runtime/pluginDiagnostics.js +309 -0
  65. package/dist/plugins/runtime/pluginExecution.d.ts +21 -0
  66. package/dist/plugins/runtime/pluginExecution.js +43 -0
  67. package/dist/plugins/runtime/pluginExecutor.d.ts +29 -0
  68. package/dist/plugins/runtime/pluginExecutor.js +223 -0
  69. package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +55 -0
  70. package/dist/plugins/runtime/pluginExecutorProtocol.js +47 -0
  71. package/dist/plugins/runtime/pluginExecutorSupervisor.d.ts +4 -0
  72. package/dist/plugins/runtime/pluginExecutorSupervisor.js +63 -0
  73. package/dist/plugins/runtime/pluginHost.d.ts +20 -0
  74. package/dist/plugins/runtime/pluginHost.js +53 -0
  75. package/dist/plugins/runtime/pluginHostFiles.d.ts +1 -0
  76. package/dist/plugins/runtime/pluginHostFiles.js +21 -0
  77. package/dist/plugins/runtime/pluginHostPaths.d.ts +11 -0
  78. package/dist/plugins/runtime/pluginHostPaths.js +22 -0
  79. package/dist/plugins/runtime/pluginHostServer.d.ts +7 -0
  80. package/dist/plugins/runtime/pluginHostServer.js +252 -0
  81. package/dist/plugins/runtime/pluginModelCatalog.d.ts +14 -0
  82. package/dist/plugins/runtime/pluginModelCatalog.js +51 -0
  83. package/dist/plugins/runtime/pluginModelSessions.d.ts +16 -0
  84. package/dist/plugins/runtime/pluginModelSessions.js +234 -0
  85. package/dist/plugins/runtime/pluginPageManager.d.ts +57 -0
  86. package/dist/plugins/runtime/pluginPageManager.js +877 -0
  87. package/dist/plugins/runtime/pluginProcessTree.d.ts +2 -0
  88. package/dist/plugins/runtime/pluginProcessTree.js +31 -0
  89. package/dist/plugins/runtime/pluginRevision.d.ts +14 -0
  90. package/dist/plugins/runtime/pluginRevision.js +227 -0
  91. package/dist/plugins/runtime/pluginRuntimeCleanup.d.ts +2 -0
  92. package/dist/plugins/runtime/pluginRuntimeCleanup.js +55 -0
  93. package/dist/plugins/runtime/pluginStateManager.d.ts +24 -0
  94. package/dist/plugins/runtime/pluginStateManager.js +132 -0
  95. package/dist/plugins/runtime/pluginStateStore.d.ts +57 -0
  96. package/dist/plugins/runtime/pluginStateStore.js +396 -0
  97. package/dist/plugins/runtime/pluginTaskManager.d.ts +92 -0
  98. package/dist/plugins/runtime/pluginTaskManager.js +1080 -0
  99. package/dist/plugins/runtime/pluginTaskStore.d.ts +53 -0
  100. package/dist/plugins/runtime/pluginTaskStore.js +319 -0
  101. package/dist/plugins/runtime/pythonPlugin.d.ts +31 -0
  102. package/dist/{pythonPlugin.js → plugins/runtime/pythonPlugin.js} +236 -31
  103. package/dist/plugins/sdk/plugin.d.ts +118 -0
  104. package/dist/plugins/sdk/plugin.js +672 -0
  105. package/dist/{pluginAppProfiles.d.ts → plugins/sdk/pluginAppProfiles.d.ts} +1 -1
  106. package/dist/{pluginAppProfiles.js → plugins/sdk/pluginAppProfiles.js} +2 -2
  107. package/dist/plugins/sdk/pluginModelDefinition.d.ts +6 -0
  108. package/dist/plugins/sdk/pluginModelDefinition.js +75 -0
  109. package/dist/plugins/sdk/pluginPageDefinition.d.ts +3 -0
  110. package/dist/plugins/sdk/pluginPageDefinition.js +62 -0
  111. package/dist/plugins/sdk/pluginRecoveryDefinition.d.ts +3 -0
  112. package/dist/plugins/sdk/pluginRecoveryDefinition.js +56 -0
  113. package/dist/plugins/sdk/pluginStateDefinition.d.ts +4 -0
  114. package/dist/plugins/sdk/pluginStateDefinition.js +68 -0
  115. package/dist/runtimePaths.d.ts +4 -0
  116. package/dist/runtimePaths.js +8 -0
  117. package/dist/socket.d.ts +2 -2
  118. package/dist/socket.js +10 -6
  119. package/dist/unified.d.ts +3 -2
  120. package/dist/unified.js +83 -2
  121. package/package.json +7 -3
  122. package/dist/pythonPlugin.d.ts +0 -20
  123. /package/dist/{generated-app-profile-definition.js → generated/app-profile-definition.js} +0 -0
  124. /package/dist/{generated-app-profile-runtime-types.js → generated/app-profile-runtime-types.js} +0 -0
  125. /package/dist/{officialPluginCatalog.d.ts → plugins/officialPluginCatalog.d.ts} +0 -0
  126. /package/dist/{officialPluginCatalog.js → plugins/officialPluginCatalog.js} +0 -0
  127. /package/dist/{officialPluginInstaller.d.ts → plugins/officialPluginInstaller.d.ts} +0 -0
  128. /package/dist/{pluginPageBundle.d.ts → plugins/pluginPageBundle.d.ts} +0 -0
  129. /package/dist/{pluginSource.d.ts → plugins/pluginSource.d.ts} +0 -0
  130. /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 { createRequire } from 'node:module';
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 || !Number.isSafeInteger(target.tabId) || target.tabId < 0 ||
91
- typeof target.runtimeId !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(target.runtimeId) ||
92
- typeof listener !== 'function') throw new Error('Invalid event subscription');
93
- const subscriptionId = String(++subscriptionSequence);
94
- pageEventListeners.set(subscriptionId, listener);
95
- try { await requestHost('subscribe', { source, target, subscriptionId }); }
96
- catch (error) { pageEventListeners.delete(subscriptionId); throw error; }
97
- return () => {
98
- if (!pageEventListeners.delete(subscriptionId)) return;
99
- void requestHost('unsubscribe', { subscriptionId }).catch(() => {});
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 api = { invoke, subscribe, onContextChange, ready: async () => { await ready; return context; }, runAgentTask, setAgentGuides, setAgentPanelVisible, setQueryParam };
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
- if (response?.type === 'event' && typeof response.subscriptionId === 'string') {
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 './generated-app-profile-definition.js';
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 './generated-app-profile-definition.js';
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 './commands.js';
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 && (!/^[a-z][a-z0-9-]*$/.test(icon) || icon.length > 40)) {
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 (pageCandidate.lifecycle !== undefined) {
213
- const lifecycle = pageCandidate.lifecycle;
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,2 @@
1
+ import type { PluginWorkerRequest } from './pluginExecutorProtocol.js';
2
+ export declare function handlePluginWorkerMessage(message: PluginWorkerRequest): void;
@@ -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;