@steerable/agent-shell-web 0.6.52 → 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.
Files changed (93) hide show
  1. package/package.json +4 -4
  2. package/src/brand.ts +1 -1
  3. package/src/components/AgentSidebar.tsx +60 -52
  4. package/src/components/ChatHeader.tsx +41 -25
  5. package/src/components/ConfirmDialog.tsx +4 -3
  6. package/src/components/CreateProjectModal.tsx +29 -19
  7. package/src/components/LocalLlmSettingsModal.tsx +4 -3
  8. package/src/components/SidebarRelease.tsx +2 -1
  9. package/src/components/TerminalPanel.tsx +3 -2
  10. package/src/components/TerminalView.tsx +12 -7
  11. package/src/components/chat/ApprovalModal.tsx +39 -25
  12. package/src/components/chat/ApprovalPromptProvider.tsx +1 -1
  13. package/src/components/chat/AskUserPromptProvider.tsx +1 -1
  14. package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
  15. package/src/components/chat/AssistantMessage.tsx +18 -13
  16. package/src/components/chat/ChatInput.tsx +138 -42
  17. package/src/components/chat/ChatProjectBadge.tsx +24 -19
  18. package/src/components/chat/EmptyChatHero.tsx +2 -1
  19. package/src/components/chat/ExecPolicyPicker.tsx +12 -11
  20. package/src/components/chat/ExecutedActionsCard.tsx +45 -33
  21. package/src/components/chat/FilePathCode.tsx +8 -3
  22. package/src/components/chat/InterruptedTurnCard.tsx +4 -3
  23. package/src/components/chat/LocalChatPanel.tsx +2 -1
  24. package/src/components/chat/Markdown.tsx +2 -1
  25. package/src/components/chat/MessageList.tsx +3 -2
  26. package/src/components/chat/ModelPicker.tsx +25 -16
  27. package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
  28. package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
  29. package/src/components/chat/PlanTodoList.tsx +2 -1
  30. package/src/components/chat/ProjectTrustBanner.tsx +19 -5
  31. package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
  32. package/src/components/chat/SessionTodoList.tsx +8 -7
  33. package/src/components/chat/SessionTreeModal.tsx +10 -9
  34. package/src/components/chat/StreamingStatus.tsx +4 -3
  35. package/src/components/chat/SuggestedReplies.tsx +3 -2
  36. package/src/components/chat/TaskOutcomeCards.tsx +8 -5
  37. package/src/components/chat/TaskPanelModal.tsx +31 -21
  38. package/src/components/chat/TaskProcessPanel.tsx +14 -13
  39. package/src/components/chat/TurnFilesCard.tsx +14 -13
  40. package/src/components/chat/UserMessage.tsx +12 -11
  41. package/src/components/chat/executed-actions-model.ts +47 -35
  42. package/src/components/chat/orchestration-flow-model.ts +16 -5
  43. package/src/components/chat/process-status.ts +8 -7
  44. package/src/components/chat/todo-list-model.ts +5 -4
  45. package/src/components/chat/turn-files.ts +8 -8
  46. package/src/components/settings/AgentCapabilityFields.tsx +24 -21
  47. package/src/components/settings/AgentsSettingsPanel.tsx +30 -25
  48. package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
  49. package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
  50. package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
  51. package/src/components/settings/InsightsSettingsPanel.tsx +43 -28
  52. package/src/components/settings/LlmSettingsPanel.tsx +104 -83
  53. package/src/components/settings/McpSettingsPanel.tsx +193 -71
  54. package/src/components/settings/ModelIdCombobox.tsx +2 -1
  55. package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
  56. package/src/components/settings/PortableSettingsPanel.tsx +83 -55
  57. package/src/components/settings/PythonRunnerSettingsPanel.tsx +22 -19
  58. package/src/components/settings/SecuritySettingsPanel.tsx +55 -31
  59. package/src/components/settings/SettingsNavMenu.tsx +3 -2
  60. package/src/components/settings/SettingsSaveButton.tsx +2 -1
  61. package/src/components/settings/SkillsSettingsPanel.tsx +36 -31
  62. package/src/components/settings/TelemetrySettingsPanel.tsx +15 -12
  63. package/src/components/settings/UsagePanel.tsx +19 -20
  64. package/src/components/settings/WebSearchSettingsPanel.tsx +23 -14
  65. package/src/components/settings/llm-vendors.ts +12 -7
  66. package/src/components/settings/model-capabilities.ts +16 -15
  67. package/src/components/settings/preset-choice-model.ts +6 -3
  68. package/src/fixtures/browser-dev-data.json +24 -24
  69. package/src/hooks/useProjects.ts +1 -1
  70. package/src/i18n/agent-label.ts +15 -0
  71. package/src/i18n/index.tsx +68 -0
  72. package/src/i18n/runtime.ts +117 -0
  73. package/src/i18n/translate.ts +65 -0
  74. package/src/layouts/AgentLayout.tsx +7 -6
  75. package/src/lib/app-release.ts +15 -10
  76. package/src/lib/attachments.ts +24 -7
  77. package/src/lib/browser-dev-electron-mock.ts +34 -34
  78. package/src/lib/chat-title.ts +3 -2
  79. package/src/lib/chat-transport.ts +6 -2
  80. package/src/lib/date-groups.ts +23 -15
  81. package/src/lib/electron-bridge.ts +19 -6
  82. package/src/lib/host-file-drop.ts +68 -0
  83. package/src/lib/host-paste.ts +38 -9
  84. package/src/lib/http-bridge.ts +4 -1
  85. package/src/lib/portable.ts +8 -7
  86. package/src/lib/show-thinking-content.ts +15 -3
  87. package/src/lib/tauri-bridge.ts +5 -0
  88. package/src/main.tsx +25 -2
  89. package/src/pages/AgentPage.tsx +17 -12
  90. package/src/pages/ChatPanelPreviewPage.tsx +46 -44
  91. package/src/pages/SettingsPage.tsx +48 -42
  92. package/src/vite-env.d.ts +3 -0
  93. package/vite.base.js +6 -2
