Skip to content

Commit ae86877

Browse files
authored
fix(windows): use PowerShell shell, fix dashboard refresh, split OS prompts (#134)
1 parent 3df480a commit ae86877

10 files changed

Lines changed: 486 additions & 171 deletions

File tree

src/app.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ export default function App() {
7979
}
8080
}, []);
8181

82-
// Refresh when Telegram bot creates a workflow or agent
82+
// Refresh dashboard when Chat or Telegram bot creates a workflow
8383
useEffect(() => {
84+
const onWorkflow = () => loadSavedTrees();
85+
window.addEventListener("loopi:workflowSaved", onWorkflow);
8486
window.electronAPI?.telegram.onWorkflowSaved(() => {
8587
loadSavedTrees();
8688
toast.success("New workflow created via Telegram");
8789
});
88-
window.electronAPI?.telegram.onAgentCreated(() => {
89-
toast.success("New agent created via Telegram");
90-
});
90+
return () => window.removeEventListener("loopi:workflowSaved", onWorkflow);
9191
}, []);
9292

9393
useEffect(() => {

src/components/AgentsPanel.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ export function AgentsPanel({ onOpenWorkflow }: AgentsPanelProps) {
3434
loadAgents();
3535
}, [loadAgents]);
3636

37+
// Refresh when Chat or Telegram bot creates an agent
38+
useEffect(() => {
39+
window.addEventListener("loopi:agentCreated", loadAgents);
40+
window.electronAPI?.telegram.onAgentCreated(() => {
41+
loadAgents();
42+
toast.success("New agent created via Telegram");
43+
});
44+
return () => window.removeEventListener("loopi:agentCreated", loadAgents);
45+
}, [loadAgents]);
46+
3747
const handleCreate = async (
3848
config: Parameters<NonNullable<typeof window.electronAPI>["agents"]["create"]>[0]
3949
) => {

src/components/Chat.tsx

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
SelectTrigger,
1212
SelectValue,
1313
} from "@components/ui/select";
14+
import { buildPlatformShellNote } from "@utils/platformPrompt";
1415
import {
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.
7597
Loopi 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
\`\`\`
122144
Do 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
158180
1. **NEVER create external scripts** (Python, Bash, etc.) and call them via systemCommand. All logic MUST be built using Loopi's native step types.
159181
2. **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.
160182
3. **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}
162184
5. **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:
@@ -258,18 +272,10 @@ ${
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

309315
export 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

Comments
 (0)