diff --git a/agents/cursor.ts b/agents/cursor.ts index 687070b..b5a5bb3 100644 --- a/agents/cursor.ts +++ b/agents/cursor.ts @@ -83,58 +83,6 @@ type CursorEvent = | CursorToolCallEvent | CursorResultEvent; -const messageHandlers = { - system: (_event: CursorSystemEvent) => { - // system init events - no logging needed - }, - user: (_event: CursorUserEvent) => { - // user messages already logged in prompt box - }, - thinking: (_event: CursorThinkingEvent) => { - // thinking events are internal - no logging needed - }, - assistant: (event: CursorAssistantEvent) => { - // only log finalized messages (ones with model_call_id) - // cursor emits each message twice: once without model_call_id, then again with it - if (event.model_call_id) { - const text = event.message?.content?.[0]?.text; - if (text?.trim()) { - log.box(text.trim(), { title: "Cursor" }); - } - } - }, - tool_call: (event: CursorToolCallEvent) => { - if (event.subtype === "started") { - // handle both MCP tools and built-in tools (bash, WebFetch, etc) - const mcpToolCall = event.tool_call?.mcpToolCall; - const builtinToolCall = (event.tool_call as any)?.builtinToolCall; - - if (mcpToolCall?.args?.toolName && mcpToolCall?.args?.args) { - log.toolCall({ - toolName: mcpToolCall.args.toolName, - input: mcpToolCall.args.args, - }); - } else if (builtinToolCall?.args?.name && builtinToolCall?.args?.args) { - log.toolCall({ - toolName: builtinToolCall.args.name, - input: builtinToolCall.args.args, - }); - } - } else if (event.subtype === "completed") { - const isError = event.tool_call?.mcpToolCall?.result?.success?.isError; - if (isError) { - log.warning("Tool call failed"); - } - } - }, - result: async (event: CursorResultEvent) => { - if (event.subtype === "success" && event.duration_ms) { - const durationSec = (event.duration_ms / 1000).toFixed(1); - log.debug(`Cursor completed in ${durationSec}s`); - } - }, -}; - export const cursor = agent({ name: "cursor", install: async () => { @@ -146,6 +94,60 @@ export const cursor = agent({ run: async ({ payload, apiKey, cliPath, mcpServers }) => { configureCursorMcpServers({ mcpServers, cliPath }); + // track logged messages to avoid duplicates + const loggedAssistantMessages = new Set(); + + // moved into `run` because it is stateful + // it tracks logged assistant messages to avoid duplicates + const messageHandlers = { + system: (_event: CursorSystemEvent) => { + // system init events - no logging needed + }, + user: (_event: CursorUserEvent) => { + // user messages already logged in prompt box + }, + thinking: (_event: CursorThinkingEvent) => { + // thinking events are internal - no logging needed + }, + assistant: (event: CursorAssistantEvent) => { + const text = event.message?.content?.[0]?.text?.trim(); + if (text && !loggedAssistantMessages.has(text)) { + loggedAssistantMessages.add(text); + log.box(text, { title: "Cursor" }); + } + }, + tool_call: (event: CursorToolCallEvent) => { + if (event.subtype === "started") { + // handle both MCP tools and built-in tools (bash, WebFetch, etc) + const mcpToolCall = event.tool_call?.mcpToolCall; + const builtinToolCall = (event.tool_call as any)?.builtinToolCall; + + if (mcpToolCall?.args?.toolName && mcpToolCall?.args?.args) { + log.toolCall({ + toolName: mcpToolCall.args.toolName, + input: mcpToolCall.args.args, + }); + } else if (builtinToolCall?.args?.name && builtinToolCall?.args?.args) { + log.toolCall({ + toolName: builtinToolCall.args.name, + input: builtinToolCall.args.args, + }); + } + } else if (event.subtype === "completed") { + const isError = event.tool_call?.mcpToolCall?.result?.success?.isError; + if (isError) { + log.warning("Tool call failed"); + } + } + }, + result: async (event: CursorResultEvent) => { + if (event.subtype === "success" && event.duration_ms) { + const durationSec = (event.duration_ms / 1000).toFixed(1); + log.debug(`Cursor completed in ${durationSec}s`); + } + }, + }; + try { const fullPrompt = addInstructions(payload); @@ -188,6 +190,11 @@ export const cursor = agent({ try { const event = JSON.parse(text) as CursorEvent; + // skip debug logging for empty thinking deltas + if (event.type === "thinking" && event.subtype === "delta" && !event.text) { + return; + } + // route to appropriate handler const handler = messageHandlers[event.type as keyof typeof messageHandlers]; if (handler) { diff --git a/agents/instructions.ts b/agents/instructions.ts index 847ffba..d98bc54 100644 --- a/agents/instructions.ts +++ b/agents/instructions.ts @@ -70,7 +70,7 @@ Before starting any work, you must first determine which mode to use by examinin Available modes: -${[...modes, ...payload.modes].map((w) => ` - "${w.name}": ${w.description}`).join("\n")} +${(payload.modes.length > 0 ? payload.modes : modes).map((w) => ` - "${w.name}": ${w.description}`).join("\n")} **IMPORTANT**: The first thing you must do is: 1. Examine the user's request/prompt carefully diff --git a/external.ts b/external.ts index 9eeee81..a8a654d 100644 --- a/external.ts +++ b/external.ts @@ -135,6 +135,10 @@ export type PayloadEvent = | { trigger: "workflow_dispatch"; [key: string]: any; + } + | { + trigger: "unknown"; + [key: string]: any; }; // payload type for agent execution diff --git a/fixtures/basic.txt b/fixtures/basic.txt index bc823bf..874c2a1 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -summarize https://github.com/pullfrogai/scratch/issues/56, its status, and pertinent discussion on the issue \ No newline at end of file +Tell me a joke \ No newline at end of file diff --git a/main.ts b/main.ts index 420a30e..19626e2 100644 --- a/main.ts +++ b/main.ts @@ -263,7 +263,7 @@ function parsePayload(inputs: Inputs): Payload { agent: null, prompt: inputs.prompt, event: { - trigger: "workflow_dispatch", + trigger: "unknown", }, modes, };