amalgm 0.1.72 → 0.1.73

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 (64) hide show
  1. package/package.json +1 -2
  2. package/runtime/scripts/amalgm-mcp/agents/rest.js +29 -22
  3. package/runtime/scripts/amalgm-mcp/agents/store.js +90 -24
  4. package/runtime/scripts/amalgm-mcp/agents/talk.js +66 -37
  5. package/runtime/scripts/amalgm-mcp/agents/tools.js +0 -4
  6. package/runtime/scripts/amalgm-mcp/automations/runner.js +4 -5
  7. package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +54 -57
  8. package/runtime/scripts/amalgm-mcp/automations/tools.js +4 -4
  9. package/runtime/scripts/amalgm-mcp/email/inbound.js +21 -0
  10. package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +3 -3
  11. package/runtime/scripts/amalgm-mcp/index.js +3 -1
  12. package/runtime/scripts/amalgm-mcp/lib/prefs.js +15 -0
  13. package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +5 -3
  14. package/runtime/scripts/amalgm-mcp/runtime-worker.js +47 -0
  15. package/runtime/scripts/amalgm-mcp/server/combined-service.js +27 -0
  16. package/runtime/scripts/amalgm-mcp/server/control-plane-service.js +24 -0
  17. package/runtime/scripts/amalgm-mcp/server/core-tools.js +52 -7
  18. package/runtime/scripts/amalgm-mcp/server/http-context.js +44 -0
  19. package/runtime/scripts/amalgm-mcp/server/http-service.js +34 -0
  20. package/runtime/scripts/amalgm-mcp/server/http.js +31 -465
  21. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +57 -0
  22. package/runtime/scripts/amalgm-mcp/server/mcp-adapter.js +22 -0
  23. package/runtime/scripts/amalgm-mcp/server/mcp-http-service.js +24 -0
  24. package/runtime/scripts/amalgm-mcp/server/mcp-request-context.js +32 -0
  25. package/runtime/scripts/amalgm-mcp/server/mcp.js +14 -73
  26. package/runtime/scripts/amalgm-mcp/server/routes/agents.js +29 -0
  27. package/runtime/scripts/amalgm-mcp/server/routes/apps.js +59 -0
  28. package/runtime/scripts/amalgm-mcp/server/routes/automations.js +45 -0
  29. package/runtime/scripts/amalgm-mcp/server/routes/browser.js +49 -0
  30. package/runtime/scripts/amalgm-mcp/server/routes/credentials.js +30 -0
  31. package/runtime/scripts/amalgm-mcp/server/routes/events.js +18 -0
  32. package/runtime/scripts/amalgm-mcp/server/routes/files.js +33 -0
  33. package/runtime/scripts/amalgm-mcp/server/routes/health.js +24 -0
  34. package/runtime/scripts/amalgm-mcp/server/routes/inbound.js +18 -0
  35. package/runtime/scripts/amalgm-mcp/server/routes/local.js +21 -0
  36. package/runtime/scripts/amalgm-mcp/server/routes/preferences.js +18 -0
  37. package/runtime/scripts/amalgm-mcp/server/routes/state.js +23 -0
  38. package/runtime/scripts/amalgm-mcp/server/routes/toolbox.js +59 -0
  39. package/runtime/scripts/amalgm-mcp/server/routes/workspace.js +57 -0
  40. package/runtime/scripts/amalgm-mcp/server/service-lifecycle.js +54 -0
  41. package/runtime/scripts/amalgm-mcp/services/control-plane.js +19 -0
  42. package/runtime/scripts/amalgm-mcp/services/mcp-adapter.js +12 -0
  43. package/runtime/scripts/amalgm-mcp/services/runtime-worker.js +18 -0
  44. package/runtime/scripts/amalgm-mcp/slack/inbound.js +21 -0
  45. package/runtime/scripts/amalgm-mcp/state/snapshot.js +3 -3
  46. package/runtime/scripts/amalgm-mcp/tests/agents-source-of-truth.test.js +82 -0
  47. package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +5 -5
  48. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +37 -0
  49. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +158 -0
  50. package/runtime/scripts/amalgm-mcp/tests/server-routing.test.js +81 -0
  51. package/runtime/scripts/amalgm-mcp/tests/server-services.test.js +156 -0
  52. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +81 -0
  53. package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +250 -0
  54. package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +106 -0
  55. package/runtime/scripts/amalgm-mcp/toolbox/names.js +41 -0
  56. package/runtime/scripts/amalgm-mcp/toolbox/rest.js +107 -53
  57. package/runtime/scripts/amalgm-mcp/toolbox/runner.js +9 -75
  58. package/runtime/scripts/amalgm-mcp/toolbox/selection.js +22 -0
  59. package/runtime/scripts/amalgm-mcp/toolbox/service.js +227 -0
  60. package/runtime/scripts/amalgm-mcp/toolbox/store.js +9 -78
  61. package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +112 -0
  62. package/runtime/scripts/amalgm-mcp/toolbox/tools.js +11 -74
  63. package/runtime/scripts/amalgm-mcp/workflows/compiler.js +1 -1
  64. package/runtime/scripts/amalgm-mcp/workflows/runner.js +4 -5
