@pheem49/mint 1.8.1 → 1.9.0

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 (200) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +0 -17
  4. package/Release_Note.md +137 -58
  5. package/bin/mint +0 -0
  6. package/crates/mint-cli/src/agent.rs +150 -45
  7. package/crates/mint-cli/src/image.rs +8 -8
  8. package/crates/mint-cli/src/main.rs +929 -412
  9. package/crates/mint-cli/src/onboard.rs +108 -108
  10. package/crates/mint-cli/src/setup.rs +143 -143
  11. package/crates/mint-core/src/agent_loop.rs +1 -0
  12. package/crates/mint-core/src/api_server.rs +108 -52
  13. package/crates/mint-core/src/browser.rs +56 -1
  14. package/crates/mint-core/src/channels.rs +1 -0
  15. package/crates/mint-core/src/chat.rs +126 -8
  16. package/crates/mint-core/src/code_tools.rs +18 -20
  17. package/crates/mint-core/src/config.rs +245 -17
  18. package/crates/mint-core/src/image_gen.rs +5 -5
  19. package/crates/mint-core/src/lib.rs +16 -2
  20. package/crates/mint-core/src/memory.rs +6 -1
  21. package/crates/mint-core/src/orchestration.rs +260 -72
  22. package/crates/mint-core/src/pictures.rs +3 -4
  23. package/crates/mint-core/src/semantic.rs +1 -1
  24. package/crates/mint-core/src/shell.rs +1 -1
  25. package/crates/mint-core/src/skills.rs +46 -19
  26. package/crates/mint-core/src/tts.rs +3 -1
  27. package/flatpak/com.pheem49.mint.desktop +9 -0
  28. package/flatpak/com.pheem49.mint.metainfo.xml +35 -0
  29. package/flatpak/com.pheem49.mint.yaml +52 -0
  30. package/package.json +1 -1
  31. package/shared-modules/.github/workflows/ci.yml +120 -0
  32. package/shared-modules/.github/workflows/update.yaml +28 -0
  33. package/shared-modules/CODEOWNERS +23 -0
  34. package/shared-modules/README.md +79 -0
  35. package/shared-modules/SDL/README.md +3 -0
  36. package/shared-modules/SDL/SDL-1.2.15.json +51 -0
  37. package/shared-modules/SDL/SDL_Pango-0.1.2-API-adds.patch +118 -0
  38. package/shared-modules/SDL/SDL_image-1.2.12.json +25 -0
  39. package/shared-modules/SDL/SDL_mixer-1.2.12.json +27 -0
  40. package/shared-modules/SDL/SDL_net-1.2.8.json +19 -0
  41. package/shared-modules/SDL/SDL_pango-0.1.2.json +26 -0
  42. package/shared-modules/SDL/SDL_ttf-2.0.11.json +28 -0
  43. package/shared-modules/SDL/libsdl-1.2.15-strict-prototypes.patch +316 -0
  44. package/shared-modules/SDL/sdl-check-for-SDL_VIDEO_X11_BACKINGSTORE.patch +24 -0
  45. package/shared-modules/SDL/sdl-libx11-build.patch +59 -0
  46. package/shared-modules/SDL/sdl-sysloadso-buffer-length.patch +27 -0
  47. package/shared-modules/SDL/sdl12-compat.json +22 -0
  48. package/shared-modules/SDL2/SDL2_image.json +27 -0
  49. package/shared-modules/SDL2/SDL2_ttf.json +29 -0
  50. package/shared-modules/cld2/CMakeLists.txt +157 -0
  51. package/shared-modules/cld2/cld2.json +24 -0
  52. package/shared-modules/clutter/clutter.json +72 -0
  53. package/shared-modules/dbus-glib/dbus-glib.json +29 -0
  54. package/shared-modules/glew/glew.json +36 -0
  55. package/shared-modules/glu/glu-9.json +12 -0
  56. package/shared-modules/gtk2/arc-gtk2-theme-Replace-Inkscape-dependency-with-rsvg-convert.patch +180 -0
  57. package/shared-modules/gtk2/gtk2-common-themes.json +360 -0
  58. package/shared-modules/gtk2/gtk2-fix-crash-in-show-uri.patch +30 -0
  59. package/shared-modules/gtk2/gtk2-gcc14.patch +281 -0
  60. package/shared-modules/gtk2/gtk2-gcc15.patch +250 -0
  61. package/shared-modules/gtk2/gtk2-murrine-c99.patch +34 -0
  62. package/shared-modules/gtk2/gtk2-use-adwaita-theme.patch +80 -0
  63. package/shared-modules/gtk2/gtk2.json +120 -0
  64. package/shared-modules/gtk2/ibus-gcc15.patch +13 -0
  65. package/shared-modules/gtk2/murrine-engine-fix-crash.patch +12 -0
  66. package/shared-modules/gtk2/readme.md +10 -0
  67. package/shared-modules/intltool/intltool-0.51.json +20 -0
  68. package/shared-modules/intltool/intltool-perl5.26-regex-fixes.patch +59 -0
  69. package/shared-modules/lame/0001-Build-xmm_quantize_sub.c-with-msse.patch +34 -0
  70. package/shared-modules/lame/0002-Include-GTK-1-autoconf-directives-in-build-system.patch +222 -0
  71. package/shared-modules/lame/0003-Patch-out-remaining-ansi2knr.patch +70 -0
  72. package/shared-modules/lame/0004-initscr-is-not-used-anywhere-in-lame-sourcetree.patch +41 -0
  73. package/shared-modules/lame/0005-Update-autoconf.patch +81 -0
  74. package/shared-modules/lame/lame-3.99.5.json +32 -0
  75. package/shared-modules/libappindicator/libappindicator-gtk2-12.10.json +140 -0
  76. package/shared-modules/libappindicator/libappindicator-gtk3-12.10.json +140 -0
  77. package/shared-modules/libappindicator/libappindicator-gtk3-introspection-12.10.json +140 -0
  78. package/shared-modules/libappindicator/patches/0001-libappindicator-ftbfs.patch +20 -0
  79. package/shared-modules/libappindicator/patches/0002-libappindicator-no-python.patch +888 -0
  80. package/shared-modules/libappindicator/patches/0003-libappindicator-fix-crash-from-incorrect-signal-emission.patch +74 -0
  81. package/shared-modules/libappindicator/patches/0004-libappindicator-fix-crash-iterating-icon-themes.patch +11 -0
  82. package/shared-modules/libayatana-appindicator/0001-Fix-HAVE_VALGRIND-AM_CONDITIONAL.patch +51 -0
  83. package/shared-modules/libayatana-appindicator/0001-Make-introspection-configurable.patch +44 -0
  84. package/shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json +121 -0
  85. package/shared-modules/libcanberra/0001-gtk-Don-t-assume-all-GdkDisplays-are-GdkX11Displays-.patch +123 -0
  86. package/shared-modules/libcanberra/libcanberra.json +44 -0
  87. package/shared-modules/libmad/libmad.json +19 -0
  88. package/shared-modules/libsecret/libsecret.json +23 -0
  89. package/shared-modules/libsoup/libsoup-2.4.json +14 -0
  90. package/shared-modules/libusb/libusb.json +27 -0
  91. package/shared-modules/linux-audio/dssi.json +31 -0
  92. package/shared-modules/linux-audio/fftw3f-static.json +44 -0
  93. package/shared-modules/linux-audio/fftw3f.json +42 -0
  94. package/shared-modules/linux-audio/fluidsynth2-static.json +36 -0
  95. package/shared-modules/linux-audio/fluidsynth2.json +25 -0
  96. package/shared-modules/linux-audio/ladspa.json +26 -0
  97. package/shared-modules/linux-audio/lash.json +35 -0
  98. package/shared-modules/linux-audio/libinstpatch.json +16 -0
  99. package/shared-modules/linux-audio/liblo-static.json +22 -0
  100. package/shared-modules/linux-audio/liblo.json +17 -0
  101. package/shared-modules/linux-audio/lilv-static.json +94 -0
  102. package/shared-modules/linux-audio/lilv.json +89 -0
  103. package/shared-modules/linux-audio/lrdf.json +57 -0
  104. package/shared-modules/linux-audio/lv2.json +20 -0
  105. package/shared-modules/linux-audio/patches/lash/0001-configure-remove-aclocal-m4.patch +29 -0
  106. package/shared-modules/linux-audio/patches/lash/0002-Fix-build-with-recent-gcc.patch +73 -0
  107. package/shared-modules/linux-audio/patches/lash/0003-Fix-linking.patch +136 -0
  108. package/shared-modules/linux-audio/patches/raptor-libxml.patch +31 -0
  109. package/shared-modules/linux-audio/patches/stk-install.sh +54 -0
  110. package/shared-modules/linux-audio/stk.json +28 -0
  111. package/shared-modules/lua5.1/0004-Fix-stack-overflow-in-vararg-functions.patch +21 -0
  112. package/shared-modules/lua5.1/lua-5.1.5-so.patch +43 -0
  113. package/shared-modules/lua5.1/lua-5.1.5.json +42 -0
  114. package/shared-modules/lua5.3/lua-5.3.5.json +41 -0
  115. package/shared-modules/lua5.3/lua.pc.in +8 -0
  116. package/shared-modules/lua5.3/luaconf.patch +47 -0
  117. package/shared-modules/lua5.3/makefile-add-liblua.so-rule.patch +47 -0
  118. package/shared-modules/lua5.4/lua-5.4.json +41 -0
  119. package/shared-modules/lua5.4/lua.pc.in +8 -0
  120. package/shared-modules/lua5.4/luaconf.patch +34 -0
  121. package/shared-modules/lua5.4/makefile-add-liblua.so-rule.patch +42 -0
  122. package/shared-modules/luajit/luajit.json +30 -0
  123. package/shared-modules/lzo/lzo.json +27 -0
  124. package/shared-modules/mac/mac.json +18 -0
  125. package/shared-modules/physfs/physfs.json +26 -0
  126. package/shared-modules/pygame/audiofile-gcc6.patch +21 -0
  127. package/shared-modules/pygame/cython-fixes.patch +34 -0
  128. package/shared-modules/pygame/fluidsynth-no-rawmidi.patch +69 -0
  129. package/shared-modules/pygame/glib.patch +25 -0
  130. package/shared-modules/pygame/pygame1-python2.json +157 -0
  131. package/shared-modules/pygtk/pygtk-c99.patch +15 -0
  132. package/shared-modules/pygtk/pygtk.json +117 -0
  133. package/shared-modules/pygtk/pygtk.patch +39 -0
  134. package/shared-modules/python2.7/python-2.7.json +48 -0
  135. package/shared-modules/smpeg/smpeg-0.4.5.json +50 -0
  136. package/shared-modules/smpeg/smpeg-am-prog-as.patch +12 -0
  137. package/shared-modules/smpeg/smpeg-export-mpegaudio-class.patch +17 -0
  138. package/shared-modules/smpeg/smpeg-gcc6.patch +40 -0
  139. package/shared-modules/smpeg/smpeg-no-gtk.patch +37 -0
  140. package/shared-modules/squashfs-tools/squashfs-tools.json +25 -0
  141. package/shared-modules/update-checker.sh +50 -0
  142. package/shared-modules/vorbisgain/0001-temp_files.patch +75 -0
  143. package/shared-modules/vorbisgain/0002-errno.patch +33 -0
  144. package/shared-modules/vorbisgain/0003-manpage.patch +248 -0
  145. package/shared-modules/vorbisgain/0004-vorbisgain_mtime.patch +183 -0
  146. package/shared-modules/vorbisgain/0005-double_fclose.patch +21 -0
  147. package/shared-modules/vorbisgain/0006-manpage_hyphens.patch +56 -0
  148. package/shared-modules/vorbisgain/0007-recursively_spelling.patch +14 -0
  149. package/shared-modules/vorbisgain/0008-manpage_recursion_mistake.patch +23 -0
  150. package/shared-modules/vorbisgain/0009-hardening.patch +13 -0
  151. package/shared-modules/vorbisgain/0010-fclose.patch +13 -0
  152. package/shared-modules/vorbisgain/README.md +23 -0
  153. package/shared-modules/vorbisgain/vorbisgain_0.37-2.json +29 -0
  154. package/src/renderer/shared/agentProgress.ts +145 -0
  155. package/src/renderer/shared/calculator.ts +52 -0
  156. package/src/renderer/shared/components/AgentActivityDrawer.tsx +72 -0
  157. package/src/renderer/shared/components/AgentActivityTable.tsx +85 -0
  158. package/src/renderer/shared/components/ApprovalCard.tsx +93 -0
  159. package/src/renderer/shared/components/ChatCodeBlock.tsx +75 -0
  160. package/src/renderer/shared/components/ThinkingBlock.tsx +101 -0
  161. package/src/renderer/shared/constants/models.ts +91 -0
  162. package/src/renderer/shared/platform.ts +82 -0
  163. package/src/renderer/shared/types.ts +140 -0
  164. package/src/renderer/shared/utils/agentActivity.ts +187 -0
  165. package/src/renderer/shared/utils/approval.ts +1 -0
  166. package/src/renderer/shared/utils/approval.tsx +64 -0
  167. package/src/renderer/shared/utils/fileIcons.ts +135 -0
  168. package/src/renderer/shared/utils/markdown.tsx +347 -0
  169. package/src/renderer/shared/utils/providers.ts +36 -0
  170. package/src/renderer/shared/utils/speech.ts +216 -0
  171. package/src/renderer/shared/utils/ui.ts +147 -0
  172. package/src/renderer/src/agentProgress.ts +11 -39
  173. package/src/renderer/src/calculator.ts +3 -46
  174. package/src/renderer/src/components/ChatPanel.tsx +219 -1308
  175. package/src/renderer/src/components/MintDashboard.tsx +67 -170
  176. package/src/renderer/src/components/PicturesLibrary.tsx +32 -8
  177. package/src/renderer/src/components/Settings/AgentsTab.tsx +381 -0
  178. package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -22
  179. package/src/renderer/src/components/Settings/GeneralTab.tsx +256 -2
  180. package/src/renderer/src/components/SettingsWindow.tsx +120 -3
  181. package/src/renderer/src/components/WorkspacePanel.tsx +7 -125
  182. package/src/renderer/src/css/styles.css +100 -0
  183. package/src/renderer/src/tauri.ts +128 -143
  184. package/src/renderer/src-web/agentProgress.ts +11 -39
  185. package/src/renderer/src-web/calculator.ts +3 -46
  186. package/src/renderer/src-web/components/ChatPanel.tsx +219 -1306
  187. package/src/renderer/src-web/components/MintDashboard.tsx +42 -170
  188. package/src/renderer/src-web/components/PicturesLibrary.tsx +23 -29
  189. package/src/renderer/src-web/components/Settings/AgentsTab.tsx +381 -0
  190. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -22
  191. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +254 -3
  192. package/src/renderer/src-web/components/SettingsWindow.tsx +127 -3
  193. package/src/renderer/src-web/css/styles.css +100 -0
  194. package/src/renderer/src-web/tauri.ts +180 -102
  195. package/src-tauri/src/desktop.rs +18 -16
  196. package/src-tauri/src/discord_rpc.rs +1 -1
  197. package/src-tauri/src/lib.rs +168 -40
  198. package/src-tauri/src/webhooks.rs +1 -0
  199. package/src-tauri/src/workflows.rs +1 -0
  200. package/src-tauri/tauri.conf.json +1 -1
