@mars.liu/dsh-cybernetics 0.8.10 → 0.25.0

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.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | [简体中文](README.zh-CN.md)
4
4
 
5
- ![version](https://img.shields.io/badge/version-0.8.2-blue) ![protocol](https://img.shields.io/badge/license-MIT-lightgrey) ![platform](https://img.shields.io/badge/platform-DSH%20plugin-9b59b6)
5
+ ![version](https://img.shields.io/badge/version-0.10.0-blue) ![protocol](https://img.shields.io/badge/license-MIT-lightgrey) ![platform](https://img.shields.io/badge/platform-DSH%20plugin-9b59b6)
6
6
 
7
7
  **dsh-cybernetics is a DSH plugin that explicitly designs LLM agent orchestration as a control loop**: the session goal persists as an artifact of the agent's thinking; the task agent acts, the verify agent measures, the triage agent triages, the controller advances between rounds, and the user calibrates the outer loop through conversation — a negative-feedback loop of goal, action, measurement, and calibration.
8
8
 
@@ -91,11 +91,17 @@ Passing `abortPrevious: true/false` explicitly keeps manual control; every revis
91
91
  One session supports multiple concurrent goals (the plugin's own goal board; dsh-goal's single slot demotes to the focus goal). The client `lib/client.js` provides two session tabs, pushed live and updated in place via the plugin's own session projection `cyberneticsGoals`:
92
92
 
93
93
  - **"Goals"**: all goals of this session (title / status badge / round N / current subgoal / depth and executor chips / superseded and revision-source annotations), sorted in two segments — unfinished first, completed last; an empty board falls back to dsh-goal's single-goal rendering.
94
+ - **"Goals" from a subagent session (v0.18)**: workflow task/verify/triage children and Agent Teams teammates own no goal board, so the tab now renders the whole session's board instead — the nearest ancestor session with a non-empty `cyberneticsGoals` board (normally the main session), resolved from the live session list (`ctx.sessions.list`): `byId[id].parentId` first, falling back to the owning session's `subagentCatalog` entry, with a visited-cycle guard and a 16-hop cap. In that mode the board also marks the goal whose `agents` contain this session (or, for a grandchild, the nearest ancestor that does): an accent left border plus a「当前 / Current」badge. The card is scrolled into view once on open (`scrollIntoView({ block: 'center' })`, deduped on the scroll container so live board updates never re-scroll; root sessions never auto-scroll). The agent chips keep working there: they navigate with the BOARD session as `parentSessionId` (their real parent) and read liveness from that session's catalog, so from any sub agent you can jump to the session's other agents. A session with its own non-empty board renders exactly as before (no banner, no scroll), and a host without the session list store keeps the pre-v0.18 empty fallback. Client-side only: takes effect after a host restart plus a browser refresh.
95
+ - **"Conversation" home entry (v0.18.1, home semantics since v0.18.3)**: both tabs open with a small tab-like「对话 / Conversation」button on the content's first line. It is a「回到主页 / home」navigation: from ANY agent's Goals/Control tab it takes you back to the MAIN (root) agent's chat view — not, as v0.18.2 did, to the current sub agent's chat. On the root session one click switches its own view directly (`openView('chat', 'cybernetics-home')`). From a sub agent it is a two-legged SPA hop through public host seams: `uiWorkspace.openSession(rootId)` selects the root session (target = `SessionId | SubagentAddress`), after dropping a one-shot, ~4 s home request addressed to that session; the root session's own Goals/Control view consumes it on mount (read-and-delete) and calls its own `openView('chat', …)`. No reload, no URL change, no new session, no new server API, and nothing is written to `dsh.conversation.*` storage. A host that does not hand the `openView` owner prop to view components renders no entry; a host without `uiWorkspace` degrades the click to the local chat switch. Known limitation: if the root session was left on some other view (chat itself or a third-party tab), our consume point never mounts and the request expires — you still land on the root session, whose view stays as it was. Client-side only: takes effect after a host restart plus a browser refresh.
94
96
  - **"Control"**: recursively shows the run state of every agent in this session (main controller + subagent catalog) with each one's goal view, a running-count summary at the top, and an executor row per goal (status + title + executor ephemeral/persistent + persistent teammate name + round count), sharing the same data source as the "Goals" tab.
95
97
 
98
+ ### Control-mode indicator (v0.10.0)
99
+
100
+ Every session header carries an always-visible pill showing the effective control mode at a glance: **green dot + "On"** = effective (explicit `/cybernetics on` override, or inherited from the global `allGoalsMode`), **gray dot + "Off"** = effective off, **dim "…"** = the projection/config has not loaded yet (a loading state, never an error state). The tooltip names the deciding source ("explicit session override" vs "inherited from global config"). Clicking the pill toggles the mode — it sends the same `/cybernetics on|off` command the slash menu takes (host `commands.execute` transport; no new server API), so the write is the same durable session event with live projection feedback. When the mode is effective, a thin hint bar above the composer states what will happen to the next input, and its wording follows the volatile `inputPolicy` (insert → "new input is inserted into the current turn as outer-loop control"; queue → "new input queues as the next turn"). Data join: the session projection `cyberneticsMode` ({override: true|false|null}) + the global volatile `allGoalsMode` from the plugin's ConfigForm, joined client-side by the same rule as the server's `resolveMode` (src/sessionMode.ts) — the projection only republishes on session events, so a global volatile flip cannot reach a server-computed effective value, while both inputs are reactive on the client. All copy is bilingual (zh/en). Client-side only: takes effect after a host restart (client module graph rebuilds at boot) plus a browser refresh.
101
+
96
102
  ### Plugin-page config card
97
103
 
98
- `src/index.ts` exports a schemastery `Config` schema that decides which fields the host settings transport serves and accepts (only `.volatile()` fields); the card UI itself is drawn by `lib/client.js`, which registers three seats — `plugins.item` / `plugins.bundle.config` / `plugins.row.config` (both the bundle detail page and the component detail page can configure). **The card is the ONLY GUI surface for the plugin's settings**: the host Plugins page renders a plugin's configuration exclusively through its slot cards and draws no fields of its own, and `@deepseek-ai/dsh-client-ui-settings` ships the `configForms` service (transport), not a generic field renderer — so since v0.8.1 the card renders editable controls for ALL volatile fields (six since v0.8.4): the all-goals-mode toggle, task/verify/triage agent model text fields, and the rounds-per-subgoal and concurrent-round-cap number fields. The `roles` table is **not** on the card (v0.8.4): hand-editing a JSON object in the GUI is unfriendly, so roles became non-volatile — the panel neither serves nor writes it — and is configured directly in the profile config yaml instead (see "Advanced configuration: roles" below). Saving a volatile field takes effect immediately (the loader commits it straight into the running instance, no remount); saving a non-volatile field triggers a plugin-row remount (requires a host reload). The write lands in the user's patch row (a `{id, name, config}` row in the profile's `cordis.patch.yml`).
104
+ `src/index.ts` exports a schemastery `Config` schema that decides which fields the host settings transport serves and accepts (only `.volatile()` fields); the card UI itself is drawn by `lib/client.js`, which registers three seats — `plugins.item` / `plugins.bundle.config` / `plugins.row.config` (both the bundle detail page and the component detail page can configure). **The card is the ONLY GUI surface for the plugin's settings**: the host Plugins page renders a plugin's configuration exclusively through its slot cards and draws no fields of its own, and `@deepseek-ai/dsh-client-ui-settings` ships the `configForms` service (transport), not a generic field renderer — so since v0.8.1 the card renders editable controls for ALL volatile fields (16 since v0.17): the all-goals-mode toggle, the hard-gate toggle, the **smart agent model selection toggle**, the **preset-plans dropdown**(预置方案)+ its request-model field, the per-agent「model + reasoning effort」pairs for task/verify/triage, and the rounds-per-subgoal and concurrent-round-cap number fields. The `roles` table is **not** on the card (v0.8.4): hand-editing a JSON object in the GUI is unfriendly, so roles became non-volatile — the panel neither serves nor writes it — and is configured directly in the profile config yaml instead (see "Advanced configuration: roles" below). Saving a volatile field takes effect immediately (the loader commits it straight into the running instance, no remount); saving a non-volatile field triggers a plugin-row remount (requires a host reload). The write lands in the user's patch row (a `{id, name, config}` row in the profile's `cordis.patch.yml`).
99
105
 
