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
|
@@ -0,0 +1,287 @@
|
|
|
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 { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Constants — load-bearing, preserved from Python
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
export const LEADERBOARD_URL = "https://api.wulong.dev/arena-ai-leaderboards/v1/leaderboard?name=code";
|
|
19
|
+
export const CACHE_TTL_MS = 24 * 3600 * 1000; // 24 hours
|
|
20
|
+
export const FETCH_TIMEOUT_MS = 8000;
|
|
21
|
+
// Tier auto-derivation thresholds (Arena ELO, calibrated April 2026)
|
|
22
|
+
export const TIER1_ELO_MIN = 1350;
|
|
23
|
+
export const TIER2_ELO_MIN = 1200;
|
|
24
|
+
// High-thinking threshold relaxation
|
|
25
|
+
export const THINKING_THRESHOLD_BOOST = 25;
|
|
26
|
+
// Thinking level score multipliers
|
|
27
|
+
export const THINKING_MULTIPLIERS = {
|
|
28
|
+
high: 1.15,
|
|
29
|
+
medium: 1.07,
|
|
30
|
+
low: 1.0,
|
|
31
|
+
};
|
|
32
|
+
// ELO normalization range → quality_score in [QUALITY_MIN, QUALITY_MAX]
|
|
33
|
+
export const ELO_NORM_MIN = 1000;
|
|
34
|
+
export const ELO_NORM_MAX = 1600;
|
|
35
|
+
export const QUALITY_MIN = 0.6;
|
|
36
|
+
export const QUALITY_MAX = 1.0;
|
|
37
|
+
export const QUALITY_DEFAULT = 0.85;
|
|
38
|
+
// User-Agent required — API returns 403 without it
|
|
39
|
+
const USER_AGENT = "harness-dispatch/0.4 (leaderboard quality scoring)";
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Benchmark file resolution
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Resolve `data/coding_benchmarks.json` relative to this module.
|
|
45
|
+
*
|
|
46
|
+
* Walks up from wherever this module lives (dist/ when built, src/ when
|
|
47
|
+
* running via tsx) to find the package root containing `data/`.
|
|
48
|
+
*/
|
|
49
|
+
function resolveBenchmarkPath() {
|
|
50
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
51
|
+
// Walk up a few levels looking for data/coding_benchmarks.json.
|
|
52
|
+
// Handles src/, dist/, or deeper nesting.
|
|
53
|
+
let dir = here;
|
|
54
|
+
for (let i = 0; i < 6; i += 1) {
|
|
55
|
+
const candidate = path.join(dir, "data", "coding_benchmarks.json");
|
|
56
|
+
if (existsSync(candidate)) {
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
const parent = path.dirname(dir);
|
|
60
|
+
if (parent === dir) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
dir = parent;
|
|
64
|
+
}
|
|
65
|
+
// Fall back to a "reasonable" path — caller just sees an empty benchmark map.
|
|
66
|
+
return path.join(here, "..", "data", "coding_benchmarks.json");
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Helpers (exposed for testing)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
export function normalizeElo(elo) {
|
|
72
|
+
let ratio = (elo - ELO_NORM_MIN) / (ELO_NORM_MAX - ELO_NORM_MIN);
|
|
73
|
+
ratio = Math.max(0, Math.min(1, ratio));
|
|
74
|
+
return QUALITY_MIN + (QUALITY_MAX - QUALITY_MIN) * ratio;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Case-insensitive partial-match lookup.
|
|
78
|
+
*
|
|
79
|
+
* Three-tier fallback (identical to Python `_fuzzy_match`):
|
|
80
|
+
* 1. Exact match after lowercasing.
|
|
81
|
+
* 2. Query is a substring of a leaderboard name → prefer the shortest
|
|
82
|
+
* match (most specific entry that still contains the query).
|
|
83
|
+
* 3. All query words appear in the leaderboard name (order-insensitive) →
|
|
84
|
+
* again prefer the shortest match.
|
|
85
|
+
*/
|
|
86
|
+
export function fuzzyMatch(query, scores) {
|
|
87
|
+
const q = query.toLowerCase().trim();
|
|
88
|
+
if (!q) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// 1. Exact
|
|
92
|
+
if (Object.prototype.hasOwnProperty.call(scores, q)) {
|
|
93
|
+
return scores[q] ?? null;
|
|
94
|
+
}
|
|
95
|
+
// 2. Substring — query appears inside a leaderboard entry; prefer shortest.
|
|
96
|
+
const substrHits = [];
|
|
97
|
+
for (const [k, v] of Object.entries(scores)) {
|
|
98
|
+
if (k.includes(q)) {
|
|
99
|
+
substrHits.push([k, v]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (substrHits.length > 0) {
|
|
103
|
+
substrHits.sort((a, b) => a[0].length - b[0].length);
|
|
104
|
+
return substrHits[0][1];
|
|
105
|
+
}
|
|
106
|
+
// 3. All words appear in entry; prefer shortest.
|
|
107
|
+
const words = q.split(/\s+/).filter(Boolean);
|
|
108
|
+
const wordHits = [];
|
|
109
|
+
for (const [k, v] of Object.entries(scores)) {
|
|
110
|
+
if (words.every((w) => k.includes(w))) {
|
|
111
|
+
wordHits.push([k, v]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (wordHits.length > 0) {
|
|
115
|
+
wordHits.sort((a, b) => a[0].length - b[0].length);
|
|
116
|
+
return wordHits[0][1];
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
export class LeaderboardCache {
|
|
121
|
+
data = {};
|
|
122
|
+
fetchedAt = 0; // epoch ms
|
|
123
|
+
fetchFailed = false;
|
|
124
|
+
/** In-flight fetch promise — callers await it to serialize access. */
|
|
125
|
+
inflight = null;
|
|
126
|
+
benchmark = {};
|
|
127
|
+
benchmarkLoadedFlag = false;
|
|
128
|
+
benchmarkPath;
|
|
129
|
+
constructor(benchmarkPath) {
|
|
130
|
+
this.benchmarkPath = benchmarkPath ?? resolveBenchmarkPath();
|
|
131
|
+
this.loadBenchmarkFileSync();
|
|
132
|
+
}
|
|
133
|
+
// ------------------------------------------------------------------
|
|
134
|
+
// Public surface
|
|
135
|
+
// ------------------------------------------------------------------
|
|
136
|
+
benchmarkLoaded() {
|
|
137
|
+
return this.benchmarkLoadedFlag;
|
|
138
|
+
}
|
|
139
|
+
cacheAgeMs() {
|
|
140
|
+
if (this.fetchedAt === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return Date.now() - this.fetchedAt;
|
|
144
|
+
}
|
|
145
|
+
async getScores() {
|
|
146
|
+
await this.ensureFetched();
|
|
147
|
+
return this.data;
|
|
148
|
+
}
|
|
149
|
+
async getElo(leaderboardModel) {
|
|
150
|
+
if (!leaderboardModel) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const scores = await this.getScores();
|
|
154
|
+
if (Object.keys(scores).length === 0) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return fuzzyMatch(leaderboardModel, scores);
|
|
158
|
+
}
|
|
159
|
+
async getQualityScore(leaderboardModel, thinkingLevel) {
|
|
160
|
+
const mult = (thinkingLevel && THINKING_MULTIPLIERS[thinkingLevel]) ?? 1.0;
|
|
161
|
+
// 1. Blended benchmark file
|
|
162
|
+
if (leaderboardModel && this.benchmarkLoadedFlag) {
|
|
163
|
+
const bs = fuzzyMatch(leaderboardModel, this.benchmark);
|
|
164
|
+
if (bs !== null) {
|
|
165
|
+
const elo = await this.getElo(leaderboardModel);
|
|
166
|
+
return { qualityScore: bs * mult, elo };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// 2. Live Arena ELO
|
|
170
|
+
const elo = leaderboardModel ? await this.getElo(leaderboardModel) : null;
|
|
171
|
+
if (elo !== null) {
|
|
172
|
+
return { qualityScore: normalizeElo(elo) * mult, elo };
|
|
173
|
+
}
|
|
174
|
+
// 3. Default
|
|
175
|
+
return { qualityScore: QUALITY_DEFAULT * mult, elo: null };
|
|
176
|
+
}
|
|
177
|
+
async autoTier(leaderboardModel, thinkingLevel, fallbackTier) {
|
|
178
|
+
const elo = leaderboardModel ? await this.getElo(leaderboardModel) : null;
|
|
179
|
+
if (elo === null) {
|
|
180
|
+
return fallbackTier;
|
|
181
|
+
}
|
|
182
|
+
const boost = thinkingLevel === "high" ? THINKING_THRESHOLD_BOOST : 0;
|
|
183
|
+
if (elo + boost >= TIER1_ELO_MIN) {
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
if (elo + boost >= TIER2_ELO_MIN) {
|
|
187
|
+
return 2;
|
|
188
|
+
}
|
|
189
|
+
return 3;
|
|
190
|
+
}
|
|
191
|
+
// ------------------------------------------------------------------
|
|
192
|
+
// Internal — fetch coordination
|
|
193
|
+
// ------------------------------------------------------------------
|
|
194
|
+
/** Ensure a fresh fetch has happened (or is in-flight); safe to call concurrently. */
|
|
195
|
+
async ensureFetched() {
|
|
196
|
+
const age = Date.now() - this.fetchedAt;
|
|
197
|
+
if (Object.keys(this.data).length > 0 && age < CACHE_TTL_MS) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (this.fetchFailed && age < CACHE_TTL_MS) {
|
|
201
|
+
// Suppress repeated retries within the same TTL window.
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (this.inflight) {
|
|
205
|
+
await this.inflight;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
this.inflight = this.doFetch().finally(() => {
|
|
209
|
+
this.inflight = null;
|
|
210
|
+
});
|
|
211
|
+
await this.inflight;
|
|
212
|
+
}
|
|
213
|
+
async doFetch() {
|
|
214
|
+
try {
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
217
|
+
let response;
|
|
218
|
+
try {
|
|
219
|
+
response = await fetch(LEADERBOARD_URL, {
|
|
220
|
+
headers: { "User-Agent": USER_AGENT },
|
|
221
|
+
signal: controller.signal,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
clearTimeout(timer);
|
|
226
|
+
}
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
this.fetchFailed = true;
|
|
229
|
+
// Update fetchedAt so the failure-suppression window starts now.
|
|
230
|
+
this.fetchedAt = Date.now();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const payload = (await response.json());
|
|
234
|
+
const models = Array.isArray(payload.models) ? payload.models : [];
|
|
235
|
+
const parsed = {};
|
|
236
|
+
for (const m of models) {
|
|
237
|
+
if (typeof m.model === "string" && typeof m.score === "number") {
|
|
238
|
+
parsed[m.model.toLowerCase()] = m.score;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (Object.keys(parsed).length > 0) {
|
|
242
|
+
this.data = parsed;
|
|
243
|
+
this.fetchedAt = Date.now();
|
|
244
|
+
this.fetchFailed = false;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
this.fetchFailed = true;
|
|
248
|
+
this.fetchedAt = Date.now();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
this.fetchFailed = true;
|
|
253
|
+
this.fetchedAt = Date.now();
|
|
254
|
+
// Return stale data rather than crashing routing.
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ------------------------------------------------------------------
|
|
258
|
+
// Benchmark file
|
|
259
|
+
// ------------------------------------------------------------------
|
|
260
|
+
loadBenchmarkFileSync() {
|
|
261
|
+
// Sync load at construction to match Python behaviour (loaded once at
|
|
262
|
+
// startup). Swallow all errors — missing/malformed file just falls back
|
|
263
|
+
// to the Arena API.
|
|
264
|
+
try {
|
|
265
|
+
if (!existsSync(this.benchmarkPath)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Sync IO here is acceptable: constructor runs once at startup.
|
|
269
|
+
const raw = readFileSync(this.benchmarkPath, "utf-8");
|
|
270
|
+
const parsed = JSON.parse(raw);
|
|
271
|
+
const out = {};
|
|
272
|
+
const models = parsed.models ?? {};
|
|
273
|
+
for (const [k, v] of Object.entries(models)) {
|
|
274
|
+
const score = v?.coding_score;
|
|
275
|
+
if (typeof score === "number") {
|
|
276
|
+
out[k.toLowerCase()] = score;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.benchmark = out;
|
|
280
|
+
this.benchmarkLoadedFlag = Object.keys(out).length > 0;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
// Malformed — silently fall back.
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=leaderboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leaderboard.js","sourceRoot":"","sources":["../src/leaderboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAIzC,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,MAAM,CAAC,MAAM,eAAe,GAC1B,uEAAuE,CAAC;AAE1E,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,WAAW;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,qEAAqE;AACrE,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAClC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAElC,qCAAqC;AACrC,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC;AAE3C,mCAAmC;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAA2B;IAC1D,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,IAAI;IACZ,GAAG,EAAE,GAAG;CACT,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AACjC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;AACjC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAC/B,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,mDAAmD;AACnD,MAAM,UAAU,GAAG,oDAAoD,CAAC;AAExE,8EAA8E;AAC9E,4BAA4B;AAC5B,8EAA8E;AAE9E;;;;;GAKG;AACH,SAAS,oBAAoB;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,gEAAgE;IAChE,0CAA0C;IAC1C,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,wBAAwB,CAAC,CAAC;QACnE,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,MAAM;QACR,CAAC;QACD,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IACD,8EAA8E;IAC9E,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,wBAAwB,CAAC,CAAC;AACjE,CAAC;AAED,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,KAAK,GAAG,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,CAAC,CAAC;IACjE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACxC,OAAO,WAAW,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,KAAK,CAAC;AAC3D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,KAAa,EACb,MAA8B;IAE9B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW;IACX,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;QACpD,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC3B,CAAC;IAED,4EAA4E;IAC5E,MAAM,UAAU,GAA4B,EAAE,CAAC;IAC/C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACrD,OAAO,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,iDAAiD;IACjD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACnD,OAAO,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAWD,MAAM,OAAO,gBAAgB;IACnB,IAAI,GAA2B,EAAE,CAAC;IAClC,SAAS,GAAG,CAAC,CAAC,CAAC,WAAW;IAC1B,WAAW,GAAG,KAAK,CAAC;IAC5B,sEAAsE;IAC9D,QAAQ,GAAyB,IAAI,CAAC;IACtC,SAAS,GAA2B,EAAE,CAAC;IACvC,mBAAmB,GAAG,KAAK,CAAC;IAC5B,aAAa,CAAS;IAE9B,YAAY,aAAsB;QAChC,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,oBAAoB,EAAE,CAAC;QAC7D,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC/B,CAAC;IAED,qEAAqE;IACrE,iBAAiB;IACjB,qEAAqE;IAErE,eAAe;QACb,OAAO,IAAI,CAAC,mBAAmB,CAAC;IAClC,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,SAAS,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,gBAAoC;QAC/C,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,UAAU,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,gBAAoC,EACpC,aAAwC;QAExC,MAAM,IAAI,GACR,CAAC,aAAa,IAAI,oBAAoB,CAAC,aAAa,CAAC,CAAC,IAAI,GAAG,CAAC;QAEhE,4BAA4B;QAC5B,IAAI,gBAAgB,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACjD,MAAM,EAAE,GAAG,UAAU,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACxD,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBAChB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;gBAChD,OAAO,EAAE,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC;YAC1C,CAAC;QACH,CAAC;QAED,oBAAoB;QACpB,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1E,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,CAAC;QACzD,CAAC;QAED,aAAa;QACb,OAAO,EAAE,YAAY,EAAE,eAAe,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,gBAAoC,EACpC,aAAwC,EACxC,YAAoB;QAEpB,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC1E,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,KAAK,MAAM,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,IAAI,GAAG,GAAG,KAAK,IAAI,aAAa,EAAE,CAAC;YACjC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,GAAG,GAAG,KAAK,IAAI,aAAa,EAAE,CAAC;YACjC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,qEAAqE;IACrE,gCAAgC;IAChC,qEAAqE;IAErE,sFAAsF;IAC9E,KAAK,CAAC,aAAa;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;QACxC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,GAAG,YAAY,EAAE,CAAC;YAC5D,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,IAAI,GAAG,GAAG,YAAY,EAAE,CAAC;YAC3C,wDAAwD;YACxD,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,CAAC,QAAQ,CAAC;YACpB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC1C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,gBAAgB,CAAC,CAAC;YACrE,IAAI,QAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE;oBACtC,OAAO,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE;oBACrC,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAC;YACL,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,iEAAiE;gBACjE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5B,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAErC,CAAC;YACF,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,MAAM,MAAM,GAA2B,EAAE,CAAC;YAC1C,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvB,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC/D,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC;gBAC1C,CAAC;YACH,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC;gBACnB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC5B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;gBACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,kDAAkD;QACpD,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,iBAAiB;IACjB,qEAAqE;IAE7D,qBAAqB;QAC3B,sEAAsE;QACtE,wEAAwE;QACxE,oBAAoB;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;gBACpC,OAAO;YACT,CAAC;YACD,gEAAgE;YAChE,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;YACtD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAE5B,CAAC;YACF,MAAM,GAAG,GAA2B,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;YACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,MAAM,KAAK,GAAG,CAAC,EAAE,YAAY,CAAC;gBAC9B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBAC9B,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;gBAC/B,CAAC;YACH,CAAC;YACD,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;YACrB,IAAI,CAAC,mBAAmB,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;CAEF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config hot-reload for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Python `server.py:_maybe_reload_config`. Rather than file-system
|
|
5
|
+
* watchers (which are flaky on Windows + WSL mounts), we poll the config
|
|
6
|
+
* file's mtime between tool calls. A reload rebuilds the dispatcher map,
|
|
7
|
+
* quota cache, and router — while preserving circuit-breaker state for any
|
|
8
|
+
* service that still exists in the new config.
|
|
9
|
+
*
|
|
10
|
+
* Concurrency: the reload is guarded by a mutex so that simultaneous
|
|
11
|
+
* tool-calls can't race on global state replacement. Only the first caller
|
|
12
|
+
* performs the reload; the rest wait behind the mutex and pick up the
|
|
13
|
+
* already-refreshed state.
|
|
14
|
+
*/
|
|
15
|
+
import { LeaderboardCache } from "../leaderboard.js";
|
|
16
|
+
import { QuotaCache } from "../quota.js";
|
|
17
|
+
import { Router } from "../router.js";
|
|
18
|
+
import type { RouterConfig } from "../types.js";
|
|
19
|
+
import { type DispatcherMap } from "./dispatcher-factory.js";
|
|
20
|
+
export interface RuntimeState {
|
|
21
|
+
config: RouterConfig;
|
|
22
|
+
dispatchers: DispatcherMap;
|
|
23
|
+
quota: QuotaCache;
|
|
24
|
+
router: Router;
|
|
25
|
+
leaderboard: LeaderboardCache;
|
|
26
|
+
mtimeMs: number;
|
|
27
|
+
/**
|
|
28
|
+
* Where this runtime's config came from (undefined = auto-detect). Jobs
|
|
29
|
+
* pass it to their detached runner process so a run bootstraps against
|
|
30
|
+
* the SAME config file as the server that started it, not whatever the
|
|
31
|
+
* runner's cwd happens to contain.
|
|
32
|
+
*/
|
|
33
|
+
configPath?: string;
|
|
34
|
+
}
|
|
35
|
+
/** A mutable holder you pass to tool handlers so hot-reloads are picked up. */
|
|
36
|
+
export declare class RuntimeHolder {
|
|
37
|
+
state: RuntimeState;
|
|
38
|
+
constructor(state: RuntimeState);
|
|
39
|
+
replace(next: RuntimeState): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Bootstrap the full runtime from a config path. Used on server start and
|
|
43
|
+
* again (internally) when a reload is triggered.
|
|
44
|
+
*/
|
|
45
|
+
export declare function bootstrapRuntime(opts: {
|
|
46
|
+
configPath?: string;
|
|
47
|
+
leaderboard?: LeaderboardCache;
|
|
48
|
+
}): Promise<RuntimeState>;
|
|
49
|
+
/**
|
|
50
|
+
* Reload helper — pairs with a RuntimeHolder for in-place swap.
|
|
51
|
+
*
|
|
52
|
+
* If the config file's mtime has not moved since we last reloaded, the call
|
|
53
|
+
* is a cheap no-op. Circuit-breaker state from the previous router is
|
|
54
|
+
* preserved for every service that still exists in the new config.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ConfigHotReloader {
|
|
57
|
+
private readonly holder;
|
|
58
|
+
private readonly configPath?;
|
|
59
|
+
private readonly mutex;
|
|
60
|
+
constructor(holder: RuntimeHolder, configPath?: string | undefined);
|
|
61
|
+
/** Returns true iff a reload actually happened. Swallows all errors. */
|
|
62
|
+
maybeReload(): Promise<boolean>;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=config-hot-reload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-hot-reload.d.ts","sourceRoot":"","sources":["../../src/mcp/config-hot-reload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,aAAa,CAAC;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+EAA+E;AAC/E,qBAAa,aAAa;IACxB,KAAK,EAAE,YAAY,CAAC;gBACR,KAAK,EAAE,YAAY;IAG/B,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;CAGlC;AAYD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC,GAAG,OAAO,CAAC,YAAY,CAAC,CAmBxB;AAkBD;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAJ9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAGlB,MAAM,EAAE,aAAa,EACrB,UAAU,CAAC,EAAE,MAAM,YAAA;IAGtC,wEAAwE;IAClE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAwCtC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config hot-reload for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Ported from Python `server.py:_maybe_reload_config`. Rather than file-system
|
|
5
|
+
* watchers (which are flaky on Windows + WSL mounts), we poll the config
|
|
6
|
+
* file's mtime between tool calls. A reload rebuilds the dispatcher map,
|
|
7
|
+
* quota cache, and router — while preserving circuit-breaker state for any
|
|
8
|
+
* service that still exists in the new config.
|
|
9
|
+
*
|
|
10
|
+
* Concurrency: the reload is guarded by a mutex so that simultaneous
|
|
11
|
+
* tool-calls can't race on global state replacement. Only the first caller
|
|
12
|
+
* performs the reload; the rest wait behind the mutex and pick up the
|
|
13
|
+
* already-refreshed state.
|
|
14
|
+
*/
|
|
15
|
+
import { promises as fs } from "node:fs";
|
|
16
|
+
import { loadConfig } from "../config.js";
|
|
17
|
+
import { setJobRetentionDays } from "../jobs.js";
|
|
18
|
+
import { LeaderboardCache } from "../leaderboard.js";
|
|
19
|
+
import { QuotaCache } from "../quota.js";
|
|
20
|
+
import { Router } from "../router.js";
|
|
21
|
+
import { buildDispatchers } from "./dispatcher-factory.js";
|
|
22
|
+
/** A mutable holder you pass to tool handlers so hot-reloads are picked up. */
|
|
23
|
+
export class RuntimeHolder {
|
|
24
|
+
state;
|
|
25
|
+
constructor(state) {
|
|
26
|
+
this.state = state;
|
|
27
|
+
}
|
|
28
|
+
replace(next) {
|
|
29
|
+
this.state = next;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function statMtime(path) {
|
|
33
|
+
if (!path)
|
|
34
|
+
return 0;
|
|
35
|
+
try {
|
|
36
|
+
const stat = await fs.stat(path);
|
|
37
|
+
return stat.mtimeMs;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Bootstrap the full runtime from a config path. Used on server start and
|
|
45
|
+
* again (internally) when a reload is triggered.
|
|
46
|
+
*/
|
|
47
|
+
export async function bootstrapRuntime(opts) {
|
|
48
|
+
const config = await loadConfig(opts.configPath);
|
|
49
|
+
// Runs at boot AND on every hot reload, so config-driven retention tracks
|
|
50
|
+
// the live config.
|
|
51
|
+
setJobRetentionDays(config.retention?.jobsDays);
|
|
52
|
+
const dispatchers = await buildDispatchers(config);
|
|
53
|
+
const quota = new QuotaCache(dispatchers);
|
|
54
|
+
const leaderboard = opts.leaderboard ?? new LeaderboardCache();
|
|
55
|
+
const router = new Router(config, quota, dispatchers, leaderboard);
|
|
56
|
+
const mtimeMs = await statMtime(opts.configPath);
|
|
57
|
+
return {
|
|
58
|
+
config,
|
|
59
|
+
dispatchers,
|
|
60
|
+
quota,
|
|
61
|
+
router,
|
|
62
|
+
leaderboard,
|
|
63
|
+
mtimeMs,
|
|
64
|
+
...(opts.configPath !== undefined ? { configPath: opts.configPath } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Gate that serialises concurrent reload attempts. */
|
|
68
|
+
class Mutex {
|
|
69
|
+
tail = Promise.resolve();
|
|
70
|
+
async run(task) {
|
|
71
|
+
const prev = this.tail;
|
|
72
|
+
let release;
|
|
73
|
+
this.tail = new Promise((r) => (release = r));
|
|
74
|
+
try {
|
|
75
|
+
await prev;
|
|
76
|
+
return await task();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
release();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reload helper — pairs with a RuntimeHolder for in-place swap.
|
|
85
|
+
*
|
|
86
|
+
* If the config file's mtime has not moved since we last reloaded, the call
|
|
87
|
+
* is a cheap no-op. Circuit-breaker state from the previous router is
|
|
88
|
+
* preserved for every service that still exists in the new config.
|
|
89
|
+
*/
|
|
90
|
+
export class ConfigHotReloader {
|
|
91
|
+
holder;
|
|
92
|
+
configPath;
|
|
93
|
+
mutex = new Mutex();
|
|
94
|
+
constructor(holder, configPath) {
|
|
95
|
+
this.holder = holder;
|
|
96
|
+
this.configPath = configPath;
|
|
97
|
+
}
|
|
98
|
+
/** Returns true iff a reload actually happened. Swallows all errors. */
|
|
99
|
+
async maybeReload() {
|
|
100
|
+
if (!this.configPath)
|
|
101
|
+
return false;
|
|
102
|
+
const mtimeMs = await statMtime(this.configPath);
|
|
103
|
+
if (mtimeMs === 0)
|
|
104
|
+
return false;
|
|
105
|
+
if (mtimeMs <= this.holder.state.mtimeMs)
|
|
106
|
+
return false;
|
|
107
|
+
return this.mutex.run(async () => {
|
|
108
|
+
// Re-check after acquiring the lock — another caller may have already
|
|
109
|
+
// reloaded, in which case we bail without redoing the work.
|
|
110
|
+
const current = this.holder.state.mtimeMs;
|
|
111
|
+
if (mtimeMs <= current)
|
|
112
|
+
return false;
|
|
113
|
+
let next;
|
|
114
|
+
try {
|
|
115
|
+
const bootOpts = {
|
|
116
|
+
leaderboard: this.holder.state.leaderboard,
|
|
117
|
+
};
|
|
118
|
+
if (this.configPath !== undefined)
|
|
119
|
+
bootOpts.configPath = this.configPath;
|
|
120
|
+
next = await bootstrapRuntime(bootOpts);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Malformed edits shouldn't crash the server — keep the old state.
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
// Preserve circuit-breaker state for services that still exist.
|
|
127
|
+
const oldRouter = this.holder.state.router;
|
|
128
|
+
const oldBreakerStatus = oldRouter.circuitBreakerStatus();
|
|
129
|
+
for (const [name, status] of Object.entries(oldBreakerStatus)) {
|
|
130
|
+
if (!(name in next.config.services))
|
|
131
|
+
continue;
|
|
132
|
+
const nb = next.router.getBreaker(name);
|
|
133
|
+
if (!nb)
|
|
134
|
+
continue;
|
|
135
|
+
if (status.tripped) {
|
|
136
|
+
nb.trip(status.cooldownRemainingSec ?? undefined);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
this.holder.replace(next);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=config-hot-reload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-hot-reload.js","sourceRoot":"","sources":["../../src/mcp/config-hot-reload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatcher factory — shared between the CLI (`bin.ts`) and the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Exactly two dispatcher classes exist: GenericCliDispatcher for every
|
|
5
|
+
* `type: cli` route (built-in harness or user-added — see the shipped
|
|
6
|
+
* config.default.yaml for how the 4 built-ins get their default behavior)
|
|
7
|
+
* and OpenAICompatibleDispatcher for `type: openai_compatible`. `harness:`
|
|
8
|
+
* no longer selects a dispatcher class — it only picks which CLI_DEFAULTS
|
|
9
|
+
* entry a route inherits metadata/protocol from at config-load time (see
|
|
10
|
+
* config.ts). Dispatch itself never branches on harness name.
|
|
11
|
+
*/
|
|
12
|
+
import type { Dispatcher } from "../dispatchers/base.js";
|
|
13
|
+
import type { RouterConfig, ServiceConfig } from "../types.js";
|
|
14
|
+
/** Map of enabled service name -> dispatcher instance. */
|
|
15
|
+
export type DispatcherMap = Record<string, Dispatcher>;
|
|
16
|
+
/** Build one dispatcher from a service config. */
|
|
17
|
+
export declare function makeDispatcher(svc: ServiceConfig): Dispatcher;
|
|
18
|
+
/** Build a dispatcher map for every enabled service in the config. */
|
|
19
|
+
export declare function buildDispatchers(config: RouterConfig): DispatcherMap;
|
|
20
|
+
//# sourceMappingURL=dispatcher-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher-factory.d.ts","sourceRoot":"","sources":["../../src/mcp/dispatcher-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,0DAA0D;AAC1D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD,kDAAkD;AAClD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAG7D;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,aAAa,CAOpE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatcher factory — shared between the CLI (`bin.ts`) and the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Exactly two dispatcher classes exist: GenericCliDispatcher for every
|
|
5
|
+
* `type: cli` route (built-in harness or user-added — see the shipped
|
|
6
|
+
* config.default.yaml for how the 4 built-ins get their default behavior)
|
|
7
|
+
* and OpenAICompatibleDispatcher for `type: openai_compatible`. `harness:`
|
|
8
|
+
* no longer selects a dispatcher class — it only picks which CLI_DEFAULTS
|
|
9
|
+
* entry a route inherits metadata/protocol from at config-load time (see
|
|
10
|
+
* config.ts). Dispatch itself never branches on harness name.
|
|
11
|
+
*/
|
|
12
|
+
import { GenericCliDispatcher } from "../dispatchers/generic-cli.js";
|
|
13
|
+
import { OpenAICompatibleDispatcher } from "../dispatchers/openai-compatible.js";
|
|
14
|
+
/** Build one dispatcher from a service config. */
|
|
15
|
+
export function makeDispatcher(svc) {
|
|
16
|
+
if (svc.type === "openai_compatible")
|
|
17
|
+
return new OpenAICompatibleDispatcher(svc);
|
|
18
|
+
return new GenericCliDispatcher(svc);
|
|
19
|
+
}
|
|
20
|
+
/** Build a dispatcher map for every enabled service in the config. */
|
|
21
|
+
export function buildDispatchers(config) {
|
|
22
|
+
const out = {};
|
|
23
|
+
for (const [name, svc] of Object.entries(config.services)) {
|
|
24
|
+
if (!svc.enabled)
|
|
25
|
+
continue;
|
|
26
|
+
out[name] = makeDispatcher(svc);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=dispatcher-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher-factory.js","sourceRoot":"","sources":["../../src/mcp/dispatcher-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AAOjF,kDAAkD;AAClD,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB;QAAE,OAAO,IAAI,0BAA0B,CAAC,GAAG,CAAC,CAAC;IACjF,OAAO,IAAI,oBAAoB,CAAC,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,SAAS;QAC3B,GAAG,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { ConfigHotReloader, RuntimeHolder } from "./config-hot-reload.js";
|
|
3
|
+
export interface ResourceDeps {
|
|
4
|
+
holder: RuntimeHolder;
|
|
5
|
+
reloader?: ConfigHotReloader;
|
|
6
|
+
}
|
|
7
|
+
export declare function registerResources(server: McpServer, deps: ResourceDeps): void;
|
|
8
|
+
//# sourceMappingURL=resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE/E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAkBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CA4C7E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { buildStatus, renderStatusText } from "../status.js";
|
|
2
|
+
async function ensureFreshConfig(reloader) {
|
|
3
|
+
if (reloader)
|
|
4
|
+
await reloader.maybeReload();
|
|
5
|
+
}
|
|
6
|
+
async function currentStatus(deps) {
|
|
7
|
+
await ensureFreshConfig(deps.reloader);
|
|
8
|
+
const state = deps.holder.state;
|
|
9
|
+
return buildStatus(state.config, state.dispatchers, state.quota, state.router, state.leaderboard);
|
|
10
|
+
}
|
|
11
|
+
export function registerResources(server, deps) {
|
|
12
|
+
server.registerResource("status", "harness-dispatch://status", {
|
|
13
|
+
title: "Harness Router Status",
|
|
14
|
+
description: "Human-readable route availability, quota, and breaker state.",
|
|
15
|
+
mimeType: "text/plain",
|
|
16
|
+
}, async () => {
|
|
17
|
+
const status = await currentStatus(deps);
|
|
18
|
+
return {
|
|
19
|
+
contents: [
|
|
20
|
+
{
|
|
21
|
+
uri: "harness-dispatch://status",
|
|
22
|
+
mimeType: "text/plain",
|
|
23
|
+
text: renderStatusText(status),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
server.registerResource("status-json", "harness-dispatch://status.json", {
|
|
29
|
+
title: "Harness Router Status JSON",
|
|
30
|
+
description: "Structured route availability, quota, and breaker state.",
|
|
31
|
+
mimeType: "application/json",
|
|
32
|
+
}, async () => {
|
|
33
|
+
const status = await currentStatus(deps);
|
|
34
|
+
return {
|
|
35
|
+
contents: [
|
|
36
|
+
{
|
|
37
|
+
uri: "harness-dispatch://status.json",
|
|
38
|
+
mimeType: "application/json",
|
|
39
|
+
text: JSON.stringify(status, null, 2),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQ7D,KAAK,UAAU,iBAAiB,CAAC,QAAuC;IACtE,IAAI,QAAQ;QAAE,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;AAC7C,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAkB;IAC7C,MAAM,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;IAChC,OAAO,WAAW,CAChB,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,KAAK,EACX,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,WAAW,CAClB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,IAAkB;IACrE,MAAM,CAAC,gBAAgB,CACrB,QAAQ,EACR,2BAA2B,EAC3B;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EAAE,8DAA8D;QAC3E,QAAQ,EAAE,YAAY;KACvB,EACD,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,2BAA2B;oBAChC,QAAQ,EAAE,YAAY;oBACtB,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC;iBAC/B;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,gBAAgB,CACrB,aAAa,EACb,gCAAgC,EAChC;QACE,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,0DAA0D;QACvE,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,IAAI,EAAE;QACT,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,gCAAgC;oBACrC,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;iBACtC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|