dsh-vision-router 2.0.0 → 2.1.0
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.md +33 -16
- package/README.zh.md +34 -17
- package/cordis.patch.yml +19 -9
- package/docs/architecture/2x-contract-ledger.md +165 -0
- package/docs/architecture/compat-inventory.md +121 -0
- package/docs/architecture/dsh-compatibility-matrix.md +57 -0
- package/docs/architecture/dsh-support-window.md +50 -0
- package/docs/architecture/final-closure-audit.md +148 -0
- package/docs/architecture/p2-jobs-feasibility.md +94 -0
- package/docs/architecture/p3-compat-retirement.md +46 -0
- package/docs/architecture/p3-host-native-seams.md +43 -0
- package/docs/architecture/p3-native-recovery-evaluation.md +65 -0
- package/docs/architecture/runtime-boundaries.md +124 -0
- package/docs/releases/v2.0.1.md +17 -0
- package/docs/releases/v2.1.0-support-window.md +29 -0
- package/docs/releases/v2.1.0.md +83 -0
- package/entry.js +13 -307
- package/index.js +85 -352
- package/lib/abort-signal-compat.js +191 -0
- package/lib/adapter-update-coalescer.js +113 -29
- package/lib/adversarial-hardening.js +24 -9
- package/lib/artifact-boundary.js +16 -175
- package/lib/artifact-io.js +220 -0
- package/lib/artifact-retention.js +305 -53
- package/lib/catalog-corrections.js +6 -1
- package/lib/client-host-compat-prelude.js +249 -0
- package/lib/client-presentation-boundary.js +61 -3
- package/lib/client.js +71 -72
- package/lib/core-vision-surface.js +124 -0
- package/lib/depth-guidance.js +114 -90
- package/lib/doctor-cli-p0.js +168 -0
- package/lib/doctor-cli.js +7 -0
- package/lib/doctor-vision-limits.js +72 -0
- package/lib/dsh-contract-compat.js +207 -39
- package/lib/dsh-host-capabilities.js +107 -0
- package/lib/dsh-support-window.js +48 -0
- package/lib/http-compat.js +22 -6
- package/lib/image-resource-governor.js +35 -10
- package/lib/legacy-core-vision-policy-bridge.js +25 -226
- package/lib/legacy-global-proxy-boundary.js +127 -0
- package/lib/live-model-client-prelude.js +0 -12
- package/lib/mixed-router.js +81 -81
- package/lib/public-entry.js +42 -4
- package/lib/runtime-composition.js +348 -0
- package/lib/runtime-i18n-boundary.js +683 -0
- package/lib/runtime-i18n-core-scope.js +56 -0
- package/lib/runtime-i18n-core.js +34 -0
- package/lib/runtime-i18n.js +121 -0
- package/lib/session-surface-policy.js +97 -0
- package/lib/session-vision-index.js +282 -0
- package/lib/session-vision-runtime.js +48 -0
- package/lib/settings-factory-lifecycle.js +144 -0
- package/lib/settings-ia-client-prelude.js +74 -2
- package/lib/settings-limit-client-prelude.js +110 -0
- package/lib/settings-native-card-layout.js +297 -0
- package/lib/settings-number-contract.js +29 -0
- package/lib/structured-flow-hardening.js +207 -76
- package/lib/tesseract-exec-compat.js +58 -32
- package/lib/update-check.js +17 -5
- package/lib/v2-settings-ia-integration.js +9 -4
- package/lib/vision-artifact-store.js +77 -0
- package/lib/vision-backend-runtime-policy.js +6 -2
- package/lib/vision-background-benchmark.js +130 -41
- package/lib/vision-background-failure-policy.js +70 -0
- package/lib/vision-background-stop-store.js +54 -9
- package/lib/vision-capability-benchmark-client.js +57 -66
- package/lib/vision-capability-benchmark-presentation.js +97 -0
- package/lib/vision-capability-benchmark-service.js +3 -3
- package/lib/vision-execution-order-apply.js +61 -0
- package/lib/vision-execution-order-plan.js +64 -0
- package/lib/vision-execution-order.js +38 -0
- package/lib/vision-limit-diagnostics.js +202 -0
- package/lib/vision-product-presentation.js +284 -0
- package/lib/vision-provider-transport.js +197 -0
- package/lib/vision-resilience.js +87 -45
- package/lib/vision-routing-evidence.js +366 -0
- package/lib/vision-routing-runtime.js +278 -0
- package/lib/vision-tool-runtime-boundary.js +27 -21
- package/lib/vision-turn-budget-client-prelude.js +9 -9
- package/lib/web/benchmark-panel.js +6 -0
- package/lib/web/diagnostics-panel.js +44 -0
- package/lib/web/index.js +60 -0
- package/lib/web/model-picker.js +22 -0
- package/lib/web/onboarding.js +6 -0
- package/lib/web/product-state.js +59 -0
- package/lib/web/remote-settings-client.js +8 -0
- package/lib/web/routing-section.js +6 -0
- package/lib/web/settings-controller.js +6 -0
- package/lib/web-capability-boundary.js +65 -9
- package/lib/windows-screenshot-dpi-compat.js +148 -0
- package/package.json +14 -6
- package/lib/vision-capability-shadow.js +0 -576
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
currentSessionSurfacePolicy,
|
|
3
|
+
resolveSessionSurfacePolicy,
|
|
4
|
+
} from './session-surface-policy.js'
|
|
5
|
+
|
|
6
|
+
const CORE_SURFACE_KEYS = Object.freeze([
|
|
7
|
+
'tool',
|
|
8
|
+
'rewriteImages',
|
|
9
|
+
'instantDescribe',
|
|
10
|
+
'autoActivateOnImage',
|
|
11
|
+
'structuredVisionBootstrap',
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
function isObject(value) {
|
|
15
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function own(object, key) {
|
|
19
|
+
return Object.prototype.hasOwnProperty.call(object, key)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function coreFlags(values) {
|
|
23
|
+
return {
|
|
24
|
+
toolAvailable: values.tool !== false,
|
|
25
|
+
rewriteEnabled: values.rewriteImages !== false,
|
|
26
|
+
instantDescribe: values.instantDescribe === true,
|
|
27
|
+
autoActivateOnImage: values.autoActivateOnImage !== false,
|
|
28
|
+
structuredBootstrap: values.structuredVisionBootstrap === true,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Explicit core-facing projection of the SessionSurfacePolicy.
|
|
34
|
+
*
|
|
35
|
+
* The returned object carries only the five config values that the historical
|
|
36
|
+
* Settings/config projection could override plus explicit booleans consumed by
|
|
37
|
+
* Core and the already-resolved image ownership facts. It deliberately cannot
|
|
38
|
+
* masquerade as a Settings service or a full plugin config object.
|
|
39
|
+
*/
|
|
40
|
+
export function projectCoreVisionSurface(config = {}, sessionPolicy) {
|
|
41
|
+
const value = isObject(config) ? config : {}
|
|
42
|
+
const policy = sessionPolicy && typeof sessionPolicy === 'object'
|
|
43
|
+
? sessionPolicy
|
|
44
|
+
: resolveSessionSurfacePolicy({ config: value })
|
|
45
|
+
const overrides = isObject(policy.legacyConfigOverrides)
|
|
46
|
+
? policy.legacyConfigOverrides
|
|
47
|
+
: {}
|
|
48
|
+
const projected = {}
|
|
49
|
+
|
|
50
|
+
for (const key of CORE_SURFACE_KEYS) {
|
|
51
|
+
projected[key] = own(overrides, key) ? overrides[key] : value[key]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
ownership: policy.ownership,
|
|
56
|
+
preserveRawImages: policy.preserveRawImages === true,
|
|
57
|
+
rewriteCurrentImages: policy.rewriteCurrentImages === true,
|
|
58
|
+
...coreFlags(projected),
|
|
59
|
+
values: Object.freeze(projected),
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Resolve an explicit core surface from an explicit ownership-policy input. */
|
|
64
|
+
export function resolveCoreVisionSurface({
|
|
65
|
+
visionPolicy,
|
|
66
|
+
config = {},
|
|
67
|
+
schemaBootstrapping = false,
|
|
68
|
+
} = {}) {
|
|
69
|
+
const value = isObject(config) ? config : {}
|
|
70
|
+
return projectCoreVisionSurface(
|
|
71
|
+
value,
|
|
72
|
+
resolveSessionSurfacePolicy({
|
|
73
|
+
visionPolicy,
|
|
74
|
+
config: value,
|
|
75
|
+
schemaBootstrapping,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Resolve the current turn-local surface without exposing a Settings facade. */
|
|
81
|
+
export function currentCoreVisionSurface(config = {}, options = {}) {
|
|
82
|
+
const value = isObject(config) ? config : {}
|
|
83
|
+
return projectCoreVisionSurface(
|
|
84
|
+
value,
|
|
85
|
+
currentSessionSurfacePolicy(value, {
|
|
86
|
+
schemaBootstrapping: options?.schemaBootstrapping === true,
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* One explicit production seam for Core's five policy-derived switches.
|
|
93
|
+
*
|
|
94
|
+
* The config source stays live, but schema bootstrap is an internal lifecycle
|
|
95
|
+
* bit rather than a fake Settings/config value. Direct Core callers can keep
|
|
96
|
+
* their two-argument compatibility path; composition passes this runtime as
|
|
97
|
+
* the authoritative production surface.
|
|
98
|
+
*/
|
|
99
|
+
export function createCoreVisionSurfaceRuntime({ config = {} } = {}) {
|
|
100
|
+
let schemaBootstrapping = true
|
|
101
|
+
|
|
102
|
+
const current = () => {
|
|
103
|
+
let source = config
|
|
104
|
+
if (typeof config === 'function') {
|
|
105
|
+
try {
|
|
106
|
+
source = config()
|
|
107
|
+
} catch {
|
|
108
|
+
source = {}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return currentCoreVisionSurface(isObject(source) ? source : {}, {
|
|
112
|
+
schemaBootstrapping,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
current,
|
|
118
|
+
finishSchemaBootstrap() {
|
|
119
|
+
schemaBootstrapping = false
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export const CORE_VISION_SURFACE_KEYS = CORE_SURFACE_KEYS
|
package/lib/depth-guidance.js
CHANGED
|
@@ -1,116 +1,140 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
import { runtimeLanguageFor } from './runtime-i18n.js'
|
|
2
3
|
|
|
3
4
|
// 看图深度与场景引导(depth-guidance)。
|
|
4
5
|
//
|
|
5
|
-
//
|
|
6
|
-
// -
|
|
7
|
-
// -
|
|
8
|
-
//
|
|
6
|
+
// 深度档位只决定模型如何查证,不再隐式决定调用次数:
|
|
7
|
+
// - fast:整体优先,减少不必要的重复识别;
|
|
8
|
+
// - standard:围绕用户问题按需查证;
|
|
9
|
+
// - deep:主动做局部检查与交叉验证。
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// 语言模型的追问 question 由模型自己生成(现状已自由),本模块不生成。
|
|
11
|
+
// 调用次数是独立的可选安全阀:visionDepthMaxCalls=0/空值表示不限,
|
|
12
|
+
// 1-100 表示本轮成功深挖证据调用的硬上限。bootstrap 预识别不计入。
|
|
13
|
+
// 旧配置 visionDepth=custom 继续兼容,按 standard 引导解释;正数
|
|
14
|
+
// visionDepthMaxCalls 仍会作为独立次数上限生效。
|
|
15
15
|
|
|
16
16
|
const MAX_OVERRIDE_CHARS = 2000
|
|
17
17
|
const DEPTH_RUNTIME_CONTEXT = new AsyncLocalStorage()
|
|
18
18
|
|
|
19
|
-
// index.js predates the custom tier and still normalizes an unknown depth to
|
|
20
|
-
// standard before it calls the helpers below. Keep the public core untouched:
|
|
21
|
-
// the entry-layer structured guard exposes the live settings through this
|
|
22
|
-
// async-local scope, so both the old inner quota and the outer hardening guard
|
|
23
|
-
// see one authoritative fast=1 / standard=2 / deep=4 / custom=N policy.
|
|
24
19
|
export function runWithDepthConfig(config, execute) {
|
|
25
20
|
if (typeof execute !== 'function') return undefined
|
|
26
21
|
const value = config && typeof config === 'object' ? config : {}
|
|
27
22
|
return DEPTH_RUNTIME_CONTEXT.run(value, execute)
|
|
28
23
|
}
|
|
29
24
|
|
|
30
|
-
function
|
|
25
|
+
export function currentDepthRuntimeLocale() {
|
|
26
|
+
const value = DEPTH_RUNTIME_CONTEXT.getStore()?.__visionRouterLocale
|
|
27
|
+
return typeof value === 'string' && value.trim() !== '' ? value : undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function strategyDepth(depth) {
|
|
31
31
|
const runtime = DEPTH_RUNTIME_CONTEXT.getStore()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
return { depth, customMax }
|
|
32
|
+
const candidate = runtime?.visionDepth ?? depth
|
|
33
|
+
return candidate === 'fast' || candidate === 'deep' ? candidate : 'standard'
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
function
|
|
36
|
+
function normalizeMaxCalls(value) {
|
|
39
37
|
const number = Number(value)
|
|
40
|
-
if (!Number.isFinite(number) || number <= 0) return
|
|
38
|
+
if (!Number.isFinite(number) || number <= 0) return undefined
|
|
41
39
|
return Math.min(100, Math.max(1, Math.floor(number)))
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
function effectiveMaxCalls(explicit) {
|
|
43
|
+
const runtime = DEPTH_RUNTIME_CONTEXT.getStore()
|
|
44
|
+
if (runtime && Object.prototype.hasOwnProperty.call(runtime, 'visionDepthMaxCalls')) {
|
|
45
|
+
return normalizeMaxCalls(runtime.visionDepthMaxCalls)
|
|
46
|
+
}
|
|
47
|
+
return normalizeMaxCalls(explicit)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const SCENE_GUIDANCE = Object.freeze({
|
|
51
|
+
zh: Object.assign(Object.create(null), {
|
|
52
|
+
code: '检测到代码内容。代码必须逐字转写,建议分区域转写 + 语义确认,避免概括。',
|
|
53
|
+
document: '检测到文档内容。语义优先;仅当需要逐字引用(长文档/合同/表单)时才用 OCR。',
|
|
54
|
+
ui: '检测到界面内容。建议元素清单(detect)+ 关键元素定位(ground)。',
|
|
55
|
+
chat: '检测到聊天截图。关注气泡顺序与关键信息提取。',
|
|
56
|
+
}),
|
|
57
|
+
en: Object.assign(Object.create(null), {
|
|
58
|
+
code: 'Code content detected. Transcribe code verbatim; use region-by-region transcription plus semantic verification instead of summarizing it.',
|
|
59
|
+
document: 'Document content detected. Prefer semantic understanding; use OCR only when verbatim quotation is required, such as for long documents, contracts, or forms.',
|
|
60
|
+
ui: 'UI content detected. Prefer an element inventory (detect) plus grounding of the important elements (ground).',
|
|
61
|
+
chat: 'Chat screenshot detected. Preserve message-bubble order and extract the important information.',
|
|
62
|
+
}),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const CONTENT_GUIDANCE = Object.freeze({
|
|
66
|
+
zh: Object.assign(Object.create(null), {
|
|
67
|
+
person: '图中主体为人物:关注身份/表情/数量/姿态/关系/穿着。',
|
|
68
|
+
animal: '图中主体为动物:关注物种/数量/状态/环境。',
|
|
69
|
+
plant: '图中主体为植物:关注种类/生长状态/环境。',
|
|
70
|
+
food: '图中主体为食物:关注菜品/食材/卖相/就餐场景。',
|
|
71
|
+
vehicle: '图中主体为交通工具:关注品牌/型号/颜色/新旧/牌照。',
|
|
72
|
+
machine: '图中主体为机器/设备:关注类型/用途/状态/铭牌。',
|
|
73
|
+
architecture: '图中主体为建筑:关注类型/风格/细节/年代。',
|
|
74
|
+
object: '图中主体为物品:关注名称/材质/用途/品牌/型号。',
|
|
75
|
+
scene: '图中主体为场景:关注场景类型/主体/前景背景/光线/天气。',
|
|
76
|
+
meme: '图中为表情包/梗图:关注模板、文字与表达含义。',
|
|
77
|
+
}),
|
|
78
|
+
en: Object.assign(Object.create(null), {
|
|
79
|
+
person: 'The main subject is a person or people: focus on identity, expression, count, posture, relationships, and clothing.',
|
|
80
|
+
animal: 'The main subject is an animal or animals: focus on species, count, condition, and environment.',
|
|
81
|
+
plant: 'The main subject is a plant or plants: focus on type, growth condition, and environment.',
|
|
82
|
+
food: 'The main subject is food: focus on the dish, ingredients, appearance, and dining context.',
|
|
83
|
+
vehicle: 'The main subject is a vehicle: focus on make, model, color, condition, and license plate when visible.',
|
|
84
|
+
machine: 'The main subject is a machine or device: focus on type, purpose, condition, and nameplate information.',
|
|
85
|
+
architecture: 'The main subject is architecture: focus on type, style, details, and period when inferable from visible evidence.',
|
|
86
|
+
object: 'The main subject is an object: focus on its name, material, purpose, brand, and model when visible.',
|
|
87
|
+
scene: 'The main subject is a scene: focus on scene type, subjects, foreground/background, lighting, and weather.',
|
|
88
|
+
meme: 'The image is a meme/reaction image: focus on the template, text, and intended meaning.',
|
|
89
|
+
}),
|
|
53
90
|
})
|
|
54
91
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
person: '图中主体为人物:关注身份/表情/数量/姿态/关系/穿着。',
|
|
59
|
-
animal: '图中主体为动物:关注物种/数量/状态/环境。',
|
|
60
|
-
plant: '图中主体为植物:关注种类/生长状态/环境。',
|
|
61
|
-
food: '图中主体为食物:关注菜品/食材/卖相/就餐场景。',
|
|
62
|
-
vehicle: '图中主体为交通工具:关注品牌/型号/颜色/新旧/牌照。',
|
|
63
|
-
machine: '图中主体为机器/设备:关注类型/用途/状态/铭牌。',
|
|
64
|
-
architecture: '图中主体为建筑:关注类型/风格/细节/年代。',
|
|
65
|
-
object: '图中主体为物品:关注名称/材质/用途/品牌/型号。',
|
|
66
|
-
scene: '图中主体为场景:关注场景类型/主体/前景背景/光线/天气。',
|
|
67
|
-
meme: '图中为表情包/梗图:关注模板、文字与表达含义。',
|
|
92
|
+
const GENERAL_FALLBACK_GUIDANCE = Object.freeze({
|
|
93
|
+
zh: '检测到未能明确归类的图。请先判断图中主体类别(人物/动物/植物/食物/交通工具/机器/建筑/物品/场景/表情包),再按该主体方向深挖(深挖时由你自行生成针对性问题)。',
|
|
94
|
+
en: 'The image could not be classified confidently. First identify the main subject category (person, animal, plant, food, vehicle, machine, architecture, object, scene, or meme), then deepen the inspection in that direction and generate targeted questions as needed.',
|
|
68
95
|
})
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
const DEPTH_COPY = Object.freeze({
|
|
98
|
+
zh: Object.freeze({
|
|
99
|
+
fast: '本轮看图策略为快速:先做整体判断,优先使用已有视觉基线;只有关键证据不确定,或用户明确要求精确定位/逐字信息时,再调用必要的视觉工具。避免重复泛化识别。',
|
|
100
|
+
standard: '本轮看图策略为标准:围绕用户问题按需查证关键区域;需要时使用定位、裁剪、OCR、比对等工具补充或验证证据。证据充分后直接作答,不必为了流程继续调用。',
|
|
101
|
+
deep: '本轮看图策略为细致:主动检查关键局部并做交叉验证;对重要文字、位置、细节或差异,按需组合定位、裁剪、OCR、比对等工具,重要结论尽量由独立证据相互印证后再作答。',
|
|
102
|
+
}),
|
|
103
|
+
en: Object.freeze({
|
|
104
|
+
fast: 'Vision strategy is Quick for this turn: start from the whole-image judgment and reuse the existing visual baseline. Call an additional vision tool only when key evidence is uncertain or the user explicitly needs precise localization or verbatim text. Avoid repeating generic recognition.',
|
|
105
|
+
standard: "Vision strategy is Standard for this turn: verify the key regions required by the user's question as needed. Use grounding, cropping, OCR, comparison, or other tools when they can add or verify evidence. Answer once the evidence is sufficient; do not keep calling tools just to satisfy a workflow.",
|
|
106
|
+
deep: 'Vision strategy is Thorough for this turn: proactively inspect important regions and cross-check key claims. Combine grounding, cropping, OCR, comparison, and other precision tools as needed for important text, positions, details, or differences, and prefer independent evidence to corroborate important conclusions before answering.',
|
|
107
|
+
}),
|
|
108
|
+
})
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
fast: '本轮深度档位为 fast:仅做初步判断 + 1 次深挖即可;若你的问题需要深度定向识别,请告知用户升级档位。',
|
|
78
|
-
standard: '本轮深度档位为 standard:深挖 1-2 次即可;第 2 次之后会停止新的证据调用。',
|
|
79
|
-
deep: '本轮深度档位为 deep:可做 2-4 次充分深挖(定位→裁剪→比对→OCR 等)后再作答。',
|
|
110
|
+
function languageFor(locale) {
|
|
111
|
+
return runtimeLanguageFor(locale ?? currentDepthRuntimeLocale(), 'zh')
|
|
80
112
|
}
|
|
81
113
|
|
|
82
114
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* 表示不设置次数上限。保存过的 custom 数值在切回内置档位后保留但不生效。
|
|
115
|
+
* Optional independent call cap. The depth strategy never supplies a cap.
|
|
116
|
+
* The depth parameter is retained for API/backward compatibility.
|
|
86
117
|
*/
|
|
87
|
-
export function depthLimitFor(
|
|
88
|
-
|
|
89
|
-
if (active.depth === 'custom') {
|
|
90
|
-
const custom = normalizeCustomMax(active.customMax)
|
|
91
|
-
return custom > 0 ? custom : undefined
|
|
92
|
-
}
|
|
93
|
-
if (active.depth === 'fast') return 1
|
|
94
|
-
if (active.depth === 'deep') return 4
|
|
95
|
-
return 2
|
|
118
|
+
export function depthLimitFor(_depth, maxCalls) {
|
|
119
|
+
return effectiveMaxCalls(maxCalls)
|
|
96
120
|
}
|
|
97
121
|
|
|
98
|
-
/**
|
|
99
|
-
export function depthCopyFor(depth,
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
/** Strategy guidance plus an optional, separately configured call-cap note. */
|
|
123
|
+
export function depthCopyFor(depth, maxCalls, locale) {
|
|
124
|
+
const language = languageFor(locale)
|
|
125
|
+
const strategy = strategyDepth(depth)
|
|
126
|
+
const strategyCopy = DEPTH_COPY[language][strategy] || DEPTH_COPY[language].standard
|
|
127
|
+
const cap = effectiveMaxCalls(maxCalls)
|
|
128
|
+
if (cap === undefined) return strategyCopy
|
|
129
|
+
const capCopy = language === 'en'
|
|
130
|
+
? `A separate deep-dive call cap is enabled for this turn: at most ${cap} successful evidence calls. The bootstrap pre-scan does not count, and failed or empty-evidence calls do not consume the cap.`
|
|
131
|
+
: `本轮另启用了深挖次数上限:最多 ${cap} 次成功证据调用;bootstrap 预识别不计入,失败或空证据调用不占次数。`
|
|
132
|
+
return `${strategyCopy}\n${capCopy}`
|
|
108
133
|
}
|
|
109
134
|
|
|
110
135
|
/** 查表:覆盖表(guidanceOverrides)优先,其次内置表,最后 fallback。 */
|
|
111
136
|
function resolveGuidance(kind, overrides, table, fallback = '') {
|
|
112
137
|
if (Array.isArray(overrides)) {
|
|
113
|
-
// 后写覆盖前写:重复 kind 的导入/编辑结果确定为 last-wins。
|
|
114
138
|
for (let i = overrides.length - 1; i >= 0; i--) {
|
|
115
139
|
const entry = overrides[i]
|
|
116
140
|
if (entry && entry.kind === kind && typeof entry.text === 'string' && entry.text.trim() !== '') {
|
|
@@ -122,34 +146,34 @@ function resolveGuidance(kind, overrides, table, fallback = '') {
|
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
/** 场景引导:按 visual_kind 查表(覆盖优先);无则空串(不硬套)。 */
|
|
125
|
-
export function sceneGuidanceFor(visualKind, overrides) {
|
|
126
|
-
return resolveGuidance(visualKind, overrides, SCENE_GUIDANCE)
|
|
149
|
+
export function sceneGuidanceFor(visualKind, overrides, locale) {
|
|
150
|
+
return resolveGuidance(visualKind, overrides, SCENE_GUIDANCE[languageFor(locale)])
|
|
127
151
|
}
|
|
128
152
|
|
|
129
|
-
/**
|
|
130
|
-
export function contentGuidanceFor(contentKind, overrides) {
|
|
131
|
-
return resolveGuidance(contentKind, overrides, CONTENT_GUIDANCE)
|
|
153
|
+
/** 内容引导:按 content_kind 大类查表(覆盖优先);无则空串。 */
|
|
154
|
+
export function contentGuidanceFor(contentKind, overrides, locale) {
|
|
155
|
+
return resolveGuidance(contentKind, overrides, CONTENT_GUIDANCE[languageFor(locale)])
|
|
132
156
|
}
|
|
133
157
|
|
|
134
158
|
/**
|
|
135
|
-
* 拼接一条完整的深挖引导(场景/内容 +
|
|
159
|
+
* 拼接一条完整的深挖引导(场景/内容 + 策略 + 可选独立调用上限)。
|
|
136
160
|
* general 时用 content_kind 内容引导(精确方向);content_kind 未知 → 通用兜底句。
|
|
137
161
|
* guidanceOverrides:可配置的引导覆盖表(默认 undefined = 内置表)。
|
|
138
|
-
*
|
|
139
|
-
* @returns {string} 空串表示无引导(放行)
|
|
162
|
+
* locale:显式传入优先;否则跟随当前 Host runtime locale;缺失时保留历史 zh 默认。
|
|
140
163
|
*/
|
|
141
|
-
export function renderDepthGuidance({ visualKind, contentKind, depth, guidanceOverrides, customMax } = {}) {
|
|
142
|
-
const
|
|
164
|
+
export function renderDepthGuidance({ visualKind, contentKind, depth, guidanceOverrides, customMax, maxCalls, locale } = {}) {
|
|
165
|
+
const language = languageFor(locale)
|
|
166
|
+
const callCap = maxCalls === undefined ? customMax : maxCalls
|
|
167
|
+
const depthCopy = depthCopyFor(depth, callCap, language)
|
|
143
168
|
if (!depthCopy) return ''
|
|
144
|
-
const scene = sceneGuidanceFor(visualKind, guidanceOverrides)
|
|
145
|
-
const content = contentGuidanceFor(contentKind, guidanceOverrides)
|
|
169
|
+
const scene = sceneGuidanceFor(visualKind, guidanceOverrides, language)
|
|
170
|
+
const content = contentGuidanceFor(contentKind, guidanceOverrides, language)
|
|
146
171
|
const parts = []
|
|
147
172
|
if (scene) {
|
|
148
173
|
parts.push(scene)
|
|
149
174
|
} else if (visualKind === 'general') {
|
|
150
|
-
|
|
151
|
-
parts.push(content || GENERAL_FALLBACK_GUIDANCE)
|
|
175
|
+
parts.push(content || GENERAL_FALLBACK_GUIDANCE[language])
|
|
152
176
|
}
|
|
153
177
|
parts.push(depthCopy)
|
|
154
|
-
return parts.join('')
|
|
178
|
+
return parts.join('\n')
|
|
155
179
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { realpathSync } from 'node:fs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { run as runBaseDoctor } from './doctor-cli.js'
|
|
6
|
+
import {
|
|
7
|
+
formatDshHostCapability,
|
|
8
|
+
normalizeDshHostCapabilities,
|
|
9
|
+
} from './dsh-host-capabilities.js'
|
|
10
|
+
import {
|
|
11
|
+
DSH_SUPPORT_WINDOW,
|
|
12
|
+
formatDshSupportWindowLines,
|
|
13
|
+
supportWindowUpgradeAdvice,
|
|
14
|
+
} from './dsh-support-window.js'
|
|
15
|
+
|
|
16
|
+
const HOST_CAPABILITIES_PATH = '/_dsh/vision-router/host-capabilities'
|
|
17
|
+
|
|
18
|
+
function commandOf(argv) {
|
|
19
|
+
const first = argv.find((value) => typeof value === 'string' && !value.startsWith('-'))
|
|
20
|
+
return first && ['doctor', 'repair', 'repair-sessions'].includes(first) ? first : 'doctor'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runtimeOptions(argv, env) {
|
|
24
|
+
let enabled = true
|
|
25
|
+
let baseUrl = env.DSH_WEB_URL || 'http://127.0.0.1:3080'
|
|
26
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
27
|
+
const value = argv[index]
|
|
28
|
+
if (value === '--no-runtime') enabled = false
|
|
29
|
+
if (value === '--runtime-url' && argv[index + 1]) {
|
|
30
|
+
baseUrl = argv[index + 1]
|
|
31
|
+
index += 1
|
|
32
|
+
} else if (typeof value === 'string' && value.startsWith('--runtime-url=')) {
|
|
33
|
+
baseUrl = value.slice('--runtime-url='.length)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { enabled, baseUrl }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function unknownSnapshot() {
|
|
40
|
+
return normalizeDshHostCapabilities({})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function probeDoctorHostCapabilities({ baseUrl, fetchImpl = globalThis.fetch } = {}) {
|
|
44
|
+
try {
|
|
45
|
+
const normalized = new URL(baseUrl || 'http://127.0.0.1:3080')
|
|
46
|
+
normalized.pathname = HOST_CAPABILITIES_PATH
|
|
47
|
+
normalized.search = ''
|
|
48
|
+
normalized.hash = ''
|
|
49
|
+
if (typeof fetchImpl !== 'function') {
|
|
50
|
+
return { ok: false, source: 'runtime-unavailable', capabilities: unknownSnapshot() }
|
|
51
|
+
}
|
|
52
|
+
const response = await fetchImpl(normalized, {
|
|
53
|
+
method: 'GET',
|
|
54
|
+
headers: { accept: 'application/json' },
|
|
55
|
+
signal: AbortSignal.timeout(1500),
|
|
56
|
+
})
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
source: 'runtime-route-unavailable',
|
|
61
|
+
status: response.status,
|
|
62
|
+
capabilities: unknownSnapshot(),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const body = await response.json()
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
source: 'live-runtime',
|
|
69
|
+
capabilities: normalizeDshHostCapabilities(body?.capabilities),
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
source: 'runtime-unavailable',
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
capabilities: unknownSnapshot(),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function capabilityLines(probe) {
|
|
82
|
+
const c = probe.capabilities
|
|
83
|
+
return [
|
|
84
|
+
'DSH Host capabilities:',
|
|
85
|
+
` batch attachments: ${formatDshHostCapability(c.batchAttachments)}`,
|
|
86
|
+
` max image dimension: ${formatDshHostCapability(c.maxImageDimension)}`,
|
|
87
|
+
` adapter registration: ${formatDshHostCapability(c.adapterRegistration)}`,
|
|
88
|
+
` registration replace: ${formatDshHostCapability(c.registrationReplace)}`,
|
|
89
|
+
` jobs: ${formatDshHostCapability(c.jobs)}`,
|
|
90
|
+
` surface replacement: ${formatDshHostCapability(c.surfaceReplacement)}`,
|
|
91
|
+
` settings live namespace: ${formatDshHostCapability(c.settingsLiveNamespace)}`,
|
|
92
|
+
` settings web exposure: ${formatDshHostCapability(c.settingsWebExposure)}`,
|
|
93
|
+
` prepareCall: ${formatDshHostCapability(c.prepareCall)}`,
|
|
94
|
+
` tool registration: ${formatDshHostCapability(c.toolRegistration)}`,
|
|
95
|
+
` tool execution: ${formatDshHostCapability(c.toolExecution)}`,
|
|
96
|
+
` source: ${probe.source}`,
|
|
97
|
+
...formatDshSupportWindowLines(c),
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isJsonRequest(argv) {
|
|
102
|
+
return argv.includes('--json')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isHelpRequest(argv) {
|
|
106
|
+
return argv.includes('--help') || argv.includes('-h')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* P0 Doctor wrapper. Existing health semantics and exit codes stay owned by the
|
|
111
|
+
* established Doctor implementation; Host-capability diagnostics and P3's
|
|
112
|
+
* support-window policy are appended as advisory data and therefore can never
|
|
113
|
+
* turn a healthy runtime unhealthy.
|
|
114
|
+
*/
|
|
115
|
+
export async function run(argv = process.argv.slice(2), io = console, env = process.env) {
|
|
116
|
+
if (commandOf(argv) !== 'doctor' || isHelpRequest(argv)) {
|
|
117
|
+
return runBaseDoctor(argv, io, env)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const runtime = runtimeOptions(argv, env)
|
|
121
|
+
const probe = runtime.enabled
|
|
122
|
+
? await probeDoctorHostCapabilities({ baseUrl: runtime.baseUrl })
|
|
123
|
+
: { ok: false, source: 'runtime-probe-disabled', capabilities: unknownSnapshot() }
|
|
124
|
+
|
|
125
|
+
if (!isJsonRequest(argv)) {
|
|
126
|
+
const code = await runBaseDoctor(argv, io, env)
|
|
127
|
+
for (const line of capabilityLines(probe)) io.log(line)
|
|
128
|
+
return code
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const logs = []
|
|
132
|
+
const errors = []
|
|
133
|
+
const capture = {
|
|
134
|
+
log(...values) { logs.push(values.map(String).join(' ')) },
|
|
135
|
+
error(...values) { errors.push(values.map(String).join(' ')) },
|
|
136
|
+
}
|
|
137
|
+
const code = await runBaseDoctor(argv, capture, env)
|
|
138
|
+
for (const line of errors) io.error(line)
|
|
139
|
+
|
|
140
|
+
if (logs.length === 1) {
|
|
141
|
+
try {
|
|
142
|
+
const report = JSON.parse(logs[0])
|
|
143
|
+
report.hostCapabilities = probe.capabilities
|
|
144
|
+
report.hostCapabilitiesSource = probe.source
|
|
145
|
+
report.hostSupportWindow = DSH_SUPPORT_WINDOW
|
|
146
|
+
report.hostSupportAdvice = supportWindowUpgradeAdvice(probe.capabilities)
|
|
147
|
+
io.log(JSON.stringify(report, null, 2))
|
|
148
|
+
return code
|
|
149
|
+
} catch {
|
|
150
|
+
// Fall through to the untouched output below.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const line of logs) io.log(line)
|
|
154
|
+
return code
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function isCliEntry(entry, moduleUrl = import.meta.url) {
|
|
158
|
+
if (typeof entry !== 'string' || entry.trim() === '') return false
|
|
159
|
+
const real = (value) => {
|
|
160
|
+
try { return realpathSync(value.startsWith('file:') ? fileURLToPath(value) : value) } catch { return value }
|
|
161
|
+
}
|
|
162
|
+
return real(entry) === real(moduleUrl)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isCliEntry(process.argv[1])) {
|
|
166
|
+
const code = await run()
|
|
167
|
+
if (Number.isInteger(code) && code !== 0) process.exitCode = code
|
|
168
|
+
}
|
package/lib/doctor-cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { realpathSync } from 'node:fs'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { doctorProfiles, resolveDshHome } from './doctor.js'
|
|
6
6
|
import { inspectPlatform, probeRuntime, supportReport } from './doctor-runtime.js'
|
|
7
|
+
import { inspectDoctorVisionLimits, formatDoctorVisionLimits } from './doctor-vision-limits.js'
|
|
7
8
|
import { repairLegacySessionLogs } from './legacy-session-repair.js'
|
|
8
9
|
|
|
9
10
|
function usage() {
|
|
@@ -206,6 +207,7 @@ export async function run(argv = process.argv.slice(2), io = console, env = proc
|
|
|
206
207
|
.filter((item) => item.installation?.applicable && item.profileDir)
|
|
207
208
|
.map((item) => item.profileDir)
|
|
208
209
|
const platform = inspectPlatform({ profileDirs: applicableDirs })
|
|
210
|
+
const visionLimits = inspectDoctorVisionLimits(profileReport.log?.file)
|
|
209
211
|
let sessions
|
|
210
212
|
if (options.sessions) sessions = repairLegacySessionLogs({ dshHome, fix: false })
|
|
211
213
|
|
|
@@ -215,6 +217,7 @@ export async function run(argv = process.argv.slice(2), io = console, env = proc
|
|
|
215
217
|
|
|
216
218
|
if (options.json) {
|
|
217
219
|
const report = supportReport({ profileReport, runtime, platform, sessions })
|
|
220
|
+
report.visionLimits = visionLimits
|
|
218
221
|
report.ok = overallOk
|
|
219
222
|
io.log(JSON.stringify(report, null, 2))
|
|
220
223
|
return overallOk ? 0 : 1
|
|
@@ -231,6 +234,10 @@ export async function run(argv = process.argv.slice(2), io = console, env = proc
|
|
|
231
234
|
if (profileReport.log?.historicalSettingsSaveFailures?.length > 0) {
|
|
232
235
|
io.log(`! Historical settings save failures exist (${profileReport.log.historicalSettingsSaveFailures.length}); none are attributed to the latest plugin start.`)
|
|
233
236
|
}
|
|
237
|
+
for (const line of formatDoctorVisionLimits(visionLimits)) {
|
|
238
|
+
if (line.startsWith('WARN:')) io.log(`! ${line.slice('WARN:'.length).trim()}`)
|
|
239
|
+
else io.log(`✓ ${line}`)
|
|
240
|
+
}
|
|
234
241
|
|
|
235
242
|
if (runtime) {
|
|
236
243
|
if (!runtime.reachable) io.log(`– Runtime probe: DSH not reachable at ${runtime.baseUrl}; offline checks still completed.`)
|