agent-relay-server 0.3.7 → 0.3.8
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/bin/agent-relay-codex.ts +71 -19
- package/package.json +1 -1
- package/src/index.ts +0 -0
- package/codex/README.md +0 -107
package/bin/agent-relay-codex.ts
CHANGED
|
@@ -76,6 +76,47 @@ function readJsonFile<T>(path: string, fallback: T): T {
|
|
|
76
76
|
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
function isAgentRelaySessionStartCommand(command: string): boolean {
|
|
80
|
+
return /agent-relay.*codex\/hooks\/session-start\.ts/.test(command);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function removeAgentRelaySessionStartToml(input: string): string {
|
|
84
|
+
const lines = input.split(/\r?\n/);
|
|
85
|
+
const blocks: Array<{ header: string | null; text: string }> = [];
|
|
86
|
+
|
|
87
|
+
for (let index = 0; index < lines.length; ) {
|
|
88
|
+
const line = lines[index] ?? "";
|
|
89
|
+
const header = /^\[\[?/.test(line) ? line : null;
|
|
90
|
+
const block: string[] = [];
|
|
91
|
+
do {
|
|
92
|
+
block.push(lines[index] ?? "");
|
|
93
|
+
index += 1;
|
|
94
|
+
} while (index < lines.length && !/^\[\[?/.test(lines[index] ?? ""));
|
|
95
|
+
blocks.push({ header, text: block.join("\n") });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let output = "";
|
|
99
|
+
for (let index = 0; index < blocks.length; ) {
|
|
100
|
+
const block = blocks[index];
|
|
101
|
+
if (block?.header?.startsWith("[[hooks.SessionStart")) {
|
|
102
|
+
const group: typeof blocks = [];
|
|
103
|
+
while (index < blocks.length && blocks[index]?.header?.startsWith("[[hooks.SessionStart")) {
|
|
104
|
+
group.push(blocks[index]!);
|
|
105
|
+
index += 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const groupText = group.map((block) => block.text).join("\n");
|
|
109
|
+
if (!isAgentRelaySessionStartCommand(groupText)) output += `${groupText}\n`;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
output += `${block?.text ?? ""}\n`;
|
|
114
|
+
index += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return output.replace(/\s+$/, "\n");
|
|
118
|
+
}
|
|
119
|
+
|
|
79
120
|
function compareVersions(left: string, right: string): number {
|
|
80
121
|
const leftParts = left.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
|
|
81
122
|
const rightParts = right.split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
|
|
@@ -215,35 +256,43 @@ function installMarketplace(quiet = false): void {
|
|
|
215
256
|
|
|
216
257
|
function installHook(): void {
|
|
217
258
|
mkdirSync(join(home, ".codex"), { recursive: true });
|
|
259
|
+
const command = `bun ${shellQuote(installedHookScript)}`;
|
|
260
|
+
|
|
261
|
+
const configPath = join(home, ".codex", "config.toml");
|
|
262
|
+
const existingConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
263
|
+
let output = removeAgentRelaySessionStartToml(existingConfig);
|
|
264
|
+
if (!output.endsWith("\n\n")) output += output.endsWith("\n") ? "\n" : "\n\n";
|
|
265
|
+
output += `[[hooks.SessionStart]]
|
|
266
|
+
matcher = "startup|resume"
|
|
267
|
+
|
|
268
|
+
[[hooks.SessionStart.hooks]]
|
|
269
|
+
type = "command"
|
|
270
|
+
command = "${command.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"
|
|
271
|
+
statusMessage = "Starting Agent Relay"
|
|
272
|
+
timeout = 10
|
|
273
|
+
`;
|
|
274
|
+
|
|
275
|
+
writeFileSync(configPath, output);
|
|
276
|
+
|
|
218
277
|
const hooksPath = join(home, ".codex", "hooks.json");
|
|
278
|
+
if (!existsSync(hooksPath)) return;
|
|
279
|
+
|
|
219
280
|
const hooksJson = readJsonFile<HooksJson>(hooksPath, { hooks: {} });
|
|
220
281
|
hooksJson.hooks ??= {};
|
|
221
|
-
hooksJson.hooks.SessionStart
|
|
222
|
-
|
|
223
|
-
const command = `bun ${shellQuote(installedHookScript)}`;
|
|
224
|
-
hooksJson.hooks.SessionStart = hooksJson.hooks.SessionStart
|
|
282
|
+
hooksJson.hooks.SessionStart = (hooksJson.hooks.SessionStart ?? [])
|
|
225
283
|
.map((group) => ({
|
|
226
284
|
...group,
|
|
227
285
|
hooks: (group.hooks ?? []).filter((hook) => {
|
|
228
286
|
if (hook.type !== "command" || typeof hook.command !== "string") return true;
|
|
229
|
-
return
|
|
287
|
+
return !isAgentRelaySessionStartCommand(hook.command);
|
|
230
288
|
}),
|
|
231
289
|
}))
|
|
232
290
|
.filter((group) => (group.hooks ?? []).length > 0);
|
|
233
291
|
|
|
234
|
-
hooksJson.hooks.SessionStart.
|
|
235
|
-
matcher: "startup|resume",
|
|
236
|
-
hooks: [
|
|
237
|
-
{
|
|
238
|
-
type: "command",
|
|
239
|
-
command,
|
|
240
|
-
statusMessage: "Starting Agent Relay",
|
|
241
|
-
timeout: 10,
|
|
242
|
-
},
|
|
243
|
-
],
|
|
244
|
-
});
|
|
292
|
+
if (hooksJson.hooks.SessionStart.length === 0) delete hooksJson.hooks.SessionStart;
|
|
245
293
|
|
|
246
|
-
|
|
294
|
+
if (Object.keys(hooksJson.hooks).length === 0) rmSync(hooksPath, { force: true });
|
|
295
|
+
else writeFileSync(hooksPath, `${JSON.stringify(hooksJson, null, 2)}\n`);
|
|
247
296
|
}
|
|
248
297
|
|
|
249
298
|
async function pickLoopbackUrl(): Promise<string> {
|
|
@@ -407,8 +456,9 @@ function cleanupRun(runDir: string, appServer: ReturnType<typeof Bun.spawn> | nu
|
|
|
407
456
|
function installCodexSupport(quiet = false): void {
|
|
408
457
|
if (!commandExists("bun")) throw new Error("Bun is required: https://bun.sh");
|
|
409
458
|
findCodexBinary();
|
|
410
|
-
|
|
459
|
+
syncInstalledPackage();
|
|
411
460
|
installHook();
|
|
461
|
+
installMarketplace(quiet);
|
|
412
462
|
}
|
|
413
463
|
|
|
414
464
|
function writeLauncherShim(name: string): void {
|
|
@@ -602,7 +652,9 @@ async function doctor(): Promise<void> {
|
|
|
602
652
|
const checks: Array<[string, boolean, string]> = [];
|
|
603
653
|
checks.push(["bun", commandExists("bun"), "Bun is required to run the sidecar"]);
|
|
604
654
|
checks.push(["codex", findOnPath("codex", [aliasBinDir]) !== null, "Codex CLI is required"]);
|
|
605
|
-
|
|
655
|
+
const configPath = join(home, ".codex", "config.toml");
|
|
656
|
+
const config = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
657
|
+
checks.push(["hook", isAgentRelaySessionStartCommand(config), "~/.codex/config.toml has Agent Relay SessionStart hook"]);
|
|
606
658
|
checks.push(["marketplace", existsSync(marketplaceFile), "Agent Relay marketplace is installed"]);
|
|
607
659
|
checks.push(["launcher", existsSync(join(aliasBinDir, process.platform === "win32" ? "codex-relay.cmd" : "codex-relay")), "codex-relay launcher shim"]);
|
|
608
660
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
File without changes
|
package/codex/README.md
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
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
|
-
## 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
|
-
|
|
80
|
-
For local development from this repo:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
bun run bin/agent-relay-codex.ts
|
|
84
|
-
bun run codex:smoke:fallback
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Useful environment variables:
|
|
88
|
-
|
|
89
|
-
- `AGENT_RELAY_URL`
|
|
90
|
-
- `AGENT_RELAY_CAPS`
|
|
91
|
-
- `CODEX_APP_SERVER_URL`
|
|
92
|
-
- `CODEX_THREAD_ID`
|
|
93
|
-
- `CODEX_THREAD_MODE=auto|resume|start`
|
|
94
|
-
- `CODEX_LIVE_STATE_PATH`
|
|
95
|
-
- `CODEX_LIVE_COALESCE_WINDOW_MS`
|
|
96
|
-
- `CODEX_LIVE_RECONNECT_INITIAL_MS`
|
|
97
|
-
- `CODEX_LIVE_RECONNECT_MAX_MS`
|
|
98
|
-
- `CODEX_LIVE_RIG`
|
|
99
|
-
- `CODEX_MODEL`
|
|
100
|
-
|
|
101
|
-
## Notes
|
|
102
|
-
|
|
103
|
-
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.
|
|
104
|
-
|
|
105
|
-
- `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.
|
|
106
|
-
- For isolated testing, set `CODEX_THREAD_MODE=start` so the sidecar always creates its own thread.
|
|
107
|
-
- 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.
|