@@ -1,106 +1,29 @@
1
- export interface RuntimeStatus {
2
- backend: string
3
- configPath: string
4
- activeProvider: string
5
- availableProviders: string[]
6
- integrations: Record<string, unknown>
7
- localIp?: string
8
- }
9
-
10
- export interface ChatResponse {
11
- provider: string
12
- model: string
13
- text: string
14
- fallbackProvider?: string | null
15
- }
1
+ export * from '../shared/types'
2
+ import type { MintPlatformApi } from '../shared/platform'
3
+ import type {
16
4
 
17
- export interface TtsUrl {
18
- shortText: string
19
- url: string
20
- }
21
-
22
- export interface DocumentAttachment {
23
- filename: string
24
- dataUri: string
25
- }
5
+ RuntimeStatus,
6
+ ChatResponse,
7
+ TtsUrl,
8
+ DocumentAttachment,
9
+ AgentProgress,
10
+ InteractionMemory,
11
+ ChatSession,
12
+ PictureEntry,
13
+ ImageGenRequest,
14
+ ImageGenProviders,
15
+ ImageGenResponse,
16
+ WorkspaceTreeEntry,
17
+ CodeEdit,
18
+ CodeEditProposal,
19
+ LearnedSkill,
20
+ } from '../shared/types'
26
21
 
