@sema-agent/client-core 0.101.0 → 0.102.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/CHANGELOG.md +57 -0
- package/README.md +12 -7
- package/dist/adapt/toolCards.d.ts +2 -1
- package/dist/adapt/toolCards.js +20 -6
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +1 -1
- package/dist/adapter/downstream/eventToSdkMessage.js +6 -68
- package/dist/adapter/downstream/wiringManifestView.d.ts +1 -0
- package/dist/adapter/downstream/wiringManifestView.js +138 -0
- package/dist/compensations.js +1 -1
- package/dist/displayUntrusted.d.ts +8 -4
- package/dist/displayUntrusted.js +769 -96
- package/dist/engineRowRef.js +1 -0
- package/dist/fleet/fleetProjection.js +1 -1
- package/dist/i18n/messages.en.d.ts +38 -0
- package/dist/i18n/messages.en.js +38 -0
- package/dist/i18n/messages.zh.js +40 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +5 -0
- package/dist/interactiveHalt.d.ts +3 -1
- package/dist/interactiveHalt.js +2 -0
- package/dist/interactiveInterrupt.d.ts +2 -0
- package/dist/interactiveInterrupt.js +15 -0
- package/dist/modsFace.d.ts +8 -0
- package/dist/modsFace.js +59 -0
- package/dist/ownKey.d.ts +1 -0
- package/dist/ownKey.js +15 -0
- package/dist/printToolResultFrame.js +1 -1
- package/dist/request/printNotification.js +1 -1
- package/dist/request/taskRequest.d.ts +21 -0
- package/dist/request/taskRequest.js +123 -7
- package/dist/seam.js +1 -1
- package/dist/seatContract.d.ts +22 -6
- package/dist/seatContract.js +35 -6
- package/dist/sessionBackground.d.ts +165 -0
- package/dist/sessionBackground.js +781 -0
- package/dist/sessionBackgroundHandoff.d.ts +54 -0
- package/dist/sessionBackgroundHandoff.js +321 -0
- package/dist/sessionMap.js +4 -1
- package/dist/settingsCredentialKeyPaths.js +2 -17
- package/dist/thinkingLatch.d.ts +69 -0
- package/dist/thinkingLatch.js +255 -0
- package/dist/thinkingLatchWords.d.ts +7 -0
- package/dist/thinkingLatchWords.js +4 -0
- package/dist/toolResult.js +2 -24
- package/docs/INTEGRATION-CLIENTS.md +309 -10
- package/package.json +1 -1
- package/dist/argvFlagValue.d.ts +0 -8
- package/dist/argvFlagValue.js +0 -37
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
import { createEngineCapReader } from './engineCapReader.js';
|
|
2
|
+
import { engineCapsGeneration } from './engineCapsCache.js';
|
|
3
|
+
import { engineWireTargetBaseUrl } from './engineWireTarget.js';
|
|
4
|
+
import { DETACH_HEADER, HEADLESS_DETACH_ENV, headlessDetachDisabledByEnv, isDetachDurableOff400 } from './detachWire.js';
|
|
5
|
+
import { isLoopbackWireUrl } from './engineWireSdk.js';
|
|
6
|
+
import { hostEnv } from './hostEnv.js';
|
|
7
|
+
import { approvalDecisionHolds } from './hitl/approvalHolds.js';
|
|
8
|
+
import { readRunTerminal } from './runTerminal.js';
|
|
9
|
+
import { appendOwnElement, ownDataField, putOwnKey, readOwnDataFiniteNumber, readOwnDataNonEmptyString, readOwnDataValue } from './ownKey.js';
|
|
10
|
+
import { msg, renderMessage } from './i18n/translate.js';
|
|
11
|
+
export function projectSessionBackgroundable(caps) {
|
|
12
|
+
try {
|
|
13
|
+
if (caps === null || typeof caps !== 'object' || Array.isArray(caps))
|
|
14
|
+
return undefined;
|
|
15
|
+
const v = ownDataField(caps, 'sessionBackgroundable');
|
|
16
|
+
if (v === undefined)
|
|
17
|
+
return { kind: 'not_reported' };
|
|
18
|
+
if (v === true)
|
|
19
|
+
return { kind: 'advertised' };
|
|
20
|
+
if (v === false)
|
|
21
|
+
return { kind: 'declined' };
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const reader = createEngineCapReader({ name: 'sessionBackgroundable', project: projectSessionBackgroundable });
|
|
29
|
+
export function readSessionBackgroundable(baseUrl = engineWireTargetBaseUrl()) {
|
|
30
|
+
try {
|
|
31
|
+
return reader.observed(baseUrl);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return Object.freeze({ kind: 'unobserved' });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const OPENING_RECORD_CAP = 256;
|
|
38
|
+
const wireStates = new WeakMap();
|
|
39
|
+
const isObj = (v) => (typeof v === 'object' && v !== null) || typeof v === 'function';
|
|
40
|
+
export function ownSlotOf(o, key) {
|
|
41
|
+
try {
|
|
42
|
+
const d = Object.getOwnPropertyDescriptor(o, key);
|
|
43
|
+
if (d === undefined)
|
|
44
|
+
return { state: 'absent' };
|
|
45
|
+
if (!Object.hasOwn(d, 'value'))
|
|
46
|
+
return { state: 'unreadable' };
|
|
47
|
+
const v = d.value;
|
|
48
|
+
return v === undefined ? { state: 'absent' } : { state: 'data', value: v };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { state: 'unreadable' };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function wireStateOf(wire) {
|
|
55
|
+
try {
|
|
56
|
+
return typeof wire === 'object' && wire !== null ? wireStates.get(wire) : undefined;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function methodOf(o, key) {
|
|
63
|
+
try {
|
|
64
|
+
let cur = o;
|
|
65
|
+
for (let depth = 0; cur !== null && depth < 32; depth++) {
|
|
66
|
+
const d = Object.getOwnPropertyDescriptor(cur, key);
|
|
67
|
+
if (d !== undefined) {
|
|
68
|
+
if (!Object.hasOwn(d, 'value'))
|
|
69
|
+
return undefined;
|
|
70
|
+
return typeof d.value === 'function' ? d.value : undefined;
|
|
71
|
+
}
|
|
72
|
+
cur = Object.getPrototypeOf(cur);
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function guardedGet(o, key) {
|
|
81
|
+
try {
|
|
82
|
+
return o[key];
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function interactiveEngineWire(init) {
|
|
89
|
+
try {
|
|
90
|
+
if (!isObj(init))
|
|
91
|
+
return undefined;
|
|
92
|
+
const baseUrl = readOwnDataValue(init, 'baseUrl');
|
|
93
|
+
const lane = readOwnDataValue(init, 'lane');
|
|
94
|
+
const tasks = readOwnDataValue(init, 'tasks');
|
|
95
|
+
const runs = readOwnDataValue(init, 'runs');
|
|
96
|
+
if (typeof baseUrl !== 'string' || baseUrl === '')
|
|
97
|
+
return undefined;
|
|
98
|
+
if (lane !== 'self-spawned' && lane !== 'external')
|
|
99
|
+
return undefined;
|
|
100
|
+
if (!isObj(tasks) || !isObj(runs))
|
|
101
|
+
return undefined;
|
|
102
|
+
const streamFn = methodOf(tasks, 'stream');
|
|
103
|
+
const eventsFn = methodOf(runs, 'events');
|
|
104
|
+
const getFn = methodOf(runs, 'get');
|
|
105
|
+
if (typeof streamFn !== 'function' || typeof eventsFn !== 'function' || typeof getFn !== 'function')
|
|
106
|
+
return undefined;
|
|
107
|
+
const stream = streamFn;
|
|
108
|
+
const events = eventsFn;
|
|
109
|
+
const get = getFn;
|
|
110
|
+
const state = {
|
|
111
|
+
retired: false,
|
|
112
|
+
baseUrl,
|
|
113
|
+
lane,
|
|
114
|
+
stream: (req, opts) => stream.call(tasks, req, opts),
|
|
115
|
+
events: (taskId, opts) => events.call(runs, taskId, opts),
|
|
116
|
+
get: (taskId, opts) => get.call(runs, taskId, opts),
|
|
117
|
+
bySession: new Map(),
|
|
118
|
+
byTask: new Map(),
|
|
119
|
+
order: [],
|
|
120
|
+
handedOff: new Map(),
|
|
121
|
+
nextOpeningId: 1,
|
|
122
|
+
};
|
|
123
|
+
const handle = Object.freeze({ baseUrl, lane });
|
|
124
|
+
wireStates.set(handle, state);
|
|
125
|
+
return handle;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const isTerminalPhase = (p) => p !== 'opening' && p !== 'bound' && p !== 'running';
|
|
132
|
+
const isHandedOffPhase = (p) => p === 'handing-off' || p === 'handed-off';
|
|
133
|
+
export function retireInteractiveEngineWire(wire) {
|
|
134
|
+
const state = wireStateOf(wire);
|
|
135
|
+
if (state === undefined || state.retired)
|
|
136
|
+
return;
|
|
137
|
+
state.retired = true;
|
|
138
|
+
for (const rec of state.order)
|
|
139
|
+
if (!isTerminalPhase(rec.phase))
|
|
140
|
+
rec.phase = 'engine-ended';
|
|
141
|
+
for (const entry of [...state.handedOff.values()]) {
|
|
142
|
+
if (entry.state === 'ended')
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
entry.stop('engine-ended');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function snapshotOpeningInput(input) {
|
|
152
|
+
let sessionKey;
|
|
153
|
+
try {
|
|
154
|
+
if (!isObj(input))
|
|
155
|
+
return { readable: false, sessionKey: undefined, reattachable: false, envOff: false };
|
|
156
|
+
const sk = readOwnDataValue(input, 'sessionKey');
|
|
157
|
+
sessionKey = typeof sk === 'string' && sk !== '' ? sk : undefined;
|
|
158
|
+
const reattachable = readOwnDataValue(input, 'reattachable') === true;
|
|
159
|
+
const envSlot = ownSlotOf(input, 'env');
|
|
160
|
+
if (envSlot.state === 'unreadable')
|
|
161
|
+
return { readable: false, sessionKey, reattachable, envOff: false };
|
|
162
|
+
if (envSlot.state === 'data' && !isObj(envSlot.value))
|
|
163
|
+
return { readable: false, sessionKey, reattachable, envOff: false };
|
|
164
|
+
const env = envSlot.state === 'data' && isObj(envSlot.value) ? envSlot.value : hostEnv();
|
|
165
|
+
const switchSlot = ownSlotOf(env, HEADLESS_DETACH_ENV);
|
|
166
|
+
if (switchSlot.state === 'unreadable')
|
|
167
|
+
return { readable: false, sessionKey, reattachable, envOff: false };
|
|
168
|
+
const raw = switchSlot.state === 'data' ? switchSlot.value : undefined;
|
|
169
|
+
if (raw !== undefined && typeof raw !== 'string')
|
|
170
|
+
return { readable: false, sessionKey, reattachable, envOff: false };
|
|
171
|
+
const one = {};
|
|
172
|
+
if (raw !== undefined)
|
|
173
|
+
one[HEADLESS_DETACH_ENV] = raw;
|
|
174
|
+
const envOff = headlessDetachDisabledByEnv(one);
|
|
175
|
+
return { readable: sessionKey !== undefined, sessionKey, reattachable, envOff };
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return { readable: false, sessionKey, reattachable: false, envOff: false };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function capabilityKindAt(baseUrl) {
|
|
182
|
+
return readSessionBackgroundable(baseUrl).kind;
|
|
183
|
+
}
|
|
184
|
+
const noStamp = (reason) => Object.freeze({ stamp: false, reason });
|
|
185
|
+
function postureFor(state, snap) {
|
|
186
|
+
if (state === undefined || !snap.readable)
|
|
187
|
+
return noStamp('unreadable-input');
|
|
188
|
+
if (state.retired)
|
|
189
|
+
return noStamp('retired-wire');
|
|
190
|
+
if (snap.envOff)
|
|
191
|
+
return noStamp('env-off');
|
|
192
|
+
if (!snap.reattachable)
|
|
193
|
+
return noStamp('not-reattachable');
|
|
194
|
+
const cap = capabilityKindAt(state.baseUrl);
|
|
195
|
+
if (cap === 'declined')
|
|
196
|
+
return noStamp('declined');
|
|
197
|
+
if (state.lane === 'self-spawned' && isLoopbackWireUrl(state.baseUrl))
|
|
198
|
+
return Object.freeze({ stamp: true, reason: 'self-spawned' });
|
|
199
|
+
if (cap === 'advertised')
|
|
200
|
+
return noStamp('unacknowledged');
|
|
201
|
+
return noStamp(cap);
|
|
202
|
+
}
|
|
203
|
+
export function interactiveDetachPosture(wire, input) {
|
|
204
|
+
try {
|
|
205
|
+
return postureFor(wireStateOf(wire), snapshotOpeningInput(input));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return noStamp('unreadable-input');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function ownDataEntries(o, strict = false) {
|
|
212
|
+
let keys;
|
|
213
|
+
try {
|
|
214
|
+
keys = Reflect.ownKeys(o);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const out = [];
|
|
220
|
+
for (const k of keys) {
|
|
221
|
+
if (typeof k !== 'string')
|
|
222
|
+
continue;
|
|
223
|
+
try {
|
|
224
|
+
const d = Object.getOwnPropertyDescriptor(o, k);
|
|
225
|
+
if (d === undefined || d.enumerable !== true)
|
|
226
|
+
continue;
|
|
227
|
+
if (Object.hasOwn(d, 'value'))
|
|
228
|
+
appendOwnElement(out, [k, d.value]);
|
|
229
|
+
else if (strict)
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
if (strict)
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
function snapshotRequestBody(req) {
|
|
240
|
+
try {
|
|
241
|
+
if (typeof req !== 'object' || req === null || Array.isArray(req))
|
|
242
|
+
return undefined;
|
|
243
|
+
const entries = ownDataEntries(req, true);
|
|
244
|
+
if (entries === undefined)
|
|
245
|
+
return undefined;
|
|
246
|
+
const out = {};
|
|
247
|
+
for (const [k, v] of entries)
|
|
248
|
+
Object.defineProperty(out, k, { value: v, enumerable: true, writable: true, configurable: true });
|
|
249
|
+
return Object.freeze(out);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function snapshotHeaders(raw) {
|
|
256
|
+
const out = {};
|
|
257
|
+
if (!isObj(raw))
|
|
258
|
+
return out;
|
|
259
|
+
for (const [k, v] of ownDataEntries(raw) ?? []) {
|
|
260
|
+
if (typeof v !== 'string')
|
|
261
|
+
continue;
|
|
262
|
+
if (k.toLowerCase() === DETACH_HEADER)
|
|
263
|
+
continue;
|
|
264
|
+
putOwnKey(out, k, v);
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
function snapshotStreamOptions(opts) {
|
|
269
|
+
const read = (key) => {
|
|
270
|
+
try {
|
|
271
|
+
return isObj(opts) ? readOwnDataValue(opts, key) : undefined;
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const signal = read('signal');
|
|
278
|
+
const onResponse = read('onResponse');
|
|
279
|
+
const idem = read('idempotencyKey');
|
|
280
|
+
const grace = read('heartbeatGraceMs');
|
|
281
|
+
return {
|
|
282
|
+
signal: isObj(signal) ? signal : undefined,
|
|
283
|
+
headers: snapshotHeaders(read('headers')),
|
|
284
|
+
onResponse: typeof onResponse === 'function' ? onResponse : undefined,
|
|
285
|
+
hasIdempotencyKey: typeof idem === 'string' || idem === null,
|
|
286
|
+
idempotencyKey: typeof idem === 'string' || idem === null ? idem : undefined,
|
|
287
|
+
heartbeatGraceMs: typeof grace === 'number' && Number.isFinite(grace) ? grace : undefined,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function outboundHeaders(host, stamp) {
|
|
291
|
+
const out = {};
|
|
292
|
+
for (const [k, v] of Object.entries(host))
|
|
293
|
+
if (k.toLowerCase() !== DETACH_HEADER)
|
|
294
|
+
putOwnKey(out, k, v);
|
|
295
|
+
if (stamp)
|
|
296
|
+
putOwnKey(out, DETACH_HEADER, 'true');
|
|
297
|
+
return Object.freeze(out);
|
|
298
|
+
}
|
|
299
|
+
function registerOpening(state, rec) {
|
|
300
|
+
state.order.push(rec);
|
|
301
|
+
if (rec.sessionKey !== undefined)
|
|
302
|
+
state.bySession.set(rec.sessionKey, rec);
|
|
303
|
+
trimOpenings(state);
|
|
304
|
+
}
|
|
305
|
+
function liveForRetention(state, rec) {
|
|
306
|
+
if (!isTerminalPhase(rec.phase))
|
|
307
|
+
return true;
|
|
308
|
+
if (!isHandedOffPhase(rec.phase) || rec.taskId === undefined)
|
|
309
|
+
return false;
|
|
310
|
+
const entry = state.handedOff.get(rec.taskId);
|
|
311
|
+
return entry !== undefined && entry.state !== 'ended';
|
|
312
|
+
}
|
|
313
|
+
function trimOpenings(state) {
|
|
314
|
+
let i = 0;
|
|
315
|
+
while (state.order.length > OPENING_RECORD_CAP && i < state.order.length) {
|
|
316
|
+
const rec = state.order[i];
|
|
317
|
+
if (rec === undefined || liveForRetention(state, rec)) {
|
|
318
|
+
i++;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
state.order.splice(i, 1);
|
|
322
|
+
if (rec.taskId !== undefined && state.byTask.get(rec.taskId) === rec)
|
|
323
|
+
state.byTask.delete(rec.taskId);
|
|
324
|
+
if (rec.sessionKey !== undefined && state.bySession.get(rec.sessionKey) === rec)
|
|
325
|
+
state.bySession.delete(rec.sessionKey);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function taskIdHeaderOf(res) {
|
|
329
|
+
try {
|
|
330
|
+
const h = guardedGet(res, 'headers');
|
|
331
|
+
if (!isObj(h))
|
|
332
|
+
return undefined;
|
|
333
|
+
const get = guardedGet(h, 'get');
|
|
334
|
+
if (typeof get !== 'function')
|
|
335
|
+
return undefined;
|
|
336
|
+
const v = get.call(h, 'x-task-id');
|
|
337
|
+
return typeof v === 'string' && v !== '' ? v : undefined;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function noteResponse(state, rec, res, hostHook) {
|
|
344
|
+
const taskId = taskIdHeaderOf(res);
|
|
345
|
+
if (taskId !== undefined && rec.phase === 'opening') {
|
|
346
|
+
rec.taskId = taskId;
|
|
347
|
+
rec.capsGenAtBind = engineCapsGeneration(state.baseUrl);
|
|
348
|
+
rec.phase = 'bound';
|
|
349
|
+
if (!state.byTask.has(taskId))
|
|
350
|
+
state.byTask.set(taskId, rec);
|
|
351
|
+
}
|
|
352
|
+
if (hostHook !== undefined) {
|
|
353
|
+
try {
|
|
354
|
+
hostHook(res);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function frameSlot(ev, key) {
|
|
361
|
+
try {
|
|
362
|
+
return readOwnDataValue(ev, key);
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function frameString(ev, key) {
|
|
369
|
+
try {
|
|
370
|
+
return readOwnDataNonEmptyString(ev, key);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function askDeadlineOf(ev) {
|
|
377
|
+
try {
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
const rel = readOwnDataFiniteNumber(ev, 'expiresInMs');
|
|
380
|
+
if (rel !== undefined)
|
|
381
|
+
return now + rel;
|
|
382
|
+
const at = readOwnDataFiniteNumber(ev, 'expiresAtMs');
|
|
383
|
+
if (at === undefined)
|
|
384
|
+
return undefined;
|
|
385
|
+
const serverNow = readOwnDataFiniteNumber(ev, 'serverNowMs');
|
|
386
|
+
return serverNow !== undefined ? now + (at - serverNow) : at;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function isParkedDone(ev) {
|
|
393
|
+
try {
|
|
394
|
+
return readRunTerminal(frameSlot(ev, 'result'))?.kind === 'paused';
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function observeFrame(rec, ev) {
|
|
401
|
+
if (isTerminalPhase(rec.phase))
|
|
402
|
+
return;
|
|
403
|
+
const type = frameSlot(ev, 'type');
|
|
404
|
+
if (type === 'meta') {
|
|
405
|
+
const sessionId = frameString(ev, 'sessionId');
|
|
406
|
+
if (sessionId !== undefined && rec.sessionId === undefined)
|
|
407
|
+
rec.sessionId = sessionId;
|
|
408
|
+
const metaTaskId = frameString(ev, 'taskId');
|
|
409
|
+
if (metaTaskId !== undefined && rec.taskId !== undefined && metaTaskId !== rec.taskId)
|
|
410
|
+
rec.inconsistent = true;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (type === 'done' || type === 'failed') {
|
|
414
|
+
if (rec.phase === 'running') {
|
|
415
|
+
rec.phase = type === 'done' && isParkedDone(ev) ? 'stream-ended' : 'finished';
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
rec.phase = 'ended-unstarted';
|
|
419
|
+
rec.replay = type === 'done' && frameSlot(ev, 'replay') === true;
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (type === 'question') {
|
|
424
|
+
const questionId = frameString(ev, 'questionId');
|
|
425
|
+
if (questionId !== undefined)
|
|
426
|
+
rec.openAsks.set(questionId, askDeadlineOf(ev));
|
|
427
|
+
}
|
|
428
|
+
else if (type === 'question_complete') {
|
|
429
|
+
const questionId = frameString(ev, 'questionId');
|
|
430
|
+
if (questionId !== undefined)
|
|
431
|
+
rec.openAsks.delete(questionId);
|
|
432
|
+
}
|
|
433
|
+
if (rec.phase === 'bound' && rec.sessionId !== undefined && frameString(ev, 'id') !== undefined)
|
|
434
|
+
rec.phase = 'running';
|
|
435
|
+
}
|
|
436
|
+
function isAskFrame(ev) {
|
|
437
|
+
const type = frameSlot(ev, 'type');
|
|
438
|
+
return type === 'question' || type === 'approval_request';
|
|
439
|
+
}
|
|
440
|
+
async function* endedWith(error) {
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
export function streamInteractiveRun(wire, req, opts, input) {
|
|
444
|
+
const state = wireStateOf(wire);
|
|
445
|
+
const inSnap = snapshotOpeningInput(input);
|
|
446
|
+
const body = snapshotRequestBody(req);
|
|
447
|
+
const o = snapshotStreamOptions(opts);
|
|
448
|
+
if (state === undefined)
|
|
449
|
+
return endedWith(new TypeError('streamInteractiveRun: the engine wire was not minted by this package copy'));
|
|
450
|
+
const posture = postureFor(state, inSnap);
|
|
451
|
+
const headers = outboundHeaders(o.headers, posture.stamp);
|
|
452
|
+
const stamped = Object.hasOwn(headers, DETACH_HEADER) && headers[DETACH_HEADER] === 'true';
|
|
453
|
+
const controller = new AbortController();
|
|
454
|
+
const rec = {
|
|
455
|
+
openingId: state.nextOpeningId++,
|
|
456
|
+
sessionKey: inSnap.sessionKey,
|
|
457
|
+
phase: state.retired ? 'engine-ended' : 'opening',
|
|
458
|
+
inconsistent: false,
|
|
459
|
+
capsGenAtBind: undefined,
|
|
460
|
+
stopRequested: false,
|
|
461
|
+
stamped,
|
|
462
|
+
postureReason: posture.reason,
|
|
463
|
+
...(stamped && posture.reason === 'self-spawned' ? { proof: 'loopback-self-spawned' } : { proof: 'none' }),
|
|
464
|
+
taskId: undefined,
|
|
465
|
+
sessionId: undefined,
|
|
466
|
+
openAsks: new Map(),
|
|
467
|
+
askUnacked: false,
|
|
468
|
+
replay: false,
|
|
469
|
+
tear: (reason) => controller.abort(reason),
|
|
470
|
+
};
|
|
471
|
+
registerOpening(state, rec);
|
|
472
|
+
if (body === undefined) {
|
|
473
|
+
if (!isTerminalPhase(rec.phase))
|
|
474
|
+
rec.phase = 'ended-unstarted';
|
|
475
|
+
return endedWith(new TypeError('streamInteractiveRun: the request body could not be read'));
|
|
476
|
+
}
|
|
477
|
+
return openingStream(state, rec, body, o, headers, controller);
|
|
478
|
+
}
|
|
479
|
+
function linkHostSignal(host, controller) {
|
|
480
|
+
if (host === undefined)
|
|
481
|
+
return () => { };
|
|
482
|
+
try {
|
|
483
|
+
if (host.aborted === true) {
|
|
484
|
+
controller.abort(host.reason);
|
|
485
|
+
return () => { };
|
|
486
|
+
}
|
|
487
|
+
const onAbort = () => {
|
|
488
|
+
try {
|
|
489
|
+
controller.abort(host.reason);
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
controller.abort();
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
host.addEventListener('abort', onAbort, { once: true });
|
|
496
|
+
return () => {
|
|
497
|
+
try {
|
|
498
|
+
host.removeEventListener('abort', onAbort);
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
}
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return () => { };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
async function* openingStream(state, rec, body, o, headers, controller) {
|
|
509
|
+
const unlink = linkHostSignal(o.signal, controller);
|
|
510
|
+
const open = (withHeader) => {
|
|
511
|
+
const sent = withHeader ? headers : outboundHeaders(headers, false);
|
|
512
|
+
const callOpts = {
|
|
513
|
+
transientOk: true,
|
|
514
|
+
signal: controller.signal,
|
|
515
|
+
headers: { ...sent },
|
|
516
|
+
onResponse: (res) => noteResponse(state, rec, res, o.onResponse),
|
|
517
|
+
};
|
|
518
|
+
if (o.hasIdempotencyKey)
|
|
519
|
+
callOpts.idempotencyKey = o.idempotencyKey ?? null;
|
|
520
|
+
if (o.heartbeatGraceMs !== undefined)
|
|
521
|
+
callOpts.heartbeatGraceMs = o.heartbeatGraceMs;
|
|
522
|
+
return state.stream(body, callOpts);
|
|
523
|
+
};
|
|
524
|
+
let inner;
|
|
525
|
+
let innerClosed = false;
|
|
526
|
+
let retried = false;
|
|
527
|
+
let delivered = false;
|
|
528
|
+
try {
|
|
529
|
+
inner = open(rec.stamped);
|
|
530
|
+
for (;;) {
|
|
531
|
+
let step;
|
|
532
|
+
try {
|
|
533
|
+
step = await inner.next();
|
|
534
|
+
}
|
|
535
|
+
catch (e) {
|
|
536
|
+
if (isHandedOffPhase(rec.phase)) {
|
|
537
|
+
innerClosed = true;
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const pinned = typeof o.idempotencyKey === 'string';
|
|
541
|
+
if (!retried && !delivered && rec.stamped && rec.taskId === undefined && !pinned && isDetachDurableOff400(e)) {
|
|
542
|
+
retried = true;
|
|
543
|
+
rec.stamped = false;
|
|
544
|
+
rec.postureReason = 'durable-off-400';
|
|
545
|
+
rec.proof = 'none';
|
|
546
|
+
inner = open(false);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
innerClosed = true;
|
|
550
|
+
throw e;
|
|
551
|
+
}
|
|
552
|
+
if (step.done === true) {
|
|
553
|
+
innerClosed = true;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const ev = step.value;
|
|
557
|
+
if (isHandedOffPhase(rec.phase))
|
|
558
|
+
return;
|
|
559
|
+
observeFrame(rec, ev);
|
|
560
|
+
const ask = isAskFrame(ev);
|
|
561
|
+
if (ask)
|
|
562
|
+
rec.askUnacked = true;
|
|
563
|
+
delivered = true;
|
|
564
|
+
let hostThrew = false;
|
|
565
|
+
let hostError;
|
|
566
|
+
try {
|
|
567
|
+
yield ev;
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
hostThrew = true;
|
|
571
|
+
hostError = e;
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
if (ask)
|
|
575
|
+
rec.askUnacked = false;
|
|
576
|
+
}
|
|
577
|
+
if (hostThrew) {
|
|
578
|
+
innerClosed = true;
|
|
579
|
+
try {
|
|
580
|
+
await inner.throw(hostError);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
}
|
|
584
|
+
throw hostError;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
unlink();
|
|
590
|
+
if (!isTerminalPhase(rec.phase))
|
|
591
|
+
rec.phase = 'stream-ended';
|
|
592
|
+
if (inner !== undefined && !innerClosed) {
|
|
593
|
+
try {
|
|
594
|
+
await inner.return(undefined);
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const refuse = (reason) => ({ verdict: 'ineligible', reason });
|
|
602
|
+
const unknownOf = (reason) => ({ verdict: 'unknown', reason });
|
|
603
|
+
function hasOpenAsk(rec, nowMs) {
|
|
604
|
+
for (const deadline of rec.openAsks.values())
|
|
605
|
+
if (deadline === undefined || deadline > nowMs)
|
|
606
|
+
return true;
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
export function handoffVerdictOf(f) {
|
|
610
|
+
const rec = f.rec;
|
|
611
|
+
if (!f.inputReadable || (f.wirePresent && !f.wireRecognized) || rec?.inconsistent === true)
|
|
612
|
+
return unknownOf('unreadable-input');
|
|
613
|
+
if (!f.wirePresent || rec === undefined || rec.phase === 'ended-unstarted')
|
|
614
|
+
return refuse('no-run');
|
|
615
|
+
if (rec.phase === 'stream-ended')
|
|
616
|
+
return refuse('stream-ended');
|
|
617
|
+
if (isHandedOffPhase(rec.phase))
|
|
618
|
+
return refuse('already-handed-off');
|
|
619
|
+
if (rec.phase === 'finished')
|
|
620
|
+
return refuse('finished');
|
|
621
|
+
if (rec.phase === 'engine-ended' || (rec.capsGenAtBind !== undefined && rec.capsGenAtBind !== f.capsGenerationNow))
|
|
622
|
+
return refuse('engine-replaced');
|
|
623
|
+
if (rec.stopRequested)
|
|
624
|
+
return refuse('stopping');
|
|
625
|
+
if (!rec.stamped)
|
|
626
|
+
return { verdict: 'ineligible', reason: 'not-stamped', cause: rec.postureReason };
|
|
627
|
+
if (f.capability === 'declined')
|
|
628
|
+
return refuse('declined');
|
|
629
|
+
if (f.capability !== 'advertised')
|
|
630
|
+
return unknownOf('capability-unconfirmed');
|
|
631
|
+
if (rec.proof === 'none')
|
|
632
|
+
return unknownOf('detach-unconfirmed');
|
|
633
|
+
if (rec.phase === 'opening' || rec.phase === 'bound')
|
|
634
|
+
return refuse('not-started');
|
|
635
|
+
if (hasOpenAsk(rec, f.nowMs) || rec.askUnacked || f.holds.some((h) => h.taskId !== undefined && h.taskId === rec.taskId))
|
|
636
|
+
return refuse('awaiting-user');
|
|
637
|
+
if (f.holds.some((h) => h.taskId === undefined && (h.sessionId === undefined || h.sessionId === rec.sessionId)))
|
|
638
|
+
return unknownOf('awaiting-user-unattributable');
|
|
639
|
+
return rec.taskId !== undefined ? { verdict: 'eligible', taskId: rec.taskId } : unknownOf('unreadable-input');
|
|
640
|
+
}
|
|
641
|
+
function holdOwners() {
|
|
642
|
+
const out = [];
|
|
643
|
+
for (const h of approvalDecisionHolds.inFlight.values()) {
|
|
644
|
+
const id = h.identity;
|
|
645
|
+
const taskId = typeof id.taskId === 'string' && id.taskId !== '' ? id.taskId : undefined;
|
|
646
|
+
const sessionId = typeof id.sessionId === 'string' && id.sessionId !== '' ? id.sessionId : undefined;
|
|
647
|
+
out.push({ taskId, sessionId });
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
export function judgeHandoff(input) {
|
|
652
|
+
try {
|
|
653
|
+
let inputReadable = false;
|
|
654
|
+
let wire;
|
|
655
|
+
let sessionKey;
|
|
656
|
+
let wireUnreadable = false;
|
|
657
|
+
if (isObj(input)) {
|
|
658
|
+
const ws = ownSlotOf(input, 'wire');
|
|
659
|
+
wireUnreadable = ws.state === 'unreadable';
|
|
660
|
+
wire = ws.state === 'data' ? ws.value : undefined;
|
|
661
|
+
const sk = readOwnDataValue(input, 'sessionKey');
|
|
662
|
+
sessionKey = typeof sk === 'string' && sk !== '' ? sk : undefined;
|
|
663
|
+
inputReadable = sessionKey !== undefined;
|
|
664
|
+
}
|
|
665
|
+
const wirePresent = wireUnreadable || (wire !== undefined && wire !== null);
|
|
666
|
+
const state = wirePresent && isObj(wire) ? wireStateOf(wire) : undefined;
|
|
667
|
+
const rec = state !== undefined && sessionKey !== undefined ? state.bySession.get(sessionKey) : undefined;
|
|
668
|
+
const facts = {
|
|
669
|
+
inputReadable,
|
|
670
|
+
wirePresent,
|
|
671
|
+
wireRecognized: state !== undefined,
|
|
672
|
+
rec,
|
|
673
|
+
capability: state !== undefined ? capabilityKindAt(state.baseUrl) : 'unobserved',
|
|
674
|
+
capsGenerationNow: state !== undefined ? engineCapsGeneration(state.baseUrl) : undefined,
|
|
675
|
+
holds: holdOwners(),
|
|
676
|
+
nowMs: Date.now(),
|
|
677
|
+
};
|
|
678
|
+
return { verdict: Object.freeze(handoffVerdictOf(facts)), rec, state };
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
return { verdict: Object.freeze(unknownOf('unreadable-input')), rec: undefined, state: undefined };
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
export function backgroundHandoffEligibility(input) {
|
|
685
|
+
return judgeHandoff(input).verdict;
|
|
686
|
+
}
|
|
687
|
+
function notStampedKey(cause) {
|
|
688
|
+
switch (cause) {
|
|
689
|
+
case 'declined':
|
|
690
|
+
return 'session.background.refusal.declined';
|
|
691
|
+
case 'env-off':
|
|
692
|
+
return 'session.background.refusal.envOff';
|
|
693
|
+
case 'not-reattachable':
|
|
694
|
+
return 'session.background.refusal.notReattachable';
|
|
695
|
+
case 'unacknowledged':
|
|
696
|
+
case 'not_reported':
|
|
697
|
+
case 'unobserved':
|
|
698
|
+
case 'unreadable':
|
|
699
|
+
return 'session.background.refusal.unconfirmed';
|
|
700
|
+
case 'durable-off-400':
|
|
701
|
+
return 'session.background.refusal.durableOff';
|
|
702
|
+
default:
|
|
703
|
+
return 'session.background.unknown';
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function verdictSentenceKey(v) {
|
|
707
|
+
const slot = (key) => {
|
|
708
|
+
try {
|
|
709
|
+
return isObj(v) ? readOwnDataValue(v, key) : undefined;
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
const verdict = slot('verdict');
|
|
716
|
+
if (verdict === 'eligible')
|
|
717
|
+
return undefined;
|
|
718
|
+
const reason = slot('reason');
|
|
719
|
+
if (verdict === 'ineligible') {
|
|
720
|
+
switch (reason) {
|
|
721
|
+
case 'no-run':
|
|
722
|
+
return 'session.background.refusal.noRun';
|
|
723
|
+
case 'stream-ended':
|
|
724
|
+
return 'session.background.refusal.streamEnded';
|
|
725
|
+
case 'already-handed-off':
|
|
726
|
+
return 'session.background.refusal.alreadyHandedOff';
|
|
727
|
+
case 'finished':
|
|
728
|
+
return 'session.background.refusal.finished';
|
|
729
|
+
case 'engine-replaced':
|
|
730
|
+
return 'session.background.refusal.engineReplaced';
|
|
731
|
+
case 'stopping':
|
|
732
|
+
return 'session.background.refusal.stopping';
|
|
733
|
+
case 'not-stamped':
|
|
734
|
+
return notStampedKey(slot('cause'));
|
|
735
|
+
case 'declined':
|
|
736
|
+
return 'session.background.refusal.declined';
|
|
737
|
+
case 'not-started':
|
|
738
|
+
return 'session.background.refusal.notStarted';
|
|
739
|
+
case 'awaiting-user':
|
|
740
|
+
return 'session.background.refusal.awaitingUser';
|
|
741
|
+
default:
|
|
742
|
+
return 'session.background.unknown';
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (verdict === 'unknown' && reason === 'capability-unconfirmed')
|
|
746
|
+
return 'session.background.refusal.unconfirmed';
|
|
747
|
+
return 'session.background.unknown';
|
|
748
|
+
}
|
|
749
|
+
export function backgroundHandoffSentence(v, lang) {
|
|
750
|
+
const key = verdictSentenceKey(v);
|
|
751
|
+
return key === undefined ? undefined : renderMessage(msg(key), lang);
|
|
752
|
+
}
|
|
753
|
+
export function interactiveStopFacts(wire, taskId) {
|
|
754
|
+
try {
|
|
755
|
+
const state = wireStateOf(wire);
|
|
756
|
+
if (state === undefined || typeof taskId !== 'string' || taskId === '')
|
|
757
|
+
return Object.freeze({ detachArmed: false, handedOff: false });
|
|
758
|
+
const rec = state.byTask.get(taskId);
|
|
759
|
+
const handedOff = state.handedOff.has(taskId) || (rec !== undefined && isHandedOffPhase(rec.phase));
|
|
760
|
+
return Object.freeze({ detachArmed: rec?.stamped === true, handedOff });
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
return Object.freeze({ detachArmed: false, handedOff: false });
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
export function noteInteractiveStopRequested(wire, taskId) {
|
|
767
|
+
try {
|
|
768
|
+
const state = wireStateOf(wire);
|
|
769
|
+
if (state === undefined)
|
|
770
|
+
return 'proceed';
|
|
771
|
+
const rec = typeof taskId === 'string' ? state.byTask.get(taskId) : undefined;
|
|
772
|
+
if (rec !== undefined)
|
|
773
|
+
rec.stopRequested = true;
|
|
774
|
+
if ((typeof taskId === 'string' && state.handedOff.has(taskId)) || (rec !== undefined && isHandedOffPhase(rec.phase)))
|
|
775
|
+
return 'withheld';
|
|
776
|
+
return 'proceed';
|
|
777
|
+
}
|
|
778
|
+
catch {
|
|
779
|
+
return 'proceed';
|
|
780
|
+
}
|
|
781
|
+
}
|