feat: add assistant board styling tools

This commit is contained in:
ajnart committed 2026-08-01 10:39:41 +02:00
1 parent a5fa6d3afe
commit 5458e7d647
21 files changed
+811 -18

No files matched your search

@@ -57,6 +57,36 @@ describe("getForcedAssistantToolName", () => {
).toBeUndefined();
});
test("continues directly from reviewed board settings to the settings mutation", () => {
expect(
getForcedAssistantToolName([
assistantMessage({
type: "dynamic-tool",
toolName: "configure_board_settings",
toolCallId: "settings-1",
input: { boardId: "board-1", boardName: "Home", changes: { customCss: ".item {}" } },
state: "output-available",
output: { id: "board-1", customCss: ".item {}" },
}),
]),
).toBe("board_savePartialBoardSettings");
});
test("does not request a mutation when board settings were left unchanged", () => {
expect(
getForcedAssistantToolName([
assistantMessage({
type: "dynamic-tool",
toolName: "configure_board_settings",
toolCallId: "settings-1",
input: { boardId: "board-1", boardName: "Home", changes: {} },
state: "output-available",
output: { id: "board-1", cancelled: true },
}),
]),
).toBeUndefined();
});
test("does not repeat app creation after the mutation tool has been called", () => {
expect(
getForcedAssistantToolName([
@@ -14,13 +14,23 @@ export const getForcedAssistantToolName = (messages: UIMessage[]) => {
if (latestMessage?.role !== "assistant") return undefined;
const latestToolPart = latestMessage.parts.toReversed().find((part) => isToolUIPart(part));
if (latestToolPart === undefined || latestToolPart.state !== "output-available") return undefined;
const nextToolByHumanTool = {
configure_app: "app_create",
configure_board_settings: "board_savePartialBoardSettings",
} as const;
const toolName = getToolName(latestToolPart);
if (
latestToolPart === undefined ||
getToolName(latestToolPart) !== "configure_app" ||
latestToolPart.state !== "output-available"
toolName === "configure_board_settings" &&
typeof latestToolPart.output === "object" &&
latestToolPart.output !== null &&
"cancelled" in latestToolPart.output &&
latestToolPart.output.cancelled === true
) {
return undefined;
}
return "app_create" as const;
return toolName in nextToolByHumanTool
? nextToolByHumanTool[toolName as keyof typeof nextToolByHumanTool]
: undefined;
};
@@ -94,6 +94,7 @@ Action rules:
- A prose response such as "Please confirm if you would like me to proceed", "Would you like me to proceed?", or a parameter summary that asks for confirmation is incorrect. Never ask for a second textual confirmation before an approval-gated tool call.
- Do not retry a denied action.
- Before creating an app, call configure_app with the best defaults so the user can review Homarr's native app form. Its icon picker searches Homarr's local icon repository. Use the returned values for app_create.
- Before changing a board's custom CSS or visual and behavior settings, call board_getBoardSettings to read the current values, then call configure_board_settings with only the requested proposed changes. The user can edit the complete CSS and settings in Homarr's native form. Use its returned flat object for board_savePartialBoardSettings, which then shows the mutation approval. Never overwrite existing CSS rules unless the user explicitly requests replacement.
- When choosing an app icon without configure_app, call the Homarr icon findIcons tool first and use one of its returned local icon URLs. Never invent a third-party icon CDN URL.
- Browser tools can navigate within Homarr or open existing Homarr UI. Never navigate to an arbitrary external URL.
- Keep responses concise and lead with the result. Summarize tool output instead of dumping JSON.