@framers/agentos-ext-meeting-browser 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +98 -0
  2. package/README.md +173 -2
  3. package/dist/BrowserMeetingProvider.d.ts +74 -0
  4. package/dist/BrowserMeetingProvider.d.ts.map +1 -0
  5. package/dist/BrowserMeetingProvider.js +136 -0
  6. package/dist/BrowserMeetingProvider.js.map +1 -0
  7. package/dist/index.d.ts +21 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +23 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/protocol.d.ts +294 -0
  12. package/dist/protocol.d.ts.map +1 -0
  13. package/dist/protocol.js +81 -0
  14. package/dist/protocol.js.map +1 -0
  15. package/dist/scheduler/channel.d.ts +45 -0
  16. package/dist/scheduler/channel.d.ts.map +1 -0
  17. package/dist/scheduler/channel.js +122 -0
  18. package/dist/scheduler/channel.js.map +1 -0
  19. package/dist/scheduler/docker.d.ts +86 -0
  20. package/dist/scheduler/docker.d.ts.map +1 -0
  21. package/dist/scheduler/docker.js +35 -0
  22. package/dist/scheduler/docker.js.map +1 -0
  23. package/dist/scheduler/main.d.ts +3 -0
  24. package/dist/scheduler/main.d.ts.map +1 -0
  25. package/dist/scheduler/main.js +79 -0
  26. package/dist/scheduler/main.js.map +1 -0
  27. package/dist/scheduler/runs.d.ts +75 -0
  28. package/dist/scheduler/runs.d.ts.map +1 -0
  29. package/dist/scheduler/runs.js +123 -0
  30. package/dist/scheduler/runs.js.map +1 -0
  31. package/dist/scheduler/server.d.ts +177 -0
  32. package/dist/scheduler/server.d.ts.map +1 -0
  33. package/dist/scheduler/server.js +653 -0
  34. package/dist/scheduler/server.js.map +1 -0
  35. package/dist/scheduler/urls.d.ts +28 -0
  36. package/dist/scheduler/urls.d.ts.map +1 -0
  37. package/dist/scheduler/urls.js +49 -0
  38. package/dist/scheduler/urls.js.map +1 -0
  39. package/manifest.json +25 -0
  40. package/package.json +73 -3
