dsh-approval-consult 1.1.2 → 1.1.3

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.en.md CHANGED
@@ -53,7 +53,7 @@ notes or reject blindly. This plugin adds a consultation panel on top of the app
53
53
 
54
54
  Requires DSH Web `0.2.1-rc.2` (tested) and Node `>=20`.
55
55
 
56
- Current version **v1.1.2**, published to npm ([`dsh-approval-consult`](https://www.npmjs.com/package/dsh-approval-consult)) and as a [GitHub release](https://github.com/0tests/dsh-approval-consult/releases/tag/v1.1.2).
56
+ Current version **v1.1.3**, published to npm ([`dsh-approval-consult`](https://www.npmjs.com/package/dsh-approval-consult)) and as a [GitHub release](https://github.com/0tests/dsh-approval-consult/releases/tag/v1.1.3).
57
57
 
58
58
  ```bash
59
59
  npm install dsh-approval-consult
@@ -61,7 +61,7 @@ npm install dsh-approval-consult
61
61
 
62
62
  Or install the release tarball:
63
63
 
64
- 1. Download `dsh-approval-consult-1.1.2.tgz` from
64
+ 1. Download `dsh-approval-consult-1.1.3.tgz` from
65
65
  [Releases](https://github.com/0tests/dsh-approval-consult/releases/latest).
66
66
  2. Open DSH Web → **Plugins** → **Add plugin** → **Install third-party plugin** and point it at the
67
67
  tarball's absolute path (or the download URL directly).
package/README.md CHANGED
@@ -37,7 +37,7 @@ Auto 审查(`@deepseek-ai/dsh-experimental-auto-review`)会在工具执行
37
37
 
38
38
  ## 安装
39
39
 
40
- > 当前版本 **v1.1.2**,已发布到 npm([`dsh-approval-consult`](https://www.npmjs.com/package/dsh-approval-consult))和 [GitHub Release](https://github.com/0tests/dsh-approval-consult/releases/tag/v1.1.2)。
40
+ > 当前版本 **v1.1.3**,已发布到 npm([`dsh-approval-consult`](https://www.npmjs.com/package/dsh-approval-consult))和 [GitHub Release](https://github.com/0tests/dsh-approval-consult/releases/tag/v1.1.3)。
41
41
  > `npm install dsh-approval-consult`,或让 Agent 用 `plugin_manager` 安装这个包名;下面也保留了 tarball / 本地目录两种方式。
42
42
 
43
43
  ### 方式 A:npm
@@ -49,10 +49,10 @@ Auto 审查(`@deepseek-ai/dsh-experimental-auto-review`)会在工具执行
49
49
  ### 方式 B:从 Release 安装(tarball)
50
50
 
51
51
  1. 在 [Releases](https://github.com/0tests/dsh-approval-consult/releases/latest) 下载
52
- `dsh-approval-consult-1.1.2.tgz`;
52
+ `dsh-approval-consult-1.1.3.tgz`;
53
53
  2. 打开 DSH Web → 左侧「插件」→「添加插件」→「安装第三方插件」,填这个 tarball 的绝对路径,
54
54
  或直接填下载链接
55
- `https://github.com/0tests/dsh-approval-consult/releases/download/v1.1.2/dsh-approval-consult-1.1.2.tgz`;
55
+ `https://github.com/0tests/dsh-approval-consult/releases/download/v1.1.3/dsh-approval-consult-1.1.3.tgz`;
56
56
  3. 安装完成后点「立即启用」,重启一次 `dsh web` 更稳。
57
57
 
58
58
  ### 方式 C:本地目录(源码 / 开发)
package/lib/impl.js CHANGED
@@ -974,7 +974,9 @@ export function apply(ctx, config, runtime) {
974
974
  typeof review.decision?.reason === 'string' &&
975
975
  review.decision.reason.length >= 12 &&
976
976
  reason.includes(review.decision.reason)
977
- // 参数已知但不一致、或两者都无法核对:一律不复用(宁可缺材料,不展示错误结论)。
977
+ // 参数已知且不一致:无论理由文字是否命中都不复用——理由文本不能替代调用身份。
978
+ if (argsKnown && !sameArgs) continue
979
+ // 参数未知时,才退化为「理由原样命中」这一弱证据;两者都无法核对则不复用。
978
980
  if (!sameArgs && !reasonHit) continue
979
981
  let score = 1
980
982
  if (reasonHit) score += 4
@@ -1652,6 +1654,11 @@ export function apply(ctx, config, runtime) {
1652
1654
  const controller = new AbortController()
1653
1655
  const timer = setTimeout(() => controller.abort(new Error('advisory review timeout')), 90_000)
1654
1656
  review.classifyController = controller
1657
+ // 每次尝试使用独立缓冲:上一次失败留下的半截输出/错误不能污染本次结果。
1658
+ let text = ''
1659
+ let error = null
1660
+ let usage = null
1661
+ let finishKind = null
1655
1662
  const options = {
1656
1663
  ...review.options,
1657
1664
  maxTokens: 1800,
@@ -1662,23 +1669,28 @@ export function apply(ctx, config, runtime) {
1662
1669
  try {
1663
1670
  for await (const chunk of ctx.llm.stream(options)) {
1664
1671
  if (!chunk || typeof chunk !== 'object') continue
1665
- if (chunk.type === 'text-delta' && typeof chunk.text === 'string') review.text += chunk.text
1666
- else if (chunk.type === 'block-end' && chunk.block?.type === 'text' && typeof chunk.block.text === 'string' && review.text.length === 0) review.text = chunk.block.text
1667
- else if (chunk.type === 'usage') review.usage = chunk.usage ?? null
1672
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string') text += chunk.text
1673
+ else if (chunk.type === 'block-end' && chunk.block?.type === 'text' && typeof chunk.block.text === 'string' && text.length === 0) text = chunk.block.text
1674
+ else if (chunk.type === 'usage') usage = chunk.usage ?? null
1668
1675
  else if (chunk.type === 'finish') {
1669
- review.finishKind = chunk.reason?.kind ?? null
1670
- if (chunk.reason?.kind === 'error' || chunk.reason?.kind === 'aborted') review.error = messageOf(chunk.reason.failure)
1676
+ finishKind = chunk.reason?.kind ?? null
1677
+ if (chunk.reason?.kind === 'error' || chunk.reason?.kind === 'aborted') error = messageOf(chunk.reason.failure)
1671
1678
  }
1672
1679
  }
1673
- review.decisionText = review.text.trim()
1680
+ review.text = text
1681
+ review.decisionText = text.trim()
1674
1682
  review.decision = parseReviewDecision(review.decisionText)
1675
- if (review.decision === null && review.error === null) {
1676
- review.error = `审查输出无法解析:${clip(review.decisionText, 200)}`
1683
+ if (review.decision === null && error === null) {
1684
+ error = `审查输出无法解析:${clip(review.decisionText, 200)}`
1677
1685
  }
1678
- } catch (error) {
1679
- review.error = friendlySideCallError(error)
1680
- noteError('advisory-classify', error)
1686
+ } catch (thrown) {
1687
+ review.text = text
1688
+ error = friendlySideCallError(thrown)
1689
+ noteError('advisory-classify', thrown)
1681
1690
  } finally {
1691
+ review.error = error
1692
+ review.usage = usage
1693
+ review.finishKind = finishKind
1682
1694
  clearTimeout(timer)
1683
1695
  state.sideOptions.delete(options)
1684
1696
  review.classifyController = null
@@ -2487,6 +2499,11 @@ export function apply(ctx, config, runtime) {
2487
2499
  if (item === undefined || item.consultKey !== consultKey || Date.now() > item.expiresAt) {
2488
2500
  throw httpError(404, 'attachment-unknown', '这张图片已经失效,请重新上传。')
2489
2501
  }
2502
+ // 审批结束后图片引用必须立即失效(finalizePending 会清理,这里兜底防止竞态残留)。
2503
+ const pendingForImage = state.pendings.get(consultKey)
2504
+ if (pendingForImage === undefined || pendingForImage.resolvedAt !== null) {
2505
+ throw httpError(404, 'attachment-unknown', '这张图片已经失效,请重新上传。')
2506
+ }
2490
2507
  if (attachmentStore === null || typeof attachmentStore.readImage !== 'function') {
2491
2508
  throw httpError(409, 'attachment-unavailable', '当前环境没有可用的附件服务。')
2492
2509
  }
@@ -2524,6 +2541,10 @@ export function apply(ctx, config, runtime) {
2524
2541
  try {
2525
2542
  const data = await readBinaryBody(req, limit)
2526
2543
  if (data.length === 0) throw httpError(400, 'attachment-bad-request', '图片内容为空。')
2544
+ // 读取期间审批可能已经结束:结束后的图片一律不再登记(否则会“复活”已失效引用)。
2545
+ if (record.resolvedAt !== null) {
2546
+ throw httpError(409, 'approval-resolved', '这次审批已经结束,不能再上传图片。')
2547
+ }
2527
2548
  let ref = null
2528
2549
  try {
2529
2550
  ref = await attachmentStore.saveImage({
@@ -2534,6 +2555,11 @@ export function apply(ctx, config, runtime) {
2534
2555
  } catch (error) {
2535
2556
  throw mapAttachmentError(error)
2536
2557
  }
2558
+ // saveImage 也可能耗时:登记前再复核一次;若此时审批已结束,本次保存的宿主对象
2559
+ // 没有对应引用(宿主接口未提供删除语义),但绝不会被登记成可读取的附件。
2560
+ if (record.resolvedAt !== null) {
2561
+ throw httpError(409, 'approval-resolved', '这次审批已经结束,不能再上传图片。')
2562
+ }
2537
2563
  const item = registerAttachment(ref, record.key, record.sessionId)
2538
2564
  sendJson(res, 200, { ok: true, attachment: attachmentMeta(item) })
2539
2565
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-approval-consult",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Approval consultation panel for DeepSeek Harness: while a tool call waits for approval, ask the executor AI and the auto-review AI independently, in two isolated channels with prompt-cache reuse.",