27
- export type AgentProgress =
28
- | { type: 'Thinking'; data: { elapsed_secs: number } }
29
- | { type: 'Thought'; data: { thought: string } }
30
- | { type: 'ToolStart'; data: { action: string; input: Record<string, unknown> } }
31
- | { type: 'ToolEnd'; data: { action: string; input: Record<string, unknown>; result: string } }
32
22
 
33
23
  type DesktopStreamEvent =
34
24
  | { type: 'chunk'; chunk: string }
35
25
  | { type: 'progress'; progress: AgentProgress }
36
26
 
37
- export interface InteractionMemory {
38
- id: number
39
- chatId: string
40
- userText: string
41
- aiText: string
42
- provider: string
43
- model: string
44
- fallbackProvider?: string | null
45
- createdAt: string
46
- agentActivity?: AgentProgress[] | null
47
- }
48
-
49
- export interface ChatSession {
50
- id: string
51
- title: string
52
- kind: string
53
- createdAt: string
54
- updatedAt: string
55
- }
56
-
57
- export interface PictureEntry {
58
- id: string
59
- filename: string
60
- path: string
61
- mimeType: string
62
- createdAt: string
63
- source: string
64
- message: string
65
- thumbnailPath?: string
66
- url?: string
67
- thumbnailUrl?: string
68
- }
69
-
70
- export interface ImageGenRequest {
71
- prompt: string
72
- negativePrompt?: string
73
- aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3'
74
- numImages?: number
75
- model?: string
76
- /** Which image generation provider to use. Falls back to config default when omitted. */
77
- provider?: string
78
- }
79
-
80
- export interface ImageGenProviders {
81
- active: string
82
- available: string[]
83
- }
84
-
85
- export interface ImageGenResponse {
86
- images: PictureEntry[]
87
- model: string
88
- provider: string
89
- prompt: string
90
- description?: string | null
91
- }
92
-
93
-
94
- export interface CodeEdit {
95
- path: string
96
- content: string
97
- }
98
-
99
- export interface CodeEditProposal {
100
- approvalRequired: boolean
101
- approvalToken: string
102
- edits: Array<{ path: string; existed: boolean; diff: string }>
103
- }
104
27
 
