amicus 4.6.1 → 4.6.2

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 (39) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +86 -0
  3. package/README.md +2 -2
  4. package/docs/ROADMAP.md +7 -2
  5. package/docs/configuration.md +13 -9
  6. package/docs/council.md +7 -2
  7. package/docs/troubleshooting.md +49 -20
  8. package/docs/usage.md +21 -1
  9. package/electron/setup-ui-aliases.js +2 -2
  10. package/electron/workspace-ui/index.html +3 -0
  11. package/electron/workspace-ui/live-model.js +71 -0
  12. package/electron/workspace-ui/workspace-app.js +2 -2
  13. package/electron/workspace-ui/workspace-panels.js +9 -10
  14. package/electron/workspace-ui/workspace-seats.js +117 -0
  15. package/electron/workspace-ui/workspace-verbs.js +1 -0
  16. package/electron/workspace-ui/workspace.css +6 -0
  17. package/package.json +1 -1
  18. package/schemas/alias-audit.schema.json +6 -1
  19. package/schemas/council-run.schema.json +14 -0
  20. package/src/cli-handlers-doctor.js +16 -4
  21. package/src/cli.js +4 -0
  22. package/src/council/run-chair.js +49 -3
  23. package/src/headless.js +119 -9
  24. package/src/mcp-council-awareness.js +1 -0
  25. package/src/opencode-client.js +21 -0
  26. package/src/sidecar/fanout-leg.js +2 -2
  27. package/src/sidecar/fanout.js +1 -1
  28. package/src/sidecar/models-probe.js +119 -0
  29. package/src/sidecar/models.js +81 -6
  30. package/src/utils/alias-audit.js +52 -1
  31. package/src/utils/base-url-classify.js +74 -0
  32. package/src/utils/council-presets.js +6 -2
  33. package/src/utils/curated-models.js +29 -10
  34. package/src/utils/doctor-base-url-check.js +41 -0
  35. package/src/utils/model-fetcher.js +1 -0
  36. package/src/utils/model-tiers.js +28 -7
  37. package/src/utils/no-output-backstop.js +48 -0
  38. package/src/utils/result-schema.js +29 -2
  39. package/src/workspace/live-normalize.js +1 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @module utils/no-output-backstop
