@steerable/agent-shell-web 0.6.53 → 0.6.55
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.
- package/package.json +4 -4
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +60 -52
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +29 -19
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +2 -1
- package/src/components/TerminalPanel.tsx +3 -2
- package/src/components/TerminalView.tsx +12 -7
- package/src/components/chat/ApprovalModal.tsx +39 -25
- package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserPromptProvider.tsx +1 -1
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +56 -40
- package/src/components/chat/ChatProjectBadge.tsx +24 -19
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +31 -21
- package/src/components/chat/TaskProcessPanel.tsx +14 -13
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/settings/AgentCapabilityFields.tsx +24 -21
- package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
- package/src/components/settings/LlmSettingsPanel.tsx +104 -83
- package/src/components/settings/McpSettingsPanel.tsx +60 -34
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
- package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
- package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
- package/src/components/settings/UsagePanel.tsx +19 -20
- package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useProjects.ts +1 -1
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +7 -6
- package/src/lib/app-release.ts +15 -10
- package/src/lib/attachments.ts +16 -7
- package/src/lib/browser-dev-electron-mock.ts +34 -34
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +6 -2
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/electron-bridge.ts +4 -5
- package/src/lib/http-bridge.ts +4 -1
- package/src/lib/portable.ts +8 -7
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/main.tsx +25 -2
- package/src/pages/AgentPage.tsx +17 -12
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +48 -42
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
|
@@ -3,6 +3,7 @@ import { LuArrowDown, LuLoaderCircle, LuTerminal } from 'react-icons/lu';
|
|
|
3
3
|
import { DockHeaderButton, DockPanelHeader } from '@/components/DockPanelHeader';
|
|
4
4
|
import { getChildProcess, getTaskProcess } from '@/lib/local-api';
|
|
5
5
|
import { getElectronBridge } from '@/lib/electron-bridge';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
import { Markdown } from '@/components/chat/Markdown';
|
|
7
8
|
import { TurnProcessGroup } from '@/components/chat/TurnProcessGroup';
|
|
8
9
|
import {
|
|
@@ -168,11 +169,11 @@ export function TaskProcessPanel({
|
|
|
168
169
|
}, [inspected.id, childRecordId]);
|
|
169
170
|
|
|
170
171
|
const statusLabel = live
|
|
171
|
-
? '
|
|
172
|
+
? t('Reasoning')
|
|
172
173
|
: stale
|
|
173
|
-
? '
|
|
174
|
-
: '
|
|
175
|
-
const panelTitle = `${childRecordId ? '
|
|
174
|
+
? t('Process interrupted (still marked running in the table)')
|
|
175
|
+
: t('Finished');
|
|
176
|
+
const panelTitle = `${childRecordId ? t('Subagent reasoning') : t('Background reasoning')} · ${statusLabel}`;
|
|
176
177
|
|
|
177
178
|
return (
|
|
178
179
|
<div
|
|
@@ -182,13 +183,13 @@ export function TaskProcessPanel({
|
|
|
182
183
|
<DockPanelHeader
|
|
183
184
|
title={panelTitle}
|
|
184
185
|
onClose={onClose}
|
|
185
|
-
closeLabel=
|
|
186
|
+
closeLabel={t('Close process panel')}
|
|
186
187
|
actions={
|
|
187
188
|
onShowTerminal && (
|
|
188
189
|
<DockHeaderButton
|
|
189
190
|
icon={<LuTerminal className="h-3 w-3" />}
|
|
190
|
-
label=
|
|
191
|
-
title=
|
|
191
|
+
label={t('Terminal')}
|
|
192
|
+
title={t('Switch to terminal')}
|
|
192
193
|
onClick={onShowTerminal}
|
|
193
194
|
/>
|
|
194
195
|
)
|
|
@@ -208,7 +209,7 @@ export function TaskProcessPanel({
|
|
|
208
209
|
{loading ? (
|
|
209
210
|
<div className="flex items-center gap-2 text-xs text-agent-muted-foreground">
|
|
210
211
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
211
|
-
|
|
212
|
+
{t('Loading reasoning process...')}
|
|
212
213
|
</div>
|
|
213
214
|
) : error ? (
|
|
214
215
|
<div className="text-xs text-red-600 dark:text-red-300">{error}</div>
|
|
@@ -223,10 +224,10 @@ export function TaskProcessPanel({
|
|
|
223
224
|
emptyFallback={
|
|
224
225
|
<div className="text-xs text-agent-muted-foreground">
|
|
225
226
|
{stale
|
|
226
|
-
? '
|
|
227
|
+
? t('The task stream ended with the process and left no reasoning record to replay.')
|
|
227
228
|
: live
|
|
228
|
-
? '
|
|
229
|
-
: '
|
|
229
|
+
? t('The task has started. Its reasoning process appears here.')
|
|
230
|
+
: t('No reasoning process to show.')}
|
|
230
231
|
</div>
|
|
231
232
|
}
|
|
232
233
|
renderAnswer={(block) => (
|
|
@@ -246,8 +247,8 @@ export function TaskProcessPanel({
|
|
|
246
247
|
scrollToBottom('smooth');
|
|
247
248
|
}}
|
|
248
249
|
className="absolute bottom-2 right-2 flex h-7 w-7 items-center justify-center rounded-full border border-agent-border bg-agent-canvas text-agent-foreground shadow-md transition-colors hover:bg-agent-foreground/5"
|
|
249
|
-
title=
|
|
250
|
-
aria-label=
|
|
250
|
+
title={t('Back to bottom')}
|
|
251
|
+
aria-label={t('Back to bottom')}
|
|
251
252
|
>
|
|
252
253
|
<LuArrowDown className="h-4 w-4" />
|
|
253
254
|
</button>
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from 'react-icons/lu';
|
|
17
17
|
import { openLocalPath } from '@/lib/local-api';
|
|
18
18
|
import { hostToolChrome } from '@/lib/host-tools';
|
|
19
|
+
import { t } from '@/i18n';
|
|
19
20
|
import {
|
|
20
21
|
formatFileSize,
|
|
21
22
|
formatIntermediateDisplayPath,
|
|
@@ -67,7 +68,7 @@ export function TurnFilesCard({ files, executedActions }: TurnFilesCardProps) {
|
|
|
67
68
|
try {
|
|
68
69
|
const result = await openLocalPath(filePath);
|
|
69
70
|
if (!result.success) {
|
|
70
|
-
setOpenError({ path: filePath, message: result.error || '
|
|
71
|
+
setOpenError({ path: filePath, message: result.error || t('Could not open') });
|
|
71
72
|
}
|
|
72
73
|
} catch (err) {
|
|
73
74
|
setOpenError({
|
|
@@ -197,7 +198,7 @@ function DeliverableCard({
|
|
|
197
198
|
</button>
|
|
198
199
|
<div className="mt-0.5 flex min-w-0 items-center gap-1.5 text-[11px] text-agent-muted-foreground">
|
|
199
200
|
<span className="truncate" title={file.description}>
|
|
200
|
-
{file.description ?? `${meta.label} · ${meta.extBadge}`}
|
|
201
|
+
{file.description ?? `${t(meta.label)} · ${meta.extBadge}`}
|
|
201
202
|
</span>
|
|
202
203
|
{size && (
|
|
203
204
|
<>
|
|
@@ -221,13 +222,13 @@ function DeliverableCard({
|
|
|
221
222
|
{busy ? (
|
|
222
223
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin text-agent-muted-foreground" />
|
|
223
224
|
) : null}
|
|
224
|
-
<span
|
|
225
|
+
<span>{t('Open with')}</span>
|
|
225
226
|
</button>
|
|
226
227
|
<button
|
|
227
228
|
type="button"
|
|
228
229
|
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
229
230
|
disabled={!allowOpen}
|
|
230
|
-
aria-label=
|
|
231
|
+
aria-label={t('More actions')}
|
|
231
232
|
className="border-l border-agent-border px-1.5 py-1 text-agent-muted-foreground hover:bg-agent-muted/50 hover:text-agent-foreground rounded-r-agent-md transition-colors"
|
|
232
233
|
>
|
|
233
234
|
<LuChevronDown className="h-3.5 w-3.5" />
|
|
@@ -246,7 +247,7 @@ function DeliverableCard({
|
|
|
246
247
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-agent-foreground hover:bg-agent-foreground/5"
|
|
247
248
|
>
|
|
248
249
|
<LuExternalLink className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
249
|
-
<span
|
|
250
|
+
<span>{t('Open with default app')}</span>
|
|
250
251
|
</button>
|
|
251
252
|
<button
|
|
252
253
|
type="button"
|
|
@@ -257,7 +258,7 @@ function DeliverableCard({
|
|
|
257
258
|
className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-agent-foreground hover:bg-agent-foreground/5"
|
|
258
259
|
>
|
|
259
260
|
<LuFolderOpen className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
260
|
-
<span
|
|
261
|
+
<span>{t('Show in folder')}</span>
|
|
261
262
|
</button>
|
|
262
263
|
<button
|
|
263
264
|
type="button"
|
|
@@ -272,7 +273,7 @@ function DeliverableCard({
|
|
|
272
273
|
) : (
|
|
273
274
|
<LuCopy className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
274
275
|
)}
|
|
275
|
-
<span>{copied ? '
|
|
276
|
+
<span>{copied ? t('Copied full path') : t('Copy file path')}</span>
|
|
276
277
|
</button>
|
|
277
278
|
</div>
|
|
278
279
|
)}
|
|
@@ -281,7 +282,7 @@ function DeliverableCard({
|
|
|
281
282
|
|
|
282
283
|
{error && (
|
|
283
284
|
<div className="mt-2 px-1 text-[11px] text-agent-destructive" role="status">
|
|
284
|
-
|
|
285
|
+
{t('Could not open: {error}', { error })}
|
|
285
286
|
</div>
|
|
286
287
|
)}
|
|
287
288
|
</div>
|
|
@@ -370,7 +371,7 @@ function EditedFilesCard({
|
|
|
370
371
|
</div>
|
|
371
372
|
<div className="min-w-0">
|
|
372
373
|
<div className="truncate text-xs font-medium text-agent-foreground">
|
|
373
|
-
|
|
374
|
+
{t('Changed {count} files', { count: files.length })}
|
|
374
375
|
</div>
|
|
375
376
|
{(totalAdditions > 0 || totalDeletions > 0) && (
|
|
376
377
|
<div className="flex items-center gap-1.5 font-mono text-[11px] tabular-nums text-agent-muted-foreground">
|
|
@@ -388,10 +389,10 @@ function EditedFilesCard({
|
|
|
388
389
|
<button
|
|
389
390
|
type="button"
|
|
390
391
|
disabled
|
|
391
|
-
title=
|
|
392
|
+
title={t('Undo file changes from this turn')}
|
|
392
393
|
className="flex items-center gap-1 px-1.5 py-0.5 text-xs text-agent-muted-foreground/50 cursor-default"
|
|
393
394
|
>
|
|
394
|
-
<span
|
|
395
|
+
<span>{t('Undo')}</span>
|
|
395
396
|
<LuUndo2 className="h-3 w-3" />
|
|
396
397
|
</button>
|
|
397
398
|
<button
|
|
@@ -400,7 +401,7 @@ function EditedFilesCard({
|
|
|
400
401
|
onClick={() => setExpanded(!expanded)}
|
|
401
402
|
className="rounded-agent-md border border-agent-border bg-agent-canvas px-2.5 py-0.5 text-xs font-medium text-agent-foreground hover:bg-agent-muted/50 transition-colors"
|
|
402
403
|
>
|
|
403
|
-
|
|
404
|
+
{t('View details')}
|
|
404
405
|
</button>
|
|
405
406
|
</div>
|
|
406
407
|
</div>
|
|
@@ -457,7 +458,7 @@ function EditedFilesCard({
|
|
|
457
458
|
|
|
458
459
|
{error && (
|
|
459
460
|
<div className="px-3 pb-1 text-[11px] text-agent-destructive" role="status">
|
|
460
|
-
|
|
461
|
+
{t('Could not open: {error}', { error })}
|
|
461
462
|
</div>
|
|
462
463
|
)}
|
|
463
464
|
</li>
|
|
@@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
|
|
|
3
3
|
import { LuChevronDown, LuChevronUp, LuCheck, LuCopy, LuCornerDownRight } from 'react-icons/lu';
|
|
4
4
|
import type { ChatMessage } from '@steerable/agent-protocol';
|
|
5
5
|
import type { LocalChat, LocalChatAgent } from '@/lib/local-api';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
import { useSlashSources } from '@/lib/slash-sources';
|
|
7
8
|
import { Markdown } from './Markdown';
|
|
8
9
|
import { getFriendlyDate } from './timestamp';
|
|
@@ -68,7 +69,7 @@ export function UserMessage({
|
|
|
68
69
|
<div className="mb-1 flex items-center gap-1 text-[11px] font-medium text-agent-muted-foreground select-none">
|
|
69
70
|
<LuCornerDownRight className="h-3 w-3 text-agent-primary/80" />
|
|
70
71
|
<span className="rounded bg-agent-primary/10 px-1 py-0.5 text-[10px] text-agent-primary font-medium">
|
|
71
|
-
|
|
72
|
+
{t('Appended')}
|
|
72
73
|
</span>
|
|
73
74
|
</div>
|
|
74
75
|
)}
|
|
@@ -110,12 +111,12 @@ export function UserMessage({
|
|
|
110
111
|
>
|
|
111
112
|
{isExpanded ? (
|
|
112
113
|
<>
|
|
113
|
-
|
|
114
|
+
{t('Collapse')}
|
|
114
115
|
<LuChevronUp className="h-3 w-3" />
|
|
115
116
|
</>
|
|
116
117
|
) : (
|
|
117
118
|
<>
|
|
118
|
-
|
|
119
|
+
{t('Expand all')}
|
|
119
120
|
<LuChevronDown className="h-3 w-3" />
|
|
120
121
|
</>
|
|
121
122
|
)}
|
|
@@ -127,14 +128,14 @@ export function UserMessage({
|
|
|
127
128
|
type="button"
|
|
128
129
|
onClick={() => void copy()}
|
|
129
130
|
className="inline-flex items-center gap-0.5 rounded text-xs text-agent-muted-foreground opacity-0 transition-all duration-200 hover:text-agent-foreground focus:opacity-100 group-hover/message:opacity-100"
|
|
130
|
-
title={copied ? '
|
|
131
|
-
aria-label={copied ? '
|
|
131
|
+
title={copied ? t('Copied') : t('Copy message')}
|
|
132
|
+
aria-label={copied ? t('Copied') : t('Copy message')}
|
|
132
133
|
>
|
|
133
134
|
{copied ? (
|
|
134
135
|
<>
|
|
135
136
|
<LuCheck className="h-3 w-3 text-emerald-600 dark:text-emerald-400" />
|
|
136
137
|
<span className="text-emerald-600 dark:text-emerald-400">
|
|
137
|
-
|
|
138
|
+
{t('Copied')}
|
|
138
139
|
</span>
|
|
139
140
|
</>
|
|
140
141
|
) : (
|
|
@@ -200,12 +201,12 @@ export function UserMessage({
|
|
|
200
201
|
>
|
|
201
202
|
{isExpanded ? (
|
|
202
203
|
<>
|
|
203
|
-
|
|
204
|
+
{t('Collapse')}
|
|
204
205
|
<LuChevronUp className="h-3 w-3" />
|
|
205
206
|
</>
|
|
206
207
|
) : (
|
|
207
208
|
<>
|
|
208
|
-
|
|
209
|
+
{t('Expand all')}
|
|
209
210
|
<LuChevronDown className="h-3 w-3" />
|
|
210
211
|
</>
|
|
211
212
|
)}
|
|
@@ -217,14 +218,14 @@ export function UserMessage({
|
|
|
217
218
|
type="button"
|
|
218
219
|
onClick={() => void copy()}
|
|
219
220
|
className="inline-flex items-center gap-0.5 rounded text-xs text-agent-muted-foreground opacity-0 transition-all duration-200 hover:text-agent-foreground focus:opacity-100 group-hover/message:opacity-100"
|
|
220
|
-
title={copied ? '
|
|
221
|
-
aria-label={copied ? '
|
|
221
|
+
title={copied ? t('Copied') : t('Copy message')}
|
|
222
|
+
aria-label={copied ? t('Copied') : t('Copy message')}
|
|
222
223
|
>
|
|
223
224
|
{copied ? (
|
|
224
225
|
<>
|
|
225
226
|
<LuCheck className="h-3 w-3 text-emerald-600 dark:text-emerald-400" />
|
|
226
227
|
<span className="text-emerald-600 dark:text-emerald-400">
|
|
227
|
-
|
|
228
|
+
{t('Copied')}
|
|
228
229
|
</span>
|
|
229
230
|
</>
|
|
230
231
|
) : (
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* result_count / status / bytes / truncated 这些小字段原样保留。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { t } from '@/i18n';
|
|
12
|
+
import { agentLabel } from '@/i18n/agent-label';
|
|
11
13
|
import { findAgentForProfile } from './orchestration-children-model';
|
|
12
14
|
|
|
13
15
|
interface WebToolResultData {
|
|
@@ -61,19 +63,21 @@ export function summarizeWebAction(
|
|
|
61
63
|
|
|
62
64
|
if (tool === 'web_search') {
|
|
63
65
|
const query = typeof obj.query === 'string' ? obj.query : '';
|
|
64
|
-
const head =
|
|
66
|
+
const head = t('Search "{query}"', {
|
|
67
|
+
query: query.length > 40 ? `${query.slice(0, 37)}…` : query,
|
|
68
|
+
});
|
|
65
69
|
if (failed || !data) return head;
|
|
66
70
|
const count = typeof data.result_count === 'number' ? data.result_count : null;
|
|
67
|
-
return count === null ? head :
|
|
71
|
+
return count === null ? head : t('{head} → {count} results', { head, count });
|
|
68
72
|
}
|
|
69
73
|
if (tool === 'web_fetch') {
|
|
70
|
-
const head =
|
|
74
|
+
const head = t('Fetch {url}', { url: shortUrl(obj.url) ?? t('unknown address') });
|
|
71
75
|
if (failed || !data) return head;
|
|
72
76
|
const parts: string[] = [];
|
|
73
77
|
if (typeof data.status === 'number') parts.push(String(data.status));
|
|
74
78
|
const size = formatBytes(data.bytes);
|
|
75
79
|
if (size) parts.push(size);
|
|
76
|
-
if (data.truncated === true) parts.push('
|
|
80
|
+
if (data.truncated === true) parts.push(t('truncated'));
|
|
77
81
|
return parts.length > 0 ? `${head} → ${parts.join(' · ')}` : head;
|
|
78
82
|
}
|
|
79
83
|
return null;
|
|
@@ -99,7 +103,7 @@ export function summarizeRunCodeAction(
|
|
|
99
103
|
const description =
|
|
100
104
|
typeof obj.description === 'string' && obj.description.trim()
|
|
101
105
|
? obj.description.trim()
|
|
102
|
-
: '
|
|
106
|
+
: t('Program');
|
|
103
107
|
const head =
|
|
104
108
|
description.length > 40 ? `${description.slice(0, 37)}…` : description;
|
|
105
109
|
const data = resultData(result);
|
|
@@ -110,10 +114,10 @@ export function summarizeRunCodeAction(
|
|
|
110
114
|
result &&
|
|
111
115
|
typeof result === 'object' &&
|
|
112
116
|
(result as { success?: unknown }).success === false;
|
|
113
|
-
if (failed) return
|
|
117
|
+
if (failed) return t('Program "{name}" failed', { name: head });
|
|
114
118
|
return calls.length > 0
|
|
115
|
-
?
|
|
116
|
-
:
|
|
119
|
+
? t('Program "{name}" · {count} inner tools', { name: head, count: calls.length })
|
|
120
|
+
: t('Program "{name}"', { name: head });
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
export interface ExecutedActionLike {
|
|
@@ -129,12 +133,17 @@ export interface DelegateSubagentView {
|
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
const BUILTIN_DELEGATE_LABELS: Record<string, string> = {
|
|
132
|
-
explore: '
|
|
133
|
-
research: '
|
|
134
|
-
coder: '
|
|
135
|
-
'general-purpose': '
|
|
136
|
+
explore: 'Explore',
|
|
137
|
+
research: 'Research',
|
|
138
|
+
coder: 'Coding',
|
|
139
|
+
'general-purpose': 'General',
|
|
136
140
|
};
|
|
137
141
|
|
|
142
|
+
function builtinDelegateLabel(profile: string): string {
|
|
143
|
+
const source = BUILTIN_DELEGATE_LABELS[profile];
|
|
144
|
+
return source ? t(source) : profile;
|
|
145
|
+
}
|
|
146
|
+
|
|
138
147
|
const BUILTIN_DELEGATE_COLORS: Record<string, string> = {
|
|
139
148
|
explore: '#0ea5e9',
|
|
140
149
|
research: '#2563eb',
|
|
@@ -157,7 +166,7 @@ function participantFromProfile(
|
|
|
157
166
|
const match = findAgentForProfile(profile, agents);
|
|
158
167
|
return {
|
|
159
168
|
key: match?.id ?? profile,
|
|
160
|
-
name: match
|
|
169
|
+
name: match ? agentLabel(match) : builtinDelegateLabel(profile),
|
|
161
170
|
color: match?.color || BUILTIN_DELEGATE_COLORS[profile] || '#7c3aed',
|
|
162
171
|
};
|
|
163
172
|
}
|
|
@@ -187,7 +196,7 @@ export function collectTurnParticipants(
|
|
|
187
196
|
const pushAgent = (agent: AgentRef) => {
|
|
188
197
|
push({
|
|
189
198
|
key: agent.id,
|
|
190
|
-
name: agent
|
|
199
|
+
name: agentLabel(agent),
|
|
191
200
|
color: agent.color || '#7c3aed',
|
|
192
201
|
});
|
|
193
202
|
};
|
|
@@ -196,7 +205,7 @@ export function collectTurnParticipants(
|
|
|
196
205
|
if (parent && !mentioned.includes(parent.id)) {
|
|
197
206
|
push({
|
|
198
207
|
key: parent.id,
|
|
199
|
-
name: parent
|
|
208
|
+
name: agentLabel(parent),
|
|
200
209
|
color: parent.color || '#7c3aed',
|
|
201
210
|
});
|
|
202
211
|
}
|
|
@@ -209,7 +218,7 @@ export function collectTurnParticipants(
|
|
|
209
218
|
if (parent) {
|
|
210
219
|
push({
|
|
211
220
|
key: parent.id,
|
|
212
|
-
name: parent
|
|
221
|
+
name: agentLabel(parent),
|
|
213
222
|
color: parent.color || '#7c3aed',
|
|
214
223
|
});
|
|
215
224
|
}
|
|
@@ -285,10 +294,10 @@ export function delegateSubagentDisplayName(
|
|
|
285
294
|
profile: string | null,
|
|
286
295
|
agents: ReadonlyArray<{ id: string; slug: string | null; name: string }>,
|
|
287
296
|
): string {
|
|
288
|
-
if (!profile) return '
|
|
297
|
+
if (!profile) return t('Subagent');
|
|
289
298
|
const match = findAgentForProfile(profile, agents);
|
|
290
|
-
if (match) return match
|
|
291
|
-
return
|
|
299
|
+
if (match) return agentLabel(match);
|
|
300
|
+
return builtinDelegateLabel(profile);
|
|
292
301
|
}
|
|
293
302
|
|
|
294
303
|
/** 任务正文作行摘要;没有任务时返回 null,调用方不再回落 JSON。 */
|
|
@@ -374,22 +383,25 @@ export function parseToolEnvelope(result: unknown): ToolEnvelope {
|
|
|
374
383
|
};
|
|
375
384
|
}
|
|
376
385
|
|
|
377
|
-
const
|
|
378
|
-
budget_exhausted: '
|
|
379
|
-
failed: '
|
|
380
|
-
cancelled: '
|
|
386
|
+
const DELEGATE_STATUS_LABELS: Record<string, string> = {
|
|
387
|
+
budget_exhausted: 'budget exhausted',
|
|
388
|
+
failed: 'failure',
|
|
389
|
+
cancelled: 'cancellation',
|
|
381
390
|
};
|
|
382
391
|
|
|
383
392
|
/** 把 sidecar 的英文失败句收成行头/错误栏可读的中文。 */
|
|
384
393
|
export function humanizeDelegateError(error: string | null): string | null {
|
|
385
394
|
if (!error) return null;
|
|
386
|
-
if (error.includes('orchestration_budget_exceeded')) return '
|
|
395
|
+
if (error.includes('orchestration_budget_exceeded')) return t('Orchestration budget used up');
|
|
387
396
|
const ended = error.match(/sub-agent ended with status:\s*(\S+)/i);
|
|
388
397
|
if (ended) {
|
|
389
398
|
const status = ended[1].replace(/[.,;]+$/, '');
|
|
390
|
-
|
|
399
|
+
const label = DELEGATE_STATUS_LABELS[status];
|
|
400
|
+
return t('Subagent ended due to {reason}', { reason: label ? t(label) : status });
|
|
401
|
+
}
|
|
402
|
+
if (error.includes('budget_exhausted')) {
|
|
403
|
+
return t('Subagent ended due to {reason}', { reason: t('budget exhausted') });
|
|
391
404
|
}
|
|
392
|
-
if (error.includes('budget_exhausted')) return '子代理因额度耗尽结束';
|
|
393
405
|
return error;
|
|
394
406
|
}
|
|
395
407
|
|
|
@@ -475,7 +487,7 @@ export interface InspectTaskInput {
|
|
|
475
487
|
}
|
|
476
488
|
|
|
477
489
|
export function inspectTaskTitle(input: InspectTaskInput): string {
|
|
478
|
-
return input.title ?? input.task ?? '
|
|
490
|
+
return input.title ?? input.task ?? t('Background task');
|
|
479
491
|
}
|
|
480
492
|
|
|
481
493
|
/** 工具行能打开右侧「后台推理」时抽出 taskId 和标题。 */
|
|
@@ -493,7 +505,7 @@ export function inspectableTaskFromAction(
|
|
|
493
505
|
parseTaskRun(args).task
|
|
494
506
|
?? parsed.items[0]?.task
|
|
495
507
|
?? parsed.items[0]?.hint
|
|
496
|
-
?? '
|
|
508
|
+
?? t('Background task');
|
|
497
509
|
return { id, title };
|
|
498
510
|
}
|
|
499
511
|
|
|
@@ -539,13 +551,13 @@ export function isTaskFamilyTool(tool: string): tool is TaskFamilyTool {
|
|
|
539
551
|
export function taskToolLabel(tool: string, args: unknown): string | null {
|
|
540
552
|
switch (tool) {
|
|
541
553
|
case 'task_run':
|
|
542
|
-
return parseTaskRun(args).worktree ? '
|
|
554
|
+
return parseTaskRun(args).worktree ? t('Background task · Isolated') : t('Background task');
|
|
543
555
|
case 'task_send':
|
|
544
|
-
return '
|
|
556
|
+
return t('Relay to task');
|
|
545
557
|
case 'task_status':
|
|
546
|
-
return '
|
|
558
|
+
return t('Check tasks');
|
|
547
559
|
case 'task_result':
|
|
548
|
-
return '
|
|
560
|
+
return t('Collect result');
|
|
549
561
|
default:
|
|
550
562
|
return null;
|
|
551
563
|
}
|
|
@@ -569,8 +581,8 @@ export function summarizeTaskAction(
|
|
|
569
581
|
const parsed = parseTaskRun(args);
|
|
570
582
|
if (!parsed.task) return null;
|
|
571
583
|
const head = clip(parsed.task);
|
|
572
|
-
if (taskRunStatus(result) === 'blocked') return
|
|
573
|
-
if (parsed.worktree) return
|
|
584
|
+
if (taskRunStatus(result) === 'blocked') return t('{head} · Waiting on dependencies', { head });
|
|
585
|
+
if (parsed.worktree) return t('{head} · Isolated workspace', { head });
|
|
574
586
|
return head;
|
|
575
587
|
}
|
|
576
588
|
if (tool === 'task_send') {
|
|
@@ -581,7 +593,7 @@ export function summarizeTaskAction(
|
|
|
581
593
|
const id = stringField(asObject(args), 'taskId');
|
|
582
594
|
if (id) return shortTaskId(id);
|
|
583
595
|
const total = asObject(result)?.total;
|
|
584
|
-
return typeof total === 'number' ?
|
|
596
|
+
return typeof total === 'number' ? t('{count} tasks', { count: total }) : t('This chat');
|
|
585
597
|
}
|
|
586
598
|
if (tool === 'task_result') {
|
|
587
599
|
const id = stringField(asObject(args), 'taskId');
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ChildInfo } from './orchestration-children-model';
|
|
9
9
|
import { parseToolEnvelope } from './executed-actions-model';
|
|
10
|
+
import { t } from '@/i18n';
|
|
10
11
|
|
|
11
12
|
export const ORCHESTRATION_TOOL_NAMES = [
|
|
12
13
|
'agent_spawn',
|
|
@@ -102,11 +103,21 @@ function buildOrchestrationFlow(nodes: OrchestrationChildNode[]): OrchestrationF
|
|
|
102
103
|
|
|
103
104
|
let summaryCopy = '';
|
|
104
105
|
if (isAllCompleted) {
|
|
105
|
-
summaryCopy =
|
|
106
|
+
summaryCopy = t('{completed}/{total} all done', {
|
|
107
|
+
completed: completedCount,
|
|
108
|
+
total: totalCount,
|
|
109
|
+
});
|
|
106
110
|
} else if (hasActive) {
|
|
107
|
-
summaryCopy =
|
|
111
|
+
summaryCopy = t('Running together ({completed}/{total} done)...', {
|
|
112
|
+
completed: completedCount,
|
|
113
|
+
total: totalCount,
|
|
114
|
+
});
|
|
108
115
|
} else {
|
|
109
|
-
summaryCopy =
|
|
116
|
+
summaryCopy = t('{completed} done · {ended} ended ({total} subtasks)', {
|
|
117
|
+
completed: completedCount,
|
|
118
|
+
ended: totalCount - completedCount,
|
|
119
|
+
total: totalCount,
|
|
120
|
+
});
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
return {
|
|
@@ -226,7 +237,7 @@ export function extractOrchestrationFlow(
|
|
|
226
237
|
|
|
227
238
|
if (envelope.success === false || envelope.error) {
|
|
228
239
|
node.status = 'failed';
|
|
229
|
-
node.error = envelope.error || '
|
|
240
|
+
node.error = envelope.error || t('Dispatch failed');
|
|
230
241
|
}
|
|
231
242
|
} else if (action.tool === 'agent_wait') {
|
|
232
243
|
const childId = typeof rawArgs.childId === 'string' ? rawArgs.childId.trim() : '';
|
|
@@ -260,7 +271,7 @@ export function extractOrchestrationFlow(
|
|
|
260
271
|
node.status = 'completed';
|
|
261
272
|
} else if (waitStatus === 'failed' || waitStatus === 'error' || envelope.success === false) {
|
|
262
273
|
node.status = 'failed';
|
|
263
|
-
node.error = envelope.error || '
|
|
274
|
+
node.error = envelope.error || t('Run failed');
|
|
264
275
|
} else if (waitStatus === 'cancelled') {
|
|
265
276
|
node.status = 'cancelled';
|
|
266
277
|
} else if (waitStatus === 'interrupted') {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* output stage (reasoning + reply), excluding tool-wait gaps.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { t } from '@/i18n';
|
|
7
8
|
import { formatElapsedCompact } from './elapsed';
|
|
8
9
|
import {
|
|
9
10
|
countProcessReasoning,
|
|
@@ -75,16 +76,16 @@ export function processStatusLabel(input: {
|
|
|
75
76
|
elapsedMs?: number;
|
|
76
77
|
}): string {
|
|
77
78
|
const { process, isStreaming, elapsedMs } = input;
|
|
78
|
-
if (isStreaming) return '
|
|
79
|
+
if (isStreaming) return t('Working');
|
|
79
80
|
|
|
80
81
|
const parts: string[] = [];
|
|
81
82
|
const thinks = countProcessReasoning(process);
|
|
82
83
|
const tools = countProcessTools(process);
|
|
83
84
|
const elapsed = formatElapsedPart(false, elapsedMs);
|
|
84
|
-
if (elapsed) parts.push(
|
|
85
|
-
if (thinks > 0) parts.push(
|
|
86
|
-
if (tools > 0) parts.push(
|
|
87
|
-
if (parts.length === 0) return '
|
|
85
|
+
if (elapsed) parts.push(t('Worked {elapsed}', { elapsed }));
|
|
86
|
+
if (thinks > 0) parts.push(t('Thought {count} times', { count: thinks }));
|
|
87
|
+
if (tools > 0) parts.push(t('{count} tool calls', { count: tools }));
|
|
88
|
+
if (parts.length === 0) return t('Run process');
|
|
88
89
|
return parts.join(' · ');
|
|
89
90
|
}
|
|
90
91
|
|
|
@@ -96,9 +97,9 @@ export function thinkingFoldLabel(input: {
|
|
|
96
97
|
}): string {
|
|
97
98
|
const elapsed = formatElapsedPart(input.isLive, input.elapsedMs);
|
|
98
99
|
if (input.isLive) {
|
|
99
|
-
return elapsed ?
|
|
100
|
+
return elapsed ? t('Thinking · {elapsed}', { elapsed }) : t('Thinking...');
|
|
100
101
|
}
|
|
101
|
-
return elapsed ?
|
|
102
|
+
return elapsed ? t('Thought for {elapsed}', { elapsed }) : t('Thinking');
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
/** Reasoning + reply tokens for the message-footer tok/s. */
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { ExecutedAction } from './ExecutedActionsCard';
|
|
10
10
|
import type { TurnBlock } from './turn-timeline';
|
|
11
|
+
import { t } from '@/i18n';
|
|
11
12
|
|
|
12
13
|
export const TODO_STATUSES = ['pending', 'in_progress', 'completed'] as const;
|
|
13
14
|
export type TodoStatus = (typeof TODO_STATUSES)[number];
|
|
@@ -108,16 +109,16 @@ export function summarizeTodoWriteAction(
|
|
|
108
109
|
): string | null {
|
|
109
110
|
if (tool !== 'todo_write') return null;
|
|
110
111
|
const todos = extractTodosFromAction({ tool, arguments: args, result });
|
|
111
|
-
if (!todos) return '
|
|
112
|
+
if (!todos) return t('Task list');
|
|
112
113
|
const { completed, total, inProgress } = summarizeTodos(todos);
|
|
113
114
|
const current = todos.find((todo) => todo.status === 'in_progress');
|
|
114
115
|
if (inProgress > 0 && current) {
|
|
115
116
|
const label =
|
|
116
117
|
current.content.length > 28 ? `${current.content.slice(0, 25)}…` : current.content;
|
|
117
|
-
return
|
|
118
|
+
return t('Task list {completed}/{total} · {label}', { completed, total, label });
|
|
118
119
|
}
|
|
119
|
-
if (completed === total) return
|
|
120
|
-
return
|
|
120
|
+
if (completed === total) return t('Task list {completed}/{total} done', { completed, total });
|
|
121
|
+
return t('Task list {completed}/{total}', { completed, total });
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
export function latestTodosFromActions(
|