@geminilight/mindos 0.6.7 → 0.6.12

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 (85) hide show
  1. package/README.md +2 -0
  2. package/README_zh.md +2 -0
  3. package/app/app/api/ask/route.ts +35 -2
  4. package/app/app/api/file/route.ts +27 -0
  5. package/app/app/api/mcp/install/route.ts +4 -1
  6. package/app/app/api/setup/check-path/route.ts +2 -7
  7. package/app/app/api/setup/check-port/route.ts +18 -13
  8. package/app/app/api/setup/ls/route.ts +3 -9
  9. package/app/app/api/setup/path-utils.ts +8 -0
  10. package/app/app/api/setup/route.ts +2 -7
  11. package/app/app/api/uninstall/route.ts +47 -0
  12. package/app/app/globals.css +11 -0
  13. package/app/components/ActivityBar.tsx +10 -3
  14. package/app/components/AskFab.tsx +7 -3
  15. package/app/components/CreateSpaceModal.tsx +1 -1
  16. package/app/components/DirView.tsx +1 -1
  17. package/app/components/FileTree.tsx +30 -23
  18. package/app/components/GuideCard.tsx +1 -1
  19. package/app/components/HomeContent.tsx +137 -109
  20. package/app/components/ImportModal.tsx +104 -60
  21. package/app/components/MarkdownView.tsx +3 -0
  22. package/app/components/OnboardingView.tsx +1 -1
  23. package/app/components/OrganizeToast.tsx +386 -0
  24. package/app/components/Panel.tsx +23 -2
  25. package/app/components/Sidebar.tsx +1 -1
  26. package/app/components/SidebarLayout.tsx +44 -1
  27. package/app/components/agents/AgentDetailContent.tsx +33 -12
  28. package/app/components/agents/AgentsMcpSection.tsx +1 -1
  29. package/app/components/agents/AgentsOverviewSection.tsx +3 -4
  30. package/app/components/agents/AgentsPrimitives.tsx +2 -2
  31. package/app/components/agents/AgentsSkillsSection.tsx +2 -2
  32. package/app/components/agents/SkillDetailPopover.tsx +24 -8
  33. package/app/components/ask/AskContent.tsx +124 -70
  34. package/app/components/ask/HighlightMatch.tsx +14 -0
  35. package/app/components/ask/MentionPopover.tsx +5 -3
  36. package/app/components/ask/MessageList.tsx +39 -11
  37. package/app/components/ask/SlashCommandPopover.tsx +4 -2
  38. package/app/components/changes/ChangesBanner.tsx +20 -2
  39. package/app/components/changes/ChangesContentPage.tsx +10 -2
  40. package/app/components/echo/EchoHero.tsx +1 -1
  41. package/app/components/echo/EchoInsightCollapsible.tsx +1 -1
  42. package/app/components/echo/EchoPageSections.tsx +1 -1
  43. package/app/components/explore/UseCaseCard.tsx +1 -1
  44. package/app/components/panels/DiscoverPanel.tsx +29 -25
  45. package/app/components/panels/ImportHistoryPanel.tsx +195 -0
  46. package/app/components/panels/PluginsPanel.tsx +2 -2
  47. package/app/components/settings/AiTab.tsx +24 -0
  48. package/app/components/settings/KnowledgeTab.tsx +1 -1
  49. package/app/components/settings/McpSkillCreateForm.tsx +1 -1
  50. package/app/components/settings/McpSkillRow.tsx +1 -1
  51. package/app/components/settings/McpSkillsSection.tsx +2 -2
  52. package/app/components/settings/McpTab.tsx +2 -2
  53. package/app/components/settings/PluginsTab.tsx +1 -1
  54. package/app/components/settings/Primitives.tsx +118 -6
  55. package/app/components/settings/SettingsContent.tsx +5 -2
  56. package/app/components/settings/UninstallTab.tsx +179 -0
  57. package/app/components/settings/UpdateTab.tsx +17 -5
  58. package/app/components/settings/types.ts +2 -1
  59. package/app/components/ui/dialog.tsx +1 -1
  60. package/app/hooks/useAiOrganize.ts +450 -0
  61. package/app/hooks/useFileImport.ts +39 -2
  62. package/app/hooks/useMention.ts +21 -3
  63. package/app/hooks/useSlashCommand.ts +18 -4
  64. package/app/lib/agent/reconnect.ts +40 -0
  65. package/app/lib/core/backlinks.ts +2 -2
  66. package/app/lib/core/git.ts +14 -10
  67. package/app/lib/fs.ts +2 -1
  68. package/app/lib/i18n-en.ts +85 -4
  69. package/app/lib/i18n-zh.ts +85 -4
  70. package/app/lib/organize-history.ts +74 -0
  71. package/app/lib/settings.ts +2 -0
  72. package/app/lib/types.ts +2 -0
  73. package/app/next-env.d.ts +1 -1
  74. package/app/next.config.ts +23 -5
  75. package/app/package.json +1 -1
  76. package/bin/cli.js +21 -18
  77. package/bin/lib/mcp-build.js +74 -0
  78. package/bin/lib/mcp-spawn.js +8 -5
  79. package/bin/lib/port.js +17 -2
  80. package/bin/lib/stop.js +12 -2
  81. package/mcp/dist/index.cjs +43 -43
  82. package/mcp/src/index.ts +58 -12
  83. package/package.json +1 -1
  84. package/scripts/release.sh +1 -1
  85. package/scripts/setup.js +2 -2
