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/bin.js
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* harness-dispatch CLI entrypoint.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
import yaml from "js-yaml";
|
|
10
|
+
import { ensureHttpToken, maskToken, readHttpToken, rotateHttpToken, tokenPath } from "./auth.js";
|
|
11
|
+
import { loadConfig } from "./config.js";
|
|
12
|
+
import { LeaderboardCache } from "./leaderboard.js";
|
|
13
|
+
import { buildDispatchers } from "./mcp/dispatcher-factory.js";
|
|
14
|
+
import { startMcpServer } from "./mcp/server.js";
|
|
15
|
+
import { initObservability } from "./observability/index.js";
|
|
16
|
+
import { QuotaCache } from "./quota.js";
|
|
17
|
+
import { Router } from "./router.js";
|
|
18
|
+
import { buildStatus, buildUsage, renderStatusText, renderUsageText } from "./status.js";
|
|
19
|
+
import { startHttpServer } from "./http/server.js";
|
|
20
|
+
import { billingIsBlocked, buildRouteBilling } from "./billing.js";
|
|
21
|
+
import { effectiveSafetyProfile } from "./safety.js";
|
|
22
|
+
async function buildRuntime(configPath) {
|
|
23
|
+
const config = await loadConfig(configPath);
|
|
24
|
+
const dispatchers = await buildDispatchers(config);
|
|
25
|
+
const quota = new QuotaCache(dispatchers);
|
|
26
|
+
const leaderboard = new LeaderboardCache();
|
|
27
|
+
const router = new Router(config, quota, dispatchers, leaderboard);
|
|
28
|
+
return { config, dispatchers, quota, leaderboard, router };
|
|
29
|
+
}
|
|
30
|
+
function printUsage() {
|
|
31
|
+
process.stdout.write([
|
|
32
|
+
"harness-dispatch",
|
|
33
|
+
"",
|
|
34
|
+
"Usage:",
|
|
35
|
+
" harness-dispatch Start stdio MCP.",
|
|
36
|
+
" harness-dispatch configure [--print] Detect and prepare harness config.",
|
|
37
|
+
" harness-dispatch doctor [--json] Check install, config, auth, and routes.",
|
|
38
|
+
" harness-dispatch doctor --live Run one routed probe when billing policy allows it.",
|
|
39
|
+
" harness-dispatch doctor --live --allow-paid Run a live probe through paid/unknown routes.",
|
|
40
|
+
" harness-dispatch status [--json] Show route, quota, and breaker state.",
|
|
41
|
+
" harness-dispatch status --watch Re-render status every --interval ms.",
|
|
42
|
+
" harness-dispatch usage [--json] Show per-route call counts, quota, and billing kind.",
|
|
43
|
+
" harness-dispatch serve [--port 3333] Serve MCP at /mcp and REST at /v1/*.",
|
|
44
|
+
" harness-dispatch auth show Print the HTTP bearer token.",
|
|
45
|
+
" harness-dispatch auth rotate Rotate the HTTP bearer token.",
|
|
46
|
+
"",
|
|
47
|
+
"Options:",
|
|
48
|
+
" --config <path> Path to config.yaml.",
|
|
49
|
+
" --port <number> HTTP port for serve (default: random free port).",
|
|
50
|
+
" --host <host> HTTP host for serve (default: 127.0.0.1).",
|
|
51
|
+
" --interval <ms> Watch refresh interval (default: 1000).",
|
|
52
|
+
" --json Print JSON where supported.",
|
|
53
|
+
" --print configure: print generated config YAML without writing it.",
|
|
54
|
+
" --yes configure: write config.yaml instead of only previewing it.",
|
|
55
|
+
" --allow-paid Allow doctor --live to probe paid or unknown-paid routes.",
|
|
56
|
+
" -h, --help Show help.",
|
|
57
|
+
"",
|
|
58
|
+
].join("\n"));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Common fields between clis: and endpoints: entries. Billing fields
|
|
62
|
+
* (provider/surface/auth_source/billing_kind/paid_usage_possible/
|
|
63
|
+
* billing_confidence/billing_notes) are deliberately NOT emitted — they're
|
|
64
|
+
* computed from the harness/endpoint defaults every time the config loads
|
|
65
|
+
* (see buildRouteBilling), so writing them out would freeze a snapshot that
|
|
66
|
+
* silently stops tracking future default changes and looks like a deliberate
|
|
67
|
+
* user override when it never was one. `allow_paid_usage` is the one real
|
|
68
|
+
* opt-in flag here, so it's the only billing-adjacent field written.
|
|
69
|
+
*
|
|
70
|
+
* `safety_profile`/`effective_safety` are only emitted when the service
|
|
71
|
+
* actually carries an explicit value — never a fallback default. Baking in
|
|
72
|
+
* `requestedSafetyProfile()`'s "workspace_edit" fallback for every route used
|
|
73
|
+
* to write `safety_profile: workspace_edit` on cursor_cli even though its
|
|
74
|
+
* real effective_safety (the capability floor that actually governs it) is
|
|
75
|
+
* full_auto, making the written file self-contradictory next to `status`.
|
|
76
|
+
*/
|
|
77
|
+
function commonEntryFields(svc) {
|
|
78
|
+
return {
|
|
79
|
+
enabled: svc.enabled ? undefined : false,
|
|
80
|
+
model: svc.model,
|
|
81
|
+
tier: svc.tier,
|
|
82
|
+
weight: svc.weight,
|
|
83
|
+
cli_capability: svc.cliCapability,
|
|
84
|
+
leaderboard_model: svc.leaderboardModel,
|
|
85
|
+
thinking_level: svc.thinkingLevel,
|
|
86
|
+
escalate_model: svc.escalateModel,
|
|
87
|
+
escalate_on: svc.escalateOn.length > 0 ? svc.escalateOn : undefined,
|
|
88
|
+
capabilities: Object.keys(svc.capabilities).length > 0 ? svc.capabilities : undefined,
|
|
89
|
+
max_output_tokens: svc.maxOutputTokens,
|
|
90
|
+
max_input_tokens: svc.maxInputTokens,
|
|
91
|
+
allow_paid_usage: svc.allowPaidUsage ? true : undefined,
|
|
92
|
+
safety_profile: svc.safetyProfile,
|
|
93
|
+
effective_safety: svc.effectiveSafety,
|
|
94
|
+
endpoint_mode: svc.endpointMode,
|
|
95
|
+
endpoint_provider: svc.endpointProvider,
|
|
96
|
+
wire_protocol: svc.wireProtocol,
|
|
97
|
+
workspace_policy: svc.workspacePolicy,
|
|
98
|
+
models: svc.models && svc.models.length > 0 ? svc.models : undefined,
|
|
99
|
+
model_hint: svc.modelHint,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function cliEntryToYaml(svc) {
|
|
103
|
+
return {
|
|
104
|
+
name: svc.name,
|
|
105
|
+
harness: svc.harness,
|
|
106
|
+
command: svc.command,
|
|
107
|
+
api_key: svc.apiKey,
|
|
108
|
+
...commonEntryFields(svc),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function endpointEntryToYaml(svc) {
|
|
112
|
+
return {
|
|
113
|
+
name: svc.name,
|
|
114
|
+
base_url: svc.baseUrl,
|
|
115
|
+
api_key: svc.apiKey,
|
|
116
|
+
...commonEntryFields(svc),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function configToYaml(config) {
|
|
120
|
+
const clis = [];
|
|
121
|
+
const endpoints = [];
|
|
122
|
+
for (const svc of Object.values(config.services)) {
|
|
123
|
+
if (svc.type === "cli")
|
|
124
|
+
clis.push(cliEntryToYaml(svc));
|
|
125
|
+
else
|
|
126
|
+
endpoints.push(endpointEntryToYaml(svc));
|
|
127
|
+
}
|
|
128
|
+
const doc = {};
|
|
129
|
+
if (clis.length > 0)
|
|
130
|
+
doc.clis = clis;
|
|
131
|
+
if (endpoints.length > 0)
|
|
132
|
+
doc.endpoints = endpoints;
|
|
133
|
+
return yaml.dump(doc, { noRefs: true, lineWidth: 100 });
|
|
134
|
+
}
|
|
135
|
+
async function cmdConfigure(configPath, explicitConfigPath, opts) {
|
|
136
|
+
const config = await loadConfig(configPath);
|
|
137
|
+
const yamlText = configToYaml(config);
|
|
138
|
+
const routeCount = Object.keys(config.services).length;
|
|
139
|
+
if (opts.print) {
|
|
140
|
+
process.stdout.write(yamlText);
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
process.stdout.write(`Detected ${routeCount} harness route${routeCount === 1 ? "" : "s"}.\n`);
|
|
144
|
+
for (const [name, svc] of Object.entries(config.services)) {
|
|
145
|
+
process.stdout.write(`- ${name}: harness=${svc.harness ?? name} billing=${buildRouteBilling(svc).kind} safety=${effectiveSafetyProfile(svc)} model=${svc.model ?? svc.leaderboardModel ?? "unknown"}\n`);
|
|
146
|
+
}
|
|
147
|
+
const blocked = Object.entries(config.services)
|
|
148
|
+
.filter(([, svc]) => svc.enabled && billingIsBlocked(buildRouteBilling(svc)))
|
|
149
|
+
.map(([name]) => name);
|
|
150
|
+
if (blocked.length > 0) {
|
|
151
|
+
process.stdout.write(`\nBlocked until you opt in: ${blocked.join(", ")}. These routes can incur paid\n` +
|
|
152
|
+
"usage (metered API, unknown billing, or subscription overage), so they are\n" +
|
|
153
|
+
"skipped until you set allow_paid_usage: true on each route you trust.\n" +
|
|
154
|
+
"Verify with: harness-dispatch doctor --live\n");
|
|
155
|
+
}
|
|
156
|
+
if (config.configWarnings && config.configWarnings.length > 0) {
|
|
157
|
+
process.stdout.write(`\nIgnored config entries (${config.configWarnings.length}) — these had no effect:\n`);
|
|
158
|
+
for (const warning of config.configWarnings) {
|
|
159
|
+
process.stdout.write(`- ${warning}\n`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const target = configPath ?? "config.yaml";
|
|
163
|
+
if (!opts.yes) {
|
|
164
|
+
process.stdout.write(`\nNo files written. Re-run with --yes to write ${target}, or use --print to inspect YAML.\n`);
|
|
165
|
+
process.stdout.write("After writing config, connect agents by adding the harness-dispatch MCP snippet to the agent you use.\n");
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
if (existsSync(target) && explicitConfigPath === undefined) {
|
|
169
|
+
process.stderr.write("configure: config.yaml already exists. Pass --config <path> or --print to avoid overwriting it.\n");
|
|
170
|
+
return 1;
|
|
171
|
+
}
|
|
172
|
+
await fs.writeFile(target, yamlText, "utf-8");
|
|
173
|
+
const absoluteTarget = path.resolve(target);
|
|
174
|
+
process.stdout.write(`Wrote ${target}.\n`);
|
|
175
|
+
process.stdout.write("MCP snippet (uses an absolute --config path so it resolves correctly no matter what\n" +
|
|
176
|
+
"directory the MCP client launches from — a relative path or none at all silently\n" +
|
|
177
|
+
"falls back to the shipped defaults, ignoring every edit you make to this file):\n");
|
|
178
|
+
process.stdout.write(JSON.stringify({
|
|
179
|
+
mcpServers: {
|
|
180
|
+
"harness-dispatch": {
|
|
181
|
+
command: "harness-dispatch",
|
|
182
|
+
args: ["--config", absoluteTarget],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}, null, 2) + "\n");
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
async function cmdStatus(configPath, opts) {
|
|
189
|
+
const runtime = await buildRuntime(configPath);
|
|
190
|
+
const render = async () => {
|
|
191
|
+
const status = await buildStatus(runtime.config, runtime.dispatchers, runtime.quota, runtime.router, runtime.leaderboard);
|
|
192
|
+
return opts.json ? `${JSON.stringify(status, null, 2)}\n` : `${renderStatusText(status)}\n`;
|
|
193
|
+
};
|
|
194
|
+
if (!opts.watch) {
|
|
195
|
+
process.stdout.write(await render());
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
let running = true;
|
|
199
|
+
const stop = () => {
|
|
200
|
+
running = false;
|
|
201
|
+
};
|
|
202
|
+
process.once("SIGINT", stop);
|
|
203
|
+
process.once("SIGTERM", stop);
|
|
204
|
+
try {
|
|
205
|
+
while (running) {
|
|
206
|
+
process.stdout.write(await render());
|
|
207
|
+
await new Promise((resolve) => setTimeout(resolve, opts.intervalMs));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
process.off("SIGINT", stop);
|
|
212
|
+
process.off("SIGTERM", stop);
|
|
213
|
+
}
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
async function cmdUsage(configPath, opts) {
|
|
217
|
+
const runtime = await buildRuntime(configPath);
|
|
218
|
+
const status = await buildStatus(runtime.config, runtime.dispatchers, runtime.quota, runtime.router, runtime.leaderboard);
|
|
219
|
+
const usage = buildUsage(status);
|
|
220
|
+
process.stdout.write(opts.json ? `${JSON.stringify(usage, null, 2)}\n` : `${renderUsageText(usage)}\n`);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
async function cmdDoctor(configPath, opts) {
|
|
224
|
+
const runtime = await buildRuntime(configPath);
|
|
225
|
+
if (opts.allowPaid) {
|
|
226
|
+
for (const svc of Object.values(runtime.config.services)) {
|
|
227
|
+
svc.allowPaidUsage = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const status = await buildStatus(runtime.config, runtime.dispatchers, runtime.quota, runtime.router, runtime.leaderboard);
|
|
231
|
+
const nodeMajor = Number(process.versions.node.split(".")[0] ?? "0");
|
|
232
|
+
const checks = [
|
|
233
|
+
{
|
|
234
|
+
name: "node",
|
|
235
|
+
ok: nodeMajor >= 24,
|
|
236
|
+
detail: `Node ${process.versions.node}`,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: "config",
|
|
240
|
+
ok: Object.keys(runtime.config.services).length > 0,
|
|
241
|
+
detail: `${Object.keys(runtime.config.services).length} configured route(s)`,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: "config-warnings",
|
|
245
|
+
ok: (runtime.config.configWarnings?.length ?? 0) === 0,
|
|
246
|
+
detail: runtime.config.configWarnings && runtime.config.configWarnings.length > 0
|
|
247
|
+
? runtime.config.configWarnings.join(" | ")
|
|
248
|
+
: "no unrecognized config entries",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "routes",
|
|
252
|
+
ok: status.ready.length > 0,
|
|
253
|
+
detail: `${status.ready.length} ready route(s)`,
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "http-auth",
|
|
257
|
+
ok: true,
|
|
258
|
+
detail: (await readHttpToken())
|
|
259
|
+
? `token configured at ${tokenPath()} or HARNESS_DISPATCH_HTTP_TOKEN`
|
|
260
|
+
: "no token yet; run harness-dispatch auth show or serve to create one before using HTTP",
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
const blocked = status.skippedRoutes.filter((skip) => skip.code === "paid_blocked" || skip.code === "unknown_billing");
|
|
264
|
+
checks.push({
|
|
265
|
+
name: "billing-policy",
|
|
266
|
+
ok: true,
|
|
267
|
+
detail: blocked.length === 0
|
|
268
|
+
? "all ready routes satisfy billing policy"
|
|
269
|
+
: `${blocked.length} route(s) intentionally blocked by paid/unknown billing policy`,
|
|
270
|
+
});
|
|
271
|
+
const unsafe = status.routes.filter((route) => route.effectiveSafetyProfile === "full_auto" && route.safetyProfile !== "full_auto");
|
|
272
|
+
checks.push({
|
|
273
|
+
name: "safety-policy",
|
|
274
|
+
ok: true,
|
|
275
|
+
detail: unsafe.length === 0
|
|
276
|
+
? "all ready routes satisfy requested safety profile"
|
|
277
|
+
: `${unsafe.length} route(s) require full_auto safety and are skipped unless requested`,
|
|
278
|
+
});
|
|
279
|
+
let liveProbe;
|
|
280
|
+
if (opts.live) {
|
|
281
|
+
const { result } = await runtime.router.route("Reply with exactly: harness-dispatch live probe ok. Do not inspect or modify files.", [], process.cwd(), { hints: { taskType: "local" }, maxFallbacks: 0 });
|
|
282
|
+
liveProbe = {
|
|
283
|
+
route: result.service,
|
|
284
|
+
success: result.success,
|
|
285
|
+
output: result.output,
|
|
286
|
+
};
|
|
287
|
+
if (result.error !== undefined)
|
|
288
|
+
liveProbe.error = result.error;
|
|
289
|
+
checks.push({
|
|
290
|
+
name: "live-probe",
|
|
291
|
+
ok: result.success,
|
|
292
|
+
detail: result.success
|
|
293
|
+
? `routed through ${result.service}`
|
|
294
|
+
: `${result.service}: ${result.error ?? "probe failed"}`,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
checks.push({
|
|
299
|
+
name: "live-probe",
|
|
300
|
+
ok: true,
|
|
301
|
+
detail: "skipped; pass --live to consume quota and validate dispatch",
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const payload = { ok: checks.every((check) => check.ok), checks, status, liveProbe };
|
|
305
|
+
if (opts.json) {
|
|
306
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
process.stdout.write("harness-dispatch doctor\n\n");
|
|
310
|
+
for (const check of checks) {
|
|
311
|
+
process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.detail}\n`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return payload.ok ? 0 : 1;
|
|
315
|
+
}
|
|
316
|
+
async function cmdServe(configPath, opts) {
|
|
317
|
+
const handle = await startHttpServer({
|
|
318
|
+
...(configPath !== undefined ? { configPath } : {}),
|
|
319
|
+
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
320
|
+
...(opts.host !== undefined ? { host: opts.host } : {}),
|
|
321
|
+
});
|
|
322
|
+
process.stderr.write(`harness-dispatch listening on http://${handle.host}:${handle.port}\n`);
|
|
323
|
+
process.stderr.write(`MCP: http://${handle.host}:${handle.port}/mcp\n`);
|
|
324
|
+
process.stderr.write(`REST: http://${handle.host}:${handle.port}/v1/chat/completions\n`);
|
|
325
|
+
if (handle.token) {
|
|
326
|
+
process.stderr.write(`Auth: Bearer ${maskToken(handle.token)}\n`);
|
|
327
|
+
}
|
|
328
|
+
const shutdown = async () => {
|
|
329
|
+
try {
|
|
330
|
+
await handle.close();
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
process.exit(0);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
process.on("SIGINT", () => void shutdown());
|
|
337
|
+
process.on("SIGTERM", () => void shutdown());
|
|
338
|
+
await new Promise(() => {
|
|
339
|
+
// server lifetime
|
|
340
|
+
});
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
async function cmdAuth(action) {
|
|
344
|
+
switch (action) {
|
|
345
|
+
case "show": {
|
|
346
|
+
const token = await ensureHttpToken();
|
|
347
|
+
process.stdout.write(`${token}\n`);
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
case "rotate": {
|
|
351
|
+
const token = await rotateHttpToken();
|
|
352
|
+
process.stdout.write(`${token}\n`);
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
process.stderr.write("auth: expected show or rotate\n");
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async function cmdRouteAlias(prompt, configPath) {
|
|
361
|
+
if (!prompt) {
|
|
362
|
+
process.stderr.write('route: missing prompt. Usage: route "<prompt>"\n');
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
const runtime = await buildRuntime(configPath);
|
|
366
|
+
const { result, decision } = await runtime.router.route(prompt, [], process.cwd(), {
|
|
367
|
+
hints: { taskType: "execute" },
|
|
368
|
+
maxFallbacks: 2,
|
|
369
|
+
});
|
|
370
|
+
if (decision) {
|
|
371
|
+
process.stderr.write(`route: ${decision.service} (${decision.reason})\n`);
|
|
372
|
+
}
|
|
373
|
+
process.stdout.write(result.output);
|
|
374
|
+
if (!result.output.endsWith("\n"))
|
|
375
|
+
process.stdout.write("\n");
|
|
376
|
+
if (!result.success) {
|
|
377
|
+
process.stderr.write(`${result.error ?? "routing failed"}\n`);
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
function parsePositiveInt(value, fallback) {
|
|
383
|
+
if (typeof value !== "string")
|
|
384
|
+
return fallback;
|
|
385
|
+
const parsed = Number(value);
|
|
386
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
387
|
+
}
|
|
388
|
+
function serveOpts(values) {
|
|
389
|
+
const out = {};
|
|
390
|
+
if (values.port !== undefined)
|
|
391
|
+
out.port = parsePositiveInt(values.port, 0);
|
|
392
|
+
if (typeof values.host === "string")
|
|
393
|
+
out.host = values.host;
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
export async function main(argv) {
|
|
397
|
+
const { values, positionals } = parseArgs({
|
|
398
|
+
args: argv,
|
|
399
|
+
options: {
|
|
400
|
+
config: { type: "string" },
|
|
401
|
+
help: { type: "boolean", short: "h" },
|
|
402
|
+
json: { type: "boolean" },
|
|
403
|
+
live: { type: "boolean" },
|
|
404
|
+
"allow-paid": { type: "boolean" },
|
|
405
|
+
watch: { type: "boolean" },
|
|
406
|
+
interval: { type: "string" },
|
|
407
|
+
port: { type: "string" },
|
|
408
|
+
host: { type: "string" },
|
|
409
|
+
print: { type: "boolean" },
|
|
410
|
+
yes: { type: "boolean" },
|
|
411
|
+
http: { type: "string" },
|
|
412
|
+
},
|
|
413
|
+
allowPositionals: true,
|
|
414
|
+
strict: false,
|
|
415
|
+
});
|
|
416
|
+
if (values.help) {
|
|
417
|
+
printUsage();
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
|
+
await initObservability();
|
|
421
|
+
const [command, ...rest] = positionals;
|
|
422
|
+
const explicitConfigPath = values.config;
|
|
423
|
+
// If the caller didn't pass --config, fall back to ./config.yaml when it
|
|
424
|
+
// exists, rather than silently ignoring it and running pure auto-detect.
|
|
425
|
+
const configPath = explicitConfigPath ?? (existsSync("config.yaml") ? "config.yaml" : undefined);
|
|
426
|
+
if (command === undefined) {
|
|
427
|
+
const handle = await startMcpServer(configPath === undefined ? {} : { configPath });
|
|
428
|
+
const shutdown = async () => {
|
|
429
|
+
try {
|
|
430
|
+
await handle.close();
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
process.exit(0);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
process.on("SIGINT", () => void shutdown());
|
|
437
|
+
process.on("SIGTERM", () => void shutdown());
|
|
438
|
+
await new Promise(() => {
|
|
439
|
+
// stdio MCP lifetime
|
|
440
|
+
});
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
switch (command) {
|
|
444
|
+
case "configure":
|
|
445
|
+
return cmdConfigure(configPath, explicitConfigPath, {
|
|
446
|
+
print: Boolean(values.print),
|
|
447
|
+
yes: Boolean(values.yes),
|
|
448
|
+
});
|
|
449
|
+
case "doctor":
|
|
450
|
+
return cmdDoctor(configPath, {
|
|
451
|
+
json: Boolean(values.json),
|
|
452
|
+
live: Boolean(values.live),
|
|
453
|
+
allowPaid: Boolean(values["allow-paid"]),
|
|
454
|
+
});
|
|
455
|
+
case "status":
|
|
456
|
+
case "dashboard":
|
|
457
|
+
case "list-services":
|
|
458
|
+
return cmdStatus(configPath, {
|
|
459
|
+
json: Boolean(values.json) || command === "list-services",
|
|
460
|
+
watch: Boolean(values.watch),
|
|
461
|
+
intervalMs: parsePositiveInt(values.interval, 1000),
|
|
462
|
+
});
|
|
463
|
+
case "usage":
|
|
464
|
+
return cmdUsage(configPath, { json: Boolean(values.json) });
|
|
465
|
+
case "serve":
|
|
466
|
+
return cmdServe(configPath, serveOpts(values));
|
|
467
|
+
case "auth":
|
|
468
|
+
return cmdAuth(rest[0]);
|
|
469
|
+
case "route":
|
|
470
|
+
return cmdRouteAlias(rest.join(" ").trim(), configPath);
|
|
471
|
+
case "mcp":
|
|
472
|
+
if (values.http !== undefined) {
|
|
473
|
+
return cmdServe(configPath, serveOpts({ port: values.http, host: values.host }));
|
|
474
|
+
}
|
|
475
|
+
return main(configPath !== undefined ? ["--config", configPath] : []);
|
|
476
|
+
default:
|
|
477
|
+
process.stderr.write(`unknown command: ${command}\n`);
|
|
478
|
+
printUsage();
|
|
479
|
+
return 1;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const entrypoint = typeof process !== "undefined" && Array.isArray(process.argv) ? process.argv[1] : "";
|
|
483
|
+
if (entrypoint && (entrypoint.endsWith("bin.ts") || entrypoint.endsWith("bin.js"))) {
|
|
484
|
+
void main(process.argv.slice(2)).then((code) => {
|
|
485
|
+
process.exit(code);
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process persistence for circuit-breaker state.
|
|
3
|
+
*
|
|
4
|
+
* CircuitBreaker itself is in-memory only (Router constructs a fresh Map on
|
|
5
|
+
* every boot) — a rate-limited route becomes eligible again the instant the
|
|
6
|
+
* server process restarts, even though the provider's real cooldown hasn't
|
|
7
|
+
* elapsed. Confirmed in production: a restart during an active Codex quota
|
|
8
|
+
* cooldown let the router immediately retry the same exhausted route.
|
|
9
|
+
*
|
|
10
|
+
* This mirrors QuotaCache's state-file pattern (same HARNESS_DISPATCH_STATE_DIR
|
|
11
|
+
* override, same atomic temp-file-then-rename write) so a restart hydrates
|
|
12
|
+
* each CircuitBreaker with any cooldown still in effect.
|
|
13
|
+
*/
|
|
14
|
+
import type { CircuitBreakerSnapshot } from "./circuit-breaker.js";
|
|
15
|
+
export declare class BreakerStore {
|
|
16
|
+
private readonly stateFile;
|
|
17
|
+
private persistCounter;
|
|
18
|
+
constructor(stateFile?: string);
|
|
19
|
+
loadAll(): Record<string, CircuitBreakerSnapshot>;
|
|
20
|
+
/**
|
|
21
|
+
* Persist one service's snapshot, synchronously — a CLI invocation can
|
|
22
|
+
* exit immediately after a dispatch, same rationale as
|
|
23
|
+
* QuotaCache.saveLocalCountsSync. A fully-healthy snapshot (no failures,
|
|
24
|
+
* not blocked) removes the entry instead of writing a no-op record, so the
|
|
25
|
+
* file stays empty in the common case.
|
|
26
|
+
*/
|
|
27
|
+
save(service: string, snapshot: CircuitBreakerSnapshot): void;
|
|
28
|
+
private nextTempFile;
|
|
29
|
+
private writeAtomicSync;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=breaker-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"breaker-store.d.ts","sourceRoot":"","sources":["../src/breaker-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAOnE,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,cAAc,CAAK;gBAEf,SAAS,CAAC,EAAE,MAAM;IAI9B,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC;IAsBjD;;;;;;OAMG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAuB7D,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;CAexB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process persistence for circuit-breaker state.
|
|
3
|
+
*
|
|
4
|
+
* CircuitBreaker itself is in-memory only (Router constructs a fresh Map on
|
|
5
|
+
* every boot) — a rate-limited route becomes eligible again the instant the
|
|
6
|
+
* server process restarts, even though the provider's real cooldown hasn't
|
|
7
|
+
* elapsed. Confirmed in production: a restart during an active Codex quota
|
|
8
|
+
* cooldown let the router immediately retry the same exhausted route.
|
|
9
|
+
*
|
|
10
|
+
* This mirrors QuotaCache's state-file pattern (same HARNESS_DISPATCH_STATE_DIR
|
|
11
|
+
* override, same atomic temp-file-then-rename write) so a restart hydrates
|
|
12
|
+
* each CircuitBreaker with any cooldown still in effect.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
function defaultStateFile() {
|
|
18
|
+
const dir = process.env.HARNESS_DISPATCH_STATE_DIR ?? path.join(homedir(), ".harness-dispatch");
|
|
19
|
+
return path.join(dir, "breaker_state.json");
|
|
20
|
+
}
|
|
21
|
+
export class BreakerStore {
|
|
22
|
+
stateFile;
|
|
23
|
+
persistCounter = 0;
|
|
24
|
+
constructor(stateFile) {
|
|
25
|
+
this.stateFile = stateFile ?? defaultStateFile();
|
|
26
|
+
}
|
|
27
|
+
loadAll() {
|
|
28
|
+
if (!existsSync(this.stateFile))
|
|
29
|
+
return {};
|
|
30
|
+
try {
|
|
31
|
+
const raw = readFileSync(this.stateFile, "utf-8");
|
|
32
|
+
const data = JSON.parse(raw);
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [service, v] of Object.entries(data)) {
|
|
35
|
+
if (!v || typeof v !== "object")
|
|
36
|
+
continue;
|
|
37
|
+
out[service] = {
|
|
38
|
+
failures: typeof v.failures === "number" ? v.failures : 0,
|
|
39
|
+
blockedUntilMs: typeof v.blockedUntilMs === "number" ? v.blockedUntilMs : null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Persist one service's snapshot, synchronously — a CLI invocation can
|
|
50
|
+
* exit immediately after a dispatch, same rationale as
|
|
51
|
+
* QuotaCache.saveLocalCountsSync. A fully-healthy snapshot (no failures,
|
|
52
|
+
* not blocked) removes the entry instead of writing a no-op record, so the
|
|
53
|
+
* file stays empty in the common case.
|
|
54
|
+
*/
|
|
55
|
+
save(service, snapshot) {
|
|
56
|
+
let existing = {};
|
|
57
|
+
try {
|
|
58
|
+
if (existsSync(this.stateFile)) {
|
|
59
|
+
const raw = readFileSync(this.stateFile, "utf-8");
|
|
60
|
+
const parsed = JSON.parse(raw);
|
|
61
|
+
if (parsed && typeof parsed === "object")
|
|
62
|
+
existing = parsed;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
existing = {};
|
|
67
|
+
}
|
|
68
|
+
if (snapshot.blockedUntilMs === null && snapshot.failures === 0) {
|
|
69
|
+
delete existing[service];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
existing[service] = snapshot;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
this.writeAtomicSync(JSON.stringify(existing, null, 2));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Best-effort — persistence failure shouldn't fail the dispatch.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
nextTempFile() {
|
|
82
|
+
this.persistCounter += 1;
|
|
83
|
+
return `${this.stateFile}.${process.pid}.${Date.now()}.${this.persistCounter}.tmp`;
|
|
84
|
+
}
|
|
85
|
+
writeAtomicSync(payload) {
|
|
86
|
+
const tmp = this.nextTempFile();
|
|
87
|
+
try {
|
|
88
|
+
mkdirSync(path.dirname(this.stateFile), { recursive: true });
|
|
89
|
+
writeFileSync(tmp, payload);
|
|
90
|
+
renameSync(tmp, this.stateFile);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
try {
|
|
94
|
+
unlinkSync(tmp);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore cleanup failures.
|
|
98
|
+
}
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=breaker-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"breaker-store.js","sourceRoot":"","sources":["../src/breaker-store.ts"],"names":[],"mappings":"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"}
|