@whittlelabs/sifter 0.30.9 → 0.30.10
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/README.md +1 -0
- package/bin.js +234 -31
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ The Sifter subscribes to your job pool and waits for review jobs. Leave it runni
|
|
|
82
82
|
A running Sifter deals with Sift or `claude` going away on its own, and says what it is doing:
|
|
83
83
|
|
|
84
84
|
- **Sift can't be reached** (a deploy, a network blip): it logs one warning, keeps trying, and says when Sift is back. This holds at startup too, so starting it during an outage is fine; it begins claiming once Sift answers.
|
|
85
|
+
- **Sift asks it to slow down** (too many sign-in attempts from this machine in a short time, which Sift answers by locking it out for a while): this is not a problem with the pairing, so it logs one warning saying when it will try again, waits until then without asking sooner, and says when it is back. `run` started during this waits the same way instead of failing; `doctor` warns and says when to try again.
|
|
85
86
|
- **`claude` stops working** (signed out, out of plan, or uninstalled): the review it was running fails with Claude's own message, and the Sifter stops taking new reviews so the rest of your queue waits for you instead of failing. It logs one line saying what to fix, checks again by itself (after 15 seconds at first, backing off to every 5 minutes), and starts claiming again once `claude` works. A check costs nothing while you are signed out, and one short turn once you are back.
|
|
86
87
|
- **Sift needs a newer Sifter**: it finishes the review in hand, installs the newest version with `npm install -g`, and restarts itself. If the install fails, it exits with code 2 and prints the command to run.
|
|
87
88
|
- **The pairing is revoked** (removed in Sift, or replaced by a newer pairing): it exits with code 3 and shows the two ways to pair again. A review it was running goes back to the queue for another Sifter.
|
package/bin.js
CHANGED
|
@@ -11199,16 +11199,97 @@ var require_client = __commonJS({
|
|
|
11199
11199
|
}
|
|
11200
11200
|
});
|
|
11201
11201
|
|
|
11202
|
+
// ../../packages/keep/dist/rate-limited.js
|
|
11203
|
+
var require_rate_limited = __commonJS({
|
|
11204
|
+
"../../packages/keep/dist/rate-limited.js"(exports2) {
|
|
11205
|
+
"use strict";
|
|
11206
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11207
|
+
exports2.RateLimitGate = exports2.RATE_LIMIT_DEFAULT_MS = exports2.RATE_LIMIT_CAP_MS = exports2.RATE_LIMIT_FLOOR_MS = exports2.KeepRateLimitedError = void 0;
|
|
11208
|
+
exports2.retryAfterMsOf = retryAfterMsOf;
|
|
11209
|
+
var KeepRateLimitedError = class extends Error {
|
|
11210
|
+
code = "RATE_LIMITED";
|
|
11211
|
+
/** Host the exchange was sent to. */
|
|
11212
|
+
host;
|
|
11213
|
+
/** Keep's own words for the refusal ("Too many attempts. Please try again later."). */
|
|
11214
|
+
reason;
|
|
11215
|
+
/** How long from now to wait before asking again (ms), already floored and capped. */
|
|
11216
|
+
retryAfterMs;
|
|
11217
|
+
/** When the next exchange may be attempted (epoch ms). */
|
|
11218
|
+
retryAt;
|
|
11219
|
+
constructor(message, detail) {
|
|
11220
|
+
super(message);
|
|
11221
|
+
this.name = "KeepRateLimitedError";
|
|
11222
|
+
this.host = detail.host;
|
|
11223
|
+
this.reason = detail.reason;
|
|
11224
|
+
this.retryAt = detail.retryAt;
|
|
11225
|
+
this.retryAfterMs = Math.max(0, detail.retryAt - Date.now());
|
|
11226
|
+
}
|
|
11227
|
+
};
|
|
11228
|
+
exports2.KeepRateLimitedError = KeepRateLimitedError;
|
|
11229
|
+
exports2.RATE_LIMIT_FLOOR_MS = 5e3;
|
|
11230
|
+
exports2.RATE_LIMIT_CAP_MS = 15 * 6e4;
|
|
11231
|
+
exports2.RATE_LIMIT_DEFAULT_MS = 6e4;
|
|
11232
|
+
function retryAfterMsOf(body, headers, now = Date.now()) {
|
|
11233
|
+
return clamp(bodyRetryAfter(body) ?? headerRetryAfter(headers, now) ?? exports2.RATE_LIMIT_DEFAULT_MS);
|
|
11234
|
+
}
|
|
11235
|
+
function bodyRetryAfter(body) {
|
|
11236
|
+
const value = body?.error?.retryAfter;
|
|
11237
|
+
return typeof value === "number" && Number.isFinite(value) ? value * 1e3 : null;
|
|
11238
|
+
}
|
|
11239
|
+
function headerRetryAfter(headers, now) {
|
|
11240
|
+
const raw = headers?.get("retry-after")?.trim();
|
|
11241
|
+
if (!raw)
|
|
11242
|
+
return null;
|
|
11243
|
+
if (/^\d+$/.test(raw))
|
|
11244
|
+
return Number(raw) * 1e3;
|
|
11245
|
+
const at = Date.parse(raw);
|
|
11246
|
+
return Number.isNaN(at) ? null : at - now;
|
|
11247
|
+
}
|
|
11248
|
+
function clamp(ms) {
|
|
11249
|
+
return Math.min(exports2.RATE_LIMIT_CAP_MS, Math.max(exports2.RATE_LIMIT_FLOOR_MS, ms));
|
|
11250
|
+
}
|
|
11251
|
+
var RateLimitGate = class {
|
|
11252
|
+
closed = null;
|
|
11253
|
+
/** Record a 429 and return the error to throw for it. */
|
|
11254
|
+
close(detail) {
|
|
11255
|
+
const retryAt = Date.now() + detail.retryAfterMs;
|
|
11256
|
+
const when = new Date(retryAt).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
11257
|
+
this.closed = new KeepRateLimitedError(`[${detail.manager}] Keep at ${detail.host} is rate limiting this client's token exchange (429: ${detail.reason}); not asking again before ${when}`, { host: detail.host, reason: detail.reason, retryAt });
|
|
11258
|
+
return this.closed;
|
|
11259
|
+
}
|
|
11260
|
+
/** Throws the rate-limited error while the gate is closed; a no-op once the time has passed. */
|
|
11261
|
+
check() {
|
|
11262
|
+
const closed = this.closed;
|
|
11263
|
+
if (closed === null)
|
|
11264
|
+
return;
|
|
11265
|
+
if (Date.now() >= closed.retryAt) {
|
|
11266
|
+
this.closed = null;
|
|
11267
|
+
return;
|
|
11268
|
+
}
|
|
11269
|
+
throw new KeepRateLimitedError(closed.message, {
|
|
11270
|
+
host: closed.host,
|
|
11271
|
+
reason: closed.reason,
|
|
11272
|
+
retryAt: closed.retryAt
|
|
11273
|
+
});
|
|
11274
|
+
}
|
|
11275
|
+
};
|
|
11276
|
+
exports2.RateLimitGate = RateLimitGate;
|
|
11277
|
+
}
|
|
11278
|
+
});
|
|
11279
|
+
|
|
11202
11280
|
// ../../packages/keep/dist/service-token.js
|
|
11203
11281
|
var require_service_token = __commonJS({
|
|
11204
11282
|
"../../packages/keep/dist/service-token.js"(exports2) {
|
|
11205
11283
|
"use strict";
|
|
11206
11284
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11207
11285
|
exports2.ServiceTokenManager = void 0;
|
|
11286
|
+
var rate_limited_1 = require_rate_limited();
|
|
11208
11287
|
var ServiceTokenManager = class {
|
|
11209
11288
|
token = null;
|
|
11210
11289
|
expiresAt = 0;
|
|
11211
11290
|
pendingExchange = null;
|
|
11291
|
+
/** Closed by a 429 until Keep's retry-after: no exchange is sent before it. */
|
|
11292
|
+
rateLimit = new rate_limited_1.RateLimitGate();
|
|
11212
11293
|
keepApiUrl;
|
|
11213
11294
|
clientId;
|
|
11214
11295
|
clientSecret;
|
|
@@ -11237,6 +11318,7 @@ var require_service_token = __commonJS({
|
|
|
11237
11318
|
if (this.pendingExchange) {
|
|
11238
11319
|
return this.pendingExchange;
|
|
11239
11320
|
}
|
|
11321
|
+
this.rateLimit.check();
|
|
11240
11322
|
this.pendingExchange = this.exchange();
|
|
11241
11323
|
try {
|
|
11242
11324
|
return await this.pendingExchange;
|
|
@@ -11255,6 +11337,15 @@ var require_service_token = __commonJS({
|
|
|
11255
11337
|
})
|
|
11256
11338
|
});
|
|
11257
11339
|
this.onResponse?.(response);
|
|
11340
|
+
if (response.status === 429) {
|
|
11341
|
+
const body = await response.json().catch(() => null);
|
|
11342
|
+
throw this.rateLimit.close({
|
|
11343
|
+
manager: "ServiceTokenManager",
|
|
11344
|
+
host: hostOf(url),
|
|
11345
|
+
reason: body?.error?.message ?? "Too Many Requests",
|
|
11346
|
+
retryAfterMs: (0, rate_limited_1.retryAfterMsOf)(body, response.headers)
|
|
11347
|
+
});
|
|
11348
|
+
}
|
|
11258
11349
|
const json = await response.json();
|
|
11259
11350
|
if (!response.ok || !json.success) {
|
|
11260
11351
|
const msg = json.error?.message ?? `Token exchange failed with status ${response.status}`;
|
|
@@ -11266,6 +11357,13 @@ var require_service_token = __commonJS({
|
|
|
11266
11357
|
}
|
|
11267
11358
|
};
|
|
11268
11359
|
exports2.ServiceTokenManager = ServiceTokenManager;
|
|
11360
|
+
function hostOf(url) {
|
|
11361
|
+
try {
|
|
11362
|
+
return new URL(url).host;
|
|
11363
|
+
} catch {
|
|
11364
|
+
return url;
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11269
11367
|
}
|
|
11270
11368
|
});
|
|
11271
11369
|
|
|
@@ -11275,6 +11373,7 @@ var require_agent_token = __commonJS({
|
|
|
11275
11373
|
"use strict";
|
|
11276
11374
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11277
11375
|
exports2.AgentTokenManager = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = void 0;
|
|
11376
|
+
var rate_limited_1 = require_rate_limited();
|
|
11278
11377
|
var AgentCredentialRevokedError2 = class extends Error {
|
|
11279
11378
|
code = "PAIRING_REVOKED";
|
|
11280
11379
|
constructor(message) {
|
|
@@ -11301,6 +11400,8 @@ var require_agent_token = __commonJS({
|
|
|
11301
11400
|
token = null;
|
|
11302
11401
|
expiresAt = 0;
|
|
11303
11402
|
pendingExchange = null;
|
|
11403
|
+
/** Closed by a 429 until Keep's retry-after: no exchange is sent before it. */
|
|
11404
|
+
rateLimit = new rate_limited_1.RateLimitGate();
|
|
11304
11405
|
keepApiUrl;
|
|
11305
11406
|
clientId;
|
|
11306
11407
|
clientSecret;
|
|
@@ -11322,6 +11423,7 @@ var require_agent_token = __commonJS({
|
|
|
11322
11423
|
if (this.pendingExchange) {
|
|
11323
11424
|
return this.pendingExchange;
|
|
11324
11425
|
}
|
|
11426
|
+
this.rateLimit.check();
|
|
11325
11427
|
this.pendingExchange = this.exchange();
|
|
11326
11428
|
try {
|
|
11327
11429
|
return await this.pendingExchange;
|
|
@@ -11357,6 +11459,14 @@ var require_agent_token = __commonJS({
|
|
|
11357
11459
|
const reason = json?.error?.message ?? `Agent token exchange failed with status ${response.status}`;
|
|
11358
11460
|
throw new KeepUnavailableError(`[AgentTokenManager] Keep at ${host} is unavailable (${response.status}): ${reason}`, { host, statusCode: response.status });
|
|
11359
11461
|
}
|
|
11462
|
+
if (response.status === 429) {
|
|
11463
|
+
throw this.rateLimit.close({
|
|
11464
|
+
manager: "AgentTokenManager",
|
|
11465
|
+
host,
|
|
11466
|
+
reason: json?.error?.message ?? "Too Many Requests",
|
|
11467
|
+
retryAfterMs: (0, rate_limited_1.retryAfterMsOf)(json, response.headers)
|
|
11468
|
+
});
|
|
11469
|
+
}
|
|
11360
11470
|
if (json === null) {
|
|
11361
11471
|
throw new Error(`[AgentTokenManager] Keep at ${host} answered ${response.status} with a body that is not JSON`);
|
|
11362
11472
|
}
|
|
@@ -11398,7 +11508,7 @@ var require_dist2 = __commonJS({
|
|
|
11398
11508
|
"../../packages/keep/dist/index.js"(exports2) {
|
|
11399
11509
|
"use strict";
|
|
11400
11510
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
11401
|
-
exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
|
|
11511
|
+
exports2.KeepRateLimitedError = exports2.KeepUnavailableError = exports2.AgentCredentialRevokedError = exports2.AgentTokenManager = exports2.ServiceTokenManager = exports2.KeepClient = void 0;
|
|
11402
11512
|
var client_1 = require_client();
|
|
11403
11513
|
Object.defineProperty(exports2, "KeepClient", { enumerable: true, get: function() {
|
|
11404
11514
|
return client_1.KeepClient;
|
|
@@ -11417,6 +11527,10 @@ var require_dist2 = __commonJS({
|
|
|
11417
11527
|
Object.defineProperty(exports2, "KeepUnavailableError", { enumerable: true, get: function() {
|
|
11418
11528
|
return agent_token_1.KeepUnavailableError;
|
|
11419
11529
|
} });
|
|
11530
|
+
var rate_limited_1 = require_rate_limited();
|
|
11531
|
+
Object.defineProperty(exports2, "KeepRateLimitedError", { enumerable: true, get: function() {
|
|
11532
|
+
return rate_limited_1.KeepRateLimitedError;
|
|
11533
|
+
} });
|
|
11420
11534
|
}
|
|
11421
11535
|
});
|
|
11422
11536
|
|
|
@@ -12282,6 +12396,10 @@ var require_checks = __commonJS({
|
|
|
12282
12396
|
return skip("pairing-live", "no readable pairing; see pairing-present");
|
|
12283
12397
|
}
|
|
12284
12398
|
const minted = await probes.mintAgentToken(stored.value);
|
|
12399
|
+
if (!minted.ok && minted.rateLimited) {
|
|
12400
|
+
const at = formatInstant(minted.rateLimited.retryAt);
|
|
12401
|
+
return warn("pairing-live", `Keep is rate limiting this pairing's token exchange (429), so the pairing could not be checked; it asked for no retry before ${at}`, subject.phase === "run" ? `nothing to do; \`run\` waits until ${at}, asks once, and starts claiming when Keep issues a token` : `wait until ${at}, then run doctor again`);
|
|
12402
|
+
}
|
|
12285
12403
|
if (!minted.ok && minted.unavailable) {
|
|
12286
12404
|
return warn("pairing-live", `Keep did not answer, so the pairing could not be checked: ${oneLine(minted.error).replace(/^\[AgentTokenManager\]\s*/, "")}`, subject.phase === "run" ? "nothing to do if this is a deployment window; `run` waits for Keep and starts claiming when it answers" : `check ${stored.value.keepApiUrl} is reachable, then run doctor again`);
|
|
12287
12405
|
}
|
|
@@ -12339,6 +12457,9 @@ var require_checks = __commonJS({
|
|
|
12339
12457
|
const pairing = stored.value;
|
|
12340
12458
|
return withConfig("jobs-reachable", probes, async (config) => {
|
|
12341
12459
|
const result = await probes.reachJobs(config, pairing);
|
|
12460
|
+
if (!result.ok && result.rateLimited) {
|
|
12461
|
+
return warn("jobs-reachable", `${config.auth.jobsApiUrl} was not asked: Keep is rate limiting the token exchange until ${formatInstant(result.rateLimited.retryAt)}`, "see pairing-live; the run loop asks once Keep issues a token");
|
|
12462
|
+
}
|
|
12342
12463
|
if (!result.ok) {
|
|
12343
12464
|
return warn("jobs-reachable", `${config.auth.jobsApiUrl} did not answer: ${oneLine(result.error)}`, "nothing to do if this is a deployment window; the run loop will keep trying");
|
|
12344
12465
|
}
|
|
@@ -12620,6 +12741,9 @@ var require_checks = __commonJS({
|
|
|
12620
12741
|
function osTmpDirOf(probes) {
|
|
12621
12742
|
return probes.osTmpDir();
|
|
12622
12743
|
}
|
|
12744
|
+
function formatInstant(epochMs) {
|
|
12745
|
+
return new Date(epochMs).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
12746
|
+
}
|
|
12623
12747
|
function oneLine(message) {
|
|
12624
12748
|
return message.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
|
|
12625
12749
|
}
|
|
@@ -20320,6 +20444,20 @@ var require_subscribe = __commonJS({
|
|
|
20320
20444
|
return true;
|
|
20321
20445
|
return err instanceof TypeError && err.message === "fetch failed";
|
|
20322
20446
|
}
|
|
20447
|
+
function isRateLimited(err) {
|
|
20448
|
+
return err?.code === "RATE_LIMITED";
|
|
20449
|
+
}
|
|
20450
|
+
function rateLimitWaitMs(err) {
|
|
20451
|
+
const e = err ?? {};
|
|
20452
|
+
if (typeof e.retryAfterMs === "number" && Number.isFinite(e.retryAfterMs))
|
|
20453
|
+
return Math.max(0, e.retryAfterMs);
|
|
20454
|
+
if (typeof e.retryAt === "number" && Number.isFinite(e.retryAt))
|
|
20455
|
+
return Math.max(0, e.retryAt - Date.now());
|
|
20456
|
+
return 0;
|
|
20457
|
+
}
|
|
20458
|
+
function formatInstant(epochMs) {
|
|
20459
|
+
return new Date(epochMs).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
20460
|
+
}
|
|
20323
20461
|
function describeOutage(err) {
|
|
20324
20462
|
const e = err ?? {};
|
|
20325
20463
|
if (typeof e.host === "string" && typeof e.statusCode === "number") {
|
|
@@ -20386,11 +20524,33 @@ var require_subscribe = __commonJS({
|
|
|
20386
20524
|
let outcome = newOutcome();
|
|
20387
20525
|
let wakeStartup = null;
|
|
20388
20526
|
let unavailableSince = null;
|
|
20527
|
+
let rateLimited = null;
|
|
20389
20528
|
function noteReachable() {
|
|
20390
20529
|
if (unavailableSince !== null) {
|
|
20391
20530
|
logger.info(`Sift is reachable again after ${formatDuration(Date.now() - unavailableSince)}`);
|
|
20392
20531
|
unavailableSince = null;
|
|
20393
20532
|
}
|
|
20533
|
+
if (rateLimited !== null) {
|
|
20534
|
+
logger.info(`${rateLimited.host} stopped rate limiting the Sifter after ${formatDuration(Date.now() - rateLimited.since)}`);
|
|
20535
|
+
rateLimited = null;
|
|
20536
|
+
}
|
|
20537
|
+
}
|
|
20538
|
+
function noteRateLimited(err) {
|
|
20539
|
+
const waitMs = Math.max(pollIntervalMs, rateLimitWaitMs(err));
|
|
20540
|
+
const e = err ?? {};
|
|
20541
|
+
const host = typeof e.host === "string" ? e.host : "the service";
|
|
20542
|
+
if (rateLimited === null) {
|
|
20543
|
+
rateLimited = { since: Date.now(), host };
|
|
20544
|
+
const reason = typeof e.reason === "string" ? `: ${e.reason}` : "";
|
|
20545
|
+
logger.warn(`${host} is rate limiting the Sifter (429${reason}). It will wait and try again at ${formatInstant(Date.now() + waitMs)} (in ${formatDuration(waitMs)}).`);
|
|
20546
|
+
} else {
|
|
20547
|
+
logger.debug?.("still rate limited", {
|
|
20548
|
+
host,
|
|
20549
|
+
rateLimitedFor: formatDuration(Date.now() - rateLimited.since),
|
|
20550
|
+
nextAttemptIn: formatDuration(waitMs)
|
|
20551
|
+
});
|
|
20552
|
+
}
|
|
20553
|
+
return waitMs;
|
|
20394
20554
|
}
|
|
20395
20555
|
function noteUnavailable(err) {
|
|
20396
20556
|
if (unavailableSince === null) {
|
|
@@ -20420,16 +20580,22 @@ var require_subscribe = __commonJS({
|
|
|
20420
20580
|
}
|
|
20421
20581
|
async function untilAvailable(call, mySession) {
|
|
20422
20582
|
for (; ; ) {
|
|
20583
|
+
let pause = pollIntervalMs;
|
|
20423
20584
|
try {
|
|
20424
20585
|
const value = await call();
|
|
20425
20586
|
noteReachable();
|
|
20426
20587
|
return value;
|
|
20427
20588
|
} catch (err) {
|
|
20428
|
-
if (isFatal(err)
|
|
20589
|
+
if (isFatal(err))
|
|
20590
|
+
throw err;
|
|
20591
|
+
if (isRateLimited(err))
|
|
20592
|
+
pause = noteRateLimited(err);
|
|
20593
|
+
else if (isUnavailable(err))
|
|
20594
|
+
noteUnavailable(err);
|
|
20595
|
+
else
|
|
20429
20596
|
throw err;
|
|
20430
|
-
noteUnavailable(err);
|
|
20431
20597
|
}
|
|
20432
|
-
await startupPause(
|
|
20598
|
+
await startupPause(pause);
|
|
20433
20599
|
if (stopped || mySession !== session)
|
|
20434
20600
|
return STOPPED;
|
|
20435
20601
|
}
|
|
@@ -20515,6 +20681,7 @@ var require_subscribe = __commonJS({
|
|
|
20515
20681
|
async function poll(mySession) {
|
|
20516
20682
|
if (stopped || mySession !== session)
|
|
20517
20683
|
return;
|
|
20684
|
+
let nextPollMs = pollIntervalMs;
|
|
20518
20685
|
if (inflight >= maxConcurrency) {
|
|
20519
20686
|
schedulePoll(pollIntervalMs);
|
|
20520
20687
|
return;
|
|
@@ -20557,14 +20724,16 @@ var require_subscribe = __commonJS({
|
|
|
20557
20724
|
void fail(err);
|
|
20558
20725
|
return;
|
|
20559
20726
|
}
|
|
20560
|
-
if (
|
|
20727
|
+
if (isRateLimited(err)) {
|
|
20728
|
+
nextPollMs = noteRateLimited(err);
|
|
20729
|
+
} else if (isUnavailable(err)) {
|
|
20561
20730
|
noteUnavailable(err);
|
|
20562
20731
|
} else {
|
|
20563
20732
|
logger.error("poll failed", { error: err instanceof Error ? err.message : String(err) });
|
|
20564
20733
|
}
|
|
20565
20734
|
}
|
|
20566
20735
|
if (!stopped && mySession === session)
|
|
20567
|
-
schedulePoll(
|
|
20736
|
+
schedulePoll(nextPollMs);
|
|
20568
20737
|
}
|
|
20569
20738
|
async function runOnce(available) {
|
|
20570
20739
|
let dispatch;
|
|
@@ -21035,6 +21204,21 @@ var require_probes = __commonJS({
|
|
|
21035
21204
|
packageName: options.brand.product.packageName
|
|
21036
21205
|
});
|
|
21037
21206
|
};
|
|
21207
|
+
const managers = /* @__PURE__ */ new Map();
|
|
21208
|
+
const tokensFor = (pairing) => {
|
|
21209
|
+
const key = JSON.stringify([pairing.keepApiUrl, pairing.credentials.clientId, pairing.credentials.clientSecret]);
|
|
21210
|
+
let tokens = managers.get(key);
|
|
21211
|
+
if (tokens === void 0) {
|
|
21212
|
+
tokens = new keep_1.AgentTokenManager({
|
|
21213
|
+
keepApiUrl: pairing.keepApiUrl,
|
|
21214
|
+
clientId: pairing.credentials.clientId,
|
|
21215
|
+
clientSecret: pairing.credentials.clientSecret,
|
|
21216
|
+
onResponse: enforceFloor
|
|
21217
|
+
});
|
|
21218
|
+
managers.set(key, tokens);
|
|
21219
|
+
}
|
|
21220
|
+
return tokens;
|
|
21221
|
+
};
|
|
21038
21222
|
let pairingRead;
|
|
21039
21223
|
let configRead;
|
|
21040
21224
|
let scaffoldedRead;
|
|
@@ -21163,12 +21347,7 @@ var require_probes = __commonJS({
|
|
|
21163
21347
|
return scaffoldedRead;
|
|
21164
21348
|
},
|
|
21165
21349
|
async mintAgentToken(pairing) {
|
|
21166
|
-
const tokens =
|
|
21167
|
-
keepApiUrl: pairing.keepApiUrl,
|
|
21168
|
-
clientId: pairing.credentials.clientId,
|
|
21169
|
-
clientSecret: pairing.credentials.clientSecret,
|
|
21170
|
-
onResponse: enforceFloor
|
|
21171
|
-
});
|
|
21350
|
+
const tokens = tokensFor(pairing);
|
|
21172
21351
|
try {
|
|
21173
21352
|
const token = await tokens.getToken();
|
|
21174
21353
|
if (token === null) {
|
|
@@ -21178,16 +21357,14 @@ var require_probes = __commonJS({
|
|
|
21178
21357
|
} catch (err) {
|
|
21179
21358
|
if (isRevoked(err) || isUpgradeRequired(err))
|
|
21180
21359
|
throw err;
|
|
21360
|
+
if (isKeepRateLimited(err)) {
|
|
21361
|
+
return { ok: false, error: describe(err), rateLimited: { retryAt: err.retryAt } };
|
|
21362
|
+
}
|
|
21181
21363
|
return { ok: false, error: describe(err), ...isKeepUnavailable(err) ? { unavailable: true } : {} };
|
|
21182
21364
|
}
|
|
21183
21365
|
},
|
|
21184
21366
|
async reachJobs(config, pairing) {
|
|
21185
|
-
const tokens =
|
|
21186
|
-
keepApiUrl: pairing.keepApiUrl,
|
|
21187
|
-
clientId: pairing.credentials.clientId,
|
|
21188
|
-
clientSecret: pairing.credentials.clientSecret,
|
|
21189
|
-
onResponse: enforceFloor
|
|
21190
|
-
});
|
|
21367
|
+
const tokens = tokensFor(pairing);
|
|
21191
21368
|
const client = new jobs_1.JobsClient({
|
|
21192
21369
|
baseUrl: config.auth.jobsApiUrl,
|
|
21193
21370
|
...config.auth.apiKey ? { apiKey: config.auth.apiKey } : {},
|
|
@@ -21204,6 +21381,9 @@ var require_probes = __commonJS({
|
|
|
21204
21381
|
} catch (err) {
|
|
21205
21382
|
if (isRevoked(err) || isUpgradeRequired(err))
|
|
21206
21383
|
throw err;
|
|
21384
|
+
if (isKeepRateLimited(err)) {
|
|
21385
|
+
return { ok: false, error: describe(err), rateLimited: { retryAt: err.retryAt } };
|
|
21386
|
+
}
|
|
21207
21387
|
return { ok: false, error: describe(err) };
|
|
21208
21388
|
}
|
|
21209
21389
|
},
|
|
@@ -21299,6 +21479,9 @@ var require_probes = __commonJS({
|
|
|
21299
21479
|
function isKeepUnavailable(err) {
|
|
21300
21480
|
return err instanceof Error && err.name === "KeepUnavailableError";
|
|
21301
21481
|
}
|
|
21482
|
+
function isKeepRateLimited(err) {
|
|
21483
|
+
return err instanceof Error && err.name === "KeepRateLimitedError";
|
|
21484
|
+
}
|
|
21302
21485
|
function isUpgradeRequired(err) {
|
|
21303
21486
|
return err instanceof Error && err.name === "UpgradeRequiredError";
|
|
21304
21487
|
}
|
|
@@ -25442,6 +25625,12 @@ var require_shuttle = __commonJS({
|
|
|
25442
25625
|
function isKeepUnavailable(err) {
|
|
25443
25626
|
return err instanceof Error && err.name === "KeepUnavailableError";
|
|
25444
25627
|
}
|
|
25628
|
+
function isKeepRateLimited(err) {
|
|
25629
|
+
return err instanceof Error && err.name === "KeepRateLimitedError";
|
|
25630
|
+
}
|
|
25631
|
+
function formatInstant(epochMs) {
|
|
25632
|
+
return new Date(epochMs).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
25633
|
+
}
|
|
25445
25634
|
async function runOutputGates(dispatch, response, executor, signal, ctx) {
|
|
25446
25635
|
let spec;
|
|
25447
25636
|
try {
|
|
@@ -25897,30 +26086,44 @@ var require_shuttle = __commonJS({
|
|
|
25897
26086
|
}
|
|
25898
26087
|
async function mintWaitingOutKeep(mint, brand, options) {
|
|
25899
26088
|
const maxDelay = options.retryMaxMs ?? KEEP_RETRY_MAX_MS;
|
|
25900
|
-
|
|
25901
|
-
let
|
|
26089
|
+
const initialDelay = options.retryInitialMs ?? KEEP_RETRY_INITIAL_MS;
|
|
26090
|
+
let delay = initialDelay;
|
|
26091
|
+
let waiting = null;
|
|
25902
26092
|
for (; ; ) {
|
|
26093
|
+
let pause;
|
|
25903
26094
|
try {
|
|
25904
26095
|
const token = await mint();
|
|
25905
|
-
if (
|
|
25906
|
-
options.logger?.info(`Keep is reachable again after ${formatDuration(Date.now() -
|
|
26096
|
+
if (waiting?.kind === "unavailable") {
|
|
26097
|
+
options.logger?.info(`Keep is reachable again after ${formatDuration(Date.now() - waiting.since)}`);
|
|
26098
|
+
} else if (waiting?.kind === "rate-limited") {
|
|
26099
|
+
options.logger?.info(`Keep issued a token; the rate limit lifted after ${formatDuration(Date.now() - waiting.since)}`);
|
|
25907
26100
|
}
|
|
25908
26101
|
return token;
|
|
25909
26102
|
} catch (err) {
|
|
25910
|
-
if (
|
|
26103
|
+
if (isKeepRateLimited(err)) {
|
|
26104
|
+
if (waiting?.kind !== "rate-limited") {
|
|
26105
|
+
waiting = { kind: "rate-limited", since: Date.now() };
|
|
26106
|
+
options.logger?.warn(`Keep is rate limiting this ${brand.product.title}'s token exchange (${err.host ?? "Keep"} answered 429: ${err.reason ?? err.message}). The credentials are not in question; the ${brand.product.title} will wait and ask again at ${formatInstant(err.retryAt)} (in ${formatDuration(err.retryAfterMs)}), and start when Keep issues a token.`);
|
|
26107
|
+
}
|
|
26108
|
+
pause = err.retryAfterMs;
|
|
26109
|
+
delay = initialDelay;
|
|
26110
|
+
} else if (isKeepUnavailable(err)) {
|
|
26111
|
+
if (waiting?.kind !== "unavailable") {
|
|
26112
|
+
waiting = { kind: "unavailable", since: Date.now() };
|
|
26113
|
+
const e = err;
|
|
26114
|
+
const what = typeof e.statusCode === "number" ? `${e.host ?? "Keep"} answered ${e.statusCode}` : `${e.host ?? "Keep"} did not answer`;
|
|
26115
|
+
options.logger?.warn(`Keep is unavailable (${what}). This is typical during a deployment window; the ${brand.product.title} will wait for it and start when it answers.`);
|
|
26116
|
+
}
|
|
26117
|
+
pause = delay;
|
|
26118
|
+
delay = Math.min(delay * 2, maxDelay);
|
|
26119
|
+
} else {
|
|
25911
26120
|
throw err;
|
|
25912
|
-
if (unavailableSince === null) {
|
|
25913
|
-
unavailableSince = Date.now();
|
|
25914
|
-
const e = err;
|
|
25915
|
-
const what = typeof e.statusCode === "number" ? `${e.host ?? "Keep"} answered ${e.statusCode}` : `${e.host ?? "Keep"} did not answer`;
|
|
25916
|
-
options.logger?.warn(`Keep is unavailable (${what}). This is typical during a deployment window; the ${brand.product.title} will wait for it and start when it answers.`);
|
|
25917
26121
|
}
|
|
25918
26122
|
}
|
|
25919
|
-
await abortableSleep(
|
|
26123
|
+
await abortableSleep(pause, options.signal);
|
|
25920
26124
|
if (options.signal?.aborted) {
|
|
25921
26125
|
throw options.signal.reason instanceof Error ? options.signal.reason : new Error("stopped while waiting for Keep");
|
|
25922
26126
|
}
|
|
25923
|
-
delay = Math.min(delay * 2, maxDelay);
|
|
25924
26127
|
}
|
|
25925
26128
|
}
|
|
25926
26129
|
}
|
|
@@ -26548,7 +26751,7 @@ var import_path = require("path");
|
|
|
26548
26751
|
var import_promises = require("fs/promises");
|
|
26549
26752
|
var import_yaml = __toESM(require_dist3());
|
|
26550
26753
|
var import_shuttle = __toESM(require_dist5());
|
|
26551
|
-
var buildVersion = true ? "0.30.
|
|
26754
|
+
var buildVersion = true ? "0.30.10" : pkg.version;
|
|
26552
26755
|
var sifterBrand = {
|
|
26553
26756
|
product: {
|
|
26554
26757
|
id: "sifter",
|