llm-relay 0.80.0 → 0.82.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 (97) hide show
  1. package/dist/backend.d.ts +43 -3
  2. package/dist/backend.js +0 -0
  3. package/dist/backend.js.map +1 -1
  4. package/dist/candidate-runner.d.ts +45 -9
  5. package/dist/candidate-runner.js +55 -22
  6. package/dist/candidate-runner.js.map +1 -1
  7. package/dist/catalog.d.ts +49 -0
  8. package/dist/catalog.js +18 -0
  9. package/dist/catalog.js.map +1 -1
  10. package/dist/circuit-breaker.d.ts +118 -1
  11. package/dist/circuit-breaker.js +96 -1
  12. package/dist/circuit-breaker.js.map +1 -1
  13. package/dist/cli.d.ts +3 -0
  14. package/dist/cli.js +23 -3
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config/routing-parser.d.ts +1 -1
  17. package/dist/config/routing-parser.js +27 -1
  18. package/dist/config/routing-parser.js.map +1 -1
  19. package/dist/config-edit.js +70 -16
  20. package/dist/config-edit.js.map +1 -1
  21. package/dist/config-types.d.ts +62 -6
  22. package/dist/config-types.js +1 -1
  23. package/dist/config-types.js.map +1 -1
  24. package/dist/dashboard/.vite/manifest.json +2 -2
  25. package/dist/dashboard/assets/index-B2UQHoym.js +74 -0
  26. package/dist/dashboard/assets/index-DFEP3XPr.css +1 -0
  27. package/dist/dashboard/index.html +2 -2
  28. package/dist/dispatch-lane-stats.d.ts +59 -5
  29. package/dist/dispatch-lane-stats.js +96 -29
  30. package/dist/dispatch-lane-stats.js.map +1 -1
  31. package/dist/dispatch.d.ts +143 -1
  32. package/dist/dispatch.js +260 -98
  33. package/dist/dispatch.js.map +1 -1
  34. package/dist/lane-affinity.d.ts +8 -0
  35. package/dist/lane-affinity.js +10 -0
  36. package/dist/lane-affinity.js.map +1 -1
  37. package/dist/log.d.ts +16 -0
  38. package/dist/log.js +1 -0
  39. package/dist/log.js.map +1 -1
  40. package/dist/mcp/agy-quota-log.d.ts +37 -0
  41. package/dist/mcp/agy-quota-log.js +68 -0
  42. package/dist/mcp/agy-quota-log.js.map +1 -0
  43. package/dist/mcp/job-archive.d.ts +55 -0
  44. package/dist/mcp/job-archive.js +151 -0
  45. package/dist/mcp/job-archive.js.map +1 -0
  46. package/dist/mcp/lane-runner.d.ts +108 -5
  47. package/dist/mcp/lane-runner.js +93 -4
  48. package/dist/mcp/lane-runner.js.map +1 -1
  49. package/dist/mcp/readonly-boundary.d.ts +46 -0
  50. package/dist/mcp/readonly-boundary.js +119 -0
  51. package/dist/mcp/readonly-boundary.js.map +1 -1
  52. package/dist/mcp/server.d.ts +132 -8
  53. package/dist/mcp/server.js +267 -68
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/openai-request.d.ts +8 -0
  56. package/dist/openai-request.js +33 -9
  57. package/dist/openai-request.js.map +1 -1
  58. package/dist/pacing.d.ts +125 -0
  59. package/dist/pacing.js +83 -0
  60. package/dist/pacing.js.map +1 -0
  61. package/dist/ping/cadence.d.ts +57 -0
  62. package/dist/ping/cadence.js +58 -0
  63. package/dist/ping/cadence.js.map +1 -1
  64. package/dist/refusal-interpretation.d.ts +24 -0
  65. package/dist/refusal-interpretation.js +10 -1
  66. package/dist/refusal-interpretation.js.map +1 -1
  67. package/dist/responses-request.d.ts +14 -1
  68. package/dist/responses-request.js +21 -4
  69. package/dist/responses-request.js.map +1 -1
  70. package/dist/routes/admin.d.ts +8 -0
  71. package/dist/routes/admin.js +122 -35
  72. package/dist/routes/admin.js.map +1 -1
  73. package/dist/routes/messages.d.ts +2 -0
  74. package/dist/routes/messages.js +7 -3
  75. package/dist/routes/messages.js.map +1 -1
  76. package/dist/routes/openai-front.d.ts +1 -0
  77. package/dist/routes/openai-front.js +7 -3
  78. package/dist/routes/openai-front.js.map +1 -1
  79. package/dist/server.d.ts +4 -0
  80. package/dist/server.js +28 -4
  81. package/dist/server.js.map +1 -1
  82. package/dist/setup-claude.d.ts +2 -2
  83. package/dist/setup-claude.js +3 -3
  84. package/dist/stream-commit.d.ts +65 -0
  85. package/dist/stream-commit.js +150 -8
  86. package/dist/stream-commit.js.map +1 -1
  87. package/dist/target-facts.js +3 -0
  88. package/dist/target-facts.js.map +1 -1
  89. package/dist/telemetry.d.ts +9 -1
  90. package/dist/telemetry.js +1 -1
  91. package/dist/telemetry.js.map +1 -1
  92. package/package.json +1 -1
  93. package/scripts/install-skill.mjs +2 -2
  94. package/skills/llm-relay/SKILL.md +5 -3
  95. package/skills/llm-relay/references/dispatch-lanes.md +8 -2
  96. package/dist/dashboard/assets/index-Bqtx14ES.css +0 -1
  97. package/dist/dashboard/assets/index-CetZdLCA.js +0 -74
