@vibe-forge/client 0.10.0 → 0.11.0

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 (111) hide show
  1. package/dist/assets/{arc-CCXV7u3V.js → arc-M4HYfcHs.js} +1 -1
  2. package/dist/assets/{blockDiagram-c4efeb88-Bm52FmvT.js → blockDiagram-c4efeb88-CUrDjrxj.js} +1 -1
  3. package/dist/assets/{c4Diagram-c83219d4-C8tTEpcK.js → c4Diagram-c83219d4-BMEtqlFp.js} +1 -1
  4. package/dist/assets/channel-Cj3Cp2OJ.js +1 -0
  5. package/dist/assets/{classDiagram-beda092f-CNAIBAH1.js → classDiagram-beda092f-BOmDJ0Ml.js} +1 -1
  6. package/dist/assets/{classDiagram-v2-2358418a-BHeZAVdc.js → classDiagram-v2-2358418a-BODzX2MB.js} +1 -1
  7. package/dist/assets/clone-B7Q9B1dS.js +1 -0
  8. package/dist/assets/{createText-1719965b-BS2hLG8t.js → createText-1719965b-B9Dd8zcR.js} +1 -1
  9. package/dist/assets/{cssMode-WHcTFAOU.js → cssMode-DLxG92Ot.js} +1 -1
  10. package/dist/assets/{edges-96097737-C07f4iWA.js → edges-96097737-CuZFd43m.js} +1 -1
  11. package/dist/assets/{erDiagram-0228fc6a-BytsAWUs.js → erDiagram-0228fc6a-8g9lu2-Z.js} +1 -1
  12. package/dist/assets/{flowDb-c6c81e3f-CQJkOpAs.js → flowDb-c6c81e3f-BlBS1tdN.js} +1 -1
  13. package/dist/assets/{flowDiagram-50d868cf-CD5Tng2S.js → flowDiagram-50d868cf-u6mWflpF.js} +1 -1
  14. package/dist/assets/flowDiagram-v2-4f6560a1-G3v545eF.js +1 -0
  15. package/dist/assets/{flowchart-elk-definition-6af322e1-ylso-GWH.js → flowchart-elk-definition-6af322e1-BDqI2NFr.js} +1 -1
  16. package/dist/assets/{freemarker2-U_9Jyyr3.js → freemarker2-tVtpTMPu.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-Cg98bJx5.js → ganttDiagram-a2739b55-CDQjx9Wu.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-7Yp4hz0N.js → gitGraphDiagram-82fe8481-DUHFKRVA.js} +1 -1
  19. package/dist/assets/{graph-Ig3nvzvL.js → graph-2HKPi5B_.js} +1 -1
  20. package/dist/assets/{handlebars-DQyCBwHe.js → handlebars-D00tgNd8.js} +1 -1
  21. package/dist/assets/{html-CNC2AT5k.js → html-B-TDzBiR.js} +1 -1
  22. package/dist/assets/{htmlMode-DlATk4xW.js → htmlMode-ClycqSTM.js} +1 -1
  23. package/dist/assets/{index-5325376f-C4zed9sb.js → index-5325376f-DPrJpRQ-.js} +1 -1
  24. package/dist/assets/{index-Dbx0JG0p.js → index-CAHZZEoo.js} +319 -323
  25. package/dist/assets/{index-DRLsOoqb.css → index-Di7lePfb.css} +1 -1
  26. package/dist/assets/{infoDiagram-8eee0895-C8oSBaFs.js → infoDiagram-8eee0895-Co5tS1I5.js} +1 -1
  27. package/dist/assets/{javascript-9wv9uKW4.js → javascript-zbkwarmb.js} +1 -1
  28. package/dist/assets/{journeyDiagram-c64418c1-D5kJldvy.js → journeyDiagram-c64418c1-k_qioHgy.js} +1 -1
  29. package/dist/assets/{jsonMode-45dv39mU.js → jsonMode-C3CSpzBF.js} +1 -1
  30. package/dist/assets/{layout-DYNFLnIl.js → layout-CjOXKxvs.js} +1 -1
  31. package/dist/assets/{line-1gvOYQYZ.js → line-C-XnQrKR.js} +1 -1
  32. package/dist/assets/{linear-DHGm6Zdw.js → linear-C7MMERzS.js} +1 -1
  33. package/dist/assets/{liquid-BGoxrdXO.js → liquid-5G37EU6K.js} +1 -1
  34. package/dist/assets/{lspLanguageFeatures-CpCCXhrd.js → lspLanguageFeatures-zaDMuhCE.js} +1 -1
  35. package/dist/assets/{mdx-Dc2iMbEw.js → mdx-Bc-LY0gi.js} +1 -1
  36. package/dist/assets/{mermaid.core-Cq2bBFF1.js → mermaid.core-CechbHof.js} +4 -4
  37. package/dist/assets/{mindmap-definition-8da855dc-y5l6GRVh.js → mindmap-definition-8da855dc-ejftCDGb.js} +1 -1
  38. package/dist/assets/{pieDiagram-a8764435-PNROcv9y.js → pieDiagram-a8764435-DY__X3Qj.js} +1 -1
  39. package/dist/assets/{python-DjYAge7h.js → python-vK2Ff2J5.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-1e28029f-CFJ3VPpp.js → quadrantDiagram-1e28029f-azIZCv_2.js} +1 -1
  41. package/dist/assets/{razor-OIY8fx_i.js → razor-BipjBJKu.js} +1 -1
  42. package/dist/assets/{requirementDiagram-08caed73-BpzDIINS.js → requirementDiagram-08caed73-C4EB0Xs2.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-a04cb91d-CH69-iIn.js → sankeyDiagram-a04cb91d-PNhR6YWu.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-c5b8d532-yBBEeVFU.js → sequenceDiagram-c5b8d532-4c-qV-Ri.js} +1 -1
  45. package/dist/assets/{stateDiagram-1ecb1508-BvF4sign.js → stateDiagram-1ecb1508-CnURumPE.js} +1 -1
  46. package/dist/assets/{stateDiagram-v2-c2b004d7-BeyT7Ghx.js → stateDiagram-v2-c2b004d7-DR2qHTPg.js} +1 -1
  47. package/dist/assets/{styles-b4e223ce-C58zxmK6.js → styles-b4e223ce-B2PWXT_i.js} +1 -1
  48. package/dist/assets/{styles-ca3715f6-DCE5sFi5.js → styles-ca3715f6-DEhgVF5H.js} +1 -1
  49. package/dist/assets/{styles-d45a18b0-CG-C1aM8.js → styles-d45a18b0-DyzccA5F.js} +1 -1
  50. package/dist/assets/{svgDrawCommon-b86b1483-F-K8GeDd.js → svgDrawCommon-b86b1483-C_1tMhxp.js} +1 -1
  51. package/dist/assets/{timeline-definition-faaaa080-DPv4uqVX.js → timeline-definition-faaaa080-FdaC0dQH.js} +1 -1
  52. package/dist/assets/{tsMode-BtU8ZELV.js → tsMode-CrMC5T3_.js} +1 -1
  53. package/dist/assets/{typescript-CJHgISWo.js → typescript-CRfPu8v7.js} +1 -1
  54. package/dist/assets/{xml-C_TJw4Bi.js → xml-jlRvQfFI.js} +1 -1
  55. package/dist/assets/{xychartDiagram-f5964ef8-BmXlhBzX.js → xychartDiagram-f5964ef8-sxjv75h9.js} +1 -1
  56. package/dist/assets/{yaml-BujeJOJ6.js → yaml-B47_IHOH.js} +1 -1
  57. package/dist/index.html +2 -2
  58. package/package.json +10 -10
  59. package/src/components/chat/ChatHistoryView.tsx +92 -14
  60. package/src/components/chat/messages/MessageItem.scss +10 -0
  61. package/src/components/chat/messages/MessageItem.tsx +5 -1
  62. package/src/components/chat/messages/MessageStatusNotice.scss +163 -0
  63. package/src/components/chat/messages/MessageStatusNotice.tsx +48 -0
  64. package/src/components/chat/messages/build-chat-history-status-notices.ts +138 -0
  65. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +0 -24
  66. package/src/components/chat/sender/@core/build-sender-controller-result.ts +0 -6
  67. package/src/components/chat/sender/@hooks/use-sender-controller.ts +0 -2
  68. package/src/components/chat/sender/@types/sender-props.ts +0 -3
  69. package/src/components/chat/sender/Sender.scss +0 -58
  70. package/src/components/chat/sender/Sender.tsx +0 -2
  71. package/src/components/chat/tools/DefaultTool.tsx +74 -207
  72. package/src/components/chat/tools/adapter-claude/ClaudeEditDiff.tsx +30 -0
  73. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.scss +128 -0
  74. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +133 -0
  75. package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +102 -0
  76. package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +168 -0
  77. package/src/components/chat/tools/adapter-claude/claude-tool-operation-builders.ts +135 -0
  78. package/src/components/chat/tools/adapter-claude/claude-tool-presentation.ts +61 -0
  79. package/src/components/chat/tools/adapter-claude/claude-tool-shared.ts +185 -0
  80. package/src/components/chat/tools/adapter-claude/claude-tool-summary.ts +76 -0
  81. package/src/components/chat/tools/adapter-claude/claude-tool-system-builders.ts +125 -0
  82. package/src/components/chat/tools/adapter-claude/claude-tool-task-builders.ts +148 -0
  83. package/src/components/chat/tools/adapter-claude/index.ts +24 -15
  84. package/src/components/chat/tools/core/ToolCallBox.scss +344 -36
  85. package/src/components/chat/tools/core/ToolCallBox.tsx +35 -13
  86. package/src/components/chat/tools/core/ToolDiffViewer.scss +138 -0
  87. package/src/components/chat/tools/core/ToolDiffViewer.tsx +180 -0
  88. package/src/components/chat/tools/core/ToolGroup.scss +52 -74
  89. package/src/components/chat/tools/core/ToolGroup.tsx +20 -26
  90. package/src/components/chat/tools/core/ToolRenderer.tsx +3 -3
  91. package/src/components/chat/tools/core/ToolResultContent.tsx +66 -0
  92. package/src/components/chat/tools/core/ToolSummaryHeader.tsx +67 -0
  93. package/src/components/chat/tools/core/tool-content-presence.ts +57 -0
  94. package/src/components/chat/tools/core/tool-display.ts +192 -0
  95. package/src/components/chat/tools/core/tool-result-content-utils.ts +171 -0
  96. package/src/components/chat/tools/core/tool-summary.ts +194 -0
  97. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +66 -53
  98. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +26 -9
  99. package/src/components/chat/tools/task/ListTasksTool.tsx +22 -9
  100. package/src/components/chat/tools/task/StartTasksTool.tsx +22 -9
  101. package/src/hooks/chat/interaction-state.ts +29 -9
  102. package/src/hooks/chat/use-chat-scroll.ts +2 -2
  103. package/src/hooks/chat/use-chat-session-messages.ts +24 -18
  104. package/src/hooks/chat/use-chat-session.ts +2 -2
  105. package/src/resources/locales/en.json +81 -0
  106. package/src/resources/locales/zh.json +81 -0
  107. package/src/routes/ChatRoute.tsx +40 -15
  108. package/src/utils/strip-ansi.ts +26 -0
  109. package/dist/assets/channel-gq_WMRvv.js +0 -1
  110. package/dist/assets/clone-XxGY7A5N.js +0 -1
  111. package/dist/assets/flowDiagram-v2-4f6560a1-DIBOANLV.js +0 -1
