dsh-vision-router 2.1.2 → 2.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/docs/releases/v2.1.3.md +34 -0
- package/index.js +79 -94
- package/lib/client-presentation-boundary-main.js +478 -2
- package/lib/client.js +4 -4
- package/lib/depth-guidance.js +4 -4
- package/lib/local-vision-stabilizer.js +1 -1
- package/lib/mixed-router.js +8 -8
- package/lib/runtime-i18n-boundary.js +27 -2
- package/lib/runtime-i18n.js +6 -6
- package/lib/settings-ia-client-prelude.js +1 -1
- package/lib/structured-bootstrap.js +2 -2
- package/lib/structured-flow-hardening.js +159 -158
- package/package.json +3 -3
|
@@ -20,9 +20,7 @@ const MAX_TURN_BUDGET_MS = 600_000
|
|
|
20
20
|
const MAX_GUIDANCE_OVERRIDES = 14
|
|
21
21
|
const MAX_GUIDANCE_CHARS = 2_000
|
|
22
22
|
const MAX_NON_PROGRESS_ATTEMPTS = 3
|
|
23
|
-
const MAX_EVIDENCE_SIGNATURE_CHARS = 4_096
|
|
24
23
|
const BUDGETED_TIMEOUT_FIELDS = ['timeoutMs', 'visionTaskTimeoutMs', 'ocrTimeoutMs']
|
|
25
|
-
const EVIDENCE_META_KEYS = new Set(['ok', 'code', 'retryable', 'reason', 'phase', 'next'])
|
|
26
24
|
const GUIDANCE_KINDS = new Set([
|
|
27
25
|
'code',
|
|
28
26
|
'document',
|
|
@@ -40,25 +38,6 @@ const GUIDANCE_KINDS = new Set([
|
|
|
40
38
|
'meme',
|
|
41
39
|
])
|
|
42
40
|
|
|
43
|
-
const BUILTIN_MIXED_GUIDANCE = {
|
|
44
|
-
zh: {
|
|
45
|
-
document: '语义优先;只有逐字引用、合同/表单字段或表格数字确需保真时才使用 OCR。',
|
|
46
|
-
ui: '优先用 vision_detect / vision_ground 验证元素、状态与位置。',
|
|
47
|
-
code: '逐字核对代码;对可执行字符、缩进和符号做交叉验证。',
|
|
48
|
-
chat: '按气泡顺序核对发言者、消息内容与时间/状态信息。',
|
|
49
|
-
general: '聚焦尚未归类的另一部分可见内容,自行选择最能新增证据的视觉工具。',
|
|
50
|
-
fallback: '聚焦这个分支新增或验证可见证据。',
|
|
51
|
-
},
|
|
52
|
-
en: {
|
|
53
|
-
document: 'Prioritize semantics; use OCR only when verbatim quotes, contract/form fields, or table numbers require exact transcription.',
|
|
54
|
-
ui: 'Prefer vision_detect / vision_ground to verify elements, states, and positions.',
|
|
55
|
-
code: 'Verify code verbatim; cross-check executable characters, indentation, and symbols.',
|
|
56
|
-
chat: 'Verify speakers, message text, and time/status information in bubble order.',
|
|
57
|
-
general: 'Focus on the remaining unclassified visible content and choose the vision tool that adds the most useful evidence.',
|
|
58
|
-
fallback: 'Add or verify visible evidence for this branch.',
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
41
|
function isObject(value) {
|
|
63
42
|
return value !== null && typeof value === 'object'
|
|
64
43
|
}
|
|
@@ -98,36 +77,143 @@ export function producedStructuredEvidence(value) {
|
|
|
98
77
|
return true
|
|
99
78
|
}
|
|
100
79
|
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
80
|
+
function finiteNumber(value) {
|
|
81
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function positiveInteger(value) {
|
|
85
|
+
return Number.isInteger(value) && value > 0
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nonEmptyText(value) {
|
|
89
|
+
return typeof value === 'string' && value.trim() !== ''
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function canonicalBox(value, width, height) {
|
|
93
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
94
|
+
const { x1, y1, x2, y2 } = value
|
|
95
|
+
if (![x1, y1, x2, y2].every(Number.isInteger)) return false
|
|
96
|
+
if (x1 < 0 || y1 < 0 || x2 <= x1 || y2 <= y1) return false
|
|
97
|
+
if (positiveInteger(width) && x2 > width) return false
|
|
98
|
+
if (positiveInteger(height) && y2 > height) return false
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function structuredDescribeEvidence(value) {
|
|
103
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
104
|
+
if (nonEmptyText(value.summary) || nonEmptyText(value.text)) return true
|
|
105
|
+
if (Array.isArray(value.layout) && value.layout.some((row) => (
|
|
106
|
+
isObject(row) && nonEmptyText(row.region) && nonEmptyText(row.content)
|
|
107
|
+
))) return true
|
|
108
|
+
return Array.isArray(value.entities) && value.entities.some((entity) => (
|
|
109
|
+
isObject(entity) && nonEmptyText(entity.type) && nonEmptyText(entity.label)
|
|
110
|
+
))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function groundEvidence(value) {
|
|
114
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
115
|
+
const { width, height } = value
|
|
116
|
+
return positiveInteger(width) && positiveInteger(height) && canonicalBox(value, width, height)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function detectEvidence(value) {
|
|
120
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
121
|
+
const { width, height, elements } = value
|
|
122
|
+
if (!positiveInteger(width) || !positiveInteger(height) || !Array.isArray(elements)) return false
|
|
123
|
+
return elements.every((element, index) => (
|
|
124
|
+
isObject(element) &&
|
|
125
|
+
element.number === index + 1 &&
|
|
126
|
+
nonEmptyText(element.label) &&
|
|
127
|
+
canonicalBox(element.box, width, height)
|
|
110
128
|
))
|
|
111
129
|
}
|
|
112
130
|
|
|
131
|
+
function ocrEvidence(value) {
|
|
132
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
133
|
+
if (value.engine !== 'tesseract' && value.engine !== 'vision') return false
|
|
134
|
+
return typeof value.text === 'string'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function colorEvidence(value) {
|
|
138
|
+
if (!Array.isArray(value)) return false
|
|
139
|
+
return value.every((entry) => (
|
|
140
|
+
isObject(entry) &&
|
|
141
|
+
typeof entry.hex === 'string' && /^#[0-9a-f]{6}$/i.test(entry.hex) &&
|
|
142
|
+
positiveInteger(entry.count) &&
|
|
143
|
+
finiteNumber(entry.share) && entry.share > 0 && entry.share <= 1
|
|
144
|
+
))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function pixelDiffEvidence(value) {
|
|
148
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
149
|
+
const { width, height, differingPixels, totalPixels, diffRatio, worstRegions } = value
|
|
150
|
+
if (!positiveInteger(width) || !positiveInteger(height) || totalPixels !== width * height) return false
|
|
151
|
+
if (!Number.isInteger(differingPixels) || differingPixels < 0 || differingPixels > totalPixels) return false
|
|
152
|
+
if (!finiteNumber(diffRatio) || diffRatio < 0 || diffRatio > 1) return false
|
|
153
|
+
if (Math.abs(diffRatio - differingPixels / totalPixels) > 0.000051) return false
|
|
154
|
+
if (!Array.isArray(worstRegions)) return false
|
|
155
|
+
return worstRegions.every((region) => (
|
|
156
|
+
isObject(region) &&
|
|
157
|
+
canonicalBox(region, width, height) &&
|
|
158
|
+
Number.isInteger(region.differing) && region.differing > 0 &&
|
|
159
|
+
positiveInteger(region.total) && region.differing <= region.total &&
|
|
160
|
+
finiteNumber(region.ratio) && region.ratio > 0 && region.ratio <= 1
|
|
161
|
+
))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function longOcrEvidence(value) {
|
|
165
|
+
if (!isObject(value) || Array.isArray(value)) return false
|
|
166
|
+
if (typeof value.text !== 'string' || !positiveInteger(value.chunks)) return false
|
|
167
|
+
if (!isObject(value.engines) || Array.isArray(value.engines)) return false
|
|
168
|
+
if (![value.markdownPath, value.manifestPath, value.artifactsDir].every(nonEmptyText)) return false
|
|
169
|
+
const engineCounts = Object.values(value.engines)
|
|
170
|
+
if (engineCounts.length === 0 || !engineCounts.every((count) => Number.isInteger(count) && count >= 0)) return false
|
|
171
|
+
if (engineCounts.reduce((sum, count) => sum + count, 0) !== value.chunks) return false
|
|
172
|
+
if (nonEmptyText(value.text)) return true
|
|
173
|
+
const completed = (value.engines.tesseract ?? 0) + (value.engines.vision ?? 0)
|
|
174
|
+
return completed > 0
|
|
175
|
+
}
|
|
176
|
+
|
|
113
177
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
178
|
+
* Decide whether one registered evidence tool completed a real observation.
|
|
179
|
+
*
|
|
180
|
+
* This deliberately follows each tool's output contract instead of treating
|
|
181
|
+
* arbitrary non-empty containers as evidence. Valid negative observations
|
|
182
|
+
* (for example zero detections, empty OCR text, or a 0 pixel diff) still count;
|
|
183
|
+
* transport failures, malformed envelopes, and describe's empty-content
|
|
184
|
+
* sentinel do not.
|
|
118
185
|
*/
|
|
119
|
-
export function
|
|
120
|
-
if (value === undefined || value === null) return false
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
186
|
+
export function producedUsableToolEvidence(toolName, value, args = {}) {
|
|
187
|
+
if (!STRUCTURED_EVIDENCE_TOOLS.has(toolName) || value === undefined || value === null) return false
|
|
188
|
+
const parsed = parseJson(value)
|
|
189
|
+
if (isObject(parsed) && !Array.isArray(parsed) && parsed.ok === false) return false
|
|
190
|
+
|
|
191
|
+
switch (toolName) {
|
|
192
|
+
case 'vision_describe': {
|
|
193
|
+
if (typeof value !== 'string') return false
|
|
194
|
+
const text = value.trim()
|
|
195
|
+
if (text === '' || text === '(the vision model returned empty content)') return false
|
|
196
|
+
if (args?.json === true) {
|
|
197
|
+
if (text.startsWith('vision_describe: the model did not produce valid JSON.')) return false
|
|
198
|
+
return structuredDescribeEvidence(parsed)
|
|
199
|
+
}
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
case 'vision_ground':
|
|
203
|
+
return groundEvidence(parsed)
|
|
204
|
+
case 'vision_detect':
|
|
205
|
+
return detectEvidence(parsed)
|
|
206
|
+
case 'vision_ocr':
|
|
207
|
+
return ocrEvidence(parsed)
|
|
208
|
+
case 'vision_colors':
|
|
209
|
+
return colorEvidence(parsed)
|
|
210
|
+
case 'vision_pixel_diff':
|
|
211
|
+
return pixelDiffEvidence(parsed)
|
|
212
|
+
case 'vision_long_screenshot_ocr':
|
|
213
|
+
return longOcrEvidence(parsed)
|
|
214
|
+
default:
|
|
215
|
+
return false
|
|
127
216
|
}
|
|
128
|
-
if (Array.isArray(value)) return value.length > 0 && value.some(meaningfulEvidenceValue)
|
|
129
|
-
if (!isObject(value) || value.ok === false) return false
|
|
130
|
-
return meaningfulEvidenceValue(value)
|
|
131
217
|
}
|
|
132
218
|
|
|
133
219
|
/**
|
|
@@ -176,15 +262,6 @@ function languageOf(config) {
|
|
|
176
262
|
return runtimeLanguageFor(config?.__visionRouterLocale, 'zh')
|
|
177
263
|
}
|
|
178
264
|
|
|
179
|
-
function guidanceFor(kind, overrides, language = 'zh') {
|
|
180
|
-
for (let i = overrides.length - 1; i >= 0; i--) {
|
|
181
|
-
const entry = overrides[i]
|
|
182
|
-
if (entry.kind === kind) return entry.text
|
|
183
|
-
}
|
|
184
|
-
const copy = BUILTIN_MIXED_GUIDANCE[language] ?? BUILTIN_MIXED_GUIDANCE.zh
|
|
185
|
-
return copy[kind] ?? copy.fallback
|
|
186
|
-
}
|
|
187
|
-
|
|
188
265
|
function turnBudgetMs(config) {
|
|
189
266
|
const value = Number(config?.visionTurnBudgetMs)
|
|
190
267
|
if (!Number.isFinite(value) || value <= 0) return DEFAULT_TURN_BUDGET_MS
|
|
@@ -234,9 +311,7 @@ function freshState(turn, _config) {
|
|
|
234
311
|
bootstrapDone: false,
|
|
235
312
|
bootstrapResult: undefined,
|
|
236
313
|
successfulEvidenceCalls: 0,
|
|
237
|
-
|
|
238
|
-
completedBranches: new Set(),
|
|
239
|
-
mixedAttemptSignatures: new Set(),
|
|
314
|
+
postBootstrapEvidenceCalls: 0,
|
|
240
315
|
consecutiveNonProgressAttempts: 0,
|
|
241
316
|
budgetExhausted: false,
|
|
242
317
|
depthExhausted: false,
|
|
@@ -284,56 +359,13 @@ function bootstrapEvidence(raw) {
|
|
|
284
359
|
return parsed.evidence
|
|
285
360
|
}
|
|
286
361
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (evidence.mixed_of.includes(kind)) unique.push(kind)
|
|
293
|
-
}
|
|
294
|
-
return unique.slice(0, 2)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function evidenceAttemptSignature(name, args) {
|
|
298
|
-
let encoded
|
|
299
|
-
try {
|
|
300
|
-
encoded = JSON.stringify(args ?? null)
|
|
301
|
-
} catch {
|
|
302
|
-
encoded = String(args ?? '')
|
|
303
|
-
}
|
|
304
|
-
if (encoded.length <= MAX_EVIDENCE_SIGNATURE_CHARS) return `${name}:${encoded}`
|
|
305
|
-
const half = Math.floor(MAX_EVIDENCE_SIGNATURE_CHARS / 2)
|
|
306
|
-
return `${name}:${encoded.length}:${encoded.slice(0, half)}:${encoded.slice(-half)}`
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function inferBranchForTool(name, pending) {
|
|
310
|
-
const prefer = (kinds) => kinds.find((kind) => pending.includes(kind))
|
|
311
|
-
if (name === 'vision_detect' || name === 'vision_ground') return prefer(['ui', 'general'])
|
|
312
|
-
if (name === 'vision_long_screenshot_ocr') return prefer(['document', 'chat', 'code', 'general'])
|
|
313
|
-
if (name === 'vision_ocr') return prefer(['document', 'code', 'chat', 'general'])
|
|
314
|
-
if (name === 'vision_colors' || name === 'vision_pixel_diff') return prefer(['general', 'ui'])
|
|
315
|
-
return pending.length === 1 ? pending[0] : undefined
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function recordEvidenceSuccess(state, name, args) {
|
|
319
|
-
state.successfulEvidenceCalls += 1
|
|
320
|
-
if (state.mixedBranches.length === 0) return true
|
|
321
|
-
const pending = state.mixedBranches.filter((kind) => !state.completedBranches.has(kind))
|
|
322
|
-
if (pending.length === 0) return true
|
|
323
|
-
const signature = evidenceAttemptSignature(name, args)
|
|
324
|
-
if (state.mixedAttemptSignatures.has(signature)) return false
|
|
325
|
-
state.mixedAttemptSignatures.add(signature)
|
|
326
|
-
const branch = inferBranchForTool(name, pending)
|
|
327
|
-
if (!branch) return false
|
|
328
|
-
state.completedBranches.add(branch)
|
|
329
|
-
return true
|
|
330
|
-
}
|
|
331
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Structured 1+x has exactly one hard completion condition after bootstrap:
|
|
364
|
+
* at least one usable task-directed evidence observation. Mixed classifications
|
|
365
|
+
* remain model-visible advisory evidence; they do not create branch quotas.
|
|
366
|
+
*/
|
|
332
367
|
function missingEvidence(state) {
|
|
333
|
-
|
|
334
|
-
return state.mixedBranches.filter((kind) => !state.completedBranches.has(kind)).length
|
|
335
|
-
}
|
|
336
|
-
return state.successfulEvidenceCalls >= 1 ? 0 : 1
|
|
368
|
+
return state.postBootstrapEvidenceCalls >= 1 ? 0 : 1
|
|
337
369
|
}
|
|
338
370
|
|
|
339
371
|
function hasCoreFollowup(messages) {
|
|
@@ -347,6 +379,8 @@ function withoutEvidenceFollowups(messages) {
|
|
|
347
379
|
const id = message && typeof message.id === 'string' ? message.id : ''
|
|
348
380
|
return !(
|
|
349
381
|
id.includes('vision-router-structured-followup-') ||
|
|
382
|
+
// Strip historical Round-0 mixed hard guards if one is still present in
|
|
383
|
+
// a persisted/compatibility message surface. Round 1 never emits them.
|
|
350
384
|
id.includes('vision-router-structured-mixed-guard-') ||
|
|
351
385
|
id.includes('vision-router-structured-evidence-guard-')
|
|
352
386
|
)
|
|
@@ -374,30 +408,8 @@ function appendSyntheticGuardOnce(decision, baseMessages, state, message, stripE
|
|
|
374
408
|
}
|
|
375
409
|
|
|
376
410
|
function rearmEvidenceGuardAfterAttempt(state) {
|
|
377
|
-
const mixedPrefix = `vision-router-structured-mixed-guard-${state.turn}-`
|
|
378
411
|
const evidenceId = `vision-router-structured-evidence-guard-${state.turn}`
|
|
379
|
-
|
|
380
|
-
if (id === evidenceId || id.startsWith(mixedPrefix)) state.emittedGuardIds.delete(id)
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function mixedReminder(state, config) {
|
|
385
|
-
const language = languageOf(config)
|
|
386
|
-
const overrides = normalizeGuidanceOverrides(config?.guidanceOverrides)
|
|
387
|
-
const pending = state.mixedBranches.filter((kind) => !state.completedBranches.has(kind))
|
|
388
|
-
const done = state.mixedBranches.filter((kind) => state.completedBranches.has(kind))
|
|
389
|
-
const separator = language === 'en' ? ': ' : ':'
|
|
390
|
-
const lines = pending.map((kind) => `- ${kind}${separator}${guidanceFor(kind, overrides, language)}`)
|
|
391
|
-
if (language === 'en') {
|
|
392
|
-
const doneText = done.length > 0 ? `Verified branches: ${done.join(' + ')}. ` : ''
|
|
393
|
-
return `${doneText}This mixed image still has unverified branches: ${pending.join(' + ')}. ` +
|
|
394
|
-
'Focus each vision call on one unfinished branch; answer only after each branch has produced at least one useful piece of evidence.\n' +
|
|
395
|
-
lines.join('\n')
|
|
396
|
-
}
|
|
397
|
-
const doneText = done.length > 0 ? `已验证分支:${done.join(' + ')}。` : ''
|
|
398
|
-
return `${doneText}混合图片仍有未验证分支:${pending.join(' + ')}。` +
|
|
399
|
-
'每次视觉调用只聚焦一个尚未完成的分支;这些分支分别产生至少一次有效证据后再作答。\n' +
|
|
400
|
-
lines.join('\n')
|
|
412
|
+
state.emittedGuardIds.delete(evidenceId)
|
|
401
413
|
}
|
|
402
414
|
|
|
403
415
|
function appendGuardMessage(decision, payload, state, config) {
|
|
@@ -421,12 +433,12 @@ function appendGuardMessage(decision, payload, state, config) {
|
|
|
421
433
|
? 'The visual turn time budget is exhausted. Do not call more vision tools; answer from the evidence already collected and state any remaining uncertainty.'
|
|
422
434
|
: state.depthExhausted
|
|
423
435
|
? 'The configured deep-dive call cap has been reached. Do not call more vision tools; answer from the evidence already collected and state any remaining uncertainty.'
|
|
424
|
-
: 'Repeated follow-up vision attempts did not produce
|
|
436
|
+
: 'Repeated follow-up vision attempts did not produce usable evidence. Do not keep retrying vision tools; answer from the evidence already collected and state the remaining uncertainty.'
|
|
425
437
|
: state.budgetExhausted
|
|
426
438
|
? '本轮视觉总时间预算已耗尽。不要再调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。'
|
|
427
439
|
: state.depthExhausted
|
|
428
440
|
? '已达到本轮设置的深挖次数上限。不要再调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。'
|
|
429
|
-
: '
|
|
441
|
+
: '连续多次后续视觉调用都没有产出可用证据。不要继续重复调用视觉工具;请基于已经获得的证据作答,并明确仍存在的不确定性。',
|
|
430
442
|
}],
|
|
431
443
|
source: { kind: 'plugin', plugin: 'dsh-vision-router' },
|
|
432
444
|
}
|
|
@@ -435,16 +447,10 @@ function appendGuardMessage(decision, payload, state, config) {
|
|
|
435
447
|
|
|
436
448
|
if (!state.bootstrapDone || missingEvidence(state) === 0) return decision
|
|
437
449
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
content: [{ type: 'text', text: mixedReminder(state, config) }],
|
|
443
|
-
source: { kind: 'plugin', plugin: 'dsh-vision-router' },
|
|
444
|
-
}
|
|
445
|
-
return appendSyntheticGuardOnce(decision, baseMessages, state, message, true)
|
|
446
|
-
}
|
|
447
|
-
|
|
450
|
+
// Core owns bootstrap ordering and its one-shot follow-up guidance. This
|
|
451
|
+
// outer boundary owns the hard x>=1 completion rule. If Core's guidance is
|
|
452
|
+
// already present, do not duplicate it; if Core's legacy presentation latch
|
|
453
|
+
// stopped emitting it after an empty/failed attempt, re-assert the hard guard.
|
|
448
454
|
if (hasCoreFollowup(baseMessages)) return decision
|
|
449
455
|
const message = {
|
|
450
456
|
role: 'user',
|
|
@@ -452,8 +458,8 @@ function appendGuardMessage(decision, payload, state, config) {
|
|
|
452
458
|
content: [{
|
|
453
459
|
type: 'text',
|
|
454
460
|
text: language === 'en'
|
|
455
|
-
? 'The structured bootstrap is complete, but
|
|
456
|
-
: '
|
|
461
|
+
? 'The structured bootstrap is complete, but no usable task-directed visual evidence has been produced yet. Call at least one vision tool that can add or verify evidence, then answer after it succeeds.'
|
|
462
|
+
: '结构化预识别已经完成,但目前还没有产出可用的任务定向视觉证据。请至少调用一个能新增或验证证据的视觉工具,成功后再作答。',
|
|
457
463
|
}],
|
|
458
464
|
source: { kind: 'plugin', plugin: 'dsh-vision-router' },
|
|
459
465
|
}
|
|
@@ -542,9 +548,11 @@ function wrapRegisteredTool(def, states, getConfig) {
|
|
|
542
548
|
if (evidence !== undefined) {
|
|
543
549
|
state.bootstrapDone = true
|
|
544
550
|
state.bootstrapResult = result
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
551
|
+
// Structured 1+x is explicitly ordered: x starts only after the
|
|
552
|
+
// bootstrap succeeds. Keep the all-turn successful-evidence count
|
|
553
|
+
// intact for the user's explicit call cap; post-bootstrap progress
|
|
554
|
+
// has its own counter so an early model call cannot satisfy x>=1.
|
|
555
|
+
state.postBootstrapEvidenceCalls = 0
|
|
548
556
|
resetNonProgress(state)
|
|
549
557
|
state.budgetExhausted = false
|
|
550
558
|
refreshDepthExhaustion(state, getConfig())
|
|
@@ -565,7 +573,7 @@ function wrapRegisteredTool(def, states, getConfig) {
|
|
|
565
573
|
if (state.bootstrapDone && missingEvidence(state) > 0 && state.nonProgressExhausted) {
|
|
566
574
|
return failure(
|
|
567
575
|
'VISION_NO_PROGRESS_LIMIT',
|
|
568
|
-
`the structured follow-up stopped after ${MAX_NON_PROGRESS_ATTEMPTS} consecutive attempts without usable evidence
|
|
576
|
+
`the structured follow-up stopped after ${MAX_NON_PROGRESS_ATTEMPTS} consecutive attempts without usable evidence`,
|
|
569
577
|
)
|
|
570
578
|
}
|
|
571
579
|
|
|
@@ -577,18 +585,11 @@ function wrapRegisteredTool(def, states, getConfig) {
|
|
|
577
585
|
)
|
|
578
586
|
const code = resultCode(result)
|
|
579
587
|
if (code === 'VISION_TURN_BUDGET_EXCEEDED') state.budgetExhausted = true
|
|
580
|
-
if (
|
|
581
|
-
|
|
588
|
+
if (producedUsableToolEvidence(def.name, result, args)) {
|
|
589
|
+
state.successfulEvidenceCalls += 1
|
|
590
|
+
if (state.bootstrapDone) state.postBootstrapEvidenceCalls += 1
|
|
582
591
|
refreshDepthExhaustion(state, active)
|
|
583
|
-
|
|
584
|
-
if (progressed) resetNonProgress(state)
|
|
585
|
-
else {
|
|
586
|
-
recordNonProgress(state)
|
|
587
|
-
if (!state.nonProgressExhausted) rearmEvidenceGuardAfterAttempt(state)
|
|
588
|
-
}
|
|
589
|
-
} else {
|
|
590
|
-
resetNonProgress(state)
|
|
591
|
-
}
|
|
592
|
+
resetNonProgress(state)
|
|
592
593
|
} else if (code !== 'VISION_TURN_BUDGET_EXCEEDED' && state.bootstrapDone && missingEvidence(state) > 0) {
|
|
593
594
|
recordNonProgress(state)
|
|
594
595
|
if (!state.nonProgressExhausted) rearmEvidenceGuardAfterAttempt(state)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vision-router",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3",
|
|
4
4
|
"description": "Eyes for text-only DeepSeek Harness agents: built-in free vision chain (no key) + pixel-level vision tools (Q&A, grounding, crop, pixel diff, colors, OCR, SVG trace, cutout, screenshots). One-command install, no Python, image turns work like ordinary tool-calling turns.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -69,13 +69,13 @@
|
|
|
69
69
|
"sharp": "^0.35.3"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
|
-
"test": "node --test tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/rc6-real-settings-persistence.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/local-ollama.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/pi-ai-bridge-wire-compat.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/runtime-i18n.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && node --test tests/vision-backend-runtime-policy.test.js",
|
|
72
|
+
"test": "node --test tests/adapter-prepare-call-compat.test.js tests/core.test.js tests/issue-374-delegated-call-config.test.js tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/capability-advisory.test.js tests/vision-execution-policy.test.js tests/vision-backend-diagnostics.test.js tests/live-model-discovery.test.js tests/live-model-credential-resolution.test.js tests/vision-model-registry.test.js tests/live-model-client-stability.test.js tests/providers-persistence.test.js tests/provider-directory-fallback.test.js tests/model-catalog-policy.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-fj.test.js tests/settings-ia-acceptance-regression.test.js tests/settings-migration.test.js tests/settings-section-order.test.js tests/attachment-admission-policy.test.js tests/vision-resilience.test.js tests/vision-resilience-lifecycle.test.js tests/vision-breaker-readonly.test.js tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/issue-307-regression.test.js tests/issue-284-vision-mode-toggle.test.js tests/issue-284-vision-mode-hardening.test.js tests/issue-284-attachment-id-hint.test.js tests/issue-284-model-visibility.test.js tests/issue-284-vision-selection-effort.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/remote-settings-risk-confirmation.test.js tests/local-remote-settings-permission.test.js tests/rc6-real-settings-persistence.test.js tests/artifact-path-security.test.js tests/http-compat.test.js tests/catalog-corrections.test.js tests/update-check.test.js tests/update-check-signal-lifecycle.test.js tests/self-update.test.js tests/doctor.test.js tests/doctor-cli.test.js tests/legacy-session-repair.test.js tests/profile-pnpm-diagnostics.test.js tests/doctor-v2.test.js tests/doctor-cli-v2.test.js tests/doctor-runtime.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/session-repair-v2.test.js tests/bundle-defaults.test.js tests/file-logger.test.js tests/replay-delegation.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/adversarial-hardening.test.js tests/vision-adversarial-hardening-v2.test.js tests/vision-capability-benchmark-visual-proof.test.js tests/wrapper-directory.test.js tests/logging-ui.test.js tests/manifest-dependencies.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js tests/vision-model-guidance-contract.test.js tests/local-ollama.test.js tests/ollama-cold-start.test.js tests/ollama-cold-start-loaded.test.js tests/zero-regression-gate.test.js tests/runtime-e2e.test.js tests/local-vision-stabilizer.test.js tests/local-connection-probe.test.js tests/repetition-guard.test.js tests/guide-scroll-gate.test.js tests/settings-scroll-jank-regression.test.js tests/android-attachment-compat.test.js tests/free-cloud-first.test.js tests/rc6-rc7-compat.test.js tests/native-image-coexistence.test.js tests/issue-276-session-image-ownership.test.js tests/issue-276-entry-policy-bridge.test.js tests/issue-276-policy-hardening.test.js tests/issue-289-native-nonintervention.test.js tests/pi-ai-bridge-wire-compat.test.js tests/mixed-router.test.js tests/depth-tier.test.js tests/depth-quota-behavior.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-budget-activation.test.js tests/runtime-boundary-fixes.test.js tests/runtime-i18n.test.js tests/tesseract-node24-boot.test.js tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/structured-guard-idempotency.test.js tests/vision-routing-product.test.js tests/vision-runtime-performance.test.js tests/vision-routing-settings-prelude.test.js tests/v2-settings-ia-integration.test.js tests/vision-capability-router.test.js tests/vision-capability-reference.test.js tests/vision-capability-benchmark.test.js tests/vision-capability-grounding-proof-contract.test.js tests/vision-capability-probe.test.js tests/vision-capability-axis-freshness.test.js tests/vision-capability-shadow-health.test.js tests/vision-capability-benchmark-service.test.js tests/vision-capability-preflight-hardening.test.js tests/vision-capability-benchmark-contract.test.js tests/vision-capability-benchmark-client.test.js tests/vision-capability-benchmark-client-productization.test.js tests/vision-capability-adapter-route.test.js tests/vision-background-benchmark.test.js tests/vision-background-lifecycle.test.js tests/vision-background-benchmark-productization.test.js tests/vision-capability-benchmark-budget.test.js tests/vision-image-input-verdict.test.js tests/vision-capability-failure-classification.test.js tests/vision-capability-gate-client.test.js tests/v2-product-review-fixes.test.js tests/qa-top5-reliability.test.js tests/qa-screenshot-runtime.test.js tests/qa-runtime-identity.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js tests/qa-reliability-tail.test.js tests/qa-endpoint-route-alias.test.js tests/settings-native-card-semantics.test.js tests/settings-native-card-lazy-data.test.js tests/settings-invalid-staged-dirty.test.js tests/vision-toggle-root-hardening.test.js && node --test tests/vision-backend-runtime-policy.test.js",
|
|
73
73
|
"test:core": "node --test tests/core.test.js tests/runtime-e2e.test.js tests/structured-bootstrap.test.js tests/structured-flow-hardening.test.js tests/structured-flow-adversarial.test.js tests/vision-execution-policy.test.js tests/vision-backend-runtime-policy.test.js",
|
|
74
74
|
"test:routing": "node --test tests/issue-374-delegated-call-config.test.js tests/twin-image-capability-fallback.test.js tests/twin-image-custom-wrapper.test.js tests/vision-routing-product.test.js tests/vision-capability-router.test.js tests/vision-capability-probe.test.js tests/vision-capability-shadow-health.test.js tests/vision-runtime-performance.test.js tests/vision-background-benchmark.test.js tests/vision-image-input-verdict.test.js",
|
|
75
75
|
"test:session": "node --test tests/session-vision-state.test.js tests/session-vision-state-integration.test.js tests/replay-delegation.test.js tests/legacy-session-repair.test.js tests/session-repair-v2.test.js tests/issue-276-session-image-ownership.test.js",
|
|
76
76
|
"test:resources": "node --test tests/artifact-path-security.test.js tests/image-resource-governor.test.js tests/resource-retention-lifecycle.test.js tests/pixel-diff-stream.test.js tests/large-image-resource-integration.test.js tests/qa-turn-budget-cancellation.test.js tests/qa-cancellation-publication.test.js",
|
|
77
77
|
"test:compat": "node --test tests/adapter-prepare-call-compat.test.js tests/attachment-admission-policy.test.js tests/rc6-rc7-compat.test.js tests/rc6-real-settings-persistence.test.js tests/android-attachment-compat.test.js tests/http-compat.test.js tests/pi-ai-bridge-wire-compat.test.js tests/native-image-coexistence.test.js tests/tesseract-node24-boot.test.js tests/dsh-host-capabilities.test.js",
|
|
78
|
-
"test:web": "node --test tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/client.test.js tests/client-presentation-boundary.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-acceptance-regression.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/local-remote-settings-permission.test.js tests/v2-settings-ia-integration.test.js tests/logging-ui.test.js",
|
|
78
|
+
"test:web": "node --test tests/alpha1-browser-lifecycle-integration.test.js tests/issue-367-remote-session-inject.test.js tests/vision-mode-toggle-injection.test.js tests/client.test.js tests/client-presentation-boundary.test.js tests/clipboard-image-paste-compat.test.js tests/settings-ia-client-prelude.test.js tests/settings-ia-acceptance-regression.test.js tests/remote-settings-bridge.test.js tests/remote-settings-v2-contract.test.js tests/local-remote-settings-permission.test.js tests/v2-settings-ia-integration.test.js tests/logging-ui.test.js",
|
|
79
79
|
"test:contract": "node --test tests/manifest-dependencies.test.js tests/bundle-defaults.test.js tests/zero-regression-gate.test.js tests/issue-289-native-nonintervention.test.js tests/issue-374-delegated-call-config.test.js tests/runtime-boundary-fixes.test.js tests/runtime-i18n.test.js tests/adapter-prepare-call-compat.test.js tests/rc6-rc7-compat.test.js tests/attachment-admission-policy.test.js tests/dsh-host-capabilities.test.js tests/doctor-host-capabilities.test.js tests/structured-bootstrap.test.js tests/structured-bootstrap-gate.test.js",
|
|
80
80
|
"test:grounding": "node --test tests/grounding-coordinate-frame.test.js tests/grounding-coordinate-runtime.test.js tests/vision-capability-grounding-proof-contract.test.js",
|
|
81
81
|
"test:stress": "node scripts/image-resource-stress.mjs && node --test tests/large-image-resource-integration.test.js tests/resource-retention-lifecycle.test.js"
|