@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.
Files changed (2) hide show
  1. package/dist/relay.mjs +147 -330
  2. 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 createMcpServerOptions() {
8310
- const taskStore = new DeferredCleanupTaskStore();
8311
- return {
8312
- capabilities: { tasks: { requests: { tools: { call: {} } } } },
8313
- taskStore,
8314
- taskMessageQueue: new InMemoryTaskMessageQueue(),
8315
- defaultTaskPollInterval: DEFAULT_POLL_INTERVAL
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.7" },
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
- * Run spawn_agent in the background and store the result in taskStore.
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
- description: "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.",
8593
- inputSchema: spawnAgentInputSchema.shape,
8594
- execution: { taskSupport: "optional" }
8595
- },
8596
- {
8597
- createTask: async (params, extra) => {
8598
- const task = await extra.taskStore.createTask({
8599
- ttl: DEFAULT_TASK_TTL,
8600
- pollInterval: DEFAULT_POLL_INTERVAL
8601
- });
8602
- this.runSpawnAgentInBackground(task.taskId, params, extra, relayContext);
8603
- return { task };
8604
- },
8605
- getTask: async (_params, extra) => {
8606
- return await extra.taskStore.getTask(extra.taskId);
8607
- },
8608
- getTaskResult: async (_params, extra) => {
8609
- return await extra.taskStore.getTaskResult(
8610
- extra.taskId
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.experimental.tasks.registerToolTask(
8383
+ server.tool(
8616
8384
  "spawn_agents_parallel",
8617
- {
8618
- description: "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.",
8619
- inputSchema: spawnAgentsParallelInputShape,
8620
- execution: { taskSupport: "optional" }
8621
- },
8622
- {
8623
- createTask: async (params, extra) => {
8624
- const task = await extra.taskStore.createTask({
8625
- ttl: DEFAULT_TASK_TTL,
8626
- pollInterval: DEFAULT_POLL_INTERVAL
8627
- });
8628
- this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra, relayContext);
8629
- return { task };
8630
- },
8631
- getTask: async (_params, extra) => {
8632
- return await extra.taskStore.getTask(extra.taskId);
8633
- },
8634
- getTaskResult: async (_params, extra) => {
8635
- return await extra.taskStore.getTaskResult(
8636
- extra.taskId
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.7" },
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
- logger.debug(`Child MCP session closed: ${sid2}`);
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 init_types3 = __esm({
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
- init_types3();
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(() => (init_types3(), types_exports));
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: `Claude SDK query timed out after ${timeoutMs}ms`
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.7" });
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.7";
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.7",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI with MCP-based multi-layer sub-agent orchestration",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",