@geminilight/mindos 0.6.28 → 0.6.30
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/README.md +10 -4
- package/app/app/api/a2a/agents/route.ts +9 -0
- package/app/app/api/a2a/delegations/route.ts +9 -0
- package/app/app/api/a2a/discover/route.ts +2 -0
- package/app/app/api/a2a/route.ts +6 -6
- package/app/app/api/acp/config/route.ts +82 -0
- package/app/app/api/acp/detect/route.ts +114 -0
- package/app/app/api/acp/install/route.ts +51 -0
- package/app/app/api/acp/registry/route.ts +31 -0
- package/app/app/api/acp/session/route.ts +185 -0
- package/app/app/api/ask/route.ts +116 -13
- package/app/app/api/workflows/route.ts +156 -0
- package/app/app/layout.tsx +2 -0
- package/app/app/page.tsx +7 -2
- package/app/components/ActivityBar.tsx +12 -4
- package/app/components/AskModal.tsx +4 -1
- package/app/components/DirView.tsx +64 -2
- package/app/components/FileTree.tsx +40 -10
- package/app/components/GuideCard.tsx +7 -17
- package/app/components/HomeContent.tsx +1 -0
- package/app/components/MarkdownView.tsx +2 -0
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAskPanel.tsx +5 -1
- package/app/components/SearchModal.tsx +234 -80
- package/app/components/SidebarLayout.tsx +6 -0
- package/app/components/agents/AgentDetailContent.tsx +266 -52
- package/app/components/agents/AgentsContentPage.tsx +32 -6
- package/app/components/agents/AgentsPanelA2aTab.tsx +684 -0
- package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
- package/app/components/agents/SkillDetailPopover.tsx +4 -9
- package/app/components/agents/agents-content-model.ts +2 -2
- package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
- package/app/components/ask/AskContent.tsx +197 -239
- package/app/components/ask/FileChip.tsx +82 -17
- package/app/components/ask/MentionPopover.tsx +21 -3
- package/app/components/ask/MessageList.tsx +30 -9
- package/app/components/ask/SlashCommandPopover.tsx +21 -3
- package/app/components/help/HelpContent.tsx +9 -9
- package/app/components/panels/AgentsPanel.tsx +2 -0
- package/app/components/panels/AgentsPanelAgentDetail.tsx +5 -8
- package/app/components/panels/AgentsPanelHubNav.tsx +16 -2
- package/app/components/panels/EchoPanel.tsx +5 -1
- package/app/components/panels/EchoSidebarStats.tsx +136 -0
- package/app/components/panels/WorkflowsPanel.tsx +206 -0
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +157 -0
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +201 -0
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +226 -0
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
- package/app/components/renderers/workflow-yaml/execution.ts +177 -0
- package/app/components/renderers/workflow-yaml/index.ts +6 -0
- package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
- package/app/components/renderers/workflow-yaml/parser.ts +172 -0
- package/app/components/renderers/workflow-yaml/selectors.tsx +522 -0
- package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
- package/app/components/renderers/workflow-yaml/types.ts +46 -0
- package/app/components/settings/KnowledgeTab.tsx +3 -6
- package/app/components/settings/McpSkillsSection.tsx +4 -5
- package/app/components/settings/McpTab.tsx +6 -8
- package/app/components/setup/StepSecurity.tsx +4 -5
- package/app/components/setup/index.tsx +5 -11
- package/app/components/ui/Toaster.tsx +39 -0
- package/app/hooks/useA2aRegistry.ts +6 -1
- package/app/hooks/useAcpConfig.ts +96 -0
- package/app/hooks/useAcpDetection.ts +120 -0
- package/app/hooks/useAcpRegistry.ts +86 -0
- package/app/hooks/useAskModal.ts +12 -5
- package/app/hooks/useAskPanel.ts +8 -5
- package/app/hooks/useAskSession.ts +19 -2
- package/app/hooks/useDelegationHistory.ts +49 -0
- package/app/hooks/useImageUpload.ts +152 -0
- package/app/lib/a2a/client.ts +49 -5
- package/app/lib/a2a/orchestrator.ts +0 -1
- package/app/lib/a2a/task-handler.ts +4 -4
- package/app/lib/a2a/types.ts +15 -0
- package/app/lib/acp/acp-tools.ts +95 -0
- package/app/lib/acp/agent-descriptors.ts +274 -0
- package/app/lib/acp/bridge.ts +144 -0
- package/app/lib/acp/index.ts +40 -0
- package/app/lib/acp/registry.ts +202 -0
- package/app/lib/acp/session.ts +717 -0
- package/app/lib/acp/subprocess.ts +495 -0
- package/app/lib/acp/types.ts +274 -0
- package/app/lib/agent/model.ts +18 -3
- package/app/lib/agent/to-agent-messages.ts +25 -2
- package/app/lib/agent/tools.ts +2 -1
- package/app/lib/i18n/_core.ts +22 -0
- package/app/lib/i18n/index.ts +35 -0
- package/app/lib/i18n/modules/ai-chat.ts +215 -0
- package/app/lib/i18n/modules/common.ts +71 -0
- package/app/lib/i18n/modules/features.ts +153 -0
- package/app/lib/i18n/modules/knowledge.ts +429 -0
- package/app/lib/i18n/modules/navigation.ts +153 -0
- package/app/lib/i18n/modules/onboarding.ts +523 -0
- package/app/lib/i18n/modules/panels.ts +1196 -0
- package/app/lib/i18n/modules/settings.ts +585 -0
- package/app/lib/i18n-en.ts +2 -1518
- package/app/lib/i18n-zh.ts +2 -1542
- package/app/lib/i18n.ts +3 -6
- package/app/lib/pi-integration/skills.ts +21 -6
- package/app/lib/renderers/index.ts +2 -2
- package/app/lib/settings.ts +10 -0
- package/app/lib/toast.ts +79 -0
- package/app/lib/types.ts +12 -1
- package/app/next-env.d.ts +1 -1
- package/app/package.json +3 -1
- package/bin/cli.js +25 -25
- package/bin/commands/file.js +29 -2
- package/bin/commands/space.js +249 -91
- package/package.json +1 -1
- package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
- package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
- package/app/components/renderers/workflow/manifest.ts +0 -14
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { Check, ChevronDown, ChevronUp, Clock, Code2, Download, Globe, Loader2, MessageSquare, Network, RefreshCw, RotateCcw, Save, Settings2, Trash2, Wifi, WifiOff, Wrench, Zap } from 'lucide-react';
|
|
5
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
+
import { useAcpConfig } from '@/hooks/useAcpConfig';
|
|
7
|
+
import type { RemoteAgent, DelegationRecord } from '@/lib/a2a/types';
|
|
8
|
+
import type { AcpRegistryEntry } from '@/lib/acp/types';
|
|
9
|
+
import { useDelegationHistory } from '@/hooks/useDelegationHistory';
|
|
10
|
+
import { useAcpRegistry } from '@/hooks/useAcpRegistry';
|
|
11
|
+
import { useAcpDetection } from '@/hooks/useAcpDetection';
|
|
12
|
+
import { openAskModal } from '@/hooks/useAskModal';
|
|
13
|
+
import DiscoverAgentModal from './DiscoverAgentModal';
|
|
14
|
+
|
|
15
|
+
interface AgentsPanelA2aTabProps {
|
|
16
|
+
agents: RemoteAgent[];
|
|
17
|
+
discovering: boolean;
|
|
18
|
+
error: string | null;
|
|
19
|
+
onDiscover: (url: string) => Promise<RemoteAgent | null>;
|
|
20
|
+
onRemove: (id: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function AgentsPanelA2aTab({
|
|
24
|
+
agents,
|
|
25
|
+
discovering,
|
|
26
|
+
error,
|
|
27
|
+
onDiscover,
|
|
28
|
+
onRemove,
|
|
29
|
+
}: AgentsPanelA2aTabProps) {
|
|
30
|
+
const { t } = useLocale();
|
|
31
|
+
const p = t.panels.agents;
|
|
32
|
+
const [showModal, setShowModal] = useState(false);
|
|
33
|
+
const { delegations } = useDelegationHistory(true);
|
|
34
|
+
const acp = useAcpRegistry();
|
|
35
|
+
|
|
36
|
+
const isEmpty = agents.length === 0 && !acp.loading && acp.agents.length === 0;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="space-y-5">
|
|
40
|
+
{/* Header + Discover button */}
|
|
41
|
+
<div className="flex items-center justify-between">
|
|
42
|
+
<h2 className="text-sm font-medium text-foreground">{p.a2aTabTitle}</h2>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
onClick={() => setShowModal(true)}
|
|
46
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-foreground text-background hover:bg-foreground/90 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
47
|
+
>
|
|
48
|
+
<Globe size={12} />
|
|
49
|
+
{p.a2aDiscover}
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
{/* Unified empty state when both A2A and ACP are empty */}
|
|
54
|
+
{isEmpty ? (
|
|
55
|
+
<NetworkEmptyState
|
|
56
|
+
onDiscover={() => setShowModal(true)}
|
|
57
|
+
onBrowseRegistry={acp.retry}
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<>
|
|
61
|
+
{/* Remote A2A agent list */}
|
|
62
|
+
{agents.length === 0 ? (
|
|
63
|
+
<div className="rounded-xl border border-dashed border-border/60 bg-gradient-to-b from-card/80 to-card/40 p-8 text-center">
|
|
64
|
+
<div className="w-12 h-12 rounded-2xl bg-muted/40 flex items-center justify-center mx-auto mb-3">
|
|
65
|
+
<Globe size={20} className="text-muted-foreground/50" aria-hidden="true" />
|
|
66
|
+
</div>
|
|
67
|
+
<p className="text-sm font-medium text-muted-foreground mb-1">{p.a2aTabEmpty}</p>
|
|
68
|
+
<p className="text-xs text-muted-foreground/70 leading-relaxed max-w-xs mx-auto">
|
|
69
|
+
{p.a2aTabEmptyHint}
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
) : (
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
{agents.map((agent) => (
|
|
75
|
+
<RemoteAgentRow key={agent.id} agent={agent} onRemove={onRemove} removeCopy={p.a2aRemoveAgent} skillsCopy={p.a2aSkills} />
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{/* ACP Registry section */}
|
|
81
|
+
<AcpRegistrySection />
|
|
82
|
+
|
|
83
|
+
{/* Recent Delegations */}
|
|
84
|
+
<DelegationHistorySection delegations={delegations} />
|
|
85
|
+
</>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<DiscoverAgentModal
|
|
89
|
+
open={showModal}
|
|
90
|
+
onClose={() => setShowModal(false)}
|
|
91
|
+
onDiscover={onDiscover}
|
|
92
|
+
discovering={discovering}
|
|
93
|
+
error={error}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ────────── Network Empty State ────────── */
|
|
100
|
+
|
|
101
|
+
function NetworkEmptyState({
|
|
102
|
+
onDiscover,
|
|
103
|
+
onBrowseRegistry,
|
|
104
|
+
}: {
|
|
105
|
+
onDiscover: () => void;
|
|
106
|
+
onBrowseRegistry: () => void;
|
|
107
|
+
}) {
|
|
108
|
+
const { t } = useLocale();
|
|
109
|
+
const p = t.panels.agents;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="rounded-xl border border-dashed border-border/60 bg-gradient-to-b from-card/80 to-card/40 p-12 text-center">
|
|
113
|
+
<div className="w-14 h-14 rounded-2xl bg-muted/40 flex items-center justify-center mx-auto mb-4">
|
|
114
|
+
<Network size={22} className="text-muted-foreground/50" aria-hidden="true" />
|
|
115
|
+
</div>
|
|
116
|
+
<p className="text-sm font-medium text-foreground mb-1">{p.networkEmptyTitle}</p>
|
|
117
|
+
<p className="text-xs text-muted-foreground/70 leading-relaxed max-w-sm mx-auto mb-5">
|
|
118
|
+
{p.networkEmptyDesc}
|
|
119
|
+
</p>
|
|
120
|
+
<div className="flex items-center justify-center gap-2.5">
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
onClick={onDiscover}
|
|
124
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg bg-foreground text-background hover:bg-foreground/90 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
125
|
+
>
|
|
126
|
+
<Globe size={12} />
|
|
127
|
+
{p.networkDiscoverBtn}
|
|
128
|
+
</button>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={onBrowseRegistry}
|
|
132
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
133
|
+
>
|
|
134
|
+
<Network size={12} />
|
|
135
|
+
{p.networkBrowseBtn}
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ────────── Quick Actions ────────── */
|
|
143
|
+
|
|
144
|
+
interface QuickAction {
|
|
145
|
+
labelKey: 'acpQuickReview' | 'acpQuickFix' | 'acpQuickExplain';
|
|
146
|
+
icon: typeof Code2;
|
|
147
|
+
promptSuffix: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const QUICK_ACTIONS: QuickAction[] = [
|
|
151
|
+
{ labelKey: 'acpQuickReview', icon: Code2, promptSuffix: 'review the code in this project' },
|
|
152
|
+
{ labelKey: 'acpQuickFix', icon: Wrench, promptSuffix: 'find and fix bugs in this project' },
|
|
153
|
+
{ labelKey: 'acpQuickExplain', icon: MessageSquare, promptSuffix: 'explain the structure of this project' },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
/* ────────── ACP Registry Section ────────── */
|
|
157
|
+
|
|
158
|
+
function AcpRegistrySection() {
|
|
159
|
+
const { t } = useLocale();
|
|
160
|
+
const p = t.panels.agents;
|
|
161
|
+
const acp = useAcpRegistry();
|
|
162
|
+
const detection = useAcpDetection();
|
|
163
|
+
const acpConfig = useAcpConfig();
|
|
164
|
+
|
|
165
|
+
if (acp.loading) {
|
|
166
|
+
return (
|
|
167
|
+
<div className="space-y-2">
|
|
168
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
169
|
+
{p.acpSectionTitle}
|
|
170
|
+
</h3>
|
|
171
|
+
<div className="flex items-center justify-center py-6 gap-2">
|
|
172
|
+
<Loader2 size={14} className="animate-spin text-muted-foreground" />
|
|
173
|
+
<span className="text-xs text-muted-foreground">{p.acpLoading}</span>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (acp.error) {
|
|
180
|
+
return (
|
|
181
|
+
<div className="space-y-2">
|
|
182
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
183
|
+
{p.acpSectionTitle}
|
|
184
|
+
</h3>
|
|
185
|
+
<div className="rounded-lg border border-border/60 bg-card/80 p-4 text-center">
|
|
186
|
+
<p className="text-xs text-muted-foreground mb-2">{p.acpLoadFailed}</p>
|
|
187
|
+
<button
|
|
188
|
+
type="button"
|
|
189
|
+
onClick={acp.retry}
|
|
190
|
+
className="text-xs font-medium text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
191
|
+
>
|
|
192
|
+
{p.acpRetry}
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (acp.agents.length === 0) return null;
|
|
200
|
+
|
|
201
|
+
// Separate installed and not-installed agents
|
|
202
|
+
const installedAgents: { agent: AcpRegistryEntry; info: { id: string; name: string; binaryPath: string } }[] = [];
|
|
203
|
+
const notInstalledAgents: { agent: AcpRegistryEntry; installCmd: string | null; packageName: string | null }[] = [];
|
|
204
|
+
|
|
205
|
+
for (const agent of acp.agents) {
|
|
206
|
+
const installed = detection.installedAgents.find((d) => d.id === agent.id);
|
|
207
|
+
if (installed) {
|
|
208
|
+
installedAgents.push({ agent, info: installed });
|
|
209
|
+
} else {
|
|
210
|
+
const notInstalled = detection.notInstalledAgents.find((d) => d.id === agent.id);
|
|
211
|
+
notInstalledAgents.push({
|
|
212
|
+
agent,
|
|
213
|
+
installCmd: notInstalled?.installCmd ?? null,
|
|
214
|
+
packageName: notInstalled?.packageName ?? agent.packageName ?? null,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<div className="space-y-3">
|
|
221
|
+
<div className="flex items-center justify-between">
|
|
222
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
223
|
+
{p.acpSectionTitle}
|
|
224
|
+
</h3>
|
|
225
|
+
<div className="flex items-center gap-2">
|
|
226
|
+
<button
|
|
227
|
+
type="button"
|
|
228
|
+
onClick={() => detection.refresh()}
|
|
229
|
+
disabled={detection.loading}
|
|
230
|
+
className="inline-flex items-center gap-1 px-2 py-1 text-2xs font-medium rounded-md 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 disabled:opacity-50"
|
|
231
|
+
>
|
|
232
|
+
<RefreshCw size={10} className={detection.loading ? 'animate-spin' : ''} />
|
|
233
|
+
{p.acpScan}
|
|
234
|
+
</button>
|
|
235
|
+
<span className="text-2xs text-muted-foreground/60">
|
|
236
|
+
{p.acpSectionDesc(acp.agents.length)}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Installed agents — prominent cards */}
|
|
242
|
+
{installedAgents.length > 0 && (
|
|
243
|
+
<div className="space-y-2">
|
|
244
|
+
{installedAgents.map(({ agent, info }) => (
|
|
245
|
+
<AcpAgentCard
|
|
246
|
+
key={agent.id}
|
|
247
|
+
agent={agent}
|
|
248
|
+
installed={info}
|
|
249
|
+
installCmd={null}
|
|
250
|
+
packageName={null}
|
|
251
|
+
detectionDone={!detection.loading}
|
|
252
|
+
onInstalled={detection.refresh}
|
|
253
|
+
acpConfig={acpConfig}
|
|
254
|
+
/>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
{/* Not-installed agents — compact rows grouped at bottom */}
|
|
260
|
+
{notInstalledAgents.length > 0 && (
|
|
261
|
+
<div className="space-y-1">
|
|
262
|
+
{notInstalledAgents.map(({ agent, installCmd, packageName }) => (
|
|
263
|
+
<AcpAgentCompactRow
|
|
264
|
+
key={agent.id}
|
|
265
|
+
agent={agent}
|
|
266
|
+
installCmd={installCmd}
|
|
267
|
+
packageName={packageName}
|
|
268
|
+
detectionDone={!detection.loading}
|
|
269
|
+
onInstalled={detection.refresh}
|
|
270
|
+
/>
|
|
271
|
+
))}
|
|
272
|
+
</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* ────────── ACP Agent Card (Installed — prominent) ────────── */
|
|
279
|
+
|
|
280
|
+
const TRANSPORT_STYLES: Record<string, string> = {
|
|
281
|
+
npx: 'bg-[var(--amber)]/15 text-[var(--amber)]',
|
|
282
|
+
binary: 'bg-muted text-muted-foreground',
|
|
283
|
+
uvx: 'bg-[var(--success)]/15 text-[var(--success)]',
|
|
284
|
+
stdio: 'bg-muted text-muted-foreground',
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
function AcpAgentCard({ agent, installed, detectionDone, acpConfig }: {
|
|
288
|
+
agent: AcpRegistryEntry;
|
|
289
|
+
installed: { id: string; name: string; binaryPath: string; resolvedCommand?: { cmd: string; args: string[]; source: string } } | null;
|
|
290
|
+
installCmd: string | null;
|
|
291
|
+
packageName: string | null;
|
|
292
|
+
detectionDone: boolean;
|
|
293
|
+
onInstalled: () => void;
|
|
294
|
+
acpConfig: ReturnType<typeof useAcpConfig>;
|
|
295
|
+
}) {
|
|
296
|
+
const { t } = useLocale();
|
|
297
|
+
const p = t.panels.agents;
|
|
298
|
+
const [configOpen, setConfigOpen] = useState(false);
|
|
299
|
+
const [editCmd, setEditCmd] = useState('');
|
|
300
|
+
const [editArgs, setEditArgs] = useState('');
|
|
301
|
+
const [saveState, setSaveState] = useState<'idle' | 'saved'>('idle');
|
|
302
|
+
const transportLabels: Record<string, string> = {
|
|
303
|
+
npx: p.acpTransportNpx,
|
|
304
|
+
binary: p.acpTransportBinary,
|
|
305
|
+
uvx: p.acpTransportUvx,
|
|
306
|
+
stdio: p.acpTransportStdio,
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const isReady = !!installed;
|
|
310
|
+
const resolved = installed?.resolvedCommand;
|
|
311
|
+
const sourceLabel = resolved?.source === 'user-override' ? p.acpConfigSourceUser
|
|
312
|
+
: resolved?.source === 'descriptor' ? p.acpConfigSourceBuiltin
|
|
313
|
+
: p.acpConfigSourceRegistry;
|
|
314
|
+
|
|
315
|
+
const handleToggleConfig = useCallback(() => {
|
|
316
|
+
if (!configOpen && resolved) {
|
|
317
|
+
// Pre-fill with current resolved values
|
|
318
|
+
setEditCmd(resolved.cmd);
|
|
319
|
+
setEditArgs(resolved.args.join(' '));
|
|
320
|
+
}
|
|
321
|
+
setConfigOpen(v => !v);
|
|
322
|
+
setSaveState('idle');
|
|
323
|
+
}, [configOpen, resolved]);
|
|
324
|
+
|
|
325
|
+
const handleSave = useCallback(async () => {
|
|
326
|
+
const args = editArgs.trim() ? editArgs.trim().split(/\s+/) : [];
|
|
327
|
+
const ok = await acpConfig.save(agent.id, {
|
|
328
|
+
command: editCmd.trim() || undefined,
|
|
329
|
+
args: args.length > 0 ? args : undefined,
|
|
330
|
+
});
|
|
331
|
+
if (ok) {
|
|
332
|
+
setSaveState('saved');
|
|
333
|
+
setTimeout(() => setSaveState('idle'), 2000);
|
|
334
|
+
}
|
|
335
|
+
}, [acpConfig, agent.id, editCmd, editArgs]);
|
|
336
|
+
|
|
337
|
+
const handleReset = useCallback(async () => {
|
|
338
|
+
await acpConfig.reset(agent.id);
|
|
339
|
+
setSaveState('idle');
|
|
340
|
+
setConfigOpen(false);
|
|
341
|
+
}, [acpConfig, agent.id]);
|
|
342
|
+
|
|
343
|
+
const handleUse = () => {
|
|
344
|
+
openAskModal('', 'user', { id: agent.id, name: agent.name });
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const handleQuickAction = (action: QuickAction) => {
|
|
348
|
+
openAskModal(action.promptSuffix, 'user', { id: agent.id, name: agent.name });
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div className="rounded-xl border border-[var(--amber)]/20 bg-card p-3 hover:border-[var(--amber)]/40 transition-all duration-150">
|
|
353
|
+
{/* Header row */}
|
|
354
|
+
<div className="flex items-center gap-2.5">
|
|
355
|
+
<div className="w-8 h-8 rounded-lg bg-[var(--amber)]/10 flex items-center justify-center shrink-0">
|
|
356
|
+
<Network size={14} className="text-[var(--amber)]" />
|
|
357
|
+
</div>
|
|
358
|
+
<div className="flex-1 min-w-0">
|
|
359
|
+
<div className="flex items-center gap-1.5">
|
|
360
|
+
<p className="text-sm font-medium text-foreground truncate">{agent.name}</p>
|
|
361
|
+
{agent.version && (
|
|
362
|
+
<span className="text-2xs text-muted-foreground/60 shrink-0">v{agent.version}</span>
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
{agent.description && (
|
|
366
|
+
<p className="text-2xs text-muted-foreground truncate">{agent.description}</p>
|
|
367
|
+
)}
|
|
368
|
+
</div>
|
|
369
|
+
<span className={`text-2xs px-1.5 py-0.5 rounded font-medium shrink-0 ${TRANSPORT_STYLES[agent.transport] ?? TRANSPORT_STYLES.stdio}`}>
|
|
370
|
+
{transportLabels[agent.transport] ?? agent.transport}
|
|
371
|
+
</span>
|
|
372
|
+
{detectionDone && (
|
|
373
|
+
<span className="text-2xs px-1.5 py-0.5 rounded font-medium shrink-0 bg-[var(--success)]/15 text-[var(--success)]">
|
|
374
|
+
{p.acpReady}
|
|
375
|
+
</span>
|
|
376
|
+
)}
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
onClick={handleToggleConfig}
|
|
380
|
+
className="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
381
|
+
title={p.acpConfigToggle}
|
|
382
|
+
aria-expanded={configOpen}
|
|
383
|
+
>
|
|
384
|
+
<Settings2 size={13} />
|
|
385
|
+
</button>
|
|
386
|
+
<button
|
|
387
|
+
type="button"
|
|
388
|
+
onClick={handleUse}
|
|
389
|
+
className="inline-flex items-center gap-1 px-2.5 py-1 text-2xs font-medium rounded-md border border-[var(--amber)] text-[var(--amber)] hover:bg-[var(--amber)]/10 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring cursor-pointer"
|
|
390
|
+
>
|
|
391
|
+
<Zap size={10} />
|
|
392
|
+
{p.acpUseAgent}
|
|
393
|
+
</button>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
{/* Collapsible config section */}
|
|
397
|
+
{configOpen && resolved && (
|
|
398
|
+
<div className="mt-2.5 pt-2.5 border-t border-border/40 space-y-2">
|
|
399
|
+
{/* Command */}
|
|
400
|
+
<div className="flex items-center gap-2">
|
|
401
|
+
<label className="text-2xs text-muted-foreground w-12 shrink-0">{p.acpConfigCommand}</label>
|
|
402
|
+
<input
|
|
403
|
+
type="text"
|
|
404
|
+
value={editCmd}
|
|
405
|
+
onChange={e => { setEditCmd(e.target.value); setSaveState('idle'); }}
|
|
406
|
+
className="flex-1 rounded-md border border-border bg-background text-foreground text-xs font-mono px-2 py-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
407
|
+
placeholder={resolved.cmd}
|
|
408
|
+
/>
|
|
409
|
+
</div>
|
|
410
|
+
{/* Args */}
|
|
411
|
+
<div className="flex items-center gap-2">
|
|
412
|
+
<label className="text-2xs text-muted-foreground w-12 shrink-0">{p.acpConfigArgs}</label>
|
|
413
|
+
<input
|
|
414
|
+
type="text"
|
|
415
|
+
value={editArgs}
|
|
416
|
+
onChange={e => { setEditArgs(e.target.value); setSaveState('idle'); }}
|
|
417
|
+
className="flex-1 rounded-md border border-border bg-background text-foreground text-xs font-mono px-2 py-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
418
|
+
placeholder={resolved.args.join(' ')}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
{/* Status row */}
|
|
422
|
+
<div className="flex items-center gap-2 text-2xs text-muted-foreground">
|
|
423
|
+
<span>{p.acpConfigSource}:</span>
|
|
424
|
+
<span className={`px-1.5 py-0.5 rounded font-medium ${
|
|
425
|
+
resolved.source === 'user-override' ? 'bg-[var(--amber)]/15 text-[var(--amber)]'
|
|
426
|
+
: 'bg-muted text-muted-foreground'
|
|
427
|
+
}`}>{sourceLabel}</span>
|
|
428
|
+
<span className="text-muted-foreground/50">|</span>
|
|
429
|
+
<span>{p.acpConfigPath}: <span className="font-mono">{installed?.binaryPath}</span></span>
|
|
430
|
+
</div>
|
|
431
|
+
{/* Action buttons */}
|
|
432
|
+
<div className="flex items-center justify-end gap-2 pt-1">
|
|
433
|
+
{acpConfig.configs[agent.id] && (
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
onClick={handleReset}
|
|
437
|
+
className="inline-flex items-center gap-1 px-2 py-1 text-2xs font-medium rounded-md 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"
|
|
438
|
+
>
|
|
439
|
+
<RotateCcw size={10} />
|
|
440
|
+
{p.acpConfigReset}
|
|
441
|
+
</button>
|
|
442
|
+
)}
|
|
443
|
+
<button
|
|
444
|
+
type="button"
|
|
445
|
+
onClick={handleSave}
|
|
446
|
+
disabled={acpConfig.saving}
|
|
447
|
+
className="inline-flex items-center gap-1 px-2.5 py-1 text-2xs font-medium rounded-md bg-[var(--amber)] text-[var(--amber-foreground)] hover:bg-[var(--amber)]/90 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
448
|
+
>
|
|
449
|
+
{saveState === 'saved' ? <><Check size={10} /> {p.acpConfigSaved}</> : <><Save size={10} /> {p.acpConfigSave}</>}
|
|
450
|
+
</button>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
)}
|
|
454
|
+
|
|
455
|
+
{/* Quick action chips */}
|
|
456
|
+
{isReady && !configOpen && (
|
|
457
|
+
<div className="mt-2.5 pt-2 border-t border-border/40 flex items-center gap-1.5 flex-wrap">
|
|
458
|
+
{QUICK_ACTIONS.map((action) => {
|
|
459
|
+
const Icon = action.icon;
|
|
460
|
+
return (
|
|
461
|
+
<button
|
|
462
|
+
key={action.labelKey}
|
|
463
|
+
type="button"
|
|
464
|
+
onClick={() => handleQuickAction(action)}
|
|
465
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 text-2xs font-medium rounded-md bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground border border-border/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
466
|
+
>
|
|
467
|
+
<Icon size={10} />
|
|
468
|
+
{p[action.labelKey]}
|
|
469
|
+
</button>
|
|
470
|
+
);
|
|
471
|
+
})}
|
|
472
|
+
</div>
|
|
473
|
+
)}
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/* ────────── ACP Agent Compact Row (Not Installed — subtle) ────────── */
|
|
479
|
+
|
|
480
|
+
function AcpAgentCompactRow({ agent, installCmd, packageName, detectionDone, onInstalled }: {
|
|
481
|
+
agent: AcpRegistryEntry;
|
|
482
|
+
installCmd: string | null;
|
|
483
|
+
packageName: string | null;
|
|
484
|
+
detectionDone: boolean;
|
|
485
|
+
onInstalled: () => void;
|
|
486
|
+
}) {
|
|
487
|
+
const { t } = useLocale();
|
|
488
|
+
const p = t.panels.agents;
|
|
489
|
+
const [installState, setInstallState] = useState<'idle' | 'installing' | 'done' | 'error'>('idle');
|
|
490
|
+
|
|
491
|
+
const handleInstall = async () => {
|
|
492
|
+
if (!packageName || installState === 'installing') return;
|
|
493
|
+
setInstallState('installing');
|
|
494
|
+
try {
|
|
495
|
+
const res = await fetch('/api/acp/install', {
|
|
496
|
+
method: 'POST',
|
|
497
|
+
headers: { 'Content-Type': 'application/json' },
|
|
498
|
+
body: JSON.stringify({ agentId: agent.id, packageName }),
|
|
499
|
+
});
|
|
500
|
+
if (!res.ok) {
|
|
501
|
+
setInstallState('error');
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
// Wait a bit for npm install to complete, then re-detect
|
|
505
|
+
await new Promise((r) => setTimeout(r, 8000));
|
|
506
|
+
onInstalled();
|
|
507
|
+
setInstallState('done');
|
|
508
|
+
} catch {
|
|
509
|
+
setInstallState('error');
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
return (
|
|
514
|
+
<div className="group rounded-lg border border-border/60 bg-card/60 px-3 py-2 hover:border-border transition-all duration-150">
|
|
515
|
+
<div className="flex items-center gap-2">
|
|
516
|
+
<div className="w-6 h-6 rounded-md bg-muted/40 flex items-center justify-center shrink-0">
|
|
517
|
+
<Network size={11} className="text-muted-foreground/60" />
|
|
518
|
+
</div>
|
|
519
|
+
<div className="flex-1 min-w-0">
|
|
520
|
+
<p className="text-xs text-muted-foreground truncate">{agent.name}</p>
|
|
521
|
+
</div>
|
|
522
|
+
{detectionDone && (
|
|
523
|
+
<span className="text-2xs px-1.5 py-0.5 rounded font-medium shrink-0 bg-muted text-muted-foreground/60">
|
|
524
|
+
{p.acpNotInstalled}
|
|
525
|
+
</span>
|
|
526
|
+
)}
|
|
527
|
+
{detectionDone && packageName && (
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
disabled={installState === 'installing'}
|
|
531
|
+
onClick={handleInstall}
|
|
532
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 text-2xs font-medium rounded-md 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 disabled:opacity-50"
|
|
533
|
+
title={installCmd ? p.acpInstallHint(installCmd) : undefined}
|
|
534
|
+
>
|
|
535
|
+
{installState === 'installing' ? (
|
|
536
|
+
<><Loader2 size={10} className="animate-spin" /> {p.acpInstalling}</>
|
|
537
|
+
) : (
|
|
538
|
+
<><Download size={10} /> {p.acpInstall}</>
|
|
539
|
+
)}
|
|
540
|
+
</button>
|
|
541
|
+
)}
|
|
542
|
+
</div>
|
|
543
|
+
</div>
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* ────────── Delegation History Section ────────── */
|
|
548
|
+
|
|
549
|
+
function DelegationHistorySection({ delegations }: { delegations: DelegationRecord[] }) {
|
|
550
|
+
const { t } = useLocale();
|
|
551
|
+
const p = t.panels.agents;
|
|
552
|
+
|
|
553
|
+
return (
|
|
554
|
+
<div className="space-y-2">
|
|
555
|
+
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
556
|
+
{p.a2aDelegations}
|
|
557
|
+
</h3>
|
|
558
|
+
{delegations.length === 0 ? (
|
|
559
|
+
<p className="text-xs text-muted-foreground/70 py-3">{p.a2aDelegationsEmpty}</p>
|
|
560
|
+
) : (
|
|
561
|
+
<div className="space-y-1.5">
|
|
562
|
+
{delegations.map((d) => (
|
|
563
|
+
<DelegationRow key={d.id} record={d} />
|
|
564
|
+
))}
|
|
565
|
+
</div>
|
|
566
|
+
)}
|
|
567
|
+
</div>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* ────────── Delegation Row ────────── */
|
|
572
|
+
|
|
573
|
+
const STATUS_STYLES: Record<DelegationRecord['status'], string> = {
|
|
574
|
+
pending: 'bg-muted text-muted-foreground',
|
|
575
|
+
completed: 'bg-[var(--success)]/15 text-[var(--success)]',
|
|
576
|
+
failed: 'bg-[var(--error)]/15 text-[var(--error)]',
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
function DelegationRow({ record }: { record: DelegationRecord }) {
|
|
580
|
+
const { t } = useLocale();
|
|
581
|
+
const p = t.panels.agents;
|
|
582
|
+
const statusLabels: Record<DelegationRecord['status'], string> = {
|
|
583
|
+
pending: p.a2aDelegationPending,
|
|
584
|
+
completed: p.a2aDelegationCompleted,
|
|
585
|
+
failed: p.a2aDelegationFailed,
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const duration = record.completedAt
|
|
589
|
+
? formatDuration(new Date(record.startedAt), new Date(record.completedAt))
|
|
590
|
+
: null;
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
<div className="rounded-lg border border-border/60 bg-card/80 px-3 py-2.5 flex items-center gap-2.5">
|
|
594
|
+
<div className="flex-1 min-w-0">
|
|
595
|
+
<p className="text-xs font-medium text-foreground truncate">{record.agentName}</p>
|
|
596
|
+
<p className="text-2xs text-muted-foreground truncate" title={record.message}>
|
|
597
|
+
{record.message.length > 60 ? record.message.slice(0, 60) + '...' : record.message}
|
|
598
|
+
</p>
|
|
599
|
+
</div>
|
|
600
|
+
<span className={`text-2xs px-1.5 py-0.5 rounded font-medium shrink-0 ${STATUS_STYLES[record.status]}`}>
|
|
601
|
+
{statusLabels[record.status]}
|
|
602
|
+
</span>
|
|
603
|
+
{duration && (
|
|
604
|
+
<span className="text-2xs text-muted-foreground/60 shrink-0 flex items-center gap-0.5">
|
|
605
|
+
<Clock size={10} aria-hidden="true" />
|
|
606
|
+
{duration}
|
|
607
|
+
</span>
|
|
608
|
+
)}
|
|
609
|
+
</div>
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function formatDuration(start: Date, end: Date): string {
|
|
614
|
+
const ms = end.getTime() - start.getTime();
|
|
615
|
+
if (ms < 1000) return `${ms}ms`;
|
|
616
|
+
const secs = Math.round(ms / 1000);
|
|
617
|
+
if (secs < 60) return `${secs}s`;
|
|
618
|
+
const mins = Math.floor(secs / 60);
|
|
619
|
+
const remSecs = secs % 60;
|
|
620
|
+
return remSecs > 0 ? `${mins}m ${remSecs}s` : `${mins}m`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/* ────────── Remote Agent Row ────────── */
|
|
624
|
+
|
|
625
|
+
function RemoteAgentRow({
|
|
626
|
+
agent,
|
|
627
|
+
onRemove,
|
|
628
|
+
removeCopy,
|
|
629
|
+
skillsCopy,
|
|
630
|
+
}: {
|
|
631
|
+
agent: RemoteAgent;
|
|
632
|
+
onRemove: (id: string) => void;
|
|
633
|
+
removeCopy: string;
|
|
634
|
+
skillsCopy: string;
|
|
635
|
+
}) {
|
|
636
|
+
const StatusIcon = agent.reachable ? Wifi : WifiOff;
|
|
637
|
+
const statusColor = agent.reachable
|
|
638
|
+
? 'text-[var(--success)]'
|
|
639
|
+
: 'text-muted-foreground/50';
|
|
640
|
+
|
|
641
|
+
return (
|
|
642
|
+
<div className="group rounded-xl border border-border bg-card p-3.5 hover:border-[var(--amber)]/30 transition-all duration-150">
|
|
643
|
+
<div className="flex items-center gap-2.5">
|
|
644
|
+
<div className="w-8 h-8 rounded-lg bg-muted/50 flex items-center justify-center shrink-0">
|
|
645
|
+
<Globe size={14} className="text-muted-foreground" />
|
|
646
|
+
</div>
|
|
647
|
+
<div className="flex-1 min-w-0">
|
|
648
|
+
<p className="text-sm font-medium text-foreground truncate">{agent.card.name}</p>
|
|
649
|
+
<p className="text-2xs text-muted-foreground truncate">{agent.card.description}</p>
|
|
650
|
+
</div>
|
|
651
|
+
<StatusIcon size={13} className={statusColor} aria-hidden="true" />
|
|
652
|
+
<button
|
|
653
|
+
type="button"
|
|
654
|
+
onClick={() => onRemove(agent.id)}
|
|
655
|
+
className="p-1.5 rounded-md text-muted-foreground/50 hover:text-error hover:bg-error/10 transition-colors opacity-0 group-hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:opacity-100"
|
|
656
|
+
aria-label={removeCopy}
|
|
657
|
+
title={removeCopy}
|
|
658
|
+
>
|
|
659
|
+
<Trash2 size={12} />
|
|
660
|
+
</button>
|
|
661
|
+
</div>
|
|
662
|
+
{agent.card.skills.length > 0 && (
|
|
663
|
+
<div className="mt-2.5 pt-2 border-t border-border/40 flex items-center gap-1.5">
|
|
664
|
+
<Zap size={11} className="text-muted-foreground/60 shrink-0" aria-hidden="true" />
|
|
665
|
+
<span className="text-2xs text-muted-foreground">{skillsCopy}: {agent.card.skills.length}</span>
|
|
666
|
+
<div className="flex flex-wrap gap-1 ml-1">
|
|
667
|
+
{agent.card.skills.slice(0, 3).map((s) => (
|
|
668
|
+
<span
|
|
669
|
+
key={s.id}
|
|
670
|
+
className="text-2xs px-1.5 py-0.5 rounded bg-muted/80 text-muted-foreground border border-border/50"
|
|
671
|
+
title={s.description}
|
|
672
|
+
>
|
|
673
|
+
{s.name}
|
|
674
|
+
</span>
|
|
675
|
+
))}
|
|
676
|
+
{agent.card.skills.length > 3 && (
|
|
677
|
+
<span className="text-2xs text-muted-foreground/60">+{agent.card.skills.length - 3}</span>
|
|
678
|
+
)}
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
)}
|
|
682
|
+
</div>
|
|
683
|
+
);
|
|
684
|
+
}
|