@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
|
@@ -3,7 +3,66 @@ import { isTerminalRunStatus } from "../../storage/types.js";
|
|
|
3
3
|
import { createAbortError } from "../../ipc/service.js";
|
|
4
4
|
import { parseDuration } from "../../utils/index.js";
|
|
5
5
|
import { readJsonBody } from "../body.js";
|
|
6
|
-
import {
|
|
6
|
+
import { parseActionRef } from "../../catalog/resolve-action.js";
|
|
7
|
+
import { getSubPath, isActionAllowedByPolicy, isFullAccessGranted, isPackageAllowedByPolicy, isPackageVisibleByPolicy, jsonResponse } from "./common.js";
|
|
8
|
+
/** 清理范围必须落在整包或精确动作授权内,全局清理必须显式拥有全部授权。 */
|
|
9
|
+
function resolveRunCleanupScope(packageId, actionId, policy) {
|
|
10
|
+
if (isFullAccessGranted(policy)) {
|
|
11
|
+
if (packageId && !isPackageVisibleByPolicy(packageId, policy)) {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
reason: `Package '${packageId}' is not in the allowed package list`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return { ok: true, filter: { packageId, actionId } };
|
|
18
|
+
}
|
|
19
|
+
// 单动作清理必须解析出完整身份,避免鉴权与实际清理范围不一致。
|
|
20
|
+
if (actionId) {
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = parseActionRef(actionId);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { ok: false, reason: `Invalid action reference '${actionId}'` };
|
|
27
|
+
}
|
|
28
|
+
// 全限定引用与显式 packageId 归一化后不一致必须拒绝,防止鉴权与过滤范围错位
|
|
29
|
+
if (parsed.packageId && packageId && parsed.packageId !== packageId) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
reason: `Action reference '${parsed.packageId}/${parsed.actionId}' does not match the requested package scope '${packageId}'`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const targetPkgId = parsed.packageId || packageId;
|
|
36
|
+
if (!targetPkgId) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
reason: "Restricted access policy requires a fully qualified 'packageId/actionId' action reference for run cleanup",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!isActionAllowedByPolicy({ packageId: targetPkgId, actionId: parsed.actionId }, policy)) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
reason: `Action '${targetPkgId}/${parsed.actionId}' is not in the allowed action list`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// 以归一化完整引用传递,确保底层过滤范围与鉴权引用一致且不扩大
|
|
49
|
+
return { ok: true, filter: { packageId: targetPkgId, actionId: parsed.actionId } };
|
|
50
|
+
}
|
|
51
|
+
// 无 actionId:必须有明确包范围,且该范围必须是整包授权
|
|
52
|
+
if (!packageId) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: "Restricted access policy requires an explicit 'packageId' or fully qualified 'actionId' scope for run cleanup",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!isPackageAllowedByPolicy(packageId, policy)) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: `Package '${packageId}' is not fully granted; run cleanup requires a fully granted package or an explicitly granted 'packageId/actionId' reference`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, filter: { packageId } };
|
|
65
|
+
}
|
|
7
66
|
/**
|
|
8
67
|
* 处理历史运行记录查询、清理、详情及 SSE 流式日志接口。
|
|
9
68
|
*/
|
|
@@ -16,6 +75,13 @@ export async function handleRunsRoutes(ctx) {
|
|
|
16
75
|
const status = url.searchParams.get("status") || undefined;
|
|
17
76
|
const actionId = url.searchParams.get("actionId") || undefined;
|
|
18
77
|
const packageId = url.searchParams.get("packageId") || undefined;
|
|
78
|
+
const channel = url.searchParams.get("channel") || undefined;
|
|
79
|
+
const view = url.searchParams.get("view") || undefined;
|
|
80
|
+
const since = url.searchParams.get("since") || undefined;
|
|
81
|
+
const until = url.searchParams.get("until") || undefined;
|
|
82
|
+
const rootRunId = url.searchParams.get("rootRunId") || undefined;
|
|
83
|
+
const parentRunId = url.searchParams.get("parentRunId") || undefined;
|
|
84
|
+
const withTotal = url.searchParams.get("withTotal") === "true";
|
|
19
85
|
const intent = url.searchParams.get("intent") || undefined;
|
|
20
86
|
// requestId 反查:支持 requestId 与 requestIds 重复查询参数,将每一个参数值作为原样字符串保存
|
|
21
87
|
const requestIds = [
|
|
@@ -47,7 +113,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
47
113
|
limit = Math.min(Math.max(parsedLimit, 1), 500);
|
|
48
114
|
}
|
|
49
115
|
}
|
|
50
|
-
if (packageId && !
|
|
116
|
+
if (packageId && !isPackageVisibleByPolicy(packageId, policy)) {
|
|
51
117
|
return jsonResponse({
|
|
52
118
|
ok: false,
|
|
53
119
|
error: {
|
|
@@ -56,7 +122,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
56
122
|
},
|
|
57
123
|
}, 403, corsHeaders);
|
|
58
124
|
}
|
|
59
|
-
if (actionId && !isActionAllowedByPolicy({ packageId, actionId }, policy)) {
|
|
125
|
+
if (actionId && (packageId || actionId.includes("/")) && !isActionAllowedByPolicy({ packageId, actionId }, policy)) {
|
|
60
126
|
return jsonResponse({
|
|
61
127
|
ok: false,
|
|
62
128
|
error: {
|
|
@@ -65,19 +131,32 @@ export async function handleRunsRoutes(ctx) {
|
|
|
65
131
|
},
|
|
66
132
|
}, 403, corsHeaders);
|
|
67
133
|
}
|
|
134
|
+
// 下推的是包可见性并集,仍须逐条校验动作,不能丢弃单动作授权包的记录。
|
|
135
|
+
const visiblePackageIds = new Set([
|
|
136
|
+
...(policy.packageAllowlist || []).filter((id) => id !== "*"),
|
|
137
|
+
...(policy.actionAllowlist || []).map((ref) => parseActionRef(ref).packageId),
|
|
138
|
+
]);
|
|
139
|
+
const backendPackageFilter = isFullAccessGranted(policy) ? undefined : [...visiblePackageIds];
|
|
140
|
+
if (backendPackageFilter?.length === 0) {
|
|
141
|
+
return jsonResponse({ ok: true, items: [], total: 0, limit, offset }, 200, corsHeaders);
|
|
142
|
+
}
|
|
68
143
|
const queryOptions = {
|
|
69
144
|
actionId,
|
|
70
145
|
status,
|
|
71
146
|
packageId,
|
|
147
|
+
channel,
|
|
148
|
+
view,
|
|
149
|
+
since,
|
|
150
|
+
until,
|
|
151
|
+
rootRunId,
|
|
152
|
+
parentRunId,
|
|
153
|
+
withTotal,
|
|
72
154
|
intent,
|
|
73
155
|
requestIds: requestIds.length > 0 ? requestIds : undefined,
|
|
74
156
|
signal: req.signal,
|
|
75
|
-
packageAllowlist:
|
|
76
|
-
? policy.packageAllowlist
|
|
77
|
-
: undefined,
|
|
157
|
+
packageAllowlist: backendPackageFilter,
|
|
78
158
|
};
|
|
79
|
-
const hasPolicyFilter =
|
|
80
|
-
(policy.actionAllowlist && policy.actionAllowlist.length > 0));
|
|
159
|
+
const hasPolicyFilter = !isFullAccessGranted(policy);
|
|
81
160
|
let runs;
|
|
82
161
|
let total;
|
|
83
162
|
if (hasPolicyFilter) {
|
|
@@ -102,7 +181,9 @@ export async function handleRunsRoutes(ctx) {
|
|
|
102
181
|
break;
|
|
103
182
|
}
|
|
104
183
|
for (const run of batch) {
|
|
105
|
-
|
|
184
|
+
// 逐条以实际 packageId/actionId 完整引用判定授权:
|
|
185
|
+
// 包可见性拦无关包,动作授权保留单动作授权目标(如 B/read)
|
|
186
|
+
const packageAllowed = isPackageVisibleByPolicy(run.packageId, policy);
|
|
106
187
|
const actionAllowed = isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy);
|
|
107
188
|
if (packageAllowed && actionAllowed) {
|
|
108
189
|
if (matchedCount >= offset && pagedRuns.length < limit) {
|
|
@@ -153,6 +234,8 @@ export async function handleRunsRoutes(ctx) {
|
|
|
153
234
|
const responseData = {
|
|
154
235
|
ok: true,
|
|
155
236
|
items: runs,
|
|
237
|
+
limit,
|
|
238
|
+
offset,
|
|
156
239
|
};
|
|
157
240
|
if (typeof total === "number") {
|
|
158
241
|
responseData.total = total;
|
|
@@ -202,20 +285,22 @@ export async function handleRunsRoutes(ctx) {
|
|
|
202
285
|
keep = parsed;
|
|
203
286
|
}
|
|
204
287
|
}
|
|
205
|
-
|
|
288
|
+
const scopeCheck = resolveRunCleanupScope(packageId, actionId, policy);
|
|
289
|
+
if (!scopeCheck.ok) {
|
|
206
290
|
return jsonResponse({
|
|
207
291
|
ok: false,
|
|
208
292
|
error: {
|
|
209
293
|
code: PACKAGE_FORBIDDEN,
|
|
210
|
-
message:
|
|
294
|
+
message: scopeCheck.reason,
|
|
211
295
|
},
|
|
212
296
|
}, 403, corsHeaders);
|
|
213
297
|
}
|
|
298
|
+
const clearedScope = scopeCheck.filter;
|
|
214
299
|
let clearedCount = 0;
|
|
215
300
|
if (service.runs.clear) {
|
|
216
301
|
clearedCount = await service.runs.clear({
|
|
217
|
-
packageId,
|
|
218
|
-
actionId,
|
|
302
|
+
packageId: clearedScope.packageId,
|
|
303
|
+
actionId: clearedScope.actionId,
|
|
219
304
|
status,
|
|
220
305
|
olderThanMs,
|
|
221
306
|
keep,
|
|
@@ -228,7 +313,17 @@ export async function handleRunsRoutes(ctx) {
|
|
|
228
313
|
}
|
|
229
314
|
}
|
|
230
315
|
// 2.1 Runs Clean Expired: POST /api/v2/runs/clean-expired, POST /runs/clean-expired
|
|
316
|
+
// 此操作无法按包或动作收窄范围,只允许显式的全部授权。
|
|
231
317
|
if (subpath === "/runs/clean-expired" && req.method === "POST") {
|
|
318
|
+
if (!isFullAccessGranted(policy)) {
|
|
319
|
+
return jsonResponse({
|
|
320
|
+
ok: false,
|
|
321
|
+
error: {
|
|
322
|
+
code: PACKAGE_FORBIDDEN,
|
|
323
|
+
message: "Expired run cleanup cannot be scoped under a restricted access policy",
|
|
324
|
+
},
|
|
325
|
+
}, 403, corsHeaders);
|
|
326
|
+
}
|
|
232
327
|
try {
|
|
233
328
|
let body = {};
|
|
234
329
|
if (req.headers.get("content-type")?.includes("json")) {
|
|
@@ -252,7 +347,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
252
347
|
if (!run) {
|
|
253
348
|
return jsonResponse({ ok: false, error: { code: RUN_NOT_FOUND, message: `Run '${runId}' not found` } }, 404, corsHeaders);
|
|
254
349
|
}
|
|
255
|
-
if (!
|
|
350
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
256
351
|
return jsonResponse({
|
|
257
352
|
ok: false,
|
|
258
353
|
error: {
|
|
@@ -427,7 +522,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
427
522
|
},
|
|
428
523
|
}, 404, corsHeaders);
|
|
429
524
|
}
|
|
430
|
-
if (!
|
|
525
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
431
526
|
return jsonResponse({
|
|
432
527
|
ok: false,
|
|
433
528
|
error: {
|
|
@@ -451,11 +546,10 @@ export async function handleRunsRoutes(ctx) {
|
|
|
451
546
|
const runCancelMatch = subpath.match(/^\/runs\/([^/]+)\/cancel$/);
|
|
452
547
|
if (runCancelMatch && req.method === "POST") {
|
|
453
548
|
const runId = decodeURIComponent(runCancelMatch[1]);
|
|
454
|
-
if ((policy
|
|
455
|
-
(policy.actionAllowlist && policy.actionAllowlist.length > 0)) {
|
|
549
|
+
if (!isFullAccessGranted(policy)) {
|
|
456
550
|
const run = await service.runs.get(runId);
|
|
457
551
|
if (run) {
|
|
458
|
-
if (!
|
|
552
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
459
553
|
return jsonResponse({
|
|
460
554
|
ok: false,
|
|
461
555
|
error: {
|
package/dist/server/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActionDockService } from "../service/types.js";
|
|
1
2
|
import type { ActionDockServerInstance, CoreHttpServerInstance, ServerOptions, ServerTlsOptions } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* 规范化主机地址用于拼接 URL。
|
|
@@ -12,4 +13,4 @@ export declare function launchHttpServer(port: number, host: string, fetchHandle
|
|
|
12
13
|
* 启动 ActionDock 原生轻量级 HTTP 服务端。
|
|
13
14
|
* 面向 ActionDockService 服务端口,负责中间件流转、认证拦截与路由分发。
|
|
14
15
|
*/
|
|
15
|
-
export declare function startActionDockServer(
|
|
16
|
+
export declare function startActionDockServer(arg1?: ServerOptions | ActionDockService, arg2?: ServerOptions): Promise<ActionDockServerInstance>;
|
package/dist/server/server.js
CHANGED
|
@@ -3,8 +3,8 @@ import { NodeHttpServer } from "./http-server.js";
|
|
|
3
3
|
import { createActionDock } from "../service/factory.js";
|
|
4
4
|
import { ActionDockError, ACCESS_DENIED, NOT_FOUND, SERVICE_ERROR, UNAUTHORIZED } from "../errors.js";
|
|
5
5
|
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
6
|
-
import { findProjectRoot } from "../project/loader.js";
|
|
7
|
-
import {
|
|
6
|
+
import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
|
|
7
|
+
import { resolvePackageRoot } from "../registry/registry.js";
|
|
8
8
|
import { handleActionsRoutes, handleConfigRoutes, handleDoctorRoute, handleHealthRoute, handleInfoRoute, handlePlaybooksRoutes, handleRunsRoutes, handleStateRoutes, jsonResponse, } from "./routes/index.js";
|
|
9
9
|
import { isLoopbackHost, resolveCorsHeaders, verifyBearerToken } from "./security.js";
|
|
10
10
|
import { createMcpEndpointHandler } from "./mcp-endpoint.js";
|
|
@@ -40,8 +40,19 @@ export async function launchHttpServer(port, host, fetchHandler, tls) {
|
|
|
40
40
|
* 启动 ActionDock 原生轻量级 HTTP 服务端。
|
|
41
41
|
* 面向 ActionDockService 服务端口,负责中间件流转、认证拦截与路由分发。
|
|
42
42
|
*/
|
|
43
|
-
export async function startActionDockServer(
|
|
44
|
-
|
|
43
|
+
export async function startActionDockServer(arg1, arg2) {
|
|
44
|
+
let options;
|
|
45
|
+
if (arg1 && typeof arg1 === "object" && ("discovery" in arg1 || "execution" in arg1 || "runs" in arg1)) {
|
|
46
|
+
options = { ...(arg2 || {}), service: arg1 };
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
options = arg1 || {};
|
|
50
|
+
}
|
|
51
|
+
const hostString = typeof options.hostname === "string"
|
|
52
|
+
? options.hostname
|
|
53
|
+
: typeof options.host === "string"
|
|
54
|
+
? options.host
|
|
55
|
+
: "127.0.0.1";
|
|
45
56
|
const port = options.port ?? 5177;
|
|
46
57
|
const host = hostString;
|
|
47
58
|
const token = options.token;
|
|
@@ -59,70 +70,42 @@ export async function startActionDockServer(options = {}) {
|
|
|
59
70
|
throw new ActionDockError(ACCESS_DENIED, "Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
|
|
60
71
|
}
|
|
61
72
|
let serviceInstance = options.service;
|
|
62
|
-
//
|
|
73
|
+
// 包授权与单动作引用都参与自建服务的装载;外部注入服务自行负责能力装配。
|
|
63
74
|
if (!serviceInstance) {
|
|
64
|
-
const
|
|
75
|
+
const declaredId = projectRoot ? loadProjectConfig(projectRoot).id : undefined;
|
|
76
|
+
const referencedPackages = new Set();
|
|
77
|
+
for (const view of views.values()) {
|
|
78
|
+
for (const pkgId of view.policy.packageAllowlist || []) {
|
|
79
|
+
if (pkgId !== "*")
|
|
80
|
+
referencedPackages.add(pkgId);
|
|
81
|
+
}
|
|
82
|
+
for (const action of view.policy.actionAllowlist || []) {
|
|
83
|
+
referencedPackages.add(parseActionRef(action).packageId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const extraPackages = [];
|
|
87
|
+
for (const pkgId of referencedPackages) {
|
|
88
|
+
if (pkgId === declaredId)
|
|
89
|
+
continue;
|
|
90
|
+
const root = resolvePackageRoot(pkgId, projectRoot || undefined, customHome);
|
|
91
|
+
if (root && !extraPackages.some((pkg) => pkg.packageRoot === root)) {
|
|
92
|
+
extraPackages.push({ packageRoot: root });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
65
95
|
serviceInstance = await createActionDock({
|
|
66
96
|
projectRoot: projectRoot || undefined,
|
|
97
|
+
packages: extraPackages.length > 0 ? extraPackages : undefined,
|
|
67
98
|
customHome,
|
|
68
99
|
platform: options.platform,
|
|
69
100
|
dataDir: options.dataDir,
|
|
70
101
|
inMemory: options.inMemory,
|
|
71
|
-
scanLinkedPackages,
|
|
102
|
+
scanLinkedPackages: options.scanLinkedPackages ?? !projectRoot,
|
|
72
103
|
enableManagement: options.enableManagement,
|
|
73
104
|
});
|
|
74
105
|
}
|
|
75
106
|
if (!serviceInstance) {
|
|
76
107
|
throw new ActionDockError(SERVICE_ERROR, "Failed to initialize ActionDockService for server");
|
|
77
108
|
}
|
|
78
|
-
// 2. 聚合所有 views 声明的依赖包与动作,确保依赖闭包满足
|
|
79
|
-
const roots = [];
|
|
80
|
-
if (projectRoot) {
|
|
81
|
-
roots.push(projectRoot);
|
|
82
|
-
}
|
|
83
|
-
const aggregatedPackageAllowlist = new Set();
|
|
84
|
-
const aggregatedActionAllowlist = new Set();
|
|
85
|
-
for (const v of views.values()) {
|
|
86
|
-
if (v.policy.packageAllowlist) {
|
|
87
|
-
for (const pkg of v.policy.packageAllowlist) {
|
|
88
|
-
aggregatedPackageAllowlist.add(pkg);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (v.policy.actionAllowlist) {
|
|
92
|
-
for (const act of v.policy.actionAllowlist) {
|
|
93
|
-
aggregatedActionAllowlist.add(act);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (aggregatedPackageAllowlist.size > 0) {
|
|
98
|
-
for (const pkgId of aggregatedPackageAllowlist) {
|
|
99
|
-
const r = resolvePackageRoot(pkgId, projectRoot || undefined, customHome);
|
|
100
|
-
if (r && !roots.includes(r)) {
|
|
101
|
-
roots.push(r);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else if (!projectRoot) {
|
|
106
|
-
for (const pkg of listLinkedPackages(customHome)) {
|
|
107
|
-
if (!roots.includes(pkg.path)) {
|
|
108
|
-
roots.push(pkg.path);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (aggregatedActionAllowlist.size > 0) {
|
|
113
|
-
for (const actRef of aggregatedActionAllowlist) {
|
|
114
|
-
try {
|
|
115
|
-
const parsed = parseActionRef(actRef);
|
|
116
|
-
if (parsed.packageId) {
|
|
117
|
-
const r = resolvePackageRoot(parsed.packageId, projectRoot || undefined, customHome);
|
|
118
|
-
if (r && !roots.includes(r)) {
|
|
119
|
-
roots.push(r);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch { }
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
109
|
// 3. 为每个启用 MCP 的视图独立装配 MCP 处理器
|
|
127
110
|
if (options.enableMcp !== false) {
|
|
128
111
|
for (const v of views.values()) {
|
package/dist/server/types.d.ts
CHANGED
|
@@ -26,9 +26,9 @@ export interface ServerViewOptions {
|
|
|
26
26
|
name?: string;
|
|
27
27
|
/** 该视图独立的 Bearer Token 鉴权令牌 */
|
|
28
28
|
token?: string;
|
|
29
|
-
/**
|
|
29
|
+
/** 整包授权列表,与动作授权取并集;空列表不授予权限,独立 * 显式授权全部 */
|
|
30
30
|
packageAllowlist?: string[];
|
|
31
|
-
/**
|
|
31
|
+
/** 全限定 packageId/actionId 精确动作授权列表;空列表不授予权限 */
|
|
32
32
|
actionAllowlist?: string[];
|
|
33
33
|
/** 该视图是否开启配置与状态管理路由能力(默认关闭) */
|
|
34
34
|
enableManagement?: boolean;
|
|
@@ -45,9 +45,9 @@ export interface EffectiveServerPolicy {
|
|
|
45
45
|
viewName?: string;
|
|
46
46
|
/** 当前生效的 Bearer Token 鉴权令牌 */
|
|
47
47
|
token?: string;
|
|
48
|
-
/**
|
|
48
|
+
/** 当前生效的整包授权列表,独立 * 代表全部 */
|
|
49
49
|
packageAllowlist?: string[];
|
|
50
|
-
/**
|
|
50
|
+
/** 当前生效的全限定精确动作授权列表,与整包授权取并集 */
|
|
51
51
|
actionAllowlist?: string[];
|
|
52
52
|
/** 当前生效的管理能力开关 */
|
|
53
53
|
enableManagement?: boolean;
|
|
@@ -80,9 +80,9 @@ export interface ServerOptions {
|
|
|
80
80
|
allowInsecureNoAuth?: boolean;
|
|
81
81
|
/** 允许跨域请求的 CORS Origin 白名单列表 */
|
|
82
82
|
corsOrigins?: string[];
|
|
83
|
-
/**
|
|
83
|
+
/** 整包授权列表,与动作授权取并集;空列表不授予权限,独立 * 显式授权全部 */
|
|
84
84
|
packageAllowlist?: string[];
|
|
85
|
-
/**
|
|
85
|
+
/** 全限定 packageId/actionId 精确动作授权列表;空列表不授予权限 */
|
|
86
86
|
actionAllowlist?: string[];
|
|
87
87
|
/** 最大允许的请求体字节限制(默认 1MB,防 DoS) */
|
|
88
88
|
maxBodyBytes?: number;
|
package/dist/service/factory.js
CHANGED
|
@@ -10,6 +10,7 @@ export async function createActionDock(options = {}) {
|
|
|
10
10
|
const inMemory = options.inMemory ?? options.runtimeOptions.inMemory;
|
|
11
11
|
const host = await createActionDockHost({
|
|
12
12
|
packages: [options.runtimeOptions],
|
|
13
|
+
actionBindings: options.actionBindings ?? options.hostOptions?.actionBindings,
|
|
13
14
|
autoLoadCurrentProject: false,
|
|
14
15
|
scanLinkedPackages: false,
|
|
15
16
|
inMemory,
|
package/dist/service/remote.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
2
|
+
import { assertConcreteConfigKey } from "../project/config-declarations.js";
|
|
2
3
|
import { ActionDockError, ACTION_CANCELLED, CAPABILITY_UNAVAILABLE, INVALID_ARGUMENT, TIMEOUT, } from "../errors.js";
|
|
3
4
|
import { createAbortError } from "../ipc/service.js";
|
|
4
|
-
import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../client/index.js";
|
|
5
|
+
import { cancelRemoteRun, clearRemoteRuns, cleanExpiredRemoteRuns, clearRemoteState, deleteRemoteConfig, deleteRemoteStateKey, executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteConfig, fetchRemoteConfigItem, fetchRemoteInfo, fetchRemotePlaybookShow, fetchRemotePlaybooks, fetchRemoteRun, fetchRemoteRuns, fetchRemoteStateList, getRemoteStateKey, setRemoteConfig, setRemoteStateKey, } from "../client/index.js";
|
|
5
6
|
import { isRemoteStateKeyNotFound, wrapRemoteError } from "./remote-errors.js";
|
|
6
7
|
import { formatTerminalRunResult, pollRunCompletion } from "./remote-polling.js";
|
|
7
8
|
import { streamRemoteEvents } from "./sse-stream.js";
|
|
@@ -259,6 +260,13 @@ export class RemoteActionDockService {
|
|
|
259
260
|
packageId: query?.packageId,
|
|
260
261
|
actionId: query?.actionId,
|
|
261
262
|
status: query?.status,
|
|
263
|
+
channel: query?.channel,
|
|
264
|
+
view: query?.view,
|
|
265
|
+
since: query?.since,
|
|
266
|
+
until: query?.until,
|
|
267
|
+
rootRunId: query?.rootRunId,
|
|
268
|
+
parentRunId: query?.parentRunId,
|
|
269
|
+
withTotal: query?.withTotal,
|
|
262
270
|
intent: query?.intent,
|
|
263
271
|
limit: batchLimit,
|
|
264
272
|
offset,
|
|
@@ -307,6 +315,13 @@ export class RemoteActionDockService {
|
|
|
307
315
|
packageId: query?.packageId,
|
|
308
316
|
actionId: query?.actionId,
|
|
309
317
|
status: query?.status,
|
|
318
|
+
channel: query?.channel,
|
|
319
|
+
view: query?.view,
|
|
320
|
+
since: query?.since,
|
|
321
|
+
until: query?.until,
|
|
322
|
+
rootRunId: query?.rootRunId,
|
|
323
|
+
parentRunId: query?.parentRunId,
|
|
324
|
+
withTotal: true,
|
|
310
325
|
intent: query?.intent,
|
|
311
326
|
limit: 1,
|
|
312
327
|
offset: 0,
|
|
@@ -435,19 +450,15 @@ export class RemoteActionDockService {
|
|
|
435
450
|
config: {
|
|
436
451
|
async get(packageId, key) {
|
|
437
452
|
self.assertNotClosed();
|
|
453
|
+
assertConcreteConfigKey(key);
|
|
438
454
|
try {
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
configured: false,
|
|
447
|
-
secret: false,
|
|
448
|
-
source: "default",
|
|
449
|
-
value: undefined,
|
|
450
|
-
};
|
|
455
|
+
const response = await fetchRemoteConfigItem(self.serverUrl, key, self.token, packageId || undefined, {
|
|
456
|
+
allowInsecureHttp: self.allowInsecureHttp,
|
|
457
|
+
insecure: self.insecure,
|
|
458
|
+
dispatcher: self.dispatcher,
|
|
459
|
+
});
|
|
460
|
+
// 严格单项读取已保证恰一项且键匹配,直接消费,不伪造未命中视图掩盖契约错误。
|
|
461
|
+
return response.items[0];
|
|
451
462
|
}
|
|
452
463
|
catch (err) {
|
|
453
464
|
wrapRemoteError(err);
|
|
@@ -455,6 +466,7 @@ export class RemoteActionDockService {
|
|
|
455
466
|
},
|
|
456
467
|
async set(packageId, key, value) {
|
|
457
468
|
self.assertNotClosed();
|
|
469
|
+
assertConcreteConfigKey(key);
|
|
458
470
|
try {
|
|
459
471
|
await setRemoteConfig(self.serverUrl, key, value, self.token, packageId || undefined, {
|
|
460
472
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -468,6 +480,7 @@ export class RemoteActionDockService {
|
|
|
468
480
|
},
|
|
469
481
|
async delete(packageId, key) {
|
|
470
482
|
self.assertNotClosed();
|
|
483
|
+
assertConcreteConfigKey(key);
|
|
471
484
|
try {
|
|
472
485
|
const res = await deleteRemoteConfig(self.serverUrl, key, self.token, packageId || undefined, {
|
|
473
486
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -488,22 +501,8 @@ export class RemoteActionDockService {
|
|
|
488
501
|
insecure: self.insecure,
|
|
489
502
|
dispatcher: self.dispatcher,
|
|
490
503
|
});
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (Array.isArray(res?.items))
|
|
494
|
-
return res.items;
|
|
495
|
-
if (Array.isArray(res?.config))
|
|
496
|
-
return res.config;
|
|
497
|
-
const values = res?.values || {};
|
|
498
|
-
const declared = res?.declared || {};
|
|
499
|
-
const defaultSource = packageId === "global" ? "global" : "package";
|
|
500
|
-
return Object.entries(values).map(([k, v]) => ({
|
|
501
|
-
key: k,
|
|
502
|
-
configured: v !== undefined,
|
|
503
|
-
secret: Boolean(declared[k]?.secret),
|
|
504
|
-
source: defaultSource,
|
|
505
|
-
value: v,
|
|
506
|
-
}));
|
|
504
|
+
// 归一化边界已强校验完整视图,直接消费,不再叠加兜底。
|
|
505
|
+
return res.items;
|
|
507
506
|
}
|
|
508
507
|
catch (err) {
|
|
509
508
|
wrapRemoteError(err);
|
package/dist/service/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { CancelResult, ExecutionTicket } from "../execution/types.js";
|
|
|
4
4
|
import type { ActionDockHostOptions } from "../host/types.js";
|
|
5
5
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
6
6
|
import type { StateEntry } from "../storage/types.js";
|
|
7
|
+
import type { ActionBindings } from "../invocation/bindings.js";
|
|
7
8
|
import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED } from "../errors.js";
|
|
8
9
|
/**
|
|
9
10
|
* 公开运行控制选项(RunOptions)。
|
|
@@ -29,6 +30,16 @@ export declare const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
|
|
|
29
30
|
* 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
|
|
30
31
|
*/
|
|
31
32
|
export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
33
|
+
/**
|
|
34
|
+
* 配置来源契约(单一事实源)。
|
|
35
|
+
* 仅表达当前生效配置值的实际来源,不混入传输方式或作用域显示别名:
|
|
36
|
+
* override 为运行时固定覆盖或单次调用覆盖,package 为包级持久化,global 为全局持久化,
|
|
37
|
+
* env 为环境变量命中,default 为清单声明且真实生效的默认值。
|
|
38
|
+
* 无任何生效值时为 null,不得用 default 伪装。
|
|
39
|
+
*/
|
|
40
|
+
export declare const CONFIG_SOURCES: readonly ["override", "package", "global", "env", "default"];
|
|
41
|
+
/** 配置来源类型;由来源元组派生,枚举值单一事实源。 */
|
|
42
|
+
export type ConfigSource = (typeof CONFIG_SOURCES)[number];
|
|
32
43
|
/**
|
|
33
44
|
* 配置项安全视图契约。
|
|
34
45
|
* 屏蔽敏感配置原文,仅暴露是否已配置、是否为秘密及实际解析来源。
|
|
@@ -36,14 +47,16 @@ export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
|
36
47
|
export interface ConfigValueView {
|
|
37
48
|
/** 配置项键名 */
|
|
38
49
|
key: string;
|
|
39
|
-
/**
|
|
50
|
+
/** 是否来自显式来源(override/package/global/env);default 与未配置(source null)均为 false */
|
|
40
51
|
configured: boolean;
|
|
41
|
-
/**
|
|
52
|
+
/** 声明含敏感字段或实际发生保守隐藏;不代表整个值不可见 */
|
|
42
53
|
secret: boolean;
|
|
43
|
-
/**
|
|
44
|
-
source:
|
|
45
|
-
/**
|
|
54
|
+
/** 配置值实际解析来源;无任何生效值时为 null */
|
|
55
|
+
source: ConfigSource | null;
|
|
56
|
+
/** 管理侧安全值;顶层敏感时不提供原值 */
|
|
46
57
|
value?: JsonValue;
|
|
58
|
+
/** 标准 JSON Pointer 隐藏路径;根为 "",未发生隐藏时为空 */
|
|
59
|
+
redactedPaths: string[];
|
|
47
60
|
}
|
|
48
61
|
/**
|
|
49
62
|
* 状态作用域与筛选控制选项。
|
|
@@ -72,6 +85,18 @@ export interface ListRunsOptions {
|
|
|
72
85
|
actionId?: string;
|
|
73
86
|
/** 运行终态状态筛选 */
|
|
74
87
|
status?: string;
|
|
88
|
+
/** 运行通道筛选(如 http、mcp) */
|
|
89
|
+
channel?: string;
|
|
90
|
+
/** 服务视图名称筛选(如 default、admin) */
|
|
91
|
+
view?: string;
|
|
92
|
+
/** 起始时间筛选(UTC ISO 8601 或时间戳字符串,匹配 startedAt >= since) */
|
|
93
|
+
since?: string;
|
|
94
|
+
/** 截止时间筛选(UTC ISO 8601 或时间戳字符串,匹配 startedAt <= until) */
|
|
95
|
+
until?: string;
|
|
96
|
+
/** 根运行标识筛选 */
|
|
97
|
+
rootRunId?: string;
|
|
98
|
+
/** 父运行标识筛选 */
|
|
99
|
+
parentRunId?: string;
|
|
75
100
|
/** 模糊意图检索模式 */
|
|
76
101
|
intent?: string;
|
|
77
102
|
/** 幂等请求标识集合(经 idempotency_keys 反查关联运行记录) */
|
|
@@ -80,6 +105,8 @@ export interface ListRunsOptions {
|
|
|
80
105
|
offset?: number;
|
|
81
106
|
/** 最大返回记录条数限制 */
|
|
82
107
|
limit?: number;
|
|
108
|
+
/** 是否需要附带总数统计 */
|
|
109
|
+
withTotal?: boolean;
|
|
83
110
|
/** 取消信号,用于中止读取请求 */
|
|
84
111
|
signal?: AbortSignal;
|
|
85
112
|
/** 包白名单过滤集合(可选) */
|
|
@@ -240,6 +267,8 @@ export interface ActionDockService {
|
|
|
240
267
|
* 创建 ActionDock 本地服务选项。
|
|
241
268
|
*/
|
|
242
269
|
export interface CreateActionDockOptions {
|
|
270
|
+
/** 按调用方包覆盖运行时命名绑定,不改变动作引用与 uses 声明。 */
|
|
271
|
+
actionBindings?: Readonly<Record<string, ActionBindings>>;
|
|
243
272
|
/** 模式类型(可选) */
|
|
244
273
|
type?: "local";
|
|
245
274
|
/** 单包 PackageRuntime 初始化配置 */
|
package/dist/service/types.js
CHANGED
|
@@ -7,3 +7,11 @@ export const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
|
|
|
7
7
|
* 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
|
|
8
8
|
*/
|
|
9
9
|
export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
10
|
+
/**
|
|
11
|
+
* 配置来源契约(单一事实源)。
|
|
12
|
+
* 仅表达当前生效配置值的实际来源,不混入传输方式或作用域显示别名:
|
|
13
|
+
* override 为运行时固定覆盖或单次调用覆盖,package 为包级持久化,global 为全局持久化,
|
|
14
|
+
* env 为环境变量命中,default 为清单声明且真实生效的默认值。
|
|
15
|
+
* 无任何生效值时为 null,不得用 default 伪装。
|
|
16
|
+
*/
|
|
17
|
+
export const CONFIG_SOURCES = ["override", "package", "global", "env", "default"];
|