@geminilight/mindos 0.6.29 → 0.6.31
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/README_zh.md +10 -4
- package/app/app/api/acp/config/route.ts +82 -0
- package/app/app/api/acp/detect/route.ts +71 -48
- package/app/app/api/acp/install/route.ts +51 -0
- package/app/app/api/acp/session/route.ts +141 -11
- package/app/app/api/ask/route.ts +126 -18
- package/app/app/api/export/route.ts +105 -0
- package/app/app/api/workflows/route.ts +156 -0
- package/app/app/globals.css +2 -2
- package/app/app/page.tsx +7 -2
- package/app/app/trash/page.tsx +7 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
- package/app/components/ActivityBar.tsx +12 -4
- package/app/components/AskModal.tsx +4 -1
- package/app/components/ExportModal.tsx +220 -0
- package/app/components/FileTree.tsx +42 -11
- package/app/components/HomeContent.tsx +92 -20
- package/app/components/MarkdownView.tsx +45 -10
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAskPanel.tsx +5 -1
- package/app/components/Sidebar.tsx +10 -1
- package/app/components/SidebarLayout.tsx +6 -0
- package/app/components/TrashPageClient.tsx +263 -0
- package/app/components/agents/AgentDetailContent.tsx +263 -47
- package/app/components/agents/AgentsContentPage.tsx +11 -0
- package/app/components/agents/AgentsPanelA2aTab.tsx +285 -46
- package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
- 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/ask/ToolCallBlock.tsx +102 -18
- package/app/components/changes/ChangesContentPage.tsx +58 -14
- package/app/components/explore/ExploreContent.tsx +4 -7
- package/app/components/explore/UseCaseCard.tsx +18 -1
- package/app/components/explore/use-cases.generated.ts +76 -0
- package/app/components/explore/use-cases.yaml +185 -0
- package/app/components/panels/AgentsPanel.tsx +1 -0
- package/app/components/panels/AgentsPanelHubNav.tsx +9 -2
- package/app/components/panels/DiscoverPanel.tsx +1 -1
- package/app/components/panels/WorkflowsPanel.tsx +206 -0
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +164 -0
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +211 -0
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +269 -0
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
- package/app/components/renderers/workflow-yaml/execution.ts +229 -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 +574 -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/AiTab.tsx +191 -174
- package/app/components/settings/AppearanceTab.tsx +168 -77
- package/app/components/settings/KnowledgeTab.tsx +131 -136
- package/app/components/settings/McpTab.tsx +11 -11
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/SettingsContent.tsx +15 -8
- package/app/components/settings/SyncTab.tsx +12 -12
- package/app/components/settings/UninstallTab.tsx +8 -18
- package/app/components/settings/UpdateTab.tsx +82 -82
- package/app/components/settings/types.ts +17 -8
- package/app/hooks/useAcpConfig.ts +96 -0
- package/app/hooks/useAcpDetection.ts +69 -14
- package/app/hooks/useAcpRegistry.ts +46 -11
- package/app/hooks/useAskModal.ts +12 -5
- package/app/hooks/useAskPanel.ts +8 -5
- package/app/hooks/useAskSession.ts +19 -2
- package/app/hooks/useImageUpload.ts +152 -0
- package/app/lib/acp/acp-tools.ts +3 -1
- package/app/lib/acp/agent-descriptors.ts +274 -0
- package/app/lib/acp/bridge.ts +6 -0
- package/app/lib/acp/index.ts +20 -4
- package/app/lib/acp/registry.ts +74 -7
- package/app/lib/acp/session.ts +490 -28
- package/app/lib/acp/subprocess.ts +307 -21
- package/app/lib/acp/types.ts +158 -20
- package/app/lib/actions.ts +57 -3
- package/app/lib/agent/model.ts +18 -3
- package/app/lib/agent/stream-consumer.ts +18 -0
- package/app/lib/agent/to-agent-messages.ts +25 -2
- package/app/lib/agent/tools.ts +56 -9
- package/app/lib/core/export.ts +116 -0
- package/app/lib/core/trash.ts +241 -0
- package/app/lib/fs.ts +47 -0
- package/app/lib/hooks/usePinnedFiles.ts +90 -0
- package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
- package/app/lib/i18n/index.ts +3 -0
- package/app/lib/i18n/modules/knowledge.ts +124 -6
- package/app/lib/i18n/modules/navigation.ts +2 -0
- package/app/lib/i18n/modules/onboarding.ts +2 -134
- package/app/lib/i18n/modules/panels.ts +146 -2
- package/app/lib/i18n/modules/settings.ts +12 -0
- 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/types.ts +12 -1
- package/app/next-env.d.ts +1 -1
- package/app/package.json +11 -3
- package/app/scripts/generate-explore.ts +145 -0
- 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/explore/use-cases.ts +0 -58
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
- package/app/components/renderers/workflow/manifest.ts +0 -14
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Clock, Globe, Loader2, Network, RefreshCw, Trash2, Wifi, WifiOff, Zap } from 'lucide-react';
|
|
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
5
|
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
+
import { useAcpConfig } from '@/hooks/useAcpConfig';
|
|
6
7
|
import type { RemoteAgent, DelegationRecord } from '@/lib/a2a/types';
|
|
7
8
|
import type { AcpRegistryEntry } from '@/lib/acp/types';
|
|
8
9
|
import { useDelegationHistory } from '@/hooks/useDelegationHistory';
|
|
@@ -138,6 +139,20 @@ function NetworkEmptyState({
|
|
|
138
139
|
);
|
|
139
140
|
}
|
|
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
|
+
|
|
141
156
|
/* ────────── ACP Registry Section ────────── */
|
|
142
157
|
|
|
143
158
|
function AcpRegistrySection() {
|
|
@@ -145,6 +160,7 @@ function AcpRegistrySection() {
|
|
|
145
160
|
const p = t.panels.agents;
|
|
146
161
|
const acp = useAcpRegistry();
|
|
147
162
|
const detection = useAcpDetection();
|
|
163
|
+
const acpConfig = useAcpConfig();
|
|
148
164
|
|
|
149
165
|
if (acp.loading) {
|
|
150
166
|
return (
|
|
@@ -182,8 +198,26 @@ function AcpRegistrySection() {
|
|
|
182
198
|
|
|
183
199
|
if (acp.agents.length === 0) return null;
|
|
184
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
|
+
|
|
185
219
|
return (
|
|
186
|
-
<div className="space-y-
|
|
220
|
+
<div className="space-y-3">
|
|
187
221
|
<div className="flex items-center justify-between">
|
|
188
222
|
<h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
189
223
|
{p.acpSectionTitle}
|
|
@@ -203,26 +237,45 @@ function AcpRegistrySection() {
|
|
|
203
237
|
</span>
|
|
204
238
|
</div>
|
|
205
239
|
</div>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<
|
|
240
|
+
|
|
241
|
+
{/* Installed agents — prominent cards */}
|
|
242
|
+
{installedAgents.length > 0 && (
|
|
243
|
+
<div className="space-y-2">
|
|
244
|
+
{installedAgents.map(({ agent, info }) => (
|
|
245
|
+
<AcpAgentCard
|
|
212
246
|
key={agent.id}
|
|
213
247
|
agent={agent}
|
|
214
|
-
installed={
|
|
215
|
-
installCmd={
|
|
248
|
+
installed={info}
|
|
249
|
+
installCmd={null}
|
|
250
|
+
packageName={null}
|
|
216
251
|
detectionDone={!detection.loading}
|
|
252
|
+
onInstalled={detection.refresh}
|
|
253
|
+
acpConfig={acpConfig}
|
|
217
254
|
/>
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
)}
|
|
221
274
|
</div>
|
|
222
275
|
);
|
|
223
276
|
}
|
|
224
277
|
|
|
225
|
-
/* ────────── ACP Agent
|
|
278
|
+
/* ────────── ACP Agent Card (Installed — prominent) ────────── */
|
|
226
279
|
|
|
227
280
|
const TRANSPORT_STYLES: Record<string, string> = {
|
|
228
281
|
npx: 'bg-[var(--amber)]/15 text-[var(--amber)]',
|
|
@@ -231,14 +284,21 @@ const TRANSPORT_STYLES: Record<string, string> = {
|
|
|
231
284
|
stdio: 'bg-muted text-muted-foreground',
|
|
232
285
|
};
|
|
233
286
|
|
|
234
|
-
function
|
|
287
|
+
function AcpAgentCard({ agent, installed, detectionDone, acpConfig }: {
|
|
235
288
|
agent: AcpRegistryEntry;
|
|
236
|
-
installed: { id: string; name: string; binaryPath: string } | null;
|
|
289
|
+
installed: { id: string; name: string; binaryPath: string; resolvedCommand?: { cmd: string; args: string[]; source: string } } | null;
|
|
237
290
|
installCmd: string | null;
|
|
291
|
+
packageName: string | null;
|
|
238
292
|
detectionDone: boolean;
|
|
293
|
+
onInstalled: () => void;
|
|
294
|
+
acpConfig: ReturnType<typeof useAcpConfig>;
|
|
239
295
|
}) {
|
|
240
296
|
const { t } = useLocale();
|
|
241
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');
|
|
242
302
|
const transportLabels: Record<string, string> = {
|
|
243
303
|
npx: p.acpTransportNpx,
|
|
244
304
|
binary: p.acpTransportBinary,
|
|
@@ -247,24 +307,61 @@ function AcpAgentRow({ agent, installed, installCmd, detectionDone }: {
|
|
|
247
307
|
};
|
|
248
308
|
|
|
249
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]);
|
|
250
342
|
|
|
251
343
|
const handleUse = () => {
|
|
252
|
-
openAskModal(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
);
|
|
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 });
|
|
258
349
|
};
|
|
259
350
|
|
|
260
351
|
return (
|
|
261
|
-
<div className="
|
|
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 */}
|
|
262
354
|
<div className="flex items-center gap-2.5">
|
|
263
|
-
<div className="w-8 h-8 rounded-lg bg-
|
|
264
|
-
<Network size={14} className="text-
|
|
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)]" />
|
|
265
357
|
</div>
|
|
266
358
|
<div className="flex-1 min-w-0">
|
|
267
|
-
<
|
|
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>
|
|
268
365
|
{agent.description && (
|
|
269
366
|
<p className="text-2xs text-muted-foreground truncate">{agent.description}</p>
|
|
270
367
|
)}
|
|
@@ -273,34 +370,176 @@ function AcpAgentRow({ agent, installed, installCmd, detectionDone }: {
|
|
|
273
370
|
{transportLabels[agent.transport] ?? agent.transport}
|
|
274
371
|
</span>
|
|
275
372
|
{detectionDone && (
|
|
276
|
-
<span className=
|
|
277
|
-
|
|
278
|
-
? 'bg-[var(--success)]/15 text-[var(--success)]'
|
|
279
|
-
: 'bg-muted text-muted-foreground/60'
|
|
280
|
-
}`}>
|
|
281
|
-
{isReady ? p.acpReady : p.acpNotInstalled}
|
|
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}
|
|
282
375
|
</span>
|
|
283
376
|
)}
|
|
284
377
|
<button
|
|
285
378
|
type="button"
|
|
286
|
-
|
|
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"
|
|
287
388
|
onClick={handleUse}
|
|
288
|
-
className=
|
|
289
|
-
isReady
|
|
290
|
-
? 'border-[var(--amber)] text-[var(--amber)] hover:bg-[var(--amber)]/10 cursor-pointer'
|
|
291
|
-
: 'border-border text-muted-foreground/50 cursor-not-allowed'
|
|
292
|
-
}`}
|
|
293
|
-
title={
|
|
294
|
-
isReady
|
|
295
|
-
? undefined
|
|
296
|
-
: installCmd
|
|
297
|
-
? p.acpInstallHint(installCmd)
|
|
298
|
-
: p.acpComingSoon
|
|
299
|
-
}
|
|
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"
|
|
300
390
|
>
|
|
391
|
+
<Zap size={10} />
|
|
301
392
|
{p.acpUseAgent}
|
|
302
393
|
</button>
|
|
303
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>
|
|
304
543
|
</div>
|
|
305
544
|
);
|
|
306
545
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { History, Play, Trash2 } from 'lucide-react';
|
|
5
|
+
import type { AcpSession } from '@/lib/acp/types';
|
|
6
|
+
|
|
7
|
+
interface SessionEntry {
|
|
8
|
+
id: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
state: string;
|
|
11
|
+
cwd?: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
lastActivityAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function AgentsPanelSessionsTab() {
|
|
17
|
+
const [sessions, setSessions] = useState<SessionEntry[]>([]);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
const [error, setError] = useState<string | null>(null);
|
|
20
|
+
|
|
21
|
+
const fetchSessions = useCallback(async () => {
|
|
22
|
+
try {
|
|
23
|
+
setLoading(true);
|
|
24
|
+
const res = await fetch('/api/acp/session');
|
|
25
|
+
if (!res.ok) throw new Error(`Failed to fetch sessions: ${res.status}`);
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
setSessions(data.sessions ?? []);
|
|
28
|
+
setError(null);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
setError((err as Error).message);
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
useEffect(() => { fetchSessions(); }, [fetchSessions]);
|
|
37
|
+
|
|
38
|
+
const handleClose = useCallback(async (sessionId: string) => {
|
|
39
|
+
try {
|
|
40
|
+
await fetch('/api/acp/session', {
|
|
41
|
+
method: 'DELETE',
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
body: JSON.stringify({ sessionId }),
|
|
44
|
+
});
|
|
45
|
+
setSessions(prev => prev.filter(s => s.id !== sessionId));
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('Failed to close session:', err);
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
if (loading) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="space-y-3 animate-pulse">
|
|
54
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
55
|
+
<div key={i} className="rounded-lg border border-border bg-card p-4">
|
|
56
|
+
<div className="flex items-center gap-3">
|
|
57
|
+
<div className="w-8 h-8 rounded-full bg-muted" />
|
|
58
|
+
<div className="flex-1 space-y-2">
|
|
59
|
+
<div className="h-4 w-32 bg-muted rounded" />
|
|
60
|
+
<div className="h-3 w-48 bg-muted rounded" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (error) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="rounded-lg border border-border bg-card p-6 text-center">
|
|
72
|
+
<p className="text-sm text-muted-foreground mb-3">{error}</p>
|
|
73
|
+
<button
|
|
74
|
+
onClick={fetchSessions}
|
|
75
|
+
className="text-sm text-[var(--amber)] hover:underline"
|
|
76
|
+
>
|
|
77
|
+
Retry
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (sessions.length === 0) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="rounded-lg border border-border bg-card p-8 text-center">
|
|
86
|
+
<History size={32} className="mx-auto mb-3 text-muted-foreground/40" />
|
|
87
|
+
<p className="text-sm font-medium text-foreground mb-1">No active sessions</p>
|
|
88
|
+
<p className="text-xs text-muted-foreground">
|
|
89
|
+
Sessions appear here when you chat with ACP agents.
|
|
90
|
+
<br />
|
|
91
|
+
Select an agent from the Network tab and send a message to start.
|
|
92
|
+
</p>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className="space-y-2">
|
|
99
|
+
<div className="flex items-center justify-between mb-3">
|
|
100
|
+
<p className="text-xs text-muted-foreground font-medium uppercase tracking-wider">
|
|
101
|
+
Active Sessions ({sessions.length})
|
|
102
|
+
</p>
|
|
103
|
+
<button
|
|
104
|
+
onClick={fetchSessions}
|
|
105
|
+
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
106
|
+
>
|
|
107
|
+
Refresh
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{sessions.map(session => (
|
|
112
|
+
<div
|
|
113
|
+
key={session.id}
|
|
114
|
+
className="group rounded-lg border border-border bg-card hover:border-[var(--amber)]/30 transition-colors p-3.5"
|
|
115
|
+
>
|
|
116
|
+
<div className="flex items-start justify-between gap-2">
|
|
117
|
+
<div className="flex-1 min-w-0">
|
|
118
|
+
<div className="flex items-center gap-2 mb-1">
|
|
119
|
+
<span className={`inline-block w-2 h-2 rounded-full ${
|
|
120
|
+
session.state === 'active' ? 'bg-[var(--success)]' :
|
|
121
|
+
session.state === 'error' ? 'bg-[var(--error)]' :
|
|
122
|
+
'bg-muted-foreground/40'
|
|
123
|
+
}`} />
|
|
124
|
+
<span className="text-sm font-medium text-foreground truncate">
|
|
125
|
+
{session.agentId}
|
|
126
|
+
</span>
|
|
127
|
+
<span className="text-2xs text-muted-foreground/60 px-1.5 py-0.5 rounded bg-muted/40">
|
|
128
|
+
{session.state}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
{session.cwd && (
|
|
132
|
+
<p className="text-xs text-muted-foreground truncate font-mono">
|
|
133
|
+
{session.cwd}
|
|
134
|
+
</p>
|
|
135
|
+
)}
|
|
136
|
+
<p className="text-2xs text-muted-foreground/60 mt-1">
|
|
137
|
+
{formatRelativeTime(session.lastActivityAt)}
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
142
|
+
<button
|
|
143
|
+
onClick={() => handleClose(session.id)}
|
|
144
|
+
className="p-1.5 rounded-md hover:bg-muted/60 text-muted-foreground hover:text-[var(--error)] transition-colors"
|
|
145
|
+
title="Close session"
|
|
146
|
+
>
|
|
147
|
+
<Trash2 size={14} />
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function formatRelativeTime(isoString: string): string {
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
const then = new Date(isoString).getTime();
|
|
160
|
+
const diff = now - then;
|
|
161
|
+
|
|
162
|
+
if (diff < 60_000) return 'just now';
|
|
163
|
+
if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m ago`;
|
|
164
|
+
if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h ago`;
|
|
165
|
+
return `${Math.floor(diff / 86400_000)}d ago`;
|
|
166
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentInfo, SkillInfo } from '@/components/settings/types';
|
|
2
2
|
|
|
3
|
-
export type AgentsDashboardTab = 'overview' | 'mcp' | 'skills' | 'a2a';
|
|
3
|
+
export type AgentsDashboardTab = 'overview' | 'mcp' | 'skills' | 'a2a' | 'sessions';
|
|
4
4
|
export type AgentResolvedStatus = 'connected' | 'detected' | 'notFound';
|
|
5
5
|
export type SkillCapability = 'research' | 'coding' | 'docs' | 'ops' | 'memory';
|
|
6
6
|
export type SkillSourceFilter = 'all' | 'builtin' | 'user';
|
|
@@ -24,7 +24,7 @@ export interface AgentBuckets {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function parseAgentsTab(tab: string | undefined): AgentsDashboardTab {
|
|
27
|
-
if (tab === 'mcp' || tab === 'skills' || tab === 'a2a') return tab;
|
|
27
|
+
if (tab === 'mcp' || tab === 'skills' || tab === 'a2a' || tab === 'sessions') return tab;
|
|
28
28
|
return 'overview';
|
|
29
29
|
}
|
|
30
30
|
|