@mearl/client 2.22.0 → 2.22.2
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 +92 -7
- package/dist/cli.js +13 -41
- package/dist/commandHelp.d.ts +3 -0
- package/dist/commandHelp.js +79 -0
- package/dist/commands.d.ts +10 -10
- package/dist/commands.js +4 -3
- package/dist/generated/browser-action-protocol.d.ts +1 -1
- package/dist/generated/data-structure.d.ts +8 -0
- package/dist/generated/data-structure.js +58 -0
- package/dist/generated/plugin-host-build.d.ts +1 -0
- package/dist/generated/plugin-host-build.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +1 -0
- package/dist/plugins/clients/pluginHostConnection.d.ts +2 -0
- package/dist/plugins/clients/pluginHostConnection.js +3 -1
- package/dist/plugins/pluginRegistry.d.ts +1 -1
- package/dist/plugins/pluginRegistry.js +12 -4
- package/dist/plugins/runtime/pluginCommandWorker.js +8 -6
- package/dist/plugins/runtime/pluginExecutor.d.ts +5 -0
- package/dist/plugins/runtime/pluginExecutor.js +15 -2
- package/dist/plugins/runtime/pluginExecutorProtocol.d.ts +4 -0
- package/dist/plugins/runtime/pluginExecutorSupervisor.js +2 -1
- package/dist/plugins/runtime/pluginHostServer.js +8 -0
- package/dist/plugins/runtime/pluginPageManager.js +0 -4
- package/dist/plugins/runtime/pluginRevision.js +2 -52
- package/dist/plugins/runtime/pluginStateStore.js +3 -8
- package/dist/plugins/runtime/pluginTaskManager.js +3 -7
- package/dist/plugins/sdk/plugin.js +5 -1
- package/dist/plugins/sdk/pluginModelDefinition.js +0 -6
- package/dist/plugins/sdk/pluginPageDefinition.d.ts +0 -1
- package/dist/plugins/sdk/pluginPageDefinition.js +2 -18
- package/dist/plugins/sdk/pluginRecoveryDefinition.js +0 -3
- package/dist/plugins/sdk/pluginStateDefinition.d.ts +0 -1
- package/dist/plugins/sdk/pluginStateDefinition.js +0 -11
- package/dist/plugins/skillManifest.d.ts +5 -0
- package/dist/plugins/skillManifest.js +35 -0
- package/dist/runtimePaths.d.ts +1 -0
- package/dist/runtimePaths.js +1 -0
- package/dist/script.d.ts +30 -0
- package/dist/script.js +260 -0
- package/dist/scriptCli.d.ts +1 -0
- package/dist/scriptCli.js +94 -0
- package/dist/scriptErrors.d.ts +8 -0
- package/dist/scriptErrors.js +21 -0
- package/dist/scriptWorker.d.ts +1 -0
- package/dist/scriptWorker.js +50 -0
- package/dist/sdkMethods.d.ts +3 -0
- package/dist/sdkMethods.js +9 -0
- package/package.json +11 -8
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSE
|
|
|
16
16
|
export type { ActionStep, AppProfileName, BrowserAction, BrowserActionMap, BrowserActionRequest, BrowserActionResponse, BrowserCapabilities, BrowserCapabilityAction, BrowserCloseParams, BrowserCommandAction, BrowserInfo, BrowserInfoBase, BrowserJsonValue, BrowserLaunchParams, BrowserProviderOperation, BrowserListResult, BrowserPairingInfo, BrowserReleaseFailure, BrowserReleaseResult, BrowserProviderDefinition, BrowserProviderIdentity, BrowserProviderInputSchema, BrowserProviderListResult, BrowserReference, BrowserDetails, BrowserSource, BrowserStatus, BrowserScreenshotKind, BrowserTransport, CaptureCheckpointParams, CaptureCheckpointResult, BrowserDevicePresetName, BrowserEmulationOptions, ExtensionBrowserAction, GetCookieParams, GetCookieResult, GetEventsParams, GetLogsParams, GetMocksParams, GetRequestsParams, ManagedBrowserInfo, PageClickParams, PageDragEndpoint, PageDragParams, PageDragPoint, PageActAction, PageDiagnosticsOptions, PageDiagnosticsSetting, PageEvalParams, PageFramesParams, PageHoverParams, PageInspectFilter, PageInspectParams, PageNavigateParams, PageObservationWaitOptions, PagePressParams, PageScreenshotParams, PageScrollParams, PageSnapshotParams, PageSnapshotQuery, PageTypeParams, PageUploadParams, PageWaitParams, RegularBrowserInfo, RequestDomainPermissionParams, RulesResult, RunActionName, RunActionsParams, SelectBrowserParams, SelectBrowserResult, SendRequestFile, SendRequestBinaryBody, SendRequestFormValue, SendMtopRequestParams, SendRequestParams, SetAppProfileParams, SetCookieParams, SetCookieResult, SetDeviceEmulationParams, SetMockParams, SetRuleParams, SetTimezoneParams, SetTimezoneResult, TabCloseParams, TabOpenParams, } from './generated/browser-action-protocol.js';
|
|
17
17
|
import { type UnifiedInvokeOptions } from './unified.js';
|
|
18
18
|
import type * as Protocol from './generated/browser-action-protocol.js';
|
|
19
|
+
export { getStructure as structure, type StructureOptions } from './generated/data-structure.js';
|
|
19
20
|
export declare function tabCheckpoint(params: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>;
|
|
20
21
|
/** Compatibility wrapper for tabCheckpoint. */
|
|
21
22
|
export declare function captureCheckpoint(params: Protocol.CaptureCheckpointParams, options?: UnifiedInvokeOptions): Promise<Protocol.BrowserActionResponse<'tab_checkpoint'>>;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { executeRegisteredPlugin, } from './plugins/sdk/plugin.js';
|
|
|
11
11
|
export { UnifiedClient, UnifiedClient as MearlClient, decodeBrowserTarget, invoke, invokePlugin, listCloudConnectors, } from './unified.js';
|
|
12
12
|
export { APP_PROFILE_NAMES, BROWSER_DEVICE_PRESET_NAMES, BROWSER_ACTIONS, BROWSER_CAPABILITY_ACTIONS, BROWSER_COMMAND_ACTIONS, EXTENSION_BROWSER_ACTIONS, PAGE_ACT_ACTIONS, SEND_REQUEST_MAX_FILES, SEND_REQUEST_MAX_TOTAL_BASE64_FILE_BYTES, SEND_REQUEST_MAX_TOTAL_FILE_BYTES, isBrowserAction, isBrowserCapabilityAction, isBrowserCommandAction, isExtensionBrowserAction, } from './generated/browser-action-protocol.js';
|
|
13
13
|
import { invoke } from './unified.js';
|
|
14
|
+
export { getStructure as structure } from './generated/data-structure.js';
|
|
14
15
|
export function tabCheckpoint(params, options) {
|
|
15
16
|
return invoke('tab_checkpoint', params, options);
|
|
16
17
|
}
|
package/dist/plugin.d.ts
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -243,7 +243,9 @@ export class PluginHostConnection {
|
|
|
243
243
|
this.connection = undefined;
|
|
244
244
|
});
|
|
245
245
|
}
|
|
246
|
-
|
|
246
|
+
const connection = await this.connection;
|
|
247
|
+
// The handshake protocol defines compatibility; build identity is diagnostic metadata.
|
|
248
|
+
return connection.request(method, input, timeoutMs);
|
|
247
249
|
}
|
|
248
250
|
disconnect() {
|
|
249
251
|
this.generation++;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AppProfileContribution } from '../generated/app-profile-definition.js';
|
|
2
2
|
export declare class PluginError extends Error {
|
|
3
3
|
readonly code: string;
|
|
4
|
-
constructor(code: string, message: string);
|
|
4
|
+
constructor(code: string, message: string, options?: ErrorOptions);
|
|
5
5
|
}
|
|
6
6
|
export interface PluginToolboxSandboxPage {
|
|
7
7
|
type: 'sandbox';
|
|
@@ -9,6 +9,7 @@ import { buildPluginPageBundle, PLUGIN_PAGE_CHUNK_BYTES, PLUGIN_PAGE_RUNTIME_HAS
|
|
|
9
9
|
import { pluginFileVersion } from './pluginSource.js';
|
|
10
10
|
const RESERVED_NAMES = new Set([
|
|
11
11
|
'check',
|
|
12
|
+
'run',
|
|
12
13
|
'help',
|
|
13
14
|
'connector_list',
|
|
14
15
|
'capture_checkpoint',
|
|
@@ -17,8 +18,8 @@ const RESERVED_NAMES = new Set([
|
|
|
17
18
|
]);
|
|
18
19
|
export class PluginError extends Error {
|
|
19
20
|
code;
|
|
20
|
-
constructor(code, message) {
|
|
21
|
-
super(`${code}: ${message}
|
|
21
|
+
constructor(code, message, options) {
|
|
22
|
+
super(`${code}: ${message}`, options);
|
|
22
23
|
this.code = code;
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -405,8 +406,15 @@ export function findPluginRegistration(name) {
|
|
|
405
406
|
...(source ? { source } : {}),
|
|
406
407
|
};
|
|
407
408
|
}
|
|
408
|
-
catch {
|
|
409
|
-
|
|
409
|
+
catch (cause) {
|
|
410
|
+
const reason = cause instanceof SyntaxError
|
|
411
|
+
? 'JSON 格式错误'
|
|
412
|
+
: cause instanceof Error
|
|
413
|
+
? cause.message
|
|
414
|
+
: '未知校验错误';
|
|
415
|
+
throw new PluginError('PLUGIN_REGISTRY_INVALID', `无效的插件记录: ${file} (${reason})`, {
|
|
416
|
+
cause,
|
|
417
|
+
});
|
|
410
418
|
}
|
|
411
419
|
}
|
|
412
420
|
export function readPluginPageBundleChunk(name, hash, index) {
|
|
@@ -31,7 +31,7 @@ async function run(message) {
|
|
|
31
31
|
: message.access === 'toolbox'
|
|
32
32
|
? invokeRegisteredToolboxPlugin
|
|
33
33
|
: invokeRegisteredPlugin;
|
|
34
|
-
const declaration = message.describe
|
|
34
|
+
const declaration = message.describe || message.help
|
|
35
35
|
? await loadPlugin(message.entry, {
|
|
36
36
|
options: {},
|
|
37
37
|
progress: () => { },
|
|
@@ -63,11 +63,13 @@ async function run(message) {
|
|
|
63
63
|
const value = migration
|
|
64
64
|
? await migration.runMigration(message.invocation)
|
|
65
65
|
: declaration
|
|
66
|
-
?
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
? message.help
|
|
67
|
+
? declaration.help(message.help.namespace, message.help.command)
|
|
68
|
+
: {
|
|
69
|
+
pageRuntime: declaration.pageRuntime,
|
|
70
|
+
stateStorage: declaration.stateStorage,
|
|
71
|
+
taskRecovery: declaration.taskRecovery,
|
|
72
|
+
}
|
|
71
73
|
: message.model
|
|
72
74
|
? await (await import('@mearl/agent-runtime/codex')).runCodexModel(message.model, {
|
|
73
75
|
signal: controller.signal,
|
|
@@ -6,6 +6,11 @@ export interface PluginExecutorDependencies {
|
|
|
6
6
|
registration?: PluginRegistration;
|
|
7
7
|
}
|
|
8
8
|
export interface PluginExecutorOptions extends PluginInvokeOptions {
|
|
9
|
+
help?: {
|
|
10
|
+
namespace: string;
|
|
11
|
+
command?: string;
|
|
12
|
+
};
|
|
13
|
+
onOutput?: (stream: 'stdout' | 'stderr', chunk: Buffer) => void;
|
|
9
14
|
describe?: boolean;
|
|
10
15
|
model?: PluginModelRequest;
|
|
11
16
|
recovery?: boolean;
|
|
@@ -8,7 +8,7 @@ import { stopPluginProcessTree } from './pluginProcessTree.js';
|
|
|
8
8
|
import { processIdentity } from '@mearl/daemon-core';
|
|
9
9
|
/** Executes registered trusted code in a process whose lifetime belongs to this invocation. */
|
|
10
10
|
export async function executeRegisteredPlugin(plugin, invocation, context, options = {}, dependencies = {}) {
|
|
11
|
-
const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
|
|
11
|
+
const { signal, onProgress, source, access = 'public', executionTimeoutMs, cancellationGraceMs = 1000, workerPath, cwd, environment, describe, help, onOutput, model, recovery, migration, expectedRecovery, onExecutorStarted, onExecutorStopped, onDiagnostic, ...invokeOptions } = options;
|
|
12
12
|
for (const [name, value] of [
|
|
13
13
|
['executionTimeoutMs', executionTimeoutMs],
|
|
14
14
|
['cancellationGraceMs', cancellationGraceMs],
|
|
@@ -25,7 +25,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
|
|
|
25
25
|
const entryVersion = pluginFileVersion(registration.entry);
|
|
26
26
|
return new Promise((resolve, reject) => {
|
|
27
27
|
const child = fork(workerPath ?? PLUGIN_WORKER_PATH, [], {
|
|
28
|
-
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
28
|
+
stdio: ['ignore', onOutput ? 'pipe' : 'ignore', 'pipe', 'ipc'],
|
|
29
29
|
serialization: 'advanced',
|
|
30
30
|
execArgv: [],
|
|
31
31
|
detached: process.platform !== 'win32',
|
|
@@ -46,6 +46,18 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
|
|
|
46
46
|
child.stderr?.on('data', chunk => {
|
|
47
47
|
stderr = `${stderr}${chunk}`.slice(-65536);
|
|
48
48
|
});
|
|
49
|
+
if (onOutput) {
|
|
50
|
+
for (const stream of ['stdout', 'stderr']) {
|
|
51
|
+
child[stream]?.on('data', chunk => {
|
|
52
|
+
try {
|
|
53
|
+
onOutput(stream, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
stop(error instanceof Error ? error : new Error(String(error)));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
49
61
|
const send = (message) => {
|
|
50
62
|
if (!child.connected || exited)
|
|
51
63
|
return;
|
|
@@ -209,6 +221,7 @@ export async function executeRegisteredPlugin(plugin, invocation, context, optio
|
|
|
209
221
|
entryVersion,
|
|
210
222
|
access,
|
|
211
223
|
describe,
|
|
224
|
+
help,
|
|
212
225
|
model,
|
|
213
226
|
recovery,
|
|
214
227
|
migration,
|
|
@@ -13,6 +13,10 @@ export type PluginWorkerRequest = {
|
|
|
13
13
|
access: 'public' | 'toolbox' | 'runtime';
|
|
14
14
|
registration?: PluginRegistration;
|
|
15
15
|
describe?: boolean;
|
|
16
|
+
help?: {
|
|
17
|
+
namespace: string;
|
|
18
|
+
command?: string;
|
|
19
|
+
};
|
|
16
20
|
model?: PluginModelRequest;
|
|
17
21
|
recovery?: boolean;
|
|
18
22
|
migration?: boolean;
|
|
@@ -3,13 +3,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
/** Ownership monitoring runs outside the event loop that executes plugin code. */
|
|
4
4
|
export function supervisePluginExecutor(workerUrl) {
|
|
5
5
|
const child = fork(fileURLToPath(workerUrl), ['--command'], {
|
|
6
|
-
stdio: ['ignore', '
|
|
6
|
+
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
|
7
7
|
serialization: 'advanced',
|
|
8
8
|
execArgv: [],
|
|
9
9
|
});
|
|
10
10
|
let ending = false;
|
|
11
11
|
let forwarded = Promise.resolve();
|
|
12
12
|
child.stderr?.on('data', chunk => process.stderr.write(chunk));
|
|
13
|
+
child.stdout?.on('data', chunk => process.stdout.write(chunk));
|
|
13
14
|
const terminate = () => {
|
|
14
15
|
if (process.platform === 'win32') {
|
|
15
16
|
try {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CLIENT_VERSION } from '../../runtimePaths.js';
|
|
2
|
+
import { PLUGIN_HOST_BUILD_ID } from '../../generated/plugin-host-build.js';
|
|
1
3
|
import { randomUUID } from 'node:crypto';
|
|
2
4
|
import { createServer } from 'node:net';
|
|
3
5
|
import { chmodSync, rmSync } from 'node:fs';
|
|
@@ -17,6 +19,8 @@ export async function startPluginHostServer(configuration = {}) {
|
|
|
17
19
|
const owner = {
|
|
18
20
|
pid: process.pid,
|
|
19
21
|
protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
|
|
22
|
+
version: CLIENT_VERSION,
|
|
23
|
+
buildId: PLUGIN_HOST_BUILD_ID,
|
|
20
24
|
processIdentity: processIdentity(process.pid),
|
|
21
25
|
};
|
|
22
26
|
if (!claimRecord(paths.lease, owner))
|
|
@@ -103,6 +107,8 @@ export async function startPluginHostServer(configuration = {}) {
|
|
|
103
107
|
};
|
|
104
108
|
return {
|
|
105
109
|
protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
|
|
110
|
+
version: CLIENT_VERSION,
|
|
111
|
+
buildId: PLUGIN_HOST_BUILD_ID,
|
|
106
112
|
host: manager.host,
|
|
107
113
|
pid: process.pid,
|
|
108
114
|
};
|
|
@@ -112,6 +118,8 @@ export async function startPluginHostServer(configuration = {}) {
|
|
|
112
118
|
if (method === 'status')
|
|
113
119
|
return {
|
|
114
120
|
protocolVersion: PLUGIN_TASK_PROTOCOL_VERSION,
|
|
121
|
+
version: CLIENT_VERSION,
|
|
122
|
+
buildId: PLUGIN_HOST_BUILD_ID,
|
|
115
123
|
host: manager.host,
|
|
116
124
|
pid: process.pid,
|
|
117
125
|
};
|
|
@@ -5,7 +5,6 @@ import { readFileSync, statSync } from 'node:fs';
|
|
|
5
5
|
import { isAbsolute, join } from 'node:path';
|
|
6
6
|
import { capturePluginRevision } from './pluginRevision.js';
|
|
7
7
|
import { findPluginRegistration } from '../sdk/plugin.js';
|
|
8
|
-
import { compilePluginPageSchema } from '../sdk/pluginPageDefinition.js';
|
|
9
8
|
import { UnifiedClient, decodeBrowserTarget } from '../../unified.js';
|
|
10
9
|
import { pluginHostPaths } from './pluginHostPaths.js';
|
|
11
10
|
import { serializePluginExecutionError, pluginExecutionError } from './pluginExecutorProtocol.js';
|
|
@@ -726,9 +725,6 @@ export class PluginPageManager {
|
|
|
726
725
|
const binding = record.runtime.events?.[name];
|
|
727
726
|
if (!binding || Buffer.byteLength(JSON.stringify(input.payload ?? null)) > 32 * 1024)
|
|
728
727
|
throw new Error('Page event is not declared or exceeds 32 KiB');
|
|
729
|
-
const validate = compilePluginPageSchema(binding.schema);
|
|
730
|
-
if (!validate(input.payload))
|
|
731
|
-
throw failure('PLUGIN_PAGE_EVENT_INVALID', 'Page event payload does not match its schema');
|
|
732
728
|
const waiting = record.deliveries.filter(item => item.name === name && item.state === 'queued');
|
|
733
729
|
if (binding.policy === 'latest') {
|
|
734
730
|
for (const previous of waiting) {
|
|
@@ -2,12 +2,10 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { claimRecord, readRecord, writeRecord } from '@mearl/daemon-core';
|
|
5
|
-
import { isBuiltin } from 'node:module';
|
|
6
|
-
import { parse } from 'acorn';
|
|
7
5
|
import { pluginHostPaths } from './pluginHostPaths.js';
|
|
6
|
+
import { skillManifestPath } from '../skillManifest.js';
|
|
8
7
|
import { findPluginRegistration, PluginError } from '../pluginRegistry.js';
|
|
9
8
|
const MAX_REVISION_BYTES = 64 * 1024 * 1024;
|
|
10
|
-
const validatedSources = new Set();
|
|
11
9
|
function within(root, file) {
|
|
12
10
|
const path = relative(root, file);
|
|
13
11
|
return path === '' || (!path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path));
|
|
@@ -15,7 +13,7 @@ function within(root, file) {
|
|
|
15
13
|
function sourceRoot(entry) {
|
|
16
14
|
let candidate = dirname(entry);
|
|
17
15
|
for (let depth = 0; depth < 6; depth++) {
|
|
18
|
-
if (
|
|
16
|
+
if (skillManifestPath(candidate))
|
|
19
17
|
return candidate;
|
|
20
18
|
const parent = dirname(candidate);
|
|
21
19
|
if (parent === candidate)
|
|
@@ -67,48 +65,6 @@ function readSources(root) {
|
|
|
67
65
|
visit(root, new Set());
|
|
68
66
|
return files;
|
|
69
67
|
}
|
|
70
|
-
function validateDependencies(files) {
|
|
71
|
-
const paths = new Set(files.map(file => file.path));
|
|
72
|
-
for (const file of files) {
|
|
73
|
-
if (!/\.[cm]?js$/.test(file.path))
|
|
74
|
-
continue;
|
|
75
|
-
const tree = parse(file.bytes.toString('utf8'), {
|
|
76
|
-
ecmaVersion: 'latest',
|
|
77
|
-
sourceType: 'module',
|
|
78
|
-
allowReturnOutsideFunction: true,
|
|
79
|
-
});
|
|
80
|
-
const check = (specifier) => {
|
|
81
|
-
if (typeof specifier !== 'string' || isBuiltin(specifier))
|
|
82
|
-
return;
|
|
83
|
-
if (!specifier.startsWith('./') && !specifier.startsWith('../'))
|
|
84
|
-
throw new PluginError('PLUGIN_SOURCE_NOT_SELF_CONTAINED', `后台任务需要自包含 Skill,请先打包依赖: ${file.path}: ${specifier}`);
|
|
85
|
-
const local = resolve('/source', dirname(file.path), specifier);
|
|
86
|
-
const normalized = relative('/source', local).split(sep).join('/');
|
|
87
|
-
if (!within('/source', local) ||
|
|
88
|
-
![normalized, `${normalized}.js`, `${normalized}.json`, `${normalized}/index.js`].some(path => paths.has(path)))
|
|
89
|
-
throw new PluginError('PLUGIN_SOURCE_NOT_SELF_CONTAINED', `依赖不在 Skill 快照中: ${file.path}: ${specifier}`);
|
|
90
|
-
};
|
|
91
|
-
const visit = (node) => {
|
|
92
|
-
if (!node || typeof node !== 'object')
|
|
93
|
-
return;
|
|
94
|
-
if (['ImportDeclaration', 'ExportNamedDeclaration', 'ExportAllDeclaration'].includes(node.type))
|
|
95
|
-
check(node.source?.value);
|
|
96
|
-
if (node.type === 'ImportExpression')
|
|
97
|
-
check(node.source?.value);
|
|
98
|
-
if (node.type === 'CallExpression' &&
|
|
99
|
-
node.callee?.type === 'Identifier' &&
|
|
100
|
-
node.callee.name === 'require')
|
|
101
|
-
check(node.arguments[0]?.value);
|
|
102
|
-
for (const value of Object.values(node)) {
|
|
103
|
-
if (Array.isArray(value))
|
|
104
|
-
value.forEach(visit);
|
|
105
|
-
else if (value && typeof value === 'object')
|
|
106
|
-
visit(value);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
visit(tree);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
68
|
function fingerprint(files, entry) {
|
|
113
69
|
return createHash('sha256')
|
|
114
70
|
.update(JSON.stringify({ entry, files: files.map(file => [file.path, file.hash, file.executable]) }))
|
|
@@ -138,12 +94,6 @@ export function capturePluginRevision(plugin) {
|
|
|
138
94
|
const relativeEntry = relative(root, entry).split(sep).join('/');
|
|
139
95
|
const files = readSources(root);
|
|
140
96
|
const revision = fingerprint(files, relativeEntry);
|
|
141
|
-
if (!validatedSources.has(revision)) {
|
|
142
|
-
validateDependencies(files);
|
|
143
|
-
if (validatedSources.size >= 64)
|
|
144
|
-
validatedSources.delete(validatedSources.values().next().value);
|
|
145
|
-
validatedSources.add(revision);
|
|
146
|
-
}
|
|
147
97
|
const revisionRoot = join(paths.revisions, installation.installationId, revision);
|
|
148
98
|
if (!existsSync(revisionRoot)) {
|
|
149
99
|
const temporary = `${revisionRoot}.${randomUUID()}.tmp`;
|
|
@@ -3,7 +3,6 @@ import { existsSync, readFileSync, readdirSync, statSync, rmSync } from 'node:fs
|
|
|
3
3
|
import { join, sep } from 'node:path';
|
|
4
4
|
import { pluginHostPaths } from './pluginHostPaths.js';
|
|
5
5
|
import { writePluginHostFile } from './pluginHostFiles.js';
|
|
6
|
-
import { compilePluginStateSchema } from '../sdk/pluginStateDefinition.js';
|
|
7
6
|
function fail(code, message) {
|
|
8
7
|
throw Object.assign(new Error(message), { code });
|
|
9
8
|
}
|
|
@@ -316,7 +315,6 @@ export class PluginStateStore {
|
|
|
316
315
|
else
|
|
317
316
|
delete entry.expiresAt;
|
|
318
317
|
}
|
|
319
|
-
const validate = compilePluginStateSchema(definition.schema);
|
|
320
318
|
for (const operation of input.operations) {
|
|
321
319
|
key(operation.key);
|
|
322
320
|
if (names.has(operation.key) ||
|
|
@@ -328,9 +326,9 @@ export class PluginStateStore {
|
|
|
328
326
|
delete entries[operation.key];
|
|
329
327
|
continue;
|
|
330
328
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
fail('
|
|
329
|
+
const json = JSON.stringify(operation.value);
|
|
330
|
+
if (json === undefined || Buffer.byteLength(json) > 256 * 1024)
|
|
331
|
+
fail('PLUGIN_STATE_INVALID', 'State value must be JSON within 256 KiB');
|
|
334
332
|
const ttl = operation.ttlMs ?? definition.ttlMs ?? 86400000;
|
|
335
333
|
if (definition.storage === 'persistent' && operation.ttlMs !== undefined)
|
|
336
334
|
fail('PLUGIN_STATE_INVALID', 'Persistent state does not accept TTL');
|
|
@@ -342,9 +340,6 @@ export class PluginStateStore {
|
|
|
342
340
|
...(definition.storage !== 'persistent' ? { expiresAt: now + ttl } : {}),
|
|
343
341
|
};
|
|
344
342
|
}
|
|
345
|
-
for (const entry of Object.values(entries))
|
|
346
|
-
if (!validate(entry.value))
|
|
347
|
-
fail('PLUGIN_STATE_SCHEMA_INVALID', 'Migration leaves entries incompatible with the target schema');
|
|
348
343
|
const commit = {
|
|
349
344
|
revision,
|
|
350
345
|
schemaVersion: definition.schemaVersion,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { normalizeModelDiagnostic, diagnosticBinding } from './pluginDiagnostics.js';
|
|
2
2
|
import { pluginModelCapabilities, normalizePluginModel, validatePluginModelResult, } from '../sdk/pluginModelDefinition.js';
|
|
3
3
|
import { pluginRecoverySignature } from '../sdk/pluginRecoveryDefinition.js';
|
|
4
|
-
import { compilePluginStateSchema } from '../sdk/pluginStateDefinition.js';
|
|
5
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
5
|
import { EventEmitter } from 'node:events';
|
|
7
6
|
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
@@ -482,8 +481,7 @@ export class PluginTaskManager {
|
|
|
482
481
|
signal.throwIfAborted();
|
|
483
482
|
if (!sourceDefinition ||
|
|
484
483
|
sourceDefinition.checkpointVersion !== record.checkpoint.version ||
|
|
485
|
-
digest(sourceDefinition.schema) !== record.checkpoint.schemaHash
|
|
486
|
-
!compilePluginStateSchema(sourceDefinition.schema)(record.checkpoint.data))
|
|
484
|
+
digest(sourceDefinition.schema) !== record.checkpoint.schemaHash)
|
|
487
485
|
throw error('PLUGIN_TASK_RECOVERY_UNAVAILABLE', 'Checkpoint does not match the retained recovery declaration');
|
|
488
486
|
let checkpoint = structuredClone(record.checkpoint);
|
|
489
487
|
let declarationHash = pluginRecoverySignature(sourceDefinition);
|
|
@@ -542,8 +540,6 @@ export class PluginTaskManager {
|
|
|
542
540
|
data: JSON.parse(json),
|
|
543
541
|
};
|
|
544
542
|
}
|
|
545
|
-
if (!compilePluginStateSchema(target.schema)(checkpoint.data))
|
|
546
|
-
throw error('PLUGIN_TASK_CHECKPOINT_INVALID', 'Migrated checkpoint does not match the target schema');
|
|
547
543
|
}
|
|
548
544
|
signal.throwIfAborted();
|
|
549
545
|
// Admission is synchronous after both immutable snapshots and execution ownership are verified.
|
|
@@ -809,8 +805,8 @@ export class PluginTaskManager {
|
|
|
809
805
|
if (declaration &&
|
|
810
806
|
payload.expectedDeclaration !== pluginRecoverySignature(declaration))
|
|
811
807
|
throw error('PLUGIN_RECOVERY_DECLARATION_CHANGED', 'Checkpoint declaration differs from the executing command');
|
|
812
|
-
if (!declaration
|
|
813
|
-
throw error('PLUGIN_TASK_CHECKPOINT_INVALID', 'Checkpoint is undeclared
|
|
808
|
+
if (!declaration)
|
|
809
|
+
throw error('PLUGIN_TASK_CHECKPOINT_INVALID', 'Checkpoint is undeclared');
|
|
814
810
|
const current = this.store.read(taskId);
|
|
815
811
|
if (current.task.state !== 'running' ||
|
|
816
812
|
payload.expectedSequence !== (current.checkpoint?.sequence ?? 0))
|
|
@@ -4,6 +4,7 @@ import { normalizePluginPageRuntime } from './pluginPageDefinition.js';
|
|
|
4
4
|
import { loadPluginAppProfiles } from './pluginAppProfiles.js';
|
|
5
5
|
export { invokePageEvalWithArgs } from '../../pageEvalArguments.js';
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { skillManifestPath } from '../skillManifest.js';
|
|
7
8
|
import { dirname, extname, resolve } from 'node:path';
|
|
8
9
|
import { pathToFileURL } from 'node:url';
|
|
9
10
|
import { Command } from 'commander';
|
|
@@ -22,7 +23,10 @@ export { assertPluginName, findPluginDirectoryEntry, findPluginEntry, findPlugin
|
|
|
22
23
|
export { findOfficialPlugin, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, OFFICIAL_PLUGINS, } from '../officialPluginCatalog.js';
|
|
23
24
|
function readPluginVersion(entry) {
|
|
24
25
|
try {
|
|
25
|
-
const
|
|
26
|
+
const manifest = skillManifestPath(resolve(dirname(entry), '..'));
|
|
27
|
+
if (!manifest)
|
|
28
|
+
return undefined;
|
|
29
|
+
const content = readFileSync(manifest, 'utf8');
|
|
26
30
|
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1];
|
|
27
31
|
const versionLine = frontmatter?.split(/\r?\n/).find(line => /^version:\s*/.test(line));
|
|
28
32
|
return versionLine?.match(/^version:\s*["']?([^"'#\s]+)["']?\s*$/)?.[1];
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CODEX_MODEL_CAPABILITY } from '@mearl/agent-runtime/capabilities';
|
|
2
|
-
import { compilePluginStateSchema } from './pluginStateDefinition.js';
|
|
3
2
|
export function pluginModelCapabilities() {
|
|
4
3
|
return { providers: [CODEX_MODEL_CAPABILITY] };
|
|
5
4
|
}
|
|
@@ -44,7 +43,6 @@ export function normalizePluginModel(value) {
|
|
|
44
43
|
request.outputSchema.$async ||
|
|
45
44
|
Buffer.byteLength(JSON.stringify(request.outputSchema)) > 64 * 1024)
|
|
46
45
|
throw new Error('Invalid model output schema');
|
|
47
|
-
compilePluginStateSchema(request.outputSchema);
|
|
48
46
|
}
|
|
49
47
|
return JSON.parse(JSON.stringify(request));
|
|
50
48
|
}
|
|
@@ -65,10 +63,6 @@ export function validatePluginModelResult(request, value) {
|
|
|
65
63
|
code: 'PLUGIN_MODEL_SCHEMA_INVALID',
|
|
66
64
|
});
|
|
67
65
|
}
|
|
68
|
-
if (!compilePluginStateSchema(request.outputSchema)(output))
|
|
69
|
-
throw Object.assign(new Error('Model result does not match the output schema'), {
|
|
70
|
-
code: 'PLUGIN_MODEL_SCHEMA_INVALID',
|
|
71
|
-
});
|
|
72
66
|
return { ...result, output };
|
|
73
67
|
}
|
|
74
68
|
return result;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import type { PluginPageRuntime } from '../../generated/plugin-pages.js';
|
|
2
2
|
export declare function normalizePluginPageRuntime(value: unknown): PluginPageRuntime | undefined;
|
|
3
|
-
export declare function compilePluginPageSchema(schema: Record<string, unknown>): import("ajv/dist/core.js").AnyValidateFunction<unknown>;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Ajv } from 'ajv';
|
|
1
|
+
import { Script } from 'node:vm';
|
|
3
2
|
import { isPluginPageMatchPattern } from '../../generated/plugin-pages.js';
|
|
4
|
-
const validators = new WeakMap();
|
|
5
3
|
export function normalizePluginPageRuntime(value) {
|
|
6
4
|
if (value === undefined)
|
|
7
5
|
return undefined;
|
|
@@ -16,12 +14,7 @@ export function normalizePluginPageRuntime(value) {
|
|
|
16
14
|
Buffer.byteLength(runtime.code) > 384 * 1024 ||
|
|
17
15
|
Buffer.byteLength(JSON.stringify(runtime)) > 448 * 1024)
|
|
18
16
|
throw new Error('Invalid plugin pageRuntime declaration');
|
|
19
|
-
|
|
20
|
-
const statement = program.body[0];
|
|
21
|
-
if (program.body.length !== 1 ||
|
|
22
|
-
statement.type !== 'ExpressionStatement' ||
|
|
23
|
-
!['ArrowFunctionExpression', 'FunctionExpression'].includes(statement.expression?.type ?? ''))
|
|
24
|
-
throw new Error('pageRuntime.code must be a self-contained function expression');
|
|
17
|
+
new Script(`(${runtime.code}\n)`, { filename: 'plugin-page-runtime.js' });
|
|
25
18
|
if (runtime.events !== undefined &&
|
|
26
19
|
(!runtime.events ||
|
|
27
20
|
typeof runtime.events !== 'object' ||
|
|
@@ -46,17 +39,8 @@ export function normalizePluginPageRuntime(value) {
|
|
|
46
39
|
event.maxPending > 64)) ||
|
|
47
40
|
event.schema.$async)
|
|
48
41
|
throw new Error(`Invalid page event binding: ${name}`);
|
|
49
|
-
compilePluginPageSchema(event.schema);
|
|
50
42
|
}
|
|
51
43
|
if (runtime.initialize !== undefined && !/^[a-z][a-z0-9-]*$/.test(runtime.initialize))
|
|
52
44
|
throw new Error('Invalid page runtime initialize command');
|
|
53
45
|
return JSON.parse(JSON.stringify(runtime));
|
|
54
46
|
}
|
|
55
|
-
export function compilePluginPageSchema(schema) {
|
|
56
|
-
let validate = validators.get(schema);
|
|
57
|
-
if (!validate) {
|
|
58
|
-
validate = new Ajv({ strict: true, allErrors: false }).compile(schema);
|
|
59
|
-
validators.set(schema, validate);
|
|
60
|
-
}
|
|
61
|
-
return validate;
|
|
62
|
-
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { compilePluginStateSchema } from './pluginStateDefinition.js';
|
|
3
2
|
export function normalizePluginTaskRecovery(value) {
|
|
4
3
|
if (value === undefined)
|
|
5
4
|
return;
|
|
@@ -22,7 +21,6 @@ export function normalizePluginTaskRecovery(value) {
|
|
|
22
21
|
Array.isArray(definition.schema) ||
|
|
23
22
|
definition.schema.$async)
|
|
24
23
|
throw new Error('Invalid task checkpoint definition');
|
|
25
|
-
compilePluginStateSchema(definition.schema);
|
|
26
24
|
if (definition.migrateFrom !== undefined) {
|
|
27
25
|
if (!Array.isArray(definition.migrateFrom) ||
|
|
28
26
|
definition.migrateFrom.length < 1 ||
|
|
@@ -40,7 +38,6 @@ export function normalizePluginTaskRecovery(value) {
|
|
|
40
38
|
Array.isArray(source.schema) ||
|
|
41
39
|
source.schema.$async)
|
|
42
40
|
throw new Error('Checkpoint migrations require distinct older versions and schemas');
|
|
43
|
-
compilePluginStateSchema(source.schema);
|
|
44
41
|
versions.add(source.version);
|
|
45
42
|
}
|
|
46
43
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
import type { PluginStateDeclaration } from '../../generated/plugin-state.js';
|
|
2
|
-
export declare function compilePluginStateSchema(schema: Record<string, unknown>): import("ajv/dist/core.js").AnyValidateFunction<unknown>;
|
|
3
2
|
export declare function normalizePluginState(value: unknown): PluginStateDeclaration | undefined;
|
|
4
3
|
export declare function pluginStateSignature(collection: import('../../generated/plugin-state.js').PluginStateCollection): string;
|
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { Ajv } from 'ajv';
|
|
3
|
-
const validators = new WeakMap();
|
|
4
|
-
export function compilePluginStateSchema(schema) {
|
|
5
|
-
let validate = validators.get(schema);
|
|
6
|
-
if (!validate) {
|
|
7
|
-
validate = new Ajv({ strict: true }).compile(schema);
|
|
8
|
-
validators.set(schema, validate);
|
|
9
|
-
}
|
|
10
|
-
return validate;
|
|
11
|
-
}
|
|
12
2
|
export function normalizePluginState(value) {
|
|
13
3
|
if (value === undefined)
|
|
14
4
|
return;
|
|
@@ -55,7 +45,6 @@ export function normalizePluginState(value) {
|
|
|
55
45
|
throw new Error('State TTL must be between 1 ms and 7 days');
|
|
56
46
|
if (collection.storage === 'persistent' && collection.ttlMs !== undefined)
|
|
57
47
|
throw new Error('Persistent state does not accept TTL');
|
|
58
|
-
compilePluginStateSchema(collection.schema);
|
|
59
48
|
}
|
|
60
49
|
return declaration;
|
|
61
50
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DISABLED_SKILL_MANIFEST = "SKILL.md.mearl-disabled";
|
|
2
|
+
/** 保留的清单供工具运行时读取,但不参与 Agent 的 SKILL.md 发现。 */
|
|
3
|
+
export declare function skillManifestPath(directory: string): string | undefined;
|
|
4
|
+
export declare function getSkillEnabled(directory: string): boolean | undefined;
|
|
5
|
+
export declare function setSkillEnabled(directory: string, enabled: boolean): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, lstatSync, renameSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const DISABLED_SKILL_MANIFEST = 'SKILL.md.mearl-disabled';
|
|
4
|
+
/** 保留的清单供工具运行时读取,但不参与 Agent 的 SKILL.md 发现。 */
|
|
5
|
+
export function skillManifestPath(directory) {
|
|
6
|
+
for (const name of ['SKILL.md', DISABLED_SKILL_MANIFEST]) {
|
|
7
|
+
const file = join(directory, name);
|
|
8
|
+
if (existsSync(file))
|
|
9
|
+
return file;
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
export function getSkillEnabled(directory) {
|
|
14
|
+
const manifest = skillManifestPath(directory);
|
|
15
|
+
return manifest ? manifest === join(directory, 'SKILL.md') : undefined;
|
|
16
|
+
}
|
|
17
|
+
export function setSkillEnabled(directory, enabled) {
|
|
18
|
+
const active = join(directory, 'SKILL.md');
|
|
19
|
+
const disabled = join(directory, DISABLED_SKILL_MANIFEST);
|
|
20
|
+
const source = enabled ? disabled : active;
|
|
21
|
+
const destination = enabled ? active : disabled;
|
|
22
|
+
const sourceStatus = lstatSync(source, { throwIfNoEntry: false });
|
|
23
|
+
const destinationStatus = lstatSync(destination, { throwIfNoEntry: false });
|
|
24
|
+
if (sourceStatus && destinationStatus) {
|
|
25
|
+
throw new Error('Skill 清单存在冲突,请检查 SKILL.md 和 SKILL.md.mearl-disabled');
|
|
26
|
+
}
|
|
27
|
+
if (!sourceStatus) {
|
|
28
|
+
if (destinationStatus?.isFile())
|
|
29
|
+
return;
|
|
30
|
+
throw new Error('找不到 Skill 清单');
|
|
31
|
+
}
|
|
32
|
+
if (!sourceStatus.isFile())
|
|
33
|
+
throw new Error('Skill 清单必须是普通文件');
|
|
34
|
+
renameSync(source, destination);
|
|
35
|
+
}
|
package/dist/runtimePaths.d.ts
CHANGED
package/dist/runtimePaths.js
CHANGED
|
@@ -5,4 +5,5 @@ const require = createRequire(import.meta.url);
|
|
|
5
5
|
export const CLIENT_VERSION = require('../package.json').version;
|
|
6
6
|
export const PLUGIN_TASK_HOST_PATH = fileURLToPath(new URL('./plugin-task-host.js', import.meta.url));
|
|
7
7
|
export const PLUGIN_WORKER_PATH = fileURLToPath(new URL('./plugin-worker.js', import.meta.url));
|
|
8
|
+
export const SCRIPT_WORKER_PATH = fileURLToPath(new URL('./scriptWorker.js', import.meta.url));
|
|
8
9
|
export const PLUGIN_MODEL_SESSION_WORKER_PATH = fileURLToPath(new URL('./pluginModelSessionWorker.js', import.meta.url));
|