claude-ws 0.3.97 → 0.3.99

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 (86) hide show
  1. package/locales/de.json +374 -12
  2. package/locales/en.json +374 -12
  3. package/locales/es.json +398 -11
  4. package/locales/fr.json +398 -11
  5. package/locales/ja.json +398 -11
  6. package/locales/ko.json +398 -11
  7. package/locales/vi.json +374 -12
  8. package/locales/zh.json +398 -11
  9. package/package.json +1 -1
  10. package/server.ts +283 -6
  11. package/src/app/[locale]/not-found.tsx +6 -3
  12. package/src/app/[locale]/page.tsx +14 -4
  13. package/src/app/api/attempts/[id]/workflow/route.ts +76 -0
  14. package/src/app/api/questions/answer/route.ts +58 -0
  15. package/src/app/api/questions/route.ts +68 -0
  16. package/src/app/api/tasks/[id]/compact/route.ts +62 -0
  17. package/src/components/access-anywhere/api-access-key-setup-modal.tsx +2 -2
  18. package/src/components/access-anywhere/tunnel-settings-dialog.tsx +6 -6
  19. package/src/components/access-anywhere/wizard-step-ctunnel.tsx +8 -8
  20. package/src/components/agent-factory/dependency-tree.tsx +5 -3
  21. package/src/components/agent-factory/discovery-dialog.tsx +26 -22
  22. package/src/components/agent-factory/plugin-detail-dialog.tsx +41 -38
  23. package/src/components/agent-factory/plugin-form-dialog.tsx +23 -20
  24. package/src/components/agent-factory/plugin-list.tsx +20 -17
  25. package/src/components/agent-factory/upload-dialog.tsx +17 -14
  26. package/src/components/auth/agent-provider-dialog.tsx +67 -65
  27. package/src/components/auth/api-key-dialog.tsx +14 -11
  28. package/src/components/auth/auth-error-message.tsx +6 -3
  29. package/src/components/editor/code-editor-with-inline-edit.tsx +4 -2
  30. package/src/components/editor/file-diff-resolver-modal.tsx +31 -26
  31. package/src/components/editor/inline-edit-dialog.tsx +9 -6
  32. package/src/components/editor/selection-mention-popup.tsx +3 -1
  33. package/src/components/header/project-selector.tsx +7 -4
  34. package/src/components/header.tsx +70 -4
  35. package/src/components/kanban/column.tsx +11 -0
  36. package/src/components/kanban/task-card.tsx +70 -4
  37. package/src/components/project-settings/component-selector.tsx +3 -1
  38. package/src/components/project-settings/plugin-upload-dialog.tsx +7 -5
  39. package/src/components/project-settings/project-settings-dialog.tsx +5 -3
  40. package/src/components/questions/questions-panel.tsx +136 -0
  41. package/src/components/settings/folder-browser-dialog.tsx +29 -25
  42. package/src/components/settings/settings-page.tsx +64 -18
  43. package/src/components/settings/setup-dialog.tsx +26 -23
  44. package/src/components/setup/unified-setup-wizard.tsx +12 -9
  45. package/src/components/sidebar/file-browser/file-create-buttons.tsx +7 -3
  46. package/src/components/sidebar/file-browser/file-tab-content.tsx +19 -15
  47. package/src/components/sidebar/file-browser/file-tabs-panel.tsx +7 -4
  48. package/src/components/sidebar/file-browser/file-tree.tsx +3 -1
  49. package/src/components/sidebar/git-changes/branch-checkout-modal.tsx +6 -4
  50. package/src/components/sidebar/git-changes/commit-details-modal.tsx +5 -3
  51. package/src/components/sidebar/git-changes/diff-tabs-panel.tsx +3 -1
  52. package/src/components/sidebar/git-changes/git-file-item.tsx +8 -6
  53. package/src/components/sidebar/git-changes/git-graph.tsx +8 -5
  54. package/src/components/sidebar/git-changes/git-panel.tsx +28 -27
  55. package/src/components/sidebar/git-changes/git-section.tsx +5 -3
  56. package/src/components/sidebar/shells/shell-panel.tsx +3 -1
  57. package/src/components/task/attachment-bar.tsx +4 -1
  58. package/src/components/task/attempt-item.tsx +7 -5
  59. package/src/components/task/conversation-view.tsx +21 -13
  60. package/src/components/task/floating-chat-window.tsx +14 -5
  61. package/src/components/task/interactive-command/checkpoint-list.tsx +5 -3
  62. package/src/components/task/interactive-command/confirm-dialog.tsx +9 -4
  63. package/src/components/task/interactive-command/interactive-command-overlay.tsx +23 -9
  64. package/src/components/task/interactive-command/question-prompt.tsx +12 -8
  65. package/src/components/task/pending-question-indicator.tsx +5 -3
  66. package/src/components/task/prompt-input.tsx +1 -1
  67. package/src/components/task/shell-log-view.tsx +3 -1
  68. package/src/components/task/status-line.tsx +84 -23
  69. package/src/components/task/task-detail-panel.tsx +27 -27
  70. package/src/components/task/task-shell-indicator.tsx +10 -6
  71. package/src/components/terminal/terminal-context-menu.tsx +6 -4
  72. package/src/components/terminal/terminal-instance.tsx +11 -3
  73. package/src/components/terminal/terminal-panel.tsx +6 -3
  74. package/src/components/terminal/terminal-shortcut-bar.tsx +3 -1
  75. package/src/components/terminal/terminal-tab-bar.tsx +5 -3
  76. package/src/components/workflow/workflow-panel.tsx +181 -0
  77. package/src/hooks/use-attempt-stream.ts +96 -3
  78. package/src/lib/agent-manager.ts +89 -3
  79. package/src/lib/db/index.ts +18 -0
  80. package/src/lib/db/schema.ts +29 -0
  81. package/src/lib/process-manager.ts +28 -7
  82. package/src/lib/session-manager.ts +60 -0
  83. package/src/lib/usage-tracker.ts +19 -19
  84. package/src/lib/workflow-tracker.ts +118 -20
  85. package/src/stores/questions-store.ts +76 -0
  86. package/src/stores/workflow-store.ts +71 -0
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { useTranslations } from 'next-intl';
4
5
  import { AlertCircle, Settings2 } from 'lucide-react';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { AgentProviderDialog, isProviderAuthError } from './agent-provider-dialog';
