@geminilight/mindos 0.6.67 → 0.6.69
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/_standalone/.mindos-build-version +1 -1
- package/_standalone/.next/BUILD_ID +1 -1
- package/_standalone/.next/app-path-routes-manifest.json +17 -17
- package/_standalone/.next/build-manifest.json +3 -3
- package/_standalone/.next/cache/.previewinfo +1 -1
- package/_standalone/.next/cache/.rscinfo +1 -1
- package/_standalone/.next/cache/config.json +3 -3
- package/_standalone/.next/prerender-manifest.json +3 -3
- package/_standalone/.next/react-loadable-manifest.json +4 -4
- package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/_global-error.html +2 -2
- package/_standalone/.next/server/app/_global-error.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/_standalone/.next/server/app/_not-found/page.js +1 -1
- package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/agents/page.js +1 -1
- package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route.js +1 -1
- package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route.js +1 -1
- package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask/route.js +8 -8
- package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/connect/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/config/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/im/test/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/clip/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/lint/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/direct-tools/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/tools/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/space-overview/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changelog/page.js +1 -1
- package/_standalone/.next/server/app/changelog/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changelog/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/changes/page.js +1 -1
- package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/echo/page.js +1 -1
- package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/explore/page.js +1 -1
- package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/help/page.js +1 -1
- package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js +1 -1
- package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/login/page.js +1 -1
- package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/page.js +1 -1
- package/_standalone/.next/server/app/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/setup/page.js +1 -1
- package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/trash/page.js +2 -2
- package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
- package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js +1 -1
- package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
- package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
- package/_standalone/.next/server/app-paths-manifest.json +17 -17
- package/_standalone/.next/server/chunks/1750.js +1 -1
- package/_standalone/.next/server/chunks/6022.js +31 -31
- package/_standalone/.next/server/chunks/6539.js +1 -1
- package/_standalone/.next/server/chunks/{8947.js → 7200.js} +3 -3
- package/_standalone/.next/server/chunks/953.js +1 -1
- package/_standalone/.next/server/middleware-build-manifest.js +1 -1
- package/_standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/_standalone/.next/server/pages/500.html +2 -2
- package/_standalone/.next/server/server-reference-manifest.js +1 -1
- package/_standalone/.next/server/server-reference-manifest.json +1 -1
- package/_standalone/.next/static/chunks/{5998.7bd28de9747440b5.js → 3466.bc6ab3172ad66091.js} +1 -1
- package/_standalone/.next/static/chunks/{476.463546c195b89cce.js → 8735.66a049abcf0971fb.js} +2 -2
- package/_standalone/.next/static/chunks/app/layout-bd0652768781e726.js +133 -0
- package/_standalone/.next/static/chunks/app/trash/page-8280ba2f5c20861e.js +1 -0
- package/_standalone/.next/static/chunks/app/view/[...path]/page-b942374e2f88c53e.js +12 -0
- package/_standalone/.next/static/chunks/webpack-9e42857d6c44fe70.js +1 -0
- package/_standalone/.next/trace +71 -71
- package/_standalone/__tests__/acp/agent-descriptors.test.ts +65 -3
- package/_standalone/__tests__/acp/registry.test.ts +34 -5
- package/_standalone/__tests__/acp/session.test.ts +1 -1
- package/_standalone/components/Breadcrumb.tsx +11 -11
- package/_standalone/components/panels/AgentsPanel.tsx +8 -1
- package/_standalone/lib/acp/index.ts +2 -0
- package/_standalone/package-lock.json +2 -2
- package/_standalone/package.json +1 -1
- package/_standalone/tsconfig.tsbuildinfo +1 -1
- package/app/app/api/acp/detect/route.ts +9 -15
- package/app/components/Breadcrumb.tsx +11 -11
- package/app/components/panels/AgentsPanel.tsx +8 -1
- package/app/lib/acp/agent-descriptors.ts +51 -29
- package/app/lib/acp/index.ts +2 -0
- package/app/lib/acp/registry.ts +42 -46
- package/app/lib/acp/session.ts +36 -7
- package/app/lib/acp/subprocess.ts +40 -2
- package/app/lib/agent/tools.ts +4 -3
- package/app/package.json +1 -1
- package/package.json +1 -1
- package/_standalone/.next/static/chunks/app/layout-9bb19a959ffb87ac.js +0 -133
- package/_standalone/.next/static/chunks/app/trash/page-bebb28bf472cf691.js +0 -1
- package/_standalone/.next/static/chunks/app/view/[...path]/page-dd5698f3df138835.js +0 -12
- package/_standalone/.next/static/chunks/webpack-043f40ef7816d8c4.js +0 -1
- /package/_standalone/.next/static/{0JtsgqDZLSJ6MrIZIV6gC → HL8b6d3NCfyoAXNuY2kcn}/_buildManifest.js +0 -0
- /package/_standalone/.next/static/{0JtsgqDZLSJ6MrIZIV6gC → HL8b6d3NCfyoAXNuY2kcn}/_ssgManifest.js +0 -0
|
@@ -2,8 +2,7 @@ export const dynamic = 'force-dynamic';
|
|
|
2
2
|
|
|
3
3
|
import { NextResponse } from 'next/server';
|
|
4
4
|
import { exec } from 'child_process';
|
|
5
|
-
import {
|
|
6
|
-
import { getDescriptorBinary, getDescriptorInstallCmd, resolveAgentCommand } from '@/lib/acp/agent-descriptors';
|
|
5
|
+
import { getDetectableAgents, resolveAgentCommand } from '@/lib/acp/agent-descriptors';
|
|
7
6
|
import { readSettings } from '@/lib/settings';
|
|
8
7
|
import { handleRouteErrorSimple } from '@/lib/errors';
|
|
9
8
|
|
|
@@ -43,8 +42,6 @@ function whichBatch(binaries: string[]): Promise<Map<string, string | null>> {
|
|
|
43
42
|
const unique = [...new Set(binaries)];
|
|
44
43
|
if (unique.length === 0) return Promise.resolve(new Map());
|
|
45
44
|
|
|
46
|
-
// Build a shell snippet: for each binary, print path or empty line
|
|
47
|
-
// e.g. `which gemini 2>/dev/null || echo ""; which claude 2>/dev/null || echo ""`
|
|
48
45
|
const script = unique
|
|
49
46
|
.map(bin => `which ${bin} 2>/dev/null || echo ""`)
|
|
50
47
|
.join('; ');
|
|
@@ -53,7 +50,6 @@ function whichBatch(binaries: string[]): Promise<Map<string, string | null>> {
|
|
|
53
50
|
exec(script, { encoding: 'utf-8', timeout: 3000 }, (err, stdout) => {
|
|
54
51
|
const map = new Map<string, string | null>();
|
|
55
52
|
if (err) {
|
|
56
|
-
// On total failure, mark all as not found
|
|
57
53
|
for (const bin of unique) map.set(bin, null);
|
|
58
54
|
resolve(map);
|
|
59
55
|
return;
|
|
@@ -77,22 +73,22 @@ export async function GET(req: Request) {
|
|
|
77
73
|
return NextResponse.json(detectCache.data);
|
|
78
74
|
}
|
|
79
75
|
|
|
80
|
-
|
|
76
|
+
// Pure local detection — no CDN fetch, instant response
|
|
77
|
+
const agents = getDetectableAgents();
|
|
81
78
|
const settings = readSettings();
|
|
82
79
|
|
|
83
|
-
const binaryNames = agents.map(
|
|
80
|
+
const binaryNames = [...new Set(agents.map(a => a.binary))];
|
|
84
81
|
const whichMap = await whichBatch(binaryNames);
|
|
85
82
|
|
|
86
83
|
const installed: InstalledAgent[] = [];
|
|
87
84
|
const notInstalled: NotInstalledAgent[] = [];
|
|
88
85
|
|
|
89
86
|
for (const agent of agents) {
|
|
90
|
-
const
|
|
91
|
-
const binaryPath = binary ? (whichMap.get(binary) ?? null) : null;
|
|
87
|
+
const binaryPath = whichMap.get(agent.binary) ?? null;
|
|
92
88
|
|
|
93
89
|
if (binaryPath) {
|
|
94
90
|
const userOverride = settings.acpAgents?.[agent.id];
|
|
95
|
-
const resolved = resolveAgentCommand(agent.id,
|
|
91
|
+
const resolved = resolveAgentCommand(agent.id, undefined, userOverride);
|
|
96
92
|
installed.push({
|
|
97
93
|
id: agent.id,
|
|
98
94
|
name: agent.name,
|
|
@@ -100,14 +96,12 @@ export async function GET(req: Request) {
|
|
|
100
96
|
resolvedCommand: { cmd: resolved.cmd, args: resolved.args, source: resolved.source },
|
|
101
97
|
});
|
|
102
98
|
} else {
|
|
103
|
-
const installCmd
|
|
104
|
-
getDescriptorInstallCmd(agent.id) ??
|
|
105
|
-
(agent.packageName ? `npm install -g ${agent.packageName}` : '');
|
|
99
|
+
const packageName = agent.installCmd?.match(/npm install -g (.+)/)?.[1];
|
|
106
100
|
notInstalled.push({
|
|
107
101
|
id: agent.id,
|
|
108
102
|
name: agent.name,
|
|
109
|
-
installCmd,
|
|
110
|
-
packageName
|
|
103
|
+
installCmd: agent.installCmd ?? (packageName ? `npm install -g ${packageName}` : ''),
|
|
104
|
+
packageName,
|
|
111
105
|
});
|
|
112
106
|
}
|
|
113
107
|
}
|
|
@@ -21,29 +21,29 @@ export default function Breadcrumb({ filePath }: { filePath: string }) {
|
|
|
21
21
|
|
|
22
22
|
if (friendly) {
|
|
23
23
|
return (
|
|
24
|
-
<nav className="flex items-center gap-
|
|
24
|
+
<nav className="flex items-center gap-2 text-xs text-muted-foreground flex-wrap">
|
|
25
25
|
<Link
|
|
26
26
|
href="/"
|
|
27
|
-
className="p-1.5 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors"
|
|
27
|
+
className="p-1.5 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors shrink-0"
|
|
28
28
|
title="Home"
|
|
29
29
|
>
|
|
30
30
|
<Home size={14} />
|
|
31
31
|
</Link>
|
|
32
32
|
<ChevronRight size={12} className="text-muted-foreground/50 shrink-0" />
|
|
33
|
-
<
|
|
33
|
+
<div className="flex items-center gap-1.5 text-foreground font-medium shrink-0">
|
|
34
34
|
{friendly.icon}
|
|
35
35
|
<span>{friendly.getLabel(t)}</span>
|
|
36
|
-
</
|
|
36
|
+
</div>
|
|
37
37
|
</nav>
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const parts = filePath.split('/');
|
|
42
42
|
return (
|
|
43
|
-
<nav className="flex items-center gap-
|
|
43
|
+
<nav className="flex items-center gap-2 text-xs text-muted-foreground flex-wrap">
|
|
44
44
|
<Link
|
|
45
45
|
href="/"
|
|
46
|
-
className="p-1.5 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors"
|
|
46
|
+
className="p-1.5 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors shrink-0"
|
|
47
47
|
title="Home"
|
|
48
48
|
>
|
|
49
49
|
<Home size={14} />
|
|
@@ -52,19 +52,19 @@ export default function Breadcrumb({ filePath }: { filePath: string }) {
|
|
|
52
52
|
const isLast = i === parts.length - 1;
|
|
53
53
|
const href = '/view/' + parts.slice(0, i + 1).map(encodeURIComponent).join('/');
|
|
54
54
|
return (
|
|
55
|
-
<
|
|
55
|
+
<div key={i} className="flex items-center gap-2 min-w-0">
|
|
56
56
|
<ChevronRight size={12} className="text-muted-foreground/50 shrink-0" />
|
|
57
57
|
{isLast ? (
|
|
58
|
-
<
|
|
58
|
+
<div className="flex items-center gap-1.5 text-foreground font-medium shrink-0">
|
|
59
59
|
<FileTypeIcon name={part} />
|
|
60
60
|
<span suppressHydrationWarning>{part}</span>
|
|
61
|
-
</
|
|
61
|
+
</div>
|
|
62
62
|
) : (
|
|
63
|
-
<Link href={href} className="px-2 py-1 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors truncate
|
|
63
|
+
<Link href={href} className="px-2 py-1 rounded-md hover:bg-muted/50 hover:text-foreground transition-colors truncate text-muted-foreground hover:text-foreground" title={part}>
|
|
64
64
|
<span suppressHydrationWarning>{part}</span>
|
|
65
65
|
</Link>
|
|
66
66
|
)}
|
|
67
|
-
</
|
|
67
|
+
</div>
|
|
68
68
|
);
|
|
69
69
|
})}
|
|
70
70
|
</nav>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { usePathname, useSearchParams } from 'next/navigation';
|
|
4
|
+
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
|
5
5
|
import { Globe, Loader2, RefreshCw, Settings } from 'lucide-react';
|
|
6
6
|
import { useMcpData } from '@/lib/stores/mcp-store';
|
|
7
7
|
import { useA2aRegistry } from '@/hooks/useA2aRegistry';
|
|
@@ -27,6 +27,7 @@ export default function AgentsPanel({
|
|
|
27
27
|
}: AgentsPanelProps) {
|
|
28
28
|
const { t } = useLocale();
|
|
29
29
|
const p = t.panels.agents;
|
|
30
|
+
const router = useRouter();
|
|
30
31
|
const mcp = useMcpData();
|
|
31
32
|
const pathname = usePathname();
|
|
32
33
|
const searchParams = useSearchParams();
|
|
@@ -42,6 +43,10 @@ export default function AgentsPanel({
|
|
|
42
43
|
setRefreshing(false);
|
|
43
44
|
};
|
|
44
45
|
|
|
46
|
+
const handleChannelsClick = () => {
|
|
47
|
+
router.push('/agents?tab=channels');
|
|
48
|
+
};
|
|
49
|
+
|
|
45
50
|
const openAdvancedConfig = () => {
|
|
46
51
|
window.dispatchEvent(new CustomEvent('mindos:open-settings', { detail: { tab: 'mcp' } }));
|
|
47
52
|
};
|
|
@@ -84,6 +89,8 @@ export default function AgentsPanel({
|
|
|
84
89
|
copy={hubCopy}
|
|
85
90
|
connectedCount={connected.length}
|
|
86
91
|
mcpEnabled={mcp.status?.connectionMode?.mcp ?? false}
|
|
92
|
+
channelsActive={isChannelsTab}
|
|
93
|
+
onChannelsClick={handleChannelsClick}
|
|
87
94
|
/>
|
|
88
95
|
);
|
|
89
96
|
|
|
@@ -50,45 +50,45 @@ export interface ResolvedAgentCommand {
|
|
|
50
50
|
enabled: boolean;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/* ── Aliases ───────────────────────────────────────────────────────────── */
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Maps alternative agent IDs to their canonical ID in AGENT_DESCRIPTORS.
|
|
57
|
+
* This eliminates full duplicate entries while maintaining backward compatibility.
|
|
58
|
+
*/
|
|
59
|
+
export const AGENT_ALIASES: Record<string, string> = {
|
|
60
|
+
'gemini-cli': 'gemini',
|
|
61
|
+
'claude-code': 'claude',
|
|
62
|
+
'claude-acp': 'claude',
|
|
63
|
+
'codebuddy': 'codebuddy-code',
|
|
64
|
+
'codex': 'codex-acp',
|
|
65
|
+
'pi-acp': 'pi',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Resolve an agent ID to its canonical form (idempotent for canonical IDs). */
|
|
69
|
+
export function resolveAlias(agentId: string): string {
|
|
70
|
+
return AGENT_ALIASES[agentId] ?? agentId;
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
/* ── Canonical Descriptors ─────────────────────────────────────────────── */
|
|
54
74
|
|
|
55
75
|
/**
|
|
56
76
|
* All known ACP agents with their detection binary, launch command, and install hint.
|
|
57
|
-
*
|
|
77
|
+
* Only canonical entries — aliases are handled by AGENT_ALIASES above.
|
|
58
78
|
*/
|
|
59
79
|
export const AGENT_DESCRIPTORS: Record<string, AcpAgentDescriptor> = {
|
|
60
|
-
// Gemini CLI — Google's AI coding agent
|
|
61
80
|
'gemini': { binary: 'gemini', cmd: 'gemini', args: ['--experimental-acp'], installCmd: 'npm install -g @google/gemini-cli',
|
|
62
81
|
displayName: 'Gemini CLI',
|
|
63
82
|
description: 'Google Gemini 驱动的编程智能体。支持多文件编辑、代码审查、调试和项目级重构,原生集成 Google 搜索实时查询技术文档。' },
|
|
64
|
-
'gemini-cli': { binary: 'gemini', cmd: 'gemini', args: ['--experimental-acp'], installCmd: 'npm install -g @google/gemini-cli',
|
|
65
|
-
displayName: 'Gemini CLI',
|
|
66
|
-
description: 'Google Gemini 驱动的编程智能体。支持多文件编辑、代码审查、调试和项目级重构,原生集成 Google 搜索实时查询技术文档。' },
|
|
67
|
-
// Claude Code — Anthropic's AI coding agent
|
|
68
83
|
'claude': { binary: 'claude', cmd: 'npx', args: ['--yes', '@agentclientprotocol/claude-agent-acp'], installCmd: 'npm install -g @anthropic-ai/claude-code',
|
|
69
84
|
displayName: 'Claude Code',
|
|
70
85
|
description: 'Anthropic Claude 驱动的编程智能体。擅长复杂推理、长上下文理解和安全代码生成,支持多文件编辑与 agentic 工作流。' },
|
|
71
|
-
'claude-code': { binary: 'claude', cmd: 'npx', args: ['--yes', '@agentclientprotocol/claude-agent-acp'], installCmd: 'npm install -g @anthropic-ai/claude-code',
|
|
72
|
-
displayName: 'Claude Code',
|
|
73
|
-
description: 'Anthropic Claude 驱动的编程智能体。擅长复杂推理、长上下文理解和安全代码生成,支持多文件编辑与 agentic 工作流。' },
|
|
74
|
-
'claude-acp': { binary: 'claude', cmd: 'npx', args: ['--yes', '@agentclientprotocol/claude-agent-acp'], installCmd: 'npm install -g @anthropic-ai/claude-code',
|
|
75
|
-
displayName: 'Claude Code',
|
|
76
|
-
description: 'Anthropic Claude 驱动的编程智能体。擅长复杂推理、长上下文理解和安全代码生成,支持多文件编辑与 agentic 工作流。' },
|
|
77
|
-
// CodeBuddy Code — Tencent Cloud's AI coding agent
|
|
78
86
|
'codebuddy-code': { binary: 'codebuddy', cmd: 'codebuddy', args: ['--acp'], installCmd: 'npm install -g @tencent-ai/codebuddy-code',
|
|
79
87
|
displayName: 'CodeBuddy Code',
|
|
80
88
|
description: '腾讯云智能编程助手。基于混元大模型,支持代码补全、生成、审查和多文件重构,深度理解中文语境,适配国内开发生态。' },
|
|
81
|
-
'codebuddy': { binary: 'codebuddy', cmd: 'codebuddy', args: ['--acp'], installCmd: 'npm install -g @tencent-ai/codebuddy-code',
|
|
82
|
-
displayName: 'CodeBuddy Code',
|
|
83
|
-
description: '腾讯云智能编程助手。基于混元大模型,支持代码补全、生成、审查和多文件重构,深度理解中文语境,适配国内开发生态。' },
|
|
84
|
-
// Codex — OpenAI's coding agent
|
|
85
89
|
'codex-acp': { binary: 'codex', cmd: 'codex', args: [], installCmd: 'npm install -g @openai/codex',
|
|
86
90
|
displayName: 'Codex',
|
|
87
91
|
description: 'OpenAI Codex 编程智能体。基于 GPT 系列模型,擅长代码生成、自动化任务和多语言编程支持。' },
|
|
88
|
-
'codex': { binary: 'codex', cmd: 'codex', args: [], installCmd: 'npm install -g @openai/codex',
|
|
89
|
-
displayName: 'Codex',
|
|
90
|
-
description: 'OpenAI Codex 编程智能体。基于 GPT 系列模型,擅长代码生成、自动化任务和多语言编程支持。' },
|
|
91
|
-
// Cursor — AI-first code editor agent
|
|
92
92
|
'cursor': { binary: 'cursor', cmd: 'cursor', args: [],
|
|
93
93
|
displayName: 'Cursor',
|
|
94
94
|
description: 'Cursor AI 编程智能体。AI-first 代码编辑器的 CLI 模式,支持上下文感知的代码编辑、Tab 补全和多文件协同修改。' },
|
|
@@ -113,9 +113,6 @@ export const AGENT_DESCRIPTORS: Record<string, AcpAgentDescriptor> = {
|
|
|
113
113
|
'pi': { binary: 'pi', cmd: 'pi', args: [],
|
|
114
114
|
displayName: 'Pi Agent',
|
|
115
115
|
description: 'Pi Agent 编程智能体。轻量级终端编程助手。' },
|
|
116
|
-
'pi-acp': { binary: 'pi', cmd: 'pi', args: [],
|
|
117
|
-
displayName: 'Pi Agent',
|
|
118
|
-
description: 'Pi Agent 编程智能体。轻量级终端编程助手。' },
|
|
119
116
|
'auggie': { binary: 'auggie', cmd: 'auggie', args: [],
|
|
120
117
|
displayName: 'Auggie',
|
|
121
118
|
description: 'Augment Code 编程智能体。支持代码理解、生成和全仓库上下文感知。' },
|
|
@@ -147,7 +144,7 @@ export function resolveAgentCommand(
|
|
|
147
144
|
registryEntry?: AcpRegistryEntry,
|
|
148
145
|
userOverride?: AcpAgentOverride,
|
|
149
146
|
): ResolvedAgentCommand {
|
|
150
|
-
const descriptor = AGENT_DESCRIPTORS[agentId];
|
|
147
|
+
const descriptor = AGENT_DESCRIPTORS[resolveAlias(agentId)];
|
|
151
148
|
const enabled = userOverride?.enabled !== false;
|
|
152
149
|
|
|
153
150
|
// Layer 1: User override
|
|
@@ -220,22 +217,47 @@ function registryToCommand(entry: AcpRegistryEntry): { cmd: string; args: string
|
|
|
220
217
|
|
|
221
218
|
/** Get the binary name for detection (used by detect endpoint). */
|
|
222
219
|
export function getDescriptorBinary(agentId: string): string | undefined {
|
|
223
|
-
return AGENT_DESCRIPTORS[agentId]?.binary;
|
|
220
|
+
return AGENT_DESCRIPTORS[resolveAlias(agentId)]?.binary;
|
|
224
221
|
}
|
|
225
222
|
|
|
226
223
|
/** Get the install command for UI display. */
|
|
227
224
|
export function getDescriptorInstallCmd(agentId: string): string | undefined {
|
|
228
|
-
return AGENT_DESCRIPTORS[agentId]?.installCmd;
|
|
225
|
+
return AGENT_DESCRIPTORS[resolveAlias(agentId)]?.installCmd;
|
|
229
226
|
}
|
|
230
227
|
|
|
231
228
|
/** Get curated display name (overrides registry name if available). */
|
|
232
229
|
export function getDescriptorDisplayName(agentId: string): string | undefined {
|
|
233
|
-
return AGENT_DESCRIPTORS[agentId]?.displayName;
|
|
230
|
+
return AGENT_DESCRIPTORS[resolveAlias(agentId)]?.displayName;
|
|
234
231
|
}
|
|
235
232
|
|
|
236
233
|
/** Get curated description (overrides registry description if available). */
|
|
237
234
|
export function getDescriptorDescription(agentId: string): string | undefined {
|
|
238
|
-
return AGENT_DESCRIPTORS[agentId]?.description;
|
|
235
|
+
return AGENT_DESCRIPTORS[resolveAlias(agentId)]?.description;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* ── Detection ─────────────────────────────────────────────────────────── */
|
|
239
|
+
|
|
240
|
+
/** Agent info needed for local binary detection (no CDN dependency). */
|
|
241
|
+
export interface DetectableAgent {
|
|
242
|
+
id: string;
|
|
243
|
+
name: string;
|
|
244
|
+
binary: string;
|
|
245
|
+
installCmd?: string;
|
|
246
|
+
description?: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Return the canonical list of agents for local detection.
|
|
251
|
+
* Pure local data — no CDN fetch, no async, no network dependency.
|
|
252
|
+
*/
|
|
253
|
+
export function getDetectableAgents(): DetectableAgent[] {
|
|
254
|
+
return Object.entries(AGENT_DESCRIPTORS).map(([id, desc]) => ({
|
|
255
|
+
id,
|
|
256
|
+
name: desc.displayName ?? id,
|
|
257
|
+
binary: desc.binary,
|
|
258
|
+
installCmd: desc.installCmd,
|
|
259
|
+
description: desc.description,
|
|
260
|
+
}));
|
|
239
261
|
}
|
|
240
262
|
|
|
241
263
|
/** Parse and validate acpAgents config from raw settings JSON. */
|
package/app/lib/acp/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { spawnAcpAgent, sendMessage, sendAndWait, onMessage, onNotification, onR
|
|
|
3
3
|
export { createSession, createSessionFromEntry, loadSession, listSessions, prompt, promptStream, cancelPrompt, setMode, setConfigOption, closeSession, getSession, getActiveSessions, closeAllSessions } from './session';
|
|
4
4
|
export { bridgeA2aToAcp, bridgeAcpResponseToA2a, bridgeAcpUpdatesToA2a } from './bridge';
|
|
5
5
|
export { acpTools } from './acp-tools';
|
|
6
|
+
export { AGENT_DESCRIPTORS, AGENT_ALIASES, resolveAlias, getDetectableAgents } from './agent-descriptors';
|
|
6
7
|
export { ACP_ERRORS } from './types';
|
|
7
8
|
export type {
|
|
8
9
|
AcpAgentCapabilities,
|
|
@@ -38,3 +39,4 @@ export type {
|
|
|
38
39
|
AcpTransportType,
|
|
39
40
|
} from './types';
|
|
40
41
|
export type { AcpProcess, AcpIncomingRequest, AcpNotification } from './subprocess';
|
|
42
|
+
export type { AcpAgentDescriptor, AcpAgentOverride, ResolvedAgentCommand, DetectableAgent } from './agent-descriptors';
|
package/app/lib/acp/registry.ts
CHANGED
|
@@ -10,57 +10,31 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { AcpRegistry, AcpRegistryEntry } from './types';
|
|
13
|
-
import { AGENT_DESCRIPTORS, getDescriptorDisplayName, getDescriptorDescription } from './agent-descriptors';
|
|
13
|
+
import { AGENT_DESCRIPTORS, getDescriptorDisplayName, getDescriptorDescription, resolveAlias } from './agent-descriptors';
|
|
14
14
|
|
|
15
15
|
/* ── Constants ─────────────────────────────────────────────────────────── */
|
|
16
16
|
|
|
17
17
|
const REGISTRY_URL = 'https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json';
|
|
18
|
-
const CACHE_TTL_MS = 60 * 60 * 1000; //
|
|
18
|
+
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days — registry updates very rarely
|
|
19
19
|
const FETCH_TIMEOUT_MS = 10_000;
|
|
20
20
|
|
|
21
21
|
/* ── Built-in Registry (from AGENT_DESCRIPTORS) ────────────────────────── */
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Generate a baseline registry from the local AGENT_DESCRIPTORS.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* We deduplicate by binary name — e.g. 'gemini' and 'gemini-cli' both map
|
|
29
|
-
* to binary 'gemini', so we only keep the canonical entry (shorter ID or
|
|
30
|
-
* the one matching the CDN convention).
|
|
25
|
+
* AGENT_DESCRIPTORS contains only canonical entries (aliases are in AGENT_ALIASES),
|
|
26
|
+
* so no deduplication is needed.
|
|
31
27
|
*/
|
|
32
28
|
function buildBuiltinRegistry(): AcpRegistryEntry[] {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const seen = new Set<string>();
|
|
43
|
-
const entries: AcpRegistryEntry[] = [];
|
|
44
|
-
|
|
45
|
-
for (const [id, desc] of Object.entries(AGENT_DESCRIPTORS)) {
|
|
46
|
-
// Skip alias entries — only keep the canonical ID for each binary
|
|
47
|
-
const canonical = CANONICAL_IDS[desc.binary];
|
|
48
|
-
if (canonical && canonical !== id) continue;
|
|
49
|
-
if (seen.has(desc.binary)) continue;
|
|
50
|
-
seen.add(desc.binary);
|
|
51
|
-
|
|
52
|
-
entries.push({
|
|
53
|
-
id,
|
|
54
|
-
name: desc.displayName ?? id,
|
|
55
|
-
description: desc.description ?? '',
|
|
56
|
-
transport: desc.cmd === 'npx' ? 'npx' : 'stdio',
|
|
57
|
-
command: desc.cmd,
|
|
58
|
-
args: desc.args,
|
|
59
|
-
packageName: desc.installCmd?.match(/npm install -g (.+)/)?.[1],
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return entries;
|
|
29
|
+
return Object.entries(AGENT_DESCRIPTORS).map(([id, desc]) => ({
|
|
30
|
+
id,
|
|
31
|
+
name: desc.displayName ?? id,
|
|
32
|
+
description: desc.description ?? '',
|
|
33
|
+
transport: (desc.cmd === 'npx' ? 'npx' : 'stdio') as AcpRegistryEntry['transport'],
|
|
34
|
+
command: desc.cmd,
|
|
35
|
+
args: desc.args,
|
|
36
|
+
packageName: desc.installCmd?.match(/npm install -g (.+)/)?.[1],
|
|
37
|
+
}));
|
|
64
38
|
}
|
|
65
39
|
|
|
66
40
|
let builtinAgents: AcpRegistryEntry[] | null = null;
|
|
@@ -78,7 +52,7 @@ let cachedRegistry: AcpRegistry | null = null;
|
|
|
78
52
|
|
|
79
53
|
/**
|
|
80
54
|
* Fetch the ACP registry from the CDN and merge with built-in entries.
|
|
81
|
-
* Caches for
|
|
55
|
+
* Caches for 7 days. Falls back to built-in registry if CDN is unreachable.
|
|
82
56
|
*/
|
|
83
57
|
export async function fetchAcpRegistry(): Promise<AcpRegistry> {
|
|
84
58
|
// Return cached if still valid
|
|
@@ -119,15 +93,36 @@ export async function fetchAcpRegistry(): Promise<AcpRegistry> {
|
|
|
119
93
|
}
|
|
120
94
|
}
|
|
121
95
|
|
|
122
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Merge built-in and CDN registries.
|
|
98
|
+
* - Same ID: keep built-in core fields, supplement with CDN metadata (tags, homepage, version)
|
|
99
|
+
* - CDN alias of built-in entry: skip (avoids duplicate agents in UI)
|
|
100
|
+
* - New CDN-only entry: add as-is
|
|
101
|
+
*/
|
|
123
102
|
function mergeRegistries(builtin: AcpRegistryEntry[], cdn: AcpRegistryEntry[]): AcpRegistryEntry[] {
|
|
124
103
|
const byId = new Map<string, AcpRegistryEntry>();
|
|
125
104
|
|
|
126
|
-
// Start with built-in
|
|
127
105
|
for (const entry of builtin) byId.set(entry.id, entry);
|
|
128
106
|
|
|
129
|
-
|
|
130
|
-
|
|
107
|
+
for (const cdnEntry of cdn) {
|
|
108
|
+
const existing = byId.get(cdnEntry.id);
|
|
109
|
+
const canonicalId = resolveAlias(cdnEntry.id);
|
|
110
|
+
const canonicalExisting = canonicalId !== cdnEntry.id ? byId.get(canonicalId) : undefined;
|
|
111
|
+
|
|
112
|
+
if (existing) {
|
|
113
|
+
// Same ID in both — keep built-in core, supplement with CDN metadata
|
|
114
|
+
byId.set(cdnEntry.id, {
|
|
115
|
+
...existing,
|
|
116
|
+
tags: cdnEntry.tags ?? existing.tags,
|
|
117
|
+
homepage: cdnEntry.homepage ?? existing.homepage,
|
|
118
|
+
version: cdnEntry.version ?? existing.version,
|
|
119
|
+
});
|
|
120
|
+
} else if (canonicalExisting) {
|
|
121
|
+
// CDN entry is an alias of a built-in entry — skip to avoid duplicates
|
|
122
|
+
} else {
|
|
123
|
+
byId.set(cdnEntry.id, cdnEntry);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
131
126
|
|
|
132
127
|
return Array.from(byId.values());
|
|
133
128
|
}
|
|
@@ -146,11 +141,12 @@ export async function getAcpAgents(): Promise<AcpRegistryEntry[]> {
|
|
|
146
141
|
}
|
|
147
142
|
|
|
148
143
|
/**
|
|
149
|
-
* Find a specific ACP agent by ID.
|
|
144
|
+
* Find a specific ACP agent by ID (supports alias resolution).
|
|
150
145
|
*/
|
|
151
146
|
export async function findAcpAgent(id: string): Promise<AcpRegistryEntry | null> {
|
|
152
147
|
const agents = await getAcpAgents();
|
|
153
|
-
|
|
148
|
+
const canonical = resolveAlias(id);
|
|
149
|
+
return agents.find(a => a.id === id || a.id === canonical) ?? null;
|
|
154
150
|
}
|
|
155
151
|
|
|
156
152
|
/**
|
package/app/lib/acp/session.ts
CHANGED
|
@@ -36,6 +36,9 @@ const sessions = new Map<string, AcpSession>();
|
|
|
36
36
|
const sessionProcesses = new Map<string, AcpProcess>();
|
|
37
37
|
const autoApprovalCleanups = new Map<string, () => void>();
|
|
38
38
|
|
|
39
|
+
const MAX_SESSIONS_PER_AGENT = 3;
|
|
40
|
+
const MAX_TOTAL_SESSIONS = 10;
|
|
41
|
+
|
|
39
42
|
/* ── Public API — Session Lifecycle ───────────────────────────────────── */
|
|
40
43
|
|
|
41
44
|
/**
|
|
@@ -61,11 +64,12 @@ export async function createSessionFromEntry(
|
|
|
61
64
|
entry: AcpRegistryEntry,
|
|
62
65
|
options?: { env?: Record<string, string>; cwd?: string },
|
|
63
66
|
): Promise<AcpSession> {
|
|
67
|
+
checkSessionLimits(entry.id);
|
|
68
|
+
|
|
64
69
|
const proc = spawnAcpAgent(entry, options);
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const unsubApproval = installAutoApproval(proc);
|
|
71
|
+
const sessionCwd = options?.cwd ?? process.cwd();
|
|
72
|
+
const unsubApproval = installAutoApproval(proc, { cwd: sessionCwd });
|
|
69
73
|
|
|
70
74
|
let agentCapabilities: AcpAgentCapabilities | undefined;
|
|
71
75
|
let authMethods: AcpAuthMethod[] | undefined;
|
|
@@ -122,7 +126,7 @@ export async function createSessionFromEntry(
|
|
|
122
126
|
|
|
123
127
|
try {
|
|
124
128
|
const newResponse = await sendAndWait(proc, 'session/new', {
|
|
125
|
-
cwd:
|
|
129
|
+
cwd: sessionCwd,
|
|
126
130
|
mcpServers: [],
|
|
127
131
|
}, 15_000);
|
|
128
132
|
|
|
@@ -188,7 +192,8 @@ export async function loadSession(
|
|
|
188
192
|
}
|
|
189
193
|
|
|
190
194
|
const proc = spawnAcpAgent(entry, options);
|
|
191
|
-
const
|
|
195
|
+
const loadCwd = options?.cwd ?? process.cwd();
|
|
196
|
+
const unsubApproval = installAutoApproval(proc, { cwd: loadCwd });
|
|
192
197
|
|
|
193
198
|
let agentCapabilities: AcpAgentCapabilities | undefined;
|
|
194
199
|
|
|
@@ -233,7 +238,7 @@ export async function loadSession(
|
|
|
233
238
|
try {
|
|
234
239
|
const loadResponse = await sendAndWait(proc, 'session/load', {
|
|
235
240
|
sessionId: existingSessionId,
|
|
236
|
-
cwd:
|
|
241
|
+
cwd: loadCwd,
|
|
237
242
|
mcpServers: [],
|
|
238
243
|
}, 15_000);
|
|
239
244
|
|
|
@@ -399,6 +404,8 @@ export async function promptStream(
|
|
|
399
404
|
updateSessionState(session, 'active');
|
|
400
405
|
const wireSessionId = session.agentSessionId ?? sessionId;
|
|
401
406
|
|
|
407
|
+
const PROMPT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
408
|
+
|
|
402
409
|
return new Promise((resolve, reject) => {
|
|
403
410
|
let aggregatedText = '';
|
|
404
411
|
let stopReason: AcpStopReason = 'end_turn';
|
|
@@ -412,6 +419,14 @@ export async function promptStream(
|
|
|
412
419
|
fn();
|
|
413
420
|
};
|
|
414
421
|
|
|
422
|
+
// ── 0. Timeout guard ──
|
|
423
|
+
const timeoutTimer = setTimeout(() => {
|
|
424
|
+
settle(() => {
|
|
425
|
+
updateSessionState(session, 'error');
|
|
426
|
+
reject(new Error(`Prompt timed out after ${PROMPT_TIMEOUT_MS / 1000}s — no response from agent`));
|
|
427
|
+
});
|
|
428
|
+
}, PROMPT_TIMEOUT_MS);
|
|
429
|
+
|
|
415
430
|
// ── 1. Notifications: primary streaming channel ──
|
|
416
431
|
const unsubNotify = onNotification(proc, (notif) => {
|
|
417
432
|
if (settled) return;
|
|
@@ -502,6 +517,7 @@ export async function promptStream(
|
|
|
502
517
|
proc.proc.once('exit', onExit);
|
|
503
518
|
|
|
504
519
|
const cleanup = () => {
|
|
520
|
+
clearTimeout(timeoutTimer);
|
|
505
521
|
unsubNotify();
|
|
506
522
|
unsubMsg();
|
|
507
523
|
proc.proc.removeListener('exit', onExit);
|
|
@@ -630,9 +646,10 @@ export function getSession(sessionId: string): AcpSession | undefined {
|
|
|
630
646
|
}
|
|
631
647
|
|
|
632
648
|
/**
|
|
633
|
-
* Get all active sessions.
|
|
649
|
+
* Get all active sessions. Also reaps stale sessions.
|
|
634
650
|
*/
|
|
635
651
|
export function getActiveSessions(): AcpSession[] {
|
|
652
|
+
reapStaleSessions();
|
|
636
653
|
return [...sessions.values()];
|
|
637
654
|
}
|
|
638
655
|
|
|
@@ -887,6 +904,18 @@ function parseNotificationToUpdate(
|
|
|
887
904
|
return base;
|
|
888
905
|
}
|
|
889
906
|
|
|
907
|
+
/* ── Internal — Session limits ─────────────────────────────────────────── */
|
|
908
|
+
|
|
909
|
+
function checkSessionLimits(agentId: string): void {
|
|
910
|
+
if (sessions.size >= MAX_TOTAL_SESSIONS) {
|
|
911
|
+
throw new Error(`Maximum concurrent sessions (${MAX_TOTAL_SESSIONS}) reached. Close existing sessions first.`);
|
|
912
|
+
}
|
|
913
|
+
const agentCount = [...sessions.values()].filter(s => s.agentId === agentId).length;
|
|
914
|
+
if (agentCount >= MAX_SESSIONS_PER_AGENT) {
|
|
915
|
+
throw new Error(`Maximum concurrent sessions for agent "${agentId}" (${MAX_SESSIONS_PER_AGENT}) reached.`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
890
919
|
/* ── Internal — Session reaping ───────────────────────────────────────── */
|
|
891
920
|
|
|
892
921
|
const STALE_SESSION_MS = 30 * 60 * 1000; // 30 minutes
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { spawn, type ChildProcess } from 'child_process';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
7
9
|
import type {
|
|
8
10
|
AcpJsonRpcRequest,
|
|
9
11
|
AcpJsonRpcResponse,
|
|
@@ -362,7 +364,12 @@ export function sendResponse(
|
|
|
362
364
|
* Without approval, the agent hangs waiting for TTY input that never comes.
|
|
363
365
|
* Returns an unsubscribe function.
|
|
364
366
|
*/
|
|
365
|
-
export function installAutoApproval(
|
|
367
|
+
export function installAutoApproval(
|
|
368
|
+
acpProc: AcpProcess,
|
|
369
|
+
options?: { cwd?: string },
|
|
370
|
+
): () => void {
|
|
371
|
+
const cwd = options?.cwd;
|
|
372
|
+
|
|
366
373
|
return onRequest(acpProc, (req) => {
|
|
367
374
|
const method = req.method;
|
|
368
375
|
const params = (req.params ?? {}) as Record<string, unknown>;
|
|
@@ -375,6 +382,10 @@ export function installAutoApproval(acpProc: AcpProcess): () => void {
|
|
|
375
382
|
sendResponse(acpProc, req.id, { error: { code: -32602, message: 'path is required' } });
|
|
376
383
|
return;
|
|
377
384
|
}
|
|
385
|
+
if (isSensitivePath(filePath)) {
|
|
386
|
+
sendResponse(acpProc, req.id, { error: { code: -32001, message: `Access denied: ${filePath} is a sensitive file` } });
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
378
389
|
try {
|
|
379
390
|
const fs = require('fs');
|
|
380
391
|
const line = typeof params.line === 'number' ? params.line : undefined;
|
|
@@ -401,9 +412,12 @@ export function installAutoApproval(acpProc: AcpProcess): () => void {
|
|
|
401
412
|
sendResponse(acpProc, req.id, { error: { code: -32602, message: 'path is required' } });
|
|
402
413
|
return;
|
|
403
414
|
}
|
|
415
|
+
if (cwd && !isWithinAllowedWritePaths(filePath, cwd)) {
|
|
416
|
+
sendResponse(acpProc, req.id, { error: { code: -32001, message: `Write denied: ${filePath} is outside the working directory` } });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
404
419
|
try {
|
|
405
420
|
const fs = require('fs');
|
|
406
|
-
const path = require('path');
|
|
407
421
|
const dir = path.dirname(filePath);
|
|
408
422
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
409
423
|
fs.writeFileSync(filePath, content, 'utf-8');
|
|
@@ -545,6 +559,30 @@ export function installAutoApproval(acpProc: AcpProcess): () => void {
|
|
|
545
559
|
});
|
|
546
560
|
}
|
|
547
561
|
|
|
562
|
+
/* ── Path safety ───────────────────────────────────────────────────────── */
|
|
563
|
+
|
|
564
|
+
const SENSITIVE_PATH_PATTERNS = [
|
|
565
|
+
/[/\\]\.ssh[/\\](id_|config$|authorized_keys|known_hosts)/i,
|
|
566
|
+
/[/\\]\.env(\.[^/\\]*)?$/i,
|
|
567
|
+
/[/\\]credentials\.json$/i,
|
|
568
|
+
/[/\\]\.aws[/\\]credentials$/i,
|
|
569
|
+
/[/\\]\.gnupg[/\\]/i,
|
|
570
|
+
];
|
|
571
|
+
|
|
572
|
+
function isSensitivePath(filePath: string): boolean {
|
|
573
|
+
const normalized = path.resolve(filePath);
|
|
574
|
+
return SENSITIVE_PATH_PATTERNS.some(p => p.test(normalized));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function isWithinAllowedWritePaths(filePath: string, cwd: string): boolean {
|
|
578
|
+
const normalized = path.resolve(filePath);
|
|
579
|
+
const allowedRoots = [cwd, os.tmpdir()];
|
|
580
|
+
return allowedRoots.some(root => {
|
|
581
|
+
const normalizedRoot = path.resolve(root);
|
|
582
|
+
return normalized === normalizedRoot || normalized.startsWith(normalizedRoot + path.sep);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
548
586
|
/* ── Terminal management (per ACP process) ─────────────────────────────── */
|
|
549
587
|
|
|
550
588
|
interface TerminalEntry {
|