@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.
Files changed (53) hide show
  1. package/dist/errors.d.ts +20 -0
  2. package/dist/errors.js +20 -0
  3. package/dist/export/templates.js +42 -30
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +3 -1
  6. package/dist/input/advice.d.ts +100 -0
  7. package/dist/input/advice.js +767 -0
  8. package/dist/input/describe.d.ts +106 -0
  9. package/dist/input/describe.js +176 -0
  10. package/dist/input/file-input.d.ts +54 -0
  11. package/dist/input/file-input.js +124 -0
  12. package/dist/input/flat-decode.d.ts +11 -0
  13. package/dist/input/flat-decode.js +13 -0
  14. package/dist/input/flat-errors.d.ts +72 -0
  15. package/dist/input/flat-errors.js +87 -0
  16. package/dist/input/flat-materializer.d.ts +29 -0
  17. package/dist/input/flat-materializer.js +179 -0
  18. package/dist/input/flat-parser.d.ts +50 -0
  19. package/dist/input/flat-parser.js +169 -0
  20. package/dist/input/flat-predicates.d.ts +34 -0
  21. package/dist/input/flat-predicates.js +50 -0
  22. package/dist/input/index.d.ts +14 -0
  23. package/dist/input/index.js +14 -0
  24. package/dist/input/input-resolver.d.ts +63 -0
  25. package/dist/input/input-resolver.js +254 -0
  26. package/dist/input/json-depth-scanner.d.ts +17 -0
  27. package/dist/input/json-depth-scanner.js +49 -0
  28. package/dist/input/metadata.d.ts +99 -0
  29. package/dist/input/metadata.js +99 -0
  30. package/dist/input/stdin-input.d.ts +26 -0
  31. package/dist/input/stdin-input.js +125 -0
  32. package/dist/input/utf8.d.ts +19 -0
  33. package/dist/input/utf8.js +36 -0
  34. package/dist/input/validation-mapper.d.ts +27 -0
  35. package/dist/input/validation-mapper.js +144 -0
  36. package/dist/json/index.d.ts +1 -0
  37. package/dist/json/index.js +1 -0
  38. package/dist/json/value-validator.d.ts +88 -0
  39. package/dist/json/value-validator.js +330 -0
  40. package/dist/profile/client-health.js +1 -0
  41. package/dist/profile/client-transport.js +1 -0
  42. package/dist/project/init.js +2 -2
  43. package/dist/runtime/runner.d.ts +2 -12
  44. package/dist/runtime/runner.js +38 -68
  45. package/dist/runtime/standalone.d.ts +14 -1
  46. package/dist/runtime/standalone.js +177 -71
  47. package/dist/schema/index.d.ts +1 -0
  48. package/dist/schema/index.js +1 -0
  49. package/dist/schema/validator.d.ts +10 -0
  50. package/dist/schema/validator.js +48 -54
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +2 -2
@@ -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
- * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
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
  */
@@ -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 { validateSchema } from "../schema/validator.js";
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
- * 校验值是否为合法的 JSON 兼容结构,严禁 NaN、Infinity、循环引用及不可序列化类型。
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 = validateJsonValue(input);
257
+ // 输入参数 JSON 格式与合法性防御校验(拦截 NaN/Infinity/循环引用等非 JSON 类型及非法原型键策略违规)
258
+ const inputCheck = validateActionInputValue(input);
305
259
  if (!inputCheck.valid) {
306
- const error = {
307
- code: INPUT_NOT_JSON,
308
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
309
- };
310
- // 安全记录 failed 状态(不可序列化的非法 input 严禁直接写入持久化存储)
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 ?? this.projectConfig?.actions?.[targetActionId]?.inputSchema;
520
- if (targetInputSchema) {
521
- const val = validateSchema(targetInputSchema, input);
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 = validateJsonValue(input);
694
+ const inputCheck = validateActionInputValue(input);
733
695
  if (!inputCheck.valid) {
734
- const error = {
735
- code: INPUT_NOT_JSON,
736
- message: `Input validation failed for action '${targetActionId}': ${inputCheck.reason}`,
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 ?? this.projectConfig?.actions?.[targetActionId]?.outputSchema;
799
- if (targetOutputSchema) {
800
- const outVal = validateSchema(targetOutputSchema, rawOutput);
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 args = [...argv];
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 < args.length; i++) {
96
- const arg = args[i];
97
- if (arg === "--data-dir" && i + 1 < args.length) {
98
- dataDir = args[++i];
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 < args.length) {
104
- const pair = args[++i];
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 (args.includes("--async")) {
123
- const isJson = args.includes("--json");
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
- return await this.handleList(target, subArgs);
166
+ code = await this.handleList(target, subArgs);
167
+ break;
167
168
  case "describe":
168
169
  case "show":
169
- return await this.handleDescribe(target, subArgs);
170
+ code = await this.handleDescribe(target, subArgs);
171
+ break;
170
172
  case "run":
171
- return await this.handleRun(target, subArgs, controller.signal);
173
+ code = await this.handleRun(target, subArgs, actionArgs, control?.signal);
174
+ break;
172
175
  case "config":
173
- return await this.handleConfig(target, subArgs);
176
+ code = await this.handleConfig(target, subArgs);
177
+ break;
174
178
  case "state":
175
- return await this.handleState(target, subArgs);
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 (err?.name === "AbortError" || controller.signal.aborted) {
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
- this.writeErr("Error: Action ID is required for describe");
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
- this.writeErr(`Error: Action '${id}' not found`);
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 detail = {
252
- id: action.id,
282
+ const payload = buildActionDescribePayload(action, {
253
283
  packageId: this.options.packageId,
254
- description: action.description,
255
- inputSchema: action.inputSchema,
256
- outputSchema: action.outputSchema,
257
- };
284
+ });
258
285
  if (isJson) {
259
- this.writeOut(JSON.stringify(detail, null, 2));
286
+ this.writeOut(JSON.stringify(payload, null, 2));
260
287
  }
261
288
  else {
262
- let text = `Action: ${action.id}\n`;
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
- this.writeErr("Error: Action ID is required for run");
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 input = {};
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 === "--json") {
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
- input = JSON.parse(subArgs[++i]);
318
+ timeoutMs = parseDuration(subArgs[++i]);
298
319
  }
299
- catch (e) {
300
- this.writeErr(`Error parsing --input JSON: ${e.message}`);
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("--input=")) {
336
+ else if (arg.startsWith("--timeout=")) {
305
337
  try {
306
- input = JSON.parse(arg.slice(8));
338
+ timeoutMs = parseDuration(arg.slice(10));
307
339
  }
308
- catch (e) {
309
- this.writeErr(`Error parsing --input JSON: ${e.message}`);
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
- try {
315
- input = JSON.parse(readFileSync(subArgs[++i], "utf-8"));
316
- }
317
- catch (e) {
318
- this.writeErr(`Error reading --input-file: ${e.message}`);
319
- return ExitCode.INVALID_ARGUMENT;
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
  *