commonswarm 0.1.57 → 0.1.59
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/cswarm.cjs +655 -24
- package/package.json +1 -1
package/cswarm.cjs
CHANGED
|
@@ -27126,6 +27126,34 @@ function describeRenewalGrant(grant) {
|
|
|
27126
27126
|
return lines;
|
|
27127
27127
|
}
|
|
27128
27128
|
|
|
27129
|
+
// src/cloud/wake.ts
|
|
27130
|
+
var WAKE_EVENT = "wake";
|
|
27131
|
+
var WAKE_TOPIC_RE = /^cswarm-wake:[A-Za-z0-9_-]{43}$/;
|
|
27132
|
+
function isWakeTopic(value) {
|
|
27133
|
+
return WAKE_TOPIC_RE.test(value);
|
|
27134
|
+
}
|
|
27135
|
+
var WakeHintError = class extends Error {
|
|
27136
|
+
code = "malformed_wake";
|
|
27137
|
+
constructor(message) {
|
|
27138
|
+
super(message);
|
|
27139
|
+
this.name = "WakeHintError";
|
|
27140
|
+
}
|
|
27141
|
+
};
|
|
27142
|
+
function parseOptionalWakeHint(value) {
|
|
27143
|
+
if (value === void 0 || value === null) return void 0;
|
|
27144
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
27145
|
+
throw new WakeHintError("wake hint must be an object");
|
|
27146
|
+
}
|
|
27147
|
+
const row = value;
|
|
27148
|
+
if (typeof row.topic !== "string" || !isWakeTopic(row.topic)) {
|
|
27149
|
+
throw new WakeHintError("wake hint topic is malformed");
|
|
27150
|
+
}
|
|
27151
|
+
if (row.event !== WAKE_EVENT) {
|
|
27152
|
+
throw new WakeHintError("wake hint event is malformed");
|
|
27153
|
+
}
|
|
27154
|
+
return { topic: row.topic, event: WAKE_EVENT };
|
|
27155
|
+
}
|
|
27156
|
+
|
|
27129
27157
|
// src/cloud/renewal.ts
|
|
27130
27158
|
var AGENT_TOKEN_DEFAULT_TTL_MS2 = 60 * 60 * 1e3;
|
|
27131
27159
|
var AGENT_TOKEN_MAX_TTL_MS2 = 8 * 60 * 60 * 1e3;
|
|
@@ -27427,6 +27455,16 @@ async function requestSuccessor(options) {
|
|
|
27427
27455
|
"The deployment issued a successor credential that lasts longer than eight hours. cswarm refused to store it. Agent credentials stay short on purpose; renewal is what makes that survivable."
|
|
27428
27456
|
);
|
|
27429
27457
|
}
|
|
27458
|
+
let wake;
|
|
27459
|
+
try {
|
|
27460
|
+
wake = parseOptionalWakeHint(body.wake);
|
|
27461
|
+
} catch {
|
|
27462
|
+
throw new RenewalRefused(
|
|
27463
|
+
response.status,
|
|
27464
|
+
"malformed_wake",
|
|
27465
|
+
"The deployment returned a successor credential with a malformed wake hint. It was not stored."
|
|
27466
|
+
);
|
|
27467
|
+
}
|
|
27430
27468
|
return {
|
|
27431
27469
|
token,
|
|
27432
27470
|
tokenId: tokenId.toLowerCase(),
|
|
@@ -27435,7 +27473,8 @@ async function requestSuccessor(options) {
|
|
|
27435
27473
|
issuedAt,
|
|
27436
27474
|
expiresAt,
|
|
27437
27475
|
horizonExpiresAt: timestamp(body.horizon_expires_at),
|
|
27438
|
-
successorsRemaining: count(body.successors_remaining)
|
|
27476
|
+
successorsRemaining: count(body.successors_remaining),
|
|
27477
|
+
...wake === void 0 ? {} : { wake }
|
|
27439
27478
|
};
|
|
27440
27479
|
}
|
|
27441
27480
|
var AgentCredentialSession = class _AgentCredentialSession {
|
|
@@ -30010,6 +30049,12 @@ async function agentSignalPage(target2, credential, query, options, allowLegacyC
|
|
|
30010
30049
|
const rawRows = body.signals;
|
|
30011
30050
|
const parsedRows = parseSignalRows(rawRows, parseOptions2);
|
|
30012
30051
|
const ascending = query.ascending === true || query.after !== void 0;
|
|
30052
|
+
let wake;
|
|
30053
|
+
try {
|
|
30054
|
+
wake = parseOptionalWakeHint(body.wake);
|
|
30055
|
+
} catch {
|
|
30056
|
+
throw plainMalformedError("signal read returned a malformed wake hint");
|
|
30057
|
+
}
|
|
30013
30058
|
return {
|
|
30014
30059
|
signals: sortSignals(
|
|
30015
30060
|
rowsAfterCursor(parsedRows.signals, query.after),
|
|
@@ -30020,7 +30065,8 @@ async function agentSignalPage(target2, credential, query, options, allowLegacyC
|
|
|
30020
30065
|
rawCount: rawRows.length,
|
|
30021
30066
|
nextCursor: rawRows.length === 0 ? null : cursorFromUnknown(rawRows[rawRows.length - 1]),
|
|
30022
30067
|
malformedRows: parsedRows.malformedRows,
|
|
30023
|
-
pendingDeliveryCount
|
|
30068
|
+
pendingDeliveryCount,
|
|
30069
|
+
...wake === void 0 ? {} : { wake }
|
|
30024
30070
|
};
|
|
30025
30071
|
}
|
|
30026
30072
|
function parseAgentMemberRow(value) {
|
|
@@ -32076,11 +32122,18 @@ function parseClaimSuccess(body, expected, now) {
|
|
|
32076
32122
|
"delivery claim response returned more deliveries than its pending count"
|
|
32077
32123
|
);
|
|
32078
32124
|
}
|
|
32125
|
+
let wake;
|
|
32126
|
+
try {
|
|
32127
|
+
wake = parseOptionalWakeHint(row.wake);
|
|
32128
|
+
} catch {
|
|
32129
|
+
throw new DeliveryProtocolError("delivery claim response wake field is malformed");
|
|
32130
|
+
}
|
|
32079
32131
|
return {
|
|
32080
32132
|
capabilities,
|
|
32081
32133
|
deliveries,
|
|
32082
32134
|
pendingDeliveryCount,
|
|
32083
|
-
terminalDeliveryFailureCount
|
|
32135
|
+
terminalDeliveryFailureCount,
|
|
32136
|
+
...wake === void 0 ? {} : { wake }
|
|
32084
32137
|
};
|
|
32085
32138
|
}
|
|
32086
32139
|
function parseAckSuccess(body, expected) {
|
|
@@ -32294,7 +32347,8 @@ var DeliveryCommandClient = class {
|
|
|
32294
32347
|
capabilities: parsed.capabilities,
|
|
32295
32348
|
deliveries: parsed.deliveries,
|
|
32296
32349
|
pendingDeliveryCount: parsed.pendingDeliveryCount,
|
|
32297
|
-
terminalDeliveryFailureCount: parsed.terminalDeliveryFailureCount
|
|
32350
|
+
terminalDeliveryFailureCount: parsed.terminalDeliveryFailureCount,
|
|
32351
|
+
...parsed.wake === void 0 ? {} : { wake: parsed.wake }
|
|
32298
32352
|
};
|
|
32299
32353
|
}
|
|
32300
32354
|
/** Acknowledge one leased delivery with an exact terminal outcome. */
|
|
@@ -37412,6 +37466,336 @@ function pendingMainEntry(signal, principalId, provenance, now, options = {}) {
|
|
|
37412
37466
|
}, true);
|
|
37413
37467
|
}
|
|
37414
37468
|
|
|
37469
|
+
// src/listener/wake.ts
|
|
37470
|
+
var LISTENER_RECONCILE_POLL_MS = 3e5;
|
|
37471
|
+
var WAKE_COALESCE_MS = 1e3;
|
|
37472
|
+
var WAKE_CLAIMS_PER_MINUTE_BUDGET = 50;
|
|
37473
|
+
var WAKE_RATE_LIMIT_POLL_MS = 6e4;
|
|
37474
|
+
var REALTIME_SUBSCRIBE_STATUS = {
|
|
37475
|
+
SUBSCRIBED: "SUBSCRIBED",
|
|
37476
|
+
CHANNEL_ERROR: "CHANNEL_ERROR",
|
|
37477
|
+
CLOSED: "CLOSED",
|
|
37478
|
+
TIMED_OUT: "TIMED_OUT"
|
|
37479
|
+
};
|
|
37480
|
+
var LISTENER_WAKE_MODES = ["push", "poll"];
|
|
37481
|
+
var LISTENER_WAKE_MODE_PUSH = LISTENER_WAKE_MODES[0];
|
|
37482
|
+
var LISTENER_WAKE_MODE_POLL = LISTENER_WAKE_MODES[1];
|
|
37483
|
+
var LISTENER_WAKE_MODE_SET = new Set(
|
|
37484
|
+
LISTENER_WAKE_MODES
|
|
37485
|
+
);
|
|
37486
|
+
var WAKE_ERROR_CODES = [
|
|
37487
|
+
"channel_error",
|
|
37488
|
+
"closed",
|
|
37489
|
+
"timed_out",
|
|
37490
|
+
"rate_limited",
|
|
37491
|
+
"wake_budget"
|
|
37492
|
+
];
|
|
37493
|
+
var WAKE_ERROR_CODE_SET = new Set(WAKE_ERROR_CODES);
|
|
37494
|
+
var WAKE_ERROR_CODE_WAKE_BUDGET = WAKE_ERROR_CODES.find(
|
|
37495
|
+
(code) => code === "wake_budget"
|
|
37496
|
+
);
|
|
37497
|
+
var LISTENER_WAKE_STATUS_KEYS = [
|
|
37498
|
+
"mode",
|
|
37499
|
+
"subscribedAt",
|
|
37500
|
+
"reconnects",
|
|
37501
|
+
"lastWakeAt",
|
|
37502
|
+
"lastReconcileAt",
|
|
37503
|
+
"errorCode",
|
|
37504
|
+
"topicRotatedAt",
|
|
37505
|
+
"rateLimited"
|
|
37506
|
+
];
|
|
37507
|
+
var LISTENER_WAKE_SENSITIVE_KEYS = [
|
|
37508
|
+
"topic",
|
|
37509
|
+
"wakeTopic",
|
|
37510
|
+
"wake_topic"
|
|
37511
|
+
];
|
|
37512
|
+
function defaultRealtime(target2) {
|
|
37513
|
+
const client = createClient(target2.url, target2.anonKey, {
|
|
37514
|
+
auth: { persistSession: false, autoRefreshToken: false }
|
|
37515
|
+
});
|
|
37516
|
+
return client.realtime;
|
|
37517
|
+
}
|
|
37518
|
+
function isSubscribeStatus(value) {
|
|
37519
|
+
return value === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED || value === REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR || value === REALTIME_SUBSCRIBE_STATUS.CLOSED || value === REALTIME_SUBSCRIBE_STATUS.TIMED_OUT;
|
|
37520
|
+
}
|
|
37521
|
+
function wakeErrorCodeFromSubscribeStatus(status) {
|
|
37522
|
+
switch (status) {
|
|
37523
|
+
case REALTIME_SUBSCRIBE_STATUS.CHANNEL_ERROR:
|
|
37524
|
+
return "channel_error";
|
|
37525
|
+
case REALTIME_SUBSCRIBE_STATUS.CLOSED:
|
|
37526
|
+
return "closed";
|
|
37527
|
+
case REALTIME_SUBSCRIBE_STATUS.TIMED_OUT:
|
|
37528
|
+
return "timed_out";
|
|
37529
|
+
default:
|
|
37530
|
+
return null;
|
|
37531
|
+
}
|
|
37532
|
+
}
|
|
37533
|
+
function emptyListenerWakeStatus() {
|
|
37534
|
+
return {
|
|
37535
|
+
mode: LISTENER_WAKE_MODE_POLL,
|
|
37536
|
+
subscribedAt: null,
|
|
37537
|
+
reconnects: 0,
|
|
37538
|
+
lastWakeAt: null,
|
|
37539
|
+
lastReconcileAt: null,
|
|
37540
|
+
errorCode: null,
|
|
37541
|
+
topicRotatedAt: null,
|
|
37542
|
+
rateLimited: false
|
|
37543
|
+
};
|
|
37544
|
+
}
|
|
37545
|
+
function listenerWakePersistWorthy(previous, next, lastPersistMs, nowMs) {
|
|
37546
|
+
if (previous === void 0) return true;
|
|
37547
|
+
if (previous.mode !== next.mode || previous.rateLimited !== next.rateLimited || previous.errorCode !== next.errorCode || previous.reconnects !== next.reconnects || previous.subscribedAt !== next.subscribedAt || previous.topicRotatedAt !== next.topicRotatedAt || previous.lastReconcileAt !== next.lastReconcileAt) {
|
|
37548
|
+
return true;
|
|
37549
|
+
}
|
|
37550
|
+
if (previous.lastWakeAt !== next.lastWakeAt) {
|
|
37551
|
+
return nowMs - lastPersistMs >= WAKE_COALESCE_MS;
|
|
37552
|
+
}
|
|
37553
|
+
return false;
|
|
37554
|
+
}
|
|
37555
|
+
function listenerWakeStatusSentence(wake, pollIntervalMs, lastWakeLabel) {
|
|
37556
|
+
if (wake.mode === LISTENER_WAKE_MODE_PUSH) {
|
|
37557
|
+
const last = lastWakeLabel === null ? "no wake yet" : `last wake ${lastWakeLabel}`;
|
|
37558
|
+
return `${LISTENER_WAKE_MODE_PUSH} (Realtime), ${last}, reconcile every ${formatIdlePollDuration(LISTENER_RECONCILE_POLL_MS)}.`;
|
|
37559
|
+
}
|
|
37560
|
+
if (wake.errorCode === WAKE_ERROR_CODE_WAKE_BUDGET) {
|
|
37561
|
+
return `Subscribed; claims paused until the minute clears (${WAKE_ERROR_CODE_WAKE_BUDGET}); polling every ${formatIdlePollDuration(pollIntervalMs)} meanwhile.`;
|
|
37562
|
+
}
|
|
37563
|
+
const code = wake.errorCode ?? "disconnected";
|
|
37564
|
+
return `${LISTENER_WAKE_MODE_POLL} every ${formatIdlePollDuration(pollIntervalMs)}. Realtime not connected (${code}).`;
|
|
37565
|
+
}
|
|
37566
|
+
var WakeSubscriber = class {
|
|
37567
|
+
now;
|
|
37568
|
+
target;
|
|
37569
|
+
createRealtime;
|
|
37570
|
+
realtime = null;
|
|
37571
|
+
channel = null;
|
|
37572
|
+
topic = null;
|
|
37573
|
+
waiter = null;
|
|
37574
|
+
pending = null;
|
|
37575
|
+
connectionState = "disconnected";
|
|
37576
|
+
subscribedAt = null;
|
|
37577
|
+
reconnects = 0;
|
|
37578
|
+
lastWakeAt = null;
|
|
37579
|
+
lastReconcileAt = null;
|
|
37580
|
+
lastErrorCode = null;
|
|
37581
|
+
topicRotatedAt = null;
|
|
37582
|
+
rateLimitedUntil = 0;
|
|
37583
|
+
lastClaimAt = 0;
|
|
37584
|
+
wakeClaimTimes = [];
|
|
37585
|
+
closed = false;
|
|
37586
|
+
everSubscribed = false;
|
|
37587
|
+
constructor(options) {
|
|
37588
|
+
this.target = options.target;
|
|
37589
|
+
this.now = options.now ?? Date.now;
|
|
37590
|
+
this.createRealtime = options.createRealtime ?? defaultRealtime;
|
|
37591
|
+
}
|
|
37592
|
+
get state() {
|
|
37593
|
+
return this.connectionState;
|
|
37594
|
+
}
|
|
37595
|
+
get hasTopic() {
|
|
37596
|
+
return this.topic !== null;
|
|
37597
|
+
}
|
|
37598
|
+
snapshot(nowMs = this.now()) {
|
|
37599
|
+
const serverLimited = nowMs < this.rateLimitedUntil;
|
|
37600
|
+
const overBudget = this.overWakeBudget(nowMs);
|
|
37601
|
+
const rateLimited = serverLimited || overBudget;
|
|
37602
|
+
const mode3 = this.connectionState === "subscribed" && !rateLimited ? LISTENER_WAKE_MODE_PUSH : LISTENER_WAKE_MODE_POLL;
|
|
37603
|
+
const errorCode = serverLimited ? "rate_limited" : overBudget ? WAKE_ERROR_CODE_WAKE_BUDGET : this.lastErrorCode;
|
|
37604
|
+
return {
|
|
37605
|
+
mode: mode3,
|
|
37606
|
+
subscribedAt: this.subscribedAt,
|
|
37607
|
+
reconnects: this.reconnects,
|
|
37608
|
+
lastWakeAt: this.lastWakeAt,
|
|
37609
|
+
lastReconcileAt: this.lastReconcileAt,
|
|
37610
|
+
errorCode,
|
|
37611
|
+
topicRotatedAt: this.topicRotatedAt,
|
|
37612
|
+
rateLimited
|
|
37613
|
+
};
|
|
37614
|
+
}
|
|
37615
|
+
noteReconcile(nowMs = this.now()) {
|
|
37616
|
+
this.lastReconcileAt = new Date(nowMs).toISOString();
|
|
37617
|
+
}
|
|
37618
|
+
noteClaim(nowMs = this.now()) {
|
|
37619
|
+
this.lastClaimAt = nowMs;
|
|
37620
|
+
}
|
|
37621
|
+
noteWakeClaim(nowMs = this.now()) {
|
|
37622
|
+
this.noteClaim(nowMs);
|
|
37623
|
+
this.wakeClaimTimes.push(nowMs);
|
|
37624
|
+
this.trimWakeClaims(nowMs);
|
|
37625
|
+
}
|
|
37626
|
+
coalescingRemainingMs(nowMs = this.now()) {
|
|
37627
|
+
if (this.lastClaimAt <= 0) return 0;
|
|
37628
|
+
return Math.max(0, this.lastClaimAt + WAKE_COALESCE_MS - nowMs);
|
|
37629
|
+
}
|
|
37630
|
+
overWakeBudget(nowMs = this.now()) {
|
|
37631
|
+
this.trimWakeClaims(nowMs);
|
|
37632
|
+
return this.wakeClaimTimes.length >= WAKE_CLAIMS_PER_MINUTE_BUDGET;
|
|
37633
|
+
}
|
|
37634
|
+
canClaimOnWake(nowMs = this.now()) {
|
|
37635
|
+
if (nowMs < this.rateLimitedUntil) return false;
|
|
37636
|
+
if (this.coalescingRemainingMs(nowMs) > 0) return false;
|
|
37637
|
+
return !this.overWakeBudget(nowMs);
|
|
37638
|
+
}
|
|
37639
|
+
markRateLimited(nowMs = this.now()) {
|
|
37640
|
+
this.rateLimitedUntil = nowMs + WAKE_RATE_LIMIT_POLL_MS;
|
|
37641
|
+
this.lastErrorCode = "rate_limited";
|
|
37642
|
+
this.emitPending("state");
|
|
37643
|
+
}
|
|
37644
|
+
setTopic(topic) {
|
|
37645
|
+
if (this.closed) return;
|
|
37646
|
+
if (!isWakeTopic(topic)) {
|
|
37647
|
+
throw new Error("wake topic is malformed");
|
|
37648
|
+
}
|
|
37649
|
+
if (this.topic === topic) return;
|
|
37650
|
+
const rotated = this.topic !== null;
|
|
37651
|
+
void this.detachChannel();
|
|
37652
|
+
this.topic = topic;
|
|
37653
|
+
if (rotated) {
|
|
37654
|
+
this.topicRotatedAt = new Date(this.now()).toISOString();
|
|
37655
|
+
}
|
|
37656
|
+
this.connect();
|
|
37657
|
+
}
|
|
37658
|
+
next(options) {
|
|
37659
|
+
if (this.waiter !== null) {
|
|
37660
|
+
throw new Error("wake next() already has a waiter");
|
|
37661
|
+
}
|
|
37662
|
+
if (options.signal?.aborted) {
|
|
37663
|
+
return Promise.resolve("deadline");
|
|
37664
|
+
}
|
|
37665
|
+
const nowMs = this.now();
|
|
37666
|
+
if (this.pending !== null) {
|
|
37667
|
+
if (!(this.pending === "wake" && this.wakeClaimPaused(nowMs))) {
|
|
37668
|
+
const reason = this.pending;
|
|
37669
|
+
this.pending = null;
|
|
37670
|
+
return Promise.resolve(reason);
|
|
37671
|
+
}
|
|
37672
|
+
}
|
|
37673
|
+
if (nowMs >= options.until) {
|
|
37674
|
+
return Promise.resolve("deadline");
|
|
37675
|
+
}
|
|
37676
|
+
return new Promise((resolve3) => {
|
|
37677
|
+
const delay2 = Math.max(0, options.until - this.now());
|
|
37678
|
+
const timer2 = setTimeout(() => this.finishWait("deadline"), delay2);
|
|
37679
|
+
const onAbort = () => this.finishWait("deadline");
|
|
37680
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
37681
|
+
this.waiter = {
|
|
37682
|
+
resolve: resolve3,
|
|
37683
|
+
timer: timer2,
|
|
37684
|
+
onAbort,
|
|
37685
|
+
signal: options.signal
|
|
37686
|
+
};
|
|
37687
|
+
});
|
|
37688
|
+
}
|
|
37689
|
+
async close() {
|
|
37690
|
+
this.closed = true;
|
|
37691
|
+
this.topic = null;
|
|
37692
|
+
this.finishWait("deadline");
|
|
37693
|
+
await this.detachChannel();
|
|
37694
|
+
try {
|
|
37695
|
+
this.realtime?.disconnect?.();
|
|
37696
|
+
} catch {
|
|
37697
|
+
}
|
|
37698
|
+
this.realtime = null;
|
|
37699
|
+
this.connectionState = "disconnected";
|
|
37700
|
+
}
|
|
37701
|
+
trimWakeClaims(nowMs) {
|
|
37702
|
+
const minuteStart = Math.floor(nowMs / 6e4) * 6e4;
|
|
37703
|
+
this.wakeClaimTimes = this.wakeClaimTimes.filter((ts) => ts >= minuteStart);
|
|
37704
|
+
}
|
|
37705
|
+
/** Client budget or a server 429: do not claim on wake; poll covers the window. */
|
|
37706
|
+
wakeClaimPaused(nowMs) {
|
|
37707
|
+
return nowMs < this.rateLimitedUntil || this.overWakeBudget(nowMs);
|
|
37708
|
+
}
|
|
37709
|
+
emitPending(reason) {
|
|
37710
|
+
if (this.waiter !== null) {
|
|
37711
|
+
if (reason === "wake" && this.wakeClaimPaused(this.now())) {
|
|
37712
|
+
this.pending = "wake";
|
|
37713
|
+
return;
|
|
37714
|
+
}
|
|
37715
|
+
this.finishWait(reason);
|
|
37716
|
+
return;
|
|
37717
|
+
}
|
|
37718
|
+
if (reason === "wake" || this.pending !== "wake") {
|
|
37719
|
+
this.pending = reason;
|
|
37720
|
+
}
|
|
37721
|
+
}
|
|
37722
|
+
finishWait(reason) {
|
|
37723
|
+
const waiter = this.waiter;
|
|
37724
|
+
if (waiter === null) return;
|
|
37725
|
+
this.waiter = null;
|
|
37726
|
+
if (waiter.timer !== null) clearTimeout(waiter.timer);
|
|
37727
|
+
if (waiter.signal && waiter.onAbort) {
|
|
37728
|
+
waiter.signal.removeEventListener("abort", waiter.onAbort);
|
|
37729
|
+
}
|
|
37730
|
+
waiter.resolve(reason);
|
|
37731
|
+
}
|
|
37732
|
+
connect() {
|
|
37733
|
+
if (this.closed || this.topic === null) return;
|
|
37734
|
+
if (this.realtime === null) {
|
|
37735
|
+
this.realtime = this.createRealtime(this.target);
|
|
37736
|
+
void this.realtime.setAuth(this.target.anonKey);
|
|
37737
|
+
}
|
|
37738
|
+
const topic = this.topic;
|
|
37739
|
+
this.connectionState = "connecting";
|
|
37740
|
+
this.lastErrorCode = null;
|
|
37741
|
+
const channel = this.realtime.channel(topic, {
|
|
37742
|
+
config: { private: true }
|
|
37743
|
+
});
|
|
37744
|
+
this.channel = channel;
|
|
37745
|
+
channel.on("broadcast", { event: WAKE_EVENT }, () => {
|
|
37746
|
+
this.lastWakeAt = new Date(this.now()).toISOString();
|
|
37747
|
+
this.emitPending("wake");
|
|
37748
|
+
});
|
|
37749
|
+
channel.subscribe((status) => {
|
|
37750
|
+
this.onSubscribeStatus(status);
|
|
37751
|
+
});
|
|
37752
|
+
}
|
|
37753
|
+
onSubscribeStatus(status) {
|
|
37754
|
+
if (this.closed) return;
|
|
37755
|
+
if (!isSubscribeStatus(status)) return;
|
|
37756
|
+
const wasSubscribed = this.connectionState === "subscribed";
|
|
37757
|
+
if (status === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED) {
|
|
37758
|
+
this.connectionState = "subscribed";
|
|
37759
|
+
this.subscribedAt = new Date(this.now()).toISOString();
|
|
37760
|
+
this.lastErrorCode = null;
|
|
37761
|
+
if (this.everSubscribed && !wasSubscribed) this.reconnects += 1;
|
|
37762
|
+
this.everSubscribed = true;
|
|
37763
|
+
if (!wasSubscribed) this.emitPending("state");
|
|
37764
|
+
return;
|
|
37765
|
+
}
|
|
37766
|
+
const code = wakeErrorCodeFromSubscribeStatus(status);
|
|
37767
|
+
if (status === REALTIME_SUBSCRIBE_STATUS.CLOSED) {
|
|
37768
|
+
this.connectionState = "disconnected";
|
|
37769
|
+
} else {
|
|
37770
|
+
this.connectionState = "errored";
|
|
37771
|
+
}
|
|
37772
|
+
this.lastErrorCode = code;
|
|
37773
|
+
this.subscribedAt = null;
|
|
37774
|
+
if (wasSubscribed) this.emitPending("state");
|
|
37775
|
+
}
|
|
37776
|
+
async detachChannel() {
|
|
37777
|
+
const channel = this.channel;
|
|
37778
|
+
this.channel = null;
|
|
37779
|
+
if (channel === null) return;
|
|
37780
|
+
try {
|
|
37781
|
+
await channel.unsubscribe();
|
|
37782
|
+
} catch {
|
|
37783
|
+
}
|
|
37784
|
+
try {
|
|
37785
|
+
await this.realtime?.removeChannel?.(channel);
|
|
37786
|
+
} catch {
|
|
37787
|
+
}
|
|
37788
|
+
if (this.channel !== null) return;
|
|
37789
|
+
if (this.connectionState === "subscribed") {
|
|
37790
|
+
this.connectionState = "disconnected";
|
|
37791
|
+
this.subscribedAt = null;
|
|
37792
|
+
}
|
|
37793
|
+
}
|
|
37794
|
+
};
|
|
37795
|
+
function createWakeSubscriber(options) {
|
|
37796
|
+
return new WakeSubscriber(options);
|
|
37797
|
+
}
|
|
37798
|
+
|
|
37415
37799
|
// src/listener/runtime.ts
|
|
37416
37800
|
var LISTENER_PAGE_LIMIT = 100;
|
|
37417
37801
|
var LISTENER_IDLE_POLL_MS = IDLE_POLL_DEFAULT_MS;
|
|
@@ -37901,6 +38285,35 @@ async function runListenerRuntime(options) {
|
|
|
37901
38285
|
abort.addEventListener("abort", onAbort);
|
|
37902
38286
|
}
|
|
37903
38287
|
let stop;
|
|
38288
|
+
let wakeSubscriber = options.wake ?? null;
|
|
38289
|
+
let reconcileDueAt = now();
|
|
38290
|
+
const ensureWake = () => {
|
|
38291
|
+
if (wakeSubscriber === null) {
|
|
38292
|
+
wakeSubscriber = options.createWake ? options.createWake(options.target) : createWakeSubscriber({ target: options.target, now });
|
|
38293
|
+
}
|
|
38294
|
+
return wakeSubscriber;
|
|
38295
|
+
};
|
|
38296
|
+
const applyWakeHint = (hint) => {
|
|
38297
|
+
if (hint === void 0) return;
|
|
38298
|
+
try {
|
|
38299
|
+
ensureWake().setTopic(hint.topic);
|
|
38300
|
+
} catch {
|
|
38301
|
+
}
|
|
38302
|
+
};
|
|
38303
|
+
const emitWake = () => {
|
|
38304
|
+
if (wakeSubscriber === null) return;
|
|
38305
|
+
options.onEvent?.({
|
|
38306
|
+
type: "wake",
|
|
38307
|
+
wake: wakeSubscriber.snapshot(now()),
|
|
38308
|
+
ts: eventTime(now)
|
|
38309
|
+
});
|
|
38310
|
+
};
|
|
38311
|
+
const waitCapMs = () => {
|
|
38312
|
+
if (wakeSubscriber !== null && wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
|
|
38313
|
+
return LISTENER_RECONCILE_POLL_MS;
|
|
38314
|
+
}
|
|
38315
|
+
return nextIdlePollMs(pollMs, emptyIdleStreak, LISTENER_IDLE_POLL_MAX_MS);
|
|
38316
|
+
};
|
|
37904
38317
|
const sendPreparedAck = async (active) => {
|
|
37905
38318
|
if (active.phase !== "ack_pending" || active.signalId === null || active.leaseId === null || active.leasedUntil === null || active.ack === null) {
|
|
37906
38319
|
return {
|
|
@@ -37967,8 +38380,33 @@ async function runListenerRuntime(options) {
|
|
|
37967
38380
|
stop = { reason: "cancelled" };
|
|
37968
38381
|
break;
|
|
37969
38382
|
}
|
|
37970
|
-
let
|
|
37971
|
-
|
|
38383
|
+
let skipRead = false;
|
|
38384
|
+
if (ready && deliveryMode === "durable_claim" && wakeSubscriber !== null && wakeSubscriber.hasTopic) {
|
|
38385
|
+
const until = Math.min(reconcileDueAt, now() + waitCapMs());
|
|
38386
|
+
const reason = await wakeSubscriber.next({
|
|
38387
|
+
until,
|
|
38388
|
+
...abort ? { signal: abort } : {}
|
|
38389
|
+
});
|
|
38390
|
+
emitWake();
|
|
38391
|
+
if (abort?.aborted) {
|
|
38392
|
+
stop = { reason: "cancelled" };
|
|
38393
|
+
break;
|
|
38394
|
+
}
|
|
38395
|
+
if (reason === "wake" && wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
|
|
38396
|
+
const coalesceMs = wakeSubscriber.coalescingRemainingMs(now());
|
|
38397
|
+
if (coalesceMs > 0) await sleep2(coalesceMs, abort);
|
|
38398
|
+
if (abort?.aborted) {
|
|
38399
|
+
stop = { reason: "cancelled" };
|
|
38400
|
+
break;
|
|
38401
|
+
}
|
|
38402
|
+
if (wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
|
|
38403
|
+
skipRead = true;
|
|
38404
|
+
}
|
|
38405
|
+
}
|
|
38406
|
+
}
|
|
38407
|
+
let page = null;
|
|
38408
|
+
if (skipRead) {
|
|
38409
|
+
} else try {
|
|
37972
38410
|
const token = await options.credentialSession.bearer();
|
|
37973
38411
|
page = await readPage({
|
|
37974
38412
|
token,
|
|
@@ -37986,6 +38424,8 @@ async function runListenerRuntime(options) {
|
|
|
37986
38424
|
}
|
|
37987
38425
|
});
|
|
37988
38426
|
requireCapabilities(page);
|
|
38427
|
+
applyWakeHint(page.wake);
|
|
38428
|
+
emitWake();
|
|
37989
38429
|
if (ready && readEpisodeStartedAtMs !== null) {
|
|
37990
38430
|
const recoveredAtMs = now();
|
|
37991
38431
|
options.onEvent?.({
|
|
@@ -38113,7 +38553,7 @@ async function runListenerRuntime(options) {
|
|
|
38113
38553
|
break;
|
|
38114
38554
|
}
|
|
38115
38555
|
}
|
|
38116
|
-
if (page
|
|
38556
|
+
if ((page?.capabilities.deliveryAck === true || skipRead) && now() < horizon && !preparedNeedsMainRoute) {
|
|
38117
38557
|
const ackStop = await sendPreparedAck(recovery);
|
|
38118
38558
|
if (ackStop !== null) {
|
|
38119
38559
|
stop = ackStop;
|
|
@@ -38143,7 +38583,7 @@ async function runListenerRuntime(options) {
|
|
|
38143
38583
|
continue;
|
|
38144
38584
|
}
|
|
38145
38585
|
if (recovery?.phase === "leased") {
|
|
38146
|
-
if (page
|
|
38586
|
+
if (page?.capabilities.deliveryAck === true || skipRead) {
|
|
38147
38587
|
let terminal = null;
|
|
38148
38588
|
if (recovery.signalId !== null) {
|
|
38149
38589
|
try {
|
|
@@ -38253,6 +38693,10 @@ async function runListenerRuntime(options) {
|
|
|
38253
38693
|
stop = { reason: "credential", error: asError2(error) };
|
|
38254
38694
|
break;
|
|
38255
38695
|
}
|
|
38696
|
+
if (error instanceof DeliveryHttpError && error.code === "rate_limited") {
|
|
38697
|
+
wakeSubscriber?.markRateLimited(now());
|
|
38698
|
+
emitWake();
|
|
38699
|
+
}
|
|
38256
38700
|
if (!isRetryableDeliveryError(error)) {
|
|
38257
38701
|
stop = { reason: "fatal", error: asError2(error) };
|
|
38258
38702
|
break;
|
|
@@ -38271,6 +38715,14 @@ async function runListenerRuntime(options) {
|
|
|
38271
38715
|
stop = { reason: "fatal", error: new Error("delivery claim did not settle") };
|
|
38272
38716
|
break;
|
|
38273
38717
|
}
|
|
38718
|
+
applyWakeHint(result.wake);
|
|
38719
|
+
if (!skipRead && wakeSubscriber !== null && wakeSubscriber.hasTopic) {
|
|
38720
|
+
wakeSubscriber.noteReconcile(now());
|
|
38721
|
+
reconcileDueAt = now() + LISTENER_RECONCILE_POLL_MS;
|
|
38722
|
+
}
|
|
38723
|
+
if (skipRead) wakeSubscriber?.noteWakeClaim(now());
|
|
38724
|
+
else wakeSubscriber?.noteClaim(now());
|
|
38725
|
+
emitWake();
|
|
38274
38726
|
const claimed = result.deliveries[0] ?? null;
|
|
38275
38727
|
options.onEvent?.({
|
|
38276
38728
|
type: "delivery_claim",
|
|
@@ -38301,6 +38753,19 @@ async function runListenerRuntime(options) {
|
|
|
38301
38753
|
stop = { reason: "fatal", error: asError2(error) };
|
|
38302
38754
|
break;
|
|
38303
38755
|
}
|
|
38756
|
+
if (wakeSubscriber !== null && wakeSubscriber.hasTopic) {
|
|
38757
|
+
const snap = wakeSubscriber.snapshot(now());
|
|
38758
|
+
const intervalMs = snap.mode === LISTENER_WAKE_MODE_PUSH ? LISTENER_RECONCILE_POLL_MS : nextIdlePollMs(pollMs, emptyIdleStreak, LISTENER_IDLE_POLL_MAX_MS);
|
|
38759
|
+
if (snap.mode === LISTENER_WAKE_MODE_PUSH) emptyIdleStreak = 0;
|
|
38760
|
+
else emptyIdleStreak += 1;
|
|
38761
|
+
options.onEvent?.({
|
|
38762
|
+
type: "idle_poll",
|
|
38763
|
+
intervalMs,
|
|
38764
|
+
ts: eventTime(now)
|
|
38765
|
+
});
|
|
38766
|
+
emitWake();
|
|
38767
|
+
continue;
|
|
38768
|
+
}
|
|
38304
38769
|
await idleSleep(false);
|
|
38305
38770
|
continue;
|
|
38306
38771
|
}
|
|
@@ -38495,6 +38960,7 @@ async function runListenerRuntime(options) {
|
|
|
38495
38960
|
}
|
|
38496
38961
|
continue;
|
|
38497
38962
|
}
|
|
38963
|
+
if (page === null) continue;
|
|
38498
38964
|
for (const signal of page.signals) {
|
|
38499
38965
|
if (abort?.aborted) {
|
|
38500
38966
|
stop = { reason: "cancelled" };
|
|
@@ -38587,6 +39053,10 @@ async function runListenerRuntime(options) {
|
|
|
38587
39053
|
} finally {
|
|
38588
39054
|
abort?.removeEventListener("abort", onAbort);
|
|
38589
39055
|
options.model.cancel();
|
|
39056
|
+
try {
|
|
39057
|
+
await wakeSubscriber?.close();
|
|
39058
|
+
} catch {
|
|
39059
|
+
}
|
|
38590
39060
|
try {
|
|
38591
39061
|
await options.model.close();
|
|
38592
39062
|
} catch (error) {
|
|
@@ -38604,6 +39074,7 @@ var LISTENER_READ_RETRY_HOUR_CAP = 25;
|
|
|
38604
39074
|
var LISTENER_READ_RETRY_MINUTE_CAP = 61;
|
|
38605
39075
|
var LISTENER_CLAIM_HOUR_CAP = 25;
|
|
38606
39076
|
var LISTENER_THROUGHPUT_LAPSE_RATIO = 0.5;
|
|
39077
|
+
var LISTENER_MODE_CHANGE_SKIP_MAX = 1;
|
|
38607
39078
|
var FAILURE_CODES = /* @__PURE__ */ new Set([
|
|
38608
39079
|
"http_status",
|
|
38609
39080
|
"no_response",
|
|
@@ -38700,9 +39171,20 @@ function recordListenerReadRecovery(health, input) {
|
|
|
38700
39171
|
retryHours: trimNewest(retryHours, LISTENER_READ_RETRY_HOUR_CAP)
|
|
38701
39172
|
};
|
|
38702
39173
|
}
|
|
39174
|
+
function freezeClosedHourExpectedClaims(rows3, currentHourStart) {
|
|
39175
|
+
return rows3.map((row) => {
|
|
39176
|
+
if (row.hourStart === currentHourStart) return row;
|
|
39177
|
+
if (row.expectedClaims !== void 0) return row;
|
|
39178
|
+
if (row.cadenceMs === void 0 || row.cadenceMs < 1) return row;
|
|
39179
|
+
return { ...row, expectedClaims: HOUR_MS / row.cadenceMs };
|
|
39180
|
+
});
|
|
39181
|
+
}
|
|
38703
39182
|
function recordListenerClaimCadence(health, cadenceMs, ts) {
|
|
38704
39183
|
const hourStart = bucketStart(ts, HOUR_MS);
|
|
38705
|
-
const claimHours =
|
|
39184
|
+
const claimHours = freezeClosedHourExpectedClaims(
|
|
39185
|
+
health.claimHours.map((row) => ({ ...row })),
|
|
39186
|
+
hourStart
|
|
39187
|
+
);
|
|
38706
39188
|
const hour = claimHours.find((row) => row.hourStart === hourStart);
|
|
38707
39189
|
if (hour) {
|
|
38708
39190
|
hour.cadenceMs = hour.cadenceMs === void 0 ? cadenceMs : Math.max(hour.cadenceMs, cadenceMs);
|
|
@@ -38715,9 +39197,26 @@ function recordListenerClaimCadence(health, cadenceMs, ts) {
|
|
|
38715
39197
|
claimHours: trimNewest(claimHours, LISTENER_CLAIM_HOUR_CAP)
|
|
38716
39198
|
};
|
|
38717
39199
|
}
|
|
39200
|
+
function recordListenerWakeModeChange(health, ts) {
|
|
39201
|
+
const hourStart = bucketStart(ts, HOUR_MS);
|
|
39202
|
+
const claimHours = freezeClosedHourExpectedClaims(
|
|
39203
|
+
health.claimHours.map((row) => ({ ...row })),
|
|
39204
|
+
hourStart
|
|
39205
|
+
);
|
|
39206
|
+
const hour = claimHours.find((row) => row.hourStart === hourStart);
|
|
39207
|
+
if (hour) hour.modeChanged = true;
|
|
39208
|
+
else claimHours.push({ hourStart, claims: 0, modeChanged: true });
|
|
39209
|
+
return {
|
|
39210
|
+
...health,
|
|
39211
|
+
claimHours: trimNewest(claimHours, LISTENER_CLAIM_HOUR_CAP)
|
|
39212
|
+
};
|
|
39213
|
+
}
|
|
38718
39214
|
function recordListenerClaim(health, ts) {
|
|
38719
39215
|
const hourStart = bucketStart(ts, HOUR_MS);
|
|
38720
|
-
const claimHours =
|
|
39216
|
+
const claimHours = freezeClosedHourExpectedClaims(
|
|
39217
|
+
health.claimHours.map((row) => ({ ...row })),
|
|
39218
|
+
hourStart
|
|
39219
|
+
);
|
|
38721
39220
|
const hour = claimHours.find((row) => row.hourStart === hourStart);
|
|
38722
39221
|
if (hour) hour.claims += 1;
|
|
38723
39222
|
else claimHours.push({ hourStart, claims: 1 });
|
|
@@ -38774,9 +39273,13 @@ function parseListenerReadHealth(value, rejectUnknownKeys = false) {
|
|
|
38774
39273
|
const hour = value2;
|
|
38775
39274
|
if (!hasExpectedKeys(hour, ["hourStart", "claims"], false) || !validTimestamp(hour.hourStart) || !validCount(hour.claims)) return null;
|
|
38776
39275
|
if (rejectUnknownKeys && Object.keys(hour).some(
|
|
38777
|
-
(key2) => key2 !== "hourStart" && key2 !== "claims" && key2 !== "cadenceMs"
|
|
39276
|
+
(key2) => key2 !== "hourStart" && key2 !== "claims" && key2 !== "cadenceMs" && key2 !== "expectedClaims" && key2 !== "modeChanged"
|
|
38778
39277
|
)) return null;
|
|
38779
39278
|
if (hour.cadenceMs !== void 0 && !(typeof hour.cadenceMs === "number" && Number.isSafeInteger(hour.cadenceMs) && hour.cadenceMs >= 1)) return null;
|
|
39279
|
+
if (hour.expectedClaims !== void 0 && !(typeof hour.expectedClaims === "number" && Number.isFinite(hour.expectedClaims) && hour.expectedClaims > 0)) return null;
|
|
39280
|
+
if (hour.modeChanged !== void 0 && typeof hour.modeChanged !== "boolean") {
|
|
39281
|
+
return null;
|
|
39282
|
+
}
|
|
38780
39283
|
}
|
|
38781
39284
|
return {
|
|
38782
39285
|
currentEpisodeStartedAt: row.currentEpisodeStartedAt,
|
|
@@ -38798,10 +39301,14 @@ function parseListenerReadHealth(value, rejectUnknownKeys = false) {
|
|
|
38798
39301
|
claimCadenceMs: row.claimCadenceMs,
|
|
38799
39302
|
claimHours: row.claimHours.map((hour) => {
|
|
38800
39303
|
const cadenceMs = hour.cadenceMs;
|
|
39304
|
+
const expectedClaims = hour.expectedClaims;
|
|
39305
|
+
const modeChanged = hour.modeChanged;
|
|
38801
39306
|
return {
|
|
38802
39307
|
hourStart: hour.hourStart,
|
|
38803
39308
|
claims: hour.claims,
|
|
38804
|
-
...typeof cadenceMs === "number" ? { cadenceMs } : {}
|
|
39309
|
+
...typeof cadenceMs === "number" ? { cadenceMs } : {},
|
|
39310
|
+
...typeof expectedClaims === "number" ? { expectedClaims } : {},
|
|
39311
|
+
...modeChanged === true ? { modeChanged: true } : {}
|
|
38805
39312
|
};
|
|
38806
39313
|
})
|
|
38807
39314
|
};
|
|
@@ -38838,14 +39345,18 @@ function summarizeListenerReadHealth(health, readyAt, nowMs) {
|
|
|
38838
39345
|
health.claimHours.map((row) => [row.hourStart, row.claims])
|
|
38839
39346
|
);
|
|
38840
39347
|
const cadenceByHour = /* @__PURE__ */ new Map();
|
|
39348
|
+
const expectedByHour = /* @__PURE__ */ new Map();
|
|
38841
39349
|
for (const row of health.claimHours) {
|
|
38842
39350
|
if (row.cadenceMs !== void 0) cadenceByHour.set(row.hourStart, row.cadenceMs);
|
|
39351
|
+
if (row.expectedClaims !== void 0) {
|
|
39352
|
+
expectedByHour.set(row.hourStart, row.expectedClaims);
|
|
39353
|
+
}
|
|
38843
39354
|
}
|
|
38844
39355
|
for (let hour = first; hour < currentHour; hour += HOUR_MS) {
|
|
38845
39356
|
const hourStart = new Date(hour).toISOString();
|
|
38846
39357
|
const claims = claimsByHour.get(hourStart) ?? 0;
|
|
38847
39358
|
const cadenceMs = cadenceByHour.get(hourStart) ?? health.claimCadenceMs;
|
|
38848
|
-
const expectedClaims = HOUR_MS / cadenceMs;
|
|
39359
|
+
const expectedClaims = expectedByHour.get(hourStart) ?? HOUR_MS / cadenceMs;
|
|
38849
39360
|
claimThroughputHours.push({
|
|
38850
39361
|
hourStart,
|
|
38851
39362
|
claims,
|
|
@@ -38854,9 +39365,25 @@ function summarizeListenerReadHealth(health, readyAt, nowMs) {
|
|
|
38854
39365
|
});
|
|
38855
39366
|
}
|
|
38856
39367
|
}
|
|
38857
|
-
const
|
|
38858
|
-
|
|
38859
|
-
|
|
39368
|
+
const consecutiveModeChangedHours = (hourStart) => {
|
|
39369
|
+
let count2 = 0;
|
|
39370
|
+
let t = Date.parse(hourStart);
|
|
39371
|
+
if (!Number.isFinite(t)) return 0;
|
|
39372
|
+
while (true) {
|
|
39373
|
+
const start = new Date(t).toISOString();
|
|
39374
|
+
const row = health.claimHours.find((hour) => hour.hourStart === start);
|
|
39375
|
+
if (row?.modeChanged !== true) break;
|
|
39376
|
+
count2 += 1;
|
|
39377
|
+
t -= HOUR_MS;
|
|
39378
|
+
}
|
|
39379
|
+
return count2;
|
|
39380
|
+
};
|
|
39381
|
+
const throughputLapseHours = claimThroughputHours.filter((hour) => {
|
|
39382
|
+
if (hour.ratio >= LISTENER_THROUGHPUT_LAPSE_RATIO) return false;
|
|
39383
|
+
const consecutive = consecutiveModeChangedHours(hour.hourStart);
|
|
39384
|
+
if (consecutive === 0) return true;
|
|
39385
|
+
return consecutive > LISTENER_MODE_CHANGE_SKIP_MAX;
|
|
39386
|
+
});
|
|
38860
39387
|
return {
|
|
38861
39388
|
currentEpisodeDurationMs,
|
|
38862
39389
|
episodesLast24h,
|
|
@@ -38960,7 +39487,8 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
38960
39487
|
"connectionReuseRatio",
|
|
38961
39488
|
"activityPublishFailures",
|
|
38962
39489
|
"activityLastErrorCode",
|
|
38963
|
-
"idlePollMs"
|
|
39490
|
+
"idlePollMs",
|
|
39491
|
+
"wake"
|
|
38964
39492
|
]);
|
|
38965
39493
|
var STATUS_ACTIVITY_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
38966
39494
|
"activity_credential_failed",
|
|
@@ -38983,7 +39511,10 @@ var STATUS_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
|
38983
39511
|
"reply",
|
|
38984
39512
|
"owner",
|
|
38985
39513
|
"ownerId",
|
|
38986
|
-
"owner_id"
|
|
39514
|
+
"owner_id",
|
|
39515
|
+
"topic",
|
|
39516
|
+
"wakeTopic",
|
|
39517
|
+
"wake_topic"
|
|
38987
39518
|
]);
|
|
38988
39519
|
var STATUS_DELIVERY_KEYS = [
|
|
38989
39520
|
"deliveryMode",
|
|
@@ -39020,6 +39551,37 @@ function parseHeldBackDeliveries(value) {
|
|
|
39020
39551
|
}
|
|
39021
39552
|
return parsed;
|
|
39022
39553
|
}
|
|
39554
|
+
var WAKE_STATUS_KEY_SET = new Set(LISTENER_WAKE_STATUS_KEYS);
|
|
39555
|
+
var WAKE_SENSITIVE_KEY_SET = new Set(LISTENER_WAKE_SENSITIVE_KEYS);
|
|
39556
|
+
function nullableIso(value) {
|
|
39557
|
+
return value === null || typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
39558
|
+
}
|
|
39559
|
+
function parseListenerWake(value, rejectUnknownKeys = false) {
|
|
39560
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
39561
|
+
const row = value;
|
|
39562
|
+
for (const key2 of Object.keys(row)) {
|
|
39563
|
+
if (WAKE_SENSITIVE_KEY_SET.has(key2)) return null;
|
|
39564
|
+
if (rejectUnknownKeys && !WAKE_STATUS_KEY_SET.has(key2)) return null;
|
|
39565
|
+
}
|
|
39566
|
+
for (const key2 of LISTENER_WAKE_STATUS_KEYS) {
|
|
39567
|
+
if (!(key2 in row)) return null;
|
|
39568
|
+
}
|
|
39569
|
+
const mode3 = LISTENER_WAKE_MODES.find((item) => item === row.mode);
|
|
39570
|
+
if (mode3 === void 0 || !nullableIso(row.subscribedAt) || !(typeof row.reconnects === "number" && Number.isSafeInteger(row.reconnects) && row.reconnects >= 0) || !nullableIso(row.lastWakeAt) || !nullableIso(row.lastReconcileAt) || !(row.errorCode === null || typeof row.errorCode === "string" && WAKE_ERROR_CODE_SET.has(row.errorCode)) || !nullableIso(row.topicRotatedAt) || typeof row.rateLimited !== "boolean") {
|
|
39571
|
+
return null;
|
|
39572
|
+
}
|
|
39573
|
+
if (mode3 === LISTENER_WAKE_MODE_PUSH && row.subscribedAt === null) return null;
|
|
39574
|
+
return {
|
|
39575
|
+
mode: mode3,
|
|
39576
|
+
subscribedAt: row.subscribedAt,
|
|
39577
|
+
reconnects: row.reconnects,
|
|
39578
|
+
lastWakeAt: row.lastWakeAt,
|
|
39579
|
+
lastReconcileAt: row.lastReconcileAt,
|
|
39580
|
+
errorCode: row.errorCode,
|
|
39581
|
+
topicRotatedAt: row.topicRotatedAt,
|
|
39582
|
+
rateLimited: row.rateLimited
|
|
39583
|
+
};
|
|
39584
|
+
}
|
|
39023
39585
|
function parseStatus(raw, rejectUnknownKeys = false) {
|
|
39024
39586
|
let value;
|
|
39025
39587
|
try {
|
|
@@ -39044,7 +39606,8 @@ function parseStatus(raw, rejectUnknownKeys = false) {
|
|
|
39044
39606
|
const nullableTimestamp3 = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
|
|
39045
39607
|
const readHealth = row.readHealth === void 0 ? void 0 : parseListenerReadHealth(row.readHealth, rejectUnknownKeys);
|
|
39046
39608
|
const heldBackDeliveries = row.heldBackDeliveries === void 0 ? void 0 : parseHeldBackDeliveries(row.heldBackDeliveries);
|
|
39047
|
-
|
|
39609
|
+
const wake = row.wake === void 0 ? void 0 : parseListenerWake(row.wake, rejectUnknownKeys);
|
|
39610
|
+
if (row.version !== 1 || typeof row.instanceId !== "string" || !UUID_RE18.test(row.instanceId) || row.provider !== "grok" && row.provider !== "opencode" && row.provider !== "claude" && row.provider !== "codex" || typeof row.profileId !== "string" || typeof row.workspaceId !== "string" || !UUID_RE18.test(row.workspaceId) || typeof row.principalId !== "string" || !UUID_RE18.test(row.principalId) || !Number.isSafeInteger(row.pid) || row.pid < 1 || typeof row.state !== "string" || !["starting", "ready", "stopping", "stopped", "failed"].includes(row.state) || typeof row.startedAt !== "string" || !Number.isFinite(Date.parse(row.startedAt)) || !(row.readyAt === null || typeof row.readyAt === "string" && Number.isFinite(Date.parse(row.readyAt))) || typeof row.updatedAt !== "string" || !Number.isFinite(Date.parse(row.updatedAt)) || !(row.stoppedAt === null || typeof row.stoppedAt === "string" && Number.isFinite(Date.parse(row.stoppedAt))) || !nullableUuid3(row.lastSignalId) || !(row.lastErrorCode === null || typeof row.lastErrorCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorCode)) || !(row.lastErrorDetail === void 0 || row.lastErrorDetail === null || typeof row.lastErrorDetail === "string" && row.lastErrorDetail.length > 0 && row.lastErrorDetail.length <= 2048 && !SECRET_SHAPE_RE.test(row.lastErrorDetail)) || !(row.lastErrorReasonCode === void 0 || row.lastErrorReasonCode === null || typeof row.lastErrorReasonCode === "string" && /^[a-z0-9_-]{1,96}$/.test(row.lastErrorReasonCode)) || !(row.providerExecutable === void 0 || row.providerExecutable === null || typeof row.providerExecutable === "string" && (0, import_node_path16.isAbsolute)(row.providerExecutable)) || !(row.providerVersion === void 0 || row.providerVersion === null || typeof row.providerVersion === "string" && SEMVER_RE2.test(row.providerVersion)) || !(row.providerLastMeasuredVersion === void 0 || row.providerLastMeasuredVersion === null || typeof row.providerLastMeasuredVersion === "string" && SEMVER_RE2.test(row.providerLastMeasuredVersion)) || !(row.providerBundledAgentSdkVersion === void 0 || row.providerBundledAgentSdkVersion === null || typeof row.providerBundledAgentSdkVersion === "string" && SEMVER_RE2.test(row.providerBundledAgentSdkVersion)) || !(row.providerBundledClaudeCodeVersion === void 0 || row.providerBundledClaudeCodeVersion === null || typeof row.providerBundledClaudeCodeVersion === "string" && SEMVER_RE2.test(row.providerBundledClaudeCodeVersion)) || !(row.providerMinimumRequiredVersion === void 0 || row.providerMinimumRequiredVersion === null || typeof row.providerMinimumRequiredVersion === "string" && SEMVER_RE2.test(row.providerMinimumRequiredVersion)) || !(row.cswarmVersion === void 0 || row.cswarmVersion === null || typeof row.cswarmVersion === "string" && SEMVER_RE2.test(row.cswarmVersion)) || (row.providerVersion === null || row.providerVersion === void 0) !== (row.providerLastMeasuredVersion === null || row.providerLastMeasuredVersion === void 0) || !(row.lastWorkerStderrTail === void 0 || row.lastWorkerStderrTail === null || typeof row.lastWorkerStderrTail === "string" && row.lastWorkerStderrTail.length > 0 && row.lastWorkerStderrTail.length <= 2048 && !SECRET_SHAPE_RE.test(row.lastWorkerStderrTail)) || typeof row.logPath !== "string" || !(0, import_node_path16.isAbsolute)(row.logPath) || !(row.deliveryMode === void 0 || row.deliveryMode === null || typeof row.deliveryMode === "string" && STATUS_DELIVERY_MODES.has(row.deliveryMode)) || !(row.pendingDeliveryCount === void 0 || nullableCount(row.pendingDeliveryCount)) || !(row.lastTerminalDeliveryFailureCount === void 0 || nullableCount(row.lastTerminalDeliveryFailureCount)) || !(row.lastTerminalDeliveryFailureAt === void 0 || nullableTimestamp3(row.lastTerminalDeliveryFailureAt)) || !(row.lastClaimAt === void 0 || nullableTimestamp3(row.lastClaimAt)) || !(row.lastAckAt === void 0 || nullableTimestamp3(row.lastAckAt)) || !(row.lastAckOutcome === void 0 || row.lastAckOutcome === null || typeof row.lastAckOutcome === "string" && deliveryOutcomes.has(row.lastAckOutcome)) || !(row.consecutiveAckFailureCount === void 0 || nullableCount(row.consecutiveAckFailureCount)) || !(row.lastAckSignalId === void 0 || row.lastAckSignalId === null || typeof row.lastAckSignalId === "string" && UUID_RE18.test(row.lastAckSignalId)) || !(row.currentDeliverySignalId === void 0 || row.currentDeliverySignalId === null || typeof row.currentDeliverySignalId === "string" && UUID_RE18.test(row.currentDeliverySignalId)) || !(row.currentDeliverySince === void 0 || nullableTimestamp3(row.currentDeliverySince)) || heldBackDeliveries === null || !(row.pendingDeliveryCountAt === void 0 || nullableTimestamp3(row.pendingDeliveryCountAt)) || !(row.routeMode === void 0 || row.routeMode === "worker" || row.routeMode === "main" || row.routeMode === "split") || !(row.deferOverChars === void 0 || row.deferOverChars === null || typeof row.deferOverChars === "number" && Number.isSafeInteger(row.deferOverChars) && row.deferOverChars >= 1 && row.deferOverChars <= 1e4) || !(row.pendingForMainCount === void 0 || typeof row.pendingForMainCount === "number" && Number.isSafeInteger(row.pendingForMainCount) && row.pendingForMainCount >= 0) || !(row.droppedForMainCount === void 0 || typeof row.droppedForMainCount === "number" && Number.isSafeInteger(row.droppedForMainCount) && row.droppedForMainCount >= 0) || readHealth === null || wake === null || !(row.connectionsOpened === void 0 || typeof row.connectionsOpened === "number" && Number.isSafeInteger(row.connectionsOpened) && row.connectionsOpened >= 0) || !(row.connectionReuseRatio === void 0 || typeof row.connectionReuseRatio === "number" && Number.isFinite(row.connectionReuseRatio) && row.connectionReuseRatio >= 0) || !(row.activityPublishFailures === void 0 || typeof row.activityPublishFailures === "number" && Number.isSafeInteger(row.activityPublishFailures) && row.activityPublishFailures >= 0) || !(row.activityLastErrorCode === void 0 || row.activityLastErrorCode === null || typeof row.activityLastErrorCode === "string" && STATUS_ACTIVITY_ERROR_CODES.has(
|
|
39048
39611
|
row.activityLastErrorCode
|
|
39049
39612
|
)) || !(row.idlePollMs === void 0 || row.idlePollMs === null || typeof row.idlePollMs === "number" && Number.isSafeInteger(row.idlePollMs) && row.idlePollMs >= 0)) {
|
|
39050
39613
|
throw new Error("stored listener status is malformed");
|
|
@@ -39090,7 +39653,8 @@ function parseStatus(raw, rejectUnknownKeys = false) {
|
|
|
39090
39653
|
pendingForMainCount: row.pendingForMainCount ?? 0,
|
|
39091
39654
|
droppedForMainCount: row.droppedForMainCount ?? 0,
|
|
39092
39655
|
...readHealth === void 0 ? {} : { readHealth },
|
|
39093
|
-
...row.idlePollMs === void 0 ? {} : { idlePollMs: row.idlePollMs ?? null }
|
|
39656
|
+
...row.idlePollMs === void 0 ? {} : { idlePollMs: row.idlePollMs ?? null },
|
|
39657
|
+
...wake === void 0 ? {} : { wake }
|
|
39094
39658
|
};
|
|
39095
39659
|
}
|
|
39096
39660
|
async function writeListenerStatus(paths, status) {
|
|
@@ -39161,7 +39725,11 @@ async function appendListenerEvent(paths, event) {
|
|
|
39161
39725
|
// How long one delivery held the worker seat, and why it gave it back.
|
|
39162
39726
|
"held_ms",
|
|
39163
39727
|
"release_reason",
|
|
39164
|
-
"idle_poll_ms"
|
|
39728
|
+
"idle_poll_ms",
|
|
39729
|
+
"wake_mode",
|
|
39730
|
+
"wake_error_code",
|
|
39731
|
+
"wake_reconnects",
|
|
39732
|
+
"rate_limited"
|
|
39165
39733
|
]);
|
|
39166
39734
|
const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
|
|
39167
39735
|
const routeModes = /* @__PURE__ */ new Set(["worker", "main", "split"]);
|
|
@@ -39229,6 +39797,18 @@ async function appendListenerEvent(paths, event) {
|
|
|
39229
39797
|
if (key2 === "idle_poll_ms" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
|
|
39230
39798
|
throw new Error("listener event idle poll interval is not allowed");
|
|
39231
39799
|
}
|
|
39800
|
+
if (key2 === "wake_mode" && !(typeof value === "string" && LISTENER_WAKE_MODE_SET.has(value))) {
|
|
39801
|
+
throw new Error("listener event wake mode is not allowed");
|
|
39802
|
+
}
|
|
39803
|
+
if (key2 === "wake_error_code" && !(value === null || typeof value === "string" && WAKE_ERROR_CODE_SET.has(value))) {
|
|
39804
|
+
throw new Error("listener event wake error code is not allowed");
|
|
39805
|
+
}
|
|
39806
|
+
if (key2 === "wake_reconnects" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
|
|
39807
|
+
throw new Error("listener event wake reconnect count is not allowed");
|
|
39808
|
+
}
|
|
39809
|
+
if (key2 === "rate_limited" && typeof value !== "boolean") {
|
|
39810
|
+
throw new Error("listener event rate-limited flag is not allowed");
|
|
39811
|
+
}
|
|
39232
39812
|
if (key2 === "release_reason" && !(typeof value === "string" && LISTENER_DELIVERY_HOLD_RELEASE_REASONS.includes(
|
|
39233
39813
|
value
|
|
39234
39814
|
))) {
|
|
@@ -39640,6 +40220,7 @@ async function runListenerSupervisor(options) {
|
|
|
39640
40220
|
activityPublishFailures: 0,
|
|
39641
40221
|
activityLastErrorCode: null,
|
|
39642
40222
|
idlePollMs: null,
|
|
40223
|
+
wake: emptyListenerWakeStatus(),
|
|
39643
40224
|
logPath: options.paths.logPath
|
|
39644
40225
|
};
|
|
39645
40226
|
let writes = Promise.resolve();
|
|
@@ -39712,7 +40293,50 @@ async function runListenerSupervisor(options) {
|
|
|
39712
40293
|
const fitted = fitWorkerStderrTailForLog(tail);
|
|
39713
40294
|
return fitted.length > 0 ? fitted : null;
|
|
39714
40295
|
};
|
|
40296
|
+
let lastWakePersistMs = 0;
|
|
39715
40297
|
const onEvent = (event) => {
|
|
40298
|
+
if (event.type === "wake") {
|
|
40299
|
+
const previousWake = status.wake;
|
|
40300
|
+
const previous = previousWake?.mode;
|
|
40301
|
+
let readHealth = status.readHealth ?? emptyListenerReadHealth();
|
|
40302
|
+
if (previous !== void 0 && previous !== event.wake.mode) {
|
|
40303
|
+
readHealth = recordListenerWakeModeChange(readHealth, event.ts);
|
|
40304
|
+
}
|
|
40305
|
+
const cadenceMs = event.wake.mode === LISTENER_WAKE_MODE_PUSH ? LISTENER_RECONCILE_POLL_MS : status.idlePollMs && status.idlePollMs > 0 ? status.idlePollMs : null;
|
|
40306
|
+
if (cadenceMs !== null) {
|
|
40307
|
+
readHealth = recordListenerClaimCadence(
|
|
40308
|
+
readHealth,
|
|
40309
|
+
cadenceMs,
|
|
40310
|
+
event.ts
|
|
40311
|
+
);
|
|
40312
|
+
}
|
|
40313
|
+
status = {
|
|
40314
|
+
...status,
|
|
40315
|
+
wake: event.wake,
|
|
40316
|
+
readHealth,
|
|
40317
|
+
updatedAt: event.ts
|
|
40318
|
+
};
|
|
40319
|
+
const eventMs = Date.parse(event.ts);
|
|
40320
|
+
const nowMs = Number.isFinite(eventMs) ? eventMs : Date.now();
|
|
40321
|
+
if (listenerWakePersistWorthy(
|
|
40322
|
+
previousWake,
|
|
40323
|
+
event.wake,
|
|
40324
|
+
lastWakePersistMs,
|
|
40325
|
+
nowMs
|
|
40326
|
+
)) {
|
|
40327
|
+
lastWakePersistMs = nowMs;
|
|
40328
|
+
persist();
|
|
40329
|
+
log({
|
|
40330
|
+
ts: event.ts,
|
|
40331
|
+
event: "listener_wake",
|
|
40332
|
+
wake_mode: event.wake.mode,
|
|
40333
|
+
wake_error_code: event.wake.errorCode,
|
|
40334
|
+
wake_reconnects: event.wake.reconnects,
|
|
40335
|
+
rate_limited: event.wake.rateLimited
|
|
40336
|
+
});
|
|
40337
|
+
}
|
|
40338
|
+
return;
|
|
40339
|
+
}
|
|
39716
40340
|
if (event.type === "idle_poll") {
|
|
39717
40341
|
status = {
|
|
39718
40342
|
...status,
|
|
@@ -43028,8 +43652,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
43028
43652
|
]);
|
|
43029
43653
|
var UUID_RE23 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
43030
43654
|
function packageVersion() {
|
|
43031
|
-
if ("0.1.
|
|
43032
|
-
return "0.1.
|
|
43655
|
+
if ("0.1.59".length > 0) {
|
|
43656
|
+
return "0.1.59";
|
|
43033
43657
|
}
|
|
43034
43658
|
try {
|
|
43035
43659
|
const value = JSON.parse(
|
|
@@ -46368,6 +46992,8 @@ function listenerStatusJson(status, permissionMode, evidence = {
|
|
|
46368
46992
|
claimCadenceMs: readHealth.claimCadenceMs,
|
|
46369
46993
|
idlePollMs: status.idlePollMs ?? null,
|
|
46370
46994
|
idlePollSentence: status.idlePollMs === void 0 || status.idlePollMs === null ? null : idlePollStatusSentence(status.idlePollMs),
|
|
46995
|
+
wake: status.wake ?? emptyListenerWakeStatus(),
|
|
46996
|
+
mode: (status.wake ?? emptyListenerWakeStatus()).mode,
|
|
46371
46997
|
claimThroughputHours: readSummary.claimThroughputHours,
|
|
46372
46998
|
listenerLapse: lapseNotices.length > 0,
|
|
46373
46999
|
listenerLapseCodes: lapseNotices.map((notice) => notice.code),
|
|
@@ -46439,7 +47065,12 @@ function renderListenerStatus(status, evidence = {
|
|
|
46439
47065
|
readSummary.longestEpisodeAttemptsLast24h === 0 ? "Longest read retry episode in the last 24h: none recorded." : `Longest read retry episode in the last 24h: ${readSummary.longestEpisodeAttemptsLast24h} attempts over ${Math.floor(readSummary.longestEpisodeDurationMsLast24h / 1e3)}s.`,
|
|
46440
47066
|
readSummary.retryHours.length === 0 ? "Read retries by hour in the last 24h: none." : `Read retries by hour in the last 24h: ${readSummary.retryHours.map((hour) => `${hour.hourStart}=${hour.retries}`).join("; ")}.`,
|
|
46441
47067
|
readSummary.claimThroughputHours.length === 0 ? "Claim throughput by full hour: no complete listener hour is available yet." : `Claim throughput by full hour: ${readSummary.claimThroughputHours.map((hour) => `${hour.hourStart} ${hour.claims}/${Math.round(hour.expectedClaims)} (${hour.ratio.toFixed(3)})`).join("; ")}.`,
|
|
46442
|
-
status.idlePollMs === void 0 || status.idlePollMs === null ? "Current idle poll interval has not been reported yet." : idlePollStatusSentence(status.idlePollMs)
|
|
47068
|
+
status.idlePollMs === void 0 || status.idlePollMs === null ? "Current idle poll interval has not been reported yet." : idlePollStatusSentence(status.idlePollMs),
|
|
47069
|
+
listenerWakeStatusSentence(
|
|
47070
|
+
status.wake ?? emptyListenerWakeStatus(),
|
|
47071
|
+
status.idlePollMs && status.idlePollMs > 0 ? status.idlePollMs : IDLE_POLL_DEFAULT_MS,
|
|
47072
|
+
status.wake?.lastWakeAt ? relativeAge(status.wake.lastWakeAt, nowMs) : null
|
|
47073
|
+
)
|
|
46443
47074
|
];
|
|
46444
47075
|
for (const notice of lapseNotices) {
|
|
46445
47076
|
lines.push(`WARNING [${notice.code}]: ${notice.message}`);
|