@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
|
@@ -8,6 +8,7 @@ import { LuFolder, LuFolderPlus, LuX } from 'react-icons/lu';
|
|
|
8
8
|
import { BRAND_NAME } from '@/brand';
|
|
9
9
|
import { ConfirmDialog } from '@/components/ConfirmDialog';
|
|
10
10
|
import { getElectronBridge } from '@/lib/electron-bridge';
|
|
11
|
+
import { t } from '@/i18n';
|
|
11
12
|
|
|
12
13
|
export interface ProjectFormValues {
|
|
13
14
|
name: string;
|
|
@@ -65,7 +66,7 @@ export function CreateProjectModal({
|
|
|
65
66
|
if (typeof document === 'undefined') return null;
|
|
66
67
|
|
|
67
68
|
const trimmed = name.trim();
|
|
68
|
-
const homePreview = `~/Documents/${BRAND_NAME}/${trimmed || '
|
|
69
|
+
const homePreview = `~/Documents/${BRAND_NAME}/${trimmed || t('project-name')}`;
|
|
69
70
|
const titleId = isEdit ? 'edit-project-title' : 'create-project-title';
|
|
70
71
|
|
|
71
72
|
const addSourceFolder = (folder: string) => {
|
|
@@ -84,11 +85,11 @@ export function CreateProjectModal({
|
|
|
84
85
|
}
|
|
85
86
|
const picker = getElectronBridge()?.local?.selectDirectory;
|
|
86
87
|
if (!picker) {
|
|
87
|
-
setError('
|
|
88
|
+
setError(t('Enter a folder path'));
|
|
88
89
|
return;
|
|
89
90
|
}
|
|
90
91
|
try {
|
|
91
|
-
const result = await picker({ title: '
|
|
92
|
+
const result = await picker({ title: t('Add source folder') });
|
|
92
93
|
if (!result || result.canceled || result.filePaths.length === 0) return;
|
|
93
94
|
addSourceFolder(result.filePaths[0]);
|
|
94
95
|
} catch (err) {
|
|
@@ -137,13 +138,13 @@ export function CreateProjectModal({
|
|
|
137
138
|
<div className="flex w-[440px] max-w-[92vw] flex-col overflow-hidden rounded-2xl border border-agent-border bg-agent-canvas shadow-2xl">
|
|
138
139
|
<div className="flex items-center justify-between px-5 pb-1 pt-4">
|
|
139
140
|
<h2 id={titleId} className="text-base font-semibold text-agent-foreground">
|
|
140
|
-
{isEdit ? '
|
|
141
|
+
{isEdit ? t('Edit project') : t('New project')}
|
|
141
142
|
</h2>
|
|
142
143
|
<button
|
|
143
144
|
type="button"
|
|
144
145
|
onClick={onClose}
|
|
145
146
|
className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
146
|
-
aria-label=
|
|
147
|
+
aria-label={t('Close')}
|
|
147
148
|
>
|
|
148
149
|
<LuX className="h-4 w-4" />
|
|
149
150
|
</button>
|
|
@@ -159,7 +160,7 @@ export function CreateProjectModal({
|
|
|
159
160
|
onKeyDown={(event) => {
|
|
160
161
|
if (event.key === 'Enter') void handleSubmit();
|
|
161
162
|
}}
|
|
162
|
-
placeholder=
|
|
163
|
+
placeholder={t('Project name')}
|
|
163
164
|
autoFocus
|
|
164
165
|
className="h-full min-w-0 flex-1 bg-transparent text-sm text-agent-foreground outline-none placeholder:text-agent-muted-foreground"
|
|
165
166
|
data-testid={isEdit ? 'edit-project-name' : 'create-project-name'}
|
|
@@ -167,14 +168,14 @@ export function CreateProjectModal({
|
|
|
167
168
|
</label>
|
|
168
169
|
{!isEdit && (
|
|
169
170
|
<p className="px-1 text-[11px] text-agent-muted-foreground">
|
|
170
|
-
|
|
171
|
+
{t('Default location: {path}', { path: homePreview })}
|
|
171
172
|
</p>
|
|
172
173
|
)}
|
|
173
174
|
|
|
174
175
|
<div>
|
|
175
|
-
<div className="mb-1 text-sm text-agent-foreground"
|
|
176
|
+
<div className="mb-1 text-sm text-agent-foreground">{t('Source folders')}</div>
|
|
176
177
|
<p className="mb-2 text-[11px] text-agent-muted-foreground">
|
|
177
|
-
|
|
178
|
+
{t('These folders and their subfolders are readable and writable.')}
|
|
178
179
|
</p>
|
|
179
180
|
<div className="overflow-hidden rounded-2xl border border-agent-border">
|
|
180
181
|
{sourceFolders.map((folder) => (
|
|
@@ -192,7 +193,7 @@ export function CreateProjectModal({
|
|
|
192
193
|
setSourceFolders((prev) => prev.filter((item) => item !== folder))
|
|
193
194
|
}
|
|
194
195
|
className="rounded-full p-0.5 text-agent-muted-foreground hover:text-agent-foreground"
|
|
195
|
-
aria-label={
|
|
196
|
+
aria-label={t('Remove {folder}', { folder })}
|
|
196
197
|
>
|
|
197
198
|
<LuX className="h-3.5 w-3.5" />
|
|
198
199
|
</button>
|
|
@@ -210,7 +211,7 @@ export function CreateProjectModal({
|
|
|
210
211
|
void handleAddFolder();
|
|
211
212
|
}
|
|
212
213
|
}}
|
|
213
|
-
placeholder=
|
|
214
|
+
placeholder={t('Type a path, or click Add to browse')}
|
|
214
215
|
className="h-7 min-w-0 flex-1 bg-transparent text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground"
|
|
215
216
|
data-testid={isEdit ? 'edit-project-folder-path' : 'create-project-folder-path'}
|
|
216
217
|
/>
|
|
@@ -220,7 +221,7 @@ export function CreateProjectModal({
|
|
|
220
221
|
className="shrink-0 rounded-full px-2 py-1 text-xs font-medium text-agent-foreground hover:bg-agent-muted"
|
|
221
222
|
data-testid={isEdit ? 'edit-project-add-folder' : 'create-project-add-folder'}
|
|
222
223
|
>
|
|
223
|
-
|
|
224
|
+
{t('Add')}
|
|
224
225
|
</button>
|
|
225
226
|
</div>
|
|
226
227
|
</div>
|
|
@@ -239,11 +240,11 @@ export function CreateProjectModal({
|
|
|
239
240
|
type="button"
|
|
240
241
|
onClick={() => setDeleteConfirmOpen(true)}
|
|
241
242
|
disabled={deleting || submitting}
|
|
242
|
-
title=
|
|
243
|
+
title={t('Delete project (its chats stay as chats without a project)')}
|
|
243
244
|
className="mr-auto h-9 rounded-full px-3 text-sm text-agent-destructive transition-colors hover:bg-agent-destructive/10 disabled:opacity-40"
|
|
244
245
|
data-testid="edit-project-delete"
|
|
245
246
|
>
|
|
246
|
-
|
|
247
|
+
{t('Delete local project')}
|
|
247
248
|
</button>
|
|
248
249
|
)}
|
|
249
250
|
<button
|
|
@@ -253,7 +254,7 @@ export function CreateProjectModal({
|
|
|
253
254
|
onDelete ? '' : 'ml-auto'
|
|
254
255
|
}`}
|
|
255
256
|
>
|
|
256
|
-
|
|
257
|
+
{t('Cancel')}
|
|
257
258
|
</button>
|
|
258
259
|
<button
|
|
259
260
|
type="button"
|
|
@@ -262,16 +263,25 @@ export function CreateProjectModal({
|
|
|
262
263
|
className="h-9 rounded-full bg-agent-foreground px-4 text-sm font-medium text-agent-canvas transition-opacity disabled:opacity-40"
|
|
263
264
|
data-testid={isEdit ? 'edit-project-submit' : 'create-project-submit'}
|
|
264
265
|
>
|
|
265
|
-
{submitting
|
|
266
|
+
{submitting
|
|
267
|
+
? isEdit
|
|
268
|
+
? t('Saving…')
|
|
269
|
+
: t('Creating…')
|
|
270
|
+
: isEdit
|
|
271
|
+
? t('Save')
|
|
272
|
+
: t('Create project')}
|
|
266
273
|
</button>
|
|
267
274
|
</div>
|
|
268
275
|
</div>
|
|
269
276
|
{onDelete && (
|
|
270
277
|
<ConfirmDialog
|
|
271
278
|
open={deleteConfirmOpen}
|
|
272
|
-
title=
|
|
273
|
-
description={
|
|
274
|
-
|
|
279
|
+
title={t('Delete project')}
|
|
280
|
+
description={t(
|
|
281
|
+
'Delete project "{name}"? Its chats will stay as chats without a project.',
|
|
282
|
+
{ name: trimmed || t('Untitled') },
|
|
283
|
+
)}
|
|
284
|
+
confirmLabel={t('Delete project')}
|
|
275
285
|
pending={deleting}
|
|
276
286
|
onCancel={() => {
|
|
277
287
|
if (deleting) return;
|
|
@@ -3,6 +3,7 @@ import { createPortal } from 'react-dom';
|
|
|
3
3
|
import { LuX } from 'react-icons/lu';
|
|
4
4
|
import type { LlmSettings } from '@/lib/local-api';
|
|
5
5
|
import { LlmSettingsPanel } from '@/components/settings/LlmSettingsPanel';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
|
|
7
8
|
interface LocalLlmSettingsModalProps {
|
|
8
9
|
open: boolean;
|
|
@@ -43,12 +44,12 @@ export function LocalLlmSettingsModal({ open, onClose, onSaved }: LocalLlmSettin
|
|
|
43
44
|
className="flex h-[560px] max-h-[90vh] w-[540px] max-w-[92vw] flex-col overflow-hidden rounded-agent-lg border border-agent-border bg-agent-canvas shadow-2xl animate-fade-in"
|
|
44
45
|
>
|
|
45
46
|
<div className="flex h-9 flex-shrink-0 items-center justify-between border-b border-agent-border px-3">
|
|
46
|
-
<h2 className="text-xs font-semibold text-agent-foreground"
|
|
47
|
+
<h2 className="text-xs font-semibold text-agent-foreground">{t('Local model settings')}</h2>
|
|
47
48
|
<button
|
|
48
49
|
type="button"
|
|
49
50
|
onClick={onClose}
|
|
50
51
|
className="rounded-full p-1 text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
51
|
-
aria-label=
|
|
52
|
+
aria-label={t('Close')}
|
|
52
53
|
>
|
|
53
54
|
<LuX className="h-4 w-4" />
|
|
54
55
|
</button>
|
|
@@ -62,7 +63,7 @@ export function LocalLlmSettingsModal({ open, onClose, onSaved }: LocalLlmSettin
|
|
|
62
63
|
onClick={onClose}
|
|
63
64
|
className="h-7 rounded-full px-3 text-xs font-medium text-agent-muted-foreground transition-colors hover:bg-agent-muted hover:text-agent-foreground"
|
|
64
65
|
>
|
|
65
|
-
|
|
66
|
+
{t('Close')}
|
|
66
67
|
</button>
|
|
67
68
|
</div>
|
|
68
69
|
</div>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getElectronBridge,
|
|
8
8
|
type AppReleaseSnapshot,
|
|
9
9
|
} from '@/lib/electron-bridge';
|
|
10
|
+
import { t } from '@/i18n';
|
|
10
11
|
|
|
11
12
|
export function useAppRelease(): AppReleaseView & { run: () => void } {
|
|
12
13
|
const [snap, setSnap] = useState<AppReleaseSnapshot | null>(null);
|
|
@@ -59,7 +60,7 @@ export function SidebarVersionLabel({ release }: { release: AppReleaseView }) {
|
|
|
59
60
|
<span
|
|
60
61
|
className="ml-auto text-[10px] tabular-nums text-agent-muted-foreground/70"
|
|
61
62
|
data-testid="sidebar-app-version"
|
|
62
|
-
title={
|
|
63
|
+
title={t('Current version {version}', { version: release.version })}
|
|
63
64
|
>
|
|
64
65
|
v{release.version}
|
|
65
66
|
</span>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Suspense, lazy, useEffect, useState } from 'react';
|
|
2
2
|
import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
|
|
4
5
|
// 把 xterm 那一坨 (~300KB) 拆到独立 chunk —— 用户没打开终端就不下载。
|
|
5
6
|
// 跟原 Next.js 版本里 `dynamic(() => import('./TerminalView'), {ssr:false})`
|
|
@@ -42,9 +43,9 @@ export function TerminalPanel({
|
|
|
42
43
|
return (
|
|
43
44
|
<div className="flex h-full w-full items-center justify-center bg-black p-4 text-center font-mono text-xs text-white">
|
|
44
45
|
<div>
|
|
45
|
-
<p className="mb-2">⚠️
|
|
46
|
+
<p className="mb-2">⚠️ {t('This panel needs the desktop client or a BS server connection.')}</p>
|
|
46
47
|
<p className="text-white/60">
|
|
47
|
-
|
|
48
|
+
{t('This page has no host bridge, so it cannot connect to a local PTY.')}
|
|
48
49
|
</p>
|
|
49
50
|
</div>
|
|
50
51
|
</div>
|
|
@@ -7,6 +7,7 @@ import '@xterm/xterm/css/xterm.css';
|
|
|
7
7
|
import { DockHeaderButton, DockPanelHeader } from '@/components/DockPanelHeader';
|
|
8
8
|
import { getElectronBridge } from '@/lib/electron-bridge';
|
|
9
9
|
import { BRAND_NAME } from '@/brand';
|
|
10
|
+
import { t } from '@/i18n';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Renders an xterm.js terminal wired up to the main-process PTY via the
|
|
@@ -48,7 +49,7 @@ export function TerminalView({
|
|
|
48
49
|
const terminal = bridge?.terminal;
|
|
49
50
|
if (!terminal) {
|
|
50
51
|
setStatus('error');
|
|
51
|
-
setStatusMsg('window.electron.terminal
|
|
52
|
+
setStatusMsg(t('window.electron.terminal is unavailable'));
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -199,23 +200,27 @@ export function TerminalView({
|
|
|
199
200
|
}, []);
|
|
200
201
|
|
|
201
202
|
const statusLabel =
|
|
202
|
-
status === 'starting'
|
|
203
|
+
status === 'starting'
|
|
204
|
+
? t('Starting…')
|
|
205
|
+
: status === 'ready'
|
|
206
|
+
? t('Online')
|
|
207
|
+
: t('Error: {message}', { message: statusMsg });
|
|
203
208
|
|
|
204
209
|
return (
|
|
205
210
|
<div className="flex h-full w-full flex-col bg-[#0b0b0c]">
|
|
206
211
|
<DockPanelHeader
|
|
207
|
-
title={
|
|
212
|
+
title={t('{brand} · Terminal · {status}', { brand: BRAND_NAME, status: statusLabel })}
|
|
208
213
|
onClose={onClose}
|
|
209
|
-
closeLabel=
|
|
214
|
+
closeLabel={t('Close terminal panel')}
|
|
210
215
|
actions={
|
|
211
216
|
onShowTaskProcess && (
|
|
212
217
|
<DockHeaderButton
|
|
213
218
|
icon={<LuListTodo className="h-3 w-3" />}
|
|
214
|
-
label=
|
|
219
|
+
label={t('Background tasks')}
|
|
215
220
|
title={
|
|
216
221
|
taskProcessTitle
|
|
217
|
-
?
|
|
218
|
-
: '
|
|
222
|
+
? t('Switch to background reasoning: {title}', { title: taskProcessTitle })
|
|
223
|
+
: t('Switch to background reasoning')
|
|
219
224
|
}
|
|
220
225
|
onClick={onShowTaskProcess}
|
|
221
226
|
/>
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type ApprovalDecisionKind,
|
|
4
4
|
type ApprovalPromptRequest,
|
|
5
5
|
} from '@/lib/electron-bridge';
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* ApprovalPromptMenu — W4-1 审批代数的输入区 UI。
|
|
@@ -19,10 +20,10 @@ import {
|
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
22
|
const MODE_LABEL: Record<string, string> = {
|
|
22
|
-
read: '
|
|
23
|
-
safe_write: '
|
|
24
|
-
destructive: '
|
|
25
|
-
other: '
|
|
23
|
+
read: 'Read only',
|
|
24
|
+
safe_write: 'Write',
|
|
25
|
+
destructive: 'Dangerous',
|
|
26
|
+
other: 'Other',
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
function summarizeArguments(args: Record<string, unknown>): string {
|
|
@@ -45,15 +46,15 @@ interface DecisionButton {
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
const ALLOW_BUTTONS: DecisionButton[] = [
|
|
48
|
-
{ kind: 'allow_once', label: '
|
|
49
|
-
{ kind: 'allow_for_session', label: '
|
|
50
|
-
{ kind: 'allow_always', label: '
|
|
49
|
+
{ kind: 'allow_once', label: 'Allow once', tone: 'allow' },
|
|
50
|
+
{ kind: 'allow_for_session', label: 'Allow for this chat', tone: 'allow' },
|
|
51
|
+
{ kind: 'allow_always', label: 'Always allow', tone: 'allow' },
|
|
51
52
|
];
|
|
52
53
|
|
|
53
54
|
const DENY_BUTTONS: DecisionButton[] = [
|
|
54
|
-
{ kind: 'deny_once', label: '
|
|
55
|
-
{ kind: 'deny_for_session', label: '
|
|
56
|
-
{ kind: 'deny_always', label: '
|
|
55
|
+
{ kind: 'deny_once', label: 'Deny once', tone: 'deny' },
|
|
56
|
+
{ kind: 'deny_for_session', label: 'Deny for this chat', tone: 'deny' },
|
|
57
|
+
{ kind: 'deny_always', label: 'Always deny', tone: 'deny' },
|
|
57
58
|
];
|
|
58
59
|
|
|
59
60
|
function DecisionRow({
|
|
@@ -77,7 +78,7 @@ function DecisionRow({
|
|
|
77
78
|
: 'rounded-agent-md border border-agent-border px-3 py-1.5 text-xs font-medium text-agent-foreground transition hover:bg-agent-muted'
|
|
78
79
|
}
|
|
79
80
|
>
|
|
80
|
-
{b.label}
|
|
81
|
+
{t(b.label)}
|
|
81
82
|
</button>
|
|
82
83
|
))}
|
|
83
84
|
</div>
|
|
@@ -120,27 +121,29 @@ export function ApprovalPromptMenu({
|
|
|
120
121
|
<div className="text-xs font-medium text-agent-foreground">
|
|
121
122
|
{isEgress ? (
|
|
122
123
|
<>
|
|
123
|
-
Agent
|
|
124
|
+
{t('Agent requests internet access')}
|
|
124
125
|
<span className="mx-1 font-mono">
|
|
125
126
|
{egressHost}
|
|
126
127
|
{egressPort !== null ? `:${egressPort}` : ''}
|
|
127
128
|
</span>
|
|
128
129
|
</>
|
|
129
130
|
) : isSandboxEscalation ? (
|
|
130
|
-
<>Agent
|
|
131
|
+
<>{t('Agent requests to run a command outside the workspace')}</>
|
|
131
132
|
) : (
|
|
132
133
|
<>
|
|
133
|
-
Agent
|
|
134
|
+
{t('Agent requests to run')}
|
|
134
135
|
<span className="mx-1 font-mono">{current.toolName}</span>
|
|
135
136
|
<span className="text-agent-muted-foreground">
|
|
136
|
-
|
|
137
|
+
{t('({mode} action)', {
|
|
138
|
+
mode: MODE_LABEL[current.mode] ? t(MODE_LABEL[current.mode]) : current.mode,
|
|
139
|
+
})}
|
|
137
140
|
</span>
|
|
138
141
|
</>
|
|
139
142
|
)}
|
|
140
143
|
</div>
|
|
141
144
|
{pendingCount > 0 && (
|
|
142
145
|
<div className="mt-0.5 text-[11px] text-agent-muted-foreground">
|
|
143
|
-
|
|
146
|
+
{t('{count} more awaiting approval', { count: pendingCount })}
|
|
144
147
|
</div>
|
|
145
148
|
)}
|
|
146
149
|
</div>
|
|
@@ -152,23 +155,30 @@ export function ApprovalPromptMenu({
|
|
|
152
155
|
</pre>
|
|
153
156
|
{isEgress ? (
|
|
154
157
|
<p className="mt-2 text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
{t(
|
|
159
|
+
'This domain is not on the egress allowlist. Check the full spelling before allowing it (lookalike domains often use similar spellings, such as cdn.jsdelivr.net.evil.com). Allowing it applies only to this chat. To allow it permanently, add the domain to the egress allowlist in Settings.',
|
|
160
|
+
)}
|
|
158
161
|
</p>
|
|
159
162
|
) : isSandboxEscalation ? (
|
|
160
163
|
<p className="mt-2 text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
161
|
-
|
|
164
|
+
{t(
|
|
165
|
+
'The workspace sandbox blocked this command. Allowing it retries only this command, without limiting where it can write.',
|
|
166
|
+
)}
|
|
162
167
|
</p>
|
|
163
168
|
) : (
|
|
164
169
|
<>
|
|
165
170
|
{current.category !== current.toolName && (
|
|
166
171
|
<p className="mt-2 text-[11px] text-agent-muted-foreground">
|
|
167
|
-
|
|
172
|
+
{t(
|
|
173
|
+
'Saved category: {category} (a chat or always decision applies to every call in this category)',
|
|
174
|
+
{ category: current.category },
|
|
175
|
+
)}
|
|
168
176
|
</p>
|
|
169
177
|
)}
|
|
170
178
|
<p className="mt-2 text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
171
|
-
|
|
179
|
+
{t(
|
|
180
|
+
'Runs in the workspace sandbox. Writes outside it are blocked. To write outside, switch the sandbox to "Full access" and try again.',
|
|
181
|
+
)}
|
|
172
182
|
</p>
|
|
173
183
|
</>
|
|
174
184
|
)}
|
|
@@ -198,13 +208,17 @@ export function ApprovalPromptMenu({
|
|
|
198
208
|
className="shrink-0 rounded-agent-md bg-agent-destructive px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90"
|
|
199
209
|
data-testid="approval-abort"
|
|
200
210
|
>
|
|
201
|
-
|
|
211
|
+
{t('Abort this turn')}
|
|
202
212
|
</button>
|
|
203
213
|
</div>
|
|
204
214
|
<p className="text-[11px] leading-relaxed text-agent-muted-foreground">
|
|
205
215
|
{isEgress
|
|
206
|
-
?
|
|
207
|
-
|
|
216
|
+
? t(
|
|
217
|
+
'"This chat" decisions are remembered in this chat until the agent process exits. With no action for about 3 minutes, the request is denied.',
|
|
218
|
+
)
|
|
219
|
+
: t(
|
|
220
|
+
'"This chat" decisions are remembered in this chat. "Always" decisions are written to ~/.steerable/approvals.json on this computer and apply across chats. With no action for about 2 minutes, the request is denied.',
|
|
221
|
+
)}
|
|
208
222
|
</p>
|
|
209
223
|
</div>
|
|
210
224
|
</div>
|
|
@@ -67,7 +67,7 @@ export function ApprovalPromptProvider({ children }: { children: ReactNode }) {
|
|
|
67
67
|
if (active) addRequests(requests, 'prepend');
|
|
68
68
|
})
|
|
69
69
|
.catch((error) => {
|
|
70
|
-
console.error('
|
|
70
|
+
console.error('Failed to restore pending approval requests:', error);
|
|
71
71
|
});
|
|
72
72
|
return () => {
|
|
73
73
|
active = false;
|
|
@@ -65,7 +65,7 @@ export function AskUserPromptProvider({ children }: { children: ReactNode }) {
|
|
|
65
65
|
if (active) addRequests(requests, 'prepend');
|
|
66
66
|
})
|
|
67
67
|
.catch((error) => {
|
|
68
|
-
console.error('
|
|
68
|
+
console.error('Failed to restore pending questions:', error);
|
|
69
69
|
});
|
|
70
70
|
return () => {
|
|
71
71
|
active = false;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { LuCheck, LuChevronLeft, LuPencil, LuWand } from 'react-icons/lu';
|
|
3
|
+
import { t } from '@/i18n';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* AskUserQuestionMenu — W8 结构化提问的分步菜单卡片。
|
|
@@ -94,7 +95,7 @@ function initialDraft(question: AskQuestion, answer: AnswerValue | undefined): {
|
|
|
94
95
|
return {
|
|
95
96
|
selected,
|
|
96
97
|
customMode: customBits.length > 0,
|
|
97
|
-
customText: customBits.join('
|
|
98
|
+
customText: customBits.join(t(', ')),
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
if (typeof answer === 'string' && answer.length > 0) {
|
|
@@ -129,7 +130,7 @@ function Footer({
|
|
|
129
130
|
className="inline-flex items-center gap-1 rounded-agent-md border border-agent-border px-2.5 py-1.5 text-xs font-medium text-agent-foreground transition hover:bg-agent-muted"
|
|
130
131
|
>
|
|
131
132
|
<LuChevronLeft size={12} />
|
|
132
|
-
|
|
133
|
+
{t('Previous')}
|
|
133
134
|
</button>
|
|
134
135
|
)}
|
|
135
136
|
{onAutoContinue && (
|
|
@@ -139,7 +140,7 @@ function Footer({
|
|
|
139
140
|
className="inline-flex items-center gap-1 rounded-agent-md px-2.5 py-1.5 text-xs font-medium text-agent-muted-foreground transition hover:bg-agent-muted hover:text-agent-foreground"
|
|
140
141
|
>
|
|
141
142
|
<LuWand size={12} />
|
|
142
|
-
|
|
143
|
+
{t('Let AI decide')}
|
|
143
144
|
</button>
|
|
144
145
|
)}
|
|
145
146
|
</div>
|
|
@@ -175,7 +176,7 @@ function PrimaryButton({
|
|
|
175
176
|
].join(' ')}
|
|
176
177
|
>
|
|
177
178
|
<LuCheck size={12} />
|
|
178
|
-
{isLast ? '
|
|
179
|
+
{isLast ? t('Submit') : t('Next')}
|
|
179
180
|
</button>
|
|
180
181
|
);
|
|
181
182
|
}
|
|
@@ -352,7 +353,7 @@ function SelectQuestionStep({
|
|
|
352
353
|
<LuCheck size={10} />
|
|
353
354
|
</span>
|
|
354
355
|
<LuPencil size={12} className="shrink-0" />
|
|
355
|
-
<span className="min-w-0 flex-1"
|
|
356
|
+
<span className="min-w-0 flex-1">{t('Other / custom')}</span>
|
|
356
357
|
</button>
|
|
357
358
|
);
|
|
358
359
|
};
|
|
@@ -395,7 +396,7 @@ function SelectQuestionStep({
|
|
|
395
396
|
commitMulti();
|
|
396
397
|
}
|
|
397
398
|
}}
|
|
398
|
-
placeholder={question.placeholder || '
|
|
399
|
+
placeholder={question.placeholder || t('Add your details...')}
|
|
399
400
|
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
400
401
|
/>
|
|
401
402
|
)}
|
|
@@ -413,7 +414,7 @@ function SelectQuestionStep({
|
|
|
413
414
|
commitCustom();
|
|
414
415
|
}
|
|
415
416
|
}}
|
|
416
|
-
placeholder={question.placeholder || '
|
|
417
|
+
placeholder={question.placeholder || t('Type your answer...')}
|
|
417
418
|
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
418
419
|
/>
|
|
419
420
|
<button
|
|
@@ -424,7 +425,7 @@ function SelectQuestionStep({
|
|
|
424
425
|
}}
|
|
425
426
|
className="text-xs text-agent-muted-foreground transition hover:text-agent-foreground"
|
|
426
427
|
>
|
|
427
|
-
←
|
|
428
|
+
{t('← Back to options')}
|
|
428
429
|
</button>
|
|
429
430
|
</div>
|
|
430
431
|
)}
|
|
@@ -434,7 +435,11 @@ function SelectQuestionStep({
|
|
|
434
435
|
canGoBack={Boolean(onBack)}
|
|
435
436
|
onBack={onBack}
|
|
436
437
|
onAutoContinue={onAutoContinue}
|
|
437
|
-
hint={
|
|
438
|
+
hint={
|
|
439
|
+
question.multiSelect
|
|
440
|
+
? t('↑↓ to move, Enter to check')
|
|
441
|
+
: t('↑↓ to choose, Enter to confirm')
|
|
442
|
+
}
|
|
438
443
|
>
|
|
439
444
|
{question.multiSelect ? (
|
|
440
445
|
<PrimaryButton
|
|
@@ -511,7 +516,7 @@ function TextQuestionStep({
|
|
|
511
516
|
commit();
|
|
512
517
|
}
|
|
513
518
|
}}
|
|
514
|
-
placeholder={question.placeholder || '
|
|
519
|
+
placeholder={question.placeholder || t('Type your answer...')}
|
|
515
520
|
className="w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 py-2 text-xs text-agent-foreground outline-none placeholder:text-agent-muted-foreground focus:border-agent-foreground focus:ring-1 focus:ring-agent-foreground"
|
|
516
521
|
/>
|
|
517
522
|
</div>
|
|
@@ -520,7 +525,7 @@ function TextQuestionStep({
|
|
|
520
525
|
canGoBack={Boolean(onBack)}
|
|
521
526
|
onBack={onBack}
|
|
522
527
|
onAutoContinue={onAutoContinue}
|
|
523
|
-
hint=
|
|
528
|
+
hint={t('Enter to confirm')}
|
|
524
529
|
>
|
|
525
530
|
<PrimaryButton disabled={!value.trim()} isLast={isLast} onClick={commit} />
|
|
526
531
|
</Footer>
|
|
@@ -581,16 +586,16 @@ export function AskUserQuestionMenu({
|
|
|
581
586
|
<div className="flex items-start justify-between gap-2 border-b border-agent-border px-3 py-2">
|
|
582
587
|
<div className="min-w-0 flex-1">
|
|
583
588
|
<p className="text-xs font-medium text-agent-foreground">
|
|
584
|
-
{intro || '
|
|
589
|
+
{intro || t('Your input is needed')}
|
|
585
590
|
</p>
|
|
586
591
|
<p className="mt-0.5 text-[11px] text-agent-muted-foreground">
|
|
587
|
-
|
|
592
|
+
{t('Question {current} / {total}', { current: step + 1, total: normalized.length })}
|
|
588
593
|
{bottomHint ? ` · ${bottomHint}` : ''}
|
|
589
594
|
</p>
|
|
590
595
|
</div>
|
|
591
596
|
{current.type === 'password' || current.multiSelect ? (
|
|
592
597
|
<span className="rounded-full border border-agent-border px-2 py-0.5 text-[10px] font-medium text-agent-muted-foreground">
|
|
593
|
-
{current.type === 'password' ? '
|
|
598
|
+
{current.type === 'password' ? t('Password input') : t('Multiple choice')}
|
|
594
599
|
</span>
|
|
595
600
|
) : null}
|
|
596
601
|
</div>
|