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/README.md +83 -390
- package/package.json +4 -16
- package/bin/agent-relay-codex.ts +0 -978
- package/codex/README.md +0 -152
- package/codex/app-client.ts +0 -239
- package/codex/hooks/session-start-lib.test.ts +0 -39
- package/codex/hooks/session-start-lib.ts +0 -25
- package/codex/hooks/session-start.ts +0 -176
- package/codex/install-codex.ps1 +0 -47
- package/codex/install-codex.sh +0 -75
- package/codex/live-sidecar.test.ts +0 -20
- package/codex/live-sidecar.ts +0 -626
- package/codex/plugin/.codex-plugin/plugin.json +0 -40
- package/codex/plugin/skills/agent-relay/SKILL.md +0 -29
- package/codex/relay.ts +0 -125
- package/codex/smoke/fallback.ts +0 -128
- package/codex/start-live.sh +0 -64
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
|
-
}
|
package/codex/smoke/fallback.ts
DELETED
|
@@ -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
|
-
});
|
package/codex/start-live.sh
DELETED
|
@@ -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"
|