dsh-auto-review 0.8.0 → 0.9.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 +27 -0
- package/README.es.md +19 -3
- package/README.hi.md +19 -3
- package/README.md +20 -4
- package/README.pt.md +19 -3
- package/README.zh.md +19 -3
- package/lib/client.js +3 -1
- package/lib/client.js.map +1 -1
- package/lib/eval-cli.js +4 -11
- package/lib/eval.js +1 -1
- package/lib/index.js +2122 -949
- package/lib/mcp.js +3 -11
- package/lib/{report-NzSUyIog.js → report-B3Uq-u1O.js} +1963 -907
- package/lib/types/audit.d.ts +19 -13
- package/lib/types/audit.d.ts.map +1 -1
- package/lib/types/audit.js +19 -13
- package/lib/types/audit.js.map +1 -1
- package/lib/types/call-id.d.ts +18 -0
- package/lib/types/call-id.d.ts.map +1 -0
- package/lib/types/call-id.js +15 -0
- package/lib/types/call-id.js.map +1 -0
- package/lib/types/client/ReviewPanel.d.ts +3 -1
- package/lib/types/client/ReviewPanel.d.ts.map +1 -1
- package/lib/types/client/ReviewPanel.js +2 -1
- package/lib/types/client/ReviewPanel.js.map +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +4 -1
- package/lib/types/client/index.js.map +1 -1
- package/lib/types/config.d.ts +26 -1
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +27 -3
- package/lib/types/config.js.map +1 -1
- package/lib/types/events.d.ts +1 -1
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/isolation.d.ts +127 -0
- package/lib/types/isolation.d.ts.map +1 -0
- package/lib/types/isolation.js +168 -0
- package/lib/types/isolation.js.map +1 -0
- package/lib/types/projection.d.ts.map +1 -1
- package/lib/types/projection.js +1 -1
- package/lib/types/projection.js.map +1 -1
- package/lib/types/review.d.ts +12 -4
- package/lib/types/review.d.ts.map +1 -1
- package/lib/types/review.js +45 -6
- package/lib/types/review.js.map +1 -1
- package/lib/types/runtime.d.ts +24 -3
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +37 -5
- package/lib/types/runtime.js.map +1 -1
- package/package.json +35 -41
- package/src/audit.ts +19 -13
- package/src/call-id.ts +20 -0
- package/src/client/ReviewPanel.tsx +7 -1
- package/src/client/index.ts +17 -3
- package/src/config.ts +35 -4
- package/src/events.ts +1 -1
- package/src/index.ts +2 -0
- package/src/isolation.ts +182 -0
- package/src/projection.ts +2 -2
- package/src/review.ts +47 -7
- package/src/runtime.ts +47 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
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.9.0] - 2026-09-01
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Host `0.1.2-alpha.3` compatibility (no behavior change): dev/test pins bumped to `0.1.2-alpha.3`, `dshWorkshop.compatibility.dshVersions` lists `0.1.2-alpha.3`; the `isUnmarkedHostVersion` classification (`0.1.2.x` → unmarked, stay-write) and the audit docs are calibrated to the alpha.3 facts.
|
|
10
|
+
|
|
11
|
+
### Security
|
|
12
|
+
|
|
13
|
+
- **The reviewer child no longer receives injected context** ([#21](https://github.com/PerryLink/dsh-auto-review/issues/21)). The reviewer is built through the ordinary agent path, so the harness composed its steps the ordinary way: the workspace instruction files (`AGENTS.md` / `CLAUDE.md`), the loop's runtime-context snapshot, and any context-injecting plugin the user has installed all entered the child ABOVE the reviewer prompt — repository-controlled text inside the component that decides whether a privileged operation is allowed. A new context firewall (`src/isolation.ts`) filters every reviewer step on the documented `agent/pre-step` seam ("reject a proposed step or replace the messages that enter it") down to an allow-list of message SOURCES: the reviewer's own prompt (`user`) and its own read-only tool results (`tool`). Everything else is dropped before the loop appends it, so it never enters the child's log or its model request. The listener is registered with `prepend: true` so it wraps the whole waterfall and removes injections whichever listener added them; non-reviewer steps are returned untouched. Reviewer children are recognized by their announced prompt before `ctx.subagents.start` has resolved their session id (the in-process driver wakes the child's loop while `start` is still resolving, so an id-only test would miss the first step) and by the latched id afterwards.
|
|
14
|
+
- **The reviewer prompt fences everything outside itself** as untrusted transcript that grants no permission and overrides no verdict rule. This is defence in depth behind the firewall, and the only defence against a seeding provider's parent-history copy (`fork`'s completed-turn prefix), which is already the child's own log rather than a message entering a step.
|
|
15
|
+
- **The injected context is not provider-dependent.** The same request measured under `reviewerProvider: fork` and `reviewerProvider: spawn` produced BYTE-IDENTICAL injected context (workspace instructions 3559 chars, runtime-context snapshot 843 chars, third-party git-status snapshot 691 chars): these producers inject fresh into any new agent session rather than arriving through the fork seed. The `agent/pre-step` source filter is therefore the only thing that closes them, and it does so under either provider — `reviewerProvider: spawn` alone does NOT keep workspace instructions out of the reviewer. `spawn` addresses the completed-turn seeding specifically; neither substitutes for the other, and the seeding produced no additional messages in either trace, so its practical impact is unquantified.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`contextBudget.turns` defaults to 2 instead of 0** ([#21](https://github.com/PerryLink/dsh-auto-review/issues/21)). With `turns: 0` the reviewer received no transcript at all, so the only evidence for a user-authorized action was the calling agent's own self-report — and the reviewer's own verdict rule ("deny … when the evidence is insufficient") turned that into a denial of every user-authorized escalation. The new default is the open turn (which carries the pending call and usually the request that authorized it) plus the one before it; `fixtures/config/config-full.yaml` already used 2. `turns: 0` remains a valid opt-out, and combining it with any `ai` policy now logs a warning at mount time (`hasAiPolicy`) instead of reading as a broken reviewer.
|
|
20
|
+
- **The context budget's character cap is spent newest-line first.** `buildContextSection` truncated the joined transcript from the front, which cut its tail — the open turn holding the evidence the verdict rules ask for. It now collects lines backwards from the newest until the cap is reached, so an over-budget transcript loses its oldest lines instead. A single line wider than the whole cap still yields its head.
|
|
21
|
+
- **The verdict cache is off by default as a consequence of the new `contextBudget` default**: `fingerprintFor` already refused to key a transcript-dependent verdict on `tool + arguments` alone, and that guard now fires with the shipped configuration. Set `contextBudget: { turns: 0 }` to trade the reviewer's transcript back for same-fingerprint verdict reuse.
|
|
22
|
+
- Host `0.1.2-alpha.2` compatibility: `Session.append`'s third parameter is now `SurfaceIntent` (surface event types only — no `ignorable` option), so append can never stamp the audit marker; the `ignorable` field stays on the event envelope and the persistence read path still fails closed on unmarked unknown event types. The `isUnmarkedHostVersion` classification (`0.1.2.x` → unmarked, stay-write) is unchanged — the audit docs are calibrated to the alpha.2 facts.
|
|
23
|
+
- The call-id brand is derived locally (`src/call-id.ts`) from `dsh-tools`' `ToolExecution['callId']` instead of importing `CallId` from `@deepseek-ai/dsh-llm` (renamed `ToolCallId` on host master), keeping the source green on both the published `0.1.1-rc.2` line and host checkout.
|
|
24
|
+
- The client half no longer imports `@deepseek-ai/dsh-client-runtime` (removed from the host): `ClientContext` is cordis `Context`, `SessionId` comes from `@deepseek-ai/dsh-session/types`, and the slots registry is accessed structurally (`ctx.get('slots')`). The `@deepseek-ai/dsh-client-runtime` peer/dev dependency, client `inject` entry, and bundle external are dropped.
|
|
25
|
+
- Peer lower bounds relaxed `>=0.1.1-rc.2 <0.2.0` → `>=0.1.0-rc.8 <0.2.0`; dev/test pins bumped to `0.1.2-alpha.2`; `@deepseek-ai/cordis` `^4.0.2`; `@deepseek-ai/schemastery` `^3.18.2`.
|
|
26
|
+
- `scripts/check-host-versions.mjs` now also watches the npm `alpha` release line (the exact dev pins are its coverage), so a newer host alpha fails the gate before a publish.
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
|
|
30
|
+
- **The five READMEs now say where the config actually comes from** ([#21](https://github.com/PerryLink/dsh-auto-review/issues/21)). An `auto-review:` block in `~/.dsh/settings.yaml` has no effect and produces no warning — this plugin receives its `Config` from the row the loader mounts it with, the profile's cordis patch layer — and the failure looks identical to the reviewer simply denying. Some other DSH plugins additionally read the settings service, so the inconsistency is easy to trip over. The new "Where the config actually comes from" subsection states the one real source, warns that an id-targeted override replaces the WHOLE config row (dropping `toolsPolicy` returns `bash`/`write` to the schema default `human` and the reviewer stops running), and carries a complete working example.
|
|
31
|
+
|
|
5
32
|
## [0.8.0] - 2026-08-30
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/README.es.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Superficie | Estado |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependencias fijadas a `0.1.1-rc.2`; peers `>=0.1.0-rc.8 <0.2.0`) |
|
|
29
|
+
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependencias fijadas a `0.1.1-rc.2`; peers `>=0.1.0-rc.8 <0.2.0`) 0.1.2-alpha.3 (adaptado el 2026-09-01): el sobre de sesión conserva su campo ignorable solo para compatibilidad de lectura de logs almacenados - Session.append aún no puede estamparlo, por lo que el comportamiento de la puerta no cambia. |
|
|
30
30
|
| Node | `^22.19.0 \|\| >=24.0.0` |
|
|
31
31
|
| Plataformas | Todas (answerer de host; panel web opcional mediante la capacidad de proyección de sesión) |
|
|
32
32
|
| Modelo | Cualquiera (el revisor hereda la ruta del agente de sesión; `reviewerModel` la reemplaza) |
|
|
@@ -94,11 +94,11 @@ Todas las opciones son campos Schemastery `Config` (modificables desde cordis.ym
|
|
|
94
94
|
| `reviewerGuidance` | *(ninguna)* | Orientación opcional añadida al prompt del revisor |
|
|
95
95
|
| `reviewerPolicyText` | *(ninguna)* | Política de decisión en Markdown inyectada en el prompt del revisor (estilo Codex) |
|
|
96
96
|
| `denyGuidance` | *(texto anti-elusión)* | Orientación añadida a cada razón de denegación inyectada |
|
|
97
|
-
| `contextBudget` | `{turns:
|
|
97
|
+
| `contextBudget` | `{turns: 2, maxChars: 4000}` | Presupuesto de transcripción compacta para el prompt del revisor (el turno abierto y el anterior); `turns: 0` desactiva la sección — y un revisor ciego deniega acciones autorizadas por el usuario, así que el runtime avisa cuando 0 coincide con una política `ai`. El presupuesto de caracteres se gasta en las líneas más recientes |
|
|
98
98
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | Los veredictos `allow` por encima de `maxAutoAllow` delegan o deniegan |
|
|
99
99
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Disyuntor de rechazos |
|
|
100
100
|
| `overrideTtlMs` | `300000` | Cuánto dura una anulación de `/auto-review approve` |
|
|
101
|
-
| `verdictCacheTtlMs` | `60000` | Reutiliza un veredicto reciente para una huella `herramienta + argumentos` idéntica; `0` desactiva la
|
|
101
|
+
| `verdictCacheTtlMs` | `60000` | Reutiliza un veredicto reciente para una huella `herramienta + argumentos` idéntica; `0` desactiva la caché. Solo se aplica con `contextBudget.turns: 0` — un veredicto que depende de la transcripción no es reproducible solo desde `herramienta + argumentos` |
|
|
102
102
|
| `verdictCacheMaxEntries` | `256` | Máximo de huellas en caché antes de desalojar la más antigua |
|
|
103
103
|
| `language` | `en` | Idioma de la UI de la salida del comando `/auto-review` (`en` \| `zh`) |
|
|
104
104
|
| `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 |
|
|
@@ -124,6 +124,20 @@ Ejemplo (forma completa anotada: `fixtures/config/config-full.yaml`):
|
|
|
124
124
|
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### De dónde sale realmente la configuración
|
|
128
|
+
|
|
129
|
+
**`~/.dsh/settings.yaml` NO es una fuente de configuración para este plugin.** Un bloque `auto-review:` allí no tiene efecto ni produce aviso alguno: como todo plugin-función de DSH, `dsh-auto-review` recibe su `Config` de la fila con la que el loader lo monta — la capa de parches cordis del perfil. (Algunos otros plugins de DSH sí leen además el servicio de settings, así que la inconsistencia es fácil de sufrir y el síntoma es indistinguible de que el revisor simplemente deniegue.)
|
|
130
|
+
|
|
131
|
+
Pon la configuración en el `cordis.patch.yml` de tu perfil. Una **anulación dirigida por id reemplaza la fila de config entera**, así que reafirma cada clave que necesites — omitir `toolsPolicy` devuelve `bash`/`write` al valor por defecto del esquema, `human`, y el revisor deja de ejecutarse por completo:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
- id: auto-review
|
|
135
|
+
config:
|
|
136
|
+
toolsPolicy:
|
|
137
|
+
overrides: { bash: ai, write: ai }
|
|
138
|
+
contextBudget: { turns: 4, maxChars: 8000 }
|
|
139
|
+
```
|
|
140
|
+
|
|
127
141
|
## Herramientas y superficies
|
|
128
142
|
|
|
129
143
|
| Superficie | Tipo | Notas |
|
|
@@ -310,6 +324,7 @@ El servidor es de solo lectura y determinista: sin red, sin modelo, sin escritur
|
|
|
310
324
|
- **El revisor es un modelo.** Sus veredictos son política consultiva, no un núcleo de seguridad; prefiere reglas `human`/`never` para operaciones irreversibles.
|
|
311
325
|
- **Cierre en fallo.** Cada camino anómalo (proveedor ausente, carencias de capacidad, rechazo al iniciar, timeout, razón de parada no `completed`, veredicto ausente/malformado, fallo de correlación de auditoría) se resuelve con `fallbackPolicy`, por defecto `rejected` — y el rechazo devuelve una razón auditable al modelo. `allow-once` concede incondicionalmente; existe solo para despliegues desatendidos cuyo administrador acepta ese riesgo.
|
|
312
326
|
- **Revisor de solo lectura.** La lista blanca `toolFilter` del revisor (`read`/`glob`/`grep`) no puede escribir, editar, ejecutar bash, acceder a la red ni delegar (`maxDepth` = su propia profundidad). Su registro de sesión se persiste y es auditable.
|
|
327
|
+
- **Revisor aislado del contexto.** Los pasos del hijo revisor se filtran en la costura oficial `agent/pre-step`: solo entran su propio prompt y los resultados de sus propias herramientas de solo lectura. Los archivos de instrucciones del espacio de trabajo (`AGENTS.md` / `CLAUDE.md`), la instantánea de contexto de ejecución del harness y cualquier plugin que inyecte contexto se descartan antes de que el bucle los añada, de modo que el texto controlado por el repositorio nunca llega al componente que decide si se permite una llamada. Esto vale con CUALQUIERA de los proveedores de subagente — esos productores inyectan de nuevo en cada sesión de agente, así que lo que los cierra es el filtro y no la elección de proveedor. La lista blanca es por ORIGEN del mensaje, así que un plugin que declare un origen nuevo también se descarta.
|
|
313
328
|
- **Los argumentos sensibles se redactan** (coincidencia por nombre de clave: `token`, `password`, `api_key`, `Authorization`, credenciales, claves privadas …) antes de entrar en el prompt del revisor; el plugin nunca ejecuta los argumentos revisados. La redacción se basa en claves, no en contenido — no revises con IA herramientas cuyos valores de argumento no te puedas permitir mostrar a un modelo.
|
|
314
329
|
- **Las desactivaciones duras se explican a sí mismas.** Una herramienta o regla de riesgo `never` rechaza de forma determinista Y registra un evento solo-registro `autoReview/rejection`, y luego inyecta un marcador `[auto-review-never]` en el resultado de la herramienta denegada — el modelo aprende que la acción está duramente desactivada en lugar de reintentarla (verificado por invariant: marcador ⟺ evento).
|
|
315
330
|
- **Disyuntor de rechazos.** Una racha de denegaciones en un turno dispara el disyuntor (`consecutiveDenies` / `windowDenies` dentro de `windowSize`), registrada como un evento solo-registro `autoReview/circuit`; las solicitudes posteriores siguen su `action` (`delegate` / `reject` / `abort-turn`).
|
|
@@ -318,6 +333,7 @@ El servidor es de solo lectura y determinista: sin red, sin modelo, sin escritur
|
|
|
318
333
|
|
|
319
334
|
## Limitaciones conocidas
|
|
320
335
|
|
|
336
|
+
- **Dos exposiciones distintas, dos respuestas distintas — ninguna sustituye a la otra.** El contexto *inyectado* (archivos de instrucciones del espacio de trabajo, la instantánea de contexto de ejecución, inyecciones de plugins de terceros) se inyecta de nuevo en cada sesión de agente, así que llega al revisor de forma idéntica con `reviewerProvider: fork` y con `reviewerProvider: spawn` — medido byte a byte idéntico en ambos con la misma petición. El filtro por origen de `agent/pre-step` es lo que lo cierra, con cualquiera de los dos proveedores; **`spawn` por sí solo NO impide que las instrucciones del espacio de trabajo lleguen al revisor.** Aparte, `fork` siembra al hijo con los turnos completados de la sesión delegante: ese historial ya es el propio registro del hijo y no un mensaje que entra en un paso, así que el filtro no puede tocarlo y solo `spawn` lo evita, con la valla de transcripción no confiable del prompt del revisor como mitigación intermedia. En las dos trazas anteriores la siembra no produjo mensajes adicionales, por lo que su impacto práctico está sin cuantificar.
|
|
321
337
|
- El revisor necesita una ruta LLM funcional (heredada por defecto); sin ella, cada revisión cae según `fallbackPolicy` — nunca una concesión silenciosa.
|
|
322
338
|
- Los nombres de `reviewerTools` deben existir como herramientas globales en el perfil; un nombre desconocido hace fallar al hijo revisor en voz alta en el punto más temprano y cae en fallback.
|
|
323
339
|
- Las reglas de riesgo emparejan el `reason` de la solicitud, el `toolName` o los `arguments` redactados de la llamada según su `field`; otras condiciones van en `toolsPolicy.overrides`.
|
package/README.hi.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| सतह | स्थिति |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependencies `0.1.1-rc.2` पर पिन की गईं; peers `>=0.1.0-rc.8 <0.2.0`) |
|
|
29
|
+
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependencies `0.1.1-rc.2` पर पिन की गईं; peers `>=0.1.0-rc.8 <0.2.0`) 0.1.2-alpha.3 (2026-09-01 को अनुकूलित): सत्र लिफ़ाफ़ा अपना ignorable फ़ील्ड केवल संग्रहीत-लॉग पठन संगतता के लिए रखता है - Session.append अभी भी इसे स्टैम्प नहीं कर सकता, इसलिए गेट व्यवहार अपरिवर्तित है। |
|
|
30
30
|
| Node | `^22.19.0 \|\| >=24.0.0` |
|
|
31
31
|
| प्लेटफ़ॉर्म | सभी (होस्ट answerer; सत्र-प्रोजेक्शन क्षमता के ज़रिए वैकल्पिक वेब समीक्षा पैनल) |
|
|
32
32
|
| मॉडल | कोई भी (समीक्षक सत्र एजेंट का मार्ग विरासत में लेता है; `reviewerModel` उसे बदल देता है) |
|
|
@@ -94,11 +94,11 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
94
94
|
| `reviewerGuidance` | *(कोई नहीं)* | समीक्षक प्रॉम्प्ट में जोड़ा गया वैकल्पिक सलाहकार मार्गदर्शन |
|
|
95
95
|
| `reviewerPolicyText` | *(कोई नहीं)* | समीक्षक प्रॉम्प्ट में इंजेक्ट की गई Markdown निर्णय-नीति (Codex शैली) |
|
|
96
96
|
| `denyGuidance` | *(परिहार-विरोधी पाठ)* | हर इंजेक्ट किए गए अस्वीकृति कारण में जोड़ा गया मार्गदर्शन |
|
|
97
|
-
| `contextBudget` | `{turns:
|
|
97
|
+
| `contextBudget` | `{turns: 2, maxChars: 4000}` | समीक्षक प्रॉम्प्ट का संक्षिप्त ट्रांसक्रिप्ट बजट (खुला टर्न और उससे पिछला); `turns: 0` इस खंड को अक्षम करता है — और बिना ट्रांसक्रिप्ट वाला समीक्षक उपयोगकर्ता-अधिकृत क्रियाओं को अस्वीकार करता है, इसलिए जब 0 किसी `ai` नीति के साथ आता है तो रनटाइम चेतावनी देता है। वर्ण बजट सबसे हालिया पंक्तियों पर खर्च होता है |
|
|
98
98
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | `maxAutoAllow` से ऊपर के `allow` निर्णय सौंपते या अस्वीकार करते हैं |
|
|
99
99
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | अस्वीकृति सर्किट ब्रेकर |
|
|
100
100
|
| `overrideTtlMs` | `300000` | `/auto-review approve` ओवरराइड कितनी देर उपयोगी रहता है |
|
|
101
|
-
| `verdictCacheTtlMs` | `60000` | समान `टूल + तर्क` फ़िंगरप्रिंट के लिए हालिया निर्णय पुनः उपयोग करें; `0` कैश बंद करता है |
|
|
101
|
+
| `verdictCacheTtlMs` | `60000` | समान `टूल + तर्क` फ़िंगरप्रिंट के लिए हालिया निर्णय पुनः उपयोग करें; `0` कैश बंद करता है। यह केवल `contextBudget.turns: 0` के साथ लागू होता है — ट्रांसक्रिप्ट पर निर्भर निर्णय केवल `टूल + तर्क` से दोहराया नहीं जा सकता |
|
|
102
102
|
| `verdictCacheMaxEntries` | `256` | सबसे पुराने को निकालने से पहले कैश किए गए फ़िंगरप्रिंट की अधिकतम संख्या |
|
|
103
103
|
| `language` | `en` | `/auto-review` कमांड आउटपुट की UI भाषा (`en` \| `zh`) |
|
|
104
104
|
| `allowUnmarkedAudit` | `false` | `ignorable` मार्कर छोड़ने वाले होस्ट पर सत्र-लॉग ऑडिट बलपूर्वक चालू करें (खतरनाक: बिना मार्कर वाली घटनाएँ अन्य होस्ट पर सत्र अप्राप्य बनाती हैं); डिफ़ॉल्ट पहचान-और-डिग्रेड है |
|
|
@@ -124,6 +124,20 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
124
124
|
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### कॉन्फ़िगरेशन असल में कहाँ से आता है
|
|
128
|
+
|
|
129
|
+
**`~/.dsh/settings.yaml` इस प्लगइन के लिए कॉन्फ़िग स्रोत नहीं है।** वहाँ लिखा `auto-review:` ब्लॉक न कोई असर करता है और न कोई चेतावनी देता है: हर DSH फ़ंक्शन-प्लगइन की तरह `dsh-auto-review` अपना `Config` उसी पंक्ति से पाता है जिससे लोडर उसे माउंट करता है — यानी प्रोफ़ाइल की cordis patch परत से। (कुछ अन्य DSH प्लगइन settings सेवा भी पढ़ते हैं, इसलिए यह असंगति आसानी से फँसा देती है, और लक्षण "समीक्षक ने बस मना कर दिया" से अलग नहीं दिखता।)
|
|
130
|
+
|
|
131
|
+
कॉन्फ़िगरेशन अपनी प्रोफ़ाइल के `cordis.patch.yml` में रखें। **id-लक्षित override पूरी config पंक्ति को बदल देता है**, इसलिए हर आवश्यक कुंजी दोबारा लिखें — `toolsPolicy` छोड़ने पर `bash`/`write` चुपचाप schema डिफ़ॉल्ट `human` पर लौट जाते हैं और समीक्षक चलना ही बंद कर देता है:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
- id: auto-review
|
|
135
|
+
config:
|
|
136
|
+
toolsPolicy:
|
|
137
|
+
overrides: { bash: ai, write: ai }
|
|
138
|
+
contextBudget: { turns: 4, maxChars: 8000 }
|
|
139
|
+
```
|
|
140
|
+
|
|
127
141
|
## उपकरण और सतहें
|
|
128
142
|
|
|
129
143
|
| सतह | प्रकार | नोट्स |
|
|
@@ -310,6 +324,7 @@ Claude Desktop (`claude_desktop_config.json`) उदाहरण:
|
|
|
310
324
|
- **समीक्षक एक मॉडल है।** उसके निर्णय सलाहकार नीति हैं, सुरक्षा कर्नेल नहीं; अपरिवर्तनीय क्रियाओं के लिए `human`/`never` नियम चुनें।
|
|
311
325
|
- **विफल-बंद।** हर असामान्य मार्ग (प्रदाता अनुपस्थित, क्षमता अंतराल, प्रारंभ अस्वीकृति, टाइमआउट, गैर-`completed` स्टॉप कारण, अनुपस्थित/विकृत निर्णय, ऑडिट-सहसंबंध विफलता) `fallbackPolicy` से हल होता है, डिफ़ॉल्ट `rejected` — और अस्वीकृति मॉडल को एक ऑडिट-योग्य कारण लौटाती है। `allow-once` बिना शर्त अनुमति देता है; यह केवल उन अप्रबंधित परिनियोजनों के लिए है जिनका व्यवस्थापक उस जोखिम को स्वीकार करता है।
|
|
312
326
|
- **केवल-पढ़ने वाला समीक्षक।** समीक्षक की `toolFilter` अनुमति-सूची (`read`/`glob`/`grep`) लिख, संपादित, bash चला, नेटवर्क उपयोग या प्रत्यायोजन नहीं कर सकती (`maxDepth` = उसकी अपनी गहराई)। उसका सत्र लॉग स्थायी और ऑडिट-योग्य है।
|
|
327
|
+
- **संदर्भ-पृथक समीक्षक।** समीक्षक चाइल्ड के हर चरण को आधिकारिक `agent/pre-step` सीम पर छाना जाता है: केवल उसका अपना प्रॉम्प्ट और उसके अपने केवल-पढ़ने वाले टूल परिणाम ही भीतर आते हैं। वर्कस्पेस निर्देश फ़ाइलें (`AGENTS.md` / `CLAUDE.md`), हार्नेस का रनटाइम-संदर्भ स्नैपशॉट और संदर्भ इंजेक्ट करने वाला कोई भी प्लगइन लूप द्वारा जोड़े जाने से पहले हटा दिए जाते हैं, इसलिए रिपॉज़िटरी-नियंत्रित पाठ कभी उस घटक तक नहीं पहुँचता जो कॉल की अनुमति तय करता है। यह किसी भी subagent प्रदाता के साथ लागू होता है — ये उत्पादक हर नए एजेंट सत्र में नए सिरे से इंजेक्ट करते हैं, इसलिए इन्हें बंद करने वाला यही फ़िल्टर है, प्रदाता का चुनाव नहीं। अनुमति-सूची संदेश के स्रोत पर आधारित है, इसलिए नया स्रोत घोषित करने वाला प्लगइन भी हटा दिया जाता है।
|
|
313
328
|
- **संवेदनशील तर्क संशोधित किए जाते हैं** (कुंजी-नाम मिलान: `token`, `password`, `api_key`, `Authorization`, क्रेडेंशियल, निजी कुंजियाँ …) समीक्षक प्रॉम्प्ट में जाने से पहले; प्लगइन समीक्षित तर्कों को कभी निष्पादित नहीं करता। संशोधन कुंजी-आधारित है, सामग्री-आधारित नहीं — उन टूल की AI समीक्षा न करें जिनके तर्क मान आप किसी मॉडल को दिखाने का जोखिम नहीं उठा सकते।
|
|
314
329
|
- **कठोर अक्षमताएँ स्वयं को समझाती हैं।** एक `never` टूल या जोखिम नियम निर्धारक रूप से अस्वीकार करता है और एक केवल-लॉग `autoReview/rejection` घटना दर्ज करता है, फिर अस्वीकृत टूल परिणाम में एक `[auto-review-never]` मार्कर इंजेक्ट करता है — मॉडल सीखता है कि क्रिया कठोर रूप से अक्षम है बजाय उसे पुनः प्रयास करने के (invariant-जाँचा गया: मार्कर ⟺ घटना)।
|
|
315
330
|
- **अस्वीकृति सर्किट ब्रेकर।** एक टर्न में अस्वीकृतियों की एक शृंखला ब्रेकर को ट्रिप करती है (`windowSize` के भीतर `consecutiveDenies` / `windowDenies`), जो एक केवल-लॉग `autoReview/circuit` घटना के रूप में दर्ज होती है; बाद के अनुरोध उसकी `action` (`delegate` / `reject` / `abort-turn`) का पालन करते हैं।
|
|
@@ -318,6 +333,7 @@ Claude Desktop (`claude_desktop_config.json`) उदाहरण:
|
|
|
318
333
|
|
|
319
334
|
## ज्ञात सीमाएँ
|
|
320
335
|
|
|
336
|
+
- **दो अलग-अलग एक्सपोज़र, दो अलग-अलग उपाय — कोई एक दूसरे का विकल्प नहीं।** *इंजेक्ट किया गया* संदर्भ (वर्कस्पेस निर्देश फ़ाइलें, रनटाइम-संदर्भ स्नैपशॉट, तृतीय-पक्ष प्लगइन इंजेक्शन) हर नए एजेंट सत्र में नए सिरे से इंजेक्ट होता है, इसलिए यह `reviewerProvider: fork` और `reviewerProvider: spawn` दोनों में समान रूप से समीक्षक तक पहुँचता है — एक ही अनुरोध पर दोनों में बाइट-दर-बाइट समान मापा गया। इसे बंद करने वाला `agent/pre-step` स्रोत फ़िल्टर है, दोनों प्रदाताओं के साथ; **केवल `spawn` अपनाने से वर्कस्पेस निर्देश समीक्षक तक पहुँचना बंद नहीं होते।** अलग से, `fork` चाइल्ड को प्रत्यायोजक सत्र के पूर्ण हुए टर्न से सीड करता है: वह इतिहास पहले से चाइल्ड का अपना लॉग है, किसी चरण में प्रवेश करता संदेश नहीं, इसलिए फ़िल्टर उस तक नहीं पहुँच सकता और केवल `spawn` उससे बचाता है — बीच में समीक्षक प्रॉम्प्ट की अविश्वसनीय-ट्रांसक्रिप्ट बाड़ शमन के रूप में रहती है। ऊपर की दोनों ट्रेस में सीडिंग से कोई अतिरिक्त संदेश नहीं बना, इसलिए उसका व्यावहारिक प्रभाव अपरिमाणित है।
|
|
321
337
|
- समीक्षक को एक कार्यशील LLM मार्ग चाहिए (डिफ़ॉल्ट रूप से विरासत); इसके बिना हर समीक्षा `fallbackPolicy` के अनुसार गिरती है — कभी मौन अनुमति नहीं।
|
|
322
338
|
- `reviewerTools` नाम प्रोफ़ाइल में वैश्विक टूल के रूप में मौजूद होने चाहिए; अज्ञात नाम समीक्षक चाइल्ड को सबसे पहले बिंदु पर ज़ोर से विफल करता है और fallback होता है।
|
|
323
339
|
- जोखिम नियम अपने `field` के अनुसार अनुरोध के `reason`, `toolName` या संशोधित कॉल `arguments` से मिलते हैं; बाकी शर्तें `toolsPolicy.overrides` में रखें।
|
package/README.md
CHANGED
|
@@ -96,14 +96,14 @@ All tunables are Schemastery `Config` fields (changeable from cordis.yml). An id
|
|
|
96
96
|
| `reviewerGuidance` | *(none)* | Optional advisory guidance appended to the reviewer prompt |
|
|
97
97
|
| `reviewerPolicyText` | *(none)* | Markdown ruling policy injected into the reviewer prompt (Codex-style) |
|
|
98
98
|
| `denyGuidance` | *(anti-circumvention text)* | Guidance appended to every injected deny reason |
|
|
99
|
-
| `contextBudget` | `{turns:
|
|
99
|
+
| `contextBudget` | `{turns: 2, maxChars: 4000}` | Compact transcript budget for the reviewer prompt (the open turn plus the one before it); `turns: 0` disables the section — and a blind reviewer denies user-authorized actions, so the runtime warns when 0 meets an `ai` policy. The character budget is spent on the most recent lines |
|
|
100
100
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | `allow` verdicts above `maxAutoAllow` delegate or deny |
|
|
101
101
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Rejection circuit breaker |
|
|
102
102
|
| `overrideTtlMs` | `300000` | How long a `/auto-review approve` override stays usable |
|
|
103
|
-
| `verdictCacheTtlMs` | `60000` | Reuse a recent verdict for an identical `tool + arguments` fingerprint; `0` disables the cache |
|
|
103
|
+
| `verdictCacheTtlMs` | `60000` | Reuse a recent verdict for an identical `tool + arguments` fingerprint; `0` disables the cache. Only applies with `contextBudget.turns: 0` — a transcript-dependent verdict is not replayable from `tool + arguments` alone |
|
|
104
104
|
| `verdictCacheMaxEntries` | `256` | Maximum cached fingerprints before oldest-eviction |
|
|
105
105
|
| `language` | `en` | UI language of the `/auto-review` command output (`en` \| `zh`) |
|
|
106
|
-
| `allowUnmarkedAudit` | `false` | Force session-log audit on hosts that drop the `ignorable` marker or fail-closed on unknown event types (host `0.1.2-alpha.
|
|
106
|
+
| `allowUnmarkedAudit` | `false` | Force session-log audit on hosts that drop the `ignorable` marker or fail-closed on unknown event types (host `0.1.2-alpha.3`+) (dangerous: unmarked events make sessions unresumable elsewhere); default is detect-and-degrade 0.1.2-alpha.3 (adapted 2026-09-01): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. |
|
|
107
107
|
|
|
108
108
|
Example (annotated full form: `fixtures/config/config-full.yaml`):
|
|
109
109
|
|
|
@@ -126,6 +126,20 @@ Example (annotated full form: `fixtures/config/config-full.yaml`):
|
|
|
126
126
|
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
### Where the config actually comes from
|
|
130
|
+
|
|
131
|
+
**`~/.dsh/settings.yaml` is NOT a config source for this plugin.** An `auto-review:` block there has no effect and produces no warning: like every DSH function plugin, `dsh-auto-review` receives its `Config` from the row the loader mounts it with — the profile's cordis patch layer. (Some other DSH plugins additionally read the settings service, so the inconsistency is easy to trip over, and the symptom is indistinguishable from the reviewer simply denying.)
|
|
132
|
+
|
|
133
|
+
Put the configuration in your profile's `cordis.patch.yml`. An **id-targeted override replaces the whole config row**, so restate every key you need — dropping `toolsPolicy` silently returns `bash`/`write` to the schema default `human` and the reviewer stops running at all:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
- id: auto-review
|
|
137
|
+
config:
|
|
138
|
+
toolsPolicy:
|
|
139
|
+
overrides: { bash: ai, write: ai }
|
|
140
|
+
contextBudget: { turns: 4, maxChars: 8000 }
|
|
141
|
+
```
|
|
142
|
+
|
|
129
143
|
## Tools & surfaces
|
|
130
144
|
|
|
131
145
|
| Surface | Kind | Notes |
|
|
@@ -305,13 +319,14 @@ The server is read-only and deterministic: no network, no model, no writes.
|
|
|
305
319
|
|
|
306
320
|
- **Permissions**: the workshop manifest declares `session:append`, `approval:answer`, `subagent:spawn`, `command:register`, and `tools:observe`.
|
|
307
321
|
- **Data**: nothing is stored on disk; the report ring buffer is in-memory and bounded. No network requests of its own.
|
|
308
|
-
- **Session log**: `autoReview/*` events carry reviewer identity, verdict, reason, risk, and duration — appended with the envelope's `ignorable: true` marker so any build loads the log. Hosts whose `Session.append` predates the marker (every released rc line through `0.1.1-rc.2` — no release stamps it yet) are detected before the first append (peer-version pre-check); host `0.1.2-alpha.
|
|
322
|
+
- **Session log**: `autoReview/*` events carry reviewer identity, verdict, reason, risk, and duration — appended with the envelope's `ignorable: true` marker so any build loads the log. Hosts whose `Session.append` predates the marker (every released rc line through `0.1.1-rc.2` — no release stamps it yet) are detected before the first append (peer-version pre-check); host `0.1.2-alpha.3` keeps the `ignorable` field on the envelope but `Session.append` offers no way to stamp it (its third parameter is `SurfaceIntent` for surface events only), and the persistence read path refuses unmarked unknown event types, so those lines — and unresolvable versions — also fail closed before any append. Audit then degrades to an in-memory mirror with marker-free feedback, so sessions stay loadable everywhere.
|
|
309
323
|
|
|
310
324
|
## Security boundaries
|
|
311
325
|
|
|
312
326
|
- **The reviewer is a model.** Its verdicts are advisory policy, not a security kernel; prefer `human`/`never` rules for irreversible operations.
|
|
313
327
|
- **Fail closed.** Every abnormal path (provider missing, capability gaps, start rejection, timeout, non-`completed` stop reason, missing/malformed verdict, audit-correlation failure) resolves through `fallbackPolicy`, default `rejected` — and the rejection feeds an auditable reason back to the model. `allow-once` grants unconditionally; it exists only for unattended deployments whose admin accepts that risk.
|
|
314
328
|
- **Read-only reviewer.** The reviewer's `toolFilter` allow-list (`read`/`glob`/`grep`) cannot write, edit, run bash, fetch the network, or delegate (`maxDepth` = its own depth). Its session log is persisted and auditable.
|
|
329
|
+
- **Context-isolated reviewer.** The reviewer child's steps are filtered on the documented `agent/pre-step` seam: only its own prompt and its own read-only tool results enter them. Workspace instruction files (`AGENTS.md` / `CLAUDE.md`), the harness runtime-context snapshot, and any context-injecting plugin are dropped before the loop appends them, so repository-controlled text never reaches the component that decides whether a call is allowed. This holds under EITHER subagent provider — those producers inject fresh into any new agent session, so the filter, not the provider choice, is what closes them. The filter is an allow-list over message SOURCES, so a plugin that declares a new source kind is dropped too.
|
|
315
330
|
- **Sensitive arguments are redacted** (key-name matching: `token`, `password`, `api_key`, `Authorization`, credentials, private keys …) before entering the reviewer prompt; the plugin never executes the reviewed arguments. Redaction is key-based, not content-based — do not AI-review tools whose argument values you cannot afford to show a model.
|
|
316
331
|
- **Hard disables explain themselves.** A `never` tool or risk rule rejects deterministically AND records a log-only `autoReview/rejection` event, then injects a `[auto-review-never]` marker into the denied tool result — the model learns the action is hard-disabled instead of retrying it (invariant-checked: marker ⟺ event).
|
|
317
332
|
- **Rejection circuit breaker.** A run of denials in one turn trips the breaker (`consecutiveDenies` / `windowDenies` inside `windowSize`), recorded as a log-only `autoReview/circuit` event; later requests follow its `action` (`delegate` / `reject` / `abort-turn`).
|
|
@@ -320,6 +335,7 @@ The server is read-only and deterministic: no network, no model, no writes.
|
|
|
320
335
|
|
|
321
336
|
## Known limitations
|
|
322
337
|
|
|
338
|
+
- **Two different exposures, two different answers — neither substitutes for the other.** *Injected* context (workspace instruction files, the runtime-context snapshot, third-party plugin injections) is injected fresh into any new agent session, so it reaches the reviewer identically under `reviewerProvider: fork` and `reviewerProvider: spawn` — measured byte-identical across both on the same request. The `agent/pre-step` source filter is what closes it, under either provider; **`spawn` alone does NOT keep workspace instructions out of the reviewer.** Separately, `fork` seeds the child with the delegating session's completed turns: that history is already the child's own log rather than a message entering a step, so the filter cannot touch it and only `spawn` avoids it, with the reviewer prompt's untrusted-transcript fence as the mitigation in between. In the two traces above the seeding produced no additional messages, so its practical impact is unquantified.
|
|
323
339
|
- The reviewer needs a working LLM route (inherited by default); without one every review falls back per `fallbackPolicy` — never a silent grant.
|
|
324
340
|
- `reviewerTools` names must exist as global tools in the profile; an unknown name fails the reviewer child loudly at the earliest point and falls back.
|
|
325
341
|
- Risk rules match the request `reason`, the `toolName`, or the redacted call `arguments` per their `field`; other conditions belong in `toolsPolicy.overrides`.
|
package/README.pt.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| Superfície | Status |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependências fixadas em `0.1.1-rc.2`; peers `>=0.1.0-rc.8 <0.2.0`) |
|
|
29
|
+
| Harness | DeepSeek Harness `0.1.1-rc.2` (dependências fixadas em `0.1.1-rc.2`; peers `>=0.1.0-rc.8 <0.2.0`) 0.1.2-alpha.3 (adaptado em 2026-09-01): o envelope de sessão mantém seu campo ignorable apenas para compatibilidade de leitura de logs armazenados - o Session.append ainda não consegue estampá-lo, então o comportamento da porta não muda. |
|
|
30
30
|
| Node | `^22.19.0 \|\| >=24.0.0` |
|
|
31
31
|
| Plataformas | Todas (answerer de host; painel web opcional via capacidade de projeção de sessão) |
|
|
32
32
|
| Modelo | Qualquer (o revisor herda a rota do agente da sessão; `reviewerModel` sobrescreve) |
|
|
@@ -94,11 +94,11 @@ Todos os ajustes são campos Schemastery `Config` (alteráveis no cordis.yml). U
|
|
|
94
94
|
| `reviewerGuidance` | *(nenhuma)* | Orientação opcional anexada ao prompt do revisor |
|
|
95
95
|
| `reviewerPolicyText` | *(nenhuma)* | Política de decisão em Markdown injetada no prompt do revisor (estilo Codex) |
|
|
96
96
|
| `denyGuidance` | *(texto anti-elusão)* | Orientação anexada a cada razão de negação injetada |
|
|
97
|
-
| `contextBudget` | `{turns:
|
|
97
|
+
| `contextBudget` | `{turns: 2, maxChars: 4000}` | Orçamento de transcrição compacta para o prompt do revisor (o turno aberto mais o anterior); `turns: 0` desativa a seção — e um revisor cego nega ações autorizadas pelo usuário, então o runtime avisa quando 0 encontra uma política `ai`. O orçamento de caracteres é gasto nas linhas mais recentes |
|
|
98
98
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | Vereditos `allow` acima de `maxAutoAllow` delegam ou negam |
|
|
99
99
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | Disjuntor de rejeições |
|
|
100
100
|
| `overrideTtlMs` | `300000` | Quanto tempo dura uma anulação de `/auto-review approve` |
|
|
101
|
-
| `verdictCacheTtlMs` | `60000` | Reutiliza um veredito recente para uma impressão `ferramenta + argumentos` idêntica; `0` desativa o cache |
|
|
101
|
+
| `verdictCacheTtlMs` | `60000` | Reutiliza um veredito recente para uma impressão `ferramenta + argumentos` idêntica; `0` desativa o cache. Só se aplica com `contextBudget.turns: 0` — um veredito que depende da transcrição não é reproduzível apenas a partir de `ferramenta + argumentos` |
|
|
102
102
|
| `verdictCacheMaxEntries` | `256` | Máximo de impressões em cache antes de despejar a mais antiga |
|
|
103
103
|
| `language` | `en` | Idioma da UI da saída do comando `/auto-review` (`en` \| `zh`) |
|
|
104
104
|
| `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 |
|
|
@@ -124,6 +124,20 @@ Exemplo (forma completa anotada: `fixtures/config/config-full.yaml`):
|
|
|
124
124
|
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### De onde a configuração realmente vem
|
|
128
|
+
|
|
129
|
+
**`~/.dsh/settings.yaml` NÃO é uma fonte de configuração para este plugin.** Um bloco `auto-review:` ali não tem efeito nem gera aviso: como todo plugin-função do DSH, `dsh-auto-review` recebe seu `Config` da linha com que o loader o monta — a camada de patch cordis do perfil. (Alguns outros plugins do DSH também leem o serviço de settings, então a inconsistência é fácil de encontrar e o sintoma é indistinguível de o revisor simplesmente negar.)
|
|
130
|
+
|
|
131
|
+
Coloque a configuração no `cordis.patch.yml` do seu perfil. Uma **substituição direcionada por id troca a linha de config inteira**, então repita cada chave necessária — omitir `toolsPolicy` devolve `bash`/`write` ao padrão do schema, `human`, e o revisor para de rodar por completo:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
- id: auto-review
|
|
135
|
+
config:
|
|
136
|
+
toolsPolicy:
|
|
137
|
+
overrides: { bash: ai, write: ai }
|
|
138
|
+
contextBudget: { turns: 4, maxChars: 8000 }
|
|
139
|
+
```
|
|
140
|
+
|
|
127
141
|
## Ferramentas e superfícies
|
|
128
142
|
|
|
129
143
|
| Superfície | Tipo | Notas |
|
|
@@ -310,6 +324,7 @@ O servidor é somente-leitura e determinista: sem rede, sem modelo, sem gravaç
|
|
|
310
324
|
- **O revisor é um modelo.** Seus vereditos são política consultiva, não um núcleo de segurança; prefira regras `human`/`never` para operações irreversíveis.
|
|
311
325
|
- **Falha fechada.** Todo caminho anômalo (provedor ausente, lacunas de capacidade, rejeição ao iniciar, timeout, razão de parada não `completed`, veredito ausente/malformado, falha de correlação de auditoria) se resolve via `fallbackPolicy`, padrão `rejected` — e a rejeição devolve uma razão auditável ao modelo. `allow-once` concede incondicionalmente; existe apenas para implantações desatendidas cujo administrador aceita esse risco.
|
|
312
326
|
- **Revisor somente leitura.** A lista de permissões `toolFilter` do revisor (`read`/`glob`/`grep`) não pode escrever, editar, executar bash, acessar a rede nem delegar (`maxDepth` = sua própria profundidade). Seu log de sessão é persistido e auditável.
|
|
327
|
+
- **Revisor isolado do contexto.** Os passos do filho revisor são filtrados na costura oficial `agent/pre-step`: apenas o seu próprio prompt e os resultados das suas próprias ferramentas somente leitura entram. Arquivos de instruções do workspace (`AGENTS.md` / `CLAUDE.md`), o instantâneo de contexto de execução do harness e qualquer plugin que injete contexto são descartados antes de o laço anexá-los, de modo que texto controlado pelo repositório nunca chega ao componente que decide se uma chamada é permitida. Isso vale sob QUALQUER provedor de subagente — esses produtores injetam de novo em cada sessão de agente, então o que os fecha é o filtro, não a escolha do provedor. A lista de permissões é por ORIGEM da mensagem, então um plugin que declare uma origem nova também é descartado.
|
|
313
328
|
- **Argumentos sensíveis são redigidos** (correspondência por nome de chave: `token`, `password`, `api_key`, `Authorization`, credenciais, chaves privadas …) antes de entrar no prompt do revisor; o plugin nunca executa os argumentos revisados. A redação é baseada em chave, não em conteúdo — não revise com IA ferramentas cujos valores de argumento você não pode se dar ao luxo de mostrar a um modelo.
|
|
314
329
|
- **Desativações duras se explicam.** Uma ferramenta ou regra de risco `never` rejeita de forma determinística E registra um evento somente-log `autoReview/rejection`, e então injeta um marcador `[auto-review-never]` no resultado da ferramenta negada — o modelo aprende que a ação está duramente desativada em vez de tentar novamente (verificado por invariant: marcador ⟺ evento).
|
|
315
330
|
- **Disjuntor de rejeições.** Uma sequência de negações em um turno dispara o disjuntor (`consecutiveDenies` / `windowDenies` dentro de `windowSize`), registrada como um evento somente-log `autoReview/circuit`; solicitações posteriores seguem sua `action` (`delegate` / `reject` / `abort-turn`).
|
|
@@ -318,6 +333,7 @@ O servidor é somente-leitura e determinista: sem rede, sem modelo, sem gravaç
|
|
|
318
333
|
|
|
319
334
|
## Limitações conhecidas
|
|
320
335
|
|
|
336
|
+
- **Duas exposições diferentes, duas respostas diferentes — nenhuma substitui a outra.** O contexto *injetado* (arquivos de instruções do workspace, o instantâneo de contexto de execução, injeções de plugins de terceiros) é injetado de novo em cada sessão de agente, então chega ao revisor de forma idêntica com `reviewerProvider: fork` e com `reviewerProvider: spawn` — medido byte a byte idêntico em ambos na mesma requisição. O filtro por origem de `agent/pre-step` é o que o fecha, sob qualquer um dos provedores; **`spawn` sozinho NÃO impede que as instruções do workspace cheguem ao revisor.** À parte, `fork` semeia o filho com os turnos concluídos da sessão delegante: esse histórico já é o log do próprio filho, não uma mensagem entrando em um passo, então o filtro não o alcança e só `spawn` o evita, com a cerca de transcrição não confiável do prompt do revisor como mitigação intermediária. Nos dois traços acima a semeadura não produziu mensagens adicionais, então seu impacto prático não está quantificado.
|
|
321
337
|
- O revisor precisa de uma rota LLM funcional (herdada por padrão); sem ela, cada revisão cai conforme `fallbackPolicy` — nunca uma concessão silenciosa.
|
|
322
338
|
- Os nomes em `reviewerTools` devem existir como ferramentas globais no perfil; um nome desconhecido faz o filho revisor falhar em voz alta no ponto mais cedo e cai em fallback.
|
|
323
339
|
- Regras de risco emparelham o `reason` da solicitação, o `toolName` ou os `arguments` redigidos da chamada conforme seu `field`; outras condições pertencem a `toolsPolicy.overrides`.
|
package/README.zh.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| 方面 | 状态 |
|
|
28
28
|
|---|---|
|
|
29
|
-
| Harness | DeepSeek Harness `0.1.1-rc.2`(依赖锁定在 `0.1.1-rc.2`;peer 范围 `>=0.1.0-rc.8 <0.2.0`) |
|
|
29
|
+
| Harness | DeepSeek Harness `0.1.1-rc.2`(依赖锁定在 `0.1.1-rc.2`;peer 范围 `>=0.1.0-rc.8 <0.2.0`) 0.1.2-alpha.3(2026-09-01 已适配):会话信封保留 ignorable 字段但仅用于存量日志读取兼容——Session.append 仍无法盖章,门控行为不变。 |
|
|
30
30
|
| Node | `^22.19.0 \|\| >=24.0.0` |
|
|
31
31
|
| 平台 | 全部(宿主 answerer;可选 Web 审查面板,依赖会话投影能力) |
|
|
32
32
|
| 模型 | 任意(审查器默认继承会话代理的路由;`reviewerModel` 可覆盖) |
|
|
@@ -94,11 +94,11 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
94
94
|
| `reviewerGuidance` | *(无)* | 追加到审查器提示词的可选指导性说明 |
|
|
95
95
|
| `reviewerPolicyText` | *(无)* | 注入审查器提示词的 Markdown 裁决策略(Codex 风格) |
|
|
96
96
|
| `denyGuidance` | *(反规避文本)* | 追加到每一条注入的拒绝理由之后的指导 |
|
|
97
|
-
| `contextBudget` | `{turns:
|
|
97
|
+
| `contextBudget` | `{turns: 2, maxChars: 4000}` | 审查器提示词的紧凑记录预算(当前回合加上一回合);`turns: 0` 表示禁用该段落——没有记录的审查器会拒绝用户已授权的操作,因此当 0 与 `ai` 策略同时出现时运行时会发出警告。字符预算优先用于最近的行 |
|
|
98
98
|
| `riskPolicy` | `{maxAutoAllow: high, onHighRisk: delegate}` | 超过 `maxAutoAllow` 的 `allow` 裁决委派或拒绝 |
|
|
99
99
|
| `circuitBreaker` | `{consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate}` | 拒绝熔断器 |
|
|
100
100
|
| `overrideTtlMs` | `300000` | `/auto-review approve` 覆盖的有效时长 |
|
|
101
|
-
| `verdictCacheTtlMs` | `60000` | 对相同 `工具 + 参数` 指纹复用近期裁决的时长;`0`
|
|
101
|
+
| `verdictCacheTtlMs` | `60000` | 对相同 `工具 + 参数` 指纹复用近期裁决的时长;`0` 关闭缓存。仅在 `contextBudget.turns: 0` 时生效——依赖会话记录的裁决无法仅凭 `工具 + 参数` 重放 |
|
|
102
102
|
| `verdictCacheMaxEntries` | `256` | 缓存指纹上限,超出后淘汰最旧条目 |
|
|
103
103
|
| `language` | `en` | `/auto-review` 命令输出的界面语言(`en` \| `zh`) |
|
|
104
104
|
| `allowUnmarkedAudit` | `false` | 强制在丢弃 `ignorable` 标记的宿主上写入会话审计(危险:未标记事件会让会话在其他宿主上无法恢复);默认自动探测并降级 |
|
|
@@ -124,6 +124,20 @@ dsh --profile web --dump-config | grep -A4 'id: auto-review'
|
|
|
124
124
|
circuitBreaker: { consecutiveDenies: 3, windowDenies: 6, windowSize: 10, action: delegate }
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
### 配置究竟从哪里来
|
|
128
|
+
|
|
129
|
+
**`~/.dsh/settings.yaml` 不是本插件的配置来源。** 在其中写 `auto-review:` 块不会生效,也不会有任何警告:与每个 DSH 函数插件一样,`dsh-auto-review` 的 `Config` 来自 Loader 挂载它的那一行——也就是 profile 的 cordis patch 层。(部分其他 DSH 插件还会读取 settings 服务,因此这种不一致很容易踩坑,而且其症状与“审查器就是拒绝了”完全无法区分。)
|
|
130
|
+
|
|
131
|
+
请把配置写进 profile 的 `cordis.patch.yml`。**按 id 定向的 override 会替换整行 config**,因此需要重述你所需的每一个键——省略 `toolsPolicy` 会让 `bash`/`write` 悄悄回落到 schema 默认值 `human`,审查器随即完全不再运行:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
- id: auto-review
|
|
135
|
+
config:
|
|
136
|
+
toolsPolicy:
|
|
137
|
+
overrides: { bash: ai, write: ai }
|
|
138
|
+
contextBudget: { turns: 4, maxChars: 8000 }
|
|
139
|
+
```
|
|
140
|
+
|
|
127
141
|
## 工具与界面
|
|
128
142
|
|
|
129
143
|
| 界面 | 类型 | 说明 |
|
|
@@ -310,6 +324,7 @@ Claude Desktop(`claude_desktop_config.json`)配置示例:
|
|
|
310
324
|
- **审查器是模型。** 其裁决是建议性策略,不是安全内核;对不可逆操作优先使用 `human`/`never` 规则。
|
|
311
325
|
- **失败关闭。** 每条异常路径(provider 缺失、能力缺口、启动拒绝、超时、非 `completed` 停止原因、缺失/畸形裁决、审计关联失败)都经由 `fallbackPolicy` 处理,默认 `rejected` —— 且拒绝会向模型反馈一条可审计的理由。`allow-once` 是无条件放行;它只用于接受该风险的无值守部署。
|
|
312
326
|
- **只读审查器。** 审查器的 `toolFilter` 白名单(`read`/`glob`/`grep`)无法写入、编辑、运行 bash、访问网络或委派(`maxDepth` = 自身深度)。其会话日志被持久化且可审计。
|
|
327
|
+
- **上下文隔离的审查器。** 审查器子代理的每一步都在官方 `agent/pre-step` 接缝上被过滤:只有它自己的提示词与它自己的只读工具结果能够进入。工作区指令文件(`AGENTS.md` / `CLAUDE.md`)、宿主运行时上下文快照,以及任何注入上下文的插件,都会在循环追加它们之前被丢弃,因此仓库可控的文本永远不会抵达决定是否放行调用的组件。这在任一 subagent provider 下都成立——这些注入方会向任何新建的 agent 会话重新注入,因此关闭它们的是这道过滤,而不是 provider 的选择。白名单按消息来源判定,所以声明了新来源类型的插件同样会被丢弃。
|
|
313
328
|
- **敏感参数会被脱敏**(按键名匹配:`token`、`password`、`api_key`、`Authorization`、凭据、私钥……)后才进入审查器提示词;该插件绝不会执行被审查的参数。脱敏是按键名而非内容 —— 不要把参数值经不起展示给模型的工具交给 AI 审查。
|
|
314
329
|
- **硬禁用会自我解释。** `never` 工具或风险规则确定性拒绝,并记录一条仅日志的 `autoReview/rejection` 事件,然后把 `[auto-review-never]` 标记注入被拒绝的工具结果 —— 模型学会该操作已被硬禁用,而不是重试(invariant 校验:标记 ⟺ 事件)。
|
|
315
330
|
- **拒绝熔断器。** 一轮内连续拒绝会触发熔断(`consecutiveDenies` / 窗口内的 `windowDenies`),记录为仅日志的 `autoReview/circuit` 事件;后续请求按其 `action`(`delegate` / `reject` / `abort-turn`)处理。
|
|
@@ -318,6 +333,7 @@ Claude Desktop(`claude_desktop_config.json`)配置示例:
|
|
|
318
333
|
|
|
319
334
|
## 已知限制
|
|
320
335
|
|
|
336
|
+
- **两种不同的暴露面,两种不同的解法——彼此不能互相替代。** *注入式*上下文(工作区指令文件、运行时上下文快照、第三方插件注入)会被重新注入到任何新建的 agent 会话中,因此在 `reviewerProvider: fork` 与 `reviewerProvider: spawn` 下抵达审查器的内容完全相同——同一请求下实测逐字节一致。关闭它的是 `agent/pre-step` 来源过滤,且在两种 provider 下都有效;**仅切换到 `spawn` 并不能阻止工作区指令进入审查器。** 另一方面,`fork` 会用委派会话已完成的回合为子会话播种:那段历史已经是子会话自己的日志,而不是进入某一步的消息,因此过滤无法触及,只有 `spawn` 能避免,两者之间由审查器提示词的“不可信记录”围栏作为缓解。在上述两次追踪中,播种并未产生额外消息,因此其实际影响尚未量化。
|
|
321
337
|
- 审查器需要可用的 LLM 路由(默认继承);没有路由时,每次审查都会按 `fallbackPolicy` 回退 —— 绝不会静默放行。
|
|
322
338
|
- `reviewerTools` 中的名称必须是 profile 中已存在的全局工具;未知名称会使审查器子代理在最早点大声失败并回退。
|
|
323
339
|
- 风险规则按各自的 `field` 匹配请求的 `reason`、`toolName` 或脱敏后的调用 `arguments`;其他条件应放入 `toolsPolicy.overrides`。
|
package/lib/client.js
CHANGED
|
@@ -466,7 +466,9 @@ window.__ModuleLoader__.load({
|
|
|
466
466
|
en
|
|
467
467
|
}), "dsh-auto-review: dictionaries");
|
|
468
468
|
ctx.effect(() => installPanelStyles(), "dsh-auto-review: stylesheet");
|
|
469
|
-
ctx.
|
|
469
|
+
const slots = ctx.get("slots");
|
|
470
|
+
if (slots === void 0) return;
|
|
471
|
+
slots.inject("conversation.session.header.actions", () => slots.register({
|
|
470
472
|
name: "conversation.session.header.actions",
|
|
471
473
|
id: "auto-review",
|
|
472
474
|
order: 40,
|
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 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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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["readProjection","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 // The projection seat is structural: the host removed the client-runtime\n // merge that used to type it, and the seat differs across harness lines\n // (alpha.2 serves useConversation/useInput standard props), so the panel\n // reads it as an optional seat and casts at the single call site.\n & { readonly useProjection?: unknown }\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 readProjection = useProjection as unknown as (unit: 'autoReview') => AutoReviewProjection | undefined\n const value = readProjection('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 { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type { SessionId } from '@deepseek-ai/dsh-session/types'\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 slots registry face (structural; the real `ctx.slots` satisfies it).\n * Pinned locally because the host removed `@deepseek-ai/dsh-client-runtime`,\n * whose `/client` types used to merge `slots` onto the Context — the runtime\n * contract is what this client depends on.\n */\ninterface SlotsLike {\n inject(key: string, callback: () => unknown): unknown\n register(options: object, component: unknown): unknown\n}\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 const slots = ctx.get('slots') as SlotsLike | undefined\n if (slots === undefined) return\n slots.inject(\n 'conversation.session.header.actions',\n () => 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as parseSuite, a as EvalEngine, i as writeReports, o as evalReviewConfig, r as renderTerminalSummary, t as exitCodeFor } from "./report-
|
|
1
|
+
import { A as parseSuite, a as EvalEngine, i as writeReports, o as evalReviewConfig, r as renderTerminalSummary, t as exitCodeFor } from "./report-B3Uq-u1O.js";
|
|
2
2
|
import { mkdir, readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
@@ -6,13 +6,6 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { boot, installFailLoud, loadEnv, resolveConfigPath } from "@deepseek-ai/dsh-app-boot";
|
|
8
8
|
import z from "@deepseek-ai/schemastery";
|
|
9
|
-
//#region src/config.ts
|
|
10
|
-
/**
|
|
11
|
-
* Config schema and resolution for `dsh-auto-review`. Every tunable is a
|
|
12
|
-
* validated {@link Config} field changeable from cordis.yml; the resolution
|
|
13
|
-
* step compiles risk-rule regexes and fails loud on invalid input.
|
|
14
|
-
* @module dsh-auto-review/config
|
|
15
|
-
*/
|
|
16
9
|
const POLICY = z.union([
|
|
17
10
|
"ai",
|
|
18
11
|
"human",
|
|
@@ -68,10 +61,10 @@ z.object({
|
|
|
68
61
|
reviewerPolicyText: z.string(),
|
|
69
62
|
denyGuidance: z.string().default("Do not attempt to work around this denial. Choose a materially safer alternative that stays inside your permissions, or ask the user before retrying this action."),
|
|
70
63
|
contextBudget: z.object({
|
|
71
|
-
turns: z.number().default(
|
|
64
|
+
turns: z.number().default(2),
|
|
72
65
|
maxChars: z.number().default(4e3)
|
|
73
66
|
}).default({
|
|
74
|
-
turns:
|
|
67
|
+
turns: 2,
|
|
75
68
|
maxChars: 4e3
|
|
76
69
|
}),
|
|
77
70
|
riskPolicy: z.object({
|
|
@@ -112,7 +105,7 @@ function resolveConfig(config = {}) {
|
|
|
112
105
|
if (!Number.isSafeInteger(config.maxReviewsPerTurn ?? 10) || (config.maxReviewsPerTurn ?? 10) <= 0) throw new TypeError(`maxReviewsPerTurn must be a positive safe integer, got ${String(config.maxReviewsPerTurn)}`);
|
|
113
106
|
if (!Number.isSafeInteger(config.maxFailuresPerTurn ?? 10) || (config.maxFailuresPerTurn ?? 10) <= 0) throw new TypeError(`maxFailuresPerTurn must be a positive safe integer, got ${String(config.maxFailuresPerTurn)}`);
|
|
114
107
|
if (!Number.isSafeInteger(config.reasonMaxChars ?? 2e3) || (config.reasonMaxChars ?? 2e3) <= 0) throw new TypeError(`reasonMaxChars must be a positive safe integer, got ${String(config.reasonMaxChars)}`);
|
|
115
|
-
const turns = config.contextBudget?.turns ??
|
|
108
|
+
const turns = config.contextBudget?.turns ?? 2;
|
|
116
109
|
if (!Number.isSafeInteger(turns) || turns < 0) throw new TypeError(`contextBudget.turns must be a non-negative safe integer, got ${String(turns)}`);
|
|
117
110
|
const contextChars = config.contextBudget?.maxChars ?? 4e3;
|
|
118
111
|
if (!Number.isSafeInteger(contextChars) || contextChars <= 0) throw new TypeError(`contextBudget.maxChars must be a positive safe integer, got ${String(contextChars)}`);
|