updates on the hook and endpoints for the ai agents
This commit is contained in:
@@ -25,6 +25,7 @@ jest.mock('@/api/doormile', () => ({
|
||||
createAiSkill: jest.fn(),
|
||||
updateAiAgent: jest.fn(),
|
||||
getAiInsights: jest.fn(),
|
||||
getAiStatus: jest.fn(),
|
||||
getAiDecisions: jest.fn(),
|
||||
runAiPlayground: jest.fn(),
|
||||
}));
|
||||
@@ -148,6 +149,7 @@ beforeEach(() => {
|
||||
api.updateAiAgent.mockResolvedValue({ success: true });
|
||||
api.getAiInsights.mockResolvedValue(INSIGHTS);
|
||||
api.getAiDecisions.mockResolvedValue(DECISIONS);
|
||||
api.getAiStatus.mockResolvedValue({ engine: { readingsettings: false, lastreadat: null }, playground: { configured: false } });
|
||||
});
|
||||
|
||||
// ── Adapters ────────────────────────────────────────────────────────────────
|
||||
@@ -184,18 +186,36 @@ describe('registry adapters', () => {
|
||||
// ── The page ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('Agent Studio page', () => {
|
||||
it('says on screen which agents the registry drives, and when', async () => {
|
||||
it('says "All connected" only when the engine reads the registry and the Test tab has a model', async () => {
|
||||
api.getAiStatus.mockResolvedValue({
|
||||
engine: { readingsettings: true, lastreadat: new Date().toISOString(), telemetry: true, liveagents: 4 },
|
||||
playground: { configured: true, model: 'openai/gpt-oss-120b' },
|
||||
});
|
||||
renderStudio();
|
||||
const note = screen.getByRole('note');
|
||||
expect(note).toHaveTextContent(/saved to the Doormile agent registry/);
|
||||
// The engine reads it only once its Phase 5 build is deployed; say so, not "live".
|
||||
expect(note).toHaveTextContent(/once its registry-reading build is deployed/);
|
||||
expect(note).toHaveTextContent(/environment defaults/);
|
||||
expect(note).toHaveTextContent(/The Test tab runs real AI model calls/);
|
||||
expect(note).not.toHaveTextContent(/The Test tab is a simulation/);
|
||||
// Since Phase 3 the console skills DO run on these settings; the note must not say otherwise.
|
||||
expect(note).toHaveTextContent(/run on these settings now/);
|
||||
expect(note).not.toHaveTextContent(/not merged/);
|
||||
const note = await screen.findByText('All connected.');
|
||||
const banner = note.closest('[role="note"]');
|
||||
expect(banner).toHaveTextContent(/AI_engine is following these settings/);
|
||||
expect(banner).toHaveTextContent(/The Test tab is live \(model openai\/gpt-oss-120b\)/);
|
||||
expect(banner).not.toHaveTextContent(/Half wired/);
|
||||
});
|
||||
|
||||
it('says what is missing when the engine has not read the registry', async () => {
|
||||
api.getAiStatus.mockResolvedValue({
|
||||
engine: { readingsettings: false, lastreadat: null, telemetry: false, liveagents: 0 },
|
||||
playground: { configured: false },
|
||||
});
|
||||
renderStudio();
|
||||
const banner = (await screen.findByText('Partly connected.')).closest('[role="note"]');
|
||||
expect(banner).toHaveTextContent(/AI_engine has not read these settings yet/);
|
||||
expect(banner).toHaveTextContent(/environment defaults/);
|
||||
expect(banner).toHaveTextContent(/Test tab is off/);
|
||||
expect(banner).toHaveTextContent(/saved to the Doormile agent registry/);
|
||||
});
|
||||
|
||||
it('says the backend needs deploying when it has no status endpoint', async () => {
|
||||
api.getAiStatus.mockRejectedValue({ response: { status: 404 } });
|
||||
renderStudio();
|
||||
expect(await screen.findByText(/does not report agent status yet/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows only the selected agent's skills", async () => {
|
||||
@@ -257,7 +277,7 @@ describe('Agent Studio page', () => {
|
||||
renderStudio();
|
||||
await screen.findAllByText('Stalled-rider response');
|
||||
await switchTo('Console Ops Agent');
|
||||
fireEvent.click(await screen.findByText('New skill'));
|
||||
fireEvent.click(await screen.findByText(/^New skill$/i));
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Skill Title *'), { target: { value: 'Night shift watch' } });
|
||||
fireEvent.change(screen.getByLabelText('Description *'), { target: { value: 'Watch the night shift.' } });
|
||||
@@ -281,7 +301,7 @@ describe('Agent Studio page', () => {
|
||||
it('does not offer a new skill on an AI_engine agent', async () => {
|
||||
renderStudio();
|
||||
await screen.findAllByText('Stalled-rider response');
|
||||
expect(screen.getByText('New skill').closest('button')).toBeDisabled();
|
||||
expect(screen.getByText(/^New skill$/i).closest('button')).toBeDisabled();
|
||||
});
|
||||
|
||||
it('needs the agent id typed before switching autonomy on', async () => {
|
||||
@@ -319,7 +339,7 @@ describe('Agent Studio page', () => {
|
||||
renderStudio();
|
||||
await screen.findAllByText('Stalled-rider response');
|
||||
expect(screen.getByText('You can view the registry. Only an admin can change it.')).toBeInTheDocument();
|
||||
expect(screen.getByText('New skill').closest('button')).toBeDisabled();
|
||||
expect(screen.getByText(/^New skill$/i).closest('button')).toBeDisabled();
|
||||
|
||||
fireEvent.click(screen.getByText('Details'));
|
||||
expect(screen.getByRole('switch', { name: 'Stalled-rider response enabled' })).toBeDisabled();
|
||||
@@ -331,12 +351,16 @@ describe('Agent Studio page', () => {
|
||||
renderStudio();
|
||||
await screen.findAllByText('Stalled-rider response');
|
||||
fireEvent.click(screen.getAllByText('Tools')[0]);
|
||||
const row = screen.getAllByText('nearby_milers').map((el) => el.closest('tr')).find(Boolean);
|
||||
expect(within(row).getByText('Read-only')).toBeInTheDocument();
|
||||
expect(within(row).getByText('No confirmation needed')).toBeInTheDocument();
|
||||
const gated = screen.getAllByText('reassign_booking').map((el) => el.closest('tr')).find(Boolean);
|
||||
expect(within(gated).getByText('Writes')).toBeInTheDocument();
|
||||
expect(within(gated).getByText('Confirmation Required')).toBeInTheDocument();
|
||||
// Tools render as cards (a header button per tool): kind + safety badges.
|
||||
// The name also appears in the agent navigator; the tool card is the button carrying its kind badge.
|
||||
const toolCard = (name) =>
|
||||
screen.getAllByText(name).map((el) => el.closest('button')).find((btn) => btn && within(btn).queryByText(/^(Read|Write|Notify|General)$/));
|
||||
const card = toolCard('nearby_milers');
|
||||
expect(within(card).getByText('Read')).toBeInTheDocument();
|
||||
expect(within(card).getByText('Open')).toBeInTheDocument();
|
||||
const gated = toolCard('reassign_booking');
|
||||
expect(within(gated).getByText('Write')).toBeInTheDocument();
|
||||
expect(within(gated).getByText('Gated')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Autonomous')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -406,7 +430,8 @@ describe('Agent Studio Test tab', () => {
|
||||
const openTest = async () => {
|
||||
renderStudio();
|
||||
await screen.findAllByText('Stalled-rider response');
|
||||
fireEvent.click(screen.getByText('Test'));
|
||||
// The tab row renders before the skill cards, whose buttons also say Test.
|
||||
fireEvent.click(screen.getAllByText('Test')[0]);
|
||||
};
|
||||
|
||||
it('runs the prompt on the backend and shows its real trace', async () => {
|
||||
|
||||
Reference in New Issue
Block a user