@geminilight/mindos 0.5.69 → 0.6.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 (58) hide show
  1. package/app/app/api/ask/route.ts +122 -92
  2. package/app/app/api/file/import/route.ts +197 -0
  3. package/app/app/api/mcp/agents/route.ts +53 -2
  4. package/app/app/api/mcp/status/route.ts +1 -1
  5. package/app/app/api/skills/route.ts +10 -114
  6. package/app/components/ActivityBar.tsx +5 -7
  7. package/app/components/CreateSpaceModal.tsx +31 -6
  8. package/app/components/FileTree.tsx +68 -11
  9. package/app/components/GuideCard.tsx +197 -131
  10. package/app/components/HomeContent.tsx +85 -18
  11. package/app/components/ImportModal.tsx +415 -0
  12. package/app/components/OnboardingView.tsx +9 -0
  13. package/app/components/Panel.tsx +4 -2
  14. package/app/components/SidebarLayout.tsx +96 -8
  15. package/app/components/SpaceInitToast.tsx +173 -0
  16. package/app/components/TableOfContents.tsx +1 -0
  17. package/app/components/agents/AgentDetailContent.tsx +69 -45
  18. package/app/components/agents/AgentsContentPage.tsx +2 -1
  19. package/app/components/agents/AgentsMcpSection.tsx +16 -12
  20. package/app/components/agents/AgentsOverviewSection.tsx +37 -36
  21. package/app/components/agents/AgentsPrimitives.tsx +41 -20
  22. package/app/components/agents/AgentsSkillsSection.tsx +16 -7
  23. package/app/components/agents/SkillDetailPopover.tsx +11 -11
  24. package/app/components/agents/agents-content-model.ts +16 -8
  25. package/app/components/ask/AskContent.tsx +148 -50
  26. package/app/components/ask/MentionPopover.tsx +16 -8
  27. package/app/components/ask/SlashCommandPopover.tsx +62 -0
  28. package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -6
  29. package/app/components/panels/AgentsPanelHubNav.tsx +3 -3
  30. package/app/components/panels/DiscoverPanel.tsx +88 -2
  31. package/app/components/settings/KnowledgeTab.tsx +61 -0
  32. package/app/components/walkthrough/steps.ts +11 -6
  33. package/app/hooks/useFileImport.ts +191 -0
  34. package/app/hooks/useFileUpload.ts +11 -0
  35. package/app/hooks/useMention.ts +14 -6
  36. package/app/hooks/useSlashCommand.ts +114 -0
  37. package/app/lib/actions.ts +79 -2
  38. package/app/lib/agent/index.ts +1 -1
  39. package/app/lib/agent/prompt.ts +2 -0
  40. package/app/lib/agent/tools.ts +252 -0
  41. package/app/lib/core/create-space.ts +11 -4
  42. package/app/lib/core/file-convert.ts +97 -0
  43. package/app/lib/core/index.ts +1 -1
  44. package/app/lib/core/organize.ts +105 -0
  45. package/app/lib/i18n-en.ts +102 -46
  46. package/app/lib/i18n-zh.ts +101 -45
  47. package/app/lib/mcp-agents.ts +8 -0
  48. package/app/lib/pdf-extract.ts +33 -0
  49. package/app/lib/pi-integration/extensions.ts +45 -0
  50. package/app/lib/pi-integration/mcporter.ts +219 -0
  51. package/app/lib/pi-integration/session-store.ts +62 -0
  52. package/app/lib/pi-integration/skills.ts +116 -0
  53. package/app/lib/settings.ts +1 -1
  54. package/app/next-env.d.ts +1 -1
  55. package/app/next.config.ts +1 -1
  56. package/app/package.json +2 -0
  57. package/mcp/src/index.ts +29 -0
  58. package/package.json +1 -1
