dsh-advisor-group 0.1.3 → 0.1.4

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 CHANGED
@@ -1,6 +1,10 @@
1
1
  # dsh-advisor-group
2
2
 
3
- > A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that lets the main model consult **multiple expert advisor models** in a retro chat-group card — for professional, long-tail world-knowledge, high-risk, or uncertain questions.
3
+ > A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that lets one session **borrow wisdom outward and memory sideways**.
4
+ >
5
+ > For professional, long-tail world-knowledge, high-risk or uncertain questions, the main model consults **multiple expert advisor models** in a retro chat-group card; for something another session already knows, it asks that session instead of re-deriving it from scratch.
6
+ >
7
+ > To try it: `dsh plugin --profile web add dsh-advisor-group`, restart DSH, and put `@顾问群` in your question.
4
8
 
5
9
  [![dsh-plugin](https://img.shields.io/badge/DSH%20plugin-dsh--plugin-3f8cff)](https://github.com/topics/dsh-plugin)
6
10
 
@@ -12,24 +16,28 @@
12
16
 
13
17
  ## ✨ Features
14
18
 
15
- - **Auto-deepen consultation pipeline** — one `ask_advisors` call runs up to `maxRounds` rounds automatically: each round is a *driver deep-question → advisor A → advisor B (sees A) → advisor C (sees A+B) → …* sequential relay, closed by a driver-generated synthesis conclusion.
16
- - **Zero-config driver model** — the driver formulation of deep follow-ups reuses the current agent's provider/model, so no extra API key or model setup is needed; it falls back to `discussion.driverModel` when the session header is unavailable.
17
- - **Three ways to activate** — `@顾问群` mention (force-start), same question repeated 3 times without resolution, or main-model self-assessed confidence below the threshold.
18
- - **Optional Jev semantic pre-classification (off by default)** — when `trigger.jev.enabled` is on, a configured Jev model judges non-force-started consultations first (escalation / high-risk / web-search fit, thresholds under `trigger.jev.*`); if Jev is unavailable the local rules-based classifier still decides. With `trigger.jev.useEnglishState`, the judge reads the caller-supplied English gist (`questionEn`) instead of the Chinese question — display and session records stay Chinese.
19
- - **Retro CRT chat cards** — green/amber/blue CRT themes, scanlines, LIVE/DONE headers, auto-expanded thinking panel with auto-scroll; advisor Markdown rendered with a link-protocol whitelist (headings, lists, code, quotes, links, tables).
20
- - **Ask another session (`ask_session`)** — hand one question to another live session — by exact session id, exact title, or a natural partial name such as the workspace name (`极简遥控器`) — and read what that session said between the delivery receipt and its next whole-session idle. The result is explicitly **not** a one-to-one reply, and the asking side renders the exchange in the same retro chat-group card. Guards: hop ≤ 2, no self/subagent target, one wait per session, a 32 KB payload budget; the target's turn is never cancelled.
21
- - **Provider presets (11 platforms · 26 presets)** — DeepSeek, Moonshot Kimi, Kimi Code, Aliyun Bailian, Zhipu AI, OpenAI, Claude, Gemini, SiliconFlow, AIHubMix, OpenRouter (OpenAI/Anthropic-compatible variants included).
22
- - **Security-minded by design** — API keys use official `SecretField` semantics (never returned to the browser; `apiKeysByProvider` key history is server-side only), SSRF-guarded diagnostics (https-only / loopback, no IP literals, no redirects), per-boot token auth on `/advisor-group/*` routes, and an atomic **configurable daily consultation cap** (default 50, can be disabled) persisted across restarts.
23
- - **Runtime toggle** — `toggle_advisor_group` enables/disables the plugin and persists the flag to settings.
19
+ - **Auto-deepen consultation pipeline** — one `ask_advisors` call runs up to `maxRounds` rounds (1–5, default 2). Round 1 uses your question as-is. Later rounds begin with a deeper follow-up from the driver model, or repeat the question if `autoDeepen` is disabled. Within each round, advisors relay in sequence (A → B sees A → C sees A+B → …). The driver concludes with a synthesis that always generates, regardless of `autoDeepen`.
20
+ - **Advisors can call session tools (2026-09-05)** — advisors can invoke tools visible to the session before answering. By default, access is limited to a read-only whitelist (read/grep/glob/web_search/web_fetch…). Tool calls route through the official `ctx.tools` pipeline (scope dispatch, guards, and pre/post policies), capped at 4 tool rounds before the advisor must reply in text. Each invocation appears as a `⛭ tool name · running / done / ⚠ failed` row in the card, expandable to inspect input and output. Switch between all session tools or off in settings.
21
+ - **Resume after a stop** — if a user stops the consultation or DSH restarts, the card's 「⏹ 停止」 button changes to 「▶ 继续聊天」 (Resume chat). Clicking it triggers `POST /advisor-group/resume` to continue from the checkpoint: unanswered advisors respond, previously answered ones are not queried again, remaining rounds run, and the final synthesis generates as usual. Session snapshots persist to `storages/advisor-group/sessions/<id>.json` (storing advisor IDs, messages, and statuses without credentials) and recover cleanly across restarts.
22
+ - **Zero-config driver model** — the driver model writes deep follow-up questions by reusing the active agent's provider and model. No separate API key or model configuration is required. If session headers are unreadable, it falls back to `discussion.driverModel`.
23
+ - **Three ways to activate** — mention `@顾问群` to force start; repeat the same question 3 times without resolution; or let the main model trigger consultation when its confidence drops below the threshold.
24
+ - **Optional Jev semantic pre-classification (off by default)** — when `trigger.jev.enabled` is active, the configured Jev model evaluates unforced requests (determining advisor need, high risk, or web-search suitability based on `trigger.jev.*` thresholds). If Jev is unreachable, the local rule-based classifier handles the decision. When `trigger.jev.useEnglishState` is enabled, Jev evaluates the caller-provided English gist (`questionEn`) instead of the Chinese prompt, while UI cards and session logs remain in Chinese.
25
+ - **Retro CRT chat cards** — green, amber, and blue themes with scanline effects and LIVE/DONE status badges. The 💭 thinking panel opens by default and expands as streams arrive. Content renders via lightweight Markdown with a strict link-protocol whitelist, supporting headings, lists, code blocks, blockquotes, links, and tables.
26
+ - **Ask another session (`ask_session`)** — forwards a question to another live session and wakes it. Use `list_sessions` to browse the roster and `ask_result` to collect asynchronous outputs. Responses represent conversation windows rather than 1-to-1 answers; see **The global swarm** for boundaries, cost, and usage.
27
+ - **Provider presets (12 platforms · 28 presets)** — DeepSeek, Moonshot Kimi, Kimi Code, Aliyun Bailian, Zhipu AI, OpenAI, Claude, Gemini, SiliconFlow, AIHubMix, OpenRouter, and **OpenCode Go** (including OpenAI and Anthropic-compatible variants).
28
+ - **Security-minded by design** — API keys follow official `SecretField` semantics and are never returned to the browser. The `apiKeysByProvider` credential archive stays strictly server-side. Diagnostic endpoints enforce SSRF protections (HTTPS-only, loopback allowed, rejecting raw IP literals and redirects). All `/advisor-group/*` routes require a per-boot token. You can also enforce a daily limit on new consultations (see **Daily quota**).
29
+ - **Runtime toggle** — `toggle_advisor_group` enables or disables the plugin and persists the setting.
24
30
 
25
31
  ## ✅ Compatibility
26
32
 
27
33
  | Surface | Status |
28
34
  |---|---|
29
- | Harness | DeepSeek Harness `0.1.7-rc.2` → `0.2.x` (incl. `0.2.0-rc.1`) |
35
+ | Harness | **`0.2.1-rc.2` — the version this release was gated against**; the declared peer range is `>=0.1.7-rc.2 <0.3.0` |
30
36
  | Node | `^22.19.0 \|\| >=24.0.0` |
31
37
  | Platforms | DSH Web and the desktop app (same client bundle) + headless host logic |
32
38
 
39
+ > On the lower bound: `0.1.7-rc.2` remains inside the declared peer range, but was not re-verified during this round. Type checks, tests, and build were all completed against `0.2.1-rc.2`. If an older 0.1.x host fails to load the plugin, the issue stems from interface changes rather than bugs in this release.
40
+
33
41
  ## 📦 Installation
34
42
 
35
43
  ```sh
@@ -40,6 +48,8 @@ dsh plugin --profile web add dsh-advisor-group
40
48
  npx @deepseek-ai/dsh web
41
49
  ```
42
50
 
51
+ > **About step 2**: `npx @deepseek-ai/dsh web` pulls whichever DSH version is latest at that moment, which can fall outside this plugin's peer range (`>=0.1.7-rc.2 <0.3.0`); the host then refuses to load the plugin. Restart the way you normally do (an existing start script or the desktop app works too) — this exact command is not required.
52
+
43
53
  > **From source (development)**
44
54
  > ```sh
45
55
  > npm install --legacy-peer-deps --no-audit --no-fund
@@ -54,22 +64,53 @@ npx @deepseek-ai/dsh web
54
64
  <img width="791" height="797" alt="image" src="https://github.com/user-attachments/assets/5fba07bc-8c36-40f1-b423-d206aa495192" />
55
65
 
56
66
  1. Restart DSH web and hard-refresh the browser (`Ctrl+Shift+R`).
57
- 2. Go to **Settings → Plugins → Advisor Group**: configure your advisors (provider route + model; use *获取模型列表* to pull the authoritative model list) and tune `maxRounds`, thresholds, and the UI theme.
67
+ 2. Go to **Settings → Plugins → Advisor Group**: configure your advisors (provider route + model; use *获取模型列表* (Fetch model list) to pull the authoritative model list) and tune `maxRounds`, thresholds, and the UI theme.
58
68
  3. Just start a conversation:
59
69
  - type `@顾问群` in your question to force a consultation, or
60
70
  - ask a professional/uncertain question — the plugin escalates automatically when appropriate.
61
71
 
62
- `ask_advisors` tools available to the model: `ask_advisors`, `toggle_advisor_group`, `ask_session`.
72
+ Tools available to the model: `ask_advisors`, `toggle_advisor_group`, `ask_session`, `list_sessions`, `ask_result`.
73
+
74
+ ## 🐝 The global swarm: sessions that can ask each other
75
+
76
+ ### One session is an expert with memory
77
+
78
+ Every session accumulates context: the code it read, the detours it took, the decisions it settled. A session is not a throwaway chat window but a **node with memory** — and re-deriving what another session already knows is both slower and more expensive.
79
+
80
+ So the plugin does two things: it borrows **wisdom** outward (consulting expert models) and **memory** sideways (asking the session that already knows). Both are opt-in, and neither takes the decision away from you.
81
+
82
+ ### Four principles
83
+
84
+ 1. **Reuse, don't rebuild.** The session that already knows answers better and faster — its context and its cache prefix are warm.
85
+ 2. **Peers, no root.** Sessions ask each other as equals; nothing dispatches from the centre. No central bottleneck, and no single session that must know everything.
86
+ 3. **Candidates, never autopilot.** Routing (automatic candidate selection) proposes candidates with reasons; delivery needs you or the model to name one. Better to ask once more than to wake the wrong session.
87
+ 4. **Task-first, never chatter.** Hop limit, one concurrent wait per session, long runs detached so they never block, silent fallback on every failure. **More nodes is not more intelligence — routing quality is.**
63
88
 
64
- ## 🔗 Ask another session
89
+ ### Three tools
65
90
 
66
- Besides consulting advisors, the plugin ships one more tool, `ask_session`: it hands a question to **another live session** (another window or tab in the same host) and wakes that session to answer.
91
+ | Tool | What it does |
92
+ |---|---|
93
+ | `list_sessions` | The roster: title, working directory, age, running state. Read-only — it never wakes anyone. |
94
+ | `ask_session` | Hands a question to another session and wakes it; waits by default, or with `detach: true` returns an `askId` immediately. |
95
+ | `ask_result` | Reads a detached result (`waiting` / `done` / `timeout` / `error`); the record carries `parentAskId`, so a relay chain can be walked. |
96
+
97
+ ### Finding the one to ask
98
+
99
+ Without a named target, the plugin scores sessions based on **title + working-directory segments** (with CJK text split into two-character groups). A few hard rules then narrow the candidates: a session's exact name mentioned in your message, a title keyword match, or a directory match.
100
+
101
+ Sorting evaluates criteria in order: score → title hit over directory-only hit → whether running → sufficient context (log size reaching the 160,380-byte floor) → activity recency (hot/warm/cold) → larger context within the same project → more recent activity → shorter title.
67
102
 
68
- - **Picking the target**: an exact session id, an exact title, or a natural partial name — the workspace directory name is enough (a session under `I:\极简遥控器\…` is found by saying 「极简遥控器」). One match is used directly; several matches become a candidate list (**running** sessions and recently active ones first) for you or the model to choose from. It never guesses.
69
- - **What you get back**: everything the target said between the **delivery receipt** and its next **whole-session idle** — "what that session said in that window", **not** a one-to-one reply to your message. On timeout (180 s by default, configurable) it returns what it already collected and does **not** interrupt the target.
70
- - **What you see**: the asking side shows a chat-group card — first "已投递,等待对方回应…", then the target's answer bubble and the "📌 投递说明" note (which delivery channel was used and how the wait ended). A failed target lookup opens no card; the error stays in the tool row.
71
- - **Guards**: hop ≤ 2 (no ping-pong relay), no self or subagent target, one concurrent wait per session, a 32 KB payload budget; the target's turn is **never** cancelled.
72
- - **Cost**: one ordinary target turn, billed on the target session's own model; it does **not** count against the daily consultation quota.
103
+ You can optionally add a **Jev** judge (Settings → Plugins → Advisor Group → 全局蜂群 [Global swarm] → 路由方式 [Routing method]). Candidates are passed as a closed option set with a "none of these" fallback; a low-confidence decision keeps the local ranking.
104
+
105
+ Jev can also evaluate whether a turn should consult another session at all. There are three modes: off, local prefilter + Jev (default), and every turn. The default mode runs a zero-cost clue prefilter first: it only calls Jev when a message clearly points to another session and names no target. Even when Jev recommends a query, it only injects a suggestion line into the next system prompt. Whether to deliver the message remains the main model's choice.
106
+
107
+ ### Practicalities and honest limits
108
+
109
+ - **Picking the target**: provide an exact session ID, exact title, or a natural short name. The workspace directory name is usually sufficient (for example, refer to a session under `I:\极简遥控器\…` as 「极简遥控器」). A unique match routes immediately. Multiple matches produce a candidate list for you to choose from. The plugin never guesses.
110
+ - **What you get back**: everything the target said between the **delivery receipt** and its next **whole-session idle** state. This captures what the session produced during that window, rather than a 1-to-1 reply. A `status` flag (`ok` / `partial` / `failed`) indicates response reliability.
111
+ - **What you see**: the sender side opens a chat-group card displaying 「已投递,等待对方回应…」 (Delivered, awaiting response...), followed by the response bubble and a 「📌 投递说明」 (Delivery details) note. If target resolution fails, no card opens and the error is reported directly in the tool row.
112
+ - **Guards**: hops are capped at 2, asking oneself or subagents is forbidden, each session allows only one concurrent wait, and the payload budget is 32 KB. Detached results are cleaned up after their TTL expires. Sessions listed under 「不参与路由的会话」 (Sessions excluded from routing) never appear as candidates and reject direct requests. Target sessions are never cancelled midway.
113
+ - **Cost**: see **Usage and cost**. Detached results are pruned on read, and every Jev decision is logged to `storages/advisor-group/asks/routing-jev.log`.
73
114
 
74
115
  In one line: say "ask the 极简遥控器 session how far along it is".
75
116
 
@@ -78,13 +119,13 @@ In one line: say "ask the 极简遥控器 session how far along it is".
78
119
  | Key | Type | Default | Description |
79
120
  |---|---|---|---|
80
121
  | `enabled` | boolean | `true` | Enable advisor group |
81
- | `discussion.maxRounds` | number | `2` | Total rounds of the auto-deepen pipeline (each round = driver question + all advisors in relay) |
122
+ | `discussion.maxRounds` | number | `2` | Total rounds of the auto-deepen pipeline (1–5; each round = all advisors in relay, with a driver follow-up before every round after the first) |
82
123
  | `discussion.maxAdvisorsPerCall` | number | `3` | Max advisors per call (1–10) |
83
- | `discussion.autoDeepen` | boolean | `true` | Run the auto-deepen pipeline (driver follow-ups + final synthesis) |
124
+ | `discussion.autoDeepen` | boolean | `true` | Whether to generate a deeper follow-up between rounds; it does not change the round count — when off, the same question repeats for `maxRounds` rounds and the synthesis is still generated |
84
125
  | `discussion.driverModel` | object | – | Fallback driver model `{provider, model}` when the session header cannot be read |
85
126
  | `discussion.advisorTimeoutMs` | number | `600000` | Per-advisor call timeout (ms, 1000–600000), applied to both channels |
86
127
  | `discussion.driverTimeoutMs` | number | `600000` | Driver generation timeout (deep-question / conclusion; ms, 1000–1200000) |
87
- | `discussion.advisorTools` | string | `'readonly'` | Global default advisor tool scope when an advisor sets no own `tools`: `readonly` / `all` (every session-visible tool incl. writable — elevated risk, see Security) / `off`. Tool calling needs the direct-http (OpenAI/Anthropic) channel. |
128
+ | `discussion.advisorTools` | string | `'readonly'` | Global default advisor tool scope when an advisor sets no own `tools`: `readonly` / `all` (every session-visible tool incl. writable — elevated risk, see Security) / `off`. Tool calling needs the direct-http (OpenAI/Anthropic) channel (requirements in **Security & privacy**). |
88
129
  | `quota.enabled` | boolean | `true` | Enable the daily new-consultation cap (cost safety valve) |
89
130
  | `quota.maxPerDay` | number | `50` | Max new consultations per UTC day (1–100000); ignored when `quota.enabled` is `false` |
90
131
  | `trigger.requireClassifier` | boolean | `true` | Run the pre-classifier before starting |
@@ -95,40 +136,58 @@ In one line: say "ask the 极简遥控器 session how far along it is".
95
136
  | `trigger.jev.confidenceThreshold` | number | `0.6` | Escalate when Jev answers `needsAdvisor` yes (or its 0–1 score reaches this value) |
96
137
  | `trigger.jev.highRiskThreshold` | number | follows `jev.confidenceThreshold` | Flag high-risk when Jev answers `high_risk` yes (or its 0–1 score reaches this value); unset = follows `trigger.jev.confidenceThreshold` |
97
138
  | `trigger.jev.useEnglishState` | boolean | `false` | Judge using the caller-supplied English gist (`questionEn`) instead of the Chinese question; display and session records stay Chinese |
139
+ | `swarm.enabled` | boolean | `true` | Master switch for cross-session work; off makes `ask_session` / `list_sessions` / `ask_result` refuse instead of failing quietly |
140
+ | `swarm.defaultMode` | string | `'wait'` | Default reply mode: `wait` (blocking) or `detach` (deliver and return an `askId`); an explicit argument always wins |
141
+ | `swarm.defaultWaitMs` | number | `180000` | Default wait ceiling in ms (1000–600000) |
142
+ | `swarm.resultTtlHours` | number | `24` | How long detached results are kept, in hours (1–720); expired records are pruned on read |
143
+ | `swarm.rosterIncludeSubagents` | boolean | `false` | List subagent sessions in the roster |
144
+ | `swarm.routingEnabled` | boolean | `true` | With no named target, match candidates from the question (candidates only — never delivered automatically) |
145
+ | `swarm.routingTopN` | number | `3` | How many candidates to return (1–8) |
146
+ | `swarm.routingEngine` | string | `'local'` | `local` scoring, or `jev` which reranks a closed option set and falls back to local on any failure |
147
+ | `swarm.routingAsk` | string | `'gated'` | Whether a turn should consult another session: `off`, `gated` (local prefilter + Jev, default), `always` |
148
+ | `swarm.routingExclude` | string[] | `[]` | Sessions excluded from routing (a title keyword or an exact sessionId per line); naming one is refused |
98
149
  | `ui.theme` | string | `retro-green` | Chat card theme (`retro-green` / `retro-amber` / `retro-blue`) |
99
150
  | `ui.showTimestamps` | boolean | `true` | Show timestamps |
100
151
  | `ui.autoExpand` | boolean | `true` | Auto-expand card |
101
- | `advisors[].tools` | string | – | Per-advisor tool-scope override (`readonly`/`all`/`off`; unset follows the global default) |
152
+ | `advisors[].tools` | string | – | Per-advisor tool-scope override (`readonly`/`all`/`off`; unset follows the global default); in the settings card, direct-channel advisors get a dropdown, while built-in-channel advisors are told that the built-in route (DSH credentials) does not support tool calling and that `baseURL` / `apiKey` / `apiKeyEnv` enables it |
153
+ | `advisors[].thinking` | string | – | Per-advisor thinking mode: `enabled` / `disabled`; unset = provider default, and `disabled` forces no thinking block (mapped to `reasoningEffort: 'off'` on the built-in channel) |
154
+ | `advisors[].reasoningEffort` | string | – | Per-advisor thinking strength: `off` / `low` / `high` / `max`; unset = provider/model default, and `off` disables thinking per request |
155
+ | `advisors[].temperature` | number | – | Sampling temperature (0–2); unset = provider default |
156
+ | `advisors[].maxTokens` | number | – | Token cap for one answer (≥1); unset = provider default |
157
+ | `advisors[].authMode` | string | – | Anthropic direct-channel auth header style: `x-api-key` / `bearer` |
158
+ | `advisors[].avatar` | string | – | Short avatar shown on the card (emoji or ASCII) |
102
159
  | `advisors` | array | `[]` | Advisor list (provider/model/baseURL/apiKey/apiKeyEnv/protocol…, keys are `role('secret')`) |
103
160
 
104
161
  > `discussion.parallel` and `discussion.stopOnConsensus` are deprecated leftovers kept only for stored-config compatibility.
105
162
 
106
163
  ## 🧮 Daily quota
107
164
 
108
- The plugin ships a cost safety valve: it counts **new consultations** per UTC calendar day, default cap 50, changeable or switchable on the settings page. Only new consultations count — resuming (「▶ continue」) and follow-ups never consume quota. The check and the increment happen inside one synchronous block, so two consultations cannot slip through the same window; the counter is written atomically to `$DSH_HOME/storages/advisor-group/daily-guard.json`, survives restarts, and resets at the UTC day boundary. When the cap is reached `ask_advisors` does not start and returns a one-line explanation; the settings page shows "today's remaining consultations X / Y", and with the cap off the plugin still counts for display without blocking.
165
+ The plugin includes a cost safety valve. It tracks **new consultations** per UTC calendar day (default cap: 50), which can be adjusted or disabled in settings. The quota applies solely to newly initiated consultations. Resuming conversations via 「▶ 继续聊天」 (Resume chat) and follow-up prompts do not consume quota.
166
+
167
+ Evaluation and increment occur within a single synchronous block, preventing concurrency race conditions. The counter persists atomically to `$DSH_HOME/storages/advisor-group/daily-guard.json`, survives restarts, and resets at 00:00 UTC. When the quota is reached, `ask_advisors` aborts and returns an explanatory message. The settings page displays "today's remaining consultations X / Y" in real time. Disabling the cap continues tracking counts without blocking requests.
109
168
 
110
169
  ## 💸 Usage and cost
111
170
 
112
- One consultation is not one call but a chain of them. With the defaults (`maxRounds = 2`, at most 3 advisors) a new consultation costs roughly 9 model calls: 2 rounds × (1 driver follow-up + 1 per advisor) + 1 driver synthesis. Advisors that use tools add one more model call per tool round (at most 4 rounds per advisor plus one forced text-only round), so the real count can be noticeably higher than 9.
171
+ A single consultation consists of a chain of model calls. Under the default configuration (`maxRounds = 2`, up to 3 advisors), launching a new consultation involves **8** model calls: round 1 initiates 3 advisor calls; round 2 includes 1 driver follow-up and 3 advisor calls; finally, the driver generates 1 overall synthesis. If advisors invoke tools, each tool loop adds an extra model call (capped at 4 tool rounds per advisor, plus 1 final text response), so actual usage may exceed 8 calls.
113
172
 
114
- Billing follows each channel: advisors bill on the route and model you gave them, while the driver bills on the current session's model. To keep costs down, lower `maxRounds`, configure fewer advisors, set `tools: 'off'` for advisors, and pick a daily cap you are comfortable with. Resuming after an interruption ("▶ continue") only asks the advisors that had not finished, so it costs less than starting over.
173
+ Billing is calculated independently per channel. Advisors bill against their configured routes and models, while the driver model bills against the current session's model. To minimize expenses, you can lower `maxRounds`, reduce the number of advisors, set `tools: 'off'` for advisors, and configure an appropriate limit under **Daily quota**. If a consultation is interrupted, clicking 「▶ 继续聊天」 (Resume chat) only queries pending advisors without restarting the entire run, costing substantially less than a fresh consultation.
115
174
 
116
- `ask_session` is outside the consultation quota: it wakes the target session, which answers with one ordinary turn billed on that session's own model, and it never counts as a new consultation.
175
+ `ask_session` does not consume consultation quota. It simply wakes the target session to run a standard response turn billed to that session's own model, and does not count as a new advisor consultation.
117
176
 
118
177
  ## 🔒 Security & privacy
119
178
 
120
- - Direct API keys can be stored in the DSH `settings.yaml` (marked secret, never returned to the browser by `describe`); prefer `apiKeyEnv` (env-var mode) if you don't want keys on disk.
121
- - `/advisor-group/*` routes use a per-boot shared token for local single-user use — **not** multi-user auth. Add a reverse-proxy auth layer before LAN exposure.
122
- - Diagnostics endpoints resolve the real key server-side and validate the target URL against an https-only / loopback SSRF guard; DNS-rebinding protection is a documented out-of-scope limitation.
123
- - The daily consultation cap (configurable via `quota.*`; default 50; can be disabled) is persisted to `$DSH_HOME/storages/advisor-group/daily-guard.json` (UTC day key), so restarts don't reset it.
124
- - Classifier shadow mode appends one observation sample per non-forced classification (read-only `/advisor-group/shadow`), used for threshold tuning only — never influences behavior.
125
- - **`advisorTools: 'all'` is an elevated-risk scope.** It exposes every session-visible tool to the advisor models, including writable/execution ones (`pwsh`, `bash`, `write`, config/SSH tools), executed through the official guarded pipeline. Only enable it for advisors you trust (e.g., your own local models), keep them on the direct-http channel, and note that every non-read-only invocation is logged with `console.warn` for audit. Prefer the default `readonly` (read/grep/glob/web_search/web_fetch/scan_discover/list_imported_sessions) or `off`.
179
+ - Direct API keys can be stored in DSH `settings.yaml` (marked as secret, never returned to the browser by `describe`). If you prefer not to store keys on disk, use the `apiKeyEnv` environment variable mode.
180
+ - The `/advisor-group/*` routes utilize a per-boot shared token designed for single-user local setups. Add a reverse-proxy authentication layer before exposing ports to a local network or the public internet.
181
+ - Diagnostic endpoints resolve keys server-side before contacting providers, applying SSRF validation (HTTPS-only and loopback allowed). DNS rebinding is a known architectural boundary.
182
+ - For counting rules, storage paths, and reset schedules regarding the daily quota, see **Daily quota**.
183
+ - Classifier shadow mode: when `trigger.requireClassifier` is enabled, all classification samples are appended to the read-only endpoint `/advisor-group/shadow` (including forced triggers like `@顾问群` or repeated-question scenarios, tagged as `provider: 'bypass'`). This data is used exclusively for threshold tuning and never affects runtime logic.
184
+ - **`advisorTools: 'all'` is an elevated-risk scope.** It exposes all session-visible tools (including write and command execution tools such as `pwsh`, `bash`, and `write`) to advisor models under official guard pipelines. Enable this only for trusted local models. Tool calling requires the direct-http (OpenAI/Anthropic) channel; the DSH built-in channel reuses host credentials and cannot invoke tools. Configure `baseURL`, `apiKey`, or `apiKeyEnv` for any advisor needing tools. Every non-read-only tool execution triggers a `console.warn` audit log. The default recommendation is `readonly` or `off` (see read-only whitelist in **Features**).
126
185
 
127
186
  ## ⚠️ Known limitations
128
187
 
129
- - A hard crash of the DSH host can leave an already-open card showing **LIVE** until the page is refreshed (a disconnect notice now appears); refresh restores the true state from the session log.
130
- - Risk notes are **fact-based, never text-based**: a consultation reports truncation (an advisor stream cut off by timeout or network) and cancellation/stop. Users see them on **stopped or completed** cards and in the session log, and the main model receives them in the `ask_advisors` result. Advisor prose is not scanned for keywords — the earlier text heuristic was removed in 0.1.1 because it also fired on plain negations such as 「没有任何风险」.
131
- - Cross-session asking (`ask_session`) has no settings switch yet, so the tool is visible in every session and relies on its own description to keep use to explicit user requests; two sessions asking each other at the same time is not detected.
188
+ - If the DSH host crashes abruptly, an active card may continue displaying **LIVE** until refreshed (a disconnection notice is now displayed). Refreshing restores the true state from session logs.
189
+ - Risk notices are strictly fact-based rather than text-based. They track truncations (advisor streams interrupted by timeout or network drop) and cancellations (consultation stopped by user). Notices appear in stopped or completed cards and return to the main model via `ask_advisors`. Advisor responses are not scanned for keywords; text-heuristic matching was removed in v0.1.1 to avoid false positives on negative statements like 「没有任何风险」.
190
+ - Cross-session queries (`ask_session`) currently lack a per-session tool visibility toggle. When `swarm.enabled` is active, the three session tools remain visible across all sessions, relying on system prompts to limit usage to explicit user instructions. Mutual wait deadlocks between two sessions are not currently detected.
132
191
 
133
192
  ## 🛠️ Development
134
193
 
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # dsh-advisor-group
2
2
 
3
- > [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)插件:主模型在遇到**专业、长尾世界知识、高风险或不确定**的问题时,召集多位**专家顾问模型**,在复古 CRT 聊天组卡片中真流式对话。
3
+ > [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)插件:让一个会话**既能向其他模型借智慧,也能向其他会话借记忆**。
4
+ >
5
+ > 遇到专业、长尾世界知识、高风险或不确定的问题,主模型召集多位**专家顾问模型**,在复古 CRT 聊天组卡片中真流式会诊;遇到别的会话已经知道的事,跨会话去问,复用已有上下文,无需从头再查。
6
+ >
4
7
 
5
8
  [![dsh-plugin](https://img.shields.io/badge/DSH%20plugin-dsh--plugin-3f8cff)](https://github.com/topics/dsh-plugin)
6
9
 
@@ -12,26 +15,28 @@
12
15
 
13
16
  ## ✨ 功能特性
14
17
 
15
- - **自动深挖咨询流水线**:一次 `ask_advisors` 自动跑满 `maxRounds` 轮——每轮 = *驱动模型深挖追问 → 顾问 A → 顾问 B(看到 A)→ 顾问 C(看到 A+B)→ …* 顺序接力,最后驱动模型产出**综合结论**。
16
- - **顾问可调用会话工具(2026-09-05)**:顾问模型回答前可调用**当前 DSH 会话可见的工具**(默认只读白名单:read/grep/glob/web_search/web_fetch…)——走官方 `ctx.tools` 完整管线(作用域分发、守卫、前置/后置策略),循环上限 4 轮后强制文字作答;每次调用在卡片 💭 面板实时播报(`🔧 模型请求调用工具…`+结果摘要)。可在设置中切到「全部会话工具」或「关闭」。
17
- - **停止后可继续**:用户停止或 dsh 重启后,卡片「⏹ 停止」原位变为「▶ 继续聊天」→ `POST /advisor-group/resume` → 从断点续跑(本轮未答的顾问补答、已答的不重问、剩余轮次照常、最终照常生成综合结论);会话快照持久化到 `storages/advisor-group/sessions/<id>.json`(仅存顾问 id/消息/状态,不含任何凭据),跨重启自动恢复。
18
- - **驱动模型零配置复用**:深挖追问由驱动模型生成,直接复用会话当前 agent 的 provider/model——无需额外配 Key 或模型;会话头不可读时回退 `discussion.driverModel`。
19
- - **三种触发方式**:`@顾问群` 提及(强制启动)、同一问题重复 3 次未解决、主模型自评置信度低于阈值。
20
- - **Jev 语义前置分类(可选,默认关闭)**:开启 `trigger.jev.enabled` 后,未强制启动的咨询先由配置的 Jev 模型语义判断(是否该找顾问 / 是否高风险 / 是否更适合联网,阈值在 `trigger.jev.*`);Jev 不可用时仍由本地规则分类器判定。`trigger.jev.useEnglishState` 开启后,判定改读主模型附带的英文概要(`questionEn`),展示与会话记录仍用中文。
21
- - **复古 CRT 聊天组卡片**:绿/琥珀/蓝三主题、扫描线、LIVE/DONE 标题、💭 思考面板默认展开自动滚底;顾问正文走轻量 Markdown 渲染(链接协议白名单,支持标题/列表/代码/引用/链接/表格)。
22
- - **跨会话提问(`ask_session`)**:把一个问题交给另一个**活着的**会话——用完整会话号、一字不差的标题,或者自然的名字(例如工作目录名「极简遥控器」)都可以——并唤醒它;拿回的是**从投递回执到对方下一次整体空闲**之间它说的话,明确**不是**逐条对应的回答。提问方一侧用同一套复古聊天组卡片呈现这次对话。护栏:跳数 ≤ 2、不能问自己或子会话、同一会话同时只等一个、载荷上限 32 KB;**永不取消对方的回合**。
23
- - **供应商预设(11 平台 · 26 预设)**:DeepSeek、月之暗面 Kimi、Kimi Code、阿里云百炼、智谱 AI、OpenAI、Claude、Gemini、硅基流动、AIHubMix、OpenRouter(含 OpenAI / Anthropic 兼容变体)。
24
- - **注重安全**:API Key 采用官方 `SecretField` 语义(浏览器永不回显;`apiKeysByProvider` 供应商密钥档案仅存服务端);诊断端点 SSRF 加固(仅 https/loopback、拒绝 IP 字面量与重定向);`/advisor-group/*` 路由启动期 token 鉴权;每日新咨询**可配置原子配额**(默认 50,可关闭),持久化跨重启。
25
- - **运行时开关**:`toggle_advisor_group` 启停插件并持久化到设置。
18
+ - **自动深挖咨询流水线**:一次 `ask_advisors` 跑满 `maxRounds` 轮(1–5,默认 2)。首轮直接用原问题,后续各轮先由驱动模型生成更深入的追问(`autoDeepen` 关闭时改用原问题)。顾问按 A → B(见 A)→ C(见 A+B)接力应答,最后由驱动模型生成综合结论(总会生成,不受 `autoDeepen` 影响)。
19
+ - **顾问可调用会话工具(2026-09-05)**:顾问回答前可调用当前会话工具,默认仅放行只读白名单(read/grep/glob/web_search/web_fetch 等)。调用走官方 `ctx.tools` 完整管线,最多 4 轮后强制用文字作答。每次调用在卡片展示 `⛭ 工具名 · 调用中 / 调用完成 / ⚠ 失败`,展开可看输入输出。可在设置中切换为「全部会话工具」或「关闭」。
20
+ - **停止后可继续**:人工停止或 DSH 重启后,卡片「⏹ 停止」按钮变成「▶ 继续聊天」。点击调用 `POST /advisor-group/resume` 从断点续跑:未答顾问补答,已答顾问不重问,剩余轮次与最终综合结论照常生成。会话快照持久化保存在 `storages/advisor-group/sessions/<id>.json`(仅存顾问 id、消息和状态,不含凭据),重启后自动恢复。
21
+ - **驱动模型零配置复用**:驱动模型负责生成深挖追问,直接复用当前会话的 provider 和 model。你不需要额外配置密钥或模型;会话头信息不可读时,自动回退到 `discussion.driverModel`。
22
+ - **三种触发方式**:在提问中带 `@顾问群`(强制启动);同一问题重复提问 3 次未解决;主模型自评置信度低于预设阈值。
23
+ - **Jev 语义前置分类(可选,默认关闭)**:开启 `trigger.jev.enabled` 后,未强制启动的咨询先由配置的 Jev 模型做语义判断(是否该找顾问、是否高风险、是否适合联网,阈值见 `trigger.jev.*`)。Jev 不可用时,由本地规则分类器接管。开启 `trigger.jev.useEnglishState` 后,Jev 改读主模型给出的英文概要(`questionEn`),卡片展示与会话记录仍保持中文。
24
+ - **复古 CRT 聊天组卡片**:提供绿、琥珀、蓝三种主题,带扫描线和 LIVE/DONE 状态标。💭 思考面板默认展开并随流式内容滚动。正文采用轻量 Markdown 渲染,设有限制协议白名单,支持标题、列表、代码、引用、链接与表格。
25
+ - **全局蜂群(跨会话提问)**:`ask_session` 把问题交给另一个活着的会话并唤醒它,`list_sessions` 查看名录,`ask_result` 获取异步结果。拿回的不是逐条问答,详情见「🐝 全局蜂群」。
26
+ - **供应商预设(12 平台 · 28 预设)**:DeepSeek、月之暗面 Kimi、Kimi Code、阿里云百炼、智谱 AI、OpenAI、Claude、Gemini、硅基流动、AIHubMix、OpenRouter、**OpenCode Go**(含 OpenAI 与 Anthropic 兼容变体)。
27
+ - **注重安全**:API Key 遵循官方 `SecretField` 语义,浏览器永不回显,供应商密钥档案 `apiKeysByProvider` 仅保存在服务端。诊断端点经过 SSRF 加固(仅限 https 与 loopback,拒绝 IP 字面量和重定向)。`/advisor-group/*` 路由在启动期生成 token 进行鉴权。支持每日新咨询上限(见「🧮 每日配额」)。
28
+ - **运行时开关**:调用 `toggle_advisor_group` 启停插件,状态自动持久化到设置。
26
29
 
27
30
  ## ✅ 兼容性
28
31
 
29
32
  | 项目 | 状态 |
30
33
  |---|---|
31
- | Harness | DeepSeek Harness `0.1.7-rc.2` → `0.2.x`(含 `0.2.0-rc.1`) |
34
+ | Harness | **`0.2.1-rc.2`(实测运行版本,本版门禁即对着它跑)**;声明的 peer 区间为 `>=0.1.7-rc.2 <0.3.0` |
32
35
  | Node | `^22.19.0 \|\| >=24.0.0` |
33
36
  | 平台 | DSH Web 与桌面端(共用同一份客户端 bundle)+ headless 宿主逻辑 |
34
37
 
38
+ > 关于下限:`0.1.7-rc.2` 仍在声明的 peer 区间内,但本轮没有重新实装验证。类型检查、测试与构建均对着 `0.2.1-rc.2` 完成。如果在更早的 0.1.x 宿主上遇到加载失败,多半是因接口变动,而非当前版本代码缺陷。
39
+
35
40
  ## 📦 安装
36
41
 
37
42
  ```sh
@@ -42,6 +47,8 @@ dsh plugin --profile web add dsh-advisor-group
42
47
  npx @deepseek-ai/dsh web
43
48
  ```
44
49
 
50
+ > **第 2 步提醒**:`npx @deepseek-ai/dsh web` 拉取的是当时的最新版 DSH,可能跨出本插件的 peer 区间(`>=0.1.7-rc.2 <0.3.0`),宿主会因此拒绝加载插件。按你平时的方式重启即可(已有的启动脚本、桌面端都可以),不必非用这条命令。
51
+
45
52
  > **从源码开发安装**
46
53
  > ```sh
47
54
  > npm install --legacy-peer-deps --no-audit --no-fund
@@ -62,17 +69,52 @@ npx @deepseek-ai/dsh web
62
69
  - 提问时带 `@顾问群` 强制发起咨询,或
63
70
  - 直接提专业/不确定的问题——插件会按需自动升级。
64
71
 
65
- 模型可用工具:`ask_advisors`、`toggle_advisor_group`、`ask_session`。
72
+ 模型可用工具:`ask_advisors`、`toggle_advisor_group`、`ask_session`、`list_sessions`、`ask_result`。
73
+
74
+ ## 🐝 全局蜂群:让会话互相提问
75
+
76
+ ### 一个会话,就是一个有记忆的专家
77
+
78
+ DSH 里每个会话都在积累上下文:读过的代码、走过的弯路、定下的约定、否掉的方案。它们不是一次性的聊天窗口,而是**带着记忆的节点**——同一个问题在别处早已查清,从零再问一遍既慢又贵。
66
79
 
67
- ## 🔗 跨会话提问
80
+ 所以这个插件做两件事:**向外借智慧**——召集专家顾问模型;**向旁边借记忆**——问那个已经知道的会话。
68
81
 
69
- 除了问顾问,插件还带一个工具 `ask_session`:把一个问题交给**另一个活着的会话**(同一宿主里的另一个窗口或标签),并唤醒它作答。
82
+ ### 四条原则
83
+
84
+ 1. **复用,而不是重造。** 让知道答案的会话来回答,比让新会话从头查更准、也更快——它的上下文与缓存前缀都是热的。
85
+ 2. **对等,没有根节点。** 会话之间平级互相提问,不存在"总控 Agent"派发一切。没有中心,就没有单点瓶颈,也不需要一个知道全部事情的会话。
86
+ 3. **只提候选,绝不代发。** 路由(自动挑选候选会话)只给候选与理由,投递必须由你或主模型点名。宁可多问一句,也不猜错打扰。
87
+ 4. **任务导向,不为通信而通信。** 跳数(一次提问最多经几个会话中转)上限、同一时刻每会话只等一个、长任务走异步不阻塞、失败一律静默降级。**节点多不等于聪明,路由质量才是。**
88
+
89
+ ### 三个工具
90
+
91
+ | 工具 | 作用 |
92
+ |---|---|
93
+ | `list_sessions` | 会话名录:标题、工作目录、活跃度、是否运行中。只读,不唤醒任何人 |
94
+ | `ask_session` | 把问题交给另一个会话并唤醒它;可同步等,也可 `detach` 异步投递,投递即返回 `askId` |
95
+ | `ask_result` | 取异步结果(等待中 / 已完成 / 超时 / 出错);记录里带 `parentAskId`,可顺着链条往上追 |
70
96
 
71
- - **怎么指定目标**:完整会话号、一字不差的标题,或者自然的名字都行——工作目录名就够(例如 `I:\极简遥控器\…` 下的会话,说「极简遥控器」即可)。唯一命中就直接问;好几个命中会列出候选(**正在运行**的、最近活跃的排前面),由你或模型挑一个;拿不准的时候它绝不猜。
72
- - **你会拿到什么**:从**投递回执**到对方会话**下一次整体空闲**之间,它产出的全部文字——这是「那段时间里对方说的话」,**不是**逐条对应的回答。超时(默认 180 秒,可调)只返回已经收到的内容,并且**不会打断对方**。
73
- - **界面上看到什么**:提问方一侧弹出聊天组卡片——先是「已投递,等待对方回应…」,然后出现对方的答案气泡和「📌 投递说明」(走的哪条通道、等到什么时候结束)。指定目标失败这类情况不弹卡片,只在工具行里如实报错。
74
- - **护栏**:跳数上限 2(防止两个会话来回接力)、不能问自己或子会话、同一会话同时只能等一个、单次载荷上限 32 KB;**任何情况下都不会取消对方的回合**。
75
- - **成本**:占用对方会话一次普通回合,按其自身模型计费;**不计入**每日咨询配额。
97
+ ### 怎么找到"该问的那一个"
98
+
99
+ 没点名时,插件按**标题 + 工作目录**给会话打分。中文按两字组切分。随后用硬规则收敛:消息里出现某会话名称、标题命中关键词,或目录命中项目名。排序时依次比较:综合分数 → 标题命中优先于目录命中 → 是否运行中 → 上下文是否足够(日志体积达到 160,380 字节门槛)→ 活跃时段(热/温/冷)→ 同项目上下文更大 → 最近活跃时间 → 标题长度(越短越前)。
100
+
101
+ 可选再叠加一层 Jev 判断(设置 → 插件 → 顾问群 → 全局蜂群 → 路由方式)。把候选作为闭合选项交给 Jev,若置信度不足则保持本地排序。
102
+
103
+ 它还能判断当前轮次是否需要向其他会话提问,分为三档:关、本地预过滤 + Jev(默认)、每轮都问。默认档先做零成本规则初筛:只有当用户消息暗示需要外援且未指定目标时,才会发起 Jev 调用。即便判定为真,也只在下轮系统提示中注入一行建议,最终仍由主模型自主决定是否发问。
104
+
105
+ ### 边界与诚实
106
+
107
+ - **区间语义**:获取的内容是「从投递回执到目标会话下一次空闲」之间的全部发言,并非逐条问答。结果附带 `status`(`ok` / `partial` / `failed`)标明可靠性。
108
+ - **成本护栏**:限制中转跳数 ≤ 2,禁止向自身或子会话发问,同一会话同一时刻只等一个请求,载荷上限 32 KB。异步结果超时自动清理。可在「不参与路由的会话」中排除特定会话。插件永不强行中断对方的回合。
109
+ - **失败即沉默**:遇到 Jev 超时、响应异常或候选超出名录时,静默退回本地规则判定,不打扰用户、不误投递、不乱报错。
110
+ - **可追溯**:每次 Jev 判定都会在 `storages/advisor-group/asks/routing-jev.log` 写入一行日志,方便事后审计。
111
+
112
+ ### 实操细节
113
+
114
+ - **怎么指定目标**:支持完整会话 ID、精准标题或自然简称。通常输入工作目录名即可(例如目录为 `I:\极简遥控器\…` 的会话,直接说「极简遥控器」)。唯一匹配直接投递;命中多个时列出候选供你挑选;匹配不确定时绝不盲猜。
115
+ - **界面上看到什么**:提问侧弹出聊天组卡片。先显示「已投递,等待对方回应…」,随后呈现对方回答气泡与「📌 投递说明」(标明通道与截止时间)。若目标定位失败则不弹卡片,仅在工具调用行输出报错。
116
+ - **不想等怎么办**:默认同步等待(180 秒,可配置)。超时仅返回已收到的部分内容,不打断对方执行。需要并行处理时传入 `detach`,投递后立即返回 `askId`,后续通过 `ask_result` 查询。
117
+ - **成本**:详见「💸 用量与成本」。
76
118
 
77
119
  一句话用法:直接说「问一下极简遥控器那个会话:进度到哪了」。
78
120
 
@@ -81,14 +123,13 @@ npx @deepseek-ai/dsh web
81
123
  | 键 | 类型 | 默认 | 说明 |
82
124
  |---|---|---|---|
83
125
  | `enabled` | boolean | `true` | 启用顾问群 |
84
- | `discussion.maxRounds` | number | `2` | 自动深挖流水线总轮数(每轮 = 驱动追问 + 全体顾问接力) |
126
+ | `discussion.maxRounds` | number | `2` | 自动深挖流水线总轮数(1–5;每轮 = 全体顾问接力,第 2 轮起先由驱动模型追问) |
85
127
  | `discussion.maxAdvisorsPerCall` | number | `3` | 单次咨询最大顾问数(1–10) |
86
- | `discussion.autoDeepen` | boolean | `true` | 启用自动深挖流水线(驱动追问 + 综合结论) |
128
+ | `discussion.autoDeepen` | boolean | `true` | 是否在轮与轮之间生成更深追问;不影响轮数——关掉后仍按 `maxRounds` 重复提问,综合结论照常生成 |
87
129
  | `discussion.driverModel` | object | – | 兜底驱动模型 `{provider, model}`(会话头信息不可读时) |
88
130
  | `discussion.advisorTimeoutMs` | number | `600000` | 单顾问调用超时(毫秒,1000–600000),双通道均生效 |
89
131
  | `discussion.driverTimeoutMs` | number | `600000` | 驱动模型生成超时(深挖追问/综合结论;毫秒,1000–1200000) |
90
- | `discussion.advisorTools` | string | `'readonly'` | **未单独设置时的全局默认**顾问工具调用范围:`readonly`(只读白名单 read/grep/glob/web_search/web_fetch…)|`all`(全部会话可见工具,含可写,慎用)|`off`(关闭)。仅直连通道(OpenAI/Anthropic)支持工具调用,DSH 内置通道的顾问需配 `baseURL`/`apiKey`/`apiKeyEnv` 直连(详情见「功能特性」)。 |
91
- | `advisors[].tools` | string | – | 每个顾问的**单独**工具调用范围覆盖(`readonly`/`all`/`off`;未设置时跟随全局默认);设置卡片上:可直连的顾问显示下拉,DSH 内置通道顾问显示“不可配置”。 |
132
+ | `discussion.advisorTools` | string | `'readonly'` | 全局默认顾问工具调用范围(未单独设置时):`readonly`(只读白名单 read/grep/glob/web_search/web_fetch…)|`all`(全部会话可见工具,含可写,慎用)|`off`(关闭)。仅直连通道(OpenAI/Anthropic)支持工具调用(要求见「🔒 安全与隐私」)。 |
92
133
  | `quota.enabled` | boolean | `true` | 启用每日咨询上限(成本安全阀) |
93
134
  | `quota.maxPerDay` | number | `50` | 每日 UTC 日周期内最多新咨询次数(1–100000);`quota.enabled=false` 时不生效 |
94
135
  | `trigger.requireClassifier` | boolean | `true` | 发起前先跑前置分类器 |
@@ -99,39 +140,58 @@ npx @deepseek-ai/dsh web
99
140
  | `trigger.jev.confidenceThreshold` | number | `0.6` | Jev 判「该找顾问」为是(或 0–1 分数达到该值)时升级 |
100
141
  | `trigger.jev.highRiskThreshold` | number | 跟随 `jev.confidenceThreshold` | Jev 判「高风险」为是(或 0–1 分数达到该值)时标记高风险;未设置则跟随 `trigger.jev.confidenceThreshold` |
101
142
  | `trigger.jev.useEnglishState` | boolean | `false` | 判定改用主模型附带的英文概要(`questionEn`)而非中文原问;展示与会话记录仍为中文 |
143
+ | `swarm.enabled` | boolean | `true` | 跨会话功能总开关;关闭后 `ask_session` / `list_sessions` / `ask_result` 明确拒绝,不静默失效 |
144
+ | `swarm.defaultMode` | string | `'wait'` | 默认应答方式:`wait` 同步等待|`detach` 异步投递(显式传参永远优先) |
145
+ | `swarm.defaultWaitMs` | number | `180000` | 默认等待上限(毫秒,1000–600000) |
146
+ | `swarm.resultTtlHours` | number | `24` | 异步结果保留时长(小时,1–720;取结果时顺带清理过期记录) |
147
+ | `swarm.rosterIncludeSubagents` | boolean | `false` | 会话名录是否列出子代理会话 |
148
+ | `swarm.routingEnabled` | boolean | `true` | 没点名目标时,按问题匹配候选(只提候选,绝不自动投递) |
149
+ | `swarm.routingTopN` | number | `3` | 返回的候选个数(1–8) |
150
+ | `swarm.routingEngine` | string | `'local'` | 路由方式:`local` 本地打分|`jev` 交 Jev 排序(闭合选项,失败自动回落本地) |
151
+ | `swarm.routingAsk` | string | `'gated'` | 是否该问别的会话:`off`|`gated` 本地预过滤 + Jev(默认)|`always` 每轮都问 |
152
+ | `swarm.routingExclude` | string[] | `[]` | 不参与路由的会话(标题关键词或完整 sessionId,一行一个);被点名时拒绝投递 |
102
153
  | `ui.theme` | string | `retro-green` | 卡片主题(retro-green / retro-amber / retro-blue) |
103
154
  | `ui.showTimestamps` | boolean | `true` | 显示时间戳 |
104
155
  | `ui.autoExpand` | boolean | `true` | 自动展开卡片 |
156
+ | `advisors[].tools` | string | – | 每个顾问的**单独**工具调用范围覆盖(`readonly`/`all`/`off`;未设置时跟随全局默认);设置卡片上:可直连的顾问显示下拉,DSH 内置通道的顾问会提示「当前为 DSH 内置通道(复用 DSH 凭据),不支持工具调用」——为其配置 `baseURL`/`apiKey`/`apiKeyEnv` 走直连通道后即可启用。 |
157
+ | `advisors[].thinking` | string | – | 单顾问思考模式:`enabled` / `disabled`;未设置为供应商默认,`disabled` 强制不带思考块(内置通道映射为 `reasoningEffort: 'off'`) |
158
+ | `advisors[].reasoningEffort` | string | – | 单顾问思考强度:`off` / `low` / `high` / `max`;未设置为供应商/模型默认,`off` 在每次请求中关闭思考 |
159
+ | `advisors[].temperature` | number | – | 采样温度(0–2);未设置为供应商默认 |
160
+ | `advisors[].maxTokens` | number | – | 单次回答的 token 上限(≥1);未设置为供应商默认 |
161
+ | `advisors[].authMode` | string | – | Anthropic 直连通道的鉴权头风格:`x-api-key` / `bearer` |
162
+ | `advisors[].avatar` | string | – | 卡片上的短头像(emoji 或 ASCII) |
105
163
  | `advisors` | array | `[]` | 顾问列表(provider/model/baseURL/apiKey/apiKeyEnv/protocol…,key 均标 `role('secret')`) |
106
164
 
107
165
  > `discussion.parallel` 与 `discussion.stopOnConsensus` 为弃用遗留项,仅保留以兼容已存配置。
108
166
 
109
167
  ## 🧮 每日配额
110
168
 
111
- 插件自带一个成本安全阀:按 UTC 日历日统计**新咨询**次数,默认上限 50,可在设置页改数字或者直接关掉。它只拦新咨询——「▶ 继续聊天」续跑与追问都不计数。判断与自增在同一个同步块内完成,所以两个会话不可能同时挤过窗口;计数原子落盘在 `$DSH_HOME/storages/advisor-group/daily-guard.json`,重启不归零,UTC 日切自动清零。额度用尽时 `ask_advisors` 不启动,只返回一行说明;设置页实时显示「今日剩余咨询次数 X / Y」,关掉上限后仍然计数,只是不拦截。
169
+ 插件内置成本安全阀。它按 UTC 日历日统计新咨询次数,默认上限为 50 次,支持在设置页调整或直接关闭。配额仅限制新发起的咨询,通过「▶ 继续聊天」续跑及后续追问均不计入。
170
+
171
+ 判断与自增在同一同步块内完成,避免并发穿透。计数结果原子落盘于 `$DSH_HOME/storages/advisor-group/daily-guard.json`,重启不丢失,并在 UTC 零点自动重置。额度耗尽时,`ask_advisors` 会拒绝启动并返回说明。设置页实时显示「今日剩余咨询次数 X / Y」;关闭上限开关后仅统计数据,不执行拦截。
112
172
 
113
173
  ## 💸 用量与成本
114
174
 
115
- 一次咨询不是「一次调用」,而是一串调用。默认配置(`maxRounds = 2`、最多 3 位顾问)下,一次新咨询约 9 次模型调用:2 轮 ×(1 次驱动模型追问 + 3 位顾问各 1 次)+ 1 次驱动模型综合结论。顾问若使用工具,每次工具往返还会再加一次模型调用(单个顾问上限 4 轮,另加一次强制文字作答),实际次数可能明显高于 9。
175
+ 一次咨询由一连串模型调用组成。在默认配置(`maxRounds = 2`、最多 3 位顾问)下,发起一次新咨询共包含 **8 次**模型调用:第 1 轮 3 位顾问各调用 1 次;第 2 轮驱动模型追问 1 次,随后 3 位顾问各调用 1 次;最后由驱动模型生成综合结论 1 次。若顾问启用了工具调用,每轮工具往返会额外增加调用(单顾问上限 4 轮工具交互,外加 1 次最终文字作答),实际消耗会高于 8 次。
116
176
 
117
- 费用分别结算:顾问按你给它们配的通道与模型计费,驱动模型按当前会话的模型计费。想控成本,可以降 `maxRounds`、减少顾问数、给顾问设 `tools: 'off'`,再把每日上限设成你能接受的数字。中断后用「▶ 继续聊天」续跑只补问没答完的顾问,不会整场重来,代价低于重开一次。
177
+ 费用按渠道独立结算:顾问按各自配置的渠道与模型计费,驱动模型按当前会话的模型计费。若需控制成本,可调低 `maxRounds`、减少顾问人数、将顾问工具设为 `tools: 'off'`,并在「🧮 每日配额」中设定合理的每日上限。咨询中断后,点击「▶ 继续聊天」仅为未完成的顾问补答,无需重跑整场流程,成本显著低于新建咨询。
118
178
 
119
- `ask_session` 不走咨询配额:它唤醒目标会话,对方用一次普通回合作答、按其自身模型计费,也不会被算作一次新咨询。
179
+ `ask_session` 不占用咨询配额。它仅唤醒目标会话并触发一次常规应答,费用记入目标会话自身模型,不视作新发起的顾问咨询。
120
180
 
121
181
  ## 🔒 安全与隐私
122
182
 
123
- - 直连 API Key 可存于 DSH `settings.yaml`(标 secret,官方 `describe` 绝不回传浏览器);不想落盘请用 `apiKeyEnv` 环境变量模式。
124
- - `/advisor-group/*` 路由为**单机共享 token**(本地单用户使用),不是多用户鉴权;暴露局域网必须外层加反代鉴权。
125
- - 诊断端点服务端按存档 key 解析真实值再打供应商,并对目标 URL 做 https-only/loopback SSRF 校验;DNS rebinding 记为文档化已知边界。
126
- - 每日咨询上限(`quota.*` 可配置,默认 50、可关闭)持久化在 `$DSH_HOME/storages/advisor-group/daily-guard.json`(UTC 日切),重启不再归零。
127
- - 分类器影子模式:每次非强制分类追加一条观测样本(只读 `/advisor-group/shadow`),仅用于阈值调优,绝不干预行为。
128
- - **`advisorTools: 'all'` 属于高风险作用域**:会把会话可见的**全部工具**(含 `pwsh`/`bash`/`write`、配置/SSH 等可写/执行类)暴露给顾问模型(经官方守卫管线执行)。仅建议为可信的本地模型启用,并保证走 direct-http 通道;每次**非只读**工具调用都会以 `console.warn` 留审计痕迹。默认请用 `readonly`(read/grep/glob/web_search/web_fetch/scan_discover/list_imported_sessions)或 `off`。
183
+ - 直连 API Key 存于 DSH `settings.yaml`(标注为 secret 字段,官方 `describe` 不向前端回传)。若不希望密钥落盘,可使用 `apiKeyEnv` 环境变量模式。
184
+ - `/advisor-group/*` 路由采用单机共享 token 机制,面向本地单用户场景。若需暴露到局域网或公网,必须在外层前置反向代理鉴权。
185
+ - 诊断端点在服务端解出真实密钥后请求服务商,并对目标 URL 执行 https-only 与 loopback 的 SSRF 校验。DNS rebinding 属于已知的架构边界。
186
+ - 每日咨询上限的计数机制、持久化文件与重置规则详见「🧮 每日配额」。
187
+ - 分类器影子模式:开启 `trigger.requireClassifier` 时,所有判定样本均会记录到只读端点 `/advisor-group/shadow`(包含 `@顾问群` 与重复 3 次等跳过分类器的场景,标记为 `provider: 'bypass'`),仅用于阈值调优,不影响运行逻辑。
188
+ - **`advisorTools: 'all'` 属于高风险作用域**:该模式会将当前会话所有可见工具(包括 `pwsh`/`bash`/`write` 等写操作与命令执行类)暴露给顾问模型,并由官方守卫管线执行。建议仅对受信任的本地模型开启。工具调用仅支持直连通道(OpenAI/Anthropic);DSH 内置通道复用宿主凭据,不支持工具调用,如需使用请为顾问配置 `baseURL`/`apiKey`/`apiKeyEnv` 直连。所有非只读工具调用均输出 `console.warn` 留存审计日志。日常建议配置为 `readonly` 或 `off`(只读白名单见「✨ 功能特性」)。
129
189
 
130
190
  ## ⚠️ 已知限制
131
191
 
132
- - DSH 宿主进程硬崩溃时,已打开的卡片在刷新前可能仍显示 **LIVE**(现在会先出现断线提示);刷新后按会话日志恢复真实状态。
133
- - 风险提示**只看事实、不看措辞**:截断(顾问输出被超时或网络中断)与取消(本轮咨询被停止)两种。用户可在**已停止或已完成**的卡片与会话日志中看到,主模型随 `ask_advisors` 返回收到。顾问正文不做关键词扫描——早期那套「凭字眼猜有没有保留意见」的启发式已在 0.1.1 移除,原因是它连「没有任何风险」这样的否定句也会当成风险提示(盲测里 4 条纯否定句全部误报)。
134
- - 跨会话提问(`ask_session`)目前**没有设置开关**,工具在所有会话里可见,靠工具说明把使用限制在「用户明确要求时」;两个会话互相等待的情况尚未检测。
192
+ - DSH 宿主进程崩溃时,未刷新的卡片可能仍保持 **LIVE** 标识(目前前端已加入断线提示)。页面刷新后会按会话持久化日志恢复真实状态。
193
+ - 风险提示基于客观运行事实生成,包括超时/网络中断引起的截断,以及手动停止导致的取消。提示会展示在已停止或已完成的卡片中,并随 `ask_advisors` 返回主模型。插件不扫描顾问回复的关键词;早期版本那套"凭字眼猜立场"的启发式已在 0.1.1 移除,原因是它连「没有任何风险」这样的否定句也会当成风险提示——盲测里 4 条纯否定句全部误报。
194
+ - 跨会话提问(`ask_session`)目前未提供针对单会话隐藏工具的独立开关。`swarm.enabled` 作为全局总开关,开启后三个工具在所有会话中均可见,依靠提示词约束仅在明确要求时调用。暂不支持检测两个会话循环互相等待的死锁情况。
135
195
 
136
196
  ## 🛠️ 开发
137
197