bingocode 1.0.2 → 1.0.3

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 (186) hide show
  1. package/desktop/README.md +30 -0
  2. package/desktop/bunfig.toml +1 -0
  3. package/desktop/index.html +17 -0
  4. package/desktop/package.json +55 -0
  5. package/desktop/pnpm-lock.yaml +3832 -0
  6. package/desktop/public/app-icon.jpg +0 -0
  7. package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
  8. package/desktop/public/fonts/inter-latin.woff2 +0 -0
  9. package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  10. package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
  11. package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
  12. package/desktop/public/fonts/manrope-latin.woff2 +0 -0
  13. package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
  14. package/desktop/public/icons/bilibili.svg +1 -0
  15. package/desktop/public/icons/douyin.svg +1 -0
  16. package/desktop/public/icons/github.svg +3 -0
  17. package/desktop/public/icons/xiaohongshu.svg +1 -0
  18. package/desktop/scripts/build-macos-arm64.sh +270 -0
  19. package/desktop/scripts/build-sidecars.ts +183 -0
  20. package/desktop/scripts/build-windows-x64.ps1 +295 -0
  21. package/desktop/scripts/scan-missing-imports.ts +235 -0
  22. package/desktop/sidecars/claude-sidecar.ts +156 -0
  23. package/desktop/src/App.tsx +5 -0
  24. package/desktop/src/__tests__/agentsSettings.test.tsx +349 -0
  25. package/desktop/src/__tests__/pages.test.tsx +290 -0
  26. package/desktop/src/__tests__/skillsSettings.test.tsx +205 -0
  27. package/desktop/src/api/adapters.ts +12 -0
  28. package/desktop/src/api/agents.ts +36 -0
  29. package/desktop/src/api/cliTasks.ts +28 -0
  30. package/desktop/src/api/client.ts +63 -0
  31. package/desktop/src/api/computerUse.ts +76 -0
  32. package/desktop/src/api/filesystem.ts +30 -0
  33. package/desktop/src/api/hahaOAuth.ts +38 -0
  34. package/desktop/src/api/models.ts +28 -0
  35. package/desktop/src/api/providers.ts +63 -0
  36. package/desktop/src/api/search.ts +29 -0
  37. package/desktop/src/api/sessions.ts +56 -0
  38. package/desktop/src/api/settings.ts +20 -0
  39. package/desktop/src/api/skills.ts +19 -0
  40. package/desktop/src/api/tasks.ts +36 -0
  41. package/desktop/src/api/teams.ts +44 -0
  42. package/desktop/src/api/websocket.ts +164 -0
  43. package/desktop/src/components/chat/AskUserQuestion.tsx +268 -0
  44. package/desktop/src/components/chat/AssistantMessage.tsx +29 -0
  45. package/desktop/src/components/chat/AttachmentGallery.tsx +113 -0
  46. package/desktop/src/components/chat/ChatInput.tsx +622 -0
  47. package/desktop/src/components/chat/CodeViewer.tsx +161 -0
  48. package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +174 -0
  49. package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +311 -0
  50. package/desktop/src/components/chat/DiffViewer.tsx +157 -0
  51. package/desktop/src/components/chat/FileSearchMenu.tsx +198 -0
  52. package/desktop/src/components/chat/ImageGalleryModal.tsx +91 -0
  53. package/desktop/src/components/chat/InlineImageGallery.tsx +106 -0
  54. package/desktop/src/components/chat/InlineTaskSummary.tsx +60 -0
  55. package/desktop/src/components/chat/MermaidRenderer.test.tsx +98 -0
  56. package/desktop/src/components/chat/MermaidRenderer.tsx +361 -0
  57. package/desktop/src/components/chat/MessageActionBar.tsx +27 -0
  58. package/desktop/src/components/chat/MessageList.test.tsx +313 -0
  59. package/desktop/src/components/chat/MessageList.tsx +249 -0
  60. package/desktop/src/components/chat/PermissionDialog.tsx +262 -0
  61. package/desktop/src/components/chat/SessionTaskBar.test.tsx +99 -0
  62. package/desktop/src/components/chat/SessionTaskBar.tsx +159 -0
  63. package/desktop/src/components/chat/StreamingIndicator.tsx +41 -0
  64. package/desktop/src/components/chat/TerminalChrome.tsx +35 -0
  65. package/desktop/src/components/chat/ThinkingBlock.tsx +87 -0
  66. package/desktop/src/components/chat/ToolCallBlock.tsx +247 -0
  67. package/desktop/src/components/chat/ToolCallGroup.tsx +617 -0
  68. package/desktop/src/components/chat/ToolResultBlock.tsx +107 -0
  69. package/desktop/src/components/chat/UserMessage.tsx +38 -0
  70. package/desktop/src/components/chat/chatBlocks.test.tsx +136 -0
  71. package/desktop/src/components/chat/clipboard.ts +25 -0
  72. package/desktop/src/components/chat/composerUtils.test.ts +55 -0
  73. package/desktop/src/components/chat/composerUtils.ts +149 -0
  74. package/desktop/src/components/controls/ModelSelector.tsx +156 -0
  75. package/desktop/src/components/controls/PermissionModeSelector.tsx +229 -0
  76. package/desktop/src/components/layout/AppShell.tsx +107 -0
  77. package/desktop/src/components/layout/ContentRouter.tsx +27 -0
  78. package/desktop/src/components/layout/ProjectFilter.tsx +126 -0
  79. package/desktop/src/components/layout/Sidebar.test.tsx +158 -0
  80. package/desktop/src/components/layout/Sidebar.tsx +384 -0
  81. package/desktop/src/components/layout/StatusBar.tsx +31 -0
  82. package/desktop/src/components/layout/TabBar.test.tsx +136 -0
  83. package/desktop/src/components/layout/TabBar.tsx +318 -0
  84. package/desktop/src/components/layout/TitleBar.tsx +96 -0
  85. package/desktop/src/components/layout/WindowControls.test.tsx +69 -0
  86. package/desktop/src/components/layout/WindowControls.tsx +89 -0
  87. package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +100 -0
  88. package/desktop/src/components/markdown/MarkdownRenderer.tsx +229 -0
  89. package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +107 -0
  90. package/desktop/src/components/shared/Button.tsx +63 -0
  91. package/desktop/src/components/shared/CopyButton.tsx +58 -0
  92. package/desktop/src/components/shared/DirectoryPicker.tsx +316 -0
  93. package/desktop/src/components/shared/Dropdown.tsx +91 -0
  94. package/desktop/src/components/shared/Input.tsx +38 -0
  95. package/desktop/src/components/shared/Modal.tsx +65 -0
  96. package/desktop/src/components/shared/ProjectContextChip.tsx +30 -0
  97. package/desktop/src/components/shared/Spinner.tsx +30 -0
  98. package/desktop/src/components/shared/Textarea.tsx +38 -0
  99. package/desktop/src/components/shared/Toast.tsx +47 -0
  100. package/desktop/src/components/shared/UpdateChecker.tsx +90 -0
  101. package/desktop/src/components/skills/SkillDetail.test.tsx +89 -0
  102. package/desktop/src/components/skills/SkillDetail.tsx +403 -0
  103. package/desktop/src/components/skills/SkillList.tsx +254 -0
  104. package/desktop/src/components/tasks/DayOfWeekPicker.tsx +57 -0
  105. package/desktop/src/components/tasks/NewTaskModal.tsx +407 -0
  106. package/desktop/src/components/tasks/PromptEditor.tsx +74 -0
  107. package/desktop/src/components/tasks/TaskEmptyState.tsx +30 -0
  108. package/desktop/src/components/tasks/TaskList.tsx +46 -0
  109. package/desktop/src/components/tasks/TaskRow.tsx +253 -0
  110. package/desktop/src/components/tasks/TaskRunsPanel.tsx +195 -0
  111. package/desktop/src/components/teams/TeamStatusBar.tsx +147 -0
  112. package/desktop/src/config/providerPresets.ts +78 -0
  113. package/desktop/src/config/spinnerVerbs.ts +193 -0
  114. package/desktop/src/hooks/useKeyboardShortcuts.ts +60 -0
  115. package/desktop/src/i18n/index.ts +54 -0
  116. package/desktop/src/i18n/locales/en.ts +670 -0
  117. package/desktop/src/i18n/locales/zh.ts +670 -0
  118. package/desktop/src/lib/__tests__/cronDescribe.test.ts +93 -0
  119. package/desktop/src/lib/cronDescribe.ts +188 -0
  120. package/desktop/src/lib/desktopRuntime.ts +54 -0
  121. package/desktop/src/lib/parseRunOutput.ts +79 -0
  122. package/desktop/src/main.tsx +13 -0
  123. package/desktop/src/mocks/data.ts +202 -0
  124. package/desktop/src/pages/ActiveSession.test.tsx +181 -0
  125. package/desktop/src/pages/ActiveSession.tsx +219 -0
  126. package/desktop/src/pages/AdapterSettings.tsx +375 -0
  127. package/desktop/src/pages/AgentTeams.tsx +200 -0
  128. package/desktop/src/pages/ComputerUseSettings.tsx +420 -0
  129. package/desktop/src/pages/EmptySession.tsx +518 -0
  130. package/desktop/src/pages/NewTaskModal.tsx +346 -0
  131. package/desktop/src/pages/ScheduledTasks.tsx +66 -0
  132. package/desktop/src/pages/ScheduledTasksEmpty.tsx +152 -0
  133. package/desktop/src/pages/ScheduledTasksList.tsx +416 -0
  134. package/desktop/src/pages/SessionControls.tsx +460 -0
  135. package/desktop/src/pages/Settings.tsx +1448 -0
  136. package/desktop/src/pages/ToolInspection.tsx +235 -0
  137. package/desktop/src/stores/adapterStore.ts +106 -0
  138. package/desktop/src/stores/agentStore.ts +34 -0
  139. package/desktop/src/stores/chatStore.test.ts +505 -0
  140. package/desktop/src/stores/chatStore.ts +850 -0
  141. package/desktop/src/stores/cliTaskStore.ts +152 -0
  142. package/desktop/src/stores/hahaOAuthStore.test.ts +77 -0
  143. package/desktop/src/stores/hahaOAuthStore.ts +97 -0
  144. package/desktop/src/stores/providerStore.ts +101 -0
  145. package/desktop/src/stores/sessionStore.test.ts +63 -0
  146. package/desktop/src/stores/sessionStore.ts +102 -0
  147. package/desktop/src/stores/settingsStore.ts +120 -0
  148. package/desktop/src/stores/skillStore.ts +51 -0
  149. package/desktop/src/stores/tabStore.ts +169 -0
  150. package/desktop/src/stores/taskStore.ts +68 -0
  151. package/desktop/src/stores/teamStore.ts +344 -0
  152. package/desktop/src/stores/uiStore.ts +100 -0
  153. package/desktop/src/stores/updateStore.test.ts +71 -0
  154. package/desktop/src/stores/updateStore.ts +221 -0
  155. package/desktop/src/theme/globals.css +465 -0
  156. package/desktop/src/types/adapter.ts +33 -0
  157. package/desktop/src/types/chat.ts +152 -0
  158. package/desktop/src/types/cliTask.ts +24 -0
  159. package/desktop/src/types/provider.ts +62 -0
  160. package/desktop/src/types/session.ts +27 -0
  161. package/desktop/src/types/settings.ts +22 -0
  162. package/desktop/src/types/skill.ts +38 -0
  163. package/desktop/src/types/task.ts +56 -0
  164. package/desktop/src/types/team.ts +38 -0
  165. package/desktop/src-tauri/Cargo.lock +5549 -0
  166. package/desktop/src-tauri/Cargo.toml +20 -0
  167. package/desktop/src-tauri/app-icon.svg +13 -0
  168. package/desktop/src-tauri/build.rs +3 -0
  169. package/desktop/src-tauri/capabilities/default.json +106 -0
  170. package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  171. package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
  172. package/desktop/src-tauri/icons/icon.icns +0 -0
  173. package/desktop/src-tauri/icons/icon.ico +0 -0
  174. package/desktop/src-tauri/src/lib.rs +408 -0
  175. package/desktop/src-tauri/src/main.rs +6 -0
  176. package/desktop/src-tauri/tauri.conf.json +78 -0
  177. package/desktop/src-tauri/tauri.macos.conf.json +18 -0
  178. package/desktop/src-tauri/tauri.release-ci.json +5 -0
  179. package/desktop/src-tauri/tauri.windows.conf.json +16 -0
  180. package/desktop/src-tauri/windows-installer-hooks.nsh +17 -0
  181. package/desktop/tsconfig.json +25 -0
  182. package/desktop/vite.config.ts +26 -0
  183. package/desktop/vitest.config.ts +18 -0
  184. package/package.json +1 -1
  185. package/src/commands/desktop/desktop.tsx +9 -0
  186. package/src/commands/desktop/index.ts +26 -0
