agent-relay-codex 0.4.25 → 0.4.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,9 +126,9 @@ agent-relay-codex uninstall --purge # also remove runtime state and PATH entrie
126
126
 
127
127
  ## How it works
128
128
 
129
- `codex-relay` launches `codex app-server`, starts the live sidecar as the long-lived App Server client, waits for it to resolve one canonical Codex thread, then opens the TUI with `codex resume <thread-id> --remote <app-server-url>`. Agent Relay messages and direct TUI messages therefore share the same Codex context.
129
+ `codex-relay` launches `codex app-server`, then opens the TUI with `codex --remote <app-server-url>`. The normal SessionStart hook registers the visible TUI thread with Agent Relay, so Agent Relay messages and direct TUI messages share the same Codex context.
130
130
 
131
- Use `codex-relay --headless` on servers where Agent Relay is the only interaction surface. It starts the same app-server and sidecar session but does not open a TUI; the launcher prints the attach command if you later want to connect one.
131
+ Use `codex-relay --headless` on servers where Agent Relay is the only interaction surface. It starts the same app-server and sidecar session but does not open a TUI; the launcher prints `codex resume --remote <app-server-url>` if you later want to attach one.
132
132
 
133
133
  The installed SessionStart hook remains for plain `codex` launches. In that mode, the hook registers the visible Codex session with Agent Relay without requiring the managed `codex-relay` launcher.
134
134
 
@@ -5,6 +5,7 @@ import { dirname, join, resolve } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import net from "node:net";
8
+ import { CodexAppClient } from "../app-client";
8
9
  import { approvalModeFromPermissions, codexArgsForApprovalMode, parseApprovalMode } from "../approval";
9
10
  import { loadAgentRelayProfile } from "../profile";
10
11
 
@@ -43,6 +44,7 @@ Usage:
43
44
  agent-relay-codex alias install
44
45
  agent-relay-codex alias remove
45
46
  agent-relay-codex doctor
