@vibe-forge/client 0.10.1 → 0.11.1

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 (133) hide show
  1. package/dist/assets/{arc-C1rWFTer.js → arc-CSepokz3.js} +1 -1
  2. package/dist/assets/{blockDiagram-c4efeb88-DlZ9x70F.js → blockDiagram-c4efeb88-D0ARcoNf.js} +1 -1
  3. package/dist/assets/{c4Diagram-c83219d4-BKKxi__y.js → c4Diagram-c83219d4-BysYF9kP.js} +1 -1
  4. package/dist/assets/channel-CeKPk6Nd.js +1 -0
  5. package/dist/assets/{classDiagram-beda092f-CVGPySZq.js → classDiagram-beda092f-BG1GhIOL.js} +1 -1
  6. package/dist/assets/{classDiagram-v2-2358418a-7kp8GVVj.js → classDiagram-v2-2358418a-Dd08uGSH.js} +1 -1
  7. package/dist/assets/clone-CrkD2PuD.js +1 -0
  8. package/dist/assets/{createText-1719965b-Dykv8kT9.js → createText-1719965b-CigPEIEn.js} +1 -1
  9. package/dist/assets/{cssMode-B59COYVW.js → cssMode-MjflyEfm.js} +1 -1
  10. package/dist/assets/{edges-96097737-CkZ1ZBro.js → edges-96097737-DuTBJJRv.js} +1 -1
  11. package/dist/assets/{erDiagram-0228fc6a-281ADcRp.js → erDiagram-0228fc6a-Cp1bL7Y7.js} +1 -1
  12. package/dist/assets/{flowDb-c6c81e3f-BQjX_flP.js → flowDb-c6c81e3f-BfKbhiq5.js} +1 -1
  13. package/dist/assets/{flowDiagram-50d868cf-DMHZTjES.js → flowDiagram-50d868cf-m7gGc3PK.js} +1 -1
  14. package/dist/assets/flowDiagram-v2-4f6560a1-4ZU4bdp1.js +1 -0
  15. package/dist/assets/{flowchart-elk-definition-6af322e1-CI3yz4z8.js → flowchart-elk-definition-6af322e1-EVeTDRRK.js} +1 -1
  16. package/dist/assets/{freemarker2-DWnWjibn.js → freemarker2-Bb3-QAIN.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-B3IING9L.js → ganttDiagram-a2739b55-DslB2U0R.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-CnArIr_T.js → gitGraphDiagram-82fe8481-C-KFWMXL.js} +1 -1
  19. package/dist/assets/{graph-BZ1F0Yve.js → graph-CukaUc0o.js} +1 -1
  20. package/dist/assets/{handlebars-C1QH9qTz.js → handlebars-C4le-2Y6.js} +1 -1
  21. package/dist/assets/{html-D1NkqHjC.js → html-CjNiRs5S.js} +1 -1
  22. package/dist/assets/{htmlMode-DAZCE_rA.js → htmlMode-B73_3-We.js} +1 -1
  23. package/dist/assets/{index-5325376f-Da9zSHjA.js → index-5325376f-CVISZFPw.js} +1 -1
  24. package/dist/assets/{index-C0vjF3D0.js → index-BZosmb5_.js} +336 -336
  25. package/dist/assets/index-C1oh0w9H.css +32 -0
  26. package/dist/assets/{infoDiagram-8eee0895-DYbFvRM7.js → infoDiagram-8eee0895-DoirLE1K.js} +1 -1
  27. package/dist/assets/{javascript-CoMjGRHa.js → javascript-BDjnqJFP.js} +1 -1
  28. package/dist/assets/{journeyDiagram-c64418c1-Boebox0b.js → journeyDiagram-c64418c1-Ckn-p2CM.js} +1 -1
  29. package/dist/assets/{jsonMode-D__gAvuz.js → jsonMode-C-ftOc5j.js} +1 -1
  30. package/dist/assets/{layout-CTcHNbHp.js → layout-Z7yUG7hB.js} +1 -1
  31. package/dist/assets/{line-4AwinCz2.js → line-DPG_cfAy.js} +1 -1
  32. package/dist/assets/{linear-CeSMLzJW.js → linear--GSeVfMi.js} +1 -1
  33. package/dist/assets/{liquid-DZF6egdE.js → liquid-COiLZ9py.js} +1 -1
  34. package/dist/assets/{lspLanguageFeatures-6K4lv5S2.js → lspLanguageFeatures-DGmhryFq.js} +1 -1
  35. package/dist/assets/{mdx-Cnt4ka6w.js → mdx-BpL87Gej.js} +1 -1
  36. package/dist/assets/{mermaid.core-B0yG5s4D.js → mermaid.core-Cg1CCDo6.js} +4 -4
  37. package/dist/assets/{mindmap-definition-8da855dc-KJEvXMKj.js → mindmap-definition-8da855dc-CKDof1lD.js} +1 -1
  38. package/dist/assets/{pieDiagram-a8764435-17nFAXPJ.js → pieDiagram-a8764435-DwvCaZVE.js} +1 -1
  39. package/dist/assets/{python-DA3TtjDv.js → python-63dBmWV_.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-1e28029f-Dt4vubi-.js → quadrantDiagram-1e28029f-CkzYBQpy.js} +1 -1
  41. package/dist/assets/{razor-CWDJgvX_.js → razor-C50tBqEZ.js} +1 -1
  42. package/dist/assets/{requirementDiagram-08caed73-H6aDyDK-.js → requirementDiagram-08caed73-Brgdjqf4.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-a04cb91d-DxsVtbjI.js → sankeyDiagram-a04cb91d-CGkYexrs.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-c5b8d532-BHa148XJ.js → sequenceDiagram-c5b8d532-D0wE-_J8.js} +1 -1
  45. package/dist/assets/{stateDiagram-1ecb1508-DgwBm8LO.js → stateDiagram-1ecb1508-BYb3NCXZ.js} +1 -1
  46. package/dist/assets/{stateDiagram-v2-c2b004d7-BK7IQLVc.js → stateDiagram-v2-c2b004d7-DrPqi4Pt.js} +1 -1
  47. package/dist/assets/{styles-b4e223ce-DzW27Bc-.js → styles-b4e223ce-DD66TIO4.js} +1 -1
  48. package/dist/assets/{styles-ca3715f6-Dex2GiLT.js → styles-ca3715f6-iy02LHIV.js} +1 -1
  49. package/dist/assets/{styles-d45a18b0-B6fGtDKS.js → styles-d45a18b0-BgqAgJyW.js} +1 -1
  50. package/dist/assets/{svgDrawCommon-b86b1483-B4HYgfV5.js → svgDrawCommon-b86b1483-CDq7ugnw.js} +1 -1
  51. package/dist/assets/{timeline-definition-faaaa080--QSbWb25.js → timeline-definition-faaaa080-DzcLLjK0.js} +1 -1
  52. package/dist/assets/{tsMode-ZM7ocZCH.js → tsMode-BFRFI4ct.js} +1 -1
  53. package/dist/assets/{typescript-CKWDmBCc.js → typescript-CBZQRAPv.js} +1 -1
  54. package/dist/assets/{xml-DuEUAzPi.js → xml-BpWm6upt.js} +1 -1
  55. package/dist/assets/{xychartDiagram-f5964ef8-D09Zkv2K.js → xychartDiagram-f5964ef8-zBN8FmLQ.js} +1 -1
  56. package/dist/assets/{yaml-DL7QPRYk.js → yaml-CqbJPiIP.js} +1 -1
  57. package/dist/index.html +2 -2
  58. package/package.json +10 -10
  59. package/src/api/git.ts +78 -0
  60. package/src/api.ts +24 -0
  61. package/src/components/chat/ChatHeader.tsx +4 -0
  62. package/src/components/chat/ChatHistoryView.tsx +22 -13
  63. package/src/components/chat/git-controls/BranchSwitcherDropdown.tsx +157 -0
  64. package/src/components/chat/git-controls/ChatGitControls.scss +616 -0
  65. package/src/components/chat/git-controls/ChatGitControls.tsx +151 -0
  66. package/src/components/chat/git-controls/GitCommitModal.tsx +199 -0
  67. package/src/components/chat/git-controls/GitCommitModalParts.tsx +151 -0
  68. package/src/components/chat/git-controls/GitOperationsDropdown.tsx +123 -0
  69. package/src/components/chat/git-controls/GitPushModal.tsx +106 -0
  70. package/src/components/chat/git-controls/GitWorktreeDropdown.tsx +68 -0
  71. package/src/components/chat/git-controls/git-branch-utils.ts +88 -0
  72. package/src/components/chat/git-controls/git-commit-utils.ts +79 -0
  73. package/src/components/chat/git-controls/git-mutation-utils.ts +69 -0
  74. package/src/components/chat/git-controls/git-operation-utils.ts +98 -0
  75. package/src/components/chat/git-controls/git-worktree-utils.ts +49 -0
  76. package/src/components/chat/git-controls/use-chat-git-commit.ts +185 -0
  77. package/src/components/chat/git-controls/use-chat-git-controls.ts +200 -0
  78. package/src/components/chat/git-controls/use-chat-git-push-state.ts +19 -0
  79. package/src/components/chat/git-controls/use-chat-git-worktrees.ts +39 -0
  80. package/src/components/chat/messages/MessageStatusNotice.scss +163 -0
  81. package/src/components/chat/messages/MessageStatusNotice.tsx +48 -0
  82. package/src/components/chat/messages/build-chat-history-status-notices.ts +138 -0
  83. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +0 -24
  84. package/src/components/chat/sender/@core/build-sender-controller-result.ts +0 -6
  85. package/src/components/chat/sender/@hooks/use-sender-controller.ts +0 -2
  86. package/src/components/chat/sender/@types/sender-props.ts +0 -3
  87. package/src/components/chat/sender/Sender.scss +0 -58
  88. package/src/components/chat/sender/Sender.tsx +0 -2
  89. package/src/components/chat/tools/DefaultTool.tsx +84 -208
  90. package/src/components/chat/tools/adapter-claude/ClaudeEditDiff.tsx +30 -0
  91. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.scss +128 -0
  92. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +119 -0
  93. package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +109 -0
  94. package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +83 -0
  95. package/src/components/chat/tools/adapter-claude/claude-tool-operation-builders.ts +135 -0
  96. package/src/components/chat/tools/adapter-claude/claude-tool-presentation.ts +61 -0
  97. package/src/components/chat/tools/adapter-claude/claude-tool-shared.ts +185 -0
  98. package/src/components/chat/tools/adapter-claude/claude-tool-summary.ts +76 -0
  99. package/src/components/chat/tools/adapter-claude/claude-tool-system-builders.ts +125 -0
  100. package/src/components/chat/tools/adapter-claude/claude-tool-task-builders.ts +148 -0
  101. package/src/components/chat/tools/adapter-claude/index.ts +24 -15
  102. package/src/components/chat/tools/core/ToolCallBox.scss +362 -36
  103. package/src/components/chat/tools/core/ToolCallBox.tsx +35 -13
  104. package/src/components/chat/tools/core/ToolDiffViewer.scss +138 -0
  105. package/src/components/chat/tools/core/ToolDiffViewer.tsx +180 -0
  106. package/src/components/chat/tools/core/ToolGroup.scss +52 -74
  107. package/src/components/chat/tools/core/ToolGroup.tsx +25 -40
  108. package/src/components/chat/tools/core/ToolRenderer.tsx +3 -3
  109. package/src/components/chat/tools/core/ToolResultContent.tsx +66 -0
  110. package/src/components/chat/tools/core/ToolSummaryHeader.tsx +67 -0
  111. package/src/components/chat/tools/core/generic-tool-presentation.ts +661 -0
  112. package/src/components/chat/tools/core/tool-content-presence.ts +57 -0
  113. package/src/components/chat/tools/core/tool-display.ts +203 -0
  114. package/src/components/chat/tools/core/tool-field-sections.tsx +132 -0
  115. package/src/components/chat/tools/core/tool-result-content-utils.ts +171 -0
  116. package/src/components/chat/tools/core/tool-summary.ts +206 -0
  117. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +59 -53
  118. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +26 -9
  119. package/src/components/chat/tools/task/ListTasksTool.tsx +22 -9
  120. package/src/components/chat/tools/task/StartTasksTool.tsx +22 -9
  121. package/src/hooks/chat/interaction-state.ts +29 -9
  122. package/src/hooks/chat/session-view-cache.ts +80 -0
  123. package/src/hooks/chat/use-chat-scroll.ts +2 -2
  124. package/src/hooks/chat/use-chat-session-messages.ts +139 -39
  125. package/src/hooks/chat/use-chat-session.ts +2 -2
  126. package/src/resources/locales/en.json +149 -0
  127. package/src/resources/locales/zh.json +149 -0
  128. package/src/routes/ChatRoute.tsx +24 -27
  129. package/src/utils/strip-ansi.ts +26 -0
  130. package/dist/assets/channel-F1aqMANO.js +0 -1
  131. package/dist/assets/clone-B-GCuXNo.js +0 -1
  132. package/dist/assets/flowDiagram-v2-4f6560a1-C5FzdVl1.js +0 -1
  133. package/dist/assets/index-vzEbM21t.css +0 -32