100
106
  ### Agent model selection
101
107
 
@@ -113,6 +119,62 @@ One session supports multiple concurrent goals (the plugin's own goal board; dsh
113
119
 
114
120
  - **Call-level fallback (v0.8)**: when a role's configured model cannot be called — an *availability* failure such as exhausted quota/rate limit, unreachable network, or a provider error — the round no longer fails as a whole: `workflows/round.js` drops that role's provider/model override and retries the role's sub-agent once on the **session agent's** model configuration. The decision signal comes from the workflow runtime's (`@deepseek-ai/dsh-workflow-ptc`) `agent()` hook error type/message: a thrown `WorkflowError` (`AGENT_START` always counts; `AGENT_RESULT`/uncoded errors count when the message matches quota/auth/5xx/network/provider patterns), or a `null` return (the runtime flattens a child run ending with `stopReason ≠ "completed"` — the most common shape of a mid-run LLM call failure — into null without the cause; retried only when an override is in effect). **Logic failures never swap models**: a task agent that delivers a `done=false` report or a verify agent returning an unmet verdict flows through unchanged, and non-availability errors (schema/option/cap guard errors) propagate as before. Every fallback is recorded on the round result's `fallbacks` array (`{role, from, trigger, reason}`), narrated into `workflow/log`, persisted in the `.cybernetics/round-*.json` audit log, and surfaced in the decision note of the `cybernetics` tool result. Configuration stays backward compatible: empty = inherit the session agent (no fallback needed); configured = the configured model wins, with fallback on availability failure. Changes to `workflows/round.js` take effect hot.
115
121
 
122
+ ### Smart agent model selection (`smartAgentModelSelection`, v0.16, default false)
123
+
124
+ A boolean toggle (volatile, on the settings card; takes effect on the **next goal round** after saving). When on, **every round start** (launch side, before any task/verify/triage child is created) runs a cheap **rule-heuristic classification** over the round's work order (brief → subgoal → objective) and picks a provider/model per role from the host's **live model catalog**. The goal is not to escalate hard tasks — it is to let **simple tasks finish faster and cheaper** (a delete-a-file or git-commit round no longer burns the top-tier model) while genuinely hard work stays on the top tier. All rules live in `src/modelSelection.ts` (pure functions, offline-testable; 中文细则见 README.zh-CN.md 同名小节).
125
+
126
+ - **Catalog source**: the host's root `llm` service (`listProviders()` / `listModels(provider)`) — the same live directory the settings-card dropdown reads (`buildModelCatalog`'s data source). **No model names are hardcoded**; provider-level failures are isolated and skipped (host parity). Soft probe: a missing service, a Cordis-guarded property read, or a throwing transport degrades to "catalog unavailable".
127
+ - **Tier derivation (generic signals, no whitelist)**: **parameter-size tokens** in id/name/description (`8b` ≤12 → light, `30b` ≤40 → mid, `70b` >40 → high) beat **naming tokens** (mini/flash/lite/… → light; opus/ultra/plus/reasoner/… → high); no signal → mid (only the writing class ever picks it). The **session agent's current route is the reference ceiling** (latest requestHeader selection wins, options fallback — matching dsh-subagent's inheritance resolution).
128
+ - **Classification → assignment rules** (precedence hard > writing > trivial > standard; **standard never intervenes** — the feature's value is cheapening simple work, not perturbing ordinary work):
129
+
130
+ | Class | Signals (examples) | task | verify | triage |
131
+ |---|---|---|---|---|
132
+ | trivial | delete file + verify absence / git commit·push / rename / typo / formatting | first light-tier model | same as task (existence checks need no strength) | same (light) |
133
+ | writing | write docs/README/report/translate/polish | a model advertising writing strength (high > mid > light), else inherit | **inherit the session model** (strongest reviewer) | light tier |
134
+ | hard | uncommon languages (Rust…) / proving non-trivial math / concurrent runtimes·compilers·consensus·crypto / from-scratch engines | an independent high-tier model; none → inherit (the session route IS the ceiling) | same as task | light tier (bucketing is light work) |
135
+ | standard | everything else | inherit | inherit | inherit |
136
+
137
+ - **Merge priority (per role)**: folded **explicit configuration** (roles table + flat settings fields, provider/model either present) **always wins**; smart selection fills only rows with NO explicit provider/model. A row pinning only a reasoning effort is **not re-routed** (the effort is pinned to the inherited route; re-routing would mismatch it). With nothing configured anywhere, smart selection decides every row; a pick identical to the session route stands down to inheritance (identical route + the inherited effort survives).
138
+ - **Persistent executor: explicit skip**: `spawnTeammate` accepts no model overrides — the task row is audited as `skipped` with the reason (never a silent miss); verify/triage still run as ephemeral round.js agents on full persistent rounds and remain selectable.
139
+ - **quick rounds**: only the task row exists (verify/triage never run; the audit carries the task row alone).
140
+ - **Fallback semantics**: catalog unavailable/empty, no suitable model for the class (e.g. nothing lighter than the session model), or a classifier failure — all **silently fall back to plain inheritance**, never erroring or blocking, and every decision (`selected` / `kept-explicit` / `kept-inherit` / `skipped`) plus its reason lands in the audit.
141
+ - **Audit**: input summary (char count + first 100 chars), the classification, catalog size, and the per-role decision trail (with picked routes and reasons) persist with the round record — the `smartSelection` field of `.cybernetics/round-<goalId>-<N>.json` (the v0.8 `fallbacks` precedent; round.js echoes it from its args, the persistent path carries it via executor.ts), and `collect`/`round` tool results surface it too. Example:
142
+
143
+ ```json
144
+ {
145
+ "smartSelection": {
146
+ "enabled": true,
147
+ "taskClass": "trivial",
148
+ "inputChars": 17,
149
+ "inputPreview": "删除 build 目录并验证文件不存在",
150
+ "catalogSize": 2,
151
+ "catalogStatus": "ok",
152
+ "roles": [
153
+ { "role": "default", "decision": "selected",
154
+ "reason": "任务分类 trivial:为 task 角色选择 p-light/qwen-8b(light 档,目录 2 个模型中按执行档位规则选取)",
155
+ "picked": { "provider": "p-light", "model": "qwen-8b" } },
156
+ { "role": "verify", "decision": "selected", "reason": "… (verification-strength rule)", "picked": { "provider": "p-light", "model": "qwen-8b" } },
157
+ { "role": "triage", "decision": "selected", "reason": "… (bucketing-light rule)", "picked": { "provider": "p-light", "model": "qwen-8b" } }
158
+ ]
159
+ }
160
+ }
161
+ ```
162
+
163
+ - **No reasoning-effort auto-assignment in v0.16**: effort ids are model-dependent; assigning them automatically would need a per-picked-model `resolveModelInfo` probe (an adapter round trip per role per round plus a second failure surface). Smart selection routes only this round; effort stays with the model default or the existing flat effort fields (an explicit model row + effort field pins both).
164
+ - **Why rule heuristics instead of one cheap LLM call**: the host does expose a raw completion face (`ctx.llm.prepareCall().stream()` — the session-title-llm pattern), but using it would import `@deepseek-ai/dsh-llm` runtime dependencies (this plugin is deliberately zero-dependency), add a provider round trip BEFORE every round, and open a second failure surface. Deterministic keyword classification costs nothing, is offline-testable, and mis-classifications are visible in the audit and overridable by explicit config — explicit config always winning is the safety floor.
165
+
166
+ ### Preset plans (`presetPlan`, v0.17)
167
+
168
+ The settings card's「预置方案」dropdown — ten options: **auto-recommend**(根据现有配置自动推荐)plus the nine **vendor registry entries** (OpenAI / Claude / Gemini / DeepSeek / Kimi / Qwen / 火山 Coding Plan / Minimax / GLM, full data in `src/modelPresets.ts`). Selecting writes the volatile `presetPlan` field; the plugin resolves each change **one-shot** on `loader/volatile-update` and applies the result to the flat task/verify/triage model + reasoning-effort fields (the same fields the per-agent groups below render) — the outcome lands in the volatile `presetRecommendation` record rendered under the dropdown.
169
+
170
+ **Session scope (user ruling, highest priority)**: the **session agent is OUT of scope**. The user manages the session model themselves at any time, so preset selection and auto-recommend write ONLY the three sub-agent roles' configuration — `ALLOWED_OP_PATHS` in `src/modelPresets.ts` pins the writable set to exactly the six flat sub-agent fields (three model fields + three effort fields), the recommendation prompt forbids a `session` key in the output, the parser drops one with an explicit note if a model emits it anyway, and every vendor's session suggestion is kept in the registry as REFERENCE data (`variant.session`, shown in the card's session callout as 「由用户自行管理,方案不应用此项」) that can never reach an op.
171
+
172
+ - **Vendor entries (data-driven, `status: 'ready'`)**: each vendor carries per-role model + reasoning-effort token (heterogeneous: OpenAI/Minimax five-level low/medium/high/xhigh/max, DeepSeek thinking switch, others model default), upgrade tier, availability fallback, per-role rationale, per-vendor note (⚠️ caveats — e.g. Qwen's 「verify 绝不用 Turbo」幻觉共振 warning, Kimi's K2-retirement avoidance, DeepSeek's 「成本不应成为降级 verify 的理由」) and VARIANTS (Claude 常规/高要求, Qwen A/B/C, DeepSeek 仅本家/verify 外置, Kimi 长期/最高验收质量, 火山 A/B, GLM 标准组合/重活命名角色). Selecting a preset applies its **default variant** (the first one); the outcome record discloses the variant applied and lists the remaining ones. Vendor-canonical model ids are applied verbatim — an id absent from this deployment's catalog shows the card's model-unavailable warning and the round's availability fallback (v0.8) keeps rounds alive.
173
+ - **Auto-recommend (since v0.18.2 = a session-agent handoff, user ruling)**: selecting auto runs **NO independent LLM call and spawns NO agent** (the previous one guarded one-shot LLM call plus its route config field were removed — re-submitting the whole context to a second model call is a waste of both time and money). The plugin now probes the live catalog (`probeCatalogOptions`, the same directory smart selection reads), builds the handoff brief — embedding the catalog (the only selectable source), the cross-vendor principles (verify ≥ task; verify/task kept cross-generation/cross-family independent; verify must not be over-confident; cost cuts hit task/triage first and never verify; triage light), the current role config and every vendor preset as advisory reference — writes a `pending` record to the card, and hands the brief to the session agent through the plugin-registered `preset_recommend` tool (a steer/followup reminder is delivered to live session agents). The SESSION AGENT reads the catalog in its OWN context, answers for task/verify/triage, and calls the same tool again to submit; the plugin only validates (catalog-matched models only; per-role `reasoningEffort` validated against `ctx.llm.resolveModelInfo` and dropped to the model default when unsupported) and writes. Until the session agent answers, the card shows the pending note; an unusable answer (off-catalog / unparseable / zero picks) degrades to a written `degraded` record with the reason and is reported back to the session agent for a corrected re-submission — nothing is applied and no round is blocked. The tool survives the hard-gate allowlist alongside the cybernetics tool itself.
174
+ - **Selecting is one-shot**: re-selecting the same entry does not re-fire (the handler guards on the last handled value; a value persisted across a restart is not re-applied at boot). Switch to「(未选择)」first to re-apply an entry.
175
+ - **`/cybernetics use [id]` (v0.22.0, command channel)**: switch the role composition to any preset plan without opening the settings card. With no argument the command replies with a plain-text list of every applicable plan — all built-in registry ids plus the configured custom presets (`custom:<id>`) — and asks the user to confirm with a second `/cybernetics use <id>`. With an id it reuses the SAME apply pipeline as the dropdown (`handlePresetSelection`, six-field-bounded ops + the outcome record), so the reply and the card stay consistent; preset ids are case-preserved (custom ids are matched verbatim). Unlike the dropdown, a repeated `use <id>` DOES re-apply the same plan (the command keeps the one-shot volatile guard in sync and deliberately bypasses it), so no「(未选择)」detour is needed. The preset applied this way is profile-global — it affects every concurrent session.
176
+ - **Editor-side preset picker (v0.24.1, popupSelect)**: the picker appears only after you express the `use` intent. Picking `/cybernetics` in the `/` menu completes the composer text to `/cybernetics` (no popup — keep typing `use ...`); pressing Enter on the bare `/cybernetics` token opens a searchable popup listing every applicable plan (the 12 built-in ids and the configured `custom:<id>` entries, grouped, fuzzy-filtered as you type, current plan highlighted); typing `/cybernetics use` and pressing Enter executes the plain-text listing and then opens the same popup over it. Picking a row runs `/cybernetics use <id>` through the same command transport (validation/apply/receipt stay server-authoritative; errors surface in the popup). This rides the host's own mechanism for commands with pickable values (`commandUi.decorate` + the public `popupFor`/`command/executed` faces, the same pattern as the official `/model` picker); hosts without the `commandUi` service keep the plain-text v0.22 behavior, and the typed `/cybernetics use <id>` path is untouched. Note: inline argument completion *inside the draft* (a dropdown right after typing `use `) is not possible with the host protocol — the slash trigger scan stops at the first whitespace (re-verified v0.25.0: the host composer publishes no draft-change event, the four `slash/input-*` scope events are plugin→shell bail-request channels only, and trigger-source outcomes are limited to `{claim}/{text}/{insert}` — nothing can observe the `/cybernetics use` draft before Enter). **Type `/cybernetics use`, then press Enter to open the preset picker** (the bare-enter path lands the plain-text listing first and the popup opens over it).
177
+
116
178
  ## Installation and Usage
117
179
 
118
180
  ### Installation
@@ -148,6 +210,7 @@ Configure directly on this plugin's card on the Web editor's "Plugins" page —
148
210
  - **task/verify/triage agent models** (`taskAgentModel` / `verifyAgentModel` / `triageAgentModel`, volatile): dropdown-backed text fields — pick from the live system model list (shown as `providerName · provider/model`) or type "provider/model" / a bare model name; Enter or blur saves, empty inherits from the session agent (triage = the convergence-decision role, full rounds only). A configured value missing from the live model list shows an unavailable warning (runtime falls back to the session agent model) with a one-click Clear;
149
211
  - **Recursion depth / parallel cap** (`roundsPerSubgoal` integer ≥1 / `maxConcurrentRounds` integer ≥0, volatile): number fields, Enter or blur saves, blank restores the default (an `unset` write);
150
212
  - **Hard gate** (`hardGate`, volatile since v0.8.10): a boolean toggle on the card — strips write/execution tools from the top-level agent at assembly and routes every change through the goal loop; takes effect immediately on save (gate listeners hot-installed/disposed), no restart needed;
213
+ - **Smart agent model selection** (`smartAgentModelSelection`, volatile since v0.16): a boolean toggle on the card — each round start auto-picks a model per role (task/verify/triage) from the live model catalog by task shape (light models for trivial work, the top tier for hard work); explicitly configured role models always win, and unresolvable picks keep inheriting the session agent with the reason audited; takes effect on the next goal round — see the classification rule table in "Features → Smart agent model selection";
151
214
  - **Other gate fields** (`gateMode`/`gatedTools`/`gateAllowlist`/`gateAllowlistExtra`/`gateApprovalRelay`, non-volatile): not served to the card; edit them in the profile config document (saving triggers a plugin reload). `gateMode` has no default: unset + `hardGate: true` runs `allowlist`; an explicit `denylist` is honored and carries the residual risk documented above.
152
215
 
153
216
  ### Advanced configuration: roles (profile yaml, agent-guided)
package/README.zh-CN.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [简体中文](README.zh-CN.md) | [English](README.md)
4
4
 
5
- ![version](https://img.shields.io/badge/version-0.8.2-blue) ![protocol](https://img.shields.io/badge/license-MIT-lightgrey) ![platform](https://img.shields.io/badge/platform-DSH%20plugin-9b59b6)
5
+ ![version](https://img.shields.io/badge/version-0.10.0-blue) ![protocol](https://img.shields.io/badge/license-MIT-lightgrey) ![platform](https://img.shields.io/badge/platform-DSH%20plugin-9b59b6)
6
6
 
7
- **dsh-cybernetics 是一个把 LLM Agent 编排明确设计成控制回路的 DSH 插件**:session goal 作为 agent 的思考产物持久存在,task agent 行动、verify agent 测量、triage agent 分诊,controller 在轮间推进,用户通过对话完成外环校准——目标、行动、测量、校准的负反馈闭环。
7
+ **dsh-cybernetics 是一个把多 agent 协作明确设计成控制回路的 DSH 插件**:session goal 作为 agent 的思考产物持久存在,task agent 行动、verify agent 测量、triage agent 分诊,controller 在轮间推进,用户通过对话完成外环校准——目标、行动、测量、校准的负反馈闭环。
8
8
 
9
9
  这套协议得名于维纳的控制论:设定目标(setpoint)、行动、测量输出与目标的偏差、用偏差修正下一次行动。传统的一次性 prompt 是开环系统——目标只活在当前 prompt 里,测量的责任全在人;本项目把回路搬进系统本身,让人从「逐条 prompt 的作者」变成「目标的设定者与外环的校准者」。设计理念详见博客文章《[Cybernetics Agent——人工智能应用的新范式](https://marchliu.github.io/2026/11/03/cybernetics-agent-new-paradigm)》。
10
10
 
@@ -16,7 +16,7 @@
16
16
  ```mermaid
17
17
  flowchart TB
18
18
  user["用户<br/>(外环校准者)"] -- "对话校准 / 确认完成" --> sa
19
- sa["session agent<br/>(编排者 / 控制器)"]
19
+ sa["session agent<br/>(控制器)"]
20
20
  subgraph loop["内环:一轮(workflows/round.js)"]
21
21
  ta["task agent<br/>执行者"] --> va["verify agent<br/>独立测量"] --> ga["triage agent<br/>四分拣"]
22
22
  end
@@ -27,7 +27,7 @@ flowchart TB
27
27
 
28
28
  | 角色 | 职责 |
29
29
  |---|---|
30
- | **session agent** | 编排者(控制器):识别用户输入、维护 goal 板、启动/收取/取消轮、向用户转述;从不亲自执行任务 |
30
+ | **session agent** | 控制器:识别用户输入、维护 goal 板、启动/收取/取消轮、向用户转述;从不亲自执行任务 |
31
31
  | **controller** | `src/controller.ts` 的轮间状态机:`startRound`/`foldRound`/`runRound`,按轮推进 subgoal(task → verify → triage),维护审计日志 |
32
32
  | **task agent** | 执行器:无对话历史的独立 sub agent,面向工作委托书(brief)实际执行,产出结构化交付报告 |
33
33
  | **verify agent** | 测量仪器:独立于执行者验收,产出结构化 verdict `{met, criteria, gaps, goalItselfQuestionable}` |
@@ -46,7 +46,7 @@ flowchart TB
46
46
 
47
47
  **热切换(v0.8.10)**:`hardGate` 已是 **volatile** 字段——插件页设置卡片上有实时开关。保存即把新值提交进运行实例,插件随即卸载或重装门禁监听(装配摘工具 + 分发拒绝 + 审批透传),**即时生效,无需重启、无需插件重载**。从关到开的热启按当前 `gateMode` 解析结果接线(显式配置优先,否则结构性 allowlist)。其余门字段(`gateMode`/`gatedTools`/`gateAllowlist`/`gateAllowlistExtra`/`gateApprovalRelay`)仍是非 volatile 配置文档字段,请在 profile 配置文档中维护(保存触发插件重载)。部署注意:本构建首次上线时运行中宿主仍是旧代码——重启宿主一次加载新代码,此后开关即热生效(`lib/client.js` 只需刷新浏览器)。
48
48
 
49
- **生效语义(v0.8.6,默认安全)**:`hardGate` 开启且未配置 `gateMode` 时,门按 **`allowlist`** 运行——顶层 session agent 只保留读 + 对话 + 展示 + goal/编排动词(内置 `DEFAULT_GATE_ALLOWLIST`,可经 `gateAllowlist`/`gateAllowlistExtra` 扩展),所有变更类工具在装配期被**移除**,并在**调用分发层被拒绝**。之所以改为这个默认:旧的 `denylist` 默认是个陷阱——它只隐藏 `gatedTools` 的三个名字(bash/edit/write),`bsk_*`、`taskboard_*`、`schedule_*`、`farm_*`、`uno_*`、`ppt_*`、`config_restore` 以及整个变更工具长尾全都仍可调用,名为硬门、实为半门。`hardGate: false` 与以前完全一致(不装门);显式配置的 `gateMode` 一律原样尊重;schema 不再给 `gateMode` 设默认值,因此「未设置」与「显式 denylist」可以被区分。
49
+ **生效语义(v0.8.6,默认安全)**:`hardGate` 开启且未配置 `gateMode` 时,门按 **`allowlist`** 运行——顶层 session agent 只保留读 + 对话 + 展示 + goal/控制动词(内置 `DEFAULT_GATE_ALLOWLIST`,可经 `gateAllowlist`/`gateAllowlistExtra` 扩展),所有变更类工具在装配期被**移除**,并在**调用分发层被拒绝**。之所以改为这个默认:旧的 `denylist` 默认是个陷阱——它只隐藏 `gatedTools` 的三个名字(bash/edit/write),`bsk_*`、`taskboard_*`、`schedule_*`、`farm_*`、`uno_*`、`ppt_*`、`config_restore` 以及整个变更工具长尾全都仍可调用,名为硬门、实为半门。`hardGate: false` 与以前完全一致(不装门);显式配置的 `gateMode` 一律原样尊重;schema 不再给 `gateMode` 设默认值,因此「未设置」与「显式 denylist」可以被区分。
50
50
 
51
51
  **显式 `gateMode: 'denylist'`(残留风险,需主动选择)**:只移除 `gatedTools` 中的工具名(默认 bash/edit/write)。顶层 agent 至少仍可使用:`bsk_*`(浏览器自动化,含 `bsk_session` 启停)、`taskboard_*` 写操作(create/update/move/comment/checklist/delete)、`schedule_*` 写操作、`farm_*` 服务生命周期(start/restart/stop/register/unregister)、`uno_*` 与 `ppt_*` 生成器、`config_restore`、`subagent`/team 派生、`knowledge_save`、`job_kill`——凡未被列入 `gatedTools` 的都在,且这个集合随每个新装插件增长,denylist 永远不可能完备。只有在你清醒接受该风险时才显式选择它(例如确实要 session agent 管理服务),并把需要的变更工具逐项补进 `gatedTools`。该模式下注入的 HARD GATE 提示会如实说明「只摘 `gatedTools`」的半门立场,不再假称所有改世界的工具都结构性不可用。
52
52
 
@@ -91,11 +91,17 @@ goal 一经存在不再原地修改。`revise` 是唯一的改目标动词(必
91
91
  同一 session 支持多个并发 goal(插件自有 goal 板;dsh-goal 的单槽降级为焦点 goal)。客户端 `lib/client.js` 提供两个会话页签,经插件自有 session 投影 `cyberneticsGoals` 实时推送、就地更新:
92
92
 
93
93
  - **「目标 / Goal」**:本会话全部 goal(标题 / 状态徽标 / 第 N 轮 / 当前 subgoal / depth、executor 小标签 / 被取代与修订来源标注),按「未完成在前、已完成在后」两段排序;空板回退 dsh-goal 单 goal 渲染。
94
+ - **子智能体会话的「目标」(v0.18)**:workflow 的 task/verify/triage 子会话与 Agent Teams teammate 自身没有 goal 板,页签改为显示**整个 session 的目标页**——沿父链找到最近一个有非空 `cyberneticsGoals` 板的祖先会话(通常就是主会话)并渲染其卡片。父链来自实时的会话列表(`ctx.sessions.list`):优先 `byId[id].parentId`,缺失时回退该会话所属 catalog 的 `subagentCatalog` 条目,带 visited 防环与 16 跳上限。该模式下还会标出「自己的目标项」——`agents` 包含本会话 id 的 goal(孙代则取最近的命中祖先):左侧 accent 边框 +「当前」小徽标;首次打开时把它滚动到视野中央(`scrollIntoView({ block: 'center' })`,去重标记落在滚动容器上,板子实时更新不会反复滚动;根会话自板永不自动滚)。「关联智能体」chips 在祖先板下以**板会话**作为 `parentSessionId`(child 的真实父会话)并从该会话的 catalog 读活性,因此随时可跳转到该板会话的其它 agents。自板非空的会话渲染与旧版完全一致(无提示行、无滚动),老宿主没有会话列表存储时保持 v0.18 之前的空态回退。仅客户端改动:宿主重启 + 刷新浏览器后生效。
95
+ - **「对话」主页入口(v0.18.1 引入,v0.18.3 起为回主页语义)**:「目标」「控制」两个页签内容的第一行都有一个页签样式的「对话」按钮。「回到主页」式导航:无论在哪个 agent 的「目标 / 控制」页签,点击都回到**主 agent(根会话)**的对话视图——而不是 v0.18.2 那样切到**当前子 agent** 自己的 chat。在根会话上一步直达(`openView('chat', 'cybernetics-home')`);在子 agent 上是两段式 SPA 跳转,全部走宿主公开 seam:`uiWorkspace.openSession(rootId)` 选中根会话(target = `SessionId | SubagentAddress`),同时向根会话投一条一次性、约 4 s 有效的 home 请求;根会话自己的「目标 / 控制」视图挂载时消费该请求(读即删)并调用它自己的 `openView('chat', …)`。不刷新页面、不改 URL、不重开会话、不新增服务端 API,也不写 `dsh.conversation.*` 存储。宿主不给视图组件递 `openView` owner prop 时入口不渲染;没有 `uiWorkspace` 时点击降级为当前会话的视图切换。已知限制:若根会话当下显示的是别的视图(chat 本身或第三方页签),我们的消费点不会挂载、请求 4 s 后过期——你仍会被切到根会话,只是视图停在根原本的状态。仅客户端改动:宿主重启 + 刷新浏览器后生效。
94
96
  - **「控制 / Control」**:递归展示本会话全部 agent(主控 + subagent catalog)的运行状态与各自 goal 视图,顶部汇总运行中数量,并列出每个 goal 的执行者行(状态 + 标题 + executor 临时/持久 + 持久 teammate 名称 + 轮次),与「目标」页签同一数据源。
95
97
 
98
+ ### 控制模式可视化指示(v0.10.0)
99
+
100
+ 每个会话头部常驻一枚徽标,一眼可见当前控制模式生效态:**绿点 +「开」** = 生效中(本会话 `/cybernetics on` 显式 override,或继承全局 `allGoalsMode`),**灰点 +「关」** = 生效关闭,**暗「…」** = 投影/配置尚未加载(加载态而非错误态)。tooltip 注明生效来源(「本会话显式设置」vs「继承全局配置」)。点击徽标即切换——走的正是斜杠菜单同款 `/cybernetics on|off` 命令通道(宿主 `commands.execute`,不新增服务端 API),写入的是同一条 durable 会话事件、投影实时回流。控制模式生效时,输入框上方显示一条细提示条说明下一次输入的去向,文案随 volatile `inputPolicy` 变化(insert →「新输入将作为外环控制即时插入当前回合」;queue →「新输入将排队为下一轮输入」)。数据联接:会话投影 `cyberneticsMode`({override: true|false|null})+ ConfigForm 里的全局 volatile `allGoalsMode`,客户端按与服务端 `resolveMode`(src/sessionMode.ts)一致的规则就地联接——投影只在会话事件时重发布,全局 volatile 翻转无法抵达服务端算好的生效值,而两个输入在客户端都是响应式的。文案双语(zh/en)。仅客户端改动:宿主重启(客户端模块图启动时构建)+ 刷新浏览器后生效。
101
+
96
102
  ### 插件页配置卡片
97
103
 
98
- `src/index.ts` 导出 schemastery `Config` schema,它决定宿主设置传输层服务与接受哪些字段(仅 `.volatile()` 字段);卡片 UI 本身由 `lib/client.js` 绘制,注册 `plugins.item` / `plugins.bundle.config` / `plugins.row.config` 三个席位(bundle 详情页与组件详情页都能配置)。**这张卡片是本插件设置在 GUI 中的唯一 surface**:宿主「插件」页对插件配置完全交给 slot 卡片渲染、自身一个字段都不画,@deepseek-ai/dsh-client-ui-settings 只提供 `configForms` 服务(传输层)而非通用字段渲染器——因此 v0.8.1 起卡片渲染全部 volatile 字段的可编辑控件(v0.8.4 起共六个):全目标模式开关、task/verify/triage agent 模型文本框、子目标轮数上限与并行轮数上限数字框。`roles` 表**不在卡片上**(v0.8.4):在 GUI 里手工编辑 JSON 对象对用户不友善,roles 改为非 volatile——面板不再服务也不接受写入——改经 profile 配置 yaml 直接配置(见下文「roles 高级配置」小节)。volatile 字段保存即时生效(loader 直接提交进运行实例,不 remount);非 volatile 字段保存触发插件行 remount(需宿主重载)。写入落点是用户 patch 行(profile `cordis.patch.yml` 的 `{id, name, config}` 行)。
104
+ `src/index.ts` 导出 schemastery `Config` schema,它决定宿主设置传输层服务与接受哪些字段(仅 `.volatile()` 字段);卡片 UI 本身由 `lib/client.js` 绘制,注册 `plugins.item` / `plugins.bundle.config` / `plugins.row.config` 三个席位(bundle 详情页与组件详情页都能配置)。**这张卡片是本插件设置在 GUI 中的唯一 surface**:宿主「插件」页对插件配置完全交给 slot 卡片渲染、自身一个字段都不画,@deepseek-ai/dsh-client-ui-settings 只提供 `configForms` 服务(传输层)而非通用字段渲染器——因此 v0.8.1 起卡片渲染全部 volatile 字段的可编辑控件(v0.17 起共 16 个):全目标模式开关、硬门开关、**智能选择 Agent 配置开关**、**预置方案下拉框**及其推荐请求模型字段、task/verify/triage 各自的「模型 + 思考强度」配对分组、子目标轮数上限与并行轮数上限数字框。`roles` 表**不在卡片上**(v0.8.4):在 GUI 里手工编辑 JSON 对象对用户不友善,roles 改为非 volatile——面板不再服务也不接受写入——改经 profile 配置 yaml 直接配置(见下文「roles 高级配置」小节)。volatile 字段保存即时生效(loader 直接提交进运行实例,不 remount);非 volatile 字段保存触发插件行 remount(需宿主重载)。写入落点是用户 patch 行(profile `cordis.patch.yml` 的 `{id, name, config}` 行)。
99
105
 
100
106
  ### agent 模型选择
101
107
 
@@ -113,6 +119,62 @@ goal 一经存在不再原地修改。`revise` 是唯一的改目标动词(必
113
119
 
114
120
  - **调用级回退(v0.8)**:为某角色设定的模型无法正常调用时——限额/quota 耗尽、网络不可达、provider 报错等**可用性失败**——不再让整个 goal 轮失败:`workflows/round.js` 撤销该角色的 provider/model 覆盖,以 **session agent** 的模型配置重试该角色的子 agent 一次。判定信号来自 workflow runtime(`@deepseek-ai/dsh-workflow-ptc`)`agent()` hook 的错误类型/消息:抛出的 `WorkflowError`(`AGENT_START` 恒算;`AGENT_RESULT`/无 code 错误需消息命中 quota/鉴权/5xx/网络/provider 模式),或返回 `null`(runtime 把子 run `stopReason ≠ "completed"` —— LLM 调用中途失败最常见的形态——压成 null 且不透传原因;仅当配置了覆盖时重试)。**逻辑失败永不换模型**:task agent 正常交付 `done=false` 报告、verify 返回未达成 verdict 都原样流转;非可用性错误(schema/option/cap 守卫错误)原样上抛。每次回退记录进轮结果的 `fallbacks` 数组(`{role, from, trigger, reason}`),narrate 进 `workflow/log`,随 `.cybernetics/round-*.json` 审计日志落盘,并追加进 `cybernetics` 工具结果的决策 note。配置语义向后兼容:留空 = 继承 session agent(无回退必要);已配置 = 优先用配置模型,可用性失败时回退。`workflows/round.js` 改动热生效。
115
121
 
122
+ ### 智能选择 Agent 配置(`smartAgentModelSelection`,v0.16,默认 false)
123
+
124
+ 二元开关(volatile,设置卡片布尔开关,保存后**下一轮 goal** 生效):开启后,**每轮启动时**(launch 侧,早于任何 task/verify/triage 子 agent 的创建)按本轮任务内容(brief → subgoal → objective)做一次**规则启发式分类**,并从宿主**实时可用模型目录**为每个角色自动选择 provider/model。目标不是给困难任务预警,而是**让简单任务更快、更省资源地完成**(删文件、git 提交这类轮不再占用顶配模型),困难任务仍落顶配。规则与选择逻辑全部在 `src/modelSelection.ts`(纯函数,离线可测)。
125
+
126
+ - **模型目录来源**:宿主根级 `llm` 服务的 `listProviders()` / `listModels(provider)`——与设置卡片下拉同一份实时目录(`buildModelCatalog` 的数据源),**绝不硬编码模型名**;provider 级失败隔离跳过(与宿主同策略)。软探测:服务缺失 / 属性读取被 Cordis 守卫拦截 / 传输抛错 → 目录视为不可用。
127
+ - **档位推导(generic 信号,非白名单)**:模型 id/名称/描述里的**参数量 token**(`8b` ≤12 → 轻量,`30b` ≤40 → 中档,`70b` >40 → 顶配)优先于**命名 token**(mini/flash/lite/… → 轻量;opus/ultra/plus/reasoner/… → 顶配);无信号 → 中档(仅写作类会选中)。**session agent 当前路由 = 顶配参照**(requestHeader 最新选择优先,options 兜底,与 dsh-subagent 的继承解析一致)。
128
+ - **分类 → 分配规则**(优先级 hard > writing > trivial > standard;standard **完全不介入**——本功能的价值在省简单任务的钱,不在扰动常规任务):
129
+
130
+ | 分类 | 命中形态(示例) | task | verify | triage |
131
+ |---|---|---|---|---|
132
+ | trivial | 删除文件并验证不存在 / git commit·push / 重命名 / typo / 格式化 | 目录中第一个轻量档 | 同 task(存在性检查无需高强度) | 同 task(轻量) |
133
+ | writing | 写文档/README/报告/翻译/润色 | 目录中声明写作特长的模型(高>中>轻),没有则继承 | **继承 session 模型**(最强评审) | 轻量档 |
134
+ | hard | Rust 等不常见语言 / 证明非平凡数学命题 / 并发运行时·编译器·共识算法·密码学 / 从零实现引擎 | 独立于 session 路由的顶配档;没有独立顶配 → 继承(session 即顶配) | 同 task | 轻量档(分拣是轻活) |
135
+ | standard | 其余一切 | 继承 | 继承 | 继承 |
136
+
137
+ - **合并优先级(逐角色)**:折叠后的**显式配置**(roles 表 + 设置面板平面字段,provider/model 任一存在)**恒优先**,智能选择只填补没有显式 provider/model 的行;只钉了思考强度档位(effort-only)的行**不换路由**(档位钉在继承路由上,改路由会失配)。全部未配置时智能选择全权决定;选定模型恰为 session 路由时也保持继承(等效路由 + 保留继承档位)。
138
+ - **persistent executor 显式跳过**:`spawnTeammate` 不接受模型覆盖——persistent 轮的 task 行审计为 `skipped` 并写明原因(绝不静默失效);verify/triage 在 full 持久轮仍走 round.js 照常可选。
139
+ - **quick 轮**:只有 task 行(verify/triage 不存在,审计也只产 task 一行)。
140
+ - **回退语义**:目录不可用/为空、该分类无合适模型(如部署里没有比 session 更轻的模型)、分类器异常——一律**静默回退现有继承行为**,不报错、不阻断,且每行决策(selected / kept-explicit / kept-inherit / skipped)连同理由写入审计。
141
+ - **审计**:每次选择的输入摘要(字符数 + 前 100 字符)、分类结果、目录规模、逐角色决策(含 picked 路由与理由)随轮记录落盘——`.cybernetics/round-<goalId>-<N>.json` 的 `smartSelection` 字段(仿 v0.8 `fallbacks` 先例;round.js 从 args 原样回传,persistent 路径经 executor.ts 同步携带),`collect`/`round` 的工具结果同样可见。示例:
142
+
143
+ ```json
144
+ {
145
+ "smartSelection": {
146
+ "enabled": true,
147
+ "taskClass": "trivial",
148
+ "inputChars": 17,
149
+ "inputPreview": "删除 build 目录并验证文件不存在",
150
+ "catalogSize": 2,
151
+ "catalogStatus": "ok",
152
+ "roles": [
153
+ { "role": "default", "decision": "selected",
154
+ "reason": "任务分类 trivial:为 task 角色选择 p-light/qwen-8b(light 档,目录 2 个模型中按执行档位规则选取)",
155
+ "picked": { "provider": "p-light", "model": "qwen-8b" } },
156
+ { "role": "verify", "decision": "selected", "reason": "…(同上,验证强度规则)", "picked": { "provider": "p-light", "model": "qwen-8b" } },
157
+ { "role": "triage", "decision": "selected", "reason": "…(分拣轻量规则)", "picked": { "provider": "p-light", "model": "qwen-8b" } }
158
+ ]
159
+ }
160
+ }
161
+ ```
162
+
163
+ - **v0.16 不自动分配思考强度**:档位 id 是模型相关的,自动分配需逐选中模型 `resolveModelInfo` 探测(每轮每角色一次适配器 I/O + 额外失败面);智能选择本轮只选路由,档位留给模型默认或现有平面 effort 字段(显式模型行 + effort 字段可精确固定)。
164
+ - **为什么用规则启发式而不是一次廉价模型调用**:宿主确有裸补全面(`ctx.llm.prepareCall().stream()`,session-title-llm 模式),但要求引入 `@deepseek-ai/dsh-llm` 运行时依赖(本插件刻意零依赖)、每轮启动前多一次 provider 往返与整条失败面。确定性关键词分类零成本、离线可测,误分类可经审计发现并由显式配置覆盖——显式配置恒优先是安全底线。
165
+
166
+ ### 预置方案(`presetPlan`,v0.17)
167
+
168
+ 设置卡片新增「预置方案」下拉,共 10 个选项:**根据现有配置自动推荐** + 九家**厂商方案**(OpenAI / Claude / Gemini / DeepSeek / Kimi / Qwen / 火山 Coding Plan / Minimax / GLM,全量数据见 `src/modelPresets.ts`)。选择写入 volatile `presetPlan` 字段;插件在 `loader/volatile-update` 上对每次变更**一次性(one-shot)解析**,把结果应用到 task/verify/triage 的扁平模型 + 思考强度字段(即下方各角色分组渲染的字段),结果记录写入 volatile `presetRecommendation`,在下拉框下方渲染。
169
+
170
+ **作用范围(用户裁定,最高优先)**:**session agent 不在本功能范围内**——session 模型由用户随时自行调整,因此选择方案与自动推荐只写三个子 agent 角色的配置:`src/modelPresets.ts` 的 `ALLOWED_OP_PATHS` 把可写字段结构性地钉死为六个扁平子 agent 字段(三个模型字段 + 三个思考强度字段);推荐 prompt 明令输出只允许 task/verify/triage 三个键、禁止 session 键(解析器对模型万一直出的 session 键丢弃并显式注明);各厂商的 session 推荐仅作参考数据保留在注册表(`variant.session`),卡片上常驻「session 由用户自行管理,方案不应用此项」提示,永远不会进入任何写入。
171
+
172
+ - **厂商方案(数据驱动,`status: 'ready'`)**:每厂商含逐角色模型 + 思考强度 token(异构:OpenAI/Minimax 五档 low/medium/high/xhigh/max、DeepSeek thinking 开关、其余模型默认)、可升级档、可用性 fallback、逐角色 rationale、厂商备注(⚠️ 注意事项——如 Qwen 的「verify 绝不用 Turbo(幻觉共振)」、Kimi 的避开退役 K2、DeepSeek 的「成本不应成为降级 verify 的理由」)与**多变体**(Claude 常规/高要求,Qwen A/B/C,DeepSeek 仅本家/verify 外置,Kimi 长期组合/最高验收质量,火山 A/B,GLM 标准组合/重活命名角色)。选择方案应用其**默认变体**(第一个);结果记录披露所应用变体并列出其余变体。厂商原生模型 id 逐字应用——部署目录中不存在的 id 会显示卡片不可用警告,轮内可用性 fallback(v0.8)保证轮次存活。
173
+ - **自动推荐(v0.18.2 起 = session agent 交办,用户裁定)**:选择 auto 时插件**不发起任何独立 LLM 调用、不新起任何子 agent**(v0.17.2 及之前的「一次一次性 LLM 调用 + 推荐所用模型配置项」已删除——另起一次调用等于把上下文重复提交一遍,时间与经济性都不合理)。现在的流程:插件探测实时目录(`probeCatalogOptions`,与智能选择同源),把推荐交办单——内嵌目录(唯一可选来源)、跨厂商通用原则(verify 档位不低于 task;verify/task 跨代际跨家族保持独立;verify 不能过度自信;省成本先压 task/triage、verify 最后动且成本不是降级 verify 的理由;triage 轻量)、当前角色配置与全部厂商方案(仅作参考)——经插件注册的 `preset_recommend` 工具交给 session agent,并在卡片写入 `pending` 记录、向在线 session agent 投递一条 steer/followup 提醒。session agent 在**自己的上下文中**读取目录、给出 task/verify/triage 的答案,再次调用该工具提交;插件只负责校验(逐字命中目录;逐角色 `reasoningEffort` 经 `ctx.llm.resolveModelInfo` 校验,不支持时降为模型默认并注明)与写入。session agent 未作答时卡片保持 pending;答案不可用(未命中目录/不可解析/零选择)时降级为带原因的 `degraded` 记录并回执给 session agent 修正重交——不应用任何更改、不阻塞任何轮次。该工具在硬门禁 allowlist 下与 cybernetics 工具一同保留。
174
+ - **选择是一次性的**:重复选择同一项不会再次触发(处理器按最后处理值守卫;跨重启持久化的值不会在启动时重放)。要重复应用某项,先切「(未选择)」再选。
175
+ - **`/cybernetics use [方案id]`(v0.22.0,命令通道)**:不开设置卡片,直接把角色组合切换为任意预置方案。无参数时命令回执纯文本清单——全部内置方案 id + 已配置的自定义方案(`custom:<id>`)——并提示用户再次输入 `/cybernetics use <id>` 确认;带 id 时复用与下拉框完全相同的应用管线(`handlePresetSelection`:六字段白名单 ops + 结果记录),回执与卡片保持一致;方案 id 大小写保留(自定义 id 逐字匹配)。与下拉框不同,重复输入同一条 `use <id>` 会**再次应用**(命令路径同步维护一次性守卫 `lastHandledPresetPlan` 并刻意绕过它),无需先切「(未选择)」。此方式应用的方案为 profile 全局生效——影响所有并发会话。
176
+ - **编辑器侧方案选择器(v0.24.1,popupSelect)**:选择器只在你表达 `use` 意图之后出现。在 / 菜单点选 `\cybernetics` 只把编辑器文本补全为 `/cybernetics`(不弹层——继续输入 `use ...` 即可);对裸 `/cybernetics` 回车弹出可搜索的方案选择器(12 个内置 id 与已配置的 `custom:<id>` 条目分组列出,输入即模糊过滤,当前方案高亮);键入 `\cybernetics use` 回车则先落地纯文本清单,随后同一弹层在其上弹出。点选一项即经同一条命令通道提交 `/cybernetics use <id>`(校验/应用/回执仍以服务端为唯一权威,错误就地显示在弹层)。实现走宿主为「带可选值的命令」提供的原生机制(`commandUi.decorate` + 公开的 `popupFor`/`command/executed` 面,与官方 `/model` 选择器同一模式);宿主无 commandUi 服务时保持 v0.22 纯文本行为,键入路径 `/cybernetics use <id>` 完全不动。说明:草稿内的参数级补全(输入 `use ` 后就地弹下拉)在宿主协议下不可实现——斜杠触发扫描遇空格即止(v0.25.0 复核:宿主 composer 不对外发射任何草稿变更事件,四个 `slash/input-*` 作用域事件均为插件→shell 的 bail 请求通道,触发源 outcome 仅 `{claim}/{text}/{insert}` 三种,均无法在回车前感知 `/cybernetics use` 草稿)。**请输入 `/cybernetics use` 后按 Enter 唤起方案选择器**(无参回车先落地纯文本清单,弹层随即在其上出现)。
177
+
116
178
  ## 安装与使用
117
179
 
118
180
  ### 安装
@@ -148,7 +210,9 @@ Web 编辑器「插件」页的本插件卡片上直接配置——v0.8.1 起卡
148
210
  - **task/verify/triage agent 模型**(`taskAgentModel` / `verifyAgentModel` / `triageAgentModel`,volatile):下拉加持的文本框——从实时系统模型列表选择(显示 `providerName · provider/model`)或手工输入「provider/model」/裸模型名,回车或移开焦点即保存,留空继承 session agent(triage = 收敛决策角色,仅 full 轮启用)。配置值不在实时模型列表中时显示不可用警告(运行时回退到 session agent 模型)并附一键清除;
149
211
  - **递归深度 / 并行上限**(`roundsPerSubgoal` 整数 ≥1 / `maxConcurrentRounds` 整数 ≥0,volatile):数字框,回车或失焦保存,留空恢复默认(`unset` 写入);
150
212
  - **roles 高级配置**(`roles`,非 volatile):profile 配置 yaml 直接维护——见下方小节;
151
- - **硬编排门**(`hardGate`,v0.8.10 起 volatile):卡片布尔开关——装配期从顶层 agent 摘除写/执行工具,所有变更经 goal 循环完成;保存即时生效(门禁监听热装卸),无需重启;
213
+ - **硬控制门**(`hardGate`,v0.8.10 起 volatile):卡片布尔开关——装配期从顶层 agent 摘除写/执行工具,所有变更经 goal 循环完成;保存即时生效(门禁监听热装卸),无需重启;
214
+ - **智能选择 Agent 配置**(`smartAgentModelSelection`,v0.16 起 volatile):卡片布尔开关——每轮启动时按任务内容从实时模型目录为 task/verify/triage 自动选模型(简单任务轻量模型、困难任务顶配);显式配置的角色模型恒优先,选不到时保持继承并记审计;保存后下一轮 goal 生效——详见「Features → 智能选择 Agent 配置」小节的分类规则表;
215
+ - **预置方案**(`presetPlan` / `presetRecommendation`,v0.17 起 volatile;v0.18.2 起不再有推荐请求模型字段):卡片下拉(自动推荐 + 八家厂商方案)+ 结果记录——选择方案应用其默认变体的 task/verify/triage 模型与思考强度;「自动推荐」由 session agent 在自己的上下文中经 `preset_recommend` 工具作答(插件零独立 LLM 调用,卡片先显示 pending),作用范围仅三个子 agent 角色,session 配置永不被触碰——详见「Features → 预置方案」小节;
152
216
  - **其余门字段**(`gateMode`/`gatedTools`/`gateAllowlist`/`gateAllowlistExtra`/`gateApprovalRelay`,非 volatile):不进入卡片,请在 profile 配置文档中维护(保存触发插件重载)。`gateMode` 无默认值:未设置 + `hardGate: true` 按 `allowlist` 运行;显式 `denylist` 会被尊重,并带有上文记录的残留风险。
153
217
 
154
218
  ### roles 高级配置(profile yaml,agent 指导)
@@ -196,13 +260,13 @@ session agent 在会话内通过 `cybernetics` 工具(`toolName` 可配置)
196
260
  - **为什么「goal 是 agent 的思考产物」**:传统范式里人是控制器、模型是执行器,回路只存在于人的脑子里。让 session agent 自己把用户输入整理为持久 goal(用户通过对话校准 agent,agent 修订 goal),目标才成为系统里可跟踪的一等公民状态——反馈回路的第一块积木。
197
261
  - **为什么 append-only**:目标被悄悄改写,会摧毁「经 goal 跟踪 agent」的全部价值——页签上的目标不再是你当初设定的目标。身份与历史由 goal 链表达,正如 git 用不可变 commit 表达历史;每次修订的 reason + 审计行就是校准的显式变更记录。
198
262
  - **为什么 verify 独立于 task**:验证者若看执行者的叙述,会退化成「检查自述是否自洽」而非「检查事实是否满足目标」。测量仪器必须独立于被测对象——协议的核心不变量:**上下文是被配给的,不是被继承的**(task 只拿委托书、verify 只拿 goal + 报告 + 证据、triage 只拿 verdict,永不用 `context: 'fork'`)。持久的是身份和 goal 边界,不是会话历史。
199
- - **为什么硬门**:早期的「session agent 不亲自执行」只是提示里的劝告,模型看到琐碎任务就会内联干完。硬门在装配期物理移除执行工具,把劝告变成结构——编排者的全部产出是 goal 和 brief(信息),执行者的全部产出是对世界的改变(行动),可观测性与可控性的边界划在角色上,不依赖模型自觉。
263
+ - **为什么硬门**:早期的「session agent 不亲自执行」只是提示里的劝告,模型看到琐碎任务就会内联干完。硬门在装配期物理移除执行工具,把劝告变成结构——控制者的全部产出是 goal 和 brief(信息),执行者的全部产出是对世界的改变(行动),可观测性与可控性的边界划在角色上,不依赖模型自觉。
200
264
  - **振荡防御与升级路径**:每轮连续迭代上限是对「反馈延迟过大导致振荡」的工程化防御;triage 的 human 分拣是控制器为「不可自动处理的扰动」预留的升级路径;`goalItselfQuestionable` 在结构上区分「方向错了」和「执行得不好」。
201
265
 
202
266
  ## 与 DSH 现有机制的关系
203
267
 
204
268
  - **goal**:不替代——dsh-goal 仍是 durable setpoint 与续跑驱动;本插件是它的工作回路。
205
- - **workflow**:每轮就是一次 `ctx.workflowEngine.start()` run;不新增编排机制(构建在 `dsh-workflow` 之上)。
269
+ - **workflow**:每轮就是一次 `ctx.workflowEngine.start()` run;不新增独立控制机制(构建在 `dsh-workflow` 之上)。
206
270
  - **完成确认**:controller 永不自宣完成、不代改 goal phase;`met` 也只暂停等用户。
207
271
 
208
272
  ## 已知限制
@@ -217,7 +281,7 @@ session agent 在会话内通过 `cybernetics` 工具(`toolName` 可配置)
217
281
  | 版本 | 关键节点 |
218
282
  |---|---|
219
283
  | v0.2 | session-goal 模式 + 隐式触发 + 角色化 sub agent + 客户端 goal 页签 |
220
- | v0.3 | 多 goal 板 + 编排者协议 + 异步轮(start/collect/cancel/list)+ 委托书 brief |
284
+ | v0.3 | 多 goal 板 + 控制者协议 + 异步轮(start/collect/cancel/list)+ 委托书 brief |
221
285
  | v0.4 / v0.4.1 | 硬门;allowlist 门模式(封住变更工具长尾) |
222
286
  | v0.5 | 双档轮次 depth(quick / full) |
223
287
  | v0.6 | append-only goals、持久执行者、多 goal 投影、板持久化与 rehydrate、控制页签执行者行 |
package/cordis.patch.yml CHANGED
@@ -26,11 +26,11 @@
26
26
  # or the browser half is silently skipped (tabs disappear).
27
27
  name: '@mars.liu/dsh-cybernetics'
28
28
  config:
29
- # v0.4 hard gate: restrict top-level session agents to read/orchestration
29
+ # v0.4 hard gate: restrict top-level session agents to read/control
30
30
  # tools at prompt-assembly time — every CHANGE must go through a
31
31
  # cybernetics goal + task agent. Task/verify/triage subagents unaffected.
32
32
  hardGate: true
33
- # allowlist mode: keep ONLY reads + conversation + orchestration verbs
33
+ # allowlist mode: keep ONLY reads + conversation + control verbs
34
34
  # (built-in DEFAULT_GATE_ALLOWLIST); robust against the long tail of
35
35
  # mutation tools (farm_*, taskboard_*, knowledge_save, ...).
36
36
  gateMode: allowlist