@actiondock/core 2.19.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.
Files changed (66) 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 +3 -1
  6. package/dist/errors.js +2 -0
  7. package/dist/execution/service.js +8 -5
  8. package/dist/execution/types.d.ts +3 -3
  9. package/dist/host/host.d.ts +9 -1
  10. package/dist/host/host.js +183 -38
  11. package/dist/host/types.d.ts +3 -0
  12. package/dist/index.d.ts +4 -3
  13. package/dist/index.js +1 -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 +2 -0
  25. package/dist/invocation/types.js +1 -0
  26. package/dist/package/index.d.ts +1 -1
  27. package/dist/package/runtime.js +31 -12
  28. package/dist/platform/types.d.ts +48 -1
  29. package/dist/project/config-declarations.d.ts +61 -0
  30. package/dist/project/config-declarations.js +297 -0
  31. package/dist/project/index.d.ts +5 -2
  32. package/dist/project/index.js +5 -2
  33. package/dist/project/init.js +2 -2
  34. package/dist/project/loader.js +4 -0
  35. package/dist/project/manifest.js +10 -0
  36. package/dist/project/types.d.ts +30 -0
  37. package/dist/runtime/context.d.ts +6 -3
  38. package/dist/runtime/context.js +60 -13
  39. package/dist/runtime/env.js +15 -1
  40. package/dist/runtime/standalone-commands.d.ts +2 -2
  41. package/dist/runtime/standalone-commands.js +113 -47
  42. package/dist/runtime/standalone-help.d.ts +1 -0
  43. package/dist/runtime/standalone-help.js +15 -9
  44. package/dist/runtime/standalone.js +2 -2
  45. package/dist/server/policy.d.ts +24 -107
  46. package/dist/server/policy.js +134 -291
  47. package/dist/server/routes/actions.js +10 -8
  48. package/dist/server/routes/common.d.ts +5 -20
  49. package/dist/server/routes/common.js +22 -58
  50. package/dist/server/routes/config.js +65 -7
  51. package/dist/server/routes/doctor.d.ts +1 -0
  52. package/dist/server/routes/doctor.js +4 -3
  53. package/dist/server/routes/info.js +100 -13
  54. package/dist/server/routes/playbooks.js +1 -3
  55. package/dist/server/routes/runs.js +96 -18
  56. package/dist/server/server.js +25 -53
  57. package/dist/server/types.d.ts +6 -6
  58. package/dist/service/factory.js +1 -0
  59. package/dist/service/remote.js +14 -29
  60. package/dist/service/types.d.ts +20 -5
  61. package/dist/service/types.js +8 -0
  62. package/dist/storage/mask.d.ts +33 -6
  63. package/dist/storage/mask.js +298 -16
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +2 -2
package/dist/host/host.js CHANGED
@@ -8,16 +8,20 @@ import { hasPendingTransactions, isProjectLockHeld, recoverPendingTransactions }
8
8
  import { listLinkedPackages } from "../registry/registry.js";
9
9
  import { InMemoryEventSink } from "../runtime/events.js";
10
10
  import { snapshotRunOrigin } from "../runtime/run-origin.js";
