@actiondock/core 2.6.0 → 2.7.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/dist/errors.d.ts +20 -0
- package/dist/errors.js +20 -0
- package/dist/export/templates.js +42 -30
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/input/advice.d.ts +100 -0
- package/dist/input/advice.js +767 -0
- package/dist/input/describe.d.ts +106 -0
- package/dist/input/describe.js +176 -0
- package/dist/input/file-input.d.ts +54 -0
- package/dist/input/file-input.js +124 -0
- package/dist/input/flat-decode.d.ts +11 -0
- package/dist/input/flat-decode.js +13 -0
- package/dist/input/flat-errors.d.ts +72 -0
- package/dist/input/flat-errors.js +87 -0
- package/dist/input/flat-materializer.d.ts +29 -0
- package/dist/input/flat-materializer.js +179 -0
- package/dist/input/flat-parser.d.ts +50 -0
- package/dist/input/flat-parser.js +169 -0
- package/dist/input/flat-predicates.d.ts +34 -0
- package/dist/input/flat-predicates.js +50 -0
- package/dist/input/index.d.ts +14 -0
- package/dist/input/index.js +14 -0
- package/dist/input/input-resolver.d.ts +63 -0
- package/dist/input/input-resolver.js +254 -0
- package/dist/input/json-depth-scanner.d.ts +17 -0
- package/dist/input/json-depth-scanner.js +49 -0
- package/dist/input/metadata.d.ts +99 -0
- package/dist/input/metadata.js +99 -0
- package/dist/input/stdin-input.d.ts +26 -0
- package/dist/input/stdin-input.js +125 -0
- package/dist/input/utf8.d.ts +19 -0
- package/dist/input/utf8.js +36 -0
- package/dist/input/validation-mapper.d.ts +27 -0
- package/dist/input/validation-mapper.js +144 -0
- package/dist/json/index.d.ts +1 -0
- package/dist/json/index.js +1 -0
- package/dist/json/value-validator.d.ts +88 -0
- package/dist/json/value-validator.js +330 -0
- package/dist/profile/client-health.js +1 -0
- package/dist/profile/client-transport.js +1 -0
- package/dist/project/init.js +2 -2
- package/dist/runtime/runner.d.ts +2 -12
- package/dist/runtime/runner.js +38 -68
- package/dist/runtime/standalone.d.ts +14 -1
- package/dist/runtime/standalone.js +177 -71
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/validator.d.ts +10 -0
- package/dist/schema/validator.js +48 -54
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -5,18 +5,8 @@ import type { Clock } from "./clock.js";
|
|
|
5
5
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
6
6
|
import type { ProcessOwner } from "../process/index.js";
|
|
7
7
|
export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
|
|
12
|
-
* 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
|
|
13
|
-
*/
|
|
14
|
-
export declare function validateJsonValue(val: unknown, stack?: WeakSet<object>): {
|
|
15
|
-
valid: true;
|
|
16
|
-
} | {
|
|
17
|
-
valid: false;
|
|
18
|
-
reason: string;
|
|
19
|
-
};
|
|
8
|
+
import { validateJsonValue, validateActionInputValue, assertJsonValue, type ValidateJsonOptions, type ValidateActionInputOptions } from "../json/value-validator.js";
|
|
9
|
+
export { validateJsonValue, validateActionInputValue, assertJsonValue, type ValidateJsonOptions, type ValidateActionInputOptions, };
|
|
20
10
|
/**
|
|
21
11
|
* 跨包运行上下文解析结果契约。
|
|
22
12
|
*/
|
package/dist/runtime/runner.js
CHANGED
|
@@ -4,62 +4,16 @@ import { ActionResolver } from "../catalog/action-resolver.js";
|
|
|
4
4
|
import { loadActions, loadProjectConfig } from "../project/loader.js";
|
|
5
5
|
import { resolveActionProject } from "../registry/registry.js";
|
|
6
6
|
import { ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_FAILED, ACTION_MAX_DEPTH_EXCEEDED, ACTION_NOT_FOUND, ACTION_SUBRUN_LIMIT, ACTION_TIMEOUT, describeActionLoadFailure, INPUT_NOT_JSON, MAX_SUBRUNS_REACHED, OUTPUT_NOT_JSON, ACTION_CANCELLED, PACKAGE_NOT_FOUND, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
|
|
7
|
-
import {
|
|
7
|
+
import { validateSchemaOnly } from "../schema/validator.js";
|
|
8
8
|
import { createActionContext, StderrLogger } from "./context.js";
|
|
9
9
|
import { ActionRegistry, findLocalAction, isActionDefinitionObject, parseActionRef, resolveAnonymousActionId, } from "./action-registry.js";
|
|
10
10
|
import { buildInitialRunRecord, createRunFinalizer, createRunOrThrow, tryCreateRun, } from "./run-persistence.js";
|
|
11
11
|
import { PackageRunnerFactory } from "./package-runner-factory.js";
|
|
12
12
|
// 错误码常量已收敛至 src/errors.ts 单一事实源,此处保留 re-export 以维持既有导入路径兼容。
|
|
13
13
|
export { RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, MAX_SUBRUNS_REACHED, ACTION_CALL_CYCLE, ACTION_CYCLE_DETECTED, ACTION_MAX_DEPTH_EXCEEDED, } from "../errors.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* 环路检测采用「当前递归路径栈」语义:对象仅在自身子树遍历期间保持标记,
|
|
18
|
-
* 子树遍历完成后立即移除,从而正确放行共享子对象的有向无环结构(如 {a: shared, b: shared})。
|
|
19
|
-
*/
|
|
20
|
-
export function validateJsonValue(val, stack = new WeakSet()) {
|
|
21
|
-
if (val === null || typeof val === "boolean" || typeof val === "string") {
|
|
22
|
-
return { valid: true };
|
|
23
|
-
}
|
|
24
|
-
if (typeof val === "number") {
|
|
25
|
-
if (!Number.isFinite(val) || Number.isNaN(val)) {
|
|
26
|
-
return { valid: false, reason: `Number is non-finite or NaN (${val})` };
|
|
27
|
-
}
|
|
28
|
-
return { valid: true };
|
|
29
|
-
}
|
|
30
|
-
if (typeof val === "undefined" || typeof val === "function" || typeof val === "symbol" || typeof val === "bigint") {
|
|
31
|
-
return { valid: false, reason: `Unsupported JSON type '${typeof val}'` };
|
|
32
|
-
}
|
|
33
|
-
if (typeof val === "object") {
|
|
34
|
-
if (stack.has(val)) {
|
|
35
|
-
return { valid: false, reason: "Circular reference detected in object structure" };
|
|
36
|
-
}
|
|
37
|
-
stack.add(val);
|
|
38
|
-
try {
|
|
39
|
-
if (Array.isArray(val)) {
|
|
40
|
-
for (const item of val) {
|
|
41
|
-
const res = validateJsonValue(item, stack);
|
|
42
|
-
if (!res.valid)
|
|
43
|
-
return res;
|
|
44
|
-
}
|
|
45
|
-
return { valid: true };
|
|
46
|
-
}
|
|
47
|
-
for (const v of Object.values(val)) {
|
|
48
|
-
if (v !== undefined) {
|
|
49
|
-
const res = validateJsonValue(v, stack);
|
|
50
|
-
if (!res.valid)
|
|
51
|
-
return res;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return { valid: true };
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
// 无论正常返回还是提前返回,均须将当前对象移出路径栈,避免祖先对象被误判为环路
|
|
58
|
-
stack.delete(val);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return { valid: true };
|
|
62
|
-
}
|
|
14
|
+
import { validateJsonValue, validateActionInputValue, assertJsonValue, } from "../json/value-validator.js";
|
|
15
|
+
// 复用统一迭代式 JsonValue 校验器,杜绝深层递归栈溢出,并保持既有导出兼容
|
|
16
|
+
export { validateJsonValue, validateActionInputValue, assertJsonValue, };
|
|
63
17
|
/**
|
|
64
18
|
* ActionDock 核心执行引擎(ActionRunner)。
|
|
65
19
|
*
|
|
@@ -300,14 +254,20 @@ export class ActionRunner {
|
|
|
300
254
|
start(actionOrId, input = {}, options = {}) {
|
|
301
255
|
const runCtx = this.prepareRunContext(actionOrId, input, options);
|
|
302
256
|
const { runId, targetActionId, targetPackageId } = runCtx;
|
|
303
|
-
// 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON
|
|
304
|
-
const inputCheck =
|
|
257
|
+
// 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型及非法原型键策略违规)
|
|
258
|
+
const inputCheck = validateActionInputValue(input);
|
|
305
259
|
if (!inputCheck.valid) {
|
|
306
|
-
const error =
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
260
|
+
const error = inputCheck.kind === "json-value"
|
|
261
|
+
? {
|
|
262
|
+
code: INPUT_NOT_JSON,
|
|
263
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
264
|
+
}
|
|
265
|
+
: {
|
|
266
|
+
code: INPUT_VALIDATION_FAILED,
|
|
267
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
268
|
+
details: [inputCheck.reason],
|
|
269
|
+
};
|
|
270
|
+
// 安全记录 failed 状态(不可序列化或策略违规的非法 input 严禁直接写入持久化存储)
|
|
311
271
|
tryCreateRun(this.storage, buildInitialRunRecord(this.buildRunPersistenceInput(runCtx), "failed", error));
|
|
312
272
|
return {
|
|
313
273
|
runId,
|
|
@@ -516,9 +476,11 @@ export class ActionRunner {
|
|
|
516
476
|
* 输入参数 JSON Schema 校验(若 action 已就绪,返回校验错误或 undefined)。
|
|
517
477
|
*/
|
|
518
478
|
checkActionInputSchema(action, targetActionId, input) {
|
|
519
|
-
const targetInputSchema = action?.inputSchema
|
|
520
|
-
|
|
521
|
-
|
|
479
|
+
const targetInputSchema = action?.inputSchema !== undefined
|
|
480
|
+
? action.inputSchema
|
|
481
|
+
: this.projectConfig?.actions?.[targetActionId]?.inputSchema;
|
|
482
|
+
if (targetInputSchema !== undefined) {
|
|
483
|
+
const val = validateSchemaOnly(targetInputSchema, input);
|
|
522
484
|
if (!val.valid) {
|
|
523
485
|
return {
|
|
524
486
|
code: INPUT_VALIDATION_FAILED,
|
|
@@ -729,12 +691,18 @@ export class ActionRunner {
|
|
|
729
691
|
finalizer.finalize("failed", undefined, error);
|
|
730
692
|
return { ok: false, runId, error };
|
|
731
693
|
}
|
|
732
|
-
const inputCheck =
|
|
694
|
+
const inputCheck = validateActionInputValue(input);
|
|
733
695
|
if (!inputCheck.valid) {
|
|
734
|
-
const error =
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
696
|
+
const error = inputCheck.kind === "json-value"
|
|
697
|
+
? {
|
|
698
|
+
code: INPUT_NOT_JSON,
|
|
699
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
700
|
+
}
|
|
701
|
+
: {
|
|
702
|
+
code: INPUT_VALIDATION_FAILED,
|
|
703
|
+
message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
|
|
704
|
+
details: [inputCheck.reason],
|
|
705
|
+
};
|
|
738
706
|
finalizer.finalize("failed", undefined, error);
|
|
739
707
|
return { ok: false, runId, error };
|
|
740
708
|
}
|
|
@@ -795,9 +763,11 @@ export class ActionRunner {
|
|
|
795
763
|
* 输出结果 Schema 校验(返回校验错误或 undefined)。
|
|
796
764
|
*/
|
|
797
765
|
checkActionOutputSchema(action, targetActionId, rawOutput) {
|
|
798
|
-
const targetOutputSchema = action?.outputSchema
|
|
799
|
-
|
|
800
|
-
|
|
766
|
+
const targetOutputSchema = action?.outputSchema !== undefined
|
|
767
|
+
? action.outputSchema
|
|
768
|
+
: this.projectConfig?.actions?.[targetActionId]?.outputSchema;
|
|
769
|
+
if (targetOutputSchema !== undefined) {
|
|
770
|
+
const outVal = validateSchemaOnly(targetOutputSchema, rawOutput);
|
|
801
771
|
if (!outVal.valid) {
|
|
802
772
|
return {
|
|
803
773
|
code: OUTPUT_VALIDATION_FAILED,
|
|
@@ -11,6 +11,13 @@ export declare const ExitCode: {
|
|
|
11
11
|
readonly INVALID_ARGUMENT: 2;
|
|
12
12
|
readonly SIGINT: 130;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* 调用控制选项契约。
|
|
16
|
+
*/
|
|
17
|
+
export interface InvocationControl {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
cancellationSource?: "external" | "sigint";
|
|
20
|
+
}
|
|
14
21
|
/**
|
|
15
22
|
* 独立二进制运行分发器配置选项。
|
|
16
23
|
*/
|
|
@@ -63,9 +70,10 @@ export declare class StandaloneDispatcher {
|
|
|
63
70
|
* 解析命令行参数并分发执行对应子命令。
|
|
64
71
|
*
|
|
65
72
|
* @param argv 命令行参数数组(如 process.argv.slice(2))
|
|
73
|
+
* @param control 可选的中断与取消控制契约
|
|
66
74
|
* @returns 退出状态码(0: 成功, 1: 失败, 2: 参数错误, 130: 中断)
|
|
67
75
|
*/
|
|
68
|
-
dispatch(argv: string[]): Promise<number>;
|
|
76
|
+
dispatch(argv: string[], control?: InvocationControl): Promise<number>;
|
|
69
77
|
private handleList;
|
|
70
78
|
private handleDescribe;
|
|
71
79
|
private handleRun;
|
|
@@ -97,3 +105,8 @@ export declare class StandaloneRuntime {
|
|
|
97
105
|
* 工厂函数:创建独立运行时实例。
|
|
98
106
|
*/
|
|
99
107
|
export declare function createStandaloneRuntime(options: StandaloneRuntimeOptions): StandaloneRuntime;
|
|
108
|
+
/**
|
|
109
|
+
* 独立二进制进程入口适配器。
|
|
110
|
+
* 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
|
|
111
|
+
*/
|
|
112
|
+
export declare function runStandaloneProcess(argv: string[], options: StandaloneDispatcherOptions): Promise<void>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { decodeStateKey } from "@actiondock/sdk";
|
|
3
2
|
import { filterWithFallbackInfo } from "../filter/index.js";
|
|
4
3
|
import { createActionDockTarget } from "../target/target.js";
|
|
5
4
|
import { STANDALONE_ASYNC_UNSUPPORTED } from "../errors.js";
|
|
5
|
+
import { resolveActionInput, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, } from "../input/index.js";
|
|
6
|
+
import { validateActionInputValue } from "../json/value-validator.js";
|
|
6
7
|
import { normalizeActionCollection } from "./action-collection.js";
|
|
7
8
|
import { parseDuration } from "../utils/index.js";
|
|
8
9
|
/**
|
|
@@ -84,24 +85,27 @@ export class StandaloneDispatcher {
|
|
|
84
85
|
* 解析命令行参数并分发执行对应子命令。
|
|
85
86
|
*
|
|
86
87
|
* @param argv 命令行参数数组(如 process.argv.slice(2))
|
|
88
|
+
* @param control 可选的中断与取消控制契约
|
|
87
89
|
* @returns 退出状态码(0: 成功, 1: 失败, 2: 参数错误, 130: 中断)
|
|
88
90
|
*/
|
|
89
|
-
async dispatch(argv) {
|
|
90
|
-
const
|
|
91
|
+
async dispatch(argv, control) {
|
|
92
|
+
const separator = argv.indexOf("--");
|
|
93
|
+
const controlArgs = separator >= 0 ? argv.slice(0, separator) : argv;
|
|
94
|
+
const actionArgs = separator >= 0 ? argv.slice(separator + 1) : [];
|
|
91
95
|
let dataDir;
|
|
92
96
|
const configOverrides = {};
|
|
93
|
-
// 1. 提取全局参数(--data-dir, --config
|
|
97
|
+
// 1. 提取全局参数(--data-dir, --config)仅从 controlArgs 中解析
|
|
94
98
|
const filteredArgs = [];
|
|
95
|
-
for (let i = 0; i <
|
|
96
|
-
const arg =
|
|
97
|
-
if (arg === "--data-dir" && i + 1 <
|
|
98
|
-
dataDir =
|
|
99
|
+
for (let i = 0; i < controlArgs.length; i++) {
|
|
100
|
+
const arg = controlArgs[i];
|
|
101
|
+
if (arg === "--data-dir" && i + 1 < controlArgs.length) {
|
|
102
|
+
dataDir = controlArgs[++i];
|
|
99
103
|
}
|
|
100
104
|
else if (arg.startsWith("--data-dir=")) {
|
|
101
105
|
dataDir = arg.slice(11);
|
|
102
106
|
}
|
|
103
|
-
else if (arg === "--config" && i + 1 <
|
|
104
|
-
const pair =
|
|
107
|
+
else if (arg === "--config" && i + 1 < controlArgs.length) {
|
|
108
|
+
const pair = controlArgs[++i];
|
|
105
109
|
const [k, ...v] = pair.split("=");
|
|
106
110
|
if (k)
|
|
107
111
|
configOverrides[k] = v.join("=");
|
|
@@ -119,8 +123,8 @@ export class StandaloneDispatcher {
|
|
|
119
123
|
const command = filteredArgs[0] || "help";
|
|
120
124
|
const subArgs = filteredArgs.slice(1);
|
|
121
125
|
// 2. 独立入口拒绝异步启动语义
|
|
122
|
-
if (
|
|
123
|
-
const isJson =
|
|
126
|
+
if (controlArgs.includes("--async")) {
|
|
127
|
+
const isJson = controlArgs.includes("--json");
|
|
124
128
|
if (isJson) {
|
|
125
129
|
this.writeOut(JSON.stringify({
|
|
126
130
|
ok: false,
|
|
@@ -155,39 +159,44 @@ export class StandaloneDispatcher {
|
|
|
155
159
|
this.writeErr(`Error initializing standalone target: ${err?.message || err}`);
|
|
156
160
|
return ExitCode.FAILURE;
|
|
157
161
|
}
|
|
158
|
-
const controller = new AbortController();
|
|
159
|
-
const sigintHandler = () => {
|
|
160
|
-
controller.abort(new Error("Interrupted by SIGINT"));
|
|
161
|
-
};
|
|
162
|
-
process.once("SIGINT", sigintHandler);
|
|
163
162
|
try {
|
|
163
|
+
let code;
|
|
164
164
|
switch (command) {
|
|
165
165
|
case "list":
|
|
166
|
-
|
|
166
|
+
code = await this.handleList(target, subArgs);
|
|
167
|
+
break;
|
|
167
168
|
case "describe":
|
|
168
169
|
case "show":
|
|
169
|
-
|
|
170
|
+
code = await this.handleDescribe(target, subArgs);
|
|
171
|
+
break;
|
|
170
172
|
case "run":
|
|
171
|
-
|
|
173
|
+
code = await this.handleRun(target, subArgs, actionArgs, control?.signal);
|
|
174
|
+
break;
|
|
172
175
|
case "config":
|
|
173
|
-
|
|
176
|
+
code = await this.handleConfig(target, subArgs);
|
|
177
|
+
break;
|
|
174
178
|
case "state":
|
|
175
|
-
|
|
179
|
+
code = await this.handleState(target, subArgs);
|
|
180
|
+
break;
|
|
176
181
|
default:
|
|
177
182
|
this.writeErr(`Unknown command: '${command}'`);
|
|
178
183
|
this.printHelp();
|
|
179
184
|
return ExitCode.INVALID_ARGUMENT;
|
|
180
185
|
}
|
|
186
|
+
if (control?.cancellationSource === "sigint" && control?.signal?.aborted) {
|
|
187
|
+
return ExitCode.SIGINT;
|
|
188
|
+
}
|
|
189
|
+
return code;
|
|
181
190
|
}
|
|
182
191
|
catch (err) {
|
|
183
|
-
if (
|
|
192
|
+
if (control?.cancellationSource === "sigint" &&
|
|
193
|
+
(control?.signal?.aborted || err?.name === "AbortError" || err?.message?.includes("SIGINT"))) {
|
|
184
194
|
return ExitCode.SIGINT;
|
|
185
195
|
}
|
|
186
196
|
this.writeErr(`Error: ${err?.message || err}`);
|
|
187
197
|
return ExitCode.FAILURE;
|
|
188
198
|
}
|
|
189
199
|
finally {
|
|
190
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
191
200
|
if (ownsTarget) {
|
|
192
201
|
await target.close();
|
|
193
202
|
}
|
|
@@ -237,7 +246,18 @@ export class StandaloneDispatcher {
|
|
|
237
246
|
const id = subArgs.find((a) => !a.startsWith("-"));
|
|
238
247
|
const isJson = subArgs.includes("--json");
|
|
239
248
|
if (!id) {
|
|
240
|
-
|
|
249
|
+
if (isJson) {
|
|
250
|
+
this.writeOut(JSON.stringify({
|
|
251
|
+
ok: false,
|
|
252
|
+
error: {
|
|
253
|
+
code: "INVALID_ARGUMENT",
|
|
254
|
+
message: "Action ID is required for describe",
|
|
255
|
+
},
|
|
256
|
+
}, null, 2));
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
this.writeErr("Error: Action ID is required for describe");
|
|
260
|
+
}
|
|
241
261
|
return ExitCode.INVALID_ARGUMENT;
|
|
242
262
|
}
|
|
243
263
|
let action;
|
|
@@ -245,81 +265,136 @@ export class StandaloneDispatcher {
|
|
|
245
265
|
action = await target.describeAction(id);
|
|
246
266
|
}
|
|
247
267
|
catch {
|
|
248
|
-
|
|
268
|
+
if (isJson) {
|
|
269
|
+
this.writeOut(JSON.stringify({
|
|
270
|
+
ok: false,
|
|
271
|
+
error: {
|
|
272
|
+
code: "INVALID_ARGUMENT",
|
|
273
|
+
message: `Action '${id}' not found`,
|
|
274
|
+
},
|
|
275
|
+
}, null, 2));
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
this.writeErr(`Error: Action '${id}' not found`);
|
|
279
|
+
}
|
|
249
280
|
return ExitCode.INVALID_ARGUMENT;
|
|
250
281
|
}
|
|
251
|
-
const
|
|
252
|
-
id: action.id,
|
|
282
|
+
const payload = buildActionDescribePayload(action, {
|
|
253
283
|
packageId: this.options.packageId,
|
|
254
|
-
|
|
255
|
-
inputSchema: action.inputSchema,
|
|
256
|
-
outputSchema: action.outputSchema,
|
|
257
|
-
};
|
|
284
|
+
});
|
|
258
285
|
if (isJson) {
|
|
259
|
-
this.writeOut(JSON.stringify(
|
|
286
|
+
this.writeOut(JSON.stringify(payload, null, 2));
|
|
260
287
|
}
|
|
261
288
|
else {
|
|
262
|
-
|
|
263
|
-
if (action.description)
|
|
264
|
-
text += `Description: ${action.description}\n`;
|
|
265
|
-
if (action.inputSchema) {
|
|
266
|
-
text += `\nInput Schema:\n${JSON.stringify(action.inputSchema, null, 2)}\n`;
|
|
267
|
-
}
|
|
268
|
-
if (action.outputSchema) {
|
|
269
|
-
text += `\nOutput Schema:\n${JSON.stringify(action.outputSchema, null, 2)}\n`;
|
|
270
|
-
}
|
|
271
|
-
this.writeOut(text.trimEnd());
|
|
289
|
+
this.writeOut(formatActionDetail(payload));
|
|
272
290
|
}
|
|
273
291
|
return ExitCode.SUCCESS;
|
|
274
292
|
}
|
|
275
|
-
async handleRun(target, subArgs, signal) {
|
|
293
|
+
async handleRun(target, subArgs, actionArgs, signal) {
|
|
294
|
+
const isJson = subArgs.includes("--json");
|
|
276
295
|
const id = subArgs.find((a) => !a.startsWith("-"));
|
|
277
296
|
if (!id) {
|
|
278
|
-
|
|
297
|
+
if (isJson) {
|
|
298
|
+
this.writeOut(JSON.stringify({
|
|
299
|
+
ok: false,
|
|
300
|
+
error: {
|
|
301
|
+
code: "INVALID_ARGUMENT",
|
|
302
|
+
message: "Error: Action ID is required for run",
|
|
303
|
+
},
|
|
304
|
+
}, null, 2));
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
this.writeErr("Error: Action ID is required for run");
|
|
308
|
+
}
|
|
279
309
|
return ExitCode.INVALID_ARGUMENT;
|
|
280
310
|
}
|
|
281
|
-
let
|
|
311
|
+
let inputStr;
|
|
312
|
+
let inputFile;
|
|
282
313
|
let timeoutMs;
|
|
283
|
-
let isJson = false;
|
|
284
314
|
for (let i = 0; i < subArgs.length; i++) {
|
|
285
315
|
const arg = subArgs[i];
|
|
286
|
-
if (arg === "--
|
|
287
|
-
isJson = true;
|
|
288
|
-
}
|
|
289
|
-
else if (arg === "--timeout" && i + 1 < subArgs.length) {
|
|
290
|
-
timeoutMs = parseDuration(subArgs[++i]);
|
|
291
|
-
}
|
|
292
|
-
else if (arg.startsWith("--timeout=")) {
|
|
293
|
-
timeoutMs = parseDuration(arg.slice(10));
|
|
294
|
-
}
|
|
295
|
-
else if (arg === "--input" && i + 1 < subArgs.length) {
|
|
316
|
+
if (arg === "--timeout" && i + 1 < subArgs.length) {
|
|
296
317
|
try {
|
|
297
|
-
|
|
318
|
+
timeoutMs = parseDuration(subArgs[++i]);
|
|
298
319
|
}
|
|
299
|
-
catch (
|
|
300
|
-
|
|
320
|
+
catch (err) {
|
|
321
|
+
if (isJson) {
|
|
322
|
+
this.writeOut(JSON.stringify({
|
|
323
|
+
ok: false,
|
|
324
|
+
error: {
|
|
325
|
+
code: "INVALID_ARGUMENT",
|
|
326
|
+
message: `Invalid timeout format: ${err?.message || err}`,
|
|
327
|
+
},
|
|
328
|
+
}, null, 2));
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
this.writeErr(`Error: Invalid timeout format: ${err?.message || err}`);
|
|
332
|
+
}
|
|
301
333
|
return ExitCode.INVALID_ARGUMENT;
|
|
302
334
|
}
|
|
303
335
|
}
|
|
304
|
-
else if (arg.startsWith("--
|
|
336
|
+
else if (arg.startsWith("--timeout=")) {
|
|
305
337
|
try {
|
|
306
|
-
|
|
338
|
+
timeoutMs = parseDuration(arg.slice(10));
|
|
307
339
|
}
|
|
308
|
-
catch (
|
|
309
|
-
|
|
340
|
+
catch (err) {
|
|
341
|
+
if (isJson) {
|
|
342
|
+
this.writeOut(JSON.stringify({
|
|
343
|
+
ok: false,
|
|
344
|
+
error: {
|
|
345
|
+
code: "INVALID_ARGUMENT",
|
|
346
|
+
message: `Invalid timeout format: ${err?.message || err}`,
|
|
347
|
+
},
|
|
348
|
+
}, null, 2));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
this.writeErr(`Error: Invalid timeout format: ${err?.message || err}`);
|
|
352
|
+
}
|
|
310
353
|
return ExitCode.INVALID_ARGUMENT;
|
|
311
354
|
}
|
|
312
355
|
}
|
|
356
|
+
else if (arg === "--input" && i + 1 < subArgs.length) {
|
|
357
|
+
inputStr = subArgs[++i];
|
|
358
|
+
}
|
|
359
|
+
else if (arg.startsWith("--input=")) {
|
|
360
|
+
inputStr = arg.slice(8);
|
|
361
|
+
}
|
|
313
362
|
else if (arg === "--input-file" && i + 1 < subArgs.length) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
363
|
+
inputFile = subArgs[++i];
|
|
364
|
+
}
|
|
365
|
+
else if (arg.startsWith("--input-file=")) {
|
|
366
|
+
inputFile = arg.slice(13);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
let input;
|
|
370
|
+
try {
|
|
371
|
+
input = await resolveActionInput({
|
|
372
|
+
input: inputStr,
|
|
373
|
+
inputFile,
|
|
374
|
+
flatArgs: actionArgs.length > 0 ? actionArgs : undefined,
|
|
375
|
+
stdin: process.stdin,
|
|
376
|
+
});
|
|
377
|
+
const check = validateActionInputValue(input);
|
|
378
|
+
if (!check.valid) {
|
|
379
|
+
throw mapInputValidationFailure("cli-pre-target", check);
|
|
321
380
|
}
|
|
322
381
|
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
if (isJson) {
|
|
384
|
+
this.writeOut(JSON.stringify({
|
|
385
|
+
ok: false,
|
|
386
|
+
error: {
|
|
387
|
+
code: err?.code || "INVALID_ARGUMENT",
|
|
388
|
+
message: err?.message || String(err),
|
|
389
|
+
...(err?.details !== undefined ? { details: err.details } : {}),
|
|
390
|
+
},
|
|
391
|
+
}, null, 2));
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
this.writeErr(`Error: ${err?.message || String(err)}`);
|
|
395
|
+
}
|
|
396
|
+
return ExitCode.INVALID_ARGUMENT;
|
|
397
|
+
}
|
|
323
398
|
const result = await target.runAction(id, input, {
|
|
324
399
|
signal,
|
|
325
400
|
timeoutMs,
|
|
@@ -646,3 +721,34 @@ export class StandaloneRuntime {
|
|
|
646
721
|
export function createStandaloneRuntime(options) {
|
|
647
722
|
return new StandaloneRuntime(options);
|
|
648
723
|
}
|
|
724
|
+
/**
|
|
725
|
+
* 独立二进制进程入口适配器。
|
|
726
|
+
* 仅在命令行可执行入口调用,负责全局 SIGINT 监听与退出状态码写入。
|
|
727
|
+
*/
|
|
728
|
+
export async function runStandaloneProcess(argv, options) {
|
|
729
|
+
const controller = new AbortController();
|
|
730
|
+
const control = {
|
|
731
|
+
signal: controller.signal,
|
|
732
|
+
};
|
|
733
|
+
let sigintCount = 0;
|
|
734
|
+
const sigintHandler = () => {
|
|
735
|
+
sigintCount++;
|
|
736
|
+
if (sigintCount === 1) {
|
|
737
|
+
control.cancellationSource = "sigint";
|
|
738
|
+
controller.abort(new Error("Interrupted by SIGINT"));
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
process.exitCode = 130;
|
|
742
|
+
process.exit(130);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
process.on("SIGINT", sigintHandler);
|
|
746
|
+
try {
|
|
747
|
+
const dispatcher = new StandaloneDispatcher(options);
|
|
748
|
+
const exitCode = await dispatcher.dispatch(argv, control);
|
|
749
|
+
process.exitCode = exitCode;
|
|
750
|
+
}
|
|
751
|
+
finally {
|
|
752
|
+
process.removeListener("SIGINT", sigintHandler);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./validator.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./validator.js";
|
|
@@ -10,8 +10,18 @@ export interface ValidationResult {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* 递归检查数据中是否包含危险的原型污染属性键名(__proto__、constructor、prototype)。
|
|
13
|
+
* 内部委托 validateActionInputValue 执行迭代遍历,确保环路安全、有向无环图安全且无调用栈溢出风险。
|
|
13
14
|
*/
|
|
14
15
|
export declare function hasDangerousKeys(data: unknown): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 内部纯 Schema 校验器。
|
|
18
|
+
* 仅处理布尔 Schema 与 Ajv 规则校验,严禁执行 dangerous-key 遍历。
|
|
19
|
+
*
|
|
20
|
+
* @param schema 期望匹配的 JSON Schema 对象
|
|
21
|
+
* @param data 待校验的原始数据
|
|
22
|
+
* @returns 包含 valid 状态与错误信息列表的 ValidationResult
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateSchemaOnly(schema: JsonSchema | undefined, data: unknown): ValidationResult;
|
|
15
25
|
/**
|
|
16
26
|
* 校验指定数据是否符合给定的 JSON Schema 契约。
|
|
17
27
|
*
|