@actiondock/core 2.18.0 → 2.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) 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/client/runs.d.ts +9 -0
  6. package/dist/client/runs.js +7 -0
  7. package/dist/errors.d.ts +3 -1
  8. package/dist/errors.js +2 -0
  9. package/dist/execution/service.js +8 -5
  10. package/dist/execution/types.d.ts +3 -3
  11. package/dist/host/host.d.ts +9 -1
  12. package/dist/host/host.js +266 -45
  13. package/dist/host/types.d.ts +3 -0
  14. package/dist/index.d.ts +4 -3
  15. package/dist/index.js +1 -1
  16. package/dist/input/config-override.d.ts +5 -0
  17. package/dist/input/config-override.js +23 -0
  18. package/dist/input/input-resolver.d.ts +24 -7
  19. package/dist/input/input-resolver.js +73 -5
  20. package/dist/invocation/bindings.d.ts +53 -0
  21. package/dist/invocation/bindings.js +248 -0
  22. package/dist/invocation/policy.d.ts +5 -1
  23. package/dist/invocation/policy.js +16 -9
  24. package/dist/invocation/remote.d.ts +40 -0
  25. package/dist/invocation/remote.js +489 -0
  26. package/dist/invocation/types.d.ts +2 -0
  27. package/dist/invocation/types.js +1 -0
  28. package/dist/package/index.d.ts +1 -1
  29. package/dist/package/runtime.js +105 -15
  30. package/dist/platform/types.d.ts +48 -1
  31. package/dist/project/config-declarations.d.ts +61 -0
  32. package/dist/project/config-declarations.js +297 -0
  33. package/dist/project/index.d.ts +5 -2
  34. package/dist/project/index.js +5 -2
  35. package/dist/project/init.js +2 -2
  36. package/dist/project/loader.js +4 -0
  37. package/dist/project/manifest.js +10 -0
  38. package/dist/project/types.d.ts +30 -0
  39. package/dist/runtime/context.d.ts +6 -3
  40. package/dist/runtime/context.js +60 -13
  41. package/dist/runtime/env.js +15 -1
  42. package/dist/runtime/standalone-commands.d.ts +2 -2
  43. package/dist/runtime/standalone-commands.js +113 -47
  44. package/dist/runtime/standalone-help.d.ts +1 -0
  45. package/dist/runtime/standalone-help.js +15 -9
  46. package/dist/runtime/standalone.js +2 -2
  47. package/dist/server/policy.d.ts +24 -107
  48. package/dist/server/policy.js +134 -291
  49. package/dist/server/routes/actions.js +10 -8
  50. package/dist/server/routes/common.d.ts +5 -20
  51. package/dist/server/routes/common.js +22 -58
  52. package/dist/server/routes/config.js +65 -7
  53. package/dist/server/routes/doctor.d.ts +1 -0
  54. package/dist/server/routes/doctor.js +4 -3
  55. package/dist/server/routes/info.js +100 -13
  56. package/dist/server/routes/playbooks.js +1 -3
  57. package/dist/server/routes/runs.js +112 -18
  58. package/dist/server/server.d.ts +2 -1
  59. package/dist/server/server.js +38 -55
  60. package/dist/server/types.d.ts +6 -6
  61. package/dist/service/factory.js +1 -0
  62. package/dist/service/remote.js +28 -29
  63. package/dist/service/types.d.ts +34 -5
  64. package/dist/service/types.js +8 -0
  65. package/dist/storage/mask.d.ts +33 -6
  66. package/dist/storage/mask.js +298 -16
  67. package/dist/storage/sqlite-runs.d.ts +5 -17
  68. package/dist/storage/sqlite-runs.js +127 -5
  69. package/dist/storage/sqlite.d.ts +5 -17
  70. package/dist/storage/sqlite.js +2 -2
  71. package/dist/storage/types.d.ts +31 -16
  72. package/dist/version.d.ts +1 -1
  73. package/dist/version.js +1 -1
  74. 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);
