@integrity-labs/agt-cli 0.28.582 → 0.28.584

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-MHBLUAOV.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-R7ACVUYP.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-MKWK27KO.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-WLMXUNII.js.map
@@ -53,7 +53,7 @@ import {
53
53
  safeWriteJsonAtomic,
54
54
  setConfigHash,
55
55
  tripClass
56
- } from "../chunk-XYWEC7PX.js";
56
+ } from "../chunk-XHC5QJCK.js";
57
57
  import {
58
58
  getProjectDir as getProjectDir2,
59
59
  getReadyTasks,
@@ -183,7 +183,7 @@ import {
183
183
  toOpencodeModel,
184
184
  transcriptActivityAgeSeconds,
185
185
  writeEgressAllowlist
186
- } from "../chunk-RDBPQZVG.js";
186
+ } from "../chunk-XRG4XYAV.js";
187
187
  import {
188
188
  reapOrphanChannelMcps
189
189
  } from "../chunk-XWVM4KPK.js";
@@ -11624,7 +11624,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
11624
11624
  var lastVersionCheckAt = 0;
11625
11625
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
11626
11626
  var lastResponsivenessProbeAt = 0;
11627
- var agtCliVersion = true ? "0.28.582" : "dev";
11627
+ var agtCliVersion = true ? "0.28.584" : "dev";
11628
11628
  function resolveBrewPath(execFileSync2) {
11629
11629
  try {
11630
11630
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -12931,7 +12931,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
12931
12931
  if (codeNames.length === 0) return;
12932
12932
  void (async () => {
12933
12933
  try {
12934
- const { collectDiagnostics } = await import("../persistent-session-MHBLUAOV.js");
12934
+ const { collectDiagnostics } = await import("../persistent-session-R7ACVUYP.js");
12935
12935
  await api.post("/host/heartbeat", {
12936
12936
  host_id: hostId,
12937
12937
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
@@ -13039,7 +13039,7 @@ async function pollCycle() {
13039
13039
  }
13040
13040
  try {
13041
13041
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
13042
- const { collectDiagnostics } = await import("../persistent-session-MHBLUAOV.js");
13042
+ const { collectDiagnostics } = await import("../persistent-session-R7ACVUYP.js");
13043
13043
  const diagCodeNames = [...agentState.persistentSessionAgents];
13044
13044
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics) : void 0;
13045
13045
  let tailscaleHostname;
@@ -13162,7 +13162,7 @@ async function pollCycle() {
13162
13162
  collectPanelessActivityProbes,
13163
13163
  getResponsivenessIntervalMs,
13164
13164
  occupancyQualificationClassifications
13165
- } = await import("../responsiveness-probe-XGMKVWSH.js");
13165
+ } = await import("../responsiveness-probe-RDBVTQD7.js");
13166
13166
  const probeIntervalMs = getResponsivenessIntervalMs();
13167
13167
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
13168
13168
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -13253,7 +13253,7 @@ async function pollCycle() {
13253
13253
  collectResponsivenessProbes,
13254
13254
  livePendingInboundOldestAgeSeconds,
13255
13255
  parkPendingInbound
13256
- } = await import("../responsiveness-probe-XGMKVWSH.js");
13256
+ } = await import("../responsiveness-probe-RDBVTQD7.js");
13257
13257
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
13258
13258
  const wedgeNow = /* @__PURE__ */ new Date();
13259
13259
  const liveAgents = agentState.persistentSessionAgents;
@@ -16823,7 +16823,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
16823
16823
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
16824
16824
  void (async () => {
16825
16825
  try {
16826
- const { collectDiagnostics } = await import("../persistent-session-MHBLUAOV.js");
16826
+ const { collectDiagnostics } = await import("../persistent-session-R7ACVUYP.js");
16827
16827
  await api.post("/host/heartbeat", {
16828
16828
  host_id: hostId,
16829
16829
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
@@ -16874,7 +16874,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
16874
16874
  }
16875
16875
  try {
16876
16876
  const hostId = await getHostId();
16877
- const { collectDiagnostics } = await import("../persistent-session-MHBLUAOV.js");
16877
+ const { collectDiagnostics } = await import("../persistent-session-R7ACVUYP.js");
16878
16878
  await api.post("/host/heartbeat", {
16879
16879
  host_id: hostId,
16880
16880
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics)
@@ -17452,7 +17452,7 @@ async function processClaudePairSessions(agents) {
17452
17452
  killPairSession,
17453
17453
  pairTmuxSession,
17454
17454
  finalizeClaudePairOnboarding
17455
- } = await import("../claude-pair-runtime-MKWK27KO.js");
17455
+ } = await import("../claude-pair-runtime-WLMXUNII.js");
17456
17456
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
17457
17457
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
17458
17458
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -36636,6 +36636,145 @@ function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDEL
36636
36636
  function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
36637
36637
  return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
36638
36638
  }
36639
+ var UNDELIVERABLE_NOTICE_DEBOUNCE_MS = 3e4;
36640
+ function undeliverableNoticeDebounceMs() {
36641
+ const raw = (process.env.AGT_CHANNEL_UNDELIVERABLE_NOTICE_DEBOUNCE_MS ?? "").trim();
36642
+ if (!/^\d+$/.test(raw)) return UNDELIVERABLE_NOTICE_DEBOUNCE_MS;
36643
+ const parsed = Number(raw);
36644
+ return Number.isSafeInteger(parsed) ? parsed : UNDELIVERABLE_NOTICE_DEBOUNCE_MS;
36645
+ }
36646
+ function undeliverableNoticeDelayMs(cause, debounceMs = undeliverableNoticeDebounceMs()) {
36647
+ if (isConfirmedUndeliverableCause(cause)) return 0;
36648
+ return debounceMs;
36649
+ }
36650
+ function isConfirmedUndeliverableCause(cause) {
36651
+ return cause === "integration_down" || cause === "session_dead";
36652
+ }
36653
+ function decideUndeliverableNoticeDispatch(i) {
36654
+ if (i.confirmed && i.hasPendingNotice) return "send-now";
36655
+ if (!shouldPostUndeliverableNotice(i.lastNoticeAtMs, i.nowMs, i.throttleMs)) return "suppress";
36656
+ if (i.confirmed) return "send-now";
36657
+ if (i.hasPendingNotice) return "suppress";
36658
+ return i.delayMs > 0 ? "schedule" : "send-now";
36659
+ }
36660
+ var UndeliverableNoticeScheduler = class {
36661
+ constructor(throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
36662
+ this.throttleMs = throttleMs;
36663
+ }
36664
+ /** decision-time stamps, keyed by conversation. Shared with other one-per-window system notices. */
36665
+ stamps = /* @__PURE__ */ new Map();
36666
+ /** scheduled-but-unsent notices, keyed by conversation. */
36667
+ timers = /* @__PURE__ */ new Map();
36668
+ /** is a notice scheduled but not yet sent for this conversation? */
36669
+ hasPending(key2) {
36670
+ return this.timers.has(key2);
36671
+ }
36672
+ /** when this conversation was last stamped, or undefined if never. */
36673
+ stampedAt(key2) {
36674
+ return this.stamps.get(key2);
36675
+ }
36676
+ /**
36677
+ * ENG-6025: claim this conversation's one-notice-per-window slot for a
36678
+ * DIFFERENT system notice (the watchdog give-up line). Returns false when the
36679
+ * window is still closed. Sharing the stamp is the point: the user gets one
36680
+ * system message per conversation per window, whichever fires first.
36681
+ */
36682
+ claimThrottleSlot(key2, nowMs = Date.now()) {
36683
+ if (!shouldPostUndeliverableNotice(this.stamps.get(key2), nowMs, this.throttleMs)) return false;
36684
+ this.stamps.set(key2, nowMs);
36685
+ return true;
36686
+ }
36687
+ /**
36688
+ * Run the dispatch decision and carry it out. `send` is invoked synchronously
36689
+ * for 'send-now' and after `delayMs` for 'schedule'; it is never invoked for
36690
+ * 'suppress'. Returns the decision so callers can log it.
36691
+ */
36692
+ dispatch(key2, i) {
36693
+ const nowMs = i.nowMs ?? Date.now();
36694
+ const decision = decideUndeliverableNoticeDispatch({
36695
+ confirmed: i.confirmed,
36696
+ delayMs: i.delayMs,
36697
+ hasPendingNotice: this.timers.has(key2),
36698
+ lastNoticeAtMs: this.stamps.get(key2),
36699
+ nowMs,
36700
+ throttleMs: this.throttleMs
36701
+ });
36702
+ if (decision === "suppress") return "suppress";
36703
+ if (decision === "send-now") {
36704
+ this.clearTimer(key2);
36705
+ this.stamps.set(key2, nowMs);
36706
+ this.safeSend(i.send);
36707
+ return "send-now";
36708
+ }
36709
+ this.stamps.set(key2, nowMs);
36710
+ const timer = setTimeout(() => {
36711
+ this.timers.delete(key2);
36712
+ this.safeSend(i.send);
36713
+ }, i.delayMs);
36714
+ timer.unref?.();
36715
+ this.timers.set(key2, timer);
36716
+ return "schedule";
36717
+ }
36718
+ /**
36719
+ * The agent engaged: drop a scheduled-but-unsent notice, and its stamp with
36720
+ * it. Returns whether anything was actually cancelled.
36721
+ *
36722
+ * Cancelling something that was never sent cannot be wrong, so this needs no
36723
+ * "was it undeliverable?" gate - which is what Slack's version had, and why
36724
+ * it missed the case where the marker had never been flagged.
36725
+ */
36726
+ cancel(key2) {
36727
+ if (!this.timers.has(key2)) return false;
36728
+ this.clearTimer(key2);
36729
+ this.stamps.delete(key2);
36730
+ return true;
36731
+ }
36732
+ /**
36733
+ * Forget this conversation entirely - pending timer AND stamp - after a
36734
+ * POSTED notice has been retracted (ENG-7705). Distinct from cancel(): that
36735
+ * one is a no-op when nothing is pending, this one always clears the stamp so
36736
+ * a later busy period earns a fresh notice instead of staying muted.
36737
+ */
36738
+ forget(key2) {
36739
+ this.clearTimer(key2);
36740
+ this.stamps.delete(key2);
36741
+ }
36742
+ /** Test seam / shutdown: drop all timers and stamps. */
36743
+ reset() {
36744
+ for (const timer of this.timers.values()) clearTimeout(timer);
36745
+ this.timers.clear();
36746
+ this.stamps.clear();
36747
+ }
36748
+ clearTimer(key2) {
36749
+ const timer = this.timers.get(key2);
36750
+ if (timer) clearTimeout(timer);
36751
+ this.timers.delete(key2);
36752
+ }
36753
+ /**
36754
+ * ENG-6247 round 3 (CodeRabbit #4446): the notice is fire-and-forget by design
36755
+ * everywhere else - "a failed notice never blocks inbound processing" - and
36756
+ * that has to hold here too.
36757
+ *
36758
+ * The delayed case is the dangerous one: a setTimeout callback runs outside
36759
+ * every request context, so a synchronous throw from `send` is an unhandled
36760
+ * exception and takes the channel server down. Both call sites close over
36761
+ * network-calling code. A notice the user was never promised must not be able
36762
+ * to cost them their agent.
36763
+ *
36764
+ * The immediate case is wrapped for the same reason one step earlier: it runs
36765
+ * inside the inbound handler, so an escaping throw would abort processing of
36766
+ * the very message the notice was about.
36767
+ *
36768
+ * Swallowed rather than logged: this module is pure and has no channel logger,
36769
+ * and both send closures already report their own failures.
36770
+ */
36771
+ safeSend(send) {
36772
+ try {
36773
+ send();
36774
+ } catch {
36775
+ }
36776
+ }
36777
+ };
36639
36778
  function undeliverableNoticeConversationKey(channel, threadTs, isThreadReply) {
36640
36779
  return isThreadReply && threadTs ? `${channel}:${threadTs}` : channel;
36641
36780
  }
@@ -41481,16 +41620,19 @@ function healSlackUndeliverable(channel, messageTs) {
41481
41620
  });
41482
41621
  });
41483
41622
  }
41484
- var lastSlackUndeliverableNoticeAt = /* @__PURE__ */ new Map();
41623
+ var slackUndeliverableNotices = new UndeliverableNoticeScheduler();
41485
41624
  var lastSlackUndeliverableNoticeInfo = /* @__PURE__ */ new Map();
41486
- function postUndeliverableNotice(channel, threadTs, isThreadReply) {
41625
+ function postUndeliverableNotice(channel, threadTs, isThreadReply, cause = "unknown") {
41487
41626
  if (!BOT_TOKEN || !channel) return;
41488
- const now = Date.now();
41489
41627
  const conversationKey = undeliverableNoticeConversationKey(channel, threadTs, isThreadReply);
41490
- if (!shouldPostUndeliverableNotice(lastSlackUndeliverableNoticeAt.get(conversationKey), now)) {
41491
- return;
41492
- }
41493
- lastSlackUndeliverableNoticeAt.set(conversationKey, now);
41628
+ slackUndeliverableNotices.dispatch(conversationKey, {
41629
+ confirmed: isConfirmedUndeliverableCause(cause),
41630
+ delayMs: undeliverableNoticeDelayMs(cause),
41631
+ send: () => sendUndeliverableNotice(channel, threadTs, conversationKey)
41632
+ });
41633
+ }
41634
+ function sendUndeliverableNotice(channel, threadTs, conversationKey) {
41635
+ if (!BOT_TOKEN || !channel) return;
41494
41636
  fetch("https://slack.com/api/chat.postMessage", {
41495
41637
  method: "POST",
41496
41638
  headers: {
@@ -41510,13 +41652,19 @@ function postUndeliverableNotice(channel, threadTs, isThreadReply) {
41510
41652
  });
41511
41653
  }
41512
41654
  function __resetSlackUndeliverableNoticeThrottle() {
41513
- lastSlackUndeliverableNoticeAt.clear();
41655
+ slackUndeliverableNotices.reset();
41514
41656
  lastSlackUndeliverableNoticeInfo.clear();
41515
41657
  }
41658
+ function __slackUndeliverableNoticeScheduled(conversationKey) {
41659
+ return slackUndeliverableNotices.hasPending(conversationKey);
41660
+ }
41661
+ function cancelScheduledSlackUndeliverableNotice(conversationKey) {
41662
+ slackUndeliverableNotices.cancel(conversationKey);
41663
+ }
41516
41664
  function deleteSlackUndeliverableNotice(conversationKey) {
41665
+ slackUndeliverableNotices.forget(conversationKey);
41517
41666
  const info = lastSlackUndeliverableNoticeInfo.get(conversationKey);
41518
41667
  lastSlackUndeliverableNoticeInfo.delete(conversationKey);
41519
- lastSlackUndeliverableNoticeAt.delete(conversationKey);
41520
41668
  if (!BOT_TOKEN || !info) return;
41521
41669
  fetch("https://slack.com/api/chat.delete", {
41522
41670
  method: "POST",
@@ -41604,6 +41752,11 @@ function clearSlackMarkerFileWithHeal(fullPath) {
41604
41752
  marker = JSON.parse(readFileSync18(fullPath, "utf-8"));
41605
41753
  } catch {
41606
41754
  }
41755
+ if (marker?.channel) {
41756
+ cancelScheduledSlackUndeliverableNotice(
41757
+ undeliverableNoticeConversationKeyFromMarker(marker)
41758
+ );
41759
+ }
41607
41760
  if (marker && decideRecoveryHeal({
41608
41761
  wasUndeliverable: marker.undeliverable === true,
41609
41762
  hasTarget: Boolean(marker.channel && marker.message_ts)
@@ -41632,6 +41785,11 @@ function healThenEngageSlackMarker(fullPath, opts) {
41632
41785
  } catch {
41633
41786
  return false;
41634
41787
  }
41788
+ if (marker.channel) {
41789
+ cancelScheduledSlackUndeliverableNotice(
41790
+ undeliverableNoticeConversationKeyFromMarker(marker)
41791
+ );
41792
+ }
41635
41793
  if (decideRecoveryHeal({
41636
41794
  wasUndeliverable: marker.undeliverable === true,
41637
41795
  hasTarget: Boolean(marker.channel && marker.message_ts)
@@ -42415,10 +42573,7 @@ function postSlackWatchdogGiveUpNotice(channel, threadTs, isThreadReply, reason)
42415
42573
  if (!BOT_TOKEN || !channel) return;
42416
42574
  const now = Date.now();
42417
42575
  const conversationKey = isThreadReply ? `${channel}:${threadTs}` : channel;
42418
- if (!shouldPostUndeliverableNotice(lastSlackUndeliverableNoticeAt.get(conversationKey), now)) {
42419
- return;
42420
- }
42421
- lastSlackUndeliverableNoticeAt.set(conversationKey, now);
42576
+ if (!slackUndeliverableNotices.claimThrottleSlot(conversationKey, now)) return;
42422
42577
  process.stderr.write(
42423
42578
  `slack-channel(${AGENT_CODE_NAME}): give-up notice posted channel=${redactSlackId(channel)}
42424
42579
  `
@@ -45988,8 +46143,8 @@ async function connectSocketMode() {
45988
46143
  };
45989
46144
  const ackDecision = decideAckReaction(ackInputs);
45990
46145
  const arrivedWhileBusy = hasInFlightInbound(SLACK_PENDING_INBOUND_DIR);
46146
+ const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
45991
46147
  if (ackDecision === "undeliverable") {
45992
- const cause = classifyUndeliverableCause(ackInputs) ?? "unknown";
45993
46148
  recordChannelDeflection(SLACK_AGENT_DIR, "slack", cause);
45994
46149
  process.stderr.write(
45995
46150
  `slack-channel(${AGENT_CODE_NAME}): [channel-deflection] cause=${cause} channel=slack ts=${redactSlackId(ts)}
@@ -46008,7 +46163,7 @@ async function connectSocketMode() {
46008
46163
  }).catch(() => {
46009
46164
  });
46010
46165
  if (ackDecision === "undeliverable" && channel && undeliverableNoticeEligible(shouldEngage)) {
46011
- postUndeliverableNotice(channel, threadTs, isThreadReply);
46166
+ postUndeliverableNotice(channel, threadTs, isThreadReply, cause);
46012
46167
  }
46013
46168
  }
46014
46169
  if (channel && ts && armMarker) {
@@ -46300,7 +46455,8 @@ connectSocketModeSafely();
46300
46455
  export {
46301
46456
  __resetSlackBusyAckNoticeThrottle,
46302
46457
  __resetSlackGiveUpNoticeStateForTests,
46303
- __resetSlackUndeliverableNoticeThrottle
46458
+ __resetSlackUndeliverableNoticeThrottle,
46459
+ __slackUndeliverableNoticeScheduled
46304
46460
  };
46305
46461
  /*! Bundled license information:
46306
46462
 
@@ -39247,6 +39247,145 @@ function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDEL
39247
39247
  function undeliverableNoticeText(replayEnabled = channelReplayEnabled()) {
39248
39248
  return replayEnabled ? "\u23F3 I can't get to this right now \u2014 no need to resend; I'll pick it up automatically as soon as I'm free." : "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
39249
39249
  }
39250
+ var UNDELIVERABLE_NOTICE_DEBOUNCE_MS = 3e4;
39251
+ function undeliverableNoticeDebounceMs() {
39252
+ const raw = (process.env.AGT_CHANNEL_UNDELIVERABLE_NOTICE_DEBOUNCE_MS ?? "").trim();
39253
+ if (!/^\d+$/.test(raw)) return UNDELIVERABLE_NOTICE_DEBOUNCE_MS;
39254
+ const parsed = Number(raw);
39255
+ return Number.isSafeInteger(parsed) ? parsed : UNDELIVERABLE_NOTICE_DEBOUNCE_MS;
39256
+ }
39257
+ function undeliverableNoticeDelayMs(cause, debounceMs = undeliverableNoticeDebounceMs()) {
39258
+ if (isConfirmedUndeliverableCause(cause)) return 0;
39259
+ return debounceMs;
39260
+ }
39261
+ function isConfirmedUndeliverableCause(cause) {
39262
+ return cause === "integration_down" || cause === "session_dead";
39263
+ }
39264
+ function decideUndeliverableNoticeDispatch(i) {
39265
+ if (i.confirmed && i.hasPendingNotice) return "send-now";
39266
+ if (!shouldPostUndeliverableNotice(i.lastNoticeAtMs, i.nowMs, i.throttleMs)) return "suppress";
39267
+ if (i.confirmed) return "send-now";
39268
+ if (i.hasPendingNotice) return "suppress";
39269
+ return i.delayMs > 0 ? "schedule" : "send-now";
39270
+ }
39271
+ var UndeliverableNoticeScheduler = class {
39272
+ constructor(throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
39273
+ this.throttleMs = throttleMs;
39274
+ }
39275
+ /** decision-time stamps, keyed by conversation. Shared with other one-per-window system notices. */
39276
+ stamps = /* @__PURE__ */ new Map();
39277
+ /** scheduled-but-unsent notices, keyed by conversation. */
39278
+ timers = /* @__PURE__ */ new Map();
39279
+ /** is a notice scheduled but not yet sent for this conversation? */
39280
+ hasPending(key2) {
39281
+ return this.timers.has(key2);
39282
+ }
39283
+ /** when this conversation was last stamped, or undefined if never. */
39284
+ stampedAt(key2) {
39285
+ return this.stamps.get(key2);
39286
+ }
39287
+ /**
39288
+ * ENG-6025: claim this conversation's one-notice-per-window slot for a
39289
+ * DIFFERENT system notice (the watchdog give-up line). Returns false when the
39290
+ * window is still closed. Sharing the stamp is the point: the user gets one
39291
+ * system message per conversation per window, whichever fires first.
39292
+ */
39293
+ claimThrottleSlot(key2, nowMs = Date.now()) {
39294
+ if (!shouldPostUndeliverableNotice(this.stamps.get(key2), nowMs, this.throttleMs)) return false;
39295
+ this.stamps.set(key2, nowMs);
39296
+ return true;
39297
+ }
39298
+ /**
39299
+ * Run the dispatch decision and carry it out. `send` is invoked synchronously
39300
+ * for 'send-now' and after `delayMs` for 'schedule'; it is never invoked for
39301
+ * 'suppress'. Returns the decision so callers can log it.
39302
+ */
39303
+ dispatch(key2, i) {
39304
+ const nowMs = i.nowMs ?? Date.now();
39305
+ const decision = decideUndeliverableNoticeDispatch({
39306
+ confirmed: i.confirmed,
39307
+ delayMs: i.delayMs,
39308
+ hasPendingNotice: this.timers.has(key2),
39309
+ lastNoticeAtMs: this.stamps.get(key2),
39310
+ nowMs,
39311
+ throttleMs: this.throttleMs
39312
+ });
39313
+ if (decision === "suppress") return "suppress";
39314
+ if (decision === "send-now") {
39315
+ this.clearTimer(key2);
39316
+ this.stamps.set(key2, nowMs);
39317
+ this.safeSend(i.send);
39318
+ return "send-now";
39319
+ }
39320
+ this.stamps.set(key2, nowMs);
39321
+ const timer = setTimeout(() => {
39322
+ this.timers.delete(key2);
39323
+ this.safeSend(i.send);
39324
+ }, i.delayMs);
39325
+ timer.unref?.();
39326
+ this.timers.set(key2, timer);
39327
+ return "schedule";
39328
+ }
39329
+ /**
39330
+ * The agent engaged: drop a scheduled-but-unsent notice, and its stamp with
39331
+ * it. Returns whether anything was actually cancelled.
39332
+ *
39333
+ * Cancelling something that was never sent cannot be wrong, so this needs no
39334
+ * "was it undeliverable?" gate - which is what Slack's version had, and why
39335
+ * it missed the case where the marker had never been flagged.
39336
+ */
39337
+ cancel(key2) {
39338
+ if (!this.timers.has(key2)) return false;
39339
+ this.clearTimer(key2);
39340
+ this.stamps.delete(key2);
39341
+ return true;
39342
+ }
39343
+ /**
39344
+ * Forget this conversation entirely - pending timer AND stamp - after a
39345
+ * POSTED notice has been retracted (ENG-7705). Distinct from cancel(): that
39346
+ * one is a no-op when nothing is pending, this one always clears the stamp so
39347
+ * a later busy period earns a fresh notice instead of staying muted.
39348
+ */
39349
+ forget(key2) {
39350
+ this.clearTimer(key2);
39351
+ this.stamps.delete(key2);
39352
+ }
39353
+ /** Test seam / shutdown: drop all timers and stamps. */
39354
+ reset() {
39355
+ for (const timer of this.timers.values()) clearTimeout(timer);
39356
+ this.timers.clear();
39357
+ this.stamps.clear();
39358
+ }
39359
+ clearTimer(key2) {
39360
+ const timer = this.timers.get(key2);
39361
+ if (timer) clearTimeout(timer);
39362
+ this.timers.delete(key2);
39363
+ }
39364
+ /**
39365
+ * ENG-6247 round 3 (CodeRabbit #4446): the notice is fire-and-forget by design
39366
+ * everywhere else - "a failed notice never blocks inbound processing" - and
39367
+ * that has to hold here too.
39368
+ *
39369
+ * The delayed case is the dangerous one: a setTimeout callback runs outside
39370
+ * every request context, so a synchronous throw from `send` is an unhandled
39371
+ * exception and takes the channel server down. Both call sites close over
39372
+ * network-calling code. A notice the user was never promised must not be able
39373
+ * to cost them their agent.
39374
+ *
39375
+ * The immediate case is wrapped for the same reason one step earlier: it runs
39376
+ * inside the inbound handler, so an escaping throw would abort processing of
39377
+ * the very message the notice was about.
39378
+ *
39379
+ * Swallowed rather than logged: this module is pure and has no channel logger,
39380
+ * and both send closures already report their own failures.
39381
+ */
39382
+ safeSend(send) {
39383
+ try {
39384
+ send();
39385
+ } catch {
39386
+ }
39387
+ }
39388
+ };
39250
39389
  var BUSY_ACK_THRESHOLD_MS = 12e4;
39251
39390
  var BUSY_ACK_NOTICE_THROTTLE_MS = 20 * 60 * 1e3;
39252
39391
  function channelBusyAckEnabled() {
@@ -39959,12 +40098,18 @@ async function setMessageReaction(chatId, messageId, emoji2) {
39959
40098
  );
39960
40099
  }
39961
40100
  }
39962
- var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
39963
- async function notifyUndeliverable(chatId, messageId) {
39964
- const key2 = String(chatId);
39965
- const now = Date.now();
39966
- if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(key2), now)) return;
39967
- lastUndeliverableNoticeAt.set(key2, now);
40101
+ var telegramUndeliverableNotices = new UndeliverableNoticeScheduler();
40102
+ function cancelScheduledUndeliverableNotice(chatId) {
40103
+ telegramUndeliverableNotices.cancel(String(chatId));
40104
+ }
40105
+ function notifyUndeliverable(chatId, messageId, cause = "unknown") {
40106
+ telegramUndeliverableNotices.dispatch(String(chatId), {
40107
+ confirmed: isConfirmedUndeliverableCause(cause),
40108
+ delayMs: undeliverableNoticeDelayMs(cause),
40109
+ send: () => void sendUndeliverableNotice(chatId, messageId)
40110
+ });
40111
+ }
40112
+ async function sendUndeliverableNotice(chatId, messageId) {
39968
40113
  try {
39969
40114
  const resp = await telegramApiCall(
39970
40115
  "sendMessage",
@@ -39991,7 +40136,10 @@ async function notifyUndeliverable(chatId, messageId) {
39991
40136
  }
39992
40137
  }
39993
40138
  function __resetUndeliverableNoticeThrottle() {
39994
- lastUndeliverableNoticeAt.clear();
40139
+ telegramUndeliverableNotices.reset();
40140
+ }
40141
+ function __telegramUndeliverableNoticeScheduled(chatId) {
40142
+ return telegramUndeliverableNotices.hasPending(String(chatId));
39995
40143
  }
39996
40144
  var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
39997
40145
  function notifyBackOnline(chatId) {
@@ -41005,6 +41153,9 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
41005
41153
  marker = JSON.parse(readFileSync14(fullPath, "utf-8"));
41006
41154
  } catch {
41007
41155
  }
41156
+ if (marker?.chat_id) {
41157
+ cancelScheduledUndeliverableNotice(marker.chat_id);
41158
+ }
41008
41159
  if (marker && decideRecoveryHeal({
41009
41160
  wasUndeliverable: marker.undeliverable === true,
41010
41161
  hasTarget: Boolean(marker.chat_id)
@@ -41035,6 +41186,7 @@ function markTelegramMarkerEngagedInPlace(fullPath, opts) {
41035
41186
  (/* @__PURE__ */ new Date()).toISOString()
41036
41187
  );
41037
41188
  if (!decision.stamp) return opts.promise ? decision.promised : false;
41189
+ if (marker.chat_id) cancelScheduledUndeliverableNotice(marker.chat_id);
41038
41190
  Object.assign(marker, decision.stamp);
41039
41191
  if (marker.undeliverable) delete marker.undeliverable;
41040
41192
  rewriteTelegramMarkerInPlace(fullPath, marker);
@@ -41053,6 +41205,7 @@ function markTelegramMarkerSeenWithHeal(fullPath) {
41053
41205
  } catch {
41054
41206
  return;
41055
41207
  }
41208
+ if (marker.chat_id) cancelScheduledUndeliverableNotice(marker.chat_id);
41056
41209
  if (decideRecoveryHeal({
41057
41210
  wasUndeliverable: marker.undeliverable === true,
41058
41211
  hasTarget: Boolean(marker.chat_id)
@@ -41733,8 +41886,7 @@ function armTelegramTurnFailureWatch(args) {
41733
41886
  }
41734
41887
  async function notifyWatchdogGiveUp(chatId, reason) {
41735
41888
  const now = Date.now();
41736
- if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(chatId), now)) return;
41737
- lastUndeliverableNoticeAt.set(chatId, now);
41889
+ if (!telegramUndeliverableNotices.claimThrottleSlot(chatId, now)) return;
41738
41890
  try {
41739
41891
  const resp = await telegramApiCall(
41740
41892
  "sendMessage",
@@ -43277,7 +43429,7 @@ async function pollLoop() {
43277
43429
  if (ackDecision === "ack") {
43278
43430
  void setMessageReaction(chatId, messageId, ACK_EMOJI);
43279
43431
  } else if (ackDecision === "undeliverable") {
43280
- void notifyUndeliverable(chatId, messageId);
43432
+ notifyUndeliverable(chatId, messageId, classifyUndeliverableCause(ackInputs) ?? "unknown");
43281
43433
  }
43282
43434
  const fileMeta = [];
43283
43435
  for (const attachment of classifiedAttachments) {
@@ -43508,6 +43660,7 @@ export {
43508
43660
  __resetBusyAckNoticeThrottle,
43509
43661
  __resetGiveUpNoticeStateForTests,
43510
43662
  __resetUndeliverableNoticeThrottle,
43663
+ __telegramUndeliverableNoticeScheduled,
43511
43664
  _resetQueuedReplyCountsForTests
43512
43665
  };
43513
43666
  /*! Bundled license information:
@@ -43,7 +43,7 @@ import {
43
43
  writeDirectChatSessionState,
44
44
  writeEgressAllowlist,
45
45
  writePersistentClaudeWrapper
46
- } from "./chunk-RDBPQZVG.js";
46
+ } from "./chunk-XRG4XYAV.js";
47
47
  import "./chunk-XWVM4KPK.js";
48
48
  export {
49
49
  EGRESS_BASELINE_DOMAINS,
@@ -91,4 +91,4 @@ export {
91
91
  writeEgressAllowlist,
92
92
  writePersistentClaudeWrapper
93
93
  };
94
- //# sourceMappingURL=persistent-session-MHBLUAOV.js.map
94
+ //# sourceMappingURL=persistent-session-R7ACVUYP.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-RDBPQZVG.js";
3
+ } from "./chunk-XRG4XYAV.js";
4
4
  import "./chunk-XWVM4KPK.js";
5
5
 
6
6
  // src/lib/responsiveness-probe.ts
@@ -689,4 +689,4 @@ export {
689
689
  readAndResetSlackReplyBindingClassifications,
690
690
  readAndResetSlackReplyTargetClassifications
691
691
  };
692
- //# sourceMappingURL=responsiveness-probe-XGMKVWSH.js.map
692
+ //# sourceMappingURL=responsiveness-probe-RDBVTQD7.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.582",
3
+ "version": "0.28.584",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {