@qlzt/dsh-subagent 0.2.1-alpha.3 → 0.2.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -29,6 +29,9 @@
29
29
  /deepseek-ai-dsh-subagent/use-this-package/local-and-external-children:
30
30
  en: 013644290e74493e
31
31
  zh: 05014c3c3773edeb
32
+ /deepseek-ai-dsh-subagent/use-this-package/child-presets:
33
+ en: a740e5e9bdd661d9
34
+ zh: 712a5458fc8b3615
32
35
  /deepseek-ai-dsh-subagent/use-this-package/messaging-interrupting-and-discovering:
33
36
  en: 050177fbbaade432
34
37
  zh: 99f2b9f98088314d
@@ -48,8 +51,11 @@
48
51
  en: f78c5e931f3a3676
49
52
  zh: 3c63f637dbaa8101
50
53
  /deepseek-ai-dsh-subagent/understand-the-implementation/activation-settlement:
51
- en: 2ce43d94b6f7025b
52
- zh: 970f35558aa1fab5
54
+ en: 3cac7a3d58cda7ea
55
+ zh: 74e3234e5101bd1d
56
+ /deepseek-ai-dsh-subagent/understand-the-implementation/restoring-child-composition:
57
+ en: d338e68edd438f57
58
+ zh: 0496090cc567b19d
53
59
  /deepseek-ai-dsh-subagent/understand-the-implementation/ownership-and-invariants:
54
60
  en: b74886a091a327d4
55
61
  zh: 2124546878ba1aa2
