@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
package/README.md
CHANGED
|
@@ -162,11 +162,15 @@ npx skills add https://github.com/GeminiLight/MindOS --skill mindos-zh -g -y #
|
|
|
162
162
|
|
|
163
163
|
- **GUI Workbench**: browse, edit, search notes with unified search + AI entry (`⌘K` / `⌘/`), designed for human-AI co-creation.
|
|
164
164
|
- **Built-in Agent Assistant**: converse with the knowledge base in context; edits seamlessly capture human-curated knowledge.
|
|
165
|
-
- **
|
|
165
|
+
- **One-Click Import**: drag-and-drop files with Inline AI Organize — auto-analyzes, categorizes, and writes into the knowledge base with progress tracking and undo support.
|
|
166
|
+
- **Guided Onboarding**: step-by-step first-run experience that helps new users set up their knowledge base and connect their first Agent.
|
|
167
|
+
- **Plugin Extensions**: multiple built-in renderer plugins — TODO Board, CSV Views, Wiki Graph, Timeline, Workflow Editor, Agent Inspector, and more.
|
|
166
168
|
|
|
167
169
|
**For Agents**
|
|
168
170
|
|
|
169
|
-
- **MCP Server + Skills**: stdio + HTTP dual transport, full-lineup Agent compatible (
|
|
171
|
+
- **MCP Server + Skills**: stdio + HTTP dual transport, full-lineup Agent compatible (Claude Code, Cursor, Gemini CLI, etc.). Zero-config access.
|
|
172
|
+
- **ACP / A2A Protocols**: Agent Communication Protocol for inter-agent discovery, delegation, and orchestration. Phase 1 live with Agent Card discovery + JSON-RPC messaging.
|
|
173
|
+
- **Workflow Orchestration**: YAML-based workflow editor with step execution engine — define, edit, and run multi-step agent workflows visually.
|
|
170
174
|
- **Structured Templates**: pre-set directory structures for Profiles, Workflows, Configurations, etc., to jumpstart personal context.
|
|
171
175
|
- **Agent-Ready Docs**: everyday notes naturally double as high-quality executable Agent commands — no format conversion needed, write and dispatch.
|
|
172
176
|
|
|
@@ -175,15 +179,17 @@ npx skills add https://github.com/GeminiLight/MindOS --skill mindos-zh -g -y #
|
|
|
175
179
|
- **Security**: Bearer Token auth, path sandboxing, INSTRUCTION.md write-protection, atomic writes.
|
|
176
180
|
- **Knowledge Graph**: dynamically parses and visualizes inter-file references and dependencies.
|
|
177
181
|
- **Backlinks View**: displays all files that reference the current file, helping you understand how a note fits into the knowledge network.
|
|
182
|
+
- **Agent Inspector**: renders Agent operation logs as a filterable timeline to audit every tool call in detail.
|
|
178
183
|
- **Git Time Machine**: Git auto-sync (commit/push/pull), records every edit by both humans and Agents. One-click rollback, cross-device sync.
|
|
179
184
|
- **Desktop App**: native macOS/Windows/Linux app with system tray, auto-start, and local process management.
|
|
180
185
|
|
|
181
186
|
<details>
|
|
182
187
|
<summary><strong>Coming Soon</strong></summary>
|
|
183
188
|
|
|
184
|
-
- [ ] ACP (Agent Communication Protocol): connect external Agents (e.g., Claude Code, Cursor) and turn the knowledge base into a multi-Agent collaboration hub
|
|
185
189
|
- [ ] Deep RAG integration: retrieval-augmented generation grounded in your knowledge base for more accurate, context-aware AI responses
|
|
186
|
-
- [ ]
|
|
190
|
+
- [ ] ACP / A2A Phase 2: deep multi-agent collaboration with task delegation, shared context, and workflow chaining
|
|
191
|
+
- [ ] Experience Compiler: auto-extract corrections and preferences from agent interactions into reusable Skills/SOPs
|
|
192
|
+
- [ ] Knowledge Health Dashboard: visualize cognitive compound metrics — rules accumulated, agent reuse count, knowledge freshness
|
|
187
193
|
|
|
188
194
|
</details>
|
|
189
195
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { getDelegationHistory } from '@/lib/a2a/client';
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
const delegations = getDelegationHistory();
|
|
8
|
+
return NextResponse.json({ delegations });
|
|
9
|
+
}
|
package/app/app/api/a2a/route.ts
CHANGED
|
@@ -74,12 +74,12 @@ export async function POST(req: NextRequest) {
|
|
|
74
74
|
if (!params?.id) {
|
|
75
75
|
return respond(jsonRpcError(rpc.id, A2A_ERRORS.INVALID_PARAMS));
|
|
76
76
|
}
|
|
77
|
-
const task = handleCancelTask(params);
|
|
78
|
-
if (
|
|
79
|
-
return respond(jsonRpcError(rpc.id,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
const { task, reason } = handleCancelTask(params);
|
|
78
|
+
if (reason === 'not_found') {
|
|
79
|
+
return respond(jsonRpcError(rpc.id, A2A_ERRORS.TASK_NOT_FOUND));
|
|
80
|
+
}
|
|
81
|
+
if (reason === 'not_cancelable') {
|
|
82
|
+
return respond(jsonRpcError(rpc.id, A2A_ERRORS.TASK_NOT_CANCELABLE));
|
|
83
83
|
}
|
|
84
84
|
return respond(jsonRpcOk(rpc.id, task));
|
|
85
85
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
+
import { readSettings, writeSettings } from '@/lib/settings';
|
|
5
|
+
import type { AcpAgentOverride } from '@/lib/acp/agent-descriptors';
|
|
6
|
+
|
|
7
|
+
/** GET /api/acp/config — Returns all per-agent ACP overrides from settings. */
|
|
8
|
+
export async function GET() {
|
|
9
|
+
const settings = readSettings();
|
|
10
|
+
return NextResponse.json({ agents: settings.acpAgents ?? {} });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** POST /api/acp/config — Save a per-agent override. Body: { agentId, config } */
|
|
14
|
+
export async function POST(req: NextRequest) {
|
|
15
|
+
let body: { agentId?: string; config?: AcpAgentOverride };
|
|
16
|
+
try {
|
|
17
|
+
body = await req.json();
|
|
18
|
+
} catch {
|
|
19
|
+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { agentId, config } = body;
|
|
23
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
24
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const settings = readSettings();
|
|
28
|
+
const existing = settings.acpAgents ?? {};
|
|
29
|
+
|
|
30
|
+
if (config && typeof config === 'object') {
|
|
31
|
+
// Sanitize the override
|
|
32
|
+
const sanitized: AcpAgentOverride = {};
|
|
33
|
+
if (typeof config.command === 'string' && config.command.trim()) {
|
|
34
|
+
sanitized.command = config.command.trim();
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(config.args)) {
|
|
37
|
+
sanitized.args = config.args.filter((a): a is string => typeof a === 'string');
|
|
38
|
+
}
|
|
39
|
+
if (config.env && typeof config.env === 'object' && !Array.isArray(config.env)) {
|
|
40
|
+
const env: Record<string, string> = {};
|
|
41
|
+
for (const [k, v] of Object.entries(config.env)) {
|
|
42
|
+
if (typeof v === 'string') env[k] = v;
|
|
43
|
+
}
|
|
44
|
+
if (Object.keys(env).length > 0) sanitized.env = env;
|
|
45
|
+
}
|
|
46
|
+
if (typeof config.enabled === 'boolean') {
|
|
47
|
+
sanitized.enabled = config.enabled;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
existing[agentId] = sanitized;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
settings.acpAgents = existing;
|
|
54
|
+
writeSettings(settings);
|
|
55
|
+
return NextResponse.json({ ok: true, agents: settings.acpAgents });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** DELETE /api/acp/config — Reset a single agent to defaults. Body: { agentId } */
|
|
59
|
+
export async function DELETE(req: NextRequest) {
|
|
60
|
+
let body: { agentId?: string };
|
|
61
|
+
try {
|
|
62
|
+
body = await req.json();
|
|
63
|
+
} catch {
|
|
64
|
+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const { agentId } = body;
|
|
68
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
69
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const settings = readSettings();
|
|
73
|
+
if (settings.acpAgents) {
|
|
74
|
+
delete settings.acpAgents[agentId];
|
|
75
|
+
if (Object.keys(settings.acpAgents).length === 0) {
|
|
76
|
+
settings.acpAgents = undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
writeSettings(settings);
|
|
81
|
+
return NextResponse.json({ ok: true, agents: settings.acpAgents ?? {} });
|
|
82
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { getAcpAgents } from '@/lib/acp/registry';
|
|
6
|
+
import { getDescriptorBinary, getDescriptorInstallCmd, resolveAgentCommand } from '@/lib/acp/agent-descriptors';
|
|
7
|
+
import { readSettings } from '@/lib/settings';
|
|
8
|
+
|
|
9
|
+
/* ── Types ─────────────────────────────────────────────────────────────── */
|
|
10
|
+
|
|
11
|
+
interface InstalledAgent {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
binaryPath: string;
|
|
15
|
+
resolvedCommand: {
|
|
16
|
+
cmd: string;
|
|
17
|
+
args: string[];
|
|
18
|
+
source: 'user-override' | 'descriptor' | 'registry';
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface NotInstalledAgent {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
installCmd: string;
|
|
26
|
+
packageName?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ── Server-side detection cache (5 min TTL) ──────────────────────────── */
|
|
30
|
+
|
|
31
|
+
const DETECT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
32
|
+
let detectCache: { data: { installed: InstalledAgent[]; notInstalled: NotInstalledAgent[] }; ts: number } | null = null;
|
|
33
|
+
|
|
34
|
+
/* ── Async detection helpers ──────────────────────────────────────────── */
|
|
35
|
+
|
|
36
|
+
function whichAsync(binary: string): Promise<string | null> {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const child = exec(`which ${binary}`, { encoding: 'utf-8', timeout: 2000 }, (err, stdout) => {
|
|
39
|
+
resolve(err ? null : (stdout.trim() || null));
|
|
40
|
+
});
|
|
41
|
+
child.on('error', () => resolve(null));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Batch `which` for multiple unique binaries in a single call.
|
|
47
|
+
* Returns a Map<binary, path | null>.
|
|
48
|
+
*/
|
|
49
|
+
async function whichBatch(binaries: string[]): Promise<Map<string, string | null>> {
|
|
50
|
+
const unique = [...new Set(binaries)];
|
|
51
|
+
if (unique.length === 0) return new Map();
|
|
52
|
+
|
|
53
|
+
const results = await Promise.all(unique.map(async (bin) => {
|
|
54
|
+
const path = await whichAsync(bin);
|
|
55
|
+
return [bin, path] as const;
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
return new Map(results);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ── Route handler ─────────────────────────────────────────────────────── */
|
|
62
|
+
|
|
63
|
+
export async function GET(req: Request) {
|
|
64
|
+
try {
|
|
65
|
+
const force = new URL(req.url).searchParams.get('force') === '1';
|
|
66
|
+
if (!force && detectCache && Date.now() - detectCache.ts < DETECT_CACHE_TTL_MS) {
|
|
67
|
+
return NextResponse.json(detectCache.data);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const agents = await getAcpAgents();
|
|
71
|
+
const settings = readSettings();
|
|
72
|
+
|
|
73
|
+
const binaryNames = agents.map((a) => getDescriptorBinary(a.id)).filter(Boolean) as string[];
|
|
74
|
+
const whichMap = await whichBatch(binaryNames);
|
|
75
|
+
|
|
76
|
+
const installed: InstalledAgent[] = [];
|
|
77
|
+
const notInstalled: NotInstalledAgent[] = [];
|
|
78
|
+
|
|
79
|
+
for (const agent of agents) {
|
|
80
|
+
const binary = getDescriptorBinary(agent.id);
|
|
81
|
+
const binaryPath = binary ? (whichMap.get(binary) ?? null) : null;
|
|
82
|
+
|
|
83
|
+
if (binaryPath) {
|
|
84
|
+
const userOverride = settings.acpAgents?.[agent.id];
|
|
85
|
+
const resolved = resolveAgentCommand(agent.id, agent, userOverride);
|
|
86
|
+
installed.push({
|
|
87
|
+
id: agent.id,
|
|
88
|
+
name: agent.name,
|
|
89
|
+
binaryPath,
|
|
90
|
+
resolvedCommand: { cmd: resolved.cmd, args: resolved.args, source: resolved.source },
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
const installCmd =
|
|
94
|
+
getDescriptorInstallCmd(agent.id) ??
|
|
95
|
+
(agent.packageName ? `npm install -g ${agent.packageName}` : '');
|
|
96
|
+
notInstalled.push({
|
|
97
|
+
id: agent.id,
|
|
98
|
+
name: agent.name,
|
|
99
|
+
installCmd,
|
|
100
|
+
packageName: agent.packageName,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const data = { installed, notInstalled };
|
|
106
|
+
detectCache = { data, ts: Date.now() };
|
|
107
|
+
return NextResponse.json(data);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{ error: (err as Error).message, installed: [], notInstalled: [] },
|
|
111
|
+
{ status: 500 },
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
|
|
6
|
+
interface InstallRequest {
|
|
7
|
+
agentId: string;
|
|
8
|
+
packageName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function POST(request: Request) {
|
|
12
|
+
try {
|
|
13
|
+
const body = (await request.json()) as InstallRequest;
|
|
14
|
+
|
|
15
|
+
if (!body.agentId || !body.packageName) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ error: 'agentId and packageName are required' },
|
|
18
|
+
{ status: 400 },
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Validate packageName to prevent command injection
|
|
23
|
+
// Allow scoped packages like @scope/name and plain names
|
|
24
|
+
if (!/^(@[\w-]+\/)?[\w][\w./-]*$/.test(body.packageName)) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Invalid package name' },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Run npm install -g in background (fire-and-forget for the HTTP response)
|
|
32
|
+
const child = exec(
|
|
33
|
+
`npm install -g ${body.packageName}`,
|
|
34
|
+
{ timeout: 120_000 },
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Detach so the process continues after response
|
|
38
|
+
child.unref();
|
|
39
|
+
|
|
40
|
+
return NextResponse.json({
|
|
41
|
+
status: 'installing',
|
|
42
|
+
agentId: body.agentId,
|
|
43
|
+
packageName: body.packageName,
|
|
44
|
+
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
return NextResponse.json(
|
|
47
|
+
{ error: (err as Error).message },
|
|
48
|
+
{ status: 500 },
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { fetchAcpRegistry, findAcpAgent } from '@/lib/acp/registry';
|
|
5
|
+
|
|
6
|
+
export async function GET(req: Request) {
|
|
7
|
+
try {
|
|
8
|
+
const { searchParams } = new URL(req.url);
|
|
9
|
+
const agentId = searchParams.get('agent');
|
|
10
|
+
|
|
11
|
+
if (agentId) {
|
|
12
|
+
const agent = await findAcpAgent(agentId);
|
|
13
|
+
if (!agent) {
|
|
14
|
+
return NextResponse.json({ error: 'Agent not found', agent: null }, { status: 404 });
|
|
15
|
+
}
|
|
16
|
+
return NextResponse.json({ agent });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const registry = await fetchAcpRegistry();
|
|
20
|
+
if (!registry) {
|
|
21
|
+
return NextResponse.json({ error: 'Failed to fetch registry', registry: null }, { status: 502 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return NextResponse.json({ registry });
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: (err as Error).message },
|
|
28
|
+
{ status: 500 },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const dynamic = 'force-dynamic';
|
|
2
|
+
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import {
|
|
5
|
+
createSession,
|
|
6
|
+
loadSession,
|
|
7
|
+
listSessions,
|
|
8
|
+
closeSession,
|
|
9
|
+
prompt,
|
|
10
|
+
cancelPrompt,
|
|
11
|
+
setMode,
|
|
12
|
+
setConfigOption,
|
|
13
|
+
getSession,
|
|
14
|
+
getActiveSessions,
|
|
15
|
+
} from '@/lib/acp/session';
|
|
16
|
+
|
|
17
|
+
export async function GET() {
|
|
18
|
+
try {
|
|
19
|
+
const sessions = getActiveSessions();
|
|
20
|
+
return NextResponse.json({ sessions });
|
|
21
|
+
} catch (err) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: (err as Error).message },
|
|
24
|
+
{ status: 500 },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* POST /api/acp/session
|
|
31
|
+
* Action-based dispatch for all ACP session operations.
|
|
32
|
+
*
|
|
33
|
+
* Actions:
|
|
34
|
+
* create — { action: "create", agentId, env?, cwd?, prompt? }
|
|
35
|
+
* load — { action: "load", agentId, sessionId, env?, cwd? }
|
|
36
|
+
* prompt — { action: "prompt", sessionId, text }
|
|
37
|
+
* cancel — { action: "cancel", sessionId }
|
|
38
|
+
* set_mode — { action: "set_mode", sessionId, modeId }
|
|
39
|
+
* set_config — { action: "set_config", sessionId, configId, value }
|
|
40
|
+
* list_sessions — { action: "list_sessions", sessionId, cursor?, cwd? }
|
|
41
|
+
*
|
|
42
|
+
* Legacy (no action field): treated as "create" for backwards compat.
|
|
43
|
+
*/
|
|
44
|
+
export async function POST(req: Request) {
|
|
45
|
+
try {
|
|
46
|
+
const body = await req.json();
|
|
47
|
+
const action = body.action ?? 'create';
|
|
48
|
+
|
|
49
|
+
switch (action) {
|
|
50
|
+
case 'create': {
|
|
51
|
+
const { agentId, env, cwd, prompt: promptText } = body as {
|
|
52
|
+
agentId?: string;
|
|
53
|
+
env?: Record<string, string>;
|
|
54
|
+
cwd?: string;
|
|
55
|
+
prompt?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
59
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const session = await createSession(agentId, { env, cwd });
|
|
63
|
+
|
|
64
|
+
// One-shot mode: send prompt, get response, close session
|
|
65
|
+
if (promptText && typeof promptText === 'string') {
|
|
66
|
+
try {
|
|
67
|
+
const response = await prompt(session.id, promptText);
|
|
68
|
+
await closeSession(session.id).catch(() => {});
|
|
69
|
+
return NextResponse.json({ session, response });
|
|
70
|
+
} catch (promptErr) {
|
|
71
|
+
await closeSession(session.id).catch(() => {});
|
|
72
|
+
return NextResponse.json(
|
|
73
|
+
{ error: `Prompt failed: ${(promptErr as Error).message}`, session },
|
|
74
|
+
{ status: 500 },
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return NextResponse.json({ session });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'load': {
|
|
83
|
+
const { agentId, sessionId, env, cwd } = body as {
|
|
84
|
+
agentId?: string;
|
|
85
|
+
sessionId?: string;
|
|
86
|
+
env?: Record<string, string>;
|
|
87
|
+
cwd?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
91
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 });
|
|
92
|
+
}
|
|
93
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
94
|
+
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const session = await loadSession(agentId, sessionId, { env, cwd });
|
|
98
|
+
return NextResponse.json({ session });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case 'prompt': {
|
|
102
|
+
const { sessionId, text } = body as { sessionId?: string; text?: string };
|
|
103
|
+
|
|
104
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
105
|
+
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 });
|
|
106
|
+
}
|
|
107
|
+
if (!text || typeof text !== 'string') {
|
|
108
|
+
return NextResponse.json({ error: 'text is required' }, { status: 400 });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const response = await prompt(sessionId, text);
|
|
112
|
+
return NextResponse.json({ response });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'cancel': {
|
|
116
|
+
const { sessionId } = body as { sessionId?: string };
|
|
117
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
118
|
+
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await cancelPrompt(sessionId);
|
|
122
|
+
return NextResponse.json({ ok: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case 'set_mode': {
|
|
126
|
+
const { sessionId, modeId } = body as { sessionId?: string; modeId?: string };
|
|
127
|
+
if (!sessionId || !modeId) {
|
|
128
|
+
return NextResponse.json({ error: 'sessionId and modeId are required' }, { status: 400 });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await setMode(sessionId, modeId);
|
|
132
|
+
return NextResponse.json({ ok: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case 'set_config': {
|
|
136
|
+
const { sessionId, configId, value } = body as { sessionId?: string; configId?: string; value?: string };
|
|
137
|
+
if (!sessionId || !configId || value === undefined) {
|
|
138
|
+
return NextResponse.json({ error: 'sessionId, configId, and value are required' }, { status: 400 });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const configOptions = await setConfigOption(sessionId, configId, String(value));
|
|
142
|
+
return NextResponse.json({ configOptions });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
case 'list_sessions': {
|
|
146
|
+
const { sessionId, cursor, cwd } = body as { sessionId?: string; cursor?: string; cwd?: string };
|
|
147
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
148
|
+
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const result = await listSessions(sessionId, { cursor, cwd });
|
|
152
|
+
return NextResponse.json(result);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
default:
|
|
156
|
+
return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const msg = (err as Error).message;
|
|
160
|
+
const status = msg.includes('not found') ? 404 : msg.includes('not support') ? 501 : 500;
|
|
161
|
+
return NextResponse.json({ error: msg }, { status });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function DELETE(req: Request) {
|
|
166
|
+
try {
|
|
167
|
+
const { sessionId } = await req.json();
|
|
168
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
169
|
+
return NextResponse.json({ error: 'sessionId is required' }, { status: 400 });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const session = getSession(sessionId);
|
|
173
|
+
if (!session) {
|
|
174
|
+
return NextResponse.json({ error: 'Session not found' }, { status: 404 });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
await closeSession(sessionId);
|
|
178
|
+
return NextResponse.json({ ok: true });
|
|
179
|
+
} catch (err) {
|
|
180
|
+
return NextResponse.json(
|
|
181
|
+
{ error: (err as Error).message },
|
|
182
|
+
{ status: 500 },
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|