feat(assistant): optimize MCP and custom widget authoring (#6743)

* feat(assistant): optimize MCP and custom widget authoring

* fix(assistant): isolate server-safe JSX analyzer

* refactor(assistant): generate MCP tool staging

* fix(assistant): preserve widget placement follow-up

* fix(ci): allow fast gate to finish

* fix(custom-widgets): address review findings

* fix(custom-widgets): split preview evidence module

* style: format assistant custom widget files

* fix(custom-widgets): sync formatted skill schema
This commit is contained in:
Thomas authored and GitHub committed 2026-08-30 23:43:29 +02:00
1 parent 38f4b0e1f8
commit 5e23659d99
95 files changed
+6799 -1778

No files matched your search

@@ -18,6 +18,7 @@ import { configureCustomWidgetSourceFromRequest } from "../../custom-widget/secr
import {
appendPreviewJournal,
configurePreviewSessionSource,
getPreviewJournal,
getPreviewSession,
} from "../../custom-widget/preview-sessions";
import { serializeCustomWidgetDefinition } from "../../custom-widget/stored-definition";
@@ -212,6 +213,125 @@ describe("custom widget definition persistence", () => {
});
});
test("requires current-revision query and action evidence after a template-only preview revision", async () => {
const db = await prepareDatabase();
const caller = createCaller(db);
const definition = customWidgetDefinitionSchema.parse({
$schema: "homarr-custom-widget-v2",
name: "Request queue",
sources: {
default: { baseUrl: "https://example.com", networkScope: "public", auth: "none" },
},
requests: {
queue: { path: "/requests" },
approve: {
kind: "action",
method: "POST",
path: "/requests/{param:id}/approve",
confirmation: "Approve this request?",
invalidates: ["queue"],
},
},
options: {},
template:
'<Stack><Text>{data.queue?.length ?? 0} requests</Text><ActionButton requestId="approve" params={{ id: 1 }}>Approve</ActionButton></Stack>',
});
const preview = await caller.previewCreate({ definition, secrets: [] });
const createInput = { previewSessionId: preview.previewSession.id };
const initialSession = await getPreviewSession(preview.previewSession.id, userId);
await appendPreviewJournal(initialSession, {
requestId: "queue",
kind: "query",
method: "GET",
path: "/requests",
status: 200,
durationMs: 1,
simulated: false,
});
await expect(caller.createFromPreview(createInput)).rejects.toThrow(
"Test every final preview action before creating the widget: approve",
);
await appendPreviewJournal(initialSession, {
requestId: "approve",
kind: "action",
method: "POST",
path: "/requests/{param:id}/approve",
status: null,
durationMs: 0,
simulated: true,
});
const revisedTemplate = definition.template.replace("requests</Text>", "pending requests</Text>");
await caller.previewReviseTemplate({
sessionId: initialSession.id,
expectedRevision: initialSession.revision,
template: revisedTemplate,
});
await expect(caller.createFromPreview(createInput)).rejects.toThrow(
"Test every final preview query successfully before creating the widget: queue",
);
const revisedSession = await getPreviewSession(preview.previewSession.id, userId);
await appendPreviewJournal(revisedSession, {
requestId: "queue",
kind: "query",
method: "GET",
path: "/requests",
status: 200,
durationMs: 1,
simulated: false,
});
await appendPreviewJournal(revisedSession, {
requestId: "approve",
kind: "action",
method: "POST",
path: "/requests/{param:id}/approve",
status: null,
durationMs: 0,
simulated: true,
});
const created = await caller.createFromPreview(createInput);
await expect(caller.get({ id: created.id })).resolves.toMatchObject({ template: revisedTemplate });
});
test("retains persistence evidence for every request beyond the bounded journal", async () => {
const db = await prepareDatabase();
const caller = createCaller(db);
const requests = Object.fromEntries(
Array.from({ length: 64 }, (_, index) => [`query-${index}`, { path: `/queries/${index}` }]),
);
const definition = customWidgetDefinitionSchema.parse({
$schema: "homarr-custom-widget-v2",
name: "Large request set",
sources: {
default: { baseUrl: "https://example.com", networkScope: "public", auth: "none" },
},
requests,
options: {},
template: "<Text>All requests tested</Text>",
});
const preview = await caller.previewCreate({ definition, secrets: [] });
const previewSession = await getPreviewSession(preview.previewSession.id, userId);
for (const [requestId, request] of Object.entries(previewSession.requests)) {
await appendPreviewJournal(previewSession, {
requestId,
kind: "query",
method: request.method,
path: request.path,
status: 200,
durationMs: 1,
simulated: false,
});
}
expect(await getPreviewJournal(previewSession.id, userId)).toHaveLength(50);
await expect(caller.createFromPreview({ previewSessionId: previewSession.id })).resolves.toMatchObject({
managementPath: expect.stringContaining("/manage/custom-widgets/edit/"),
});
});
test("updates a saved widget with MCP-friendly multiline templateLines", async () => {
const db = await prepareDatabase();
const caller = createCaller(db);
@@ -91,4 +91,52 @@ describe("custom widget agent preview workflow", () => {
});
expect(mocks.executeRequest).not.toHaveBeenCalled();
});
test("revises a preview template without resending or changing its manifest", async () => {
const db = createDb();
const userId = createId();
await db.insert(users).values({ id: userId });
const session = {
user: { id: userId, permissions: ["admin"], colorScheme: "light" },
expires: new Date(Date.now() + 60_000).toISOString(),
} satisfies Session;
const caller = customWidgetRouter.createCaller({ db, deviceType: undefined, session });
const { template, ...definition } = CUSTOM_WIDGET_STARTER;
const preview = await caller.previewCreate({
definition: {
...definition,
requests: { fixtures: { path: "/fixtures" } },
templateLines: template.split("\n"),
},
secrets: [],
});
const revisedTemplate = "<Stack><Text>{data.fixtures?.name}</Text><Badge>Ready</Badge></Stack>";
const revised = await caller.previewReviseTemplate({
sessionId: preview.previewSession.id,
expectedRevision: 0,
templateLines: revisedTemplate.split("\n"),
});
expect(revised).toMatchObject({
success: true,
evidenceReset: true,
previewSession: { id: preview.previewSession.id, revision: 1 },
queries: [expect.objectContaining({ requestId: "fixtures" })],
});
expect(revised).not.toHaveProperty("definition");
expect(revised).not.toHaveProperty("template");
await expect(caller.previewGet({ sessionId: preview.previewSession.id })).resolves.toMatchObject({
revision: 1,
template: revisedTemplate,
requests: [expect.objectContaining({ id: "fixtures" })],
});
await expect(
caller.previewReviseTemplate({
sessionId: preview.previewSession.id,
expectedRevision: 1,
templateLines: ['<ActionButton requestId="missing">Run</ActionButton>'],
}),
).rejects.toMatchObject({ code: "BAD_REQUEST" });
});
});