@@ -0,0 +1,294 @@
1
+ import { z } from 'zod';
2
+ /** The platforms the browser bot joins. */
3
+ export declare const PLATFORMS: readonly ["google_meet", "microsoft_teams"];
4
+ /** The job the scheduler sends a container as the channel's first message. */
5
+ export declare const JobMessage: z.ZodObject<{
6
+ type: z.ZodLiteral<"job">;
7
+ runId: z.ZodString;
8
+ platform: z.ZodEnum<{
9
+ google_meet: "google_meet";
10
+ microsoft_teams: "microsoft_teams";
11
+ }>;
12
+ meetingUrl: z.ZodString;
13
+ botName: z.ZodString;
14
+ notice: z.ZodObject<{
15
+ text: z.ZodString;
16
+ version: z.ZodString;
17
+ lateArrivalText: z.ZodString;
18
+ }, z.core.$strip>;
19
+ stopPhrase: z.ZodString;
20
+ limits: z.ZodObject<{
21
+ waitingRoomMs: z.ZodNumber;
22
+ aloneMs: z.ZodNumber;
23
+ totalMs: z.ZodNumber;
24
+ }, z.core.$strip>;
25
+ livekit: z.ZodObject<{
26
+ url: z.ZodString;
27
+ token: z.ZodString;
28
+ identity: z.ZodString;
29
+ }, z.core.$strip>;
30
+ profileDir: z.ZodOptional<z.ZodString>;
31
+ adapter: z.ZodOptional<z.ZodLiteral<"fixture">>;
32
+ }, z.core.$strip>;
33
+ /** A job, as {@link JobMessage} parses it. */
34
+ export type JobMessage = z.infer<typeof JobMessage>;
35
+ /** The commands the scheduler relays to a container. */
36
+ export declare const CommandName: z.ZodEnum<{
37
+ startCapture: "startCapture";
38
+ suspendCapture: "suspendCapture";
39
+ postChat: "postChat";
40
+ leave: "leave";
41
+ kill: "kill";
42
+ }>;
43
+ /** A command down the channel, with the id its acknowledgement names. */
44
+ export declare const CommandMessage: z.ZodObject<{
45
+ type: z.ZodLiteral<"command">;
46
+ id: z.ZodString;
47
+ name: z.ZodEnum<{
48
+ startCapture: "startCapture";
49
+ suspendCapture: "suspendCapture";
50
+ postChat: "postChat";
51
+ leave: "leave";
52
+ kill: "kill";
53
+ }>;
54
+ text: z.ZodOptional<z.ZodString>;
55
+ reason: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strip>;
57
+ /** A command, as {@link CommandMessage} parses it. */
58
+ export type CommandMessage = z.infer<typeof CommandMessage>;
59
+ /** A container's answer to one command, by the command's id. */
60
+ export declare const AckMessage: z.ZodObject<{
61
+ type: z.ZodLiteral<"ack">;
62
+ id: z.ZodString;
63
+ ok: z.ZodBoolean;
64
+ error: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>;
66
+ /** An acknowledgement, as {@link AckMessage} parses it. */
67
+ export type AckMessage = z.infer<typeof AckMessage>;
68
+ /** The lifecycle events a container reports: the channel's provider event types. */
69
+ export declare const ReportEvent: z.ZodEnum<{
70
+ error: "error";
71
+ joining: "joining";
72
+ waiting_room: "waiting_room";
73
+ in_call: "in_call";
74
+ notice_posted: "notice_posted";
75
+ monitor_heartbeat: "monitor_heartbeat";
76
+ participant_joined: "participant_joined";
77
+ participant_left: "participant_left";
78
+ chat_message: "chat_message";
79
+ stop_command: "stop_command";
80
+ capture_started: "capture_started";
81
+ capture_suspended: "capture_suspended";
82
+ left: "left";
83
+ done: "done";
84
+ crashed: "crashed";
85
+ usage: "usage";
86
+ }>;
87
+ /** One report up the channel, numbered by the container's sequence. */
88
+ export declare const ReportMessage: z.ZodObject<{
89
+ type: z.ZodLiteral<"report">;
90
+ seq: z.ZodNumber;
91
+ event: z.ZodEnum<{
92
+ error: "error";
93
+ joining: "joining";
94
+ waiting_room: "waiting_room";
95
+ in_call: "in_call";
96
+ notice_posted: "notice_posted";
97
+ monitor_heartbeat: "monitor_heartbeat";
98
+ participant_joined: "participant_joined";
99
+ participant_left: "participant_left";
100
+ chat_message: "chat_message";
101
+ stop_command: "stop_command";
102
+ capture_started: "capture_started";
103
+ capture_suspended: "capture_suspended";
104
+ left: "left";
105
+ done: "done";
106
+ crashed: "crashed";
107
+ usage: "usage";
108
+ }>;
109
+ at: z.ZodNumber;
110
+ data: z.ZodOptional<z.ZodObject<{
111
+ reason: z.ZodOptional<z.ZodString>;
112
+ seconds: z.ZodOptional<z.ZodNumber>;
113
+ text: z.ZodOptional<z.ZodString>;
114
+ sender: z.ZodOptional<z.ZodString>;
115
+ participantRef: z.ZodOptional<z.ZodString>;
116
+ participantCount: z.ZodOptional<z.ZodNumber>;
117
+ message: z.ZodOptional<z.ZodString>;
118
+ }, z.core.$strip>>;
119
+ }, z.core.$strip>;
120
+ /** A report, as {@link ReportMessage} parses it. */
121
+ export type ReportMessage = z.infer<typeof ReportMessage>;
122
+ /** What the container sends the scheduler, or the scheduler the container. */
123
+ export declare const ChannelMessage: z.ZodDiscriminatedUnion<[z.ZodObject<{
124
+ type: z.ZodLiteral<"job">;
125
+ runId: z.ZodString;
126
+ platform: z.ZodEnum<{
127
+ google_meet: "google_meet";
128
+ microsoft_teams: "microsoft_teams";
129
+ }>;
130
+ meetingUrl: z.ZodString;
131
+ botName: z.ZodString;
132
+ notice: z.ZodObject<{
133
+ text: z.ZodString;
134
+ version: z.ZodString;
135
+ lateArrivalText: z.ZodString;
136
+ }, z.core.$strip>;
137
+ stopPhrase: z.ZodString;
138
+ limits: z.ZodObject<{
139
+ waitingRoomMs: z.ZodNumber;
140
+ aloneMs: z.ZodNumber;
141
+ totalMs: z.ZodNumber;
142
+ }, z.core.$strip>;
143
+ livekit: z.ZodObject<{
144
+ url: z.ZodString;
145
+ token: z.ZodString;
146
+ identity: z.ZodString;
147
+ }, z.core.$strip>;
148
+ profileDir: z.ZodOptional<z.ZodString>;
149
+ adapter: z.ZodOptional<z.ZodLiteral<"fixture">>;
150
+ }, z.core.$strip>, z.ZodObject<{
151
+ type: z.ZodLiteral<"command">;
152
+ id: z.ZodString;
153
+ name: z.ZodEnum<{
154
+ startCapture: "startCapture";
155
+ suspendCapture: "suspendCapture";
156
+ postChat: "postChat";
157
+ leave: "leave";
158
+ kill: "kill";
159
+ }>;
160
+ text: z.ZodOptional<z.ZodString>;
161
+ reason: z.ZodOptional<z.ZodString>;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ type: z.ZodLiteral<"ack">;
164
+ id: z.ZodString;
165
+ ok: z.ZodBoolean;
166
+ error: z.ZodOptional<z.ZodString>;
167
+ }, z.core.$strip>, z.ZodObject<{
168
+ type: z.ZodLiteral<"report">;
169
+ seq: z.ZodNumber;
170
+ event: z.ZodEnum<{
171
+ error: "error";
172
+ joining: "joining";
173
+ waiting_room: "waiting_room";
174
+ in_call: "in_call";
175
+ notice_posted: "notice_posted";
176
+ monitor_heartbeat: "monitor_heartbeat";
177
+ participant_joined: "participant_joined";
178
+ participant_left: "participant_left";
179
+ chat_message: "chat_message";
180
+ stop_command: "stop_command";
181
+ capture_started: "capture_started";
182
+ capture_suspended: "capture_suspended";
183
+ left: "left";
184
+ done: "done";
185
+ crashed: "crashed";
186
+ usage: "usage";
187
+ }>;
188
+ at: z.ZodNumber;
189
+ data: z.ZodOptional<z.ZodObject<{
190
+ reason: z.ZodOptional<z.ZodString>;
191
+ seconds: z.ZodOptional<z.ZodNumber>;
192
+ text: z.ZodOptional<z.ZodString>;
193
+ sender: z.ZodOptional<z.ZodString>;
194
+ participantRef: z.ZodOptional<z.ZodString>;
195
+ participantCount: z.ZodOptional<z.ZodNumber>;
196
+ message: z.ZodOptional<z.ZodString>;
197
+ }, z.core.$strip>>;
198
+ }, z.core.$strip>], "type">;
199
+ /** A channel message, as {@link ChannelMessage} parses it. */
200
+ export type ChannelMessage = z.infer<typeof ChannelMessage>;
201
+ /** The scheduler's `POST /runs` body, from the provider. */
202
+ export declare const StartRunRequest: z.ZodObject<{
203
+ runId: z.ZodString;
204
+ platform: z.ZodEnum<{
205
+ google_meet: "google_meet";
206
+ microsoft_teams: "microsoft_teams";
207
+ }>;
208
+ meetingUrl: z.ZodString;
209
+ botName: z.ZodString;
210
+ notice: z.ZodObject<{
211
+ text: z.ZodString;
212
+ version: z.ZodString;
213
+ lateArrivalText: z.ZodString;
214
+ }, z.core.$strip>;
215
+ stopPhrase: z.ZodString;
216
+ limits: z.ZodObject<{
217
+ waitingRoomMs: z.ZodNumber;
218
+ aloneMs: z.ZodNumber;
219
+ totalMs: z.ZodNumber;
220
+ }, z.core.$strip>;
221
+ livekit: z.ZodObject<{
222
+ url: z.ZodString;
223
+ token: z.ZodString;
224
+ identity: z.ZodString;
225
+ }, z.core.$strip>;
226
+ workerToken: z.ZodString;
227
+ reportUrl: z.ZodString;
228
+ }, z.core.$strip>;
229
+ /** A start request, as {@link StartRunRequest} parses it. */
230
+ export type StartRunRequest = z.infer<typeof StartRunRequest>;
231
+ /** What the scheduler posts to `reportUrl`: one report, with the run id, under `Authorization: Bearer <workerToken>`. */
232
+ export declare const ReportPost: z.ZodObject<{
233
+ type: z.ZodLiteral<"report">;
234
+ seq: z.ZodNumber;
235
+ event: z.ZodEnum<{
236
+ error: "error";
237
+ joining: "joining";
238
+ waiting_room: "waiting_room";
239
+ in_call: "in_call";
240
+ notice_posted: "notice_posted";
241
+ monitor_heartbeat: "monitor_heartbeat";
242
+ participant_joined: "participant_joined";
243
+ participant_left: "participant_left";
244
+ chat_message: "chat_message";
245
+ stop_command: "stop_command";
246
+ capture_started: "capture_started";
247
+ capture_suspended: "capture_suspended";
248
+ left: "left";
249
+ done: "done";
250
+ crashed: "crashed";
251
+ usage: "usage";
252
+ }>;
253
+ at: z.ZodNumber;
254
+ data: z.ZodOptional<z.ZodObject<{
255
+ reason: z.ZodOptional<z.ZodString>;
256
+ seconds: z.ZodOptional<z.ZodNumber>;
257
+ text: z.ZodOptional<z.ZodString>;
258
+ sender: z.ZodOptional<z.ZodString>;
259
+ participantRef: z.ZodOptional<z.ZodString>;
260
+ participantCount: z.ZodOptional<z.ZodNumber>;
261
+ message: z.ZodOptional<z.ZodString>;
262
+ }, z.core.$strip>>;
263
+ runId: z.ZodString;
264
+ workerId: z.ZodString;
265
+ }, z.core.$strip>;
266
+ /** A forwarded report, as {@link ReportPost} parses it. */
267
+ export type ReportPost = z.infer<typeof ReportPost>;
268
+ /** One run as the scheduler's `GET /runs` lists it, and as `POST /runs` answers. */
269
+ export declare const RunStatus: z.ZodObject<{
270
+ runId: z.ZodString;
271
+ state: z.ZodEnum<{
272
+ starting: "starting";
273
+ running: "running";
274
+ leaving: "leaving";
275
+ exited: "exited";
276
+ }>;
277
+ startedAt: z.ZodNumber;
278
+ seconds: z.ZodNumber;
279
+ containerId: z.ZodOptional<z.ZodString>;
280
+ }, z.core.$strip>;
281
+ /** A run's status, as {@link RunStatus} parses it. */
282
+ export type RunStatus = z.infer<typeof RunStatus>;
283
+ /** The scheduler's `GET /capacity` answer: the worker's slots, how many are busy, and whether it takes new runs. */
284
+ export declare const Capacity: z.ZodObject<{
285
+ workerId: z.ZodString;
286
+ slots: z.ZodNumber;
287
+ busy: z.ZodNumber;
288
+ paused: z.ZodBoolean;
289
+ }, z.core.$strip>;
290
+ /** A worker's capacity, as {@link Capacity} parses it. */
291
+ export type Capacity = z.infer<typeof Capacity>;
292
+ /** A stable event id for a report: the run, the worker's sequence number and the event. */
293
+ export declare function reportEventId(runId: string, seq: number, event: string): string;
294
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,2CAA2C;AAC3C,eAAO,MAAM,SAAS,6CAA8C,CAAC;AAErE,8EAA8E;AAC9E,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBrB,CAAC;AACH,8CAA8C;AAC9C,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,wDAAwD;AACxD,eAAO,MAAM,WAAW;;;;;;EAA0E,CAAC;AACnG,yEAAyE;AACzE,eAAO,MAAM,cAAc;;;;;;;;;;;;iBAMzB,CAAC;AACH,sDAAsD;AACtD,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,gEAAgE;AAChE,eAAO,MAAM,UAAU;;;;;iBAAsG,CAAC;AAC9H,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,oFAAoF;AACpF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;EAAsP,CAAC;AAC/Q,uEAAuE;AACvE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAcxB,CAAC;AACH,oDAAoD;AACpD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,8EAA8E;AAC9E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAwF,CAAC;AACpH,8DAA8D;AAC9D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,4DAA4D;AAC5D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa1B,CAAC;AACH,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE9D,yHAAyH;AACzH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAkF,CAAC;AAC1G,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAEpD,oFAAoF;AACpF,eAAO,MAAM,SAAS;;;;;;;;;;;iBAA+K,CAAC;AACtM,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAClD,oHAAoH;AACpH,eAAO,MAAM,QAAQ;;;;;iBAA2G,CAAC;AACjI,0DAA0D;AAC1D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEhD,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/E"}
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ /** The platforms the browser bot joins. */
3
+ export const PLATFORMS = ['google_meet', 'microsoft_teams'];
4
+ /** The job the scheduler sends a container as the channel's first message. */
5
+ export const JobMessage = z.object({
6
+ type: z.literal('job'),
7
+ runId: z.string().min(1),
8
+ platform: z.enum(PLATFORMS),
9
+ meetingUrl: z.string().url(),
10
+ botName: z.string().min(1).max(80),
11
+ notice: z.object({ text: z.string().min(1).max(2000), version: z.string(), lateArrivalText: z.string().min(1).max(500) }),
12
+ stopPhrase: z.string().min(2).max(80),
13
+ limits: z.object({ waitingRoomMs: z.number().int().positive(), aloneMs: z.number().int().positive(), totalMs: z.number().int().positive() }),
14
+ livekit: z.object({ url: z.string().url(), token: z.string().min(1), identity: z.string().min(1) }),
15
+ /** The profile directory inside the container (Meet), absent for a guest join (Teams). */
16
+ profileDir: z.string().optional(),
17
+ /**
18
+ * `fixture` when the scheduler took the link's host from its test allowlist (`MEETING_RESOLVE_ALLOW`): the bot drives
19
+ * the fixture page's adapter, whose network policy admits that host. A production scheduler's allowlist is empty, so
20
+ * no production job carries it, and the product's `StartRunRequest` has no way to ask for it.
21
+ */
22
+ adapter: z.literal('fixture').optional(),
23
+ });
24
+ /** The commands the scheduler relays to a container. */
25
+ export const CommandName = z.enum(['startCapture', 'suspendCapture', 'postChat', 'leave', 'kill']);
26
+ /** A command down the channel, with the id its acknowledgement names. */
27
+ export const CommandMessage = z.object({
28
+ type: z.literal('command'),
29
+ id: z.string().min(1),
30
+ name: CommandName,
31
+ text: z.string().max(2000).optional(),
32
+ reason: z.string().optional(),
33
+ });
34
+ /** A container's answer to one command, by the command's id. */
35
+ export const AckMessage = z.object({ type: z.literal('ack'), id: z.string(), ok: z.boolean(), error: z.string().optional() });
36
+ /** The lifecycle events a container reports: the channel's provider event types. */
37
+ export const ReportEvent = z.enum(['joining', 'waiting_room', 'in_call', 'notice_posted', 'monitor_heartbeat', 'participant_joined', 'participant_left', 'chat_message', 'stop_command', 'capture_started', 'capture_suspended', 'left', 'done', 'crashed', 'usage', 'error']);
38
+ /** One report up the channel, numbered by the container's sequence. */
39
+ export const ReportMessage = z.object({
40
+ type: z.literal('report'),
41
+ seq: z.number().int().nonnegative(),
42
+ event: ReportEvent,
43
+ at: z.number().int().nonnegative(),
44
+ data: z.object({
45
+ reason: z.string().optional(),
46
+ seconds: z.number().nonnegative().optional(),
47
+ text: z.string().max(4000).optional(),
48
+ sender: z.string().max(200).optional(),
49
+ participantRef: z.string().max(200).optional(),
50
+ participantCount: z.number().int().nonnegative().optional(),
51
+ message: z.string().max(2000).optional(),
52
+ }).optional(),
53
+ });
54
+ /** What the container sends the scheduler, or the scheduler the container. */
55
+ export const ChannelMessage = z.discriminatedUnion('type', [JobMessage, CommandMessage, AckMessage, ReportMessage]);
56
+ /** The scheduler's `POST /runs` body, from the provider. */
57
+ export const StartRunRequest = z.object({
58
+ runId: z.string().min(1),
59
+ platform: z.enum(PLATFORMS),
60
+ meetingUrl: z.string().url(),
61
+ botName: z.string().min(1).max(80),
62
+ notice: JobMessage.shape.notice,
63
+ stopPhrase: z.string().min(2).max(80),
64
+ limits: JobMessage.shape.limits,
65
+ livekit: JobMessage.shape.livekit,
66
+ /** The per-run bearer the container presents and the scheduler forwards on every report. */
67
+ workerToken: z.string().min(16),
68
+ /** Where the scheduler posts the run's reports. */
69
+ reportUrl: z.string().url(),
70
+ });
71
+ /** What the scheduler posts to `reportUrl`: one report, with the run id, under `Authorization: Bearer <workerToken>`. */
72
+ export const ReportPost = ReportMessage.extend({ runId: z.string().min(1), workerId: z.string().min(1) });
73
+ /** One run as the scheduler's `GET /runs` lists it, and as `POST /runs` answers. */
74
+ export const RunStatus = z.object({ runId: z.string(), state: z.enum(['starting', 'running', 'leaving', 'exited']), startedAt: z.number(), seconds: z.number(), containerId: z.string().optional() });
75
+ /** The scheduler's `GET /capacity` answer: the worker's slots, how many are busy, and whether it takes new runs. */
76
+ export const Capacity = z.object({ workerId: z.string(), slots: z.number().int(), busy: z.number().int(), paused: z.boolean() });
77
+ /** A stable event id for a report: the run, the worker's sequence number and the event. */
78
+ export function reportEventId(runId, seq, event) {
79
+ return `browser:${runId}:${seq}:${event}`;
80
+ }
81
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { WebSocket } from 'ws';
2
+ import { type AckMessage, type CommandMessage, type JobMessage, type ReportMessage } from '../protocol.js';
3
+ /** How long a command waits for the container's acknowledgement. */
4
+ export declare const ACK_TIMEOUT_MS = 10000;
5
+ /** How often the scheduler pings a channel; a channel that has not answered the last ping by the next is cut. */
6
+ export declare const HEARTBEAT_MS = 15000;
7
+ /**
8
+ * The scheduler's end of one run's command channel: it sends the job, sends commands and waits for their
9
+ * acknowledgements, and hands the container's reports on. A message that does not parse, or that is not the container's
10
+ * to send (a job, a command), is dropped. A socket that stops answering pings is cut, so a link that broke without a
11
+ * close never holds the run's one channel against the bot's reconnect.
12
+ */
13
+ export declare class ChannelPeer {
14
+ private readonly ws;
15
+ private readonly ackTimeoutMs;
16
+ private readonly pending;
17
+ private reportHandler;
18
+ private readonly closeHandlers;
19
+ private readonly heartbeat;
20
+ private answered;
21
+ private closed;
22
+ /**
23
+ * @param ws - The container's socket, after the handshake the scheduler authenticated.
24
+ * @param ackTimeoutMs - How long {@link ChannelPeer.command} waits for an acknowledgement.
25
+ * @param heartbeatMs - How often the socket is pinged.
26
+ */
27
+ constructor(ws: WebSocket, ackTimeoutMs?: number, heartbeatMs?: number);
28
+ /** Sends the run's job: the channel's first message, and again on every channel a reconnecting bot opens. */
29
+ sendJob(job: JobMessage): void;
30
+ /**
31
+ * Sends a command and resolves with the container's acknowledgement. No acknowledgement within the timeout, or a
32
+ * channel that closes first, resolves with `ok: false`; it never rejects.
33
+ */
34
+ command(command: CommandMessage): Promise<AckMessage>;
35
+ /** Sets the handler each of the container's reports goes to. */
36
+ onReport(handler: (report: ReportMessage) => void): void;
37
+ /** Adds a handler called once when the channel closes, whichever side closed it. */
38
+ onClose(handler: () => void): void;
39
+ /** Closes the channel with a WebSocket close code and a reason. */
40
+ close(code: number, reason: string): void;
41
+ private ping;
42
+ private receive;
43
+ private ended;
44
+ }
45
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/scheduler/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,SAAS,EAAE,MAAM,IAAI,CAAC;AAC7C,OAAO,EAAkB,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE3H,oEAAoE;AACpE,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,iHAAiH;AACjH,eAAO,MAAM,YAAY,QAAS,CAAC;AAEnC;;;;;GAKG;AACH,qBAAa,WAAW;IAaV,OAAO,CAAC,QAAQ,CAAC,EAAE;IAAa,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+F;IACvH,OAAO,CAAC,aAAa,CAAgD;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyB;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAS;IAEvB;;;;OAIG;gBAC0B,EAAE,EAAE,SAAS,EAAmB,YAAY,SAAiB,EAAE,WAAW,SAAe;IAWtH,6GAA6G;IAC7G,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAI9B;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAarD,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAIxD,oFAAoF;IACpF,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,mEAAmE;IACnE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAIzC,OAAO,CAAC,IAAI;IAaZ,OAAO,CAAC,OAAO;IAqBf,OAAO,CAAC,KAAK;CAWd"}
@@ -0,0 +1,122 @@
1
+ import { ChannelMessage } from '../protocol.js';
2
+ /** How long a command waits for the container's acknowledgement. */
3
+ export const ACK_TIMEOUT_MS = 10_000;
4
+ /** How often the scheduler pings a channel; a channel that has not answered the last ping by the next is cut. */
5
+ export const HEARTBEAT_MS = 15_000;
6
+ /**
7
+ * The scheduler's end of one run's command channel: it sends the job, sends commands and waits for their
8
+ * acknowledgements, and hands the container's reports on. A message that does not parse, or that is not the container's
9
+ * to send (a job, a command), is dropped. A socket that stops answering pings is cut, so a link that broke without a
10
+ * close never holds the run's one channel against the bot's reconnect.
11
+ */
12
+ export class ChannelPeer {
13
+ ws;
14
+ ackTimeoutMs;
15
+ pending = new Map();
16
+ reportHandler;
17
+ closeHandlers = [];
18
+ heartbeat;
19
+ answered = true;
20
+ closed = false;
21
+ /**
22
+ * @param ws - The container's socket, after the handshake the scheduler authenticated.
23
+ * @param ackTimeoutMs - How long {@link ChannelPeer.command} waits for an acknowledgement.
24
+ * @param heartbeatMs - How often the socket is pinged.
25
+ */
26
+ constructor(ws, ackTimeoutMs = ACK_TIMEOUT_MS, heartbeatMs = HEARTBEAT_MS) {
27
+ this.ws = ws;
28
+ this.ackTimeoutMs = ackTimeoutMs;
29
+ ws.on('message', (raw) => this.receive(raw));
30
+ ws.on('close', () => this.ended());
31
+ ws.on('error', () => undefined); // a socket error is followed by its close, which ends the peer
32
+ ws.on('pong', () => {
33
+ this.answered = true;
34
+ });
35
+ this.heartbeat = setInterval(() => this.ping(), heartbeatMs);
36
+ this.heartbeat.unref?.();
37
+ }
38
+ /** Sends the run's job: the channel's first message, and again on every channel a reconnecting bot opens. */
39
+ sendJob(job) {
40
+ this.ws.send(JSON.stringify(job));
41
+ }
42
+ /**
43
+ * Sends a command and resolves with the container's acknowledgement. No acknowledgement within the timeout, or a
44
+ * channel that closes first, resolves with `ok: false`; it never rejects.
45
+ */
46
+ command(command) {
47
+ if (this.closed)
48
+ return Promise.resolve({ type: 'ack', id: command.id, ok: false, error: 'the channel is closed' });
49
+ return new Promise((resolve) => {
50
+ const timer = setTimeout(() => {
51
+ this.pending.delete(command.id);
52
+ resolve({ type: 'ack', id: command.id, ok: false, error: `no acknowledgement within ${this.ackTimeoutMs} ms` });
53
+ }, this.ackTimeoutMs);
54
+ timer.unref?.();
55
+ this.pending.set(command.id, { resolve, timer });
56
+ this.ws.send(JSON.stringify(command));
57
+ });
58
+ }
59
+ /** Sets the handler each of the container's reports goes to. */
60
+ onReport(handler) {
61
+ this.reportHandler = handler;
62
+ }
63
+ /** Adds a handler called once when the channel closes, whichever side closed it. */
64
+ onClose(handler) {
65
+ this.closeHandlers.push(handler);
66
+ }
67
+ /** Closes the channel with a WebSocket close code and a reason. */
68
+ close(code, reason) {
69
+ this.ws.close(code, reason);
70
+ }
71
+ ping() {
72
+ if (!this.answered) {
73
+ this.ws.terminate(); // the close that follows ends the peer
74
+ return;
75
+ }
76
+ this.answered = false;
77
+ try {
78
+ this.ws.ping();
79
+ }
80
+ catch {
81
+ this.ws.terminate();
82
+ }
83
+ }
84
+ receive(raw) {
85
+ let data;
86
+ try {
87
+ data = JSON.parse(raw.toString());
88
+ }
89
+ catch {
90
+ return;
91
+ }
92
+ const parsed = ChannelMessage.safeParse(data);
93
+ if (!parsed.success)
94
+ return;
95
+ const message = parsed.data;
96
+ if (message.type === 'ack') {
97
+ const waiting = this.pending.get(message.id);
98
+ if (!waiting)
99
+ return;
100
+ clearTimeout(waiting.timer);
101
+ this.pending.delete(message.id);
102
+ waiting.resolve(message);
103
+ return;
104
+ }
105
+ if (message.type === 'report')
106
+ this.reportHandler?.(message);
107
+ }
108
+ ended() {
109
+ if (this.closed)
110
+ return;
111
+ this.closed = true;
112
+ clearInterval(this.heartbeat);
113
+ for (const [id, waiting] of this.pending) {
114
+ clearTimeout(waiting.timer);
115
+ waiting.resolve({ type: 'ack', id, ok: false, error: 'the channel closed' });
116
+ }
117
+ this.pending.clear();
118
+ for (const handler of this.closeHandlers)
119
+ handler();
120
+ }
121
+ }
122
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/scheduler/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import type Dockerode from 'dockerode';
2
+ /** A container as the scheduler commands it after its start; `dockerode`'s `Container` is one. */
3
+ export interface ContainerLike {
4
+ /** Stops the container, killing it after `t` seconds. */
5
+ stop(opts?: {
6
+ t?: number;
7
+ }): Promise<void>;
8
+ /** Removes the container; `force` kills a running one first. */
9
+ remove(opts?: {
10
+ force?: boolean;
11
+ }): Promise<void>;
12
+ /** Resolves with the exit code once the container stops. */
13
+ wait(): Promise<{
14
+ StatusCode: number;
15
+ }>;
16
+ /** With `hijack`, the stream is the container's stdin: what is written reaches the bot, and ending it closes its stdin. */
17
+ attach(opts: {
18
+ stream: boolean;
19
+ stdin: boolean;
20
+ hijack: boolean;
21
+ }): Promise<NodeJS.WritableStream>;
22
+ }
23
+ /** The part of a Docker client the scheduler uses; `dockerode`'s `Docker` is one, and the tests pass a fake. */
24
+ export interface DockerLike {
25
+ /** Creates a container from the options and answers a handle to start and watch it. */
26
+ createContainer(options: Dockerode.ContainerCreateOptions): Promise<{
27
+ id: string;
28
+ start(): Promise<void>;
29
+ stop(opts?: {
30
+ t?: number;
31
+ }): Promise<void>;
32
+ remove(opts?: {
33
+ force?: boolean;
34
+ }): Promise<void>;
35
+ wait(): Promise<{
36
+ StatusCode: number;
37
+ }>;
38
+ }>;
39
+ /** Lists containers, the stopped ones too with `all`, by label filters (`key=value`). */
40
+ listContainers(options: {
41
+ all: boolean;
42
+ filters: Record<string, string[]>;
43
+ }): Promise<Array<{
44
+ Id: string;
45
+ State: string;
46
+ Labels: Record<string, string>;
47
+ }>>;
48
+ /** A handle on an existing container by its id. */
49
+ getContainer(id: string): ContainerLike;
50
+ }
51
+ /** The scheduler's fixed container policy: the image and the limits come from its configuration, never from a request. */
52
+ export interface ContainerPolicy {
53
+ /** The bot image, by tag or digest. */
54
+ image: string;
55
+ /** The Docker network the containers join (the bots' bridge, never the host's network). */
56
+ network: string;
57
+ /** The memory limit per container, in bytes. */
58
+ memoryBytes: number;
59
+ /** The CPU limit per container, in billionths of a CPU. */
60
+ nanoCpus: number;
61
+ /** The most processes a container may hold. */
62
+ pidsLimit: number;
63
+ /** The size of `/dev/shm`, in bytes (Chromium's shared memory). */
64
+ shmBytes: number;
65
+ /**
66
+ * The seccomp profile as its JSON text. The Engine API takes the profile itself in `SecurityOpt` (the `docker` command
67
+ * reads a file and sends its contents), so the scheduler's entry point reads `MEETING_SECCOMP_PROFILE` once at start.
68
+ */
69
+ seccompProfile?: string;
70
+ /** The encrypted profile volume's mount point on the host; a run's profile is `<root>/<identity>`. */
71
+ profilesRoot?: string;
72
+ /** The channel's url as the containers reach it. */
73
+ channelUrl: string;
74
+ }
75
+ /** The label that names a container's run. */
76
+ export declare const LABEL_RUN = "dev.frame.meeting.run";
77
+ /** The label that names the worker a container belongs to. */
78
+ export declare const LABEL_WORKER = "dev.frame.meeting.worker";
79
+ /** Where an identity's profile is mounted inside the container. */
80
+ export declare const PROFILE_MOUNT = "/home/bot/profile";
81
+ /**
82
+ * The container's create options: nothing customer-supplied, only the run id and the channel's address. Its stdin stays
83
+ * open for one attached writer, which is how the run's token reaches the bot without appearing in `docker inspect`.
84
+ */
85
+ export declare function containerOptions(policy: ContainerPolicy, runId: string, workerId: string, profileDir: string | undefined): Dockerode.ContainerCreateOptions;
86
+ //# sourceMappingURL=docker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../src/scheduler/docker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAC;AAEvC,kGAAkG;AAClG,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,gEAAgE;IAChE,MAAM,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,4DAA4D;IAC5D,IAAI,IAAI,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxC,2HAA2H;IAC3H,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;CACpG;AAED,gHAAgH;AAChH,MAAM,WAAW,UAAU;IACzB,uFAAuF;IACvF,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,sBAAsB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,CAAC,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,CAAC,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,OAAO,CAAA;SAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,IAAI,IAAI,OAAO,CAAC;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACpP,yFAAyF;IACzF,cAAc,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC,CAAC;IAC5J,mDAAmD;IACnD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,CAAC;CACzC;AAED,0HAA0H;AAC1H,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2FAA2F;IAC3F,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sGAAsG;IACtG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,8CAA8C;AAC9C,eAAO,MAAM,SAAS,0BAA0B,CAAC;AACjD,8DAA8D;AAC9D,eAAO,MAAM,YAAY,6BAA6B,CAAC;AACvD,mEAAmE;AACnE,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC,sBAAsB,CAuB3J"}