package/app/lib/fs.ts CHANGED
@@ -585,6 +585,7 @@ export type { MindSpaceSummary } from './core';
585
585
  export type { ContentChangeEvent, ContentChangeInput, ContentChangeSummary, ContentChangeSource } from './core';
586
586
 
587
587
  export function findBacklinks(targetPath: string): BacklinkEntry[] {
588
- return coreFindBacklinks(getMindRoot(), targetPath);
588
+ const { allFiles } = ensureCache();
589
+ return coreFindBacklinks(getMindRoot(), targetPath, allFiles);
589
590
  }
590
591
 
@@ -72,12 +72,15 @@ export const en = {
72
72
  agents: 'Agents',
73
73
  echo: 'Echo',
74
74
  discover: 'Discover',
75
+ history: 'History',
75
76
  help: 'Help',
76
77
  syncLabel: 'Sync',
77
78
  collapseTitle: 'Collapse sidebar',
78
79
  expandTitle: 'Expand sidebar',
79
80
  collapseLevel: 'Collapse one level',
80
81
  expandLevel: 'Expand one level',
82
+ importFile: 'Import file',
83
+ newFile: 'New file',
81
84
  sync: {
82
85
  synced: 'Synced',
83
86
  unpushed: 'awaiting push',
@@ -105,6 +108,7 @@ export const en = {
105
108
  },
106
109
  ask: {
107
110
  title: 'MindOS Agent',
111
+ fabLabel: 'Ask AI',
108
112
  placeholder: 'Ask a question... @ files, / skills',
109
113
  emptyPrompt: 'Ask anything about your knowledge base',
110
114
  send: 'send',
@@ -118,13 +122,17 @@ export const en = {
118
122
  skillsHint: 'skills',
119
123
  attachCurrent: 'attach current file',
120
124
  stopTitle: 'Stop',
121
- connecting: 'Thinking with your mind...',
125
+ cancelReconnect: 'Cancel reconnect',
126
+ connecting: 'Thinking with you...',
122
127
  thinking: 'Thinking...',
123
128
  thinkingLabel: 'Thinking',
124
129
  searching: 'Searching knowledge base...',
125
130
  generating: 'Generating response...',
126
131
  stopped: 'Generation stopped.',
127
132
  errorNoResponse: 'No response from AI. Please check your API key and provider settings.',
133
+ reconnecting: (attempt: number, max: number) => `Connection lost. Reconnecting (${attempt}/${max})...`,
134
+ reconnectFailed: 'Connection failed after multiple attempts.',
135
+ retry: 'Retry',
128
136
  suggestions: [
129
137
  'Summarize this document',
130
138
  'List all action items and TODOs',
@@ -637,6 +645,8 @@ export const en = {
637
645
  },
638
646
  detailSubtitle: '',
639
647
  detailNotFound: 'Agent not found — it may have been removed or renamed.',
648
+ detailNotFoundHint: 'The agent may have disconnected or its configuration file was moved. Try restarting the agent or check the MCP configuration.',
649
+ detailNotFoundSuggestion: 'Connected agents you can explore:',
640
650
  },
641
651
  shortcutPanel: {
642
652
  title: 'Keyboard Shortcuts',
@@ -718,10 +728,56 @@ export const en = {
718
728
  dropOverlay: 'Drop files to import into knowledge base',
719
729
  dropOverlayFormats: 'Supports .md .txt .pdf .csv .json .yaml .html',
720
730
  onboardingHint: 'Already have notes? Import files →',
721
- digestPromptSingle: (name: string) => `Please read ${name}, extract key information and organize it into the appropriate place in my knowledge base.`,
722
- digestPromptMulti: (n: number) => `Please read these ${n} files, extract key information and organize each into the appropriate place in my knowledge base.`,
731
+ digestPromptSingle: (name: string, targetSpace?: string) => {
732
+ const loc = targetSpace ? ` under the "${targetSpace}" space` : '';
733
+ return `The user uploaded "${name}". You MUST:\n1. Read the content from the "USER-UPLOADED FILES" section above\n2. Extract and reorganize the key information into well-structured Markdown notes\n3. Save the result${loc} in the knowledge base — create new files or update existing ones as appropriate\n\nDo NOT just reply with a text summary. You must actually write to the knowledge base.`;
734
+ },
735
+ digestPromptMulti: (n: number, targetSpace?: string) => {
736
+ const loc = targetSpace ? ` under the "${targetSpace}" space` : '';
737
+ return `The user uploaded ${n} files. You MUST:\n1. Read their content from the "USER-UPLOADED FILES" section above\n2. Extract and reorganize the key information into well-structured Markdown notes\n3. Save the results${loc} in the knowledge base — create new files or update existing ones as appropriate\n\nDo NOT just reply with a text summary. You must actually write to the knowledge base.`;
738
+ },
723
739
  arrowTo: '→',
724
740
  remove: 'Remove',
741
+ conflictsFound: (n: number) => `${n} file${n !== 1 ? 's' : ''} already exist${n === 1 ? 's' : ''}`,
742
+ organizeTitle: 'AI Organizing',
743
+ organizeProcessing: 'AI is analyzing and organizing your files...',
744
+ organizeConnecting: 'Connecting to AI...',
745
+ organizeAnalyzing: 'AI is analyzing your files...',
746
+ organizeReading: (detail?: string) => detail ? `Reading ${detail}...` : 'Reading files...',
747
+ organizeThinking: 'AI is thinking deeply...',
748
+ organizeWriting: (detail?: string) => detail ? `Writing ${detail}...` : 'Writing files...',
749
+ organizeElapsed: (seconds: number) => {
750
+ const m = Math.floor(seconds / 60);
751
+ const s = seconds % 60;
752
+ return `${m}:${s.toString().padStart(2, '0')}`;
753
+ },
754
+ organizeCancel: 'Cancel',
755
+ organizeMinimize: 'Continue browsing',
756
+ organizeExpand: 'View',
757
+ organizeCreating: (path: string) => `Creating ${path}`,
758
+ organizeUpdating: (path: string) => `Updating ${path}`,
759
+ organizeReviewTitle: 'Organization Complete',
760
+ organizeErrorTitle: 'Organization Failed',
761
+ organizeReviewDesc: (n: number) => `AI organized your files into ${n} change${n !== 1 ? 's' : ''}`,
762
+ organizeCreated: 'Created',
763
+ organizeUpdated: 'Updated',
764
+ organizeFailed: 'Failed',
765
+ organizeNoChanges: 'AI analyzed your files but made no changes.',
766
+ organizeToolCallsInfo: (n: number) => `AI executed ${n} operation${n > 1 ? 's' : ''} — check knowledge base for updates`,
767
+ organizeError: 'Organization failed',
768
+ organizeRetry: 'Retry',
769
+ organizeDone: 'Done',
770
+ organizeUndoAll: 'Undo All',
771
+ organizeUndoOne: 'Undo',
772
+ organizeUndone: 'Undone',
773
+ organizeViewFile: 'View file',
774
+ organizeUndoSuccess: (n: number) => `Reverted ${n} file${n !== 1 ? 's' : ''}`,
775
+ },
776
+ importHistory: {
777
+ title: 'Import History',
778
+ clearAll: 'Clear history',
779
+ emptyTitle: 'No import history yet',
780
+ emptyDesc: 'AI organize results will appear here',
725
781
  },
726
782
  dirView: {
727
783
  gridView: 'Grid view',
@@ -741,7 +797,7 @@ export const en = {
741
797
  },
742
798
  settings: {
743
799
  title: 'Settings',
744
- tabs: { ai: 'AI', appearance: 'Appearance', knowledge: 'General', sync: 'Sync', mcp: 'MCP & Skills', plugins: 'Plugins', shortcuts: 'Shortcuts', monitoring: 'Monitoring', agents: 'Agents', update: 'Update' },
800
+ tabs: { ai: 'AI', appearance: 'Appearance', knowledge: 'General', sync: 'Sync', mcp: 'MCP & Skills', plugins: 'Plugins', shortcuts: 'Shortcuts', monitoring: 'Monitoring', agents: 'Agents', update: 'Update', uninstall: 'Uninstall' },
745
801
  ai: {
746
802
  provider: 'Provider',
747
803
  model: 'Model',
@@ -776,6 +832,8 @@ export const en = {
776
832
  thinkingHint: "Show Claude's reasoning process (uses more tokens)",
777
833
  thinkingBudget: 'Thinking Budget',
778
834
  thinkingBudgetHint: 'Max tokens for reasoning (1000-50000)',
835
+ reconnectRetries: 'Auto Reconnect',
836
+ reconnectRetriesHint: 'When connection drops, automatically retry this many times before giving up (0 = disabled)',
779
837
  },
780
838
  appearance: {
781
839
  readingFont: 'Reading font',
@@ -1000,6 +1058,29 @@ export const en = {
1000
1058
  desktopRestart: 'Restart Now',
1001
1059
  desktopHint: 'Updates are delivered through the Desktop app auto-updater.',
1002
1060
  },
1061
+ uninstall: {
1062
+ title: 'Uninstall MindOS',
1063
+ descCli: 'Remove MindOS CLI, background services, and configuration files from this machine.',
1064
+ descDesktop: 'Remove MindOS Desktop, background services, and configuration files from this machine.',
1065
+ warning: 'Select what to clean up. Your knowledge base files are always kept safe.',
1066
+ stopServices: 'Stop services & remove daemon',
1067
+ stopServicesDesc: 'Stop all running MindOS processes and remove the background daemon.',
1068
+ removeConfig: 'Remove configuration',
1069
+ removeConfigDesc: 'Delete ~/.mindos/ directory (config, logs, PID files).',
1070
+ removeNpm: 'Uninstall CLI package',
1071
+ removeNpmDesc: 'Run npm uninstall -g @geminilight/mindos.',
1072
+ removeApp: 'Move Desktop app to Trash',
1073
+ removeAppDesc: 'Move MindOS.app to Trash. You can restore it later if needed.',
1074
+ confirmTitle: 'Confirm Uninstall',
1075
+ confirmButton: 'Uninstall',
1076
+ cancelButton: 'Cancel',
1077
+ running: 'Uninstalling...',
1078
+ success: 'MindOS has been uninstalled.',
1079
+ successDesktop: 'MindOS has been uninstalled. The app will quit now.',
1080
+ error: 'Uninstall failed. You can run `mindos uninstall` in terminal manually.',
1081
+ nothingSelected: 'Select at least one item to uninstall.',
1082
+ kbSafe: 'Your knowledge base files are always safe — they are never deleted by this action.',
1083
+ },
1003
1084
  },
1004
1085
  onboarding: {
1005
1086
  subtitle: 'Your knowledge base is empty. Pick a starter template to get going.',
@@ -97,12 +97,15 @@ export const zh = {
97
97
  agents: '智能体',
98
98
  echo: '回响',
99
99
  discover: '探索',
100
+ history: '历史',
100
101
  help: '帮助',
101
102
  syncLabel: '同步',
102
103
  collapseTitle: '收起侧栏',
103
104
  expandTitle: '展开侧栏',
104
105
  collapseLevel: '折叠一级',
105
106
  expandLevel: '展开一级',
107
+ importFile: '导入文件',
108
+ newFile: '新建文件',
106
109
  sync: {
107
110
  synced: '已同步',
108
111
  unpushed: '待推送',
@@ -130,6 +133,7 @@ export const zh = {
130
133
  },
131
134
  ask: {
132
135
  title: 'MindOS Agent',
136
+ fabLabel: 'AI 助手',
133
137
  placeholder: '输入问题… @ 附加文件,/ 技能',
134
138
  emptyPrompt: '可以问任何关于知识库的问题',
135
139
  send: '发送',
@@ -143,13 +147,17 @@ export const zh = {
143
147
  skillsHint: '技能',
144
148
  attachCurrent: '附加当前文件',
145
149
  stopTitle: '停止',
146
- connecting: '正在与你的心智一起思考...',
150
+ cancelReconnect: '取消重连',
151
+ connecting: '正在和你一起思考...',
147
152
  thinking: '思考中...',
148
153
  thinkingLabel: '思考中',
149
154
  searching: '正在搜索知识库...',
150
155
  generating: '正在生成回复...',
151
156
  stopped: '已停止生成。',
152
157
  errorNoResponse: 'AI 未返回响应,请检查 API Key 和服务商设置。',
158
+ reconnecting: (attempt: number, max: number) => `连接中断,正在重连 (${attempt}/${max})...`,
159
+ reconnectFailed: '多次重连失败,请检查网络后重试。',
160
+ retry: '重试',
153
161
  suggestions: [
154
162
  '总结这篇文档',
155
163
  '列出所有待办事项',
@@ -661,6 +669,8 @@ export const zh = {
661
669
  },
662
670
  detailSubtitle: '',
663
671
  detailNotFound: '未找到该 Agent,可能已移除或重命名。',
672
+ detailNotFoundHint: '该 Agent 可能已断开连接或配置文件已移动。请尝试重启 Agent 或检查 MCP 配置。',
673
+ detailNotFoundSuggestion: '已连接的 Agent:',
664
674
  },
665
675
  shortcutPanel: {
666
676
  title: '快捷键',
@@ -742,10 +752,56 @@ export const zh = {
742
752
  dropOverlay: '松开鼠标,导入文件到知识库',
743
753
  dropOverlayFormats: '支持 .md .txt .pdf .csv .json .yaml .html',
744
754
  onboardingHint: '已有笔记?导入文件到知识库 →',
745
- digestPromptSingle: (name: string) => `请阅读 ${name},提取关键信息整理到知识库中合适的位置。`,
746
- digestPromptMulti: (n: number) => `请阅读这 ${n} 个文件,提取关键信息分别整理到知识库中合适的位置。`,
755
+ digestPromptSingle: (name: string, targetSpace?: string) => {
756
+ const loc = targetSpace ? `"${targetSpace}" 空间下` : '知识库中合适的位置';
757
+ return `用户上传了「${name}」。你必须:\n1. 从上方「USER-UPLOADED FILES」区域读取文件内容\n2. 提取和重新整理关键信息为结构清晰的 Markdown 笔记\n3. 将整理后的内容保存到${loc}——可以创建新文件,也可以更新已有文件\n\n不要只做文字回复。你必须实际写入知识库。`;
758
+ },
759
+ digestPromptMulti: (n: number, targetSpace?: string) => {
760
+ const loc = targetSpace ? `"${targetSpace}" 空间下` : '知识库中合适的位置';
761
+ return `用户上传了 ${n} 个文件。你必须:\n1. 从上方「USER-UPLOADED FILES」区域读取它们的内容\n2. 提取和重新整理关键信息为结构清晰的 Markdown 笔记\n3. 将整理后的内容保存到${loc}——可以创建新文件,也可以更新已有文件\n\n不要只做文字回复。你必须实际写入知识库。`;
762
+ },
747
763
  arrowTo: '→',
748
764
  remove: '移除',
765
+ conflictsFound: (n: number) => `${n} 个文件已存在`,
766
+ organizeTitle: 'AI 整理中',
767
+ organizeProcessing: 'AI 正在分析和整理你的文件...',
768
+ organizeConnecting: '正在连接 AI...',
769
+ organizeAnalyzing: 'AI 正在分析你的文件...',
770
+ organizeReading: (detail?: string) => detail ? `正在阅读 ${detail}...` : '正在阅读文件...',
771
+ organizeThinking: 'AI 正在深度思考...',
772
+ organizeWriting: (detail?: string) => detail ? `正在写入 ${detail}...` : '正在写入文件...',
773
+ organizeElapsed: (seconds: number) => {
774
+ const m = Math.floor(seconds / 60);
775
+ const s = seconds % 60;
776
+ return `${m}:${s.toString().padStart(2, '0')}`;
777
+ },
778
+ organizeCancel: '取消',
779
+ organizeMinimize: '继续浏览',
780
+ organizeExpand: '查看',
781
+ organizeCreating: (path: string) => `正在创建 ${path}`,
782
+ organizeUpdating: (path: string) => `正在更新 ${path}`,
783
+ organizeReviewTitle: '整理完成',
784
+ organizeErrorTitle: '整理失败',
785
+ organizeReviewDesc: (n: number) => `AI 整理了 ${n} 处变更`,
786
+ organizeCreated: '已创建',
787
+ organizeUpdated: '已更新',
788
+ organizeFailed: '失败',
789
+ organizeNoChanges: 'AI 分析了你的文件,但没有做任何更改。',
790
+ organizeToolCallsInfo: (n: number) => `AI 执行了 ${n} 个操作 — 请检查知识库查看更新`,
791
+ organizeError: '整理失败',
792
+ organizeRetry: '重试',
793
+ organizeDone: '完成',
794
+ organizeUndoAll: '撤销全部',
795
+ organizeUndoOne: '撤销',
796
+ organizeUndone: '已撤销',
797
+ organizeViewFile: '查看文件',
798
+ organizeUndoSuccess: (n: number) => `已撤销 ${n} 个文件`,
799
+ },
800
+ importHistory: {
801
+ title: '导入历史',
802
+ clearAll: '清空历史',
803
+ emptyTitle: '暂无导入记录',
804
+ emptyDesc: 'AI 整理的结果会出现在这里',
749
805
  },
750
806
  dirView: {
751
807
  gridView: '网格视图',
@@ -765,7 +821,7 @@ export const zh = {
765
821
  },
766
822
  settings: {
767
823
  title: '设置',
768
- tabs: { ai: 'AI', appearance: '外观', knowledge: '通用', sync: '同步', mcp: 'MCP & Skills', plugins: '插件', shortcuts: '快捷键', monitoring: '监控', agents: 'Agents', update: '更新' },
824
+ tabs: { ai: 'AI', appearance: '外观', knowledge: '通用', sync: '同步', mcp: 'MCP & Skills', plugins: '插件', shortcuts: '快捷键', monitoring: '监控', agents: 'Agents', update: '更新', uninstall: '卸载' },
769
825
  ai: {
770
826
  provider: '服务商',
771
827
  model: '模型',
@@ -800,6 +856,8 @@ export const zh = {
800
856
  thinkingHint: '显示 Claude 的推理过程(消耗更多 token)',
801
857
  thinkingBudget: '思考预算',
802
858
  thinkingBudgetHint: '推理最大 token 数(1000-50000)',
859
+ reconnectRetries: '自动重连',
860
+ reconnectRetriesHint: '连接断开时自动重试次数,重试耗尽后停止(0 = 关闭)',
803
861
  },
804
862
  appearance: {
805
863
  readingFont: '正文字体',
@@ -1024,6 +1082,29 @@ export const zh = {
1024
1082
  desktopRestart: '立即重启',
1025
1083
  desktopHint: '更新通过桌面端自动更新推送。',
1026
1084
  },
1085
+ uninstall: {
1086
+ title: '卸载 MindOS',
1087
+ descCli: '从本机移除 MindOS CLI、后台服务和配置文件。',
1088
+ descDesktop: '从本机移除 MindOS Desktop、后台服务和配置文件。',
1089
+ warning: '选择要清理的内容。你的知识库文件始终是安全的。',
1090
+ stopServices: '停止服务并移除守护进程',
1091
+ stopServicesDesc: '停止所有运行中的 MindOS 进程并移除后台守护进程。',
1092
+ removeConfig: '移除配置',
1093
+ removeConfigDesc: '删除 ~/.mindos/ 目录(配置、日志、PID 文件)。',
1094
+ removeNpm: '卸载 CLI 包',
1095
+ removeNpmDesc: '执行 npm uninstall -g @geminilight/mindos。',
1096
+ removeApp: '将 Desktop 移入废纸篓',
1097
+ removeAppDesc: '将 MindOS.app 移入废纸篓,之后可以恢复。',
1098
+ confirmTitle: '确认卸载',
1099
+ confirmButton: '卸载',
1100
+ cancelButton: '取消',
1101
+ running: '正在卸载...',
1102
+ success: 'MindOS 已卸载。',
1103
+ successDesktop: 'MindOS 已卸载,应用即将退出。',
1104
+ error: '卸载失败,可在终端手动运行 `mindos uninstall`。',
1105
+ nothingSelected: '请至少选择一项要卸载的内容。',
1106
+ kbSafe: '你的知识库文件始终是安全的——此操作绝不会删除它们。',
1107
+ },
1027
1108
  },
1028
1109
  onboarding: {
1029
1110
  subtitle: '知识库为空,选择一个模板快速开始。',
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Organize History — localStorage persistence for past AI organize operations
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export interface OrganizeHistoryFile {
8
+ action: 'create' | 'update' | 'unknown';
9
+ path: string;
10
+ ok: boolean;
11
+ /** Undone by user (deleted for create / restored for update) */
12
+ undone?: boolean;
13
+ }
14
+
15
+ export interface OrganizeHistoryEntry {
16
+ id: string;
17
+ /** Unix ms */
18
+ timestamp: number;
19
+ /** Original uploaded file names */
20
+ sourceFiles: string[];
21
+ files: OrganizeHistoryFile[];
22
+ status: 'completed' | 'partial' | 'undone';
23
+ }
24
+
25
+ const STORAGE_KEY = 'mindos:organize-history';
26
+ const MAX_ENTRIES = 50;
27
+
28
+ export function loadHistory(): OrganizeHistoryEntry[] {
29
+ if (typeof window === 'undefined') return [];
30
+ try {
31
+ const raw = localStorage.getItem(STORAGE_KEY);
32
+ if (!raw) return [];
33
+ return JSON.parse(raw) as OrganizeHistoryEntry[];
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+
39
+ export function saveHistory(entries: OrganizeHistoryEntry[]): void {
40
+ if (typeof window === 'undefined') return;
41
+ try {
42
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(entries.slice(0, MAX_ENTRIES)));
43
+ } catch { /* quota exceeded — silently drop oldest */ }
44
+ }
45
+
46
+ export function appendEntry(entry: OrganizeHistoryEntry): OrganizeHistoryEntry[] {
47
+ const all = loadHistory();
48
+ all.unshift(entry);
49
+ const trimmed = all.slice(0, MAX_ENTRIES);
50
+ saveHistory(trimmed);
51
+ return trimmed;
52
+ }
53
+
54
+ export function updateEntry(id: string, patch: Partial<OrganizeHistoryEntry>): OrganizeHistoryEntry[] {
55
+ const all = loadHistory();
56
+ const idx = all.findIndex(e => e.id === id);
57
+ if (idx >= 0) {
58
+ all[idx] = { ...all[idx], ...patch };
59
+ saveHistory(all);
60
+ }
61
+ return all;
62
+ }
63
+
64
+ export function clearHistory(): void {
65
+ if (typeof window === 'undefined') return;
66
+ try {
67
+ localStorage.removeItem(STORAGE_KEY);
68
+ } catch { /* ignore */ }
69
+ }
70
+
71
+ let _idCounter = 0;
72
+ export function generateEntryId(): string {
73
+ return `org-${Date.now()}-${++_idCounter}`;
74
+ }
@@ -23,6 +23,7 @@ export interface AgentConfig {
23
23
  enableThinking?: boolean; // default false, Anthropic only
24
24
  thinkingBudget?: number; // default 5000
25
25
  contextStrategy?: 'auto' | 'off'; // default 'auto'
26
+ reconnectRetries?: number; // default 3, range 0-10 (0 = disabled)
26
27
  }
27
28
 
28
29
  export interface GuideState {
@@ -128,6 +129,7 @@ function parseAgent(raw: unknown): AgentConfig | undefined {
128
129
  if (typeof obj.enableThinking === 'boolean') result.enableThinking = obj.enableThinking;
129
130
  if (typeof obj.thinkingBudget === 'number') result.thinkingBudget = Math.min(50000, Math.max(1000, obj.thinkingBudget));
130
131
  if (obj.contextStrategy === 'auto' || obj.contextStrategy === 'off') result.contextStrategy = obj.contextStrategy;
132
+ if (typeof obj.reconnectRetries === 'number') result.reconnectRetries = Math.min(10, Math.max(0, obj.reconnectRetries));
131
133
  return Object.keys(result).length > 0 ? result : undefined;
132
134
  }
133
135
 
package/app/lib/types.ts CHANGED
@@ -41,6 +41,8 @@ export interface Message {
41
41
  timestamp?: number;
42
42
  /** Structured parts for assistant messages (tool calls + text segments) */
43
43
  parts?: MessagePart[];
44
+ /** Skill name used for this user message (rendered as a capsule in the UI) */
45
+ skillName?: string;
44
46
  }
45
47
 
46
48
  export interface LocalAttachment {
package/app/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,21 +1,39 @@
1
1
  import type { NextConfig } from "next";
2
2
  import path from "path";
3
3
 
4
+ // When MindOS is installed globally via npm, the entire project lives
5
+ // under node_modules/@geminilight/mindos/. Next.js skips tsconfig path
6
+ // resolution and SWC TypeScript compilation for files inside node_modules.
7
+ // We detect this at config time and apply the necessary overrides.
8
+ const projectDir = path.resolve(__dirname);
9
+ const inNodeModules = projectDir.includes('node_modules');
10
+
4
11
  const nextConfig: NextConfig = {
5
- transpilePackages: ['github-slugger'],
12
+ transpilePackages: [
13
+ 'github-slugger',
14
+ // Self-reference: ensures the SWC loader compiles our own TypeScript
15
+ // when the project is inside node_modules (global npm install).
16
+ ...(inNodeModules ? ['@geminilight/mindos'] : []),
17
+ ],
6
18
  serverExternalPackages: ['chokidar', 'openai', '@mariozechner/pi-ai', '@mariozechner/pi-agent-core', '@mariozechner/pi-coding-agent', 'mcporter'],
7
19
  output: 'standalone',
8
- outputFileTracingRoot: path.join(__dirname),
20
+ outputFileTracingRoot: projectDir,
9
21
  turbopack: {
10
- root: path.join(__dirname),
22
+ root: projectDir,
11
23
  },
12
- // Disable client-side router cache for dynamic layouts so that
13
- // router.refresh() always fetches a fresh file tree from the server.
14
24
  experimental: {
15
25
  staleTimes: {
16
26
  dynamic: 0,
17
27
  },
18
28
  },
29
+ webpack: (config) => {
30
+ if (inNodeModules) {
31
+ config.resolve = config.resolve ?? {};
32
+ config.resolve.alias = config.resolve.alias ?? {};
33
+ (config.resolve.alias as Record<string, string>)['@'] = projectDir;
34
+ }
35
+ return config;
36
+ },
19
37
  };
20
38
 
21
39
  export default nextConfig;
package/app/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "scripts": {
6
6
  "dev": "next dev -p ${MINDOS_WEB_PORT:-3456}",
7
7
  "prebuild": "node ../scripts/gen-renderer-index.js",
8
- "build": "next build",
8
+ "build": "next build --webpack",
9
9
  "start": "next start -p ${MINDOS_WEB_PORT:-3456}",
10
10
  "lint": "eslint",
11
11
  "test": "vitest run",
package/bin/cli.js CHANGED
@@ -40,7 +40,7 @@
40
40
 
41
41
  import { execSync, spawn as nodeSpawn } from 'node:child_process';
42
42
  import { existsSync, readFileSync, writeFileSync, rmSync, cpSync } from 'node:fs';
43
- import { resolve } from 'node:path';
43
+ import { dirname, resolve } from 'node:path';
44
44
  import { homedir } from 'node:os';
45
45
 
46
46
  import { ROOT, CONFIG_PATH, BUILD_STAMP, LOG_PATH, MINDOS_DIR } from './lib/constants.js';
@@ -57,6 +57,7 @@ import { stopMindos } from './lib/stop.js';
57
57
  import { getPlatform, ensureMindosDir, waitForHttp, waitForPortFree, runGatewayCommand } from './lib/gateway.js';
58
58
  import { printStartupInfo, getLocalIP } from './lib/startup.js';
59
59
  import { spawnMcp } from './lib/mcp-spawn.js';
60
+ import { ensureMcpBundle } from './lib/mcp-build.js';
60
61
  import { mcpInstall } from './lib/mcp-install.js';
61
62
  import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, listConflicts, setSyncEnabled } from './lib/sync.js';
62
63
 
@@ -140,7 +141,7 @@ if (cmd === '--version' || cmd === '-v') {
140
141
 
141
142
  const isDaemon = process.argv.includes('--daemon') || (!cmd && isDaemonMode());
142
143
  const isVerbose = process.argv.includes('--verbose');
143
- const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose').join(' ');
144
+ const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose' && a !== '--turbo').join(' ');
144
145
 
145
146
  const commands = {
146
147
  // ── onboard ────────────────────────────────────────────────────────────────
@@ -267,10 +268,12 @@ const commands = {
267
268
  // ── dev ────────────────────────────────────────────────────────────────────
268
269
  dev: async () => {
269
270
  loadConfig();
271
+ if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
272
+ if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
270
273
  process.env.MINDOS_CLI_PATH = resolve(ROOT, 'bin', 'cli.js');
271
274
  process.env.MINDOS_NODE_BIN = process.execPath;
272
- const webPort = process.env.MINDOS_WEB_PORT || '3456';
273
- const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
275
+ const webPort = process.env.MINDOS_WEB_PORT;
276
+ const mcpPort = process.env.MINDOS_MCP_PORT;
274
277
  await assertPortFree(Number(webPort), 'web');
275
278
  await assertPortFree(Number(mcpPort), 'mcp');
276
279
  ensureAppDeps();
@@ -303,8 +306,10 @@ const commands = {
303
306
  console.warn(yellow('Warning: daemon mode not supported on this platform. Falling back to foreground.'));
304
307
  } else {
305
308
  loadConfig();
306
- const webPort = process.env.MINDOS_WEB_PORT || '3456';
307
- const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
309
+ if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
310
+ if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
311
+ const webPort = process.env.MINDOS_WEB_PORT;
312
+ const mcpPort = process.env.MINDOS_MCP_PORT;
308
313
  console.log(cyan(`Installing MindOS as a background service (${platform})...`));
309
314
  await runGatewayCommand('install');
310
315
  // install() already starts the service via launchctl bootstrap + RunAtLoad=true.
@@ -334,8 +339,10 @@ const commands = {
334
339
  }
335
340
  }
336
341
  loadConfig();
337
- const webPort = process.env.MINDOS_WEB_PORT || '3456';
338
- const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
342
+ if (!process.env.MINDOS_WEB_PORT) process.env.MINDOS_WEB_PORT = '3456';
343
+ if (!process.env.MINDOS_MCP_PORT) process.env.MINDOS_MCP_PORT = '8781';
344
+ const webPort = process.env.MINDOS_WEB_PORT;
345
+ const mcpPort = process.env.MINDOS_MCP_PORT;
339
346
 
340
347
  // ── Auto-migrate user-rules.md to root user-skill-rules.md ─────────────
341
348
  try {
@@ -382,7 +389,7 @@ const commands = {
382
389
  console.log(yellow('Building MindOS (first run or new version detected)...\n'));
383
390
  cleanNextDir();
384
391
  run('node scripts/gen-renderer-index.js', ROOT);
385
- run(`${NEXT_BIN} build`, resolve(ROOT, 'app'));
392
+ run(`${NEXT_BIN} build --webpack`, resolve(ROOT, 'app'));
386
393
  writeBuildStamp();
387
394
  }
388
395
  const mcp = spawnMcp(isVerbose);
@@ -397,7 +404,7 @@ const commands = {
397
404
  run(
398
405
  `${NEXT_BIN} start -p ${webPort} ${extra}`,
399
406
  resolve(ROOT, 'app'),
400
- process.env.HOSTNAME ? undefined : { HOSTNAME: '127.0.0.1' }
407
+ { HOSTNAME: '127.0.0.1' }
401
408
  );
402
409
  },
403
410
 
@@ -406,7 +413,7 @@ const commands = {
406
413
  ensureAppDeps();
407
414
  cleanNextDir();
408
415
  run('node scripts/gen-renderer-index.js', ROOT);
409
- run(`${NEXT_BIN} build ${extra}`, resolve(ROOT, 'app'));
416
+ run(`${NEXT_BIN} build --webpack ${extra}`, resolve(ROOT, 'app'));
410
417
  writeBuildStamp();
411
418
  },
412
419
 
@@ -416,11 +423,7 @@ const commands = {
416
423
  const hasInstallFlags = restArgs.some(a => ['-g', '--global', '-y', '--yes'].includes(a));
417
424
  if (sub === 'install' || hasInstallFlags) { await mcpInstall(); return; }
418
425
  loadConfig();
419
- const mcpSdk = resolve(ROOT, 'mcp', 'node_modules', '@modelcontextprotocol', 'sdk', 'package.json');
420
- if (!existsSync(mcpSdk)) {
421
- console.log(yellow('Installing MCP dependencies (first run)...\n'));
422
- npmInstall(resolve(ROOT, 'mcp'), '--no-workspaces');
423
- }
426
+ ensureMcpBundle();
424
427
  // `mindos mcp` is the entry point for MCP clients (Claude Code, Cursor, etc.)
425
428
  // which communicate over stdin/stdout. Default to stdio; HTTP is handled by
426
429
  // `mindos start` via spawnMcp(). Callers can still override via env.
@@ -784,7 +787,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
784
787
  const webPort = updateConfig.port ?? 3456;
785
788
  const mcpPort = updateConfig.mcpPort ?? 8781;
786
789
  console.log(dim(' (Waiting for Web UI to come back up — first run after update includes a rebuild...)'));
787
- const ready = await waitForHttp(Number(webPort), { retries: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
790
+ const ready = await waitForHttp(Number(webPort), { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
788
791
  if (ready) {
789
792
  const localIP = getLocalIP();
790
793
  console.log(`\n${'─'.repeat(53)}`);
@@ -843,7 +846,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
843
846
  child.unref();
844
847
 
845
848
  console.log(dim(' (Waiting for Web UI to come back up...)'));
846
- const ready = await waitForHttp(webPort, { retries: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
849
+ const ready = await waitForHttp(webPort, { retries: 180, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
847
850
  if (ready) {
848
851
  const localIP = getLocalIP();
849
852
  console.log(`\n${'─'.repeat(53)}`);