@geminilight/mindos 0.5.64 → 0.5.66

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 (86) 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/prompt.ts +17 -29
  76. package/app/lib/agent/stream-consumer.ts +3 -0
  77. package/app/lib/agent/to-agent-messages.ts +6 -4
  78. package/app/lib/core/agent-audit-log.ts +280 -0
  79. package/app/lib/core/index.ts +11 -0
  80. package/app/lib/fs.ts +9 -0
  81. package/app/lib/i18n-en.ts +259 -33
  82. package/app/lib/i18n-zh.ts +258 -32
  83. package/app/lib/mcp-agents.ts +231 -2
  84. package/app/lib/types.ts +2 -0
  85. package/package.json +1 -1
  86. package/scripts/migrate-agent-audit-log.js +170 -0
@@ -0,0 +1,416 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useState } from 'react';
4
+ import {
5
+ BookOpen,
6
+ Code2,
7
+ Copy,
8
+ Check,
9
+ FileText,
10
+ Loader2,
11
+ Zap,
12
+ Search,
13
+ Server,
14
+ ToggleLeft,
15
+ Trash2,
16
+ X,
17
+ } from 'lucide-react';
18
+ import { apiFetch } from '@/lib/api';
19
+ import { copyToClipboard } from '@/lib/clipboard';
20
+ import type { SkillInfo } from '@/components/settings/types';
21
+ import { Toggle } from '@/components/settings/Primitives';
22
+ import { AgentAvatar, ConfirmDialog } from './AgentsPrimitives';
23
+ import { capabilityFromText, type SkillCapability } from './agents-content-model';
24
+
25
+ /* ────────── Types ────────── */
26
+
27
+ export interface SkillDetailPopoverCopy {
28
+ close: string;
29
+ source: string;
30
+ sourceBuiltin: string;
31
+ sourceUser: string;
32
+ sourceNative: string;
33
+ capability: string;
34
+ path: string;
35
+ enabled: string;
36
+ disabled: string;
37
+ agents: string;
38
+ noAgents: string;
39
+ content: string;
40
+ loading: string;
41
+ loadFailed: string;
42
+ retry: string;
43
+ copyContent: string;
44
+ copied: string;
45
+ noDescription: string;
46
+ deleteSkill: string;
47
+ confirmDeleteTitle: string;
48
+ confirmDeleteMessage: (name: string) => string;
49
+ confirmDeleteAction: string;
50
+ cancelAction: string;
51
+ deleted: string;
52
+ deleteFailed: string;
53
+ }
54
+
55
+ interface SkillDetailPopoverProps {
56
+ open: boolean;
57
+ skillName: string | null;
58
+ skill?: SkillInfo | null;
59
+ agentNames?: string[];
60
+ isNative?: boolean;
61
+ nativeSourcePath?: string;
62
+ copy: SkillDetailPopoverCopy;
63
+ onClose: () => void;
64
+ onToggle?: (name: string, enabled: boolean) => Promise<boolean>;
65
+ onDelete?: (name: string) => Promise<void>;
66
+ onRefresh?: () => Promise<void>;
67
+ }
68
+
69
+ /* ────────── Capability Icon ────────── */
70
+
71
+ const CAPABILITY_ICONS: Record<SkillCapability, React.ComponentType<{ size?: number; className?: string }>> = {
72
+ research: Search,
73
+ coding: Code2,
74
+ docs: FileText,
75
+ ops: Server,
76
+ memory: BookOpen,
77
+ };
78
+
79
+ /* ────────── Component ────────── */
80
+
81
+ export default function SkillDetailPopover({
82
+ open,
83
+ skillName,
84
+ skill,
85
+ agentNames = [],
86
+ isNative = false,
87
+ nativeSourcePath,
88
+ copy,
89
+ onClose,
90
+ onToggle,
91
+ onDelete,
92
+ onRefresh,
93
+ }: SkillDetailPopoverProps) {
94
+ const [content, setContent] = useState<string | null>(null);
95
+ const [nativeDesc, setNativeDesc] = useState<string>('');
96
+ const [loading, setLoading] = useState(false);
97
+ const [loadError, setLoadError] = useState(false);
98
+ const [copied, setCopied] = useState(false);
99
+ const [confirmDelete, setConfirmDelete] = useState(false);
100
+ const [deleting, setDeleting] = useState(false);
101
+ const [deleteMsg, setDeleteMsg] = useState<string | null>(null);
102
+ const [toggleBusy, setToggleBusy] = useState(false);
103
+
104
+ const fetchContent = useCallback(async () => {
105
+ if (!skillName) return;
106
+ setLoading(true);
107
+ setLoadError(false);
108
+ try {
109
+ if (isNative && nativeSourcePath) {
110
+ const res = await apiFetch<{ content: string; description?: string }>('/api/skills', {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({ action: 'read-native', name: skillName, sourcePath: nativeSourcePath }),
114
+ });
115
+ setContent(res.content);
116
+ setNativeDesc(res.description || '');
117
+ } else if (!isNative) {
118
+ const res = await apiFetch<{ content: string }>('/api/skills', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify({ action: 'read', name: skillName }),
122
+ });
123
+ setContent(res.content);
124
+ }
125
+ } catch {
126
+ setLoadError(true);
127
+ } finally {
128
+ setLoading(false);
129
+ }
130
+ }, [skillName, isNative, nativeSourcePath]);
131
+
132
+ useEffect(() => {
133
+ if (open && skillName) {
134
+ setContent(null);
135
+ setNativeDesc('');
136
+ setLoadError(false);
137
+ setCopied(false);
138
+ setDeleteMsg(null);
139
+ fetchContent();
140
+ }
141
+ }, [open, skillName, fetchContent]);
142
+
143
+ useEffect(() => {
144
+ if (!open) return;
145
+ function onKey(e: KeyboardEvent) {
146
+ if (e.key === 'Escape') onClose();
147
+ }
148
+ document.addEventListener('keydown', onKey);
149
+ return () => document.removeEventListener('keydown', onKey);
150
+ }, [open, onClose]);
151
+
152
+ const handleCopy = useCallback(async () => {
153
+ if (!content) return;
154
+ const ok = await copyToClipboard(content);
155
+ if (ok) {
156
+ setCopied(true);
157
+ setTimeout(() => setCopied(false), 1500);
158
+ }
159
+ }, [content]);
160
+
161
+ const handleToggle = useCallback(async (enabled: boolean) => {
162
+ if (!skillName || !onToggle) return;
163
+ setToggleBusy(true);
164
+ try {
165
+ await onToggle(skillName, enabled);
166
+ } finally {
167
+ setToggleBusy(false);
168
+ }
169
+ }, [skillName, onToggle]);
170
+
171
+ const handleDelete = useCallback(async () => {
172
+ if (!skillName || !onDelete) return;
173
+ setConfirmDelete(false);
174
+ setDeleting(true);
175
+ try {
176
+ await onDelete(skillName);
177
+ setDeleteMsg(copy.deleted);
178
+ await onRefresh?.();
179
+ setTimeout(() => onClose(), 800);
180
+ } catch {
181
+ setDeleteMsg(copy.deleteFailed);
182
+ setDeleting(false);
183
+ }
184
+ }, [skillName, onDelete, onRefresh, onClose, copy.deleted, copy.deleteFailed]);
185
+
186
+ if (!open || !skillName) return null;
187
+
188
+ const capability = skill
189
+ ? capabilityFromText(`${skill.name} ${skill.description}`)
190
+ : capabilityFromText(skillName);
191
+ const CapIcon = CAPABILITY_ICONS[capability] ?? Zap;
192
+ const isUserSkill = skill?.source === 'user';
193
+ const sourceLabel = isNative
194
+ ? copy.sourceNative
195
+ : skill?.source === 'builtin'
196
+ ? copy.sourceBuiltin
197
+ : copy.sourceUser;
198
+ const description = skill?.description || nativeDesc || '';
199
+ const skillPath = skill?.path || (isNative && nativeSourcePath ? `${nativeSourcePath}/${skillName}/SKILL.md` : '');
200
+
201
+ return (
202
+ <>
203
+ {/* Backdrop */}
204
+ <div
205
+ className="fixed inset-0 z-40 bg-black/30 backdrop-blur-[2px]"
206
+ onClick={onClose}
207
+ aria-hidden="true"
208
+ />
209
+
210
+ {/* Slide-over panel */}
211
+ <div
212
+ role="dialog"
213
+ aria-modal="true"
214
+ aria-label={skillName}
215
+ className="fixed right-0 top-0 z-50 h-full w-full max-w-md border-l border-border bg-card shadow-2xl flex flex-col animate-in slide-in-from-right duration-200"
216
+ >
217
+ {/* ─── Header ─── */}
218
+ <div className="flex items-center gap-3 px-5 py-4 border-b border-border shrink-0">
219
+ <div className="w-9 h-9 rounded-lg bg-muted/60 flex items-center justify-center text-muted-foreground shrink-0">
220
+ <CapIcon size={18} />
221
+ </div>
222
+ <div className="flex-1 min-w-0">
223
+ <h2 className="text-sm font-semibold text-foreground truncate">{skillName}</h2>
224
+ <div className="flex items-center gap-2 mt-0.5">
225
+ <span className={`text-2xs px-1.5 py-0.5 rounded font-medium select-none ${
226
+ isNative
227
+ ? 'bg-muted text-muted-foreground'
228
+ : skill?.source === 'builtin'
229
+ ? 'bg-muted text-muted-foreground'
230
+ : 'bg-[var(--amber-dim)] text-[var(--amber)]'
231
+ }`}>
232
+ {sourceLabel}
233
+ </span>
234
+ <span className="text-2xs text-muted-foreground capitalize">{capability}</span>
235
+ </div>
236
+ </div>
237
+ <button
238
+ type="button"
239
+ onClick={onClose}
240
+ className="shrink-0 p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
241
+ aria-label={copy.close}
242
+ >
243
+ <X size={16} />
244
+ </button>
245
+ </div>
246
+
247
+ {/* ─── Body (scrollable) ─── */}
248
+ <div className="flex-1 overflow-y-auto px-5 py-4 space-y-5">
249
+ {/* Description */}
250
+ {description ? (
251
+ <p className="text-sm text-foreground leading-relaxed">{description}</p>
252
+ ) : !isNative ? (
253
+ <p className="text-sm text-muted-foreground italic">{copy.noDescription}</p>
254
+ ) : null}
255
+
256
+ {/* Quick meta */}
257
+ <div className="grid grid-cols-2 gap-3">
258
+ {!isNative && skill && (
259
+ <MetaCard label={copy.enabled} value={skill.enabled ? '✓' : '—'} tone={skill.enabled ? 'ok' : 'muted'} />
260
+ )}
261
+ <MetaCard label={copy.capability} value={capability} />
262
+ <MetaCard label={copy.source} value={sourceLabel} />
263
+ <MetaCard label={copy.agents} value={String(agentNames.length)} />
264
+ </div>
265
+
266
+ {/* Path */}
267
+ {skillPath && (
268
+ <div className="rounded-lg border border-border bg-background p-3">
269
+ <span className="text-2xs text-muted-foreground block mb-1">{copy.path}</span>
270
+ <code className="text-xs text-foreground font-mono break-all leading-relaxed">{skillPath}</code>
271
+ </div>
272
+ )}
273
+
274
+ {/* Agents */}
275
+ {agentNames.length > 0 && (
276
+ <div>
277
+ <h3 className="text-xs font-medium text-muted-foreground mb-2">{copy.agents}</h3>
278
+ <div className="flex flex-wrap gap-2">
279
+ {agentNames.map((name) => (
280
+ <AgentAvatar key={name} name={name} size="sm" />
281
+ ))}
282
+ </div>
283
+ </div>
284
+ )}
285
+ {agentNames.length === 0 && (
286
+ <div className="rounded-lg border border-dashed border-border p-3 text-center">
287
+ <p className="text-xs text-muted-foreground">{copy.noAgents}</p>
288
+ </div>
289
+ )}
290
+
291
+ {/* Content */}
292
+ {(content !== null || loading || loadError) && (
293
+ <div>
294
+ <div className="flex items-center justify-between mb-2">
295
+ <h3 className="text-xs font-medium text-muted-foreground">{copy.content}</h3>
296
+ {content && (
297
+ <button
298
+ type="button"
299
+ onClick={handleCopy}
300
+ className="inline-flex items-center gap-1 text-2xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded px-1.5 py-0.5"
301
+ aria-label={copy.copyContent}
302
+ >
303
+ {copied ? <Check size={11} /> : <Copy size={11} />}
304
+ {copied ? copy.copied : copy.copyContent}
305
+ </button>
306
+ )}
307
+ </div>
308
+
309
+ {loading && (
310
+ <div className="rounded-lg border border-border bg-background p-6 flex items-center justify-center gap-2 text-sm text-muted-foreground">
311
+ <Loader2 size={14} className="animate-spin" />
312
+ {copy.loading}
313
+ </div>
314
+ )}
315
+
316
+ {loadError && (
317
+ <div className="rounded-lg border border-destructive/20 bg-destructive/[0.03] p-4 text-center space-y-2">
318
+ <p className="text-sm text-destructive">{copy.loadFailed}</p>
319
+ <button
320
+ type="button"
321
+ onClick={fetchContent}
322
+ className="text-xs text-foreground underline hover:no-underline cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded"
323
+ >
324
+ {copy.retry}
325
+ </button>
326
+ </div>
327
+ )}
328
+
329
+ {content && !loading && !loadError && (
330
+ <pre className="rounded-lg border border-border bg-background p-3 text-xs text-foreground font-mono overflow-x-auto max-h-80 leading-relaxed whitespace-pre-wrap break-words">
331
+ {content}
332
+ </pre>
333
+ )}
334
+ </div>
335
+ )}
336
+
337
+ {/* Delete message */}
338
+ {deleteMsg && (
339
+ <div role="status" aria-live="polite" className="rounded-md border border-border bg-muted/50 px-3 py-2 text-xs text-muted-foreground animate-in fade-in duration-200">
340
+ {deleteMsg}
341
+ </div>
342
+ )}
343
+ </div>
344
+
345
+ {/* ─── Footer actions ─── */}
346
+ {!isNative && skill && (
347
+ <div className="shrink-0 border-t border-border px-5 py-3 flex items-center gap-2">
348
+ {/* Toggle */}
349
+ {onToggle && (
350
+ <div className="flex items-center gap-2 mr-auto">
351
+ <ToggleLeft size={14} className="text-muted-foreground" aria-hidden="true" />
352
+ <span className="text-xs text-muted-foreground">
353
+ {skill.enabled ? copy.enabled : copy.disabled}
354
+ </span>
355
+ <Toggle
356
+ size="sm"
357
+ checked={skill.enabled}
358
+ onChange={(v) => void handleToggle(v)}
359
+ disabled={toggleBusy}
360
+ />
361
+ </div>
362
+ )}
363
+
364
+ {/* Delete */}
365
+ {isUserSkill && onDelete && (
366
+ <button
367
+ type="button"
368
+ onClick={() => setConfirmDelete(true)}
369
+ disabled={deleting}
370
+ className="inline-flex items-center gap-1.5 px-2.5 min-h-[32px] text-xs rounded-md border border-destructive/30 text-destructive hover:bg-destructive/10 cursor-pointer disabled:opacity-50 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
371
+ >
372
+ {deleting ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
373
+ {copy.deleteSkill}
374
+ </button>
375
+ )}
376
+ </div>
377
+ )}
378
+ </div>
379
+
380
+ {/* Delete confirmation */}
381
+ <ConfirmDialog
382
+ open={confirmDelete}
383
+ title={copy.confirmDeleteTitle}
384
+ message={skillName ? copy.confirmDeleteMessage(skillName) : ''}
385
+ confirmLabel={copy.confirmDeleteAction}
386
+ cancelLabel={copy.cancelAction}
387
+ onConfirm={() => void handleDelete()}
388
+ onCancel={() => setConfirmDelete(false)}
389
+ variant="destructive"
390
+ />
391
+ </>
392
+ );
393
+ }
394
+
395
+ /* ────────── Meta Card ────────── */
396
+
397
+ function MetaCard({
398
+ label,
399
+ value,
400
+ tone = 'default',
401
+ }: {
402
+ label: string;
403
+ value: string;
404
+ tone?: 'ok' | 'muted' | 'default';
405
+ }) {
406
+ const valueColor =
407
+ tone === 'ok' ? 'text-emerald-600 dark:text-emerald-400'
408
+ : tone === 'muted' ? 'text-muted-foreground'
409
+ : 'text-foreground';
410
+ return (
411
+ <div className="rounded-lg border border-border bg-background px-3 py-2.5">
412
+ <span className="text-2xs text-muted-foreground block mb-0.5">{label}</span>
413
+ <span className={`text-sm font-medium capitalize ${valueColor}`}>{value}</span>
414
+ </div>
415
+ );
416
+ }