@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
@@ -1,11 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
- import { Loader2, RefreshCw, ChevronDown, ChevronRight, Settings } from 'lucide-react';
4
+ import { usePathname } from 'next/navigation';
5
+ import { Loader2, RefreshCw, Settings } from 'lucide-react';
5
6
  import { useMcpData } from '@/hooks/useMcpData';
6
7
  import { useLocale } from '@/lib/LocaleContext';
7
- import { Toggle } from '../settings/Primitives';
8
- import type { SkillInfo } from '../settings/types';
9
8
  import PanelHeader from './PanelHeader';
10
9
  import { AgentsPanelHubNav } from './AgentsPanelHubNav';
11
10
  import { AgentsPanelAgentGroups } from './AgentsPanelAgentGroups';
@@ -14,9 +13,7 @@ interface AgentsPanelProps {
14
13
  active: boolean;
15
14
  maximized?: boolean;
16
15
  onMaximize?: () => void;
17
- /** Highlights the row for the agent whose detail is open in the right dock. */
18
16
  selectedAgentKey?: string | null;
19
- onOpenAgentDetail?: (key: string) => void;
20
17
  }
21
18
 
22
19
  export default function AgentsPanel({
@@ -24,14 +21,13 @@ export default function AgentsPanel({
24
21
  maximized,
25
22
  onMaximize,
26
23
  selectedAgentKey = null,
27
- onOpenAgentDetail,
28
24
  }: AgentsPanelProps) {
29
25
  const { t } = useLocale();
30
26
  const p = t.panels.agents;
31
27
  const mcp = useMcpData();
28
+ const pathname = usePathname();
32
29
  const [refreshing, setRefreshing] = useState(false);
33
30
  const [showNotDetected, setShowNotDetected] = useState(false);
34
- const [showBuiltinSkills, setShowBuiltinSkills] = useState(false);
35
31
 
36
32
  const handleRefresh = async () => {
37
33
  setRefreshing(true);
@@ -47,15 +43,17 @@ export default function AgentsPanel({
47
43
  const detected = mcp.agents.filter(a => a.present && !a.installed);
48
44
  const notFound = mcp.agents.filter(a => !a.present);
49
45
 
50
- const customSkills = mcp.skills.filter(s => s.source === 'user');
51
- const builtinSkills = mcp.skills.filter(s => s.source === 'builtin');
52
- const activeSkillCount = mcp.skills.filter(s => s.enabled).length;
53
46
  const installAgentWithRefresh = async (key: string) => {
54
47
  const ok = await mcp.installAgent(key);
55
48
  if (ok) await mcp.refresh();
56
49
  return ok;
57
50
  };
58
51
 
52
+ const routeSelectedAgentKey = pathname?.startsWith('/agents/')
53
+ ? decodeURIComponent(pathname.slice('/agents/'.length))
54
+ : null;
55
+ const effectiveSelectedAgentKey = routeSelectedAgentKey ?? selectedAgentKey;
56
+
59
57
  const listCopy = {
60
58
  installing: p.installing,
61
59
  install: p.install,
@@ -108,28 +106,13 @@ export default function AgentsPanel({
108
106
  <div className="flex flex-col gap-2 py-4 px-0">
109
107
  {hub}
110
108
  <div className="mx-4 border-t border-border" />
111
- <div className="mx-3 rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between">
112
- <span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
113
- {mcp.status?.running ? (
114
- <span className="flex items-center gap-1.5 text-[11px]">
115
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
116
- <span className="text-emerald-600 dark:text-emerald-400">:{mcp.status.port}</span>
117
- <span className="text-muted-foreground">· {mcp.status.toolCount} tools</span>
118
- </span>
119
- ) : (
120
- <span className="flex items-center gap-1.5 text-[11px]">
121
- <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
122
- <span className="text-muted-foreground">{p.stopped}</span>
123
- </span>
124
- )}
125
- </div>
126
- <div className="mx-3 rounded-lg border border-dashed border-border px-3 py-3 text-center">
127
- <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>
128
111
  <p className="text-2xs text-muted-foreground mb-3">{p.skillsEmptyHint}</p>
129
112
  <button
130
113
  onClick={handleRefresh}
131
114
  type="button"
132
- 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"
133
116
  >
134
117
  <RefreshCw size={11} /> {p.retry}
135
118
  </button>
@@ -142,28 +125,11 @@ export default function AgentsPanel({
142
125
  <div className="mx-4 border-t border-border" />
143
126
 
144
127
  <div className="px-3 py-3 space-y-4">
145
- <div className="rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between">
146
- <span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
147
- {mcp.status?.running ? (
148
- <span className="flex items-center gap-1.5 text-[11px]">
149
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block" />
150
- <span className="text-emerald-600 dark:text-emerald-400">:{mcp.status.port}</span>
151
- <span className="text-muted-foreground">· {mcp.status.toolCount} tools</span>
152
- </span>
153
- ) : (
154
- <span className="flex items-center gap-1.5 text-[11px]">
155
- <span className="w-1.5 h-1.5 rounded-full bg-zinc-400 inline-block" />
156
- <span className="text-muted-foreground">{p.stopped}</span>
157
- </span>
158
- )}
159
- </div>
160
-
161
128
  <AgentsPanelAgentGroups
162
129
  connected={connected}
163
130
  detected={detected}
164
131
  notFound={notFound}
165
- onOpenDetail={onOpenAgentDetail}
166
- selectedAgentKey={selectedAgentKey}
132
+ selectedAgentKey={effectiveSelectedAgentKey}
167
133
  listCopy={listCopy}
168
134
  onInstallAgent={installAgentWithRefresh}
169
135
  showNotDetected={showNotDetected}
@@ -175,55 +141,6 @@ export default function AgentsPanel({
175
141
  sectionNotDetected: p.sectionNotDetected,
176
142
  }}
177
143
  />
178
-
179
- <section>
180
- {mcp.skills.length > 0 ? (
181
- <>
182
- <div className="flex items-center justify-between mb-2">
183
- <h3 className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">
184
- {p.skillsTitle} <span className="normal-case font-normal">{activeSkillCount} {p.skillsActive}</span>
185
- </h3>
186
- <button
187
- type="button"
188
- onClick={openAdvancedConfig}
189
- 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"
190
- >
191
- {p.newSkill}
192
- </button>
193
- </div>
194
-
195
- {customSkills.length > 0 && (
196
- <div className="space-y-0.5 mb-2">
197
- {customSkills.map(skill => (
198
- <SkillRow key={skill.name} skill={skill} onToggle={mcp.toggleSkill} />
199
- ))}
200
- </div>
201
- )}
202
-
203
- {builtinSkills.length > 0 && (
204
- <>
205
- <button
206
- type="button"
207
- onClick={() => setShowBuiltinSkills(!showBuiltinSkills)}
208
- 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"
209
- >
210
- {showBuiltinSkills ? <ChevronDown size={10} /> : <ChevronRight size={10} />}
211
- {p.builtinSkills} ({builtinSkills.length})
212
- </button>
213
- {showBuiltinSkills && (
214
- <div className="space-y-0.5">
215
- {builtinSkills.map(skill => (
216
- <SkillRow key={skill.name} skill={skill} onToggle={mcp.toggleSkill} />
217
- ))}
218
- </div>
219
- )}
220
- </>
221
- )}
222
- </>
223
- ) : (
224
- <p className="text-2xs text-muted-foreground py-1">{p.skillsEmptyHint}</p>
225
- )}
226
- </section>
227
144
  </div>
228
145
  </div>
229
146
  )}
@@ -242,12 +159,3 @@ export default function AgentsPanel({
242
159
  </div>
243
160
  );
244
161
  }
245
-
246
- function SkillRow({ skill, onToggle }: { skill: SkillInfo; onToggle: (name: string, enabled: boolean) => void }) {
247
- return (
248
- <div className="flex items-center justify-between gap-2 px-2 py-1.5 rounded-md hover:bg-muted/30 transition-colors">
249
- <span className="text-xs text-foreground truncate">{skill.name}</span>
250
- <Toggle size="sm" checked={skill.enabled} onChange={v => onToggle(skill.name, v)} />
251
- </div>
252
- );
253
- }
@@ -77,7 +77,7 @@ export default function AgentsPanelAgentDetail({
77
77
  };
78
78
 
79
79
  const dot =
80
- 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';
81
81
 
82
82
  return (
83
83
  <div className="flex flex-col h-full min-h-0">
@@ -125,7 +125,7 @@ export default function AgentsPanelAgentDetail({
125
125
  </button>
126
126
  {result && (
127
127
  <span
128
- 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'}`}
129
129
  >
130
130
  {result.type === 'success' ? <CheckCircle2 size={12} /> : <AlertCircle size={12} />}
131
131
  {result.text}
@@ -15,7 +15,6 @@ export function AgentsPanelAgentGroups({
15
15
  connected,
16
16
  detected,
17
17
  notFound,
18
- onOpenDetail,
19
18
  selectedAgentKey,
20
19
  onInstallAgent,
21
20
  listCopy,
@@ -26,7 +25,6 @@ export function AgentsPanelAgentGroups({
26
25
  connected: AgentInfo[];
27
26
  detected: AgentInfo[];
28
27
  notFound: AgentInfo[];
29
- onOpenDetail?: (key: string) => void;
30
28
  selectedAgentKey?: string | null;
31
29
  onInstallAgent: (key: string) => Promise<boolean>;
32
30
  listCopy: AgentsPanelAgentListRowCopy;
@@ -34,8 +32,6 @@ export function AgentsPanelAgentGroups({
34
32
  setShowNotDetected: (v: boolean | ((prev: boolean) => boolean)) => void;
35
33
  p: AgentsCopy;
36
34
  }) {
37
- const open = onOpenDetail ?? (() => {});
38
-
39
35
  return (
40
36
  <div>
41
37
  <div className="px-0 py-1 mb-0.5">
@@ -53,7 +49,7 @@ export function AgentsPanelAgentGroups({
53
49
  agent={agent}
54
50
  agentStatus="connected"
55
51
  selected={selectedAgentKey === agent.key}
56
- onOpenDetail={() => open(agent.key)}
52
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
57
53
  onInstallAgent={onInstallAgent}
58
54
  copy={listCopy}
59
55
  />
@@ -74,7 +70,7 @@ export function AgentsPanelAgentGroups({
74
70
  agent={agent}
75
71
  agentStatus="detected"
76
72
  selected={selectedAgentKey === agent.key}
77
- onOpenDetail={() => open(agent.key)}
73
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
78
74
  onInstallAgent={onInstallAgent}
79
75
  copy={listCopy}
80
76
  />
@@ -101,7 +97,7 @@ export function AgentsPanelAgentGroups({
101
97
  agent={agent}
102
98
  agentStatus="notFound"
103
99
  selected={selectedAgentKey === agent.key}
104
- onOpenDetail={() => open(agent.key)}
100
+ detailHref={`/agents/${encodeURIComponent(agent.key)}`}
105
101
  onInstallAgent={onInstallAgent}
106
102
  copy={listCopy}
107
103
  />
@@ -1,8 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import Link from 'next/link';
4
5
  import { Check, ChevronRight, Loader2, RotateCw } from 'lucide-react';
5
6
  import type { AgentInfo } from '../settings/types';
7
+ import { AgentAvatar } from '../agents/AgentsPrimitives';
6
8
 
7
9
  export type AgentsPanelAgentListStatus = 'connected' | 'detected' | 'notFound';
8
10
 
@@ -18,20 +20,17 @@ export default function AgentsPanelAgentListRow({
18
20
  agent,
19
21
  agentStatus,
20
22
  selected = false,
21
- onOpenDetail,
23
+ detailHref,
22
24
  onInstallAgent,
23
25
  copy,
24
26
  }: {
25
27
  agent: AgentInfo;
26
28
  agentStatus: AgentsPanelAgentListStatus;
27
29
  selected?: boolean;
28
- onOpenDetail: () => void;
30
+ detailHref: string;
29
31
  onInstallAgent: (key: string) => Promise<boolean>;
30
32
  copy: AgentsPanelAgentListRowCopy;
31
33
  }) {
32
- const dot =
33
- agentStatus === 'connected' ? 'bg-emerald-500' : agentStatus === 'detected' ? 'bg-amber-500' : 'bg-zinc-400';
34
-
35
34
  return (
36
35
  <div
37
36
  className={`
@@ -41,12 +40,11 @@ export default function AgentsPanelAgentListRow({
41
40
  : 'border-border/70 bg-card/50 hover:border-border hover:bg-muted/25'}
42
41
  `}
43
42
  >
44
- <button
45
- type="button"
46
- onClick={onOpenDetail}
47
- className="flex flex-1 min-w-0 items-center gap-2.5 text-left rounded-xl pl-3 pr-2 py-2.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
43
+ <Link
44
+ href={detailHref}
45
+ className="flex flex-1 min-w-0 items-center gap-2.5 text-left rounded-xl pl-2.5 pr-2 py-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
48
46
  >
49
- <span className={`w-2 h-2 rounded-full shrink-0 ring-2 ring-background ${dot}`} />
47
+ <AgentAvatar name={agent.name} status={agentStatus} size="sm" />
50
48
  <span className="text-sm font-medium text-foreground truncate leading-tight">{agent.name}</span>
51
49
  {agentStatus === 'connected' && agent.transport && (
52
50
  <span className="text-2xs font-mono tabular-nums px-1.5 py-0.5 rounded-md bg-muted/90 text-muted-foreground shrink-0 border border-border/50">
@@ -59,7 +57,7 @@ export default function AgentsPanelAgentListRow({
59
57
  className={`shrink-0 transition-opacity duration-150 ${selected ? 'text-[var(--amber)] opacity-90' : 'text-muted-foreground/45 group-hover:text-muted-foreground/80'}`}
60
58
  aria-hidden
61
59
  />
62
- </button>
60
+ </Link>
63
61
 
64
62
  {agentStatus === 'detected' && (
65
63
  <div className="pr-2 py-2 shrink-0">
@@ -19,27 +19,25 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
19
19
  const e = t.panels.echo;
20
20
  const pathname = usePathname() ?? '';
21
21
 
22
- const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string }> = {
23
- 'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle },
24
- continued: { icon: <Bookmark size={14} />, title: e.continuedTitle },
25
- daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle },
26
- 'past-you': { icon: <History size={14} />, title: e.pastYouTitle },
27
- growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle },
22
+ const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string; subtitle: string }> = {
23
+ 'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle, subtitle: e.aboutYouDesc },
24
+ continued: { icon: <Bookmark size={14} />, title: e.continuedTitle, subtitle: e.continuedDesc },
25
+ daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle, subtitle: e.dailyDesc },
26
+ 'past-you': { icon: <History size={14} />, title: e.pastYouTitle, subtitle: e.pastYouDesc },
27
+ growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle, subtitle: e.growthDesc },
28
28
  };
29
29
 
30
30
  return (
31
31
  <div className={`flex flex-col h-full ${active ? '' : 'hidden'}`}>
32
32
  <PanelHeader title={e.title} maximized={maximized} onMaximize={onMaximize} />
33
33
  <div className="flex-1 overflow-y-auto min-h-0">
34
- <div className="flex flex-col py-1">
34
+ <div className="flex flex-col gap-0.5 py-1.5">
35
35
  {ECHO_SEGMENT_ORDER.map((segment) => {
36
36
  const row = rowBySegment[segment];
37
37
  const href = ECHO_SEGMENT_HREF[segment];
38
38
  const isActive = pathname === href || pathname.startsWith(`${href}/`);
39
39
  return (
40
- <div key={segment} className="border-b border-border/60 last:border-b-0">
41
- <PanelNavRow href={href} icon={row.icon} title={row.title} active={isActive} />
42
- </div>
40
+ <PanelNavRow key={segment} href={href} icon={row.icon} title={row.title} subtitle={row.subtitle} active={isActive} />
43
41
  );
44
42
  })}
45
43
  </div>
@@ -5,10 +5,11 @@ import Link from 'next/link';
5
5
  import { ChevronRight } from 'lucide-react';
6
6
  import { cn } from '@/lib/utils';
7
7
 
8
- /** Row matching Discover panel nav: icon tile, title, optional badge, chevron. */
8
+ /** Row matching Discover panel nav: icon tile, title, optional subtitle, optional badge, chevron. */
9
9
  export function PanelNavRow({
10
10
  icon,
11
11
  title,
12
+ subtitle,
12
13
  badge,
13
14
  href,
14
15
  onClick,
@@ -16,6 +17,7 @@ export function PanelNavRow({
16
17
  }: {
17
18
  icon: ReactNode;
18
19
  title: string;
20
+ subtitle?: string;
19
21
  badge?: React.ReactNode;
20
22
  href?: string;
21
23
  onClick?: () => void;
@@ -25,7 +27,12 @@ export function PanelNavRow({
25
27
  const content = (
26
28
  <>
27
29
  <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted">{icon}</span>
28
- <span className="flex-1 text-left text-sm font-medium text-foreground">{title}</span>
30
+ <span className="flex-1 min-w-0">
31
+ <span className="block text-left text-sm font-medium text-foreground truncate">{title}</span>
32
+ {subtitle ? (
33
+ <span className="block text-left text-2xs text-muted-foreground truncate">{subtitle}</span>
34
+ ) : null}
35
+ </span>
29
36
  {badge}
30
37
  <ChevronRight size={14} className="shrink-0 text-muted-foreground" />
31
38
  </>
@@ -142,12 +142,12 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
142
142
  </span>
143
143
  ))}
144
144
  {r.entryPath && enabled && !fileExists && (
145
- <span className="text-2xs" style={{ color: 'var(--amber)' }}>
145
+ <span className="text-2xs text-[var(--amber)]">
146
146
  {(p.createFile ?? 'Create {file}').replace('{file}', r.entryPath)}
147
147
  </span>
148
148
  )}
149
149
  {canOpen && (
150
- <span className="text-2xs" style={{ color: 'var(--amber)' }}>
150
+ <span className="text-2xs text-[var(--amber)]">
151
151
  → {r.entryPath}
152
152
  </span>
153
153
  )}
@@ -6,13 +6,13 @@ import { Terminal, FileEdit, FilePlus, Trash2, Search, Clock, ChevronDown, Alert
6
6
  import type { RendererContext } from '@/lib/renderers/registry';
7
7
 
8
8
  // ─── Log entry format ─────────────────────────────────────────────────────────
9
- // Each entry is a fenced JSON block in the markdown:
9
+ // Primary format:
10
+ // {
11
+ // "version": 1,
12
+ // "events": [{ "ts": "...", "tool": "mindos_write_file", "params": {}, "result": "ok" }]
13
+ // }
10
14
  //
11
- // ```agent-op
12
- // { "ts": "2025-01-15T10:30:00Z", "tool": "mindos_write_file",
13
- // "params": { "path": "...", "content": "..." },
14
- // "result": "ok" | "error", "message": "..." }
15
- // ```
15
+ // Legacy format (still supported for compatibility): JSON Lines.
16
16
 
17
17
  interface AgentOp {
18
18
  ts: string;
@@ -22,9 +22,14 @@ interface AgentOp {
22
22
  message?: string;
23
23
  }
24
24
 
25
+ interface AgentAuditState {
26
+ version?: number;
27
+ events?: AgentOp[];
28
+ }
29
+
25
30
  // ─── Parser ───────────────────────────────────────────────────────────────────
26
31
 
27
- function parseOps(content: string): AgentOp[] {
32
+ function parseJsonLines(content: string): AgentOp[] {
28
33
  const ops: AgentOp[] = [];
29
34
 
30
35
  // JSON Lines format: each line is a JSON object
@@ -36,6 +41,23 @@ function parseOps(content: string): AgentOp[] {
36
41
  if (op.tool && op.ts) ops.push(op);
37
42
  } catch { /* skip non-JSON lines */ }
38
43
  }
44
+ return ops;
45
+ }
46
+
47
+ function parseOps(content: string): AgentOp[] {
48
+ // New format: JSON object with events array.
49
+ try {
50
+ const parsed = JSON.parse(content) as AgentAuditState;
51
+ if (Array.isArray(parsed.events)) {
52
+ return parsed.events
53
+ .filter((op) => Boolean(op?.tool) && Boolean(op?.ts))
54
+ .sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());
55
+ }
56
+ } catch {
57
+ // Fallback to legacy JSONL.
58
+ }
59
+
60
+ const ops = parseJsonLines(content);
39
61
 
40
62
  // newest first
41
63
  return ops.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime());
@@ -236,7 +258,7 @@ export function AgentInspectorRenderer({ content }: RendererContext) {
236
258
  <Terminal size={28} style={{ margin: '0 auto 10px', opacity: 0.3 }} />
237
259
  <p>No agent operations logged yet.</p>
238
260
  <p style={{ marginTop: 6, opacity: 0.6, fontSize: 11 }}>
239
- Agent writes appear here as <code style={{ background: 'var(--muted)', padding: '1px 5px', borderRadius: 4 }}>```agent-op</code> blocks.
261
+ Agent writes appear here from <code style={{ background: 'var(--muted)', padding: '1px 5px', borderRadius: 4 }}>.mindos/agent-audit-log.json</code>.
240
262
  </p>
241
263
  </div>
242
264
  );
@@ -3,14 +3,14 @@ import type { RendererDefinition } from '@/lib/renderers/registry';
3
3
  export const manifest: RendererDefinition = {
4
4
  id: 'agent-inspector',
5
5
  name: 'Agent Inspector',
6
- description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .agent-log.json (JSON Lines format).',
6
+ description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .mindos/agent-audit-log.json and supports legacy .agent-log.json.',
7
7
  author: 'MindOS',
8
8
  icon: '🔍',
9
9
  tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
10
10
  builtin: true,
11
11
  core: true,
12
12
  appBuiltinFeature: true,
13
- entryPath: '.agent-log.json',
14
- match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
13
+ entryPath: '.mindos/agent-audit-log.json',
14
+ match: ({ filePath }) => /(^|\/)\.mindos\/agent-audit-log\.json$/i.test(filePath) || /\.agent-log\.json$/i.test(filePath),
15
15
  load: () => import('./AgentInspectorRenderer').then(m => ({ default: m.AgentInspectorRenderer })),
16
16
  };
@@ -9,6 +9,7 @@ export const manifest: RendererDefinition = {
9
9
  tags: ['productivity', 'tasks', 'markdown'],
10
10
  builtin: true,
11
11
  core: true,
12
+ appBuiltinFeature: true,
12
13
  entryPath: 'TODO.md',
13
14
  match: ({ filePath }) => /\bTODO\b.*\.(md|csv)$/i.test(filePath),
14
15
  load: () => import('./TodoRenderer').then(m => ({ default: m.TodoRenderer })),
@@ -121,7 +121,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
121
121
  type="button"
122
122
  disabled={disabled}
123
123
  onClick={() => handleTestKey(providerName)}
124
- className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
124
+ 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:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
125
125
  >
126
126
  {result.state === 'testing' ? (
127
127
  <>
@@ -143,7 +143,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
143
143
  };
144
144
 
145
145
  return (
146
- <div className="space-y-5">
146
+ <div className="space-y-6">
147
147
  <Field label={<>{t.settings.ai.provider} <EnvBadge overridden={env.AI_PROVIDER} /></>}>
148
148
  <Select
149
149
  value={provider}
@@ -214,7 +214,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
214
214
  )}
215
215
 
216
216
  {Object.values(env).some(Boolean) && (
217
- <div className="flex items-start gap-2 text-xs text-amber-500/80 bg-amber-500/8 border border-amber-500/20 rounded-lg px-3 py-2.5">
217
+ <div className="flex items-start gap-2 text-xs text-[var(--amber)] bg-[var(--amber-subtle)] border border-[var(--amber)]/20 rounded-lg px-3 py-2.5">
218
218
  <AlertCircle size={13} className="shrink-0 mt-0.5" />
219
219
  <span>{t.settings.ai.envHint}</span>
220
220
  </div>
@@ -67,7 +67,7 @@ export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, da
67
67
  onClick={() => setFont(f.value)}
68
68
  className={`px-3 py-1.5 text-xs rounded-lg border transition-all ${
69
69
  font === f.value
70
- ? 'border-amber-500 bg-amber-500/10 text-foreground font-medium shadow-sm'
70
+ ? 'border-[var(--amber)] bg-[var(--amber-subtle)] text-foreground font-medium shadow-sm'
71
71
  : 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
72
72
  }`}
73
73
  style={{ fontFamily: f.style.fontFamily }}
@@ -94,7 +94,7 @@ export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, da
94
94
  onClick={() => setContentWidth(w.value)}
95
95
  className={`px-3 py-1.5 text-xs rounded-lg border transition-all ${
96
96
  contentWidth === w.value
97
- ? 'border-amber-500 bg-amber-500/10 text-foreground font-medium shadow-sm'
97
+ ? 'border-[var(--amber)] bg-[var(--amber-subtle)] text-foreground font-medium shadow-sm'
98
98
  : 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
99
99
  }`}
100
100
  >
@@ -116,7 +116,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
116
116
  }
117
117
 
118
118
  return (
119
- <div className="space-y-5">
119
+ <div className="space-y-6">
120
120
  <SectionLabel>Knowledge Base</SectionLabel>
121
121
 
122
122
  <Field
@@ -208,7 +208,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
208
208
  )}
209
209
  </div>
210
210
  {revealedToken && (
211
- <p className="text-xs text-amber-500">
211
+ <p className="text-xs text-[var(--amber)]">
212
212
  New token generated. Copy it now — it won&apos;t be shown in full again.
213
213
  </p>
214
214
  )}
@@ -221,7 +221,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
221
221
  <SectionLabel>{t.guide?.title ?? 'Getting Started'}</SectionLabel>
222
222
  <div className="flex items-center justify-between py-2">
223
223
  <div className="flex items-center gap-2">
224
- <Sparkles size={14} style={{ color: 'var(--amber)' }} />
224
+ <Sparkles size={14} className="text-[var(--amber)]" />
225
225
  <div>
226
226
  <div className="text-sm text-foreground">{t.guide?.showGuide ?? 'Show getting started guide'}</div>
227
227
  </div>
@@ -94,8 +94,7 @@ export default function AgentInstall({ agents, t, onRefresh }: McpAgentInstallPr
94
94
  style={{ accentColor: 'var(--amber)' }}
95
95
  />
96
96
  <span className="w-28 shrink-0 text-xs">{agent.name}</span>
97
- <span className="text-2xs px-1.5 py-0.5 rounded font-mono"
98
- style={{ background: 'rgba(100,100,120,0.08)' }}>
97
+ <span className="text-2xs px-1.5 py-0.5 rounded font-mono bg-muted">
99
98
  {getEffectiveTransport(agent)}
100
99
  </span>
101
100
  {agent.installed ? (
@@ -131,8 +130,7 @@ export default function AgentInstall({ agents, t, onRefresh }: McpAgentInstallPr
131
130
  onClick={() => setSelected(new Set(
132
131
  agents.filter(a => !a.installed && a.present).map(a => a.key)
133
132
  ))}
134
- className="px-2.5 py-1 rounded-md border transition-colors hover:bg-muted/50"
135
- style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
133
+ className="px-2.5 py-1 rounded-md border border-[var(--amber)] text-[var(--amber)] transition-colors hover:bg-muted/50">
136
134
  {m?.selectDetected ?? 'Select Detected'}
137
135
  </button>
138
136
  <button type="button"
@@ -205,8 +203,7 @@ export default function AgentInstall({ agents, t, onRefresh }: McpAgentInstallPr
205
203
  <button
206
204
  onClick={handleInstall}
207
205
  disabled={selected.size === 0 || installing}
208
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
209
- style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
206
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed bg-[var(--amber)] text-[var(--amber-foreground)]"
210
207
  >
211
208
  {installing && <Loader2 size={12} className="animate-spin" />}
212
209
  {installing ? (m?.installing ?? 'Installing...') : (m?.installSelected ?? 'Install Selected')}
@@ -129,7 +129,7 @@ export default function SkillCreateForm({ onSave, onCancel, saving, error, m }:
129
129
  onClick={() => handleTemplateChange(tmpl)}
130
130
  className={`px-2.5 py-1 text-xs transition-colors ${i > 0 ? 'border-l border-border' : ''} ${
131
131
  selectedTemplate === tmpl
132
- ? 'bg-amber-500/15 text-amber-600 font-medium'
132
+ ? 'bg-[var(--amber-subtle)] text-[var(--amber)] font-medium'
133
133
  : 'text-muted-foreground hover:bg-muted'
134
134
  }`}
135
135
  >
@@ -160,8 +160,7 @@ export default function SkillCreateForm({ onSave, onCancel, saving, error, m }:
160
160
  <button
161
161
  onClick={() => onSave(newName.trim(), newContent || getTemplate(newName.trim() || 'my-skill'))}
162
162
  disabled={!newName.trim() || saving}
163
- className="flex items-center gap-1 px-2.5 py-1 text-xs rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
164
- style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
163
+ 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)]"
165
164
  >
166
165
  {saving && <Loader2 size={10} className="animate-spin" />}
167
166
  {m?.saveSkill ?? 'Save'}