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
package/docs/configuration.md
CHANGED
|
@@ -49,6 +49,180 @@ for the pre-normalization failure mode and the manual fix if you've disabled the
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
+
## Output budget (`outputBudget`)
|
|
53
|
+
|
|
54
|
+
Each council leg reserves a `max_tokens` allowance before the model runs. Amicus previously handed
|
|
55
|
+
OpenCode no per-model limit at all, so OpenCode's own fixed default — **32,000** — governed every
|
|
56
|
+
leg regardless of the model's real ceiling.
|
|
57
|
+
|
|
58
|
+
That reservation is not free. OpenRouter validates it against your remaining credit *before* serving,
|
|
59
|
+
so a leg that would have emitted 800 tokens gets refused outright for asking to reserve 32,000:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
This request requires more credits, or fewer max_tokens.
|
|
63
|
+
You requested up to 32000 tokens, but can only afford 354
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`outputBudget` sets the reservation, in either direction. Every leg reserves
|
|
67
|
+
`min(outputBudget, that model's real ceiling)` wherever a ceiling is known — by the Amicus catalog
|
|
68
|
+
(a per-model `limit` descriptor) or, failing that, by the engine's own catalog (every engine Amicus
|
|
69
|
+
starts gets `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` set to the budget) — except on the direct
|
|
70
|
+
`openai` route, which carries no reservation field at all (M5/M13/M22). A model neither catalog
|
|
71
|
+
knows receives the budget itself, exactly as it received the raw 32,000 before.
|
|
72
|
+
|
|
73
|
+
If you set `outputBudget` on 4.9.3, one thing changes on upgrade: a budget below 32,000 now also
|
|
74
|
+
reaches routes the catalog cannot clamp — an unrefreshed direct row (other than `openai`, which no
|
|
75
|
+
budget reaches — M13/M22), a local-provider model, anything the catalog lacks — which 4.9.3 left at
|
|
76
|
+
the engine's 32,000. Those legs reserve `min(budget, the ceiling the engine's own catalog knows)`
|
|
77
|
+
(K12: 8,000 on a bare kimi row), or the budget as-is where the engine knows the model no better
|
|
78
|
+
(K13).
|
|
79
|
+
|
|
80
|
+
Setting `outputBudget` can only make the `--thinking` guard stricter or leave it unchanged, never
|
|
81
|
+
looser: the guard reads the engine's own row for the model, not the descriptor a budget makes Amicus
|
|
82
|
+
write, so whether a level is declared is decided identically with and without a budget. The one
|
|
83
|
+
refusal a budget can add is `VARIANT_OVER_BUDGET` — a declared level whose own thinking budget would
|
|
84
|
+
push the reservation past the budget — and that check exists only because a budget was set (council
|
|
85
|
+
#235 r3).
|
|
86
|
+
|
|
87
|
+
| Setting | Values | Default | Effect |
|
|
88
|
+
|---------|--------|---------|--------|
|
|
89
|
+
| `outputBudget` (config.json, top-level) | positive integer | *unset* | Per-leg output reservation, clamped to each model's real ceiling wherever one is known. Unset means no limit is sent and no engine flag is set — OpenCode's 32,000 default applies, exactly as before. |
|
|
90
|
+
| `modelsDevCeilings` (config.json, top-level) | `true` / `false` | `true` | Fill direct-provider context/ceiling numbers from models.dev at refresh. Set `false` to never contact models.dev; the anthropic / deepseek direct rows then carry no ceiling in the Amicus catalog and are clamped by the engine's own catalog instead (Google publishes its own ceiling and OpenRouter rows keep OpenRouter's); the direct `openai` rows send no output reservation at all regardless (#218 PR 4). |
|
|
91
|
+
|
|
92
|
+
`modelsDevCeilings` is the opt-out for the one third-party lookup a refresh makes. Only a literal
|
|
93
|
+
`false` turns it off; the key being absent means it runs. Even with it on, the call is **skipped
|
|
94
|
+
automatically when no candidate row is missing a number** — a refresh with nothing to fill never
|
|
95
|
+
contacts models.dev at all. `amicus models --refresh` names whichever of those happened on its
|
|
96
|
+
`Ceilings:` line.
|
|
97
|
+
|
|
98
|
+
Set it by hand-editing `~/.config/amicus/config.json`:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "outputBudget": 8000 }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`amicus doctor`'s `output-budget` row then says what the value reaches: how many of your alias
|
|
105
|
+
routes the catalog can clamp it to, whether an `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` you exported
|
|
106
|
+
yourself is being honoured or overridden, and a malformed value in either place — the engine falls
|
|
107
|
+
back to 32,000 *silently* on those (measured), so the doctor row is where it surfaces.
|
|
108
|
+
|
|
109
|
+
Five things worth knowing before you set it. Every number below was measured on the wire by
|
|
110
|
+
`scripts/probe-max-tokens.js` against the pinned engine, or read in the pinned binary where it says
|
|
111
|
+
so; the row ids refer to the four probe tables filed in `BACKLOG.md` under "v4.9.4 records" (#218
|
|
112
|
+
P1, PR 2, PR 3 and PR 4).
|
|
113
|
+
|
|
114
|
+
- **Above 32,000 it is the engine flag doing the work.** OpenCode computes
|
|
115
|
+
`Math.min(limit.output, OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX)` with the flag defaulting to
|
|
116
|
+
32,000 (read in the pinned binary; the rows below are its wire effects), so Amicus sets that
|
|
117
|
+
flag to your budget for every engine it starts — around the spawn only. It never lands in
|
|
118
|
+
your shell, and a value you exported yourself is honoured untouched when `outputBudget` is
|
|
119
|
+
unset and overridden (for Amicus-started engines only) when it is set. A budget of 100,000 on a
|
|
120
|
+
model with a 943,718 ceiling reserves 100,000 (K6); on a model whose ceiling is 64,000 it
|
|
121
|
+
reserves 64,000 (K5). The flag is experimental on the engine's side, and a malformed value is
|
|
122
|
+
ignored without a word (D1/D2): CI runs the probe's flag rows on every push, and the full matrix
|
|
123
|
+
is re-run and re-filed after every engine bump. An SDK bump is covered by a test that drives the
|
|
124
|
+
real SDK against a fake engine and checks the flag reached the spawn.
|
|
125
|
+
- **It clamps best with a catalog that knows each model's ceiling.** Run `amicus models --refresh`
|
|
126
|
+
after setting it. OpenRouter rows carry OpenRouter's own ceiling and Google rows carry Google's;
|
|
127
|
+
the direct `openai` / `anthropic` / `deepseek` rows — and any other row whose number the provider
|
|
128
|
+
left empty or unusable — are filled from [models.dev](https://models.dev) at refresh. models.dev
|
|
129
|
+
fills a field only where the provider gave no usable positive integer (a null, a zero, a negative
|
|
130
|
+
or a malformed number), and never overwrites a usable provider value; the `openrouter/openrouter/*`
|
|
131
|
+
meta-routers and local-provider rows are never filled at all. The refresh output says how many
|
|
132
|
+
rows were filled or why none could be. One route no budget reaches: the direct `openai` provider —
|
|
133
|
+
the engine drives it through the Responses API, whose request carries no output-limit field at all
|
|
134
|
+
(M5 bare; M13 with the descriptor and the flag both at 8,000; M22 for gpt-4o), so neither lever
|
|
135
|
+
changes what goes out there; `doctor` lists those routes apart from the clamped and unclamped
|
|
136
|
+
counts. A route the Amicus catalog cannot clamp still gets the budget through the engine flag,
|
|
137
|
+
clamped by the engine's own catalog where it knows the model (K5: 100,000 → 64,000 on a bare haiku
|
|
138
|
+
row; K12: 8,000 on a bare kimi row, passed through under its ceiling); a model neither knows
|
|
139
|
+
receives the budget as-is (K13). That is the one way a raised budget can fail where the 32,000
|
|
140
|
+
default did not: a custom or local model neither catalog knows is asked for the full budget, and a
|
|
141
|
+
provider that enforces its ceiling refuses the request — loudly, with the provider's own error on
|
|
142
|
+
the leg, never silently. `amicus doctor` names such routes; lower the budget if you have one, or
|
|
143
|
+
give the model a catalog entry.
|
|
144
|
+
- **A thinking variant leaves the budget alone on every route but one.** `--thinking` now reaches
|
|
145
|
+
the engine (#218 PR 4) as its `variant` field, and the probe measured the reservation with a
|
|
146
|
+
variant in play on each provider whose request carries one (the direct openai route carries
|
|
147
|
+
none — M5/M13/M22): OpenRouter (M1: 8,000 stays 8,000 under `low`; M9: OpenRouter's Anthropic
|
|
148
|
+
row sends 32,000 with `high` on both of the engine's catalogues — a variant adds nothing there),
|
|
149
|
+
direct Google (M15: 8,000), direct DeepSeek (M16: 8,000) and an adaptive-thinking Anthropic model
|
|
150
|
+
(M10b: `claude-sonnet-5` at 8,000) all hold it. The one shape that does not is a direct
|
|
151
|
+
Anthropic variant declared as `thinking: {type: 'enabled', budgetTokens: N}` — today Haiku 4.5
|
|
152
|
+
(`high` 16,000, `max` 31,999) and Opus 4.5 (16,000 for `low`, `medium` and `high`) — where the
|
|
153
|
+
engine adds N on top (M2, measured on Haiku: 24,000 + 16,000 = 40,000; K2 — Opus 4.5 declares
|
|
154
|
+
the same `enabled` + `budgetTokens` shape, M0, and is refused under the same condition) and clamps the sum
|
|
155
|
+
to the model's ceiling (K3/K4/K10). Amicus cannot lower the descriptor by N before the spawn (N
|
|
156
|
+
is the engine's own number, read only from a running engine, and nothing changes a descriptor
|
|
157
|
+
afterwards — a runtime `PATCH /config` is accepted, changes nothing the engine serves, and
|
|
158
|
+
writes a `config.json` into the engine's working directory, M3/M4/M11), so with a budget below
|
|
159
|
+
that model's ceiling such a leg is **refused before anything is sent**, with the reservation it
|
|
160
|
+
would have made and three ways out: raise the budget to at least the ceiling (the sum is then
|
|
161
|
+
clamped to it, K4), route the model through OpenRouter (M1, M9), or use an adaptive-thinking
|
|
162
|
+
model (M10b). With no budget set the engine's own behaviour applies (32,000 + N, H3/H4). The
|
|
163
|
+
exact fit — a descriptor lowered by N lands the sum exactly on the budget (M17: 8,000 + 16,000
|
|
164
|
+
= 24,000) — is filed in the BACKLOG as the follow-up it would take.
|
|
165
|
+
- **The reservation comes out of the context window.** Input plus `max_tokens` has to fit the
|
|
166
|
+
window, and the engine subtracts this same reservation from the window before it decides to
|
|
167
|
+
compact (read in the pinned binary's `SessionCompaction.isOverflow`, not wire-measured: that is
|
|
168
|
+
the branch for a model without `limit.input`; a model with one loses at most 20,000, and a
|
|
169
|
+
`compaction.reserved` config overrides both). A budget of 100,000 leaves a 131,072-context
|
|
170
|
+
model 31,072 tokens for the prompt. `amicus doctor` warns when a budget takes at least half of
|
|
171
|
+
any alias route's window.
|
|
172
|
+
- **When a leg hits it, the run says so.** The engine records `finish: 'length'` on the leg's
|
|
173
|
+
assistant message whenever the provider stopped at the reservation (A, H1, L1–L4 — both provider
|
|
174
|
+
families). A leg whose finalized message carries **no answer text** — the whole reservation went
|
|
175
|
+
to reasoning, the #218 "Mode 2" rows (32,000 reasoning, 0–2 output, $0.63 billed) — now ends
|
|
176
|
+
`error` with a reason starting `OUTPUT_LENGTH:` that carries the engine's reasoning/output counts
|
|
177
|
+
for the leg and the budget in force (or the ambient `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` the
|
|
178
|
+
engine was started with, when no budget is set); it used to end `complete` with an empty summary
|
|
179
|
+
or, when the provider streamed the reasoning, with its *thinking* promoted to the review (L2/L4).
|
|
180
|
+
A leg whose finalized message carries answer text keeps its review — the answer text (a tool
|
|
181
|
+
loop's earlier answer text included; reasoning an earlier message promoted as a stand-in is
|
|
182
|
+
dropped the moment real answer text arrives) — and a council prints a `Note:` on the
|
|
183
|
+
`output-truncated` channel — informational, the exit code does not move — and marks the review as
|
|
184
|
+
cut in the chair packet's header. The counts are reported, never decided on: on OpenAI-compatible
|
|
185
|
+
routes the engine subtracts reasoning from completion (L3: 8 = 40 − 32); on the direct Anthropic
|
|
186
|
+
route it reports no split (L4: 24,000 output / 0 reasoning). Two limits: a leg whose hidden
|
|
187
|
+
reasoning outlasts the no-output backstop window dies under `NO_OUTPUT_BACKSTOP` first (the
|
|
188
|
+
message has not finalized yet), and the Note is Stage-1 only — a judge, chair or debate leg cut at
|
|
189
|
+
its reservation gets the death name but no Note. Separately, L5 settles the catalog question PR 2
|
|
190
|
+
parked: a descriptor above the engine's own ceiling is clamped to that ceiling with a thinking
|
|
191
|
+
variant (K10: 70,000 + 31,999 → 64,000) and without one (L5: 70,000 + flag 100,000 → 64,000 on
|
|
192
|
+
haiku), so a catalog row whose ceiling exceeds the engine's is harmless on the
|
|
193
|
+
wire. It is not harmless to #218 PR 4's `VARIANT_OVER_BUDGET` fit, which judges against the
|
|
194
|
+
ceiling **Amicus's own catalog** carries for the model — its row's `maxOutputTokens`, the
|
|
195
|
+
number a budget-derived descriptor is clamped TO, not the value that descriptor carries —
|
|
196
|
+
because once a budget is set `/config/providers` echoes that descriptor back (M3) and the
|
|
197
|
+
engine's own ceiling is no longer readable there. (With no row for the model the fit falls back
|
|
198
|
+
to the dump's own value, which is then the engine's own ceiling — the descriptor was bare,
|
|
199
|
+
K5/K12.) A divergence therefore costs something in BOTH directions. A row ABOVE the engine's
|
|
200
|
+
ceiling can refuse a leg the engine would have run: a haiku row of 100,000 with `outputBudget`
|
|
201
|
+
70,000 refuses `high` on an 86,000 reservation it would never have made, where K10 just above
|
|
202
|
+
measures the engine clamping that same 70,000 descriptor — under a LARGER 31,999-token
|
|
203
|
+
variant — to 64,000, comfortably under the budget. A row BELOW it goes silent for any budget
|
|
204
|
+
between the two: the guard is `budget < ceiling`, so no fit runs; the descriptor Amicus writes
|
|
205
|
+
is the row itself (`min(ceiling, budget)`) and the engine adds N on top of THAT, so the leg
|
|
206
|
+
overshoots the budget whenever N exceeds the gap. The two catalogs can disagree without either
|
|
207
|
+
being stale in the ordinary sense: the engine serves its bundled catalogue until its startup
|
|
208
|
+
models.dev fetch lands, which moved a measured ceiling by 104,858 tokens (kimi 1,048,576 cold
|
|
209
|
+
vs 943,718 warm), so `amicus models --refresh` can open the gap rather than close it. No
|
|
210
|
+
`doctor` row detects either direction: `output-budget` compares no ceiling against the engine's
|
|
211
|
+
(it counts the routes with a known catalog ceiling, and warns only on starvation or a route
|
|
212
|
+
without one), and `catalog` is an age heuristic that compares no numbers.
|
|
213
|
+
|
|
214
|
+
This addresses reservation *rejections* and *clips*. It does **not** stop a reasoning-heavy model
|
|
215
|
+
from spending its whole allowance on reasoning and emitting nothing — that is governed by reasoning
|
|
216
|
+
effort — which `--thinking` now delivers to the engine as its `variant` field (#218 PR 4), checked
|
|
217
|
+
against what the model declares before anything is sent, on solo and fanout legs; a council seat has
|
|
218
|
+
no effort knob yet (filed).
|
|
219
|
+
Lowering the budget makes such a leg fail faster and cheaper; raising it gives the reasoning more
|
|
220
|
+
room; neither makes it produce output — but since #218 PR 3 the failure is at least *named*: the leg
|
|
221
|
+
ends `error` with an `OUTPUT_LENGTH:` reason instead of `complete` with nothing, or with its thinking
|
|
222
|
+
as the review (see [Troubleshooting](./troubleshooting.md#headless-leg-fails-with-output_length)).
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
52
226
|
## Routing
|
|
53
227
|
|
|
54
228
|
`routing.prefer` in `config.json` sets the global default gateway policy; `--gateway` (CLI) or the
|
|
@@ -154,7 +328,7 @@ These variables control the polling loop that drives headless sessions. The defa
|
|
|
154
328
|
| `AMICUS_STABLE_IDLE_POLLS` | Number of consecutive idle polls required when no explicit completion signal is received (approximately 60 s at the 2 s default). This is the fallback heuristic for models or SDK versions that don't emit a clean completion event. | `30` |
|
|
155
329
|
| `AMICUS_MAX_CONSECUTIVE_POLL_FAILURES` | Consecutive poll failures before the headless runner bails. At the 2 s interval this is approximately 30 s. Prevents a dead server from burning the full session timeout on futile polls. | `15` |
|
|
156
330
|
| `AMICUS_TOOL_CALL_STALL_MS` | How long a tool call may sit pending with **no** result and no output growth before the leg is failed with `Tool call stalled: <tool>` and its OpenCode session aborted. This is the wedge guard: it targets a leg producing nothing at all, and it is skipped while a tool-settle deferral is active (`AMICUS_TOOL_SETTLE_GRACE_MS` owns that decision instead, and ends in a completion rather than a failure). **`0` is ignored** — it falls back to the default rather than disabling the guard, because a `0` threshold would kill every leg on its first poll. There is no way to switch this off; raise it if you legitimately run very long single tool calls. | `180000` |
|
|
157
|
-
| `AMICUS_NO_OUTPUT_BACKSTOP_MS` | Fail a headless leg fast when the model has produced no output, reasoning, or tool calls for this long — the "accepted but not serving" class. Disarms permanently on the first sign of activity, so slow cold-prefill local models are unaffected. **Set `0` (or negative) to disable the backstop entirely** — silent legs then run to the ordinary timeout. | `300000` |
|
|
331
|
+
| `AMICUS_NO_OUTPUT_BACKSTOP_MS` | Fail a headless leg fast when the model has produced no output, reasoning, or tool calls for this long — the "accepted but not serving" class. Disarms permanently on the first sign of activity, so slow cold-prefill local models are unaffected. **Set `0` (or negative) to disable the backstop entirely** — silent legs then run to the ordinary timeout. A window that fires while a `--thinking` leg is still inside its declaration wait (#218 PR 4; bounded at five seconds — one `/config/providers` read on a warm, declared model, the full wait only on a cold or unknown one) cuts that wait short: the leg dies `NO_OUTPUT_BACKSTOP` before the level is validated and nothing is sent. Each individual read carries its own deadline — two seconds, or whatever is left of the five, whichever is shorter — so a catalogue endpoint that accepts and never answers cannot stretch that wait past it (council #235 r2, r4). A request already in flight when the backstop fires may still reach the provider — the backstop has never been able to recall a send (the v4.6.2 orphaned-promise design) — and the record then omits the `variant` it cannot vouch for. | `300000` |
|
|
158
332
|
| `AMICUS_USAGE_SETTLE_POLLS` | How many extra `getMessages` reads run **after** a leg has already finished, to catch provider usage/cost that lands milliseconds after the completion signal (measured: real paid legs losing their cost by 29 ms and 155 ms). The loop breaks early as soon as every assistant message carries usage, so the common case is one extra read. **Set to `0` to disable the reconciliation entirely** — legs then report whatever usage was present at completion, which can be `$0` on a leg that really did cost money. | `3` |
|
|
159
333
|
| `AMICUS_USAGE_SETTLE_INTERVAL_MS` | Delay between those settle reads. **`0` is honoured and means no delay** — the reads run back to back. It does **not** disable the reconciliation (that is `AMICUS_USAGE_SETTLE_POLLS=0`); it only removes the gap between attempts. | `400` |
|
|
160
334
|
| `AMICUS_USAGE_SETTLE_CALL_TIMEOUT_MS` | Per-call deadline for a settle read and for the child-session (subagent) spend walk. Deliberately much tighter than `AMICUS_POLL_CALL_TIMEOUT_MS`: the leg is already finished, so a hung read must not add 30 s × 3 to a run's wall time. The effective value is the **smaller** of this and `AMICUS_POLL_CALL_TIMEOUT_MS`, so raising it above that has no effect. **`0` is honoured and means no timer is armed at all** — a hung settle read or subtree walk would then wait indefinitely. | `5000` |
|
package/docs/council.md
CHANGED
|
@@ -343,6 +343,15 @@ them their own **Notes:** list and keeps them out of `## What was lost`. Related
|
|
|
343
343
|
[`amicus council stats`](#amicus-council-stats) on an empty ledger now names where rows come from
|
|
344
344
|
instead of implying that no council ever ran.
|
|
345
345
|
|
|
346
|
+
**A review cut at its output reservation is announced, not lost** (#218 PR 3). When a Stage-1 leg's
|
|
347
|
+
assistant message carries `finish: 'length'` and still delivered answer text, the run prints a
|
|
348
|
+
`Note:` on the `output-truncated` channel (`kind: "info"`, so it never degrades the run or moves the
|
|
349
|
+
exit code) naming the seat and the engine's reasoning/output token counts for the leg, with `Try:
|
|
350
|
+
raise outputBudget…`. The chair packet marks that review too — its header reads `--- Review by
|
|
351
|
+
<seat> — CUT at its output reservation (…) ---` — so the chair weighs it as partial. A length stop
|
|
352
|
+
with **no** answer text is a dead leg whose reason starts `OUTPUT_LENGTH:` — see
|
|
353
|
+
[Troubleshooting](./troubleshooting.md#headless-leg-fails-with-output_length).
|
|
354
|
+
|
|
346
355
|
**Read the tiers correctly.** A task run's report carries the line *"Tiers report peer concurrence,
|
|
347
356
|
never verification."* directly under the tier counts, and the chair's own packet carries the same
|
|
348
357
|
caveat beside the adjudications it is weighing. Peer agreement on a generative bench is correlation
|
package/docs/doc-system.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Documentation System
|
|
2
2
|
|
|
3
|
-
Canonical reference for the auto-documentation system that keeps
|
|
3
|
+
Canonical reference for the auto-documentation system that keeps the generated docs in sync with the codebase.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -10,7 +10,9 @@ Inspired by [OpenAI's Harness Engineering](https://openai.com/index/harness-engi
|
|
|
10
10
|
|
|
11
11
|
## Auto-Generated Sections
|
|
12
12
|
|
|
13
|
-
Sections between `<!-- AUTO:name -->` markers
|
|
13
|
+
Sections between `<!-- AUTO:name -->` markers are maintained by `scripts/generate-docs.js`. Do NOT edit these by hand.
|
|
14
|
+
|
|
15
|
+
Each marker is routed to the document that owns it by the `MARKER_TARGETS` table in `scripts/generate-docs.js`. A marker with no entry there defaults to CLAUDE.md. Today both markers live in [architecture-map.md](architecture-map.md), which keeps the ~15k-token generated inventory out of the file loaded into every agent context.
|
|
14
16
|
|
|
15
17
|
### Marker Format
|
|
16
18
|
|
|
@@ -22,27 +24,28 @@ Sections between `<!-- AUTO:name -->` markers in CLAUDE.md are maintained by `sc
|
|
|
22
24
|
|
|
23
25
|
### Current Markers
|
|
24
26
|
|
|
25
|
-
| Marker | Content | Source |
|
|
26
|
-
|
|
27
|
-
| `tree` | ASCII directory tree with JSDoc annotations | Filesystem scan of `bin/`, `src/`, `electron/`, `scripts/`, `evals/` (note: `tests/` is NOT included) |
|
|
28
|
-
| `modules` | Markdown table of all `src/**/*.js` modules | JSDoc description + `module.exports` extraction |
|
|
27
|
+
| Marker | Target document | Content | Source |
|
|
28
|
+
|--------|-----------------|---------|--------|
|
|
29
|
+
| `tree` | `docs/architecture-map.md` | ASCII directory tree with JSDoc annotations | Filesystem scan of `bin/`, `src/`, `electron/`, `scripts/`, `evals/` (note: `tests/` is NOT included) |
|
|
30
|
+
| `modules` | `docs/architecture-map.md` | Markdown table of all `src/**/*.js` modules | JSDoc description + `module.exports` extraction |
|
|
29
31
|
|
|
30
32
|
### How It Works
|
|
31
33
|
|
|
32
34
|
1. `scripts/generate-docs.js` scans the codebase
|
|
33
35
|
2. For each marker, it generates new content from the source of truth (filesystem, JSDoc)
|
|
34
|
-
3. It replaces the content between the open/close marker tags
|
|
35
|
-
4.
|
|
36
|
+
3. It replaces the content between the open/close marker tags, in whichever document `MARKER_TARGETS` routes that marker to
|
|
37
|
+
4. It auto-stages every document it wrote with `git add`
|
|
36
38
|
|
|
37
39
|
### Adding a New Auto-Generated Section
|
|
38
40
|
|
|
39
|
-
1. Add a new marker pair to
|
|
41
|
+
1. Add a new marker pair to the document that should own it (short lowercase name, no hyphens required but keep it terse):
|
|
40
42
|
```markdown
|
|
41
43
|
<!-- AUTO:my-section -->
|
|
42
44
|
<!-- /AUTO:my-section -->
|
|
43
45
|
```
|
|
44
46
|
2. Add a generator function in `scripts/generate-docs.js` (or `scripts/generate-docs-helpers.js`)
|
|
45
47
|
3. Add it to the `generated` map in `main()`
|
|
48
|
+
4. If it should NOT live in CLAUDE.md, add `markerName: 'relative/path.md'` to `MARKER_TARGETS`
|
|
46
49
|
4. Add tests in `tests/scripts/generate-docs.test.js`
|
|
47
50
|
|
|
48
51
|
## Cross-Link Validation
|
package/docs/testing.md
CHANGED
|
@@ -30,7 +30,7 @@ npm test tests/context.test.js # Single file (preferred during dev)
|
|
|
30
30
|
npm test -- --coverage # Coverage report
|
|
31
31
|
npm test -- -t "should extract" # Run tests matching pattern
|
|
32
32
|
|
|
33
|
-
npm run test:integration # Integration tier, KEYLESS — credentials scrubbed, paid suites skip (free, ~10s)
|
|
33
|
+
npm run test:integration # Integration tier, KEYLESS — credentials scrubbed, paid suites skip (free, ~10s plus the ~15s engine-flag canary)
|
|
34
34
|
npm run test:integration:live # Integration tier with real keys — SPENDS MONEY (serial: --runInBand is baked in)
|
|
35
35
|
npm run test:all # Unit + integration with real keys — SPENDS MONEY (not a gate anywhere)
|
|
36
36
|
npm run test:e2e:mcp # MCP E2E with real repomix (requires OPENROUTER_API_KEY)
|
|
@@ -103,6 +103,7 @@ Integration tests verify source-level invariants without mocking. They read actu
|
|
|
103
103
|
| Test File | What It Verifies |
|
|
104
104
|
|-----------|-----------------|
|
|
105
105
|
| `spawn-pipe-deadlock.integration.test.js` | `spawnSidecarProcess()` in `src/mcp-server.js` uses `ignore` (not `pipe`) for stdio, no `detached: true`, uses `child.unref()` |
|
|
106
|
+
| `probe-flag-canary.integration.test.js` | Engine honours `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` on the wire (five `scripts/probe-max-tokens.js` rows: A, C3, K6, K12, K13, in the probe's own keyless sandbox; ~15 s, zero spend). The pinned engine's reading of the flag `outputBudget` relies on above 32,000 — an engine bump that drops it turns CI's keyless job red |
|
|
106
107
|
| `electron-headless-mode.test.js` | `electron/main.js` gates `mainWindow.show()` behind `AMICUS_HEADLESS_TEST` env var |
|
|
107
108
|
|
|
108
109
|
These tests catch regressions in critical spawn/process configuration that would be hard to debug in production.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -252,6 +252,82 @@ The two clauses are independent: either can appear without the other, and the sk
|
|
|
252
252
|
|
|
253
253
|
---
|
|
254
254
|
|
|
255
|
+
## Headless Leg Fails with `OUTPUT_LENGTH`
|
|
256
|
+
|
|
257
|
+
**Symptom:** A headless leg (`amicus start --no-ui`, or one leg of a `fanout`/council run) ends `error` with a reason starting `OUTPUT_LENGTH: the provider stopped at the max_tokens reservation (finish 'length') and no answer text arrived — 32000 reasoning / 0 output tokens; outputBudget is unset — the engine's 32000 default reservation governs — raise outputBudget …`. The middle clause may instead read `and only reasoning was streamed, no answer text` — the provider showed its reasoning and nothing else. On a council run the seat is a dead leg and is retried once like any other death — unchanged for the no-output shape; new for the promoted-thinking shape, which 4.9.3 counted as a review — and if the retry dies too the seat is announced (`Notice: seat … did not review — the leg ended 'error': OUTPUT_LENGTH: …`).
|
|
258
|
+
|
|
259
|
+
**Cause:** The model spent its whole output reservation reasoning and never started the answer. Every clause is an observation, not a guess: `finish 'length'` is the engine's record of the provider's own stop reason; the two counts are the engine's token record for that message (on OpenAI-compatible routes reasoning and output are split; on the direct Anthropic route everything lands in `output` and reasoning reads 0 — the message still says `finish 'length'`); the budget clause is the value the engine serving the leg was started with — read once at spawn and carried on the server handle (`config.json` at that moment; a later edit does not change what the leg reserved); with no budget set it names the ambient `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` the engine was started with when there was one (a plain positive integer is honoured; anything else falls back to 32,000 — measured, D1/D2) and the engine's 32,000 default otherwise. A server handle amicus did not start carries no spawn value; then `config.json` is read when the death is named. The reservation is 32,000 by default (see [Output budget](./configuration.md#output-budget-outputbudget)). The likeliest driver is reasoning effort — OpenRouter applies a model's default effort when none is sent, and `--thinking` is not available on a council seat (on a solo or fanout leg it reaches the engine as its `variant` field since #218 PR 4) — so raising the budget gives the reasoning room, and the leg still bills for it.
|
|
260
|
+
|
|
261
|
+
**Confirm:** `finish: 'length'` is on the leg's `metadata.json` and on its row in `~/.config/amicus/spend-ledger.jsonl` (the `finish` field, present only when the engine recorded one), beside the token counts. A council run's `run.json` carries it on the leg document.
|
|
262
|
+
|
|
263
|
+
**Fix:** Raise `outputBudget` in `config.json` (the reservation is `min(outputBudget, the model's ceiling)`; see the five bullets in [Output budget](./configuration.md#output-budget-outputbudget)), or seat a model whose default effort fits the reservation. If the same seat dies the same way on its retry, the retry cost you the reservation twice — a council seat has no effort knob (filed), so raise the budget or drop the seat; on a solo or fanout leg, lower `--thinking` (it reaches the engine since #218 PR 4). A leg that took longer than `AMICUS_NO_OUTPUT_BACKSTOP_MS` to reason with nothing visible dies as `NO_OUTPUT_BACKSTOP` first, not as this — see that section above.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## `--thinking` Refused Before Anything Is Sent (`VARIANT_UNDECLARED`, `VARIANT_OVER_BUDGET`)
|
|
268
|
+
|
|
269
|
+
**Symptom:** A solo run (`amicus start --no-ui`) or a fanout leg ends `error` with a reason starting
|
|
270
|
+
`VARIANT_UNDECLARED: openrouter/moonshotai/kimi-k3 does not declare a
|
|
271
|
+
'medium' variant — the engine's catalogue lists low, high, max for it …` or `VARIANT_OVER_BUDGET:
|
|
272
|
+
the 'high' variant on anthropic/claude-haiku-4-5 carries a 16000-token thinking budget that the
|
|
273
|
+
engine adds ON TOP of the reservation on this route … with outputBudget 24000 this leg would
|
|
274
|
+
reserve 40000 …` (an interactive `amicus start` ends the same way with the reason prefixed
|
|
275
|
+
`Session setup failed: `). Nothing was billed: the request was never sent. On a fanout the other
|
|
276
|
+
legs run.
|
|
277
|
+
|
|
278
|
+
**Cause:** Since #218 PR 4, `--thinking` is sent as the engine's `variant` field and checked first
|
|
279
|
+
against what the engine's own catalogue declares for that model (`/config/providers`). A level the
|
|
280
|
+
model does not declare would be a silent no-op the engine still echoes on the artifact (probe
|
|
281
|
+
F3/M7), so it is refused. The declared set is read from the engine's catalogue at that moment: on a
|
|
282
|
+
cold `~/.cache/opencode` (first engine start after an install or a cleared cache) the bundled
|
|
283
|
+
catalogue can declare a different set than the live one for the same model (PR 4 record:
|
|
284
|
+
`openrouter/anthropic/claude-haiku-4.5` `high`/`max` cold, `low`/`medium`/`high` warm), so a level
|
|
285
|
+
refused on one run can be accepted on the next; the reason lists the set in force, or says the row
|
|
286
|
+
declares none at all. The dump says whose row it is: Amicus writes exactly one cell into a model's
|
|
287
|
+
entry (`limit`, at `src/utils/config.js:406`), so a row that also carries a release
|
|
288
|
+
date, family, display name, pricing or capabilities reads as a declaration, and an empty
|
|
289
|
+
`variants` there is a real answer (record M23). That dump is the engine's MERGED view of its own
|
|
290
|
+
catalogue and your `opencode.json`, so declaring model metadata there (a display name, family,
|
|
291
|
+
release date, pricing or capabilities) makes Amicus read the row as declared: a model you add that
|
|
292
|
+
way with no `variants` block is refused rather than waited for — add the block, or omit
|
|
293
|
+
`--thinking`. A declared
|
|
294
|
+
level whose entry carries a thinking budget the engine adds on top of the reservation (direct
|
|
295
|
+
Anthropic Haiku 4.5 — M2; Opus 4.5 declares the same shape, M0) is refused when `outputBudget`
|
|
296
|
+
is below the model's ceiling — or when no ceiling is declared anywhere, since nothing then clamps
|
|
297
|
+
the sum — because the leg would reserve more than the budget promises. The
|
|
298
|
+
reason names the model, the level, what the catalogue lists (or that it lists nothing at all), and
|
|
299
|
+
— for the budget case — the exact reservation, the budget and the ceiling.
|
|
300
|
+
|
|
301
|
+
**Confirm:** The reason is on the session's `metadata.json` (`reason`) and, for a fanout, on the
|
|
302
|
+
leg document in `wave.json`; the ledger row for it reads `status: "error"` with zero tokens and no
|
|
303
|
+
`variant` (nothing was spent, but the run is still attributed — its `cost.source` is `unknown`,
|
|
304
|
+
not a price). `amicus models` does not list variants; the declared set is in the reason itself.
|
|
305
|
+
|
|
306
|
+
**Fix:** Pick a level the reason lists, or omit `--thinking` to run at the provider's own default.
|
|
307
|
+
For `VARIANT_OVER_BUDGET`: raise `outputBudget` to at least the ceiling the reason names (the sum is
|
|
308
|
+
then clamped to the ceiling — the number the reason names is Amicus's own catalog's ceiling for the
|
|
309
|
+
model, which is what the fit can read once a budget is set, M3; for a model Amicus's catalog has no
|
|
310
|
+
row for it is instead the engine's own ceiling, straight from the dump, K5/K12). When neither
|
|
311
|
+
Amicus's catalog nor the dump declares a ceiling, the reason names none and says so: declare a
|
|
312
|
+
`limit` for the model in your `opencode.json` instead — its `output` has to leave room for the
|
|
313
|
+
thinking budget the engine adds on top, so at most `outputBudget` minus that budget (a value at or
|
|
314
|
+
above `outputBudget` silences the check without shrinking what the leg reserves, and declaring one
|
|
315
|
+
never clamps the sum — only the model's real ceiling does that, K3/K9/K10) — or clear
|
|
316
|
+
`outputBudget`. You can also route the model
|
|
317
|
+
through OpenRouter (a variant leaves the reservation at the budget there — M1, M9), or use an
|
|
318
|
+
adaptive-thinking model such as `claude-sonnet-5`. When the reason says to refresh first, the
|
|
319
|
+
ceiling it named came from Amicus's catalog rather than the engine, and the two can disagree. A
|
|
320
|
+
model the engine's catalogue does not know yet (a custom or local model, or one newer than the
|
|
321
|
+
engine's bundled list before its startup refresh lands) is never refused: the level is sent after a
|
|
322
|
+
bounded wait, the run logs `Variant sent unverified`, and the same note is printed as a `Notice:`
|
|
323
|
+
line on stderr — the structured log alone is dropped at the shipped default log level, so stderr
|
|
324
|
+
is where you will actually see it. A budget changes none of that: whether the level is declared is
|
|
325
|
+
decided identically with and without one, and `VARIANT_OVER_BUDGET` above is the only refusal a
|
|
326
|
+
budget can add. A model the engine's catalogue does not know yet still waits and is still sent
|
|
327
|
+
unverified, with `variantUnverified: true` on the leg document.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
255
331
|
## Multiple Active Sessions / Wrong Session Picked Up
|
|
256
332
|
|
|
257
333
|
**Symptom:** Amicus resumes or reads from the wrong session.
|
package/docs/usage.md
CHANGED
|
@@ -24,7 +24,7 @@ amicus setup --api-keys # Open just the API-key step
|
|
|
24
24
|
amicus setup --add-alias fast=google/gemini-3.1-flash-lite-preview # bare canonical, direct-first
|
|
25
25
|
amicus models # List the live catalog
|
|
26
26
|
amicus models --search gemini # Filter by substring
|
|
27
|
-
amicus models --refresh # Force-fetch from provider APIs
|
|
27
|
+
amicus models --refresh # Force-fetch from provider APIs (+ ceilings from models.dev)
|
|
28
28
|
amicus models --check # Audit aliases against catalog
|
|
29
29
|
amicus mcp # Start MCP server (stdio transport)
|
|
30
30
|
amicus update # Update to latest version
|
|
@@ -76,7 +76,7 @@ amicus start --model deepseek --prompt "Generate tests" --no-ui --timeout 30
|
|
|
76
76
|
| `--context-since <duration>` | Time filter (e.g. `2h`); overrides turns. | |
|
|
77
77
|
| `--context-max-tokens <N>` | Max context tokens. | 80000 |
|
|
78
78
|
| `--no-context` | Skip parent conversation history. | off |
|
|
79
|
-
| `--thinking <level>` | Reasoning effort: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`.
|
|
79
|
+
| `--thinking <level>` | Reasoning effort: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. A level the model does not declare is refused before anything is sent. A model the engine's catalogue does not know in time is sent the level unverified (`variantUnverified: true` on the record, and a `Notice:` line on stderr). Whether the level is declared does not depend on `outputBudget`; the one refusal a budget can add is `VARIANT_OVER_BUDGET`, when a declared level's own thinking budget would push the reservation past the budget. A model whose row the engine's own catalogue supplied is judged on that row: an empty variants set is a refusal, never an unverified send. Only a model the engine has no row for — its `/config/providers` entry is nothing but the descriptor Amicus registered — waits and is then sent unverified. `continue` and `resume` do not take a level — it belongs to the run that starts a session, and both reject `--thinking` rather than ignore it. | provider default (nothing sent) |
|
|
80
80
|
| `--summary-length <length>` | Fold summary verbosity: `brief`, `normal`, `verbose`. | `normal` |
|
|
81
81
|
| `--mcp <spec>` | Add an MCP server (`name=url` or `name=command`). | |
|
|
82
82
|
| `--mcp-config <path>` | Path to an `opencode.json` with MCP config. | |
|
|
@@ -303,7 +303,7 @@ Then invoke it with `--pack <name|path>` on `start` / `fanout` / `council run`
|
|
|
303
303
|
| `fanout` | `bench` (a saved council name, or an array of ≥2 members) | — | `timeout`, `maxCost`, `gateway`, `agent`, `thinking`, `summaryLength`, `noContext`, `contextTurns`, `contextMaxTokens` |
|
|
304
304
|
| `solo` | `model` | — | `timeout`, `maxCost`, `gateway`, `agent`, `thinking`, `summaryLength`, `noUi`, `noContext`, `contextTurns`, `contextMaxTokens` |
|
|
305
305
|
|
|
306
|
-
`council` packs do **not** accept `agent`, `thinking`, or `summaryLength` — they were inert on every surface (no council code path, CLI or MCP, ever reads a pack-filled one; the engine hardcodes agent `Plan`/summaryLength `verbose`), so they were dropped before release rather than shipped as dead weight a pack author would reasonably expect to work. A `council` pack that still sets one fails `pack save` with `PACK_INVALID`, naming the key. They remain valid, and functional, on `fanout`/`solo` packs.
|
|
306
|
+
`council` packs do **not** accept `agent`, `thinking`, or `summaryLength` — they were inert on every surface (no council code path, CLI or MCP, ever reads a pack-filled one; the engine hardcodes agent `Plan`/summaryLength `verbose`), so they were dropped before release rather than shipped as dead weight a pack author would reasonably expect to work. A `council` pack that still sets one fails `pack save` with `PACK_INVALID`, naming the key. They remain valid, and functional, on `fanout`/`solo` packs. Those releases recorded `medium` on EVERY session's metadata, **a fanout leg's included**, whether or not the flag was typed (a level nothing ever sent), so a pack saved with `pack save --from-run` on 4.9.3 or earlier copied it into `options.thinking` on **fanout packs as well as solo ones** — where it then applies to every seat of the bench at once. Such a pack now SENDS it: refused on every model that does not declare `medium` (kimi-k3, Haiku 4.5, deepseek-v4-pro among the curated routes), and on a model that DOES declare it the level really goes out — so a pack that was inert can now change a run's cost and behaviour. Delete the key or re-save the pack from a run that requested a level.
|
|
307
307
|
|
|
308
308
|
Every kind may also carry `description`, `version` (semver, default `1.0.0`), and `briefing.template` (a template **reference**, not rendered text — a pack never captures briefing prose).
|
|
309
309
|
|
|
@@ -381,7 +381,7 @@ Amicus does **not** ship a frozen table of model names. Aliases and validation r
|
|
|
381
381
|
```bash
|
|
382
382
|
amicus models # List the catalog
|
|
383
383
|
amicus models --search gemini # Filter by substring over id and name
|
|
384
|
-
amicus models --refresh # Force-
|
|
384
|
+
amicus models --refresh # Force-fetch from provider APIs (+ ceilings from models.dev)
|
|
385
385
|
amicus models --check # Audit your aliases against the catalog
|
|
386
386
|
amicus models --check --strict # + exit non-zero on curated per-gateway drift too
|
|
387
387
|
amicus models --check --live # + probe every stored alias with a real leg (spends)
|
|
@@ -520,6 +520,8 @@ usage error when combined with `continue`, `resume`, or `--retry-failed` — the
|
|
|
520
520
|
set. An untagged parent still leaves the key absent (not `null`) on the new metadata, and its spend
|
|
521
521
|
row still groups under `(unattributed)`, exactly as an untagged `start`/`fanout` would.
|
|
522
522
|
|
|
523
|
+
**Effort level on a reopen.** Neither `continue` nor `resume` sends an effort level — both reject `--thinking` outright and a level is not carried across a reopen — so when the session being reopened recorded one, each prints a `Notice:` on stderr naming the level it is dropping and saying the leg runs at the provider's default. That line reports what the session's metadata RECORDS, not what was typed: 4.9.3 and earlier stamped `thinking: medium` on every session whether or not the flag was given (and never sent it), so on a session from those releases the Notice names that stamp and says so in the same breath.
|
|
524
|
+
|
|
523
525
|
**`amicus status <id>` output.** Human-readable:
|
|
524
526
|
|
|
525
527
|
```
|
|
@@ -538,7 +540,7 @@ $ amicus status demo123 --json
|
|
|
538
540
|
"taskId": "demo123",
|
|
539
541
|
"status": "complete",
|
|
540
542
|
"elapsed": "5m 0s",
|
|
541
|
-
"version": "4.9.
|
|
543
|
+
"version": "4.9.4",
|
|
542
544
|
"model": "google/gemini-2.5-flash",
|
|
543
545
|
"phase": "terminal"
|
|
544
546
|
}
|
|
@@ -593,9 +595,11 @@ Runs every check below, in order, and prints a ✓/⚠/✗ line for each plus a
|
|
|
593
595
|
| `node` | Node.js ≥ 22.12 | error |
|
|
594
596
|
| `config-dir` | The resolved config directory | *(always ok)* |
|
|
595
597
|
| `keys` | At least one cloud-vendor key configured | error |
|
|
598
|
+
| `key-auth` **(#210)** | Every stored key is still accepted by its provider (skipped — reports `ok` — when no keys are stored) | error on a 401/403, warn when the probe can't reach the provider |
|
|
596
599
|
| `default-model` | Your default model alias resolves | error |
|
|
597
600
|
| `catalog` | Model-catalog cache present and within the 24h TTL | warn |
|
|
598
601
|
| `aliases` | Your configured aliases still resolve against the catalog | warn |
|
|
602
|
+
| `output-budget` **(#218)** | `outputBudget` is a positive integer and says which alias routes have a known catalog ceiling; whether an ambient `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` is honoured or overridden; flags a malformed `outputBudget`, and a malformed ambient flag whenever it would govern — beside a valid budget the ambient value is only reported as overridden (only a plain positive integer is measured to be honoured; `64000abc` and `0` fell back to 32,000 without a word) — and a budget that takes at least half of a route's context window; lists `openai/` alias routes apart (no output reservation on a leg that resolves direct, and which gateway a leg takes is a launch-time decision the row cannot read) | warn |
|
|
599
603
|
| `anthropic-base-url` | `ANTHROPIC_BASE_URL` isn't host-form (host-form 404s every direct-Anthropic leg unless normalized) | warn |
|
|
600
604
|
| `opencode-bin` | The OpenCode engine binary is on `PATH` | error |
|
|
601
605
|
| `engine-mcp` | The engine copy `npx -y amicus@latest mcp` would actually launch (catches a broken npx-cache copy a healthy local install would hide, and a version-skewed one — present but the wrong opencode-ai release vs. the global install, #133) | warn (error only if there's exactly one npx-cache copy and it's broken; also warns, never errors, on engine version skew between the npx copies and the global install) |
|
|
@@ -609,8 +613,12 @@ Runs every check below, in order, and prints a ✓/⚠/✗ line for each plus a
|
|
|
609
613
|
| `local-providers` **(v4.2)** | Every provider in `config.providers` is reachable | warn |
|
|
610
614
|
| `project-root` | Your cwd looks like a real project, not an app/install dir | warn |
|
|
611
615
|
|
|
616
|
+
**`key-auth`** re-validates stored keys against each provider's own endpoint, because `keys` above tests **presence** only — a key that rots *after* it was entered was previously never re-checked, so `doctor` could report ✓ while a provider returned 401 and the catalog silently served zero rows for it. All stored keys are probed in parallel (sequential 10s timeouts would add up to ~50s to every run). Only a **401/403** is treated as a definitive rejection and fails the check; a timeout, DNS/socket failure, 5xx or unexpected status is reported as `warn` — being offline is not a rotted key, and a false error would send you to re-enter a perfectly good one. Only provider names and a sanitized reason are ever printed; no key material or request URL reaches the output or the `--json` artifact.
|
|
617
|
+
|
|
612
618
|
**`local-providers`** probes every configured local provider (2s timeout each) the same way `amicus provider test` does, and reports per-id reachability in one line, e.g. `ollama: 3 models @ http://127.0.0.1:11434/v1; my-vllm: unreachable @ http://127.0.0.1:8000/v1`. No providers configured at all is a plain `ok` ("none configured") — this check can never fail your doctor run outright, only warn: a napping `ollama serve` isn't treated as broken setup.
|
|
613
619
|
|
|
620
|
+
**`output-budget`** reads the stored `outputBudget` and the `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` this process sees, and reports what the value reaches (see [Output budget](configuration.md#output-budget-outputbudget)): how many alias routes have a known catalog ceiling and which do not (the engine clamps those its own catalog knows; a model neither knows receives the budget as-is and may be refused by a provider that enforces its ceiling — a warning, with the remedy of lowering the budget, only when the budget is above the engine's 32,000 default, since below it the flag never raises what goes out; the same rule decides whether a missing catalog cache is a warning), and whether a budget takes at least half of a route's context window. An `openai/` alias route is listed apart from both counts: on a leg that resolves **direct** to that provider the engine drives it through the Responses API, whose request carries no output-limit field at all (probe M5/M13/M22), so neither the stored budget nor the ambient flag reaches it. Which gateway a leg takes is a launch-time decision this row cannot read (`--gateway`, `routing.prefer`, key presence), and the `openrouter/openai/…` form of the same model does carry the reservation (M1/M9). The malformed cases are the ones worth the row: only a plain decimal integer is measured to be honoured — `64000abc` and `0` were ignored *silently* and sent 32,000 (probe rows D1/D2) — so any other form is reported as unmeasured rather than healthy, and `doctor` is where that surfaces — including a malformed `outputBudget` sitting next to an ambient flag, where the row says which value actually governs and — if the ambient value is valid — gives it the same route analysis a budget gets.
|
|
621
|
+
|
|
614
622
|
`--fix` self-heals five of the checks above in place: reprovisions Electron, copies the OpenCode engine into a broken npx-cache install, removes a duplicate legacy MCP entry, sweeps orphaned session-index tmp files, and sweeps orphaned per-session metadata tmp files (both tmp sweeps only ones older than 60s). It does **not** start a local server for you — `local-providers` stays a warning until you start the server yourself.
|
|
615
623
|
|
|
616
624
|
Exit code is `1` if anything is `error`, else `0` (same rule drives `--json`'s `ok` field).
|
|
@@ -880,7 +888,7 @@ Every tool below also takes an optional `project` — an absolute path naming th
|
|
|
880
888
|
- `prompt` — the task briefing: objective, background, files of interest, success criteria.
|
|
881
889
|
- `agent` — OpenCode agent mode (`Chat`, `Plan`, `Build`); see [OpenCode Agent Types](#opencode-agent-types) below.
|
|
882
890
|
- `noUi` — run headless instead of opening the Electron window. Default `false`.
|
|
883
|
-
- `thinking` — reasoning effort (`low` | `medium` | `high`).
|
|
891
|
+
- `thinking` — reasoning effort (`none` | `minimal` | `low` | `medium` | `high` | `xhigh` | `max`). Omitted: nothing is sent and the provider's own default effort governs. A level the model does not declare is refused before anything is sent. A model the engine's catalogue does not know in time is sent the level unverified (`variantUnverified: true` on the record, and a `Notice:` line on stderr). Whether the level is declared does not depend on `outputBudget`; the one refusal a budget can add is `VARIANT_OVER_BUDGET`, when a declared level's own thinking budget would push the reservation past the budget. A model whose row the engine's own catalogue supplied is judged on that row: an empty variants set is a refusal, never an unverified send. Only a model the engine has no row for — its `/config/providers` entry is nothing but the descriptor Amicus registered — waits and is then sent unverified.
|
|
884
892
|
- `timeout` — headless timeout in minutes (default 15); applies only when `noUi` is true.
|
|
885
893
|
- `contextTurns` — max parent-conversation turns to include. Default 50; the MCP twin of `--context-turns`.
|
|
886
894
|
- `contextSince` — time window for parent context (`30m`, `2h`, `1d`); overrides `contextTurns` when set. The MCP twin of `--context-since`.
|
package/electron/main.js
CHANGED
|
@@ -370,7 +370,19 @@ async function createSetupWindow() {
|
|
|
370
370
|
}
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
|
|
373
|
+
// issue 213: render Step 3 from the user's EFFECTIVE aliases (defaults merged
|
|
374
|
+
// with config), not the 21 built-in defaults. Custom aliases used to get no
|
|
375
|
+
// row at all -- and the config arriving later over IPC could not add one,
|
|
376
|
+
// since applyAliasEditsToUI only rewrites rows that already exist. Its own
|
|
377
|
+
// try/catch: an unreadable config must cost the alias list, not the window.
|
|
378
|
+
let aliases;
|
|
379
|
+
try {
|
|
380
|
+
aliases = require('../src/utils/config').getEffectiveAliases();
|
|
381
|
+
} catch (_err) {
|
|
382
|
+
aliases = undefined; // buildSetupHTML falls back to the defaults
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const html = buildSetupHTML({ client: CLIENT, quickPicks, shortlists, aliases });
|
|
374
386
|
mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
|
|
375
387
|
mainWindow.webContents.on('page-title-updated', (e) => e.preventDefault());
|
|
376
388
|
|
|
@@ -569,7 +581,18 @@ function createSettingsChildWindow() {
|
|
|
569
581
|
}
|
|
570
582
|
});
|
|
571
583
|
|
|
572
|
-
|
|
584
|
+
// issue 213: mirror createSetupWindow -- Step 3 renders the user's EFFECTIVE
|
|
585
|
+
// aliases, not just the built-in defaults. getEffectiveAliases() reads the
|
|
586
|
+
// config file synchronously (loadConfig), so this keeps the "Settings must
|
|
587
|
+
// never trigger a network fetch / stay synchronous" property intact.
|
|
588
|
+
let aliases;
|
|
589
|
+
try {
|
|
590
|
+
aliases = require('../src/utils/config').getEffectiveAliases();
|
|
591
|
+
} catch (_err) {
|
|
592
|
+
aliases = undefined; // buildSetupHTML falls back to the defaults
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const html = buildSetupHTML({ client: CLIENT, quickPicks, shortlists, aliases });
|
|
573
596
|
settingsWin.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
|
|
574
597
|
settingsWin.webContents.on('page-title-updated', (e) => e.preventDefault());
|
|
575
598
|
}
|