diff --git a/jest.config.js b/jest.config.js index 3502aa5d7..74eda6695 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,7 +19,7 @@ module.exports = { { tsconfig: { target: "ES2020", - lib: ["ES2020", "DOM"], + lib: ["ES2020", "DOM", "ES2022.Intl"], esModuleInterop: true, allowSyntheticDefaultImports: true, }, diff --git a/src/components/tools/StatusSetToolCall.tsx b/src/components/tools/StatusSetToolCall.tsx index 803362a58..fc9942d46 100644 --- a/src/components/tools/StatusSetToolCall.tsx +++ b/src/components/tools/StatusSetToolCall.tsx @@ -31,7 +31,7 @@ export const StatusSetToolCall: React.FC = ({ status_set {args.message} - {errorMessage && ({errorMessage})} + {errorMessage && ({errorMessage})} {statusDisplay} diff --git a/src/services/tools/status_set.test.ts b/src/services/tools/status_set.test.ts index 5625c7b13..36e6d7daf 100644 --- a/src/services/tools/status_set.test.ts +++ b/src/services/tools/status_set.test.ts @@ -31,6 +31,35 @@ describe("status_set tool validation", () => { } }); + it("should accept emojis with variation selectors", async () => { + const tool = createStatusSetTool(mockConfig); + + // Emojis with variation selectors (U+FE0F) + const emojis = ["✏️", "✅", "➡️", "☀️"]; + for (const emoji of emojis) { + const result = (await tool.execute!({ emoji, message: "Test" }, mockToolCallOptions)) as { + success: boolean; + emoji: string; + message: string; + }; + expect(result).toEqual({ success: true, emoji, message: "Test" }); + } + }); + + it("should accept emojis with skin tone modifiers", async () => { + const tool = createStatusSetTool(mockConfig); + + const emojis = ["👋🏻", "👋🏽", "👋🏿"]; + for (const emoji of emojis) { + const result = (await tool.execute!({ emoji, message: "Test" }, mockToolCallOptions)) as { + success: boolean; + emoji: string; + message: string; + }; + expect(result).toEqual({ success: true, emoji, message: "Test" }); + } + }); + it("should reject multiple emojis", async () => { const tool = createStatusSetTool(mockConfig); diff --git a/src/services/tools/status_set.ts b/src/services/tools/status_set.ts index ee73d90d9..14a499402 100644 --- a/src/services/tools/status_set.ts +++ b/src/services/tools/status_set.ts @@ -18,23 +18,35 @@ export type StatusSetToolResult = /** * Validates that a string is a single emoji character - * Uses Unicode property escapes to match emoji characters + * Uses Intl.Segmenter to count grapheme clusters (handles variation selectors, skin tones, etc.) */ function isValidEmoji(str: string): boolean { - // Check if string contains exactly one character (handles multi-byte emojis) - const chars = [...str]; - if (chars.length !== 1) { + if (!str) return false; + + // Use Intl.Segmenter to count grapheme clusters (what users perceive as single characters) + // This properly handles emojis with variation selectors (like ✏️), skin tones, flags, etc. + const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" }); + const segments = [...segmenter.segment(str)]; + + // Must be exactly one grapheme cluster + if (segments.length !== 1) { return false; } // Check if it's an emoji using Unicode properties - const emojiRegex = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]$/u; - return emojiRegex.test(str); + const emojiRegex = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]/u; + return emojiRegex.test(segments[0].segment); } /** * Status set tool factory for AI assistant * Creates a tool that allows the AI to set status indicator showing current activity + * + * The status is displayed IMMEDIATELY when this tool is called, even before other + * tool calls complete. This prevents agents from prematurely declaring success + * (e.g., "PR checks passed") when operations are still pending. Agents should only + * set success status after confirming the outcome of long-running operations. + * * @param config Required configuration (not used for this tool, but required by interface) */ export const createStatusSetTool: ToolFactory = () => { diff --git a/src/utils/tools/toolDefinitions.ts b/src/utils/tools/toolDefinitions.ts index 24304c2fa..58fb11f81 100644 --- a/src/utils/tools/toolDefinitions.ts +++ b/src/utils/tools/toolDefinitions.ts @@ -185,13 +185,10 @@ export const TOOL_DEFINITIONS = { description: "Set a status indicator to show what the agent is currently doing. " + "The emoji appears left of the streaming indicator, and the message shows on hover. " + + "The status is set IMMEDIATELY when this tool is called, even before other tool calls complete. " + "IMPORTANT: Always set a status at the start of each response and update it as your work progresses. " + - "Set a final status before finishing your response that reflects the outcome: " + - "'✅ PR checks pass and ready to merge' (success), " + - "'❌ CreateWorkspace Tests failed' (failure), " + - "'⚠️ Encountered serious issue with design' (warning/blocker). " + - "The status is cleared at the start of each new response, so you must set it again. " + - "Use this to communicate ongoing activities (e.g., '🔍 Analyzing code', '📝 Writing tests', '🔧 Refactoring logic').", + "The status is cleared when a new user message comes in, so you must set it again for each response. " + + "Use this to communicate ongoing activities and set a final status before completing that reflects the outcome.", schema: z .object({ emoji: z.string().describe("A single emoji character representing the current activity"), diff --git a/tsconfig.json b/tsconfig.json index b887b01f6..33d44c08e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "lib": ["ES2023", "DOM"], + "lib": ["ES2023", "DOM", "ES2022.Intl"], "module": "ESNext", "moduleResolution": "node", "jsx": "react-jsx",