feat(module2): chat with your agent — three canned prompts, then Layers 2/3
Module 2 is now a chat straight to the agent instead of Build & flash. Removed the "Open your node" links and the Live board feed card. New AgentChat component: three imperative canned prompts — - List the I2C devices on the bus - Count to 100 and print the value once a second in the LED matrix - Scroll GO CLAWS on the LED matrix Each sends to the agent (fire-and-forget, cloud) and streams its activity (tools/flash/reply) back into the transcript. A prompt is marked done on its first terminal step (flash/response); an error resets it to retry. Prompts run one at a time. Once all three have run successfully, Module 2 reveals "What's next" — the ADD Layer 2 (Skills) + Layer 3 (Policies & failure) capture — and Proceed gates on all-three-tried AND L2 + L3 filled. Note: BuildFlash / LiveBoardFeed / ActorMap are now orphaned (kept for possible reuse). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c2ef338888
commit
7780903278
+41
-12
@@ -13,7 +13,6 @@ vi.mock('@/lib/api', async (orig) => ({
|
||||
emit = on
|
||||
return () => {}
|
||||
},
|
||||
getNodeStatus: vi.fn().mockResolvedValue({ teamId: 't', online: false }),
|
||||
sendPrompt: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
@@ -25,6 +24,13 @@ function renderPage() {
|
||||
)
|
||||
}
|
||||
|
||||
/** Click a canned prompt and let the agent reach a terminal (success) step. */
|
||||
async function completePrompt(user: ReturnType<typeof userEvent.setup>, id: string) {
|
||||
await user.click(screen.getByTestId(`prompt-${id}`))
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await waitFor(() => expect(screen.getByTestId(`prompt-${id}`)).toHaveAttribute('data-state', 'done'))
|
||||
}
|
||||
|
||||
describe('Module2', () => {
|
||||
beforeEach(() => {
|
||||
useSession.getState().reset()
|
||||
@@ -39,31 +45,54 @@ describe('Module2', () => {
|
||||
).toHaveAttribute('data-state', 'active')
|
||||
})
|
||||
|
||||
it('shows the live board feed and the build & flash panel', () => {
|
||||
it('is a chat with the three canned prompts — no live feed / build & flash', () => {
|
||||
renderPage()
|
||||
expect(screen.getByTestId('live-board-feed')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('activity-log')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('agent-chat')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('prompt-i2c')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('prompt-count')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('prompt-scroll')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('live-board-feed')).toBeNull()
|
||||
expect(screen.queryByTestId('activity-log')).toBeNull()
|
||||
})
|
||||
|
||||
it('gates Proceed until the board acted and L2 + L3 are filled', async () => {
|
||||
it('reveals "what\'s next" (the ADD layers) only after all three prompts succeed', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
expect(screen.queryByTestId('whats-next')).toBeNull()
|
||||
expect(screen.queryByLabelText(/layer 2/i)).toBeNull()
|
||||
|
||||
await completePrompt(user, 'i2c')
|
||||
await completePrompt(user, 'count')
|
||||
expect(screen.queryByTestId('whats-next')).toBeNull() // still one to go
|
||||
await completePrompt(user, 'scroll')
|
||||
|
||||
expect(screen.getByTestId('whats-next')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText(/layer 2/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('gates Proceed until all prompts ran AND L2 + L3 are filled', async () => {
|
||||
const user = userEvent.setup()
|
||||
useSession.getState().setAddLayer('L2', 'escalate on critical')
|
||||
useSession.getState().setAddLayer('L3', 'drive damper on critical')
|
||||
renderPage()
|
||||
const proceed = screen.getByRole('button', { name: /proceed/i })
|
||||
expect(proceed).toBeDisabled()
|
||||
|
||||
// the board acts (via Build & flash) → activity streams from the node
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await waitFor(() => expect(proceed).toBeEnabled())
|
||||
await completePrompt(user, 'i2c')
|
||||
await completePrompt(user, 'count')
|
||||
await completePrompt(user, 'scroll')
|
||||
|
||||
const proceed = screen.getByRole('button', { name: /proceed/i })
|
||||
expect(proceed).toBeEnabled()
|
||||
})
|
||||
|
||||
it('marks m2 complete on Proceed', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderPage()
|
||||
await completePrompt(user, 'i2c')
|
||||
await completePrompt(user, 'count')
|
||||
await completePrompt(user, 'scroll')
|
||||
await user.type(screen.getByLabelText(/layer 2/i), 'L2 text')
|
||||
await user.type(screen.getByLabelText(/layer 3/i), 'L3 text')
|
||||
act(() => emit({ type: 'node:activity', teamId: 'x', kind: 'response', label: 'Agent finished', ts: '' }))
|
||||
await user.click(await screen.findByRole('button', { name: /proceed/i }))
|
||||
await user.click(screen.getByRole('button', { name: /proceed/i }))
|
||||
expect(useSession.getState().phases.m2).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user