@@ -15,6 +16,7 @@ interface AuthErrorMessageProps {
15
16
  * Shows when authentication/provider errors are detected
16
17
  */
17
18
  export function AuthErrorMessage({ message, className }: AuthErrorMessageProps) {
19
+ const t = useTranslations('auth');
18
20
  const [dialogOpen, setDialogOpen] = useState(false);
19
21
 
20
22
  // Only show if it's an auth-related error
@@ -29,7 +31,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
29
31
  <AlertCircle className="h-5 w-5 text-destructive shrink-0 mt-0.5" />
30
32
  <div className="flex-1 space-y-3">
31
33
  <div className="text-sm text-destructive font-medium">
32
- Authentication Error
34
+ {t('authenticationError')}
33
35
  </div>
34
36
  <div className="text-sm text-muted-foreground">
35
37
  {message}
@@ -41,7 +43,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
41
43
  className="gap-2"
42
44
  >
43
45
  <Settings2 className="h-4 w-4" />
44
- Config Agent Provider
46
+ {t('configAgentProvider')}
45
47
  </Button>
46
48
  </div>
47
49
  </div>
@@ -59,6 +61,7 @@ export function AuthErrorMessage({ message, className }: AuthErrorMessageProps)
59
61
  * Inline button for showing in tool results or smaller contexts
60
62
  */
61
63
  export function ConfigProviderButton({ className }: { className?: string }) {
64
+ const t = useTranslations('auth');
62
65
  const [dialogOpen, setDialogOpen] = useState(false);
63
66
 
64
67
  return (
@@ -70,7 +73,7 @@ export function ConfigProviderButton({ className }: { className?: string }) {
70
73
  className={className}
71
74
  >
72
75
  <Settings2 className="h-4 w-4 mr-2" />
73
- Config Agent Provider
76
+ {t('configAgentProvider')}
74
77
  </Button>
75
78
 
76
79
  <AgentProviderDialog
@@ -33,6 +33,7 @@ import { useContextMentionStore } from '@/stores/context-mention-store';
33
33
  import { useTaskStore } from '@/stores/task-store';
34
34
  import { useState } from 'react';
35
35
  import { toast } from 'sonner';
36
+ import { useTranslations } from 'next-intl';
36
37
 
37
38
  interface EditorPosition {
38
39
  lineNumber?: number;
@@ -75,6 +76,7 @@ export function CodeEditorWithInlineEdit({
75
76
  onSelectionChange,
76
77
  }: CodeEditorWithInlineEditProps) {
77
78
  const { theme } = useTheme();
79
+ const t = useTranslations('editor');
78
80
  const containerRef = useRef<HTMLDivElement>(null);
79
81
  const editorViewRef = useRef<EditorView | null>(null);
80
82
  const [height, setHeight] = useState<number>(400);
@@ -340,7 +342,7 @@ export function CodeEditorWithInlineEdit({
340
342
  const handleAddToContext = useCallback(
341
343
  (selection: ContextSelection) => {
342
344
  if (!selectedTaskId) {
343
- toast.error('Select a task first to add context');
345
+ toast.error(t('selectTaskFirst'));
344
346
  return;
345
347
  }
346
348
 
@@ -365,7 +367,7 @@ export function CodeEditorWithInlineEdit({
365
367
  (startLine: number, endLine: number) => {
366
368
  if (!filePath) return;
367
369
  if (!selectedTaskId) {
368
- toast.error('Select a task first to add context');
370
+ toast.error(t('selectTaskFirst'));
369
371
  return;
370
372
  }
371
373
 
@@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button';
15
15
  import { AlertTriangle, Copy, ArrowLeft, Check, Plus } from 'lucide-react';
16
16
  import { cn } from '@/lib/utils';
17
17
  import { toast } from 'sonner';
18
+ import { useTranslations } from 'next-intl';
18
19
 
19
20
  interface FileDiffResolverModalProps {
20
21
  /** Whether the modal is open */
@@ -53,6 +54,8 @@ export function FileDiffResolverModal({
53
54
  onKeepLocal,
54
55
  onMerge,
55
56
  }: FileDiffResolverModalProps) {
57
+ const t = useTranslations('editor');
58
+
56
59
  // Working copy of local content that user can modify via inserts
57
60
  const [workingContent, setWorkingContent] = useState(localContent);
58
61
  const localScrollRef = useRef<HTMLDivElement>(null);
@@ -81,7 +84,7 @@ export function FileDiffResolverModal({
81
84
  // Insert the remote line with >>>>> marker before it
82
85
  lines.splice(insertAfterLocalLine, 0, '>>>>> REMOTE', remoteLine);
83
86
  setWorkingContent(lines.join('\n'));
84
- toast.success('Line inserted with marker');
87
+ toast.success(t('lineInserted'));
85
88
  }, [workingContent]);
86
89
 
87
90
  // Insert all lines from a remote block
@@ -90,7 +93,7 @@ export function FileDiffResolverModal({
90
93
  // Insert marker before the block and all remote lines
91
94
  lines.splice(insertAfterLocalLine, 0, '>>>>> REMOTE', ...remoteLines, '<<<<< END REMOTE');
92
95
  setWorkingContent(lines.join('\n'));
93
- toast.success(`${remoteLines.length} line(s) inserted with markers`);
96
+ toast.success(t('linesInserted', { count: remoteLines.length }));
94
97
  }, [workingContent]);
95
98
 
96
99
  // Insert deletion marker for lines that exist in local but not remote
@@ -106,34 +109,34 @@ export function FileDiffResolverModal({
106
109
  lines.splice(startPosition, 0, '>>>>> REMOTE DELETED');
107
110
 
108
111
  setWorkingContent(lines.join('\n'));
109
- toast.success(lineCount > 1 ? 'Deletion markers inserted' : 'Deletion marker inserted');
112
+ toast.success(lineCount > 1 ? t('deletionMarkersInserted') : t('deletionMarkerInserted'));
110
113
  }, [workingContent]);
111
114
 
112
115
  // Handle keeping local (dismiss all remote changes)
113
116
  const handleKeepLocal = useCallback(() => {
114
117
  onKeepLocal();
115
118
  onClose();
116
- toast.success('Kept local changes');
119
+ toast.success(t('keptLocalChanges'));
117
120
  }, [onKeepLocal, onClose]);
118
121
 
119
122
  // Handle accepting remote entirely
120
123
  const handleAcceptRemote = useCallback(() => {
121
124
  onAcceptRemote();
122
125
  onClose();
123
- toast.success('Accepted remote changes');
126
+ toast.success(t('acceptedRemoteChanges'));
124
127
  }, [onAcceptRemote, onClose]);
125
128
 
126
129
  // Handle applying the working (merged) content
127
130
  const handleApplyMerged = useCallback(() => {
128
131
  onMerge(workingContent);
129
132
  onClose();
130
- toast.success('Applied merged changes');
133
+ toast.success(t('appliedMergedChanges'));
131
134
  }, [workingContent, onMerge, onClose]);
132
135
 
133
136
  // Copy content to clipboard
134
137
  const handleCopy = useCallback(async (content: string, label: string) => {
135
138
  await navigator.clipboard.writeText(content);
136
- toast.success(`Copied ${label} to clipboard`);
139
+ toast.success(t('copiedToClipboard', { label }));
137
140
  }, []);
138
141
 
139
142
  // Sync scroll between panels
@@ -159,32 +162,32 @@ export function FileDiffResolverModal({
159
162
  <DialogHeader>
160
163
  <DialogTitle className="flex items-center gap-2">
161
164
  <AlertTriangle className="size-5 text-amber-500" />
162
- File Changed Externally
165
+ {t('fileChangedExternally')}
163
166
  </DialogTitle>
164
167
  <DialogDescription>
165
168
  <span className="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">{fileName}</span>
166
- {' '}has been modified on disk. Click <Plus className="inline size-3" /> to insert remote lines into your version.
169
+ {' '}{t('hasBeenModified')} <Plus className="inline size-3" /> {t('toInsertRemoteLines')}
167
170
  </DialogDescription>
168
171
  </DialogHeader>
169
172
 
170
173
  {/* Stats bar */}
171
174
  <div className="flex items-center gap-4 text-sm">
172
- <span className="text-muted-foreground">Differences:</span>
175
+ <span className="text-muted-foreground">{t('differences')}</span>
173
176
  {hasChanges ? (
174
177
  <>
175
178
  <span className="text-blue-600 dark:text-blue-400">
176
- {diffBlocks.filter(b => b.type === 'added').reduce((sum, b) => sum + b.remoteLines.length, 0)} new in remote
179
+ {diffBlocks.filter(b => b.type === 'added').reduce((sum, b) => sum + b.remoteLines.length, 0)} {t('newInRemote')}
177
180
  </span>
178
181
  <span className="text-green-600 dark:text-green-400">
179
- {diffBlocks.filter(b => b.type === 'removed').reduce((sum, b) => sum + b.localLines.length, 0)} only in local
182
+ {diffBlocks.filter(b => b.type === 'removed').reduce((sum, b) => sum + b.localLines.length, 0)} {t('onlyInLocal')}
180
183
  </span>
181
184
  </>
182
185
  ) : (
183
- <span className="text-green-600 dark:text-green-400">✓ Files are identical</span>
186
+ <span className="text-green-600 dark:text-green-400">✓ {t('filesIdentical')}</span>
184
187
  )}
185
188
  {hasLocalModifications && (
186
189
  <span className="text-amber-600 dark:text-amber-400 ml-auto text-xs">
187
- Modified from original
190
+ {t('modifiedFromOriginal')}
188
191
  </span>
189
192
  )}
190
193
  </div>
@@ -195,13 +198,13 @@ export function FileDiffResolverModal({
195
198
  <div className="flex flex-col border rounded-md overflow-hidden">
196
199
  <div className="flex items-center justify-between px-3 py-1.5 bg-muted/50 border-b">
197
200
  <span className="text-xs font-medium text-muted-foreground">
198
- Local {hasLocalModifications && '(modified)'}
201
+ {t('local')} {hasLocalModifications && t('modified')}
199
202
  </span>
200
203
  <Button
201
204
  variant="ghost"
202
205
  size="icon-sm"
203
206
  onClick={() => handleCopy(workingContent, 'local content')}
204
- title="Copy local content"
207
+ title={t('copyLocalContent')}
205
208
  >
206
209
  <Copy className="size-3.5" />
207
210
  </Button>
@@ -221,12 +224,12 @@ export function FileDiffResolverModal({
221
224
  {/* Remote (right) */}
222
225
  <div className="flex flex-col border rounded-md overflow-hidden">
223
226
  <div className="flex items-center justify-between px-3 py-1.5 bg-muted/50 border-b">
224
- <span className="text-xs font-medium text-muted-foreground">Remote (Disk)</span>
227
+ <span className="text-xs font-medium text-muted-foreground">{t('remoteDisk')}</span>
225
228
  <Button
226
229
  variant="ghost"
227
230
  size="icon-sm"
228
231
  onClick={() => handleCopy(remoteContent, 'remote content')}
229
- title="Copy remote content"
232
+ title={t('copyRemoteContent')}
230
233
  >
231
234
  <Copy className="size-3.5" />
232
235
  </Button>
@@ -250,15 +253,15 @@ export function FileDiffResolverModal({
250
253
  <DialogFooter className="gap-2 sm:gap-2 flex-wrap">
251
254
  <Button variant="outline" onClick={handleKeepLocal} className="gap-2">
252
255
  <ArrowLeft className="size-4" />
253
- Keep Local Only
256
+ {t('keepLocalOnly')}
254
257
  </Button>
255
258
  <Button variant="outline" onClick={handleAcceptRemote} className="gap-2">
256
- Accept Remote Only
259
+ {t('acceptRemoteOnly')}
257
260
  </Button>
258
261
  {hasLocalModifications && (
259
262
  <Button variant="default" onClick={handleApplyMerged} className="gap-2">
260
263
  <Check className="size-4" />
261
- Apply Merged
264
+ {t('applyMerged')}
262
265
  </Button>
263
266
  )}
264
267
  </DialogFooter>
@@ -432,6 +435,7 @@ interface DiffPanelLocalProps {
432
435
  }
433
436
 
434
437
  function DiffPanelLocal({ diffBlocks, workingLines }: DiffPanelLocalProps) {
438
+ const t = useTranslations('editor');
435
439
  let lineNumber = 0;
436
440
 
437
441
  return (
@@ -490,7 +494,7 @@ function DiffPanelLocal({ diffBlocks, workingLines }: DiffPanelLocalProps) {
490
494
  className="px-2 py-0.5 flex bg-blue-500/10 text-muted-foreground/50 italic"
491
495
  >
492
496
  <span className="w-8 text-right mr-2 shrink-0">+</span>
493
- <span className="flex-1">{block.remoteLines.length} line(s) in remote</span>
497
+ <span className="flex-1">{t('linesInRemote', { count: block.remoteLines.length })}</span>
494
498
  </div>
495
499
  );
496
500
  }
@@ -512,6 +516,7 @@ interface DiffPanelRemoteProps {
512
516
  }
513
517
 
514
518
  function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock, onInsertDeletedMarker }: DiffPanelRemoteProps) {
519
+ const t = useTranslations('editor');
515
520
  let lineNumber = 0;
516
521
  let currentLocalLine = 0;
517
522
 
@@ -564,7 +569,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
564
569
  size="icon-sm"
565
570
  className="opacity-0 group-hover:opacity-100 shrink-0 ml-1 h-5 w-5"
566
571
  onClick={() => onInsertLine(line, insertPosition)}
567
- title="Insert this line into local"
572
+ title={t('insertLineIntoLocal')}
568
573
  >
569
574
  <Plus className="size-3" />
570
575
  </Button>
@@ -587,7 +592,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
587
592
  onClick={() => onInsertBlock(block.remoteLines, insertPosition)}
588
593
  >
589
594
  <Plus className="size-3" />
590
- Insert all {block.remoteLines.length} lines
595
+ {t('insertAllLines', { count: block.remoteLines.length })}
591
596
  </Button>
592
597
  </div>
593
598
  );
@@ -603,7 +608,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
603
608
  className="px-2 py-0.5 flex items-center justify-between bg-green-500/10 gap-2"
604
609
  >
605
610
  <span className="text-muted-foreground/70 text-xs italic">
606
- − {deletedLineCount} line(s) deleted in remote
611
+ − {t('linesDeletedInRemote', { count: deletedLineCount })}
607
612
  </span>
608
613
  <Button
609
614
  variant="outline"
@@ -612,7 +617,7 @@ function DiffPanelRemote({ diffBlocks, remoteLines, onInsertLine, onInsertBlock,
612
617
  onClick={() => onInsertDeletedMarker(insertPosition, deletedLineCount)}
613
618
  >
614
619
  <Plus className="size-3" />
615
- Mark as deleted
620
+ {t('markAsDeleted')}
616
621
  </Button>
617
622
  </div>
618
623
  );
@@ -12,6 +12,7 @@ import { Button } from '@/components/ui/button';
12
12
  import { Input } from '@/components/ui/input';
13
13
  import { X, Loader2, Check, RotateCcw } from 'lucide-react';
14
14
  import { useInlineEditStore } from '@/stores/inline-edit-store';
15
+ import { useTranslations } from 'next-intl';
15
16
 
16
17
  interface InlineEditDialogProps {
17
18
  filePath: string;
@@ -21,6 +22,8 @@ interface InlineEditDialogProps {
21
22
  }
22
23
 
23
24
  export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: InlineEditDialogProps) {
25
+ const t = useTranslations('editor');
26
+ const tCommon = useTranslations('common');
24
27
  const [instruction, setInstruction] = useState('');
25
28
  const inputRef = useRef<HTMLInputElement>(null);
26
29
  const popupRef = useRef<HTMLDivElement>(null);
@@ -155,7 +158,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
155
158
  className="flex items-center gap-1.5 text-xs bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30 hover:text-emerald-300 font-medium px-2.5 py-1 rounded-md transition-colors"
156
159
  >
157
160
  <Check className="size-3" />
158
- Accept
161
+ {t('accept')}
159
162
  <kbd className="text-[10px] text-emerald-600 bg-emerald-950/50 px-1 py-0.5 rounded">⌘↵</kbd>
160
163
  </button>
161
164
 
@@ -165,7 +168,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
165
168
  className="flex items-center gap-1.5 text-xs bg-zinc-700/30 text-zinc-400 hover:bg-zinc-700/50 hover:text-zinc-300 font-medium px-2.5 py-1 rounded-md transition-colors"
166
169
  >
167
170
  <RotateCcw className="size-3" />
168
- Reject
171
+ {t('reject')}
169
172
  <kbd className="text-[10px] text-zinc-600 bg-zinc-800/50 px-1 py-0.5 rounded">Esc</kbd>
170
173
  </button>
171
174
  </div>
@@ -187,7 +190,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
187
190
  <div className="bg-[#1e1e1e] border border-[#3c3c3c] rounded-lg shadow-xl p-2 flex items-center gap-2">
188
191
  <Input
189
192
  ref={inputRef}
190
- placeholder="Describe the change you want..."
193
+ placeholder={t('describeChange')}
191
194
  value={instruction}
192
195
  onChange={(e) => setInstruction(e.target.value)}
193
196
  onKeyDown={handleKeyDown}
@@ -197,14 +200,14 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
197
200
 
198
201
  {error && (
199
202
  <span className="text-xs text-red-400 truncate max-w-[80px]" title={error}>
200
- Error
203
+ {t('error')}
201
204
  </span>
202
205
  )}
203
206
 
204
207
  {isGenerating ? (
205
208
  <div className="flex items-center gap-2 text-zinc-400 text-xs">
206
209
  <Loader2 className="size-4 animate-spin" />
207
- <span>Generating...</span>
210
+ <span>{tCommon('generating')}</span>
208
211
  </div>
209
212
  ) : (
210
213
  <Button
@@ -213,7 +216,7 @@ export function InlineEditDialog({ filePath, onSubmit, onAccept, onReject }: Inl
213
216
  disabled={!instruction.trim()}
214
217
  className="h-7 px-3 bg-violet-600 hover:bg-violet-700 text-white text-xs font-medium"
215
218
  >
216
- Submit
219
+ {tCommon('submit')}
217
220
  </Button>
218
221
  )}
219
222
 
@@ -4,6 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { AtSign } from 'lucide-react';
5
5
  import { EditorView } from '@codemirror/view';
6
6
  import { cn } from '@/lib/utils';
7
+ import { useTranslations } from 'next-intl';
7
8
 
8
9
  interface SelectionMentionPopupProps {
9
10
  containerRef: React.RefObject<HTMLDivElement | null>;
@@ -16,6 +17,7 @@ export function SelectionMentionPopup({
16
17
  editorViewRef,
17
18
  onAddToContext,
18
19
  }: SelectionMentionPopupProps) {
20
+ const t = useTranslations('editor');
19
21
  const [visible, setVisible] = useState(false);
20
22
  const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
21
23
  const [selection, setSelection] = useState<{ startLine: number; endLine: number } | null>(null);
@@ -142,7 +144,7 @@ export function SelectionMentionPopup({
142
144
  transform: 'translateX(-100%)', // Align to right
143
145
  }}
144
146
  onClick={handleClick}
145
- title={`Add ${lineRange} to chat context`}
147
+ title={t('addLinesToChat', { startLine: selection.startLine, endLine: selection.endLine })}
146
148
  >
147
149
  <AtSign className="size-3" />
148
150
  <span className="text-xs font-medium">{lineRange}</span>
@@ -22,6 +22,7 @@ import {
22
22
  import { useProjectStore } from '@/stores/project-store';
23
23
  import { ProjectSettingsDialog } from '@/components/project-settings/project-settings-dialog';
24
24
  import { getFolderName } from '@/lib/utils';
25
+ import { useTranslations } from 'next-intl';
25
26
 
26
27
  interface ProjectSelectorProps {
27
28
  onAddProject?: () => void;
@@ -29,6 +30,7 @@ interface ProjectSelectorProps {
29
30
 
30
31
  // Content component for reuse in mobile dropdown
31
32
  export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
33
+ const tCommon = useTranslations('common');
32
34
  const {
33
35
  projects,
34
36
  selectedProjectIds,
@@ -76,7 +78,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
76
78
  onCheckedChange={() => selectAllProjects()}
77
79
  className="px-2 py-1.5"
78
80
  >
79
- All Projects
81
+ {tCommon('allProjects')}
80
82
  </DropdownMenuCheckboxItem>
81
83
  <DropdownMenuSeparator />
82
84
 
@@ -84,7 +86,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
84
86
  <div className="max-h-[180px] overflow-y-auto">
85
87
  {projects.length === 0 ? (
86
88
  <div className="px-2 py-1.5 text-sm text-muted-foreground">
87
- No projects yet
89
+ {tCommon('noProjectsYet')}
88
90
  </div>
89
91
  ) : (
90
92
  projects.map((project) => {
@@ -195,6 +197,7 @@ export function ProjectSelectorContent({ onAddProject }: ProjectSelectorProps) {
195
197
  }
196
198
 
197
199
  export function ProjectSelector({ onAddProject }: ProjectSelectorProps) {
200
+ const tCommon = useTranslations('common');
198
201
  const {
199
202
  projects,
200
203
  selectedProjectIds,
@@ -203,11 +206,11 @@ export function ProjectSelector({ onAddProject }: ProjectSelectorProps) {
203
206
 
204
207
  // Compute display text
205
208
  const allMode = isAllProjectsMode();
206
- let displayText = 'All Projects';
209
+ let displayText = tCommon('allProjects');
207
210
  if (!allMode) {
208
211
  if (selectedProjectIds.length === 1) {
209
212
  const project = projects.find(p => p.id === selectedProjectIds[0]);
210
- displayText = project ? getFolderName(project.name) : 'Select Project';
213
+ displayText = project ? getFolderName(project.name) : tCommon('selectProject');
211
214
  } else if (selectedProjectIds.length > 1) {
212
215
  displayText = `${selectedProjectIds.length} projects`;
213
216
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
4
- import { Settings, Plus, Search, PanelLeft, PanelRight, FolderTree, Terminal, X } from 'lucide-react';
3
+ import { useState, useEffect } from 'react';
4
+ import { Settings, Plus, Search, PanelLeft, PanelRight, FolderTree, MessageCircleQuestion, Network, Terminal, X } from 'lucide-react';
5
5
  import Image from 'next/image';
6
6
  import { Button } from '@/components/ui/button';
7
7
  import { Input } from '@/components/ui/input';
@@ -25,6 +25,8 @@ import { useTerminalStore } from '@/stores/terminal-store';
25
25
  import { useProjectStore } from '@/stores/project-store';
26
26
  import { useSettingsUIStore } from '@/stores/settings-ui-store';
27
27
  import { ProjectSelector, ProjectSelectorContent } from '@/components/header/project-selector';
28
+ import { useQuestionsStore } from '@/stores/questions-store';
29
+ import { useWorkflowStore } from '@/stores/workflow-store';
28
30
  import { useTranslations } from 'next-intl';
29
31
 
30
32
  interface HeaderProps {
@@ -43,7 +45,16 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
43
45
  const { setOpen: setSettingsOpen } = useSettingsUIStore();
44
46
  const { isOpen: terminalOpen, togglePanel: toggleTerminal } = useTerminalStore();
45
47
  const { activeProjectId, selectedProjectIds } = useProjectStore();
48
+ const { pendingQuestions, fetchQuestions, isOpen: questionsPanelOpen, togglePanel: toggleQuestionsPanel } = useQuestionsStore();
49
+ const questionCount = pendingQuestions.size;
50
+ const { isOpen: workflowPanelOpen, togglePanel: toggleWorkflowPanel, getActiveAgentCount } = useWorkflowStore();
51
+ const activeAgentCount = getActiveAgentCount();
46
52
  const [searchOpen, setSearchOpen] = useState(false);
53
+
54
+ // Fetch pending questions on mount
55
+ useEffect(() => {
56
+ fetchQuestions(selectedProjectIds);
57
+ }, [selectedProjectIds.join(',')]);
47
58
  const [internalSearchQuery, setInternalSearchQuery] = useState('');
48
59
  const searchQuery = externalSearchQuery !== undefined ? externalSearchQuery : internalSearchQuery;
49
60
  const setSearchQuery = onSearchChange || setInternalSearchQuery;
@@ -102,7 +113,7 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
102
113
  <button
103
114
  onClick={() => setSearchQuery('')}
104
115
  className="absolute right-2 top-2 h-5 w-5 flex items-center justify-center rounded-sm hover:bg-muted-foreground/20 transition-colors"
105
- aria-label="Clear search"
116
+ aria-label={t('clearSearch')}
106
117
  >
107
118
  <X className="h-3.5 w-3.5 text-muted-foreground" />
108
119
  </button>
@@ -161,6 +172,61 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
161
172
  </div>
162
173
  </div>
163
174
 
175
+ {/* Questions panel toggle */}
176
+ <TooltipProvider>
177
+ <Tooltip>
178
+ <TooltipTrigger asChild>
179
+ <Button
180
+ variant={questionsPanelOpen ? 'secondary' : 'ghost'}
181
+ size="icon"
182
+ onClick={() => {
183
+ fetchQuestions(selectedProjectIds);
184
+ toggleQuestionsPanel();
185
+ }}
186
+ className="shrink-0 relative"
187
+ >
188
+ <MessageCircleQuestion className="h-4 w-4" />
189
+ {questionCount > 0 && (
190
+ <span className="absolute -top-1 -right-1 h-4 min-w-4 px-1 flex items-center justify-center text-[10px] font-medium bg-amber-500 text-white rounded-full">
191
+ {questionCount}
192
+ </span>
193
+ )}
194
+ </Button>
195
+ </TooltipTrigger>
196
+ <TooltipContent>
197
+ <p>
198
+ Pending questions{questionCount > 0 ? ` (${questionCount})` : ''}
199
+ </p>
200
+ </TooltipContent>
201
+ </Tooltip>
202
+ </TooltipProvider>
203
+
204
+ {/* Workflow panel toggle */}
205
+ <TooltipProvider>
206
+ <Tooltip>
207
+ <TooltipTrigger asChild>
208
+ <Button
209
+ variant={workflowPanelOpen ? 'secondary' : 'ghost'}
210
+ size="icon"
211
+ onClick={toggleWorkflowPanel}
212
+ className="shrink-0 relative"
213
+ >
214
+ <Network className="h-4 w-4" />
215
+ {activeAgentCount > 0 && (
216
+ <span className="absolute -top-1 -right-1 h-4 min-w-4 px-1 flex items-center justify-center text-[10px] font-medium bg-blue-500 text-white rounded-full">
217
+ {activeAgentCount}
218
+ </span>
219
+ )}
220
+ </Button>
221
+ </TooltipTrigger>
222
+ <TooltipContent>
223
+ <p>
224
+ Agent workflow{activeAgentCount > 0 ? ` (${activeAgentCount})` : ''}
225
+ </p>
226
+ </TooltipContent>
227
+ </Tooltip>
228
+ </TooltipProvider>
229
+
164
230
  {/* Terminal toggle */}
165
231
  <TooltipProvider>
166
232
  <Tooltip>
@@ -226,7 +292,7 @@ export function Header({ onCreateTask, onAddProject, searchQuery: externalSearch
226
292
  <button
227
293
  onClick={() => setSearchQuery('')}
228
294
  className="absolute right-2 top-2 h-5 w-5 flex items-center justify-center rounded-sm hover:bg-muted-foreground/20 transition-colors"
229
- aria-label="Clear search"
295
+ aria-label={t('clearSearch')}
230
296
  >
231
297
  <X className="h-3.5 w-3.5 text-muted-foreground" />
232
298
  </button>
@@ -62,6 +62,17 @@ export function Column({ status, title, tasks, attemptCounts = new Map(), onCrea
62
62
  <span className="text-xs text-muted-foreground bg-muted px-2 py-1 rounded-full">
63
63
  {tasks.length}
64
64
  </span>
65
+ {isArchiveColumn && tasks.length > 0 && (
66
+ <Button
67
+ variant="ghost"
68
+ size="sm"
69
+ className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
70
+ onClick={handleEmptyColumn}
71
+ title={t('deleteAllTasks', { count: tasks.length, status: title })}
72
+ >
73
+ <Trash2 className="h-3 w-3" />
74
+ </Button>
75
+ )}
65
76
  {isTodoColumn && onCreateTask && (
66
77
  <Button
67
78
  variant="default"