@pheem49/mint 1.8.0 → 1.8.1

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 (41) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/Cargo.lock +34 -3
  3. package/Cargo.toml +2 -2
  4. package/README.md +31 -0
  5. package/Release_Note.md +95 -0
  6. package/assets/Mint_CLI.png +0 -0
  7. package/crates/mint-cli/Cargo.toml +1 -0
  8. package/crates/mint-cli/src/agent.rs +142 -48
  9. package/crates/mint-cli/src/main.rs +231 -22
  10. package/crates/mint-core/src/api_server.rs +9 -0
  11. package/crates/mint-core/src/browser.rs +401 -0
  12. package/crates/mint-core/src/config.rs +69 -1
  13. package/crates/mint-core/src/lib.rs +5 -0
  14. package/crates/mint-core/src/orchestration.rs +137 -1
  15. package/crates/mint-core/src/skills.rs +113 -4
  16. package/crates/mint-core/src/workflows.rs +1 -22
  17. package/package.json +1 -1
  18. package/src/renderer/src/components/ChatPanel.tsx +170 -33
  19. package/src/renderer/src/components/ImageStudioPanel.tsx +11 -1
  20. package/src/renderer/src/components/MintDashboard.tsx +40 -2
  21. package/src/renderer/src/components/Settings/PluginsTab.tsx +221 -41
  22. package/src/renderer/src/components/SettingsWindow.tsx +1 -9
  23. package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
  24. package/src/renderer/src/css/image-studio.css +6 -0
  25. package/src/renderer/src/css/styles.css +64 -11
  26. package/src/renderer/src/index.css +12 -1
  27. package/src/renderer/src/tauri.ts +45 -4
  28. package/src/renderer/src-web/components/ChatPanel.tsx +163 -32
  29. package/src/renderer/src-web/components/ImageStudioPanel.tsx +11 -1
  30. package/src/renderer/src-web/components/MintDashboard.tsx +8 -3
  31. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +221 -41
  32. package/src/renderer/src-web/components/SettingsWindow.tsx +1 -9
  33. package/src/renderer/src-web/css/image-studio.css +6 -0
  34. package/src/renderer/src-web/css/styles.css +100 -12
  35. package/src/renderer/src-web/index.css +12 -1
  36. package/src/renderer/src-web/tauri.ts +24 -2
  37. package/src-tauri/src/browser.rs +1 -141
  38. package/src-tauri/src/headless.rs +3 -2
  39. package/src-tauri/src/lib.rs +117 -26
  40. package/src-tauri/src/workflows.rs +56 -4
  41. package/src-tauri/tauri.conf.json +1 -1
@@ -128,6 +128,28 @@ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
128
128
  return invoke<RuntimeStatus>('get_runtime_status')
129
129
  }
130
130
 
