commonswarm 0.1.57 → 0.1.58

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 +665 -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,346 @@ 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 finish = (reason) => {
37678
+ if (this.waiter === null) return;
37679
+ const current = this.waiter;
37680
+ this.waiter = null;
37681
+ if (current.timer !== null) clearTimeout(current.timer);
37682
+ if (current.signal && current.onAbort) {
37683
+ current.signal.removeEventListener("abort", current.onAbort);
37684
+ }
37685
+ resolve3(reason);
37686
+ };
37687
+ const delay2 = Math.max(0, options.until - this.now());
37688
+ const timer2 = setTimeout(() => finish("deadline"), delay2);
37689
+ const onAbort = () => finish("deadline");
37690
+ options.signal?.addEventListener("abort", onAbort, { once: true });
37691
+ this.waiter = {
37692
+ resolve: finish,
37693
+ timer: timer2,
37694
+ onAbort,
37695
+ signal: options.signal
37696
+ };
37697
+ });
37698
+ }
37699
+ async close() {
37700
+ this.closed = true;
37701
+ this.topic = null;
37702
+ this.finishWait("deadline");
37703
+ await this.detachChannel();
37704
+ try {
37705
+ this.realtime?.disconnect?.();
37706
+ } catch {
37707
+ }
37708
+ this.realtime = null;
37709
+ this.connectionState = "disconnected";
37710
+ }
37711
+ trimWakeClaims(nowMs) {
37712
+ const minuteStart = Math.floor(nowMs / 6e4) * 6e4;
37713
+ this.wakeClaimTimes = this.wakeClaimTimes.filter((ts) => ts >= minuteStart);
37714
+ }
37715
+ /** Client budget or a server 429: do not claim on wake; poll covers the window. */
37716
+ wakeClaimPaused(nowMs) {
37717
+ return nowMs < this.rateLimitedUntil || this.overWakeBudget(nowMs);
37718
+ }
37719
+ emitPending(reason) {
37720
+ if (this.waiter !== null) {
37721
+ if (reason === "wake" && this.wakeClaimPaused(this.now())) {
37722
+ this.pending = "wake";
37723
+ return;
37724
+ }
37725
+ this.finishWait(reason);
37726
+ return;
37727
+ }
37728
+ if (reason === "wake" || this.pending !== "wake") {
37729
+ this.pending = reason;
37730
+ }
37731
+ }
37732
+ finishWait(reason) {
37733
+ const waiter = this.waiter;
37734
+ if (waiter === null) return;
37735
+ this.waiter = null;
37736
+ if (waiter.timer !== null) clearTimeout(waiter.timer);
37737
+ if (waiter.signal && waiter.onAbort) {
37738
+ waiter.signal.removeEventListener("abort", waiter.onAbort);
37739
+ }
37740
+ waiter.resolve(reason);
37741
+ }
37742
+ connect() {
37743
+ if (this.closed || this.topic === null) return;
37744
+ if (this.realtime === null) {
37745
+ this.realtime = this.createRealtime(this.target);
37746
+ void this.realtime.setAuth(this.target.anonKey);
37747
+ }
37748
+ const topic = this.topic;
37749
+ this.connectionState = "connecting";
37750
+ this.lastErrorCode = null;
37751
+ const channel = this.realtime.channel(topic, {
37752
+ config: { private: true }
37753
+ });
37754
+ this.channel = channel;
37755
+ channel.on("broadcast", { event: WAKE_EVENT }, () => {
37756
+ this.lastWakeAt = new Date(this.now()).toISOString();
37757
+ this.emitPending("wake");
37758
+ });
37759
+ channel.subscribe((status) => {
37760
+ this.onSubscribeStatus(status);
37761
+ });
37762
+ }
37763
+ onSubscribeStatus(status) {
37764
+ if (this.closed) return;
37765
+ if (!isSubscribeStatus(status)) return;
37766
+ const wasSubscribed = this.connectionState === "subscribed";
37767
+ if (status === REALTIME_SUBSCRIBE_STATUS.SUBSCRIBED) {
37768
+ this.connectionState = "subscribed";
37769
+ this.subscribedAt = new Date(this.now()).toISOString();
37770
+ this.lastErrorCode = null;
37771
+ if (this.everSubscribed && !wasSubscribed) this.reconnects += 1;
37772
+ this.everSubscribed = true;
37773
+ if (!wasSubscribed) this.emitPending("state");
37774
+ return;
37775
+ }
37776
+ const code = wakeErrorCodeFromSubscribeStatus(status);
37777
+ if (status === REALTIME_SUBSCRIBE_STATUS.CLOSED) {
37778
+ this.connectionState = "disconnected";
37779
+ } else {
37780
+ this.connectionState = "errored";
37781
+ }
37782
+ this.lastErrorCode = code;
37783
+ this.subscribedAt = null;
37784
+ if (wasSubscribed) this.emitPending("state");
37785
+ }
37786
+ async detachChannel() {
37787
+ const channel = this.channel;
37788
+ this.channel = null;
37789
+ if (channel === null) return;
37790
+ try {
37791
+ await channel.unsubscribe();
37792
+ } catch {
37793
+ }
37794
+ try {
37795
+ await this.realtime?.removeChannel?.(channel);
37796
+ } catch {
37797
+ }
37798
+ if (this.channel !== null) return;
37799
+ if (this.connectionState === "subscribed") {
37800
+ this.connectionState = "disconnected";
37801
+ this.subscribedAt = null;
37802
+ }
37803
+ }
37804
+ };
37805
+ function createWakeSubscriber(options) {
37806
+ return new WakeSubscriber(options);
37807
+ }
37808
+
37415
37809
  // src/listener/runtime.ts
