agent-relay-server 0.3.4 → 0.3.5

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/README.md CHANGED
@@ -16,6 +16,7 @@ This sidecar connects to a Codex app-server session and to Agent Relay, then del
16
16
  - otherwise resumes the newest thread for the current `cwd`
17
17
  - otherwise creates a new thread
18
18
  - registers a relay agent with `client: codex-live`
19
+ - marks the relay agent `ready=true` once app-server + thread are attached
19
20
  - polls relay inbox and delivers messages into the live thread
20
21
  - coalesces ordinary relay bursts into one delivery turn
21
22
  - reconnects to the app-server with exponential backoff after disconnects
@@ -55,6 +56,7 @@ For local development from this repo:
55
56
 
56
57
  ```bash
57
58
  bun run bin/agent-relay-codex.ts
59
+ bun run codex:smoke:fallback
58
60
  ```
59
61
 
60
62
  Useful environment variables:
@@ -319,9 +319,13 @@ class CodexLiveSidecar {
319
319
  : this.activeTurnId || this.threadStatus === "active"
320
320
  ? "busy"
321
321
  : "idle";
322
+ const ready = this.appConnected && Boolean(this.threadId);
322
323
 
323
324
  try {
324
- await this.relay.setStatus(this.agentId, status);
325
+ await Promise.all([
326
+ this.relay.setStatus(this.agentId, status),
327
+ this.relay.setReady(this.agentId, ready),
328
+ ]);
325
329
  } catch {
326
330
  // Best-effort status sync.
327
331
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Agent Relay integration for Codex sessions",
5
5
  "author": {
6
6
  "name": "Edin Mujkanovic"
package/codex/relay.ts CHANGED
@@ -68,6 +68,10 @@ export class RelayClient {
68
68
  await this.json("PATCH", `/api/agents/${encodeURIComponent(agentId)}/status`, { status });
69
69
  }
70
70
 
71
+ async setReady(agentId: string, ready: boolean): Promise<void> {
72
+ await this.json("PATCH", `/api/agents/${encodeURIComponent(agentId)}/ready`, { ready });
73
+ }
74
+
71
75
  async pollMessages(agentId: string, sinceId: number): Promise<RelayMessage[]> {
72
76
  const url = new URL(`/api/messages`, this.baseUrl);
73
77
  url.searchParams.set("for", agentId);
@@ -0,0 +1,125 @@
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(stderr: string): string | null {
52
+ const match = stderr.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(stderr);
89
+ if (!runtimeDir) {
90
+ throw new Error(`missing runtime directory in launcher output\nstderr:\n${stderr}`);
91
+ }
92
+
93
+ if (!stderr.includes("SessionStart hook did not start sidecar; started fallback sidecar pid")) {
94
+ throw new Error(`fallback start message not found\nstderr:\n${stderr}`);
95
+ }
96
+
97
+ const fallbackPidPath = join(runtimeDir, "auto", "sidecar.pid");
98
+ const fallbackLogPath = join(runtimeDir, "auto", "sidecar.log");
99
+ const sidecarPidsPath = join(runtimeDir, "sidecar-pids.txt");
100
+
101
+ if (!existsSync(fallbackPidPath)) {
102
+ throw new Error(`fallback pid file missing: ${fallbackPidPath}`);
103
+ }
104
+ if (!existsSync(sidecarPidsPath)) {
105
+ throw new Error(`sidecar-pids missing: ${sidecarPidsPath}`);
106
+ }
107
+
108
+ const pid = readFileSync(fallbackPidPath, "utf8").trim();
109
+ const pidsFile = readFileSync(sidecarPidsPath, "utf8");
110
+ if (!pid || !pidsFile.includes(pid)) {
111
+ throw new Error(`fallback pid ${pid || "<empty>"} not recorded in sidecar-pids.txt`);
112
+ }
113
+
114
+ log(`fallback sidecar created pid ${pid}`);
115
+ if (existsSync(fallbackLogPath)) {
116
+ log(`fallback log file present at ${fallbackLogPath}`);
117
+ }
118
+ log("fallback smoke passed");
119
+ }
120
+
121
+ main().catch((error) => {
122
+ log(error instanceof Error ? error.stack || error.message : String(error));
123
+ process.exitCode = 1;
124
+ });
125
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -26,7 +26,8 @@
26
26
  "codex:live": "bun run codex/live-sidecar.ts",
27
27
  "codex:live:start": "bash codex/start-live.sh",
28
28
  "codex:install": "bun run bin/agent-relay-codex.ts install",
29
- "codex:doctor": "bun run bin/agent-relay-codex.ts doctor"
29
+ "codex:doctor": "bun run bin/agent-relay-codex.ts doctor",
30
+ "codex:smoke:fallback": "bun run codex/smoke/fallback.ts"
30
31
  },
31
32
  "keywords": [
32
33
  "agent",
package/public/index.html CHANGED
@@ -134,7 +134,7 @@
134
134
  <div class="card-body">
135
135
  <div class="d-flex align-items-center">
136
136
  <div>
137
- <div class="text-secondary small">Online</div>
137
+ <div class="text-secondary small">Active</div>
138
138
  <div class="h1 mb-0 text-success" x-text="onlineCount"></div>
139
139
  </div>
140
140
  <i class="ti ti-circle-check ms-auto stat-card"></i>
@@ -176,7 +176,7 @@
176
176
  <div class="card">
177
177
  <div class="card-header d-flex align-items-center">
178
178
  <h3 class="card-title">Agents</h3>
179
- <span class="badge bg-success text-white ms-auto" x-text="onlineCount + ' online'"></span>
179
+ <span class="badge bg-success text-white ms-auto" x-text="onlineCount + ' active'"></span>
180
180
  </div>
181
181
  <div class="list-group list-group-flush" style="max-height: 60vh; overflow-y: auto">
182
182
  <template x-for="a in sortedAgents.slice(0, 20)" :key="a.id">
@@ -319,7 +319,7 @@
319
319
  <div class="card">
320
320
  <div class="card-body text-center text-secondary py-5">
321
321
  <i class="ti ti-robot-off" style="font-size:48px; opacity:0.3"></i>
322
- <p class="mt-2" x-text="showOffline ? 'No agents registered' : 'No agents online — enable Show Offline'"></p>
322
+ <p class="mt-2" x-text="showOffline ? 'No agents registered' : 'No active agents — enable Show Offline'"></p>
323
323
  </div>
324
324
  </div>
325
325
  </template>
@@ -822,11 +822,11 @@ function relay() {
822
822
  // ── Computed ──
823
823
 
824
824
  get onlineCount() {
825
- return this.agents.filter(a => a.status === 'online').length;
825
+ return this.agents.filter(a => a.status !== 'offline').length;
826
826
  },
827
827
 
828
828
  get sortedAgents() {
829
- let list = this.showOffline ? [...this.agents] : this.agents.filter(a => a.status === 'online');
829
+ let list = this.showOffline ? [...this.agents] : this.agents.filter(a => a.status !== 'offline');
830
830
  const dir = this.agentSortDir === 'desc' ? -1 : 1;
831
831
  return list.sort((a, b) => {
832
832
  let cmp = 0;