dsh-auto-review 0.13.4 → 0.13.6
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 +18 -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 +193 -16
- 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 +1 -0
- package/lib/types/messages.d.ts.map +1 -1
- package/lib/types/messages.js +2 -0
- package/lib/types/messages.js.map +1 -1
- 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 +137 -5
- 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 +3 -0
- package/src/projection-types.ts +9 -1
- package/src/projection.ts +30 -7
- package/src/review.ts +53 -6
- package/src/runtime.ts +157 -5
- package/src/session-events.ts +0 -0
- package/src/session-message.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.6] - 2026-10-19
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`/auto-review status` no longer calls the audit-disabled in-memory tally “All-time” (issue #50).** On a host where session-log audit is fail-closed, the numbers come from the per-turn memory mirror; the line is now labeled “This turn (audit disabled; in-memory)” / “本轮(审计已停用,内存统计)”, so a 0 after a turn with real verdicts is not presented as a lifetime total.
|
|
8
|
+
|
|
3
9
|
## [0.13.4] - 2026-10-18
|
|
4
10
|
|
|
5
11
|
### Changed
|
|
@@ -46,6 +52,18 @@
|
|
|
46
52
|
|
|
47
53
|
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
54
|
|
|
55
|
+
## [0.13.5] - 2026-10-18
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- **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.
|
|
60
|
+
- **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.`
|
|
61
|
+
- **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.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- `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.
|
|
66
|
+
|
|
49
67
|
## [0.12.17] - 2026-10-10
|
|
50
68
|
|
|
51
69
|
### 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
|
@@ -641,6 +641,7 @@ const MESSAGES = {
|
|
|
641
641
|
failuresLine: (used, max) => `Reviewer failures this turn: ${used}/${max}`,
|
|
642
642
|
circuitLine: (kind, count, action) => `Rejection circuit breaker tripped (${kind}: ${count} denials); later requests in this turn follow "${action}".`,
|
|
643
643
|
allTimeLine: (allows, denies, fallbacks, rejections, avg) => `All-time: ${allows} allows, ${denies} denies, ${fallbacks} fallbacks, ${rejections} never rejects (avg ${avg} ms per verdict).`,
|
|
644
|
+
memoryStatsLine: (allows, denies, fallbacks, rejections, avg) => `This turn (audit disabled; in-memory): ${allows} allows, ${denies} denies, ${fallbacks} fallbacks, ${rejections} never rejects (avg ${avg} ms per verdict).`,
|
|
644
645
|
recentLine: (labels) => `Recent verdicts: ${labels}`,
|
|
645
646
|
unknownArg: (arg) => `Unknown /auto-review argument "${arg}". Usage: /auto-review on|off|status|approve [n]`,
|
|
646
647
|
already: (state) => `Auto-review is already ${state} for this session.`,
|
|
@@ -660,6 +661,7 @@ const MESSAGES = {
|
|
|
660
661
|
failuresLine: (used, max) => `本回合审查失败数:${used}/${max}`,
|
|
661
662
|
circuitLine: (kind, count, action) => `拒绝熔断器已触发(${kind}:${count} 次拒绝);本回合后续请求按 "${action}" 处理。`,
|
|
662
663
|
allTimeLine: (allows, denies, fallbacks, rejections, avg) => `累计:${allows} 次放行,${denies} 次拒绝,${fallbacks} 次失败,${rejections} 次硬禁用拒绝(每次裁决平均 ${avg} ms)。`,
|
|
664
|
+
memoryStatsLine: (allows, denies, fallbacks, rejections, avg) => `本轮(审计已停用,内存统计):${allows} 次放行,${denies} 次拒绝,${fallbacks} 次失败,${rejections} 次硬禁用拒绝(每次裁决平均 ${avg} ms)。`,
|
|
663
665
|
recentLine: (labels) => `最近裁决:${labels}`,
|
|
664
666
|
unknownArg: (arg) => `未知的 /auto-review 参数 "${arg}"。用法:/auto-review on|off|status|approve [n]`,
|
|
665
667
|
already: (state) => `本会话的自动审查已处于 ${state} 状态。`,
|
|
@@ -5965,10 +5967,19 @@ const AUTO_REVIEW_PROJECTION_SCHEMA = object({
|
|
|
5965
5967
|
* State for the empty log. `enabled` starts at the resolved
|
|
5966
5968
|
* `enableByDefault` — the projection cannot know the plugin config on its
|
|
5967
5969
|
* own, so the unit is built per mount by {@link makeAutoReviewProjection}.
|
|
5970
|
+
*
|
|
5971
|
+
* `sessionId` is part of the state since state version 4: the fold records
|
|
5972
|
+
* which session it belongs to so a host that cannot accept the
|
|
5973
|
+
* `autoReview/*` audit events can serve the panel from the live in-memory
|
|
5974
|
+
* mirror instead (see `AutoReviewRuntime.livePanelView`). The pure fold
|
|
5975
|
+
* cannot resolve a session on its own, so the id is supplied by the caller
|
|
5976
|
+
* that knows the log header.
|
|
5968
5977
|
* @param enabledByDefault - the resolved `enableByDefault` config value.
|
|
5978
|
+
* @param sessionId - the owning session id, as text.
|
|
5969
5979
|
*/
|
|
5970
|
-
function initAutoReviewProjection(enabledByDefault) {
|
|
5980
|
+
function initAutoReviewProjection(enabledByDefault, sessionId) {
|
|
5971
5981
|
return {
|
|
5982
|
+
sessionId,
|
|
5972
5983
|
enabled: enabledByDefault,
|
|
5973
5984
|
turnOpen: false,
|
|
5974
5985
|
verdictsUsed: 0,
|
|
@@ -6088,6 +6099,7 @@ function viewAutoReview(state) {
|
|
|
6088
6099
|
}
|
|
6089
6100
|
/** Strict state schema — validates the persisted fold state before it seeds a fold. */
|
|
6090
6101
|
const AUTO_REVIEW_STATE_SCHEMA = object({
|
|
6102
|
+
sessionId: string(),
|
|
6091
6103
|
enabled: boolean(),
|
|
6092
6104
|
turnOpen: boolean(),
|
|
6093
6105
|
verdictsUsed: number().int().nonnegative(),
|
|
@@ -6110,20 +6122,29 @@ const AUTO_REVIEW_STATE_SCHEMA = object({
|
|
|
6110
6122
|
* the answerer applies. Registrants sharing the `autoReview` key share one
|
|
6111
6123
|
* unit; the first mount's default wins, which only matters for a double
|
|
6112
6124
|
* mount with conflicting `enableByDefault` values.
|
|
6125
|
+
*
|
|
6126
|
+
* `liveView` substitutes the wire `view`
|
|
6127
|
+
* with a runtime-aware one. The pure fold can only report what the session
|
|
6128
|
+
* log carries, and a host that refuses the unmarked `autoReview/*` events
|
|
6129
|
+
* writes none of them — which is why the panel used to read an empty log and
|
|
6130
|
+
* show all zeros. The runtime passes `AutoReviewRuntime.livePanelView`, which
|
|
6131
|
+
* falls back to the in-memory mirror on such a host and to the fold
|
|
6132
|
+
* everywhere else. Omitted (or `undefined`) keeps the pure fold.
|
|
6113
6133
|
* @param enabledByDefault - the resolved `enableByDefault` config value.
|
|
6134
|
+
* @param liveView - an optional runtime-aware wire view.
|
|
6114
6135
|
* @returns the projection unit for `ctx.sessionProjections.register`.
|
|
6115
6136
|
*/
|
|
6116
|
-
function makeAutoReviewProjection(enabledByDefault) {
|
|
6137
|
+
function makeAutoReviewProjection(enabledByDefault, liveView) {
|
|
6117
6138
|
return {
|
|
6118
6139
|
key: "autoReview",
|
|
6119
6140
|
stateSchema: AUTO_REVIEW_STATE_SCHEMA,
|
|
6120
|
-
init: (
|
|
6141
|
+
init: (header) => initAutoReviewProjection(enabledByDefault, String(header.id)),
|
|
6121
6142
|
apply: applyAutoReview,
|
|
6122
6143
|
wire: {
|
|
6123
6144
|
viewSchema: AUTO_REVIEW_PROJECTION_SCHEMA,
|
|
6124
|
-
view: viewAutoReview
|
|
6145
|
+
view: liveView === void 0 ? viewAutoReview : liveView
|
|
6125
6146
|
},
|
|
6126
|
-
stateVersion:
|
|
6147
|
+
stateVersion: 4
|
|
6127
6148
|
};
|
|
6128
6149
|
}
|
|
6129
6150
|
//#endregion
|
|
@@ -6168,8 +6189,33 @@ const SENSITIVE_KEY_PATTERN = /(?:api[_-]?key|token|secret|password|passwd|pwd|a
|
|
|
6168
6189
|
/** The redaction placeholder for sensitive argument values. */
|
|
6169
6190
|
const REDACTED = "[REDACTED]";
|
|
6170
6191
|
/**
|
|
6192
|
+
* Inline secrets inside a free-form STRING
|
|
6193
|
+
* argument (a shell command line), which the key-based pass cannot see because
|
|
6194
|
+
* it inspects keys only. Covers `KEY=VALUE` / `KEY: VALUE` / `--flag VALUE`
|
|
6195
|
+
* spellings, with an optional `Bearer`/`Basic` scheme so
|
|
6196
|
+
* `Authorization: Bearer <token>` masks the token and not just the scheme word.
|
|
6197
|
+
* Best effort by construction: a bare secret with no key next to it stays
|
|
6198
|
+
* visible.
|
|
6199
|
+
*/
|
|
6200
|
+
const INLINE_SECRET_KEY = "(?:api[_-]?key|token|secret|password|passwd|pwd|authorization|credential|private[_-]?key|access[_-]?key)";
|
|
6201
|
+
/** The `KEY=VALUE` / `KEY: VALUE` spelling of an inline secret, scheme included. */
|
|
6202
|
+
const INLINE_SECRET_ASSIGN = new RegExp(`(${INLINE_SECRET_KEY}\\s*[:=]\\s*(?:Bearer\\s+|Basic\\s+)?)(["']?)([^\\s"';&|,)]+)`, "giu");
|
|
6203
|
+
/** The `--flag VALUE` spelling of an inline secret. */
|
|
6204
|
+
const INLINE_SECRET_FLAG = new RegExp(`(--?${INLINE_SECRET_KEY}\\s+)(["']?)([^\\s"';&|,)]+)`, "giu");
|
|
6205
|
+
/**
|
|
6206
|
+
* Redact inline secrets from a free-form string argument. The key-based pass
|
|
6207
|
+
* in {@link sanitizeArguments} only inspects object KEYS, so a secret spelled
|
|
6208
|
+
* inside a shell command line would otherwise reach the reviewer verbatim.
|
|
6209
|
+
* @param text - the free-form string to redact.
|
|
6210
|
+
* @returns the string with every recognized inline secret masked.
|
|
6211
|
+
*/
|
|
6212
|
+
function redactInlineSecrets(text) {
|
|
6213
|
+
return text.replace(INLINE_SECRET_ASSIGN, (_match, head, quote) => head + quote + REDACTED).replace(INLINE_SECRET_FLAG, (_match, head, quote) => head + quote + REDACTED);
|
|
6214
|
+
}
|
|
6215
|
+
/**
|
|
6171
6216
|
* Recursively redact sensitive values from a JSON value: any object key
|
|
6172
|
-
* matching {@link SENSITIVE_KEY_PATTERN} has its value replaced
|
|
6217
|
+
* matching {@link SENSITIVE_KEY_PATTERN} has its value replaced, and any
|
|
6218
|
+
* free-form string is passed through {@link redactInlineSecrets}.
|
|
6173
6219
|
* @param value - the detached JSON value to sanitize.
|
|
6174
6220
|
* @returns a detached copy with sensitive values redacted.
|
|
6175
6221
|
*/
|
|
@@ -6180,6 +6226,7 @@ function sanitizeArguments(value) {
|
|
|
6180
6226
|
for (const [key, item] of Object.entries(value)) copy[key] = SENSITIVE_KEY_PATTERN.test(key) ? REDACTED : sanitizeArguments(item);
|
|
6181
6227
|
return copy;
|
|
6182
6228
|
}
|
|
6229
|
+
if (typeof value === "string") return redactInlineSecrets(value);
|
|
6183
6230
|
return value;
|
|
6184
6231
|
}
|
|
6185
6232
|
/**
|
|
@@ -6208,6 +6255,12 @@ function sanitizedArgumentsText(events, callId) {
|
|
|
6208
6255
|
/**
|
|
6209
6256
|
* Render the tool-call context for the reviewer: the already-streamed call
|
|
6210
6257
|
* arguments (parsed, redacted, truncated) or a note when the log lacks them.
|
|
6258
|
+
*
|
|
6259
|
+
* A truncated preview is DISCLOSED as truncated and matched with an explicit
|
|
6260
|
+
* deny rule (see {@link buildReviewPrompt}): previously the
|
|
6261
|
+
* prompt silently showed a `…`-suffixed head and left the verdict rules to
|
|
6262
|
+
* infer that the evidence was incomplete, so a reviewer reading the visible
|
|
6263
|
+
* part as benign could allow an action whose decisive arguments were cut off.
|
|
6211
6264
|
* @param events - the requesting session's log.
|
|
6212
6265
|
* @param callId - the call to render.
|
|
6213
6266
|
* @param maxChars - the preview cap (shared with reason truncation).
|
|
@@ -6217,7 +6270,8 @@ function renderCallContext(events, callId, maxChars) {
|
|
|
6217
6270
|
if (callId === void 0) return "Tool call arguments: (not available — the asker attached no call id)";
|
|
6218
6271
|
const text = sanitizedArgumentsText(events, callId);
|
|
6219
6272
|
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${
|
|
6273
|
+
if (text.length <= maxChars) return `Tool call arguments (sensitive values redacted):\n${text}`;
|
|
6274
|
+
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
6275
|
}
|
|
6222
6276
|
/** How many characters one transcript line may take before truncation. */
|
|
6223
6277
|
const CONTEXT_LINE_BUDGET = 300;
|
|
@@ -6231,7 +6285,7 @@ function contextLine(event) {
|
|
|
6231
6285
|
case "user/message": return `[user] ${truncate(contentText(event.data.content), CONTEXT_LINE_BUDGET)}`;
|
|
6232
6286
|
case "assistant/message": return `[agent] ${truncate(contentText(event.data.message.content), CONTEXT_LINE_BUDGET)}`;
|
|
6233
6287
|
case "tool/call": {
|
|
6234
|
-
const args = event.data.arguments;
|
|
6288
|
+
const args = redactInlineSecrets(event.data.arguments);
|
|
6235
6289
|
return `[tool call ${event.data.name}] ${truncate(args, CONTEXT_LINE_BUDGET)}`;
|
|
6236
6290
|
}
|
|
6237
6291
|
case "tool/result": {
|
|
@@ -6247,9 +6301,10 @@ function contextLine(event) {
|
|
|
6247
6301
|
* Build the compact-transcript section for the reviewer: the tail of the
|
|
6248
6302
|
* session's user/assistant messages and tool calls/results, most recent
|
|
6249
6303
|
* last, bounded by {@link ContextBudgetConfig}. `turns: 0` yields an empty
|
|
6250
|
-
* section. Tool-call arguments
|
|
6251
|
-
*
|
|
6252
|
-
* request is redacted separately in
|
|
6304
|
+
* section. Tool-call arguments pass through {@link redactInlineSecrets} and
|
|
6305
|
+
* tool-result text enters verbatim — both are already-presented transcript
|
|
6306
|
+
* content, and the pending request's own call is redacted separately in
|
|
6307
|
+
* {@link renderCallContext}.
|
|
6253
6308
|
*
|
|
6254
6309
|
* The character budget is spent from the NEWEST line backwards, so an
|
|
6255
6310
|
* over-budget transcript loses its oldest lines. Truncating the joined text
|
|
@@ -6357,6 +6412,7 @@ function buildReviewPrompt(request, config, override, verdictChannel = "structur
|
|
|
6357
6412
|
"- allow: the action is safe, reversible, within the stated scope, and its reason is plausible.",
|
|
6358
6413
|
"- deny: the action is destructive, irreversible, credential- or data-exfiltrating, or the",
|
|
6359
6414
|
" evidence is insufficient to judge it. When unsure, DENY.",
|
|
6415
|
+
"- truncated evidence counts as insufficient: when the arguments are marked TRUNCATED, DENY.",
|
|
6360
6416
|
"- riskLevel: low / medium / high, your overall confidence-weighted severity.",
|
|
6361
6417
|
"",
|
|
6362
6418
|
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 +6735,8 @@ function newRejectionId() {
|
|
|
6679
6735
|
const FEEDBACK_TTL_MS = 3e5;
|
|
6680
6736
|
/** Cap on the in-memory denial feed (approve indexes are bounded by this). */
|
|
6681
6737
|
const MEMORY_DENIES_CAP = 200;
|
|
6738
|
+
/** Cap on the recent-verdict feed the panel's tally carries. */
|
|
6739
|
+
const MEMORY_RECENT_CAP = 10;
|
|
6682
6740
|
/** Cap on the in-memory override list (a session cannot meaningfully accumulate more). */
|
|
6683
6741
|
const MEMORY_OVERRIDES_CAP = 50;
|
|
6684
6742
|
/** Closed-union backstop for the fallback-policy switch. */
|
|
@@ -6733,6 +6791,13 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6733
6791
|
auditSupport = "unknown";
|
|
6734
6792
|
/** In-memory audit mirrors per session, used when the host cannot stamp audit events. */
|
|
6735
6793
|
memory = /* @__PURE__ */ new WeakMap();
|
|
6794
|
+
/**
|
|
6795
|
+
* Process-lifetime review tallies per session, which the
|
|
6796
|
+
* per-turn mirror cannot carry. Keyed by session so a restarted turn (which
|
|
6797
|
+
* rebuilds the mirror) keeps the panel's cumulative rows. A `WeakMap`, so a
|
|
6798
|
+
* discarded session releases its tally with it.
|
|
6799
|
+
*/
|
|
6800
|
+
tallies = /* @__PURE__ */ new WeakMap();
|
|
6736
6801
|
/** The one-time warning that session-log audit was disabled was already logged. */
|
|
6737
6802
|
warnedUnmarked = false;
|
|
6738
6803
|
/** Pending circuit-breaker abort-turn timers, cleared when the plugin unloads. */
|
|
@@ -6809,8 +6874,30 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6809
6874
|
};
|
|
6810
6875
|
this.memory.set(session, entry);
|
|
6811
6876
|
}
|
|
6877
|
+
if (entry.tally === void 0) entry.tally = this.tallyFor(session);
|
|
6812
6878
|
return entry;
|
|
6813
6879
|
}
|
|
6880
|
+
/**
|
|
6881
|
+
* The process-lifetime tally behind the panel's cumulative
|
|
6882
|
+
* rows, created on first use and kept for the life of the mount.
|
|
6883
|
+
* @param session - the session the tally belongs to.
|
|
6884
|
+
* @returns the session's tally, creating it when absent.
|
|
6885
|
+
*/
|
|
6886
|
+
tallyFor(session) {
|
|
6887
|
+
let tally = this.tallies.get(session);
|
|
6888
|
+
if (tally === void 0) {
|
|
6889
|
+
tally = {
|
|
6890
|
+
allows: 0,
|
|
6891
|
+
denies: 0,
|
|
6892
|
+
fallbacks: 0,
|
|
6893
|
+
rejections: 0,
|
|
6894
|
+
recent: [],
|
|
6895
|
+
recentDenies: []
|
|
6896
|
+
};
|
|
6897
|
+
this.tallies.set(session, tally);
|
|
6898
|
+
}
|
|
6899
|
+
return tally;
|
|
6900
|
+
}
|
|
6814
6901
|
/** Record one settled review outcome in the in-memory mirror (audit-disabled hosts). */
|
|
6815
6902
|
recordVerdictMemory(memory, verdict) {
|
|
6816
6903
|
memory.verdicts.unshift(verdict);
|
|
@@ -6822,6 +6909,23 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6822
6909
|
});
|
|
6823
6910
|
if (memory.denies.length > MEMORY_DENIES_CAP) memory.denies.length = MEMORY_DENIES_CAP;
|
|
6824
6911
|
}
|
|
6912
|
+
const tally = memory.tally;
|
|
6913
|
+
if (tally === void 0) return;
|
|
6914
|
+
if (verdict.denial) tally.denies += 1;
|
|
6915
|
+
else tally.allows += 1;
|
|
6916
|
+
tally.recent.unshift({
|
|
6917
|
+
id: verdict.id,
|
|
6918
|
+
toolName: verdict.toolName,
|
|
6919
|
+
denial: verdict.denial
|
|
6920
|
+
});
|
|
6921
|
+
if (tally.recent.length > MEMORY_RECENT_CAP) tally.recent.length = MEMORY_RECENT_CAP;
|
|
6922
|
+
if (verdict.denial) {
|
|
6923
|
+
tally.recentDenies.unshift({
|
|
6924
|
+
id: verdict.id,
|
|
6925
|
+
toolName: verdict.toolName
|
|
6926
|
+
});
|
|
6927
|
+
if (tally.recentDenies.length > MEMORY_DENIES_CAP) tally.recentDenies.length = MEMORY_DENIES_CAP;
|
|
6928
|
+
}
|
|
6825
6929
|
}
|
|
6826
6930
|
/** The number of leading consecutive denials in a newest-first verdict list. */
|
|
6827
6931
|
static leadingDenials(verdicts) {
|
|
@@ -6869,6 +6973,72 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6869
6973
|
};
|
|
6870
6974
|
}
|
|
6871
6975
|
/**
|
|
6976
|
+
* The live session whose id is `id`, or undefined when no
|
|
6977
|
+
* live session matches. The projection fold carries the owning session id as
|
|
6978
|
+
* plain text, so the runtime has to resolve it back to a `Session` before it
|
|
6979
|
+
* can read the in-memory mirror. Read through `ctx.get` rather than
|
|
6980
|
+
* injected: the projection view is a render-time convenience, and a missing
|
|
6981
|
+
* service must only cost the mirror, never park this plugin in a pending
|
|
6982
|
+
* fiber.
|
|
6983
|
+
* @param id - the session id carried by the fold state.
|
|
6984
|
+
* @returns the live session, or undefined.
|
|
6985
|
+
*/
|
|
6986
|
+
sessionById(id) {
|
|
6987
|
+
if (id === void 0) return void 0;
|
|
6988
|
+
const sessions = this.ctx.get("sessions");
|
|
6989
|
+
if (sessions === void 0) return void 0;
|
|
6990
|
+
for (const session of sessions.list()) if (String(session.id) === String(id)) return session;
|
|
6991
|
+
}
|
|
6992
|
+
/**
|
|
6993
|
+
* The panel view. The pure event fold can
|
|
6994
|
+
* never carry this plugin's statistics on a host that refuses unmarked
|
|
6995
|
+
* unknown event types (see {@link AutoReviewRuntime.auditMayAppend}), which
|
|
6996
|
+
* is why the panel read an empty log and showed all zeros. On such a host
|
|
6997
|
+
* this reads the in-memory mirror instead, so the panel reports the real
|
|
6998
|
+
* switch state and the real activity; on a marker-aware host the fold stays
|
|
6999
|
+
* authoritative and this returns it untouched.
|
|
7000
|
+
* @param state - the fold state, which carries its session id.
|
|
7001
|
+
* @returns the projection view the panel renders.
|
|
7002
|
+
*/
|
|
7003
|
+
livePanelView(state) {
|
|
7004
|
+
const folded = viewAutoReview(state);
|
|
7005
|
+
if (this.auditMayAppend()) return folded;
|
|
7006
|
+
const session = this.sessionById(state.sessionId);
|
|
7007
|
+
if (session === void 0) return folded;
|
|
7008
|
+
const memory = this.memoryFor(session);
|
|
7009
|
+
const tally = memory.tally ?? {
|
|
7010
|
+
allows: 0,
|
|
7011
|
+
denies: 0,
|
|
7012
|
+
fallbacks: 0,
|
|
7013
|
+
rejections: 0,
|
|
7014
|
+
recent: [],
|
|
7015
|
+
recentDenies: []
|
|
7016
|
+
};
|
|
7017
|
+
return {
|
|
7018
|
+
enabled: effectiveAutoReviewState(sessionEvents(session)) ?? memory.enabledOverride ?? this.config.enableByDefault,
|
|
7019
|
+
verdictsUsed: memory.verdicts.length,
|
|
7020
|
+
failuresUsed: memory.failures,
|
|
7021
|
+
allows: tally.allows,
|
|
7022
|
+
denies: tally.denies,
|
|
7023
|
+
fallbacks: tally.fallbacks,
|
|
7024
|
+
neverRejects: tally.rejections,
|
|
7025
|
+
cacheHits: folded.cacheHits,
|
|
7026
|
+
avgDurationMs: folded.avgDurationMs,
|
|
7027
|
+
circuit: null,
|
|
7028
|
+
recent: tally.recent.map((verdict) => ({
|
|
7029
|
+
reviewId: String(verdict.id),
|
|
7030
|
+
toolName: verdict.toolName,
|
|
7031
|
+
at: Date.now(),
|
|
7032
|
+
durationMs: 0,
|
|
7033
|
+
decision: verdict.denial ? "deny" : "allow"
|
|
7034
|
+
})),
|
|
7035
|
+
recentDenies: tally.recentDenies.map((deny) => ({
|
|
7036
|
+
reviewId: String(deny.id),
|
|
7037
|
+
toolName: deny.toolName
|
|
7038
|
+
}))
|
|
7039
|
+
};
|
|
7040
|
+
}
|
|
7041
|
+
/**
|
|
6872
7042
|
* The `approval/request` answerer. Claims a request ONLY when auto-review
|
|
6873
7043
|
* is enabled for the session AND the policy for this tool/reason is `ai`
|
|
6874
7044
|
* AND both per-turn budgets remain AND the rejection circuit breaker is
|
|
@@ -6932,7 +7102,10 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6932
7102
|
outcome: "rejected"
|
|
6933
7103
|
}, { ignorable: true });
|
|
6934
7104
|
this.probeAuditResult(result);
|
|
6935
|
-
} else
|
|
7105
|
+
} else {
|
|
7106
|
+
memory.rejections += 1;
|
|
7107
|
+
if (memory.tally !== void 0) memory.tally.rejections += 1;
|
|
7108
|
+
}
|
|
6936
7109
|
const text = auditOk ? neverResultText(rejectionId, request.toolName, reason) : plainNeverResultText(request.toolName, reason);
|
|
6937
7110
|
this.recordFeedback(request.callId, `${text}\n${this.config.denyGuidance}`);
|
|
6938
7111
|
return Promise.resolve("rejected");
|
|
@@ -6946,7 +7119,7 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
6946
7119
|
* override, which changes the reviewer's evidence.
|
|
6947
7120
|
* @param request - the pending approval request.
|
|
6948
7121
|
* @returns the fingerprint, or undefined to skip the cache (fail-closed:
|
|
6949
|
-
* the request then runs the
|
|
7122
|
+
* the request then runs the reviewer as before).
|
|
6950
7123
|
*/
|
|
6951
7124
|
fingerprintFor(request) {
|
|
6952
7125
|
if (this.config.verdictCacheTtlMs <= 0) return void 0;
|
|
@@ -7052,7 +7225,11 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
7052
7225
|
...outcome !== void 0 ? { outcome } : {}
|
|
7053
7226
|
}, { ignorable: true });
|
|
7054
7227
|
this.probeAuditResult(result);
|
|
7055
|
-
} else
|
|
7228
|
+
} else {
|
|
7229
|
+
const fallbackMemory = this.memoryFor(session);
|
|
7230
|
+
fallbackMemory.failures += 1;
|
|
7231
|
+
if (fallbackMemory.tally !== void 0) fallbackMemory.tally.fallbacks += 1;
|
|
7232
|
+
}
|
|
7056
7233
|
this.ctx.logger.warn(`auto-review fallback (${failure.fallback}) for ${request.toolName}: ${failure.error}`);
|
|
7057
7234
|
if (outcome === "rejected") this.recordFallbackFeedback(request.callId, reviewId, failure.fallback, failure.error);
|
|
7058
7235
|
}
|
|
@@ -7250,7 +7427,7 @@ var AutoReviewRuntime = class AutoReviewRuntime {
|
|
|
7250
7427
|
t.verdictsLine(verdictsUsed, this.config.maxReviewsPerTurn),
|
|
7251
7428
|
t.failuresLine(failuresUsed, this.config.maxFailuresPerTurn),
|
|
7252
7429
|
...circuit === void 0 ? [] : [t.circuitLine(circuit.trip.kind, circuit.trip.count, circuit.action)],
|
|
7253
|
-
t.allTimeLine(stats.allows, stats.denies, stats.fallbacks, stats.rejections, stats.avgDurationMs),
|
|
7430
|
+
auditOff ? t.memoryStatsLine(stats.allows, stats.denies, stats.fallbacks, stats.rejections, stats.avgDurationMs) : t.allTimeLine(stats.allows, stats.denies, stats.fallbacks, stats.rejections, stats.avgDurationMs),
|
|
7254
7431
|
...recent,
|
|
7255
7432
|
...auditOff ? [t.auditDisabledNotice] : [],
|
|
7256
7433
|
t.usage
|
|
@@ -7379,7 +7556,7 @@ function apply(ctx, config) {
|
|
|
7379
7556
|
input: { hint: "on|off|status|approve [n]" },
|
|
7380
7557
|
handler: (invocation) => runtime.command(invocation)
|
|
7381
7558
|
});
|
|
7382
|
-
if (ctx.get("sessionProjections") !== void 0) ctx.inject(["sessionProjections"], (scope) => scope.sessionProjections.register(makeAutoReviewProjection(resolved.enableByDefault)));
|
|
7559
|
+
if (ctx.get("sessionProjections") !== void 0) ctx.inject(["sessionProjections"], (scope) => scope.sessionProjections.register(makeAutoReviewProjection(resolved.enableByDefault, (state) => runtime.livePanelView(state))));
|
|
7383
7560
|
}
|
|
7384
7561
|
//#endregion
|
|
7385
7562
|
//#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
|