37416
37810
  var LISTENER_PAGE_LIMIT = 100;
37417
37811
  var LISTENER_IDLE_POLL_MS = IDLE_POLL_DEFAULT_MS;
@@ -37901,6 +38295,35 @@ async function runListenerRuntime(options) {
37901
38295
  abort.addEventListener("abort", onAbort);
37902
38296
  }
37903
38297
  let stop;
38298
+ let wakeSubscriber = options.wake ?? null;
38299
+ let reconcileDueAt = now();
38300
+ const ensureWake = () => {
38301
+ if (wakeSubscriber === null) {
38302
+ wakeSubscriber = options.createWake ? options.createWake(options.target) : createWakeSubscriber({ target: options.target, now });
38303
+ }
38304
+ return wakeSubscriber;
38305
+ };
38306
+ const applyWakeHint = (hint) => {
38307
+ if (hint === void 0) return;
38308
+ try {
38309
+ ensureWake().setTopic(hint.topic);
38310
+ } catch {
38311
+ }
38312
+ };
38313
+ const emitWake = () => {
38314
+ if (wakeSubscriber === null) return;
38315
+ options.onEvent?.({
38316
+ type: "wake",
38317
+ wake: wakeSubscriber.snapshot(now()),
38318
+ ts: eventTime(now)
38319
+ });
38320
+ };
38321
+ const waitCapMs = () => {
38322
+ if (wakeSubscriber !== null && wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
38323
+ return LISTENER_RECONCILE_POLL_MS;
38324
+ }
38325
+ return nextIdlePollMs(pollMs, emptyIdleStreak, LISTENER_IDLE_POLL_MAX_MS);
38326
+ };
37904
38327
  const sendPreparedAck = async (active) => {
37905
38328
  if (active.phase !== "ack_pending" || active.signalId === null || active.leaseId === null || active.leasedUntil === null || active.ack === null) {
37906
38329
  return {
@@ -37967,8 +38390,33 @@ async function runListenerRuntime(options) {
37967
38390
  stop = { reason: "cancelled" };
37968
38391
  break;
37969
38392
  }
37970
- let page;
37971
- try {
38393
+ let skipRead = false;
38394
+ if (ready && deliveryMode === "durable_claim" && wakeSubscriber !== null && wakeSubscriber.hasTopic) {
38395
+ const until = Math.min(reconcileDueAt, now() + waitCapMs());
38396
+ const reason = await wakeSubscriber.next({
38397
+ until,
38398
+ ...abort ? { signal: abort } : {}
38399
+ });
38400
+ emitWake();
38401
+ if (abort?.aborted) {
38402
+ stop = { reason: "cancelled" };
38403
+ break;
38404
+ }
38405
+ if (reason === "wake" && wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
38406
+ const coalesceMs = wakeSubscriber.coalescingRemainingMs(now());
38407
+ if (coalesceMs > 0) await sleep2(coalesceMs, abort);
38408
+ if (abort?.aborted) {
38409
+ stop = { reason: "cancelled" };
38410
+ break;
38411
+ }
38412
+ if (wakeSubscriber.snapshot(now()).mode === LISTENER_WAKE_MODE_PUSH) {
38413
+ skipRead = true;
38414
+ }
38415
+ }
38416
+ }
38417
+ let page = null;
38418
+ if (skipRead) {
38419
+ } else try {
37972
38420
  const token = await options.credentialSession.bearer();
37973
38421
  page = await readPage({
37974
38422
  token,
@@ -37986,6 +38434,8 @@ async function runListenerRuntime(options) {
37986
38434
  }
37987
38435
  });
37988
38436
  requireCapabilities(page);
38437
+ applyWakeHint(page.wake);
38438
+ emitWake();
37989
38439
  if (ready && readEpisodeStartedAtMs !== null) {
37990
38440
  const recoveredAtMs = now();
37991
38441
  options.onEvent?.({
@@ -38113,7 +38563,7 @@ async function runListenerRuntime(options) {
38113
38563
  break;
38114
38564
  }
38115
38565
  }
38116
- if (page.capabilities.deliveryAck && now() < horizon && !preparedNeedsMainRoute) {
38566
+ if ((page?.capabilities.deliveryAck === true || skipRead) && now() < horizon && !preparedNeedsMainRoute) {
38117
38567
  const ackStop = await sendPreparedAck(recovery);
38118
38568
  if (ackStop !== null) {
38119
38569
  stop = ackStop;
@@ -38143,7 +38593,7 @@ async function runListenerRuntime(options) {
38143
38593
  continue;
38144
38594
  }
38145
38595
  if (recovery?.phase === "leased") {
38146
- if (page.capabilities.deliveryAck) {
38596
+ if (page?.capabilities.deliveryAck === true || skipRead) {
38147
38597
  let terminal = null;
38148
38598
  if (recovery.signalId !== null) {
38149
38599
  try {
@@ -38253,6 +38703,10 @@ async function runListenerRuntime(options) {
38253
38703
  stop = { reason: "credential", error: asError2(error) };
38254
38704
  break;
38255
38705
  }
38706
+ if (error instanceof DeliveryHttpError && error.code === "rate_limited") {
38707
+ wakeSubscriber?.markRateLimited(now());
38708
+ emitWake();
38709
+ }
38256
38710
  if (!isRetryableDeliveryError(error)) {
38257
38711
  stop = { reason: "fatal", error: asError2(error) };
38258
38712
  break;
@@ -38271,6 +38725,14 @@ async function runListenerRuntime(options) {
38271
38725
  stop = { reason: "fatal", error: new Error("delivery claim did not settle") };
38272
38726
  break;
38273
38727
  }
38728
+ applyWakeHint(result.wake);
38729
+ if (!skipRead && wakeSubscriber !== null && wakeSubscriber.hasTopic) {
38730
+ wakeSubscriber.noteReconcile(now());
38731
+ reconcileDueAt = now() + LISTENER_RECONCILE_POLL_MS;
38732
+ }
38733
+ if (skipRead) wakeSubscriber?.noteWakeClaim(now());
38734
+ else wakeSubscriber?.noteClaim(now());
38735
+ emitWake();
38274
38736
  const claimed = result.deliveries[0] ?? null;
38275
38737
  options.onEvent?.({
38276
38738
  type: "delivery_claim",
@@ -38301,6 +38763,19 @@ async function runListenerRuntime(options) {
38301
38763
  stop = { reason: "fatal", error: asError2(error) };
38302
38764
  break;
38303
38765
  }
38766
+ if (wakeSubscriber !== null && wakeSubscriber.hasTopic) {
38767
+ const snap = wakeSubscriber.snapshot(now());
38768
+ const intervalMs = snap.mode === LISTENER_WAKE_MODE_PUSH ? LISTENER_RECONCILE_POLL_MS : nextIdlePollMs(pollMs, emptyIdleStreak, LISTENER_IDLE_POLL_MAX_MS);
38769
+ if (snap.mode === LISTENER_WAKE_MODE_PUSH) emptyIdleStreak = 0;
38770
+ else emptyIdleStreak += 1;
38771
+ options.onEvent?.({
38772
+ type: "idle_poll",
38773
+ intervalMs,
38774
+ ts: eventTime(now)
38775
+ });
38776
+ emitWake();
38777
+ continue;
38778
+ }
38304
38779
  await idleSleep(false);
38305
38780
  continue;
38306
38781
  }
@@ -38495,6 +38970,7 @@ async function runListenerRuntime(options) {
38495
38970
  }
38496
38971
  continue;
38497
38972
  }
38973
+ if (page === null) continue;
38498
38974
  for (const signal of page.signals) {
38499
38975
  if (abort?.aborted) {
38500
38976
  stop = { reason: "cancelled" };
@@ -38587,6 +39063,10 @@ async function runListenerRuntime(options) {
38587
39063
  } finally {
38588
39064
  abort?.removeEventListener("abort", onAbort);
38589
39065
  options.model.cancel();
39066
+ try {
39067
+ await wakeSubscriber?.close();
39068
+ } catch {
39069
+ }
38590
39070
  try {
38591
39071
  await options.model.close();
38592
39072
  } catch (error) {
@@ -38604,6 +39084,7 @@ var LISTENER_READ_RETRY_HOUR_CAP = 25;
38604
39084
  var LISTENER_READ_RETRY_MINUTE_CAP = 61;
38605
39085
  var LISTENER_CLAIM_HOUR_CAP = 25;
38606
39086
  var LISTENER_THROUGHPUT_LAPSE_RATIO = 0.5;
39087
+ var LISTENER_MODE_CHANGE_SKIP_MAX = 1;
38607
39088
  var FAILURE_CODES = /* @__PURE__ */ new Set([
38608
39089
  "http_status",
38609
39090
  "no_response",
@@ -38700,9 +39181,20 @@ function recordListenerReadRecovery(health, input) {
38700
39181
  retryHours: trimNewest(retryHours, LISTENER_READ_RETRY_HOUR_CAP)
38701
39182
  };
38702
39183
  }
39184
+ function freezeClosedHourExpectedClaims(rows3, currentHourStart) {
39185
+ return rows3.map((row) => {
39186
+ if (row.hourStart === currentHourStart) return row;
39187
+ if (row.expectedClaims !== void 0) return row;
39188
+ if (row.cadenceMs === void 0 || row.cadenceMs < 1) return row;
39189
+ return { ...row, expectedClaims: HOUR_MS / row.cadenceMs };
39190
+ });
39191
+ }
38703
39192
  function recordListenerClaimCadence(health, cadenceMs, ts) {
38704
39193
  const hourStart = bucketStart(ts, HOUR_MS);
38705
- const claimHours = health.claimHours.map((row) => ({ ...row }));
39194
+ const claimHours = freezeClosedHourExpectedClaims(
39195
+ health.claimHours.map((row) => ({ ...row })),
39196
+ hourStart
39197
+ );
38706
39198
  const hour = claimHours.find((row) => row.hourStart === hourStart);
38707
39199
  if (hour) {
38708
39200
  hour.cadenceMs = hour.cadenceMs === void 0 ? cadenceMs : Math.max(hour.cadenceMs, cadenceMs);
@@ -38715,9 +39207,26 @@ function recordListenerClaimCadence(health, cadenceMs, ts) {
38715
39207
  claimHours: trimNewest(claimHours, LISTENER_CLAIM_HOUR_CAP)
38716
39208
  };
38717
39209
  }
39210
+ function recordListenerWakeModeChange(health, ts) {
39211
+ const hourStart = bucketStart(ts, HOUR_MS);
39212
+ const claimHours = freezeClosedHourExpectedClaims(
39213
+ health.claimHours.map((row) => ({ ...row })),
39214
+ hourStart
39215
+ );
39216
+ const hour = claimHours.find((row) => row.hourStart === hourStart);
39217
+ if (hour) hour.modeChanged = true;
39218
+ else claimHours.push({ hourStart, claims: 0, modeChanged: true });
39219
+ return {
39220
+ ...health,
39221
+ claimHours: trimNewest(claimHours, LISTENER_CLAIM_HOUR_CAP)
39222
+ };
39223
+ }
38718
39224
  function recordListenerClaim(health, ts) {
38719
39225
  const hourStart = bucketStart(ts, HOUR_MS);
38720
- const claimHours = health.claimHours.map((row) => ({ ...row }));
39226
+ const claimHours = freezeClosedHourExpectedClaims(
39227
+ health.claimHours.map((row) => ({ ...row })),
39228
+ hourStart
39229
+ );
38721
39230
  const hour = claimHours.find((row) => row.hourStart === hourStart);
38722
39231
  if (hour) hour.claims += 1;
38723
39232
  else claimHours.push({ hourStart, claims: 1 });
@@ -38774,9 +39283,13 @@ function parseListenerReadHealth(value, rejectUnknownKeys = false) {
38774
39283
  const hour = value2;
38775
39284
  if (!hasExpectedKeys(hour, ["hourStart", "claims"], false) || !validTimestamp(hour.hourStart) || !validCount(hour.claims)) return null;
38776
39285
  if (rejectUnknownKeys && Object.keys(hour).some(
38777
- (key2) => key2 !== "hourStart" && key2 !== "claims" && key2 !== "cadenceMs"
39286
+ (key2) => key2 !== "hourStart" && key2 !== "claims" && key2 !== "cadenceMs" && key2 !== "expectedClaims" && key2 !== "modeChanged"
38778
39287
  )) return null;
38779
39288
  if (hour.cadenceMs !== void 0 && !(typeof hour.cadenceMs === "number" && Number.isSafeInteger(hour.cadenceMs) && hour.cadenceMs >= 1)) return null;
39289
+ if (hour.expectedClaims !== void 0 && !(typeof hour.expectedClaims === "number" && Number.isFinite(hour.expectedClaims) && hour.expectedClaims > 0)) return null;
39290
+ if (hour.modeChanged !== void 0 && typeof hour.modeChanged !== "boolean") {
39291
+ return null;
39292
+ }
38780
39293
  }
38781
39294
  return {
38782
39295
  currentEpisodeStartedAt: row.currentEpisodeStartedAt,
@@ -38798,10 +39311,14 @@ function parseListenerReadHealth(value, rejectUnknownKeys = false) {
38798
39311
  claimCadenceMs: row.claimCadenceMs,
38799
39312
  claimHours: row.claimHours.map((hour) => {
38800
39313
  const cadenceMs = hour.cadenceMs;
39314
+ const expectedClaims = hour.expectedClaims;
39315
+ const modeChanged = hour.modeChanged;
38801
39316
  return {
38802
39317
  hourStart: hour.hourStart,
38803
39318
  claims: hour.claims,
38804
- ...typeof cadenceMs === "number" ? { cadenceMs } : {}
39319
+ ...typeof cadenceMs === "number" ? { cadenceMs } : {},
39320
+ ...typeof expectedClaims === "number" ? { expectedClaims } : {},
39321
+ ...modeChanged === true ? { modeChanged: true } : {}
38805
39322
  };
38806
39323
  })
38807
39324
  };
@@ -38838,14 +39355,18 @@ function summarizeListenerReadHealth(health, readyAt, nowMs) {
38838
39355
  health.claimHours.map((row) => [row.hourStart, row.claims])
38839
39356
  );
38840
39357
  const cadenceByHour = /* @__PURE__ */ new Map();
39358
+ const expectedByHour = /* @__PURE__ */ new Map();
38841
39359
  for (const row of health.claimHours) {
38842
39360
  if (row.cadenceMs !== void 0) cadenceByHour.set(row.hourStart, row.cadenceMs);
39361
+ if (row.expectedClaims !== void 0) {
39362
+ expectedByHour.set(row.hourStart, row.expectedClaims);
39363
+ }
38843
39364
  }
38844
39365
  for (let hour = first; hour < currentHour; hour += HOUR_MS) {
38845
39366
  const hourStart = new Date(hour).toISOString();
38846
39367
  const claims = claimsByHour.get(hourStart) ?? 0;
38847
39368
  const cadenceMs = cadenceByHour.get(hourStart) ?? health.claimCadenceMs;
38848
- const expectedClaims = HOUR_MS / cadenceMs;
39369
+ const expectedClaims = expectedByHour.get(hourStart) ?? HOUR_MS / cadenceMs;
38849
39370
  claimThroughputHours.push({
38850
39371
  hourStart,
38851
39372
  claims,
@@ -38854,9 +39375,25 @@ function summarizeListenerReadHealth(health, readyAt, nowMs) {
38854
39375
  });
38855
39376
  }
38856
39377
  }
38857
- const throughputLapseHours = claimThroughputHours.filter(
38858
- (hour) => hour.ratio < LISTENER_THROUGHPUT_LAPSE_RATIO
38859
- );
39378
+ const consecutiveModeChangedHours = (hourStart) => {
39379
+ let count2 = 0;
39380
+ let t = Date.parse(hourStart);
39381
+ if (!Number.isFinite(t)) return 0;
39382
+ while (true) {
39383
+ const start = new Date(t).toISOString();
39384
+ const row = health.claimHours.find((hour) => hour.hourStart === start);
39385
+ if (row?.modeChanged !== true) break;
39386
+ count2 += 1;
39387
+ t -= HOUR_MS;
39388
+ }
39389
+ return count2;
39390
+ };
39391
+ const throughputLapseHours = claimThroughputHours.filter((hour) => {
39392
+ if (hour.ratio >= LISTENER_THROUGHPUT_LAPSE_RATIO) return false;
39393
+ const consecutive = consecutiveModeChangedHours(hour.hourStart);
39394
+ if (consecutive === 0) return true;
39395
+ return consecutive > LISTENER_MODE_CHANGE_SKIP_MAX;
39396
+ });
38860
39397
  return {
38861
39398
  currentEpisodeDurationMs,
38862
39399
  episodesLast24h,
@@ -38960,7 +39497,8 @@ var STATUS_ALLOWED_KEYS = /* @__PURE__ */ new Set([
38960
39497
  "connectionReuseRatio",
38961
39498
  "activityPublishFailures",
38962
39499
  "activityLastErrorCode",
38963
- "idlePollMs"
39500
+ "idlePollMs",
39501
+ "wake"
38964
39502
  ]);
38965
39503
  var STATUS_ACTIVITY_ERROR_CODES = /* @__PURE__ */ new Set([
38966
39504
  "activity_credential_failed",
@@ -38983,7 +39521,10 @@ var STATUS_SENSITIVE_KEYS = /* @__PURE__ */ new Set([
38983
39521
  "reply",
38984
39522
  "owner",
38985
39523
  "ownerId",
38986
- "owner_id"
39524
+ "owner_id",
39525
+ "topic",
39526
+ "wakeTopic",
39527
+ "wake_topic"
38987
39528
  ]);
38988
39529
  var STATUS_DELIVERY_KEYS = [
38989
39530
  "deliveryMode",
@@ -39020,6 +39561,37 @@ function parseHeldBackDeliveries(value) {
39020
39561
  }
39021
39562
  return parsed;
39022
39563
  }
39564
+ var WAKE_STATUS_KEY_SET = new Set(LISTENER_WAKE_STATUS_KEYS);
39565
+ var WAKE_SENSITIVE_KEY_SET = new Set(LISTENER_WAKE_SENSITIVE_KEYS);
39566
+ function nullableIso(value) {
39567
+ return value === null || typeof value === "string" && Number.isFinite(Date.parse(value));
39568
+ }
39569
+ function parseListenerWake(value, rejectUnknownKeys = false) {
39570
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
39571
+ const row = value;
39572
+ for (const key2 of Object.keys(row)) {
39573
+ if (WAKE_SENSITIVE_KEY_SET.has(key2)) return null;
39574
+ if (rejectUnknownKeys && !WAKE_STATUS_KEY_SET.has(key2)) return null;
39575
+ }
39576
+ for (const key2 of LISTENER_WAKE_STATUS_KEYS) {
39577
+ if (!(key2 in row)) return null;
39578
+ }
39579
+ const mode3 = LISTENER_WAKE_MODES.find((item) => item === row.mode);
39580
+ 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") {
39581
+ return null;
39582
+ }
39583
+ if (mode3 === LISTENER_WAKE_MODE_PUSH && row.subscribedAt === null) return null;
39584
+ return {
39585
+ mode: mode3,
39586
+ subscribedAt: row.subscribedAt,
39587
+ reconnects: row.reconnects,
39588
+ lastWakeAt: row.lastWakeAt,
39589
+ lastReconcileAt: row.lastReconcileAt,
39590
+ errorCode: row.errorCode,
39591
+ topicRotatedAt: row.topicRotatedAt,
39592
+ rateLimited: row.rateLimited
39593
+ };
39594
+ }
39023
39595
  function parseStatus(raw, rejectUnknownKeys = false) {
39024
39596
  let value;
39025
39597
  try {
@@ -39044,7 +39616,8 @@ function parseStatus(raw, rejectUnknownKeys = false) {
39044
39616
  const nullableTimestamp3 = (candidate) => candidate === null || typeof candidate === "string" && Number.isFinite(Date.parse(candidate));
39045
39617
  const readHealth = row.readHealth === void 0 ? void 0 : parseListenerReadHealth(row.readHealth, rejectUnknownKeys);
39046
39618
  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(
39619
+ const wake = row.wake === void 0 ? void 0 : parseListenerWake(row.wake, rejectUnknownKeys);
39620
+ 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
39621
  row.activityLastErrorCode
39049
39622
  )) || !(row.idlePollMs === void 0 || row.idlePollMs === null || typeof row.idlePollMs === "number" && Number.isSafeInteger(row.idlePollMs) && row.idlePollMs >= 0)) {
39050
39623
  throw new Error("stored listener status is malformed");
@@ -39090,7 +39663,8 @@ function parseStatus(raw, rejectUnknownKeys = false) {
39090
39663
  pendingForMainCount: row.pendingForMainCount ?? 0,
39091
39664
  droppedForMainCount: row.droppedForMainCount ?? 0,
39092
39665
  ...readHealth === void 0 ? {} : { readHealth },
39093
- ...row.idlePollMs === void 0 ? {} : { idlePollMs: row.idlePollMs ?? null }
39666
+ ...row.idlePollMs === void 0 ? {} : { idlePollMs: row.idlePollMs ?? null },
39667
+ ...wake === void 0 ? {} : { wake }
39094
39668
  };
39095
39669
  }
39096
39670
  async function writeListenerStatus(paths, status) {
@@ -39161,7 +39735,11 @@ async function appendListenerEvent(paths, event) {
39161
39735
  // How long one delivery held the worker seat, and why it gave it back.
39162
39736
  "held_ms",
39163
39737
  "release_reason",
39164
- "idle_poll_ms"
39738
+ "idle_poll_ms",
39739
+ "wake_mode",
39740
+ "wake_error_code",
39741
+ "wake_reconnects",
39742
+ "rate_limited"
39165
39743
  ]);
39166
39744
  const deliveryModes = /* @__PURE__ */ new Set(["durable_claim", "cursor_fallback"]);
39167
39745
  const routeModes = /* @__PURE__ */ new Set(["worker", "main", "split"]);
@@ -39229,6 +39807,18 @@ async function appendListenerEvent(paths, event) {
39229
39807
  if (key2 === "idle_poll_ms" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
39230
39808
  throw new Error("listener event idle poll interval is not allowed");
39231
39809
  }
39810
+ if (key2 === "wake_mode" && !(typeof value === "string" && LISTENER_WAKE_MODE_SET.has(value))) {
39811
+ throw new Error("listener event wake mode is not allowed");
39812
+ }
39813
+ if (key2 === "wake_error_code" && !(value === null || typeof value === "string" && WAKE_ERROR_CODE_SET.has(value))) {
39814
+ throw new Error("listener event wake error code is not allowed");
39815
+ }
39816
+ if (key2 === "wake_reconnects" && !(typeof value === "number" && Number.isSafeInteger(value) && value >= 0)) {
39817
+ throw new Error("listener event wake reconnect count is not allowed");
39818
+ }
39819
+ if (key2 === "rate_limited" && typeof value !== "boolean") {
39820
+ throw new Error("listener event rate-limited flag is not allowed");
39821
+ }
39232
39822
  if (key2 === "release_reason" && !(typeof value === "string" && LISTENER_DELIVERY_HOLD_RELEASE_REASONS.includes(
39233
39823
  value
39234
39824
  ))) {
@@ -39640,6 +40230,7 @@ async function runListenerSupervisor(options) {
39640
40230
  activityPublishFailures: 0,
39641
40231
  activityLastErrorCode: null,
39642
40232
  idlePollMs: null,
40233
+ wake: emptyListenerWakeStatus(),
39643
40234
  logPath: options.paths.logPath
39644
40235
  };
39645
40236
  let writes = Promise.resolve();
@@ -39712,7 +40303,50 @@ async function runListenerSupervisor(options) {
39712
40303
  const fitted = fitWorkerStderrTailForLog(tail);
39713
40304
  return fitted.length > 0 ? fitted : null;
39714
40305
  };
40306
+ let lastWakePersistMs = 0;
39715
40307
  const onEvent = (event) => {
40308
+ if (event.type === "wake") {
40309
+ const previousWake = status.wake;
40310
+ const previous = previousWake?.mode;
40311
+ let readHealth = status.readHealth ?? emptyListenerReadHealth();
40312
+ if (previous !== void 0 && previous !== event.wake.mode) {
40313
+ readHealth = recordListenerWakeModeChange(readHealth, event.ts);
40314
+ }
40315
+ const cadenceMs = event.wake.mode === LISTENER_WAKE_MODE_PUSH ? LISTENER_RECONCILE_POLL_MS : status.idlePollMs && status.idlePollMs > 0 ? status.idlePollMs : null;
40316
+ if (cadenceMs !== null) {
40317
+ readHealth = recordListenerClaimCadence(
40318
+ readHealth,
40319
+ cadenceMs,
40320
+ event.ts
40321
+ );
40322
+ }
40323
+ status = {
40324
+ ...status,
40325
+ wake: event.wake,
40326
+ readHealth,
40327
+ updatedAt: event.ts
40328
+ };
40329
+ const eventMs = Date.parse(event.ts);
40330
+ const nowMs = Number.isFinite(eventMs) ? eventMs : Date.now();
40331
+ if (listenerWakePersistWorthy(
40332
+ previousWake,
40333
+ event.wake,
40334
+ lastWakePersistMs,
40335
+ nowMs
40336
+ )) {
40337
+ lastWakePersistMs = nowMs;
40338
+ persist();
40339
+ log({
40340
+ ts: event.ts,
40341
+ event: "listener_wake",
40342
+ wake_mode: event.wake.mode,
40343
+ wake_error_code: event.wake.errorCode,
40344
+ wake_reconnects: event.wake.reconnects,
40345
+ rate_limited: event.wake.rateLimited
40346
+ });
40347
+ }
40348
+ return;
40349
+ }
39716
40350
  if (event.type === "idle_poll") {
39717
40351
  status = {
39718
40352
  ...status,
@@ -43028,8 +43662,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
43028
43662
  ]);
43029
43663
  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
43664
  function packageVersion() {
43031
- if ("0.1.57".length > 0) {
43032
- return "0.1.57";
43665
+ if ("0.1.58".length > 0) {
43666
+ return "0.1.58";
43033
43667
  }
43034
43668
  try {
43035
43669
  const value = JSON.parse(
@@ -46368,6 +47002,8 @@ function listenerStatusJson(status, permissionMode, evidence = {
46368
47002
  claimCadenceMs: readHealth.claimCadenceMs,
46369
47003
  idlePollMs: status.idlePollMs ?? null,
46370
47004
  idlePollSentence: status.idlePollMs === void 0 || status.idlePollMs === null ? null : idlePollStatusSentence(status.idlePollMs),
47005
+ wake: status.wake ?? emptyListenerWakeStatus(),
47006
+ mode: (status.wake ?? emptyListenerWakeStatus()).mode,
46371
47007
  claimThroughputHours: readSummary.claimThroughputHours,
46372
47008
  listenerLapse: lapseNotices.length > 0,
46373
47009
  listenerLapseCodes: lapseNotices.map((notice) => notice.code),
@@ -46439,7 +47075,12 @@ function renderListenerStatus(status, evidence = {
46439
47075
  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
47076
  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
47077
  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)
47078
+ status.idlePollMs === void 0 || status.idlePollMs === null ? "Current idle poll interval has not been reported yet." : idlePollStatusSentence(status.idlePollMs),
47079
+ listenerWakeStatusSentence(
47080
+ status.wake ?? emptyListenerWakeStatus(),
47081
+ status.idlePollMs && status.idlePollMs > 0 ? status.idlePollMs : IDLE_POLL_DEFAULT_MS,
47082
+ status.wake?.lastWakeAt ? relativeAge(status.wake.lastWakeAt, nowMs) : null
47083
+ )
46443
47084
  ];
46444
47085
  for (const notice of lapseNotices) {
46445
47086
  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.58",
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"