@@ -2,12 +2,8 @@
2
2
  * MCP server — built on @modelcontextprotocol/sdk.
3
3
  *
4
4
  * The SDK handles JSON-RPC framing, protocol-version negotiation, batching,
5
- * and streamable-HTTP transport. We just register the two tool handlers.
6
- *
7
- * Each domain module exports a flat array of
8
- * { name, description, inputSchema, handler }. We concatenate the built-in
9
- * Amalgm tools with toolbox management tools. Custom CLI/API actions registered
10
- * in the ToolboxDB are exposed dynamically through this same MCP transport.
5
+ * and streamable-HTTP transport. The Toolbox MCP surface owns tool listing and
6
+ * call resolution.
11
7
  */
12
8
 
13
9
  const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
@@ -19,47 +15,18 @@ const {
19
15
  CallToolRequestSchema,
20
16
  } = require('@modelcontextprotocol/sdk/types.js');
21
17
 
22
- const { AMALGM_USER_ID } = require('../config');
23
- const { hasSupabase, supabaseSelect } = require('../lib/supabase');
24
18
  const { attachActionDescriptor } = require('../lib/tool-result');
25
- const { CORE_TOOLS } = require('./core-tools');
26
19
  const {
27
- callToolboxMcpTool,
28
- listToolboxMcpTools,
29
- } = require('../toolbox/runner');
30
-
31
- const TOOLS = [
32
- ...CORE_TOOLS,
33
- ...require('../toolbox/tools'),
34
- ];
35
-
36
- /**
37
- * Look up the caller's session to tag DB rows with origin metadata. Reads
38
- * x-amalgm-session-id from the transport's passed-through HTTP headers.
39
- */
40
- async function buildRequestContext(extra) {
41
- const headers = extra?.requestInfo?.headers || {};
42
- const h = headers['x-amalgm-session-id'];
43
- const callerSessionId = Array.isArray(h) ? h[0] : h || null;
44
- const ctx = { callerSessionId };
20
+ DEFAULT_STATIC_MCP_TOOLS,
21
+ createToolboxMcpSurface,
22
+ } = require('../toolbox/mcp-surface');
23
+ const { buildMcpRequestContext } = require('./mcp-request-context');
45
24
 
46
- if (callerSessionId && hasSupabase()) {
47
- const rows = await supabaseSelect(
48
- 'sessions',
49
- `id=eq.${callerSessionId}&user_id=eq.${AMALGM_USER_ID}&select=id,title,harness,metadata`,
50
- );
51
- const parent = Array.isArray(rows) ? rows[0] : null;
52
- if (parent) {
53
- ctx.sessionMetadata = parent.metadata || {};
54
- ctx.originName = parent.title || undefined;
55
- ctx.originHarnessId = parent.harness || undefined;
56
- ctx.originBaseHarnessId = parent.metadata?.baseHarnessId || undefined;
57
- }
58
- }
59
- return ctx;
60
- }
25
+ const TOOLS = DEFAULT_STATIC_MCP_TOOLS;
61
26
 
