@geminilight/mindos 0.5.64 → 0.5.65

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 +4 -0
  2. package/README_zh.md +4 -0
  3. package/app/app/api/ask/route.ts +12 -0
  4. package/app/app/api/file/route.ts +9 -0
  5. package/app/app/api/mcp/agents/route.ts +27 -1
  6. package/app/app/api/skills/route.ts +18 -2
  7. package/app/app/api/tree-version/route.ts +8 -0
  8. package/app/components/ActivityBar.tsx +2 -2
  9. package/app/components/Backlinks.tsx +5 -5
  10. package/app/components/CreateSpaceModal.tsx +3 -2
  11. package/app/components/DirPicker.tsx +1 -1
  12. package/app/components/DirView.tsx +2 -3
  13. package/app/components/EditorWrapper.tsx +3 -3
  14. package/app/components/FileTree.tsx +25 -10
  15. package/app/components/GuideCard.tsx +4 -4
  16. package/app/components/HomeContent.tsx +6 -11
  17. package/app/components/MarkdownView.tsx +2 -2
  18. package/app/components/OnboardingView.tsx +1 -1
  19. package/app/components/Panel.tsx +1 -1
  20. package/app/components/RightAgentDetailPanel.tsx +1 -1
  21. package/app/components/RightAskPanel.tsx +1 -1
  22. package/app/components/SearchModal.tsx +10 -2
  23. package/app/components/SidebarLayout.tsx +35 -10
  24. package/app/components/ThemeToggle.tsx +1 -1
  25. package/app/components/agents/AgentDetailContent.tsx +454 -59
  26. package/app/components/agents/AgentsContentPage.tsx +70 -5
  27. package/app/components/agents/AgentsMcpSection.tsx +474 -159
  28. package/app/components/agents/AgentsOverviewSection.tsx +418 -59
  29. package/app/components/agents/AgentsPrimitives.tsx +335 -0
  30. package/app/components/agents/AgentsSkillsSection.tsx +739 -121
  31. package/app/components/agents/SkillDetailPopover.tsx +416 -0
  32. package/app/components/agents/agents-content-model.ts +292 -10
  33. package/app/components/ask/AskContent.tsx +34 -5
  34. package/app/components/ask/FileChip.tsx +1 -0
  35. package/app/components/ask/MentionPopover.tsx +13 -1
  36. package/app/components/ask/MessageList.tsx +5 -7
  37. package/app/components/ask/ToolCallBlock.tsx +4 -4
  38. package/app/components/changes/ChangesBanner.tsx +1 -2
  39. package/app/components/echo/EchoHero.tsx +10 -24
  40. package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
  41. package/app/components/echo/EchoPageSections.tsx +13 -9
  42. package/app/components/echo/EchoSegmentNav.tsx +14 -11
  43. package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
  44. package/app/components/explore/ExploreContent.tsx +3 -7
  45. package/app/components/explore/UseCaseCard.tsx +4 -15
  46. package/app/components/panels/AgentsPanel.tsx +12 -104
  47. package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
  48. package/app/components/panels/AgentsPanelAgentGroups.tsx +3 -7
  49. package/app/components/panels/AgentsPanelAgentListRow.tsx +9 -11
  50. package/app/components/panels/EchoPanel.tsx +8 -10
  51. package/app/components/panels/PanelNavRow.tsx +9 -2
  52. package/app/components/panels/PluginsPanel.tsx +2 -2
  53. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
  54. package/app/components/renderers/agent-inspector/manifest.ts +3 -3
  55. package/app/components/renderers/todo/manifest.ts +1 -0
  56. package/app/components/settings/AiTab.tsx +3 -3
  57. package/app/components/settings/AppearanceTab.tsx +2 -2
  58. package/app/components/settings/KnowledgeTab.tsx +3 -3
  59. package/app/components/settings/McpAgentInstall.tsx +3 -6
  60. package/app/components/settings/McpSkillCreateForm.tsx +2 -3
  61. package/app/components/settings/McpSkillRow.tsx +2 -3
  62. package/app/components/settings/McpSkillsSection.tsx +2 -2
  63. package/app/components/settings/McpTab.tsx +12 -13
  64. package/app/components/settings/MonitoringTab.tsx +13 -13
  65. package/app/components/settings/PluginsTab.tsx +2 -2
  66. package/app/components/settings/Primitives.tsx +3 -4
  67. package/app/components/settings/SettingsContent.tsx +3 -3
  68. package/app/components/settings/SyncTab.tsx +11 -17
  69. package/app/components/settings/UpdateTab.tsx +18 -21
  70. package/app/components/settings/types.ts +14 -0
  71. package/app/components/setup/StepKB.tsx +1 -1
  72. package/app/hooks/useMcpData.tsx +4 -2
  73. package/app/hooks/useMention.ts +25 -8
  74. package/app/lib/agent/log.ts +15 -18
  75. package/app/lib/agent/stream-consumer.ts +3 -0
  76. package/app/lib/agent/to-agent-messages.ts +6 -4
  77. package/app/lib/core/agent-audit-log.ts +280 -0
  78. package/app/lib/core/index.ts +11 -0
  79. package/app/lib/fs.ts +9 -0
  80. package/app/lib/i18n-en.ts +259 -33
  81. package/app/lib/i18n-zh.ts +258 -32
  82. package/app/lib/mcp-agents.ts +231 -2
  83. package/app/lib/types.ts +2 -0
  84. package/package.json +1 -1
  85. package/scripts/migrate-agent-audit-log.js +170 -0
