@rk0429/agentic-relay 2.0.7 → 2.0.9
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.mjs +147 -330
- package/package.json +1 -1
package/dist/relay.mjs
CHANGED
|
@@ -204,129 +204,6 @@ var init_version_check = __esm({
|
|
|
204
204
|
}
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
// src/mcp-server/deferred-cleanup-task-store.ts
|
|
208
|
-
import { isTerminal } from "@modelcontextprotocol/sdk/experimental/tasks/interfaces.js";
|
|
209
|
-
import { randomBytes } from "crypto";
|
|
210
|
-
var DeferredCleanupTaskStore;
|
|
211
|
-
var init_deferred_cleanup_task_store = __esm({
|
|
212
|
-
"src/mcp-server/deferred-cleanup-task-store.ts"() {
|
|
213
|
-
"use strict";
|
|
214
|
-
DeferredCleanupTaskStore = class {
|
|
215
|
-
tasks = /* @__PURE__ */ new Map();
|
|
216
|
-
cleanupTimers = /* @__PURE__ */ new Map();
|
|
217
|
-
generateTaskId() {
|
|
218
|
-
return randomBytes(16).toString("hex");
|
|
219
|
-
}
|
|
220
|
-
async createTask(taskParams, requestId, request, _sessionId) {
|
|
221
|
-
const taskId = this.generateTaskId();
|
|
222
|
-
if (this.tasks.has(taskId)) {
|
|
223
|
-
throw new Error(`Task with ID ${taskId} already exists`);
|
|
224
|
-
}
|
|
225
|
-
const actualTtl = taskParams.ttl ?? null;
|
|
226
|
-
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
227
|
-
const task = {
|
|
228
|
-
taskId,
|
|
229
|
-
status: "working",
|
|
230
|
-
ttl: actualTtl,
|
|
231
|
-
createdAt,
|
|
232
|
-
lastUpdatedAt: createdAt,
|
|
233
|
-
pollInterval: taskParams.pollInterval ?? 1e3
|
|
234
|
-
};
|
|
235
|
-
this.tasks.set(taskId, { task, request, requestId });
|
|
236
|
-
return { ...task };
|
|
237
|
-
}
|
|
238
|
-
async getTask(taskId, _sessionId) {
|
|
239
|
-
const stored = this.tasks.get(taskId);
|
|
240
|
-
return stored ? { ...stored.task } : null;
|
|
241
|
-
}
|
|
242
|
-
async storeTaskResult(taskId, status, result, _sessionId) {
|
|
243
|
-
const stored = this.tasks.get(taskId);
|
|
244
|
-
if (!stored) {
|
|
245
|
-
throw new Error(`Task with ID ${taskId} not found`);
|
|
246
|
-
}
|
|
247
|
-
if (isTerminal(stored.task.status)) {
|
|
248
|
-
throw new Error(
|
|
249
|
-
`Cannot store result for task ${taskId} in terminal status '${stored.task.status}'. Task results can only be stored once.`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
stored.result = result;
|
|
253
|
-
stored.task.status = status;
|
|
254
|
-
stored.task.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
255
|
-
this.scheduleCleanup(taskId, stored.task.ttl);
|
|
256
|
-
}
|
|
257
|
-
async getTaskResult(taskId, _sessionId) {
|
|
258
|
-
const stored = this.tasks.get(taskId);
|
|
259
|
-
if (!stored) {
|
|
260
|
-
throw new Error(`Task with ID ${taskId} not found`);
|
|
261
|
-
}
|
|
262
|
-
if (!stored.result) {
|
|
263
|
-
throw new Error(`Task ${taskId} has no result stored`);
|
|
264
|
-
}
|
|
265
|
-
return stored.result;
|
|
266
|
-
}
|
|
267
|
-
async updateTaskStatus(taskId, status, statusMessage, _sessionId) {
|
|
268
|
-
const stored = this.tasks.get(taskId);
|
|
269
|
-
if (!stored) {
|
|
270
|
-
throw new Error(`Task with ID ${taskId} not found`);
|
|
271
|
-
}
|
|
272
|
-
if (isTerminal(stored.task.status)) {
|
|
273
|
-
throw new Error(
|
|
274
|
-
`Cannot update task ${taskId} from terminal status '${stored.task.status}'.`
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
stored.task.status = status;
|
|
278
|
-
if (statusMessage) {
|
|
279
|
-
stored.task.statusMessage = statusMessage;
|
|
280
|
-
}
|
|
281
|
-
stored.task.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
282
|
-
if (isTerminal(status)) {
|
|
283
|
-
this.scheduleCleanup(taskId, stored.task.ttl);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
async listTasks(cursor, _sessionId) {
|
|
287
|
-
const PAGE_SIZE = 10;
|
|
288
|
-
const allTaskIds = Array.from(this.tasks.keys());
|
|
289
|
-
let startIndex = 0;
|
|
290
|
-
if (cursor) {
|
|
291
|
-
const cursorIndex = allTaskIds.indexOf(cursor);
|
|
292
|
-
if (cursorIndex >= 0) {
|
|
293
|
-
startIndex = cursorIndex + 1;
|
|
294
|
-
} else {
|
|
295
|
-
throw new Error(`Invalid cursor: ${cursor}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
const pageTaskIds = allTaskIds.slice(startIndex, startIndex + PAGE_SIZE);
|
|
299
|
-
const tasks = pageTaskIds.map((id) => {
|
|
300
|
-
const stored = this.tasks.get(id);
|
|
301
|
-
return { ...stored.task };
|
|
302
|
-
});
|
|
303
|
-
const nextCursor = startIndex + PAGE_SIZE < allTaskIds.length ? pageTaskIds[pageTaskIds.length - 1] : void 0;
|
|
304
|
-
return { tasks, nextCursor };
|
|
305
|
-
}
|
|
306
|
-
/** Cleanup all timers (for testing or graceful shutdown) */
|
|
307
|
-
cleanup() {
|
|
308
|
-
for (const timer of this.cleanupTimers.values()) {
|
|
309
|
-
clearTimeout(timer);
|
|
310
|
-
}
|
|
311
|
-
this.cleanupTimers.clear();
|
|
312
|
-
this.tasks.clear();
|
|
313
|
-
}
|
|
314
|
-
scheduleCleanup(taskId, ttl) {
|
|
315
|
-
if (!ttl) return;
|
|
316
|
-
const existingTimer = this.cleanupTimers.get(taskId);
|
|
317
|
-
if (existingTimer) {
|
|
318
|
-
clearTimeout(existingTimer);
|
|
319
|
-
}
|
|
320
|
-
const timer = setTimeout(() => {
|
|
321
|
-
this.tasks.delete(taskId);
|
|
322
|
-
this.cleanupTimers.delete(taskId);
|
|
323
|
-
}, ttl);
|
|
324
|
-
this.cleanupTimers.set(taskId, timer);
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
207
|
// src/core/agent-event-store.ts
|
|
331
208
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
332
209
|
import { join as join6 } from "path";
|
|
@@ -1085,7 +962,16 @@ function buildContextFromEnv() {
|
|
|
1085
962
|
const depth = Number(process.env["RELAY_DEPTH"] ?? "0");
|
|
1086
963
|
return { traceId, parentSessionId, depth };
|
|
1087
964
|
}
|
|
1088
|
-
async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext) {
|
|
965
|
+
async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext, signal) {
|
|
966
|
+
if (signal?.aborted) {
|
|
967
|
+
return {
|
|
968
|
+
sessionId: "",
|
|
969
|
+
exitCode: 1,
|
|
970
|
+
stdout: "",
|
|
971
|
+
stderr: "Spawn cancelled: parent session disconnected",
|
|
972
|
+
failureReason: "unknown"
|
|
973
|
+
};
|
|
974
|
+
}
|
|
1089
975
|
onProgress?.({ stage: "initializing", percent: 0 });
|
|
1090
976
|
let inlineTaskId;
|
|
1091
977
|
if (input.title && taskLifecycleManager) {
|
|
@@ -1405,7 +1291,8 @@ ${input.prompt}`;
|
|
|
1405
1291
|
maxDepth: guard.getConfig().maxDepth,
|
|
1406
1292
|
traceId: envContext.traceId
|
|
1407
1293
|
},
|
|
1408
|
-
...mcpServers ? { mcpServers } : {}
|
|
1294
|
+
...mcpServers ? { mcpServers } : {},
|
|
1295
|
+
...signal ? { signal } : {}
|
|
1409
1296
|
});
|
|
1410
1297
|
}
|
|
1411
1298
|
})();
|
|
@@ -1853,7 +1740,7 @@ var init_conflict_detector = __esm({
|
|
|
1853
1740
|
});
|
|
1854
1741
|
|
|
1855
1742
|
// src/mcp-server/tools/spawn-agents-parallel.ts
|
|
1856
|
-
async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext) {
|
|
1743
|
+
async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext, signal) {
|
|
1857
1744
|
for (let index = 0; index < agents.length; index += 1) {
|
|
1858
1745
|
try {
|
|
1859
1746
|
resolveValidatedSessionMetadata(agents[index]);
|
|
@@ -1939,7 +1826,8 @@ async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, gu
|
|
|
1939
1826
|
hookMemoryDir,
|
|
1940
1827
|
taskCompleter,
|
|
1941
1828
|
taskLifecycleManager,
|
|
1942
|
-
relayContext
|
|
1829
|
+
relayContext,
|
|
1830
|
+
signal
|
|
1943
1831
|
).then((result) => {
|
|
1944
1832
|
completedCount++;
|
|
1945
1833
|
onProgress?.({
|
|
@@ -8098,24 +7986,6 @@ var init_backend_selector = __esm({
|
|
|
8098
7986
|
}
|
|
8099
7987
|
});
|
|
8100
7988
|
|
|
8101
|
-
// src/types/mcp.ts
|
|
8102
|
-
var DEFAULT_TASK_TTL, DEFAULT_POLL_INTERVAL;
|
|
8103
|
-
var init_mcp = __esm({
|
|
8104
|
-
"src/types/mcp.ts"() {
|
|
8105
|
-
"use strict";
|
|
8106
|
-
DEFAULT_TASK_TTL = 5 * 60 * 1e3;
|
|
8107
|
-
DEFAULT_POLL_INTERVAL = 3e3;
|
|
8108
|
-
}
|
|
8109
|
-
});
|
|
8110
|
-
|
|
8111
|
-
// src/types/index.ts
|
|
8112
|
-
var init_types2 = __esm({
|
|
8113
|
-
"src/types/index.ts"() {
|
|
8114
|
-
"use strict";
|
|
8115
|
-
init_mcp();
|
|
8116
|
-
}
|
|
8117
|
-
});
|
|
8118
|
-
|
|
8119
7989
|
// src/mcp-server/response-formatter.ts
|
|
8120
7990
|
import * as fs from "fs";
|
|
8121
7991
|
import * as path from "path";
|
|
@@ -8279,7 +8149,6 @@ __export(server_exports, {
|
|
|
8279
8149
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8280
8150
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8281
8151
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
8282
|
-
import { InMemoryTaskMessageQueue } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
|
|
8283
8152
|
import { createServer } from "http";
|
|
8284
8153
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
8285
8154
|
import path2 from "path";
|
|
@@ -8306,20 +8175,20 @@ function extractRelayContextFromUrl(url) {
|
|
|
8306
8175
|
}
|
|
8307
8176
|
return void 0;
|
|
8308
8177
|
}
|
|
8309
|
-
function
|
|
8310
|
-
const
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
}
|
|
8178
|
+
function combineAbortSignals(...signals) {
|
|
8179
|
+
const activeSignals = signals.filter((signal) => signal !== void 0);
|
|
8180
|
+
if (activeSignals.length === 0) {
|
|
8181
|
+
return void 0;
|
|
8182
|
+
}
|
|
8183
|
+
if (activeSignals.length === 1) {
|
|
8184
|
+
return activeSignals[0];
|
|
8185
|
+
}
|
|
8186
|
+
return AbortSignal.any(activeSignals);
|
|
8317
8187
|
}
|
|
8318
8188
|
var spawnAgentsParallelInputShape, MAX_CHILD_HTTP_SESSIONS, DEFAULT_TASKS_DB_PATH, RelayMCPServer;
|
|
8319
8189
|
var init_server = __esm({
|
|
8320
8190
|
"src/mcp-server/server.ts"() {
|
|
8321
8191
|
"use strict";
|
|
8322
|
-
init_deferred_cleanup_task_store();
|
|
8323
8192
|
init_agent_event_store();
|
|
8324
8193
|
init_session_health_monitor();
|
|
8325
8194
|
init_recursion_guard();
|
|
@@ -8335,7 +8204,6 @@ var init_server = __esm({
|
|
|
8335
8204
|
init_metadata_validation();
|
|
8336
8205
|
init_logger();
|
|
8337
8206
|
init_version_check();
|
|
8338
|
-
init_types2();
|
|
8339
8207
|
init_response_formatter();
|
|
8340
8208
|
init_tasks();
|
|
8341
8209
|
spawnAgentsParallelInputShape = {
|
|
@@ -8442,8 +8310,7 @@ var init_server = __esm({
|
|
|
8442
8310
|
this.agentEventStore
|
|
8443
8311
|
);
|
|
8444
8312
|
this.server = new McpServer(
|
|
8445
|
-
{ name: "agentic-relay", version: "2.0.
|
|
8446
|
-
createMcpServerOptions()
|
|
8313
|
+
{ name: "agentic-relay", version: "2.0.9" }
|
|
8447
8314
|
);
|
|
8448
8315
|
this.registerTools(this.server);
|
|
8449
8316
|
this.sessionHealthMonitor.start();
|
|
@@ -8470,171 +8337,87 @@ var init_server = __esm({
|
|
|
8470
8337
|
get childHttpServer() {
|
|
8471
8338
|
return this._childHttpServer;
|
|
8472
8339
|
}
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
* Fire-and-forget: errors are caught and stored as failed task results.
|
|
8476
|
-
*/
|
|
8477
|
-
runSpawnAgentInBackground(taskId, params, extra, relayContext) {
|
|
8478
|
-
const run = async () => {
|
|
8479
|
-
try {
|
|
8480
|
-
const result = await executeSpawnAgent(
|
|
8481
|
-
params,
|
|
8482
|
-
this.registry,
|
|
8483
|
-
this.sessionManager,
|
|
8484
|
-
this.guard,
|
|
8485
|
-
this.hooksEngine,
|
|
8486
|
-
this.contextMonitor,
|
|
8487
|
-
this.backendSelector,
|
|
8488
|
-
this._childHttpUrl,
|
|
8489
|
-
void 0,
|
|
8490
|
-
this.agentEventStore,
|
|
8491
|
-
this.hookMemoryDir,
|
|
8492
|
-
void 0,
|
|
8493
|
-
this.taskLifecycleManager,
|
|
8494
|
-
relayContext
|
|
8495
|
-
);
|
|
8496
|
-
const controlOptions = {
|
|
8497
|
-
inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
|
|
8498
|
-
sessionId: result.sessionId,
|
|
8499
|
-
responseOutputDir: this.responseOutputDir
|
|
8500
|
-
};
|
|
8501
|
-
const { text, isError } = await formatSpawnAgentResponse(result, controlOptions);
|
|
8502
|
-
const callToolResult = {
|
|
8503
|
-
content: [{ type: "text", text }],
|
|
8504
|
-
isError
|
|
8505
|
-
};
|
|
8506
|
-
await extra.taskStore.storeTaskResult(
|
|
8507
|
-
taskId,
|
|
8508
|
-
"completed",
|
|
8509
|
-
callToolResult
|
|
8510
|
-
);
|
|
8511
|
-
} catch (outerError) {
|
|
8512
|
-
try {
|
|
8513
|
-
const message = outerError instanceof Error ? outerError.message : String(outerError);
|
|
8514
|
-
await extra.taskStore.storeTaskResult(
|
|
8515
|
-
taskId,
|
|
8516
|
-
"failed",
|
|
8517
|
-
{
|
|
8518
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
8519
|
-
isError: true
|
|
8520
|
-
}
|
|
8521
|
-
);
|
|
8522
|
-
} catch (storeError) {
|
|
8523
|
-
logger.error(
|
|
8524
|
-
`Failed to store task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
|
|
8525
|
-
);
|
|
8526
|
-
}
|
|
8527
|
-
}
|
|
8528
|
-
};
|
|
8529
|
-
void run();
|
|
8530
|
-
}
|
|
8531
|
-
/**
|
|
8532
|
-
* Run spawn_agents_parallel in the background and store the result in taskStore.
|
|
8533
|
-
* Fire-and-forget: errors are caught and stored as failed task results.
|
|
8534
|
-
*/
|
|
8535
|
-
runSpawnAgentsParallelInBackground(taskId, agents, extra, relayContext) {
|
|
8536
|
-
const run = async () => {
|
|
8537
|
-
try {
|
|
8538
|
-
const result = await executeSpawnAgentsParallel(
|
|
8539
|
-
agents,
|
|
8540
|
-
this.registry,
|
|
8541
|
-
this.sessionManager,
|
|
8542
|
-
this.guard,
|
|
8543
|
-
this.hooksEngine,
|
|
8544
|
-
this.contextMonitor,
|
|
8545
|
-
this.backendSelector,
|
|
8546
|
-
this._childHttpUrl,
|
|
8547
|
-
void 0,
|
|
8548
|
-
this.agentEventStore,
|
|
8549
|
-
this.hookMemoryDir,
|
|
8550
|
-
void 0,
|
|
8551
|
-
this.taskLifecycleManager,
|
|
8552
|
-
relayContext
|
|
8553
|
-
);
|
|
8554
|
-
const controlOptions = {
|
|
8555
|
-
inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
|
|
8556
|
-
responseOutputDir: this.responseOutputDir
|
|
8557
|
-
};
|
|
8558
|
-
const { text, isError } = await formatParallelResponse(result, controlOptions);
|
|
8559
|
-
const callToolResult = {
|
|
8560
|
-
content: [{ type: "text", text }],
|
|
8561
|
-
isError
|
|
8562
|
-
};
|
|
8563
|
-
await extra.taskStore.storeTaskResult(
|
|
8564
|
-
taskId,
|
|
8565
|
-
"completed",
|
|
8566
|
-
callToolResult
|
|
8567
|
-
);
|
|
8568
|
-
} catch (outerError) {
|
|
8569
|
-
try {
|
|
8570
|
-
const message = outerError instanceof Error ? outerError.message : String(outerError);
|
|
8571
|
-
await extra.taskStore.storeTaskResult(
|
|
8572
|
-
taskId,
|
|
8573
|
-
"failed",
|
|
8574
|
-
{
|
|
8575
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
8576
|
-
isError: true
|
|
8577
|
-
}
|
|
8578
|
-
);
|
|
8579
|
-
} catch (storeError) {
|
|
8580
|
-
logger.error(
|
|
8581
|
-
`Failed to store parallel task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
|
|
8582
|
-
);
|
|
8583
|
-
}
|
|
8584
|
-
}
|
|
8585
|
-
};
|
|
8586
|
-
void run();
|
|
8587
|
-
}
|
|
8588
|
-
registerTools(server, relayContext) {
|
|
8589
|
-
server.experimental.tasks.registerToolTask(
|
|
8340
|
+
registerTools(server, relayContext, sessionSignal) {
|
|
8341
|
+
server.tool(
|
|
8590
8342
|
"spawn_agent",
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
extra.
|
|
8343
|
+
"Spawn a sub-agent on a backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Backend is auto-selected by priority: agentType mapping (coder/researcher\u2192codex, documenter\u2192claude) > taskType mapping > default (claude). metadata with metadata.taskId is required for task-first delegation. Use 'agentDefinition' to inject an agent .md file, 'skillContext' for a SKILL.md, or 'systemPrompt' for custom instructions.",
|
|
8344
|
+
spawnAgentInputSchema.shape,
|
|
8345
|
+
async (params, extra) => {
|
|
8346
|
+
try {
|
|
8347
|
+
const result = await executeSpawnAgent(
|
|
8348
|
+
params,
|
|
8349
|
+
this.registry,
|
|
8350
|
+
this.sessionManager,
|
|
8351
|
+
this.guard,
|
|
8352
|
+
this.hooksEngine,
|
|
8353
|
+
this.contextMonitor,
|
|
8354
|
+
this.backendSelector,
|
|
8355
|
+
this._childHttpUrl,
|
|
8356
|
+
void 0,
|
|
8357
|
+
this.agentEventStore,
|
|
8358
|
+
this.hookMemoryDir,
|
|
8359
|
+
void 0,
|
|
8360
|
+
this.taskLifecycleManager,
|
|
8361
|
+
relayContext,
|
|
8362
|
+
combineAbortSignals(extra.signal, sessionSignal)
|
|
8611
8363
|
);
|
|
8364
|
+
const controlOptions = {
|
|
8365
|
+
inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
|
|
8366
|
+
sessionId: result.sessionId,
|
|
8367
|
+
responseOutputDir: this.responseOutputDir
|
|
8368
|
+
};
|
|
8369
|
+
const { text, isError } = await formatSpawnAgentResponse(result, controlOptions);
|
|
8370
|
+
return {
|
|
8371
|
+
content: [{ type: "text", text }],
|
|
8372
|
+
isError
|
|
8373
|
+
};
|
|
8374
|
+
} catch (error) {
|
|
8375
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8376
|
+
return {
|
|
8377
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
8378
|
+
isError: true
|
|
8379
|
+
};
|
|
8612
8380
|
}
|
|
8613
8381
|
}
|
|
8614
8382
|
);
|
|
8615
|
-
server.
|
|
8383
|
+
server.tool(
|
|
8616
8384
|
"spawn_agents_parallel",
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
extra.
|
|
8385
|
+
"Spawn multiple sub-agents in parallel across available backends. Use this when you have 2+ independent tasks that can execute concurrently. Each agent entry accepts the same parameters as spawn_agent. Results are returned as an array with per-agent status (success/failure). RecursionGuard batch pre-validation ensures the batch fits within call limits. Failed results include 'originalInput' for retry via retry_failed_agents.",
|
|
8386
|
+
spawnAgentsParallelInputShape,
|
|
8387
|
+
async (params, extra) => {
|
|
8388
|
+
try {
|
|
8389
|
+
const result = await executeSpawnAgentsParallel(
|
|
8390
|
+
params.agents,
|
|
8391
|
+
this.registry,
|
|
8392
|
+
this.sessionManager,
|
|
8393
|
+
this.guard,
|
|
8394
|
+
this.hooksEngine,
|
|
8395
|
+
this.contextMonitor,
|
|
8396
|
+
this.backendSelector,
|
|
8397
|
+
this._childHttpUrl,
|
|
8398
|
+
void 0,
|
|
8399
|
+
this.agentEventStore,
|
|
8400
|
+
this.hookMemoryDir,
|
|
8401
|
+
void 0,
|
|
8402
|
+
this.taskLifecycleManager,
|
|
8403
|
+
relayContext,
|
|
8404
|
+
combineAbortSignals(extra.signal, sessionSignal)
|
|
8637
8405
|
);
|
|
8406
|
+
const controlOptions = {
|
|
8407
|
+
inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
|
|
8408
|
+
responseOutputDir: this.responseOutputDir
|
|
8409
|
+
};
|
|
8410
|
+
const { text, isError } = await formatParallelResponse(result, controlOptions);
|
|
8411
|
+
return {
|
|
8412
|
+
content: [{ type: "text", text }],
|
|
8413
|
+
isError
|
|
8414
|
+
};
|
|
8415
|
+
} catch (error) {
|
|
8416
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8417
|
+
return {
|
|
8418
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
8419
|
+
isError: true
|
|
8420
|
+
};
|
|
8638
8421
|
}
|
|
8639
8422
|
}
|
|
8640
8423
|
);
|
|
@@ -8647,7 +8430,7 @@ var init_server = __esm({
|
|
|
8647
8430
|
originalInput: spawnAgentInputSchema
|
|
8648
8431
|
})).min(1).describe("Array of failed results with their original input configurations")
|
|
8649
8432
|
},
|
|
8650
|
-
async (params) => {
|
|
8433
|
+
async (params, extra) => {
|
|
8651
8434
|
try {
|
|
8652
8435
|
const agents = params.failedResults.map((r) => ({ ...r.originalInput }));
|
|
8653
8436
|
const result = await executeSpawnAgentsParallel(
|
|
@@ -8664,7 +8447,8 @@ var init_server = __esm({
|
|
|
8664
8447
|
this.hookMemoryDir,
|
|
8665
8448
|
void 0,
|
|
8666
8449
|
this.taskLifecycleManager,
|
|
8667
|
-
relayContext
|
|
8450
|
+
relayContext,
|
|
8451
|
+
combineAbortSignals(extra.signal, sessionSignal)
|
|
8668
8452
|
);
|
|
8669
8453
|
const controlOptions = {
|
|
8670
8454
|
inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
|
|
@@ -8936,6 +8720,7 @@ var init_server = __esm({
|
|
|
8936
8720
|
*/
|
|
8937
8721
|
async startChildHttpServer() {
|
|
8938
8722
|
const sessions = /* @__PURE__ */ new Map();
|
|
8723
|
+
const sessionAbortControllers = /* @__PURE__ */ new Map();
|
|
8939
8724
|
const httpServer = createServer(async (req, res) => {
|
|
8940
8725
|
const url = req.url ?? "";
|
|
8941
8726
|
if (url !== "/mcp" && !url.startsWith("/mcp?")) {
|
|
@@ -8952,19 +8737,26 @@ var init_server = __esm({
|
|
|
8952
8737
|
}
|
|
8953
8738
|
}
|
|
8954
8739
|
const childRelayContext = extractRelayContextFromUrl(url);
|
|
8740
|
+
const abortController = new AbortController();
|
|
8955
8741
|
const transport = new StreamableHTTPServerTransport({
|
|
8956
8742
|
sessionIdGenerator: () => randomUUID()
|
|
8957
8743
|
});
|
|
8958
8744
|
const server = new McpServer(
|
|
8959
|
-
{ name: "agentic-relay", version: "2.0.
|
|
8960
|
-
createMcpServerOptions()
|
|
8745
|
+
{ name: "agentic-relay", version: "2.0.9" }
|
|
8961
8746
|
);
|
|
8962
|
-
this.registerTools(server, childRelayContext);
|
|
8747
|
+
this.registerTools(server, childRelayContext, abortController.signal);
|
|
8963
8748
|
transport.onclose = () => {
|
|
8964
8749
|
const sid2 = transport.sessionId;
|
|
8965
8750
|
if (sid2) {
|
|
8966
8751
|
sessions.delete(sid2);
|
|
8967
|
-
|
|
8752
|
+
const ctrl = sessionAbortControllers.get(sid2);
|
|
8753
|
+
if (ctrl) {
|
|
8754
|
+
ctrl.abort();
|
|
8755
|
+
sessionAbortControllers.delete(sid2);
|
|
8756
|
+
logger.debug(`Child MCP session closed, in-flight requests aborted: ${sid2}`);
|
|
8757
|
+
} else {
|
|
8758
|
+
logger.debug(`Child MCP session closed: ${sid2}`);
|
|
8759
|
+
}
|
|
8968
8760
|
}
|
|
8969
8761
|
};
|
|
8970
8762
|
await server.connect(transport);
|
|
@@ -8972,12 +8764,18 @@ var init_server = __esm({
|
|
|
8972
8764
|
const sid = transport.sessionId;
|
|
8973
8765
|
if (sid) {
|
|
8974
8766
|
sessions.set(sid, { transport, server });
|
|
8767
|
+
sessionAbortControllers.set(sid, abortController);
|
|
8975
8768
|
logger.debug(`Child MCP session created: ${sid}`);
|
|
8976
8769
|
if (sessions.size > MAX_CHILD_HTTP_SESSIONS) {
|
|
8977
8770
|
const oldestEntry = sessions.entries().next().value;
|
|
8978
8771
|
if (oldestEntry) {
|
|
8979
8772
|
const [oldestSessionId, oldestSession] = oldestEntry;
|
|
8980
8773
|
sessions.delete(oldestSessionId);
|
|
8774
|
+
const evictedCtrl = sessionAbortControllers.get(oldestSessionId);
|
|
8775
|
+
if (evictedCtrl) {
|
|
8776
|
+
evictedCtrl.abort();
|
|
8777
|
+
sessionAbortControllers.delete(oldestSessionId);
|
|
8778
|
+
}
|
|
8981
8779
|
logger.warn(
|
|
8982
8780
|
`Child MCP session evicted due to limit (${MAX_CHILD_HTTP_SESSIONS}): ${oldestSessionId}`
|
|
8983
8781
|
);
|
|
@@ -9719,7 +9517,7 @@ __export(types_exports, {
|
|
|
9719
9517
|
DEFAULT_TUI_CONFIG: () => DEFAULT_TUI_CONFIG
|
|
9720
9518
|
});
|
|
9721
9519
|
var DEFAULT_TUI_CONFIG;
|
|
9722
|
-
var
|
|
9520
|
+
var init_types2 = __esm({
|
|
9723
9521
|
"src/tui/types.ts"() {
|
|
9724
9522
|
"use strict";
|
|
9725
9523
|
DEFAULT_TUI_CONFIG = {
|
|
@@ -9743,7 +9541,7 @@ var TUIConfigManager;
|
|
|
9743
9541
|
var init_tui_config_manager = __esm({
|
|
9744
9542
|
"src/tui/services/tui-config-manager.ts"() {
|
|
9745
9543
|
"use strict";
|
|
9746
|
-
|
|
9544
|
+
init_types2();
|
|
9747
9545
|
TUIConfigManager = class {
|
|
9748
9546
|
constructor(configManager2) {
|
|
9749
9547
|
this.configManager = configManager2;
|
|
@@ -10070,7 +9868,7 @@ function App({ registry: registry2, sessionManager: sessionManager2, hooksEngine
|
|
|
10070
9868
|
setTuiConfig(updated);
|
|
10071
9869
|
setStatusMessage(`Config updated: ${key} = ${JSON.stringify(value)}`);
|
|
10072
9870
|
} else if (subCmd === "reset") {
|
|
10073
|
-
const { DEFAULT_TUI_CONFIG: DEFAULT_TUI_CONFIG2 } = await Promise.resolve().then(() => (
|
|
9871
|
+
const { DEFAULT_TUI_CONFIG: DEFAULT_TUI_CONFIG2 } = await Promise.resolve().then(() => (init_types2(), types_exports));
|
|
10074
9872
|
for (const [section, sectionValue] of Object.entries(DEFAULT_TUI_CONFIG2)) {
|
|
10075
9873
|
for (const [k, v] of Object.entries(sectionValue)) {
|
|
10076
9874
|
await tuiConfigManager.set(`${section}.${k}`, v);
|
|
@@ -10635,6 +10433,14 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
10635
10433
|
const timeoutMs = resolveClaudeSdkTimeoutMs();
|
|
10636
10434
|
const abortController = new AbortController();
|
|
10637
10435
|
const timer = timeoutMs !== void 0 ? setTimeout(() => abortController.abort(), timeoutMs) : void 0;
|
|
10436
|
+
const onExternalAbort = () => abortController.abort();
|
|
10437
|
+
if (flags.signal) {
|
|
10438
|
+
if (flags.signal.aborted) {
|
|
10439
|
+
abortController.abort();
|
|
10440
|
+
} else {
|
|
10441
|
+
flags.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
10442
|
+
}
|
|
10443
|
+
}
|
|
10638
10444
|
try {
|
|
10639
10445
|
const { query } = await loadClaudeSDK();
|
|
10640
10446
|
const options = {
|
|
@@ -10705,10 +10511,11 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
10705
10511
|
};
|
|
10706
10512
|
} catch (error) {
|
|
10707
10513
|
if (abortController.signal.aborted) {
|
|
10514
|
+
const reason = flags.signal?.aborted ? "Claude execution cancelled: parent session disconnected" : `Claude SDK query timed out after ${timeoutMs}ms`;
|
|
10708
10515
|
return {
|
|
10709
10516
|
exitCode: 1,
|
|
10710
10517
|
stdout: "",
|
|
10711
|
-
stderr:
|
|
10518
|
+
stderr: reason
|
|
10712
10519
|
};
|
|
10713
10520
|
}
|
|
10714
10521
|
return {
|
|
@@ -10718,6 +10525,7 @@ var ClaudeAdapter = class extends BaseAdapter {
|
|
|
10718
10525
|
};
|
|
10719
10526
|
} finally {
|
|
10720
10527
|
if (timer !== void 0) clearTimeout(timer);
|
|
10528
|
+
flags.signal?.removeEventListener("abort", onExternalAbort);
|
|
10721
10529
|
}
|
|
10722
10530
|
}
|
|
10723
10531
|
async *executeStreaming(flags) {
|
|
@@ -11307,7 +11115,9 @@ ${prompt}`;
|
|
|
11307
11115
|
workingDirectory: process.cwd(),
|
|
11308
11116
|
approvalPolicy: "never"
|
|
11309
11117
|
});
|
|
11310
|
-
const result = await thread.run(effectivePrompt
|
|
11118
|
+
const result = await thread.run(effectivePrompt, {
|
|
11119
|
+
...flags.signal ? { signal: flags.signal } : {}
|
|
11120
|
+
});
|
|
11311
11121
|
return {
|
|
11312
11122
|
exitCode: 0,
|
|
11313
11123
|
stdout: result.finalResponse,
|
|
@@ -11320,6 +11130,13 @@ ${prompt}`;
|
|
|
11320
11130
|
} : {}
|
|
11321
11131
|
};
|
|
11322
11132
|
} catch (error) {
|
|
11133
|
+
if (flags.signal?.aborted) {
|
|
11134
|
+
return {
|
|
11135
|
+
exitCode: 1,
|
|
11136
|
+
stdout: "",
|
|
11137
|
+
stderr: "Codex execution cancelled: parent session disconnected"
|
|
11138
|
+
};
|
|
11139
|
+
}
|
|
11323
11140
|
return {
|
|
11324
11141
|
exitCode: 1,
|
|
11325
11142
|
stdout: "",
|
|
@@ -13569,7 +13386,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
|
|
|
13569
13386
|
responseOutputDir,
|
|
13570
13387
|
relayConfig
|
|
13571
13388
|
);
|
|
13572
|
-
await server.start({ transport, port, currentVersion: "2.0.
|
|
13389
|
+
await server.start({ transport, port, currentVersion: "2.0.9" });
|
|
13573
13390
|
}
|
|
13574
13391
|
})
|
|
13575
13392
|
},
|
|
@@ -13729,7 +13546,7 @@ function createVersionCommand(registry2) {
|
|
|
13729
13546
|
description: "Show relay and backend versions"
|
|
13730
13547
|
},
|
|
13731
13548
|
async run() {
|
|
13732
|
-
const relayVersion = "2.0.
|
|
13549
|
+
const relayVersion = "2.0.9";
|
|
13733
13550
|
console.log(`agentic-relay v${relayVersion}`);
|
|
13734
13551
|
console.log("");
|
|
13735
13552
|
console.log("Backends:");
|
|
@@ -14126,7 +13943,7 @@ var subCommandNames = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "upd
|
|
|
14126
13943
|
var main = defineCommand11({
|
|
14127
13944
|
meta: {
|
|
14128
13945
|
name: "relay",
|
|
14129
|
-
version: "2.0.
|
|
13946
|
+
version: "2.0.9",
|
|
14130
13947
|
description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
|
|
14131
13948
|
},
|
|
14132
13949
|
args: {
|
package/package.json
CHANGED