agent-relay-server 0.3.5 → 0.3.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.
- package/README.md +25 -0
- package/bin/agent-relay-codex.ts +53 -0
- package/codex/README.md +25 -0
- package/codex/live-sidecar.ts +15 -8
- package/codex/plugin/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/public/index.html +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,31 @@ 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
|
|
81
|
+
|
|
82
|
+
Replying to relay messages is usually done with a shell command (`curl` to
|
|
83
|
+
`/api/messages`), so Codex may prompt for approval in stricter modes.
|
|
84
|
+
|
|
85
|
+
`codex-relay` now forwards your Codex runtime mode to the sidecar, including
|
|
86
|
+
`--ask-for-approval`, `--sandbox`, `--full-auto`, and `--yolo`.
|
|
87
|
+
|
|
88
|
+
Useful setups:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# no approval prompts, still sandboxed to workspace boundaries
|
|
92
|
+
codex-relay -- --ask-for-approval never --sandbox workspace-write
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# ~/.codex/rules/default.rules
|
|
97
|
+
# allow only relay message sends without repeated prompts
|
|
98
|
+
prefix_rule(
|
|
99
|
+
pattern = ["curl", "-sS", "-X", "POST", "http://127.0.0.1:4850/api/messages"],
|
|
100
|
+
decision = "allow",
|
|
101
|
+
justification = "Allow local Agent Relay message posts",
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
80
105
|
Use a remote relay server by setting:
|
|
81
106
|
|
|
82
107
|
```bash
|
package/bin/agent-relay-codex.ts
CHANGED
|
@@ -329,6 +329,56 @@ function spawnFallbackSidecar(runDir: string, env: Record<string, string | undef
|
|
|
329
329
|
return sidecar.pid;
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
type SessionPermissions = {
|
|
333
|
+
approvalPolicy?: string;
|
|
334
|
+
sandbox?: string;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
function resolveSessionPermissions(codexArgs: string[]): SessionPermissions {
|
|
338
|
+
let approvalPolicy: string | undefined;
|
|
339
|
+
let sandbox: string | undefined;
|
|
340
|
+
|
|
341
|
+
for (let index = 0; index < codexArgs.length; index += 1) {
|
|
342
|
+
const arg = codexArgs[index]!;
|
|
343
|
+
if (arg === "--yolo" || arg === "--dangerously-bypass-approvals-and-sandbox") {
|
|
344
|
+
approvalPolicy = "never";
|
|
345
|
+
sandbox = "danger-full-access";
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (arg === "--full-auto") {
|
|
349
|
+
approvalPolicy = "on-request";
|
|
350
|
+
sandbox = "workspace-write";
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (arg === "--ask-for-approval" || arg === "-a") {
|
|
354
|
+
const next = codexArgs[index + 1];
|
|
355
|
+
if (next && !next.startsWith("-")) {
|
|
356
|
+
approvalPolicy = next;
|
|
357
|
+
index += 1;
|
|
358
|
+
}
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (arg.startsWith("--ask-for-approval=")) {
|
|
362
|
+
approvalPolicy = arg.slice("--ask-for-approval=".length);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (arg === "--sandbox" || arg === "-s") {
|
|
366
|
+
const next = codexArgs[index + 1];
|
|
367
|
+
if (next && !next.startsWith("-")) {
|
|
368
|
+
sandbox = next;
|
|
369
|
+
index += 1;
|
|
370
|
+
}
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (arg.startsWith("--sandbox=")) {
|
|
374
|
+
sandbox = arg.slice("--sandbox=".length);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { approvalPolicy, sandbox };
|
|
380
|
+
}
|
|
381
|
+
|
|
332
382
|
function cleanupRun(runDir: string, appServer: ReturnType<typeof Bun.spawn> | null): void {
|
|
333
383
|
if (existsSync(runDir)) {
|
|
334
384
|
const pidsPath = join(runDir, "sidecar-pids.txt");
|
|
@@ -489,6 +539,7 @@ async function start(args: string[]): Promise<void> {
|
|
|
489
539
|
}
|
|
490
540
|
|
|
491
541
|
if (!listenUrl) listenUrl = await pickLoopbackUrl();
|
|
542
|
+
const permissions = resolveSessionPermissions(codexArgs);
|
|
492
543
|
|
|
493
544
|
mkdirSync(runtimeRoot, { recursive: true });
|
|
494
545
|
const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -502,6 +553,8 @@ async function start(args: string[]): Promise<void> {
|
|
|
502
553
|
AGENT_RELAY_CODEX_RUN_ID: runId,
|
|
503
554
|
AGENT_RELAY_CODEX_RUNTIME_DIR: runDir,
|
|
504
555
|
CODEX_APP_SERVER_URL: listenUrl,
|
|
556
|
+
CODEX_LIVE_APPROVAL_POLICY: permissions.approvalPolicy,
|
|
557
|
+
CODEX_LIVE_SANDBOX: permissions.sandbox,
|
|
505
558
|
};
|
|
506
559
|
|
|
507
560
|
const appLog = Bun.file(join(runDir, "app-server.log"));
|
package/codex/README.md
CHANGED
|
@@ -52,6 +52,31 @@ starts Codex with
|
|
|
52
52
|
`--remote`, lets the SessionStart hook attach a sidecar to the actual thread,
|
|
53
53
|
and kills sidecars plus the app-server when Codex exits.
|
|
54
54
|
|
|
55
|
+
## Approvals and prompts
|
|
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
|
+
`codex-relay` forwards your launch mode to the sidecar, including
|
|
61
|
+
`--ask-for-approval`, `--sandbox`, `--full-auto`, and `--yolo`.
|
|
62
|
+
|
|
63
|
+
Example: no prompt loop, still workspace sandboxing:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
codex-relay -- --ask-for-approval never --sandbox workspace-write
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
If you prefer prompts for everything else but want relay sends auto-approved,
|
|
70
|
+
add a rule in `~/.codex/rules/default.rules` (adjust URL when using a remote relay):
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
prefix_rule(
|
|
74
|
+
pattern = ["curl", "-sS", "-X", "POST", "http://127.0.0.1:4850/api/messages"],
|
|
75
|
+
decision = "allow",
|
|
76
|
+
justification = "Allow local Agent Relay message posts",
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
55
80
|
For local development from this repo:
|
|
56
81
|
|
|
57
82
|
```bash
|
package/codex/live-sidecar.ts
CHANGED
|
@@ -20,6 +20,8 @@ interface Config {
|
|
|
20
20
|
threadMode: "auto" | "resume" | "start";
|
|
21
21
|
threadId?: string;
|
|
22
22
|
model?: string;
|
|
23
|
+
approvalPolicy?: string;
|
|
24
|
+
sandbox?: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
interface RuntimeState {
|
|
@@ -196,8 +198,7 @@ class CodexLiveSidecar {
|
|
|
196
198
|
const resumed = await this.app.threadResume({
|
|
197
199
|
threadId,
|
|
198
200
|
cwd: this.config.cwd,
|
|
199
|
-
|
|
200
|
-
sandbox: "workspace-write",
|
|
201
|
+
...this.threadPermissions(),
|
|
201
202
|
persistExtendedHistory: false,
|
|
202
203
|
});
|
|
203
204
|
return normalizeThread(resumed.thread);
|
|
@@ -213,8 +214,7 @@ class CodexLiveSidecar {
|
|
|
213
214
|
const resumed = await this.app.threadResume({
|
|
214
215
|
threadId: this.config.threadId,
|
|
215
216
|
cwd: this.config.cwd,
|
|
216
|
-
|
|
217
|
-
sandbox: "workspace-write",
|
|
217
|
+
...this.threadPermissions(),
|
|
218
218
|
persistExtendedHistory: false,
|
|
219
219
|
});
|
|
220
220
|
return normalizeThread(resumed.thread);
|
|
@@ -238,8 +238,7 @@ class CodexLiveSidecar {
|
|
|
238
238
|
const resumed = await this.app.threadResume({
|
|
239
239
|
threadId: latest.id,
|
|
240
240
|
cwd: this.config.cwd,
|
|
241
|
-
|
|
242
|
-
sandbox: "workspace-write",
|
|
241
|
+
...this.threadPermissions(),
|
|
243
242
|
persistExtendedHistory: false,
|
|
244
243
|
});
|
|
245
244
|
return normalizeThread(resumed.thread);
|
|
@@ -248,8 +247,7 @@ class CodexLiveSidecar {
|
|
|
248
247
|
|
|
249
248
|
const started = await this.app.threadStart({
|
|
250
249
|
cwd: this.config.cwd,
|
|
251
|
-
|
|
252
|
-
sandbox: "workspace-write",
|
|
250
|
+
...this.threadPermissions(),
|
|
253
251
|
ephemeral: false,
|
|
254
252
|
sessionStartSource: "startup",
|
|
255
253
|
model: this.config.model ?? null,
|
|
@@ -257,6 +255,13 @@ class CodexLiveSidecar {
|
|
|
257
255
|
return normalizeThread(started.thread);
|
|
258
256
|
}
|
|
259
257
|
|
|
258
|
+
private threadPermissions(): Record<string, string> {
|
|
259
|
+
const payload: Record<string, string> = {};
|
|
260
|
+
if (this.config.approvalPolicy) payload.approvalPolicy = this.config.approvalPolicy;
|
|
261
|
+
if (this.config.sandbox) payload.sandbox = this.config.sandbox;
|
|
262
|
+
return payload;
|
|
263
|
+
}
|
|
264
|
+
|
|
260
265
|
private async readThreadWithFallback(threadId: string): Promise<Thread> {
|
|
261
266
|
try {
|
|
262
267
|
const read = await this.app.threadRead(threadId, true);
|
|
@@ -595,6 +600,8 @@ function loadConfig(): Config {
|
|
|
595
600
|
threadMode: (process.env.CODEX_THREAD_MODE as Config["threadMode"]) || "auto",
|
|
596
601
|
threadId: process.env.CODEX_THREAD_ID || undefined,
|
|
597
602
|
model: process.env.CODEX_MODEL || undefined,
|
|
603
|
+
approvalPolicy: process.env.CODEX_LIVE_APPROVAL_POLICY || undefined,
|
|
604
|
+
sandbox: process.env.CODEX_LIVE_SANDBOX || undefined,
|
|
598
605
|
};
|
|
599
606
|
}
|
|
600
607
|
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -875,7 +875,7 @@ function relay() {
|
|
|
875
875
|
},
|
|
876
876
|
|
|
877
877
|
get composeAgents() {
|
|
878
|
-
return this.showOffline ? this.agents : this.agents.filter(a => a.status
|
|
878
|
+
return this.showOffline ? this.agents : this.agents.filter(a => a.status !== 'offline');
|
|
879
879
|
},
|
|
880
880
|
|
|
881
881
|
get uniqueLabels() {
|