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 +11 -3
- package/bin/agent-relay-codex.ts +22 -3
- package/codex/README.md +116 -0
- package/codex/smoke/fallback.ts +4 -5
- package/package.json +1 -1
- package/src/index.ts +0 -0
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
|
|
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`
|
|
86
|
-
`--
|
|
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
|
package/bin/agent-relay-codex.ts
CHANGED
|
@@ -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.
|
|
631
|
-
console.
|
|
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,
|
package/codex/README.md
ADDED
|
@@ -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.
|
package/codex/smoke/fallback.ts
CHANGED
|
@@ -48,8 +48,8 @@ exit 64
|
|
|
48
48
|
chmodSync(path, 0o755);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function parseRuntimeDir(
|
|
52
|
-
const match =
|
|
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
package/src/index.ts
CHANGED
|
File without changes
|