@@ -0,0 +1,173 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useTransition } from 'react';
4
+ import { Sparkles, Check, Undo2, X } from 'lucide-react';
5
+ import { useRouter } from 'next/navigation';
6
+ import { useLocale } from '@/lib/LocaleContext';
7
+ import { encodePath } from '@/lib/utils';
8
+ import { revertSpaceInitAction } from '@/lib/actions';
9
+
10
+ type InitState = 'working' | 'done' | 'reverted' | 'error';
11
+
12
+ interface InitInfo {
13
+ spaceName: string;
14
+ spacePath: string;
15
+ description: string;
16
+ state: InitState;
17
+ }
18
+
19
+ /**
20
+ * Global toast that shows AI initialization progress when a new space is created.
21
+ * Mounted in SidebarLayout so it persists across route transitions.
22
+ *
23
+ * States:
24
+ * - working: ✨ progress animation
25
+ * - done: ✓ ready + [Review] [Discard] [×] — persists until user acts
26
+ * - reverted: ↩ "reverted to template" — auto-dismiss 2s
27
+ * - error: silent dismiss
28
+ */
29
+ export default function SpaceInitToast() {
30
+ const [info, setInfo] = useState<InitInfo | null>(null);
31
+ const [visible, setVisible] = useState(false);
32
+ const [reverting, startRevert] = useTransition();
33
+ const router = useRouter();
34
+ const { t } = useLocale();
35
+
36
+ const dismiss = useCallback(() => {
37
+ setVisible(false);
38
+ setTimeout(() => setInfo(null), 200);
39
+ }, []);
40
+
41
+ const handleEvent = useCallback((e: Event) => {
42
+ const detail = (e as CustomEvent).detail as Partial<InitInfo>;
43
+ if (detail.state === 'working') {
44
+ setInfo(detail as InitInfo);
45
+ requestAnimationFrame(() => setVisible(true));
46
+ } else if (detail.state === 'done') {
47
+ setInfo((prev) => prev ? { ...prev, state: 'done' } : null);
48
+ router.refresh();
49
+ } else {
50
+ dismiss();
51
+ }
52
+ }, [router, dismiss]);
53
+
54
+ useEffect(() => {
55
+ window.addEventListener('mindos:ai-init', handleEvent);
56
+ return () => window.removeEventListener('mindos:ai-init', handleEvent);
57
+ }, [handleEvent]);
58
+
59
+ const handleReview = useCallback(() => {
60
+ if (!info) return;
61
+ dismiss();
62
+ router.push(`/view/${encodePath(info.spacePath + '/')}`);
63
+ }, [info, dismiss, router]);
64
+
65
+ const handleDiscard = useCallback(() => {
66
+ if (!info) return;
67
+ startRevert(async () => {
68
+ await revertSpaceInitAction(info.spacePath, info.spaceName, info.description);
69
+ setInfo((prev) => prev ? { ...prev, state: 'reverted' } : null);
70
+ router.refresh();
71
+ window.dispatchEvent(new Event('mindos:files-changed'));
72
+ setTimeout(dismiss, 2000);
73
+ });
74
+ }, [info, router, dismiss]);
75
+
76
+ const handleAccept = useCallback(() => {
77
+ dismiss();
78
+ }, [dismiss]);
79
+
80
+ if (!info) return null;
81
+
82
+ const h = t.home;
83
+ const { state } = info;
84
+ const isDone = state === 'done';
85
+ const isReverted = state === 'reverted';
86
+ const isWorking = state === 'working';
87
+
88
+ return (
89
+ <div
90
+ className={`fixed bottom-6 left-1/2 z-50 -translate-x-1/2 transition-all duration-200 ease-out ${
91
+ visible
92
+ ? 'opacity-100 translate-y-0 scale-100'
93
+ : 'opacity-0 translate-y-3 scale-[0.96] pointer-events-none'
94
+ }`}
95
+ >
96
+ <div
97
+ className="flex items-center gap-2.5 rounded-full border bg-card/95 backdrop-blur py-2 shadow-lg"
98
+ style={{
99
+ borderColor: 'color-mix(in srgb, var(--amber) 40%, var(--border))',
100
+ boxShadow: '0 8px 24px color-mix(in srgb, var(--amber) 12%, rgba(0,0,0,.2))',
101
+ paddingLeft: '0.75rem',
102
+ paddingRight: isDone ? '0.5rem' : '1rem',
103
+ }}
104
+ >
105
+ {/* Icon */}
106
+ <span
107
+ className={`inline-flex h-6 w-6 items-center justify-center rounded-full shrink-0 ${
108
+ isReverted
109
+ ? 'text-muted-foreground bg-muted'
110
+ : 'text-[var(--amber)] bg-[var(--amber-subtle)]'
111
+ } ${isWorking ? 'animate-pulse' : ''}`}
112
+ >
113
+ {isReverted ? <Undo2 size={12} /> : isDone ? <Check size={13} strokeWidth={2.5} /> : <Sparkles size={13} />}
114
+ </span>
115
+
116
+ {/* Text */}
117
+ <span className="text-xs text-foreground whitespace-nowrap select-none font-medium">
118
+ {isReverted
119
+ ? h.aiInitReverted(info.spaceName)
120
+ : isDone
121
+ ? h.aiInitReady(info.spaceName)
122
+ : <>{h.aiInitGenerating(info.spaceName)}<DotPulse /></>
123
+ }
124
+ </span>
125
+
126
+ {/* Action buttons — only in done state */}
127
+ {isDone && (
128
+ <div className="flex items-center gap-1 ml-1">
129
+ <button
130
+ type="button"
131
+ onClick={handleReview}
132
+ className="px-2.5 py-1 rounded-full text-2xs font-medium bg-[var(--amber)] text-white hover:opacity-90 transition-opacity focus-visible:ring-2 focus-visible:ring-ring"
133
+ >
134
+ {h.aiInitReview}
135
+ </button>
136
+ <button
137
+ type="button"
138
+ onClick={handleDiscard}
139
+ disabled={reverting}
140
+ className="px-2.5 py-1 rounded-full text-2xs font-medium bg-muted text-muted-foreground hover:text-foreground transition-colors focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
141
+ >
142
+ {h.aiInitDiscard}
143
+ </button>
144
+ <button
145
+ type="button"
146
+ onClick={handleAccept}
147
+ aria-label="Accept"
148
+ className="ml-0.5 inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors focus-visible:ring-2 focus-visible:ring-ring"
149
+ >
150
+ <X size={12} />
151
+ </button>
152
+ </div>
153
+ )}
154
+ </div>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ function DotPulse() {
160
+ return (
161
+ <span className="inline-flex gap-[2px] items-end h-[1em] ml-px">
162
+ <span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_0ms_infinite]" />
163
+ <span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_200ms_infinite]" />
164
+ <span className="w-[3px] h-[3px] rounded-full bg-muted-foreground animate-[dotBounce_1.2s_400ms_infinite]" />
165
+ <style>{`
166
+ @keyframes dotBounce {
167
+ 0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
168
+ 30% { opacity: 1; transform: translateY(-2px); }
169
+ }
170
+ `}</style>
171
+ </span>
172
+ );
173
+ }
@@ -128,6 +128,7 @@ export default function TableOfContents({ content }: TableOfContentsProps) {
128
128
  href={`#${heading.id}`}
129
129
  onClick={(e) => handleClick(e, heading.id)}
130
130
  className="block text-xs py-1 rounded transition-colors duration-100 leading-snug"
131
+ suppressHydrationWarning
131
132
  style={{
132
133
  paddingLeft: `${8 + indent}px`,
133
134
  paddingRight: '8px',
@@ -73,6 +73,7 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
73
73
  return map;
74
74
  }, [mcp.agents]);
75
75
 
76
+ const isMindOS = agentKey === 'mindos';
76
77
  const status = agent ? resolveAgentStatus(agent) : 'notFound';
77
78
  const currentScope = agent?.scope === 'project' ? 'project' : 'global';
78
79
  const currentTransport: 'stdio' | 'http' = agent?.transport === 'http' ? 'http' : 'stdio';
@@ -214,49 +215,61 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
214
215
  </Link>
215
216
 
216
217
  {/* ═══════════ AGENT PROFILE (consolidated header) ═══════════ */}
217
- <section className="rounded-lg border border-border bg-card p-4 space-y-3">
218
- <div className="flex items-center gap-3">
218
+ <section className="rounded-xl border border-border bg-gradient-to-b from-card to-card/80 overflow-hidden">
219
+ <div className="flex items-center gap-4 p-5">
219
220
  <AgentAvatar name={agent.name} status={status} size="md" />
220
- <div className="min-w-0">
221
+ <div className="min-w-0 flex-1">
221
222
  <h1 className="text-xl font-semibold tracking-tight font-display text-foreground">{agent.name}</h1>
222
- <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-0.5">
223
- <span className={`text-2xs font-medium px-1.5 py-0.5 rounded ${
223
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-1">
224
+ <span className={`text-2xs font-medium px-2 py-0.5 rounded-full ${
224
225
  status === 'connected' ? 'bg-success/10 text-success'
225
226
  : status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber)]'
226
227
  : 'bg-muted text-muted-foreground'
227
228
  }`}>{status}</span>
228
- <span className="text-2xs text-muted-foreground font-mono">{agent.transport ?? agent.preferredTransport}</span>
229
- <span className="text-2xs text-muted-foreground">·</span>
230
- <span className="text-2xs text-muted-foreground">{agent.skillMode ?? a.na}</span>
229
+ <span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport ?? agent.preferredTransport}</span>
230
+ <span className="text-2xs text-muted-foreground/30" aria-hidden="true">·</span>
231
+ <span className="text-2xs text-muted-foreground/60">{agent.skillMode ?? a.na}</span>
231
232
  </div>
232
233
  </div>
233
234
  </div>
234
- <div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground pt-2 border-t border-border">
235
- <span>{a.detail.format}: <span className="text-foreground">{agent.format}</span></span>
236
- <span>{a.detail.lastActivityAt}: <span className="text-foreground tabular-nums">{agent.runtimeLastActivityAt ?? a.na}</span></span>
237
- <span>{configuredMcpServers.length} MCP · {nativeInstalledSkills.length} skills</span>
235
+ <div className="flex flex-wrap items-center gap-x-5 gap-y-1 text-xs text-muted-foreground/70 px-5 py-3 border-t border-border/50 bg-muted/[0.03]">
236
+ <span>{a.detail.format}: <span className="text-foreground/80 font-medium">{agent.format}</span></span>
237
+ <span>{a.detail.lastActivityAt}: <span className="text-foreground/80 tabular-nums font-medium">{agent.runtimeLastActivityAt ?? a.na}</span></span>
238
+ <span className="font-medium text-foreground/80 tabular-nums">{configuredMcpServers.length} MCP · {nativeInstalledSkills.length} skills</span>
238
239
  </div>
239
240
  </section>
240
241
 
241
242
  {/* ═══════════ MCP MANAGEMENT ═══════════ */}
242
- <section className="rounded-lg border border-border bg-card p-4 space-y-3">
243
- <h2 className="text-sm font-medium text-foreground flex items-center gap-1.5">
244
- <Server size={14} className="text-muted-foreground" />
243
+ <section className="rounded-xl border border-border bg-card p-5 space-y-4">
244
+ <h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
245
+ <div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
246
+ <Server size={13} className="text-muted-foreground/70" />
247
+ </div>
245
248
  {a.detail.mcpManagement}
246
249
  </h2>
247
250
 
248
251
  {/* MCP status row */}
249
252
  <div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-sm">
250
- <DetailLine label={a.detail.mcpInstalled} value={agent.installed ? a.detail.yes : a.detail.no} />
251
- <DetailLine label={a.detail.mcpScope} value={agent.scope ?? a.na} />
252
- <DetailLine label={a.detail.mcpConfigPath} value={agent.configPath ?? a.na} />
253
+ {isMindOS ? (
254
+ <>
255
+ <DetailLine label="Status" value={mcp.status?.running ? '● Running' : '○ Stopped'} />
256
+ <DetailLine label="Endpoint" value={mcp.status?.endpoint ?? a.na} />
257
+ <DetailLine label="Tools" value={mcp.status?.toolCount != null ? String(mcp.status.toolCount) : a.na} />
258
+ </>
259
+ ) : (
260
+ <>
261
+ <DetailLine label={a.detail.mcpInstalled} value={agent.installed ? a.detail.yes : a.detail.no} />
262
+ <DetailLine label={a.detail.mcpScope} value={agent.scope ?? a.na} />
263
+ <DetailLine label={a.detail.mcpConfigPath} value={agent.configPath ?? a.na} />
264
+ </>
265
+ )}
253
266
  </div>
254
267
 
255
268
  {/* Configured MCP servers with management */}
256
- <div className="rounded-lg border border-border bg-background p-4 space-y-2">
269
+ <div className="rounded-xl border border-border/60 bg-background/50 p-4 space-y-2.5">
257
270
  <div className="flex items-center justify-between">
258
271
  <p className="text-xs font-semibold text-foreground">{a.detail.configuredMcpServers}</p>
259
- <span className="text-2xs text-muted-foreground tabular-nums">{a.detail.configuredMcpServersCount(configuredMcpServers.length)}</span>
272
+ <span className="text-2xs text-muted-foreground/60 tabular-nums">{a.detail.configuredMcpServersCount(configuredMcpServers.length)}</span>
260
273
  </div>
261
274
 
262
275
  {mcpHint && (
@@ -272,8 +285,10 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
272
285
  {configuredMcpServers.map((name) => {
273
286
  const sharedWith = (crossAgentMcpMap.get(name) ?? []).filter((n) => n !== agent.name);
274
287
  return (
275
- <div key={name} className="flex items-center gap-2 rounded-md border border-border/60 px-2.5 py-2 group/mcp hover:border-border hover:bg-muted/20 transition-all duration-100">
276
- <Server size={11} className="text-[var(--amber)] shrink-0" />
288
+ <div key={name} className="flex items-center gap-2.5 rounded-lg border border-border/40 bg-muted/[0.02] px-3 py-2.5 group/mcp hover:border-border/60 hover:bg-muted/[0.06] hover:shadow-[0_1px_3px_rgba(0,0,0,0.02)] transition-all duration-150">
289
+ <div className="w-5 h-5 rounded-md bg-[var(--amber)]/[0.08] flex items-center justify-center shrink-0">
290
+ <Server size={10} className="text-[var(--amber)]" />
291
+ </div>
277
292
  <span className="text-xs font-medium text-foreground flex-1 min-w-0 truncate">{name}</span>
278
293
  {sharedWith.length > 0 && (
279
294
  <div className="flex items-center gap-1">
@@ -300,38 +315,47 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
300
315
 
301
316
  {/* MCP actions */}
302
317
  <div className="flex flex-wrap items-center gap-2">
303
- <ActionButton
304
- onClick={() => void handleCopySnippet()}
305
- disabled={false}
306
- busy={false}
307
- label={snippetCopied ? a.detail.mcpCopied : a.detail.mcpCopySnippet}
308
- />
318
+ {!isMindOS && (
319
+ <ActionButton
320
+ onClick={() => void handleCopySnippet()}
321
+ disabled={false}
322
+ busy={false}
323
+ label={snippetCopied ? a.detail.mcpCopied : a.detail.mcpCopySnippet}
324
+ />
325
+ )}
309
326
  <ActionButton
310
327
  onClick={() => void mcp.refresh()}
311
328
  disabled={false}
312
329
  busy={false}
313
330
  label={a.detail.mcpRefresh}
314
331
  />
315
- <ActionButton
316
- onClick={() => void handleApplyMcpConfig(currentScope, currentTransport)}
317
- disabled={mcpBusy}
318
- busy={mcpBusy}
319
- label={a.detail.mcpReconnect}
320
- variant="primary"
321
- />
332
+ {!isMindOS && (
333
+ <ActionButton
334
+ onClick={() => void handleApplyMcpConfig(currentScope, currentTransport)}
335
+ disabled={mcpBusy}
336
+ busy={mcpBusy}
337
+ label={a.detail.mcpReconnect}
338
+ variant="primary"
339
+ />
340
+ )}
322
341
  </div>
323
- <p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>
342
+ {!isMindOS && <p className="text-2xs text-muted-foreground truncate">{snippet.path}</p>}
324
343
  {mcpMessage && <p className="text-xs text-muted-foreground animate-in fade-in duration-200">{mcpMessage}</p>}
325
344
  </section>
326
345
 
327
346
  {/* ═══════════ SKILL ASSIGNMENTS ═══════════ */}
328
- <section className="rounded-lg border border-border bg-card p-4 space-y-3">
347
+ <section className="rounded-xl border border-border bg-card p-5 space-y-4">
329
348
  <div className="flex items-center justify-between">
330
- <h2 className="text-sm font-medium text-foreground">{a.detail.skillAssignments}</h2>
331
- <div className="flex items-center gap-3 text-2xs text-muted-foreground tabular-nums">
332
- <span>MindOS {skillSummary.total}</span>
333
- <span>{a.detail.skillsEnabled.split(' ')[0]} {skillSummary.enabled}</span>
334
- <span>{a.detail.nativeInstalledSkills} {nativeInstalledSkills.length}</span>
349
+ <h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
350
+ <div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
351
+ <Zap size={13} className="text-muted-foreground/70" />
352
+ </div>
353
+ {a.detail.skillAssignments}
354
+ </h2>
355
+ <div className="flex items-center gap-2 text-2xs text-muted-foreground/60 tabular-nums">
356
+ <span className="px-1.5 py-0.5 rounded bg-muted/40">MindOS {skillSummary.total}</span>
357
+ <span className="px-1.5 py-0.5 rounded bg-emerald-500/[0.06] text-emerald-600 dark:text-emerald-400">{a.detail.skillsEnabled.split(' ')[0]} {skillSummary.enabled}</span>
358
+ <span className="px-1.5 py-0.5 rounded bg-muted/40">{a.detail.nativeInstalledSkills} {nativeInstalledSkills.length}</span>
335
359
  </div>
336
360
  </div>
337
361
 
@@ -504,9 +528,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
504
528
 
505
529
  function DetailLine({ label, value }: { label: string; value: string }) {
506
530
  return (
507
- <div className="rounded-md border border-border px-3 py-2">
508
- <p className="text-2xs text-muted-foreground mb-1">{label}</p>
509
- <p className="text-sm text-foreground truncate">{value}</p>
531
+ <div className="rounded-lg border border-border/60 bg-muted/[0.02] px-3.5 py-2.5 hover:bg-muted/[0.06] transition-colors duration-100">
532
+ <p className="text-2xs text-muted-foreground/60 mb-1 uppercase tracking-wider">{label}</p>
533
+ <p className="text-sm text-foreground font-medium truncate">{value}</p>
510
534
  </div>
511
535
  );
512
536
  }
@@ -46,8 +46,9 @@ export default function AgentsContentPage({ tab }: { tab: AgentsDashboardTab })
46
46
  detectedCount: buckets.detected.length,
47
47
  notFoundCount: buckets.notFound.length,
48
48
  allSkillsDisabled: mcp.skills.length > 0 && mcp.skills.every((s) => !s.enabled),
49
+ copy: a.overview,
49
50
  }),
50
- [mcp.skills, mcp.status?.running, buckets.detected.length, buckets.notFound.length],
51
+ [mcp.skills, mcp.status?.running, buckets.detected.length, buckets.notFound.length, a.overview],
51
52
  );
52
53
  const enabledSkillCount = useMemo(
53
54
  () => mcp.skills.filter((skill) => skill.enabled).length,
@@ -142,8 +142,10 @@ export default function AgentsMcpSection({
142
142
  {/* Header */}
143
143
  <div className="flex items-center justify-between gap-2">
144
144
  <div className="flex items-center gap-2.5">
145
- <h2 className="text-sm font-medium text-foreground flex items-center gap-2">
146
- <Server size={15} className="text-muted-foreground" aria-hidden="true" />
145
+ <h2 className="text-sm font-semibold text-foreground flex items-center gap-2">
146
+ <div className="w-6 h-6 rounded-md bg-muted/50 flex items-center justify-center">
147
+ <Server size={13} className="text-muted-foreground/70" aria-hidden="true" />
148
+ </div>
147
149
  {copy.title}
148
150
  </h2>
149
151
  <button
@@ -164,8 +166,8 @@ export default function AgentsMcpSection({
164
166
  </div>
165
167
 
166
168
  {/* Compact status strip + risk alerts */}
167
- <div className="rounded-lg border border-border bg-card p-3">
168
- <div className="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs">
169
+ <div className="rounded-xl border border-border/60 bg-gradient-to-r from-card to-card/80 p-3.5">
170
+ <div className="flex flex-wrap items-center gap-x-5 gap-y-2 text-xs">
169
171
  <StatusDot tone="ok" label={copy.filters.connected} count={buckets.connected.length} />
170
172
  <StatusDot tone="warn" label={copy.filters.detected} count={buckets.detected.length} />
171
173
  {buckets.notFound.length > 0 && (
@@ -294,7 +296,7 @@ function ByAgentView({
294
296
  const mcpServers = agent.configuredMcpServers ?? [];
295
297
  const nativeSkillCount = (agent.installedSkillNames ?? []).length;
296
298
  return (
297
- <div key={agent.key} className="rounded-lg border border-border bg-card group hover:border-border/60 hover:shadow-sm transition-all duration-150">
299
+ <div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'connected' ? 'border-l-2 border-l-[var(--success)] border-border' : status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : 'border-border'}`}>
298
300
  {/* Card header with avatar */}