@@ -906,6 +1021,12 @@ export class DefaultActionDockHost {
906
1021
  const batch = await runtime.listRuns({
907
1022
  actionId: query.actionId,
908
1023
  status: query.status,
1024
+ channel: query.channel,
1025
+ view: query.view,
1026
+ since: query.since,
1027
+ until: query.until,
1028
+ rootRunId: query.rootRunId,
1029
+ parentRunId: query.parentRunId,
909
1030
  requestIds: query.requestIds,
910
1031
  limit: BATCH_SIZE,
911
1032
  offset: batchOffset,
@@ -921,8 +1042,21 @@ export class DefaultActionDockHost {
921
1042
  ...r,
922
1043
  packageId: r.packageId || runtime.packageId,
923
1044
  }));
924
- const filtered = filterByIntent(normalized, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
925
- matchedRecords.push(...filtered);
1045
+ let filtered = normalized;
1046
+ if (query.channel)
1047
+ filtered = filtered.filter((r) => r.origin?.channel === query.channel);
1048
+ if (query.view)
1049
+ filtered = filtered.filter((r) => r.origin?.viewName === query.view);
1050
+ if (query.since)
1051
+ filtered = filtered.filter((r) => r.startedAt >= query.since);
1052
+ if (query.until)
1053
+ filtered = filtered.filter((r) => r.startedAt <= query.until);
1054
+ if (query.rootRunId)
1055
+ filtered = filtered.filter((r) => r.rootRunId === query.rootRunId);
1056
+ if (query.parentRunId)
1057
+ filtered = filtered.filter((r) => r.parentRunId === query.parentRunId);
1058
+ const intentMatched = filterByIntent(filtered, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
1059
+ matchedRecords.push(...intentMatched);
926
1060
  if (matchedRecords.length >= neededCount) {
927
1061
  break;
928
1062
  }
@@ -944,6 +1078,12 @@ export class DefaultActionDockHost {
944
1078
  const batch = await runtime.listRuns({
945
1079
  actionId: query.actionId,
946
1080
  status: query.status,
1081
+ channel: query.channel,
1082
+ view: query.view,
1083
+ since: query.since,
1084
+ until: query.until,
1085
+ rootRunId: query.rootRunId,
1086
+ parentRunId: query.parentRunId,
947
1087
  requestIds: query.requestIds,
948
1088
  limit: BATCH_SIZE,
949
1089
  offset: batchOffset,
@@ -959,8 +1099,21 @@ export class DefaultActionDockHost {
959
1099
  ...r,
960
1100
  packageId: r.packageId || runtime.packageId,
961
1101
  }));
962
- const filtered = filterByIntent(normalized, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
963
- matchedRecords.push(...filtered);
1102
+ let filtered = normalized;
1103
+ if (query.channel)
1104
+ filtered = filtered.filter((r) => r.origin?.channel === query.channel);
1105
+ if (query.view)
1106
+ filtered = filtered.filter((r) => r.origin?.viewName === query.view);
1107
+ if (query.since)
1108
+ filtered = filtered.filter((r) => r.startedAt >= query.since);
1109
+ if (query.until)
1110
+ filtered = filtered.filter((r) => r.startedAt <= query.until);
1111
+ if (query.rootRunId)
1112
+ filtered = filtered.filter((r) => r.rootRunId === query.rootRunId);
1113
+ if (query.parentRunId)
1114
+ filtered = filtered.filter((r) => r.parentRunId === query.parentRunId);
1115
+ const intentMatched = filterByIntent(filtered, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
1116
+ matchedRecords.push(...intentMatched);
964
1117
  if (batch.length < BATCH_SIZE) {
965
1118
  break;
966
1119
  }
@@ -1043,6 +1196,12 @@ export class DefaultActionDockHost {
1043
1196
  const batch = await runtime.listRuns({
1044
1197
  actionId: query.actionId,
1045
1198
  status: query.status,
1199
+ channel: query.channel,
1200
+ view: query.view,
1201
+ since: query.since,
1202
+ until: query.until,
1203
+ rootRunId: query.rootRunId,
1204
+ parentRunId: query.parentRunId,
1046
1205
  requestIds: query.requestIds,
1047
1206
  limit: BATCH_SIZE,
1048
1207
  offset: batchOffset,
@@ -1058,8 +1217,21 @@ export class DefaultActionDockHost {
1058
1217
  ...r,
1059
1218
  packageId: r.packageId || runtime.packageId,
1060
1219
  }));
1061
- const filtered = filterByIntent(normalized, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
1062
- count += filtered.length;
1220
+ let filtered = normalized;
1221
+ if (query.channel)
1222
+ filtered = filtered.filter((r) => r.origin?.channel === query.channel);
1223
+ if (query.view)
1224
+ filtered = filtered.filter((r) => r.origin?.viewName === query.view);
1225
+ if (query.since)
1226
+ filtered = filtered.filter((r) => r.startedAt >= query.since);
1227
+ if (query.until)
1228
+ filtered = filtered.filter((r) => r.startedAt <= query.until);
1229
+ if (query.rootRunId)
1230
+ filtered = filtered.filter((r) => r.rootRunId === query.rootRunId);
1231
+ if (query.parentRunId)
1232
+ filtered = filtered.filter((r) => r.parentRunId === query.parentRunId);
1233
+ const intentMatched = filterByIntent(filtered, intentStr, [(r) => r.id, (r) => r.actionId, (r) => r.status, (r) => r.packageId], false);
1234
+ count += intentMatched.length;
1063
1235
  if (batch.length < BATCH_SIZE) {
1064
1236
  break;
1065
1237
  }
@@ -1079,6 +1251,12 @@ export class DefaultActionDockHost {
1079
1251
  const all = await runtime.listRuns({
1080
1252
  actionId: query?.actionId,
1081
1253
  status: query?.status,
1254
+ channel: query?.channel,
1255
+ view: query?.view,
1256
+ since: query?.since,
1257
+ until: query?.until,
1258
+ rootRunId: query?.rootRunId,
1259
+ parentRunId: query?.parentRunId,
1082
1260
  requestIds: query?.requestIds,
1083
1261
  limit: 100_000,
1084
1262
  signal: effectiveSignal,
@@ -1086,7 +1264,20 @@ export class DefaultActionDockHost {
1086
1264
  if (effectiveSignal?.aborted) {
1087
1265
  throw createAbortError(effectiveSignal.reason);
1088
1266
  }
1089
- total += all.length;
1267
+ let filtered = all;
1268
+ if (query?.channel)
1269
+ filtered = filtered.filter((r) => r.origin?.channel === query.channel);
1270
+ if (query?.view)
1271
+ filtered = filtered.filter((r) => r.origin?.viewName === query.view);
1272
+ if (query?.since)
1273
+ filtered = filtered.filter((r) => r.startedAt >= query.since);
1274
+ if (query?.until)
1275
+ filtered = filtered.filter((r) => r.startedAt <= query.until);
1276
+ if (query?.rootRunId)
1277
+ filtered = filtered.filter((r) => r.rootRunId === query.rootRunId);
1278
+ if (query?.parentRunId)
1279
+ filtered = filtered.filter((r) => r.parentRunId === query.parentRunId);
1280
+ total += filtered.length;
1090
1281
  }
1091
1282
  }
1092
1283
  return total;
@@ -1165,17 +1356,26 @@ export class DefaultActionDockHost {
1165
1356
  }
1166
1357
  return this.globalStorage;
1167
1358
  }
1168
- findDeclaredConfigItem(key) {
1169
- let foundItem;
1359
+ findAllDeclaredConfigItems(key) {
1360
+ const items = [];
1170
1361
  for (const runtime of this.listRuntimes()) {
1171
- const item = runtime.projectConfig?.config?.[key];
1172
- if (item) {
1173
- if (item.secret)
1174
- return item;
1175
- foundItem = item;
1362
+ const decl = resolveConfigDeclaration(runtime.projectConfig?.config, key);
1363
+ if (decl?.item) {
1364
+ items.push(decl.item);
1176
1365
  }
1177
1366
  }
1178
- 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 };
1179
1379
  }
1180
1380
  resolveRuntime(packageId) {
1181
1381
  if (packageId) {
@@ -1196,25 +1396,32 @@ export class DefaultActionDockHost {
1196
1396
  }
1197
1397
  async getConfig(packageId, key) {
1198
1398
  await this.ensureInitialized();
1399
+ assertConcreteConfigKey(key);
1199
1400
  if (packageId === "global") {
1200
1401
  const globalStorage = this.getGlobalStorage();
1201
1402
  const val = globalStorage.getConfig(key);
1202
1403
  const configured = val !== undefined;
1203
- const declaredItem = this.findDeclaredConfigItem(key);
1204
- const isSecret = isSecretConfigKey(key, declaredItem);
1404
+ const items = this.findAllDeclaredConfigItems(key);
1405
+ const redacted = redactConfigValueForDeclarations(val, items);
1205
1406
  return {
1206
1407
  key,
1207
1408
  configured,
1208
- secret: isSecret,
1209
- source: configured ? "global" : "default",
1210
- value: !isSecret && configured ? val : undefined,
1409
+ secret: redacted.secret,
1410
+ source: configured ? "global" : null,
1411
+ value: redacted.value,
1412
+ redactedPaths: redacted.redactedPaths,
1211
1413
  };
1212
1414
  }
1213
- return this.requireRuntime(packageId).getConfig(key);
1415
+ return this.protectGlobalConfigView(await this.requireRuntime(packageId).getConfig(key));
1214
1416
  }
1215
1417
  async setConfig(packageId, key, value) {
1216
1418
  await this.ensureInitialized();
1419
+ assertConcreteConfigKey(key);
1217
1420
  if (packageId === "global") {
1421
+ const items = this.findAllDeclaredConfigItems(key);
1422
+ for (const item of items) {
1423
+ validateConfigValue(key, value, item);
1424
+ }
1218
1425
  await this.getGlobalStorage().setConfig(key, value);
1219
1426
  return;
1220
1427
  }
@@ -1222,6 +1429,7 @@ export class DefaultActionDockHost {
1222
1429
  }
1223
1430
  async deleteConfig(packageId, key) {
1224
1431
  await this.ensureInitialized();
1432
+ assertConcreteConfigKey(key);
1225
1433
  if (packageId === "global") {
1226
1434
  return await this.getGlobalStorage().deleteConfig(key);
1227
1435
  }
@@ -1232,19 +1440,26 @@ export class DefaultActionDockHost {
1232
1440
  if (packageId === "global") {
1233
1441
  const globalStorage = this.getGlobalStorage();
1234
1442
  const all = globalStorage.listConfig();
1235
- return Object.entries(all).map(([key, val]) => {
1236
- const declaredItem = this.findDeclaredConfigItem(key);
1237
- const isSecret = isSecretConfigKey(key, declaredItem);
1238
- 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({
1239
1451
  key,
1240
1452
  configured: true,
1241
- secret: isSecret,
1453
+ secret: redacted.secret,
1242
1454
  source: "global",
1243
- value: isSecret ? undefined : val,
1244
- };
1245
- });
1455
+ value: redacted.value,
1456
+ redactedPaths: redacted.redactedPaths,
1457
+ });
1458
+ }
1459
+ return views;
1246
1460
  }
1247
- return this.requireRuntime(packageId).listConfig();
1461
+ const views = await this.requireRuntime(packageId).listConfig();
1462
+ return views.map((view) => this.protectGlobalConfigView(view));
1248
1463
  }
1249
1464
  async getState(packageId, actionId, key, options) {
1250
1465
  await this.ensureInitialized();
@@ -1301,7 +1516,11 @@ export class DefaultActionDockHost {
1301
1516
  // 忽略 Runtime 关闭异常,确保全部安全释放
1302
1517
  }
1303
1518
  }));
1519
+ const invocationCloseErrors = await this.closeInvocationTargets();
1304
1520
  this.runtimes.clear();
1521
+ this.actionBindings.clear();
1522
+ this.remoteTargets.clear();
1523
+ await this.remoteInvoker.close();
1305
1524
  try {
1306
1525
  this.globalStorage?.close();
1307
1526
  }
@@ -1316,6 +1535,8 @@ export class DefaultActionDockHost {
1316
1535
  // 忽略目录排他锁释放异常
1317
1536
  }
1318
1537
  this.dataDirLock = undefined;
1538
+ if (invocationCloseErrors.length)
1539
+ throw new AggregateError(invocationCloseErrors, "Invocation target cleanup failed");
1319
1540
  }
1320
1541
  }
1321
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
+ }