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.
- package/README.md +16 -7
- package/README.zh.md +16 -7
- package/docs/architecture/2.3.0-compat-retirement.md +147 -0
- package/docs/architecture/2.3.0-execution-plan.md +410 -0
- package/docs/architecture/2.3.0-final-audit.md +279 -0
- package/docs/architecture/2.3.0-reopened-plan.md +720 -0
- package/docs/architecture/3.0-architecture-plan.md +252 -0
- package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
- package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
- package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
- package/docs/architecture/3.0-r11-adversarial-review.md +441 -0
- package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
- package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
- package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
- package/docs/architecture/3.0-r7-public-api.md +151 -0
- package/docs/architecture/3.0-r8-ci-release.md +248 -0
- package/docs/architecture/3.0-r9-final-audit.md +370 -0
- package/docs/architecture/compat-inventory.md +26 -34
- package/docs/architecture/dsh-compatibility-matrix.md +65 -54
- package/docs/architecture/dsh-support-window.md +30 -22
- package/docs/architecture/final-closure-audit.md +2 -0
- package/docs/releases/v2.3.0.md +53 -0
- package/docs/releases/v3.0.0.md +85 -0
- package/entry.js +9 -57
- package/index.js +268 -761
- package/lib/agent-request-route-authority.js +1 -20
- package/lib/catalog-corrections.js +1 -2
- package/lib/client-maintenance-actions.js +145 -0
- package/lib/client-presentation-boundary-main.js +75 -17
- package/lib/client-presentation-boundary.js +53 -28
- package/lib/client.js +246 -144
- package/lib/core-apply-lifecycle.js +41 -0
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +91 -278
- package/lib/core-vision-surface-domain.js +63 -0
- package/lib/core-vision-surface.js +11 -77
- package/lib/delegated-call-config.js +15 -24
- package/lib/desktop-screenshot-tool.js +262 -0
- package/lib/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +37 -256
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- package/lib/dsh-support-window.js +3 -3
- package/lib/file-logger.js +8 -3
- package/lib/grounding-coordinate-runtime.js +1 -2
- package/lib/http-body-limit.js +97 -79
- package/lib/http-compat.js +1 -2
- package/lib/image-offload-compat.js +30 -21
- package/lib/image-resource-governor.js +210 -228
- package/lib/legacy-global-proxy-boundary.js +46 -3
- package/lib/live-model-client-prelude.js +12 -4
- package/lib/live-model-discovery.js +134 -43
- package/lib/local-remote-settings-permission.js +33 -7
- package/lib/local-vision-stabilizer.js +31 -17
- package/lib/maintenance-routes.js +134 -0
- package/lib/native-image-coexistence.js +15 -3
- package/lib/ollama-cold-start.js +87 -23
- package/lib/pi-ai-bridge-wire-compat.js +3 -2
- package/lib/proxy-dispatcher-pool.js +151 -92
- package/lib/proxy-routing.js +75 -51
- package/lib/proxy-url-compat.js +3 -10
- package/lib/public-config.js +36 -0
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +19 -11
- package/lib/remote-settings-bridge.js +11 -0
- package/lib/remote-settings-risk-client-boundary.js +155 -0
- package/lib/remote-settings-risk-confirmation.js +17 -105
- package/lib/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +127 -34
- package/lib/runtime-i18n-boundary.js +10 -11
- package/lib/screenshot-source-boundary.js +2 -2
- package/lib/session-affinity-runtime.js +113 -97
- package/lib/session-affinity.js +89 -76
- package/lib/session-event-history-compat.js +30 -0
- package/lib/session-message-source-compat.js +7 -3
- package/lib/session-query-readers.js +263 -0
- package/lib/session-surface-compat.js +30 -31
- package/lib/session-surface-policy-domain.js +85 -0
- package/lib/session-surface-policy.js +2 -111
- package/lib/session-turn-resolver.js +2 -6
- package/lib/session-vision-attachment-index.js +143 -0
- package/lib/session-vision-delegation-boundary.js +34 -0
- package/lib/session-vision-delegation.js +22 -0
- package/lib/session-vision-index.js +179 -116
- package/lib/session-vision-mode-authority.js +229 -192
- package/lib/session-vision-mode-boundary.js +65 -22
- package/lib/session-vision-policy-domain.js +304 -0
- package/lib/session-vision-policy.js +104 -0
- package/lib/session-vision-state.js +368 -378
- package/lib/settings-client-017-compat.js +9 -7
- package/lib/settings-client-loader-lifecycle.js +129 -0
- package/lib/sharp-runtime.js +0 -7
- package/lib/structured-flow-hardening.js +25 -6
- package/lib/tesseract-exec-compat.js +9 -1
- package/lib/turn-budget-context.js +47 -41
- package/lib/update-check.js +11 -2
- package/lib/vision-attachment-handle-runtime.js +176 -156
- package/lib/vision-backend-runtime-policy.js +19 -2
- package/lib/vision-background-benchmark.js +41 -4
- package/lib/vision-background-failure-policy.js +41 -41
- package/lib/vision-background-stop-store-core.js +217 -0
- package/lib/vision-background-stop-store.js +8 -179
- package/lib/vision-capability-benchmark-service.js +1 -0
- package/lib/vision-capability-benchmark.js +11 -61
- package/lib/vision-capability-evidence.js +35 -0
- package/lib/vision-capability-fingerprint.js +72 -0
- package/lib/vision-capability-identity.js +116 -61
- package/lib/vision-capability-router.js +13 -33
- package/lib/vision-chunk-assembler.js +95 -0
- package/lib/vision-diagnostics-routes.js +246 -0
- package/lib/vision-evidence-guidance.js +3 -1
- package/lib/vision-execution-order-apply.js +45 -55
- package/lib/vision-execution-order.js +19 -19
- package/lib/vision-execution-policy.js +12 -7
- package/lib/vision-model-registry.js +30 -17
- package/lib/vision-provider-transport.js +137 -176
- package/lib/vision-routing-authority.js +47 -48
- package/lib/vision-routing-product.js +26 -29
- package/lib/vision-routing-settings-prelude.js +8 -3
- package/lib/vision-runtime-performance-store.js +166 -0
- package/lib/vision-runtime-performance.js +13 -142
- package/lib/vision-toggle-root-hardening.js +22 -5
- package/lib/vision-tool-runtime-boundary.js +104 -31
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +6 -9
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +56 -20
- package/lib/android-attachment-compat.js +0 -244
- package/lib/settings-client-rc8-lifecycle.js +0 -397
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
import { currentSessionSurfacePolicy } from './session-surface-policy.js'
|
|
2
2
|
import { sessionSurfaceReplacementIntent } from './session-surface-compat.js'
|
|
3
3
|
import { createSessionVisionStateStore } from './session-vision-state.js'
|
|
4
|
+
import { legacySessionEvents } from './session-event-history-compat.js'
|
|
5
|
+
import { createSessionVisionAttachmentIndex } from './session-vision-attachment-index.js'
|
|
4
6
|
|
|
5
7
|
const MAX_PENDING_REPAIR_EVENTS = 256
|
|
8
|
+
const MAX_REPAIR_OVERFLOW_RANGES = 32
|
|
9
|
+
const MAX_REPAIR_OVERFLOW_INSPECTIONS_PER_PASS = 512
|
|
10
|
+
const MAX_REPAIR_OVERFLOW_READS_PER_PASS = 32
|
|
6
11
|
|
|
7
12
|
function isObject(value) {
|
|
8
13
|
return value !== null && typeof value === 'object'
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
function legacySessionEvents(session) {
|
|
12
|
-
try {
|
|
13
|
-
// dsh 0.1.2-alpha.4 removed the bare `session.events` array in favor of
|
|
14
|
-
// `session.snapshotEvents()`; both are handled so one build runs on both
|
|
15
|
-
// harness generations.
|
|
16
|
-
if (session && typeof session.snapshotEvents === 'function') {
|
|
17
|
-
const events = session.snapshotEvents()
|
|
18
|
-
return Array.isArray(events) ? events : undefined
|
|
19
|
-
}
|
|
20
|
-
return Array.isArray(session?.events) ? session.events : undefined
|
|
21
|
-
} catch {
|
|
22
|
-
return undefined
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
function surfaceNodes(session) {
|
|
27
17
|
try {
|
|
28
18
|
return Array.isArray(session?.surface?.nodes) ? session.surface.nodes : undefined
|
|
@@ -49,9 +39,10 @@ function isReplacementEvent(event) {
|
|
|
49
39
|
/**
|
|
50
40
|
* Session visual data-plane index.
|
|
51
41
|
*
|
|
52
|
-
* SessionVisionStateStore remains the bounded storage owner.
|
|
53
|
-
* target-only durable
|
|
54
|
-
*
|
|
42
|
+
* SessionVisionStateStore remains the bounded storage owner. Attachment cache
|
|
43
|
+
* access and target-only durable recovery are delegated to one typed attachment
|
|
44
|
+
* index; this object owns event-feed-backed tool-result image surface repair and
|
|
45
|
+
* expired structured guard-stop surface repair.
|
|
55
46
|
*
|
|
56
47
|
* Supported DSH Hosts expose the post-commit `session/event` feed. Production
|
|
57
48
|
* surface repair consumes only the exact frozen events observed on that feed;
|
|
@@ -80,105 +71,37 @@ export function createSessionVisionIndex({
|
|
|
80
71
|
if (!store || typeof store !== 'object') {
|
|
81
72
|
throw new TypeError('session vision index requires a state store')
|
|
82
73
|
}
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
:
|
|
74
|
+
const attachmentIndex = createSessionVisionAttachmentIndex({
|
|
75
|
+
stateStore: store,
|
|
76
|
+
collectEventAttachmentRefs:
|
|
77
|
+
typeof core.collectEventAttachmentRefs === 'function'
|
|
78
|
+
? (events) => core.collectEventAttachmentRefs(events)
|
|
79
|
+
: undefined,
|
|
80
|
+
readSessionLog,
|
|
81
|
+
logger,
|
|
82
|
+
})
|
|
83
|
+
const {
|
|
84
|
+
recordAttachments,
|
|
85
|
+
lookupAttachment,
|
|
86
|
+
resolveAttachment,
|
|
87
|
+
resolveAttachments,
|
|
88
|
+
} = attachmentIndex
|
|
89
|
+
|
|
90
|
+
// Surface repair/feed state remains owned by SessionVisionIndex. Attachment
|
|
91
|
+
// cache/recovery state lives exclusively in SessionVisionAttachmentIndex.
|
|
86
92
|
const toolSurfaceScans = new WeakMap()
|
|
87
93
|
const guardSurfaceScans = new WeakMap()
|
|
88
94
|
const pendingToolRepairEvents = new WeakMap()
|
|
89
95
|
const pendingGuardRepairEvents = new WeakMap()
|
|
96
|
+
const toolRepairOverflowRanges = new WeakMap()
|
|
97
|
+
const guardRepairOverflowRanges = new WeakMap()
|
|
90
98
|
const unsupportedSurfaceContracts = new WeakSet()
|
|
91
99
|
const repairReadWarnings = new WeakMap()
|
|
92
100
|
const repairFeedOverflowWarnings = new WeakSet()
|
|
93
101
|
const repairFeedObserverWarnings = new WeakMap()
|
|
94
102
|
const surfaceFeedBackfills = new WeakMap()
|
|
95
|
-
const attachmentRecoveryWarnings = new WeakMap()
|
|
96
103
|
let surfaceEventFeedActive = false
|
|
97
104
|
|
|
98
|
-
const recordAttachments = (session, refs) => {
|
|
99
|
-
if (!session || !Array.isArray(refs) || refs.length === 0) return
|
|
100
|
-
store.recordAttachments(session, refs)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Synchronous lookup is cache-only. Projected short handles first warm this
|
|
104
|
-
// cache from current derived messages and must not trigger historical I/O.
|
|
105
|
-
const lookupAttachment = (session, id) => {
|
|
106
|
-
if (!primitiveLookup) return undefined
|
|
107
|
-
return primitiveLookup(session, id)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const recoverAttachmentsFromEvents = (session, ids, events) => {
|
|
111
|
-
const found = new Map()
|
|
112
|
-
if (!Array.isArray(events) || events.length === 0 || typeof core.collectEventAttachmentRefs !== 'function') {
|
|
113
|
-
return found
|
|
114
|
-
}
|
|
115
|
-
const wanted = new Set(ids.map((id) => String(id)))
|
|
116
|
-
for (const ref of core.collectEventAttachmentRefs(events)) {
|
|
117
|
-
if (!ref) continue
|
|
118
|
-
const id = String(ref.attachmentId ?? ref.id)
|
|
119
|
-
if (wanted.has(id) && !found.has(id)) found.set(id, ref)
|
|
120
|
-
}
|
|
121
|
-
if (found.size > 0) store.recordAttachments(session, [...found.values()])
|
|
122
|
-
return found
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const warnAttachmentRecoveryFailure = (session, id, error) => {
|
|
126
|
-
const message = boundedMessage(error)
|
|
127
|
-
const key = `${String(id)}:${message}`
|
|
128
|
-
if (attachmentRecoveryWarnings.get(session) === key) return
|
|
129
|
-
attachmentRecoveryWarnings.set(session, key)
|
|
130
|
-
logger?.warn?.(
|
|
131
|
-
'vision-router: session attachment recovery failed id=%s error=%s',
|
|
132
|
-
String(id),
|
|
133
|
-
message,
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const resolveAttachments = async (session, ids) => {
|
|
138
|
-
const requested = [...new Set((Array.isArray(ids) ? ids : []).map((id) => String(id)))]
|
|
139
|
-
const resolved = new Map()
|
|
140
|
-
const missing = []
|
|
141
|
-
for (const id of requested) {
|
|
142
|
-
const cached = lookupAttachment(session, id)
|
|
143
|
-
if (cached !== undefined) resolved.set(id, cached)
|
|
144
|
-
else missing.push(id)
|
|
145
|
-
}
|
|
146
|
-
if (missing.length === 0 || session === undefined) return resolved
|
|
147
|
-
|
|
148
|
-
let events
|
|
149
|
-
if (typeof readSessionLog === 'function') {
|
|
150
|
-
let result
|
|
151
|
-
try {
|
|
152
|
-
result = await readSessionLog(session)
|
|
153
|
-
} catch (error) {
|
|
154
|
-
warnAttachmentRecoveryFailure(session, missing[0], error)
|
|
155
|
-
return resolved
|
|
156
|
-
}
|
|
157
|
-
if (result?.supported === true) {
|
|
158
|
-
attachmentRecoveryWarnings.delete(session)
|
|
159
|
-
events = result.events
|
|
160
|
-
} else if (result?.supported !== false) {
|
|
161
|
-
warnAttachmentRecoveryFailure(
|
|
162
|
-
session,
|
|
163
|
-
missing[0],
|
|
164
|
-
new Error('Session log reader returned an invalid capability result'),
|
|
165
|
-
)
|
|
166
|
-
return resolved
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Historical/partial Hosts without SessionQuery keep the released fallback.
|
|
171
|
-
// Public rc8+ Hosts use readSessionLog above and never touch sync history.
|
|
172
|
-
if (events === undefined) events = legacySessionEvents(session)
|
|
173
|
-
const recovered = recoverAttachmentsFromEvents(session, missing, events)
|
|
174
|
-
for (const [id, ref] of recovered) resolved.set(id, ref)
|
|
175
|
-
return resolved
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const resolveAttachment = async (session, id) => {
|
|
179
|
-
return (await resolveAttachments(session, [id])).get(String(id))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
105
|
const pendingSurfaceScan = (scans, session) => {
|
|
183
106
|
if (!session) return undefined
|
|
184
107
|
const nodes = surfaceNodes(session)
|
|
@@ -295,7 +218,132 @@ export function createSessionVisionIndex({
|
|
|
295
218
|
return undefined
|
|
296
219
|
}
|
|
297
220
|
|
|
298
|
-
|
|
221
|
+
// Feed overflow stores only dropped sequence ranges, never frozen event
|
|
222
|
+
// objects. Adjacent ranges coalesce; pathological sparse overflow is
|
|
223
|
+
// conservatively collapsed once the range count reaches a fixed bound. The
|
|
224
|
+
// recovery loop then advances that range in bounded numeric/read batches and
|
|
225
|
+
// consults only Host-owned readSessionEvent(), so memory remains bounded
|
|
226
|
+
// without returning to whole-Session history materialization.
|
|
227
|
+
const addRepairOverflowSeq = (overflowBySession, session, seq) => {
|
|
228
|
+
if (!Number.isSafeInteger(seq) || seq < 0 || Object.is(seq, -0)) return
|
|
229
|
+
let ranges = overflowBySession.get(session)
|
|
230
|
+
if (!ranges) {
|
|
231
|
+
ranges = []
|
|
232
|
+
overflowBySession.set(session, ranges)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let start = seq
|
|
236
|
+
let end = seq
|
|
237
|
+
let insertAt = 0
|
|
238
|
+
while (insertAt < ranges.length && ranges[insertAt].end + 1 < start) insertAt += 1
|
|
239
|
+
while (insertAt < ranges.length && ranges[insertAt].start - 1 <= end) {
|
|
240
|
+
start = Math.min(start, ranges[insertAt].start)
|
|
241
|
+
end = Math.max(end, ranges[insertAt].end)
|
|
242
|
+
ranges.splice(insertAt, 1)
|
|
243
|
+
}
|
|
244
|
+
ranges.splice(insertAt, 0, { start, end })
|
|
245
|
+
|
|
246
|
+
if (ranges.length > MAX_REPAIR_OVERFLOW_RANGES) {
|
|
247
|
+
overflowBySession.set(session, [{
|
|
248
|
+
start: ranges[0].start,
|
|
249
|
+
end: ranges[ranges.length - 1].end,
|
|
250
|
+
}])
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const advanceRepairOverflowRange = (ranges) => {
|
|
255
|
+
const range = ranges?.[0]
|
|
256
|
+
if (!range) return
|
|
257
|
+
if (range.start >= range.end) ranges.shift()
|
|
258
|
+
else range.start += 1
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const recoverRepairOverflowEvents = async (overflowBySession, pendingBySession, session) => {
|
|
262
|
+
const ranges = overflowBySession.get(session)
|
|
263
|
+
if (!ranges || ranges.length === 0) return []
|
|
264
|
+
|
|
265
|
+
const nodes = surfaceNodes(session)
|
|
266
|
+
if (!nodes || nodes.length === 0) {
|
|
267
|
+
overflowBySession.delete(session)
|
|
268
|
+
repairFeedOverflowWarnings.delete(session)
|
|
269
|
+
return []
|
|
270
|
+
}
|
|
271
|
+
const current = new Set(nodes)
|
|
272
|
+
const pending = pendingBySession.get(session)
|
|
273
|
+
const recovered = []
|
|
274
|
+
let inspections = 0
|
|
275
|
+
let reads = 0
|
|
276
|
+
|
|
277
|
+
while (
|
|
278
|
+
ranges.length > 0
|
|
279
|
+
&& inspections < MAX_REPAIR_OVERFLOW_INSPECTIONS_PER_PASS
|
|
280
|
+
&& reads < MAX_REPAIR_OVERFLOW_READS_PER_PASS
|
|
281
|
+
) {
|
|
282
|
+
const seq = ranges[0].start
|
|
283
|
+
inspections += 1
|
|
284
|
+
|
|
285
|
+
// A dropped repair that is no longer on the live surface is settled. A
|
|
286
|
+
// seq still present in the fast-path map will be consumed normally.
|
|
287
|
+
if (!current.has(seq) || pending?.has(seq)) {
|
|
288
|
+
advanceRepairOverflowRange(ranges)
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (typeof readSessionEvent !== 'function') {
|
|
293
|
+
warnRepairReadFailure(
|
|
294
|
+
session,
|
|
295
|
+
seq,
|
|
296
|
+
new Error('Session event reader is unavailable during feed overflow recovery'),
|
|
297
|
+
)
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let result
|
|
302
|
+
try {
|
|
303
|
+
result = await readSessionEvent(session, seq)
|
|
304
|
+
} catch (error) {
|
|
305
|
+
warnRepairReadFailure(session, seq, error)
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
if (result?.supported !== true) {
|
|
309
|
+
warnRepairReadFailure(
|
|
310
|
+
session,
|
|
311
|
+
seq,
|
|
312
|
+
result?.supported === false
|
|
313
|
+
? new Error('Session event reader is unsupported during feed overflow recovery')
|
|
314
|
+
: new Error('Session event reader returned an invalid capability result during feed overflow recovery'),
|
|
315
|
+
)
|
|
316
|
+
break
|
|
317
|
+
}
|
|
318
|
+
const event = result.event
|
|
319
|
+
if (!event || typeof event !== 'object' || (event.seq !== undefined && event.seq !== seq)) {
|
|
320
|
+
warnRepairReadFailure(
|
|
321
|
+
session,
|
|
322
|
+
seq,
|
|
323
|
+
new Error(`Session event reader returned no matching event for overflow seq ${seq}`),
|
|
324
|
+
)
|
|
325
|
+
break
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
reads += 1
|
|
329
|
+
advanceRepairOverflowRange(ranges)
|
|
330
|
+
repairReadWarnings.delete(session)
|
|
331
|
+
// The live feed may deliver the same committed event while the exact read
|
|
332
|
+
// is in flight. In that case the fast-path map owns it; do not return a
|
|
333
|
+
// second copy from recovery.
|
|
334
|
+
if (!pendingBySession.get(session)?.has(seq) && !isReplacementEvent(event)) {
|
|
335
|
+
recovered.push({ seq, event })
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (ranges.length === 0) {
|
|
340
|
+
overflowBySession.delete(session)
|
|
341
|
+
repairFeedOverflowWarnings.delete(session)
|
|
342
|
+
}
|
|
343
|
+
return recovered
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const cachePendingRepairEvent = (pendingBySession, overflowBySession, session, event) => {
|
|
299
347
|
if (!session || !Number.isSafeInteger(event?.seq) || event.seq < 0 || Object.is(event.seq, -0)) return
|
|
300
348
|
let pending = pendingBySession.get(session)
|
|
301
349
|
if (!pending) {
|
|
@@ -304,11 +352,14 @@ export function createSessionVisionIndex({
|
|
|
304
352
|
}
|
|
305
353
|
if (!pending.has(event.seq) && pending.size >= MAX_PENDING_REPAIR_EVENTS) {
|
|
306
354
|
const oldest = pending.keys().next().value
|
|
307
|
-
if (oldest !== undefined)
|
|
355
|
+
if (oldest !== undefined) {
|
|
356
|
+
pending.delete(oldest)
|
|
357
|
+
addRepairOverflowSeq(overflowBySession, session, oldest)
|
|
358
|
+
}
|
|
308
359
|
if (!repairFeedOverflowWarnings.has(session)) {
|
|
309
360
|
repairFeedOverflowWarnings.add(session)
|
|
310
361
|
logger?.warn?.(
|
|
311
|
-
'vision-router: session surface repair event feed exceeded %s pending events;
|
|
362
|
+
'vision-router: session surface repair event feed exceeded %s pending events; scheduling bounded exact-event recovery',
|
|
312
363
|
MAX_PENDING_REPAIR_EVENTS,
|
|
313
364
|
)
|
|
314
365
|
}
|
|
@@ -340,10 +391,10 @@ export function createSessionVisionIndex({
|
|
|
340
391
|
event,
|
|
341
392
|
(events, seqs) => core.planToolResultImageShadows(events, seqs, () => true),
|
|
342
393
|
)
|
|
343
|
-
if (plans.length > 0) cachePendingRepairEvent(pendingToolRepairEvents, session, event)
|
|
394
|
+
if (plans.length > 0) cachePendingRepairEvent(pendingToolRepairEvents, toolRepairOverflowRanges, session, event)
|
|
344
395
|
} else if (event.type === 'user/message' && typeof core.planGuardStopShadows === 'function') {
|
|
345
396
|
const plans = planAgainstOneEvent(event, (events, seqs) => core.planGuardStopShadows(events, seqs))
|
|
346
|
-
if (plans.length > 0) cachePendingRepairEvent(pendingGuardRepairEvents, session, event)
|
|
397
|
+
if (plans.length > 0) cachePendingRepairEvent(pendingGuardRepairEvents, guardRepairOverflowRanges, session, event)
|
|
347
398
|
}
|
|
348
399
|
repairFeedObserverWarnings.delete(session)
|
|
349
400
|
} catch (error) {
|
|
@@ -439,11 +490,17 @@ export function createSessionVisionIndex({
|
|
|
439
490
|
|
|
440
491
|
const repairToolResultSurfaceFromFeed = async (session) => {
|
|
441
492
|
if (typeof session?.append !== 'function' || typeof core.planToolResultImageShadows !== 'function') return 0
|
|
493
|
+
const recovered = await recoverRepairOverflowEvents(
|
|
494
|
+
toolRepairOverflowRanges,
|
|
495
|
+
pendingToolRepairEvents,
|
|
496
|
+
session,
|
|
497
|
+
)
|
|
442
498
|
const pending = consumePendingRepairEvents(pendingToolRepairEvents, session)
|
|
443
|
-
|
|
499
|
+
const entries = [...recovered, ...pending]
|
|
500
|
+
if (entries.length === 0) return 0
|
|
444
501
|
const preserve = keepToolResultImages()
|
|
445
502
|
let repaired = 0
|
|
446
|
-
for (const { seq, event } of
|
|
503
|
+
for (const { seq, event } of entries) {
|
|
447
504
|
const events = { [seq]: event }
|
|
448
505
|
const plans = core.planToolResultImageShadows(events, [seq], () => !preserve)
|
|
449
506
|
for (const plan of plans) {
|
|
@@ -470,10 +527,16 @@ export function createSessionVisionIndex({
|
|
|
470
527
|
|
|
471
528
|
const repairGuardStopSurfaceFromFeed = async (session) => {
|
|
472
529
|
if (typeof session?.append !== 'function' || typeof core.planGuardStopShadows !== 'function') return 0
|
|
530
|
+
const recovered = await recoverRepairOverflowEvents(
|
|
531
|
+
guardRepairOverflowRanges,
|
|
532
|
+
pendingGuardRepairEvents,
|
|
533
|
+
session,
|
|
534
|
+
)
|
|
473
535
|
const pending = consumePendingRepairEvents(pendingGuardRepairEvents, session)
|
|
474
|
-
|
|
536
|
+
const entries = [...recovered, ...pending]
|
|
537
|
+
if (entries.length === 0) return 0
|
|
475
538
|
let repaired = 0
|
|
476
|
-
for (const { seq, event } of
|
|
539
|
+
for (const { seq, event } of entries) {
|
|
477
540
|
const events = { [seq]: event }
|
|
478
541
|
const plans = core.planGuardStopShadows(events, [seq])
|
|
479
542
|
for (const plan of plans) {
|
|
@@ -587,7 +650,7 @@ export function createSessionVisionIndex({
|
|
|
587
650
|
if (typeof core.rewriteImageBlocks === 'function') {
|
|
588
651
|
const found = core.rewriteImageBlocks(messages)
|
|
589
652
|
if (Array.isArray(found?.attachments) && found.attachments.length > 0) {
|
|
590
|
-
|
|
653
|
+
recordAttachments(session, found.attachments)
|
|
591
654
|
}
|
|
592
655
|
}
|
|
593
656
|
await ensureSurfaceEventFeedBackfill(session)
|