dsh-approval-review 0.2.1 → 0.2.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/README-zh.md CHANGED
@@ -68,7 +68,7 @@ dsh --profile <profile> --dump-config | grep -A6 'id: approval-review'
68
68
  | `reviewer.provider` / `.model` | *(继承)* | 复核路由;不填则继承调用 Agent 自己的路由。会话内可用 `/approval-review model [<provider>/]<id>` 覆盖(**「审批」页签右上角可以直接选**:点开即列出本机配置的模型,候选来自客户端自己的模型目录服务 `modelDirectories`——和 `/model` 选择器、输入框里的模型座位读的是同一份目录。列表由插件自己渲染(原生 `datalist`/`select` 的弹层字号字重无法用 CSS 控制,会显得比页面吵),支持输入过滤、方向键+回车,也可以手打目录里没有的 id)。 |
69
69
  | `reviewer.subagentProvider` | `fork` | `mode: subagent` 用的子代理后端(`fork` / `spawn`)。 |
70
70
  | `reviewer.tools` | `[read, glob, grep]` | 复核子代理的工具白名单。留空会回退到只读默认,而不是继承父代理的全部工具。 |
71
- | `reviewer.timeoutMs` | `60000` | 单次复核的硬超时。 |
71
+ | `reviewer.timeoutMs` | `120000` | 单次复核的硬超时。慢路由 + 推理型复核者实测要 ~50 秒;超时不是「否决」,而是按失败策略走 fail-closed。 |
72
72
  | `reviewer.maxTokens` | `1024` | 输出上限。 |
73
73
  | `reviewer.temperature` | `0` | 采样温度。 |
74
74
  | `reviewer.policyText` | *(内置策略)* | 替换裁决策略正文。 |
package/README.md CHANGED
@@ -88,7 +88,7 @@ schema defaults.
88
88
  | `reviewer.provider` / `.model` | *(inherit)* | Reviewer route; unset inherits the calling agent's own route. |
89
89
  | `reviewer.subagentProvider` | `fork` | Subagent backend for `mode: subagent` (`fork` / `spawn`). |
90
90
  | `reviewer.tools` | `[read, glob, grep]` | The reviewer child's tool allow-list. An empty list falls back to the read-only default rather than the parent's whole face. |
91
- | `reviewer.timeoutMs` | `60000` | Hard deadline for one reviewer call. |
91
+ | `reviewer.timeoutMs` | `120000` | Hard deadline for one reviewer call. A slow route plus a reasoning reviewer can take ~50s; a deadline that expires mid-review becomes a fail-closed refusal, not a verdict. |
92
92
  | `reviewer.maxTokens` | `1024` | Output cap. |
93
93
  | `reviewer.temperature` | `0` | Sampling temperature. |
94
94
  | `reviewer.policyText` | *(shipping policy)* | Replaces the ruling policy text. |
package/cordis.patch.yml CHANGED
@@ -35,7 +35,10 @@
35
35
  # Reviewer model, prompt, and size limits. `provider`/`model` unset means
36
36
  # the reviewer inherits the calling agent's own route.
37
37
  reviewer:
38
- timeoutMs: 60000
38
+ # 120s, not 60s: a reasoning reviewer on a slower route measured 52.5s
39
+ # here, and a deadline that expires mid-review is not a "no" — it is a
40
+ # fail-closed refusal under the default `onReviewerFailure: rejected`.
41
+ timeoutMs: 120000
39
42
  maxTokens: 1024
40
43
  temperature: 0
41
44
  argumentMaxChars: 4000
package/lib/index.js CHANGED
@@ -2,7 +2,6 @@ import Schema from "@deepseek-ai/schemastery";
2
2
  import { z } from "zod";
3
3
  import { BlockAssembler, LlmError, createUserMessage } from "@deepseek-ai/dsh-llm";
4
4
  import { createHash } from "node:crypto";
5
- import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
6
5
  //#region src/review-types.ts
7
6
  /** Every {@link RiskLevel}, least to most dangerous (index is the rank). */
