agent-relay-codex 0.4.14

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.
@@ -0,0 +1,25 @@
1
+ export type HookInput = {
2
+ session_id?: string;
3
+ sessionId?: string;
4
+ thread_id?: string;
5
+ threadId?: string;
6
+ session?: { id?: string };
7
+ thread?: { id?: string };
8
+ cwd?: string;
9
+ model?: string;
10
+ };
11
+
12
+ export function pickThreadId(input: HookInput): string {
13
+ const candidates = [
14
+ input.thread_id,
15
+ input.threadId,
16
+ input.thread?.id,
17
+ input.session_id,
18
+ input.sessionId,
19
+ input.session?.id,
20
+ ];
21
+ for (const value of candidates) {
22
+ if (typeof value === "string" && value.trim()) return value.trim();
23
+ }
24
+ return "";
25
+ }
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { describeApprovalMode, parseApprovalMode } from "../approval.ts";
6
+ import { buildAgentIdentity } from "../relay.ts";
7
+ import { pickThreadId, type HookInput } from "./session-start-lib.ts";
8
+
9
+ type HookHandshake = {
10
+ status: "ok" | "error";
11
+ code: string;
12
+ message: string;
13
+ pid?: number;
14
+ threadId?: string;
15
+ timestamp: string;
16
+ };
17
+
18
+ function readStdin(): string {
19
+ return readFileSync(0, "utf8");
20
+ }
21
+
22
+ function sanitize(value: string): string {
23
+ return value.replace(/[^a-zA-Z0-9._-]/g, "-").slice(0, 120) || "session";
24
+ }
25
+
26
+ function isAlive(pid: number): boolean {
27
+ try {
28
+ process.kill(pid, 0);
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function outputContext(context: string): never {
36
+ console.log(
37
+ JSON.stringify({
38
+ continue: true,
39
+ hookSpecificOutput: {
40
+ hookEventName: "SessionStart",
41
+ additionalContext: context,
42
+ },
43
+ }),
44
+ );
45
+ process.exit(0);
46
+ }
47
+
48
+ function handshakePath(runtimeDir: string): string {
49
+ return join(runtimeDir, "session-start-handshake.json");
50
+ }
51
+
52
+ function writeHandshake(runtimeDir: string, payload: HookHandshake): void {
53
+ writeFileSync(handshakePath(runtimeDir), `${JSON.stringify(payload, null, 2)}\n`);
54
+ }
55
+
56
+ function existingAlivePid(pidPath: string): number | null {
57
+ if (!existsSync(pidPath)) return null;
58
+ const existingPid = Number(readFileSync(pidPath, "utf8").trim());
59
+ if (!Number.isFinite(existingPid) || !isAlive(existingPid)) return null;
60
+ return existingPid;
61
+ }
62
+
63
+ const input = JSON.parse(readStdin() || "{}") as HookInput;
64
+ const packageRoot =
65
+ process.env.AGENT_RELAY_CODEX_PACKAGE_ROOT || resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
66
+ const appServerUrl = process.env.CODEX_APP_SERVER_URL;
67
+ const runId = process.env.AGENT_RELAY_CODEX_RUN_ID;
68
+ const cwd = input.cwd || process.cwd();
69
+ const threadId = pickThreadId(input);
70
+ const relayUrl = process.env.AGENT_RELAY_URL || "http://127.0.0.1:4850";
71
+ const rig = process.env.AGENT_RELAY_CODEX_RIG || "codex-live";
72
+ const approvalMode = parseApprovalMode(process.env.AGENT_RELAY_APPROVAL);
73
+
74
+ if (!appServerUrl || !runId) {
75
+ outputContext(
76
+ "Agent Relay for Codex is installed. For live incoming relay messages, start Codex with `agent-relay-codex start` so a managed app-server and sidecar can attach to this session.",
77
+ );
78
+ }
79
+
80
+ const runtimeDir = process.env.AGENT_RELAY_CODEX_RUNTIME_DIR || join(process.env.HOME || ".", ".agent-relay", "codex", "runtime", runId);
81
+ const sessionKey = sanitize(threadId || "auto");
82
+ const sessionDir = join(runtimeDir, sessionKey);
83
+ const pidPath = join(sessionDir, "sidecar.pid");
84
+ const statePath = join(sessionDir, "live-state.json");
85
+ const logPath = join(sessionDir, "sidecar.log");
86
+ mkdirSync(sessionDir, { recursive: true });
87
+ mkdirSync(runtimeDir, { recursive: true });
88
+
89
+ const autoPidPath = join(runtimeDir, "auto", "sidecar.pid");
90
+ const activePid = existingAlivePid(pidPath) ?? (threadId ? existingAlivePid(autoPidPath) : null);
91
+ if (activePid !== null) {
92
+ writeHandshake(runtimeDir, {
93
+ status: "ok",
94
+ code: "HOOK_SIDECAR_REUSED",
95
+ message: `using existing sidecar pid ${activePid}`,
96
+ pid: activePid,
97
+ threadId: threadId || undefined,
98
+ timestamp: new Date().toISOString(),
99
+ });
100
+ if (threadId) {
101
+ const identity = buildAgentIdentity({
102
+ relayUrl,
103
+ cwd,
104
+ rig,
105
+ capabilities: (process.env.AGENT_RELAY_CAPS || "chat").split(",").map((value) => value.trim()).filter(Boolean),
106
+ tags: ["codex", rig, cwd.split("/").filter(Boolean).at(-1) || "unknown"],
107
+ threadId,
108
+ model: input.model,
109
+ appServerUrl,
110
+ });
111
+ outputContext(buildStartupContext(identity.id, relayUrl, approvalMode));
112
+ }
113
+ outputContext(`Agent Relay sidecar already running (pid ${activePid}). Relay URL: ${relayUrl}. Approval mode: ${approvalMode} (${describeApprovalMode(approvalMode)}).`);
114
+ }
115
+
116
+ const spawnEnv: Record<string, string | undefined> = {
117
+ ...process.env,
118
+ AGENT_RELAY_URL: relayUrl,
119
+ CODEX_APP_SERVER_URL: appServerUrl,
120
+ CODEX_THREAD_MODE: threadId ? "resume" : process.env.CODEX_THREAD_MODE || "start",
121
+ AGENT_RELAY_CODEX_CWD: cwd,
122
+ AGENT_RELAY_CODEX_STATE_PATH: statePath,
123
+ CODEX_MODEL: input.model || process.env.CODEX_MODEL || "",
124
+ };
125
+ if (threadId) {
126
+ spawnEnv.CODEX_THREAD_ID = threadId;
127
+ } else {
128
+ delete spawnEnv.CODEX_THREAD_ID;
129
+ }
130
+
131
+ const logFile = Bun.file(logPath);
132
+ let sidecarPid = 0;
133
+ try {
134
+ const sidecar = Bun.spawn(["bun", "run", join(packageRoot, "codex", "live-sidecar.ts")], {
135
+ env: spawnEnv,
136
+ stdout: logFile,
137
+ stderr: logFile,
138
+ });
139
+ sidecar.unref();
140
+ sidecarPid = sidecar.pid;
141
+
142
+ writeFileSync(pidPath, String(sidecarPid));
143
+ appendFileSync(join(runtimeDir, "sidecar-pids.txt"), `${sidecarPid}\n`);
144
+ writeHandshake(runtimeDir, {
145
+ status: "ok",
146
+ code: "HOOK_SIDECAR_STARTED",
147
+ message: `spawned sidecar pid ${sidecarPid}`,
148
+ pid: sidecarPid,
149
+ threadId: threadId || undefined,
150
+ timestamp: new Date().toISOString(),
151
+ });
152
+ } catch (error) {
153
+ writeHandshake(runtimeDir, {
154
+ status: "error",
155
+ code: "HOOK_SIDECAR_SPAWN_FAILED",
156
+ message: error instanceof Error ? error.message : String(error),
157
+ threadId: threadId || undefined,
158
+ timestamp: new Date().toISOString(),
159
+ });
160
+ throw error;
161
+ }
162
+
163
+ if (!threadId) {
164
+ outputContext(`Agent Relay sidecar started in auto-thread mode. Relay URL: ${relayUrl}. Incoming messages will arrive as live user turns once the sidecar resolves the active thread.`);
165
+ }
166
+
167
+ const identity = buildAgentIdentity({
168
+ relayUrl,
169
+ cwd,
170
+ rig,
171
+ capabilities: (process.env.AGENT_RELAY_CAPS || "chat").split(",").map((value) => value.trim()).filter(Boolean),
172
+ tags: ["codex", rig, cwd.split("/").filter(Boolean).at(-1) || "unknown"],
173
+ threadId,
174
+ model: input.model,
175
+ appServerUrl,
176
+ });
177
+
178
+ outputContext(buildStartupContext(identity.id, relayUrl, approvalMode));
179
+
180
+ function buildStartupContext(agentId: string, url: string, mode: string): string {
181
+ return [
182
+ "Agent Relay active.",
183
+ `Agent ID: ${agentId}`,
184
+ `Relay URL: ${url}`,
185
+ `Approval mode: ${mode} (${describeApprovalMode(parseApprovalMode(mode))})`,
186
+ "Incoming messages will arrive as live user turns.",
187
+ `To send a message, POST JSON to ${url}/api/messages with from="${agentId}", to, subject, and body.`,
188
+ "Targets can be an agent id, tag:name, cap:name, label:name, or broadcast.",
189
+ "To reply to a specific incoming message, include replyTo set to that message id.",
190
+ "If AGENT_RELAY_TOKEN is set, include it as the X-Agent-Relay-Token header.",
191
+ "Message etiquette: acknowledge incoming agent messages briefly unless they are obvious noise.",
192
+ "Anti-loop rule: do not auto-reply to pure acknowledgements/thanks/received messages; acknowledge once, then follow up only when there is new work, a decision, or a deliverable.",
193
+ ].join(" ");
194
+ }
@@ -0,0 +1,47 @@
1
+ param(
2
+ [switch]$Alias,
3
+ [switch]$NoAlias
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+
8
+ $Package = if ($env:AGENT_RELAY_CODEX_PACKAGE) { $env:AGENT_RELAY_CODEX_PACKAGE } else { "agent-relay-codex@latest" }
9
+ $InstallArgs = New-Object System.Collections.Generic.List[string]
10
+ $InstallArgs.Add("install")
11
+
12
+ if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
13
+ Write-Error @"
14
+ Bun is required to install Agent Relay for Codex.
15
+
16
+ Install Bun first:
17
+ powershell -c "irm bun.sh/install.ps1 | iex"
18
+
19
+ Then rerun this installer.
20
+ "@
21
+ }
22
+
23
+ if (-not (Get-Command codex -ErrorAction SilentlyContinue)) {
24
+ Write-Error "Codex CLI is required before installing Agent Relay for Codex. Install and log in to Codex first, then rerun this installer."
25
+ }
26
+
27
+ if ($Alias -and $NoAlias) {
28
+ Write-Error "Use only one of -Alias or -NoAlias."
29
+ }
30
+
31
+ if ($Alias) {
32
+ $InstallArgs.Add("--alias")
33
+ } elseif ($NoAlias) {
34
+ $InstallArgs.Add("--no-alias")
35
+ } elseif ($env:AGENT_RELAY_CODEX_ALIAS -eq "1" -or $env:AGENT_RELAY_CODEX_ALIAS -eq "true") {
36
+ $InstallArgs.Add("--alias")
37
+ } else {
38
+ $answer = Read-Host "Make plain 'codex' start with Agent Relay? You can still use 'codex-relay' either way. [y/N]"
39
+ if ($answer -match "^(y|yes)$") {
40
+ $InstallArgs.Add("--alias")
41
+ } else {
42
+ $InstallArgs.Add("--no-alias")
43
+ }
44
+ }
45
+
46
+ & bunx -p $Package agent-relay-codex @InstallArgs
47
+ exit $LASTEXITCODE
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PACKAGE="${AGENT_RELAY_CODEX_PACKAGE:-agent-relay-codex@latest}"
5
+ INSTALL_ARGS=(install)
6
+
7
+ usage() {
8
+ cat <<'EOF'
9
+ Install Agent Relay for Codex.
10
+
11
+ Usage:
12
+ curl -fsSL https://unpkg.com/agent-relay-codex@latest/codex/install-codex.sh | bash
13
+ curl -fsSL https://unpkg.com/agent-relay-codex@latest/codex/install-codex.sh | bash -s -- --alias
14
+ curl -fsSL https://unpkg.com/agent-relay-codex@latest/codex/install-codex.sh | bash -s -- --no-alias
15
+
16
+ Options:
17
+ --alias Install a PATH shim so plain `codex` starts with Agent Relay.
18
+ --no-alias Keep plain `codex` unchanged. You can still use `codex-relay`.
19
+ EOF
20
+ }
21
+
22
+ for arg in "$@"; do
23
+ case "$arg" in
24
+ --alias|--no-alias)
25
+ INSTALL_ARGS+=("$arg")
26
+ ;;
27
+ -h|--help)
28
+ usage
29
+ exit 0
30
+ ;;
31
+ *)
32
+ echo "Unknown option: $arg" >&2
33
+ usage >&2
34
+ exit 2
35
+ ;;
36
+ esac
37
+ done
38
+
39
+ if ! command -v bun >/dev/null 2>&1; then
40
+ cat >&2 <<'EOF'
41
+ Error: Bun is required to install Agent Relay for Codex.
42
+
43
+ Install Bun first:
44
+ curl -fsSL https://bun.sh/install | bash
45
+
46
+ Then rerun this installer.
47
+ EOF
48
+ exit 1
49
+ fi
50
+
51
+ if ! command -v codex >/dev/null 2>&1; then
52
+ cat >&2 <<'EOF'
53
+ Error: Codex CLI is required before installing Agent Relay for Codex.
54
+
55
+ Install and log in to Codex first, then rerun this installer.
56
+ EOF
57
+ exit 1
58
+ fi
59
+
60
+ if [[ " ${INSTALL_ARGS[*]} " != *" --alias "* && " ${INSTALL_ARGS[*]} " != *" --no-alias "* ]]; then
61
+ if [[ "${AGENT_RELAY_CODEX_ALIAS:-}" == "1" || "${AGENT_RELAY_CODEX_ALIAS:-}" == "true" ]]; then
62
+ INSTALL_ARGS+=(--alias)
63
+ elif [[ -r /dev/tty && -w /dev/tty ]]; then
64
+ printf 'Make plain `codex` start with Agent Relay? You can still use `codex-relay` either way. [y/N] ' >/dev/tty
65
+ read -r answer </dev/tty || answer=""
66
+ case "${answer,,}" in
67
+ y|yes) INSTALL_ARGS+=(--alias) ;;
68
+ *) INSTALL_ARGS+=(--no-alias) ;;
69
+ esac
70
+ else
71
+ INSTALL_ARGS+=(--no-alias)
72
+ fi
73
+ fi
74
+
75
+ exec bunx -p "$PACKAGE" agent-relay-codex "${INSTALL_ARGS[@]}"