dsh-vision-router 2.2.8 → 3.0.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.
Files changed (132) hide show
  1. package/README.md +16 -7
  2. package/README.zh.md +16 -7
  3. package/docs/architecture/2.3.0-compat-retirement.md +147 -0
  4. package/docs/architecture/2.3.0-execution-plan.md +410 -0
  5. package/docs/architecture/2.3.0-final-audit.md +279 -0
  6. package/docs/architecture/2.3.0-reopened-plan.md +720 -0
  7. package/docs/architecture/3.0-architecture-plan.md +252 -0
  8. package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
  9. package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
  10. package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
  11. package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
  12. package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
  13. package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
  14. package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
  15. package/docs/architecture/3.0-r7-public-api.md +151 -0
  16. package/docs/architecture/3.0-r8-ci-release.md +248 -0
  17. package/docs/architecture/3.0-r9-final-audit.md +370 -0
  18. package/docs/architecture/compat-inventory.md +26 -34
  19. package/docs/architecture/dsh-compatibility-matrix.md +65 -54
  20. package/docs/architecture/dsh-support-window.md +30 -22
  21. package/docs/architecture/final-closure-audit.md +2 -0
  22. package/docs/releases/v2.3.0.md +53 -0
  23. package/docs/releases/v3.0.0.md +85 -0
  24. package/entry.js +9 -57
  25. package/index.js +268 -761
  26. package/lib/agent-request-route-authority.js +1 -20
  27. package/lib/catalog-corrections.js +1 -2
  28. package/lib/client-maintenance-actions.js +145 -0
  29. package/lib/client-presentation-boundary-main.js +75 -17
  30. package/lib/client-presentation-boundary.js +53 -28
  31. package/lib/client.js +246 -144
  32. package/lib/core-apply-lifecycle.js +41 -0
  33. package/lib/core-cache.js +87 -0
  34. package/lib/core-primitives.js +91 -278
  35. package/lib/core-vision-surface-domain.js +63 -0
  36. package/lib/core-vision-surface.js +11 -77
  37. package/lib/delegated-call-config.js +15 -24
  38. package/lib/desktop-screenshot-tool.js +262 -0
  39. package/lib/doctor-cli.js +0 -0
  40. package/lib/doctor-runtime.js +4 -1
  41. package/lib/dsh-contract-compat.js +37 -256
  42. package/lib/dsh-host-capabilities.js +3 -9
  43. package/lib/dsh-settings-017-compat.js +17 -10
  44. package/lib/dsh-support-window.js +3 -3
  45. package/lib/file-logger.js +8 -3
  46. package/lib/grounding-coordinate-runtime.js +1 -2
  47. package/lib/http-body-limit.js +97 -79
  48. package/lib/http-compat.js +1 -2
  49. package/lib/image-offload-compat.js +30 -21
  50. package/lib/image-resource-governor.js +210 -228
  51. package/lib/legacy-global-proxy-boundary.js +46 -3
  52. package/lib/live-model-client-prelude.js +12 -4
  53. package/lib/live-model-discovery.js +134 -43
  54. package/lib/local-remote-settings-permission.js +33 -7
  55. package/lib/local-vision-stabilizer.js +31 -17
  56. package/lib/maintenance-routes.js +134 -0
  57. package/lib/native-image-coexistence.js +15 -3
  58. package/lib/ollama-cold-start.js +87 -23
  59. package/lib/pi-ai-bridge-wire-compat.js +3 -2
  60. package/lib/proxy-dispatcher-pool.js +151 -92
  61. package/lib/proxy-routing.js +75 -51
  62. package/lib/proxy-url-compat.js +3 -10
  63. package/lib/public-config.js +36 -0
  64. package/lib/public-entry.d.ts +148 -0
  65. package/lib/public-entry.js +19 -11
  66. package/lib/remote-settings-bridge.js +11 -0
  67. package/lib/remote-settings-risk-client-boundary.js +155 -0
  68. package/lib/remote-settings-risk-confirmation.js +17 -105
  69. package/lib/replay-delegation.js +12 -7
  70. package/lib/replay-envelope-v2-compat.js +12 -7
  71. package/lib/runtime-composition.js +127 -34
  72. package/lib/runtime-i18n-boundary.js +10 -11
  73. package/lib/screenshot-source-boundary.js +2 -2
  74. package/lib/session-affinity-runtime.js +113 -97
  75. package/lib/session-affinity.js +89 -76
  76. package/lib/session-event-history-compat.js +30 -0
  77. package/lib/session-message-source-compat.js +7 -3
  78. package/lib/session-query-readers.js +263 -0
  79. package/lib/session-surface-compat.js +30 -31
  80. package/lib/session-surface-policy-domain.js +85 -0
  81. package/lib/session-surface-policy.js +2 -111
  82. package/lib/session-turn-resolver.js +2 -6
  83. package/lib/session-vision-attachment-index.js +143 -0
  84. package/lib/session-vision-delegation-boundary.js +34 -0
  85. package/lib/session-vision-delegation.js +22 -0
  86. package/lib/session-vision-index.js +179 -116
  87. package/lib/session-vision-mode-authority.js +229 -192
  88. package/lib/session-vision-mode-boundary.js +65 -22
  89. package/lib/session-vision-policy-domain.js +304 -0
  90. package/lib/session-vision-policy.js +104 -0
  91. package/lib/session-vision-state.js +368 -378
  92. package/lib/settings-client-017-compat.js +9 -7
  93. package/lib/settings-client-loader-lifecycle.js +129 -0
  94. package/lib/sharp-runtime.js +0 -7
  95. package/lib/structured-flow-hardening.js +25 -6
  96. package/lib/tesseract-exec-compat.js +9 -1
  97. package/lib/turn-budget-context.js +47 -41
  98. package/lib/update-check.js +11 -2
  99. package/lib/vision-attachment-handle-runtime.js +176 -156
  100. package/lib/vision-backend-runtime-policy.js +19 -2
  101. package/lib/vision-background-benchmark.js +41 -4
  102. package/lib/vision-background-failure-policy.js +41 -41
  103. package/lib/vision-background-stop-store-core.js +217 -0
  104. package/lib/vision-background-stop-store.js +8 -179
  105. package/lib/vision-capability-benchmark-service.js +1 -0
  106. package/lib/vision-capability-benchmark.js +11 -61
  107. package/lib/vision-capability-evidence.js +35 -0
  108. package/lib/vision-capability-fingerprint.js +72 -0
  109. package/lib/vision-capability-identity.js +116 -61
  110. package/lib/vision-capability-router.js +13 -33
  111. package/lib/vision-chunk-assembler.js +95 -0
  112. package/lib/vision-diagnostics-routes.js +246 -0
  113. package/lib/vision-evidence-guidance.js +3 -1
  114. package/lib/vision-execution-order-apply.js +45 -55
  115. package/lib/vision-execution-order.js +19 -19
  116. package/lib/vision-execution-policy.js +12 -7
  117. package/lib/vision-model-registry.js +30 -17
  118. package/lib/vision-provider-transport.js +137 -176
  119. package/lib/vision-routing-authority.js +47 -48
  120. package/lib/vision-routing-product.js +26 -29
  121. package/lib/vision-routing-settings-prelude.js +8 -3
  122. package/lib/vision-runtime-performance-store.js +166 -0
  123. package/lib/vision-runtime-performance.js +13 -142
  124. package/lib/vision-toggle-root-hardening.js +22 -5
  125. package/lib/vision-tool-runtime-boundary.js +104 -31
  126. package/lib/web/dsh-settings-017-root-transport.js +27 -12
  127. package/lib/web/remote-settings-client.js +6 -9
  128. package/lib/web-capability-boundary.js +17 -7
  129. package/lib/wrapper-directory.js +14 -23
  130. package/package.json +56 -20
  131. package/lib/android-attachment-compat.js +0 -244
  132. package/lib/settings-client-rc8-lifecycle.js +0 -397
