dsh-vision-router 2.3.0 → 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 +12 -3
- package/README.zh.md +12 -3
- 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/dsh-support-window.md +5 -3
- package/docs/releases/v3.0.0.md +85 -0
- package/entry.js +1 -1
- package/index.js +108 -71
- package/lib/agent-request-route-authority.js +1 -20
- package/lib/core-apply-lifecycle.js +37 -43
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +3 -143
- 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/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +26 -230
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- 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/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-vision-stabilizer.js +31 -17
- package/lib/maintenance-routes.js +1 -3
- 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 +23 -44
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +17 -4
- package/lib/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +22 -4
- 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-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 +25 -107
- 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/structured-flow-hardening.js +22 -4
- 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 +16 -0
- 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.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 +34 -4
- 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 -147
- 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-tool-runtime-boundary.js +99 -30
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +4 -7
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +37 -11
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { legacySessionEvents } from './session-event-history-compat.js';
|
|
2
|
+
function propertyBag(value) {
|
|
3
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
function weakKey(value) {
|
|
8
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function attachmentRef(value) {
|
|
13
|
+
return propertyBag(value) ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
function attachmentIdOf(ref) {
|
|
16
|
+
const record = propertyBag(ref);
|
|
17
|
+
return String(record?.attachmentId ?? record?.id);
|
|
18
|
+
}
|
|
19
|
+
function iterableValues(value) {
|
|
20
|
+
const iterator = propertyBag(value)?.[Symbol.iterator];
|
|
21
|
+
if (typeof iterator !== 'function') {
|
|
22
|
+
throw new TypeError('Session attachment collector returned a non-iterable result');
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function boundedMessage(error) {
|
|
27
|
+
const record = propertyBag(error);
|
|
28
|
+
const text = record?.message ?? error;
|
|
29
|
+
return String(text ?? '').slice(0, 400);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Attachment access boundary for one SessionVisionStateStore.
|
|
33
|
+
*
|
|
34
|
+
* The state store remains the sole bounded storage owner. This boundary owns
|
|
35
|
+
* only cache access plus target-only cold recovery from the durable Session
|
|
36
|
+
* event source. Synchronous lookup never performs historical I/O.
|
|
37
|
+
*/
|
|
38
|
+
export function createSessionVisionAttachmentIndex({ stateStore, collectEventAttachmentRefs, readSessionLog, logger, }) {
|
|
39
|
+
const runtimeStore = stateStore;
|
|
40
|
+
const lookup = runtimeStore.lookupAttachment;
|
|
41
|
+
const primitiveLookup = typeof lookup === 'function'
|
|
42
|
+
? (session, attachmentId) => lookup.call(stateStore, session, attachmentId)
|
|
43
|
+
: undefined;
|
|
44
|
+
const attachmentRecoveryWarnings = new WeakMap();
|
|
45
|
+
const recordAttachments = (session, refs) => {
|
|
46
|
+
if (!session || !Array.isArray(refs) || refs.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
stateStore.recordAttachments(session, refs);
|
|
49
|
+
};
|
|
50
|
+
const lookupAttachment = (session, attachmentId) => {
|
|
51
|
+
if (primitiveLookup === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
return primitiveLookup(session, attachmentId);
|
|
54
|
+
};
|
|
55
|
+
const recoverAttachmentsFromEvents = (session, ids, events) => {
|
|
56
|
+
const found = new Map();
|
|
57
|
+
if (!Array.isArray(events)
|
|
58
|
+
|| events.length === 0
|
|
59
|
+
|| typeof collectEventAttachmentRefs !== 'function') {
|
|
60
|
+
return found;
|
|
61
|
+
}
|
|
62
|
+
const wanted = new Set(ids);
|
|
63
|
+
const collected = collectEventAttachmentRefs(events);
|
|
64
|
+
for (const value of iterableValues(collected)) {
|
|
65
|
+
if (!value)
|
|
66
|
+
continue;
|
|
67
|
+
const ref = attachmentRef(value);
|
|
68
|
+
if (ref === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
const id = attachmentIdOf(ref);
|
|
71
|
+
if (wanted.has(id) && !found.has(id))
|
|
72
|
+
found.set(id, ref);
|
|
73
|
+
}
|
|
74
|
+
if (found.size > 0)
|
|
75
|
+
stateStore.recordAttachments(session, [...found.values()]);
|
|
76
|
+
return found;
|
|
77
|
+
};
|
|
78
|
+
const warnAttachmentRecoveryFailure = (session, attachmentId, error) => {
|
|
79
|
+
const message = boundedMessage(error);
|
|
80
|
+
const warningKey = `${String(attachmentId)}:${message}`;
|
|
81
|
+
const key = weakKey(session);
|
|
82
|
+
if (key !== undefined && attachmentRecoveryWarnings.get(key) === warningKey)
|
|
83
|
+
return;
|
|
84
|
+
if (key !== undefined)
|
|
85
|
+
attachmentRecoveryWarnings.set(key, warningKey);
|
|
86
|
+
logger?.warn?.('vision-router: session attachment recovery failed id=%s error=%s', String(attachmentId), message);
|
|
87
|
+
};
|
|
88
|
+
const resolveAttachments = async (session, attachmentIds) => {
|
|
89
|
+
const requested = [
|
|
90
|
+
...new Set((Array.isArray(attachmentIds) ? attachmentIds : []).map((id) => String(id))),
|
|
91
|
+
];
|
|
92
|
+
const resolved = new Map();
|
|
93
|
+
const missing = [];
|
|
94
|
+
for (const id of requested) {
|
|
95
|
+
const cached = lookupAttachment(session, id);
|
|
96
|
+
if (cached !== undefined)
|
|
97
|
+
resolved.set(id, cached);
|
|
98
|
+
else
|
|
99
|
+
missing.push(id);
|
|
100
|
+
}
|
|
101
|
+
if (missing.length === 0 || session === undefined)
|
|
102
|
+
return resolved;
|
|
103
|
+
let events;
|
|
104
|
+
if (typeof readSessionLog === 'function') {
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = await readSessionLog(session);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
warnAttachmentRecoveryFailure(session, missing[0], error);
|
|
111
|
+
return resolved;
|
|
112
|
+
}
|
|
113
|
+
const resultRecord = propertyBag(result);
|
|
114
|
+
if (resultRecord?.supported === true) {
|
|
115
|
+
const key = weakKey(session);
|
|
116
|
+
if (key !== undefined)
|
|
117
|
+
attachmentRecoveryWarnings.delete(key);
|
|
118
|
+
events = resultRecord.events;
|
|
119
|
+
}
|
|
120
|
+
else if (resultRecord?.supported !== false) {
|
|
121
|
+
warnAttachmentRecoveryFailure(session, missing[0], new Error('Session log reader returned an invalid capability result'));
|
|
122
|
+
return resolved;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Explicitly unsupported SessionQuery keeps the released Session-local
|
|
126
|
+
// compatibility fallback. Advertised reader failures above never fall back.
|
|
127
|
+
if (events === undefined)
|
|
128
|
+
events = legacySessionEvents(session);
|
|
129
|
+
const recovered = recoverAttachmentsFromEvents(session, missing, events);
|
|
130
|
+
for (const [id, ref] of recovered)
|
|
131
|
+
resolved.set(id, ref);
|
|
132
|
+
return resolved;
|
|
133
|
+
};
|
|
134
|
+
const resolveAttachment = async (session, attachmentId) => {
|
|
135
|
+
return (await resolveAttachments(session, [attachmentId])).get(String(attachmentId));
|
|
136
|
+
};
|
|
137
|
+
return Object.freeze({
|
|
138
|
+
recordAttachments,
|
|
139
|
+
lookupAttachment,
|
|
140
|
+
resolveAttachment,
|
|
141
|
+
resolveAttachments,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { materializeDelegatedSessionVisionPolicy, } from './session-vision-delegation.js';
|
|
2
|
+
/**
|
|
3
|
+
* Materialize inherited DVR authority for exact runtime-owned child Agents.
|
|
4
|
+
*
|
|
5
|
+
* DSH awaits serial `agent/created` listeners before releasing queued work,
|
|
6
|
+
* so the snapshot is durable before the child can assemble its first prompt.
|
|
7
|
+
* Runtime ownership is revalidated with `agents.isOwnedBy`; ambient initiator
|
|
8
|
+
* presence alone is never treated as delegation authority.
|
|
9
|
+
*/
|
|
10
|
+
export function installSessionVisionDelegationBoundary(ctx, store, resolveParentAuthority) {
|
|
11
|
+
const currentInitiator = ctx.agents?.currentInitiator;
|
|
12
|
+
const isOwnedBy = ctx.agents?.isOwnedBy;
|
|
13
|
+
const on = ctx.on;
|
|
14
|
+
if (typeof currentInitiator !== 'function'
|
|
15
|
+
|| typeof isOwnedBy !== 'function'
|
|
16
|
+
|| typeof on !== 'function') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
on.call(ctx, 'agent/created', async ({ agent: child }) => {
|
|
20
|
+
if (typeof store.hydrate === 'function') {
|
|
21
|
+
await store.hydrate(child.id);
|
|
22
|
+
}
|
|
23
|
+
const parent = currentInitiator.call(ctx.agents);
|
|
24
|
+
if (parent === undefined || parent.id === child.id)
|
|
25
|
+
return;
|
|
26
|
+
if (!isOwnedBy.call(ctx.agents, child.id, parent))
|
|
27
|
+
return;
|
|
28
|
+
if (typeof store.hydrate === 'function') {
|
|
29
|
+
await store.hydrate(parent.id);
|
|
30
|
+
}
|
|
31
|
+
await materializeDelegatedSessionVisionPolicy(store, parent, child, resolveParentAuthority);
|
|
32
|
+
});
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { delegatedSessionVisionPolicy, } from './session-vision-policy.js';
|
|
2
|
+
/**
|
|
3
|
+
* Materialize one child-local DVR policy snapshot at the delegation boundary.
|
|
4
|
+
*
|
|
5
|
+
* Child-local durable truth wins when it already exists. Otherwise the direct
|
|
6
|
+
* parent's durable policy wins over route-derived compatibility authority, and
|
|
7
|
+
* the resulting enabled bit is copied into the child as a delegation snapshot.
|
|
8
|
+
* Later parent changes therefore cannot retroactively mutate an existing child.
|
|
9
|
+
*/
|
|
10
|
+
export async function materializeDelegatedSessionVisionPolicy(store, parent, child, resolveParentAuthority) {
|
|
11
|
+
if (parent.id === child.id) {
|
|
12
|
+
throw new TypeError('Session Vision delegation requires distinct parent and child ids');
|
|
13
|
+
}
|
|
14
|
+
const existing = store.get(child.id);
|
|
15
|
+
if (existing !== undefined) {
|
|
16
|
+
return Object.freeze({ status: 'existing', policy: existing });
|
|
17
|
+
}
|
|
18
|
+
const parentPolicy = store.get(parent.id);
|
|
19
|
+
const enabled = parentPolicy?.enabled ?? resolveParentAuthority(parent).enabled;
|
|
20
|
+
const policy = await store.set(child.id, delegatedSessionVisionPolicy(enabled, parent.id));
|
|
21
|
+
return Object.freeze({ status: 'materialized', policy });
|
|
22
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
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
|
|
6
8
|
const MAX_REPAIR_OVERFLOW_RANGES = 32
|
|
@@ -11,21 +13,6 @@ function isObject(value) {
|
|
|
11
13
|
return value !== null && typeof value === 'object'
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
function legacySessionEvents(session) {
|
|
15
|
-
try {
|
|
16
|
-
// dsh 0.1.2-alpha.4 removed the bare `session.events` array in favor of
|
|
17
|
-
// `session.snapshotEvents()`; both are handled so one build runs on both
|
|
18
|
-
// harness generations.
|
|
19
|
-
if (session && typeof session.snapshotEvents === 'function') {
|
|
20
|
-
const events = session.snapshotEvents()
|
|
21
|
-
return Array.isArray(events) ? events : undefined
|
|
22
|
-
}
|
|
23
|
-
return Array.isArray(session?.events) ? session.events : undefined
|
|
24
|
-
} catch {
|
|
25
|
-
return undefined
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
16
|
function surfaceNodes(session) {
|
|
30
17
|
try {
|
|
31
18
|
return Array.isArray(session?.surface?.nodes) ? session.surface.nodes : undefined
|
|
@@ -52,9 +39,10 @@ function isReplacementEvent(event) {
|
|
|
52
39
|
/**
|
|
53
40
|
* Session visual data-plane index.
|
|
54
41
|
*
|
|
55
|
-
* SessionVisionStateStore remains the bounded storage owner.
|
|
56
|
-
* target-only durable
|
|
57
|
-
*
|
|
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.
|
|
58
46
|
*
|
|
59
47
|
* Supported DSH Hosts expose the post-commit `session/event` feed. Production
|
|
60
48
|
* surface repair consumes only the exact frozen events observed on that feed;
|
|
@@ -83,9 +71,24 @@ export function createSessionVisionIndex({
|
|
|
83
71
|
if (!store || typeof store !== 'object') {
|
|
84
72
|
throw new TypeError('session vision index requires a state store')
|
|
85
73
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
:
|
|
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.
|
|
89
92
|
const toolSurfaceScans = new WeakMap()
|
|
90
93
|
const guardSurfaceScans = new WeakMap()
|
|
91
94
|
const pendingToolRepairEvents = new WeakMap()
|
|
@@ -97,93 +100,8 @@ export function createSessionVisionIndex({
|
|
|
97
100
|
const repairFeedOverflowWarnings = new WeakSet()
|
|
98
101
|
const repairFeedObserverWarnings = new WeakMap()
|
|
99
102
|
const surfaceFeedBackfills = new WeakMap()
|
|
100
|
-
const attachmentRecoveryWarnings = new WeakMap()
|
|
101
103
|
let surfaceEventFeedActive = false
|
|
102
104
|
|
|
103
|
-
const recordAttachments = (session, refs) => {
|
|
104
|
-
if (!session || !Array.isArray(refs) || refs.length === 0) return
|
|
105
|
-
store.recordAttachments(session, refs)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Synchronous lookup is cache-only. Projected short handles first warm this
|
|
109
|
-
// cache from current derived messages and must not trigger historical I/O.
|
|
110
|
-
const lookupAttachment = (session, id) => {
|
|
111
|
-
if (!primitiveLookup) return undefined
|
|
112
|
-
return primitiveLookup(session, id)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const recoverAttachmentsFromEvents = (session, ids, events) => {
|
|
116
|
-
const found = new Map()
|
|
117
|
-
if (!Array.isArray(events) || events.length === 0 || typeof core.collectEventAttachmentRefs !== 'function') {
|
|
118
|
-
return found
|
|
119
|
-
}
|
|
120
|
-
const wanted = new Set(ids.map((id) => String(id)))
|
|
121
|
-
for (const ref of core.collectEventAttachmentRefs(events)) {
|
|
122
|
-
if (!ref) continue
|
|
123
|
-
const id = String(ref.attachmentId ?? ref.id)
|
|
124
|
-
if (wanted.has(id) && !found.has(id)) found.set(id, ref)
|
|
125
|
-
}
|
|
126
|
-
if (found.size > 0) store.recordAttachments(session, [...found.values()])
|
|
127
|
-
return found
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const warnAttachmentRecoveryFailure = (session, id, error) => {
|
|
131
|
-
const message = boundedMessage(error)
|
|
132
|
-
const key = `${String(id)}:${message}`
|
|
133
|
-
if (attachmentRecoveryWarnings.get(session) === key) return
|
|
134
|
-
attachmentRecoveryWarnings.set(session, key)
|
|
135
|
-
logger?.warn?.(
|
|
136
|
-
'vision-router: session attachment recovery failed id=%s error=%s',
|
|
137
|
-
String(id),
|
|
138
|
-
message,
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const resolveAttachments = async (session, ids) => {
|
|
143
|
-
const requested = [...new Set((Array.isArray(ids) ? ids : []).map((id) => String(id)))]
|
|
144
|
-
const resolved = new Map()
|
|
145
|
-
const missing = []
|
|
146
|
-
for (const id of requested) {
|
|
147
|
-
const cached = lookupAttachment(session, id)
|
|
148
|
-
if (cached !== undefined) resolved.set(id, cached)
|
|
149
|
-
else missing.push(id)
|
|
150
|
-
}
|
|
151
|
-
if (missing.length === 0 || session === undefined) return resolved
|
|
152
|
-
|
|
153
|
-
let events
|
|
154
|
-
if (typeof readSessionLog === 'function') {
|
|
155
|
-
let result
|
|
156
|
-
try {
|
|
157
|
-
result = await readSessionLog(session)
|
|
158
|
-
} catch (error) {
|
|
159
|
-
warnAttachmentRecoveryFailure(session, missing[0], error)
|
|
160
|
-
return resolved
|
|
161
|
-
}
|
|
162
|
-
if (result?.supported === true) {
|
|
163
|
-
attachmentRecoveryWarnings.delete(session)
|
|
164
|
-
events = result.events
|
|
165
|
-
} else if (result?.supported !== false) {
|
|
166
|
-
warnAttachmentRecoveryFailure(
|
|
167
|
-
session,
|
|
168
|
-
missing[0],
|
|
169
|
-
new Error('Session log reader returned an invalid capability result'),
|
|
170
|
-
)
|
|
171
|
-
return resolved
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Historical/partial Hosts without SessionQuery keep the released fallback.
|
|
176
|
-
// Public rc8+ Hosts use readSessionLog above and never touch sync history.
|
|
177
|
-
if (events === undefined) events = legacySessionEvents(session)
|
|
178
|
-
const recovered = recoverAttachmentsFromEvents(session, missing, events)
|
|
179
|
-
for (const [id, ref] of recovered) resolved.set(id, ref)
|
|
180
|
-
return resolved
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const resolveAttachment = async (session, id) => {
|
|
184
|
-
return (await resolveAttachments(session, [id])).get(String(id))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
105
|
const pendingSurfaceScan = (scans, session) => {
|
|
188
106
|
if (!session) return undefined
|
|
189
107
|
const nodes = surfaceNodes(session)
|
|
@@ -732,7 +650,7 @@ export function createSessionVisionIndex({
|
|
|
732
650
|
if (typeof core.rewriteImageBlocks === 'function') {
|
|
733
651
|
const found = core.rewriteImageBlocks(messages)
|
|
734
652
|
if (Array.isArray(found?.attachments) && found.attachments.length > 0) {
|
|
735
|
-
|
|
653
|
+
recordAttachments(session, found.attachments)
|
|
736
654
|
}
|
|
737
655
|
}
|
|
738
656
|
await ensureSurfaceEventFeedBackfill(session)
|