dsh-auto-review 0.13.4 → 0.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +0 -0
- package/README-es.md +1 -1
- package/README-hi.md +4 -2
- package/README-pt.md +1 -1
- package/README-zh.md +1 -1
- package/README.md +1 -1
- package/bin/dsh-auto-review-mcp.mjs +0 -0
- package/bin/dsh-eval.mjs +0 -0
- package/cordis.patch.yml +0 -0
- package/eval/cases/demo.yaml +0 -0
- package/eval/cordis.yml +0 -0
- package/lib/client.js +0 -0
- package/lib/client.js.map +0 -0
- package/lib/eval-cli.js +0 -0
- package/lib/eval.js +0 -0
- package/lib/index.js +190 -15
- package/lib/invariant.js +0 -0
- package/lib/mcp.js +0 -0
- package/lib/report-Df29db58.js +0 -0
- package/lib/session-message-emzsbx_9.js +0 -0
- package/lib/types/audit.d.ts +0 -0
- package/lib/types/audit.d.ts.map +0 -0
- package/lib/types/audit.js +0 -0
- package/lib/types/audit.js.map +0 -0
- package/lib/types/cache.d.ts +0 -0
- package/lib/types/cache.d.ts.map +0 -0
- package/lib/types/cache.js +0 -0
- package/lib/types/cache.js.map +0 -0
- package/lib/types/call-id.d.ts +0 -0
- package/lib/types/call-id.d.ts.map +0 -0
- package/lib/types/call-id.js +0 -0
- package/lib/types/call-id.js.map +0 -0
- package/lib/types/client/ReviewPanel.d.ts +0 -0
- package/lib/types/client/ReviewPanel.d.ts.map +0 -0
- package/lib/types/client/ReviewPanel.js +0 -0
- package/lib/types/client/ReviewPanel.js.map +0 -0
- package/lib/types/client/index.d.ts +0 -0
- package/lib/types/client/index.d.ts.map +0 -0
- package/lib/types/client/index.js +0 -0
- package/lib/types/client/index.js.map +0 -0
- package/lib/types/client/locales.d.ts +0 -0
- package/lib/types/client/locales.d.ts.map +0 -0
- package/lib/types/client/locales.js +0 -0
- package/lib/types/client/locales.js.map +0 -0
- package/lib/types/client/present.d.ts +0 -0
- package/lib/types/client/present.d.ts.map +0 -0
- package/lib/types/client/present.js +0 -0
- package/lib/types/client/present.js.map +0 -0
- package/lib/types/client/styles.d.ts +0 -0
- package/lib/types/client/styles.d.ts.map +0 -0
- package/lib/types/client/styles.js +0 -0
- package/lib/types/client/styles.js.map +0 -0
- package/lib/types/config.d.ts +0 -0
- package/lib/types/config.d.ts.map +0 -0
- package/lib/types/config.js +0 -0
- package/lib/types/config.js.map +0 -0
- package/lib/types/eval/assert.d.ts +0 -0
- package/lib/types/eval/assert.d.ts.map +0 -0
- package/lib/types/eval/assert.js +0 -0
- package/lib/types/eval/assert.js.map +0 -0
- package/lib/types/eval/cli.d.ts +0 -0
- package/lib/types/eval/cli.d.ts.map +0 -0
- package/lib/types/eval/cli.js +0 -0
- package/lib/types/eval/cli.js.map +0 -0
- package/lib/types/eval/diff.d.ts +0 -0
- package/lib/types/eval/diff.d.ts.map +0 -0
- package/lib/types/eval/diff.js +0 -0
- package/lib/types/eval/diff.js.map +0 -0
- package/lib/types/eval/dsl.d.ts +0 -0
- package/lib/types/eval/dsl.d.ts.map +0 -0
- package/lib/types/eval/dsl.js +0 -0
- package/lib/types/eval/dsl.js.map +0 -0
- package/lib/types/eval/index.d.ts +0 -0
- package/lib/types/eval/index.d.ts.map +0 -0
- package/lib/types/eval/index.js +0 -0
- package/lib/types/eval/index.js.map +0 -0
- package/lib/types/eval/report.d.ts +0 -0
- package/lib/types/eval/report.d.ts.map +0 -0
- package/lib/types/eval/report.js +0 -0
- package/lib/types/eval/report.js.map +0 -0
- package/lib/types/eval/review.d.ts +0 -0
- package/lib/types/eval/review.d.ts.map +0 -0
- package/lib/types/eval/review.js +0 -0
- package/lib/types/eval/review.js.map +0 -0
- package/lib/types/eval/runner.d.ts +0 -0
- package/lib/types/eval/runner.d.ts.map +0 -0
- package/lib/types/eval/runner.js +0 -0
- package/lib/types/eval/runner.js.map +0 -0
- package/lib/types/eval/stress.d.ts +0 -0
- package/lib/types/eval/stress.d.ts.map +0 -0
- package/lib/types/eval/stress.js +0 -0
- package/lib/types/eval/stress.js.map +0 -0
- package/lib/types/eval/trace.d.ts +0 -0
- package/lib/types/eval/trace.d.ts.map +0 -0
- package/lib/types/eval/trace.js +0 -0
- package/lib/types/eval/trace.js.map +0 -0
- package/lib/types/events.d.ts +0 -0
- package/lib/types/events.d.ts.map +0 -0
- package/lib/types/events.js +0 -0
- package/lib/types/events.js.map +0 -0
- package/lib/types/index.d.ts +0 -0
- package/lib/types/index.d.ts.map +0 -0
- package/lib/types/index.js +0 -0
- package/lib/types/index.js.map +0 -0
- package/lib/types/invariant.d.ts +0 -0
- package/lib/types/invariant.d.ts.map +0 -0
- package/lib/types/invariant.js +0 -0
- package/lib/types/invariant.js.map +0 -0
- package/lib/types/isolation.d.ts +0 -0
- package/lib/types/isolation.d.ts.map +0 -0
- package/lib/types/isolation.js +0 -0
- package/lib/types/isolation.js.map +0 -0
- package/lib/types/mcp/index.d.ts +0 -0
- package/lib/types/mcp/index.d.ts.map +0 -0
- package/lib/types/mcp/index.js +0 -0
- package/lib/types/mcp/index.js.map +0 -0
- package/lib/types/mcp/server.d.ts +0 -0
- package/lib/types/mcp/server.d.ts.map +0 -0
- package/lib/types/mcp/server.js +0 -0
- package/lib/types/mcp/server.js.map +0 -0
- package/lib/types/mcp/standalone.d.ts +0 -0
- package/lib/types/mcp/standalone.d.ts.map +0 -0
- package/lib/types/mcp/standalone.js +0 -0
- package/lib/types/mcp/standalone.js.map +0 -0
- package/lib/types/message-source.d.ts +0 -0
- package/lib/types/message-source.d.ts.map +0 -0
- package/lib/types/message-source.js +0 -0
- package/lib/types/message-source.js.map +0 -0
- package/lib/types/messages.d.ts +0 -0
- package/lib/types/messages.d.ts.map +0 -0
- package/lib/types/messages.js +0 -0
- package/lib/types/messages.js.map +0 -0
- package/lib/types/projection-types.d.ts +9 -1
- package/lib/types/projection-types.d.ts.map +1 -1
- package/lib/types/projection-types.js +0 -0
- package/lib/types/projection-types.js.map +0 -0
- package/lib/types/projection.d.ts +22 -2
- package/lib/types/projection.d.ts.map +1 -1
- package/lib/types/projection.js +27 -7
- 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 +51 -6
- package/lib/types/review.js.map +1 -1
- package/lib/types/runtime.d.ts +40 -1
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +134 -4
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/session-events.d.ts +0 -0
- package/lib/types/session-events.d.ts.map +0 -0
- package/lib/types/session-events.js +0 -0
- package/lib/types/session-events.js.map +0 -0
- package/lib/types/session-message.d.ts +0 -0
- package/lib/types/session-message.d.ts.map +0 -0
- package/lib/types/session-message.js +0 -0
- package/lib/types/session-message.js.map +0 -0
- package/package.json +1 -1
- package/src/audit.ts +0 -0
- package/src/cache.ts +0 -0
- package/src/call-id.ts +0 -0
- package/src/client/ReviewPanel.tsx +0 -0
- package/src/client/index.ts +0 -0
- package/src/client/locales.ts +0 -0
- package/src/client/present.ts +0 -0
- package/src/client/styles.ts +0 -0
- package/src/config.ts +0 -0
- package/src/eval/assert.ts +0 -0
- package/src/eval/cli.ts +0 -0
- package/src/eval/diff.ts +0 -0
- package/src/eval/dsl.ts +0 -0
- package/src/eval/index.ts +0 -0
- package/src/eval/report.ts +0 -0
- package/src/eval/review.ts +0 -0
- package/src/eval/runner.ts +0 -0
- package/src/eval/stress.ts +0 -0
- package/src/eval/trace.ts +0 -0
- package/src/events.ts +0 -0
- package/src/index.ts +0 -0
- package/src/invariant.ts +0 -0
- package/src/isolation.ts +0 -0
- package/src/mcp/index.ts +0 -0
- package/src/mcp/server.ts +0 -0
- package/src/mcp/standalone.ts +0 -0
- package/src/message-source.ts +0 -0
- package/src/messages.ts +0 -0
- package/src/projection-types.ts +9 -1
- package/src/projection.ts +30 -7
- package/src/review.ts +53 -6
- package/src/runtime.ts +154 -4
- package/src/session-events.ts +0 -0
- package/src/session-message.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -46,6 +46,18 @@
|
|
|
46
46
|
|
|
47
47
|
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.
|
|
48
48
|
|
|
49
|
+
## [0.13.5] - 2026-10-18
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **A secret spelled inside a free-form string argument reached the reviewer verbatim.** `sanitizeArguments` redacts by object KEY, so it sees `{"apiKey": "..."}` but not `deploy --token sk-live-123` or `curl -H "Authorization: Bearer …"` — which is how a secret usually appears in a shell call. Both rendering paths are covered now: the pending call's own preview (`sanitizeArguments`) and the compact transcript line (`contextLine`), the second being the one that reached the reviewer intact. Known limit, pinned by a test: a bare secret with no key adjacent to it (`echo sk-live-123`) stays visible — this is a heuristic, not a secret scanner.
|
|
54
|
+
- **Truncated evidence read as complete evidence.** The pending call's preview is cut at `reasonMaxChars` (default 2000) behind a bare `…`, so a reviewer shown only the benign head of a long call could allow it with the decisive arguments cut off. A truncated preview is now labelled `TRUNCATED`, states the exact counts (`only the first <n> of <m> characters are shown; the remainder is NOT visible to you`), and the verdict rules gained the matching `- truncated evidence counts as insufficient: when the arguments are marked TRUNCATED, DENY.`
|
|
55
|
+
- **The review panel showed all zeros on every host that declines the audit events.** The `autoReview` projection folds the plugin's own `autoReview/*` session events, and on a host whose `Session.append` cannot stamp the `ignorable` marker none of them is ever written — which is every published line — so the panel folded an empty log while the in-memory mirror held the real numbers. The persisted state now carries the owning `sessionId` (state version 4) and the runtime registers a live wire view that serves the switch state and the session's activity from the mirror; on a marker-aware host the pure fold stays authoritative and is returned untouched.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `test/hardening.spec.ts` — a regression case per fix above, including the documented bare-secret limit. `test/projection.spec.ts` covers the state-version-4 contract and the substituted panel view.
|
|
60
|
+
|
|
49
61
|
## [0.12.17] - 2026-10-10
|
|
50
62
|
|
|
51
63
|
### Fixed
|
package/LICENSE
CHANGED
|
File without changes
|
package/README-es.md
CHANGED
|
@@ -360,7 +360,7 @@ El servidor es de solo lectura y determinista: sin red, sin modelo, sin escritur
|
|
|
360
360
|
- **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.
|
|
361
361
|
- **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.
|
|
362
362
|
- **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`) y los plugins que inyectan contexto se descartan en esa costura; **la instantánea de contexto de ejecución del propio harness se re-admite después de `agent/pre-step`, el firewall no la elimina y el revisor la ve.** 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.
|
|
363
|
-
- **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
|
|
363
|
+
- **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, y un argumento de texto libre pasa además por una heurística en línea que enmascara las formas `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE` y los esquemas `Bearer`/`Basic` — que es como suele aparecer un secreto en una llamada de shell. El plugin nunca ejecuta los argumentos revisados. Ambas pasadas son de mejor esfuerzo, no un escáner de secretos: un secreto sin clave al lado sigue siendo visible, así que no revises con IA herramientas cuyos valores de argumento no te puedas permitir mostrar a un modelo.
|
|
364
364
|
- **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).
|
|
365
365
|
- **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`).
|
|
366
366
|
- **El contexto del revisor es transcripción ya presentada.** `contextBudget` alimenta al revisor con contenido de sesión ya presentado. Con el modelo revisor de la misma ruta por defecto, ese contenido permanece dentro de un proveedor; configura `reviewerModel` a un proveedor diferente solo si aceptas presentarle esa transcripción.
|
package/README-hi.md
CHANGED
|
@@ -392,7 +392,9 @@ dsh-eval eval/cases --model deepseek-flash --timeout-ms 240000 --out .eval-repor
|
|
|
392
392
|
|
|
393
393
|
</details>
|
|
394
394
|
|
|
395
|
-
</details
|
|
395
|
+
</details>
|
|
396
|
+
|
|
397
|
+
## MCP सर्वर (स्टैंडअलोन)
|
|
396
398
|
|
|
397
399
|
`dsh-auto-review` एक stdio **MCP सर्वर** (`dsh-auto-review-mcp`) भी देता है ताकि बाहरी MCP क्लाइंट (Claude, Codex, …) बिना harness के एक निर्धारक समीक्षा पथ का उपयोग कर सकें। यह newline-delimited JSON (NDJSON) पर JSON-RPC 2.0 बोलता है — प्रति पंक्ति एक JSON ऑब्जेक्ट, कोई `Content-Length` फ़्रेमिंग नहीं।
|
|
398
400
|
|
|
@@ -447,7 +449,7 @@ Claude Desktop (`claude_desktop_config.json`) उदाहरण:
|
|
|
447
449
|
- **विफल-बंद।** हर असामान्य मार्ग (प्रदाता अनुपस्थित, क्षमता अंतराल, प्रारंभ अस्वीकृति, टाइमआउट, गैर-`completed` स्टॉप कारण, अनुपस्थित/विकृत निर्णय, ऑडिट-सहसंबंध विफलता) `fallbackPolicy` से हल होता है, डिफ़ॉल्ट `rejected` — और अस्वीकृति मॉडल को एक ऑडिट-योग्य कारण लौटाती है। `allow-once` बिना शर्त अनुमति देता है; यह केवल उन अप्रबंधित परिनियोजनों के लिए है जिनका व्यवस्थापक उस जोखिम को स्वीकार करता है।
|
|
448
450
|
- **केवल-पढ़ने वाला समीक्षक।** समीक्षक की `toolFilter` अनुमति-सूची (`read`/`glob`/`grep`) लिख, संपादित, bash चला, नेटवर्क उपयोग या प्रत्यायोजन नहीं कर सकती (`maxDepth` = उसकी अपनी गहराई)। उसका सत्र लॉग स्थायी और ऑडिट-योग्य है।
|
|
449
451
|
- **संदर्भ-पृथक समीक्षक।** समीक्षक चाइल्ड के हर चरण को आधिकारिक `agent/pre-step` सीम पर छाना जाता है: केवल उसका अपना प्रॉम्प्ट और उसके अपने केवल-पढ़ने वाले टूल परिणाम ही भीतर आते हैं। वर्कस्पेस निर्देश फ़ाइलें (`AGENTS.md` / `CLAUDE.md`), हार्नेस का रनटाइम-संदर्भ स्नैपशॉट और संदर्भ इंजेक्ट करने वाला कोई भी प्लगइन लूप द्वारा जोड़े जाने से पहले हटा दिए जाते हैं, इसलिए रिपॉज़िटरी-नियंत्रित पाठ कभी उस घटक तक नहीं पहुँचता जो कॉल की अनुमति तय करता है। यह किसी भी subagent प्रदाता के साथ लागू होता है — ये उत्पादक हर नए एजेंट सत्र में नए सिरे से इंजेक्ट करते हैं, इसलिए इन्हें बंद करने वाला यही फ़िल्टर है, प्रदाता का चुनाव नहीं। अनुमति-सूची संदेश के स्रोत पर आधारित है, इसलिए नया स्रोत घोषित करने वाला प्लगइन भी हटा दिया जाता है।
|
|
450
|
-
- **संवेदनशील तर्क संशोधित किए जाते हैं** (कुंजी-नाम मिलान: `token`, `password`, `api_key`, `Authorization`, क्रेडेंशियल, निजी कुंजियाँ …) समीक्षक प्रॉम्प्ट में जाने से
|
|
452
|
+
- **संवेदनशील तर्क संशोधित किए जाते हैं** (कुंजी-नाम मिलान: `token`, `password`, `api_key`, `Authorization`, क्रेडेंशियल, निजी कुंजियाँ …) समीक्षक प्रॉम्प्ट में जाने से पहले, और एक मुक्त-रूप स्ट्रिंग तर्क पर अतिरिक्त रूप से एक इनलाइन अनुमान लगता है जो `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE` रूपों तथा `Bearer`/`Basic` स्कीमों को छिपा देता है — शेल कॉल में सीक्रेट प्रायः इसी तरह दिखता है। प्लगइन समीक्षित तर्कों को कभी निष्पादित नहीं करता। दोनों पास सर्वोत्तम-प्रयास हैं, कोई सीक्रेट स्कैनर नहीं: बिना कुंजी वाला नंगा सीक्रेट दिखता रहता है, इसलिए उन टूल की AI समीक्षा न करें जिनके तर्क मान आप किसी मॉडल को दिखाने का जोखिम नहीं उठा सकते।
|
|
451
453
|
- **कठोर अक्षमताएँ स्वयं को समझाती हैं।** एक `never` टूल या जोखिम नियम निर्धारक रूप से अस्वीकार करता है और एक केवल-लॉग `autoReview/rejection` घटना दर्ज करता है, फिर अस्वीकृत टूल परिणाम में एक `[auto-review-never]` मार्कर इंजेक्ट करता है — मॉडल सीखता है कि क्रिया कठोर रूप से अक्षम है बजाय उसे पुनः प्रयास करने के (invariant-जाँचा गया: मार्कर ⟺ घटना)।
|
|
452
454
|
- **अस्वीकृति सर्किट ब्रेकर।** एक टर्न में अस्वीकृतियों की एक शृंखला ब्रेकर को ट्रिप करती है (`windowSize` के भीतर `consecutiveDenies` / `windowDenies`), जो एक केवल-लॉग `autoReview/circuit` घटना के रूप में दर्ज होती है; बाद के अनुरोध उसकी `action` (`delegate` / `reject` / `abort-turn`) का पालन करते हैं।
|
|
453
455
|
- **समीक्षक संदर्भ प्रस्तुत ट्रांसक्रिप्ट है।** `contextBudget` समीक्षक को पहले से प्रस्तुत सत्र सामग्री खिलाता है। डिफ़ॉल्ट समान-मार्ग समीक्षक मॉडल के साथ वह सामग्री एक ही प्रदाता के भीतर रहती है; `reviewerModel` को किसी भिन्न प्रदाता पर तभी कॉन्फ़िगर करें जब आप उस ट्रांसक्रिप्ट को उसे प्रस्तुत करना स्वीकार करते हों।
|
package/README-pt.md
CHANGED
|
@@ -360,7 +360,7 @@ O servidor é somente-leitura e determinista: sem rede, sem modelo, sem gravaç
|
|
|
360
360
|
- **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.
|
|
361
361
|
- **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.
|
|
362
362
|
- **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`) e plugins que injetam contexto são descartados nessa costura; **o instantâneo de contexto de execução do próprio harness é readmitido após `agent/pre-step`, o firewall não o remove e o revisor o vê.** 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.
|
|
363
|
-
- **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
|
|
363
|
+
- **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, e um argumento de texto livre passa ainda por uma heurística em linha que mascara as formas `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE` e os esquemas `Bearer`/`Basic` — que é como um segredo costuma aparecer numa chamada de shell. O plugin nunca executa os argumentos revisados. Ambas as passadas são de melhor esforço, não um scanner de segredos: um segredo sem chave ao lado continua visível, então não revise com IA ferramentas cujos valores de argumento você não pode se dar ao luxo de mostrar a um modelo.
|
|
364
364
|
- **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).
|
|
365
365
|
- **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`).
|
|
366
366
|
- **O contexto do revisor é transcrição apresentada.** `contextBudget` alimenta o revisor com conteúdo de sessão já apresentado. Com o modelo revisor da mesma rota por padrão, esse conteúdo permanece dentro de um provedor; configure `reviewerModel` para um provedor diferente apenas se você aceitar apresentar essa transcrição a ele.
|
package/README-zh.md
CHANGED
|
@@ -360,7 +360,7 @@ Claude Desktop(`claude_desktop_config.json`)配置示例:
|
|
|
360
360
|
- **失败关闭。** 每条异常路径(provider 缺失、能力缺口、启动拒绝、超时、非 `completed` 停止原因、缺失/畸形裁决、审计关联失败)都经由 `fallbackPolicy` 处理,默认 `rejected` —— 且拒绝会向模型反馈一条可审计的理由。`allow-once` 是无条件放行;它只用于接受该风险的无值守部署。
|
|
361
361
|
- **只读审查器。** 审查器的 `toolFilter` 白名单(`read`/`glob`/`grep`)无法写入、编辑、运行 bash、访问网络或委派(`maxDepth` = 自身深度)。其会话日志被持久化且可审计。
|
|
362
362
|
- **上下文隔离的审查器。** 审查器子代理的每一步都在官方 `agent/pre-step` 接缝上被过滤:只有它自己的提示词与它自己的只读工具结果能够进入。工作区指令文件(`AGENTS.md` / `CLAUDE.md`)与注入上下文的插件会在该接缝处被丢弃;**宿主自己的运行时上下文快照会在 `agent/pre-step` 之后被循环重新接纳,防火墙无法移除它,因此审查器能看到该快照。**这在任一 subagent provider 下都成立——这些注入方会向任何新建的 agent 会话重新注入,因此关闭它们的是这道过滤,而不是 provider 的选择。白名单按消息来源判定,所以声明了新来源类型的插件同样会被丢弃。
|
|
363
|
-
- **敏感参数会被脱敏**(按键名匹配:`token`、`password`、`api_key`、`Authorization
|
|
363
|
+
- **敏感参数会被脱敏**(按键名匹配:`token`、`password`、`api_key`、`Authorization`、凭据、私钥……)后才进入审查器提示词;自由字符串参数还会额外经过一层内联启发式,掩掉 `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE` 这几种写法以及 `Bearer`/`Basic` 方案 —— 而命令行里的密钥通常正是这样出现的。该插件绝不会执行被审查的参数。两层都是尽力而为,不是密钥扫描器:旁边没有键名的裸密钥仍然可见,所以不要把参数值经不起展示给模型的工具交给 AI 审查。
|
|
364
364
|
- **硬禁用会自我解释。** `never` 工具或风险规则确定性拒绝,并记录一条仅日志的 `autoReview/rejection` 事件,然后把 `[auto-review-never]` 标记注入被拒绝的工具结果 —— 模型学会该操作已被硬禁用,而不是重试(invariant 校验:标记 ⟺ 事件)。
|
|
365
365
|
- **拒绝熔断器。** 一轮内连续拒绝会触发熔断(`consecutiveDenies` / 窗口内的 `windowDenies`),记录为仅日志的 `autoReview/circuit` 事件;后续请求按其 `action`(`delegate` / `reject` / `abort-turn`)处理。
|
|
366
366
|
- **审查上下文是已呈现的 transcript。** `contextBudget` 把已呈现的会话内容喂给审查器。默认同路由审查模型时该内容停留在单一 provider 内;仅当你接受把该 transcript 呈现给另一 provider 时,才把 `reviewerModel` 配成别的 provider。
|
package/README.md
CHANGED
|
@@ -363,7 +363,7 @@ The server is read-only and deterministic: no network, no model, no writes.
|
|
|
363
363
|
- **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.
|
|
364
364
|
- **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.
|
|
365
365
|
- **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`) and context-injecting plugins are dropped from the step this seam controls. **The host's own runtime-context snapshot is re-admitted by the loop after `agent/pre-step`; the firewall does not remove it, so the reviewer sees it.** This holds under EITHER subagent provider. The filter is an allow-list over message SOURCES, so a plugin that declares a new source kind is dropped too.
|
|
366
|
-
- **Sensitive arguments are redacted** (key-name matching: `token`, `password`, `api_key`, `Authorization`, credentials, private keys …) before entering the reviewer prompt
|
|
366
|
+
- **Sensitive arguments are redacted** (key-name matching: `token`, `password`, `api_key`, `Authorization`, credentials, private keys …) before entering the reviewer prompt, and a free-form string argument additionally passes an inline heuristic that masks the `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE` spellings and `Bearer`/`Basic` schemes — which is how a secret usually appears in a shell call. The plugin never executes the reviewed arguments. Both passes are best-effort, not a secret scanner: a bare secret with no key next to it stays visible, so do not AI-review tools whose argument values you cannot afford to show a model.
|
|
367
367
|
- **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).
|
|
368
368
|
- **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`).
|
|
369
369
|
- **Reviewer context is presented transcript.** `contextBudget` feeds already-presented session content to the reviewer. With the default same-route reviewer model that content stays inside one provider; configure `reviewerModel` to a different provider only if you accept presenting that transcript to it.
|
|
File without changes
|
package/bin/dsh-eval.mjs
CHANGED
|
File without changes
|
package/cordis.patch.yml
CHANGED
|
File without changes
|
package/eval/cases/demo.yaml
CHANGED
|
File without changes
|
package/eval/cordis.yml
CHANGED
|
File without changes
|
package/lib/client.js
CHANGED
|
File without changes
|
package/lib/client.js.map
CHANGED
|
File without changes
|
package/lib/eval-cli.js
CHANGED
|
File without changes
|
package/lib/eval.js
CHANGED
|
File without changes
|
package/lib/index.js
CHANGED
|
@@ -5965,10 +5965,19 @@ const AUTO_REVIEW_PROJECTION_SCHEMA = object({
|
|
|
5965
5965
|
* State for the empty log. `enabled` starts at the resolved
|
|
5966
5966
|
* `enableByDefault` — the projection cannot know the plugin config on its
|
|
5967
5967
|
* own, so the unit is built per mount by {@link makeAutoReviewProjection}.
|
|
5968
|
+
*
|
|
5969
|
+
* `sessionId` is part of the state since state version 4: the fold records
|
|
5970
|
+
* which session it belongs to so a host that cannot accept the
|
|
5971
|
+
* `autoReview/*` audit events can serve the panel from the live in-memory
|
|
5972
|
+
* mirror instead (see `AutoReviewRuntime.livePanelView`). The pure fold
|
|
5973
|
+
* cannot resolve a session on its own, so the id is supplied by the caller
|
|
5974
|
+
* that knows the log header.
|
|
5968
5975
|
* @param enabledByDefault - the resolved `enableByDefault` config value.
|
|
5976
|
+
* @param sessionId - the owning session id, as text.
|
|
5969
5977
|
*/
|
|
5970
|
-
function initAutoReviewProjection(enabledByDefault) {
|
|
5978
|
+
function initAutoReviewProjection(enabledByDefault, sessionId) {
|
|
5971
5979
|
return {
|
|
5980
|
+
sessionId,
|
|
5972
5981
|
enabled: enabledByDefault,
|
|
5973
5982
|
turnOpen: false,
|
|
5974
5983
|
verdictsUsed: 0,
|
|
@@ -6088,6 +6097,7 @@ function viewAutoReview(state) {
|
|
|
6088
6097
|
}
|
|
6089
6098
|
/** Strict state schema — validates the persisted fold state before it seeds a fold. */
|
|
6090
6099
|
const AUTO_REVIEW_STATE_SCHEMA = object({
|
|
6100
|
+
sessionId: string(),
|
|
6091
6101
|
enabled: boolean(),
|
|
6092
6102
|
turnOpen: boolean(),
|
|
6093
6103
|
verdictsUsed: number().int().nonnegative(),
|
|
@@ -6110,20 +6120,29 @@ const AUTO_REVIEW_STATE_SCHEMA = object({
|
|
|
6110
6120
|
* the answerer applies. Registrants sharing the `autoReview` key share one
|
|
6111
6121
|
* unit; the first mount's default wins, which only matters for a double
|
|
6112
6122
|
* mount with conflicting `enableByDefault` values.
|
|
6123
|
+
*
|
|
6124
|
+
* `liveView` substitutes the wire `view`
|
|
6125
|
+
* with a runtime-aware one. The pure fold can only report what the session
|
|
6126
|
+
* log carries, and a host that refuses the unmarked `autoReview/*` events
|
|
6127
|
+
* writes none of them — which is why the panel used to read an empty log and
|
|
6128
|
+
* show all zeros. The runtime passes `AutoReviewRuntime.livePanelView`, which
|
|
6129
|
+
* falls back to the in-memory mirror on such a host and to the fold
|
|
6130
|
+
* everywhere else. Omitted (or `undefined`) keeps the pure fold.
|
|
6113
6131
|
* @param enabledByDefault - the resolved `enableByDefault` config value.
|
|
6132
|
+
* @param liveView - an optional runtime-aware wire view.
|
|
6114
6133
|
* @returns the projection unit for `ctx.sessionProjections.register`.
|
|
6115
6134
|
*/
|
|
6116
|
-
function makeAutoReviewProjection(enabledByDefault) {
|
|
6135
|
+
function makeAutoReviewProjection(enabledByDefault, liveView) {
|
|
6117
6136
|
return {
|
|
6118
6137
|
key: "autoReview",
|
|
6119
6138
|
stateSchema: AUTO_REVIEW_STATE_SCHEMA,
|
|
6120
|
-
init: (
|
|
6139
|
+
init: (header) => initAutoReviewProjection(enabledByDefault, String(header.id)),
|
|
6121
6140
|
apply: applyAutoReview,
|
|
6122
6141
|
wire: {
|
|
6123
6142
|
viewSchema: AUTO_REVIEW_PROJECTION_SCHEMA,
|
|
6124
|
-
view: viewAutoReview
|
|
6143
|
+
view: liveView === void 0 ? viewAutoReview : liveView
|
|
6125
6144
|
},
|
|
6126
|
-
stateVersion:
|
|
6145
|
+
stateVersion: 4
|
|
6127
6146
|
};
|
|
6128
6147
|
}
|
|
6129
6148
|
//#endregion
|
|
@@ -6168,8 +6187,33 @@ const SENSITIVE_KEY_PATTERN = /(?:api[_-]?key|token|secret|password|passwd|pwd|a
|
|
|
6168
6187
|
/** The redaction placeholder for sensitive argument values. */
|
|
6169
6188
|
const REDACTED = "[REDACTED]";
|
|
6170
6189
|
/**
|
|
6190
|
+
* Inline secrets inside a free-form STRING
|
|
6191
|
+
* argument (a shell command line), which the key-based pass cannot see because
|
|
6192
|
+
* it inspects keys only. Covers `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE`
|
|
6193
|
+
* spellings, with an optional `Bearer`/`Basic` scheme so
|
|
6194
|
+
* `Authorization: Bearer <token>` masks the token and not just the scheme word.
|
|
6195
|
+
* Best effort by construction: a bare secret with no key next to it stays
|
|
6196
|
+
* visible.
|
|
6197
|
+
*/
|
|
6198
|
+
const INLINE_SECRET_KEY = "(?:api[_-]?key|token|secret|password|passwd|pwd|authorization|credential|private[_-]?key|access[_-]?key)";
|
|
6199
|
+
/** The `KEY=VALUE` / `KEY: VALUE` spelling of an inline secret, scheme included. */
|
|
6200
|
+
const INLINE_SECRET_ASSIGN = new RegExp(`(${INLINE_SECRET_KEY}\\s*[:=]\\s*(?:Bearer\\s+|Basic\\s+)?)(["']?)([^\\s"';&|,)]+)`, "giu");
|
|
6201
|
+
/** The `--flag VALUE` spelling of an inline secret. */
|
|
6202
|
+
const INLINE_SECRET_FLAG = new RegExp(`(--?${INLINE_SECRET_KEY}\\s+)(["']?)([^\\s"';&|,)]+)`, "giu");
|
|
6203
|
+
/**
|
|
6204
|
+
* Redact inline secrets from a free-form string argument. The key-based pass
|
|
6205
|
+
* in {@link sanitizeArguments} only inspects object KEYS, so a secret spelled
|
|
6206
|
+
* inside a shell command line would otherwise reach the reviewer verbatim.
|
|
6207
|
+
* @param text - the free-form string to redact.
|
|
6208
|
+
* @returns the string with every recognized inline secret masked.
|
|
6209
|
+
*/
|
|
6210
|
+
function redactInlineSecrets(text) {
|
|
6211
|
+
return text.replace(INLINE_SECRET_ASSIGN, (_match, head, quote) => head + quote + REDACTED).replace(INLINE_SECRET_FLAG, (_match, head, quote) => head + quote + REDACTED);
|
|
6212
|
+
}
|
|
6213
|
+
/**
|
|
6171
6214
|
* Recursively redact sensitive values from a JSON value: any object key
|
|
6172
|
-
* matching {@link SENSITIVE_KEY_PATTERN} has its value replaced
|
|
6215
|
+
* matching {@link SENSITIVE_KEY_PATTERN} has its value replaced, and any
|
|
6216
|
+
* free-form string is passed through {@link redactInlineSecrets}.
|
|
6173
6217
|
* @param value - the detached JSON value to sanitize.
|
|
6174
6218
|
* @returns a detached copy with sensitive values redacted.
|
|
6175
6219
|
*/
|
|
@@ -6180,6 +6224,7 @@ function sanitizeArguments(value) {
|
|
|
6180
6224
|
for (const [key, item] of Object.entries(value)) copy[key] = SENSITIVE_KEY_PATTERN.test(key) ? REDACTED : sanitizeArguments(item);
|
|
6181
6225
|
return copy;
|
|
6182
6226
|
}
|
|
6227
|
+
if (typeof value === "string") return redactInlineSecrets(value);
|
|
6183
6228
|
return value;
|
|
6184
6229
|
}
|
|
6185
6230
|
/**
|
|
@@ -6208,6 +6253,12 @@ function sanitizedArgumentsText(events, callId) {
|
|
|
6208
6253
|
/**
|
|
6209
6254
|
* Render the tool-call context for the reviewer: the already-streamed call
|
|
6210
6255
|
* arguments (parsed, redacted, truncated) or a note when the log lacks them.
|
|
6256
|
+
*
|
|
6257
|
+
* A truncated preview is DISCLOSED as truncated and matched with an explicit
|
|
6258
|
+
* deny rule (see {@link buildReviewPrompt}): previously the
|
|
6259
|
+
* prompt silently showed a `…`-suffixed head and left the verdict rules to
|
|
6260
|
+
* infer that the evidence was incomplete, so a reviewer reading the visible
|
|
6261
|
+
* part as benign could allow an action whose decisive arguments were cut off.
|
|
6211
6262
|
* @param events - the requesting session's log.
|
|
6212
6263
|
* @param callId - the call to render.
|
|
6213
6264
|
* @param maxChars - the preview cap (shared with reason truncation).
|
|
@@ -6217,7 +6268,8 @@ function renderCallContext(events, callId, maxChars) {
|
|
|
6217
6268
|
if (callId === void 0) return "Tool call arguments: (not available — the asker attached no call id)";
|
|
6218
6269
|
const text = sanitizedArgumentsText(events, callId);
|
|
6219
6270
|
if (text === void 0) return "Tool call arguments: (not found or unparseable in the presented transcript)";
|
|
6220
|
-
return `Tool call arguments (sensitive values redacted):\n${
|
|
6271
|
+
if (text.length <= maxChars) return `Tool call arguments (sensitive values redacted):\n${text}`;
|
|
6272
|
+
return `Tool call arguments (sensitive values redacted, TRUNCATED):\n${truncate(text, maxChars)}\n[TRUNCATED EVIDENCE: only the first ${maxChars} of ${text.length} characters are shown; the remainder is NOT visible to you. An action you cannot see in full is insufficient evidence — the verdict rules require DENY on insufficient evidence. Deny unless the visible part alone proves the action safe.]`;
|
|
6221
6273
|
}
|
|
6222
6274
|
/** How many characters one transcript line may take before truncation. */
|
|
6223
6275
|
const CONTEXT_LINE_BUDGET = 300;
|
|
@@ -6231,7 +6283,7 @@ function contextLine(event) {
|
|
|
6231
6283
|
case "user/message": return `[user] ${truncate(contentText(event.data.content), CONTEXT_LINE_BUDGET)}`;
|
|
6232
6284
|
case "assistant/message": return `[agent] ${truncate(contentText(event.data.message.content), CONTEXT_LINE_BUDGET)}`;
|
|
6233
6285
|
case "tool/call": {
|
|
6234
|
-
const args = event.data.arguments;
|
|
6286
|
+
const args = redactInlineSecrets(event.data.arguments);
|
|
6235
6287
|
return `[tool call ${event.data.name}] ${truncate(args, CONTEXT_LINE_BUDGET)}`;
|
|
6236
6288
|
}
|
|
6237
6289
|
case "tool/result": {
|
|
@@ -6247,9 +6299,10 @@ function contextLine(event) {
|
|
|
6247
6299
|
* Build the compact-transcript section for the reviewer: the tail of the
|
|
6248
6300
|
* session's user/assistant messages and tool calls/results, most recent
|
|
6249
6301
|
* last, bounded by {@link ContextBudgetConfig}. `turns: 0` yields an empty
|
|
6250
|
-
* section. Tool-call arguments
|
|
6251
|
-
*
|
|
6252
|
-
* request is redacted separately in
|
|
6302
|
+
* section. Tool-call arguments pass through {@link redactInlineSecrets} and
|
|
6303
|
+
* tool-result text enters verbatim — both are already-presented transcript
|
|
6304
|
+
* content, and the pending request's own call is redacted separately in
|
|
6305
|
+
* {@link renderCallContext}.
|
|
6253
6306
|
*
|
|
6254
6307
|
* The character budget is spent from the NEWEST line backwards, so an
|
|
6255
6308
|
* over-budget transcript loses its oldest lines. Truncating the joined text
|
|
@@ -6357,6 +6410,7 @@ function buildReviewPrompt(request, config, override, verdictChannel = "structur
|
|
|
6357
6410
|
"- allow: the action is safe, reversible, within the stated scope, and its reason is plausible.",
|
|
6358
6411
|
"- deny: the action is destructive, irreversible, credential- or data-exfiltrating, or the",
|
|
6359
6412
|
" evidence is insufficient to judge it. When unsure, DENY.",
|
|
6413
|
+
"- truncated evidence counts as insufficient: when the arguments are marked TRUNCATED, DENY.",
|
|
6360
6414
|
"- riskLevel: low / medium / high, your overall confidence-weighted severity.",
|
|
6361
6415
|
"",
|
|
6362
6416
|
verdictChannel === "structured_output" ? "Report your decision by calling the structured_output tool EXACTLY ONCE with:" : "Report your decision by returning EXACTLY ONE JSON object and nothing else — no prose, no markdown fence — with:",
|
|
@@ -6679,6 +6733,8 @@ function newRejectionId() {
|
|
|
6679
6733
|
const FEEDBACK_TTL_MS = 3e5;
|
|
6680
6734
|
/** Cap on the in-memory denial feed (approve indexes are bounded by this). */
|
|
6681
6735
|
const MEMORY_DENIES_CAP = 200;
|
|
6736
|
+
/** Cap on the recent-verdict feed the panel's tally carries. */
|
|
6737
|
+
const MEMORY_RECENT_CAP = 10;
|
|
6682
6738
|
/** Cap on the in-memory override list (a session cannot meaningfully accumulate more). */
|
|
6683
6739
|
const MEMORY_OVERRIDES_CAP = 50;
|
|
6684
6740
|
/** Closed-union backstop for the fallback-policy switch. */
|
|
@@ -6733,6 +6789,13 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6733
6789
|
auditSupport = "unknown";
|
|
6734
6790
|
/** In-memory audit mirrors per session, used when the host cannot stamp audit events. */
|
|
6735
6791
|
memory = /* @__PURE__ */ new WeakMap();
|
|
6792
|
+
/**
|
|
6793
|
+
* Process-lifetime review tallies per session, which the
|
|
6794
|
+
* per-turn mirror cannot carry. Keyed by session so a restarted turn (which
|
|
6795
|
+
* rebuilds the mirror) keeps the panel's cumulative rows. A `WeakMap`, so a
|
|
6796
|
+
* discarded session releases its tally with it.
|
|
6797
|
+
*/
|
|
6798
|
+
tallies = /* @__PURE__ */ new WeakMap();
|
|
6736
6799
|
/** The one-time warning that session-log audit was disabled was already logged. */
|
|
6737
6800
|
warnedUnmarked = false;
|
|
6738
6801
|
/** Pending circuit-breaker abort-turn timers, cleared when the plugin unloads. */
|
|
@@ -6809,8 +6872,30 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6809
6872
|
};
|
|
6810
6873
|
this.memory.set(session, entry);
|
|
6811
6874
|
}
|
|
6875
|
+
if (entry.tally === void 0) entry.tally = this.tallyFor(session);
|
|
6812
6876
|
return entry;
|
|
6813
6877
|
}
|
|
6878
|
+
/**
|
|
6879
|
+
* The process-lifetime tally behind the panel's cumulative
|
|
6880
|
+
* rows, created on first use and kept for the life of the mount.
|
|
6881
|
+
* @param session - the session the tally belongs to.
|
|
6882
|
+
* @returns the session's tally, creating it when absent.
|
|
6883
|
+
*/
|
|
6884
|
+
tallyFor(session) {
|
|
6885
|
+
let tally = this.tallies.get(session);
|
|
6886
|
+
if (tally === void 0) {
|
|
6887
|
+
tally = {
|
|
6888
|
+
allows: 0,
|
|
6889
|
+
denies: 0,
|
|
6890
|
+
fallbacks: 0,
|
|
6891
|
+
rejections: 0,
|
|
6892
|
+
recent: [],
|
|
6893
|
+
recentDenies: []
|
|
6894
|
+
};
|
|
6895
|
+
this.tallies.set(session, tally);
|
|
6896
|
+
}
|
|
6897
|
+
return tally;
|
|
6898
|
+
}
|
|
6814
6899
|
/** Record one settled review outcome in the in-memory mirror (audit-disabled hosts). */
|
|
6815
6900
|
recordVerdictMemory(memory, verdict) {
|
|
6816
6901
|
memory.verdicts.unshift(verdict);
|
|
@@ -6822,6 +6907,23 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6822
6907
|
});
|
|
6823
6908
|
if (memory.denies.length > MEMORY_DENIES_CAP) memory.denies.length = MEMORY_DENIES_CAP;
|
|
6824
6909
|
}
|
|
6910
|
+
const tally = memory.tally;
|
|
6911
|
+
if (tally === void 0) return;
|
|
6912
|
+
if (verdict.denial) tally.denies += 1;
|
|
6913
|
+
else tally.allows += 1;
|
|
6914
|
+
tally.recent.unshift({
|
|
6915
|
+
id: verdict.id,
|
|
6916
|
+
toolName: verdict.toolName,
|
|
6917
|
+
denial: verdict.denial
|
|
6918
|
+
});
|
|
6919
|
+
if (tally.recent.length > MEMORY_RECENT_CAP) tally.recent.length = MEMORY_RECENT_CAP;
|
|
6920
|
+
if (verdict.denial) {
|
|
6921
|
+
tally.recentDenies.unshift({
|
|
6922
|
+
id: verdict.id,
|
|
6923
|
+
toolName: verdict.toolName
|
|
6924
|
+
});
|
|
6925
|
+
if (tally.recentDenies.length > MEMORY_DENIES_CAP) tally.recentDenies.length = MEMORY_DENIES_CAP;
|
|
6926
|
+
}
|
|
6825
6927
|
}
|
|
6826
6928
|
/** The number of leading consecutive denials in a newest-first verdict list. */
|
|
6827
6929
|
static leadingDenials(verdicts) {
|
|
@@ -6869,6 +6971,72 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6869
6971
|
};
|
|
6870
6972
|
}
|
|
6871
6973
|
/**
|
|
6974
|
+
* The live session whose id is `id`, or undefined when no
|
|
6975
|
+
* live session matches. The projection fold carries the owning session id as
|
|
6976
|
+
* plain text, so the runtime has to resolve it back to a `Session` before it
|
|
6977
|
+
* can read the in-memory mirror. Read through `ctx.get` rather than
|
|
6978
|
+
* injected: the projection view is a render-time convenience, and a missing
|
|
6979
|
+
* service must only cost the mirror, never park this plugin in a pending
|
|
6980
|
+
* fiber.
|
|
6981
|
+
* @param id - the session id carried by the fold state.
|
|
6982
|
+
* @returns the live session, or undefined.
|
|
6983
|
+
*/
|
|
6984
|
+
sessionById(id) {
|
|
6985
|
+
if (id === void 0) return void 0;
|
|
6986
|
+
const sessions = this.ctx.get("sessions");
|
|
6987
|
+
if (sessions === void 0) return void 0;
|
|
6988
|
+
for (const session of sessions.list()) if (String(session.id) === String(id)) return session;
|
|
6989
|
+
}
|
|
6990
|
+
/**
|
|
6991
|
+
* The panel view. The pure event fold can
|
|
6992
|
+
* never carry this plugin's statistics on a host that refuses unmarked
|
|
6993
|
+
* unknown event types (see {@link AutoReviewRuntime.auditMayAppend}), which
|
|
6994
|
+
* is why the panel read an empty log and showed all zeros. On such a host
|
|
6995
|
+
* this reads the in-memory mirror instead, so the panel reports the real
|
|
6996
|
+
* switch state and the real activity; on a marker-aware host the fold stays
|
|
6997
|
+
* authoritative and this returns it untouched.
|
|
6998
|
+
* @param state - the fold state, which carries its session id.
|
|
6999
|
+
* @returns the projection view the panel renders.
|
|
7000
|
+
*/
|
|
7001
|
+
livePanelView(state) {
|
|
7002
|
+
const folded = viewAutoReview(state);
|
|
7003
|
+
if (this.auditMayAppend()) return folded;
|
|
7004
|
+
const session = this.sessionById(state.sessionId);
|
|
7005
|
+
if (session === void 0) return folded;
|
|
7006
|
+
const memory = this.memoryFor(session);
|
|
7007
|
+
const tally = memory.tally ?? {
|
|
7008
|
+
allows: 0,
|
|
7009
|
+
denies: 0,
|
|
7010
|
+
fallbacks: 0,
|
|
7011
|
+
rejections: 0,
|
|
7012
|
+
recent: [],
|
|
7013
|
+
recentDenies: []
|
|
7014
|
+
};
|
|
7015
|
+
return {
|
|
7016
|
+
enabled: effectiveAutoReviewState(sessionEvents(session)) ?? memory.enabledOverride ?? this.config.enableByDefault,
|
|
7017
|
+
verdictsUsed: memory.verdicts.length,
|
|
7018
|
+
failuresUsed: memory.failures,
|
|
7019
|
+
allows: tally.allows,
|
|
7020
|
+
denies: tally.denies,
|
|
7021
|
+
fallbacks: tally.fallbacks,
|
|
7022
|
+
neverRejects: tally.rejections,
|
|
7023
|
+
cacheHits: folded.cacheHits,
|
|
7024
|
+
avgDurationMs: folded.avgDurationMs,
|
|
7025
|
+
circuit: null,
|
|
7026
|
+
recent: tally.recent.map((verdict) => ({
|
|
7027
|
+
reviewId: String(verdict.id),
|
|
7028
|
+
toolName: verdict.toolName,
|
|
7029
|
+
at: Date.now(),
|
|
7030
|
+
durationMs: 0,
|
|
7031
|
+
decision: verdict.denial ? "deny" : "allow"
|
|
7032
|
+
})),
|
|
7033
|
+
recentDenies: tally.recentDenies.map((deny) => ({
|
|
7034
|
+
reviewId: String(deny.id),
|
|
7035
|
+
toolName: deny.toolName
|
|
7036
|
+
}))
|
|
7037
|
+
};
|
|
7038
|
+
}
|
|
7039
|
+
/**
|
|
6872
7040
|
* The `approval/request` answerer. Claims a request ONLY when auto-review
|
|
6873
7041
|
* is enabled for the session AND the policy for this tool/reason is `ai`
|
|
6874
7042
|
* AND both per-turn budgets remain AND the rejection circuit breaker is
|
|
@@ -6932,7 +7100,10 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6932
7100
|
outcome: "rejected"
|
|
6933
7101
|
}, { ignorable: true });
|
|
6934
7102
|
this.probeAuditResult(result);
|
|
6935
|
-
} else
|
|
7103
|
+
} else {
|
|
7104
|
+
memory.rejections += 1;
|
|
7105
|
+
if (memory.tally !== void 0) memory.tally.rejections += 1;
|
|
7106
|
+
}
|
|
6936
7107
|
const text = auditOk ? neverResultText(rejectionId, request.toolName, reason) : plainNeverResultText(request.toolName, reason);
|
|
6937
7108
|
this.recordFeedback(request.callId, `${text}\n${this.config.denyGuidance}`);
|
|
6938
7109
|
return Promise.resolve("rejected");
|
|
@@ -6946,7 +7117,7 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6946
7117
|
* override, which changes the reviewer's evidence.
|
|
6947
7118
|
* @param request - the pending approval request.
|
|
6948
7119
|
* @returns the fingerprint, or undefined to skip the cache (fail-closed:
|
|
6949
|
-
* the request then runs the
|
|
7120
|
+
* the request then runs the reviewer as before).
|
|
6950
7121
|
*/
|
|
6951
7122
|
fingerprintFor(request) {
|
|
6952
7123
|
if (this.config.verdictCacheTtlMs <= 0) return void 0;
|
|
@@ -7052,7 +7223,11 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
7052
7223
|
...outcome !== void 0 ? { outcome } : {}
|
|
7053
7224
|
}, { ignorable: true });
|
|
7054
7225
|
this.probeAuditResult(result);
|
|
7055
|
-
} else
|
|
7226
|
+
} else {
|
|
7227
|
+
const fallbackMemory = this.memoryFor(session);
|
|
7228
|
+
fallbackMemory.failures += 1;
|
|
7229
|
+
if (fallbackMemory.tally !== void 0) fallbackMemory.tally.fallbacks += 1;
|
|
7230
|
+
}
|
|
7056
7231
|
this.ctx.logger.warn(`auto-review fallback (${failure.fallback}) for ${request.toolName}: ${failure.error}`);
|
|
7057
7232
|
if (outcome === "rejected") this.recordFallbackFeedback(request.callId, reviewId, failure.fallback, failure.error);
|
|
7058
7233
|
}
|
|
@@ -7379,7 +7554,7 @@ function apply(ctx, config) {
|
|
|
7379
7554
|
input: { hint: "on|off|status|approve [n]" },
|
|
7380
7555
|
handler: (invocation) => runtime.command(invocation)
|
|
7381
7556
|
});
|
|
7382
|
-
if (ctx.get("sessionProjections") !== void 0) ctx.inject(["sessionProjections"], (scope) => scope.sessionProjections.register(makeAutoReviewProjection(resolved.enableByDefault)));
|
|
7557
|
+
if (ctx.get("sessionProjections") !== void 0) ctx.inject(["sessionProjections"], (scope) => scope.sessionProjections.register(makeAutoReviewProjection(resolved.enableByDefault, (state) => runtime.livePanelView(state))));
|
|
7383
7558
|
}
|
|
7384
7559
|
//#endregion
|
|
7385
7560
|
//#region src/call-id.ts
|
package/lib/invariant.js
CHANGED
|
File without changes
|
package/lib/mcp.js
CHANGED
|
File without changes
|
package/lib/report-Df29db58.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/audit.d.ts
CHANGED
|
File without changes
|
package/lib/types/audit.d.ts.map
CHANGED
|
File without changes
|
package/lib/types/audit.js
CHANGED
|
File without changes
|
package/lib/types/audit.js.map
CHANGED
|
File without changes
|
package/lib/types/cache.d.ts
CHANGED
|
File without changes
|
package/lib/types/cache.d.ts.map
CHANGED
|
File without changes
|
package/lib/types/cache.js
CHANGED
|
File without changes
|
package/lib/types/cache.js.map
CHANGED
|
File without changes
|
package/lib/types/call-id.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/call-id.js
CHANGED
|
File without changes
|
package/lib/types/call-id.js.map
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/types/config.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/config.js
CHANGED
|
File without changes
|
package/lib/types/config.js.map
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/types/eval/assert.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/eval/cli.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/eval/cli.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/eval/diff.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/eval/diff.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/types/eval/dsl.d.ts
CHANGED
|
File without changes
|