3
+ * v4.6.2 PR2 (spec §5, D4): fail a headless leg fast when the model produces
4
+ * ZERO output, reasoning, and tool calls — the "accepted but not serving"
5
+ * class (the v4.6.1 gemini release-gate incident: requests accepted, zero
6
+ * tokens, three suites burned 130s timeouts each to learn nothing).
7
+ *
8
+ * Pure state machine, loop-driven (no timers of its own — the poll loop
9
+ * ticks it): armed at leg start, DISARMED PERMANENTLY by the first
10
+ * `progressed` tick (a 30-90s cold-prefill local model is never affected),
11
+ * fired when the deadline passes with nothing ever observed. `fired` is
12
+ * terminal. `ms <= 0` never arms — 0 is the documented escape hatch, which
13
+ * is why the env resolver uses envNumber (explicit 0 honored) rather than
14
+ * the `Number(env) || default` idiom.
15
+ *
16
+ * PR3's live probe reuses this with a 30s override — ms is an input; only
17
+ * the exported resolver reads the environment.
18
+ */
19
+ 'use strict';
20
+
21
+ const { envNumber } = require('./env-num');
22
+
23
+ const DEFAULT_NO_OUTPUT_BACKSTOP_MS = 120000;
24
+
25
+ /** @param {object} [env] test seam; defaults to process.env */
26
+ function resolveNoOutputBackstopMs(env) {
27
+ return envNumber('AMICUS_NO_OUTPUT_BACKSTOP_MS', DEFAULT_NO_OUTPUT_BACKSTOP_MS, env);
28
+ }
29
+
30
+ /**
31
+ * @param {{ms:number, startedAt:number}} opts
32
+ * @returns {{tick:(progressed:boolean, nowMs:number)=>string, state:()=>string}}
33
+ */
34
+ function createNoOutputBackstop({ ms, startedAt }) {
35
+ let state = ms > 0 ? 'armed' : 'disarmed';
36
+ const deadline = startedAt + ms;
37
+ return {
38
+ tick(progressed, nowMs) {
39
+ if (state !== 'armed') { return state; }
40
+ if (progressed) { state = 'disarmed'; return state; }
41
+ if (nowMs >= deadline) { state = 'fired'; }
42
+ return state;
43
+ },
44
+ state() { return state; },
45
+ };
46
+ }
47
+
48
+ module.exports = { resolveNoOutputBackstopMs, createNoOutputBackstop, DEFAULT_NO_OUTPUT_BACKSTOP_MS };
@@ -192,10 +192,32 @@ function buildCatalogDoc({ models, fetchedAt, refreshed = false, search = null,
192
192
  * of curated DEFAULT aliases (toGatewayRoutes() vs. the live catalog),
193
193
  * distinct from the flat `stale` audit above. Defaults to [] so existing
194
194
  * callers that omit it are unaffected.
195
+ * `drifted` (v4.6.2 PR1, 2A) is additive: stored user-config aliases whose
196
+ * target is still catalog-live but behind the current quick-pick family
197
+ * resolution (findDriftedStoredAliases). Defaults to [] so existing callers
198
+ * that omit it are unaffected. Paired with an additive `driftedCount`
199
+ * (drifted.length), mirroring the staleCount/stale and
200
+ * gatewayFindingsCount/gatewayFindings pairs above.
201
+ * `probe` (v4.6.2 PR3, spec §6 D5) is additive: the `--live` opt-in per-alias
202
+ * probe outcomes (probeStoredAliases — served/accepted-but-silent/error), one
203
+ * row per stored alias actually probed. Defaults to [] so every call without
204
+ * --live (i.e. every existing caller) is unaffected. Paired with an additive
205
+ * `probeCount` (probe.length), mirroring the driftedCount/drifted pair above.
206
+ * `probeSkipped` (v4.6.2 PR3 Task 4) is additive: null when the probe ran or
207
+ * `--live` wasn't requested; otherwise a stable reason slug (e.g.
208
+ * 'catalog-unavailable') for the one case this function can see — the
209
+ * `--refresh --check --live` skip (runRefresh returns before this is ever
210
+ * called) is announced by the caller instead, since that path emits a
211
+ * model-catalog doc, not this one.
195
212
  * @param {{stale: Array<{alias,model,source,suggestions}>, catalogAvailable: boolean,
196
- * gatewayFindings?: Array<{alias,gateway,kind,model,expected?}>}} opts
213
+ * gatewayFindings?: Array<{alias,gateway,kind,model,expected?}>,
214
+ * drifted?: Array<{alias,stored,current}>,
215
+ * probe?: Array<{alias,target,outcome,detail,cost}>,
216
+ * probeSkipped?: string|null}} opts
197
217
  */
198
- function buildAuditDoc({ stale, catalogAvailable, gatewayFindings = [] }) {
218
+ function buildAuditDoc({
219
+ stale, catalogAvailable, gatewayFindings = [], drifted = [], probe = [], probeSkipped = null
220
+ }) {
199
221
  return {
200
222
  schemaVersion: SCHEMA_VERSION,
201
223
  type: 'alias-audit',
@@ -204,6 +226,11 @@ function buildAuditDoc({ stale, catalogAvailable, gatewayFindings = [] }) {
204
226
  stale,
205
227
  gatewayFindingsCount: gatewayFindings.length,
206
228
  gatewayFindings,
229
+ driftedCount: drifted.length,
230
+ drifted,
231
+ probeCount: probe.length,
232
+ probe,
233
+ probeSkipped,
207
234
  };
208
235
  }
209
236
 
@@ -126,6 +126,7 @@ function normalizeLive(doc) {
126
126
  stageName: active ? active.name : null,
127
127
  stages,
128
128
  seats: legRowsOf(doc).map(seatOf),
129
+ degrades: Array.isArray(doc.degrades) ? doc.degrades : [],
129
130
  // ⚠️ v4.4.1 RN-8 (D1 ruling: delete the promise). `legsTotal`/`legsComplete` used to be
130
131
  // mapped here under a comment promising them as "the honest fallback readout if a seat row
131
132
  // is ever unavailable" — a UI fallback nobody ever wired. Nothing in electron/workspace-ui/