@actiondock/core 2.5.1 → 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 (175) hide show
  1. package/dist/app/app.d.ts +14 -1
  2. package/dist/app/app.js +88 -222
  3. package/dist/app/static-index.d.ts +39 -0
  4. package/dist/app/static-index.js +164 -0
  5. package/dist/app/types.d.ts +5 -0
  6. package/dist/catalog/action-index.js +6 -4
  7. package/dist/catalog/package-catalog.js +2 -1
  8. package/dist/doctor/checks/index.d.ts +5 -0
  9. package/dist/doctor/checks/index.js +5 -0
  10. package/dist/doctor/checks/project-locate.d.ts +6 -0
  11. package/dist/doctor/checks/project-locate.js +36 -0
  12. package/dist/doctor/checks/project.d.ts +6 -0
  13. package/dist/doctor/checks/project.js +383 -0
  14. package/dist/doctor/checks/registry.d.ts +13 -0
  15. package/dist/doctor/checks/registry.js +207 -0
  16. package/dist/doctor/checks/runtime.d.ts +13 -0
  17. package/dist/doctor/checks/runtime.js +90 -0
  18. package/dist/doctor/checks/storage.d.ts +5 -0
  19. package/dist/doctor/checks/storage.js +34 -0
  20. package/dist/doctor/context.d.ts +35 -0
  21. package/dist/doctor/context.js +11 -0
  22. package/dist/doctor/doctor.d.ts +5 -0
  23. package/dist/doctor/doctor.js +33 -602
  24. package/dist/errors.d.ts +22 -0
  25. package/dist/errors.js +22 -0
  26. package/dist/execution/service.js +14 -9
  27. package/dist/export/templates.js +42 -30
  28. package/dist/host/host.d.ts +33 -0
  29. package/dist/host/host.js +222 -362
  30. package/dist/host/routing.d.ts +92 -0
  31. package/dist/host/routing.js +238 -0
  32. package/dist/host/types.d.ts +8 -0
  33. package/dist/index.d.ts +3 -1
  34. package/dist/index.js +3 -1
  35. package/dist/input/advice.d.ts +100 -0
  36. package/dist/input/advice.js +767 -0
  37. package/dist/input/describe.d.ts +106 -0
  38. package/dist/input/describe.js +176 -0
  39. package/dist/input/file-input.d.ts +54 -0
  40. package/dist/input/file-input.js +124 -0
  41. package/dist/input/flat-decode.d.ts +11 -0
  42. package/dist/input/flat-decode.js +13 -0
  43. package/dist/input/flat-errors.d.ts +72 -0
  44. package/dist/input/flat-errors.js +87 -0
  45. package/dist/input/flat-materializer.d.ts +29 -0
  46. package/dist/input/flat-materializer.js +179 -0
  47. package/dist/input/flat-parser.d.ts +50 -0
  48. package/dist/input/flat-parser.js +169 -0
  49. package/dist/input/flat-predicates.d.ts +34 -0
  50. package/dist/input/flat-predicates.js +50 -0
  51. package/dist/input/index.d.ts +14 -0
  52. package/dist/input/index.js +14 -0
  53. package/dist/input/input-resolver.d.ts +63 -0
  54. package/dist/input/input-resolver.js +254 -0
  55. package/dist/input/json-depth-scanner.d.ts +17 -0
  56. package/dist/input/json-depth-scanner.js +49 -0
  57. package/dist/input/metadata.d.ts +99 -0
  58. package/dist/input/metadata.js +99 -0
  59. package/dist/input/stdin-input.d.ts +26 -0
  60. package/dist/input/stdin-input.js +125 -0
  61. package/dist/input/utf8.d.ts +19 -0
  62. package/dist/input/utf8.js +36 -0
  63. package/dist/input/validation-mapper.d.ts +27 -0
  64. package/dist/input/validation-mapper.js +144 -0
  65. package/dist/ipc/host.js +38 -0
  66. package/dist/json/index.d.ts +1 -0
  67. package/dist/json/index.js +1 -0
  68. package/dist/json/value-validator.d.ts +88 -0
  69. package/dist/json/value-validator.js +330 -0
  70. package/dist/platform/types.d.ts +7 -0
  71. package/dist/process/control-arbiter.d.ts +107 -0
  72. package/dist/process/control-arbiter.js +251 -0
  73. package/dist/process/diagnostics-sink.d.ts +26 -0
  74. package/dist/process/diagnostics-sink.js +35 -0
  75. package/dist/process/index.d.ts +7 -0
  76. package/dist/process/index.js +7 -0
  77. package/dist/process/input-dispatcher.d.ts +88 -0
  78. package/dist/process/input-dispatcher.js +240 -0
  79. package/dist/process/managed-record.d.ts +78 -0
  80. package/dist/process/managed-record.js +1 -0
  81. package/dist/process/process-manager.d.ts +15 -56
  82. package/dist/process/process-manager.js +119 -707
  83. package/dist/process/reservation-table.d.ts +39 -0
  84. package/dist/process/reservation-table.js +80 -0
  85. package/dist/process/run-executor.d.ts +16 -0
  86. package/dist/process/run-executor.js +155 -0
  87. package/dist/process/terminal-output-cache.d.ts +66 -0
  88. package/dist/process/terminal-output-cache.js +132 -0
  89. package/dist/profile/client-actions.d.ts +56 -0
  90. package/dist/profile/client-actions.js +215 -0
  91. package/dist/profile/client-config.d.ts +20 -0
  92. package/dist/profile/client-config.js +51 -0
  93. package/dist/profile/client-health.d.ts +14 -0
  94. package/dist/profile/client-health.js +69 -0
  95. package/dist/profile/client-playbooks.d.ts +19 -0
  96. package/dist/profile/client-playbooks.js +36 -0
  97. package/dist/profile/client-query.d.ts +30 -0
  98. package/dist/profile/client-query.js +70 -0
  99. package/dist/profile/client-runs.d.ts +42 -0
  100. package/dist/profile/client-runs.js +58 -0
  101. package/dist/profile/client-state.d.ts +56 -0
  102. package/dist/profile/client-state.js +83 -0
  103. package/dist/profile/client-transport.d.ts +61 -0
  104. package/dist/profile/client-transport.js +156 -0
  105. package/dist/profile/client.d.ts +23 -142
  106. package/dist/profile/client.js +28 -588
  107. package/dist/profile/manager.js +1 -1
  108. package/dist/project/closure.d.ts +4 -4
  109. package/dist/project/closure.js +11 -5
  110. package/dist/project/init.js +2 -2
  111. package/dist/project/loader.d.ts +0 -25
  112. package/dist/project/loader.js +1 -154
  113. package/dist/project/transactions.d.ts +12 -14
  114. package/dist/project/transactions.js +60 -836
  115. package/dist/registry/registry.js +6 -4
  116. package/dist/registry/resolve.js +1 -1
  117. package/dist/runtime/action-registry.d.ts +45 -0
  118. package/dist/runtime/action-registry.js +108 -0
  119. package/dist/runtime/clock.d.ts +5 -17
  120. package/dist/runtime/clock.js +5 -15
  121. package/dist/runtime/context.d.ts +3 -0
  122. package/dist/runtime/context.js +17 -56
  123. package/dist/runtime/index.d.ts +3 -0
  124. package/dist/runtime/index.js +3 -0
  125. package/dist/runtime/package-runner-factory.d.ts +82 -0
  126. package/dist/runtime/package-runner-factory.js +186 -0
  127. package/dist/runtime/run-persistence.d.ts +85 -0
  128. package/dist/runtime/run-persistence.js +111 -0
  129. package/dist/runtime/runner.d.ts +25 -45
  130. package/dist/runtime/runner.js +130 -354
  131. package/dist/runtime/standalone.d.ts +25 -2
  132. package/dist/runtime/standalone.js +305 -87
  133. package/dist/schema/index.d.ts +1 -0
  134. package/dist/schema/index.js +1 -0
  135. package/dist/schema/validator.d.ts +10 -0
  136. package/dist/schema/validator.js +48 -54
  137. package/dist/server/dispatcher.d.ts +8 -3
  138. package/dist/server/dispatcher.js +16 -40
  139. package/dist/server/routes/runs.js +6 -4
  140. package/dist/server/security.d.ts +1 -7
  141. package/dist/server/security.js +2 -13
  142. package/dist/storage/clock.d.ts +25 -0
  143. package/dist/storage/clock.js +23 -0
  144. package/dist/storage/data-dir-lock.d.ts +3 -36
  145. package/dist/storage/data-dir-lock.js +48 -881
  146. package/dist/storage/index.d.ts +8 -1
  147. package/dist/storage/index.js +8 -2
  148. package/dist/storage/lazy.js +1 -0
  149. package/dist/storage/lock-core.d.ts +162 -0
  150. package/dist/storage/lock-core.js +885 -0
  151. package/dist/storage/sqlite.d.ts +19 -6
  152. package/dist/storage/sqlite.js +81 -89
  153. package/dist/storage/types.d.ts +27 -5
  154. package/dist/storage/types.js +21 -0
  155. package/dist/storage/utils.d.ts +22 -0
  156. package/dist/storage/utils.js +34 -0
  157. package/dist/target/local.d.ts +1 -0
  158. package/dist/target/local.js +23 -17
  159. package/dist/target/remote-errors.d.ts +21 -0
  160. package/dist/target/remote-errors.js +51 -0
  161. package/dist/target/remote-polling.d.ts +35 -0
  162. package/dist/target/remote-polling.js +104 -0
  163. package/dist/target/remote.d.ts +4 -0
  164. package/dist/target/remote.js +108 -301
  165. package/dist/target/sse-parser.d.ts +35 -0
  166. package/dist/target/sse-parser.js +163 -0
  167. package/dist/target/target.js +2 -0
  168. package/dist/target/types.d.ts +6 -0
  169. package/dist/utils/index.d.ts +1 -0
  170. package/dist/utils/index.js +1 -0
  171. package/dist/utils/net.d.ts +10 -0
  172. package/dist/utils/net.js +16 -0
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import type { ActionDefinition } from "@actiondock/sdk";
1
+ import { type ActionDefinition } from "@actiondock/sdk";
2
2
  import type { ActionSpec } from "../app/types.js";
