dsh-auto-review 0.6.0 → 0.7.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/CHANGELOG.md +15 -0
- package/README.es.md +47 -0
- package/README.hi.md +47 -0
- package/README.md +48 -1
- package/README.pt.md +47 -0
- package/README.zh.md +47 -0
- package/bin/dsh-auto-review-mcp.mjs +19 -0
- package/lib/client.js +11 -5
- package/lib/client.js.map +1 -1
- package/lib/eval-cli.js +6 -0
- package/lib/index.js +246 -5
- package/lib/mcp.js +720 -0
- package/lib/types/cache.d.ts +90 -0
- package/lib/types/cache.d.ts.map +1 -0
- package/lib/types/cache.js +183 -0
- package/lib/types/cache.js.map +1 -0
- package/lib/types/client/ReviewPanel.d.ts +5 -5
- package/lib/types/client/ReviewPanel.d.ts.map +1 -1
- package/lib/types/client/ReviewPanel.js +6 -6
- package/lib/types/client/ReviewPanel.js.map +1 -1
- package/lib/types/client/locales.d.ts +1 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +2 -0
- package/lib/types/client/locales.js.map +1 -1
- package/lib/types/config.d.ts +10 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +10 -0
- package/lib/types/config.js.map +1 -1
- package/lib/types/events.d.ts +8 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js.map +1 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/mcp/index.d.ts +11 -0
- package/lib/types/mcp/index.d.ts.map +1 -0
- package/lib/types/mcp/index.js +9 -0
- package/lib/types/mcp/index.js.map +1 -0
- package/lib/types/mcp/server.d.ts +44 -0
- package/lib/types/mcp/server.d.ts.map +1 -0
- package/lib/types/mcp/server.js +146 -0
- package/lib/types/mcp/server.js.map +1 -0
- package/lib/types/mcp/standalone.d.ts +72 -0
- package/lib/types/mcp/standalone.d.ts.map +1 -0
- package/lib/types/mcp/standalone.js +162 -0
- package/lib/types/mcp/standalone.js.map +1 -0
- package/lib/types/projection-types.d.ts +3 -0
- package/lib/types/projection-types.d.ts.map +1 -1
- package/lib/types/projection.d.ts +2 -0
- package/lib/types/projection.d.ts.map +1 -1
- package/lib/types/projection.js +8 -3
- package/lib/types/projection.js.map +1 -1
- package/lib/types/runtime.d.ts +14 -0
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +50 -2
- package/lib/types/runtime.js.map +1 -1
- package/package.json +17 -12
- package/src/cache.ts +199 -0
- package/src/client/ReviewPanel.tsx +9 -5
- package/src/client/locales.ts +2 -0
- package/src/config.ts +20 -0
- package/src/events.ts +8 -0
- package/src/index.ts +1 -0
- package/src/mcp/index.ts +23 -0
- package/src/mcp/server.ts +165 -0
- package/src/mcp/standalone.ts +195 -0
- package/src/projection-types.ts +3 -0
- package/src/projection.ts +8 -3
- package/src/runtime.ts +50 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `dsh-auto-review` are documented here. The repo is pre-release; versions follow the DeepSeek Harness `0.1.0-rc.x` target runtime and bump on every behavior change.
|
|
4
4
|
|
|
5
|
+
## [0.7.0] — 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **stdio MCP server export** (`dsh-auto-review/mcp` + the `dsh-auto-review-mcp` bin): a standalone stdio JSON-RPC 2.0 / MCP server (newline-delimited JSON, one object per line) exposing two tools — `review_action` (deterministic deny on a matched never-rule or fail-closed, otherwise replay a cached verdict for an identical fingerprint) and `cache_stats` (cache counters + TTL). The new `src/mcp/` modules own only protocol dispatch; every decision lives in the standalone reviewer.
|
|
10
|
+
- **Same-fingerprint verdict cache with TTL**: a repeated, identical tool action reuses its recent reviewer verdict instead of paying another second-model round-trip. The fingerprint is a SHA-256 digest of the tool name plus canonicalized call arguments (volatile keys stripped, keys sorted); only the digest is stored, so plaintext arguments never enter the cache. New config knobs control TTL and cacheability, `cache_stats` reports hits/stores/live size, and the web panel shows the cache state.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Renovate is enabled via the shared `dsh-plugin-kit` preset, and the five-language READMEs credit community contributors. No plugin behavior changed.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Packed smoke install now resolves the next-tagged rc line: the `@deepseek-ai/dsh-*` peer floor is raised to `0.1.1-rc.2` (the peer ranges already cover it), so `pnpm pack` plus the smoke install no longer fails to resolve peers.
|
|
19
|
+
|
|
5
20
|
## [0.6.0] — 2026-08-23
|
|
6
21
|
|
|
7
22
|
### Added
|
package/README.es.md
CHANGED
|
@@ -95,6 +95,8 @@ Todas las opciones son campos Schemastery `Config` (modificables desde cordis.ym
|
|
|
95
95
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | Los veredictos `allow` por encima de `maxAutoAllow` delegan o deniegan |
|
|
96
96
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Disyuntor de rechazos |
|
|
97
97
|
| `overrideTtlMs` | `300000` | Cuánto dura una anulación de `/auto-review approve` |
|
|
98
|
+
| `verdictCacheTtlMs` | `60000` | Reutiliza un veredicto reciente para una huella `herramienta + argumentos` idéntica; `0` desactiva la caché |
|
|
99
|
+
| `verdictCacheMaxEntries` | `256` | Máximo de huellas en caché antes de desalojar la más antigua |
|
|
98
100
|
| `language` | `en` | Idioma de la UI de la salida del comando `/auto-review` (`en` \| `zh`) |
|
|
99
101
|
| `allowUnmarkedAudit` | `false` | Fuerza la auditoría del registro de sesión en hosts que descartan el marcador `ignorable` (peligroso: los eventos sin marcar hacen las sesiones irrecuperables en otros hosts); por defecto se detecta y se degrada |
|
|
100
102
|
|
|
@@ -251,6 +253,49 @@ Puerta de CI: el proceso sale con 0 solo cuando todos los casos de todas las sui
|
|
|
251
253
|
|
|
252
254
|
`dsh-eval` difiere de [openai/codex-research](https://github.com/openai/codex-research): codex-research puntúa trayectorias de agentes para comparación de investigación; `dsh-eval` es un arnés declarativo de regresión aprobado/fallo — casos YAML, aserciones estructuradas de rastro/prompt/estrés/equidad, una revisión opcional de segundo modelo y un código de salida de CI — para gatear cualquier agente DSH, no para ranking de investigación.
|
|
253
255
|
|
|
256
|
+
## Servidor MCP (independiente)
|
|
257
|
+
|
|
258
|
+
`dsh-auto-review` también incluye un **servidor MCP** stdio (`dsh-auto-review-mcp`) para que clientes MCP externos (Claude, Codex, …) consuman una ruta de revisión determinista sin el harness. Habla JSON-RPC 2.0 sobre JSON delimitado por saltos de línea (NDJSON): un objeto JSON por línea, sin tramado `Content-Length`.
|
|
259
|
+
|
|
260
|
+
**Límite.** El revisor completo necesita el seam de subagentes del harness y un segundo modelo, que un proceso stdio separado no puede alcanzar. Por lo tanto, el servidor independiente es **reglas deterministas + caché, sin revisión por modelo**:
|
|
261
|
+
|
|
262
|
+
- `review_action` reutiliza la caché de veredictos por huella (`src/cache.ts`) y la resolución de reglas de riesgo / políticas de herramientas (`src/config.ts`): una regla `never` → `deny`; un acierto de caché sobre una huella idéntica de `tool + arguments` reproduce ese veredicto; todo lo demás (`ai` necesita modelo, `human` necesita a un humano) → `deny` fail-closed con `reason: "standalone path, no model"`. Nunca permite una acción que un modelo no haya permitido ya.
|
|
263
|
+
- `cache_stats` informa los contadores de aciertos/almacenamiento y el estado del TTL.
|
|
264
|
+
|
|
265
|
+
| Herramienta | Propósito |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `review_action` | `{tool, args?, reason?}` → `{decision, reason, riskLevel}` — denegación determinista / reproducción de caché |
|
|
268
|
+
| `cache_stats` | `{}` → `{hits, stores, size, ttlMs, enabled}` |
|
|
269
|
+
|
|
270
|
+
Ejecución directa:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
# las reglas de riesgo vienen de variables de entorno
|
|
274
|
+
export DSH_AUTO_REVIEW_RISK_RULES='[{"pattern":"rm -rf","policy":"never","field":"arguments"}]'
|
|
275
|
+
node bin/dsh-auto-review-mcp.mjs
|
|
276
|
+
# o, tras npm install: npx dsh-auto-review-mcp
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Configuración por entorno: `DSH_AUTO_REVIEW_RISK_RULES` (array JSON de `{pattern, policy, field?}`), `DSH_AUTO_REVIEW_TOOLS_POLICY` (JSON `{default?, overrides?}`), `DSH_AUTO_REVIEW_CACHE_TTL_MS`, `DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES`.
|
|
280
|
+
|
|
281
|
+
Ejemplo para Claude Desktop (`claude_desktop_config.json`):
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"mcpServers": {
|
|
286
|
+
"dsh-auto-review": {
|
|
287
|
+
"command": "npx",
|
|
288
|
+
"args": ["-y", "dsh-auto-review-mcp"],
|
|
289
|
+
"env": {
|
|
290
|
+
"DSH_AUTO_REVIEW_RISK_RULES": "[{\"pattern\":\"rm -rf\",\"policy\":\"never\",\"field\":\"arguments\"}]"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
El servidor es de solo lectura y determinista: sin red, sin modelo, sin escrituras.
|
|
298
|
+
|
|
254
299
|
## Permisos y datos
|
|
255
300
|
|
|
256
301
|
- **Permisos**: el manifiesto del workshop declara `session:append`, `approval:answer`, `subagent:spawn`, `command:register` y `tools:observe`.
|
|
@@ -304,6 +349,8 @@ Estructura del repositorio: `src/index.ts` (contrato del plugin) · `src/config.
|
|
|
304
349
|
## Contribuidores
|
|
305
350
|
|
|
306
351
|
- [@PerryLink](https://github.com/PerryLink) — creador y mantenedor: el answerer de aprobación, el subagente revisor, la política de riesgo y el disyuntor, el panel de revisión por proyección de sesión, el compañero invariant, dsh-eval y la documentación en cinco idiomas.
|
|
352
|
+
- [@weipeng1999](https://github.com/weipeng1999) — propuso el enrutamiento independiente de proveedor/modelo del revisor ([#11](https://github.com/PerryLink/dsh-auto-review/issues/11), [discusión #12](https://github.com/PerryLink/dsh-auto-review/discussions/12)), que se publicó como `reviewerProvider` / `reviewerModel`.
|
|
353
|
+
- [@alexchenzl](https://github.com/alexchenzl) — incluyó el plugin en el directorio de plugins de DSH ([#10](https://github.com/PerryLink/dsh-auto-review/issues/10)).
|
|
307
354
|
|
|
308
355
|
## Familia de plugins DSH de PerryLink
|
|
309
356
|
|
package/README.hi.md
CHANGED
|
@@ -95,6 +95,8 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
95
95
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | `maxAutoAllow` से ऊपर के `allow` निर्णय सौंपते या अस्वीकार करते हैं |
|
|
96
96
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | अस्वीकृति सर्किट ब्रेकर |
|
|
97
97
|
| `overrideTtlMs` | `300000` | `/auto-review approve` ओवरराइड कितनी देर उपयोगी रहता है |
|
|
98
|
+
| `verdictCacheTtlMs` | `60000` | समान `टूल + तर्क` फ़िंगरप्रिंट के लिए हालिया निर्णय पुनः उपयोग करें; `0` कैश बंद करता है |
|
|
99
|
+
| `verdictCacheMaxEntries` | `256` | सबसे पुराने को निकालने से पहले कैश किए गए फ़िंगरप्रिंट की अधिकतम संख्या |
|
|
98
100
|
| `language` | `en` | `/auto-review` कमांड आउटपुट की UI भाषा (`en` \| `zh`) |
|
|
99
101
|
| `allowUnmarkedAudit` | `false` | `ignorable` मार्कर छोड़ने वाले होस्ट पर सत्र-लॉग ऑडिट बलपूर्वक चालू करें (खतरनाक: बिना मार्कर वाली घटनाएँ अन्य होस्ट पर सत्र अप्राप्य बनाती हैं); डिफ़ॉल्ट पहचान-और-डिग्रेड है |
|
|
100
102
|
|
|
@@ -251,6 +253,49 @@ CI द्वार: प्रक्रिया केवल तभी 0 से
|
|
|
251
253
|
|
|
252
254
|
`dsh-eval` [openai/codex-research](https://github.com/openai/codex-research) से भिन्न है: codex-research अनुसंधान तुलना के लिए एजेंट ट्रैजेक्टरी को स्कोर करता है; `dsh-eval` एक घोषणात्मक पास/फेल रिग्रेशन हार्नेस है — YAML केस, संरचित ट्रेस/प्रॉम्प्ट/स्ट्रेस/निष्पक्षता दावे, एक वैकल्पिक द्वितीय-मॉडल समीक्षा और एक CI एग्जिट कोड — किसी भी DSH एजेंट को गेट करने के लिए, अनुसंधान रैंकिंग के लिए नहीं।
|
|
253
255
|
|
|
256
|
+
## MCP सर्वर (स्टैंडअलोन)
|
|
257
|
+
|
|
258
|
+
`dsh-auto-review` एक stdio **MCP सर्वर** (`dsh-auto-review-mcp`) भी देता है ताकि बाहरी MCP क्लाइंट (Claude, Codex, …) बिना harness के एक निर्धारक समीक्षा पथ का उपयोग कर सकें। यह newline-delimited JSON (NDJSON) पर JSON-RPC 2.0 बोलता है — प्रति पंक्ति एक JSON ऑब्जेक्ट, कोई `Content-Length` फ़्रेमिंग नहीं।
|
|
259
|
+
|
|
260
|
+
**सीमा।** पूर्ण reviewer को harness के subagent seam और दूसरे मॉडल की आवश्यकता होती है, जिसे एक अलग stdio प्रक्रिया नहीं पहुँच सकती। इसलिए स्टैंडअलोन सर्वर **निर्धारक नियम + कैश, कोई मॉडल समीक्षा नहीं** है:
|
|
261
|
+
|
|
262
|
+
- `review_action` समान-फ़िंगरप्रिंट वर्डिक्ट कैश (`src/cache.ts`) और जोखिम-नियम / टूल-नीति समाधान (`src/config.ts`) का पुनः उपयोग करता है: एक `never` नियम → `deny`; समान `tool + arguments` फ़िंगरप्रिंट पर कैश हिट उस वर्डिक्ट को दोहराता है; बाकी सब (`ai` को मॉडल चाहिए, `human` को मानव चाहिए) → fail-closed `deny` जिसका reason `"standalone path, no model"` है। यह कभी भी ऐसी कार्रवाई की अनुमति नहीं देता जिसे किसी मॉडल ने पहले से अनुमति न दी हो।
|
|
263
|
+
- `cache_stats` हिट/स्टोर गणना और TTL स्थिति की सूचना देता है।
|
|
264
|
+
|
|
265
|
+
| टूल | उद्देश्य |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `review_action` | `{tool, args?, reason?}` → `{decision, reason, riskLevel}` — निर्धारक अस्वीकृति / कैश पुनरावृत्ति |
|
|
268
|
+
| `cache_stats` | `{}` → `{hits, stores, size, ttlMs, enabled}` |
|
|
269
|
+
|
|
270
|
+
सीधे चलाएँ:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
# जोखिम नियम पर्यावरण चरों से आते हैं
|
|
274
|
+
export DSH_AUTO_REVIEW_RISK_RULES='[{"pattern":"rm -rf","policy":"never","field":"arguments"}]'
|
|
275
|
+
node bin/dsh-auto-review-mcp.mjs
|
|
276
|
+
# या, npm install के बाद: npx dsh-auto-review-mcp
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
पर्यावरण विन्यास: `DSH_AUTO_REVIEW_RISK_RULES` (`{pattern, policy, field?}` की JSON सरणी), `DSH_AUTO_REVIEW_TOOLS_POLICY` (JSON `{default?, overrides?}`), `DSH_AUTO_REVIEW_CACHE_TTL_MS`, `DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES`।
|
|
280
|
+
|
|
281
|
+
Claude Desktop (`claude_desktop_config.json`) उदाहरण:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"mcpServers": {
|
|
286
|
+
"dsh-auto-review": {
|
|
287
|
+
"command": "npx",
|
|
288
|
+
"args": ["-y", "dsh-auto-review-mcp"],
|
|
289
|
+
"env": {
|
|
290
|
+
"DSH_AUTO_REVIEW_RISK_RULES": "[{\"pattern\":\"rm -rf\",\"policy\":\"never\",\"field\":\"arguments\"}]"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
सर्वर केवल-पठन और निर्धारक है: कोई नेटवर्क नहीं, कोई मॉडल नहीं, कोई लेखन नहीं।
|
|
298
|
+
|
|
254
299
|
## अनुमतियाँ और डेटा
|
|
255
300
|
|
|
256
301
|
- **अनुमतियाँ**: workshop मेनिफ़ेस्ट `session:append`, `approval:answer`, `subagent:spawn`, `command:register` और `tools:observe` घोषित करता है।
|
|
@@ -304,6 +349,8 @@ pnpm pack # प्रकाशित tarball
|
|
|
304
349
|
## योगदानकर्ता
|
|
305
350
|
|
|
306
351
|
- [@PerryLink](https://github.com/PerryLink) — निर्माता और अनुरक्षक: अनुमोदन answerer, समीक्षक उप-एजेंट, जोखिम नीति और सर्किट ब्रेकर, सत्र-प्रोजेक्शन समीक्षा पैनल, invariant साथी, dsh-eval, और पाँच-भाषा दस्तावेज़।
|
|
352
|
+
- [@weipeng1999](https://github.com/weipeng1999) — समीक्षक के स्वतंत्र प्रदाता/मॉडल रूटिंग का प्रस्ताव रखा ([#11](https://github.com/PerryLink/dsh-auto-review/issues/11), [चर्चा #12](https://github.com/PerryLink/dsh-auto-review/discussions/12)), जो `reviewerProvider` / `reviewerModel` के रूप में जारी हुआ।
|
|
353
|
+
- [@alexchenzl](https://github.com/alexchenzl) — DSH प्लगइन निर्देशिका में प्लगइन को सूचीबद्ध किया ([#10](https://github.com/PerryLink/dsh-auto-review/issues/10))।
|
|
307
354
|
|
|
308
355
|
## PerryLink DSH प्लगइन परिवार
|
|
309
356
|
|
package/README.md
CHANGED
|
@@ -96,6 +96,8 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
96
96
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | `allow` verdicts above `maxAutoAllow` delegate or deny |
|
|
97
97
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Rejection circuit breaker |
|
|
98
98
|
| `overrideTtlMs` | `300000` | How long a `/auto-review approve` override stays usable |
|
|
99
|
+
| `verdictCacheTtlMs` | `60000` | Reuse a recent verdict for an identical `tool + arguments` fingerprint; `0` disables the cache |
|
|
100
|
+
| `verdictCacheMaxEntries` | `256` | Maximum cached fingerprints before oldest-eviction |
|
|
99
101
|
| `language` | `en` | UI language of the `/auto-review` command output (`en` \| `zh`) |
|
|
100
102
|
| `allowUnmarkedAudit` | `false` | Force session-log audit on hosts that drop the `ignorable` marker (dangerous: unmarked events make sessions unresumable elsewhere); default is detect-and-degrade |
|
|
101
103
|
|
|
@@ -142,7 +144,7 @@ Example (annotated full form: `fixtures/config/config-full.yaml`):
|
|
|
142
144
|
|
|
143
145
|
## Web review panel
|
|
144
146
|
|
|
145
|
-
In the Web GUI (web profile), the package contributes a session-header action (**AI Review**) that opens a panel with the session's auto-review state: the switch with on/off buttons (they execute `/auto-review on|off`), both per-turn budgets, cumulative statistics (including hard-disable rejections), the circuit trip, the recent verdicts, and one-shot **approve** buttons for recent denials (they execute `/auto-review approve [n]`).
|
|
147
|
+
In the Web GUI (web profile), the package contributes a session-header action (**AI Review**) that opens a panel with the session's auto-review state: the switch with on/off buttons (they execute `/auto-review on|off`), both per-turn budgets, cumulative statistics (including hard-disable rejections and cache hits), the circuit trip, the recent verdicts, and one-shot **approve** buttons for recent denials (they execute `/auto-review approve [n]`).
|
|
146
148
|
|
|
147
149
|
How it is wired:
|
|
148
150
|
|
|
@@ -252,6 +254,49 @@ CI gate: the process exits 0 only when every case of every suite passed — fail
|
|
|
252
254
|
|
|
253
255
|
`dsh-eval` differs from [openai/codex-research](https://github.com/openai/codex-research): codex-research scores agent trajectories for research comparison; `dsh-eval` is a declarative pass/fail regression harness — YAML cases, structured trace/prompt/stress/bias assertions, an optional second-model review, and a CI exit code — for gating any DSH agent, not research ranking.
|
|
254
256
|
|
|
257
|
+
## MCP server (standalone)
|
|
258
|
+
|
|
259
|
+
`dsh-auto-review` also ships a stdio **MCP server** (`dsh-auto-review-mcp`) so external MCP clients (Claude, Codex, …) can consume a deterministic review path without a harness. It speaks JSON-RPC 2.0 over newline-delimited JSON (NDJSON) — one JSON object per line, no `Content-Length` framing.
|
|
260
|
+
|
|
261
|
+
**Boundary.** The full reviewer needs the harness subagent seam and a second model, which a separate stdio process cannot reach. The standalone server is therefore **deterministic rules + cache, no model review**:
|
|
262
|
+
|
|
263
|
+
- `review_action` reuses the same-fingerprint verdict cache (`src/cache.ts`) and the risk-rule / tool-policy resolution (`src/config.ts`): a `never` rule → `deny`; a cache hit on an identical `tool + arguments` fingerprint replays that verdict; anything else (`ai` needs a model, `human` needs a human) → fail-closed `deny` with `reason: "standalone path, no model"`. It never allows an action a model did not already allow.
|
|
264
|
+
- `cache_stats` reports hit/store counts and the TTL status.
|
|
265
|
+
|
|
266
|
+
| Tool | Purpose |
|
|
267
|
+
|---|---|
|
|
268
|
+
| `review_action` | `{tool, args?, reason?}` → `{decision, reason, riskLevel}` — deterministic deny / cache replay |
|
|
269
|
+
| `cache_stats` | `{}` → `{hits, stores, size, ttlMs, enabled}` |
|
|
270
|
+
|
|
271
|
+
Run it directly:
|
|
272
|
+
|
|
273
|
+
```sh
|
|
274
|
+
# risk rules come from environment variables
|
|
275
|
+
export DSH_AUTO_REVIEW_RISK_RULES='[{"pattern":"rm -rf","policy":"never","field":"arguments"}]'
|
|
276
|
+
node bin/dsh-auto-review-mcp.mjs
|
|
277
|
+
# or, after npm install: npx dsh-auto-review-mcp
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Environment config: `DSH_AUTO_REVIEW_RISK_RULES` (JSON array of `{pattern, policy, field?}`), `DSH_AUTO_REVIEW_TOOLS_POLICY` (JSON `{default?, overrides?}`), `DSH_AUTO_REVIEW_CACHE_TTL_MS`, `DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES`.
|
|
281
|
+
|
|
282
|
+
Claude Desktop (`claude_desktop_config.json`) example:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"mcpServers": {
|
|
287
|
+
"dsh-auto-review": {
|
|
288
|
+
"command": "npx",
|
|
289
|
+
"args": ["-y", "dsh-auto-review-mcp"],
|
|
290
|
+
"env": {
|
|
291
|
+
"DSH_AUTO_REVIEW_RISK_RULES": "[{\"pattern\":\"rm -rf\",\"policy\":\"never\",\"field\":\"arguments\"}]"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The server is read-only and deterministic: no network, no model, no writes.
|
|
299
|
+
|
|
255
300
|
## Permissions & data
|
|
256
301
|
|
|
257
302
|
- **Permissions**: the workshop manifest declares `session:append`, `approval:answer`, `subagent:spawn`, `command:register`, and `tools:observe`.
|
|
@@ -305,6 +350,8 @@ Repository layout: `src/index.ts` (plugin contract) · `src/config.ts` (Schemast
|
|
|
305
350
|
## Contributors
|
|
306
351
|
|
|
307
352
|
- [@PerryLink](https://github.com/PerryLink) — creator and maintainer: the approval answerer, the reviewer subagent, risk policy and circuit breaker, the session-projection review panel, the invariant companion, dsh-eval, and the five-language docs.
|
|
353
|
+
- [@weipeng1999](https://github.com/weipeng1999) — proposed independent reviewer provider/model routing ([#11](https://github.com/PerryLink/dsh-auto-review/issues/11), [discussion #12](https://github.com/PerryLink/dsh-auto-review/discussions/12)), which shipped as `reviewerProvider` / `reviewerModel`.
|
|
354
|
+
- [@alexchenzl](https://github.com/alexchenzl) — listed the plugin on the DSH plugin directory ([#10](https://github.com/PerryLink/dsh-auto-review/issues/10)).
|
|
308
355
|
|
|
309
356
|
## PerryLink DSH Plugin Family
|
|
310
357
|
|
package/README.pt.md
CHANGED
|
@@ -95,6 +95,8 @@ Todos os ajustes são campos Schemastery `Config` (alteráveis no cordis.yml). U
|
|
|
95
95
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | Vereditos `allow` acima de `maxAutoAllow` delegam ou negam |
|
|
96
96
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Disjuntor de rejeições |
|
|
97
97
|
| `overrideTtlMs` | `300000` | Quanto tempo dura uma anulação de `/auto-review approve` |
|
|
98
|
+
| `verdictCacheTtlMs` | `60000` | Reutiliza um veredito recente para uma impressão `ferramenta + argumentos` idêntica; `0` desativa o cache |
|
|
99
|
+
| `verdictCacheMaxEntries` | `256` | Máximo de impressões em cache antes de despejar a mais antiga |
|
|
98
100
|
| `language` | `en` | Idioma da UI da saída do comando `/auto-review` (`en` \| `zh`) |
|
|
99
101
|
| `allowUnmarkedAudit` | `false` | Força a auditoria do registro de sessão em hosts que descartam o marcador `ignorable` (perigoso: eventos sem marcador tornam sessões irrecuperáveis em outros hosts); o padrão é detectar e degradar |
|
|
100
102
|
|
|
@@ -251,6 +253,49 @@ Portão de CI: o processo sai com 0 somente quando todos os casos de todas as su
|
|
|
251
253
|
|
|
252
254
|
`dsh-eval` difere de [openai/codex-research](https://github.com/openai/codex-research): o codex-research pontua trajetórias de agentes para comparação de pesquisa; `dsh-eval` é um harness declarativo de regressão aprovação/falha — casos YAML, asserções estruturadas de rastro/prompt/estresse/equidade, uma revisão opcional de segundo modelo e um código de saída de CI — para gatear qualquer agente DSH, não para ranking de pesquisa.
|
|
253
255
|
|
|
256
|
+
## Servidor MCP (autônomo)
|
|
257
|
+
|
|
258
|
+
O `dsh-auto-review` também inclui um **servidor MCP** stdio (`dsh-auto-review-mcp`) para que clientes MCP externos (Claude, Codex, …) consumam uma rota de revisão determinista sem o harness. Ele fala JSON-RPC 2.0 sobre JSON delimitado por novas linhas (NDJSON): um objeto JSON por linha, sem enquadramento `Content-Length`.
|
|
259
|
+
|
|
260
|
+
**Limite.** O revisor completo precisa do seam de subagentes do harness e de um segundo modelo, que um processo stdio separado não consegue alcançar. Portanto, o servidor autônomo é **regras deterministas + cache, sem revisão por modelo**:
|
|
261
|
+
|
|
262
|
+
- `review_action` reutiliza o cache de veredictos por impressão digital (`src/cache.ts`) e a resolução de regras de risco / políticas de ferramentas (`src/config.ts`): uma regra `never` → `deny`; um acerto de cache sobre uma impressão idêntica de `tool + arguments` reproduz esse veredicto; todo o resto (`ai` precisa de modelo, `human` precisa de um humano) → `deny` fail-closed com `reason: "standalone path, no model"`. Ele nunca permite uma ação que um modelo ainda não tenha permitido.
|
|
263
|
+
- `cache_stats` informa os contadores de acertos/armazenamento e o estado do TTL.
|
|
264
|
+
|
|
265
|
+
| Ferramenta | Propósito |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `review_action` | `{tool, args?, reason?}` → `{decision, reason, riskLevel}` — negação determinista / reprodução de cache |
|
|
268
|
+
| `cache_stats` | `{}` → `{hits, stores, size, ttlMs, enabled}` |
|
|
269
|
+
|
|
270
|
+
Execução direta:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
# as regras de risco vêm de variáveis de ambiente
|
|
274
|
+
export DSH_AUTO_REVIEW_RISK_RULES='[{"pattern":"rm -rf","policy":"never","field":"arguments"}]'
|
|
275
|
+
node bin/dsh-auto-review-mcp.mjs
|
|
276
|
+
# ou, após npm install: npx dsh-auto-review-mcp
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Configuração por ambiente: `DSH_AUTO_REVIEW_RISK_RULES` (array JSON de `{pattern, policy, field?}`), `DSH_AUTO_REVIEW_TOOLS_POLICY` (JSON `{default?, overrides?}`), `DSH_AUTO_REVIEW_CACHE_TTL_MS`, `DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES`.
|
|
280
|
+
|
|
281
|
+
Exemplo para o Claude Desktop (`claude_desktop_config.json`):
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"mcpServers": {
|
|
286
|
+
"dsh-auto-review": {
|
|
287
|
+
"command": "npx",
|
|
288
|
+
"args": ["-y", "dsh-auto-review-mcp"],
|
|
289
|
+
"env": {
|
|
290
|
+
"DSH_AUTO_REVIEW_RISK_RULES": "[{\"pattern\":\"rm -rf\",\"policy\":\"never\",\"field\":\"arguments\"}]"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
O servidor é somente-leitura e determinista: sem rede, sem modelo, sem gravações.
|
|
298
|
+
|
|
254
299
|
## Permissões e dados
|
|
255
300
|
|
|
256
301
|
- **Permissões**: o manifesto do workshop declara `session:append`, `approval:answer`, `subagent:spawn`, `command:register` e `tools:observe`.
|
|
@@ -304,6 +349,8 @@ Layout do repositório: `src/index.ts` (contrato do plugin) · `src/config.ts` (
|
|
|
304
349
|
## Contribuidores
|
|
305
350
|
|
|
306
351
|
- [@PerryLink](https://github.com/PerryLink) — criador e mantenedor: o answerer de aprovação, o subagente revisor, a política de risco e o disjuntor, o painel de revisão por projeção de sessão, o companheiro invariant, o dsh-eval e a documentação em cinco idiomas.
|
|
352
|
+
- [@weipeng1999](https://github.com/weipeng1999) — propôs o roteamento independente de provedor/modelo do revisor ([#11](https://github.com/PerryLink/dsh-auto-review/issues/11), [discussão #12](https://github.com/PerryLink/dsh-auto-review/discussions/12)), que foi lançado como `reviewerProvider` / `reviewerModel`.
|
|
353
|
+
- [@alexchenzl](https://github.com/alexchenzl) — incluiu o plugin no diretório de plugins do DSH ([#10](https://github.com/PerryLink/dsh-auto-review/issues/10)).
|
|
307
354
|
|
|
308
355
|
## Família de Plugins DSH PerryLink
|
|
309
356
|
|
package/README.zh.md
CHANGED
|
@@ -95,6 +95,8 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
95
95
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | 超过 `maxAutoAllow` 的 `allow` 裁决委派或拒绝 |
|
|
96
96
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | 拒绝熔断器 |
|
|
97
97
|
| `overrideTtlMs` | `300000` | `/auto-review approve` 覆盖的有效时长 |
|
|
98
|
+
| `verdictCacheTtlMs` | `60000` | 对相同 `工具 + 参数` 指纹复用近期裁决的时长;`0` 关闭缓存 |
|
|
99
|
+
| `verdictCacheMaxEntries` | `256` | 缓存指纹上限,超出后淘汰最旧条目 |
|
|
98
100
|
| `language` | `en` | `/auto-review` 命令输出的界面语言(`en` \| `zh`) |
|
|
99
101
|
| `allowUnmarkedAudit` | `false` | 强制在丢弃 `ignorable` 标记的宿主上写入会话审计(危险:未标记事件会让会话在其他宿主上无法恢复);默认自动探测并降级 |
|
|
100
102
|
|
|
@@ -251,6 +253,49 @@ CI 门禁:仅当每个套件的每个用例都通过时,进程才以 0 退
|
|
|
251
253
|
|
|
252
254
|
`dsh-eval` 与 [openai/codex-research](https://github.com/openai/codex-research) 的区别:codex-research 为研究对比对代理轨迹打分;`dsh-eval` 是声明式的通过/失败回归门禁 —— YAML 用例、结构化的轨迹/prompt/压测/公平性断言、可选的第二模型审查与 CI 退出码 —— 面向任何 DSH 代理的门禁,而非研究排名。
|
|
253
255
|
|
|
256
|
+
## MCP 服务器(独立进程)
|
|
257
|
+
|
|
258
|
+
`dsh-auto-review` 还附带一个 stdio **MCP 服务器**(`dsh-auto-review-mcp`),让外部 MCP 客户端(Claude、Codex 等)无需 harness 即可消费确定性裁决路径。它通过 newline-delimited JSON(NDJSON)承载 JSON-RPC 2.0——每行一个 JSON 对象,不支持 `Content-Length` 分帧。
|
|
259
|
+
|
|
260
|
+
**边界。** 完整 reviewer 依赖 harness 的 subagent seam 与第二模型,独立 stdio 进程无法调用。因此独立服务器是**确定性规则 + 缓存,无模型评审**:
|
|
261
|
+
|
|
262
|
+
- `review_action` 复用同指纹裁决缓存(`src/cache.ts`)与风险规则 / 工具策略解析(`src/config.ts`):命中 `never` 规则 → `deny`;缓存命中相同 `tool + arguments` 指纹 → 重放该裁决;其余情况(`ai` 需要模型、`human` 需要人工)→ fail-closed `deny`,reason 为 `"standalone path, no model"`。它绝不会放行一个模型尚未放行的动作。
|
|
263
|
+
- `cache_stats` 报告命中/存储计数与 TTL 状态。
|
|
264
|
+
|
|
265
|
+
| 工具 | 用途 |
|
|
266
|
+
|---|---|
|
|
267
|
+
| `review_action` | `{tool, args?, reason?}` → `{decision, reason, riskLevel}` —— 确定性拒绝 / 缓存重放 |
|
|
268
|
+
| `cache_stats` | `{}` → `{hits, stores, size, ttlMs, enabled}` |
|
|
269
|
+
|
|
270
|
+
直接运行:
|
|
271
|
+
|
|
272
|
+
```sh
|
|
273
|
+
# 风险规则来自环境变量
|
|
274
|
+
export DSH_AUTO_REVIEW_RISK_RULES='[{"pattern":"rm -rf","policy":"never","field":"arguments"}]'
|
|
275
|
+
node bin/dsh-auto-review-mcp.mjs
|
|
276
|
+
# 或 npm 安装后:npx dsh-auto-review-mcp
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
环境变量配置:`DSH_AUTO_REVIEW_RISK_RULES`(`{pattern, policy, field?}` 的 JSON 数组)、`DSH_AUTO_REVIEW_TOOLS_POLICY`(JSON `{default?, overrides?}`)、`DSH_AUTO_REVIEW_CACHE_TTL_MS`、`DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES`。
|
|
280
|
+
|
|
281
|
+
Claude Desktop(`claude_desktop_config.json`)配置示例:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"mcpServers": {
|
|
286
|
+
"dsh-auto-review": {
|
|
287
|
+
"command": "npx",
|
|
288
|
+
"args": ["-y", "dsh-auto-review-mcp"],
|
|
289
|
+
"env": {
|
|
290
|
+
"DSH_AUTO_REVIEW_RISK_RULES": "[{\"pattern\":\"rm -rf\",\"policy\":\"never\",\"field\":\"arguments\"}]"
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
服务器只读且确定性:无网络、无模型、无写入。
|
|
298
|
+
|
|
254
299
|
## 权限与数据
|
|
255
300
|
|
|
256
301
|
- **权限**:workshop 清单声明 `session:append`、`approval:answer`、`subagent:spawn`、`command:register` 与 `tools:observe`。
|
|
@@ -304,6 +349,8 @@ pnpm pack # 发布产物
|
|
|
304
349
|
## 贡献者
|
|
305
350
|
|
|
306
351
|
- [@PerryLink](https://github.com/PerryLink) —— 创建者与维护者:审批 answerer、审查子代理、风险策略与熔断器、会话投影审查面板、invariant 配套插件、dsh-eval,以及五语文档。
|
|
352
|
+
- [@weipeng1999](https://github.com/weipeng1999) —— 提出审查器独立的 provider/model 路由([#11](https://github.com/PerryLink/dsh-auto-review/issues/11)、[讨论 #12](https://github.com/PerryLink/dsh-auto-review/discussions/12)),已落地为 `reviewerProvider` / `reviewerModel`。
|
|
353
|
+
- [@alexchenzl](https://github.com/alexchenzl) —— 将本插件收录进 DSH 插件目录([#10](https://github.com/PerryLink/dsh-auto-review/issues/10))。
|
|
307
354
|
|
|
308
355
|
## PerryLink DSH 插件家族
|
|
309
356
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// dsh-auto-review stdio MCP server launcher. Serves review_action (deterministic
|
|
3
|
+
// verdict + same-fingerprint cache) and cache_stats over newline-delimited
|
|
4
|
+
// JSON-RPC 2.0 on stdio. No harness, no model, no write path — this is the
|
|
5
|
+
// independent, fail-closed review path; see README "MCP server (standalone)".
|
|
6
|
+
//
|
|
7
|
+
// Config comes from environment variables (see resolveEnvConfig):
|
|
8
|
+
// DSH_AUTO_REVIEW_RISK_RULES JSON [{pattern, policy, field?}]
|
|
9
|
+
// DSH_AUTO_REVIEW_TOOLS_POLICY JSON {default?, overrides?}
|
|
10
|
+
// DSH_AUTO_REVIEW_CACHE_TTL_MS non-negative integer
|
|
11
|
+
// DSH_AUTO_REVIEW_CACHE_MAX_ENTRIES positive integer
|
|
12
|
+
import { readFileSync } from 'node:fs'
|
|
13
|
+
import { StandaloneReviewer, createMcpServer, resolveEnvConfig, runStdioServer } from '../lib/mcp.js'
|
|
14
|
+
|
|
15
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
16
|
+
const config = resolveEnvConfig(process.env)
|
|
17
|
+
const server = createMcpServer(new StandaloneReviewer(config), pkg.version)
|
|
18
|
+
|
|
19
|
+
runStdioServer(server)
|
package/lib/client.js
CHANGED
|
@@ -37,11 +37,11 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
/**
|
|
38
38
|
* The session-header review panel: a button in the conversation header that
|
|
39
39
|
* opens a popover with this session's auto-review state — the on/off switch,
|
|
40
|
-
* budgets, cumulative statistics (including hard-disable rejections
|
|
41
|
-
* circuit trip, recent verdicts, and one-shot approve
|
|
42
|
-
* denials. Data arrives through the `autoReview` session
|
|
43
|
-
* switches and approves execute the `/auto-review` command
|
|
44
|
-
* commands Remote.
|
|
40
|
+
* budgets, cumulative statistics (including hard-disable rejections and
|
|
41
|
+
* cache hits), the circuit trip, recent verdicts, and one-shot approve
|
|
42
|
+
* buttons for recent denials. Data arrives through the `autoReview` session
|
|
43
|
+
* projection; switches and approves execute the `/auto-review` command
|
|
44
|
+
* through the commands Remote.
|
|
45
45
|
* @module dsh-auto-review/client/ReviewPanel
|
|
46
46
|
*/
|
|
47
47
|
/** Compact verdict-row badge label for the panel. */
|
|
@@ -150,6 +150,10 @@ window.__ModuleLoader__.load({
|
|
|
150
150
|
"data-dsh-auto-review-row": true,
|
|
151
151
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("failures") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: value.failuresUsed })]
|
|
152
152
|
}),
|
|
153
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
154
|
+
"data-dsh-auto-review-row": true,
|
|
155
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("cacheHits") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: value.cacheHits })]
|
|
156
|
+
}),
|
|
153
157
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
154
158
|
"data-dsh-auto-review-row": true,
|
|
155
159
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("allTime") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
@@ -243,6 +247,7 @@ window.__ModuleLoader__.load({
|
|
|
243
247
|
disable: "关闭",
|
|
244
248
|
verdicts: "本回合 AI 裁决",
|
|
245
249
|
failures: "本回合失败",
|
|
250
|
+
cacheHits: "缓存命中",
|
|
246
251
|
allTime: "累计",
|
|
247
252
|
allows: "放行",
|
|
248
253
|
denies: "拒绝",
|
|
@@ -280,6 +285,7 @@ window.__ModuleLoader__.load({
|
|
|
280
285
|
disable: "Disable",
|
|
281
286
|
verdicts: "AI verdicts this turn",
|
|
282
287
|
failures: "Failures this turn",
|
|
288
|
+
cacheHits: "Cache hits",
|
|
283
289
|
allTime: "All-time",
|
|
284
290
|
allows: "allows",
|
|
285
291
|
denies: "denies",
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useState","useRef"],"sources":["../src/client/present.ts","../src/client/ReviewPanel.tsx","../src/client/locales.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Pure presentation for the review panel: verdict rows and badges as plain\n * strings and tone codes — no I/O, clock, or randomness. The component\n * owns layout and interaction; this module owns the text.\n * @module dsh-auto-review/client/present\n */\n\nimport type { AutoReviewVerdictView } from '../projection-types.ts'\n\n/** Badge tones the component maps to scoped styles. */\nexport type VerdictTone = 'allow' | 'deny' | 'fallback' | 'escalation'\n\n/** One presented verdict row. */\nexport interface PresentedVerdict {\n readonly reviewId: string\n readonly toolName: string\n readonly label: string\n readonly detail: string\n readonly tone: VerdictTone\n}\n\n/** Present one verdict row (the component supplies the localized label texts). */\nexport function presentVerdict(\n verdict: AutoReviewVerdictView,\n labels: {\n readonly allow: string\n readonly deny: string\n readonly fallback: (kind: string) => string\n readonly escalation: string\n },\n): PresentedVerdict {\n if (verdict.fallback !== undefined) {\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label: labels.fallback(verdict.fallback),\n detail: `${verdict.durationMs} ms`,\n tone: 'fallback',\n }\n }\n if (verdict.escalation === 'risk-policy') {\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label: labels.escalation,\n detail: verdict.reason ?? '',\n tone: 'escalation',\n }\n }\n const label = verdict.decision === 'deny' ? labels.deny : labels.allow\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label,\n detail: verdict.reason ?? `${verdict.durationMs} ms`,\n tone: verdict.decision === 'deny' ? 'deny' : 'allow',\n }\n}\n","/**\n * The session-header review panel: a button in the conversation header that\n * opens a popover with this session's auto-review state — the on/off switch,\n * budgets, cumulative statistics (including hard-disable rejections), the\n * circuit trip, recent verdicts, and one-shot approve buttons for recent\n * denials. Data arrives through the `autoReview` session projection;\n * switches and approves execute the `/auto-review` command through the\n * commands Remote.\n * @module dsh-auto-review/client/ReviewPanel\n */\n\nimport { useRef, useState, type ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { AutoReviewProjection } from '../projection-types.ts'\nimport { presentVerdict } from './present.ts'\nimport { NS } from './locales.ts'\n\n/** Registration-side injected face: the approve action for one recent denial. */\nexport interface ReviewPanelInjected {\n /** Approve the n-th most recent denial (1 = most recent); resolves with the command result text. */\n approve: (n: number) => Promise<string>\n /** Switch the session's auto-review on/off (`/auto-review on|off`); resolves with the command result text. */\n setEnabled: (enabled: boolean) => Promise<string>\n}\n\n/** Full component props assembled by the session-header slot renderer. */\nexport type ReviewPanelProps =\n PropsRuntime<'conversation.session.header.actions'>\n & PropsLocale<typeof NS>\n & InjectFace<ReviewPanelInjected>\n\n/** Compact verdict-row badge label for the panel. */\nfunction verdictBadge(\n value: AutoReviewProjection | undefined,\n t: ReviewPanelProps['t'],\n): ReactNode {\n if (value === undefined) return t('unavailable')\n const fallback = (kind: string): string => t('fallbackLabel').replace('{kind}', kind)\n const labels = {\n allow: t('decisionAllow'),\n deny: t('decisionDeny'),\n fallback,\n escalation: t('escalationLabel'),\n }\n return value.recent.map(verdict => presentVerdict(verdict, labels)).map(row => (\n <div key={row.reviewId} data-dsh-auto-review-verdict>\n <span data-dsh-auto-review-muted>{row.toolName}</span>\n <span data-dsh-auto-review={`tone-${row.tone}`}>{row.label}</span>\n <span data-dsh-auto-review-muted>{row.detail}</span>\n </div>\n ))\n}\n\n/**\n * Render the review panel action.\n * @param props - framework kit (sessionId, useProjection), locale, and the approve face.\n * @returns the header button with its popover.\n */\nexport function ReviewPanel({ useProjection, approve, setEnabled, t }: ReviewPanelProps): ReactNode {\n const value = useProjection('autoReview')\n const [open, setOpen] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const anchor = useRef<HTMLDivElement | null>(null)\n const toggle = (): void => setOpen(previous => !previous)\n const runApprove = async (n: number): Promise<void> => {\n try {\n const text = await approve(n)\n setNotice(t('approveResult').replace('{text}', text))\n } catch (error: unknown) {\n setNotice(t('approveFailed').replace('{text}', error instanceof Error ? error.message : String(error)))\n }\n }\n const runSwitch = async (enabled: boolean): Promise<void> => {\n try {\n const text = await setEnabled(enabled)\n setNotice(t('switchedResult').replace('{text}', text))\n } catch (error: unknown) {\n setNotice(t('switchFailed').replace('{text}', error instanceof Error ? error.message : String(error)))\n }\n }\n return (\n <div data-dsh-auto-review ref={anchor}>\n <button type=\"button\" data-dsh-auto-review-button onClick={toggle}>\n {t('label')}\n </button>\n {open && (\n <div data-dsh-auto-review-panel>\n <div data-dsh-auto-review-title>{t('title')}</div>\n {value === undefined\n ? <div data-dsh-auto-review-muted>{t('unavailable')}</div>\n : (\n <div>\n <div data-dsh-auto-review-row>\n <span>{t('state')}</span>\n <span>{value.enabled ? t('stateOn') : t('stateOff')}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('enable')} / {t('disable')}</span>\n <span>\n <button\n type=\"button\"\n data-dsh-auto-review-switch\n disabled={value.enabled}\n onClick={() => void runSwitch(true)}\n >\n {t('enable')}\n </button>\n <button\n type=\"button\"\n data-dsh-auto-review-switch\n disabled={!value.enabled}\n onClick={() => void runSwitch(false)}\n >\n {t('disable')}\n </button>\n </span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('verdicts')}</span>\n <span>{value.verdictsUsed}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('failures')}</span>\n <span>{value.failuresUsed}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('allTime')}</span>\n <span>\n {value.allows} {t('allows')} · {value.denies} {t('denies')} · {value.fallbacks} {t('fallbacks')} · {value.neverRejects} {t('neverRejects')} · {t('avg')} {value.avgDurationMs} {t('ms')}\n </span>\n </div>\n {value.circuit !== null && (\n <div data-dsh-auto-review-circuit>\n {t('circuit')} — {t('circuitDetail')\n .replace('{kind}', value.circuit.trip.kind)\n .replace('{count}', String(value.circuit.trip.count))\n .replace('{action}', value.circuit.action)}\n </div>\n )}\n {value.recentDenies.length > 0 && (\n <div data-dsh-auto-review-section>\n <div data-dsh-auto-review-title>{t('denyList')}</div>\n {value.recentDenies.map((deny, index) => (\n <div key={deny.reviewId} data-dsh-auto-review-row>\n <span data-dsh-auto-review-muted>{deny.toolName}</span>\n <button type=\"button\" data-dsh-auto-review-approve onClick={() => void runApprove(index + 1)}>\n {t('approve')} #{index + 1}\n </button>\n </div>\n ))}\n </div>\n )}\n <div data-dsh-auto-review-section>\n <div data-dsh-auto-review-title>{t('recent')}</div>\n {value.recent.length === 0 ? <div data-dsh-auto-review-muted>{t('empty')}</div> : verdictBadge(value, t)}\n </div>\n </div>\n )}\n {notice !== null && <div data-dsh-auto-review-muted>{notice}</div>}\n </div>\n )}\n </div>\n )\n}\n","/**\n * Copy dictionaries for the auto-review session-header panel (en/zh — the\n * harness locale registry accepts these two UI language codes today).\n * @module dsh-auto-review/client/locales\n */\n\n/** Simplified Chinese dictionary and key source of truth. */\nexport const zh = {\n label: 'AI 审查',\n title: 'AI 自动审查',\n state: '状态',\n stateOn: '已开启',\n stateOff: '已关闭',\n enable: '开启',\n disable: '关闭',\n verdicts: '本回合 AI 裁决',\n failures: '本回合失败',\n allTime: '累计',\n allows: '放行',\n denies: '拒绝',\n fallbacks: '失败',\n neverRejects: '硬禁用拒绝',\n avg: '平均耗时',\n circuit: '拒绝熔断器已触发',\n circuitDetail: '触发类型:{kind},计数 {count},动作 {action}',\n empty: '本会话暂无 AI 审查裁决。',\n recent: '最近裁决',\n denyList: '可批准的重试',\n approve: '批准重试',\n approveResult: '已授权:{text}',\n approveFailed: '批准失败:{text}',\n switchedResult: '已切换:{text}',\n switchFailed: '切换失败:{text}',\n unavailable: '审查面板不可用(宿主未提供会话投影能力)。',\n decisionAllow: '放行',\n decisionDeny: '拒绝',\n fallbackLabel: '失败({kind})',\n escalationLabel: '风险策略升级',\n riskLow: '低风险',\n riskMedium: '中风险',\n riskHigh: '高风险',\n ms: 'ms',\n} satisfies Record<string, string>\n\n/** Auto-review panel locale key union. */\nexport type ReviewPanelLocaleKey = keyof typeof zh\n\n/** English dictionary checked against the Chinese key set. */\nexport const en: Record<ReviewPanelLocaleKey, string> = {\n label: 'AI Review',\n title: 'AI Auto-Review',\n state: 'State',\n stateOn: 'ON',\n stateOff: 'OFF',\n enable: 'Enable',\n disable: 'Disable',\n verdicts: 'AI verdicts this turn',\n failures: 'Failures this turn',\n allTime: 'All-time',\n allows: 'allows',\n denies: 'denies',\n fallbacks: 'fallbacks',\n neverRejects: 'never rejects',\n avg: 'avg',\n circuit: 'Rejection circuit breaker tripped',\n circuitDetail: '{kind}: {count} denials, action {action}',\n empty: 'No AI review verdicts in this session yet.',\n recent: 'Recent verdicts',\n denyList: 'Approvable retries',\n approve: 'Approve retry',\n approveResult: 'Authorized: {text}',\n approveFailed: 'Approve failed: {text}',\n switchedResult: 'Switched: {text}',\n switchFailed: 'Switch failed: {text}',\n unavailable: 'The review panel is unavailable (the host does not provide the session-projection capability).',\n decisionAllow: 'allow',\n decisionDeny: 'deny',\n fallbackLabel: 'fallback ({kind})',\n escalationLabel: 'risk-policy escalation',\n riskLow: 'low risk',\n riskMedium: 'medium risk',\n riskHigh: 'high risk',\n ms: 'ms',\n}\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'autoReviewPanel'\n","/**\n * Scoped stylesheet for the review panel. Standalone bundles cannot use the\n * in-repo CSS-module pipeline, so the panel injects one `<style>` element\n * whose selectors are all scoped under `[data-dsh-auto-review]`.\n * @module dsh-auto-review/client/styles\n */\n\nconst STYLE_ID = 'dsh-auto-review-panel-styles'\n\nconst CSS = `\n[data-dsh-auto-review] {\n --ar-allow: var(--dsw-color-success, #16a34a);\n --ar-deny: var(--dsw-color-danger, #dc2626);\n --ar-fallback: var(--dsw-color-warning, #d97706);\n --ar-escalation: var(--dsw-color-warning, #d97706);\n --ar-border: var(--dsw-color-border, rgba(128, 128, 128, 0.3));\n --ar-bg: var(--dsw-color-surface, #ffffff);\n --ar-fg: var(--dsw-color-text, #1f2328);\n --ar-muted: var(--dsw-color-text-muted, #6b7280);\n font-family: inherit;\n color: var(--ar-fg);\n}\n[data-dsh-auto-review-panel] {\n position: absolute;\n z-index: 40;\n min-width: 320px;\n max-width: 420px;\n max-height: 70vh;\n overflow-y: auto;\n background: var(--ar-bg);\n border: 1px solid var(--ar-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n padding: 12px;\n font-size: 12px;\n line-height: 1.5;\n}\n[data-dsh-auto-review-button] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n background: transparent;\n border: 1px solid var(--ar-border);\n border-radius: 6px;\n padding: 3px 8px;\n font-size: 12px;\n color: var(--ar-fg);\n}\n[data-dsh-auto-review-button]:hover {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-section] {\n margin: 8px 0;\n border-top: 1px solid var(--ar-border);\n padding-top: 8px;\n}\n[data-dsh-auto-review-title] {\n font-weight: 600;\n margin-bottom: 6px;\n}\n[data-dsh-auto-review-row] {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n padding: 2px 0;\n align-items: baseline;\n}\n[data-dsh-auto-review-muted] {\n color: var(--ar-muted);\n}\n[data-dsh-auto-review-verdict] {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n padding: 3px 0;\n border-bottom: 1px dashed var(--ar-border);\n}\n[data-dsh-auto-review-tone-allow] { color: var(--ar-allow); }\n[data-dsh-auto-review-tone-deny] { color: var(--ar-deny); }\n[data-dsh-auto-review-tone-fallback] { color: var(--ar-fallback); }\n[data-dsh-auto-review-tone-escalation] { color: var(--ar-escalation); }\n[data-dsh-auto-review-approve] {\n cursor: pointer;\n border: 1px solid var(--ar-border);\n border-radius: 4px;\n background: transparent;\n color: var(--ar-fg);\n padding: 1px 6px;\n font-size: 11px;\n}\n[data-dsh-auto-review-approve]:hover {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-switch] {\n cursor: pointer;\n border: 1px solid var(--ar-border);\n border-radius: 4px;\n background: transparent;\n color: var(--ar-fg);\n padding: 1px 6px;\n font-size: 11px;\n}\n[data-dsh-auto-review-switch] + [data-dsh-auto-review-switch] {\n margin-left: 4px;\n}\n[data-dsh-auto-review-switch]:hover:not(:disabled) {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-approve]:disabled,\n[data-dsh-auto-review-switch]:disabled {\n opacity: 0.45;\n cursor: default;\n}\n[data-dsh-auto-review-circuit] {\n color: var(--ar-deny);\n font-weight: 600;\n}\n`\n\n/** Install the scoped stylesheet once per plugin fiber. */\nexport function installPanelStyles(): () => void {\n if (document.getElementById(STYLE_ID) !== null) return () => undefined\n const element = document.createElement('style')\n element.id = STYLE_ID\n element.textContent = CSS\n document.head.appendChild(element)\n return () => {\n element.remove()\n }\n}\n","/**\n * `dsh-auto-review`, browser half: registers the locale dictionaries, the\n * scoped stylesheet, and the session-header review panel action. All data\n * arrives through the `autoReview` session projection; the approve buttons\n * execute the `/auto-review approve [n]` command through the commands\n * Remote namespace.\n *\n * @module dsh-auto-review/client\n */\n\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the 'conversation.session.header.actions' SlotMap\n// declaration into this program so the registration typechecks.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: merges the commands namespace onto ctx.remote.\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '../projection-types.ts'\nimport { ReviewPanel, type ReviewPanelInjected } from './ReviewPanel.tsx'\nimport { en, NS, zh, type ReviewPanelLocaleKey } from './locales.ts'\nimport { installPanelStyles } from './styles.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Review panel copy. */\n 'autoReviewPanel': ReviewPanelLocaleKey\n }\n}\n\nexport type { ReviewPanelProps, ReviewPanelInjected } from './ReviewPanel.tsx'\nexport type { ReviewPanelLocaleKey } from './locales.ts'\nexport { presentVerdict, type PresentedVerdict, type VerdictTone } from './present.ts'\n\n/** Plugin name: matches the package name, the graph row id, and the bundle id. */\nexport const name = 'dsh-auto-review'\n\n/** Services the panel reads; `remote.commands` is mounted by the api-remotes client. */\nexport const inject = ['slots', 'locale', 'remote', 'remote.commands']\n\n/**\n * The structural shape of the api-remotes `commands` Remote namespace the\n * panel needs. Declared locally because the ambient namespace merge onto\n * `TypertClientRemote` is assembled from several generated modules and can\n * resolve to a different physical copy under strict package managers —\n * the runtime contract is what this client depends on, so the type is\n * pinned to it explicitly (rc.6 and rc.7 both serve this shape).\n */\ninterface CommandsRemote {\n readonly execute: (\n agentId: SessionId,\n line: string,\n images?: readonly unknown[],\n ) => Promise<\n | { ok: false; error: { code: string; message: string } }\n | { ok: true; value?: { result?: { kind?: string; text?: string } } }\n >\n}\n\n/**\n * Browser plugin body: dictionaries, the scoped stylesheet, and the\n * session-header action registration.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-auto-review: dictionaries')\n ctx.effect(() => installPanelStyles(), 'dsh-auto-review: stylesheet')\n ctx.slots.inject(\n 'conversation.session.header.actions',\n () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'auto-review',\n order: 40,\n locale: NS,\n inject: (sessionId: SessionId): ReviewPanelInjected => {\n const remote = ctx.remote as unknown as { commands: CommandsRemote }\n const executeCommand = async (line: string): Promise<string> => {\n const result = await remote.commands.execute(sessionId, line, [])\n if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`)\n if (result.value === undefined) throw new Error('unknown command: /auto-review')\n const command = result.value.result\n if (command === undefined) throw new Error('/auto-review produced no command result')\n const text = command.text ?? ''\n return command.kind === 'success' ? text : `${command.kind}: ${text}`\n }\n return {\n approve: async (n: number): Promise<string> => executeCommand(`/auto-review approve ${n}`),\n setEnabled: async (enabled: boolean): Promise<string> => executeCommand(`/auto-review ${enabled ? 'on' : 'off'}`),\n }\n },\n }, ReviewPanel),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","useRef"],"sources":["../src/client/present.ts","../src/client/ReviewPanel.tsx","../src/client/locales.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Pure presentation for the review panel: verdict rows and badges as plain\n * strings and tone codes — no I/O, clock, or randomness. The component\n * owns layout and interaction; this module owns the text.\n * @module dsh-auto-review/client/present\n */\n\nimport type { AutoReviewVerdictView } from '../projection-types.ts'\n\n/** Badge tones the component maps to scoped styles. */\nexport type VerdictTone = 'allow' | 'deny' | 'fallback' | 'escalation'\n\n/** One presented verdict row. */\nexport interface PresentedVerdict {\n readonly reviewId: string\n readonly toolName: string\n readonly label: string\n readonly detail: string\n readonly tone: VerdictTone\n}\n\n/** Present one verdict row (the component supplies the localized label texts). */\nexport function presentVerdict(\n verdict: AutoReviewVerdictView,\n labels: {\n readonly allow: string\n readonly deny: string\n readonly fallback: (kind: string) => string\n readonly escalation: string\n },\n): PresentedVerdict {\n if (verdict.fallback !== undefined) {\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label: labels.fallback(verdict.fallback),\n detail: `${verdict.durationMs} ms`,\n tone: 'fallback',\n }\n }\n if (verdict.escalation === 'risk-policy') {\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label: labels.escalation,\n detail: verdict.reason ?? '',\n tone: 'escalation',\n }\n }\n const label = verdict.decision === 'deny' ? labels.deny : labels.allow\n return {\n reviewId: verdict.reviewId,\n toolName: verdict.toolName,\n label,\n detail: verdict.reason ?? `${verdict.durationMs} ms`,\n tone: verdict.decision === 'deny' ? 'deny' : 'allow',\n }\n}\n","/**\n * The session-header review panel: a button in the conversation header that\n * opens a popover with this session's auto-review state — the on/off switch,\n * budgets, cumulative statistics (including hard-disable rejections and\n * cache hits), the circuit trip, recent verdicts, and one-shot approve\n * buttons for recent denials. Data arrives through the `autoReview` session\n * projection; switches and approves execute the `/auto-review` command\n * through the commands Remote.\n * @module dsh-auto-review/client/ReviewPanel\n */\n\nimport { useRef, useState, type ReactNode } from 'react'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { AutoReviewProjection } from '../projection-types.ts'\nimport { presentVerdict } from './present.ts'\nimport { NS } from './locales.ts'\n\n/** Registration-side injected face: the approve action for one recent denial. */\nexport interface ReviewPanelInjected {\n /** Approve the n-th most recent denial (1 = most recent); resolves with the command result text. */\n approve: (n: number) => Promise<string>\n /** Switch the session's auto-review on/off (`/auto-review on|off`); resolves with the command result text. */\n setEnabled: (enabled: boolean) => Promise<string>\n}\n\n/** Full component props assembled by the session-header slot renderer. */\nexport type ReviewPanelProps =\n PropsRuntime<'conversation.session.header.actions'>\n & PropsLocale<typeof NS>\n & InjectFace<ReviewPanelInjected>\n\n/** Compact verdict-row badge label for the panel. */\nfunction verdictBadge(\n value: AutoReviewProjection | undefined,\n t: ReviewPanelProps['t'],\n): ReactNode {\n if (value === undefined) return t('unavailable')\n const fallback = (kind: string): string => t('fallbackLabel').replace('{kind}', kind)\n const labels = {\n allow: t('decisionAllow'),\n deny: t('decisionDeny'),\n fallback,\n escalation: t('escalationLabel'),\n }\n return value.recent.map(verdict => presentVerdict(verdict, labels)).map(row => (\n <div key={row.reviewId} data-dsh-auto-review-verdict>\n <span data-dsh-auto-review-muted>{row.toolName}</span>\n <span data-dsh-auto-review={`tone-${row.tone}`}>{row.label}</span>\n <span data-dsh-auto-review-muted>{row.detail}</span>\n </div>\n ))\n}\n\n/**\n * Render the review panel action.\n * @param props - framework kit (sessionId, useProjection), locale, and the approve face.\n * @returns the header button with its popover.\n */\nexport function ReviewPanel({ useProjection, approve, setEnabled, t }: ReviewPanelProps): ReactNode {\n const value = useProjection('autoReview')\n const [open, setOpen] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const anchor = useRef<HTMLDivElement | null>(null)\n const toggle = (): void => setOpen(previous => !previous)\n const runApprove = async (n: number): Promise<void> => {\n try {\n const text = await approve(n)\n setNotice(t('approveResult').replace('{text}', text))\n } catch (error: unknown) {\n setNotice(t('approveFailed').replace('{text}', error instanceof Error ? error.message : String(error)))\n }\n }\n const runSwitch = async (enabled: boolean): Promise<void> => {\n try {\n const text = await setEnabled(enabled)\n setNotice(t('switchedResult').replace('{text}', text))\n } catch (error: unknown) {\n setNotice(t('switchFailed').replace('{text}', error instanceof Error ? error.message : String(error)))\n }\n }\n return (\n <div data-dsh-auto-review ref={anchor}>\n <button type=\"button\" data-dsh-auto-review-button onClick={toggle}>\n {t('label')}\n </button>\n {open && (\n <div data-dsh-auto-review-panel>\n <div data-dsh-auto-review-title>{t('title')}</div>\n {value === undefined\n ? <div data-dsh-auto-review-muted>{t('unavailable')}</div>\n : (\n <div>\n <div data-dsh-auto-review-row>\n <span>{t('state')}</span>\n <span>{value.enabled ? t('stateOn') : t('stateOff')}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('enable')} / {t('disable')}</span>\n <span>\n <button\n type=\"button\"\n data-dsh-auto-review-switch\n disabled={value.enabled}\n onClick={() => void runSwitch(true)}\n >\n {t('enable')}\n </button>\n <button\n type=\"button\"\n data-dsh-auto-review-switch\n disabled={!value.enabled}\n onClick={() => void runSwitch(false)}\n >\n {t('disable')}\n </button>\n </span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('verdicts')}</span>\n <span>{value.verdictsUsed}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('failures')}</span>\n <span>{value.failuresUsed}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('cacheHits')}</span>\n <span>{value.cacheHits}</span>\n </div>\n <div data-dsh-auto-review-row>\n <span>{t('allTime')}</span>\n <span>\n {value.allows} {t('allows')} · {value.denies} {t('denies')} · {value.fallbacks} {t('fallbacks')} · {value.neverRejects} {t('neverRejects')} · {t('avg')} {value.avgDurationMs} {t('ms')}\n </span>\n </div>\n {value.circuit !== null && (\n <div data-dsh-auto-review-circuit>\n {t('circuit')} — {t('circuitDetail')\n .replace('{kind}', value.circuit.trip.kind)\n .replace('{count}', String(value.circuit.trip.count))\n .replace('{action}', value.circuit.action)}\n </div>\n )}\n {value.recentDenies.length > 0 && (\n <div data-dsh-auto-review-section>\n <div data-dsh-auto-review-title>{t('denyList')}</div>\n {value.recentDenies.map((deny, index) => (\n <div key={deny.reviewId} data-dsh-auto-review-row>\n <span data-dsh-auto-review-muted>{deny.toolName}</span>\n <button type=\"button\" data-dsh-auto-review-approve onClick={() => void runApprove(index + 1)}>\n {t('approve')} #{index + 1}\n </button>\n </div>\n ))}\n </div>\n )}\n <div data-dsh-auto-review-section>\n <div data-dsh-auto-review-title>{t('recent')}</div>\n {value.recent.length === 0 ? <div data-dsh-auto-review-muted>{t('empty')}</div> : verdictBadge(value, t)}\n </div>\n </div>\n )}\n {notice !== null && <div data-dsh-auto-review-muted>{notice}</div>}\n </div>\n )}\n </div>\n )\n}\n","/**\n * Copy dictionaries for the auto-review session-header panel (en/zh — the\n * harness locale registry accepts these two UI language codes today).\n * @module dsh-auto-review/client/locales\n */\n\n/** Simplified Chinese dictionary and key source of truth. */\nexport const zh = {\n label: 'AI 审查',\n title: 'AI 自动审查',\n state: '状态',\n stateOn: '已开启',\n stateOff: '已关闭',\n enable: '开启',\n disable: '关闭',\n verdicts: '本回合 AI 裁决',\n failures: '本回合失败',\n cacheHits: '缓存命中',\n allTime: '累计',\n allows: '放行',\n denies: '拒绝',\n fallbacks: '失败',\n neverRejects: '硬禁用拒绝',\n avg: '平均耗时',\n circuit: '拒绝熔断器已触发',\n circuitDetail: '触发类型:{kind},计数 {count},动作 {action}',\n empty: '本会话暂无 AI 审查裁决。',\n recent: '最近裁决',\n denyList: '可批准的重试',\n approve: '批准重试',\n approveResult: '已授权:{text}',\n approveFailed: '批准失败:{text}',\n switchedResult: '已切换:{text}',\n switchFailed: '切换失败:{text}',\n unavailable: '审查面板不可用(宿主未提供会话投影能力)。',\n decisionAllow: '放行',\n decisionDeny: '拒绝',\n fallbackLabel: '失败({kind})',\n escalationLabel: '风险策略升级',\n riskLow: '低风险',\n riskMedium: '中风险',\n riskHigh: '高风险',\n ms: 'ms',\n} satisfies Record<string, string>\n\n/** Auto-review panel locale key union. */\nexport type ReviewPanelLocaleKey = keyof typeof zh\n\n/** English dictionary checked against the Chinese key set. */\nexport const en: Record<ReviewPanelLocaleKey, string> = {\n label: 'AI Review',\n title: 'AI Auto-Review',\n state: 'State',\n stateOn: 'ON',\n stateOff: 'OFF',\n enable: 'Enable',\n disable: 'Disable',\n verdicts: 'AI verdicts this turn',\n failures: 'Failures this turn',\n cacheHits: 'Cache hits',\n allTime: 'All-time',\n allows: 'allows',\n denies: 'denies',\n fallbacks: 'fallbacks',\n neverRejects: 'never rejects',\n avg: 'avg',\n circuit: 'Rejection circuit breaker tripped',\n circuitDetail: '{kind}: {count} denials, action {action}',\n empty: 'No AI review verdicts in this session yet.',\n recent: 'Recent verdicts',\n denyList: 'Approvable retries',\n approve: 'Approve retry',\n approveResult: 'Authorized: {text}',\n approveFailed: 'Approve failed: {text}',\n switchedResult: 'Switched: {text}',\n switchFailed: 'Switch failed: {text}',\n unavailable: 'The review panel is unavailable (the host does not provide the session-projection capability).',\n decisionAllow: 'allow',\n decisionDeny: 'deny',\n fallbackLabel: 'fallback ({kind})',\n escalationLabel: 'risk-policy escalation',\n riskLow: 'low risk',\n riskMedium: 'medium risk',\n riskHigh: 'high risk',\n ms: 'ms',\n}\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'autoReviewPanel'\n","/**\n * Scoped stylesheet for the review panel. Standalone bundles cannot use the\n * in-repo CSS-module pipeline, so the panel injects one `<style>` element\n * whose selectors are all scoped under `[data-dsh-auto-review]`.\n * @module dsh-auto-review/client/styles\n */\n\nconst STYLE_ID = 'dsh-auto-review-panel-styles'\n\nconst CSS = `\n[data-dsh-auto-review] {\n --ar-allow: var(--dsw-color-success, #16a34a);\n --ar-deny: var(--dsw-color-danger, #dc2626);\n --ar-fallback: var(--dsw-color-warning, #d97706);\n --ar-escalation: var(--dsw-color-warning, #d97706);\n --ar-border: var(--dsw-color-border, rgba(128, 128, 128, 0.3));\n --ar-bg: var(--dsw-color-surface, #ffffff);\n --ar-fg: var(--dsw-color-text, #1f2328);\n --ar-muted: var(--dsw-color-text-muted, #6b7280);\n font-family: inherit;\n color: var(--ar-fg);\n}\n[data-dsh-auto-review-panel] {\n position: absolute;\n z-index: 40;\n min-width: 320px;\n max-width: 420px;\n max-height: 70vh;\n overflow-y: auto;\n background: var(--ar-bg);\n border: 1px solid var(--ar-border);\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n padding: 12px;\n font-size: 12px;\n line-height: 1.5;\n}\n[data-dsh-auto-review-button] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n background: transparent;\n border: 1px solid var(--ar-border);\n border-radius: 6px;\n padding: 3px 8px;\n font-size: 12px;\n color: var(--ar-fg);\n}\n[data-dsh-auto-review-button]:hover {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-section] {\n margin: 8px 0;\n border-top: 1px solid var(--ar-border);\n padding-top: 8px;\n}\n[data-dsh-auto-review-title] {\n font-weight: 600;\n margin-bottom: 6px;\n}\n[data-dsh-auto-review-row] {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n padding: 2px 0;\n align-items: baseline;\n}\n[data-dsh-auto-review-muted] {\n color: var(--ar-muted);\n}\n[data-dsh-auto-review-verdict] {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n padding: 3px 0;\n border-bottom: 1px dashed var(--ar-border);\n}\n[data-dsh-auto-review-tone-allow] { color: var(--ar-allow); }\n[data-dsh-auto-review-tone-deny] { color: var(--ar-deny); }\n[data-dsh-auto-review-tone-fallback] { color: var(--ar-fallback); }\n[data-dsh-auto-review-tone-escalation] { color: var(--ar-escalation); }\n[data-dsh-auto-review-approve] {\n cursor: pointer;\n border: 1px solid var(--ar-border);\n border-radius: 4px;\n background: transparent;\n color: var(--ar-fg);\n padding: 1px 6px;\n font-size: 11px;\n}\n[data-dsh-auto-review-approve]:hover {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-switch] {\n cursor: pointer;\n border: 1px solid var(--ar-border);\n border-radius: 4px;\n background: transparent;\n color: var(--ar-fg);\n padding: 1px 6px;\n font-size: 11px;\n}\n[data-dsh-auto-review-switch] + [data-dsh-auto-review-switch] {\n margin-left: 4px;\n}\n[data-dsh-auto-review-switch]:hover:not(:disabled) {\n background: rgba(128, 128, 128, 0.12);\n}\n[data-dsh-auto-review-approve]:disabled,\n[data-dsh-auto-review-switch]:disabled {\n opacity: 0.45;\n cursor: default;\n}\n[data-dsh-auto-review-circuit] {\n color: var(--ar-deny);\n font-weight: 600;\n}\n`\n\n/** Install the scoped stylesheet once per plugin fiber. */\nexport function installPanelStyles(): () => void {\n if (document.getElementById(STYLE_ID) !== null) return () => undefined\n const element = document.createElement('style')\n element.id = STYLE_ID\n element.textContent = CSS\n document.head.appendChild(element)\n return () => {\n element.remove()\n }\n}\n","/**\n * `dsh-auto-review`, browser half: registers the locale dictionaries, the\n * scoped stylesheet, and the session-header review panel action. All data\n * arrives through the `autoReview` session projection; the approve buttons\n * execute the `/auto-review approve [n]` command through the commands\n * Remote namespace.\n *\n * @module dsh-auto-review/client\n */\n\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: pulls the 'conversation.session.header.actions' SlotMap\n// declaration into this program so the registration typechecks.\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// Type-only: merges the commands namespace onto ctx.remote.\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '../projection-types.ts'\nimport { ReviewPanel, type ReviewPanelInjected } from './ReviewPanel.tsx'\nimport { en, NS, zh, type ReviewPanelLocaleKey } from './locales.ts'\nimport { installPanelStyles } from './styles.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Review panel copy. */\n 'autoReviewPanel': ReviewPanelLocaleKey\n }\n}\n\nexport type { ReviewPanelProps, ReviewPanelInjected } from './ReviewPanel.tsx'\nexport type { ReviewPanelLocaleKey } from './locales.ts'\nexport { presentVerdict, type PresentedVerdict, type VerdictTone } from './present.ts'\n\n/** Plugin name: matches the package name, the graph row id, and the bundle id. */\nexport const name = 'dsh-auto-review'\n\n/** Services the panel reads; `remote.commands` is mounted by the api-remotes client. */\nexport const inject = ['slots', 'locale', 'remote', 'remote.commands']\n\n/**\n * The structural shape of the api-remotes `commands` Remote namespace the\n * panel needs. Declared locally because the ambient namespace merge onto\n * `TypertClientRemote` is assembled from several generated modules and can\n * resolve to a different physical copy under strict package managers —\n * the runtime contract is what this client depends on, so the type is\n * pinned to it explicitly (rc.6 and rc.7 both serve this shape).\n */\ninterface CommandsRemote {\n readonly execute: (\n agentId: SessionId,\n line: string,\n images?: readonly unknown[],\n ) => Promise<\n | { ok: false; error: { code: string; message: string } }\n | { ok: true; value?: { result?: { kind?: string; text?: string } } }\n >\n}\n\n/**\n * Browser plugin body: dictionaries, the scoped stylesheet, and the\n * session-header action registration.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-auto-review: dictionaries')\n ctx.effect(() => installPanelStyles(), 'dsh-auto-review: stylesheet')\n ctx.slots.inject(\n 'conversation.session.header.actions',\n () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'auto-review',\n order: 40,\n locale: NS,\n inject: (sessionId: SessionId): ReviewPanelInjected => {\n const remote = ctx.remote as unknown as { commands: CommandsRemote }\n const executeCommand = async (line: string): Promise<string> => {\n const result = await remote.commands.execute(sessionId, line, [])\n if (!result.ok) throw new Error(`${result.error.code}: ${result.error.message}`)\n if (result.value === undefined) throw new Error('unknown command: /auto-review')\n const command = result.value.result\n if (command === undefined) throw new Error('/auto-review produced no command result')\n const text = command.text ?? ''\n return command.kind === 'success' ? text : `${command.kind}: ${text}`\n }\n return {\n approve: async (n: number): Promise<string> => executeCommand(`/auto-review approve ${n}`),\n setEnabled: async (enabled: boolean): Promise<string> => executeCommand(`/auto-review ${enabled ? 'on' : 'off'}`),\n }\n },\n }, ReviewPanel),\n )\n}\n"],"mappings":"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"}
|
package/lib/eval-cli.js
CHANGED
|
@@ -93,6 +93,8 @@ z.object({
|
|
|
93
93
|
action: "delegate"
|
|
94
94
|
}),
|
|
95
95
|
overrideTtlMs: z.number().default(3e5),
|
|
96
|
+
verdictCacheTtlMs: z.number().default(6e4),
|
|
97
|
+
verdictCacheMaxEntries: z.number().default(256),
|
|
96
98
|
language: z.union(["en", "zh"]).default("en"),
|
|
97
99
|
allowUnmarkedAudit: z.boolean().default(false)
|
|
98
100
|
});
|
|
@@ -121,6 +123,8 @@ function resolveConfig(config = {}) {
|
|
|
121
123
|
["windowSize", breaker.windowSize ?? 10]
|
|
122
124
|
]) if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError(`circuitBreaker.${key} must be a positive safe integer, got ${String(value)}`);
|
|
123
125
|
if (!Number.isSafeInteger(config.overrideTtlMs ?? 3e5) || (config.overrideTtlMs ?? 3e5) <= 0) throw new TypeError(`overrideTtlMs must be a positive safe integer, got ${String(config.overrideTtlMs)}`);
|
|
126
|
+
if (!Number.isSafeInteger(config.verdictCacheTtlMs ?? 6e4) || (config.verdictCacheTtlMs ?? 6e4) < 0) throw new TypeError(`verdictCacheTtlMs must be a non-negative safe integer (0 disables the cache), got ${String(config.verdictCacheTtlMs)}`);
|
|
127
|
+
if (!Number.isSafeInteger(config.verdictCacheMaxEntries ?? 256) || (config.verdictCacheMaxEntries ?? 256) <= 0) throw new TypeError(`verdictCacheMaxEntries must be a positive safe integer, got ${String(config.verdictCacheMaxEntries)}`);
|
|
124
128
|
if (config.allowUnmarkedAudit !== void 0 && typeof config.allowUnmarkedAudit !== "boolean") throw new TypeError(`allowUnmarkedAudit must be a boolean, got ${String(config.allowUnmarkedAudit)}`);
|
|
125
129
|
const reviewerTools = config.reviewerTools ?? [
|
|
126
130
|
"read",
|
|
@@ -175,6 +179,8 @@ function resolveConfig(config = {}) {
|
|
|
175
179
|
action: breaker.action ?? "delegate"
|
|
176
180
|
},
|
|
177
181
|
overrideTtlMs: config.overrideTtlMs ?? 3e5,
|
|
182
|
+
verdictCacheTtlMs: config.verdictCacheTtlMs ?? 6e4,
|
|
183
|
+
verdictCacheMaxEntries: config.verdictCacheMaxEntries ?? 256,
|
|
178
184
|
language: config.language ?? "en",
|
|
179
185
|
allowUnmarkedAudit: config.allowUnmarkedAudit ?? false
|
|
180
186
|
};
|