@actiondock/core 2.19.0 → 2.21.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/errors.d.ts +13 -1
- package/dist/errors.js +25 -0
- package/dist/execution/service.js +44 -10
- package/dist/execution/types.d.ts +3 -3
- package/dist/host/host.d.ts +20 -1
- package/dist/host/host.js +430 -66
- package/dist/host/types.d.ts +13 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +5 -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 +33 -0
- package/dist/invocation/types.js +11 -1
- package/dist/package/index.d.ts +2 -2
- package/dist/package/runtime.js +48 -15
- package/dist/platform/node.d.ts +6 -0
- package/dist/platform/node.js +59 -16
- package/dist/platform/types.d.ts +59 -1
- package/dist/process/command-executor.d.ts +35 -0
- package/dist/process/command-executor.js +326 -0
- package/dist/process/index.d.ts +1 -0
- package/dist/process/index.js +1 -0
- package/dist/process/process-driver.d.ts +19 -6
- package/dist/process/process-driver.js +223 -54
- package/dist/process/run-executor.js +101 -47
- package/dist/project/config-declarations.d.ts +61 -0
- package/dist/project/config-declarations.js +297 -0
- package/dist/project/dependencies.d.ts +92 -0
- package/dist/project/dependencies.js +543 -0
- package/dist/project/index.d.ts +8 -2
- package/dist/project/index.js +8 -2
- package/dist/project/init.js +2 -2
- package/dist/project/loader.js +19 -30
- package/dist/project/manifest.js +10 -0
- package/dist/project/package-manager.d.ts +40 -0
- package/dist/project/package-manager.js +224 -0
- package/dist/project/preparation.d.ts +8 -0
- package/dist/project/preparation.js +130 -0
- package/dist/project/transactions.d.ts +5 -1
- package/dist/project/transactions.js +35 -75
- package/dist/project/types.d.ts +30 -0
- package/dist/runtime/context.d.ts +27 -5
- package/dist/runtime/context.js +98 -26
- package/dist/runtime/env.js +15 -1
- package/dist/runtime/run-persistence.d.ts +1 -1
- package/dist/runtime/run-persistence.js +4 -3
- 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/security/config-cipher.d.ts +54 -0
- package/dist/security/config-cipher.js +190 -0
- package/dist/security/config-key.d.ts +35 -0
- package/dist/security/config-key.js +469 -0
- 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 +84 -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 +96 -18
- package/dist/server/server.js +25 -53
- package/dist/server/types.d.ts +6 -6
- package/dist/service/factory.js +4 -0
- package/dist/service/remote.js +14 -29
- package/dist/service/types.d.ts +28 -5
- package/dist/service/types.js +8 -0
- package/dist/storage/config-value.d.ts +33 -0
- package/dist/storage/config-value.js +102 -0
- package/dist/storage/file-lock.js +35 -21
- package/dist/storage/index.d.ts +24 -2
- package/dist/storage/index.js +65 -2
- package/dist/storage/mask.d.ts +33 -6
- package/dist/storage/mask.js +298 -16
- package/dist/storage/migrations/index.d.ts +40 -0
- package/dist/storage/migrations/index.js +96 -0
- package/dist/storage/migrations/v2-to-v3.d.ts +16 -0
- package/dist/storage/migrations/v2-to-v3.js +116 -0
- package/dist/storage/sqlite-config.d.ts +62 -3
- package/dist/storage/sqlite-config.js +163 -9
- package/dist/storage/sqlite-driver.d.ts +5 -2
- package/dist/storage/sqlite-driver.js +24 -3
- package/dist/storage/sqlite-schema.d.ts +20 -2
- package/dist/storage/sqlite-schema.js +129 -132
- package/dist/storage/sqlite.d.ts +7 -2
- package/dist/storage/sqlite.js +64 -13
- package/dist/storage/types.d.ts +72 -4
- package/dist/storage/types.js +7 -2
- 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
|
*/
|
|
@@ -54,7 +113,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
54
113
|
limit = Math.min(Math.max(parsedLimit, 1), 500);
|
|
55
114
|
}
|
|
56
115
|
}
|
|
57
|
-
if (packageId && !
|
|
116
|
+
if (packageId && !isPackageVisibleByPolicy(packageId, policy)) {
|
|
58
117
|
return jsonResponse({
|
|
59
118
|
ok: false,
|
|
60
119
|
error: {
|
|
@@ -63,7 +122,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
63
122
|
},
|
|
64
123
|
}, 403, corsHeaders);
|
|
65
124
|
}
|
|
66
|
-
if (actionId && !isActionAllowedByPolicy({ packageId, actionId }, policy)) {
|
|
125
|
+
if (actionId && (packageId || actionId.includes("/")) && !isActionAllowedByPolicy({ packageId, actionId }, policy)) {
|
|
67
126
|
return jsonResponse({
|
|
68
127
|
ok: false,
|
|
69
128
|
error: {
|
|
@@ -72,6 +131,15 @@ export async function handleRunsRoutes(ctx) {
|
|
|
72
131
|
},
|
|
73
132
|
}, 403, corsHeaders);
|
|
74
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
|
+
}
|
|
75
143
|
const queryOptions = {
|
|
76
144
|
actionId,
|
|
77
145
|
status,
|
|
@@ -86,12 +154,9 @@ export async function handleRunsRoutes(ctx) {
|
|
|
86
154
|
intent,
|
|
87
155
|
requestIds: requestIds.length > 0 ? requestIds : undefined,
|
|
88
156
|
signal: req.signal,
|
|
89
|
-
packageAllowlist:
|
|
90
|
-
? policy.packageAllowlist
|
|
91
|
-
: undefined,
|
|
157
|
+
packageAllowlist: backendPackageFilter,
|
|
92
158
|
};
|
|
93
|
-
const hasPolicyFilter =
|
|
94
|
-
(policy.actionAllowlist && policy.actionAllowlist.length > 0));
|
|
159
|
+
const hasPolicyFilter = !isFullAccessGranted(policy);
|
|
95
160
|
let runs;
|
|
96
161
|
let total;
|
|
97
162
|
if (hasPolicyFilter) {
|
|
@@ -116,7 +181,9 @@ export async function handleRunsRoutes(ctx) {
|
|
|
116
181
|
break;
|
|
117
182
|
}
|
|
118
183
|
for (const run of batch) {
|
|
119
|
-
|
|
184
|
+
// 逐条以实际 packageId/actionId 完整引用判定授权:
|
|
185
|
+
// 包可见性拦无关包,动作授权保留单动作授权目标(如 B/read)
|
|
186
|
+
const packageAllowed = isPackageVisibleByPolicy(run.packageId, policy);
|
|
120
187
|
const actionAllowed = isActionAllowedByPolicy({ packageId: run.packageId, actionId: run.actionId }, policy);
|
|
121
188
|
if (packageAllowed && actionAllowed) {
|
|
122
189
|
if (matchedCount >= offset && pagedRuns.length < limit) {
|
|
@@ -218,20 +285,22 @@ export async function handleRunsRoutes(ctx) {
|
|
|
218
285
|
keep = parsed;
|
|
219
286
|
}
|
|
220
287
|
}
|
|
221
|
-
|
|
288
|
+
const scopeCheck = resolveRunCleanupScope(packageId, actionId, policy);
|
|
289
|
+
if (!scopeCheck.ok) {
|
|
222
290
|
return jsonResponse({
|
|
223
291
|
ok: false,
|
|
224
292
|
error: {
|
|
225
293
|
code: PACKAGE_FORBIDDEN,
|
|
226
|
-
message:
|
|
294
|
+
message: scopeCheck.reason,
|
|
227
295
|
},
|
|
228
296
|
}, 403, corsHeaders);
|
|
229
297
|
}
|
|
298
|
+
const clearedScope = scopeCheck.filter;
|
|
230
299
|
let clearedCount = 0;
|
|
231
300
|
if (service.runs.clear) {
|
|
232
301
|
clearedCount = await service.runs.clear({
|
|
233
|
-
packageId,
|
|
234
|
-
actionId,
|
|
302
|
+
packageId: clearedScope.packageId,
|
|
303
|
+
actionId: clearedScope.actionId,
|
|
235
304
|
status,
|
|
236
305
|
olderThanMs,
|
|
237
306
|
keep,
|
|
@@ -244,7 +313,17 @@ export async function handleRunsRoutes(ctx) {
|
|
|
244
313
|
}
|
|
245
314
|
}
|
|
246
315
|
// 2.1 Runs Clean Expired: POST /api/v2/runs/clean-expired, POST /runs/clean-expired
|
|
316
|
+
// 此操作无法按包或动作收窄范围,只允许显式的全部授权。
|
|
247
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
|
+
}
|
|
248
327
|
try {
|
|
249
328
|
let body = {};
|
|
250
329
|
if (req.headers.get("content-type")?.includes("json")) {
|
|
@@ -268,7 +347,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
268
347
|
if (!run) {
|
|
269
348
|
return jsonResponse({ ok: false, error: { code: RUN_NOT_FOUND, message: `Run '${runId}' not found` } }, 404, corsHeaders);
|
|
270
349
|
}
|
|
271
|
-
if (!
|
|
350
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
272
351
|
return jsonResponse({
|
|
273
352
|
ok: false,
|
|
274
353
|
error: {
|
|
@@ -443,7 +522,7 @@ export async function handleRunsRoutes(ctx) {
|
|
|
443
522
|
},
|
|
444
523
|
}, 404, corsHeaders);
|
|
445
524
|
}
|
|
446
|
-
if (!
|
|
525
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
447
526
|
return jsonResponse({
|
|
448
527
|
ok: false,
|
|
449
528
|
error: {
|
|
@@ -467,11 +546,10 @@ export async function handleRunsRoutes(ctx) {
|
|
|
467
546
|
const runCancelMatch = subpath.match(/^\/runs\/([^/]+)\/cancel$/);
|
|
468
547
|
if (runCancelMatch && req.method === "POST") {
|
|
469
548
|
const runId = decodeURIComponent(runCancelMatch[1]);
|
|
470
|
-
if ((policy
|
|
471
|
-
(policy.actionAllowlist && policy.actionAllowlist.length > 0)) {
|
|
549
|
+
if (!isFullAccessGranted(policy)) {
|
|
472
550
|
const run = await service.runs.get(runId);
|
|
473
551
|
if (run) {
|
|
474
|
-
if (!
|
|
552
|
+
if (!isPackageVisibleByPolicy(run.packageId, policy)) {
|
|
475
553
|
return jsonResponse({
|
|
476
554
|
ok: false,
|
|
477
555
|
error: {
|
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";
|
|
@@ -70,70 +70,42 @@ export async function startActionDockServer(arg1, arg2) {
|
|
|
70
70
|
throw new ActionDockError(ACCESS_DENIED, "Authentication token is required when binding to a non-loopback address. Use --allow-insecure-no-auth to override.");
|
|
71
71
|
}
|
|
72
72
|
let serviceInstance = options.service;
|
|
73
|
-
//
|
|
73
|
+
// 包授权与单动作引用都参与自建服务的装载;外部注入服务自行负责能力装配。
|
|
74
74
|
if (!serviceInstance) {
|
|
75
|
-
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
|
+
}
|
|
76
95
|
serviceInstance = await createActionDock({
|
|
77
96
|
projectRoot: projectRoot || undefined,
|
|
97
|
+
packages: extraPackages.length > 0 ? extraPackages : undefined,
|
|
78
98
|
customHome,
|
|
79
99
|
platform: options.platform,
|
|
80
100
|
dataDir: options.dataDir,
|
|
81
101
|
inMemory: options.inMemory,
|
|
82
|
-
scanLinkedPackages,
|
|
102
|
+
scanLinkedPackages: options.scanLinkedPackages ?? !projectRoot,
|
|
83
103
|
enableManagement: options.enableManagement,
|
|
84
104
|
});
|
|
85
105
|
}
|
|
86
106
|
if (!serviceInstance) {
|
|
87
107
|
throw new ActionDockError(SERVICE_ERROR, "Failed to initialize ActionDockService for server");
|
|
88
108
|
}
|
|
89
|
-
// 2. 聚合所有 views 声明的依赖包与动作,确保依赖闭包满足
|
|
90
|
-
const roots = [];
|
|
91
|
-
if (projectRoot) {
|
|
92
|
-
roots.push(projectRoot);
|
|
93
|
-
}
|
|
94
|
-
const aggregatedPackageAllowlist = new Set();
|
|
95
|
-
const aggregatedActionAllowlist = new Set();
|
|
96
|
-
for (const v of views.values()) {
|
|
97
|
-
if (v.policy.packageAllowlist) {
|
|
98
|
-
for (const pkg of v.policy.packageAllowlist) {
|
|
99
|
-
aggregatedPackageAllowlist.add(pkg);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (v.policy.actionAllowlist) {
|
|
103
|
-
for (const act of v.policy.actionAllowlist) {
|
|
104
|
-
aggregatedActionAllowlist.add(act);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
if (aggregatedPackageAllowlist.size > 0) {
|
|
109
|
-
for (const pkgId of aggregatedPackageAllowlist) {
|
|
110
|
-
const r = resolvePackageRoot(pkgId, projectRoot || undefined, customHome);
|
|
111
|
-
if (r && !roots.includes(r)) {
|
|
112
|
-
roots.push(r);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else if (!projectRoot) {
|
|
117
|
-
for (const pkg of listLinkedPackages(customHome)) {
|
|
118
|
-
if (!roots.includes(pkg.path)) {
|
|
119
|
-
roots.push(pkg.path);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (aggregatedActionAllowlist.size > 0) {
|
|
124
|
-
for (const actRef of aggregatedActionAllowlist) {
|
|
125
|
-
try {
|
|
126
|
-
const parsed = parseActionRef(actRef);
|
|
127
|
-
if (parsed.packageId) {
|
|
128
|
-
const r = resolvePackageRoot(parsed.packageId, projectRoot || undefined, customHome);
|
|
129
|
-
if (r && !roots.includes(r)) {
|
|
130
|
-
roots.push(r);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch { }
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
109
|
// 3. 为每个启用 MCP 的视图独立装配 MCP 处理器
|
|
138
110
|
if (options.enableMcp !== false) {
|
|
139
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,
|
|
@@ -21,6 +22,8 @@ export async function createActionDock(options = {}) {
|
|
|
21
22
|
logger: options.runtimeOptions.logger,
|
|
22
23
|
recoverOrphans: options.recoverOrphans,
|
|
23
24
|
enableManagement: options.enableManagement,
|
|
25
|
+
allowInstall: options.allowInstall,
|
|
26
|
+
prepareLocalPackage: options.hostOptions?.prepareLocalPackage ?? options.prepareLocalPackage,
|
|
24
27
|
});
|
|
25
28
|
return host;
|
|
26
29
|
}
|
|
@@ -29,6 +32,7 @@ export async function createActionDock(options = {}) {
|
|
|
29
32
|
...options,
|
|
30
33
|
recoverOrphans: options.recoverOrphans ?? true,
|
|
31
34
|
enableManagement: options.enableManagement,
|
|
35
|
+
allowInstall: options.allowInstall,
|
|
32
36
|
...(options.hostOptions || {}),
|
|
33
37
|
});
|
|
34
38
|
return host;
|
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";
|
|
@@ -449,19 +450,15 @@ export class RemoteActionDockService {
|
|
|
449
450
|
config: {
|
|
450
451
|
async get(packageId, key) {
|
|
451
452
|
self.assertNotClosed();
|
|
453
|
+
assertConcreteConfigKey(key);
|
|
452
454
|
try {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
configured: false,
|
|
461
|
-
secret: false,
|
|
462
|
-
source: "default",
|
|
463
|
-
value: undefined,
|
|
464
|
-
};
|
|
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];
|
|
465
462
|
}
|
|
466
463
|
catch (err) {
|
|
467
464
|
wrapRemoteError(err);
|
|
@@ -469,6 +466,7 @@ export class RemoteActionDockService {
|
|
|
469
466
|
},
|
|
470
467
|
async set(packageId, key, value) {
|
|
471
468
|
self.assertNotClosed();
|
|
469
|
+
assertConcreteConfigKey(key);
|
|
472
470
|
try {
|
|
473
471
|
await setRemoteConfig(self.serverUrl, key, value, self.token, packageId || undefined, {
|
|
474
472
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -482,6 +480,7 @@ export class RemoteActionDockService {
|
|
|
482
480
|
},
|
|
483
481
|
async delete(packageId, key) {
|
|
484
482
|
self.assertNotClosed();
|
|
483
|
+
assertConcreteConfigKey(key);
|
|
485
484
|
try {
|
|
486
485
|
const res = await deleteRemoteConfig(self.serverUrl, key, self.token, packageId || undefined, {
|
|
487
486
|
allowInsecureHttp: self.allowInsecureHttp,
|
|
@@ -502,22 +501,8 @@ export class RemoteActionDockService {
|
|
|
502
501
|
insecure: self.insecure,
|
|
503
502
|
dispatcher: self.dispatcher,
|
|
504
503
|
});
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
if (Array.isArray(res?.items))
|
|
508
|
-
return res.items;
|
|
509
|
-
if (Array.isArray(res?.config))
|
|
510
|
-
return res.config;
|
|
511
|
-
const values = res?.values || {};
|
|
512
|
-
const declared = res?.declared || {};
|
|
513
|
-
const defaultSource = packageId === "global" ? "global" : "package";
|
|
514
|
-
return Object.entries(values).map(([k, v]) => ({
|
|
515
|
-
key: k,
|
|
516
|
-
configured: v !== undefined,
|
|
517
|
-
secret: Boolean(declared[k]?.secret),
|
|
518
|
-
source: defaultSource,
|
|
519
|
-
value: v,
|
|
520
|
-
}));
|
|
504
|
+
// 归一化边界已强校验完整视图,直接消费,不再叠加兜底。
|
|
505
|
+
return res.items;
|
|
521
506
|
}
|
|
522
507
|
catch (err) {
|
|
523
508
|
wrapRemoteError(err);
|
package/dist/service/types.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ import type { ActionRef, ExecutionEvent, ExecutionResult, JsonValue, RunOrigin,
|
|
|
2
2
|
import type { ActionSpec, ActionSummary, ListActionsOptions, PackageInfo, PackageRuntime, PackageRuntimeOptions, PlaybookSpec, PlaybookSummary } from "../package/types.js";
|
|
3
3
|
import type { CancelResult, ExecutionTicket } from "../execution/types.js";
|
|
4
4
|
import type { ActionDockHostOptions } from "../host/types.js";
|
|
5
|
+
import type { LocalPackagePreparer } from "../invocation/types.js";
|
|
5
6
|
import type { RuntimePlatform } from "../platform/types.js";
|
|
6
7
|
import type { StateEntry } from "../storage/types.js";
|
|
8
|
+
import type { ActionBindings } from "../invocation/bindings.js";
|
|
7
9
|
import { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED } from "../errors.js";
|
|
8
10
|
/**
|
|
9
11
|
* 公开运行控制选项(RunOptions)。
|
|
@@ -29,6 +31,16 @@ export declare const ACTIONDOCK_PROTOCOL_VERSION = "2.0";
|
|
|
29
31
|
* 服务端/通信协议错误码常量(单一事实源引用自 errors.ts)。
|
|
30
32
|
*/
|
|
31
33
|
export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
34
|
+
/**
|
|
35
|
+
* 配置来源契约(单一事实源)。
|
|
36
|
+
* 仅表达当前生效配置值的实际来源,不混入传输方式或作用域显示别名:
|
|
37
|
+
* override 为运行时固定覆盖或单次调用覆盖,package 为包级持久化,global 为全局持久化,
|
|
38
|
+
* env 为环境变量命中,default 为清单声明且真实生效的默认值。
|
|
39
|
+
* 无任何生效值时为 null,不得用 default 伪装。
|
|
40
|
+
*/
|
|
41
|
+
export declare const CONFIG_SOURCES: readonly ["override", "package", "global", "env", "default"];
|
|
42
|
+
/** 配置来源类型;由来源元组派生,枚举值单一事实源。 */
|
|
43
|
+
export type ConfigSource = (typeof CONFIG_SOURCES)[number];
|
|
32
44
|
/**
|
|
33
45
|
* 配置项安全视图契约。
|
|
34
46
|
* 屏蔽敏感配置原文,仅暴露是否已配置、是否为秘密及实际解析来源。
|
|
@@ -36,14 +48,16 @@ export { PROTOCOL_UNSUPPORTED, SERVICE_RESULT_UNKNOWN, SERVICE_CLOSED };
|
|
|
36
48
|
export interface ConfigValueView {
|
|
37
49
|
/** 配置项键名 */
|
|
38
50
|
key: string;
|
|
39
|
-
/**
|
|
51
|
+
/** 是否来自显式来源(override/package/global/env);default 与未配置(source null)均为 false */
|
|
40
52
|
configured: boolean;
|
|
41
|
-
/**
|
|
53
|
+
/** 声明含敏感字段或实际发生保守隐藏;不代表整个值不可见 */
|
|
42
54
|
secret: boolean;
|
|
43
|
-
/**
|
|
44
|
-
source:
|
|
45
|
-
/**
|
|
55
|
+
/** 配置值实际解析来源;无任何生效值时为 null */
|
|
56
|
+
source: ConfigSource | null;
|
|
57
|
+
/** 管理侧安全值;顶层敏感时不提供原值 */
|
|
46
58
|
value?: JsonValue;
|
|
59
|
+
/** 标准 JSON Pointer 隐藏路径;根为 "",未发生隐藏时为空 */
|
|
60
|
+
redactedPaths: string[];
|
|
47
61
|
}
|
|
48
62
|
/**
|
|
49
63
|
* 状态作用域与筛选控制选项。
|
|
@@ -254,6 +268,8 @@ export interface ActionDockService {
|
|
|
254
268
|
* 创建 ActionDock 本地服务选项。
|
|
255
269
|
*/
|
|
256
270
|
export interface CreateActionDockOptions {
|
|
271
|
+
/** 按调用方包覆盖运行时命名绑定,不改变动作引用与 uses 声明。 */
|
|
272
|
+
actionBindings?: Readonly<Record<string, ActionBindings>>;
|
|
257
273
|
/** 模式类型(可选) */
|
|
258
274
|
type?: "local";
|
|
259
275
|
/** 单包 PackageRuntime 初始化配置 */
|
|
@@ -280,6 +296,13 @@ export interface CreateActionDockOptions {
|
|
|
280
296
|
recoverOrphans?: boolean;
|
|
281
297
|
/** 是否开启管理端口(默认 true) */
|
|
282
298
|
enableManagement?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* 是否允许执行依赖自动安装(主要控制启动与事务恢复中的依赖安装;
|
|
301
|
+
* 本地包运行期准备由 prepareLocalPackage 注入器根据自身配置决定)。
|
|
302
|
+
*/
|
|
303
|
+
allowInstall?: boolean;
|
|
304
|
+
/** 本地包准备协调器回调 */
|
|
305
|
+
prepareLocalPackage?: LocalPackagePreparer;
|
|
283
306
|
}
|
|
284
307
|
/**
|
|
285
308
|
* 连接 ActionDock 远程服务选项。
|
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"];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ConfigItemDefinition } from "../project/types.js";
|
|
2
|
+
import { type ConfigEncryptionKey } from "../security/config-cipher.js";
|
|
3
|
+
/**
|
|
4
|
+
* 密文行内部负载信封:业务值与历史保护声明一同加密。
|
|
5
|
+
*
|
|
6
|
+
* 历史保护声明仅保留脱敏所需的结构/类型/secret 信息,供当前声明
|
|
7
|
+
* 弱化后的管理视图脱敏兜底;业务值透明解密后原样返回。
|
|
8
|
+
*
|
|
9
|
+
* 本模块是密文信封编码与保护声明清洗的单一事实源:正常写入路径
|
|
10
|
+
* (SqliteConfigStore)与版本迁移路径(migrations/v2-to-v3)共用,
|
|
11
|
+
* 禁止任何一侧复制 envelope 或保护清洗逻辑。
|
|
12
|
+
*/
|
|
13
|
+
export interface ConfigValueEnvelope {
|
|
14
|
+
value: unknown;
|
|
15
|
+
protection: ConfigItemDefinition[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 合并历史保护声明与当前敏感声明,绝不弱化保护强度。
|
|
19
|
+
*
|
|
20
|
+
* - 以规范化 JSON 作为去重标识,防止重复写入导致保护数组无界增长;
|
|
21
|
+
* - 当前声明中仅并入判定敏感的条目(非敏感声明不产生历史保护义务);
|
|
22
|
+
* - 合并结果为空时保守回退为整项敏感,保证密文行始终有脱敏依据。
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeProtectionDeclarations(stored: readonly ConfigItemDefinition[] | undefined, current: readonly ConfigItemDefinition[], key: string): ConfigItemDefinition[];
|
|
25
|
+
/** 加密业务值与保护声明构成的完整信封,返回规范密文信封字符串。 */
|
|
26
|
+
export declare function encryptConfigEnvelope(value: unknown, protection: readonly ConfigItemDefinition[], scope: string, key: string, material: ConfigEncryptionKey): string;
|
|
27
|
+
/**
|
|
28
|
+
* 严格解密密文信封并校验负载形态。
|
|
29
|
+
*
|
|
30
|
+
* 信封负载必须为含 value 属性的对象;protection 省略或为声明对象数组。
|
|
31
|
+
* 任何不合规输入抛 CONFIG_DECRYPT_FAILED,绝不把密文降级当作明文返回。
|
|
32
|
+
*/
|
|
33
|
+
export declare function decryptConfigEnvelope(ciphertext: string, scope: string, key: string, material: ConfigEncryptionKey): ConfigValueEnvelope;
|