mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
fix(subagent): gate model selection with explicit allowlist
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-18-model-selected-subagent-routes.md
|
||||
2026-08-18-model-selected-subagent-routes.md: 9c6d777a1dbed569340e4900559a2cee2a42ff82
|
||||
2026-08-18-model-selected-subagent-routes.zh.md: a6d665f9533b481edf2f91071f7561544744d202
|
||||
2026-08-18-model-selected-subagent-routes.md: 768da30d26f7daede6ed68dda72efcb4207ab60f
|
||||
2026-08-18-model-selected-subagent-routes.zh.md: 9cf3033af0e143589ff6806acbb4600478e83929
|
||||
|
||||
@@ -12,15 +12,15 @@ The model also needs a bounded way to discover live providers and model-owned ef
|
||||
|
||||
## Decision
|
||||
|
||||
`dsh-tool-subagent` exposes optional `provider`, `model`, and `reasoning_effort` fields only when its instance enables `enableModelSelection`, or its Agent-scoped `modelSelectionSettings` instance resolves a non-empty Session policy, and the bound subagent provider advertises `SubagentCapabilities.agentOptions`. Static enablement needs no route list and can select any route its adapter accepts. The shipped settings-controlled path uses the exact user authorization owned by [user-authorized subagent model routes](2026-08-24-user-authorized-subagent-model-routes.md). Disabled instances omit and reject model-facing selection, while configured `Config.agentOptions` remain deployment-owned defaults. Either selection mode against a provider without the capability fails the plugin mount.
|
||||
`dsh-tool-subagent` exposes optional `provider`, `model`, and `reasoning_effort` fields only when its Agent-scoped `modelSelectionSettings` instance resolves a Session policy and the bound subagent provider advertises `SubagentCapabilities.agentOptions`. The policy uses the exact user authorization owned by [user-authorized subagent model routes](2026-08-24-user-authorized-subagent-model-routes.md); there is no unrestricted static mode. Disabled instances omit and reject model-facing selection, while configured `Config.agentOptions` remain deployment-owned defaults. A settings-enabled instance against a provider without the capability fails the plugin mount.
|
||||
|
||||
Provider and model form one route and must be supplied together. An effort may be supplied alone when configured, parent, or provider-owned route defaults provide the effective route. Static `provider.agentRouteDefaults`, when present, establish the provider/model baseline; `Config.agentOptions` and model arguments overlay it before route-aware effort clearing. Providers without static defaults use compatible fields from the parent Agent's latest logged request selection, with creation options supplying the fallback before its first request and retaining the configured output-token limit. Reasoning-effort identifiers remain adapter-owned. An unchanged route inherits an omitted effort only from the selected baseline; changing provider or model without naming an effort clears the lower layer's route-owned value so the selected model resolves its own default. `AgentOptions` carries the resulting effort into the child loop, whose request header logs the effective value. A continuable descriptor records it with the resolved provider and model so a child that has not logged its first request can cold-resume with the same selection.
|
||||
|
||||
An explicit or configured provider, model, or effort resolves through `ctx.llm.resolveCallConfig()` after the provider baseline and request precedence are complete. Providers with static route defaults suppress parent-effort inheritance when the request omits effort, preserving the selected model's default. The LLM lookup owns provider registration, exact-model metadata, reasoning-effort validation, and adapter defaults. After the asynchronous lookup, the tool checks cancellation and confirms the same provider instance remains registered before creating a child or background job, so HMR cannot combine one provider's defaults with another provider's process. Calls with no model-facing selection and no configured route fields preserve the existing provider path without requiring the optional LLM service.
|
||||
|
||||
An enabled definition registers `list_subagent_models`. With no arguments the tool lists registered providers; with `provider` it calls that adapter's advisory model catalog; with `provider` and `model` it resolves the exact model and returns its reasoning efforts and default. At most one instance in a tool scope enables selection because the discovery name is global. Static enablement exposes the live directory without another filter. Shipped product compositions put `modelSelectionSettings: true` on the primary Agent-scoped `subagent` instance and register the default-empty Host-owned `subagent-model-selection.allowedModels` setting. The Plugins settings page stores exact provider/model routes from the adapter directory. A new top-level Session snapshots a non-empty policy as `subagent/model-selection-policy` before any model request. A child Session inherits the live parent's policy, and a resumed Session uses its recorded event instead of current settings. Therefore a settings edit affects only subsequently composed top-level Sessions. The fixed discovery definition remains available without the optional LLM service, while discovery and selected-route calls fail until that service is present. In settings-controlled Sessions, discovery lists the intersection of the live catalog and recorded policy, and the executor rejects explicit routes outside it.
|
||||
An enabled definition registers `list_subagent_models`. With no arguments the tool lists authorized registered providers; with `provider` it calls that adapter's advisory model catalog only after authorization; with `provider` and `model` it authorizes the exact route before resolving its reasoning efforts and default. At most one instance in a tool scope enables selection because the discovery name is global. Shipped product compositions put `modelSelectionSettings: true` on the primary Agent-scoped `subagent` instance and register the default-off Host-owned `subagent-model-selection` setting with an explicit `enabled` switch and `allowedModels` list. The Plugins settings page stores both fields atomically. A new top-level Session snapshots the route policy as `subagent/model-selection-policy` when the setting is enabled, before any model request. A child Session inherits the live parent's policy, and a resumed Session uses its recorded event instead of current settings. Therefore a settings edit affects only subsequently composed top-level Sessions. The fixed discovery definition remains available without the optional LLM service, while discovery and selected-route calls fail until that service is present. Discovery lists the intersection of the live catalog and recorded policy, and the executor rejects explicit routes outside it.
|
||||
|
||||
Shipped `subagent_fork` instances leave `enableModelSelection` disabled even though the in-process fork provider supports `agentOptions`. A fork inherits the parent's effective provider and model so its copied conversation prefix remains eligible for provider-side KV Cache reuse. Changing either route component requires the new route to prefill that inherited history again, and that recomputation can dominate the delegated task's cost. This restriction is independent of the discovery tool's global name: separating discovery ownership would permit the configuration but would not preserve reuse. Fork route selection remains unavailable until a route change can retain prefix reuse or the caller can explicitly bound and accept the recomputation cost.
|
||||
Shipped `subagent_fork` instances do not read model-selection settings even though the in-process fork provider supports `agentOptions`. A fork inherits the parent's effective provider and model so its copied conversation prefix remains eligible for provider-side KV Cache reuse. Changing either route component requires the new route to prefill that inherited history again, and that recomputation can dominate the delegated task's cost. This restriction is independent of the discovery tool's global name: separating discovery ownership would permit the configuration but would not preserve reuse. Fork route selection remains unavailable until a route change can retain prefix reuse or the caller can explicitly bound and accept the recomputation cost.
|
||||
|
||||
The delegation definition is static across adapter registration and catalog changes, so live topology neither expands every parent request nor invalidates its cache prefix. The discovery result enters the transcript only when called. A custom inheritance-capable instance that enables selection warns that changing provider or model can prevent provider-side reuse of the inherited conversation prefix.
|
||||
|
||||
@@ -49,7 +49,7 @@ The delegation definition is static across adapter registration and catalog chan
|
||||
## Consequences
|
||||
|
||||
- A statically enabled delegation tool can select any live child LLM route without deployment selector configuration; disabled instances omit and reject model-facing route fields.
|
||||
- The primary delegation-tool instance defaults selection off, exposes a Plugins-page exact-route opt-in for new Sessions, and registers `list_subagent_models` only in Sessions whose durable policy is non-empty; discovery and explicit selection are constrained to that policy.
|
||||
- The primary delegation-tool instance defaults selection off, exposes a Plugins-page exact-route opt-in for new Sessions, and registers `list_subagent_models` only in Sessions whose durable policy exists; discovery and explicit selection are constrained to that policy.
|
||||
- Shipped fork tools inherit the parent's provider and model and omit model-facing route fields so the inherited conversation prefix remains eligible for KV Cache reuse.
|
||||
- Omission retains configured defaults plus static provider route defaults or compatible parent inheritance; a route change without an explicit effort uses the selected model's default.
|
||||
- Adapter catalog and topology changes leave the delegation definition and its prompt-cache prefix unchanged.
|
||||
|
||||
@@ -12,15 +12,15 @@ Status: implemented
|
||||
|
||||
## 决策
|
||||
|
||||
只有实例启用 `enableModelSelection`,或其 Agent 作用域的 `modelSelectionSettings` 实例解析出非空 Session 策略,且绑定的 subagent 提供方声明 `SubagentCapabilities.agentOptions` 时,`dsh-tool-subagent` 才公开可选的 `provider`、`model` 与 `reasoning_effort` 字段。静态启用无需路由列表,并且可以选择适配器接受的任意路由。随附的 settings 控制路径使用[用户授权的 subagent 模型路由](2026-08-24-user-authorized-subagent-model-routes.zh.md)所拥有的精确用户授权。禁用的实例会省略并拒绝面向模型的选择,而配置的 `Config.agentOptions` 仍是部署方所有的默认值。如果提供方缺少该能力,任一种选择模式都会使插件挂载失败。
|
||||
只有 Agent 作用域的 `modelSelectionSettings` 实例解析出 Session 策略,且绑定的 subagent 提供方声明 `SubagentCapabilities.agentOptions` 时,`dsh-tool-subagent` 才公开可选的 `provider`、`model` 与 `reasoning_effort` 字段。该策略使用[用户授权的 subagent 模型路由](2026-08-24-user-authorized-subagent-model-routes.zh.md)所拥有的精确用户授权;不存在无限制静态模式。禁用的实例会省略并拒绝面向模型的选择,而配置的 `Config.agentOptions` 仍是部署方所有的默认值。如果 settings 已启用的实例缺少该提供方能力,插件挂载会失败。
|
||||
|
||||
提供方与模型共同组成一条路由,必须一起提供。如果配置值、父级值或提供方持有的路由默认值能够提供生效路由,则可以只提供推理强度。静态的 `provider.agentRouteDefaults` 在存在时构成 provider/model 基线;`Config.agentOptions` 与模型参数会在路由相关强度清除之前覆盖它。没有静态默认值的提供方会使用父 Agent 最新记录请求中的兼容字段,首个请求之前由创建选项提供回退,并保留其中配置的输出 token 上限。推理强度 ID 仍由 adapter 所有。只有所选基线的路由不变时才会继承省略的强度;更换提供方或模型但没有指定强度时,会清除下层路由自有的值,使所选模型解析自己的默认值。`AgentOptions` 把结果强度传入子级循环,其请求 header 会记录生效值。可继续描述符会把它与解析后的提供方和模型一同记录,使尚未写入首个请求的子级能以相同选择冷恢复。
|
||||
|
||||
显式或配置的提供方、模型或强度会在提供方基线与请求优先级完成后,通过 `ctx.llm.resolveCallConfig()` 解析。具有静态路由默认值的提供方会在请求省略强度时禁止继承父级强度,从而保留所选模型的默认值。LLM 查询负责提供方注册、精确模型元数据、推理强度校验和 adapter 默认值。异步查询完成后、创建子级或后台 job 之前,工具会再次检查取消状态,并确认同一个提供方实例仍处于注册状态,因此 HMR 不会把一个提供方的默认值与另一个提供方的进程组合。既没有面向模型的选择、也没有配置路由字段的调用会保留原有提供方路径,不要求可选 LLM 服务存在。
|
||||
|
||||
启用的定义会注册 `list_subagent_models`。无参数调用列出已注册提供方;提供 `provider` 时调用该适配器的建议性模型目录;同时提供 `provider` 与 `model` 时解析精确模型,并返回其推理强度和默认值。因为发现工具使用全局名称,一个工具作用域最多由一个实例启用选择。静态启用会公开不带额外过滤的实时目录。随附产品组合在 Agent 作用域的主 `subagent` 实例上设置 `modelSelectionSettings: true`,并注册默认空值的 Host 自有 `subagent-model-selection.allowedModels` 设置。Plugins 设置页从适配器目录保存精确 provider/model 路由。新的顶层 Session 会在任何模型请求之前,把非空策略快照记录为 `subagent/model-selection-policy`。子 Session 继承在线父级的策略;恢复的 Session 使用已记录事件,而不是当前设置。因此,设置修改只影响之后组合的顶层 Session。即使缺少可选 LLM 服务,固定发现定义仍保持可用;发现调用和所选路由调用会在该服务出现前失败。在 settings 控制的 Session 中,发现会列出实时目录与已记录策略的交集,执行器会拒绝策略之外的显式路由。
|
||||
启用的定义会注册 `list_subagent_models`。无参数调用列出已授权且已注册的提供方;提供 `provider` 时先授权,再调用该适配器的建议性模型目录;同时提供 `provider` 与 `model` 时会先授权精确路由,再解析其推理强度和默认值。因为发现工具使用全局名称,一个工具作用域最多由一个实例启用选择。随附产品组合在 Agent 作用域的主 `subagent` 实例上设置 `modelSelectionSettings: true`,并注册 Host 自有的 `subagent-model-selection` 设置,其中包含默认关闭的显式 `enabled` 开关与 `allowedModels` 列表。Plugins 设置页会原子保存两个字段。设置启用时,新的顶层 Session 会在任何模型请求之前,把路由策略快照记录为 `subagent/model-selection-policy`。子 Session 继承在线父级的策略;恢复的 Session 使用已记录事件,而不是当前设置。因此,设置修改只影响之后组合的顶层 Session。即使缺少可选 LLM 服务,固定发现定义仍保持可用;发现调用和所选路由调用会在该服务出现前失败。发现会列出实时目录与已记录策略的交集,执行器会拒绝策略之外的显式路由。
|
||||
|
||||
随附的 `subagent_fork` 实例不会启用 `enableModelSelection`,即使进程内 fork 提供方支持 `agentOptions` 也是如此。fork 会继承父级生效的提供方与模型,使复制的对话前缀仍可供提供方侧 KV Cache 复用。更改任一路由组件都会要求新路由重新预填充继承的历史,而这项重算成本可能超过委派任务本身。该限制与发现工具的全局名称无关:分离发现工具的持有权可以让配置生效,却无法保留复用。只有在路由变化仍能保留前缀复用,或调用方可以显式限制并接受重算成本时,才重新考虑 fork 路由选择。
|
||||
随附的 `subagent_fork` 实例不会读取模型选择设置,即使进程内 fork 提供方支持 `agentOptions` 也是如此。fork 会继承父级生效的提供方与模型,使复制的对话前缀仍可供提供方侧 KV Cache 复用。更改任一路由组件都会要求新路由重新预填充继承的历史,而这项重算成本可能超过委派任务本身。该限制与发现工具的全局名称无关:分离发现工具的持有权可以让配置生效,却无法保留复用。只有在路由变化仍能保留前缀复用,或调用方可以显式限制并接受重算成本时,才重新考虑 fork 路由选择。
|
||||
|
||||
委派定义不会随 adapter 注册和目录变化而改变,因此实时拓扑既不会扩大每个父级请求,也不会使缓存前缀失效。只有调用发现工具时,目录结果才进入 transcript。自定义的上下文继承实例如果启用选择,其描述会警告,更改提供方或模型可能阻止提供方复用继承的对话前缀。
|
||||
|
||||
@@ -49,7 +49,7 @@ Status: implemented
|
||||
## 结果
|
||||
|
||||
- 静态启用的委派工具无需部署选择器配置,即可选择任意实时子级 LLM 路由;禁用的实例会省略并拒绝面向模型的路由字段。
|
||||
- 主委派工具实例默认关闭选择,为新 Session 提供 Plugins 页面精确路由 opt-in,并且只在持久策略非空的 Session 中注册 `list_subagent_models`;发现与显式选择都受该策略限制。
|
||||
- 主委派工具实例默认关闭选择,为新 Session 提供 Plugins 页面精确路由 opt-in,并且只在持久策略存在的 Session 中注册 `list_subagent_models`;发现与显式选择都受该策略限制。
|
||||
- 随附 fork 工具会继承父级的提供方与模型,并省略面向模型的路由字段,使继承的对话前缀仍可供 KV Cache 复用。
|
||||
- 省略选择时保留配置默认值,并使用静态提供方路由默认值或来自父级最新记录请求的兼容继承;改变路由但不显式指定强度时,使用所选模型的默认值。
|
||||
- adapter 目录和拓扑变化不会改变委派定义及其 prompt 缓存前缀。
|
||||
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-24-user-authorized-subagent-model-routes.md
|
||||
2026-08-24-user-authorized-subagent-model-routes.md: 3a56e48b35bcd1b1801108022e85ec83ad98b437
|
||||
2026-08-24-user-authorized-subagent-model-routes.zh.md: dca788993f1eccb96a424d2bd8186749363488b3
|
||||
2026-08-24-user-authorized-subagent-model-routes.md: af293dd2e74ea801892f2b73553cc15beb1c679e
|
||||
2026-08-24-user-authorized-subagent-model-routes.zh.md: 973142f0294ad2bfc10dd5729e3a4a4992d6a84b
|
||||
|
||||
+4
-4
@@ -10,13 +10,13 @@ Registering an LLM adapter makes its routes reachable, but does not authorize an
|
||||
|
||||
## Decision
|
||||
|
||||
The Host-owned `subagent-model-selection` settings section stores `allowedModels`, an array of exact `{ provider, model }` routes. An empty array disables model-facing child route selection. The Plugins settings card reads the live adapter directory through `llm.models`, lets the user stage one or more exact routes, and replaces the whole array in one revision-fenced field write. It stores no adapter-owned display names, descriptions, or reasoning-effort metadata. A stored route absent from the current directory remains visible as unavailable and removable; a provider-local catalog failure does not block other providers or erase stored authorization.
|
||||
The Host-owned `subagent-model-selection` settings section stores an explicit `enabled` switch and `allowedModels`, an array of exact `{ provider, model }` routes. Enabling requires at least one route; disabling may retain the selected routes for later reuse. The Plugins settings card reads the live adapter directory through `llm.models`, lets the user stage the switch and routes, and saves both fields in one revision-fenced settings mutation. It stores no adapter-owned display names, descriptions, or reasoning-effort metadata. A stored route absent from the current directory remains visible as unavailable and removable; a provider-local catalog failure does not block other providers or erase stored authorization.
|
||||
|
||||
A newly composed top-level Session snapshots a non-empty route list in `subagent/model-selection-policy` before its model-selectable definitions can reach a request. Child Sessions inherit that exact list from their live parent, and resumed Sessions use the recorded event instead of current settings. Settings changes therefore affect only subsequently composed top-level Sessions.
|
||||
A newly composed top-level Session snapshots the route list in `subagent/model-selection-policy` when the setting is enabled, before its model-selectable definitions can reach a request. Event presence means selection was enabled; the event does not store the global switch. Child Sessions inherit that exact list from their live parent, and resumed Sessions use the recorded event instead of current settings. Settings changes therefore affect only subsequently composed top-level Sessions, while a non-empty legacy Session without the event remains disabled.
|
||||
|
||||
The fixed `list_subagent_models` schema does not enumerate the policy. At call time, provider and model listings are the intersection of the Session route list and the adapter's live advertised directory. An exact provider/model lookup first requires authorization, then resolves the adapter-owned model metadata and all advertised reasoning efforts. The delegation executor independently rejects any explicit provider, model, or effort selection whose effective provider/model route is outside the Session list before `resolveCallConfig()` validates adapter availability and effort support. A call that supplies no selection field retains configured or inherited routing because the model made no route choice.
|
||||
|
||||
Static `enableModelSelection: true` remains an unrestricted deployment-owned mode for custom compositions. The shipped `modelSelectionSettings` path is user-authorized and default-off. The primary spawn tool uses that path; the shipped fork tool still exposes no route selection so inherited conversation prefixes remain eligible for provider-side KV Cache reuse.
|
||||
Model selection has no unrestricted static mode. The default-off Host setting is the only authority, and an enabled Session always carries an exact allowlist. The primary spawn tool reads that setting; the shipped fork tool still exposes no route selection so inherited conversation prefixes remain eligible for provider-side KV Cache reuse.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -24,7 +24,7 @@ Static `enableModelSelection: true` remains an unrestricted deployment-owned mod
|
||||
|
||||
**Filter only the settings UI or discovery result.** Rejected because a model can guess a route or retain one from an earlier transcript. Authorization is enforced in the executor that starts the child.
|
||||
|
||||
**Store `enabled` and `allowedModels` as separate fields.** Rejected because two writes admit an enabled state with no completed authorization decision. A non-empty array is both the opt-in and its exact policy; an empty user-layer array can explicitly disable a deployment base list.
|
||||
**Infer enablement from a non-empty `allowedModels` array.** Rejected because disabling would have to discard a useful selection or preserve a non-empty array whose meaning depends on write history. The explicit switch is authoritative, and the settings scope submits both fields in one Host-validated mutation so no intermediate state is persisted.
|
||||
|
||||
**Store per-route reasoning-effort allowlists.** Rejected because the user decision concerns child models, while effort ids and compatibility belong to the exact adapter route. Every adapter-supported effort remains available after the route is authorized.
|
||||
|
||||
|
||||
+4
-4
@@ -10,13 +10,13 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
Host 自有的 `subagent-model-selection` 设置 section 保存 `allowedModels`,即由精确 `{ provider, model }` 路由组成的数组。空数组会关闭面向模型的子级路由选择。Plugins 设置卡通过 `llm.models` 读取实时适配器目录,让用户暂存一条或多条精确路由,再用一次带 revision 限制的字段写入整体替换该数组。它不保存适配器自有的显示名称、描述或推理强度元数据。当前目录中缺失的已存路由仍显示为不可用并允许移除;某个提供方的目录失败不会阻塞其他提供方,也不会清除已存授权。
|
||||
Host 自有的 `subagent-model-selection` 设置 section 保存显式 `enabled` 开关与 `allowedModels`,后者是由精确 `{ provider, model }` 路由组成的数组。启用时必须至少有一条路由;关闭时可以保留已选路由,供以后重新启用。Plugins 设置卡通过 `llm.models` 读取实时适配器目录,让用户暂存开关与路由,再在一次带 revision 限制的设置 mutation 中保存两个字段。它不保存适配器自有的显示名称、描述或推理强度元数据。当前目录中缺失的已存路由仍显示为不可用并允许移除;某个提供方的目录失败不会阻塞其他提供方,也不会清除已存授权。
|
||||
|
||||
新组合的顶层 Session 会在模型可选定义进入请求之前,把非空路由列表快照记录为 `subagent/model-selection-policy`。子 Session 从在线父级继承同一份精确列表,恢复的 Session 使用已记录事件而不是当前设置。因此,设置修改只影响之后组合的顶层 Session。
|
||||
设置启用时,新组合的顶层 Session 会在模型可选定义进入请求之前,把路由列表快照记录为 `subagent/model-selection-policy`。事件存在就表示模型选择已启用;事件不保存全局开关。子 Session 从在线父级继承同一份精确列表,恢复的 Session 使用已记录事件而不是当前设置。因此,设置修改只影响之后组合的顶层 Session,而已有非空日志但没有该事件的 Session 仍保持禁用。
|
||||
|
||||
固定的 `list_subagent_models` schema 不会枚举该策略。调用时,提供方和模型列表是 Session 路由列表与适配器实时公布目录的交集。精确 provider/model 查询先要求授权,再解析适配器自有的模型元数据和全部已公布推理强度。委派执行器还会独立拒绝任何生效 provider/model 路由不在 Session 列表内的显式提供方、模型或强度选择,然后才由 `resolveCallConfig()` 校验适配器可用性与强度支持。完全没有选择字段的调用保留配置或继承路由,因为模型没有作出路由选择。
|
||||
|
||||
静态 `enableModelSelection: true` 继续作为自定义组合中由部署方所有的无限制模式。随附的 `modelSelectionSettings` 路径由用户授权且默认关闭。主 spawn 工具使用该路径;随附 fork 工具仍不公开路由选择,使继承的对话前缀继续符合提供方侧 KV Cache 复用条件。
|
||||
模型选择不再有无限制的静态模式。默认关闭的 Host 设置是唯一授权来源,启用的 Session 始终携带精确允许列表。主 spawn 工具读取该设置;随附 fork 工具仍不公开路由选择,使继承的对话前缀继续符合提供方侧 KV Cache 复用条件。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -24,7 +24,7 @@ Host 自有的 `subagent-model-selection` 设置 section 保存 `allowedModels`
|
||||
|
||||
**只过滤设置 UI 或发现结果。** 不采用,因为模型可以猜测路由,或从较早的 transcript 中保留路由。授权由启动子级的执行器强制执行。
|
||||
|
||||
**把 `enabled` 与 `allowedModels` 存成两个字段。** 不采用,因为两次写入会产生已经启用但尚无完整授权决定的状态。非空数组同时表示 opt-in 与精确策略;用户层空数组可以显式关闭部署基础列表。
|
||||
**从非空 `allowedModels` 数组推断是否启用。** 不采用,因为关闭功能时要么必须丢弃仍有用的选择,要么要保留一个含义取决于写入历史的非空数组。显式开关是权威依据,设置 scope 会在一次由 Host 校验的 mutation 中提交两个字段,因此不会持久化中间状态。
|
||||
|
||||
**保存每条路由的推理强度允许列表。** 不采用,因为用户决定针对子级模型,而强度 id 与兼容性属于精确适配器路由。路由获准后,仍可使用适配器支持的每种强度。
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 1
|
||||
|
||||
|
||||
@@ -242,12 +242,16 @@ describe('the shipped Web composition', () => {
|
||||
})
|
||||
|
||||
it('applies the default-off subagent model allowlist only to new sessions', async () => {
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: false,
|
||||
allowedModels: [],
|
||||
})
|
||||
const disabled = await ctx.agents.create({
|
||||
sessionId: SessionId('preset-model-selection-disabled'),
|
||||
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
|
||||
})
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: [{ provider: 'deepseek-official', model: 'deepseek-v4-flash' }],
|
||||
})
|
||||
const enabled = await ctx.agents.create({
|
||||
@@ -265,7 +269,7 @@ describe('the shipped Web composition', () => {
|
||||
]))
|
||||
expect(toolNames(ctx, disabled.agent)).not.toContain('list_subagent_models')
|
||||
} finally {
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
|
||||
await enabled.dispose()
|
||||
await disabled.dispose()
|
||||
}
|
||||
|
||||
@@ -106,10 +106,20 @@ describe('web e2e: plugin configuration section', () => {
|
||||
await expect.poll(() => toggle.getAttribute('aria-checked'), { timeout: 5_000 }).toBe('true')
|
||||
await expect.poll(async () => (await settingsDocument()).includes('subagent-model-selection:'), { timeout: 10_000 })
|
||||
.toBe(true)
|
||||
expect(await settingsDocument()).toContain('enabled: true')
|
||||
expect(await settingsDocument()).toContain('allowedModels:')
|
||||
expect(await settingsDocument()).toContain('provider:')
|
||||
expect(await settingsDocument()).toContain('model:')
|
||||
expect(await dialog.getByRole('status').textContent()).toBe('已保存,新会话将使用此设置。')
|
||||
|
||||
await toggle.click()
|
||||
await dialog.getByRole('button', { name: '保存', exact: true }).click()
|
||||
await expect.poll(() => toggle.getAttribute('aria-checked'), { timeout: 5_000 }).toBe('false')
|
||||
await expect.poll(async () => (await settingsDocument()).includes('enabled: false'), { timeout: 10_000 })
|
||||
.toBe(true)
|
||||
expect(await settingsDocument()).toContain('allowedModels:')
|
||||
expect(await settingsDocument()).toContain('provider:')
|
||||
expect(await settingsDocument()).toContain('model:')
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 60_000)
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: 6432f5d359027a30fd436c9becd6bb6b8c0abaf4
|
||||
config-catalog.zh.md: 1ab6838e4cea77a7d98a2227aca6e8ac47d84fcd
|
||||
config-catalog.md: 57b7f120ff09459e02f998806f6335f2b45d1b1b
|
||||
config-catalog.zh.md: f0911b9d004d885868758351e16e3783b660eb18
|
||||
|
||||
@@ -2863,12 +2863,9 @@ export interface Config {
|
||||
* a distinct name.
|
||||
*/
|
||||
toolName?: string
|
||||
/** Let the model discover and select the child LLM route (default false). */
|
||||
enableModelSelection?: boolean
|
||||
/**
|
||||
* Sample the Host `subagent-model-selection` user setting for each new
|
||||
* top-level session and inherit that decision in its child sessions. Mutually
|
||||
* exclusive with `enableModelSelection`.
|
||||
* top-level session and inherit that decision in its child sessions.
|
||||
*/
|
||||
modelSelectionSettings?: boolean
|
||||
/**
|
||||
|
||||
@@ -2865,12 +2865,9 @@ export interface Config {
|
||||
* a distinct name.
|
||||
*/
|
||||
toolName?: string
|
||||
/** Let the model discover and select the child LLM route (default false). */
|
||||
enableModelSelection?: boolean
|
||||
/**
|
||||
* Sample the Host `subagent-model-selection` user setting for each new
|
||||
* top-level session and inherit that decision in its child sessions. Mutually
|
||||
* exclusive with `enableModelSelection`.
|
||||
* top-level session and inherit that decision in its child sessions.
|
||||
*/
|
||||
modelSelectionSettings?: boolean
|
||||
/**
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/subagent.md
|
||||
subagent.md: 792e03aa8bc5b8533094b4fd678ef8b43383e043
|
||||
subagent.zh.md: 1b5af32dfc6405c1df09d38628332e8e6c8ae737
|
||||
subagent.md: 9206672b89ac31e30bee176f536b3057eb37f3ee
|
||||
subagent.zh.md: 30e4e09a145a4b3d51044d85c602ce81fd44c3b3
|
||||
|
||||
@@ -505,10 +505,10 @@ Singleton settings owner read by delegation tools when an Agent is published.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read a detached route policy for the next eligible Agent publication.
|
||||
* @returns exact allowed routes; an empty list disables model-facing selection.
|
||||
* Read a detached selection preference for the next eligible Agent publication.
|
||||
* @returns the enabled state and exact allowed routes.
|
||||
*/
|
||||
currentAllowedModels(): AllowedModelRoute[]
|
||||
current(): SubagentModelSelectionSettings
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent/src/model-selection-settings.ts`](../../packages/subagent/tool-subagent/src/model-selection-settings.ts)
|
||||
|
||||
@@ -509,10 +509,10 @@ Singleton settings owner read by delegation tools when an Agent is published.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Read a detached route policy for the next eligible Agent publication.
|
||||
* @returns exact allowed routes; an empty list disables model-facing selection.
|
||||
* Read a detached selection preference for the next eligible Agent publication.
|
||||
* @returns the enabled state and exact allowed routes.
|
||||
*/
|
||||
currentAllowedModels(): AllowedModelRoute[]
|
||||
current(): SubagentModelSelectionSettings
|
||||
```
|
||||
|
||||
Source: [`packages/subagent/tool-subagent/src/model-selection-settings.ts`](../../packages/subagent/tool-subagent/src/model-selection-settings.ts)
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/tool-catalog.md
|
||||
tool-catalog.md: be8f503ed983e68e39da1a9401ffbe70a029968c
|
||||
tool-catalog.zh.md: 16fd7de1235250d91dfa7df2304d1a66ba192b0d
|
||||
tool-catalog.md: 16142c2f7d98cf1037b034d2836c742b62f26594
|
||||
tool-catalog.zh.md: 533caacc7a923b5ea36f527292f420b610f1c488
|
||||
|
||||
+3
-15
@@ -33,7 +33,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`, `ctx.workflowEngine`, `ctx.subagents`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents every fresh round)` | `tool/call`, `tool/result`, `workflow and child session events during execution` | - | A fixed foreground workflow starts one fresh structured child per round; the model selects only the immutable objective and an optional round cap. |
|
||||
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`, `ctx.agents`, `ctx.skills` | `tool/call`, `tool/result`, `user/message replacement catalogs via agent.inject()` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-session-query` | `session_event_read`, `session_event_search`, `session_event_trace`, `session_search`, `session_trace` | `ctx.tools`, `ctx.systemPrompt`, `ctx.sessionQuery`, `a calling Agent for workspace authority` | `tool/call`, `tool/result` | - | The five read-only tools hide provider cursors and authorize every result from the immutable calling agent session. The package is opt-in; compositions that need enforced deadlines or bounded inline output also mount the generic timeout or spill policies. |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`, `subagent` | `ctx.tools`, `ctx.subagents`, `ctx.systemPrompt`, `ctx.llm for model discovery and selected-route validation` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered delegation name is the load-time `toolName` config (default `subagent`); the schema above shows static model selection enabled for reference. Model selection defaults off. Web presets sample the default-off Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Explicit compositions may instead use static `enableModelSelection`. Each instance independently controls model selection, discovery ownership, and background behavior through `enableModelSelection`, `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`. |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`, `subagent` | `ctx.tools`, `ctx.subagents`, `ctx.systemPrompt`, `ctx.llm for model discovery and selected-route validation` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance independently controls whether it reads model-selection settings and its background behavior through `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`. |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`, `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). |
|
||||
| `@deepseek-ai/dsh-tool-subagent-report` | `report` | `ctx.subagents`, `ctx.systemPrompt`, `a live continuable in-process child Agent` | `tool/call`, `tool/result`, `a user-role message in the direct parent session` | - | Registered per continuable in-process child rather than globally, so this schema is visible only inside such a child and survives its global `toolFilter`. The same contribution installs the child-scoped `tool:report` prompt section, which this catalog does not render. The parent-facing `send_message` tool is installed independently. |
|
||||
| `@deepseek-ai/dsh-tool-jobs` | `job_kill`, `job_list`, `job_output` | `ctx.tools`, `ctx.jobs`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. |
|
||||
@@ -1561,7 +1561,7 @@ Source: [`packages/subagent/tool-subagent/src/list-models.ts`](../packages/subag
|
||||
|
||||
### `subagent`
|
||||
|
||||
Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This call waits for the result by default. Set `run_in_background: true` to return a job id; collect with `job_output` and stop with `job_kill`. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.
|
||||
Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This call waits for the result by default. Set `run_in_background: true` to return a job id; collect with `job_output` and stop with `job_kill`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -1575,18 +1575,6 @@ Delegate a self-contained task to a subagent (a separate agent that works in its
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run as a background job and return its id. Defaults to false; collect with job_output or stop with job_kill."
|
||||
@@ -1601,7 +1589,7 @@ Delegate a self-contained task to a subagent (a separate agent that works in its
|
||||
|
||||
Source: [`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts)
|
||||
|
||||
The registered delegation name is the load-time `toolName` config (default `subagent`); the schema above shows static model selection enabled for reference. Model selection defaults off. Web presets sample the default-off Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Explicit compositions may instead use static `enableModelSelection`. Each instance independently controls model selection, discovery ownership, and background behavior through `enableModelSelection`, `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`.
|
||||
The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance independently controls whether it reads model-selection settings and its background behavior through `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`.
|
||||
|
||||
<a id="deepseek-aidsh-tool-subagent-control"></a>
|
||||
|
||||
|
||||
+3
-15
@@ -37,7 +37,7 @@
|
||||
| `@deepseek-ai/dsh-tool-ralph` | `ralph` | `ctx.tools`、`ctx.workflowEngine`、`ctx.subagents`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents every fresh round)` | `tool/call`、`tool/result`、`workflow and child session events during execution` | - | 固定的前台工作流会在每个 Round 启动一个全新的结构化子级;模型只能选择不可变目标和可选的 Round 上限。 |
|
||||
| `@deepseek-ai/dsh-tool-skill` | `skill` | `ctx.tools`、`ctx.agents`、`ctx.skills` | `tool/call`、`tool/result`、`user/message replacement catalogs via agent.inject()` | - | - |
|
||||
| `@deepseek-ai/dsh-tool-session-query` | `session_event_read`、`session_event_search`、`session_event_trace`、`session_search`、`session_trace` | `ctx.tools`、`ctx.systemPrompt`、`ctx.sessionQuery`、`a calling Agent for workspace authority` | `tool/call`、`tool/result` | - | 这 5 个只读工具会隐藏提供方游标,并根据不可变的调用 agent 会话为每个结果授权。该包需要选择启用;需要强制截止时间或限制行内输出的组合还会挂载通用超时或 spill 策略。 |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`、`subagent` | `ctx.tools`、`ctx.subagents`、`ctx.systemPrompt`、`用于模型发现和所选路由校验的 ctx.llm` | `tool/call`、`tool/result`、`child session events through the chosen provider` | `subagent`、`subagent_fork` | 注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述 schema 以静态启用模型选择作为参考。模型选择默认为关闭。Web preset 会在每个新顶层 Session 创建时读取插件页中默认关闭的偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。显式组合也可以改用静态 `enableModelSelection`。每个实例通过 `enableModelSelection`、`modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制模型选择、发现工具持有权和后台行为。 |
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`、`subagent` | `ctx.tools`、`ctx.subagents`、`ctx.systemPrompt`、`用于模型发现和所选路由校验的 ctx.llm` | `tool/call`、`tool/result`、`child session events through the chosen provider` | `subagent`、`subagent_fork` | 注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制是否读取模型选择设置及其后台行为。 |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`、`list_agents`、`send_message` | `ctx.tools`、`ctx.subagents`、`ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`、`tool/result`、`child session events through ctx.subagents` | - | 这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 |
|
||||
| `@deepseek-ai/dsh-tool-subagent-report` | `report` | `ctx.subagents`、`ctx.systemPrompt`、`a live continuable in-process child Agent` | `tool/call`、`tool/result`、`a user-role message in the direct parent session` | - | 按可继续的进程内子级注册,而非全局注册,因此该 schema 仅在这种子级内部可见,并且不受其全局 `toolFilter` 影响。同一份贡献还会安装子级作用域的 `tool:report` 系统提示词 section,本目录不渲染该 section。面向父级的 `send_message` 工具单独安装。 |
|
||||
| `@deepseek-ai/dsh-tool-jobs` | `job_kill`、`job_list`、`job_output` | `ctx.tools`、`ctx.jobs`、`ctx.systemPrompt` | `tool/call`、`tool/result`、`user/message via agent.inject() for background completion notices` | - | 与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 |
|
||||
@@ -1567,7 +1567,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
|
||||
|
||||
### `subagent`
|
||||
|
||||
将一项自包含任务委派给 subagent(在自身上下文中工作的独立 agent),用它卸载聚焦且独立的工作,例如研究、限定范围的实现或分析,以免消耗当前对话的上下文。subagent 会返回结果,但不会返回中间步骤。请提供完整、独立的提示词,因为它看不到当前对话。此调用默认等待结果。设置 `run_in_background: true` 可返回 job id;使用 `job_output` 收集结果,使用 `job_kill` 停止任务。子级 LLM 选择是可选的。省略 `provider`、`model` 与 `reasoning_effort` 会使用配置的子级默认值,并从父 Agent 继承兼容的缺失值。先用 `list_subagent_models` 检查公布的路由和强度,再一起提供 `provider` 与 `model`。改变生效路由但不指定强度时,会使用所选模型的默认强度。
|
||||
将一项自包含任务委派给 subagent(在自身上下文中工作的独立 agent),用它卸载聚焦且独立的工作,例如研究、限定范围的实现或分析,以免消耗当前对话的上下文。subagent 会返回结果,但不会返回中间步骤。请提供完整、独立的提示词,因为它看不到当前对话。此调用默认等待结果。设置 `run_in_background: true` 可返回 job id;使用 `job_output` 收集结果,使用 `job_kill` 停止任务。
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -1581,18 +1581,6 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run as a background job and return its id. Defaults to false; collect with job_output or stop with job_kill."
|
||||
@@ -1607,7 +1595,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
|
||||
|
||||
来源:[`packages/subagent/tool-subagent/src/index.ts`](../packages/subagent/tool-subagent/src/index.ts)
|
||||
|
||||
注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述 schema 以静态启用模型选择作为参考。模型选择默认为关闭。Web preset 会在每个新顶层 Session 创建时读取插件页中默认关闭的偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。显式组合也可以改用静态 `enableModelSelection`。每个实例通过 `enableModelSelection`、`modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制模型选择、发现工具持有权和后台行为。
|
||||
注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制是否读取模型选择设置及其后台行为。
|
||||
|
||||
<a id="deepseek-aidsh-tool-subagent-control"></a>
|
||||
|
||||
|
||||
@@ -357,7 +357,6 @@
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
|
||||
# Fork omits model selection so provider/model stay equal to the parent and
|
||||
|
||||
+22
-11
@@ -20,8 +20,10 @@ export interface AllowedSubagentModel {
|
||||
|
||||
/** Settings fields stored for subagent model selection. */
|
||||
export interface SubagentModelSelectionSettings {
|
||||
/** Whether model-facing child route selection applies to new Sessions. */
|
||||
enabled: boolean
|
||||
/** Exact child routes offered to newly composed top-level Sessions. */
|
||||
allowedModels?: AllowedSubagentModel[]
|
||||
allowedModels: AllowedSubagentModel[]
|
||||
}
|
||||
|
||||
/** One catalog row joined with a stored route that may no longer be advertised. */
|
||||
@@ -64,7 +66,7 @@ export interface SubagentModelSelectionCardFace {
|
||||
toggleModel: (key: string) => void
|
||||
/** Retry the adapter directory. */
|
||||
retryCatalog: () => void
|
||||
/** Persist the whole exact route list as one revision-fenced field write. */
|
||||
/** Persist the switch and exact routes as one revision-fenced mutation. */
|
||||
save: () => void
|
||||
/** Drop the staged enabled state and route choices. */
|
||||
discard: () => void
|
||||
@@ -151,9 +153,10 @@ export class SubagentModelSelectionCardController {
|
||||
) {
|
||||
this.store = createSnapshotStore(this.projection())
|
||||
this.unsubscribe = scope.subscribe(() => {
|
||||
if (this.currentRoutes().length > 0 && this.catalogStatus === 'idle') void this.loadCatalog()
|
||||
if (this.enabled() && this.catalogStatus === 'idle') void this.loadCatalog()
|
||||
this.publish()
|
||||
})
|
||||
if (this.enabled() && this.catalogStatus === 'idle') void this.loadCatalog()
|
||||
}
|
||||
|
||||
/** Stop observing settings and suppress late directory/write settlements. */
|
||||
@@ -180,7 +183,11 @@ export class SubagentModelSelectionCardController {
|
||||
}
|
||||
|
||||
private currentRoutes(): AllowedSubagentModel[] {
|
||||
return this.scope.getSnapshot().value?.allowedModels?.map(route => ({ ...route })) ?? []
|
||||
return this.scope.getSnapshot().value?.allowedModels.map(route => ({ ...route })) ?? []
|
||||
}
|
||||
|
||||
private currentEnabled(): boolean {
|
||||
return this.scope.getSnapshot().value?.enabled ?? false
|
||||
}
|
||||
|
||||
private selected(): Set<string> {
|
||||
@@ -188,11 +195,11 @@ export class SubagentModelSelectionCardController {
|
||||
}
|
||||
|
||||
private enabled(): boolean {
|
||||
return this.draftEnabled ?? this.currentRoutes().length > 0
|
||||
return this.draftEnabled ?? this.currentEnabled()
|
||||
}
|
||||
|
||||
private beginDraft(): Set<string> {
|
||||
this.draftEnabled ??= this.currentRoutes().length > 0
|
||||
this.draftEnabled ??= this.currentEnabled()
|
||||
this.draftSelected ??= new Set(this.currentRoutes().map(subagentModelKey))
|
||||
return this.draftSelected
|
||||
}
|
||||
@@ -233,7 +240,6 @@ export class SubagentModelSelectionCardController {
|
||||
}
|
||||
|
||||
private desiredRoutes(): AllowedSubagentModel[] {
|
||||
if (!this.enabled()) return []
|
||||
return this.candidates()
|
||||
.filter(candidate => candidate.selected)
|
||||
.map(({ provider, model }) => ({ provider, model }))
|
||||
@@ -241,17 +247,22 @@ export class SubagentModelSelectionCardController {
|
||||
|
||||
private async save(): Promise<void> {
|
||||
const snapshot = this.scope.getSnapshot()
|
||||
const desiredEnabled = this.enabled()
|
||||
const desired = this.desiredRoutes()
|
||||
if (this.disposed || snapshot.status !== 'ready' || !snapshot.writable || this.saving
|
||||
|| sameRoutes(this.currentRoutes(), desired) || (this.enabled() && desired.length === 0)) return
|
||||
|| (this.currentEnabled() === desiredEnabled && sameRoutes(this.currentRoutes(), desired))
|
||||
|| (desiredEnabled && desired.length === 0)) return
|
||||
const generation = this.saveGeneration
|
||||
this.saving = true
|
||||
this.saved = false
|
||||
this.failed = false
|
||||
this.publish()
|
||||
await this.scope.set('allowedModels', desired)
|
||||
await this.scope.mutate([
|
||||
{ op: 'set', path: ['enabled'], value: desiredEnabled },
|
||||
{ op: 'set', path: ['allowedModels'], value: desired },
|
||||
])
|
||||
if (generation !== this.saveGeneration) return
|
||||
const landed = sameRoutes(this.currentRoutes(), desired)
|
||||
const landed = this.currentEnabled() === desiredEnabled && sameRoutes(this.currentRoutes(), desired)
|
||||
this.saving = false
|
||||
this.saved = landed
|
||||
this.failed = !landed
|
||||
@@ -291,7 +302,7 @@ export class SubagentModelSelectionCardController {
|
||||
return {
|
||||
available: snapshot.status === 'ready',
|
||||
writable: snapshot.writable,
|
||||
dirty: !sameRoutes(current, desired),
|
||||
dirty: this.currentEnabled() !== enabled || !sameRoutes(current, desired),
|
||||
invalid: enabled && desired.length === 0,
|
||||
saving: this.saving,
|
||||
failed: this.failed,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import { stubSettingsScope, type StubSettingsScope } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { CardForm, numberField, textField } from '../src/client/card-form.ts'
|
||||
import { AgentLoopCardController, type AgentLoopSettings } from '../src/client/agent-loop-card-controller.ts'
|
||||
@@ -26,6 +27,18 @@ function acceptWrites<T>(host: StubSettingsScope<T>): void {
|
||||
host.set.mockImplementation((field: string, value: unknown) => {
|
||||
host.publish({ value: { ...section(), [field]: value } as T, user: { ...layer(), [field]: value } })
|
||||
})
|
||||
host.mutate.mockImplementation((ops: readonly SettingsPathOpView[]) => {
|
||||
const value = { ...section() }
|
||||
const user = { ...layer() }
|
||||
for (const op of ops) {
|
||||
const field = op.path[0]!
|
||||
if (op.op === 'set') {
|
||||
value[field] = op.value
|
||||
user[field] = op.value
|
||||
}
|
||||
}
|
||||
host.publish({ value: value as T, user })
|
||||
})
|
||||
host.unset.mockImplementation((field: string) => {
|
||||
const user = Object.fromEntries(Object.entries(layer()).filter(([key]) => key !== field))
|
||||
const base = host.scope.getSnapshot().base as Record<string, unknown> | undefined
|
||||
@@ -436,14 +449,14 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('loads adapter models and saves one exact route as a whole field', async () => {
|
||||
it('loads adapter models and saves the switch and routes atomically', async () => {
|
||||
const host = stubSettingsScope<SubagentModelSelectionSettings>()
|
||||
acceptWrites(host)
|
||||
const models = modelsApi({
|
||||
groups: [{ id: 'alpha', name: 'Alpha API', models: [{ id: 'fast', name: 'Fast' }] }],
|
||||
})
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, models.api)
|
||||
host.publish({ status: 'ready', writable: true, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: true, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
const face = controller.inject()
|
||||
|
||||
expect(face.hooks.subagentModelSelectionCard.getSnapshot().enabled).toBe(false)
|
||||
@@ -454,7 +467,10 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
face.toggleModel('alpha\0fast')
|
||||
face.save()
|
||||
await vi.waitFor(() => {
|
||||
expect(host.set).toHaveBeenCalledWith('allowedModels', [{ provider: 'alpha', model: 'fast' }])
|
||||
expect(host.mutate).toHaveBeenCalledWith([
|
||||
{ op: 'set', path: ['enabled'], value: true },
|
||||
{ op: 'set', path: ['allowedModels'], value: [{ provider: 'alpha', model: 'fast' }] },
|
||||
])
|
||||
})
|
||||
|
||||
expect(face.hooks.subagentModelSelectionCard.getSnapshot()).toMatchObject({
|
||||
@@ -472,7 +488,7 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
groups: [{ id: 'alpha', name: 'Alpha API', models: [{ id: 'fast', name: 'Fast' }] }],
|
||||
})
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, models.api)
|
||||
host.publish({ status: 'ready', writable: true, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: true, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
const face = controller.inject()
|
||||
|
||||
face.toggleEnabled()
|
||||
@@ -502,7 +518,7 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, models.api)
|
||||
host.publish({
|
||||
status: 'ready', writable: true,
|
||||
value: { allowedModels: [{ provider: 'alpha', model: 'fast' }] }, user: {},
|
||||
value: { enabled: true, allowedModels: [{ provider: 'alpha', model: 'fast' }] }, user: {},
|
||||
})
|
||||
const face = controller.inject()
|
||||
const state = () => face.hooks.subagentModelSelectionCard.getSnapshot()
|
||||
@@ -521,11 +537,38 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
expect(state()).toMatchObject({ dirty: false, enabled: true })
|
||||
})
|
||||
|
||||
it('retains selected routes when disabling and loads an already-ready enabled card', async () => {
|
||||
const host = stubSettingsScope<SubagentModelSelectionSettings>()
|
||||
acceptWrites(host)
|
||||
host.publish({
|
||||
status: 'ready', writable: true,
|
||||
value: { enabled: true, allowedModels: [{ provider: 'alpha', model: 'fast' }] }, user: {},
|
||||
})
|
||||
const models = modelsApi({
|
||||
groups: [{ id: 'alpha', name: 'Alpha API', models: [{ id: 'fast', name: 'Fast' }] }],
|
||||
})
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, models.api)
|
||||
const face = controller.inject()
|
||||
await vi.waitFor(() => { expect(models.models).toHaveBeenCalledOnce() })
|
||||
|
||||
face.toggleEnabled()
|
||||
face.save()
|
||||
await vi.waitFor(() => {
|
||||
expect(host.mutate).toHaveBeenCalledWith([
|
||||
{ op: 'set', path: ['enabled'], value: false },
|
||||
{ op: 'set', path: ['allowedModels'], value: [{ provider: 'alpha', model: 'fast' }] },
|
||||
])
|
||||
})
|
||||
expect(face.hooks.subagentModelSelectionCard.getSnapshot()).toMatchObject({
|
||||
enabled: false, dirty: false, saved: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('reports a directory error and retries it', async () => {
|
||||
const host = stubSettingsScope<SubagentModelSelectionSettings>()
|
||||
const models = modelsApi({ error: 'offline' })
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, models.api)
|
||||
host.publish({ status: 'ready', writable: true, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: true, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
const face = controller.inject()
|
||||
const state = () => face.hooks.subagentModelSelectionCard.getSnapshot()
|
||||
|
||||
@@ -541,16 +584,21 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
groups: [{ id: 'alpha', name: 'Alpha API', models: [{ id: 'fast', name: 'Fast' }] }],
|
||||
})
|
||||
const write = deferred<undefined>()
|
||||
const set = vi.fn(async (field: string, value: unknown) => {
|
||||
const mutate = vi.fn(async (ops: readonly SettingsPathOpView[]) => {
|
||||
await write.promise
|
||||
host.publish({ value: { [field]: value } })
|
||||
const enabled = ops.find(op => op.path[0] === 'enabled')
|
||||
const allowedModels = ops.find(op => op.path[0] === 'allowedModels')
|
||||
host.publish({ value: {
|
||||
enabled: enabled?.op === 'set' ? enabled.value as boolean : false,
|
||||
allowedModels: allowedModels?.op === 'set' ? allowedModels.value as never[] : [],
|
||||
} })
|
||||
})
|
||||
const controller = new SubagentModelSelectionCardController({ ...host.scope, set }, catalog.api)
|
||||
const controller = new SubagentModelSelectionCardController({ ...host.scope, mutate }, catalog.api)
|
||||
const face = controller.inject()
|
||||
|
||||
face.save()
|
||||
face.toggleModel('alpha\0fast')
|
||||
host.publish({ status: 'ready', writable: true, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: true, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
face.save()
|
||||
face.toggleEnabled()
|
||||
await vi.waitFor(() => { expect(face.hooks.subagentModelSelectionCard.getSnapshot().catalogStatus).toBe('ready') })
|
||||
@@ -565,12 +613,12 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
controller.dispose()
|
||||
write.resolve(undefined)
|
||||
await write.promise
|
||||
expect(set).toHaveBeenCalledOnce()
|
||||
expect(mutate).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('suppresses duplicate directory loads and late resolve or reject settlements', async () => {
|
||||
const host = stubSettingsScope<SubagentModelSelectionSettings>()
|
||||
host.publish({ status: 'ready', writable: true, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: true, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
|
||||
const pending = deferred<never>()
|
||||
const models = vi.fn(() => pending.promise)
|
||||
@@ -601,20 +649,20 @@ describe('SubagentModelSelectionCardController', () => {
|
||||
it('ignores writes while read-only and scope notifications after disposal', () => {
|
||||
const host = stubSettingsScope<SubagentModelSelectionSettings>()
|
||||
const controller = new SubagentModelSelectionCardController(host.scope, modelsApi().api)
|
||||
host.publish({ status: 'ready', writable: false, value: { allowedModels: [] }, user: {} })
|
||||
host.publish({ status: 'ready', writable: false, value: { enabled: false, allowedModels: [] }, user: {} })
|
||||
const face = controller.inject()
|
||||
|
||||
face.toggleEnabled()
|
||||
face.toggleModel('alpha\0fast')
|
||||
face.save()
|
||||
expect(host.set).not.toHaveBeenCalled()
|
||||
expect(host.mutate).not.toHaveBeenCalled()
|
||||
|
||||
controller.dispose()
|
||||
face.toggleEnabled()
|
||||
face.retryCatalog()
|
||||
face.save()
|
||||
host.publish({ value: { allowedModels: [{ provider: 'alpha', model: 'fast' }] } })
|
||||
expect(host.set).not.toHaveBeenCalled()
|
||||
host.publish({ value: { enabled: true, allowedModels: [{ provider: 'alpha', model: 'fast' }] } })
|
||||
expect(host.mutate).not.toHaveBeenCalled()
|
||||
expect(face.hooks.subagentModelSelectionCard.getSnapshot().enabled).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-settings/README.md
|
||||
README.md: a9a45d90eaa46502829ee6d2c1793b7dadb1f0a5
|
||||
README.zh.md: bd615cbb5a3236370b7bbc9fc735deeab6efd8ce
|
||||
README.md: beda3aec1750da77af86cebdf658bde8e3834a46
|
||||
README.zh.md: 8d4fb0983c8a0ba627411fd3b653114625530639
|
||||
|
||||
@@ -29,7 +29,7 @@ Feature plugins use this package to store and edit their preferences without re-
|
||||
|
||||
### Binding a namespace
|
||||
|
||||
A feature calls `ctx.settingsScope.bind(spec)` with a per-namespace spec and gets a scope derived from the shared document mirror. The scope snapshot carries the resolved section, composition `base`, raw `user`, revision, writability, and host/memory mode; a field is overridden when it is present in `user`, even when its value equals `base`, and `unset` clears that override. Writes go through the scope: one field path fenced by the namespace revision as `expectedRevision`, so a concurrent write from another surface is refused instead of silently overwritten.
|
||||
A feature calls `ctx.settingsScope.bind(spec)` with a per-namespace spec and gets a scope derived from the shared document mirror. The scope snapshot carries the resolved section, composition `base`, raw `user`, revision, writability, and host/memory mode; a field is overridden when it is present in `user`, even when its value equals `base`, and `unset` clears that override. Writes go through the scope: `set` and `unset` submit one operation, while `mutate` submits several ordered operations atomically. Each write is fenced by the namespace revision as `expectedRevision`, so a concurrent write from another surface is refused instead of silently overwritten.
|
||||
|
||||
### Filling the settings slots
|
||||
|
||||
@@ -55,7 +55,7 @@ The plugin injects `connection` and `remote` and owns the one `settings.describe
|
||||
|
||||
### Scope derivation
|
||||
|
||||
`ctx.settingsScope.bind(spec)` returns a per-namespace scope derived from the mirror on the caller's context: the scope's disposer belongs to the calling fiber, binding adds no wire read, and a row's activation never blocks on the settings transport. Writes stay per-scope with the namespace revision as `expectedRevision`; a committed write folds its answer in, a rejected or failed latest write triggers one recovery read, and a superseded one leaves recovery to its successor. The cold-boot read count is pinned by `../../../apps/web/tests/startup-rpc-budget.e2e.ts`; a new direct `settings.describe` caller in client code is a regression against it.
|
||||
`ctx.settingsScope.bind(spec)` returns a per-namespace scope derived from the mirror on the caller's context: the scope's disposer belongs to the calling fiber, binding adds no wire read, and a row's activation never blocks on the settings transport. Writes stay per-scope: `set` and `unset` are single-operation forms of `mutate`, which copies and queues several ordered field operations behind one namespace revision as `expectedRevision`. A committed mutation folds its answer in, a rejected or failed latest mutation triggers one recovery read, and a superseded one leaves recovery to its successor. The cold-boot read count is pinned by `../../../apps/web/tests/startup-rpc-budget.e2e.ts`; a new direct `settings.describe` caller in client code is a regression against it.
|
||||
|
||||
### Schema service
|
||||
|
||||
@@ -95,7 +95,6 @@ None; this package neither assembles nor sends a provider request.
|
||||
These limits define where the settings transport cannot reach; they are current package constraints.
|
||||
|
||||
- **Non-loopback pages get no durable settings** — this Client keeps Host persistence disabled there, so a scope starts `unavailable` and never crosses the wire; every row it backs is inert even though Connection authentication covers the API.
|
||||
- **One field per write** — `set` sends a single `set` op, so a row that must move two fields together has no transaction and publishes two revisions.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 绑定命名空间
|
||||
|
||||
功能调用 `ctx.settingsScope.bind(spec)` 并传入按命名空间的 spec,得到一个由共享文档镜像派生的 scope。scope 快照携带解析后的分区、组合 `base`、原始 `user`、revision、可写性以及 host/内存模式;字段只要出现在 `user` 中即视为覆盖,即使其值与 `base` 相等,`unset` 会清除该覆盖。写入经 scope 进行:单一字段路径以命名空间 revision 作为 `expectedRevision` 围栏,因此来自另一界面的并发写入会被拒绝,而不是被静默覆盖。
|
||||
功能调用 `ctx.settingsScope.bind(spec)` 并传入按命名空间的 spec,得到一个由共享文档镜像派生的 scope。scope 快照携带解析后的分区、组合 `base`、原始 `user`、revision、可写性以及 host/内存模式;字段只要出现在 `user` 中即视为覆盖,即使其值与 `base` 相等,`unset` 会清除该覆盖。写入经 scope 进行:`set` 与 `unset` 提交一个操作,`mutate` 则原子提交多个有序操作。每次写入都以命名空间 revision 作为 `expectedRevision` 围栏,因此来自另一界面的并发写入会被拒绝,而不是被静默覆盖。
|
||||
|
||||
### 填充设置 slot
|
||||
|
||||
@@ -55,7 +55,7 @@ kind: "package-reference"
|
||||
|
||||
### Scope 派生
|
||||
|
||||
`ctx.settingsScope.bind(spec)` 在调用方的 context 上返回一个由镜像派生的按命名空间 scope:scope 的 disposer 归调用方 fiber 所有,绑定不新增任何线路读取,某一行的激活绝不会阻塞在设置传输层上。写入仍归各 scope,以命名空间 revision 作为 `expectedRevision` 围栏;提交成功的写入把应答折回镜像,被拒绝或失败的最新写入触发一次恢复读取,被取代的写入把恢复留给后继者。冷启动读取次数由 `../../../apps/web/tests/startup-rpc-budget.e2e.ts` 钉住;客户端代码中新增直连 `settings.describe` 调用即是对它的回归。
|
||||
`ctx.settingsScope.bind(spec)` 在调用方的 context 上返回一个由镜像派生的按命名空间 scope:scope 的 disposer 归调用方 fiber 所有,绑定不新增任何线路读取,某一行的激活绝不会阻塞在设置传输层上。写入仍归各 scope:`set` 与 `unset` 是 `mutate` 的单操作形式,后者会复制操作列表,并把多个有序字段操作排在同一个作为 `expectedRevision` 的命名空间 revision 之后。提交成功的 mutation 把应答折回镜像,被拒绝或失败的最新 mutation 触发一次恢复读取,被取代的 mutation 把恢复留给后继者。冷启动读取次数由 `../../../apps/web/tests/startup-rpc-budget.e2e.ts` 钉住;客户端代码中新增直连 `settings.describe` 调用即是对它的回归。
|
||||
|
||||
### Schema 服务
|
||||
|
||||
@@ -95,7 +95,6 @@ kind: "package-reference"
|
||||
这些限制说明设置传输层够不到的地方;它们是当前包约束。
|
||||
|
||||
- **非 loopback 页面没有持久化设置**:本 Client 在那里禁用 Host 持久化,因此 scope 以 `unavailable` 起步且从不跨线路;尽管 Connection 认证覆盖 API,它支撑的每一行仍在那里无效。
|
||||
- **每次写入仅一个字段**:`set` 只发送单个 `set` op,因此需要同时改动两个字段的行没有事务可用,会发布两个 revision。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Settings-namespace scope contracts owned beside the settings transport.
|
||||
*/
|
||||
|
||||
import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
|
||||
/** Client-side sync state of one settings namespace. */
|
||||
export interface SettingsScopeSnapshot<T> {
|
||||
/**
|
||||
@@ -46,7 +48,8 @@ export interface SettingsScopeSpec<T> {
|
||||
/**
|
||||
* Reactive owner handle over one namespace's durable section — the browser
|
||||
* mirror of the Host-side `SettingsScope` owner seam. Domain services read
|
||||
* and observe the snapshot and route explicit user choices through `set`.
|
||||
* and observe the snapshot and route explicit user choices through its
|
||||
* mutation methods.
|
||||
*/
|
||||
export interface SettingsScope<T> {
|
||||
/** @returns the current sync snapshot (stable reference until the next change). */
|
||||
@@ -57,6 +60,13 @@ export interface SettingsScope<T> {
|
||||
* @returns the disposer removing this listener.
|
||||
*/
|
||||
subscribe(listener: () => void): () => void
|
||||
/**
|
||||
* Queue one atomic namespace mutation. All operations share one revision
|
||||
* fence, Host validation, persistence decision, and recovery read.
|
||||
* @param ops - ordered field operations copied when queued.
|
||||
* @returns settlement after the mutation and any latest-write recovery read.
|
||||
*/
|
||||
mutate(ops: readonly SettingsPathOpView[]): Promise<void>
|
||||
/**
|
||||
* Queue one field write. Rapid writes preserve mutation order, each carries
|
||||
* the latest known namespace revision, and only the latest settlement may
|
||||
|
||||
@@ -104,7 +104,7 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
* @returns settlement after the write and any latest-write recovery read.
|
||||
*/
|
||||
set(field: string, value: unknown): Promise<void> {
|
||||
return this.write({ op: 'set', path: [field], value: value as JsonValue })
|
||||
return this.mutate([{ op: 'set', path: [field], value: value as JsonValue }])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,16 +114,22 @@ export class SettingsScopeController<T> implements SettingsScope<T> {
|
||||
* @returns settlement after the clear and any latest-write recovery read.
|
||||
*/
|
||||
unset(field: string): Promise<void> {
|
||||
return this.write({ op: 'unset', path: [field] })
|
||||
return this.mutate([{ op: 'unset', path: [field] }])
|
||||
}
|
||||
|
||||
private write(op: SettingsPathOpView): Promise<void> {
|
||||
/**
|
||||
* Queue one atomic namespace mutation; see {@link SettingsScope.mutate}.
|
||||
* @param ops - ordered field operations copied when queued.
|
||||
* @returns settlement after the mutation and any latest-write recovery read.
|
||||
*/
|
||||
mutate(ops: readonly SettingsPathOpView[]): Promise<void> {
|
||||
const ownedOps = structuredClone(ops) as SettingsPathOpView[]
|
||||
const generation = ++this.writeGeneration
|
||||
return this.enqueue(async () => {
|
||||
const revision = this.pendingRevision ?? this.getSnapshot().revision
|
||||
let response: Awaited<ReturnType<SettingsFace['settings']['mutate']>>
|
||||
try {
|
||||
response = await this.api.settings.mutate(this.spec.namespace, [op], revision)
|
||||
response = await this.api.settings.mutate(this.spec.namespace, ownedOps, revision)
|
||||
} catch (_settingsWriteFailure) {
|
||||
await this.recover(generation)
|
||||
return
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { JsonValue, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import type {
|
||||
JsonValue, SettingsNamespaceView, SettingsPathOpView,
|
||||
} from '@deepseek-ai/dsh-api-remotes/client'
|
||||
import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||
import { SettingsSchemaService } from '../src/client/schema.ts'
|
||||
@@ -174,6 +176,31 @@ describe('SettingsScopeController', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('sends one copied multi-field mutation behind one revision fence', async () => {
|
||||
const describeCall = vi.fn().mockResolvedValueOnce(described({ preference: 'system' }, 7))
|
||||
const mutate = vi.fn().mockResolvedValueOnce(ok(view({ preference: 'dark' }, 8)))
|
||||
const { mirror, scope } = derivedScope({ describe: describeCall, mutate })
|
||||
await mirror.load()
|
||||
const ops: SettingsPathOpView[] = [
|
||||
{ op: 'set', path: ['enabled'], value: true },
|
||||
{ op: 'set', path: ['allowedModels'], value: [{ provider: 'alpha', model: 'fast' }] },
|
||||
]
|
||||
|
||||
const write = scope.mutate(ops)
|
||||
ops[0] = { op: 'unset', path: ['enabled'] }
|
||||
;(ops[1] as { value: Array<{ model: string }> }).value[0]!.model = 'changed'
|
||||
await write
|
||||
|
||||
expect(mutate).toHaveBeenCalledWith(
|
||||
'ui-test',
|
||||
[
|
||||
{ op: 'set', path: ['enabled'], value: true },
|
||||
{ op: 'set', path: ['allowedModels'], value: [{ provider: 'alpha', model: 'fast' }] },
|
||||
],
|
||||
7,
|
||||
)
|
||||
})
|
||||
|
||||
it('folds the latest write answer into the mirror so a sibling scope sees it', async () => {
|
||||
const describeCall = vi.fn().mockResolvedValueOnce(described({ preference: 'system' }, 4))
|
||||
const mutate = vi.fn().mockResolvedValueOnce(ok(view({ preference: 'dark' }, 5)))
|
||||
|
||||
@@ -2086,10 +2086,10 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
description: 'Singleton settings owner read by delegation tools when an Agent is published.',
|
||||
methods: [
|
||||
{
|
||||
signature: 'currentAllowedModels(): AllowedModelRoute[]',
|
||||
description: 'Read a detached route policy for the next eligible Agent publication.',
|
||||
signature: 'current(): SubagentModelSelectionSettings',
|
||||
description: 'Read a detached selection preference for the next eligible Agent publication.',
|
||||
parameters: [],
|
||||
returns: 'exact allowed routes; an empty list disables model-facing selection.',
|
||||
returns: 'the enabled state and exact allowed routes.',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -3362,10 +3362,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'AgentStatus',
|
||||
declaration: 'export type AgentStatus = \'idle\' | \'running\';',
|
||||
},
|
||||
{
|
||||
name: 'AllowedModelRoute',
|
||||
declaration: 'export interface AllowedModelRoute {\n readonly provider: string;\n readonly model: string;\n}',
|
||||
},
|
||||
{
|
||||
name: 'ApiKeyRecord',
|
||||
declaration: 'export interface ApiKeyRecord {\n readonly kind: \'api-key\';\n readonly key?: string;\n readonly env?: Readonly<Record<string, string>>;\n}',
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/subagent/tool-subagent/README.md
|
||||
README.md: 72874e94b792ef3e75e4fa0d6ed7475808f209a0
|
||||
README.zh.md: 1cab6922b93c0fd8776158f1659927d0c4aa4126
|
||||
README.md: 5225aa28719e92b2158951aa145d9335a67ff1a2
|
||||
README.zh.md: 702cf46d36e15c7524c3dffc5dd46035547ff768
|
||||
|
||||
@@ -44,8 +44,7 @@ Load the subagent service, an in-process or remote backend, and this tool; then
|
||||
|---|---|---|
|
||||
| `provider` | required | Provider name on `ctx.subagents` (e.g. `spawn`, `fork`, `acp`) |
|
||||
| `toolName` | `subagent` | Model-facing tool name; distinct for every loaded instance |
|
||||
| `enableModelSelection` | `false` | Statically expose child LLM selection fields and register `list_subagent_models`; requires provider `agentOptions` support |
|
||||
| `modelSelectionSettings` | `false` | Sample the Host preference for each new top-level Session; mutually exclusive with `enableModelSelection` and valid only in Agent scope |
|
||||
| `modelSelectionSettings` | `false` | Sample the Host's exact-route authorization preference for each new top-level Session; valid only in Agent scope and requires provider `agentOptions` support |
|
||||
| `enableRunInBackground` | `true` | Expose `run_in_background`; disabling also rejects forced background calls |
|
||||
| `backgroundMode` | `one-shot` | Background policy: `one-shot` defaults calls to foreground; `continuable` defaults them to background and requires the provider's `prepareContinuable` capability |
|
||||
| `agentOptions` | — | Configured child `provider`, `model`, adapter-owned `reasoningEffort`, and positive `maxTokens` defaults; requires provider `agentOptions` support and overlays any provider-owned route defaults |
|
||||
@@ -65,7 +64,7 @@ Under `continuable` policy, an omitted or `true` `run_in_background` starts a du
|
||||
|
||||
### Selecting a child LLM
|
||||
|
||||
Set `enableModelSelection: true` to expose optional `provider`, `model`, and `reasoning_effort` fields and register the shared `list_subagent_models` tool. Alternatively, set `modelSelectionSettings: true` to sample the Host's `subagent-model-selection.enabled` preference when each top-level Session is composed. That decision is recorded in the Session, inherited by child Sessions, and unchanged by later settings edits. These modes are mutually exclusive and require a backend that advertises `agentOptions`; ACP, Codex, and Claude Code reject them, while DSH SDK supports route selection.
|
||||
Set `modelSelectionSettings: true` to sample the Host's `subagent-model-selection` preference when each top-level Session is composed. When enabled, its non-empty exact provider/model route list is recorded in the Session, inherited by child Sessions, and unchanged by later settings edits. The tool then exposes optional `provider`, `model`, and `reasoning_effort` fields and registers the shared `list_subagent_models` tool. This mode requires a backend that advertises `agentOptions`; both in-process backends and DSH SDK support it, while ACP, Codex, and Claude Code reject it rather than ignore it.
|
||||
|
||||
A call supplies `provider` and `model` together, or supplies only an effort when configured, parent, or provider-owned defaults provide the route. Static `provider.agentRouteDefaults`, when present, form the provider/model baseline; tool configuration and model fields overlay it before route-aware effort merging and exact-route preflight. Providers without these defaults use compatible values from the parent's latest logged request, then the parent's creation options before its first request, while retaining the configured `maxTokens`. Changing the route without an explicit effort clears the inherited route-owned effort, so the selected model resolves its default. The live LLM adapter validates the effective route before child creation. Catalog membership remains advisory, so a model can use an unlisted id when its adapter accepts it.
|
||||
|
||||
@@ -132,7 +131,7 @@ Read these pages when the package-level contract is not enough; they move from t
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The generated default [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent) under this instance's configured name while its provider exists. Model selection adds `provider`, `model`, and `reasoning_effort` plus inheritance and selection guidance. The tool and prompt descriptions follow whether the child inherits the conversation. Enabled background mode adds `run_in_background`: continuable mode documents its `true` default and the settlement notice, while one-shot mode documents its `false` default and job collection. A tool restriction removes both the schema and the guidance section.
|
||||
The generated default [`subagent` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-subagent) under this instance's configured name while its provider exists. An enabled Session policy adds `provider`, `model`, and `reasoning_effort` plus inheritance and selection guidance; the provider must support `agentOptions`. Provider context inheritance changes the tool and prompt descriptions. Enabled background mode adds `run_in_background`: continuable mode documents its `true` default, runtime settlement notice, and explicit foreground override, while one-shot mode documents its `false` default and the job id collected with `job_output` or stopped with `job_kill`. While the tool is visible in an assembly's scope, a `tool:<toolName>` system-prompt section tells the model to start independent continuable delegations together, keep working while they run, and choose foreground only when its next action depends on the result; a tool restriction removes both its schema and this guidance.
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -146,7 +145,7 @@ Prefix-stable while provider instances and their configuration are unchanged. Ad
|
||||
|
||||
#### What the model sees
|
||||
|
||||
An instance with static `enableModelSelection: true`, or a settings-controlled instance whose Session policy is non-empty, exposes the child LLM selection fields and `list_subagent_models`. Calls reject while the optional `ctx.llm` service is unavailable. Static enablement returns the live adapter directory. A settings-controlled instance returns only registered providers and advertised models in its exact route policy; an exact lookup must also be allowed before it resolves the model's reasoning efforts and default. Execution independently enforces the same policy.
|
||||
A settings-controlled instance whose Session carries a policy exposes the child LLM selection fields and `list_subagent_models`. Calls reject while the optional `ctx.llm` service is unavailable. Discovery returns only registered providers and advertised models in the exact route policy; an unauthorized provider is rejected before its adapter catalog is called, and an exact lookup must be allowed before it resolves the model's reasoning efforts and default. Execution independently enforces the same policy.
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -213,8 +212,8 @@ These limits define what this tool does not return or enforce; they are current
|
||||
|
||||
- **Background runs expose no result through this tool** — a one-shot task's final output is collected through the generic task surface, and a continuable child's output stays in its own session, read by its subagent id. The settlement notice states how that child ended and carries any final assistant message, but it is not this call's return value and cannot be awaited here.
|
||||
- **Duplicate names across waiting one-shot instances are detected late** (`TODO(subagent-dup-toolname)`) — continuable instances reserve their prompt-section name during plugin application, but preventing provider-registration rollback for waiting one-shot instances requires a registry of intended names.
|
||||
- **Shipped fork tools cannot select a child LLM route** — they inherit the parent's provider and model to keep the copied conversation prefix eligible for KV Cache reuse. Re-enable selection only when route changes preserve reuse or expose a bounded recomputation cost.
|
||||
- **Non-routing child policy is fixed per instance** — another persona, tool filter, or depth cap requires another distinctly named tool. LLM selection requires static enablement or an enabled per-Session preference and a provider that advertises `agentOptions`; ACP, Codex, and Claude Code reject it rather than ignore it.
|
||||
- **Shipped fork tools cannot select a child LLM route** — they inherit the parent's provider and model to keep the copied conversation prefix eligible for KV Cache reuse. Re-enable the fields only when route changes preserve reuse or expose a bounded recomputation cost.
|
||||
- **Non-routing child policy is fixed per instance** — another persona, tool filter, or depth cap requires another distinctly named tool. LLM provider/model/reasoning-effort selection requires an enabled per-Session preference and a subagent provider that advertises `agentOptions`; out-of-process providers currently reject enabling it rather than ignore it.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -44,8 +44,7 @@ kind: "package-reference"
|
||||
|---|---|---|
|
||||
| `provider` | 必填 | `ctx.subagents` 上的提供方名称(如 `spawn`、`fork`、`acp`) |
|
||||
| `toolName` | `subagent` | 面向模型的工具名称;每个已加载实例必须不同 |
|
||||
| `enableModelSelection` | `false` | 静态公开子级 LLM 选择字段并注册 `list_subagent_models`;要求提供方支持 `agentOptions` |
|
||||
| `modelSelectionSettings` | `false` | 为每个新顶层 Session 读取宿主偏好;与 `enableModelSelection` 互斥,且只在 Agent 作用域内有效 |
|
||||
| `modelSelectionSettings` | `false` | 为每个新顶层 Session 读取宿主的精确路由授权偏好;只在 Agent 作用域内有效,并要求提供方支持 `agentOptions` |
|
||||
| `enableRunInBackground` | `true` | 公开 `run_in_background`;禁用时也会拒绝强制后台调用 |
|
||||
| `backgroundMode` | `one-shot` | 后台策略:`one-shot` 默认前台调用;`continuable` 默认后台调用,并要求提供方具备 `prepareContinuable` 能力 |
|
||||
| `agentOptions` | — | 配置的子级 `provider`、`model`、适配器所有的 `reasoningEffort` 与正整数 `maxTokens` 默认值;要求提供方支持 `agentOptions`,并会覆盖提供方持有的路由默认值 |
|
||||
@@ -65,7 +64,7 @@ kind: "package-reference"
|
||||
|
||||
### 选择子级 LLM
|
||||
|
||||
设置 `enableModelSelection: true` 可公开可选的 `provider`、`model` 与 `reasoning_effort` 字段,并注册共享的 `list_subagent_models` 工具。也可以设置 `modelSelectionSettings: true`,在组合每个顶层 Session 时读取宿主的 `subagent-model-selection.enabled` 偏好。该决定会记录进 Session、由子 Session 继承,后续设置编辑不会改变它。这两种模式互斥,且要求后端声明 `agentOptions`;ACP、Codex 与 Claude Code 会拒绝它们,DSH SDK 则支持路由选择。
|
||||
设置 `modelSelectionSettings: true`,即可在组合每个顶层 Session 时读取宿主的 `subagent-model-selection` 偏好。启用后,非空的精确 provider/model 路由列表会记录进 Session、由子 Session 继承,后续设置编辑不会改变它。工具随后公开可选的 `provider`、`model` 与 `reasoning_effort` 字段,并注册共享的 `list_subagent_models` 工具。此模式要求后端声明 `agentOptions`;两个进程内后端和 DSH SDK 支持该能力,而 ACP、Codex 与 Claude Code 会拒绝它,而不是忽略它。
|
||||
|
||||
一次调用需同时提供 `provider` 与 `model`;当配置值、父 agent 值或提供方持有的默认值能提供路由时,也可只提供推理等级。静态的 `provider.agentRouteDefaults` 在存在时构成提供方/模型基线;工具配置与模型字段会在路由相关强度合并和确切路由预检前覆盖它。没有这些默认值的提供方会使用父 agent 最新已记录请求中的兼容值,再使用父级首次请求前的创建选项,并保留配置的 `maxTokens`。更改路由但未显式提供推理等级时,会清除继承的路由自有等级,使所选模型解析自己的默认值。实时 LLM 适配器在创建子 agent 前校验有效路由。目录成员资格只提供建议,因此适配器接受时,模型可以使用未列出的 id。
|
||||
|
||||
@@ -132,7 +131,7 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
当提供方存在时,以当前实例配置的名称公开已生成的默认 [`subagent` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-subagent)。模型选择会添加 `provider`、`model` 与 `reasoning_effort`,以及继承和选择指引。工具与提示词描述会随子 agent 是否继承对话而调整。启用后台模式会添加 `run_in_background`:可继续模式记录其默认值为 `true` 及结算通知,一次性模式记录其默认值为 `false` 及任务收集。工具限制会同时移除 schema 与指导 section。
|
||||
当提供方存在时,以当前实例配置的名称公开已生成的默认 [`subagent` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-subagent)。启用的 Session 策略会添加 `provider`、`model` 与 `reasoning_effort`,以及继承和选择指引;提供方必须支持 `agentOptions`。提供方是否继承上下文会改变工具描述和提示词描述。启用后台模式会添加 `run_in_background`:可继续模式会记录其默认值为 `true`、运行时结算通知与显式前台覆盖;一次性模式会记录其默认值为 `false`,以及用 `job_output` 收集或用 `job_kill` 停止的 job id。当工具在本次组装的作用域中可见时,一个 `tool:<toolName>` 系统提示词 section 会指示模型同时启动相互独立的可继续委派、在它们运行时继续工作,并且仅当下一步动作依赖结果时选择前台;工具限制会同时移除其 schema 和这段指引。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
@@ -146,7 +145,7 @@ kind: "package-reference"
|
||||
|
||||
#### 模型看到什么
|
||||
|
||||
静态配置 `enableModelSelection: true` 的实例,或 Session 策略非空的 settings 控制实例,会公开子级 LLM 选择字段与 `list_subagent_models`。可选 `ctx.llm` 服务不可用时,调用会失败。静态启用返回实时适配器目录。settings 控制实例只返回其精确路由策略中的已注册提供方与已公布模型;精确查询也必须先获准,才会解析模型的推理强度与默认值。执行阶段会独立强制同一策略。
|
||||
Session 携带策略的 settings 控制实例会公开子级 LLM 选择字段与 `list_subagent_models`。可选 `ctx.llm` 服务不可用时,调用会失败。发现只返回精确路由策略中的已注册提供方与已公布模型;未授权提供方会在调用其适配器目录前被拒绝,精确查询也必须先获准,才会解析模型的推理强度与默认值。执行阶段会独立强制同一策略。
|
||||
|
||||
#### Token 影响
|
||||
|
||||
@@ -213,8 +212,8 @@ Use subagent in the background by default. Start independent delegations togethe
|
||||
|
||||
- **后台运行不通过本工具公开结果**——一次性任务的最终输出通过通用 Task 接口收集,可继续子 agent 的输出留在其自身会话中,按其 subagent id 读取。结算通知会说明该子 agent 如何结束,并携带可能存在的最终 assistant 消息,但它不是本次调用的返回值,也无法在此等待。
|
||||
- **等待中的一次性实例较晚才发现重复名称**(`TODO(subagent-dup-toolname)`)——可继续实例会在插件应用期间预留提示词 section 名称,但若要阻止等待中的一次性实例回滚提供方注册,仍需要一份预期名称注册表。
|
||||
- **随附 fork 工具不能选择子级 LLM 路由**——它们继承父级提供方与模型,使复制的对话前缀仍有资格复用 KV Cache。仅当路由变更能保留复用或公开有界重算成本时,才重新启用选择。
|
||||
- **非路由子 agent 策略按实例固定**——另一个 persona、工具过滤器或深度上限需要另一个名称不同的工具。LLM 选择要求静态启用或已启用的逐 Session 偏好,且提供方必须声明 `agentOptions`;ACP、Codex 与 Claude Code 会拒绝它,而不是忽略它。
|
||||
- **随附 fork 工具无法选择子级 LLM 路由**:它们会继承父级的提供方与模型,使复制的对话前缀仍可供 KV Cache 复用。只有在路由变化仍能保留复用,或接口能公开一项有界的重算成本时,才重新启用这些字段。
|
||||
- **每个实例的非路由子 agent 策略固定**:其他 persona、工具过滤器或深度上限都需要另一个名称不同的工具。LLM 提供方/模型/推理强度选择要求每 Session 偏好已启用,并要求 subagent 提供方声明 `agentOptions`;进程外提供方目前会拒绝启用它,而不是忽略它。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -54,12 +54,9 @@ export interface Config {
|
||||
* a distinct name.
|
||||
*/
|
||||
toolName?: string
|
||||
/** Let the model discover and select the child LLM route (default false). */
|
||||
enableModelSelection?: boolean
|
||||
/**
|
||||
* Sample the Host `subagent-model-selection` user setting for each new
|
||||
* top-level session and inherit that decision in its child sessions. Mutually
|
||||
* exclusive with `enableModelSelection`.
|
||||
* top-level session and inherit that decision in its child sessions.
|
||||
*/
|
||||
modelSelectionSettings?: boolean
|
||||
/**
|
||||
@@ -109,7 +106,6 @@ export interface Config {
|
||||
export const Config: z<Config> = z.object({
|
||||
provider: z.string().required(),
|
||||
toolName: z.string().default('subagent'),
|
||||
enableModelSelection: z.boolean().default(false),
|
||||
modelSelectionSettings: z.boolean().default(false),
|
||||
enableRunInBackground: z.boolean().default(true),
|
||||
backgroundMode: z.union(['one-shot', 'continuable'] as const).default('one-shot'),
|
||||
@@ -317,14 +313,11 @@ export function apply(ctx: Context, config: Config): void {
|
||||
if (config.toolFilter !== undefined && config.toolFilter.allow === undefined && config.toolFilter.deny === undefined) {
|
||||
throw new Error('tool-subagent: `toolFilter` is configured but names neither `allow` nor `deny` — remove the key or fill the filter')
|
||||
}
|
||||
if (config.enableModelSelection === true && config.modelSelectionSettings === true) {
|
||||
throw new Error('tool-subagent: `enableModelSelection` and `modelSelectionSettings` are mutually exclusive')
|
||||
}
|
||||
const backgroundEnabled = config.enableRunInBackground !== false
|
||||
const continuable = (config.backgroundMode ?? 'one-shot') === 'continuable'
|
||||
const toolName = config.toolName ?? 'subagent'
|
||||
|
||||
const modelSelectionCapable = config.enableModelSelection === true || config.modelSelectionSettings === true
|
||||
const modelSelectionCapable = config.modelSelectionSettings === true
|
||||
|
||||
const assertSubagentProviderConfiguration = (subagentProvider: SubagentProvider): void => {
|
||||
if (typeof config.maxDepth === 'number' && !subagentProvider.capabilities.depthLimit) {
|
||||
@@ -607,7 +600,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}
|
||||
|
||||
if (config.modelSelectionSettings !== true) {
|
||||
install(ctx, config.enableModelSelection === true ? { kind: 'unrestricted' } : undefined)
|
||||
install(ctx, undefined)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -633,12 +626,12 @@ export function apply(ctx: Context, config: Config): void {
|
||||
const parent = ctx.get('agents')?.get(parentId)
|
||||
allowedModels = parent === undefined ? undefined : subagentModelSelectionPolicy(parent.session)
|
||||
} else if (agent.session.firstLiveSeq === 0) {
|
||||
const current = settings.currentAllowedModels()
|
||||
allowedModels = current.length === 0 ? undefined : current
|
||||
const current = settings.current()
|
||||
allowedModels = current.enabled ? current.allowedModels : undefined
|
||||
}
|
||||
}
|
||||
if (allowedModels !== undefined) recordSubagentModelSelection(agent.session, allowedModels)
|
||||
return allowedModels === undefined ? undefined : { kind: 'allowlist', routes: allowedModels }
|
||||
return allowedModels === undefined ? undefined : { routes: allowedModels }
|
||||
}
|
||||
|
||||
const agent = ctx.agent
|
||||
|
||||
@@ -12,11 +12,18 @@ interface ListSubagentModelsRequest {
|
||||
}
|
||||
|
||||
/** Resolve one registered provider with a model-correctable diagnostic. */
|
||||
function registeredProvider(llm: LlmRuntime, providerId: string): LlmProviderInfo {
|
||||
function registeredProvider(
|
||||
llm: LlmRuntime,
|
||||
policy: ModelSelectionPolicy,
|
||||
providerId: string,
|
||||
): LlmProviderInfo {
|
||||
const providers = llm.listProviders()
|
||||
const provider = providers.find(candidate => candidate.id === providerId)
|
||||
if (provider !== undefined) return provider
|
||||
const available = providers.map(candidate => candidate.id).join(', ') || '(none)'
|
||||
const available = providers
|
||||
.filter(candidate => policy.routes.some(route => route.provider === candidate.id))
|
||||
.map(candidate => candidate.id)
|
||||
.join(', ') || '(none)'
|
||||
throw new Error(`LLM provider "${providerId}" is not registered; available providers: ${available}`)
|
||||
}
|
||||
|
||||
@@ -40,24 +47,27 @@ async function listSubagentModels(
|
||||
throw new Error('`model` requires `provider`')
|
||||
}
|
||||
if (request.provider === undefined) {
|
||||
const providers = llm.listProviders().filter(provider => policy.kind === 'unrestricted'
|
||||
|| policy.routes.some(route => route.provider === provider.id))
|
||||
const providers = llm.listProviders()
|
||||
.filter(provider => policy.routes.some(route => route.provider === provider.id))
|
||||
return providers.length === 0
|
||||
? '(no LLM providers)'
|
||||
: providers.map(provider => `${provider.id} — ${provider.name}`).join('\n')
|
||||
}
|
||||
if (request.provider.length === 0) throw new Error('`provider` must be non-empty')
|
||||
const provider = registeredProvider(llm, request.provider)
|
||||
const allowedRoutes = policy.routes.filter(route => route.provider === request.provider)
|
||||
if (allowedRoutes.length === 0) {
|
||||
throw new Error(`LLM provider "${request.provider}" is not allowed for this Session`)
|
||||
}
|
||||
const provider = registeredProvider(llm, policy, request.provider)
|
||||
if (request.model === undefined) {
|
||||
const models = (await llm.listModels(provider.id)).filter(model => policy.kind === 'unrestricted'
|
||||
|| policy.routes.some(route => route.provider === provider.id && route.model === model.id))
|
||||
const models = (await llm.listModels(provider.id))
|
||||
.filter(model => allowedRoutes.some(route => route.model === model.id))
|
||||
return models.length === 0
|
||||
? `(no advertised models for ${provider.id})`
|
||||
: models.map(model => modelLine(provider.id, model)).join('\n')
|
||||
}
|
||||
if (request.model.length === 0) throw new Error('`model` must be non-empty')
|
||||
if (policy.kind === 'allowlist'
|
||||
&& !policy.routes.some(route => route.provider === provider.id && route.model === request.model)) {
|
||||
if (!allowedRoutes.some(route => route.model === request.model)) {
|
||||
throw new Error(`child LLM route "${provider.id}/${request.model}" is not allowed for this Session`)
|
||||
}
|
||||
const model = await llm.resolveModelInfo(provider.id, request.model, signal)
|
||||
|
||||
@@ -21,17 +21,22 @@ export const SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE = settingsNamespace('su
|
||||
|
||||
/** Stored user preference; the shipped composition defaults it off. */
|
||||
export interface SubagentModelSelectionSettings {
|
||||
/** Whether newly composed top-level Sessions receive model selection. */
|
||||
enabled: boolean
|
||||
/** Exact child LLM routes offered to newly composed top-level Sessions. */
|
||||
allowedModels: AllowedModelRoute[]
|
||||
}
|
||||
|
||||
/** Schema served to settings clients for the opt-in preference. */
|
||||
export const SUBAGENT_MODEL_SELECTION_SETTINGS_SCHEMA: z<SubagentModelSelectionSettings> = z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
allowedModels: z.array(AllowedModelRouteSchema).default([]),
|
||||
})
|
||||
|
||||
/** Optional deployment base for the preference. */
|
||||
export interface Config {
|
||||
/** Initial enabled state inherited when the user document does not override it. */
|
||||
enabled?: boolean
|
||||
/** Initial route list inherited when the user document does not override it. */
|
||||
allowedModels?: AllowedModelRoute[]
|
||||
}
|
||||
@@ -39,6 +44,7 @@ export interface Config {
|
||||
/** Singleton settings owner read by delegation tools when an Agent is published. */
|
||||
export class SubagentModelSelectionConfig extends Service {
|
||||
static Config: z<Config> = z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
allowedModels: z.array(AllowedModelRouteSchema).default([]),
|
||||
})
|
||||
|
||||
@@ -48,8 +54,11 @@ export class SubagentModelSelectionConfig extends Service {
|
||||
super(ctx, 'subagentModelSelection')
|
||||
// Cordis supplies the schema default; the fallback also covers direct construction.
|
||||
/* v8 ignore next */
|
||||
const entry: SubagentModelSelectionSettings = { allowedModels: config.allowedModels ?? [] }
|
||||
assertAllowedModelRoutes(entry.allowedModels)
|
||||
const entry: SubagentModelSelectionSettings = {
|
||||
enabled: config.enabled ?? false,
|
||||
allowedModels: config.allowedModels ?? [],
|
||||
}
|
||||
this.validate(entry)
|
||||
this.source = () => entry
|
||||
installSettingsSection(
|
||||
ctx,
|
||||
@@ -58,7 +67,7 @@ export class SubagentModelSelectionConfig extends Service {
|
||||
entry,
|
||||
{
|
||||
setSource: (source) => { this.source = source },
|
||||
validate: (value) => { assertAllowedModelRoutes(value.allowedModels) },
|
||||
validate: (value) => { this.validate(value) },
|
||||
// Consumers sample at Agent publication, so a settings update never
|
||||
// rebuilds the tool definitions of an Agent that is already running.
|
||||
onChange: () => {},
|
||||
@@ -67,11 +76,22 @@ export class SubagentModelSelectionConfig extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a detached route policy for the next eligible Agent publication.
|
||||
* @returns exact allowed routes; an empty list disables model-facing selection.
|
||||
* Read a detached selection preference for the next eligible Agent publication.
|
||||
* @returns the enabled state and exact allowed routes.
|
||||
*/
|
||||
currentAllowedModels(): AllowedModelRoute[] {
|
||||
return this.source().allowedModels.map(route => ({ ...route }))
|
||||
current(): SubagentModelSelectionSettings {
|
||||
const current = this.source()
|
||||
return {
|
||||
enabled: current.enabled,
|
||||
allowedModels: current.allowedModels.map(route => ({ ...route })),
|
||||
}
|
||||
}
|
||||
|
||||
private validate(value: SubagentModelSelectionSettings): void {
|
||||
assertAllowedModelRoutes(value.allowedModels)
|
||||
if (value.enabled && value.allowedModels.length === 0) {
|
||||
throw new Error('enabled subagent model selection requires at least one allowed model')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ export const AllowedModelRouteSchema: z<AllowedModelRoute> = z.object({
|
||||
})
|
||||
|
||||
/** Route-selection authority captured by one delegation definition. */
|
||||
export type ModelSelectionPolicy =
|
||||
| { readonly kind: 'unrestricted' }
|
||||
| { readonly kind: 'allowlist'; readonly routes: readonly AllowedModelRoute[] }
|
||||
export interface ModelSelectionPolicy {
|
||||
/** Exact provider/model routes authorized for explicit selection. */
|
||||
readonly routes: readonly AllowedModelRoute[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable identity for one provider/model pair.
|
||||
@@ -132,7 +133,7 @@ export function assertAllowedModelSelection(
|
||||
requested: AgentOptions | undefined,
|
||||
request: DelegationModelRequest,
|
||||
): void {
|
||||
if (policy?.kind !== 'allowlist' || !hasDelegationModelRequest(request)) return
|
||||
if (policy === undefined || !hasDelegationModelRequest(request)) return
|
||||
const provider = requested?.provider ?? parentOptions.provider
|
||||
const model = requested?.model ?? parentOptions.model
|
||||
if (provider === undefined || model === undefined) {
|
||||
|
||||
@@ -3,10 +3,13 @@ import LlmRuntime, { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRuntime from '@deepseek-ai/dsh-tools'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
|
||||
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
|
||||
import SubagentRuntime from '@deepseek-ai/dsh-subagent'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import * as mock from './scripted-provider.ts'
|
||||
import * as tool from '../src/index.ts'
|
||||
import SubagentModelSelectionConfig from '../src/model-selection-settings.ts'
|
||||
|
||||
/** Shared non-aborted tool signal for package-local integration tests. */
|
||||
export const testToolSignal = new AbortController().signal
|
||||
@@ -18,17 +21,58 @@ export function fakeAgent(id = 'parent-1'): Agent {
|
||||
}
|
||||
|
||||
/** Mount the real tool and service stack around one scripted subagent provider. */
|
||||
export async function setup(toolConfig: tool.Config, mockConfig: Partial<mock.Config> = {}): Promise<Context> {
|
||||
const setupAgents = new WeakMap<Context, Agent>()
|
||||
let setupAgentCounter = 0
|
||||
|
||||
/** Test-only opt-in translated to the real Host setting and Session path. */
|
||||
type SetupConfig = tool.Config & { withModelSelection?: boolean }
|
||||
|
||||
const TEST_ALLOWED_MODELS = [
|
||||
'allowed-model', 'configured-model', 'current-model', 'fast-model', 'other-model',
|
||||
'parent-model', 'unlisted-model',
|
||||
].flatMap(model => [
|
||||
{ provider: 'alpha', model },
|
||||
{ provider: 'current-provider', model },
|
||||
{ provider: 'missing', model },
|
||||
])
|
||||
|
||||
export async function setup(toolConfig: SetupConfig, mockConfig: Partial<mock.Config> = {}): Promise<Context> {
|
||||
const ctx = new Context()
|
||||
const { withModelSelection, ...config } = toolConfig
|
||||
if (withModelSelection === true) {
|
||||
await ctx.plugin(SubagentModelSelectionConfig, {
|
||||
enabled: true,
|
||||
allowedModels: TEST_ALLOWED_MODELS,
|
||||
})
|
||||
await mountAgentLoopTestDependencies(ctx)
|
||||
await ctx.plugin(AgentLoop, { agents: [] })
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
|
||||
const handle = await ctx.agents.create({
|
||||
sessionId: SessionId(`model-selection-setup-${++setupAgentCounter}`),
|
||||
setup: async (agentCtx) => {
|
||||
await agentCtx.plugin(tool, { ...config, modelSelectionSettings: true })
|
||||
},
|
||||
})
|
||||
setupAgents.set(ctx, handle.agent)
|
||||
return ctx
|
||||
}
|
||||
await ctx.plugin(LlmRuntime)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
await mock.mountScriptedProvider(ctx, { name: 'mock', ...mockConfig })
|
||||
await ctx.plugin(tool, toolConfig)
|
||||
await ctx.plugin(tool, config)
|
||||
return ctx
|
||||
}
|
||||
|
||||
/** Return the real Agent created for a settings-controlled setup. */
|
||||
export function modelSelectionSetupAgent(ctx: Context): Agent {
|
||||
const agent = setupAgents.get(ctx)
|
||||
if (agent === undefined) throw new Error('context has no model-selection setup Agent')
|
||||
return agent
|
||||
}
|
||||
|
||||
let callCounter = 0
|
||||
|
||||
/** Execute the registered subagent tool through the real ToolRuntime pipeline. */
|
||||
@@ -40,7 +84,7 @@ export function callSubagent(
|
||||
// Distinguish "no override" (use a default agent) from an explicit
|
||||
// `{ agent: undefined }` (test the no-agent path). Under
|
||||
// exactOptionalPropertyTypes the key is omitted rather than set to undefined.
|
||||
const agent = 'agent' in over ? over.agent : fakeAgent()
|
||||
const agent = 'agent' in over ? over.agent : setupAgents.get(ctx) ?? fakeAgent()
|
||||
return ctx.tools.execute({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId(`call-${++callCounter}`),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import LlmRuntime, {
|
||||
ToolCallId,
|
||||
@@ -57,14 +57,18 @@ class CatalogAdapter extends LlmAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async function setupListTool() {
|
||||
async function setupListTool(routes = [
|
||||
{ provider: 'alpha', model: 'fast' },
|
||||
{ provider: 'alpha', model: 'plain' },
|
||||
{ provider: 'beta', model: 'fast' },
|
||||
{ provider: 'beta', model: 'plain' },
|
||||
]) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(LlmRuntime)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
const fiber = await ctx.plugin(tool, { provider: 'unused', enableModelSelection: true })
|
||||
return { ctx, fiber }
|
||||
registerListSubagentModels(ctx, { routes })
|
||||
return ctx
|
||||
}
|
||||
|
||||
async function setupAllowedListTool() {
|
||||
@@ -73,7 +77,6 @@ async function setupAllowedListTool() {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
registerListSubagentModels(ctx, {
|
||||
kind: 'allowlist',
|
||||
routes: [
|
||||
{ provider: 'alpha', model: 'fast' },
|
||||
{ provider: 'alpha', model: 'unlisted' },
|
||||
@@ -110,23 +113,21 @@ describe('list_subagent_models', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
await ctx.plugin(tool, { provider: 'unused', enableModelSelection: true })
|
||||
registerListSubagentModels(ctx, { routes: [{ provider: 'alpha', model: 'fast' }] })
|
||||
const result = await call(ctx, {})
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('`llm` service is unavailable')
|
||||
})
|
||||
|
||||
it('rejects two discovery-owning instances in one tool scope', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
await expect(ctx.plugin(tool, {
|
||||
provider: 'another-unused',
|
||||
toolName: 'subagent_other',
|
||||
enableModelSelection: true,
|
||||
}).then(() => undefined)).rejects.toThrow('tool "list_subagent_models" is already registered')
|
||||
const ctx = await setupListTool()
|
||||
expect(() => {
|
||||
registerListSubagentModels(ctx, { routes: [{ provider: 'alpha', model: 'fast' }] })
|
||||
}).toThrow('tool "list_subagent_models" is already registered')
|
||||
})
|
||||
|
||||
it('lists registered providers and follows live registration changes', async () => {
|
||||
const { ctx, fiber } = await setupListTool()
|
||||
const ctx = await setupListTool()
|
||||
const empty = await call(ctx, {})
|
||||
expect(empty.isError).toBe(false)
|
||||
expect(text(empty)).toBe('(no LLM providers)')
|
||||
@@ -140,12 +141,13 @@ describe('list_subagent_models', () => {
|
||||
const changed = await call(ctx, {})
|
||||
expect(text(changed)).toBe('beta — BETA API')
|
||||
|
||||
await fiber.dispose()
|
||||
expect(ctx.tools.get('list_subagent_models')).toBeUndefined()
|
||||
const tools = ctx.tools
|
||||
await ctx.fiber.dispose()
|
||||
expect(tools.get('list_subagent_models')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('lists one provider\'s advertised models without treating the catalog as a whitelist', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool()
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'alpha' })
|
||||
expect(result.isError).toBe(false)
|
||||
@@ -166,8 +168,21 @@ describe('list_subagent_models', () => {
|
||||
expect(text(denied)).toContain('is not allowed for this Session')
|
||||
})
|
||||
|
||||
it('rejects an unauthorized provider before calling its adapter catalog', async () => {
|
||||
const ctx = await setupListTool([{ provider: 'alpha', model: 'fast' }])
|
||||
const adapter = new CatalogAdapter()
|
||||
const listModels = vi.spyOn(adapter, 'listModels')
|
||||
ctx.llm.registerAdapter(['alpha', 'secret'], adapter)
|
||||
|
||||
const result = await call(ctx, { provider: 'secret' })
|
||||
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('provider "secret" is not allowed for this Session')
|
||||
expect(listModels).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders an empty advertised model list', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool([{ provider: 'alpha', model: 'fast' }])
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter(true))
|
||||
const result = await call(ctx, { provider: 'alpha' })
|
||||
expect(result.isError).toBe(false)
|
||||
@@ -175,8 +190,8 @@ describe('list_subagent_models', () => {
|
||||
})
|
||||
|
||||
it('inspects exact-model efforts, descriptions, and defaults', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const ctx = await setupListTool()
|
||||
ctx.llm.registerAdapter(['alpha', 'secret'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'alpha', model: 'fast' })
|
||||
expect(result.isError).toBe(false)
|
||||
expect(text(result)).toBe(
|
||||
@@ -186,7 +201,7 @@ describe('list_subagent_models', () => {
|
||||
})
|
||||
|
||||
it('renders exact models without reasoning metadata', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool()
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'alpha', model: 'plain' })
|
||||
expect(result.isError).toBe(false)
|
||||
@@ -196,16 +211,16 @@ describe('list_subagent_models', () => {
|
||||
it.each([
|
||||
{ args: { model: 'fast' }, expected: '`model` requires `provider`' },
|
||||
{ args: { provider: '' }, expected: '`provider` must be non-empty' },
|
||||
{ args: { provider: 'missing' }, expected: 'available providers: (none)' },
|
||||
{ args: { provider: 'missing' }, expected: 'is not allowed for this Session' },
|
||||
])('rejects incomplete or unavailable provider requests', async ({ args, expected }) => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool()
|
||||
const result = await call(ctx, args)
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain(expected)
|
||||
})
|
||||
|
||||
it('rejects an empty exact model after resolving the provider', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool()
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'alpha', model: '' })
|
||||
expect(result.isError).toBe(true)
|
||||
@@ -213,10 +228,14 @@ describe('list_subagent_models', () => {
|
||||
})
|
||||
|
||||
it('reports registered alternatives for an unavailable provider', async () => {
|
||||
const { ctx } = await setupListTool()
|
||||
const ctx = await setupListTool([
|
||||
{ provider: 'alpha', model: 'fast' },
|
||||
{ provider: 'missing', model: 'fast' },
|
||||
])
|
||||
ctx.llm.registerAdapter(['alpha'], new CatalogAdapter())
|
||||
const result = await call(ctx, { provider: 'missing' })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('available providers: alpha')
|
||||
expect(text(result)).not.toContain('secret')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -85,9 +85,9 @@ async function createAgent(ctx: Context, id: string, options: {
|
||||
describe('SubagentModelSelectionConfig', () => {
|
||||
it('uses the composed default without a settings provider', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SubagentModelSelectionConfig, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.plugin(SubagentModelSelectionConfig, { enabled: true, allowedModels: ALLOWED_MODELS })
|
||||
|
||||
expect(ctx.subagentModelSelection.currentAllowedModels()).toEqual(ALLOWED_MODELS)
|
||||
expect(ctx.subagentModelSelection.current()).toEqual({ enabled: true, allowedModels: ALLOWED_MODELS })
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
@@ -96,13 +96,16 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
await ctx.plugin(MemorySettings)
|
||||
await ctx.plugin(SubagentModelSelectionConfig)
|
||||
|
||||
expect(ctx.subagentModelSelection.currentAllowedModels()).toEqual([])
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
expect(ctx.subagentModelSelection.currentAllowedModels()).toEqual(ALLOWED_MODELS)
|
||||
expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: [] })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
expect(ctx.subagentModelSelection.current()).toEqual({ enabled: true, allowedModels: ALLOWED_MODELS })
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('rejects duplicate routes and an empty durable policy', async () => {
|
||||
it('rejects duplicate routes, enabled empty settings, and an empty durable policy', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(MemorySettings)
|
||||
await ctx.plugin(SubagentModelSelectionConfig)
|
||||
@@ -111,6 +114,18 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
allowedModels: [...ALLOWED_MODELS, ...ALLOWED_MODELS],
|
||||
})).rejects.toThrow('repeats route "alpha/fast-model"')
|
||||
|
||||
await expect(ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: [],
|
||||
})).rejects.toThrow('enabled subagent model selection requires at least one allowed model')
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: false,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
|
||||
expect(ctx.subagentModelSelection.current()).toEqual({ enabled: false, allowedModels: [] })
|
||||
|
||||
const invalid = Session.create(SessionId('empty-policy'))
|
||||
invalid.append('subagent/model-selection-policy', { allowedModels: [] })
|
||||
expect(() => subagentModelSelectionPolicy(invalid)).toThrow('requires at least one route')
|
||||
@@ -123,13 +138,16 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
expect(selectable(ctx, disabled)).toBe(false)
|
||||
expect(subagentModelSelectionPolicy(disabled.session)).toBeUndefined()
|
||||
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const enabled = await createAgent(ctx, 'enabled')
|
||||
expect(subagentModelSelectionPolicy(enabled.session)).toEqual(ALLOWED_MODELS)
|
||||
expect(selectable(ctx, enabled)).toBe(true)
|
||||
expect(selectable(ctx, disabled)).toBe(false)
|
||||
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
|
||||
const disabledAgain = await createAgent(ctx, 'disabled-again')
|
||||
expect(selectable(ctx, disabledAgain)).toBe(false)
|
||||
expect(selectable(ctx, enabled)).toBe(true)
|
||||
@@ -138,7 +156,10 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
|
||||
it('rejects a forced route outside the Session policy before child creation', async () => {
|
||||
const ctx = await boot()
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const agent = await createAgent(ctx, 'enforced')
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
@@ -180,7 +201,10 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
|
||||
const disabled = await createComposed('preset-disabled')
|
||||
expect(selectable(ctx, disabled.agent)).toBe(false)
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const enabled = await createComposed('preset-enabled')
|
||||
expect(selectable(ctx, enabled.agent)).toBe(true)
|
||||
expect(selectable(ctx, disabled.agent)).toBe(false)
|
||||
@@ -200,9 +224,12 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
|
||||
it('inherits the parent decision and preserves seeded decisions across composition', async () => {
|
||||
const ctx = await boot()
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const parent = await createAgent(ctx, 'parent')
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: [] })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { enabled: false })
|
||||
const child = await createAgent(ctx, 'child', {
|
||||
meta: { parentSession: parent.id, origin: 'subagent' },
|
||||
})
|
||||
@@ -220,27 +247,16 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
expect(selectable(ctx, resumedEnabled)).toBe(true)
|
||||
|
||||
const oldSeed = Session.create(SessionId('old-seed'), [])
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const resumedDisabled = await createAgent(ctx, 'resumed-disabled', { seed: oldSeed.events })
|
||||
expect(selectable(ctx, resumedDisabled)).toBe(false)
|
||||
expect(subagentModelSelectionPolicy(resumedDisabled.session)).toBeUndefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('rejects ambiguous static and settings-controlled configuration', async () => {
|
||||
const ctx = new Context()
|
||||
await mountAgentLoopTestDependencies(ctx)
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
expect(() => {
|
||||
tool.apply(ctx, {
|
||||
provider: 'missing',
|
||||
enableModelSelection: true,
|
||||
modelSelectionSettings: true,
|
||||
})
|
||||
}).toThrow('mutually exclusive')
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('requires both the Host setting owner and a composition scope', async () => {
|
||||
const withoutSettings = new Context()
|
||||
await mountAgentLoopTestDependencies(withoutSettings)
|
||||
@@ -286,7 +302,10 @@ describe('SubagentModelSelectionConfig', () => {
|
||||
await expect(ctx.waterfall(ctx as never, 'agent/pre-step', payload, next))
|
||||
.rejects.toThrow('must expose route fields and list_subagent_models')
|
||||
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, { allowedModels: ALLOWED_MODELS })
|
||||
await ctx.settings.update(SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE, {
|
||||
enabled: true,
|
||||
allowedModels: ALLOWED_MODELS,
|
||||
})
|
||||
const enabled = await createAgent(ctx, 'invariant-enabled')
|
||||
await expect(ctx.waterfall(ctx as never, 'agent/pre-step', { ...payload, agent: enabled }, next))
|
||||
.resolves.toEqual({ kind: 'enter', messages: [] })
|
||||
|
||||
@@ -11,7 +11,7 @@ import { MockAdapter } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
import * as mock from './scripted-provider.ts'
|
||||
import * as tool from '../src/index.ts'
|
||||
import { assertAllowedModelRoutes, assertAllowedModelSelection } from '../src/model-selection.ts'
|
||||
import { callSubagent, setup, text } from './harness.ts'
|
||||
import { callSubagent, modelSelectionSetupAgent, setup, text } from './harness.ts'
|
||||
|
||||
const REASONING = {
|
||||
efforts: [
|
||||
@@ -42,7 +42,6 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
|
||||
it('allows pure inheritance but rejects explicit values outside a Session allowlist', () => {
|
||||
const policy = {
|
||||
kind: 'allowlist' as const,
|
||||
routes: [{ provider: 'alpha', model: 'allowed-model' }],
|
||||
}
|
||||
const parent = { provider: 'alpha', model: 'parent-model' }
|
||||
@@ -81,9 +80,28 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
)
|
||||
}).toThrow('without an effective provider and model')
|
||||
})
|
||||
it('exposes static route fields and discovery when selection is enabled', async () => {
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true })
|
||||
const schema = ctx.tools.schemas().find(entry => entry.name === 'subagent')!
|
||||
|
||||
it('leaves deployment or parent defaults outside the allowlist usable when the call selects nothing', async () => {
|
||||
let starts = 0
|
||||
const ctx = await setup(
|
||||
{ provider: 'mock', withModelSelection: true },
|
||||
{ onStart: () => { starts += 1 } },
|
||||
)
|
||||
const parent = modelSelectionSetupAgent(ctx)
|
||||
;(parent as unknown as { options: Agent['options'] }).options = {
|
||||
provider: 'deployment-provider',
|
||||
model: 'deployment-model',
|
||||
}
|
||||
|
||||
const result = await callSubagent(ctx, { description: 'default route', prompt: 'do it' })
|
||||
|
||||
expect(result.isError).toBe(false)
|
||||
expect(starts).toBe(1)
|
||||
})
|
||||
it('exposes Session-authorized route fields and discovery when selection is enabled', async () => {
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true })
|
||||
const agent = modelSelectionSetupAgent(ctx)
|
||||
const schema = ctx.tools.schemas(agent).find(entry => entry.name === 'subagent')!
|
||||
const props = (schema.parameters as { properties?: Record<string, unknown> }).properties ?? {}
|
||||
expect(Object.keys(props).sort()).toEqual([
|
||||
'description',
|
||||
@@ -94,13 +112,13 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
'run_in_background',
|
||||
])
|
||||
expect(schema.description).toContain('list_subagent_models')
|
||||
expect(ctx.tools.get('list_subagent_models')).toBeDefined()
|
||||
expect(ctx.tools.get('list_subagent_models', agent)).toBeDefined()
|
||||
expect(schema.description).not.toContain('alpha')
|
||||
|
||||
const registration = ctx.llm.registerAdapter(['alpha'], new MockAdapter([]))
|
||||
const definition = ctx.tools.get('subagent')
|
||||
const definition = ctx.tools.get('subagent', agent)
|
||||
registration.replace(['beta'])
|
||||
expect(ctx.tools.get('subagent')).toBe(definition)
|
||||
expect(ctx.tools.get('subagent', agent)).toBe(definition)
|
||||
expect(definition?.description).not.toContain('beta')
|
||||
})
|
||||
|
||||
@@ -124,7 +142,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
|
||||
it('rejects enabled model selection when the provider cannot apply Agent options', async () => {
|
||||
await expect(setup(
|
||||
{ provider: 'mock', enableModelSelection: true, maxDepth: 'provider-managed' },
|
||||
{ provider: 'mock', withModelSelection: true, maxDepth: 'provider-managed' },
|
||||
{ capabilities: { agentOptions: false } },
|
||||
)).rejects.toThrow('provider "mock" does not support child model selection')
|
||||
})
|
||||
@@ -133,7 +151,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
const requests: SubagentStartRequest[] = []
|
||||
const ctx = await setup({
|
||||
provider: 'mock',
|
||||
enableModelSelection: true,
|
||||
withModelSelection: true,
|
||||
agentOptions: {
|
||||
provider: 'alpha',
|
||||
model: 'configured-model',
|
||||
@@ -142,6 +160,8 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
},
|
||||
}, { onStart: (request) => { requests.push(request) } })
|
||||
ctx.llm.registerAdapter(['alpha'], new MockAdapter([], REASONING))
|
||||
const parent = modelSelectionSetupAgent(ctx)
|
||||
;(parent as unknown as { options: Agent['options'] }).options = parentWithRoute().options
|
||||
|
||||
const selected = await callSubagent(ctx, {
|
||||
description: 'route work',
|
||||
@@ -176,38 +196,44 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
const requests: SubagentStartRequest[] = []
|
||||
const ctx = await setup({
|
||||
provider: 'mock',
|
||||
enableModelSelection: true,
|
||||
withModelSelection: true,
|
||||
agentOptions: { provider: 'alpha' },
|
||||
}, { onStart: (request) => { requests.push(request) } })
|
||||
ctx.llm.registerAdapter(['alpha'], new MockAdapter([], REASONING))
|
||||
const parent = modelSelectionSetupAgent(ctx)
|
||||
;(parent as unknown as { options: Agent['options'] }).options = parentWithRoute().options
|
||||
|
||||
const result = await callSubagent(ctx, {
|
||||
description: 'effort work',
|
||||
prompt: 'do it',
|
||||
reasoning_effort: 'low',
|
||||
}, { agent: parentWithRoute() })
|
||||
})
|
||||
expect(result.isError).toBe(false)
|
||||
expect(requests[0]?.agentOptions).toEqual({ provider: 'alpha', reasoningEffort: 'low' })
|
||||
|
||||
const inherited = await setup({ provider: 'mock', enableModelSelection: true })
|
||||
const inherited = await setup({ provider: 'mock', withModelSelection: true })
|
||||
inherited.llm.registerAdapter(['alpha'], new MockAdapter([], REASONING))
|
||||
const inheritedParent = modelSelectionSetupAgent(inherited)
|
||||
;(inheritedParent as unknown as { options: Agent['options'] }).options = parentWithRoute().options
|
||||
const inheritedResult = await callSubagent(inherited, {
|
||||
description: 'parent effort work',
|
||||
prompt: 'do it',
|
||||
reasoning_effort: 'low',
|
||||
}, { agent: parentWithRoute() })
|
||||
})
|
||||
expect(inheritedResult.isError).toBe(false)
|
||||
})
|
||||
|
||||
it('inherits a parent effort only when an explicit route stays unchanged', async () => {
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true })
|
||||
ctx.llm.registerAdapter(['alpha'], new MockAdapter([], REASONING))
|
||||
const parent = modelSelectionSetupAgent(ctx)
|
||||
;(parent as unknown as { options: Agent['options'] }).options = parentWithRoute().options
|
||||
const result = await callSubagent(ctx, {
|
||||
description: 'same route work',
|
||||
prompt: 'do it',
|
||||
provider: 'alpha',
|
||||
model: 'parent-model',
|
||||
}, { agent: parentWithRoute() })
|
||||
})
|
||||
expect(result.isError).toBe(false)
|
||||
})
|
||||
|
||||
@@ -215,11 +241,14 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
const requests: SubagentStartRequest[] = []
|
||||
const ctx = await setup({
|
||||
provider: 'mock',
|
||||
enableModelSelection: true,
|
||||
withModelSelection: true,
|
||||
agentOptions: { reasoningEffort: ReasoningEffortId('high') },
|
||||
}, { onStart: (request) => { requests.push(request) } })
|
||||
ctx.llm.registerAdapter(['current-provider'], new MockAdapter([], REASONING))
|
||||
const parent = parentWithRoute({ provider: 'created-provider', model: 'created-model' })
|
||||
const parent = modelSelectionSetupAgent(ctx)
|
||||
;(parent as unknown as { options: Agent['options'] }).options = {
|
||||
provider: 'created-provider', model: 'created-model',
|
||||
}
|
||||
parent.session.append('request/header', {
|
||||
header: { config: { provider: 'current-provider', model: 'current-model' } },
|
||||
reason: 'initial',
|
||||
@@ -230,7 +259,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
prompt: 'do it',
|
||||
provider: 'current-provider',
|
||||
model: 'current-model',
|
||||
}, { agent: parent })
|
||||
})
|
||||
|
||||
expect(result.isError).toBe(false)
|
||||
expect(requests[0]?.agentOptions).toEqual({
|
||||
@@ -241,7 +270,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
})
|
||||
|
||||
it('rejects an effort without any effective route', async () => {
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true })
|
||||
const result = await callSubagent(ctx, {
|
||||
description: 'missing route',
|
||||
prompt: 'do it',
|
||||
@@ -256,7 +285,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
{ model: 'fast-model' },
|
||||
])('rejects a partial model-facing route before child creation', async (route) => {
|
||||
let starts = 0
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
const result = await callSubagent(ctx, { description: 'partial route', prompt: 'do it', ...route })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain('`provider` and `model` must be supplied together')
|
||||
@@ -268,7 +297,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
{ provider: 'alpha', model: '', expected: '`model` must be non-empty' },
|
||||
{ reasoning_effort: '', expected: '`reasoning_effort` must be non-empty' },
|
||||
])('rejects empty model-facing values', async ({ expected, ...selection }) => {
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true })
|
||||
const result = await callSubagent(ctx, { description: 'empty route', prompt: 'do it', ...selection })
|
||||
expect(result.isError).toBe(true)
|
||||
expect(text(result)).toContain(expected)
|
||||
@@ -276,7 +305,7 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
|
||||
it('uses the LLM runtime for provider and reasoning-effort validation before child creation', async () => {
|
||||
let starts = 0
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
ctx.llm.registerAdapter(['alpha'], new MockAdapter([], REASONING))
|
||||
|
||||
const unsupported = await callSubagent(ctx, {
|
||||
@@ -351,7 +380,6 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
await mock.mountScriptedProvider(ctx, { name: 'mock' })
|
||||
await ctx.plugin(tool, {
|
||||
provider: 'mock',
|
||||
enableModelSelection: true,
|
||||
agentOptions: {
|
||||
provider: 'alpha',
|
||||
model: 'fast-model',
|
||||
@@ -363,14 +391,6 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
expect(configured.isError).toBe(true)
|
||||
expect(text(configured)).toContain('`llm` service is unavailable')
|
||||
|
||||
const selected = await callSubagent(ctx, {
|
||||
description: 'selected route',
|
||||
prompt: 'do it',
|
||||
provider: 'alpha',
|
||||
model: 'other-model',
|
||||
})
|
||||
expect(selected.isError).toBe(true)
|
||||
expect(text(selected)).toContain('`llm` service is unavailable')
|
||||
})
|
||||
|
||||
it('keeps pure inherited routing usable without an LLM service lookup', async () => {
|
||||
@@ -388,15 +408,15 @@ describe('dsh-tool-subagent model selection', () => {
|
||||
})
|
||||
|
||||
it('warns that changing a fork route can lose inherited-prefix reuse', async () => {
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true }, { inheritsParentContext: true })
|
||||
const schema = ctx.tools.schemas().find(entry => entry.name === 'subagent')!
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true }, { inheritsParentContext: true })
|
||||
const schema = ctx.tools.schemas(modelSelectionSetupAgent(ctx)).find(entry => entry.name === 'subagent')!
|
||||
expect(schema.description).toContain('inherits this conversation')
|
||||
expect(schema.description).toContain('can prevent provider-side reuse of the inherited conversation prefix')
|
||||
})
|
||||
|
||||
it('propagates an exact-route resolver failure before child creation', async () => {
|
||||
let starts = 0
|
||||
const ctx = await setup({ provider: 'mock', enableModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
const ctx = await setup({ provider: 'mock', withModelSelection: true }, { onStart: () => { starts += 1 } })
|
||||
const adapter = new MockAdapter([])
|
||||
vi.spyOn(adapter, 'resolveModel').mockRejectedValue(new Error('selected route unavailable'))
|
||||
ctx.llm.registerAdapter(['alpha'], adapter)
|
||||
|
||||
@@ -964,7 +964,10 @@ describe('dsh-tool-subagent background mode', () => {
|
||||
})
|
||||
|
||||
it('skips background startup when cancellation wins asynchronous route preflight', async () => {
|
||||
const ctx = await backgroundSetup({ provider: 'mock', enableModelSelection: true })
|
||||
const ctx = await backgroundSetup({
|
||||
provider: 'mock',
|
||||
agentOptions: { provider: 'alpha', model: 'selected-model' },
|
||||
})
|
||||
const parent = ownerAgent(ctx, 'sess-parent')
|
||||
const adapter = new MockAdapter([])
|
||||
let releasePreflight!: () => void
|
||||
@@ -979,8 +982,6 @@ describe('dsh-tool-subagent background mode', () => {
|
||||
const resultPromise = callSubagent(ctx, {
|
||||
description: 'cancelled selection',
|
||||
prompt: 'do it',
|
||||
provider: 'alpha',
|
||||
model: 'selected-model',
|
||||
run_in_background: true,
|
||||
}, { agent: parent, signal: controller.signal })
|
||||
await vi.waitFor(() => { expect(resolveModel).toHaveBeenCalledOnce() })
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface StubSettingsScope<T> {
|
||||
scope: SettingsScope<T>
|
||||
/** Spy behind `scope.set`; resolves immediately. */
|
||||
set: ReturnType<typeof vi.fn>
|
||||
/** Spy behind `scope.mutate`; resolves immediately. */
|
||||
mutate: ReturnType<typeof vi.fn>
|
||||
/** Spy behind `scope.unset`; resolves immediately. */
|
||||
unset: ReturnType<typeof vi.fn>
|
||||
/** @returns how many listeners are currently subscribed (disposal assertions). */
|
||||
@@ -34,6 +36,7 @@ export function stubSettingsScope<T>(): StubSettingsScope<T> {
|
||||
}
|
||||
const listeners = new Set<() => void>()
|
||||
const set = vi.fn(() => Promise.resolve())
|
||||
const mutate = vi.fn(() => Promise.resolve())
|
||||
const unset = vi.fn(() => Promise.resolve())
|
||||
return {
|
||||
scope: {
|
||||
@@ -42,10 +45,12 @@ export function stubSettingsScope<T>(): StubSettingsScope<T> {
|
||||
listeners.add(listener)
|
||||
return () => { listeners.delete(listener) }
|
||||
},
|
||||
mutate,
|
||||
set,
|
||||
unset,
|
||||
},
|
||||
set,
|
||||
mutate,
|
||||
unset,
|
||||
listenerCount: () => listeners.size,
|
||||
publish: (next) => {
|
||||
|
||||
@@ -241,6 +241,7 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
|
||||
AgentHandle: 'core.md',
|
||||
ModelSelection: 'core.md',
|
||||
AllowedModelRoute: 'subagent.md',
|
||||
SubagentModelSelectionSettings: 'subagent.md',
|
||||
AgentOptions: 'core.md',
|
||||
AgentStatus: 'core.md',
|
||||
ContentBlock: 'llm-streaming.md',
|
||||
|
||||
@@ -63,6 +63,7 @@ import type TeamService from '@deepseek-ai/dsh-experimental-agent-team'
|
||||
import * as ToolTeam from '@deepseek-ai/dsh-experimental-tool-agent-team'
|
||||
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
|
||||
import * as ToolSubagent from '@deepseek-ai/dsh-tool-subagent'
|
||||
import { registerListSubagentModels } from '../packages/subagent/tool-subagent/src/list-models.ts'
|
||||
import * as ToolWeb from '@deepseek-ai/dsh-tool-web'
|
||||
import VmWorkflowEngine from '@deepseek-ai/dsh-workflow-worker-thread'
|
||||
import * as ToolRalph from '@deepseek-ai/dsh-tool-ralph'
|
||||
@@ -467,10 +468,11 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
await ctx.plugin(SubagentRuntime)
|
||||
await ctx.plugin(LlmRuntime)
|
||||
registerCatalogSubagentProvider(ctx, 'mock')
|
||||
await ctx.plugin(ToolSubagent, { provider: 'mock', enableModelSelection: true })
|
||||
await ctx.plugin(ToolSubagent, { provider: 'mock' })
|
||||
registerListSubagentModels(ctx, { routes: [{ provider: 'mock', model: 'mock' }] })
|
||||
},
|
||||
note:
|
||||
'The registered delegation name is the load-time `toolName` config (default `subagent`); the schema above shows static model selection enabled for reference. Model selection defaults off. Web presets sample the default-off Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Explicit compositions may instead use static `enableModelSelection`. Each instance independently controls model selection, discovery ownership, and background behavior through `enableModelSelection`, `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`.',
|
||||
'The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance independently controls whether it reads model-selection settings and its background behavior through `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-tool-subagent-control',
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"mcpCapabilities":{"http":true},"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false},"sessionCapabilities":{"close":{},"list":{},"resume":{}}},"authMethods":[]}}
|
||||
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}","configOptions":[{"id":"model","name":"Model","category":"model","type":"select","currentValue":"[\"deepseek-official\",\"deepseek-v4-flash\"]","options":[{"group":"deepseek-official","name":"DeepSeek","options":[{"value":"[\"deepseek-official\",\"deepseek-v4-flash\"]","name":"deepseek-v4-flash"},{"value":"[\"deepseek-official\",\"deepseek-v4-pro\"]","name":"deepseek-v4-pro"}]}]}]}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_wait","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","description":"Wait until cancellation"}}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_wait","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted"}}]}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call","toolCallId":"call_skipped","title":"bash","kind":"other","status":"in_progress","rawInput":{"command":"printf skipped > skipped.txt","description":"Write skipped marker"}}}}
|
||||
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"tool_call_update","toolCallId":"call_skipped","status":"failed","content":[{"type":"content","content":{"type":"text","text":"Error: tool call aborted before dispatch"}}]}}}
|
||||
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"cancelled"}}
|
||||
@@ -54,7 +54,6 @@
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 1
|
||||
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -477,7 +460,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -489,18 +472,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -477,7 +460,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -489,18 +472,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -477,7 +460,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -489,18 +472,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -477,7 +460,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -489,18 +472,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -134,13 +134,6 @@ interface ToolArgsMap {
|
||||
/** children (default) lists direct children only; descendants walks the complete tree below you. */
|
||||
scope?: "children" | "descendants";
|
||||
} & Record<string, JsonValue>;
|
||||
/** Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields. */
|
||||
list_subagent_models: {
|
||||
/** Registered LLM provider id. Omit to list providers. */
|
||||
provider?: string;
|
||||
/** Exact model id to inspect. Requires provider; omit to list that provider's advertised models. */
|
||||
model?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */
|
||||
ralph: {
|
||||
/** The immutable completion objective for every fresh Ralph round. */
|
||||
@@ -191,18 +184,12 @@ interface ToolArgsMap {
|
||||
/** Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file. */
|
||||
view_range?: number[] | null;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort. */
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */
|
||||
subagent: {
|
||||
/** A short (3-5 word) description of the delegated task, for display. */
|
||||
description: string;
|
||||
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
|
||||
prompt: string;
|
||||
/** LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route. */
|
||||
provider?: string;
|
||||
/** Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route. */
|
||||
model?: string;
|
||||
/** Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default. */
|
||||
reasoning_effort?: string;
|
||||
/** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */
|
||||
run_in_background?: boolean;
|
||||
} & Record<string, JsonValue>;
|
||||
@@ -414,7 +401,6 @@ interface ToolOutputMap {
|
||||
parent?: string;
|
||||
depth?: number;
|
||||
})[];
|
||||
list_subagent_models: string;
|
||||
ralph: {
|
||||
runId: string;
|
||||
agentsStarted: number;
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -482,7 +465,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -494,18 +477,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -136,13 +136,6 @@ interface ToolArgsMap {
|
||||
/** children (default) lists direct children only; descendants walks the complete tree below you. */
|
||||
scope?: "children" | "descendants";
|
||||
} & Record<string, JsonValue>;
|
||||
/** Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields. */
|
||||
list_subagent_models: {
|
||||
/** Registered LLM provider id. Omit to list providers. */
|
||||
provider?: string;
|
||||
/** Exact model id to inspect. Requires provider; omit to list that provider's advertised models. */
|
||||
model?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */
|
||||
ralph: {
|
||||
/** The immutable completion objective for every fresh Ralph round. */
|
||||
@@ -193,18 +186,12 @@ interface ToolArgsMap {
|
||||
/** Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file. */
|
||||
view_range?: number[] | null;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort. */
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */
|
||||
subagent: {
|
||||
/** A short (3-5 word) description of the delegated task, for display. */
|
||||
description: string;
|
||||
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
|
||||
prompt: string;
|
||||
/** LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route. */
|
||||
provider?: string;
|
||||
/** Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route. */
|
||||
model?: string;
|
||||
/** Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default. */
|
||||
reasoning_effort?: string;
|
||||
/** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */
|
||||
run_in_background?: boolean;
|
||||
} & Record<string, JsonValue>;
|
||||
@@ -416,7 +403,6 @@ interface ToolOutputMap {
|
||||
parent?: string;
|
||||
depth?: number;
|
||||
})[];
|
||||
list_subagent_models: string;
|
||||
ralph: {
|
||||
runId: string;
|
||||
agentsStarted: number;
|
||||
|
||||
@@ -136,13 +136,6 @@ interface ToolArgsMap {
|
||||
/** children (default) lists direct children only; descendants walks the complete tree below you. */
|
||||
scope?: "children" | "descendants";
|
||||
} & Record<string, JsonValue>;
|
||||
/** Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields. */
|
||||
list_subagent_models: {
|
||||
/** Registered LLM provider id. Omit to list providers. */
|
||||
provider?: string;
|
||||
/** Exact model id to inspect. Requires provider; omit to list that provider's advertised models. */
|
||||
model?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */
|
||||
ralph: {
|
||||
/** The immutable completion objective for every fresh Ralph round. */
|
||||
@@ -193,18 +186,12 @@ interface ToolArgsMap {
|
||||
/** Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file. */
|
||||
view_range?: number[] | null;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort. */
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */
|
||||
subagent: {
|
||||
/** A short (3-5 word) description of the delegated task, for display. */
|
||||
description: string;
|
||||
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
|
||||
prompt: string;
|
||||
/** LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route. */
|
||||
provider?: string;
|
||||
/** Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route. */
|
||||
model?: string;
|
||||
/** Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default. */
|
||||
reasoning_effort?: string;
|
||||
/** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */
|
||||
run_in_background?: boolean;
|
||||
} & Record<string, JsonValue>;
|
||||
@@ -416,7 +403,6 @@ interface ToolOutputMap {
|
||||
parent?: string;
|
||||
depth?: number;
|
||||
})[];
|
||||
list_subagent_models: string;
|
||||
ralph: {
|
||||
runId: string;
|
||||
agentsStarted: number;
|
||||
|
||||
@@ -301,13 +301,6 @@ interface ToolArgsMap {
|
||||
/** children (default) lists direct children only; descendants walks the complete tree below you. */
|
||||
scope?: "children" | "descendants";
|
||||
} & Record<string, JsonValue>;
|
||||
/** Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields. */
|
||||
list_subagent_models: {
|
||||
/** Registered LLM provider id. Omit to list providers. */
|
||||
provider?: string;
|
||||
/** Exact model id to inspect. Requires provider; omit to list that provider's advertised models. */
|
||||
model?: string;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools. */
|
||||
ralph: {
|
||||
/** The immutable completion objective for every fresh Ralph round. */
|
||||
@@ -358,18 +351,12 @@ interface ToolArgsMap {
|
||||
/** Optional parameter of `view` command when `path` points to a file. If omitted or null, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file. */
|
||||
view_range?: number[] | null;
|
||||
} & Record<string, JsonValue>;
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort. */
|
||||
/** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. */
|
||||
subagent: {
|
||||
/** A short (3-5 word) description of the delegated task, for display. */
|
||||
description: string;
|
||||
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
|
||||
prompt: string;
|
||||
/** LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route. */
|
||||
provider?: string;
|
||||
/** Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route. */
|
||||
model?: string;
|
||||
/** Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default. */
|
||||
reasoning_effort?: string;
|
||||
/** Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it. */
|
||||
run_in_background?: boolean;
|
||||
} & Record<string, JsonValue>;
|
||||
@@ -600,7 +587,6 @@ interface ToolOutputMap {
|
||||
parent?: string;
|
||||
depth?: number;
|
||||
})[];
|
||||
list_subagent_models: string;
|
||||
ralph: {
|
||||
runId: string;
|
||||
agentsStarted: number;
|
||||
|
||||
@@ -457,23 +457,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -679,7 +662,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -691,18 +674,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -180,23 +180,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -365,7 +348,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -377,18 +360,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lsp",
|
||||
"description": "Query a language server for precise code navigation. operation is one of goToDefinition, findReferences, goToImplementation, hover. line and character are one-based UTF-16 cursor coordinates. findReferences includes the declaration.",
|
||||
@@ -498,7 +481,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -510,18 +493,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -665,7 +648,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -677,18 +660,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -323,23 +323,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -524,7 +507,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -536,18 +519,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# Keyless counterpart to subagent-configured-effort.cordis.yml: disable the
|
||||
# live adapter, insert replay, and apply the configured-effort rejection patch.
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
disabled: true
|
||||
|
||||
- id: sandbox
|
||||
name: '@deepseek-ai/dsh-sandbox-local'
|
||||
config:
|
||||
runnerCommand:
|
||||
- bash
|
||||
- -c
|
||||
- while [ "$1" != "--" ]; do shift; done; shift; exec "$@"
|
||||
- passthrough-runner
|
||||
runnerFailureSignatures:
|
||||
- 'passthrough-runner: profile rejected'
|
||||
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 1
|
||||
agentOptions:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
reasoningEffort: unsupported
|
||||
|
||||
# Select the recorded flash model for this composition.
|
||||
- id: agent-default-model
|
||||
name: '@deepseek-ai/dsh-agent-default-model'
|
||||
config:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
|
||||
- id: session-persistence-jsonl
|
||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
config:
|
||||
root: !!js dshHomePath('sessions')
|
||||
compression: none
|
||||
|
||||
- id: agent-instructions
|
||||
name: '@deepseek-ai/dsh-agent-instructions'
|
||||
config:
|
||||
maxBytes: 65536
|
||||
|
||||
- id: system-prompt
|
||||
name: '@deepseek-ai/dsh-system-prompt'
|
||||
config:
|
||||
persona: |
|
||||
You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug.
|
||||
|
||||
Verify your work by running the code or tests. Keep answers brief and factual.
|
||||
|
||||
- insert:
|
||||
- id: llm-replay
|
||||
name: '@deepseek-ai/dsh-llm-replay'
|
||||
config:
|
||||
providers:
|
||||
- id: deepseek-official
|
||||
name: DeepSeek
|
||||
models:
|
||||
- id: deepseek-v4-flash
|
||||
- id: deepseek-v4-pro
|
||||
@@ -1,15 +0,0 @@
|
||||
# Configured-effort rejection snapshot overlay: keep one invalid configured
|
||||
# effort so the tool rejects before starting a child instead of deferring the
|
||||
# failure to the child agent loop.
|
||||
- id: tool-subagent
|
||||
name: '@deepseek-ai/dsh-tool-subagent'
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 1
|
||||
agentOptions:
|
||||
provider: deepseek-official
|
||||
model: deepseek-v4-flash
|
||||
reasoningEffort: unsupported
|
||||
@@ -1,32 +0,0 @@
|
||||
[
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
|
||||
{ "type": "tool-call-delta", "index": 0, "id": "call_list_child_model", "name": "list_subagent_models", "argumentsDelta": "{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_list_child_model", "name": "list_subagent_models", "arguments": "{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
|
||||
{ "type": "finish", "reason": { "kind": "tool-calls" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
|
||||
{ "type": "tool-call-delta", "index": 0, "id": "call_configured_effort", "name": "subagent", "argumentsDelta": "{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_configured_effort", "name": "subagent", "arguments": "{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
|
||||
{ "type": "finish", "reason": { "kind": "tool-calls" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "chunks",
|
||||
"chunks": [
|
||||
{ "type": "block-start", "index": 0, "blockType": "text" },
|
||||
{ "type": "text-delta", "index": 0, "text": "CONFIGURED_EFFORT_REJECTED" },
|
||||
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "CONFIGURED_EFFORT_REJECTED" } },
|
||||
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 2 } },
|
||||
{ "type": "finish", "reason": { "kind": "stop" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,41 +0,0 @@
|
||||
{"type":"session","version":0,"id":"{{session:1}}","createdAt":1000,"cwd":"{{cwd}}","delegationDepth":0}
|
||||
{"type":"permission/preset","data":{"preset":"danger-full-access"}}
|
||||
{"type":"sandbox/mode","data":{"mode":"danger-full-access"}}
|
||||
{"type":"approval/policy","data":{"policy":"never"}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Inspect the configured child model, then attempt one subagent call so its configured reasoning effort is validated before child creation."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"}]}}
|
||||
{"type":"turn/start","data":{"turn":1}}
|
||||
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
|
||||
{"type":"step/start","data":{"turn":1,"step":1}}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Inspect the configured child model, then attempt one subagent call so its configured reasoning effort is validated before child creation."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
|
||||
{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt","form":"snapshot","sections":[{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."}]},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"}
|
||||
{"type":"session/title","data":{"title":"Inspect the configured child model,","messageSeqs":[7],"source":{"kind":"fallback"}}}
|
||||
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
|
||||
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_list_child_model","name":"list_subagent_models","argumentsDelta":"{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_list_child_model","name":"list_subagent_models","arguments":"{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}"}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_list_child_model","name":"list_subagent_models","arguments":"{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_list_child_model","name":"list_subagent_models","arguments":"{\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\"}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_list_child_model"},"content":[{"type":"tool-result","toolCallId":"call_list_child_model","content":[{"type":"text","text":"deepseek-official/deepseek-v4-flash — deepseek-v4-flash\nReasoning efforts:\n(no advertised reasoning efforts)"}],"isError":false}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"tool-call-delta","index":0,"id":"call_configured_effort","name":"subagent","argumentsDelta":"{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_configured_effort","name":"subagent","arguments":"{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}"}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_configured_effort","name":"subagent","arguments":"{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[22,23,24,25,26],"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_configured_effort","name":"subagent","arguments":"{\"description\":\"Validate configured effort\",\"prompt\":\"This child must never start.\",\"provider\":\"deepseek-official\",\"model\":\"deepseek-v4-flash\",\"run_in_background\":false}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_configured_effort"},"content":[{"type":"tool-result","toolCallId":"call_configured_effort","content":[{"type":"text","text":"Error: provider \"deepseek-official\" model \"deepseek-v4-flash\" does not support reasoning effort \"unsupported\""}],"isError":true}],"role":"user","id":"{{message:6}}"},"error":{"name":"LlmError","code":"UNSUPPORTED_REASONING_EFFORT"}},"sourceEventSeqs":[28],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":2}}
|
||||
{"type":"step/start","data":{"turn":1,"step":3}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"text-delta","index":0,"text":"CONFIGURED_EFFORT_REJECTED"}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"CONFIGURED_EFFORT_REJECTED"}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
|
||||
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"CONFIGURED_EFFORT_REJECTED"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[32,33,34,35,36],"surfaceOp":"append"}
|
||||
{"type":"step/end","data":{"turn":1,"step":3}}
|
||||
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
|
||||
@@ -20,7 +20,6 @@
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 2
|
||||
# Select the recorded flash model for this composition.
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
enableModelSelection: true
|
||||
backgroundMode: continuable
|
||||
maxDepth: 2
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
@@ -260,23 +260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_subagent_models",
|
||||
"description": "Discover LLM routes for subagents without changing the current Agent. Call with no arguments to list registered providers, with `provider` to list its advertised models, or with `provider` and `model` to inspect that exact model and its reasoning efforts. Catalog membership is advisory: an adapter may accept an unlisted model id. Use the returned ids with a delegation tool's `provider`, `model`, and `reasoning_effort` fields.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "Registered LLM provider id. Omit to list providers."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Exact model id to inspect. Requires provider; omit to list that provider's advertised models."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ralph",
|
||||
"description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.",
|
||||
@@ -461,7 +444,7 @@
|
||||
},
|
||||
{
|
||||
"name": "subagent",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result. Child LLM selection is optional. Omit `provider`, `model`, and `reasoning_effort` to use configured child defaults and inherit compatible missing values from the parent Agent. Supply `provider` and `model` together after using `list_subagent_models` to inspect advertised routes and efforts. Changing the effective route without naming an effort uses the selected model's default effort.",
|
||||
"description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -473,18 +456,6 @@
|
||||
"type": "string",
|
||||
"description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs."
|
||||
},
|
||||
"provider": {
|
||||
"type": "string",
|
||||
"description": "LLM provider route for the child. Supply together with model; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"model": {
|
||||
"type": "string",
|
||||
"description": "Model id interpreted by provider. Supply together with provider; omit both to use configured child defaults or inherit the parent route."
|
||||
},
|
||||
"reasoning_effort": {
|
||||
"type": "string",
|
||||
"description": "Adapter-owned reasoning effort for the effective child route. Omit to inherit a compatible configured/parent effort or use a newly selected model's default."
|
||||
},
|
||||
"run_in_background": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it."
|
||||
|
||||
Reference in New Issue
Block a user