dsh-advisor 0.1.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/LICENSE +21 -0
- package/README.i18n.yaml +7 -0
- package/README.md +303 -0
- package/README.zh.md +166 -0
- package/cordis.patch.yml +6 -0
- package/lib/advisor-runtime.d.ts +242 -0
- package/lib/advisor-runtime.js +662 -0
- package/lib/advisor-runtime.js.map +1 -0
- package/lib/client/advisor-card.d.ts +90 -0
- package/lib/client/advisor-store.d.ts +310 -0
- package/lib/client/index.d.ts +39 -0
- package/lib/client/locales.d.ts +40 -0
- package/lib/client.d.ts +1 -0
- package/lib/client.js +840 -0
- package/lib/commands.d.ts +136 -0
- package/lib/commands.js +185 -0
- package/lib/commands.js.map +1 -0
- package/lib/config.d.ts +74 -0
- package/lib/config.js +93 -0
- package/lib/config.js.map +1 -0
- package/lib/delivery.d.ts +129 -0
- package/lib/delivery.js +169 -0
- package/lib/delivery.js.map +1 -0
- package/lib/emission-guard.d.ts +99 -0
- package/lib/emission-guard.js +155 -0
- package/lib/emission-guard.js.map +1 -0
- package/lib/gateway.d.ts +116 -0
- package/lib/gateway.js +214 -0
- package/lib/gateway.js.map +1 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.js +485 -0
- package/lib/index.js.map +1 -0
- package/lib/kinds.d.ts +38 -0
- package/lib/kinds.js +24 -0
- package/lib/kinds.js.map +1 -0
- package/lib/prompts.d.ts +22 -0
- package/lib/prompts.js +38 -0
- package/lib/prompts.js.map +1 -0
- package/lib/settings.d.ts +96 -0
- package/lib/settings.js +141 -0
- package/lib/settings.js.map +1 -0
- package/lib/transcript.d.ts +257 -0
- package/lib/transcript.js +530 -0
- package/lib/transcript.js.map +1 -0
- package/package.json +90 -0
- package/scripts/build-client.mjs +268 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-external
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Bilingual-pair consistency record: the git blob hash of each side as of the
|
|
2
|
+
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
|
+
# editing either side, bring the other along and re-record with:
|
|
4
|
+
# git hash-object README.md
|
|
5
|
+
# git hash-object README.zh.md
|
|
6
|
+
README.md: 9c49bb1959b760bce4db030d92ca5efd57f0cbe7
|
|
7
|
+
README.zh.md: e4db627807e12277ab0e516b445ccb35138ba6a9
|
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# dsh-advisor
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
A standalone dsh plugin bundle porting the omp "advisor"
|
|
10
|
+
subsystem: a per-session reviewer model that observes the primary transcript,
|
|
11
|
+
reviews each stepped turn with an explicitly configured model (provider +
|
|
12
|
+
model are required), and injects severity-ranked advice (nit / concern /
|
|
13
|
+
blocker) back into the session — without polluting or recursively reviewing
|
|
14
|
+
itself.
|
|
15
|
+
|
|
16
|
+
Install with a single command (pnpm ≥ 10 needs one build-allow step — see [Install](#install)):
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
dsh plugin --profile web add github:btspoony/dsh-advisor # <name> = your profile name; pin a commit with #<sha>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Advisory only.** The advisor never approves or rejects the primary agent's
|
|
23
|
+
actions; it never issues commands as if it were the primary agent. Every
|
|
24
|
+
delivered message is self-described advisory content, and a misbehaving
|
|
25
|
+
reviewer is bounded end to end (emission guard, immuneTurns cooldown, failure
|
|
26
|
+
policy) so it can never stall or pollute the primary loop.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
### One-line git install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
dsh plugin --profile web add github:btspoony/dsh-advisor # <name> = your profile name; pin a commit with #<sha>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A git install fetches **sources, not built artifacts**, so the bundle builds
|
|
37
|
+
itself on install (`prepare` self-build). pnpm ≥ 10 blocks a git dependency's
|
|
38
|
+
`prepare` by default: the first
|
|
39
|
+
`add` fails with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, and pnpm prints the
|
|
40
|
+
exact package key — allow the build in the profile's `pnpm-workspace.yaml`
|
|
41
|
+
(`onlyBuiltDependencies: [dsh-advisor]`, or run `dsh plugin --profile web
|
|
42
|
+
approve-builds`), then re-run the `add`. Treat that allowance as permission to
|
|
43
|
+
execute the package's code on your machine at install time, and pin a commit
|
|
44
|
+
(`#<sha>`) so a later push cannot silently change what runs.
|
|
45
|
+
|
|
46
|
+
### Local directory install (recommended for development / verification)
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pnpm install # build the bundle (the prepare self-build)
|
|
50
|
+
dsh plugin --profile web add . # <name> = your profile name
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Verify
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
dsh --profile web --dump-config # shows a "# == dsh-advisor" layer with the advisor row
|
|
57
|
+
dsh --profile web
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Tarball install and uninstall are covered in [docs/install.md](docs/install.md).
|
|
61
|
+
|
|
62
|
+
## Config
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
The advisor is off by default. When enabled, `provider` and `model` are
|
|
67
|
+
**mandatory**: `enabled: true` without both is a hard gate — the advisor never
|
|
68
|
+
starts a model call and reports a disabled-with-reason status. Unknown config
|
|
69
|
+
keys are rejected.
|
|
70
|
+
|
|
71
|
+
Configuration composes across **three surfaces** (later layers override earlier
|
|
72
|
+
ones; every surface uses the same key set):
|
|
73
|
+
|
|
74
|
+
1. **Plugin-row config** — `$DSH_HOME/profiles/web/cordis.patch.yml`
|
|
75
|
+
(below). This is the composition base.
|
|
76
|
+
2. **dsh web Settings page — the "插件配置" (Plugin Configuration) page** —
|
|
77
|
+
the Advisor **card** (id `advisor`, rendered after the upstream bash /
|
|
78
|
+
agent-loop / web-search cards) with the enabled toggle, provider / model
|
|
79
|
+
selects restricted to system-configured providers and their models, and the
|
|
80
|
+
optional fields. Saving writes into the `advisor` settings namespace and
|
|
81
|
+
overrides the plugin-row config without editing it. Saving applies to new
|
|
82
|
+
sessions immediately — no restart (the runtime reads the composed value
|
|
83
|
+
live). Requires a current dsh web build whose shell declares the
|
|
84
|
+
`settings.plugin.item` card slot and loads packages that declare
|
|
85
|
+
`dsh.client`. The card reads and writes the namespace through the
|
|
86
|
+
**official `GatewayService` RPC channel** (`/api/advisor/get` +
|
|
87
|
+
`/api/advisor/set`, claimed by the host's typertGateway — the same
|
|
88
|
+
mechanism the dsh `goals` service uses), which is **not gated by the
|
|
89
|
+
settings exposure allowlist**: the in-process write
|
|
90
|
+
(`ctx.settings.update`) carries no exposed-namespace check. No host
|
|
91
|
+
patching is applied or required.
|
|
92
|
+
3. **`/advisor` command** — per-session and ephemeral: it flips a session
|
|
93
|
+
override, never the persisted config (see [Usage](#usage)).
|
|
94
|
+
|
|
95
|
+
Both persisted surfaces share the same hard gate: `enabled: true` with empty
|
|
96
|
+
`provider`/`model` never starts a model call (disabled-with-reason). The
|
|
97
|
+
Settings page additionally blocks saving while enabled with a required field
|
|
98
|
+
empty; the host-side gate stays the final line of defense on every path.
|
|
99
|
+
|
|
100
|
+
Plugin-row config:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
# profiles/web/cordis.patch.yml — the profile's user patch layer
|
|
104
|
+
- id: advisor
|
|
105
|
+
config:
|
|
106
|
+
enabled: true # master switch (default false)
|
|
107
|
+
provider: deepseek-official # REQUIRED when enabled
|
|
108
|
+
model: deepseek-v4-flash # REQUIRED when enabled
|
|
109
|
+
systemPrompt: "" # optional; "" = built-in reviewer prompt
|
|
110
|
+
immuneTurns: 3 # int ≥ 0, default 3 — cooldown after a delivered interrupt
|
|
111
|
+
maxDeltaMessages: 60 # int ≥ 0, default 60 — delta window; 0 = unbounded
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Key | Type / default | Meaning |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `enabled` | bool, `false` | Master switch. |
|
|
117
|
+
| `provider` | string, optional | Provider route. Required (non-empty) when `enabled: true`. |
|
|
118
|
+
| `model` | string, optional | Model id. Required (non-empty) when `enabled: true`. |
|
|
119
|
+
| `systemPrompt` | string, `""` | Overrides the built-in reviewer prompt (severity definitions + JSON-frame output contract). |
|
|
120
|
+
| `immuneTurns` | int ≥ 0, `3` | After a concern/blocker is actually steered, the next N stepped primary turns must complete before another interrupting note may steer; notes inside the window downgrade to inject. |
|
|
121
|
+
| `maxDeltaMessages` | int ≥ 0, `60` | Bounded advisor input window. Deltas beyond N are truncated with a `… <earlier messages omitted>` marker; `0` = unbounded. |
|
|
122
|
+
|
|
123
|
+
**Model capability & budget**: the advisor call runs with `reasoningEffort:
|
|
124
|
+
'off'` — sent only when the configured model's adapter declares that effort
|
|
125
|
+
(deepseek models do; any other model gets the option omitted automatically, so
|
|
126
|
+
non-reasoning providers keep working) — and a **5120-token** output cap (a
|
|
127
|
+
user-directed 20× supersession of the original 256). Extracted notes are
|
|
128
|
+
bounded (1000 chars) and the notice summary to 120 chars, so the raised budget
|
|
129
|
+
cannot translate into an unbounded injection into the primary session.
|
|
130
|
+
|
|
131
|
+
## Usage
|
|
132
|
+
|
|
133
|
+
Once installed and enabled, the advisor observes every session. Control it per
|
|
134
|
+
session with the `/advisor` command (available when a command registry is
|
|
135
|
+
composed):
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
/advisor toggle the advisor for this session
|
|
139
|
+
/advisor on enable the advisor for this session
|
|
140
|
+
/advisor off disable the advisor for this session
|
|
141
|
+
/advisor status show state, model, runtime status, pending count, last activity
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`/advisor on|off|toggle` are session-scoped and ephemeral: they flip a
|
|
145
|
+
per-session override, never the persisted config. Enabling a session whose
|
|
146
|
+
config lacks `provider`/`model` starts no model call — `/advisor status` (and
|
|
147
|
+
the `/advisor on` reply) shows the gate reason.
|
|
148
|
+
|
|
149
|
+
`/advisor on` is also the manual recovery path: a session advisor paused by a
|
|
150
|
+
quota/rate-limit (`quota_exhausted` — KD-5 has no auto-resume timer) resumes in
|
|
151
|
+
place, and a halted advisor (permanent model error, e.g. invalid credentials)
|
|
152
|
+
is rebuilt fresh for the session.
|
|
153
|
+
|
|
154
|
+
The advisor reviews on a dual-mode trigger, depending on the session shape:
|
|
155
|
+
|
|
156
|
+
- **Standard stepped sessions** — after each stepped primary turn that ends
|
|
157
|
+
normally (`completed`, `max-tokens`, or `error`), the advisor reviews the
|
|
158
|
+
incremental transcript delta.
|
|
159
|
+
- **Agentic / harness sessions** (never emit `turn/end`) — after each completed
|
|
160
|
+
agent reply round: when a new human input arrives (inbox-spliced input
|
|
161
|
+
included) after an unreviewed assistant increment, the advisor reviews that
|
|
162
|
+
increment.
|
|
163
|
+
|
|
164
|
+
Either way the advisor emits at most one note per review, ranked by severity:
|
|
165
|
+
|
|
166
|
+
- **nit** — a minor style, clarity, or quality suggestion; delivered via
|
|
167
|
+
`agent.inject` (non-waking, consumed at the next pre-step boundary).
|
|
168
|
+
- **concern** — a material risk or clearly better direction to weigh before
|
|
169
|
+
continuing; delivered via `agent.steer` (waking), subject to the
|
|
170
|
+
`immuneTurns` cooldown.
|
|
171
|
+
- **blocker** — continuing clearly wastes work (contradicts an explicit user
|
|
172
|
+
instruction, going in circles, fundamentally unsound); delivered via
|
|
173
|
+
`agent.steer`.
|
|
174
|
+
|
|
175
|
+
Injected advice appears in the session stream as a user-role message carrying
|
|
176
|
+
the advisor source kind and self-describing content, e.g.:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
[advisor:concern] extract the helper into a module and unit-test it
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The `[advisor:{severity}]` prefix is the only cue the primary model gets about
|
|
183
|
+
how to treat it — the primary system prompt never mentions advisories. Advisor
|
|
184
|
+
messages are excluded from later advisor deltas, so the advisor never reads
|
|
185
|
+
its own advice back.
|
|
186
|
+
|
|
187
|
+

|
|
188
|
+
|
|
189
|
+
## How it works
|
|
190
|
+
|
|
191
|
+
The plugin subscribes to `session/event`. Two triggers render an incremental
|
|
192
|
+
markdown delta of the primary transcript (own advisor messages excluded) and
|
|
193
|
+
queue it on a per-session runtime: after each stepped `turn/end` in standard
|
|
194
|
+
stepped sessions, and — in agentic/harness sessions that never emit `turn/end`
|
|
195
|
+
— when a new human input arrives (inbox-spliced input included) after an
|
|
196
|
+
unreviewed assistant increment, i.e. at each completed agent reply round. The
|
|
197
|
+
runtime calls a separately configured model via `ctx.llm.stream`, extracts one
|
|
198
|
+
`{note, severity}` from the JSON-framed reply, gates it through an emission
|
|
199
|
+
guard (normalize / dedupe / content-free suppression / one-note-per-update),
|
|
200
|
+
and routes it: nit → inject, concern/blocker → steer. The advisor call runs
|
|
201
|
+
with reasoning off and a 20x token budget so the JSON note is never starved by
|
|
202
|
+
reasoning output. Compaction and surface rewrites reset the observer, the
|
|
203
|
+
emission guard, and the immuneTurns latch
|
|
204
|
+
(KD-5); the drain is fully async with a bounded backlog, so a failing or
|
|
205
|
+
quota'd advisor can only drop its own backlog — never park the primary loop.
|
|
206
|
+
|
|
207
|
+
## Limitations & roadmap
|
|
208
|
+
|
|
209
|
+
The MVP deliberately drops full omp parity. Accepted gaps (tracked in the
|
|
210
|
+
harness iteration roadmap):
|
|
211
|
+
|
|
212
|
+
- **Single advisor per session** — no parallel advisor roster or WATCHDOG-style
|
|
213
|
+
file discovery (next iteration).
|
|
214
|
+
- **No advisor tools** — the reviewer is an independent model call only; it
|
|
215
|
+
cannot verify claims itself (next-next iteration).
|
|
216
|
+
- **No in-session advisor panel** — advice surfaces only as tagged injected
|
|
217
|
+
messages (the Advisor card on the "插件配置" settings page is a config
|
|
218
|
+
surface, not a session view; an in-session card is next-next iteration).
|
|
219
|
+
- **No transcript persistence or cost stats** — no resumable advisor history or
|
|
220
|
+
cost observability (next-next iteration).
|
|
221
|
+
- **No secret obfuscation of delta content** — secrets present in the transcript
|
|
222
|
+
can reach the advisor model; mitigate by configuring a trusted reviewer model.
|
|
223
|
+
- **No quarantine of unsafe advisor output** — a misbehaving note can carry
|
|
224
|
+
directive text; the JSON frame + validation + advisory-only framing
|
|
225
|
+
(`[advisor:…]`, "weigh, don't blindly obey") are the only mitigation, and the
|
|
226
|
+
note is delivered as-is into the primary transcript (roadmap).
|
|
227
|
+
- **No `syncBacklog` catch-up wait** — a far-behind advisor does not wait for
|
|
228
|
+
the primary loop; its backlog is bounded and dropped (never parks the
|
|
229
|
+
primary), so advisor notes may arrive after the next primary turn started
|
|
230
|
+
(roadmap: context-maintenance batch).
|
|
231
|
+
- **Bounded advisor context** — long-session full replays are truncated
|
|
232
|
+
(`maxDeltaMessages`), so the advisor may lose early context after compaction;
|
|
233
|
+
advisor context maintenance is roadmap (next-next iteration).
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
The bundle builds itself on install: `package.json` declares `"prepare": "node
|
|
238
|
+
scripts/setup-dsh-links.mjs && pnpm build"` (the dev-time link farm plus the
|
|
239
|
+
same build `prepack` runs), so any clone is
|
|
240
|
+
immediately buildable **once `DSH_HOME` points at a dsh home whose
|
|
241
|
+
`source/current` is a dsh source tree** (or `DSH_SOURCE_DIR` points at such a
|
|
242
|
+
tree directly). The private
|
|
243
|
+
`@deepseek-ai/dsh-*` runtime dependencies are **peerDependencies only**; at dev
|
|
244
|
+
time `scripts/setup-dsh-links.mjs` (wired into `prepare`, standalone as
|
|
245
|
+
`pnpm dsh:link`, verified with `pnpm dsh:link:check`) links the REAL packages
|
|
246
|
+
from that tree into `node_modules/@deepseek-ai/` — every `@deepseek-ai/*`
|
|
247
|
+
package the tree declares (tool CLIs with a `bin` are skipped: linking them
|
|
248
|
+
would make pnpm write their bins into the shared tree), a bin-less shim for
|
|
249
|
+
the in-box `cordis` framework, and the tree's own `react`/`react-dom` copies
|
|
250
|
+
(node resolution — including externalized CJS deps — must see ONE react
|
|
251
|
+
identity, the identity the real client packages use; the dsh profile
|
|
252
|
+
convention `nodeLinker=hoisted` lives in `pnpm-workspace.yaml` (pnpm 11+
|
|
253
|
+
ignores non-auth settings in `.npmrc`), so no `.pnpm` per-package dirs shadow
|
|
254
|
+
those links). The farm is idempotent, prunes stale entries, and fails with
|
|
255
|
+
guidance when the tree is missing or a peer cannot be linked.
|
|
256
|
+
`pnpm-workspace.yaml` also sets `autoInstallPeers: false` (dsh profile
|
|
257
|
+
convention): the private peers must never be fetched from the npm registry.
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
export DSH_HOME=~/.dsh # a dsh home with source/current (or set DSH_SOURCE_DIR)
|
|
261
|
+
pnpm install # registry deps + link farm (via prepare), no private-registry access
|
|
262
|
+
pnpm test # vitest (unit + the composed integration loop)
|
|
263
|
+
pnpm typecheck # tsc --noEmit (node) + tsc -p tsconfig.client.json --noEmit + tsc -p tsconfig.spec.json --noEmit
|
|
264
|
+
pnpm build # tsc -p tsconfig.build.json emit to lib/ + node scripts/build-client.mjs (client bundle)
|
|
265
|
+
pnpm pack # build + produce dsh-advisor-0.0.1.tgz
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
On Windows the link farm creates directory entries as junctions (no special
|
|
269
|
+
privileges), but the cordis shim's file entries use file symlinks, which need
|
|
270
|
+
[Developer Mode](https://learn.microsoft.com/windows/apps/get-started/enable-your-device-for-development)
|
|
271
|
+
(or an admin shell) — enable it before `pnpm install`. Windows has no
|
|
272
|
+
`HOME`, so the script falls back to `USERPROFILE` to resolve the dsh source
|
|
273
|
+
tree.
|
|
274
|
+
|
|
275
|
+
The in-box `cordis` framework is declared as the scoped peer
|
|
276
|
+
`@deepseek-ai/cordis: ^4.0.1-rc.1` (the range carries the exact publish tag —
|
|
277
|
+
a comparator prerelease such as `^4.0.0-rc.7` never matches the vendored
|
|
278
|
+
`4.0.1-rc.1` per the node-semver tuple rule); after install the link farm's
|
|
279
|
+
bin-less cordis shim at `node_modules/@deepseek-ai/cordis` answers the scoped
|
|
280
|
+
name and resolves to the vendored files, because the real packages type and
|
|
281
|
+
run against the vendored build and module identity requires dev-time
|
|
282
|
+
`import '@deepseek-ai/cordis'` to resolve to the same files. The public
|
|
283
|
+
devDependencies (`schemastery`, `react`, …) resolve from the npm registry as
|
|
284
|
+
usual.
|
|
285
|
+
|
|
286
|
+
`prepack` runs `pnpm build`; `prepare` runs the link farm and the build, so
|
|
287
|
+
`pnpm pack` runs the build twice (once per lifecycle) — the documented
|
|
288
|
+
tradeoff that keeps git-install builds working. There is no `postinstall`
|
|
289
|
+
step: already-built tarball installs skip the build entirely.
|
|
290
|
+
|
|
291
|
+
The integration test (`tests/integration.test.ts`) composes the plugin into a
|
|
292
|
+
real cordis context with a stub LLM adapter and drives the full
|
|
293
|
+
turn → delta → advisor call → inject/steer cycle.
|
|
294
|
+
|
|
295
|
+
## Documentation
|
|
296
|
+
|
|
297
|
+
| Doc | Content |
|
|
298
|
+
|---|---|
|
|
299
|
+
| [docs/install.md](docs/install.md) | full install guide: git / tarball / local-directory install, web Settings exposure, uninstall, `--dump-config` verification |
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# dsh-advisor
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
一个移植 omp「advisor」子系统的独立 dsh 插件组合包:一个按会话运行的评审模型,观察主会话 transcript,用显式配置的模型(provider 与 model 均为必填)评审每个已完成的 stepped turn,并把按严重度排序的建议(nit / concern / blocker)注入回会话 —— 不污染主循环,也不递归地评审自己。
|
|
10
|
+
|
|
11
|
+
一条命令即可安装(pnpm ≥ 10 需要一次构建放行步骤 —— 见[安装](#安装)):
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
dsh plugin --profile web add github:btspoony/dsh-advisor # <name> = 你的 profile 名;用 #<sha> 钉住 commit
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**仅作建议。** advisor 从不批准或否决主 agent 的动作,也绝不会像主 agent 那样发出命令。每条送达的消息都是自我描述的 advisory 内容;一个行为异常的评审者会被端到端约束(emission guard、immuneTurns 冷却、failure policy),因此它永远不会卡住或污染主循环。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
### 一条命令的 git 安装
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh plugin --profile web add github:btspoony/dsh-advisor # <name> = 你的 profile 名;用 #<sha> 钉住 commit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
git 安装拉取的是**源码而非构建产物**,因此组合包会在安装时自行构建(`prepare` 自建)。pnpm ≥ 10 默认拦截 git 依赖的 `prepare`:第一次 `add` 会报 `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`,pnpm 会打印出确切的包 key —— 在 profile 的 `pnpm-workspace.yaml` 中放行构建(`onlyBuiltDependencies: [dsh-advisor]`,或运行 `dsh plugin --profile web approve-builds`),然后重新执行 `add`。请把这次放行当作它本来的样子:允许该包的代码在安装时于你的机器上执行;并钉住 commit(`#<sha>`),这样之后的 push 无法悄悄改变实际运行的代码。
|
|
28
|
+
|
|
29
|
+
### 本地目录安装(推荐用于开发 / 验证)
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
pnpm install # 构建组合包(prepare 自建)
|
|
33
|
+
dsh plugin --profile web add . # <name> = 你的 profile 名
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 验证
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
dsh --profile web --dump-config # 显示带 advisor 配置行的 "# == dsh-advisor" 层
|
|
40
|
+
dsh --profile web
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
tarball 安装与卸载见 [docs/install.zh.md](docs/install.zh.md)。
|
|
44
|
+
|
|
45
|
+
## 配置
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
advisor 默认关闭。启用后,`provider` 与 `model` 为**必填**:`enabled: true` 而缺少两者之一是一个硬门禁 —— advisor 不会发起任何模型调用,并报告带原因的禁用状态(disabled-with-reason)。未知配置键会被拒绝。
|
|
50
|
+
|
|
51
|
+
配置在**三个配置面**之间合成(后一层覆盖前一层;各处使用同一组键):
|
|
52
|
+
|
|
53
|
+
1. **插件行 config** —— `$DSH_HOME/profiles/web/cordis.patch.yml`(见下)。这是合成 base。
|
|
54
|
+
2. **dsh web Settings 页 —— "插件配置"页** —— Advisor **卡片**(id `advisor`,渲染在三张上游卡片 bash / agent-loop / web-search 之后),含 enabled 开关、只列出系统内已配置 provider 及其模型的 provider/model 选择框与可选字段。保存写入 `advisor` settings namespace,覆盖插件行 config 而无需改动它。保存后新会话立即生效,无需重启(运行时 live 读取合成值)。需要当前版本的 dsh web 构建(其 web shell 声明了 `settings.plugin.item` 卡片 slot 并能加载 `dsh.client` 声明包)。卡片通过**官方 `GatewayService` RPC 通道**读写该命名空间(`/api/advisor/get` + `/api/advisor/set`,由宿主的 typertGateway claims——与 dsh 内建 `goals` 服务同一机制),该通道**不受 settings 暴露白名单门控**:进程内写入(`ctx.settings.update`)没有 exposed-namespace 检查。无需也不施加任何宿主补丁。
|
|
55
|
+
3. **`/advisor` 指令** —— 按会话且临时:翻转的是会话级 override,从不修改持久化配置(见[用法](#用法))。
|
|
56
|
+
|
|
57
|
+
两个持久化配置面共享同一个硬门禁:`enabled: true` 而 `provider`/`model` 为空时绝不发起模型调用(disabled-with-reason)。Settings 页还会在 enabled 且必填字段为空时阻止保存;宿主侧硬门禁始终是所有路径上的最后防线。
|
|
58
|
+
|
|
59
|
+
插件行配置:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
# profiles/web/cordis.patch.yml — the profile's user patch layer
|
|
63
|
+
- id: advisor
|
|
64
|
+
config:
|
|
65
|
+
enabled: true # master switch (default false)
|
|
66
|
+
provider: deepseek-official # REQUIRED when enabled
|
|
67
|
+
model: deepseek-v4-flash # REQUIRED when enabled
|
|
68
|
+
systemPrompt: "" # optional; "" = built-in reviewer prompt
|
|
69
|
+
immuneTurns: 3 # int ≥ 0, default 3 — cooldown after a delivered interrupt
|
|
70
|
+
maxDeltaMessages: 60 # int ≥ 0, default 60 — delta window; 0 = unbounded
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| 键 | 类型 / 默认值 | 含义 |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `enabled` | bool, `false` | 总开关。 |
|
|
76
|
+
| `provider` | string, optional | 供应商路由。`enabled: true` 时必须(非空)。 |
|
|
77
|
+
| `model` | string, optional | 模型 id。`enabled: true` 时必须(非空)。 |
|
|
78
|
+
| `systemPrompt` | string, `""` | 覆盖内置评审 prompt(严重度定义 + JSON-frame 输出契约)。 |
|
|
79
|
+
| `immuneTurns` | int ≥ 0, `3` | 实际 steer 过一次 concern/blocker 后,接下来 N 个完成的 stepped 主 turn 必须走完,另一条打断性 note 才可再次 steer;窗口内的 note 降级为 inject。 |
|
|
80
|
+
| `maxDeltaMessages` | int ≥ 0, `60` | 有界的 advisor 输入窗口。超过 N 的 delta 以 `… <earlier messages omitted>` 标记截断;`0` = 无上限。 |
|
|
81
|
+
|
|
82
|
+
**模型能力与预算**:advisor 调用以 `reasoningEffort: 'off'` 运行 —— 仅当所配置模型的 adapter 声明该档位时才发送(deepseek 模型声明;其他模型会自动省略该选项,因此非推理供应商照常工作)—— 并以 **5120 tokens** 作为输出上限(用户指示的 256 → 5120 的 20 倍超驰)。抽取出的 note 有界(1000 字符),notice summary 有界(120 字符),因此提高的预算不会变成注入主会话的无界内容。
|
|
83
|
+
|
|
84
|
+
## 用法
|
|
85
|
+
|
|
86
|
+
安装并启用后,advisor 观察每个会话。用 `/advisor` 指令按会话控制它(组合了 command registry 时可用):
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
/advisor toggle the advisor for this session
|
|
90
|
+
/advisor on enable the advisor for this session
|
|
91
|
+
/advisor off disable the advisor for this session
|
|
92
|
+
/advisor status show state, model, runtime status, pending count, last activity
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`/advisor on|off|toggle` 是会话级且临时的:它们翻转的是按会话的 override,从不修改持久化配置。启用一个 config 缺少 `provider`/`model` 的会话不会发起模型调用 —— `/advisor status`(以及 `/advisor on` 的回复)会显示门禁原因。
|
|
96
|
+
|
|
97
|
+
`/advisor on` 也是手动恢复路径:被 quota/rate-limit 暂停的会话 advisor(`quota_exhausted` —— KD-5 没有自动恢复定时器)会在原地恢复;被终止的 advisor(永久性模型错误,如凭据无效)会为该会话全新重建。
|
|
98
|
+
|
|
99
|
+
advisor 采用双模式触发,取决于会话形态:
|
|
100
|
+
|
|
101
|
+
- **标准 stepped 会话** —— 在每个正常结束(`completed`、`max-tokens` 或 `error`)的 stepped 主 turn 之后,评审增量 transcript delta。
|
|
102
|
+
- **agentic / harness 会话**(从不发出 `turn/end`)—— 在每个完成的 agent 回复轮次之后:当新的用户输入(含 inbox 拼接输入)在未评审的 assistant 增量之后到达时,评审该增量。
|
|
103
|
+
|
|
104
|
+
无论哪种模式,每次评审至多发出一条 note,按严重度排序:
|
|
105
|
+
|
|
106
|
+
- **nit** —— 轻微的样式、清晰度或质量建议;通过 `agent.inject` 送达(非唤醒,在下一个 pre-step 边界消费)。
|
|
107
|
+
- **concern** —— 在继续之前值得权衡的重大风险或明显更优的方向;通过 `agent.steer` 送达(唤醒),受 `immuneTurns` 冷却约束。
|
|
108
|
+
- **blocker** —— 继续下去明显是在浪费工作(与显式用户指令矛盾、原地打转、根本性不可行);通过 `agent.steer` 送达。
|
|
109
|
+
|
|
110
|
+
注入的建议以 user-role 消息出现在会话流中,携带 advisor source kind 与自我描述的内容,例如:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
[advisor:concern] extract the helper into a module and unit-test it
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`[advisor:{severity}]` 前缀是主模型获得的关于如何对待它的唯一线索 —— 主 system prompt 从不提及 advisory。advisor 消息会被排除在此后的 advisor delta 之外,因此 advisor 永远不会读回自己的建议。
|
|
117
|
+
|
|
118
|
+

|
|
119
|
+
|
|
120
|
+
## 工作原理
|
|
121
|
+
|
|
122
|
+
插件订阅 `session/event`。两种触发方式会把主 transcript 的增量 markdown delta(排除 advisor 自己的消息)渲染出来并放入按会话的 runtime 队列:标准 stepped 会话在每个 stepped `turn/end` 之后;agentic/harness 会话(从不发出 `turn/end`)则在新的用户输入(含 inbox 拼接输入)于未评审的 assistant 增量之后到达时 —— 即每个完成的 agent 回复轮次。runtime 通过 `ctx.llm.stream` 调用一个单独配置的模型,从 JSON-framed 回复中提取一条 `{note, severity}`,经过 emission guard 门禁(normalize / dedupe / content-free 抑制 / 每次更新至多一条 note),然后路由:nit → inject,concern/blocker → steer。advisor 调用以关闭推理(reasoning off)和 20 倍 token 预算运行,因此 JSON note 绝不会被推理输出挤占。compaction 与 surface 重写会重置 observer、emission guard 与 immuneTurns latch(KD-5);drain 完全异步且 backlog 有界,因此失败或 quota 耗尽的 advisor 只能丢弃自己的 backlog —— 永远不会卡住主循环。
|
|
123
|
+
|
|
124
|
+
## 限制与路线图
|
|
125
|
+
|
|
126
|
+
MVP 有意放弃与 omp 的完整对等。已接受的差距(在 harness 迭代路线图中跟踪):
|
|
127
|
+
|
|
128
|
+
- **每个会话一个 advisor** —— 无并行 advisor roster 或 WATCHDOG 式文件发现(下一迭代)。
|
|
129
|
+
- **无 advisor tools** —— 评审者只是一个独立的模型调用;它无法自行核验主张(下下迭代)。
|
|
130
|
+
- **无会话内 advisor 面板** —— 建议仅以带标签的注入消息呈现("插件配置"设置页上的 Advisor 卡片是配置面,不是会话内视图;会话内卡片为下下迭代)。
|
|
131
|
+
- **无 transcript 持久化或成本统计** —— 无可恢复的 advisor 历史或成本可观测性(下下迭代)。
|
|
132
|
+
- **无 delta 内容密钥混淆** —— transcript 中出现的 secrets 可能到达 advisor 模型;请通过配置可信的评审模型来缓解。
|
|
133
|
+
- **不隔离不安全的 advisor 输出** —— 行为异常的 note 可能携带指令性文本;JSON frame + 校验 + advisory-only 框架(`[advisor:…]`、"weigh, don't blindly obey")是仅有的缓解手段,且 note 会原样送达主 transcript(路线图)。
|
|
134
|
+
- **无 `syncBacklog` 追赶等待** —— 落后很多的 advisor 不会等待主循环;其 backlog 有界且会被丢弃(永远不会卡住主循环),因此 advisor note 可能在下一次主 turn 开始之后才到达(路线图:context-maintenance batch)。
|
|
135
|
+
- **advisor 上下文有界** —— 长会话的完整重放会被截断(`maxDeltaMessages`),因此 compaction 后 advisor 可能丢失早期上下文;advisor 上下文维护在路线图中(下下迭代)。
|
|
136
|
+
|
|
137
|
+
## 开发
|
|
138
|
+
|
|
139
|
+
组合包在安装时自行构建:`package.json` 声明了 `"prepare": "node scripts/setup-dsh-links.mjs && pnpm build"`(开发期链接农场、与 `prepack` 相同的构建),因此任何克隆在 **`DSH_HOME` 指向一个含 `source/current` 的 dsh home(或 `DSH_SOURCE_DIR` 直接指向一个 dsh 源码树)** 后立即可构建。私有的 `@deepseek-ai/dsh-*` 运行时依赖**只声明为 peerDependencies**;开发期由 `scripts/setup-dsh-links.mjs`(挂在 `prepare` 上、独立命令为 `pnpm dsh:link`、用 `pnpm dsh:link:check` 校验)把该树里的**真实包**链接进 `node_modules/@deepseek-ai/` —— 树声明的每个 `@deepseek-ai/*` 包(声明 `bin` 的工具 CLI 会被跳过:链接它们会让 pnpm 向共享树写入 bin)、无 bin 的内置 `cordis` 框架 shim、以及树自带的 `react`/`react-dom` 副本(node 解析 —— 包括外部化的 CJS 依赖 —— 必须看到同一个 react 身份,即真实 client 包所用的身份;dsh profile 约定 `nodeLinker=hoisted` 放在 `pnpm-workspace.yaml`(pnpm 11+ 忽略 `.npmrc` 中的非认证设置),避免 `.pnpm` 逐包目录遮蔽这些链接)。农场幂等、会清理陈旧条目,并在树缺失或 peer 无法链接时给出明确指引。`pnpm-workspace.yaml` 还设了 `autoInstallPeers: false`(dsh profile 约定):私有 peer 绝不能从 npm registry 获取。
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
export DSH_HOME=~/.dsh # 含 source/current 的 dsh home(或直接设置 DSH_SOURCE_DIR)
|
|
143
|
+
pnpm install # registry deps + 链接农场(经 prepare),无需访问私有 registry
|
|
144
|
+
pnpm test # vitest (unit + the composed integration loop)
|
|
145
|
+
pnpm typecheck # tsc --noEmit (node) + tsc -p tsconfig.client.json --noEmit + tsc -p tsconfig.spec.json --noEmit
|
|
146
|
+
pnpm build # tsc -p tsconfig.build.json emit to lib/ + node scripts/build-client.mjs (client bundle)
|
|
147
|
+
pnpm pack # build + produce dsh-advisor-0.0.1.tgz
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Windows 上链接农场的目录条目以 junction 创建(无需特权),但 cordis shim 的文件条目使用文件符号链接,需要开启[开发者模式](https://learn.microsoft.com/windows/apps/get-started/enable-your-device-for-development)(或以管理员 shell 运行)——请先开启再执行 `pnpm install`。Windows 没有 `HOME`,脚本回退到 `USERPROFILE` 解析 dsh 源码树。
|
|
151
|
+
|
|
152
|
+
内置 `cordis` 框架声明为 scoped peer `@deepseek-ai/cordis: ^4.0.1-rc.1`(范围必须带精确的发布 tag —— 带 prerelease 的 comparator 只匹配同 `[major, minor, patch]` tuple,`^4.0.0-rc.7` 永远不匹配 vendored 的 `4.0.1-rc.1`);安装后链接农场的无 bin cordis shim 位于 `node_modules/@deepseek-ai/cordis`,以 scoped 名应答并解析到 vendored 文件,因为真实包是对着 vendored 构建类型化/运行的,模块身份要求开发期的 `import '@deepseek-ai/cordis'` 解析到同一份文件。其余公开 devDependencies(`schemastery`、`react` 等)照常从 npm registry 解析。
|
|
153
|
+
|
|
154
|
+
`prepack` 运行 `pnpm build`;`prepare` 运行链接农场与构建,因此 `pnpm pack` 会构建两次(每个生命周期一次)——这是为保持 git 安装可构建而接受的取舍。没有 `postinstall` 步骤:tarball 安装已带构建产物,完全跳过构建。
|
|
155
|
+
|
|
156
|
+
集成测试(`tests/integration.test.ts`)把插件组合进一个带 stub LLM adapter 的真实 cordis 上下文,驱动完整的 turn → delta → advisor call → inject/steer 循环。
|
|
157
|
+
|
|
158
|
+
## 文档
|
|
159
|
+
|
|
160
|
+
| 文档 | 内容 |
|
|
161
|
+
|---|---|
|
|
162
|
+
| [docs/install.zh.md](docs/install.zh.md) | 完整安装指南:git / tarball / 本地目录安装、web Settings 暴露、卸载、`--dump-config` 验证 |
|
|
163
|
+
|
|
164
|
+
## 许可证
|
|
165
|
+
|
|
166
|
+
MIT
|
package/cordis.patch.yml
ADDED