11
- import { ActionDockError, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, EXECUTION_FAILED, INVALID_ACTION_REF, PACKAGE_ID_CONFLICT, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, SERVICE_CLOSED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
11
+ import { ActionDockError, ACTION_CANCELLED, ACTION_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_SUBRUN_LIMIT, EXECUTION_FAILED, INVALID_ACTION_REF, PACKAGE_ID_CONFLICT, PACKAGE_NOT_FOUND, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, SERVICE_CLOSED, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
12
12
  import { InvocationPolicy } from "../invocation/policy.js";
13
+ import { loadActionBindings, resolveActionBinding, validateActionBindings, } from "../invocation/bindings.js";
14
+ import { RemoteInvocationExecutor } from "../invocation/remote.js";
15
+ import { resolveTarget } from "../profile/manager.js";
13
16
  import { createAbortError } from "../ipc/service.js";
14
17
  import { DataDirLock } from "../storage/data-dir-lock.js";
15
18
  import { DefaultActionCatalog, DefaultPackageGraph, PackageGraphBuilder, resolveAction, } from "../catalog/index.js";
16
19
  import { parseActionRef } from "../catalog/resolve-action.js";
17
20
  import { buildRuntimeError, collectPackageInfos, describeActionAcrossRuntimes, describeVisiblePlaybook, isRootCallVisible, listVisiblePlaybooks, packageNotFoundMessage, parseRefLoose, resolveProjectRoot, } from "./routing.js";
18
21
  import { CAPABILITY_UNAVAILABLE } from "../errors.js";
19
- import { createGlobalStorage, isSecretConfigKey } from "../storage/index.js";
22
+ import { createGlobalStorage, redactConfigValueAtPaths, redactConfigValueForDeclarations } from "../storage/index.js";
20
23
  import { filterByIntent } from "../utils/intent.js";
24
+ import { assertConcreteConfigKey, isConfigTemplate, resolveConfigDeclaration, validateConfigValue, } from "../project/config-declarations.js";
21
25
  function isPackageRuntime(item) {
22
26
  return (typeof item === "object" &&
23
27
  item !== null &&
@@ -60,6 +64,8 @@ export class DefaultActionDockHost {
60
64
  hostSessionId;
61
65
  options;
62
66
  runtimes = new Map();
67
+ extraRuntimes = new Set();
68
+ extraRuntimeScopes = new Map();
63
69
  policy;
64
70
  hostPublicPackageIds = new Set();
65
71
  maxCallDepth;
@@ -76,6 +82,9 @@ export class DefaultActionDockHost {
76
82
  globalStorage;
77
83
  initPromise;
78
84
  isInitialized = false;
85
+ actionBindings = new Map();
86
+ remoteTargets = new Map();
87
+ remoteInvoker;
79
88
  discovery;
80
89
  execution;
81
90
  runs;
@@ -100,6 +109,7 @@ export class DefaultActionDockHost {
100
109
  }
101
110
  this.hostSessionId = randomUUID();
102
111
  this.options = options;
112
+ this.remoteInvoker = new RemoteInvocationExecutor({ logger: options.logger });
103
113
  this.maxCallDepth = options.maxCallDepth ?? 16;
104
114
  this.maxSubRuns = options.maxSubRuns ?? 64;
105
115
  this.policy = new InvocationPolicy({
@@ -348,6 +358,21 @@ export class DefaultActionDockHost {
348
358
  // 持有者在返回前打开共享全局存储,保留对全局库存储错误的提前发现能力
349
359
  this.getGlobalStorage();
350
360
  }
361
+ /** 返回的 Runtime 由 Host 统一接管,端口只清理自身缓存和附属资源。 */
362
+ async closeInvocationTargets() {
363
+ const extras = Array.from(this.extraRuntimes).filter((runtime) => this.runtimes.get(runtime.packageId) !== runtime);
364
+ this.extraRuntimes.clear();
365
+ this.extraRuntimeScopes.clear();
366
+ const results = await Promise.allSettled(extras.map((runtime) => runtime.close()));
367
+ const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
368
+ try {
369
+ await this.options.platform?.invocationTargets?.close?.();
370
+ }
371
+ catch (error) {
372
+ errors.push(error);
373
+ }
374
+ return errors;
375
+ }
351
376
  /**
352
377
  * 异步安全回滚初始化失败时所占用的资源。
353
378
  */
@@ -363,7 +388,11 @@ export class DefaultActionDockHost {
363
388
  // 忽略 Runtime 关闭异常
364
389
  }
365
390
  }));
391
+ const invocationCloseErrors = await this.closeInvocationTargets();
366
392
  this.runtimes.clear();
393
+ this.actionBindings.clear();
394
+ this.remoteTargets.clear();
395
+ await this.remoteInvoker.close();
367
396
  try {
368
397
  this.globalStorage?.close();
369
398
  }
@@ -379,6 +408,8 @@ export class DefaultActionDockHost {
379
408
  }
380
409
  this.dataDirLock = undefined;
381
410
  this.isClosed = true;
411
+ if (invocationCloseErrors.length)
412
+ throw new AggregateError(invocationCloseErrors, "Invocation target cleanup failed");
382
413
  }
383
414
  /**
384
415
  * 阶段函数:注册显式传入的 packages 列表(PackageRuntimeOptions 配置或包目录物理路径)。
@@ -551,21 +582,75 @@ export class DefaultActionDockHost {
551
582
  * 铁律 2:跨包调用改走 ActionInvoker 回到 Host 执行主链,经由唯一的 InvocationPolicy 授权后调用目标 Package 的 ExecutionService。
552
583
  */
553
584
  createActionInvoker(callerRuntime) {
554
- return async (childAction, childInput, context) => {
555
- const resolved = resolveAction(childAction, {
585
+ return async (childAction, childInput, context, options) => {
586
+ if (this.isClosed)
587
+ throw new ActionDockError(SERVICE_CLOSED, "ActionDockHost is closed");
588
+ if (context.signal.aborted)
589
+ throw new ActionDockError(ACTION_CANCELLED, "Caller invocation is cancelled");
590
+ const parsed = parseActionRef(childAction);
591
+ // 裸引用仍由既有解析器消歧;远端专属依赖须使用完全限定引用。
592
+ const localResolved = parsed.packageId ? undefined : resolveAction(childAction, {
556
593
  caller: callerRuntime.packageId,
557
594
  graph: this.graph,
558
595
  catalog: this.catalog,
559
596
  });
560
- const targetPackageId = resolved.package.id;
561
- const targetActionId = resolved.ref.actionId;
562
- const isSamePackage = targetPackageId === callerRuntime.packageId;
563
- const targetRuntime = isSamePackage ? callerRuntime : this.getRuntime(targetPackageId);
564
- if (!targetRuntime) {
565
- throw new ActionDockError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
597
+ const targetPackageId = parsed.packageId ?? localResolved.package.id;
598
+ const targetActionId = parsed.actionId;
599
+ const canonicalRef = `${targetPackageId}/${targetActionId}`;
600
+ const canonicalActionRef = { packageId: targetPackageId, actionId: targetActionId };
601
+ const callerBindings = this.actionBindings.get(callerRuntime.packageId) ?? {};
602
+ const isNamedTarget = Boolean(options?.binding && options.binding !== "default");
603
+ // 目标装配是同步解析,不在此引入另一条执行或网络调用链。
604
+ const targetOverride = this.options.platform?.invocationTargets?.resolve(canonicalActionRef, options, {
605
+ packageId: callerRuntime.packageId,
606
+ actionId: context.caller?.actionId,
607
+ });
608
+ let targetRuntime;
609
+ let targetScope;
610
+ let isRemote = false;
611
+ let remoteProfile;
612
+ if (targetOverride) {
613
+ if (targetOverride.runtime.packageId !== targetPackageId) {
614
+ throw new ActionDockError(INVALID_ACTION_REF, `Invocation target packageId mismatch: expected '${targetPackageId}', got '${targetOverride.runtime.packageId}'`);
615
+ }
616
+ targetRuntime = targetOverride.runtime;
617
+ this.extraRuntimes.add(targetRuntime);
618
+ this.bindRuntime(targetRuntime);
619
+ targetScope = targetOverride.scope ?? (isNamedTarget ? `binding:${options?.binding}` : undefined);
620
+ if (targetScope)
621
+ this.extraRuntimeScopes.set(targetRuntime, targetScope);
622
+ }
623
+ else {
624
+ // 没有绑定的旧调用不引入额外的清单命名约束,保持编程式 Runtime 兼容。
625
+ const binding = options?.binding !== undefined || Object.hasOwn(callerBindings, canonicalRef)
626
+ ? resolveActionBinding(callerBindings, canonicalRef, options?.binding)
627
+ : undefined;
628
+ if (binding?.type === "remote") {
629
+ isRemote = true;
630
+ remoteProfile = binding.profile;
631
+ targetScope = `profile:${binding.profile}`;
632
+ }
633
+ else {
634
+ isRemote = false;
635
+ if (!localResolved) {
636
+ resolveAction(childAction, { caller: callerRuntime.packageId, graph: this.graph, catalog: this.catalog });
637
+ }
638
+ const isLogicalSamePackage = targetPackageId === callerRuntime.packageId;
639
+ targetRuntime = isLogicalSamePackage ? callerRuntime : this.getRuntime(targetPackageId);
640
+ if (targetRuntime)
641
+ targetScope = this.extraRuntimeScopes.get(targetRuntime);
642
+ if (!targetRuntime) {
643
+ throw new ActionDockError(PACKAGE_NOT_FOUND, packageNotFoundMessage(targetPackageId, this.failedLinkedPackages));
644
+ }
645
+ }
566
646
  }
647
+ const isLogicalSamePackage = targetPackageId === callerRuntime.packageId;
648
+ const isPhysicalSameRuntime = Boolean(targetRuntime && targetRuntime === callerRuntime);
649
+ const requiresExplicitUses = Boolean(isRemote ||
650
+ targetOverride?.requiresExplicitUses ||
651
+ (isNamedTarget && targetOverride !== undefined));
567
652
  // 跨包 uses 依赖声明校验(统一委托 InvocationPolicy 单一事实源)
568
- if (!isSamePackage) {
653
+ if (!isLogicalSamePackage || requiresExplicitUses) {
569
654
  let declaredUses = context.caller?.declaredUses;
570
655
  if (!declaredUses) {
571
656
  try {
@@ -580,7 +665,11 @@ export class DefaultActionDockHost {
580
665
  packageId: callerRuntime.packageId,
581
666
  actionId: context.caller?.actionId || "",
582
667
  declaredUses,
583
- }, { packageId: targetPackageId, actionId: targetActionId }, this.graph);
668
+ }, {
669
+ packageId: targetPackageId,
670
+ actionId: targetActionId,
671
+ requiresExplicitUses,
672
+ }, this.graph);
584
673
  if (authErr) {
585
674
  const hint = authErr.details?.hint;
586
675
  const msg = hint ? `${authErr.message}\n${hint}` : authErr.message;
@@ -593,7 +682,7 @@ export class DefaultActionDockHost {
593
682
  throw new ActionDockError(depthErr.code, depthErr.message, depthErr.details);
594
683
  }
595
684
  // 调用链环路死锁检测
596
- const cycle = this.policy.checkCycle(context.callStack, targetActionId, targetPackageId, callerRuntime.packageId);
685
+ const cycle = this.policy.checkCycle(context.callStack, targetActionId, targetPackageId, callerRuntime.packageId, targetScope);
597
686
  if (cycle.error) {
598
687
  throw new ActionDockError(cycle.error.code, cycle.error.message, cycle.error.details);
599
688
  }
@@ -608,14 +697,18 @@ export class DefaultActionDockHost {
608
697
  }
609
698
  try {
610
699
  const nextCallStack = [...context.callStack, cycle.callKey];
700
+ if (isRemote) {
701
+ const candidate = this.catalog.find({ packageId: targetPackageId, actionId: targetActionId })[0];
702
+ return await this.remoteInvoker.invoke(this.remoteTargets.get(remoteProfile), { packageId: targetPackageId, actionId: targetActionId }, childInput, { ...context, callStack: nextCallStack }, { binding: options?.binding ?? "default", profile: remoteProfile }, candidate?.contract);
703
+ }
611
704
  const targetOwner = context.owner
612
705
  ? {
613
706
  tenantId: context.owner.tenantId,
614
707
  principalId: context.owner.principalId,
615
- packageInstanceId: isSamePackage
708
+ packageInstanceId: isPhysicalSameRuntime
616
709
  ? (context.owner.packageInstanceId || callerRuntime.identity.instanceId)
617
710
  : targetRuntime.identity.instanceId,
618
- generationId: isSamePackage
711
+ generationId: isPhysicalSameRuntime
619
712
  ? (context.owner.generationId || callerRuntime.identity.generation)
620
713
  : targetRuntime.identity.generation,
621
714
  }
@@ -626,6 +719,7 @@ export class DefaultActionDockHost {
626
719
  parentRunId: context.parentRunId,
627
720
  caller: context.caller,
628
721
  callStack: nextCallStack,
722
+ targetScope,
629
723
  package: targetRuntime.identity,
630
724
  signal: context.signal,
631
725
  timeoutMs: context.timeoutMs,
@@ -699,6 +793,27 @@ export class DefaultActionDockHost {
699
793
  }
700
794
  throw new ActionDockError(PACKAGE_ID_CONFLICT, `Package ID conflict: package '${runtime.packageId}' is already registered in host`);
701
795
  }
796
+ const explicitBindings = this.options.actionBindings;
797
+ const bindings = explicitBindings && Object.hasOwn(explicitBindings, runtime.packageId)
798
+ ? validateActionBindings(explicitBindings[runtime.packageId])
799
+ : runtime.packageRoot ? loadActionBindings(runtime.packageRoot) : validateActionBindings({});
800
+ for (const group of Object.values(bindings)) {
801
+ for (const target of Object.values(group)) {
802
+ if (target.type !== "remote" || this.remoteTargets.has(target.profile))
803
+ continue;
804
+ const resolved = resolveTarget({ profile: target.profile }, this.options.customHome);
805
+ if (resolved.type !== "remote" || !resolved.serverUrl) {
806
+ throw new ActionDockError(INVALID_ACTION_REF, `Binding profile '${target.profile}' must identify a remote service`);
807
+ }
808
+ this.remoteTargets.set(target.profile, Object.freeze({
809
+ serverUrl: resolved.serverUrl,
810
+ token: resolved.token,
811
+ insecure: resolved.insecure,
812
+ allowInsecureHttp: resolved.allowInsecureHttp,
813
+ }));
814
+ }
815
+ }
816
+ this.actionBindings.set(runtime.packageId, bindings);
702
817
  this.runtimes.set(runtime.packageId, runtime);
703
818
  if (isPublic) {
704
819
  this.hostPublicPackageIds.add(runtime.packageId);
@@ -1241,17 +1356,26 @@ export class DefaultActionDockHost {
1241
1356
  }
1242
1357
  return this.globalStorage;
1243
1358
  }
1244
- findDeclaredConfigItem(key) {
1245
- let foundItem;
1359
+ findAllDeclaredConfigItems(key) {
1360
+ const items = [];
1246
1361
  for (const runtime of this.listRuntimes()) {
1247
- const item = runtime.projectConfig?.config?.[key];
1248
- if (item) {
1249
- if (item.secret)
1250
- return item;
1251
- foundItem = item;
1362
+ const decl = resolveConfigDeclaration(runtime.projectConfig?.config, key);
1363
+ if (decl?.item) {
1364
+ items.push(decl.item);
1252
1365
  }
1253
1366
  }
1254
- return foundItem;
1367
+ return items;
1368
+ }
1369
+ protectGlobalConfigView(view) {
1370
+ if (view.source !== "global")
1371
+ return view;
1372
+ const items = this.findAllDeclaredConfigItems(view.key);
1373
+ if (items.length === 0)
1374
+ return view;
1375
+ // 外部注入的运行时可能持有独立全局库;只能加强它返回的安全值,不能换读 Host 的库。
1376
+ const merged = redactConfigValueForDeclarations(view.value, items);
1377
+ const safe = redactConfigValueAtPaths(merged.value, [...view.redactedPaths, ...merged.redactedPaths]);
1378
+ return { ...view, ...safe, secret: view.secret || merged.secret };
1255
1379
  }
1256
1380
  resolveRuntime(packageId) {
1257
1381
  if (packageId) {
@@ -1272,25 +1396,32 @@ export class DefaultActionDockHost {
1272
1396
  }
1273
1397
  async getConfig(packageId, key) {
1274
1398
  await this.ensureInitialized();
1399
+ assertConcreteConfigKey(key);
1275
1400
  if (packageId === "global") {
1276
1401
  const globalStorage = this.getGlobalStorage();
1277
1402
  const val = globalStorage.getConfig(key);
1278
1403
  const configured = val !== undefined;
1279
- const declaredItem = this.findDeclaredConfigItem(key);
1280
- const isSecret = isSecretConfigKey(key, declaredItem);
1404
+ const items = this.findAllDeclaredConfigItems(key);
1405
+ const redacted = redactConfigValueForDeclarations(val, items);
1281
1406
  return {
1282
1407
  key,
1283
1408
  configured,
1284
- secret: isSecret,
1285
- source: configured ? "global" : "default",
1286
- value: !isSecret && configured ? val : undefined,
1409
+ secret: redacted.secret,
1410
+ source: configured ? "global" : null,
1411
+ value: redacted.value,
1412
+ redactedPaths: redacted.redactedPaths,
1287
1413
  };
1288
1414
  }
1289
- return this.requireRuntime(packageId).getConfig(key);
1415
+ return this.protectGlobalConfigView(await this.requireRuntime(packageId).getConfig(key));
1290
1416
  }
1291
1417
  async setConfig(packageId, key, value) {
1292
1418
  await this.ensureInitialized();
1419
+ assertConcreteConfigKey(key);
1293
1420
  if (packageId === "global") {
1421
+ const items = this.findAllDeclaredConfigItems(key);
1422
+ for (const item of items) {
1423
+ validateConfigValue(key, value, item);
1424
+ }
1294
1425
  await this.getGlobalStorage().setConfig(key, value);
1295
1426
  return;
1296
1427
  }
@@ -1298,6 +1429,7 @@ export class DefaultActionDockHost {
1298
1429
  }
1299
1430
  async deleteConfig(packageId, key) {
1300
1431
  await this.ensureInitialized();
1432
+ assertConcreteConfigKey(key);
1301
1433
  if (packageId === "global") {
1302
1434
  return await this.getGlobalStorage().deleteConfig(key);
1303
1435
  }
@@ -1308,19 +1440,26 @@ export class DefaultActionDockHost {
1308
1440
  if (packageId === "global") {
1309
1441
  const globalStorage = this.getGlobalStorage();
1310
1442
  const all = globalStorage.listConfig();
1311
- return Object.entries(all).map(([key, val]) => {
1312
- const declaredItem = this.findDeclaredConfigItem(key);
1313
- const isSecret = isSecretConfigKey(key, declaredItem);
1314
- return {
1443
+ const views = [];
1444
+ for (const [key, val] of Object.entries(all)) {
1445
+ if (isConfigTemplate(key)) {
1446
+ continue;
1447
+ }
1448
+ const items = this.findAllDeclaredConfigItems(key);
1449
+ const redacted = redactConfigValueForDeclarations(val, items);
1450
+ views.push({
1315
1451
  key,
1316
1452
  configured: true,
1317
- secret: isSecret,
1453
+ secret: redacted.secret,
1318
1454
  source: "global",
1319
- value: isSecret ? undefined : val,
1320
- };
1321
- });
1455
+ value: redacted.value,
1456
+ redactedPaths: redacted.redactedPaths,
1457
+ });
1458
+ }
1459
+ return views;
1322
1460
  }
1323
- return this.requireRuntime(packageId).listConfig();
1461
+ const views = await this.requireRuntime(packageId).listConfig();
1462
+ return views.map((view) => this.protectGlobalConfigView(view));
1324
1463
  }
1325
1464
  async getState(packageId, actionId, key, options) {
1326
1465
  await this.ensureInitialized();
@@ -1377,7 +1516,11 @@ export class DefaultActionDockHost {
1377
1516
  // 忽略 Runtime 关闭异常,确保全部安全释放
1378
1517
  }
1379
1518
  }));
1519
+ const invocationCloseErrors = await this.closeInvocationTargets();
1380
1520
  this.runtimes.clear();
1521
+ this.actionBindings.clear();
1522
+ this.remoteTargets.clear();
1523
+ await this.remoteInvoker.close();
1381
1524
  try {
1382
1525
  this.globalStorage?.close();
1383
1526
  }
@@ -1392,6 +1535,8 @@ export class DefaultActionDockHost {
1392
1535
  // 忽略目录排他锁释放异常
1393
1536
  }
1394
1537
  this.dataDirLock = undefined;
1538
+ if (invocationCloseErrors.length)
1539
+ throw new AggregateError(invocationCloseErrors, "Invocation target cleanup failed");
1395
1540
  }
1396
1541
  }
1397
1542
  /**
@@ -7,6 +7,7 @@ import type { EventSink } from "../runtime/events.js";
7
7
  import type { RuntimePlatform } from "../platform/types.js";
8
8
  import type { ActionDockService, ConfigValueView, EventsPort, ListRunsOptions, StateScopeOptions } from "../service/types.js";
9
9
  import type { StateEntry } from "../storage/types.js";
10
+ import type { ActionBindings } from "../invocation/bindings.js";
10
11
  /**
11
12
  * 宿主事件服务端口(既作为独立方法调用,又满足 EventsPort 规范)。
12
13
  */
@@ -21,6 +22,8 @@ export interface HostEventsPort extends EventsPort {
21
22
  * ActionDock 宿主容器初始化配置选项。
22
23
  */
23
24
  export interface ActionDockHostOptions {
25
+ /** 按调用方包覆盖运行时命名绑定;未覆盖的包读取其 .actiondock/bindings.json。 */
26
+ actionBindings?: Readonly<Record<string, ActionBindings>>;
24
27
  /** 显式预注册的包列表(PackageRuntime 实例、PackageRuntimeOptions 配置或包目录物理路径)。Host 统一管理其生命周期,关闭时统一释放。 */
25
28
  packages?: Array<PackageRuntime | PackageRuntimeOptions | string>;
26
29
  /** 当前工程根目录绝对物理路径 */
package/dist/index.d.ts CHANGED
@@ -7,15 +7,16 @@
7
7
  */
8
8
  export { ACTIONDOCK_VERSION } from "./version.js";
9
9
  export { createActionDock, connectActionDock, } from "./service/factory.js";
10
- export type { ActionDockService, ActionDockService as ActionDock, DiscoveryPort, ExecutionPort, RunsPort, EventsPort, RunEventSubscriptionOptions, ConfigPort, StatePort, CreateActionDockOptions, ConnectActionDockOptions, RemoteServiceOptions, ConfigValueView, ListRunsOptions, RunReadOptions, StateScopeOptions, } from "./service/types.js";
10
+ export type { ActionDockService, ActionDockService as ActionDock, DiscoveryPort, ExecutionPort, RunsPort, EventsPort, RunEventSubscriptionOptions, ConfigPort, StatePort, CreateActionDockOptions, ConnectActionDockOptions, RemoteServiceOptions, ConfigValueView, ConfigSource, ListRunsOptions, RunReadOptions, StateScopeOptions, } from "./service/types.js";
11
11
  export { isTerminalRunStatus, DEFAULT_RUNS_RETENTION_MS, DEFAULT_MAX_RUNS_PER_PACKAGE, DEFAULT_MIN_RETAIN_RUNS, } from "./storage/types.js";
12
12
  export { isRunHostDead } from "./storage/run-liveness.js";
13
13
  export type { TerminalRunStatus } from "./storage/types.js";
14
14
  export { parseDuration } from "./utils/index.js";
15
15
  export { toSnakeUpperCase, resolveEnvValue } from "./runtime/env.js";
16
16
  export type { ResolvedEnv } from "./runtime/env.js";
17
- export type { ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, RunOrigin, ExecutionEvent, } from "@actiondock/sdk";
17
+ export type { ActionInvokeOptions, ActionRef, ResolvedActionRef, ExecutionResult, RunRecord, RunOrigin, ExecutionEvent, } from "@actiondock/sdk";
18
18
  export type { RunOptions, ExecutionTicket, CancelResult, } from "./execution/types.js";
19
+ export type { ActionBindings, ActionBindingTarget } from "./invocation/bindings.js";
19
20
  export type { ActionSpec, ActionSummary, PlaybookSpec, PlaybookSummary, PackageInfo, ListActionsOptions, } from "./package/types.js";
20
21
  export { createNodePlatform, } from "./platform/node.js";
21
22
  export { startActionDockServer, } from "./server/server.js";
@@ -24,4 +25,4 @@ export { initProject } from "./project/init.js";
24
25
  export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
25
26
  export type { ProjectConfig, } from "./project/types.js";
26
27
  export type { PackageGraph, } from "./catalog/graph.js";
27
- export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
28
+ export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, CONFIG_VALUE_INVALID, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
package/dist/index.js CHANGED
@@ -20,4 +20,4 @@ export { startActionDockServer, } from "./server/server.js";
20
20
  export { initProject } from "./project/init.js";
21
21
  export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
22
22
  // 8. 统一错误模型与核心标准错误码常量
23
- export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
23
+ export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, CONFIG_VALUE_INVALID, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
@@ -0,0 +1,5 @@
1
+ /** 配置赋值只识别等号左侧的 :=,不物化路径,也不把解析失败回退为字符串。 */
2
+ export declare function parseConfigOverrideAssignment(pair: string): {
3
+ key: string;
4
+ value: unknown;
5
+ };
@@ -0,0 +1,23 @@
1
+ import { ActionDockError, INVALID_ARGUMENT } from "../errors.js";
2
+ import { assertConcreteConfigKey } from "../project/config-declarations.js";
3
+ import { parseJson } from "./input-resolver.js";
4
+ /** 配置赋值只识别等号左侧的 :=,不物化路径,也不把解析失败回退为字符串。 */
5
+ export function parseConfigOverrideAssignment(pair) {
6
+ const equals = pair.indexOf("=");
7
+ if (equals < 0) {
8
+ throw new ActionDockError(INVALID_ARGUMENT, "Expected configuration assignment 'KEY=value' or 'KEY:=json'");
9
+ }
10
+ const json = equals > 0 && pair[equals - 1] === ":";
11
+ const key = pair.slice(0, json ? equals - 1 : equals);
12
+ assertConcreteConfigKey(key);
13
+ const rawValue = pair.slice(equals + 1);
14
+ if (!json)
15
+ return { key, value: rawValue };
16
+ try {
17
+ return { key, value: parseJson(rawValue, "configuration override") };
18
+ }
19
+ catch {
20
+ // JSON.parse 和通用输入校验的诊断可能含输入片段,不能直接回显或挂载 cause。
21
+ throw new ActionDockError(INVALID_ARGUMENT, `Invalid JSON configuration override for '${key}'`);
22
+ }
23
+ }
@@ -16,7 +16,7 @@ export interface InputResolutionPolicy {
16
16
  sanitizeInputErrors?: boolean;
17
17
  /** 是否仅允许字节流输入(默认 false) */
18
18
  byteStreamOnly?: boolean;
19
- /** 原始 stdin 文本模式:保留开头 BOM,UTF-8 编码失败视为读取失败(默认 false,仅作用于 stdinField) */
19
+ /** 原始 stdin 文本模式:保留开头 BOM,UTF-8 编码失败视为读取失败(stdinField 默认启用) */
20
20
  stdinRawText?: boolean;
21
21
  }
22
22
  /**
@@ -26,19 +26,21 @@ export interface InputResolutionPolicy {
26
26
  */
27
27
  export declare function readStdin(stream?: NodeJS.ReadableStream): Promise<string>;
28
28
  /**
29
- * Action 执行输入解析选项。
29
+ * 通用 JSON 值输入解析选项;扁平参数与字段绑定产生对象,完整 JSON 可以是任意合法值。
30
30
  */
31
- export interface ResolveActionInputOptions {
31
+ export interface ResolveInputOptions {
32
32
  input?: string;
33
33
  inputFile?: string;
34
34
  flatArgs?: string[];
35
- /** 将 stdin 完整原始文本绑定为指定顶层入参字段的字符串值(与 input / inputFile 互斥,可与 flatArgs 组合) */
35
+ /** 将 stdin 完整原始文本绑定为指定顶层字段的字符串值(与 input / inputFile 互斥,可与 flatArgs 组合) */
36
36
  stdinField?: string;
37
37
  stdin?: NodeJS.ReadableStream;
38
38
  flatOptions?: FlatParserOptions & FlatMaterializerOptions;
39
39
  policy?: InputResolutionPolicy;
40
40
  signal?: AbortSignal;
41
41
  }
42
+ /** Action 输入沿用通用解码选项,Action 专属契约校验由执行边界负责。 */
43
+ export type ResolveActionInputOptions = ResolveInputOptions;
42
44
  /**
43
45
  * 解析完整 JSON 文档字符串。
44
46
  * 若解析失败抛出 INVALID_JSON 异常。
@@ -49,7 +51,12 @@ export interface ResolveActionInputOptions {
49
51
  */
50
52
  export declare function parseJson(text: string, source: string, policy?: InputResolutionPolicy): JsonValue;
51
53
  /**
52
- * 统一解析 Action 执行入参。
54
+ * 解析一个完整值:合法 JSON 解码为对应类型,非 JSON 文本保留为字符串。
55
+ * 只有语法错误允许回退;大小、深度、有限数和危险属性校验失败必须透传。
56
+ */
57
+ export declare function parseValueLiteral(text: string, policy?: InputResolutionPolicy): JsonValue;
58
+ /**
59
+ * 统一解析 JSON 值及结构化输入,不承担 Action、配置或状态的业务校验。
53
60
  *
54
61
  * 输入模式仲裁规则(Input Mode Arbitration):
55
62
  * - effectiveFlat: flatArgs 存在且 length > 0
@@ -66,6 +73,16 @@ export declare function parseJson(text: string, source: string, policy?: InputRe
66
73
  * - stdinField 的 stdin 读取为原始文本绑定:不做 JSON 解析、不剥 BOM、保留首尾空白,空 stdin 得到空字符串。
67
74
  *
68
75
  * @param options 输入解析选项
69
- * @returns 解析后的 JSON 对象
76
+ * @returns 解析后的 JSON 值
70
77
  */
71
- export declare function resolveActionInput(options: ResolveActionInputOptions): Promise<JsonValue>;
78
+ export declare function resolveInput(options: ResolveInputOptions): Promise<JsonValue>;
79
+ /** Action 入参复用同一解码器,避免各命令的扁平路径与 stdin 绑定语义漂移。 */
80
+ export declare const resolveActionInput: typeof resolveInput;
81
+ /** 完整值写入的输入选项;业务层仍负责声明校验与整键替换。 */
82
+ export interface ResolveValueInputOptions extends Pick<ResolveInputOptions, "flatArgs" | "stdinField" | "stdin" | "signal" | "policy" | "flatOptions"> {
83
+ value?: string;
84
+ fromStdin?: boolean;
85
+ }
86
+ export declare function hasValueInput(input: ResolveValueInputOptions): boolean;
87
+ /** 先仲裁来源再读流,配置、状态及独立运行入口使用同一完整值解码规则。 */
88
+ export declare function resolveValueInput(options: ResolveValueInputOptions): Promise<JsonValue>;