agent-relay-server 0.4.13 → 0.4.15

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/codex/relay.ts DELETED
@@ -1,125 +0,0 @@
1
- import { hostname } from "node:os";
2
-
3
- export type RelayAgentStatus = "online" | "idle" | "busy" | "offline";
4
-
5
- export interface RelayMessage {
6
- id: number;
7
- from: string;
8
- to: string;
9
- subject?: string;
10
- body: string;
11
- replyTo?: number;
12
- claimable?: boolean;
13
- meta?: Record<string, unknown>;
14
- createdAt: number;
15
- }
16
-
17
- export interface RelayConfig {
18
- relayUrl: string;
19
- cwd: string;
20
- rig: string;
21
- capabilities: string[];
22
- tags: string[];
23
- threadId: string;
24
- model?: string;
25
- appServerUrl: string;
26
- }
27
-
28
- export function buildAgentIdentity(config: RelayConfig): { id: string; name: string; machine: string; project: string } {
29
- const machine = hostname();
30
- const project = config.cwd.split("/").filter(Boolean).at(-1) || "unknown";
31
- const shortThread = config.threadId.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8) || "thread";
32
- const id = `${machine}-${config.rig}-${project}-${shortThread}`;
33
- const name = `${project} (${config.rig} @ ${machine})`;
34
- return { id, name, machine, project };
35
- }
36
-
37
- export class RelayClient {
38
- constructor(private readonly baseUrl: string, private readonly log: (msg: string) => void = () => {}) {}
39
-
40
- async registerAgent(config: RelayConfig): Promise<{ agentId: string; project: string }> {
41
- const identity = buildAgentIdentity(config);
42
- const payload = {
43
- id: identity.id,
44
- name: identity.name,
45
- machine: identity.machine,
46
- rig: config.rig,
47
- tags: config.tags,
48
- capabilities: config.capabilities,
49
- status: "online",
50
- meta: {
51
- client: "codex-live",
52
- threadId: config.threadId,
53
- appServerUrl: config.appServerUrl,
54
- cwd: config.cwd,
55
- model: config.model || null,
56
- },
57
- };
58
-
59
- await this.json("POST", "/api/agents", payload);
60
- return { agentId: identity.id, project: identity.project };
61
- }
62
-
63
- async heartbeat(agentId: string): Promise<void> {
64
- await this.json("POST", `/api/agents/${encodeURIComponent(agentId)}/heartbeat`);
65
- }
66
-
67
- async setStatus(agentId: string, status: RelayAgentStatus): Promise<void> {
68
- await this.json("PATCH", `/api/agents/${encodeURIComponent(agentId)}/status`, { status });
69
- }
70
-
71
- async setReady(agentId: string, ready: boolean): Promise<void> {
72
- await this.json("PATCH", `/api/agents/${encodeURIComponent(agentId)}/ready`, { ready });
73
- }
74
-
75
- async pollMessages(agentId: string, sinceId: number): Promise<RelayMessage[]> {
76
- const url = new URL(`/api/messages`, this.baseUrl);
77
- url.searchParams.set("for", agentId);
78
- url.searchParams.set("unread", "true");
79
- if (sinceId > 0) url.searchParams.set("sinceId", String(sinceId));
80
- const response = await fetch(url, { headers: this.headers() });
81
- if (!response.ok) {
82
- throw new Error(`relay poll failed: ${response.status} ${response.statusText}`);
83
- }
84
- return (await response.json()) as RelayMessage[];
85
- }
86
-
87
- async claimMessage(messageId: number, agentId: string): Promise<boolean> {
88
- const response = await fetch(new URL(`/api/messages/${messageId}/claim`, this.baseUrl), {
89
- method: "POST",
90
- headers: this.headers({ "Content-Type": "application/json" }),
91
- body: JSON.stringify({ agentId }),
92
- });
93
-
94
- if (response.ok) return true;
95
- if (response.status === 409) return false;
96
- if (response.status === 400 || response.status === 404) return false;
97
- throw new Error(`relay claim failed: ${response.status} ${response.statusText}`);
98
- }
99
-
100
- async markRead(messageId: number, agentId: string): Promise<void> {
101
- await this.json("PATCH", `/api/messages/${messageId}`, { readBy: agentId });
102
- }
103
-
104
- private async json(method: string, path: string, body?: unknown): Promise<unknown> {
105
- const response = await fetch(new URL(path, this.baseUrl), {
106
- method,
107
- headers: this.headers({ "Content-Type": "application/json" }),
108
- body: body === undefined ? undefined : JSON.stringify(body),
109
- });
110
-
111
- if (!response.ok) {
112
- const text = await response.text();
113
- this.log(`relay error ${method} ${path}: ${response.status} ${text}`);
114
- throw new Error(`relay ${method} ${path} failed: ${response.status}`);
115
- }
116
-
117
- if (response.status === 204) return null;
118
- return response.json().catch(() => null);
119
- }
120
-
121
- private headers(base: Record<string, string> = {}): Record<string, string> {
122
- const token = process.env.AGENT_RELAY_TOKEN;
123
- return token ? { ...base, "X-Agent-Relay-Token": token } : base;
124
- }
125
- }
@@ -1,128 +0,0 @@
1
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join, resolve } from "node:path";
3
- import { tmpdir } from "node:os";
4
-
5
- const ROOT_DIR = resolve(import.meta.dir, "../..");
6
-
7
- function log(message: string): void {
8
- console.error(`[codex-fallback-smoke] ${message}`);
9
- }
10
-
11
- async function waitForExit(process: Bun.Subprocess, timeoutMs: number): Promise<number> {
12
- const started = Date.now();
13
- while (Date.now() - started < timeoutMs) {
14
- if (process.exitCode !== null) return process.exitCode;
15
- await Bun.sleep(100);
16
- }
17
- try {
18
- process.kill();
19
- } catch {
20
- // Process already exited.
21
- }
22
- throw new Error(`timed out waiting for process to exit after ${timeoutMs}ms`);
23
- }
24
-
25
- function writeFakeCodex(path: string): void {
26
- const script = `#!/usr/bin/env bash
27
- set -euo pipefail
28
-
29
- if [[ "\${1:-}" == "plugin" && "\${2:-}" == "marketplace" && "\${3:-}" == "add" ]]; then
30
- exit 0
31
- fi
32
-
33
- if [[ "\${1:-}" == "app-server" && "\${2:-}" == "--listen" ]]; then
34
- url="\${3:-}"
35
- port="\${url##*:}"
36
- exec node -e 'const net=require("node:net"); const port=Number(process.argv[1]); const server=net.createServer((socket)=>socket.destroy()); server.listen(port,"127.0.0.1"); setInterval(()=>{},1000);' "$port"
37
- fi
38
-
39
- if [[ "\${1:-}" == "--remote" ]]; then
40
- sleep 6
41
- exit 0
42
- fi
43
-
44
- echo "fake codex: unsupported args: $*" >&2
45
- exit 64
46
- `;
47
- writeFileSync(path, script, "utf8");
48
- chmodSync(path, 0o755);
49
- }
50
-
51
- function parseRuntimeDir(output: string): string | null {
52
- const match = output.match(/^Runtime:\s+(.+)$/m);
53
- return match?.[1]?.trim() || null;
54
- }
55
-
56
- async function main(): Promise<void> {
57
- const tempHome = mkdtempSync(join(tmpdir(), "agent-relay-codex-fallback-"));
58
- const fakeBin = join(tempHome, "fake-bin");
59
- mkdirSync(fakeBin, { recursive: true });
60
-
61
- const fakeCodexPath = join(fakeBin, "codex");
62
- writeFakeCodex(fakeCodexPath);
63
-
64
- const env = {
65
- ...process.env,
66
- HOME: tempHome,
67
- USERPROFILE: tempHome,
68
- PATH: `${fakeBin}:${process.env.PATH || ""}`,
69
- AGENT_RELAY_URL: process.env.AGENT_RELAY_URL || "http://127.0.0.1:4850",
70
- };
71
-
72
- log("starting launcher with fake codex runtime (hooks intentionally not fired)");
73
- const run = Bun.spawn(["bun", "run", "bin/agent-relay-codex.ts", "start"], {
74
- cwd: ROOT_DIR,
75
- env,
76
- stdout: "pipe",
77
- stderr: "pipe",
78
- });
79
-
80
- const exitCode = await waitForExit(run, 25_000);
81
- const stderr = await new Response(run.stderr).text();
82
- const stdout = await new Response(run.stdout).text();
83
-
84
- if (exitCode !== 0) {
85
- throw new Error(`launcher exited ${exitCode}\nstdout:\n${stdout}\nstderr:\n${stderr}`);
86
- }
87
-
88
- const runtimeDir = parseRuntimeDir(`${stdout}\n${stderr}`);
89
- if (!runtimeDir) {
90
- throw new Error(`missing runtime directory in launcher output\nstdout:\n${stdout}\nstderr:\n${stderr}`);
91
- }
92
-
93
- const fallbackPidPath = join(runtimeDir, "auto", "sidecar.pid");
94
- const fallbackLogPath = join(runtimeDir, "auto", "sidecar.log");
95
- const sidecarPidsPath = join(runtimeDir, "sidecar-pids.txt");
96
- const launcherLogPath = join(runtimeDir, "launcher.log");
97
-
98
- if (!existsSync(fallbackPidPath)) {
99
- throw new Error(`fallback pid file missing: ${fallbackPidPath}`);
100
- }
101
- if (!existsSync(sidecarPidsPath)) {
102
- throw new Error(`sidecar-pids missing: ${sidecarPidsPath}`);
103
- }
104
- if (!existsSync(launcherLogPath)) {
105
- throw new Error(`launcher log missing: ${launcherLogPath}`);
106
- }
107
-
108
- const pid = readFileSync(fallbackPidPath, "utf8").trim();
109
- const pidsFile = readFileSync(sidecarPidsPath, "utf8");
110
- const launcherLog = readFileSync(launcherLogPath, "utf8");
111
- if (!pid || !pidsFile.includes(pid)) {
112
- throw new Error(`fallback pid ${pid || "<empty>"} not recorded in sidecar-pids.txt`);
113
- }
114
- if (!launcherLog.includes("HOOK_FALLBACK_STARTED reason=HOOK_HANDSHAKE_TIMEOUT")) {
115
- throw new Error(`fallback reason code missing in launcher log\nlog:\n${launcherLog}`);
116
- }
117
-
118
- log(`fallback sidecar created pid ${pid}`);
119
- if (existsSync(fallbackLogPath)) {
120
- log(`fallback log file present at ${fallbackLogPath}`);
121
- }
122
- log("fallback smoke passed");
123
- }
124
-
125
- main().catch((error) => {
126
- log(error instanceof Error ? error.stack || error.message : String(error));
127
- process.exitCode = 1;
128
- });
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT_DIR=$(cd "$(dirname "$0")/.." && pwd)
5
- CODEX_DIR="$ROOT_DIR/codex"
6
- RUNTIME_DIR="$CODEX_DIR/runtime"
7
- APP_LOG="$RUNTIME_DIR/app-server.log"
8
- APP_PID="$RUNTIME_DIR/app-server.pid"
9
- APP_URL_FILE="$RUNTIME_DIR/app-server.url"
10
- STATE_FILE="${CODEX_LIVE_STATE_PATH:-$RUNTIME_DIR/live-state.json}"
11
- CODEX_APP_SERVER_URL="${CODEX_APP_SERVER_URL:-ws://127.0.0.1:4501}"
12
- STARTED_APP_SERVER=0
13
-
14
- mkdir -p "$RUNTIME_DIR"
15
-
16
- cleanup() {
17
- if [ "$STARTED_APP_SERVER" = "1" ] && [ -f "$APP_PID" ]; then
18
- pid=$(cat "$APP_PID" 2>/dev/null || true)
19
- if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
20
- kill "$pid" 2>/dev/null || true
21
- fi
22
- rm -f "$APP_PID"
23
- fi
24
- }
25
- trap cleanup EXIT INT TERM
26
-
27
- if [ -f "$APP_PID" ]; then
28
- pid=$(cat "$APP_PID" 2>/dev/null || true)
29
- if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
30
- echo "app-server already running (pid=$pid) at $(cat "$APP_URL_FILE" 2>/dev/null || echo "$CODEX_APP_SERVER_URL")"
31
- else
32
- rm -f "$APP_PID"
33
- fi
34
- fi
35
-
36
- if [ ! -f "$APP_PID" ]; then
37
- setsid bash -lc "exec codex app-server --listen '$CODEX_APP_SERVER_URL' >>'$APP_LOG' 2>&1 < /dev/null" &
38
- pid=$!
39
- STARTED_APP_SERVER=1
40
- printf '%s\n' "$pid" > "$APP_PID"
41
- printf '%s\n' "$CODEX_APP_SERVER_URL" > "$APP_URL_FILE"
42
-
43
- for _ in $(seq 1 50); do
44
- if grep -qiE 'listening|ws://' "$APP_LOG" 2>/dev/null; then
45
- break
46
- fi
47
- if ! kill -0 "$pid" 2>/dev/null; then
48
- echo "app-server exited early; see $APP_LOG" >&2
49
- exit 1
50
- fi
51
- sleep 0.2
52
- done
53
-
54
- echo "started app-server pid=$pid url=$CODEX_APP_SERVER_URL"
55
- fi
56
-
57
- echo "state file: $STATE_FILE"
58
- echo "connect Codex TUI with: codex --remote $CODEX_APP_SERVER_URL"
59
- echo "if sidecar creates a new thread first, inspect $STATE_FILE for threadId and use: codex resume <threadId> --remote $CODEX_APP_SERVER_URL"
60
-
61
- export CODEX_APP_SERVER_URL
62
- export CODEX_LIVE_CWD="${CODEX_LIVE_CWD:-$ROOT_DIR}"
63
- export CODEX_LIVE_STATE_PATH="$STATE_FILE"
64
- exec bun run "$CODEX_DIR/live-sidecar.ts"