8
7
  const RISK_LEVELS = [
@@ -56,7 +55,7 @@ const Config = Schema.object({
56
55
  "glob",
57
56
  "grep"
58
57
  ]).description("The reviewer child's tool allow-list. An empty list falls back to the read-only default rather than the parent's whole face."),
59
- timeoutMs: Schema.number().step(1).min(1e3).default(6e4).description("Hard deadline for one reviewer call."),
58
+ timeoutMs: Schema.number().step(1).min(1e3).default(12e4).description("Hard deadline for one reviewer call. A reasoning reviewer on a slow route can take tens of seconds; a deadline that is too tight turns into a fail-closed refusal (the default failure policy) rather than a verdict."),
60
59
  maxTokens: Schema.number().step(1).min(64).default(1024).description("Output-token cap for one reviewer call (`mode: direct`)."),
61
60
  temperature: Schema.number().min(0).max(2).default(0).description("Sampling temperature; 0 keeps the reviewer near-deterministic."),
62
61
  policyText: Schema.string().description("Ruling policy appended to the reviewer prompt."),
@@ -1361,41 +1360,6 @@ var VerdictCache = class {
1361
1360
  };
1362
1361
  //#endregion
1363
1362
  //#region src/subagent-reviewer.ts
1364
- /**
1365
- * The reviewer's requested structured output. An object-rooted schema is the
1366
- * reliable channel: a subagent returns it validated rather than as text this
1367
- * plugin has to salvage.
1368
- */
1369
- const REVIEWER_OUTPUT_SCHEMA = {
1370
- type: "object",
1371
- properties: {
1372
- decision: {
1373
- type: "string",
1374
- enum: [
1375
- "allow",
1376
- "deny",
1377
- "uncertain"
1378
- ]
1379
- },
1380
- risk: {
1381
- type: "string",
1382
- enum: [
1383
- "low",
1384
- "medium",
1385
- "high",
1386
- "critical"
1387
- ]
1388
- },
1389
- reason: { type: "string" },
1390
- suggestion: { type: "string" }
1391
- },
1392
- required: [
1393
- "decision",
1394
- "risk",
1395
- "reason"
1396
- ],
1397
- additionalProperties: false
1398
- };
1399
1363
  /** Join text blocks from a child's output, walking nested tool-result blocks. */
1400
1364
  function childText(blocks) {
1401
1365
  const out = [];
@@ -1440,8 +1404,6 @@ async function runSubagentReviewer(ctx, input) {
1440
1404
  failure: "cancelled before dispatch",
1441
1405
  durationMs: 0
1442
1406
  };
1443
- const schema = REVIEWER_OUTPUT_SCHEMA;
1444
- assertObjectJsonSchema(schema);
1445
1407
  const evidence = buildReviewerUserMessage({
1446
1408
  toolName: input.evidence.toolName,
1447
1409
  argumentsText: input.evidence.argumentsText,
@@ -1466,7 +1428,6 @@ async function runSubagentReviewer(ctx, input) {
1466
1428
  "grep"
1467
1429
  ] },
1468
1430
  maxDepth: 1,
1469
- outputSchema: schema,
1470
1431
  ...input.provider === void 0 && input.model === void 0 ? {} : { agentOptions: {
1471
1432
  ...input.provider === void 0 ? {} : { provider: input.provider },
1472
1433
  ...input.model === void 0 ? {} : { model: input.model }
@@ -1555,6 +1516,8 @@ var ReviewRuntime = class {
1555
1516
  reviewerSessions = /* @__PURE__ */ new Set();
1556
1517
  /** Latest folded audit state per session, for the live view defaults. */
1557
1518
  auditStates = /* @__PURE__ */ new WeakMap();
1519
+ /** Sessions whose committed log has already been replayed into that fold. */
1520
+ replayed = /* @__PURE__ */ new WeakSet();
1558
1521
  /** Reused verdicts for identical actions, when the evidence allows it. */
1559
1522
  cache;
1560
1523
  /** Number of verdicts served from the cache since mount. */
@@ -1613,6 +1576,16 @@ var ReviewRuntime = class {
1613
1576
  */
1614
1577
  observeEvent(session, event) {
1615
1578
  this.sessions.observe(session, event);
1579
+ if (!this.replayed.has(session)) {
1580
+ this.replayed.add(session);
1581
+ let state = initAuditState();
1582
+ for (let seq = 0; seq < session.seq; seq += 1) {
1583
+ const committed = session.eventAt(seq);
1584
+ if (committed !== void 0) state = applyAuditEvent(state, committed, this.config);
1585
+ }
1586
+ this.auditStates.set(session, state);
1587
+ return;
1588
+ }
1616
1589
  const previous = this.auditStates.get(session) ?? initAuditState();
1617
1590
  this.auditStates.set(session, applyAuditEvent(previous, event, this.config));
1618
1591
  }
@@ -1887,7 +1860,7 @@ var ReviewRuntime = class {
1887
1860
  }
1888
1861
  this.putRefusal(callId, {
1889
1862
  marker: formatReviewMarker({
1890
- reason: verdict?.reason ?? gate.note,
1863
+ reason: clampReason(verdict?.reason ?? (failure === void 0 ? gate.note : `${gate.note} — ${failure}`), this.config.reasonMaxChars),
1891
1864
  ...verdict?.suggestion === void 0 ? {} : { suggestion: verdict.suggestion },
1892
1865
  ...verdict?.risk === void 0 ? {} : { risk: verdict.risk },
1893
1866
  reviewerRoute: `${route.provider}/${route.model}`,
@@ -2042,6 +2015,21 @@ var ReviewRuntime = class {
2042
2015
  }
2043
2016
  }
2044
2017
  };
2018
+ /**
2019
+ * Bound a reason the plugin is about to publish.
2020
+ *
2021
+ * The marker rides the tool result, which is model context, so `reasonMaxChars`
2022
+ * has to hold here rather than only in the config schema. This is also what
2023
+ * keeps a provider's multi-line error digest from swallowing the guidance that
2024
+ * follows it.
2025
+ * @param reason - the assembled reason.
2026
+ * @param max - configured cap.
2027
+ * @returns the reason, truncated with an ellipsis when it exceeds the cap.
2028
+ */
2029
+ function clampReason(reason, max) {
2030
+ if (reason.length <= max) return reason;
2031
+ return `${reason.slice(0, Math.max(0, max - 1))}…`;
2032
+ }
2045
2033
  /** Join the text of a content-block list, walking nested tool-result blocks. */
2046
2034
  function blocksToText(blocks) {
2047
2035
  const out = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-approval-review",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Codex-style agent auto-approval for DeepSeek Harness: an independent reviewer model decides allow/deny on the approval answerer chain, fail-closed, with a per-decision rationale — refusals and allows alike — in a dedicated Approvals tab. · DSH 插件:Codex 风格的 Agent 自动审批,独立 reviewer 模型裁决、fail-closed、每次审批(放行与否决)都留下详细理由,并在独立的「审批」页签中展示。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",