@geminilight/mindos 0.5.63 → 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 (104) 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/changes/route.ts +7 -1
  5. package/app/app/api/file/route.ts +9 -0
  6. package/app/app/api/mcp/agents/route.ts +27 -1
  7. package/app/app/api/mcp/install-skill/route.ts +9 -24
  8. package/app/app/api/skills/route.ts +18 -2
  9. package/app/app/api/tree-version/route.ts +8 -0
  10. package/app/app/layout.tsx +1 -0
  11. package/app/app/page.tsx +1 -2
  12. package/app/app/view/[...path]/ViewPageClient.tsx +0 -1
  13. package/app/components/ActivityBar.tsx +2 -2
  14. package/app/components/Backlinks.tsx +5 -5
  15. package/app/components/CreateSpaceModal.tsx +3 -2
  16. package/app/components/DirPicker.tsx +1 -1
  17. package/app/components/DirView.tsx +2 -3
  18. package/app/components/EditorWrapper.tsx +3 -3
  19. package/app/components/FileTree.tsx +25 -10
  20. package/app/components/GuideCard.tsx +4 -4
  21. package/app/components/HomeContent.tsx +44 -14
  22. package/app/components/MarkdownView.tsx +2 -2
  23. package/app/components/OnboardingView.tsx +1 -1
  24. package/app/components/Panel.tsx +1 -1
  25. package/app/components/RightAgentDetailPanel.tsx +2 -1
  26. package/app/components/RightAskPanel.tsx +1 -1
  27. package/app/components/SearchModal.tsx +10 -2
  28. package/app/components/SidebarLayout.tsx +36 -10
  29. package/app/components/ThemeToggle.tsx +1 -1
  30. package/app/components/agents/AgentDetailContent.tsx +454 -59
  31. package/app/components/agents/AgentsContentPage.tsx +89 -20
  32. package/app/components/agents/AgentsMcpSection.tsx +513 -85
  33. package/app/components/agents/AgentsOverviewSection.tsx +418 -59
  34. package/app/components/agents/AgentsPrimitives.tsx +335 -0
  35. package/app/components/agents/AgentsSkillsSection.tsx +746 -105
  36. package/app/components/agents/SkillDetailPopover.tsx +416 -0
  37. package/app/components/agents/agents-content-model.ts +308 -10
  38. package/app/components/ask/AskContent.tsx +34 -5
  39. package/app/components/ask/FileChip.tsx +1 -0
  40. package/app/components/ask/MentionPopover.tsx +13 -1
  41. package/app/components/ask/MessageList.tsx +5 -7
  42. package/app/components/ask/ToolCallBlock.tsx +4 -4
  43. package/app/components/changes/ChangesBanner.tsx +89 -13
  44. package/app/components/changes/ChangesContentPage.tsx +134 -51
  45. package/app/components/echo/EchoHero.tsx +10 -24
  46. package/app/components/echo/EchoInsightCollapsible.tsx +52 -43
  47. package/app/components/echo/EchoPageSections.tsx +13 -9
  48. package/app/components/echo/EchoSegmentNav.tsx +14 -11
  49. package/app/components/echo/EchoSegmentPageClient.tsx +64 -43
  50. package/app/components/explore/ExploreContent.tsx +3 -7
  51. package/app/components/explore/UseCaseCard.tsx +4 -15
  52. package/app/components/panels/AgentsPanel.tsx +22 -128
  53. package/app/components/panels/AgentsPanelAgentDetail.tsx +7 -6
  54. package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -13
  55. package/app/components/panels/AgentsPanelAgentListRow.tsx +39 -16
  56. package/app/components/panels/AgentsPanelHubNav.tsx +12 -12
  57. package/app/components/panels/EchoPanel.tsx +8 -10
  58. package/app/components/panels/PanelNavRow.tsx +9 -2
  59. package/app/components/panels/PluginsPanel.tsx +5 -5
  60. package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +30 -8
  61. package/app/components/renderers/agent-inspector/manifest.ts +5 -3
  62. package/app/components/renderers/config/manifest.ts +1 -0
  63. package/app/components/renderers/csv/manifest.ts +1 -0
  64. package/app/components/renderers/todo/manifest.ts +1 -0
  65. package/app/components/settings/AiTab.tsx +3 -3
  66. package/app/components/settings/AppearanceTab.tsx +2 -2
  67. package/app/components/settings/KnowledgeTab.tsx +3 -3
  68. package/app/components/settings/McpAgentInstall.tsx +3 -6
  69. package/app/components/settings/McpSkillCreateForm.tsx +2 -3
  70. package/app/components/settings/McpSkillRow.tsx +2 -3
  71. package/app/components/settings/McpSkillsSection.tsx +2 -2
  72. package/app/components/settings/McpTab.tsx +12 -13
  73. package/app/components/settings/MonitoringTab.tsx +13 -13
  74. package/app/components/settings/PluginsTab.tsx +6 -5
  75. package/app/components/settings/Primitives.tsx +3 -4
  76. package/app/components/settings/SettingsContent.tsx +3 -3
  77. package/app/components/settings/SyncTab.tsx +11 -17
  78. package/app/components/settings/UpdateTab.tsx +18 -21
  79. package/app/components/settings/types.ts +14 -0
  80. package/app/components/setup/StepKB.tsx +1 -1
  81. package/app/hooks/useMcpData.tsx +7 -4
  82. package/app/hooks/useMention.ts +25 -8
  83. package/app/lib/agent/log.ts +15 -18
  84. package/app/lib/agent/stream-consumer.ts +3 -0
  85. package/app/lib/agent/to-agent-messages.ts +6 -4
  86. package/app/lib/core/agent-audit-log.ts +280 -0
  87. package/app/lib/core/content-changes.ts +148 -8
  88. package/app/lib/core/index.ts +11 -0
  89. package/app/lib/fs.ts +16 -1
  90. package/app/lib/i18n-en.ts +317 -36
  91. package/app/lib/i18n-zh.ts +316 -35
  92. package/app/lib/mcp-agents.ts +273 -2
  93. package/app/lib/renderers/index.ts +1 -2
  94. package/app/lib/renderers/registry.ts +10 -0
  95. package/app/lib/types.ts +2 -0
  96. package/app/next-env.d.ts +1 -1
  97. package/bin/lib/mcp-agents.js +38 -13
  98. package/package.json +1 -1
  99. package/scripts/migrate-agent-audit-log.js +170 -0
  100. package/scripts/migrate-agent-diff.js +146 -0
  101. package/scripts/setup.js +12 -17
  102. package/skills/plugin-core-builtin-migration/SKILL.md +178 -0
  103. package/app/components/renderers/diff/DiffRenderer.tsx +0 -311
  104. package/app/components/renderers/diff/manifest.ts +0 -14
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useId, useMemo, useState } from 'react';
3
+ import type { ReactNode } from 'react';
4
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
5
+ import { ArrowUpRight, Bookmark, Brain, Check, History, Sun, UserRound } from 'lucide-react';
4
6
  import type { EchoSegment } from '@/lib/echo-segments';
