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/README.md +177 -91
- package/bin/agent-relay-codex.ts +547 -0
- package/codex/README.md +80 -0
- package/codex/app-client.ts +239 -0
- package/codex/hooks/session-start.ts +114 -0
- package/codex/install-codex.ps1 +47 -0
- package/codex/install-codex.sh +75 -0
- package/codex/live-sidecar.ts +606 -0
- package/codex/plugin/.codex-plugin/plugin.json +25 -0
- package/codex/plugin/skills/agent-relay/SKILL.md +28 -0
- package/codex/relay.ts +116 -0
- package/codex/start-live.sh +64 -0
- package/package.json +14 -3
- package/public/index.html +1078 -446
- package/src/config.ts +8 -0
- package/src/db.ts +49 -20
- package/src/index.ts +5 -1
- package/src/routes.ts +83 -15
- package/src/sse.ts +115 -0
- package/src/types.ts +6 -0
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.
|
|
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",
|