47
+ agent-relay-codex upgrade [--dry-run] [--version VERSION] [--no-restart] [--yes]
46
48
  agent-relay-codex start [--headless] [--relay-url URL] [--listen ws://127.0.0.1:PORT] [--thread-mode auto|resume|start] [--thread-id ID] [-- <codex args...>]
47
49
  codex-relay [--headless] [--relay-url URL] [--listen ws://127.0.0.1:PORT] [--thread-mode auto|resume|start] [--thread-id ID] [-- <codex args...>]
48
50
 
@@ -536,6 +538,41 @@ async function waitForManagedAgent(statePath: string, sidecarPid: number, timeou
536
538
  throw new Error(`timed out waiting for managed sidecar to register with Agent Relay; inspect ${dirname(statePath)}/sidecar.log`);
537
539
  }
538
540
 
541
+ async function loadedThreadIds(listenUrl: string): Promise<string[]> {
542
+ const client = new CodexAppClient(listenUrl);
543
+ try {
544
+ await client.connect();
545
+ await client.initialize();
546
+ const loaded = await client.threadLoadedList(50);
547
+ return loaded.data.filter((id) => typeof id === "string" && id.trim());
548
+ } finally {
549
+ client.close();
550
+ }
551
+ }
552
+
553
+ async function waitForRemoteTuiThread(
554
+ listenUrl: string,
555
+ beforeIds: Set<string>,
556
+ codex: ReturnType<typeof Bun.spawn>,
557
+ timeoutMs = 15000,
558
+ ): Promise<string | null> {
559
+ const deadline = Date.now() + timeoutMs;
560
+
561
+ while (Date.now() < deadline) {
562
+ try {
563
+ const loaded = await loadedThreadIds(listenUrl);
564
+ const candidate = loaded.find((id) => !beforeIds.has(id)) ?? loaded[0];
565
+ if (candidate) return candidate;
566
+ } catch {
567
+ // The remote TUI may still be negotiating its app-server connection.
568
+ }
569
+ if (codex.exitCode !== null) return null;
570
+ await Bun.sleep(250);
571
+ }
572
+
573
+ return null;
574
+ }
575
+
539
576
  function cleanupRun(runDir: string, appServer: ReturnType<typeof Bun.spawn> | null): void {
540
577
  if (existsSync(runDir)) {
541
578
  const pidsPath = join(runDir, "sidecar-pids.txt");
@@ -777,7 +814,7 @@ async function start(args: string[]): Promise<void> {
777
814
  installCodexSupport(true);
778
815
 
779
816
  let relayUrl = process.env.AGENT_RELAY_URL || "http://127.0.0.1:4850";
780
- let listenUrl = process.env.CODEX_APP_SERVER_URL || "";
817
+ let listenUrl = process.env.AGENT_RELAY_CODEX_LISTEN || "";
781
818
  let threadMode = process.env.CODEX_THREAD_MODE || "start";
782
819
  let headless = process.env.AGENT_RELAY_CODEX_HEADLESS === "1";
783
820
  let threadId = process.env.CODEX_THREAD_ID || "";
@@ -853,7 +890,6 @@ async function start(args: string[]): Promise<void> {
853
890
  AGENT_RELAY_CODEX_APPROVAL_POLICY: permissions.approvalPolicy,
854
891
  AGENT_RELAY_CODEX_SANDBOX: permissions.sandbox,
855
892
  AGENT_RELAY_APPROVAL: effectiveApprovalMode,
856
- AGENT_RELAY_CODEX_MANAGED: "1",
857
893
  AGENT_RELAY_CODEX_PARENT_PID: String(process.pid),
858
894
  };
859
895
 
@@ -877,12 +913,61 @@ async function start(args: string[]): Promise<void> {
877
913
  process.once("exit", shutdown);
878
914
 
879
915
  await waitForPort(listenUrl, appServer);
916
+
917
+ console.log(`Agent Relay Codex session: ${listenUrl}`);
918
+ console.log(`Runtime: ${runDir}`);
919
+
920
+ if (!headless) {
921
+ const loadedBefore = new Set(await loadedThreadIds(listenUrl).catch(() => []));
922
+ const codex = Bun.spawn([codexBinary, "--remote", listenUrl, ...codexArgs], {
923
+ env,
924
+ stdin: "inherit",
925
+ stdout: "inherit",
926
+ stderr: "inherit",
927
+ });
928
+
929
+ const tuiThreadId = await waitForRemoteTuiThread(listenUrl, loadedBefore, codex);
930
+ if (tuiThreadId) {
931
+ const sidecarDir = join(runDir, sanitizeSessionKey(tuiThreadId));
932
+ const statePath = join(sidecarDir, "live-state.json");
933
+ const sidecarPid = spawnManagedSidecar({
934
+ runDir,
935
+ env: { ...env, AGENT_RELAY_CODEX_MANAGED: "1" },
936
+ sessionDir: sidecarDir,
937
+ statePath,
938
+ threadMode: "resume",
939
+ threadId: tuiThreadId,
940
+ });
941
+ appendLauncherLog(runDir, `REMOTE_TUI_THREAD_BOUND thread=${tuiThreadId} sidecarPid=${sidecarPid}`);
942
+ } else {
943
+ const fallbackDir = join(runDir, "auto");
944
+ const fallbackStatePath = join(fallbackDir, "live-state.json");
945
+ const fallbackPid = spawnManagedSidecar({
946
+ runDir,
947
+ env: { ...env, AGENT_RELAY_CODEX_MANAGED: "1" },
948
+ sessionDir: fallbackDir,
949
+ statePath: fallbackStatePath,
950
+ threadMode,
951
+ threadId: threadId || undefined,
952
+ });
953
+ appendLauncherLog(runDir, `REMOTE_TUI_FALLBACK_STARTED reason=THREAD_DISCOVERY_TIMEOUT sidecarPid=${fallbackPid}`);
954
+ }
955
+
956
+ const exitCode = await codex.exited;
957
+ shutdown();
958
+ process.exit(exitCode);
959
+ }
960
+
961
+ const managedSidecarEnv = {
962
+ ...env,
963
+ AGENT_RELAY_CODEX_MANAGED: "1",
964
+ };
880
965
  const initialSessionKey = threadId ? sanitizeSessionKey(threadId) : "managed";
881
966
  const sidecarDir = join(runDir, initialSessionKey);
882
967
  const statePath = join(sidecarDir, "live-state.json");
883
968
  const sidecarPid = spawnManagedSidecar({
884
969
  runDir,
885
- env,
970
+ env: managedSidecarEnv,
886
971
  sessionDir: sidecarDir,
887
972
  statePath,
888
973
  threadMode,
@@ -890,34 +975,14 @@ async function start(args: string[]): Promise<void> {
890
975
  });
891
976
  const managedAgent = await waitForManagedAgent(statePath, sidecarPid);
892
977
  const canonicalThreadId = managedAgent.threadId;
893
- const managedEnv = {
894
- ...env,
895
- CODEX_THREAD_MODE: "resume",
896
- CODEX_THREAD_ID: canonicalThreadId,
897
- };
898
978
 
899
- console.log(`Agent Relay Codex session: ${listenUrl}`);
900
979
  console.log(`Thread: ${canonicalThreadId}`);
901
980
  console.log(`Agent: ${managedAgent.agentId}`);
902
- console.log(`Runtime: ${runDir}`);
903
981
  appendLauncherLog(runDir, `MANAGED_THREAD thread=${canonicalThreadId} agent=${managedAgent.agentId} sidecarPid=${sidecarPid} headless=${headless}`);
904
982
 
905
- if (headless) {
906
- console.log(`Headless relay sidecar pid: ${sidecarPid}`);
907
- console.log(`Attach TUI with: ${codexBinary} resume ${canonicalThreadId} --remote ${listenUrl}`);
908
- const exitCode = await appServer.exited;
909
- shutdown();
910
- process.exit(exitCode);
911
- }
912
-
913
- const codex = Bun.spawn([codexBinary, "resume", canonicalThreadId, "--remote", listenUrl, ...codexArgs], {
914
- env: managedEnv,
915
- stdin: "inherit",
916
- stdout: "inherit",
917
- stderr: "inherit",
918
- });
919
-
920
- const exitCode = await codex.exited;
983
+ console.log(`Headless relay sidecar pid: ${sidecarPid}`);
984
+ console.log(`Attach TUI with: ${codexBinary} resume --remote ${listenUrl}`);
985
+ const exitCode = await appServer.exited;
921
986
  shutdown();
922
987
  process.exit(exitCode);
923
988
  }
@@ -941,6 +1006,11 @@ async function doctor(): Promise<void> {
941
1006
  }
942
1007
  }
943
1008
 
1009
+ function upgrade(args: string[]): void {
1010
+ const hasProviderOverride = args.some((arg) => arg === "--providers" || arg === "--provider" || arg === "--codex" || arg === "--claude" || arg === "--all");
1011
+ runChecked(["agent-relay", "upgrade", ...(hasProviderOverride ? [] : ["--providers", "codex"]), ...args]);
1012
+ }
1013
+
944
1014
  async function install(args: string[]): Promise<void> {
945
1015
  const installAlias = args.includes("--alias");
946
1016
  const skipAlias = args.includes("--no-alias");
@@ -1000,6 +1070,7 @@ async function main(): Promise<void> {
1000
1070
  }
1001
1071
  if (command === "alias" && args[0] === "remove") return removeCodexAlias();
1002
1072
  if (command === "doctor") return doctor();
1073
+ if (command === "upgrade") return upgrade(args);
1003
1074
  if (command === "start") return start(args);
1004
1075
  return start(command ? [command, ...args] : []);
1005
1076
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-codex",
3
- "version": "0.4.25",
3
+ "version": "0.4.26",
4
4
  "description": "Codex integration for Agent Relay — auto-registers sessions as agents and enables inter-agent messaging",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "0.4.25",
3
+ "version": "0.4.26",
4
4
  "description": "Agent Relay integration for Codex sessions",
5
5
  "author": {
6
6
  "name": "Edin Mujkanovic"