5
7
  import { buildEchoInsightUserPrompt } from '@/lib/echo-insight-prompt';
6
8
  import type { Locale, Messages } from '@/lib/i18n';
@@ -44,12 +46,20 @@ function segmentLead(segment: EchoSegment, p: ReturnType<typeof useLocale>['t'][
44
46
  }
45
47
  }
46
48
 
49
+ const SEGMENT_ICON: Record<EchoSegment, ReactNode> = {
50
+ 'about-you': <UserRound size={18} strokeWidth={1.75} />,
51
+ continued: <Bookmark size={18} strokeWidth={1.75} />,
52
+ daily: <Sun size={18} strokeWidth={1.75} />,
53
+ 'past-you': <History size={18} strokeWidth={1.75} />,
54
+ growth: <Brain size={18} strokeWidth={1.75} />,
55
+ };
56
+
47
57
  const fieldLabelClass =
48
58
  'block font-sans text-2xs font-semibold uppercase tracking-wide text-muted-foreground';
49
59
  const inputClass =
50
- 'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
60
+ 'mt-2 w-full min-h-[5rem] resize-y rounded-lg border border-border bg-background px-3 py-2.5 font-sans text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:border-[var(--amber)]/40';
51
61
  const cardSectionClass =
52
- 'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow-md sm:p-6';
62
+ 'rounded-xl border border-border bg-card p-5 shadow-sm transition-[border-color,box-shadow] duration-150 ease-out hover:border-[var(--amber)]/20 hover:shadow sm:p-6';
53
63
 
