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
@@ -0,0 +1,304 @@
1
+ import { z } from 'zod';
2
+ import { SESSION_VISION_POLICY_REVISION, parseSessionVisionPolicy, } from './session-vision-policy.js';
3
+ const policySchema = z.discriminatedUnion('source', [
4
+ z.object({
5
+ revision: z.literal(SESSION_VISION_POLICY_REVISION),
6
+ enabled: z.boolean(),
7
+ source: z.literal('user'),
8
+ }).strict(),
9
+ z.object({
10
+ revision: z.literal(SESSION_VISION_POLICY_REVISION),
11
+ enabled: z.boolean(),
12
+ source: z.literal('delegation'),
13
+ inheritedFrom: z.string().trim().min(1),
14
+ }).strict(),
15
+ ]);
16
+ /**
17
+ * Plugin-owned Session sidecar. The shape intentionally satisfies DSH's
18
+ * public DomainSpec contract without importing storage-domain at runtime:
19
+ * older Hosts may not mount that optional capability at all.
20
+ */
21
+ export const sessionVisionPolicyDomainSpec = {
22
+ name: 'vision_router_session_policy',
23
+ version: 1,
24
+ layout: 'per-record',
25
+ invalidRecords: 'backup-and-skip',
26
+ tables: {
27
+ policies: {
28
+ valueSchema: policySchema,
29
+ },
30
+ },
31
+ };
32
+ function normalizedSessionId(value) {
33
+ if (typeof value !== 'string' || value.trim() === '') {
34
+ throw new TypeError('session vision policy requires a non-empty session id');
35
+ }
36
+ return value.trim();
37
+ }
38
+ function storageDomainOf(ctx) {
39
+ // storageDomain is optional across the supported Host window. Reading the
40
+ // Cordis service property without inject() turns capability detection into a
41
+ // hard dependency; Context#get is the non-owning, live lookup seam here.
42
+ try {
43
+ const candidate = ctx.get?.('storageDomain');
44
+ return candidate && typeof candidate.open === 'function' ? candidate : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /**
51
+ * Create a synchronous-read policy store with lazy official DSH sidecar
52
+ * hydration. No storage capability means ordinary volatile operation.
53
+ *
54
+ * Domain ownership is bound to the consumer fiber before any asynchronous open
55
+ * begins. That ordering is deliberate: an HMR/unload racing a slow open must
56
+ * await/close the eventual handle instead of leaving a same-name domain behind
57
+ * in the longer-lived storageDomain facility. Live facility replacement also
58
+ * retires the previous handle before opening against the replacement.
59
+ *
60
+ * Once a storage failure is observed, this store degrades to volatile for the
61
+ * rest of its lifetime. Vision availability must not depend on auxiliary
62
+ * persistence health; a later plugin generation can retry the durable seam.
63
+ */
64
+ export function createSessionVisionPolicyRuntimeStore(ctx) {
65
+ const cache = new Map();
66
+ const closedDomains = new WeakSet();
67
+ let activeFacility;
68
+ let domainPromise;
69
+ let transition = Promise.resolve();
70
+ const mutationTails = new Map();
71
+ const activeMutations = new Set();
72
+ const hydrationTokens = new Map();
73
+ let durableDisabled = false;
74
+ let lifecycleClosed = false;
75
+ let lifecycleBound = false;
76
+ let warned = false;
77
+ const warnDegraded = (error) => {
78
+ if (warned)
79
+ return;
80
+ warned = true;
81
+ try {
82
+ ctx.logger?.warn?.('vision-router: Session Vision policy persistence unavailable; using process-local fallback: %s', error instanceof Error ? error.message : String(error));
83
+ }
84
+ catch { }
85
+ };
86
+ const markDurabilityFailed = (error) => {
87
+ durableDisabled = true;
88
+ warnDegraded(error);
89
+ };
90
+ const closeDomainOnce = async (domain) => {
91
+ const key = domain;
92
+ if (closedDomains.has(key))
93
+ return;
94
+ closedDomains.add(key);
95
+ try {
96
+ await domain.close();
97
+ }
98
+ catch (error) {
99
+ warnDegraded(error);
100
+ }
101
+ };
102
+ const retireActiveDomain = async () => {
103
+ const pending = domainPromise;
104
+ domainPromise = undefined;
105
+ activeFacility = undefined;
106
+ if (pending === undefined)
107
+ return;
108
+ try {
109
+ const domain = await pending;
110
+ if (domain !== undefined)
111
+ await closeDomainOnce(domain);
112
+ }
113
+ catch (error) {
114
+ warnDegraded(error);
115
+ }
116
+ };
117
+ const enqueueRetire = () => {
118
+ transition = transition.then(retireActiveDomain, retireActiveDomain);
119
+ return transition;
120
+ };
121
+ if (typeof ctx.effect === 'function') {
122
+ try {
123
+ ctx.effect(() => {
124
+ lifecycleBound = true;
125
+ return async () => {
126
+ lifecycleClosed = true;
127
+ // No new durable mutation is admitted after lifecycleClosed flips.
128
+ // Wait every mutation that was already admitted before closing the
129
+ // shared domain underneath it.
130
+ await Promise.allSettled([...activeMutations]);
131
+ await enqueueRetire();
132
+ };
133
+ }, 'vision-router: Session Vision policy domain lifecycle');
134
+ }
135
+ catch (error) {
136
+ markDurabilityFailed(error);
137
+ }
138
+ }
139
+ const ensureDomainFor = async (facility, allowClosed = false) => {
140
+ if ((lifecycleClosed && !allowClosed) || durableDisabled)
141
+ return;
142
+ if (activeFacility === facility && domainPromise !== undefined)
143
+ return;
144
+ if (domainPromise !== undefined || activeFacility !== undefined) {
145
+ await retireActiveDomain();
146
+ }
147
+ if ((lifecycleClosed && !allowClosed) || durableDisabled)
148
+ return;
149
+ activeFacility = facility;
150
+ let opening;
151
+ opening = facility.open(sessionVisionPolicyDomainSpec)
152
+ .then(async (domain) => {
153
+ if ((lifecycleClosed && !allowClosed)
154
+ || durableDisabled
155
+ || activeFacility !== facility
156
+ || domainPromise !== opening) {
157
+ await closeDomainOnce(domain);
158
+ return undefined;
159
+ }
160
+ return domain;
161
+ })
162
+ .catch((error) => {
163
+ if (domainPromise === opening) {
164
+ domainPromise = undefined;
165
+ activeFacility = undefined;
166
+ markDurabilityFailed(error);
167
+ }
168
+ return undefined;
169
+ });
170
+ domainPromise = opening;
171
+ };
172
+ const openDomain = async (allowClosed = false) => {
173
+ if (!lifecycleBound || (lifecycleClosed && !allowClosed) || durableDisabled)
174
+ return undefined;
175
+ const facility = storageDomainOf(ctx);
176
+ if (facility === undefined)
177
+ return undefined;
178
+ transition = transition.then(() => ensureDomainFor(facility, allowClosed), () => ensureDomainFor(facility, allowClosed));
179
+ await transition;
180
+ return await domainPromise;
181
+ };
182
+ const table = async (allowClosed = false) => {
183
+ const domain = await openDomain(allowClosed);
184
+ if (domain === undefined)
185
+ return undefined;
186
+ return domain.table('policies');
187
+ };
188
+ const failDurabilityAndRetire = async (error) => {
189
+ markDurabilityFailed(error);
190
+ await enqueueRetire();
191
+ };
192
+ const enqueueMutation = (key, mutate) => {
193
+ if (lifecycleClosed)
194
+ return Promise.resolve();
195
+ const previous = mutationTails.get(key) ?? Promise.resolve();
196
+ let current;
197
+ // Admission is decided above. Once admitted, a mutation must run even if
198
+ // lifecycle shutdown begins while it is queued behind an earlier mutation;
199
+ // the disposer waits activeMutations before retiring the domain.
200
+ current = previous.then(mutate, mutate);
201
+ mutationTails.set(key, current);
202
+ activeMutations.add(current);
203
+ void current.then(() => {
204
+ activeMutations.delete(current);
205
+ if (mutationTails.get(key) === current)
206
+ mutationTails.delete(key);
207
+ }, () => {
208
+ activeMutations.delete(current);
209
+ if (mutationTails.get(key) === current)
210
+ mutationTails.delete(key);
211
+ });
212
+ return current;
213
+ };
214
+ return {
215
+ get durable() {
216
+ return lifecycleBound
217
+ && !lifecycleClosed
218
+ && !durableDisabled
219
+ && storageDomainOf(ctx) !== undefined;
220
+ },
221
+ get(sessionId) {
222
+ return cache.get(normalizedSessionId(sessionId));
223
+ },
224
+ async hydrate(sessionId) {
225
+ const key = normalizedSessionId(sessionId);
226
+ const existing = cache.get(key);
227
+ if (existing !== undefined)
228
+ return existing;
229
+ const token = {};
230
+ hydrationTokens.set(key, token);
231
+ try {
232
+ const handle = await table();
233
+ if (hydrationTokens.get(key) !== token)
234
+ return cache.get(key);
235
+ if (handle === undefined)
236
+ return undefined;
237
+ const policy = parseSessionVisionPolicy(handle.get(key));
238
+ if (hydrationTokens.get(key) !== token)
239
+ return cache.get(key);
240
+ if (policy !== undefined)
241
+ cache.set(key, policy);
242
+ return policy;
243
+ }
244
+ catch (error) {
245
+ await failDurabilityAndRetire(error);
246
+ return undefined;
247
+ }
248
+ finally {
249
+ if (hydrationTokens.get(key) === token)
250
+ hydrationTokens.delete(key);
251
+ }
252
+ },
253
+ release(sessionId) {
254
+ const key = normalizedSessionId(sessionId);
255
+ hydrationTokens.delete(key);
256
+ // A durable row can be rehydrated on the next Agent creation. Keep the
257
+ // process-local copy when durability is unavailable so volatile support
258
+ // does not lose the only Session-scoped truth it has.
259
+ if (this.durable)
260
+ cache.delete(key);
261
+ },
262
+ async set(sessionId, policy) {
263
+ const key = normalizedSessionId(sessionId);
264
+ hydrationTokens.delete(key);
265
+ const normalized = parseSessionVisionPolicy(policy);
266
+ if (normalized === undefined)
267
+ throw new TypeError('invalid session vision policy');
268
+ // Publish process-local authority before the first await. A committed
269
+ // model/selection event must affect the next prompt immediately even if
270
+ // sidecar I/O is still settling; delegation still awaits this method and
271
+ // therefore preserves its stronger durability-before-child-release rule.
272
+ cache.set(key, normalized);
273
+ await enqueueMutation(key, async () => {
274
+ try {
275
+ // This mutation was admitted before lifecycle close; the disposer
276
+ // waits it before retiring the domain, so it may finish durable I/O.
277
+ const handle = await table(true);
278
+ if (handle !== undefined)
279
+ await handle.put(key, normalized);
280
+ }
281
+ catch (error) {
282
+ await failDurabilityAndRetire(error);
283
+ }
284
+ });
285
+ return normalized;
286
+ },
287
+ async delete(sessionId) {
288
+ const key = normalizedSessionId(sessionId);
289
+ hydrationTokens.delete(key);
290
+ cache.delete(key);
291
+ await enqueueMutation(key, async () => {
292
+ try {
293
+ // Same admission rule as set(): pre-close deletes must not be lost.
294
+ const handle = await table(true);
295
+ if (handle !== undefined)
296
+ await handle.delete(key);
297
+ }
298
+ catch (error) {
299
+ await failDurabilityAndRetire(error);
300
+ }
301
+ });
302
+ },
303
+ };
304
+ }
@@ -0,0 +1,104 @@
1
+ export const SESSION_VISION_POLICY_REVISION = 1;
2
+ function nonEmptyString(value) {
3
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
4
+ }
5
+ function normalizedSessionId(value) {
6
+ const id = nonEmptyString(value);
7
+ if (id === undefined)
8
+ throw new TypeError('session vision policy requires a non-empty session id');
9
+ return id;
10
+ }
11
+ export function parseSessionVisionPolicy(value) {
12
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
13
+ return undefined;
14
+ const record = value;
15
+ if (record.revision !== SESSION_VISION_POLICY_REVISION)
16
+ return undefined;
17
+ if (typeof record.enabled !== 'boolean')
18
+ return undefined;
19
+ if (record.source !== 'user' && record.source !== 'delegation')
20
+ return undefined;
21
+ if (record.source === 'user') {
22
+ if (record.inheritedFrom !== undefined)
23
+ return undefined;
24
+ return Object.freeze({
25
+ revision: SESSION_VISION_POLICY_REVISION,
26
+ enabled: record.enabled,
27
+ source: 'user',
28
+ });
29
+ }
30
+ const inheritedFrom = nonEmptyString(record.inheritedFrom);
31
+ if (inheritedFrom === undefined)
32
+ return undefined;
33
+ return Object.freeze({
34
+ revision: SESSION_VISION_POLICY_REVISION,
35
+ enabled: record.enabled,
36
+ source: 'delegation',
37
+ inheritedFrom,
38
+ });
39
+ }
40
+ export function userSessionVisionPolicy(enabled) {
41
+ return Object.freeze({
42
+ revision: SESSION_VISION_POLICY_REVISION,
43
+ enabled,
44
+ source: 'user',
45
+ });
46
+ }
47
+ export function delegatedSessionVisionPolicy(enabled, parentSessionId) {
48
+ return Object.freeze({
49
+ revision: SESSION_VISION_POLICY_REVISION,
50
+ enabled,
51
+ source: 'delegation',
52
+ inheritedFrom: normalizedSessionId(parentSessionId),
53
+ });
54
+ }
55
+ /**
56
+ * Own Session-scoped DVR intent independently of provider/model routing.
57
+ *
58
+ * The injected table is deliberately narrower than DSH storage-domain. This
59
+ * module owns DVR policy semantics; the Host boundary that opens a durable
60
+ * domain is a separate composition concern. A malformed persisted row fails
61
+ * closed as absent policy so route-derived compatibility can remain the
62
+ * migration fallback until the durable policy rollout is complete.
63
+ */
64
+ export function createSessionVisionPolicyStore(table) {
65
+ return {
66
+ get(sessionId) {
67
+ const key = normalizedSessionId(sessionId);
68
+ return parseSessionVisionPolicy(table.get(key));
69
+ },
70
+ async set(sessionId, policy) {
71
+ const key = normalizedSessionId(sessionId);
72
+ const normalized = parseSessionVisionPolicy(policy);
73
+ if (normalized === undefined) {
74
+ throw new TypeError('invalid session vision policy');
75
+ }
76
+ await table.put(key, normalized);
77
+ return normalized;
78
+ },
79
+ async delete(sessionId) {
80
+ await table.delete(normalizedSessionId(sessionId));
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Process-scoped policy backend used while no durable Host sidecar is bound.
86
+ *
87
+ * The Map is closure-owned by the returned store, never process-global. It is
88
+ * intentionally replaceable by a durable table without changing policy,
89
+ * delegation, or authority semantics.
90
+ */
91
+ export function createVolatileSessionVisionPolicyStore() {
92
+ const rows = new Map();
93
+ return createSessionVisionPolicyStore({
94
+ get(key) {
95
+ return rows.get(key);
96
+ },
97
+ async put(key, value) {
98
+ rows.set(key, value);
99
+ },
100
+ async delete(key) {
101
+ rows.delete(key);
102
+ },
103
+ });
104
+ }