@geminilight/mindos 0.6.29 → 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.
Files changed (71) hide show
  1. package/README.md +10 -4
  2. package/app/app/api/acp/config/route.ts +82 -0
  3. package/app/app/api/acp/detect/route.ts +71 -48
  4. package/app/app/api/acp/install/route.ts +51 -0
  5. package/app/app/api/acp/session/route.ts +141 -11
  6. package/app/app/api/ask/route.ts +116 -13
  7. package/app/app/api/workflows/route.ts +156 -0
  8. package/app/app/page.tsx +7 -2
  9. package/app/components/ActivityBar.tsx +12 -4
  10. package/app/components/AskModal.tsx +4 -1
  11. package/app/components/FileTree.tsx +21 -10
  12. package/app/components/HomeContent.tsx +1 -0
  13. package/app/components/Panel.tsx +1 -0
  14. package/app/components/RightAskPanel.tsx +5 -1
  15. package/app/components/SidebarLayout.tsx +6 -0
  16. package/app/components/agents/AgentDetailContent.tsx +263 -47
  17. package/app/components/agents/AgentsContentPage.tsx +11 -0
  18. package/app/components/agents/AgentsPanelA2aTab.tsx +285 -46
  19. package/app/components/agents/AgentsPanelSessionsTab.tsx +166 -0
  20. package/app/components/agents/agents-content-model.ts +2 -2
  21. package/app/components/ask/AgentSelectorCapsule.tsx +218 -0
  22. package/app/components/ask/AskContent.tsx +197 -239
  23. package/app/components/ask/FileChip.tsx +82 -17
  24. package/app/components/ask/MentionPopover.tsx +21 -3
  25. package/app/components/ask/MessageList.tsx +30 -9
  26. package/app/components/ask/SlashCommandPopover.tsx +21 -3
  27. package/app/components/panels/AgentsPanel.tsx +1 -0
  28. package/app/components/panels/AgentsPanelHubNav.tsx +9 -2
  29. package/app/components/panels/WorkflowsPanel.tsx +206 -0
  30. package/app/components/renderers/workflow-yaml/StepEditor.tsx +157 -0
  31. package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +201 -0
  32. package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +226 -0
  33. package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +126 -0
  34. package/app/components/renderers/workflow-yaml/execution.ts +177 -0
  35. package/app/components/renderers/workflow-yaml/index.ts +6 -0
  36. package/app/components/renderers/workflow-yaml/manifest.ts +21 -0
  37. package/app/components/renderers/workflow-yaml/parser.ts +172 -0
  38. package/app/components/renderers/workflow-yaml/selectors.tsx +522 -0
  39. package/app/components/renderers/workflow-yaml/serializer.ts +56 -0
  40. package/app/components/renderers/workflow-yaml/types.ts +46 -0
  41. package/app/hooks/useAcpConfig.ts +96 -0
  42. package/app/hooks/useAcpDetection.ts +69 -14
  43. package/app/hooks/useAcpRegistry.ts +46 -11
  44. package/app/hooks/useAskModal.ts +12 -5
  45. package/app/hooks/useAskPanel.ts +8 -5
  46. package/app/hooks/useAskSession.ts +19 -2
  47. package/app/hooks/useImageUpload.ts +152 -0
  48. package/app/lib/acp/acp-tools.ts +3 -1
  49. package/app/lib/acp/agent-descriptors.ts +274 -0
  50. package/app/lib/acp/bridge.ts +6 -0
  51. package/app/lib/acp/index.ts +20 -4
  52. package/app/lib/acp/registry.ts +74 -7
  53. package/app/lib/acp/session.ts +481 -28
  54. package/app/lib/acp/subprocess.ts +307 -21
  55. package/app/lib/acp/types.ts +158 -20
  56. package/app/lib/agent/model.ts +18 -3
  57. package/app/lib/agent/to-agent-messages.ts +25 -2
  58. package/app/lib/i18n/modules/knowledge.ts +4 -0
  59. package/app/lib/i18n/modules/navigation.ts +2 -0
  60. package/app/lib/i18n/modules/panels.ts +146 -2
  61. package/app/lib/pi-integration/skills.ts +21 -6
  62. package/app/lib/renderers/index.ts +2 -2
  63. package/app/lib/settings.ts +10 -0
  64. package/app/lib/types.ts +12 -1
  65. package/app/next-env.d.ts +1 -1
  66. package/app/package.json +3 -1
  67. package/package.json +1 -1
  68. package/templates/en/.mindos/workflows/Sprint Release.flow.yaml +130 -0
  69. package/templates/zh/.mindos/workflows//345/221/250/350/277/255/344/273/243/346/243/200/346/237/245.flow.yaml +84 -0
  70. package/app/components/renderers/workflow/WorkflowRenderer.tsx +0 -409
  71. 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
- - **Plugin Extensions**: multiple built-in renderer pluginsTODO Board, CSV Views, Wiki Graph, Timeline, Agent Inspector, and more.
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 (OpenClaw, Claude Code, Cursor, etc.). Zero-config access.
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
- - [ ] Agent Inspector: render Agent operation logs as a filterable timeline to audit every tool call in detail
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,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
+ }
@@ -1,87 +1,110 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
 
3
3
  import { NextResponse } from 'next/server';
4
- import { execSync } from 'child_process';
4
+ import { exec } from 'child_process';
5
5
  import { getAcpAgents } from '@/lib/acp/registry';
