@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.
Files changed (108) hide show
  1. package/dist/client/config.d.ts +17 -14
  2. package/dist/client/config.js +112 -14
  3. package/dist/client/index.d.ts +1 -1
  4. package/dist/client/index.js +1 -1
  5. package/dist/errors.d.ts +13 -1
  6. package/dist/errors.js +25 -0
  7. package/dist/execution/service.js +44 -10
  8. package/dist/execution/types.d.ts +3 -3
  9. package/dist/host/host.d.ts +20 -1
  10. package/dist/host/host.js +430 -66
  11. package/dist/host/types.d.ts +13 -1
  12. package/dist/index.d.ts +11 -3
  13. package/dist/index.js +5 -1
  14. package/dist/input/config-override.d.ts +5 -0
  15. package/dist/input/config-override.js +23 -0
  16. package/dist/input/input-resolver.d.ts +24 -7
  17. package/dist/input/input-resolver.js +73 -5
  18. package/dist/invocation/bindings.d.ts +53 -0
  19. package/dist/invocation/bindings.js +248 -0
  20. package/dist/invocation/policy.d.ts +5 -1
  21. package/dist/invocation/policy.js +16 -9
  22. package/dist/invocation/remote.d.ts +40 -0
  23. package/dist/invocation/remote.js +489 -0
  24. package/dist/invocation/types.d.ts +33 -0
  25. package/dist/invocation/types.js +11 -1
  26. package/dist/package/index.d.ts +2 -2
  27. package/dist/package/runtime.js +48 -15
  28. package/dist/platform/node.d.ts +6 -0
  29. package/dist/platform/node.js +59 -16
  30. package/dist/platform/types.d.ts +59 -1
  31. package/dist/process/command-executor.d.ts +35 -0
  32. package/dist/process/command-executor.js +326 -0
  33. package/dist/process/index.d.ts +1 -0
  34. package/dist/process/index.js +1 -0
  35. package/dist/process/process-driver.d.ts +19 -6
  36. package/dist/process/process-driver.js +223 -54
  37. package/dist/process/run-executor.js +101 -47
  38. package/dist/project/config-declarations.d.ts +61 -0
  39. package/dist/project/config-declarations.js +297 -0
  40. package/dist/project/dependencies.d.ts +92 -0
  41. package/dist/project/dependencies.js +543 -0
  42. package/dist/project/index.d.ts +8 -2
  43. package/dist/project/index.js +8 -2
  44. package/dist/project/init.js +2 -2
  45. package/dist/project/loader.js +19 -30
  46. package/dist/project/manifest.js +10 -0
  47. package/dist/project/package-manager.d.ts +40 -0
  48. package/dist/project/package-manager.js +224 -0
  49. package/dist/project/preparation.d.ts +8 -0
  50. package/dist/project/preparation.js +130 -0
  51. package/dist/project/transactions.d.ts +5 -1
  52. package/dist/project/transactions.js +35 -75
  53. package/dist/project/types.d.ts +30 -0
  54. package/dist/runtime/context.d.ts +27 -5
  55. package/dist/runtime/context.js +98 -26
  56. package/dist/runtime/env.js +15 -1
  57. package/dist/runtime/run-persistence.d.ts +1 -1
  58. package/dist/runtime/run-persistence.js +4 -3
  59. package/dist/runtime/standalone-commands.d.ts +2 -2
  60. package/dist/runtime/standalone-commands.js +113 -47
  61. package/dist/runtime/standalone-help.d.ts +1 -0
  62. package/dist/runtime/standalone-help.js +15 -9
  63. package/dist/runtime/standalone.js +2 -2
  64. package/dist/security/config-cipher.d.ts +54 -0
  65. package/dist/security/config-cipher.js +190 -0
  66. package/dist/security/config-key.d.ts +35 -0
  67. package/dist/security/config-key.js +469 -0
  68. package/dist/server/policy.d.ts +24 -107
  69. package/dist/server/policy.js +134 -291
  70. package/dist/server/routes/actions.js +10 -8
  71. package/dist/server/routes/common.d.ts +5 -20
  72. package/dist/server/routes/common.js +22 -58
  73. package/dist/server/routes/config.js +84 -7
  74. package/dist/server/routes/doctor.d.ts +1 -0
  75. package/dist/server/routes/doctor.js +4 -3
  76. package/dist/server/routes/info.js +100 -13
  77. package/dist/server/routes/playbooks.js +1 -3
  78. package/dist/server/routes/runs.js +96 -18
  79. package/dist/server/server.js +25 -53
  80. package/dist/server/types.d.ts +6 -6
  81. package/dist/service/factory.js +4 -0
  82. package/dist/service/remote.js +14 -29
  83. package/dist/service/types.d.ts +28 -5
  84. package/dist/service/types.js +8 -0
  85. package/dist/storage/config-value.d.ts +33 -0
  86. package/dist/storage/config-value.js +102 -0
  87. package/dist/storage/file-lock.js +35 -21
  88. package/dist/storage/index.d.ts +24 -2
  89. package/dist/storage/index.js +65 -2
  90. package/dist/storage/mask.d.ts +33 -6
  91. package/dist/storage/mask.js +298 -16
  92. package/dist/storage/migrations/index.d.ts +40 -0
  93. package/dist/storage/migrations/index.js +96 -0
  94. package/dist/storage/migrations/v2-to-v3.d.ts +16 -0
  95. package/dist/storage/migrations/v2-to-v3.js +116 -0
  96. package/dist/storage/sqlite-config.d.ts +62 -3
  97. package/dist/storage/sqlite-config.js +163 -9
  98. package/dist/storage/sqlite-driver.d.ts +5 -2
  99. package/dist/storage/sqlite-driver.js +24 -3
  100. package/dist/storage/sqlite-schema.d.ts +20 -2
  101. package/dist/storage/sqlite-schema.js +129 -132
  102. package/dist/storage/sqlite.d.ts +7 -2
  103. package/dist/storage/sqlite.js +64 -13
  104. package/dist/storage/types.d.ts +72 -4
  105. package/dist/storage/types.js +7 -2
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. 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 { getSubPath, isActionAllowedByPolicy, isPackageAllowedByPolicy, jsonResponse } from "./common.js";
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 && !isPackageAllowedByPolicy(packageId, policy)) {
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: policy.packageAllowlist && policy.packageAllowlist.length > 0
90
- ? policy.packageAllowlist
91
- : undefined,
157
+ packageAllowlist: backendPackageFilter,
92
158
  };