package/README.md CHANGED
@@ -60,6 +60,10 @@ At capacity, creation or cold resume rejects with `ACTIVATION_LIMIT_REACHED` (br
60
60
 
61
61
  `startActivation({ provider, label, request, signal, delivery })` returns `{ childId, messageId?, result, dispose }`. Supplying `childId` selects a local backend and guarantees `messageId`. Local children accept later messages and can cold-resume; external children expose one execution and reject continuation. `request.agentOptions` overrides supported child model settings. `delivery: 'parent'` supplies the model-facing completion notice; `delivery: 'caller'` lets workflows await the result without a parent notice or initial return guidance. Local parent delivery requires persistence; caller delivery can use an ephemeral local session. Cold resume requires persistence and Session query.
62
62
 
63
+ ### Child presets
64
+
65
+ In-process spawn accepts an independent `agentPreset`; fork and external backends reject this option. Without a selection, a local child joins the parent's current preset revision. An explicit id mounts the currently available revision, even when that id matches the parent's. Configure model choices through [`dsh-tool-subagent`](../tool-subagent/README.md#selecting-a-child-preset) and persona and tools in the [preset composition](../../preset/agent-preset-registry/README.md). Cold resume preserves persona and tool restrictions recorded in historical child descriptors.
66
+
63
67
  ### Messaging, interrupting, and discovering
64
68
 
65
69
  Every exact live Agent can use `sendMessage()` with a direct continuable child; a resident continuable child can also use it with its direct parent. A working target receives the Agent message through Steer at its nearest step; an idle target starts a turn, and only a direct child can be cold-resumed. The parent can also interrupt a running descendant or list its children at any time. A browser continuation prompt independently selects Queue or Steer and may carry image parts: the Host admits and persists each image batch through the attachment store before the child inbox accepts the message, and refuses delivery when the child's declared model does not accept image input. Direct-child discovery reads the parent-owned `subagentCatalog` projection. `listChildren(parentSessionId, signal?)` owns a live-preferred Session observation and returns the catalog asynchronously without reading child logs. It forwards cancellation and releases the observation after materialization. Materialization preserves parent event order in O(D) time for D facts. External entries are leaves and require no child Session observation. Descendant discovery recursively reads local child catalogs in parent event order, observing each reachable Session once. It skips branches whose catalogs cannot be read and returns diagnostics for them; neither path loads or resumes a child Agent.
@@ -118,6 +122,13 @@ A local Agent failure without a subsequently committed `turn/end` settles as `er
118
122
 
119
123
  Successful local creation appends a `subagent/catalog` fact to the parent Session regardless of result delivery. External creation appends a catalog fact only with `delivery: 'parent'`; caller-owned external executions retain their caller's membership records. External entries use `mode: 'external'` to identify children without a local Session. Catalog entries record membership once at creation and carry no execution status. Complete results go to the caller or the parent completion notice. Projections exclude inherited facts. Direct lists read one parent projection, so completed local workflow children remain discoverable and can be opened or continued. Catalog payload v0 records local modes; v1 also accepts unknown mode; v2 records external executions. Readers support all three versions. Historical migration appends a v1 `subagent/catalog` from a readable child header when its descriptor is unavailable; local creation retains v0. Its `mode: 'unknown'` projection keeps the child visible without claiming continuation support; an existing complete entry remains authoritative.
120
124
 
125
+ <a id="restoring-child-composition"></a>
126
+ ### Restoring child composition
127
+
128
+ An independently selected spawn records `agent-preset/selected` before its first own `subagent/descriptor`. Cold resume excludes the inherited event prefix and checks that ordering, then reads the preset id from the existing `agentPreset` projection. It mounts that id without requiring the original subagent provider to remain registered. A missing or broken selected preset rejects restoration; it does not fall back to the parent or deployment default.
129
+
130
+ Logs without that selection before the first own descriptor, including historical spawn and fork logs, retain `composeFrom` of the parent's current composition. A header preset id, an inherited selection, or a selection after the descriptor does not opt a child into independent restoration, including a fork with no inherited events. Session and descriptor formats are unchanged. Independent cold resume uses the currently registered preset definition, without preserving an earlier revision across restarts.
131
+
121
132
  ### Ownership and invariants
122
133
 
123
134
  - **The manager owns accepted work** — unpublished failures roll back; published handles cancel their exact activation and await child-first disposal.
package/README.zh.md CHANGED
@@ -60,6 +60,10 @@ kind: "package-reference"
60
60
 
61
61
  `startActivation({ provider, label, request, signal, delivery })` 返回 `{ childId, messageId?, result, dispose }`。提供 `childId` 会选择本地后端,并保证返回 `messageId`。本地子级接受后续消息并支持冷恢复;外部子级只暴露一次执行,拒绝继续输入。`request.agentOptions` 覆盖后端支持的子级模型设置。`delivery: 'parent'` 提供面向模型的完成通知;`delivery: 'caller'` 让工作流等待结果,不发送父级通知,也不附加初始返回指导。本地父级投递需要持久化,调用方投递可以使用临时本地会话;冷恢复需要持久化与 Session 查询服务。
62
62
 
63
+ ### 子级 preset
64
+
65
+ 进程内 spawn 接受独立的 `agentPreset`;fork 与外部后端拒绝此选项。省略选择时,本地子级加入父方当前的 preset 代际。显式 id 挂载当前可用代际,即使该 id 与父方相同。通过 [`dsh-tool-subagent`](../tool-subagent/README.zh.md#selecting-a-child-preset) 配置模型可选项,并在 [preset 组装](../../preset/agent-preset-registry/README.zh.md)中配置 persona 与工具。冷恢复保留历史子级 descriptor 记录的 persona 和工具限制。
66
+
63
67
  ### 消息、中断与发现
64
68
 
65
69
  每个确切在线 Agent 都可以对直接可继续 child 使用 `sendMessage()`;驻留的可继续 child 还可以对自己的直接 parent 使用它。正在工作的目标通过 Steer 在最近 step 接收 Agent 消息;空闲目标启动轮次,且只有直接 child 可以冷恢复。parent 也可以随时中断正在运行的后代或列举自己的子级。浏览器发出的继续执行 prompt 会独立选择 Queue 或 Steer,并且可以携带图片部分:Host 先通过附件存储完成整批图片的准入与持久化,子级 inbox 才接受这条消息;当子级声明的模型不接受图片输入时拒绝投递。 直接子级发现读取 parent 自有的 `subagentCatalog` projection。`listChildren(parentSessionId, signal?)` 持有一次优先实时来源的 Session 观察,异步返回目录,不读取子级日志。它转发取消信号,并在物化后释放观察。物化以 O(D) 时间保留 D 条事实的父日志事件顺序。外部条目是叶节点,不需要观察子 Session。后代发现按父事件顺序递归读取子级目录,每个可达 Session 观察一次。无法读取目录的分支会被跳过,并返回诊断;两条路径都不加载或恢复子级 Agent。
@@ -118,6 +122,13 @@ kind: "package-reference"
118
122
 
119
123
  本地创建成功后,无论结果交付给谁,都会向父 Session 追加 `subagent/catalog` 事实。外部创建仅在使用 `delivery: 'parent'` 时追加目录事实;调用方拥有的外部执行保留调用方自己的成员记录。外部条目以 `mode: 'external'` 标记没有本地 Session 的子级。目录仅在创建时记录一次成员关系,不携带执行状态。完整结果交给调用方或父级完成通知。投影排除继承事实。直接列表读取一个父级投影,因此已完成的本地工作流子级仍可被发现、打开或继续。Catalog 载荷 v0 记录本地模式,v1 还接受未知模式,v2 记录外部执行,读取器支持三版。历史迁移在 descriptor 不可用时根据可读子 header 追加 v1 `subagent/catalog`;本地创建保留 v0。其 `mode: 'unknown'` 投影让子会话保持可见,但不表示支持继续执行;已有完整条目仍具有权威性。
120
124
 
125
+ <a id="restoring-child-composition"></a>
126
+ ### 恢复子级组装
127
+
128
+ 独立选择的 spawn 在首条自有 `subagent/descriptor` 之前记录 `agent-preset/selected`。冷恢复排除继承事件前缀并检查此顺序,再从已有 `agentPreset` 投影读取 preset id。恢复挂载该 id,不要求原始 subagent 提供方仍然注册。所选 preset 缺失或损坏时拒绝恢复,不回退到父方或部署默认值。
129
+
130
+ 首条自有 descriptor 前没有该选择的日志,包括历史 spawn 与 fork 日志,继续通过 `composeFrom` 加入父方当前组装。header 中的 preset id、继承的选择或 descriptor 之后的选择,都不会让子级采用独立恢复;没有继承事件的 fork 也遵守此规则。Session 与 descriptor 格式不变。独立冷恢复使用当前注册的 preset 定义,不跨重启保留旧代际。
131
+
121
132
  ### 所有权与不变式
122
133
 
123
134
  - **管理器拥有已接受工作**——未发布的失败会回滚;已发布句柄取消对应 activation,并等待子级优先释放。
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import z from "@deepseek-ai/schemastery";
2
2
  import { scopeTarget } from "@qlzt/dsh-scope";
3
3
  import { ToolArgsError, assertObjectJsonSchema, validateJsonSchemaValue } from "@qlzt/dsh-tools";
4
4
  import { canonicalClientTimeZone } from "@qlzt/dsh-util-time";
5
- import { Remote, RemoteError, TypertRemoteService } from "@qlzt/dsh-typert-protocol";
5
+ import { Remote, RemoteError, TypertRemoteService, principalOf } from "@qlzt/dsh-typert-protocol";
6
6
  import { AttachmentError } from "@qlzt/dsh-attachment";
7
7
  import { z as z$1 } from "zod";
8
8
  import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@qlzt/dsh-llm";
@@ -335,20 +335,17 @@ function resolveChildAgentOptions(parent, requested, childDepth) {
335
335
  * survive persistence, the seed boundary that separates inherited parent
336
336
  * history from child work, and the composition the child runs under.
337
337
  *
338
- * The preset is read from the parent's LIVE scope chain rather than from its
339
- * header, because a parent that switched preset while blank runs on the newer
340
- * composition and its header still names the older one. Recording it is what
341
- * makes a child's history reconstructable: without it a cold read of the child
342
- * resolves the deployment default and rebuilds turns under a tool set the
343
- * child never had.
338
+ * Omitted selection uses the parent's live composition, including any switch
339
+ * made while the parent was blank.
344
340
  * @param parent - the delegating parent agent.
345
341
  * @param childDepth - the resolved delegation depth to persist.
346
342
  * @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
343
+ * @param selectedPreset - independent selection, or omission to inherit the parent.
347
344
  * @returns the `meta` for `ctx.agents.create()`.
348
345
  */
349
- function childSessionMeta(parent, childDepth, isSeeded) {
346
+ function childSessionMeta(parent, childDepth, isSeeded, selectedPreset) {
350
347
  const parentHeader = parent.session.header;
351
- const agentPreset = parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
348
+ const agentPreset = selectedPreset ?? parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
352
349
  return {
353
350
  ...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
354
351
  ...agentPreset === void 0 ? {} : { agentPreset },
@@ -365,29 +362,26 @@ function childSessionMeta(parent, childDepth, isSeeded) {
365
362
  */
366
363
  const SUBAGENT_DELEGATION_CONTEXT = "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it.";
367
364
  /**
368
- * Compose one child inside its creation window: join its parent's preset,
369
- * register the fixed delegation-scope statement, then apply the child's own
370
- * shadowing persona section and tool restriction, all owned by the child's
371
- * scope and therefore invisible to its parent and siblings. Creation and cold
372
- * resume both pass through here.
373
- *
374
- * The join comes first and the child's own registrations second, which is the
375
- * order the layering already implies — the nearest scope wins a name, and a
376
- * per-child restriction intersects with everything its chain admits — but
377
- * stating it here keeps the two steps from being read as independent.
378
- *
379
- * The join and the per-child registrations live in ONE call because a child
380
- * composed without the join is exactly the defect this function exists to
381
- * prevent: with every model-facing row on the agent plane, a child that joins
382
- * no preset sees an empty tool registry and none of its parent's prompt
383
- * sections. Taking the parent as a parameter is what makes that omission
384
- * unrepresentable at the call sites.
365
+ * Compose a child before publication and restore historical descriptor overrides.
366
+ * Explicit selection mounts the named preset; omission synchronously joins the
367
+ * parent's current generation. Registrations belong to the child's scope.
385
368
  * @param childCtx - the child agent's scoped creation context.
386
- * @param parent - the delegating parent whose composition the child joins.
387
- * @param composition - the per-child persona and tool filter to install.
369
+ * @param parent - the delegating parent used when no preset is selected.
370
+ * @param composition - independent preset and any overrides read from a historical descriptor.
371
+ * @returns A promise when mounting an independent preset; otherwise completes synchronously.
388
372
  */
389
373
  function applyChildComposition(childCtx, parent, composition) {
374
+ if (composition.agentPreset !== void 0) {
375
+ const presets = childCtx.get("agentPresets");
376
+ if (presets === void 0) throw new SubagentError("selecting a child agent preset requires the agent-presets service", "PRESET_UNAVAILABLE");
377
+ return presets.mount(childCtx, composition.agentPreset).then(() => {
378
+ applyChildOverrides(childCtx, composition);
379
+ });
380
+ }
390
381
  childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
382
+ applyChildOverrides(childCtx, composition);
383
+ }
384
+ function applyChildOverrides(childCtx, composition) {
391
385
  childCtx.systemPrompt.context({
392
386
  name: "subagent:delegation",
393
387
  order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
@@ -1413,9 +1407,7 @@ var SubagentManager = class {
1413
1407
  label: spec.label,
1414
1408
  ...agentProvider !== void 0 ? { agentProvider } : {},
1415
1409
  ...agentModel !== void 0 ? { agentModel } : {},
1416
- ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
1417
- ...request.persona !== void 0 ? { persona: request.persona } : {},
1418
- ...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
1410
+ ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
1419
1411
  });
1420
1412
  const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
1421
1413
  const releaseHold = this.holdOwnership(parent, childId);
@@ -1451,16 +1443,13 @@ var SubagentManager = class {
1451
1443
  create: {
1452
1444
  cwd,
1453
1445
  seed,
1454
- meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
1446
+ meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, request.agentPreset),
1455
1447
  inheritedEventCount,
1456
1448
  delegatedPolicies,
1457
1449
  descriptor
1458
1450
  },
1459
1451
  agentOptions,
1460
- composition: {
1461
- persona: request.persona,
1462
- toolFilter: request.toolFilter
1463
- },
1452
+ composition: { agentPreset: request.agentPreset },
1464
1453
  signal: spec.signal,
1465
1454
  delivery: spec.delivery,
1466
1455
  outputSchema: spec.request.outputSchema
@@ -1805,14 +1794,16 @@ var SubagentManager = class {
1805
1794
  const { create } = inputs;
1806
1795
  let structured;
1807
1796
  const setup = async (childCtx, child) => {
1797
+ if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, parent, inputs.composition);
1808
1798
  if (create !== void 0) {
1799
+ if (inputs.composition.agentPreset !== void 0) child.session.append("agent-preset/selected", { agentPreset: inputs.composition.agentPreset });
1809
1800
  child.session.append("subagent/descriptor", create.descriptor);
1810
1801
  appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
1811
1802
  const workingDirectory = childCtx.get("workingDirectory");
1812
1803
  if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
1813
1804
  await workingDirectory.set(child, create.cwd, inputs.signal);
1814
1805
  }
1815
- applyChildComposition(childCtx, parent, inputs.composition);
1806
+ if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, parent, inputs.composition);
1816
1807
  if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => this.resident.get(childId)?.ownedChildren.size === 0 && child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
1817
1808
  };
1818
1809
  const handle = create === void 0 ? await this.ownerCtx.agents.resume({
@@ -2165,10 +2156,14 @@ var SubagentManager = class {
2165
2156
  const source = __addDisposableResource$2(env_1, observation, false);
2166
2157
  this.assertAdmitting(parent);
2167
2158
  this.authorizeLineage(parent, childId, source.header.parentSession);
2168
- const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
2159
+ const ownEvents = source.events.slice(source.inheritedEventCount);
2160
+ const descriptor = foldSubagentDescriptor(ownEvents);
2169
2161
  if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, "NOT_RESUMABLE");
2170
2162
  let activation;
2171
2163
  try {
2164
+ const independentlySelected = hasIndependentPresetSelection(ownEvents);
2165
+ const agentPreset = independentlySelected ? source.projections?.values.agentPreset : void 0;
2166
+ if (independentlySelected && typeof agentPreset !== "string") throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
2172
2167
  activation = await this.materialize({
2173
2168
  kind: "local",
2174
2169
  childId,
@@ -2180,6 +2175,7 @@ var SubagentManager = class {
2180
2175
  ...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
2181
2176
  },
2182
2177
  composition: {
2178
+ agentPreset: agentPreset ?? void 0,
2183
2179
  persona: descriptor.persona,
2184
2180
  toolFilter: descriptor.toolFilter
2185
2181
  },
@@ -2258,6 +2254,15 @@ var SubagentManager = class {
2258
2254
  return query;
2259
2255
  }
2260
2256
  };
2257
+ /** Creation-time selection precedes the own descriptor; historical forks can select after it. */
2258
+ function hasIndependentPresetSelection(events) {
2259
+ for (const event of events) {
2260
+ if (event.type === "subagent/descriptor") return false;
2261
+ if (event.type === "agent-preset/selected") return true;
2262
+ }
2263
+ /* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
2264
+ return false;
2265
+ }
2261
2266
  //#endregion
2262
2267
  //#region lib/types/list-children.js
2263
2268
  /**
@@ -2778,15 +2783,14 @@ function normalizeSubagentDiagnostic(result) {
2778
2783
  /**
2779
2784
  * The capability advertisement of an out-of-process backend: NONE. A child in
2780
2785
  * another process cannot honor parent-enforced start features
2781
- * (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
2786
+ * (`agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`), so the service rejects a
2782
2787
  * request needing any of them before `start` runs — never accepted-then-ignored.
2783
2788
  */
2784
2789
  const NO_START_CAPABILITIES = Object.freeze({
2785
2790
  agentOptions: false,
2791
+ agentPreset: false,
2786
2792
  outputSchema: false,
2787
- depthLimit: false,
2788
- toolFilter: false,
2789
- persona: false
2793
+ depthLimit: false
2790
2794
  });
2791
2795
  /**
2792
2796
  * Assert a configured timing bound is a positive finite number (it bounds a
@@ -3187,10 +3191,12 @@ let SubagentRuntime = (() => {
3187
3191
  if (clientTimeZone !== void 0 && canonicalTimeZone === void 0) throw new RemoteError("subagent/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: clientTimeZone });
3188
3192
  const parent = this.ctx.get("agents")?.get(parentSessionId);
3189
3193
  if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
3194
+ const principal = principalOf(this.ctx.invocation);
3190
3195
  const source = {
3191
3196
  kind: "user",
3192
3197
  rpcId: request.requestId,
3193
- ...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone }
3198
+ ...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone },
3199
+ ...principal === void 0 ? {} : { principal }
3194
3200
  };
3195
3201
  try {
3196
3202
  let content;
@@ -3303,6 +3309,7 @@ let SubagentRuntime = (() => {
3303
3309
  }
3304
3310
  /** Reject the first requested capability that the provider lacks. */
3305
3311
  assertCapabilities(provider, request) {
3312
+ if (request.agentPreset !== void 0 && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, "UNSUPPORTED_CAPABILITY");
3306
3313
  const needs = [
3307
3314
  {
3308
3315
  when: request.agentOptions !== void 0,
@@ -3315,14 +3322,6 @@ let SubagentRuntime = (() => {
3315
3322
  {
3316
3323
  when: request.maxDepth !== void 0,
3317
3324
  cap: "depthLimit"
3318
- },
3319
- {
3320
- when: request.toolFilter !== void 0,
3321
- cap: "toolFilter"
3322
- },
3323
- {
3324
- when: request.persona !== void 0,
3325
- cap: "persona"
3326
3325
  }
3327
3326
  ];
3328
3327
  for (const { when, cap } of needs) if (when && !provider.capabilities[cap]) throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, "UNSUPPORTED_CAPABILITY");