@@ -22,9 +22,11 @@
22
22
  */
23
23
  import type { Config } from "../config.js";
24
24
  import type { DispatchView } from "../dispatch.js";
25
- import type { DispatchedTelemetryReport } from "../dispatch-lane-stats.js";
26
- import { type AnswerFetch, type LaneSpawner, type DispatchedQuotaReport } from "./lane-runner.js";
25
+ import type { DispatchedTelemetryReport, DispatchMode } from "../dispatch-lane-stats.js";
26
+ import { type AnswerFetch, type LaneJob, type LaneSpawner, type DispatchedQuotaReport } from "./lane-runner.js";
27
+ import { type AgyLogSnapshot } from "./agy-quota-log.js";
27
28
  import { type JobJournal } from "./job-journal.js";
29
+ import { type JobArchive } from "./job-archive.js";
28
30
  export declare const MCP_SERVER_NAME = "llm-relay";
29
31
  /** `max_tokens` for an answer-mode call when the caller names none. */
30
32
  export declare const DEFAULT_ANSWER_MAX_TOKENS = 4096;
@@ -37,6 +39,13 @@ export type DispatchViewBuilder = (opts: {
37
39
  task: string | undefined;
38
40
  tier: string | undefined;
39
41
  lane: string | undefined;
42
+ /**
43
+ * How this dispatch will run its lanes, so the relay hands back budgets measured in the SAME mode
44
+ * (`DispatchMode`). Absent from `dispatch_lanes`, which runs nothing.
45
+ */
46
+ mode?: DispatchMode | undefined;
47
+ /** A routing spec to run as its own one-lane view instead of the ladder (`dispatch`'s `model`). */
48
+ model?: string | undefined;
40
49
  }) => Promise<DispatchView>;
