sentinelayer-cli 0.34.5 → 0.34.6

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": "sentinelayer-cli",
3
- "version": "0.34.5",
3
+ "version": "0.34.6",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -349,6 +349,65 @@ function recordCircuitSuccess(circuit) {
349
349
  persistCircuitState();
350
350
  }
351
351
 
352
+ function responseHeader(response, name) {
353
+ const headers = response?.headers;
354
+ if (!headers || !name) return "";
355
+ if (typeof headers.get === "function") {
356
+ return normalizeString(headers.get(name));
357
+ }
358
+ const lower = String(name).toLowerCase();
359
+ if (headers instanceof Map) {
360
+ for (const [key, value] of headers.entries()) {
361
+ if (String(key).toLowerCase() === lower) return normalizeString(value);
362
+ }
363
+ return "";
364
+ }
365
+ if (typeof headers === "object") {
366
+ for (const [key, value] of Object.entries(headers)) {
367
+ if (String(key).toLowerCase() === lower) return normalizeString(value);
368
+ }
369
+ }
370
+ return "";
371
+ }
372
+
373
+ function retryAfterMsFromResponse(response, payload, nowMs = Date.now()) {
374
+ const retryAfterMs = [
375
+ payload?.retryAfterMs,
376
+ payload?.error?.retryAfterMs,
377
+ payload?.detail?.retryAfterMs,
378
+ ].map(Number).find((value) => Number.isFinite(value) && value > 0);
379
+ if (Number.isFinite(retryAfterMs) && retryAfterMs > 0) {
380
+ return Math.ceil(retryAfterMs);
381
+ }
382
+
383
+ const retryAfterSeconds = [
384
+ payload?.retryAfterSeconds,
385
+ payload?.error?.retryAfterSeconds,
386
+ payload?.detail?.retryAfterSeconds,
387
+ ].map(Number).find((value) => Number.isFinite(value) && value > 0);
388
+ if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
389
+ return Math.ceil(retryAfterSeconds * 1000);
390
+ }
391
+
392
+ const header = responseHeader(response, "Retry-After");
393
+ if (!header) return null;
394
+ const seconds = Number(header);
395
+ if (Number.isFinite(seconds) && seconds >= 0) {
396
+ return Math.ceil(seconds * 1000);
397
+ }
398
+ const retryAtMs = Date.parse(header);
399
+ if (Number.isFinite(retryAtMs)) {
400
+ return Math.max(0, Math.ceil(retryAtMs - nowMs));
401
+ }
402
+ return null;
403
+ }
404
+
405
+ function isRateLimitResponse(response, payload) {
406
+ if (response?.status === 429) return true;
407
+ const code = normalizeString(payload?.error?.code || payload?.code || payload?.detail?.code).toUpperCase();
408
+ return code === "RATE_LIMITED";
409
+ }
410
+
352
411
  // Test-only helper. Resets both circuits in memory AND on disk so unit
353
412
  // tests that exercise hydration don't leak state across runs.
354
413
  export function __resetCircuitStateForTests(homeDir) {
@@ -1357,8 +1416,19 @@ export async function pollSessionEvents(
1357
1416
  },
1358
1417
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1359
1418
  fetchImpl,
1419
+ { readErrorBody: true },
1360
1420
  );
1361
1421
  if (!response || !response.ok) {
1422
+ if (isRateLimitResponse(response, payload)) {
1423
+ return {
1424
+ ok: false,
1425
+ reason: "rate_limited",
1426
+ status: response?.status || 429,
1427
+ retryAfterMs: retryAfterMsFromResponse(response, payload, normalizedNowMs),
1428
+ events: [],
1429
+ cursor: normalizedSince || null,
1430
+ };
1431
+ }
1362
1432
  recordCircuitFailure(inboundCircuit, normalizedNowMs);
1363
1433
  return {
1364
1434
  ok: false,
@@ -1677,8 +1747,20 @@ export async function pollSessionEventsBefore(
1677
1747
  },
1678
1748
  normalizePositiveInteger(timeoutMs, DEFAULT_SYNC_TIMEOUT_MS),
1679
1749
  fetchImpl,
1750
+ { readErrorBody: true },
1680
1751
  );
1681
1752
  if (!response || !response.ok) {
1753
+ if (isRateLimitResponse(response, payload)) {
1754
+ return {
1755
+ ok: false,
1756
+ reason: "rate_limited",
1757
+ status: response?.status || 429,
1758
+ retryAfterMs: retryAfterMsFromResponse(response, payload, normalizedNowMs),
1759
+ events: [],
1760
+ cursor: null,
1761
+ beforeSequence: Number.isFinite(normalizedBeforeSequence) ? normalizedBeforeSequence : null,
1762
+ };
1763
+ }
1682
1764
  recordCircuitFailure(inboundCircuit, normalizedNowMs);
1683
1765
  return {
1684
1766
  ok: false,