feat: Reginald follows the model you load (auto-detect) + shows it in the header

Instead of a hardcoded model name (which forces LM Studio to JIT-swap your loaded
model out — and fails when a big model already fills memory), resolve the model
at request time: an explicit env override wins, else ask the server which model
is *loaded* (LM Studio's native /api/v0/models), else the first non-embedding
model, else a default. Reginald now uses whatever you load, no config churn.

- main/model.ts: resolveLoadedModel() drives both model:status and model:chat;
  COMMITEA_MODEL_SMALL still overrides.
- useChat exposes the resolved model id; the panel header shows it
  (google/gemma-4-26b-a4b-qat → "gemma-4-26b-a4b · local").
- live-reginald e2e: header assertion relaxed to the loaded model; timeouts
  raised for a slow big local model (~2 calls/turn + a reconcile).

Verified: 14 fixture e2e green; live e2e drives the app against the loaded
gemma-4-26b — "What now?" → "You should work on #2 … on the critical path,
unblocks #33 and #4" (the real scheduler pick), header shows the live model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Croissant Le Doux
2026-07-08 21:03:23 -04:00
parent 62d62ef4fd
commit d70c6e2542
4 changed files with 54 additions and 14 deletions

View File

@@ -11,13 +11,13 @@ const MAIN = join(here, '..', 'out', 'main', 'index.js')
test.describe('live Reginald', () => {
test('answers a question by consulting the real project', async () => {
test.skip(!process.env.GITEA_LIVE || !process.env.COMMITEA_MODEL_LIVE, 'live model test — opt-in')
test.setTimeout(120_000)
test.setTimeout(300_000) // a big local model is slow: ~2 calls/turn + a reconcile
const app = await electron.launch({ args: [MAIN], env: { ...process.env } })
const win = await app.firstWindow()
await win.waitForLoadState('domcontentloaded')
// model configured → the live greeting + header, not the scripted demo
await expect(win.getByText('gemma-4 · local')).toBeVisible({ timeout: 20000 })
// model configured → the live greeting + header (the loaded model, not the scripted demo)
await expect(win.getByText(/· local$/)).toBeVisible({ timeout: 20000 })
await expect(win.getByText(/I check the real board before I answer/)).toBeVisible()
const composer = win.getByPlaceholder(/Tell me what to do/)
@@ -25,7 +25,7 @@ test.describe('live Reginald', () => {
await composer.press('Enter')
// the agent loop ran end-to-end: it consulted the project, then answered
await expect(win.getByText(/consulted the project/)).toBeVisible({ timeout: 90_000 })
await expect(win.getByText(/consulted the project/)).toBeVisible({ timeout: 240_000 })
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-reginald.png'), fullPage: true, animations: 'disabled' })
await app.close()