harness-dispatch 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +228 -86
  2. package/config.default.yaml +416 -397
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +2 -2
  5. package/dist/auth.js.map +1 -1
  6. package/dist/billing.d.ts +35 -4
  7. package/dist/billing.d.ts.map +1 -1
  8. package/dist/billing.js +48 -17
  9. package/dist/billing.js.map +1 -1
  10. package/dist/bin.d.ts.map +1 -1
  11. package/dist/bin.js +253 -23
  12. package/dist/bin.js.map +1 -1
  13. package/dist/breaker-store.d.ts +60 -9
  14. package/dist/breaker-store.d.ts.map +1 -1
  15. package/dist/breaker-store.js +246 -50
  16. package/dist/breaker-store.js.map +1 -1
  17. package/dist/circuit-breaker.d.ts +35 -0
  18. package/dist/circuit-breaker.d.ts.map +1 -1
  19. package/dist/circuit-breaker.js +68 -11
  20. package/dist/circuit-breaker.js.map +1 -1
  21. package/dist/config/coercions.d.ts +31 -0
  22. package/dist/config/coercions.d.ts.map +1 -0
  23. package/dist/config/coercions.js +169 -0
  24. package/dist/config/coercions.js.map +1 -0
  25. package/dist/config/env-interpolation.d.ts +36 -0
  26. package/dist/config/env-interpolation.d.ts.map +1 -0
  27. package/dist/config/env-interpolation.js +73 -0
  28. package/dist/config/env-interpolation.js.map +1 -0
  29. package/dist/config/protocol.d.ts +51 -0
  30. package/dist/config/protocol.d.ts.map +1 -0
  31. package/dist/config/protocol.js +300 -0
  32. package/dist/config/protocol.js.map +1 -0
  33. package/dist/config/validation.d.ts +72 -0
  34. package/dist/config/validation.d.ts.map +1 -0
  35. package/dist/config/validation.js +189 -0
  36. package/dist/config/validation.js.map +1 -0
  37. package/dist/config.d.ts +19 -2
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +229 -511
  40. package/dist/config.js.map +1 -1
  41. package/dist/dispatch-log.d.ts.map +1 -1
  42. package/dist/dispatch-log.js +2 -3
  43. package/dist/dispatch-log.js.map +1 -1
  44. package/dist/dispatchers/generic-cli.d.ts +15 -1
  45. package/dist/dispatchers/generic-cli.d.ts.map +1 -1
  46. package/dist/dispatchers/generic-cli.js +65 -5
  47. package/dist/dispatchers/generic-cli.js.map +1 -1
  48. package/dist/dispatchers/openai-compatible.d.ts.map +1 -1
  49. package/dist/dispatchers/openai-compatible.js +52 -2
  50. package/dist/dispatchers/openai-compatible.js.map +1 -1
  51. package/dist/dispatchers/shared/kill-tree.d.ts +17 -4
  52. package/dist/dispatchers/shared/kill-tree.d.ts.map +1 -1
  53. package/dist/dispatchers/shared/kill-tree.js +28 -4
  54. package/dist/dispatchers/shared/kill-tree.js.map +1 -1
  55. package/dist/dispatchers/shared/rate-limit-headers.d.ts +17 -0
  56. package/dist/dispatchers/shared/rate-limit-headers.d.ts.map +1 -1
  57. package/dist/dispatchers/shared/rate-limit-headers.js +27 -0
  58. package/dist/dispatchers/shared/rate-limit-headers.js.map +1 -1
  59. package/dist/dispatchers/shared/stream-subprocess.d.ts.map +1 -1
  60. package/dist/dispatchers/shared/stream-subprocess.js +5 -0
  61. package/dist/dispatchers/shared/stream-subprocess.js.map +1 -1
  62. package/dist/dispatchers/shared/subprocess.d.ts.map +1 -1
  63. package/dist/dispatchers/shared/subprocess.js +3 -0
  64. package/dist/dispatchers/shared/subprocess.js.map +1 -1
  65. package/dist/dispatchers/shared/which-available.d.ts.map +1 -1
  66. package/dist/dispatchers/shared/which-available.js +16 -2
  67. package/dist/dispatchers/shared/which-available.js.map +1 -1
  68. package/dist/file-lock.d.ts +17 -0
  69. package/dist/file-lock.d.ts.map +1 -0
  70. package/dist/file-lock.js +143 -0
  71. package/dist/file-lock.js.map +1 -0
  72. package/dist/http/server.d.ts +11 -0
  73. package/dist/http/server.d.ts.map +1 -1
  74. package/dist/http/server.js +331 -162
  75. package/dist/http/server.js.map +1 -1
  76. package/dist/job-runner.js +23 -5
  77. package/dist/job-runner.js.map +1 -1
  78. package/dist/jobs.d.ts +91 -1
  79. package/dist/jobs.d.ts.map +1 -1
  80. package/dist/jobs.js +647 -36
  81. package/dist/jobs.js.map +1 -1
  82. package/dist/leaderboard.d.ts +24 -1
  83. package/dist/leaderboard.d.ts.map +1 -1
  84. package/dist/leaderboard.js +94 -2
  85. package/dist/leaderboard.js.map +1 -1
  86. package/dist/mcp/config-hot-reload.d.ts.map +1 -1
  87. package/dist/mcp/config-hot-reload.js +1 -1
  88. package/dist/mcp/config-hot-reload.js.map +1 -1
  89. package/dist/mcp/dispatcher-factory.d.ts +16 -1
  90. package/dist/mcp/dispatcher-factory.d.ts.map +1 -1
  91. package/dist/mcp/dispatcher-factory.js +41 -3
  92. package/dist/mcp/dispatcher-factory.js.map +1 -1
  93. package/dist/mcp/tools.d.ts +23 -4
  94. package/dist/mcp/tools.d.ts.map +1 -1
  95. package/dist/mcp/tools.js +163 -16
  96. package/dist/mcp/tools.js.map +1 -1
  97. package/dist/observability/index.d.ts +16 -3
  98. package/dist/observability/index.d.ts.map +1 -1
  99. package/dist/observability/index.js +23 -7
  100. package/dist/observability/index.js.map +1 -1
  101. package/dist/observability/spans.d.ts.map +1 -1
  102. package/dist/observability/spans.js +4 -1
  103. package/dist/observability/spans.js.map +1 -1
  104. package/dist/quota.d.ts +88 -22
  105. package/dist/quota.d.ts.map +1 -1
  106. package/dist/quota.js +163 -58
  107. package/dist/quota.js.map +1 -1
  108. package/dist/route-policy.d.ts.map +1 -1
  109. package/dist/route-policy.js +13 -3
  110. package/dist/route-policy.js.map +1 -1
  111. package/dist/router.d.ts +25 -1
  112. package/dist/router.d.ts.map +1 -1
  113. package/dist/router.js +94 -40
  114. package/dist/router.js.map +1 -1
  115. package/dist/safety.d.ts.map +1 -1
  116. package/dist/safety.js +11 -2
  117. package/dist/safety.js.map +1 -1
  118. package/dist/state-dir.d.ts +17 -0
  119. package/dist/state-dir.d.ts.map +1 -0
  120. package/dist/state-dir.js +21 -0
  121. package/dist/state-dir.js.map +1 -0
  122. package/dist/status.d.ts +36 -0
  123. package/dist/status.d.ts.map +1 -1
  124. package/dist/status.js +61 -4
  125. package/dist/status.js.map +1 -1
  126. package/dist/types.d.ts +50 -1
  127. package/dist/types.d.ts.map +1 -1
  128. package/dist/working-dir.d.ts +13 -0
  129. package/dist/working-dir.d.ts.map +1 -1
  130. package/dist/working-dir.js +27 -0
  131. package/dist/working-dir.js.map +1 -1
  132. package/dist/workspace-lock.d.ts +40 -0
  133. package/dist/workspace-lock.d.ts.map +1 -0
  134. package/dist/workspace-lock.js +258 -0
  135. package/dist/workspace-lock.js.map +1 -0
  136. package/dist/workspaces.d.ts +15 -0
  137. package/dist/workspaces.d.ts.map +1 -1
  138. package/dist/workspaces.js +104 -12
  139. package/dist/workspaces.js.map +1 -1
  140. package/package.json +98 -96
