@rk0429/agentic-relay 2.0.8 → 2.0.10

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 +288 -377
  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";
@@ -1390,7 +1267,10 @@ ${input.prompt}`;
1390
1267
  } else {
1391
1268
  effectiveNativeSessionId = input.resumeSessionId;
1392
1269
  }
1393
- return adapter.continueSession(effectiveNativeSessionId, effectivePrompt);
1270
+ return adapter.continueSession(effectiveNativeSessionId, effectivePrompt, {
1271
+ ...input.model ? { model: input.model } : {},
1272
+ ...signal ? { signal } : {}
1273
+ });
1394
1274
  } else {
1395
1275
  let mcpServers;
1396
1276
  if (childHttpUrl) {
@@ -8109,24 +7989,6 @@ var init_backend_selector = __esm({
8109
7989
  }
8110
7990
  });
8111
7991
 
8112
- // src/types/mcp.ts
8113
- var DEFAULT_TASK_TTL, DEFAULT_POLL_INTERVAL;
8114
- var init_mcp = __esm({
8115
- "src/types/mcp.ts"() {
8116
- "use strict";
8117
- DEFAULT_TASK_TTL = 5 * 60 * 1e3;
8118
- DEFAULT_POLL_INTERVAL = 3e3;
8119
- }
8120
- });
8121
-
8122
- // src/types/index.ts
8123
- var init_types2 = __esm({
8124
- "src/types/index.ts"() {
8125
- "use strict";
8126
- init_mcp();
8127
- }
8128
- });
8129
-
8130
7992
  // src/mcp-server/response-formatter.ts
8131
7993
  import * as fs from "fs";
8132
7994
  import * as path from "path";
@@ -8290,7 +8152,6 @@ __export(server_exports, {
8290
8152
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8291
8153
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8292
8154
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8293
- import { InMemoryTaskMessageQueue } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
8294
8155
  import { createServer } from "http";
8295
8156
  import { mkdirSync as mkdirSync3 } from "fs";
8296
8157
  import path2 from "path";
@@ -8317,20 +8178,20 @@ function extractRelayContextFromUrl(url) {
8317
8178
  }
8318
8179
  return void 0;
8319
8180
  }
8320
- function createMcpServerOptions() {
8321
- const taskStore = new DeferredCleanupTaskStore();
8322
- return {
8323
- capabilities: { tasks: { requests: { tools: { call: {} } } } },
8324
- taskStore,
8325
- taskMessageQueue: new InMemoryTaskMessageQueue(),
8326
- defaultTaskPollInterval: DEFAULT_POLL_INTERVAL
8327
- };
8181
+ function combineAbortSignals(...signals) {
8182
+ const activeSignals = signals.filter((signal) => signal !== void 0);
8183
+ if (activeSignals.length === 0) {
8184
+ return void 0;
8185
+ }
8186
+ if (activeSignals.length === 1) {
8187
+ return activeSignals[0];
8188
+ }
8189
+ return AbortSignal.any(activeSignals);
8328
8190
  }
8329
8191
  var spawnAgentsParallelInputShape, MAX_CHILD_HTTP_SESSIONS, DEFAULT_TASKS_DB_PATH, RelayMCPServer;
8330
8192
  var init_server = __esm({
8331
8193
  "src/mcp-server/server.ts"() {
8332
8194
  "use strict";
8333
- init_deferred_cleanup_task_store();
8334
8195
  init_agent_event_store();
8335
8196
  init_session_health_monitor();
8336
8197
  init_recursion_guard();
@@ -8346,7 +8207,6 @@ var init_server = __esm({
8346
8207
  init_metadata_validation();
8347
8208
  init_logger();
8348
8209
  init_version_check();
8349
- init_types2();
8350
8210
  init_response_formatter();
8351
8211
  init_tasks();
8352
8212
  spawnAgentsParallelInputShape = {
@@ -8453,8 +8313,7 @@ var init_server = __esm({
8453
8313
  this.agentEventStore
8454
8314
  );
8455
8315
  this.server = new McpServer(
8456
- { name: "agentic-relay", version: "2.0.8" },
8457
- createMcpServerOptions()
8316
+ { name: "agentic-relay", version: "2.0.10" }
8458
8317
  );
8459
8318
  this.registerTools(this.server);
8460
8319
  this.sessionHealthMonitor.start();
@@ -8481,173 +8340,87 @@ var init_server = __esm({
8481
8340
  get childHttpServer() {
8482
8341
  return this._childHttpServer;
8483
8342
  }
8484
- /**
8485
- * Run spawn_agent in the background and store the result in taskStore.
8486
- * Fire-and-forget: errors are caught and stored as failed task results.
8487
- */
8488
- runSpawnAgentInBackground(taskId, params, extra, relayContext, signal) {
8489
- const run = async () => {
8490
- try {
8491
- const result = await executeSpawnAgent(
8492
- params,
8493
- this.registry,
8494
- this.sessionManager,
8495
- this.guard,
8496
- this.hooksEngine,
8497
- this.contextMonitor,
8498
- this.backendSelector,
8499
- this._childHttpUrl,
8500
- void 0,
8501
- this.agentEventStore,
8502
- this.hookMemoryDir,
8503
- void 0,
8504
- this.taskLifecycleManager,
8505
- relayContext,
8506
- signal
8507
- );
8508
- const controlOptions = {
8509
- inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
8510
- sessionId: result.sessionId,
8511
- responseOutputDir: this.responseOutputDir
8512
- };
8513
- const { text, isError } = await formatSpawnAgentResponse(result, controlOptions);
8514
- const callToolResult = {
8515
- content: [{ type: "text", text }],
8516
- isError
8517
- };
8518
- await extra.taskStore.storeTaskResult(
8519
- taskId,
8520
- "completed",
8521
- callToolResult
8522
- );
8523
- } catch (outerError) {
8524
- try {
8525
- const message = outerError instanceof Error ? outerError.message : String(outerError);
8526
- await extra.taskStore.storeTaskResult(
8527
- taskId,
8528
- "failed",
8529
- {
8530
- content: [{ type: "text", text: `Error: ${message}` }],
8531
- isError: true
8532
- }
8533
- );
8534
- } catch (storeError) {
8535
- logger.error(
8536
- `Failed to store task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
8537
- );
8538
- }
8539
- }
8540
- };
8541
- void run();
8542
- }
8543
- /**
8544
- * Run spawn_agents_parallel in the background and store the result in taskStore.
8545
- * Fire-and-forget: errors are caught and stored as failed task results.
8546
- */
8547
- runSpawnAgentsParallelInBackground(taskId, agents, extra, relayContext, signal) {
8548
- const run = async () => {
8549
- try {
8550
- const result = await executeSpawnAgentsParallel(
8551
- agents,
8552
- this.registry,
8553
- this.sessionManager,
8554
- this.guard,
8555
- this.hooksEngine,
8556
- this.contextMonitor,
8557
- this.backendSelector,
8558
- this._childHttpUrl,
8559
- void 0,
8560
- this.agentEventStore,
8561
- this.hookMemoryDir,
8562
- void 0,
8563
- this.taskLifecycleManager,
8564
- relayContext,
8565
- signal
8566
- );
8567
- const controlOptions = {
8568
- inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
8569
- responseOutputDir: this.responseOutputDir
8570
- };
8571
- const { text, isError } = await formatParallelResponse(result, controlOptions);
8572
- const callToolResult = {
8573
- content: [{ type: "text", text }],
8574
- isError
8575
- };
8576
- await extra.taskStore.storeTaskResult(
8577
- taskId,
8578
- "completed",
8579
- callToolResult
8580
- );
8581
- } catch (outerError) {
8582
- try {
8583
- const message = outerError instanceof Error ? outerError.message : String(outerError);
8584
- await extra.taskStore.storeTaskResult(
8585
- taskId,
8586
- "failed",
8587
- {
8588
- content: [{ type: "text", text: `Error: ${message}` }],
8589
- isError: true
8590
- }
8591
- );
8592
- } catch (storeError) {
8593
- logger.error(
8594
- `Failed to store parallel task result for ${taskId}: ${storeError instanceof Error ? storeError.message : String(storeError)}`
8595
- );
8596
- }
8597
- }
8598
- };
8599
- void run();
8600
- }
8601
- registerTools(server, relayContext, signal) {
8602
- server.experimental.tasks.registerToolTask(
8343
+ registerTools(server, relayContext, sessionSignal) {
8344
+ server.tool(
8603
8345
  "spawn_agent",
8604
- {
8605
- 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.",
8606
- inputSchema: spawnAgentInputSchema.shape,
8607
- execution: { taskSupport: "optional" }
8608
- },
8609
- {
8610
- createTask: async (params, extra) => {
8611
- const task = await extra.taskStore.createTask({
8612
- ttl: DEFAULT_TASK_TTL,
8613
- pollInterval: DEFAULT_POLL_INTERVAL
8614
- });
8615
- this.runSpawnAgentInBackground(task.taskId, params, extra, relayContext, signal);
8616
- return { task };
8617
- },
8618
- getTask: async (_params, extra) => {
8619
- return await extra.taskStore.getTask(extra.taskId);
8620
- },
8621
- getTaskResult: async (_params, extra) => {
8622
- return await extra.taskStore.getTaskResult(
8623
- extra.taskId
8346
+ "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.",
8347
+ spawnAgentInputSchema.shape,
8348
+ async (params, extra) => {
8349
+ try {
8350
+ const result = await executeSpawnAgent(
8351
+ params,
8352
+ this.registry,
8353
+ this.sessionManager,
8354
+ this.guard,
8355
+ this.hooksEngine,
8356
+ this.contextMonitor,
8357
+ this.backendSelector,
8358
+ this._childHttpUrl,
8359
+ void 0,
8360
+ this.agentEventStore,
8361
+ this.hookMemoryDir,
8362
+ void 0,
8363
+ this.taskLifecycleManager,
8364
+ relayContext,
8365
+ combineAbortSignals(extra.signal, sessionSignal)
8624
8366
  );
8367
+ const controlOptions = {
8368
+ inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
8369
+ sessionId: result.sessionId,
8370
+ responseOutputDir: this.responseOutputDir
8371
+ };
8372
+ const { text, isError } = await formatSpawnAgentResponse(result, controlOptions);
8373
+ return {
8374
+ content: [{ type: "text", text }],
8375
+ isError
8376
+ };
8377
+ } catch (error) {
8378
+ const message = error instanceof Error ? error.message : String(error);
8379
+ return {
8380
+ content: [{ type: "text", text: `Error: ${message}` }],
8381
+ isError: true
8382
+ };
8625
8383
  }
8626
8384
  }
8627
8385
  );
8628
- server.experimental.tasks.registerToolTask(
8386
+ server.tool(
8629
8387
  "spawn_agents_parallel",
8630
- {
8631
- 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.",
8632
- inputSchema: spawnAgentsParallelInputShape,
8633
- execution: { taskSupport: "optional" }
8634
- },
8635
- {
8636
- createTask: async (params, extra) => {
8637
- const task = await extra.taskStore.createTask({
8638
- ttl: DEFAULT_TASK_TTL,
8639
- pollInterval: DEFAULT_POLL_INTERVAL
8640
- });
8641
- this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra, relayContext, signal);
8642
- return { task };
8643
- },
8644
- getTask: async (_params, extra) => {
8645
- return await extra.taskStore.getTask(extra.taskId);
8646
- },
8647
- getTaskResult: async (_params, extra) => {
8648
- return await extra.taskStore.getTaskResult(
8649
- extra.taskId
8388
+ "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.",
8389
+ spawnAgentsParallelInputShape,
8390
+ async (params, extra) => {
8391
+ try {
8392
+ const result = await executeSpawnAgentsParallel(
8393
+ params.agents,
8394
+ this.registry,
8395
+ this.sessionManager,
8396
+ this.guard,
8397
+ this.hooksEngine,
8398
+ this.contextMonitor,
8399
+ this.backendSelector,
8400
+ this._childHttpUrl,
8401
+ void 0,
8402
+ this.agentEventStore,
8403
+ this.hookMemoryDir,
8404
+ void 0,
8405
+ this.taskLifecycleManager,
8406
+ relayContext,
8407
+ combineAbortSignals(extra.signal, sessionSignal)
8650
8408
  );
8409
+ const controlOptions = {
8410
+ inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
8411
+ responseOutputDir: this.responseOutputDir
8412
+ };
8413
+ const { text, isError } = await formatParallelResponse(result, controlOptions);
8414
+ return {
8415
+ content: [{ type: "text", text }],
8416
+ isError
8417
+ };
8418
+ } catch (error) {
8419
+ const message = error instanceof Error ? error.message : String(error);
8420
+ return {
8421
+ content: [{ type: "text", text: `Error: ${message}` }],
8422
+ isError: true
8423
+ };
8651
8424
  }
8652
8425
  }
8653
8426
  );
@@ -8660,7 +8433,7 @@ var init_server = __esm({
8660
8433
  originalInput: spawnAgentInputSchema
8661
8434
  })).min(1).describe("Array of failed results with their original input configurations")
8662
8435
  },
8663
- async (params) => {
8436
+ async (params, extra) => {
8664
8437
  try {
8665
8438
  const agents = params.failedResults.map((r) => ({ ...r.originalInput }));
8666
8439
  const result = await executeSpawnAgentsParallel(
@@ -8678,7 +8451,7 @@ var init_server = __esm({
8678
8451
  void 0,
8679
8452
  this.taskLifecycleManager,
8680
8453
  relayContext,
8681
- signal
8454
+ combineAbortSignals(extra.signal, sessionSignal)
8682
8455
  );
8683
8456
  const controlOptions = {
8684
8457
  inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
@@ -8972,8 +8745,7 @@ var init_server = __esm({
8972
8745
  sessionIdGenerator: () => randomUUID()
8973
8746
  });
8974
8747
  const server = new McpServer(
8975
- { name: "agentic-relay", version: "2.0.8" },
8976
- createMcpServerOptions()
8748
+ { name: "agentic-relay", version: "2.0.10" }
8977
8749
  );
8978
8750
  this.registerTools(server, childRelayContext, abortController.signal);
8979
8751
  transport.onclose = () => {
@@ -8984,7 +8756,7 @@ var init_server = __esm({
8984
8756
  if (ctrl) {
8985
8757
  ctrl.abort();
8986
8758
  sessionAbortControllers.delete(sid2);
8987
- logger.debug(`Child MCP session closed, background tasks aborted: ${sid2}`);
8759
+ logger.debug(`Child MCP session closed, in-flight requests aborted: ${sid2}`);
8988
8760
  } else {
8989
8761
  logger.debug(`Child MCP session closed: ${sid2}`);
8990
8762
  }
@@ -9748,7 +9520,7 @@ __export(types_exports, {
9748
9520
  DEFAULT_TUI_CONFIG: () => DEFAULT_TUI_CONFIG
9749
9521
  });
9750
9522
  var DEFAULT_TUI_CONFIG;
9751
- var init_types3 = __esm({
9523
+ var init_types2 = __esm({
9752
9524
  "src/tui/types.ts"() {
9753
9525
  "use strict";
9754
9526
  DEFAULT_TUI_CONFIG = {
@@ -9772,7 +9544,7 @@ var TUIConfigManager;
9772
9544
  var init_tui_config_manager = __esm({
9773
9545
  "src/tui/services/tui-config-manager.ts"() {
9774
9546
  "use strict";
9775
- init_types3();
9547
+ init_types2();
9776
9548
  TUIConfigManager = class {
9777
9549
  constructor(configManager2) {
9778
9550
  this.configManager = configManager2;
@@ -10099,7 +9871,7 @@ function App({ registry: registry2, sessionManager: sessionManager2, hooksEngine
10099
9871
  setTuiConfig(updated);
10100
9872
  setStatusMessage(`Config updated: ${key} = ${JSON.stringify(value)}`);
10101
9873
  } else if (subCmd === "reset") {
10102
- const { DEFAULT_TUI_CONFIG: DEFAULT_TUI_CONFIG2 } = await Promise.resolve().then(() => (init_types3(), types_exports));
9874
+ const { DEFAULT_TUI_CONFIG: DEFAULT_TUI_CONFIG2 } = await Promise.resolve().then(() => (init_types2(), types_exports));
10103
9875
  for (const [section, sectionValue] of Object.entries(DEFAULT_TUI_CONFIG2)) {
10104
9876
  for (const [k, v] of Object.entries(sectionValue)) {
10105
9877
  await tuiConfigManager.set(`${section}.${k}`, v);
@@ -10311,6 +10083,18 @@ import { homedir as homedir7 } from "os";
10311
10083
  // src/infrastructure/process-manager.ts
10312
10084
  init_logger();
10313
10085
  import { execa } from "execa";
10086
+ function toProcessResultFromError(error) {
10087
+ const exitCode = typeof error === "object" && error !== null && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1;
10088
+ const stdout = typeof error === "object" && error !== null && "stdout" in error && typeof error.stdout === "string" ? error.stdout : "";
10089
+ const stderr = typeof error === "object" && error !== null && "stderr" in error && typeof error.stderr === "string" && error.stderr.length > 0 ? error.stderr : error instanceof Error ? error.message : String(error);
10090
+ return { exitCode, stdout, stderr };
10091
+ }
10092
+ function getCanceledMessage(signal) {
10093
+ if (!signal?.aborted) {
10094
+ return "";
10095
+ }
10096
+ return signal.reason instanceof Error ? signal.reason.message : typeof signal.reason === "string" ? signal.reason : "";
10097
+ }
10314
10098
  var ProcessManager = class {
10315
10099
  activeProcesses = /* @__PURE__ */ new Set();
10316
10100
  constructor() {
@@ -10324,6 +10108,7 @@ var ProcessManager = class {
10324
10108
  env: options?.env,
10325
10109
  extendEnv: options?.env ? false : true,
10326
10110
  timeout: options?.timeout,
10111
+ cancelSignal: options?.signal,
10327
10112
  reject: false
10328
10113
  };
10329
10114
  const proc = execa(command, args, execaOptions);
@@ -10344,6 +10129,7 @@ var ProcessManager = class {
10344
10129
  env: options?.env,
10345
10130
  extendEnv: options?.env ? false : true,
10346
10131
  timeout: options?.timeout,
10132
+ cancelSignal: options?.signal,
10347
10133
  reject: false
10348
10134
  };
10349
10135
  const proc = execa(command, args, execaOptions);
@@ -10353,8 +10139,10 @@ var ProcessManager = class {
10353
10139
  return {
10354
10140
  exitCode: result.exitCode ?? 1,
10355
10141
  stdout: result.stdout?.toString() ?? "",
10356
- stderr: result.stderr?.toString() ?? ""
10142
+ stderr: result.stderr?.toString() || getCanceledMessage(options?.signal)
10357
10143
  };
10144
+ } catch (error) {
10145
+ return toProcessResultFromError(error);
10358
10146
  } finally {
10359
10147
  this.activeProcesses.delete(proc);
10360
10148
  }
@@ -10366,7 +10154,9 @@ var ProcessManager = class {
10366
10154
  input: stdinData,
10367
10155
  cwd: options?.cwd,
10368
10156
  env: options?.env,
10157
+ extendEnv: options?.env ? false : true,
10369
10158
  timeout: options?.timeout,
10159
+ cancelSignal: options?.signal,
10370
10160
  reject: false
10371
10161
  };
10372
10162
  const proc = execa(command, args, execaOptions);
@@ -10376,8 +10166,10 @@ var ProcessManager = class {
10376
10166
  return {
10377
10167
  exitCode: result.exitCode ?? 1,
10378
10168
  stdout: result.stdout?.toString() ?? "",
10379
- stderr: result.stderr?.toString() ?? ""
10169
+ stderr: result.stderr?.toString() || getCanceledMessage(options?.signal)
10380
10170
  };
10171
+ } catch (error) {
10172
+ return toProcessResultFromError(error);
10381
10173
  } finally {
10382
10174
  this.activeProcesses.delete(proc);
10383
10175
  }
@@ -10466,7 +10258,7 @@ var BaseAdapter = class _BaseAdapter {
10466
10258
  }
10467
10259
  return result.stdout.trim();
10468
10260
  }
10469
- async continueSession(_nativeSessionId, _prompt) {
10261
+ async continueSession(_nativeSessionId, _prompt, _options) {
10470
10262
  return {
10471
10263
  exitCode: 1,
10472
10264
  stdout: "",
@@ -10855,9 +10647,11 @@ var ClaudeAdapter = class extends BaseAdapter {
10855
10647
  if (timer !== void 0) clearTimeout(timer);
10856
10648
  }
10857
10649
  }
10858
- async continueSession(nativeSessionId, prompt) {
10650
+ async continueSession(nativeSessionId, prompt, options) {
10859
10651
  const timeoutMs = resolveClaudeSdkTimeoutMs();
10860
10652
  const abortController = new AbortController();
10653
+ const onAbort = () => abortController.abort();
10654
+ options?.signal?.addEventListener("abort", onAbort, { once: true });
10861
10655
  const timer = timeoutMs !== void 0 ? setTimeout(() => abortController.abort(), timeoutMs) : void 0;
10862
10656
  try {
10863
10657
  const { query } = await loadClaudeSDK();
@@ -10906,6 +10700,7 @@ var ClaudeAdapter = class extends BaseAdapter {
10906
10700
  stderr: error instanceof Error ? error.message : String(error)
10907
10701
  };
10908
10702
  } finally {
10703
+ options?.signal?.removeEventListener("abort", onAbort);
10909
10704
  if (timer !== void 0) clearTimeout(timer);
10910
10705
  }
10911
10706
  }
@@ -11008,8 +10803,8 @@ var ClaudeAdapter = class extends BaseAdapter {
11008
10803
 
11009
10804
  // src/adapters/codex-adapter.ts
11010
10805
  init_logger();
11011
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, mkdtemp, copyFile } from "fs/promises";
11012
- import { homedir as homedir2 } from "os";
10806
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, mkdtemp, copyFile, rm } from "fs/promises";
10807
+ import { homedir as homedir2, tmpdir } from "os";
11013
10808
  import { join as join2, dirname as dirname2 } from "path";
11014
10809
  async function loadCodexSDK() {
11015
10810
  return await import("@openai/codex-sdk");
@@ -11195,6 +10990,70 @@ async function copyIfExists(from, to) {
11195
10990
  throw error;
11196
10991
  }
11197
10992
  }
10993
+ async function readFileIfExists(path3) {
10994
+ try {
10995
+ return await readFile2(path3, "utf-8");
10996
+ } catch (error) {
10997
+ if (isFileNotFoundError(error)) {
10998
+ return void 0;
10999
+ }
11000
+ throw error;
11001
+ }
11002
+ }
11003
+ function parseCodexExecJson(stdout) {
11004
+ const parsed = {
11005
+ completedMessages: [],
11006
+ errorMessages: []
11007
+ };
11008
+ for (const rawLine of stdout.split(/\r?\n/)) {
11009
+ const line = rawLine.trim();
11010
+ if (!line) continue;
11011
+ let event;
11012
+ try {
11013
+ event = JSON.parse(line);
11014
+ } catch {
11015
+ continue;
11016
+ }
11017
+ if (typeof event !== "object" || event === null || !("type" in event) || typeof event.type !== "string") {
11018
+ continue;
11019
+ }
11020
+ switch (event.type) {
11021
+ case "thread.started":
11022
+ if ("thread_id" in event && typeof event.thread_id === "string") {
11023
+ parsed.threadId = event.thread_id;
11024
+ }
11025
+ break;
11026
+ case "item.completed":
11027
+ if ("item" in event && typeof event.item === "object" && event.item !== null && "type" in event.item && event.item.type === "agent_message" && "text" in event.item && typeof event.item.text === "string") {
11028
+ parsed.completedMessages.push(event.item.text);
11029
+ }
11030
+ break;
11031
+ case "turn.completed":
11032
+ if ("usage" in event && typeof event.usage === "object" && event.usage !== null) {
11033
+ const cachedInputTokens = "cached_input_tokens" in event.usage && typeof event.usage.cached_input_tokens === "number" ? event.usage.cached_input_tokens : 0;
11034
+ parsed.tokenUsage = {
11035
+ inputTokens: ("input_tokens" in event.usage && typeof event.usage.input_tokens === "number" ? event.usage.input_tokens : 0) + cachedInputTokens,
11036
+ outputTokens: "output_tokens" in event.usage && typeof event.usage.output_tokens === "number" ? event.usage.output_tokens : 0,
11037
+ ...cachedInputTokens > 0 ? { cachedInputTokens } : {}
11038
+ };
11039
+ }
11040
+ break;
11041
+ case "turn.failed":
11042
+ if ("error" in event && typeof event.error === "object" && event.error !== null && "message" in event.error && typeof event.error.message === "string") {
11043
+ parsed.failureMessage = event.error.message;
11044
+ }
11045
+ break;
11046
+ case "error":
11047
+ if ("message" in event && typeof event.message === "string") {
11048
+ parsed.errorMessages.push(event.message);
11049
+ }
11050
+ break;
11051
+ default:
11052
+ break;
11053
+ }
11054
+ }
11055
+ return parsed;
11056
+ }
11198
11057
  var CodexAdapter = class extends BaseAdapter {
11199
11058
  id = "codex";
11200
11059
  command = "codex";
@@ -11328,6 +11187,62 @@ ${systemPrompt}
11328
11187
  [User Request]
11329
11188
  ${prompt}`;
11330
11189
  }
11190
+ shouldHardenRelayChild(flags) {
11191
+ return Boolean(
11192
+ flags.mcpContext || flags.mcpServers && Object.keys(flags.mcpServers).length > 0
11193
+ );
11194
+ }
11195
+ buildCodexExecBaseArgs(model, hardenRelayChild) {
11196
+ const args = [
11197
+ "-a",
11198
+ "never",
11199
+ "-C",
11200
+ process.cwd()
11201
+ ];
11202
+ if (model) {
11203
+ args.unshift(model);
11204
+ args.unshift("--model");
11205
+ }
11206
+ if (hardenRelayChild) {
11207
+ args.push("-c", "project_root_markers=[]");
11208
+ }
11209
+ return args;
11210
+ }
11211
+ async runCodexExecCommand(args, prompt, env, signal, isolatedCodexHome, fallbackNativeSessionId) {
11212
+ const execTempDir = await mkdtemp(join2(tmpdir(), "relay-codex-exec-"));
11213
+ const outputPath = join2(execTempDir, "last-message.txt");
11214
+ try {
11215
+ const result = await this.processManager.executeWithInput(
11216
+ this.command,
11217
+ [...args, "--json", "-o", outputPath, "-"],
11218
+ prompt,
11219
+ { env, signal }
11220
+ );
11221
+ const parsed = parseCodexExecJson(result.stdout);
11222
+ const outputFileContent = await readFileIfExists(outputPath);
11223
+ const finalResponse = outputFileContent ?? parsed.completedMessages.join("\n");
11224
+ const nativeSessionId = parsed.threadId ? encodeNativeSessionId(parsed.threadId, isolatedCodexHome) : fallbackNativeSessionId;
11225
+ if (result.exitCode === 0) {
11226
+ return {
11227
+ exitCode: 0,
11228
+ stdout: finalResponse,
11229
+ stderr: "",
11230
+ ...nativeSessionId ? { nativeSessionId } : {},
11231
+ ...parsed.tokenUsage ? { tokenUsage: parsed.tokenUsage } : {}
11232
+ };
11233
+ }
11234
+ const stderr = signal?.aborted ? "Codex execution cancelled: parent session disconnected" : parsed.failureMessage ?? parsed.errorMessages.at(-1) ?? (result.stderr || "Codex execution failed");
11235
+ return {
11236
+ exitCode: result.exitCode || 1,
11237
+ stdout: finalResponse,
11238
+ stderr,
11239
+ ...nativeSessionId ? { nativeSessionId } : {},
11240
+ ...parsed.tokenUsage ? { tokenUsage: parsed.tokenUsage } : {}
11241
+ };
11242
+ } finally {
11243
+ await rm(execTempDir, { recursive: true, force: true }).catch(() => void 0);
11244
+ }
11245
+ }
11331
11246
  async execute(flags) {
11332
11247
  if (!flags.prompt) {
11333
11248
  throw new Error("execute requires a prompt (-p flag)");
@@ -11338,28 +11253,22 @@ ${prompt}`;
11338
11253
  systemPrompt
11339
11254
  );
11340
11255
  try {
11341
- const { Codex } = await loadCodexSDK();
11342
11256
  const { options, isolatedCodexHome } = await this.buildCodexOptions(flags);
11343
- const codex = new Codex(options);
11344
- const thread = codex.startThread({
11345
- ...flags.model ? { model: flags.model } : {},
11346
- workingDirectory: process.cwd(),
11347
- approvalPolicy: "never"
11348
- });
11349
- const result = await thread.run(effectivePrompt, {
11350
- ...flags.signal ? { signal: flags.signal } : {}
11351
- });
11352
- return {
11353
- exitCode: 0,
11354
- stdout: result.finalResponse,
11355
- stderr: "",
11356
- ...thread.id ? {
11357
- nativeSessionId: encodeNativeSessionId(
11358
- thread.id,
11359
- isolatedCodexHome
11360
- )
11361
- } : {}
11362
- };
11257
+ const args = this.buildCodexExecBaseArgs(
11258
+ flags.model,
11259
+ this.shouldHardenRelayChild(flags)
11260
+ );
11261
+ args.push("exec");
11262
+ if (this.shouldHardenRelayChild(flags)) {
11263
+ args.push("--disable", "multi_agent");
11264
+ }
11265
+ return await this.runCodexExecCommand(
11266
+ args,
11267
+ effectivePrompt,
11268
+ options.env,
11269
+ flags.signal,
11270
+ isolatedCodexHome
11271
+ );
11363
11272
  } catch (error) {
11364
11273
  if (flags.signal?.aborted) {
11365
11274
  return {
@@ -11479,34 +11388,36 @@ ${prompt}`;
11479
11388
  };
11480
11389
  }
11481
11390
  }
11482
- async continueSession(nativeSessionId, prompt) {
11391
+ async continueSession(nativeSessionId, prompt, options) {
11483
11392
  try {
11484
- const { Codex } = await loadCodexSDK();
11485
11393
  const { threadId, codexHome } = decodeNativeSessionId(nativeSessionId);
11486
- const codex = new Codex(
11487
- codexHome ? {
11488
- env: {
11489
- ...Object.fromEntries(
11490
- Object.entries(process.env).filter(
11491
- ([, value]) => value !== void 0
11492
- )
11493
- ),
11494
- CODEX_HOME: codexHome
11495
- }
11496
- } : {}
11497
- );
11498
- const thread = codex.resumeThread(threadId, {
11499
- workingDirectory: process.cwd(),
11500
- approvalPolicy: "never"
11501
- });
11502
- const result = await thread.run(prompt);
11503
- return {
11504
- exitCode: 0,
11505
- stdout: result.finalResponse,
11506
- stderr: "",
11394
+ const env = codexHome ? {
11395
+ ...Object.fromEntries(
11396
+ Object.entries(process.env).filter(
11397
+ ([, value]) => value !== void 0
11398
+ )
11399
+ ),
11400
+ CODEX_HOME: codexHome
11401
+ } : void 0;
11402
+ const args = this.buildCodexExecBaseArgs(options?.model, true);
11403
+ args.push("exec", "resume", "--disable", "multi_agent", threadId);
11404
+ return await this.runCodexExecCommand(
11405
+ args,
11406
+ prompt,
11407
+ env,
11408
+ options?.signal,
11409
+ codexHome,
11507
11410
  nativeSessionId
11508
- };
11411
+ );
11509
11412
  } catch (error) {
11413
+ if (options?.signal?.aborted) {
11414
+ return {
11415
+ exitCode: 1,
11416
+ stdout: "",
11417
+ stderr: "Codex execution cancelled: parent session disconnected",
11418
+ nativeSessionId
11419
+ };
11420
+ }
11510
11421
  return {
11511
11422
  exitCode: 1,
11512
11423
  stdout: "",
@@ -13617,7 +13528,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
13617
13528
  responseOutputDir,
13618
13529
  relayConfig
13619
13530
  );
13620
- await server.start({ transport, port, currentVersion: "2.0.8" });
13531
+ await server.start({ transport, port, currentVersion: "2.0.10" });
13621
13532
  }
13622
13533
  })
13623
13534
  },
@@ -13777,7 +13688,7 @@ function createVersionCommand(registry2) {
13777
13688
  description: "Show relay and backend versions"
13778
13689
  },
13779
13690
  async run() {
13780
- const relayVersion = "2.0.8";
13691
+ const relayVersion = "2.0.10";
13781
13692
  console.log(`agentic-relay v${relayVersion}`);
13782
13693
  console.log("");
13783
13694
  console.log("Backends:");
@@ -14174,7 +14085,7 @@ var subCommandNames = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "upd
14174
14085
  var main = defineCommand11({
14175
14086
  meta: {
14176
14087
  name: "relay",
14177
- version: "2.0.8",
14088
+ version: "2.0.10",
14178
14089
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
14179
14090
  },
14180
14091
  args: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "2.0.8",
3
+ "version": "2.0.10",
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",