@animalabs/connectome-host 0.5.1 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.2 — 2026-07-26
6
+
7
+ ### Fixed
8
+
9
+ - **Settings preview reported unreachable budgets as fitting.** context-manager's
10
+ `PreviewResult.budgetTokens` is the *rejection* budget —
11
+ `(requested - reserve) * (1 + overBudgetGraceRatio)` — and its `fits` means
12
+ "would not throw `OverBudgetError`", not "fits the budget you asked for". On
13
+ Mythos (`overBudgetGraceRatio: 0.35`) those differ by a third: previewing
14
+ 250k reported `fits: true` at 273,828 tokens, a budget the picker had in fact
15
+ exhausted trying to reach. The endpoint now returns an `accounting` block
16
+ separating `fitsRequested` / `withinGrace` / `unreachable`, and the panel
17
+ renders three distinct verdicts (fits / over-requested-but-graced /
18
+ would-hard-fail) plus the full budget derivation.
19
+
5
20
  ## 0.5.1
6
21
 
7
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animalabs/connectome-host",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "General-purpose agent TUI host with recipe-based configuration",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1249,7 +1249,43 @@ export class WebUiModule implements Module {
1249
1249
  { status: 501 },
1250
1250
  );
1251
1251
  }
1252
- return Response.json({ agent: agentName, budget, ...(overrides as object), preview: result });
1252
+ // Honest budget accounting. context-manager's `budgetTokens` is the
1253
+ // REJECTION budget: (requested - reserve) * (1 + overBudgetGraceRatio),
1254
+ // i.e. the threshold above which a compile throws. Its `fits` therefore
1255
+ // means "would not hard-fail", NOT "fits the budget you asked for".
1256
+ //
1257
+ // On a recipe with overBudgetGraceRatio 0.35 those differ by a third, so
1258
+ // reporting cm's `fits` verbatim told operators that a budget they
1259
+ // cannot actually reach was fine. Split the two questions apart and let
1260
+ // the panel say which one it means.
1261
+ const r = result as { finalTokens?: number; budgetTokens?: number; exhausted?: boolean };
1262
+ const reserve = app.recipe.agent.maxTokens ?? 16_384;
1263
+ const effectiveBudget = Math.max(0, budget - reserve);
1264
+ const finalTokens = typeof r.finalTokens === 'number' ? r.finalTokens : NaN;
1265
+ const fitsRequested = Number.isFinite(finalTokens) && finalTokens <= effectiveBudget;
1266
+ const withinGrace = Number.isFinite(finalTokens) && typeof r.budgetTokens === 'number'
1267
+ ? finalTokens <= r.budgetTokens
1268
+ : undefined;
1269
+ return Response.json({
1270
+ agent: agentName,
1271
+ budget,
1272
+ ...(overrides as object),
1273
+ accounting: {
1274
+ requestedBudgetTokens: budget,
1275
+ reserveForResponseTokens: reserve,
1276
+ /** What the picker actually targets. */
1277
+ effectiveBudgetTokens: effectiveBudget,
1278
+ /** Hard-fail ceiling — requested minus reserve, plus grace. */
1279
+ rejectionBudgetTokens: r.budgetTokens,
1280
+ /** Fits the budget the operator asked for. */
1281
+ fitsRequested,
1282
+ /** Merely tolerated by the grace margin — over budget, but no throw. */
1283
+ withinGrace,
1284
+ /** Exhausted AND over the request => this budget is UNREACHABLE. */
1285
+ unreachable: r.exhausted === true && !fitsRequested,
1286
+ },
1287
+ preview: result,
1288
+ });
1253
1289
  } catch (err) {
1254
1290
  return Response.json(
1255
1291
  { error: err instanceof Error ? err.message : String(err) },
@@ -36,6 +36,24 @@ export interface SettingsState {
36
36
  previewAvailable: boolean;
37
37
  }
38
38
 
39
+ /**
40
+ * Honest budget accounting from the server.
41
+ *
42
+ * `preview.fits` from context-manager means "would not throw OverBudgetError",
43
+ * which on a recipe with a grace ratio is NOT the same as "fits the budget you
44
+ * typed". These three flags keep the questions separate.
45
+ */
46
+ interface PreviewAccounting {
47
+ requestedBudgetTokens: number;
48
+ reserveForResponseTokens: number;
49
+ effectiveBudgetTokens: number;
50
+ rejectionBudgetTokens?: number;
51
+ fitsRequested: boolean;
52
+ withinGrace?: boolean;
53
+ /** Picker exhausted AND still over the request — this budget cannot be reached. */
54
+ unreachable: boolean;
55
+ }
56
+
39
57
  interface PreviewResult {
40
58
  finalTokens: number;
41
59
  budgetTokens: number;
@@ -91,6 +109,7 @@ export function SettingsPanel(props: {
91
109
  const [notify, setNotify] = createSignal(false);
92
110
 
93
111
  const [preview, setPreview] = createSignal<PreviewResult | null>(null);
112
+ const [acct, setAcct] = createSignal<PreviewAccounting | null>(null);
94
113
  const [previewErr, setPreviewErr] = createSignal<string | null>(null);
95
114
  const [previewing, setPreviewing] = createSignal(false);
96
115
 
@@ -133,12 +152,15 @@ export function SettingsPanel(props: {
133
152
  const body = await res.json();
134
153
  if (!res.ok) {
135
154
  setPreview(null);
155
+ setAcct(null);
136
156
  setPreviewErr(body?.error ?? `HTTP ${res.status}`);
137
157
  return;
138
158
  }
139
159
  setPreview(body.preview as PreviewResult);
160
+ setAcct((body.accounting ?? null) as PreviewAccounting | null);
140
161
  } catch (e) {
141
162
  setPreview(null);
163
+ setAcct(null);
142
164
  setPreviewErr(e instanceof Error ? e.message : String(e));
143
165
  } finally {
144
166
  setPreviewing(false);
@@ -357,20 +379,45 @@ export function SettingsPanel(props: {
357
379
  <Show when={preview()}>
358
380
  {(p) => (
359
381
  <div>
382
+ {/* Three distinct verdicts. Collapsing "fits your budget" into
383
+ "wouldn't throw" is how a 35%-grace recipe reports an
384
+ unreachable budget as fine — so they are kept apart. */}
360
385
  <div
361
386
  class={`px-2 py-1 rounded mb-1.5 text-[11px] border ${
362
- p().fits
387
+ acct()?.fitsRequested
363
388
  ? 'bg-emerald-950/40 border-emerald-800 text-emerald-300'
364
- : 'bg-red-950/40 border-red-800 text-red-300'
389
+ : acct()?.withinGrace
390
+ ? 'bg-amber-950/40 border-amber-800 text-amber-200'
391
+ : 'bg-red-950/40 border-red-800 text-red-300'
365
392
  }`}
366
393
  >
367
- {p().fits
368
- ? `fits — ${p().finalTokens.toLocaleString()} of ${p().budgetTokens.toLocaleString()} tok`
369
- : `DOES NOT FIT ${p().finalTokens.toLocaleString()} tok vs ${p().budgetTokens.toLocaleString()} budget`}
370
- <Show when={!p().fits && p().exhausted}>
394
+ <Show when={acct()?.fitsRequested}>
395
+ <div>
396
+ fits — {p().finalTokens.toLocaleString()} of{' '}
397
+ {acct()!.effectiveBudgetTokens.toLocaleString()} usable tok
398
+ </div>
399
+ </Show>
400
+ <Show when={!acct()?.fitsRequested && acct()?.withinGrace}>
401
+ <div>
402
+ OVER REQUESTED BUDGET — {p().finalTokens.toLocaleString()} tok vs{' '}
403
+ {acct()!.effectiveBudgetTokens.toLocaleString()} usable
404
+ </div>
405
+ <div class="text-[10px] opacity-90 mt-0.5">
406
+ it would not hard-fail, but only because the grace margin absorbs the
407
+ overshoot (ceiling {acct()!.rejectionBudgetTokens?.toLocaleString()}).
408
+ The context would run above your target indefinitely.
409
+ </div>
410
+ </Show>
411
+ <Show when={acct() && !acct()!.fitsRequested && acct()!.withinGrace === false}>
412
+ <div>
413
+ WOULD HARD-FAIL — {p().finalTokens.toLocaleString()} tok exceeds even the
414
+ grace ceiling {acct()!.rejectionBudgetTokens?.toLocaleString()}
415
+ </div>
416
+ </Show>
417
+ <Show when={acct()?.unreachable}>
371
418
  <div class="text-[10px] opacity-90 mt-0.5">
372
- picker exhausted: nothing further can be folded. Applying this would
373
- hard-fail the compile.
419
+ <b>unreachable:</b> the picker exhausted nothing further can be folded,
420
+ so this budget cannot be reached no matter how long you wait.
374
421
  </div>
375
422
  </Show>
376
423
  </div>
@@ -378,6 +425,10 @@ export function SettingsPanel(props: {
378
425
  <table class="w-full font-mono text-[10px]">
379
426
  <tbody>
380
427
  <For each={[
428
+ ['requested budget', acct()?.requestedBudgetTokens],
429
+ ['− reserve for response', acct()?.reserveForResponseTokens],
430
+ ['= usable for context', acct()?.effectiveBudgetTokens],
431
+ ['grace ceiling (hard fail above)', acct()?.rejectionBudgetTokens],
381
432
  ['head (verbatim)', p().headTokens],
382
433
  ['tail (verbatim)', p().tailTokens],
383
434
  ['middle (foldable)', p().middleTokens],
@@ -389,7 +440,11 @@ export function SettingsPanel(props: {
389
440
  <tr>
390
441
  <td class="text-neutral-500 pr-2">{k}</td>
391
442
  <td class="text-neutral-300 text-right tabular-nums">
392
- {typeof v === 'number' ? v.toLocaleString() : String(v)}
443
+ {typeof v === 'number'
444
+ ? v.toLocaleString()
445
+ : v === undefined || v === null
446
+ ? '–'
447
+ : String(v)}
393
448
  </td>
394
449
  </tr>
395
450
  )}