Completes "chat is the write-path" (decisions.md D1). Ask Reginald to re-estimate
or reprioritize an issue; it formulates a proposal, you approve it inline, and the
write runs through the same guarded apply_changes engine the Issue screen uses.
The model never writes — it proposes; the app owns approval + execution.
core (@commitea/core):
- propose_change tool declaration + REGINALD_SYSTEM updated ("never claim a change
is applied; you propose, the human approves").
- proposalsFor(args, currentLabels, title): pure — builds the concrete, non-noop
ChangeProposal(s) (change + label diff) for a propose_change request, dropping
invalid/unchanged axes. ChangeProposal / ProposeChangeArgs types.
app:
- model bridge executes propose_change by planning against the issue's current
labels (no write) and returns the proposals with the turn.
- useChat surfaces pending proposals + approve/dismiss; approve calls onApplyChange
(AppShell's guarded handler → PUT + board/forecast refetch), dismiss leaves it.
- ChatPanel renders each proposal as a propose-approve card (diff + Approve/Dismiss).
Verified: 101 core tests green (4 proposalsFor added), desktop typecheck clean,
14 fixture e2e green. Gated live e2e against gemma-4-26b: "Set the estimate on #3
to est/5d" → Reginald proposes "est/2d → est/5d" as an inline card, says it's
*proposed* not done; Dismiss leaves the repo untouched. The approve→write path is
the #41 engine (separately verified change→revert).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
2.9 KiB
TypeScript
57 lines
2.9 KiB
TypeScript
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { _electron as electron, expect, test } from '@playwright/test'
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
const MAIN = join(here, '..', 'out', 'main', 'index.js')
|
|
|
|
// Opt-in (GITEA_LIVE=1 + COMMITEA_MODEL_LIVE=1 + a local model on :1234). Launches
|
|
// WITHOUT COMMITEA_E2E so Reginald runs the real agent loop against the real repo.
|
|
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(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 (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/)
|
|
await composer.fill('What should I work on right now?')
|
|
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: 240_000 })
|
|
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-reginald.png'), fullPage: true, animations: 'disabled' })
|
|
|
|
await app.close()
|
|
})
|
|
|
|
test('proposes an estimate change for inline approval (writes via chat)', async () => {
|
|
test.skip(!process.env.GITEA_LIVE || !process.env.COMMITEA_MODEL_LIVE, 'live model test — opt-in')
|
|
test.setTimeout(300_000)
|
|
const app = await electron.launch({ args: [MAIN], env: { ...process.env } })
|
|
const win = await app.firstWindow()
|
|
await win.waitForLoadState('domcontentloaded')
|
|
await expect(win.getByText(/· local$/)).toBeVisible({ timeout: 20000 })
|
|
|
|
const composer = win.getByPlaceholder(/Tell me what to do/)
|
|
await composer.fill('Set the estimate on issue #3 to est/5d.')
|
|
await composer.press('Enter')
|
|
|
|
// propose_change → an inline propose-approve card (never an auto-write)
|
|
await expect(win.getByText('Proposed · #3')).toBeVisible({ timeout: 240_000 })
|
|
await expect(win.getByText(/→ est\/5d/)).toBeVisible()
|
|
await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-reginald-propose.png'), fullPage: true, animations: 'disabled' })
|
|
// Dismiss so the live run never mutates the repo (the write path itself is #41-tested)
|
|
await win.getByRole('button', { name: 'Dismiss' }).click()
|
|
await expect(win.getByText(/Left #3 as it was/)).toBeVisible()
|
|
|
|
await app.close()
|
|
})
|
|
})
|