@@ -1,18 +1,27 @@
1
1
  import { useCallback, useEffect, useRef, useState } from 'react'
2
+ import type { SetStateAction } from 'react'
2
3
  import { useTranslation } from 'react-i18next'
3
4
  import { useSWRConfig } from 'swr'
4
5
 
5
- import { getSessionMessages } from '#~/api.js'
6
- import { connectionManager } from '#~/connectionManager.js'
7
6
  import type { AskUserQuestionParams, ChatMessage, Session, WSEvent } from '@vibe-forge/core'
8
7
  import type { SessionInfo } from '@vibe-forge/types'
9
- import type { ChatErrorBannerState } from './interaction-state'
8
+
9
+ import { getSessionMessages } from '#~/api.js'
10
+ import { connectionManager } from '#~/connectionManager.js'
11
+
12
+ import type { ChatErrorState, InteractionRequestState } from './interaction-state'
10
13
  import {
11
14
  applyInteractionStateEvent,
12
15
  findLatestFatalError,
13
16
  getFatalSessionError,
14
17
  restoreInteractionStateFromHistory
15
18
  } from './interaction-state'
19
+ import {
20
+ deleteChatSessionViewSnapshot,
21
+ restoreChatSessionViewSnapshot,
22
+ setChatSessionViewSnapshot
23
+ } from './session-view-cache'
24
+ import type { ChatSessionViewSnapshot } from './session-view-cache'
16
25
  import type { ChatEffort } from './use-chat-effort'
