@geminilight/mindos 0.5.63 → 0.5.64
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.
- package/app/app/api/changes/route.ts +7 -1
- package/app/app/api/mcp/install-skill/route.ts +9 -24
- package/app/app/layout.tsx +1 -0
- package/app/app/page.tsx +1 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +0 -1
- package/app/components/HomeContent.tsx +41 -6
- package/app/components/RightAgentDetailPanel.tsx +1 -0
- package/app/components/SidebarLayout.tsx +1 -0
- package/app/components/agents/AgentsContentPage.tsx +20 -16
- package/app/components/agents/AgentsMcpSection.tsx +178 -65
- package/app/components/agents/AgentsOverviewSection.tsx +1 -1
- package/app/components/agents/AgentsSkillsSection.tsx +78 -55
- package/app/components/agents/agents-content-model.ts +16 -0
- package/app/components/changes/ChangesBanner.tsx +90 -13
- package/app/components/changes/ChangesContentPage.tsx +134 -51
- package/app/components/panels/AgentsPanel.tsx +14 -28
- package/app/components/panels/AgentsPanelAgentDetail.tsx +5 -4
- package/app/components/panels/AgentsPanelAgentGroups.tsx +5 -6
- package/app/components/panels/AgentsPanelAgentListRow.tsx +30 -5
- package/app/components/panels/AgentsPanelHubNav.tsx +12 -12
- package/app/components/panels/PluginsPanel.tsx +3 -3
- package/app/components/renderers/agent-inspector/manifest.ts +2 -0
- package/app/components/renderers/config/manifest.ts +1 -0
- package/app/components/renderers/csv/manifest.ts +1 -0
- package/app/components/settings/PluginsTab.tsx +4 -3
- package/app/hooks/useMcpData.tsx +3 -2
- package/app/lib/core/content-changes.ts +148 -8
- package/app/lib/fs.ts +7 -1
- package/app/lib/i18n-en.ts +58 -3
- package/app/lib/i18n-zh.ts +58 -3
- package/app/lib/mcp-agents.ts +42 -0
- package/app/lib/renderers/index.ts +1 -2
- package/app/lib/renderers/registry.ts +10 -0
- package/app/next-env.d.ts +1 -1
- package/bin/lib/mcp-agents.js +38 -13
- package/package.json +1 -1
- package/scripts/migrate-agent-diff.js +146 -0
- package/scripts/setup.js +12 -17
- package/skills/plugin-core-builtin-migration/SKILL.md +178 -0
- package/app/components/renderers/diff/DiffRenderer.tsx +0 -311
- package/app/components/renderers/diff/manifest.ts +0 -14
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
3
|
+
import { useState } from 'react';
|
|
5
4
|
import { Loader2, RefreshCw, ChevronDown, ChevronRight, Settings } from 'lucide-react';
|
|
6
5
|
import { useMcpData } from '@/hooks/useMcpData';
|
|
7
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
@@ -28,26 +27,18 @@ export default function AgentsPanel({
|
|
|
28
27
|
onOpenAgentDetail,
|
|
29
28
|
}: AgentsPanelProps) {
|
|
30
29
|
const { t } = useLocale();
|
|
31
|
-
const router = useRouter();
|
|
32
30
|
const p = t.panels.agents;
|
|
33
31
|
const mcp = useMcpData();
|
|
34
32
|
const [refreshing, setRefreshing] = useState(false);
|
|
35
33
|
const [showNotDetected, setShowNotDetected] = useState(false);
|
|
36
34
|
const [showBuiltinSkills, setShowBuiltinSkills] = useState(false);
|
|
37
35
|
|
|
38
|
-
const overviewRef = useRef<HTMLDivElement>(null);
|
|
39
|
-
const skillsRef = useRef<HTMLDivElement>(null);
|
|
40
|
-
|
|
41
36
|
const handleRefresh = async () => {
|
|
42
37
|
setRefreshing(true);
|
|
43
38
|
await mcp.refresh();
|
|
44
39
|
setRefreshing(false);
|
|
45
40
|
};
|
|
46
41
|
|
|
47
|
-
const scrollTo = useCallback((el: HTMLElement | null) => {
|
|
48
|
-
el?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
42
|
const openAdvancedConfig = () => {
|
|
52
43
|
window.dispatchEvent(new CustomEvent('mindos:open-settings', { detail: { tab: 'mcp' } }));
|
|
53
44
|
};
|
|
@@ -59,10 +50,18 @@ export default function AgentsPanel({
|
|
|
59
50
|
const customSkills = mcp.skills.filter(s => s.source === 'user');
|
|
60
51
|
const builtinSkills = mcp.skills.filter(s => s.source === 'builtin');
|
|
61
52
|
const activeSkillCount = mcp.skills.filter(s => s.enabled).length;
|
|
53
|
+
const installAgentWithRefresh = async (key: string) => {
|
|
54
|
+
const ok = await mcp.installAgent(key);
|
|
55
|
+
if (ok) await mcp.refresh();
|
|
56
|
+
return ok;
|
|
57
|
+
};
|
|
62
58
|
|
|
63
59
|
const listCopy = {
|
|
64
60
|
installing: p.installing,
|
|
65
61
|
install: p.install,
|
|
62
|
+
installSuccess: p.installSuccess,
|
|
63
|
+
installFailed: p.installFailed,
|
|
64
|
+
retryInstall: p.retryInstall,
|
|
66
65
|
};
|
|
67
66
|
|
|
68
67
|
const hubCopy = {
|
|
@@ -75,10 +74,6 @@ export default function AgentsPanel({
|
|
|
75
74
|
<AgentsPanelHubNav
|
|
76
75
|
copy={hubCopy}
|
|
77
76
|
connectedCount={connected.length}
|
|
78
|
-
overviewRef={overviewRef}
|
|
79
|
-
skillsRef={skillsRef}
|
|
80
|
-
scrollTo={scrollTo}
|
|
81
|
-
openAdvancedConfig={openAdvancedConfig}
|
|
82
77
|
/>
|
|
83
78
|
);
|
|
84
79
|
|
|
@@ -91,15 +86,6 @@ export default function AgentsPanel({
|
|
|
91
86
|
{connected.length} {p.connected}
|
|
92
87
|
</span>
|
|
93
88
|
)}
|
|
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
89
|
<button
|
|
104
90
|
onClick={handleRefresh}
|
|
105
91
|
disabled={refreshing}
|
|
@@ -122,7 +108,7 @@ export default function AgentsPanel({
|
|
|
122
108
|
<div className="flex flex-col gap-2 py-4 px-0">
|
|
123
109
|
{hub}
|
|
124
110
|
<div className="mx-4 border-t border-border" />
|
|
125
|
-
<div
|
|
111
|
+
<div className="mx-3 rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between">
|
|
126
112
|
<span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
|
|
127
113
|
{mcp.status?.running ? (
|
|
128
114
|
<span className="flex items-center gap-1.5 text-[11px]">
|
|
@@ -137,7 +123,7 @@ export default function AgentsPanel({
|
|
|
137
123
|
</span>
|
|
138
124
|
)}
|
|
139
125
|
</div>
|
|
140
|
-
<div
|
|
126
|
+
<div className="mx-3 rounded-lg border border-dashed border-border px-3 py-3 text-center">
|
|
141
127
|
<p className="text-xs text-muted-foreground mb-2">{p.noAgents}</p>
|
|
142
128
|
<p className="text-2xs text-muted-foreground mb-3">{p.skillsEmptyHint}</p>
|
|
143
129
|
<button
|
|
@@ -156,7 +142,7 @@ export default function AgentsPanel({
|
|
|
156
142
|
<div className="mx-4 border-t border-border" />
|
|
157
143
|
|
|
158
144
|
<div className="px-3 py-3 space-y-4">
|
|
159
|
-
<div
|
|
145
|
+
<div className="rounded-lg border border-border bg-card/50 px-3 py-2.5 flex items-center justify-between">
|
|
160
146
|
<span className="text-xs font-medium text-foreground">{p.mcpServer}</span>
|
|
161
147
|
{mcp.status?.running ? (
|
|
162
148
|
<span className="flex items-center gap-1.5 text-[11px]">
|
|
@@ -178,8 +164,8 @@ export default function AgentsPanel({
|
|
|
178
164
|
notFound={notFound}
|
|
179
165
|
onOpenDetail={onOpenAgentDetail}
|
|
180
166
|
selectedAgentKey={selectedAgentKey}
|
|
181
|
-
mcp={mcp}
|
|
182
167
|
listCopy={listCopy}
|
|
168
|
+
onInstallAgent={installAgentWithRefresh}
|
|
183
169
|
showNotDetected={showNotDetected}
|
|
184
170
|
setShowNotDetected={setShowNotDetected}
|
|
185
171
|
p={{
|
|
@@ -190,7 +176,7 @@ export default function AgentsPanel({
|
|
|
190
176
|
}}
|
|
191
177
|
/>
|
|
192
178
|
|
|
193
|
-
<section
|
|
179
|
+
<section>
|
|
194
180
|
{mcp.skills.length > 0 ? (
|
|
195
181
|
<>
|
|
196
182
|
<div className="flex items-center justify-between mb-2">
|
|
@@ -12,7 +12,8 @@ export type { AgentsPanelAgentDetailStatus };
|
|
|
12
12
|
export interface AgentsPanelAgentDetailCopy {
|
|
13
13
|
connected: string;
|
|
14
14
|
installing: string;
|
|
15
|
-
install:
|
|
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:
|
|
65
|
+
: { type: 'error', text: copy.installFailed },
|
|
65
66
|
);
|
|
66
67
|
setInstalling(false);
|
|
67
68
|
};
|
|
@@ -117,10 +118,10 @@ 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
|
|
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
|
|
124
|
+
{installing ? copy.installing : copy.install}
|
|
124
125
|
</button>
|
|
125
126
|
{result && (
|
|
126
127
|
<span
|
|
@@ -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 = {
|
|
@@ -18,7 +17,7 @@ export function AgentsPanelAgentGroups({
|
|
|
18
17
|
notFound,
|
|
19
18
|
onOpenDetail,
|
|
20
19
|
selectedAgentKey,
|
|
21
|
-
|
|
20
|
+
onInstallAgent,
|
|
22
21
|
listCopy,
|
|
23
22
|
showNotDetected,
|
|
24
23
|
setShowNotDetected,
|
|
@@ -29,7 +28,7 @@ export function AgentsPanelAgentGroups({
|
|
|
29
28
|
notFound: AgentInfo[];
|
|
30
29
|
onOpenDetail?: (key: string) => void;
|
|
31
30
|
selectedAgentKey?: string | null;
|
|
32
|
-
|
|
31
|
+
onInstallAgent: (key: string) => Promise<boolean>;
|
|
33
32
|
listCopy: AgentsPanelAgentListRowCopy;
|
|
34
33
|
showNotDetected: boolean;
|
|
35
34
|
setShowNotDetected: (v: boolean | ((prev: boolean) => boolean)) => void;
|
|
@@ -55,7 +54,7 @@ export function AgentsPanelAgentGroups({
|
|
|
55
54
|
agentStatus="connected"
|
|
56
55
|
selected={selectedAgentKey === agent.key}
|
|
57
56
|
onOpenDetail={() => open(agent.key)}
|
|
58
|
-
onInstallAgent={
|
|
57
|
+
onInstallAgent={onInstallAgent}
|
|
59
58
|
copy={listCopy}
|
|
60
59
|
/>
|
|
61
60
|
))}
|
|
@@ -76,7 +75,7 @@ export function AgentsPanelAgentGroups({
|
|
|
76
75
|
agentStatus="detected"
|
|
77
76
|
selected={selectedAgentKey === agent.key}
|
|
78
77
|
onOpenDetail={() => open(agent.key)}
|
|
79
|
-
onInstallAgent={
|
|
78
|
+
onInstallAgent={onInstallAgent}
|
|
80
79
|
copy={listCopy}
|
|
81
80
|
/>
|
|
82
81
|
))}
|
|
@@ -103,7 +102,7 @@ export function AgentsPanelAgentGroups({
|
|
|
103
102
|
agentStatus="notFound"
|
|
104
103
|
selected={selectedAgentKey === agent.key}
|
|
105
104
|
onOpenDetail={() => open(agent.key)}
|
|
106
|
-
onInstallAgent={
|
|
105
|
+
onInstallAgent={onInstallAgent}
|
|
107
106
|
copy={listCopy}
|
|
108
107
|
/>
|
|
109
108
|
))}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { ChevronRight, Loader2 } from 'lucide-react';
|
|
4
|
+
import { Check, ChevronRight, Loader2, RotateCw } from 'lucide-react';
|
|
5
5
|
import type { AgentInfo } from '../settings/types';
|
|
6
6
|
|
|
7
7
|
export type AgentsPanelAgentListStatus = 'connected' | 'detected' | 'notFound';
|
|
8
8
|
|
|
9
9
|
export interface AgentsPanelAgentListRowCopy {
|
|
10
10
|
installing: string;
|
|
11
|
-
install:
|
|
11
|
+
install: string;
|
|
12
|
+
installSuccess: string;
|
|
13
|
+
installFailed: string;
|
|
14
|
+
retryInstall: string;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export default function AgentsPanelAgentListRow({
|
|
@@ -79,23 +82,45 @@ function AgentInstallButton({
|
|
|
79
82
|
copy: AgentsPanelAgentListRowCopy;
|
|
80
83
|
}) {
|
|
81
84
|
const [installing, setInstalling] = useState(false);
|
|
85
|
+
const [installState, setInstallState] = useState<'idle' | 'success' | 'error'>('idle');
|
|
82
86
|
|
|
83
87
|
const handleInstall = async (e: React.MouseEvent) => {
|
|
84
88
|
e.stopPropagation();
|
|
89
|
+
if (installing) return;
|
|
85
90
|
setInstalling(true);
|
|
86
|
-
|
|
91
|
+
setInstallState('idle');
|
|
92
|
+
const ok = await onInstallAgent(agentKey);
|
|
87
93
|
setInstalling(false);
|
|
94
|
+
setInstallState(ok ? 'success' : 'error');
|
|
88
95
|
};
|
|
89
96
|
|
|
97
|
+
const isError = installState === 'error';
|
|
98
|
+
const isSuccess = installState === 'success';
|
|
99
|
+
const label = installing
|
|
100
|
+
? copy.installing
|
|
101
|
+
: isSuccess
|
|
102
|
+
? copy.installSuccess
|
|
103
|
+
: isError
|
|
104
|
+
? copy.retryInstall
|
|
105
|
+
: copy.install;
|
|
106
|
+
|
|
90
107
|
return (
|
|
91
108
|
<button
|
|
92
109
|
type="button"
|
|
93
110
|
onClick={handleInstall}
|
|
94
111
|
disabled={installing}
|
|
95
|
-
className=
|
|
112
|
+
className={`flex items-center gap-1 px-2 py-1.5 text-2xs rounded-lg font-medium text-white disabled:opacity-50 transition-colors shrink-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
|
113
|
+
isError ? 'bg-error hover:bg-error/90' : isSuccess ? 'bg-success hover:bg-success/90' : 'bg-[var(--amber)] hover:bg-[var(--amber)]/90'
|
|
114
|
+
}`}
|
|
115
|
+
aria-label={`${agentName} ${label}`}
|
|
96
116
|
>
|
|
97
117
|
{installing ? <Loader2 size={10} className="animate-spin" /> : null}
|
|
98
|
-
{installing ?
|
|
118
|
+
{!installing && isSuccess ? <Check size={10} /> : null}
|
|
119
|
+
{!installing && isError ? <RotateCw size={10} /> : null}
|
|
120
|
+
{label}
|
|
121
|
+
<span className="sr-only" aria-live="polite">
|
|
122
|
+
{isError ? copy.installFailed : ''}
|
|
123
|
+
</span>
|
|
99
124
|
</button>
|
|
100
125
|
);
|
|
101
126
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { usePathname, useSearchParams } from 'next/navigation';
|
|
4
4
|
import { LayoutDashboard, Server, Zap } from 'lucide-react';
|
|
5
5
|
import { PanelNavRow } from './PanelNavRow';
|
|
6
6
|
|
|
@@ -13,35 +13,35 @@ type HubCopy = {
|
|
|
13
13
|
export function AgentsPanelHubNav({
|
|
14
14
|
copy,
|
|
15
15
|
connectedCount,
|
|
16
|
-
overviewRef,
|
|
17
|
-
skillsRef,
|
|
18
|
-
scrollTo,
|
|
19
|
-
openAdvancedConfig,
|
|
20
16
|
}: {
|
|
21
17
|
copy: HubCopy;
|
|
22
18
|
connectedCount: number;
|
|
23
|
-
overviewRef: RefObject<HTMLDivElement | null>;
|
|
24
|
-
skillsRef: RefObject<HTMLDivElement | null>;
|
|
25
|
-
scrollTo: (el: HTMLElement | null) => void;
|
|
26
|
-
openAdvancedConfig: () => void;
|
|
27
19
|
}) {
|
|
20
|
+
const pathname = usePathname();
|
|
21
|
+
const searchParams = useSearchParams();
|
|
22
|
+
const tab = searchParams.get('tab');
|
|
23
|
+
const inAgentsRoute = pathname === '/agents';
|
|
24
|
+
|
|
28
25
|
return (
|
|
29
26
|
<div className="py-2">
|
|
30
27
|
<PanelNavRow
|
|
31
28
|
icon={<LayoutDashboard size={14} className="text-[var(--amber)]" />}
|
|
32
29
|
title={copy.navOverview}
|
|
33
30
|
badge={<span className="text-2xs tabular-nums text-muted-foreground">{connectedCount}</span>}
|
|
34
|
-
|
|
31
|
+
href="/agents"
|
|
32
|
+
active={inAgentsRoute && (tab === null || tab === 'overview')}
|
|
35
33
|
/>
|
|
36
34
|
<PanelNavRow
|
|
37
35
|
icon={<Server size={14} className="text-muted-foreground" />}
|
|
38
36
|
title={copy.navMcp}
|
|
39
|
-
|
|
37
|
+
href="/agents?tab=mcp"
|
|
38
|
+
active={inAgentsRoute && tab === 'mcp'}
|
|
40
39
|
/>
|
|
41
40
|
<PanelNavRow
|
|
42
41
|
icon={<Zap size={14} className="text-muted-foreground" />}
|
|
43
42
|
title={copy.navSkills}
|
|
44
|
-
|
|
43
|
+
href="/agents?tab=skills"
|
|
44
|
+
active={inAgentsRoute && tab === 'skills'}
|
|
45
45
|
/>
|
|
46
46
|
</div>
|
|
47
47
|
);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
|
-
import {
|
|
5
|
+
import { getPluginRenderers, isRendererEnabled, setRendererEnabled, loadDisabledState } from '@/lib/renderers/registry';
|
|
6
6
|
import { Toggle } from '../settings/Primitives';
|
|
7
7
|
import PanelHeader from './PanelHeader';
|
|
8
8
|
import { useLocale } from '@/lib/LocaleContext';
|
|
@@ -32,7 +32,7 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
|
|
|
32
32
|
useEffect(() => {
|
|
33
33
|
if (!mounted || fetchedRef.current) return;
|
|
34
34
|
fetchedRef.current = true;
|
|
35
|
-
const entryPaths =
|
|
35
|
+
const entryPaths = getPluginRenderers()
|
|
36
36
|
.map(r => r.entryPath)
|
|
37
37
|
.filter((p): p is string => !!p);
|
|
38
38
|
if (entryPaths.length === 0) return;
|
|
@@ -47,7 +47,7 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
|
|
|
47
47
|
.catch(() => {});
|
|
48
48
|
}, [mounted]);
|
|
49
49
|
|
|
50
|
-
const renderers = mounted ?
|
|
50
|
+
const renderers = mounted ? getPluginRenderers() : [];
|
|
51
51
|
const enabledCount = mounted ? renderers.filter(r => isRendererEnabled(r.id)).length : 0;
|
|
52
52
|
|
|
53
53
|
const handleToggle = useCallback((id: string, enabled: boolean) => {
|
|
@@ -8,6 +8,8 @@ export const manifest: RendererDefinition = {
|
|
|
8
8
|
icon: '🔍',
|
|
9
9
|
tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
|
|
10
10
|
builtin: true,
|
|
11
|
+
core: true,
|
|
12
|
+
appBuiltinFeature: true,
|
|
11
13
|
entryPath: '.agent-log.json',
|
|
12
14
|
match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
|
|
13
15
|
load: () => import('./AgentInspectorRenderer').then(m => ({ default: m.AgentInspectorRenderer })),
|
|
@@ -9,6 +9,7 @@ export const manifest: RendererDefinition = {
|
|
|
9
9
|
tags: ['config', 'json', 'settings', 'schema'],
|
|
10
10
|
builtin: true,
|
|
11
11
|
core: true,
|
|
12
|
+
appBuiltinFeature: true,
|
|
12
13
|
entryPath: 'CONFIG.json',
|
|
13
14
|
match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
|
|
14
15
|
load: () => import('./ConfigRenderer').then(m => ({ default: m.ConfigRenderer })),
|
|
@@ -9,6 +9,7 @@ export const manifest: RendererDefinition = {
|
|
|
9
9
|
tags: ['csv', 'table', 'gallery', 'board', 'data'],
|
|
10
10
|
builtin: true,
|
|
11
11
|
core: true,
|
|
12
|
+
appBuiltinFeature: true,
|
|
12
13
|
entryPath: 'Resources/Products.csv',
|
|
13
14
|
match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
|
|
14
15
|
load: () => import('./CsvRenderer').then(m => ({ default: m.CsvRenderer })),
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Puzzle } from 'lucide-react';
|
|
4
|
-
import {
|
|
4
|
+
import { getPluginRenderers, setRendererEnabled } from '@/lib/renderers/registry';
|
|
5
5
|
import { Toggle } from './Primitives';
|
|
6
6
|
import type { PluginsTabProps } from './types';
|
|
7
7
|
|
|
8
8
|
export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps) {
|
|
9
|
+
const renderers = getPluginRenderers();
|
|
9
10
|
return (
|
|
10
11
|
<div className="space-y-5">
|
|
11
12
|
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">{t.settings.plugins.title}</p>
|
|
12
13
|
|
|
13
|
-
{
|
|
14
|
+
{renderers.length === 0 ? (
|
|
14
15
|
<p className="text-sm text-muted-foreground">{t.settings.plugins.noPlugins}</p>
|
|
15
16
|
) : (
|
|
16
17
|
<div className="flex flex-col gap-3">
|
|
17
|
-
{
|
|
18
|
+
{renderers.map(renderer => {
|
|
18
19
|
const isCore = !!renderer.core;
|
|
19
20
|
const enabled = isCore ? true : (pluginStates[renderer.id] ?? true);
|
|
20
21
|
return (
|
package/app/hooks/useMcpData.tsx
CHANGED
|
@@ -128,7 +128,7 @@ export default function McpProvider({ children }: { children: ReactNode }) {
|
|
|
128
128
|
if (!agent) return false;
|
|
129
129
|
|
|
130
130
|
try {
|
|
131
|
-
const res = await apiFetch<{ results: Array<{
|
|
131
|
+
const res = await apiFetch<{ results: Array<{ agent?: string; status?: string; ok?: boolean; error?: string }> }>('/api/mcp/install', {
|
|
132
132
|
method: 'POST',
|
|
133
133
|
headers: { 'Content-Type': 'application/json' },
|
|
134
134
|
body: JSON.stringify({
|
|
@@ -141,7 +141,8 @@ export default function McpProvider({ children }: { children: ReactNode }) {
|
|
|
141
141
|
}),
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
-
const
|
|
144
|
+
const first = res.results?.[0];
|
|
145
|
+
const ok = first?.ok === true || first?.status === 'ok';
|
|
145
146
|
if (ok) {
|
|
146
147
|
// Refresh to pick up newly installed agent
|
|
147
148
|
await fetchAll();
|
|
@@ -32,11 +32,18 @@ interface ChangeLogState {
|
|
|
32
32
|
version: 1;
|
|
33
33
|
lastSeenAt: string | null;
|
|
34
34
|
events: ContentChangeEvent[];
|
|
35
|
+
legacy?: {
|
|
36
|
+
agentDiffImportedCount?: number;
|
|
37
|
+
lastImportedAt?: string | null;
|
|
38
|
+
};
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
interface ListOptions {
|
|
38
42
|
path?: string;
|
|
39
43
|
limit?: number;
|
|
44
|
+
source?: ContentChangeSource;
|
|
45
|
+
op?: string;
|
|
46
|
+
q?: string;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export interface ContentChangeSummary {
|
|
@@ -64,6 +71,10 @@ function defaultState(): ChangeLogState {
|
|
|
64
71
|
version: 1,
|
|
65
72
|
lastSeenAt: null,
|
|
66
73
|
events: [],
|
|
74
|
+
legacy: {
|
|
75
|
+
agentDiffImportedCount: 0,
|
|
76
|
+
lastImportedAt: null,
|
|
77
|
+
},
|
|
67
78
|
};
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -87,6 +98,16 @@ function readState(mindRoot: string): ChangeLogState {
|
|
|
87
98
|
version: 1,
|
|
88
99
|
lastSeenAt: typeof parsed.lastSeenAt === 'string' ? parsed.lastSeenAt : null,
|
|
89
100
|
events: parsed.events,
|
|
101
|
+
legacy: {
|
|
102
|
+
agentDiffImportedCount:
|
|
103
|
+
typeof parsed.legacy?.agentDiffImportedCount === 'number'
|
|
104
|
+
? parsed.legacy.agentDiffImportedCount
|
|
105
|
+
: 0,
|
|
106
|
+
lastImportedAt:
|
|
107
|
+
typeof parsed.legacy?.lastImportedAt === 'string'
|
|
108
|
+
? parsed.legacy.lastImportedAt
|
|
109
|
+
: null,
|
|
110
|
+
},
|
|
90
111
|
};
|
|
91
112
|
} catch {
|
|
92
113
|
return defaultState();
|
|
@@ -99,8 +120,115 @@ function writeState(mindRoot: string, state: ChangeLogState): void {
|
|
|
99
120
|
fs.writeFileSync(file, JSON.stringify(state, null, 2), 'utf-8');
|
|
100
121
|
}
|
|
101
122
|
|
|
102
|
-
|
|
123
|
+
interface LegacyAgentDiffEntry {
|
|
124
|
+
ts?: string;
|
|
125
|
+
path?: string;
|
|
126
|
+
tool?: string;
|
|
127
|
+
before?: string;
|
|
128
|
+
after?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseLegacyAgentDiffBlocks(content: string): LegacyAgentDiffEntry[] {
|
|
132
|
+
const blocks: LegacyAgentDiffEntry[] = [];
|
|
133
|
+
const re = /```agent-diff\s*\n([\s\S]*?)```/g;
|
|
134
|
+
let m: RegExpExecArray | null;
|
|
135
|
+
while ((m = re.exec(content)) !== null) {
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(m[1].trim()) as LegacyAgentDiffEntry;
|
|
138
|
+
blocks.push(parsed);
|
|
139
|
+
} catch {
|
|
140
|
+
// Skip malformed block, keep import best-effort.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return blocks;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function toValidIso(ts: string | undefined): string {
|
|
147
|
+
if (!ts) return nowIso();
|
|
148
|
+
const ms = new Date(ts).getTime();
|
|
149
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : nowIso();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function removeLegacyFile(filePath: string): void {
|
|
153
|
+
try {
|
|
154
|
+
fs.rmSync(filePath, { force: true });
|
|
155
|
+
} catch {
|
|
156
|
+
// keep best-effort; migration should not fail main flow.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function importLegacyAgentDiffIfNeeded(mindRoot: string, state: ChangeLogState): ChangeLogState {
|
|
161
|
+
const legacyPath = path.join(mindRoot, 'Agent-Diff.md');
|
|
162
|
+
if (!fs.existsSync(legacyPath)) return state;
|
|
163
|
+
|
|
164
|
+
let raw = '';
|
|
165
|
+
try {
|
|
166
|
+
raw = fs.readFileSync(legacyPath, 'utf-8');
|
|
167
|
+
} catch {
|
|
168
|
+
return state;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const blocks = parseLegacyAgentDiffBlocks(raw);
|
|
172
|
+
const importedCount = state.legacy?.agentDiffImportedCount ?? 0;
|
|
173
|
+
if (blocks.length <= importedCount) {
|
|
174
|
+
// Already migrated before: remove legacy file to avoid stale duplicate source.
|
|
175
|
+
if (blocks.length > 0) removeLegacyFile(legacyPath);
|
|
176
|
+
return state;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const incoming = blocks.slice(importedCount);
|
|
180
|
+
const importedEvents: ContentChangeEvent[] = incoming.map((entry, idx) => {
|
|
181
|
+
const before = normalizeText(entry.before);
|
|
182
|
+
const after = normalizeText(entry.after);
|
|
183
|
+
const toolName = typeof entry.tool === 'string' && entry.tool.trim()
|
|
184
|
+
? entry.tool.trim()
|
|
185
|
+
: 'unknown-tool';
|
|
186
|
+
const targetPath = typeof entry.path === 'string' && entry.path.trim()
|
|
187
|
+
? entry.path
|
|
188
|
+
: 'Agent-Diff.md';
|
|
189
|
+
return {
|
|
190
|
+
id: `legacy-${Date.now().toString(36)}-${idx.toString(36)}`,
|
|
191
|
+
ts: toValidIso(entry.ts),
|
|
192
|
+
op: 'legacy_agent_diff_import',
|
|
193
|
+
path: targetPath,
|
|
194
|
+
source: 'agent',
|
|
195
|
+
summary: `Imported legacy agent diff (${toolName})`,
|
|
196
|
+
before: before.value,
|
|
197
|
+
after: after.value,
|
|
198
|
+
truncated: before.truncated || after.truncated || undefined,
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const merged = [...state.events, ...importedEvents].sort(
|
|
203
|
+
(a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime(),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const nextState = {
|
|
207
|
+
...state,
|
|
208
|
+
events: merged.slice(0, MAX_EVENTS),
|
|
209
|
+
legacy: {
|
|
210
|
+
agentDiffImportedCount: blocks.length,
|
|
211
|
+
lastImportedAt: nowIso(),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
removeLegacyFile(legacyPath);
|
|
215
|
+
return nextState;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function loadState(mindRoot: string): ChangeLogState {
|
|
103
219
|
const state = readState(mindRoot);
|
|
220
|
+
const migrated = importLegacyAgentDiffIfNeeded(mindRoot, state);
|
|
221
|
+
const changed =
|
|
222
|
+
(state.legacy?.agentDiffImportedCount ?? 0) !== (migrated.legacy?.agentDiffImportedCount ?? 0) ||
|
|
223
|
+
state.events.length !== migrated.events.length;
|
|
224
|
+
if (changed) {
|
|
225
|
+
writeState(mindRoot, migrated);
|
|
226
|
+
}
|
|
227
|
+
return migrated;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function appendContentChange(mindRoot: string, input: ContentChangeInput): ContentChangeEvent {
|
|
231
|
+
const state = loadState(mindRoot);
|
|
104
232
|
const before = normalizeText(input.before);
|
|
105
233
|
const after = normalizeText(input.after);
|
|
106
234
|
const event: ContentChangeEvent = {
|
|
@@ -125,23 +253,35 @@ export function appendContentChange(mindRoot: string, input: ContentChangeInput)
|
|
|
125
253
|
}
|
|
126
254
|
|
|
127
255
|
export function listContentChanges(mindRoot: string, options: ListOptions = {}): ContentChangeEvent[] {
|
|
128
|
-
const state =
|
|
256
|
+
const state = loadState(mindRoot);
|
|
129
257
|
const limit = Math.max(1, Math.min(options.limit ?? 50, 200));
|
|
130
|
-
const pathFilter = options.path;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
258
|
+
const pathFilter = options.path?.trim();
|
|
259
|
+
const sourceFilter = options.source;
|
|
260
|
+
const opFilter = options.op?.trim();
|
|
261
|
+
const q = options.q?.trim().toLowerCase();
|
|
262
|
+
const events = state.events.filter((event) => {
|
|
263
|
+
if (pathFilter && event.path !== pathFilter && event.beforePath !== pathFilter && event.afterPath !== pathFilter) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (sourceFilter && event.source !== sourceFilter) return false;
|
|
267
|
+
if (opFilter && event.op !== opFilter) return false;
|
|
268
|
+
if (q) {
|
|
269
|
+
const haystack = `${event.path} ${event.beforePath ?? ''} ${event.afterPath ?? ''} ${event.summary} ${event.op} ${event.source}`.toLowerCase();
|
|
270
|
+
if (!haystack.includes(q)) return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
});
|
|
134
274
|
return events.slice(0, limit);
|
|
135
275
|
}
|
|
136
276
|
|
|
137
277
|
export function markContentChangesSeen(mindRoot: string): void {
|
|
138
|
-
const state =
|
|
278
|
+
const state = loadState(mindRoot);
|
|
139
279
|
state.lastSeenAt = nowIso();
|
|
140
280
|
writeState(mindRoot, state);
|
|
141
281
|
}
|
|
142
282
|
|
|
143
283
|
export function getContentChangeSummary(mindRoot: string): ContentChangeSummary {
|
|
144
|
-
const state =
|
|
284
|
+
const state = loadState(mindRoot);
|
|
145
285
|
const lastSeenAtMs = state.lastSeenAt ? new Date(state.lastSeenAt).getTime() : 0;
|
|
146
286
|
const unreadCount = state.events.filter((event) => new Date(event.ts).getTime() > lastSeenAtMs).length;
|
|
147
287
|
return {
|
package/app/lib/fs.ts
CHANGED
|
@@ -199,7 +199,13 @@ export function appendContentChange(input: ContentChangeInput): ContentChangeEve
|
|
|
199
199
|
return coreAppendContentChange(getMindRoot(), input);
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
export function listContentChanges(options: {
|
|
202
|
+
export function listContentChanges(options: {
|
|
203
|
+
path?: string;
|
|
204
|
+
limit?: number;
|
|
205
|
+
source?: 'user' | 'agent' | 'system';
|
|
206
|
+
op?: string;
|
|
207
|
+
q?: string;
|
|
208
|
+
} = {}): ContentChangeEvent[] {
|
|
203
209
|
return coreListContentChanges(getMindRoot(), options);
|
|
204
210
|
}
|
|
205
211
|
|