harness-dispatch 0.4.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/LICENSE +21 -0
- package/README.md +582 -0
- package/config.default.yaml +397 -0
- package/data/coding_benchmarks.json +431 -0
- package/dist/auth.d.ts +9 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +73 -0
- package/dist/auth.js.map +1 -0
- package/dist/billing.d.ts +7 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +201 -0
- package/dist/billing.js.map +1 -0
- package/dist/bin.d.ts +6 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +488 -0
- package/dist/bin.js.map +1 -0
- package/dist/breaker-store.d.ts +31 -0
- package/dist/breaker-store.d.ts.map +1 -0
- package/dist/breaker-store.js +103 -0
- package/dist/breaker-store.js.map +1 -0
- package/dist/circuit-breaker.d.ts +47 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +102 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1248 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/live.d.ts +49 -0
- package/dist/dashboard/live.d.ts.map +1 -0
- package/dist/dashboard/live.js +149 -0
- package/dist/dashboard/live.js.map +1 -0
- package/dist/dispatch-log.d.ts +46 -0
- package/dist/dispatch-log.d.ts.map +1 -0
- package/dist/dispatch-log.js +89 -0
- package/dist/dispatch-log.js.map +1 -0
- package/dist/dispatchers/base.d.ts +55 -0
- package/dist/dispatchers/base.d.ts.map +1 -0
- package/dist/dispatchers/base.js +80 -0
- package/dist/dispatchers/base.js.map +1 -0
- package/dist/dispatchers/generic-cli.d.ts +30 -0
- package/dist/dispatchers/generic-cli.d.ts.map +1 -0
- package/dist/dispatchers/generic-cli.js +471 -0
- package/dist/dispatchers/generic-cli.js.map +1 -0
- package/dist/dispatchers/openai-compatible.d.ts +57 -0
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -0
- package/dist/dispatchers/openai-compatible.js +662 -0
- package/dist/dispatchers/openai-compatible.js.map +1 -0
- package/dist/dispatchers/shared/kill-tree.d.ts +21 -0
- package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -0
- package/dist/dispatchers/shared/kill-tree.js +35 -0
- package/dist/dispatchers/shared/kill-tree.js.map +1 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts +22 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -0
- package/dist/dispatchers/shared/rate-limit-headers.js +124 -0
- package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts +53 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -0
- package/dist/dispatchers/shared/stream-subprocess.js +274 -0
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -0
- package/dist/dispatchers/shared/subprocess.d.ts +25 -0
- package/dist/dispatchers/shared/subprocess.d.ts.map +1 -0
- package/dist/dispatchers/shared/subprocess.js +110 -0
- package/dist/dispatchers/shared/subprocess.js.map +1 -0
- package/dist/dispatchers/shared/which-available.d.ts +2 -0
- package/dist/dispatchers/shared/which-available.d.ts.map +1 -0
- package/dist/dispatchers/shared/which-available.js +8 -0
- package/dist/dispatchers/shared/which-available.js.map +1 -0
- package/dist/dispatchers/shared/windows-cmd.d.ts +38 -0
- package/dist/dispatchers/shared/windows-cmd.d.ts.map +1 -0
- package/dist/dispatchers/shared/windows-cmd.js +91 -0
- package/dist/dispatchers/shared/windows-cmd.js.map +1 -0
- package/dist/http/server.d.ts +15 -0
- package/dist/http/server.d.ts.map +1 -0
- package/dist/http/server.js +495 -0
- package/dist/http/server.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/job-runner.d.ts +19 -0
- package/dist/job-runner.d.ts.map +1 -0
- package/dist/job-runner.js +42 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/jobs.d.ts +116 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +542 -0
- package/dist/jobs.js.map +1 -0
- package/dist/leaderboard.d.ts +62 -0
- package/dist/leaderboard.d.ts.map +1 -0
- package/dist/leaderboard.js +287 -0
- package/dist/leaderboard.js.map +1 -0
- package/dist/mcp/config-hot-reload.d.ts +64 -0
- package/dist/mcp/config-hot-reload.d.ts.map +1 -0
- package/dist/mcp/config-hot-reload.js +144 -0
- package/dist/mcp/config-hot-reload.js.map +1 -0
- package/dist/mcp/dispatcher-factory.d.ts +20 -0
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -0
- package/dist/mcp/dispatcher-factory.js +30 -0
- package/dist/mcp/dispatcher-factory.js.map +1 -0
- package/dist/mcp/resources.d.ts +8 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +45 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +80 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +171 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +658 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/observability/index.d.ts +52 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +111 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/spans.d.ts +47 -0
- package/dist/observability/spans.d.ts.map +1 -0
- package/dist/observability/spans.js +72 -0
- package/dist/observability/spans.js.map +1 -0
- package/dist/quota.d.ts +91 -0
- package/dist/quota.d.ts.map +1 -0
- package/dist/quota.js +331 -0
- package/dist/quota.js.map +1 -0
- package/dist/route-policy.d.ts +24 -0
- package/dist/route-policy.d.ts.map +1 -0
- package/dist/route-policy.js +92 -0
- package/dist/route-policy.js.map +1 -0
- package/dist/router.d.ts +162 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +878 -0
- package/dist/router.js.map +1 -0
- package/dist/safety.d.ts +7 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +33 -0
- package/dist/safety.js.map +1 -0
- package/dist/status.d.ts +100 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +228 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +402 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/dist/working-dir.d.ts +15 -0
- package/dist/working-dir.d.ts.map +1 -0
- package/dist/working-dir.js +22 -0
- package/dist/working-dir.js.map +1 -0
- package/dist/workspaces.d.ts +19 -0
- package/dist/workspaces.d.ts.map +1 -0
- package/dist/workspaces.js +352 -0
- package/dist/workspaces.js.map +1 -0
- package/package.json +96 -0
package/dist/jobs.js
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async job bundle support.
|
|
3
|
+
*
|
|
4
|
+
* `dispatch` returns quickly after creating a reproducible bundle on disk,
|
|
5
|
+
* then the run executes in a DETACHED job-runner process (job-runner.ts) —
|
|
6
|
+
* not inside the MCP server — so a server restart, session reconnect, or
|
|
7
|
+
* client timeout never kills an in-flight run. The server (and any later
|
|
8
|
+
* server instance) reads progress and results back from the job directory.
|
|
9
|
+
* Set HARNESS_DISPATCH_INPROC_JOBS=1 to run jobs in-process instead (used
|
|
10
|
+
* by the unit-test suite, which injects fake dispatchers a separate
|
|
11
|
+
* process could not see).
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { closeSync, existsSync, openSync } from "node:fs";
|
|
16
|
+
import { appendFile, chmod, copyFile, mkdir, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { resolveWorkingDir, workingDirWarning } from "./working-dir.js";
|
|
22
|
+
/**
|
|
23
|
+
* Dispatcher error strings are unbounded (a corrupted downstream config once
|
|
24
|
+
* produced a 173KB parse error). Full text always lands in stderr.log; the
|
|
25
|
+
* JSON surfaces returned over MCP carry a bounded copy.
|
|
26
|
+
*/
|
|
27
|
+
const MAX_JSON_ERROR_CHARS = 4000;
|
|
28
|
+
/** Suggested delay before an agent checks `job_status` again. */
|
|
29
|
+
const SUGGESTED_POLL_SECONDS = 300;
|
|
30
|
+
/** How often a live background run bumps its status file's updatedAt. */
|
|
31
|
+
const HEARTBEAT_INTERVAL_MS = 15_000;
|
|
32
|
+
/**
|
|
33
|
+
* A "running" status whose updatedAt is older than this is a lie — the
|
|
34
|
+
* process that owned the run is gone (several missed heartbeats), so
|
|
35
|
+
* readers report the job as orphaned instead of keeping callers polling a
|
|
36
|
+
* corpse forever. Generous multiple of the heartbeat so an event-loop
|
|
37
|
+
* stall can't produce false orphans.
|
|
38
|
+
*/
|
|
39
|
+
const ORPHAN_THRESHOLD_MS = 90_000;
|
|
40
|
+
/**
|
|
41
|
+
* Compute-on-read orphan detection. Never writes the verdict back — the
|
|
42
|
+
* status file stays whatever the (dead) owner last wrote, so a future
|
|
43
|
+
* attach/recovery feature keeps its evidence intact.
|
|
44
|
+
*/
|
|
45
|
+
function withOrphanCheck(status) {
|
|
46
|
+
if (status.status !== "running" && status.status !== "queued")
|
|
47
|
+
return status;
|
|
48
|
+
const beat = Date.parse(status.updatedAt);
|
|
49
|
+
if (Number.isFinite(beat) && Date.now() - beat <= ORPHAN_THRESHOLD_MS)
|
|
50
|
+
return status;
|
|
51
|
+
return {
|
|
52
|
+
...status,
|
|
53
|
+
status: "orphaned",
|
|
54
|
+
success: false,
|
|
55
|
+
error: "The dispatch server that started this job exited before the run finished — " +
|
|
56
|
+
"the background run died with it. Re-dispatch the task; this job will never complete.",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fallback dispatch timeout for jobs. Dispatchers hard-code a short default
|
|
61
|
+
* (10 min for CLI harnesses, 2 min for openai_compatible) meant to catch a
|
|
62
|
+
* genuinely hung process — waiting on stdin that'll never come, a stalled
|
|
63
|
+
* network call — not to cap a slow-but-healthy run. That default made sense
|
|
64
|
+
* as-is for `code`, which blocks an MCP call anyway, but `job` runs in the
|
|
65
|
+
* background and is polled, so nothing about it requires killing a process
|
|
66
|
+
* that's still making progress after 10 minutes. Below both an explicit
|
|
67
|
+
* `hints.timeoutMs` and the route's own configured `timeoutMs` in
|
|
68
|
+
* precedence, so this only fills the gap when nobody set either.
|
|
69
|
+
*
|
|
70
|
+
* Router.stream() treats this specific value as a budget for the WHOLE call
|
|
71
|
+
* (including router fallback retries), not a per-attempt allowance — without
|
|
72
|
+
* that, a job that falls back twice (the router's default maxFallbacks: 2)
|
|
73
|
+
* could burn up to 3x this value before failing conclusively.
|
|
74
|
+
*/
|
|
75
|
+
const JOB_DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
76
|
+
function boundedError(error) {
|
|
77
|
+
if (error === undefined)
|
|
78
|
+
return undefined;
|
|
79
|
+
if (error.length <= MAX_JSON_ERROR_CHARS)
|
|
80
|
+
return error;
|
|
81
|
+
return (error.slice(0, MAX_JSON_ERROR_CHARS) +
|
|
82
|
+
` … [truncated ${error.length - MAX_JSON_ERROR_CHARS} chars — full text in output/stderr.log]`);
|
|
83
|
+
}
|
|
84
|
+
function pollInstructions(jobId) {
|
|
85
|
+
return (`Job runs in the background; CLI harnesses typically take 3-15 minutes. ` +
|
|
86
|
+
`Wait ~${Math.round(SUGGESTED_POLL_SECONDS / 60)} minutes (e.g. sleep), then call ` +
|
|
87
|
+
`job_status with jobId=${jobId}. While status is "running", partialOutput shows ` +
|
|
88
|
+
`progress; check again until status is "completed" or "failed". Results persist ` +
|
|
89
|
+
`on disk, so checking late loses nothing.`);
|
|
90
|
+
}
|
|
91
|
+
function jobsRoot() {
|
|
92
|
+
return (process.env.HARNESS_DISPATCH_JOBS_DIR ??
|
|
93
|
+
path.join(homedir(), ".harness-dispatch", "jobs"));
|
|
94
|
+
}
|
|
95
|
+
const DEFAULT_JOB_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
96
|
+
let configuredJobMaxAgeMs;
|
|
97
|
+
/**
|
|
98
|
+
* Config-driven retention (`retention: { jobs_days: N }` in config.yaml) —
|
|
99
|
+
* set at runtime bootstrap and on every hot reload. Precedence:
|
|
100
|
+
* HARNESS_DISPATCH_JOB_MAX_AGE_MS env > config > 7-day default.
|
|
101
|
+
*/
|
|
102
|
+
export function setJobRetentionDays(days) {
|
|
103
|
+
configuredJobMaxAgeMs =
|
|
104
|
+
days !== undefined && Number.isFinite(days) && days >= 0
|
|
105
|
+
? days * 24 * 60 * 60 * 1000
|
|
106
|
+
: undefined;
|
|
107
|
+
}
|
|
108
|
+
function jobMaxAgeMs() {
|
|
109
|
+
const raw = process.env.HARNESS_DISPATCH_JOB_MAX_AGE_MS;
|
|
110
|
+
const parsed = raw ? Number(raw) : NaN;
|
|
111
|
+
if (Number.isFinite(parsed) && parsed >= 0)
|
|
112
|
+
return parsed;
|
|
113
|
+
return configuredJobMaxAgeMs ?? DEFAULT_JOB_MAX_AGE_MS;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Nothing ever pruned old job directories — status.json/result.json/output
|
|
117
|
+
* logs and every snapshotted context file accumulated under jobsRoot()
|
|
118
|
+
* forever. Prune anything with no activity for the retention window
|
|
119
|
+
* (default 7 days, override via HARNESS_DISPATCH_JOB_MAX_AGE_MS) each time a
|
|
120
|
+
* new job is about to start. Job directory mtime is a reasonable proxy for
|
|
121
|
+
* "last activity": writeJson's tmp-then-rename touches the job dir on every
|
|
122
|
+
* status update, so a running (or freshly completed but unpolled) job keeps
|
|
123
|
+
* bumping it — only genuinely abandoned jobs go stale. Best effort: a prune
|
|
124
|
+
* failure must never block starting the job that was actually requested.
|
|
125
|
+
*/
|
|
126
|
+
async function pruneStaleJobs() {
|
|
127
|
+
const root = jobsRoot();
|
|
128
|
+
let entries;
|
|
129
|
+
try {
|
|
130
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const maxAgeMs = jobMaxAgeMs();
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (!entry.isDirectory())
|
|
139
|
+
continue;
|
|
140
|
+
const jobDir = path.join(root, entry.name);
|
|
141
|
+
try {
|
|
142
|
+
const info = await stat(jobDir);
|
|
143
|
+
if (now - info.mtimeMs > maxAgeMs) {
|
|
144
|
+
await rm(jobDir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// best effort — a locked/already-gone/permission-denied entry is skipped
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function timestamp() {
|
|
153
|
+
return new Date().toISOString();
|
|
154
|
+
}
|
|
155
|
+
function safeBaseName(filePath) {
|
|
156
|
+
return path.basename(filePath).replace(/[^A-Za-z0-9_.-]/g, "_");
|
|
157
|
+
}
|
|
158
|
+
async function writeJson(filePath, value) {
|
|
159
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${randomUUID().slice(0, 8)}.tmp`;
|
|
160
|
+
await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
161
|
+
await renameWithRetry(tmpPath, filePath);
|
|
162
|
+
}
|
|
163
|
+
async function renameWithRetry(tmpPath, filePath) {
|
|
164
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
165
|
+
try {
|
|
166
|
+
await rename(tmpPath, filePath);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
const code = typeof err === "object" && err !== null ? err.code : undefined;
|
|
171
|
+
if (code !== "EPERM" && code !== "EACCES" && code !== "EBUSY")
|
|
172
|
+
throw err;
|
|
173
|
+
await delay(25 * (attempt + 1));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await rename(tmpPath, filePath);
|
|
177
|
+
}
|
|
178
|
+
async function readJson(filePath) {
|
|
179
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
180
|
+
}
|
|
181
|
+
async function updateStatus(jobDir, status) {
|
|
182
|
+
await writeJson(path.join(jobDir, "status.json"), {
|
|
183
|
+
...status,
|
|
184
|
+
updatedAt: timestamp(),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
async function snapshotFiles(jobDir, files) {
|
|
188
|
+
const out = [];
|
|
189
|
+
const filesDir = path.join(jobDir, "context", "files");
|
|
190
|
+
await mkdir(filesDir, { recursive: true, mode: 0o700 });
|
|
191
|
+
for (const [index, originalPath] of files.entries()) {
|
|
192
|
+
const item = { originalPath };
|
|
193
|
+
try {
|
|
194
|
+
const fileStat = await stat(originalPath);
|
|
195
|
+
if (!fileStat.isFile()) {
|
|
196
|
+
item.error = "not a regular file";
|
|
197
|
+
out.push(item);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const snapshotName = `${String(index + 1).padStart(3, "0")}-${safeBaseName(originalPath)}`;
|
|
201
|
+
const snapshotPath = path.join(filesDir, snapshotName);
|
|
202
|
+
await copyFile(originalPath, snapshotPath);
|
|
203
|
+
await chmod(snapshotPath, 0o600);
|
|
204
|
+
item.snapshotPath = snapshotPath;
|
|
205
|
+
item.sizeBytes = fileStat.size;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
item.error = err instanceof Error ? err.message : String(err);
|
|
209
|
+
}
|
|
210
|
+
out.push(item);
|
|
211
|
+
}
|
|
212
|
+
await writeJson(path.join(jobDir, "context", "files.json"), out);
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
async function runJob(deps, jobDir, manifest, input) {
|
|
216
|
+
const started = Date.now();
|
|
217
|
+
const runningStatus = () => ({
|
|
218
|
+
jobId: manifest.jobId,
|
|
219
|
+
status: "running",
|
|
220
|
+
createdAt: manifest.createdAt,
|
|
221
|
+
updatedAt: timestamp(),
|
|
222
|
+
jobDir,
|
|
223
|
+
...(input.service !== undefined ? { service: input.service } : {}),
|
|
224
|
+
...(manifest.warning !== undefined ? { warning: manifest.warning } : {}),
|
|
225
|
+
});
|
|
226
|
+
await updateStatus(jobDir, runningStatus());
|
|
227
|
+
// Heartbeat: bump updatedAt while the run is alive so a reader can tell
|
|
228
|
+
// "running" apart from "the server that owned this run died and left a
|
|
229
|
+
// stale status file" (getAsyncJob reports the latter as "orphaned").
|
|
230
|
+
// unref'd so an exiting process never lingers on it — which is exactly
|
|
231
|
+
// the scenario the heartbeat exists to expose. The `finished` flag stops
|
|
232
|
+
// a beat that fires between the terminal status write and clearInterval
|
|
233
|
+
// from resurrecting "running".
|
|
234
|
+
let finished = false;
|
|
235
|
+
const heartbeat = setInterval(() => {
|
|
236
|
+
if (finished)
|
|
237
|
+
return;
|
|
238
|
+
void updateStatus(jobDir, runningStatus()).catch(() => undefined);
|
|
239
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
240
|
+
heartbeat.unref?.();
|
|
241
|
+
try {
|
|
242
|
+
const state = deps.holder.state;
|
|
243
|
+
const files = input.files ?? [];
|
|
244
|
+
// Reuse the value already resolved (and recorded) at job creation, not a
|
|
245
|
+
// fresh process.cwd() snapshot — the two must stay in sync with the
|
|
246
|
+
// warning captured in manifest.warning.
|
|
247
|
+
const workingDir = manifest.workingDir;
|
|
248
|
+
const hints = { ...(input.hints ?? {}) };
|
|
249
|
+
if (input.workspacePolicy !== undefined)
|
|
250
|
+
hints.workspacePolicy = input.workspacePolicy;
|
|
251
|
+
// Stream the dispatch so agents polling action=get can watch progress in
|
|
252
|
+
// stdout.partial.log instead of waiting blind for the final result.
|
|
253
|
+
const partialPath = path.join(jobDir, "output", "stdout.partial.log");
|
|
254
|
+
const events = input.service
|
|
255
|
+
? state.router.streamTo(input.service, input.prompt, files, workingDir, {
|
|
256
|
+
...(hints.safetyProfile !== undefined
|
|
257
|
+
? { safetyProfile: hints.safetyProfile }
|
|
258
|
+
: {}),
|
|
259
|
+
...(hints.workspacePolicy !== undefined
|
|
260
|
+
? { workspacePolicy: hints.workspacePolicy }
|
|
261
|
+
: {}),
|
|
262
|
+
...(hints.routePolicy !== undefined
|
|
263
|
+
? { routePolicy: hints.routePolicy }
|
|
264
|
+
: {}),
|
|
265
|
+
...(hints.model !== undefined ? { model: hints.model } : {}),
|
|
266
|
+
...(hints.taskType !== undefined ? { taskType: hints.taskType } : {}),
|
|
267
|
+
...(hints.timeoutMs !== undefined ? { timeoutMs: hints.timeoutMs } : {}),
|
|
268
|
+
defaultTimeoutMs: JOB_DEFAULT_TIMEOUT_MS,
|
|
269
|
+
})
|
|
270
|
+
: state.router.stream(input.prompt, files, workingDir, {
|
|
271
|
+
hints,
|
|
272
|
+
maxFallbacks: 2,
|
|
273
|
+
defaultTimeoutMs: JOB_DEFAULT_TIMEOUT_MS,
|
|
274
|
+
});
|
|
275
|
+
let finalResult = null;
|
|
276
|
+
let finalDecision = null;
|
|
277
|
+
for await (const { event, decision } of events) {
|
|
278
|
+
if (decision)
|
|
279
|
+
finalDecision = decision;
|
|
280
|
+
if (input.onEvent) {
|
|
281
|
+
try {
|
|
282
|
+
input.onEvent(event);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Progress forwarding is best-effort; the job itself must not fail.
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (event.type === "stdout" || event.type === "stderr") {
|
|
289
|
+
try {
|
|
290
|
+
await appendFile(partialPath, event.chunk, { encoding: "utf8", mode: 0o600 });
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Progress mirroring is best-effort; the final result still lands.
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (event.type === "completion") {
|
|
297
|
+
// Fallback chains yield one completion per attempt; last one wins.
|
|
298
|
+
finalResult = event.result;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const result = finalResult ?? {
|
|
302
|
+
output: "",
|
|
303
|
+
service: input.service ?? "none",
|
|
304
|
+
success: false,
|
|
305
|
+
error: "Router stream ended without a completion event",
|
|
306
|
+
};
|
|
307
|
+
finished = true;
|
|
308
|
+
const payload = {
|
|
309
|
+
jobId: manifest.jobId,
|
|
310
|
+
result: { ...result, ...(result.error !== undefined ? { error: boundedError(result.error) } : {}) },
|
|
311
|
+
decision: finalDecision,
|
|
312
|
+
};
|
|
313
|
+
await writeFile(path.join(jobDir, "output", "stdout.log"), result.output, { encoding: "utf8", mode: 0o600 });
|
|
314
|
+
await writeFile(path.join(jobDir, "output", "stderr.log"), result.error ?? "", { encoding: "utf8", mode: 0o600 });
|
|
315
|
+
await writeJson(path.join(jobDir, "output", "result.json"), payload);
|
|
316
|
+
await writeFile(path.join(jobDir, "output", "result.md"), result.output || result.error || "", { encoding: "utf8", mode: 0o600 });
|
|
317
|
+
await updateStatus(jobDir, {
|
|
318
|
+
jobId: manifest.jobId,
|
|
319
|
+
status: result.success ? "completed" : "failed",
|
|
320
|
+
createdAt: manifest.createdAt,
|
|
321
|
+
updatedAt: timestamp(),
|
|
322
|
+
jobDir,
|
|
323
|
+
...(input.service !== undefined ? { service: input.service } : {}),
|
|
324
|
+
route: result.service,
|
|
325
|
+
success: result.success,
|
|
326
|
+
...(result.error !== undefined ? { error: boundedError(result.error) } : {}),
|
|
327
|
+
...(manifest.warning !== undefined ? { warning: manifest.warning } : {}),
|
|
328
|
+
durationMs: Date.now() - started,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
finished = true;
|
|
333
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
334
|
+
await writeFile(path.join(jobDir, "output", "stderr.log"), message, { encoding: "utf8", mode: 0o600 });
|
|
335
|
+
await updateStatus(jobDir, {
|
|
336
|
+
jobId: manifest.jobId,
|
|
337
|
+
status: "failed",
|
|
338
|
+
createdAt: manifest.createdAt,
|
|
339
|
+
updatedAt: timestamp(),
|
|
340
|
+
jobDir,
|
|
341
|
+
...(input.service !== undefined ? { service: input.service } : {}),
|
|
342
|
+
success: false,
|
|
343
|
+
error: boundedError(message),
|
|
344
|
+
...(manifest.warning !== undefined ? { warning: manifest.warning } : {}),
|
|
345
|
+
durationMs: Date.now() - started,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
clearInterval(heartbeat);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Rebuild a job's input from its on-disk bundle and execute it. This is the
|
|
354
|
+
* detached runner's whole job; the manifest deliberately carries everything
|
|
355
|
+
* a run needs (prompt path, resolved workingDir, hints, service) precisely
|
|
356
|
+
* so execution can happen in a process that wasn't there when the job was
|
|
357
|
+
* created.
|
|
358
|
+
*/
|
|
359
|
+
export async function executeJobDir(deps, jobDir) {
|
|
360
|
+
const manifest = await readJson(path.join(jobDir, "manifest.json"));
|
|
361
|
+
const prompt = await readFile(manifest.promptPath, "utf8");
|
|
362
|
+
const input = {
|
|
363
|
+
prompt,
|
|
364
|
+
files: manifest.files.map((f) => f.originalPath),
|
|
365
|
+
workingDir: manifest.workingDir,
|
|
366
|
+
...(manifest.hints !== undefined ? { hints: manifest.hints } : {}),
|
|
367
|
+
...(manifest.workspacePolicy !== undefined
|
|
368
|
+
? { workspacePolicy: manifest.workspacePolicy }
|
|
369
|
+
: {}),
|
|
370
|
+
...(manifest.service !== undefined ? { service: manifest.service } : {}),
|
|
371
|
+
};
|
|
372
|
+
await runJob(deps, jobDir, manifest, input);
|
|
373
|
+
}
|
|
374
|
+
/** dist/job-runner.js next to this module (compiled), or via the package's dist/ when running from src. */
|
|
375
|
+
function resolveRunnerPath() {
|
|
376
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
377
|
+
const candidates = [
|
|
378
|
+
path.join(here, "job-runner.js"),
|
|
379
|
+
path.join(here, "..", "dist", "job-runner.js"),
|
|
380
|
+
];
|
|
381
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
382
|
+
}
|
|
383
|
+
const TERMINAL_WATCH_INTERVAL_MS = 300;
|
|
384
|
+
/**
|
|
385
|
+
* Watch a detached job's directory until it reaches a terminal state
|
|
386
|
+
* (result.json present, or a failed/orphaned status — the orphan check
|
|
387
|
+
* doubles as the exit path if the runner dies). Timer is unref'd: an
|
|
388
|
+
* exiting server abandons the watch, which is exactly the point of
|
|
389
|
+
* detached execution.
|
|
390
|
+
*/
|
|
391
|
+
async function watchUntilTerminal(jobDir) {
|
|
392
|
+
const deadline = Date.now() + JOB_DEFAULT_TIMEOUT_MS + 10 * 60 * 1000;
|
|
393
|
+
while (Date.now() < deadline) {
|
|
394
|
+
if (existsSync(path.join(jobDir, "output", "result.json")))
|
|
395
|
+
return;
|
|
396
|
+
try {
|
|
397
|
+
const status = withOrphanCheck(await readJson(path.join(jobDir, "status.json")));
|
|
398
|
+
if (status.status === "completed" ||
|
|
399
|
+
status.status === "failed" ||
|
|
400
|
+
status.status === "orphaned") {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
// Transient read during an atomic rename — retry next tick.
|
|
406
|
+
}
|
|
407
|
+
await delay(TERMINAL_WATCH_INTERVAL_MS, undefined, { ref: false });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function spawnDetachedRunner(runnerPath, jobDir, configPath) {
|
|
411
|
+
// The runner's own stdout/stderr go to a log inside the job dir so a
|
|
412
|
+
// bootstrap crash (bad config, missing module) leaves evidence.
|
|
413
|
+
const logFd = openSync(path.join(jobDir, "output", "runner.log"), "a");
|
|
414
|
+
try {
|
|
415
|
+
const child = spawn(process.execPath, [runnerPath, jobDir], {
|
|
416
|
+
detached: true,
|
|
417
|
+
stdio: ["ignore", logFd, logFd],
|
|
418
|
+
windowsHide: true,
|
|
419
|
+
env: {
|
|
420
|
+
...process.env,
|
|
421
|
+
...(configPath !== undefined ? { HARNESS_DISPATCH_CONFIG: configPath } : {}),
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
child.unref();
|
|
425
|
+
}
|
|
426
|
+
finally {
|
|
427
|
+
closeSync(logFd);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
export async function startAsyncJob(deps, input) {
|
|
431
|
+
return (await startAsyncJobTracked(deps, input)).status;
|
|
432
|
+
}
|
|
433
|
+
export async function startAsyncJobTracked(deps, input) {
|
|
434
|
+
await pruneStaleJobs();
|
|
435
|
+
const jobId = `job-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
436
|
+
const root = jobsRoot();
|
|
437
|
+
const jobDir = path.join(root, jobId);
|
|
438
|
+
await mkdir(path.join(jobDir, "context"), { recursive: true, mode: 0o700 });
|
|
439
|
+
await mkdir(path.join(jobDir, "output"), { recursive: true, mode: 0o700 });
|
|
440
|
+
const promptPath = path.join(jobDir, "prompt.md");
|
|
441
|
+
await writeFile(promptPath, input.prompt, { encoding: "utf8", mode: 0o600 });
|
|
442
|
+
const fileSnapshots = await snapshotFiles(jobDir, input.files ?? []);
|
|
443
|
+
const createdAt = timestamp();
|
|
444
|
+
const resolvedWorkingDir = resolveWorkingDir(input.workingDir);
|
|
445
|
+
const warning = workingDirWarning(resolvedWorkingDir);
|
|
446
|
+
const manifest = {
|
|
447
|
+
jobId,
|
|
448
|
+
createdAt,
|
|
449
|
+
workingDir: resolvedWorkingDir.workingDir,
|
|
450
|
+
promptPath,
|
|
451
|
+
files: fileSnapshots,
|
|
452
|
+
...(input.hints !== undefined ? { hints: input.hints } : {}),
|
|
453
|
+
...(input.workspacePolicy !== undefined ? { workspacePolicy: input.workspacePolicy } : {}),
|
|
454
|
+
...(input.service !== undefined ? { service: input.service } : {}),
|
|
455
|
+
...(warning !== undefined ? { warning } : {}),
|
|
456
|
+
};
|
|
457
|
+
await writeJson(path.join(jobDir, "manifest.json"), manifest);
|
|
458
|
+
const status = {
|
|
459
|
+
jobId,
|
|
460
|
+
status: "queued",
|
|
461
|
+
createdAt,
|
|
462
|
+
updatedAt: createdAt,
|
|
463
|
+
jobDir,
|
|
464
|
+
...(input.service !== undefined ? { service: input.service } : {}),
|
|
465
|
+
nextPollSeconds: SUGGESTED_POLL_SECONDS,
|
|
466
|
+
instructions: pollInstructions(jobId),
|
|
467
|
+
...(warning !== undefined ? { warning } : {}),
|
|
468
|
+
};
|
|
469
|
+
await updateStatus(jobDir, status);
|
|
470
|
+
// Detached by default: the run must not die with this process. In-process
|
|
471
|
+
// mode exists for unit tests (injected fake dispatchers aren't visible to
|
|
472
|
+
// a separate process) and as the fallback when the runner script can't be
|
|
473
|
+
// found (running from raw src/ with no dist/ build).
|
|
474
|
+
const inproc = process.env.HARNESS_DISPATCH_INPROC_JOBS === "1";
|
|
475
|
+
const runnerPath = inproc ? undefined : resolveRunnerPath();
|
|
476
|
+
if (inproc || runnerPath === undefined) {
|
|
477
|
+
if (!inproc) {
|
|
478
|
+
console.error("harness-dispatch: dist/job-runner.js not found (unbuilt checkout?) — " +
|
|
479
|
+
"running the job in-process; it will not survive a server restart.");
|
|
480
|
+
}
|
|
481
|
+
const completion = runJob(deps, jobDir, manifest, input);
|
|
482
|
+
return { status, completion };
|
|
483
|
+
}
|
|
484
|
+
spawnDetachedRunner(runnerPath, jobDir, deps.holder.state.configPath);
|
|
485
|
+
return { status, completion: watchUntilTerminal(jobDir) };
|
|
486
|
+
}
|
|
487
|
+
const MAX_PARTIAL_OUTPUT_CHARS = 4000;
|
|
488
|
+
export async function getAsyncJob(jobId) {
|
|
489
|
+
const jobDir = path.join(jobsRoot(), jobId);
|
|
490
|
+
const manifest = await readJson(path.join(jobDir, "manifest.json"));
|
|
491
|
+
const status = withOrphanCheck(await readJson(path.join(jobDir, "status.json")));
|
|
492
|
+
const resultPath = path.join(jobDir, "output", "result.json");
|
|
493
|
+
if (existsSync(resultPath)) {
|
|
494
|
+
return { manifest, status, result: await readJson(resultPath) };
|
|
495
|
+
}
|
|
496
|
+
if (status.status === "orphaned") {
|
|
497
|
+
// Terminal: no poll guidance — polling will never resolve this job.
|
|
498
|
+
return { manifest, status };
|
|
499
|
+
}
|
|
500
|
+
const out = {
|
|
501
|
+
manifest,
|
|
502
|
+
status: {
|
|
503
|
+
...status,
|
|
504
|
+
nextPollSeconds: SUGGESTED_POLL_SECONDS,
|
|
505
|
+
instructions: pollInstructions(jobId),
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
const partialPath = path.join(jobDir, "output", "stdout.partial.log");
|
|
509
|
+
if (existsSync(partialPath)) {
|
|
510
|
+
try {
|
|
511
|
+
const partial = await readFile(partialPath, "utf8");
|
|
512
|
+
out.partialOutput =
|
|
513
|
+
partial.length <= MAX_PARTIAL_OUTPUT_CHARS
|
|
514
|
+
? partial
|
|
515
|
+
: `… [${partial.length - MAX_PARTIAL_OUTPUT_CHARS} chars omitted] …` +
|
|
516
|
+
partial.slice(-MAX_PARTIAL_OUTPUT_CHARS);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
// Best-effort; absence of partial output isn't an error.
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
export async function listAsyncJobs() {
|
|
525
|
+
const root = jobsRoot();
|
|
526
|
+
if (!existsSync(root))
|
|
527
|
+
return [];
|
|
528
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
529
|
+
const statuses = [];
|
|
530
|
+
for (const entry of entries) {
|
|
531
|
+
if (!entry.isDirectory())
|
|
532
|
+
continue;
|
|
533
|
+
try {
|
|
534
|
+
statuses.push(withOrphanCheck(await readJson(path.join(root, entry.name, "status.json"))));
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// Ignore incomplete or manually edited job directories.
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return statuses.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
541
|
+
}
|
|
542
|
+
//# sourceMappingURL=jobs.js.map
|
package/dist/jobs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../src/jobs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaderboard-based quality scoring for harness-dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `coding_agent.leaderboard`. Fetches Arena ELO scores from the
|
|
5
|
+
* public wulong.dev API with a 24-hour cache. Scores are used as routing
|
|
6
|
+
* quality multipliers — higher ELO → higher routing priority within the
|
|
7
|
+
* same tier.
|
|
8
|
+
*
|
|
9
|
+
* API reference: https://blog.wulong.dev/posts/i-built-an-auto-updating-archive-of-every-ai-arena-leaderboard/
|
|
10
|
+
* Endpoint: https://api.wulong.dev/arena-ai-leaderboards/v1/leaderboard?name=code
|
|
11
|
+
*/
|
|
12
|
+
import type { ThinkingLevel } from "./types.js";
|
|
13
|
+
export declare const LEADERBOARD_URL = "https://api.wulong.dev/arena-ai-leaderboards/v1/leaderboard?name=code";
|
|
14
|
+
export declare const CACHE_TTL_MS: number;
|
|
15
|
+
export declare const FETCH_TIMEOUT_MS = 8000;
|
|
16
|
+
export declare const TIER1_ELO_MIN = 1350;
|
|
17
|
+
export declare const TIER2_ELO_MIN = 1200;
|
|
18
|
+
export declare const THINKING_THRESHOLD_BOOST = 25;
|
|
19
|
+
export declare const THINKING_MULTIPLIERS: Record<string, number>;
|
|
20
|
+
export declare const ELO_NORM_MIN = 1000;
|
|
21
|
+
export declare const ELO_NORM_MAX = 1600;
|
|
22
|
+
export declare const QUALITY_MIN = 0.6;
|
|
23
|
+
export declare const QUALITY_MAX = 1;
|
|
24
|
+
export declare const QUALITY_DEFAULT = 0.85;
|
|
25
|
+
export declare function normalizeElo(elo: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Case-insensitive partial-match lookup.
|
|
28
|
+
*
|
|
29
|
+
* Three-tier fallback (identical to Python `_fuzzy_match`):
|
|
30
|
+
* 1. Exact match after lowercasing.
|
|
31
|
+
* 2. Query is a substring of a leaderboard name → prefer the shortest
|
|
32
|
+
* match (most specific entry that still contains the query).
|
|
33
|
+
* 3. All query words appear in the leaderboard name (order-insensitive) →
|
|
34
|
+
* again prefer the shortest match.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fuzzyMatch(query: string, scores: Record<string, number>): number | null;
|
|
37
|
+
export interface QualityScoreResult {
|
|
38
|
+
qualityScore: number;
|
|
39
|
+
elo: number | null;
|
|
40
|
+
}
|
|
41
|
+
export declare class LeaderboardCache {
|
|
42
|
+
private data;
|
|
43
|
+
private fetchedAt;
|
|
44
|
+
private fetchFailed;
|
|
45
|
+
/** In-flight fetch promise — callers await it to serialize access. */
|
|
46
|
+
private inflight;
|
|
47
|
+
private benchmark;
|
|
48
|
+
private benchmarkLoadedFlag;
|
|
49
|
+
private benchmarkPath;
|
|
50
|
+
constructor(benchmarkPath?: string);
|
|
51
|
+
benchmarkLoaded(): boolean;
|
|
52
|
+
cacheAgeMs(): number | null;
|
|
53
|
+
getScores(): Promise<Record<string, number>>;
|
|
54
|
+
getElo(leaderboardModel: string | undefined): Promise<number | null>;
|
|
55
|
+
getQualityScore(leaderboardModel: string | undefined, thinkingLevel: ThinkingLevel | undefined): Promise<QualityScoreResult>;
|
|
56
|
+
autoTier(leaderboardModel: string | undefined, thinkingLevel: ThinkingLevel | undefined, fallbackTier: number): Promise<number>;
|
|
57
|
+
/** Ensure a fresh fetch has happened (or is in-flight); safe to call concurrently. */
|
|
58
|
+
private ensureFetched;
|
|
59
|
+
private doFetch;
|
|
60
|
+
private loadBenchmarkFileSync;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=leaderboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaderboard.d.ts","sourceRoot":"","sources":["../src/leaderboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAMhD,eAAO,MAAM,eAAe,0EAC6C,CAAC;AAE1E,eAAO,MAAM,YAAY,QAAmB,CAAC;AAC7C,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAGrC,eAAO,MAAM,aAAa,OAAO,CAAC;AAClC,eAAO,MAAM,aAAa,OAAO,CAAC;AAGlC,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAG3C,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAIvD,CAAC;AAGF,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,YAAY,OAAO,CAAC;AACjC,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B,eAAO,MAAM,WAAW,IAAM,CAAC;AAC/B,eAAO,MAAM,eAAe,OAAO,CAAC;AAuCpC,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,GAAG,IAAI,CAqCf;AAMD,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,IAAI,CAA8B;IAC1C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,WAAW,CAAS;IAC5B,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,CAAC,EAAE,MAAM;IASlC,eAAe,IAAI,OAAO;IAI1B,UAAU,IAAI,MAAM,GAAG,IAAI;IAOrB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAK5C,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAWpE,eAAe,CACnB,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,aAAa,EAAE,aAAa,GAAG,SAAS,GACvC,OAAO,CAAC,kBAAkB,CAAC;IAuBxB,QAAQ,CACZ,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC;IAmBlB,sFAAsF;YACxE,aAAa;YAmBb,OAAO;IAgDrB,OAAO,CAAC,qBAAqB;CA4B9B"}
|