bingocode 1.0.3 → 1.0.5

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 +3 -7
  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,850 +0,0 @@
1
- import { create } from 'zustand'
2
- import { wsManager } from '../api/websocket'
3
- import { sessionsApi } from '../api/sessions'
4
- import { useTeamStore } from './teamStore'
5
- import { useSessionStore } from './sessionStore'
6
- import { useCLITaskStore } from './cliTaskStore'
7
- import { useTabStore } from './tabStore'
8
- import { randomSpinnerVerb } from '../config/spinnerVerbs'
9
- import { AGENT_LIFECYCLE_TYPES } from '../types/team'
10
- import type { MessageEntry } from '../types/session'
11
- import type { PermissionMode } from '../types/settings'
12
- import type {
13
- AgentTaskNotification,
14
- AttachmentRef,
15
- ChatState,
16
- ComputerUsePermissionRequest,
17
- ComputerUsePermissionResponse,
18
- UIAttachment,
19
- UIMessage,
20
- ServerMessage,
21
- TokenUsage,
22
- } from '../types/chat'
23
-
24
- type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
25
-
26
- export type PerSessionState = {
27
- messages: UIMessage[]
28
- chatState: ChatState
29
- connectionState: ConnectionState
30
- streamingText: string
31
- streamingToolInput: string
32
- activeToolUseId: string | null
33
- activeToolName: string | null
34
- activeThinkingId: string | null
35
- pendingPermission: {
36
- requestId: string
37
- toolName: string
38
- input: unknown
39
- description?: string
40
- } | null
41
- pendingComputerUsePermission: {
42
- requestId: string
43
- request: ComputerUsePermissionRequest
44
- } | null
45
- tokenUsage: TokenUsage
46
- elapsedSeconds: number
47
- statusVerb: string
48
- slashCommands: Array<{ name: string; description: string }>
49
- agentTaskNotifications: Record<string, AgentTaskNotification>
50
- elapsedTimer: ReturnType<typeof setInterval> | null
51
- }
52
-
53
- const DEFAULT_SESSION_STATE: PerSessionState = {
54
- messages: [],
55
- chatState: 'idle',
56
- connectionState: 'disconnected',
57
- streamingText: '',
58
- streamingToolInput: '',
59
- activeToolUseId: null,
60
- activeToolName: null,
61
- activeThinkingId: null,
62
- pendingPermission: null,
63
- pendingComputerUsePermission: null,
64
- tokenUsage: { input_tokens: 0, output_tokens: 0 },
65
- elapsedSeconds: 0,
66
- statusVerb: '',
67
- slashCommands: [],
68
- agentTaskNotifications: {},
69
- elapsedTimer: null,
70
- }
71
-
72
- function createDefaultSessionState(): PerSessionState {
73
- return { ...DEFAULT_SESSION_STATE, messages: [], tokenUsage: { input_tokens: 0, output_tokens: 0 } }
74
- }
75
-
76
- type ChatStore = {
77
- sessions: Record<string, PerSessionState>
78
-
79
- getSession: (sessionId: string) => PerSessionState
80
- connectToSession: (sessionId: string) => void
81
- disconnectSession: (sessionId: string) => void
82
- sendMessage: (sessionId: string, content: string, attachments?: AttachmentRef[]) => void
83
- respondToPermission: (sessionId: string, requestId: string, allowed: boolean, rule?: string) => void
84
- respondToComputerUsePermission: (
85
- sessionId: string,
86
- requestId: string,
87
- response: ComputerUsePermissionResponse,
88
- ) => void
89
- setSessionPermissionMode: (sessionId: string, mode: PermissionMode) => void
90
- stopGeneration: (sessionId: string) => void
91
- loadHistory: (sessionId: string) => Promise<void>
92
- clearMessages: (sessionId: string) => void
93
- handleServerMessage: (sessionId: string, msg: ServerMessage) => void
94
- }
95
-
96
- const TASK_TOOL_NAMES = new Set(['TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList', 'TodoWrite'])
97
- const pendingTaskToolUseIds = new Set<string>()
98
-
99
- let msgCounter = 0
100
- const nextId = () => `msg-${++msgCounter}-${Date.now()}`
101
-
102
- // Streaming throttle for content_delta
103
- let pendingDelta = ''
104
- let flushTimer: ReturnType<typeof setTimeout> | null = null
105
-
106
- /** Helper: immutably update a specific session within the sessions record */
107
- function updateSessionIn(
108
- sessions: Record<string, PerSessionState>,
109
- sessionId: string,
110
- updater: (s: PerSessionState) => Partial<PerSessionState>,
111
- ): Record<string, PerSessionState> {
112
- const session = sessions[sessionId]
113
- if (!session) return sessions
114
- return { ...sessions, [sessionId]: { ...session, ...updater(session) } }
115
- }
116
-
117
- export const useChatStore = create<ChatStore>((set, get) => ({
118
- sessions: {},
119
-
120
- getSession: (sessionId) => get().sessions[sessionId] ?? createDefaultSessionState(),
121
-
122
- connectToSession: (sessionId) => {
123
- const existing = get().sessions[sessionId]
124
- if (existing && existing.connectionState !== 'disconnected') return
125
-
126
- set((s) => ({
127
- sessions: {
128
- ...s.sessions,
129
- [sessionId]: {
130
- ...createDefaultSessionState(),
131
- connectionState: 'connecting',
132
- messages: existing?.messages ?? [],
133
- },
134
- },
135
- }))
136
-
137
- wsManager.clearHandlers(sessionId)
138
- wsManager.connect(sessionId)
139
- wsManager.onMessage(sessionId, (msg) => {
140
- if (msg.type === 'connected') {
141
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, () => ({ connectionState: 'connected' })) }))
142
- }
143
- get().handleServerMessage(sessionId, msg)
144
- })
145
-
146
- get().loadHistory(sessionId)
147
- useCLITaskStore.getState().fetchSessionTasks(sessionId)
148
- sessionsApi.getSlashCommands(sessionId)
149
- .then(({ commands }) => {
150
- if (get().sessions[sessionId]) {
151
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, () => ({ slashCommands: commands })) }))
152
- }
153
- })
154
- .catch(() => {
155
- if (get().sessions[sessionId]) {
156
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, () => ({ slashCommands: [] })) }))
157
- }
158
- })
159
- },
160
-
161
- disconnectSession: (sessionId) => {
162
- const session = get().sessions[sessionId]
163
- if (session?.elapsedTimer) clearInterval(session.elapsedTimer)
164
- if (flushTimer) { clearTimeout(flushTimer); flushTimer = null }
165
- if (pendingDelta) {
166
- const text = pendingDelta
167
- pendingDelta = ''
168
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, (sess) => ({ streamingText: sess.streamingText + text })) }))
169
- }
170
- wsManager.disconnect(sessionId)
171
- set((s) => {
172
- const { [sessionId]: _, ...rest } = s.sessions
173
- return { sessions: rest }
174
- })
175
- },
176
-
177
- sendMessage: (sessionId, content, attachments?) => {
178
- const userFacingContent = content.trim()
179
- const isMemberSession = !!useTeamStore.getState().getMemberBySessionId(sessionId)
180
- const uiAttachments: UIAttachment[] | undefined =
181
- attachments && attachments.length > 0
182
- ? attachments.map((a) => ({
183
- type: a.type,
184
- name: a.name || a.path || a.mimeType || a.type,
185
- data: a.data,
186
- mimeType: a.mimeType,
187
- }))
188
- : undefined
189
-
190
- const taskStore = useCLITaskStore.getState()
191
- const allTasksDone = taskStore.tasks.length > 0 && taskStore.tasks.every((t) => t.status === 'completed')
192
-
193
- set((s) => {
194
- const session = s.sessions[sessionId] ?? createDefaultSessionState()
195
- if (flushTimer) {
196
- clearTimeout(flushTimer)
197
- flushTimer = null
198
- }
199
- const bufferedDelta = pendingDelta
200
- pendingDelta = ''
201
- const pendingAssistantText = `${session.streamingText}${bufferedDelta}`.trim()
202
-
203
- const newMessages = pendingAssistantText
204
- ? [
205
- ...session.messages,
206
- {
207
- id: nextId(),
208
- type: 'assistant_text' as const,
209
- content: pendingAssistantText,
210
- timestamp: Date.now(),
211
- },
212
- ]
213
- : [...session.messages]
214
- if (!isMemberSession && allTasksDone) {
215
- newMessages.push({
216
- id: nextId(),
217
- type: 'task_summary',
218
- tasks: taskStore.tasks.map((t) => ({ id: t.id, subject: t.subject, status: t.status, activeForm: t.activeForm })),
219
- timestamp: Date.now(),
220
- })
221
- taskStore.clearTasks()
222
- }
223
- newMessages.push({
224
- id: nextId(),
225
- type: 'user_text',
226
- content: userFacingContent,
227
- attachments: isMemberSession ? undefined : uiAttachments,
228
- timestamp: Date.now(),
229
- ...(isMemberSession ? { pending: true } : {}),
230
- })
231
-
232
- if (!isMemberSession && session.elapsedTimer) clearInterval(session.elapsedTimer)
233
-
234
- const timer = !isMemberSession
235
- ? setInterval(() => {
236
- set((st) => ({ sessions: updateSessionIn(st.sessions, sessionId, (sess) => ({ elapsedSeconds: sess.elapsedSeconds + 1 })) }))
237
- }, 1000)
238
- : null
239
-
240
- return {
241
- sessions: {
242
- ...s.sessions,
243
- [sessionId]: {
244
- ...session,
245
- messages: newMessages,
246
- chatState: 'thinking',
247
- elapsedSeconds: 0,
248
- streamingText: '',
249
- statusVerb: isMemberSession ? '' : randomSpinnerVerb(),
250
- elapsedTimer: timer,
251
- connectionState: isMemberSession ? 'connected' : session.connectionState,
252
- },
253
- },
254
- }
255
- })
256
-
257
- if (isMemberSession) {
258
- void useTeamStore.getState().sendMessageToMember(sessionId, userFacingContent)
259
- .catch((err) => {
260
- set((s) => ({
261
- sessions: updateSessionIn(s.sessions, sessionId, (session) => ({
262
- chatState: 'idle',
263
- messages: [
264
- ...session.messages,
265
- {
266
- id: nextId(),
267
- type: 'error',
268
- message: err instanceof Error ? err.message : String(err),
269
- code: 'TEAM_MEMBER_MESSAGE_FAILED',
270
- timestamp: Date.now(),
271
- },
272
- ],
273
- })),
274
- }))
275
- })
276
- return
277
- }
278
-
279
- wsManager.send(sessionId, { type: 'user_message', content, attachments })
280
- },
281
-
282
- respondToPermission: (sessionId, requestId, allowed, rule?) => {
283
- wsManager.send(sessionId, { type: 'permission_response', requestId, allowed, ...(rule ? { rule } : {}) })
284
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, () => ({ pendingPermission: null, chatState: allowed ? 'tool_executing' : 'idle' })) }))
285
- },
286
-
287
- respondToComputerUsePermission: (sessionId, requestId, response) => {
288
- wsManager.send(sessionId, {
289
- type: 'computer_use_permission_response',
290
- requestId,
291
- response,
292
- })
293
- set((s) => ({
294
- sessions: updateSessionIn(s.sessions, sessionId, () => ({
295
- pendingComputerUsePermission: null,
296
- chatState: response.userConsented === false ? 'idle' : 'tool_executing',
297
- })),
298
- }))
299
- },
300
-
301
- setSessionPermissionMode: (sessionId, mode) => {
302
- if (!get().sessions[sessionId]) return
303
- wsManager.send(sessionId, { type: 'set_permission_mode', mode })
304
- },
305
-
306
- stopGeneration: (sessionId) => {
307
- wsManager.send(sessionId, { type: 'stop_generation' })
308
- if (flushTimer) { clearTimeout(flushTimer); flushTimer = null }
309
- if (pendingDelta) {
310
- const text = pendingDelta
311
- pendingDelta = ''
312
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, (sess) => ({ streamingText: sess.streamingText + text })) }))
313
- }
314
- set((s) => {
315
- const session = s.sessions[sessionId]
316
- if (!session) return s
317
- if (session.elapsedTimer) clearInterval(session.elapsedTimer)
318
- return {
319
- sessions: {
320
- ...s.sessions,
321
- [sessionId]: {
322
- ...session,
323
- chatState: 'idle',
324
- pendingPermission: null,
325
- pendingComputerUsePermission: null,
326
- elapsedTimer: null,
327
- },
328
- },
329
- }
330
- })
331
- },
332
-
333
- loadHistory: async (sessionId) => {
334
- try {
335
- const { messages } = await sessionsApi.getMessages(sessionId)
336
- const uiMessages = mapHistoryMessagesToUiMessages(messages)
337
- const restoredNotifications = reconstructAgentNotifications(messages)
338
- set((state) => {
339
- const session = state.sessions[sessionId]
340
- if (!session || session.messages.length > 0) return state
341
- return { sessions: updateSessionIn(state.sessions, sessionId, (s) => ({
342
- messages: uiMessages,
343
- agentTaskNotifications: { ...s.agentTaskNotifications, ...restoredNotifications },
344
- })) }
345
- })
346
- const lastTodos = extractLastTodoWriteFromHistory(messages)
347
- if (lastTodos && lastTodos.length > 0) {
348
- const taskStore = useCLITaskStore.getState()
349
- if (taskStore.tasks.length === 0) taskStore.setTasksFromTodos(lastTodos)
350
- }
351
- if (hasUserMessagesAfterTaskCompletion(messages)) {
352
- useCLITaskStore.getState().markCompletedAndDismissed()
353
- }
354
- } catch {
355
- // Session may not have messages yet
356
- }
357
- },
358
-
359
- clearMessages: (sessionId) => {
360
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, () => ({ messages: [], streamingText: '', chatState: 'idle' })) }))
361
- },
362
-
363
- handleServerMessage: (sessionId, msg) => {
364
- const update = (updater: (session: PerSessionState) => Partial<PerSessionState>) => {
365
- set((s) => ({ sessions: updateSessionIn(s.sessions, sessionId, updater) }))
366
- }
367
-
368
- switch (msg.type) {
369
- case 'connected':
370
- break
371
-
372
- case 'status':
373
- update((session) => {
374
- const pendingText = session.streamingText.trim()
375
- const shouldFlush = pendingText && session.chatState === 'streaming' && msg.state !== 'streaming'
376
- return {
377
- chatState: msg.state,
378
- ...(msg.verb && msg.verb !== 'Thinking' ? { statusVerb: msg.verb } : {}),
379
- ...(msg.tokens ? { tokenUsage: { ...session.tokenUsage, output_tokens: msg.tokens } } : {}),
380
- ...(msg.state === 'idle' ? { activeThinkingId: null, statusVerb: '' } : {}),
381
- ...(shouldFlush ? {
382
- messages: [...session.messages, { id: nextId(), type: 'assistant_text' as const, content: pendingText, timestamp: Date.now() }],
383
- streamingText: '',
384
- } : {}),
385
- }
386
- })
387
- if (msg.state === 'idle') {
388
- const session = get().sessions[sessionId]
389
- if (session?.elapsedTimer) {
390
- clearInterval(session.elapsedTimer)
391
- update(() => ({ elapsedTimer: null }))
392
- }
393
- }
394
- // Sync tab status
395
- useTabStore.getState().updateTabStatus(sessionId, msg.state === 'idle' ? 'idle' : 'running')
396
- break
397
-
398
- case 'content_start': {
399
- const session = get().sessions[sessionId]
400
- if (!session) break
401
- const pendingText = session.streamingText.trim()
402
- if (pendingText) {
403
- update((s) => ({
404
- messages: [...s.messages, { id: nextId(), type: 'assistant_text' as const, content: pendingText, timestamp: Date.now() }],
405
- streamingText: '',
406
- }))
407
- }
408
- if (msg.blockType === 'text') {
409
- update(() => ({ streamingText: '', chatState: 'streaming', activeThinkingId: null }))
410
- } else if (msg.blockType === 'tool_use') {
411
- update(() => ({
412
- activeToolUseId: msg.toolUseId ?? null,
413
- activeToolName: msg.toolName ?? null,
414
- streamingToolInput: '',
415
- chatState: 'tool_executing',
416
- activeThinkingId: null,
417
- }))
418
- }
419
- break
420
- }
421
-
422
- case 'content_delta':
423
- if (msg.text !== undefined) {
424
- pendingDelta += msg.text
425
- if (!flushTimer) {
426
- flushTimer = setTimeout(() => {
427
- const text = pendingDelta
428
- pendingDelta = ''
429
- flushTimer = null
430
- update((s) => ({ streamingText: s.streamingText + text }))
431
- }, 50)
432
- }
433
- }
434
- if (msg.toolInput !== undefined) update((s) => ({ streamingToolInput: s.streamingToolInput + msg.toolInput }))
435
- break
436
-
437
- case 'thinking':
438
- update((s) => {
439
- const pendingText = s.streamingText.trim()
440
- const base = pendingText
441
- ? [...s.messages, { id: nextId(), type: 'assistant_text' as const, content: pendingText, timestamp: Date.now() }]
442
- : s.messages
443
- const last = base[base.length - 1]
444
- if (last && last.type === 'thinking') {
445
- const updated = [...base]
446
- updated[updated.length - 1] = { ...last, content: last.content + msg.text }
447
- return { messages: updated, chatState: 'thinking', activeThinkingId: last.id, streamingText: '' }
448
- }
449
- const id = nextId()
450
- return {
451
- messages: [...base, { id, type: 'thinking', content: msg.text, timestamp: Date.now() }],
452
- chatState: 'thinking',
453
- activeThinkingId: id,
454
- streamingText: '',
455
- }
456
- })
457
- break
458
-
459
- case 'tool_use_complete': {
460
- const session = get().sessions[sessionId]
461
- const toolName = msg.toolName || session?.activeToolName || 'unknown'
462
- update((s) => ({
463
- messages: [...s.messages, {
464
- id: nextId(), type: 'tool_use', toolName,
465
- toolUseId: msg.toolUseId || s.activeToolUseId || '',
466
- input: msg.input, timestamp: Date.now(), parentToolUseId: msg.parentToolUseId,
467
- }],
468
- activeToolUseId: null, activeToolName: null, activeThinkingId: null, streamingToolInput: '',
469
- }))
470
- if (toolName === 'TodoWrite' && Array.isArray((msg.input as any)?.todos)) {
471
- useCLITaskStore.getState().setTasksFromTodos((msg.input as any).todos)
472
- } else if (TASK_TOOL_NAMES.has(toolName)) {
473
- const useId = msg.toolUseId || session?.activeToolUseId
474
- if (useId) pendingTaskToolUseIds.add(useId)
475
- }
476
- break
477
- }
478
-
479
- case 'tool_result':
480
- update((s) => ({
481
- messages: [...s.messages, {
482
- id: nextId(), type: 'tool_result', toolUseId: msg.toolUseId,
483
- content: msg.content, isError: msg.isError, timestamp: Date.now(), parentToolUseId: msg.parentToolUseId,
484
- }],
485
- chatState: 'thinking', activeThinkingId: null,
486
- }))
487
- if (pendingTaskToolUseIds.has(msg.toolUseId)) {
488
- pendingTaskToolUseIds.delete(msg.toolUseId)
489
- useCLITaskStore.getState().refreshTasks()
490
- }
491
- break
492
-
493
- case 'permission_request':
494
- update((s) => ({
495
- pendingPermission: { requestId: msg.requestId, toolName: msg.toolName, input: msg.input, description: msg.description },
496
- pendingComputerUsePermission: null,
497
- chatState: 'permission_pending',
498
- activeThinkingId: null,
499
- messages: [...s.messages, {
500
- id: nextId(), type: 'permission_request', requestId: msg.requestId,
501
- toolName: msg.toolName, input: msg.input, description: msg.description, timestamp: Date.now(),
502
- }],
503
- }))
504
- break
505
-
506
- case 'computer_use_permission_request':
507
- update(() => ({
508
- pendingComputerUsePermission: {
509
- requestId: msg.requestId,
510
- request: msg.request,
511
- },
512
- pendingPermission: null,
513
- chatState: 'permission_pending',
514
- activeThinkingId: null,
515
- }))
516
- break
517
-
518
- case 'message_complete': {
519
- const session = get().sessions[sessionId]
520
- if (!session) break
521
- const text = session.streamingText
522
- if (text) {
523
- update((s) => ({
524
- messages: [...s.messages, { id: nextId(), type: 'assistant_text', content: text, timestamp: Date.now() }],
525
- streamingText: '',
526
- }))
527
- }
528
- if (session.elapsedTimer) clearInterval(session.elapsedTimer)
529
- update(() => ({
530
- tokenUsage: msg.usage,
531
- chatState: 'idle',
532
- activeThinkingId: null,
533
- pendingPermission: null,
534
- pendingComputerUsePermission: null,
535
- elapsedTimer: null,
536
- }))
537
- break
538
- }
539
-
540
- case 'error':
541
- update((s) => {
542
- const pendingText = s.streamingText.trim()
543
- const newMessages = [...s.messages]
544
- if (pendingText) {
545
- newMessages.push({ id: nextId(), type: 'assistant_text' as const, content: pendingText, timestamp: Date.now() })
546
- }
547
- newMessages.push({ id: nextId(), type: 'error', message: msg.message, code: msg.code, timestamp: Date.now() })
548
- return {
549
- messages: newMessages,
550
- chatState: 'idle',
551
- activeThinkingId: null,
552
- streamingText: '',
553
- pendingPermission: null,
554
- pendingComputerUsePermission: null,
555
- }
556
- })
557
- useTabStore.getState().updateTabStatus(sessionId, 'error')
558
- {
559
- const session = get().sessions[sessionId]
560
- if (session?.elapsedTimer) {
561
- clearInterval(session.elapsedTimer)
562
- update(() => ({ elapsedTimer: null }))
563
- }
564
- }
565
- break
566
-
567
- case 'team_created':
568
- useTeamStore.getState().handleTeamCreated(msg.teamName)
569
- break
570
- case 'team_update':
571
- useTeamStore.getState().handleTeamUpdate(msg.teamName, msg.members)
572
- break
573
- case 'team_deleted':
574
- useTeamStore.getState().handleTeamDeleted(msg.teamName)
575
- break
576
- case 'task_update':
577
- break
578
- case 'session_title_updated':
579
- useSessionStore.getState().updateSessionTitle(msg.sessionId, msg.title)
580
- useTabStore.getState().updateTabTitle(msg.sessionId, msg.title)
581
- break
582
- case 'system_notification':
583
- if (msg.subtype === 'slash_commands' && Array.isArray(msg.data)) {
584
- update(() => ({ slashCommands: msg.data as Array<{ name: string; description: string }> }))
585
- }
586
- if (msg.subtype === 'task_notification' && msg.data && typeof msg.data === 'object') {
587
- const data = msg.data as Record<string, unknown>
588
- const toolUseId =
589
- typeof data.tool_use_id === 'string' && data.tool_use_id.trim()
590
- ? data.tool_use_id
591
- : null
592
- const taskStatus = data.status
593
- if (
594
- toolUseId &&
595
- (taskStatus === 'completed' ||
596
- taskStatus === 'failed' ||
597
- taskStatus === 'stopped')
598
- ) {
599
- update((session) => ({
600
- agentTaskNotifications: {
601
- ...session.agentTaskNotifications,
602
- [toolUseId]: {
603
- taskId:
604
- typeof data.task_id === 'string' && data.task_id.trim()
605
- ? data.task_id
606
- : toolUseId,
607
- toolUseId,
608
- status: taskStatus,
609
- summary:
610
- typeof data.summary === 'string' && data.summary.trim()
611
- ? data.summary
612
- : undefined,
613
- outputFile:
614
- typeof data.output_file === 'string' && data.output_file.trim()
615
- ? data.output_file
616
- : undefined,
617
- },
618
- },
619
- }))
620
- }
621
- }
622
- break
623
- case 'pong':
624
- break
625
- }
626
- },
627
- }))
628
-
629
- // ─── History mapping helpers (unchanged from original) ─────────
630
-
631
- type AssistantHistoryBlock = { type: string; text?: string; thinking?: string; name?: string; id?: string; input?: unknown }
632
- type UserHistoryBlock = { type: string; text?: string; tool_use_id?: string; content?: unknown; is_error?: boolean; source?: { data?: string }; mimeType?: string; media_type?: string; name?: string }
633
-
634
- /**
635
- * Check if text is a teammate-message (internal agent-to-agent communication).
636
- * Uses full open+close tag match to avoid false positives on user text
637
- * that merely mentions the tag name (e.g., pasting code or discussing the protocol).
638
- */
639
- function isTeammateMessage(text: string): boolean {
640
- return text.includes('<teammate-message') && text.includes('</teammate-message>')
641
- }
642
-
643
- const TEAMMATE_CONTENT_REGEX = /<teammate-message\s+teammate_id="([^"]+)"[^>]*>\n?([\s\S]*?)\n?<\/teammate-message>/g
644
-
645
- function extractVisibleTeammateMessageContents(text: string): string[] {
646
- const contents: string[] = []
647
-
648
- for (const match of text.matchAll(TEAMMATE_CONTENT_REGEX)) {
649
- const content = match[2]?.trim()
650
- if (!content) continue
651
-
652
- if (content.startsWith('{') && content.endsWith('}')) {
653
- try {
654
- const parsed = JSON.parse(content) as Record<string, unknown>
655
- if (typeof parsed.type === 'string' && AGENT_LIFECYCLE_TYPES.has(parsed.type)) {
656
- continue
657
- }
658
- } catch {
659
- // Keep non-JSON payloads that happen to look like JSON.
660
- }
661
- }
662
-
663
- contents.push(content)
664
- }
665
-
666
- return contents
667
- }
668
-
669
- type HistoryMappingOptions = {
670
- includeTeammateMessages?: boolean
671
- }
672
-
673
- /**
674
- * Reconstruct agentTaskNotifications from history.
675
- *
676
- * During a live session, background agents report completion via system_notification
677
- * events (task_notification). These are NOT persisted in JSONL history. On reload,
678
- * we reconstruct them by correlating Agent tool_use names with <teammate-message>
679
- * teammate_ids found in subsequent user messages.
680
- */
681
- export function reconstructAgentNotifications(messages: MessageEntry[]): Record<string, AgentTaskNotification> {
682
- // Step 1: Collect Agent tool_use blocks → map agent name to toolUseId
683
- const agentNameToToolUseId = new Map<string, string>()
684
-
685
- for (const msg of messages) {
686
- if ((msg.type === 'assistant' || msg.type === 'tool_use') && Array.isArray(msg.content)) {
687
- for (const block of msg.content as AssistantHistoryBlock[]) {
688
- if (block.type === 'tool_use' && block.name === 'Agent' && block.id) {
689
- const input = block.input as Record<string, unknown> | undefined
690
- const name = input?.name as string | undefined
691
- // Keep first toolUseId per name (consistent with first-wins for teammateContent)
692
- if (name && !agentNameToToolUseId.has(name)) agentNameToToolUseId.set(name, block.id)
693
- }
694
- }
695
- }
696
- }
697
-
698
- if (agentNameToToolUseId.size === 0) return {}
699
-
700
- // Step 2: Extract <teammate-message> content by teammate_id
701
- // Skip lifecycle messages (shutdown_approved, idle_notification, etc.)
702
- // which overwrite actual review content if stored later in history
703
- const teammateContent = new Map<string, string>()
704
- for (const msg of messages) {
705
- if (msg.type !== 'user') continue
706
- const text = typeof msg.content === 'string'
707
- ? msg.content
708
- : Array.isArray(msg.content)
709
- ? (msg.content as Array<{ type?: string; text?: string }>).filter((b) => b.type === 'text' && b.text).map((b) => b.text).join('\n')
710
- : ''
711
- if (!text.includes('<teammate-message')) continue
712
- for (const match of text.matchAll(TEAMMATE_CONTENT_REGEX)) {
713
- if (match[1] && match[2]) {
714
- const content = match[2].trim()
715
- // Skip lifecycle JSON messages (shutdown, idle, terminated notifications)
716
- if (content.startsWith('{') && content.endsWith('}')) {
717
- try {
718
- const parsed = JSON.parse(content) as Record<string, unknown>
719
- if (typeof parsed.type === 'string' && AGENT_LIFECYCLE_TYPES.has(parsed.type)) continue
720
- } catch { /* not JSON, keep it */ }
721
- }
722
- // Only store the first meaningful content per teammate (avoid overwrite by later lifecycle msgs)
723
- if (!teammateContent.has(match[1])) {
724
- teammateContent.set(match[1], content)
725
- }
726
- }
727
- }
728
- }
729
-
730
- // Step 3: Correlate and build notifications
731
- const notifications: Record<string, AgentTaskNotification> = {}
732
- for (const [name, toolUseId] of agentNameToToolUseId) {
733
- const content = teammateContent.get(name)
734
- if (content) {
735
- notifications[toolUseId] = {
736
- taskId: toolUseId,
737
- toolUseId,
738
- status: 'completed',
739
- summary: content,
740
- }
741
- }
742
- }
743
-
744
- return notifications
745
- }
746
-
747
- export function mapHistoryMessagesToUiMessages(
748
- messages: MessageEntry[],
749
- options?: HistoryMappingOptions,
750
- ): UIMessage[] {
751
- const includeTeammateMessages = options?.includeTeammateMessages === true
752
- const uiMessages: UIMessage[] = []
753
- for (const msg of messages) {
754
- const timestamp = new Date(msg.timestamp).getTime()
755
- if (msg.type === 'user' && typeof msg.content === 'string') {
756
- if (isTeammateMessage(msg.content)) {
757
- if (!includeTeammateMessages) continue
758
- const teammateContents = extractVisibleTeammateMessageContents(msg.content)
759
- if (teammateContents.length === 0) continue
760
- uiMessages.push({
761
- id: msg.id || nextId(),
762
- type: 'user_text',
763
- content: teammateContents.join('\n\n'),
764
- timestamp,
765
- })
766
- continue
767
- }
768
- uiMessages.push({ id: msg.id || nextId(), type: 'user_text', content: msg.content, timestamp })
769
- continue
770
- }
771
- if (msg.type === 'assistant' && typeof msg.content === 'string') {
772
- uiMessages.push({ id: msg.id || nextId(), type: 'assistant_text', content: msg.content, timestamp, model: msg.model })
773
- continue
774
- }
775
- if ((msg.type === 'assistant' || msg.type === 'tool_use') && Array.isArray(msg.content)) {
776
- for (const block of msg.content as AssistantHistoryBlock[]) {
777
- if (block.type === 'thinking' && block.thinking) uiMessages.push({ id: nextId(), type: 'thinking', content: block.thinking, timestamp })
778
- else if (block.type === 'text' && block.text) uiMessages.push({ id: nextId(), type: 'assistant_text', content: block.text, timestamp, model: msg.model })
779
- else if (block.type === 'tool_use') uiMessages.push({ id: nextId(), type: 'tool_use', toolName: block.name ?? 'unknown', toolUseId: block.id ?? '', input: block.input, timestamp, parentToolUseId: msg.parentToolUseId })
780
- }
781
- continue
782
- }
783
- if ((msg.type === 'user' || msg.type === 'tool_result') && Array.isArray(msg.content)) {
784
- const textParts: string[] = []
785
- const attachments: UIAttachment[] = []
786
- for (const block of msg.content as UserHistoryBlock[]) {
787
- if (block.type === 'text' && block.text && isTeammateMessage(block.text)) {
788
- if (!includeTeammateMessages) continue
789
- textParts.push(...extractVisibleTeammateMessageContents(block.text))
790
- } else if (block.type === 'text' && block.text) {
791
- textParts.push(block.text)
792
- }
793
- else if (block.type === 'image') attachments.push({ type: 'image', name: block.name || 'image', data: block.source?.data, mimeType: block.mimeType || block.media_type })
794
- else if (block.type === 'file') attachments.push({ type: 'file', name: block.name || 'file' })
795
- else if (block.type === 'tool_result') uiMessages.push({ id: nextId(), type: 'tool_result', toolUseId: block.tool_use_id ?? '', content: block.content, isError: !!block.is_error, timestamp, parentToolUseId: msg.parentToolUseId })
796
- }
797
- if (textParts.length > 0 || attachments.length > 0) {
798
- uiMessages.push({ id: nextId(), type: 'user_text', content: textParts.join('\n'), attachments: attachments.length > 0 ? attachments : undefined, timestamp })
799
- }
800
- }
801
- }
802
- return uiMessages
803
- }
804
-
805
- function extractLastTodoWriteFromHistory(messages: MessageEntry[]): Array<{ content: string; status: string; activeForm?: string }> | null {
806
- let foundIndex = -1
807
- let todos: Array<{ content: string; status: string; activeForm?: string }> | null = null
808
- for (let i = messages.length - 1; i >= 0; i--) {
809
- const msg = messages[i]!
810
- if ((msg.type === 'assistant' || msg.type === 'tool_use') && Array.isArray(msg.content)) {
811
- const blocks = msg.content as AssistantHistoryBlock[]
812
- for (let j = blocks.length - 1; j >= 0; j--) {
813
- const block = blocks[j]!
814
- if (block.type === 'tool_use' && block.name === 'TodoWrite') {
815
- const input = block.input as { todos?: unknown } | undefined
816
- if (input && Array.isArray(input.todos)) {
817
- todos = input.todos as Array<{ content: string; status: string; activeForm?: string }>
818
- foundIndex = i
819
- break
820
- }
821
- }
822
- }
823
- if (todos) break
824
- }
825
- }
826
- if (!todos) return null
827
- const allDone = todos.every((t) => t.status === 'completed')
828
- if (allDone) {
829
- for (let i = foundIndex + 1; i < messages.length; i++) {
830
- if (messages[i]!.type === 'user' && messages[i]!.content) return null
831
- }
832
- }
833
- return todos
834
- }
835
-
836
- const TASK_RELATED_TOOL_NAMES = new Set(['TodoWrite', 'TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList'])
837
-
838
- function hasUserMessagesAfterTaskCompletion(messages: MessageEntry[]): boolean {
839
- let lastTaskIndex = -1
840
- for (let i = messages.length - 1; i >= 0; i--) {
841
- const msg = messages[i]!
842
- if ((msg.type === 'assistant' || msg.type === 'tool_use') && Array.isArray(msg.content)) {
843
- const blocks = msg.content as AssistantHistoryBlock[]
844
- if (blocks.some((b) => b.type === 'tool_use' && TASK_RELATED_TOOL_NAMES.has(b.name ?? ''))) { lastTaskIndex = i; break }
845
- }
846
- }
847
- if (lastTaskIndex < 0) return false
848
- for (let i = lastTaskIndex + 1; i < messages.length; i++) { if (messages[i]!.type === 'user') return true }
849
- return false
850
- }