harness-dispatch 0.8.0 → 0.10.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 +1074 -2
- package/README.md +92 -36
- package/config.default.yaml +19 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/billing.d.ts +24 -0
- package/dist/billing.d.ts.map +1 -1
- package/dist/billing.js +24 -0
- package/dist/billing.js.map +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +299 -48
- package/dist/bin.js.map +1 -1
- package/dist/circuit-breaker.d.ts +9 -1
- package/dist/circuit-breaker.d.ts.map +1 -1
- package/dist/circuit-breaker.js +15 -1
- package/dist/circuit-breaker.js.map +1 -1
- package/dist/client-register.d.ts +11 -1
- package/dist/client-register.d.ts.map +1 -1
- package/dist/client-register.js +15 -6
- package/dist/client-register.js.map +1 -1
- package/dist/config/coercions.d.ts +5 -0
- package/dist/config/coercions.d.ts.map +1 -1
- package/dist/config/coercions.js +8 -0
- package/dist/config/coercions.js.map +1 -1
- package/dist/config/protocol.js.map +1 -1
- package/dist/config/validation.d.ts +12 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +133 -7
- package/dist/config/validation.js.map +1 -1
- package/dist/config.d.ts +15 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +199 -152
- package/dist/config.js.map +1 -1
- package/dist/configure-yaml.d.ts +2 -0
- package/dist/configure-yaml.d.ts.map +1 -1
- package/dist/configure-yaml.js +114 -4
- package/dist/configure-yaml.js.map +1 -1
- package/dist/dispatch-log.d.ts.map +1 -1
- package/dist/dispatch-log.js +3 -1
- package/dist/dispatch-log.js.map +1 -1
- package/dist/dispatchers/generic-cli.d.ts.map +1 -1
- package/dist/dispatchers/generic-cli.js +281 -112
- package/dist/dispatchers/generic-cli.js.map +1 -1
- package/dist/dispatchers/openai-compatible.d.ts +0 -5
- package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
- package/dist/dispatchers/openai-compatible.js +150 -83
- package/dist/dispatchers/openai-compatible.js.map +1 -1
- package/dist/dispatchers/shared/harness-login.d.ts +25 -0
- package/dist/dispatchers/shared/harness-login.d.ts.map +1 -0
- package/dist/dispatchers/shared/harness-login.js +73 -0
- package/dist/dispatchers/shared/harness-login.js.map +1 -0
- package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
- package/dist/dispatchers/shared/stream-subprocess.js +71 -58
- package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
- package/dist/file-lock.d.ts +29 -1
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +53 -20
- package/dist/file-lock.js.map +1 -1
- package/dist/harness-presets.d.ts +51 -0
- package/dist/harness-presets.d.ts.map +1 -0
- package/dist/harness-presets.js +154 -0
- package/dist/harness-presets.js.map +1 -0
- package/dist/http/parse.d.ts +8 -1
- package/dist/http/parse.d.ts.map +1 -1
- package/dist/http/parse.js +10 -3
- package/dist/http/parse.js.map +1 -1
- package/dist/http/server.d.ts +2 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +188 -16
- package/dist/http/server.js.map +1 -1
- package/dist/job-runner.js +6 -0
- package/dist/job-runner.js.map +1 -1
- package/dist/jobs/context.d.ts +0 -7
- package/dist/jobs/context.d.ts.map +1 -1
- package/dist/jobs/context.js +95 -7
- package/dist/jobs/context.js.map +1 -1
- package/dist/jobs/lifecycle.d.ts +100 -0
- package/dist/jobs/lifecycle.d.ts.map +1 -0
- package/dist/jobs/lifecycle.js +263 -0
- package/dist/jobs/lifecycle.js.map +1 -0
- package/dist/jobs/read.d.ts +17 -0
- package/dist/jobs/read.d.ts.map +1 -0
- package/dist/jobs/read.js +103 -0
- package/dist/jobs/read.js.map +1 -0
- package/dist/jobs/run.d.ts +45 -0
- package/dist/jobs/run.d.ts.map +1 -0
- package/dist/jobs/run.js +351 -0
- package/dist/jobs/run.js.map +1 -0
- package/dist/jobs/start.d.ts +9 -0
- package/dist/jobs/start.d.ts.map +1 -0
- package/dist/jobs/start.js +126 -0
- package/dist/jobs/start.js.map +1 -0
- package/dist/jobs/store.d.ts.map +1 -1
- package/dist/jobs/store.js +13 -3
- package/dist/jobs/store.js.map +1 -1
- package/dist/jobs/supervisor.d.ts +151 -0
- package/dist/jobs/supervisor.d.ts.map +1 -0
- package/dist/jobs/supervisor.js +679 -0
- package/dist/jobs/supervisor.js.map +1 -0
- package/dist/jobs/types.d.ts +8 -1
- package/dist/jobs/types.d.ts.map +1 -1
- package/dist/jobs.d.ts +15 -218
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +14 -1317
- package/dist/jobs.js.map +1 -1
- package/dist/leaderboard.d.ts.map +1 -1
- package/dist/leaderboard.js +44 -28
- package/dist/leaderboard.js.map +1 -1
- package/dist/mcp/config-hot-reload.d.ts +14 -3
- package/dist/mcp/config-hot-reload.d.ts.map +1 -1
- package/dist/mcp/config-hot-reload.js +27 -6
- package/dist/mcp/config-hot-reload.js.map +1 -1
- package/dist/mcp/dispatcher-factory.d.ts.map +1 -1
- package/dist/mcp/dispatcher-factory.js +15 -4
- package/dist/mcp/dispatcher-factory.js.map +1 -1
- package/dist/mcp/resources.d.ts.map +1 -1
- package/dist/mcp/resources.js +3 -2
- package/dist/mcp/resources.js.map +1 -1
- package/dist/mcp/tool-schemas.d.ts +1 -1
- package/dist/mcp/tool-schemas.d.ts.map +1 -1
- package/dist/mcp/tool-schemas.js +6 -3
- package/dist/mcp/tool-schemas.js.map +1 -1
- package/dist/mcp/tools.d.ts +20 -6
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +92 -14
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp-clients.d.ts +9 -0
- package/dist/mcp-clients.d.ts.map +1 -1
- package/dist/mcp-clients.js +4 -0
- package/dist/mcp-clients.js.map +1 -1
- package/dist/observability/spans.d.ts.map +1 -1
- package/dist/observability/spans.js +10 -2
- package/dist/observability/spans.js.map +1 -1
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +16 -2
- package/dist/quota.js.map +1 -1
- package/dist/redaction.d.ts +90 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +221 -0
- package/dist/redaction.js.map +1 -0
- package/dist/route-policy.d.ts +2 -1
- package/dist/route-policy.d.ts.map +1 -1
- package/dist/route-policy.js +54 -0
- package/dist/route-policy.js.map +1 -1
- package/dist/router.d.ts +12 -23
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +86 -223
- package/dist/router.js.map +1 -1
- package/dist/safety.d.ts.map +1 -1
- package/dist/safety.js +7 -1
- package/dist/safety.js.map +1 -1
- package/dist/state-dir.d.ts +10 -0
- package/dist/state-dir.d.ts.map +1 -1
- package/dist/state-dir.js +27 -1
- package/dist/state-dir.js.map +1 -1
- package/dist/status.d.ts +14 -3
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +36 -3
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +24 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workspace-lock.d.ts +20 -0
- package/dist/workspace-lock.d.ts.map +1 -1
- package/dist/workspace-lock.js +63 -0
- package/dist/workspace-lock.js.map +1 -1
- package/dist/workspace-resolve.d.ts +0 -9
- package/dist/workspace-resolve.d.ts.map +1 -1
- package/dist/workspace-resolve.js +115 -183
- package/dist/workspace-resolve.js.map +1 -1
- package/dist/workspaces.d.ts +15 -0
- package/dist/workspaces.d.ts.map +1 -1
- package/dist/workspaces.js +330 -90
- package/dist/workspaces.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place that knows every secret this process holds, and one function that
|
|
3
|
+
* removes them from anything on its way out.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS, because the alternative was tried six times and failed six
|
|
6
|
+
* times. The same defect — a configured credential reaching a caller, a log
|
|
7
|
+
* file or an agent's context — was found and "fixed" in six consecutive
|
|
8
|
+
* rounds. Every fix was correct. Every fix was also incomplete, because each
|
|
9
|
+
* one scrubbed at a SITE: a specific error path in a specific file. Four of
|
|
10
|
+
* the six were found as "the fix landed in one branch and the sibling beside
|
|
11
|
+
* it kept leaking", twice inside a single file. One round shipped a comment
|
|
12
|
+
* asserting that every branch was covered while a branch was not.
|
|
13
|
+
*
|
|
14
|
+
* Sites are the wrong unit. There is no bounded list of places a string can be
|
|
15
|
+
* built, and a new one is added by ordinary feature work with no reason for
|
|
16
|
+
* its author to think about credentials. What IS bounded is the set of ways a
|
|
17
|
+
* string leaves this process: it is serialized to JSON for a tool result or an
|
|
18
|
+
* HTTP response, appended to the dispatch log, written into a job file, or
|
|
19
|
+
* printed to the terminal. Those are sinks, there are few of them, and they
|
|
20
|
+
* change rarely.
|
|
21
|
+
*
|
|
22
|
+
* So: scrub at the sinks, and derive the secrets from the loaded config rather
|
|
23
|
+
* than naming them per call site. A leak then requires someone to add a whole
|
|
24
|
+
* new egress mechanism, not merely to write a new error message.
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately more machinery than the individual fixes it replaces.
|
|
27
|
+
* It has earned that: it is not guarding against a hypothetical, it is
|
|
28
|
+
* guarding against six measured, reproduced disclosures, one of which put a
|
|
29
|
+
* key into a file on disk and another into an orchestrating agent's context.
|
|
30
|
+
*/
|
|
31
|
+
import type { RouterConfig } from "./types.js";
|
|
32
|
+
/** What a removed secret is replaced with. Matches `scrubEndpointSecrets`. */
|
|
33
|
+
export declare const REDACTED = "<redacted>";
|
|
34
|
+
/**
|
|
35
|
+
* Every secret value reachable from a loaded config.
|
|
36
|
+
*
|
|
37
|
+
* Collected from fields that HOLD credentials, and deliberately not from
|
|
38
|
+
* `envRefs`. Keying off `envRefs` was the obvious shortcut — it records every
|
|
39
|
+
* `${VAR}` in the file by resolved value — and it was wrong in a way that
|
|
40
|
+
* mattered: `${VAR}` is documented as legal in any string value, so a config
|
|
41
|
+
* writing `model: ${MY_MODEL}` or `command: ${CODEX_BIN}` turned a model name
|
|
42
|
+
* and a file path into process-wide redaction targets. An acceptance pass
|
|
43
|
+
* measured `model=<redacted>` in `status` output, and a harness answer
|
|
44
|
+
* mentioning that model would have been mangled the same way, silently.
|
|
45
|
+
*
|
|
46
|
+
* That failure is worse than the leak this guards against: a corrupted answer
|
|
47
|
+
* is wrong work product delivered as if it were right. A `${VAR}` in a
|
|
48
|
+
* credential field still resolves into `apiKey` before this runs, so nothing
|
|
49
|
+
* real is lost by dropping the shortcut.
|
|
50
|
+
*/
|
|
51
|
+
export declare function collectSecrets(config: RouterConfig | undefined): string[];
|
|
52
|
+
/** Remove every one of `secrets` from `text`, by value. */
|
|
53
|
+
export declare function scrubSecrets(text: string, secrets: readonly string[]): string;
|
|
54
|
+
/** Record a secret found while parsing, for redaction to pick up. */
|
|
55
|
+
export declare function registerSecretValue(value: string | undefined): void;
|
|
56
|
+
/** Install the secrets for a loaded config. Safe to call on every reload. */
|
|
57
|
+
export declare function setActiveSecrets(config: RouterConfig | undefined): void;
|
|
58
|
+
/** For tests that need to restore a clean slate. */
|
|
59
|
+
export declare function clearActiveSecrets(): void;
|
|
60
|
+
/** How many secrets are currently registered. For diagnostics and tests. */
|
|
61
|
+
export declare function activeSecretCount(): number;
|
|
62
|
+
/**
|
|
63
|
+
* Remove every registered secret from text that is about to leave the process.
|
|
64
|
+
*
|
|
65
|
+
* Call this at SINKS — serialization, disk writes, terminal output — not at
|
|
66
|
+
* the places strings are built. That distinction is the whole design.
|
|
67
|
+
*/
|
|
68
|
+
export declare function redact(text: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Redact everything written to stdout and stderr, for the life of the process.
|
|
71
|
+
*
|
|
72
|
+
* The terminal is a sink like any other, and it was the one left unwired when
|
|
73
|
+
* the JSON and disk sinks were done — measured immediately afterwards, with a
|
|
74
|
+
* path-embedded credential surviving into `status --json`, `usage` and
|
|
75
|
+
* `configure --print` while the same value was correctly removed from the MCP
|
|
76
|
+
* and HTTP payloads. That is the per-site failure this whole design exists to
|
|
77
|
+
* end, reproduced one more time by doing four sinks out of five.
|
|
78
|
+
*
|
|
79
|
+
* Installed once at the entrypoint rather than applied at each print, because
|
|
80
|
+
* `bin.ts` alone writes to stdout from dozens of places and every future
|
|
81
|
+
* command adds more. `redact` reads the registry at call time, so installing
|
|
82
|
+
* before config load is correct: writes made before any secret is known are
|
|
83
|
+
* unchanged, and everything after is covered with no ordering requirement.
|
|
84
|
+
*
|
|
85
|
+
* Non-string chunks pass through untouched — a Buffer write is not text this
|
|
86
|
+
* process composed, and decoding one to scan it would risk corrupting binary
|
|
87
|
+
* output for no benefit.
|
|
88
|
+
*/
|
|
89
|
+
export declare function installOutputRedaction(): void;
|
|
90
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,eAAe,CAAC;AA4BrC;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,EAAE,CAiCzE;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAc7E;AA+BD,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAInE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAGvE;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAY7C"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place that knows every secret this process holds, and one function that
|
|
3
|
+
* removes them from anything on its way out.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS, because the alternative was tried six times and failed six
|
|
6
|
+
* times. The same defect — a configured credential reaching a caller, a log
|
|
7
|
+
* file or an agent's context — was found and "fixed" in six consecutive
|
|
8
|
+
* rounds. Every fix was correct. Every fix was also incomplete, because each
|
|
9
|
+
* one scrubbed at a SITE: a specific error path in a specific file. Four of
|
|
10
|
+
* the six were found as "the fix landed in one branch and the sibling beside
|
|
11
|
+
* it kept leaking", twice inside a single file. One round shipped a comment
|
|
12
|
+
* asserting that every branch was covered while a branch was not.
|
|
13
|
+
*
|
|
14
|
+
* Sites are the wrong unit. There is no bounded list of places a string can be
|
|
15
|
+
* built, and a new one is added by ordinary feature work with no reason for
|
|
16
|
+
* its author to think about credentials. What IS bounded is the set of ways a
|
|
17
|
+
* string leaves this process: it is serialized to JSON for a tool result or an
|
|
18
|
+
* HTTP response, appended to the dispatch log, written into a job file, or
|
|
19
|
+
* printed to the terminal. Those are sinks, there are few of them, and they
|
|
20
|
+
* change rarely.
|
|
21
|
+
*
|
|
22
|
+
* So: scrub at the sinks, and derive the secrets from the loaded config rather
|
|
23
|
+
* than naming them per call site. A leak then requires someone to add a whole
|
|
24
|
+
* new egress mechanism, not merely to write a new error message.
|
|
25
|
+
*
|
|
26
|
+
* This is deliberately more machinery than the individual fixes it replaces.
|
|
27
|
+
* It has earned that: it is not guarding against a hypothetical, it is
|
|
28
|
+
* guarding against six measured, reproduced disclosures, one of which put a
|
|
29
|
+
* key into a file on disk and another into an orchestrating agent's context.
|
|
30
|
+
*/
|
|
31
|
+
/** What a removed secret is replaced with. Matches `scrubEndpointSecrets`. */
|
|
32
|
+
export const REDACTED = "<redacted>";
|
|
33
|
+
/**
|
|
34
|
+
* Below this length a "secret" is more likely to be a coincidence than a
|
|
35
|
+
* credential, and redacting it would corrupt ordinary output.
|
|
36
|
+
*
|
|
37
|
+
* A real key is far longer. The risk being traded here is asymmetric and worth
|
|
38
|
+
* stating: too low a bound mangles legitimate text (a route named `a` would
|
|
39
|
+
* turn every `a` in an answer into a placeholder), while too high a bound
|
|
40
|
+
* misses a short key. Eight is below every provider key format in this
|
|
41
|
+
* project's own config and far above the length at which a value collides with
|
|
42
|
+
* prose by accident.
|
|
43
|
+
*/
|
|
44
|
+
const MIN_SECRET_LENGTH = 8;
|
|
45
|
+
/**
|
|
46
|
+
* Query keys whose VALUE is a credential.
|
|
47
|
+
*
|
|
48
|
+
* A name test, not a length test. The length heuristic this replaces treated
|
|
49
|
+
* any URL path segment of 16+ characters as a secret, which made an Azure
|
|
50
|
+
* deployment name (`.../deployments/gpt-4-turbo-preview`) a redaction target —
|
|
51
|
+
* measured by an acceptance pass. A credential in the path is a real shape,
|
|
52
|
+
* but it cannot be told from a deployment name by inspection, and guessing
|
|
53
|
+
* wrong corrupts output. `warnCredentialInUrlPath` in config.ts says so out
|
|
54
|
+
* loud instead, which is actionable where a silent guess is not.
|
|
55
|
+
*/
|
|
56
|
+
const CREDENTIAL_QUERY_KEY = /^(api[_-]?key|key|token|access[_-]?token|auth|password|secret|sig|signature)$/i;
|
|
57
|
+
/**
|
|
58
|
+
* Every secret value reachable from a loaded config.
|
|
59
|
+
*
|
|
60
|
+
* Collected from fields that HOLD credentials, and deliberately not from
|
|
61
|
+
* `envRefs`. Keying off `envRefs` was the obvious shortcut — it records every
|
|
62
|
+
* `${VAR}` in the file by resolved value — and it was wrong in a way that
|
|
63
|
+
* mattered: `${VAR}` is documented as legal in any string value, so a config
|
|
64
|
+
* writing `model: ${MY_MODEL}` or `command: ${CODEX_BIN}` turned a model name
|
|
65
|
+
* and a file path into process-wide redaction targets. An acceptance pass
|
|
66
|
+
* measured `model=<redacted>` in `status` output, and a harness answer
|
|
67
|
+
* mentioning that model would have been mangled the same way, silently.
|
|
68
|
+
*
|
|
69
|
+
* That failure is worse than the leak this guards against: a corrupted answer
|
|
70
|
+
* is wrong work product delivered as if it were right. A `${VAR}` in a
|
|
71
|
+
* credential field still resolves into `apiKey` before this runs, so nothing
|
|
72
|
+
* real is lost by dropping the shortcut.
|
|
73
|
+
*/
|
|
74
|
+
export function collectSecrets(config) {
|
|
75
|
+
const out = new Set();
|
|
76
|
+
if (!config)
|
|
77
|
+
return [];
|
|
78
|
+
const add = (value) => {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return;
|
|
81
|
+
const trimmed = value.trim();
|
|
82
|
+
if (trimmed.length >= MIN_SECRET_LENGTH)
|
|
83
|
+
out.add(trimmed);
|
|
84
|
+
};
|
|
85
|
+
for (const svc of Object.values(config.services ?? {})) {
|
|
86
|
+
add(svc.apiKey);
|
|
87
|
+
if (svc.baseUrl === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
try {
|
|
90
|
+
const url = new URL(svc.baseUrl);
|
|
91
|
+
add(url.password);
|
|
92
|
+
add(url.username);
|
|
93
|
+
// Only query values under a credential-looking KEY. Taking every value
|
|
94
|
+
// meant `?model=gemini-2.5-flash` made that model name a redaction
|
|
95
|
+
// target — measured. A query string carries ordinary parameters too.
|
|
96
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
97
|
+
if (CREDENTIAL_QUERY_KEY.test(key))
|
|
98
|
+
add(value);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// An unparseable base_url has no parts to take apart. A credential
|
|
103
|
+
// written into one is still caught if it also appears as an api_key,
|
|
104
|
+
// which is how it is normally configured.
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Longest first, so a secret that contains another is removed whole rather
|
|
108
|
+
// than being left as a recognisable fragment around a placeholder.
|
|
109
|
+
return [...out].sort((a, b) => b.length - a.length);
|
|
110
|
+
}
|
|
111
|
+
/** Remove every one of `secrets` from `text`, by value. */
|
|
112
|
+
export function scrubSecrets(text, secrets) {
|
|
113
|
+
let out = text;
|
|
114
|
+
for (const secret of secrets) {
|
|
115
|
+
if (out.includes(secret))
|
|
116
|
+
out = out.split(secret).join(REDACTED);
|
|
117
|
+
// Redaction runs AFTER JSON.stringify at most sinks, so a secret holding a
|
|
118
|
+
// character JSON escapes — a quote, or the backslashes in a Windows path —
|
|
119
|
+
// is no longer present in its raw form and survived. Measured with a key
|
|
120
|
+
// containing a double quote.
|
|
121
|
+
const escaped = JSON.stringify(secret).slice(1, -1);
|
|
122
|
+
if (escaped !== secret && out.includes(escaped)) {
|
|
123
|
+
out = out.split(escaped).join(REDACTED);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The secrets of the config this process is currently running.
|
|
130
|
+
*
|
|
131
|
+
* Process-wide mutable state, which is normally a smell and is the right shape
|
|
132
|
+
* here: whether a given string is a credential is a property of the process,
|
|
133
|
+
* not of the call. Threading config into the dispatch log, the JSON
|
|
134
|
+
* serializers and the job-file writers is exactly the per-site plumbing whose
|
|
135
|
+
* absence caused six rounds of this bug — every one of those sinks would have
|
|
136
|
+
* needed a caller to remember to pass it.
|
|
137
|
+
*/
|
|
138
|
+
let activeSecrets = [];
|
|
139
|
+
/**
|
|
140
|
+
* Secrets seen while PARSING a config, which never become a route field.
|
|
141
|
+
*
|
|
142
|
+
* The top-level `api_keys:` block is the case that forced this. Its entries
|
|
143
|
+
* are applied to routes by name, so an entry whose route also declares an
|
|
144
|
+
* inline `api_key:` — or that names no route at all — is a live credential
|
|
145
|
+
* sitting in the config file that `collectSecrets` cannot see from the
|
|
146
|
+
* finished RouterConfig. A pass measured every entry after the first in that
|
|
147
|
+
* block leaking, and this is the machine setup this project documents.
|
|
148
|
+
*
|
|
149
|
+
* Accumulated rather than replaced on reload, deliberately: continuing to
|
|
150
|
+
* scrub a credential that has been removed from the config costs one
|
|
151
|
+
* unnecessary replacement in output that would not have contained it anyway,
|
|
152
|
+
* whereas forgetting one costs a disclosure.
|
|
153
|
+
*/
|
|
154
|
+
const parsedSecrets = new Set();
|
|
155
|
+
/** Record a secret found while parsing, for redaction to pick up. */
|
|
156
|
+
export function registerSecretValue(value) {
|
|
157
|
+
if (value === undefined)
|
|
158
|
+
return;
|
|
159
|
+
const trimmed = value.trim();
|
|
160
|
+
if (trimmed.length >= MIN_SECRET_LENGTH)
|
|
161
|
+
parsedSecrets.add(trimmed);
|
|
162
|
+
}
|
|
163
|
+
/** Install the secrets for a loaded config. Safe to call on every reload. */
|
|
164
|
+
export function setActiveSecrets(config) {
|
|
165
|
+
const merged = new Set([...collectSecrets(config), ...parsedSecrets]);
|
|
166
|
+
activeSecrets = [...merged].sort((a, b) => b.length - a.length);
|
|
167
|
+
}
|
|
168
|
+
/** For tests that need to restore a clean slate. */
|
|
169
|
+
export function clearActiveSecrets() {
|
|
170
|
+
activeSecrets = [];
|
|
171
|
+
parsedSecrets.clear();
|
|
172
|
+
}
|
|
173
|
+
/** How many secrets are currently registered. For diagnostics and tests. */
|
|
174
|
+
export function activeSecretCount() {
|
|
175
|
+
return activeSecrets.length;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Remove every registered secret from text that is about to leave the process.
|
|
179
|
+
*
|
|
180
|
+
* Call this at SINKS — serialization, disk writes, terminal output — not at
|
|
181
|
+
* the places strings are built. That distinction is the whole design.
|
|
182
|
+
*/
|
|
183
|
+
export function redact(text) {
|
|
184
|
+
if (activeSecrets.length === 0)
|
|
185
|
+
return text;
|
|
186
|
+
return scrubSecrets(text, activeSecrets);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Redact everything written to stdout and stderr, for the life of the process.
|
|
190
|
+
*
|
|
191
|
+
* The terminal is a sink like any other, and it was the one left unwired when
|
|
192
|
+
* the JSON and disk sinks were done — measured immediately afterwards, with a
|
|
193
|
+
* path-embedded credential surviving into `status --json`, `usage` and
|
|
194
|
+
* `configure --print` while the same value was correctly removed from the MCP
|
|
195
|
+
* and HTTP payloads. That is the per-site failure this whole design exists to
|
|
196
|
+
* end, reproduced one more time by doing four sinks out of five.
|
|
197
|
+
*
|
|
198
|
+
* Installed once at the entrypoint rather than applied at each print, because
|
|
199
|
+
* `bin.ts` alone writes to stdout from dozens of places and every future
|
|
200
|
+
* command adds more. `redact` reads the registry at call time, so installing
|
|
201
|
+
* before config load is correct: writes made before any secret is known are
|
|
202
|
+
* unchanged, and everything after is covered with no ordering requirement.
|
|
203
|
+
*
|
|
204
|
+
* Non-string chunks pass through untouched — a Buffer write is not text this
|
|
205
|
+
* process composed, and decoding one to scan it would risk corrupting binary
|
|
206
|
+
* output for no benefit.
|
|
207
|
+
*/
|
|
208
|
+
export function installOutputRedaction() {
|
|
209
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
210
|
+
const original = stream.write.bind(stream);
|
|
211
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
|
+
stream.write = ((chunk, ...rest) => {
|
|
213
|
+
if (typeof chunk === "string" && activeSecrets.length > 0) {
|
|
214
|
+
return original(redact(chunk), ...rest);
|
|
215
|
+
}
|
|
216
|
+
return original(chunk, ...rest);
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
|
package/dist/route-policy.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Dispatcher } from "./dispatchers/base.js";
|
|
2
|
-
import type { RouteBilling, RoutePolicy, RouteSkip, SafetyProfile, ServiceConfig } from "./types.js";
|
|
2
|
+
import type { RouteBilling, RoutePolicy, RouteSkip, SafetyProfile, ServiceConfig, TaskType } from "./types.js";
|
|
3
3
|
export interface RoutePolicyResult {
|
|
4
4
|
blocked: boolean;
|
|
5
5
|
skipped?: RouteSkip;
|
|
@@ -9,6 +9,7 @@ export declare function evaluateRoutePolicy(route: string, svc: ServiceConfig, o
|
|
|
9
9
|
circuitBroken?: boolean;
|
|
10
10
|
requestedSafetyProfile?: SafetyProfile;
|
|
11
11
|
routePolicy?: RoutePolicy;
|
|
12
|
+
taskType?: TaskType;
|
|
12
13
|
}): RoutePolicyResult;
|
|
13
14
|
/**
|
|
14
15
|
* Scoring penalty applied to nudge route selection toward cheaper options
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-policy.d.ts","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,aAAa,EACb,aAAa,
|
|
1
|
+
{"version":3,"file":"route-policy.d.ts","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,aAAa,EACb,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,aAAa,EAClB,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CAChB,GACL,iBAAiB,CA2InB;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAI1E;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAO3D"}
|
package/dist/route-policy.js
CHANGED
|
@@ -26,6 +26,22 @@ export function evaluateRoutePolicy(route, svc, opts = {}) {
|
|
|
26
26
|
// about a route `status` prints as `billing=metered_api` — the kind is
|
|
27
27
|
// known perfectly well; what is unknown is how sure we are of it. An
|
|
28
28
|
// acceptance pass caught the two surfaces contradicting each other.
|
|
29
|
+
// A KNOWN kind that has declared it cannot bill you gets the remedy that
|
|
30
|
+
// works, and it has to be given HERE.
|
|
31
|
+
//
|
|
32
|
+
// The same advice was added to the `billingIsBlocked` branch below and was
|
|
33
|
+
// unreachable for any non-included kind, because this branch catches those
|
|
34
|
+
// first — so a `metered_api` route with `paid_usage_possible: false` was
|
|
35
|
+
// still told to add `allow_paid_usage: true`, which is "yes, bill me" for
|
|
36
|
+
// a route the operator has said cannot. The fix that was supposed to stop
|
|
37
|
+
// that was one branch too late.
|
|
38
|
+
if (billing.kind !== "unknown" && !billing.paidUsagePossible) {
|
|
39
|
+
return skip(route, "unknown_billing", `billing_confidence is \`unknown\` for '${route}', which blocks it even though its ` +
|
|
40
|
+
`billing kind (${billing.kind}) is declared and \`paid_usage_possible\` is false. ` +
|
|
41
|
+
`That is what \`billing_confidence: unknown\` means — "do not trust this ` +
|
|
42
|
+
`classification". Set it to the value you actually believe (\`documented\` or ` +
|
|
43
|
+
`\`inferred\`); do NOT allow paid usage on a route you have said cannot bill you.`);
|
|
44
|
+
}
|
|
29
45
|
const why = billing.kind === "unknown"
|
|
30
46
|
? "billing source is unknown"
|
|
31
47
|
: `billing is recorded as ${billing.kind} but its confidence is unknown`;
|
|
@@ -34,6 +50,25 @@ export function evaluateRoutePolicy(route, svc, opts = {}) {
|
|
|
34
50
|
`to '${route}' in config.yaml to enable it`);
|
|
35
51
|
}
|
|
36
52
|
if (billingIsBlocked(billing)) {
|
|
53
|
+
// THREE conditions reach `billingIsBlocked`, and the message below fitted
|
|
54
|
+
// only two of them.
|
|
55
|
+
//
|
|
56
|
+
// A route with `paid_usage_possible: false` and a KNOWN kind is blocked
|
|
57
|
+
// solely because its `billing_confidence` is `unknown` — a deliberate
|
|
58
|
+
// operator signal meaning "I do not trust this classification", kept on
|
|
59
|
+
// purpose. But it was told "route can incur paid usage" (contradicting
|
|
60
|
+
// `paid=no` on the same screen) and handed `paid_usage_possible: false` as
|
|
61
|
+
// the remedy, which it already had. An acceptance pass measured the
|
|
62
|
+
// recommended fix producing byte-identical output: the only working escape
|
|
63
|
+
// was `allow_paid_usage: true` — "yes, bill me" — for a free local model.
|
|
64
|
+
if (billing.kind !== "unknown" && !billing.paidUsagePossible) {
|
|
65
|
+
return skip(route, "paid_blocked", `billing_confidence is \`unknown\` for '${route}', which blocks it even though ` +
|
|
66
|
+
`its billing kind (${billing.kind}) is declared and \`paid_usage_possible\` is ` +
|
|
67
|
+
`false. That is what \`billing_confidence: unknown\` means — "do not trust this ` +
|
|
68
|
+
`classification" — so the fix is to set it to the value you actually believe ` +
|
|
69
|
+
`(\`documented\` or \`inferred\`), NOT to allow paid usage on a route you have ` +
|
|
70
|
+
`said cannot bill you.`);
|
|
71
|
+
}
|
|
37
72
|
return skip(route, "paid_blocked",
|
|
38
73
|
// Two different fixes, and recommending only the permissive one is a
|
|
39
74
|
// steer in the wrong direction. If the route genuinely CANNOT cost money
|
|
@@ -48,6 +83,25 @@ export function evaluateRoutePolicy(route, svc, opts = {}) {
|
|
|
48
83
|
`route already covered by a subscription — the correct fix is ` +
|
|
49
84
|
`\`paid_usage_possible: false\` instead.`);
|
|
50
85
|
}
|
|
86
|
+
// An HTTP endpoint cannot execute, structurally: no agent loop, no file
|
|
87
|
+
// access, no shell. PRODUCT.md states this as design rather than gap — and
|
|
88
|
+
// routing still sent `execute` work to one, because an undeclared capability
|
|
89
|
+
// defaults to 1.0 and NO endpoint example in config.default.yaml declares
|
|
90
|
+
// any. An acceptance pass measured a `--task-type execute` dispatch routed to
|
|
91
|
+
// an endpoint returning prose and exit 0: execution reported as succeeded
|
|
92
|
+
// when none happened. It surfaces exactly when the CLI routes are busy or
|
|
93
|
+
// tripped — the degraded case the caller is least able to check.
|
|
94
|
+
//
|
|
95
|
+
// A REFUSAL rather than a capability score, for two reasons. A score of 0
|
|
96
|
+
// still leaves the route selectable when it is the only candidate, which is
|
|
97
|
+
// the failing case itself. And declared capabilities must not be able to
|
|
98
|
+
// override it: this is the same rule as the safety-flag check below — a
|
|
99
|
+
// declaration cannot conjure an ability the route does not have.
|
|
100
|
+
if (opts.taskType === "execute" && svc.type === "openai_compatible") {
|
|
101
|
+
return skip(route, "cannot_execute", "this is an HTTP model endpoint — it has no agent loop, no file access and no shell, " +
|
|
102
|
+
"so it cannot carry out an `execute` task. Endpoint routes serve plan, review and " +
|
|
103
|
+
"second opinions. Use a CLI route for execution, or send this as taskType: plan/review.");
|
|
104
|
+
}
|
|
51
105
|
if (!safetyProfileCompatible(svc, opts.requestedSafetyProfile)) {
|
|
52
106
|
return skip(route, "safety_incompatible", `effective safety ${effectiveSafetyProfile(svc, opts.requestedSafetyProfile)} exceeds requested safety`);
|
|
53
107
|
}
|
package/dist/route-policy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-policy.js","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route-policy.js","sourceRoot":"","sources":["../src/route-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAe9E,MAAM,UAAU,mBAAmB,CACjC,KAAa,EACb,GAAkB,EAClB,OAMI,EAAE;IAEN,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,mBAAmB,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,KAAK,EAAE,eAAe,EAAE,4CAA4C,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,6CAA6C,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAAE,+BAA+B,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,8BAA8B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACrF,IAAI,WAAW,CAAC,OAAO;QAAE,OAAO,WAAW,CAAC;IAE5C,IACE,gBAAgB,CAAC,OAAO,CAAC;QACzB,CAAC,sBAAsB,CAAC,OAAO,CAAC;QAChC,CAAC,OAAO,CAAC,cAAc,EACvB,CAAC;QACD,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,uEAAuE;QACvE,qEAAqE;QACrE,oEAAoE;QACpE,yEAAyE;QACzE,sCAAsC;QACtC,EAAE;QACF,2EAA2E;QAC3E,2EAA2E;QAC3E,yEAAyE;QACzE,0EAA0E;QAC1E,0EAA0E;QAC1E,gCAAgC;QAChC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC7D,OAAO,IAAI,CACT,KAAK,EACL,iBAAiB,EACjB,0CAA0C,KAAK,qCAAqC;gBAClF,iBAAiB,OAAO,CAAC,IAAI,sDAAsD;gBACnF,0EAA0E;gBAC1E,+EAA+E;gBAC/E,kFAAkF,CACrF,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GACP,OAAO,CAAC,IAAI,KAAK,SAAS;YACxB,CAAC,CAAC,2BAA2B;YAC7B,CAAC,CAAC,0BAA0B,OAAO,CAAC,IAAI,gCAAgC,CAAC;QAC7E,OAAO,IAAI,CACT,KAAK,EACL,iBAAiB,EACjB,GAAG,GAAG,0DAA0D;YAC9D,uFAAuF;YACvF,OAAO,KAAK,+BAA+B,CAC9C,CAAC;IACJ,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,oBAAoB;QACpB,EAAE;QACF,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,uEAAuE;QACvE,2EAA2E;QAC3E,oEAAoE;QACpE,2EAA2E;QAC3E,0EAA0E;QAC1E,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC7D,OAAO,IAAI,CACT,KAAK,EACL,cAAc,EACd,0CAA0C,KAAK,iCAAiC;gBAC9E,qBAAqB,OAAO,CAAC,IAAI,+CAA+C;gBAChF,iFAAiF;gBACjF,8EAA8E;gBAC9E,gFAAgF;gBAChF,uBAAuB,CAC1B,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CACT,KAAK,EACL,cAAc;QACd,qEAAqE;QACrE,yEAAyE;QACzE,+DAA+D;QAC/D,0EAA0E;QAC1E,yEAAyE;QACzE,2DAA2D;QAC3D,oFAAoF;YAClF,2CAA2C,KAAK,6BAA6B;YAC7E,0DAA0D;YAC1D,qFAAqF;YACrF,+DAA+D;YAC/D,yCAAyC,CAC5C,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,6EAA6E;IAC7E,0EAA0E;IAC1E,8EAA8E;IAC9E,0EAA0E;IAC1E,0EAA0E;IAC1E,iEAAiE;IACjE,EAAE;IACF,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,wEAAwE;IACxE,iEAAiE;IACjE,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;QACpE,OAAO,IAAI,CACT,KAAK,EACL,gBAAgB,EAChB,sFAAsF;YACpF,mFAAmF;YACnF,wFAAwF,CAC3F,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC,sBAAsB,CAAC,EAAE,CAAC;QAC/D,OAAO,IAAI,CACT,KAAK,EACL,qBAAqB,EACrB,oBAAoB,sBAAsB,CACxC,GAAG,EACH,IAAI,CAAC,sBAAsB,CAC5B,2BAA2B,CAC7B,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,4BAA4B,CAAC,OAAqB;IAChE,IAAI,YAAY,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,sBAAsB,CAAC,OAAO,CAAC;QAAE,OAAO,GAAG,CAAC;IAChD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,8BAA8B,CACrC,KAAa,EACb,OAAqB,EACrB,WAAoC;IAEpC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,IAAI,CACT,KAAK,EACL,cAAc,EACd,qFAAqF;YACnF,iFAAiF;YACjF,+CAA+C,CAClD,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,KAAK,YAAY,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3D,OAAO,IAAI,CACT,KAAK,EACL,cAAc,EACd,8EAA8E;YAC5E,iFAAiF;YACjF,6DAA6D,CAChE,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,KAAK,mBAAmB,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CACT,KAAK,EACL,mBAAmB,EACnB,qFAAqF;YACnF,iFAAiF;YACjF,6DAA6D,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,OAAO,CACL,OAAO,CAAC,IAAI,KAAK,eAAe;QAChC,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC5B,OAAO,CAAC,OAAO,KAAK,gBAAgB;QACpC,OAAO,CAAC,UAAU,KAAK,eAAe,CACvC,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAAC,OAAqB;IACnD,OAAO,CACL,YAAY,CAAC,OAAO,CAAC;QACrB,OAAO,CAAC,IAAI,KAAK,YAAY;QAC7B,OAAO,CAAC,IAAI,KAAK,qBAAqB;QACtC,OAAO,CAAC,IAAI,KAAK,qCAAqC;QACtD,OAAO,CAAC,IAAI,KAAK,uCAAuC;QACxD,OAAO,CAAC,IAAI,KAAK,+BAA+B,CACjD,CAAC;AACJ,CAAC;AAED,SAAS,IAAI,CAAC,KAAa,EAAE,IAAuB,EAAE,OAAe;IACnE,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE;KAClC,CAAC;AACJ,CAAC"}
|
package/dist/router.d.ts
CHANGED
|
@@ -55,8 +55,18 @@
|
|
|
55
55
|
* Explicit `tier` in config is the fallback when ELO is unavailable.
|
|
56
56
|
*
|
|
57
57
|
* R3: adds `stream()` / `streamTo()` that emit `DispatcherEvent`s with an
|
|
58
|
-
* attached `RoutingDecision`.
|
|
59
|
-
*
|
|
58
|
+
* attached `RoutingDecision`.
|
|
59
|
+
*
|
|
60
|
+
* There is ONE selection-and-fallback loop per shape — `#runStream` for the
|
|
61
|
+
* routed path, `#runStreamTo` for an explicit route — and the buffered
|
|
62
|
+
* `route()` / `routeTo()` drain them. This paragraph used to claim exactly
|
|
63
|
+
* that while it was false: the buffered methods were separate 120-line
|
|
64
|
+
* implementations, and the copies drifted. The timeout rule ended up written
|
|
65
|
+
* four times, and an audit mutation showed a test pinned only the `route()`
|
|
66
|
+
* copy, leaving the pair that jobs and the HTTP server actually use unguarded.
|
|
67
|
+
*
|
|
68
|
+
* What still differs between buffered and streaming is the dispatcher call
|
|
69
|
+
* itself, and only that: see `DispatcherInvoke`.
|
|
60
70
|
*/
|
|
61
71
|
import type { DispatchResult, DispatcherEvent, RouterConfig, RoutingDecision, RouteHints, RouteSkip, SafetyProfile, ServiceConfig, TaskType } from "./types.js";
|
|
62
72
|
import { CircuitBreaker } from "./circuit-breaker.js";
|
|
@@ -120,27 +130,6 @@ export interface ExplicitDispatchOpts {
|
|
|
120
130
|
*/
|
|
121
131
|
defaultTimeoutMs?: number;
|
|
122
132
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Every hint that means something to an explicitly-chosen route, in one place.
|
|
125
|
-
*
|
|
126
|
-
* The HTTP fanout path built these options by naming three fields inline and
|
|
127
|
-
* missed two: `routePolicy` never reached `routeTo`, so
|
|
128
|
-
* `{"mode":"fanout","hints":{"routePolicy":"blocked"}}` — documented as
|
|
129
|
-
* "dry-run: block everything" — returned 200 having run a live agent in the
|
|
130
|
-
* caller's working tree, and `local_only` let a metered route run. `timeoutMs`
|
|
131
|
-
* was dropped the same way, so the bound the release notes describe never
|
|
132
|
-
* reached the child in fanout mode. Single mode enforced both correctly, so
|
|
133
|
-
* one surface disagreed with itself.
|
|
134
|
-
*
|
|
135
|
-
* Picking fields by name is what allowed that: nothing fails when a new hint
|
|
136
|
-
* is added and a call site is not updated. Adding one here reaches every
|
|
137
|
-
* caller, and `ExplicitDispatchOpts` is the compiler's list of what belongs.
|
|
138
|
-
*
|
|
139
|
-
* `model` is deliberately absent — fanout selects routes with `models`, and
|
|
140
|
-
* each arm runs its own. `preferLargeContext` and `service` only steer which
|
|
141
|
-
* route is PICKED, which an explicit route has already decided.
|
|
142
|
-
*/
|
|
143
|
-
export declare function explicitOptsFromHints(hints: RouteHints): ExplicitDispatchOpts;
|
|
144
133
|
/**
|
|
145
134
|
* Does this route actually DECLARE this model? The route's own name does not
|
|
146
135
|
* count.
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,YAAY,EACZ,eAAe,EACf,UAAU,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAA+B,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAsB9D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO;IAClB,kEAAkE;;IAElE,uEAAuE;;IAEvE,uEAAuE;;;;IAIvE,6DAA6D;;CAErD,CAAC;AAYX;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,eAAe,CAAC,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACnD,WAAW,CAAC,EAAE,OAAO,YAAY,EAAE,WAAW,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAOpF;AA4MD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;CAClC;AAYD,qBAAa,MAAM;;IAMf,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,iBAAiB,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;gBAGzB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,EACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACvC,WAAW,EAAE,gBAAgB,EAC9C,YAAY,CAAC,EAAE,YAAY;IAe7B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAIvD,aAAa,IAAI,SAAS,EAAE;IAI5B;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAuBtB,WAAW,CAAC,IAAI,GAAE;QACtB,KAAK,CAAC,EAAE,UAAU,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;KAClB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA+SxC;;;;;;;;OAQG;IACH,MAAM,CACJ,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACxG,aAAa,CAAC,iBAAiB,CAAC;IAyHnC;;;OAGG;IACH,QAAQ,CACN,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,oBAAyB,GAC9B,aAAa,CAAC,iBAAiB,CAAC;IAyInC;;;;;;;;;;;OAWG;IACG,KAAK,CACT,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,UAAU,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAC7E,OAAO,CAAC;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;KAAE,CAAC;IA0DxE;;OAEG;IACG,OAAO,CACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC;QAAE,MAAM,EAAE,cAAc,CAAC;QAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAA;KAAE,CAAC;IA0BxE,OAAO,CAAC,YAAY;IAsDpB;;;;;;;;;;;;;;;OAeG;IACH,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IAO5E;;;;;OAKG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,wBAAwB;CAYjC;AAED,OAAO,EAAE,qBAAqB,EAAE,CAAC"}
|