agent-relay-server 0.1.0 → 0.3.0

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 ADDED
@@ -0,0 +1,116 @@
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 pollMessages(agentId: string, sinceId: number): Promise<RelayMessage[]> {
72
+ const url = new URL(`/api/messages`, this.baseUrl);
73
+ url.searchParams.set("for", agentId);
74
+ url.searchParams.set("unread", "true");
75
+ if (sinceId > 0) url.searchParams.set("sinceId", String(sinceId));
76
+ const response = await fetch(url);
77
+ if (!response.ok) {
78
+ throw new Error(`relay poll failed: ${response.status} ${response.statusText}`);
79
+ }
80
+ return (await response.json()) as RelayMessage[];
81
+ }
82
+
83
+ async claimMessage(messageId: number, agentId: string): Promise<boolean> {
84
+ const response = await fetch(new URL(`/api/messages/${messageId}/claim`, this.baseUrl), {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({ agentId }),
88
+ });
89
+
90
+ if (response.ok) return true;
91
+ if (response.status === 409) return false;
92
+ if (response.status === 400 || response.status === 404) return false;
93
+ throw new Error(`relay claim failed: ${response.status} ${response.statusText}`);
94
+ }
95
+
96
+ async markRead(messageId: number, agentId: string): Promise<void> {
97
+ await this.json("PATCH", `/api/messages/${messageId}`, { readBy: agentId });
98
+ }
99
+
100
+ private async json(method: string, path: string, body?: unknown): Promise<unknown> {
101
+ const response = await fetch(new URL(path, this.baseUrl), {
102
+ method,
103
+ headers: { "Content-Type": "application/json" },
104
+ body: body === undefined ? undefined : JSON.stringify(body),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ const text = await response.text();
109
+ this.log(`relay error ${method} ${path}: ${response.status} ${text}`);
110
+ throw new Error(`relay ${method} ${path} failed: ${response.status}`);
111
+ }
112
+
113
+ if (response.status === 204) return null;
114
+ return response.json().catch(() => null);
115
+ }
116
+ }
@@ -0,0 +1,64 @@
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"
package/package.json CHANGED
@@ -1,21 +1,32 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
7
7
  "bin": {
8
- "agent-relay": "src/index.ts"
8
+ "agent-relay": "src/index.ts",
9
+ "agent-relay-codex": "bin/agent-relay-codex.ts",
10
+ "codex-relay": "bin/agent-relay-codex.ts"
9
11
  },
10
12
  "files": [
13
+ "bin/**/*.ts",
11
14
  "src/**/*.ts",
12
15
  "!src/**/*.test.ts",
16
+ "codex/**/*.ts",
17
+ "codex/**/*.sh",
18
+ "codex/**/*.ps1",
19
+ "codex/plugin/**",
13
20
  "public/**",
14
21
  "README.md"
15
22
  ],
16
23
  "scripts": {
17
24
  "start": "bun run src/index.ts",
18
- "dev": "bun --watch run src/index.ts"
25
+ "dev": "bun --watch run src/index.ts",
26
+ "codex:live": "bun run codex/live-sidecar.ts",
27
+ "codex:live:start": "bash codex/start-live.sh",
28
+ "codex:install": "bun run bin/agent-relay-codex.ts install",
29
+ "codex:doctor": "bun run bin/agent-relay-codex.ts doctor"
19
30
  },
20
31
  "keywords": [
21
32
  "agent",