@@ -1,82 +1,20 @@
1
+ import { currentSessionSurfacePolicy } from './session-surface-policy.js'
1
2
  import {
2
- currentSessionSurfacePolicy,
3
- resolveSessionSurfacePolicy,
4
- } from './session-surface-policy.js'
3
+ CORE_VISION_SURFACE_KEYS,
4
+ projectCoreVisionSurface,
5
+ resolveCoreVisionSurface,
6
+ } from './core-vision-surface-domain.js'
5
7
 
6
- const CORE_SURFACE_KEYS = Object.freeze([
7
- 'tool',
8
- 'rewriteImages',
9
- 'instantDescribe',
10
- 'autoActivateOnImage',
11
- 'structuredVisionBootstrap',
12
- ])
8
+ export {
9
+ CORE_VISION_SURFACE_KEYS,
10
+ projectCoreVisionSurface,
11
+ resolveCoreVisionSurface,
12
+ } from './core-vision-surface-domain.js'
13
13
 
14
14
  function isObject(value) {
15
15
  return value !== null && typeof value === 'object' && !Array.isArray(value)
16
16
  }
17
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
18
  /** Resolve the current turn-local surface without exposing a Settings facade. */
81
19
  export function currentCoreVisionSurface(config = {}, options = {}) {
82
20
  const value = isObject(config) ? config : {}
@@ -92,9 +30,7 @@ export function currentCoreVisionSurface(config = {}, options = {}) {
92
30
  * One explicit production seam for Core's five policy-derived switches.
93
31
  *
94
32
  * 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.
33
+ * bit rather than a fake Settings/config value.
98
34
  */
99
35
  export function createCoreVisionSurfaceRuntime({ config = {} } = {}) {
100
36
  let schemaBootstrapping = true
@@ -120,5 +56,3 @@ export function createCoreVisionSurfaceRuntime({ config = {} } = {}) {
120
56
  },
121
57
  })
122
58
  }
