agent-relay-server 0.3.8 → 0.3.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.
package/README.md CHANGED
@@ -77,16 +77,24 @@ codex-relay
77
77
  incoming messages as live turns, and cleans up sidecar processes when Codex
78
78
  exits.
79
79
 
80
- ### Codex approval prompts
80
+ ### Codex approval mode
81
81
 
82
82
  Replying to relay messages is usually done with a shell command (`curl` to
83
83
  `/api/messages`), so Codex may prompt for approval in stricter modes.
84
84
 
85
- `codex-relay` now forwards your Codex runtime mode to the sidecar, including
86
- `--ask-for-approval`, `--sandbox`, `--full-auto`, and `--yolo`.
85
+ By default, `codex-relay` starts Codex with
86
+ `--dangerously-bypass-approvals-and-sandbox` so relay turns do not get stuck on
87
+ approval prompts. If you pass an explicit Codex runtime mode, `codex-relay`
88
+ leaves it alone and forwards it to the sidecar, including `--ask-for-approval`,
89
+ `--sandbox`, `--full-auto`, and `--yolo`.
87
90
 
88
91
  Useful setups:
89
92
 
93
+ ```bash
94
+ # default: no approval prompts, no Codex sandbox
95
+ codex-relay
96
+ ```
97
+
90
98
  ```bash
91
99
  # no approval prompts, still sandboxed to workspace boundaries
92
100
  codex-relay -- --ask-for-approval never --sandbox workspace-write
@@ -383,6 +383,25 @@ type SessionPermissions = {
383
383
  sandbox?: string;
384
384
  };
385
385
 
386
+ function hasCodexPermissionMode(codexArgs: string[]): boolean {
387
+ for (const arg of codexArgs) {
388
+ if (
389
+ arg === "--yolo" ||
390
+ arg === "--dangerously-bypass-approvals-and-sandbox" ||
391
+ arg === "--full-auto" ||
392
+ arg === "--ask-for-approval" ||
393
+ arg === "-a" ||
394
+ arg.startsWith("--ask-for-approval=") ||
395
+ arg === "--sandbox" ||
396
+ arg === "-s" ||
397
+ arg.startsWith("--sandbox=")
398
+ ) {
399
+ return true;
400
+ }
401
+ }
402
+ return false;
403
+ }
404
+
386
405
  function resolveSessionPermissions(codexArgs: string[]): SessionPermissions {
387
406
  let approvalPolicy: string | undefined;
388
407
  let sandbox: string | undefined;
@@ -488,7 +507,6 @@ function installLauncherShims(includeCodexAlias: boolean): void {
488
507
  mkdirSync(aliasBinDir, { recursive: true });
489
508
  writeLauncherShim("codex-relay");
490
509
  if (includeCodexAlias) writeLauncherShim("codex");
491
- else removeLauncherShim("codex");
492
510
  }
493
511
 
494
512
  function isAliasBinOnPath(): boolean {
@@ -589,6 +607,7 @@ async function start(args: string[]): Promise<void> {
589
607
  }
590
608
 
591
609
  if (!listenUrl) listenUrl = await pickLoopbackUrl();
610
+ if (!hasCodexPermissionMode(codexArgs)) codexArgs.unshift("--dangerously-bypass-approvals-and-sandbox");
592
611
  const permissions = resolveSessionPermissions(codexArgs);
593
612
 
594
613
  mkdirSync(runtimeRoot, { recursive: true });
@@ -627,8 +646,8 @@ async function start(args: string[]): Promise<void> {
627
646
  process.once("exit", shutdown);
628
647
 
629
648
  await waitForPort(listenUrl, appServer);
630
- console.error(`Agent Relay Codex session: ${listenUrl}`);
631
- console.error(`Runtime: ${runDir}`);
649
+ console.log(`Agent Relay Codex session: ${listenUrl}`);
650
+ console.log(`Runtime: ${runDir}`);
632
651
 
633
652
  const codex = Bun.spawn([codexBinary, "--remote", listenUrl, ...codexArgs], {
634
653
  env,
@@ -0,0 +1,116 @@
1
+ # Codex Live Sidecar
2
+
3
+ Codex integration for Agent Relay.
4
+
5
+ ## Purpose
6
+
7
+ This sidecar connects to a Codex app-server session and to Agent Relay, then delivers incoming relay messages into the active Codex thread using:
8
+
9
+ - `turn/start`
10
+ - `turn/steer`
11
+ - `turn/interrupt`
12
+
13
+ ## Current behavior
14
+
15
+ - attaches to a loaded thread for the current `cwd` when one exists
16
+ - otherwise resumes the newest thread for the current `cwd`
17
+ - otherwise creates a new thread
18
+ - registers a relay agent with `client: codex-live`
19
+ - marks the relay agent `ready=true` once app-server + thread are attached
20
+ - polls relay inbox and delivers messages into the live thread
21
+ - coalesces ordinary relay bursts into one delivery turn
22
+ - reconnects to the app-server with exponential backoff after disconnects
23
+ - writes runtime state to `codex/runtime/live-state.json`
24
+
25
+ ## Delivery behavior
26
+
27
+ - idle thread: `turn/start`
28
+ - active thread: `turn/steer`
29
+ - urgent or `meta.delivery = "interrupt"`: `turn/interrupt` then `turn/start`
30
+
31
+ ## Run
32
+
33
+ ```bash
34
+ codex/start-live.sh
35
+ ```
36
+
37
+ ## Installable workflow
38
+
39
+ The packaged Codex path is:
40
+
41
+ ```bash
42
+ bunx agent-relay-server@latest
43
+ curl -fsSL https://unpkg.com/agent-relay-server@latest/codex/install-codex.sh | bash
44
+ # after restarting your shell
45
+ codex-relay
46
+ ```
47
+
48
+ The installer always adds a `codex-relay` launcher and asks whether plain
49
+ `codex` should also route through Agent Relay. `codex-relay` idempotently
50
+ installs or refreshes the Codex hook/plugin, then launches `codex app-server`,
51
+ starts Codex with
52
+ `--remote`, lets the SessionStart hook attach a sidecar to the actual thread,
53
+ and kills sidecars plus the app-server when Codex exits.
54
+
55
+ ## Approval mode
56
+
57
+ Relay replies are usually sent with a shell command (`curl` to
58
+ `/api/messages`), so Codex can prompt for approval in stricter modes.
59
+
60
+ By default, `codex-relay` starts Codex with
61
+ `--dangerously-bypass-approvals-and-sandbox` so relay turns do not get stuck on
62
+ approval prompts. If you pass an explicit Codex runtime mode, `codex-relay`
63
+ leaves it alone and forwards it to the sidecar, including `--ask-for-approval`,
64
+ `--sandbox`, `--full-auto`, and `--yolo`.
65
+
66
+ Default:
67
+
68
+ ```bash
69
+ codex-relay
70
+ ```
71
+
72
+ Example: no prompt loop, still workspace sandboxing:
73
+
74
+ ```bash
75
+ codex-relay -- --ask-for-approval never --sandbox workspace-write
76
+ ```
77
+
78
+ If you prefer prompts for everything else but want relay sends auto-approved,
79
+ add a rule in `~/.codex/rules/default.rules` (adjust URL when using a remote relay):
80
+
81
+ ```python
82
+ prefix_rule(
83
+ pattern = ["curl", "-sS", "-X", "POST", "http://127.0.0.1:4850/api/messages"],
84
+ decision = "allow",
85
+ justification = "Allow local Agent Relay message posts",
86
+ )
87
+ ```
88
+
89
+ For local development from this repo:
90
+
91
+ ```bash
92
+ bun run bin/agent-relay-codex.ts
93
+ bun run codex:smoke:fallback
94
+ ```
95
+
96
+ Useful environment variables:
97
+
98
+ - `AGENT_RELAY_URL`
99
+ - `AGENT_RELAY_CAPS`
100
+ - `CODEX_APP_SERVER_URL`
101
+ - `CODEX_THREAD_ID`
102
+ - `CODEX_THREAD_MODE=auto|resume|start`
103
+ - `CODEX_LIVE_STATE_PATH`
104
+ - `CODEX_LIVE_COALESCE_WINDOW_MS`
105
+ - `CODEX_LIVE_RECONNECT_INITIAL_MS`
106
+ - `CODEX_LIVE_RECONNECT_MAX_MS`
107
+ - `CODEX_LIVE_RIG`
108
+ - `CODEX_MODEL`
109
+
110
+ ## Notes
111
+
112
+ Current sidecar behavior is stable for live delivery. Remaining gaps are advanced policies such as batching by sender, message prioritization queues, and more nuanced retry/backoff behavior.
113
+
114
+ - `CODEX_THREAD_MODE=auto` will attach to an already loaded thread for the same `cwd`. That is what you want for real live control, but it also means the sidecar can attach to your current interactive Codex session if one is already open.
115
+ - For isolated testing, set `CODEX_THREAD_MODE=start` so the sidecar always creates its own thread.
116
+ - A brand-new thread is not materialized for `includeTurns` reads until the first turn starts. That is an app-server behavior, not a relay bug.
@@ -48,8 +48,8 @@ exit 64
48
48
  chmodSync(path, 0o755);
49
49
  }
50
50
 
51
- function parseRuntimeDir(stderr: string): string | null {
52
- const match = stderr.match(/^Runtime:\s+(.+)$/m);
51
+ function parseRuntimeDir(output: string): string | null {
52
+ const match = output.match(/^Runtime:\s+(.+)$/m);
53
53
  return match?.[1]?.trim() || null;
54
54
  }
55
55
 
@@ -85,9 +85,9 @@ async function main(): Promise<void> {
85
85
  throw new Error(`launcher exited ${exitCode}\nstdout:\n${stdout}\nstderr:\n${stderr}`);
86
86
  }
87
87
 
88
- const runtimeDir = parseRuntimeDir(stderr);
88
+ const runtimeDir = parseRuntimeDir(`${stdout}\n${stderr}`);
89
89
  if (!runtimeDir) {
90
- throw new Error(`missing runtime directory in launcher output\nstderr:\n${stderr}`);
90
+ throw new Error(`missing runtime directory in launcher output\nstdout:\n${stdout}\nstderr:\n${stderr}`);
91
91
  }
92
92
 
93
93
  if (!stderr.includes("SessionStart hook did not start sidecar; started fallback sidecar pid")) {
@@ -122,4 +122,3 @@ main().catch((error) => {
122
122
  log(error instanceof Error ? error.stack || error.message : String(error));
123
123
  process.exitCode = 1;
124
124
  });
125
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/index.ts CHANGED
File without changes