@open-everywhere/adapters 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/directory/okta.ts +41 -2
- package/agent/meetings/native-http.ts +72 -0
- package/agent/meetings/native-meet.ts +173 -0
- package/agent/meetings/native-sources.ts +14 -0
- package/agent/meetings/native-teams.ts +119 -0
- package/agent/meetings/native-zoom.ts +98 -0
- package/agent/model/gateway.ts +44 -4
- package/agent/model/meeting-gateway.ts +88 -0
- package/agent/model/transcription-routes.ts +347 -0
- package/agent/oe-oauth/config.ts +71 -3
- package/agent/oe-oauth/errors.ts +17 -0
- package/agent/oe-oauth/oauth-client.ts +118 -2
- package/agent/oe-oauth/token-vault.ts +359 -8
- package/agent/register-defaults.ts +11 -0
- package/agent/sandbox/long-lived-box.ts +464 -0
- package/dist/agent/directory/okta.d.ts +10 -0
- package/dist/agent/directory/okta.d.ts.map +1 -1
- package/dist/agent/directory/okta.js +34 -2
- package/dist/agent/directory/okta.js.map +1 -1
- package/dist/agent/meetings/native-http.d.ts +22 -0
- package/dist/agent/meetings/native-http.d.ts.map +1 -0
- package/dist/agent/meetings/native-http.js +61 -0
- package/dist/agent/meetings/native-http.js.map +1 -0
- package/dist/agent/meetings/native-meet.d.ts +6 -0
- package/dist/agent/meetings/native-meet.d.ts.map +1 -0
- package/dist/agent/meetings/native-meet.js +176 -0
- package/dist/agent/meetings/native-meet.js.map +1 -0
- package/dist/agent/meetings/native-sources.d.ts +2 -0
- package/dist/agent/meetings/native-sources.d.ts.map +1 -0
- package/dist/agent/meetings/native-sources.js +13 -0
- package/dist/agent/meetings/native-sources.js.map +1 -0
- package/dist/agent/meetings/native-teams.d.ts +6 -0
- package/dist/agent/meetings/native-teams.d.ts.map +1 -0
- package/dist/agent/meetings/native-teams.js +118 -0
- package/dist/agent/meetings/native-teams.js.map +1 -0
- package/dist/agent/meetings/native-zoom.d.ts +7 -0
- package/dist/agent/meetings/native-zoom.d.ts.map +1 -0
- package/dist/agent/meetings/native-zoom.js +98 -0
- package/dist/agent/meetings/native-zoom.js.map +1 -0
- package/dist/agent/model/gateway.d.ts +10 -0
- package/dist/agent/model/gateway.d.ts.map +1 -1
- package/dist/agent/model/gateway.js +43 -5
- package/dist/agent/model/gateway.js.map +1 -1
- package/dist/agent/model/meeting-gateway.d.ts +24 -0
- package/dist/agent/model/meeting-gateway.d.ts.map +1 -0
- package/dist/agent/model/meeting-gateway.js +75 -0
- package/dist/agent/model/meeting-gateway.js.map +1 -0
- package/dist/agent/model/transcription-routes.d.ts +89 -0
- package/dist/agent/model/transcription-routes.d.ts.map +1 -0
- package/dist/agent/model/transcription-routes.js +279 -0
- package/dist/agent/model/transcription-routes.js.map +1 -0
- package/dist/agent/oe-oauth/config.d.ts +28 -2
- package/dist/agent/oe-oauth/config.d.ts.map +1 -1
- package/dist/agent/oe-oauth/config.js +40 -1
- package/dist/agent/oe-oauth/config.js.map +1 -1
- package/dist/agent/oe-oauth/errors.d.ts +14 -0
- package/dist/agent/oe-oauth/errors.d.ts.map +1 -1
- package/dist/agent/oe-oauth/errors.js +15 -0
- package/dist/agent/oe-oauth/errors.js.map +1 -1
- package/dist/agent/oe-oauth/oauth-client.d.ts +46 -0
- package/dist/agent/oe-oauth/oauth-client.d.ts.map +1 -1
- package/dist/agent/oe-oauth/oauth-client.js +82 -1
- package/dist/agent/oe-oauth/oauth-client.js.map +1 -1
- package/dist/agent/oe-oauth/token-vault.d.ts +15 -0
- package/dist/agent/oe-oauth/token-vault.d.ts.map +1 -1
- package/dist/agent/oe-oauth/token-vault.js +317 -7
- package/dist/agent/oe-oauth/token-vault.js.map +1 -1
- package/dist/agent/register-defaults.d.ts.map +1 -1
- package/dist/agent/register-defaults.js +9 -0
- package/dist/agent/register-defaults.js.map +1 -1
- package/dist/agent/sandbox/long-lived-box.d.ts +70 -0
- package/dist/agent/sandbox/long-lived-box.d.ts.map +1 -0
- package/dist/agent/sandbox/long-lived-box.js +414 -0
- package/dist/agent/sandbox/long-lived-box.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
// The long-lived box on each sandbox backend — the vendor half of core's sandbox/long-lived-box.ts seam.
|
|
2
|
+
// One implementation per backend `resolveSandboxBackend()` can pick, so a box runs where the conversation
|
|
3
|
+
// sandbox runs. A box carries the `oeBox` label and never `sessionId`, so the turn-end stopper
|
|
4
|
+
// (backend-stop.ts) cannot match it.
|
|
5
|
+
//
|
|
6
|
+
// A PERSISTENT box (a `persist` spec — the person's browser, OPE-2441) keeps its files between opens: a
|
|
7
|
+
// NAMED, persistent Vercel Sandbox whose filesystem snapshot survives each stop, or a docker container on a
|
|
8
|
+
// named volume. Before it starts one, the backend stops any box still running on the same key, because two
|
|
9
|
+
// programs on one browser profile corrupt it.
|
|
10
|
+
|
|
11
|
+
import { Sandbox } from "@vercel/sandbox";
|
|
12
|
+
import {
|
|
13
|
+
BOX_LABEL,
|
|
14
|
+
BOX_REF_LABEL,
|
|
15
|
+
boxLabels,
|
|
16
|
+
clampBoxMinutes,
|
|
17
|
+
registerLongLivedBoxBackend,
|
|
18
|
+
resolveSandboxBackend,
|
|
19
|
+
type BoxListing,
|
|
20
|
+
type BoxPurpose,
|
|
21
|
+
type BoxSpec,
|
|
22
|
+
type LongLivedBoxBackend,
|
|
23
|
+
type OpenedBox,
|
|
24
|
+
} from "@open-everywhere/core/agent/adapter-api.js";
|
|
25
|
+
import { dockerStatusToSandboxStatus } from "./backend-stop.js";
|
|
26
|
+
import { execFile } from "node:child_process";
|
|
27
|
+
|
|
28
|
+
/** Vercel Sandbox's plan ceiling per session on Pro and Enterprise (vercel.com/docs/sandbox/pricing). */
|
|
29
|
+
export const VERCEL_BOX_CEILING_MINUTES = 24 * 60;
|
|
30
|
+
/** Docker has no platform ceiling. OE still caps a box, at the same 24 h, so one rule bounds both stacks. */
|
|
31
|
+
const DOCKER_BOX_CEILING_MINUTES = 24 * 60;
|
|
32
|
+
|
|
33
|
+
function isPurpose(v: string | undefined): v is BoxPurpose {
|
|
34
|
+
return v === "meeting" || v === "browser";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Vercel ────────────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** The raw `@vercel/sandbox` surface the Vercel box needs. Injected in tests. */
|
|
40
|
+
export interface VercelBoxSdk {
|
|
41
|
+
create(params: {
|
|
42
|
+
image: string;
|
|
43
|
+
vcpus: number;
|
|
44
|
+
timeoutMs: number;
|
|
45
|
+
tags: Record<string, string>;
|
|
46
|
+
env: Record<string, string>;
|
|
47
|
+
networkPolicy: "allow-all" | "deny-all" | { allow: string[] };
|
|
48
|
+
}): Promise<{ name: string; createdAt: number }>;
|
|
49
|
+
/** `env` (a persistent box) goes to this one command only, never into the sandbox's stored config. */
|
|
50
|
+
runDetached(name: string, cmd: string, args: string[], timeoutMs: number, env?: Record<string, string>): Promise<void>;
|
|
51
|
+
get(name: string): Promise<BoxListing | undefined>;
|
|
52
|
+
/** Every sandbox carrying the `oeBox` tag with this purpose. */
|
|
53
|
+
listByPurpose(purpose: BoxPurpose): Promise<BoxListing[]>;
|
|
54
|
+
stop(name: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* A persistent, NAMED sandbox: create it, or update the one that exists (and stop it first when it is
|
|
57
|
+
* running, so the program that starts next is the only one on its files). The next command resumes it
|
|
58
|
+
* from its snapshot. `createdAt` is the start of THIS session, for billing.
|
|
59
|
+
*/
|
|
60
|
+
openPersistent?(params: {
|
|
61
|
+
name: string;
|
|
62
|
+
image: string;
|
|
63
|
+
vcpus: number;
|
|
64
|
+
timeoutMs: number;
|
|
65
|
+
tags: Record<string, string>;
|
|
66
|
+
networkPolicy: "allow-all" | "deny-all" | { allow: string[] };
|
|
67
|
+
snapshotExpirationMs: number;
|
|
68
|
+
ports: number[];
|
|
69
|
+
}): Promise<{ name: string; createdAt: number }>;
|
|
70
|
+
/** The public HTTPS URL of an exposed port. */
|
|
71
|
+
domain?(name: string, port: number): Promise<string>;
|
|
72
|
+
/** Delete a persistent sandbox and its snapshots. Idempotent. */
|
|
73
|
+
remove?(name: string): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const defaultVercelBoxSdk: VercelBoxSdk = {
|
|
77
|
+
async create(p) {
|
|
78
|
+
const sandbox = await Sandbox.create({
|
|
79
|
+
image: p.image,
|
|
80
|
+
resources: { vcpus: p.vcpus },
|
|
81
|
+
timeout: p.timeoutMs,
|
|
82
|
+
tags: p.tags,
|
|
83
|
+
env: p.env,
|
|
84
|
+
networkPolicy: p.networkPolicy,
|
|
85
|
+
// A box is single-use. Nothing resumes it, so it keeps no snapshot.
|
|
86
|
+
persistent: false,
|
|
87
|
+
});
|
|
88
|
+
return { name: sandbox.name, createdAt: sandbox.createdAt.getTime() };
|
|
89
|
+
},
|
|
90
|
+
async runDetached(name, cmd, args, timeoutMs, env) {
|
|
91
|
+
const sandbox = await Sandbox.get({ name, resume: false });
|
|
92
|
+
await sandbox.runCommand({ cmd, args, detached: true, timeoutMs, ...(env !== undefined ? { env } : {}) });
|
|
93
|
+
},
|
|
94
|
+
async get(name) {
|
|
95
|
+
try {
|
|
96
|
+
const s = await Sandbox.get({ name, resume: false });
|
|
97
|
+
const purpose = s.tags?.[BOX_LABEL];
|
|
98
|
+
return {
|
|
99
|
+
name: s.name,
|
|
100
|
+
status: s.status,
|
|
101
|
+
createdAt: s.createdAt.getTime(),
|
|
102
|
+
...(isPurpose(purpose) ? { purpose } : {}),
|
|
103
|
+
...(s.tags?.[BOX_REF_LABEL] !== undefined ? { ref: s.tags[BOX_REF_LABEL] } : {}),
|
|
104
|
+
};
|
|
105
|
+
} catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
async listByPurpose(purpose) {
|
|
110
|
+
const result = await Sandbox.list({ tags: { oeBox: purpose } });
|
|
111
|
+
const out: BoxListing[] = [];
|
|
112
|
+
for await (const s of result) out.push({ name: s.name, status: s.status, createdAt: s.createdAt, purpose });
|
|
113
|
+
return out;
|
|
114
|
+
},
|
|
115
|
+
async stop(name) {
|
|
116
|
+
try {
|
|
117
|
+
await (await Sandbox.get({ name, resume: false })).stop();
|
|
118
|
+
} catch {
|
|
119
|
+
// Idempotent: a box that is already gone is stopped.
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
async openPersistent(p) {
|
|
123
|
+
let existing: Sandbox | undefined;
|
|
124
|
+
try {
|
|
125
|
+
existing = await Sandbox.get({ name: p.name, resume: false });
|
|
126
|
+
} catch {
|
|
127
|
+
existing = undefined;
|
|
128
|
+
}
|
|
129
|
+
if (existing === undefined) {
|
|
130
|
+
await Sandbox.create({
|
|
131
|
+
name: p.name,
|
|
132
|
+
image: p.image,
|
|
133
|
+
resources: { vcpus: p.vcpus },
|
|
134
|
+
timeout: p.timeoutMs,
|
|
135
|
+
tags: p.tags,
|
|
136
|
+
networkPolicy: p.networkPolicy,
|
|
137
|
+
ports: p.ports,
|
|
138
|
+
// The filesystem (the browser profile) survives each stop as a snapshot, and expires unused.
|
|
139
|
+
persistent: true,
|
|
140
|
+
snapshotExpiration: p.snapshotExpirationMs,
|
|
141
|
+
});
|
|
142
|
+
return { name: p.name, createdAt: Date.now() };
|
|
143
|
+
}
|
|
144
|
+
// A program still running on these files would share the profile with the one about to start. A stop
|
|
145
|
+
// snapshots the filesystem; the next command resumes from it with no process left over.
|
|
146
|
+
if (existing.status === "running" || existing.status === "pending") await existing.stop();
|
|
147
|
+
await existing.update({ timeout: p.timeoutMs, networkPolicy: p.networkPolicy, ports: p.ports, tags: p.tags, snapshotExpiration: p.snapshotExpirationMs });
|
|
148
|
+
return { name: p.name, createdAt: Date.now() };
|
|
149
|
+
},
|
|
150
|
+
async domain(name, port) {
|
|
151
|
+
return (await Sandbox.get({ name, resume: false })).domain(port);
|
|
152
|
+
},
|
|
153
|
+
async remove(name) {
|
|
154
|
+
try {
|
|
155
|
+
await (await Sandbox.get({ name, resume: false })).delete({ deleteOrphanSnapshots: true });
|
|
156
|
+
} catch {
|
|
157
|
+
// Idempotent: a sandbox that is already gone is deleted.
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** The Vercel firewall form of a box's egress. */
|
|
163
|
+
function vercelNetworkPolicy(egress: BoxSpec["egress"]): "allow-all" | "deny-all" | { allow: string[] } {
|
|
164
|
+
return egress === "open" ? "allow-all" : egress.allow.length === 0 ? "deny-all" : { allow: [...egress.allow] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createVercelBoxBackend(sdk: VercelBoxSdk = defaultVercelBoxSdk): LongLivedBoxBackend {
|
|
168
|
+
return {
|
|
169
|
+
backend: "vercel",
|
|
170
|
+
metered: true,
|
|
171
|
+
maxMinutesCeiling: VERCEL_BOX_CEILING_MINUTES,
|
|
172
|
+
// The Vercel Sandbox firewall holds an `{ allow }` list at the network, outside the box.
|
|
173
|
+
enforcesHostAllowList: true,
|
|
174
|
+
async open(spec: BoxSpec): Promise<OpenedBox> {
|
|
175
|
+
const [cmd, ...args] = spec.command;
|
|
176
|
+
if (cmd === undefined) throw new Error("a box needs a command to run");
|
|
177
|
+
const maxMinutes = clampBoxMinutes(spec.maxMinutes, VERCEL_BOX_CEILING_MINUTES);
|
|
178
|
+
const timeoutMs = maxMinutes * 60_000;
|
|
179
|
+
if (spec.persist !== undefined) {
|
|
180
|
+
if (sdk.openPersistent === undefined || sdk.domain === undefined) throw new Error("this Vercel box SDK cannot keep a box's files between opens");
|
|
181
|
+
const opened = await sdk.openPersistent({
|
|
182
|
+
name: spec.persist.key,
|
|
183
|
+
image: spec.image,
|
|
184
|
+
vcpus: Math.max(1, Math.floor(spec.vcpus)),
|
|
185
|
+
timeoutMs,
|
|
186
|
+
tags: boxLabels(spec),
|
|
187
|
+
networkPolicy: vercelNetworkPolicy(spec.egress),
|
|
188
|
+
snapshotExpirationMs: Math.max(1, Math.floor(spec.persist.keepDays)) * 24 * 60 * 60_000,
|
|
189
|
+
ports: spec.port !== undefined ? [spec.port] : [],
|
|
190
|
+
});
|
|
191
|
+
try {
|
|
192
|
+
// The per-open secrets go to this command only. A persistent sandbox stores its create-time env in its
|
|
193
|
+
// config, so a key placed there would outlive the box it was minted for.
|
|
194
|
+
await sdk.runDetached(opened.name, cmd, args, timeoutMs, { ...spec.env });
|
|
195
|
+
} catch (err) {
|
|
196
|
+
await sdk.stop(opened.name);
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
const url = spec.port !== undefined ? await sdk.domain(opened.name, spec.port) : undefined;
|
|
200
|
+
return { name: opened.name, backend: "vercel", createdAt: opened.createdAt, maxMinutes, ...(url !== undefined ? { url } : {}) };
|
|
201
|
+
}
|
|
202
|
+
const created = await sdk.create({
|
|
203
|
+
image: spec.image,
|
|
204
|
+
// Vercel sizes memory from vCPUs (2 GiB each), so `memoryMb` has no field to land in.
|
|
205
|
+
vcpus: Math.max(1, Math.floor(spec.vcpus)),
|
|
206
|
+
// The session timeout IS the cap: Vercel stops the VM at it whatever the bot does.
|
|
207
|
+
timeoutMs,
|
|
208
|
+
tags: boxLabels(spec),
|
|
209
|
+
env: { ...spec.env },
|
|
210
|
+
networkPolicy: vercelNetworkPolicy(spec.egress),
|
|
211
|
+
});
|
|
212
|
+
try {
|
|
213
|
+
await sdk.runDetached(created.name, cmd, args, timeoutMs);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
// A box whose program never started must not idle out its whole cap.
|
|
216
|
+
await sdk.stop(created.name);
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
return { name: created.name, backend: "vercel", createdAt: created.createdAt, maxMinutes };
|
|
220
|
+
},
|
|
221
|
+
status: async (name) => await sdk.get(name),
|
|
222
|
+
list: async (purpose) => await sdk.listByPurpose(purpose),
|
|
223
|
+
stop: async (name) => await sdk.stop(name),
|
|
224
|
+
async forget(key) {
|
|
225
|
+
if (sdk.remove === undefined) throw new Error("this Vercel box SDK cannot delete a persistent box");
|
|
226
|
+
await sdk.remove(key);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── docker ────────────────────────────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
/** The pid ceiling of a docker box: Chromium's threads count against it, so it is generous. */
|
|
234
|
+
const DOCKER_BOX_PIDS_LIMIT = 2048;
|
|
235
|
+
|
|
236
|
+
/** The docker label keys. Prefixed like eve's own `eve.sandbox.tag.*`, but never its `sessionId` key. */
|
|
237
|
+
export const DOCKER_BOX_LABEL = `oe.box.${BOX_LABEL}`;
|
|
238
|
+
const DOCKER_BOX_REF_LABEL = `oe.box.${BOX_REF_LABEL}`;
|
|
239
|
+
/** The label naming a persistent box's volume, so a second open finds the box still on it. */
|
|
240
|
+
export const DOCKER_BOX_PERSIST_LABEL = "oe.box.oePersist";
|
|
241
|
+
|
|
242
|
+
/** A docker volume name from a persistence key: lowercase, `[a-z0-9_.-]`, never empty. */
|
|
243
|
+
function dockerVolumeName(key: string): string {
|
|
244
|
+
const clean = key.toLowerCase().replace(/[^a-z0-9_.-]/g, "-").slice(0, 60);
|
|
245
|
+
return clean.length > 0 ? clean : "oe-box-data";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The docker CLI for boxes. `env` carries the VALUES of the box's `--env NAME` flags: docker reads each named
|
|
250
|
+
* variable from its own environment, so a value (the meeting's callback key, a meeting passcode) never sits
|
|
251
|
+
* on the argv, where `ps` shows it and Node's `execFile` error message would repeat it.
|
|
252
|
+
*/
|
|
253
|
+
export type BoxDockerRunner = (args: readonly string[], env?: Readonly<Record<string, string>>) => Promise<string>;
|
|
254
|
+
|
|
255
|
+
const defaultDockerRunner: BoxDockerRunner = async (args, env) =>
|
|
256
|
+
await new Promise<string>((resolve, reject) => {
|
|
257
|
+
const bin = process.env.EVE_DOCKER_PATH?.trim() || "docker";
|
|
258
|
+
execFile(bin, [...args], { encoding: "utf8", maxBuffer: 8 * 1024 * 1024, ...(env !== undefined ? { env: { ...process.env, ...env } } : {}) }, (err, stdout, stderr) => {
|
|
259
|
+
// A SANITISED error: the verb and docker's own first stderr line, never the argv. The message reaches
|
|
260
|
+
// an approval card's outcome line and the `approval.failed` audit row.
|
|
261
|
+
if (err) reject(new Error(`docker ${args[0] ?? ""} failed: ${String(stderr).split("\n")[0]?.trim() || "no error output"}`));
|
|
262
|
+
else resolve(stdout);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/** A docker container name from the box ref: lowercase, `[a-z0-9_.-]`, unique per open. */
|
|
267
|
+
function dockerBoxName(spec: Pick<BoxSpec, "purpose" | "ref">, nonce: string): string {
|
|
268
|
+
const ref = spec.ref.toLowerCase().replace(/[^a-z0-9_.-]/g, "-").slice(0, 40);
|
|
269
|
+
return `oe-box-${spec.purpose}-${ref}-${nonce}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** The `docker run` argument list for a box. Pure, so the sizing and the labels are unit-tested. */
|
|
273
|
+
export function dockerRunArgs(spec: BoxSpec, name: string, maxMinutes: number): string[] {
|
|
274
|
+
if (spec.egress !== "open" && spec.egress.allow.length > 0) {
|
|
275
|
+
// docker's own network has no host allow-list, and a box outside the egress proxy cannot be
|
|
276
|
+
// filtered by it (OPE-2407). Refuse rather than run a box open that asked to be closed.
|
|
277
|
+
throw new Error("the docker backend cannot enforce a host allow-list on a box — refusing to open it with open network");
|
|
278
|
+
}
|
|
279
|
+
const labels = boxLabels(spec);
|
|
280
|
+
const args = [
|
|
281
|
+
"run",
|
|
282
|
+
"-d",
|
|
283
|
+
// The box removes itself when its program exits, so nothing accumulates on the host.
|
|
284
|
+
"--rm",
|
|
285
|
+
"--name",
|
|
286
|
+
name,
|
|
287
|
+
"--label",
|
|
288
|
+
`${DOCKER_BOX_LABEL}=${labels[BOX_LABEL]}`,
|
|
289
|
+
"--label",
|
|
290
|
+
`${DOCKER_BOX_REF_LABEL}=${labels[BOX_REF_LABEL]}`,
|
|
291
|
+
"--cpus",
|
|
292
|
+
String(Math.max(0.5, spec.vcpus)),
|
|
293
|
+
"--memory",
|
|
294
|
+
`${Math.max(256, Math.floor(spec.memoryMb))}m`,
|
|
295
|
+
"--shm-size",
|
|
296
|
+
`${Math.max(64, Math.floor(spec.shmMb ?? 64))}m`,
|
|
297
|
+
// Hardening every box gets: no Linux capabilities, no privilege gain through setuid binaries, and a pid
|
|
298
|
+
// ceiling (Chromium runs a few hundred threads; a fork bomb stops here). The box program runs as the
|
|
299
|
+
// image's non-root user when the spec names one.
|
|
300
|
+
"--cap-drop",
|
|
301
|
+
"ALL",
|
|
302
|
+
"--security-opt",
|
|
303
|
+
"no-new-privileges",
|
|
304
|
+
"--pids-limit",
|
|
305
|
+
String(DOCKER_BOX_PIDS_LIMIT),
|
|
306
|
+
...(spec.user !== undefined ? ["--user", spec.user] : []),
|
|
307
|
+
];
|
|
308
|
+
if (spec.persist !== undefined) {
|
|
309
|
+
args.push("--label", `${DOCKER_BOX_PERSIST_LABEL}=${dockerVolumeName(spec.persist.key)}`, "--volume", `${dockerVolumeName(spec.persist.key)}:${spec.persist.mount}`);
|
|
310
|
+
}
|
|
311
|
+
if (spec.egress !== "open") {
|
|
312
|
+
// A closed box has no network, so nothing could reach its port either.
|
|
313
|
+
if (spec.port !== undefined) throw new Error("a docker box with no network cannot serve a port — refusing to open it");
|
|
314
|
+
args.push("--network", "none");
|
|
315
|
+
} else if (spec.network !== undefined) {
|
|
316
|
+
args.push("--network", spec.network);
|
|
317
|
+
}
|
|
318
|
+
// Off a named network the port is published on the loopback interface only, never on the host's others.
|
|
319
|
+
if (spec.port !== undefined && spec.network === undefined) args.push("--publish", `127.0.0.1::${spec.port}`);
|
|
320
|
+
// Names only: the values travel in the CLI's own environment (BoxDockerRunner), never on the argv.
|
|
321
|
+
for (const k of Object.keys(spec.env)) args.push("--env", k);
|
|
322
|
+
// `timeout` is the cap on this backend: coreutils kills the program at it, and `--rm` removes the box.
|
|
323
|
+
args.push("--entrypoint", "timeout", spec.image, `${maxMinutes * 60}s`, ...spec.command);
|
|
324
|
+
return args;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function createDockerBoxBackend(run: BoxDockerRunner = defaultDockerRunner, nonce: () => string = () => Date.now().toString(36)): LongLivedBoxBackend {
|
|
328
|
+
async function inspect(names: readonly string[]): Promise<BoxListing[]> {
|
|
329
|
+
if (names.length === 0) return [];
|
|
330
|
+
let out: string;
|
|
331
|
+
try {
|
|
332
|
+
out = await run([
|
|
333
|
+
"inspect",
|
|
334
|
+
"--format",
|
|
335
|
+
`{{.Name}}\t{{.State.Status}}\t{{.State.StartedAt}}\t{{index .Config.Labels "${DOCKER_BOX_LABEL}"}}\t{{index .Config.Labels "${DOCKER_BOX_REF_LABEL}"}}`,
|
|
336
|
+
...names,
|
|
337
|
+
]);
|
|
338
|
+
} catch {
|
|
339
|
+
return [];
|
|
340
|
+
}
|
|
341
|
+
const listings: BoxListing[] = [];
|
|
342
|
+
for (const line of out.split("\n")) {
|
|
343
|
+
const [rawName, state, startedAt, purpose, ref] = line.trim().split("\t");
|
|
344
|
+
if (rawName === undefined || state === undefined) continue;
|
|
345
|
+
const parsed = startedAt === undefined ? Number.NaN : Date.parse(startedAt);
|
|
346
|
+
listings.push({
|
|
347
|
+
name: rawName.replace(/^\//, ""),
|
|
348
|
+
status: dockerStatusToSandboxStatus(state),
|
|
349
|
+
createdAt: Number.isFinite(parsed) ? parsed : Date.now(),
|
|
350
|
+
...(isPurpose(purpose) ? { purpose } : {}),
|
|
351
|
+
...(ref !== undefined && ref.length > 0 ? { ref } : {}),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return listings;
|
|
355
|
+
}
|
|
356
|
+
/** Stop every box still running on a persistent key's volume. */
|
|
357
|
+
async function stopOnVolume(volume: string): Promise<void> {
|
|
358
|
+
const ids = (await run(["ps", "-q", "--filter", `label=${DOCKER_BOX_PERSIST_LABEL}=${volume}`]))
|
|
359
|
+
.split("\n")
|
|
360
|
+
.map((l) => l.trim())
|
|
361
|
+
.filter((l) => l.length > 0);
|
|
362
|
+
for (const id of ids) {
|
|
363
|
+
try {
|
|
364
|
+
await run(["stop", "-t", "10", id]);
|
|
365
|
+
} catch {
|
|
366
|
+
// Already gone.
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Where the app reaches a box's port: by name on a shared network, else the loopback port docker picked. */
|
|
371
|
+
async function boxUrl(spec: BoxSpec, name: string): Promise<string | undefined> {
|
|
372
|
+
if (spec.port === undefined) return undefined;
|
|
373
|
+
if (spec.network !== undefined) return `http://${name}:${spec.port}`;
|
|
374
|
+
const mapped = (await run(["port", name, `${spec.port}/tcp`])).split("\n").map((l) => l.trim()).find((l) => /^127\.0\.0\.1:\d+$/.test(l));
|
|
375
|
+
return mapped === undefined ? undefined : `http://${mapped}`;
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
backend: "docker",
|
|
379
|
+
metered: false,
|
|
380
|
+
maxMinutesCeiling: DOCKER_BOX_CEILING_MINUTES,
|
|
381
|
+
// docker has no host firewall for a box: `dockerRunArgs` refuses a non-empty `{ allow }` list.
|
|
382
|
+
enforcesHostAllowList: false,
|
|
383
|
+
async open(spec) {
|
|
384
|
+
if (spec.command.length === 0) throw new Error("a box needs a command to run");
|
|
385
|
+
const maxMinutes = clampBoxMinutes(spec.maxMinutes, DOCKER_BOX_CEILING_MINUTES);
|
|
386
|
+
const name = dockerBoxName(spec, nonce());
|
|
387
|
+
const args = dockerRunArgs(spec, name, maxMinutes);
|
|
388
|
+
if (spec.persist !== undefined) await stopOnVolume(dockerVolumeName(spec.persist.key));
|
|
389
|
+
await run(args, spec.env);
|
|
390
|
+
const [listing] = await inspect([name]);
|
|
391
|
+
let url: string | undefined;
|
|
392
|
+
try {
|
|
393
|
+
url = await boxUrl(spec, name);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
await run(["stop", "-t", "10", name]).catch(() => "");
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
return { name, backend: "docker", createdAt: listing?.createdAt ?? Date.now(), maxMinutes, ...(url !== undefined ? { url } : {}) };
|
|
399
|
+
},
|
|
400
|
+
async status(name) {
|
|
401
|
+
const [listing] = await inspect([name]);
|
|
402
|
+
return listing;
|
|
403
|
+
},
|
|
404
|
+
async list(purpose) {
|
|
405
|
+
const names = (await run(["ps", "-a", "--no-trunc", "--filter", `label=${DOCKER_BOX_LABEL}=${purpose}`, "--format", "{{.Names}}"]))
|
|
406
|
+
.split("\n")
|
|
407
|
+
.map((l) => l.trim())
|
|
408
|
+
.filter((l) => l.length > 0);
|
|
409
|
+
return await inspect(names);
|
|
410
|
+
},
|
|
411
|
+
async stop(name) {
|
|
412
|
+
try {
|
|
413
|
+
await run(["stop", "-t", "10", name]);
|
|
414
|
+
} catch {
|
|
415
|
+
// Idempotent: `--rm` already removed a box whose program exited.
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
async forget(key) {
|
|
419
|
+
const volume = dockerVolumeName(key);
|
|
420
|
+
await stopOnVolume(volume);
|
|
421
|
+
// `-f` makes a missing volume a success, so a forget is idempotent.
|
|
422
|
+
await run(["volume", "rm", "-f", volume]);
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ── just-bash ─────────────────────────────────────────────────────────────────────────────────
|
|
428
|
+
|
|
429
|
+
/** just-bash is an in-process interpreter: it has no VM, no container and no browser. It refuses. */
|
|
430
|
+
export const justBashBoxBackend: LongLivedBoxBackend = {
|
|
431
|
+
backend: "justbash",
|
|
432
|
+
metered: false,
|
|
433
|
+
maxMinutesCeiling: 0,
|
|
434
|
+
unavailable:
|
|
435
|
+
"this deployment's sandbox backend is just-bash, which runs inside the app process and cannot run a long-lived box (no VM, no container, no browser). Declare `sandbox.selfHostBackend: \"docker\"` or run on Vercel Sandbox",
|
|
436
|
+
async open() {
|
|
437
|
+
throw new Error(justBashBoxBackend.unavailable);
|
|
438
|
+
},
|
|
439
|
+
async status() {
|
|
440
|
+
return undefined;
|
|
441
|
+
},
|
|
442
|
+
async list() {
|
|
443
|
+
return [];
|
|
444
|
+
},
|
|
445
|
+
async stop() {
|
|
446
|
+
// Nothing to stop: this backend never opens a box.
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// ── selection ─────────────────────────────────────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
/** The box backend for this deployment: the SAME rule `resolveSandboxStopper()` reads, so they never disagree. */
|
|
453
|
+
export function selectLongLivedBoxBackend(): LongLivedBoxBackend {
|
|
454
|
+
const backend = resolveSandboxBackend();
|
|
455
|
+
if (backend === "vercel") return createVercelBoxBackend();
|
|
456
|
+
return backend === "justbash" ? justBashBoxBackend : createDockerBoxBackend();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** Boot registration, from `registerDefaultAdapters`. */
|
|
460
|
+
export function registerLongLivedBoxes(): string {
|
|
461
|
+
const backend = selectLongLivedBoxBackend();
|
|
462
|
+
registerLongLivedBoxBackend(backend);
|
|
463
|
+
return backend.backend;
|
|
464
|
+
}
|
|
@@ -8,7 +8,17 @@ export interface OktaDirectoryDeps {
|
|
|
8
8
|
readonly fetch?: FetchLike;
|
|
9
9
|
/** Resolve the API token. Default: core's broker seam — see config.ts's header. */
|
|
10
10
|
readonly resolveToken?: (broker: string, ref: string) => Promise<string>;
|
|
11
|
+
/** Does a connection sign people in through THIS Okta org for Cross App Access? Default: a declared `xaa`
|
|
12
|
+
* sign-in whose `idpIssuer` has this org's origin. See {@link xaaNamesOrg}. */
|
|
13
|
+
readonly proposeOwnAccount?: () => boolean;
|
|
11
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* (OPE-2453) True when a declared `xaa` sign-in names this Okta org as its IdP. Only then does the arm propose each
|
|
17
|
+
* person's OWN Okta account (`okta`, the Okta user id) beside the app accounts: the Cross App Access completion
|
|
18
|
+
* admits a sign-in only for a confirmed `okta` account, and nothing else writes one. With no `xaa` declaration
|
|
19
|
+
* the arm proposes exactly what it did before, so a deployment that does not use Cross App Access sees no change.
|
|
20
|
+
*/
|
|
21
|
+
export declare function xaaNamesOrg(orgUrl: string, declared?: ReadonlyMap<string, unknown>): boolean;
|
|
12
22
|
/**
|
|
13
23
|
* The `rel="next"` URL from the RFC-5988 `Link` header. Okta paginates with an opaque cursor and no total
|
|
14
24
|
* count, so following the header is the ONLY correct way to page — an offset would truncate or duplicate.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"okta.d.ts","sourceRoot":"","sources":["../../../agent/directory/okta.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"okta.d.ts","sourceRoot":"","sources":["../../../agent/directory/okta.ts"],"names":[],"mappings":"AAGA,OAAO,EAML,KAAK,iBAAiB,EACvB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAkB,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvE,oGAAoG;AACpG,MAAM,MAAM,SAAS,GAAG,OAAO,KAAK,CAAC;AAoDrC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE;oFACgF;IAChF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;CAC5C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAA+B,GAAG,OAAO,CAiBzH;AAUD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO9E;AAED,2GAA2G;AAC3G,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,CAkIpF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// THE OKTA DIRECTORY ADAPTER — everything it returns is a PROPOSAL, and nothing here confirms a link. The
|
|
2
2
|
// grade follows the field: `externalId` is attested (the SCIM write-back), `credentials.userName` asserted.
|
|
3
|
-
import { getSecretVia } from "@open-everywhere/core/agent/adapter-api.js";
|
|
3
|
+
import { declaredConnectionSignIns, getSecretVia, OKTA_LINK_PLATFORM, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
4
4
|
/** Okta user statuses that mean "this person is deactivated". Everything else (ACTIVE, PROVISIONED,
|
|
5
5
|
* STAGED, RECOVERY, LOCKED_OUT, PASSWORD_EXPIRED) is a live human with an access problem, not a
|
|
6
6
|
* departed one — and treating a locked-out person as deprovisioned would suspend their identity
|
|
@@ -13,6 +13,34 @@ const APP_PAGE_LIMIT = 500;
|
|
|
13
13
|
/** A hard cap on pages followed per resource — a runaway `Link: rel="next"` loop (a proxy rewriting
|
|
14
14
|
* the header, a vendor bug) must not spin forever inside a scheduled tick. 200 pages ≈ 40k users. */
|
|
15
15
|
const MAX_PAGES = 200;
|
|
16
|
+
/**
|
|
17
|
+
* (OPE-2453) True when a declared `xaa` sign-in names this Okta org as its IdP. Only then does the arm propose each
|
|
18
|
+
* person's OWN Okta account (`okta`, the Okta user id) beside the app accounts: the Cross App Access completion
|
|
19
|
+
* admits a sign-in only for a confirmed `okta` account, and nothing else writes one. With no `xaa` declaration
|
|
20
|
+
* the arm proposes exactly what it did before, so a deployment that does not use Cross App Access sees no change.
|
|
21
|
+
*/
|
|
22
|
+
export function xaaNamesOrg(orgUrl, declared = declaredConnectionSignIns()) {
|
|
23
|
+
let origin;
|
|
24
|
+
try {
|
|
25
|
+
origin = new URL(orgUrl).origin;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
for (const v of declared.values()) {
|
|
31
|
+
const rec = v;
|
|
32
|
+
if (rec.mode !== "xaa" || typeof rec.idpIssuer !== "string")
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
if (new URL(rec.idpIssuer).origin === origin)
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
16
44
|
/** The display name to show a human, from whichever profile fields Okta actually filled in. DISPLAY
|
|
17
45
|
* ONLY — link-store.ts invariant 4 means it can never become a lookup key, here or downstream. */
|
|
18
46
|
function displayNameOf(user) {
|
|
@@ -39,6 +67,7 @@ export function makeOktaDirectoryProvider(deps) {
|
|
|
39
67
|
const { config } = deps;
|
|
40
68
|
const fetchImpl = deps.fetch ?? fetch;
|
|
41
69
|
const resolveToken = deps.resolveToken ?? ((broker, ref) => getSecretVia(broker, ref));
|
|
70
|
+
const proposeOwnAccount = deps.proposeOwnAccount ?? (() => xaaNamesOrg(config.orgUrl));
|
|
42
71
|
// The operator's OWN Okta origin — the ONLY origin the SSWS token may ever be sent to. Computed
|
|
43
72
|
// once; every request in getAll (the first AND every `Link: rel="next"` hop) is checked against it.
|
|
44
73
|
const orgOrigin = new URL(config.orgUrl).origin;
|
|
@@ -148,6 +177,8 @@ export function makeOktaDirectoryProvider(deps) {
|
|
|
148
177
|
const users = await getAll(`/api/v1/users?limit=${PAGE_LIMIT}`, token);
|
|
149
178
|
const groups = await groupsByUser(token);
|
|
150
179
|
const accounts = await accountsByUser(token);
|
|
180
|
+
// Read once per sync, like the token: a declaration change takes effect at the next tick.
|
|
181
|
+
const ownAccount = proposeOwnAccount();
|
|
151
182
|
const people = [];
|
|
152
183
|
for (const user of users) {
|
|
153
184
|
if (!user.id)
|
|
@@ -157,7 +188,8 @@ export function makeOktaDirectoryProvider(deps) {
|
|
|
157
188
|
externalId: user.id,
|
|
158
189
|
displayName: displayNameOf(user),
|
|
159
190
|
...(email ? { email } : {}),
|
|
160
|
-
|
|
191
|
+
// The Okta user id IS Okta's own answer for its own account, so the grade is "attested".
|
|
192
|
+
accounts: ownAccount ? [...(accounts.get(user.id) ?? []), { platform: OKTA_LINK_PLATFORM, nativeId: user.id, grade: "attested" }] : (accounts.get(user.id) ?? []),
|
|
161
193
|
groups: (groups.get(user.id) ?? []).slice().sort(),
|
|
162
194
|
deprovisioned: DEPROVISIONED_STATUSES.has((user.status ?? "").toUpperCase()),
|
|
163
195
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"okta.js","sourceRoot":"","sources":["../../../agent/directory/okta.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAC1G,4GAA4G;AAE5G,OAAO,
|
|
1
|
+
{"version":3,"file":"okta.js","sourceRoot":"","sources":["../../../agent/directory/okta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The fetch a reader uses. Injected in tests with recorded fixtures; the live one is the global fetch. */
|
|
2
|
+
export type NativeFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
/** The live fetch every reader uses by default: the global fetch with a per-call timeout. */
|
|
4
|
+
export declare const timedFetch: NativeFetch;
|
|
5
|
+
/** Read a response body as text, refusing one larger than the cap. */
|
|
6
|
+
export declare function readCappedText(res: Response): Promise<string>;
|
|
7
|
+
/** Read a JSON body, or `{}` when it does not parse (an error body need not be JSON). */
|
|
8
|
+
export declare function readJsonLoose(res: Response): Promise<Record<string, unknown>>;
|
|
9
|
+
/** A thrown error a reader turns into "the platform did not answer, check again later". Never echoes a body. */
|
|
10
|
+
export declare function unexpectedStatus(what: string, status: number): Error;
|
|
11
|
+
/**
|
|
12
|
+
* A per-process access-token cache, keyed by the caller (credential + subject). A token is reused until one
|
|
13
|
+
* minute before it expires. The key never holds the secret itself: callers pass a non-secret key.
|
|
14
|
+
*/
|
|
15
|
+
export declare class TokenCache {
|
|
16
|
+
#private;
|
|
17
|
+
get(key: string, now: number): string | undefined;
|
|
18
|
+
set(key: string, token: string, expiresInSeconds: unknown, now: number): void;
|
|
19
|
+
}
|
|
20
|
+
/** True when `raw` is an https URL on `host` or one of its subdomains. A reader sends its token only there. */
|
|
21
|
+
export declare function onHost(raw: string, host: string): boolean;
|
|
22
|
+
//# sourceMappingURL=native-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-http.d.ts","sourceRoot":"","sources":["../../../agent/meetings/native-http.ts"],"names":[],"mappings":"AAIA,2GAA2G;AAC3G,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAOjF,6FAA6F;AAC7F,eAAO,MAAM,UAAU,EAAE,WAAyG,CAAC;AAKnI,sEAAsE;AACtE,wBAAsB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAMnE;AAED,yFAAyF;AACzF,wBAAsB,aAAa,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAOnF;AAED,gHAAgH;AAChH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAEpE;AAOD;;;GAGG;AACH,qBAAa,UAAU;;IAErB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGhD;IACD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAG5E;CACF;AAED,+GAA+G;AAC/G,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOzD"}
|