3
3
  import type { ConfigItemDefinition } from "../project/types.js";
4
4
  import type { ActionDockTarget } from "../target/types.js";
@@ -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,14 +70,25 @@ 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;
72
80
  private handleConfig;
73
81
  private handleState;
82
+ /**
83
+ * 判定配置读取异常是否为目标层能力缺失(键未配置语义)而非内部故障。
84
+ */
85
+ private isTargetCapabilityError;
86
+ /**
87
+ * 解析携带命名空间前缀的状态键参数:委托 sdk 单一事实源 decodeStateKey。
88
+ *
89
+ * 解析失败(如存在多个未转义冒号的歧义键)时按纯键处理,交由存储层兜底。
90
+ */
91
+ private decodeStateKeyArg;
74
92
  private printHelp;
75
93
  }
76
94
  /**
@@ -87,3 +105,8 @@ export declare class StandaloneRuntime {
87
105
  * 工厂函数:创建独立运行时实例。
88
106
  */
89
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,7 +1,9 @@
1
- import { readFileSync } from "node:fs";
1
+ import { decodeStateKey } from "@actiondock/sdk";
2
2
  import { filterWithFallbackInfo } from "../filter/index.js";
3
3
  import { createActionDockTarget } from "../target/target.js";
4
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";
5
7
  import { normalizeActionCollection } from "./action-collection.js";
