dsh-vision-router 2.3.0 → 3.0.1
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 +18 -3
- package/README.zh.md +16 -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 +447 -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 +13 -3
- package/docs/releases/v3.0.0.md +85 -0
- package/docs/releases/v3.0.1.md +77 -0
- package/entry.js +1 -1
- package/index.js +110 -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 +11 -144
- 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 +33 -18
- 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 +30 -10
- 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 +16 -10
- 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 +55 -6
- 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 +42 -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-model-visibility-boundary-main.js +15 -16
- 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,30 @@
|
|
|
1
|
+
function propertyBag(value) {
|
|
2
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read the released Session-local event snapshot used only when the Host does
|
|
8
|
+
* not advertise the asynchronous SessionQuery capability.
|
|
9
|
+
*
|
|
10
|
+
* Durable-data compatibility is intentionally separate from the current Host
|
|
11
|
+
* floor: this helper feature-detects historical Session shapes at runtime and
|
|
12
|
+
* never claims that either shape exists through a static Host type.
|
|
13
|
+
*/
|
|
14
|
+
export function legacySessionEvents(session) {
|
|
15
|
+
const source = propertyBag(session);
|
|
16
|
+
if (source === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
try {
|
|
19
|
+
const snapshotEvents = source.snapshotEvents;
|
|
20
|
+
if (typeof snapshotEvents === 'function') {
|
|
21
|
+
const events = snapshotEvents.call(session);
|
|
22
|
+
return Array.isArray(events) ? events : undefined;
|
|
23
|
+
}
|
|
24
|
+
const events = source.events;
|
|
25
|
+
return Array.isArray(events) ? events : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const disposeSymbol = Symbol.dispose;
|
|
2
|
+
const SESSION_EVENT_TAIL_INITIAL_AFTER = 50;
|
|
3
|
+
const SESSION_EVENT_TAIL_MAX_EVENTS = 4096;
|
|
4
|
+
const SESSION_EVENT_TAIL_MAX_READS = 128;
|
|
5
|
+
function objectRecord(value) {
|
|
6
|
+
return value !== null && typeof value === 'object'
|
|
7
|
+
? value
|
|
8
|
+
: undefined;
|
|
9
|
+
}
|
|
10
|
+
function propertyBag(value) {
|
|
11
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
12
|
+
? value
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
function sessionIdOf(session) {
|
|
16
|
+
return propertyBag(session)?.id;
|
|
17
|
+
}
|
|
18
|
+
function sessionQueryOf(ctx) {
|
|
19
|
+
const context = objectRecord(ctx);
|
|
20
|
+
if (context === undefined)
|
|
21
|
+
return undefined;
|
|
22
|
+
// sessionQuery is an optional capability. Cordis service property reads may
|
|
23
|
+
// register a hard pending dependency outside inject(); Context#get is the
|
|
24
|
+
// non-owning capability probe and is the only supported lookup here.
|
|
25
|
+
try {
|
|
26
|
+
const get = context.get;
|
|
27
|
+
if (typeof get !== 'function')
|
|
28
|
+
return undefined;
|
|
29
|
+
return objectRecord(get.call(ctx, 'sessionQuery'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function sessionQueryCode(error) {
|
|
36
|
+
const code = propertyBag(error)?.code;
|
|
37
|
+
return typeof code === 'string' ? code : undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the narrow bounded Session event reader used by durable surface repair.
|
|
41
|
+
*
|
|
42
|
+
* The Host SessionQuery service is intentionally discovered by capability at
|
|
43
|
+
* runtime instead of imported as a concrete service graph. DVR owns only the
|
|
44
|
+
* small supported/unsupported reader port returned from this adapter.
|
|
45
|
+
*/
|
|
46
|
+
export function createSessionEventReader(ctx) {
|
|
47
|
+
return async function readSessionEvent(session, seqValue) {
|
|
48
|
+
const sessionId = sessionIdOf(session);
|
|
49
|
+
if (sessionId === undefined || sessionId === null)
|
|
50
|
+
return { supported: false };
|
|
51
|
+
if (typeof seqValue !== 'number'
|
|
52
|
+
|| !Number.isSafeInteger(seqValue)
|
|
53
|
+
|| seqValue < 0
|
|
54
|
+
|| Object.is(seqValue, -0)) {
|
|
55
|
+
throw new TypeError(`session event seq must be a non-negative safe integer, got ${String(seqValue)}`);
|
|
56
|
+
}
|
|
57
|
+
const seq = seqValue;
|
|
58
|
+
const query = sessionQueryOf(ctx);
|
|
59
|
+
const readEvent = query?.readEvent;
|
|
60
|
+
if (query === undefined || typeof readEvent !== 'function') {
|
|
61
|
+
return { supported: false };
|
|
62
|
+
}
|
|
63
|
+
const window = await readEvent.call(query, { sessionId, seq });
|
|
64
|
+
const event = propertyBag(window)?.target;
|
|
65
|
+
const eventRecord = objectRecord(event);
|
|
66
|
+
if (eventRecord === undefined) {
|
|
67
|
+
throw new Error(`sessionQuery.readEvent returned no target for seq ${seq}`);
|
|
68
|
+
}
|
|
69
|
+
if (eventRecord.seq !== undefined && eventRecord.seq !== seq) {
|
|
70
|
+
throw new Error(`sessionQuery.readEvent returned seq ${String(eventRecord.seq)} for requested seq ${seq}`);
|
|
71
|
+
}
|
|
72
|
+
return { supported: true, event };
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read raw Session events strictly after one known committed seq through the
|
|
77
|
+
* Host-owned bounded SessionQuery window contract.
|
|
78
|
+
*/
|
|
79
|
+
export function createSessionEventTailReader(ctx) {
|
|
80
|
+
let afterHint = SESSION_EVENT_TAIL_INITIAL_AFTER;
|
|
81
|
+
const readWindow = async (query, sessionId, seq) => {
|
|
82
|
+
let after = afterHint;
|
|
83
|
+
while (true) {
|
|
84
|
+
try {
|
|
85
|
+
const request = after > 0 ? { sessionId, seq, after } : { sessionId, seq };
|
|
86
|
+
const readEvent = query.readEvent;
|
|
87
|
+
if (typeof readEvent !== 'function') {
|
|
88
|
+
throw new TypeError('sessionQuery.readEvent is unavailable');
|
|
89
|
+
}
|
|
90
|
+
const window = await readEvent.call(query, request);
|
|
91
|
+
afterHint = after;
|
|
92
|
+
return { window, after };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (sessionQueryCode(error) !== 'SESSION_QUERY_INVALID_WINDOW' || after <= 0) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
after = after <= 1 ? 0 : Math.floor(after / 2);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
return async function readSessionEventTail(session, anchorSeqValue, options = {}) {
|
|
103
|
+
const sessionId = sessionIdOf(session);
|
|
104
|
+
if (sessionId === undefined || sessionId === null)
|
|
105
|
+
return { supported: false };
|
|
106
|
+
if (typeof anchorSeqValue !== 'number'
|
|
107
|
+
|| !Number.isSafeInteger(anchorSeqValue)
|
|
108
|
+
|| anchorSeqValue < 0
|
|
109
|
+
|| Object.is(anchorSeqValue, -0)) {
|
|
110
|
+
throw new TypeError(`session tail anchor seq must be a non-negative safe integer, got ${String(anchorSeqValue)}`);
|
|
111
|
+
}
|
|
112
|
+
const anchorSeq = anchorSeqValue;
|
|
113
|
+
const query = sessionQueryOf(ctx);
|
|
114
|
+
if (query === undefined || typeof query.readEvent !== 'function') {
|
|
115
|
+
return { supported: false };
|
|
116
|
+
}
|
|
117
|
+
const collect = propertyBag(options)?.collect !== false;
|
|
118
|
+
const events = [];
|
|
119
|
+
let capturedThroughSeq = anchorSeq;
|
|
120
|
+
let cursor = anchorSeq;
|
|
121
|
+
let inspected = 0;
|
|
122
|
+
let reads = 0;
|
|
123
|
+
while (reads < SESSION_EVENT_TAIL_MAX_READS) {
|
|
124
|
+
let result;
|
|
125
|
+
try {
|
|
126
|
+
result = await readWindow(query, sessionId, cursor);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (sessionQueryCode(error) === 'SESSION_QUERY_EVENT_NOT_FOUND'
|
|
130
|
+
&& cursor > anchorSeq) {
|
|
131
|
+
return {
|
|
132
|
+
supported: true,
|
|
133
|
+
events,
|
|
134
|
+
capturedThroughSeq,
|
|
135
|
+
truncated: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
reads += 1;
|
|
141
|
+
const window = propertyBag(result.window);
|
|
142
|
+
const target = window?.target;
|
|
143
|
+
const targetRecord = objectRecord(target);
|
|
144
|
+
if (targetRecord === undefined || targetRecord.seq !== cursor) {
|
|
145
|
+
throw new Error(`sessionQuery.readEvent returned an invalid target for seq ${cursor}`);
|
|
146
|
+
}
|
|
147
|
+
const windowEvents = window?.events;
|
|
148
|
+
if (!Array.isArray(windowEvents) || windowEvents.length === 0) {
|
|
149
|
+
throw new Error(`sessionQuery.readEvent returned no event window for seq ${cursor}`);
|
|
150
|
+
}
|
|
151
|
+
let expectedSeq = cursor;
|
|
152
|
+
let endSeq = cursor;
|
|
153
|
+
for (const event of windowEvents) {
|
|
154
|
+
const seqValue = propertyBag(event)?.seq;
|
|
155
|
+
if (typeof seqValue !== 'number'
|
|
156
|
+
|| !Number.isSafeInteger(seqValue)
|
|
157
|
+
|| seqValue !== expectedSeq) {
|
|
158
|
+
throw new Error(`sessionQuery.readEvent returned non-contiguous seq ${String(seqValue)}; expected ${expectedSeq}`);
|
|
159
|
+
}
|
|
160
|
+
const seq = seqValue;
|
|
161
|
+
expectedSeq += 1;
|
|
162
|
+
endSeq = seq;
|
|
163
|
+
if (seq <= capturedThroughSeq)
|
|
164
|
+
continue;
|
|
165
|
+
capturedThroughSeq = seq;
|
|
166
|
+
inspected += 1;
|
|
167
|
+
if (collect)
|
|
168
|
+
events.push(event);
|
|
169
|
+
if (inspected >= SESSION_EVENT_TAIL_MAX_EVENTS) {
|
|
170
|
+
return {
|
|
171
|
+
supported: true,
|
|
172
|
+
events,
|
|
173
|
+
capturedThroughSeq,
|
|
174
|
+
truncated: true,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const windowEndSeq = window?.endSeq;
|
|
179
|
+
if (typeof windowEndSeq === 'number'
|
|
180
|
+
&& Number.isSafeInteger(windowEndSeq)
|
|
181
|
+
&& windowEndSeq !== endSeq) {
|
|
182
|
+
throw new Error(`sessionQuery.readEvent returned endSeq ${String(windowEndSeq)} for window ending at ${endSeq}`);
|
|
183
|
+
}
|
|
184
|
+
if (result.after > 0) {
|
|
185
|
+
if (endSeq <= cursor || windowEvents.length < result.after + 1) {
|
|
186
|
+
return {
|
|
187
|
+
supported: true,
|
|
188
|
+
events,
|
|
189
|
+
capturedThroughSeq: Math.max(capturedThroughSeq, endSeq),
|
|
190
|
+
truncated: false,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
cursor = endSeq;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// readWindowMax: 0 is valid. Walk exact seqs and use EVENT_NOT_FOUND as
|
|
197
|
+
// the logical tail sentinel.
|
|
198
|
+
cursor += 1;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
supported: true,
|
|
202
|
+
events,
|
|
203
|
+
capturedThroughSeq,
|
|
204
|
+
truncated: true,
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Build the asynchronous Session-log reader used by cold attachment recovery
|
|
210
|
+
* and batched surface repair.
|
|
211
|
+
*
|
|
212
|
+
* Prefer the Host observation lease when available and always dispose the lease
|
|
213
|
+
* after copying out the raw event array. Older supported capability shapes keep
|
|
214
|
+
* the released readSession() path.
|
|
215
|
+
*/
|
|
216
|
+
export function createSessionLogReader(ctx) {
|
|
217
|
+
return async function readSessionLog(session) {
|
|
218
|
+
const sessionId = sessionIdOf(session);
|
|
219
|
+
if (sessionId === undefined || sessionId === null)
|
|
220
|
+
return { supported: false };
|
|
221
|
+
const query = sessionQueryOf(ctx);
|
|
222
|
+
if (query === undefined)
|
|
223
|
+
return { supported: false };
|
|
224
|
+
const observeSession = query.observeSession;
|
|
225
|
+
if (typeof observeSession === 'function') {
|
|
226
|
+
let observation;
|
|
227
|
+
try {
|
|
228
|
+
observation = await observeSession.call(query, sessionId, { projectionMode: 'none' });
|
|
229
|
+
const observationBag = propertyBag(observation);
|
|
230
|
+
const events = observationBag?.events;
|
|
231
|
+
if (!Array.isArray(events)) {
|
|
232
|
+
throw new Error('sessionQuery.observeSession returned no event log');
|
|
233
|
+
}
|
|
234
|
+
const observedId = propertyBag(observationBag?.header)?.id;
|
|
235
|
+
if (observedId !== undefined && observedId !== sessionId) {
|
|
236
|
+
throw new Error(`sessionQuery.observeSession returned session ${String(observedId)} for requested ${String(sessionId)}`);
|
|
237
|
+
}
|
|
238
|
+
return { supported: true, events };
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
const dispose = disposeSymbol === undefined
|
|
242
|
+
? undefined
|
|
243
|
+
: propertyBag(observation)?.[disposeSymbol];
|
|
244
|
+
if (typeof dispose === 'function')
|
|
245
|
+
dispose.call(observation);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const readSession = query.readSession;
|
|
249
|
+
if (typeof readSession !== 'function')
|
|
250
|
+
return { supported: false };
|
|
251
|
+
const snapshot = await readSession.call(query, sessionId);
|
|
252
|
+
const snapshotBag = propertyBag(snapshot);
|
|
253
|
+
const events = snapshotBag?.events;
|
|
254
|
+
if (!Array.isArray(events)) {
|
|
255
|
+
throw new Error('sessionQuery.readSession returned no event log');
|
|
256
|
+
}
|
|
257
|
+
const observedId = propertyBag(snapshotBag?.session)?.id;
|
|
258
|
+
if (observedId !== undefined && observedId !== sessionId) {
|
|
259
|
+
throw new Error(`sessionQuery.readSession returned session ${String(observedId)} for requested ${String(sessionId)}`);
|
|
260
|
+
}
|
|
261
|
+
return { supported: true, events };
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -1,41 +1,40 @@
|
|
|
1
|
+
function propertyBag(value) {
|
|
2
|
+
return value !== null && typeof value === 'object'
|
|
3
|
+
? value
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
1
6
|
function nonNegativeSafeInteger(value) {
|
|
2
|
-
|
|
7
|
+
return (typeof value === 'number'
|
|
8
|
+
&& Number.isSafeInteger(value)
|
|
9
|
+
&& value >= 0
|
|
10
|
+
&& !Object.is(value, -0));
|
|
3
11
|
}
|
|
4
|
-
|
|
5
12
|
/**
|
|
6
13
|
* Build the Host-owned Session surface replacement intent for one exact node.
|
|
7
14
|
*
|
|
8
15
|
* DSH Session format v3 renamed replacement endpoints from start/end to
|
|
9
|
-
* startSeq/endSeq, and reviewed format v4 keeps that
|
|
10
|
-
*
|
|
11
|
-
* not to a DSH package-version heuristic, so dispatch on session.header.version
|
|
12
|
-
* and refuse unknown future formats instead of guessing forward compatibility.
|
|
13
|
-
*
|
|
14
|
-
* Returns undefined when the Host does not expose a supported logical Session
|
|
15
|
-
* format. Callers can then keep request-local safety behavior while skipping
|
|
16
|
-
* optional durable surface hygiene.
|
|
16
|
+
* startSeq/endSeq, and reviewed format v4 keeps that shape. Unknown future
|
|
17
|
+
* formats fail closed instead of being guessed from package version.
|
|
17
18
|
*/
|
|
18
|
-
export function sessionSurfaceReplacementIntent(session,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
export function sessionSurfaceReplacementIntent(session, seqValue) {
|
|
20
|
+
if (!nonNegativeSafeInteger(seqValue)) {
|
|
21
|
+
throw new TypeError('session surface replacement seq must be a non-negative safe integer');
|
|
22
|
+
}
|
|
23
|
+
const seq = seqValue;
|
|
24
|
+
const version = propertyBag(propertyBag(session)?.header)?.version;
|
|
25
|
+
if (!nonNegativeSafeInteger(version))
|
|
26
|
+
return undefined;
|
|
27
|
+
if (version <= 2) {
|
|
28
|
+
return {
|
|
29
|
+
surfaceOp: { op: 'replace', start: seq, end: seq },
|
|
30
|
+
sourceEventSeqs: [seq],
|
|
31
|
+
};
|
|
30
32
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
sourceEventSeqs: [seq],
|
|
33
|
+
if (version === 3 || version === 4) {
|
|
34
|
+
return {
|
|
35
|
+
surfaceOp: { op: 'replace', startSeq: seq, endSeq: seq },
|
|
36
|
+
sourceEventSeqs: [seq],
|
|
37
|
+
};
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return undefined
|
|
39
|
+
return undefined;
|
|
41
40
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const OWNERSHIP = Object.freeze({
|
|
2
|
+
PLUGIN_OWNED: 'vision-router-owned',
|
|
3
|
+
NATIVE: 'native-image',
|
|
4
|
+
TEXT_ONLY: 'text-only',
|
|
5
|
+
UNKNOWN: 'unknown',
|
|
6
|
+
});
|
|
7
|
+
const KNOWN_OWNERSHIP = new Set(Object.values(OWNERSHIP));
|
|
8
|
+
function objectRecord(value) {
|
|
9
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
10
|
+
? value
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
function normalizedOwnership(policy) {
|
|
14
|
+
const source = objectRecord(policy);
|
|
15
|
+
if (source === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
return typeof source.ownership === 'string'
|
|
18
|
+
&& KNOWN_OWNERSHIP.has(source.ownership)
|
|
19
|
+
? source.ownership
|
|
20
|
+
: OWNERSHIP.UNKNOWN;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve one immutable Core-facing capability snapshot for the current Session.
|
|
24
|
+
*
|
|
25
|
+
* Image ownership and Vision-mode authority remain separate facts. Durable
|
|
26
|
+
* transcript preservation is fail-safe: every real Session policy preserves
|
|
27
|
+
* the user-owned image, while the step authority alone decides whether DVR
|
|
28
|
+
* automatic/tool surfaces are active.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveSessionSurfacePolicy({ visionPolicy, visionModeAuthority, config = {}, schemaBootstrapping = false, } = {}) {
|
|
31
|
+
const source = objectRecord(visionPolicy);
|
|
32
|
+
const authority = objectRecord(visionModeAuthority);
|
|
33
|
+
const value = objectRecord(config) ?? {};
|
|
34
|
+
const ownership = normalizedOwnership(source);
|
|
35
|
+
const pluginOwned = ownership === OWNERSHIP.PLUGIN_OWNED;
|
|
36
|
+
const visionModeEnabled = authority !== undefined
|
|
37
|
+
? authority.enabled === true
|
|
38
|
+
: source === undefined
|
|
39
|
+
? true
|
|
40
|
+
: pluginOwned;
|
|
41
|
+
const preserveRawImages = source !== undefined;
|
|
42
|
+
const rewriteCurrentImages = false;
|
|
43
|
+
const allowStructuredBootstrap = visionModeEnabled;
|
|
44
|
+
const allowGenericAutoMount = visionModeEnabled;
|
|
45
|
+
const surface = Object.freeze({
|
|
46
|
+
preserveRawImages,
|
|
47
|
+
rewriteCurrentImages,
|
|
48
|
+
visionTools: value.tool !== false && visionModeEnabled,
|
|
49
|
+
structuredBootstrap: value.structuredVisionBootstrap === true && visionModeEnabled,
|
|
50
|
+
genericAutoMount: value.autoActivateOnImage !== false && visionModeEnabled,
|
|
51
|
+
instantDescribe: value.instantDescribe !== false && visionModeEnabled,
|
|
52
|
+
});
|
|
53
|
+
const overrides = {};
|
|
54
|
+
if (schemaBootstrapping === true
|
|
55
|
+
&& source === undefined
|
|
56
|
+
&& value.tool === false) {
|
|
57
|
+
overrides.tool = true;
|
|
58
|
+
}
|
|
59
|
+
if (source !== undefined && value.rewriteImages !== false) {
|
|
60
|
+
overrides.rewriteImages = false;
|
|
61
|
+
}
|
|
62
|
+
if (source !== undefined && !visionModeEnabled) {
|
|
63
|
+
if (value.tool !== false)
|
|
64
|
+
overrides.tool = false;
|
|
65
|
+
if (value.instantDescribe !== false)
|
|
66
|
+
overrides.instantDescribe = false;
|
|
67
|
+
if (value.autoActivateOnImage !== false) {
|
|
68
|
+
overrides.autoActivateOnImage = false;
|
|
69
|
+
}
|
|
70
|
+
if (value.structuredVisionBootstrap !== false) {
|
|
71
|
+
overrides.structuredVisionBootstrap = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
ownership,
|
|
76
|
+
visionModeEnabled,
|
|
77
|
+
participates: source !== undefined,
|
|
78
|
+
preserveRawImages,
|
|
79
|
+
rewriteCurrentImages,
|
|
80
|
+
allowStructuredBootstrap,
|
|
81
|
+
allowGenericAutoMount,
|
|
82
|
+
surface,
|
|
83
|
+
legacyConfigOverrides: Object.freeze(overrides),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -1,117 +1,8 @@
|
|
|
1
1
|
import { currentSessionVisionPolicy } from './native-image-coexistence.js'
|
|
2
2
|
import { currentSessionVisionModeAuthority } from './session-vision-mode-authority.js'
|
|
3
|
+
import { resolveSessionSurfacePolicy } from './session-surface-policy-domain.js'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
PLUGIN_OWNED: 'vision-router-owned',
|
|
6
|
-
NATIVE: 'native-image',
|
|
7
|
-
TEXT_ONLY: 'text-only',
|
|
8
|
-
UNKNOWN: 'unknown',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
const KNOWN_OWNERSHIP = new Set(Object.values(OWNERSHIP))
|
|
12
|
-
|
|
13
|
-
function isObject(value) {
|
|
14
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizedOwnership(policy) {
|
|
18
|
-
if (!isObject(policy)) return undefined
|
|
19
|
-
return KNOWN_OWNERSHIP.has(policy.ownership) ? policy.ownership : OWNERSHIP.UNKNOWN
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Resolve one immutable, read-only snapshot of the Vision Router capability
|
|
24
|
-
* surface for the current session.
|
|
25
|
-
*
|
|
26
|
-
* Image ownership and Vision-mode authority are intentionally distinct. The
|
|
27
|
-
* native-image policy answers who can consume raw pixels; the mode authority
|
|
28
|
-
* answers whether this Session explicitly selected a Vision Router-owned
|
|
29
|
-
* wrapper/twin for the current step. Only the latter can grant plugin tools or
|
|
30
|
-
* automatic visual work. This prevents a historical image, a native multimodal
|
|
31
|
-
* source model, or a stale request header from silently turning Vision back on.
|
|
32
|
-
*
|
|
33
|
-
* Durable transcript invariant: once a real session policy exists, Core may
|
|
34
|
-
* observe/index image blocks but may never replace a user-owned image message
|
|
35
|
-
* before the Agent loop appends it to the Session log. Image projection belongs
|
|
36
|
-
* at the adapter/request boundary. The historical `rewriteCurrentImages` bit is
|
|
37
|
-
* therefore intentionally ignored even when an older policy producer still
|
|
38
|
-
* exposes it; the compatibility field below is permanently false.
|
|
39
|
-
*/
|
|
40
|
-
export function resolveSessionSurfacePolicy({
|
|
41
|
-
visionPolicy,
|
|
42
|
-
visionModeAuthority,
|
|
43
|
-
config = {},
|
|
44
|
-
schemaBootstrapping = false,
|
|
45
|
-
} = {}) {
|
|
46
|
-
const source = isObject(visionPolicy) ? visionPolicy : undefined
|
|
47
|
-
const authority = isObject(visionModeAuthority) ? visionModeAuthority : undefined
|
|
48
|
-
const value = isObject(config) ? config : {}
|
|
49
|
-
const ownership = normalizedOwnership(source)
|
|
50
|
-
const pluginOwned = ownership === OWNERSHIP.PLUGIN_OWNED
|
|
51
|
-
|
|
52
|
-
// During schema bootstrap there is no real Session policy, so global Settings
|
|
53
|
-
// still decide which definitions Core registers. During a real step, an
|
|
54
|
-
// explicit authority snapshot wins; direct helper callers fall back to the
|
|
55
|
-
// already-classified plugin ownership instead of inventing an enabled mode.
|
|
56
|
-
const visionModeEnabled = authority !== undefined
|
|
57
|
-
? authority.enabled === true
|
|
58
|
-
: source === undefined
|
|
59
|
-
? true
|
|
60
|
-
: pluginOwned
|
|
61
|
-
|
|
62
|
-
// No active session policy means no session-specific preservation authority.
|
|
63
|
-
// During a real pre-step, every ownership result — including explicit
|
|
64
|
-
// text-only and unknown — preserves the durable user message unchanged.
|
|
65
|
-
const preserveRawImages = source !== undefined
|
|
66
|
-
const rewriteCurrentImages = false
|
|
67
|
-
const allowStructuredBootstrap = visionModeEnabled
|
|
68
|
-
const allowGenericAutoMount = visionModeEnabled
|
|
69
|
-
|
|
70
|
-
const surface = Object.freeze({
|
|
71
|
-
preserveRawImages,
|
|
72
|
-
rewriteCurrentImages,
|
|
73
|
-
visionTools: value.tool !== false && visionModeEnabled,
|
|
74
|
-
structuredBootstrap:
|
|
75
|
-
value.structuredVisionBootstrap === true && visionModeEnabled,
|
|
76
|
-
genericAutoMount:
|
|
77
|
-
value.autoActivateOnImage !== false && visionModeEnabled,
|
|
78
|
-
instantDescribe:
|
|
79
|
-
value.instantDescribe !== false && visionModeEnabled,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
const overrides = {}
|
|
83
|
-
if (schemaBootstrapping === true && source === undefined && value.tool === false) {
|
|
84
|
-
overrides.tool = true
|
|
85
|
-
}
|
|
86
|
-
// Core's historical rewriteImages switch controls an agent/pre-step message
|
|
87
|
-
// transform. Disable it for every real session policy so neither current nor
|
|
88
|
-
// historical user image blocks can be persisted as internal attachment text.
|
|
89
|
-
// Vision Router-owned adapters still perform their private request projection.
|
|
90
|
-
if (source !== undefined && value.rewriteImages !== false) overrides.rewriteImages = false
|
|
91
|
-
|
|
92
|
-
// The composer/model selection is the Session authority. When it is OFF,
|
|
93
|
-
// suppress every automatic/plugin-owned visual surface while preserving the
|
|
94
|
-
// durable image itself. Settings remain unchanged; these are turn-local Core
|
|
95
|
-
// projections only.
|
|
96
|
-
if (source !== undefined && !visionModeEnabled) {
|
|
97
|
-
if (value.tool !== false) overrides.tool = false
|
|
98
|
-
if (value.instantDescribe !== false) overrides.instantDescribe = false
|
|
99
|
-
if (value.autoActivateOnImage !== false) overrides.autoActivateOnImage = false
|
|
100
|
-
if (value.structuredVisionBootstrap !== false) overrides.structuredVisionBootstrap = false
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return Object.freeze({
|
|
104
|
-
ownership,
|
|
105
|
-
visionModeEnabled,
|
|
106
|
-
participates: source !== undefined,
|
|
107
|
-
preserveRawImages,
|
|
108
|
-
rewriteCurrentImages,
|
|
109
|
-
allowStructuredBootstrap,
|
|
110
|
-
allowGenericAutoMount,
|
|
111
|
-
surface,
|
|
112
|
-
legacyConfigOverrides: Object.freeze(overrides),
|
|
113
|
-
})
|
|
114
|
-
}
|
|
5
|
+
export { resolveSessionSurfacePolicy } from './session-surface-policy-domain.js'
|
|
115
6
|
|
|
116
7
|
/** Read the turn-local ownership + mode snapshots and resolve the Core surface. */
|
|
117
8
|
export function currentSessionSurfacePolicy(config = {}, options = {}) {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
function projectionServiceOf(ctx) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
if (service && typeof service.stateOf === 'function') return service
|
|
5
|
-
} catch {
|
|
6
|
-
// Some compatibility contexts expose services only through get().
|
|
7
|
-
}
|
|
2
|
+
// sessionProjections is optional here; probing the Cordis service property
|
|
3
|
+
// outside inject() would turn this reader into a hard pending dependency.
|
|
8
4
|
try {
|
|
9
5
|
const service = ctx?.get?.('sessionProjections')
|
|
10
6
|
return service && typeof service.stateOf === 'function' ? service : undefined
|