@rk0429/agentic-relay 2.0.6 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/relay.mjs +250 -66
  2. package/package.json +1 -1
package/dist/relay.mjs CHANGED
@@ -332,7 +332,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } fr
332
332
  import { join as join6 } from "path";
333
333
  import { homedir as homedir5 } from "os";
334
334
  import { nanoid as nanoid2 } from "nanoid";
335
- function getRelayHome2() {
335
+ function getRelayHome3() {
336
336
  return process.env["RELAY_HOME"] ?? join6(homedir5(), ".relay");
337
337
  }
338
338
  function isValidEventType(type) {
@@ -347,7 +347,7 @@ var init_agent_event_store = __esm({
347
347
  maxEvents: 1e3,
348
348
  ttlMs: 36e5,
349
349
  backend: "jsonl",
350
- sessionDir: join6(getRelayHome2(), "sessions"),
350
+ sessionDir: join6(getRelayHome3(), "sessions"),
351
351
  eventsFileName: "events.jsonl"
352
352
  };
353
353
  AgentEventStore = class {
@@ -1085,7 +1085,16 @@ function buildContextFromEnv() {
1085
1085
  const depth = Number(process.env["RELAY_DEPTH"] ?? "0");
1086
1086
  return { traceId, parentSessionId, depth };
1087
1087
  }
1088
- async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext) {
1088
+ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext, signal) {
1089
+ if (signal?.aborted) {
1090
+ return {
1091
+ sessionId: "",
1092
+ exitCode: 1,
1093
+ stdout: "",
1094
+ stderr: "Spawn cancelled: parent session disconnected",
1095
+ failureReason: "unknown"
1096
+ };
1097
+ }
1089
1098
  onProgress?.({ stage: "initializing", percent: 0 });
1090
1099
  let inlineTaskId;
1091
1100
  if (input.title && taskLifecycleManager) {
@@ -1405,7 +1414,8 @@ ${input.prompt}`;
1405
1414
  maxDepth: guard.getConfig().maxDepth,
1406
1415
  traceId: envContext.traceId
1407
1416
  },
1408
- ...mcpServers ? { mcpServers } : {}
1417
+ ...mcpServers ? { mcpServers } : {},
1418
+ ...signal ? { signal } : {}
1409
1419
  });
1410
1420
  }
1411
1421
  })();
@@ -1853,7 +1863,7 @@ var init_conflict_detector = __esm({
1853
1863
  });
1854
1864
 
1855
1865
  // 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) {
1866
+ async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl, onProgress, agentEventStore, hookMemoryDir = "./memory", taskCompleter, taskLifecycleManager, relayContext, signal) {
1857
1867
  for (let index = 0; index < agents.length; index += 1) {
1858
1868
  try {
1859
1869
  resolveValidatedSessionMetadata(agents[index]);
@@ -1939,7 +1949,8 @@ async function executeSpawnAgentsParallel(agents, registry2, sessionManager2, gu
1939
1949
  hookMemoryDir,
1940
1950
  taskCompleter,
1941
1951
  taskLifecycleManager,
1942
- relayContext
1952
+ relayContext,
1953
+ signal
1943
1954
  ).then((result) => {
1944
1955
  completedCount++;
1945
1956
  onProgress?.({
@@ -8442,7 +8453,7 @@ var init_server = __esm({
8442
8453
  this.agentEventStore
8443
8454
  );
8444
8455
  this.server = new McpServer(
8445
- { name: "agentic-relay", version: "2.0.6" },
8456
+ { name: "agentic-relay", version: "2.0.8" },
8446
8457
  createMcpServerOptions()
8447
8458
  );
8448
8459
  this.registerTools(this.server);
@@ -8474,7 +8485,7 @@ var init_server = __esm({
8474
8485
  * Run spawn_agent in the background and store the result in taskStore.
8475
8486
  * Fire-and-forget: errors are caught and stored as failed task results.
8476
8487
  */
8477
- runSpawnAgentInBackground(taskId, params, extra, relayContext) {
8488
+ runSpawnAgentInBackground(taskId, params, extra, relayContext, signal) {
8478
8489
  const run = async () => {
8479
8490
  try {
8480
8491
  const result = await executeSpawnAgent(
@@ -8491,7 +8502,8 @@ var init_server = __esm({
8491
8502
  this.hookMemoryDir,
8492
8503
  void 0,
8493
8504
  this.taskLifecycleManager,
8494
- relayContext
8505
+ relayContext,
8506
+ signal
8495
8507
  );
8496
8508
  const controlOptions = {
8497
8509
  inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
@@ -8532,7 +8544,7 @@ var init_server = __esm({
8532
8544
  * Run spawn_agents_parallel in the background and store the result in taskStore.
8533
8545
  * Fire-and-forget: errors are caught and stored as failed task results.
8534
8546
  */
8535
- runSpawnAgentsParallelInBackground(taskId, agents, extra, relayContext) {
8547
+ runSpawnAgentsParallelInBackground(taskId, agents, extra, relayContext, signal) {
8536
8548
  const run = async () => {
8537
8549
  try {
8538
8550
  const result = await executeSpawnAgentsParallel(
@@ -8549,7 +8561,8 @@ var init_server = __esm({
8549
8561
  this.hookMemoryDir,
8550
8562
  void 0,
8551
8563
  this.taskLifecycleManager,
8552
- relayContext
8564
+ relayContext,
8565
+ signal
8553
8566
  );
8554
8567
  const controlOptions = {
8555
8568
  inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
@@ -8585,7 +8598,7 @@ var init_server = __esm({
8585
8598
  };
8586
8599
  void run();
8587
8600
  }
8588
- registerTools(server, relayContext) {
8601
+ registerTools(server, relayContext, signal) {
8589
8602
  server.experimental.tasks.registerToolTask(
8590
8603
  "spawn_agent",
8591
8604
  {
@@ -8599,7 +8612,7 @@ var init_server = __esm({
8599
8612
  ttl: DEFAULT_TASK_TTL,
8600
8613
  pollInterval: DEFAULT_POLL_INTERVAL
8601
8614
  });
8602
- this.runSpawnAgentInBackground(task.taskId, params, extra, relayContext);
8615
+ this.runSpawnAgentInBackground(task.taskId, params, extra, relayContext, signal);
8603
8616
  return { task };
8604
8617
  },
8605
8618
  getTask: async (_params, extra) => {
@@ -8625,7 +8638,7 @@ var init_server = __esm({
8625
8638
  ttl: DEFAULT_TASK_TTL,
8626
8639
  pollInterval: DEFAULT_POLL_INTERVAL
8627
8640
  });
8628
- this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra, relayContext);
8641
+ this.runSpawnAgentsParallelInBackground(task.taskId, params.agents, extra, relayContext, signal);
8629
8642
  return { task };
8630
8643
  },
8631
8644
  getTask: async (_params, extra) => {
@@ -8664,7 +8677,8 @@ var init_server = __esm({
8664
8677
  this.hookMemoryDir,
8665
8678
  void 0,
8666
8679
  this.taskLifecycleManager,
8667
- relayContext
8680
+ relayContext,
8681
+ signal
8668
8682
  );
8669
8683
  const controlOptions = {
8670
8684
  inlineSummaryLength: this.inlineSummaryLength ?? DEFAULT_INLINE_SUMMARY_LENGTH,
@@ -8936,6 +8950,7 @@ var init_server = __esm({
8936
8950
  */
8937
8951
  async startChildHttpServer() {
8938
8952
  const sessions = /* @__PURE__ */ new Map();
8953
+ const sessionAbortControllers = /* @__PURE__ */ new Map();
8939
8954
  const httpServer = createServer(async (req, res) => {
8940
8955
  const url = req.url ?? "";
8941
8956
  if (url !== "/mcp" && !url.startsWith("/mcp?")) {
@@ -8952,19 +8967,27 @@ var init_server = __esm({
8952
8967
  }
8953
8968
  }
8954
8969
  const childRelayContext = extractRelayContextFromUrl(url);
8970
+ const abortController = new AbortController();
8955
8971
  const transport = new StreamableHTTPServerTransport({
8956
8972
  sessionIdGenerator: () => randomUUID()
8957
8973
  });
8958
8974
  const server = new McpServer(
8959
- { name: "agentic-relay", version: "2.0.6" },
8975
+ { name: "agentic-relay", version: "2.0.8" },
8960
8976
  createMcpServerOptions()
8961
8977
  );
8962
- this.registerTools(server, childRelayContext);
8978
+ this.registerTools(server, childRelayContext, abortController.signal);
8963
8979
  transport.onclose = () => {
8964
8980
  const sid2 = transport.sessionId;
8965
8981
  if (sid2) {
8966
8982
  sessions.delete(sid2);
8967
- logger.debug(`Child MCP session closed: ${sid2}`);
8983
+ const ctrl = sessionAbortControllers.get(sid2);
8984
+ if (ctrl) {
8985
+ ctrl.abort();
8986
+ sessionAbortControllers.delete(sid2);
8987
+ logger.debug(`Child MCP session closed, background tasks aborted: ${sid2}`);
8988
+ } else {
8989
+ logger.debug(`Child MCP session closed: ${sid2}`);
8990
+ }
8968
8991
  }
8969
8992
  };
8970
8993
  await server.connect(transport);
@@ -8972,12 +8995,18 @@ var init_server = __esm({
8972
8995
  const sid = transport.sessionId;
8973
8996
  if (sid) {
8974
8997
  sessions.set(sid, { transport, server });
8998
+ sessionAbortControllers.set(sid, abortController);
8975
8999
  logger.debug(`Child MCP session created: ${sid}`);
8976
9000
  if (sessions.size > MAX_CHILD_HTTP_SESSIONS) {
8977
9001
  const oldestEntry = sessions.entries().next().value;
8978
9002
  if (oldestEntry) {
8979
9003
  const [oldestSessionId, oldestSession] = oldestEntry;
8980
9004
  sessions.delete(oldestSessionId);
9005
+ const evictedCtrl = sessionAbortControllers.get(oldestSessionId);
9006
+ if (evictedCtrl) {
9007
+ evictedCtrl.abort();
9008
+ sessionAbortControllers.delete(oldestSessionId);
9009
+ }
8981
9010
  logger.warn(
8982
9011
  `Child MCP session evicted due to limit (${MAX_CHILD_HTTP_SESSIONS}): ${oldestSessionId}`
8983
9012
  );
@@ -10635,6 +10664,14 @@ var ClaudeAdapter = class extends BaseAdapter {
10635
10664
  const timeoutMs = resolveClaudeSdkTimeoutMs();
10636
10665
  const abortController = new AbortController();
10637
10666
  const timer = timeoutMs !== void 0 ? setTimeout(() => abortController.abort(), timeoutMs) : void 0;
10667
+ const onExternalAbort = () => abortController.abort();
10668
+ if (flags.signal) {
10669
+ if (flags.signal.aborted) {
10670
+ abortController.abort();
10671
+ } else {
10672
+ flags.signal.addEventListener("abort", onExternalAbort, { once: true });
10673
+ }
10674
+ }
10638
10675
  try {
10639
10676
  const { query } = await loadClaudeSDK();
10640
10677
  const options = {
@@ -10705,10 +10742,11 @@ var ClaudeAdapter = class extends BaseAdapter {
10705
10742
  };
10706
10743
  } catch (error) {
10707
10744
  if (abortController.signal.aborted) {
10745
+ const reason = flags.signal?.aborted ? "Claude execution cancelled: parent session disconnected" : `Claude SDK query timed out after ${timeoutMs}ms`;
10708
10746
  return {
10709
10747
  exitCode: 1,
10710
10748
  stdout: "",
10711
- stderr: `Claude SDK query timed out after ${timeoutMs}ms`
10749
+ stderr: reason
10712
10750
  };
10713
10751
  }
10714
10752
  return {
@@ -10718,6 +10756,7 @@ var ClaudeAdapter = class extends BaseAdapter {
10718
10756
  };
10719
10757
  } finally {
10720
10758
  if (timer !== void 0) clearTimeout(timer);
10759
+ flags.signal?.removeEventListener("abort", onExternalAbort);
10721
10760
  }
10722
10761
  }
10723
10762
  async *executeStreaming(flags) {
@@ -10969,7 +11008,7 @@ var ClaudeAdapter = class extends BaseAdapter {
10969
11008
 
10970
11009
  // src/adapters/codex-adapter.ts
10971
11010
  init_logger();
10972
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
11011
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, mkdtemp, copyFile } from "fs/promises";
10973
11012
  import { homedir as homedir2 } from "os";
10974
11013
  import { join as join2, dirname as dirname2 } from "path";
10975
11014
  async function loadCodexSDK() {
@@ -11066,6 +11105,12 @@ function toTOMLString(value) {
11066
11105
  function toTOMLStringArray(values) {
11067
11106
  return `[${values.map(toTOMLString).join(", ")}]`;
11068
11107
  }
11108
+ function toTOMLInlineTable(values) {
11109
+ return `{ ${Object.entries(values).map(([key, value]) => `${toTOMLString(key)} = ${toTOMLString(value)}`).join(", ")} }`;
11110
+ }
11111
+ function toTOMLTableKey(key) {
11112
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : toTOMLString(key);
11113
+ }
11069
11114
  function generateMcpServersTOML(servers) {
11070
11115
  const parts = [];
11071
11116
  for (const server of servers) {
@@ -11079,11 +11124,88 @@ function generateMcpServersTOML(servers) {
11079
11124
  }
11080
11125
  return parts.join("\n");
11081
11126
  }
11127
+ function generateChildMcpServersTOML(servers) {
11128
+ const parts = [];
11129
+ for (const [name, server] of Object.entries(servers)) {
11130
+ parts.push(`[mcp_servers.${toTOMLTableKey(name)}]`);
11131
+ if ("url" in server) {
11132
+ parts.push(`url = ${toTOMLString(server.url)}`);
11133
+ if (server.headers && Object.keys(server.headers).length > 0) {
11134
+ parts.push(`headers = ${toTOMLInlineTable(server.headers)}`);
11135
+ }
11136
+ } else {
11137
+ parts.push(`command = ${toTOMLString(server.command)}`);
11138
+ if (server.args && server.args.length > 0) {
11139
+ parts.push(`args = ${toTOMLStringArray(server.args)}`);
11140
+ }
11141
+ if (server.env && Object.keys(server.env).length > 0) {
11142
+ parts.push(`env = ${toTOMLInlineTable(server.env)}`);
11143
+ }
11144
+ }
11145
+ parts.push("");
11146
+ }
11147
+ return parts.join("\n");
11148
+ }
11149
+ function rewriteCodexConfigWithMcpServers(existingContent, mcpServers) {
11150
+ const parsed = parseTOMLMcpServers(existingContent);
11151
+ const preamble = parsed.preambleLines.join("\n").replace(/\n+$/, "");
11152
+ const mcpSection = generateChildMcpServersTOML(mcpServers);
11153
+ const postamble = parsed.postambleLines.join("\n").replace(/^\n+/, "");
11154
+ const parts = [];
11155
+ if (preamble) parts.push(preamble);
11156
+ if (mcpSection) parts.push(mcpSection);
11157
+ if (postamble) parts.push(postamble);
11158
+ let output = parts.join("\n\n");
11159
+ if (!output.endsWith("\n")) output += "\n";
11160
+ return output;
11161
+ }
11162
+ function getRelayHome() {
11163
+ return process.env["RELAY_HOME"] ?? join2(homedir2(), ".relay");
11164
+ }
11165
+ function encodeNativeSessionId(threadId, codexHome) {
11166
+ if (!codexHome) {
11167
+ return threadId;
11168
+ }
11169
+ return `${threadId}@@${Buffer.from(codexHome, "utf8").toString("base64url")}`;
11170
+ }
11171
+ function decodeNativeSessionId(nativeSessionId) {
11172
+ const separatorIndex = nativeSessionId.lastIndexOf("@@");
11173
+ if (separatorIndex === -1) {
11174
+ return { threadId: nativeSessionId };
11175
+ }
11176
+ const threadId = nativeSessionId.slice(0, separatorIndex);
11177
+ const encodedHome = nativeSessionId.slice(separatorIndex + 2);
11178
+ try {
11179
+ const codexHome = Buffer.from(encodedHome, "base64url").toString("utf8");
11180
+ return { threadId, codexHome };
11181
+ } catch {
11182
+ return { threadId: nativeSessionId };
11183
+ }
11184
+ }
11185
+ function isFileNotFoundError(error) {
11186
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
11187
+ }
11188
+ async function copyIfExists(from, to) {
11189
+ try {
11190
+ await copyFile(from, to);
11191
+ } catch (error) {
11192
+ if (isFileNotFoundError(error)) {
11193
+ return;
11194
+ }
11195
+ throw error;
11196
+ }
11197
+ }
11082
11198
  var CodexAdapter = class extends BaseAdapter {
11083
11199
  id = "codex";
11084
11200
  command = "codex";
11201
+ getCodexHomePath() {
11202
+ return join2(homedir2(), ".codex");
11203
+ }
11204
+ getIsolatedCodexHomesPath() {
11205
+ return join2(getRelayHome(), "codex-homes");
11206
+ }
11085
11207
  getConfigPath() {
11086
- return join2(homedir2(), ".codex", "config.toml");
11208
+ return join2(this.getCodexHomePath(), "config.toml");
11087
11209
  }
11088
11210
  async checkAuthStatus() {
11089
11211
  const result = await this.processManager.execute(this.command, [
@@ -11096,46 +11218,74 @@ var CodexAdapter = class extends BaseAdapter {
11096
11218
  ...!authenticated ? { message: "codex authentication not configured" } : {}
11097
11219
  };
11098
11220
  }
11099
- buildCodexMcpConfig(mcpServers) {
11100
- if (!mcpServers || Object.keys(mcpServers).length === 0) {
11101
- return void 0;
11102
- }
11103
- const configServers = {};
11104
- for (const [name, server] of Object.entries(mcpServers)) {
11105
- if ("url" in server) {
11106
- configServers[name] = {
11107
- url: server.url,
11108
- ...server.headers ? { headers: server.headers } : {}
11109
- };
11110
- } else {
11111
- configServers[name] = {
11112
- command: server.command,
11113
- ...server.args ? { args: server.args } : {},
11114
- ...server.env ? { env: server.env } : {}
11115
- };
11221
+ async prepareIsolatedCodexHome(mcpServers) {
11222
+ const codexHomesDir = this.getIsolatedCodexHomesPath();
11223
+ await mkdir2(codexHomesDir, { recursive: true });
11224
+ const isolatedCodexHome = await mkdtemp(join2(codexHomesDir, "codex-"));
11225
+ const realCodexHome = this.getCodexHomePath();
11226
+ await copyIfExists(
11227
+ join2(realCodexHome, "auth.json"),
11228
+ join2(isolatedCodexHome, "auth.json")
11229
+ );
11230
+ await copyIfExists(
11231
+ join2(realCodexHome, "version.json"),
11232
+ join2(isolatedCodexHome, "version.json")
11233
+ );
11234
+ await copyIfExists(
11235
+ join2(realCodexHome, "internal_storage.json"),
11236
+ join2(isolatedCodexHome, "internal_storage.json")
11237
+ );
11238
+ await copyIfExists(
11239
+ join2(realCodexHome, ".codex-global-state.json"),
11240
+ join2(isolatedCodexHome, ".codex-global-state.json")
11241
+ );
11242
+ let existingConfig = "";
11243
+ try {
11244
+ existingConfig = await readFile2(this.getConfigPath(), "utf-8");
11245
+ } catch (error) {
11246
+ if (!isFileNotFoundError(error)) {
11247
+ throw error;
11116
11248
  }
11117
11249
  }
11118
- return { mcp_servers: configServers };
11250
+ const isolatedConfig = rewriteCodexConfigWithMcpServers(
11251
+ existingConfig,
11252
+ mcpServers
11253
+ );
11254
+ await writeFile2(join2(isolatedCodexHome, "config.toml"), isolatedConfig, {
11255
+ mode: 384
11256
+ });
11257
+ return isolatedCodexHome;
11119
11258
  }
11120
- buildCodexOptions(flags) {
11259
+ async buildCodexOptions(flags) {
11121
11260
  const options = {};
11122
- if (flags.mcpContext) {
11123
- const env = {};
11124
- for (const [key, value] of Object.entries(process.env)) {
11125
- if (value !== void 0) {
11126
- env[key] = value;
11261
+ let env;
11262
+ let isolatedCodexHome;
11263
+ const ensureEnv = () => {
11264
+ if (!env) {
11265
+ env = {};
11266
+ for (const [key, value] of Object.entries(process.env)) {
11267
+ if (value !== void 0) {
11268
+ env[key] = value;
11269
+ }
11127
11270
  }
11128
11271
  }
11129
- env.RELAY_TRACE_ID = flags.mcpContext.traceId;
11130
- env.RELAY_PARENT_SESSION_ID = flags.mcpContext.parentSessionId;
11131
- env.RELAY_DEPTH = String(flags.mcpContext.depth);
11132
- options.env = env;
11272
+ return env;
11273
+ };
11274
+ if (flags.mcpContext) {
11275
+ const resolvedEnv = ensureEnv();
11276
+ resolvedEnv.RELAY_TRACE_ID = flags.mcpContext.traceId;
11277
+ resolvedEnv.RELAY_PARENT_SESSION_ID = flags.mcpContext.parentSessionId;
11278
+ resolvedEnv.RELAY_DEPTH = String(flags.mcpContext.depth);
11133
11279
  }
11134
- const config = this.buildCodexMcpConfig(flags.mcpServers);
11135
- if (config) {
11136
- options.config = config;
11280
+ if (flags.mcpServers && Object.keys(flags.mcpServers).length > 0) {
11281
+ const resolvedEnv = ensureEnv();
11282
+ isolatedCodexHome = await this.prepareIsolatedCodexHome(flags.mcpServers);
11283
+ resolvedEnv.CODEX_HOME = isolatedCodexHome;
11137
11284
  }
11138
- return options;
11285
+ if (env) {
11286
+ options.env = env;
11287
+ }
11288
+ return { options, isolatedCodexHome };
11139
11289
  }
11140
11290
  mapFlags(flags) {
11141
11291
  const args = mapCommonToNative("codex", flags);
@@ -11189,20 +11339,35 @@ ${prompt}`;
11189
11339
  );
11190
11340
  try {
11191
11341
  const { Codex } = await loadCodexSDK();
11192
- const codex = new Codex(this.buildCodexOptions(flags));
11342
+ const { options, isolatedCodexHome } = await this.buildCodexOptions(flags);
11343
+ const codex = new Codex(options);
11193
11344
  const thread = codex.startThread({
11194
11345
  ...flags.model ? { model: flags.model } : {},
11195
11346
  workingDirectory: process.cwd(),
11196
11347
  approvalPolicy: "never"
11197
11348
  });
11198
- const result = await thread.run(effectivePrompt);
11349
+ const result = await thread.run(effectivePrompt, {
11350
+ ...flags.signal ? { signal: flags.signal } : {}
11351
+ });
11199
11352
  return {
11200
11353
  exitCode: 0,
11201
11354
  stdout: result.finalResponse,
11202
11355
  stderr: "",
11203
- ...thread.id ? { nativeSessionId: thread.id } : {}
11356
+ ...thread.id ? {
11357
+ nativeSessionId: encodeNativeSessionId(
11358
+ thread.id,
11359
+ isolatedCodexHome
11360
+ )
11361
+ } : {}
11204
11362
  };
11205
11363
  } catch (error) {
11364
+ if (flags.signal?.aborted) {
11365
+ return {
11366
+ exitCode: 1,
11367
+ stdout: "",
11368
+ stderr: "Codex execution cancelled: parent session disconnected"
11369
+ };
11370
+ }
11206
11371
  return {
11207
11372
  exitCode: 1,
11208
11373
  stdout: "",
@@ -11221,7 +11386,8 @@ ${prompt}`;
11221
11386
  );
11222
11387
  try {
11223
11388
  const { Codex } = await loadCodexSDK();
11224
- const codex = new Codex(this.buildCodexOptions(flags));
11389
+ const { options, isolatedCodexHome } = await this.buildCodexOptions(flags);
11390
+ const codex = new Codex(options);
11225
11391
  const thread = codex.startThread({
11226
11392
  ...flags.model ? { model: flags.model } : {},
11227
11393
  workingDirectory: process.cwd(),
@@ -11282,14 +11448,20 @@ ${prompt}`;
11282
11448
  yield {
11283
11449
  type: "done",
11284
11450
  result: { exitCode: 0, stdout: finalResponse, stderr: "" },
11285
- nativeSessionId: threadId ?? thread.id ?? void 0
11451
+ nativeSessionId: threadId || thread.id ? encodeNativeSessionId(
11452
+ threadId ?? thread.id ?? "",
11453
+ isolatedCodexHome
11454
+ ) : void 0
11286
11455
  };
11287
11456
  } else if (event.type === "turn.failed") {
11288
11457
  const errorMessage = event.error.message ?? "Turn failed";
11289
11458
  yield {
11290
11459
  type: "done",
11291
11460
  result: { exitCode: 1, stdout: "", stderr: errorMessage },
11292
- nativeSessionId: threadId ?? thread.id ?? void 0
11461
+ nativeSessionId: threadId || thread.id ? encodeNativeSessionId(
11462
+ threadId ?? thread.id ?? "",
11463
+ isolatedCodexHome
11464
+ ) : void 0
11293
11465
  };
11294
11466
  } else if (event.type === "error") {
11295
11467
  yield {
@@ -11310,8 +11482,20 @@ ${prompt}`;
11310
11482
  async continueSession(nativeSessionId, prompt) {
11311
11483
  try {
11312
11484
  const { Codex } = await loadCodexSDK();
11313
- const codex = new Codex();
11314
- const thread = codex.resumeThread(nativeSessionId, {
11485
+ 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, {
11315
11499
  workingDirectory: process.cwd(),
11316
11500
  approvalPolicy: "never"
11317
11501
  });
@@ -11650,7 +11834,7 @@ import { readFile as readFile4, writeFile as writeFile4, readdir, mkdir as mkdir
11650
11834
  import { join as join4 } from "path";
11651
11835
  import { homedir as homedir4 } from "os";
11652
11836
  import { nanoid } from "nanoid";
11653
- function getRelayHome() {
11837
+ function getRelayHome2() {
11654
11838
  return process.env["RELAY_HOME"] ?? join4(homedir4(), ".relay");
11655
11839
  }
11656
11840
  function getSessionsDir(relayHome2) {
@@ -11690,7 +11874,7 @@ var SessionManager = class _SessionManager {
11690
11874
  ]);
11691
11875
  sessionsDir;
11692
11876
  constructor(sessionsDir) {
11693
- this.sessionsDir = sessionsDir ?? getSessionsDir(getRelayHome());
11877
+ this.sessionsDir = sessionsDir ?? getSessionsDir(getRelayHome2());
11694
11878
  }
11695
11879
  getSessionsDir() {
11696
11880
  return this.sessionsDir;
@@ -13433,7 +13617,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
13433
13617
  responseOutputDir,
13434
13618
  relayConfig
13435
13619
  );
13436
- await server.start({ transport, port, currentVersion: "2.0.6" });
13620
+ await server.start({ transport, port, currentVersion: "2.0.8" });
13437
13621
  }
13438
13622
  })
13439
13623
  },
@@ -13593,7 +13777,7 @@ function createVersionCommand(registry2) {
13593
13777
  description: "Show relay and backend versions"
13594
13778
  },
13595
13779
  async run() {
13596
- const relayVersion = "2.0.6";
13780
+ const relayVersion = "2.0.8";
13597
13781
  console.log(`agentic-relay v${relayVersion}`);
13598
13782
  console.log("");
13599
13783
  console.log("Backends:");
@@ -13990,7 +14174,7 @@ var subCommandNames = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "upd
13990
14174
  var main = defineCommand11({
13991
14175
  meta: {
13992
14176
  name: "relay",
13993
- version: "2.0.6",
14177
+ version: "2.0.8",
13994
14178
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
13995
14179
  },
13996
14180
  args: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
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",