@@ -5,6 +5,8 @@ import React, { useMemo } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
 
7
7
  import { ToolCallBox } from '../core/ToolCallBox'
8
+ import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
9
+ import { getToolTargetPresentation } from '../core/tool-display'
8
10
  import { defineToolRender } from '../defineToolRender'
9
11
  import { TaskRow } from './components/TaskRow'
10
12
 
@@ -53,18 +55,33 @@ export const GetTaskInfoTool = defineToolRender(({ item, resultItem }) => {
53
55
  })()
54
56
  : []
55
57
  const titleFallback = t('chat.tools.task')
58
+ const taskIdPresentation = getToolTargetPresentation(taskResult?.taskId ?? inputTaskId)
59
+ const errorMeta = resultItem?.is_error === true
60
+ ? (
61
+ <span className='tool-status tool-status--error'>
62
+ <span className='material-symbols-rounded'>error</span>
63
+ </span>
64
+ )
65
+ : undefined
56
66
 
57
67
  return (
58
- <div className='tool-group get-task-info-tool'>
68
+ <div className='tool-group tool-group--compact get-task-info-tool'>
59
69
  <ToolCallBox
60
- defaultExpanded={true}
61
- header={
62
- <div className='tool-header-content'>
63
- <span className='material-symbols-rounded tool-header-icon'>info</span>
64
- <span className='tool-header-title'>{t('chat.tools.getTaskInfo')}</span>
65
- <span className='tool-header-chip'>{taskResult ? 1 : 0}</span>
66
- </div>
67
- }
70
+ variant='inline'
71
+ defaultExpanded={false}
72
+ header={({ isExpanded, isCollapsible }) => (
73
+ <ToolSummaryHeader
74
+ icon={<span className='material-symbols-rounded'>info</span>}
75
+ title={t('chat.tools.getTaskInfo')}
76
+ target={taskIdPresentation.text}
77
+ targetTitle={taskIdPresentation.title}
78
+ targetMonospace={taskIdPresentation.monospace}
79
+ expanded={isExpanded}
80
+ collapsible={isCollapsible}
81
+ meta={errorMeta}
82
+ metaTitle={errorMeta == null ? undefined : t('chat.result')}
83
+ />
84
+ )}
68
85
  content={
69
86
  <div className='tool-content'>
70
87
  {taskResult
@@ -4,6 +4,7 @@ import React, { useMemo } from 'react'
4
4
  import { useTranslation } from 'react-i18next'
5
5
 
6
6
  import { ToolCallBox } from '../core/ToolCallBox'
7
+ import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
7
8
  import { defineToolRender } from '../defineToolRender'
8
9
  import { TaskRow } from './components/TaskRow'
9
10
 
@@ -29,18 +30,30 @@ export const ListTasksTool = defineToolRender(({ resultItem }) => {
29
30
  const text = resultItem.content[0].text.trim()
30
31
  return JSON.parse(text) as TaskResult[]
31
32
  }, [resultItem?.content])
33
+ const errorMeta = resultItem?.is_error === true
34
+ ? (
35
+ <span className='tool-status tool-status--error'>
36
+ <span className='material-symbols-rounded'>error</span>
37
+ </span>
38
+ )
39
+ : undefined
32
40
 
33
41
  return (
34
- <div className='tool-group list-tasks-tool'>
42
+ <div className='tool-group tool-group--compact list-tasks-tool'>
35
43
  <ToolCallBox
36
- defaultExpanded={true}
37
- header={
38
- <div className='tool-header-content'>
39
- <span className='material-symbols-rounded tool-header-icon'>list_alt</span>
40
- <span className='tool-header-title'>{t('chat.tools.listTasks')}</span>
41
- <span className='tool-header-chip'>{taskResults.length}</span>
42
- </div>
43
- }
44
+ variant='inline'
45
+ defaultExpanded={false}
46
+ header={({ isExpanded, isCollapsible }) => (
47
+ <ToolSummaryHeader
48
+ icon={<span className='material-symbols-rounded'>list_alt</span>}
49
+ title={t('chat.tools.listTasks')}
50
+ target={t('chat.tools.taskCount', { count: taskResults.length })}
51
+ expanded={isExpanded}
52
+ collapsible={isCollapsible}
53
+ meta={errorMeta}
54
+ metaTitle={errorMeta == null ? undefined : t('chat.result')}
55
+ />
56
+ )}
44
57
  content={
45
58
  <div className='tool-content'>
46
59
  <div className='list-tasks-tool__list'>
@@ -5,6 +5,7 @@ import React, { useMemo } from 'react'
5
5
  import { useTranslation } from 'react-i18next'
6
6
 
7
7
  import { ToolCallBox } from '../core/ToolCallBox'
8
+ import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
8
9
  import { defineToolRender } from '../defineToolRender'
9
10
  import { TaskRow } from './components/TaskRow'
10
11
 
@@ -39,18 +40,30 @@ export const StartTasksTool = defineToolRender(({ item, resultItem }) => {
39
40
  }, [resultItem?.content])
40
41
 
41
42
  const { taskResults } = parsedResult
43
+ const errorMeta = resultItem?.is_error === true
44
+ ? (
45
+ <span className='tool-status tool-status--error'>
46
+ <span className='material-symbols-rounded'>error</span>
47
+ </span>
48
+ )
49
+ : undefined
42
50
 
43
51
  return (
44
- <div className='tool-group start-tasks-tool'>
52
+ <div className='tool-group tool-group--compact start-tasks-tool'>
45
53
  <ToolCallBox
46
- defaultExpanded={true}
47
- header={
48
- <div className='tool-header-content'>
49
- <span className='material-symbols-rounded tool-header-icon'>playlist_add</span>
50
- <span className='tool-header-title'>{t('chat.tools.startTasks')}</span>
51
- <span className='tool-header-chip'>{tasks.length}</span>
52
- </div>
53
- }
54
+ variant='inline'
55
+ defaultExpanded={false}
56
+ header={({ isExpanded, isCollapsible }) => (
57
+ <ToolSummaryHeader
58
+ icon={<span className='material-symbols-rounded'>playlist_add</span>}
59
+ title={t('chat.tools.startTasks')}
60
+ target={t('chat.tools.taskCount', { count: tasks.length })}
61
+ expanded={isExpanded}
62
+ collapsible={isCollapsible}
63
+ meta={errorMeta}
64
+ metaTitle={errorMeta == null ? undefined : t('chat.result')}
65
+ />
66
+ )}
54
67
  content={
55
68
  <div className='tool-content'>
56
69
  <div className='start-tasks-tool__list'>
@@ -1,13 +1,17 @@
1
1
  import type { AskUserQuestionParams, Session, WSEvent } from '@vibe-forge/core'
2
2
 
3
+ import { stripAnsi } from '#~/utils/strip-ansi'
4
+
3
5
  export interface InteractionRequestState {
4
6
  id: string
5
7
  payload: AskUserQuestionParams
6
8
  }
7
9
 
8
- export interface ChatErrorBannerState {
10
+ export interface ChatErrorState {
9
11
  kind: 'connection' | 'session'
10
12
  message: string
13
+ code?: string
14
+ reason?: 'error' | 'closed'
11
15
  }
12
16
 
13
17
  export interface FatalSessionErrorState {
@@ -15,6 +19,8 @@ export interface FatalSessionErrorState {
15
19
  code?: string
16
20
  }
17
21
 
22
+ const normalizeErrorMessage = (value: string) => stripAnsi(value).trim()
23
+
18
24
  export const getFatalSessionError = (event: WSEvent): FatalSessionErrorState | null => {
19
25
  if (event?.type !== 'error') {
20
26
  return null
@@ -24,20 +30,34 @@ export const getFatalSessionError = (event: WSEvent): FatalSessionErrorState | n
24
30
  return null
25
31
  }
26
32
 
33
+ const code = event.data != null && typeof event.data === 'object' &&
34
+ 'code' in event.data &&
35
+ typeof event.data.code === 'string' &&
36
+ event.data.code.trim() !== ''
37
+ ? event.data.code
38
+ : undefined
39
+
27
40
  if (event.data != null && typeof event.data === 'object' && 'message' in event.data) {
28
41
  const message = event.data.message
29
- if (typeof message === 'string' && message.trim() !== '') {
30
- return {
31
- message,
32
- code: typeof event.data.code === 'string' && event.data.code.trim() !== ''
33
- ? event.data.code
34
- : undefined
42
+ if (typeof message === 'string') {
43
+ const normalizedMessage = normalizeErrorMessage(message)
44
+ if (normalizedMessage !== '') {
45
+ return {
46
+ message: normalizedMessage,
47
+ code
48
+ }
35
49
  }
36
50
  }
37
51
  }
38
52
 
39
- if (typeof event.message === 'string' && event.message.trim() !== '') {
40
- return { message: event.message }
53
+ if (typeof event.message === 'string') {
54
+ const normalizedMessage = normalizeErrorMessage(event.message)
55
+ if (normalizedMessage !== '') {
56
+ return {
57
+ message: normalizedMessage,
58
+ code
59
+ }
60
+ }
41
61
  }
42
62
 
43
63
  return null
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
2
2
 
3
3
  const SCROLL_THRESHOLD = 80
4
4
 
5
- export function useChatScroll({ messagesLength }: { messagesLength: number }) {
5
+ export function useChatScroll({ contentVersion }: { contentVersion: number }) {
6
6
  const messagesEndRef = useRef<HTMLDivElement>(null)
7
7
  const messagesContainerRef = useRef<HTMLDivElement>(null)
8
8
  const messagesContentRef = useRef<HTMLDivElement>(null)
@@ -39,7 +39,7 @@ export function useChatScroll({ messagesLength }: { messagesLength: number }) {
39
39
 
40
40
  useEffect(() => {
41
41
  updateScrollState()
42
- }, [updateScrollState, messagesLength])
42
+ }, [contentVersion, updateScrollState])
43
43
 
44
44
  return {
45
45
  messagesEndRef,
@@ -2,11 +2,13 @@ import { useCallback, useEffect, useRef, useState } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
3
  import { useSWRConfig } from 'swr'
4
4
 
5
- import { getSessionMessages } from '#~/api.js'
6
- import { connectionManager } from '#~/connectionManager.js'
7
5
  import type { AskUserQuestionParams, ChatMessage, Session, WSEvent } from '@vibe-forge/core'
8
6
  import type { SessionInfo } from '@vibe-forge/types'
9
- import type { ChatErrorBannerState } from './interaction-state'
7
+
8
+ import { getSessionMessages } from '#~/api.js'
9
+ import { connectionManager } from '#~/connectionManager.js'
10
+
11
+ import type { ChatErrorState } from './interaction-state'
10
12
  import {
11
13
  applyInteractionStateEvent,
12
14
  findLatestFatalError,
@@ -63,7 +65,7 @@ export function useChatSessionMessages({
63
65
  const [messages, setMessages] = useState<ChatMessage[]>([])
64
66
  const [sessionInfo, setSessionInfo] = useState<SessionInfo | null>(null)
65
67
  const [isReady, setIsReady] = useState(false)
66
- const [errorBanner, setErrorBanner] = useState<ChatErrorBannerState | null>(null)
68
+ const [errorState, setErrorState] = useState<ChatErrorState | null>(null)
67
69
  const [retryCount, setRetryCount] = useState(0)
68
70
  const isInitialLoadRef = useRef<boolean>(true)
69
71
  const lastConnectedModelRef = useRef<string | undefined>(undefined)
@@ -124,11 +126,12 @@ export function useChatSessionMessages({
124
126
 
125
127
  interactionRequestRef.current = restoredInteraction
126
128
  setInteractionRequest(restoredInteraction)
127
- setErrorBanner(
129
+ setErrorState(
128
130
  restoredInteraction == null && res.session?.status === 'failed' && latestFatalError != null
129
131
  ? {
130
132
  kind: 'session',
131
- message: latestFatalError.message
133
+ message: latestFatalError.message,
134
+ code: latestFatalError.code
132
135
  }
133
136
  : null
134
137
  )
@@ -174,7 +177,7 @@ export function useChatSessionMessages({
174
177
  const retryConnection = useCallback(() => {
175
178
  if (session?.id == null || session.id === '') return
176
179
  expectedCloseRef.current = true
177
- setErrorBanner(null)
180
+ setErrorState(null)
178
181
  connectionManager.close(session.id)
179
182
  setRetryCount((count) => count + 1)
180
183
  }, [session?.id])
@@ -183,7 +186,7 @@ export function useChatSessionMessages({
183
186
  setMessages([])
184
187
  setSessionInfo(null)
185
188
  setIsReady(false)
186
- setErrorBanner(null)
189
+ setErrorState(null)
187
190
  setInteractionRequest(null)
188
191
  interactionRequestRef.current = null
189
192
  isInitialLoadRef.current = true
@@ -249,7 +252,7 @@ export function useChatSessionMessages({
249
252
  session?.status !== 'running'
250
253
  if (modelChanged || effortChanged || permissionModeChanged || adapterChanged) {
251
254
  expectedCloseRef.current = true
252
- setErrorBanner(null)
255
+ setErrorState(null)
253
256
  connectionManager.send(session.id, { type: 'terminate_session' })
254
257
  connectionManager.close(session.id)
255
258
  }
@@ -278,7 +281,7 @@ export function useChatSessionMessages({
278
281
  cleanup = connectionManager.connect(session.id, {
279
282
  onOpen() {
280
283
  expectedCloseRef.current = false
281
- setErrorBanner((current) => current?.kind === 'session' ? current : null)
284
+ setErrorState((current) => current?.kind === 'session' ? current : null)
282
285
  },
283
286
  onMessage(data: WSEvent) {
284
287
  if (isDisposed) return
@@ -287,7 +290,7 @@ export function useChatSessionMessages({
287
290
  interactionRequestRef.current = nextInteraction
288
291
  setInteractionRequest(nextInteraction)
289
292
  if (nextInteraction != null) {
290
- setErrorBanner(null)
293
+ setErrorState(null)
291
294
  }
292
295
  }
293
296
  if (data.type === 'interaction_response') {
@@ -297,9 +300,10 @@ export function useChatSessionMessages({
297
300
  if (data.type === 'error') {
298
301
  const fatalError = getFatalSessionError(data)
299
302
  if (fatalError != null) {
300
- setErrorBanner({
303
+ setErrorState({
301
304
  kind: 'session',
302
- message: fatalError.message
305
+ message: fatalError.message,
306
+ code: fatalError.code
303
307
  })
304
308
  }
305
309
  return
@@ -367,9 +371,10 @@ export function useChatSessionMessages({
367
371
  },
368
372
  onError() {
369
373
  if (isDisposed) return
370
- setErrorBanner({
374
+ setErrorState({
371
375
  kind: 'connection',
372
- message: t('chat.connectionError')
376
+ message: t('chat.connectionError'),
377
+ reason: 'error'
373
378
  })
374
379
  },
375
380
  onClose() {
@@ -378,10 +383,11 @@ export function useChatSessionMessages({
378
383
  expectedCloseRef.current = false
379
384
  return
380
385
  }
381
- setErrorBanner((current) =>
386
+ setErrorState((current) =>
382
387
  current ?? {
383
388
  kind: 'connection',
384
- message: t('chat.connectionClosed')
389
+ message: t('chat.connectionClosed'),
390
+ reason: 'closed'
385
391
  }
386
392
  )
387
393
  }
@@ -414,7 +420,7 @@ export function useChatSessionMessages({
414
420
  setMessages,
415
421
  sessionInfo,
416
422
  isReady,
417
- errorBanner,
423
+ errorState,
418
424
  retryConnection,
419
425
  reconcileAfterInteraction
420
426
  }
@@ -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,
@@ -324,11 +324,22 @@
324
324
  "modelSearchPlaceholder": "Search models or services",
325
325
  "modelUnavailable": "No models available",
326
326
  "modelConfigRequired": "Add a model service in config before starting a session",
327
+ "modelConfigRequiredTitle": "Model setup required",
328
+ "modelConfigRequiredHelp": "Add at least one model service, then retry sending from the composer.",
327
329
  "connectionErrorTitle": "Connection error",
330
+ "connectionClosedTitle": "Connection closed",
328
331
  "sessionErrorTitle": "Task failed",
329
332
  "connectionError": "WebSocket connection failed. Check the server and try again.",
330
333
  "connectionClosed": "WebSocket connection closed. Try reconnecting.",
334
+ "connectionErrorHelp": "Streaming updates stopped before the session could recover. Retry to resubscribe to the running session.",
335
+ "connectionClosedHelp": "The current stream has ended unexpectedly. Retry to reconnect and continue receiving new messages.",
336
+ "sessionErrorHelp": "Check the latest tool output or terminal logs, then continue from the most recent user message if needed.",
337
+ "sessionErrorCode": "Error code: {{code}}",
331
338
  "retryConnection": "Retry",
339
+ "debugMockLabel": "Debug Preview",
340
+ "debugMockConnectionErrorMessage": "Unable to reach the session stream. The websocket handshake timed out before any updates arrived.",
341
+ "debugMockConnectionClosedMessage": "The connection dropped after the assistant started responding. No further tokens will arrive until you reconnect.",
342
+ "debugMockSessionErrorMessage": "The runtime hit its session timeout while waiting for a downstream tool result and stopped this turn.",
332
343
  "permissionRequestBadge": "Permission request",
333
344
  "permissionRequestTitleWithTool": "Requesting permission to use 【{{tool}}】. Choose how to proceed.",
334
345
  "permissionSubject": "Scope",
@@ -547,9 +558,30 @@
547
558
  "startTasksFailed": "Failed",
548
559
  "startTasksLogs": "Task Logs",
549
560
  "task": "Task",
561
+ "taskCount": "{{count}} tasks",
550
562
  "taskExitCode": "Exit: {{code}}",
551
563
  "writeSuccess": "Write Success",
552
564
  "writeFailed": "Write Failed",
565
+ "askUserQuestion": "Ask User Question",
566
+ "globTool": "Glob",
567
+ "grepTool": "Grep",
568
+ "editTool": "Edit File",
569
+ "lsTool": "List Directory",
570
+ "notebookEdit": "Notebook Edit",
571
+ "webFetch": "Web Fetch",
572
+ "webSearch": "Web Search",
573
+ "booleanOn": "On",
574
+ "booleanOff": "Off",
575
+ "diffSplit": "Split",
576
+ "diffInline": "Inline",
577
+ "skill": "Skill",
578
+ "enterPlanMode": "Enter Plan Mode",
579
+ "exitPlanMode": "Exit Plan Mode",
580
+ "taskCreate": "Create Task",
581
+ "taskGet": "Get Task",
582
+ "taskUpdate": "Update Task",
583
+ "taskList": "List Tasks",
584
+ "claudeTask": "Claude Task",
553
585
  "todo": "Task Planning",
554
586
  "call": "call",
555
587
  "reading": "Reading file...",
@@ -559,6 +591,55 @@
559
591
  "runInBackground": "run in background",
560
592
  "dangerouslyDisableSandbox": "sandbox disabled",
561
593
  "viewCommand": "View command",
594
+ "noParameters": "No parameters",
595
+ "groupSummaryCount": "{{name}} {{count}}x",
596
+ "groupSummaryMoreCount": "+{{count}}x",
597
+ "singleSelect": "single",
598
+ "multiSelect": "multi",
599
+ "fields": {
600
+ "activeForm": "Active Form",
601
+ "addBlockedBy": "Add Blocked By",
602
+ "addBlocks": "Add Blocks",
603
+ "allowedDomains": "Allowed Domains",
604
+ "allowedPrompts": "Allowed Prompts",
605
+ "answers": "Answers",
606
+ "args": "Args",
607
+ "blockedDomains": "Blocked Domains",
608
+ "cellId": "Cell ID",
609
+ "cellType": "Cell Type",
610
+ "command": "Command",
611
+ "content": "Content",
612
+ "description": "Description",
613
+ "disableSandbox": "Disable Sandbox",
614
+ "details": "Details",
615
+ "editMode": "Edit Mode",
616
+ "glob": "Glob",
617
+ "ignore": "Ignore",
618
+ "limit": "Limit",
619
+ "maxTurns": "Max Turns",
620
+ "metadata": "Metadata",
621
+ "model": "Model",
622
+ "mode": "Mode",
623
+ "newSource": "New Source",
624
+ "newString": "New String",
625
+ "oldString": "Old String",
626
+ "owner": "Owner",
627
+ "offset": "Offset",
628
+ "path": "Path",
629
+ "prompt": "Prompt",
630
+ "pushToRemote": "Push To Remote",
631
+ "questions": "Questions",
632
+ "remoteSession": "Remote Session",
633
+ "remoteSessionUrl": "Remote Session URL",
634
+ "replaceAll": "Replace All",
635
+ "resume": "Resume",
636
+ "runInBackground": "Run In Background",
637
+ "status": "Status",
638
+ "subagentType": "Subagent Type",
639
+ "subject": "Subject",
640
+ "timeout": "Timeout",
641
+ "todos": "Todos"
642
+ },
562
643
  "unknown": "Unknown tool"
563
644
  },
564
645
  "terminal": {
@@ -325,11 +325,22 @@
325
325
  "modelSearchPlaceholder": "搜索模型或服务",
326
326
  "modelUnavailable": "暂无可用模型",
327
327
  "modelConfigRequired": "请先在配置中添加模型服务后再开始会话",
328
+ "modelConfigRequiredTitle": "需要先配置模型服务",
329
+ "modelConfigRequiredHelp": "至少添加一个模型服务后,再回到输入框重新发送消息。",
328
330
  "connectionErrorTitle": "连接异常",
331
+ "connectionClosedTitle": "连接已断开",
329
332
  "sessionErrorTitle": "任务失败",
330
333
  "connectionError": "WebSocket 连接失败,请检查服务状态后重试",
331
334
  "connectionClosed": "WebSocket 连接已关闭,请重试",
335
+ "connectionErrorHelp": "当前流式更新还没恢复就中断了。重试连接后可以重新订阅这个会话的实时输出。",
336
+ "connectionClosedHelp": "当前消息流意外结束了。重试连接后,可以继续接收这个会话后续的新消息。",
337
+ "sessionErrorHelp": "可以先检查最近一次工具输出或终端日志,再决定是否从最近一条用户消息继续。",
338
+ "sessionErrorCode": "错误码:{{code}}",
332
339
  "retryConnection": "重试连接",
340
+ "debugMockLabel": "调试预览",
341
+ "debugMockConnectionErrorMessage": "无法连上当前会话的消息流,WebSocket 握手在收到任何更新前就超时了。",
342
+ "debugMockConnectionClosedMessage": "assistant 已经开始回复,但连接中途断开;在重新连接前,不会再收到新的 token。",
343
+ "debugMockSessionErrorMessage": "runtime 在等待下游工具结果时触发了会话超时,这一轮任务已经停止。",
333
344
  "permissionRequestBadge": "权限请求",
334
345
  "permissionRequestTitleWithTool": "正在请求使用【{{tool}}】的调用权限,请选择通过",
335
346
  "permissionSubject": "审批范围",
@@ -548,9 +559,30 @@
548
559
  "startTasksFailed": "失败",
549
560
  "startTasksLogs": "任务日志",
550
561
  "task": "任务",
562
+ "taskCount": "{{count}} 个任务",
551
563
  "taskExitCode": "退出码: {{code}}",
552
564
  "writeSuccess": "写入成功",
553
565
  "writeFailed": "写入失败",
566
+ "askUserQuestion": "询问问题",
567
+ "globTool": "Glob",
568
+ "grepTool": "Grep",
569
+ "editTool": "编辑文件",
570
+ "lsTool": "浏览目录",
571
+ "notebookEdit": "编辑 Notebook",
572
+ "webFetch": "抓取网页",
573
+ "webSearch": "网页搜索",
574
+ "booleanOn": "开启",
575
+ "booleanOff": "关闭",
576
+ "diffSplit": "双栏",
577
+ "diffInline": "单栏",
578
+ "skill": "技能",
579
+ "enterPlanMode": "进入计划模式",
580
+ "exitPlanMode": "退出计划模式",
581
+ "taskCreate": "创建任务",
582
+ "taskGet": "获取任务",
583
+ "taskUpdate": "更新任务",
584
+ "taskList": "列出任务",
585
+ "claudeTask": "Claude 任务",
554
586
  "todo": "任务规划",
555
587
  "call": "调用",
556
588
  "reading": "正在读取文件...",
@@ -560,6 +592,55 @@
560
592
  "runInBackground": "后台执行",
561
593
  "dangerouslyDisableSandbox": "禁用沙箱",
562
594
  "viewCommand": "查看指令",
595
+ "noParameters": "无参数",
596
+ "groupSummaryCount": "{{name}} {{count}} 次",
597
+ "groupSummaryMoreCount": "+{{count}} 次",
598
+ "singleSelect": "单选",
599
+ "multiSelect": "多选",
600
+ "fields": {
601
+ "activeForm": "当前表述",
602
+ "addBlockedBy": "新增阻塞源",
603
+ "addBlocks": "新增阻塞项",
604
+ "allowedDomains": "允许域名",
605
+ "allowedPrompts": "允许的提示",
606
+ "answers": "回答",
607
+ "args": "参数",
608
+ "blockedDomains": "屏蔽域名",
609
+ "cellId": "单元 ID",
610
+ "cellType": "单元类型",
611
+ "command": "命令",
612
+ "content": "内容",
613
+ "description": "说明",
614
+ "disableSandbox": "禁用沙箱",
615
+ "details": "详情",
616
+ "editMode": "编辑模式",
617
+ "glob": "Glob",
618
+ "ignore": "忽略项",
619
+ "limit": "Limit",
620
+ "maxTurns": "最大轮次",
621
+ "metadata": "元数据",
622
+ "model": "模型",
623
+ "mode": "模式",
624
+ "newSource": "新内容",
625
+ "newString": "新字符串",
626
+ "oldString": "旧字符串",
627
+ "owner": "负责人",
628
+ "offset": "Offset",
629
+ "path": "路径",
630
+ "prompt": "提示词",
631
+ "pushToRemote": "推送到远端",
632
+ "questions": "问题",
633
+ "remoteSession": "远端会话",
634
+ "remoteSessionUrl": "远端会话地址",
635
+ "replaceAll": "全量替换",
636
+ "resume": "恢复",
637
+ "runInBackground": "后台执行",
638
+ "status": "状态",
639
+ "subagentType": "子代理类型",
640
+ "subject": "主题",
641
+ "timeout": "超时",
642
+ "todos": "待办事项"
643
+ },
563
644
  "unknown": "未知工具"
564
645
  },
565
646
  "terminal": {