bingocode 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/package.json +1 -1
  2. package/desktop/README.md +0 -30
  3. package/desktop/bunfig.toml +0 -1
  4. package/desktop/index.html +0 -17
  5. package/desktop/package.json +0 -55
  6. package/desktop/pnpm-lock.yaml +0 -3832
  7. package/desktop/public/app-icon.jpg +0 -0
  8. package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
  9. package/desktop/public/fonts/inter-latin.woff2 +0 -0
  10. package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  11. package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
  12. package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
  13. package/desktop/public/fonts/manrope-latin.woff2 +0 -0
  14. package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
  15. package/desktop/public/icons/bilibili.svg +0 -1
  16. package/desktop/public/icons/douyin.svg +0 -1
  17. package/desktop/public/icons/github.svg +0 -3
  18. package/desktop/public/icons/xiaohongshu.svg +0 -1
  19. package/desktop/scripts/build-macos-arm64.sh +0 -270
  20. package/desktop/scripts/build-sidecars.ts +0 -183
  21. package/desktop/scripts/build-windows-x64.ps1 +0 -295
  22. package/desktop/scripts/scan-missing-imports.ts +0 -235
  23. package/desktop/sidecars/claude-sidecar.ts +0 -156
  24. package/desktop/src/App.tsx +0 -5
  25. package/desktop/src/__tests__/agentsSettings.test.tsx +0 -349
  26. package/desktop/src/__tests__/pages.test.tsx +0 -290
  27. package/desktop/src/__tests__/skillsSettings.test.tsx +0 -205
  28. package/desktop/src/api/adapters.ts +0 -12
  29. package/desktop/src/api/agents.ts +0 -36
  30. package/desktop/src/api/cliTasks.ts +0 -28
  31. package/desktop/src/api/client.ts +0 -63
  32. package/desktop/src/api/computerUse.ts +0 -76
  33. package/desktop/src/api/filesystem.ts +0 -30
  34. package/desktop/src/api/hahaOAuth.ts +0 -38
  35. package/desktop/src/api/models.ts +0 -28
  36. package/desktop/src/api/providers.ts +0 -63
  37. package/desktop/src/api/search.ts +0 -29
  38. package/desktop/src/api/sessions.ts +0 -56
  39. package/desktop/src/api/settings.ts +0 -20
  40. package/desktop/src/api/skills.ts +0 -19
  41. package/desktop/src/api/tasks.ts +0 -36
  42. package/desktop/src/api/teams.ts +0 -44
  43. package/desktop/src/api/websocket.ts +0 -164
  44. package/desktop/src/components/chat/AskUserQuestion.tsx +0 -268
  45. package/desktop/src/components/chat/AssistantMessage.tsx +0 -29
  46. package/desktop/src/components/chat/AttachmentGallery.tsx +0 -113
  47. package/desktop/src/components/chat/ChatInput.tsx +0 -622
  48. package/desktop/src/components/chat/CodeViewer.tsx +0 -161
  49. package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +0 -174
  50. package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +0 -311
  51. package/desktop/src/components/chat/DiffViewer.tsx +0 -157
  52. package/desktop/src/components/chat/FileSearchMenu.tsx +0 -198
  53. package/desktop/src/components/chat/ImageGalleryModal.tsx +0 -91
  54. package/desktop/src/components/chat/InlineImageGallery.tsx +0 -106
  55. package/desktop/src/components/chat/InlineTaskSummary.tsx +0 -60
  56. package/desktop/src/components/chat/MermaidRenderer.test.tsx +0 -98
  57. package/desktop/src/components/chat/MermaidRenderer.tsx +0 -361
  58. package/desktop/src/components/chat/MessageActionBar.tsx +0 -27
  59. package/desktop/src/components/chat/MessageList.test.tsx +0 -313
  60. package/desktop/src/components/chat/MessageList.tsx +0 -249
  61. package/desktop/src/components/chat/PermissionDialog.tsx +0 -262
  62. package/desktop/src/components/chat/SessionTaskBar.test.tsx +0 -99
  63. package/desktop/src/components/chat/SessionTaskBar.tsx +0 -159
  64. package/desktop/src/components/chat/StreamingIndicator.tsx +0 -41
  65. package/desktop/src/components/chat/TerminalChrome.tsx +0 -35
  66. package/desktop/src/components/chat/ThinkingBlock.tsx +0 -87
  67. package/desktop/src/components/chat/ToolCallBlock.tsx +0 -247
  68. package/desktop/src/components/chat/ToolCallGroup.tsx +0 -617
  69. package/desktop/src/components/chat/ToolResultBlock.tsx +0 -107
  70. package/desktop/src/components/chat/UserMessage.tsx +0 -38
  71. package/desktop/src/components/chat/chatBlocks.test.tsx +0 -136
  72. package/desktop/src/components/chat/clipboard.ts +0 -25
  73. package/desktop/src/components/chat/composerUtils.test.ts +0 -55
  74. package/desktop/src/components/chat/composerUtils.ts +0 -149
  75. package/desktop/src/components/controls/ModelSelector.tsx +0 -156
  76. package/desktop/src/components/controls/PermissionModeSelector.tsx +0 -229
  77. package/desktop/src/components/layout/AppShell.tsx +0 -107
  78. package/desktop/src/components/layout/ContentRouter.tsx +0 -27
  79. package/desktop/src/components/layout/ProjectFilter.tsx +0 -126
  80. package/desktop/src/components/layout/Sidebar.test.tsx +0 -158
  81. package/desktop/src/components/layout/Sidebar.tsx +0 -384
  82. package/desktop/src/components/layout/StatusBar.tsx +0 -31
  83. package/desktop/src/components/layout/TabBar.test.tsx +0 -136
  84. package/desktop/src/components/layout/TabBar.tsx +0 -318
  85. package/desktop/src/components/layout/TitleBar.tsx +0 -96
  86. package/desktop/src/components/layout/WindowControls.test.tsx +0 -69
  87. package/desktop/src/components/layout/WindowControls.tsx +0 -89
  88. package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +0 -100
  89. package/desktop/src/components/markdown/MarkdownRenderer.tsx +0 -229
  90. package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +0 -107
  91. package/desktop/src/components/shared/Button.tsx +0 -63
  92. package/desktop/src/components/shared/CopyButton.tsx +0 -58
  93. package/desktop/src/components/shared/DirectoryPicker.tsx +0 -316
  94. package/desktop/src/components/shared/Dropdown.tsx +0 -91
  95. package/desktop/src/components/shared/Input.tsx +0 -38
  96. package/desktop/src/components/shared/Modal.tsx +0 -65
  97. package/desktop/src/components/shared/ProjectContextChip.tsx +0 -30
  98. package/desktop/src/components/shared/Spinner.tsx +0 -30
  99. package/desktop/src/components/shared/Textarea.tsx +0 -38
  100. package/desktop/src/components/shared/Toast.tsx +0 -47
  101. package/desktop/src/components/shared/UpdateChecker.tsx +0 -90
  102. package/desktop/src/components/skills/SkillDetail.test.tsx +0 -89
  103. package/desktop/src/components/skills/SkillDetail.tsx +0 -403
  104. package/desktop/src/components/skills/SkillList.tsx +0 -254
  105. package/desktop/src/components/tasks/DayOfWeekPicker.tsx +0 -57
  106. package/desktop/src/components/tasks/NewTaskModal.tsx +0 -407
  107. package/desktop/src/components/tasks/PromptEditor.tsx +0 -74
  108. package/desktop/src/components/tasks/TaskEmptyState.tsx +0 -30
  109. package/desktop/src/components/tasks/TaskList.tsx +0 -46
  110. package/desktop/src/components/tasks/TaskRow.tsx +0 -253
  111. package/desktop/src/components/tasks/TaskRunsPanel.tsx +0 -195
  112. package/desktop/src/components/teams/TeamStatusBar.tsx +0 -147
  113. package/desktop/src/config/providerPresets.ts +0 -78
  114. package/desktop/src/config/spinnerVerbs.ts +0 -193
  115. package/desktop/src/hooks/useKeyboardShortcuts.ts +0 -60
  116. package/desktop/src/i18n/index.ts +0 -54
  117. package/desktop/src/i18n/locales/en.ts +0 -670
  118. package/desktop/src/i18n/locales/zh.ts +0 -670
  119. package/desktop/src/lib/__tests__/cronDescribe.test.ts +0 -93
  120. package/desktop/src/lib/cronDescribe.ts +0 -188
  121. package/desktop/src/lib/desktopRuntime.ts +0 -54
  122. package/desktop/src/lib/parseRunOutput.ts +0 -79
  123. package/desktop/src/main.tsx +0 -13
  124. package/desktop/src/mocks/data.ts +0 -202
  125. package/desktop/src/pages/ActiveSession.test.tsx +0 -181
  126. package/desktop/src/pages/ActiveSession.tsx +0 -219
  127. package/desktop/src/pages/AdapterSettings.tsx +0 -375
  128. package/desktop/src/pages/AgentTeams.tsx +0 -200
  129. package/desktop/src/pages/ComputerUseSettings.tsx +0 -420
  130. package/desktop/src/pages/EmptySession.tsx +0 -518
  131. package/desktop/src/pages/NewTaskModal.tsx +0 -346
  132. package/desktop/src/pages/ScheduledTasks.tsx +0 -66
  133. package/desktop/src/pages/ScheduledTasksEmpty.tsx +0 -152
  134. package/desktop/src/pages/ScheduledTasksList.tsx +0 -416
  135. package/desktop/src/pages/SessionControls.tsx +0 -460
  136. package/desktop/src/pages/Settings.tsx +0 -1448
  137. package/desktop/src/pages/ToolInspection.tsx +0 -235
  138. package/desktop/src/stores/adapterStore.ts +0 -106
  139. package/desktop/src/stores/agentStore.ts +0 -34
  140. package/desktop/src/stores/chatStore.test.ts +0 -505
  141. package/desktop/src/stores/chatStore.ts +0 -850
  142. package/desktop/src/stores/cliTaskStore.ts +0 -152
  143. package/desktop/src/stores/hahaOAuthStore.test.ts +0 -77
  144. package/desktop/src/stores/hahaOAuthStore.ts +0 -97
  145. package/desktop/src/stores/providerStore.ts +0 -101
  146. package/desktop/src/stores/sessionStore.test.ts +0 -63
  147. package/desktop/src/stores/sessionStore.ts +0 -102
  148. package/desktop/src/stores/settingsStore.ts +0 -120
  149. package/desktop/src/stores/skillStore.ts +0 -51
  150. package/desktop/src/stores/tabStore.ts +0 -169
  151. package/desktop/src/stores/taskStore.ts +0 -68
  152. package/desktop/src/stores/teamStore.ts +0 -344
  153. package/desktop/src/stores/uiStore.ts +0 -100
  154. package/desktop/src/stores/updateStore.test.ts +0 -71
  155. package/desktop/src/stores/updateStore.ts +0 -221
  156. package/desktop/src/theme/globals.css +0 -465
  157. package/desktop/src/types/adapter.ts +0 -33
  158. package/desktop/src/types/chat.ts +0 -152
  159. package/desktop/src/types/cliTask.ts +0 -24
  160. package/desktop/src/types/provider.ts +0 -62
  161. package/desktop/src/types/session.ts +0 -27
  162. package/desktop/src/types/settings.ts +0 -22
  163. package/desktop/src/types/skill.ts +0 -38
  164. package/desktop/src/types/task.ts +0 -56
  165. package/desktop/src/types/team.ts +0 -38
  166. package/desktop/src-tauri/Cargo.lock +0 -5549
  167. package/desktop/src-tauri/Cargo.toml +0 -20
  168. package/desktop/src-tauri/app-icon.svg +0 -13
  169. package/desktop/src-tauri/build.rs +0 -3
  170. package/desktop/src-tauri/capabilities/default.json +0 -106
  171. package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +0 -5
  172. package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +0 -4
  173. package/desktop/src-tauri/icons/icon.icns +0 -0
  174. package/desktop/src-tauri/icons/icon.ico +0 -0
  175. package/desktop/src-tauri/src/lib.rs +0 -408
  176. package/desktop/src-tauri/src/main.rs +0 -6
  177. package/desktop/src-tauri/tauri.conf.json +0 -78
  178. package/desktop/src-tauri/tauri.macos.conf.json +0 -18
  179. package/desktop/src-tauri/tauri.release-ci.json +0 -5
  180. package/desktop/src-tauri/tauri.windows.conf.json +0 -16
  181. package/desktop/src-tauri/windows-installer-hooks.nsh +0 -17
  182. package/desktop/tsconfig.json +0 -25
  183. package/desktop/vite.config.ts +0 -26
  184. package/desktop/vitest.config.ts +0 -18
