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).
63 lines
2 KiB
TypeScript
63 lines
2 KiB
TypeScript
import type { ToolResult } from '../../src/types/agent';
|
|
|
|
export type LayoutPhase = 'idle' | 'layout_done' | 'content_started';
|
|
|
|
export interface LayoutSessionState {
|
|
layoutPhase: LayoutPhase;
|
|
layoutRootId: string | null;
|
|
}
|
|
|
|
function parseToolInput(input: unknown): Record<string, unknown> | null {
|
|
if (!input) return null;
|
|
if (typeof input === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(input);
|
|
return parsed && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return typeof input === 'object' ? (input as Record<string, unknown>) : null;
|
|
}
|
|
|
|
export function shouldShortCircuitPlanLayout(
|
|
session: LayoutSessionState,
|
|
toolName: string,
|
|
input: unknown,
|
|
): ToolResult | null {
|
|
if (toolName !== 'plan_layout') return null;
|
|
if (session.layoutPhase === 'idle') return null;
|
|
|
|
const parsed = parseToolInput(input);
|
|
const newRoot = parsed?.newRoot === true;
|
|
if (newRoot) return null;
|
|
|
|
return {
|
|
success: false,
|
|
...(session.layoutRootId ? { data: { rootFrameId: session.layoutRootId } } : {}),
|
|
error:
|
|
`Layout already exists for this session${session.layoutRootId ? ` (rootFrameId: ${session.layoutRootId})` : ''}. ` +
|
|
'Use batch_insert or insert_node to add content to the existing frame. ' +
|
|
'Only call plan_layout again with {"prompt": "...", "newRoot": true} if you intentionally want another root frame or artboard.',
|
|
};
|
|
}
|
|
|
|
export function updateLayoutSessionState(
|
|
session: LayoutSessionState,
|
|
toolName: string | undefined,
|
|
result: ToolResult,
|
|
): void {
|
|
if (!toolName || !result?.success) return;
|
|
|
|
if (toolName === 'plan_layout') {
|
|
session.layoutPhase = 'layout_done';
|
|
const rootFrameId = (result.data as { rootFrameId?: string } | undefined)?.rootFrameId;
|
|
if (rootFrameId) session.layoutRootId = rootFrameId;
|
|
return;
|
|
}
|
|
|
|
if (toolName === 'batch_insert' || toolName === 'insert_node') {
|
|
session.layoutPhase = 'content_started';
|
|
}
|
|
}
|