62
- function createMcpServer() {
27
+ function createMcpServer(options = {}) {
28
+ const surface = options.surface || createToolboxMcpSurface();
29
+ const buildRequestContext = options.buildRequestContext || buildMcpRequestContext;
63
30
  const server = new Server(
64
31
  { name: 'amalgm', version: '1.0.0' },
65
32
  { capabilities: { tools: {} } },
@@ -67,41 +34,15 @@ function createMcpServer() {
67
34
 
68
35
  server.setRequestHandler(ListToolsRequestSchema, async (_request, extra) => {
69
36
  const ctx = await buildRequestContext(extra);
70
- return {
71
- tools: [
72
- ...TOOLS.map(({ name, description, inputSchema }) => ({
73
- name,
74
- description,
75
- inputSchema,
76
- })),
77
- ...listToolboxMcpTools(ctx),
78
- ],
79
- };
37
+ return { tools: await surface.listTools(ctx) };
80
38
  });
81
39
 
82
40
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
83
41
  const toolName = request.params?.name;
84
42
  const toolArgs = request.params?.arguments || {};
85
- const tool = TOOLS.find((t) => t.name === toolName);
86
- if (!tool) {
87
- const ctx = await buildRequestContext(extra);
88
- const toolboxResult = await callToolboxMcpTool(toolName, toolArgs, ctx);
89
- if (toolboxResult) return attachActionDescriptor(toolboxResult, toolName, toolArgs);
90
- return attachActionDescriptor({
91
- content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],
92
- isError: true,
93
- }, toolName || 'unknown', toolArgs);
94
- }
95
43
  const ctx = await buildRequestContext(extra);
96
- try {
97
- const result = await tool.handler(toolArgs, ctx);
98
- return attachActionDescriptor(result, toolName, toolArgs);
99
- } catch (err) {
100
- return attachActionDescriptor({
101
- content: [{ type: 'text', text: `Error: ${err.message}` }],
102
- isError: true,
103
- }, toolName, toolArgs);
104
- }
44
+ const result = await surface.callTool(toolName, toolArgs, ctx);
45
+ return attachActionDescriptor(result, toolName || 'unknown', toolArgs);
105
46
  });
106
47
 
107
48
  return server;
@@ -124,4 +65,4 @@ async function createTransport() {
124
65
  return transport;
125
66
  }
126
67
 
127
- module.exports = { createTransport, TOOLS };
68
+ module.exports = { createMcpServer, createTransport, TOOLS };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const agentsRest = require('../../agents/rest');
4
+
5
+ async function handleAgentRoutes(ctx) {
6
+ if (ctx.pathname === '/agents/list' && ctx.method === 'GET') {
7
+ agentsRest.handleList(ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/agents/get' && ctx.method === 'POST') {
11
+ agentsRest.handleGet(await ctx.readJsonBody(), ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/agents/create' && ctx.method === 'POST') {
15
+ agentsRest.handleCreate(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/agents/update' && ctx.method === 'POST') {
19
+ agentsRest.handleUpdate(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/agents/delete' && ctx.method === 'POST') {
23
+ agentsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ return false;
27
+ }
28
+
29
+ module.exports = { handleAgentRoutes };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const appsRest = require('../../apps/rest');
4
+
5
+ function appRoute(pathname) {
6
+ if (pathname.startsWith('/apps/')) {
7
+ return { path: pathname.slice('/apps'.length), legacy: false };
8
+ }
9
+ if (pathname.startsWith('/artifacts/')) {
10
+ return { path: pathname.slice('/artifacts'.length), legacy: true };
11
+ }
12
+ return null;
13
+ }
14
+
15
+ async function handleAppRoutes(ctx) {
16
+ const route = appRoute(ctx.pathname);
17
+ if (!route) return false;
18
+ const options = route.legacy ? { legacy: true } : undefined;
19
+
20
+ if (route.path === '/list' && ctx.method === 'GET') {
21
+ appsRest.handleList(ctx.sendJson, options);
22
+ return true;
23
+ }
24
+ if (route.path === '/routes' && ctx.method === 'GET') {
25
+ await appsRest.handleRoutes(ctx.sendJson, options);
26
+ return true;
27
+ }
28
+ if (route.path === '/register' && ctx.method === 'POST') {
29
+ await appsRest.handleRegister(await ctx.readJsonBody(), ctx.sendJson, options);
30
+ return true;
31
+ }
32
+ if (route.path === '/redeploy' && ctx.method === 'POST') {
33
+ await appsRest.handleRedeploy(await ctx.readJsonBody(), ctx.sendJson, options);
34
+ return true;
35
+ }
36
+ if (route.path === '/start' && ctx.method === 'POST') {
37
+ await appsRest.handleStart(await ctx.readJsonBody(), ctx.sendJson, options);
38
+ return true;
39
+ }
40
+ if (route.path === '/stop' && ctx.method === 'POST') {
41
+ await appsRest.handleStop(await ctx.readJsonBody(), ctx.sendJson, options);
42
+ return true;
43
+ }
44
+ if (route.path === '/delete' && ctx.method === 'POST') {
45
+ await appsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson, options);
46
+ return true;
47
+ }
48
+ if (route.path === '/connect-dns' && ctx.method === 'POST') {
49
+ await appsRest.handleConnectDns(await ctx.readJsonBody(), ctx.sendJson, options);
50
+ return true;
51
+ }
52
+ if (route.path === '/disconnect-dns' && ctx.method === 'POST') {
53
+ await appsRest.handleDisconnectDns(await ctx.readJsonBody(), ctx.sendJson, options);
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ module.exports = { appRoute, handleAppRoutes };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ const automationsRest = require('../../automations/rest');
4
+
5
+ async function handleAutomationRoutes(ctx) {
6
+ if (ctx.pathname === '/automations/list' && ctx.method === 'GET') {
7
+ automationsRest.handleList(ctx.req, ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/automations/get' && ctx.method === 'POST') {
11
+ automationsRest.handleGet(await ctx.readJsonBody(), ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/automations/create' && ctx.method === 'POST') {
15
+ await automationsRest.handleCreate(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/automations/update' && ctx.method === 'POST') {
19
+ automationsRest.handleUpdate(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/automations/delete' && ctx.method === 'POST') {
23
+ automationsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ if (ctx.pathname === '/automations/run-now' && ctx.method === 'POST') {
27
+ await automationsRest.handleRunNow(await ctx.readJsonBody(), ctx.sendJson);
28
+ return true;
29
+ }
30
+ if (ctx.pathname === '/automations/validate' && ctx.method === 'POST') {
31
+ await automationsRest.handleValidate(await ctx.readJsonBody(), ctx.sendJson);
32
+ return true;
33
+ }
34
+ if (ctx.pathname === '/automations/triggers/update' && ctx.method === 'POST') {
35
+ automationsRest.handleTriggerUpdate(await ctx.readJsonBody(), ctx.sendJson);
36
+ return true;
37
+ }
38
+ if (ctx.pathname === '/automations/triggers/delete' && ctx.method === 'POST') {
39
+ automationsRest.handleTriggerDelete(await ctx.readJsonBody(), ctx.sendJson);
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+
45
+ module.exports = { handleAutomationRoutes };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const browserRest = require('../../browser/rest');
4
+
5
+ async function handleBrowserRoutes(ctx) {
6
+ if (ctx.pathname === '/browser/profiles' && ctx.method === 'GET') {
7
+ browserRest.handleProfilesList(ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/browser/profiles' && ctx.method === 'POST') {
11
+ await browserRest.handleProfilesUpsert(await ctx.readJsonBody(), ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/browser/auth-bundles' && ctx.method === 'GET') {
15
+ browserRest.handleAuthBundlesList(ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/browser/auth-bundles/export' && ctx.method === 'POST') {
19
+ await browserRest.handleAuthBundleExport(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/browser/auth-bundles/import' && ctx.method === 'POST') {
23
+ await browserRest.handleAuthBundleImport(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ if (ctx.pathname === '/browser/auth-bundles' && ctx.method === 'DELETE') {
27
+ await browserRest.handleAuthBundleDelete(await ctx.readJsonBody(), ctx.sendJson);
28
+ return true;
29
+ }
30
+ if (ctx.pathname.startsWith('/browser/login-sessions') && ctx.method === 'GET') {
31
+ browserRest.handleLoginSessionsList(ctx.getQuery(), ctx.sendJson);
32
+ return true;
33
+ }
34
+ if (ctx.pathname === '/browser/login-sessions' && ctx.method === 'POST') {
35
+ browserRest.handleLoginSessionCreate(await ctx.readJsonBody(), ctx.sendJson);
36
+ return true;
37
+ }
38
+ if (ctx.pathname === '/browser/login-sessions/complete' && ctx.method === 'POST') {
39
+ await browserRest.handleLoginSessionComplete(await ctx.readJsonBody(), ctx.sendJson);
40
+ return true;
41
+ }
42
+ if (ctx.pathname === '/browser/login-sessions/cancel' && ctx.method === 'POST') {
43
+ browserRest.handleLoginSessionCancel(await ctx.readJsonBody(), ctx.sendJson);
44
+ return true;
45
+ }
46
+ return false;
47
+ }
48
+
49
+ module.exports = { handleBrowserRoutes };
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const credentialsRest = require('../../credentials/rest');
4
+ const userApiKeysRest = require('../../user-api-keys/rest');
5
+
6
+ async function handleCredentialRoutes(ctx) {
7
+ if (ctx.pathname === '/credentials' && ctx.method === 'GET') {
8
+ credentialsRest.handleList(ctx.sendJson);
9
+ return true;
10
+ }
11
+ if (ctx.pathname === '/credentials' && ctx.method === 'POST') {
12
+ credentialsRest.handleSave(await ctx.readJsonBody(), ctx.sendJson);
13
+ return true;
14
+ }
15
+ if (ctx.pathname === '/credentials' && ctx.method === 'DELETE') {
16
+ credentialsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson);
17
+ return true;
18
+ }
19
+ if (ctx.pathname === '/user-api-keys' && ctx.method === 'POST') {
20
+ userApiKeysRest.handleSave(await ctx.readJsonBody(), ctx.sendJson);
21
+ return true;
22
+ }
23
+ if (ctx.pathname === '/user-api-keys' && ctx.method === 'DELETE') {
24
+ userApiKeysRest.handleDelete(ctx.sendJson);
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+
30
+ module.exports = { handleCredentialRoutes };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const { handleEventsPost } = require('../../events/ingress');
4
+ const eventsRing = require('../../events/ring-buffer');
5
+
6
+ async function handleEventRoutes(ctx) {
7
+ if (ctx.pathname === '/events' && ctx.method === 'POST') {
8
+ await handleEventsPost(ctx.req, ctx.sendJson);
9
+ return true;
10
+ }
11
+ if (ctx.pathname === '/events' && ctx.method === 'GET') {
12
+ ctx.sendJson(200, { events: eventsRing.recent(50) });
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+
18
+ module.exports = { handleEventRoutes };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const fsRest = require('../../fs/rest');
4
+
5
+ async function handleFileRoutes(ctx) {
6
+ if (ctx.pathname === '/fs/list' && ctx.method === 'GET') {
7
+ fsRest.handleList(ctx.getQuery(), ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/fs/read' && ctx.method === 'GET') {
11
+ fsRest.handleRead(ctx.getQuery(), ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/fs/write' && ctx.method === 'POST') {
15
+ fsRest.handleWrite(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/fs/delete' && ctx.method === 'POST') {
19
+ fsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/fs/mkdir' && ctx.method === 'POST') {
23
+ fsRest.handleMkdir(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ if (ctx.pathname === '/fs/rename' && ctx.method === 'POST') {
27
+ fsRest.handleRename(await ctx.readJsonBody(), ctx.sendJson);
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+
33
+ module.exports = { handleFileRoutes };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { loadAgents } = require('../../agents/store');
4
+ const { loadApps } = require('../../apps/store');
5
+ const { activeAgentConversations } = require('../../agents/talk');
6
+ const { listAutomations } = require('../../automations/store');
7
+
8
+ async function handleHealthRoutes(ctx) {
9
+ if (ctx.pathname !== '/healthz') return false;
10
+
11
+ const agents = loadAgents();
12
+ const automations = listAutomations();
13
+ ctx.sendJson(200, {
14
+ status: 'ok',
15
+ automations: automations.length,
16
+ enabledAutomations: automations.filter((automation) => automation.enabled !== false).length,
17
+ apps: loadApps().apps.length,
18
+ customAgents: agents.agents.length,
19
+ activeAgentConversations: activeAgentConversations.size,
20
+ });
21
+ return true;
22
+ }
23
+
24
+ module.exports = { handleHealthRoutes };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const emailInbound = require('../../email/inbound');
4
+ const slackInbound = require('../../slack/inbound');
5
+
6
+ async function handleInboundRoutes(ctx) {
7
+ if (ctx.pathname === '/email/inbound' && ctx.method === 'POST') {
8
+ await emailInbound.handleEmailInbound(await ctx.readJsonBody(), ctx.sendJson);
9
+ return true;
10
+ }
11
+ if (ctx.pathname === '/slack/inbound' && ctx.method === 'POST') {
12
+ await slackInbound.handleSlackInbound(await ctx.readJsonBody(), ctx.sendJson);
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+
18
+ module.exports = { handleInboundRoutes };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const localRest = require('../../local/rest');
4
+
5
+ async function handleLocalRoutes(ctx) {
6
+ if (ctx.pathname === '/local/init' && ctx.method === 'POST') {
7
+ localRest.handleInit(ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/local/credentials' && ctx.method === 'GET') {
11
+ localRest.handleCredentials(ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/local/credentials/resolve' && ctx.method === 'POST') {
15
+ localRest.handleResolve(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ return false;
19
+ }
20
+
21
+ module.exports = { handleLocalRoutes };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const prefsStore = require('../../lib/prefs');
4
+
5
+ async function handlePreferenceRoutes(ctx) {
6
+ if (ctx.pathname === '/user-preferences' && ctx.method === 'GET') {
7
+ ctx.sendJson(200, prefsStore.readUserPreferences());
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/user-preferences' && ctx.method === 'PUT') {
11
+ const preferences = prefsStore.updateUserPreferences(await ctx.readJsonBody());
12
+ ctx.sendJson(200, { success: true, preferences });
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+
18
+ module.exports = { handlePreferenceRoutes };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const stateRest = require('../../state/rest');
4
+
5
+ async function handleStateRoutes(ctx) {
6
+ if (ctx.method !== 'GET') return false;
7
+
8
+ if (ctx.pathname === '/state/snapshot') {
9
+ stateRest.handleSnapshot(ctx.getQuery(), ctx.sendJson);
10
+ return true;
11
+ }
12
+ if (ctx.pathname === '/state/events') {
13
+ stateRest.handleEvents(ctx.getQuery(), ctx.sendJson);
14
+ return true;
15
+ }
16
+ if (ctx.pathname === '/state/stream') {
17
+ stateRest.handleStream(ctx.req, ctx.res, ctx.getQuery());
18
+ return true;
19
+ }
20
+ return false;
21
+ }
22
+
23
+ module.exports = { handleStateRoutes };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const mcpConnectionsRest = require('../../mcp-connections/rest');
4
+ const toolboxRest = require('../../toolbox/rest');
5
+
6
+ async function handleToolboxRoutes(ctx) {
7
+ if (ctx.pathname === '/mcp-connections' && ctx.method === 'GET') {
8
+ mcpConnectionsRest.handleList(ctx.sendJson);
9
+ return true;
10
+ }
11
+ if (ctx.pathname === '/mcp-connections' && ctx.method === 'POST') {
12
+ mcpConnectionsRest.handleUpsert(await ctx.readJsonBody(), ctx.sendJson);
13
+ return true;
14
+ }
15
+ if (ctx.pathname === '/mcp-connections' && ctx.method === 'DELETE') {
16
+ mcpConnectionsRest.handleDelete(await ctx.readJsonBody(), ctx.sendJson);
17
+ return true;
18
+ }
19
+
20
+ if (ctx.pathname === '/toolbox' && ctx.method === 'GET') {
21
+ toolboxRest.handleList(ctx.sendJson);
22
+ return true;
23
+ }
24
+ if (ctx.pathname === '/toolbox/tools' && ctx.method === 'GET') {
25
+ toolboxRest.handleGetTool(ctx.getQuery(), ctx.sendJson);
26
+ return true;
27
+ }
28
+ if (ctx.pathname === '/toolbox/tools' && ctx.method === 'POST') {
29
+ toolboxRest.handleUpsertTool(await ctx.readJsonBody(), ctx.sendJson);
30
+ return true;
31
+ }
32
+ if (ctx.pathname === '/toolbox/tools' && ctx.method === 'PATCH') {
33
+ toolboxRest.handleUpdateTool(await ctx.readJsonBody(), ctx.sendJson);
34
+ return true;
35
+ }
36
+ if (ctx.pathname === '/toolbox/tools' && ctx.method === 'DELETE') {
37
+ toolboxRest.handleDeleteTool(await ctx.readJsonBody(), ctx.sendJson);
38
+ return true;
39
+ }
40
+ if (ctx.pathname === '/toolbox/actions' && ctx.method === 'GET') {
41
+ toolboxRest.handleGetAction(ctx.getQuery(), ctx.sendJson);
42
+ return true;
43
+ }
44
+ if (ctx.pathname === '/toolbox/actions' && ctx.method === 'POST') {
45
+ toolboxRest.handleUpsertAction(await ctx.readJsonBody(), ctx.sendJson);
46
+ return true;
47
+ }
48
+ if (ctx.pathname === '/toolbox/actions' && ctx.method === 'PATCH') {
49
+ toolboxRest.handleUpdateAction(await ctx.readJsonBody(), ctx.sendJson);
50
+ return true;
51
+ }
52
+ if (ctx.pathname === '/toolbox/actions' && ctx.method === 'DELETE') {
53
+ toolboxRest.handleDeleteAction(await ctx.readJsonBody(), ctx.sendJson);
54
+ return true;
55
+ }
56
+ return false;
57
+ }
58
+
59
+ module.exports = { handleToolboxRoutes };
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const workspaceRest = require('../../workspace/rest');
4
+
5
+ async function handleWorkspaceRoutes(ctx) {
6
+ if (ctx.pathname === '/workspace/projects' && ctx.method === 'GET') {
7
+ workspaceRest.handleProjects(ctx.sendJson);
8
+ return true;
9
+ }
10
+ if (ctx.pathname === '/workspace/projects' && ctx.method === 'POST') {
11
+ workspaceRest.handleProjectRegister(await ctx.readJsonBody(), ctx.sendJson);
12
+ return true;
13
+ }
14
+ if (ctx.pathname === '/workspace/projects' && ctx.method === 'PATCH') {
15
+ workspaceRest.handleProjectUpdate(await ctx.readJsonBody(), ctx.sendJson);
16
+ return true;
17
+ }
18
+ if (ctx.pathname === '/workspace/projects' && ctx.method === 'DELETE') {
19
+ workspaceRest.handleProjectDelete(await ctx.readJsonBody(), ctx.sendJson);
20
+ return true;
21
+ }
22
+ if (ctx.pathname === '/workspace/import' && ctx.method === 'POST') {
23
+ workspaceRest.handleImport(await ctx.readJsonBody(), ctx.sendJson);
24
+ return true;
25
+ }
26
+ if (ctx.pathname === '/github/clone' && ctx.method === 'POST') {
27
+ await workspaceRest.handleClone(await ctx.readJsonBody(), ctx.sendJson);
28
+ return true;
29
+ }
30
+ if (ctx.pathname === '/workspace/branches' && ctx.method === 'GET') {
31
+ workspaceRest.handleBranches(ctx.getQuery(), ctx.sendJson);
32
+ return true;
33
+ }
34
+ if (ctx.pathname === '/workspace/checkout' && ctx.method === 'POST') {
35
+ workspaceRest.handleCheckout(await ctx.readJsonBody(), ctx.sendJson);
36
+ return true;
37
+ }
38
+ if (ctx.pathname === '/workspace/diff' && ctx.method === 'GET') {
39
+ workspaceRest.handleDiff(ctx.getQuery(), ctx.sendJson);
40
+ return true;
41
+ }
42
+ if (ctx.pathname === '/workspace/worktree' && ctx.method === 'GET') {
43
+ workspaceRest.handleWorktreeList(ctx.getQuery(), ctx.sendJson);
44
+ return true;
45
+ }
46
+ if (ctx.pathname === '/workspace/worktree' && ctx.method === 'POST') {
47
+ workspaceRest.handleWorktreeCreate(await ctx.readJsonBody(), ctx.sendJson);
48
+ return true;
49
+ }
50
+ if (ctx.pathname === '/workspace/worktree' && ctx.method === 'DELETE') {
51
+ workspaceRest.handleWorktreeDelete(await ctx.readJsonBody(), ctx.sendJson);
52
+ return true;
53
+ }
54
+ return false;
55
+ }
56
+
57
+ module.exports = { handleWorkspaceRoutes };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ function startHttpServer(server, options = {}) {
4
+ const serviceName = options.serviceName || 'AmalgmService';
5
+ const host = options.host || '127.0.0.1';
6
+ const port = Number(options.port);
7
+ if (!Number.isFinite(port) || port < 0) {
8
+ throw new Error(`${serviceName} requires a valid port`);
9
+ }
10
+
11
+ server.listen(port, host, async () => {
12
+ const address = server.address();
13
+ const boundHost = address && typeof address === 'object' ? address.address : host;
14
+ const boundPort = address && typeof address === 'object' ? address.port : port;
15
+ console.log(`[${serviceName}] Listening on ${boundHost}:${boundPort}`);
16
+ await options.onListening?.();
17
+ });
18
+
19
+ if (options.installShutdown !== false) {
20
+ installGracefulShutdown(server, {
21
+ serviceName,
22
+ onShutdown: options.onShutdown,
23
+ exit: options.exit !== false,
24
+ });
25
+ }
26
+
27
+ return server;
28
+ }
29
+
30
+ function installGracefulShutdown(server, options = {}) {
31
+ const serviceName = options.serviceName || 'AmalgmService';
32
+ const exit = options.exit !== false;
33
+ let shuttingDown = false;
34
+
35
+ function shutdown() {
36
+ if (shuttingDown) return;
37
+ shuttingDown = true;
38
+ console.log(`[${serviceName}] Shutting down...`);
39
+ options.onShutdown?.();
40
+ server.close(() => {
41
+ if (exit) process.exit(0);
42
+ });
43
+ if (exit) setTimeout(() => process.exit(0), 5000);
44
+ }
45
+
46
+ process.on('SIGTERM', shutdown);
47
+ process.on('SIGINT', shutdown);
48
+ return shutdown;
49
+ }
50
+
51
+ module.exports = {
52
+ installGracefulShutdown,
53
+ startHttpServer,
54
+ };