54
64
  function echoSnapshotCopy(segment: EchoSegment, p: Messages['echoPages']): { title: string; body: string } {
55
65
  switch (segment) {
@@ -77,6 +87,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
77
87
 
78
88
  const [dailyLine, setDailyLine] = useState('');
79
89
  const [growthIntent, setGrowthIntent] = useState('');
90
+ const [dailySaved, setDailySaved] = useState(false);
91
+ const [growthSaved, setGrowthSaved] = useState(false);
92
+ const dailySavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
93
+ const growthSavedTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
94
+
95
+ useEffect(() => () => {
96
+ clearTimeout(dailySavedTimer.current);
97
+ clearTimeout(growthSavedTimer.current);
98
+ }, []);
80
99
 
81
100
  const snapshot = useMemo(() => echoSnapshotCopy(segment, p), [segment, p]);
82
101
 
@@ -97,6 +116,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
97
116
  } catch {
98
117
  /* ignore */
99
118
  }
119
+ clearTimeout(dailySavedTimer.current);
120
+ setDailySaved(true);
121
+ dailySavedTimer.current = setTimeout(() => setDailySaved(false), 1800);
100
122
  }, [dailyLine]);
101
123
 
102
124
  const persistGrowth = useCallback(() => {
@@ -105,6 +127,9 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
105
127
  } catch {
106
128
  /* ignore */
107
129
  }
130
+ clearTimeout(growthSavedTimer.current);
131
+ setGrowthSaved(true);
132
+ growthSavedTimer.current = setTimeout(() => setGrowthSaved(false), 1800);
108
133
  }, [growthIntent]);
109
134
 
110
135
  const openDailyAsk = useCallback(() => {
@@ -150,7 +175,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
150
175
  );
151
176
 
152
177
  const secondaryBtnClass =
153
- 'inline-flex items-center rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
178
+ 'inline-flex items-center gap-1.5 rounded-lg border border-border bg-background px-4 py-2.5 font-sans text-sm font-medium text-foreground transition-colors duration-150 hover:border-[var(--amber)]/35 hover:bg-[var(--amber-dim)]/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring';
179
+
180
+ const agentBtn = (onClick: () => void) => (
181
+ <button type="button" onClick={onClick} className={secondaryBtnClass}>
182
+ {p.continueAgent}
183
+ <ArrowUpRight size={14} className="shrink-0 text-muted-foreground" aria-hidden />
184
+ </button>
185
+ );
154
186
 
155
187
  return (
156
188
  <article
@@ -158,16 +190,13 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
158
190
  aria-labelledby={pageTitleId}
159
191
  >
160
192
  <EchoHero
161
- breadcrumbNav={p.breadcrumbNav}
162
- parentHref="/echo/about-you"
163
- parent={p.parent}
164
193
  heroKicker={p.heroKicker}
165
194
  pageTitle={title}
166
195
  lead={lead}
167
196
  titleId={pageTitleId}
168
- />
169
-
170
- <EchoSegmentNav activeSegment={segment} />
197
+ >
198
+ <EchoSegmentNav activeSegment={segment} />
199
+ </EchoHero>
171
200
 
172
201
  <div className="mt-6 space-y-6 sm:mt-8">
173
202
  <EchoFactSnapshot
@@ -176,24 +205,15 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
176
205
  snapshotBadge={p.snapshotBadge}
177
206
  emptyTitle={snapshot.title}
178
207
  emptyBody={snapshot.body}
179
- actions={
180
- segment === 'about-you' ? (
181
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
182
- {p.continueAgent}
183
- </button>
184
- ) : undefined
185
- }
208
+ icon={SEGMENT_ICON[segment]}
209
+ actions={segment === 'about-you' ? agentBtn(openSegmentAsk) : undefined}
186
210
  />
187
211
  {segment === 'continued' ? (
188
212
  <EchoContinuedGroups
189
213
  draftsLabel={p.continuedDrafts}
190
214
  todosLabel={p.continuedTodos}
191
215
  subEmptyHint={p.subEmptyHint}
192
- footer={
193
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
194
- {p.continueAgent}
195
- </button>
196
- }
216
+ footer={agentBtn(openSegmentAsk)}
197
217
  />
198
218
  ) : null}
199
219
  </div>
@@ -212,11 +232,14 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
212
232
  placeholder={p.dailyLinePlaceholder}
213
233
  className={inputClass}
214
234
  />
215
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.dailySavedNote}</p>
235
+ <p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
236
+ <span>{p.dailySavedNote}</span>
237
+ <span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
238
+ {dailySaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
239
+ </span>
240
+ </p>
216
241
  <div className="mt-4">
217
- <button type="button" onClick={openDailyAsk} className={secondaryBtnClass}>
218
- {p.continueAgent}
219
- </button>
242
+ {agentBtn(openDailyAsk)}
220
243
  </div>
221
244
  </section>
222
245
  ) : null}
