min-agent 0.5.0 → 0.6.0

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 (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,367 +0,0 @@
1
- import { loadInstructions } from "../instructions.js";
2
- import { loadSession, saveSession, withSessionLock } from "../sessions.js";
3
- import { buildUserContent } from "../agent.js";
4
- import { lastUserText } from "../tui/hydrate.js";
5
- import { compactMessages } from "../compaction.js";
6
- import { resolveModel } from "../provider.js";
7
- import { emptyTaskState } from "../tools/todo.js";
8
- import { MAX_CONCURRENT_CHATS, sendJson, sendJsonError, readJsonBody, readSandboxTighten, readThinking, readMemory, normalizeMessages, chatRunOptions, executeChat, } from "./common.js";
9
- /** Shared handler for /v1/chat and /v1/code (streaming + JSON). */
10
- async function handleChatRequest(ctx, req, res) {
11
- if (req.headers["content-type"]?.split(";")[0]?.trim() !== "application/json") {
12
- sendJson(res, 415, { error: "unsupported_media_type", detail: "Use Content-Type: application/json" });
13
- return;
14
- }
15
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
16
- sendJson(res, 429, {
17
- error: "too_many_concurrent_requests",
18
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
19
- });
20
- return;
21
- }
22
- ctx.activeChats++;
23
- try {
24
- const parsed = await readJsonBody(req);
25
- if (!parsed.ok) {
26
- sendJsonError(res, parsed);
27
- return;
28
- }
29
- const body = parsed.value;
30
- const sandboxParsed = readSandboxTighten(body);
31
- if (!sandboxParsed.ok) {
32
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
33
- return;
34
- }
35
- const sandboxTighten = sandboxParsed.policy;
36
- const thinkingParsed = readThinking(body);
37
- if (!thinkingParsed.ok) {
38
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
39
- return;
40
- }
41
- const memoryParsed = readMemory(body);
42
- if (!memoryParsed.ok) {
43
- sendJson(res, 400, { error: memoryParsed.error, detail: memoryParsed.detail });
44
- return;
45
- }
46
- const modelId = typeof body.model === "string" ? body.model : undefined;
47
- const stream = body.stream === true;
48
- const sessionId = typeof body.session_id === "string" ? body.session_id : undefined;
49
- let messages;
50
- if (sessionId) {
51
- await withSessionLock(sessionId, async () => {
52
- const session = loadSession(sessionId);
53
- if (!session) {
54
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
55
- return;
56
- }
57
- if (typeof body.message !== "string" || !body.message.trim()) {
58
- sendJson(res, 400, {
59
- error: "session_requires_message",
60
- detail: "With `session_id`, send a non-empty `message` for the new user turn",
61
- });
62
- return;
63
- }
64
- const messages = [...session.messages];
65
- const content = body.images && body.images.length > 0 ? await buildUserContent(body.message, body.images) : body.message;
66
- messages.push({ role: "user", content });
67
- await executeChat(ctx, {
68
- res,
69
- messages,
70
- modelId,
71
- stream,
72
- sessionId,
73
- runOptions: chatRunOptions(body),
74
- autoApprove: body.auto_approve !== false,
75
- taskState: session.taskState ?? emptyTaskState(),
76
- sandboxTighten,
77
- });
78
- });
79
- return;
80
- }
81
- else {
82
- const norm = normalizeMessages(body);
83
- if (!norm.ok) {
84
- sendJson(res, 400, { error: "invalid_body", detail: norm.error });
85
- return;
86
- }
87
- messages = norm.messages;
88
- if (body.images && body.images.length > 0) {
89
- const last = messages[messages.length - 1];
90
- if (!last || last.role !== "user" || typeof body.message !== "string") {
91
- sendJson(res, 400, {
92
- error: "images_require_message",
93
- detail: "With `images`, send a top-level `message` string for the user turn",
94
- });
95
- return;
96
- }
97
- messages[messages.length - 1] = {
98
- role: "user",
99
- content: await buildUserContent(body.message, body.images),
100
- };
101
- }
102
- }
103
- await executeChat(ctx, {
104
- res,
105
- messages,
106
- modelId,
107
- stream,
108
- sessionId,
109
- runOptions: chatRunOptions(body),
110
- autoApprove: body.auto_approve !== false,
111
- sandboxTighten,
112
- });
113
- }
114
- finally {
115
- ctx.activeChats--;
116
- }
117
- }
118
- export const handleChatRoutes = async (req, res, ctx, _url, pathname) => {
119
- if (req.method === "POST" && pathname === "/v1/chat/reload-instructions") {
120
- ctx.instructions = await loadInstructions();
121
- sendJson(res, 200, { ok: true, instructions_chars: ctx.instructions.join("\n").length });
122
- return true;
123
- }
124
- if (req.method === "POST" && (pathname === "/v1/chat" || pathname === "/v1/code")) {
125
- await handleChatRequest(ctx, req, res);
126
- return true;
127
- }
128
- if (req.method === "POST" && pathname === "/v1/chat/redo") {
129
- const parsed = await readJsonBody(req);
130
- if (!parsed.ok) {
131
- sendJsonError(res, parsed);
132
- return true;
133
- }
134
- const body = parsed.value;
135
- const sessionId = typeof body.session_id === "string" ? body.session_id : undefined;
136
- if (!sessionId) {
137
- sendJson(res, 400, { error: "missing_session_id" });
138
- return true;
139
- }
140
- const sandboxParsed = readSandboxTighten(body);
141
- if (!sandboxParsed.ok) {
142
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
143
- return true;
144
- }
145
- const thinkingParsed = readThinking(body);
146
- if (!thinkingParsed.ok) {
147
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
148
- return true;
149
- }
150
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
151
- sendJson(res, 429, {
152
- error: "too_many_concurrent_requests",
153
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
154
- });
155
- return true;
156
- }
157
- ctx.activeChats++;
158
- try {
159
- await withSessionLock(sessionId, async () => {
160
- const session = loadSession(sessionId);
161
- if (!session) {
162
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
163
- return;
164
- }
165
- const text = lastUserText(session.messages);
166
- if (!text) {
167
- sendJson(res, 400, { error: "no_user_message" });
168
- return;
169
- }
170
- await executeChat(ctx, {
171
- res,
172
- messages: [...session.messages, { role: "user", content: text }],
173
- modelId: typeof body.model === "string" ? body.model : undefined,
174
- stream: body.stream === true,
175
- sessionId,
176
- runOptions: chatRunOptions(body),
177
- autoApprove: body.auto_approve !== false,
178
- taskState: session.taskState ?? emptyTaskState(),
179
- sandboxTighten: sandboxParsed.policy,
180
- });
181
- });
182
- }
183
- finally {
184
- ctx.activeChats--;
185
- }
186
- return true;
187
- }
188
- if (req.method === "POST" && pathname === "/v1/approvals") {
189
- const parsed = await readJsonBody(req);
190
- if (!parsed.ok) {
191
- sendJsonError(res, parsed);
192
- return true;
193
- }
194
- const id = parsed.value.id?.trim();
195
- if (!id) {
196
- sendJson(res, 400, { error: "missing_id" });
197
- return true;
198
- }
199
- if (typeof parsed.value.accepted !== "boolean") {
200
- sendJson(res, 400, { error: "missing_accepted" });
201
- return true;
202
- }
203
- if (!ctx.approvalGate.resolveConfirm(id, parsed.value.accepted)) {
204
- sendJson(res, 404, { error: "unknown_approval_id", id });
205
- return true;
206
- }
207
- sendJson(res, 200, { ok: true, id, accepted: parsed.value.accepted });
208
- return true;
209
- }
210
- if (req.method === "POST" && pathname === "/v1/answers") {
211
- const parsed = await readJsonBody(req);
212
- if (!parsed.ok) {
213
- sendJsonError(res, parsed);
214
- return true;
215
- }
216
- const id = parsed.value.id?.trim();
217
- if (!id) {
218
- sendJson(res, 400, { error: "missing_id" });
219
- return true;
220
- }
221
- const answer = parsed.value.answer === undefined ? null : parsed.value.answer;
222
- if (answer !== null && typeof answer !== "string") {
223
- sendJson(res, 400, { error: "invalid_answer" });
224
- return true;
225
- }
226
- if (!ctx.approvalGate.resolveQuestion(id, answer)) {
227
- sendJson(res, 404, { error: "unknown_answer_id", id });
228
- return true;
229
- }
230
- sendJson(res, 200, { ok: true, id });
231
- return true;
232
- }
233
- if (req.method === "POST" && pathname === "/v1/paste") {
234
- if (req.headers["content-type"]?.split(";")[0]?.trim() !== "application/json") {
235
- sendJson(res, 415, { error: "unsupported_media_type" });
236
- return true;
237
- }
238
- if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
239
- sendJson(res, 429, {
240
- error: "too_many_concurrent_requests",
241
- detail: `Max ${MAX_CONCURRENT_CHATS} concurrent chats`,
242
- });
243
- return true;
244
- }
245
- const parsed = await readJsonBody(req);
246
- if (!parsed.ok) {
247
- sendJsonError(res, parsed);
248
- return true;
249
- }
250
- const body = parsed.value;
251
- if (!body.image_base64) {
252
- sendJson(res, 400, { error: "missing_image_base64" });
253
- return true;
254
- }
255
- const sandboxParsed = readSandboxTighten(body);
256
- if (!sandboxParsed.ok) {
257
- sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
258
- return true;
259
- }
260
- const thinkingParsed = readThinking(body);
261
- if (!thinkingParsed.ok) {
262
- sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
263
- return true;
264
- }
265
- const imageBuffer = Buffer.from(body.image_base64, "base64");
266
- const mimeType = body.mime_type ?? "image/png";
267
- const text = body.message ?? "What's in this image?";
268
- const modelId = body.model;
269
- const sessionId = body.session_id;
270
- const stream = body.stream === true;
271
- ctx.activeChats++;
272
- try {
273
- await withSessionLock(sessionId, async () => {
274
- let messages = [];
275
- let pasteTaskState;
276
- if (sessionId) {
277
- const session = loadSession(sessionId);
278
- if (!session) {
279
- sendJson(res, 404, { error: "session_not_found", session_id: sessionId });
280
- return;
281
- }
282
- messages = [...session.messages];
283
- pasteTaskState = session.taskState;
284
- }
285
- messages.push({
286
- role: "user",
287
- content: [
288
- { type: "text", text },
289
- { type: "image", image: imageBuffer, mimeType },
290
- ],
291
- });
292
- await executeChat(ctx, {
293
- res,
294
- messages,
295
- modelId,
296
- stream,
297
- sessionId,
298
- runOptions: chatRunOptions(body),
299
- autoApprove: body.auto_approve !== false,
300
- taskState: pasteTaskState ?? emptyTaskState(),
301
- sandboxTighten: sandboxParsed.policy,
302
- });
303
- });
304
- }
305
- finally {
306
- ctx.activeChats--;
307
- }
308
- return true;
309
- }
310
- if (req.method === "POST" && pathname === "/v1/chat/compact") {
311
- const parsed = await readJsonBody(req);
312
- if (!parsed.ok) {
313
- sendJsonError(res, parsed);
314
- return true;
315
- }
316
- const body = parsed.value;
317
- if (!body.session_id) {
318
- sendJson(res, 400, { error: "missing_session_id" });
319
- return true;
320
- }
321
- const session = loadSession(body.session_id);
322
- if (!session) {
323
- sendJson(res, 404, { error: "session_not_found" });
324
- return true;
325
- }
326
- const model = resolveModel();
327
- const result = await compactMessages(session.messages, model, { taskGoal: session.taskState?.goal });
328
- if (result.compacted) {
329
- saveSession(result.messages, body.session_id);
330
- }
331
- sendJson(res, 200, { ok: true, compacted: result.compacted, message_count: result.messages.length });
332
- return true;
333
- }
334
- if (req.method === "POST" && pathname === "/v1/chat/undo") {
335
- const parsed = await readJsonBody(req);
336
- if (!parsed.ok) {
337
- sendJsonError(res, parsed);
338
- return true;
339
- }
340
- const body = parsed.value;
341
- if (!body.session_id) {
342
- sendJson(res, 400, { error: "missing_session_id" });
343
- return true;
344
- }
345
- const session = loadSession(body.session_id);
346
- if (!session) {
347
- sendJson(res, 404, { error: "session_not_found" });
348
- return true;
349
- }
350
- let userIdx = -1;
351
- for (let i = session.messages.length - 1; i >= 0; i--) {
352
- if (session.messages[i].role === "user") {
353
- userIdx = i;
354
- break;
355
- }
356
- }
357
- if (userIdx === -1) {
358
- sendJson(res, 400, { error: "no_user_message" });
359
- return true;
360
- }
361
- session.messages = session.messages.slice(0, userIdx + 1);
362
- saveSession(session.messages, body.session_id);
363
- sendJson(res, 200, { ok: true, message_count: session.messages.length, messages: session.messages });
364
- return true;
365
- }
366
- return false;
367
- };
@@ -1,212 +0,0 @@
1
- import { getMcpStatus, syncMcpConnections, loadMcpConfigEntries, listMcpCatalog, readMcpResource, getMcpPrompt, } from "../mcp.js";
2
- import { sendJson, sendJsonError, readJsonBody, decodePathSegment } from "./common.js";
3
- export const handleMcpRoutes = async (req, res, _ctx, url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/mcp") {
5
- const status = getMcpStatus();
6
- sendJson(res, 200, { servers: status });
7
- return true;
8
- }
9
- const mcpResources = pathname.match(/^\/v1\/mcp\/([^/]+)\/resources$/);
10
- if (req.method === "GET" && mcpResources) {
11
- const name = decodePathSegment(mcpResources[1]);
12
- if (!name) {
13
- sendJson(res, 400, { error: "missing_name" });
14
- return true;
15
- }
16
- const status = getMcpStatus()[name];
17
- if (!status) {
18
- sendJson(res, 404, { error: "mcp_not_found", name });
19
- return true;
20
- }
21
- if (!status.connected) {
22
- sendJson(res, 404, { error: "mcp_not_connected", name });
23
- return true;
24
- }
25
- const catalog = listMcpCatalog(name).servers[0];
26
- sendJson(res, 200, { name, resources: catalog.resources, templates: catalog.templates });
27
- return true;
28
- }
29
- const mcpPrompts = pathname.match(/^\/v1\/mcp\/([^/]+)\/prompts$/);
30
- if (req.method === "GET" && mcpPrompts) {
31
- const name = decodePathSegment(mcpPrompts[1]);
32
- if (!name) {
33
- sendJson(res, 400, { error: "missing_name" });
34
- return true;
35
- }
36
- const status = getMcpStatus()[name];
37
- if (!status) {
38
- sendJson(res, 404, { error: "mcp_not_found", name });
39
- return true;
40
- }
41
- if (!status.connected) {
42
- sendJson(res, 404, { error: "mcp_not_connected", name });
43
- return true;
44
- }
45
- const catalog = listMcpCatalog(name).servers[0];
46
- sendJson(res, 200, { name, prompts: catalog.prompts });
47
- return true;
48
- }
49
- const mcpRead = pathname.match(/^\/v1\/mcp\/([^/]+)\/resources\/read$/);
50
- if (req.method === "POST" && mcpRead) {
51
- const name = decodePathSegment(mcpRead[1]);
52
- if (!name) {
53
- sendJson(res, 400, { error: "missing_name" });
54
- return true;
55
- }
56
- const parsed = await readJsonBody(req);
57
- if (!parsed.ok) {
58
- sendJsonError(res, parsed);
59
- return true;
60
- }
61
- const uri = parsed.value.uri?.trim();
62
- if (!uri) {
63
- sendJson(res, 400, { error: "missing_uri" });
64
- return true;
65
- }
66
- const status = getMcpStatus()[name];
67
- if (!status) {
68
- sendJson(res, 404, { error: "mcp_not_found", name });
69
- return true;
70
- }
71
- if (!status.connected) {
72
- sendJson(res, 404, { error: "mcp_not_connected", name });
73
- return true;
74
- }
75
- const content = await readMcpResource(uri, name);
76
- sendJson(res, 200, { ok: true, name, uri, content });
77
- return true;
78
- }
79
- const mcpGetPrompt = pathname.match(/^\/v1\/mcp\/([^/]+)\/prompts\/get$/);
80
- if (req.method === "POST" && mcpGetPrompt) {
81
- const name = decodePathSegment(mcpGetPrompt[1]);
82
- if (!name) {
83
- sendJson(res, 400, { error: "missing_name" });
84
- return true;
85
- }
86
- const parsed = await readJsonBody(req);
87
- if (!parsed.ok) {
88
- sendJsonError(res, parsed);
89
- return true;
90
- }
91
- const prompt = parsed.value.prompt?.trim();
92
- if (!prompt) {
93
- sendJson(res, 400, { error: "missing_prompt" });
94
- return true;
95
- }
96
- const status = getMcpStatus()[name];
97
- if (!status) {
98
- sendJson(res, 404, { error: "mcp_not_found", name });
99
- return true;
100
- }
101
- if (!status.connected) {
102
- sendJson(res, 404, { error: "mcp_not_connected", name });
103
- return true;
104
- }
105
- const content = await getMcpPrompt(prompt, parsed.value.arguments, name);
106
- sendJson(res, 200, { ok: true, name, prompt, content });
107
- return true;
108
- }
109
- if (req.method === "POST" && pathname === "/v1/mcp") {
110
- const parsed = await readJsonBody(req);
111
- if (!parsed.ok) {
112
- sendJsonError(res, parsed);
113
- return true;
114
- }
115
- const body = parsed.value;
116
- if (!body.name) {
117
- sendJson(res, 400, { error: "missing_name" });
118
- return true;
119
- }
120
- if (body.remoteTransport && !["streamable-http", "sse", "auto"].includes(body.remoteTransport)) {
121
- sendJson(res, 400, { error: "invalid_remote_transport", allowed: ["streamable-http", "sse", "auto"] });
122
- return true;
123
- }
124
- const scope = body.project ? "project" : "global";
125
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
126
- const config = loadMcpConfigFile(scope);
127
- const common = {
128
- ...(body.environment ? { environment: body.environment } : {}),
129
- ...(body.headers ? { headers: body.headers } : {}),
130
- ...(body.remoteTransport ? { remoteTransport: body.remoteTransport } : {}),
131
- ...(body.timeout !== undefined ? { timeout: body.timeout } : {}),
132
- ...(body.connectTimeout !== undefined ? { connectTimeout: body.connectTimeout } : {}),
133
- ...(body.callTimeout !== undefined ? { callTimeout: body.callTimeout } : {}),
134
- enabled: body.enabled !== false,
135
- };
136
- if (body.url) {
137
- config.mcpServers[body.name] = { url: body.url, ...(body.token ? { token: body.token } : {}), ...common };
138
- }
139
- else if (typeof body.command === "string" && body.command.length > 0) {
140
- config.mcpServers[body.name] = { command: body.command, ...(body.args ? { args: body.args } : {}), ...common };
141
- }
142
- else if (Array.isArray(body.command) && body.command.length > 0) {
143
- config.mcpServers[body.name] = { command: body.command, ...common };
144
- }
145
- else {
146
- sendJson(res, 400, { error: "provide command or url" });
147
- return true;
148
- }
149
- saveMcpConfig(config, scope);
150
- await syncMcpConnections();
151
- const status = getMcpStatus()[body.name];
152
- sendJson(res, 201, {
153
- ok: true,
154
- name: body.name,
155
- scope,
156
- connected: status?.connected ?? false,
157
- ...(status?.error ? { error: status.error } : {}),
158
- ...(status?.tools?.length ? { tools: status.tools } : {}),
159
- });
160
- return true;
161
- }
162
- if (req.method === "DELETE" && pathname.startsWith("/v1/mcp/")) {
163
- const name = decodePathSegment(pathname.slice("/v1/mcp/".length));
164
- if (!name) {
165
- sendJson(res, 400, { error: "missing_name" });
166
- return true;
167
- }
168
- const scope = url.searchParams.get("project") === "1" ? "project" : "global";
169
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
170
- const config = loadMcpConfigFile(scope);
171
- if (!config.mcpServers[name]) {
172
- sendJson(res, 404, { error: "mcp_not_found", name, scope, available: Object.keys(config.mcpServers) });
173
- return true;
174
- }
175
- delete config.mcpServers[name];
176
- saveMcpConfig(config, scope);
177
- await syncMcpConnections();
178
- sendJson(res, 200, { ok: true, deleted: name, scope });
179
- return true;
180
- }
181
- const mcpToggle = pathname.match(/^\/v1\/mcp\/([^/]+)\/(enable|disable)$/);
182
- if (req.method === "POST" && mcpToggle) {
183
- const name = decodePathSegment(mcpToggle[1]);
184
- if (!name) {
185
- sendJson(res, 400, { error: "missing_name" });
186
- return true;
187
- }
188
- const enabled = mcpToggle[2] === "enable";
189
- const parsed = await readJsonBody(req);
190
- const scope = parsed.ok && parsed.value.scope === "project" ? "project" : "global";
191
- const { loadMcpConfigFile, saveMcpConfig } = await import("../mcp.js");
192
- const config = loadMcpConfigFile(scope);
193
- if (!config.mcpServers[name]) {
194
- const known = loadMcpConfigEntries().some((e) => e.name === name);
195
- sendJson(res, 404, {
196
- error: "mcp_not_found",
197
- name,
198
- scope,
199
- ...(known
200
- ? { hint: `server exists in another scope; pass the matching scope` }
201
- : { available: Object.keys(config.mcpServers) }),
202
- });
203
- return true;
204
- }
205
- config.mcpServers[name].enabled = enabled;
206
- saveMcpConfig(config, scope);
207
- await syncMcpConnections();
208
- sendJson(res, 200, { ok: true, name, enabled, scope, connected: getMcpStatus()[name]?.connected ?? false });
209
- return true;
210
- }
211
- return false;
212
- };
@@ -1,95 +0,0 @@
1
- import { loadMemories, addMemory, deleteMemory, searchMemories, readMemoryScope, parseMemoryMode, setMemoryMode, memoryPayload, } from "../memory.js";
2
- import { sendJson, sendJsonError, readJsonBody } from "./common.js";
3
- export const handleMemoryRoutes = async (req, res, _ctx, url, pathname) => {
4
- if (req.method === "GET" && pathname === "/v1/memory/mode") {
5
- sendJson(res, 200, memoryPayload());
6
- return true;
7
- }
8
- if (req.method === "POST" && pathname === "/v1/memory/mode") {
9
- const parsed = await readJsonBody(req);
10
- if (!parsed.ok) {
11
- sendJsonError(res, parsed);
12
- return true;
13
- }
14
- const mode = parseMemoryMode(parsed.value.memory);
15
- if (!mode) {
16
- sendJson(res, 400, { error: "invalid_memory", detail: "memory must be on or off" });
17
- return true;
18
- }
19
- const scopeParsed = readMemoryScope(parsed.value.scope);
20
- if (!scopeParsed.ok) {
21
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
22
- return true;
23
- }
24
- const scope = scopeParsed.scope ?? "global";
25
- setMemoryMode(mode, scope);
26
- sendJson(res, 200, { ok: true, scope, ...memoryPayload() });
27
- return true;
28
- }
29
- if (req.method === "GET" && pathname === "/v1/memory") {
30
- sendJson(res, 200, {
31
- ...memoryPayload(),
32
- memories: loadMemories("global"),
33
- project_memories: loadMemories("project"),
34
- });
35
- return true;
36
- }
37
- if (req.method === "POST" && pathname === "/v1/memory") {
38
- const parsed = await readJsonBody(req);
39
- if (!parsed.ok) {
40
- sendJsonError(res, parsed);
41
- return true;
42
- }
43
- const body = parsed.value;
44
- if (!body.content) {
45
- sendJson(res, 400, { error: "missing_content" });
46
- return true;
47
- }
48
- const scopeParsed = readMemoryScope(body.scope);
49
- if (!scopeParsed.ok) {
50
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
51
- return true;
52
- }
53
- const scope = scopeParsed.scope ?? "global";
54
- const memory = addMemory(body.content, body.tags ?? [], scope);
55
- sendJson(res, 201, { ok: true, memory, scope });
56
- return true;
57
- }
58
- if (req.method === "GET" && pathname === "/v1/memory/search") {
59
- const query = url.searchParams.get("q") ?? "";
60
- if (!query) {
61
- sendJson(res, 400, { error: "missing_query_param_q" });
62
- return true;
63
- }
64
- const scopeParsed = readMemoryScope(url.searchParams.get("scope"));
65
- if (!scopeParsed.ok) {
66
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
67
- return true;
68
- }
69
- const globalResults = scopeParsed.scope === "project" ? [] : searchMemories(query, "global");
70
- const projectResults = scopeParsed.scope === "global" ? [] : searchMemories(query, "project");
71
- sendJson(res, 200, { results: globalResults, project_results: projectResults });
72
- return true;
73
- }
74
- if (req.method === "DELETE" && pathname.startsWith("/v1/memory/")) {
75
- const idx = parseInt(pathname.slice("/v1/memory/".length), 10);
76
- if (Number.isNaN(idx)) {
77
- sendJson(res, 400, { error: "invalid_index" });
78
- return true;
79
- }
80
- const scopeParsed = readMemoryScope(url.searchParams.get("scope"));
81
- if (!scopeParsed.ok) {
82
- sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
83
- return true;
84
- }
85
- const scope = scopeParsed.scope ?? "global";
86
- const ok = deleteMemory(idx - 1, scope);
87
- if (!ok) {
88
- sendJson(res, 404, { error: "memory_not_found", index: idx, scope });
89
- return true;
90
- }
91
- sendJson(res, 200, { ok: true, deleted: idx, scope });
92
- return true;
93
- }
94
- return false;
95
- };