dsh-approval-consult 1.1.4 → 1.1.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.en.md +2 -2
- package/README.md +3 -3
- package/lib/impl.js +40 -10
- package/package.json +2 -2
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.5**, 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.5).
|
|
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.5.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.5**,已发布到 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.5)。
|
|
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.5.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.5/dsh-approval-consult-1.1.5.tgz`;
|
|
56
56
|
3. 安装完成后点「立即启用」,重启一次 `dsh web` 更稳。
|
|
57
57
|
|
|
58
58
|
### 方式 C:本地目录(源码 / 开发)
|
package/lib/impl.js
CHANGED
|
@@ -1278,6 +1278,7 @@ export function apply(ctx, config, runtime) {
|
|
|
1278
1278
|
|
|
1279
1279
|
/* 上面的 await 会让出执行权:必须在不再 await 的临界段里重新核对审批状态与并发名额,
|
|
1280
1280
|
否则多个并发请求会一起通过数量检查(安全审查 2026-11-03)。 */
|
|
1281
|
+
refreshReconstructed(record)
|
|
1281
1282
|
if (record.resolvedAt !== null) {
|
|
1282
1283
|
throw httpError(409, 'approval-resolved', '这次审批已经结束了,不能继续提问。')
|
|
1283
1284
|
}
|
|
@@ -1394,6 +1395,8 @@ export function apply(ctx, config, runtime) {
|
|
|
1394
1395
|
entry.answer = job.text
|
|
1395
1396
|
entry.truncated = finishKind === 'length'
|
|
1396
1397
|
entry.usage = job.usage
|
|
1398
|
+
// 模型可能在后台轮询前就结束:提交结果/自动重试前同步审批。
|
|
1399
|
+
refreshReconstructed(record)
|
|
1397
1400
|
if (job.controller.signal.aborted) {
|
|
1398
1401
|
job.status = 'cancelled'
|
|
1399
1402
|
entry.status = 'cancelled'
|
|
@@ -1677,6 +1680,8 @@ export function apply(ctx, config, runtime) {
|
|
|
1677
1680
|
if (chunk.reason?.kind === 'error' || chunk.reason?.kind === 'aborted') error = messageOf(chunk.reason.failure)
|
|
1678
1681
|
}
|
|
1679
1682
|
}
|
|
1683
|
+
refreshReconstructed(record)
|
|
1684
|
+
if (controller.signal.aborted) throw controller.signal.reason ?? new Error('review cancelled')
|
|
1680
1685
|
review.text = text
|
|
1681
1686
|
review.decisionText = text.trim()
|
|
1682
1687
|
review.decision = parseReviewDecision(review.decisionText)
|
|
@@ -1875,20 +1880,24 @@ export function apply(ctx, config, runtime) {
|
|
|
1875
1880
|
* 否则用户一旦在别处决定,面板会一直停在“等待审批”(安全复查 2026-11-04)。
|
|
1876
1881
|
* 命中结果时统一走 finalizePending,确保中止侧调用、失效图片引用等清理一致。
|
|
1877
1882
|
*/
|
|
1878
|
-
function refreshReconstructed(record) {
|
|
1883
|
+
function refreshReconstructed(record, decisionsBySession) {
|
|
1879
1884
|
if (record === null || record === undefined) return record
|
|
1880
1885
|
if (record.reconstructed !== true || record.resolvedAt !== null) return record
|
|
1881
1886
|
if (record.approvalId === undefined) return record
|
|
1882
1887
|
try {
|
|
1883
|
-
|
|
1884
|
-
if (
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1888
|
+
let decisions = decisionsBySession?.get(record.sessionId)
|
|
1889
|
+
if (decisions === undefined) {
|
|
1890
|
+
const agent = liveAgentBySessionId(record.sessionId)
|
|
1891
|
+
if (agent === null || typeof agent.session?.snapshotEvents !== 'function') return record
|
|
1892
|
+
decisions = new Map()
|
|
1893
|
+
for (const event of agent.session.snapshotEvents()) {
|
|
1894
|
+
if (event?.type !== 'approval/decided' || event.data?.id === undefined) continue
|
|
1895
|
+
decisions.set(String(event.data.id), typeof event.data?.outcome === 'string' ? event.data.outcome : null)
|
|
1896
|
+
}
|
|
1897
|
+
// 后台一轮检查中,同一会话只读取和索引一次日志。
|
|
1898
|
+
decisionsBySession?.set(record.sessionId, decisions)
|
|
1891
1899
|
}
|
|
1900
|
+
if (decisions.has(record.approvalId)) finalizePending(record, decisions.get(record.approvalId), null)
|
|
1892
1901
|
} catch (error) {
|
|
1893
1902
|
noteError('refresh-reconstructed', error)
|
|
1894
1903
|
}
|
|
@@ -2235,6 +2244,7 @@ export function apply(ctx, config, runtime) {
|
|
|
2235
2244
|
const jobId = typeof query.jobId === 'string' ? query.jobId : ''
|
|
2236
2245
|
const job = state.jobs.get(jobId)
|
|
2237
2246
|
if (job === undefined) throw httpError(404, 'job-not-found', '这次回答已经过期或不存在。')
|
|
2247
|
+
refreshReconstructed(state.pendings.get(job.consultKey))
|
|
2238
2248
|
const cursor = intIn(query.cursor, 0, 0, Number.MAX_SAFE_INTEGER)
|
|
2239
2249
|
const safeCursor = Math.min(cursor, job.text.length)
|
|
2240
2250
|
return {
|
|
@@ -2501,7 +2511,7 @@ export function apply(ctx, config, runtime) {
|
|
|
2501
2511
|
throw httpError(404, 'attachment-unknown', '这张图片已经失效,请重新上传。')
|
|
2502
2512
|
}
|
|
2503
2513
|
// 审批结束后图片引用必须立即失效(finalizePending 会清理,这里兜底防止竞态残留)。
|
|
2504
|
-
const pendingForImage = state.pendings.get(consultKey)
|
|
2514
|
+
const pendingForImage = refreshReconstructed(state.pendings.get(consultKey))
|
|
2505
2515
|
if (pendingForImage === undefined || pendingForImage.resolvedAt !== null) {
|
|
2506
2516
|
throw httpError(404, 'attachment-unknown', '这张图片已经失效,请重新上传。')
|
|
2507
2517
|
}
|
|
@@ -2514,6 +2524,11 @@ export function apply(ctx, config, runtime) {
|
|
|
2514
2524
|
} catch (error) {
|
|
2515
2525
|
throw mapAttachmentError(error)
|
|
2516
2526
|
}
|
|
2527
|
+
// readImage 等待期间也可能结束审批或撤销引用,发送字节前再次核对。
|
|
2528
|
+
refreshReconstructed(pendingForImage)
|
|
2529
|
+
if (pendingForImage.resolvedAt !== null || state.attachments.get(id) !== item || Date.now() > item.expiresAt) {
|
|
2530
|
+
throw httpError(404, 'attachment-unknown', '这张图片已经失效,请重新上传。')
|
|
2531
|
+
}
|
|
2517
2532
|
sendImage(res, stored.data, stored.ref?.mediaType ?? item.ref.mediaType)
|
|
2518
2533
|
return
|
|
2519
2534
|
}
|
|
@@ -2543,6 +2558,7 @@ export function apply(ctx, config, runtime) {
|
|
|
2543
2558
|
const data = await readBinaryBody(req, limit)
|
|
2544
2559
|
if (data.length === 0) throw httpError(400, 'attachment-bad-request', '图片内容为空。')
|
|
2545
2560
|
// 读取期间审批可能已经结束:结束后的图片一律不再登记(否则会“复活”已失效引用)。
|
|
2561
|
+
refreshReconstructed(record)
|
|
2546
2562
|
if (record.resolvedAt !== null) {
|
|
2547
2563
|
throw httpError(409, 'approval-resolved', '这次审批已经结束,不能再上传图片。')
|
|
2548
2564
|
}
|
|
@@ -2558,6 +2574,7 @@ export function apply(ctx, config, runtime) {
|
|
|
2558
2574
|
}
|
|
2559
2575
|
// saveImage 也可能耗时:登记前再复核一次;若此时审批已结束,本次保存的宿主对象
|
|
2560
2576
|
// 没有对应引用(宿主接口未提供删除语义),但绝不会被登记成可读取的附件。
|
|
2577
|
+
refreshReconstructed(record)
|
|
2561
2578
|
if (record.resolvedAt !== null) {
|
|
2562
2579
|
throw httpError(409, 'approval-resolved', '这次审批已经结束,不能再上传图片。')
|
|
2563
2580
|
}
|
|
@@ -3453,6 +3470,14 @@ export function apply(ctx, config, runtime) {
|
|
|
3453
3470
|
}
|
|
3454
3471
|
}
|
|
3455
3472
|
|
|
3473
|
+
// 重建记录没有原审批的完成回调:即使面板关闭,也要取消侧调用并失效附件。
|
|
3474
|
+
// 后台延迟为一次轮询间隔(正常调度下约 1 秒);敏感路由另做即时复核。
|
|
3475
|
+
const reconstructedTimer = setInterval(() => {
|
|
3476
|
+
const decisionsBySession = new Map()
|
|
3477
|
+
for (const record of state.pendings.values()) refreshReconstructed(record, decisionsBySession)
|
|
3478
|
+
}, 1000)
|
|
3479
|
+
if (typeof reconstructedTimer.unref === 'function') reconstructedTimer.unref()
|
|
3480
|
+
addDisposer(() => clearInterval(reconstructedTimer))
|
|
3456
3481
|
|
|
3457
3482
|
const sweepTimer = setInterval(() => {
|
|
3458
3483
|
try {
|
|
@@ -3525,6 +3550,11 @@ export function apply(ctx, config, runtime) {
|
|
|
3525
3550
|
}
|
|
3526
3551
|
}
|
|
3527
3552
|
disposers.length = 0
|
|
3553
|
+
for (const record of state.pendings.values()) {
|
|
3554
|
+
for (const review of [record.review, record.advisoryReview]) {
|
|
3555
|
+
review?.classifyController?.abort(new Error('approval-consult unloaded'))
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3528
3558
|
for (const job of state.jobs.values()) {
|
|
3529
3559
|
if (job.status !== 'streaming') continue
|
|
3530
3560
|
job.status = 'cancelled'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-approval-consult",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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.",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"NOTICE.md"
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "node tests/unit.mjs && node tests/locale-parity.mjs && node tests/css-classes.mjs && node tests/security.mjs",
|
|
37
|
+
"test": "node tests/unit.mjs && node tests/locale-parity.mjs && node tests/css-classes.mjs && node tests/security.mjs && node tests/security-lifecycle.mjs",
|
|
38
38
|
"check": "node --check lib/impl.js && node --check lib/client.js && node --check lib/index.js"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|