93
- const hasPolicyFilter = Boolean((policy.packageAllowlist && policy.packageAllowlist.length > 0) ||
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
- const packageAllowed = isPackageAllowedByPolicy(run.packageId, policy);
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
- if (packageId && !isPackageAllowedByPolicy(packageId, policy)) {
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: `Package '${packageId}' is not in the allowed package list`,
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 (!isPackageAllowedByPolicy(run.packageId, policy)) {
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 (!isPackageAllowedByPolicy(run.packageId, policy)) {
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.packageAllowlist && policy.packageAllowlist.length > 0) ||
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 (!isPackageAllowedByPolicy(run.packageId, policy)) {
552
+ if (!isPackageVisibleByPolicy(run.packageId, policy)) {
475
553
  return jsonResponse({
476
554
  ok: false,
477
555
  error: {
@@ -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 { listLinkedPackages, resolvePackageRoot } from "../registry/registry.js";
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
- // 若未传 service,服务端统一通过 createActionDock() 实例化,不再接受或处理外部传入的 Host 实体
73
+ // 包授权与单动作引用都参与自建服务的装载;外部注入服务自行负责能力装配。
74
74
  if (!serviceInstance) {
75
- const scanLinkedPackages = options.scanLinkedPackages ?? !projectRoot;
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()) {
@@ -26,9 +26,9 @@ export interface ServerViewOptions {
26
26
  name?: string;
27
27
  /** 该视图独立的 Bearer Token 鉴权令牌 */
28
28
  token?: string;
29
- /** 该视图允许访问执行的 Package ID 白名单列表(为空允许全部) */
29
+ /** 整包授权列表,与动作授权取并集;空列表不授予权限,独立 * 显式授权全部 */
30
30
  packageAllowlist?: string[];
31
- /** 该视图允许访问执行的 Action 标识白名单列表(为空允许全部,支持短名或 packageId/actionId) */
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
- /** 当前生效的 Package ID 白名单列表 */
48
+ /** 当前生效的整包授权列表,独立 * 代表全部 */
49
49
  packageAllowlist?: string[];
50
- /** 当前生效的 Action 标识白名单列表 */
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
- /** 允许访问执行的 Package ID 白名单列表(为空允许全部) */
83
+ /** 整包授权列表,与动作授权取并集;空列表不授予权限,独立 * 显式授权全部 */
84
84
  packageAllowlist?: string[];
85
- /** 允许访问执行的 Action 标识白名单列表(为空允许全部,支持短名或 packageId/actionId) */
85
+ /** 全限定 packageId/actionId 精确动作授权列表;空列表不授予权限 */
86
86
  actionAllowlist?: string[];
87
87
  /** 最大允许的请求体字节限制(默认 1MB,防 DoS) */
88
88
  maxBodyBytes?: number;
@@ -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;
@@ -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 list = await self.management.config.list(packageId);
454
- const found = list.find((c) => c.key === key);
455
- if (found) {
456
- return found;
457
- }
458
- return {
459
- key,
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
- if (Array.isArray(res))
506
- return res;
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);
@@ -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: string;
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 远程服务选项。
@@ -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;