@mearl/client 3.0.0-beta.8 → 3.0.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.
@@ -1,8 +1,10 @@
1
1
  import { diagnosticBinding } from './pluginDiagnostics.js';
2
2
  import { createHash, randomUUID } from 'node:crypto';
3
3
  import { EventEmitter } from 'node:events';
4
- import { readFileSync, statSync } from 'node:fs';
5
- import { isAbsolute, join } from 'node:path';
4
+ import { lstatSync, readFileSync, statSync } from 'node:fs';
5
+ import { dirname, isAbsolute, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { keyedDir } from '@mearl/daemon-core';
6
8
  import { capturePluginRevision } from './pluginRevision.js';
7
9
  import { findPluginRegistration } from '../sdk/plugin.js';
8
10
  import { UnifiedClient, decodeBrowserTarget } from '../../unified.js';
@@ -361,6 +363,14 @@ export class PluginPageManager {
361
363
  throw failure('PLUGIN_PAGE_TARGET_CHANGED', 'Nested plugin must use the inherited document');
362
364
  if (!matchesPluginPageURL(runtime.matches, target.url))
363
365
  throw failure('PLUGIN_PAGE_URL_NOT_ALLOWED', 'Plugin page URL is outside its declared matches');
366
+ if (target.url.startsWith('file:')) {
367
+ const file = fileURLToPath(target.url);
368
+ const directory = keyedDir(join('plugin-previews', plugin));
369
+ if (dirname(file) !== directory ||
370
+ !lstatSync(directory, { throwIfNoEntry: false })?.isDirectory() ||
371
+ !lstatSync(file, { throwIfNoEntry: false })?.isFile())
372
+ throw failure('PLUGIN_PAGE_URL_NOT_ALLOWED', 'Local preview must be a regular file in the plugin preview directory');
373
+ }
364
374
  const existing = [...this.instances.values()].find(item => item.value.state === 'active' &&
365
375
  item.value.plugin === plugin &&
366
376
  sameTarget(item.value.target, target));
@@ -781,7 +791,7 @@ export class PluginPageManager {
781
791
  try {
782
792
  await this.validate(record);
783
793
  const binding = record.runtime.events[delivery.name];
784
- const started = this.start(record, binding.command, { event: { name: delivery.name, payload: delivery.payload, sequence: delivery.sequence } }, `page:${record.value.instanceId}:${delivery.sequence}`);
794
+ const started = this.start(record, binding.command, { event: { name: delivery.name, payload: delivery.payload, sequence: delivery.sequence } }, `page:${record.value.instanceId}:${delivery.sequence}`, undefined, binding.executionTimeoutMs);
785
795
  delivery.taskId = started.taskId;
786
796
  this.append(record, {
787
797
  type: 'running',
@@ -813,7 +823,7 @@ export class PluginPageManager {
813
823
  });
814
824
  }
815
825
  }
816
- start(record, command, input, key, parentIdentity) {
826
+ start(record, command, input, key, parentIdentity, executionTimeoutMs) {
817
827
  // The runtime delegates through the instance's current connection. Its identity follows
818
828
  // that connection so binding cannot replace the connection with this forwarding proxy.
819
829
  const bridge = {
@@ -838,6 +848,7 @@ export class PluginPageManager {
838
848
  environment: record.execution.environment,
839
849
  options: record.options,
840
850
  idempotencyKey: key,
851
+ executionTimeoutMs,
841
852
  }, bridge, {
842
853
  ...(parentIdentity?.taskId ? { parentTaskId: parentIdentity.taskId, parentIdentity } : {}),
843
854
  revision: record.revision,
@@ -1,3 +1,4 @@
1
+ import type { CommandRunner } from '@mearl/daemon-core';
1
2
  import type { PluginTaskRecoveryDeclaration, PluginTaskCheckpoint, PluginCheckpointMigrationInput } from '@mearl/browser-core';
2
3
  import type { PluginStateDeclaration } from '@mearl/browser-core';
3
4
  import type { PluginModelRequest, PluginModelResult } from '@mearl/browser-core';
@@ -71,6 +72,9 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
71
72
  onInvocation?: (identity: PluginInvocationIdentity | undefined) => void;
72
73
  }, pluginName?: string): Promise<{
73
74
  name: string | undefined;
75
+ setup: ((context: {
76
+ runCommand: CommandRunner;
77
+ }) => unknown | Promise<unknown>) | undefined;
74
78
  description: string | undefined;
75
79
  defaultCommand: string | undefined;
76
80
  toolbox: PluginToolboxContribution | undefined;
@@ -90,6 +94,9 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'opti
90
94
  }>;
91
95
  export declare function loadRegisteredPlugin(name: string, context: PluginExecutionContext, options?: PluginInvokeOptions): Promise<{
92
96
  name: string | undefined;
97
+ setup: ((context: {
98
+ runCommand: CommandRunner;
99
+ }) => unknown | Promise<unknown>) | undefined;
93
100
  description: string | undefined;
94
101
  defaultCommand: string | undefined;
95
102
  toolbox: PluginToolboxContribution | undefined;
@@ -153,6 +153,7 @@ export async function loadPlugin(entry, context, pluginName) {
153
153
  let taskRecovery;
154
154
  let appProfiles;
155
155
  let profileSources;
156
+ let setup;
156
157
  const registerCommand = (signature, handler, description = '', examples = [], toolboxOnly = false, runtimeOnly = false) => {
157
158
  if (!registering)
158
159
  throw new Error('命令只能在入口注册阶段声明');
@@ -313,6 +314,9 @@ export async function loadPlugin(entry, context, pluginName) {
313
314
  if (module.description !== undefined && typeof module.description !== 'string')
314
315
  throw new Error('入口的 description 导出必须为字符串');
315
316
  description = module.description;
317
+ if (module.setup !== undefined && typeof module.setup !== 'function')
318
+ throw new Error('入口的 setup 导出必须为函数');
319
+ setup = module.setup;
316
320
  if (module.defaultCommand !== undefined && typeof module.defaultCommand !== 'string')
317
321
  throw new Error('入口的 defaultCommand 导出必须为字符串');
318
322
  defaultCommand = module.defaultCommand;
@@ -535,6 +539,7 @@ export async function loadPlugin(entry, context, pluginName) {
535
539
  };
536
540
  return {
537
541
  name,
542
+ setup,
538
543
  description,
539
544
  defaultCommand,
540
545
  toolbox,
@@ -37,6 +37,8 @@ export function normalizePluginPageRuntime(value) {
37
37
  (!Number.isSafeInteger(event.maxPending) ||
38
38
  event.maxPending < 1 ||
39
39
  event.maxPending > 64)) ||
40
+ (event.executionTimeoutMs !== undefined &&
41
+ (!Number.isSafeInteger(event.executionTimeoutMs) || event.executionTimeoutMs < 1 || event.executionTimeoutMs > 300000)) ||
40
42
  event.schema.$async)
41
43
  throw new Error(`Invalid page event binding: ${name}`);
42
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/client",
3
- "version": "3.0.0-beta.8",
3
+ "version": "3.0.0",
4
4
  "description": "Unified Mearl SDK & CLI for local and remote browsers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -47,6 +47,7 @@
47
47
  "mearl": "./dist/bin.js"
48
48
  },
49
49
  "files": [
50
+ "README.en.md",
50
51
  "dist",
51
52
  "!dist/**/*.map"
52
53
  ],
@@ -67,11 +68,11 @@
67
68
  "commander": "^14.0.3",
68
69
  "fastest-levenshtein": "^1.0.16",
69
70
  "ws": "^8.18.0",
70
- "@mearl/daemon-core": "3.0.0-beta.8",
71
- "@mearl/cloud-types": "3.0.0-beta.8",
72
- "@mearl/provider": "3.0.0-beta.8",
73
- "@mearl/agent-runtime": "3.0.0-beta.8",
74
- "@mearl/browser-core": "3.0.0-beta.8"
71
+ "@mearl/daemon-core": "3.0.0",
72
+ "@mearl/cloud-types": "3.0.0",
73
+ "@mearl/browser-core": "3.0.0",
74
+ "@mearl/agent-runtime": "3.0.0",
75
+ "@mearl/provider": "3.0.0"
75
76
  },
76
77
  "devDependencies": {
77
78
  "@types/node": "^24.9.1",