dsh-advisor-group 0.1.4 → 0.1.5

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,203 +1,276 @@
1
1
  # dsh-advisor-group
2
2
 
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.
3
+ > Let one [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) session **borrow wisdom from other models** and **reuse the memory of other sessions**.
8
4
 
9
- [![dsh-plugin](https://img.shields.io/badge/DSH%20plugin-dsh--plugin-3f8cff)](https://github.com/topics/dsh-plugin)
5
+ [中文](README.md) · [Installation](#compatibility-and-installation) · [Quick start](#quick-start) · [Core features](#core-features) · [Configuration](#configuration-reference) · [Development](#development)
10
6
 
11
- 📖 [中文文档 / Chinese: README.md](README.md)
7
+ [![npm version](https://img.shields.io/npm/v/dsh-advisor-group.svg)](https://www.npmjs.com/package/dsh-advisor-group)
8
+ [![DSH plugin](https://img.shields.io/badge/DSH%20plugin-dsh--plugin-3f8cff)](https://github.com/topics/dsh-plugin)
9
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
12
10
 
13
- ---
11
+ | Capability | What it helps you do |
12
+ | --- | --- |
13
+ | **Multi-model consultation (Advisor Group)** | For professional, high-risk, or uncertain questions, let expert advisors answer in sequence before a driver model synthesizes their conclusions. |
14
+ | **Cross-session collaboration (global swarm)** | Ask another existing session and reuse the code it read, the investigation it completed, and the decisions it settled instead of starting over. Beyond this session's subagent capability, it lets the model write across projects. |
15
+ | **Advisor Corps role cards** | Assign a name and duty to a session. You can then say “ask 小明” without remembering its title or sessionId. |
14
16
 
15
- <img width="865" height="983" alt="1788679983177" src="https://github.com/user-attachments/assets/dad2f2be-acf6-41d7-b61a-c11596e48408" />
17
+ ## Compatibility and installation
16
18
 
17
- ## ✨ Features
19
+ | Item | Details |
20
+ | --- | --- |
21
+ | DSH | The current development baseline is `0.2.1-rc.3`; the declared peer range is `>=0.1.7-rc.2 <0.3.0` |
22
+ | Node.js | Required for source builds: `^22.19.0 || >=24.0.0` |
23
+ | Applications | DSH Web and the desktop app, sharing the same client bundle |
18
24
 
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.
25
+ Earlier versions inside the peer range have not all been re-verified on a live host. Cross-session features also require the host to provide `sessionController`; the related tools report an explicit error when it is unavailable, while multi-model consultation remains unaffected.
30
26
 
31
- ## ✅ Compatibility
27
+ ### Install from npm
32
28
 
33
- | Surface | Status |
34
- |---|---|
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` |
36
- | Node | `^22.19.0 \|\| >=24.0.0` |
37
- | Platforms | DSH Web and the desktop app (same client bundle) + headless host logic |
29
+ For DSH Web:
38
30
 
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.
31
+ ```sh
32
+ dsh plugin --profile web add dsh-advisor-group
33
+ ```
34
+
35
+ Restart DSH Web the way you normally do, then hard-refresh the browser (`Ctrl+Shift+R`).
36
+
37
+ **Desktop app**: open **Plugins → Add plugin** inside the app and enter `dsh-advisor-group`. The desktop app owns its profile; `dsh plugin --profile desktop …` is refused by design, not because your permissions are insufficient.
40
38
 
41
- ## 📦 Installation
39
+ ### Install from source (main)
42
40
 
43
41
  ```sh
44
- # 1. Install the plugin (one package, everything included)
45
- dsh plugin --profile web add dsh-advisor-group
42
+ git clone https://github.com/xingzhen199186/dsh-advisor-group.git
43
+ cd dsh-advisor-group
44
+ npm install --legacy-peer-deps --no-audit --no-fund
45
+ npm run build
46
+ npm pack
46
47
 
47
- # 2. Restart DSH web
48
- npx @deepseek-ai/dsh web
48
+ # Use the actual filename printed by npm pack.
49
+ # The current package.json version is 0.1.5, producing:
50
+ dsh plugin --profile web add ./dsh-advisor-group-0.1.5.tgz
49
51
  ```
50
52
 
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.
53
+ For a source-built desktop installation, enter the `.tgz` file's absolute path under **Plugins → Add plugin**. Restart the corresponding DSH application after installation.
54
+
55
+ ## Quick start
56
+
57
+ ### Path 1: consult several models
58
+
59
+ 1. Open **Settings → Plugins → 顾问群 (Advisor Group)**.
60
+ 2. Add at least one advisor and select its provider route and model. Use **获取模型列表** (Fetch model list) to retrieve the route's model catalogue.
61
+ 3. Prefix your question with `@顾问群` and include the necessary background. For example:
52
62
 
53
- > **From source (development)**
54
- > ```sh
55
- > npm install --legacy-peer-deps --no-audit --no-fund
56
- > npm run build
57
- > dsh plugin --profile web add ./dsh-advisor-group-<version>.tgz # after npm pack (version from package.json)
58
- > ```
63
+ ```text
64
+ @顾问群 Review this plugin's cross-session collaboration design.
65
+ Background: DSH Web and desktop share one codebase. The design must never deliver automatically or guess a target session.
66
+ ```
59
67
 
60
- > **Desktop app**: the desktop app owns its own profile — `dsh plugin --profile desktop …` is refused by design, not by permissions. Install from inside the app instead: **Plugins** in the sidebar → **Add plugin** → enter the package name or the absolute path of a local tarball.
68
+ `@顾问群` forces a consultation. The main model can also escalate ordinary professional questions under its routing rules, including when the same unresolved question appears three times or its reported confidence falls below the threshold.
61
69
 
62
- ## 🚀 Quick start
70
+ <img width="720" alt="Advisor Group settings page: sections for enable and daily quota, advisors, consultation behavior, triggers and classification, global swarm, and advanced settings, all collapsed by default" src="https://raw.githubusercontent.com/xingzhen199186/dsh-advisor-group/main/docs/images/advisor-group-settings.png" />
63
71
 
64
- <img width="791" height="797" alt="image" src="https://github.com/user-attachments/assets/5fba07bc-8c36-40f1-b423-d206aa495192" />
72
+ The settings page follows the setup path, with every section folded by default; each open section has its own save row.
65
73
 
66
- 1. Restart DSH web and hard-refresh the browser (`Ctrl+Shift+R`).
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.
68
- 3. Just start a conversation:
69
- - type `@顾问群` in your question to force a consultation, or
70
- - ask a professional/uncertain question — the plugin escalates automatically when appropriate.
74
+ ### Path 2: ask another session by role name
71
75
 
72
- Tools available to the model: `ask_advisors`, `toggle_advisor_group`, `ask_session`, `list_sessions`, `ask_result`.
76
+ 1. Open the **顾问团 (Advisor Corps)** tab in any conversation. If the tab bar runs out of space, it may appear under the **…** menu.
77
+ 2. Fill in **身份姓名** (Role name) and **主要负责** (Main responsibility) for the target session, then click **加入顾问团** (Add to Advisor Corps) or **保存** (Save). Alternatively, click **用主模型生成** (Generate with main model), review the suggestions, and save them individually or through **保存全部更改** (Save all changes).
78
+ 3. Return to the conversation and ask naturally:
73
79
 
74
- ## 🐝 The global swarm: sessions that can ask each other
80
+ ```text
81
+ Ask 小明: what changed in the Advisor Corps role cards today?
82
+ ```
75
83
 
76
- ### One session is an expert with memory
84
+ Or ask several members at once:
77
85
 
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.
86
+ ```text
87
+ Ask 小明 and 阿龙: review this design for desktop and Web omissions respectively, then combine both conclusions.
88
+ ```
79
89
 
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.
90
+ Generated cards only fill drafts; **they take effect only after you save them, and existing cards are never overwritten**. Session titles, working-directory names, and sessionIds remain valid target spellings.
81
91
 
82
- ### Four principles
92
+ ## Core features
83
93
 
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.**
94
+ ### Multi-model consultation: deepen the discussion automatically
88
95
 
89
- ### Three tools
96
+ One `ask_advisors` call runs the whole consultation:
90
97
 
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. |
98
+ ```text
99
+ Original question
100
+ → Round 1: Advisor A → Advisor B (sees A) → Advisor C (sees A and B)
101
+ → Later rounds: a deeper driver question → the same advisor relay
102
+ → A synthesis from the driver model
103
+ ```
104
+
105
+ <img width="865" height="983" alt="An Advisor Group consultation card showing actions, thinking, tool calls, and the final response in step order" src="https://github.com/user-attachments/assets/dad2f2be-acf6-41d7-b61a-c11596e48408" />
106
+
107
+ - **Controlled rounds**: `maxRounds` ranges from 1 to 5 and defaults to 2. With `autoDeepen` disabled, later rounds repeat the original question; the final synthesis is still generated.
108
+ - **No separate driver route to configure**: the driver writes follow-ups and the synthesis through the current session's provider and model. It falls back to `discussion.driverModel` when that route cannot be read.
109
+ - **Advisors can use session tools**: the default scope is read-only. Calls appear in the card, and after at most four tool rounds the advisor must answer in text. Set the scope globally or override it per advisor.
110
+ - **A streaming card that preserves the process**: the green CRT card renders `📋 Action → 💭 Thinking → ⛭ Tool → 📄 Response`. Thinking and tool details start collapsed; the response starts expanded.
111
+ - **Resume from the checkpoint**: after a manual stop or host restart, **▶ 继续聊天** (Resume chat) completes only the missing advisors and rounds instead of asking completed advisors again.
112
+ - **Optional Jev pre-classification**: off by default. When enabled, Jev can judge whether advisors are needed, whether the request is high-risk, and whether web search is more appropriate. Failures fall back to the local rule-based classifier.
113
+ - **28 provider presets across 12 platforms**: DeepSeek, Moonshot Kimi, Kimi Code, Aliyun Bailian, Zhipu AI, OpenAI, Claude, Gemini, SiliconFlow, AIHubMix, OpenRouter, and OpenCode Go.
114
+
115
+ ### Advisor Corps: give each session a durable identity
116
+
117
+ A session title describes a window, but it may not be a durable way to address that session. Advisor Corps binds a **name and duty** to the session id and stores it locally in `$DSH_HOME/storages/advisor-group/corps.json`; it never renames the session.
96
118
 
97
- ### Finding the one to ask
119
+ <img width="760" alt="Advisor Corps panel: one row per visible session, showing its role name, duty, copy-name action, and save controls" src="https://raw.githubusercontent.com/xingzhen199186/dsh-advisor-group/main/docs/images/advisor-corps-panel.png" />
98
120
 
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.
121
+ - Names are globally unique and case-insensitive, with a 40-character limit. Duties can contain up to 200 characters.
122
+ - **Generate with main model** uses the model of the **currently open conversation**. Each pass proposes cards for at most 12 unassigned sessions, fills drafts only, and never saves automatically.
123
+ - Edit rows, copy role names, save in batch, or remove cards. Cards whose sessions are archived, excluded, outside every workspace, or no longer present appear under **未挂接** (Unlinked), where you can keep or delete them.
124
+ - `list_sessions` includes role names and duties. `ask_session` and `ask_sessions` accept an exact role name, which takes precedence over a session title with the same name.
100
125
 
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.
126
+ ### Cross-session collaboration: ask one session or a group
102
127
 
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.
128
+ - **Single-session asks**: `ask_session` waits synchronously by default. With `detach: true`, it returns an `askId` immediately so you can read the result later through `ask_result`.
129
+ - **Batch asks**: `ask_sessions` sends one question to 1–8 explicit targets. `mode: 'all'` waits for every target; `mode: 'any'` returns when the first target settles while the others continue and write their own records.
130
+ - **Automatic answer copy-back**: `swarm.deliverBack` is enabled by default. Answers that finish as `done` are copied back into the asking session; timeouts and errors are not. A batch combines all completed answers into one copy-back message.
131
+ - **Traceable results**: every delivered target gets its own `askId`. An asynchronous record can be `waiting`, `done`, `timeout`, `error`, or `interrupted`; the last state means a host restart prevented the wait from completing.
132
+ - **No guessed targets**: when `ask_session` has no target, routing returns candidates without delivering anything. `ask_sessions` performs no routing; unresolved, ambiguous, or excluded targets are reported in its skipped list with reasons.
133
+ - **Explicit collaboration guards**: at most two relay hops; no self or subagent targets; one waiting `ask_session` per asking session; a 32,000-byte combined limit for question and context; and no cancellation of a target turn when the wait times out.
104
134
 
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.
135
+ > **What the returned text means**: the tool collects everything the target says between the **delivery receipt** and its next **whole-session idle**. It is not guaranteed to be a one-to-one answer to your question. The `status` field (`ok` / `partial` / `failed`) describes the reliability of that collection.
106
136
 
107
- ### Practicalities and honest limits
137
+ ## Model tool reference
108
138
 
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`.
139
+ The main model calls these tools inside a conversation. **They are not slash commands, and you do not need to write tool arguments yourself.** Cross-session tools should be used only when you explicitly ask to contact another session or window.
114
140
 
115
- In one line: say "ask the 极简遥控器 session how far along it is".
141
+ | Tool | Purpose |
142
+ | --- | --- |
143
+ | `ask_advisors` | Start a multi-model consultation, or append a round with `sessionId + followUp` |
144
+ | `ask_session` | Ask one existing session; supports waiting, detached delivery, and candidate routing |
145
+ | `ask_sessions` | Ask 1–8 explicit targets in a batch; supports `all` and `any` collection modes |
146
+ | `list_sessions` | Read session titles, directories, activity, and Advisor Corps names and duties without waking anyone |
147
+ | `ask_result` | Read one asynchronous result by `askId`, or list recent asks started by this session |
148
+ | `toggle_advisor_group` | Enable or disable multi-model consultation and persist the setting |
116
149
 
117
- ## ⚙️ Configuration
150
+ ## Configuration reference
151
+
152
+ For everyday use, these are the settings you are most likely to adjust:
153
+
154
+ | Key | Default | When to adjust it |
155
+ | --- | --- | --- |
156
+ | `discussion.maxRounds` | `2` | Lower it to reduce cost, or raise it up to 5 for a deeper discussion |
157
+ | `discussion.maxAdvisorsPerCall` | `3` | Control how many advisors join one consultation, from 1 to 10 |
158
+ | `discussion.advisorTools` | `'readonly'` | Use `'off'` to disable advisor tools; reserve `'all'` for fully trusted scenarios |
159
+ | `quota.enabled` / `quota.maxPerDay` | `true` / `50` | Set the daily new-consultation cap; counts continue when the cap is disabled |
160
+ | `swarm.defaultMode` | `'wait'` | Use `'detach'` to make cross-session asks asynchronous by default |
161
+ | `swarm.defaultWaitMs` | `180000` | Adjust the default wait ceiling, from 1000 to 600000 ms |
162
+ | `swarm.deliverBack` | `true` | Disable it to read completed answers only through `ask_result`, without automatic copy-back |
163
+
164
+ <details>
165
+ <summary><strong>Expand the complete configuration reference</strong></summary>
166
+
167
+ ### Discussion and advisors
118
168
 
119
169
  | Key | Type | Default | Description |
120
- |---|---|---|---|
121
- | `enabled` | boolean | `true` | Enable advisor group |
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) |
123
- | `discussion.maxAdvisorsPerCall` | number | `3` | Max advisors per call (1–10) |
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 |
125
- | `discussion.driverModel` | object | – | Fallback driver model `{provider, model}` when the session header cannot be read |
126
- | `discussion.advisorTimeoutMs` | number | `600000` | Per-advisor call timeout (ms, 1000–600000), applied to both channels |
127
- | `discussion.driverTimeoutMs` | number | `600000` | Driver generation timeout (deep-question / conclusion; ms, 1000–1200000) |
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**). |
129
- | `quota.enabled` | boolean | `true` | Enable the daily new-consultation cap (cost safety valve) |
130
- | `quota.maxPerDay` | number | `50` | Max new consultations per UTC day (1–100000); ignored when `quota.enabled` is `false` |
131
- | `trigger.requireClassifier` | boolean | `true` | Run the pre-classifier before starting |
132
- | `trigger.allowWebFallback` | boolean | `true` | Allow classifier to recommend web search |
133
- | `trigger.confidenceThreshold` | number | `0.6` | Escalate when main-model confidence is below this |
134
- | `trigger.jev.enabled` | boolean | `false` | Use the external Jev model for semantic pre-classification of non-forced consultations; falls back to the local rules-based classifier when Jev is unavailable |
135
- | `trigger.jev.provider` / `trigger.jev.model` | string | `'typesafe'` / `'jev-latest'` | Jev route (`typesafe` or `openrouter`) and model; key via `trigger.jev.apiKey` (secret) or `trigger.jev.apiKeyEnv`; optional `baseURL`, `timeoutMs` (default `10000`) |
136
- | `trigger.jev.confidenceThreshold` | number | `0.6` | Escalate when Jev answers `needsAdvisor` yes (or its 0–1 score reaches this value) |
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` |
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 |
149
- | `ui.theme` | string | `retro-green` | Chat card theme (`retro-green` / `retro-amber` / `retro-blue`) |
150
- | `ui.showTimestamps` | boolean | `true` | Show timestamps |
151
- | `ui.autoExpand` | boolean | `true` | Auto-expand card |
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) |
159
- | `advisors` | array | `[]` | Advisor list (provider/model/baseURL/apiKey/apiKeyEnv/protocol…, keys are `role('secret')`) |
160
-
161
- > `discussion.parallel` and `discussion.stopOnConsensus` are deprecated leftovers kept only for stored-config compatibility.
162
-
163
- ## 🧮 Daily quota
164
-
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.
168
-
169
- ## 💸 Usage and cost
170
-
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.
172
-
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.
174
-
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.
176
-
177
- ## 🔒 Security & privacy
178
-
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**).
185
-
186
- ## ⚠️ Known limitations
187
-
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.
191
-
192
- ## 🛠️ Development
170
+ | --- | --- | --- | --- |
171
+ | `enabled` | boolean | `true` | Enable multi-model consultation |
172
+ | `discussion.maxRounds` | number | `2` | Total consultation rounds, 1–5 |
173
+ | `discussion.maxAdvisorsPerCall` | number | `3` | Maximum advisors in one consultation, 1–10 |
174
+ | `discussion.autoDeepen` | boolean | `true` | Generate a deeper driver question before later rounds; when off, repeat the original question and still synthesize a conclusion |
175
+ | `discussion.driverModel` | object | – | Fallback `{ provider, model }` when the current session route cannot be read |
176
+ | `discussion.advisorTimeoutMs` | number | `600000` | Per-advisor timeout, 1000–600000 ms |
177
+ | `discussion.driverTimeoutMs` | number | `600000` | Timeout for a follow-up or synthesis, 1000–1200000 ms |
178
+ | `discussion.advisorTools` | string | `'readonly'` | Default advisor tool scope: `readonly` / `all` / `off` |
179
+ | `advisors` | array | `[]` | Advisor list, including `provider`, `model`, `systemPrompt`, and optional `baseURL`, `apiKey`, `apiKeyEnv`, `protocol`, and related fields |
180
+ | `advisors[].tools` | string | – | Per-advisor tool scope; unset follows `discussion.advisorTools` |
181
+ | `advisors[].thinking` | string | – | `enabled` / `disabled`; unset follows the provider default |
182
+ | `advisors[].reasoningEffort` | string | – | `off` / `low` / `high` / `max`; unset follows the provider or model default |
183
+ | `advisors[].temperature` | number | – | Sampling temperature, 0–2 |
184
+ | `advisors[].maxTokens` | number | – | Token limit for one answer, at least 1 |
185
+ | `advisors[].authMode` | string | – | Anthropic direct-channel authentication header: `x-api-key` / `bearer` |
186
+ | `advisors[].avatar` | string | – | Short card avatar, using an emoji or ASCII text |
187
+
188
+ Advisor tool calling is supported only on direct OpenAI / Anthropic channels. The DSH built-in channel can reuse host credentials for ordinary responses, but it does not support this advisor tool loop.
189
+
190
+ ### Triggers and Jev
191
+
192
+ | Key | Type | Default | Description |
193
+ | --- | --- | --- | --- |
194
+ | `trigger.requireClassifier` | boolean | `true` | Run pre-classification before a non-forced consultation |
195
+ | `trigger.allowWebFallback` | boolean | `true` | Return a no-consultation hint when the classifier recommends web search |
196
+ | `trigger.confidenceThreshold` | number | `0.6` | Allow escalation below this main-model confidence score, from 0 to 1 |
197
+ | `trigger.jev.enabled` | boolean | `false` | Use Jev for semantic pre-classification; failures fall back to local rules |
198
+ | `trigger.jev.provider` / `trigger.jev.model` | string | `'typesafe'` / `'jev-latest'` | Jev route and model; the route can also be `openrouter` |
199
+ | `trigger.jev.baseURL` | string | – | Optional custom Jev endpoint |
200
+ | `trigger.jev.apiKey` / `trigger.jev.apiKeyEnv` | string | – | Jev key or the environment variable containing it; key fields are treated as secrets |
201
+ | `trigger.jev.timeoutMs` | number | `10000` | Jev request timeout, 1000–60000 ms |
202
+ | `trigger.jev.confidenceThreshold` | number | `0.6` | Threshold for deciding that advisors are needed |
203
+ | `trigger.jev.highRiskThreshold` | number | follows the previous setting | High-risk threshold; unset follows `trigger.jev.confidenceThreshold` |
204
+ | `trigger.jev.useEnglishState` | boolean | `false` | Let Jev read the caller-provided English gist `questionEn`; cards and session records keep the original question |
205
+
206
+ ### Global swarm
207
+
208
+ | Key | Type | Default | Description |
209
+ | --- | --- | --- | --- |
210
+ | `swarm.enabled` | boolean | `true` | Cross-session master switch; when off, all four cross-session tools refuse explicitly |
211
+ | `swarm.defaultMode` | string | `'wait'` | Default `ask_session` behavior: `wait` / `detach` |
212
+ | `swarm.defaultWaitMs` | number | `180000` | Default wait ceiling, 1000–600000 ms |
213
+ | `swarm.resultTtlHours` | number | `24` | How long asynchronous results are retained, 1–720 hours |
214
+ | `swarm.deliverBack` | boolean | `true` | Copy `done` answers back automatically; never auto-copy `timeout` / `error` |
215
+ | `swarm.rosterIncludeSubagents` | boolean | `false` | Show subagents in the roster; delivery tools still refuse subagent targets |
216
+ | `swarm.routingEnabled` | boolean | `true` | Suggest candidates when `ask_session` has no target; never deliver automatically |
217
+ | `swarm.routingTopN` | number | `3` | Number of candidates, 1–8 |
218
+ | `swarm.routingEngine` | string | `'local'` | `local` ordering or `jev` reranking of a closed candidate set; failures fall back to local |
219
+ | `swarm.routingAsk` | string | `'gated'` | Whether to suggest a cross-session ask: `off` / `gated` / `always`; a suggestion never delivers |
220
+ | `swarm.routingExclude` | string[] | `[]` | Sessions excluded from the swarm, its roster, and its candidates; direct asks are refused too |
221
+
222
+ ### Legacy compatibility fields
223
+
224
+ `discussion.parallel` and `discussion.stopOnConsensus` are deprecated and retained only so stored configurations remain readable.
225
+
226
+ `ui.theme`, `ui.showTimestamps`, and `ui.autoExpand` are also legacy fields. The current client does not read them, so changing them has no effect on the card's appearance or expansion behavior. The current card uses a green CRT style; thinking and tool details start collapsed.
227
+
228
+ </details>
229
+
230
+ ## Usage, quota, and cost
231
+
232
+ A consultation that completes `R` rounds with `N` advisors and no tool calls normally makes **`R × (N + 1)` model calls**: `R × N` advisor responses, `R - 1` deepening questions, and one final synthesis. With the default `R = 2` and three advisors actually participating, this produces 8 calls.
233
+
234
+ Actual usage also depends on these behaviors:
235
+
236
+ - Advisor tool loops add model calls. The driver and every advisor are billed through their respective routes.
237
+ - Generating role cards calls the current conversation's model. Jev classification or routing is also billed through its configured route when enabled.
238
+ - Cross-session asks do not consume the advisor-consultation quota, but they wake target sessions and consume each target's own model quota. Batch asks are accounted for per target.
239
+
240
+ The daily quota counts **new consultations per UTC calendar day**, with a default cap of 50. Resumes, follow-up rounds, and cross-session asks do not consume this quota. The counter lives in `$DSH_HOME/storages/advisor-group/daily-guard.json`; disabling the cap keeps counting without blocking requests. To control cost, reduce rounds or advisors, disable advisor tools, and set a daily cap.
241
+
242
+ ## Security and privacy
243
+
244
+ - **Keys are not returned to the browser**: direct API keys are stored in the local DSH configuration under `SecretField` semantics. Use `apiKeyEnv` if you prefer not to write a key into that configuration.
245
+ - **Local routes require a token**: `/advisor-group/*` uses a shared token generated at startup and is designed for a local single-user setup. Add reverse-proxy authentication before exposing it to a LAN or the public internet.
246
+ - **Diagnostic requests are constrained**: model-list and connection-test calls run server-side and validate the target URL, protocol, and redirects. DNS rebinding remains a known architectural boundary.
247
+ - **Use `advisorTools: 'all'` with care**: it exposes every session-visible tool to advisors, including file writes and command execution. Calls still pass through DSH's tool pipeline and permission guards. Keep `readonly` or `off` for everyday use; non-read-only tool calls emit warning logs.
248
+ - **Cross-session content leaves a record**: questions and context are permanently recorded in target session logs, so send only what is necessary. Role-card generation sends session titles, working directories, and activity hints to the current main model. The cards themselves are stored locally and contain no API keys.
249
+ - **Local state is centralized**: consultation snapshots, asynchronous ask records, role cards, and quota data live under `$DSH_HOME/storages/advisor-group/`. Classifier shadow logs are used only for threshold tuning and do not influence runtime decisions.
250
+
251
+ ## Known limitations
252
+
253
+ - After an abrupt DSH host crash, an unrefreshed card may still display **LIVE**. Refreshing restores its actual state from the persisted session log; the client already shows a disconnection notice.
254
+ - Risk notices are based on objective events: truncation caused by timeout or network interruption, and cancellation caused by a manual stop. The plugin no longer guesses risk by scanning advisor prose for words such as “risk” or “uncertain”.
255
+ - Cross-session tools have no per-session visibility switch. When `swarm.enabled` is on, all four tools are visible to sessions; when off, calls are refused explicitly. Mutual waits between two sessions are not specifically detected as deadlocks.
256
+ - The Advisor Corps panel displays at most 50 visible sessions. Automatic generation handles at most 12 unassigned sessions per pass; run it again to continue filling the roster.
257
+
258
+ ## Development
193
259
 
194
260
  ```sh
195
261
  npm install --legacy-peer-deps --no-audit --no-fund
196
262
  npm run typecheck
197
- npm test # vitest suite, incl. provider streaming contracts against a local fake LLM server
198
- npm run build # tsdown; client bundle must NOT be built with minify: true
263
+ npm test
264
+ npm run build
265
+ npm pack --dry-run
199
266
  ```
200
267
 
201
- ## 📄 License
268
+ The Vitest suite covers provider streaming contracts, cross-session delivery and waits, role cards, settings redaction, and recovery boundaries. Builds use tsdown. The client bundle **must not use `minify: true`**, because minification breaks the plugin-id form required by the startup guard.
269
+
270
+ - [Changelog](CHANGELOG.md)
271
+ - [Issue tracker](https://github.com/xingzhen199186/dsh-advisor-group/issues)
272
+ - [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
273
+
274
+ ## License
202
275
 
203
276
  [Apache License 2.0](LICENSE) © 2026 dsh-advisor-group contributors.