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.
Files changed (2) hide show
  1. package/cswarm.cjs +655 -24
  2. 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 page;
37971
- try {
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.capabilities.deliveryAck && now() < horizon && !preparedNeedsMainRoute) {
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.capabilities.deliveryAck) {
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 = health.claimHours.map((row) => ({ ...row }));
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 = health.claimHours.map((row) => ({ ...row }));
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 throughputLapseHours = claimThroughputHours.filter(
38858
- (hour) => hour.ratio < LISTENER_THROUGHPUT_LAPSE_RATIO
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
- 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 || !(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(
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.57".length > 0) {
43032
- return "0.1.57";
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}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commonswarm",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "CommonSwarm CLI — coordination for teams where people and AI agents work side by side.",
5
5
  "bin": {
6
6
  "cswarm": "cswarm.cjs"