17
26
  import type { PermissionMode } from './use-chat-permission-mode'
18
27
 
@@ -60,10 +69,10 @@ export function useChatSessionMessages({
60
69
  }) {
61
70
  const { t } = useTranslation()
62
71
  const { mutate } = useSWRConfig()
63
- const [messages, setMessages] = useState<ChatMessage[]>([])
72
+ const [messagesState, setMessagesState] = useState<ChatMessage[]>([])
64
73
  const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
65
74
  const [isReady, setIsReady] = useState(false)
66
- const [errorBanner, setErrorBanner] = useState<ChatErrorBannerState | null>(null)
75
+ const [errorState, setErrorState] = useState<ChatErrorState | null>(null)
67
76
  const [retryCount, setRetryCount] = useState(0)
68
77
  const isInitialLoadRef = useRef<boolean>(true)
69
78
  const lastConnectedModelRef = useRef<string | undefined>(undefined)
@@ -72,13 +81,50 @@ export function useChatSessionMessages({
72
81
  const lastConnectedAdapterRef = useRef<string | undefined>(undefined)
73
82
  const lastObservedSessionStatusRef = useRef<Session['status'] | undefined>(session?.status)
74
83
  const expectedCloseRef = useRef(false)
75
- const interactionRequestRef = useRef<{ id: string; payload: AskUserQuestionParams } | null>(null)
84
+ const interactionRequestRef = useRef<InteractionRequestState | null>(null)
76
85
  const activeSessionIdRef = useRef<string | undefined>(session?.id)
77
86
  const historyRequestSeqRef = useRef(0)
78
87
  const reconcileTimersRef = useRef<Array<ReturnType<typeof setTimeout>>>([])
88
+ const sessionViewCacheRef = useRef(new Map<string, ChatSessionViewSnapshot>())
79
89
 
80
90
  activeSessionIdRef.current = session?.id
81
91
 
92
+ const updateSessionViewCache = useCallback((
93
+ sessionId: string,
94
+ patch: Partial<{
95
+ messages: ChatMessage[]
96
+ sessionInfo: SessionInfo | null
97
+ errorState: ChatErrorState | null
98
+ interactionRequest: InteractionRequestState | null
99
+ isHydrated: boolean
100
+ }>
101
+ ) => {
102
+ return setChatSessionViewSnapshot(sessionViewCacheRef.current, sessionId, patch)
103
+ }, [])
104
+
105
+ const removeSessionViewCache = useCallback((sessionId: string) => {
106
+ deleteChatSessionViewSnapshot(sessionViewCacheRef.current, sessionId)
107
+ }, [])
108
+
109
+ const setMessages = useCallback((value: SetStateAction<ChatMessage[]>) => {
110
+ setMessagesState((current) => {
111
+ const next = typeof value === 'function'
112
+ ? value(current)
113
+ : value
114
+ const sessionId = activeSessionIdRef.current
115
+
116
+ if (sessionId != null && sessionId !== '') {
117
+ const currentSnapshot = sessionViewCacheRef.current.get(sessionId)
118
+ updateSessionViewCache(sessionId, {
119
+ messages: next,
120
+ isHydrated: currentSnapshot?.isHydrated === true
121
+ })
122
+ }
123
+
124
+ return next
125
+ })
126
+ }, [updateSessionViewCache])
127
+
82
128
  const clearScheduledReconciles = useCallback(() => {
83
129
  for (const timer of reconcileTimersRef.current) {
84
130
  clearTimeout(timer)
@@ -121,17 +167,17 @@ export function useChatSessionMessages({
121
167
  res.session?.status
122
168
  )
123
169
  const latestFatalError = findLatestFatalError(events)
170
+ const nextErrorState = restoredInteraction == null && res.session?.status === 'failed' && latestFatalError != null
171
+ ? {
172
+ kind: 'session' as const,
173
+ message: latestFatalError.message,
174
+ code: latestFatalError.code
175
+ }
176
+ : null
124
177
 
125
178
  interactionRequestRef.current = restoredInteraction
126
179
  setInteractionRequest(restoredInteraction)
127
- setErrorBanner(
128
- restoredInteraction == null && res.session?.status === 'failed' && latestFatalError != null
129
- ? {
130
- kind: 'session',
131
- message: latestFatalError.message
132
- }
133
- : null
134
- )
180
+ setErrorState(nextErrorState)
135
181
 
136
182
  for (const data of events) {
137
183
  currentMessages = applyMessageEvent(currentMessages, data)
@@ -143,6 +189,14 @@ export function useChatSessionMessages({
143
189
  }
144
190
  }
145
191
 
192
+ updateSessionViewCache(sessionId, {
193
+ messages: currentMessages,
194
+ sessionInfo: currentSessionInfo,
195
+ errorState: nextErrorState,
196
+ interactionRequest: restoredInteraction,
197
+ isHydrated: true
198
+ })
199
+
146
200
  setMessages(currentMessages)
147
201
  setSessionInfo(currentSessionInfo)
148
202
 
@@ -158,7 +212,7 @@ export function useChatSessionMessages({
158
212
  } catch (err) {
159
213
  console.error('Failed to fetch history messages:', err)
160
214
  }
161
- }, [mutate, setInteractionRequest])
215
+ }, [mutate, setInteractionRequest, setMessages, updateSessionViewCache])
162
216
 
163
217
  const reconcileAfterInteraction = useCallback(() => {
164
218
  clearScheduledReconciles()
@@ -174,22 +228,21 @@ export function useChatSessionMessages({
174
228
  const retryConnection = useCallback(() => {
175
229
  if (session?.id == null || session.id === '') return
176
230
  expectedCloseRef.current = true
177
- setErrorBanner(null)
231
+ setErrorState(null)
232
+ updateSessionViewCache(session.id, { errorState: null })
178
233
  connectionManager.close(session.id)
179
234
  setRetryCount((count) => count + 1)
180
- }, [session?.id])
235
+ }, [session?.id, updateSessionViewCache])
181
236
 
182
237
  useEffect(() => {
183
- setMessages([])
184
- setSessionInfo(null)
185
- setIsReady(false)
186
- setErrorBanner(null)
187
- setInteractionRequest(null)
188
- interactionRequestRef.current = null
189
- isInitialLoadRef.current = true
190
-
191
238
  if (session?.id == null || session.id === '') {
239
+ setMessagesState([])
240
+ setSessionInfo(null)
192
241
  setIsReady(true)
242
+ setErrorState(null)
243
+ setInteractionRequest(null)
244
+ interactionRequestRef.current = null
245
+ isInitialLoadRef.current = true
193
246
  lastConnectedModelRef.current = undefined
194
247
  lastConnectedEffortRef.current = undefined
195
248
  lastConnectedPermissionModeRef.current = undefined
@@ -198,6 +251,16 @@ export function useChatSessionMessages({
198
251
  return
199
252
  }
200
253
 
254
+ const restoredState = restoreChatSessionViewSnapshot(sessionViewCacheRef.current.get(session.id))
255
+
256
+ setMessagesState(restoredState.messages)
257
+ setSessionInfo(restoredState.sessionInfo)
258
+ setErrorState(restoredState.errorState)
259
+ setInteractionRequest(restoredState.interactionRequest)
260
+ interactionRequestRef.current = restoredState.interactionRequest
261
+ setIsReady(restoredState.isReady)
262
+ isInitialLoadRef.current = !restoredState.isReady
263
+
201
264
  void refreshHistory()
202
265
 
203
266
  return () => {
@@ -249,7 +312,7 @@ export function useChatSessionMessages({
249
312
  session?.status !== 'running'
250
313
  if (modelChanged || effortChanged || permissionModeChanged || adapterChanged) {
251
314
  expectedCloseRef.current = true
252
- setErrorBanner(null)
315
+ setErrorState(null)
253
316
  connectionManager.send(session.id, { type: 'terminate_session' })
254
317
  connectionManager.close(session.id)
255
318
  }
@@ -278,7 +341,13 @@ export function useChatSessionMessages({
278
341
  cleanup = connectionManager.connect(session.id, {
279
342
  onOpen() {
280
343
  expectedCloseRef.current = false
281
- setErrorBanner((current) => current?.kind === 'session' ? current : null)
344
+ setErrorState((current) => {
345
+ const next = current?.kind === 'session' ? current : null
346
+ updateSessionViewCache(session.id, {
347
+ errorState: next
348
+ })
349
+ return next
350
+ })
282
351
  },
283
352
  onMessage(data: WSEvent) {
284
353
  if (isDisposed) return
@@ -286,8 +355,14 @@ export function useChatSessionMessages({
286
355
  if (nextInteraction !== interactionRequestRef.current) {
287
356
  interactionRequestRef.current = nextInteraction
288
357
  setInteractionRequest(nextInteraction)
358
+ updateSessionViewCache(session.id, {
359
+ interactionRequest: nextInteraction
360
+ })
289
361
  if (nextInteraction != null) {
290
- setErrorBanner(null)
362
+ setErrorState(null)
363
+ updateSessionViewCache(session.id, {
364
+ errorState: null
365
+ })
291
366
  }
292
367
  }
293
368
  if (data.type === 'interaction_response') {
@@ -297,9 +372,14 @@ export function useChatSessionMessages({
297
372
  if (data.type === 'error') {
298
373
  const fatalError = getFatalSessionError(data)
299
374
  if (fatalError != null) {
300
- setErrorBanner({
375
+ const nextErrorState = {
301
376
  kind: 'session',
302
- message: fatalError.message
377
+ message: fatalError.message,
378
+ code: fatalError.code
379
+ } satisfies ChatErrorState
380
+ setErrorState(nextErrorState)
381
+ updateSessionViewCache(session.id, {
382
+ errorState: nextErrorState
303
383
  })
304
384
  }
305
385
  return
@@ -311,6 +391,7 @@ export function useChatSessionMessages({
311
391
  const updatedSession = data.session as Session | { id: string; isDeleted: boolean }
312
392
 
313
393
  if ('isDeleted' in updatedSession && updatedSession.isDeleted) {
394
+ removeSessionViewCache(updatedSession.id)
314
395
  return {
315
396
  ...prev,
316
397
  sessions: prev.sessions.filter((s: Session) => s.id !== updatedSession.id)
@@ -343,6 +424,9 @@ export function useChatSessionMessages({
343
424
  void mutate('/api/sessions')
344
425
  } else {
345
426
  setSessionInfo(data.info ?? null)
427
+ updateSessionViewCache(session.id, {
428
+ sessionInfo: data.info ?? null
429
+ })
346
430
  if (isInitialLoadRef.current) {
347
431
  setTimeout(() => {
348
432
  if (isDisposed) return
@@ -362,14 +446,23 @@ export function useChatSessionMessages({
362
446
  }
363
447
 
364
448
  if (data.type === 'interaction_request') {
449
+ interactionRequestRef.current = data
365
450
  setInteractionRequest(data)
451
+ updateSessionViewCache(session.id, {
452
+ interactionRequest: data
453
+ })
366
454
  }
367
455
  },
368
456
  onError() {
369
457
  if (isDisposed) return
370
- setErrorBanner({
458
+ const nextErrorState = {
371
459
  kind: 'connection',
372
- message: t('chat.connectionError')
460
+ message: t('chat.connectionError'),
461
+ reason: 'error'
462
+ } satisfies ChatErrorState
463
+ setErrorState(nextErrorState)
464
+ updateSessionViewCache(session.id, {
465
+ errorState: nextErrorState
373
466
  })
374
467
  },
375
468
  onClose() {
@@ -378,12 +471,17 @@ export function useChatSessionMessages({
378
471
  expectedCloseRef.current = false
379
472
  return
380
473
  }
381
- setErrorBanner((current) =>
382
- current ?? {
474
+ setErrorState((current) => {
475
+ const next = current ?? {
383
476
  kind: 'connection',
384
- message: t('chat.connectionClosed')
477
+ message: t('chat.connectionClosed'),
478
+ reason: 'closed'
385
479
  }
386
- )
480
+ updateSessionViewCache(session.id, {
481
+ errorState: next
482
+ })
483
+ return next
484
+ })
387
485
  }
388
486
  }, Object.keys(connectionParams).length > 0 ? connectionParams : undefined)
389
487
  }, (modelChanged || effortChanged || permissionModeChanged || adapterChanged) ? 200 : 100)
@@ -406,15 +504,17 @@ export function useChatSessionMessages({
406
504
  session?.id,
407
505
  session?.status,
408
506
  setInteractionRequest,
409
- t
507
+ t,
508
+ removeSessionViewCache,
509
+ updateSessionViewCache
410
510
  ])
411
511
 
412
512
  return {
413
- messages,
513
+ messages: messagesState,
414
514
  setMessages,
415
515
  sessionInfo,
416
516
  isReady,
417
- errorBanner,
517
+ errorState,
418
518
  retryConnection,
419
519
  reconcileAfterInteraction
420
520
  }
@@ -45,7 +45,7 @@ export function useChatSession({
45
45
  setMessages,
46
46
  sessionInfo,
47
47
  isReady,
48
- errorBanner,
48
+ errorState,
49
49
  retryConnection,
50
50
  reconcileAfterInteraction
51
51
  } = useChatSessionMessages({
@@ -112,7 +112,7 @@ export function useChatSession({
112
112
  sessionInfo,
113
113
  interactionRequest,
114
114
  isReady,
115
- errorBanner,
115
+ errorState,
116
116
  retryConnection,
117
117
  isThinking,
118
118
  activeView,
@@ -24,6 +24,7 @@
24
24
  "batchRestoreSuccess": "Batch restored successfully",
25
25
  "batchRestoreFailed": "Failed to restore some sessions",
26
26
  "cancel": "Cancel",
27
+ "continue": "Continue",
27
28
  "confirm": "Confirm",
28
29
  "confirmAction": "Confirm {{action}}",
29
30
  "startNewChat": "No active sessions found. Start a new chat!",
@@ -324,11 +325,22 @@
324
325
  "modelSearchPlaceholder": "Search models or services",
325
326
  "modelUnavailable": "No models available",
326
327
  "modelConfigRequired": "Add a model service in config before starting a session",
328
+ "modelConfigRequiredTitle": "Model setup required",
329
+ "modelConfigRequiredHelp": "Add at least one model service, then retry sending from the composer.",
327
330
  "connectionErrorTitle": "Connection error",
331
+ "connectionClosedTitle": "Connection closed",
328
332
  "sessionErrorTitle": "Task failed",
329
333
  "connectionError": "WebSocket connection failed. Check the server and try again.",
330
334
  "connectionClosed": "WebSocket connection closed. Try reconnecting.",
335
+ "connectionErrorHelp": "Streaming updates stopped before the session could recover. Retry to resubscribe to the running session.",
336
+ "connectionClosedHelp": "The current stream has ended unexpectedly. Retry to reconnect and continue receiving new messages.",
337
+ "sessionErrorHelp": "Check the latest tool output or terminal logs, then continue from the most recent user message if needed.",
338
+ "sessionErrorCode": "Error code: {{code}}",
331
339
  "retryConnection": "Retry",
340
+ "debugMockLabel": "Debug Preview",
341
+ "debugMockConnectionErrorMessage": "Unable to reach the session stream. The websocket handshake timed out before any updates arrived.",
342
+ "debugMockConnectionClosedMessage": "The connection dropped after the assistant started responding. No further tokens will arrive until you reconnect.",
343
+ "debugMockSessionErrorMessage": "The runtime hit its session timeout while waiting for a downstream tool result and stopped this turn.",
332
344
  "permissionRequestBadge": "Permission request",
333
345
  "permissionRequestTitleWithTool": "Requesting permission to use 【{{tool}}】. Choose how to proceed.",
334
346
  "permissionSubject": "Scope",
@@ -427,6 +439,73 @@
427
439
  "viewTimeline": "Timeline",
428
440
  "viewTerminal": "Terminal",
429
441
  "viewSettings": "Settings",
442
+ "gitBranchSwitcher": "Switch branch",
443
+ "gitOperations": "Git actions",
444
+ "gitWorktree": "Worktree",
445
+ "gitDetachedHead": "DETACHED",
446
+ "gitSearchBranches": "Search local or remote branches",
447
+ "gitBranchesLocal": "Local branches",
448
+ "gitBranchesWorktrees": "Other worktrees",
449
+ "gitBranchesRemote": "Remote branches",
450
+ "gitBranchCheckedOutInOtherWorktree": "Checked out in {{path}}",
451
+ "gitRemoteBranchCheckedOutInOtherWorktree": "Local branch already checked out in {{path}}",
452
+ "gitNoBranches": "No matching branches",
453
+ "gitCreateBranch": "Create branch",
454
+ "gitCreateBranchWithName": "Create branch {{branch}}",
455
+ "gitSwitchBranchSuccess": "Switched to {{branch}}",
456
+ "gitCreateBranchSuccess": "Created and switched to {{branch}}",
457
+ "gitCommitShort": "Commit",
458
+ "gitCommitAllChanges": "Commit all changes",
459
+ "gitCommitDialogTitle": "Commit all changes",
460
+ "gitCommitDescription": "This stages all current repository changes before committing.",
461
+ "gitCommitPanelTitle": "Commit changes",
462
+ "gitCommitPanelBranch": "Branch",
463
+ "gitCommitPanelChanges": "Changes",
464
+ "gitChangedFilesCount": "{{count}} files",
465
+ "gitCommitIncludeUnstagedChanges": "Include unstaged changes",
466
+ "gitCommitIncludeUnstagedChangesDescription": "Include unstaged and untracked files.",
467
+ "gitCommitOnlyStagedChangesDescription": "Commit staged changes only.",
468
+ "gitCommitSkipHooks": "Skip Git hooks",
469
+ "gitCommitSkipHooksDescription": "Skip pre-commit and commit-msg hooks.",
470
+ "gitCommitAmend": "Amend latest commit",
471
+ "gitCommitAmendDescription": "Fold into the latest commit. Current: {{subject}}",
472
+ "gitCommitAmendUnavailableDescription": "There is no existing commit to amend in this repository.",
473
+ "gitCommitMessageLabel": "Commit message",
474
+ "gitCommitMessageOptional": "Optional",
475
+ "gitCommitMessagePlaceholder": "Enter a commit message",
476
+ "gitCommitMessagePlaceholderAmend": "Leave blank to keep the latest commit message",
477
+ "gitCommitMessageAmendHint": "Leave it blank to reuse the latest commit message.",
478
+ "gitCommitMessageRequired": "Enter a commit message",
479
+ "gitCommitSuccess": "Commit created",
480
+ "gitCommitNoChanges": "There are no changes to commit",
481
+ "gitCommitNoStagedChanges": "There are no staged changes to commit",
482
+ "gitCommitNextStep": "Next step",
483
+ "gitCommitAndPush": "Commit and push",
484
+ "gitCommitAndPushSuccess": "Commit and push completed",
485
+ "gitCommitPushFailedAfterCommit": "Commit completed, but push failed: {{error}}",
486
+ "gitAmendSuccess": "Amend completed",
487
+ "gitAmendAndPushSuccess": "Amend and push completed",
488
+ "gitAmendUnavailable": "There is no commit available to amend",
489
+ "gitForcePush": "Force push",
490
+ "gitForcePushDescription": "Use force-with-lease for the remote branch.",
491
+ "gitForcePushHint": "Only replaces it when the remote has no newer commits.",
492
+ "gitPushNeedsSyncOrForce": "This branch is behind upstream. Sync first or enable force push.",
493
+ "gitPushShort": "Push",
494
+ "gitPush": "Push current branch",
495
+ "gitPushPanelTitle": "Push changes",
496
+ "gitPushPanelUpstream": "Upstream",
497
+ "gitPushPanelUpstreamHint": "The first push sets the upstream automatically.",
498
+ "gitForcePushSuccess": "Force push completed",
499
+ "gitPushSuccess": "Push completed",
500
+ "gitSyncShort": "Sync",
501
+ "gitSync": "Sync current branch",
502
+ "gitSyncSuccess": "Sync completed",
503
+ "gitStatusDirty": "Uncommitted changes",
504
+ "gitStatusClean": "Working tree clean",
505
+ "gitUpstreamStatus": "Ahead {{ahead}} / behind {{behind}}",
506
+ "gitNoUpstream": "No upstream configured",
507
+ "gitLocalBranch": "Local",
508
+ "gitRemoteBranch": "Remote · {{remote}}",
430
509
  "deleteSessionTitle": "Delete session",
431
510
  "deleteSessionDesc": "This will permanently remove the session and all messages. Proceed carefully.",
432
511
  "timelineEmpty": "No events yet",
@@ -547,9 +626,30 @@
547
626
  "startTasksFailed": "Failed",
548
627
  "startTasksLogs": "Task Logs",
549
628
  "task": "Task",
629
+ "taskCount": "{{count}} tasks",
550
630
  "taskExitCode": "Exit: {{code}}",
551
631
  "writeSuccess": "Write Success",
552
632
  "writeFailed": "Write Failed",
633
+ "askUserQuestion": "Ask User Question",
634
+ "globTool": "Glob",
635
+ "grepTool": "Grep",
636
+ "editTool": "Edit File",
637
+ "lsTool": "List Directory",
638
+ "notebookEdit": "Notebook Edit",
639
+ "webFetch": "Web Fetch",
640
+ "webSearch": "Web Search",
641
+ "booleanOn": "On",
642
+ "booleanOff": "Off",
643
+ "diffSplit": "Split",
644
+ "diffInline": "Inline",
645
+ "skill": "Skill",
646
+ "enterPlanMode": "Enter Plan Mode",
647
+ "exitPlanMode": "Exit Plan Mode",
648
+ "taskCreate": "Create Task",
649
+ "taskGet": "Get Task",
650
+ "taskUpdate": "Update Task",
651
+ "taskList": "List Tasks",
652
+ "claudeTask": "Claude Task",
553
653
  "todo": "Task Planning",
554
654
  "call": "call",
555
655
  "reading": "Reading file...",
@@ -559,6 +659,55 @@
559
659
  "runInBackground": "run in background",
560
660
  "dangerouslyDisableSandbox": "sandbox disabled",
561
661
  "viewCommand": "View command",
662
+ "noParameters": "No parameters",
663
+ "groupSummaryCount": "{{name}} {{count}}x",
664
+ "groupSummaryMoreCount": "+{{count}}x",
665
+ "singleSelect": "single",
666
+ "multiSelect": "multi",
667
+ "fields": {
668
+ "activeForm": "Active Form",
669
+ "addBlockedBy": "Add Blocked By",
670
+ "addBlocks": "Add Blocks",
671
+ "allowedDomains": "Allowed Domains",
672
+ "allowedPrompts": "Allowed Prompts",
673
+ "answers": "Answers",
674
+ "args": "Args",
675
+ "blockedDomains": "Blocked Domains",
676
+ "cellId": "Cell ID",
677
+ "cellType": "Cell Type",
678
+ "command": "Command",
679
+ "content": "Content",
680
+ "description": "Description",
681
+ "disableSandbox": "Disable Sandbox",
682
+ "details": "Details",
683
+ "editMode": "Edit Mode",
684
+ "glob": "Glob",
685
+ "ignore": "Ignore",
686
+ "limit": "Limit",
687
+ "maxTurns": "Max Turns",
688
+ "metadata": "Metadata",
689
+ "model": "Model",
690
+ "mode": "Mode",
691
+ "newSource": "New Source",
692
+ "newString": "New String",
693
+ "oldString": "Old String",
694
+ "owner": "Owner",
695
+ "offset": "Offset",
696
+ "path": "Path",
697
+ "prompt": "Prompt",
698
+ "pushToRemote": "Push To Remote",
699
+ "questions": "Questions",
700
+ "remoteSession": "Remote Session",
701
+ "remoteSessionUrl": "Remote Session URL",
702
+ "replaceAll": "Replace All",
703
+ "resume": "Resume",
704
+ "runInBackground": "Run In Background",
705
+ "status": "Status",
706
+ "subagentType": "Subagent Type",
707
+ "subject": "Subject",
708
+ "timeout": "Timeout",
709
+ "todos": "Todos"
710
+ },
562
711
  "unknown": "Unknown tool"
563
712
  },
564
713
  "terminal": {