dsh-plugin-weapp-gateway 0.8.23 → 0.8.34
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/LICENSE +21 -21
- package/PROTOCOL.md +1545 -1486
- package/README.md +503 -464
- package/THIRD-PARTY-NOTICES/dsh-cost-meter.txt +31 -31
- package/bin/setup-ip.mjs +474 -474
- package/cordis.patch.yml +50 -50
- package/docs/agent-team-app-integration.md +945 -0
- package/docs/blog-mobile-gateway.md +542 -542
- package/docs/dsh-0.1.5-rc.2-compatibility-audit.md +255 -255
- package/docs/dsh-0.1.6-alpha.1-compatibility-audit.md +122 -122
- package/docs/dsh-0.1.7-rc.1-compatibility-audit.md +52 -52
- package/docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md +109 -109
- package/docs/dsh-rc2-mobile-integration.md +155 -155
- package/docs/file-upload-app-integration.md +250 -0
- package/docs/multi-gateway-app-integration.md +141 -125
- package/docs/multi-gateway-phase1-acceptance.md +179 -179
- package/docs/multi-gateway-todo.md +137 -137
- package/docs/remote-gateway-refactor-plan.md +52 -52
- package/docs/runtime-architecture.architecture.json +264 -264
- package/docs/runtime-architecture.html +15001 -15001
- package/docs/runtime-architecture.visual-check.html +32 -32
- package/docs/runtime-architecture.visual-check.json +548 -548
- package/docs/session-agent-preset-app-integration.md +93 -93
- package/docs/turn-outline-app-integration.md +196 -0
- package/docs/typert-remote-gateway-feature-checklist.md +294 -294
- package/docs/voice-input-implementation.md +219 -219
- package/docs/workspace-changes-mobile-integration.md +32 -32
- package/helper/dsh_mobile_gateway_helper.py +227 -227
- package/lib/account-bridge.mjs +57 -57
- package/lib/client.js +1409 -1361
- package/lib/cloudflare-tunnel.mjs +314 -314
- package/lib/cloudflared-binary.mjs +135 -135
- package/lib/context-bridge.mjs +3338 -1715
- package/lib/cost-meter-bridge.mjs +794 -794
- package/lib/cost-meter-dashboard-bridge.mjs +1655 -1449
- package/lib/devices.js +273 -245
- package/lib/dsh-host-adapter.mjs +508 -426
- package/lib/file-upload.mjs +205 -0
- package/lib/gateway-probe.mjs +6 -6
- package/lib/gateway-state.mjs +57 -57
- package/lib/gateway-tools.mjs +36 -36
- package/lib/history-cache.mjs +66 -0
- package/lib/index.mjs +4390 -3803
- package/lib/plugin-management.mjs +251 -233
- package/lib/public-endpoint-check.mjs +61 -61
- package/lib/relay-connector.mjs +213 -181
- package/lib/relay-state.mjs +83 -76
- package/lib/session-follower.mjs +140 -136
- package/lib/session-resume-bridge.mjs +86 -0
- package/lib/session-target.mjs +29 -0
- package/lib/speech-input.mjs +192 -192
- package/lib/vendor/cost-meter/aliyun-balance.mjs +123 -123
- package/lib/vendor/cost-meter/coding-plans.mjs +1668 -1668
- package/lib/vendor/cost-meter/credentials-shim.mjs +8 -8
- package/lib/vendor/cost-meter/custom-balance.mjs +281 -281
- package/lib/vendor/cost-meter/factory-quota.mjs +69 -69
- package/lib/vendor/cost-meter/gateway-quota-adapters.mjs +1006 -1006
- package/lib/vendor/cost-meter/gateway-quotas.mjs +664 -664
- package/lib/vendor/cost-meter/home-paths-shim.mjs +13 -13
- package/lib/vendor/cost-meter/net.mjs +183 -183
- package/lib/vendor/cost-meter/opendesign.mjs +204 -204
- package/lib/vendor/cost-meter/tokenrhythm-balance.mjs +95 -95
- package/lib/vendor/cost-meter/workbuddy-accounts.mjs +44 -44
- package/lib/vendor/cost-meter/workbuddy-transport.mjs +38 -38
- package/lib/vendor/cost-meter/workbuddy.mjs +356 -356
- package/lib/web-restart.mjs +47 -47
- package/lib/wire-json.mjs +7 -7
- package/lib/workspace-changes-bridge.mjs +32 -32
- package/package.json +54 -54
- package/relay/agent-connector.js +588 -569
- package/relay/vendor/tweetnacl.js +2391 -2391
package/lib/dsh-host-adapter.mjs
CHANGED
|
@@ -1,426 +1,508 @@
|
|
|
1
|
-
import crypto from 'node:crypto'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
export const DSH_VERSION = '0.1
|
|
20
|
-
export const SESSION_FORMAT_VERSION = 4
|
|
21
|
-
|
|
22
|
-
export function readHistoryRecords(records) {
|
|
23
|
-
return requireArray(records, 'session history').map(record => {
|
|
24
|
-
const value = requireRecord(record, 'session history record')
|
|
25
|
-
if (value.type !== 'event') throw new Error('DSH Session V4 history requires event records')
|
|
26
|
-
const event = requireRecord(value.event, 'session history event')
|
|
27
|
-
if (!Number.isSafeInteger(event.seq) || event.seq < 0) throw new Error('invalid history sequence')
|
|
28
|
-
return event
|
|
29
|
-
})
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function readSessionSnapshot(frame, sessionId) {
|
|
33
|
-
requireRecord(frame, 'session/follow')
|
|
34
|
-
if (frame.type !== 'snapshot' || !Number.isSafeInteger(frame.cursor) || frame.cursor < -1
|
|
35
|
-
|| frame.header?.id !== sessionId) throw new Error('session/follow returned an invalid snapshot')
|
|
36
|
-
if (frame.header.version !== SESSION_FORMAT_VERSION) {
|
|
37
|
-
throw Object.assign(new Error(`weapp-gateway requires Session format ${SESSION_FORMAT_VERSION}; received ${frame.header.version}`), {
|
|
38
|
-
code: 'unsupported-session-format',
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
events: readHistoryRecords(frame.records).map(event => ({ event })),
|
|
43
|
-
hasMore: frame.hasMore === true,
|
|
44
|
-
projections: requireRecord(frame.projections, 'session/follow projections'),
|
|
45
|
-
historyFormatVersion: frame.header.version,
|
|
46
|
-
cursor: frame.cursor,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function firstStreamFrame(gateway, namespace, method, args, signal, peer) {
|
|
51
|
-
const ownedAbort = new AbortController()
|
|
52
|
-
const streamSignal = signal ? AbortSignal.any([signal, ownedAbort.signal]) : ownedAbort.signal
|
|
53
|
-
let iterator
|
|
54
|
-
try {
|
|
55
|
-
const iterable = await gateway.stream({ namespace, method, args, signal: streamSignal, peer })
|
|
56
|
-
const iteratorFactory = iterable?.[Symbol.asyncIterator] ?? iterable?.[Symbol.iterator]
|
|
57
|
-
if (typeof iteratorFactory !== 'function') throw new Error(`${namespace}/${method} returned an invalid stream`)
|
|
58
|
-
iterator = iteratorFactory.call(iterable)
|
|
59
|
-
const first = await iterator.next()
|
|
60
|
-
if (first.done) throw new Error(`${namespace}/${method} ended before its opening frame`)
|
|
61
|
-
return first.value
|
|
62
|
-
} finally {
|
|
63
|
-
ownedAbort.abort()
|
|
64
|
-
if (typeof iterator?.return === 'function') await iterator.return()
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function requestArgs(request) {
|
|
69
|
-
return { request }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// The SessionController names its reserved list argument
|
|
73
|
-
// `_request`. Typert descriptors preserve that source parameter name exactly,
|
|
74
|
-
// so this endpoint cannot share the normal `{ request }` wrapper.
|
|
75
|
-
function sessionListArgs(request) {
|
|
76
|
-
return { _request: request }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The only module allowed to know DSH Remote endpoint names and argument
|
|
81
|
-
* descriptors. Its public methods intentionally match weapp-gateway domain
|
|
82
|
-
* operations, not the upstream transport envelope.
|
|
83
|
-
*/
|
|
84
|
-
export function createDshHostAdapter(typertGateway) {
|
|
85
|
-
if (!typertGateway || typeof typertGateway.invoke !== 'function' || typeof typertGateway.stream !== 'function') {
|
|
86
|
-
throw new Error('weapp-gateway requires the DSH Remote Gateway host service')
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// DSH 0.2.1-alpha made the Peer explicit for in-process Remote calls:
|
|
90
|
-
// InvokeRemoteRequest.peer is required, the Gateway reads peer.signal (both
|
|
91
|
-
// assertPeerLive and methodSignal) and peer.principal for the remote-access
|
|
92
|
-
// decision, and omitting it fails every call with
|
|
93
|
-
// "Cannot read properties of undefined (reading 'signal')". Hosts up to
|
|
94
|
-
// 0.2.0-rc.2 ignore the field, so always naming it works on both. The plugin
|
|
95
|
-
// speaks as the Host itself — an owner Peer living as long as this adapter,
|
|
96
|
-
// so disposing the plugin ends every stream it opened.
|
|
97
|
-
const peerLifetime = new AbortController()
|
|
98
|
-
const hostPeer =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
},
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}),
|
|
358
|
-
},
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
},
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import { Readable } from 'node:stream'
|
|
4
|
+
|
|
5
|
+
function requireRecord(value, endpoint) {
|
|
6
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
7
|
+
throw new Error(`${endpoint} returned an invalid object`)
|
|
8
|
+
}
|
|
9
|
+
return value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function requireArray(value, endpoint) {
|
|
13
|
+
if (!Array.isArray(value)) throw new Error(`${endpoint} returned an invalid array`)
|
|
14
|
+
return value
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The current session address, upload and Peer routes are provided by this
|
|
18
|
+
// Host baseline. Keep the hello value truthful so clients can gate features.
|
|
19
|
+
export const DSH_VERSION = '0.2.1-rc.2'
|
|
20
|
+
export const SESSION_FORMAT_VERSION = 4
|
|
21
|
+
|
|
22
|
+
export function readHistoryRecords(records) {
|
|
23
|
+
return requireArray(records, 'session history').map(record => {
|
|
24
|
+
const value = requireRecord(record, 'session history record')
|
|
25
|
+
if (value.type !== 'event') throw new Error('DSH Session V4 history requires event records')
|
|
26
|
+
const event = requireRecord(value.event, 'session history event')
|
|
27
|
+
if (!Number.isSafeInteger(event.seq) || event.seq < 0) throw new Error('invalid history sequence')
|
|
28
|
+
return event
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function readSessionSnapshot(frame, sessionId) {
|
|
33
|
+
requireRecord(frame, 'session/follow')
|
|
34
|
+
if (frame.type !== 'snapshot' || !Number.isSafeInteger(frame.cursor) || frame.cursor < -1
|
|
35
|
+
|| frame.header?.id !== sessionId) throw new Error('session/follow returned an invalid snapshot')
|
|
36
|
+
if (frame.header.version !== SESSION_FORMAT_VERSION) {
|
|
37
|
+
throw Object.assign(new Error(`weapp-gateway requires Session format ${SESSION_FORMAT_VERSION}; received ${frame.header.version}`), {
|
|
38
|
+
code: 'unsupported-session-format',
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
events: readHistoryRecords(frame.records).map(event => ({ event })),
|
|
43
|
+
hasMore: frame.hasMore === true,
|
|
44
|
+
projections: requireRecord(frame.projections, 'session/follow projections'),
|
|
45
|
+
historyFormatVersion: frame.header.version,
|
|
46
|
+
cursor: frame.cursor,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function firstStreamFrame(gateway, namespace, method, args, signal, peer) {
|
|
51
|
+
const ownedAbort = new AbortController()
|
|
52
|
+
const streamSignal = signal ? AbortSignal.any([signal, ownedAbort.signal]) : ownedAbort.signal
|
|
53
|
+
let iterator
|
|
54
|
+
try {
|
|
55
|
+
const iterable = await gateway.stream({ namespace, method, args, signal: streamSignal, peer })
|
|
56
|
+
const iteratorFactory = iterable?.[Symbol.asyncIterator] ?? iterable?.[Symbol.iterator]
|
|
57
|
+
if (typeof iteratorFactory !== 'function') throw new Error(`${namespace}/${method} returned an invalid stream`)
|
|
58
|
+
iterator = iteratorFactory.call(iterable)
|
|
59
|
+
const first = await iterator.next()
|
|
60
|
+
if (first.done) throw new Error(`${namespace}/${method} ended before its opening frame`)
|
|
61
|
+
return first.value
|
|
62
|
+
} finally {
|
|
63
|
+
ownedAbort.abort()
|
|
64
|
+
if (typeof iterator?.return === 'function') await iterator.return()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function requestArgs(request) {
|
|
69
|
+
return { request }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The SessionController names its reserved list argument
|
|
73
|
+
// `_request`. Typert descriptors preserve that source parameter name exactly,
|
|
74
|
+
// so this endpoint cannot share the normal `{ request }` wrapper.
|
|
75
|
+
function sessionListArgs(request) {
|
|
76
|
+
return { _request: request }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The only module allowed to know DSH Remote endpoint names and argument
|
|
81
|
+
* descriptors. Its public methods intentionally match weapp-gateway domain
|
|
82
|
+
* operations, not the upstream transport envelope.
|
|
83
|
+
*/
|
|
84
|
+
export function createDshHostAdapter(typertGateway, connection) {
|
|
85
|
+
if (!typertGateway || typeof typertGateway.invoke !== 'function' || typeof typertGateway.stream !== 'function') {
|
|
86
|
+
throw new Error('weapp-gateway requires the DSH Remote Gateway host service')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// DSH 0.2.1-alpha made the Peer explicit for in-process Remote calls:
|
|
90
|
+
// InvokeRemoteRequest.peer is required, the Gateway reads peer.signal (both
|
|
91
|
+
// assertPeerLive and methodSignal) and peer.principal for the remote-access
|
|
92
|
+
// decision, and omitting it fails every call with
|
|
93
|
+
// "Cannot read properties of undefined (reading 'signal')". Hosts up to
|
|
94
|
+
// 0.2.0-rc.2 ignore the field, so always naming it works on both. The plugin
|
|
95
|
+
// speaks as the Host itself — an owner Peer living as long as this adapter,
|
|
96
|
+
// so disposing the plugin ends every stream it opened.
|
|
97
|
+
const peerLifetime = new AbortController()
|
|
98
|
+
const hostPeer = typeof connection?.createPeer === 'function'
|
|
99
|
+
? connection.createPeer({ principal: { kind: 'owner' }, interactions: 'none' })
|
|
100
|
+
: {
|
|
101
|
+
id: `weapp-gateway:${crypto.randomUUID()}`,
|
|
102
|
+
principal: Object.freeze({ kind: 'owner' }),
|
|
103
|
+
interactions: 'none',
|
|
104
|
+
signal: peerLifetime.signal,
|
|
105
|
+
dispose: async () => { peerLifetime.abort(new Error('weapp-gateway host adapter disposed')) },
|
|
106
|
+
}
|
|
107
|
+
if (!(hostPeer?.signal instanceof AbortSignal)) throw new Error('DSH Connection.createPeer returned an invalid Peer')
|
|
108
|
+
|
|
109
|
+
// Reuse DSH's authenticated in-process route for binary file admission.
|
|
110
|
+
// Older hosts without this route remain usable; the capability is omitted.
|
|
111
|
+
const uploadHandler = connection?.createSharedFetchHandler?.('/api')
|
|
112
|
+
const uploadUrl = new URL('http://localhost/api/session/uploadFileBinary')
|
|
113
|
+
const uploadsAvailable = () => uploadHandler?.requestBodyMode?.({ method: 'POST', url: uploadUrl }) === 'streaming'
|
|
114
|
+
|
|
115
|
+
const invoke = (namespace, method, args, signal) => typertGateway.invoke({
|
|
116
|
+
namespace,
|
|
117
|
+
method,
|
|
118
|
+
args,
|
|
119
|
+
...(signal === undefined ? {} : { signal }),
|
|
120
|
+
peer: hostPeer,
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
// 子智能体(subagent)会话必须带持久父地址寻址:host 对裸 sessionId 的
|
|
124
|
+
// 子会话读历史会直接以 session/agent-busy 拒绝("subagent Sessions require
|
|
125
|
+
// their durable parent address")。父会话 id 由调用方(小程序协作面板)从
|
|
126
|
+
// subagentCatalog 投影里带入。
|
|
127
|
+
const historyAddress = (payload) => {
|
|
128
|
+
const parentSessionId = typeof payload.parentSessionId === 'string' ? payload.parentSessionId.trim() : ''
|
|
129
|
+
if (!parentSessionId) return { kind: 'session', sessionId: payload.sessionId }
|
|
130
|
+
const requestedMode = payload.subagentMode ?? payload.mode
|
|
131
|
+
const mode = ['one-shot', 'continuable', 'unknown'].includes(requestedMode) ? requestedMode : 'unknown'
|
|
132
|
+
return { kind: 'subagent', parentSessionId, childSessionId: payload.sessionId, mode }
|
|
133
|
+
}
|
|
134
|
+
const addressSessionId = (address) => (address.kind === 'session' ? address.sessionId : address.childSessionId)
|
|
135
|
+
|
|
136
|
+
const sessionSnapshot = async (request, signal) => {
|
|
137
|
+
const frame = requireRecord(await firstStreamFrame(
|
|
138
|
+
typertGateway,
|
|
139
|
+
'session',
|
|
140
|
+
'follow',
|
|
141
|
+
requestArgs(request),
|
|
142
|
+
signal,
|
|
143
|
+
hostPeer,
|
|
144
|
+
), 'session/follow')
|
|
145
|
+
return readSessionSnapshot(frame, addressSessionId(request.address))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const history = async (payload, signal) => {
|
|
149
|
+
const request = {
|
|
150
|
+
address: historyAddress(payload),
|
|
151
|
+
// Older-page requests need only the opening cursor/projections, not another large latest page.
|
|
152
|
+
...(payload.beforeSeq !== undefined ? { maxMessages: 1 }
|
|
153
|
+
: payload.maxMessages === undefined ? {} : { maxMessages: payload.maxMessages }),
|
|
154
|
+
}
|
|
155
|
+
const snapshot = await sessionSnapshot(request, signal)
|
|
156
|
+
let events = snapshot.events
|
|
157
|
+
let hasMore = snapshot.hasMore
|
|
158
|
+
if (payload.beforeSeq !== undefined) {
|
|
159
|
+
const page = requireRecord(await invoke('session', 'page', requestArgs({
|
|
160
|
+
address: request.address,
|
|
161
|
+
throughSeq: Number.isSafeInteger(payload.throughSeq) ? payload.throughSeq : snapshot.cursor,
|
|
162
|
+
beforeSeq: payload.beforeSeq,
|
|
163
|
+
...(payload.maxMessages === undefined ? {} : { maxMessages: payload.maxMessages }),
|
|
164
|
+
}), signal), 'session/page')
|
|
165
|
+
events = readHistoryRecords(page.records).map(event => ({ event }))
|
|
166
|
+
hasMore = page.hasMore === true
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
...snapshot,
|
|
170
|
+
events,
|
|
171
|
+
hasMore,
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const modelCatalog = async (signal) => requireRecord(
|
|
176
|
+
await invoke('session', 'modelCatalog', {}, signal),
|
|
177
|
+
'session/modelCatalog',
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
// Control and conversation sockets share this adapter. Admit a prompt only
|
|
181
|
+
// after an earlier preset switch finishes, and re-check later switches.
|
|
182
|
+
const sessionOperations = new Map()
|
|
183
|
+
const admittedPrompts = new Set()
|
|
184
|
+
const serializeSession = (sessionId, operation) => {
|
|
185
|
+
const task = (sessionOperations.get(sessionId) ?? Promise.resolve()).then(operation)
|
|
186
|
+
const guard = task.catch(() => {})
|
|
187
|
+
sessionOperations.set(sessionId, guard)
|
|
188
|
+
return task.finally(() => {
|
|
189
|
+
if (sessionOperations.get(sessionId) === guard) sessionOperations.delete(sessionId)
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
const sessionPreset = async (sessionId, signal) => {
|
|
193
|
+
const snapshot = await sessionSnapshot({ address: { kind: 'session', sessionId }, maxMessages: 1 }, signal)
|
|
194
|
+
const values = snapshot.projections.values
|
|
195
|
+
const metadata = values?.sessionListMetadata
|
|
196
|
+
if (typeof values?.agentPreset !== 'string' || !metadata || typeof metadata.blank !== 'boolean'
|
|
197
|
+
|| !(metadata.lastPromptAt === null || Number.isFinite(metadata.lastPromptAt))) {
|
|
198
|
+
throw Object.assign(new Error('session preset state is unavailable'), { code: 'agent-preset/unavailable' })
|
|
199
|
+
}
|
|
200
|
+
const durableLock = !metadata.blank || metadata.lastPromptAt !== null
|
|
201
|
+
if (durableLock) admittedPrompts.delete(sessionId)
|
|
202
|
+
return { sessionId, agentPreset: values.agentPreset, locked: durableLock || admittedPrompts.has(sessionId) }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const commands = {
|
|
206
|
+
list: (sessionId, signal) => invoke('commands', 'list', { agentId: sessionId }, signal),
|
|
207
|
+
// DSH accepts `submittedAttachments`. The parsed
|
|
208
|
+
// wire images already carry the { type: 'image', ... } shape it expects, so
|
|
209
|
+
// only the field name changes.
|
|
210
|
+
execute: (sessionId, line, attachments, signal) => invoke(
|
|
211
|
+
'commands',
|
|
212
|
+
'execute',
|
|
213
|
+
{ agentId: sessionId, line, submittedAttachments: attachments },
|
|
214
|
+
signal,
|
|
215
|
+
),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const goalRefValue = (value, endpoint) => {
|
|
219
|
+
const goal = requireRecord(value, endpoint)
|
|
220
|
+
if (typeof goal.id !== 'string' || !Number.isSafeInteger(goal.revision)) {
|
|
221
|
+
throw new Error(`${endpoint} returned an invalid goal`)
|
|
222
|
+
}
|
|
223
|
+
return { ref: { id: goal.id, revision: goal.revision } }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const describeHost = async (signal) => {
|
|
227
|
+
const [sessions, catalog, canOpenPath] = await Promise.all([
|
|
228
|
+
invoke('session', 'list', sessionListArgs({}), signal),
|
|
229
|
+
modelCatalog(signal),
|
|
230
|
+
invoke('session', 'canOpenWorkspacePath', {}, signal).catch(() => false),
|
|
231
|
+
])
|
|
232
|
+
return {
|
|
233
|
+
// 真实 DSH 版本。客户端(host-info.normalizeHostInfo)优先读 dshVersion,
|
|
234
|
+
// 但 settings / session-settings 直接读 host.version 作为账号 API 的版本门控,
|
|
235
|
+
// 所以这里必须给真版本,不能放占位标记。
|
|
236
|
+
version: DSH_VERSION,
|
|
237
|
+
dshVersion: DSH_VERSION,
|
|
238
|
+
historyFormatVersion: SESSION_FORMAT_VERSION,
|
|
239
|
+
cwd: os.homedir(),
|
|
240
|
+
attachedSessions: Array.isArray(sessions?.items) ? sessions.items.length : 0,
|
|
241
|
+
canOpenPath: canOpenPath === true,
|
|
242
|
+
defaultProvider: catalog.default?.provider,
|
|
243
|
+
defaultModel: catalog.default?.model,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
files: {
|
|
249
|
+
available: uploadsAvailable,
|
|
250
|
+
async upload({ sessionId, name, data }, signal) {
|
|
251
|
+
if (!uploadsAvailable()) throw Object.assign(new Error('The official file upload service is unavailable'), { code: 'file-upload-unavailable' })
|
|
252
|
+
const url = new URL(uploadUrl)
|
|
253
|
+
url.searchParams.set('sessionId', sessionId)
|
|
254
|
+
url.searchParams.set('name', name)
|
|
255
|
+
const uploadSignal = signal ? AbortSignal.any([signal, hostPeer.signal]) : hostPeer.signal
|
|
256
|
+
uploadSignal.throwIfAborted()
|
|
257
|
+
const source = Readable.from(data, { objectMode: false })
|
|
258
|
+
try {
|
|
259
|
+
const response = await uploadHandler.fetch(new Request(url, {
|
|
260
|
+
method: 'POST', headers: { 'content-type': 'application/octet-stream' },
|
|
261
|
+
body: Readable.toWeb(source), duplex: 'half', signal: uploadSignal,
|
|
262
|
+
}), hostPeer)
|
|
263
|
+
if (!response.ok) throw Object.assign(new Error(`Official file upload returned HTTP ${response.status}`), { code: 'file-upload-unavailable' })
|
|
264
|
+
const result = await response.json()
|
|
265
|
+
if (result.ok !== true) throw Object.assign(new Error(result.error?.message || 'Official file upload failed'),
|
|
266
|
+
{ code: result.error?.code || 'file-upload-failed' })
|
|
267
|
+
return requireRecord(result.value, 'file upload')
|
|
268
|
+
} catch (error) {
|
|
269
|
+
uploadSignal.throwIfAborted()
|
|
270
|
+
throw error
|
|
271
|
+
} finally { source.destroy() }
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
speech: {
|
|
275
|
+
catalog: (signal) => invoke('speech', 'catalog', {}, signal),
|
|
276
|
+
follow: (signal) => typertGateway.stream({ namespace: 'speech', method: 'follow', args: {}, signal, peer: hostPeer }),
|
|
277
|
+
configure: (patch, signal) => invoke('speech', 'configure', { patch }, signal),
|
|
278
|
+
prepare: (providerId, options, signal) => invoke('speech', 'prepare', {
|
|
279
|
+
providerId, ...(options === undefined ? {} : { options }),
|
|
280
|
+
}, signal),
|
|
281
|
+
cancelPreparation: (providerId, signal) => invoke('speech', 'cancelPreparation', { providerId }, signal),
|
|
282
|
+
transcribe: (request, signal) => invoke('speech', 'transcribe', { request }, signal),
|
|
283
|
+
},
|
|
284
|
+
sessions: {
|
|
285
|
+
list: (payload = {}, signal) => invoke('session', 'list', sessionListArgs(payload), signal),
|
|
286
|
+
projections: (payload, signal) => invoke('session', 'projections', requestArgs(payload), signal),
|
|
287
|
+
search: (payload, signal) => invoke('session', 'search', requestArgs(payload), signal),
|
|
288
|
+
create: (payload, signal) => invoke('session', 'create', requestArgs(payload), signal),
|
|
289
|
+
prompt: (payload, signal) => serializeSession(payload.sessionId, async () => {
|
|
290
|
+
if (payload.parentSessionId !== undefined) {
|
|
291
|
+
if (payload.subagentMode !== 'continuable') {
|
|
292
|
+
throw Object.assign(new Error('only continuable subagents accept messages'), { code: 'subagent/not-resumable' })
|
|
293
|
+
}
|
|
294
|
+
return invoke('subagents', 'prompt', requestArgs({
|
|
295
|
+
requestId: payload.requestId ?? crypto.randomUUID(),
|
|
296
|
+
parentSessionId: payload.parentSessionId,
|
|
297
|
+
childSessionId: payload.sessionId,
|
|
298
|
+
mode: 'continuable',
|
|
299
|
+
delivery: payload.mode,
|
|
300
|
+
content: payload.content,
|
|
301
|
+
...(payload.clientTimeZone === undefined ? {} : { clientTimeZone: payload.clientTimeZone }),
|
|
302
|
+
}), signal)
|
|
303
|
+
}
|
|
304
|
+
// Accepted input may not have reached turn/start yet. Keep that gap
|
|
305
|
+
// locked; the persistent event or a subsequent read retires the marker.
|
|
306
|
+
const alreadyAdmitted = admittedPrompts.has(payload.sessionId)
|
|
307
|
+
admittedPrompts.add(payload.sessionId)
|
|
308
|
+
try {
|
|
309
|
+
return await invoke('session', 'prompt', requestArgs({ requestId: crypto.randomUUID(), ...payload }), signal)
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (!alreadyAdmitted) admittedPrompts.delete(payload.sessionId)
|
|
312
|
+
throw error
|
|
313
|
+
}
|
|
314
|
+
}),
|
|
315
|
+
attachment: (payload, signal) => invoke('session', 'attachment', requestArgs(payload), signal),
|
|
316
|
+
fork: (payload, signal) => invoke('session', 'fork', requestArgs(payload), signal),
|
|
317
|
+
cancel: (payload, signal) => payload.parentSessionId === undefined
|
|
318
|
+
? invoke('session', 'cancel', requestArgs(payload), signal)
|
|
319
|
+
: payload.subagentMode !== 'continuable'
|
|
320
|
+
? Promise.reject(Object.assign(new Error('only continuable subagents accept interrupts'), { code: 'subagent/not-resumable' }))
|
|
321
|
+
: invoke('subagents', 'interruptByParent', {
|
|
322
|
+
parentSessionId: payload.parentSessionId,
|
|
323
|
+
childSessionId: payload.sessionId,
|
|
324
|
+
mode: 'continuable',
|
|
325
|
+
}, signal),
|
|
326
|
+
updateQueue: (payload, signal) => invoke('session', 'updateQueue', requestArgs(payload), signal),
|
|
327
|
+
rename: (payload, signal) => invoke('session', 'rename', requestArgs(payload), signal),
|
|
328
|
+
selectModel: (payload, signal) => invoke('session', 'selectModel', requestArgs(payload), signal),
|
|
329
|
+
history,
|
|
330
|
+
async models(payload, signal) {
|
|
331
|
+
const [catalog, snapshot] = await Promise.all([
|
|
332
|
+
modelCatalog(signal),
|
|
333
|
+
sessionSnapshot({ address: { kind: 'session', sessionId: payload.sessionId }, maxMessages: 1 }, signal),
|
|
334
|
+
])
|
|
335
|
+
const current = snapshot.projections?.values?.modelSelection?.next ?? catalog.default
|
|
336
|
+
return {
|
|
337
|
+
current,
|
|
338
|
+
routable: typeof current?.provider === 'string'
|
|
339
|
+
&& requireArray(catalog.routableProviders, 'session/modelCatalog routableProviders').includes(current.provider),
|
|
340
|
+
groups: requireArray(catalog.groups, 'session/modelCatalog groups'),
|
|
341
|
+
failures: requireArray(catalog.failures, 'session/modelCatalog failures'),
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
workspace: {
|
|
346
|
+
async list(_payload = {}, signal) {
|
|
347
|
+
const frame = requireRecord(await firstStreamFrame(typertGateway, 'workspace', 'follow', {}, signal, hostPeer), 'workspace/follow')
|
|
348
|
+
if (frame.type !== 'baseline') throw new Error('workspace/follow returned an invalid opening baseline')
|
|
349
|
+
return requireRecord(frame.value, 'workspace/follow baseline')
|
|
350
|
+
},
|
|
351
|
+
create: (payload, signal) => invoke('workspace', 'create', requestArgs(payload), signal),
|
|
352
|
+
archiveSession: (payload, signal) => invoke('workspace', 'archiveSession', requestArgs(payload), signal),
|
|
353
|
+
},
|
|
354
|
+
pluginConfiguration: {
|
|
355
|
+
credentialDescribe: (ref) => invoke('credentials', 'describe', { refs: [ref] }),
|
|
356
|
+
credentialSet: (ref, value) => invoke('credentials', 'set', { ref, value }),
|
|
357
|
+
translationLimits: () => invoke('cotTranslation', 'limits', {}),
|
|
358
|
+
},
|
|
359
|
+
settings: {
|
|
360
|
+
describe: (_payload = {}, signal) => invoke('settings', 'describe', {}, signal),
|
|
361
|
+
mutate: (payload, signal) => invoke('settings', 'mutate', {
|
|
362
|
+
ns: payload.ns,
|
|
363
|
+
ops: payload.ops,
|
|
364
|
+
expectedRevision: payload.expectedRevision,
|
|
365
|
+
}, signal),
|
|
366
|
+
update: (payload, signal) => invoke('settings', 'update', {
|
|
367
|
+
ns: payload.ns,
|
|
368
|
+
patch: payload.patch,
|
|
369
|
+
...(payload.expectedRevision === undefined ? {} : { expectedRevision: payload.expectedRevision }),
|
|
370
|
+
}, signal),
|
|
371
|
+
},
|
|
372
|
+
pluginMarket: {
|
|
373
|
+
listIndexes: () => invoke('pluginMarket', 'listIndexes', {}),
|
|
374
|
+
setIndexes: (urls) => invoke('pluginMarket', 'setIndexes', { urls }),
|
|
375
|
+
search: (url, query) => invoke('pluginMarket', 'search', { url, query }),
|
|
376
|
+
},
|
|
377
|
+
plugins: {
|
|
378
|
+
inventory: (_payload = {}, signal) => invoke('pluginInventory', 'list', {}, signal),
|
|
379
|
+
listBundles: (signal) => invoke('pluginManager', 'listBundles', {}, signal),
|
|
380
|
+
listPlugins: (signal) => invoke('pluginManager', 'listPlugins', {}, signal),
|
|
381
|
+
registries: (signal) => invoke('pluginManager', 'registries', {}, signal),
|
|
382
|
+
inspect: (spec, options, signal) => invoke('pluginManager', 'inspect', {
|
|
383
|
+
spec, ...(options === undefined ? {} : { options }),
|
|
384
|
+
}, signal),
|
|
385
|
+
installBundle: (spec, options) => invoke('pluginManager', 'installBundle', { spec, options }),
|
|
386
|
+
waitForInstall: (requestId) => invoke('pluginManager', 'waitForInstall', { requestId }),
|
|
387
|
+
cancelInstall: (requestId) => invoke('pluginManager', 'cancelInstall', { requestId }),
|
|
388
|
+
setBundleEnabled: (name, enabled) => invoke('pluginManager', 'setBundleEnabled', { name, enabled }),
|
|
389
|
+
setPluginEnabled: (id, enabled) => invoke('pluginManager', 'setPluginEnabled', { id, enabled }),
|
|
390
|
+
removeBundle: (name) => invoke('pluginManager', 'removeBundle', { name }),
|
|
391
|
+
listVersionExemptions: () => invoke('pluginManager', 'listVersionExemptions', {}),
|
|
392
|
+
setVersionExemption: (packageVersion, runtimeVersion, enabled, acceptRisk) => invoke(
|
|
393
|
+
'pluginManager', 'setVersionExemption', {
|
|
394
|
+
packageVersion, runtimeVersion, enabled,
|
|
395
|
+
...(acceptRisk === undefined ? {} : { acceptRisk }),
|
|
396
|
+
},
|
|
397
|
+
),
|
|
398
|
+
},
|
|
399
|
+
permissionPresets: {
|
|
400
|
+
catalog: (_payload = {}, signal) => invoke('permissionPresets', 'catalog', {}, signal),
|
|
401
|
+
},
|
|
402
|
+
schedule: {
|
|
403
|
+
async catalog(_payload = {}, signal) {
|
|
404
|
+
return { items: requireArray(await invoke('schedule', 'catalog', {}, signal), 'schedule/catalog') }
|
|
405
|
+
},
|
|
406
|
+
async list(payload, signal) {
|
|
407
|
+
return { items: requireArray(await invoke('schedule', 'list', requestArgs(payload), signal), 'schedule/list') }
|
|
408
|
+
},
|
|
409
|
+
history: (payload, signal) => invoke('schedule', 'history', requestArgs(payload), signal),
|
|
410
|
+
update: (payload, signal) => invoke('schedule', 'update', requestArgs(payload), signal),
|
|
411
|
+
delete: (payload, signal) => invoke('schedule', 'delete', requestArgs(payload), signal),
|
|
412
|
+
},
|
|
413
|
+
skills: {
|
|
414
|
+
list: (payload, signal) => invoke('skills', 'list', requestArgs(payload), signal),
|
|
415
|
+
},
|
|
416
|
+
agentPresets: {
|
|
417
|
+
list: (_payload = {}, signal) => invoke('agentPresets', 'list', {}, signal),
|
|
418
|
+
session: (payload, signal) => serializeSession(payload.sessionId, () => sessionPreset(payload.sessionId, signal)),
|
|
419
|
+
select: (payload, signal) => serializeSession(payload.sessionId, async () => {
|
|
420
|
+
const state = await sessionPreset(payload.sessionId, signal)
|
|
421
|
+
if (state.locked) {
|
|
422
|
+
throw Object.assign(new Error('session has already started; its agent preset is fixed'), { code: 'agent-preset/locked' })
|
|
423
|
+
}
|
|
424
|
+
// Host validates the live catalog, mounts the composition, checks its
|
|
425
|
+
// own turn boundary and persists agent-preset/selected on this ID.
|
|
426
|
+
const selected = await invoke('agentPresets', 'select', {
|
|
427
|
+
agentId: payload.sessionId, agentPreset: payload.agentPreset,
|
|
428
|
+
}, signal)
|
|
429
|
+
if (typeof selected !== 'string' || !selected) throw new Error('agentPresets/select returned an invalid preset')
|
|
430
|
+
return { sessionId: payload.sessionId, agentPreset: selected }
|
|
431
|
+
}),
|
|
432
|
+
},
|
|
433
|
+
llm: {
|
|
434
|
+
async models(_payload = {}, signal) {
|
|
435
|
+
const catalog = await modelCatalog(signal)
|
|
436
|
+
return { groups: catalog.groups, failures: catalog.failures }
|
|
437
|
+
},
|
|
438
|
+
async providers(_payload = {}, signal) {
|
|
439
|
+
const providers = await invoke('llm', 'listConfigurableProviders', {}, signal)
|
|
440
|
+
return { providers: requireArray(providers, 'llm/listConfigurableProviders') }
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
goals: {
|
|
444
|
+
async edit(payload) {
|
|
445
|
+
return goalRefValue(await invoke('goals', 'edit', {
|
|
446
|
+
agentId: payload.sessionId,
|
|
447
|
+
ref: payload.ref,
|
|
448
|
+
request: {
|
|
449
|
+
...(payload.objective === undefined ? {} : { objective: payload.objective }),
|
|
450
|
+
...(payload.maxGoalRounds === undefined ? {} : { maxGoalRounds: payload.maxGoalRounds }),
|
|
451
|
+
},
|
|
452
|
+
}), 'goals/edit')
|
|
453
|
+
},
|
|
454
|
+
async pause(payload) {
|
|
455
|
+
return goalRefValue(
|
|
456
|
+
await invoke('goals', 'pause', { agentId: payload.sessionId, ref: payload.ref }),
|
|
457
|
+
'goals/pause',
|
|
458
|
+
)
|
|
459
|
+
},
|
|
460
|
+
async resume(payload) {
|
|
461
|
+
return goalRefValue(
|
|
462
|
+
await invoke('goals', 'resume', { agentId: payload.sessionId, ref: payload.ref }),
|
|
463
|
+
'goals/resume',
|
|
464
|
+
)
|
|
465
|
+
},
|
|
466
|
+
clear: async payload => {
|
|
467
|
+
await invoke('goals', 'clear', { agentId: payload.sessionId, ref: payload.ref })
|
|
468
|
+
return { cleared: true }
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
commands,
|
|
472
|
+
host: {
|
|
473
|
+
describe: (_payload = {}, signal) => describeHost(signal),
|
|
474
|
+
},
|
|
475
|
+
describeHost,
|
|
476
|
+
observeSessionEvent(sessionId, event) {
|
|
477
|
+
if (event.type === 'turn/start') admittedPrompts.delete(sessionId)
|
|
478
|
+
},
|
|
479
|
+
openSessionStream(target, signal) {
|
|
480
|
+
const sessionId = typeof target === 'string' ? target : target?.sessionId
|
|
481
|
+
const parentSessionId = typeof target === 'object' && target ? target.parentSessionId : undefined
|
|
482
|
+
const mode = typeof target === 'object' && target ? (target.subagentMode ?? target.mode) : undefined
|
|
483
|
+
const address = parentSessionId
|
|
484
|
+
? { kind: 'subagent', parentSessionId, childSessionId: sessionId,
|
|
485
|
+
mode: ['continuable', 'one-shot', 'unknown'].includes(mode) ? mode : 'unknown' }
|
|
486
|
+
: { kind: 'session', sessionId }
|
|
487
|
+
return typertGateway.stream({
|
|
488
|
+
namespace: 'session', method: 'follow',
|
|
489
|
+
// Bound the opening window at the Host; older records remain available via session.page.
|
|
490
|
+
args: requestArgs({ address, maxMessages: 12, assistantStream: true }),
|
|
491
|
+
signal,
|
|
492
|
+
peer: hostPeer,
|
|
493
|
+
})
|
|
494
|
+
},
|
|
495
|
+
openControlStream(signal) {
|
|
496
|
+
return typertGateway.stream({ namespace: 'session', method: 'control', args: {}, signal, peer: hostPeer })
|
|
497
|
+
},
|
|
498
|
+
openWorkspaceStream(signal) {
|
|
499
|
+
return typertGateway.stream({ namespace: 'workspace', method: 'follow', args: {}, signal, peer: hostPeer })
|
|
500
|
+
},
|
|
501
|
+
// Ends every stream this adapter opened: the host Peer's signal is joined
|
|
502
|
+
// into each one by the 0.2.1 Gateway.
|
|
503
|
+
dispose() {
|
|
504
|
+
if (typeof hostPeer.dispose === 'function') return hostPeer.dispose()
|
|
505
|
+
peerLifetime.abort(new Error('weapp-gateway host adapter disposed'))
|
|
506
|
+
},
|
|
507
|
+
}
|
|
508
|
+
}
|