@@ -235,31 +258,29 @@ export default function EchoSegmentPageClient({ segment }: { segment: EchoSegmen
235
258
  placeholder={p.growthIntentPlaceholder}
236
259
  className={`${inputClass} min-h-[6.5rem]`}
237
260
  />
238
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.growthSavedNote}</p>
261
+ <p className="mt-3 flex items-center gap-2 font-sans text-2xs text-muted-foreground">
262
+ <span>{p.growthSavedNote}</span>
263
+ <span className="inline-flex items-center gap-1 text-[var(--success)]" aria-live="polite">
264
+ {growthSaved ? <><Check size={12} aria-hidden /> {p.savedFlash}</> : null}
265
+ </span>
266
+ </p>
239
267
  <div className="mt-4 border-t border-border/60 pt-4">
240
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
241
- {p.continueAgent}
242
- </button>
268
+ {agentBtn(openSegmentAsk)}
243
269
  </div>
244
270
  </section>
245
271
  ) : null}
246
272
 
247
273
  {segment === 'past-you' ? (
248
274
  <section className={`${cardSectionClass} mt-6`}>
249
- <label className={fieldLabelClass}>{p.pastYouDrawLabel}</label>
250
- <button
251
- type="button"
252
- disabled
253
- title={p.pastYouDisabledHint}
254
- className="mt-2 inline-flex cursor-not-allowed items-center rounded-lg border border-dashed border-border bg-muted/20 px-4 py-2.5 font-sans text-sm text-muted-foreground opacity-85"
255
- >
256
- {p.pastYouAnother}
257
- </button>
258
- <p className="mt-3 font-sans text-2xs text-muted-foreground">{p.pastYouDisabledHint}</p>
275
+ <div className="flex items-center gap-3">
276
+ <span className={fieldLabelClass}>{p.pastYouDrawLabel}</span>
277
+ <span className="rounded-full bg-muted px-2 py-0.5 font-sans text-2xs font-medium text-muted-foreground">
278
+ {p.pastYouComingSoon}
279
+ </span>
280
+ </div>
281
+ <p className="mt-3 font-sans text-sm leading-relaxed text-muted-foreground">{p.pastYouDisabledHint}</p>
259
282
  <div className="mt-4 border-t border-border/60 pt-4">
260
- <button type="button" onClick={openSegmentAsk} className={secondaryBtnClass}>
261
- {p.continueAgent}
262
- </button>
283
+ {agentBtn(openSegmentAsk)}
263
284
  </div>
264
285
  </section>
265
286
  ) : null}