@@ -1,3 +1,4 @@
1
+ import { t } from '@/i18n';
1
2
  import type { GatewayModelEntry } from '@/lib/local-api';
2
3
 
3
4
  /**
@@ -18,17 +19,17 @@ export interface ModelCapabilityChip {
18
19
  const MODALITY_ORDER = ['image', 'pdf', 'audio', 'video'] as const;
19
20
 
20
21
  const MODALITY_LABELS: Record<(typeof MODALITY_ORDER)[number], string> = {
21
- image: '图像',
22
+ image: 'Image',
22
23
  pdf: 'PDF',
23
- audio: '音频',
24
- video: '视频',
24
+ audio: 'Audio',
25
+ video: 'Video',
25
26
  };
26
27
 
27
28
  const MODALITY_TITLES: Record<(typeof MODALITY_ORDER)[number], string> = {
28
- image: '支持图像输入',
29
- pdf: '支持 PDF 输入',
30
- audio: '支持音频输入',
31
- video: '支持视频输入',
29
+ image: 'Supports image input',
30
+ pdf: 'Supports PDF input',
31
+ audio: 'Supports audio input',
32
+ video: 'Supports video input',
32
33
  };
33
34
 
34
35
  /** 上下文窗口:1_000_000 → 1M,131_072 → 131K。 */
@@ -45,8 +46,8 @@ export function formatContextWindow(window: number | null | undefined): string |
45
46
  function unknownChip(): ModelCapabilityChip {
46
47
  return {
47
48
  key: 'unknown',
48
- label: '未识别',
49
- title: '网关目录未匹配到该 id,思考档位与多模态未核实。',
49
+ label: t('Unrecognized'),
50
+ title: t('The gateway catalog has no match for this id. Thinking levels and multimodal input are unverified.'),
50
51
  kind: 'unknown',
51
52
  };
52
53
  }
@@ -68,8 +69,8 @@ export function modelCapabilityChips(
68
69
  const joined = levels.join(' / ');
69
70
  chips.push({
70
71
  key: 'reasoning',
71
- label: options?.detail ? `思考(${joined})` : '思考',
72
- title: `支持思考档位:${joined}`,
72
+ label: options?.detail ? t('Thinking ({levels})', { levels: joined }) : t('Thinking'),
73
+ title: t('Supported thinking levels: {levels}', { levels: joined }),
73
74
  kind: 'reasoning',
74
75
  });
75
76
  }
@@ -80,8 +81,8 @@ export function modelCapabilityChips(
80
81
  seen.add(modality);
81
82
  chips.push({
82
83
  key: modality,
83
- label: MODALITY_LABELS[modality],
84
- title: MODALITY_TITLES[modality],
84
+ label: t(MODALITY_LABELS[modality]),
85
+ title: t(MODALITY_TITLES[modality]),
85
86
  kind: 'modality',
86
87
  });
87
88
  }
@@ -92,7 +93,7 @@ export function modelCapabilityChips(
92
93
  chips.push({
93
94
  key: key,
94
95
  label: modality,
95
- title: `输入模态:${modality}`,
96
+ title: t('Input modality: {modality}', { modality }),
96
97
  kind: 'modality',
97
98
  });
98
99
  }
@@ -102,7 +103,7 @@ export function modelCapabilityChips(
102
103
  chips.push({
103
104
  key: 'window',
104
105
  label: windowLabel,
105
- title: `上下文窗口 ${entry.window} tokens`,
106
+ title: t('Context window {window} tokens', { window: entry.window ?? '' }),
106
107
  kind: 'window',
107
108
  });
108
109
  }
