@@ -11,6 +11,7 @@ import {
1111 SelectTrigger ,
1212 SelectValue ,
1313} from "@components/ui/select" ;
14+ import { buildPlatformShellNote } from "@utils/platformPrompt" ;
1415import {
1516 AlertCircle ,
1617 ArrowUp ,
@@ -54,22 +55,43 @@ const PROVIDER_DEFAULTS: Record<Provider, { model: string; baseUrl: string; labe
5455 "claude-code" : { model : "claude" , baseUrl : "" , label : "Claude Code" } ,
5556} ;
5657
57- function buildChatSystemPrompt ( isWindows : boolean ) : string {
58- const shellNote = isWindows
59- ? `## Platform: Windows
60- run-command uses cmd.exe (NOT PowerShell). Use cmd.exe syntax only.
61- - Open apps: \`start notepad.exe\`, \`start "" calc.exe\`, \`start "" "C:\\\\Windows\\\\System32\\\\mspaint.exe"\`
62- - Create dirs: \`mkdir C:\\\\path\\\\to\\\\dir\`
63- - Write files: \`echo text > file.txt\`
64- - Do NOT use: \`Start-Process\`, \`Write-Host\`, \`$env:\`, \`New-Object\`, or ANY PowerShell syntax
65- - Do NOT chain commands with \`;\` — use \`&&\` or separate run-command blocks
66- - To type text into an open app: create a workflow with a \`desktopKeyboard\` step (type: "desktopKeyboard", text: "Hello world") — do NOT try to do it via run-command
67- `
68- : `## Platform: Linux/macOS
69- run-command uses /bin/sh.
70- - Notifications: \`notify-send "Title" "Body"\` (Linux) or \`osascript -e 'display notification "Body" with title "Title"'\` (macOS)
71- - Open apps: \`xdg-open file\` (Linux) / \`open -a "TextEdit"\` (macOS)
72- ` ;
58+ function buildChatSystemPrompt ( platform : string ) : string {
59+ const shellNote = buildPlatformShellNote ( platform ) ;
60+ const isWindows = platform === "win32" ;
61+ const isMac = platform === "darwin" ;
62+
63+ // Pre-built per-OS example snippets — use template literals so single quotes need no escaping
64+ const exForEachOutput = isWindows
65+ ? `{ "type": "systemCommand", "command": "Write-Host \\"{{title}}\\"", "description": "Output title" }`
66+ : isMac
67+ ? `{ "type": "systemCommand", "command": "osascript -e 'display notification \\"{{title}}\\" with title \\"Loopi\\"'", "description": "Notify" }`
68+ : `{ "type": "systemCommand", "command": "notify-send \\"{{title}}\\"", "description": "Notify" }` ;
69+
70+ const exDesktopNotif = isWindows
71+ ? "Use PowerShell: `Write-Host '{{body}}'` or `Out-File` to write to a file. notify-send is not available on Windows."
72+ : isMac
73+ ? 'Use systemCommand with osascript: `osascript -e \'display notification "{{body}}" with title "Loopi"\'`'
74+ : 'Use systemCommand with notify-send: `notify-send "Title" "{{body}}"`. Double quotes around `{{var}}`, not single.' ;
75+
76+ const exNewsNotify = isWindows
77+ ? `{ "type": "systemCommand", "command": "Write-Host \\"{{newsTitle}} — {{newsUrl}}\\"", "description": "Output news" }`
78+ : isMac
79+ ? `{ "type": "systemCommand", "command": "osascript -e 'display notification \\"{{newsUrl}}\\" with title \\"{{newsTitle}}\\"'", "description": "Send desktop notification" }`
80+ : `{ "type": "systemCommand", "command": "notify-send -i info \\"Tech News\\" \\"{{newsTitle}} — {{newsUrl}}\\"", "description": "Send desktop notification" }` ;
81+
82+ const exRunCmd = isWindows
83+ ? `{ "action": "run-command", "command": "Start-Process notepad", "description": "Open Notepad" }`
84+ : isMac
85+ ? `{ "action": "run-command", "command": "open -a TextEdit", "description": "Open TextEdit" }`
86+ : `{ "action": "run-command", "command": "notify-send \\"Hello\\" \\"World\\"", "description": "Send notification" }` ;
87+
88+ const exMkdir = isWindows
89+ ? `{ "action": "run-command", "command": "New-Item -ItemType Directory -Force \\"$env:TEMP\\\\loopi-test\\"", "description": "Create test folder" }`
90+ : `{ "action": "run-command", "command": "mkdir -p ~/loopi-test", "description": "Create test folder" }` ;
91+
92+ const exWriteFile = isWindows
93+ ? `{ "action": "run-command", "command": "Set-Content \\"$env:TEMP\\\\loopi-test\\\\out.txt\\" \\"hello\\"", "description": "Write file" }`
94+ : `{ "action": "run-command", "command": "echo hello > ~/loopi-test/out.txt", "description": "Write file" }` ;
7395
7496 return `You are Loopi, a helpful AI assistant integrated into the Loopi automation platform.
7597Loopi is a visual browser & desktop automation tool with full desktop control (mouse, keyboard, CLI, browser).
@@ -116,7 +138,7 @@ Correct flat layout example:
116138 { "type": "forEach", "arrayVariable": "stories", "itemVariable": "story", "description": "Loop stories" },
117139 { "type": "jsonParse", "sourceVariable": "story", "path": "title", "storeKey": "title", "description": "Title" },
118140 { "type": "variableConditional", "variableConditionType": "variableExists", "variableName": "title", "description": "If title present" },
119- ${ isWindows ? '{ "type": "systemCommand", "command": "echo {{title}}", "description": "Output title" }' : '{ "type": "systemCommand", "command": "notify-send \\"{{title}}\\"", "description": "Notify" }' }
141+ ${ exForEachOutput }
120142]
121143\`\`\`
122144Do NOT wrap the body steps inside a \`steps: [...]\` property of the forEach/conditional node.
@@ -158,7 +180,7 @@ Variable substitutions ({{var}}) often contain characters that break shell quoti
1581801. **NEVER create external scripts** (Python, Bash, etc.) and call them via systemCommand. All logic MUST be built using Loopi's native step types.
1591812. **NEVER write files to \`~\`, \`~/.config/\`, \`/tmp\`, or anywhere else on the filesystem as part of workflow logic.** When an agent workflow needs to persist data between runs (dedup tracking, caches, state), use the per-agent folder exposed as \`{{agentDataDir}}\`. Loopi injects this variable at runtime — the folder is created per-agent and visible to the user in the agent detail UI.
1601823. **For uniqueness/dedup tracking inside agent workflows**: Use \`{{agentDataDir}}/<filename>\` — e.g. \`{{agentDataDir}}/seen-ids.txt\`. NEVER use \`~/.config/loopi\` or any hand-rolled path.
161- 4. **For desktop notifications**: ${ isWindows ? 'Use a systemCommand with `msg %username% "{{body}}"` or simply `echo {{body}}` to write to a file. On Windows notify-send is not available.' : 'Use systemCommand with notify-send directly — e.g. { "type": "systemCommand", "command": "notify-send \\"Title\\" \\"{{body}}\\"" }. Double quotes around `{{var}}`, not single.' }
183+ 4. **For desktop notifications**: ${ exDesktopNotif }
1621845. **Workflows MUST be self-contained** — every step uses Loopi's built-in step types. No external dependencies, no pip install, no script files.
163185
164186### Agent working directory — \`{{agentDataDir}}\`:
@@ -172,11 +194,7 @@ The user can open the agent detail view to inspect and edit these files. **Never
172194{ "type": "apiCall", "url": "https://hn.algolia.com/api/v1/search_by_date?tags=story&numericFilters=points>20", "method": "GET", "storeKey": "hnData", "description": "Fetch recent HN stories" },
173195{ "type": "jsonParse", "sourceVariable": "hnData", "path": "data.hits[0].title", "storeKey": "newsTitle", "description": "Extract first story title (data. prefix because apiCall wraps response)" },
174196{ "type": "jsonParse", "sourceVariable": "hnData", "path": "data.hits[0].url", "storeKey": "newsUrl", "description": "Extract first story URL" },
175- ${
176- isWindows
177- ? '{ "type": "systemCommand", "command": "echo {{newsTitle}} — {{newsUrl}}", "description": "Output news" }'
178- : '{ "type": "systemCommand", "command": "notify-send -i info \\"Tech News\\" \\"{{newsTitle}} — {{newsUrl}}\\"", "description": "Send desktop notification" }'
179- }
197+ ${ exNewsNotify }
180198\`\`\`
181199
182200### Example — WRONG approaches (DO NOT do any of these):
@@ -244,11 +262,7 @@ You can execute system commands and run workflows directly from chat. This is ho
244262${ shellNote }
245263### Run a shell command on the user's PC:
246264\`\`\`loopi-action
247- ${
248- isWindows
249- ? '{ "action": "run-command", "command": "start notepad.exe", "description": "Open Notepad" }'
250- : '{ "action": "run-command", "command": "notify-send \\"Hello\\" \\"World\\"", "description": "Send notification" }'
251- }
265+ ${ exRunCmd }
252266\`\`\`
253267
254268### Run a workflow by name:
258272
259273### Run multiple commands sequentially:
260274\`\`\`loopi-action
261- ${
262- isWindows
263- ? '{ "action": "run-command", "command": "start notepad.exe", "description": "Open Notepad" }'
264- : '{ "action": "run-command", "command": "mkdir -p ~/loopi-test", "description": "Create test folder" }'
265- }
275+ ${ exMkdir }
266276\`\`\`
267277\`\`\`loopi-action
268- ${
269- isWindows
270- ? '{ "action": "run-command", "command": "echo Hello > test.txt", "description": "Write file" }'
271- : '{ "action": "run-command", "command": "echo hello > ~/loopi-test/out.txt", "description": "Write file" }'
272- }
278+ ${ exWriteFile }
273279\`\`\`
274280
275281**CRITICAL: You have FULL access to the user's PC.** You can run commands, open apps, control the desktop — ANYTHING via run-command. When the user asks you to do something, DO IT using run-command blocks. NEVER say "I can't run commands" or ask the user to run commands themselves. You ARE the automation tool.
@@ -308,6 +314,7 @@ When the user asks to delete an agent or workflow, ALWAYS include the loopi-acti
308314
309315export function Chat ( ) {
310316 const [ messages , setMessages ] = useState < ChatMessage [ ] > ( [ ] ) ;
317+ const [ sessionId , setSessionId ] = useState ( ( ) => `app-chat-${ Date . now ( ) } ` ) ;
311318 const [ input , setInput ] = useState ( "" ) ;
312319 const [ isLoading , setIsLoading ] = useState ( false ) ;
313320 const [ isConnected , setIsConnected ] = useState ( false ) ;
@@ -516,6 +523,7 @@ export function Chat() {
516523
517524 const handleResetChat = ( ) => {
518525 setMessages ( [ ] ) ;
526+ setSessionId ( `app-chat-${ Date . now ( ) } ` ) ;
519527 window . electronAPI ?. chat ?. clear ( ) . catch ( ( ) => {
520528 /* ignore */
521529 } ) ;
@@ -541,7 +549,7 @@ export function Chat() {
541549 const apiMessages = [
542550 {
543551 role : "system" as const ,
544- content : buildChatSystemPrompt ( window . electronAPI ?. system . platform === "win32 ") ,
552+ content : buildChatSystemPrompt ( window . electronAPI ?. system . platform ?? "linux ") ,
545553 } ,
546554 ...messages . map ( ( m ) => ( { role : m . role , content : m . content } ) ) ,
547555 { role : "user" as const , content : userMessage . content } ,
@@ -554,6 +562,7 @@ export function Chat() {
554562 credentialId : config . credentialId ,
555563 model : config . model ,
556564 baseUrl : config . baseUrl ,
565+ ...( config . provider === "claude-code" ? { sessionId } : { } ) ,
557566 } ) ;
558567
559568 if ( result . success && result . response ) {
@@ -705,6 +714,7 @@ export function Chat() {
705714 if ( savedId ) {
706715 createdWorkflowIds [ wfConfig . name ] = automation . id ;
707716 toast . success ( `Workflow "${ wfConfig . name } " created! View it in the Dashboard.` ) ;
717+ window . dispatchEvent ( new CustomEvent ( "loopi:workflowSaved" ) ) ;
708718 }
709719 }
710720 } catch ( parseErr ) {
@@ -792,6 +802,7 @@ export function Chat() {
792802 } ) ;
793803 if ( agent ) {
794804 toast . success ( `Agent "${ agent . name } " created! View it in the Agents tab.` ) ;
805+ window . dispatchEvent ( new CustomEvent ( "loopi:agentCreated" ) ) ;
795806 }
796807 }
797808 } catch ( parseErr ) {
0 commit comments