@beastmode-develeap/beastmode 0.1.363 → 0.1.364

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.
@@ -15,7 +15,7 @@
15
15
  }
16
16
  </script>
17
17
  <!--BOARD_DATA-->
18
- <script>window.__BUILD_STAMP__ = "20260605-015717-0e94f70";</script>
18
+ <script>window.__BUILD_STAMP__ = "20260605-023046-eb133e1";</script>
19
19
  <link rel="preconnect" href="https://fonts.googleapis.com">
20
20
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
21
21
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
@@ -9947,6 +9947,14 @@ function SettingsPage() {
9947
9947
 
9948
9948
  const [restartFields, setRestartFields] = useState(new Set());
9949
9949
 
9950
+ // Cost knobs (feature #888): the highest-cost in-flight item, shown next to
9951
+ // the per_item_usd_cap field so the operator can pick a sensible ceiling.
9952
+ const [costsByItems, setCostsByItems] = useState(null);
9953
+ // FR-2 hot-reload confirmation: the `cost` category is AUTO-reloaded by the
9954
+ // daemon (no restart), so after a save we surface "Reloading…" then
9955
+ // "Applied at HH:MM:SS" instead of a restart badge.
9956
+ const [costReload, setCostReload] = useState(null);
9957
+
9950
9958
  useEffect(() => {
9951
9959
  api('GET', '/api/config')
9952
9960
  .then(setConfig)
@@ -9954,6 +9962,15 @@ function SettingsPage() {
9954
9962
  .finally(() => setLoading(false));
9955
9963
  }, []);
9956
9964
 
9965
+ useEffect(() => {
9966
+ // Not board-scoped: we want the global highest-cost item regardless of the
9967
+ // project filter. Swallow errors — the indicator falls back to "—".
9968
+ fetch('/api/costs/by-items')
9969
+ .then(r => r.ok ? r.json() : {})
9970
+ .then(data => setCostsByItems(data || {}))
9971
+ .catch(() => setCostsByItems({}));
9972
+ }, []);
9973
+
9957
9974
  useEffect(() => {
9958
9975
  api('GET', '/api/daemon/reload-categories')
9959
9976
  .then(data => {
@@ -10007,6 +10024,15 @@ function SettingsPage() {
10007
10024
  setConfig(result);
10008
10025
  setSuccess('Configuration saved');
10009
10026
  setTimeout(() => setSuccess(null), 3000);
10027
+
10028
+ // FR-2: the cost category hot-reloads without a restart. Show a brief
10029
+ // "Reloading…" then a timestamped "Applied" so the operator knows the
10030
+ // new ceiling is live, not pending a daemon bounce.
10031
+ setCostReload('reloading');
10032
+ setTimeout(() => {
10033
+ const t = new Date().toLocaleTimeString([], { hour12: false });
10034
+ setCostReload({ appliedAt: t });
10035
+ }, 700);
10010
10036
  } catch (e) { setError(e.message); }
10011
10037
  setSaving(false);
10012
10038
  };
@@ -10044,6 +10070,17 @@ function SettingsPage() {
10044
10070
  const cost = config.cost || {};
10045
10071
  const humanGates = config.human_gates || {};
10046
10072
 
10073
+ // FR-3: highest in-flight item cost, used as a reference point next to the
10074
+ // per_item_usd_cap field. costsByItems is keyed by item id → { total_cost_usd }.
10075
+ // null = still loading; {} = loaded but no data → show "—".
10076
+ const highestItemCost = costsByItems
10077
+ ? Object.values(costsByItems).reduce(
10078
+ (max, c) => Math.max(max, (c && c.total_cost_usd) || 0), 0)
10079
+ : null;
10080
+ const highestItemCostLabel = (highestItemCost && highestItemCost > 0)
10081
+ ? (formatCost(highestItemCost) || '$' + highestItemCost.toFixed(2))
10082
+ : '—';
10083
+
10047
10084
  // Build model options dynamically from whatever the config actually uses,
10048
10085
  // plus well-known model families. No more hardcoding — when Anthropic
10049
10086
  // releases a new model, it shows up as soon as someone sets it in config.
@@ -10232,13 +10269,40 @@ function SettingsPage() {
10232
10269
  </div>
10233
10270
  </div>
10234
10271
 
10235
- <!-- Cost Section -->
10272
+ <!-- Cost Section. The "cost" category hot-reloads (ReloadCategory.AUTO),
10273
+ so it carries NO RestartBadge — instead saveConfig surfaces an
10274
+ inline "Reloading / Applied at HH:MM:SS" confirmation (FR-2). -->
10236
10275
  <div class="settings-section">
10237
- <h3 style="display:flex;align-items:center;gap:8px">Cost Optimization <${RestartBadge} field="cost" restartFields=${restartFields} /></h3>
10276
+ <h3 style="display:flex;align-items:center;gap:8px">
10277
+ Cost Optimization
10278
+ ${costReload === 'reloading'
10279
+ ? html`<span style="font-size:12px;color:#888;font-weight:normal">⏳ Reloading…</span>`
10280
+ : (costReload && costReload.appliedAt)
10281
+ ? html`<span style="font-size:12px;color:#16a34a;font-weight:normal">✅ Applied at ${costReload.appliedAt}</span>`
10282
+ : null}
10283
+ </h3>
10284
+
10285
+ <!-- Per-item USD cap (FR-3/FR-5): hard spend ceiling per item. -->
10286
+ <div class="setting-row">
10287
+ <div>
10288
+ <div class="setting-label">Per-Item Cost Cap (USD)</div>
10289
+ <div class="setting-desc">Hard ceiling on Claude cost per item in USD. Set to 0 to disable. When exceeded, the item transitions to Stuck on next dispatch.</div>
10290
+ <div class="setting-desc" style="margin-top:4px">Current item with highest cost: ${highestItemCostLabel}</div>
10291
+ </div>
10292
+ <div class="setting-control">
10293
+ <input type="number" class="form-input" min="0" max="10000" step="1" style="width:90px;text-align:center"
10294
+ value=${cost.per_item_usd_cap ?? 50.0}
10295
+ onInput=${e => {
10296
+ const v = parseFloat(e.target.value);
10297
+ updateField('cost.per_item_usd_cap', Number.isFinite(v) ? v : 50.0);
10298
+ }} />
10299
+ </div>
10300
+ </div>
10301
+
10238
10302
  <div class="setting-row">
10239
10303
  <div>
10240
10304
  <div class="setting-label">Model Tiering</div>
10241
- <div class="setting-desc">Use cheaper models for iterations</div>
10305
+ <div class="setting-desc">Use cheaper models for cheaper sub-tasks.</div>
10242
10306
  </div>
10243
10307
  <div class="setting-control">
10244
10308
  <label class="toggle-switch">
@@ -10248,10 +10312,11 @@ function SettingsPage() {
10248
10312
  </label>
10249
10313
  </div>
10250
10314
  </div>
10315
+
10251
10316
  <div class="setting-row">
10252
10317
  <div>
10253
10318
  <div class="setting-label">Incremental Verification</div>
10254
- <div class="setting-desc">Only re-run failed scenarios</div>
10319
+ <div class="setting-desc">Re-verify only changed scenarios between iterations.</div>
10255
10320
  </div>
10256
10321
  <div class="setting-control">
10257
10322
  <label class="toggle-switch">
@@ -10261,19 +10326,95 @@ function SettingsPage() {
10261
10326
  </label>
10262
10327
  </div>
10263
10328
  </div>
10329
+
10330
+ <div class="setting-row">
10331
+ <div>
10332
+ <div class="setting-label">Full Re-Verify Interval</div>
10333
+ <div class="setting-desc">Run a full re-verification every N iterations.</div>
10334
+ </div>
10335
+ <div class="setting-control">
10336
+ <input type="number" class="form-input" min="1" max="20" style="width:80px;text-align:center"
10337
+ value=${cost.full_reverify_interval ?? 3}
10338
+ onInput=${e => updateField('cost.full_reverify_interval', parseInt(e.target.value) || 3)} />
10339
+ </div>
10340
+ </div>
10341
+
10342
+ <!-- Build Check toggle (FR-6): disabling needs an explicit confirm so a
10343
+ broken build can't silently slip past verification. -->
10264
10344
  <div class="setting-row">
10265
10345
  <div>
10266
10346
  <div class="setting-label">Build Check</div>
10267
- <div class="setting-desc">Run build before verification</div>
10347
+ <div class="setting-desc">Whether to run the build/compile check between iterations.</div>
10268
10348
  </div>
10269
10349
  <div class="setting-control">
10270
10350
  <label class="toggle-switch">
10271
10351
  <input type="checkbox" checked=${cost.build_check_enabled !== false}
10272
- onChange=${e => updateField('cost.build_check_enabled', e.target.checked)} />
10352
+ onChange=${e => {
10353
+ if (!e.target.checked && !window.confirm('Disable the build check? Broken builds will no longer be caught before verification.')) {
10354
+ return; // controlled input reverts to the prior state
10355
+ }
10356
+ updateField('cost.build_check_enabled', e.target.checked);
10357
+ }} />
10358
+ <span class="toggle-slider"></span>
10359
+ </label>
10360
+ </div>
10361
+ </div>
10362
+
10363
+ <div class="setting-row">
10364
+ <div>
10365
+ <div class="setting-label">Build Command</div>
10366
+ <div class="setting-desc">Shell command used for the incremental build check.</div>
10367
+ </div>
10368
+ <div class="setting-control">
10369
+ <input type="text" class="form-input" style="width:180px"
10370
+ placeholder="npm run build"
10371
+ value=${cost.build_check_command ?? 'npm run build'}
10372
+ onInput=${e => updateField('cost.build_check_command', e.target.value)} />
10373
+ </div>
10374
+ </div>
10375
+
10376
+ <div class="setting-row">
10377
+ <div>
10378
+ <div class="setting-label">Build Timeout (seconds)</div>
10379
+ <div class="setting-desc">Timeout in seconds for the build check command.</div>
10380
+ </div>
10381
+ <div class="setting-control">
10382
+ <input type="number" class="form-input" min="1" max="3600" style="width:90px;text-align:center"
10383
+ value=${cost.build_check_timeout_seconds ?? 90}
10384
+ onInput=${e => updateField('cost.build_check_timeout_seconds', parseInt(e.target.value) || 90)} />
10385
+ </div>
10386
+ </div>
10387
+
10388
+ <div class="setting-row">
10389
+ <div>
10390
+ <div class="setting-label">NLSpec Pre-Check</div>
10391
+ <div class="setting-desc">Cheap NLSpec compliance precheck before full verify.</div>
10392
+ </div>
10393
+ <div class="setting-control">
10394
+ <label class="toggle-switch">
10395
+ <input type="checkbox" checked=${cost.nlspec_precheck_enabled !== false}
10396
+ onChange=${e => updateField('cost.nlspec_precheck_enabled', e.target.checked)} />
10273
10397
  <span class="toggle-slider"></span>
10274
10398
  </label>
10275
10399
  </div>
10276
10400
  </div>
10401
+
10402
+ <!-- NLSpec pre-check model (FR-6): a fixed dropdown, no free text. -->
10403
+ <div class="setting-row">
10404
+ <div>
10405
+ <div class="setting-label">NLSpec Pre-Check Model</div>
10406
+ <div class="setting-desc">Model used for the NLSpec precheck.</div>
10407
+ </div>
10408
+ <div class="setting-control">
10409
+ <select class="form-input" style="width:220px"
10410
+ value=${cost.nlspec_precheck_model || 'claude-haiku-4-5-20251001'}
10411
+ onChange=${e => updateField('cost.nlspec_precheck_model', e.target.value)}>
10412
+ ${[...new Set([cost.nlspec_precheck_model || 'claude-haiku-4-5-20251001', ...MODEL_OPTIONS])]
10413
+ .filter(Boolean)
10414
+ .map(m => html`<option key=${m} value=${m}>${m}</option>`)}
10415
+ </select>
10416
+ </div>
10417
+ </div>
10277
10418
  </div>
10278
10419
 
10279
10420
  <!-- Human Gates Section -->
@@ -1 +1 @@
1
- 0e94f70ebfc456dfddb4d1473f699f340e9d67af
1
+ eb133e19f9d3433957c65c557b350553b63d4c36
@@ -1 +1 @@
1
- 20260605-015717-0e94f70
1
+ 20260605-023046-eb133e1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beastmode-develeap/beastmode",
3
- "version": "0.1.363",
3
+ "version": "0.1.364",
4
4
  "description": "BeastMode Dark Factory — turn intent into verified software",
5
5
  "type": "module",
6
6
  "bin": {