@dnalec/dsh-auto-approve 0.2.0 → 0.2.1
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 +64 -0
- package/README.md +21 -12
- package/README.zh.md +21 -10
- package/client.js +300 -54
- package/locales.mjs +40 -4
- package/package.json +3 -2
- package/src/index.mjs +99 -16
- package/src/preset-patch.mjs +331 -76
- package/src/rules.mjs +214 -70
- package/src/util.mjs +43 -2
package/locales.mjs
CHANGED
|
@@ -99,7 +99,15 @@ export const zh = {
|
|
|
99
99
|
'detail.script': '脚本',
|
|
100
100
|
'detail.sql': 'SQL',
|
|
101
101
|
'detail.prompt': '提示词',
|
|
102
|
+
'detail.input': '输入',
|
|
103
|
+
'detail.text': '文本',
|
|
104
|
+
'detail.body': '正文',
|
|
105
|
+
'detail.message': '消息',
|
|
106
|
+
'detail.pattern': '模式',
|
|
107
|
+
'detail.selector': '选择器',
|
|
102
108
|
'detail.cwd': '工作目录',
|
|
109
|
+
'detail.workdir': '命令工作目录',
|
|
110
|
+
'detail.empty': '(空)',
|
|
103
111
|
'detail.sandbox': '沙箱',
|
|
104
112
|
'detail.justification': '模型理由',
|
|
105
113
|
'detail.pipe': '管道',
|
|
@@ -122,6 +130,8 @@ export const zh = {
|
|
|
122
130
|
'set.loadFailed': '加载失败:{error}',
|
|
123
131
|
'set.resetConfirm': '再点一次确认恢复',
|
|
124
132
|
'set.presetMissingTitle': '权限预设',
|
|
133
|
+
'set.driftTitle': '权限预设表需要更新',
|
|
134
|
+
'set.driftBody': 'DSH 出厂权限预设表多了 {keys}。本 profile 的 permission 行由本插件写入,patch 会整块替换 config,所以不会自动出现这些预设;请更新插件或手工合并该行。',
|
|
125
135
|
'set.presetMissingSub': '插件启动时会写入 auto-approve。当前未检测到,可手动补写。',
|
|
126
136
|
'set.presetWrite': '写入权限预设',
|
|
127
137
|
'set.presetWriteNote': '写入后需重启 dsh web',
|
|
@@ -131,6 +141,7 @@ export const zh = {
|
|
|
131
141
|
'set.modeWsHint': '工作区内直接放行,越出工作区需审核。',
|
|
132
142
|
'set.modeRo': '只读',
|
|
133
143
|
'set.modeRoHint': '工作区内写操作也需审核。',
|
|
144
|
+
'set.modeHint': '点选即写入。改完需重启 dsh web,并重新选择「自动审批」或开新会话。',
|
|
134
145
|
'set.save': '保存',
|
|
135
146
|
'set.hotOk': '已生效(热更新,无需重启)',
|
|
136
147
|
'set.modeSaved': '已写入。请重启 dsh web,并重新选择「自动审批」或开新会话',
|
|
@@ -170,11 +181,17 @@ export const zh = {
|
|
|
170
181
|
'set.modelDefaultEffort': '模型默认',
|
|
171
182
|
'set.effortOff': 'off(建议)',
|
|
172
183
|
'set.judgeTimeoutMs': '审核超时(ms):',
|
|
173
|
-
'set.judgeSaved': '
|
|
184
|
+
'set.judgeSaved': '审核设置已保存',
|
|
174
185
|
'set.judgeLang': '提示词语言',
|
|
175
186
|
'set.judgeLangZh': '中文',
|
|
176
187
|
'set.judgeLangEn': 'English',
|
|
177
|
-
'set.judgeLangHint': '
|
|
188
|
+
'set.judgeLangHint': '切换发给审核模型的框架语言与卡片标签,不会改当前审核表。下面的提示词按此语言编辑;恢复默认审核表也用这里选的语言。',
|
|
189
|
+
'set.judgePrompt': '审核提示词',
|
|
190
|
+
'set.judgePromptHint': '按所选语言内置默认提示词。用 {{criteria}} 插入当前审核表;删掉占位符时会把审核表附在末尾。与审核模型、超时一起保存。',
|
|
191
|
+
'set.judgePromptCustom': '已自定义',
|
|
192
|
+
'set.judgePromptDefault': '默认',
|
|
193
|
+
'set.resetJudgePrompt': '恢复默认提示词',
|
|
194
|
+
'set.resetJudgePromptOk': '已恢复默认提示词',
|
|
178
195
|
'set.overwriteOk': '已覆盖损坏的插件配置',
|
|
179
196
|
'err.missingPayload': '工具参数不完整,转人工',
|
|
180
197
|
'err.missingPayloadUncaptured': '未捕获工具参数,转人工',
|
|
@@ -215,6 +232,7 @@ export const zh = {
|
|
|
215
232
|
'err.judgeUpstream': '审核模型路由失败:{error}',
|
|
216
233
|
'err.noPresetsKey': 'permission 条目缺少 presets 键,请手动添加',
|
|
217
234
|
'err.preset': '写入预设失败:{error}',
|
|
235
|
+
'err.presetSandboxMissing': '预设里没有 sandbox 行,无法写入沙箱模式;请检查 profile 的 cordis.patch.yml',
|
|
218
236
|
'rpc.unavailable': 'connection.rpc 不可用',
|
|
219
237
|
'rpc.failed': 'RPC 失败',
|
|
220
238
|
}
|
|
@@ -313,7 +331,15 @@ export const en = {
|
|
|
313
331
|
'detail.script': 'Script',
|
|
314
332
|
'detail.sql': 'SQL',
|
|
315
333
|
'detail.prompt': 'Prompt',
|
|
334
|
+
'detail.input': 'Input',
|
|
335
|
+
'detail.text': 'Text',
|
|
336
|
+
'detail.body': 'Body',
|
|
337
|
+
'detail.message': 'Message',
|
|
338
|
+
'detail.pattern': 'Pattern',
|
|
339
|
+
'detail.selector': 'Selector',
|
|
316
340
|
'detail.cwd': 'Working directory',
|
|
341
|
+
'detail.workdir': 'Command working directory',
|
|
342
|
+
'detail.empty': '(empty)',
|
|
317
343
|
'detail.sandbox': 'Sandbox',
|
|
318
344
|
'detail.justification': 'Model justification',
|
|
319
345
|
'detail.pipe': 'Pipeline',
|
|
@@ -336,6 +362,8 @@ export const en = {
|
|
|
336
362
|
'set.loadFailed': 'Failed to load: {error}',
|
|
337
363
|
'set.resetConfirm': 'Click again to confirm restore',
|
|
338
364
|
'set.presetMissingTitle': 'Permission preset',
|
|
365
|
+
'set.driftTitle': 'Permission preset table needs updating',
|
|
366
|
+
'set.driftBody': 'The shipped permission table has new presets: {keys}. This profile\'s permission row is written by this plugin, and a patch replaces that config wholesale, so they will not appear automatically. Update the plugin or merge that row by hand.',
|
|
339
367
|
'set.presetMissingSub': 'Startup writes auto-approve. It was not detected; you can write it now.',
|
|
340
368
|
'set.presetWrite': 'Write permission preset',
|
|
341
369
|
'set.presetWriteNote': 'Restart dsh web after writing',
|
|
@@ -345,6 +373,7 @@ export const en = {
|
|
|
345
373
|
'set.modeWsHint': 'In-workspace writes skip approval; outside the workspace still goes through the judge.',
|
|
346
374
|
'set.modeRo': 'Read-only',
|
|
347
375
|
'set.modeRoHint': 'In-workspace writes also go through the judge.',
|
|
376
|
+
'set.modeHint': 'Clicking a mode writes it. Then restart dsh web and re-select Auto-approve or start a new session.',
|
|
348
377
|
'set.save': 'Save',
|
|
349
378
|
'set.hotOk': 'Applied (live; no restart)',
|
|
350
379
|
'set.modeSaved': 'Written. Restart dsh web and re-select Auto-approve or start a new session',
|
|
@@ -384,11 +413,17 @@ export const en = {
|
|
|
384
413
|
'set.modelDefaultEffort': 'Model default',
|
|
385
414
|
'set.effortOff': 'off (recommended)',
|
|
386
415
|
'set.judgeTimeoutMs': 'Judge timeout (ms):',
|
|
387
|
-
'set.judgeSaved': 'Judge
|
|
416
|
+
'set.judgeSaved': 'Judge settings saved',
|
|
388
417
|
'set.judgeLang': 'Prompt language',
|
|
389
418
|
'set.judgeLangZh': 'Chinese',
|
|
390
419
|
'set.judgeLangEn': 'English',
|
|
391
|
-
'set.judgeLangHint': '
|
|
420
|
+
'set.judgeLangHint': 'Switches the judge prompt framework and card labels, not the current criteria table. The prompt below is edited per language; restore default criteria also uses this language.',
|
|
421
|
+
'set.judgePrompt': 'Judge prompt',
|
|
422
|
+
'set.judgePromptHint': 'Built-in default for the selected language. Use {{criteria}} to insert the current criteria table; if the placeholder is removed, the table is appended. Saved with the judge model and timeout.',
|
|
423
|
+
'set.judgePromptCustom': 'Custom',
|
|
424
|
+
'set.judgePromptDefault': 'Default',
|
|
425
|
+
'set.resetJudgePrompt': 'Restore default prompt',
|
|
426
|
+
'set.resetJudgePromptOk': 'Default prompt restored',
|
|
392
427
|
'set.overwriteOk': 'Corrupt plugin config overwritten',
|
|
393
428
|
'err.missingPayload': 'Tool arguments are incomplete; sending to human',
|
|
394
429
|
'err.missingPayloadUncaptured': 'Tool arguments were not captured; sending to human',
|
|
@@ -429,6 +464,7 @@ export const en = {
|
|
|
429
464
|
'err.judgeUpstream': 'Judge route failed: {error}',
|
|
430
465
|
'err.noPresetsKey': 'The permission entry has no presets key; add it manually',
|
|
431
466
|
'err.preset': 'Failed to write preset: {error}',
|
|
467
|
+
'err.presetSandboxMissing': 'The preset has no sandbox line; cannot set the sandbox mode. Check the profile cordis.patch.yml',
|
|
432
468
|
'rpc.unavailable': 'connection.rpc is unavailable',
|
|
433
469
|
'rpc.failed': 'RPC failed',
|
|
434
470
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dnalec/dsh-auto-approve",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "DeepSeek Harness 自动审批门控 + 设置页",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"test": "node --test tests/*.test.mjs",
|
|
25
|
-
"check": "node --check src/index.mjs && node --check src/rules.mjs && node --check src/preset-patch.mjs && node --check src/util.mjs && node --check locales.mjs && node --check client.js"
|
|
25
|
+
"check": "node --check src/index.mjs && node --check src/rules.mjs && node --check src/preset-patch.mjs && node --check src/util.mjs && node --check locales.mjs && node --check client.js && node --check scripts/sync-locales.mjs && node scripts/sync-locales.mjs",
|
|
26
|
+
"locales:sync": "node scripts/sync-locales.mjs --write"
|
|
26
27
|
},
|
|
27
28
|
"keywords": [
|
|
28
29
|
"deepseek-harness",
|
package/src/index.mjs
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
NAME,
|
|
19
19
|
pathsFor,
|
|
20
|
+
resolveProfilePatchPath,
|
|
20
21
|
tryLoadJson,
|
|
21
22
|
saveJson,
|
|
22
23
|
audit as appendAudit,
|
|
@@ -37,12 +38,15 @@ import {
|
|
|
37
38
|
parseReason,
|
|
38
39
|
matchKeywordBuckets,
|
|
39
40
|
buildJudgePrompt,
|
|
41
|
+
resolveJudgePromptTemplate,
|
|
42
|
+
shippedJudgePromptTemplate,
|
|
40
43
|
parseJudgeClassify,
|
|
41
44
|
pickToolArgs,
|
|
42
45
|
toolArgsTruncated,
|
|
43
46
|
clipToolArgsForEvent,
|
|
44
47
|
formatKeywordHay,
|
|
45
48
|
formatAllowKeywordHay,
|
|
49
|
+
formatPathKeywordHay,
|
|
46
50
|
formatJudgeCard,
|
|
47
51
|
hasToolPayload,
|
|
48
52
|
rememberCachedCall,
|
|
@@ -53,12 +57,25 @@ import {
|
|
|
53
57
|
mutateAllowlistOp,
|
|
54
58
|
fail,
|
|
55
59
|
effectiveJudgeTimeoutMs,
|
|
60
|
+
judgeMaxTokens,
|
|
56
61
|
} from './rules.mjs'
|
|
57
|
-
import {
|
|
62
|
+
import { dirname } from 'node:path'
|
|
63
|
+
import {
|
|
64
|
+
getSetupState,
|
|
65
|
+
migratePresetCopy,
|
|
66
|
+
presetDrift,
|
|
67
|
+
readBasePresetKeys,
|
|
68
|
+
setAutoApproveSandbox,
|
|
69
|
+
} from './preset-patch.mjs'
|
|
58
70
|
|
|
59
71
|
export const name = NAME
|
|
60
|
-
/**
|
|
61
|
-
|
|
72
|
+
/**
|
|
73
|
+
* 只列门控真正需要的服务。**不要**放 `webServer`:cordis 把插件 `inject` 当必需服务,
|
|
74
|
+
* 缺一个就停在 PENDING、apply 完全不执行(`vendor/cordis/src/fiber.ts`),
|
|
75
|
+
* 而 `webserver` 行只在 web-app bundle 里 —— headless / acp / sdk 组合下会连审批门控一起失踪。
|
|
76
|
+
* RPC 侧自己用 `ctx.inject(['connection'], …)`,`connection.fetch.register` 不需要 webServer。
|
|
77
|
+
*/
|
|
78
|
+
export const inject = ['approval', 'permissionPresets', 'llm', 'timer']
|
|
62
79
|
|
|
63
80
|
/** Host Session 的工作目录在 header.cwd,没有 session.cwd。 */
|
|
64
81
|
export function readSessionCwd(session) {
|
|
@@ -80,6 +97,8 @@ export function rpcFail(code, details) {
|
|
|
80
97
|
*/
|
|
81
98
|
export function apply(ctx, rawConfig = {}) {
|
|
82
99
|
const paths = pathsFor()
|
|
100
|
+
// 真实 profile 目录从 ctx.baseUrl 推导;拿不到才回落到 profiles/web。
|
|
101
|
+
paths.profilePatch = resolveProfilePatchPath(ctx, rawConfig, paths.profilePatch)
|
|
83
102
|
ensureDir(paths.auto)
|
|
84
103
|
migratePresetCopy(paths.profilePatch)
|
|
85
104
|
|
|
@@ -132,10 +151,24 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
132
151
|
if (presetSetup.ok && presetSetup.needRestart) {
|
|
133
152
|
console.log(`[${NAME}] 已写入 auto-approve 权限预设(sandbox=${pluginCfg.presetSandbox});live patch 重载后会话权限会出现「自动审批」`)
|
|
134
153
|
} else if (!presetSetup.ok) {
|
|
135
|
-
console.error(
|
|
154
|
+
console.error(
|
|
155
|
+
`[${NAME}] 写入 auto-approve 预设失败(${presetSetup.code || presetSetup.error || 'err.preset'}):${paths.profilePatch}`,
|
|
156
|
+
presetSetup.details || '',
|
|
157
|
+
)
|
|
136
158
|
}
|
|
137
159
|
}
|
|
138
160
|
}
|
|
161
|
+
{
|
|
162
|
+
// 出厂预设表比本 profile 那份新:只警告,不自动改写用户文件(patch 里 config 是整块替换)。
|
|
163
|
+
const drift = presetSetupState().drift
|
|
164
|
+
if (drift.baseKnown && drift.missing.length) {
|
|
165
|
+
console.warn(
|
|
166
|
+
`[${NAME}] DSH 出厂权限预设表多了 ${drift.missing.join(', ')},`
|
|
167
|
+
+ `但 profile 的 permission 行是本插件写入的副本(patch 整块替换 config),不会自动包含;`
|
|
168
|
+
+ `请更新插件或手工合并 ${paths.profilePatch}`,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
139
172
|
let eventSeq = maxEventId(paths.events)
|
|
140
173
|
let rpcTail = Promise.resolve()
|
|
141
174
|
function enqueueRpc(fn) {
|
|
@@ -151,6 +184,18 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
151
184
|
const pendingCalls = new Map()
|
|
152
185
|
const log = (line) => console.log(`[${NAME}] ${line}`)
|
|
153
186
|
const audit = (line) => appendAudit(paths.audit, line)
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* setup 状态 + 预设表漂移。
|
|
190
|
+
* 插件写进 profile patch 的 `permission` 行会整块替换 base 的 config(patch 语义不做深合并),
|
|
191
|
+
* 所以 DSH 出厂表新增预设时本 profile 不会有:读 base 的 patch 比一比,只提示、不自动改写。
|
|
192
|
+
*/
|
|
193
|
+
function presetSetupState() {
|
|
194
|
+
const setup = getSetupState(paths.profilePatch)
|
|
195
|
+
const base = readBasePresetKeys(dirname(paths.profilePatch))
|
|
196
|
+
if (!base.ok) return { ...setup, drift: { baseKnown: false, missing: [], extra: [] } }
|
|
197
|
+
return { ...setup, drift: { baseKnown: true, baseKeys: base.keys, ...presetDrift(base.keys, setup.presets) } }
|
|
198
|
+
}
|
|
154
199
|
const llm = ctx.llm
|
|
155
200
|
const permissionPresets = ctx.permissionPresets
|
|
156
201
|
const agentDefaultModel = ctx.get('agentDefaultModel')
|
|
@@ -324,11 +369,11 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
324
369
|
}
|
|
325
370
|
|
|
326
371
|
/**
|
|
327
|
-
* 调用审核模型。不要传 messages.system
|
|
328
|
-
* 分类提示全部折进 user
|
|
372
|
+
* 调用审核模型。不要传 messages.system:部分 OpenAI 兼容网关会把 system 映射成 developer 导致 400。
|
|
373
|
+
* 分类提示全部折进 user 文本。带推理档位时输出预算要留出推理 token,否则空文本会被当成解析失败。
|
|
329
374
|
*/
|
|
330
375
|
async function callJudge(userText, signal, route, system) {
|
|
331
|
-
const prompt = system || buildJudgePrompt(allowlist.criteria, pluginCfg.judgePromptLang)
|
|
376
|
+
const prompt = system || buildJudgePrompt(allowlist.criteria, pluginCfg.judgePromptLang, resolveJudgePromptTemplate(pluginCfg, pluginCfg.judgePromptLang))
|
|
332
377
|
const opts = {
|
|
333
378
|
provider: route.provider,
|
|
334
379
|
model: route.model,
|
|
@@ -337,7 +382,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
337
382
|
content: [{ type: 'text', text: prompt + '\n\n' + userText }],
|
|
338
383
|
}],
|
|
339
384
|
temperature: 0,
|
|
340
|
-
maxTokens:
|
|
385
|
+
maxTokens: judgeMaxTokens(route.reasoningEffort),
|
|
341
386
|
signal,
|
|
342
387
|
}
|
|
343
388
|
if (route.reasoningEffort) opts.reasoningEffort = route.reasoningEffort
|
|
@@ -359,7 +404,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
359
404
|
const criteria = allowlist.criteria || DEFAULT_CRITERIA
|
|
360
405
|
const lang = normalizeJudgePromptLang(pluginCfg.judgePromptLang)
|
|
361
406
|
const user = formatJudgeCard(toolName, mode, justification, args, cwd, lang)
|
|
362
|
-
const text = await callJudge(user, signal, route, buildJudgePrompt(criteria, lang))
|
|
407
|
+
const text = await callJudge(user, signal, route, buildJudgePrompt(criteria, lang, resolveJudgePromptTemplate(pluginCfg, lang)))
|
|
363
408
|
try {
|
|
364
409
|
return { ...parseJudgeClassify(text, criteria), raw: String(text || '').slice(0, 800) }
|
|
365
410
|
} catch (error) {
|
|
@@ -368,10 +413,24 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
368
413
|
}
|
|
369
414
|
}
|
|
370
415
|
|
|
371
|
-
|
|
416
|
+
/**
|
|
417
|
+
* 单次判定 + 超时 + 重试。
|
|
418
|
+
* `outerSignal` 是审批请求自己的取消信号:请求被取消后不该继续烧模型调用。
|
|
419
|
+
* 注意**不要**去 abort `req.signal`,这里只观察它。
|
|
420
|
+
*/
|
|
421
|
+
async function withRetry(runFn, label, timeoutMs, outerSignal) {
|
|
422
|
+
const cancelled = () => Boolean(outerSignal && outerSignal.aborted)
|
|
372
423
|
const runOnce = async () => {
|
|
424
|
+
if (cancelled()) return { aborted: true }
|
|
373
425
|
const controller = new AbortController()
|
|
374
426
|
let cancelTimer
|
|
427
|
+
const onOuterAbort = () => controller.abort(`${NAME}: ${label} 请求已取消`)
|
|
428
|
+
const linkable = outerSignal && typeof outerSignal.addEventListener === 'function'
|
|
429
|
+
if (linkable) {
|
|
430
|
+
outerSignal.addEventListener('abort', onOuterAbort, { once: true })
|
|
431
|
+
// 竞态窗口:cancelled() 之后、addEventListener 之前就 abort 了,事件不会再触发。
|
|
432
|
+
if (outerSignal.aborted) onOuterAbort()
|
|
433
|
+
}
|
|
375
434
|
const timed = new Promise((resolve) => {
|
|
376
435
|
cancelTimer = ctx.timeout(() => resolve({ timedOut: true }), timeoutMs)
|
|
377
436
|
})
|
|
@@ -380,18 +439,23 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
380
439
|
.then((r) => ({ ...r, timedOut: false }))
|
|
381
440
|
.catch((error) => ({ judgeError: error }))
|
|
382
441
|
const result = await Promise.race([call, timed])
|
|
442
|
+
if (cancelled()) return { aborted: true }
|
|
383
443
|
if (result.judgeError) throw result.judgeError
|
|
384
444
|
return result
|
|
385
445
|
} finally {
|
|
386
446
|
if (typeof cancelTimer === 'function') {
|
|
387
447
|
try { cancelTimer() } catch { /* disposer */ }
|
|
388
448
|
}
|
|
449
|
+
if (linkable && typeof outerSignal.removeEventListener === 'function') {
|
|
450
|
+
outerSignal.removeEventListener('abort', onOuterAbort)
|
|
451
|
+
}
|
|
389
452
|
controller.abort(`${NAME}: ${label} 结束`)
|
|
390
453
|
}
|
|
391
454
|
}
|
|
392
455
|
let last = { failed: true }
|
|
393
456
|
try {
|
|
394
457
|
const first = await runOnce()
|
|
458
|
+
if (first.aborted) return first
|
|
395
459
|
if (!first.timedOut) return first
|
|
396
460
|
last = { failed: true, timedOut: true, errorCode: 'err.judgeTimeout', error: 'err.judgeTimeout', errorMs: String(timeoutMs) }
|
|
397
461
|
console.warn(`[${NAME}] ${label} 超时(${timeoutMs}ms),转人工`)
|
|
@@ -413,6 +477,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
413
477
|
}
|
|
414
478
|
try {
|
|
415
479
|
const second = await runOnce()
|
|
480
|
+
if (second.aborted) return second
|
|
416
481
|
if (!second.timedOut) return second
|
|
417
482
|
last = { failed: true, timedOut: true, errorCode: 'err.judgeRetryTimeout', error: 'err.judgeRetryTimeout', errorMs: String(timeoutMs) }
|
|
418
483
|
console.warn(`[${NAME}] ${label} 重试超时(${timeoutMs}ms)`)
|
|
@@ -431,7 +496,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
431
496
|
}
|
|
432
497
|
}
|
|
433
498
|
|
|
434
|
-
async function judgeOperation(toolName, mode, justification, args, cwd) {
|
|
499
|
+
async function judgeOperation(toolName, mode, justification, args, cwd, requestSignal) {
|
|
435
500
|
const route = await resolveJudgeRoute()
|
|
436
501
|
const meta = {
|
|
437
502
|
provider: route.provider || '',
|
|
@@ -447,7 +512,11 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
447
512
|
(signal) => judgeOnce(toolName, mode, justification, args, signal, route, cwd),
|
|
448
513
|
'审核模型',
|
|
449
514
|
timeoutMs,
|
|
515
|
+
requestSignal,
|
|
450
516
|
)
|
|
517
|
+
if (result.aborted) {
|
|
518
|
+
return { aborted: true, criterion: 'other', reason: '', ...meta }
|
|
519
|
+
}
|
|
451
520
|
if (result.failed) {
|
|
452
521
|
return {
|
|
453
522
|
action: 'human',
|
|
@@ -588,7 +657,8 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
588
657
|
return toHuman('missing-payload', 'other', { judgeReason: why })
|
|
589
658
|
}
|
|
590
659
|
const hay = formatKeywordHay(toolName, reason, toolArgs, sessionCwd)
|
|
591
|
-
const
|
|
660
|
+
const pathHay = formatPathKeywordHay(toolArgs, sessionCwd)
|
|
661
|
+
const kw = matchKeywordBuckets(hay, allowlist, formatAllowKeywordHay(toolArgs), pathHay)
|
|
592
662
|
|
|
593
663
|
const eventDetail = { args: toolArgs, cwd: sessionCwd }
|
|
594
664
|
if (kw && kw.action === 'reject') {
|
|
@@ -617,9 +687,15 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
617
687
|
return toHuman('keyword-human', '')
|
|
618
688
|
}
|
|
619
689
|
|
|
620
|
-
const judged = await judgeOperation(toolName, mode, justification, toolArgs, sessionCwd)
|
|
690
|
+
const judged = await judgeOperation(toolName, mode, justification, toolArgs, sessionCwd, req.signal)
|
|
621
691
|
const criterion = judged.criterion || 'other'
|
|
622
692
|
const judgeReason = judged.reason || ''
|
|
693
|
+
if (judged.aborted) {
|
|
694
|
+
// 请求已被取消(工具执行 signal abort)。审批服务会以 cancelled 结算并丢弃迟到结果,
|
|
695
|
+
// 这里不再占坑、也不再调 next(),避免取消后还弹人工框。
|
|
696
|
+
audit(`CANCEL ${toolName} mode=${mode || 'none'} judge aborted`)
|
|
697
|
+
return 'cancelled'
|
|
698
|
+
}
|
|
623
699
|
if (judged.failed) {
|
|
624
700
|
audit(`FAILED ${toolName} mode=${mode || 'none'} → 人工 | ${judged.error || reason.slice(0, 120)}`)
|
|
625
701
|
return toHuman('judge-failed', criterion, {
|
|
@@ -703,8 +779,12 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
703
779
|
rejectKeywords: shippedRejectKeywords(),
|
|
704
780
|
humanKeywords: [],
|
|
705
781
|
criteria: shippedCriteria(pluginCfg.judgePromptLang),
|
|
782
|
+
judgePrompts: {
|
|
783
|
+
zh: shippedJudgePromptTemplate('zh'),
|
|
784
|
+
en: shippedJudgePromptTemplate('en'),
|
|
785
|
+
},
|
|
706
786
|
},
|
|
707
|
-
setup:
|
|
787
|
+
setup: presetSetupState(),
|
|
708
788
|
plugin: pluginCfg,
|
|
709
789
|
pluginCorrupt: pluginCfgCorrupt,
|
|
710
790
|
providers: (() => {
|
|
@@ -766,6 +846,9 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
766
846
|
if (body && Object.prototype.hasOwnProperty.call(body, 'presetSandbox')) {
|
|
767
847
|
preset = setAutoApproveSandbox(paths.profilePatch, pluginCfg.presetSandbox)
|
|
768
848
|
if (preset && !preset.ok) {
|
|
849
|
+
// 沙箱没写进 patch 就整个回滚:不能让 config.json 说 read-only、patch 还是全权限。
|
|
850
|
+
pluginCfg = prev
|
|
851
|
+
persistPluginCfg({ overwriteCorrupt: Boolean(body.overwriteCorrupt) })
|
|
769
852
|
return rpcFail(preset.code || 'err.preset', preset.details || { error: String(preset.error || '') })
|
|
770
853
|
|
|
771
854
|
}
|
|
@@ -780,7 +863,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
780
863
|
persistPluginCfg()
|
|
781
864
|
}
|
|
782
865
|
audit('CONFIG plugin 已更新')
|
|
783
|
-
return { ok: true, value: { ok: true, plugin: pluginCfg, preset, setup:
|
|
866
|
+
return { ok: true, value: { ok: true, plugin: pluginCfg, preset, setup: presetSetupState() } }
|
|
784
867
|
}
|
|
785
868
|
if (endpoint === 'judge-catalog') {
|
|
786
869
|
const provider = String(body.provider || configuredRoute().provider)
|
|
@@ -860,5 +943,5 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
860
943
|
}
|
|
861
944
|
})
|
|
862
945
|
|
|
863
|
-
log(`已挂载:关键词→审核表 sandbox=${pluginCfg.presetSandbox}`)
|
|
946
|
+
log(`已挂载:关键词→审核表 sandbox=${pluginCfg.presetSandbox} profilePatch=${paths.profilePatch}`)
|
|
864
947
|
}
|