@@ -31,17 +31,13 @@ export default function ExploreContent() {
31
31
  {/* Header */}
32
32
  <div className="mb-8">
33
33
  <div className="flex items-center gap-2 mb-3">
34
- <div className="w-1 h-5 rounded-full" style={{ background: 'var(--amber)' }} />
35
- <h1
36
- className="text-2xl font-semibold tracking-tight font-display"
37
- style={{ color: 'var(--foreground)' }}
38
- >
34
+ <div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
35
+ <h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
39
36
  {e.title}
40
37
  </h1>
41
38
  </div>
42
39
  <p
43
- className="text-sm leading-relaxed"
44
- style={{ color: 'var(--muted-foreground)', paddingLeft: '1rem' }}
40
+ className="text-sm leading-relaxed text-muted-foreground pl-4"
45
41
  >
46
42
  {e.subtitle}
47
43
  </p>
@@ -13,35 +13,24 @@ interface UseCaseCardProps {
13
13
  export default function UseCaseCard({ icon, title, description, prompt, tryItLabel }: UseCaseCardProps) {
14
14
  return (
15
15
  <div
16
- className="group flex flex-col gap-3 p-4 rounded-xl border transition-all duration-150 hover:border-amber-500/30 hover:bg-muted/50"
17
- style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
16
+ className="group flex flex-col gap-3 p-4 rounded-xl border border-border bg-card transition-all duration-150 hover:border-[var(--amber)]/30 hover:bg-muted/50"
18
17
  >
19
18
  <div className="flex items-start gap-3">
20
19
  <span className="text-xl leading-none shrink-0 mt-0.5" suppressHydrationWarning>
21
20
  {icon}
22
21
  </span>
23
22
  <div className="flex-1 min-w-0">
24
- <h3
25
- className="text-sm font-semibold font-display truncate"
26
- style={{ color: 'var(--foreground)' }}
27
- >
23
+ <h3 className="text-sm font-semibold font-display truncate text-foreground">
28
24
  {title}
29
25
  </h3>
30
- <p
31
- className="text-xs leading-relaxed mt-1 line-clamp-2"
32
- style={{ color: 'var(--muted-foreground)' }}
33
- >
26
+ <p className="text-xs leading-relaxed mt-1 line-clamp-2 text-muted-foreground">
34
27
  {description}
35
28
  </p>
36
29
  </div>
37
30
  </div>
38
31
  <button
39
32
  onClick={() => openAskModal(prompt, 'user')}
40
- className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer"
41
- style={{
42
- background: 'var(--amber-dim)',
43
- color: 'var(--amber)',
44
- }}
33
+ className="self-start inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all duration-150 hover:opacity-80 cursor-pointer bg-[var(--amber-dim)] text-[var(--amber)]"
45
34
  >
46
35
  {tryItLabel} →
47
36
  </button>
@@ -1,12 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { useState, useRef, useCallback } from 'react';
4
- import { useRouter } from 'next/navigation';
5
- import { Loader2, RefreshCw, ChevronDown, ChevronRight, Settings } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { usePathname } from 'next/navigation';
5
+ import { Loader2, RefreshCw, Settings } from 'lucide-react';
6
6
  import { useMcpData } from '@/hooks/useMcpData';
7
7
  import { useLocale } from '@/lib/LocaleContext';
8
- import { Toggle } from '../settings/Primitives';
9
- import type { SkillInfo } from '../settings/types';
10
8
  import PanelHeader from './PanelHeader';
11
9
  import { AgentsPanelHubNav } from './AgentsPanelHubNav';
12
10
  import { AgentsPanelAgentGroups } from './AgentsPanelAgentGroups';
@@ -15,9 +13,7 @@ interface AgentsPanelProps {
15
13
  active: boolean;
16
14
  maximized?: boolean;
17
15
  onMaximize?: () => void;
18
- /** Highlights the row for the agent whose detail is open in the right dock. */
19
16
  selectedAgentKey?: string | null;
20
- onOpenAgentDetail?: (key: string) => void;
21
17
  }
22
18
 
23
19
  export default function AgentsPanel({
@@ -25,18 +21,13 @@ export default function AgentsPanel({
25
21
  maximized,
26
22
  onMaximize,
27
23
  selectedAgentKey = null,
28
- onOpenAgentDetail,
29
24
  }: AgentsPanelProps) {
30
25
  const { t } = useLocale();
31
- const router = useRouter();
32
26
  const p = t.panels.agents;
33
27
  const mcp = useMcpData();
28
+ const pathname = usePathname();
34
29
  const [refreshing, setRefreshing] = useState(false);
35
30
  const [showNotDetected, setShowNotDetected] = useState(false);
36
- const [showBuiltinSkills, setShowBuiltinSkills] = useState(false);
37
-
38
- const overviewRef = useRef<HTMLDivElement>(null);
39
- const skillsRef = useRef<HTMLDivElement>(null);
40
31
 
41
32
  const handleRefresh = async () => {
42
33
  setRefreshing(true);
@@ -44,10 +35,6 @@ export default function AgentsPanel({
44
35
  setRefreshing(false);
45
36
  };
46
37
 
47
- const scrollTo = useCallback((el: HTMLElement | null) => {
48
- el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
49
- }, []);
50
-
51
38
  const openAdvancedConfig = () => {
52
39
  window.dispatchEvent(new CustomEvent('mindos:open-settings', { detail: { tab: 'mcp' } }));
53
40
  };
@@ -56,13 +43,23 @@ export default function AgentsPanel({
56
43
  const detected = mcp.agents.filter(a => a.present && !a.installed);
57
44
  const notFound = mcp.agents.filter(a => !a.present);
58
45
 
59
- const customSkills = mcp.skills.filter(s => s.source === 'user');
60
- const builtinSkills = mcp.skills.filter(s => s.source === 'builtin');
61
- const activeSkillCount = mcp.skills.filter(s => s.enabled).length;
46
+ const installAgentWithRefresh = async (key: string) => {
47
+ const ok = await mcp.installAgent(key);
48
+ if (ok) await mcp.refresh();
49
+ return ok;
50
+ };
51
+
52
+ const routeSelectedAgentKey = pathname?.startsWith('/agents/')
53
+ ? decodeURIComponent(pathname.slice('/agents/'.length))
54
+ : null;
55
+ const effectiveSelectedAgentKey = routeSelectedAgentKey ?? selectedAgentKey;
62
56
 
63
57
  const listCopy = {
64
58
  installing: p.installing,
65
59
  install: p.install,
60
+ installSuccess: p.installSuccess,
61
+ installFailed: p.installFailed,
62
+ retryInstall: p.retryInstall,
66
63
  };
67
64
 
68
65
  const hubCopy = {
@@ -75,10 +72,6 @@ export default function AgentsPanel({
75
72
  <AgentsPanelHubNav
76
73
  copy={hubCopy}
77
74
  connectedCount={connected.length}
78
- overviewRef={overviewRef}
79
- skillsRef={skillsRef}
80
- scrollTo={scrollTo}
81
- openAdvancedConfig={openAdvancedConfig}
82
75
  />
83
76
  );
84
77
 
@@ -91,15 +84,6 @@ export default function AgentsPanel({
91
84
  {connected.length} {p.connected}
92
85
  </span>
93
86
  )}
94
- <button
95
- onClick={() => router.push('/agents')}
96
- className="px-2 py-1 rounded border border-border text-2xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
97
- aria-label={p.openDashboard}
98
- title={p.openDashboard}
99
- type="button"
100
- >
101
- {p.openDashboard}
102
- </button>
103
87
  <button
104
88
  onClick={handleRefresh}
105
89
  disabled={refreshing}
@@ -122,28 +106,13 @@ export default function AgentsPanel({
122
106
  <div className="flex flex-col gap-2 py-4 px-0">
123
107
  {hub}
124
108
  <div className="mx-4 border-t border-border" />
125
- <div ref={overviewRef} className="mx-3 rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between scroll-mt-2">
126
- <span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
127
- {mcp.status?.running ? (
128
- <span className="flex items-center gap-1.5 text-[11px]">
129
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
130
- <span className="text-emerald-600 dark:text-emerald-400">:{mcp.status.port}</span>
131
- <span className="text-muted-foreground">· {mcp.status.toolCount} tools</span>
132
- </span>
133
- ) : (
134
- <span className="flex items-center gap-1.5 text-[11px]">
135
- <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
136
- <span className="text-muted-foreground">{p.stopped}</span>
137
- </span>
138
- )}
139
- </div>
140
- <div ref={skillsRef} className="mx-3 scroll-mt-2 rounded-lg border border-dashed border-border px-3 py-3 text-center">
141
- <p className="text-xs text-muted-foreground mb-2">{p.noAgents}</p>
109
+ <div className="mx-3 rounded-lg border border-dashed border-border px-3 py-4 text-center">
110
+ <p className="text-xs text-muted-foreground mb-1.5">{p.noAgents}</p>
142
111
  <p className="text-2xs text-muted-foreground mb-3">{p.skillsEmptyHint}</p>
143
112
  <button
144
113
  onClick={handleRefresh}
145
114
  type="button"
146
- className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
115
+ className="inline-flex items-center gap-1.5 px-2.5 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
147
116
  >
148
117
  <RefreshCw size={11} /> {p.retry}
149
118
  </button>
@@ -156,30 +125,13 @@ export default function AgentsPanel({
156
125
  <div className="mx-4 border-t border-border" />
157
126
 
158
127
  <div className="px-3 py-3 space-y-4">
159
- <div ref={overviewRef} className="rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between scroll-mt-2">
160
- <span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
161
- {mcp.status?.running ? (
162
- <span className="flex items-center gap-1.5 text-[11px]">
163
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
164
- <span className="text-emerald-600 dark:text-emerald-400">:{mcp.status.port}</span>
165
- <span className="text-muted-foreground">· {mcp.status.toolCount} tools</span>
166
- </span>
167
- ) : (
168
- <span className="flex items-center gap-1.5 text-[11px]">
169
- <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
170
- <span className="text-muted-foreground">{p.stopped}</span>
171
- </span>
172
- )}
173
- </div>
174
-
175
128
  <AgentsPanelAgentGroups
176
129
  connected={connected}
177
130
  detected={detected}
178
131
  notFound={notFound}
179
- onOpenDetail={onOpenAgentDetail}
180
- selectedAgentKey={selectedAgentKey}
181
- mcp={mcp}
132
+ selectedAgentKey={effectiveSelectedAgentKey}
182
133
  listCopy={listCopy}
134
+ onInstallAgent={installAgentWithRefresh}
183
135
  showNotDetected={showNotDetected}
184
136
  setShowNotDetected={setShowNotDetected}
185
137
  p={{
@@ -189,55 +141,6 @@ export default function AgentsPanel({
189
141
  sectionNotDetected: p.sectionNotDetected,
190
142
  }}
191
143
  />
192
-
193
- <section ref={skillsRef} className="scroll-mt-2">
194
- {mcp.skills.length > 0 ? (
195
- <>
196
- <div className="flex items-center justify-between mb-2">
197
- <h3 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">
198
- {p.skillsTitle} <span className="normal-case font-normal">{activeSkillCount} {p.skillsActive}</span>
199
- </h3>
200
- <button
201
- type="button"
202
- onClick={openAdvancedConfig}
203
- className="text-2xs text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
204
- >
205
- {p.newSkill}
206
- </button>
207
- </div>
208
-
209
- {customSkills.length > 0 && (
210
- <div className="space-y-0.5 mb-2">
211
- {customSkills.map(skill => (
212
- <SkillRow key={skill.name} skill={skill} onToggle={mcp.toggleSkill} />
213
- ))}
214
- </div>
215
- )}
216
-
217
- {builtinSkills.length > 0 && (
218
- <>
219
- <button
220
- type="button"
221
- onClick={() => setShowBuiltinSkills(!showBuiltinSkills)}
222
- className="flex items-center gap-1 text-2xs text-muted-foreground hover:text-foreground transition-colors mb-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
223
- >
224
- {showBuiltinSkills ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
225
- {p.builtinSkills} ({builtinSkills.length})
226
- </button>
227
- {showBuiltinSkills && (
228
- <div className="space-y-0.5">
229
- {builtinSkills.map(skill => (
230
- <SkillRow key={skill.name} skill={skill} onToggle={mcp.toggleSkill} />
231
- ))}
232
- </div>
233
- )}
234
- </>
235
- )}
236
- </>
237
- ) : (
238
- <p className="text-2xs text-muted-foreground py-1">{p.skillsEmptyHint}</p>
239
- )}
240
- </section>
241
144
  </div>
242
145
  </div>
243
146
  )}
@@ -256,12 +159,3 @@ export default function AgentsPanel({
256
159
  </div>
257
160
  );
258
161
  }
259
-
260
- function SkillRow({ skill, onToggle }: { skill: SkillInfo; onToggle: (name: string, enabled: boolean) => void }) {
261
- return (
262
- <div className="flex items-center justify-between gap-2 px-2 py-1.5 rounded-md hover:bg-muted/30 transition-colors">
263
- <span className="text-xs text-foreground truncate">{skill.name}</span>
264
- <Toggle size="sm" checked={skill.enabled} onChange={v => onToggle(skill.name, v)} />
265
- </div>
266
- );
267
- }
@@ -12,7 +12,8 @@ export type { AgentsPanelAgentDetailStatus };
12
12
  export interface AgentsPanelAgentDetailCopy {
13
13
  connected: string;
14
14
  installing: string;
15
- install: (name: string) => string;
15
+ install: string;
16
+ installFailed: string;
16
17
  copyConfig: string;
17
18
  copied: string;
18
19
  transportLocal: string;
@@ -61,7 +62,7 @@ export default function AgentsPanelAgentDetail({
61
62
  setResult(
62
63
  ok
63
64
  ? { type: 'success', text: `${agent.name} ${copy.connected}` }
64
- : { type: 'error', text: 'Install failed' },
65
+ : { type: 'error', text: copy.installFailed },
65
66
  );
66
67
  setInstalling(false);
67
68
  };
@@ -76,7 +77,7 @@ export default function AgentsPanelAgentDetail({
76
77
  };
77
78
 
78
79
  const dot =
79
- agentStatus === 'connected' ? 'bg-emerald-500' : agentStatus === 'detected' ? 'bg-amber-500' : 'bg-zinc-400';
80
+ agentStatus === 'connected' ? 'bg-[var(--success)]' : agentStatus === 'detected' ? 'bg-[var(--amber)]' : 'bg-muted-foreground';
80
81
 
81
82
  return (
82
83
  <div className="flex flex-col h-full min-h-0">
@@ -117,14 +118,14 @@ export default function AgentsPanelAgentDetail({
117
118
  type="button"
118
119
  onClick={handleInstall}
119
120
  disabled={installing}
120
- className="inline-flex items-center gap-1.5 px-3 py-2 text-xs rounded-lg font-medium text-[var(--amber-foreground)] disabled:opacity-50 bg-[var(--amber)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
121
+ className="inline-flex items-center gap-1.5 px-3 py-2 text-xs rounded-lg font-medium text-white disabled:opacity-50 bg-[var(--amber)] hover:bg-[var(--amber)]/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
121
122
  >
122
123
  {installing ? <Loader2 size={14} className="animate-spin" /> : null}
123
- {installing ? copy.installing : copy.install(agent.name)}
124
+ {installing ? copy.installing : copy.install}
124
125
  </button>
125
126
  {result && (
126
127
  <span
127
- className={`flex items-center gap-1 text-2xs ${result.type === 'success' ? 'text-emerald-600 dark:text-emerald-400' : 'text-destructive'}`}
128
+ className={`flex items-center gap-1 text-2xs ${result.type === 'success' ? 'text-success' : 'text-destructive'}`}
128
129
  >
129
130
  {result.type === 'success' ? <CheckCircle2 size={12} /> : <AlertCircle size={12} />}
130
131
  {result.text}
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { ChevronDown, ChevronRight } from 'lucide-react';
4
4
  import type { AgentInfo } from '../settings/types';
5
- import type { McpContextValue } from '@/hooks/useMcpData';
6
5
  import AgentsPanelAgentListRow, { type AgentsPanelAgentListRowCopy } from './AgentsPanelAgentListRow';
7
6
 
8
7
  type AgentsCopy = {
@@ -16,9 +15,8 @@ export function AgentsPanelAgentGroups({
16
15
  connected,
17
16
  detected,
18
17
  notFound,
19
- onOpenDetail,
20
18
  selectedAgentKey,
21
- mcp,
19
+ onInstallAgent,
22
20
  listCopy,
23
21
  showNotDetected,
24
22
  setShowNotDetected,
@@ -27,16 +25,13 @@ export function AgentsPanelAgentGroups({
27
25
  connected: AgentInfo[];
28
26
  detected: AgentInfo[];
29
27
  notFound: AgentInfo[];
30
- onOpenDetail?: (key: string) => void;
31
28
  selectedAgentKey?: string | null;
32
- mcp: Pick<McpContextValue, 'installAgent'>;
29
+ onInstallAgent: (key: string) => Promise<boolean>;
33
30
  listCopy: AgentsPanelAgentListRowCopy;
34
31
  showNotDetected: boolean;
35
32
  setShowNotDetected: (v: boolean | ((prev: boolean) => boolean)) => void;
36
33
  p: AgentsCopy;
37
34
  }) {
38
- const open = onOpenDetail ?? (() => {});
39
-
40
35
  return (
41
36
  <div>
42
37
  <div className="px-0 py-1 mb-0.5">
@@ -54,8 +49,8 @@ export function AgentsPanelAgentGroups({
54
49
  agent={agent}
55
50
  agentStatus="connected"
56
51
  selected={selectedAgentKey === agent.key}
57
- onOpenDetail={() => open(agent.key)}
58
- onInstallAgent={mcp.installAgent}
52
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
53
+ onInstallAgent={onInstallAgent}
59
54
  copy={listCopy}
60
55
  />
61
56
  ))}
@@ -75,8 +70,8 @@ export function AgentsPanelAgentGroups({
75
70
  agent={agent}
76
71
  agentStatus="detected"
77
72
  selected={selectedAgentKey === agent.key}
78
- onOpenDetail={() => open(agent.key)}
79
- onInstallAgent={mcp.installAgent}
73
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
74
+ onInstallAgent={onInstallAgent}
80
75
  copy={listCopy}
81
76
  />
82
77
  ))}
@@ -102,8 +97,8 @@ export function AgentsPanelAgentGroups({
102
97
  agent={agent}
103
98
  agentStatus="notFound"
104
99
  selected={selectedAgentKey === agent.key}
105
- onOpenDetail={() => open(agent.key)}
106
- onInstallAgent={mcp.installAgent}
100
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
101
+ onInstallAgent={onInstallAgent}
107
102
  copy={listCopy}
108
103
  />
109
104
  ))}