@@ -7,11 +7,37 @@
7
7
  */
8
8
  export const CIRCUIT_BREAKER_THRESHOLD = 5;
9
9
  export const CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC = 300;
10
+ /**
11
+ * Hard ceiling on any cooldown, in seconds (24h).
12
+ *
13
+ * Belt-and-braces alongside the clamp in rate-limit-headers.ts. That one
14
+ * guards the parser; this one guards the class, because `retryAfter` reaches
15
+ * trip()/recordFailure() from dispatcher code paths generally, not only from
16
+ * that parser — and because a cooldown is now written to disk and rehydrated
17
+ * by later processes, so a bad value stops being a per-process nuisance and
18
+ * becomes a permanent one.
19
+ */
20
+ export const MAX_COOLDOWN_SEC = 24 * 60 * 60;
21
+ /**
22
+ * Gap after which the consecutive-failure count restarts (30 min).
23
+ *
24
+ * Without this the counter only ever reset on success, so five failures
25
+ * separated by days tripped the breaker as readily as five in a burst — the
26
+ * opposite of what a breaker is for.
27
+ */
28
+ export const FAILURE_DECAY_SEC = 30 * 60;
29
+ function boundedCooldown(retryAfterSec) {
30
+ if (retryAfterSec === undefined || !Number.isFinite(retryAfterSec) || retryAfterSec <= 0) {
31
+ return CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC;
32
+ }
33
+ return Math.min(retryAfterSec, MAX_COOLDOWN_SEC);
34
+ }
10
35
  function monotonicSec() {
11
36
  return performance.now() / 1000;
12
37
  }
