feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { mockNexusApi, PROPOSAL_ID } from './support/nexusApi'
|
||||
|
||||
test.describe('agent proposal lifecycle', () => {
|
||||
test('an owner creates, reviews, and approves a proposal with mutation guards', async ({ page }) => {
|
||||
const api = await mockNexusApi(page, { role: 'owner' })
|
||||
await page.goto('/agents/new')
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Agent vorschlagen' })).toBeVisible()
|
||||
await page.getByRole('textbox', { name: 'Name Pflichtfeld', exact: true }).fill('Release Sentinel')
|
||||
await page.getByLabel('Rolle').fill('Release Operations')
|
||||
await page.getByLabel('Zweck und Verantwortungsbereich').fill(
|
||||
'Verifies release readiness and reports blockers.',
|
||||
)
|
||||
await page.getByLabel('OpenClaw-Modell').selectOption('openai/gpt-5.4')
|
||||
await page.getByLabel('Datei').selectOption('SOUL.md')
|
||||
await page.getByLabel('SOUL.md Inhalt').fill('# Release Sentinel\nVerify before reporting.')
|
||||
await page.getByRole('button', { name: 'Vorschlag anlegen' }).click()
|
||||
|
||||
await expect(page).toHaveURL(new RegExp(`/agents/proposals/${PROPOSAL_ID}$`))
|
||||
await expect(page.getByText('Freigabe offen', { exact: true })).toBeVisible()
|
||||
|
||||
const createRequest = api.proposalRequests.find(request =>
|
||||
request.path === '/api/v1/openclaw/agent-proposals'
|
||||
)
|
||||
expect(createRequest).toBeDefined()
|
||||
expect(createRequest?.headers['idempotency-key']).toBeTruthy()
|
||||
expect(createRequest?.headers['x-correlation-id']).toBeTruthy()
|
||||
expect(createRequest?.body).toMatchObject({
|
||||
name: 'Release Sentinel',
|
||||
role: 'Release Operations',
|
||||
model: 'openai/gpt-5.4',
|
||||
files: {
|
||||
'SOUL.md': '# Release Sentinel\nVerify before reporting.',
|
||||
},
|
||||
})
|
||||
|
||||
await page.getByRole('button', { name: 'Freigeben' }).click()
|
||||
const dialog = page.getByRole('dialog', { name: 'Agent-Provisionierung freigeben' })
|
||||
await expect(dialog).toBeVisible()
|
||||
await dialog.getByLabel('Notiz (optional)').fill('Reviewed in the owner E2E flow.')
|
||||
await dialog.getByRole('button', { name: 'Bestätigen' }).click()
|
||||
|
||||
await expect(page.getByText('Bereit', { exact: true })).toBeVisible()
|
||||
await expect(page.getByText('Agent ist bereit.', { exact: true })).toBeVisible()
|
||||
|
||||
const approveRequest = api.proposalRequests.find(request =>
|
||||
request.path.endsWith('/approve')
|
||||
)
|
||||
expect(approveRequest).toBeDefined()
|
||||
expect(approveRequest?.headers['idempotency-key']).toBeTruthy()
|
||||
expect(approveRequest?.headers['x-correlation-id']).toBeTruthy()
|
||||
expect(approveRequest?.body).toMatchObject({
|
||||
expectedRevision: 1,
|
||||
reason: 'Reviewed in the owner E2E flow.',
|
||||
})
|
||||
})
|
||||
|
||||
test('a non-owner sees the permission boundary without owner API traffic', async ({ page }) => {
|
||||
const api = await mockNexusApi(page, { role: 'member' })
|
||||
|
||||
await page.goto('/agents')
|
||||
await expect(page.getByText(
|
||||
'Agent-Vorschläge und OpenClaw-Provisionierung sind ausschließlich für Owner sichtbar.',
|
||||
)).toBeVisible()
|
||||
await expect(page.getByRole('link', { name: 'Agent vorschlagen' })).toHaveCount(0)
|
||||
|
||||
await page.goto('/agents/new')
|
||||
await expect(page.getByRole('alert')).toContainText('Owner-Berechtigung erforderlich')
|
||||
await expect(page.getByRole('button', { name: 'Vorschlag anlegen' })).toHaveCount(0)
|
||||
|
||||
await page.goto(`/agents/proposals/${PROPOSAL_ID}`)
|
||||
await expect(page.getByRole('alert')).toContainText('Owner-Berechtigung erforderlich')
|
||||
await expect(page.getByRole('button', { name: 'Freigeben' })).toHaveCount(0)
|
||||
|
||||
expect(api.proposalRequests).toEqual([])
|
||||
})
|
||||
|
||||
test('an Iris proposal event becomes a structured approval card and deep link', async ({ page }) => {
|
||||
const api = await mockNexusApi(page, { role: 'owner' })
|
||||
await page.goto('/dashboard')
|
||||
|
||||
await page.getByRole('button', { name: 'Iris Chat öffnen' }).click()
|
||||
const chat = page.getByRole('dialog', { name: 'Iris Chat' })
|
||||
await expect(chat).toBeVisible()
|
||||
await chat.getByRole('textbox', { name: 'Nachricht an Iris' }).fill(
|
||||
'Schlage einen Release Sentinel Agenten vor, aber provisioniere ihn nicht.',
|
||||
)
|
||||
await chat.getByRole('button', { name: 'Nachricht senden' }).click()
|
||||
await expect(chat.getByText('OpenClaw accepted the Iris request.')).toBeVisible()
|
||||
expect(api.chatRequests).toHaveLength(1)
|
||||
expect(api.chatRequests[0]?.body).toMatchObject({
|
||||
agentId: 'iris',
|
||||
message: 'Schlage einen Release Sentinel Agenten vor, aber provisioniere ihn nicht.',
|
||||
})
|
||||
expect(api.proposalRequests).toEqual([])
|
||||
|
||||
await page.evaluate(({ proposalId, occurredAt }) => {
|
||||
window.dispatchEvent(new CustomEvent('nexus:domain-event', {
|
||||
detail: {
|
||||
sequence: 73,
|
||||
eventType: 'agent.proposal.created',
|
||||
entity: {
|
||||
type: 'agent-proposal',
|
||||
id: proposalId,
|
||||
label: 'Release Sentinel',
|
||||
},
|
||||
entityRevision: 1,
|
||||
occurredAt,
|
||||
payload: { state: 'awaiting_approval' },
|
||||
},
|
||||
}))
|
||||
}, { proposalId: PROPOSAL_ID, occurredAt: '2026-07-31T10:00:00.000Z' })
|
||||
|
||||
await expect(chat.getByText(
|
||||
'Iris hat einen Agent-Vorschlag zur Owner-Freigabe angelegt.',
|
||||
)).toBeVisible()
|
||||
const proposalLink = chat.getByRole('link', {
|
||||
name: /Agent-Vorschlag Release Sentinel/,
|
||||
})
|
||||
await expect(proposalLink).toHaveAttribute(
|
||||
'href',
|
||||
`/agents/proposals/${PROPOSAL_ID}`,
|
||||
)
|
||||
await proposalLink.click()
|
||||
|
||||
await expect(page).toHaveURL(`/agents/proposals/${PROPOSAL_ID}`)
|
||||
await expect(page.getByRole('heading', {
|
||||
level: 1,
|
||||
name: 'Release Sentinel',
|
||||
})).toBeVisible()
|
||||
await expect(chat).toHaveCount(0)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user