@@ -0,0 +1,505 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { MessageEntry } from '../types/session'
3
+
4
+ const {
5
+ sendMock,
6
+ getMemberBySessionIdMock,
7
+ sendMessageToMemberMock,
8
+ handleTeamCreatedMock,
9
+ handleTeamUpdateMock,
10
+ handleTeamDeletedMock,
11
+ } = vi.hoisted(() => ({
12
+ sendMock: vi.fn(),
13
+ getMemberBySessionIdMock: vi.fn<(sessionId: string) => any>(() => null),
14
+ sendMessageToMemberMock: vi.fn(async () => {}),
15
+ handleTeamCreatedMock: vi.fn(),
16
+ handleTeamUpdateMock: vi.fn(),
17
+ handleTeamDeletedMock: vi.fn(),
18
+ }))
19
+
20
+ vi.mock('../api/websocket', () => ({
21
+ wsManager: {
22
+ connect: vi.fn(),
23
+ disconnect: vi.fn(),
24
+ onMessage: vi.fn(() => () => {}),
25
+ clearHandlers: vi.fn(),
26
+ send: sendMock,
27
+ },
28
+ }))
29
+
30
+ vi.mock('../api/sessions', () => ({
31
+ sessionsApi: {
32
+ getMessages: vi.fn(async () => ({ messages: [] })),
33
+ getSlashCommands: vi.fn(async () => ({ commands: [] })),
34
+ },
35
+ }))
36
+
37
+ vi.mock('./teamStore', () => ({
38
+ useTeamStore: {
39
+ getState: () => ({
40
+ getMemberBySessionId: getMemberBySessionIdMock,
41
+ sendMessageToMember: sendMessageToMemberMock,
42
+ handleTeamCreated: handleTeamCreatedMock,
43
+ handleTeamUpdate: handleTeamUpdateMock,
44
+ handleTeamDeleted: handleTeamDeletedMock,
45
+ }),
46
+ },
47
+ }))
48
+
49
+ vi.mock('./tabStore', () => ({
50
+ useTabStore: {
51
+ getState: () => ({
52
+ updateTabStatus: vi.fn(),
53
+ updateTabTitle: vi.fn(),
54
+ }),
55
+ },
56
+ }))
57
+
58
+ vi.mock('./sessionStore', () => ({
59
+ useSessionStore: {
60
+ getState: () => ({
61
+ updateSessionTitle: vi.fn(),
62
+ }),
63
+ },
64
+ }))
65
+
66
+ vi.mock('./cliTaskStore', () => ({
67
+ useCLITaskStore: {
68
+ getState: () => ({
69
+ fetchSessionTasks: vi.fn(),
70
+ tasks: [],
71
+ clearTasks: vi.fn(),
72
+ setTasksFromTodos: vi.fn(),
73
+ markCompletedAndDismissed: vi.fn(),
74
+ refreshTasks: vi.fn(),
75
+ }),
76
+ },
77
+ }))
78
+
79
+ import { mapHistoryMessagesToUiMessages, useChatStore } from './chatStore'
80
+
81
+ const TEST_SESSION_ID = 'test-session-1'
82
+ const initialState = useChatStore.getState()
83
+
84
+ describe('chatStore history mapping', () => {
85
+ beforeEach(() => {
86
+ sendMock.mockReset()
87
+ getMemberBySessionIdMock.mockReset()
88
+ getMemberBySessionIdMock.mockReturnValue(null)
89
+ sendMessageToMemberMock.mockReset()
90
+ useChatStore.setState({
91
+ ...initialState,
92
+ sessions: {},
93
+ })
94
+ })
95
+
96
+ it('preserves thinking blocks when restoring transcript history', () => {
97
+ const messages: MessageEntry[] = [
98
+ {
99
+ id: 'assistant-1',
100
+ type: 'assistant',
101
+ timestamp: '2026-04-06T00:00:00.000Z',
102
+ model: 'opus',
103
+ parentToolUseId: 'agent-1',
104
+ content: [
105
+ { type: 'thinking', thinking: 'internal reasoning' },
106
+ { type: 'text', text: '目录结构分析' },
107
+ { type: 'tool_use', name: 'Read', id: 'tool-1', input: { file_path: 'src/App.tsx' } },
108
+ ],
109
+ },
110
+ {
111
+ id: 'user-1',
112
+ type: 'user',
113
+ timestamp: '2026-04-06T00:00:01.000Z',
114
+ parentToolUseId: 'agent-1',
115
+ content: [
116
+ { type: 'tool_result', tool_use_id: 'tool-1', content: 'ok', is_error: false },
117
+ ],
118
+ },
119
+ ]
120
+
121
+ const mapped = mapHistoryMessagesToUiMessages(messages)
122
+
123
+ expect(mapped.map((message) => message.type)).toEqual([
124
+ 'thinking',
125
+ 'assistant_text',
126
+ 'tool_use',
127
+ 'tool_result',
128
+ ])
129
+ expect(mapped[2]).toMatchObject({ parentToolUseId: 'agent-1' })
130
+ expect(mapped[3]).toMatchObject({ parentToolUseId: 'agent-1' })
131
+ })
132
+
133
+ it('surfaces teammate prompt content when mapping member transcript history', () => {
134
+ const messages: MessageEntry[] = [
135
+ {
136
+ id: 'user-1',
137
+ type: 'user',
138
+ timestamp: '2026-04-06T00:00:00.000Z',
139
+ content: '<teammate-message teammate_id="security-reviewer">Review the auth diff and call out risks.</teammate-message>',
140
+ },
141
+ ]
142
+
143
+ const mapped = mapHistoryMessagesToUiMessages(messages, {
144
+ includeTeammateMessages: true,
145
+ })
146
+
147
+ expect(mapped).toMatchObject([
148
+ {
149
+ type: 'user_text',
150
+ content: 'Review the auth diff and call out risks.',
151
+ },
152
+ ])
153
+ })
154
+
155
+ it('keeps parent tool linkage for live tool events', () => {
156
+ // Initialize the session first
157
+ useChatStore.setState({
158
+ sessions: {
159
+ [TEST_SESSION_ID]: {
160
+ messages: [],
161
+ chatState: 'idle',
162
+ connectionState: 'connected',
163
+ streamingText: '',
164
+ streamingToolInput: '',
165
+ activeToolUseId: null,
166
+ activeToolName: null,
167
+ activeThinkingId: null,
168
+ pendingPermission: null,
169
+ pendingComputerUsePermission: null,
170
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
171
+ elapsedSeconds: 0,
172
+ statusVerb: '',
173
+ slashCommands: [],
174
+ agentTaskNotifications: {},
175
+ elapsedTimer: null,
176
+ },
177
+ },
178
+ })
179
+
180
+ useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
181
+ type: 'tool_use_complete',
182
+ toolName: 'Read',
183
+ toolUseId: 'tool-1',
184
+ input: { file_path: 'src/App.tsx' },
185
+ parentToolUseId: 'agent-1',
186
+ })
187
+
188
+ useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
189
+ type: 'tool_result',
190
+ toolUseId: 'tool-1',
191
+ content: 'ok',
192
+ isError: false,
193
+ parentToolUseId: 'agent-1',
194
+ })
195
+
196
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([
197
+ {
198
+ type: 'tool_use',
199
+ toolUseId: 'tool-1',
200
+ parentToolUseId: 'agent-1',
201
+ },
202
+ {
203
+ type: 'tool_result',
204
+ toolUseId: 'tool-1',
205
+ parentToolUseId: 'agent-1',
206
+ },
207
+ ])
208
+ })
209
+
210
+ it('sends permission mode updates to the active session only', () => {
211
+ useChatStore.getState().setSessionPermissionMode('nonexistent-session', 'acceptEdits')
212
+ expect(sendMock).not.toHaveBeenCalled()
213
+
214
+ useChatStore.setState({
215
+ sessions: {
216
+ 'session-1': {
217
+ messages: [],
218
+ chatState: 'idle',
219
+ connectionState: 'connected',
220
+ streamingText: '',
221
+ streamingToolInput: '',
222
+ activeToolUseId: null,
223
+ activeToolName: null,
224
+ activeThinkingId: null,
225
+ pendingPermission: null,
226
+ pendingComputerUsePermission: null,
227
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
228
+ elapsedSeconds: 0,
229
+ statusVerb: '',
230
+ slashCommands: [],
231
+ agentTaskNotifications: {},
232
+ elapsedTimer: null,
233
+ },
234
+ },
235
+ })
236
+ useChatStore.getState().setSessionPermissionMode('session-1', 'acceptEdits')
237
+
238
+ expect(sendMock).toHaveBeenCalledWith('session-1', {
239
+ type: 'set_permission_mode',
240
+ mode: 'acceptEdits',
241
+ })
242
+ })
243
+
244
+ it('stores terminal task notifications for agent tool cards', () => {
245
+ useChatStore.setState({
246
+ sessions: {
247
+ [TEST_SESSION_ID]: {
248
+ messages: [],
249
+ chatState: 'idle',
250
+ connectionState: 'connected',
251
+ streamingText: '',
252
+ streamingToolInput: '',
253
+ activeToolUseId: null,
254
+ activeToolName: null,
255
+ activeThinkingId: null,
256
+ pendingPermission: null,
257
+ pendingComputerUsePermission: null,
258
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
259
+ elapsedSeconds: 0,
260
+ statusVerb: '',
261
+ slashCommands: [],
262
+ agentTaskNotifications: {},
263
+ elapsedTimer: null,
264
+ },
265
+ },
266
+ })
267
+
268
+ useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
269
+ type: 'system_notification',
270
+ subtype: 'task_notification',
271
+ data: {
272
+ task_id: 'agent-task-1',
273
+ tool_use_id: 'agent-tool-1',
274
+ status: 'completed',
275
+ summary: 'Agent "修复异常处理" completed',
276
+ output_file: '/tmp/agent-output.txt',
277
+ },
278
+ })
279
+
280
+ expect(
281
+ useChatStore.getState().sessions[TEST_SESSION_ID]?.agentTaskNotifications[
282
+ 'agent-tool-1'
283
+ ],
284
+ ).toMatchObject({
285
+ taskId: 'agent-task-1',
286
+ toolUseId: 'agent-tool-1',
287
+ status: 'completed',
288
+ summary: 'Agent "修复异常处理" completed',
289
+ outputFile: '/tmp/agent-output.txt',
290
+ })
291
+ })
292
+
293
+ it('flushes the previous assistant draft before starting a new user turn', () => {
294
+ useChatStore.setState({
295
+ sessions: {
296
+ [TEST_SESSION_ID]: {
297
+ messages: [],
298
+ chatState: 'streaming',
299
+ connectionState: 'connected',
300
+ streamingText: '上一次分析结果 **还在流式区域**',
301
+ streamingToolInput: '',
302
+ activeToolUseId: null,
303
+ activeToolName: null,
304
+ activeThinkingId: null,
305
+ pendingPermission: null,
306
+ pendingComputerUsePermission: null,
307
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
308
+ elapsedSeconds: 0,
309
+ statusVerb: '',
310
+ slashCommands: [],
311
+ agentTaskNotifications: {},
312
+ elapsedTimer: null,
313
+ },
314
+ },
315
+ })
316
+
317
+ useChatStore.getState().sendMessage(TEST_SESSION_ID, '你是什么模型?')
318
+
319
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.messages).toMatchObject([
320
+ {
321
+ type: 'assistant_text',
322
+ content: '上一次分析结果 **还在流式区域**',
323
+ },
324
+ {
325
+ type: 'user_text',
326
+ content: '你是什么模型?',
327
+ },
328
+ ])
329
+ expect(useChatStore.getState().sessions[TEST_SESSION_ID]?.streamingText).toBe('')
330
+ })
331
+
332
+ it('tracks Computer Use approval requests separately from generic tool permissions', () => {
333
+ useChatStore.setState({
334
+ sessions: {
335
+ [TEST_SESSION_ID]: {
336
+ messages: [],
337
+ chatState: 'idle',
338
+ connectionState: 'connected',
339
+ streamingText: '',
340
+ streamingToolInput: '',
341
+ activeToolUseId: null,
342
+ activeToolName: null,
343
+ activeThinkingId: null,
344
+ pendingPermission: null,
345
+ pendingComputerUsePermission: null,
346
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
347
+ elapsedSeconds: 0,
348
+ statusVerb: '',
349
+ slashCommands: [],
350
+ agentTaskNotifications: {},
351
+ elapsedTimer: null,
352
+ },
353
+ },
354
+ })
355
+
356
+ useChatStore.getState().handleServerMessage(TEST_SESSION_ID, {
357
+ type: 'computer_use_permission_request',
358
+ requestId: 'cu-1',
359
+ request: {
360
+ requestId: 'cu-1',
361
+ reason: 'Open Finder and inspect a file',
362
+ apps: [
363
+ {
364
+ requestedName: 'Finder',
365
+ resolved: {
366
+ bundleId: 'com.apple.finder',
367
+ displayName: 'Finder',
368
+ },
369
+ isSentinel: false,
370
+ alreadyGranted: false,
371
+ proposedTier: 'full',
372
+ },
373
+ ],
374
+ requestedFlags: { clipboardRead: true },
375
+ screenshotFiltering: 'native',
376
+ },
377
+ })
378
+
379
+ expect(
380
+ useChatStore.getState().sessions[TEST_SESSION_ID]?.pendingComputerUsePermission,
381
+ ).toMatchObject({
382
+ requestId: 'cu-1',
383
+ request: {
384
+ reason: 'Open Finder and inspect a file',
385
+ },
386
+ })
387
+ expect(
388
+ useChatStore.getState().sessions[TEST_SESSION_ID]?.chatState,
389
+ ).toBe('permission_pending')
390
+ })
391
+
392
+ it('sends Computer Use approval payloads back over websocket', () => {
393
+ useChatStore.setState({
394
+ sessions: {
395
+ [TEST_SESSION_ID]: {
396
+ messages: [],
397
+ chatState: 'permission_pending',
398
+ connectionState: 'connected',
399
+ streamingText: '',
400
+ streamingToolInput: '',
401
+ activeToolUseId: null,
402
+ activeToolName: null,
403
+ activeThinkingId: null,
404
+ pendingPermission: null,
405
+ pendingComputerUsePermission: {
406
+ requestId: 'cu-1',
407
+ request: {
408
+ requestId: 'cu-1',
409
+ reason: 'Open Finder',
410
+ apps: [],
411
+ requestedFlags: {},
412
+ screenshotFiltering: 'native',
413
+ },
414
+ },
415
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
416
+ elapsedSeconds: 0,
417
+ statusVerb: '',
418
+ slashCommands: [],
419
+ agentTaskNotifications: {},
420
+ elapsedTimer: null,
421
+ },
422
+ },
423
+ })
424
+
425
+ useChatStore.getState().respondToComputerUsePermission(TEST_SESSION_ID, 'cu-1', {
426
+ granted: [],
427
+ denied: [],
428
+ flags: {
429
+ clipboardRead: true,
430
+ clipboardWrite: false,
431
+ systemKeyCombos: false,
432
+ },
433
+ userConsented: true,
434
+ })
435
+
436
+ expect(sendMock).toHaveBeenCalledWith(TEST_SESSION_ID, {
437
+ type: 'computer_use_permission_response',
438
+ requestId: 'cu-1',
439
+ response: {
440
+ granted: [],
441
+ denied: [],
442
+ flags: {
443
+ clipboardRead: true,
444
+ clipboardWrite: false,
445
+ systemKeyCombos: false,
446
+ },
447
+ userConsented: true,
448
+ },
449
+ })
450
+ expect(
451
+ useChatStore.getState().sessions[TEST_SESSION_ID]?.pendingComputerUsePermission,
452
+ ).toBeNull()
453
+ expect(
454
+ useChatStore.getState().sessions[TEST_SESSION_ID]?.chatState,
455
+ ).toBe('tool_executing')
456
+ })
457
+
458
+ it('routes member-session messages through team mailbox delivery instead of websocket', async () => {
459
+ const memberSessionId = 'team-member:security-reviewer@test-team'
460
+ getMemberBySessionIdMock.mockReturnValue({
461
+ agentId: 'security-reviewer@test-team',
462
+ role: 'security-reviewer',
463
+ status: 'running',
464
+ })
465
+
466
+ useChatStore.setState({
467
+ sessions: {
468
+ [memberSessionId]: {
469
+ messages: [],
470
+ chatState: 'idle',
471
+ connectionState: 'connected',
472
+ streamingText: '',
473
+ streamingToolInput: '',
474
+ activeToolUseId: null,
475
+ activeToolName: null,
476
+ activeThinkingId: null,
477
+ pendingPermission: null,
478
+ pendingComputerUsePermission: null,
479
+ tokenUsage: { input_tokens: 0, output_tokens: 0 },
480
+ elapsedSeconds: 0,
481
+ statusVerb: '',
482
+ slashCommands: [],
483
+ agentTaskNotifications: {},
484
+ elapsedTimer: null,
485
+ },
486
+ },
487
+ })
488
+
489
+ useChatStore.getState().sendMessage(memberSessionId, 'Check the latest regression')
490
+ await Promise.resolve()
491
+
492
+ expect(sendMessageToMemberMock).toHaveBeenCalledWith(
493
+ memberSessionId,
494
+ 'Check the latest regression',
495
+ )
496
+ expect(sendMock).not.toHaveBeenCalled()
497
+ const sessionMessages = useChatStore.getState().sessions[memberSessionId]?.messages ?? []
498
+
499
+ expect(sessionMessages[sessionMessages.length - 1]).toMatchObject({
500
+ type: 'user_text',
501
+ content: 'Check the latest regression',
502
+ pending: true,
503
+ })
504
+ })
505
+ })