41
50
  export interface McpServerDeps {
42
51
  config: Config;
@@ -74,6 +83,24 @@ export interface McpServerDeps {
74
83
  * real one, so a host-launched server can report the jobs a previous instance died holding.
75
84
  */
76
85
  journal?: JobJournal;
86
+ /**
87
+ * The finished-job archive. Absent ⇒ `nullJobArchive`, which keeps nothing. `cli.ts` passes the
88
+ * real one, so a job that ENDED before a restart still answers `dispatch_result` afterwards and
89
+ * job ids continue past the highest one the previous process minted (`job-archive.ts`).
90
+ */
91
+ archive?: JobArchive;
92
+ /**
93
+ * The llm-relay version INSTALLED on disk now, read fresh on each call — or null when unknown.
94
+ * When it differs from `version` (the code this process started with), every tool reply says so:
95
+ * the Claude desktop app keeps one MCP process alive across sessions and releases, and on
96
+ * 2026-09-10 two processes from before v0.78.0 were still serving old code with no sign of it.
97
+ */
98
+ installedVersion?: () => string | null;
99
+ /**
100
+ * AGY's log, read after an AGY lane ends or is stopped, so a quota death AGY stated only in its
101
+ * log still reaches the relay (`agy-quota-log.ts`). Absent ⇒ never read.
102
+ */
103
+ readAgyLog?: () => AgyLogSnapshot | null;
77
104
  write: (chunk: string) => void;
78
105
  }
79
106
  /**
@@ -108,6 +135,16 @@ export type ResolvedWaitMs = {
108
135
  refusal: string;
109
136
  };
110
137
  export declare function resolveWaitMs(requested: unknown, ceiling: number): ResolvedWaitMs;
138
+ /**
139
+ * A RUNNING spawned attempt's output so far: how long the lane has been silent, or how much it has
140
+ * written and how long ago. Null for anything else — a finished job, or an answer-mode call, which
141
+ * has no output stream and must not read as "silent".
142
+ *
143
+ * ⚠ The zero-output line says why silence alone proves nothing: `claude -p` buffers its whole
144
+ * answer until exit. Without that, a caller reading "silent for 300 s" on the free pool would cancel
145
+ * a healthy run — the false failure the backlog item names as worse than an honest slow status.
146
+ */
147
+ export declare function describeActivity(job: LaneJob, now: number): string | null;
111
148
  /**
112
149
  * What a caller is told when the walk tried every lane it had and none of them answered.
113
150
  *
@@ -132,9 +169,24 @@ export declare const LANE_LADDER_EXHAUSTED_ADVICE: string;
132
169
  * (found by adversarial review, 2026-09-06). Both of its sentences were then false: lanes remained,
133
170
  * and "it would pick the same lanes" is wrong precisely because the walk has just DEMOTED every
134
171
  * lane it tried, so the next dispatch reorders around them. Telling an autonomous caller to stop
135
- * delegating, on a false premise, abandons free capacity that was never contacted.
172
+ * delegating, on a false premise, abandons capacity that was never contacted.
136
173
  */
137
174
  export declare const LANE_LADDER_PARTIAL_ADVICE: string;
175
+ /**
176
+ * What a caller is told when the walk STOPPED a lane at its time budget and nothing answered after.
177
+ *
178
+ * ⚠ The stopped lane did not fail: it was still working when the walk moved on. So "every dispatch
179
+ * lane has now been tried" was false there, and it ended the caller's use of dispatch for a task the
180
+ * stopped lane would have finished (`docs/dispatch-giveup-diagnosis-2026-09-10.md` §5). A NAMED lane
181
+ * runs with no budget, so this names the call that lets it finish.
182
+ */
183
+ export declare function laneStoppedAdvice(laneId: string): string;
184
+ /**
185
+ * What a caller is told when it NAMED the lane or the model and that one lane did not answer. Only
186
+ * that lane ran, so "every dispatch lane has now been tried" would be false — measured 2026-09-10 on
187
+ * jobs 0023 and 0024, which each ran one forced lane and were told to stop delegating.
188
+ */
189
+ export declare const FORCED_LANE_ADVICE: string;
138
190
  export declare class McpDispatchServer {
139
191
  private readonly deps;
140
192
  private readonly jobs;
@@ -173,18 +225,51 @@ export declare class McpDispatchServer {
173
225
  * takes the loop down. Resolves after the source ends AND every handler it started has settled.
174
226
  */
175
227
  serve(source: AsyncIterable<string>): Promise<void>;
176
- /** Kill every running child. Wired to process exit so nothing is orphaned. */
228
+ /**
229
+ * Kill every running child, then flush the finished-job archive. Wired to process exit so nothing
230
+ * is orphaned and nothing the archive still owes to disk is lost on a graceful stop (a hard kill
231
+ * is covered by the archive's eager terminal writes — `job-archive.ts`).
232
+ */
177
233
  shutdown(): void;
178
234
  private send;
179
235
  private handleLine;
180
236
  private route;
181
237
  private initialize;
182
238
  private callTool;
239
+ private runTool;
240
+ /**
241
+ * Append a notice to a tool reply when this process runs OLDER code than the version installed on
242
+ * disk. The Claude desktop app keeps one MCP process alive across sessions and releases, and on
243
+ * 2026-09-10 two processes from before v0.78.0 were still serving old code with nothing to say so.
244
+ * Only a plain one-block text reply is touched; anything else passes through unchanged. A version
245
+ * that cannot be read is unknown, and unknown adds nothing.
246
+ */
247
+ private withVersionNotice;
183
248
  private toolLanes;
184
249
  private toolStatus;
185
250
  private toolResult;
186
251
  private toolCancel;
187
252
  private toolDispatch;
253
+ /**
254
+ * The lanes this dispatch will try, best first, and how many selectable lanes it leaves untried.
255
+ *
256
+ * `view.order` is the ONE definition of selection order (`dispatch.ts`). With the walk turned off
257
+ * — or with a view that carries no order — this collapses to EXACTLY the pre-walk behaviour: the
258
+ * single lane the view named as `next`, tried once, with no attempt budget.
259
+ *
260
+ * ⚠ The `Array.isArray` test is a VERSION SKEW guard, not defensive noise. `buildView` reaches the
261
+ * running daemon over HTTP, and a daemon started before this field existed answers without it — an
262
+ * MCP child upgraded ahead of a long-running daemon is the normal state on a machine that starts
263
+ * the relay at logon and leaves it up for days. Reading `.length` off `undefined` there would throw
264
+ * on EVERY dispatch, turning a new optional field into a total outage.
265
+ *
266
+ * ⚠ A second skew guard: a daemon older than `requester=mcp` still offers a pass-through rung as
267
+ * ready, and this process can never run one (`mcpPassThroughReason`). Such a lane is dropped from an
268
+ * UNFORCED walk here; a caller who named it still reaches it and reads why it cannot run.
269
+ */
270
+ private walkOrder;
271
+ /** A pass-through relay lane — one this process cannot run (`walkOrder`). Never throws. */
272
+ private isPassThroughLane;
188
273
  /**
189
274
  * Try each lane in turn until one answers. THE JOB IS THE WALK.
190
275
  *
@@ -199,6 +284,16 @@ export declare class McpDispatchServer {
199
284
  * with it. Only a caller error ends the walk early — see `refusal` on `LaneAttemptOutcome`.
200
285
  */
201
286
  private runWalk;
287
+ /**
288
+ * Does the lane at position `i` run with NO attempt budget? The last lane does — there is nowhere
289
+ * to move to. So does a lane whose later lanes are all unlikely to answer: each is on a streak of
290
+ * `LANE_UNRELIABLE_STREAK` own failures or more, or marked `failing`. Stopping a lane that may
291
+ * still answer in order to reach those trades an answer for a near-certain failure — measured
292
+ * 2026-09-10, when the walk stopped `free-pool` at 90 s to try lanes that had answered 0 of 12,
293
+ * 0 of 34 and 0 of 21 runs (`docs/dispatch-giveup-diagnosis-2026-09-10.md` §1). A lane with no
294
+ * record counts as reliable: unmeasured is no opinion, never a failure.
295
+ */
296
+ private budgetWithheld;
202
297
  /**
203
298
  * A `cli` rung whose configured `maxConcurrent` this MCP server process has already reached for
204
299
  * — SKIP it for this walk rather than starting a competing process. Records the skip as an
@@ -211,6 +306,18 @@ export declare class McpDispatchServer {
211
306
  * always `null`, so this returns `false` for one without needing a `kind` check of its own.
212
307
  */
213
308
  private skipIfAtConcurrencyCap;
309
+ /**
310
+ * For a read-only dispatch, the invocation this lane will be spawned with — its own CLI's
311
+ * read-only tool binding (`readOnlyInvoke`) — or a SKIP when its CLI offers none. Recorded exactly
312
+ * like a concurrency-cap skip: an attempt in the walk's own record, `lanesNotTried` grown so the
313
+ * terminal advice is PARTIAL rather than EXHAUSTED, nothing spawned, no telemetry, no demotion —
314
+ * a lane the relay declined to run unbound has not failed.
315
+ *
316
+ * Not consulted at all for an ordinary dispatch, for an answer-mode relay call (no harness, so
317
+ * nothing to bind), or for a lane with no invocation (the existing "cannot be run from here" path
318
+ * reports that on its own).
319
+ */
320
+ private bindReadOnlyOrSkip;
214
321
  /**
215
322
  * Run ONE lane, optionally bounded by an attempt budget. Returns what happened; never throws.
216
323
  *
@@ -236,10 +343,27 @@ export declare class McpDispatchServer {
236
343
  */
237
344
  private startLane;
238
345
  /**
239
- * Forward one lane-execution report after an agent-mode job reaches a terminal state.
240
- * Never a RELAY-kind answer-mode job (the daemon's HTTP pipeline already accounts it —
241
- * finding F3); a `cli`-kind rung dispatched with `mode: "answer"` spawns like agent mode
242
- * and IS forwarded. A cancelled job is discarded, never reported.
346
+ * Settle a spawned lane's run. A quota death the lane states — in its own output, or for an AGY
347
+ * lane in AGY's own log — is reported to the relay; a content-empty answer is a failure.
348
+ *
349
+ * ⚠ It also runs for a lane the walk STOPPED: the killed child still settles here, so a death AGY
350
+ * stated only in its log reaches the relay although the walk has moved on. The outcome itself is
351
+ * discarded in that case — `runOneLane` already returned `abandoned`.
352
+ */
353
+ private settleSpawnedRun;
354
+ /**
355
+ * The quota death an AGY lane stated only in AGY's own log (`agy-quota-log.ts`). AGY retries a
356
+ * spent quota in silence, so a lane stopped by the walk or by its own timeout printed nothing, and
357
+ * the death reached the relay only when a run happened to last its whole length
358
+ * (`docs/dispatch-giveup-diagnosis-2026-09-10.md` §4). Undefined unless the lane is AGY, names its
359
+ * model, and the log is provably this run's — `agyQuotaStatement` refuses everything else.
360
+ */
361
+ private agyLogReport;
362
+ /**
363
+ * Forward one lane-execution report per ATTEMPT, for both lane kinds: the daemon records the
364
+ * lane's stats and routing memory from these, and skips the ledger row for a `relay` lane on its
365
+ * own because the HTTP pipeline already accounts it. Never for a cancelled job, and never for an
366
+ * ad-hoc lane (`DispatchLane.adHoc`), which the daemon has no rung to record against.
243
367
  *
244
368
  * Fire-and-forget by design: the reporter is never awaited on the response path, and a
245
369
  * throwing or rejecting reporter is swallowed after ONE metadata-only stderr line (lane id