6
+ import { getDescriptorBinary, getDescriptorInstallCmd, resolveAgentCommand } from '@/lib/acp/agent-descriptors';
7
+ import { readSettings } from '@/lib/settings';
6
8
 
7
- /* ── Agent ID → binary name mapping ─────────────────────────────────── */
8
-
9
- const AGENT_BINARY_MAP: Record<string, string> = {
10
- claude: 'claude',
11
- 'claude-code': 'claude',
12
- gemini: 'gemini',
13
- 'gemini-cli': 'gemini',
14
- codex: 'codex',
15
- cursor: 'cursor',
16
- cline: 'cline',
17
- goose: 'goose',
18
- opencode: 'opencode',
19
- kilo: 'kilo',
20
- codebuddy: 'codebuddy',
21
- openclaw: 'openclaw',
22
- pi: 'pi',
23
- augment: 'auggie',
24
- iflow: 'iflow',
25
- kimi: 'kimi',
26
- };
27
-
28
- const INSTALL_COMMANDS: Record<string, string> = {
29
- claude: 'npm install -g @anthropic-ai/claude-code',
30
- 'claude-code': 'npm install -g @anthropic-ai/claude-code',
31
- gemini: 'npm install -g @anthropic-ai/claude-code && npx @anthropic-ai/claude-code',
32
- 'gemini-cli': 'npm install -g @anthropic-ai/claude-code',
33
- codex: 'npm install -g @openai/codex',
34
- goose: 'pip install goose-ai',
35
- opencode: 'go install github.com/opencode-ai/opencode@latest',
36
- codebuddy: 'npm install -g @anthropic-ai/claude-code',
37
- };
9
+ /* ── Types ─────────────────────────────────────────────────────────────── */
38
10
 
39
11
  interface InstalledAgent {
40
12
  id: string;
41
13
  name: string;
42
14
  binaryPath: string;
15
+ resolvedCommand: {
16
+ cmd: string;
17
+ args: string[];
18
+ source: 'user-override' | 'descriptor' | 'registry';
19
+ };
43
20
  }
44
21
 
45
22
  interface NotInstalledAgent {
46
23
  id: string;
47
24
  name: string;
48
25
  installCmd: string;
26
+ packageName?: string;
49
27
  }
50
28
 
51
- function whichBinary(binary: string): string | null {
52
- try {
53
- return execSync(`which ${binary}`, { encoding: 'utf-8', timeout: 3000 }).trim() || null;
54
- } catch {
55
- return null;
56
- }
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);
57
59
  }
58
60
 
59
- export async function GET() {
61
+ /* ── Route handler ─────────────────────────────────────────────────────── */
62
+
63
+ export async function GET(req: Request) {
60
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
+
61
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
+
62
76
  const installed: InstalledAgent[] = [];
63
77
  const notInstalled: NotInstalledAgent[] = [];
64
78
 
65
79
  for (const agent of agents) {
66
- const binary = AGENT_BINARY_MAP[agent.id] ?? AGENT_BINARY_MAP[agent.command] ?? agent.command;
67
- if (!binary) {
68
- notInstalled.push({ id: agent.id, name: agent.name, installCmd: '' });
69
- continue;
70
- }
80
+ const binary = getDescriptorBinary(agent.id);
81
+ const binaryPath = binary ? (whichMap.get(binary) ?? null) : null;
71
82
 
72
- const binaryPath = whichBinary(binary);
73
83
  if (binaryPath) {
74
- installed.push({ id: agent.id, name: agent.name, 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
+ });
75
92
  } else {
93
+ const installCmd =
94
+ getDescriptorInstallCmd(agent.id) ??
95
+ (agent.packageName ? `npm install -g ${agent.packageName}` : '');
76
96
  notInstalled.push({
77
97
  id: agent.id,
78
98
  name: agent.name,
79
- installCmd: INSTALL_COMMANDS[agent.id] ?? `npm install -g ${agent.command}`,
99
+ installCmd,
100
+ packageName: agent.packageName,
80
101
  });
81
102
  }
82
103
  }
83
104
 
84
- return NextResponse.json({ installed, notInstalled });
105
+ const data = { installed, notInstalled };
106
+ detectCache = { data, ts: Date.now() };
107
+ return NextResponse.json(data);
85
108
  } catch (err) {
86
109
  return NextResponse.json(
87
110
  { error: (err as Error).message, installed: [], notInstalled: [] },
@@ -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
+ }
@@ -1,7 +1,18 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
 
3
3
  import { NextResponse } from 'next/server';
4
- import { createSession, closeSession, getSession, getActiveSessions } from '@/lib/acp/session';
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';
5
16
 
6
17
  export async function GET() {
7
18
  try {
@@ -15,20 +26,139 @@ export async function GET() {
15
26
  }
16
27
  }
17
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
+ */
18
44
  export async function POST(req: Request) {
19
45
  try {
20
- const { agentId, env } = await req.json();
21
- if (!agentId || typeof agentId !== 'string') {
22
- return NextResponse.json({ error: 'agentId is required' }, { status: 400 });
23
- }
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
+ }
24
120
 
25
- const session = await createSession(agentId, { env });
26
- return NextResponse.json({ session });
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
+ }
27
158
  } catch (err) {
28
- return NextResponse.json(
29
- { error: (err as Error).message },
30
- { status: 500 },
31
- );
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 });
32
162
  }
33
163
  }
34
164