131
+ export interface DetectedTools {
132
+ docker: boolean
133
+ git: boolean
134
+ gh: boolean
135
+ node: boolean
136
+ }
137
+
138
+ export async function detectSystemTools(): Promise<DetectedTools> {
139
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
140
+ const API_BASE = "http://localhost:3000/api";
141
+ try {
142
+ const res = await fetch(`${API_BASE}/detect-tools`);
143
+ return await res.json();
144
+ } catch (e) {
145
+ console.error("Failed to detect tools from local server:", e);
146
+ return { docker: false, git: false, gh: false, node: false };
147
+ }
148
+ }
149
+ const { invoke } = await import('@tauri-apps/api/core')
150
+ return invoke<DetectedTools>('detect_system_tools')
151
+ }
152
+
131
153
  export async function sendChatMessage(
132
154
  message: string,
133
155
  imageDataUri?: string | null,
@@ -426,9 +448,10 @@ export interface LearnedSkill {
426
448
  sourcePath: string
427
449
  content: string
428
450
  updatedAt: string
451
+ location?: string
429
452
  }
430
453
 
431
- export async function listLearnedSkills(): Promise<LearnedSkill[]> {
454
+ export async function listLearnedSkills(workspacePath?: string): Promise<LearnedSkill[]> {
432
455
  if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
433
456
  try {
434
457
  const val = await getProfileValue('learned-skills-web-mock')
@@ -442,7 +465,7 @@ export async function listLearnedSkills(): Promise<LearnedSkill[]> {
442
465
  }
443
466
  }
444
467
  const { invoke } = await import('@tauri-apps/api/core')
445
- return invoke<LearnedSkill[]>('list_learned_skills')
468
+ return invoke<LearnedSkill[]>('list_learned_skills', { workspacePath })
446
469
  }
447
470
 
448
471
  export async function addLearnedSkill(name: string, content: string): Promise<LearnedSkill> {
@@ -647,6 +670,24 @@ export async function getWorkspaceTree(path?: string | null): Promise<WorkspaceT
647
670
  return invoke<WorkspaceTreeEntry>('get_workspace_tree', { path })
648
671
  }
649
672
 
673
+ export async function createWorkspaceFile(path: string): Promise<void> {
674
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
675
+ const { invoke } = await import('@tauri-apps/api/core')
676
+ return invoke('create_workspace_file', { path })
677
+ }
678
+
679
+ export async function createWorkspaceFolder(path: string): Promise<void> {
680
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
681
+ const { invoke } = await import('@tauri-apps/api/core')
682
+ return invoke('create_workspace_folder', { path })
683
+ }
684
+
685
+ export async function deleteWorkspaceItem(path: string): Promise<void> {
686
+ if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
687
+ const { invoke } = await import('@tauri-apps/api/core')
688
+ return invoke('delete_workspace_item', { path })
689
+ }
690
+
650
691
  export async function selectWorkspaceDirectory(): Promise<string | null> {
651
692
  if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
652
693
  return null
@@ -656,12 +697,12 @@ export async function selectWorkspaceDirectory(): Promise<string | null> {
656
697
  return selected?.trim() || null
657
698
  }
658
699
 
659
- export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
700
+ export async function submitToolApproval(token: string, approved: boolean, answer?: string): Promise<void> {
660
701
  if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
661
702
  return;
662
703
  }
663
704
  const { invoke } = await import('@tauri-apps/api/core')
664
- return invoke('submit_tool_approval', { token, approved })
705
+ return invoke('submit_tool_approval', { token, approved, answer })
665
706
  }
666
707
 
667
708
  export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {
@@ -68,15 +68,7 @@ const LOCAL_MODELS = [
68
68
  'lmstudio-community/gemma-2-9b-it-GGUF'
69
69
  ]
70
70
 
71
- const OLLAMA_MODELS = [
72
- 'llama3:latest',
73
- 'llama3.1:latest',
74
- 'llama3.2:latest',
75
- 'gemma2:latest',
76
- 'mistral:latest',
77
- 'phi3:latest',
78
- 'qwen2.5:latest'
79
- ]
71
+ const OLLAMA_MODELS: string[] = []
80
72
 
81
73
  interface ApprovalDetails {
82
74
  title: string
@@ -122,6 +114,7 @@ interface AgentActivity {
122
114
  target: string
123
115
  kind: 'file' | 'folder' | 'search' | 'terminal' | 'tool'
124
116
  state: 'active' | 'done' | 'error'
117
+ action?: string
125
118
  }
126
119
 
127
120
  interface AgentActivityView {
@@ -153,7 +146,20 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
153
146
  const command = activityDetail(input, 'command')
154
147
  const name = activityDetail(input, 'name')
155
148
  const tool = activityDetail(input, 'tool')
156
- const target = path || query || command || name || tool || action.replaceAll('_', ' ')
149
+ const rawTarget = path || query || command || name || tool || action.replaceAll('_', ' ')
150
+
151
+ // Append line range for read_file when startLine / endLine are available
152
+ let target = rawTarget
153
+ if (action === 'read_file' && path) {
154
+ const startLine = typeof input.startLine === 'number' ? input.startLine : undefined
155
+ const endLine = typeof input.endLine === 'number' ? input.endLine : undefined
156
+ if (startLine !== undefined && endLine !== undefined) {
157
+ target = `${rawTarget} #L${startLine}-${endLine}`
158
+ } else if (startLine !== undefined) {
159
+ target = `${rawTarget} #L${startLine}`
160
+ }
161
+ }
162
+
157
163
  const labels: Record<string, string> = {
158
164
  apply_patch: 'Applying patch',
159
165
  ask_user: 'Asking user',
@@ -189,6 +195,7 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
189
195
  target: formatActivityTarget(target),
190
196
  kind: activityKind(action, target),
191
197
  state: 'active',
198
+ action,
192
199
  }
193
200
  }
194
201
 
@@ -215,7 +222,13 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
215
222
  for (let index = activities.length - 1; index >= 0; index -= 1) {
216
223
  if (activities[index].state !== 'active') continue
217
224
  activities[index].state = event.data.result.startsWith('Error:') ? 'error' : 'done'
218
- if (activities[index].state === 'error') activities[index].label = 'Failed'
225
+ if (activities[index].state === 'error') {
226
+ activities[index].label = 'Failed'
227
+ }
228
+ if (activities[index].action === 'ask_user' && event.data.result.startsWith('User answered:')) {
229
+ const answer = event.data.result.replace('User answered:', '').trim()
230
+ activities[index].target = `(Answered: "${answer}") ${activities[index].target}`
231
+ }
219
232
  break
220
233
  }
221
234
  }
@@ -224,7 +237,16 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
224
237
  return { summary: activitySummary(activities), items }
225
238
  }
226
239
 
227
- function renderAgentActivityTable(activityView: AgentActivityView) {
240
+ function AgentActivityTable({ activityView }: { activityView: AgentActivityView }) {
241
+ const [expandedIndices, setExpandedIndices] = useState<Record<number, boolean>>({})
242
+
243
+ const toggleExpand = (index: number) => {
244
+ setExpandedIndices(prev => ({
245
+ ...prev,
246
+ [index]: !prev[index]
247
+ }))
248
+ }
249
+
228
250
  return (
229
251
  <div className="agent-activity-list">
230
252
  <div className="agent-activity-table-head" aria-hidden="true">
@@ -233,14 +255,39 @@ function renderAgentActivityTable(activityView: AgentActivityView) {
233
255
  <span>Target</span>
234
256
  <span />
235
257
  </div>
236
- {activityView.items.map((activity, index) => (
237
- <div className="agent-activity-item" data-kind={activity.kind} data-state={activity.state} key={`${index}-${activity.label}-${activity.target}`}>
238
- <span className="agent-activity-label">{activity.label}</span>
239
- <span className="agent-activity-icon" aria-hidden="true" />
240
- <span className="agent-activity-text">{activity.target}</span>
241
- <span className="agent-activity-chevron" aria-hidden="true">&gt;</span>
242
- </div>
243
- ))}
258
+ {activityView.items.map((activity, index) => {
259
+ const isExpanded = !!expandedIndices[index]
260
+ return (
261
+ <div
262
+ className="agent-activity-item"
263
+ data-kind={activity.kind}
264
+ data-state={activity.state}
265
+ key={`${index}-${activity.label}-${activity.target}`}
266
+ style={{ cursor: 'pointer' }}
267
+ onClick={() => toggleExpand(index)}
268
+ >
269
+ <span className="agent-activity-label">{activity.label}</span>
270
+ <span className="agent-activity-icon" aria-hidden="true" />
271
+ <span
272
+ className="agent-activity-text"
273
+ style={isExpanded ? { whiteSpace: 'normal', wordBreak: 'break-all', overflow: 'visible', textOverflow: 'clip' } : undefined}
274
+ >
275
+ {activity.target}
276
+ </span>
277
+ <span
278
+ className="agent-activity-chevron"
279
+ style={{
280
+ transform: isExpanded ? 'rotate(90deg)' : 'none',
281
+ transition: 'transform 0.2s ease',
282
+ display: 'inline-block'
283
+ }}
284
+ aria-hidden="true"
285
+ >
286
+ &gt;
287
+ </span>
288
+ </div>
289
+ )
290
+ })}
244
291
  </div>
245
292
  )
246
293
  }
@@ -257,7 +304,7 @@ function renderApprovalDetails(approval: any): ApprovalDetails {
257
304
  return { title: `Run MCP Tool: ${server}/${tool}`, body: typeof args === 'string' ? args : JSON.stringify(args, null, 2), reason: 'Running external MCP tool.', isDangerous: false }
258
305
  }
259
306
  if (approval.UserApproval) return { title: approval.UserApproval.title, body: approval.UserApproval.prompt, reason: 'The agent requested explicit approval.', isDangerous: false }
260
- if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: 'Approve to continue without a typed answer, or cancel to decline.', isDangerous: false }
307
+ if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: 'Type your answer below and submit to respond to the agent.', isDangerous: false }
261
308
  return { title: 'Unknown Action', body: JSON.stringify(approval, null, 2), reason: 'Requires approval to proceed.', isDangerous: false }
262
309
  }
263
310
 
@@ -353,7 +400,7 @@ interface ChatPanelProps {
353
400
  onSetSmartContext: (enabled: boolean) => void
354
401
  onSetAgentMode: (enabled: boolean) => void
355
402
  onSetProvider: (provider: string) => void
356
- onApproval: (approved: boolean, autoApproveSession?: boolean) => void
403
+ onApproval: (approved: boolean, autoApproveSession?: boolean, answer?: string) => void
357
404
  onToggleMobileSidebar: () => void
358
405
  settingsConfig: any
359
406
  onSetModel: (model: string) => void
@@ -449,7 +496,17 @@ function renderFormattedMessage(text: string) {
449
496
  </strong>
450
497
  )
451
498
  }
452
- return boldPart
499
+ const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
500
+ return mentionParts.map((mentionPart, mentionIndex) => {
501
+ if (mentionIndex % 2 === 1) {
502
+ return (
503
+ <span key={`mention-${mentionIndex}`} className="chat-mention">
504
+ {mentionPart}
505
+ </span>
506
+ )
507
+ }
508
+ return mentionPart
509
+ })
453
510
  })