@@ -51,7 +51,7 @@ export default function SkillRow({
51
51
  {expanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
52
52
  <span className="text-xs font-medium flex-1">{skill.name}</span>
53
53
  <span className={`text-2xs px-1.5 py-0.5 rounded ${
54
- skill.source === 'builtin' ? 'bg-blue-500/15 text-blue-500' : 'bg-purple-500/15 text-purple-500'
54
+ skill.source === 'builtin' ? 'bg-muted text-muted-foreground' : 'bg-[var(--amber-subtle)] text-[var(--amber)]'
55
55
  }`}>
56
56
  {skill.source === 'builtin' ? (m?.skillBuiltin ?? 'Built-in') : (m?.skillUser ?? 'Custom')}
57
57
  </span>
@@ -106,8 +106,7 @@ export default function SkillRow({
106
106
  <button
107
107
  onClick={() => onEditSave(skill.name)}
108
108
  disabled={saving}
109
- className="flex items-center gap-1 px-2.5 py-1 text-xs rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
110
- style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
109
+ className="flex items-center gap-1 px-2.5 py-1 text-xs rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-colors bg-[var(--amber)] text-[var(--amber-foreground)]"
111
110
  >
112
111
  {saving && <Loader2 size={10} className="animate-spin" />}
113
112
  {m?.saveSkill ?? 'Save'}
@@ -239,7 +239,7 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
239
239
  onClick={() => handleLangSwitch('en')}
240
240
  disabled={switchingLang}
241
241
  className={`px-2.5 py-1 text-xs transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
242
- currentLang === 'en' ? 'bg-amber-500/15 text-amber-600 font-medium' : 'text-muted-foreground hover:bg-muted'
242
+ currentLang === 'en' ? 'bg-[var(--amber-subtle)] text-[var(--amber)] font-medium' : 'text-muted-foreground hover:bg-muted'
243
243
  }`}
244
244
  >
245
245
  {m?.skillLangEn ?? 'English'}
@@ -248,7 +248,7 @@ export default function SkillsSection({ t }: McpSkillsSectionProps) {
248
248
  onClick={() => handleLangSwitch('zh')}
249
249
  disabled={switchingLang}
250
250
  className={`px-2.5 py-1 text-xs transition-colors disabled:opacity-50 disabled:cursor-not-allowed border-l border-border ${
251
- currentLang === 'zh' ? 'bg-amber-500/15 text-amber-600 font-medium' : 'text-muted-foreground hover:bg-muted'
251
+ currentLang === 'zh' ? 'bg-[var(--amber-subtle)] text-[var(--amber)] font-medium' : 'text-muted-foreground hover:bg-muted'
252
252
  }`}
253
253
  >
254
254
  {m?.skillLangZh ?? '中文'}
@@ -68,7 +68,7 @@ export function McpTab({ t }: McpTabProps) {
68
68
  {/* MCP Config Viewer */}
69
69
  {mcp.agents.length > 0 && (
70
70
  <div>
71
- <h3 className="text-sm font-medium text-foreground mb-3">MCP</h3>
71
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">MCP</p>
72
72
  <AgentConfigViewer
73
73
  connectedAgents={connectedAgents}
74
74
  detectedAgents={detectedAgents}
@@ -91,13 +91,13 @@ export function McpTab({ t }: McpTabProps) {
91
91
 
92
92
  {/* Skills */}
93
93
  <div>
94
- <h3 className="text-sm font-medium text-foreground mb-3">{m?.skillsTitle ?? 'Skills'}</h3>
94
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">{m?.skillsTitle ?? 'Skills'}</p>
95
95
  <SkillsSection t={t} />
96
96
  </div>
97
97
 
98
98
  {/* Batch Agent Install */}
99
99
  <div>
100
- <h3 className="text-sm font-medium text-foreground mb-3">{m?.agentsTitle ?? 'Agent Configuration'}</h3>
100
+ <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">{m?.agentsTitle ?? 'Agent Configuration'}</p>
101
101
  <AgentInstall agents={mcp.agents} t={t} onRefresh={mcp.refresh} />
102
102
  </div>
103
103
  </div>
@@ -119,8 +119,8 @@ function McpStatusCard({ status, restarting, onRestart, onRefresh, m }: {
119
119
  <div className="flex items-center gap-2.5 text-xs">
120
120
  {restarting ? (
121
121
  <>
122
- <Loader2 size={12} className="animate-spin" style={{ color: 'var(--amber)' }} />
123
- <span style={{ color: 'var(--amber)' }}>{m?.restarting ?? 'Restarting...'}</span>
122
+ <Loader2 size={12} className="animate-spin text-[var(--amber)]" />
123
+ <span className="text-[var(--amber)]">{m?.restarting ?? 'Restarting...'}</span>
124
124
  </>
125
125
  ) : (
126
126
  <>
@@ -142,8 +142,7 @@ function McpStatusCard({ status, restarting, onRestart, onRefresh, m }: {
142
142
  <div className="flex items-center gap-2">
143
143
  {!status.running && !restarting && (
144
144
  <button onClick={onRestart}
145
- className="flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg font-medium text-white transition-colors"
146
- style={{ background: 'var(--amber)' }}>
145
+ className="flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg font-medium text-[var(--amber-foreground)] bg-[var(--amber)] transition-colors">
147
146
  <RotateCcw size={12} /> {m?.restart ?? 'Restart'}
148
147
  </button>
149
148
  )}
@@ -222,18 +221,18 @@ function AgentConfigViewer({ connectedAgents, detectedAgents, notFoundAgents, cu
222
221
  {/* Agent status badge */}
223
222
  <div className="flex items-center gap-2">
224
223
  {currentAgent.present && currentAgent.installed ? (
225
- <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
226
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
224
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-success/10 text-success">
225
+ <span className="w-1.5 h-1.5 rounded-full bg-success inline-block" />
227
226
  {m?.tagConnected ?? 'Connected'}
228
227
  </span>
229
228
  ) : currentAgent.present && !currentAgent.installed ? (
230
- <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium" style={{ background: 'var(--amber-subtle, rgba(200,135,58,0.1))', color: 'var(--amber)' }}>
231
- <span className="w-1.5 h-1.5 rounded-full inline-block" style={{ background: 'var(--amber)' }} />
229
+ <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-[var(--amber-subtle)] text-[var(--amber)]">
230
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--amber)] inline-block" />
232
231
  {m?.tagDetected ?? 'Detected — not configured'}
233
232
  </span>
234
233
  ) : (
235
234
  <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-2xs font-medium bg-muted text-muted-foreground">
236
- <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
235
+ <span className="w-1.5 h-1.5 rounded-full bg-muted-foreground inline-block" />
237
236
  {m?.tagNotInstalled ?? 'Not installed'}
238
237
  </span>
239
238
  )}
@@ -267,7 +266,7 @@ function AgentConfigViewer({ connectedAgents, detectedAgents, notFoundAgents, cu
267
266
 
268
267
  {/* Auth warning */}
269
268
  {transport === 'http' && mcpStatus && !mcpStatus.authConfigured && (
270
- <p className="flex items-center gap-1.5 text-xs" style={{ color: 'var(--amber)' }}>
269
+ <p className="flex items-center gap-1.5 text-xs text-[var(--amber)]">
271
270
  <AlertCircle size={12} />
272
271
  {m?.noAuthWarning ?? 'Auth not configured. Run `mindos token` to set up.'}
273
272
  </p>
@@ -52,7 +52,7 @@ function ProgressBar({ value, max, className }: { value: number; max: number; cl
52
52
  return (
53
53
  <div className={`h-2 w-full rounded-full bg-muted ${className ?? ''}`}>
54
54
  <div
55
- className={`h-full rounded-full transition-all duration-300 ${pct > 85 ? 'bg-destructive' : 'bg-amber-500'}`}
55
+ className={`h-full rounded-full transition-all duration-300 ${pct > 85 ? 'bg-destructive' : 'bg-[var(--amber)]'}`}
56
56
  style={{ width: `${pct}%` }}
57
57
  />
58
58
  </div>
@@ -125,10 +125,10 @@ export function MonitoringTab({ t }: MonitoringTabProps) {
125
125
  <div className="space-y-6">
126
126
  {/* System */}
127
127
  <section>
128
- <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
129
- <Cpu size={13} className="text-muted-foreground" />
128
+ <p className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
129
+ <Cpu size={13} />
130
130
  {mon.system || 'System'}
131
- </h3>
131
+ </p>
132
132
  <div className="space-y-3">
133
133
  <div>
134
134
  <div className="flex justify-between text-xs mb-1">
@@ -147,10 +147,10 @@ export function MonitoringTab({ t }: MonitoringTabProps) {
147
147
 
148
148
  {/* Application */}
149
149
  <section>
150
- <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
151
- <Zap size={13} className="text-muted-foreground" />
150
+ <p className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
151
+ <Zap size={13} />
152
152
  {mon.application || 'Application'}
153
- </h3>
153
+ </p>
154
154
  <div className="grid grid-cols-3 gap-4">
155
155
  <StatCard label={mon.requests || 'Requests'} value={application.agentRequests} />
156
156
  <StatCard label={mon.toolCalls || 'Tool Calls'} value={application.toolExecutions} />
@@ -166,10 +166,10 @@ export function MonitoringTab({ t }: MonitoringTabProps) {
166
166
 
167
167
  {/* Knowledge Base */}
168
168
  <section>
169
- <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
170
- <Database size={13} className="text-muted-foreground" />
169
+ <p className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
170
+ <Database size={13} />
171
171
  {mon.knowledgeBase || 'Knowledge Base'}
172
- </h3>
172
+ </p>
173
173
  <div className="grid grid-cols-3 gap-4">
174
174
  <StatCard label={mon.files || 'Files'} value={knowledgeBase.fileCount} />
175
175
  <StatCard label={mon.totalSize || 'Total Size'} value={formatBytes(knowledgeBase.totalSizeBytes)} />
@@ -179,10 +179,10 @@ export function MonitoringTab({ t }: MonitoringTabProps) {
179
179
 
180
180
  {/* MCP */}
181
181
  <section>
182
- <h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
183
- <HardDrive size={13} className="text-muted-foreground" />
182
+ <p className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3">
183
+ <HardDrive size={13} />
184
184
  MCP
185
- </h3>
185
+ </p>
186
186
  <div className="grid grid-cols-3 gap-4">
187
187
  <StatCard
188
188
  label={mon.mcpStatus || 'Status'}
@@ -8,7 +8,7 @@ import type { PluginsTabProps } from './types';
8
8
  export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps) {
9
9
  const renderers = getPluginRenderers();
10
10
  return (
11
- <div className="space-y-5">
11
+ <div className="space-y-6">
12
12
  <p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">{t.settings.plugins.title}</p>
13
13
 
14
14
  {renderers.length === 0 ? (
@@ -30,7 +30,7 @@ export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps
30
30
  <div className="flex items-center gap-2 flex-wrap">
31
31
  <span className="text-sm font-medium text-foreground">{renderer.name}</span>
32
32
  {isCore && (
33
- <span className="text-2xs px-1.5 py-0.5 rounded bg-amber-600/15 text-amber-600 font-mono">
33
+ <span className="text-2xs px-1.5 py-0.5 rounded bg-[var(--amber-subtle)] text-[var(--amber)] font-mono">
34
34
  core
35
35
  </span>
36
36
  )}
@@ -35,7 +35,7 @@ export function Select({ className = '', ...props }: React.SelectHTMLAttributes<
35
35
  export function EnvBadge({ overridden }: { overridden: boolean }) {
36
36
  if (!overridden) return null;
37
37
  return (
38
- <span className="text-2xs px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-500 font-mono ml-1.5">env</span>
38
+ <span className="text-2xs px-1.5 py-0.5 rounded bg-[var(--amber-subtle)] text-[var(--amber)] font-mono ml-1.5">env</span>
39
39
  );
40
40
  }
41
41
 
@@ -72,7 +72,7 @@ export function Toggle({ checked, onChange, size = 'md', disabled, title, onClic
72
72
  onClick={onClick ?? (() => onChange?.(!checked))}
73
73
  className={`relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60 ${
74
74
  sm ? 'h-4 w-7' : 'h-5 w-9'
75
- } ${checked ? 'bg-amber-500' : 'bg-muted'}`}
75
+ } ${checked ? 'bg-[var(--amber)]' : 'bg-muted'}`}
76
76
  >
77
77
  <span
78
78
  className={`pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform ${
@@ -119,8 +119,7 @@ export function PrimaryButton({ children, disabled, onClick, type = 'button', cl
119
119
  type={type}
120
120
  onClick={onClick}
121
121
  disabled={disabled}
122
- className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${className}`}
123
- style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
122
+ className={`px-4 py-2 text-sm font-medium rounded-lg bg-[var(--amber)] text-[var(--amber-foreground)] transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${className}`}
124
123
  {...props}
125
124
  >
126
125
  {children}
@@ -245,7 +245,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
245
245
  onClick={() => setTab(tabItem.id)}
246
246
  className={`flex items-center gap-1 px-2 py-2 text-[11px] font-medium transition-colors border-b-2 -mb-px whitespace-nowrap ${
247
247
  tab === tabItem.id
248
- ? 'border-amber-500 text-foreground'
248
+ ? 'border-[var(--amber)] text-foreground'
249
249
  : 'border-transparent text-muted-foreground hover:text-foreground'
250
250
  }`}
251
251
  >
@@ -296,7 +296,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
296
296
  onClick={() => setTab(tabItem.id)}
297
297
  className={`flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px whitespace-nowrap ${
298
298
  tab === tabItem.id
299
- ? 'border-amber-500 text-foreground'
299
+ ? 'border-[var(--amber)] text-foreground'
300
300
  : 'border-transparent text-muted-foreground hover:text-foreground'
301
301
  }`}
302
302
  >
@@ -330,7 +330,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
330
330
  }`}
331
331
  >
332
332
  {tab === tabItem.id && (
333
- <div className="absolute left-0 top-1 bottom-1 w-[3px] rounded-r-full bg-amber-500" />
333
+ <div className="absolute left-0 top-1 bottom-1 w-[3px] rounded-r-full bg-[var(--amber)]" />
334
334
  )}
335
335
  {tabItem.icon}
336
336
  {tabItem.label}
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback } from 'react';
4
4
  import { RefreshCw, AlertCircle, CheckCircle2, Loader2, GitBranch, ExternalLink, Eye, EyeOff } from 'lucide-react';
5
- import { SectionLabel, PrimaryButton } from './Primitives';
5
+ import { SectionLabel, PrimaryButton, Input } from './Primitives';
6
6
  import { apiFetch } from '@/lib/api';
7
7
  import type { SyncStatus, SyncTabProps } from './types';
8
8
  import type { Messages } from '@/lib/i18n';
@@ -64,7 +64,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: ()
64
64
  };
65
65
 
66
66
  return (
67
- <div className="space-y-5">
67
+ <div className="space-y-6">
68
68
  {/* Header */}
69
69
  <div className="flex items-center gap-3">
70
70
  <div className="w-9 h-9 rounded-lg bg-muted flex items-center justify-center shrink-0">
@@ -85,19 +85,15 @@ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: ()
85
85
  <label className="text-xs font-medium text-foreground block">
86
86
  {syncT?.remoteUrl ?? 'Git Remote URL'}
87
87
  </label>
88
- <input
88
+ <Input
89
89
  type="text"
90
90
  value={remoteUrl}
91
91
  onChange={e => { setRemoteUrl(e.target.value); setError(''); }}
92
92
  placeholder="https://github.com/user/my-mind.git"
93
- className="w-full px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
94
- style={{
95
- borderColor: remoteUrl.trim() && !isValid ? 'var(--destructive, red)' : 'var(--border)',
96
- color: 'var(--foreground)',
97
- }}
93
+ className={`font-mono ${remoteUrl.trim() && !isValid ? 'border-destructive' : ''}`}
98
94
  />
99
95
  {remoteUrl.trim() && !isValid && (
100
- <p className="text-xs" style={{ color: 'var(--destructive, red)' }}>
96
+ <p className="text-xs text-destructive">
101
97
  {syncT?.invalidUrl ?? 'Invalid Git URL — use HTTPS (https://...) or SSH (git@...)'}
102
98
  </p>
103
99
  )}
@@ -117,13 +113,12 @@ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: ()
117
113
  <span className="text-muted-foreground font-normal">{syncT?.optional ?? '(optional, for private repos)'}</span>
118
114
  </label>
119
115
  <div className="relative">
120
- <input
116
+ <Input
121
117
  type={showToken ? 'text' : 'password'}
122
118
  value={token}
123
119
  onChange={e => setToken(e.target.value)}
124
120
  placeholder="ghp_xxxxxxxxxxxx"
125
- className="w-full px-3 py-2 pr-9 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
126
- style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
121
+ className="pr-9 font-mono"
127
122
  />
128
123
  <button
129
124
  type="button"
@@ -144,13 +139,12 @@ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: ()
144
139
  <label className="text-xs font-medium text-foreground block">
145
140
  {syncT?.branchLabel ?? 'Branch'}
146
141
  </label>
147
- <input
142
+ <Input
148
143
  type="text"
149
144
  value={branch}
150
145
  onChange={e => setBranch(e.target.value)}
151
146
  placeholder="main"
152
- className="w-full max-w-[200px] px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
153
- style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
147
+ className="max-w-[200px] font-mono"
154
148
  />
155
149
  </div>
156
150
 
@@ -168,7 +162,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: ()
168
162
 
169
163
  {/* Error */}
170
164
  {error && (
171
- <div className="flex items-start gap-2 text-xs p-3 rounded-lg" role="alert" aria-live="polite" style={{ background: 'rgba(200,80,80,0.1)', color: 'var(--error)' }}>
165
+ <div className="flex items-start gap-2 text-xs p-3 rounded-lg bg-destructive/10 text-destructive" role="alert" aria-live="polite">
172
166
  <AlertCircle size={13} className="shrink-0 mt-0.5" />
173
167
  <span>{error}</span>
174
168
  </div>
@@ -269,7 +263,7 @@ export function SyncTab({ t }: SyncTabProps) {
269
263
  const conflicts = status.conflicts || [];
270
264
 
271
265
  return (
272
- <div className="space-y-5">
266
+ <div className="space-y-6">
273
267
  <SectionLabel>Sync</SectionLabel>
274
268
 
275
269
  {/* Status overview */}
@@ -57,7 +57,7 @@ function StageIcon({ status }: { status: string }) {
57
57
  case 'done':
58
58
  return <CheckCircle2 size={14} className="text-success shrink-0" />;
59
59
  case 'running':
60
- return <Loader2 size={14} className="animate-spin shrink-0" style={{ color: 'var(--amber)' }} />;
60
+ return <Loader2 size={14} className="animate-spin shrink-0 text-[var(--amber)]" />;
61
61
  case 'failed':
62
62
  return <AlertCircle size={14} className="text-destructive shrink-0" />;
63
63
  default:
@@ -119,7 +119,7 @@ function DesktopUpdateTab() {
119
119
  };
120
120
 
121
121
  return (
122
- <div className="space-y-5">
122
+ <div className="space-y-6">
123
123
  <div className="rounded-xl border border-border bg-card p-4 space-y-3">
124
124
  <div className="flex items-center justify-between">
125
125
  <div className="flex items-center gap-2">
@@ -137,14 +137,14 @@ function DesktopUpdateTab() {
137
137
  )}
138
138
 
139
139
  {state === 'idle' && !available && (
140
- <div className="flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400">
140
+ <div className="flex items-center gap-2 text-xs text-success">
141
141
  <CheckCircle2 size={13} />
142
142
  {u?.upToDate ?? "You're up to date"}
143
143
  </div>
144
144
  )}
145
145
 
146
146
  {state === 'idle' && available && (
147
- <div className="flex items-center gap-2 text-xs" style={{ color: 'var(--amber)' }}>
147
+ <div className="flex items-center gap-2 text-xs text-[var(--amber)]">
148
148
  <Download size={13} />
149
149
  {version ? `Update available: v${version}` : 'Update available'}
150
150
  </div>
@@ -153,20 +153,20 @@ function DesktopUpdateTab() {
153
153
  {state === 'downloading' && (
154
154
  <div className="space-y-2">
155
155
  <div className="flex items-center gap-2 text-xs text-foreground">
156
- <Loader2 size={13} className="animate-spin" style={{ color: 'var(--amber)' }} />
156
+ <Loader2 size={13} className="animate-spin text-[var(--amber)]" />
157
157
  {u?.desktopDownloading ?? 'Downloading update...'}
158
158
  </div>
159
159
  <div className="h-1 rounded-full bg-muted overflow-hidden">
160
160
  <div
161
- className="h-full rounded-full transition-all duration-300"
162
- style={{ width: `${Math.max(progress, 3)}%`, background: 'var(--amber)' }}
161
+ className="h-full rounded-full bg-[var(--amber)] transition-all duration-300"
162
+ style={{ width: `${Math.max(progress, 3)}%` }}
163
163
  />
164
164
  </div>
165
165
  </div>
166
166
  )}
167
167
 
168
168
  {state === 'ready' && (
169
- <div className="flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400">
169
+ <div className="flex items-center gap-2 text-xs text-success">
170
170
  <CheckCircle2 size={13} />
171
171
  {u?.desktopReady ?? 'Update downloaded. Restart to apply.'}
172
172
  </div>
@@ -193,8 +193,7 @@ function DesktopUpdateTab() {
193
193
  {state === 'idle' && available && (
194
194
  <button
195
195
  onClick={handleInstall}
196
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-white transition-colors"
197
- style={{ background: 'var(--amber)' }}
196
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-[var(--amber-foreground)] bg-[var(--amber)] transition-colors"
198
197
  >
199
198
  <Download size={12} />
200
199
  {version ? `Update to v${version}` : 'Update'}
@@ -204,8 +203,7 @@ function DesktopUpdateTab() {
204
203
  {state === 'ready' && (
205
204
  <button
206
205
  onClick={() => bridge.installUpdate()}
207
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-white transition-colors"
208
- style={{ background: 'var(--amber)' }}
206
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-[var(--amber-foreground)] bg-[var(--amber)] transition-colors"
209
207
  >
210
208
  <RefreshCw size={12} />
211
209
  {u?.desktopRestart ?? 'Restart Now'}
@@ -401,7 +399,7 @@ function BrowserUpdateTab() {
401
399
  const progress = stages.length > 0 ? Math.round((doneCount / stages.length) * 100) : 0;
402
400
 
403
401
  return (
404
- <div className="space-y-5">
402
+ <div className="space-y-6">
405
403
  {/* Version Card */}
406
404
  <div className="rounded-xl border border-border bg-card p-4 space-y-3">
407
405
  <div className="flex items-center justify-between">
@@ -419,14 +417,14 @@ function BrowserUpdateTab() {
419
417
  )}
420
418
 
421
419
  {state === 'idle' && info && !info.hasUpdate && (
422
- <div className="flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400">
420
+ <div className="flex items-center gap-2 text-xs text-success">
423
421
  <CheckCircle2 size={13} />
424
422
  {u?.upToDate ?? "You're up to date"}
425
423
  </div>
426
424
  )}
427
425
 
428
426
  {state === 'idle' && info?.hasUpdate && (
429
- <div className="flex items-center gap-2 text-xs" style={{ color: 'var(--amber)' }}>
427
+ <div className="flex items-center gap-2 text-xs text-[var(--amber)]">
430
428
  <Download size={13} />
431
429
  {u?.available ? u.available(info.current, info.latest) : `Update available: v${info.current} → v${info.latest}`}
432
430
  </div>
@@ -449,8 +447,8 @@ function BrowserUpdateTab() {
449
447
  {/* Progress bar */}
450
448
  <div className="h-1 rounded-full bg-muted overflow-hidden">
451
449
  <div
452
- className="h-full rounded-full transition-all duration-500 ease-out"
453
- style={{ width: `${Math.max(progress, 5)}%`, background: 'var(--amber)' }}
450
+ className="h-full rounded-full bg-[var(--amber)] transition-all duration-500 ease-out"
451
+ style={{ width: `${Math.max(progress, 5)}%` }}
454
452
  />
455
453
  </div>
456
454
 
@@ -463,7 +461,7 @@ function BrowserUpdateTab() {
463
461
  )}
464
462
 
465
463
  {state === 'updated' && (
466
- <div className="flex items-center gap-2 text-xs text-emerald-600 dark:text-emerald-400">
464
+ <div className="flex items-center gap-2 text-xs text-success">
467
465
  <CheckCircle2 size={13} />
468
466
  {u?.updated ?? 'Updated successfully! Reloading...'}
469
467
  </div>
@@ -471,7 +469,7 @@ function BrowserUpdateTab() {
471
469
 
472
470
  {state === 'timeout' && (
473
471
  <div className="space-y-2">
474
- <div className="flex items-center gap-2 text-xs text-amber-600 dark:text-amber-400">
472
+ <div className="flex items-center gap-2 text-xs text-[var(--amber)]">
475
473
  <AlertCircle size={13} />
476
474
  {u?.timeout ?? 'Update may still be in progress.'}
477
475
  </div>
@@ -521,8 +519,7 @@ function BrowserUpdateTab() {
521
519
  {info?.hasUpdate && state !== 'updating' && state !== 'updated' && (
522
520
  <button
523
521
  onClick={handleUpdate}
524
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-white transition-colors"
525
- style={{ background: 'var(--amber)' }}
522
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-medium text-[var(--amber-foreground)] bg-[var(--amber)] transition-colors"
526
523
  >
527
524
  <Download size={12} />
528
525
  {u?.updateButton ? u.updateButton(info.latest) : `Update to v${info.latest}`}
@@ -79,6 +79,20 @@ export interface AgentInfo {
79
79
  globalNestedKey?: string;
80
80
  globalPath: string;
81
81
  projectPath?: string | null;
82
+ skillMode?: 'universal' | 'additional' | 'unsupported';
83
+ skillAgentName?: string;
84
+ skillWorkspacePath?: string;
85
+ hiddenRootPath?: string;
86
+ hiddenRootPresent?: boolean;
87
+ runtimeConversationSignal?: boolean;
88
+ runtimeUsageSignal?: boolean;
89
+ runtimeLastActivityAt?: string;
90
+ configuredMcpServers?: string[];
91
+ configuredMcpServerCount?: number;
92
+ configuredMcpSources?: string[];
93
+ installedSkillNames?: string[];
94
+ installedSkillCount?: number;
95
+ installedSkillSourcePath?: string;
82
96
  }
83
97
 
84
98
  export interface SkillInfo {
@@ -61,7 +61,7 @@ export default function StepKB({ state, update, t, homeDir }: StepKBProps) {
61
61
  const full: string[] = (d.dirs as string[]).map((dir: string) => parentNorm + dir);
62
62
  const endsWithAnySep = typed.endsWith('/') || typed.endsWith('\\');
63
63
  const filtered = endsWithAnySep ? full : full.filter(f => f.startsWith(typed));
64
- setSuggestions(filtered.slice(0, 8));
64
+ setSuggestions(filtered.slice(0, 20));
65
65
  setShowSuggestions(filtered.length > 0);
66
66
  setActiveSuggestion(-1);
67
67
  })
@@ -12,7 +12,7 @@ export interface McpContextValue {
12
12
  skills: SkillInfo[];
13
13
  loading: boolean;
14
14
  refresh: () => Promise<void>;
15
- toggleSkill: (name: string, enabled: boolean) => Promise<void>;
15
+ toggleSkill: (name: string, enabled: boolean) => Promise<boolean>;
16
16
  installAgent: (key: string, opts?: { scope?: string; transport?: string }) => Promise<boolean>;
17
17
  }
18
18
 
@@ -108,7 +108,7 @@ export default function McpProvider({ children }: { children: ReactNode }) {
108
108
  await fetchAll();
109
109
  }, [fetchAll]);
110
110
 
111
- const toggleSkill = useCallback(async (name: string, enabled: boolean) => {
111
+ const toggleSkill = useCallback(async (name: string, enabled: boolean): Promise<boolean> => {
112
112
  // Optimistic update
113
113
  setSkills(prev => prev.map(s => s.name === name ? { ...s, enabled } : s));
114
114
  try {
@@ -117,9 +117,11 @@ export default function McpProvider({ children }: { children: ReactNode }) {
117
117
  headers: { 'Content-Type': 'application/json' },
118
118
  body: JSON.stringify({ action: 'toggle', name, enabled }),
119
119
  });
120
+ return true;
120
121
  } catch {
121
122
  // Revert on failure
122
123
  setSkills(prev => prev.map(s => s.name === name ? { ...s, enabled: !enabled } : s));
124
+ return false;
123
125
  }
124
126
  }, []);
125
127
 
@@ -2,21 +2,30 @@
2
2
 
3
3
  import { useState, useCallback, useEffect } from 'react';
4
4
 
5
+ function safeFetchFiles(): Promise<string[]> {
6
+ return fetch('/api/files')
7
+ .then((r) => (r.ok ? r.json() : []))
8
+ .then((data) => (Array.isArray(data) ? data : []))
9
+ .catch(() => [] as string[]);
10
+ }
11
+
5
12
  export function useMention() {
6
13
  const [allFiles, setAllFiles] = useState<string[]>([]);
7
14
  const [mentionQuery, setMentionQuery] = useState<string | null>(null);
8
15
  const [mentionResults, setMentionResults] = useState<string[]>([]);
9
16
  const [mentionIndex, setMentionIndex] = useState(0);
10
17
 
11
- // Load file list once
12
- useEffect(() => {
13
- fetch('/api/files')
14
- .then((r) => r.json())
15
- .then(setAllFiles)
16
- .catch(() => {});
18
+ const loadFiles = useCallback(() => {
19
+ safeFetchFiles().then(setAllFiles);
17
20
  }, []);
18
21
 
19
- /** Parse @-mention from input text; returns updated input if a mention was selected. */
22
+ useEffect(() => {
23
+ loadFiles();
24
+ const handler = () => loadFiles();
25
+ window.addEventListener('mindos:files-changed', handler);
26
+ return () => window.removeEventListener('mindos:files-changed', handler);
27
+ }, [loadFiles]);
28
+
20
29
  const updateMentionFromInput = useCallback(
21
30
  (val: string) => {
22
31
  const atIdx = val.lastIndexOf('@');
@@ -30,8 +39,15 @@ export function useMention() {
30
39
  return;
31
40
  }
32
41
  const query = val.slice(atIdx + 1).toLowerCase();
42
+ const filtered = allFiles.filter((f) => f.toLowerCase().includes(query)).slice(0, 30);
43
+ if (filtered.length === 0) {
44
+ setMentionQuery(null);
45
+ setMentionResults([]);
46
+ setMentionIndex(0);
47
+ return;
48
+ }
33
49
  setMentionQuery(query);
34
- setMentionResults(allFiles.filter((f) => f.toLowerCase().includes(query)).slice(0, 8));
50
+ setMentionResults(filtered);
35
51
  setMentionIndex(0);
36
52
  },
37
53
  [allFiles],
@@ -39,6 +55,7 @@ export function useMention() {
39
55
 
40
56
  const navigateMention = useCallback(
41
57
  (direction: 'up' | 'down') => {
58
+ if (mentionResults.length === 0) return;
42
59
  if (direction === 'down') {
43
60
  setMentionIndex((i) => Math.min(i + 1, mentionResults.length - 1));
44
61
  } else {