self-bench 0.3.2 → 0.3.4
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/.dockerignore +0 -1
- package/Dockerfile +2 -1
- package/Dockerfile.sandbox +1 -1
- package/README.md +114 -170
- package/bun.lock +60 -12
- package/compose.yaml +11 -5
- package/dist/agent-smoke-main.js +3 -2
- package/dist/agent-smoke-main.js.map +1 -1
- package/dist/agent-smoke.d.ts +2 -1
- package/dist/agent-smoke.d.ts.map +1 -1
- package/dist/agent-smoke.js +6 -1
- package/dist/agent-smoke.js.map +1 -1
- package/dist/api.d.ts +24 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +65 -20
- package/dist/api.js.map +1 -1
- package/dist/artifacts.d.ts +0 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -3
- package/dist/artifacts.js.map +1 -1
- package/dist/build-metadata.js +1 -1
- package/dist/cli.js +75 -11
- package/dist/cli.js.map +1 -1
- package/dist/codex-review.d.ts +2 -1
- package/dist/codex-review.d.ts.map +1 -1
- package/dist/codex-review.js +26 -22
- package/dist/codex-review.js.map +1 -1
- package/dist/config.d.ts +36 -11
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +89 -17
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +23 -3
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +31 -6
- package/dist/contracts.js.map +1 -1
- package/dist/eval-main.js +5 -4
- package/dist/eval-main.js.map +1 -1
- package/dist/evaluate.d.ts +2 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +14 -8
- package/dist/evaluate.js.map +1 -1
- package/dist/harbor-environment.d.ts +2 -0
- package/dist/harbor-environment.d.ts.map +1 -0
- package/dist/harbor-environment.js +15 -0
- package/dist/harbor-environment.js.map +1 -0
- package/dist/harbor-results.d.ts.map +1 -1
- package/dist/harbor-results.js +2 -0
- package/dist/harbor-results.js.map +1 -1
- package/dist/process.d.ts +2 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js.map +1 -1
- package/dist/provenance.d.ts +0 -1
- package/dist/provenance.d.ts.map +1 -1
- package/dist/provenance.js.map +1 -1
- package/dist/providers.d.ts +8 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +14 -0
- package/dist/providers.js.map +1 -0
- package/dist/reaudit-main.js +14 -11
- package/dist/reaudit-main.js.map +1 -1
- package/dist/repair-main.js +12 -9
- package/dist/repair-main.js.map +1 -1
- package/dist/review/assets/index-CZ3G759O.js +1568 -0
- package/dist/review/assets/index-DgSLzX8l.css +1 -0
- package/dist/review/assets/{mojo-Blby8dQK.js → mojo-DJz3ZmWd.js} +1 -1
- package/dist/review/assets/typst-BUadGCkm.js +1 -0
- package/dist/review/index.html +2 -2
- package/dist/sandbox-author.bundle.js +40 -6
- package/dist/sandbox-repair.bundle.js +8 -7
- package/dist/sandbox-repair.js +7 -7
- package/dist/sandbox-repair.js.map +1 -1
- package/dist/sandbox-review.bundle.js +29 -25
- package/dist/sandbox-review.js +5 -3
- package/dist/sandbox-review.js.map +1 -1
- package/dist/sandbox-timeout.d.ts +11 -0
- package/dist/sandbox-timeout.d.ts.map +1 -0
- package/dist/sandbox-timeout.js +41 -0
- package/dist/sandbox-timeout.js.map +1 -0
- package/dist/sandbox-validation-repair.bundle.js +49 -15
- package/dist/sandbox-validation-repair.js +11 -9
- package/dist/sandbox-validation-repair.js.map +1 -1
- package/dist/sandbox.d.ts +2 -2
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/sandbox.js +10 -3
- package/dist/sandbox.js.map +1 -1
- package/dist/subscription-auth.d.ts +12 -1
- package/dist/subscription-auth.d.ts.map +1 -1
- package/dist/subscription-auth.js +16 -16
- package/dist/subscription-auth.js.map +1 -1
- package/dist/{activities.d.ts → temporal/activities.d.ts} +3 -4
- package/dist/temporal/activities.d.ts.map +1 -0
- package/dist/{activities.js → temporal/activities.js} +51 -34
- package/dist/temporal/activities.js.map +1 -0
- package/dist/{temporal.d.ts → temporal/connection.d.ts} +2 -2
- package/dist/temporal/connection.d.ts.map +1 -0
- package/dist/{temporal.js → temporal/connection.js} +1 -1
- package/dist/temporal/connection.js.map +1 -0
- package/dist/{worker-main.d.ts.map → temporal/worker-main.d.ts.map} +1 -1
- package/dist/{worker-main.js → temporal/worker-main.js} +6 -10
- package/dist/temporal/worker-main.js.map +1 -0
- package/dist/{workflow.d.ts → temporal/workflow.d.ts} +1 -1
- package/dist/temporal/workflow.d.ts.map +1 -0
- package/dist/temporal/workflow.js.map +1 -0
- package/dist/terminal-prompts.d.ts +26 -0
- package/dist/terminal-prompts.d.ts.map +1 -0
- package/dist/terminal-prompts.js +137 -0
- package/dist/terminal-prompts.js.map +1 -0
- package/dist/terminal-reporter.d.ts +30 -0
- package/dist/terminal-reporter.d.ts.map +1 -0
- package/dist/terminal-reporter.js +90 -0
- package/dist/terminal-reporter.js.map +1 -0
- package/dist/validate-main.js +8 -2
- package/dist/validate-main.js.map +1 -1
- package/dist/vercel-cli.d.ts +64 -0
- package/dist/vercel-cli.d.ts.map +1 -0
- package/dist/vercel-cli.js +303 -0
- package/dist/vercel-cli.js.map +1 -0
- package/dist/vercel-command.d.ts +14 -0
- package/dist/vercel-command.d.ts.map +1 -0
- package/dist/vercel-command.js +93 -0
- package/dist/vercel-command.js.map +1 -0
- package/dist/vercel-executor.d.ts +14 -0
- package/dist/vercel-executor.d.ts.map +1 -0
- package/dist/vercel-executor.js +356 -0
- package/dist/vercel-executor.js.map +1 -0
- package/dist/vercel-fetch.d.ts +6 -0
- package/dist/vercel-fetch.d.ts.map +1 -0
- package/dist/vercel-fetch.js +39 -0
- package/dist/vercel-fetch.js.map +1 -0
- package/dist/vercel-profile.d.ts +34 -0
- package/dist/vercel-profile.d.ts.map +1 -0
- package/dist/vercel-profile.js +244 -0
- package/dist/vercel-profile.js.map +1 -0
- package/dist/vercel-runtime-image.d.ts +51 -0
- package/dist/vercel-runtime-image.d.ts.map +1 -0
- package/dist/vercel-runtime-image.js +123 -0
- package/dist/vercel-runtime-image.js.map +1 -0
- package/dist/vercel-setup-probe.d.ts +49 -0
- package/dist/vercel-setup-probe.d.ts.map +1 -0
- package/dist/vercel-setup-probe.js +151 -0
- package/dist/vercel-setup-probe.js.map +1 -0
- package/dist/vercel-setup.d.ts +42 -0
- package/dist/vercel-setup.d.ts.map +1 -0
- package/dist/vercel-setup.js +280 -0
- package/dist/vercel-setup.js.map +1 -0
- package/docs/evaluations.md +15 -12
- package/docs/operations.md +134 -21
- package/package.json +8 -6
- package/src/agent-smoke-main.ts +3 -2
- package/src/agent-smoke.ts +8 -2
- package/src/api.ts +71 -20
- package/src/artifacts.ts +0 -4
- package/src/cli.ts +81 -11
- package/src/codex-review.ts +63 -54
- package/src/config.ts +150 -28
- package/src/contracts.ts +33 -11
- package/src/eval-main.ts +5 -4
- package/src/evaluate.ts +18 -11
- package/src/harbor-environment.ts +17 -0
- package/src/harbor-results.ts +2 -0
- package/src/process.ts +3 -1
- package/src/provenance.ts +0 -1
- package/src/providers.ts +22 -0
- package/src/reaudit-main.ts +17 -11
- package/src/repair-main.ts +15 -9
- package/src/sandbox-repair.ts +7 -11
- package/src/sandbox-review.ts +5 -3
- package/src/sandbox-timeout.ts +56 -0
- package/src/sandbox-validation-repair.ts +13 -10
- package/src/sandbox.ts +15 -5
- package/src/subscription-auth.ts +30 -17
- package/src/{activities.ts → temporal/activities.ts} +53 -42
- package/src/{temporal.ts → temporal/connection.ts} +1 -1
- package/src/{worker-main.ts → temporal/worker-main.ts} +6 -10
- package/src/{workflow.ts → temporal/workflow.ts} +10 -10
- package/src/terminal-prompts.ts +197 -0
- package/src/terminal-reporter.ts +126 -0
- package/src/validate-main.ts +9 -3
- package/src/vercel-cli.ts +403 -0
- package/src/vercel-command.ts +127 -0
- package/src/vercel-executor.ts +448 -0
- package/src/vercel-fetch.ts +45 -0
- package/src/vercel-profile.ts +319 -0
- package/src/vercel-runtime-image.ts +204 -0
- package/src/vercel-setup-probe.ts +229 -0
- package/src/vercel-setup.ts +419 -0
- package/dist/activities.d.ts.map +0 -1
- package/dist/activities.js.map +0 -1
- package/dist/extensions/review.d.ts +0 -3
- package/dist/extensions/review.d.ts.map +0 -1
- package/dist/extensions/review.js +0 -44
- package/dist/extensions/review.js.map +0 -1
- package/dist/review/assets/index-Dq-6DFzI.css +0 -1
- package/dist/review/assets/index-DurW_RD7.js +0 -1567
- package/dist/review/assets/typst-DHCkPAjA.js +0 -1
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js.map +0 -1
- package/dist/worker-main.js.map +0 -1
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/src/extensions/review.ts +0 -54
- /package/dist/{worker-main.d.ts → temporal/worker-main.d.ts} +0 -0
- /package/dist/{workflow.js → temporal/workflow.js} +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { APIError, Sandbox } from "@vercel/sandbox";
|
|
3
|
+
import type { VercelCredentials } from "./config.js";
|
|
4
|
+
import { HOBBY_VERCEL_TIMEOUT_CAP_MS, STANDARD_VERCEL_TIMEOUT_CAP_MS } from "./sandbox-timeout.js";
|
|
5
|
+
|
|
6
|
+
const PROBE_COMMAND_TIMEOUT_MS = 15_000;
|
|
7
|
+
const CLEANUP_TIMEOUT_MS = 60_000;
|
|
8
|
+
const ABSENCE_RETRIES = 3;
|
|
9
|
+
|
|
10
|
+
export interface VercelCapability {
|
|
11
|
+
readonly timeoutCapMs: number;
|
|
12
|
+
readonly timeoutClass: "45m" | "standard";
|
|
13
|
+
readonly checkedAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ProbeSandbox {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
runCommand(
|
|
19
|
+
command: string,
|
|
20
|
+
args: string[],
|
|
21
|
+
options: { readonly signal?: AbortSignal; readonly timeoutMs?: number },
|
|
22
|
+
): Promise<{ readonly exitCode: number }>;
|
|
23
|
+
delete(options: { readonly signal?: AbortSignal }): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface VercelSandboxProbeApi {
|
|
27
|
+
create(input: {
|
|
28
|
+
readonly token: string;
|
|
29
|
+
readonly teamId: string;
|
|
30
|
+
readonly projectId: string;
|
|
31
|
+
readonly image: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly persistent: false;
|
|
34
|
+
readonly resources: { readonly vcpus: 1 };
|
|
35
|
+
readonly signal?: AbortSignal;
|
|
36
|
+
readonly tags: Readonly<Record<string, string>>;
|
|
37
|
+
readonly timeout: number;
|
|
38
|
+
}): Promise<ProbeSandbox>;
|
|
39
|
+
get(input: {
|
|
40
|
+
readonly token: string;
|
|
41
|
+
readonly teamId: string;
|
|
42
|
+
readonly projectId: string;
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly resume: false;
|
|
45
|
+
readonly signal?: AbortSignal;
|
|
46
|
+
}): Promise<ProbeSandbox>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function probeVercelCapability(
|
|
50
|
+
input: {
|
|
51
|
+
readonly credentials: VercelCredentials;
|
|
52
|
+
readonly image: string;
|
|
53
|
+
readonly signal?: AbortSignal;
|
|
54
|
+
},
|
|
55
|
+
api: VercelSandboxProbeApi = vercelSandboxProbeApi,
|
|
56
|
+
): Promise<VercelCapability> {
|
|
57
|
+
try {
|
|
58
|
+
await runProbe(input, STANDARD_VERCEL_TIMEOUT_CAP_MS, api);
|
|
59
|
+
return {
|
|
60
|
+
timeoutCapMs: STANDARD_VERCEL_TIMEOUT_CAP_MS,
|
|
61
|
+
timeoutClass: "standard",
|
|
62
|
+
checkedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (!isFortyFiveMinuteLimit(error)) {
|
|
66
|
+
throw sanitizeError(error, input.credentials.token);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await runProbe(input, HOBBY_VERCEL_TIMEOUT_CAP_MS, api);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw sanitizeError(error, input.credentials.token);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
timeoutCapMs: HOBBY_VERCEL_TIMEOUT_CAP_MS,
|
|
77
|
+
timeoutClass: "45m",
|
|
78
|
+
checkedAt: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const vercelSandboxProbeApi: VercelSandboxProbeApi = {
|
|
83
|
+
async create(input) {
|
|
84
|
+
return await Sandbox.create(input);
|
|
85
|
+
},
|
|
86
|
+
async get(input) {
|
|
87
|
+
return await Sandbox.get(input);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
async function runProbe(
|
|
92
|
+
input: {
|
|
93
|
+
readonly credentials: VercelCredentials;
|
|
94
|
+
readonly image: string;
|
|
95
|
+
readonly signal?: AbortSignal;
|
|
96
|
+
},
|
|
97
|
+
timeoutMs: number,
|
|
98
|
+
api: VercelSandboxProbeApi,
|
|
99
|
+
): Promise<void> {
|
|
100
|
+
input.signal?.throwIfAborted();
|
|
101
|
+
const name = `selfbench-setup-${crypto.randomUUID().replaceAll("-", "").slice(0, 20)}`;
|
|
102
|
+
let sandbox: ProbeSandbox | undefined;
|
|
103
|
+
let primaryError: unknown;
|
|
104
|
+
try {
|
|
105
|
+
sandbox = await api.create({
|
|
106
|
+
...input.credentials,
|
|
107
|
+
image: input.image,
|
|
108
|
+
name,
|
|
109
|
+
persistent: false,
|
|
110
|
+
resources: { vcpus: 1 },
|
|
111
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
112
|
+
tags: { selfbench_stage: "setup-probe" },
|
|
113
|
+
timeout: timeoutMs,
|
|
114
|
+
});
|
|
115
|
+
const result = await sandbox.runCommand("true", [], {
|
|
116
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
117
|
+
timeoutMs: PROBE_COMMAND_TIMEOUT_MS,
|
|
118
|
+
});
|
|
119
|
+
if (result.exitCode !== 0) {
|
|
120
|
+
throw new Error(`Vercel setup probe exited ${result.exitCode}`);
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
primaryError = error;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let cleanupError: unknown;
|
|
127
|
+
const status = httpStatus(primaryError);
|
|
128
|
+
const recoverMissingHandle =
|
|
129
|
+
sandbox === undefined && primaryError !== undefined && (status === undefined || status >= 500);
|
|
130
|
+
try {
|
|
131
|
+
await cleanupProbe(name, sandbox, recoverMissingHandle, input.credentials, api);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
cleanupError = error;
|
|
134
|
+
}
|
|
135
|
+
if (cleanupError !== undefined) {
|
|
136
|
+
throw new AggregateError(
|
|
137
|
+
primaryError === undefined ? [cleanupError] : [primaryError, cleanupError],
|
|
138
|
+
`Vercel setup probe ${name} could not be deleted cleanly`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (primaryError !== undefined) {
|
|
142
|
+
throw primaryError;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function cleanupProbe(
|
|
147
|
+
name: string,
|
|
148
|
+
sandbox: ProbeSandbox | undefined,
|
|
149
|
+
recoverMissingHandle: boolean,
|
|
150
|
+
credentials: VercelCredentials,
|
|
151
|
+
api: VercelSandboxProbeApi,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
let directDeleteError: unknown;
|
|
154
|
+
if (sandbox) {
|
|
155
|
+
try {
|
|
156
|
+
await sandbox.delete({ signal: AbortSignal.timeout(CLEANUP_TIMEOUT_MS) });
|
|
157
|
+
await confirmAbsent(name, credentials, api);
|
|
158
|
+
return;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
directDeleteError = error;
|
|
161
|
+
}
|
|
162
|
+
} else if (!recoverMissingHandle) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await confirmAbsent(name, credentials, api);
|
|
168
|
+
} catch (recoveryError) {
|
|
169
|
+
throw new AggregateError(
|
|
170
|
+
directDeleteError === undefined ? [recoveryError] : [directDeleteError, recoveryError],
|
|
171
|
+
`failed to recover Vercel setup probe ${name} by exact name`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function confirmAbsent(
|
|
177
|
+
name: string,
|
|
178
|
+
credentials: VercelCredentials,
|
|
179
|
+
api: VercelSandboxProbeApi,
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
for (let attempt = 0; attempt < ABSENCE_RETRIES; attempt += 1) {
|
|
182
|
+
try {
|
|
183
|
+
const recovered = await api.get({
|
|
184
|
+
...credentials,
|
|
185
|
+
name,
|
|
186
|
+
resume: false,
|
|
187
|
+
signal: AbortSignal.timeout(CLEANUP_TIMEOUT_MS),
|
|
188
|
+
});
|
|
189
|
+
await recovered.delete({ signal: AbortSignal.timeout(CLEANUP_TIMEOUT_MS) });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (httpStatus(error) === 404) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
await delay(250 * (attempt + 1));
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`Vercel setup probe ${name} still exists after deletion`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isFortyFiveMinuteLimit(error: unknown): boolean {
|
|
202
|
+
return (
|
|
203
|
+
httpStatus(error) === 400 &&
|
|
204
|
+
/`?timeout`?.*(?:should be|must be).*<=\s*45m/i.test(errorDetail(error))
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function httpStatus(error: unknown): number | undefined {
|
|
209
|
+
if (error instanceof APIError) {
|
|
210
|
+
return error.response.status;
|
|
211
|
+
}
|
|
212
|
+
if (error && typeof error === "object" && "status" in error && typeof error.status === "number") {
|
|
213
|
+
return error.status;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function errorDetail(error: unknown): string {
|
|
219
|
+
if (error instanceof APIError) {
|
|
220
|
+
return [error.message, error.text, JSON.stringify(error.json)].filter(Boolean).join(" ");
|
|
221
|
+
}
|
|
222
|
+
return error instanceof Error ? error.message : String(error);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sanitizeError(error: unknown, token: string): Error {
|
|
226
|
+
const sanitized = new Error(errorDetail(error).replaceAll(token, "[redacted]").slice(0, 2_000));
|
|
227
|
+
sanitized.name = error instanceof Error ? error.name : "VercelSetupError";
|
|
228
|
+
return sanitized;
|
|
229
|
+
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { VercelCredentials } from "./config.js";
|
|
5
|
+
import {
|
|
6
|
+
isInteractiveTerminal,
|
|
7
|
+
SetupCanceledError,
|
|
8
|
+
type SetupPrompter,
|
|
9
|
+
TerminalPrompter,
|
|
10
|
+
} from "./terminal-prompts.js";
|
|
11
|
+
import { type SetupReporter, TerminalSetupReporter } from "./terminal-reporter.js";
|
|
12
|
+
import { VercelCli, type VercelProject, type VercelTeam } from "./vercel-cli.js";
|
|
13
|
+
import {
|
|
14
|
+
loadVercelProfileData,
|
|
15
|
+
saveVercelProfile,
|
|
16
|
+
selfBenchConfigDirectory,
|
|
17
|
+
type VercelProfile,
|
|
18
|
+
type VercelProfileData,
|
|
19
|
+
validateVercelProfileName,
|
|
20
|
+
} from "./vercel-profile.js";
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_VCR_REPOSITORY,
|
|
23
|
+
ensureVercelRuntimeImage,
|
|
24
|
+
type VercelRuntimeImageCli,
|
|
25
|
+
vercelRuntimeFingerprint,
|
|
26
|
+
} from "./vercel-runtime-image.js";
|
|
27
|
+
import { probeVercelCapability, type VercelCapability } from "./vercel-setup-probe.js";
|
|
28
|
+
|
|
29
|
+
const DEFAULT_PROFILE_NAME = "default";
|
|
30
|
+
const DEFAULT_PROJECT_NAME = "selfbench-sandbox";
|
|
31
|
+
|
|
32
|
+
export interface VercelSetupCli extends VercelRuntimeImageCli {
|
|
33
|
+
ensureAvailable(): Promise<void>;
|
|
34
|
+
ensureLoggedIn(): Promise<void>;
|
|
35
|
+
listTeams(): Promise<readonly VercelTeam[]>;
|
|
36
|
+
listProjects(teamSlug: string): Promise<readonly VercelProject[]>;
|
|
37
|
+
createProject(teamSlug: string, projectName: string): Promise<VercelProject>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface VercelSetupServices {
|
|
41
|
+
readonly cli: VercelSetupCli;
|
|
42
|
+
readonly prompter: SetupPrompter;
|
|
43
|
+
readonly reporter: SetupReporter;
|
|
44
|
+
readonly interactive: boolean;
|
|
45
|
+
readonly loadProfiles: (directory: string) => Promise<VercelProfileData>;
|
|
46
|
+
readonly saveProfile: typeof saveVercelProfile;
|
|
47
|
+
readonly probe: (input: {
|
|
48
|
+
readonly credentials: VercelCredentials;
|
|
49
|
+
readonly image: string;
|
|
50
|
+
readonly signal?: AbortSignal;
|
|
51
|
+
}) => Promise<VercelCapability>;
|
|
52
|
+
readonly sleep: (delayMs: number) => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VercelSetupResult {
|
|
56
|
+
readonly profileName: string;
|
|
57
|
+
readonly profile: VercelProfile;
|
|
58
|
+
readonly configDirectory: string;
|
|
59
|
+
readonly projectCreated: boolean;
|
|
60
|
+
readonly imagePublished: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function setupVercel(
|
|
64
|
+
options: {
|
|
65
|
+
readonly profileName?: string;
|
|
66
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
67
|
+
readonly projectRoot?: string;
|
|
68
|
+
readonly verbose?: boolean;
|
|
69
|
+
} = {},
|
|
70
|
+
providedServices?: VercelSetupServices,
|
|
71
|
+
): Promise<VercelSetupResult> {
|
|
72
|
+
const services = providedServices ?? defaultServices(options.verbose ?? false);
|
|
73
|
+
if (!services.interactive) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"self-bench setup vercel requires an interactive terminal; unattended environments should set VERCEL_TOKEN, VERCEL_TEAM_ID, VERCEL_PROJECT_ID, SELFBENCH_VERCEL_IMAGE, and optionally SELFBENCH_VERCEL_TIMEOUT_CAP",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
services.reporter.intro("Configure Vercel Sandbox");
|
|
79
|
+
const profileName = validateVercelProfileName(options.profileName ?? DEFAULT_PROFILE_NAME);
|
|
80
|
+
const environment = options.environment ?? process.env;
|
|
81
|
+
const configDirectory = selfBenchConfigDirectory(environment);
|
|
82
|
+
const projectRoot = resolve(options.projectRoot ?? defaultProjectRoot());
|
|
83
|
+
const existingData = await services.loadProfiles(configDirectory);
|
|
84
|
+
const existingProfile = existingData.profiles[profileName];
|
|
85
|
+
const existingToken = existingData.tokens[profileName];
|
|
86
|
+
const action = await chooseSetupAction(services.prompter, existingProfile, existingToken);
|
|
87
|
+
|
|
88
|
+
await services.cli.ensureAvailable();
|
|
89
|
+
await services.cli.ensureLoggedIn();
|
|
90
|
+
|
|
91
|
+
const teams = await services.cli.listTeams();
|
|
92
|
+
if (teams.length === 0) {
|
|
93
|
+
throw new Error("The Vercel account has no accessible team or personal scope");
|
|
94
|
+
}
|
|
95
|
+
let selected: SelectedProject;
|
|
96
|
+
let projectCreated = false;
|
|
97
|
+
if (action === "change-project" || !existingProfile) {
|
|
98
|
+
const team = await chooseTeam(services.prompter, teams);
|
|
99
|
+
const projectSelection = await chooseProject(services, team);
|
|
100
|
+
selected = { team, project: projectSelection.project };
|
|
101
|
+
projectCreated = projectSelection.created;
|
|
102
|
+
} else {
|
|
103
|
+
selected = await resolveExistingSelection(services.cli, teams, existingProfile);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const fingerprint = await vercelRuntimeFingerprint(projectRoot);
|
|
107
|
+
const imageResult = await ensureVercelRuntimeImage({
|
|
108
|
+
services: {
|
|
109
|
+
cli: services.cli,
|
|
110
|
+
prompter: services.prompter,
|
|
111
|
+
reporter: services.reporter,
|
|
112
|
+
sleep: services.sleep,
|
|
113
|
+
},
|
|
114
|
+
scope: { teamSlug: selected.team.slug, projectId: selected.project.id },
|
|
115
|
+
projectRoot,
|
|
116
|
+
fingerprint,
|
|
117
|
+
preferredRepository:
|
|
118
|
+
action === "change-project" || !existingProfile
|
|
119
|
+
? DEFAULT_VCR_REPOSITORY
|
|
120
|
+
: existingProfile.vcrRepository,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const token =
|
|
124
|
+
action === "revalidate" && existingToken
|
|
125
|
+
? existingToken
|
|
126
|
+
: await requestProjectToken(services, selected);
|
|
127
|
+
const credentials = {
|
|
128
|
+
token,
|
|
129
|
+
teamId: selected.team.id,
|
|
130
|
+
projectId: selected.project.id,
|
|
131
|
+
};
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const interrupt = (): void => controller.abort(new SetupCanceledError());
|
|
134
|
+
process.once("SIGINT", interrupt);
|
|
135
|
+
let capability: VercelCapability;
|
|
136
|
+
try {
|
|
137
|
+
capability = await services.reporter.task(
|
|
138
|
+
{
|
|
139
|
+
pending: "Verifying Sandbox access and detecting the timeout cap",
|
|
140
|
+
success: "Sandbox access verified",
|
|
141
|
+
failure: "Sandbox verification failed",
|
|
142
|
+
},
|
|
143
|
+
async () => {
|
|
144
|
+
try {
|
|
145
|
+
return await services.probe({
|
|
146
|
+
credentials,
|
|
147
|
+
image: imageResult.image,
|
|
148
|
+
signal: controller.signal,
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (controller.signal.aborted) {
|
|
152
|
+
throw controller.signal.reason;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (controller.signal.aborted) {
|
|
160
|
+
throw controller.signal.reason;
|
|
161
|
+
}
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Vercel Sandbox verification failed: ${errorMessage(error)}. If the access token expired or has the wrong project scope, rerun setup and paste a new project-scoped token; choose Replace the access token when revalidating an existing profile.`,
|
|
164
|
+
{ cause: error },
|
|
165
|
+
);
|
|
166
|
+
} finally {
|
|
167
|
+
process.removeListener("SIGINT", interrupt);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (capability.timeoutClass === "45m") {
|
|
171
|
+
services.reporter.warn(
|
|
172
|
+
"This Vercel scope enforces a 45-minute Sandbox limit. SelfBench will cap longer authoring and repair stages at 45 minutes; accepted tasks remain fully validated, but slow candidates may time out and be rejected.",
|
|
173
|
+
);
|
|
174
|
+
services.reporter.warn(
|
|
175
|
+
"Vercel Hobby usage is intended for personal, non-commercial work. Use a paid team for commercial SelfBench runs.",
|
|
176
|
+
);
|
|
177
|
+
if (
|
|
178
|
+
!(await services.prompter.confirm("Continue with the 45-minute compatibility cap?", true))
|
|
179
|
+
) {
|
|
180
|
+
services.reporter.cancel("Vercel setup canceled before profile activation.");
|
|
181
|
+
throw new SetupCanceledError("Vercel setup canceled before profile activation");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const profile: VercelProfile = {
|
|
186
|
+
teamId: selected.team.id,
|
|
187
|
+
teamSlug: selected.team.slug,
|
|
188
|
+
teamName: selected.team.name,
|
|
189
|
+
projectId: selected.project.id,
|
|
190
|
+
projectName: selected.project.name,
|
|
191
|
+
vcrRepository: imageResult.repository,
|
|
192
|
+
image: imageResult.image,
|
|
193
|
+
runtimeFingerprint: fingerprint,
|
|
194
|
+
timeoutCapMs: capability.timeoutCapMs,
|
|
195
|
+
capabilityCheckedAt: capability.checkedAt,
|
|
196
|
+
};
|
|
197
|
+
await services.saveProfile({ directory: configDirectory, profileName, profile, token });
|
|
198
|
+
services.reporter.finish(`Saved Vercel profile "${profileName}"`, [
|
|
199
|
+
`Location: ${configDirectory}`,
|
|
200
|
+
`Project: ${profile.teamSlug}/${profile.projectName}`,
|
|
201
|
+
`Runtime image: ${imageRepository(profile.image)}`,
|
|
202
|
+
"Digest:",
|
|
203
|
+
` ${imageDigest(profile.image)}`,
|
|
204
|
+
`Vercel tier: ${timeoutTier(capability.timeoutClass)}`,
|
|
205
|
+
`Timeout cap: ${formatDuration(profile.timeoutCapMs)}`,
|
|
206
|
+
"Resources: Project and runtime image remain reusable after self-bench down.",
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
profileName,
|
|
211
|
+
profile,
|
|
212
|
+
configDirectory,
|
|
213
|
+
projectCreated,
|
|
214
|
+
imagePublished: imageResult.published,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function defaultServices(verbose: boolean): VercelSetupServices {
|
|
219
|
+
const reporter = new TerminalSetupReporter({ verbose });
|
|
220
|
+
return {
|
|
221
|
+
cli: new VercelCli(),
|
|
222
|
+
prompter: new TerminalPrompter(),
|
|
223
|
+
reporter,
|
|
224
|
+
interactive: isInteractiveTerminal(),
|
|
225
|
+
loadProfiles: loadVercelProfileData,
|
|
226
|
+
saveProfile: saveVercelProfile,
|
|
227
|
+
probe: probeVercelCapability,
|
|
228
|
+
sleep: async (delayMs) => await delay(delayMs),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
type SetupAction = "revalidate" | "replace-token" | "change-project";
|
|
233
|
+
|
|
234
|
+
async function chooseSetupAction(
|
|
235
|
+
prompter: SetupPrompter,
|
|
236
|
+
profile: VercelProfile | undefined,
|
|
237
|
+
token: string | undefined,
|
|
238
|
+
): Promise<SetupAction> {
|
|
239
|
+
if (!profile) {
|
|
240
|
+
return "change-project";
|
|
241
|
+
}
|
|
242
|
+
const choices = [
|
|
243
|
+
...(token
|
|
244
|
+
? [
|
|
245
|
+
{
|
|
246
|
+
value: "revalidate",
|
|
247
|
+
label: `Revalidate ${profile.teamSlug}/${profile.projectName}`,
|
|
248
|
+
hint: "Recommended",
|
|
249
|
+
},
|
|
250
|
+
]
|
|
251
|
+
: []),
|
|
252
|
+
{ value: "replace-token", label: "Replace the access token" },
|
|
253
|
+
{ value: "change-project", label: "Choose another team or project" },
|
|
254
|
+
];
|
|
255
|
+
const selected = await prompter.select(
|
|
256
|
+
`Vercel profile is already configured for ${profile.teamSlug}/${profile.projectName}`,
|
|
257
|
+
choices,
|
|
258
|
+
token ? "revalidate" : "replace-token",
|
|
259
|
+
);
|
|
260
|
+
if (selected !== "revalidate" && selected !== "replace-token" && selected !== "change-project") {
|
|
261
|
+
throw new Error(`Unsupported Vercel setup action: ${selected}`);
|
|
262
|
+
}
|
|
263
|
+
return selected;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function chooseTeam(
|
|
267
|
+
prompter: SetupPrompter,
|
|
268
|
+
teams: readonly VercelTeam[],
|
|
269
|
+
): Promise<VercelTeam> {
|
|
270
|
+
const selectedId = await prompter.search(
|
|
271
|
+
"Choose a Vercel team or personal scope",
|
|
272
|
+
teams.map((team) => ({
|
|
273
|
+
value: team.id,
|
|
274
|
+
label: team.name,
|
|
275
|
+
hint: [
|
|
276
|
+
team.slug,
|
|
277
|
+
team.id.startsWith("team_") ? undefined : "personal scope",
|
|
278
|
+
team.current ? "current" : undefined,
|
|
279
|
+
]
|
|
280
|
+
.filter(Boolean)
|
|
281
|
+
.join(" · "),
|
|
282
|
+
})),
|
|
283
|
+
teams.find(({ current }) => current)?.id,
|
|
284
|
+
);
|
|
285
|
+
return teams.find(({ id }) => id === selectedId) ?? fail("selected Vercel team disappeared");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function chooseProject(
|
|
289
|
+
services: VercelSetupServices,
|
|
290
|
+
team: VercelTeam,
|
|
291
|
+
): Promise<{ readonly project: VercelProject; readonly created: boolean }> {
|
|
292
|
+
let projects = await services.cli.listProjects(team.slug);
|
|
293
|
+
const modes = [
|
|
294
|
+
...(projects.length > 0 ? [{ value: "existing", label: "Select an existing project" }] : []),
|
|
295
|
+
{ value: "create", label: "Create a dedicated project", hint: "Recommended" },
|
|
296
|
+
];
|
|
297
|
+
const mode = await services.prompter.select(
|
|
298
|
+
"Choose how to configure the Vercel project",
|
|
299
|
+
modes,
|
|
300
|
+
"create",
|
|
301
|
+
);
|
|
302
|
+
if (mode === "existing") {
|
|
303
|
+
const projectId = await services.prompter.search(
|
|
304
|
+
"Choose an existing project",
|
|
305
|
+
projects.map((project) => ({
|
|
306
|
+
value: project.id,
|
|
307
|
+
label: project.name,
|
|
308
|
+
hint: project.id,
|
|
309
|
+
})),
|
|
310
|
+
);
|
|
311
|
+
return {
|
|
312
|
+
project:
|
|
313
|
+
projects.find(({ id }) => id === projectId) ?? fail("selected Vercel project disappeared"),
|
|
314
|
+
created: false,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
for (;;) {
|
|
319
|
+
const name = (await services.prompter.text("New project name", DEFAULT_PROJECT_NAME)).trim();
|
|
320
|
+
if (!isValidProjectName(name)) {
|
|
321
|
+
services.reporter.warn(
|
|
322
|
+
"Project names must use 1-100 lowercase letters, digits, dots, underscores, or hyphens and cannot contain three consecutive hyphens.",
|
|
323
|
+
);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (projects.some((project) => project.name.toLowerCase() === name.toLowerCase())) {
|
|
327
|
+
services.reporter.warn(`Project name "${name}" is already in use. Choose another name.`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
return { project: await services.cli.createProject(team.slug, name), created: true };
|
|
332
|
+
} catch (error) {
|
|
333
|
+
projects = await services.cli.listProjects(team.slug);
|
|
334
|
+
if (projects.some((project) => project.name.toLowerCase() === name.toLowerCase())) {
|
|
335
|
+
services.reporter.warn(`Project name "${name}" became unavailable. Choose another name.`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
throw new Error(`Unable to create project "${name}": ${errorMessage(error)}`, {
|
|
339
|
+
cause: error,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function resolveExistingSelection(
|
|
346
|
+
cli: VercelSetupCli,
|
|
347
|
+
teams: readonly VercelTeam[],
|
|
348
|
+
profile: VercelProfile,
|
|
349
|
+
): Promise<SelectedProject> {
|
|
350
|
+
const team = teams.find(({ id }) => id === profile.teamId);
|
|
351
|
+
if (!team) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`Saved Vercel team ${profile.teamSlug} is no longer accessible; rerun setup and choose another team`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const project = (await cli.listProjects(team.slug)).find(({ id }) => id === profile.projectId);
|
|
357
|
+
if (!project) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Saved Vercel project ${profile.projectName} is no longer accessible; rerun setup and choose another project`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return { team, project };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function requestProjectToken(
|
|
366
|
+
services: VercelSetupServices,
|
|
367
|
+
selected: SelectedProject,
|
|
368
|
+
): Promise<string> {
|
|
369
|
+
services.reporter.message("");
|
|
370
|
+
services.reporter.message(
|
|
371
|
+
`Create a Vercel access token for team ${selected.team.name} and restrict it to project ${selected.project.name}:`,
|
|
372
|
+
);
|
|
373
|
+
services.reporter.message("https://vercel.com/account/settings/tokens");
|
|
374
|
+
services.reporter.message('A descriptive token name such as "selfbench-local" is recommended.');
|
|
375
|
+
const token = (await services.prompter.secret("Paste the project-scoped access token")).trim();
|
|
376
|
+
if (!token) {
|
|
377
|
+
throw new Error("Vercel access token must not be blank");
|
|
378
|
+
}
|
|
379
|
+
return token;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function isValidProjectName(value: string): boolean {
|
|
383
|
+
return /^[a-z0-9._-]{1,100}$/.test(value) && !value.includes("---");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function formatDuration(timeoutMs: number): string {
|
|
387
|
+
return timeoutMs % (60 * 60 * 1_000) === 0
|
|
388
|
+
? `${timeoutMs / (60 * 60 * 1_000)}h`
|
|
389
|
+
: `${timeoutMs / (60 * 1_000)}m`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function timeoutTier(timeoutClass: VercelCapability["timeoutClass"]): string {
|
|
393
|
+
return timeoutClass === "45m" ? "Hobby-compatible" : "Pro/Enterprise-compatible";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function imageRepository(image: string): string {
|
|
397
|
+
return image.slice(0, image.lastIndexOf("@"));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function imageDigest(image: string): string {
|
|
401
|
+
return image.slice(image.lastIndexOf("@") + 1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function defaultProjectRoot(): string {
|
|
405
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function errorMessage(error: unknown): string {
|
|
409
|
+
return error instanceof Error ? error.message : String(error);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function fail(message: string): never {
|
|
413
|
+
throw new Error(message);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
interface SelectedProject {
|
|
417
|
+
readonly team: VercelTeam;
|
|
418
|
+
readonly project: VercelProject;
|
|
419
|
+
}
|
package/dist/activities.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"activities.d.ts","sourceRoot":"","sources":["../src/activities.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAElB,KAAK,SAAS,EAEd,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EAEf,KAAK,gBAAgB,EACtB,MAAM,gBAAgB,CAAC;AAiDxB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,WAAW,yBAA0B,SAAQ,cAAc;IAC/D,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;CACvC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,mBAAmB;IAClC,sBAAsB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC7E,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACrE,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC/D,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC/E,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACzD,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3D,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACvD,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACvD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAa7E;AA2kCD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAElE"}
|