dsh-expert-group 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +330 -0
- package/README.md +6 -18
- package/client-bundle.js +2701 -184
- package/client-expert-group-model.js +31 -4
- package/client-expert-group-sidebar.js +2611 -129
- package/client.js +10 -2
- package/host/expert-group-runtime.js +710 -33
- package/host/expert-group-snapshot.js +5 -1
- package/index.js +708 -66
- package/package.json +2 -1
- package/prompt-contracts.js +22 -15
package/README.en.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# dsh-expert-group
|
|
2
|
+
|
|
3
|
+
**English** | [中文](README.md)
|
|
4
|
+
|
|
5
|
+
This English edition is a translation of [README.md](README.md); if the two versions ever disagree, the Chinese version is authoritative.
|
|
6
|
+
|
|
7
|
+
> The `dsh-expert-group` settings namespace stores the global providers, `defaults`, and notification preferences; session overrides and subagent snapshots belong to the plugin's own `dsh_expert_group` storage domain.
|
|
8
|
+
|
|
9
|
+
An external **dual-face Cordis bundle** that can be installed into DSH (DeepSeek Harness). Its expert division of labour, per-role model and reasoning-depth routing, and background parallel delegation discipline are borrowed from the open-source **oh-my-opencode-slim** project
|
|
10
|
+
(https://github.com/alvinunreal/oh-my-opencode-slim , MIT).
|
|
11
|
+
It brings "per-role model and reasoning depth" expert subagents into DSH, and maintains the global models, reasoning depths, and notification preferences on the "dsh-expert-group" settings page in the Web UI.
|
|
12
|
+
It also ports oh-my-opencode-slim's parent-agent orchestrator design: the root session breaks down, delegates, integrates, and verifies, while expert subagents keep their leaf-worker constraints.
|
|
13
|
+
|
|
14
|
+
## Capabilities
|
|
15
|
+
|
|
16
|
+
- 5 expert subagent tools (enabled by default):
|
|
17
|
+
- `explorer` — read-only codebase reconnaissance (file location, symbol/pattern mapping)
|
|
18
|
+
- `librarian` — read-only research into external documentation / library APIs
|
|
19
|
+
- `oracle` — read-only architecture decisions / hard debugging / code review
|
|
20
|
+
- `designer` — UI/UX and visual implementation (can write)
|
|
21
|
+
- `fixer` — implementation of already-decided plans, repetitive edits, migrations, and filling in tests, including large amounts of work in a single file (can write)
|
|
22
|
+
- **Visual agent** `observer`: a read-only visual-analysis expert that interprets the real durable image attachments provided by the Host, including screenshots, UI mockups, charts, and PDF pages already rendered as images; it does not accept arbitrary local paths. Off by default in a bare Config; the current `cordis.patch.yml` explicitly turns the bundle override on. Image routing hands the Host-created `image/png` durable attachment ContentBlock directly to the Observer; the automatic image entry point only dispatches bounded, purely visual tasks, and the user's original task is still held by the parent agent, which synthesizes the visual report.
|
|
23
|
+
- Plugin tools use short, conflict-free role tool names (`explorer`, `librarian`, and so on) registered on the current DSH's `ctx.tools`;
|
|
24
|
+
the parent agent must not treat the Harness-level `subagent_<role>`, PTC's `run_code`, or the generic subagent entry point as a substitute for plugin delegation.
|
|
25
|
+
- Every expert delegation is always a continuable subagent: by default it runs in the background and immediately returns the subagent id;
|
|
26
|
+
`run_in_background: false` is accepted for compatibility only — like omitting it or passing `true`, it uses non-blocking admission and does not wait for the final result (errors / empty returns are still handled as "automatic recovery").
|
|
27
|
+
- **No built-in default models**: after a fresh install every role is empty, so you must choose and save models from those already configured in DSH on the "dsh-expert-group" settings page; a role without a model reports a clear message when it is invoked.
|
|
28
|
+
- Models and reasoning depths are validated through `llm.listProviders/listModels` + `resolveCallConfig`, and only models that have been added in settings are accepted.
|
|
29
|
+
- **Dynamic settings write capability**: the plugin does not import `@deepseek-ai/dsh-settings` at module-load time (the package declares a `>=0.2.1-rc.1` peer, so the host should provide a compatible version and the plugin does not additionally load that package). The target runtimes are `0.2.1-rc.1` / `0.2.1-rc.2` (that is, the RC2 contract host line above `>0.2.1-rc.1`); the plugin reads and writes profile entries through the describe/update/mutate surface of the host settings service, and DEG's own settings-commit endpoint performs semantic validation and revision conflict detection (the host no longer provides an owner validate hook).
|
|
30
|
+
- The subagent's `reasoningEffort` is injected through the subagent `agent/request` waterfall, bypassing the limitation that `dsh-tool-subagent`'s
|
|
31
|
+
`AgentOptions` does not expose that field yet.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
The plugin is published to the public npm registry and can be installed directly with npm:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm i dsh-expert-group
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You can also install it by package name on DSH's "Add plugin" page: enter `dsh-expert-group` in the input box, switch the install source on the right if needed, then click "Install".
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
> This screenshot lives under the repository's `docs/assets/`. The npm package is published per the `files` allowlist in `package.json` and **does not include `docs/`**, so the image does not show up in a copy installed from npm; reading it in the repository works normally.
|
|
46
|
+
|
|
47
|
+
Prerequisites: DSH version ≥ `0.2.1-rc.1`, a working DSH Web (`dsh web`), Node.js ≥ 22, and `pnpm` on PATH. Restart `dsh web` after installation for the plugin to take effect.
|
|
48
|
+
|
|
49
|
+
The plugin ships no built-in default models per role: first configure models under DSH "Settings → Models", then choose models for each role and save them under "Settings → dsh-expert-group".
|
|
50
|
+
|
|
51
|
+
## Interface Overview
|
|
52
|
+
|
|
53
|
+
DEG has exactly three visible surfaces in the DSH Web UI, independent of one another and non-overlapping:
|
|
54
|
+
|
|
55
|
+
| Surface | Location | What it does | Details |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| **dsh-expert-group settings page** | Settings → `dsh-expert-group` | Maintains global providers, the default models and reasoning depths of the six expert roles, plus notification preferences, `Observer优先`(Observer priority), `任务管理`(Task management), `Auto`, and the per-role concurrency limit | [Settings page in detail](#settings-page-dsh-expert-group) |
|
|
58
|
+
| **Expert Group task sidebar** | Right sidebar (the native right pane, or a standalone page when better-sidebar is installed) | Shows the task tree and run state of the current root session; use `>>` / `<<` to open or close a subagent session in the right sidebar; cancel / retry fan-out tasks | [Expert Group task sidebar](#expert-group-task-sidebar) |
|
|
59
|
+
| **Run status line and system notifications** | Bottom-left of the main view / bottom-right of the Windows desktop | While running it shows run words such as `Orchestrator running` and `Explorer running`; when a task reaches a terminal state a Windows Toast notifies you | Web status hints |
|
|
60
|
+
|
|
61
|
+
DEG has no model-callable notification tool, and it puts no model-configuration entry point in the session title bar or the right sidebar: global expert configuration is maintained only on the settings page, while in-session main-model overrides are still managed by the DSH Host itself.
|
|
62
|
+
|
|
63
|
+
> The UI screenshots below were taken from the DSH desktop app and live under the repository's `docs/assets/`. The npm package is published per the `files` allowlist in `package.json` and **does not include `docs/`**, so these image links do not resolve in a copy installed from npm; reading them in the repository works normally.
|
|
64
|
+
|
|
65
|
+
## Settings Page (dsh-expert-group)
|
|
66
|
+
|
|
67
|
+
The settings page is a standalone section that DEG registers through `settings.section` (label `dsh-expert-group`), and it calls `ctx.settings.configure({ auto: false })` to turn off the configuration page the host would otherwise generate — **every control that appears on the page is rendered by DEG itself**.
|
|
68
|
+
|
|
69
|
+
The page as a whole uses **deferred saving**: every dropdown, checkbox, and number field only edits a draft, and nothing is persisted until you click "Save" in the footer; the only exception is the provider cards inside the "Model configuration" section, each of which holds its own draft with its own "Apply / Cancel". When the settings namespace is not writable the whole page stays read-only.
|
|
70
|
+
|
|
71
|
+
The features below are described in top-to-bottom render order; implementation details and boundaries (data flow, revision fence, the seven-effort write rules, and so on) are covered in the "[Model Configuration](#model-configuration)" section later on.
|
|
72
|
+
|
|
73
|
+
> To make public release possible, every UI screenshot in this document is redacted: provider route names, model names, Base URLs, and credential reference names appear as grey bars.
|
|
74
|
+
|
|
75
|
+
### 1. Page Header and Description
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
- The title is `dsh-expert-group`, and the subtitle reads verbatim `专家模型使用 DSH 当前已配置的 provider 和模型目录。`("Expert models use the providers and model catalog already configured in DSH.")
|
|
80
|
+
- That sentence is the most important constraint on this page: **candidate providers and models come only from what has already been added under DSH "Settings → Models"**. DEG ships no built-in model catalog and will not accept models outside it.
|
|
81
|
+
|
|
82
|
+
### 2. The "Model Configuration" Section (Collapsed by Default)
|
|
83
|
+
|
|
84
|
+

|
|
85
|
+
|
|
86
|
+
- The only collapsible area on the page, **collapsed by default**; while collapsed it does not read the provider catalog, and only expanding it issues the request.
|
|
87
|
+
- The collapsed state is not persisted: refreshing the page returns it to collapsed.
|
|
88
|
+
- The section's description text reads verbatim `与 DSH 内置模型设置页同一份数据流:provider 目录、凭据状态与模型目录都来自宿主公开接口,保存走正式 settings API 与 schema 校验。`("Same data flow as the DSH built-in model settings page: the provider catalog, credential status, and model catalog all come from public host interfaces, and saving goes through the official settings API and schema validation.")
|
|
89
|
+
- The section **has no save button of its own**: each provider card has its own "Apply / Cancel", independent of saving the role table above.
|
|
90
|
+
- When the host provides no model-configuration capability (no settings namespace view or remote service), the whole section is not rendered, and the rest of the page's controls keep working normally.
|
|
91
|
+
|
|
92
|
+
### 3. Provider Cards (Collapsed)
|
|
93
|
+
|
|
94
|
+

|
|
95
|
+
|
|
96
|
+
- Each card is one row: on the left is the provider display name (the route id is shown as well when it differs from the display name), followed by a green dot meaning the API Key is configured (both `title` and `aria-label` are `API Key 已配置`(API Key configured)); on the right are `删除`(Delete) and `编辑`(Edit).
|
|
97
|
+
- `编辑` expands the card; `删除` deletes that provider and its configuration. This is the **only operation on the whole page with a confirmation step**; the confirmation text is `确定删除 provider「<显示名>」及其配置吗?`(Delete provider "<display name>" and its configuration?), and cancelling writes nothing.
|
|
98
|
+
- Before deleting there is a role-linkage guard: if the deletion would make a default reasoning effort that some role is currently using disappear, the deletion is blocked and the affected roles are listed (see section 6).
|
|
99
|
+
|
|
100
|
+
### 4. Expanded Provider: API Key, Base URL, Protocol
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+
- **API Key**: `type=password`, write-only with no read-back, and no plaintext echo after saving. The input box has three placeholder states: `已配置(留空表示不修改)`(configured — leave blank to keep), `粘贴 API Key 后点应用`(paste an API Key, then click Apply), and `当前凭据只读`(the current credential is read-only).
|
|
105
|
+
- **Credential reference**: shown as `凭据引用:<REF>(API Key 只写入 credentials 服务,保存后不回显明文)`(Credential reference: <REF> — the API Key is only written to the credentials service and is not echoed in plaintext after saving); when there is no reference it shows `未设置凭据引用`(no credential reference set). REF is derived from the provider route id — uppercased, non-alphanumeric characters replaced with `_`, and `_API_KEY` appended, so the route `my-gateway` yields `MY_GATEWAY_API_KEY`; when the profile explicitly sets `apiKeyEnv`, that value wins.
|
|
106
|
+
- **Base URL**: placeholder `留空表示使用适配器默认端点`(leave blank to use the adapter's default endpoint); leaving it blank deletes the field and falls back to the adapter's default endpoint. Inside the card only empty / non-empty is checked, with no format validation (strict format validation happens in the "Add custom provider" card).
|
|
107
|
+
- **Protocol**: the first dropdown entry is always `适配器默认`(adapter default), and selecting it deletes the `api` field; the remaining options are read dynamically from the union type of `providers.<route>.api` in the **host namespace schema** — which protocols exist is decided by the current DSH version, and the plugin does not hardcode a vocabulary.
|
|
108
|
+
|
|
109
|
+
### 5. Model Catalog: Adapter Default / User Override / Restore Default / Fetch Models
|
|
110
|
+
|
|
111
|
+

|
|
112
|
+
|
|
113
|
+
- The label to the right of the `模型`(Models) heading has two states: `适配器默认(首次编辑时写入用户覆盖)`(adapter default — the first edit writes a user override) and `已按用户覆盖`(overridden by the user). Without a user override the adapter default catalog is shown read-only; only the first real edit writes a user override.
|
|
114
|
+
- `恢复默认`(Restore default): submits `unset` for the user-layer `models`, that is, **deletes the user override** rather than writing an empty array.
|
|
115
|
+
- `获取模型`(Fetch models): uses the draft (not yet saved) Base URL, protocol, and API Key to pull candidate models from the endpoint; on an empty result it reports `该端点没有返回任何模型`(the endpoint returned no models). The candidate list supports select all / deselect all, and when you click `采纳所选`(take the selection), **existing rows are not overwritten**.
|
|
116
|
+
- When the model catalog is empty it reports `没有模型:留空表示沿用适配器内置目录`(no models — leaving this empty keeps the adapter's built-in catalog).
|
|
117
|
+
|
|
118
|
+
### 6. Model Entries
|
|
119
|
+
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
Each model contains two rows of fields:
|
|
123
|
+
|
|
124
|
+
- **Model ID** and **Display name**: two text boxes whose placeholders are `模型 ID`(Model ID) and `显示名`(Display name). The ID is required and must be unique (validation messages `模型 ID 不能为空`(Model ID cannot be empty) / `模型 ID 重复(<id>)`(duplicate Model ID <id>)); a completely blank row is not written into the user override.
|
|
125
|
+
- **Context window** and **Max output tokens**: placeholder `继承适配器默认`(inherit the adapter default), accepting forms such as `500K` / `128K` (K = 1000, M = 1000000); leaving them blank inherits. Non-positive integers are rejected (`上下文窗口必须是正整数`(context window must be a positive integer) / `最大输出 token 必须是正整数`(max output tokens must be a positive integer)).
|
|
126
|
+
- **Reasoning efforts (multi-select)**: the fixed seven effort levels `off / minimal / low / medium / high / xhigh / max`, in the same order as DSH's pi-ai effort vocabulary, and **always shown regardless of whether the current catalog declares support**. On write, `off` is stored as an empty string and the other levels as the same lowercase string; when none of the seven is checked and there are no unknown keys, the `reasoningEfforts` field is deleted. Existing models restore their checks from `reasoningEfforts`; newly added models start with all seven unchecked.
|
|
127
|
+
- **Input types**: the `text` and `image` checkboxes, of which **at least one must remain** — once only one is checked it is locked and can no longer be unchecked.
|
|
128
|
+
|
|
129
|
+
This row also carries two cross-section constraints:
|
|
130
|
+
|
|
131
|
+
- The seven effort levels on the card are **model capabilities** (which effort levels this model offers), whereas `defaults.<role>.reasoningEffort` is a **single choice per role**. If one save would delete or uncheck a level that a role currently uses as its default, the save is blocked and the affected roles are listed, with the message reading verbatim `无法保存:将删除的思考档位 <档位> 正被以下专家角色使用:<角色(档位)>。请先在这些角色的配置中改用其它档位,本页不会自动改写角色配置。`("Cannot save: the reasoning effort <effort> being deleted is currently used by the following expert roles: <role (effort)>. Please switch those roles to another effort level in their configuration first; this page will not rewrite role configuration automatically.")
|
|
132
|
+
- The plugin **writes only the fields visible on the card** (`displayName`, `apiKeyEnv`, `baseURL`, `api`, `models`); hidden advanced fields such as `transport`, `retryPolicy`, and `compat` are neither displayed nor rebuilt nor deleted.
|
|
133
|
+
|
|
134
|
+
### 7. Model Actions: Add Model / Remove Model / Cancel / Apply
|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
138
|
+
- `添加模型`(Add model): appends an empty model row (all seven levels unchecked, no input types).
|
|
139
|
+
- **Remove model**: the `✕` button on the right of each row, whose accessible name is `删除模型 N`(Remove model N); **there is no confirmation step**.
|
|
140
|
+
- `取消`(Cancel): discards this card's draft and returns to the saved state.
|
|
141
|
+
- `应用`(Apply) (showing `应用中…`(Applying…) while in progress): local schema validation first → write the settings namespace → write the API Key only after that succeeds → re-read the provider catalog and credential status (the available efforts in the role table above refresh accordingly). A validation failure reports `设置校验未通过,已拒绝写入:<原因>`(settings validation did not pass, the write was rejected: <reason>).
|
|
142
|
+
- **Revision conflict**: if the configuration is changed by another session or page after the card was opened, the submission is rejected and the card shows `配置冲突:本卡片的修改未提交。`(Configuration conflict: this card's changes were not submitted.) together with `重新打开(加载最新配置)`(Reopen and load the latest configuration); both the local re-check and the host rejection paths carry explicit text, so other people's changes are never silently overwritten.
|
|
143
|
+
|
|
144
|
+
### 8. Provider Actions: Add Provider / Add Custom Provider / Reload Provider Catalog
|
|
145
|
+
|
|
146
|
+

|
|
147
|
+
|
|
148
|
+
- `添加提供方`(Add provider): picks one route from the adapters the host already has and adds it to the profile; the button is disabled when there is nothing to add.
|
|
149
|
+
- `添加自定义提供方`(Add custom provider): opens the custom card, with fields in the order route ID → display name → Base URL → protocol → API Key → models.
|
|
150
|
+
- The route ID has its own validation: it must look like `/^[a-z][a-z0-9-]{0,62}[a-z0-9]$/`, and it rejects a name that duplicates an existing provider or collides with the same credential reference.
|
|
151
|
+
- The Base URL must be http / https with a valid hostname; verbatim errors include `Base URL 只支持 http:// 或 https://`(the Base URL only supports http:// or https://) and `Base URL 必须包含主机名`(the Base URL must contain a hostname).
|
|
152
|
+
- Submission has two phases: write the settings profile first, then write the API Key separately; if the latter fails it shows `provider 配置已保存,但 API Key 写入失败:<原因>`(the provider configuration has been saved, but writing the API Key failed: <reason>), and only the credential write can be retried.
|
|
153
|
+
- `重新读取 provider 目录`(Reload provider catalog) (showing `读取中…`(Loading…) while in progress): re-fetches the provider catalog, credential status, and model catalog; this is the manual recovery entry point after a catalog anomaly or an external change.
|
|
154
|
+
- When there is no configurable provider at all it shows the empty state `当前宿主没有可配置的 provider。`(this host has no configurable providers).
|
|
155
|
+
|
|
156
|
+
### 9. Notifications
|
|
157
|
+
|
|
158
|
+

|
|
159
|
+
|
|
160
|
+
| Control | Default | Values | Stored field |
|
|
161
|
+
| --- | --- | --- | --- |
|
|
162
|
+
| `通知弹窗`(Notification popup) (checkbox) | On | On / Off | `dsh-expert-group.notificationEnabled` |
|
|
163
|
+
| `弹窗停留时间`(Popup duration) (dropdown) | `5s` | `5s / 10s / 30s / 60s / 5min / 10min / 永久`(forever) | `dsh-expert-group.notificationHoldMs` |
|
|
164
|
+
| `通知音量`(Notification volume) (slider + number box) | `100` | `0 – 100`, step `1` | `dsh-expert-group.notificationVolume` |
|
|
165
|
+
|
|
166
|
+
- Once `通知弹窗` is turned off, the Host **sends no notifications at all** and does not backfill them either; it only affects delivery and does not affect task snapshots, root-turn waiting, or recovery semantics.
|
|
167
|
+
- A volume of `100` means no attenuation at all, with only the Windows master volume applying; the slider and the number box are two-way bound to the same value.
|
|
168
|
+
- All three use deferred saving: after changing them you must click "Save" in the footer; invalid values fall back to the defaults (duration → `5s`, volume → `100`).
|
|
169
|
+
|
|
170
|
+
### 10. Unified Provider
|
|
171
|
+
|
|
172
|
+

|
|
173
|
+
|
|
174
|
+
- The displayed value is "the provider shared by the currently visible roles"; when it is mixed or nothing is selected it shows `无`(None), and the first entry `无` means "not unified".
|
|
175
|
+
- Choosing a concrete provider: switches every visible role's provider to it in one step, and migrates models with the same name by **model display name**; models that cannot be matched are cleared, leaving that role with "a provider selected but no model selected".
|
|
176
|
+
- Choosing `无`: only clears the top-level unified selection; it **does not** delete the provider any role already has.
|
|
177
|
+
- It only rewrites the draft: the role table below refreshes in step before you click Save, and is given up if you do not; after that, manually changing the provider on any row makes the "unified" state fall back to mixed (showing `无` again).
|
|
178
|
+
|
|
179
|
+
### 11. Role Table
|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
Six rows in a fixed order: **Explorer, Librarian, Oracle, Designer, Fixer, Observer**. The four columns mean:
|
|
184
|
+
|
|
185
|
+
| Column | Where the available values come from | Placeholder when nothing is selected |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| Expert | A role label button; clicking it opens that role's prompt editor | — |
|
|
188
|
+
| Provider | Providers from the DSH model catalog | `未选择供应商`(No provider selected) |
|
|
189
|
+
| Model | Models filtered by the selected provider | `先选择供应商`(Select a provider first) / `未选择模型`(No model selected) |
|
|
190
|
+
| Reasoning depth | `模型默认`(Model default) plus the efforts declared by the selected model, based on that model's reasoning capability | `先选择模型`(Select a model first) / `模型无思考深度`(Model has no reasoning depth) |
|
|
191
|
+
|
|
192
|
+
- When the current provider / model is no longer in the catalog, it stays visible as a disabled entry `<provider>(不可用)` / `<model>(不可用)`(unavailable), which makes stale configuration easy to spot.
|
|
193
|
+
- Selecting a model automatically applies the `defaultEffort` declared by the catalog; selecting `模型默认` (an empty value) for reasoning depth deletes that role's `reasoningEffort` field.
|
|
194
|
+
- The Observer row follows the host's meta projection: when the visual agent is not enabled the row is still in the table, but its three dropdowns are disabled.
|
|
195
|
+
- Stored field paths: `dsh-expert-group.defaults.<role>.provider / .model / .reasoningEffort`.
|
|
196
|
+
- Writes from the role table go through DEG's own commit endpoint (semantic validation + revision conflict detection) and do not reuse the configuration form's set / unset / mutate.
|
|
197
|
+
|
|
198
|
+
### 12. Bottom Management Options
|
|
199
|
+
|
|
200
|
+

|
|
201
|
+
|
|
202
|
+
| Control | Default | Stored field | Description |
|
|
203
|
+
| --- | --- | --- | --- |
|
|
204
|
+
| `Observer优先`(Observer priority) | Off | `observerPreferred` | When enabled, image submission switches to the Observer-first route and requires the Observer model to explicitly support image, otherwise it reports `已开启 Observer优先,但 Observer 模型未明确支持 image;原始输入已保留。`(Observer priority is on, but the Observer model does not explicitly support image; the original input has been preserved.); the parent-agent prompt also switches to the Observer-first paragraph |
|
|
205
|
+
| `任务管理`(Task management) | On | `taskManagementEnabled` | Controls whether DEG registers Expert Group in the sidebar: after turning it off and saving, it calls the disposer returned by the current install to release the registration (the native right pane and better-sidebar's default sidebar stay unchanged), and after turning it back on and saving it registers again; a missing field or a failed read is always treated as on |
|
|
206
|
+
| `Auto` | Off | `autoOpenSubagentSidebar` | When enabled, it automatically opens the subagent's session page in the right pane while the subagent runs, and automatically retracts it once the subagent finishes and resources go idle; it only closes tabs that Auto itself opened during this run, and tabs the user opened manually are never auto-closed. When disabled you can open or close them manually with `>>` / `<<` |
|
|
207
|
+
| `单个角色并发上限`(Per-role concurrency limit) | `10` | `roleConcurrencyLimit` | An integer in the range `1–64`. **It does not take effect immediately** and only applies after you click Save; invalid values are rejected by the Host (`设置 roleConcurrencyLimit 无效`, i.e. "setting roleConcurrencyLimit is invalid") |
|
|
208
|
+
|
|
209
|
+
### 13. Footer and Saving
|
|
210
|
+
|
|
211
|
+
- The footer status line has only three values: `有未保存的修改`(Unsaved changes), `已保存`(Saved), and empty.
|
|
212
|
+
- Buttons: `保存`(Save) (showing `保存中…`(Saving…) while saving) and `放弃修改`(Discard changes). Save errors are displayed as text above the footer, with no separate dialog.
|
|
213
|
+
- Saving requires the settings namespace to be writable and the catalog to be ready; when it is read-only or not ready, saving is unavailable.
|
|
214
|
+
|
|
215
|
+
### Differences from the DSH Built-in "Models" Settings Page
|
|
216
|
+
|
|
217
|
+
| Aspect | DSH built-in models page | DEG "Model configuration" section |
|
|
218
|
+
| --- | --- | --- |
|
|
219
|
+
| Visible fields | Includes hidden advanced YAML fields (`transport`, `retryPolicy`, `compat`, etc.) | Only the fields visible on the card; hidden fields are not displayed, rebuilt, or deleted |
|
|
220
|
+
| Save granularity | Whole-page submission | Each provider card has its own "Apply / Cancel", and the section has no save button |
|
|
221
|
+
| Write path | The host's generic settings write surface | Also goes through the host's `settings.mutate`; the model-catalog layer does not run DEG semantic validation |
|
|
222
|
+
| Role configuration | — | Written through DEG's own settings-commit endpoint, with semantic validation and revision conflict detection |
|
|
223
|
+
|
|
224
|
+
## Parent Agent Configuration
|
|
225
|
+
|
|
226
|
+
- `enableOrchestrator` is enabled by default: the root session receives the upstream orchestrator's workflow manager prompt and is responsible for breaking down tasks, choosing experts, integrating results, and verifying; subagents do not inherit that parent-agent section.
|
|
227
|
+
- To turn it off: set `enableOrchestrator` to `false` in `cordis.patch.yml` and restart DSH; this only disables the parent-agent prompt and does not affect the plugin's role tools themselves.
|
|
228
|
+
- This port does not override the root agent's `provider` / `model`; the main model and the UI model override continue to be managed by the DSH Host.
|
|
229
|
+
|
|
230
|
+
## Model Configuration
|
|
231
|
+
|
|
232
|
+
> For the user-facing, control-by-control description with screenshots, see "[Settings page in detail](#settings-page-dsh-expert-group)" above; this section only records implementation details and boundaries.
|
|
233
|
+
|
|
234
|
+
- **Global expert configuration is maintained only on the "dsh-expert-group" settings page**: providers, each expert's default model / reasoning depth, the notification toggle and volume, `Observer优先`, **Task management**, and **Auto** are all persisted to the plugin settings namespace.
|
|
235
|
+
- The plugin no longer offers a right-side **Model configuration** panel, a better-sidebar "Expert models" tab, or a session title-bar entry point; model selection is still maintained only on the settings page. Task state is shown by the Expert Group sidebar described below.
|
|
236
|
+
- The settings page's model candidates come only from DSH's current model catalog (the models already added under "Settings → Models"), and they are validated twice by the client and the Host before saving.
|
|
237
|
+
- **The "Model configuration" section (collapsed by default)**: at the bottom of the settings page there is another section, also collapsed by default, that mirrors the visible capabilities of DSH's built-in model settings page — provider catalog, API Key (through the credentials service, write-only with no plaintext read-back), Base URL, protocol, adding / removing providers, fetching models, adding / removing models, model ID / display name, context window, max output tokens, input types, and the model catalog's "adapter default / user override / restore default". Its scope **excludes** the built-in page's hidden advanced YAML fields (`transport`, `retryPolicy`, `compat`, etc.): the card only writes the fields it can see, and those fields are neither displayed nor rebuilt.
|
|
238
|
+
- **Saved separately from role configuration**: each provider card holds its own draft with its own "Apply / Cancel", and the section itself has no save button; the save button and behaviour of the role table above are completely unchanged. A submission is fenced by the namespace revision read when the card was opened: if the revision has already moved on outside (another session or page), the overwrite is refused, and the card shows a conflict notice together with a "Reopen" entry point.
|
|
239
|
+
- **Seven reasoning efforts (multi-select)**: the advanced settings of every model entry contain seven checkboxes `off / minimal / low / medium / high / xhigh / max`, in the same order as DSH's pi-ai effort vocabulary, and they are always shown regardless of whether the current catalog declares support. Existing models restore their checks from `reasoningEfforts`; new models start with all seven unchecked. On save, `off` writes an empty string value and the other levels write the same lowercase string; only these seven visible keys are added or removed, while unknown keys that are not displayed are preserved; when none of the seven is checked and there are no unknown keys, the `reasoningEfforts` field is deleted.
|
|
240
|
+
|
|
241
|
+
## Expert Group Task Sidebar
|
|
242
|
+
|
|
243
|
+
Expert Group is the task view that DEG registers in the right sidebar: it draws the complete task tree of **the root session the current session belongs to** — the parent agent is the root node, and ordinary experts, fan-out shards, and live run state all hang below it. It is a read-only view, and the only operations that write state are cancelling and retrying fan-out tasks.
|
|
244
|
+
|
|
245
|
+

|
|
246
|
+
|
|
247
|
+
How it is registered depends on the host: without `dsh-better-sidebar` installed it registers a native DSH right-pane page and auto-opens it for each root session the first time; with it installed it registers only a standalone `expert-group` page with `ctx.betterSidebar`. If the user disables that page in better-sidebar, DEG neither opens it automatically nor falls back to registering the other kind of page.
|
|
248
|
+
|
|
249
|
+
### The Four Node Types
|
|
250
|
+
|
|
251
|
+
| Node | First-line title | When it appears |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| Parent agent (root node) | The fixed Chinese text `主代理`(Parent agent) | Always present; the root's taskName / session id are not shown |
|
|
254
|
+
| Ordinary expert | The short description of the latest dispatch | One node is created per expert delegation |
|
|
255
|
+
| fan-out aggregate branch | `<角色> · <N> shards · running|ready` (`<角色>` is the role name) | One fan-out submission creates a parent node, and its shards are its child nodes |
|
|
256
|
+
| Idle subagent group | `空闲子代理(N)`(Idle subagents (N)) | Appears when a ready ordinary expert or a settled fan-out parent task exists; collapsible |
|
|
257
|
+
|
|
258
|
+
### Three-Line Structure of a Task Row
|
|
259
|
+
|
|
260
|
+
Except for fan-out shards, every agent node has a fixed three lines, and their content does not depend on the collapsed state:
|
|
261
|
+
|
|
262
|
+

|
|
263
|
+
|
|
264
|
+
1. **Task name**: the root node always shows `主代理`; expert nodes show the latest dispatch description; fan-out shard rows show `分片代号 任务名称`(shard code, then task name) (shard rows wrap by content instead of being ellipsized onto a single line). The full text doubles as the tooltip and the accessible label.
|
|
265
|
+
2. **Model metadata**: `角色 · provider · model · 思考深度`(role · provider · model · reasoning depth) (provider and model names in this document's screenshots are redacted). Missing values use the stable placeholders `未知供应商`(Unknown provider) / `未知模型`(Unknown model) / `默认`(Default).
|
|
266
|
+
3. **Status summary**: `标题 · 可继续|一次性 · 正在运行|当前未运行`(title · continuable | one-shot · running | not running), for example, verbatim `修复 DSH 运行状态文字 Animations · 正在运行`(fixing the DSH run-status text animations · running) and `你是 oracle 子代理。任务内容 · 可继续 · 当前未运行`(you are an oracle subagent. Task content · continuable · currently not running). The root node keeps only the dynamic title and the activity state and does not show the continuation mode.
|
|
267
|
+
|
|
268
|
+
- `可继续`(Continuable) is shown only when the session catalog explicitly proves the subagent is continuable; anything unknown degrades to not continuable, and unknown is never described as continuable.
|
|
269
|
+
- The state dot is DSH's native `primitives.StateDot` (`running` / `ready`); when the official component is missing it degrades to a static dot.
|
|
270
|
+
|
|
271
|
+
### Interaction
|
|
272
|
+
|
|
273
|
+
- Clicking the **task name** or the **third line**: the main view switches to that subagent session; when the host provides no workspace navigation capability the whole row stops being clickable, so no dead button is left behind.
|
|
274
|
+
- Clicking the **chevron**: only collapses / expands that node (`aria-expanded`; titles `折叠`(Collapse) / `展开`(Expand)) without navigating.
|
|
275
|
+
- Clicking **`>>` / `<<`**: affects only the right sidebar; see the next section.
|
|
276
|
+
- A fan-out branch offers `取消父任务(未完成 N 个分片)`(Cancel parent task (N shards unfinished)) while active shards remain; a shard row offers `取消分片`(Cancel shard) in the active state and `重试分片`(Retry shard) in the retryable state. Destructive operations ask the host for confirmation first (for example `确认取消父任务?未完成 N 个分片。`(Cancel the parent task? N shards are unfinished.)); when the plugin cannot obtain confirmation capability the whole operation is not executed — it never runs silently.
|
|
277
|
+
|
|
278
|
+
### State, Collapse, and Refresh
|
|
279
|
+
|
|
280
|
+
- Expanded by default: running, waiting, failed, timed-out, and interrupted tasks; collapsed by default: idle, ready, successful, or cancelled tasks.
|
|
281
|
+
- Manual collapse / expand choices are stored per **root session** in `sessionStorage`, and later state changes do not override the user's choice.
|
|
282
|
+
- When idle it refreshes by bounded polling (about once every 2.5s); it does not re-render when the whole tree is unchanged, and real changes are never swallowed.
|
|
283
|
+
- Only one root session is drawn: the cache is isolated per root, so switching sessions never shows the previous root's content on the new root.
|
|
284
|
+
- The panel title is `Expert Group`; if the snapshot fails to load, the title bar shows `快照读取失败`(Snapshot load failed) and a `重试`(Retry) button; the first screen shows a skeleton when there is no cache, and an empty tree shows `暂无 Expert Group 任务`(No Expert Group tasks).
|
|
285
|
+
|
|
286
|
+
### fan-out Aggregate Branch
|
|
287
|
+
|
|
288
|
+

|
|
289
|
+
|
|
290
|
+

|
|
291
|
+
|
|
292
|
+
- The parent node's title is `<角色> · <N> shards · running|ready`, and its dot takes its colour from the canonical run state of the whole fan-out.
|
|
293
|
+
- The first line of a shard row is `分片代号 任务名称`, followed by the same model metadata and status summary.
|
|
294
|
+
- A shard whose latest attempt succeeded is displayed as successful (even if earlier attempts failed); cancel and retry always act on the **latest attempt's childId**.
|
|
295
|
+
- While a cancel / retry request is in flight the button is disabled and marked `aria-busy`, so repeated clicks do not send a second request.
|
|
296
|
+
|
|
297
|
+
### Idle Subagent Group
|
|
298
|
+
|
|
299
|
+

|
|
300
|
+
|
|
301
|
+
Running experts and unsettled fan-outs stay in the main list; idle (ready) ordinary experts and fan-out parent tasks whose shards have all settled are collected into the collapsible group `空闲子代理(N)`, so that long task trees are not drowned by historical nodes. A settled fan-out keeps its shard tree and task operations after moving into the group. The group header is a container rather than an agent, so its dot is always ready; members inside the group each take their colour from their own actual state.
|
|
302
|
+
|
|
303
|
+
### `>>` and `<<`: Opening and Closing Subagent Sessions in the Right Sidebar
|
|
304
|
+
|
|
305
|
+
After every task name there is a 30×20px arrow button, and **the same button has two forms**:
|
|
306
|
+
|
|
307
|
+
| Form | Meaning | Colour | Accessibility |
|
|
308
|
+
| --- | --- | --- | --- |
|
|
309
|
+
| `>>` | **Opens** this row's subagent session in the right sidebar | Amber (`state-warning-primary`, or `#f59e0b` when the token is missing) | `title` / `aria-label` is `在侧边栏打开 <任务名>`(Open <task name> in the sidebar); with toggle capability, `aria-pressed=false` and `data-open=false` |
|
|
310
|
+
| `<<` | **Closes** this row's session, which is already open in the right sidebar | Cyan (`state-business-primary`, or `#22d3ee` when the token is missing) | `aria-label` is `在侧边栏关闭 <任务名>`(Close <task name> in the sidebar); `aria-pressed=true`, `data-open=true` |
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
**`>>` (Open)**
|
|
314
|
+
|
|
315
|
+
- None of the render conditions may be missing: the host provides `sidebarRight.openResource`, this row's address resolves to a subagent session address, and the named arrow icon is available. If any one of them fails, **the button is not rendered** — an empty button, or a warning icon posing as an arrow, is misleading.
|
|
316
|
+
- Clicking only opens the subagent session page in the right sidebar and **never switches the main view**; `preventDefault + stopPropagation` block both mouse bubbling and keyboard Enter / Space, so clicking the arrow does not also navigate to that subagent session.
|
|
317
|
+
- The first open prefers DSH's public `split()` and uses the returned `paneId` to place the resource explicitly; only hosts without `split()` fall back to `preferNewPane: true`. When a subagent pane already exists it passes `paneId` explicitly and turns `preferNewPane` off, so multiple subagents never wrongly share the left pane with Expert Group.
|
|
318
|
+
- If the host temporarily cannot open that address (the seat has not been minted yet), the thrown error is swallowed by the plugin: it affects neither the main view nor the other buttons on the same row.
|
|
319
|
+
|
|
320
|
+
**`<<` (Close)**
|
|
321
|
+
|
|
322
|
+
- It turns into `<<` only when all three capabilities are present — "`close` is available", "either `active()` or the public `openTabs` inventory can provide evidence that it is open", and "the named left-arrow icon is available" — and when this row's exact `tabId` **has already been recorded from evidence**; if any one of them is missing it falls back to a static `>>`, and it never shows a fake toggle that will not close anything when clicked.
|
|
323
|
+
- Clicking closes only the one tab belonging to this row; it does not switch the main view, does not affect other rows, and performs no fuzzy matching.
|
|
324
|
+
- When the user closes that tab in the right sidebar themselves, the plugin makes a **best-effort** observation through the right-pane DOM markers and `openTabs`: only after two consecutive confirmations that the target is gone does it reset the arrow to `>>`. Indeterminate states such as panel switching, focus changes, or unreadable DOM are never guessed as "closed" nor as "still open".
|
|
325
|
+
|
|
326
|
+
**Right sidebar width**
|
|
327
|
+
|
|
328
|
+
- When splitting for the first time from a single pane holding only Expert Group, the open transaction first temporarily widens the right sidebar to about 50%, and once the target tab is confirmed to have landed in the returned pane it keeps the 50% adjustable mode; if the split fails it restores the original 20% locked width.
|
|
329
|
+
- Day-to-day opening / closing of tabs inside an existing subagent pane does not change the width.
|
|
330
|
+
- After the first two panes form: Expert Group takes 40% of the right sidebar (about 20% of the viewport) and the subagent pane takes 60% of the right sidebar (about 30% of the viewport); once the last tracked pane closes, the default width of about 20% is restored.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-expert-group
|
|
2
2
|
|
|
3
|
+
[English](README.en.md) | **中文**
|
|
4
|
+
|
|
3
5
|
> settings 命名空间 `dsh-expert-group` 保存全局 provider、`defaults` 和通知偏好;会话覆盖与子代理快照属于插件自己的 `dsh_expert_group` storage domain。
|
|
4
6
|
|
|
5
7
|
一个可安装到 DSH(DeepSeek Harness)的外部 **dual-face Cordis bundle**,其专家分工、
|
|
@@ -52,9 +54,9 @@ DEG 在 DSH Web 界面上的可见面只有三处,彼此独立、互不重叠
|
|
|
52
54
|
|
|
53
55
|
| 界面 | 位置 | 能做什么 | 详细说明 |
|
|
54
56
|
| --- | --- | --- | --- |
|
|
55
|
-
| **dsh-expert-group 设置页** | 设置 → `dsh-expert-group` | 维护全局 provider、六类专家的默认模型与思考深度,以及通知偏好、`Observer
|
|
57
|
+
| **dsh-expert-group 设置页** | 设置 → `dsh-expert-group` | 维护全局 provider、六类专家的默认模型与思考深度,以及通知偏好、`Observer优先`、`任务管理`、`Auto` 与单个角色并发上限 | [设置页功能详解](#设置页功能详解dsh-expert-group) |
|
|
56
58
|
| **Expert Group 任务侧边栏** | 右侧栏(原生右栏,或装有 better-sidebar 时的独立页) | 查看当前根会话的任务树与运行状态;用 `>>` / `<<` 在右栏打开或关闭某个子代理会话;取消 / 重试 fan-out 任务 | [Expert Group 任务侧边栏](#expert-group-任务侧边栏) |
|
|
57
|
-
| **运行状态行与系统通知** | 主视图左下角 / Windows 桌面右下角 | 运行中显示 `Orchestrator running`、`Explorer running` 等运行词;任务进入终态时由 Windows Toast 提醒 |
|
|
59
|
+
| **运行状态行与系统通知** | 主视图左下角 / Windows 桌面右下角 | 运行中显示 `Orchestrator running`、`Explorer running` 等运行词;任务进入终态时由 Windows Toast 提醒 | Web 状态提示 |
|
|
58
60
|
|
|
59
61
|
DEG 没有模型可调用的通知工具,也不在会话标题栏或右侧栏放模型配置入口:全局专家配置只在设置页维护,会话内的主模型覆盖仍由 DSH Host 自己管理。
|
|
60
62
|
|
|
@@ -201,6 +203,7 @@ DEG 没有模型可调用的通知工具,也不在会话标题栏或右侧栏
|
|
|
201
203
|
| --- | --- | --- | --- |
|
|
202
204
|
| `Observer优先` | 关闭 | `observerPreferred` | 开启后图片提交改为 Observer 优先路由,并要求 Observer 模型明确支持 image,否则给出 `已开启 Observer优先,但 Observer 模型未明确支持 image;原始输入已保留。`;主代理提示词也切换到 Observer 优先段落 |
|
|
203
205
|
| `任务管理` | 开启 | `taskManagementEnabled` | 控制 DEG 是否把 Expert Group 注册到侧边栏:关闭并保存后调用当前 install 返回的 disposer 释放注册(原生右栏与 better-sidebar 的默认侧栏保持不变),重新开启并保存后重新注册;字段缺失或读取失败一律按开启处理 |
|
|
206
|
+
| `Auto` | 关闭 | `autoOpenSubagentSidebar` | 开启后,子代理运行期间自动在右栏打开它的会话页,并在子代理结束、资源空闲后自动收回;只关闭本次运行中由 Auto 亲自打开的 tab,用户手动打开的 tab 不会被自动关闭。关闭后可用 `>>` / `<<` 手动打开或关闭 |
|
|
204
207
|
| `单个角色并发上限` | `10` | `roleConcurrencyLimit` | 取值范围 `1–64` 的整数。**不是即时生效**,点保存后才应用;非法值由 Host 拒绝(`设置 roleConcurrencyLimit 无效`) |
|
|
205
208
|
|
|
206
209
|
### 13. 页脚与保存
|
|
@@ -229,27 +232,12 @@ DEG 没有模型可调用的通知工具,也不在会话标题栏或右侧栏
|
|
|
229
232
|
|
|
230
233
|
> 面向使用者的逐控件说明与截图见前面的「[设置页功能详解](#设置页功能详解dsh-expert-group)」;本节只记录实现细节与边界。
|
|
231
234
|
|
|
232
|
-
- **全局专家配置只在“dsh-expert-group”设置页维护**:provider、各专家默认模型/思考深度、通知开关和音量、`Observer
|
|
235
|
+
- **全局专家配置只在“dsh-expert-group”设置页维护**:provider、各专家默认模型/思考深度、通知开关和音量、`Observer优先`、**任务管理** 与 **Auto** 都会持久化到插件 settings namespace。
|
|
233
236
|
- 插件不再提供右侧**模型配置**面板、better-sidebar 的“专家模型”Tab 或会话标题栏入口;模型选择仍只在设置页维护。任务状态由下述 Expert Group 侧边栏展示。
|
|
234
237
|
- 设置页的模型候选只来自 DSH 当前模型目录(“设置 → 模型”中已添加的模型),保存前会由客户端和 Host 双重校验。
|
|
235
238
|
- **“模型配置”区域(默认折叠)**:设置页底部另有一个同样默认折叠的区域,复制 DSH 内置模型设置页的可见能力——provider 目录、API Key(走 credentials 服务,只写不读明文)、Base URL、协议、添加/删除 provider、获取模型、模型增删、模型 ID/显示名、上下文窗口、最大输出 token、输入类型,以及模型目录的「适配器默认 / 用户覆盖 / 恢复默认」。范围**不包括**内置页的隐藏高级 YAML 字段(`transport`、`retryPolicy`、`compat` 等):卡片只写自己看得见的字段,这些字段既不被显示也不被重建。
|
|
236
239
|
- **与角色配置分开保存**:每个 provider 卡片各自持有草稿并有独立的「应用 / 取消」,区域本身没有保存按钮;上方角色表的保存按钮与行为完全不变。提交以卡片打开时读到的 namespace revision 作为栅栏:外部(其它会话/页面)已推进 revision 时拒绝覆盖,卡片显示冲突提示与「重新打开」入口。
|
|
237
240
|
- **七个思考档位(可多选)**:每个模型条目的高级设置里有 `off / minimal / low / medium / high / xhigh / max` 七个复选框,顺序与 DSH 的 pi-ai 档位词表一致,且无论当前目录是否声明支持都始终显示。已有模型按 `reasoningEfforts` 映射恢复勾选;新模型七档全部不勾选。保存时 `off` 写空字符串值、其余档位写同名小写字符串,只增删这七个可见键并保留未展示的未知键;七档全不勾选且没有未知键时删除 `reasoningEfforts` 字段。
|
|
238
|
-
- **模型目录的继承/覆盖/恢复默认**:没有用户覆盖时显示适配器/组合层默认(只读展示),首次实际编辑才 materialize 一份用户覆盖(写入卡片可见字段);「恢复默认」删除用户覆盖(`unset`),绝不把默认值固化成冗余覆盖。
|
|
239
|
-
- **能力多选与角色单选分离**:卡片上的七档是**模型能力**(该模型会提供哪些档位),`dsh-expert-group.defaults.<role>.reasoningEffort` 仍是**角色单选**。若一次保存会删除/取消某个正被角色默认使用的档位,保存被阻止并列出受影响角色,插件**不会**自动改写角色配置。
|
|
240
|
-
- 保存成功后区域会重新读取 provider 目录与凭据状态,因此上方角色表的可用档位随之刷新。
|
|
241
|
-
- 客户端唯一的数据流实现在 `client-model-config.js`(装配期与 freshness 一样先于 descriptor 执行的浏览器安全 IIFE);`scripts/assemble-client.mjs` 负责把它拼进发布 bundle,请勿手工编辑 `client-bundle.js`。
|
|
242
|
-
- 子代理实际生效的 `provider / model / reasoningEffort` 快照仍写入插件自有 `dsh_expert_group` storage domain 的 `child_models` 表(上限 300 条自动淘汰最旧记录);这是运行数据,不是 settings 配置。
|
|
243
|
-
- **图片路由(问题2)**:`Observer优先` 默认关闭并持久化在该 settings namespace。根会话 Composer 的图片提交先经客户端公开 `ConversationController.sendSession` 接缝上传到 Host;Host 对全部图片严格校验,使用真实转码器将 JPEG/WebP/GIF 转为 PNG,并通过公开 `ctx.attachments.saveImage` 创建新的 PNG durable ref。PNG 字节也会先验签再保存,绝不只改文件名后缀。副本保持 durable,随 session/attachment 引用存活,由 Host 的 retention/GC 回收不可达对象;插件不修改原始 durable attachment ref。能力判定优先读取最新的 `model/selection` 与 `request/header` 事件证据,模型切换后立即发图不会把旧 header 当作当前模型;旧宿主缺少事件面时才保守回退兼容路径。
|
|
244
|
-
- **提交期路由复核(TOCTOU)**:Host 判定能力后会签发一个不泄露 `provider/model` 的不透明 `routeClaim`(Node `randomUUID`,claim 表只存 Host 侧路由摘要,容量上限 64 + 5 分钟 TTL,惰性淘汰、同时绑定签发它的 root session 与 **exact root generation token**)。客户端在真正 `session.prompt` 之前用同一 Connection/legacy RPC 发起 `image-route-validate` 复核:路由变化返回 409 `route-changed`、claim 伪造/过期/跨会话/跨代际返回 400 `route-claim-invalid`,两者都会 abandon 本次提交且**不**调用 `session.prompt`;Host 自身也会在能力异步查询之后再核对一次 claim,避免明显窗口。同一个 `sessionId` 的 root 被重建/替换(replacement、重启恢复)后,旧 claim 立即失效并清理,绝不会把旧代际的图片提交落到新 root 上。
|
|
245
|
-
- **签发失败回收**:claim 只有在**成功交付**给客户端时才保留。签发之后若 Host 自检发现路由变化、请求被取消、命中结构化错误,或响应写入实际失败(传输层吸收 socket 竞态并返回 `written:false`),Host 都会立即删除该 claim(连同暂存的 durable refs),因此不会留下客户端永远无法消费的 pending claim。
|
|
246
|
-
- **Observer 两阶段提交(避免孤儿 child)**:`target=observer` 时 `image-route` **只**完成 PNG 规范化、能力判定与 claim 签发,并把 Host 创建的 durable PNG refs 暂存在 claim 条目里(只存 ref、不存字节、不存用户文本),绝不启动 child。客户端随后执行 `image-route-validate`(对 observer claim **只验证不消费**,可安全重复复核)→ `image-route-observer-commit`(Host 复核当前路由 → Observer admission → 一次性消费 claim 并丢弃暂存 refs)。commit 失败(路由变化/claim 失效/重复提交/取消/跨代际)一律不启动 child;若 admission 已被接受而后续检测失败,Host 会用公开 `subagents.interrupt(childId, { kind: "ancestor", agent })` 尽力中断该 child。claim 条目带 `issued/committing` 状态:并发或重复 commit 返回 409 `route-commit-in-progress`(成功后变为 `route-claim-invalid`),进入 commit 前取消请求会顺带删除 claim,claim 表始终有界。
|
|
247
|
-
- **Observer 清理 token(commit 成功后的补偿与释放)**:commit 成功时 Host 额外签发一次性 `cleanupToken`(`randomUUID`,绑定 `{sessionId, exact root generation, childId}`,不含 provider/model/text;独立表同样容量上限 64 + 5 分钟 TTL + 卸载清理,且**只淘汰过期项**)。客户端在提交 root prompt 之后按结果二选一:失败/取消路径(prompt 非成功返回、抛错、被取消、retirement 非 `observed`、插件在等待期间被卸载)尽力调用 `image-route-observer-cancel` 让 Host 中断刚创建的 child;成功 `observed` 路径调用 `image-route-observer-release` **只释放凭据、绝不 interrupt**,否则 token 会一直占到 TTL,连续成功提交填满容量后新的 Observer commit 会被 503 拒绝。两个动作都是严格 `action/sessionId/childId/cleanupToken`(Connection/legacy 都要求图片专用标记),**不复用**调用方可能已取消的信号,失败只尽力而为且不改变原始 prompt 结果;token 在全部校验通过后立即消费(不可重放),伪造/跨会话/串 child/重复消费统一返回 404 `route-cleanup-invalid`(不泄露是哪一项校验失败)。
|
|
248
|
-
- **容量预占与交付校验**:commit 在**启动 Observer 之前**原子预占一个 cleanup token 槽位(`已签发 + 已预占 < 上限`,检查与自增之间无 await,因此并发 commit 不可能超发);容量已满时拒绝本次 commit 并返回 503 `observer-cleanup-capacity`、删除 routeClaim、不创建 child —— 绝不为新 child 淘汰仍可能活跃的旧 token(那会让旧 child 失去客户端补偿通道而孤儿化)。admission 或后置检查失败会释放预占,签发成功则把预占转成 token。**所有 image-route 成功响应都做交付判定**(传输层报告 `written:false` 的 socket 竞态不算交付):commit 响应未交付时立即撤销刚签发的 token 并 `interrupt` 该 child;`image-route-validate` 未交付时删除仍处于"只验证不消费"的 observer claim(连同暂存 durable refs),使客户端既无法继续 commit 也不占用容量(main 的 validate 成功即消费语义不变)。
|
|
249
|
-
- **旧 Host 兼容**:main 路径在没有 `routeClaim` 的旧 Host 上保持既有提交行为(不发校验请求、不静默假设校验通过);`target=observer` 的响应没有 `routeClaim` 时客户端**必须 fail-closed**(abandon、不调用 `session.prompt`,提示"Host 不支持安全的 Observer 图片提交,请升级插件/Host"),因为旧 Host 会在 image-route 阶段就启动 child,无法保证"能力判定模型 == 实际 prompt 模型",也无法在提交失败时避免孤儿 child。
|
|
250
|
-
- 当 Observer 直接接收图片时,插件只使用 Host 创建的 `image/png` durable attachment ContentBlock;Observer admission 固定为有界的纯视觉子任务(正文由 `prompt-contracts.js` 的固定契约与"无用户文字上下文"说明组成),禁止读取主任务上下文、执行主任务步骤、修改文件或调用委派/goal/finalize 工具。**自动图片入口不向 Observer 传递任何用户原始文本**(含路径、秘密与注入载荷;`buildObserverVisualTask()` 不接受参数);主代理仍通过 root prompt 持有原始 text,客户端提交文字通知说明视觉报告会经既有 child feedback 返回、原始任务仍由主代理持有。需要携带用户问题的视觉任务只能走 role tool 的手工调用路径。主模型直达时,Host 返回的也必须是实际 PNG encoded image。
|
|
251
|
-
- **取消语义**:`llm.resolveModelInfo` 等异步边界抛出的 `AbortError/ABORT_ERR`(或请求 signal 已取消)一律不被能力兜底吞掉,图片路由的 normalize 后、能力查询前后、claim 校验/commit 前后与 Observer admission 前后都做 fail-fast,由同一个 `mapRpcError` 映射为 `gateway/cancelled`(499);只有真正的非取消失败才按 `unknown` → main-first 继续。取消的 commit 不会 admission,也不会留下 pending claim;已 admission 但随后取消时会尽力中断 child。
|
|
252
|
-
- **原生输入边界**:插件通过公开的 `ConversationController.sendSession` 接缝覆盖 Composer 根会话图片提交,并在 `Session.beginSubmission/prompt` 上保留原生回显、模式、requestId、退休和失败收尾语义;它不能拦截其他调用方绕过 Composer 直接调用的原生 Session.prompt。因此该边界不是对 DSH 核心的修改或承诺全局拦截;`image-routing.js` 和 Observer role tool 仍提供可独立测试的 PNG 规范化与三态路由。unknown 能力始终保持 main-first,不静默切换。
|
|
253
241
|
|
|
254
242
|
## Expert Group 任务侧边栏
|
|
255
243
|
|