agent-relay-runner 0.76.0 → 0.77.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
- "version": "0.76.0",
3
+ "version": "0.77.0",
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.51"
23
+ "agent-relay-sdk": "0.2.52"
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.76.0",
4
+ "version": "0.77.0",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
@@ -47,7 +47,7 @@ export class ClaudeAdapter implements ProviderAdapter {
47
47
  stderr: "inherit",
48
48
  });
49
49
  void proc.exited.then((code) => this.statusCb(code === 0 ? "offline" : "error"));
50
- return { pid: proc.pid, process: proc, meta: { monitor: config.monitor } };
50
+ return { pid: proc.pid, process: proc, meta: { monitor: config.monitor, env: args.env } };
51
51
  }
52
52
 
53
53
  async shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<void> {
@@ -335,9 +335,8 @@ export class ClaudeAdapter implements ProviderAdapter {
335
335
  }
336
336
 
337
337
  return {
338
- pid: undefined,
339
- process: undefined,
340
- meta: {
338
+ pid: undefined, process: undefined, meta: {
339
+ env: spawnArgs.env,
341
340
  monitor: config.monitor,
342
341
  // When the profile disables the relay plugin, the bundled monitor never
343
342
  // loads, so no monitor will ever connect — deliver() must use tmux injection.
package/src/quota.ts CHANGED
@@ -1,20 +1,46 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import type { ContextProbeMetrics, QuotaState, QuotaWindowName } from "agent-relay-sdk";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { homedir, hostname } from "node:os";
5
+ import { dirname, join } from "node:path";
6
+ import type { ContextProbeMetrics, ProviderQuotaError, ProviderQuotaUpdateInput, QuotaState, QuotaWindowName } from "agent-relay-sdk";
5
7
  import { errMessage, isRecord, quotaStateFromProbeMetrics, stringValue } from "agent-relay-sdk";
8
+ import { sanitizeFsName } from "agent-relay-sdk/fs-name";
6
9
  import type { ManagedProcess } from "./adapter";
10
+ import { agentRelayHome, runnerOutboxDirWithInfoFallback } from "./config";
7
11
 
8
12
  const CLAUDE_USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
9
13
  const CLAUDE_OAUTH_BETA = "oauth-2025-04-20";
10
- const QUOTA_POLL_INTERVAL_MS = 15 * 60 * 1000;
14
+ const QUOTA_POLL_INTERVAL_MS = 5 * 60 * 1000;
15
+ const QUOTA_FAST_RETRY_MS = 15 * 1000;
11
16
  const QUOTA_FAILURE_LOG_INTERVAL_MS = 5 * 60 * 1000;
17
+ const QUOTA_LOCK_STALE_MS = 60 * 1000;
12
18
 
13
19
  type CodexQuotaClient = {
14
20
  isConnected?: () => boolean;
15
21
  accountRateLimitsRead?: () => Promise<unknown>;
16
22
  };
17
23
 
24
+ type QuotaIdentity = {
25
+ provider: string;
26
+ accountKey: string;
27
+ credentialsPath?: string;
28
+ };
29
+
30
+ type RunnerQuotaSample = {
31
+ quota?: QuotaState;
32
+ accountKey?: string;
33
+ };
34
+
35
+ type SharedQuotaEntry = {
36
+ provider: string;
37
+ accountKey: string;
38
+ lastAttemptAt?: number;
39
+ nextAttemptAt?: number;
40
+ quota?: QuotaState;
41
+ lastError?: ProviderQuotaError | null;
42
+ };
43
+
18
44
  export class RunnerQuotaPoller {
19
45
  private quotaState?: QuotaState;
20
46
  private timer?: Timer;
@@ -28,6 +54,9 @@ export class RunnerQuotaPoller {
28
54
  getProcess: () => ManagedProcess | undefined;
29
55
  onUpdate: () => void;
30
56
  log: (message: string) => void;
57
+ reportProviderQuota?: (input: ProviderQuotaUpdateInput) => Promise<unknown>;
58
+ quotaStateDir?: string;
59
+ fetchImpl?: typeof fetch;
31
60
  }) {}
32
61
 
33
62
  get current(): QuotaState | undefined {
@@ -64,26 +93,91 @@ export class RunnerQuotaPoller {
64
93
  private async refresh(): Promise<void> {
65
94
  if (this.inFlight) return;
66
95
  this.inFlight = true;
96
+ const now = Date.now();
97
+ let identity: QuotaIdentity | undefined;
98
+ let lockDir: string | undefined;
67
99
  try {
68
- const quota = await collectRunnerQuotaState({
100
+ const process = this.options.getProcess();
101
+ identity = await resolveRunnerQuotaIdentity({
102
+ provider: this.options.provider,
103
+ process,
104
+ });
105
+ const attempt = beginSharedQuotaAttempt(identity, this.options.quotaStateDir, now);
106
+ if (!attempt.poll) {
107
+ if (attempt.entry?.quota && this.active) {
108
+ this.quotaState = attempt.entry.quota;
109
+ this.options.onUpdate();
110
+ }
111
+ this.schedule(attempt.retryInMs);
112
+ return;
113
+ }
114
+ lockDir = attempt.lockDir;
115
+ const sample = await collectRunnerQuotaSample({
69
116
  provider: this.options.provider,
70
117
  agentId: this.options.agentId,
71
- process: this.options.getProcess(),
118
+ process,
119
+ credentialsPath: identity.credentialsPath,
120
+ fetchImpl: this.options.fetchImpl,
72
121
  });
73
- if (quota && this.active) {
122
+ const quota = sample.quota;
123
+ if (!quota) throw new QuotaCollectionError("creds_not_ready", `${this.options.provider} quota source is not ready`);
124
+ if (this.active) {
125
+ const update: ProviderQuotaUpdateInput = {
126
+ provider: identity.provider,
127
+ accountKey: sample.accountKey ?? identity.accountKey,
128
+ quota,
129
+ lastAttemptAt: quota.updatedAt,
130
+ sourceAgentId: this.options.agentId,
131
+ };
132
+ await this.reportProviderQuota(update);
133
+ finishSharedQuotaAttempt(identity, {
134
+ provider: update.provider,
135
+ accountKey: update.accountKey,
136
+ quota,
137
+ lastAttemptAt: update.lastAttemptAt,
138
+ nextAttemptAt: now + QUOTA_POLL_INTERVAL_MS,
139
+ lastError: null,
140
+ }, this.options.quotaStateDir);
74
141
  this.quotaState = quota;
75
142
  this.options.onUpdate();
76
143
  }
77
144
  this.schedule();
78
145
  } catch (error) {
79
146
  const retryAfterMs = quotaRetryAfterMs(error);
147
+ const lastError = providerQuotaErrorFromCollectorError(error, retryAfterMs);
148
+ const lastAttemptAt = Date.now();
149
+ const retryDelayMs = retryAfterMs ?? (identity && !readSharedQuotaEntry(identity, this.options.quotaStateDir)?.lastAttemptAt ? QUOTA_FAST_RETRY_MS : QUOTA_POLL_INTERVAL_MS);
150
+ if (identity) {
151
+ await this.reportProviderQuota({
152
+ provider: identity.provider,
153
+ accountKey: identity.accountKey,
154
+ lastAttemptAt,
155
+ lastError,
156
+ sourceAgentId: this.options.agentId,
157
+ }).catch((publishError) => {
158
+ this.options.log(`quota status publish failed: ${errMessage(publishError)}`);
159
+ });
160
+ finishSharedQuotaAttempt(identity, {
161
+ provider: identity.provider,
162
+ accountKey: identity.accountKey,
163
+ lastAttemptAt,
164
+ nextAttemptAt: lastAttemptAt + retryDelayMs,
165
+ lastError,
166
+ }, this.options.quotaStateDir);
167
+ }
80
168
  if (this.active) this.logFailure(error, retryAfterMs);
81
- this.schedule(retryAfterMs ?? QUOTA_POLL_INTERVAL_MS);
169
+ this.schedule(retryDelayMs);
82
170
  } finally {
171
+ if (lockDir) releaseQuotaLock(lockDir);
83
172
  this.inFlight = false;
84
173
  }
85
174
  }
86
175
 
176
+ private async reportProviderQuota(input: ProviderQuotaUpdateInput): Promise<void> {
177
+ if (!this.options.reportProviderQuota) return;
178
+ await this.options.reportProviderQuota(input);
179
+ }
180
+
87
181
  private logFailure(error: unknown, retryAfterMs: number | undefined): void {
88
182
  const key = retryAfterMs !== undefined ? `retry-after:${retryAfterMs}` : errMessage(error);
89
183
  const now = Date.now();
@@ -101,31 +195,91 @@ export class QuotaRetryAfterError extends Error {
101
195
  }
102
196
  }
103
197
 
104
- export function quotaRetryAfterMs(error: unknown): number | undefined {
198
+ class QuotaCollectionError extends Error {
199
+ constructor(readonly type: ProviderQuotaError["type"], message: string) {
200
+ super(message);
201
+ this.name = "QuotaCollectionError";
202
+ }
203
+ }
204
+
205
+ function quotaRetryAfterMs(error: unknown): number | undefined {
105
206
  return error instanceof QuotaRetryAfterError ? error.retryAfterMs : undefined;
106
207
  }
107
208
 
108
- export async function collectRunnerQuotaState(input: {
209
+ function providerQuotaErrorFromCollectorError(error: unknown, retryAfterMs: number | undefined): ProviderQuotaError {
210
+ if (retryAfterMs !== undefined) {
211
+ return { type: "rate_limited", message: errMessage(error), retryAfterMs };
212
+ }
213
+ if (error instanceof QuotaCollectionError) {
214
+ return { type: error.type, message: error.message };
215
+ }
216
+ return { type: "other", message: errMessage(error) };
217
+ }
218
+
219
+ export async function resolveRunnerQuotaIdentity(input: {
220
+ provider: string;
221
+ process?: ManagedProcess;
222
+ }): Promise<QuotaIdentity> {
223
+ const provider = input.provider.trim().toLowerCase();
224
+ if (provider === "claude") {
225
+ const credentialsPath = claudeCredentialsPathFromProcess(input.process);
226
+ const token = await readClaudeOAuthAccessToken(credentialsPath);
227
+ return {
228
+ provider,
229
+ accountKey: token ? `credential:${shortHash(token)}` : hostProviderAccountKey(provider),
230
+ credentialsPath,
231
+ };
232
+ }
233
+ return {
234
+ provider,
235
+ accountKey: codexAccountKeyFromProcess(input.process) ?? hostProviderAccountKey(provider),
236
+ };
237
+ }
238
+
239
+ async function collectRunnerQuotaSample(input: {
109
240
  provider: string;
110
241
  agentId: string;
111
242
  process?: ManagedProcess;
112
- }): Promise<QuotaState | undefined> {
113
- if (input.provider === "claude") return collectClaudeQuotaState({ agentId: input.agentId });
114
- if (input.provider !== "codex") return undefined;
243
+ credentialsPath?: string;
244
+ fetchImpl?: typeof fetch;
245
+ }): Promise<RunnerQuotaSample> {
246
+ if (input.provider === "claude") return collectClaudeQuotaSample({ agentId: input.agentId, credentialsPath: input.credentialsPath, fetchImpl: input.fetchImpl });
247
+ if (input.provider !== "codex") return {};
115
248
  const client = input.process?.meta?.client as CodexQuotaClient | undefined;
116
- if (!client?.isConnected?.() || typeof client.accountRateLimitsRead !== "function") return undefined;
117
- return collectCodexQuotaState({
249
+ if (!client?.isConnected?.() || typeof client.accountRateLimitsRead !== "function") return {};
250
+ return collectCodexQuotaSample({
118
251
  agentId: codexProcessAgentId(input.process, input.agentId),
119
252
  rateLimitsRead: () => client.accountRateLimitsRead!(),
120
253
  });
121
254
  }
122
255
 
256
+ async function collectClaudeQuotaSample(options: {
257
+ agentId: string;
258
+ credentialsPath?: string;
259
+ fetchImpl?: typeof fetch;
260
+ now?: number;
261
+ }): Promise<RunnerQuotaSample> {
262
+ const payload = await fetchClaudeUsagePayload(options);
263
+ if (payload === undefined) return {};
264
+ return {
265
+ quota: claudeUsageQuotaState(payload, options.agentId, options.now ?? Date.now()),
266
+ accountKey: providerPayloadAccountKey(payload),
267
+ };
268
+ }
269
+
123
270
  export async function collectClaudeQuotaState(options: {
124
271
  agentId: string;
125
272
  credentialsPath?: string;
126
273
  fetchImpl?: typeof fetch;
127
274
  now?: number;
128
275
  }): Promise<QuotaState | undefined> {
276
+ return (await collectClaudeQuotaSample(options)).quota;
277
+ }
278
+
279
+ async function fetchClaudeUsagePayload(options: {
280
+ credentialsPath?: string;
281
+ fetchImpl?: typeof fetch;
282
+ }): Promise<unknown | undefined> {
129
283
  const accessToken = await readClaudeOAuthAccessToken(options.credentialsPath ?? defaultClaudeCredentialsPath());
130
284
  if (!accessToken) return undefined;
131
285
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -140,8 +294,9 @@ export async function collectClaudeQuotaState(options: {
140
294
  if (response.status === 429) {
141
295
  throw new QuotaRetryAfterError(retryAfterHeaderMs(response.headers.get("retry-after")) ?? 60_000, "Claude quota source returned 429");
142
296
  }
143
- if (!response.ok) return undefined;
144
- return claudeUsageQuotaState(await response.json(), options.agentId, options.now ?? Date.now());
297
+ if (response.status === 401 || response.status === 403) throw new QuotaCollectionError("auth", `Claude quota source returned ${response.status}`);
298
+ if (!response.ok) throw new QuotaCollectionError("other", `Claude quota source returned ${response.status}`);
299
+ return response.json();
145
300
  }
146
301
 
147
302
  export function claudeUsageQuotaState(payload: unknown, agentId: string, now = Date.now()): QuotaState | undefined {
@@ -163,8 +318,20 @@ export async function collectCodexQuotaState(input: {
163
318
  rateLimitsRead: () => Promise<unknown>;
164
319
  now?: number;
165
320
  }): Promise<QuotaState | undefined> {
321
+ return (await collectCodexQuotaSample(input)).quota;
322
+ }
323
+
324
+ async function collectCodexQuotaSample(input: {
325
+ agentId: string;
326
+ rateLimitsRead: () => Promise<unknown>;
327
+ now?: number;
328
+ }): Promise<RunnerQuotaSample> {
166
329
  try {
167
- return codexRateLimitsQuotaState(await input.rateLimitsRead(), input.agentId, input.now ?? Date.now());
330
+ const payload = await input.rateLimitsRead();
331
+ return {
332
+ quota: codexRateLimitsQuotaState(payload, input.agentId, input.now ?? Date.now()),
333
+ accountKey: providerPayloadAccountKey(payload),
334
+ };
168
335
  } catch (error) {
169
336
  const retryAfter = codexRetryAfterMs(error);
170
337
  if (retryAfter !== undefined) throw new QuotaRetryAfterError(retryAfter, "Codex quota source returned 429");
@@ -190,7 +357,143 @@ export function codexRateLimitsQuotaState(payload: unknown, agentId: string, now
190
357
  }
191
358
 
192
359
  function defaultClaudeCredentialsPath(): string {
193
- return join(homedir(), ".claude", ".credentials.json");
360
+ return join(defaultClaudeConfigDir(), ".credentials.json");
361
+ }
362
+
363
+ export function defaultClaudeConfigDir(): string {
364
+ return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
365
+ }
366
+
367
+ function claudeCredentialsPathFromProcess(proc: ManagedProcess | undefined): string {
368
+ const env = processEnvFromMeta(proc);
369
+ const configDir = stringValue(env?.CLAUDE_CONFIG_DIR) ?? process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude");
370
+ return join(configDir, ".credentials.json");
371
+ }
372
+
373
+ function codexAccountKeyFromProcess(proc: ManagedProcess | undefined): string | undefined {
374
+ const env = processEnvFromMeta(proc);
375
+ const codexHome = stringValue(env?.CODEX_HOME) ?? process.env.CODEX_HOME;
376
+ return codexHome ? `home:${shortHash(codexHome)}` : undefined;
377
+ }
378
+
379
+ function processEnvFromMeta(proc: ManagedProcess | undefined): Record<string, unknown> | undefined {
380
+ const direct = isRecord(proc?.meta?.env) ? proc.meta.env : undefined;
381
+ if (direct) return direct;
382
+ const config = isRecord(proc?.meta?.config) ? proc.meta.config : undefined;
383
+ return isRecord(config?.env) ? config.env : undefined;
384
+ }
385
+
386
+ function hostProviderAccountKey(provider: string): string {
387
+ return `host:${hostname()}:${provider}`;
388
+ }
389
+
390
+ export function beginSharedQuotaAttempt(identity: QuotaIdentity, stateDir?: string, now = Date.now()): {
391
+ poll: boolean;
392
+ retryInMs: number;
393
+ entry?: SharedQuotaEntry;
394
+ lockDir?: string;
395
+ } {
396
+ const entry = readSharedQuotaEntry(identity, stateDir);
397
+ const nextAttemptAt = entry?.nextAttemptAt;
398
+ if (nextAttemptAt !== undefined && nextAttemptAt > now) {
399
+ return { poll: false, retryInMs: nextAttemptAt - now, entry };
400
+ }
401
+
402
+ const lockDir = sharedQuotaLockDir(identity, stateDir);
403
+ if (!acquireQuotaLock(lockDir, now)) {
404
+ return { poll: false, retryInMs: QUOTA_FAST_RETRY_MS, entry };
405
+ }
406
+ return { poll: true, retryInMs: 0, entry, lockDir };
407
+ }
408
+
409
+ function finishSharedQuotaAttempt(identity: QuotaIdentity, entry: SharedQuotaEntry, stateDir?: string): void {
410
+ const path = sharedQuotaStatePath(identity, stateDir);
411
+ mkdirSync(dirname(path), { recursive: true });
412
+ writeFileSync(path, JSON.stringify(entry, null, 2));
413
+ }
414
+
415
+ function readSharedQuotaEntry(identity: QuotaIdentity, stateDir?: string): SharedQuotaEntry | undefined {
416
+ try {
417
+ const parsed = JSON.parse(readFileSync(sharedQuotaStatePath(identity, stateDir), "utf8")) as unknown;
418
+ if (!isRecord(parsed)) return undefined;
419
+ const provider = stringValue(parsed.provider);
420
+ const accountKey = stringValue(parsed.accountKey);
421
+ if (!provider || !accountKey) return undefined;
422
+ return {
423
+ provider,
424
+ accountKey,
425
+ ...(typeof parsed.lastAttemptAt === "number" && Number.isFinite(parsed.lastAttemptAt) ? { lastAttemptAt: parsed.lastAttemptAt } : {}),
426
+ ...(typeof parsed.nextAttemptAt === "number" && Number.isFinite(parsed.nextAttemptAt) ? { nextAttemptAt: parsed.nextAttemptAt } : {}),
427
+ ...(isQuotaState(parsed.quota) ? { quota: parsed.quota } : {}),
428
+ ...(parsed.lastError === null || isRecord(parsed.lastError) ? { lastError: parsed.lastError as ProviderQuotaError | null } : {}),
429
+ };
430
+ } catch {
431
+ return undefined;
432
+ }
433
+ }
434
+
435
+ function sharedQuotaStatePath(identity: QuotaIdentity, stateDir?: string): string {
436
+ return join(sharedQuotaStateDir(stateDir), `${sharedQuotaKey(identity)}.json`);
437
+ }
438
+
439
+ function sharedQuotaLockDir(identity: QuotaIdentity, stateDir?: string): string {
440
+ return join(sharedQuotaStateDir(stateDir), `${sharedQuotaKey(identity)}.lock`);
441
+ }
442
+
443
+ function sharedQuotaStateDir(stateDir?: string): string {
444
+ return stateDir ?? join(runnerOutboxDirWithInfoFallback() ?? agentRelayHome(), "quota");
445
+ }
446
+
447
+ function sharedQuotaKey(identity: QuotaIdentity): string {
448
+ return sanitizeFsName(`${identity.provider}-${identity.accountKey}`);
449
+ }
450
+
451
+ function acquireQuotaLock(lockDir: string, now = Date.now()): boolean {
452
+ try {
453
+ mkdirSync(lockDir, { recursive: false });
454
+ writeFileSync(join(lockDir, "owner"), String(now));
455
+ return true;
456
+ } catch {
457
+ const ownerPath = join(lockDir, "owner");
458
+ try {
459
+ const lockedAt = Number(readFileSync(ownerPath, "utf8"));
460
+ if (Number.isFinite(lockedAt) && now - lockedAt <= QUOTA_LOCK_STALE_MS) return false;
461
+ rmSync(lockDir, { recursive: true, force: true });
462
+ mkdirSync(lockDir, { recursive: false });
463
+ writeFileSync(ownerPath, String(now));
464
+ return true;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+ }
470
+
471
+ function releaseQuotaLock(lockDir: string): void {
472
+ rmSync(lockDir, { recursive: true, force: true });
473
+ }
474
+
475
+ function isQuotaState(value: unknown): value is QuotaState {
476
+ return isRecord(value) && Array.isArray(value.windows) && typeof value.updatedAt === "number";
477
+ }
478
+
479
+ export function providerPayloadAccountKey(payload: unknown): string | undefined {
480
+ if (!isRecord(payload)) return undefined;
481
+ const direct = stringValue(payload.accountKey) ??
482
+ stringValue(payload.account_key) ??
483
+ stringValue(payload.accountId) ??
484
+ stringValue(payload.account_id) ??
485
+ stringValue(payload.orgId) ??
486
+ stringValue(payload.org_id) ??
487
+ stringValue(payload.organizationId) ??
488
+ stringValue(payload.organization_id);
489
+ if (direct) return direct;
490
+ const account = isRecord(payload.account) ? payload.account : undefined;
491
+ const org = isRecord(payload.org) ? payload.org : isRecord(payload.organization) ? payload.organization : undefined;
492
+ return stringValue(account?.id) ?? stringValue(org?.id);
493
+ }
494
+
495
+ function shortHash(value: string): string {
496
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
194
497
  }
195
498
 
196
499
  function quotaProviderSupported(provider: string): boolean {
@@ -353,7 +353,7 @@ export class AgentRunner {
353
353
  clearProviderTurn: (reason) => this.forceClearProviderTurn(reason),
354
354
  sessionDebug: (message) => this.sessionDebug(message),
355
355
  });
356
- this.quotaPoller = new RunnerQuotaPoller({ provider: options.provider, agentId: this.agentId, getProcess: () => this.process, onUpdate: () => this.publishStatus(), log: (message) => this.logRunnerDiagnostic(message) });
356
+ this.quotaPoller = new RunnerQuotaPoller({ provider: options.provider, agentId: this.agentId, getProcess: () => this.process, onUpdate: () => this.publishStatus(), log: (message) => this.logRunnerDiagnostic(message), reportProviderQuota: (input) => this.http.upsertProviderQuota(input).then(() => {}) });
357
357
  this.bus = new RelayBusClient({
358
358
  url: relayBusUrl(options.relayUrl),
359
359
  role: "provider",
@@ -2126,7 +2126,6 @@ export class AgentRunner {
2126
2126
  ...(terminalSocket ? { tmuxSocket: terminalSocket } : {}),
2127
2127
  };
2128
2128
  if (context) meta.context = context;
2129
- if (quota) meta.quota = quota;
2130
2129
  return meta;
2131
2130
  }
2132
2131