123
-
124
- export const CORE_VISION_SURFACE_KEYS = CORE_SURFACE_KEYS
@@ -1,27 +1,18 @@
1
1
  const ROUTE_OWNED_CALL_FIELDS = Object.freeze([
2
- 'reasoningEffort',
3
- 'temperature',
4
- 'maxTokens',
5
- 'stop',
6
- ])
7
-
8
- /**
9
- * Project an internal Vision Router delegation onto a different provider/model.
10
- *
11
- * The delegated route owns its own call defaults and compatibility. Carry the
12
- * request payload and lifecycle through, but never leak source-route sampling,
13
- * reasoning or output-limit state into the target adapter. DSH will materialize
14
- * the target adapter's configured defaults when these fields are absent.
15
- *
16
- * Direct transports owned by Vision Router do not use this projection; their
17
- * wire compatibility remains the Router's responsibility.
18
- */
2
+ 'reasoningEffort',
3
+ 'temperature',
4
+ 'maxTokens',
5
+ 'stop',
6
+ ]);
19
7
  export function projectDelegatedCallConfig(options) {
20
- if (!options || typeof options !== 'object' || Array.isArray(options)) return options
21
- if (!ROUTE_OWNED_CALL_FIELDS.some((field) => Object.hasOwn(options, field))) return options
22
- const next = { ...options }
23
- for (const field of ROUTE_OWNED_CALL_FIELDS) delete next[field]
24
- return next
8
+ if (!options || typeof options !== 'object' || Array.isArray(options))
9
+ return options;
10
+ const record = options;
11
+ if (!ROUTE_OWNED_CALL_FIELDS.some((field) => Object.hasOwn(record, field)))
12
+ return options;
13
+ const next = { ...record };
14
+ for (const field of ROUTE_OWNED_CALL_FIELDS)
15
+ delete next[field];
16
+ return next;
25
17
  }
