@workweave/router 0.2.10 → 0.2.11
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/README.md +15 -3
- package/cc-statusline.sh +179 -7
- package/codex-skills/force-model/SKILL.md +14 -0
- package/codex-skills/router-feedback/SKILL.md +14 -0
- package/codex-skills/unforce-model/SKILL.md +14 -0
- package/commands/models.md +46 -0
- package/commands/router-models.md +46 -0
- package/directives.tsv +12 -0
- package/install.sh +1036 -78
- package/package.json +6 -1
- package/pi-router/README.md +36 -7
- package/pi-router/skills/install-lsps/SKILL.md +75 -0
- package/pi-router/skills/lsp-guide/SKILL.md +63 -0
- package/pi-router/src/compaction.ts +46 -8
- package/pi-router/src/config.ts +34 -5
- package/pi-router/src/context-window.ts +12 -0
- package/pi-router/src/dispatch.ts +35 -2
- package/pi-router/src/index.ts +10 -1
- package/pi-router/src/lsp-broker.ts +255 -0
- package/pi-router/src/lsp-client.ts +435 -0
- package/pi-router/src/lsp-format.ts +230 -0
- package/pi-router/src/lsp-install.ts +215 -0
- package/pi-router/src/lsp-protocol.ts +128 -0
- package/pi-router/src/lsp-servers.ts +361 -0
- package/pi-router/src/lsp.ts +529 -0
- package/pi-router/src/pricing.generated.ts +6 -1
- package/pi-router/src/provider.ts +15 -2
- package/pi-router/src/routed-model.ts +17 -0
- package/registry.sh +100 -0
- package/uninstall.sh +173 -30
package/README.md
CHANGED
|
@@ -54,6 +54,19 @@ slash commands. The shell equivalent is `npx @workweave/router disable-routing`.
|
|
|
54
54
|
Cursor has no config file we own — toggle its base URL override in **Settings →
|
|
55
55
|
Models** instead.
|
|
56
56
|
|
|
57
|
+
Pick which models the router is allowed to route to:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx @workweave/router models --claude # list every model, with its on/off state
|
|
61
|
+
npx @workweave/router models disable gpt-5.6 --claude # take one out of rotation
|
|
62
|
+
npx @workweave/router models enable gpt-5.6 --claude # put it back
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Inside Claude Code that's `/router-models` (alias `/models`). Editing needs a
|
|
66
|
+
router that serves the model-selection API; against the Weave-hosted router the
|
|
67
|
+
list still prints and points you at the dashboard, where model selection is an
|
|
68
|
+
organization-wide setting.
|
|
69
|
+
|
|
57
70
|
Uninstall:
|
|
58
71
|
|
|
59
72
|
```bash
|
|
@@ -82,9 +95,8 @@ Four install targets:
|
|
|
82
95
|
- **Codex** (`--codex`) — patches `~/.codex/config.toml` (or
|
|
83
96
|
`<repo>/.codex/config.toml`) with a managed `[model_providers.weave]`
|
|
84
97
|
block plus `model_provider = "weave"`. The provider preserves the existing
|
|
85
|
-
ChatGPT OAuth login.
|
|
86
|
-
|
|
87
|
-
their router's configured default because its HMM sidecar is optional. HMM or forced
|
|
98
|
+
ChatGPT OAuth login. No install pins `X-Weave-Router-Strategy`; every
|
|
99
|
+
endpoint keeps its router's configured default. HMM or forced
|
|
88
100
|
`gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` turns use that plan;
|
|
89
101
|
every other selected model uses its WorkWeave deployment or BYOK credential.
|
|
90
102
|
The block lives between begin/end markers
|
package/cc-statusline.sh
CHANGED
|
@@ -159,6 +159,23 @@ weave_command_tracked_by_git() {
|
|
|
159
159
|
git -C "$(dirname "$1")" ls-files --error-unmatch -- "$1" >/dev/null 2>&1
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
# weave_installed_command_names lists the wrappers this install may refresh.
|
|
163
|
+
# The statusline ships standalone (no registry.sh beside it), so the installed
|
|
164
|
+
# set — itself written from the registry — is the only source of truth here.
|
|
165
|
+
#
|
|
166
|
+
# Files written before ownership markers existed carry none, so matching on the
|
|
167
|
+
# marker alone would freeze every pre-marker install out of refreshes forever.
|
|
168
|
+
# List every wrapper instead and let the baseline comparison below decide: a
|
|
169
|
+
# file is replaced only when its bytes still match the last canonical copy, so
|
|
170
|
+
# a user-authored command is never touched whether or not it carries a marker.
|
|
171
|
+
weave_installed_command_names() {
|
|
172
|
+
local dir="$1" file
|
|
173
|
+
for file in "$dir"/*.md; do
|
|
174
|
+
[ -f "$file" ] || continue
|
|
175
|
+
printf '%s\n' "$(basename "$file" .md)"
|
|
176
|
+
done
|
|
177
|
+
}
|
|
178
|
+
|
|
162
179
|
# weave_render_command prints $1 with the installer's {{SCOPE}} placeholder
|
|
163
180
|
# replaced by $2, matching how install_slash_commands writes the same file.
|
|
164
181
|
# Trailing newlines are stripped on both sides of every comparison below.
|
|
@@ -228,8 +245,7 @@ weave_sync_commands() {
|
|
|
228
245
|
# Detach stdin (CC pipes JSON to us) so curl can't consume it, and silence
|
|
229
246
|
# everything so no output leaks into the statusline.
|
|
230
247
|
exec </dev/null
|
|
231
|
-
|
|
232
|
-
router-off router-on router-status router-session; do
|
|
248
|
+
while IFS= read -r name; do
|
|
233
249
|
installed="$cmd_dir/$name.md"
|
|
234
250
|
# Only ever refresh a wrapper that is already installed: a missing one
|
|
235
251
|
# was uninstalled or deliberately deleted, and resurrecting it would be
|
|
@@ -258,10 +274,10 @@ weave_sync_commands() {
|
|
|
258
274
|
if [ -f "$prev" ]; then
|
|
259
275
|
new_body="$(weave_render_command "$raw" "$scope_args")"
|
|
260
276
|
prev_body="$(weave_render_command "$prev" "$scope_args")"
|
|
261
|
-
installed_body="$(cat "$installed" 2>/dev/null)" || installed_body=""
|
|
277
|
+
installed_body="$(cat "$installed" 2>/dev/null | sed '/^<!-- weave-router managed command: .* -->$/d')" || installed_body=""
|
|
262
278
|
if [ "$prev_body" = "$installed_body" ] && [ "$new_body" != "$installed_body" ]; then
|
|
263
279
|
tmp="$installed.tmp.$$"
|
|
264
|
-
if printf '%s\n' "$new_body" >"$tmp" 2>/dev/null; then
|
|
280
|
+
if printf '%s\n<!-- weave-router managed command: %s -->' "$new_body" "$name" >"$tmp" 2>/dev/null; then
|
|
265
281
|
mv "$tmp" "$installed" 2>/dev/null || rm -f "$tmp"
|
|
266
282
|
else
|
|
267
283
|
rm -f "$tmp"
|
|
@@ -269,13 +285,152 @@ weave_sync_commands() {
|
|
|
269
285
|
fi
|
|
270
286
|
fi
|
|
271
287
|
mv "$raw" "$prev" 2>/dev/null || rm -f "$raw"
|
|
272
|
-
done
|
|
288
|
+
done <<EOF
|
|
289
|
+
$(weave_installed_command_names "$cmd_dir")
|
|
290
|
+
EOF
|
|
273
291
|
) >/dev/null 2>&1 &
|
|
274
292
|
disown 2>/dev/null || true
|
|
275
293
|
return 0
|
|
276
294
|
}
|
|
277
295
|
weave_sync_commands 2>/dev/null || true
|
|
278
296
|
|
|
297
|
+
# ---------- org "hide terminal surfaces" gate ----------
|
|
298
|
+
#
|
|
299
|
+
# When the org has hidden the router's terminal surfaces, the statusline
|
|
300
|
+
# renders nothing: this prints no output and exits 0, leaving the slot blank.
|
|
301
|
+
# The setting comes from GET /v1/display-settings with the install's own
|
|
302
|
+
# router key, but the foreground path NEVER touches the network: it decides
|
|
303
|
+
# solely from a per-install cache file (TTL WEAVE_DISPLAY_SETTINGS_TTL_SECONDS,
|
|
304
|
+
# default 1h), and a missing or stale cache fails open — the statusline
|
|
305
|
+
# renders normally. When the cache is missing or stale a detached background
|
|
306
|
+
# refresh re-fetches the setting and rewrites the cache atomically, so the
|
|
307
|
+
# next turn picks up the fresh value; a refresh failure simply leaves the
|
|
308
|
+
# cache stale, which keeps failing open rather than pinning the gate closed.
|
|
309
|
+
#
|
|
310
|
+
# Claude Code runs the statusline every turn, so several invocations can see a
|
|
311
|
+
# stale cache at once. Letting each fetch independently is not safe: the
|
|
312
|
+
# responses can land out of order, and the loser's mv would replace a newer
|
|
313
|
+
# setting with an older one AND stamp it fresh, pinning the gate on a stale
|
|
314
|
+
# value for a full TTL. Refreshes are therefore serialized per cache key on a
|
|
315
|
+
# mkdir mutex (atomic everywhere; flock is absent on macOS), held across both
|
|
316
|
+
# the fetch and the write. A refresh that finds the mutex held exits rather
|
|
317
|
+
# than queueing — the in-flight one is at least as fresh as anything it would
|
|
318
|
+
# fetch, so waiting only to overwrite it is the bug. The one path that is not
|
|
319
|
+
# strictly exclusive is reclaiming a lock whose holder died; see below.
|
|
320
|
+
weave_hidden_gate() {
|
|
321
|
+
command -v curl >/dev/null 2>&1 || return 1
|
|
322
|
+
command -v jq >/dev/null 2>&1 || return 1
|
|
323
|
+
|
|
324
|
+
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/weave-router"
|
|
325
|
+
mkdir -p "$cache_dir" 2>/dev/null || return 1
|
|
326
|
+
local self="${BASH_SOURCE[0]:-$0}"
|
|
327
|
+
local script_slug
|
|
328
|
+
script_slug="$(printf '%s' "$self" | tr -c 'A-Za-z0-9._-' '_')"
|
|
329
|
+
local cache="$cache_dir/display-settings${script_slug}"
|
|
330
|
+
local ttl="${WEAVE_DISPLAY_SETTINGS_TTL_SECONDS:-3600}"
|
|
331
|
+
|
|
332
|
+
local now mtime fresh="false"
|
|
333
|
+
now="$(date +%s 2>/dev/null)" || now=0
|
|
334
|
+
if [ -f "$cache" ]; then
|
|
335
|
+
mtime="$(stat -c %Y "$cache" 2>/dev/null || stat -f %m "$cache" 2>/dev/null)" || mtime=0
|
|
336
|
+
if [ -n "${mtime:-}" ] && [ "$mtime" -gt 0 ] && [ $(( now - mtime )) -lt "$ttl" ]; then
|
|
337
|
+
fresh="true"
|
|
338
|
+
fi
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# Foreground decision: only a fresh cache hides the surfaces. Anything else
|
|
342
|
+
# (no cache, stale cache, unreadable cache) renders normally so a slow or
|
|
343
|
+
# unreachable router can never stall a turn or wedge the statusline blank.
|
|
344
|
+
if [ "$fresh" = "true" ]; then
|
|
345
|
+
[ "$(cat "$cache" 2>/dev/null)" = "1" ]
|
|
346
|
+
return
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
# Background refresh for the next invocation. Resolve the router base URL
|
|
350
|
+
# and key inside the subshell from the Claude Code settings the installer
|
|
351
|
+
# wrote: project/--dir installs put both under <base>/.claude alongside
|
|
352
|
+
# this script (key in settings.local.json), while a user-scope install
|
|
353
|
+
# lives under ~/.weave and reads ~/.claude. Resolve relative to the
|
|
354
|
+
# script's own location, falling back to user scope, so a project install
|
|
355
|
+
# never reads (or leaks) the user-scope key. ANTHROPIC_BASE_URL and
|
|
356
|
+
# WEAVE_ROUTER_BASE_URL may also be set in the environment. A file:// base
|
|
357
|
+
# URL is the offline/test seam: curl reads it as the response body
|
|
358
|
+
# directly, so the endpoint path is meaningless for it; real router URLs
|
|
359
|
+
# (https) get /v1/display-settings appended.
|
|
360
|
+
(
|
|
361
|
+
exec </dev/null
|
|
362
|
+
|
|
363
|
+
# Take the per-cache-key mutex, or bail. mkdir is the portable atomic
|
|
364
|
+
# test-and-set. A crashed holder would otherwise block refreshes forever,
|
|
365
|
+
# so a lock older than the fetch timeout is treated as abandoned and
|
|
366
|
+
# reclaimed. Releasing from a trap covers every exit path below.
|
|
367
|
+
lock="$cache.lock"
|
|
368
|
+
if ! mkdir "$lock" 2>/dev/null; then
|
|
369
|
+
lock_mtime="$(stat -c %Y "$lock" 2>/dev/null || stat -f %m "$lock" 2>/dev/null)" || lock_mtime=0
|
|
370
|
+
lock_now="$(date +%s 2>/dev/null)" || lock_now=0
|
|
371
|
+
if [ "${lock_mtime:-0}" -le 0 ] || [ $(( lock_now - lock_mtime )) -le 30 ]; then
|
|
372
|
+
exit 0
|
|
373
|
+
fi
|
|
374
|
+
# Reclaiming an abandoned lock must not be delete-then-recreate. With
|
|
375
|
+
# `rm -rf` + `mkdir`, refreshers that all see the same stale lock each
|
|
376
|
+
# delete the next one's freshly created directory, so many end up holding
|
|
377
|
+
# it at once (measured at 50 claimants: 4-11 concurrent holders) and their
|
|
378
|
+
# writes can land out of order. Renaming is atomic, so only one racer can
|
|
379
|
+
# move a given directory aside and the losers exit instead of clobbering
|
|
380
|
+
# the winner (same measurement: 1-2). It is not a perfect mutex — a
|
|
381
|
+
# straggler can still reclaim the new lock a winner just created, since
|
|
382
|
+
# nothing distinguishes it from the stale one — but real refreshes arrive
|
|
383
|
+
# one per turn rather than 50 at once, and the staleness threshold below
|
|
384
|
+
# is what bounds the rest.
|
|
385
|
+
dead="$lock.dead.$$"
|
|
386
|
+
mv "$lock" "$dead" 2>/dev/null || exit 0
|
|
387
|
+
rm -rf "$dead" 2>/dev/null
|
|
388
|
+
mkdir "$lock" 2>/dev/null || exit 0
|
|
389
|
+
fi
|
|
390
|
+
trap 'rmdir "$lock" 2>/dev/null' EXIT
|
|
391
|
+
|
|
392
|
+
self_dir="$(cd "$(dirname "$self")" 2>/dev/null && pwd)"
|
|
393
|
+
settings_base="$HOME"
|
|
394
|
+
case "$self_dir" in
|
|
395
|
+
*/.claude) settings_base="${self_dir%/.claude}" ;;
|
|
396
|
+
esac
|
|
397
|
+
settings="$settings_base/.claude/settings.json"
|
|
398
|
+
local_settings="$settings_base/.claude/settings.local.json"
|
|
399
|
+
base_url="${WEAVE_ROUTER_BASE_URL:-${ANTHROPIC_BASE_URL:-}}"
|
|
400
|
+
key="${WEAVE_ROUTER_KEY:-}"
|
|
401
|
+
if [ -z "$key" ] && [ -f "$settings" ]; then
|
|
402
|
+
key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$settings" 2>/dev/null | head -n1)"
|
|
403
|
+
fi
|
|
404
|
+
if [ -z "$key" ] && [ -f "$local_settings" ]; then
|
|
405
|
+
key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$local_settings" 2>/dev/null | head -n1)"
|
|
406
|
+
fi
|
|
407
|
+
if [ -z "$base_url" ] && [ -f "$settings" ]; then
|
|
408
|
+
base_url="$(jq -r '.env.ANTHROPIC_BASE_URL // empty' "$settings" 2>/dev/null)"
|
|
409
|
+
fi
|
|
410
|
+
[ -n "$base_url" ] && [ -n "$key" ] || exit 0
|
|
411
|
+
url="${base_url%/}"
|
|
412
|
+
case "$url" in
|
|
413
|
+
file://*) ;;
|
|
414
|
+
*) url="$url/v1/display-settings" ;;
|
|
415
|
+
esac
|
|
416
|
+
body="$(curl -fsS --max-time 5 -H "X-Weave-Router-Key: $key" "$url" 2>/dev/null)" || exit 0
|
|
417
|
+
hidden="$(printf '%s' "$body" | jq -r '.hide_terminal_surfaces // false' 2>/dev/null)"
|
|
418
|
+
tmp="$cache.tmp.$$"
|
|
419
|
+
if [ "$hidden" = "true" ]; then
|
|
420
|
+
printf '1' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
|
|
421
|
+
else
|
|
422
|
+
printf '0' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
|
|
423
|
+
fi
|
|
424
|
+
rm -f "$tmp" 2>/dev/null
|
|
425
|
+
) >/dev/null 2>&1 &
|
|
426
|
+
disown 2>/dev/null || true
|
|
427
|
+
return 1
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if weave_hidden_gate </dev/null; then
|
|
431
|
+
exit 0
|
|
432
|
+
fi
|
|
433
|
+
|
|
279
434
|
input="$(cat)"
|
|
280
435
|
transcript_path="$(printf '%s' "$input" | jq -r '.transcript_path // empty')"
|
|
281
436
|
# Prefer model.id over display_name: pricing keys + the routed model id in
|
|
@@ -316,6 +471,7 @@ prices='{
|
|
|
316
471
|
"claude-sonnet-5": 0.003,
|
|
317
472
|
"deepseek/deepseek-v4-flash": 0.0001134,
|
|
318
473
|
"deepseek/deepseek-v4-pro": 0.00174,
|
|
474
|
+
"deepseek/deepseek-v4-pro-0813": 0.00174,
|
|
319
475
|
"gemini-2.0-flash": 0.0001,
|
|
320
476
|
"gemini-2.0-flash-lite": 0.000075,
|
|
321
477
|
"gemini-2.5-flash": 0.0003,
|
|
@@ -328,6 +484,7 @@ prices='{
|
|
|
328
484
|
"gemini-3.5-flash": 0.0015,
|
|
329
485
|
"gemini-3.5-flash-lite": 0.0003,
|
|
330
486
|
"gemini-3.6-flash": 0.0015,
|
|
487
|
+
"gemini-3.7-flash": 0.0015,
|
|
331
488
|
"google/gemma-4-26b-a4b-it": 0.00015,
|
|
332
489
|
"gpt-4.1": 0.002,
|
|
333
490
|
"gpt-4.1-mini": 0.0004,
|
|
@@ -347,7 +504,9 @@ prices='{
|
|
|
347
504
|
"gpt-5.5-nano": 0.00015,
|
|
348
505
|
"gpt-5.5-pro": 0.03,
|
|
349
506
|
"gpt-5.6-luna": 0.001,
|
|
507
|
+
"gpt-5.6-luna-pro": 0.001,
|
|
350
508
|
"gpt-5.6-sol": 0.005,
|
|
509
|
+
"gpt-5.6-sol-pro": 0.005,
|
|
351
510
|
"gpt-5.6-terra": 0.0025,
|
|
352
511
|
"grok-4.5": 0.002,
|
|
353
512
|
"grok-4.6": 0.002,
|
|
@@ -370,7 +529,8 @@ prices='{
|
|
|
370
529
|
"xiaomi/mimo-v2.5-pro": 0.001,
|
|
371
530
|
"z-ai/glm-5": 0.001,
|
|
372
531
|
"z-ai/glm-5.1": 0.0014,
|
|
373
|
-
"z-ai/glm-5.2": 0.0014
|
|
532
|
+
"z-ai/glm-5.2": 0.0014,
|
|
533
|
+
"z-ai/glm-5.3-flash": 0.00015
|
|
374
534
|
},
|
|
375
535
|
"output": {
|
|
376
536
|
"claude-fable-5": 0.05,
|
|
@@ -387,6 +547,7 @@ prices='{
|
|
|
387
547
|
"claude-sonnet-5": 0.015,
|
|
388
548
|
"deepseek/deepseek-v4-flash": 0.0002791,
|
|
389
549
|
"deepseek/deepseek-v4-pro": 0.00348,
|
|
550
|
+
"deepseek/deepseek-v4-pro-0813": 0.00348,
|
|
390
551
|
"gemini-2.0-flash": 0.0004,
|
|
391
552
|
"gemini-2.0-flash-lite": 0.0003,
|
|
392
553
|
"gemini-2.5-flash": 0.0012,
|
|
@@ -399,6 +560,7 @@ prices='{
|
|
|
399
560
|
"gemini-3.5-flash": 0.009,
|
|
400
561
|
"gemini-3.5-flash-lite": 0.0025,
|
|
401
562
|
"gemini-3.6-flash": 0.0075,
|
|
563
|
+
"gemini-3.7-flash": 0.0075,
|
|
402
564
|
"google/gemma-4-26b-a4b-it": 0.0006,
|
|
403
565
|
"gpt-4.1": 0.008,
|
|
404
566
|
"gpt-4.1-mini": 0.0016,
|
|
@@ -418,7 +580,9 @@ prices='{
|
|
|
418
580
|
"gpt-5.5-nano": 0.0006,
|
|
419
581
|
"gpt-5.5-pro": 0.18,
|
|
420
582
|
"gpt-5.6-luna": 0.006,
|
|
583
|
+
"gpt-5.6-luna-pro": 0.006,
|
|
421
584
|
"gpt-5.6-sol": 0.03,
|
|
585
|
+
"gpt-5.6-sol-pro": 0.03,
|
|
422
586
|
"gpt-5.6-terra": 0.015,
|
|
423
587
|
"grok-4.5": 0.006,
|
|
424
588
|
"grok-4.6": 0.006,
|
|
@@ -441,7 +605,8 @@ prices='{
|
|
|
441
605
|
"xiaomi/mimo-v2.5-pro": 0.003,
|
|
442
606
|
"z-ai/glm-5": 0.0032,
|
|
443
607
|
"z-ai/glm-5.1": 0.0044,
|
|
444
|
-
"z-ai/glm-5.2": 0.0044
|
|
608
|
+
"z-ai/glm-5.2": 0.0044,
|
|
609
|
+
"z-ai/glm-5.3-flash": 0.0005
|
|
445
610
|
}
|
|
446
611
|
}'
|
|
447
612
|
# END_GENERATED_PRICES
|
|
@@ -496,6 +661,12 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
496
661
|
# * unrecognized model → "… isn't a recognized model · keeping
|
|
497
662
|
# automatic routing" — a NO-OP: the prior
|
|
498
663
|
# pin, if any, is left untouched
|
|
664
|
+
# * listing (historical) → "… pick a model by id …" / "no models are
|
|
665
|
+
# available to pin …" — also NO-OPs. The
|
|
666
|
+
# router no longer emits these, but a
|
|
667
|
+
# transcript written while it did still
|
|
668
|
+
# carries them, and they must not read as
|
|
669
|
+
# a clear or a live pin loses its [forced]
|
|
499
670
|
# These persist on disk (the ingress stripper only scrubs them from upstream
|
|
500
671
|
# requests). Classify each weave-router turn newest-first, skip the no-op
|
|
501
672
|
# "rejected" acks, and let the latest real state change decide: an "applied"
|
|
@@ -509,6 +680,7 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
509
680
|
| ([.message.content[]? | select(.type? == "text") | .text] | join(" ") | gsub("[\n\r]"; " ")) as $t
|
|
510
681
|
| if ($t | test("force-model applied:")) then "APPLIED " + ($t | capture("force-model applied: (?<m>[^ ]+)").m)
|
|
511
682
|
elif ($t | test("isn.t a recognized model")) then "REJECTED"
|
|
683
|
+
elif ($t | test("pick a model by id|no models are available to pin")) then "REJECTED"
|
|
512
684
|
else "CLEARED" end' 2>/dev/null \
|
|
513
685
|
| grep -m1 -v '^REJECTED$' || true)"
|
|
514
686
|
if [[ "$force_state" == APPLIED\ * ]]; then
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: force-model
|
|
3
|
+
description: "Pin this Codex session to a specific model through the Weave Router."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed force-model skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$force-model <model-id>` (or asks to use the `$fm` alias), send a normal user message whose first character is one literal space, followed by `/force-model ` and the requested model id. Do not use a Codex slash command and do not omit the leading space. For example, send exactly:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
/force-model gpt-5.6-terra
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Preserve any model id exactly as provided. Report the router's response after it returns.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-feedback
|
|
3
|
+
description: "Submit feedback about a Weave Router decision or model performance."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-feedback skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$router-feedback <feedback>` (or asks to use the `$rf` alias), send a normal user message whose first character is one literal space, followed by `/router-feedback ` and the feedback text. Do not use a Codex slash command and do not omit the leading space. Preserve the feedback text exactly. For example:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
/router-feedback the selected model struggled with this task
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Report the router's response after it returns.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unforce-model
|
|
3
|
+
description: "Clear the active Weave Router model pin for this Codex session."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed unforce-model skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$unforce-model` (or asks to use the `$ufm` alias), send a normal user message whose first character is one literal space, followed by `/unforce-model`. Do not use a Codex slash command and do not omit the leading space. Send exactly:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
/unforce-model
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Report the router's response after it returns.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Alias for /router-models — list the models the Weave Router may route to, and turn them on or off.
|
|
3
|
+
argument-hint: [model or provider to enable/disable]
|
|
4
|
+
allowed-tools: Bash(npx:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Show me which models this installation lets the Weave Router pick from, and
|
|
8
|
+
change that selection when I ask. This is the same list — and the same stored
|
|
9
|
+
setting — as the checkboxes on the router dashboard's settings page.
|
|
10
|
+
|
|
11
|
+
Start by running:
|
|
12
|
+
|
|
13
|
+
`npx @workweave/router models --claude{{SCOPE}}`
|
|
14
|
+
|
|
15
|
+
That prints every deployed model grouped by provider, with `[x]` for models the
|
|
16
|
+
router may pick and `[ ]` for models it may not. Present it back to me as a
|
|
17
|
+
compact checklist in that same `[x]` / `[ ]` form, keeping the provider
|
|
18
|
+
grouping and the exact model ids — I select models by id.
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
- If I named models or providers in `$ARGUMENTS`, work out whether I want them
|
|
23
|
+
on or off from how I phrased it, and apply it with
|
|
24
|
+
`npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
|
|
25
|
+
`npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
|
|
26
|
+
`providers` before `enable`/`disable` to switch a whole provider). Several
|
|
27
|
+
ids can go in one call. Then re-run the list and show me the result.
|
|
28
|
+
- If I named nothing, stop after the list and ask which ones I want to change.
|
|
29
|
+
Don't change anything I didn't ask for.
|
|
30
|
+
|
|
31
|
+
Other things I might ask for:
|
|
32
|
+
|
|
33
|
+
- Rank models by preference:
|
|
34
|
+
`npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
|
|
35
|
+
matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
|
|
36
|
+
drop the ranking.
|
|
37
|
+
- Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
|
|
38
|
+
|
|
39
|
+
If the command reports that this router doesn't expose model selection, that's
|
|
40
|
+
a Weave-hosted router: model selection belongs to the whole organization there,
|
|
41
|
+
so tell me to change it at https://router.workweave.ai/dashboard/settings —
|
|
42
|
+
don't try to work around it. Its listing carries no on/off state, so present it
|
|
43
|
+
as a plain list; don't infer which models are enabled.
|
|
44
|
+
|
|
45
|
+
Disabling a model takes effect on the router's next routing decision; no
|
|
46
|
+
restart is needed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List the models the Weave Router may route to, and turn them on or off.
|
|
3
|
+
argument-hint: [model or provider to enable/disable]
|
|
4
|
+
allowed-tools: Bash(npx:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Show me which models this installation lets the Weave Router pick from, and
|
|
8
|
+
change that selection when I ask. This is the same list — and the same stored
|
|
9
|
+
setting — as the checkboxes on the router dashboard's settings page.
|
|
10
|
+
|
|
11
|
+
Start by running:
|
|
12
|
+
|
|
13
|
+
`npx @workweave/router models --claude{{SCOPE}}`
|
|
14
|
+
|
|
15
|
+
That prints every deployed model grouped by provider, with `[x]` for models the
|
|
16
|
+
router may pick and `[ ]` for models it may not. Present it back to me as a
|
|
17
|
+
compact checklist in that same `[x]` / `[ ]` form, keeping the provider
|
|
18
|
+
grouping and the exact model ids — I select models by id.
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
- If I named models or providers in `$ARGUMENTS`, work out whether I want them
|
|
23
|
+
on or off from how I phrased it, and apply it with
|
|
24
|
+
`npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
|
|
25
|
+
`npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
|
|
26
|
+
`providers` before `enable`/`disable` to switch a whole provider). Several
|
|
27
|
+
ids can go in one call. Then re-run the list and show me the result.
|
|
28
|
+
- If I named nothing, stop after the list and ask which ones I want to change.
|
|
29
|
+
Don't change anything I didn't ask for.
|
|
30
|
+
|
|
31
|
+
Other things I might ask for:
|
|
32
|
+
|
|
33
|
+
- Rank models by preference:
|
|
34
|
+
`npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
|
|
35
|
+
matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
|
|
36
|
+
drop the ranking.
|
|
37
|
+
- Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
|
|
38
|
+
|
|
39
|
+
If the command reports that this router doesn't expose model selection, that's
|
|
40
|
+
a Weave-hosted router: model selection belongs to the whole organization there,
|
|
41
|
+
so tell me to change it at https://router.workweave.ai/dashboard/settings —
|
|
42
|
+
don't try to work around it. Its listing carries no on/off state, so present it
|
|
43
|
+
as a plain list; don't infer which models are enabled.
|
|
44
|
+
|
|
45
|
+
Disabling a model takes effect on the router's next routing decision; no
|
|
46
|
+
restart is needed.
|
package/directives.tsv
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Weave Router directive registry
|
|
2
|
+
# canonical|aliases|capability|claude|codex|opencode|pi|cursor|adapter
|
|
3
|
+
# aliases are comma-separated; client columns are yes/no. adapter is the native asset kind.
|
|
4
|
+
force-model|fm|prompt|yes|yes|yes|yes|manual|command,skill
|
|
5
|
+
unforce-model|ufm|prompt|yes|yes|yes|yes|manual|command,skill
|
|
6
|
+
router-feedback|rf|prompt|yes|yes|yes|no|manual|command,skill
|
|
7
|
+
router-off||local-toggle|yes|no|no|no|manual|command
|
|
8
|
+
router-on||local-toggle|yes|no|no|no|manual|command
|
|
9
|
+
router-status||local-toggle|yes|no|no|no|manual|command
|
|
10
|
+
router-session||prompt|yes|no|no|no|manual|command
|
|
11
|
+
router-models|models|local-toggle|yes|no|no|no|manual|command
|
|
12
|
+
disable-routing||local-toggle|no|yes|no|no|manual|skill
|