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 +2 -2
- package/README.md +3 -3
- package/lib/impl.js +38 -12
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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')
|
|
1666
|
-
else if (chunk.type === 'block-end' && chunk.block?.type === 'text' && typeof chunk.block.text === 'string' &&
|
|
1667
|
-
else if (chunk.type === 'usage')
|
|
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
|
-
|
|
1670
|
-
if (chunk.reason?.kind === 'error' || chunk.reason?.kind === 'aborted')
|
|
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.
|
|
1680
|
+
review.text = text
|
|
1681
|
+
review.decisionText = text.trim()
|
|
1674
1682
|
review.decision = parseReviewDecision(review.decisionText)
|
|
1675
|
-
if (review.decision === null &&
|
|
1676
|
-
|
|
1683
|
+
if (review.decision === null && error === null) {
|
|
1684
|
+
error = `审查输出无法解析:${clip(review.decisionText, 200)}`
|
|
1677
1685
|
}
|
|
1678
|
-
} catch (
|
|
1679
|
-
review.
|
|
1680
|
-
|
|
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.
|
|
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.",
|