26
-
27
- export const delegatedRouteOwnedCallFields = ROUTE_OWNED_CALL_FIELDS
18
+ export const delegatedRouteOwnedCallFields = ROUTE_OWNED_CALL_FIELDS;
@@ -0,0 +1,262 @@
1
+ import { execFile } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { readFile, unlink } from 'node:fs/promises'
4
+ import { tmpdir } from 'node:os'
5
+ import path from 'node:path'
6
+ import { promisify } from 'node:util'
7
+
8
+ import {
9
+ callLocalBackend,
10
+ localDescribePrompt,
11
+ localProvidersOf,
12
+ toOpenAIContent,
13
+ } from './core-primitives.js'
14
+ import { loadSharp } from './sharp-runtime.js'
15
+ import { captureWindowsDesktop } from './windows-desktop-capture.js'
16
+ import { currentVisionTurnBudgetSignal } from './turn-budget-context.js'
17
+ import { combineSignals } from './vision-resilience.js'
18
+
19
+ function abortReason(signal, fallback = 'vision_screenshot aborted') {
20
+ if (signal?.reason instanceof Error) return signal.reason
21
+ const error = new Error(fallback)
22
+ error.name = 'AbortError'
23
+ error.code = 'ABORT_ERR'
24
+ return error
25
+ }
26
+
27
+ function throwIfAborted(signal) {
28
+ if (signal?.aborted) throw abortReason(signal)
29
+ }
30
+
31
+ function isAbortFailure(error) {
32
+ return error?.name === 'AbortError' || error?.code === 'ABORT_ERR'
33
+ }
34
+
35
+ export async function captureDesktopPng(target, timeoutMs, signal, options = {}) {
36
+ const platform = options.platform ?? process.platform
37
+ const run = options.execFileAsync ?? promisify(execFile)
38
+ const captureWindows = options.captureWindowsDesktop ?? captureWindowsDesktop
39
+ throwIfAborted(signal)
40
+ if (platform === 'win32') {
41
+ // Own DPI-aware capture at the implementation boundary rather than
42
+ // depending on a process-wide execFile argument rewrite.
43
+ await captureWindows(target, { timeoutMs, signal })
44
+ throwIfAborted(signal)
45
+ return platform
46
+ }
47
+ if (platform === 'darwin') {
48
+ // Without -m, screencapture writes one file per display while the tool owns
49
+ // exactly one artifact path, so request the main display explicitly.
50
+ await run('screencapture', ['-x', '-m', target], {
51
+ timeout: timeoutMs,
52
+ windowsHide: true,
53
+ ...(signal === undefined ? {} : { signal }),
54
+ })
55
+ throwIfAborted(signal)
56
+ return platform
57
+ }
58
+ try {
59
+ await run('import', ['-window', 'root', target], {
60
+ timeout: timeoutMs,
61
+ ...(signal === undefined ? {} : { signal }),
62
+ })
63
+ } catch (error) {
64
+ if (signal?.aborted || isAbortFailure(error)) throw error
65
+ await run('scrot', [target], {
66
+ timeout: timeoutMs,
67
+ ...(signal === undefined ? {} : { signal }),
68
+ })
69
+ }
70
+ throwIfAborted(signal)
71
+ return platform
72
+ }
73
+
74
+ async function downscaleForIdentification(data) {
75
+ try {
76
+ const sharp = await loadSharp()
77
+ if (!sharp) return data
78
+ const downscaled = await sharp(data, { failOn: 'none' })
79
+ .resize({ width: 1280, height: 1280, fit: 'inside', withoutEnlargement: true })
80
+ .png()
81
+ .toBuffer()
82
+ return downscaled.length > 0 && downscaled.length < data.length ? downscaled : data
83
+ } catch {
84
+ return data
85
+ }
86
+ }
87
+
88
+ async function identifyDesktopCapture(data, {
89
+ current,
90
+ timeoutMs,
91
+ instantLocalStyle,
92
+ providerTransport,
93
+ signal,
94
+ } = {}) {
95
+ throwIfAborted(signal)
96
+ const locals = localProvidersOf(current())
97
+ if (locals.length === 0) {
98
+ return {
99
+ identifyError: 'no local vision backend enabled (localOllama / localLmStudio); enable one to use identify',
100
+ }
101
+ }
102
+
103
+ const startedAt = Date.now()
104
+ const identifyBytes = await downscaleForIdentification(data)
105
+ throwIfAborted(signal)
106
+ const result = {}
107
+ if (identifyBytes !== data) {
108
+ result.identifyDownscaled = {
109
+ originalBytes: data.length,
110
+ sentBytes: identifyBytes.length,
111
+ }
112
+ }
113
+
114
+ const content = toOpenAIContent(
115
+ [{ type: 'image', attachment: { mediaType: 'image/png', data: identifyBytes } }],
116
+ () => identifyBytes,
117
+ )
118
+ content.push({ type: 'text', text: localDescribePrompt(instantLocalStyle()) })
119
+
120
+ const deadlineAt = Date.now() + timeoutMs()
121
+ const errors = []
122
+ for (let index = 0; index < locals.length; index++) {
123
+ throwIfAborted(signal)
124
+ const local = locals[index]
125
+ const remainingMs = deadlineAt - Date.now()
126
+ if (remainingMs <= 0) break
127
+ // Reserve a fair share for later local backends. A connected but hung
128
+ // first backend must not consume the entire fallback budget.
129
+ const roundBudgetMs = Math.max(1, Math.floor(remainingMs / (locals.length - index)))
130
+ const controller = new AbortController()
131
+ const roundSignal = signal
132
+ ? AbortSignal.any([signal, controller.signal])
133
+ : controller.signal
134
+ const timer = setTimeout(() => controller.abort(), roundBudgetMs)
135
+ try {
136
+ const identified = await callLocalBackend(
137
+ local,
138
+ [{ role: 'user', content }],
139
+ {
140
+ maxTokens: local.maxTokens ?? 2048,
141
+ signal: roundSignal,
142
+ providerTransport,
143
+ },
144
+ )
145
+ if (typeof identified === 'string' && identified.trim() !== '') {
146
+ result.identified = identified.trim()
147
+ result.identifiedBy = local.name
148
+ result.elapsedSec = Math.max(1, Math.round((Date.now() - startedAt) / 1000))
149
+ return result
150
+ }
151
+ errors.push(`${local.name}: empty response`)
152
+ } catch (error) {
153
+ if (signal?.aborted) throw abortReason(signal)
154
+ errors.push(`${local.name}: ${error && error.message ? error.message : String(error)}`)
155
+ } finally {
156
+ clearTimeout(timer)
157
+ }
158
+ }
159
+
160
+ result.identifyError = errors.length > 0
161
+ ? errors.join('; ').slice(0, 1000)
162
+ : 'local vision identification timed out before a backend could respond'
163
+ return result
164
+ }
165
+
166
+ /**
167
+ * Build the privacy-sensitive desktop screenshot tool definition.
168
+ *
169
+ * Tool registration/exposure remains owned by Core + LocalVisionStabilizer;
170
+ * this module owns only the platform capture and optional local-recognition
171
+ * implementation behind that existing permission boundary.
172
+ */
173
+ export function createDesktopScreenshotTool({
174
+ current,
175
+ timeoutMs,
176
+ saveArtifact,
177
+ stringOutput,
178
+ instantLocalStyle,
179
+ providerTransport,
180
+ captureDesktop = captureDesktopPng,
181
+ } = {}) {
182
+ if (
183
+ typeof current !== 'function' ||
184
+ typeof timeoutMs !== 'function' ||
185
+ typeof saveArtifact !== 'function' ||
186
+ typeof captureDesktop !== 'function'
187
+ ) {
188
+ throw new TypeError('desktop screenshot tool requires live config, timeout, artifact and capture callbacks')
189
+ }
190
+ if (typeof instantLocalStyle !== 'function') {
191
+ throw new TypeError('desktop screenshot tool requires a live local describe style')
192
+ }
193
+
194
+ return {
195
+ name: 'vision_screenshot',
196
+ description:
197
+ 'Capture the user\'s desktop screen as a PNG artifact (the virtual screen on Windows; the main display on macOS; the root display on Linux). ' +
198
+ 'Windows: per-monitor-DPI-aware PowerShell capture; macOS: screencapture; Linux: ImageMagick import (falls back to scrot; either command must be installed). ' +
199
+ 'This privacy-sensitive tool is disabled by default and works only after the user explicitly enables Desktop screenshot in Vision Router settings. ' +
200
+ 'Use it when you need to see what is on the user\'s screen right now — e.g. their current GUI, an app, or a page outside this browser. ' +
201
+ 'Optional identify=true also runs local recognition on the capture using the enabled local backends (Ollama, then LM Studio) and returns the description alongside the path.',
202
+ parameters: {
203
+ type: 'object',
204
+ properties: {
205
+ identify: {
206
+ type: 'boolean',
207
+ description:
208
+ 'Also recognize the captured screen with enabled local vision backends (Ollama, then LM Studio) and return the description text with the path. Default false.',
209
+ },
210
+ },
211
+ additionalProperties: false,
212
+ },
213
+ output: stringOutput,
214
+ async execute(args, exec) {
215
+ if (current().desktopScreenshot !== true) {
216
+ throw new Error(
217
+ 'vision_screenshot is disabled; enable Desktop screenshot explicitly in Vision Router settings before use',
218
+ )
219
+ }
220
+
221
+ const tmp = path.join(
222
+ tmpdir(),
223
+ `vision-screenshot-${Date.now()}-${Math.floor(Math.random() * 1e9)}.png`,
224
+ )
225
+ try {
226
+ const signal = combineSignals(currentVisionTurnBudgetSignal(), exec?.signal)
227
+ throwIfAborted(signal)
228
+ const platform = await captureDesktop(tmp, timeoutMs(), signal)
229
+ throwIfAborted(signal)
230
+ if (!existsSync(tmp)) {
231
+ throw new Error(
232
+ `vision_screenshot: no output produced on ${platform} (is a screen available?)`,
233
+ )
234
+ }
235
+ const data = await readFile(tmp, signal === undefined ? undefined : { signal })
236
+ throwIfAborted(signal)
237
+ let identification
238
+ if (args.identify === true) {
239
+ identification = await identifyDesktopCapture(data, {
240
+ current,
241
+ timeoutMs,
242
+ instantLocalStyle,
243
+ providerTransport,
244
+ signal,
245
+ })
246
+ }
247
+ // Artifact publication is the final commit point. If optional local
248
+ // identification is cancelled, no successful screenshot result should
249
+ // already have escaped into the managed artifact store.
250
+ throwIfAborted(signal)
251
+ const target = await saveArtifact(exec, `screenshot-${Date.now()}.png`, data)
252
+ return JSON.stringify({
253
+ path: target,
254
+ bytes: data.length,
255
+ ...(identification ?? {}),
256
+ })
257
+ } finally {
258
+ try { await unlink(tmp) } catch { /* best effort cleanup */ }
259
+ }
260
+ },
261
+ }
262
+ }
package/lib/doctor-cli.js CHANGED
File without changes
@@ -52,6 +52,10 @@ export async function probeRuntime({
52
52
  dshHome,
53
53
  applicableProfiles = [],
54
54
  timeoutMs = 800,
55
+ // Doctor probes are a CLI surface: they never install a process fetch
56
+ // wrapper and must observe whatever transport the surrounding process
57
+ // currently provides.
58
+ // diagnostics-lint: sealed-process-global
55
59
  fetchImpl = globalThis.fetch,
56
60
  } = {}) {
57
61
  if (typeof fetchImpl !== 'function') throw new TypeError('runtime probe requires fetch')
@@ -220,7 +224,6 @@ function chromiumCandidates(env = process.env) {
220
224
  }
221
225
 
222
226
  const HOST_PACKAGES = [
223
- '@deepseek-ai/dsh-client-runtime',
224
227
  '@deepseek-ai/dsh-client-ui-settings-plugins',
225
228
  '@deepseek-ai/dsh-attachment-local',
226
229
  '@deepseek-ai/dsh-llm-deepseek',