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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +582 -0
  3. package/config.default.yaml +397 -0
  4. package/data/coding_benchmarks.json +431 -0
  5. package/dist/auth.d.ts +9 -0
  6. package/dist/auth.d.ts.map +1 -0
  7. package/dist/auth.js +73 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/billing.d.ts +7 -0
  10. package/dist/billing.d.ts.map +1 -0
  11. package/dist/billing.js +201 -0
  12. package/dist/billing.js.map +1 -0
  13. package/dist/bin.d.ts +6 -0
  14. package/dist/bin.d.ts.map +1 -0
  15. package/dist/bin.js +488 -0
  16. package/dist/bin.js.map +1 -0
  17. package/dist/breaker-store.d.ts +31 -0
  18. package/dist/breaker-store.d.ts.map +1 -0
  19. package/dist/breaker-store.js +103 -0
  20. package/dist/breaker-store.js.map +1 -0
  21. package/dist/circuit-breaker.d.ts +47 -0
  22. package/dist/circuit-breaker.d.ts.map +1 -0
  23. package/dist/circuit-breaker.js +102 -0
  24. package/dist/circuit-breaker.js.map +1 -0
  25. package/dist/config.d.ts +48 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +1248 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/dashboard/live.d.ts +49 -0
  30. package/dist/dashboard/live.d.ts.map +1 -0
  31. package/dist/dashboard/live.js +149 -0
  32. package/dist/dashboard/live.js.map +1 -0
  33. package/dist/dispatch-log.d.ts +46 -0
  34. package/dist/dispatch-log.d.ts.map +1 -0
  35. package/dist/dispatch-log.js +89 -0
  36. package/dist/dispatch-log.js.map +1 -0
  37. package/dist/dispatchers/base.d.ts +55 -0
  38. package/dist/dispatchers/base.d.ts.map +1 -0
  39. package/dist/dispatchers/base.js +80 -0
  40. package/dist/dispatchers/base.js.map +1 -0
  41. package/dist/dispatchers/generic-cli.d.ts +30 -0
  42. package/dist/dispatchers/generic-cli.d.ts.map +1 -0
  43. package/dist/dispatchers/generic-cli.js +471 -0
  44. package/dist/dispatchers/generic-cli.js.map +1 -0
  45. package/dist/dispatchers/openai-compatible.d.ts +57 -0
  46. package/dist/dispatchers/openai-compatible.d.ts.map +1 -0
  47. package/dist/dispatchers/openai-compatible.js +662 -0
  48. package/dist/dispatchers/openai-compatible.js.map +1 -0
  49. package/dist/dispatchers/shared/kill-tree.d.ts +21 -0
  50. package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -0
  51. package/dist/dispatchers/shared/kill-tree.js +35 -0
  52. package/dist/dispatchers/shared/kill-tree.js.map +1 -0
  53. package/dist/dispatchers/shared/rate-limit-headers.d.ts +22 -0
  54. package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -0
  55. package/dist/dispatchers/shared/rate-limit-headers.js +124 -0
  56. package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -0
  57. package/dist/dispatchers/shared/stream-subprocess.d.ts +53 -0
  58. package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -0
  59. package/dist/dispatchers/shared/stream-subprocess.js +274 -0
  60. package/dist/dispatchers/shared/stream-subprocess.js.map +1 -0
  61. package/dist/dispatchers/shared/subprocess.d.ts +25 -0
  62. package/dist/dispatchers/shared/subprocess.d.ts.map +1 -0
  63. package/dist/dispatchers/shared/subprocess.js +110 -0
  64. package/dist/dispatchers/shared/subprocess.js.map +1 -0
  65. package/dist/dispatchers/shared/which-available.d.ts +2 -0
  66. package/dist/dispatchers/shared/which-available.d.ts.map +1 -0
  67. package/dist/dispatchers/shared/which-available.js +8 -0
  68. package/dist/dispatchers/shared/which-available.js.map +1 -0
  69. package/dist/dispatchers/shared/windows-cmd.d.ts +38 -0
  70. package/dist/dispatchers/shared/windows-cmd.d.ts.map +1 -0
  71. package/dist/dispatchers/shared/windows-cmd.js +91 -0
  72. package/dist/dispatchers/shared/windows-cmd.js.map +1 -0
  73. package/dist/http/server.d.ts +15 -0
  74. package/dist/http/server.d.ts.map +1 -0
  75. package/dist/http/server.js +495 -0
  76. package/dist/http/server.js.map +1 -0
  77. package/dist/index.d.ts +27 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +29 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/job-runner.d.ts +19 -0
  82. package/dist/job-runner.d.ts.map +1 -0
  83. package/dist/job-runner.js +42 -0
  84. package/dist/job-runner.js.map +1 -0
  85. package/dist/jobs.d.ts +116 -0
  86. package/dist/jobs.d.ts.map +1 -0
  87. package/dist/jobs.js +542 -0
  88. package/dist/jobs.js.map +1 -0
  89. package/dist/leaderboard.d.ts +62 -0
  90. package/dist/leaderboard.d.ts.map +1 -0
  91. package/dist/leaderboard.js +287 -0
  92. package/dist/leaderboard.js.map +1 -0
  93. package/dist/mcp/config-hot-reload.d.ts +64 -0
  94. package/dist/mcp/config-hot-reload.d.ts.map +1 -0
  95. package/dist/mcp/config-hot-reload.js +144 -0
  96. package/dist/mcp/config-hot-reload.js.map +1 -0
  97. package/dist/mcp/dispatcher-factory.d.ts +20 -0
  98. package/dist/mcp/dispatcher-factory.d.ts.map +1 -0
  99. package/dist/mcp/dispatcher-factory.js +30 -0
  100. package/dist/mcp/dispatcher-factory.js.map +1 -0
  101. package/dist/mcp/resources.d.ts +8 -0
  102. package/dist/mcp/resources.d.ts.map +1 -0
  103. package/dist/mcp/resources.js +45 -0
  104. package/dist/mcp/resources.js.map +1 -0
  105. package/dist/mcp/server.d.ts +36 -0
  106. package/dist/mcp/server.d.ts.map +1 -0
  107. package/dist/mcp/server.js +80 -0
  108. package/dist/mcp/server.js.map +1 -0
  109. package/dist/mcp/tools.d.ts +171 -0
  110. package/dist/mcp/tools.d.ts.map +1 -0
  111. package/dist/mcp/tools.js +658 -0
  112. package/dist/mcp/tools.js.map +1 -0
  113. package/dist/observability/index.d.ts +52 -0
  114. package/dist/observability/index.d.ts.map +1 -0
  115. package/dist/observability/index.js +111 -0
  116. package/dist/observability/index.js.map +1 -0
  117. package/dist/observability/spans.d.ts +47 -0
  118. package/dist/observability/spans.d.ts.map +1 -0
  119. package/dist/observability/spans.js +72 -0
  120. package/dist/observability/spans.js.map +1 -0
  121. package/dist/quota.d.ts +91 -0
  122. package/dist/quota.d.ts.map +1 -0
  123. package/dist/quota.js +331 -0
  124. package/dist/quota.js.map +1 -0
  125. package/dist/route-policy.d.ts +24 -0
  126. package/dist/route-policy.d.ts.map +1 -0
  127. package/dist/route-policy.js +92 -0
  128. package/dist/route-policy.js.map +1 -0
  129. package/dist/router.d.ts +162 -0
  130. package/dist/router.d.ts.map +1 -0
  131. package/dist/router.js +878 -0
  132. package/dist/router.js.map +1 -0
  133. package/dist/safety.d.ts +7 -0
  134. package/dist/safety.d.ts.map +1 -0
  135. package/dist/safety.js +33 -0
  136. package/dist/safety.js.map +1 -0
  137. package/dist/status.d.ts +100 -0
  138. package/dist/status.d.ts.map +1 -0
  139. package/dist/status.js +228 -0
  140. package/dist/status.js.map +1 -0
  141. package/dist/types.d.ts +402 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +9 -0
  144. package/dist/types.js.map +1 -0
  145. package/dist/version.d.ts +2 -0
  146. package/dist/version.d.ts.map +1 -0
  147. package/dist/version.js +13 -0
  148. package/dist/version.js.map +1 -0
  149. package/dist/working-dir.d.ts +15 -0
  150. package/dist/working-dir.d.ts.map +1 -0
  151. package/dist/working-dir.js +22 -0
  152. package/dist/working-dir.js.map +1 -0
  153. package/dist/workspaces.d.ts +19 -0
  154. package/dist/workspaces.d.ts.map +1 -0
  155. package/dist/workspaces.js +352 -0
  156. package/dist/workspaces.js.map +1 -0
  157. 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
@@ -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"}