@geminilight/mindos 0.6.21 → 0.6.22
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.
|
@@ -74,6 +74,7 @@ export async function GET() {
|
|
|
74
74
|
scope: status.scope,
|
|
75
75
|
transport: status.transport,
|
|
76
76
|
configPath: status.configPath,
|
|
77
|
+
url: status.url, // Store URL for verification
|
|
77
78
|
hasProjectScope: !!agent.project,
|
|
78
79
|
hasGlobalScope: !!agent.global,
|
|
79
80
|
preferredTransport: agent.preferredTransport,
|
|
@@ -102,6 +103,29 @@ export async function GET() {
|
|
|
102
103
|
const mindos = agents.find(a => a.key === 'mindos');
|
|
103
104
|
if (mindos) enrichMindOsAgent(mindos as unknown as Record<string, unknown>);
|
|
104
105
|
|
|
106
|
+
// Runtime verification: for agents marked as installed with HTTP endpoint,
|
|
107
|
+
// verify endpoint is reachable (1s timeout to avoid blocking)
|
|
108
|
+
await Promise.all(agents.map(async (agent) => {
|
|
109
|
+
if (agent.installed && agent.url && agent.transport?.startsWith('http')) {
|
|
110
|
+
try {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeout = setTimeout(() => controller.abort(), 1000);
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(agent.url, { method: 'HEAD', signal: controller.signal });
|
|
115
|
+
// Accept 200-299 or 405 (HEAD not allowed). Others = unreachable
|
|
116
|
+
if (response.status >= 300 && response.status !== 405) {
|
|
117
|
+
agent.installed = false;
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Timeout, network error, or abort — mark as not installed (false positive prevention)
|
|
124
|
+
agent.installed = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
|
|
105
129
|
// Sort: mindos first, then installed, then detected, then not found
|
|
106
130
|
agents.sort((a, b) => {
|
|
107
131
|
if (a.key === 'mindos') return -1;
|
|
@@ -345,9 +345,10 @@ function DirectoryNode({ node, depth, currentPath, onNavigate, maxOpenDepth, onI
|
|
|
345
345
|
}, [renaming, router, node.path, onNavigate]);
|
|
346
346
|
|
|
347
347
|
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
e.stopPropagation();
|
|
349
|
+
// Double-click to toggle expand/collapse
|
|
350
|
+
toggle();
|
|
351
|
+
}, [toggle]);
|
|
351
352
|
|
|
352
353
|
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
|
353
354
|
e.preventDefault();
|
|
@@ -306,6 +306,7 @@ export default function AgentsSkillsSection({
|
|
|
306
306
|
onToggle={mcp.toggleSkill}
|
|
307
307
|
onDelete={handleDeleteFromPopover}
|
|
308
308
|
onRefresh={mcp.refresh}
|
|
309
|
+
allAgentNames={mcp.agents.filter(a => a.present && a.installed).map(a => a.name)}
|
|
309
310
|
/>
|
|
310
311
|
</section>
|
|
311
312
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
BookOpen,
|
|
6
6
|
Code2,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Check,
|
|
9
9
|
FileText,
|
|
10
10
|
Loader2,
|
|
11
|
+
Plus,
|
|
11
12
|
Zap,
|
|
12
13
|
Search,
|
|
13
14
|
Server,
|
|
@@ -58,6 +59,7 @@ interface SkillDetailPopoverProps {
|
|
|
58
59
|
skillName: string | null;
|
|
59
60
|
skill?: SkillInfo | null;
|
|
60
61
|
agentNames?: string[];
|
|
62
|
+
allAgentNames?: string[];
|
|
61
63
|
isNative?: boolean;
|
|
62
64
|
nativeSourcePath?: string;
|
|
63
65
|
copy: SkillDetailPopoverCopy;
|
|
@@ -65,6 +67,8 @@ interface SkillDetailPopoverProps {
|
|
|
65
67
|
onToggle?: (name: string, enabled: boolean) => Promise<boolean>;
|
|
66
68
|
onDelete?: (name: string) => Promise<void>;
|
|
67
69
|
onRefresh?: () => Promise<void>;
|
|
70
|
+
onAddAgent?: (skillName: string, agentName: string) => void;
|
|
71
|
+
onRemoveAgent?: (skillName: string, agentName: string) => void;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
/* ────────── Capability Icon ────────── */
|
|
@@ -77,6 +81,90 @@ const CAPABILITY_ICONS: Record<SkillCapability, React.ComponentType<{ size?: num
|
|
|
77
81
|
memory: BookOpen,
|
|
78
82
|
};
|
|
79
83
|
|
|
84
|
+
/* ────────── Content Parser ────────── */
|
|
85
|
+
|
|
86
|
+
interface ParsedContent {
|
|
87
|
+
triggerConditions: string;
|
|
88
|
+
instructions: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseSkillContent(raw: string, description: string): ParsedContent {
|
|
92
|
+
// Strip YAML frontmatter
|
|
93
|
+
let body = raw;
|
|
94
|
+
const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n?/);
|
|
95
|
+
if (fmMatch) body = raw.slice(fmMatch[0].length).trim();
|
|
96
|
+
|
|
97
|
+
// Extract trigger conditions from description
|
|
98
|
+
// The description field usually contains trigger/usage info
|
|
99
|
+
const triggerConditions = description || '';
|
|
100
|
+
|
|
101
|
+
return { triggerConditions, instructions: body };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* ────────── Simple Markdown Renderer ────────── */
|
|
105
|
+
|
|
106
|
+
function MarkdownContent({ text, className = '' }: { text: string; className?: string }) {
|
|
107
|
+
const html = useMemo(() => renderMarkdown(text), [text]);
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={`prose prose-sm prose-invert max-w-none
|
|
111
|
+
prose-headings:text-foreground prose-headings:font-semibold prose-headings:mt-4 prose-headings:mb-2
|
|
112
|
+
prose-h1:text-sm prose-h2:text-xs prose-h3:text-xs
|
|
113
|
+
prose-p:text-xs prose-p:text-foreground/80 prose-p:leading-relaxed prose-p:my-1.5
|
|
114
|
+
prose-li:text-xs prose-li:text-foreground/80 prose-li:my-0.5
|
|
115
|
+
prose-strong:text-foreground prose-strong:font-semibold
|
|
116
|
+
prose-code:text-[var(--amber)] prose-code:bg-muted/50 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-2xs prose-code:font-mono
|
|
117
|
+
prose-pre:bg-muted/30 prose-pre:border prose-pre:border-border/50 prose-pre:rounded-lg prose-pre:p-3 prose-pre:text-2xs prose-pre:overflow-x-auto
|
|
118
|
+
prose-ul:my-1 prose-ol:my-1
|
|
119
|
+
prose-hr:border-border/30 prose-hr:my-3
|
|
120
|
+
${className}`}
|
|
121
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Minimal markdown to HTML — no external deps */
|
|
127
|
+
function renderMarkdown(md: string): string {
|
|
128
|
+
let html = md
|
|
129
|
+
// Code blocks (fenced)
|
|
130
|
+
.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) =>
|
|
131
|
+
`<pre><code>${escHtml(code.trimEnd())}</code></pre>`)
|
|
132
|
+
// Inline code
|
|
133
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
134
|
+
// Headers
|
|
135
|
+
.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
|
|
136
|
+
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
|
137
|
+
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
138
|
+
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
139
|
+
// Bold
|
|
140
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
141
|
+
// Italic
|
|
142
|
+
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
143
|
+
// HR
|
|
144
|
+
.replace(/^---$/gm, '<hr/>')
|
|
145
|
+
// List items
|
|
146
|
+
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
|
147
|
+
.replace(/^\* (.+)$/gm, '<li>$1</li>')
|
|
148
|
+
.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
|
|
149
|
+
|
|
150
|
+
// Wrap consecutive <li> in <ul>
|
|
151
|
+
html = html.replace(/((?:<li>.*<\/li>\n?)+)/g, '<ul>$1</ul>');
|
|
152
|
+
|
|
153
|
+
// Paragraphs: lines not wrapped in a block tag
|
|
154
|
+
html = html.split('\n').map(line => {
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
if (!trimmed) return '';
|
|
157
|
+
if (/^<(h[1-4]|ul|ol|li|pre|hr|div|p|blockquote)/.test(trimmed)) return line;
|
|
158
|
+
return `<p>${trimmed}</p>`;
|
|
159
|
+
}).join('\n');
|
|
160
|
+
|
|
161
|
+
return html;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function escHtml(s: string): string {
|
|
165
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
166
|
+
}
|
|
167
|
+
|
|
80
168
|
/* ────────── Component ────────── */
|
|
81
169
|
|
|
82
170
|
export default function SkillDetailPopover({
|
|
@@ -84,6 +172,7 @@ export default function SkillDetailPopover({
|
|
|
84
172
|
skillName,
|
|
85
173
|
skill,
|
|
86
174
|
agentNames = [],
|
|
175
|
+
allAgentNames = [],
|
|
87
176
|
isNative = false,
|
|
88
177
|
nativeSourcePath,
|
|
89
178
|
copy,
|
|
@@ -91,6 +180,8 @@ export default function SkillDetailPopover({
|
|
|
91
180
|
onToggle,
|
|
92
181
|
onDelete,
|
|
93
182
|
onRefresh,
|
|
183
|
+
onAddAgent,
|
|
184
|
+
onRemoveAgent,
|
|
94
185
|
}: SkillDetailPopoverProps) {
|
|
95
186
|
const [content, setContent] = useState<string | null>(null);
|
|
96
187
|
const [nativeDesc, setNativeDesc] = useState<string>('');
|
|
@@ -101,7 +192,8 @@ export default function SkillDetailPopover({
|
|
|
101
192
|
const [deleting, setDeleting] = useState(false);
|
|
102
193
|
const [deleteMsg, setDeleteMsg] = useState<string | null>(null);
|
|
103
194
|
const [toggleBusy, setToggleBusy] = useState(false);
|
|
104
|
-
const [
|
|
195
|
+
const [showAddAgent, setShowAddAgent] = useState(false);
|
|
196
|
+
const [contentExpanded, setContentExpanded] = useState(false);
|
|
105
197
|
|
|
106
198
|
const fetchContent = useCallback(async () => {
|
|
107
199
|
if (!skillName) return;
|
|
@@ -140,6 +232,8 @@ export default function SkillDetailPopover({
|
|
|
140
232
|
setDeleteMsg(null);
|
|
141
233
|
setDeleting(false);
|
|
142
234
|
setToggleBusy(false);
|
|
235
|
+
setShowAddAgent(false);
|
|
236
|
+
setContentExpanded(false);
|
|
143
237
|
fetchContent();
|
|
144
238
|
}
|
|
145
239
|
}, [open, skillName, fetchContent]);
|
|
@@ -202,6 +296,12 @@ export default function SkillDetailPopover({
|
|
|
202
296
|
const description = skill?.description || nativeDesc || '';
|
|
203
297
|
const skillPath = skill?.path || (isNative && nativeSourcePath ? `${nativeSourcePath}/${skillName}/SKILL.md` : '');
|
|
204
298
|
|
|
299
|
+
// Parse content into structured sections
|
|
300
|
+
const parsed = content ? parseSkillContent(content, description) : null;
|
|
301
|
+
|
|
302
|
+
// Available agents to add (not already assigned)
|
|
303
|
+
const availableAgents = allAgentNames.filter(a => !agentNames.includes(a));
|
|
304
|
+
|
|
205
305
|
return (
|
|
206
306
|
<>
|
|
207
307
|
{/* Backdrop */}
|
|
@@ -250,78 +350,103 @@ export default function SkillDetailPopover({
|
|
|
250
350
|
|
|
251
351
|
{/* ─── Body (scrollable) ─── */}
|
|
252
352
|
<div className="flex-1 overflow-y-auto px-5 py-4 space-y-5">
|
|
253
|
-
{/* Description */}
|
|
254
|
-
{description ? (
|
|
255
|
-
<div className="space-y-2">
|
|
256
|
-
<p className={`text-sm text-foreground leading-relaxed whitespace-pre-wrap ${!descriptionExpanded ? 'line-clamp-3' : ''}`}>
|
|
257
|
-
{description}
|
|
258
|
-
</p>
|
|
259
|
-
{description.split('\n').length > 3 && (
|
|
260
|
-
<button
|
|
261
|
-
type="button"
|
|
262
|
-
onClick={() => setDescriptionExpanded(!descriptionExpanded)}
|
|
263
|
-
className="inline-flex items-center gap-1 text-2xs font-medium text-[var(--amber)] hover:opacity-80 transition-opacity cursor-pointer"
|
|
264
|
-
>
|
|
265
|
-
<ChevronDown size={12} className={`transition-transform duration-200 ${descriptionExpanded ? 'rotate-180' : ''}`} />
|
|
266
|
-
<span>{descriptionExpanded ? '收起' : '查看全部'}</span>
|
|
267
|
-
</button>
|
|
268
|
-
)}
|
|
269
|
-
</div>
|
|
270
|
-
) : !isNative ? (
|
|
271
|
-
<p className="text-sm text-muted-foreground italic">{copy.noDescription}</p>
|
|
272
|
-
) : null}
|
|
273
|
-
|
|
274
|
-
{/* Quick meta */}
|
|
275
|
-
<div className="grid grid-cols-2 gap-3">
|
|
276
|
-
{!isNative && skill && (
|
|
277
|
-
<MetaCard label={copy.enabled} value={skill.enabled ? '✓' : '—'} tone={skill.enabled ? 'ok' : 'muted'} />
|
|
278
|
-
)}
|
|
279
|
-
<MetaCard label={copy.capability} value={capability} />
|
|
280
|
-
<MetaCard label={copy.source} value={sourceLabel} />
|
|
281
|
-
<MetaCard label={copy.agents} value={String(agentNames.length)} />
|
|
282
|
-
</div>
|
|
283
353
|
|
|
284
|
-
{/*
|
|
285
|
-
{
|
|
286
|
-
<
|
|
287
|
-
<
|
|
288
|
-
<
|
|
289
|
-
|
|
354
|
+
{/* ── Section: Trigger Conditions ── */}
|
|
355
|
+
{description && (
|
|
356
|
+
<section>
|
|
357
|
+
<SectionTitle label="Trigger Conditions" />
|
|
358
|
+
<div className="rounded-lg border border-border/40 bg-muted/[0.03] p-3.5">
|
|
359
|
+
<p className="text-xs text-foreground/80 leading-relaxed whitespace-pre-wrap">
|
|
360
|
+
{description}
|
|
361
|
+
</p>
|
|
362
|
+
</div>
|
|
363
|
+
</section>
|
|
364
|
+
)}
|
|
365
|
+
{!description && !isNative && (
|
|
366
|
+
<p className="text-sm text-muted-foreground italic">{copy.noDescription}</p>
|
|
290
367
|
)}
|
|
291
368
|
|
|
292
|
-
{/*
|
|
293
|
-
|
|
294
|
-
<div>
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
369
|
+
{/* ── Section: Quick Meta ── */}
|
|
370
|
+
<section>
|
|
371
|
+
<div className="grid grid-cols-2 gap-3">
|
|
372
|
+
{!isNative && skill && (
|
|
373
|
+
<MetaCard label={copy.enabled} value={skill.enabled ? '✓' : '—'} tone={skill.enabled ? 'ok' : 'muted'} />
|
|
374
|
+
)}
|
|
375
|
+
<MetaCard label={copy.capability} value={capability} />
|
|
376
|
+
<MetaCard label={copy.source} value={sourceLabel} />
|
|
377
|
+
<MetaCard label={copy.agents} value={String(agentNames.length)} />
|
|
301
378
|
</div>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
379
|
+
</section>
|
|
380
|
+
|
|
381
|
+
{/* ── Section: Connected Agents ── */}
|
|
382
|
+
<section>
|
|
383
|
+
<div className="flex items-center justify-between mb-2">
|
|
384
|
+
<SectionTitle label={copy.agents} noMargin />
|
|
385
|
+
{onAddAgent && availableAgents.length > 0 && (
|
|
386
|
+
<button
|
|
387
|
+
type="button"
|
|
388
|
+
onClick={() => setShowAddAgent(!showAddAgent)}
|
|
389
|
+
className="inline-flex items-center gap-1 text-2xs font-medium text-[var(--amber)] hover:opacity-80 transition-opacity cursor-pointer"
|
|
390
|
+
>
|
|
391
|
+
<Plus size={12} />
|
|
392
|
+
<span>Add</span>
|
|
393
|
+
</button>
|
|
394
|
+
)}
|
|
306
395
|
</div>
|
|
307
|
-
)}
|
|
308
396
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
<h3 className="text-xs font-medium text-muted-foreground">{copy.content}</h3>
|
|
314
|
-
{content && (
|
|
397
|
+
{/* Add agent picker */}
|
|
398
|
+
{showAddAgent && availableAgents.length > 0 && (
|
|
399
|
+
<div className="flex flex-wrap gap-1.5 mb-2 p-2 rounded-lg border border-dashed border-[var(--amber)]/30 bg-[var(--amber)]/[0.03]">
|
|
400
|
+
{availableAgents.map((name) => (
|
|
315
401
|
<button
|
|
402
|
+
key={name}
|
|
316
403
|
type="button"
|
|
317
|
-
onClick={
|
|
318
|
-
className="inline-flex items-center gap-1
|
|
319
|
-
aria-label={copy.copyContent}
|
|
404
|
+
onClick={() => { onAddAgent?.(skillName, name); setShowAddAgent(false); }}
|
|
405
|
+
className="inline-flex items-center gap-1 px-2 py-1 text-2xs rounded-md border border-border bg-card hover:bg-muted cursor-pointer transition-colors"
|
|
320
406
|
>
|
|
321
|
-
|
|
322
|
-
{
|
|
407
|
+
<Plus size={10} className="text-[var(--amber)]" />
|
|
408
|
+
<span className="text-foreground/80">{name}</span>
|
|
323
409
|
</button>
|
|
324
|
-
)}
|
|
410
|
+
))}
|
|
411
|
+
</div>
|
|
412
|
+
)}
|
|
413
|
+
|
|
414
|
+
{agentNames.length > 0 ? (
|
|
415
|
+
<div className="flex flex-wrap gap-2">
|
|
416
|
+
{agentNames.map((name) => (
|
|
417
|
+
<AgentAvatar
|
|
418
|
+
key={name}
|
|
419
|
+
name={name}
|
|
420
|
+
size="sm"
|
|
421
|
+
onRemove={onRemoveAgent ? () => onRemoveAgent(skillName, name) : undefined}
|
|
422
|
+
/>
|
|
423
|
+
))}
|
|
424
|
+
</div>
|
|
425
|
+
) : (
|
|
426
|
+
<div className="rounded-lg border border-dashed border-border p-3 text-center">
|
|
427
|
+
<p className="text-xs text-muted-foreground">{copy.noAgents}</p>
|
|
428
|
+
</div>
|
|
429
|
+
)}
|
|
430
|
+
</section>
|
|
431
|
+
|
|
432
|
+
{/* ── Section: Skill Instructions (markdown) ── */}
|
|
433
|
+
{(content !== null || loading || loadError) && (
|
|
434
|
+
<section>
|
|
435
|
+
<div className="flex items-center justify-between mb-2">
|
|
436
|
+
<SectionTitle label="Instructions" noMargin />
|
|
437
|
+
<div className="flex items-center gap-2">
|
|
438
|
+
{content && (
|
|
439
|
+
<button
|
|
440
|
+
type="button"
|
|
441
|
+
onClick={handleCopy}
|
|
442
|
+
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"
|
|
443
|
+
aria-label={copy.copyContent}
|
|
444
|
+
>
|
|
445
|
+
{copied ? <Check size={11} /> : <Copy size={11} />}
|
|
446
|
+
{copied ? copy.copied : copy.copyContent}
|
|
447
|
+
</button>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
325
450
|
</div>
|
|
326
451
|
|
|
327
452
|
{loading && (
|
|
@@ -344,12 +469,32 @@ export default function SkillDetailPopover({
|
|
|
344
469
|
</div>
|
|
345
470
|
)}
|
|
346
471
|
|
|
347
|
-
{
|
|
348
|
-
<
|
|
349
|
-
{
|
|
350
|
-
</
|
|
472
|
+
{parsed && !loading && !loadError && (
|
|
473
|
+
<div className={`rounded-lg border border-border/50 bg-muted/[0.02] p-4 overflow-hidden transition-all duration-200 ${!contentExpanded ? 'max-h-60' : ''}`}>
|
|
474
|
+
<MarkdownContent text={parsed.instructions} />
|
|
475
|
+
</div>
|
|
351
476
|
)}
|
|
352
|
-
|
|
477
|
+
{parsed && !loading && !loadError && parsed.instructions.split('\n').length > 15 && (
|
|
478
|
+
<button
|
|
479
|
+
type="button"
|
|
480
|
+
onClick={() => setContentExpanded(!contentExpanded)}
|
|
481
|
+
className="inline-flex items-center gap-1 mt-2 text-2xs font-medium text-[var(--amber)] hover:opacity-80 transition-opacity cursor-pointer"
|
|
482
|
+
>
|
|
483
|
+
<ChevronDown size={12} className={`transition-transform duration-200 ${contentExpanded ? 'rotate-180' : ''}`} />
|
|
484
|
+
<span>{contentExpanded ? 'Collapse' : 'View All'}</span>
|
|
485
|
+
</button>
|
|
486
|
+
)}
|
|
487
|
+
</section>
|
|
488
|
+
)}
|
|
489
|
+
|
|
490
|
+
{/* ── Section: File Path ── */}
|
|
491
|
+
{skillPath && (
|
|
492
|
+
<section>
|
|
493
|
+
<SectionTitle label={copy.path} />
|
|
494
|
+
<div className="rounded-lg border border-border/50 bg-muted/[0.03] p-3">
|
|
495
|
+
<code className="text-2xs text-foreground/60 font-mono break-all leading-relaxed">{skillPath}</code>
|
|
496
|
+
</div>
|
|
497
|
+
</section>
|
|
353
498
|
)}
|
|
354
499
|
|
|
355
500
|
{/* Delete message */}
|
|
@@ -410,6 +555,16 @@ export default function SkillDetailPopover({
|
|
|
410
555
|
);
|
|
411
556
|
}
|
|
412
557
|
|
|
558
|
+
/* ────────── Section Title ────────── */
|
|
559
|
+
|
|
560
|
+
function SectionTitle({ label, noMargin }: { label: string; noMargin?: boolean }) {
|
|
561
|
+
return (
|
|
562
|
+
<h3 className={`text-xs font-semibold text-muted-foreground uppercase tracking-wider ${noMargin ? '' : 'mb-2'}`}>
|
|
563
|
+
{label}
|
|
564
|
+
</h3>
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
413
568
|
/* ────────── Meta Card ────────── */
|
|
414
569
|
|
|
415
570
|
function MetaCard({
|
package/app/lib/mcp-agents.ts
CHANGED
|
@@ -494,7 +494,7 @@ export function detectAgentRuntimeSignals(agentKey: string): AgentRuntimeSignals
|
|
|
494
494
|
|
|
495
495
|
/* ── MindOS MCP Install Detection ──────────────────────────────────────── */
|
|
496
496
|
|
|
497
|
-
export function detectInstalled(agentKey: string): { installed: boolean; scope?: string; transport?: string; configPath?: string } {
|
|
497
|
+
export function detectInstalled(agentKey: string): { installed: boolean; scope?: string; transport?: string; configPath?: string; url?: string } {
|
|
498
498
|
const agent = MCP_AGENTS[agentKey];
|
|
499
499
|
if (!agent) return { installed: false };
|
|
500
500
|
|
|
@@ -510,7 +510,7 @@ export function detectInstalled(agentKey: string): { installed: boolean; scope?:
|
|
|
510
510
|
if (result.found && result.entry) {
|
|
511
511
|
const entry = result.entry;
|
|
512
512
|
const transport = entry.type === 'stdio' ? 'stdio' : entry.url ? 'http' : 'unknown';
|
|
513
|
-
return { installed: true, scope: scopeType, transport, configPath: cfgPath };
|
|
513
|
+
return { installed: true, scope: scopeType, transport, configPath: cfgPath, url: entry.url };
|
|
514
514
|
}
|
|
515
515
|
} else {
|
|
516
516
|
// JSON format (default)
|
|
@@ -521,7 +521,7 @@ export function detectInstalled(agentKey: string): { installed: boolean; scope?:
|
|
|
521
521
|
if (servers?.mindos) {
|
|
522
522
|
const entry = servers.mindos as Record<string, unknown>;
|
|
523
523
|
const transport = entry.type === 'stdio' ? 'stdio' : entry.url ? 'http' : 'unknown';
|
|
524
|
-
return { installed: true, scope: scopeType, transport, configPath: cfgPath };
|
|
524
|
+
return { installed: true, scope: scopeType, transport, configPath: cfgPath, url: entry.url as string | undefined };
|
|
525
525
|
}
|
|
526
526
|
}
|
|
527
527
|
} catch { /* ignore parse errors */ }
|
package/package.json
CHANGED