6
8
  import { parseDuration } from "../utils/index.js";
7
9
  /**
@@ -83,24 +85,27 @@ export class StandaloneDispatcher {
83
85
  * 解析命令行参数并分发执行对应子命令。
84
86
  *
85
87
  * @param argv 命令行参数数组(如 process.argv.slice(2))
88
+ * @param control 可选的中断与取消控制契约
86
89
  * @returns 退出状态码(0: 成功, 1: 失败, 2: 参数错误, 130: 中断)
87
90
  */
88
- async dispatch(argv) {
89
- 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) : [];
90
95
  let dataDir;
91
96
  const configOverrides = {};
92
- // 1. 提取全局参数(--data-dir, --config
97
+ // 1. 提取全局参数(--data-dir, --config)仅从 controlArgs 中解析
93
98
  const filteredArgs = [];
94
- for (let i = 0; i < args.length; i++) {
95
- const arg = args[i];
96
- if (arg === "--data-dir" && i + 1 < args.length) {
97
- 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];
98
103
  }
99
104
  else if (arg.startsWith("--data-dir=")) {
100
105
  dataDir = arg.slice(11);
101
106
  }
102
- else if (arg === "--config" && i + 1 < args.length) {
103
- const pair = args[++i];
107
+ else if (arg === "--config" && i + 1 < controlArgs.length) {
108
+ const pair = controlArgs[++i];
104
109
  const [k, ...v] = pair.split("=");
105
110
  if (k)
106
111
  configOverrides[k] = v.join("=");
@@ -118,8 +123,8 @@ export class StandaloneDispatcher {
118
123
  const command = filteredArgs[0] || "help";
119
124
  const subArgs = filteredArgs.slice(1);
120
125
  // 2. 独立入口拒绝异步启动语义
121
- if (args.includes("--async")) {
122
- const isJson = args.includes("--json") || args.includes("--envelope");
126
+ if (controlArgs.includes("--async")) {
127
+ const isJson = controlArgs.includes("--json");
123
128
  if (isJson) {
124
129
  this.writeOut(JSON.stringify({
125
130
  ok: false,
@@ -154,47 +159,51 @@ export class StandaloneDispatcher {
154
159
  this.writeErr(`Error initializing standalone target: ${err?.message || err}`);
155
160
  return ExitCode.FAILURE;
156
161
  }
157
- const controller = new AbortController();
158
- const sigintHandler = () => {
159
- controller.abort(new Error("Interrupted by SIGINT"));
160
- };
161
- process.once("SIGINT", sigintHandler);
162
162
  try {
163
+ let code;
163
164
  switch (command) {
164
165
  case "list":
165
- return await this.handleList(target, subArgs);
166
+ code = await this.handleList(target, subArgs);
167
+ break;
166
168
  case "describe":
167
169
  case "show":
168
- return await this.handleDescribe(target, subArgs);
170
+ code = await this.handleDescribe(target, subArgs);
171
+ break;
169
172
  case "run":
170
- return await this.handleRun(target, subArgs, controller.signal);
173
+ code = await this.handleRun(target, subArgs, actionArgs, control?.signal);
174
+ break;
171
175
  case "config":
172
- return await this.handleConfig(target, subArgs);
176
+ code = await this.handleConfig(target, subArgs);
177
+ break;
173
178
  case "state":
174
- return await this.handleState(target, subArgs);
179
+ code = await this.handleState(target, subArgs);
180
+ break;
175
181
  default:
176
182
  this.writeErr(`Unknown command: '${command}'`);
177
183
  this.printHelp();
178
184
  return ExitCode.INVALID_ARGUMENT;
179
185
  }
186
+ if (control?.cancellationSource === "sigint" && control?.signal?.aborted) {
187
+ return ExitCode.SIGINT;
188
+ }
189
+ return code;
180
190
  }
181
191
  catch (err) {
182
- if (err?.name === "AbortError" || controller.signal.aborted) {
192
+ if (control?.cancellationSource === "sigint" &&
193
+ (control?.signal?.aborted || err?.name === "AbortError" || err?.message?.includes("SIGINT"))) {
183
194
  return ExitCode.SIGINT;
184
195
  }
185
196
  this.writeErr(`Error: ${err?.message || err}`);
186
197
  return ExitCode.FAILURE;
187
198
  }
188
199
  finally {
189
- process.removeListener("SIGINT", sigintHandler);
190
200
  if (ownsTarget) {
191
201
  await target.close();
192
202
  }
193
203
  }
194
204
  }
195
205
  async handleList(target, subArgs) {
196
- const isJson = subArgs.includes("--json") || subArgs.includes("--envelope");
197
- const useEnvelope = subArgs.includes("--envelope");
206
+ const isJson = subArgs.includes("--json");
198
207
  const noFallback = subArgs.includes("--no-fallback");
199
208
  let intent;
200
209
  const positionalPatterns = [];
@@ -222,10 +231,7 @@ export class StandaloneDispatcher {
222
231
  }));
223
232
  const filterRes = filterWithFallbackInfo(list, effectiveIntent, [(a) => a.id, (a) => a.description], !noFallback);
224
233
  if (isJson) {
225
- const payload = useEnvelope
226
- ? { ok: true, data: filterRes.items }
227
- : filterRes.items;
228
- this.writeOut(JSON.stringify(payload, null, 2));
234
+ this.writeOut(JSON.stringify(filterRes.items, null, 2));
229
235
  }
230
236
  else {
231
237
  let text = `Actions in ${this.options.packageId} (v${this.options.version}):\n\n`;
@@ -238,10 +244,20 @@ export class StandaloneDispatcher {
238
244
  }
239
245
  async handleDescribe(target, subArgs) {
240
246
  const id = subArgs.find((a) => !a.startsWith("-"));
241
- const isJson = subArgs.includes("--json") || subArgs.includes("--envelope");
242
- const useEnvelope = subArgs.includes("--envelope");
247
+ const isJson = subArgs.includes("--json");
243
248
  if (!id) {
244
- 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
+ }
245
261
  return ExitCode.INVALID_ARGUMENT;
246
262
  }
247
263
  let action;
@@ -249,83 +265,223 @@ export class StandaloneDispatcher {
249
265
  action = await target.describeAction(id);
250
266
  }
251
267
  catch {
252
- 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
+ }
253
280
  return ExitCode.INVALID_ARGUMENT;
254
281
  }
255
- const detail = {
256
- id: action.id,
282
+ const payload = buildActionDescribePayload(action, {
257
283
  packageId: this.options.packageId,
258
- description: action.description,
259
- inputSchema: action.inputSchema,
260
- outputSchema: action.outputSchema,
261
- };
284
+ });
262
285
  if (isJson) {
263
- const payload = useEnvelope ? { ok: true, data: detail } : detail;
264
286
  this.writeOut(JSON.stringify(payload, null, 2));
265
287
  }
266
288
  else {
267
- let text = `Action: ${action.id}\n`;
268
- if (action.description)
269
- text += `Description: ${action.description}\n`;
270
- if (action.inputSchema) {
271
- text += `\nInput Schema:\n${JSON.stringify(action.inputSchema, null, 2)}\n`;
272
- }
273
- if (action.outputSchema) {
274
- text += `\nOutput Schema:\n${JSON.stringify(action.outputSchema, null, 2)}\n`;
275
- }
276
- this.writeOut(text.trimEnd());
289
+ this.writeOut(formatActionDetail(payload));
277
290
  }
278
291
  return ExitCode.SUCCESS;
279
292
  }
280
- async handleRun(target, subArgs, signal) {
293
+ async handleRun(target, subArgs, actionArgs, signal) {
294
+ const isJson = subArgs.includes("--json");
281
295
  const id = subArgs.find((a) => !a.startsWith("-"));
282
296
  if (!id) {
283
- 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
+ }
284
309
  return ExitCode.INVALID_ARGUMENT;
285
310
  }
286
- let input = {};
311
+ let inputStr;
312
+ let inputFile;
287
313
  let timeoutMs;
288
314
  for (let i = 0; i < subArgs.length; i++) {
289
315
  const arg = subArgs[i];
290
316
  if (arg === "--timeout" && i + 1 < subArgs.length) {
291
- timeoutMs = parseDuration(subArgs[++i]);
292
- }
293
- else if (arg.startsWith("--timeout=")) {
294
- timeoutMs = parseDuration(arg.slice(10));
295
- }
296
- else if (arg === "--input" && i + 1 < subArgs.length) {
297
317
  try {
298
- input = JSON.parse(subArgs[++i]);
318
+ timeoutMs = parseDuration(subArgs[++i]);
299
319
  }
300
- catch (e) {
301
- 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
+ }
302
333
  return ExitCode.INVALID_ARGUMENT;
303
334
  }
304
335
  }
305
- else if (arg.startsWith("--input=")) {
336
+ else if (arg.startsWith("--timeout=")) {
306
337
  try {
307
- input = JSON.parse(arg.slice(8));
338
+ timeoutMs = parseDuration(arg.slice(10));
308
339
  }
309
- catch (e) {
310
- 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
+ }
311
353
  return ExitCode.INVALID_ARGUMENT;
312
354
  }
313
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
+ }
314
362
  else if (arg === "--input-file" && i + 1 < subArgs.length) {
315
- try {
316
- input = JSON.parse(readFileSync(subArgs[++i], "utf-8"));
317
- }
318
- catch (e) {
319
- this.writeErr(`Error reading --input-file: ${e.message}`);
320
- return ExitCode.INVALID_ARGUMENT;
321
- }
363
+ inputFile = subArgs[++i];
364
+ }
365
+ else if (arg.startsWith("--input-file=")) {
366
+ inputFile = arg.slice(13);
322
367
  }
323
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);
380
+ }
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
+ }
324
398
  const result = await target.runAction(id, input, {
325
399
  signal,
326
400
  timeoutMs,
327
401
  });
328
- this.writeOut(JSON.stringify(result, null, 2));
402
+ if (isJson) {
403
+ this.writeOut(JSON.stringify(result, null, 2));
404
+ }
405
+ else {
406
+ if (result.ok) {
407
+ const data = result.data;
408
+ let rawText;
409
+ let metaInfo;
410
+ if (typeof data === "string") {
411
+ rawText = data;
412
+ }
413
+ else if (data !== null && typeof data === "object") {
414
+ if ("content" in data && data.content !== undefined) {
415
+ rawText =
416
+ typeof data.content === "object" && data.content !== null
417
+ ? JSON.stringify(data.content, null, 2)
418
+ : String(data.content);
419
+ const { content, ...rest } = data;
420
+ if (Object.keys(rest).length > 0) {
421
+ metaInfo = rest;
422
+ }
423
+ }
424
+ else if ("text" in data && typeof data.text === "string") {
425
+ rawText = data.text;
426
+ const { text, ...rest } = data;
427
+ if (Object.keys(rest).length > 0) {
428
+ metaInfo = rest;
429
+ }
430
+ }
431
+ else if ("message" in data && typeof data.message === "string") {
432
+ rawText = data.message;
433
+ const { message, ...rest } = data;
434
+ if (Object.keys(rest).length > 0) {
435
+ metaInfo = rest;
436
+ }
437
+ }
438
+ else {
439
+ rawText = JSON.stringify(data, null, 2);
440
+ }
441
+ }
442
+ else if (data !== undefined) {
443
+ rawText = String(data);
444
+ }
445
+ else {
446
+ rawText = "";
447
+ }
448
+ if (metaInfo) {
449
+ const parts = [];
450
+ const title = metaInfo.path ? String(metaInfo.path) : id;
451
+ parts.push(title);
452
+ if (metaInfo.startLine !== undefined && metaInfo.endLine !== undefined) {
453
+ parts.push(`lines ${metaInfo.startLine}-${metaInfo.endLine}`);
454
+ }
455
+ else if (metaInfo.line !== undefined) {
456
+ parts.push(`line ${metaInfo.line}`);
457
+ }
458
+ if (metaInfo.hasMore !== undefined) {
459
+ parts.push(`hasMore: ${metaInfo.hasMore}`);
460
+ }
461
+ if (metaInfo.truncated) {
462
+ parts.push("truncated: true");
463
+ }
464
+ const handled = new Set(["path", "startLine", "endLine", "line", "hasMore", "truncated"]);
465
+ for (const [k, v] of Object.entries(metaInfo)) {
466
+ if (!handled.has(k) && v !== undefined) {
467
+ parts.push(`${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`);
468
+ }
469
+ }
470
+ if (parts.length > 0) {
471
+ this.writeErr(`[${parts.join(" | ")}]`);
472
+ }
473
+ }
474
+ this.writeOut(rawText);
475
+ }
476
+ else {
477
+ this.writeErr(`Error [${result.error.code}]: ${result.error.message}`);
478
+ if (result.error.details) {
479
+ this.writeErr(typeof result.error.details === "string"
480
+ ? result.error.details
481
+ : JSON.stringify(result.error.details, null, 2));
482
+ }
483
+ }
484
+ }
329
485
  return result.ok ? ExitCode.SUCCESS : ExitCode.FAILURE;
330
486
  }
331
487
  async handleConfig(target, subArgs) {
@@ -352,9 +508,15 @@ export class StandaloneDispatcher {
352
508
  this.writeOut(item.configured ? JSON.stringify(item.value) : "undefined");
353
509
  return ExitCode.SUCCESS;
354
510
  }
355
- catch {
356
- this.writeOut("undefined");
357
- return ExitCode.SUCCESS;
511
+ catch (err) {
512
+ // 目标层能力缺失(如远端未开启管理接口)视为键未配置,输出 undefined
513
+ if (this.isTargetCapabilityError(err)) {
514
+ this.writeOut("undefined");
515
+ return ExitCode.SUCCESS;
516
+ }
517
+ // 内部异常严禁吞没:透传 stderr 并以 FAILURE 退出码暴露
518
+ this.writeErr(`Error reading config '${key}': ${err?.message || String(err)}`);
519
+ return ExitCode.FAILURE;
358
520
  }
359
521
  }
360
522
  if (sub === "set") {
@@ -425,10 +587,10 @@ export class StandaloneDispatcher {
425
587
  }
426
588
  let ns = namespace;
427
589
  let actualKey = key;
428
- if (ns === undefined && key.includes(":")) {
429
- const colonIdx = key.indexOf(":");
430
- ns = key.slice(0, colonIdx);
431
- actualKey = key.slice(colonIdx + 1);
590
+ if (ns === undefined) {
591
+ const decoded = this.decodeStateKeyArg(key);
592
+ ns = decoded.namespace || undefined;
593
+ actualKey = decoded.key;
432
594
  }
433
595
  const val = await target.getState(this.options.packageId, "", actualKey, { namespace: ns });
434
596
  if (isJson) {
@@ -448,10 +610,10 @@ export class StandaloneDispatcher {
448
610
  }
449
611
  let ns = namespace || "";
450
612
  let actualKey = key;
451
- if (namespace === undefined && key.includes(":")) {
452
- const colonIdx = key.indexOf(":");
453
- ns = key.slice(0, colonIdx);
454
- actualKey = key.slice(colonIdx + 1);
613
+ if (namespace === undefined) {
614
+ const decoded = this.decodeStateKeyArg(key);
615
+ ns = decoded.namespace;
616
+ actualKey = decoded.key;
455
617
  }
456
618
  let parsed = rawVal;
457
619
  try {
@@ -501,6 +663,30 @@ export class StandaloneDispatcher {
501
663
  this.writeErr(`Unknown state subcommand: '${sub}'`);
502
664
  return ExitCode.INVALID_ARGUMENT;
503
665
  }
666
+ /**
667
+ * 判定配置读取异常是否为目标层能力缺失(键未配置语义)而非内部故障。
668
+ */
669
+ isTargetCapabilityError(err) {
670
+ const code = String(err?.code || "");
671
+ const msg = String(err?.message || "");
672
+ return (code === "TARGET_CAPABILITY_UNAVAILABLE" ||
673
+ code === "CAPABILITY_UNAVAILABLE" ||
674
+ msg.includes("TARGET_CAPABILITY_UNAVAILABLE") ||
675
+ msg.includes("Management APIs are not enabled"));
676
+ }
677
+ /**
678
+ * 解析携带命名空间前缀的状态键参数:委托 sdk 单一事实源 decodeStateKey。
679
+ *
680
+ * 解析失败(如存在多个未转义冒号的歧义键)时按纯键处理,交由存储层兜底。
681
+ */
682
+ decodeStateKeyArg(key) {
683
+ try {
684
+ return decodeStateKey(key);
685
+ }
686
+ catch {
687
+ return { namespace: "", key };
688
+ }
689
+ }
504
690
  printHelp() {
505
691
  this.writeOut(`${this.options.packageId} (v${this.options.version})`);
506
692
  if (this.options.description)
@@ -508,7 +694,8 @@ export class StandaloneDispatcher {
508
694
  this.writeOut("Usage:");
509
695
  this.writeOut(" <cmd> list [--json] List available actions");
510
696
  this.writeOut(" <cmd> describe <id> [--json] Show action details and schemas");
511
- this.writeOut(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
697
+ this.writeOut(" <cmd> run <id> [--input '<json>'] Execute action (raw text output by default)");
698
+ this.writeOut(" <cmd> run <id> [--json] Output standard execution result in JSON format");
512
699
  this.writeOut(" <cmd> config list/get/set/delete Manage package configuration");
513
700
  this.writeOut(" <cmd> state list/get/set/delete Manage shared state store");
514
701
  this.writeOut("\nGlobal options:");
@@ -534,3 +721,34 @@ export class StandaloneRuntime {
534
721
  export function createStandaloneRuntime(options) {
535
722
  return new StandaloneRuntime(options);
536
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
  *