454
511
  })
455
512
  }
@@ -975,6 +1032,34 @@ export default function ChatPanel({
975
1032
  const [openFileDiffs, setOpenFileDiffs] = useState<Record<string, boolean>>({})
976
1033
  const [toolMenuOpen, setToolMenuOpen] = useState(false)
977
1034
  const toolMenuRef = useRef<HTMLDivElement | null>(null)
1035
+ const [dynamicOllamaModels, setDynamicOllamaModels] = useState<string[]>(OLLAMA_MODELS)
1036
+ const [askAnswer, setAskAnswer] = useState('')
1037
+
1038
+ useEffect(() => {
1039
+ setAskAnswer('')
1040
+ }, [pendingApproval])
1041
+
1042
+ useEffect(() => {
1043
+ const fetchOllamaModels = async () => {
1044
+ if (status?.activeProvider !== 'ollama') return;
1045
+ const host = settingsConfig?.ollamaHost || 'http://localhost:11434';
1046
+ const cleanHost = host.endsWith('/') ? host.slice(0, -1) : host;
1047
+ try {
1048
+ const res = await fetch(`${cleanHost}/api/tags`);
1049
+ if (res.ok) {
1050
+ const data = await res.json();
1051
+ if (data && Array.isArray(data.models)) {
1052
+ setDynamicOllamaModels(data.models.map((m: any) => m.name));
1053
+ return;
1054
+ }
1055
+ }
1056
+ } catch (err) {
1057
+ // fallback to default if fetch fails
1058
+ }
1059
+ setDynamicOllamaModels(OLLAMA_MODELS);
1060
+ }
1061
+ fetchOllamaModels();
1062
+ }, [status?.activeProvider, settingsConfig?.ollamaHost])
978
1063
  const canSubmit = Boolean(message.trim() || imageAttachments.length > 0 || documentName)
979
1064
  const sendingImageMarkers = Array.from({ length: sendingImageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
980
1065
 
@@ -995,7 +1080,7 @@ export default function ChatPanel({
995
1080
  case 'local_openai':
996
1081
  return LOCAL_MODELS
997
1082
  case 'ollama':
998
- return OLLAMA_MODELS
1083
+ return dynamicOllamaModels
999
1084
  default:
1000
1085
  return []
1001
1086
  }
@@ -1543,7 +1628,7 @@ export default function ChatPanel({
1543
1628
  </button>
1544
1629
  {isOpen && (
1545
1630
  <div className="agent-activity-card agent-activity-card-history">
1546
- {renderAgentActivityTable(activityView)}
1631
+ <AgentActivityTable activityView={activityView} />
1547
1632
  </div>
1548
1633
  )}
1549
1634
  </div>
@@ -1885,7 +1970,7 @@ export default function ChatPanel({
1885
1970
  {pendingApproval ? 'Waiting for approval' : 'Working'}
1886
1971
  </span>
1887
1972
  </div>
1888
- {renderAgentActivityTable(agentActivities)}
1973
+ <AgentActivityTable activityView={agentActivities} />
1889
1974
  </div>
1890
1975
  </div>
1891
1976
  )}
@@ -1967,6 +2052,7 @@ export default function ChatPanel({
1967
2052
  const writeFile = pendingApproval.approval?.WriteFile
1968
2053
  const applyPatch = pendingApproval.approval?.ApplyPatch
1969
2054
  const diffText = writeFile?.diff || applyPatch?.diff
2055
+ const isAskUser = !!pendingApproval.approval?.AskUser
1970
2056
 
1971
2057
  return (
1972
2058
  <div className="message ai-message" style={{ width: '100%' }}>
@@ -1983,12 +2069,54 @@ export default function ChatPanel({
1983
2069
  ) : (
1984
2070
  details.reason && <div className="approval-card-reason" style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>{details.reason}</div>
1985
2071
  )}
2072
+ {isAskUser && (
2073
+ <div className="approval-card-input-container" style={{ marginTop: '12px', width: '100%' }}>
2074
+ <textarea
2075
+ style={{
2076
+ width: '100%',
2077
+ minHeight: '70px',
2078
+ padding: '10px',
2079
+ background: 'rgba(0, 0, 0, 0.2)',
2080
+ border: '1px solid rgba(255, 255, 255, 0.1)',
2081
+ borderRadius: '6px',
2082
+ color: 'var(--text-main, #f8fafc)',
2083
+ fontSize: '0.9rem',
2084
+ fontFamily: 'inherit',
2085
+ resize: 'vertical',
2086
+ outline: 'none',
2087
+ }}
2088
+ placeholder="Type your answer here..."
2089
+ value={askAnswer}
2090
+ onChange={(e) => setAskAnswer(e.target.value)}
2091
+ />
2092
+ </div>
2093
+ )}
1986
2094
  </div>
1987
- <div className="approval-card-actions">
1988
- <button type="button" className="approval-btn approval-btn-approve" onClick={() => onApproval(true)}>Approve</button>
1989
- <button type="button" className="approval-btn" style={{ backgroundColor: 'rgba(16, 185, 129, 0.22)', borderColor: 'rgba(16, 185, 129, 0.4)', color: '#a7f3d0' }} onClick={() => onApproval(true, true)}>Approve this session</button>
1990
- <button type="button" className="approval-btn approval-btn-cancel" onClick={() => onApproval(false)}>Cancel</button>
1991
- </div>
2095
+ {isAskUser ? (
2096
+ <div className="approval-card-actions">
2097
+ <button
2098
+ type="button"
2099
+ className="approval-btn approval-btn-approve"
2100
+ disabled={!askAnswer.trim()}
2101
+ onClick={() => onApproval(true, false, askAnswer)}
2102
+ >
2103
+ Submit Answer
2104
+ </button>
2105
+ <button
2106
+ type="button"
2107
+ className="approval-btn approval-btn-cancel"
2108
+ onClick={() => onApproval(false)}
2109
+ >
2110
+ Decline
2111
+ </button>
2112
+ </div>
2113
+ ) : (
2114
+ <div className="approval-card-actions">
2115
+ <button type="button" className="approval-btn approval-btn-approve" onClick={() => onApproval(true)}>Approve</button>
2116
+ <button type="button" className="approval-btn" style={{ backgroundColor: 'rgba(16, 185, 129, 0.22)', borderColor: 'rgba(16, 185, 129, 0.4)', color: '#a7f3d0' }} onClick={() => onApproval(true, true)}>Approve this session</button>
2117
+ <button type="button" className="approval-btn approval-btn-cancel" onClick={() => onApproval(false)}>Cancel</button>
2118
+ </div>
2119
+ )}
1992
2120
  </div>
1993
2121
  </div>
1994
2122
  </div>
@@ -2134,7 +2262,7 @@ export default function ChatPanel({
2134
2262
  return <option key={provider} value={provider}>{displayName}</option>
2135
2263
  })}
2136
2264
  </select>
2137
- {availableModels.length > 0 && (
2265
+ {(availableModels.length > 0 || activeModel) && (
2138
2266
  <select
2139
2267
  value={activeModel}
2140
2268
  onChange={(event) => onSetModel(event.target.value)}
@@ -2192,6 +2320,9 @@ export default function ChatPanel({
2192
2320
  </button>
2193
2321
  </form>
2194
2322
  </div>
2323
+ <p className="input-disclaimer">
2324
+ Mint Agent is an AI gateway. Responses via third-party APIs. Verify critical info.
2325
+ </p>
2195
2326
  </section>
2196
2327
  )
2197
2328
  }
@@ -16,6 +16,7 @@ interface ImageStudioPanelProps {
16
16
  view: string
17
17
  onRefreshPictures?: () => void
18
18
  onSendToChat?: (dataUri: string, prompt: string) => void
19
+ onToggleMobileSidebar?: () => void
19
20
  }
20
21
 
21
22
  const ASPECT_OPTIONS: { value: AspectRatio; label: string; icon: string }[] = [
@@ -85,7 +86,7 @@ function SkeletonCard() {
85
86
  )
86
87
  }
87
88
 
88
- export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat }: ImageStudioPanelProps) {
89
+ export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat, onToggleMobileSidebar }: ImageStudioPanelProps) {
89
90
  const [prompt, setPrompt] = useState('')
90
91
  const [negativePrompt, setNegativePrompt] = useState('')
91
92
  const [showNegative, setShowNegative] = useState(false)
@@ -206,6 +207,15 @@ export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat
206
207
  {/* Header */}
207
208
  <header className="img-studio-header">
208
209
  <div className="img-studio-header-title">
210
+ <button
211
+ className="mobile-menu-btn"
212
+ type="button"
213
+ onClick={onToggleMobileSidebar}
214
+ aria-label="Toggle menu"
215
+ style={{ marginRight: '8px' }}
216
+ >
217
+ ☰
218
+ </button>
209
219
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
210
220
  <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
211
221
  </svg>
@@ -384,6 +384,10 @@ export default function MintDashboard() {
384
384
  }
385
385
 
386
386
  const toggleSidebar = () => {
387
+ if (window.innerWidth <= 760) {
388
+ setMobileSidebarOpen(false)
389
+ return
390
+ }
387
391
  const next = !sidebarCollapsed
388
392
  window.localStorage.setItem('mint:sidebar-collapsed', String(next))
389
393
  setSidebarCollapsed(next)
@@ -400,10 +404,10 @@ export default function MintDashboard() {
400
404
  setAgentMode(enabled)
401
405
  }
402
406
 
403
- async function handleApproval(approved: boolean, _autoApproveSession = false) {
407
+ async function handleApproval(approved: boolean, _autoApproveSession = false, answer?: string) {
404
408
  if (!pendingApproval) return
405
409
  try {
406
- await submitToolApproval(pendingApproval.token, approved)
410
+ await submitToolApproval(pendingApproval.token, approved, answer)
407
411
  } catch (reason) {
408
412
  setError(errorMessage(reason))
409
413
  } finally {
@@ -825,7 +829,7 @@ export default function MintDashboard() {
825
829
 
826
830
  return (
827
831
  <div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
828
- <div className={`app-body ${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}>
832
+ <div className={`app-body ${(sidebarCollapsed && window.innerWidth > 760) ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}>
829
833
  {mobileSidebarOpen && (
830
834
  <div
831
835
  className="sidebar-backdrop"
@@ -908,6 +912,7 @@ export default function MintDashboard() {
908
912
  setView('chat')
909
913
  setMessage(imgPrompt)
910
914
  }}
915
+ onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
911
916
  />
912
917
  </div>
913
918
  <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>