@dylanrussell/agent-router 2.0.1 → 2.1.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 CHANGED
@@ -1,3 +1,17 @@
1
+ # 2.1.0
2
+
3
+ - Add opt-in account-scoped quota preflight before automatic main turns and new subagent tasks, using the usage service's credential-free RPC.
4
+ - Skip only fresh confirmed exhausted candidates; unknown quota keeps the primary. Paid fallback requires explicit approval.
5
+ - Add session-scoped `router_pin`, `router_auto`, and `router_routing_status` controls. Pins survive restarts with bounded storage and deletion cleanup.
6
+ - Preserve staged next-turn reactive fallback precedence, manual overrides, and completed tools. No automatic mid-task retry is enabled.
7
+ - Verify native main/child admission, quota recovery, durable pinning, timeout handling, and concurrent admission/disposal guards.
8
+
9
+ # 2.0.2
10
+
11
+ - Simplify stack chains to one model per line in precedence order, without Primary/Fallback labels.
12
+ - Highlight the live session's selected agent/model with native theme color, bold text and a marker; show out-of-chain selections separately.
13
+ - React to session model/agent changes, normalize default variants, and wrap full provider/model identities in narrow sidebars.
14
+
1
15
  # 2.0.1
2
16
 
3
17
  - Normalize V2's `default` variant so pending next-user-turn fallback is not disabled by an equivalent model reference.
@@ -21,6 +35,6 @@
21
35
 
22
36
  - Applying/backing out stacks still requires restarting OpenCode to reload agent files.
23
37
  - Terminal stack operations access local files; remote-server filesystem management is not supported.
24
- - Fallback notices are server logs. The sidebar shows configured routing, not session-local fallback state.
38
+ - Fallback notices are server logs. The sidebar shows configured routing with the current session selection highlighted; it does not expose pending fallback state.
25
39
  - OpenCode 2.0.8 does not identify request kind on retry hooks. The plugin conservatively tracks the last model-request kind and only handles primary requests. Concurrent auxiliary requests can suppress fallback handling.
26
40
  - Session defaults must resolve to explicit agent/model selections before routing. Fallback routing is bounded to 1,024 tracked sessions per plugin instance.
package/README.md CHANGED
@@ -175,7 +175,102 @@ Duplicate failures advance only once per admitted user turn. Routing budgets are
175
175
 
176
176
  **Verified boundary:** compiled against the published **2.0.8** API types. Deterministic adapter tests cover next-turn selection, retry veto, auxiliary-request exclusion, variants, and no replay or persistent agent writes. These are not real-provider end-to-end tests. An LLM may choose to repeat a tool when explicitly asked to continue.
177
177
 
