From 2e4c0789a25a51ec9fdb05ea6f57e1e70c73e591 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 28 Oct 2026 21:32:20 +0100 Subject: [PATCH] feat: agent to flow, tabbed agent page, one webhook panel with an AI prompt (#11903) * feat: open a flow linking an agent from its menu Co-Authored-By: Claude Opus 5.5 * fix: toast when the flow handoff cannot be stored Co-Authored-By: Claude Opus 5.5 * feat: turn an agent into a flow from its editor, and into a chat app from its page Co-Authored-By: Claude Opus 5.5 * fix: let sandboxed apps run agents, and keep the flow handoff race-free Co-Authored-By: Claude Opus 5.5 * fix: save the new flow as a draft before letting the agent draft go Co-Authored-By: Claude Opus 5.5 * fix: offer the chat app only to those who may build apps Co-Authored-By: Claude Opus 5.5 * feat: give the agent page's chat app section a tab on small screens Co-Authored-By: Claude Opus 5.5 * fix: show the flow handoff as an info alert, framed around context and output Co-Authored-By: Claude Opus 5.5 * fix: keep the flow handoff from operators and say why the chat app is locked Co-Authored-By: Claude Opus 5.5 * fix: collapse the flow handoff hint to its title and button Co-Authored-By: Claude Opus 5.5 * fix: show the flow handoff as a corner hint, as the flow editor shows its own Co-Authored-By: Claude Opus 5.5 * fix: name the agent on the app button and say why it is disabled Co-Authored-By: Claude Opus 5.5 * fix: ask whether the agent needs steps before or after it Co-Authored-By: Claude Opus 5.5 * fix: name what a flow adds around the agent in its hint Co-Authored-By: Claude Opus 5.5 * fix: say a never-deployed agent turns into a flow Co-Authored-By: Claude Opus 5.5 * revert: leave agent runs and the raw-app guidance to the chat SDK's runnable transport Co-Authored-By: Claude Opus 5.5 * feat: show the agent page's sections as tabs at every width, as a chat-mode flow's page does Co-Authored-By: Claude Opus 5.5 * feat: one webhook panel for scripts, flows and agents, with an AI prompt and chat-mode examples Co-Authored-By: Claude Opus 5.5 * docs: describe the preprocessor's event argument instead of the v1 wm_trigger Co-Authored-By: Claude Opus 5.5 * docs: give the flow chat's preprocessor example the v2 event signature Co-Authored-By: Claude Opus 5.5 * feat: group a webhook's request choices and examples under an HTTP tab, beside the AI prompt and chat SDK Co-Authored-By: Claude Opus 5.5 * chore: drop a comment about guidance this branch no longer adds Co-Authored-By: Claude Opus 5.5 * fix: say where the AI prompt expects the webhook token Co-Authored-By: Claude Opus 5.5 * fix: tell the chat-flow prompt that memory_id is required Co-Authored-By: Claude Opus 5.5 * fix: keep the webhook tabs next to their content Co-Authored-By: Claude Opus 5.5 * fix: keep the webhook's REST tab name Co-Authored-By: Claude Opus 5.5 * feat: build an agent's chat app from a template, not an AI session Co-Authored-By: Claude Opus 5.5 * fix: don't send the agent chat app's message on an IME confirmation Co-Authored-By: Claude Opus 5.5 * feat: agent API tab with one row of call examples and a copy-prompt button Co-Authored-By: Claude Opus 5.5 * feat: open build app in an AI session when AI sessions are on Co-Authored-By: Claude Opus 5.5 * feat: app conversations through the memory_id query parameter, chat-mode flows included Co-Authored-By: Claude Opus 5.5 * chore: regenerate the mcp session instructions Co-Authored-By: Claude Opus 5.5 * fix: type backendAsync's memoryId option in the eval diagnostics Co-Authored-By: Claude Opus 5.5 * chore: pin git-sync hub scripts to windmill-cli 1.849.0 Co-Authored-By: Claude Opus 5.5 * fix: keep build app in the editor when windmill ai is off Co-Authored-By: Claude Opus 5.5 * feat: webhook panel with http and chat sdk tabs over toggle settings Co-Authored-By: Claude Opus 5.5 * feat: memoryId option on the sync backend call too Co-Authored-By: Claude Opus 5.5 * chore: bump the ui builder artifact for the memoryId option Co-Authored-By: Claude Opus 5.5 * fix: keep the webhook panel's http and chat sdk tabs close to their content Co-Authored-By: Claude Opus 5.5 * chore: shared-utils 1.0.14 Co-Authored-By: Claude Opus 5.5 * chore: cli takes shared-utils 1.0.14 for the memoryId option Co-Authored-By: Claude Opus 5.5 * test: pin the app conversation scoping and the memoryId signatures Co-Authored-By: Claude Opus 5.5 * test: assert the ownership refusal, not any refusal Co-Authored-By: Claude Opus 5.5 * test: match the memoryId option in the cli wmill.ts signature tests Co-Authored-By: Claude Opus 5.5 * feat: show the webhook request formats in a card with their toggle Co-Authored-By: Claude Opus 5.5 * feat: put the chat sdk example in the same card as the http request Co-Authored-By: Claude Opus 5.5 * feat: add a windmill app tab to chat-mode flow pages Co-Authored-By: Claude Opus 5.5 * feat: title the webhook example cards and label the ai prompt Co-Authored-By: Claude Opus 5.5 * feat: offer the chat app from the conversations sidebar instead of a tab Co-Authored-By: Claude Opus 5.5 * chore: raise the chat app's chat-sdk floor to 1.851.0 Co-Authored-By: Claude Opus 5.5 * fix: render the agent's chat app card only once the workspace is known Co-Authored-By: Claude Opus 5.5 * fix: gate the flow's build chat app menu item like the sidebar card Co-Authored-By: Claude Opus 5.5 * fix: build the agent's chat app through the page's navigation, for app-only authors too Co-Authored-By: Claude Opus 5.5 * fix: keep an imported app in the editor inside a session preview Co-Authored-By: Claude Opus 5.5 * feat: build a form app from an agent without managed memory Co-Authored-By: Claude Opus 5.5 * test: cover the agent form app's generated source Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- .../details/BuildChatAppCard.svelte | 80 +++ .../lib/components/details/FlowDetail.svelte | 52 +- .../details/createRawAppFromScript.test.ts | 17 + .../details/createRawAppFromScript.ts | 464 +++++++++++++++++- .../src/lib/components/flows/agentActions.ts | 77 ++- .../flows/content/AgentDetailPage.svelte | 129 +++-- .../flows/content/AgentEditorHost.svelte | 66 ++- .../flows/content/AgentEditorModal.svelte | 93 +++- .../flows/content/AgentUsePanel.svelte | 166 +------ .../flows/content/FlowEditorPanel.svelte | 1 + .../flows/content/FlowModuleWrapper.svelte | 5 +- .../flows/conversations/FlowChat.svelte | 6 +- .../FlowConversationsSidebar.svelte | 9 +- .../src/lib/components/raw_apps/templates.ts | 4 +- .../components/triggers/TriggersEditor.svelte | 6 +- .../triggers/TriggersWrapper.svelte | 4 + .../webhook/WebhooksConfigSection.svelte | 417 +++++++++------- .../triggers/webhook/WebhooksPanel.svelte | 31 +- .../webhook/webhookExamples.ts} | 162 ++++-- .../apps_raw/edit/[...path]/+page.svelte | 77 +-- system_prompts/auto-generated/prompts.ts | 7 +- .../base/flow-chat-special-modules.md | 7 +- 22 files changed, 1369 insertions(+), 511 deletions(-) create mode 100644 frontend/src/lib/components/details/BuildChatAppCard.svelte rename frontend/src/lib/components/{flows/content/agentApiExamples.ts => triggers/webhook/webhookExamples.ts} (57%) diff --git a/frontend/src/lib/components/details/BuildChatAppCard.svelte b/frontend/src/lib/components/details/BuildChatAppCard.svelte new file mode 100644 index 0000000000..7c1a52ef32 --- /dev/null +++ b/frontend/src/lib/components/details/BuildChatAppCard.svelte @@ -0,0 +1,80 @@ + + +{#snippet card(classes: string)} +
+ Share as an app + + Turn this {kind} into a chat app you can share. Adapt its design to fit your needs. + + +
+{/snippet} + +{#if canBuild} + {#if expanded} + {@render card('rounded-md border border-light bg-surface-tertiary')} + {:else} + + + {#snippet trigger()} + {error !== undefined &&
{error}
} - {result !== undefined &&
{JSON.stringify(result, null, 2)}
} + {result !== undefined &&
{${outputOnly ? `typeof result === 'string' ? result : ` : ''}JSON.stringify(result, null, 2)}
} ) } @@ -651,7 +656,7 @@ function createRawApp(opts: { path: string summary: string | undefined schema: Record | undefined - runType: 'script' | 'flow' + runType: 'script' | 'flow' | 'agent' }): RawAppImport { const { path, summary, schema, runType } = opts const runnableId = runnableIdFromPath(path) @@ -677,6 +682,7 @@ function createRawApp(opts: { return { summary: title, + openInAiSession: true, value: { files: { ...react19Template, @@ -684,7 +690,8 @@ function createRawApp(opts: { runnableId, fields, title, - subtitle: `${runType} · ${path}` + subtitle: `${runType} · ${path}`, + outputOnly: runType === 'agent' }), '/index.css': indexCss }, @@ -718,3 +725,448 @@ export function createRawAppFromFlow( ): RawAppImport { return createRawApp({ path, summary, schema, runType: 'flow' }) } + +/** For an agent without managed memory, which answers each message on its own: a form over the + * message. Attachments are left out, a raw app having no upload for them. */ +export function createRawAppFromAgent(path: string): RawAppImport { + const { user_message } = AGENT_CHAT_SCHEMA.properties + return createRawApp({ + path, + summary: undefined, + schema: { ...AGENT_CHAT_SCHEMA, properties: { user_message } }, + runType: 'agent' + }) +} + +const chatAppTsx = (chatName: string) => `import React, { useEffect, useRef, useState } from 'react' +import { useWindmillChat } from '@windmill-labs/chat-sdk/react' +import * as wmill from './wmill' +import './index.css' + +const CHAT_NAME = ${JSON.stringify(chatName)} +document.title = CHAT_NAME + +export default function App() { + // Each turn runs through the \`chat\` runnable, as this app. Conversations are + // kept in the viewer's browser. + const chat = useWindmillChat({ wmill, runnable: 'chat' }) + const [input, setInput] = useState('') + const [showList, setShowList] = useState(false) + const end = useRef(null) + const busy = chat.status === 'submitted' || chat.status === 'streaming' + const last = chat.messages[chat.messages.length - 1] + + useEffect(() => { + end.current?.scrollIntoView({ block: 'end' }) + }, [chat.messages]) + + function send() { + const text = input.trim() + if (!text || busy) return + setInput('') + // A message refused before it starts leaves the transcript as it was: give it back. + chat.sendMessage(text).catch(() => setInput(text)) + } + + function open(id?: string) { + if (id) chat.selectConversation(id) + else chat.newConversation() + setShowList(false) + } + + return ( +
+ + {showList &&
setShowList(false)} />} + +
+
+ +

{CHAT_NAME}

+
+ +
+
+ {chat.messages.length === 0 && ( +

Ask {CHAT_NAME} anything.

+ )} + {chat.messages.map((m) => + m.tool ? ( +
+ {m.tool.name} + {m.tool.status === 'running' ? '…' : m.tool.status === 'error' ? ' failed' : ''} +
+ ) : m.role === 'user' || m.role === 'assistant' ? ( +
+ {m.content || (m.pending && Thinking…)} +
+ ) : null + )} + {busy && last?.role === 'user' && ( +
+ Thinking… +
+ )} +
+
+
+ +