@@ -5,6 +5,7 @@
5
5
  * 载荷之间的转换,不硬编码任何厂商参数。
6
6
  */
7
7
 
8
+ import { t } from '@/i18n';
8
9
  import type {
9
10
  ProviderPresetDescriptor,
10
11
  ProviderPresetOverride,
@@ -28,12 +29,12 @@ export function overrideFromDescriptor(d: ProviderPresetDescriptor): ProviderPre
28
29
  /** 选择器选项标签:`host + 前缀*`,缺省键名退化为「未命名」。 */
29
30
  export function descriptorLabel(d: ProviderPresetDescriptor): string {
30
31
  const parts = [d.host, d.modelPrefix ? `${d.modelPrefix}*` : null].filter(Boolean);
31
- return parts.join(' + ') || '(未命名预制)';
32
+ return parts.join(' + ') || t('(Unnamed preset)');
32
33
  }
33
34
 
34
35
  /** 预制参数的一行摘要,用于生效预览。 */
35
36
  export function summarizePreset(p: ProviderPresetOverride | null): string {
36
- if (!p) return '不下发额外采样参数';
37
+ if (!p) return t('No extra sampling parameters are sent');
37
38
  const parts: string[] = [];
38
39
  if (p.temperature != null) parts.push(`temperature ${p.temperature}`);
39
40
  if (p.topP != null) parts.push(`top_p ${p.topP}`);
@@ -42,7 +43,9 @@ export function summarizePreset(p: ProviderPresetOverride | null): string {
42
43
  if (p.extraBody) {
43
44
  for (const [k, v] of Object.entries(p.extraBody)) parts.push(`${k} ${JSON.stringify(v)}`);
44
45
  }
45
- return parts.length > 0 ? parts.join(' · ') : '不下发额外采样参数(该厂商最优请求即不带采样字段)';
46
+ return parts.length > 0
47
+ ? parts.join(' · ')
48
+ : t('No extra sampling parameters are sent (the best request for this vendor omits sampling fields)');
46
49
  }
47
50
 
48
51
  export function overridesEqual(a: ProviderPresetOverride, b: ProviderPresetOverride): boolean {
@@ -6,11 +6,11 @@
6
6
  {
7
7
  "id": "local-assistant",
8
8
  "slug": "local-assistant",
9
- "name": "电脑操作员",
9
+ "name": "Computer operator",
10
10
  "icon": "Cpu",
11
11
  "color": "#4f46e5",
12
- "description": "默认本地助手,可调用 shell、文件与 MCP 工具。",
13
- "rolePrompt": "你是 **电脑操作员**,本地离线助手,回答时清晰、可执行。",
12
+ "description": "Default local assistant. It can use the shell, files, and MCP tools.",
13
+ "rolePrompt": "You are **Computer operator**, a local offline assistant. Answer clearly and in steps that can be carried out.",
14
14
  "forbiddenPrompt": null,
15
15
  "skillIds": [],
16
16
  "toolPolicy": {
@@ -27,11 +27,11 @@
27
27
  {
28
28
  "id": "data-analyst",
29
29
  "slug": "data-analyst",
30
- "name": "数据分析助手",
30
+ "name": "Data analyst",
31
31
  "icon": "Activity",
32
32
  "color": "#0ea5e9",
33
- "description": "处理本地 CSV 与日志文件:清洗、统计、出图与汇总报告。",
34
- "rolePrompt": "你是 **数据分析助手**,专注本地数据文件的清洗、统计与可视化。\n工作语言:中文。回答简洁、结论先行。\n\n工作纪律:\n1) 先探查文件结构再动手;\n2) 每一步都说明做了什么;\n3) 结果用表格或要点呈现。",
33
+ "description": "Works on local CSV and log files: cleaning, stats, charts, and a summary.",
34
+ "rolePrompt": "You are **Data analyst**. You clean, summarize, and chart local data files.\nBe concise and lead with the conclusion.\n\nWorking rules:\n1) Inspect the file structure before changing it;\n2) Say what each step did;\n3) Present results as a table or bullets.",
35
35
  "forbiddenPrompt": null,
36
36
  "skillIds": [],
37
37
  "toolPolicy": {
@@ -48,11 +48,11 @@
48
48
  {
49
49
  "id": "all-round-assistant",
50
50
  "slug": "all-round-assistant",
51
- "name": "智能助手",
51
+ "name": "Assistant",
52
52
  "icon": "🔮",
53
53
  "color": "#a855f7",
54
- "description": "全能智能体,自动加载全部已安装本地技能和工具,无需限定条件。",
55
- "rolePrompt": "你是 **智能助手**,一个具备高权限且集成了全部本地技能(Skills)的行动体。\n你具备完整的本地命令执行(local_exec_shell)、文件读写与数据分析能力。\n你的系统提示词中已经无条件加载了所有已安装的本地技能与工具。\n请根据用户的需求,直接、高效地调用最合适的本地工具或执行脚本来解决任务。",
54
+ "description": "General agent. It loads every installed local skill and tool, with no extra trigger.",
55
+ "rolePrompt": "You are **Assistant**, with full access to every installed local skill.\nYou can run local commands (local_exec_shell), read and write files, and analyze data.\nEvery installed local skill and tool is already in your system prompt.\nCall the most direct local tool or script for the task.",
56
56
  "forbiddenPrompt": null,
57
57
  "skillIds": [],
58
58
  "toolPolicy": {
@@ -72,7 +72,7 @@
72
72
  "id": "8286e3c8-2d83-409e-930a-3e99b2004d41",
73
73
  "projectId": null,
74
74
  "userId": "local",
75
- "title": "你好",
75
+ "title": "Hello",
76
76
  "agentId": "local-assistant",
77
77
  "createdAt": "2026-07-01T10:05:58.604Z",
78
78
  "updatedAt": "2026-07-01T10:06:06.736Z",
@@ -84,7 +84,7 @@
84
84
  "id": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
85
85
  "projectId": null,
86
86
  "userId": "local",
87
- "title": "启用 csv-data-tools 技能",
87
+ "title": "Enable the csv-data-tools skill",
88
88
  "agentId": "all-round-assistant",
89
89
  "createdAt": "2026-06-22T04:30:54.662Z",
90
90
  "updatedAt": "2026-06-22T04:32:12.657Z",
@@ -96,7 +96,7 @@
96
96
  "id": "d536659c-6492-424d-97e8-2f701a9ce5d8",
97
97
  "projectId": null,
98
98
  "userId": "local",
99
- "title": "新对话",
99
+ "title": "New chat",
100
100
  "agentId": "all-round-assistant",
101
101
  "createdAt": "2026-06-22T04:19:52.607Z",
102
102
  "updatedAt": "2026-06-22T04:20:00.151Z",
@@ -108,7 +108,7 @@
108
108
  "id": "4845b527-c9c9-467c-9d72-5fc5f8763aa5",
109
109
  "projectId": null,
110
110
  "userId": "local",
111
- "title": "新对话",
111
+ "title": "New chat",
112
112
  "agentId": "all-round-assistant",
113
113
  "createdAt": "2026-06-22T04:12:17.151Z",
114
114
  "updatedAt": "2026-06-22T04:12:22.038Z",
@@ -120,7 +120,7 @@
120
120
  "id": "405f5964-f01c-4ece-9c8f-02a73d539469",
121
121
  "projectId": null,
122
122
  "userId": "local",
123
- "title": "启用 csv-data-tools 技能",
123
+ "title": "Enable the csv-data-tools skill",
124
124
  "agentId": "all-round-assistant",
125
125
  "createdAt": "2026-06-22T04:06:21.745Z",
126
126
  "updatedAt": "2026-06-22T04:11:42.782Z",
@@ -132,7 +132,7 @@
132
132
  "id": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
133
133
  "projectId": null,
134
134
  "userId": "local",
135
- "title": "启用 csv-data-tools 技能",
135
+ "title": "Enable the csv-data-tools skill",
136
136
  "agentId": "all-round-assistant",
137
137
  "createdAt": "2026-06-22T04:01:21.158Z",
138
138
  "updatedAt": "2026-06-22T04:05:08.801Z",
@@ -144,7 +144,7 @@
144
144
  "id": "492c1a80-85b2-486a-b365-3d668c9ad7e6",
145
145
  "projectId": null,
146
146
  "userId": "local",
147
- "title": "新对话",
147
+ "title": "New chat",
148
148
  "agentId": "all-round-assistant",
149
149
  "createdAt": "2026-06-22T03:44:47.381Z",
150
150
  "updatedAt": "2026-06-22T03:44:54.479Z",
@@ -171,7 +171,7 @@
171
171
  "id": "ea36a5de-e2e0-428b-b882-f5e67ca326b0",
172
172
  "chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
173
173
  "role": "assistant",
174
- "content": "你好!我是**本地智能助手**,运行在你的本地机器上。\n\n我可以帮你做这些事情:\n\n**💻 本地终端操作**\n- 执行 shell 命令、读写文件\n- 搜索代码库、批量替换\n\n**📊 数据处理**\n- 清洗 CSV / 日志文件\n- 生成统计汇总\n\n直接输入需求即可开始。",
174
+ "content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
175
175
  "createdAt": "2026-07-01T10:06:06.733Z",
176
176
  "messageMetadata": "{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"sessionId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"chatId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"startedAtMs\":1782900364396,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782900364396,\"data\":{\"path\":\"/api/v2/chats/8286e3c8-2d83-409e-930a-3e99b2004d41/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782900366733,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":218,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782900366733,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":218}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782900364396,\"endMs\":1782900366733,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":8596,\"completion\":130,\"total\":8726},\"toolCalls\":0},\"durationMs\":2337}]},\"executedActions\":[]}"
177
177
  },
@@ -179,7 +179,7 @@
179
179
  "id": "ab1387a6-5c43-4d4b-986d-073d98b3f5e2",
180
180
  "chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
181
181
  "role": "user",
182
- "content": "你好",
182
+ "content": "Hello",
183
183
  "createdAt": "2026-07-01T10:06:04.392Z",
184
184
  "messageMetadata": null
185
185
  }
@@ -189,7 +189,7 @@
189
189
  "id": "6599eb84-515c-4f58-bec8-0f37fcfbb234",
190
190
  "chatId": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
191
191
  "role": "assistant",
192
- "content": "好的,我先查看 `csv-data-tools` 技能的可用模块。\n\n**`csv-data-tools`** 技能已就绪。以下是它的能力概览:\n\n---\n\n## 📦 csv-data-tools\n\n- `csv_stats` — 列级统计(行数、缺失、分布)\n- `csv_filter` — 条件过滤与导出\n- `csv_plot` — 快速出图\n\n告诉我文件路径即可开始。",
192
+ "content": "I'll look at the modules in `csv-data-tools`.\n\n**`csv-data-tools`** is ready:\n\n---\n\n## csv-data-tools\n\n- `csv_stats` — column stats (rows, missing values, distribution)\n- `csv_filter` — filter and export\n- `csv_plot` — a quick chart\n\nSend a file path to start.",
193
193
  "createdAt": "2026-06-22T04:32:11.576Z",
194
194
  "messageMetadata": null
195
195
  },
@@ -227,7 +227,7 @@
227
227
  "id": "c8dc9e96-642b-4232-8c3c-c8abc32a615c",
228
228
  "chatId": "405f5964-f01c-4ece-9c8f-02a73d539469",
229
229
  "role": "assistant",
230
- "content": "我来查看当前可用的 MCP 工具,找到 `csv-data-tools` 的相关信息。\n\n看起来 `csv-data-tools` 没有作为独立的 MCP 服务器注册。换个方式,先扫描本地脚本和可执行工具。\n\n结论:该技能当前未安装,可通过技能设置页导入。",
230
+ "content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
231
231
  "createdAt": "2026-06-22T04:11:42.077Z",
232
232
  "messageMetadata": null
233
233
  },
@@ -245,7 +245,7 @@
245
245
  "id": "cfb0f03c-7faa-4594-b29f-f4c11fb09105",
246
246
  "chatId": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
247
247
  "role": "assistant",
248
- "content": "本地模型调用失败:无法连接 OpenAI 兼容网关 (https://api.deepseek.com),请检查 baseUrl / 网络。原始错误: fetch failed",
248
+ "content": "The local model call failed: could not reach the OpenAI-compatible gateway (https://api.deepseek.com). Check baseUrl and the network. Original error: fetch failed",
249
249
  "createdAt": "2026-06-22T04:05:06.751Z",
250
250
  "messageMetadata": null
251
251
  },
@@ -273,7 +273,7 @@
273
273
  "id": "c5d6136b-eaa4-4cc7-878b-450099b7d226",
274
274
  "chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
275
275
  "role": "assistant",
276
- "content": "我来查看当前可用的 MCP 工具,找到 `csv-data-tools` 的相关信息。\n\n看起来 `csv-data-tools` 没有作为独立的 MCP 服务器注册。换个方式,先扫描本地脚本和可执行工具。\n\n结论:该技能当前未安装,可通过技能设置页导入。",
276
+ "content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
277
277
  "createdAt": "2026-06-22T03:37:37.601Z",
278
278
  "messageMetadata": null
279
279
  },
@@ -289,7 +289,7 @@
289
289
  "id": "fa89030e-5db1-4d04-9320-e796baeb89e2",
290
290
  "chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
291
291
  "role": "assistant",
292
- "content": "你好!我是**本地智能助手**,运行在你的本地机器上。\n\n我可以帮你做这些事情:\n\n**💻 本地终端操作**\n- 执行 shell 命令、读写文件\n- 搜索代码库、批量替换\n\n**📊 数据处理**\n- 清洗 CSV / 日志文件\n- 生成统计汇总\n\n直接输入需求即可开始。",
292
+ "content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
293
293
  "createdAt": "2026-06-22T03:34:48.778Z",
294
294
  "messageMetadata": "{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"sessionId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"chatId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"startedAtMs\":1782099286405,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782099286405,\"data\":{\"path\":\"/api/v2/chats/4e997d62-20ef-4937-a866-27778dee3a17/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782099288778,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":224,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782099288778,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":224}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782099286405,\"endMs\":1782099288777,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":2234,\"completion\":130,\"total\":2364},\"toolCalls\":0},\"durationMs\":2372}]},\"executedActions\":[]}"
295
295
  },
@@ -309,7 +309,7 @@
309
309
  "baseUrl": "https://api.deepseek.com",
310
310
  "apiKey": "",
311
311
  "temperature": 0.3,
312
- "systemPrompt": "你是一款本地行动助手,优先给出可执行建议并在需要时调用本地工具。"
312
+ "systemPrompt": "You are a local assistant. Prefer advice that can be carried out, and call local tools when that helps."
313
313
  },
314
314
  "skills": []
315
315
  }
