@steerable/agent-shell-web 0.6.53 → 0.6.56
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/README.md +3 -4
- package/package.json +4 -4
- package/src/AppShell.tsx +3 -3
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +64 -56
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +31 -21
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +6 -5
- package/src/components/TerminalPanel.tsx +6 -5
- package/src/components/TerminalView.tsx +15 -10
- package/src/components/chat/ApprovalModal.tsx +40 -26
- package/src/components/chat/ApprovalPromptProvider.tsx +5 -5
- package/src/components/chat/AskUserPromptProvider.tsx +4 -4
- 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 +27 -22
- 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 +33 -23
- package/src/components/chat/TaskProcessPanel.tsx +16 -15
- 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/chat/useChatTasks.ts +3 -3
- package/src/components/settings/AgentCapabilityFields.tsx +26 -23
- package/src/components/settings/AgentsSettingsPanel.tsx +33 -28
- 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 +58 -43
- package/src/components/settings/LlmSettingsPanel.tsx +107 -86
- package/src/components/settings/McpSettingsPanel.tsx +72 -46
- 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 +26 -23
- package/src/components/settings/SecuritySettingsPanel.tsx +58 -34
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +43 -38
- package/src/components/settings/TelemetrySettingsPanel.tsx +20 -17
- package/src/components/settings/UsagePanel.tsx +22 -23
- package/src/components/settings/WebSearchSettingsPanel.tsx +28 -19
- 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/useChatsAndAgents.ts +9 -9
- package/src/hooks/useProjects.ts +3 -3
- 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 +13 -12
- package/src/lib/app-release.ts +16 -11
- package/src/lib/attachments.ts +18 -9
- package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +42 -42
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +16 -12
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/host-bridge.ts +371 -4
- package/src/lib/host-paste.ts +1 -1
- package/src/lib/http-bridge.ts +12 -9
- package/src/lib/insights.ts +2 -2
- package/src/lib/local-api.ts +5 -5
- package/src/lib/portable.ts +11 -10
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/slash-sources.ts +3 -3
- package/src/lib/tauri-bridge.ts +1 -1
- package/src/main.tsx +27 -4
- package/src/pages/AgentPage.tsx +39 -34
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +51 -45
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
- package/src/lib/electron-bridge.ts +0 -390
|
@@ -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(
|
|
@@ -141,7 +141,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
141
141
|
case '.tsv':
|
|
142
142
|
case '.numbers':
|
|
143
143
|
return {
|
|
144
|
-
label: '
|
|
144
|
+
label: 'Spreadsheet',
|
|
145
145
|
extBadge: extClean,
|
|
146
146
|
kind: 'spreadsheet',
|
|
147
147
|
themeColor: 'emerald',
|
|
@@ -150,7 +150,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
150
150
|
case '.ppt': // shell-neutral:allow — Office 幻灯片扩展名,不是产品品牌
|
|
151
151
|
case '.key':
|
|
152
152
|
return {
|
|
153
|
-
label: '
|
|
153
|
+
label: 'Presentation',
|
|
154
154
|
extBadge: extClean,
|
|
155
155
|
kind: 'presentation',
|
|
156
156
|
themeColor: 'amber',
|
|
@@ -160,14 +160,14 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
160
160
|
case '.pages':
|
|
161
161
|
case '.rtf':
|
|
162
162
|
return {
|
|
163
|
-
label: '
|
|
163
|
+
label: 'Document',
|
|
164
164
|
extBadge: extClean,
|
|
165
165
|
kind: 'document',
|
|
166
166
|
themeColor: 'blue',
|
|
167
167
|
};
|
|
168
168
|
case '.pdf':
|
|
169
169
|
return {
|
|
170
|
-
label: '
|
|
170
|
+
label: 'Document',
|
|
171
171
|
extBadge: 'PDF',
|
|
172
172
|
kind: 'pdf',
|
|
173
173
|
themeColor: 'rose',
|
|
@@ -179,7 +179,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
179
179
|
case '.webp':
|
|
180
180
|
case '.svg':
|
|
181
181
|
return {
|
|
182
|
-
label: '
|
|
182
|
+
label: 'Image',
|
|
183
183
|
extBadge: extClean,
|
|
184
184
|
kind: 'image',
|
|
185
185
|
themeColor: 'purple',
|
|
@@ -189,7 +189,7 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
189
189
|
case '.gz':
|
|
190
190
|
case '.7z':
|
|
191
191
|
return {
|
|
192
|
-
label: '
|
|
192
|
+
label: 'Archive',
|
|
193
193
|
extBadge: extClean,
|
|
194
194
|
kind: 'archive',
|
|
195
195
|
themeColor: 'amber',
|
|
@@ -197,14 +197,14 @@ export function getDeliverableMeta(filePath: string): DeliverableFileMeta {
|
|
|
197
197
|
case '.html':
|
|
198
198
|
case '.htm':
|
|
199
199
|
return {
|
|
200
|
-
label: '
|
|
200
|
+
label: 'Web page',
|
|
201
201
|
extBadge: 'HTML',
|
|
202
202
|
kind: 'document',
|
|
203
203
|
themeColor: 'blue',
|
|
204
204
|
};
|
|
205
205
|
default:
|
|
206
206
|
return {
|
|
207
|
-
label: '
|
|
207
|
+
label: 'File',
|
|
208
208
|
extBadge: extClean,
|
|
209
209
|
kind: 'file',
|
|
210
210
|
themeColor: 'slate',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import { listChatTasks, type LocalTask } from '@/lib/local-api';
|
|
13
|
-
import {
|
|
13
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
14
14
|
|
|
15
15
|
export interface ChatTaskSummary {
|
|
16
16
|
total: number;
|
|
@@ -68,7 +68,7 @@ export function useChatTasks(chatId: string | null): ChatTasksState {
|
|
|
68
68
|
const seenRef = useRef<Map<string, LocalTask['status']> | null>(null);
|
|
69
69
|
|
|
70
70
|
const refresh = useCallback(async () => {
|
|
71
|
-
if (!chatId || !
|
|
71
|
+
if (!chatId || !hasHostBridge()) {
|
|
72
72
|
setTasks(NO_TASKS);
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
@@ -105,7 +105,7 @@ export function useChatTasks(chatId: string | null): ChatTasksState {
|
|
|
105
105
|
|
|
106
106
|
useEffect(() => {
|
|
107
107
|
if (!chatId) return;
|
|
108
|
-
const bridge =
|
|
108
|
+
const bridge = getHostBridge();
|
|
109
109
|
if (!bridge?.onTaskUpdated) return;
|
|
110
110
|
return bridge.onTaskUpdated((payload) => {
|
|
111
111
|
if (payload.chatId === chatId) void refresh();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { LuLoaderCircle, LuSearch } from 'react-icons/lu';
|
|
3
|
-
import {
|
|
3
|
+
import { t } from '@/i18n';
|
|
4
|
+
import { hasHostBridge } from '@/lib/host-bridge';
|
|
4
5
|
import {
|
|
5
6
|
listChatAgentSkills,
|
|
6
7
|
listChatAgentTools,
|
|
@@ -43,16 +44,16 @@ const TOOL_POLICY_MODES: Array<{
|
|
|
43
44
|
label: string;
|
|
44
45
|
hint: string;
|
|
45
46
|
}> = [
|
|
46
|
-
{ mode: 'all', label: '
|
|
47
|
+
{ mode: 'all', label: 'All tools', hint: 'No restriction. The agent can call every wired tool.' },
|
|
47
48
|
{
|
|
48
49
|
mode: 'allowlist',
|
|
49
|
-
label: '
|
|
50
|
-
hint: '
|
|
50
|
+
label: 'Only checked',
|
|
51
|
+
hint: 'Only checked tools enter the tool list. The agent cannot even see the names of the rest.',
|
|
51
52
|
},
|
|
52
53
|
{
|
|
53
54
|
mode: 'denylist',
|
|
54
|
-
label: '
|
|
55
|
-
hint: '
|
|
55
|
+
label: 'Block checked',
|
|
56
|
+
hint: 'Checked tools are refused. The rest stay available.',
|
|
56
57
|
},
|
|
57
58
|
];
|
|
58
59
|
|
|
@@ -153,7 +154,7 @@ export function AgentCapabilityFields({
|
|
|
153
154
|
const [toolFilter, setToolFilter] = useState('');
|
|
154
155
|
|
|
155
156
|
const fetchCatalogs = useCallback(async () => {
|
|
156
|
-
if (!
|
|
157
|
+
if (!hasHostBridge()) return;
|
|
157
158
|
setLoading(true);
|
|
158
159
|
try {
|
|
159
160
|
const [skillRes, toolRes] = await Promise.all([
|
|
@@ -203,24 +204,26 @@ export function AgentCapabilityFields({
|
|
|
203
204
|
<div className="space-y-3 rounded-agent-md border border-agent-border/50 bg-agent-canvas/60 p-3">
|
|
204
205
|
<section className="space-y-1.5">
|
|
205
206
|
<div className="flex items-center justify-between gap-2">
|
|
206
|
-
<h5 className="text-[11px] font-semibold text-agent-foreground"
|
|
207
|
+
<h5 className="text-[11px] font-semibold text-agent-foreground">{t('Skills')}</h5>
|
|
207
208
|
{value.skillIds.length > 0 && (
|
|
208
209
|
<span className="text-[10px] text-agent-muted-foreground">
|
|
209
|
-
|
|
210
|
+
{t('{count} selected', { count: value.skillIds.length })}
|
|
210
211
|
</span>
|
|
211
212
|
)}
|
|
212
213
|
</div>
|
|
213
214
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
214
|
-
|
|
215
|
+
{t(
|
|
216
|
+
"Checked skills stay in this agent's system prompt without waiting for their triggers to match.",
|
|
217
|
+
)}
|
|
215
218
|
</p>
|
|
216
219
|
{loading ? (
|
|
217
220
|
<div className="flex items-center gap-2 py-3 text-[11px] text-agent-muted-foreground">
|
|
218
221
|
<LuLoaderCircle className="h-3 w-3 animate-spin" />
|
|
219
|
-
|
|
222
|
+
{t('Loading skill catalog...')}
|
|
220
223
|
</div>
|
|
221
224
|
) : skills.length === 0 ? (
|
|
222
225
|
<p className="py-2 text-[11px] text-agent-muted-foreground">
|
|
223
|
-
|
|
226
|
+
{t('No skills to check yet. Import them in "Skill settings".')}
|
|
224
227
|
</p>
|
|
225
228
|
) : (
|
|
226
229
|
<>
|
|
@@ -231,7 +234,7 @@ export function AgentCapabilityFields({
|
|
|
231
234
|
type="text"
|
|
232
235
|
value={skillFilter}
|
|
233
236
|
onChange={(event) => setSkillFilter(event.target.value)}
|
|
234
|
-
placeholder=
|
|
237
|
+
placeholder={t('Filter skills')}
|
|
235
238
|
className="h-7 w-full rounded-agent-md border border-agent-border bg-agent-canvas pl-7 pr-2 text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
236
239
|
data-testid="agent-form-skill-filter"
|
|
237
240
|
/>
|
|
@@ -247,7 +250,7 @@ export function AgentCapabilityFields({
|
|
|
247
250
|
}
|
|
248
251
|
testId={`agent-form-skill-${skill.id}`}
|
|
249
252
|
title={skill.displayName || skill.name}
|
|
250
|
-
badge={skill.layer === 'eager' ? '
|
|
253
|
+
badge={skill.layer === 'eager' ? t('Always on') : t('On demand')}
|
|
251
254
|
description={skill.description}
|
|
252
255
|
/>
|
|
253
256
|
))}
|
|
@@ -261,21 +264,21 @@ export function AgentCapabilityFields({
|
|
|
261
264
|
onChange({ ...value, allowExternalSkills: !value.allowExternalSkills })
|
|
262
265
|
}
|
|
263
266
|
testId="agent-form-allow-external-skills"
|
|
264
|
-
label=
|
|
265
|
-
hint=
|
|
267
|
+
label={t('Allow unchecked skills')}
|
|
268
|
+
hint={t('When off, this agent can only use the skills checked above. No other skill can load.')}
|
|
266
269
|
/>
|
|
267
270
|
<SwitchRow
|
|
268
271
|
checked={value.loadAllSkills}
|
|
269
272
|
onChange={() => onChange({ ...value, loadAllSkills: !value.loadAllSkills })}
|
|
270
273
|
testId="agent-form-load-all-skills"
|
|
271
|
-
label=
|
|
272
|
-
hint="
|
|
274
|
+
label={t('Ignore triggers and load all skills')}
|
|
275
|
+
hint={t('The built-in "Assistant" uses this mode. It makes every turn\'s prompt much longer.')}
|
|
273
276
|
/>
|
|
274
277
|
</div>
|
|
275
278
|
</section>
|
|
276
279
|
|
|
277
280
|
<section className="space-y-1.5 border-t border-agent-border/40 pt-3">
|
|
278
|
-
<h5 className="text-[11px] font-semibold text-agent-foreground"
|
|
281
|
+
<h5 className="text-[11px] font-semibold text-agent-foreground">{t('Tool permissions')}</h5>
|
|
279
282
|
<div className="flex flex-wrap gap-1" data-testid="agent-form-tool-modes">
|
|
280
283
|
{TOOL_POLICY_MODES.map((item) => (
|
|
281
284
|
<button
|
|
@@ -290,12 +293,12 @@ export function AgentCapabilityFields({
|
|
|
290
293
|
: 'bg-agent-muted text-agent-muted-foreground hover:text-agent-foreground'
|
|
291
294
|
}`}
|
|
292
295
|
>
|
|
293
|
-
{item.label}
|
|
296
|
+
{t(item.label)}
|
|
294
297
|
</button>
|
|
295
298
|
))}
|
|
296
299
|
</div>
|
|
297
300
|
{activeMode && (
|
|
298
|
-
<p className="text-[10px] text-agent-muted-foreground">{activeMode.hint}</p>
|
|
301
|
+
<p className="text-[10px] text-agent-muted-foreground">{t(activeMode.hint)}</p>
|
|
299
302
|
)}
|
|
300
303
|
{policy.mode !== 'all' && (
|
|
301
304
|
<>
|
|
@@ -304,7 +307,7 @@ export function AgentCapabilityFields({
|
|
|
304
307
|
className="text-[10px] text-agent-destructive"
|
|
305
308
|
data-testid="agent-form-tool-policy-empty"
|
|
306
309
|
>
|
|
307
|
-
|
|
310
|
+
{t('No tool is checked. Saving treats this as "All tools".')}
|
|
308
311
|
</p>
|
|
309
312
|
)}
|
|
310
313
|
<div className="relative">
|
|
@@ -313,7 +316,7 @@ export function AgentCapabilityFields({
|
|
|
313
316
|
type="text"
|
|
314
317
|
value={toolFilter}
|
|
315
318
|
onChange={(event) => setToolFilter(event.target.value)}
|
|
316
|
-
placeholder=
|
|
319
|
+
placeholder={t('Filter tools')}
|
|
317
320
|
className="h-7 w-full rounded-agent-md border border-agent-border bg-agent-canvas pl-7 pr-2 text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
318
321
|
data-testid="agent-form-tool-filter"
|
|
319
322
|
/>
|