@rk0429/agentic-relay 2.0.5 → 2.0.7

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 +236 -39
  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 {
@@ -8442,7 +8442,7 @@ var init_server = __esm({
8442
8442
  this.agentEventStore
8443
8443
  );
8444
8444
  this.server = new McpServer(
8445
- { name: "agentic-relay", version: "2.0.5" },
8445
+ { name: "agentic-relay", version: "2.0.7" },
8446
8446
  createMcpServerOptions()
8447
8447
  );
8448
8448
  this.registerTools(this.server);
@@ -8460,6 +8460,8 @@ var init_server = __esm({
8460
8460
  purgeTimer;
8461
8461
  _childHttpServer;
8462
8462
  _childHttpUrl;
8463
+ _closePromise;
8464
+ _isClosed = false;
8463
8465
  /** URL for child agents to connect via HTTP. Available after start() in stdio mode. */
8464
8466
  get childHttpUrl() {
8465
8467
  return this._childHttpUrl;
@@ -8870,7 +8872,29 @@ var init_server = __esm({
8870
8872
  redirectToStderr();
8871
8873
  logger.info("Starting agentic-relay MCP server (stdio transport)...");
8872
8874
  const transport = new StdioServerTransport();
8873
- await this.server.connect(transport);
8875
+ const handleStdioDisconnect = () => {
8876
+ void transport.close().catch((error) => {
8877
+ const message = error instanceof Error ? error.message : String(error);
8878
+ logger.debug(`Failed to close stdio transport: ${message}`);
8879
+ void this.close();
8880
+ });
8881
+ };
8882
+ const cleanupStdioListeners = () => {
8883
+ process.stdin.off("end", handleStdioDisconnect);
8884
+ process.stdin.off("close", handleStdioDisconnect);
8885
+ };
8886
+ transport.onclose = () => {
8887
+ cleanupStdioListeners();
8888
+ void this.close();
8889
+ };
8890
+ process.stdin.once("end", handleStdioDisconnect);
8891
+ process.stdin.once("close", handleStdioDisconnect);
8892
+ try {
8893
+ await this.server.connect(transport);
8894
+ } catch (error) {
8895
+ cleanupStdioListeners();
8896
+ throw error;
8897
+ }
8874
8898
  await this.startChildHttpServer();
8875
8899
  return;
8876
8900
  }
@@ -8932,7 +8956,7 @@ var init_server = __esm({
8932
8956
  sessionIdGenerator: () => randomUUID()
8933
8957
  });
8934
8958
  const server = new McpServer(
8935
- { name: "agentic-relay", version: "2.0.5" },
8959
+ { name: "agentic-relay", version: "2.0.7" },
8936
8960
  createMcpServerOptions()
8937
8961
  );
8938
8962
  this.registerTools(server, childRelayContext);
@@ -8978,16 +9002,27 @@ var init_server = __esm({
8978
9002
  });
8979
9003
  }
8980
9004
  async close() {
8981
- this.sessionHealthMonitor.stop();
8982
- this.agentEventStore.cleanup();
8983
- clearInterval(this.purgeTimer);
8984
- if (this._childHttpServer) {
8985
- await new Promise((resolve3) => {
8986
- this._childHttpServer.close(() => resolve3());
8987
- });
8988
- this._childHttpServer = void 0;
8989
- this._childHttpUrl = void 0;
9005
+ if (this._isClosed) {
9006
+ return;
8990
9007
  }
9008
+ if (this._closePromise) {
9009
+ await this._closePromise;
9010
+ return;
9011
+ }
9012
+ this._closePromise = (async () => {
9013
+ this.sessionHealthMonitor.stop();
9014
+ this.agentEventStore.cleanup();
9015
+ clearInterval(this.purgeTimer);
9016
+ if (this._childHttpServer) {
9017
+ await new Promise((resolve3) => {
9018
+ this._childHttpServer.close(() => resolve3());
9019
+ });
9020
+ this._childHttpServer = void 0;
9021
+ this._childHttpUrl = void 0;
9022
+ }
9023
+ this._isClosed = true;
9024
+ })();
9025
+ await this._closePromise;
8991
9026
  }
8992
9027
  /** Exposed for testing and graceful shutdown */
8993
9028
  get httpServer() {
@@ -10934,7 +10969,7 @@ var ClaudeAdapter = class extends BaseAdapter {
10934
10969
 
10935
10970
  // src/adapters/codex-adapter.ts
10936
10971
  init_logger();
10937
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
10972
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, mkdtemp, copyFile } from "fs/promises";
10938
10973
  import { homedir as homedir2 } from "os";
10939
10974
  import { join as join2, dirname as dirname2 } from "path";
10940
10975
  async function loadCodexSDK() {
@@ -11031,6 +11066,12 @@ function toTOMLString(value) {
11031
11066
  function toTOMLStringArray(values) {
11032
11067
  return `[${values.map(toTOMLString).join(", ")}]`;
11033
11068
  }
11069
+ function toTOMLInlineTable(values) {
11070
+ return `{ ${Object.entries(values).map(([key, value]) => `${toTOMLString(key)} = ${toTOMLString(value)}`).join(", ")} }`;
11071
+ }
11072
+ function toTOMLTableKey(key) {
11073
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : toTOMLString(key);
11074
+ }
11034
11075
  function generateMcpServersTOML(servers) {
11035
11076
  const parts = [];
11036
11077
  for (const server of servers) {
@@ -11044,11 +11085,88 @@ function generateMcpServersTOML(servers) {
11044
11085
  }
11045
11086
  return parts.join("\n");
11046
11087
  }
11088
+ function generateChildMcpServersTOML(servers) {
11089
+ const parts = [];
11090
+ for (const [name, server] of Object.entries(servers)) {
11091
+ parts.push(`[mcp_servers.${toTOMLTableKey(name)}]`);
11092
+ if ("url" in server) {
11093
+ parts.push(`url = ${toTOMLString(server.url)}`);
11094
+ if (server.headers && Object.keys(server.headers).length > 0) {
11095
+ parts.push(`headers = ${toTOMLInlineTable(server.headers)}`);
11096
+ }
11097
+ } else {
11098
+ parts.push(`command = ${toTOMLString(server.command)}`);
11099
+ if (server.args && server.args.length > 0) {
11100
+ parts.push(`args = ${toTOMLStringArray(server.args)}`);
11101
+ }
11102
+ if (server.env && Object.keys(server.env).length > 0) {
11103
+ parts.push(`env = ${toTOMLInlineTable(server.env)}`);
11104
+ }
11105
+ }
11106
+ parts.push("");
11107
+ }
11108
+ return parts.join("\n");
11109
+ }
11110
+ function rewriteCodexConfigWithMcpServers(existingContent, mcpServers) {
11111
+ const parsed = parseTOMLMcpServers(existingContent);
11112
+ const preamble = parsed.preambleLines.join("\n").replace(/\n+$/, "");
11113
+ const mcpSection = generateChildMcpServersTOML(mcpServers);
11114
+ const postamble = parsed.postambleLines.join("\n").replace(/^\n+/, "");
11115
+ const parts = [];
11116
+ if (preamble) parts.push(preamble);
11117
+ if (mcpSection) parts.push(mcpSection);
11118
+ if (postamble) parts.push(postamble);
11119
+ let output = parts.join("\n\n");
11120
+ if (!output.endsWith("\n")) output += "\n";
11121
+ return output;
11122
+ }
11123
+ function getRelayHome() {
11124
+ return process.env["RELAY_HOME"] ?? join2(homedir2(), ".relay");
11125
+ }
11126
+ function encodeNativeSessionId(threadId, codexHome) {
11127
+ if (!codexHome) {
11128
+ return threadId;
11129
+ }
11130
+ return `${threadId}@@${Buffer.from(codexHome, "utf8").toString("base64url")}`;
11131
+ }
11132
+ function decodeNativeSessionId(nativeSessionId) {
11133
+ const separatorIndex = nativeSessionId.lastIndexOf("@@");
11134
+ if (separatorIndex === -1) {
11135
+ return { threadId: nativeSessionId };
11136
+ }
11137
+ const threadId = nativeSessionId.slice(0, separatorIndex);
11138
+ const encodedHome = nativeSessionId.slice(separatorIndex + 2);
11139
+ try {
11140
+ const codexHome = Buffer.from(encodedHome, "base64url").toString("utf8");
11141
+ return { threadId, codexHome };
11142
+ } catch {
11143
+ return { threadId: nativeSessionId };
11144
+ }
11145
+ }
11146
+ function isFileNotFoundError(error) {
11147
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
11148
+ }
11149
+ async function copyIfExists(from, to) {
11150
+ try {
11151
+ await copyFile(from, to);
11152
+ } catch (error) {
11153
+ if (isFileNotFoundError(error)) {
11154
+ return;
11155
+ }
11156
+ throw error;
11157
+ }
11158
+ }
11047
11159
  var CodexAdapter = class extends BaseAdapter {
11048
11160
  id = "codex";
11049
11161
  command = "codex";
11162
+ getCodexHomePath() {
11163
+ return join2(homedir2(), ".codex");
11164
+ }
11165
+ getIsolatedCodexHomesPath() {
11166
+ return join2(getRelayHome(), "codex-homes");
11167
+ }
11050
11168
  getConfigPath() {
11051
- return join2(homedir2(), ".codex", "config.toml");
11169
+ return join2(this.getCodexHomePath(), "config.toml");
11052
11170
  }
11053
11171
  async checkAuthStatus() {
11054
11172
  const result = await this.processManager.execute(this.command, [
@@ -11061,20 +11179,74 @@ var CodexAdapter = class extends BaseAdapter {
11061
11179
  ...!authenticated ? { message: "codex authentication not configured" } : {}
11062
11180
  };
11063
11181
  }
11064
- buildCodexOptions(flags) {
11065
- if (!flags.mcpContext) {
11066
- return {};
11182
+ async prepareIsolatedCodexHome(mcpServers) {
11183
+ const codexHomesDir = this.getIsolatedCodexHomesPath();
11184
+ await mkdir2(codexHomesDir, { recursive: true });
11185
+ const isolatedCodexHome = await mkdtemp(join2(codexHomesDir, "codex-"));
11186
+ const realCodexHome = this.getCodexHomePath();
11187
+ await copyIfExists(
11188
+ join2(realCodexHome, "auth.json"),
11189
+ join2(isolatedCodexHome, "auth.json")
11190
+ );
11191
+ await copyIfExists(
11192
+ join2(realCodexHome, "version.json"),
11193
+ join2(isolatedCodexHome, "version.json")
11194
+ );
11195
+ await copyIfExists(
11196
+ join2(realCodexHome, "internal_storage.json"),
11197
+ join2(isolatedCodexHome, "internal_storage.json")
11198
+ );
11199
+ await copyIfExists(
11200
+ join2(realCodexHome, ".codex-global-state.json"),
11201
+ join2(isolatedCodexHome, ".codex-global-state.json")
11202
+ );
11203
+ let existingConfig = "";
11204
+ try {
11205
+ existingConfig = await readFile2(this.getConfigPath(), "utf-8");
11206
+ } catch (error) {
11207
+ if (!isFileNotFoundError(error)) {
11208
+ throw error;
11209
+ }
11067
11210
  }
11068
- const env = {};
11069
- for (const [key, value] of Object.entries(process.env)) {
11070
- if (value !== void 0) {
11071
- env[key] = value;
11211
+ const isolatedConfig = rewriteCodexConfigWithMcpServers(
11212
+ existingConfig,
11213
+ mcpServers
11214
+ );
11215
+ await writeFile2(join2(isolatedCodexHome, "config.toml"), isolatedConfig, {
11216
+ mode: 384
11217
+ });
11218
+ return isolatedCodexHome;
11219
+ }
11220
+ async buildCodexOptions(flags) {
11221
+ const options = {};
11222
+ let env;
11223
+ let isolatedCodexHome;
11224
+ const ensureEnv = () => {
11225
+ if (!env) {
11226
+ env = {};
11227
+ for (const [key, value] of Object.entries(process.env)) {
11228
+ if (value !== void 0) {
11229
+ env[key] = value;
11230
+ }
11231
+ }
11072
11232
  }
11233
+ return env;
11234
+ };
11235
+ if (flags.mcpContext) {
11236
+ const resolvedEnv = ensureEnv();
11237
+ resolvedEnv.RELAY_TRACE_ID = flags.mcpContext.traceId;
11238
+ resolvedEnv.RELAY_PARENT_SESSION_ID = flags.mcpContext.parentSessionId;
11239
+ resolvedEnv.RELAY_DEPTH = String(flags.mcpContext.depth);
11240
+ }
11241
+ if (flags.mcpServers && Object.keys(flags.mcpServers).length > 0) {
11242
+ const resolvedEnv = ensureEnv();
11243
+ isolatedCodexHome = await this.prepareIsolatedCodexHome(flags.mcpServers);
11244
+ resolvedEnv.CODEX_HOME = isolatedCodexHome;
11073
11245
  }
11074
- env.RELAY_TRACE_ID = flags.mcpContext.traceId;
11075
- env.RELAY_PARENT_SESSION_ID = flags.mcpContext.parentSessionId;
11076
- env.RELAY_DEPTH = String(flags.mcpContext.depth);
11077
- return { env };
11246
+ if (env) {
11247
+ options.env = env;
11248
+ }
11249
+ return { options, isolatedCodexHome };
11078
11250
  }
11079
11251
  mapFlags(flags) {
11080
11252
  const args = mapCommonToNative("codex", flags);
@@ -11128,7 +11300,8 @@ ${prompt}`;
11128
11300
  );
11129
11301
  try {
11130
11302
  const { Codex } = await loadCodexSDK();
11131
- const codex = new Codex(this.buildCodexOptions(flags));
11303
+ const { options, isolatedCodexHome } = await this.buildCodexOptions(flags);
11304
+ const codex = new Codex(options);
11132
11305
  const thread = codex.startThread({
11133
11306
  ...flags.model ? { model: flags.model } : {},
11134
11307
  workingDirectory: process.cwd(),
@@ -11139,7 +11312,12 @@ ${prompt}`;
11139
11312
  exitCode: 0,
11140
11313
  stdout: result.finalResponse,
11141
11314
  stderr: "",
11142
- ...thread.id ? { nativeSessionId: thread.id } : {}
11315
+ ...thread.id ? {
11316
+ nativeSessionId: encodeNativeSessionId(
11317
+ thread.id,
11318
+ isolatedCodexHome
11319
+ )
11320
+ } : {}
11143
11321
  };
11144
11322
  } catch (error) {
11145
11323
  return {
@@ -11160,7 +11338,8 @@ ${prompt}`;
11160
11338
  );
11161
11339
  try {
11162
11340
  const { Codex } = await loadCodexSDK();
11163
- const codex = new Codex(this.buildCodexOptions(flags));
11341
+ const { options, isolatedCodexHome } = await this.buildCodexOptions(flags);
11342
+ const codex = new Codex(options);
11164
11343
  const thread = codex.startThread({
11165
11344
  ...flags.model ? { model: flags.model } : {},
11166
11345
  workingDirectory: process.cwd(),
@@ -11221,14 +11400,20 @@ ${prompt}`;
11221
11400
  yield {
11222
11401
  type: "done",
11223
11402
  result: { exitCode: 0, stdout: finalResponse, stderr: "" },
11224
- nativeSessionId: threadId ?? thread.id ?? void 0
11403
+ nativeSessionId: threadId || thread.id ? encodeNativeSessionId(
11404
+ threadId ?? thread.id ?? "",
11405
+ isolatedCodexHome
11406
+ ) : void 0
11225
11407
  };
11226
11408
  } else if (event.type === "turn.failed") {
11227
11409
  const errorMessage = event.error.message ?? "Turn failed";
11228
11410
  yield {
11229
11411
  type: "done",
11230
11412
  result: { exitCode: 1, stdout: "", stderr: errorMessage },
11231
- nativeSessionId: threadId ?? thread.id ?? void 0
11413
+ nativeSessionId: threadId || thread.id ? encodeNativeSessionId(
11414
+ threadId ?? thread.id ?? "",
11415
+ isolatedCodexHome
11416
+ ) : void 0
11232
11417
  };
11233
11418
  } else if (event.type === "error") {
11234
11419
  yield {
@@ -11249,8 +11434,20 @@ ${prompt}`;
11249
11434
  async continueSession(nativeSessionId, prompt) {
11250
11435
  try {
11251
11436
  const { Codex } = await loadCodexSDK();
11252
- const codex = new Codex();
11253
- const thread = codex.resumeThread(nativeSessionId, {
11437
+ const { threadId, codexHome } = decodeNativeSessionId(nativeSessionId);
11438
+ const codex = new Codex(
11439
+ codexHome ? {
11440
+ env: {
11441
+ ...Object.fromEntries(
11442
+ Object.entries(process.env).filter(
11443
+ ([, value]) => value !== void 0
11444
+ )
11445
+ ),
11446
+ CODEX_HOME: codexHome
11447
+ }
11448
+ } : {}
11449
+ );
11450
+ const thread = codex.resumeThread(threadId, {
11254
11451
  workingDirectory: process.cwd(),
11255
11452
  approvalPolicy: "never"
11256
11453
  });
@@ -11589,7 +11786,7 @@ import { readFile as readFile4, writeFile as writeFile4, readdir, mkdir as mkdir
11589
11786
  import { join as join4 } from "path";
11590
11787
  import { homedir as homedir4 } from "os";
11591
11788
  import { nanoid } from "nanoid";
11592
- function getRelayHome() {
11789
+ function getRelayHome2() {
11593
11790
  return process.env["RELAY_HOME"] ?? join4(homedir4(), ".relay");
11594
11791
  }
11595
11792
  function getSessionsDir(relayHome2) {
@@ -11629,7 +11826,7 @@ var SessionManager = class _SessionManager {
11629
11826
  ]);
11630
11827
  sessionsDir;
11631
11828
  constructor(sessionsDir) {
11632
- this.sessionsDir = sessionsDir ?? getSessionsDir(getRelayHome());
11829
+ this.sessionsDir = sessionsDir ?? getSessionsDir(getRelayHome2());
11633
11830
  }
11634
11831
  getSessionsDir() {
11635
11832
  return this.sessionsDir;
@@ -13372,7 +13569,7 @@ function createMCPCommand(configManager2, registry2, sessionManager2, hooksEngin
13372
13569
  responseOutputDir,
13373
13570
  relayConfig
13374
13571
  );
13375
- await server.start({ transport, port, currentVersion: "2.0.5" });
13572
+ await server.start({ transport, port, currentVersion: "2.0.7" });
13376
13573
  }
13377
13574
  })
13378
13575
  },
@@ -13532,7 +13729,7 @@ function createVersionCommand(registry2) {
13532
13729
  description: "Show relay and backend versions"
13533
13730
  },
13534
13731
  async run() {
13535
- const relayVersion = "2.0.5";
13732
+ const relayVersion = "2.0.7";
13536
13733
  console.log(`agentic-relay v${relayVersion}`);
13537
13734
  console.log("");
13538
13735
  console.log("Backends:");
@@ -13929,7 +14126,7 @@ var subCommandNames = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "upd
13929
14126
  var main = defineCommand11({
13930
14127
  meta: {
13931
14128
  name: "relay",
13932
- version: "2.0.5",
14129
+ version: "2.0.7",
13933
14130
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
13934
14131
  },
13935
14132
  args: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
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",