@actiondock/cli 2.7.0 → 2.9.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/README.md +6 -7
- package/bin/ad.js +52 -23
- package/dist/commands/action/create.js +4 -5
- package/dist/commands/add.js +9 -17
- package/dist/commands/build.js +3 -18
- package/dist/commands/config/delete.js +8 -7
- package/dist/commands/config/env.js +7 -11
- package/dist/commands/config/get.js +8 -5
- package/dist/commands/config/list.js +16 -22
- package/dist/commands/config/schema.d.ts +7 -1
- package/dist/commands/config/schema.js +44 -67
- package/dist/commands/config/set.js +9 -7
- package/dist/commands/describe.js +10 -9
- package/dist/commands/doctor.js +4 -3
- package/dist/commands/export.js +5 -13
- package/dist/commands/generate.js +4 -3
- package/dist/commands/info.js +3 -1
- package/dist/commands/init.js +3 -3
- package/dist/commands/link.js +4 -4
- package/dist/commands/list.js +19 -10
- package/dist/commands/mcp.js +27 -66
- package/dist/commands/pack.js +3 -12
- package/dist/commands/playbook.js +30 -10
- package/dist/commands/profile.js +10 -19
- package/dist/commands/remove.js +5 -14
- package/dist/commands/run.d.ts +5 -2
- package/dist/commands/run.js +19 -20
- package/dist/commands/runs.js +26 -35
- package/dist/commands/serve.js +177 -57
- package/dist/commands/state-list.d.ts +2 -2
- package/dist/commands/state-list.js +10 -8
- package/dist/commands/state.d.ts +2 -2
- package/dist/commands/state.js +110 -103
- package/dist/commands/test.js +1 -1
- package/dist/commands/validate.js +4 -17
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +46 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +1 -1
- package/dist/services/config-merge.d.ts +6 -5
- package/dist/services/config-merge.js +10 -8
- package/dist/services/inspect.js +3 -1
- package/dist/standalone/index.d.ts +4 -14
- package/dist/standalone/index.js +5 -44
- package/dist/types.d.ts +2 -1
- package/dist/types.js +1 -1
- package/dist/utils/cert.js +1 -1
- package/dist/utils/duration.d.ts +1 -1
- package/dist/utils/duration.js +1 -1
- package/dist/utils/index.d.ts +1 -2
- package/dist/utils/index.js +1 -2
- package/dist/utils/input.d.ts +9 -1
- package/dist/utils/input.js +12 -1
- package/dist/utils/server-launch.d.ts +114 -0
- package/dist/utils/server-launch.js +275 -0
- package/dist/utils/target.d.ts +14 -13
- package/dist/utils/target.js +20 -39
- package/dist/utils/utils.d.ts +101 -1
- package/dist/utils/utils.js +97 -7
- package/package.json +5 -6
- package/dist/utils/bytes.d.ts +0 -1
- package/dist/utils/bytes.js +0 -1
- package/dist/utils/filter.d.ts +0 -1
- package/dist/utils/filter.js +0 -1
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InvocationControl } from "@actiondock/core";
|
|
1
|
+
import { type InvocationControl } from "@actiondock/core/server";
|
|
2
2
|
import type { ExecutionResult } from "@actiondock/sdk";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import type { CliContext } from "../types.js";
|
|
@@ -10,7 +10,10 @@ import type { CliContext } from "../types.js";
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function renderRawExecutionResult(targetRef: string, result: ExecutionResult, context?: CliContext): void;
|
|
12
12
|
/**
|
|
13
|
-
* 统一执行 Action 核心逻辑(使用
|
|
13
|
+
* 统一执行 Action 核心逻辑(使用 ActionDockService 服务端口)。
|
|
14
|
+
*
|
|
15
|
+
* @param id 目标 Action 标识
|
|
16
|
+
* @param options 执行选项(由 Command 统一构造)
|
|
14
17
|
*/
|
|
15
18
|
export declare function executeAction(id: string, options: any, context?: CliContext, flatArgs?: string[], control?: InvocationControl): Promise<void>;
|
|
16
19
|
/**
|
package/dist/commands/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validateActionInputValue, mapInputValidationFailure, } from "@actiondock/core/project";
|
|
2
|
+
import { resolveTarget, } from "@actiondock/core/profile";
|
|
2
3
|
import { ArgumentError, ExecutionError, SigintError, packageNotFoundError } from "../errors.js";
|
|
3
4
|
import { writeStderr, writeStdout } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot,
|
|
5
|
+
import { applyTargetOptions, getEffectiveOptions, resolveActionInput, resolveLocalPackageRoot, resolveTimeoutMs, withService, } from "../utils/index.js";
|
|
5
6
|
/**
|
|
6
7
|
* 以原始纯文本形式渲染 Action 执行终态结果(默认纯文本模式)。
|
|
7
8
|
*
|
|
@@ -95,7 +96,10 @@ export function renderRawExecutionResult(targetRef, result, context) {
|
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
/**
|
|
98
|
-
* 统一执行 Action 核心逻辑(使用
|
|
99
|
+
* 统一执行 Action 核心逻辑(使用 ActionDockService 服务端口)。
|
|
100
|
+
*
|
|
101
|
+
* @param id 目标 Action 标识
|
|
102
|
+
* @param options 执行选项(由 Command 统一构造)
|
|
99
103
|
*/
|
|
100
104
|
export async function executeAction(id, options, context, flatArgs, control) {
|
|
101
105
|
if (!id) {
|
|
@@ -114,15 +118,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
|
|
|
114
118
|
if (!check.valid) {
|
|
115
119
|
throw mapInputValidationFailure("cli-pre-target", check);
|
|
116
120
|
}
|
|
117
|
-
|
|
118
|
-
if (options.timeout) {
|
|
119
|
-
try {
|
|
120
|
-
timeoutMs = parseDuration(options.timeout);
|
|
121
|
-
}
|
|
122
|
-
catch (err) {
|
|
123
|
-
throw new ArgumentError(`Invalid timeout format: ${err.message}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
121
|
+
const timeoutMs = resolveTimeoutMs(options.timeout);
|
|
126
122
|
const configOverrides = {};
|
|
127
123
|
if (options.config) {
|
|
128
124
|
const list = Array.isArray(options.config) ? options.config : [options.config];
|
|
@@ -149,14 +145,14 @@ export async function executeAction(id, options, context, flatArgs, control) {
|
|
|
149
145
|
throw packageNotFoundError(options.package);
|
|
150
146
|
}
|
|
151
147
|
let targetRef = id;
|
|
152
|
-
if (options.package && !id.includes("/")
|
|
148
|
+
if (options.package && !id.includes("/")) {
|
|
153
149
|
targetRef = `${options.package}/${id}`;
|
|
154
150
|
}
|
|
155
|
-
// 通过
|
|
156
|
-
await
|
|
151
|
+
// 通过 Service 统一执行
|
|
152
|
+
await withService(options, context, async (service) => {
|
|
157
153
|
const isMachine = Boolean(options.json);
|
|
158
154
|
if (options.async) {
|
|
159
|
-
const ticket = await
|
|
155
|
+
const ticket = await service.execution.start(targetRef, input, {
|
|
160
156
|
signal: effectiveSignal,
|
|
161
157
|
timeoutMs,
|
|
162
158
|
config: configOverrides,
|
|
@@ -182,7 +178,7 @@ export async function executeAction(id, options, context, flatArgs, control) {
|
|
|
182
178
|
}
|
|
183
179
|
}
|
|
184
180
|
else {
|
|
185
|
-
const result = await
|
|
181
|
+
const result = await service.execution.run(targetRef, input, {
|
|
186
182
|
signal: effectiveSignal,
|
|
187
183
|
timeoutMs,
|
|
188
184
|
config: configOverrides,
|
|
@@ -208,10 +204,13 @@ export async function executeAction(id, options, context, flatArgs, control) {
|
|
|
208
204
|
}, { localRoot: targetPackageRoot || undefined, scanLinkedPackages: true, ownDataDir: true });
|
|
209
205
|
}
|
|
210
206
|
catch (err) {
|
|
207
|
+
// sigint 取消源、中断链路特征错误码或 SigintError 本身,均统一归并为 SigintError
|
|
208
|
+
// 原 if 中额外的「cancellationSource 为 sigint 且 AbortError/aborted/ACTION_CANCELLED」
|
|
209
|
+
// 子句被首项 cancellationSource === "sigint" 完全蕴含,等价化简后移除
|
|
211
210
|
const isSigint = effectiveControl?.cancellationSource === "sigint" ||
|
|
212
|
-
err?.
|
|
213
|
-
|
|
214
|
-
|
|
211
|
+
err?.code === "SIGINT_INTERRUPTED" ||
|
|
212
|
+
err instanceof SigintError;
|
|
213
|
+
if (isSigint) {
|
|
215
214
|
throw new SigintError();
|
|
216
215
|
}
|
|
217
216
|
throw err;
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { findProjectRoot, loadProjectConfig, } from "@actiondock/core";
|
|
2
|
+
import { filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
3
|
+
import { listLinkedPackages, resolvePackageRoot, } from "@actiondock/core/registry";
|
|
4
|
+
import { ArgumentError, ExecutionError, NO_PROJECT_NO_LINKED_MESSAGE, packageNotFoundError, } from "../errors.js";
|
|
3
5
|
import { renderResult, renderRunDetail, renderRunsList } from "../renderer.js";
|
|
4
|
-
import { applyTargetOptions, getEffectiveOptions,
|
|
6
|
+
import { applyTargetOptions, getEffectiveOptions, remoteTargetSuffix, requirePackageRoot, resolveFallbackStrategy, resolveIntent, withService, } from "../utils/index.js";
|
|
5
7
|
/**
|
|
6
8
|
* 解析本地目标包根目录与工程配置(仅在 local 分支调用)。
|
|
7
9
|
* 显式指定包且寻址失败时抛出参数错误;工程清单损坏时降级为链接包视图。
|
|
@@ -50,21 +52,26 @@ export function registerRunsCommands(program, context) {
|
|
|
50
52
|
.option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
|
|
51
53
|
.option("-a, --action <actionId>", "Filter by action ID")
|
|
52
54
|
.option("-n, --limit <count>", "Maximum number of records to return", "20"))
|
|
55
|
+
.option("--fallback", "Enable fallback to full list when no items match intent")
|
|
53
56
|
.option("--no-fallback", "Disable fallback to full list when no items match intent")
|
|
54
57
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
55
58
|
.option("--json", "Output as JSON")
|
|
56
59
|
.action(async (patterns = [], rawOptions, cmd) => {
|
|
57
60
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
58
61
|
const effectiveIntent = resolveIntent(options.intent, patterns);
|
|
59
|
-
const shouldFallback = options
|
|
62
|
+
const { shouldFallback } = resolveFallbackStrategy(options);
|
|
60
63
|
const limit = Number.parseInt(options.limit, 10) || 20;
|
|
61
64
|
const scope = resolveLocalRunScope(options.package);
|
|
62
|
-
// 通过
|
|
63
|
-
await
|
|
64
|
-
|
|
65
|
+
// 通过 Service 门面统一访问
|
|
66
|
+
await withService(options, context, async (service, resolved) => {
|
|
67
|
+
// limit 下推查询层:intent 过滤发生在内存(过滤后仍需截断到用户值),
|
|
68
|
+
// 下推时附加合理裕量,保证过滤后可截断数量不少于用户请求
|
|
69
|
+
const FETCH_HEADROOM = 200;
|
|
70
|
+
const queryLimit = effectiveIntent ? limit + FETCH_HEADROOM : limit;
|
|
71
|
+
const records = await service.runs.list({
|
|
65
72
|
packageId: options.package,
|
|
66
73
|
actionId: options.action,
|
|
67
|
-
limit:
|
|
74
|
+
limit: queryLimit,
|
|
68
75
|
});
|
|
69
76
|
// 若处于本地无项目环境且无任何软链接包,则直接输出友好提示
|
|
70
77
|
if (resolved.type === "local" && !scope.targetPackageRoot) {
|
|
@@ -85,7 +92,7 @@ export function registerRunsCommands(program, context) {
|
|
|
85
92
|
humanFormatter: () => {
|
|
86
93
|
let title = "Execution Runs";
|
|
87
94
|
if (resolved.type === "remote") {
|
|
88
|
-
title = `Execution Runs
|
|
95
|
+
title = `Execution Runs ${remoteTargetSuffix(resolved)}`;
|
|
89
96
|
}
|
|
90
97
|
else if (scope.projConfig) {
|
|
91
98
|
title = `Execution Runs in ${scope.projConfig.name} (${scope.projConfig.id})`;
|
|
@@ -112,9 +119,9 @@ export function registerRunsCommands(program, context) {
|
|
|
112
119
|
throw new ArgumentError("Run ID is required");
|
|
113
120
|
}
|
|
114
121
|
const scope = resolveLocalRunScope(options.package);
|
|
115
|
-
// 通过
|
|
116
|
-
await
|
|
117
|
-
const run = await
|
|
122
|
+
// 通过 Service 门面统一查询
|
|
123
|
+
await withService(options, context, async (service, resolved) => {
|
|
124
|
+
const run = await service.runs.get(id);
|
|
118
125
|
if (!run) {
|
|
119
126
|
if (resolved.type === "remote") {
|
|
120
127
|
throw new ExecutionError(`Run record '${id}' not found on remote server`);
|
|
@@ -144,12 +151,8 @@ export function registerRunsCommands(program, context) {
|
|
|
144
151
|
if (!id) {
|
|
145
152
|
throw new ArgumentError("Run ID is required for cancel");
|
|
146
153
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new ArgumentError("'ad runs cancel' is only supported for remote execution targets. Use --profile <name> or --server <url>.");
|
|
150
|
-
}
|
|
151
|
-
await withRemoteTarget(options, context, async (target) => {
|
|
152
|
-
const result = await target.cancelRun(id, options.reason);
|
|
154
|
+
await withService(options, context, async (service) => {
|
|
155
|
+
const result = await service.runs.cancel(id, options.reason);
|
|
153
156
|
const isErrorOutcome = result.outcome === "not_found" || result.outcome === "not_owner";
|
|
154
157
|
renderResult(result, {
|
|
155
158
|
json: options.json,
|
|
@@ -167,7 +170,7 @@ export function registerRunsCommands(program, context) {
|
|
|
167
170
|
if (isErrorOutcome) {
|
|
168
171
|
process.exitCode = 1;
|
|
169
172
|
}
|
|
170
|
-
});
|
|
173
|
+
}, { requireRemote: true });
|
|
171
174
|
});
|
|
172
175
|
// runs clear
|
|
173
176
|
applyTargetOptions(runsCmd
|
|
@@ -182,19 +185,7 @@ export function registerRunsCommands(program, context) {
|
|
|
182
185
|
let targetPackageRoot;
|
|
183
186
|
let packageId = options.package;
|
|
184
187
|
if (!options.profile && !options.server) {
|
|
185
|
-
|
|
186
|
-
const root = resolvePackageRoot(options.package);
|
|
187
|
-
if (!root) {
|
|
188
|
-
throw packageNotFoundError(options.package);
|
|
189
|
-
}
|
|
190
|
-
targetPackageRoot = root;
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
targetPackageRoot = findProjectRoot() || undefined;
|
|
194
|
-
if (!targetPackageRoot) {
|
|
195
|
-
throw notInProjectError("Please specify -P, --package <id> or cd into a project directory.");
|
|
196
|
-
}
|
|
197
|
-
}
|
|
188
|
+
targetPackageRoot = requirePackageRoot(options.package).root;
|
|
198
189
|
try {
|
|
199
190
|
packageId = loadProjectConfig(targetPackageRoot).id;
|
|
200
191
|
}
|
|
@@ -202,9 +193,9 @@ export function registerRunsCommands(program, context) {
|
|
|
202
193
|
// 清单损坏时保留原始 -P 参数作为包标识
|
|
203
194
|
}
|
|
204
195
|
}
|
|
205
|
-
await
|
|
206
|
-
const count =
|
|
207
|
-
? await
|
|
196
|
+
await withService(options, context, async (service, resolved) => {
|
|
197
|
+
const count = service.runs.clear
|
|
198
|
+
? await service.runs.clear({ packageId, actionId: options.action })
|
|
208
199
|
: 0;
|
|
209
200
|
const payload = { ok: true, clearedCount: count };
|
|
210
201
|
renderResult(payload, {
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { createActionDock, createNodePlatform, findProjectRoot, loadProjectConfig, startActionDockServer, } from "@actiondock/core";
|
|
4
|
+
import { resolvePackageRoot } from "@actiondock/core/registry";
|
|
5
|
+
import { parseActionRef } from "@actiondock/core/graph";
|
|
6
|
+
import { formatHostForUrl, } from "@actiondock/core/server";
|
|
7
|
+
import { ArgumentError, ExecutionError, packageNotFoundError } from "../errors.js";
|
|
6
8
|
import { writeStderr, writeStdout } from "../renderer.js";
|
|
7
|
-
import { ensureSelfSignedCertificate, getEffectiveOptions,
|
|
9
|
+
import { ensureSelfSignedCertificate, getEffectiveOptions, loadViewsFromFile, mergeServerViews, normalizeCorsOrigins, registerStopSignalHandler, printServerBanner, resolveMaxBodyBytes, resolveServerHost, resolveServerPort, resolveServerToken, parseListOption, } from "../utils/index.js";
|
|
8
10
|
/**
|
|
9
11
|
* 注册 serve HTTP 服务启动命令。
|
|
10
12
|
*
|
|
@@ -31,17 +33,20 @@ export function registerServeCommand(program, context) {
|
|
|
31
33
|
.option("--tls-ca <path>", "Path to TLS CA certificate file (or set ACTIONDOCK_TLS_CA)")
|
|
32
34
|
.option("--tls-passphrase <passphrase>", "Passphrase for TLS private key (or set ACTIONDOCK_TLS_PASSPHRASE)")
|
|
33
35
|
.option("-d, --dir <path>", "Project root directory (default: current working directory)")
|
|
36
|
+
.option("-P, --package <package-id>", "Specific package ID(s) to serve (can be specified multiple times or comma-separated)", parseListOption, [])
|
|
37
|
+
.option("-A, --action <action-ref>", "Specific action ref(s) to serve (can be specified multiple times or comma-separated)", parseListOption, [])
|
|
38
|
+
.option("--views-file <path>", "Path to JSON file configuring virtual projection views")
|
|
39
|
+
.option("--views <json>", "JSON string configuring virtual projection views")
|
|
34
40
|
.option("--data-dir <path>", "Custom database storage directory")
|
|
35
41
|
.action(async (rawOptions, cmd) => {
|
|
36
42
|
const options = getEffectiveOptions(rawOptions, cmd);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const token = options.token || (typeof process !== "undefined" ? process.env?.ACTIONDOCK_TOKEN : undefined);
|
|
43
|
+
const port = resolveServerPort(options.port, 5177);
|
|
44
|
+
const host = resolveServerHost(options.host);
|
|
45
|
+
const token = resolveServerToken(options.token);
|
|
41
46
|
const allowInsecureNoAuth = Boolean(options.allowInsecureNoAuth);
|
|
42
47
|
const allowQueryToken = Boolean(options.allowQueryToken);
|
|
43
48
|
const enableManagement = Boolean(options.management);
|
|
44
|
-
const corsOrigins = options.corsOrigin
|
|
49
|
+
const corsOrigins = normalizeCorsOrigins(options.corsOrigin);
|
|
45
50
|
const exposeDebugInfo = Boolean(options.exposeDebugInfo);
|
|
46
51
|
const httpsEnabled = Boolean(options.https ||
|
|
47
52
|
options.tlsCert ||
|
|
@@ -82,42 +87,153 @@ export function registerServeCommand(program, context) {
|
|
|
82
87
|
};
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
|
-
|
|
86
|
-
if (options.maxBody) {
|
|
87
|
-
try {
|
|
88
|
-
maxBodyBytes = parseByteSize(options.maxBody);
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
throw new ArgumentError(`Invalid max-body format: ${err.message}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
90
|
+
const maxBodyBytes = resolveMaxBodyBytes(options.maxBody);
|
|
94
91
|
const projectRoot = options.dir
|
|
95
92
|
? resolve(options.dir)
|
|
96
93
|
: findProjectRoot(process.cwd());
|
|
94
|
+
let projectConfig;
|
|
97
95
|
let projectName = "Global Registry Mode";
|
|
98
96
|
if (projectRoot) {
|
|
99
97
|
try {
|
|
100
|
-
|
|
101
|
-
projectName = `${
|
|
98
|
+
projectConfig = loadProjectConfig(projectRoot);
|
|
99
|
+
projectName = `${projectConfig.name} (${projectConfig.id})`;
|
|
102
100
|
}
|
|
103
101
|
catch {
|
|
104
102
|
// 降级处理
|
|
105
103
|
}
|
|
106
104
|
}
|
|
105
|
+
const fileViews = options.viewsFile
|
|
106
|
+
? loadViewsFromFile(options.viewsFile)
|
|
107
|
+
: undefined;
|
|
108
|
+
let cliViews;
|
|
109
|
+
if (options.views) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = typeof options.views === "string" ? JSON.parse(options.views) : options.views;
|
|
112
|
+
cliViews =
|
|
113
|
+
parsed && typeof parsed === "object" && "views" in parsed && (typeof parsed.views === "object" || Array.isArray(parsed.views))
|
|
114
|
+
? parsed.views
|
|
115
|
+
: parsed;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
119
|
+
throw new ArgumentError(`Failed to parse --views option: ${msg}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const configViews = projectConfig?.server?.views;
|
|
123
|
+
const viewsConfig = mergeServerViews(mergeServerViews(configViews, fileViews), cliViews);
|
|
124
|
+
const rawPackages = options.package || [];
|
|
125
|
+
const packageAllowlist = rawPackages.length > 0 ? [] : undefined;
|
|
126
|
+
const resolvedPackageIds = [];
|
|
127
|
+
const explicitPackages = [];
|
|
128
|
+
const rawActions = options.action || [];
|
|
129
|
+
const actionAllowlist = rawActions.length > 0 ? [...rawActions] : undefined;
|
|
130
|
+
if (rawPackages.length > 0) {
|
|
131
|
+
for (const pkgId of rawPackages) {
|
|
132
|
+
const root = resolvePackageRoot(pkgId, projectRoot || undefined, context?.customHome);
|
|
133
|
+
if (!root) {
|
|
134
|
+
throw packageNotFoundError(pkgId);
|
|
135
|
+
}
|
|
136
|
+
if (!projectRoot || root !== projectRoot) {
|
|
137
|
+
if (!explicitPackages.some((p) => p.packageRoot === root)) {
|
|
138
|
+
explicitPackages.push({ packageRoot: root });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
let resolvedId = pkgId;
|
|
142
|
+
try {
|
|
143
|
+
const config = loadProjectConfig(root);
|
|
144
|
+
if (config.id) {
|
|
145
|
+
resolvedId = config.id;
|
|
146
|
+
if (!packageAllowlist.includes(config.id)) {
|
|
147
|
+
packageAllowlist.push(config.id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// 降级使用原始标识符
|
|
153
|
+
}
|
|
154
|
+
if (!packageAllowlist.includes(pkgId)) {
|
|
155
|
+
packageAllowlist.push(pkgId);
|
|
156
|
+
}
|
|
157
|
+
if (!resolvedPackageIds.includes(resolvedId)) {
|
|
158
|
+
resolvedPackageIds.push(resolvedId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (rawActions.length > 0) {
|
|
163
|
+
for (const actRef of rawActions) {
|
|
164
|
+
try {
|
|
165
|
+
const parsed = parseActionRef(actRef);
|
|
166
|
+
if (parsed.packageId) {
|
|
167
|
+
const root = resolvePackageRoot(parsed.packageId, projectRoot || undefined, context?.customHome);
|
|
168
|
+
if (root) {
|
|
169
|
+
if (!projectRoot || root !== projectRoot) {
|
|
170
|
+
if (!explicitPackages.some((p) => p.packageRoot === root)) {
|
|
171
|
+
explicitPackages.push({ packageRoot: root });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
writeStderr(`Warning: Package '${parsed.packageId}' specified in action '${actRef}' was not found.`, context);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// 忽略非标准或短引用形态
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (viewsConfig) {
|
|
186
|
+
const viewItems = Array.isArray(viewsConfig) ? viewsConfig : Object.values(viewsConfig);
|
|
187
|
+
for (const item of viewItems) {
|
|
188
|
+
if (!item || typeof item !== "object")
|
|
189
|
+
continue;
|
|
190
|
+
if (Array.isArray(item.packageAllowlist)) {
|
|
191
|
+
for (const pkgId of item.packageAllowlist) {
|
|
192
|
+
if (typeof pkgId === "string") {
|
|
193
|
+
const root = resolvePackageRoot(pkgId, projectRoot || undefined, context?.customHome);
|
|
194
|
+
if (root && (!projectRoot || root !== projectRoot)) {
|
|
195
|
+
if (!explicitPackages.some((p) => p.packageRoot === root)) {
|
|
196
|
+
explicitPackages.push({ packageRoot: root });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(item.actionAllowlist)) {
|
|
203
|
+
for (const actRef of item.actionAllowlist) {
|
|
204
|
+
if (typeof actRef === "string") {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = parseActionRef(actRef);
|
|
207
|
+
if (parsed.packageId) {
|
|
208
|
+
const root = resolvePackageRoot(parsed.packageId, projectRoot || undefined, context?.customHome);
|
|
209
|
+
if (root && (!projectRoot || root !== projectRoot)) {
|
|
210
|
+
if (!explicitPackages.some((p) => p.packageRoot === root)) {
|
|
211
|
+
explicitPackages.push({ packageRoot: root });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
107
222
|
const platform = createNodePlatform({
|
|
108
223
|
customHome: context?.customHome,
|
|
109
224
|
dataDir: options.dataDir || context?.dataDir,
|
|
110
225
|
rootDir: projectRoot || undefined,
|
|
111
226
|
});
|
|
112
|
-
const
|
|
227
|
+
const service = await createActionDock({
|
|
113
228
|
type: "local",
|
|
114
229
|
projectRoot: projectRoot || undefined,
|
|
230
|
+
packages: explicitPackages.length > 0 ? explicitPackages : undefined,
|
|
115
231
|
customHome: context?.customHome,
|
|
116
232
|
dataDir: options.dataDir || context?.dataDir,
|
|
117
233
|
platform,
|
|
118
|
-
scanLinkedPackages: !projectRoot,
|
|
234
|
+
scanLinkedPackages: !projectRoot || rawPackages.length > 0 || rawActions.length > 0 || Boolean(viewsConfig),
|
|
119
235
|
});
|
|
120
|
-
let
|
|
236
|
+
let mcpHandlerFactory;
|
|
121
237
|
const enableMcp = options.mcp !== false;
|
|
122
238
|
if (enableMcp) {
|
|
123
239
|
// MCP 处理器创建失败时直接终止启动,避免横幅宣称不存在的端点
|
|
@@ -126,21 +242,26 @@ export function registerServeCommand(program, context) {
|
|
|
126
242
|
import("@actiondock/mcp"),
|
|
127
243
|
import("@modelcontextprotocol/server"),
|
|
128
244
|
]);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
245
|
+
mcpHandlerFactory = (view) => {
|
|
246
|
+
const handler = createMcpHandler(() => {
|
|
247
|
+
return createActionDockMcpServer({
|
|
248
|
+
service,
|
|
249
|
+
packageAllowlist: view.packageAllowlist,
|
|
250
|
+
packageIds: view.packageAllowlist,
|
|
251
|
+
actionAllowlist: view.actionAllowlist,
|
|
252
|
+
});
|
|
253
|
+
}, {
|
|
254
|
+
onerror: (err) => {
|
|
255
|
+
writeStderr(`[MCP HTTP Error] ${err?.message || String(err)}`, context);
|
|
256
|
+
},
|
|
132
257
|
});
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
mcpHandler = async (req) => {
|
|
139
|
-
return handler.fetch(req);
|
|
258
|
+
return async (req) => {
|
|
259
|
+
return handler.fetch(req);
|
|
260
|
+
};
|
|
140
261
|
};
|
|
141
262
|
}
|
|
142
263
|
catch (err) {
|
|
143
|
-
await
|
|
264
|
+
await service.close().catch(() => { });
|
|
144
265
|
throw new ExecutionError(`Failed to initialize MCP endpoint: ${err?.message || String(err)}`, err);
|
|
145
266
|
}
|
|
146
267
|
}
|
|
@@ -156,19 +277,31 @@ export function registerServeCommand(program, context) {
|
|
|
156
277
|
exposeDebugInfo,
|
|
157
278
|
enableMcp,
|
|
158
279
|
enableManagement,
|
|
159
|
-
|
|
280
|
+
mcpHandlerFactory,
|
|
160
281
|
tls,
|
|
161
282
|
projectRoot: projectRoot || undefined,
|
|
162
|
-
|
|
283
|
+
service,
|
|
284
|
+
packageAllowlist,
|
|
285
|
+
actionAllowlist,
|
|
286
|
+
views: viewsConfig,
|
|
163
287
|
});
|
|
164
288
|
const displayHost = formatHostForUrl(host);
|
|
165
289
|
const actualEndpointHost = formatHostForUrl(host === "0.0.0.0" ? "127.0.0.1" : host);
|
|
166
290
|
const scheme = tls ? "https" : "http";
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
291
|
+
printServerBanner(`ActionDock 2.0 HTTP Runner Server`, scheme, displayHost, server.port, context, {
|
|
292
|
+
views: viewsConfig,
|
|
293
|
+
endpointHost: actualEndpointHost,
|
|
294
|
+
enableMcp,
|
|
295
|
+
});
|
|
296
|
+
if (resolvedPackageIds.length > 0) {
|
|
297
|
+
writeStdout(` * Packages: ${resolvedPackageIds.join(", ")}`, context);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
writeStdout(` * Project: ${projectName}`, context);
|
|
301
|
+
}
|
|
302
|
+
if (actionAllowlist && actionAllowlist.length > 0) {
|
|
303
|
+
writeStdout(` * Actions: ${actionAllowlist.join(", ")}`, context);
|
|
304
|
+
}
|
|
172
305
|
if (projectRoot && exposeDebugInfo) {
|
|
173
306
|
writeStdout(` * Root Path: ${projectRoot}`, context);
|
|
174
307
|
}
|
|
@@ -183,30 +316,17 @@ export function registerServeCommand(program, context) {
|
|
|
183
316
|
writeStdout(` * Management APIs: ${enableManagement ? "Enabled" : "Disabled (Default)"}`, context);
|
|
184
317
|
writeStdout(` * CORS Origins: ${corsOrigins ? corsOrigins.join(", ") : "Disabled (Default)"}`, context);
|
|
185
318
|
writeStdout(` * Max Body Size: ${options.maxBody || "1mb"}`, context);
|
|
186
|
-
writeStdout(` * Health Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/api/
|
|
319
|
+
writeStdout(` * Health Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/api/v2/health`, context);
|
|
187
320
|
if (enableMcp) {
|
|
188
321
|
writeStdout(` * MCP Endpoint: ${scheme}://${actualEndpointHost}:${server.port}/mcp`, context);
|
|
189
322
|
}
|
|
190
323
|
writeStdout(`======================================================\n`, context);
|
|
191
324
|
writeStdout(`Server is ready to accept remote requests.`, context);
|
|
192
325
|
writeStdout(`Press Ctrl+C to terminate.\n`, context);
|
|
193
|
-
|
|
194
|
-
writeStdout("\nStopping ActionDock server...", context);
|
|
195
|
-
Promise.resolve(server.stop())
|
|
196
|
-
.catch((err) => {
|
|
197
|
-
// 停止失败必须可见:写入 stderr 一行并标记失败退出码
|
|
198
|
-
writeStderr(`[ERROR] Failed to stop ActionDock server gracefully: ${err instanceof Error ? err.message : String(err)}`, context);
|
|
199
|
-
process.exitCode = 1;
|
|
200
|
-
})
|
|
201
|
-
.finally(() => {
|
|
202
|
-
process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
|
|
203
|
-
});
|
|
204
|
-
};
|
|
205
|
-
process.once("SIGINT", stopSignalHandler);
|
|
206
|
-
process.once("SIGTERM", stopSignalHandler);
|
|
326
|
+
registerStopSignalHandler(server, `ActionDock server`, context);
|
|
207
327
|
}
|
|
208
328
|
catch (err) {
|
|
209
|
-
await
|
|
329
|
+
await service.close().catch(() => { });
|
|
210
330
|
throw new ExecutionError(`Failed to start ActionDock server: ${err.message}`, err);
|
|
211
331
|
}
|
|
212
332
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ActionDockService } from "@actiondock/core";
|
|
2
2
|
import type { CliContext } from "../types.js";
|
|
3
3
|
/**
|
|
4
4
|
* 状态键列表渲染参数视图。
|
|
5
5
|
*/
|
|
6
6
|
export interface StateListRenderArgs {
|
|
7
|
-
|
|
7
|
+
service: ActionDockService;
|
|
8
8
|
actionId: string;
|
|
9
9
|
prefix: string;
|
|
10
10
|
options: any;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
2
|
+
import { filterWithFallbackInfo, } from "@actiondock/core/project";
|
|
3
|
+
import { listLinkedPackages, } from "@actiondock/core/registry";
|
|
2
4
|
import { existsSync } from "node:fs";
|
|
3
5
|
import { NO_PROJECT_NO_LINKED_MESSAGE } from "../errors.js";
|
|
4
6
|
import { renderResult, renderStateList } from "../renderer.js";
|
|
@@ -6,16 +8,16 @@ import { renderResult, renderStateList } from "../renderer.js";
|
|
|
6
8
|
* 渲染单个项目作用域的状态键列表(含 --detail JSON 明细分支)。
|
|
7
9
|
*/
|
|
8
10
|
export async function renderProjectScopedStateList(args) {
|
|
9
|
-
const {
|
|
11
|
+
const { service, targetRoot, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
|
|
10
12
|
const projConfig = loadProjectConfig(targetRoot);
|
|
11
|
-
const allKeys = await
|
|
13
|
+
const allKeys = (await service.management?.state.list(projConfig.id, actionId, {
|
|
12
14
|
namespace: options.namespace !== undefined ? options.namespace : null,
|
|
13
15
|
prefix,
|
|
14
|
-
});
|
|
16
|
+
})) ?? [];
|
|
15
17
|
if (options.detail && options.json) {
|
|
16
18
|
const entries = [];
|
|
17
19
|
for (const k of allKeys) {
|
|
18
|
-
const entry = await
|
|
20
|
+
const entry = await service.management?.state.get(projConfig.id, actionId, k, {
|
|
19
21
|
namespace: options.namespace,
|
|
20
22
|
detail: true,
|
|
21
23
|
});
|
|
@@ -41,7 +43,7 @@ export async function renderProjectScopedStateList(args) {
|
|
|
41
43
|
* 渲染全部链接包聚合的状态键列表(跳过磁盘失效与清单损坏的链接项)。
|
|
42
44
|
*/
|
|
43
45
|
export async function renderLinkedPackagesStateList(args) {
|
|
44
|
-
const {
|
|
46
|
+
const { service, actionId, prefix, options, effectiveIntent, shouldFallback, context } = args;
|
|
45
47
|
// 扫描所有链接的包状态
|
|
46
48
|
const linked = listLinkedPackages();
|
|
47
49
|
if (linked.length === 0) {
|
|
@@ -58,10 +60,10 @@ export async function renderLinkedPackagesStateList(args) {
|
|
|
58
60
|
continue;
|
|
59
61
|
try {
|
|
60
62
|
const config = loadProjectConfig(pkg.path);
|
|
61
|
-
const keys = await
|
|
63
|
+
const keys = (await service.management?.state.list(config.id, actionId, {
|
|
62
64
|
namespace: options.namespace !== undefined ? options.namespace : null,
|
|
63
65
|
prefix,
|
|
64
|
-
});
|
|
66
|
+
})) ?? [];
|
|
65
67
|
for (const k of keys) {
|
|
66
68
|
aggregatedKeys.push(`${config.id}/${k}`);
|
|
67
69
|
}
|
package/dist/commands/state.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CliContext } from "../types.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* 注册 state 状态管理命令(get、set、delete、clear、keys、list)。
|
|
5
5
|
*
|
|
6
|
-
* @param program Commander
|
|
7
|
-
* @param context
|
|
6
|
+
* @param program Commander 根程序对象
|
|
7
|
+
* @param context CLI 上下文
|
|
8
8
|
*/
|
|
9
9
|
export declare function registerStateCommands(program: Command, context?: CliContext): void;
|