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).
59 lines
1.5 KiB
TypeScript
59 lines
1.5 KiB
TypeScript
import {
|
|
createError,
|
|
defineEventHandler,
|
|
getQuery,
|
|
getRequestHeader,
|
|
setResponseHeaders,
|
|
} from 'h3';
|
|
import { readFile } from 'node:fs/promises';
|
|
|
|
import { resolveServableLocalImagePath } from '../utils/local-asset';
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const { path } = getQuery(event) as { path?: string };
|
|
const secFetchSite = getRequestHeader(event, 'sec-fetch-site');
|
|
|
|
if (secFetchSite === 'cross-site') {
|
|
throw createError({
|
|
statusCode: 403,
|
|
message: 'Cross-site local asset requests are blocked',
|
|
});
|
|
}
|
|
|
|
if (!path?.trim()) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'Missing required query parameter: path',
|
|
});
|
|
}
|
|
|
|
if (path.includes('\0') || !isAbsoluteLocalPath(path)) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: 'Only absolute local image paths are supported',
|
|
});
|
|
}
|
|
|
|
const resolvedAsset = await resolveServableLocalImagePath(path);
|
|
if (!resolvedAsset) {
|
|
throw createError({
|
|
statusCode: 404,
|
|
message: 'Image file not found or unsupported',
|
|
});
|
|
}
|
|
|
|
const content = await readFile(resolvedAsset.resolvedPath);
|
|
setResponseHeaders(event, {
|
|
'Content-Type': resolvedAsset.mimeType,
|
|
'Cache-Control': 'no-cache',
|
|
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
'X-Content-Type-Options': 'nosniff',
|
|
});
|
|
|
|
return content;
|
|
});
|
|
|
|
function isAbsoluteLocalPath(value: string): boolean {
|
|
return /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('\\\\') || value.startsWith('/');
|
|
}
|