agent-relay-runner 0.77.0 → 0.77.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.77.0",
3
+ "version": "0.77.1",
4
4
  "description": "Unified provider lifecycle runner for Agent Relay",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.77.0",
4
+ "version": "0.77.1",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
package/src/quota.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { createHash } from "node:crypto";
3
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { homedir, hostname } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import type { ContextProbeMetrics, ProviderQuotaError, ProviderQuotaUpdateInput, QuotaState, QuotaWindowName } from "agent-relay-sdk";
@@ -46,6 +46,7 @@ export class RunnerQuotaPoller {
46
46
  private timer?: Timer;
47
47
  private inFlight = false;
48
48
  private active = false;
49
+ private activeLockDir?: string;
49
50
  private lastLog?: { key: string; at: number };
50
51
 
51
52
  constructor(private readonly options: {
@@ -74,6 +75,9 @@ export class RunnerQuotaPoller {
74
75
  this.active = false;
75
76
  if (this.timer) clearTimeout(this.timer);
76
77
  this.timer = undefined;
78
+ const lockDir = this.activeLockDir;
79
+ this.activeLockDir = undefined;
80
+ if (lockDir) releaseQuotaLock(lockDir);
77
81
  }
78
82
 
79
83
  clear(): void {
@@ -112,6 +116,7 @@ export class RunnerQuotaPoller {
112
116
  return;
113
117
  }
114
118
  lockDir = attempt.lockDir;
119
+ this.activeLockDir = lockDir;
115
120
  const sample = await collectRunnerQuotaSample({
116
121
  provider: this.options.provider,
117
122
  agentId: this.options.agentId,
@@ -147,7 +152,7 @@ export class RunnerQuotaPoller {
147
152
  const lastError = providerQuotaErrorFromCollectorError(error, retryAfterMs);
148
153
  const lastAttemptAt = Date.now();
149
154
  const retryDelayMs = retryAfterMs ?? (identity && !readSharedQuotaEntry(identity, this.options.quotaStateDir)?.lastAttemptAt ? QUOTA_FAST_RETRY_MS : QUOTA_POLL_INTERVAL_MS);
150
- if (identity) {
155
+ if (identity && this.active) {
151
156
  await this.reportProviderQuota({
152
157
  provider: identity.provider,
153
158
  accountKey: identity.accountKey,
@@ -168,7 +173,10 @@ export class RunnerQuotaPoller {
168
173
  if (this.active) this.logFailure(error, retryAfterMs);
169
174
  this.schedule(retryDelayMs);
170
175
  } finally {
171
- if (lockDir) releaseQuotaLock(lockDir);
176
+ if (lockDir && this.activeLockDir === lockDir) {
177
+ releaseQuotaLock(lockDir);
178
+ this.activeLockDir = undefined;
179
+ }
172
180
  this.inFlight = false;
173
181
  }
174
182
  }
@@ -223,16 +231,15 @@ export async function resolveRunnerQuotaIdentity(input: {
223
231
  const provider = input.provider.trim().toLowerCase();
224
232
  if (provider === "claude") {
225
233
  const credentialsPath = claudeCredentialsPathFromProcess(input.process);
226
- const token = await readClaudeOAuthAccessToken(credentialsPath);
227
234
  return {
228
235
  provider,
229
- accountKey: token ? `credential:${shortHash(token)}` : hostProviderAccountKey(provider),
236
+ accountKey: hostProviderAccountKey(),
230
237
  credentialsPath,
231
238
  };
232
239
  }
233
240
  return {
234
241
  provider,
235
- accountKey: codexAccountKeyFromProcess(input.process) ?? hostProviderAccountKey(provider),
242
+ accountKey: codexAccountKeyFromProcess(input.process) ?? hostProviderAccountKey(),
236
243
  };
237
244
  }
238
245
 
@@ -383,8 +390,8 @@ function processEnvFromMeta(proc: ManagedProcess | undefined): Record<string, un
383
390
  return isRecord(config?.env) ? config.env : undefined;
384
391
  }
385
392
 
386
- function hostProviderAccountKey(provider: string): string {
387
- return `host:${hostname()}:${provider}`;
393
+ function hostProviderAccountKey(): string {
394
+ return `host:${hostname()}`;
388
395
  }
389
396
 
390
397
  export function beginSharedQuotaAttempt(identity: QuotaIdentity, stateDir?: string, now = Date.now()): {
@@ -449,16 +456,17 @@ function sharedQuotaKey(identity: QuotaIdentity): string {
449
456
  }
450
457
 
451
458
  function acquireQuotaLock(lockDir: string, now = Date.now()): boolean {
459
+ mkdirSync(dirname(lockDir), { recursive: true });
452
460
  try {
453
461
  mkdirSync(lockDir, { recursive: false });
454
462
  writeFileSync(join(lockDir, "owner"), String(now));
455
463
  return true;
456
464
  } catch {
457
465
  const ownerPath = join(lockDir, "owner");
466
+ const lockedAt = quotaLockTimestamp(lockDir, ownerPath);
467
+ if (lockedAt !== undefined && now - lockedAt <= QUOTA_LOCK_STALE_MS) return false;
468
+ rmSync(lockDir, { recursive: true, force: true });
458
469
  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
470
  mkdirSync(lockDir, { recursive: false });
463
471
  writeFileSync(ownerPath, String(now));
464
472
  return true;
@@ -472,6 +480,18 @@ function releaseQuotaLock(lockDir: string): void {
472
480
  rmSync(lockDir, { recursive: true, force: true });
473
481
  }
474
482
 
483
+ function quotaLockTimestamp(lockDir: string, ownerPath: string): number | undefined {
484
+ try {
485
+ const lockedAt = Number(readFileSync(ownerPath, "utf8"));
486
+ if (Number.isFinite(lockedAt)) return lockedAt;
487
+ } catch {}
488
+ try {
489
+ return statSync(lockDir).mtimeMs;
490
+ } catch {
491
+ return undefined;
492
+ }
493
+ }
494
+
475
495
  function isQuotaState(value: unknown): value is QuotaState {
476
496
  return isRecord(value) && Array.isArray(value.windows) && typeof value.updatedAt === "number";
477
497
  }