105
28
  export const isTauriRuntime = () => (
106
29
  typeof window !== 'undefined' && Boolean((window as any).__TAURI_INTERNALS__)
@@ -163,6 +86,7 @@ export async function sendChatMessage(
163
86
  documentAttachment?: DocumentAttachment | null,
164
87
  workspacePath?: string | null,
165
88
  chatId?: string | null,
89
+ agentId?: string | null,
166
90
  ): Promise<ChatResponse> {
167
91
  const outgoingMessage = withImagePlaceholder(message, imageDataUri)
168
92
  if (typeof window === 'undefined' || !isTauriRuntime()) {
@@ -171,7 +95,7 @@ export async function sendChatMessage(
171
95
  const res = await fetch(`${API_BASE}/chat`, {
172
96
  method: 'POST',
173
97
  headers: { 'Content-Type': 'application/json' },
174
- body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment })
98
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, agentId })
175
99
  });
176
100
  const data = await res.json().catch(() => null);
177
101
  if (!res.ok) {
@@ -196,7 +120,7 @@ export async function sendChatMessage(
196
120
  }
197
121
  const { invoke } = await import('@tauri-apps/api/core')
198
122
  const response = await invoke<ChatResponse>('send_chat_message', {
199
- request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
123
+ request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath, agentId },
200
124
  })