299
301
  <div className="flex items-center gap-3 p-3">
300
302
  <AgentAvatar name={agent.name} status={status} />
@@ -344,8 +346,8 @@ function ByAgentView({
344
346
  {mcpServers.length > 0 && (
345
347
  <div className="flex flex-wrap gap-1 px-3 pb-3 ml-12">
346
348
  {mcpServers.map((name) => (
347
- <span key={name} className="inline-flex items-center gap-1 rounded-full bg-muted/60 px-2 py-0.5 text-2xs text-muted-foreground">
348
- <Server size={9} className="text-[var(--amber)] shrink-0" aria-hidden="true" />
349
+ <span key={name} className="inline-flex items-center gap-1.5 rounded-full bg-muted/40 border border-border/30 px-2.5 py-0.5 text-2xs text-muted-foreground hover:bg-muted/60 transition-colors duration-100">
350
+ <span className="w-1 h-1 rounded-full bg-[var(--amber)]" aria-hidden="true" />
349
351
  {name}
350
352
  </span>
351
353
  ))}
@@ -450,12 +452,14 @@ function ByServerView({
450
452
  const notFoundCount = agentDetails.length - connectedCount - detectedCount + orphanNames.length;
451
453
 
452
454
  return (
453
- <div key={srv.serverName} className="rounded-lg border border-border bg-card p-4 hover:border-border/60 hover:shadow-sm transition-all duration-150">
455
+ <div key={srv.serverName} className="rounded-xl border border-border bg-card p-4 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-200">
454
456
  {/* Server header */}
455
- <div className="flex items-center justify-between gap-2 mb-2">
456
- <div className="flex items-center gap-2 min-w-0">
457
- <Server size={14} className="text-[var(--amber)] shrink-0" aria-hidden="true" />
458
- <span className="text-sm font-medium text-foreground truncate">{srv.serverName}</span>
457
+ <div className="flex items-center justify-between gap-2 mb-3">
458
+ <div className="flex items-center gap-2.5 min-w-0">
459
+ <div className="w-7 h-7 rounded-lg bg-[var(--amber)]/[0.08] flex items-center justify-center shrink-0">
460
+ <Server size={13} className="text-[var(--amber)]" aria-hidden="true" />
461
+ </div>
462
+ <span className="text-sm font-semibold text-foreground truncate">{srv.serverName}</span>
459
463
  </div>
460
464
  <div className="flex items-center gap-1.5 shrink-0">
461
465
  {canManage && agentDetails.length > 0 && (
@@ -91,10 +91,10 @@ export default function AgentsOverviewSection({
91
91
  <div className="space-y-5">
92
92
  {/* ═══════════ HERO STATS BAR ═══════════ */}
93
93
  <section
94
- className="rounded-xl border border-border bg-card overflow-hidden"
94
+ className="rounded-xl border border-border bg-gradient-to-b from-card to-card/80 overflow-hidden"
95
95
  aria-label={pulseCopy.connected}
96
96
  >
97
- <div className="flex divide-x divide-border [&>*]:flex-1">
97
+ <div className="flex divide-x divide-border/50 [&>*]:flex-1">
98
98
  <StatCell
99
99
  icon={<Zap size={14} aria-hidden="true" />}
100
100
  label={pulseCopy.connected}
@@ -231,13 +231,13 @@ export default function AgentsOverviewSection({
231
231
  </section>
232
232
  ) : (
233
233
  <section
234
- className="rounded-xl border border-dashed border-border bg-card/50 p-10 text-center"
234
+ className="rounded-xl border border-dashed border-border/60 bg-gradient-to-b from-card/80 to-card/40 p-12 text-center"
235
235
  aria-label={copy.usagePulse}
236
236
  >
237
- <div className="w-12 h-12 rounded-full bg-muted/60 flex items-center justify-center mx-auto mb-3">
238
- <Cable size={20} className="text-muted-foreground" aria-hidden="true" />
237
+ <div className="w-14 h-14 rounded-2xl bg-muted/40 flex items-center justify-center mx-auto mb-4">
238
+ <Cable size={22} className="text-muted-foreground/50" aria-hidden="true" />
239
239
  </div>
240
- <p className="text-sm text-muted-foreground leading-relaxed max-w-xs mx-auto">
240
+ <p className="text-sm text-muted-foreground/70 leading-relaxed max-w-xs mx-auto">
241
241
  {copy.nextActionHint as string}
242
242
  </p>
243
243
  </section>
@@ -269,27 +269,33 @@ function StatCell({
269
269
  : 'text-muted-foreground';
270
270
  const iconColor =
271
271
  tone === 'ok'
272
- ? 'text-muted-foreground'
272
+ ? 'text-emerald-500/70'
273
273
  : tone === 'warn'
274
274
  ? 'text-amber-500/70'
275
- : 'text-muted-foreground/50';
275
+ : 'text-muted-foreground/40';
276
+ const hoverBg =
277
+ tone === 'ok'
278
+ ? 'hover:bg-emerald-500/[0.04]'
279
+ : tone === 'warn'
280
+ ? 'hover:bg-amber-500/[0.04]'
281
+ : 'hover:bg-muted/20';
276
282
 
277
283
  return (
278
284
  <div
279
- className="px-3 py-3.5 text-center hover:bg-muted/20 transition-colors duration-100 group/stat"
285
+ className={`px-3 py-4 text-center ${hoverBg} transition-colors duration-150 group/stat`}
280
286
  role="group"
281
287
  aria-label={`${label}: ${value}${total !== undefined ? `/${total}` : ''}`}
282
288
  >
283
- <div className={`flex items-center justify-center gap-1.5 mb-1.5 ${iconColor} group-hover/stat:text-foreground transition-colors duration-100`}>
289
+ <div className={`flex items-center justify-center gap-1.5 mb-2 ${iconColor} group-hover/stat:opacity-100 transition-all duration-150`}>
284
290
  {icon}
285
- <span className="text-2xs text-muted-foreground truncate">{label}</span>
286
291
  </div>
287
- <p className={`text-lg font-semibold tabular-nums leading-none ${textColor}`}>
292
+ <p className={`text-xl font-semibold tabular-nums leading-none mb-1.5 ${textColor}`}>
288
293
  {value}
289
294
  {total !== undefined && (
290
295
  <span className="text-xs font-normal text-muted-foreground ml-0.5">/{total}</span>
291
296
  )}
292
297
  </p>
298
+ <span className="text-2xs text-muted-foreground/70 truncate block">{label}</span>
293
299
  </div>
294
300
  );
295
301
  }
@@ -315,19 +321,19 @@ function QuickNavCard({
315
321
  <Link
316
322
  href={href}
317
323
  className="group rounded-xl border border-border bg-card p-4 flex items-start gap-3.5
318
- hover:border-[var(--amber)]/30 hover:bg-muted/20 hover:shadow-sm
324
+ hover:border-[var(--amber)]/30 hover:shadow-[0_2px_12px_rgba(0,0,0,0.04)]
319
325
  active:scale-[0.99]
320
- transition-all duration-150
326
+ transition-all duration-200
321
327
  focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
322
328
  >
323
- <div className="shrink-0 w-10 h-10 rounded-lg bg-muted/60 flex items-center justify-center text-muted-foreground group-hover:text-[var(--amber)] group-hover:bg-[var(--amber-dim)] transition-colors duration-150">
329
+ <div className="shrink-0 w-10 h-10 rounded-xl bg-muted/50 flex items-center justify-center text-muted-foreground/70 group-hover:text-[var(--amber)] group-hover:bg-[var(--amber)]/[0.08] transition-all duration-200">
324
330
  {icon}
325
331
  </div>
326
332
  <div className="flex-1 min-w-0">
327
333
  <div className="flex items-center gap-2 mb-1">
328
334
  <span className="text-sm font-semibold text-foreground">{title}</span>
329
335
  <span
330
- className={`text-2xs px-1.5 py-0.5 rounded font-medium select-none ${
336
+ className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
331
337
  statTone === 'ok'
332
338
  ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
333
339
  : 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
@@ -336,11 +342,11 @@ function QuickNavCard({
336
342
  {stat}
337
343
  </span>
338
344
  </div>
339
- <p className="text-xs text-muted-foreground leading-relaxed line-clamp-2">{description}</p>
345
+ <p className="text-xs text-muted-foreground/70 leading-relaxed line-clamp-2">{description}</p>
340
346
  </div>
341
347
  <ArrowRight
342
348
  size={14}
343
- className="shrink-0 mt-1.5 text-muted-foreground/30 group-hover:text-[var(--amber)] group-hover:translate-x-0.5 transition-all duration-150"
349
+ className="shrink-0 mt-1.5 text-muted-foreground/20 group-hover:text-[var(--amber)] group-hover:translate-x-0.5 transition-all duration-200"
344
350
  aria-hidden="true"
345
351
  />
346
352
  </Link>
@@ -375,11 +381,11 @@ function AgentCard({
375
381
  return (
376
382
  <Link
377
383
  href={`/agents/${encodeURIComponent(agent.key)}`}
378
- className="group rounded-xl border border-border bg-card p-3.5
379
- hover:border-[var(--amber)]/30 hover:shadow-sm
384
+ className={`group rounded-xl border border-border bg-card p-3.5
385
+ hover:border-[var(--amber)]/30 hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)]
380
386
  active:scale-[0.98]
381
387
  transition-all duration-150 animate-in
382
- focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
388
+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring`}
383
389
  style={{ animationDelay: `${Math.min(index * 30, 300)}ms` }}
384
390
  >
385
391
  {/* Top row: avatar + name + status */}
@@ -390,27 +396,18 @@ function AgentCard({
390
396
  {agent.name}
391
397
  </span>
392
398
  {agent.transport && status === 'connected' && (
393
- <span className="text-2xs text-muted-foreground font-mono">{agent.transport}</span>
399
+ <span className="text-2xs text-muted-foreground/60 font-mono">{agent.transport}</span>
394
400
  )}
395
401
  </div>
396
- <span className={`text-2xs px-1.5 py-0.5 rounded font-medium shrink-0 select-none ${statusColor}`}>
402
+ <span className={`text-2xs px-2 py-0.5 rounded-full font-medium shrink-0 select-none ${statusColor}`}>
397
403
  {statusLabel}
398
404
  </span>
399
405
  </div>
400
406
 
401
407
  {/* Metrics row */}
402
- <div className="flex items-center gap-0 pt-2.5 border-t border-border/50">
408
+ <div className="flex items-center gap-1 pt-2.5 border-t border-border/40">
403
409
  <MetricChip icon={<Server size={11} aria-hidden="true" />} value={mcpCount} label={copy.colMcp as string} />
404
- <span className="text-border mx-2 select-none" aria-hidden="true">·</span>
405
410
  <MetricChip icon={<Zap size={11} aria-hidden="true" />} value={skillCount} label={copy.colSkills as string} />
406
- {agent.skillMode && (
407
- <>
408
- <span className="text-border mx-2 select-none" aria-hidden="true">·</span>
409
- <span className="text-2xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground truncate select-none">
410
- {agent.skillMode}
411
- </span>
412
- </>
413
- )}
414
411
  <span className="flex-1 min-w-[4px]" />
415
412
  {hasRuntime && (
416
413
  <span
@@ -438,9 +435,13 @@ function MetricChip({
438
435
  label: string;
439
436
  }) {
440
437
  return (
441
- <span className="inline-flex items-center gap-1" title={label} aria-label={`${label}: ${value}`}>
442
- <span className="text-muted-foreground/60">{icon}</span>
443
- <span className={`tabular-nums text-xs ${value > 0 ? 'text-foreground font-medium' : 'text-muted-foreground/50'}`}>
438
+ <span
439
+ className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-md ${value > 0 ? 'bg-muted/40' : ''}`}
440
+ title={label}
441
+ aria-label={`${label}: ${value}`}
442
+ >
443
+ <span className={value > 0 ? 'text-muted-foreground' : 'text-muted-foreground/30'}>{icon}</span>
444
+ <span className={`tabular-nums text-xs ${value > 0 ? 'text-foreground font-medium' : 'text-muted-foreground/40'}`}>
444
445
  {value}
445
446
  </span>
446
447
  </span>