Built-in design generation now runs as an agentic MCP tool-loop (reusing the agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the Settings experimental toggle; the orchestrator stays the default. - design-agent system prompt + in-process design toolset (parity-locked with the MCP surface) + flag-gated Intent::Design routing - spawn_agents execution as sequential sub-loops + live creation-mode badges (per-agent glow + 'N/M designing...' header) - new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state, export_nodes, spawn_agents; style-guide local audit - #27 AI panel restyle: rounded tool cards + green check-rings, gray user bubbles, model-pill bottom toolbar, header, empty-state pills, the PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown - multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI (switch / close / + / Cmd+T) with each run bound to its tab Large checkpoint commit spanning the working tree (Rust shell crates).
86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
shouldShortCircuitPlanLayout,
|
|
updateLayoutSessionState,
|
|
type LayoutSessionState,
|
|
} from '../utils/agent-tool-guard';
|
|
|
|
function createSessionState(): LayoutSessionState {
|
|
return {
|
|
layoutPhase: 'idle',
|
|
layoutRootId: null,
|
|
};
|
|
}
|
|
|
|
describe('agent tool guard', () => {
|
|
it('does not short-circuit the first plan_layout call', () => {
|
|
const session = createSessionState();
|
|
|
|
const result = shouldShortCircuitPlanLayout(session, 'plan_layout', {
|
|
prompt: 'mobile food app homepage',
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('short-circuits repeated plan_layout calls once layout exists', () => {
|
|
const session: LayoutSessionState = {
|
|
layoutPhase: 'layout_done',
|
|
layoutRootId: 'root-1',
|
|
};
|
|
|
|
const result = shouldShortCircuitPlanLayout(session, 'plan_layout', {
|
|
prompt: 'mobile food app homepage',
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
success: false,
|
|
data: { rootFrameId: 'root-1' },
|
|
});
|
|
expect(result?.error).toContain('Use batch_insert or insert_node');
|
|
expect(result?.error).toContain('"newRoot": true');
|
|
});
|
|
|
|
it('allows repeated plan_layout when newRoot is explicitly requested', () => {
|
|
const session: LayoutSessionState = {
|
|
layoutPhase: 'layout_done',
|
|
layoutRootId: 'root-1',
|
|
};
|
|
|
|
const result = shouldShortCircuitPlanLayout(session, 'plan_layout', {
|
|
prompt: 'another screen',
|
|
newRoot: true,
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it('tracks plan_layout success as layout_done with rootFrameId', () => {
|
|
const session = createSessionState();
|
|
|
|
updateLayoutSessionState(session, 'plan_layout', {
|
|
success: true,
|
|
data: { rootFrameId: 'root-1' },
|
|
});
|
|
|
|
expect(session).toEqual({
|
|
layoutPhase: 'layout_done',
|
|
layoutRootId: 'root-1',
|
|
});
|
|
});
|
|
|
|
it('tracks content insertion success as content_started', () => {
|
|
const session: LayoutSessionState = {
|
|
layoutPhase: 'layout_done',
|
|
layoutRootId: 'root-1',
|
|
};
|
|
|
|
updateLayoutSessionState(session, 'batch_insert', { success: true });
|
|
expect(session.layoutPhase).toBe('content_started');
|
|
|
|
session.layoutPhase = 'layout_done';
|
|
updateLayoutSessionState(session, 'insert_node', { success: true });
|
|
expect(session.layoutPhase).toBe('content_started');
|
|
});
|
|
});
|