akemon 0.1.7 → 0.1.8
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/dist/relay-client.js +127 -0
- package/dist/server.js +43 -3
- package/package.json +1 -1
package/dist/relay-client.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
2
|
import http from "http";
|
|
3
3
|
const DEFAULT_RELAY_URL = "wss://relay.akemon.dev";
|
|
4
|
+
// Pending agent_call results (callId → resolve function)
|
|
5
|
+
const pendingAgentCalls = new Map();
|
|
6
|
+
let relayWsRef = null;
|
|
7
|
+
/** Call another agent through the relay. Available to any engine. */
|
|
8
|
+
export function callAgent(target, task) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
if (!relayWsRef || relayWsRef.readyState !== WebSocket.OPEN) {
|
|
11
|
+
reject(new Error("Not connected to relay"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const callId = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
15
|
+
pendingAgentCalls.set(callId, resolve);
|
|
16
|
+
relayWsRef.send(JSON.stringify({
|
|
17
|
+
type: "agent_call",
|
|
18
|
+
call_id: callId,
|
|
19
|
+
target,
|
|
20
|
+
task,
|
|
21
|
+
}));
|
|
22
|
+
// Timeout after 5 minutes
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
if (pendingAgentCalls.has(callId)) {
|
|
25
|
+
pendingAgentCalls.delete(callId);
|
|
26
|
+
reject(new Error(`agent_call to ${target} timed out`));
|
|
27
|
+
}
|
|
28
|
+
}, 300_000);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
4
31
|
export function connectRelay(options) {
|
|
5
32
|
const relayUrl = options.relayUrl || DEFAULT_RELAY_URL;
|
|
6
33
|
let wsUrl = relayUrl.replace(/^http/, "ws");
|
|
@@ -52,6 +79,7 @@ export function connectRelay(options) {
|
|
|
52
79
|
ws.on("open", () => {
|
|
53
80
|
console.log(`[relay-ws] Connected. Registering agent "${options.agentName}"...`);
|
|
54
81
|
reconnectDelay = 1000; // reset backoff
|
|
82
|
+
relayWsRef = ws;
|
|
55
83
|
// Send registration message
|
|
56
84
|
const reg = {
|
|
57
85
|
type: "register",
|
|
@@ -93,6 +121,12 @@ export function connectRelay(options) {
|
|
|
93
121
|
case "control":
|
|
94
122
|
handleControl(ws, msg);
|
|
95
123
|
break;
|
|
124
|
+
case "agent_call":
|
|
125
|
+
handleIncomingAgentCall(ws, msg, options.localPort);
|
|
126
|
+
break;
|
|
127
|
+
case "agent_call_result":
|
|
128
|
+
handleAgentCallResult(msg);
|
|
129
|
+
break;
|
|
96
130
|
default:
|
|
97
131
|
console.log(`[relay-ws] Unknown message type: ${msg.type}`);
|
|
98
132
|
}
|
|
@@ -116,6 +150,99 @@ export function connectRelay(options) {
|
|
|
116
150
|
}
|
|
117
151
|
connect();
|
|
118
152
|
}
|
|
153
|
+
function handleIncomingAgentCall(ws, msg, localPort) {
|
|
154
|
+
const callId = msg.call_id || "";
|
|
155
|
+
const caller = msg.caller || "unknown";
|
|
156
|
+
const task = msg.task || "";
|
|
157
|
+
console.log(`[agent_call] Incoming from ${caller}: ${task.slice(0, 80)}`);
|
|
158
|
+
// Forward to local MCP as a submit_task call
|
|
159
|
+
const initBody = JSON.stringify({
|
|
160
|
+
jsonrpc: "2.0", id: 1,
|
|
161
|
+
method: "initialize",
|
|
162
|
+
params: { protocolVersion: "2025-03-26", capabilities: {}, clientInfo: { name: "agent-call", version: "1.0" } },
|
|
163
|
+
});
|
|
164
|
+
const doRequest = (body, sessionId) => {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const headers = {
|
|
167
|
+
"Content-Type": "application/json",
|
|
168
|
+
"Accept": "application/json, text/event-stream",
|
|
169
|
+
};
|
|
170
|
+
if (sessionId)
|
|
171
|
+
headers["mcp-session-id"] = sessionId;
|
|
172
|
+
const req = http.request({ hostname: "127.0.0.1", port: localPort, path: "/mcp", method: "POST", headers: { ...headers, "Content-Length": Buffer.byteLength(body) } }, (res) => {
|
|
173
|
+
const chunks = [];
|
|
174
|
+
res.on("data", (c) => chunks.push(c));
|
|
175
|
+
res.on("end", () => {
|
|
176
|
+
const sid = res.headers["mcp-session-id"];
|
|
177
|
+
resolve({ data: Buffer.concat(chunks).toString(), sessionId: sid || sessionId });
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
req.on("error", reject);
|
|
181
|
+
req.write(body);
|
|
182
|
+
req.end();
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
// Initialize → call tool → return result
|
|
186
|
+
doRequest(initBody)
|
|
187
|
+
.then(({ sessionId: sid }) => {
|
|
188
|
+
const callBody = JSON.stringify({
|
|
189
|
+
jsonrpc: "2.0", id: 2,
|
|
190
|
+
method: "tools/call",
|
|
191
|
+
params: { name: "submit_task", arguments: { task } },
|
|
192
|
+
});
|
|
193
|
+
return doRequest(callBody, sid);
|
|
194
|
+
})
|
|
195
|
+
.then(({ data }) => {
|
|
196
|
+
// Extract text from SSE or JSON response
|
|
197
|
+
let result = data;
|
|
198
|
+
try {
|
|
199
|
+
// Try SSE extraction
|
|
200
|
+
const lines = data.split("\n");
|
|
201
|
+
let lastData = "";
|
|
202
|
+
for (const line of lines) {
|
|
203
|
+
if (line.startsWith("data: "))
|
|
204
|
+
lastData = line.slice(6);
|
|
205
|
+
}
|
|
206
|
+
if (lastData) {
|
|
207
|
+
const parsed = JSON.parse(lastData);
|
|
208
|
+
const content = parsed?.result?.content;
|
|
209
|
+
if (content)
|
|
210
|
+
result = content.map((c) => c.text || "").join("\n");
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const parsed = JSON.parse(data);
|
|
214
|
+
const content = parsed?.result?.content;
|
|
215
|
+
if (content)
|
|
216
|
+
result = content.map((c) => c.text || "").join("\n");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch { /* use raw */ }
|
|
220
|
+
ws.send(JSON.stringify({
|
|
221
|
+
type: "agent_call_result",
|
|
222
|
+
call_id: callId,
|
|
223
|
+
caller,
|
|
224
|
+
result,
|
|
225
|
+
}));
|
|
226
|
+
console.log(`[agent_call] Replied to ${caller} (${result.length} bytes)`);
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
ws.send(JSON.stringify({
|
|
230
|
+
type: "agent_call_result",
|
|
231
|
+
call_id: callId,
|
|
232
|
+
caller,
|
|
233
|
+
result: `[error] ${err.message}`,
|
|
234
|
+
}));
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function handleAgentCallResult(msg) {
|
|
238
|
+
const callId = msg.call_id || "";
|
|
239
|
+
const resolve = pendingAgentCalls.get(callId);
|
|
240
|
+
if (resolve) {
|
|
241
|
+
pendingAgentCalls.delete(callId);
|
|
242
|
+
resolve(msg.result || "");
|
|
243
|
+
console.log(`[agent_call] Got result for call_id=${callId.slice(0, 8)} from ${msg.caller}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
119
246
|
function handleControl(ws, msg) {
|
|
120
247
|
const action = msg.action || "";
|
|
121
248
|
console.log(`[control] Received: ${action}`);
|
package/dist/server.js
CHANGED
|
@@ -2,9 +2,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { spawn } from "child_process";
|
|
5
|
+
import { spawn, exec } from "child_process";
|
|
6
6
|
import { createServer } from "http";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
|
+
import { callAgent } from "./relay-client.js";
|
|
8
9
|
function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
9
10
|
return new Promise((resolve, reject) => {
|
|
10
11
|
const { CLAUDECODE, ...cleanEnv } = process.env;
|
|
@@ -45,6 +46,19 @@ function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
|
45
46
|
child.on("error", reject);
|
|
46
47
|
});
|
|
47
48
|
}
|
|
49
|
+
function runTerminal(command, cwd) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
exec(command, { cwd, timeout: 300_000, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
52
|
+
const output = (stdout || "") + (stderr ? "\n[stderr]\n" + stderr : "");
|
|
53
|
+
if (err && !output.trim()) {
|
|
54
|
+
resolve(`[error] ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
resolve(output.trim() || "[no output]");
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
48
62
|
// stdinMode: true = send task via stdin, false = send task as argument
|
|
49
63
|
function buildEngineCommand(engine, model, allowAll) {
|
|
50
64
|
switch (engine) {
|
|
@@ -198,8 +212,15 @@ function createMcpServer(opts) {
|
|
|
198
212
|
console.log(`[approve] Owner approved. Executing with ${engine}...`);
|
|
199
213
|
}
|
|
200
214
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
let output;
|
|
216
|
+
if (engine === "terminal") {
|
|
217
|
+
console.log(`[terminal] Executing: ${task}`);
|
|
218
|
+
output = await runTerminal(task, workdir);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const { cmd, args, stdinMode } = buildEngineCommand(engine, model, allowAll);
|
|
222
|
+
output = await runCommand(cmd, args, safeTask, workdir, stdinMode);
|
|
223
|
+
}
|
|
203
224
|
// Store updated context
|
|
204
225
|
if (contextEnabled && publisherId) {
|
|
205
226
|
const newContext = buildContextPayload(prevContext, task, output);
|
|
@@ -217,6 +238,25 @@ function createMcpServer(opts) {
|
|
|
217
238
|
};
|
|
218
239
|
}
|
|
219
240
|
});
|
|
241
|
+
// Agent-to-agent calling tool
|
|
242
|
+
server.tool("call_agent", "Call another akemon agent by name. The target agent will execute the task and return the result. Use this to delegate subtasks to specialized agents.", {
|
|
243
|
+
agent: z.string().describe("Name of the target agent to call"),
|
|
244
|
+
task: z.string().describe("Task to send to the target agent"),
|
|
245
|
+
}, async ({ agent: target, task }) => {
|
|
246
|
+
console.log(`[call_agent] ${agentName} → ${target}: ${task.slice(0, 80)}`);
|
|
247
|
+
try {
|
|
248
|
+
const result = await callAgent(target, task);
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: result }],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text: `[error] Failed to call agent "${target}": ${err.message}` }],
|
|
256
|
+
isError: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
});
|
|
220
260
|
return server;
|
|
221
261
|
}
|
|
222
262
|
export async function serve(options) {
|