harness-dispatch 0.4.0 → 0.6.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/CHANGELOG.md +152 -0
- package/README.md +232 -87
- package/config.default.yaml +416 -397
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/billing.d.ts +35 -4
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +48 -17
- package/dist/billing.js.map +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +147 -91
- package/dist/bin.js.map +1 -1
- package/dist/breaker-store.d.ts +60 -9
- package/dist/breaker-store.d.ts.map +1 -1
- package/dist/breaker-store.js +246 -50
- package/dist/breaker-store.js.map +1 -1
- package/dist/circuit-breaker.d.ts +35 -0
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +68 -11
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/config/coercions.d.ts +31 -0
- package/dist/config/coercions.d.ts.map +1 -0
- package/dist/config/coercions.js +169 -0
- package/dist/config/coercions.js.map +1 -0
- package/dist/config/env-interpolation.d.ts +36 -0
- package/dist/config/env-interpolation.d.ts.map +1 -0
- package/dist/config/env-interpolation.js +73 -0
- package/dist/config/env-interpolation.js.map +1 -0
- package/dist/config/protocol.d.ts +51 -0
- package/dist/config/protocol.d.ts.map +1 -0
- package/dist/config/protocol.js +300 -0
- package/dist/config/protocol.js.map +1 -0
- package/dist/config/route-fields.d.ts +72 -0
- package/dist/config/route-fields.d.ts.map +1 -0
- package/dist/config/route-fields.js +128 -0
- package/dist/config/route-fields.js.map +1 -0
- package/dist/config/validation.d.ts +72 -0
- package/dist/config/validation.d.ts.map +1 -0
- package/dist/config/validation.js +189 -0
- package/dist/config/validation.js.map +1 -0
- package/dist/config.d.ts +19 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +214 -635
- package/dist/config.js.map +1 -1
- package/dist/configure-yaml.d.ts +23 -0
- package/dist/configure-yaml.d.ts.map +1 -0
- package/dist/configure-yaml.js +193 -0
- package/dist/configure-yaml.js.map +1 -0
- package/dist/dispatch-log.d.ts.map +1 -1
- package/dist/dispatch-log.js +2 -3
- package/dist/dispatch-log.js.map +1 -1
- package/dist/dispatchers/base.d.ts +3 -0
- package/dist/dispatchers/base.d.ts.map +1 -1
- package/dist/dispatchers/base.js.map +1 -1
- package/dist/dispatchers/generic-cli.d.ts +15 -1
- package/dist/dispatchers/generic-cli.d.ts.map +1 -1
- package/dist/dispatchers/generic-cli.js +69 -5
- package/dist/dispatchers/generic-cli.js.map +1 -1
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
- package/dist/dispatchers/openai-compatible.js +70 -2
- package/dist/dispatchers/openai-compatible.js.map +1 -1
- package/dist/dispatchers/shared/kill-tree.d.ts +17 -4
- package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -1
- package/dist/dispatchers/shared/kill-tree.js +28 -4
- package/dist/dispatchers/shared/kill-tree.js.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.d.ts +17 -0
- package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -1
- package/dist/dispatchers/shared/rate-limit-headers.js +27 -0
- package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.d.ts +11 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.js +62 -35
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
- package/dist/dispatchers/shared/subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/subprocess.js +3 -0
- package/dist/dispatchers/shared/subprocess.js.map +1 -1
- package/dist/dispatchers/shared/which-available.d.ts.map +1 -1
- package/dist/dispatchers/shared/which-available.js +16 -2
- package/dist/dispatchers/shared/which-available.js.map +1 -1
- package/dist/file-lock.d.ts +17 -0
- package/dist/file-lock.d.ts.map +1 -0
- package/dist/file-lock.js +143 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/http/parse.d.ts +64 -0
- package/dist/http/parse.d.ts.map +1 -0
- package/dist/http/parse.js +230 -0
- package/dist/http/parse.js.map +1 -0
- package/dist/http/server.d.ts +2 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +249 -286
- package/dist/http/server.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/job-runner.js +23 -5
- package/dist/job-runner.js.map +1 -1
- package/dist/jobs/context.d.ts +17 -0
- package/dist/jobs/context.d.ts.map +1 -0
- package/dist/jobs/context.js +84 -0
- package/dist/jobs/context.js.map +1 -0
- package/dist/jobs/store.d.ts +80 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +279 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/jobs/types.d.ts +135 -0
- package/dist/jobs/types.d.ts.map +1 -0
- package/dist/jobs/types.js +11 -0
- package/dist/jobs/types.js.map +1 -0
- package/dist/jobs.d.ts +165 -85
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +824 -208
- package/dist/jobs.js.map +1 -1
- package/dist/leaderboard.d.ts +24 -1
- package/dist/leaderboard.d.ts.map +1 -1
- package/dist/leaderboard.js +94 -2
- package/dist/leaderboard.js.map +1 -1
- package/dist/mcp/config-hot-reload.d.ts.map +1 -1
- package/dist/mcp/config-hot-reload.js +1 -1
- package/dist/mcp/config-hot-reload.js.map +1 -1
- package/dist/mcp/dispatcher-factory.d.ts +16 -1
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -1
- package/dist/mcp/dispatcher-factory.js +41 -3
- package/dist/mcp/dispatcher-factory.js.map +1 -1
- package/dist/mcp/tool-schemas.d.ts +194 -0
- package/dist/mcp/tool-schemas.d.ts.map +1 -0
- package/dist/mcp/tool-schemas.js +311 -0
- package/dist/mcp/tool-schemas.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -51
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +122 -151
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability/index.d.ts +16 -3
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +23 -7
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/spans.d.ts.map +1 -1
- package/dist/observability/spans.js +4 -1
- package/dist/observability/spans.js.map +1 -1
- package/dist/quota.d.ts +106 -22
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +198 -58
- package/dist/quota.js.map +1 -1
- package/dist/route-policy.d.ts.map +1 -1
- package/dist/route-policy.js +13 -3
- package/dist/route-policy.js.map +1 -1
- package/dist/router.d.ts +29 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +102 -40
- package/dist/router.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +11 -2
- package/dist/safety.js.map +1 -1
- package/dist/state-dir.d.ts +17 -0
- package/dist/state-dir.d.ts.map +1 -0
- package/dist/state-dir.js +21 -0
- package/dist/state-dir.js.map +1 -0
- package/dist/status.d.ts +49 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +67 -4
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +74 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/working-dir.d.ts +13 -0
- package/dist/working-dir.d.ts.map +1 -1
- package/dist/working-dir.js +27 -0
- package/dist/working-dir.js.map +1 -1
- package/dist/workspace-lock.d.ts +40 -0
- package/dist/workspace-lock.d.ts.map +1 -0
- package/dist/workspace-lock.js +258 -0
- package/dist/workspace-lock.js.map +1 -0
- package/dist/workspace-resolve.d.ts +98 -0
- package/dist/workspace-resolve.d.ts.map +1 -0
- package/dist/workspace-resolve.js +382 -0
- package/dist/workspace-resolve.js.map +1 -0
- package/dist/workspaces.d.ts +16 -0
- package/dist/workspaces.d.ts.map +1 -1
- package/dist/workspaces.js +166 -22
- package/dist/workspaces.js.map +1 -1
- package/package.json +99 -96
- package/dist/dashboard/live.d.ts +0 -49
- package/dist/dashboard/live.d.ts.map +0 -1
- package/dist/dashboard/live.js +0 -149
- package/dist/dashboard/live.js.map +0 -1
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AASA,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAS5D;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAOvD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAKvD;AAmBD,wBAAgB,YAAY,CAC1B,mBAAmB,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAClD,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAOT;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/C"}
|
package/dist/auth.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { stateRoot } from "./state-dir.js";
|
|
5
5
|
const TOKEN_ENV = "HARNESS_DISPATCH_HTTP_TOKEN";
|
|
6
6
|
export function authDir() {
|
|
7
|
-
return process.env.HARNESS_DISPATCH_HOME ??
|
|
7
|
+
return process.env.HARNESS_DISPATCH_HOME ?? stateRoot();
|
|
8
8
|
}
|
|
9
9
|
export function tokenPath() {
|
|
10
10
|
return path.join(authDir(), "http-token");
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/billing.d.ts
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
|
-
import type { RouteBilling, ServiceConfig } from "./types.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { BillingKind, RouteBilling, ServiceConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a route can incur a REAL charge with no further action from the
|
|
4
|
+
* user, by default — i.e. whether harness-dispatch should block it until
|
|
5
|
+
* explicitly allowed.
|
|
6
|
+
*
|
|
7
|
+
* "included_X_then_optional_Y" kinds (Codex flexible credits, Claude usage
|
|
8
|
+
* credits, Cursor on-demand) are NOT blocked by default: researched across
|
|
9
|
+
* Anthropic, OpenAI, and Cursor (2026-07), all three hard-stop at the
|
|
10
|
+
* included cap by default — continuing past it requires the user to have
|
|
11
|
+
* ALREADY completed a separate, deliberate opt-in on the PROVIDER's own
|
|
12
|
+
* side (enabling usage credits/flexible pricing/on-demand billing, usually
|
|
13
|
+
* with its own payment method and spend limit). harness-dispatch blocking
|
|
14
|
+
* these by default would just be re-gating something the provider already
|
|
15
|
+
* gates, and asking every user to prove a negative ("I haven't opted into
|
|
16
|
+
* my provider's overage") for a state that's off by default anyway.
|
|
17
|
+
*
|
|
18
|
+
* A user who HAS enabled provider-side overage can still restore the block
|
|
19
|
+
* by setting `paid_usage_possible: true` explicitly in that route's config
|
|
20
|
+
* — the override in buildRouteBilling() takes precedence over this
|
|
21
|
+
* function. Only kinds with no such provider-side backstop — metered_api
|
|
22
|
+
* (raw API keys bill from the first token, no included pool at all) and
|
|
23
|
+
* unknown (no data to reason about) — are blocked by default here.
|
|
24
|
+
*/
|
|
25
|
+
export declare function inferredPaidUsagePossible(kind: BillingKind): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Classification is date-independent by construction.
|
|
28
|
+
*
|
|
29
|
+
* This took an `opts.now` that was threaded into inferredKind/defaultNotes
|
|
30
|
+
* and used by neither — a leftover from when the Agent SDK credit split was
|
|
31
|
+
* expected to change classification on 2026-06-15. Anthropic paused that on
|
|
32
|
+
* launch day, the date logic was removed, and the parameter stayed behind
|
|
33
|
+
* implying a time dependency that no longer exists.
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildRouteBilling(svc: ServiceConfig): RouteBilling;
|
|
5
36
|
export declare function billingIsUnknown(billing: RouteBilling): boolean;
|
|
6
37
|
export declare function billingIsBlocked(billing: RouteBilling): boolean;
|
|
7
38
|
//# sourceMappingURL=billing.d.ts.map
|
package/dist/billing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,WAAW,EAGX,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AA0IpB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAapE;AAuBD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,YAAY,CAoBlE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAE/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAI/D"}
|
package/dist/billing.js
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Parse a base_url into (hostname, port), or undefined if it isn't a URL.
|
|
3
|
+
*
|
|
4
|
+
* Both predicates below used `String.includes`, which is not a host check:
|
|
5
|
+
* `https://evil.example.com/proxy?upstream=localhost:11434/v1` contains
|
|
6
|
+
* "localhost:11434" and so classified as free local compute — provider
|
|
7
|
+
* "local", kind "local_compute", paidUsagePossible false. That also exempted
|
|
8
|
+
* it from the caller-supplied `local_only` and `approval_required` policies,
|
|
9
|
+
* because route-policy.ts's isLocalRoute ORs those same four fields.
|
|
10
|
+
*
|
|
11
|
+
* Requires a hostile or mistaken config entry, so it is not remotely
|
|
12
|
+
* triggerable — but "is this host local" is exactly the question a substring
|
|
13
|
+
* cannot answer. config.ts:inferEndpointProvider already did this correctly;
|
|
14
|
+
* this is the same approach applied to the two predicates that did not.
|
|
15
|
+
*/
|
|
16
|
+
function hostOf(baseUrl) {
|
|
2
17
|
if (!baseUrl)
|
|
3
|
-
return
|
|
4
|
-
|
|
5
|
-
baseUrl
|
|
6
|
-
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(baseUrl);
|
|
21
|
+
return { host: url.hostname.toLowerCase(), port: url.port };
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
28
|
+
function isLoopback(baseUrl) {
|
|
29
|
+
const parsed = hostOf(baseUrl);
|
|
30
|
+
return parsed !== undefined && LOOPBACK_HOSTS.has(parsed.host);
|
|
7
31
|
}
|
|
32
|
+
/** Ollama (11434) and LM Studio (1234) on loopback — the two runtimes we can name. */
|
|
8
33
|
function isKnownLocalRuntime(baseUrl) {
|
|
9
|
-
|
|
34
|
+
const parsed = hostOf(baseUrl);
|
|
35
|
+
if (parsed === undefined || !LOOPBACK_HOSTS.has(parsed.host))
|
|
10
36
|
return false;
|
|
11
|
-
return
|
|
12
|
-
baseUrl.includes("127.0.0.1:11434") ||
|
|
13
|
-
baseUrl.includes("localhost:1234") ||
|
|
14
|
-
baseUrl.includes("127.0.0.1:1234"));
|
|
37
|
+
return parsed.port === "11434" || parsed.port === "1234";
|
|
15
38
|
}
|
|
16
39
|
// NOTE: no harness-name special cases here. Built-in harnesses declare
|
|
17
40
|
// provider/surface/auth_source/billing_kind in the shipped config.default.yaml,
|
|
@@ -58,7 +81,7 @@ function authSourceFromService(svc, surface) {
|
|
|
58
81
|
}
|
|
59
82
|
return "unknown";
|
|
60
83
|
}
|
|
61
|
-
function inferredKind(svc, surface, authSource
|
|
84
|
+
function inferredKind(svc, surface, authSource) {
|
|
62
85
|
if (svc.billingKind)
|
|
63
86
|
return svc.billingKind;
|
|
64
87
|
if (authSource === "api_key")
|
|
@@ -136,7 +159,7 @@ function inferredConfidence(svc, surface, kind) {
|
|
|
136
159
|
* (raw API keys bill from the first token, no included pool at all) and
|
|
137
160
|
* unknown (no data to reason about) — are blocked by default here.
|
|
138
161
|
*/
|
|
139
|
-
function inferredPaidUsagePossible(kind) {
|
|
162
|
+
export function inferredPaidUsagePossible(kind) {
|
|
140
163
|
switch (kind) {
|
|
141
164
|
case "local_compute":
|
|
142
165
|
case "included_plan_usage":
|
|
@@ -150,7 +173,7 @@ function inferredPaidUsagePossible(kind) {
|
|
|
150
173
|
return true;
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
|
-
function defaultNotes(svc, surface, kind
|
|
176
|
+
function defaultNotes(svc, surface, kind) {
|
|
154
177
|
if (svc.billingNotes)
|
|
155
178
|
return svc.billingNotes;
|
|
156
179
|
if (surface === "claude_agent_sdk" || surface === "claude_code") {
|
|
@@ -165,12 +188,20 @@ function defaultNotes(svc, surface, kind, now) {
|
|
|
165
188
|
}
|
|
166
189
|
return undefined;
|
|
167
190
|
}
|
|
168
|
-
|
|
169
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Classification is date-independent by construction.
|
|
193
|
+
*
|
|
194
|
+
* This took an `opts.now` that was threaded into inferredKind/defaultNotes
|
|
195
|
+
* and used by neither — a leftover from when the Agent SDK credit split was
|
|
196
|
+
* expected to change classification on 2026-06-15. Anthropic paused that on
|
|
197
|
+
* launch day, the date logic was removed, and the parameter stayed behind
|
|
198
|
+
* implying a time dependency that no longer exists.
|
|
199
|
+
*/
|
|
200
|
+
export function buildRouteBilling(svc) {
|
|
170
201
|
const provider = providerFromService(svc);
|
|
171
202
|
const surface = surfaceFromService(svc);
|
|
172
203
|
const authSource = authSourceFromService(svc, surface);
|
|
173
|
-
const kind = inferredKind(svc, surface, authSource
|
|
204
|
+
const kind = inferredKind(svc, surface, authSource);
|
|
174
205
|
const confidence = inferredConfidence(svc, surface, kind);
|
|
175
206
|
const paidUsagePossible = svc.paidUsagePossible ?? inferredPaidUsagePossible(kind);
|
|
176
207
|
const billing = {
|
|
@@ -183,7 +214,7 @@ export function buildRouteBilling(svc, opts = {}) {
|
|
|
183
214
|
paidUsageRequiresOptIn: paidUsagePossible ? true : false,
|
|
184
215
|
confidence,
|
|
185
216
|
};
|
|
186
|
-
const notes = defaultNotes(svc, surface, kind
|
|
217
|
+
const notes = defaultNotes(svc, surface, kind);
|
|
187
218
|
if (notes !== undefined)
|
|
188
219
|
billing.notes = notes;
|
|
189
220
|
return billing;
|
package/dist/billing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"AAUA,SAAS,
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../src/billing.ts"],"names":[],"mappings":"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"}
|
package/dist/bin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAmhBH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiH1D"}
|
package/dist/bin.js
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* harness-dispatch CLI entrypoint.
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { promises as fs } from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
|
-
import yaml from "js-yaml";
|
|
10
9
|
import { ensureHttpToken, maskToken, readHttpToken, rotateHttpToken, tokenPath } from "./auth.js";
|
|
11
|
-
import { loadConfig } from "./config.js";
|
|
10
|
+
import { AUTO_DETECT_COMMANDS, loadConfig } from "./config.js";
|
|
12
11
|
import { LeaderboardCache } from "./leaderboard.js";
|
|
13
12
|
import { buildDispatchers } from "./mcp/dispatcher-factory.js";
|
|
14
13
|
import { startMcpServer } from "./mcp/server.js";
|
|
@@ -19,11 +18,15 @@ import { buildStatus, buildUsage, renderStatusText, renderUsageText } from "./st
|
|
|
19
18
|
import { startHttpServer } from "./http/server.js";
|
|
20
19
|
import { billingIsBlocked, buildRouteBilling } from "./billing.js";
|
|
21
20
|
import { effectiveSafetyProfile } from "./safety.js";
|
|
21
|
+
import { configToYaml } from "./configure-yaml.js";
|
|
22
|
+
import { stateRoot } from "./state-dir.js";
|
|
22
23
|
async function buildRuntime(configPath) {
|
|
23
24
|
const config = await loadConfig(configPath);
|
|
24
25
|
const dispatchers = await buildDispatchers(config);
|
|
25
26
|
const quota = new QuotaCache(dispatchers);
|
|
26
|
-
const leaderboard = new LeaderboardCache(
|
|
27
|
+
const leaderboard = new LeaderboardCache(undefined, {
|
|
28
|
+
enabled: config.leaderboard?.enabled === true,
|
|
29
|
+
});
|
|
27
30
|
const router = new Router(config, quota, dispatchers, leaderboard);
|
|
28
31
|
return { config, dispatchers, quota, leaderboard, router };
|
|
29
32
|
}
|
|
@@ -52,94 +55,34 @@ function printUsage() {
|
|
|
52
55
|
" --json Print JSON where supported.",
|
|
53
56
|
" --print configure: print generated config YAML without writing it.",
|
|
54
57
|
" --yes configure: write config.yaml instead of only previewing it.",
|
|
58
|
+
" --force configure: overwrite an existing config file.",
|
|
55
59
|
" --allow-paid Allow doctor --live to probe paid or unknown-paid routes.",
|
|
56
60
|
" -h, --help Show help.",
|
|
57
61
|
"",
|
|
58
62
|
].join("\n"));
|
|
59
63
|
}
|
|
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
64
|
async function cmdConfigure(configPath, explicitConfigPath, opts) {
|
|
136
|
-
|
|
137
|
-
|
|
65
|
+
// configure's --config names where it will WRITE, so a path that does not
|
|
66
|
+
// exist yet is the normal first-run case, not a typo.
|
|
67
|
+
const config = await loadConfig(configPath, { allowMissing: true });
|
|
138
68
|
const routeCount = Object.keys(config.services).length;
|
|
139
69
|
if (opts.print) {
|
|
140
|
-
|
|
70
|
+
// Preview goes to a terminal and, routinely, into a bug report — a
|
|
71
|
+
// literal key with no ${VAR} to restore is redacted rather than echoed.
|
|
72
|
+
const preview = configToYaml(config, { redactLiterals: true });
|
|
73
|
+
process.stdout.write(preview);
|
|
74
|
+
const redacted = Object.values(config.services).some((svc) => svc.apiKey !== undefined &&
|
|
75
|
+
svc.apiKey !== "" &&
|
|
76
|
+
config.envRefs?.get(svc.apiKey) === undefined);
|
|
77
|
+
if (redacted) {
|
|
78
|
+
process.stderr.write("note: one or more api_key values are literals in the source config and were " +
|
|
79
|
+
"replaced with ${ENV_VAR} placeholders in this preview. Move them to " +
|
|
80
|
+
"environment variables — this output is not a drop-in replacement for that file " +
|
|
81
|
+
"until you do.\n");
|
|
82
|
+
}
|
|
141
83
|
return 0;
|
|
142
84
|
}
|
|
85
|
+
const yamlText = configToYaml(config, { redactLiterals: false });
|
|
143
86
|
process.stdout.write(`Detected ${routeCount} harness route${routeCount === 1 ? "" : "s"}.\n`);
|
|
144
87
|
for (const [name, svc] of Object.entries(config.services)) {
|
|
145
88
|
process.stdout.write(`- ${name}: harness=${svc.harness ?? name} billing=${buildRouteBilling(svc).kind} safety=${effectiveSafetyProfile(svc)} model=${svc.model ?? svc.leaderboardModel ?? "unknown"}\n`);
|
|
@@ -165,8 +108,17 @@ async function cmdConfigure(configPath, explicitConfigPath, opts) {
|
|
|
165
108
|
process.stdout.write("After writing config, connect agents by adding the harness-dispatch MCP snippet to the agent you use.\n");
|
|
166
109
|
return 0;
|
|
167
110
|
}
|
|
168
|
-
|
|
169
|
-
|
|
111
|
+
// Guard ANY existing file, however the path was supplied.
|
|
112
|
+
//
|
|
113
|
+
// This previously read `existsSync(target) && explicitConfigPath === undefined`,
|
|
114
|
+
// so passing --config skipped the protection entirely — and the message told
|
|
115
|
+
// you to pass --config, which is exactly what disabled it. Overwriting a
|
|
116
|
+
// hand-written config is not recoverable, so it now takes an explicit
|
|
117
|
+
// --force rather than an accident of which flag you happened to use.
|
|
118
|
+
if (existsSync(target) && !opts.force) {
|
|
119
|
+
process.stderr.write(`configure: ${target} already exists and would be overwritten.\n` +
|
|
120
|
+
"Use --print to inspect the generated YAML, --config <other-path> to write\n" +
|
|
121
|
+
"elsewhere, or --force to overwrite it deliberately.\n");
|
|
170
122
|
return 1;
|
|
171
123
|
}
|
|
172
124
|
await fs.writeFile(target, yamlText, "utf-8");
|
|
@@ -220,6 +172,30 @@ async function cmdUsage(configPath, opts) {
|
|
|
220
172
|
process.stdout.write(opts.json ? `${JSON.stringify(usage, null, 2)}\n` : `${renderUsageText(usage)}\n`);
|
|
221
173
|
return 0;
|
|
222
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Can we actually persist state? Breaker cooldowns, quota counters and job
|
|
177
|
+
* records all live here, and every write path deliberately swallows its own
|
|
178
|
+
* failures so a dispatch is never lost to a bookkeeping problem. The cost of
|
|
179
|
+
* that choice is silence, which is what this check buys back.
|
|
180
|
+
*/
|
|
181
|
+
function stateDirWritable() {
|
|
182
|
+
const dir = stateRoot();
|
|
183
|
+
const probe = path.join(dir, `.write-probe-${process.pid}`);
|
|
184
|
+
try {
|
|
185
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
186
|
+
writeFileSync(probe, "", { mode: 0o600 });
|
|
187
|
+
rmSync(probe, { force: true });
|
|
188
|
+
return { ok: true, detail: `writable: ${dir}` };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
detail: `NOT writable: ${dir} (${err instanceof Error ? err.message : String(err)}). ` +
|
|
194
|
+
`Breaker cooldowns and usage counters will not persist, and jobs may be ` +
|
|
195
|
+
`reported as orphaned after they have actually succeeded.`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
223
199
|
async function cmdDoctor(configPath, opts) {
|
|
224
200
|
const runtime = await buildRuntime(configPath);
|
|
225
201
|
if (opts.allowPaid) {
|
|
@@ -228,12 +204,24 @@ async function cmdDoctor(configPath, opts) {
|
|
|
228
204
|
}
|
|
229
205
|
}
|
|
230
206
|
const status = await buildStatus(runtime.config, runtime.dispatchers, runtime.quota, runtime.router, runtime.leaderboard);
|
|
231
|
-
|
|
207
|
+
// Must agree with package.json engines (>=22.22.2) and the README. It said
|
|
208
|
+
// >= 24 while both of those said 22, so a user following the README's own
|
|
209
|
+
// install block hit `fail node` on the second command — on a runtime where
|
|
210
|
+
// dispatch works correctly. Three sources, two answers, and the one the
|
|
211
|
+
// user sees first was the wrong one.
|
|
212
|
+
const [nodeMajor, nodeMinor, nodePatch] = process.versions.node
|
|
213
|
+
.split(".")
|
|
214
|
+
.map((n) => Number(n) || 0);
|
|
215
|
+
const nodeOk = (nodeMajor ?? 0) > 22 ||
|
|
216
|
+
((nodeMajor ?? 0) === 22 &&
|
|
217
|
+
((nodeMinor ?? 0) > 22 || ((nodeMinor ?? 0) === 22 && (nodePatch ?? 0) >= 2)));
|
|
232
218
|
const checks = [
|
|
233
219
|
{
|
|
234
220
|
name: "node",
|
|
235
|
-
ok:
|
|
236
|
-
detail:
|
|
221
|
+
ok: nodeOk,
|
|
222
|
+
detail: nodeOk
|
|
223
|
+
? `Node ${process.versions.node}`
|
|
224
|
+
: `Node ${process.versions.node} — harness-dispatch needs >=22.22.2`,
|
|
237
225
|
},
|
|
238
226
|
{
|
|
239
227
|
name: "config",
|
|
@@ -250,7 +238,23 @@ async function cmdDoctor(configPath, opts) {
|
|
|
250
238
|
{
|
|
251
239
|
name: "routes",
|
|
252
240
|
ok: status.ready.length > 0,
|
|
253
|
-
|
|
241
|
+
// When nothing is ready, say what was looked for. "0 ready route(s)" on
|
|
242
|
+
// its own leaves a new user with no idea whether the tool is broken or
|
|
243
|
+
// simply has nothing to route to, and no hint what to install.
|
|
244
|
+
detail: status.ready.length > 0
|
|
245
|
+
? `${status.ready.length} ready route(s)`
|
|
246
|
+
: `0 ready route(s). Looked for these harness CLIs on PATH: ` +
|
|
247
|
+
`${Object.values(AUTO_DETECT_COMMANDS).join(", ")}. ` +
|
|
248
|
+
`Install one, or add a route to config.yaml (endpoints: need no CLI).`,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
// Nothing checked this, so an unwritable state directory surfaced only
|
|
252
|
+
// as jobs mysteriously reported "the dispatch server exited before the
|
|
253
|
+
// run finished" — a false cause, 90s after the work had actually
|
|
254
|
+
// succeeded.
|
|
255
|
+
name: "state-dir",
|
|
256
|
+
ok: stateDirWritable().ok,
|
|
257
|
+
detail: stateDirWritable().detail,
|
|
254
258
|
},
|
|
255
259
|
{
|
|
256
260
|
name: "http-auth",
|
|
@@ -385,10 +389,26 @@ function parsePositiveInt(value, fallback) {
|
|
|
385
389
|
const parsed = Number(value);
|
|
386
390
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
387
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Thrown for a flag value the user typed wrong. main() turns it into a plain
|
|
394
|
+
* one-line message and exit 1 — never a stack trace, which tells a CLI user
|
|
395
|
+
* nothing they can act on.
|
|
396
|
+
*/
|
|
397
|
+
class UsageError extends Error {
|
|
398
|
+
}
|
|
388
399
|
function serveOpts(values) {
|
|
389
400
|
const out = {};
|
|
390
|
-
if (values.port !== undefined)
|
|
391
|
-
|
|
401
|
+
if (values.port !== undefined) {
|
|
402
|
+
// `--port abc` used to fall back to 0, which binds a RANDOM free port and
|
|
403
|
+
// prints it as though it were what was asked for. A typo'd port silently
|
|
404
|
+
// serving somewhere else is worse than refusing to start.
|
|
405
|
+
const port = parsePositiveInt(values.port, 0);
|
|
406
|
+
if (port === 0 || !Number.isInteger(port) || port > 65535) {
|
|
407
|
+
throw new UsageError(`--port must be an integer between 1 and 65535 (got ${JSON.stringify(values.port)}). ` +
|
|
408
|
+
`Omit --port to bind a random free one.`);
|
|
409
|
+
}
|
|
410
|
+
out.port = port;
|
|
411
|
+
}
|
|
392
412
|
if (typeof values.host === "string")
|
|
393
413
|
out.host = values.host;
|
|
394
414
|
return out;
|
|
@@ -408,17 +428,37 @@ export async function main(argv) {
|
|
|
408
428
|
host: { type: "string" },
|
|
409
429
|
print: { type: "boolean" },
|
|
410
430
|
yes: { type: "boolean" },
|
|
431
|
+
force: { type: "boolean" },
|
|
411
432
|
http: { type: "string" },
|
|
412
433
|
},
|
|
413
434
|
allowPositionals: true,
|
|
414
435
|
strict: false,
|
|
415
436
|
});
|
|
437
|
+
// parseArgs runs with strict:false so positionals and subcommand shapes stay
|
|
438
|
+
// flexible — the cost is that an unknown flag is silently accepted. `status
|
|
439
|
+
// --jsonn` printed human text and exited 0, so a cron job piping it to jq
|
|
440
|
+
// got garbage AND a success code. PRODUCT.md names automation as a user, and
|
|
441
|
+
// a wrong exit code is the one thing automation cannot recover from.
|
|
442
|
+
const knownFlags = new Set([
|
|
443
|
+
"help", "config", "json", "live", "allow-paid", "watch", "interval",
|
|
444
|
+
"port", "host", "print", "yes", "force", "http",
|
|
445
|
+
]);
|
|
446
|
+
const unknownFlags = Object.keys(values).filter((k) => !knownFlags.has(k));
|
|
447
|
+
if (unknownFlags.length > 0) {
|
|
448
|
+
throw new UsageError(`unknown option${unknownFlags.length > 1 ? "s" : ""}: ` +
|
|
449
|
+
`${unknownFlags.map((f) => `--${f}`).join(", ")}. Run --help for the list.`);
|
|
450
|
+
}
|
|
416
451
|
if (values.help) {
|
|
417
452
|
printUsage();
|
|
418
453
|
return 0;
|
|
419
454
|
}
|
|
420
455
|
await initObservability();
|
|
421
456
|
const [command, ...rest] = positionals;
|
|
457
|
+
// `--config` with no value: parseArgs yields boolean true, which reached
|
|
458
|
+
// path.join and threw ERR_INVALID_ARG_TYPE as a raw Node stack trace.
|
|
459
|
+
if (values.config !== undefined && typeof values.config !== "string") {
|
|
460
|
+
throw new UsageError("--config needs a path, e.g. --config ./config.yaml");
|
|
461
|
+
}
|
|
422
462
|
const explicitConfigPath = values.config;
|
|
423
463
|
// If the caller didn't pass --config, fall back to ./config.yaml when it
|
|
424
464
|
// exists, rather than silently ignoring it and running pure auto-detect.
|
|
@@ -445,6 +485,7 @@ export async function main(argv) {
|
|
|
445
485
|
return cmdConfigure(configPath, explicitConfigPath, {
|
|
446
486
|
print: Boolean(values.print),
|
|
447
487
|
yes: Boolean(values.yes),
|
|
488
|
+
force: Boolean(values.force),
|
|
448
489
|
});
|
|
449
490
|
case "doctor":
|
|
450
491
|
return cmdDoctor(configPath, {
|
|
@@ -481,8 +522,23 @@ export async function main(argv) {
|
|
|
481
522
|
}
|
|
482
523
|
const entrypoint = typeof process !== "undefined" && Array.isArray(process.argv) ? process.argv[1] : "";
|
|
483
524
|
if (entrypoint && (entrypoint.endsWith("bin.ts") || entrypoint.endsWith("bin.js"))) {
|
|
484
|
-
void main(process.argv.slice(2))
|
|
525
|
+
void main(process.argv.slice(2))
|
|
526
|
+
.then((code) => {
|
|
485
527
|
process.exit(code);
|
|
528
|
+
})
|
|
529
|
+
.catch((err) => {
|
|
530
|
+
// A CLI user gets one actionable line, not a stack trace. Every Error is
|
|
531
|
+
// flattened to its message — UsageError and config-loading failures
|
|
532
|
+
// (missing file, bad YAML) are things the user typed and can fix, and
|
|
533
|
+
// the codebase throws user-facing Errors by convention, so there is no
|
|
534
|
+
// reliable way to tell "bug" from "bad input" by class here. Only a
|
|
535
|
+
// non-Error throw (a genuine programming error) keeps its stack.
|
|
536
|
+
if (err instanceof UsageError || err instanceof Error) {
|
|
537
|
+
process.stderr.write(`harness-dispatch: ${err.message}
|
|
538
|
+
`);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
throw err;
|
|
486
542
|
});
|
|
487
543
|
}
|
|
488
544
|
//# sourceMappingURL=bin.js.map
|