178
- OpenCode 2.0.8 exposes no atomic compare-and-switch in the prompt hook and no request kind in the retry hook. Concurrent manual selections/admissions or auxiliary requests remain host API limitations. The sidebar displays configured routing, not the session-local runtime candidate. Terminal stack operations require local filesystem access; remote server filesystem management is not supported.
178
+ OpenCode 2.0.8 exposes no atomic compare-and-switch in the prompt hook and no request kind in the retry hook. Concurrent manual selections/admissions or auxiliary requests remain host API limitations. The sidebar displays configured routing and highlights the current session selection, not pending fallback state. Terminal stack operations require local filesystem access; remote server filesystem management is not supported.
179
+
180
+ ### Opt-in quota preflight (phase 1)
181
+
182
+ The server plugin can consult the usage-tracker server's `direct-api-usage.query`
183
+ RPC before each new explicit main-session prompt and each new native `subagent`
184
+ start. Configure the server plugin using the object form:
185
+
186
+ ```jsonc
187
+ {
188
+ "plugins": [
189
+ {
190
+ "package": "@dylanrussell/agent-router",
191
+ "options": {
192
+ "quotaPreflight": {
193
+ "enabled": true,
194
+ "allowPaidFallbacks": false
195
+ }
196
+ }
197
+ }
198
+ ]
199
+ }
200
+ ```
201
+
202
+ Both options default to `false`. Existing applied fallback chains supply candidate
203
+ order; preflight never writes agent frontmatter, router state, or stacks. A staged
204
+ reactive fallback has precedence on the next eligible admission, even when the
205
+ primary has available or unknown quota. Preflight checks that backup and later
206
+ candidates for known exhaustion. Otherwise, each eligible admission reconsiders
207
+ the configured primary. Retry hooks and same-turn retry behavior are unchanged.
208
+
209
+ - Only fresh, account-and-scope-matched `exhausted` evidence skips a candidate.
210
+ An available or unknown primary remains the primary. Expired observations,
211
+ reached resets, unavailable methods, malformed replies, and timeouts are unknown.
212
+ - After earlier candidates are exhausted, an unknown backup with a service-proven
213
+ subscription binding may be tried. Without that binding, selecting a backup
214
+ requires explicit `allowPaidFallbacks: true`, which permits potentially paid
215
+ configured backups. If no eligible candidate remains, selection is unchanged.
216
+ - The usage service owns shared caching, bounded stale refreshes, credential
217
+ resolution, explicit Headroom/Go account bindings, and model-specific OpenAI
218
+ scope matching. Router receives opaque references and quota decisions only;
219
+ it neither polls providers nor infers account equivalence from provider names.
220
+ - Quota queries have a 3-second caller deadline and at most 16 outstanding calls,
221
+ including timed-out calls that ignore cancellation. Excess admissions use
222
+ unknown evidence rather than joining a queue. Admission ownership is bounded
223
+ to 1,024 sessions and concurrent main admission checks to 64.
224
+ - Explicit initial model selections and observable manual selections take priority,
225
+ including selections equal to the configured primary. Native child overrides
226
+ are checked before model resolution in `tool.execute.before`; resumed children
227
+ are excluded. Main sessions whose agent is unresolved are left native.
228
+ - Automatic selection ownership is in memory. After plugin restart, a stored
229
+ model is conservatively treated as pinned; explicit pins also persist in
230
+ plugin-scoped server storage. A second session read and observed
231
+ selection events guard asynchronous admission, but the host offers no atomic
232
+ compare-and-switch.
233
+
234
+ **Explicit session controls:** with preflight enabled, the server exposes three
235
+ additional tools. Pin/auto tools are for explicit user requests, under the host's
236
+ normal tool-permission policy. They take no model or session arguments; their
237
+ scope is the calling session.
238
+
239
+ - `router_pin` freezes the current selection, resolving the native agent/default
240
+ model when no selection is stored. It disables quota preflight and clears staged
241
+ reactive fallback for this session. The pin persists in server plugin storage.
242
+ Pins share one durable value capped at 1,024 sessions across restarts. Serialized
243
+ writes prevent lost updates; session deletion removes its durable pin, including
244
+ when deletion races a pin write. Plugin cleanup drains dispatched pin writes.
245
+ - `router_auto` clears the explicit pin and authorizes automatic routing on the
246
+ **next explicit user turn**. It neither switches the model nor sends a prompt.
247
+ Automatic ownership is not restored across plugin restarts: use this control
248
+ again to opt an existing selected session back in.
249
+ - `router_routing_status` reports `automatic`/`pinned`, the current model, and a
250
+ concise reason. Admissions and controls also write concise server-log notices
251
+ such as `primary_unknown`, `known_exhaustion_fallback`, and
252
+ `staged_reactive_fallback`.
253
+
254
+ **Picker boundary:** OpenCode 2.0.8 treats selecting the already-active model as a
255
+ no-op and emits no selection event. Selecting an automatic fallback again in the
256
+ standard picker does **not** pin it. Explicitly request `router_pin` to freeze it;
257
+ request `router_auto` when ready to resume automatic routing. Different-model
258
+ picker selections remain observable and take priority.
259
+
260
+ `npm run build && npm run test:quota` exercises the production router and a fake
261
+ quota RPC on a private native 2.0.8 host, including true native child starts,
262
+ fresh/exhausted/unknown/reset evidence, explicit pin/auto controls, staged 429/503
263
+ precedence, timeout, recovery, and tool continuation without midtask switching.
264
+ It explicitly reports the native same-model picker boundary.
265
+ `scripts/probe-admission-v2.mjs` independently records
266
+ native admission/provenance behavior. Neither script contacts real quota APIs.
267
+ Set `USAGE_TRACKER_SOURCE` to the usage-tracker source directory to run the same
268
+ native main/child checks with its actual RPC definition and quota implementation,
269
+ using synthetic credentials and direct-provider response fixtures. Service-side
270
+ account approvals belong in usage-tracker's `options.quotaBindings`, whose records
271
+ use `{ providerID, source, models, connection: { type, id }, approval }` for saved
272
+ credentials (`{ type: "env", name }` for environment connections). Router options
273
+ do not contain credentials or binding attestations.
179
274
 
180
275
  ### Install This Checkout
181
276
 
@@ -210,8 +305,8 @@ The plugin exposes six tools the agent (or you, by asking it) can call:
210
305
 
211
306
  The V2 terminal half loads from the package's `./tui` export (`cli.json`, wired up by `init`):
212
307
 
213
- - **Sidebar panel** — lists available stacks with the active one checked. Under **Current Stack → Configured routing**, each agent has an indented **Primary** model and ordered **Fallback 1**, **Fallback 2**, … rows. Explicit variants appear in brackets; agents without fallbacks show only their primary. Model IDs are kept in full. A `⟳ restart required` badge appears when the active stack differs from the one at TUI startup. Updates live (≤1.5s), including fallback-only and variant-only edits.
214
- - **Configuration, not live failover** — sidebar chains come from the active stack file, not session-local routing or the applied `state.json.fallbackAgents` snapshot. Editing a stack changes this preview but does not apply it: use the stack and restart opencode to activate changes. A fallback row is a configured candidate, not a claim that failover is enabled or that the model is currently running.
308
+ - **Sidebar panel** — lists available stacks with the active one checked. Under **Current Stack**, each agent has one indented model per line, in precedence order, without Primary/Fallback labels. Explicit variants appear in brackets; full model IDs wrap rather than truncate. A `⟳ restart required` badge appears when the active stack differs from the one at TUI startup. File changes update live (≤1.5s).
309
+ - **Current selection** — the live session's agent/model is highlighted in color and bold with `●`; out-of-chain selections get a separate Current row. Other agents are not presented as live selections. Configured chains still come from the active stack file, not the applied `state.json.fallbackAgents` snapshot. Editing a stack changes the preview but does not apply it: use the stack and restart opencode to activate changes. The marker reflects session selection, not proof that a request is running or that failover is enabled.
215
310
  - **Commands** — type `/` or open the command palette:
216
311
 
217
312
  | command | what it does |
package/dist/cli.js CHANGED
@@ -17006,7 +17006,7 @@ async function exportStack(paths, name, toFile) {
17006
17006
  }
17007
17007
 
17008
17008
  // src/version.ts
17009
- var VERSION = "2.0.1";
17009
+ var VERSION = "2.1.0";
17010
17010
 
17011
17011
  // src/cli.ts
17012
17012
  var log = (...args) => console.log(...args);