@actiondock/core 2.18.0 → 2.20.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/client/config.d.ts +17 -14
- package/dist/client/config.js +112 -14
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/runs.d.ts +9 -0
- package/dist/client/runs.js +7 -0
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +8 -5
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +9 -1
- package/dist/host/host.js +266 -45
- package/dist/host/types.d.ts +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/input/config-override.d.ts +5 -0
- package/dist/input/config-override.js +23 -0
- package/dist/input/input-resolver.d.ts +24 -7
- package/dist/input/input-resolver.js +73 -5
- package/dist/invocation/bindings.d.ts +53 -0
- package/dist/invocation/bindings.js +248 -0
- package/dist/invocation/policy.d.ts +5 -1
- package/dist/invocation/policy.js +16 -9
- package/dist/invocation/remote.d.ts +40 -0
- package/dist/invocation/remote.js +489 -0
- package/dist/invocation/types.d.ts +2 -0
- package/dist/invocation/types.js +1 -0
- package/dist/package/index.d.ts +1 -1
- package/dist/package/runtime.js +105 -15
- package/dist/platform/types.d.ts +48 -1
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/index.d.ts +5 -2
- package/dist/project/index.js +5 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +4 -0
- package/dist/project/manifest.js +10 -0
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +6 -3
- package/dist/runtime/context.js +60 -13
- package/dist/runtime/env.js +15 -1
- package/dist/runtime/standalone-commands.d.ts +2 -2
- package/dist/runtime/standalone-commands.js +113 -47
- package/dist/runtime/standalone-help.d.ts +1 -0
- package/dist/runtime/standalone-help.js +15 -9
- package/dist/runtime/standalone.js +2 -2
- package/dist/server/policy.d.ts +24 -107
- package/dist/server/policy.js +134 -291
- package/dist/server/routes/actions.js +10 -8
- package/dist/server/routes/common.d.ts +5 -20
- package/dist/server/routes/common.js +22 -58
- package/dist/server/routes/config.js +65 -7
- package/dist/server/routes/doctor.d.ts +1 -0
- package/dist/server/routes/doctor.js +4 -3
- package/dist/server/routes/info.js +100 -13
- package/dist/server/routes/playbooks.js +1 -3
- package/dist/server/routes/runs.js +112 -18
- package/dist/server/server.d.ts +2 -1
- package/dist/server/server.js +38 -55
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +1 -0
- package/dist/service/remote.js +28 -29
- package/dist/service/types.d.ts +34 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/sqlite-runs.d.ts +5 -17
- package/dist/storage/sqlite-runs.js +127 -5
- package/dist/storage/sqlite.d.ts +5 -17
- package/dist/storage/sqlite.js +2 -2
- package/dist/storage/types.d.ts +31 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/project/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { loadActionBindings, validateActionBindings, resolveActionBinding, hasRemoteActionBinding, validateCanonicalActionRef, validateBindingName, } from "../invocation/bindings.js";
|
|
1
2
|
export * from "./digest.js";
|
|
2
3
|
export * from "./init.js";
|
|
3
4
|
export * from "./loader.js";
|
|
@@ -7,12 +8,14 @@ export { SNAPSHOT_TRACKED_FILES, isProjectLockHeld, acquireProjectLock, runFroze
|
|
|
7
8
|
export * from "./types.js";
|
|
8
9
|
export * from "./types-generator.js";
|
|
9
10
|
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, } from "../utils/index.js";
|
|
10
|
-
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
11
|
+
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, redactConfigValue, redactConfigValueForDeclarations, } from "../storage/mask.js";
|
|
12
|
+
export { isConfigTemplate, resolveConfigDeclaration, validateConfigDeclarations, assertValidConfigDeclarations, assertConcreteConfigKey, validateConfigValue, } from "./config-declarations.js";
|
|
11
13
|
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
12
14
|
// 动作入参解析、输出契约分析、校验与格式化(含 BOM 剥离与标准输入有界读取的单一事实源转引)
|
|
13
|
-
export { resolveActionInput, buildActionDescribePayload, formatActionDetail, ACTION_DESCRIBE_SYNTAX_REFERENCE, inspectActionTextFieldAnnotation, analyzeActionOutputContract, mapInputValidationFailure, stripBom, readStdinBounded, } from "../input/index.js";
|
|
15
|
+
export { resolveInput, resolveActionInput, resolveValueInput, hasValueInput, parseValueLiteral, buildActionDescribePayload, formatActionDetail, ACTION_DESCRIBE_SYNTAX_REFERENCE, inspectActionTextFieldAnnotation, analyzeActionOutputContract, mapInputValidationFailure, stripBom, readStdinBounded, } from "../input/index.js";
|
|
14
16
|
export { validateActionInputValue } from "../value-validator.js";
|
|
15
17
|
export { parseJson } from "../input/input-resolver.js";
|
|
18
|
+
export { parseConfigOverrideAssignment } from "../input/config-override.js";
|
|
16
19
|
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
17
20
|
// 意图过滤与查询
|
|
18
21
|
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, } from "../utils/intent.js";
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.
|
|
89
|
+
"@actiondock/sdk": "^2.20.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.20.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
package/dist/project/loader.js
CHANGED
|
@@ -4,6 +4,7 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import { NodeModuleLoader, unwrapDefaultExport } from "../platform/module-loader.js";
|
|
5
5
|
import { ActionDockError, ACTION_LOAD_FAILED, describeActionLoadFailure, INVALID_ACTION_ID, INVALID_JSON, INVALID_PACKAGE_ID, INVALID_PLAYBOOK_ID, MANIFEST_LOAD_FAILED, PLAYBOOK_NOT_FOUND, PROJECT_CONFIG_NOT_FOUND, } from "../errors.js";
|
|
6
6
|
import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
|
|
7
|
+
import { assertValidConfigDeclarations } from "./config-declarations.js";
|
|
7
8
|
/**
|
|
8
9
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
|
9
10
|
*
|
|
@@ -68,6 +69,9 @@ export function loadProjectConfig(projectRoot) {
|
|
|
68
69
|
if (parsed.playbooksDir) {
|
|
69
70
|
assertWithinProjectRoot(projectRoot, parsed.playbooksDir, "playbooksDir");
|
|
70
71
|
}
|
|
72
|
+
if (parsed.config !== undefined) {
|
|
73
|
+
assertValidConfigDeclarations(parsed.config);
|
|
74
|
+
}
|
|
71
75
|
return parsed;
|
|
72
76
|
}
|
|
73
77
|
catch (err) {
|
package/dist/project/manifest.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { assertValidConfigDeclarations, validateConfigDeclarations } from "./config-declarations.js";
|
|
3
4
|
import { isPathOutsideBoundary, PACKAGE_ID_REGEX } from "../utils/index.js";
|
|
4
5
|
import { ActionDockError, MANIFEST_CORRUPTED, MANIFEST_INVALID, MANIFEST_READ_FAILED, UNSUPPORTED_MANIFEST_SCHEMA, } from "../errors.js";
|
|
5
6
|
export const MANIFEST_FILE_NAME = "actiondock.json";
|
|
@@ -38,6 +39,9 @@ export function loadManifest(projectRoot) {
|
|
|
38
39
|
if (!parsed.id) {
|
|
39
40
|
parsed.id = basename(projectRoot);
|
|
40
41
|
}
|
|
42
|
+
if (parsed.config !== undefined) {
|
|
43
|
+
assertValidConfigDeclarations(parsed.config);
|
|
44
|
+
}
|
|
41
45
|
return parsed;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
@@ -65,6 +69,12 @@ export function validateManifest(manifest, options) {
|
|
|
65
69
|
errors.push(`Manifest 'id' is invalid: '${m.id}' (must match ${PACKAGE_ID_REGEX})`);
|
|
66
70
|
}
|
|
67
71
|
}
|
|
72
|
+
if (m.config !== undefined) {
|
|
73
|
+
const configErrors = validateConfigDeclarations(m.config);
|
|
74
|
+
for (const err of configErrors) {
|
|
75
|
+
errors.push(err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
68
78
|
if (m.actions !== undefined) {
|
|
69
79
|
if (!m.actions || typeof m.actions !== "object" || Array.isArray(m.actions)) {
|
|
70
80
|
errors.push("Manifest 'actions' must be an object");
|
package/dist/project/types.d.ts
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
* 配置项的值类型枚举。用于自动类型推断与环境变量自动转换。
|
|
3
3
|
*/
|
|
4
4
|
export type ConfigValueType = "string" | "number" | "boolean" | "object" | "array";
|
|
5
|
+
/**
|
|
6
|
+
* 配置值受限模式定义契约。
|
|
7
|
+
* 仅允许受支持的纯受限关键字集合,禁止动态计算或高级条件指令。
|
|
8
|
+
*/
|
|
9
|
+
export interface ConfigValueSchema {
|
|
10
|
+
/** 节点数据类型 */
|
|
11
|
+
type?: "object" | "array" | "string" | "integer" | "number" | "boolean";
|
|
12
|
+
/** 对象属性字段子模式映射 */
|
|
13
|
+
properties?: Record<string, ConfigValueSchema>;
|
|
14
|
+
/** 必填属性字段列表 */
|
|
15
|
+
required?: string[];
|
|
16
|
+
/** 额外属性放行策略(布尔开关或子模式) */
|
|
17
|
+
additionalProperties?: boolean | ConfigValueSchema;
|
|
18
|
+
/** 数组元素子模式 */
|
|
19
|
+
items?: ConfigValueSchema;
|
|
20
|
+
/** 枚举有效值列表 */
|
|
21
|
+
enum?: unknown[];
|
|
22
|
+
/** 字符串最小字符长度 */
|
|
23
|
+
minLength?: number;
|
|
24
|
+
/** 字符串最大字符长度 */
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
/** 数值最小值约束 */
|
|
27
|
+
minimum?: number;
|
|
28
|
+
/** 数值最大值约束 */
|
|
29
|
+
maximum?: number;
|
|
30
|
+
/** 是否为敏感字段(仅可在模式节点中扩展指定) */
|
|
31
|
+
secret?: boolean;
|
|
32
|
+
}
|
|
5
33
|
/**
|
|
6
34
|
* 在 actiondock.json 中声明的单项配置定义。
|
|
7
35
|
*/
|
|
@@ -20,6 +48,8 @@ export interface ConfigItemDefinition {
|
|
|
20
48
|
required?: boolean;
|
|
21
49
|
/** 是否允许单次调用覆盖 */
|
|
22
50
|
allowInvocationOverride?: boolean;
|
|
51
|
+
/** 配置值验证与字段级脱敏所绑定的受限模式定义 */
|
|
52
|
+
schema?: ConfigValueSchema;
|
|
23
53
|
}
|
|
24
54
|
/**
|
|
25
55
|
* 单个 Action 在 actiondock.json 清单中的声明项。
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ActionContext, ActionRef, Config, Logger, ProcessAPI, ProgressReporter, StateStore } from "@actiondock/sdk";
|
|
1
|
+
import type { ActionContext, ActionInvokeOptions, ActionRef, Config, Logger, ProcessAPI, ProgressReporter, StateStore } from "@actiondock/sdk";
|
|
2
2
|
import type { ProjectConfig } from "../project/types.js";
|
|
3
3
|
import type { RuntimeStorage } from "../storage/types.js";
|
|
4
4
|
import { ProcessManager, type ProcessOwner } from "../process/index.js";
|
|
5
|
+
import type { ConfigSource } from "../service/types.js";
|
|
5
6
|
/**
|
|
6
7
|
* 生产级配置解析器实现。
|
|
7
8
|
* 严格践行 5 层配置解析优先级链:
|
|
@@ -26,9 +27,11 @@ export declare class RuntimeConfig implements Config {
|
|
|
26
27
|
*
|
|
27
28
|
* 作为五层优先级链的唯一事实源读取视图,供 app 层 getConfig 安全视图直接委托,
|
|
28
29
|
* 避免重复实现解析链导致行为漂移。
|
|
30
|
+
* source 仅表达实际生效值来源;undefined 统一视为未命中并继续向低优先级解析,
|
|
31
|
+
* null/false/0/空串均为真实值;整链未命中时 source 为 null。
|
|
29
32
|
*/
|
|
30
33
|
describe<T = unknown>(key: string): {
|
|
31
|
-
source:
|
|
34
|
+
source: ConfigSource | null;
|
|
32
35
|
value: T | undefined;
|
|
33
36
|
};
|
|
34
37
|
}
|
|
@@ -81,7 +84,7 @@ export interface ContextOptions {
|
|
|
81
84
|
owner?: ProcessOwner;
|
|
82
85
|
progress?: ProgressReporter;
|
|
83
86
|
logger?: Logger;
|
|
84
|
-
onActionInvoke?: (action: ActionRef | string, input: unknown, parentRunId?: string) => Promise<unknown>;
|
|
87
|
+
onActionInvoke?: (action: ActionRef | string, input: unknown, parentRunId?: string, options?: ActionInvokeOptions) => Promise<unknown>;
|
|
85
88
|
}
|
|
86
89
|
/**
|
|
87
90
|
* 构建并装配传递给 Action 的完整 ActionContext 运行时上下文。
|
package/dist/runtime/context.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { assertConcreteConfigKey, assertValidConfigDeclarations, resolveConfigDeclaration, validateConfigValue } from "../project/config-declarations.js";
|
|
1
2
|
import { MemoryProcessDriver, ProcessManager } from "../process/index.js";
|
|
2
3
|
import { createDefaultProcessOwner } from "../invocation/types.js";
|
|
3
4
|
import { resolveEnvValue } from "./env.js";
|
|
4
|
-
import { ActionDockError, INVALID_ACTION_REF, INVALID_STATE } from "../errors.js";
|
|
5
|
+
import { ActionDockError, INVALID_ACTION_REF, INVALID_ARGUMENT, INVALID_STATE } from "../errors.js";
|
|
5
6
|
/**
|
|
6
7
|
* 生产级配置解析器实现。
|
|
7
8
|
* 严格践行 5 层配置解析优先级链:
|
|
@@ -20,32 +21,41 @@ export class RuntimeConfig {
|
|
|
20
21
|
constructor(storage, overrides = {}, projectConfig, globalStorage) {
|
|
21
22
|
this.storage = storage;
|
|
22
23
|
this.overrides = new Map(Object.entries(overrides));
|
|
24
|
+
assertValidConfigDeclarations(projectConfig?.config);
|
|
23
25
|
this.projectConfig = projectConfig;
|
|
24
26
|
this.globalStorage = globalStorage;
|
|
25
27
|
}
|
|
26
28
|
get(key, defaultValue) {
|
|
27
|
-
// 委托 describe 五层解析链唯一事实源:整链未命中(source 为
|
|
29
|
+
// 委托 describe 五层解析链唯一事实源:整链未命中(source 为 null 且无值)时回退调用方默认值
|
|
28
30
|
const resolved = this.describe(key);
|
|
29
|
-
if (resolved.source ===
|
|
31
|
+
if (resolved.source === null && resolved.value === undefined) {
|
|
30
32
|
return defaultValue;
|
|
31
33
|
}
|
|
34
|
+
const resolvedDecl = resolveConfigDeclaration(this.projectConfig?.config, key);
|
|
35
|
+
validateConfigValue(key, resolved.value, resolvedDecl?.item);
|
|
32
36
|
return resolved.value;
|
|
33
37
|
}
|
|
34
38
|
has(key) {
|
|
35
|
-
// 委托 describe
|
|
39
|
+
// 委托 describe 五层解析链唯一事实源:来源 null 且无值即视为未配置
|
|
36
40
|
const resolved = this.describe(key);
|
|
37
|
-
return !(resolved.source ===
|
|
41
|
+
return !(resolved.source === null && resolved.value === undefined);
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* 结构化解析指定配置项的来源与取值视图。
|
|
41
45
|
*
|
|
42
46
|
* 作为五层优先级链的唯一事实源读取视图,供 app 层 getConfig 安全视图直接委托,
|
|
43
47
|
* 避免重复实现解析链导致行为漂移。
|
|
48
|
+
* source 仅表达实际生效值来源;undefined 统一视为未命中并继续向低优先级解析,
|
|
49
|
+
* null/false/0/空串均为真实值;整链未命中时 source 为 null。
|
|
44
50
|
*/
|
|
45
51
|
describe(key) {
|
|
46
|
-
|
|
52
|
+
assertConcreteConfigKey(key);
|
|
53
|
+
// 1. 运行时固定覆盖与单次调用覆盖(值为 undefined 视为未命中,继续解析链)
|
|
47
54
|
if (this.overrides.has(key)) {
|
|
48
|
-
|
|
55
|
+
const overrideValue = this.overrides.get(key);
|
|
56
|
+
if (overrideValue !== undefined) {
|
|
57
|
+
return { source: "override", value: overrideValue };
|
|
58
|
+
}
|
|
49
59
|
}
|
|
50
60
|
// 2. 包级持久化存储
|
|
51
61
|
const stored = this.storage.getConfig(key);
|
|
@@ -65,13 +75,17 @@ export class RuntimeConfig {
|
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
// 4. 环境变量
|
|
68
|
-
const
|
|
78
|
+
const resolvedDecl = resolveConfigDeclaration(this.projectConfig?.config, key);
|
|
79
|
+
const itemDef = resolvedDecl?.item;
|
|
69
80
|
const envResolved = resolveEnvValue(key, itemDef, this.projectConfig?.id);
|
|
70
81
|
if (envResolved !== undefined) {
|
|
71
82
|
return { source: "env", value: envResolved.value };
|
|
72
83
|
}
|
|
73
|
-
// 5.
|
|
74
|
-
|
|
84
|
+
// 5. 项目声明的默认值;仅存在真实生效的 default 值才报 default,整链未命中为 null
|
|
85
|
+
if (itemDef?.default !== undefined) {
|
|
86
|
+
return { source: "default", value: itemDef.default };
|
|
87
|
+
}
|
|
88
|
+
return { source: null, value: undefined };
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
/**
|
|
@@ -156,6 +170,38 @@ export class StderrLogger {
|
|
|
156
170
|
process.stderr.write(this.format("error", message, data) + "\n");
|
|
157
171
|
}
|
|
158
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* 校验并规范化 ActionInvokeOptions 调用选项。
|
|
175
|
+
*
|
|
176
|
+
* 针对第三参数运行时检查:
|
|
177
|
+
* - 必须为普通对象或 undefined(2 参数兼容)
|
|
178
|
+
* - 严禁未知字段(如 profile、server、token 等)
|
|
179
|
+
* - binding 若存在必须为非空且非全空白字符串
|
|
180
|
+
*/
|
|
181
|
+
function validateActionInvokeOptions(options) {
|
|
182
|
+
if (options === undefined) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
if (typeof options !== "object" ||
|
|
186
|
+
options === null ||
|
|
187
|
+
Array.isArray(options) ||
|
|
188
|
+
(Object.getPrototypeOf(options) !== Object.prototype && Object.getPrototypeOf(options) !== null)) {
|
|
189
|
+
throw new ActionDockError(INVALID_ARGUMENT, "INVALID_ARGUMENT: ActionInvokeOptions must be a plain object");
|
|
190
|
+
}
|
|
191
|
+
const allowedKeys = new Set(["binding"]);
|
|
192
|
+
for (const key of Reflect.ownKeys(options)) {
|
|
193
|
+
if (typeof key !== "string" || !allowedKeys.has(key)) {
|
|
194
|
+
throw new ActionDockError(INVALID_ARGUMENT, `INVALID_ARGUMENT: Unknown option '${String(key)}' in ActionInvokeOptions. Only 'binding' is allowed.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const rawBinding = options.binding;
|
|
198
|
+
if (rawBinding !== undefined) {
|
|
199
|
+
if (typeof rawBinding !== "string" || rawBinding.trim() === "") {
|
|
200
|
+
throw new ActionDockError(INVALID_ARGUMENT, "INVALID_ARGUMENT: 'binding' option must be a non-empty string");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return options;
|
|
204
|
+
}
|
|
159
205
|
/**
|
|
160
206
|
* 构建并装配传递给 Action 的完整 ActionContext 运行时上下文。
|
|
161
207
|
*
|
|
@@ -169,17 +215,18 @@ export function createActionContext(options) {
|
|
|
169
215
|
const signal = options.signal ?? new AbortController().signal;
|
|
170
216
|
const currentRunId = options.runId || crypto.randomUUID();
|
|
171
217
|
const currentRootRunId = options.rootRunId || options.parentRunId || currentRunId;
|
|
172
|
-
const invoke = async (action, input) => {
|
|
218
|
+
const invoke = async (action, input, rawOptions) => {
|
|
173
219
|
if (typeof action !== "string" &&
|
|
174
220
|
(!action || typeof action !== "object" || typeof action.run === "function" || !("actionId" in action))) {
|
|
175
221
|
throw new ActionDockError(INVALID_ACTION_REF, "INVALID_ACTION_REF: ctx.actions.invoke strictly accepts only ActionRef or string, passing ActionDefinition or function is prohibited");
|
|
176
222
|
}
|
|
223
|
+
const validatedOptions = validateActionInvokeOptions(rawOptions);
|
|
177
224
|
if (options.onActionInvoke) {
|
|
178
|
-
return (await options.onActionInvoke(action, input, currentRunId));
|
|
225
|
+
return (await options.onActionInvoke(action, input, currentRunId, validatedOptions));
|
|
179
226
|
}
|
|
180
227
|
throw new ActionDockError(INVALID_STATE, "ActionInvoker not configured with an invocation delegate");
|
|
181
228
|
};
|
|
182
|
-
const invokerFn = (ref, input) => invoke(ref, input);
|
|
229
|
+
const invokerFn = (ref, input, invokeOpts) => invoke(ref, input, invokeOpts);
|
|
183
230
|
const invoker = Object.assign(invokerFn, { invoke });
|
|
184
231
|
const defaultOwner = createDefaultProcessOwner();
|
|
185
232
|
const effectiveOwner = options.owner || defaultOwner;
|
package/dist/runtime/env.js
CHANGED
|
@@ -142,7 +142,21 @@ export function resolveEnvValue(key, itemDef, packageId) {
|
|
|
142
142
|
// 4. 原始键名 (例如 apiKey)
|
|
143
143
|
candidateKeys.add(key);
|
|
144
144
|
// 按优先级顺序依次检索环境变量
|
|
145
|
-
|
|
145
|
+
let effectiveType = itemDef?.type;
|
|
146
|
+
if (!effectiveType && itemDef?.schema?.type) {
|
|
147
|
+
if (itemDef.schema.type === "integer" || itemDef.schema.type === "number") {
|
|
148
|
+
effectiveType = "number";
|
|
149
|
+
}
|
|
150
|
+
else if (itemDef.schema.type === "string" ||
|
|
151
|
+
itemDef.schema.type === "boolean" ||
|
|
152
|
+
itemDef.schema.type === "object" ||
|
|
153
|
+
itemDef.schema.type === "array") {
|
|
154
|
+
effectiveType = itemDef.schema.type;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!effectiveType) {
|
|
158
|
+
effectiveType = inferTypeFromDefault(itemDef?.default);
|
|
159
|
+
}
|
|
146
160
|
for (const envKey of candidateKeys) {
|
|
147
161
|
const rawVal = process.env[envKey];
|
|
148
162
|
if (rawVal !== undefined) {
|
|
@@ -14,5 +14,5 @@ export interface CommandContext {
|
|
|
14
14
|
export declare function handleList(ctx: CommandContext, subArgs: string[]): Promise<number>;
|
|
15
15
|
export declare function handleDescribe(ctx: CommandContext, subArgs: string[]): Promise<number>;
|
|
16
16
|
export declare function handleRun(ctx: CommandContext, subArgs: string[], actionArgs: string[], signal?: AbortSignal): Promise<number>;
|
|
17
|
-
export declare function handleConfig(ctx: CommandContext, subArgs: string[]): Promise<number>;
|
|
18
|
-
export declare function handleState(ctx: CommandContext, subArgs: string[]): Promise<number>;
|
|
17
|
+
export declare function handleConfig(ctx: CommandContext, subArgs: string[], flatArgs?: string[], signal?: AbortSignal, stdin?: NodeJS.ReadableStream): Promise<number>;
|
|
18
|
+
export declare function handleState(ctx: CommandContext, subArgs: string[], flatArgs?: string[], signal?: AbortSignal, stdin?: NodeJS.ReadableStream): Promise<number>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { decodeStateKey } from "@actiondock/sdk";
|
|
2
2
|
import { filterWithFallbackInfo } from "../utils/intent.js";
|
|
3
|
-
import { resolveExecutionHint } from "../errors.js";
|
|
4
|
-
import {
|
|
3
|
+
import { ActionDockError, INVALID_ARGUMENT, resolveExecutionHint } from "../errors.js";
|
|
4
|
+
import { resolveInput, resolveValueInput, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, } from "../input/index.js";
|
|
5
5
|
import { validateActionInputValue } from "../value-validator.js";
|
|
6
|
+
import { assertConcreteConfigKey } from "../project/config-declarations.js";
|
|
6
7
|
import { parseDuration } from "../utils/index.js";
|
|
7
8
|
import { ExitCode } from "./standalone.js";
|
|
8
9
|
export async function handleList(ctx, subArgs) {
|
|
@@ -148,7 +149,7 @@ export async function handleRun(ctx, subArgs, actionArgs, signal) {
|
|
|
148
149
|
}
|
|
149
150
|
let input;
|
|
150
151
|
try {
|
|
151
|
-
input = await
|
|
152
|
+
input = await resolveInput({
|
|
152
153
|
input: inputStr,
|
|
153
154
|
inputFile,
|
|
154
155
|
stdinField,
|
|
@@ -273,7 +274,57 @@ function isServiceCapabilityError(err) {
|
|
|
273
274
|
return (code === "CAPABILITY_UNAVAILABLE" ||
|
|
274
275
|
code === "TARGET_CAPABILITY_UNAVAILABLE");
|
|
275
276
|
}
|
|
276
|
-
|
|
277
|
+
function parseSetInput(subArgs, isState) {
|
|
278
|
+
const positional = [];
|
|
279
|
+
const input = {};
|
|
280
|
+
let namespace;
|
|
281
|
+
let ttl;
|
|
282
|
+
for (let i = 1; i < subArgs.length; i++) {
|
|
283
|
+
const arg = subArgs[i];
|
|
284
|
+
if (arg === "--json")
|
|
285
|
+
continue;
|
|
286
|
+
if (arg === "--stdin") {
|
|
287
|
+
if (input.fromStdin)
|
|
288
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Duplicate --stdin option");
|
|
289
|
+
input.fromStdin = true;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const equals = arg.indexOf("=");
|
|
293
|
+
const option = equals < 0 ? arg : arg.slice(0, equals);
|
|
294
|
+
if (option === "--stdin-field" || (isState && ["-n", "--namespace", "--ttl"].includes(option))) {
|
|
295
|
+
const value = equals < 0 ? subArgs[++i] : arg.slice(equals + 1);
|
|
296
|
+
if (value === undefined) {
|
|
297
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Option '${option}' requires a value`);
|
|
298
|
+
}
|
|
299
|
+
if (option === "--stdin-field") {
|
|
300
|
+
if (input.stdinField !== undefined) {
|
|
301
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Duplicate --stdin-field option");
|
|
302
|
+
}
|
|
303
|
+
input.stdinField = value;
|
|
304
|
+
}
|
|
305
|
+
else if (option === "--ttl") {
|
|
306
|
+
ttl = parseInt(value, 10);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
namespace = value;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else if (arg.startsWith("-") && !/^-\d/.test(arg)) {
|
|
313
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Unknown set option");
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
positional.push(arg);
|
|
317
|
+
if (positional.length > 2) {
|
|
318
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Only one complete positional value is allowed");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (!positional[0])
|
|
323
|
+
throw new ActionDockError(INVALID_ARGUMENT, "Key is required");
|
|
324
|
+
input.value = positional[1];
|
|
325
|
+
return { key: positional[0], input, namespace, ttl };
|
|
326
|
+
}
|
|
327
|
+
export async function handleConfig(ctx, subArgs, flatArgs, signal, stdin) {
|
|
277
328
|
const sub = subArgs[0] || "list";
|
|
278
329
|
if (!ctx.service.management) {
|
|
279
330
|
ctx.writeErr("Error: management port is not enabled on this service");
|
|
@@ -281,10 +332,15 @@ export async function handleConfig(ctx, subArgs) {
|
|
|
281
332
|
}
|
|
282
333
|
if (sub === "list") {
|
|
283
334
|
const views = await ctx.service.management.config.list(ctx.options.packageId);
|
|
284
|
-
const dict =
|
|
335
|
+
const dict = Object.create(null);
|
|
285
336
|
for (const v of views) {
|
|
286
|
-
if (v.configured
|
|
287
|
-
|
|
337
|
+
if (v.configured) {
|
|
338
|
+
if (v.secret && v.value === undefined) {
|
|
339
|
+
dict[v.key] = "********";
|
|
340
|
+
}
|
|
341
|
+
else if (v.value !== undefined) {
|
|
342
|
+
dict[v.key] = v.value;
|
|
343
|
+
}
|
|
288
344
|
}
|
|
289
345
|
}
|
|
290
346
|
ctx.writeOut(JSON.stringify(dict, null, 2));
|
|
@@ -298,7 +354,15 @@ export async function handleConfig(ctx, subArgs) {
|
|
|
298
354
|
}
|
|
299
355
|
try {
|
|
300
356
|
const item = await ctx.service.management.config.get(ctx.options.packageId, key);
|
|
301
|
-
|
|
357
|
+
if (item.redactedPaths.includes("") || (item.configured && item.value === undefined)) {
|
|
358
|
+
ctx.writeOut("<hidden>");
|
|
359
|
+
}
|
|
360
|
+
else if (item.value === undefined) {
|
|
361
|
+
ctx.writeOut("<not configured>");
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
ctx.writeOut(JSON.stringify(item.value));
|
|
365
|
+
}
|
|
302
366
|
return ExitCode.SUCCESS;
|
|
303
367
|
}
|
|
304
368
|
catch (err) {
|
|
@@ -311,21 +375,27 @@ export async function handleConfig(ctx, subArgs) {
|
|
|
311
375
|
}
|
|
312
376
|
}
|
|
313
377
|
if (sub === "set") {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (!key || rawVal === undefined) {
|
|
317
|
-
ctx.writeErr("Error: key and value required");
|
|
318
|
-
return ExitCode.INVALID_ARGUMENT;
|
|
319
|
-
}
|
|
320
|
-
let parsed = rawVal;
|
|
378
|
+
let parsed;
|
|
379
|
+
let value;
|
|
321
380
|
try {
|
|
322
|
-
parsed =
|
|
381
|
+
parsed = parseSetInput(subArgs, false);
|
|
382
|
+
assertConcreteConfigKey(parsed.key);
|
|
383
|
+
value = await resolveValueInput({
|
|
384
|
+
...parsed.input,
|
|
385
|
+
flatArgs,
|
|
386
|
+
signal,
|
|
387
|
+
stdin,
|
|
388
|
+
policy: { sanitizeInputErrors: true },
|
|
389
|
+
});
|
|
390
|
+
signal?.throwIfAborted();
|
|
323
391
|
}
|
|
324
|
-
catch {
|
|
325
|
-
|
|
392
|
+
catch (err) {
|
|
393
|
+
return ctx.emitError(subArgs.includes("--json"), err?.code || "INVALID_ARGUMENT", err?.message || String(err), ExitCode.INVALID_ARGUMENT, {
|
|
394
|
+
details: err?.details,
|
|
395
|
+
});
|
|
326
396
|
}
|
|
327
|
-
await ctx.service.management.config.set(ctx.options.packageId, key,
|
|
328
|
-
ctx.writeOut(`Config '${key}' updated`);
|
|
397
|
+
await ctx.service.management.config.set(ctx.options.packageId, parsed.key, value);
|
|
398
|
+
ctx.writeOut(`Config '${parsed.key}' updated`);
|
|
329
399
|
return ExitCode.SUCCESS;
|
|
330
400
|
}
|
|
331
401
|
if (sub === "delete") {
|
|
@@ -349,7 +419,7 @@ function decodeStateKeyArg(key) {
|
|
|
349
419
|
return { namespace: "", key };
|
|
350
420
|
}
|
|
351
421
|
}
|
|
352
|
-
export async function handleState(ctx, subArgs) {
|
|
422
|
+
export async function handleState(ctx, subArgs, flatArgs, signal, stdin) {
|
|
353
423
|
const sub = subArgs[0] || "list";
|
|
354
424
|
let namespace;
|
|
355
425
|
let isAll = false;
|
|
@@ -405,36 +475,32 @@ export async function handleState(ctx, subArgs) {
|
|
|
405
475
|
return ExitCode.SUCCESS;
|
|
406
476
|
}
|
|
407
477
|
if (sub === "set") {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (!key || rawVal === undefined) {
|
|
411
|
-
ctx.writeErr("Error: key and value required");
|
|
412
|
-
return ExitCode.INVALID_ARGUMENT;
|
|
413
|
-
}
|
|
414
|
-
let ns = namespace || "";
|
|
415
|
-
let actualKey = key;
|
|
416
|
-
if (namespace === undefined) {
|
|
417
|
-
const decoded = decodeStateKeyArg(key);
|
|
418
|
-
ns = decoded.namespace;
|
|
419
|
-
actualKey = decoded.key;
|
|
420
|
-
}
|
|
421
|
-
let parsed = rawVal;
|
|
478
|
+
let parsed;
|
|
479
|
+
let value;
|
|
422
480
|
try {
|
|
423
|
-
parsed =
|
|
481
|
+
parsed = parseSetInput(subArgs, true);
|
|
482
|
+
value = await resolveValueInput({
|
|
483
|
+
...parsed.input,
|
|
484
|
+
flatArgs,
|
|
485
|
+
signal,
|
|
486
|
+
stdin,
|
|
487
|
+
policy: { sanitizeInputErrors: true },
|
|
488
|
+
});
|
|
489
|
+
signal?.throwIfAborted();
|
|
424
490
|
}
|
|
425
|
-
catch {
|
|
426
|
-
|
|
491
|
+
catch (err) {
|
|
492
|
+
return ctx.emitError(isJson, err?.code || "INVALID_ARGUMENT", err?.message || String(err), ExitCode.INVALID_ARGUMENT, {
|
|
493
|
+
details: err?.details,
|
|
494
|
+
});
|
|
427
495
|
}
|
|
428
|
-
let
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
ttl = parseInt(subArgs[i].slice(6), 10);
|
|
435
|
-
}
|
|
496
|
+
let ns = parsed.namespace || "";
|
|
497
|
+
let actualKey = parsed.key;
|
|
498
|
+
if (parsed.namespace === undefined) {
|
|
499
|
+
const decoded = decodeStateKeyArg(parsed.key);
|
|
500
|
+
ns = decoded.namespace;
|
|
501
|
+
actualKey = decoded.key;
|
|
436
502
|
}
|
|
437
|
-
await ctx.service.management.state.set(ctx.options.packageId, "", actualKey,
|
|
503
|
+
await ctx.service.management.state.set(ctx.options.packageId, "", actualKey, value, { namespace: ns, ttl: parsed.ttl });
|
|
438
504
|
const displayKey = ns ? `${ns}:${actualKey}` : actualKey;
|
|
439
505
|
ctx.writeOut(`State '${displayKey}' updated`);
|
|
440
506
|
return ExitCode.SUCCESS;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { parseConfigOverrideAssignment } from "../input/config-override.js";
|
|
2
|
+
export { parseConfigOverrideAssignment } from "../input/config-override.js";
|
|
1
3
|
export function parseStandaloneArgs(argv) {
|
|
2
4
|
const separator = argv.indexOf("--");
|
|
3
5
|
const controlArgs = separator >= 0 ? argv.slice(0, separator) : argv;
|
|
@@ -13,17 +15,15 @@ export function parseStandaloneArgs(argv) {
|
|
|
13
15
|
else if (arg.startsWith("--data-dir=")) {
|
|
14
16
|
dataDir = arg.slice(11);
|
|
15
17
|
}
|
|
16
|
-
else if (arg === "--config"
|
|
17
|
-
const pair = controlArgs[++i];
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
configOverrides[k] = v.join("=");
|
|
18
|
+
else if (arg === "--config" || arg === "-c") {
|
|
19
|
+
const pair = controlArgs[++i] ?? "";
|
|
20
|
+
const { key, value } = parseConfigOverrideAssignment(pair);
|
|
21
|
+
Object.defineProperty(configOverrides, key, { value, enumerable: true, writable: true, configurable: true });
|
|
21
22
|
}
|
|
22
23
|
else if (arg.startsWith("--config=")) {
|
|
23
24
|
const pair = arg.slice(9);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
configOverrides[k] = v.join("=");
|
|
25
|
+
const { key, value } = parseConfigOverrideAssignment(pair);
|
|
26
|
+
Object.defineProperty(configOverrides, key, { value, enumerable: true, writable: true, configurable: true });
|
|
27
27
|
}
|
|
28
28
|
else {
|
|
29
29
|
filteredArgs.push(arg);
|
|
@@ -45,10 +45,16 @@ export function printHelp(writeOut, options) {
|
|
|
45
45
|
writeOut(" <cmd> run <id> [--stdin-field <field>] [-- <assignments...>]");
|
|
46
46
|
writeOut(" Bind raw stdin text to an input field");
|
|
47
47
|
writeOut(" <cmd> config list/get/set/delete Manage package configuration");
|
|
48
|
+
writeOut(" <cmd> config set <key> [<value> | --stdin]");
|
|
49
|
+
writeOut(" <cmd> config set <key> [--stdin-field <field>] [-- <assignments...>]");
|
|
48
50
|
writeOut(" <cmd> state list/get/set/delete Manage shared state store");
|
|
51
|
+
writeOut(" <cmd> state set <key> [<value> | --stdin] [-n <namespace>] [--ttl <seconds>]");
|
|
52
|
+
writeOut(" <cmd> state set <key> [--stdin-field <field>] [-n <namespace>] [--ttl <seconds>] [-- <assignments...>]");
|
|
53
|
+
writeOut(" set 整键替换;完整值、--stdin 与结构化输入互斥");
|
|
54
|
+
writeOut(" 扁平赋值:field=text 或 field:=json;--stdin-field 保留原始正文");
|
|
49
55
|
writeOut("\nGlobal options:");
|
|
50
56
|
writeOut(" --data-dir <path> Custom runtime database directory");
|
|
51
|
-
writeOut(" --config <KEY=val>
|
|
57
|
+
writeOut(" --config <KEY=val|KEY:=json> Temporary config override");
|
|
52
58
|
}
|
|
53
59
|
export function emitError(writeOut, writeErr, isJson, code, message, exitCode, options) {
|
|
54
60
|
if (isJson) {
|
|
@@ -149,10 +149,10 @@ export class StandaloneDispatcher {
|
|
|
149
149
|
code = await handleRun(ctx, subArgs, actionArgs, control?.signal);
|
|
150
150
|
break;
|
|
151
151
|
case "config":
|
|
152
|
-
code = await handleConfig(ctx, subArgs);
|
|
152
|
+
code = await handleConfig(ctx, subArgs, argv.includes("--") ? actionArgs : undefined, control?.signal);
|
|
153
153
|
break;
|
|
154
154
|
case "state":
|
|
155
|
-
code = await handleState(ctx, subArgs);
|
|
155
|
+
code = await handleState(ctx, subArgs, argv.includes("--") ? actionArgs : undefined, control?.signal);
|
|
156
156
|
break;
|
|
157
157
|
default:
|
|
158
158
|
this.writeErr(`Unknown command: '${command}'`);
|