13
38
  export class CircuitBreaker {
14
39
  failures = 0;
40
+ lastFailureAt = null;
15
41
  trippedAt = null;
16
42
  cooldown = CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC;
17
43
  get isTripped() {
@@ -25,13 +51,19 @@ export class CircuitBreaker {
25
51
  return true;
26
52
  }
27
53
  recordFailure(retryAfterSec) {
54
+ // Decay first: five failures spread across a week are not the same signal
55
+ // as five in a row, but the counter never reset except on success, so a
56
+ // route that fails rarely eventually tripped anyway. Any gap longer than
57
+ // the decay window starts the count over.
58
+ const now = monotonicSec();
59
+ if (this.lastFailureAt !== null && now - this.lastFailureAt > FAILURE_DECAY_SEC) {
60
+ this.failures = 0;
61
+ }
62
+ this.lastFailureAt = now;
28
63
  this.failures += 1;
29
64
  if (this.failures >= CIRCUIT_BREAKER_THRESHOLD) {
30
65
  this.trippedAt = monotonicSec();
31
- this.cooldown =
32
- retryAfterSec && retryAfterSec > 0
33
- ? retryAfterSec
34
- : CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC;
66
+ this.cooldown = boundedCooldown(retryAfterSec);
35
67
  }
36
68
  }
37
69
  recordSuccess() {
@@ -40,10 +72,7 @@ export class CircuitBreaker {
40
72
  /** Immediately trip — use on 429 or explicit rate-limit response. */
41
73
  trip(retryAfterSec) {
42
74
  this.trippedAt = monotonicSec();
43
- this.cooldown =
44
- retryAfterSec && retryAfterSec > 0
45
- ? retryAfterSec
46
- : CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC;
75
+ this.cooldown = boundedCooldown(retryAfterSec);
47
76
  }
48
77
  cooldownRemaining() {
49
78
  if (!this.isTripped || this.trippedAt === null) {
@@ -69,10 +98,27 @@ export class CircuitBreaker {
69
98
  * a restart) can compare against its own Date.now().
70
99
  */
71
100
  snapshot() {
101
+ // Order matters: the isTripped getter auto-resets on expiry, which also
102
+ // clears lastFailureAt — evaluate it first so the snapshot reflects the
103
+ // post-expiry state.
72
104
  if (!this.isTripped) {
73
- return { failures: this.failures, blockedUntilMs: null };
105
+ return {
106
+ failures: this.failures,
107
+ blockedUntilMs: null,
108
+ lastFailureAtMs: this.lastFailureWallMs(),
109
+ };
74
110
  }
75
- return { failures: this.failures, blockedUntilMs: Date.now() + this.cooldownRemaining() * 1000 };
111
+ return {
112
+ failures: this.failures,
113
+ blockedUntilMs: Date.now() + this.cooldownRemaining() * 1000,
114
+ lastFailureAtMs: this.lastFailureWallMs(),
115
+ };
116
+ }
117
+ /** The monotonic lastFailureAt as a wall-clock epoch, for persistence. */
118
+ lastFailureWallMs() {
119
+ if (this.lastFailureAt === null)
120
+ return null;
121
+ return Date.now() - (monotonicSec() - this.lastFailureAt) * 1000;
76
122
  }
77
123
  /**
78
124
  * Hydrate from a snapshot persisted by a previous process. A
@@ -81,6 +127,13 @@ export class CircuitBreaker {
81
127
  */
82
128
  restore(snapshot) {
83
129
  this.failures = snapshot.failures;
130
+ // Rehydrate the failure clock so FAILURE_DECAY_SEC keeps working across
131
+ // processes (a failure age in the future, from clock skew, clamps to 0
132
+ // rather than extending the decay window).
133
+ this.lastFailureAt =
134
+ typeof snapshot.lastFailureAtMs === "number"
135
+ ? monotonicSec() - Math.max(0, (Date.now() - snapshot.lastFailureAtMs) / 1000)
136
+ : null;
84
137
  if (snapshot.blockedUntilMs === null) {
85
138
  this.trippedAt = null;
86
139
  return;
@@ -90,11 +143,15 @@ export class CircuitBreaker {
90
143
  this.trippedAt = null;
91
144
  return;
92
145
  }
93
- this.cooldown = remainingSec;
146
+ // Clamped on the way back in too: state written by an older build (or a
147
+ // hand-edited file) can carry a deadline centuries out, and hydrating it
148
+ // verbatim would make the ceiling above trivially bypassable.
149
+ this.cooldown = Math.min(remainingSec, MAX_COOLDOWN_SEC);
94
150
  this.trippedAt = monotonicSec();
95
151
  }
96
152
  reset() {
97
153
  this.failures = 0;
154
+ this.lastFailureAt = null;
98
155
  this.trippedAt = null;
99
156
  this.cooldown = CIRCUIT_BREAKER_DEFAULT_COOLDOWN_SEC;
100
157
  }
@@ -1 +1 @@
1
- {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Value coercion and enum narrowing for config parsing.
3
+ *
4
+ * Split out of config.ts, which reached 1738 lines with three parallel route
5
+ * builders — the shape that produced four separate silent-drop defects. These
6
+ * are the leaves of that parser: pure functions over `unknown`, no config
7
+ * state, no I/O, trivially testable in isolation.
8
+ *
9
+ * The convention throughout is DROP ON MISMATCH — every `*From` returns
10
+ * undefined rather than guessing, and the caller decides the fallback. That is
11
+ * deliberate, and it is also exactly why a typo could silently select a
12
+ * default. config.ts warns separately about unrecognised keys to cover it;
13
+ * the two belong together.
14
+ */
15
+ import type { AuthSource, BillingConfidence, BillingKind, BillingProvider, BillingSurface, EndpointMode, EndpointProvider, ThinkingLevel, WireProtocol, WorkspacePolicy } from "../types.js";
16
+ export declare function num(v: unknown, def: number): number;
17
+ export declare function int(v: unknown, def: number): number;
18
+ export declare function bool(v: unknown, def: boolean): boolean;
19
+ export declare function str(v: unknown): string | undefined;
20
+ export declare function thinkingFrom(v: unknown): ThinkingLevel | undefined;
21
+ export declare function providerFrom(v: unknown): BillingProvider | undefined;
22
+ export declare function surfaceFrom(v: unknown): BillingSurface | undefined;
23
+ export declare function authSourceFrom(v: unknown): AuthSource | undefined;
24
+ export declare function billingKindFrom(v: unknown): BillingKind | undefined;
25
+ export declare function confidenceFrom(v: unknown): BillingConfidence | undefined;
26
+ export declare function endpointModeFrom(v: unknown): EndpointMode | undefined;
27
+ export declare function endpointProviderFrom(v: unknown): EndpointProvider | undefined;
28
+ export declare function wireProtocolFrom(v: unknown): WireProtocol | undefined;
29
+ export declare function workspacePolicyFrom(v: unknown): WorkspacePolicy | undefined;
30
+ export declare function inferEndpointProvider(baseUrl: string | undefined): EndpointProvider;
31
+ //# sourceMappingURL=coercions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coercions.d.ts","sourceRoot":"","sources":["../../src/config/coercions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAOtD;AAED,wBAAgB,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAIlD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAGlE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAYpE;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAmBlE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAYjE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAcnE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAKxE;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CASrE;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,GAAG,gBAAgB,GAAG,SAAS,CAY7E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAWrE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAK3E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAmBnF"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Value coercion and enum narrowing for config parsing.
3
+ *
4
+ * Split out of config.ts, which reached 1738 lines with three parallel route
5
+ * builders — the shape that produced four separate silent-drop defects. These
6
+ * are the leaves of that parser: pure functions over `unknown`, no config
7
+ * state, no I/O, trivially testable in isolation.
8
+ *
9
+ * The convention throughout is DROP ON MISMATCH — every `*From` returns
10
+ * undefined rather than guessing, and the caller decides the fallback. That is
11
+ * deliberate, and it is also exactly why a typo could silently select a
12
+ * default. config.ts warns separately about unrecognised keys to cover it;
13
+ * the two belong together.
14
+ */
15
+ export function num(v, def) {
16
+ if (typeof v === "number")
17
+ return v;
18
+ if (typeof v === "string" && v !== "") {
19
+ const n = Number(v);
20
+ if (!Number.isNaN(n))
21
+ return n;
22
+ }
23
+ return def;
24
+ }
25
+ export function int(v, def) {
26
+ return Math.trunc(num(v, def));
27
+ }
28
+ export function bool(v, def) {
29
+ if (typeof v === "boolean")
30
+ return v;
31
+ if (typeof v === "string") {
32
+ if (v === "true")
33
+ return true;
34
+ if (v === "false")
35
+ return false;
36
+ }
37
+ return def;
38
+ }
39
+ export function str(v) {
40
+ if (typeof v !== "string")
41
+ return undefined;
42
+ if (v === "")
43
+ return undefined;
44
+ return v;
45
+ }
46
+ export function thinkingFrom(v) {
47
+ if (v === "low" || v === "medium" || v === "high")
48
+ return v;
49
+ return undefined;
50
+ }
51
+ export function providerFrom(v) {
52
+ if (v === "anthropic" ||
53
+ v === "openai" ||
54
+ v === "cursor" ||
55
+ v === "google" ||
56
+ v === "local" ||
57
+ v === "custom") {
58
+ return v;
59
+ }
60
+ return undefined;
61
+ }
62
+ export function surfaceFrom(v) {
63
+ if (v === "claude_code" ||
64
+ v === "claude_agent_sdk" ||
65
+ v === "anthropic_api" ||
66
+ v === "codex_cli" ||
67
+ v === "codex_sdk" ||
68
+ v === "openai_api" ||
69
+ v === "cursor_agent_cli" ||
70
+ v === "antigravity_cli" ||
71
+ v === "gemini_api" ||
72
+ v === "vertex_ai" ||
73
+ v === "openai_compatible" ||
74
+ v === "local_endpoint" ||
75
+ v === "custom") {
76
+ return v;
77
+ }
78
+ return undefined;
79
+ }
80
+ export function authSourceFrom(v) {
81
+ if (v === "product_login" ||
82
+ v === "api_key" ||
83
+ v === "oauth_session" ||
84
+ v === "local_network" ||
85
+ v === "configured_endpoint" ||
86
+ v === "unknown") {
87
+ return v;
88
+ }
89
+ return undefined;
90
+ }
91
+ export function billingKindFrom(v) {
92
+ if (v === "local_compute" ||
93
+ v === "included_plan_usage" ||
94
+ v === "included_plan_then_flexible_credits" ||
95
+ v === "included_credit_then_optional_overage" ||
96
+ v === "included_usage_then_on_demand" ||
97
+ v === "metered_api" ||
98
+ v === "free_quota" ||
99
+ v === "unknown") {
100
+ return v;
101
+ }
102
+ return undefined;
103
+ }
104
+ export function confidenceFrom(v) {
105
+ if (v === "documented" || v === "inferred" || v === "unknown" || v === "unsupported") {
106
+ return v;
107
+ }
108
+ return undefined;
109
+ }
110
+ export function endpointModeFrom(v) {
111
+ if (v === "provider_cloud" ||
112
+ v === "direct_openai_compatible" ||
113
+ v === "harness_native_endpoint") {
114
+ return v;
115
+ }
116
+ return undefined;
117
+ }
118
+ export function endpointProviderFrom(v) {
119
+ if (v === "ollama" ||
120
+ v === "lmstudio" ||
121
+ v === "openai_compatible" ||
122
+ v === "anthropic_gateway" ||
123
+ v === "gemini_proxy" ||
124
+ v === "custom") {
125
+ return v;
126
+ }
127
+ return undefined;
128
+ }
129
+ export function wireProtocolFrom(v) {
130
+ if (v === "openai_chat_completions" ||
131
+ v === "anthropic_messages" ||
132
+ v === "gemini_generate_content" ||
133
+ v === "provider_native" ||
134
+ v === "unknown") {
135
+ return v;
136
+ }
137
+ return undefined;
138
+ }
139
+ export function workspacePolicyFrom(v) {
140
+ if (v === "shared" || v === "shared_locked" || v === "git_worktree" || v === "copy") {
141
+ return v;
142
+ }
143
+ return undefined;
144
+ }
145
+ export function inferEndpointProvider(baseUrl) {
146
+ if (!baseUrl)
147
+ return "custom";
148
+ try {
149
+ const url = new URL(baseUrl);
150
+ const host = url.hostname.toLowerCase();
151
+ const port = url.port;
152
+ if ((host === "localhost" || host === "127.0.0.1" || host === "::1") && port === "11434") {
153
+ return "ollama";
154
+ }
155
+ if ((host === "localhost" || host === "127.0.0.1" || host === "::1") && port === "1234") {
156
+ return "lmstudio";
157
+ }
158
+ }
159
+ catch {
160
+ // Fall through to substring checks for partial or nonstandard URLs.
161
+ }
162
+ const lower = baseUrl.toLowerCase();
163
+ if (lower.includes("ollama"))
164
+ return "ollama";
165
+ if (lower.includes("lmstudio") || lower.includes("lm-studio"))
166
+ return "lmstudio";
167
+ return "custom";
168
+ }
169
+ //# sourceMappingURL=coercions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coercions.js","sourceRoot":"","sources":["../../src/config/coercions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `${VAR}` interpolation over a loaded config tree.
3
+ *
4
+ * Split out of config.ts. Small, but it carries two invariants worth keeping
5
+ * together and away from the parsing code:
6
+ *
7
+ * 1. It records every substitution it makes (`refs`), mapping the RESOLVED
8
+ * value back to the reference that produced it. That map is what lets
9
+ * `configure` write `${GROQ_API_KEY}` back out instead of the live secret
10
+ * it resolved to — without it, regenerating a config leaks credentials into
11
+ * a file people paste into bug reports.
12
+ * 2. It records references whose variable is unset (`unsetVars`), because the
13
+ * result is an empty string that otherwise looks exactly like "not
14
+ * configured" — a route silently losing its api_key and reporting ready.
15
+ */
16
+ export declare const ENV_VAR_RE: RegExp;
17
+ /**
18
+ * `unsetVars` collects the names of ${VAR} references that resolved to
19
+ * nothing because the env var isn't set at all — distinct from a var
20
+ * deliberately set to "". Without this, a typo'd or forgotten env var
21
+ * (${ANTHROPIC_API_KEY} when the real name is ${ANTHROPIC_KEY}) silently
22
+ * becomes an empty string: `str()` then drops the field entirely, a route
23
+ * that needed an api_key loses it with zero feedback, and doctor reports
24
+ * the route "ready" right up until the first real call 401s.
25
+ *
26
+ * Replacement applies ANYWHERE in the string, matching what the shipped
27
+ * config's comment has promised all along ("anywhere in a string value").
28
+ * The implementation was anchored to whole-string matches, so
29
+ * `base_url: https://${HOST}/v1` stayed a literal, got no unset-var warning
30
+ * (unsetVars only tracked whole-string matches), and failed downstream with
31
+ * an error about a URL containing a dollar sign.
32
+ */
33
+ export declare function interpolateEnv(value: string, unsetVars: Set<string>, refs: Map<string, string>): string;
34
+ /** Walk an object tree and replace any "${VAR}" string leaves with env values. */
35
+ export declare function interpolateTree<T>(node: T, unsetVars: Set<string>, refs: Map<string, string>): T;
36
+ //# sourceMappingURL=env-interpolation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-interpolation.d.ts","sourceRoot":"","sources":["../../src/config/env-interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,UAAU,QAAqC,CAAC;AAK7D;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACxB,MAAM,CAgBR;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAehG"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `${VAR}` interpolation over a loaded config tree.
3
+ *
4
+ * Split out of config.ts. Small, but it carries two invariants worth keeping
5
+ * together and away from the parsing code:
6
+ *
7
+ * 1. It records every substitution it makes (`refs`), mapping the RESOLVED
8
+ * value back to the reference that produced it. That map is what lets
9
+ * `configure` write `${GROQ_API_KEY}` back out instead of the live secret
10
+ * it resolved to — without it, regenerating a config leaks credentials into
11
+ * a file people paste into bug reports.
12
+ * 2. It records references whose variable is unset (`unsetVars`), because the
13
+ * result is an empty string that otherwise looks exactly like "not
14
+ * configured" — a route silently losing its api_key and reporting ready.
15
+ */
16
+ export const ENV_VAR_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
17
+ /** Every ${VAR} occurrence, not only a whole-string match. */
18
+ const ENV_VAR_ANYWHERE_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
19
+ /**
20
+ * `unsetVars` collects the names of ${VAR} references that resolved to
21
+ * nothing because the env var isn't set at all — distinct from a var
22
+ * deliberately set to "". Without this, a typo'd or forgotten env var
23
+ * (${ANTHROPIC_API_KEY} when the real name is ${ANTHROPIC_KEY}) silently
24
+ * becomes an empty string: `str()` then drops the field entirely, a route
25
+ * that needed an api_key loses it with zero feedback, and doctor reports
26
+ * the route "ready" right up until the first real call 401s.
27
+ *
28
+ * Replacement applies ANYWHERE in the string, matching what the shipped
29
+ * config's comment has promised all along ("anywhere in a string value").
30
+ * The implementation was anchored to whole-string matches, so
31
+ * `base_url: https://${HOST}/v1` stayed a literal, got no unset-var warning
32
+ * (unsetVars only tracked whole-string matches), and failed downstream with
33
+ * an error about a URL containing a dollar sign.
34
+ */
35
+ export function interpolateEnv(value, unsetVars, refs) {
36
+ if (!value.includes("${"))
37
+ return value;
38
+ const resolved = value.replace(ENV_VAR_ANYWHERE_RE, (_match, name) => {
39
+ if (!(name in process.env))
40
+ unsetVars.add(name);
41
+ return process.env[name] ?? "";
42
+ });
43
+ if (resolved === value)
44
+ return value; // nothing legal to substitute
45
+ // Remember which reference produced this value so `configure` can emit the
46
+ // ${VAR} template back instead of the secret it resolved to. Keyed by
47
+ // resolved value rather than by config path because interpolation runs over
48
+ // the raw tree before any of it is shaped into routes. Two variables holding
49
+ // the same secret collide, but harmlessly: either reference resolves to that
50
+ // same value, so emitting either is correct. Empty resolutions are skipped —
51
+ // they carry no secret and would collide with every other unset var.
52
+ if (resolved !== "")
53
+ refs.set(resolved, value);
54
+ return resolved;
55
+ }
56
+ /** Walk an object tree and replace any "${VAR}" string leaves with env values. */
57
+ export function interpolateTree(node, unsetVars, refs) {
58
+ if (typeof node === "string") {
59
+ return interpolateEnv(node, unsetVars, refs);
60
+ }
61
+ if (Array.isArray(node)) {
62
+ return node.map((v) => interpolateTree(v, unsetVars, refs));
63
+ }
64
+ if (node !== null && typeof node === "object") {
65
+ const out = {};
66
+ for (const [k, v] of Object.entries(node)) {
67
+ out[k] = interpolateTree(v, unsetVars, refs);
68
+ }
69
+ return out;
70
+ }
71
+ return node;
72
+ }
73
+ //# sourceMappingURL=env-interpolation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-interpolation.js","sourceRoot":"","sources":["../../src/config/env-interpolation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,kCAAkC,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,mBAAmB,GAAG,iCAAiC,CAAC;AAE9D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAa,EACb,SAAsB,EACtB,IAAyB;IAEzB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE;QAC3E,IAAI,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjC,CAAC,CAAC,CAAC;IACH,IAAI,QAAQ,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,8BAA8B;IACpE,2EAA2E;IAC3E,sEAAsE;IACtE,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,qEAAqE;IACrE,IAAI,QAAQ,KAAK,EAAE;QAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAI,IAAO,EAAE,SAAsB,EAAE,IAAyB;IAC3F,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAiB,CAAC;IAC/D,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAiB,CAAC;IAC9E,CAAC;IACD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;YACrE,GAAG,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,GAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Parsing for a route's `protocol:` block — how a CLI harness is actually
3
+ * invoked and how its output is read.
4
+ *
5
+ * Split out of config.ts. This is the largest single concern in that file and
6
+ * the most self-contained: it turns YAML into a CliProtocolConfig and knows
7
+ * nothing about routes, billing, or defaults merging.
8
+ *
9
+ * It is also the reason `harness: generic` works at all — every CLI harness,
10
+ * built-in or user-added, is driven by this structure rather than by
11
+ * TypeScript. That is why a malformed protocol block skips the whole entry
12
+ * with a warning instead of landing a route that would fail at dispatch time.
13
+ */
14
+ import type { CliEventRule, CliProtocolConfig } from "../types.js";
15
+ /**
16
+ * Named protocols a route may reference by string or `extends:`.
17
+ *
18
+ * INJECTED rather than imported. config.ts builds presets by calling
19
+ * this very function (via the shipped-harness loader), so importing it here
20
+ * would be an initialisation cycle — protocol.ts would need a value that does
21
+ * not exist until protocol.ts has finished running. Passing it in keeps the
22
+ * dependency pointing one way.
23
+ */
24
+ export type ProtocolPresets = Readonly<Record<string, CliProtocolConfig>>;
25
+ /** The full set of `{{name}}` tokens expandToken() in generic-cli.ts understands. */
26
+ export declare const KNOWN_ARG_PLACEHOLDERS: ReadonlySet<string>;
27
+ export declare function stringArrayFrom(raw: unknown): string[] | undefined;
28
+ /**
29
+ * Parse `protocol:` for any route (see CliProtocolConfig in types.ts).
30
+ * Three shapes:
31
+ * - A string ("cursor", "codex", ...) — looked up in presets by
32
+ * name. Lets a config select a known protocol without retyping it, and
33
+ * is the extension point for "others add further protocols" — a new
34
+ * named entry in the shipped config.default.yaml is immediately selectable here,
35
+ * no code changes.
36
+ * - An object with `extends: <preset name>` — starts from that preset and
37
+ * overrides only the fields present, for the common "95% the same, one
38
+ * flag different" case. safety merges per-profile (overriding just
39
+ * full_auto doesn't erase read_only/workspace_edit from the preset).
40
+ * - A plain object — the full protocol, no preset involved (unchanged
41
+ * behavior from before presets existed).
42
+ *
43
+ * Returns undefined — with a warning — for anything malformed, so a broken
44
+ * block degrades to "route unusable" (isAvailable() checks for a missing
45
+ * protocol) rather than a half-built dispatcher silently doing the wrong
46
+ * thing.
47
+ */
48
+ export declare function protocolFrom(raw: unknown, routeLabel: string, warnings: string[], presets: ProtocolPresets): CliProtocolConfig | undefined;
49
+ export declare function parseProtocolFields(r: Record<string, unknown>, routeLabel: string, warnings: string[], base: CliProtocolConfig | undefined): CliProtocolConfig | undefined;
50
+ export declare function eventRuleFrom(raw: unknown, label: string, warnings: string[]): CliEventRule | undefined;
51
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/config/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAGlB,MAAM,aAAa,CAAC;AAKrB;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE1E,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAOrD,CAAC;AAEH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAIlE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,EAKlB,OAAO,EAAE,eAAe,GACvB,iBAAiB,GAAG,SAAS,CA2B/B;AAED,wBAAgB,mBAAmB,CACjC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,EAAE,iBAAiB,GAAG,SAAS,GAClC,iBAAiB,GAAG,SAAS,CAmK/B;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,GAAG,SAAS,CA+CvG"}