mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
fix(mobile): keep the terminal glyph on a classified Codex shell row
Mobile's run header picks between a terminal and a generic glyph by tool name. Now that the host publishes `read`/`search`/`list` for the same commands it used to publish as `shell`, that name check answers false and a command that really ran heads its run with a wrench. Ask the shared category vocabulary instead. Mobile keeps its two icons — porting the full glyph set is a separate lane.
This commit is contained in:
@@ -14,9 +14,9 @@ import {
|
||||
describeActiveToolCall,
|
||||
formatActiveToolLabel,
|
||||
formatToolCallCount,
|
||||
isCommandToolName,
|
||||
selectActiveToolCall
|
||||
} from '../../../src/shared/native-chat-tool-activity'
|
||||
import { isShellActivityToolRow } from '../../../src/shared/native-chat-tool-icon'
|
||||
import type { NativeChatBlock } from '../../../src/shared/native-chat-types'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { styles } from './mobile-native-chat-message-styles'
|
||||
@@ -195,7 +195,11 @@ export function ToolRun({
|
||||
}
|
||||
callCount ||= pairs.length
|
||||
const summary = summarizeToolRun(blocks)
|
||||
const ActiveToolIcon = activeCall && isCommandToolName(activeCall.name) ? SquareTerminal : Wrench
|
||||
// Read through the shared category vocabulary, not the tool name: Codex names
|
||||
// a classified shell row `read`/`search`/`list`, and a wrench on one of those
|
||||
// would call a command that ran a generic tool.
|
||||
const ActiveToolIcon =
|
||||
activeCall && isShellActivityToolRow(activeCall.name) ? SquareTerminal : Wrench
|
||||
return (
|
||||
<View style={styles.toolRun}>
|
||||
<View style={styles.toolRunHeader}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isShellActivityToolRow,
|
||||
NATIVE_CHAT_TOOL_ICON_NAMES,
|
||||
nativeChatToolCategory,
|
||||
nativeChatToolIconName,
|
||||
@@ -108,6 +109,44 @@ describe('native chat tool icons', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('reads a classified shell row as terminal activity, not as a named tool', () => {
|
||||
// A lane with only a terminal and a generic glyph (mobile) asks this instead
|
||||
// of `isCommandToolName`, which answers false for the words Codex publishes
|
||||
// for a command that really ran.
|
||||
for (const word of ['read', 'search', 'list', 'shell', 'bash', 'run_terminal_cmd']) {
|
||||
expect(isShellActivityToolRow(word)).toBe(true)
|
||||
}
|
||||
// Claude's own filesystem tools share those categories, so they read as
|
||||
// terminal activity on that lane too — it has no closer glyph for them.
|
||||
for (const word of ['Read', 'Grep', 'Glob']) {
|
||||
expect(isShellActivityToolRow(word)).toBe(true)
|
||||
}
|
||||
for (const word of ['Edit', 'Diff', 'Task', 'WebFetch', 'TodoWrite', 'AskUserQuestion', '']) {
|
||||
expect(isShellActivityToolRow(word)).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('answers terminal activity for exactly the shell categories, and no others', () => {
|
||||
// A `Record` of the whole vocabulary, so a category added later cannot join
|
||||
// or leave the terminal set without this listing changing.
|
||||
const rowWordByCategory: Record<NativeChatToolCategory, string> = {
|
||||
read: 'read',
|
||||
search: 'search',
|
||||
listFiles: 'list',
|
||||
unknown: 'shell',
|
||||
fileChange: 'Edit',
|
||||
webSearch: 'WebFetch',
|
||||
mcpToolCall: 'mcp__linear__create_issue',
|
||||
subAgentActivity: 'Task',
|
||||
todoList: 'TodoWrite',
|
||||
other: 'AskUserQuestion'
|
||||
}
|
||||
|
||||
expect(
|
||||
ALL_CATEGORIES.filter((category) => isShellActivityToolRow(rowWordByCategory[category]))
|
||||
).toEqual(['read', 'search', 'listFiles', 'unknown'])
|
||||
})
|
||||
|
||||
it('does not answer a prototype key with a glyph', () => {
|
||||
expect(nativeChatToolCategory('__proto__')).toBeNull()
|
||||
expect(nativeChatToolCategory('constructor')).toBeNull()
|
||||
|
||||
@@ -108,3 +108,22 @@ export function nativeChatToolCategory(rowWord: string): NativeChatToolCategory
|
||||
export function nativeChatToolIconName(rowWord: string): NativeChatToolIconName {
|
||||
return NATIVE_CHAT_TOOL_ICON_NAMES[nativeChatToolCategory(rowWord) ?? 'other']
|
||||
}
|
||||
|
||||
/** The categories a shell command produces: Codex's three classified classes,
|
||||
* plus an unclassified one. Kept here rather than in a lane so the classified
|
||||
* words and `shell` stay one answer. */
|
||||
const SHELL_ACTIVITY_CATEGORIES: ReadonlySet<NativeChatToolCategory> = new Set([
|
||||
'read',
|
||||
'search',
|
||||
'listFiles',
|
||||
'unknown'
|
||||
])
|
||||
|
||||
/** Whether a row reads as terminal activity. For a lane with no per-category
|
||||
* glyph (mobile), which only chooses between a terminal and a generic tool:
|
||||
* keying that on the tool name alone would call Codex's classified `read` /
|
||||
* `search` / `list` rows generic tools, though a shell command produced them. */
|
||||
export function isShellActivityToolRow(rowWord: string): boolean {
|
||||
const category = nativeChatToolCategory(rowWord)
|
||||
return category !== null && SHELL_ACTIVITY_CATEGORIES.has(category)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user