amicus 4.9.2 → 4.9.4
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +324 -0
- package/README.md +1 -1
- package/bin/amicus.js +6 -0
- package/docs/ROADMAP.md +5 -4
- package/docs/architecture-map.md +732 -0
- package/docs/configuration.md +175 -1
- package/docs/council.md +9 -0
- package/docs/doc-system.md +12 -9
- package/docs/testing.md +2 -1
- package/docs/troubleshooting.md +76 -0
- package/docs/usage.md +14 -6
- package/electron/main.js +25 -2
- package/electron/setup-ui-alias-groups.js +161 -0
- package/electron/setup-ui-alias-script.js +70 -4
- package/electron/setup-ui-aliases.js +25 -21
- package/electron/setup-ui.js +11 -1
- package/package.json +1 -1
- package/schemas/model-catalog.schema.json +2 -1
- package/schemas/run.schema.json +13 -0
- package/skills/sidecar/SKILL.md +1 -8
- package/src/cli-handlers-doctor.js +12 -16
- package/src/cli-handlers-fanout.js +10 -1
- package/src/cli-handlers-resume-continue.js +25 -0
- package/src/cli-handlers.js +17 -1
- package/src/cli.js +5 -8
- package/src/council/briefings-chair.js +4 -2
- package/src/council/run-assemble.js +7 -2
- package/src/council/run-retry-notes.js +21 -1
- package/src/council/run-stages.js +8 -1
- package/src/headless.js +125 -7
- package/src/mcp-server.js +26 -0
- package/src/mcp-tools.js +4 -4
- package/src/opencode-client.js +84 -8
- package/src/pack/pack-validate.js +3 -0
- package/src/session-manager.js +2 -2
- package/src/sidecar/continue.js +6 -1
- package/src/sidecar/conversation-mirror.js +35 -11
- package/src/sidecar/fanout-leg-fallback.js +1 -0
- package/src/sidecar/fanout-leg.js +10 -2
- package/src/sidecar/fanout.js +2 -2
- package/src/sidecar/interactive.js +31 -4
- package/src/sidecar/models-ceiling-line.js +72 -0
- package/src/sidecar/models.js +4 -2
- package/src/sidecar/reopen-notices.js +97 -0
- package/src/sidecar/reopen-spend.js +3 -2
- package/src/sidecar/resume.js +15 -2
- package/src/sidecar/session-finalize.js +4 -1
- package/src/sidecar/session-utils.js +5 -1
- package/src/sidecar/start-metadata.js +1 -1
- package/src/sidecar/start.js +10 -5
- package/src/utils/api-key-validation.js +183 -94
- package/src/utils/config.js +65 -2
- package/src/utils/curated-models.js +8 -8
- package/src/utils/degrade.js +7 -0
- package/src/utils/doctor-credit-check.js +61 -0
- package/src/utils/doctor-key-auth-check.js +271 -0
- package/src/utils/doctor-output-budget-check.js +198 -0
- package/src/utils/engine-output-flag.js +105 -0
- package/src/utils/engine-variants.js +298 -0
- package/src/utils/http-get.js +284 -0
- package/src/utils/live-probes.js +53 -0
- package/src/utils/model-catalog.js +36 -4
- package/src/utils/model-ceilings-modelsdev.js +230 -0
- package/src/utils/model-fetcher.js +14 -36
- package/src/utils/model-output-limit.js +132 -0
- package/src/utils/openrouter-credit.js +104 -0
- package/src/utils/output-length.js +90 -0
- package/src/utils/result-schema.js +7 -2
- package/src/utils/spend-ledger.js +5 -1
- package/src/utils/thinking-validators.js +27 -80
- package/src/utils/validators.js +2 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.4",
|
|
4
4
|
"description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Christian Wagner"
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,330 @@
|
|
|
3
3
|
All notable changes to Amicus are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
|
|
5
5
|
|
|
6
|
+
## [4.9.4] - 2026-09-07
|
|
7
|
+
|
|
8
|
+
*The effort level was never on the wire, and the budget stopped at the routes the catalog could clamp.*
|
|
9
|
+
|
|
10
|
+
Every `--thinking <level>` Amicus has ever sent went out as a `reasoning` object the engine's prompt
|
|
11
|
+
endpoint does not read — a silent no-op on every run since the flag existed, measured on the wire
|
|
12
|
+
(probe F1). It now goes out as the engine's `variant` field, checked first against what the engine's
|
|
13
|
+
own catalogue declares for the model, so a level the model does not declare is refused before
|
|
14
|
+
anything is sent instead of being dropped in silence. Beside it, `outputBudget` becomes
|
|
15
|
+
bidirectional and reaches every route but the direct `openai` one, whose request carries no
|
|
16
|
+
output-limit field at all; direct-provider rows gain real context and ceiling numbers from
|
|
17
|
+
models.dev; and the death that opened #218 — a leg that spends its whole reservation on reasoning
|
|
18
|
+
and finalizes with no answer text — is named `OUTPUT_LENGTH` rather than passing as a completion
|
|
19
|
+
with an empty summary. Every claim here was measured by a zero-spend wire probe that plays the
|
|
20
|
+
provider, so the pinned engine's outbound fields can be read under each shape Amicus can produce.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Direct-provider output ceilings (#218 P3).** `amicus models --refresh` now fills
|
|
25
|
+
`contextLength` / `maxOutputTokens` for any `anthropic`, `openai`, `google`, `deepseek` or
|
|
26
|
+
`openrouter` row still missing a number, from the keyless [models.dev](https://models.dev)
|
|
27
|
+
index, and lifts Google's own `outputTokenLimit` first-party. The provider's own number
|
|
28
|
+
always wins — Google's own ceiling and OpenRouter's own value included; models.dev fills
|
|
29
|
+
only fields the provider left empty or unusable, never a zero, and `openrouter/openrouter/*`
|
|
30
|
+
routers and local rows are never filled at all. The models.dev call is keyless, bounded by a
|
|
31
|
+
10 s timeout, and its failure is reported on the refresh line rather than hidden — including
|
|
32
|
+
a 200 that parses but carries no recognised vendor limits, which is a `bad-shape` failure
|
|
33
|
+
and not a silent no-op. It is also skippable both ways: a refresh where every candidate row
|
|
34
|
+
already carries both numbers never makes the call at all, and the new top-level `config.json`
|
|
35
|
+
key **`modelsDevCeilings: false`** opts out of contacting models.dev entirely (the anthropic /
|
|
36
|
+
deepseek direct rows then carry no ceiling in the Amicus catalog and are clamped by the engine's
|
|
37
|
+
own catalog instead, and the direct openai rows send no output reservation at all regardless
|
|
38
|
+
— M5/M13/M22; Google publishes its own ceiling and OpenRouter rows keep OpenRouter's). The
|
|
39
|
+
refresh prints the outcome (`Ceilings: …`), naming which of those happened, and `--json`
|
|
40
|
+
carries it as `ceilingEnrichment`. Effect: no request changes with `outputBudget` unset;
|
|
41
|
+
direct-provider rows now carry context and ceiling numbers (visible in `amicus models`), and
|
|
42
|
+
`outputBudget` can clamp the direct `google` / `deepseek` routes once the catalog is refreshed
|
|
43
|
+
— which 4.9.3 documented as impossible because those lists "don't publish one". The direct
|
|
44
|
+
`openai` route is the exception, measured in PR 4 (probe M5/M13/M22): the engine drives that
|
|
45
|
+
provider through the Responses API, whose request carries no output-limit field at all, so
|
|
46
|
+
neither the descriptor nor the flag changes what goes out there and a budget never reaches it;
|
|
47
|
+
`doctor` lists such routes apart. Direct `anthropic/*` was held out of clamping by the council
|
|
48
|
+
review of PR #230 until the thinking-budget interaction was measured; PR 2 measured it and
|
|
49
|
+
lifted the hold-out — see the next bullet.
|
|
50
|
+
- **`outputBudget` now works in both directions (#218 PR 2).** A budget above the engine's 32,000
|
|
51
|
+
default is honoured: Amicus starts every engine with `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` set to
|
|
52
|
+
the budget — around the spawn only, restored before anything is awaited, never written to the
|
|
53
|
+
caller's shell; a value you exported yourself is honoured untouched when no budget is set — so
|
|
54
|
+
every leg reserves `min(budget, ceiling)` wherever a ceiling is known (every leg but a direct
|
|
55
|
+
`openai` one — that route carries no reservation field, see the previous bullet), by the Amicus
|
|
56
|
+
catalog through the per-model descriptor or else by the engine's own, and a model neither knows
|
|
57
|
+
receives the budget as-is, exactly as it received the raw 32,000 before. Direct `anthropic/*`
|
|
58
|
+
routes are no longer held out: the probe measured the descriptor lowering the reservation there
|
|
59
|
+
(K1), a thinking variant's budget added on top (K2: 8,000 + 16,000 = 24,000) and the sum clamped
|
|
60
|
+
to the model's real ceiling (K3/K4/K10), so no budget can push a thinking leg over it. Thirteen
|
|
61
|
+
new probe rows (K1–K13) pin every shape this ships, and the full 32-case matrix is filed in the
|
|
62
|
+
BACKLOG. New `doctor` row **`output-budget`** says what the value reaches — routes the catalog can
|
|
63
|
+
clamp, routes it cannot, an ambient flag honoured or overridden — and flags the one silent failure
|
|
64
|
+
the engine has here: a malformed budget or flag, on which it falls back to 32,000 without a word.
|
|
65
|
+
- **`scripts/probe-max-tokens.js`.** A zero-spend wire probe: a local capture server plays the
|
|
66
|
+
provider so the pinned engine's outbound `max_tokens` / `reasoning` / `thinking` fields can be
|
|
67
|
+
read under every descriptor, env-flag and prompt shape amicus can produce. Re-run after every
|
|
68
|
+
engine bump.
|
|
69
|
+
- **The "Mode 2" death is named (#218 PR 3).** A leg whose provider stopped at the `max_tokens`
|
|
70
|
+
reservation before any answer text on the message it finalized — the whole reservation spent on
|
|
71
|
+
reasoning; a tool loop's earlier text or promoted reasoning does not count — now ends `error`
|
|
72
|
+
with a reason starting `OUTPUT_LENGTH:` that carries the engine's own reasoning/output counts for
|
|
73
|
+
the leg and the `outputBudget` in force (or the ambient `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX`
|
|
74
|
+
the engine was started with, when no budget is set), and the poll loop exits the moment the engine
|
|
75
|
+
finalizes such a message instead of waiting out the no-output backstop. The engine records `finish`
|
|
76
|
+
on the finalized assistant message (measured on both provider families for a length stop); it now
|
|
77
|
+
rides every leg document (`metadata.json`, `run.json`, the wave doc), the spend-ledger row
|
|
78
|
+
(`finish`, present only when recorded) and solo session metadata. A review that was cut at the
|
|
79
|
+
reservation but still answered is kept, announced as a `Note:` on the new `output-truncated` channel
|
|
80
|
+
(`kind: "info"` — never a loss, never an exit-code change), and marked as cut in its chair-packet
|
|
81
|
+
header. Five probe rows (L1–L5) measured the shapes: `finish: 'length'` on both provider families;
|
|
82
|
+
reasoning subtracted from completion on OpenAI-compatible routes but no split on direct Anthropic;
|
|
83
|
+
a `reasoning` part and no `text` part when the reasoning was visible; and a descriptor above the
|
|
84
|
+
engine's own ceiling clamped to that ceiling with no thinking variant in play. The probe's capture
|
|
85
|
+
server now answers with a per-case body and speaks the Anthropic messages SSE, so the direct rows
|
|
86
|
+
record the assistant message instead of an APIError; the full 37-case matrix is filed in the
|
|
87
|
+
BACKLOG.
|
|
88
|
+
- **`--thinking` reaches the engine (#218 PR 4).** Every `--thinking <level>` amicus ever sent went
|
|
89
|
+
out as a `reasoning` object the engine's prompt endpoint does not read — a silent no-op on every
|
|
90
|
+
run (probe F1). It now goes out as the engine's `variant` field (F2, M1, M12), and it is checked
|
|
91
|
+
first against what the engine's own catalogue declares for the model (`/config/providers`): a
|
|
92
|
+
level the model does not declare is refused before anything is sent (`VARIANT_UNDECLARED`, naming
|
|
93
|
+
the declared set — the engine would have dropped it silently and still echoed it on the message,
|
|
94
|
+
F3/M7); a declared level whose thinking budget the direct Anthropic route adds on top of the
|
|
95
|
+
reservation (Haiku 4.5 — M2: 24,000 + 16,000 = 40,000; Opus 4.5 declares the same shape, M0) is
|
|
96
|
+
refused when `outputBudget` is below the model's ceiling, and also when no ceiling is declared
|
|
97
|
+
anywhere — there the sum has no clamp to land under (`VARIANT_OVER_BUDGET`, with the reservation
|
|
98
|
+
the leg would have made and the remedies for each case); a model the engine's catalogue does not
|
|
99
|
+
know within a five-second wait — its bundled catalogue predates the model and the models.dev fetch
|
|
100
|
+
has not landed yet, the state of a cold `~/.cache/opencode` (M0 cold vs M12 warm) — gets the level
|
|
101
|
+
unverified, logged and marked `variantUnverified: true` on the leg document. The same note is
|
|
102
|
+
printed as a `Notice:` line on stderr, because the structured warning alone is dropped at the
|
|
103
|
+
default log level. Whether a level counts as declared does not depend on `outputBudget` — the
|
|
104
|
+
`VARIANT_OVER_BUDGET` fit above is the only refusal a budget can add: `/config/providers` returns a
|
|
105
|
+
model's row with the catalogue's own release date, family, display name, pricing, capabilities and
|
|
106
|
+
variants, and Amicus writes only `limit` into it, so a row carrying any of those is a
|
|
107
|
+
declaration (an empty variants set is a refusal) and a row carrying none of them is Amicus's own
|
|
108
|
+
descriptor (unknown — the bounded wait, then an unverified send). That dump is the engine's MERGED
|
|
109
|
+
view of its own catalogue and your `opencode.json`, so model metadata you declare yourself reads as
|
|
110
|
+
a declaration too. Measured as record M23 and
|
|
111
|
+
pinned by a keyless engine canary. The wait polls every 500 ms. The ceiling that
|
|
112
|
+
`VARIANT_OVER_BUDGET` fit judges against is Amicus's own catalog's row for
|
|
113
|
+
the model — its `maxOutputTokens`, the number a budget-derived descriptor is clamped TO, not the
|
|
114
|
+
value that descriptor carries — because the engine echoes that descriptor back once a budget is
|
|
115
|
+
set (M3); for a model that catalog has no row for it is the dump's own value, the engine's own
|
|
116
|
+
ceiling (K5/K12). `docs/configuration.md` records what a divergence from the engine's own ceiling
|
|
117
|
+
costs in each direction: a row above it can refuse a leg the engine would have clamped under the
|
|
118
|
+
budget, and a row below it goes silent on one it never judged. A refusal is a zero-spend leg death
|
|
119
|
+
through the usual channel (`error` with the reason; a fanout's other legs run; `start --no-ui`
|
|
120
|
+
exits 1). The MCP `amicus_start` tool's in-process (shared-server) path carries the level too —
|
|
121
|
+
its `thinking` had been argv-only, which that path never read. A backstop window that fires while
|
|
122
|
+
a leg is still inside its declaration wait (bounded at five seconds; one read on a warm, declared
|
|
123
|
+
model) ends the leg `NO_OUTPUT_BACKSTOP` before anything is sent (an abandon signal stops the
|
|
124
|
+
orphaned send); an unreadable `/config/providers` (a non-2xx, or a read that throws — a transport
|
|
125
|
+
error, a dead engine) sends the level unverified after ONE read, and the log line says so. `max`
|
|
126
|
+
joins the vocabulary (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` — the levels the
|
|
127
|
+
curated routes declare between them, M0). The level SENT rides the run document (`variant`), the
|
|
128
|
+
spend-ledger row (`variant`, present only when one was sent) and the leg patch. Twenty-four probe
|
|
129
|
+
rows (M0–M17 and M22, plus M18–M21 through amicus's own `sendPrompt`) measured every shape this
|
|
130
|
+
ships — the full 61-case matrix is filed in the BACKLOG — and CI's keyless job now also runs M1,
|
|
131
|
+
M2, M10b and M17. Council seats have no effort knob in this release (filed as the owner's
|
|
132
|
+
decision).
|
|
133
|
+
|
|
134
|
+
### Changed
|
|
135
|
+
|
|
136
|
+
- **A configured `outputBudget` no longer weakens the `--thinking` guard (#218 PR 4, council #235
|
|
137
|
+
round 3 C1/B1).** Earlier on this unreleased branch, a model whose `/config/providers` row
|
|
138
|
+
reported no variants was, *with a budget set*, polled for five seconds, reported ambiguous and
|
|
139
|
+
then sent the level unverified — while the same command with no budget refused it at once. What
|
|
140
|
+
flips: a model the engine's own catalogue supplied that declares no variants (`openai/gpt-4o`, and
|
|
141
|
+
the shipped aliases `minimax`, `qwen-coder`, `qwen-flash`) is now refused before anything is sent
|
|
142
|
+
when a budget is set, exactly as it already was without one — and the five-second wait disappears
|
|
143
|
+
with it, so a 12-leg fanout on such a model no longer burns 12 × 5 s before failing. What stops
|
|
144
|
+
being sent is a level nobody could ever use: probe F3/M7 measured an undeclared variant as a
|
|
145
|
+
certain no-op on the wire, echoed back on the assistant message. The no-budget path is unchanged
|
|
146
|
+
except for four zero-context openai image rows (`chatgpt-image-latest`, `gpt-image-1.5`,
|
|
147
|
+
`gpt-image-1-mini`, `gpt-image-2`), which are now refused rather than silently no-op'd. A model
|
|
148
|
+
the engine has NOT learned yet is unchanged bit for bit: the bounded wait, then an unverified send
|
|
149
|
+
with `variantUnverified: true` and the `Notice:` line. Under a budget, a stale bundled catalogue
|
|
150
|
+
can now refuse a level the next run accepts — the refusal says so. The `ambiguous` key is gone
|
|
151
|
+
from `sentVariant` (it existed only on this unreleased branch, in no schema or document). C1 is
|
|
152
|
+
not fully closed: its second clause — a genuinely cold direct-Anthropic model with an
|
|
153
|
+
`enabled + budgetTokens` entry still sending `24,000 + 16,000 = 40,000` marked only
|
|
154
|
+
`variantUnverified: true` — survives, because N exists only in the post-spawn dump; the filed
|
|
155
|
+
pre-spawn fit (descriptor = budget − N, proven M17) is the answer and is not built here.
|
|
156
|
+
- **`outputBudget` below 32,000 now reaches every leg but a direct `openai` one (#218 PR 2; the
|
|
157
|
+
openai exception measured in PR 4, M5/M13/M22).** On 4.9.3 a budget applied only to routes whose
|
|
158
|
+
ceiling the catalog knew; rows it could not clamp kept the engine's 32,000. The engine flag now
|
|
159
|
+
carries the budget to those rows too — clamped by the engine's own catalog where it knows the
|
|
160
|
+
model (K5, K12), as-is on a model it does not (J2, K13). A user who set `outputBudget: 8000` on
|
|
161
|
+
4.9.3 sees those rows reserve 8,000 after upgrading — except a direct `openai` route, which
|
|
162
|
+
carries no reservation field at all. A model neither catalog knows receives a raised budget as-is
|
|
163
|
+
and may be refused by a provider that enforces its ceiling — loudly; `doctor`'s `output-budget`
|
|
164
|
+
row names such routes.
|
|
165
|
+
- **A length-stopped leg with no answer text is an error, not a completion (#218 PR 3).** On 4.9.3
|
|
166
|
+
such a leg ended `complete` with an empty summary (a council dropped it as "ended 'complete' with
|
|
167
|
+
no usable output"; `amicus start --no-ui` exited 0 with "No Output") or, when the provider streamed
|
|
168
|
+
its reasoning, `complete` with the *thinking* as the review — adjudicated as one. It now ends
|
|
169
|
+
`error` with the `OUTPUT_LENGTH:` reason — decided on the message the engine finalized, so a tool
|
|
170
|
+
loop's earlier text neither hides the death nor, once promoted reasoning is replaced by the
|
|
171
|
+
answer, pollutes a kept review — and a council treats it as any other dead leg: the once-only
|
|
172
|
+
Stage-1 retry fires and the run degrades if the retry dies too. For the no-output shape that retry
|
|
173
|
+
is unchanged from 4.9.3; for the promoted-thinking shape it is new — 4.9.3 counted that leg as a
|
|
174
|
+
review and never retried it, so such a seat now bills one more reservation. `start --no-ui` exits
|
|
175
|
+
1 with the reason. The ledger row for such a leg reads `status: "error"` where it read `complete`.
|
|
176
|
+
- **A `--thinking` level the model does not declare is refused, not adjusted (#218 PR 4).** On 4.9.3
|
|
177
|
+
the CLI rewrote `minimal` to `low` and any other unsupported level to `medium` from a static
|
|
178
|
+
per-model table (gpt-5 "without minimal", gemini "with everything") with a warning, then sent the
|
|
179
|
+
result as a field the engine never read; the table is gone (a static guess that the engine's
|
|
180
|
+
catalogue confirms on one row and contradicts on the other: both exclude `minimal` for gpt-5,
|
|
181
|
+
while the table gave gemini `none` and `xhigh` and gemini-3.6-flash declares neither — M0), the
|
|
182
|
+
CLI checks only the vocabulary, and the model's own declaration decides at send time. Solo session
|
|
183
|
+
metadata records `thinking` only when one was requested — it used to record `medium` for every
|
|
184
|
+
run, including runs that sent nothing. That stamp landed on EVERY session's metadata, **a fanout
|
|
185
|
+
leg's included**, so a pack saved with `pack save --from-run` on 4.9.3 or earlier copied that
|
|
186
|
+
`medium` into its `options.thinking` on **fanout packs as well as solo ones** — where it then
|
|
187
|
+
applies to every seat of the bench at once. Such a pack now SENDS it: refused on every model that
|
|
188
|
+
does not declare `medium` (kimi-k3, Haiku 4.5, deepseek-v4-pro among the curated routes), and on
|
|
189
|
+
a model that DOES declare it the level really goes out — so a pack that was inert can now change
|
|
190
|
+
a run's cost and behaviour. Delete the key or re-save the pack from a run that requested a level.
|
|
191
|
+
MCP `thinking` parameters no longer claim "Default: medium": omitted means nothing is sent and the provider's
|
|
192
|
+
default effort governs (on the direct OpenAI route the engine sends `medium` itself, M13). The one
|
|
193
|
+
case the removal makes worse is the row the table got right: `--thinking minimal` on a gpt-5 route
|
|
194
|
+
used to be rewritten to `low`, a level the engine really declares, and is now refused — loudly,
|
|
195
|
+
before anything is sent, which is the trade this release makes deliberately.
|
|
196
|
+
- `src/utils/http-get.js` now owns the always-resolves HTTPS GET that `model-fetcher.js` carried
|
|
197
|
+
inline; the failure vocabulary (`timeout` / `http-status` / `network-error` / `parse-error`) gains
|
|
198
|
+
one reason, `too-large`. A response-stream error mid-body and a synchronous throw from `https.get`
|
|
199
|
+
(a URL it cannot parse) now resolve as `network-error` instead of escaping the promise. Redirects
|
|
200
|
+
are opt-in per call (`followRedirects`, default off, so the keyed provider fetches are unchanged
|
|
201
|
+
and a 3xx stays their terminal `http-status` failure); with it on, up to two `https` redirects are
|
|
202
|
+
followed under one deadline for the whole chain, and a cross-origin hop carries only an allowlist
|
|
203
|
+
of headers (`user-agent`, `accept`, `accept-language`) so no credential can
|
|
204
|
+
follow a `Location` to another host. A redirect to a non-`https` target, one with no `Location`,
|
|
205
|
+
and a third hop are each an `http-status` failure whose `detail` names which, and every one of
|
|
206
|
+
those refusals releases the connection — the response is retired and the live request destroyed —
|
|
207
|
+
so a refused 3xx whose body never ends cannot hold the socket open after the promise has settled. Response bodies are capped at 16 MiB
|
|
208
|
+
(`maxBytes`); an over-size body is destroyed and reported as `too-large` rather than accumulated.
|
|
209
|
+
- CI council bench: `deepseek` moves from `deepseek-v4-pro` to `deepseek-v4-flash-0731` — 1.3M context
|
|
210
|
+
(was 1M), input ~$0.05/M and output ~$0.10/M on OpenRouter (was ~$0.69/M and ~$1.38/M), output
|
|
211
|
+
ceiling 131,072 (was 384,000). Bench-only; the shipped alias table is unchanged. The alias-shadow
|
|
212
|
+
notice now names `deepseek` beside `qwen` as a bench pin that differs from the shipped one.
|
|
213
|
+
- **`amicus continue` / `amicus resume` reject `--thinking` instead of ignoring it (#218 PR 4).**
|
|
214
|
+
The flag parses on every command (the unknown-flag gate is built from the whole usage string), but
|
|
215
|
+
neither handler ever read it and the vocabulary check runs only on `start` — so a level typed on a
|
|
216
|
+
continuation, valid or not, used to exit 0 having done nothing. Both now fail with `BAD_ARGS`
|
|
217
|
+
naming where the level belongs, the same way `--tag` already does. Each also prints a stderr
|
|
218
|
+
`Notice:` when the session being reopened records a level, naming it and saying the leg runs at
|
|
219
|
+
the provider's default — worded as what the metadata RECORDS, since 4.9.3 and earlier stamped
|
|
220
|
+
`medium` on every session typed or not, so an older session's `medium` may be that stamp rather
|
|
221
|
+
than a request. Forwarding a level on `continue` is filed, not built.
|
|
222
|
+
|
|
223
|
+
### Fixed
|
|
224
|
+
|
|
225
|
+
- **The curated `qwen` alias pointed at a model OpenRouter no longer lists.** Between 2026-09-04 and
|
|
226
|
+
2026-09-05 both OpenRouter and models.dev renamed `qwen/qwen3.8-max` to `qwen/qwen3.8-max-0902`;
|
|
227
|
+
the #218 PR 2 probe run caught it (a variant sent for the old id went silent, and its ceiling read
|
|
228
|
+
`0/0`). The pin now names the dated id. Found and fixed in PR #231; the CI bench map already
|
|
229
|
+
pinned `qwen3.8-27b` and is unchanged.
|
|
230
|
+
- **`startServer` read `config.json` twice for one budget.** The per-model descriptor and the engine
|
|
231
|
+
flag each called `loadConfig()`; a config write between the two reads could hand the engine a
|
|
232
|
+
descriptor from one budget and a flag from another (bounded — the engine takes the smaller — but
|
|
233
|
+
split). One read now feeds both (#218 PR 3).
|
|
234
|
+
- **CI council read its alias map from the PR's frozen base sha.** `council-review.yml` provisioned
|
|
235
|
+
`.github/amicus-ci-aliases.json` from `github.event.pull_request.base.sha`, which GitHub fixes at
|
|
236
|
+
PR creation — so a bench change merged to `main` afterwards never reached an open PR (PR #232's
|
|
237
|
+
round 3 still reviewed with the pre-#233 map). The map is now read from the base branch name,
|
|
238
|
+
which resolves to its current tip on every run; still never the PR head.
|
|
239
|
+
- **A cancelled council run discarded its spend receipt (#220).** The receipt step was gated on
|
|
240
|
+
`!cancelled()`, so the one path where the cost is least visible — a superseded or hand-cancelled
|
|
241
|
+
run — recorded nothing at all, though the legs it had launched still billed. The receipt now runs
|
|
242
|
+
on `always()`, and a ledger-only artifact fires ahead of it on the cancelled path so the smallest
|
|
243
|
+
upload takes the first claim on the runner's bounded post-cancellation grace; the full evidence
|
|
244
|
+
upload stays `!cancelled()`, because a truncated artifact is worse than none. It is best-effort by
|
|
245
|
+
construction and the step now says so rather than promising a record, and a test pins the
|
|
246
|
+
workflow's ledger path against the engine's own.
|
|
247
|
+
|
|
248
|
+
## [4.9.3] - 2026-08-28
|
|
249
|
+
|
|
250
|
+
*Doctor stops vouching for things it never checked.*
|
|
251
|
+
|
|
252
|
+
`doctor`'s `keys` row tested PRESENCE only, and `validateApiKey` was called at exactly two
|
|
253
|
+
save-time sites — so a key that rotted after it was entered was never re-checked. On the
|
|
254
|
+
reporting machine `doctor` printed a green row while the stored DeepSeek key returned 401 and
|
|
255
|
+
the catalog served zero deepseek rows. Closing that gap surfaced a family of the same shape:
|
|
256
|
+
several places reported health they had not established, and two of them were introduced by
|
|
257
|
+
the fixes for the others. Every one is now the same rule — a check that did not complete says
|
|
258
|
+
so, and only a definitive 401 is a verdict about a credential.
|
|
259
|
+
|
|
260
|
+
### Added
|
|
261
|
+
|
|
262
|
+
- **`outputBudget` (#218), opt-in, no default change.** Each council leg reserved
|
|
263
|
+
`max_tokens: 32000` regardless of the model's real ceiling, and OpenRouter validates that
|
|
264
|
+
RESERVATION against remaining credit *before* serving — so legs died in 2.2 s with zero
|
|
265
|
+
tokens and "You requested up to 32000 tokens, but can only afford 354". Set `outputBudget`
|
|
266
|
+
in `config.json` and each leg reserves `min(budget, that model's real ceiling)`; leave it
|
|
267
|
+
unset and every model is registered exactly as before. A new `maxOutputTokens` catalog field
|
|
268
|
+
(OpenRouter's `top_provider.max_completion_tokens`, present on 411 of 417 rows) supplies the
|
|
269
|
+
ceiling, and a model without one keeps the old behaviour rather than receiving a guess.
|
|
270
|
+
MEASURED in the pinned engine binary: `maxOutputTokens = Math.min(limit.output, 32000)`, so
|
|
271
|
+
this can only LOWER a reservation — a value at or above 32000 leaves it unchanged. It does
|
|
272
|
+
not address a reasoning-heavy leg spending its whole allowance and emitting nothing; that is
|
|
273
|
+
governed by reasoning effort, not by `max_tokens`, and no claim is made otherwise.
|
|
274
|
+
|
|
275
|
+
### Fixed
|
|
276
|
+
|
|
277
|
+
- **`doctor` re-validates stored API keys (#210).** New `key-auth` row probes every stored key
|
|
278
|
+
against its provider's own endpoint, in parallel — sequential 10 s timeouts would have added
|
|
279
|
+
~50 s to every run. Only HTTP 401 fails the check; a timeout, DNS failure, 5xx or 429 warns,
|
|
280
|
+
because being offline is not a rotted key and a false error sends someone to re-enter a
|
|
281
|
+
working one. A stored key for a provider with no validation endpoint warns rather than
|
|
282
|
+
reporting ok — it cannot be probed, so the check cannot vouch for it.
|
|
283
|
+
- **403 is no longer treated as a credential verdict.** Google returns 403 for "API not
|
|
284
|
+
enabled" and for quota; a WAF returns it for bot protection. It warns now, and `amicus key`
|
|
285
|
+
saves on it rather than refusing — as it does for 429 and 5xx. Only a definitive 401 blocks
|
|
286
|
+
a save, expressed as an allowlist so it cannot rot as new status codes appear.
|
|
287
|
+
- **The OpenRouter credit row means CHECKED.** `checkOpenRouterCredit` resolves `warning: null`
|
|
288
|
+
for a healthy account, for a skipped probe, and for every failure alike — so the row rendered
|
|
289
|
+
"credit ok" for an account nobody had reached, concealing quota exhaustion behind a green
|
|
290
|
+
line. It now distinguishes all three.
|
|
291
|
+
- **A key can no longer escape in an error message.** `https.get` can throw synchronously, and
|
|
292
|
+
the Google probe embeds the key in the URL as `?key=…` — so an error quoting that URL quoted
|
|
293
|
+
the key. Redaction happens at the source now, covering the raw, percent-encoded and
|
|
294
|
+
form-encoded spellings, which protects the two save-time call sites that have no handling of
|
|
295
|
+
their own: `electron/ipc-setup.js` returns the message to the renderer *and* logs it, and
|
|
296
|
+
`src/cli-handlers.js` awaits with no try/catch at all.
|
|
297
|
+
- **`validateApiKey` honours its "always resolves" contract (#224).** `req.on('error')` covered
|
|
298
|
+
the connection phase only; an error once the response existed — a socket reset mid-body — was
|
|
299
|
+
an unhandled `'error'` event, which Node turns into a THROW rather than a rejection: the
|
|
300
|
+
promise never settled and the process died. Both functions in the module handle it now, and
|
|
301
|
+
the message coercion itself can no longer throw for a null-prototype object or one whose
|
|
302
|
+
`toString` throws.
|
|
303
|
+
- **Diagnostics no longer make live authenticated requests outside the CLI.** Probes are opt-in,
|
|
304
|
+
enabled once by `bin/amicus.js`; a skipped probe is reported as unverified, never as healthy.
|
|
305
|
+
`AMICUS_NO_NETWORK_PROBES=1` forces them off.
|
|
306
|
+
- **The setup wizard renders the aliases you actually have (#213).** The alias editor grouped
|
|
307
|
+
rows from a hardcoded list of alias NAMES and iterated that whitelist rather than your
|
|
308
|
+
aliases, so any alias whose name missed the list rendered nowhere — including the `lmstudio`
|
|
309
|
+
local-provider alias and every `free-*` alias the `councils.free` preset references. Grouping
|
|
310
|
+
derives from the alias's route vendor now. Measured against a real 33-alias config: 21
|
|
311
|
+
rendered before, 33 after, none dropped, none duplicated.
|
|
312
|
+
- **A stale pin no longer looks like a recommendation (#211).** When nothing in the catalog
|
|
313
|
+
matched an alias's current value, the dropdown echoed that value back as a bare ungrouped
|
|
314
|
+
option — indistinguishable from a real offer, and observed presenting an id that exists on no
|
|
315
|
+
gateway above 13 genuine ones. It now sits in a labelled "Current — not found in catalog"
|
|
316
|
+
group. What gets saved is unchanged.
|
|
317
|
+
- **Alias names and routes are HTML-escaped** in the wizard; a quote in an alias name broke the
|
|
318
|
+
row's `data-alias` attribute.
|
|
319
|
+
|
|
320
|
+
### Changed
|
|
321
|
+
|
|
322
|
+
- **CI: the macOS/node-24 jest-worker `SIGSEGV` mitigation switches levers.** A fifth
|
|
323
|
+
occurrence landed with the 512 MB idle ceiling in force, so per the rule recorded beside it
|
|
324
|
+
the lever changes rather than the number: `--maxWorkers=1` now caps concurrent worker heaps.
|
|
325
|
+
A "Runner capacity" step reports cpus/mem on every leg, which established what five previous
|
|
326
|
+
hit records had assumed — macOS runners have 3 vCPU and 8 GB against 4 vCPU and 17 GB
|
|
327
|
+
elsewhere, so `--maxWorkers=2` would have been the default spelled out and changed nothing.
|
|
328
|
+
That leg runs ~4m → 6.5m as a result.
|
|
329
|
+
|
|
6
330
|
## [4.9.2] - 2026-08-27
|
|
7
331
|
|
|
8
332
|
*The instrument existed; nothing could read it.*
|
package/README.md
CHANGED
package/bin/amicus.js
CHANGED
|
@@ -16,6 +16,12 @@ if (!_nv.ok) { process.stderr.write(_nv.message + '\n'); process.exit(1); }
|
|
|
16
16
|
const { loadCredentials } = require('../src/utils/env-loader');
|
|
17
17
|
loadCredentials();
|
|
18
18
|
|
|
19
|
+
// Diagnostics probe live provider endpoints with those keys. That is allowed
|
|
20
|
+
// HERE and nowhere else: utils/live-probes.js defaults to off, so a module
|
|
21
|
+
// required outside this CLI (a test, a script) can never spend them. A skipped
|
|
22
|
+
// probe is reported as unverified, never as healthy — see live-probes.js.
|
|
23
|
+
require('../src/utils/live-probes').enableLiveProbes();
|
|
24
|
+
|
|
19
25
|
const { parseArgs, getUsage, getCommandNames } = require('../src/cli');
|
|
20
26
|
const { handleSetup, handleAbort, handleUpdate, handleMcp, handleKey } = require('../src/cli-handlers');
|
|
21
27
|
const { handleStart, handleFanout, handleRead } = require('../src/cli-handlers-run');
|
package/docs/ROADMAP.md
CHANGED
|
@@ -13,11 +13,12 @@ lives under **Backlog (tracked, not scheduled)** with everything else that is re
|
|
|
13
13
|
Nothing about the content changed and no judgment about its value is implied; only its status. When
|
|
14
14
|
an org buyer and the org to support them exist, it earns a number then.
|
|
15
15
|
|
|
16
|
-
Amicus is at **v4.9.
|
|
17
|
-
plumbing.
|
|
16
|
+
Amicus is at **v4.9.3** (2026-08-28). Each 4.x rev below leads with the benefit, not the
|
|
17
|
+
plumbing; the patch releases since v4.9.0 carry no section of their own, because they fixed
|
|
18
|
+
defects rather than adding scope — see `CHANGELOG.md` for what each one contained.
|
|
18
19
|
|
|
19
|
-
**Status:** v4.0 through **v4.9.0** have **shipped
|
|
20
|
-
landed, not a plan. Composition — the scope that
|
|
20
|
+
**Status:** v4.0 through **v4.9.0** have **shipped**, plus the v4.9.1–v4.9.3 patch releases —
|
|
21
|
+
everything on this page is a record of what landed, not a plan. Composition — the scope that
|
|
21
22
|
carried the number v4.6 here until the degrade-announcement-invariant milestone took the v4.6.0
|
|
22
23
|
release (2026-08-02) — is now an unscheduled candidate for the next rev, tabled in its own section
|
|
23
24
|
below (dropped from v4.7, 2026-08-05); its contents are decided at kickoff per the anti-rot rule,
|