@@ -1,617 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { ToolCallBlock } from './ToolCallBlock'
3
- import { MarkdownRenderer } from '../markdown/MarkdownRenderer'
4
- import { Modal } from '../shared/Modal'
5
- import { useTranslation } from '../../i18n'
6
- import type { TranslationKey } from '../../i18n'
7
- import type { AgentTaskNotification, UIMessage } from '../../types/chat'
8
- import { AGENT_LIFECYCLE_TYPES } from '../../types/team'
9
-
10
- type ToolCall = Extract<UIMessage, { type: 'tool_use' }>
11
- type ToolResult = Extract<UIMessage, { type: 'tool_result' }>
12
-
13
- type Props = {
14
- toolCalls: ToolCall[]
15
- resultMap: Map<string, ToolResult>
16
- childToolCallsByParent: Map<string, ToolCall[]>
17
- agentTaskNotifications: Record<string, AgentTaskNotification>
18
- /** When true, the last tool is still executing — show expanded */
19
- isStreaming?: boolean
20
- }
21
-
22
- const TOOL_VERBS: Record<string, (count: number, t: (key: TranslationKey, params?: Record<string, string | number>) => string) => string> = {
23
- Read: (n, t) => n === 1 ? t('toolGroup.readOne') : t('toolGroup.readMany', { count: n }),
24
- Write: (n, t) => n === 1 ? t('toolGroup.createdOne') : t('toolGroup.createdMany', { count: n }),
25
- Edit: (n, t) => n === 1 ? t('toolGroup.editedOne') : t('toolGroup.editedMany', { count: n }),
26
- Bash: (n, t) => n === 1 ? t('toolGroup.ranOne') : t('toolGroup.ranMany', { count: n }),
27
- Glob: (_n, t) => t('toolGroup.foundFiles'),
28
- Grep: (n, t) => n === 1 ? t('toolGroup.searchedOne') : t('toolGroup.searchedMany', { count: n }),
29
- Agent: (n, t) => n === 1 ? t('toolGroup.agentOne') : t('toolGroup.agentMany', { count: n }),
30
- WebSearch: (_n, t) => t('toolGroup.searchedWeb'),
31
- WebFetch: (n, t) => n === 1 ? t('toolGroup.fetchedOne') : t('toolGroup.fetchedMany', { count: n }),
32
- }
33
-
34
- function generateSummary(toolCalls: ToolCall[], t: (key: TranslationKey, params?: Record<string, string | number>) => string): string {
35
- const counts = new Map<string, number>()
36
- for (const tc of toolCalls) {
37
- counts.set(tc.toolName, (counts.get(tc.toolName) ?? 0) + 1)
38
- }
39
-
40
- const parts: string[] = []
41
- for (const [name, count] of counts) {
42
- const verbFn = TOOL_VERBS[name]
43
- parts.push(verbFn ? verbFn(count, t) : `${name} (${count})`)
44
- }
45
-
46
- return parts.join(', ')
47
- }
48
-
49
- function groupHasErrors(toolCalls: ToolCall[], resultMap: Map<string, ToolResult>): boolean {
50
- return toolCalls.some((tc) => {
51
- const result = resultMap.get(tc.toolUseId)
52
- return result?.isError
53
- })
54
- }
55
-
56
- export function ToolCallGroup({
57
- toolCalls,
58
- resultMap,
59
- childToolCallsByParent,
60
- agentTaskNotifications,
61
- isStreaming,
62
- }: Props) {
63
- const allAgents = toolCalls.every((toolCall) => toolCall.toolName === 'Agent')
64
-
65
- if (allAgents) {
66
- return (
67
- <AgentToolGroup
68
- toolCalls={toolCalls}
69
- resultMap={resultMap}
70
- childToolCallsByParent={childToolCallsByParent}
71
- agentTaskNotifications={agentTaskNotifications}
72
- isStreaming={isStreaming}
73
- />
74
- )
75
- }
76
-
77
- // Single tool call — render directly without group wrapper
78
- if (toolCalls.length === 1) {
79
- const tc = toolCalls[0]!
80
- return (
81
- <ToolCallTree
82
- toolCall={tc}
83
- resultMap={resultMap}
84
- childToolCallsByParent={childToolCallsByParent}
85
- />
86
- )
87
- }
88
-
89
- return (
90
- <ToolCallGroupMulti
91
- toolCalls={toolCalls}
92
- resultMap={resultMap}
93
- childToolCallsByParent={childToolCallsByParent}
94
- agentTaskNotifications={agentTaskNotifications}
95
- isStreaming={isStreaming}
96
- />
97
- )
98
- }
99
-
100
- function AgentToolGroup({
101
- toolCalls,
102
- resultMap,
103
- childToolCallsByParent,
104
- agentTaskNotifications,
105
- isStreaming,
106
- }: Props) {
107
- const [expanded, setExpanded] = useState(true)
108
- const t = useTranslation()
109
- const statuses = toolCalls.map((toolCall) =>
110
- getAgentStatus({
111
- hasResult: resultMap.has(toolCall.toolUseId),
112
- isError: !!resultMap.get(toolCall.toolUseId)?.isError,
113
- isLaunchResult: isAgentLaunchResult(resultMap.get(toolCall.toolUseId)?.content),
114
- isStreaming: !!isStreaming && !resultMap.has(toolCall.toolUseId),
115
- childCount: (childToolCallsByParent.get(toolCall.toolUseId) ?? []).length,
116
- taskStatus: agentTaskNotifications[toolCall.toolUseId]?.status,
117
- }),
118
- )
119
- const isAnyRunning = statuses.some((status) => status === 'running' || status === 'starting')
120
- const errorPresent = statuses.some((status) => status === 'failed')
121
- const allComplete = statuses.every((status) => status === 'done')
122
- const anyStopped = statuses.some((status) => status === 'stopped')
123
-
124
- useEffect(() => {
125
- if (isStreaming) {
126
- setExpanded(true)
127
- }
128
- }, [isStreaming])
129
-
130
- return (
131
- <div className="mb-2 ml-10">
132
- <button
133
- type="button"
134
- onClick={() => setExpanded((value) => !value)}
135
- className="flex w-full items-center gap-2 rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-low)] px-3 py-1.5 text-left transition-colors hover:bg-[var(--color-surface-container-high)]"
136
- >
137
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)]">
138
- {expanded ? 'expand_less' : 'expand_more'}
139
- </span>
140
- <span className="flex-1 truncate text-[12px] text-[var(--color-text-secondary)]">
141
- {toolCalls.length === 1 ? t('toolGroup.agentOne') : t('toolGroup.agentMany', { count: toolCalls.length })}
142
- </span>
143
- {isAnyRunning && (
144
- <span className="rounded-full bg-[var(--color-warning)]/12 px-2 py-0.5 text-[10px] font-semibold text-[var(--color-warning)]">
145
- {t('agentStatus.running')}
146
- </span>
147
- )}
148
- {!isAnyRunning && errorPresent && (
149
- <span className="material-symbols-outlined text-[14px] text-[var(--color-error)]">error</span>
150
- )}
151
- {!isAnyRunning && !errorPresent && allComplete && (
152
- <span className="material-symbols-outlined text-[14px] text-[var(--color-success)]">check_circle</span>
153
- )}
154
- {!isAnyRunning && !errorPresent && !allComplete && !anyStopped && (
155
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)]">pending</span>
156
- )}
157
- {!isAnyRunning && !errorPresent && !allComplete && anyStopped && (
158
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)]">stop_circle</span>
159
- )}
160
- </button>
161
-
162
- {expanded && (
163
- <div className="relative mt-3 pl-5">
164
- <div className="absolute bottom-6 left-[11px] top-4 w-px rounded-full bg-[var(--color-border)]/45" />
165
- <div className="space-y-2">
166
- {toolCalls.map((toolCall) => (
167
- <div key={toolCall.id} className="relative pl-7">
168
- <div className="absolute left-0 top-1/2 -translate-y-1/2">
169
- <div className="absolute left-[11px] top-1/2 h-px w-4 -translate-y-1/2 bg-[var(--color-border)]/45" />
170
- <div className="absolute left-[8px] top-1/2 h-2.5 w-2.5 -translate-y-1/2 rounded-full border border-[var(--color-border)]/65 bg-[var(--color-surface-container-lowest)] shadow-[0_0_0_2px_var(--color-surface)]" />
171
- </div>
172
- <AgentCallCard
173
- toolCall={toolCall}
174
- resultMap={resultMap}
175
- childToolCallsByParent={childToolCallsByParent}
176
- agentTaskNotification={agentTaskNotifications[toolCall.toolUseId]}
177
- isStreaming={isStreaming && !resultMap.has(toolCall.toolUseId)}
178
- />
179
- </div>
180
- ))}
181
- </div>
182
- </div>
183
- )}
184
- </div>
185
- )
186
- }
187
-
188
- /** Separated so the useState hook is never called conditionally. */
189
- function ToolCallGroupMulti({ toolCalls, resultMap, childToolCallsByParent, isStreaming }: Props) {
190
- const [expanded, setExpanded] = useState(false)
191
- const t = useTranslation()
192
- const summary = generateSummary(toolCalls, t)
193
- const errorPresent = groupHasErrors(toolCalls, resultMap)
194
- const allComplete = toolCalls.every((tc) => resultMap.has(tc.toolUseId))
195
- const hasNestedToolCalls = toolCalls.some((tc) => (childToolCallsByParent.get(tc.toolUseId)?.length ?? 0) > 0)
196
-
197
- useEffect(() => {
198
- if (isStreaming || hasNestedToolCalls) {
199
- setExpanded(true)
200
- }
201
- }, [hasNestedToolCalls, isStreaming])
202
-
203
- return (
204
- <div className="mb-2 ml-10">
205
- <button
206
- type="button"
207
- onClick={() => setExpanded((v) => !v)}
208
- className="flex w-full items-center gap-2 rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-low)] px-3 py-1.5 text-left transition-colors hover:bg-[var(--color-surface-container-high)]"
209
- >
210
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)]">
211
- {expanded ? 'expand_less' : 'expand_more'}
212
- </span>
213
- <span className="flex-1 truncate text-[12px] text-[var(--color-text-secondary)]">
214
- {summary}
215
- </span>
216
- {!isStreaming && allComplete && !errorPresent && (
217
- <span className="material-symbols-outlined text-[14px] text-[var(--color-success)]">check_circle</span>
218
- )}
219
- {!isStreaming && errorPresent && (
220
- <span className="material-symbols-outlined text-[14px] text-[var(--color-error)]">error</span>
221
- )}
222
- {!isStreaming && !allComplete && !errorPresent && (
223
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)]">pending</span>
224
- )}
225
- {isStreaming && (
226
- <span className="h-1.5 w-1.5 rounded-full bg-[var(--color-brand)] animate-pulse-dot" />
227
- )}
228
- </button>
229
-
230
- {expanded && (
231
- <div className="mt-1.5 space-y-1">
232
- {toolCalls.map((tc) => {
233
- return (
234
- <ToolCallTree
235
- key={tc.id}
236
- toolCall={tc}
237
- resultMap={resultMap}
238
- childToolCallsByParent={childToolCallsByParent}
239
- compact
240
- />
241
- )
242
- })}
243
- </div>
244
- )}
245
- </div>
246
- )
247
- }
248
-
249
- function AgentCallCard({
250
- toolCall,
251
- resultMap,
252
- childToolCallsByParent,
253
- agentTaskNotification,
254
- isStreaming = false,
255
- }: {
256
- toolCall: ToolCall
257
- resultMap: Map<string, ToolResult>
258
- childToolCallsByParent: Map<string, ToolCall[]>
259
- agentTaskNotification?: AgentTaskNotification
260
- isStreaming?: boolean
261
- }) {
262
- const [expanded, setExpanded] = useState(false)
263
- const [previewOpen, setPreviewOpen] = useState(false)
264
- const t = useTranslation()
265
- const input = toolCall.input && typeof toolCall.input === 'object'
266
- ? toolCall.input as Record<string, unknown>
267
- : {}
268
- const result = resultMap.get(toolCall.toolUseId)
269
- const childToolCalls = childToolCallsByParent.get(toolCall.toolUseId) ?? []
270
- const isLaunchResult = isAgentLaunchResult(result?.content)
271
- const recentToolCalls = childToolCalls.slice(-2)
272
- const status = getAgentStatus({
273
- hasResult: !!result,
274
- isError: !!result?.isError,
275
- isLaunchResult,
276
- isStreaming,
277
- childCount: childToolCalls.length,
278
- taskStatus: agentTaskNotification?.status,
279
- })
280
- const statusClassName = getAgentStatusClassName(status)
281
- const statusLabel = getAgentStatusLabel(status, t)
282
- const taskSummary = agentTaskNotification?.summary?.trim() || ''
283
- const errorText =
284
- status === 'failed'
285
- ? taskSummary || (result?.isError ? getAgentErrorSummary(result.content) : '')
286
- : result?.isError
287
- ? getAgentErrorSummary(result.content)
288
- : ''
289
- const fullOutputText =
290
- result && !result.isError && !isLaunchResult && !isAgentLifecycleResult(result.content)
291
- ? extractTextContent(result.content).trim()
292
- : ''
293
- const previewText = fullOutputText || (status === 'done' || status === 'stopped' ? taskSummary : '')
294
- const outputSummary = previewText ? getAgentOutputSummary(previewText) : ''
295
- const description = typeof input.description === 'string' ? input.description : ''
296
-
297
- return (
298
- <div className="overflow-hidden rounded-lg border border-[var(--color-border)]/50 bg-[var(--color-surface-container-lowest)]">
299
- <div className="flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-[var(--color-surface-hover)]/50">
300
- <span className="material-symbols-outlined text-[18px] text-[var(--color-outline)]">smart_toy</span>
301
- <div className="min-w-0 flex-1">
302
- <div className="flex items-center gap-2">
303
- <span className="text-[13px] font-semibold text-[var(--color-text-primary)]">Agent</span>
304
- {description && (
305
- <span className="truncate text-[12px] text-[var(--color-text-secondary)]">
306
- {description}
307
- </span>
308
- )}
309
- </div>
310
- {!expanded && outputSummary && (
311
- <div className="mt-1 line-clamp-2 text-[11px] text-[var(--color-text-tertiary)]">
312
- {outputSummary}
313
- </div>
314
- )}
315
- {!expanded && !outputSummary && recentToolCalls.length > 0 && (
316
- <div className="mt-1 space-y-1">
317
- {recentToolCalls.map((recentToolCall) => (
318
- <div
319
- key={recentToolCall.id}
320
- className="truncate text-[11px] text-[var(--color-text-tertiary)]"
321
- >
322
- {formatRecentToolUseSummary(recentToolCall, resultMap)}
323
- </div>
324
- ))}
325
- </div>
326
- )}
327
- {!expanded && !outputSummary && !recentToolCalls.length && errorText && (
328
- <div className="mt-1 truncate text-[11px] text-[var(--color-error)]">
329
- {errorText}
330
- </div>
331
- )}
332
- </div>
333
- {outputSummary && (
334
- <button
335
- type="button"
336
- onClick={(event) => {
337
- event.stopPropagation()
338
- setPreviewOpen(true)
339
- }}
340
- className="shrink-0 rounded-md border border-[var(--color-border)] px-2.5 py-1 text-[11px] font-medium text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]"
341
- >
342
- {t('agentStatus.viewResult')}
343
- </button>
344
- )}
345
- <span className={`rounded-full px-2 py-0.5 text-[10px] font-semibold ${statusClassName}`}>
346
- {statusLabel}
347
- </span>
348
- <button
349
- type="button"
350
- onClick={() => setExpanded((value) => !value)}
351
- className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[var(--color-outline)] transition-colors hover:bg-[var(--color-surface-hover)]"
352
- aria-label={expanded ? 'Collapse agent' : 'Expand agent'}
353
- >
354
- <span className="material-symbols-outlined text-[16px]">
355
- {expanded ? 'expand_less' : 'expand_more'}
356
- </span>
357
- </button>
358
- </div>
359
-
360
- {expanded && (
361
- <div className="border-t border-[var(--color-border)]/60 px-3 py-3">
362
- {errorText && (
363
- <div className="mb-3 rounded-lg border border-[var(--color-error)]/20 bg-[var(--color-error-container)]/60 px-3 py-2 text-[11px] text-[var(--color-error)]">
364
- {errorText}
365
- </div>
366
- )}
367
- {childToolCalls.length > 0 ? (
368
- <div className="space-y-1">
369
- {childToolCalls.map((childToolCall) => (
370
- <ToolCallTree
371
- key={childToolCall.id}
372
- toolCall={childToolCall}
373
- resultMap={resultMap}
374
- childToolCallsByParent={childToolCallsByParent}
375
- compact
376
- />
377
- ))}
378
- </div>
379
- ) : outputSummary ? (
380
- <div className="rounded-lg border border-[var(--color-border)]/60 bg-[var(--color-surface)] px-3 py-3">
381
- <div className="line-clamp-3 text-[11px] leading-[1.55] text-[var(--color-text-secondary)]">
382
- {outputSummary}
383
- </div>
384
- <div className="mt-3 flex justify-end">
385
- <span className="text-[10px] text-[var(--color-text-tertiary)]">
386
- {t('agentStatus.viewResult')}
387
- </span>
388
- </div>
389
- </div>
390
- ) : (
391
- <div className="text-[11px] text-[var(--color-text-tertiary)]">
392
- {status === 'starting' ? t('agentStatus.starting') : t('agentStatus.noActivity')}
393
- </div>
394
- )}
395
- </div>
396
- )}
397
- <Modal
398
- open={previewOpen}
399
- onClose={() => setPreviewOpen(false)}
400
- title={description || t('agentStatus.resultTitle')}
401
- width={900}
402
- >
403
- <div className="max-h-[70vh] overflow-y-auto">
404
- <MarkdownRenderer content={previewText || errorText} />
405
- </div>
406
- </Modal>
407
- </div>
408
- )
409
- }
410
-
411
- function ToolCallTree({
412
- toolCall,
413
- resultMap,
414
- childToolCallsByParent,
415
- compact = false,
416
- }: {
417
- toolCall: ToolCall
418
- resultMap: Map<string, ToolResult>
419
- childToolCallsByParent: Map<string, ToolCall[]>
420
- compact?: boolean
421
- }) {
422
- const result = resultMap.get(toolCall.toolUseId)
423
- const childToolCalls = childToolCallsByParent.get(toolCall.toolUseId) ?? []
424
-
425
- return (
426
- <div className={compact ? 'space-y-1' : ''}>
427
- <ToolCallBlock
428
- toolName={toolCall.toolName}
429
- input={toolCall.input}
430
- result={result ? { content: result.content, isError: result.isError } : null}
431
- compact={compact}
432
- />
433
- {childToolCalls.length > 0 && (
434
- <div className={compact ? 'ml-4 border-l border-[var(--color-border)]/60 pl-3' : 'mb-2 ml-16 border-l border-[var(--color-border)]/60 pl-3'}>
435
- <div className="space-y-1">
436
- {childToolCalls.map((childToolCall) => (
437
- <ToolCallTree
438
- key={childToolCall.id}
439
- toolCall={childToolCall}
440
- resultMap={resultMap}
441
- childToolCallsByParent={childToolCallsByParent}
442
- compact
443
- />
444
- ))}
445
- </div>
446
- </div>
447
- )}
448
- </div>
449
- )
450
- }
451
-
452
- type AgentStatus = 'starting' | 'running' | 'done' | 'failed' | 'stopped'
453
- type AgentTaskStatus = AgentTaskNotification['status']
454
-
455
- function getAgentStatus({
456
- hasResult,
457
- isError,
458
- isLaunchResult,
459
- isStreaming,
460
- childCount,
461
- taskStatus,
462
- }: {
463
- hasResult: boolean
464
- isError: boolean
465
- isLaunchResult: boolean
466
- isStreaming: boolean
467
- childCount: number
468
- taskStatus?: AgentTaskStatus
469
- }): AgentStatus {
470
- if (taskStatus === 'failed') return 'failed'
471
- if (taskStatus === 'stopped') return 'stopped'
472
- if (taskStatus === 'completed') return 'done'
473
- if (hasResult && isError && !isLaunchResult) return 'failed'
474
- if (hasResult && !isLaunchResult) return 'done'
475
- if (isStreaming || childCount > 0 || isLaunchResult) return 'running'
476
- return 'starting'
477
- }
478
-
479
- function getAgentStatusLabel(
480
- status: AgentStatus,
481
- t: (key: TranslationKey, params?: Record<string, string | number>) => string,
482
- ): string {
483
- switch (status) {
484
- case 'failed':
485
- return t('agentStatus.failed')
486
- case 'stopped':
487
- return t('agentStatus.stopped')
488
- case 'done':
489
- return t('agentStatus.done')
490
- case 'running':
491
- return t('agentStatus.running')
492
- case 'starting':
493
- default:
494
- return t('agentStatus.starting')
495
- }
496
- }
497
-
498
- function getAgentStatusClassName(status: AgentStatus): string {
499
- switch (status) {
500
- case 'failed':
501
- return 'bg-[var(--color-error)]/10 text-[var(--color-error)]'
502
- case 'stopped':
503
- return 'bg-[var(--color-surface-container-high)] text-[var(--color-text-secondary)]'
504
- case 'done':
505
- return 'bg-[var(--color-success)]/10 text-[var(--color-success)]'
506
- case 'running':
507
- return 'bg-[var(--color-warning)]/10 text-[var(--color-warning)]'
508
- case 'starting':
509
- default:
510
- return 'bg-[var(--color-surface-container-high)] text-[var(--color-text-secondary)]'
511
- }
512
- }
513
-
514
- function formatRecentToolUseSummary(
515
- toolCall: ToolCall,
516
- resultMap: Map<string, ToolResult>,
517
- ): string {
518
- const input = toolCall.input && typeof toolCall.input === 'object'
519
- ? toolCall.input as Record<string, unknown>
520
- : {}
521
- const result = resultMap.get(toolCall.toolUseId)
522
- const suffix = result?.isError ? ' • failed' : result ? ' • done' : ' • running'
523
-
524
- switch (toolCall.toolName) {
525
- case 'Bash':
526
- return `Bash · ${typeof input.command === 'string' ? input.command : ''}${suffix}`
527
- case 'Read':
528
- return `Read · ${typeof input.file_path === 'string' ? input.file_path.split('/').pop() : 'file'}${suffix}`
529
- case 'Glob':
530
- return `Glob · ${typeof input.pattern === 'string' ? input.pattern : ''}${suffix}`
531
- case 'Grep':
532
- return `Grep · ${typeof input.pattern === 'string' ? input.pattern : ''}${suffix}`
533
- case 'Agent':
534
- return `Agent · ${typeof input.description === 'string' ? input.description : ''}${suffix}`
535
- default:
536
- return `${toolCall.toolName}${suffix}`
537
- }
538
- }
539
-
540
- function getAgentErrorSummary(content: unknown): string {
541
- const text = extractTextContent(content).replace(/\s+/g, ' ').trim()
542
- if (!text) return ''
543
- if (text.includes(`Agent type 'Explore' not found`)) {
544
- return 'Explore agent unavailable in this session'
545
- }
546
- return text.length > 120 ? `${text.slice(0, 120)}...` : text
547
- }
548
-
549
- function getAgentOutputSummary(content: string): string {
550
- const text = content.replace(/\s+\n/g, '\n').trim()
551
- if (!text) return ''
552
- return text.length > 220 ? `${text.slice(0, 220)}...` : text
553
- }
554
-
555
- function isAgentLaunchResult(content: unknown): boolean {
556
- const text = extractTextContent(content).trim()
557
- if (!text) return false
558
-
559
- return (
560
- text.startsWith('Async agent launched successfully.') ||
561
- text.startsWith('Remote agent launched in CCR.') ||
562
- (text.startsWith('Spawned successfully.') &&
563
- text.includes('The agent is now running and will receive instructions via mailbox.')) ||
564
- text.includes('The agent is working in the background. You will be notified automatically when it completes.') ||
565
- text.includes('The agent is running remotely. You will be notified automatically when it completes.')
566
- )
567
- }
568
-
569
- /**
570
- * Check if agent result content is a lifecycle notification (shutdown, terminated, etc.)
571
- * rather than actual agent output. These should not be shown to the user as results.
572
- */
573
- function isAgentLifecycleResult(content: unknown): boolean {
574
- const text = extractTextContent(content).trim()
575
- if (!text) return false
576
- // Detect JSON lifecycle messages: shutdown_approved, shutdown_rejected, teammate_terminated
577
- if (text.startsWith('{') && text.endsWith('}')) {
578
- try {
579
- const parsed = JSON.parse(text) as Record<string, unknown>
580
- if (typeof parsed.type === 'string' && AGENT_LIFECYCLE_TYPES.has(parsed.type)) {
581
- return true
582
- }
583
- } catch {
584
- // Not valid JSON, not a lifecycle message
585
- }
586
- }
587
- return false
588
- }
589
-
590
- function extractTextContent(content: unknown): string {
591
- if (typeof content === 'string') return content
592
- if (Array.isArray(content)) {
593
- return content
594
- .map((chunk) => {
595
- if (typeof chunk === 'string') return chunk
596
- if (chunk && typeof chunk === 'object' && 'text' in chunk) {
597
- return typeof chunk.text === 'string' ? chunk.text : ''
598
- }
599
- return ''
600
- })
601
- .filter(Boolean)
602
- .join('\n')
603
- }
604
- if (content && typeof content === 'object') {
605
- if (
606
- 'status' in content &&
607
- (content as Record<string, unknown>).status === 'completed' &&
608
- Array.isArray((content as Record<string, unknown>).content)
609
- ) {
610
- return extractTextContent((content as Record<string, unknown>).content)
611
- }
612
- }
613
- if (content && typeof content === 'object') {
614
- return JSON.stringify(content)
615
- }
616
- return ''
617
- }