@minionry/sdk 0.4.23
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 +14 -0
- package/README.md +8 -0
- package/dist/base64.d.ts +2 -0
- package/dist/base64.js +15 -0
- package/dist/cache.d.ts +22 -0
- package/dist/cache.js +79 -0
- package/dist/cdn/v1.js +16 -0
- package/dist/detect.d.ts +8 -0
- package/dist/detect.js +73 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +11 -0
- package/dist/keyboard.d.ts +6 -0
- package/dist/keyboard.js +39 -0
- package/dist/protocol.d.ts +61 -0
- package/dist/protocol.js +15 -0
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +1503 -0
- package/dist/transport.d.ts +73 -0
- package/dist/transport.js +1 -0
- package/dist/transports/mock.d.ts +29 -0
- package/dist/transports/mock.js +4286 -0
- package/dist/transports/post-message.d.ts +11 -0
- package/dist/transports/post-message.js +294 -0
- package/dist/types.d.ts +1967 -0
- package/dist/types.js +85 -0
- package/package.json +27 -0
package/dist/provider.js
ADDED
|
@@ -0,0 +1,1503 @@
|
|
|
1
|
+
import { MinionryError } from './types.js';
|
|
2
|
+
import { base64ToBytes, bytesToBase64 } from './base64.js';
|
|
3
|
+
class Emitter {
|
|
4
|
+
listeners = new Map();
|
|
5
|
+
on(event, cb) {
|
|
6
|
+
let list = this.listeners.get(event);
|
|
7
|
+
if (!list) {
|
|
8
|
+
list = [];
|
|
9
|
+
this.listeners.set(event, list);
|
|
10
|
+
}
|
|
11
|
+
const entry = { cb };
|
|
12
|
+
list.push(entry);
|
|
13
|
+
let removed = false;
|
|
14
|
+
return () => {
|
|
15
|
+
if (removed)
|
|
16
|
+
return;
|
|
17
|
+
removed = true;
|
|
18
|
+
const current = this.listeners.get(event);
|
|
19
|
+
if (!current)
|
|
20
|
+
return;
|
|
21
|
+
const index = current.indexOf(entry);
|
|
22
|
+
if (index !== -1)
|
|
23
|
+
current.splice(index, 1);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
emit(event, payload) {
|
|
27
|
+
const list = this.listeners.get(event);
|
|
28
|
+
if (!list || list.length === 0)
|
|
29
|
+
return;
|
|
30
|
+
for (const entry of [...list]) {
|
|
31
|
+
try {
|
|
32
|
+
entry.cb(payload);
|
|
33
|
+
}
|
|
34
|
+
catch (cause) {
|
|
35
|
+
console.error('[@minionry/sdk] listener error', cause);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const CLOSED_MESSAGE = 'the transport is closed — the host frame was torn down or the Space disconnected';
|
|
41
|
+
const TRANSFER_CHUNK_BYTES = 256 * 1024;
|
|
42
|
+
const MAX_TRANSFER_BYTES = 50 * 1024 * 1024;
|
|
43
|
+
const MAX_CHUNK_ATTEMPTS = 6;
|
|
44
|
+
const CHUNK_RETRY_DELAY_MS = 500;
|
|
45
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
+
function basenameOf(path) {
|
|
47
|
+
const idx = path.lastIndexOf('/');
|
|
48
|
+
return idx === -1 ? path : path.slice(idx + 1);
|
|
49
|
+
}
|
|
50
|
+
const STREAM_RESUME_FEATURE = 'streamResume';
|
|
51
|
+
const APP_ENDPOINTS_FEATURE = 'appEndpoints';
|
|
52
|
+
const AGENTS_APP_ENDPOINT_FEATURE = 'agentsAppEndpoint';
|
|
53
|
+
const AGENT_RUN_APP_ENDPOINT_FIELDS = ['model', 'systemPrompt', 'budget', 'sessionId'];
|
|
54
|
+
const AGENTS_MODEL_SELECTION_FEATURE = 'agentsModelSelection';
|
|
55
|
+
function deliverStreamFrames(tracker, frames) {
|
|
56
|
+
for (const frame of [...frames].sort((a, b) => a.seq - b.seq)) {
|
|
57
|
+
if (frame.seq <= tracker.lastSeq)
|
|
58
|
+
continue;
|
|
59
|
+
tracker.deliver(frame.event, frame.payload);
|
|
60
|
+
tracker.lastSeq = frame.seq;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function describeGapCause(cause) {
|
|
64
|
+
return cause instanceof MinionryError ? cause.message : String(cause);
|
|
65
|
+
}
|
|
66
|
+
export function createProvider(transport) {
|
|
67
|
+
const pending = new Map();
|
|
68
|
+
const streams = new Map();
|
|
69
|
+
const providerEvents = new Emitter();
|
|
70
|
+
let requestCounter = 0;
|
|
71
|
+
let closed = false;
|
|
72
|
+
let pendingIntent;
|
|
73
|
+
let intentListenerCount = 0;
|
|
74
|
+
const mintRequestId = () => `r${++requestCounter}`;
|
|
75
|
+
const request = (method, params, requestId = mintRequestId()) => {
|
|
76
|
+
if (closed) {
|
|
77
|
+
return Promise.reject(new MinionryError('SPACE_DISCONNECTED', `${method} failed: ${CLOSED_MESSAGE}`));
|
|
78
|
+
}
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
pending.set(requestId, { resolve, reject });
|
|
81
|
+
const frame = { requestId, method };
|
|
82
|
+
if (params !== undefined)
|
|
83
|
+
frame.params = params;
|
|
84
|
+
try {
|
|
85
|
+
transport.send(frame);
|
|
86
|
+
}
|
|
87
|
+
catch (cause) {
|
|
88
|
+
pending.delete(requestId);
|
|
89
|
+
reject(new MinionryError('SPACE_DISCONNECTED', `${method} failed: transport send threw (${String(cause)})`));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
let featuresMemo;
|
|
94
|
+
const hostFeatures = () => {
|
|
95
|
+
if (!featuresMemo) {
|
|
96
|
+
const probe = request('features').then((flags) => (Array.isArray(flags) ? flags : []), (cause) => {
|
|
97
|
+
if (featuresMemo === probe)
|
|
98
|
+
featuresMemo = undefined;
|
|
99
|
+
throw cause;
|
|
100
|
+
});
|
|
101
|
+
featuresMemo = probe;
|
|
102
|
+
}
|
|
103
|
+
return featuresMemo;
|
|
104
|
+
};
|
|
105
|
+
const requireFeature = async (flag, what) => {
|
|
106
|
+
if (!(await hostFeatures()).includes(flag)) {
|
|
107
|
+
throw new MinionryError('UNSUPPORTED', `${what} needs a host that advertises the '${flag}' feature — this host does not, so nothing was sent`);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const supportsResume = () => hostFeatures().then((flags) => flags.includes(STREAM_RESUME_FEATURE), () => false);
|
|
111
|
+
const registerStream = (requestId, deliver, onGap) => {
|
|
112
|
+
streams.set(requestId, { lastSeq: 0, resuming: false, pending: [], deliver, onGap });
|
|
113
|
+
};
|
|
114
|
+
const unregisterStream = (requestId) => {
|
|
115
|
+
streams.delete(requestId);
|
|
116
|
+
};
|
|
117
|
+
const resumeStream = (requestId, tracker) => {
|
|
118
|
+
tracker.resuming = true;
|
|
119
|
+
void supportsResume()
|
|
120
|
+
.then((canResume) => {
|
|
121
|
+
if (closed)
|
|
122
|
+
return;
|
|
123
|
+
if (!canResume) {
|
|
124
|
+
throw new MinionryError('SCOPE_DENIED', 'this host does not advertise the streamResume feature');
|
|
125
|
+
}
|
|
126
|
+
return request('stream.resume', { requestId, afterSeq: tracker.lastSeq });
|
|
127
|
+
})
|
|
128
|
+
.then((result) => {
|
|
129
|
+
if (closed || !streams.has(requestId))
|
|
130
|
+
return;
|
|
131
|
+
deliverStreamFrames(tracker, result && Array.isArray(result.events) ? result.events : []);
|
|
132
|
+
const queued = tracker.pending;
|
|
133
|
+
tracker.pending = [];
|
|
134
|
+
tracker.resuming = false;
|
|
135
|
+
deliverStreamFrames(tracker, queued);
|
|
136
|
+
})
|
|
137
|
+
.catch((cause) => {
|
|
138
|
+
if (closed || !streams.has(requestId))
|
|
139
|
+
return;
|
|
140
|
+
tracker.resuming = false;
|
|
141
|
+
const queued = tracker.pending;
|
|
142
|
+
tracker.pending = [];
|
|
143
|
+
tracker.onGap(describeGapCause(cause));
|
|
144
|
+
deliverStreamFrames(tracker, queued);
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const routeStreamEvent = (requestId, seq, event, payload) => {
|
|
148
|
+
const tracker = streams.get(requestId);
|
|
149
|
+
if (!tracker)
|
|
150
|
+
return;
|
|
151
|
+
if (seq === undefined) {
|
|
152
|
+
tracker.deliver(event, payload);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (tracker.resuming) {
|
|
156
|
+
tracker.pending.push({ seq, event, payload });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (seq <= tracker.lastSeq)
|
|
160
|
+
return;
|
|
161
|
+
if (seq === tracker.lastSeq + 1) {
|
|
162
|
+
tracker.deliver(event, payload);
|
|
163
|
+
tracker.lastSeq = seq;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
tracker.pending.push({ seq, event, payload });
|
|
167
|
+
resumeStream(requestId, tracker);
|
|
168
|
+
};
|
|
169
|
+
const toolRegistrations = new Map();
|
|
170
|
+
const answerToolCall = (result) => {
|
|
171
|
+
if (closed)
|
|
172
|
+
return;
|
|
173
|
+
request('tools.result', result).catch(() => {
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const retireToolRegistration = (requestId) => {
|
|
177
|
+
const registration = toolRegistrations.get(requestId);
|
|
178
|
+
if (!registration)
|
|
179
|
+
return;
|
|
180
|
+
toolRegistrations.delete(requestId);
|
|
181
|
+
for (const controller of registration.calls.values())
|
|
182
|
+
controller.abort();
|
|
183
|
+
registration.calls.clear();
|
|
184
|
+
};
|
|
185
|
+
const routeToolCall = (frame) => {
|
|
186
|
+
const registration = toolRegistrations.get(frame.requestId);
|
|
187
|
+
if (frame.op === 'cancel') {
|
|
188
|
+
const controller = registration?.calls.get(frame.callId);
|
|
189
|
+
registration?.calls.delete(frame.callId);
|
|
190
|
+
controller?.abort();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const definition = registration?.tools.get(frame.tool);
|
|
194
|
+
if (!registration || !definition) {
|
|
195
|
+
answerToolCall({
|
|
196
|
+
callId: frame.callId,
|
|
197
|
+
ok: false,
|
|
198
|
+
error: {
|
|
199
|
+
code: 'APP_UNAVAILABLE',
|
|
200
|
+
message: `this app is not serving a tool named '${frame.tool}'`,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
registration.calls.set(frame.callId, controller);
|
|
207
|
+
const ctx = { callId: frame.callId, tool: frame.tool, signal: controller.signal };
|
|
208
|
+
void Promise.resolve()
|
|
209
|
+
.then(() => definition.handler(frame.input, ctx))
|
|
210
|
+
.then((result) => {
|
|
211
|
+
if (!registration.calls.delete(frame.callId))
|
|
212
|
+
return;
|
|
213
|
+
answerToolCall({ callId: frame.callId, ok: true, result });
|
|
214
|
+
}, (cause) => {
|
|
215
|
+
if (!registration.calls.delete(frame.callId))
|
|
216
|
+
return;
|
|
217
|
+
answerToolCall({
|
|
218
|
+
callId: frame.callId,
|
|
219
|
+
ok: false,
|
|
220
|
+
error: {
|
|
221
|
+
code: 'APP_ERROR',
|
|
222
|
+
message: cause instanceof Error ? cause.message : String(cause),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
transport.start({
|
|
228
|
+
onResponse(frame) {
|
|
229
|
+
const entry = pending.get(frame.requestId);
|
|
230
|
+
if (!entry)
|
|
231
|
+
return;
|
|
232
|
+
pending.delete(frame.requestId);
|
|
233
|
+
if (frame.ok)
|
|
234
|
+
entry.resolve(frame.result);
|
|
235
|
+
else
|
|
236
|
+
entry.reject(new MinionryError(frame.error.code, frame.error.message));
|
|
237
|
+
},
|
|
238
|
+
onEvent(frame) {
|
|
239
|
+
routeStreamEvent(frame.requestId, frame.seq, frame.event, frame.payload);
|
|
240
|
+
},
|
|
241
|
+
onNotification(frame) {
|
|
242
|
+
if (frame.event === 'intent') {
|
|
243
|
+
if (intentListenerCount > 0) {
|
|
244
|
+
providerEvents.emit('intent', frame.payload);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
pendingIntent = frame.payload;
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (frame.event === 'disconnect' || frame.event === 'spaceChanged') {
|
|
252
|
+
featuresMemo = undefined;
|
|
253
|
+
}
|
|
254
|
+
if (frame.event === 'disconnect' ||
|
|
255
|
+
frame.event === 'grantsChanged' ||
|
|
256
|
+
frame.event === 'spaceChanged' ||
|
|
257
|
+
frame.event === 'themeChanged' ||
|
|
258
|
+
frame.event === 'localeChanged') {
|
|
259
|
+
providerEvents.emit(frame.event, frame.payload);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
onToolCall(frame) {
|
|
263
|
+
if (closed)
|
|
264
|
+
return;
|
|
265
|
+
routeToolCall(frame);
|
|
266
|
+
},
|
|
267
|
+
onClose() {
|
|
268
|
+
if (closed)
|
|
269
|
+
return;
|
|
270
|
+
closed = true;
|
|
271
|
+
const cause = new MinionryError('SPACE_DISCONNECTED', CLOSED_MESSAGE);
|
|
272
|
+
const inflight = [...pending.values()];
|
|
273
|
+
pending.clear();
|
|
274
|
+
streams.clear();
|
|
275
|
+
for (const requestId of [...toolRegistrations.keys()])
|
|
276
|
+
retireToolRegistration(requestId);
|
|
277
|
+
for (const entry of inflight)
|
|
278
|
+
entry.reject(cause);
|
|
279
|
+
providerEvents.emit('disconnect', undefined);
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
return {
|
|
283
|
+
version: '1',
|
|
284
|
+
async features() {
|
|
285
|
+
return (await request('features'));
|
|
286
|
+
},
|
|
287
|
+
async connect(req) {
|
|
288
|
+
return (await request('connect', { scopes: req.scopes }));
|
|
289
|
+
},
|
|
290
|
+
async disconnect() {
|
|
291
|
+
await request('disconnect');
|
|
292
|
+
},
|
|
293
|
+
auth: {
|
|
294
|
+
async getToken() {
|
|
295
|
+
return (await request('auth.getToken'));
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
agents: {
|
|
299
|
+
async run(req) {
|
|
300
|
+
const endpointFields = AGENT_RUN_APP_ENDPOINT_FIELDS.filter((field) => req[field] !== undefined);
|
|
301
|
+
if (endpointFields.length > 0) {
|
|
302
|
+
await requireFeature(AGENTS_APP_ENDPOINT_FEATURE, `agents.run with ${endpointFields.join(', ')}`);
|
|
303
|
+
}
|
|
304
|
+
if (req.modelSelection !== undefined) {
|
|
305
|
+
await requireFeature(AGENTS_MODEL_SELECTION_FEATURE, 'agents.run with modelSelection');
|
|
306
|
+
}
|
|
307
|
+
const params = { prompt: req.prompt, title: req.title };
|
|
308
|
+
for (const field of endpointFields)
|
|
309
|
+
params[field] = req[field];
|
|
310
|
+
if (req.modelSelection !== undefined)
|
|
311
|
+
params.modelSelection = req.modelSelection;
|
|
312
|
+
const requestId = mintRequestId();
|
|
313
|
+
const runEvents = new Emitter();
|
|
314
|
+
registerStream(requestId, (event, payload) => runEvents.emit(event, payload), (message) => runEvents.emit('gap', { message }));
|
|
315
|
+
let runId;
|
|
316
|
+
let sessionId;
|
|
317
|
+
try {
|
|
318
|
+
const result = (await request('agents.run', params, requestId));
|
|
319
|
+
runId = result.runId;
|
|
320
|
+
sessionId = typeof result.sessionId === 'string' ? result.sessionId : '';
|
|
321
|
+
}
|
|
322
|
+
catch (cause) {
|
|
323
|
+
unregisterStream(requestId);
|
|
324
|
+
throw cause;
|
|
325
|
+
}
|
|
326
|
+
let resultPromise;
|
|
327
|
+
const run = {
|
|
328
|
+
id: runId,
|
|
329
|
+
sessionId,
|
|
330
|
+
on(event, cb) {
|
|
331
|
+
return runEvents.on(event, cb);
|
|
332
|
+
},
|
|
333
|
+
result() {
|
|
334
|
+
resultPromise ??= request('agents.result', { runId }).catch((cause) => {
|
|
335
|
+
resultPromise = undefined;
|
|
336
|
+
throw cause;
|
|
337
|
+
});
|
|
338
|
+
return resultPromise;
|
|
339
|
+
},
|
|
340
|
+
async cancel() {
|
|
341
|
+
await request('cancel', { requestId });
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
return run;
|
|
345
|
+
},
|
|
346
|
+
sessions: {
|
|
347
|
+
async list() {
|
|
348
|
+
return (await request('agents.sessions.list'));
|
|
349
|
+
},
|
|
350
|
+
async create(req) {
|
|
351
|
+
return (await request('agents.sessions.create', { name: req?.name }));
|
|
352
|
+
},
|
|
353
|
+
async attach(sessionId) {
|
|
354
|
+
return (await request('agents.sessions.attach', { sessionId }));
|
|
355
|
+
},
|
|
356
|
+
async close(sessionId) {
|
|
357
|
+
await request('agents.sessions.close', { sessionId });
|
|
358
|
+
},
|
|
359
|
+
async rename(sessionId, req) {
|
|
360
|
+
return (await request('agents.sessions.rename', { sessionId, name: req.name }));
|
|
361
|
+
},
|
|
362
|
+
async history(sessionId) {
|
|
363
|
+
return (await request('agents.sessions.history', { sessionId }));
|
|
364
|
+
},
|
|
365
|
+
async replay(sessionId, afterSeq) {
|
|
366
|
+
return (await request('agents.sessions.replay', { sessionId, afterSeq }));
|
|
367
|
+
},
|
|
368
|
+
async approve(sessionId, approved) {
|
|
369
|
+
await request('agents.sessions.approve', { sessionId, approved });
|
|
370
|
+
},
|
|
371
|
+
async answerQuestion(sessionId, toolUseId, answers) {
|
|
372
|
+
await request('agents.sessions.answerQuestion', { sessionId, toolUseId, answers });
|
|
373
|
+
},
|
|
374
|
+
async prompt(sessionId, text, attachments) {
|
|
375
|
+
return (await request('agents.sessions.prompt', { sessionId, prompt: text, attachments }));
|
|
376
|
+
},
|
|
377
|
+
async stop(sessionId) {
|
|
378
|
+
await request('agents.sessions.stop', { sessionId });
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
apps: {
|
|
383
|
+
async open(req) {
|
|
384
|
+
await request('apps.open', { app: req.app, params: req.params });
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
pm: {
|
|
388
|
+
async createBoard(req) {
|
|
389
|
+
return (await request('pm.createBoard', {
|
|
390
|
+
prompt: req.prompt,
|
|
391
|
+
autoImplement: req.autoImplement,
|
|
392
|
+
input: req.input,
|
|
393
|
+
}));
|
|
394
|
+
},
|
|
395
|
+
async getBoard(boardId) {
|
|
396
|
+
return (await request('pm.getBoard', { boardId }));
|
|
397
|
+
},
|
|
398
|
+
async listBoards() {
|
|
399
|
+
return (await request('pm.listBoards', {}));
|
|
400
|
+
},
|
|
401
|
+
async createIssue(req) {
|
|
402
|
+
return (await request('pm.createIssue', req));
|
|
403
|
+
},
|
|
404
|
+
async updateIssue(boardId, issueId, fields) {
|
|
405
|
+
return (await request('pm.updateIssue', { boardId, issueId, fields }));
|
|
406
|
+
},
|
|
407
|
+
async deleteIssue(boardId, issueId) {
|
|
408
|
+
await request('pm.deleteIssue', { boardId, issueId });
|
|
409
|
+
},
|
|
410
|
+
async updateBoard(boardId, fields) {
|
|
411
|
+
return (await request('pm.updateBoard', { boardId, fields }));
|
|
412
|
+
},
|
|
413
|
+
onBoardUpdate(boardId, cb) {
|
|
414
|
+
const requestId = mintRequestId();
|
|
415
|
+
let active = true;
|
|
416
|
+
registerStream(requestId, (event, payload) => {
|
|
417
|
+
if (active && event === 'boardUpdate')
|
|
418
|
+
cb(payload);
|
|
419
|
+
}, (message) => {
|
|
420
|
+
if (active)
|
|
421
|
+
providerEvents.emit('boardGap', { boardId, message });
|
|
422
|
+
});
|
|
423
|
+
request('pm.subscribeBoard', { boardId }, requestId).catch((cause) => {
|
|
424
|
+
if (!active)
|
|
425
|
+
return;
|
|
426
|
+
active = false;
|
|
427
|
+
unregisterStream(requestId);
|
|
428
|
+
console.error(`[@minionry/sdk] pm.onBoardUpdate(${boardId}) subscription failed`, cause);
|
|
429
|
+
});
|
|
430
|
+
return () => {
|
|
431
|
+
if (!active)
|
|
432
|
+
return;
|
|
433
|
+
active = false;
|
|
434
|
+
unregisterStream(requestId);
|
|
435
|
+
if (!closed) {
|
|
436
|
+
request('cancel', { requestId }).catch(() => { });
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
},
|
|
440
|
+
async execute(boardId) {
|
|
441
|
+
const requestId = mintRequestId();
|
|
442
|
+
const runEvents = new Emitter();
|
|
443
|
+
registerStream(requestId, (event, payload) => runEvents.emit(event, payload), (message) => runEvents.emit('gap', { message }));
|
|
444
|
+
let runId;
|
|
445
|
+
try {
|
|
446
|
+
const result = (await request('pm.execute', { boardId }, requestId));
|
|
447
|
+
runId = result.runId;
|
|
448
|
+
}
|
|
449
|
+
catch (cause) {
|
|
450
|
+
unregisterStream(requestId);
|
|
451
|
+
throw cause;
|
|
452
|
+
}
|
|
453
|
+
const run = {
|
|
454
|
+
id: runId,
|
|
455
|
+
boardId,
|
|
456
|
+
on(event, cb) {
|
|
457
|
+
return runEvents.on(event, cb);
|
|
458
|
+
},
|
|
459
|
+
async pause() {
|
|
460
|
+
await request('pm.pause', { boardId, runId });
|
|
461
|
+
},
|
|
462
|
+
async resume() {
|
|
463
|
+
await request('pm.resume', { boardId, runId });
|
|
464
|
+
},
|
|
465
|
+
async stop() {
|
|
466
|
+
await request('pm.stop', { boardId, runId });
|
|
467
|
+
},
|
|
468
|
+
};
|
|
469
|
+
return run;
|
|
470
|
+
},
|
|
471
|
+
schedules: {
|
|
472
|
+
async list() {
|
|
473
|
+
const result = (await request('pm.schedules.list', {}));
|
|
474
|
+
return result.schedules;
|
|
475
|
+
},
|
|
476
|
+
async create(req) {
|
|
477
|
+
return (await request('pm.schedules.create', req));
|
|
478
|
+
},
|
|
479
|
+
async update(scheduleId, patch) {
|
|
480
|
+
return (await request('pm.schedules.update', { scheduleId, patch }));
|
|
481
|
+
},
|
|
482
|
+
async delete(scheduleId) {
|
|
483
|
+
await request('pm.schedules.delete', { scheduleId });
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
files: {
|
|
488
|
+
async read(path) {
|
|
489
|
+
return (await request('files.read', { path }));
|
|
490
|
+
},
|
|
491
|
+
async readEntry(path) {
|
|
492
|
+
return (await request('files.readEntry', { path }));
|
|
493
|
+
},
|
|
494
|
+
async write(path, content) {
|
|
495
|
+
await request('files.write', { path, content });
|
|
496
|
+
},
|
|
497
|
+
async list(path) {
|
|
498
|
+
return (await request('files.list', { path }));
|
|
499
|
+
},
|
|
500
|
+
async delete(path) {
|
|
501
|
+
await request('files.delete', { path });
|
|
502
|
+
},
|
|
503
|
+
async mkdir(path) {
|
|
504
|
+
await request('files.mkdir', { path });
|
|
505
|
+
},
|
|
506
|
+
async rename(path, newPath) {
|
|
507
|
+
await request('files.rename', { path, newPath });
|
|
508
|
+
},
|
|
509
|
+
async search(path, query, options) {
|
|
510
|
+
const requestId = mintRequestId();
|
|
511
|
+
const searchEvents = new Emitter();
|
|
512
|
+
registerStream(requestId, (event, payload) => searchEvents.emit(event, payload), (message) => searchEvents.emit('gap', { message }));
|
|
513
|
+
try {
|
|
514
|
+
await request('files.search', { path, query, options }, requestId);
|
|
515
|
+
}
|
|
516
|
+
catch (cause) {
|
|
517
|
+
unregisterStream(requestId);
|
|
518
|
+
throw cause;
|
|
519
|
+
}
|
|
520
|
+
let cancelled = false;
|
|
521
|
+
const handle = {
|
|
522
|
+
id: requestId,
|
|
523
|
+
on(event, cb) {
|
|
524
|
+
return searchEvents.on(event, cb);
|
|
525
|
+
},
|
|
526
|
+
async cancel() {
|
|
527
|
+
if (cancelled)
|
|
528
|
+
return;
|
|
529
|
+
cancelled = true;
|
|
530
|
+
unregisterStream(requestId);
|
|
531
|
+
await request('cancel', { requestId });
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
return handle;
|
|
535
|
+
},
|
|
536
|
+
async definition(path, symbolName, opts) {
|
|
537
|
+
return (await request('files.definition', {
|
|
538
|
+
path,
|
|
539
|
+
symbolName,
|
|
540
|
+
language: opts?.language,
|
|
541
|
+
currentFile: opts?.currentFile,
|
|
542
|
+
}));
|
|
543
|
+
},
|
|
544
|
+
onChanged(path, cb) {
|
|
545
|
+
const requestId = mintRequestId();
|
|
546
|
+
let active = true;
|
|
547
|
+
registerStream(requestId, (event, payload) => {
|
|
548
|
+
if (active && event === 'changed')
|
|
549
|
+
cb(payload);
|
|
550
|
+
}, (message) => {
|
|
551
|
+
if (active)
|
|
552
|
+
providerEvents.emit('fileChangeGap', { path, message });
|
|
553
|
+
});
|
|
554
|
+
request('files.subscribeChanges', { path }, requestId).catch((cause) => {
|
|
555
|
+
if (!active)
|
|
556
|
+
return;
|
|
557
|
+
active = false;
|
|
558
|
+
unregisterStream(requestId);
|
|
559
|
+
console.error(`[@minionry/sdk] files.onChanged(${path}) subscription failed`, cause);
|
|
560
|
+
});
|
|
561
|
+
return () => {
|
|
562
|
+
if (!active)
|
|
563
|
+
return;
|
|
564
|
+
active = false;
|
|
565
|
+
unregisterStream(requestId);
|
|
566
|
+
if (!closed) {
|
|
567
|
+
request('cancel', { requestId }).catch(() => { });
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
},
|
|
571
|
+
async upload(path, data, opts) {
|
|
572
|
+
if (data.byteLength > MAX_TRANSFER_BYTES) {
|
|
573
|
+
throw new MinionryError('BAD_REQUEST', `files.upload: ${data.byteLength} bytes exceeds the 50 MiB cap`);
|
|
574
|
+
}
|
|
575
|
+
const requestId = mintRequestId();
|
|
576
|
+
const transferEvents = new Emitter();
|
|
577
|
+
registerStream(requestId, (event, payload) => transferEvents.emit(event, payload), () => { });
|
|
578
|
+
const totalBytes = data.byteLength;
|
|
579
|
+
const totalChunks = Math.max(1, Math.ceil(totalBytes / TRANSFER_CHUNK_BYTES));
|
|
580
|
+
let uploadId;
|
|
581
|
+
try {
|
|
582
|
+
const start = (await request('files.uploadStart', {
|
|
583
|
+
path,
|
|
584
|
+
fileName: basenameOf(path),
|
|
585
|
+
fileSize: totalBytes,
|
|
586
|
+
totalChunks,
|
|
587
|
+
mimeType: opts?.mimeType,
|
|
588
|
+
overwrite: opts?.overwrite,
|
|
589
|
+
}, requestId));
|
|
590
|
+
uploadId = start.uploadId;
|
|
591
|
+
}
|
|
592
|
+
catch (cause) {
|
|
593
|
+
unregisterStream(requestId);
|
|
594
|
+
throw cause;
|
|
595
|
+
}
|
|
596
|
+
let cancelled = false;
|
|
597
|
+
let settle;
|
|
598
|
+
let settleReject;
|
|
599
|
+
const donePromise = new Promise((resolve, reject) => {
|
|
600
|
+
settle = resolve;
|
|
601
|
+
settleReject = reject;
|
|
602
|
+
});
|
|
603
|
+
void (async () => {
|
|
604
|
+
let chunkIndex = 0;
|
|
605
|
+
let attempts = 0;
|
|
606
|
+
while (chunkIndex < totalChunks) {
|
|
607
|
+
if (cancelled)
|
|
608
|
+
return;
|
|
609
|
+
const from = chunkIndex * TRANSFER_CHUNK_BYTES;
|
|
610
|
+
const to = Math.min(from + TRANSFER_CHUNK_BYTES, totalBytes);
|
|
611
|
+
const content = bytesToBase64(data.subarray(from, to));
|
|
612
|
+
attempts += 1;
|
|
613
|
+
try {
|
|
614
|
+
const ack = (await request('files.uploadChunk', { uploadId, chunkIndex, content }));
|
|
615
|
+
transferEvents.emit('progress', {
|
|
616
|
+
chunkIndex,
|
|
617
|
+
totalChunks,
|
|
618
|
+
bytesTransferred: ack.bytesWritten,
|
|
619
|
+
totalBytes,
|
|
620
|
+
});
|
|
621
|
+
chunkIndex += 1;
|
|
622
|
+
attempts = 0;
|
|
623
|
+
}
|
|
624
|
+
catch (cause) {
|
|
625
|
+
if (cancelled)
|
|
626
|
+
return;
|
|
627
|
+
if (attempts >= MAX_CHUNK_ATTEMPTS) {
|
|
628
|
+
unregisterStream(requestId);
|
|
629
|
+
settleReject(cause);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
await sleep(CHUNK_RETRY_DELAY_MS);
|
|
633
|
+
try {
|
|
634
|
+
const status = (await request('files.uploadStatus', { uploadId }));
|
|
635
|
+
if (status.nextExpectedChunk !== chunkIndex)
|
|
636
|
+
attempts = 0;
|
|
637
|
+
chunkIndex = status.nextExpectedChunk;
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
unregisterStream(requestId);
|
|
644
|
+
settle({ path, bytesWritten: totalBytes });
|
|
645
|
+
})();
|
|
646
|
+
const handle = {
|
|
647
|
+
id: uploadId,
|
|
648
|
+
on(event, cb) {
|
|
649
|
+
return transferEvents.on(event, cb);
|
|
650
|
+
},
|
|
651
|
+
result() {
|
|
652
|
+
return donePromise;
|
|
653
|
+
},
|
|
654
|
+
async cancel() {
|
|
655
|
+
cancelled = true;
|
|
656
|
+
unregisterStream(requestId);
|
|
657
|
+
settleReject(new MinionryError('CANCELLED', 'files.upload cancelled'));
|
|
658
|
+
await request('files.uploadCancel', { uploadId }).catch(() => { });
|
|
659
|
+
},
|
|
660
|
+
};
|
|
661
|
+
return handle;
|
|
662
|
+
},
|
|
663
|
+
async download(path, opts) {
|
|
664
|
+
const requestId = mintRequestId();
|
|
665
|
+
const transferEvents = new Emitter();
|
|
666
|
+
registerStream(requestId, (event, payload) => transferEvents.emit(event, payload), () => { });
|
|
667
|
+
let downloadId;
|
|
668
|
+
let fileSize;
|
|
669
|
+
let totalChunks;
|
|
670
|
+
let mimeType;
|
|
671
|
+
let isZip;
|
|
672
|
+
try {
|
|
673
|
+
const start = (await request('files.downloadStart', { path, asZip: opts?.asZip }, requestId));
|
|
674
|
+
downloadId = start.downloadId;
|
|
675
|
+
fileSize = start.fileSize;
|
|
676
|
+
totalChunks = start.totalChunks;
|
|
677
|
+
mimeType = start.mimeType;
|
|
678
|
+
isZip = start.isZip;
|
|
679
|
+
}
|
|
680
|
+
catch (cause) {
|
|
681
|
+
unregisterStream(requestId);
|
|
682
|
+
throw cause;
|
|
683
|
+
}
|
|
684
|
+
let cancelled = false;
|
|
685
|
+
let settle;
|
|
686
|
+
let settleReject;
|
|
687
|
+
const donePromise = new Promise((resolve, reject) => {
|
|
688
|
+
settle = resolve;
|
|
689
|
+
settleReject = reject;
|
|
690
|
+
});
|
|
691
|
+
void (async () => {
|
|
692
|
+
const parts = new Array(totalChunks);
|
|
693
|
+
let bytesTransferred = 0;
|
|
694
|
+
let chunkIndex = 0;
|
|
695
|
+
while (chunkIndex < totalChunks) {
|
|
696
|
+
if (cancelled)
|
|
697
|
+
return;
|
|
698
|
+
let attempt = 0;
|
|
699
|
+
let received = false;
|
|
700
|
+
while (!received && attempt < MAX_CHUNK_ATTEMPTS) {
|
|
701
|
+
attempt += 1;
|
|
702
|
+
try {
|
|
703
|
+
const chunk = (await request('files.downloadChunk', { downloadId, chunkIndex }));
|
|
704
|
+
const bytes = base64ToBytes(chunk.content);
|
|
705
|
+
parts[chunkIndex] = bytes;
|
|
706
|
+
bytesTransferred += bytes.byteLength;
|
|
707
|
+
transferEvents.emit('progress', {
|
|
708
|
+
chunkIndex,
|
|
709
|
+
totalChunks,
|
|
710
|
+
bytesTransferred,
|
|
711
|
+
totalBytes: fileSize,
|
|
712
|
+
});
|
|
713
|
+
received = true;
|
|
714
|
+
}
|
|
715
|
+
catch (cause) {
|
|
716
|
+
if (cancelled)
|
|
717
|
+
return;
|
|
718
|
+
if (attempt >= MAX_CHUNK_ATTEMPTS) {
|
|
719
|
+
unregisterStream(requestId);
|
|
720
|
+
settleReject(cause);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
await sleep(CHUNK_RETRY_DELAY_MS);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
chunkIndex += 1;
|
|
727
|
+
}
|
|
728
|
+
const bytes = new Uint8Array(fileSize);
|
|
729
|
+
let offset = 0;
|
|
730
|
+
for (const part of parts) {
|
|
731
|
+
bytes.set(part, offset);
|
|
732
|
+
offset += part.byteLength;
|
|
733
|
+
}
|
|
734
|
+
unregisterStream(requestId);
|
|
735
|
+
settle({ path, bytes, mimeType, isZip, fileSize });
|
|
736
|
+
})();
|
|
737
|
+
const handle = {
|
|
738
|
+
id: downloadId,
|
|
739
|
+
on(event, cb) {
|
|
740
|
+
return transferEvents.on(event, cb);
|
|
741
|
+
},
|
|
742
|
+
result() {
|
|
743
|
+
return donePromise;
|
|
744
|
+
},
|
|
745
|
+
async cancel() {
|
|
746
|
+
cancelled = true;
|
|
747
|
+
unregisterStream(requestId);
|
|
748
|
+
settleReject(new MinionryError('CANCELLED', 'files.download cancelled'));
|
|
749
|
+
await request('files.downloadCancel', { downloadId }).catch(() => { });
|
|
750
|
+
},
|
|
751
|
+
};
|
|
752
|
+
return handle;
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
storage: {
|
|
756
|
+
async get(key) {
|
|
757
|
+
return await request('storage.get', { key });
|
|
758
|
+
},
|
|
759
|
+
async set(key, value) {
|
|
760
|
+
await request('storage.set', { key, value });
|
|
761
|
+
},
|
|
762
|
+
async delete(key) {
|
|
763
|
+
await request('storage.delete', { key });
|
|
764
|
+
},
|
|
765
|
+
async keys() {
|
|
766
|
+
return (await request('storage.keys'));
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
notifications: {
|
|
770
|
+
async request() {
|
|
771
|
+
await request('notifications.request');
|
|
772
|
+
},
|
|
773
|
+
async status() {
|
|
774
|
+
return (await request('notifications.status'));
|
|
775
|
+
},
|
|
776
|
+
async revoke() {
|
|
777
|
+
await request('notifications.revoke');
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
theme: {
|
|
781
|
+
async get() {
|
|
782
|
+
return (await request('theme.get'));
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
locale: {
|
|
786
|
+
async get() {
|
|
787
|
+
return (await request('locale.get'));
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
browser: {
|
|
791
|
+
tabs: {
|
|
792
|
+
async list() {
|
|
793
|
+
return (await request('browser.tabs.list'));
|
|
794
|
+
},
|
|
795
|
+
onChanged(cb) {
|
|
796
|
+
const requestId = mintRequestId();
|
|
797
|
+
let active = true;
|
|
798
|
+
registerStream(requestId, (event, payload) => {
|
|
799
|
+
if (active && event === 'tabsChanged')
|
|
800
|
+
cb(payload);
|
|
801
|
+
}, () => { });
|
|
802
|
+
request('browser.tabs.subscribe', undefined, requestId).catch((cause) => {
|
|
803
|
+
if (!active)
|
|
804
|
+
return;
|
|
805
|
+
active = false;
|
|
806
|
+
unregisterStream(requestId);
|
|
807
|
+
console.error('[@minionry/sdk] browser.tabs.onChanged subscription failed', cause);
|
|
808
|
+
});
|
|
809
|
+
return () => {
|
|
810
|
+
if (!active)
|
|
811
|
+
return;
|
|
812
|
+
active = false;
|
|
813
|
+
unregisterStream(requestId);
|
|
814
|
+
if (!closed) {
|
|
815
|
+
request('cancel', { requestId }).catch(() => { });
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
},
|
|
819
|
+
},
|
|
820
|
+
view: {
|
|
821
|
+
async attach(opts) {
|
|
822
|
+
const requestId = mintRequestId();
|
|
823
|
+
const viewEvents = new Emitter();
|
|
824
|
+
registerStream(requestId, (event, payload) => viewEvents.emit(event, payload), (message) => viewEvents.emit('gap', { message }));
|
|
825
|
+
let attached;
|
|
826
|
+
try {
|
|
827
|
+
attached = (await request('browser.view.attach', { tabId: opts?.tabId, bounds: opts?.bounds }, requestId));
|
|
828
|
+
}
|
|
829
|
+
catch (cause) {
|
|
830
|
+
unregisterStream(requestId);
|
|
831
|
+
throw cause;
|
|
832
|
+
}
|
|
833
|
+
const sessionId = attached.sessionId;
|
|
834
|
+
let detached = false;
|
|
835
|
+
const session = {
|
|
836
|
+
id: sessionId,
|
|
837
|
+
tabId: attached.tabId,
|
|
838
|
+
on(event, cb) {
|
|
839
|
+
return viewEvents.on(event, cb);
|
|
840
|
+
},
|
|
841
|
+
async answer(sdp) {
|
|
842
|
+
await request('browser.view.answer', { sessionId, sdp });
|
|
843
|
+
},
|
|
844
|
+
async addIceCandidate(candidate) {
|
|
845
|
+
await request('browser.view.ice', { sessionId, candidate });
|
|
846
|
+
},
|
|
847
|
+
async setConnectionState(state, reason) {
|
|
848
|
+
await request('browser.view.state', { sessionId, state, reason });
|
|
849
|
+
},
|
|
850
|
+
async sendInput(input) {
|
|
851
|
+
return (await request('browser.view.input', { sessionId, input }));
|
|
852
|
+
},
|
|
853
|
+
async setBounds(bounds) {
|
|
854
|
+
await request('browser.view.bounds', { sessionId, bounds });
|
|
855
|
+
},
|
|
856
|
+
async detach() {
|
|
857
|
+
if (detached)
|
|
858
|
+
return;
|
|
859
|
+
detached = true;
|
|
860
|
+
unregisterStream(requestId);
|
|
861
|
+
await request('cancel', { requestId });
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
return session;
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
async navigate(url, tabId) {
|
|
868
|
+
await request('browser.navigate', { url, tabId });
|
|
869
|
+
},
|
|
870
|
+
async back(tabId) {
|
|
871
|
+
await request('browser.back', { tabId });
|
|
872
|
+
},
|
|
873
|
+
async forward(tabId) {
|
|
874
|
+
await request('browser.forward', { tabId });
|
|
875
|
+
},
|
|
876
|
+
async reload(tabId) {
|
|
877
|
+
await request('browser.reload', { tabId });
|
|
878
|
+
},
|
|
879
|
+
async stop(tabId) {
|
|
880
|
+
await request('browser.stop', { tabId });
|
|
881
|
+
},
|
|
882
|
+
async open(url, opts) {
|
|
883
|
+
return (await request('browser.open', { url, pinnedApp: opts?.pinnedApp }));
|
|
884
|
+
},
|
|
885
|
+
async close(tabId) {
|
|
886
|
+
await request('browser.close', { tabId });
|
|
887
|
+
},
|
|
888
|
+
async select(tabId) {
|
|
889
|
+
await request('browser.select', { tabId });
|
|
890
|
+
},
|
|
891
|
+
async snapshot(tabId) {
|
|
892
|
+
return (await request('browser.snapshot', { tabId }));
|
|
893
|
+
},
|
|
894
|
+
async screenshot(tabId, opts) {
|
|
895
|
+
return (await request('browser.screenshot', {
|
|
896
|
+
tabId,
|
|
897
|
+
fullPage: opts?.fullPage,
|
|
898
|
+
format: opts?.format,
|
|
899
|
+
quality: opts?.quality,
|
|
900
|
+
}));
|
|
901
|
+
},
|
|
902
|
+
async evaluate(tabId, expr, opts) {
|
|
903
|
+
return (await request('browser.evaluate', {
|
|
904
|
+
tabId,
|
|
905
|
+
expr,
|
|
906
|
+
world: opts?.world,
|
|
907
|
+
timeoutMs: opts?.timeoutMs,
|
|
908
|
+
}));
|
|
909
|
+
},
|
|
910
|
+
async preflight() {
|
|
911
|
+
return (await request('browser.preflight'));
|
|
912
|
+
},
|
|
913
|
+
async resumeDriver(tabId) {
|
|
914
|
+
await request('browser.resumeDriver', { tabId });
|
|
915
|
+
},
|
|
916
|
+
async setDeviceProfile(tabId, formFactor) {
|
|
917
|
+
await request('browser.setDeviceProfile', { tabId, formFactor });
|
|
918
|
+
},
|
|
919
|
+
async automate(prompt, opts) {
|
|
920
|
+
const requestId = mintRequestId();
|
|
921
|
+
const automationEvents = new Emitter();
|
|
922
|
+
registerStream(requestId, (event, payload) => automationEvents.emit(event, payload), (message) => automationEvents.emit('gap', { message }));
|
|
923
|
+
let taskId;
|
|
924
|
+
try {
|
|
925
|
+
const result = (await request('browser.automate', { prompt, engine: opts?.engine, model: opts?.model, tabId: opts?.tabId }, requestId));
|
|
926
|
+
taskId = result.taskId;
|
|
927
|
+
}
|
|
928
|
+
catch (cause) {
|
|
929
|
+
unregisterStream(requestId);
|
|
930
|
+
throw cause;
|
|
931
|
+
}
|
|
932
|
+
let cancelled = false;
|
|
933
|
+
const automation = {
|
|
934
|
+
id: taskId,
|
|
935
|
+
on(event, cb) {
|
|
936
|
+
return automationEvents.on(event, cb);
|
|
937
|
+
},
|
|
938
|
+
async approve(proposalId, verdict, editedText) {
|
|
939
|
+
await request('browser.automate.approve', { proposalId, verdict, editedText });
|
|
940
|
+
},
|
|
941
|
+
async cancel() {
|
|
942
|
+
if (cancelled)
|
|
943
|
+
return;
|
|
944
|
+
cancelled = true;
|
|
945
|
+
unregisterStream(requestId);
|
|
946
|
+
await request('cancel', { requestId });
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
return automation;
|
|
950
|
+
},
|
|
951
|
+
},
|
|
952
|
+
composer: {
|
|
953
|
+
async autocomplete(query) {
|
|
954
|
+
return (await request('composer.autocomplete', { query }));
|
|
955
|
+
},
|
|
956
|
+
async skills() {
|
|
957
|
+
return (await request('composer.skills'));
|
|
958
|
+
},
|
|
959
|
+
async snippets(query) {
|
|
960
|
+
return (await request('composer.snippets', { query }));
|
|
961
|
+
},
|
|
962
|
+
async settings() {
|
|
963
|
+
return (await request('composer.settings'));
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
terminal: {
|
|
967
|
+
sessions: {
|
|
968
|
+
async list() {
|
|
969
|
+
return (await request('terminal.sessions.list'));
|
|
970
|
+
},
|
|
971
|
+
onChanged(cb) {
|
|
972
|
+
const requestId = mintRequestId();
|
|
973
|
+
let active = true;
|
|
974
|
+
registerStream(requestId, (event, payload) => {
|
|
975
|
+
if (active && event === 'sessionChanged')
|
|
976
|
+
cb(payload);
|
|
977
|
+
}, () => { });
|
|
978
|
+
request('terminal.sessions.subscribe', undefined, requestId).catch((cause) => {
|
|
979
|
+
if (!active)
|
|
980
|
+
return;
|
|
981
|
+
active = false;
|
|
982
|
+
unregisterStream(requestId);
|
|
983
|
+
console.error('[@minionry/sdk] terminal.sessions.onChanged subscription failed', cause);
|
|
984
|
+
});
|
|
985
|
+
return () => {
|
|
986
|
+
if (!active)
|
|
987
|
+
return;
|
|
988
|
+
active = false;
|
|
989
|
+
unregisterStream(requestId);
|
|
990
|
+
if (!closed) {
|
|
991
|
+
request('cancel', { requestId }).catch(() => { });
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
async open(opts) {
|
|
997
|
+
return (await request('terminal.open', { cwd: opts.cwd, cols: opts.cols, rows: opts.rows }));
|
|
998
|
+
},
|
|
999
|
+
async attach(id) {
|
|
1000
|
+
const requestId = mintRequestId();
|
|
1001
|
+
const termEvents = new Emitter();
|
|
1002
|
+
registerStream(requestId, (event, payload) => termEvents.emit(event, payload), (message) => termEvents.emit('gap', { message }));
|
|
1003
|
+
try {
|
|
1004
|
+
await request('terminal.attach', { id }, requestId);
|
|
1005
|
+
}
|
|
1006
|
+
catch (cause) {
|
|
1007
|
+
unregisterStream(requestId);
|
|
1008
|
+
throw cause;
|
|
1009
|
+
}
|
|
1010
|
+
let detached = false;
|
|
1011
|
+
const handle = {
|
|
1012
|
+
id,
|
|
1013
|
+
on(event, cb) {
|
|
1014
|
+
return termEvents.on(event, cb);
|
|
1015
|
+
},
|
|
1016
|
+
async detach() {
|
|
1017
|
+
if (detached)
|
|
1018
|
+
return;
|
|
1019
|
+
detached = true;
|
|
1020
|
+
unregisterStream(requestId);
|
|
1021
|
+
await request('cancel', { requestId });
|
|
1022
|
+
},
|
|
1023
|
+
};
|
|
1024
|
+
return handle;
|
|
1025
|
+
},
|
|
1026
|
+
async write(id, data) {
|
|
1027
|
+
await request('terminal.write', { id, data: Array.isArray(data) ? data.join('') : data });
|
|
1028
|
+
},
|
|
1029
|
+
async resize(id, size) {
|
|
1030
|
+
await request('terminal.resize', { id, cols: size.cols, rows: size.rows });
|
|
1031
|
+
},
|
|
1032
|
+
async close(id) {
|
|
1033
|
+
await request('terminal.close', { id });
|
|
1034
|
+
},
|
|
1035
|
+
},
|
|
1036
|
+
git: {
|
|
1037
|
+
async status(req) {
|
|
1038
|
+
return (await request('git.status', { worktree: req?.worktree }));
|
|
1039
|
+
},
|
|
1040
|
+
async log(req) {
|
|
1041
|
+
return (await request('git.log', {
|
|
1042
|
+
worktree: req?.worktree,
|
|
1043
|
+
limit: req?.limit,
|
|
1044
|
+
skip: req?.skip,
|
|
1045
|
+
search: req?.search,
|
|
1046
|
+
}));
|
|
1047
|
+
},
|
|
1048
|
+
async diff(req) {
|
|
1049
|
+
return (await request('git.diff', { worktree: req.worktree, path: req.path, staged: req.staged }));
|
|
1050
|
+
},
|
|
1051
|
+
async show(hash, req) {
|
|
1052
|
+
return (await request('git.show', { worktree: req?.worktree, hash }));
|
|
1053
|
+
},
|
|
1054
|
+
async commitDiff(req) {
|
|
1055
|
+
return (await request('git.commitDiff', {
|
|
1056
|
+
worktree: req.worktree,
|
|
1057
|
+
hash: req.hash,
|
|
1058
|
+
path: req.path,
|
|
1059
|
+
}));
|
|
1060
|
+
},
|
|
1061
|
+
branches: {
|
|
1062
|
+
async list(req) {
|
|
1063
|
+
return (await request('git.branches.list', { worktree: req?.worktree }));
|
|
1064
|
+
},
|
|
1065
|
+
async create(req) {
|
|
1066
|
+
return (await request('git.branches.create', {
|
|
1067
|
+
worktree: req.worktree,
|
|
1068
|
+
name: req.name,
|
|
1069
|
+
startPoint: req.startPoint,
|
|
1070
|
+
checkout: req.checkout,
|
|
1071
|
+
}));
|
|
1072
|
+
},
|
|
1073
|
+
async delete(req) {
|
|
1074
|
+
await request('git.branches.delete', { worktree: req.worktree, name: req.name, force: req.force });
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
tags: {
|
|
1078
|
+
async list(req) {
|
|
1079
|
+
return (await request('git.tags.list', { worktree: req?.worktree }));
|
|
1080
|
+
},
|
|
1081
|
+
async create(req) {
|
|
1082
|
+
return (await request('git.tags.create', {
|
|
1083
|
+
worktree: req.worktree,
|
|
1084
|
+
name: req.name,
|
|
1085
|
+
message: req.message,
|
|
1086
|
+
commit: req.commit,
|
|
1087
|
+
}));
|
|
1088
|
+
},
|
|
1089
|
+
async push(req) {
|
|
1090
|
+
return (await request('git.tags.push', {
|
|
1091
|
+
worktree: req?.worktree,
|
|
1092
|
+
name: req?.name,
|
|
1093
|
+
all: req?.all,
|
|
1094
|
+
}));
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
remote: {
|
|
1098
|
+
async info(req) {
|
|
1099
|
+
return (await request('git.remote.info', { worktree: req?.worktree }));
|
|
1100
|
+
},
|
|
1101
|
+
},
|
|
1102
|
+
repos: {
|
|
1103
|
+
async discover() {
|
|
1104
|
+
return (await request('git.repos.discover'));
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
worktrees: {
|
|
1108
|
+
async list() {
|
|
1109
|
+
return (await request('git.worktrees.list'));
|
|
1110
|
+
},
|
|
1111
|
+
async create(req) {
|
|
1112
|
+
return (await request('git.worktrees.create', {
|
|
1113
|
+
branchName: req.branchName,
|
|
1114
|
+
baseBranch: req.baseBranch,
|
|
1115
|
+
path: req.path,
|
|
1116
|
+
assign: req.assign,
|
|
1117
|
+
}));
|
|
1118
|
+
},
|
|
1119
|
+
async remove(req) {
|
|
1120
|
+
await request('git.worktrees.remove', { path: req.path, force: req.force, deleteBranch: req.deleteBranch });
|
|
1121
|
+
},
|
|
1122
|
+
async switchActive(req) {
|
|
1123
|
+
return (await request('git.worktrees.switchActive', { path: req?.path }));
|
|
1124
|
+
},
|
|
1125
|
+
onChanged(cb) {
|
|
1126
|
+
const requestId = mintRequestId();
|
|
1127
|
+
let active = true;
|
|
1128
|
+
registerStream(requestId, (event, payload) => {
|
|
1129
|
+
if (active && event === 'worktreesChanged')
|
|
1130
|
+
cb(payload);
|
|
1131
|
+
}, () => { });
|
|
1132
|
+
request('git.worktrees.subscribe', undefined, requestId).catch((cause) => {
|
|
1133
|
+
if (!active)
|
|
1134
|
+
return;
|
|
1135
|
+
active = false;
|
|
1136
|
+
unregisterStream(requestId);
|
|
1137
|
+
console.error('[@minionry/sdk] git.worktrees.onChanged subscription failed', cause);
|
|
1138
|
+
});
|
|
1139
|
+
return () => {
|
|
1140
|
+
if (!active)
|
|
1141
|
+
return;
|
|
1142
|
+
active = false;
|
|
1143
|
+
unregisterStream(requestId);
|
|
1144
|
+
if (!closed) {
|
|
1145
|
+
request('cancel', { requestId }).catch(() => { });
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
merge: {
|
|
1151
|
+
async preview(req) {
|
|
1152
|
+
return (await request('git.merge.preview', {
|
|
1153
|
+
worktree: req.worktree,
|
|
1154
|
+
sourceBranch: req.sourceBranch,
|
|
1155
|
+
targetBranch: req.targetBranch,
|
|
1156
|
+
}));
|
|
1157
|
+
},
|
|
1158
|
+
async run(req) {
|
|
1159
|
+
return (await request('git.merge.run', {
|
|
1160
|
+
worktree: req.worktree,
|
|
1161
|
+
sourceBranch: req.sourceBranch,
|
|
1162
|
+
targetBranch: req.targetBranch,
|
|
1163
|
+
strategy: req.strategy,
|
|
1164
|
+
commitMessage: req.commitMessage,
|
|
1165
|
+
deleteWorktree: req.deleteWorktree,
|
|
1166
|
+
deleteBranch: req.deleteBranch,
|
|
1167
|
+
stashFirst: req.stashFirst,
|
|
1168
|
+
}));
|
|
1169
|
+
},
|
|
1170
|
+
async abort(req) {
|
|
1171
|
+
return (await request('git.merge.abort', { worktree: req?.worktree }));
|
|
1172
|
+
},
|
|
1173
|
+
async complete(req) {
|
|
1174
|
+
return (await request('git.merge.complete', { worktree: req?.worktree }));
|
|
1175
|
+
},
|
|
1176
|
+
async stashPop(req) {
|
|
1177
|
+
return (await request('git.merge.stashPop', {
|
|
1178
|
+
worktree: req.worktree,
|
|
1179
|
+
stashSha: req.stashSha,
|
|
1180
|
+
}));
|
|
1181
|
+
},
|
|
1182
|
+
async discardBlockers(req) {
|
|
1183
|
+
return (await request('git.merge.discardBlockers', {
|
|
1184
|
+
worktree: req.worktree,
|
|
1185
|
+
paths: req.paths,
|
|
1186
|
+
}));
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1189
|
+
pr: {
|
|
1190
|
+
async generateDescription(req) {
|
|
1191
|
+
return (await request('git.pr.generateDescription', {
|
|
1192
|
+
worktree: req?.worktree,
|
|
1193
|
+
baseBranch: req?.baseBranch,
|
|
1194
|
+
}));
|
|
1195
|
+
},
|
|
1196
|
+
async create(req) {
|
|
1197
|
+
return (await request('git.pr.create', {
|
|
1198
|
+
worktree: req.worktree,
|
|
1199
|
+
title: req.title,
|
|
1200
|
+
body: req.body,
|
|
1201
|
+
baseBranch: req.baseBranch,
|
|
1202
|
+
draft: req.draft,
|
|
1203
|
+
}));
|
|
1204
|
+
},
|
|
1205
|
+
},
|
|
1206
|
+
async stage(req) {
|
|
1207
|
+
return (await request('git.stage', {
|
|
1208
|
+
worktree: req.worktree,
|
|
1209
|
+
paths: req.paths,
|
|
1210
|
+
stageAll: req.stageAll,
|
|
1211
|
+
}));
|
|
1212
|
+
},
|
|
1213
|
+
async unstage(req) {
|
|
1214
|
+
return (await request('git.unstage', { worktree: req.worktree, paths: req.paths }));
|
|
1215
|
+
},
|
|
1216
|
+
async commit(req) {
|
|
1217
|
+
return (await request('git.commit', { worktree: req.worktree, message: req.message }));
|
|
1218
|
+
},
|
|
1219
|
+
async generateCommitMessage(req) {
|
|
1220
|
+
return (await request('git.generateCommitMessage', {
|
|
1221
|
+
worktree: req?.worktree,
|
|
1222
|
+
}));
|
|
1223
|
+
},
|
|
1224
|
+
async checkout(req) {
|
|
1225
|
+
return (await request('git.checkout', {
|
|
1226
|
+
worktree: req.worktree,
|
|
1227
|
+
branch: req.branch,
|
|
1228
|
+
create: req.create,
|
|
1229
|
+
startPoint: req.startPoint,
|
|
1230
|
+
}));
|
|
1231
|
+
},
|
|
1232
|
+
async push(req) {
|
|
1233
|
+
return (await request('git.push', {
|
|
1234
|
+
worktree: req?.worktree,
|
|
1235
|
+
remote: req?.remote,
|
|
1236
|
+
branch: req?.branch,
|
|
1237
|
+
setUpstream: req?.setUpstream,
|
|
1238
|
+
}));
|
|
1239
|
+
},
|
|
1240
|
+
async pull(req) {
|
|
1241
|
+
return (await request('git.pull', { worktree: req?.worktree }));
|
|
1242
|
+
},
|
|
1243
|
+
onBranchChanged(cb) {
|
|
1244
|
+
const requestId = mintRequestId();
|
|
1245
|
+
let active = true;
|
|
1246
|
+
registerStream(requestId, (event, payload) => {
|
|
1247
|
+
if (active && event === 'branchChanged')
|
|
1248
|
+
cb(payload);
|
|
1249
|
+
}, () => { });
|
|
1250
|
+
request('git.subscribeBranchChanged', undefined, requestId).catch((cause) => {
|
|
1251
|
+
if (!active)
|
|
1252
|
+
return;
|
|
1253
|
+
active = false;
|
|
1254
|
+
unregisterStream(requestId);
|
|
1255
|
+
console.error('[@minionry/sdk] git.onBranchChanged subscription failed', cause);
|
|
1256
|
+
});
|
|
1257
|
+
return () => {
|
|
1258
|
+
if (!active)
|
|
1259
|
+
return;
|
|
1260
|
+
active = false;
|
|
1261
|
+
unregisterStream(requestId);
|
|
1262
|
+
if (!closed) {
|
|
1263
|
+
request('cancel', { requestId }).catch(() => { });
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
},
|
|
1267
|
+
onCommitSuggestion(cb) {
|
|
1268
|
+
const requestId = mintRequestId();
|
|
1269
|
+
let active = true;
|
|
1270
|
+
registerStream(requestId, (event, payload) => {
|
|
1271
|
+
if (active && event === 'commitSuggestion')
|
|
1272
|
+
cb(payload);
|
|
1273
|
+
}, () => { });
|
|
1274
|
+
request('git.subscribeCommitSuggestion', undefined, requestId).catch((cause) => {
|
|
1275
|
+
if (!active)
|
|
1276
|
+
return;
|
|
1277
|
+
active = false;
|
|
1278
|
+
unregisterStream(requestId);
|
|
1279
|
+
console.error('[@minionry/sdk] git.onCommitSuggestion subscription failed', cause);
|
|
1280
|
+
});
|
|
1281
|
+
return () => {
|
|
1282
|
+
if (!active)
|
|
1283
|
+
return;
|
|
1284
|
+
active = false;
|
|
1285
|
+
unregisterStream(requestId);
|
|
1286
|
+
if (!closed) {
|
|
1287
|
+
request('cancel', { requestId }).catch(() => { });
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
},
|
|
1291
|
+
},
|
|
1292
|
+
tools: {
|
|
1293
|
+
async register(req) {
|
|
1294
|
+
const requestId = mintRequestId();
|
|
1295
|
+
const tools = new Map();
|
|
1296
|
+
for (const tool of req.tools) {
|
|
1297
|
+
if (tools.has(tool.name)) {
|
|
1298
|
+
throw new MinionryError('BAD_REQUEST', `tools.register: duplicate tool name '${tool.name}'`);
|
|
1299
|
+
}
|
|
1300
|
+
tools.set(tool.name, tool);
|
|
1301
|
+
}
|
|
1302
|
+
toolRegistrations.set(requestId, { tools, calls: new Map() });
|
|
1303
|
+
try {
|
|
1304
|
+
await request('tools.register', {
|
|
1305
|
+
tools: req.tools.map(({ name, description, inputSchema, timeoutMs }) => ({
|
|
1306
|
+
name,
|
|
1307
|
+
description,
|
|
1308
|
+
inputSchema,
|
|
1309
|
+
timeoutMs,
|
|
1310
|
+
})),
|
|
1311
|
+
profile: req.profile,
|
|
1312
|
+
}, requestId);
|
|
1313
|
+
}
|
|
1314
|
+
catch (cause) {
|
|
1315
|
+
retireToolRegistration(requestId);
|
|
1316
|
+
throw cause;
|
|
1317
|
+
}
|
|
1318
|
+
let unregistered = false;
|
|
1319
|
+
const registration = {
|
|
1320
|
+
id: requestId,
|
|
1321
|
+
tools: [...tools.keys()],
|
|
1322
|
+
async unregister() {
|
|
1323
|
+
if (unregistered)
|
|
1324
|
+
return;
|
|
1325
|
+
unregistered = true;
|
|
1326
|
+
retireToolRegistration(requestId);
|
|
1327
|
+
await request('cancel', { requestId });
|
|
1328
|
+
},
|
|
1329
|
+
};
|
|
1330
|
+
return registration;
|
|
1331
|
+
},
|
|
1332
|
+
},
|
|
1333
|
+
quality: {
|
|
1334
|
+
async state() {
|
|
1335
|
+
return (await request('quality.state'));
|
|
1336
|
+
},
|
|
1337
|
+
async detectTools(path) {
|
|
1338
|
+
return (await request('quality.detectTools', { path }));
|
|
1339
|
+
},
|
|
1340
|
+
async run(path) {
|
|
1341
|
+
const requestId = mintRequestId();
|
|
1342
|
+
const runEvents = new Emitter();
|
|
1343
|
+
registerStream(requestId, (event, payload) => runEvents.emit(event, payload), () => { });
|
|
1344
|
+
let started;
|
|
1345
|
+
try {
|
|
1346
|
+
started = (await request('quality.run', { path }, requestId));
|
|
1347
|
+
}
|
|
1348
|
+
catch (cause) {
|
|
1349
|
+
unregisterStream(requestId);
|
|
1350
|
+
throw cause;
|
|
1351
|
+
}
|
|
1352
|
+
const runPath = started.path;
|
|
1353
|
+
let resultPromise;
|
|
1354
|
+
const run = {
|
|
1355
|
+
path: runPath,
|
|
1356
|
+
attached: started.attached === true,
|
|
1357
|
+
on(event, cb) {
|
|
1358
|
+
return runEvents.on(event, cb);
|
|
1359
|
+
},
|
|
1360
|
+
result() {
|
|
1361
|
+
resultPromise ??= request('quality.result', { path: runPath }).catch((cause) => {
|
|
1362
|
+
resultPromise = undefined;
|
|
1363
|
+
throw cause;
|
|
1364
|
+
});
|
|
1365
|
+
return resultPromise;
|
|
1366
|
+
},
|
|
1367
|
+
};
|
|
1368
|
+
return run;
|
|
1369
|
+
},
|
|
1370
|
+
async cancel(path) {
|
|
1371
|
+
await request('quality.cancel', { path });
|
|
1372
|
+
},
|
|
1373
|
+
async installTools(path, tools) {
|
|
1374
|
+
return (await request('quality.installTools', { path, tools }));
|
|
1375
|
+
},
|
|
1376
|
+
async saveDirectories(directories) {
|
|
1377
|
+
await request('quality.saveDirectories', { directories });
|
|
1378
|
+
},
|
|
1379
|
+
async settings() {
|
|
1380
|
+
return (await request('quality.settings'));
|
|
1381
|
+
},
|
|
1382
|
+
async setSettings(settings) {
|
|
1383
|
+
return (await request('quality.setSettings', settings));
|
|
1384
|
+
},
|
|
1385
|
+
onChanged(cb) {
|
|
1386
|
+
const requestId = mintRequestId();
|
|
1387
|
+
let active = true;
|
|
1388
|
+
registerStream(requestId, (event, payload) => {
|
|
1389
|
+
if (active && event === 'changed')
|
|
1390
|
+
cb(payload);
|
|
1391
|
+
}, () => { });
|
|
1392
|
+
request('quality.subscribeChanges', undefined, requestId).catch((cause) => {
|
|
1393
|
+
if (!active)
|
|
1394
|
+
return;
|
|
1395
|
+
active = false;
|
|
1396
|
+
unregisterStream(requestId);
|
|
1397
|
+
console.error('[@minionry/sdk] quality.onChanged subscription failed', cause);
|
|
1398
|
+
});
|
|
1399
|
+
return () => {
|
|
1400
|
+
if (!active)
|
|
1401
|
+
return;
|
|
1402
|
+
active = false;
|
|
1403
|
+
unregisterStream(requestId);
|
|
1404
|
+
if (!closed) {
|
|
1405
|
+
request('cancel', { requestId }).catch(() => { });
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
},
|
|
1409
|
+
async setFindingState(update) {
|
|
1410
|
+
return (await request('quality.setFindingState', update));
|
|
1411
|
+
},
|
|
1412
|
+
schedules: {
|
|
1413
|
+
async list() {
|
|
1414
|
+
const result = (await request('quality.schedules.list', {}));
|
|
1415
|
+
return result.schedules;
|
|
1416
|
+
},
|
|
1417
|
+
async create(req) {
|
|
1418
|
+
return (await request('quality.schedules.create', req));
|
|
1419
|
+
},
|
|
1420
|
+
async update(scheduleId, patch) {
|
|
1421
|
+
return (await request('quality.schedules.update', { scheduleId, patch }));
|
|
1422
|
+
},
|
|
1423
|
+
async delete(scheduleId) {
|
|
1424
|
+
await request('quality.schedules.delete', { scheduleId });
|
|
1425
|
+
},
|
|
1426
|
+
},
|
|
1427
|
+
},
|
|
1428
|
+
inference: {
|
|
1429
|
+
async chat(req) {
|
|
1430
|
+
const requestId = mintRequestId();
|
|
1431
|
+
const runEvents = new Emitter();
|
|
1432
|
+
registerStream(requestId, (event, payload) => runEvents.emit(event, payload), (message) => runEvents.emit('gap', { message }));
|
|
1433
|
+
let runId;
|
|
1434
|
+
try {
|
|
1435
|
+
const params = {
|
|
1436
|
+
messages: req.messages,
|
|
1437
|
+
system: req.system,
|
|
1438
|
+
engine: req.engine,
|
|
1439
|
+
model: req.model,
|
|
1440
|
+
effortLevel: req.effortLevel,
|
|
1441
|
+
};
|
|
1442
|
+
runId = (await request('inference.chat', params, requestId)).runId;
|
|
1443
|
+
}
|
|
1444
|
+
catch (cause) {
|
|
1445
|
+
unregisterStream(requestId);
|
|
1446
|
+
throw cause;
|
|
1447
|
+
}
|
|
1448
|
+
let resultPromise;
|
|
1449
|
+
const run = {
|
|
1450
|
+
id: runId,
|
|
1451
|
+
on(event, cb) {
|
|
1452
|
+
return runEvents.on(event, cb);
|
|
1453
|
+
},
|
|
1454
|
+
result() {
|
|
1455
|
+
resultPromise ??= request('inference.result', { runId }).catch((cause) => {
|
|
1456
|
+
resultPromise = undefined;
|
|
1457
|
+
throw cause;
|
|
1458
|
+
});
|
|
1459
|
+
return resultPromise;
|
|
1460
|
+
},
|
|
1461
|
+
async cancel() {
|
|
1462
|
+
await request('cancel', { requestId });
|
|
1463
|
+
},
|
|
1464
|
+
};
|
|
1465
|
+
return run;
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
endpoints: {
|
|
1469
|
+
async register(req) {
|
|
1470
|
+
await requireFeature(APP_ENDPOINTS_FEATURE, 'endpoints.register');
|
|
1471
|
+
return (await request('endpoints.register', req));
|
|
1472
|
+
},
|
|
1473
|
+
async unregister(id) {
|
|
1474
|
+
await requireFeature(APP_ENDPOINTS_FEATURE, 'endpoints.unregister');
|
|
1475
|
+
await request('endpoints.unregister', { id });
|
|
1476
|
+
},
|
|
1477
|
+
async list() {
|
|
1478
|
+
await requireFeature(APP_ENDPOINTS_FEATURE, 'endpoints.list');
|
|
1479
|
+
return (await request('endpoints.list'));
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
on(event, cb) {
|
|
1483
|
+
const unsubscribe = providerEvents.on(event, cb);
|
|
1484
|
+
if (event === 'intent') {
|
|
1485
|
+
intentListenerCount++;
|
|
1486
|
+
if (pendingIntent !== undefined) {
|
|
1487
|
+
const intent = pendingIntent;
|
|
1488
|
+
pendingIntent = undefined;
|
|
1489
|
+
cb(intent);
|
|
1490
|
+
}
|
|
1491
|
+
let done = false;
|
|
1492
|
+
return () => {
|
|
1493
|
+
if (done)
|
|
1494
|
+
return;
|
|
1495
|
+
done = true;
|
|
1496
|
+
intentListenerCount--;
|
|
1497
|
+
unsubscribe();
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return unsubscribe;
|
|
1501
|
+
},
|
|
1502
|
+
};
|
|
1503
|
+
}
|