201
125
  if (imageDataUri) {
202
126
  await invoke('save_pictures', {
@@ -218,6 +142,7 @@ export async function streamChatMessage(
218
142
  documentAttachment?: DocumentAttachment | null,
219
143
  workspacePath?: string | null,
220
144
  chatId?: string | null,
145
+ agentId?: string | null,
221
146
  ): Promise<ChatResponse> {
222
147
  if (typeof window === 'undefined' || !isTauriRuntime()) {
223
148
  const API_BASE = getApiBase();
@@ -225,7 +150,7 @@ export async function streamChatMessage(
225
150
  const res = await fetch(`${API_BASE}/chat-stream`, {
226
151
  method: 'POST',
227
152
  headers: { 'Content-Type': 'application/json' },
228
- body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment })
153
+ body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, agentId })
229
154
  });
230
155
  if (!res.ok) {
231
156
  const data = await res.json().catch(() => null);
@@ -271,7 +196,7 @@ export async function streamChatMessage(
271
196
  else onProgress?.(event.progress)
272
197
  }
273
198
  const response = await invoke<ChatResponse>('stream_chat_message', {
274
- request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
199
+ request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath, agentId },
275
200
  onEvent,
276
201
  })
277
202
  if (imageDataUri) {
@@ -298,6 +223,20 @@ export async function getTtsUrls(text: string): Promise<TtsUrl[]> {
298
223
  return invoke<TtsUrl[]>('get_tts_urls', { text })
299
224
  }
300
225
 
226
+ export async function cancelChatMessage(chatId: string): Promise<void> {
227
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
228
+ const API_BASE = getApiBase();
229
+ await fetch(`${API_BASE}/cancel-chat`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify({ chatId })
233
+ });
234
+ return;
235
+ }
236
+ const { invoke } = await import('@tauri-apps/api/core')
237
+ await invoke('cancel_chat_message', { chatId })
238
+ }
239
+
301
240
  export async function getRecentInteractions(limit = 50, chatId?: string | null): Promise<InteractionMemory[]> {
302
241
  if (typeof window === 'undefined' || !isTauriRuntime()) {
303
242
  const API_BASE = getApiBase();
@@ -476,11 +415,12 @@ export async function listSavedPictures(): Promise<PictureEntry[]> {
476
415
  if (typeof window === 'undefined' || !isTauriRuntime()) {
477
416
  const API_BASE = getApiBase();
478
417
  try {
479
- const res = await fetch(`${API_BASE}/pictures`);
418
+ const res = await fetch(`${API_BASE}/pictures?_t=${Date.now()}`);
480
419
  const pictures = await res.json();
420
+ const timestamp = Date.now();
481
421
  return Array.isArray(pictures)
482
422
  ? pictures.map((picture) => {
483
- const pictureUrl = picture.url ? `${API_BASE.replace('/api', '')}${picture.url}` : undefined
423
+ const pictureUrl = picture.url ? `${API_BASE.replace('/api', '')}${picture.url}?_t=${timestamp}` : undefined
484
424
  return {
485
425
  ...picture,
486
426
  path: pictureUrl || picture.path,
@@ -1176,8 +1116,109 @@ async function captureSharedScreen(): Promise<string> {
1176
1116
  }
1177
1117
  }
1178
1118
 
1119
+ export async function listLearnedSkills(workspacePath?: string): Promise<LearnedSkill[]> {
1120
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
1121
+ try {
1122
+ const val = await getProfileValue('learned-skills-web-mock')
1123
+ if (val) {
1124
+ return JSON.parse(val)
1125
+ }
1126
+ return []
1127
+ } catch (e) {
1128
+ console.error("Failed to load web mock skills:", e)
1129
+ return []
1130
+ }
1131
+ }
1132
+ const { invoke } = await import('@tauri-apps/api/core')
1133
+ return invoke<LearnedSkill[]>('list_learned_skills', { workspacePath })
1134
+ }
1135
+
1136
+ export async function addLearnedSkill(name: string, content: string): Promise<LearnedSkill> {
1137
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
1138
+ try {
1139
+ const list = await listLearnedSkills()
1140
+ const newSkill: LearnedSkill = {
1141
+ id: Date.now(),
1142
+ name,
1143
+ sourcePath: 'ui_manual',
1144
+ content,
1145
+ updatedAt: new Date().toISOString()
1146
+ }
1147
+ list.push(newSkill)
1148
+ await setProfileValue('learned-skills-web-mock', JSON.stringify(list))
1149
+ return newSkill
1150
+ } catch (e) {
1151
+ console.error("Failed to add web mock skill:", e)
1152
+ throw e
1153
+ }
1154
+ }
1155
+ const { invoke } = await import('@tauri-apps/api/core')
1156
+ return invoke<LearnedSkill>('add_learned_skill', { name, content })
1157
+ }
1158
+
1159
+ export async function deleteLearnedSkill(name: string): Promise<number> {
1160
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
1161
+ try {
1162
+ const list = await listLearnedSkills()
1163
+ const filtered = list.filter(s => s.name !== name)
1164
+ const deletedCount = list.length - filtered.length
1165
+ await setProfileValue('learned-skills-web-mock', JSON.stringify(filtered))
1166
+ return deletedCount
1167
+ } catch (e) {
1168
+ console.error("Failed to delete web mock skill:", e)
1169
+ return 0
1170
+ }
1171
+ }
1172
+ const { invoke } = await import('@tauri-apps/api/core')
1173
+ return invoke<number>('delete_learned_skill', { name })
1174
+ }
1175
+
1176
+ export async function getWorkspaceTree(path?: string | null): Promise<WorkspaceTreeEntry> {
1177
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
1178
+ return {
1179
+ name: 'Workspace',
1180
+ path: '.',
1181
+ kind: 'directory',
1182
+ children: [
1183
+ { name: 'src', path: 'src', kind: 'directory', children: [] },
1184
+ { name: 'package.json', path: 'package.json', kind: 'file', children: [] },
1185
+ ],
1186
+ }
1187
+ }
1188
+ const { invoke } = await import('@tauri-apps/api/core')
1189
+ return invoke<WorkspaceTreeEntry>('get_workspace_tree', { path })
1190
+ }
1191
+
1192
+ export async function createWorkspaceFile(path: string): Promise<void> {
1193
+ if (typeof window === 'undefined' || !isTauriRuntime()) return
1194
+ const { invoke } = await import('@tauri-apps/api/core')
1195
+ return invoke('create_workspace_file', { path })
1196
+ }
1197
+
1198
+ export async function createWorkspaceFolder(path: string): Promise<void> {
1199
+ if (typeof window === 'undefined' || !isTauriRuntime()) return
1200
+ const { invoke } = await import('@tauri-apps/api/core')
1201
+ return invoke('create_workspace_folder', { path })
1202
+ }
1203
+
1204
+ export async function deleteWorkspaceItem(path: string): Promise<void> {
1205
+ if (typeof window === 'undefined' || !isTauriRuntime()) return
1206
+ const { invoke } = await import('@tauri-apps/api/core')
1207
+ return invoke('delete_workspace_item', { path })
1208
+ }
1209
+
1210
+ export async function selectWorkspaceDirectory(): Promise<string | null> {
1211
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
1212
+ return null
1213
+ }
1214
+ const { invoke } = await import('@tauri-apps/api/core')
1215
+ const selected = await invoke<string | null>('select_workspace_directory')
1216
+ return selected
1217
+ }
1218
+
1179
1219
  export async function readClipboardImage(): Promise<string | null> {
1180
1220
  if (typeof window === 'undefined' || !isTauriRuntime()) {
1221
+
1181
1222
  return null
1182
1223
  }
1183
1224
  const { invoke } = await import('@tauri-apps/api/core')
@@ -1188,3 +1229,40 @@ export async function readClipboardImage(): Promise<string | null> {
1188
1229
  return null
1189
1230
  }
1190
1231
  }
1232
+
1233
+ // Enforce compile-time check against the shared platform interface
1234
+ const _apiCheck: MintPlatformApi = {
1235
+ getRuntimeStatus,
1236
+ detectSystemTools,
1237
+ sendChatMessage,
1238
+ streamChatMessage,
1239
+ getTtsUrls,
1240
+ cancelChatMessage,
1241
+ getRecentInteractions,
1242
+ saveSystemInteraction,
1243
+ saveInteractionAgentActivity,
1244
+ listChatSessions,
1245
+ deleteChatSession,
1246
+ renameChatSession,
1247
+ getProfileValue,
1248
+ setProfileValue,
1249
+ listLearnedSkills,
1250
+ addLearnedSkill,
1251
+ deleteLearnedSkill,
1252
+ clearChatHistory,
1253
+ listSavedPictures,
1254
+ generateImages,
1255
+ getImageGenProviders,
1256
+ setDefaultImageProvider,
1257
+ getWorkspaceTree,
1258
+ createWorkspaceFile,
1259
+ createWorkspaceFolder,
1260
+ deleteWorkspaceItem,
1261
+ selectWorkspaceDirectory,
1262
+ submitToolApproval,
1263
+ proposeCodeEdits,
1264
+ applyCodeEdits,
1265
+ listen,
1266
+ readClipboardImage,
1267
+ }
1268
+
@@ -201,28 +201,30 @@ pub fn capture_screen() -> Result<String, String> {
201
201
  }
202
202
 
203
203
  pub fn read_clipboard_image() -> Result<String, String> {
204
- let output = Command::new("wl-paste").args(&["-t", "image/png"]).output();
204
+ let output = Command::new("wl-paste").args(["-t", "image/png"]).output();
205
205
 
206
- if let Ok(out) = output {
207
- if out.status.success() && !out.stdout.is_empty() {
208
- return Ok(format!(
209
- "data:image/png;base64,{}",
210
- STANDARD.encode(&out.stdout)
211
- ));
212
- }
206
+ if let Ok(out) = output
207
+ && out.status.success()
208
+ && !out.stdout.is_empty()
209
+ {
210
+ return Ok(format!(
211
+ "data:image/png;base64,{}",
212
+ STANDARD.encode(&out.stdout)
213
+ ));
213
214
  }
214
215
 
215
216
  let output = Command::new("xclip")
216
- .args(&["-selection", "clipboard", "-t", "image/png", "-o"])
217
+ .args(["-selection", "clipboard", "-t", "image/png", "-o"])
217
218
  .output();
218
219
 
219
- if let Ok(out) = output {
220
- if out.status.success() && !out.stdout.is_empty() {
221
- return Ok(format!(
222
- "data:image/png;base64,{}",
223
- STANDARD.encode(&out.stdout)
224
- ));
225
- }
220
+ if let Ok(out) = output
221
+ && out.status.success()
222
+ && !out.stdout.is_empty()
223
+ {
224
+ return Ok(format!(
225
+ "data:image/png;base64,{}",
226
+ STANDARD.encode(&out.stdout)
227
+ ));
226
228
  }
227
229
 
228
230
  Err("No image found in clipboard".into())
@@ -14,7 +14,7 @@ pub fn set_activity(config: &MintConfig, instruction: &str) -> Result<String, St
14
14
  .filter(|value| !value.trim().is_empty())
15
15
  .ok_or("missing config value 'discordApplicationId'")?;
16
16
  let input: Value = serde_json::from_str(instruction).unwrap_or_else(|_| {
17
- json!({ "details": instruction.trim().is_empty().then_some("Using Mint Assistant").unwrap_or(instruction) })
17
+ json!({ "details": if instruction.trim().is_empty() { "Using Mint Assistant" } else { instruction } })
18
18
  });
19
19
  let activity = json!({
20
20
  "details": input["details"].as_str().unwrap_or("Using Mint Assistant"),
@@ -119,11 +119,7 @@ fn get_runtime_status() -> Result<RuntimeStatus, String> {
119
119
  .display()
120
120
  .to_string(),
121
121
  active_provider: config.ai_provider.clone(),
122
- available_providers: config
123
- .available_providers()
124
- .into_iter()
125
- .map(str::to_owned)
126
- .collect(),
122
+ available_providers: config.available_providers(),
127
123
  integrations: integration_status(&config),
128
124
  })
129
125
  }
@@ -322,10 +318,38 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
322
318
  if request.message.starts_with("/chat ") {
323
319
  let mut clean_request = request.clone();
324
320
  clean_request.message = request.message.strip_prefix("/chat ").unwrap().to_owned();
321
+ let config_clone = config.clone();
322
+ let chat_id_str = request.chat_id.clone().unwrap_or_default();
325
323
 
326
- let (response, _) = orchestrate_chat_with_fallback(&config, &clean_request)
327
- .await
328
- .map_err(|error| error.to_string())?;
324
+ let join_handle = tokio::spawn(async move {
325
+ orchestrate_chat_with_fallback(&config_clone, &clean_request).await
326
+ });
327
+
328
+ let abort_handle = join_handle.abort_handle();
329
+ if !chat_id_str.is_empty() {
330
+ mint_core::ACTIVE_AGENTS
331
+ .lock()
332
+ .unwrap()
333
+ .insert(chat_id_str.clone(), abort_handle);
334
+ }
335
+
336
+ let res = join_handle.await;
337
+
338
+ if !chat_id_str.is_empty() {
339
+ mint_core::ACTIVE_AGENTS
340
+ .lock()
341
+ .unwrap()
342
+ .remove(&chat_id_str);
343
+ }
344
+
345
+ let (response, _) = match res {
346
+ Ok(Ok(val)) => val,
347
+ Ok(Err(e)) => return Err(e.to_string()),
348
+ Err(e) if e.is_cancelled() => {
349
+ return Err("Chat execution cancelled by user".to_string());
350
+ }
351
+ Err(e) => return Err(format!("Task panicked: {}", e)),
352
+ };
329
353
  return Ok(response);
330
354
  }
331
355
 
@@ -363,19 +387,53 @@ async fn send_chat_message(app: AppHandle, request: ChatRequest) -> Result<ChatR
363
387
  let progress_cb = |_| {};
364
388
  let on_chunk = |_| {};
365
389
 
366
- let res = orchestrate_agent_loop(
367
- &config,
368
- &request.message,
369
- &root,
370
- request.image_data_uri.clone(),
371
- request.chat_id.as_deref(),
372
- fast_mode,
373
- approve_cb,
374
- progress_cb,
375
- on_chunk,
376
- )
377
- .await
378
- .map_err(|e| e.to_string())?;
390
+ let chat_id_str = request.chat_id.clone().unwrap_or_default();
391
+ let config_clone = config.clone();
392
+ let message_clone = request.message.clone();
393
+ let root_clone = root.clone();
394
+ let image_data_uri_clone = request.image_data_uri.clone();
395
+ let chat_id_clone = request.chat_id.clone();
396
+ let agent_id_clone = request.agent_id.clone();
397
+
398
+ let join_handle = tokio::spawn(async move {
399
+ orchestrate_agent_loop(
400
+ &config_clone,
401
+ &message_clone,
402
+ &root_clone,
403
+ image_data_uri_clone,
404
+ chat_id_clone.as_deref(),
405
+ agent_id_clone.as_deref(),
406
+ fast_mode,
407
+ approve_cb,
408
+ progress_cb,
409
+ on_chunk,
410
+ )
411
+ .await
412
+ });
413
+
414
+ let abort_handle = join_handle.abort_handle();
415
+ if !chat_id_str.is_empty() {
416
+ mint_core::ACTIVE_AGENTS
417
+ .lock()
418
+ .unwrap()
419
+ .insert(chat_id_str.clone(), abort_handle);
420
+ }
421
+
422
+ let res = join_handle.await;
423
+
424
+ if !chat_id_str.is_empty() {
425
+ mint_core::ACTIVE_AGENTS
426
+ .lock()
427
+ .unwrap()
428
+ .remove(&chat_id_str);
429
+ }
430
+
431
+ let res = match res {
432
+ Ok(Ok(val)) => val,
433
+ Ok(Err(e)) => return Err(e.to_string()),
434
+ Err(e) if e.is_cancelled() => return Err("Agent execution cancelled by user".to_string()),
435
+ Err(e) => return Err(format!("Task panicked: {}", e)),
436
+ };
379
437
 
380
438
  Ok(ChatResponse {
381
439
  provider: res.provider,
@@ -397,13 +455,42 @@ async fn stream_chat_message(
397
455
  if request.message.starts_with("/chat ") {
398
456
  let mut clean_request = request.clone();
399
457
  clean_request.message = request.message.strip_prefix("/chat ").unwrap().to_owned();
458
+ let config_clone = config.clone();
459
+ let on_event_clone = on_event.clone();
460
+ let chat_id_str = request.chat_id.clone().unwrap_or_default();
400
461
 
401
- let (response, _) =
402
- orchestrate_chat_stream_with_fallback(&config, &clean_request, |chunk| {
403
- let _ = on_event.send(DesktopStreamEvent::Chunk { chunk });
462
+ let join_handle = tokio::spawn(async move {
463
+ orchestrate_chat_stream_with_fallback(&config_clone, &clean_request, move |chunk| {
464
+ let _ = on_event_clone.send(DesktopStreamEvent::Chunk { chunk });
404
465
  })
405
466
  .await
406
- .map_err(|error| error.to_string())?;
467
+ });
468
+
469
+ let abort_handle = join_handle.abort_handle();
470
+ if !chat_id_str.is_empty() {
471
+ mint_core::ACTIVE_AGENTS
472
+ .lock()
473
+ .unwrap()
474
+ .insert(chat_id_str.clone(), abort_handle);
475
+ }
476
+
477
+ let res = join_handle.await;
478
+
479
+ if !chat_id_str.is_empty() {
480
+ mint_core::ACTIVE_AGENTS
481
+ .lock()
482
+ .unwrap()
483
+ .remove(&chat_id_str);
484
+ }
485
+
486
+ let (response, _) = match res {
487
+ Ok(Ok(val)) => val,
488
+ Ok(Err(e)) => return Err(e.to_string()),
489
+ Err(e) if e.is_cancelled() => {
490
+ return Err("Chat execution cancelled by user".to_string());
491
+ }
492
+ Err(e) => return Err(format!("Task panicked: {}", e)),
493
+ };
407
494
  return Ok(response);
408
495
  }
409
496
 
@@ -456,19 +543,53 @@ async fn stream_chat_message(
456
543
  }
457
544
  };
458
545
 
459
- let res = orchestrate_agent_loop(
460
- &config,
461
- &request.message,
462
- &root,
463
- request.image_data_uri.clone(),
464
- request.chat_id.as_deref(),
465
- fast_mode,
466
- approve_cb,
467
- progress_cb,
468
- on_chunk,
469
- )
470
- .await
471
- .map_err(|e| e.to_string())?;
546
+ let chat_id_str = request.chat_id.clone().unwrap_or_default();
547
+ let config_clone = config.clone();
548
+ let message_clone = request.message.clone();
549
+ let root_clone = root.clone();
550
+ let image_data_uri_clone = request.image_data_uri.clone();
551
+ let chat_id_clone = request.chat_id.clone();
552
+ let agent_id_clone = request.agent_id.clone();
553
+
554
+ let join_handle = tokio::spawn(async move {
555
+ orchestrate_agent_loop(
556
+ &config_clone,
557
+ &message_clone,
558
+ &root_clone,
559
+ image_data_uri_clone,
560
+ chat_id_clone.as_deref(),
561
+ agent_id_clone.as_deref(),
562
+ fast_mode,
563
+ approve_cb,
564
+ progress_cb,
565
+ on_chunk,
566
+ )
567
+ .await
568
+ });
569
+
570
+ let abort_handle = join_handle.abort_handle();
571
+ if !chat_id_str.is_empty() {
572
+ mint_core::ACTIVE_AGENTS
573
+ .lock()
574
+ .unwrap()
575
+ .insert(chat_id_str.clone(), abort_handle);
576
+ }
577
+
578
+ let res = join_handle.await;
579
+
580
+ if !chat_id_str.is_empty() {
581
+ mint_core::ACTIVE_AGENTS
582
+ .lock()
583
+ .unwrap()
584
+ .remove(&chat_id_str);
585
+ }
586
+
587
+ let res = match res {
588
+ Ok(Ok(val)) => val,
589
+ Ok(Err(e)) => return Err(e.to_string()),
590
+ Err(e) if e.is_cancelled() => return Err("Agent execution cancelled by user".to_string()),
591
+ Err(e) => return Err(format!("Task panicked: {}", e)),
592
+ };
472
593
 
473
594
  Ok(ChatResponse {
474
595
  provider: res.provider,
@@ -478,6 +599,12 @@ async fn stream_chat_message(
478
599
  })
479
600
  }
480
601
 
602
+ #[tauri::command]
603
+ async fn cancel_chat_message(chat_id: String) -> Result<(), String> {
604
+ mint_core::cancel_agent(&chat_id);
605
+ Ok(())
606
+ }
607
+
481
608
  #[tauri::command]
482
609
  fn save_interaction_agent_activity(
483
610
  interaction_id: i64,
@@ -1054,8 +1181,8 @@ fn install_tray(app: &AppHandle) -> tauri::Result<()> {
1054
1181
  fn install_shortcuts(app: &AppHandle) -> tauri::Result<()> {
1055
1182
  let main_shortcut = Shortcut::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::Space);
1056
1183
  let spotlight_shortcut = Shortcut::new(Some(Modifiers::ALT), Code::Space);
1057
- let main_handler = main_shortcut.clone();
1058
- let spotlight_handler = spotlight_shortcut.clone();
1184
+ let main_handler = main_shortcut;
1185
+ let spotlight_handler = spotlight_shortcut;
1059
1186
  app.plugin(
1060
1187
  tauri_plugin_global_shortcut::Builder::new()
1061
1188
  .with_handler(move |app, shortcut, event| {
@@ -1123,6 +1250,7 @@ pub fn run() {
1123
1250
  inspect_shell_command,
1124
1251
  send_chat_message,
1125
1252
  stream_chat_message,
1253
+ cancel_chat_message,
1126
1254
  submit_tool_approval,
1127
1255
  get_recent_interactions,
1128
1256
  save_interaction_agent_activity,
@@ -154,6 +154,7 @@ async fn answer(text: &str, system: &str) -> String {
154
154
  audio_data_uri: None,
155
155
  document_attachment: None,
156
156
  workspace_path: None,
157
+ agent_id: None,
157
158
  },
158
159
  )
159
160
  .await