@@ -51,7 +51,7 @@ export async function refreshProjects(): Promise<void> {
51
51
  } catch (err) {
52
52
  if (seq !== requestSeq) return;
53
53
  const message = err instanceof Error ? err.message : String(err);
54
- console.error('获取项目列表失败:', err);
54
+ console.error('Failed to load projects:', err);
55
55
  snapshot = { projects: snapshot.projects, error: message };
56
56
  emit();
57
57
  }
@@ -0,0 +1,15 @@
1
+ import { t } from './runtime';
2
+
3
+ /**
4
+ * 内置智能体的界面名按 id 翻译。库里的 name 仍是种子原文,切换语言不改历史行。
5
+ * 用户自建智能体用库里的名字。
6
+ */
7
+ const BUILTIN_AGENT_LABELS: Record<string, string> = {
8
+ 'local-assistant': 'Computer operator',
9
+ 'all-round-assistant': 'Assistant',
10
+ };
11
+
12
+ export function agentLabel(agent: { id: string; name: string }): string {
13
+ const source = BUILTIN_AGENT_LABELS[agent.id];
14
+ return source ? t(source) : agent.name;
15
+ }
@@ -0,0 +1,68 @@
1
+ import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
2
+
3
+ import {
4
+ configureI18n,
5
+ getLocale,
6
+ registerLocale,
7
+ setLocale,
8
+ subscribeI18n,
9
+ supportedLocales,
10
+ t,
11
+ } from './runtime';
12
+
13
+ export { configureI18n, getLocale, registerLocale, setLocale, supportedLocales, t };
14
+ export { LOCALE_STORAGE_KEY, SOURCE_LOCALE, normalizeLocale, resolveLocale } from './translate';
15
+
16
+ interface I18nContextValue {
17
+ locale: string;
18
+ locales: readonly string[];
19
+ t: typeof t;
20
+ setLocale: (locale: string) => void;
21
+ }
22
+
23
+ const I18nContext = createContext<I18nContextValue | null>(null);
24
+
25
+ /**
26
+ * 包住应用树。语言变化时用 key 重挂子树,这样直接调用 `t()` 的组件也会换文案。
27
+ */
28
+ export function I18nProvider({ children }: { children: ReactNode }) {
29
+ const [locale, setSnapshot] = useState(getLocale);
30
+ useEffect(() => subscribeI18n(() => setSnapshot(getLocale())), []);
31
+ const value = useMemo<I18nContextValue>(
32
+ () => ({
33
+ locale,
34
+ locales: supportedLocales(),
35
+ t,
36
+ setLocale: (next) => {
37
+ if (!setLocale(next)) return;
38
+ window.electron?.setLocale?.(next);
39
+ },
40
+ }),
41
+ [locale],
42
+ );
43
+ return (
44
+ <I18nContext.Provider value={value}>
45
+ <div key={locale} className="contents">
46
+ {children}
47
+ </div>
48
+ </I18nContext.Provider>
49
+ );
50
+ }
51
+
52
+ export function useI18n(): I18nContextValue {
53
+ const value = useContext(I18nContext);
54
+ if (value) return value;
55
+ return {
56
+ locale: getLocale(),
57
+ locales: supportedLocales(),
58
+ t,
59
+ setLocale,
60
+ };
61
+ }
62
+
63
+ /** 语言名用该语言自己的写法,不跟着界面语言翻。 */
64
+ export function localeEndonym(locale: string): string {
65
+ if (locale === 'zh') return '中文'; // i18n:allow 语言名用该语言自己的写法
66
+ if (locale === 'en') return 'English';
67
+ return locale;
68
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * 渲染进程的当前语言。产品入口在 bootstrap 前 registerLocale,
3
+ * bootstrap 再 configureI18n。未配置时只有英语。
4
+ */
5
+
6
+ import {
7
+ LOCALE_STORAGE_KEY,
8
+ SOURCE_LOCALE,
9
+ resolveLocale,
10
+ translate,
11
+ } from './translate';
12
+
13
+ export interface I18nStorage {
14
+ getItem(key: string): string | null;
15
+ setItem(key: string, value: string): void;
16
+ }
17
+
18
+ interface I18nState {
19
+ locales: string[];
20
+ defaultLocale: string;
21
+ locale: string;
22
+ catalogs: Record<string, Record<string, string>>;
23
+ }
24
+
25
+ const state: I18nState = {
26
+ locales: [SOURCE_LOCALE],
27
+ defaultLocale: SOURCE_LOCALE,
28
+ locale: SOURCE_LOCALE,
29
+ catalogs: {},
30
+ };
31
+
32
+ const listeners = new Set<() => void>();
33
+
34
+ function emit(): void {
35
+ for (const listener of listeners) listener();
36
+ }
37
+
38
+ export function subscribeI18n(listener: () => void): () => void {
39
+ listeners.add(listener);
40
+ return () => {
41
+ listeners.delete(listener);
42
+ };
43
+ }
44
+
45
+ export function registerLocale(locale: string, messages: Record<string, string>): void {
46
+ const normalized = locale.trim();
47
+ if (!normalized || normalized === SOURCE_LOCALE) return;
48
+ state.catalogs[normalized] = { ...state.catalogs[normalized], ...messages };
49
+ emit();
50
+ }
51
+
52
+ function readStorage(): I18nStorage | null {
53
+ if (typeof localStorage === 'undefined') return null;
54
+ return localStorage;
55
+ }
56
+
57
+ function systemLocale(): string | null {
58
+ if (typeof navigator === 'undefined') return null;
59
+ return navigator.language || null;
60
+ }
61
+
62
+ export function configureI18n(options: {
63
+ locales: readonly string[];
64
+ defaultLocale: string;
65
+ systemLocale?: string | null;
66
+ storage?: I18nStorage | null;
67
+ }): void {
68
+ const locales = [...new Set(options.locales.filter((locale) => locale.length > 0))];
69
+ state.locales = locales.length > 0 ? locales : [SOURCE_LOCALE];
70
+ state.defaultLocale = options.defaultLocale || SOURCE_LOCALE;
71
+ const storage = options.storage === undefined ? readStorage() : options.storage;
72
+ state.locale = resolveLocale({
73
+ supported: state.locales,
74
+ defaultLocale: state.defaultLocale,
75
+ saved: storage?.getItem(LOCALE_STORAGE_KEY) ?? null,
76
+ system: options.systemLocale === undefined ? systemLocale() : options.systemLocale,
77
+ });
78
+ applyDocumentLang(state.locale);
79
+ emit();
80
+ }
81
+
82
+ export function getLocale(): string {
83
+ return state.locale;
84
+ }
85
+
86
+ export function supportedLocales(): readonly string[] {
87
+ return state.locales;
88
+ }
89
+
90
+ export function setLocale(locale: string): boolean {
91
+ if (!state.locales.includes(locale)) return false;
92
+ if (state.locale === locale) return true;
93
+ state.locale = locale;
94
+ const storage = readStorage();
95
+ storage?.setItem(LOCALE_STORAGE_KEY, locale);
96
+ applyDocumentLang(locale);
97
+ emit();
98
+ return true;
99
+ }
100
+
101
+ export function t(message: string, params?: Record<string, string | number>): string {
102
+ return translate(message, state.locale, state.catalogs, params);
103
+ }
104
+
105
+ function applyDocumentLang(locale: string): void {
106
+ if (typeof document === 'undefined') return;
107
+ document.documentElement.lang = locale === 'zh' ? 'zh-CN' : locale;
108
+ }
109
+
110
+ /** 测试隔离。生产代码不调用。 */
111
+ export function resetI18nForTests(): void {
112
+ state.locales = [SOURCE_LOCALE];
113
+ state.defaultLocale = SOURCE_LOCALE;
114
+ state.locale = SOURCE_LOCALE;
115
+ state.catalogs = {};
116
+ listeners.clear();
117
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * 界面文案查找。源语言是英语:调用处写英文句子,应用层语言包把英文映射到译文。
3
+ * 缺译文时原样返回英文,漏翻在界面上看得见。
4
+ */
5
+
6
+ export const SOURCE_LOCALE = 'en';
7
+ export const LOCALE_STORAGE_KEY = 'agent-shell.locale';
8
+
9
+ /** `zh-CN` / `en-US` 收成语言包用的主标签。不认识的标签保留主段。 */
10
+ export function normalizeLocale(raw: string | null | undefined): string | null {
11
+ if (!raw) return null;
12
+ const lower = raw.trim().toLowerCase().replace(/_/g, '-');
13
+ if (!lower) return null;
14
+ if (lower.startsWith('zh')) return 'zh';
15
+ if (lower.startsWith('en')) return 'en';
16
+ const primary = lower.split('-')[0];
17
+ return primary || null;
18
+ }
19
+
20
+ export function resolveLocale(options: {
21
+ supported: readonly string[];
22
+ defaultLocale: string;
23
+ saved: string | null;
24
+ system: string | null;
25
+ }): string {
26
+ const supported = options.supported.filter((locale) => locale.length > 0);
27
+ const list = supported.length > 0 ? supported : [SOURCE_LOCALE];
28
+ const pick = (value: string | null): string | null => {
29
+ if (!value) return null;
30
+ if (list.includes(value)) return value;
31
+ const normalized = normalizeLocale(value);
32
+ if (normalized && list.includes(normalized)) return normalized;
33
+ return null;
34
+ };
35
+ return (
36
+ pick(options.saved) ??
37
+ pick(options.system) ??
38
+ pick(options.defaultLocale) ??
39
+ list[0] ??
40
+ SOURCE_LOCALE
41
+ );
42
+ }
43
+
44
+ export function formatMessage(
45
+ template: string,
46
+ params?: Record<string, string | number>,
47
+ ): string {
48
+ if (!params) return template;
49
+ return template.replace(/\{(\w+)\}/g, (whole, name: string) => {
50
+ const value = params[name];
51
+ return value === undefined ? whole : String(value);
52
+ });
53
+ }
54
+
55
+ /** 语言包的键是英文源句。源语言不查表。 */
56
+ export function translate(
57
+ message: string,
58
+ locale: string,
59
+ catalogs: Readonly<Record<string, Readonly<Record<string, string>>>>,
60
+ params?: Record<string, string | number>,
61
+ ): string {
62
+ const table = locale === SOURCE_LOCALE ? undefined : catalogs[locale];
63
+ const template = table?.[message] ?? message;
64
+ return formatMessage(template, params);
65
+ }
@@ -17,6 +17,7 @@ import {
17
17
  type UseChatsAndAgentsResult,
18
18
  } from '@/hooks/useChatsAndAgents';
19
19
  import { hostToolChrome, sanitizeRightPanelKind, settingsChrome } from '@/lib/host-tools';
20
+ import { t } from '@/i18n';
20
21
 
21
22
  /**
22
23
  * AgentLayout wraps `/agent`, `/agent/:chatId`, and `/` (default) with a
@@ -560,8 +561,8 @@ function AgentLayoutContent() {
560
561
  type="button"
561
562
  onClick={toggleSidebarCollapsed}
562
563
  className="flex h-7 w-7 items-center justify-center rounded-full text-agent-muted-foreground transition-colors duration-200 hover:bg-agent-foreground/5 hover:text-agent-foreground"
563
- title="展开侧边栏"
564
- aria-label="展开侧边栏"
564
+ title={t('Expand sidebar')}
565
+ aria-label={t('Expand sidebar')}
565
566
  >
566
567
  <LuPanelLeftOpen className="h-3.5 w-3.5" />
567
568
  </button>
@@ -581,11 +582,11 @@ function AgentLayoutContent() {
581
582
  <div
582
583
  role="separator"
583
584
  aria-orientation="vertical"
584
- aria-label="调整侧边栏宽度"
585
+ aria-label={t('Resize sidebar')}
585
586
  onMouseDown={() => setIsResizing(true)}
586
587
  onDoubleClick={resetWidth}
587
588
  className="w-1 flex-shrink-0 cursor-col-resize bg-transparent transition-colors hover:bg-agent-border/60"
588
- title="拖拽调整侧边栏宽度(双击恢复默认)"
589
+ title={t('Drag to resize the sidebar (double-click to reset)')}
589
590
  />
590
591
  </>
591
592
  )}
@@ -612,11 +613,11 @@ function AgentLayoutContent() {
612
613
  <div
613
614
  role="separator"
614
615
  aria-orientation="vertical"
615
- aria-label="调整右侧面板宽度"
616
+ aria-label={t('Resize right panel')}
616
617
  onMouseDown={() => setIsTerminalResizing(true)}
617
618
  onDoubleClick={resetTerminalWidth}
618
619
  className="w-1 flex-shrink-0 cursor-col-resize bg-transparent transition-colors hover:bg-agent-border/60"
619
- title="拖拽调整右侧面板宽度(双击恢复默认)"
620
+ title={t('Drag to resize the right panel (double-click to reset)')}
620
621
  />
621
622
  <div
622
623
  className={`h-full flex-shrink-0 overflow-hidden rounded-agent-lg shadow-sm ${