agent-relay-runner 0.63.0 → 0.63.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
- "version": "0.63.0",
3
+ "version": "0.63.1",
4
4
  "description": "Unified provider lifecycle runner for Agent Relay",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "directory": "runner"
21
21
  },
22
22
  "dependencies": {
23
- "agent-relay-sdk": "0.2.41"
23
+ "agent-relay-sdk": "0.2.42"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/bun": "latest",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.63.0",
4
+ "version": "0.63.1",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
package/src/adapter.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { AgentProfile, Message } from "agent-relay-sdk";
2
2
  import { isRecord } from "agent-relay-sdk";
3
3
  import type { SessionEvent } from "./session-insights";
4
+ import { messageBodyMaxCharsFromEnv } from "./config";
4
5
 
5
6
  export type SemanticStatus = "idle" | "busy" | "offline" | "error";
6
7
  type ProviderWorkKind = "provider-turn" | "subagent";
@@ -202,7 +203,7 @@ export const DEFAULT_PROVIDER_MESSAGE_BODY_MAX_CHARS = 24_000;
202
203
  // Resolve the delivered-body cap. Deployment-dependent (the right ceiling tracks the host's
203
204
  // context budget), so it's overridable via env — set on the orchestrator/host that spawns runners.
204
205
  export function providerMessageBodyMaxChars(): number {
205
- const raw = process.env.AGENT_RELAY_MESSAGE_BODY_MAX_CHARS;
206
+ const raw = messageBodyMaxCharsFromEnv();
206
207
  if (raw !== undefined) {
207
208
  const parsed = Number.parseInt(raw.trim(), 10);
208
209
  if (Number.isFinite(parsed) && parsed > 0) return parsed;
@@ -4,6 +4,7 @@ import { basename, join } from "node:path";
4
4
  import type { Artifact, Message } from "agent-relay-sdk";
5
5
  import { errMessage, isRecord } from "agent-relay-sdk";
6
6
  import { sanitizeFsName } from "agent-relay-sdk/fs-name";
7
+ import { attachmentCacheDirFromEnv } from "./config";
7
8
 
8
9
  const DEFAULT_CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
9
10
 
@@ -34,11 +35,11 @@ function attachmentRefs(message: Message): Record<string, unknown>[] {
34
35
  return refs.filter(isRecord);
35
36
  }
36
37
 
37
- function attachmentCacheRoot(agentId: string, rootDir = process.env.AGENT_RELAY_ATTACHMENT_CACHE_DIR): string {
38
+ function attachmentCacheRoot(agentId: string, rootDir = attachmentCacheDirFromEnv()): string {
38
39
  return join(attachmentCacheBase(rootDir), safePathPart(agentId));
39
40
  }
40
41
 
41
- function attachmentCacheBase(rootDir = process.env.AGENT_RELAY_ATTACHMENT_CACHE_DIR): string {
42
+ function attachmentCacheBase(rootDir = attachmentCacheDirFromEnv()): string {
42
43
  return rootDir || join(homedir(), ".agent-relay", "attachments");
43
44
  }
44
45
 
@@ -147,7 +148,7 @@ function withCacheError(ref: Record<string, unknown>, error: unknown): Record<st
147
148
 
148
149
  export function pruneAttachmentCache(
149
150
  agentId: string,
150
- rootDir = process.env.AGENT_RELAY_ATTACHMENT_CACHE_DIR,
151
+ rootDir = attachmentCacheDirFromEnv(),
151
152
  maxAgeMs = DEFAULT_CACHE_MAX_AGE_MS,
152
153
  now = Date.now(),
153
154
  ): void {
package/src/config.ts CHANGED
@@ -1,11 +1,26 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { homedir, hostname } from "node:os";
4
- import { join, resolve } from "node:path";
4
+ import { dirname, join, resolve } from "node:path";
5
5
  import { DEFAULT_RELAY_URL, RELAY_TOKEN_HEADER, errMessage, stringValue } from "agent-relay-sdk";
6
6
  import type { SettingEntry, WorkspaceMetadata } from "agent-relay-sdk";
7
7
  import { sanitizeFsName } from "agent-relay-sdk/fs-name";
8
- import type { ProviderConfig } from "./adapter";
8
+
9
+ interface ProviderConfig {
10
+ command: string;
11
+ defaultArgs: string[];
12
+ env: Record<string, string>;
13
+ pluginDirs: string[];
14
+ defaultCapabilities: string[];
15
+ defaultApprovalMode: string;
16
+ defaultTags: string[];
17
+ chatCaptureMode: "final" | "full";
18
+ reasoningCapture?: boolean;
19
+ headless: {
20
+ tmuxPrefix: string;
21
+ shutdownTimeoutMs: number;
22
+ };
23
+ }
9
24
 
10
25
  interface GlobalRunnerConfig {
11
26
  relayUrl: string;
@@ -17,7 +32,7 @@ interface LoadedProviderConfig extends ProviderConfig {
17
32
  path: string;
18
33
  }
19
34
 
20
- function agentRelayHome(): string {
35
+ export function agentRelayHome(): string {
21
36
  return process.env.AGENT_RELAY_HOME || join(homedir(), ".agent-relay");
22
37
  }
23
38
 
@@ -48,12 +63,135 @@ export function loadGlobalConfig(home = agentRelayHome()): GlobalRunnerConfig {
48
63
  const path = join(home, "config.json");
49
64
  const parsed = readJson(path);
50
65
  return {
51
- relayUrl: stringValue(parsed.relayUrl) ?? process.env.AGENT_RELAY_URL ?? DEFAULT_RELAY_URL,
52
- token: stringValue(parsed.token) ?? process.env.AGENT_RELAY_TOKEN,
66
+ relayUrl: stringValue(parsed.relayUrl) ?? relayUrlFromEnv() ?? DEFAULT_RELAY_URL,
67
+ token: stringValue(parsed.token) ?? relayTokenFromEnv(),
53
68
  defaultCwd: stringValue(parsed.defaultCwd) ?? process.cwd(),
54
69
  };
55
70
  }
56
71
 
72
+ function relayUrlFromEnv(): string | undefined {
73
+ return process.env.AGENT_RELAY_URL;
74
+ }
75
+
76
+ export function relayTokenFromEnv(): string | undefined {
77
+ return process.env.AGENT_RELAY_TOKEN;
78
+ }
79
+
80
+ export function runtimeTokenProfileFromEnv(): string | undefined {
81
+ return process.env.AGENT_RELAY_TOKEN_PROFILE;
82
+ }
83
+
84
+ export function runtimeTokenJtiFromEnv(): string | undefined {
85
+ return process.env.AGENT_RELAY_TOKEN_JTI;
86
+ }
87
+
88
+ export function runtimeTokenExpiresAtFromEnv(): string | undefined {
89
+ return process.env.AGENT_RELAY_TOKEN_EXPIRES_AT;
90
+ }
91
+
92
+ export function agentProfileNameFromEnv(): string | undefined {
93
+ return process.env.AGENT_RELAY_AGENT_PROFILE;
94
+ }
95
+
96
+ export function agentProfileJsonFromEnv(): string | undefined {
97
+ return process.env.AGENT_RELAY_AGENT_PROFILE_JSON;
98
+ }
99
+
100
+ export function workspaceJsonFromEnv(): string | undefined {
101
+ return process.env.AGENT_RELAY_WORKSPACE_JSON;
102
+ }
103
+
104
+ export function tmuxSessionFromEnv(): string | undefined {
105
+ return process.env.AGENT_RELAY_TMUX_SESSION;
106
+ }
107
+
108
+ export function policyNameFromEnv(): string | undefined {
109
+ return process.env.AGENT_RELAY_POLICY;
110
+ }
111
+
112
+ export function spawnRequestIdFromEnv(): string | undefined {
113
+ return process.env.AGENT_RELAY_SPAWN_REQUEST_ID;
114
+ }
115
+
116
+ export function automationIdFromEnv(): string | undefined {
117
+ return process.env.AGENT_RELAY_AUTOMATION_ID;
118
+ }
119
+
120
+ export function automationRunIdFromEnv(): string | undefined {
121
+ return process.env.AGENT_RELAY_AUTOMATION_RUN_ID;
122
+ }
123
+
124
+ export function lifecycleFromEnv(): string | undefined {
125
+ return process.env.AGENT_RELAY_LIFECYCLE;
126
+ }
127
+
128
+ export function sessionDebugEnabled(): boolean {
129
+ return process.env.AGENT_RELAY_SESSION_DEBUG === "1";
130
+ }
131
+
132
+ export function logLevelFromEnv(): string | undefined {
133
+ return process.env.AGENT_RELAY_LOG_LEVEL;
134
+ }
135
+
136
+ export function mcpProxyEnabledFromEnv(): boolean {
137
+ return !["0", "false", "off"].includes((process.env.AGENT_RELAY_MCP_PROXY ?? "").trim().toLowerCase());
138
+ }
139
+
140
+ export function runnerInfoFileFromEnv(): string | undefined {
141
+ return process.env.AGENT_RELAY_RUNNER_INFO_FILE;
142
+ }
143
+
144
+ export function runnerOutboxDirFromEnv(): string | undefined {
145
+ return process.env.AGENT_RELAY_RUNNER_OUTBOX_DIR;
146
+ }
147
+
148
+ export function runnerOutboxDirWithInfoFallback(): string | undefined {
149
+ const dir = runnerOutboxDirFromEnv();
150
+ if (dir) return dir;
151
+ const infoFile = runnerInfoFileFromEnv();
152
+ return infoFile ? join(dirname(infoFile), "outbox") : undefined;
153
+ }
154
+
155
+ export function capsFromEnv(): string | undefined {
156
+ return process.env.AGENT_RELAY_CAPS;
157
+ }
158
+
159
+ export function tagsFromEnv(): string | undefined {
160
+ return process.env.AGENT_RELAY_TAGS;
161
+ }
162
+
163
+ export function workspaceModeFromEnv(): string | undefined {
164
+ return process.env.AGENT_RELAY_WORKSPACE_MODE;
165
+ }
166
+
167
+ export function orchestratorBaseDirFromEnv(): string | undefined {
168
+ return process.env.AGENT_RELAY_ORCHESTRATOR_BASE_DIR;
169
+ }
170
+
171
+ export function runnerLogFileFromEnv(): string | undefined {
172
+ return typeof process.env.AGENT_RELAY_LOG_FILE === "string" ? process.env.AGENT_RELAY_LOG_FILE : undefined;
173
+ }
174
+
175
+ export function orchestratorUrlFromEnv(): string | undefined {
176
+ return process.env.AGENT_RELAY_ORCHESTRATOR_URL;
177
+ }
178
+
179
+ export function contextStateDirFromEnv(): string | undefined {
180
+ return process.env.AGENT_RELAY_CONTEXT_STATE_DIR;
181
+ }
182
+
183
+ export function attachmentCacheDirFromEnv(): string | undefined {
184
+ return process.env.AGENT_RELAY_ATTACHMENT_CACHE_DIR;
185
+ }
186
+
187
+ export function providerHomeRootFromEnv(): string {
188
+ return process.env.AGENT_RELAY_PROVIDER_HOME_ROOT || join(homedir(), ".agent-relay", "provider-homes");
189
+ }
190
+
191
+ export function messageBodyMaxCharsFromEnv(): string | undefined {
192
+ return process.env.AGENT_RELAY_MESSAGE_BODY_MAX_CHARS;
193
+ }
194
+
57
195
  export function loadProviderConfig(provider: string, home = agentRelayHome()): LoadedProviderConfig {
58
196
  const path = join(providersDir(home), `${provider}.json`);
59
197
  const raw = readJson(path);
package/src/index.ts CHANGED
@@ -3,7 +3,24 @@ import { hostname } from "node:os";
3
3
  import { basename } from "node:path";
4
4
  import { isatty } from "node:tty";
5
5
  import { AgentRunner } from "./runner";
6
- import { loadGlobalConfig, loadProviderConfigFromRelay, resolveCwd, runnerId } from "./config";
6
+ import {
7
+ agentProfileJsonFromEnv,
8
+ agentProfileNameFromEnv,
9
+ automationIdFromEnv,
10
+ automationRunIdFromEnv,
11
+ lifecycleFromEnv,
12
+ loadGlobalConfig,
13
+ loadProviderConfigFromRelay,
14
+ policyNameFromEnv,
15
+ resolveCwd,
16
+ runnerId,
17
+ runtimeTokenExpiresAtFromEnv,
18
+ runtimeTokenJtiFromEnv,
19
+ runtimeTokenProfileFromEnv,
20
+ spawnRequestIdFromEnv,
21
+ tmuxSessionFromEnv,
22
+ workspaceJsonFromEnv,
23
+ } from "./config";
7
24
  import { createProviderAdapter } from "./providers";
8
25
  import { VERSION } from "./version";
9
26
  import type { AgentProfile, SpawnProvider, WorkspaceMetadata } from "agent-relay-sdk";
@@ -49,9 +66,9 @@ export async function main(argv = process.argv): Promise<void> {
49
66
  interactive: opts.interactive,
50
67
  relayUrl,
51
68
  token: opts.token ?? globalConfig.token,
52
- runtimeTokenProfile: process.env.AGENT_RELAY_TOKEN_PROFILE,
53
- runtimeTokenJti: process.env.AGENT_RELAY_TOKEN_JTI,
54
- runtimeTokenExpiresAt: parseTokenExpiresAt(process.env.AGENT_RELAY_TOKEN_EXPIRES_AT),
69
+ runtimeTokenProfile: runtimeTokenProfileFromEnv(),
70
+ runtimeTokenJti: runtimeTokenJtiFromEnv(),
71
+ runtimeTokenExpiresAt: parseTokenExpiresAt(runtimeTokenExpiresAtFromEnv()),
55
72
  provider: opts.provider,
56
73
  cwd,
57
74
  runnerId: id,
@@ -84,20 +101,20 @@ export async function main(argv = process.argv): Promise<void> {
84
101
  approvalMode,
85
102
  label: opts.label,
86
103
  rig: opts.rig,
87
- profile: opts.profile ?? process.env.AGENT_RELAY_AGENT_PROFILE,
88
- agentProfile: parseAgentProfileEnv(process.env.AGENT_RELAY_AGENT_PROFILE_JSON),
89
- workspace: parseWorkspaceEnv(process.env.AGENT_RELAY_WORKSPACE_JSON),
104
+ profile: opts.profile ?? agentProfileNameFromEnv(),
105
+ agentProfile: parseAgentProfileEnv(agentProfileJsonFromEnv()),
106
+ workspace: parseWorkspaceEnv(workspaceJsonFromEnv()),
90
107
  prompt: opts.prompt,
91
108
  systemPromptAppend: opts.systemPromptAppend,
92
- tmuxSession: process.env.AGENT_RELAY_TMUX_SESSION,
109
+ tmuxSession: tmuxSessionFromEnv(),
93
110
  tags: opts.tags,
94
111
  capabilities: opts.caps,
95
112
  providerArgs: opts.providerArgs,
96
- policyName: process.env.AGENT_RELAY_POLICY,
97
- spawnRequestId: process.env.AGENT_RELAY_SPAWN_REQUEST_ID,
98
- automationId: process.env.AGENT_RELAY_AUTOMATION_ID,
99
- automationRunId: process.env.AGENT_RELAY_AUTOMATION_RUN_ID,
100
- lifecycle: normalizeAgentLifecycle(process.env.AGENT_RELAY_LIFECYCLE) ?? "persistent",
113
+ policyName: policyNameFromEnv(),
114
+ spawnRequestId: spawnRequestIdFromEnv(),
115
+ automationId: automationIdFromEnv(),
116
+ automationRunId: automationRunIdFromEnv(),
117
+ lifecycle: normalizeAgentLifecycle(lifecycleFromEnv()) ?? "persistent",
101
118
  startedAt: Date.now(),
102
119
  providerConfig,
103
120
  adapter,
package/src/logger.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { appendFileSync, mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { sanitizeFsName } from "agent-relay-sdk/fs-name";
4
+ import { logLevelFromEnv } from "./config";
4
5
 
5
6
  // Phase 1 observability (#198): one leveled, runtime-togglable logger for the
6
7
  // Runner and the provider adapters below it. Replaces the ad-hoc scatter of
@@ -47,7 +48,7 @@ export class Logger {
47
48
  private logDir: string;
48
49
 
49
50
  constructor(config: LoggerConfig = {}) {
50
- this.level = config.level ?? parseLogLevel(process.env.AGENT_RELAY_LOG_LEVEL) ?? "info";
51
+ this.level = config.level ?? parseLogLevel(logLevelFromEnv()) ?? "info";
51
52
  this.agentId = config.agentId ?? "runner";
52
53
  this.headless = config.headless ?? false;
53
54
  this.logDir = config.logDir ?? join(process.env.HOME || ".", ".agent-relay", "logs");
package/src/outbox.ts CHANGED
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
5
5
  import { sanitizeFsName } from "agent-relay-sdk/fs-name";
6
6
  import { errMessage } from "agent-relay-sdk";
7
7
  import { logger } from "./logger";
8
+ import { runnerOutboxDirFromEnv } from "./config";
8
9
 
9
10
  // Phase 2 (#196) — the "nothing is ever lost" half. Runner→server events that used to be
10
11
  // fire-and-forget over HTTP (session turns, reasoning/tool traces, prompt echoes, insights,
@@ -112,7 +113,7 @@ export class Outbox {
112
113
  this.maxBackoffMs = options.maxBackoffMs ?? DEFAULTS.maxBackoffMs;
113
114
  this.pollMs = options.pollMs ?? DEFAULTS.pollMs;
114
115
 
115
- const dir = options.dir ?? process.env.AGENT_RELAY_RUNNER_OUTBOX_DIR ?? join(tmpdir(), "agent-relay-outbox");
116
+ const dir = options.dir ?? runnerOutboxDirFromEnv() ?? join(tmpdir(), "agent-relay-outbox");
116
117
  this.path = options.dir === ":memory:" ? ":memory:" : join(dir, `outbox-${safeName(this.agentId)}.sqlite`);
117
118
  if (this.path !== ":memory:") mkdirSync(dirname(this.path), { recursive: true });
118
119
 
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
4
4
  import { sanitizeFsName } from "agent-relay-sdk/fs-name";
5
5
  import { profileAllowsRelayFeature, type RunnerSpawnConfig } from "./adapter";
6
6
  import { CLAUDE_RELAY_MANUAL } from "./relay-instructions";
7
+ import { providerHomeRootFromEnv } from "./config";
7
8
 
8
9
  type ProviderHome = {
9
10
  path: string;
@@ -105,7 +106,7 @@ function readHostClaudeConfig(): Record<string, unknown> | undefined {
105
106
  }
106
107
 
107
108
  function providerHomePath(provider: "claude" | "codex", config: RunnerSpawnConfig): string {
108
- const root = process.env.AGENT_RELAY_PROVIDER_HOME_ROOT || join(homedir(), ".agent-relay", "provider-homes");
109
+ const root = providerHomeRootFromEnv();
109
110
  const profileName = sanitizePathPart(config.agentProfile?.name || config.profile || "profile");
110
111
  const instance = sanitizePathPart(config.instanceId || config.runnerId);
111
112
  return join(root, provider, profileName, instance);
@@ -22,6 +22,7 @@ import { ensureSessionScratch, reapSessionScratch, sweepStaleSessions, type Sess
22
22
  import { deliverBufferedMcpCall as deliverBufferedMcpOutboxCall, deliverRunnerOutboxEvent } from "./mcp-outbox";
23
23
  import { RunnerInsights } from "./runner-insights";
24
24
  import { BusyReconciler } from "./busy-reconciler";
25
+ import { capsFromEnv, contextStateDirFromEnv, logLevelFromEnv, mcpProxyEnabledFromEnv, orchestratorBaseDirFromEnv, orchestratorUrlFromEnv, runnerInfoFileFromEnv, runnerLogFileFromEnv, runnerOutboxDirWithInfoFallback, sessionDebugEnabled, tagsFromEnv, workspaceModeFromEnv } from "./config";
25
26
  import {
26
27
  appliedAgentProfileMetadata,
27
28
  commandTimeoutMs,
@@ -272,7 +273,7 @@ export class AgentRunner {
272
273
  private injectedPrompts: Array<{ text: string; at: number }> = [];
273
274
  // Verbose session-mirror diagnostics (turn lifecycle, reconciler probes, tail
274
275
  // emits) → a dedicated clean log. Always on for key transitions; AGENT_RELAY_SESSION_DEBUG=1 adds the high-frequency probe/emit lines.
275
- private readonly sessionDebugVerbose = process.env.AGENT_RELAY_SESSION_DEBUG === "1";
276
+ private readonly sessionDebugVerbose = sessionDebugEnabled();
276
277
  // Tracks whether the provider is in a legitimate blocked/approval state, so the
277
278
  // busy reconciler doesn't mistake a permission prompt for a stuck-busy turn.
278
279
  private providerBlocked = false;
@@ -299,21 +300,20 @@ export class AgentRunner {
299
300
  logger.configure({
300
301
  agentId: this.agentId,
301
302
  headless: options.headless,
302
- ...(this.sessionDebugVerbose && !parseLogLevel(process.env.AGENT_RELAY_LOG_LEVEL) ? { level: "debug" as const } : {}),
303
+ ...(this.sessionDebugVerbose && !parseLogLevel(logLevelFromEnv()) ? { level: "debug" as const } : {}),
303
304
  });
304
305
  this.currentToken = options.token;
305
306
  this.currentTokenJti = options.tokenJti;
306
307
  this.currentTokenProfileId = options.tokenProfileId;
307
308
  this.currentTokenExpiresAt = options.tokenExpiresAt;
308
- this.mcpProxyEnabled = !["0", "false", "off"].includes((process.env.AGENT_RELAY_MCP_PROXY ?? "").trim().toLowerCase());
309
+ this.mcpProxyEnabled = mcpProxyEnabledFromEnv();
309
310
  this.mcpProxySecret = crypto.randomUUID();
310
311
  const runtime = runtimeMetadata(options.provider);
311
312
  this.http = new RelayHttpClient({ baseUrl: options.relayUrl, token: this.currentToken });
312
313
  this.obligationCache = new ReplyObligationCache({ fetch: () => this.http.listReplyObligations(this.agentId) });
313
314
  // Co-locate the durable outbox with the runner's runtime state (survives reboot) when the
314
315
  // orchestrator told us where that is; otherwise the Outbox falls back to a temp dir.
315
- const outboxDir = process.env.AGENT_RELAY_RUNNER_OUTBOX_DIR
316
- ?? (process.env.AGENT_RELAY_RUNNER_INFO_FILE ? join(dirname(process.env.AGENT_RELAY_RUNNER_INFO_FILE), "outbox") : undefined);
316
+ const outboxDir = runnerOutboxDirWithInfoFallback();
317
317
  this.outbox = new Outbox({ agentId: this.agentId, dir: outboxDir, send: (record) => this.deliverOutboxEvent(record) });
318
318
  this.sessionOutbox = new Outbox({
319
319
  agentId: `${this.agentId}-session`,
@@ -359,7 +359,7 @@ export class AgentRunner {
359
359
  ...new Set([
360
360
  ...options.providerConfig.defaultCapabilities,
361
361
  ...options.capabilities,
362
- ...csvTags(process.env.AGENT_RELAY_CAPS),
362
+ ...csvTags(capsFromEnv()),
363
363
  "lifecycle.shutdown.hard",
364
364
  "lifecycle.restart.hard",
365
365
  "lifecycle.status.semantic",
@@ -369,7 +369,7 @@ export class AgentRunner {
369
369
  "capabilities.report",
370
370
  ]),
371
371
  ],
372
- tags: [...new Set([options.provider, ...csvTags(process.env.AGENT_RELAY_TAGS), ...options.tags, ...options.providerConfig.defaultTags, ...(options.headless ? ["headless"] : [])])],
372
+ tags: [...new Set([options.provider, ...csvTags(tagsFromEnv()), ...options.tags, ...options.providerConfig.defaultTags, ...(options.headless ? ["headless"] : [])])],
373
373
  meta: {
374
374
  ...runtime,
375
375
  version: runtime.package.version,
@@ -388,7 +388,7 @@ export class AgentRunner {
388
388
  automationRunId: options.automationRunId ?? null,
389
389
  workspace: options.workspace ?? null,
390
390
  lifecycle: options.lifecycle ?? "persistent",
391
- workspaceMode: options.workspace?.requestedMode ?? options.workspace?.mode ?? process.env.AGENT_RELAY_WORKSPACE_MODE ?? null,
391
+ workspaceMode: options.workspace?.requestedMode ?? options.workspace?.mode ?? workspaceModeFromEnv() ?? null,
392
392
  runnerManaged: true,
393
393
  cwd: options.cwd,
394
394
  approvalMode: options.approvalMode,
@@ -471,7 +471,7 @@ export class AgentRunner {
471
471
  reapSessionScratch({
472
472
  agentId: this.agentId,
473
473
  cwd: this.options.cwd,
474
- fallbackBaseDir: process.env.AGENT_RELAY_ORCHESTRATOR_BASE_DIR,
474
+ fallbackBaseDir: orchestratorBaseDirFromEnv(),
475
475
  });
476
476
  if (!alreadyStopped) await this.bus.statusAsync({ agentStatus: "offline", ready: false });
477
477
  if (this.process && !alreadyStopped) {
@@ -528,7 +528,7 @@ export class AgentRunner {
528
528
  }
529
529
 
530
530
  private ownsIsolatedWorktree(): boolean {
531
- const mode = this.options.workspace?.requestedMode ?? this.options.workspace?.mode ?? process.env.AGENT_RELAY_WORKSPACE_MODE;
531
+ const mode = this.options.workspace?.requestedMode ?? this.options.workspace?.mode ?? workspaceModeFromEnv();
532
532
  return mode === "isolated";
533
533
  }
534
534
 
@@ -607,7 +607,7 @@ export class AgentRunner {
607
607
  }
608
608
 
609
609
  private writeRunnerInfoFile(): void {
610
- const file = process.env.AGENT_RELAY_RUNNER_INFO_FILE;
610
+ const file = runnerInfoFileFromEnv();
611
611
  if (!file || !this.control) return;
612
612
  try {
613
613
  mkdirSync(dirname(file), { recursive: true });
@@ -1101,7 +1101,7 @@ export class AgentRunner {
1101
1101
  const tmuxSession = typeof this.process?.meta?.tmuxSession === "string" ? this.process.meta.tmuxSession : undefined;
1102
1102
  const tmuxSocket = typeof this.process?.meta?.tmuxSocket === "string" ? this.process.meta.tmuxSocket : undefined;
1103
1103
  const exitSource = tmuxSession ? "tmux-session-ended" : this.process?.process ? "process-exit" : "provider-status";
1104
- const logFile = typeof process.env.AGENT_RELAY_LOG_FILE === "string" ? process.env.AGENT_RELAY_LOG_FILE : undefined;
1104
+ const logFile = runnerLogFileFromEnv();
1105
1105
  const claudeResumeId = this.options.provider === "claude" && logFile ? latestClaudeResumeIdFromLogFile(logFile) : undefined;
1106
1106
  return {
1107
1107
  status,
@@ -1820,7 +1820,7 @@ export class AgentRunner {
1820
1820
  automationId: this.options.automationId ?? null,
1821
1821
  automationRunId: this.options.automationRunId ?? null,
1822
1822
  workspace: this.options.workspace ?? null,
1823
- lifecycle: this.options.lifecycle ?? "persistent", workspaceMode: this.options.workspace?.requestedMode ?? this.options.workspace?.mode ?? process.env.AGENT_RELAY_WORKSPACE_MODE ?? null,
1823
+ lifecycle: this.options.lifecycle ?? "persistent", workspaceMode: this.options.workspace?.requestedMode ?? this.options.workspace?.mode ?? workspaceModeFromEnv() ?? null,
1824
1824
  lifecycleAction: this.lifecycleAction ?? null,
1825
1825
  profile: this.options.profile ?? null,
1826
1826
  ...(status === "error" ? { terminalStatus: "error" } : {}),
@@ -1938,7 +1938,7 @@ export class AgentRunner {
1938
1938
  this.scratch = ensureSessionScratch({
1939
1939
  agentId: this.agentId,
1940
1940
  cwd: this.options.cwd,
1941
- fallbackBaseDir: process.env.AGENT_RELAY_ORCHESTRATOR_BASE_DIR,
1941
+ fallbackBaseDir: orchestratorBaseDirFromEnv(),
1942
1942
  });
1943
1943
  } catch (error) {
1944
1944
  this.logRunnerDiagnostic(`session scratch setup failed: ${errMessage(error)}`);
@@ -1952,7 +1952,7 @@ export class AgentRunner {
1952
1952
  keep.add(this.agentId);
1953
1953
  const removed = sweepStaleSessions({
1954
1954
  cwd: this.options.cwd,
1955
- fallbackBaseDir: process.env.AGENT_RELAY_ORCHESTRATOR_BASE_DIR,
1955
+ fallbackBaseDir: orchestratorBaseDirFromEnv(),
1956
1956
  keepAgentIds: keep,
1957
1957
  now: Date.now(),
1958
1958
  });
@@ -2004,7 +2004,7 @@ export class AgentRunner {
2004
2004
  // is already expired — the orchestrator's standing credential is the authority.
2005
2005
  private canRemintViaOrchestrator(): boolean {
2006
2006
  return Boolean(
2007
- process.env.AGENT_RELAY_ORCHESTRATOR_URL &&
2007
+ orchestratorUrlFromEnv() &&
2008
2008
  this.currentToken &&
2009
2009
  (this.currentTokenProfileId === "provider-agent" || this.currentTokenProfileId === "provider-interactive"),
2010
2010
  );
@@ -2079,7 +2079,7 @@ export class AgentRunner {
2079
2079
  // (possibly expired) token; the orchestrator re-mints it via the relay using its
2080
2080
  // standing credential. Returns true on success.
2081
2081
  private async remintViaOrchestrator(): Promise<boolean> {
2082
- const orchUrl = process.env.AGENT_RELAY_ORCHESTRATOR_URL;
2082
+ const orchUrl = orchestratorUrlFromEnv();
2083
2083
  if (!orchUrl || !this.currentToken) return false;
2084
2084
  try {
2085
2085
  const res = await fetch(`${orchUrl.replace(/\/+$/, "")}/api/runtime-tokens/runner-renew`, {
@@ -2153,7 +2153,7 @@ export class AgentRunner {
2153
2153
  }
2154
2154
 
2155
2155
  private latestProbeMetrics() {
2156
- const stateDir = process.env.AGENT_RELAY_CONTEXT_STATE_DIR;
2156
+ const stateDir = contextStateDirFromEnv();
2157
2157
  return readContextProbeState(this.agentId, stateDir);
2158
2158
  }
2159
2159