@integrity-labs/agt-cli 0.28.582 → 0.28.583
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-XYWEC7PX.js → chunk-I4ZI4W7S.js} +2 -2
- package/dist/lib/manager-worker.js +2 -2
- package/dist/mcp/slack-channel.js +172 -16
- package/dist/mcp/telegram-channel.js +163 -10
- package/package.json +1 -1
- /package/dist/{chunk-XYWEC7PX.js.map → chunk-I4ZI4W7S.js.map} +0 -0
package/dist/bin/agt.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
success,
|
|
41
41
|
table,
|
|
42
42
|
warn
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-I4ZI4W7S.js";
|
|
44
44
|
import {
|
|
45
45
|
AnchorSessionClient,
|
|
46
46
|
CHANNEL_REGISTRY,
|
|
@@ -4908,7 +4908,7 @@ import { execFileSync, execSync } from "child_process";
|
|
|
4908
4908
|
import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
|
|
4909
4909
|
import chalk18 from "chalk";
|
|
4910
4910
|
import ora16 from "ora";
|
|
4911
|
-
var cliVersion = true ? "0.28.
|
|
4911
|
+
var cliVersion = true ? "0.28.583" : "dev";
|
|
4912
4912
|
async function fetchLatestVersion() {
|
|
4913
4913
|
const host2 = getHost();
|
|
4914
4914
|
if (!host2) return null;
|
|
@@ -6086,7 +6086,7 @@ function handleError(err) {
|
|
|
6086
6086
|
}
|
|
6087
6087
|
|
|
6088
6088
|
// src/bin/agt.ts
|
|
6089
|
-
var cliVersion2 = true ? "0.28.
|
|
6089
|
+
var cliVersion2 = true ? "0.28.583" : "dev";
|
|
6090
6090
|
var program = new Command();
|
|
6091
6091
|
program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
|
|
6092
6092
|
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
@@ -5971,7 +5971,7 @@ function exchangeFailureKind(err) {
|
|
|
5971
5971
|
}
|
|
5972
5972
|
|
|
5973
5973
|
// src/lib/api-client.ts
|
|
5974
|
-
var agtCliVersion = true ? "0.28.
|
|
5974
|
+
var agtCliVersion = true ? "0.28.583" : "dev";
|
|
5975
5975
|
var lastConfigHash = null;
|
|
5976
5976
|
function setConfigHash(hash) {
|
|
5977
5977
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -9347,4 +9347,4 @@ export {
|
|
|
9347
9347
|
managerInstallSystemUnitCommand,
|
|
9348
9348
|
managerUninstallSystemUnitCommand
|
|
9349
9349
|
};
|
|
9350
|
-
//# sourceMappingURL=chunk-
|
|
9350
|
+
//# sourceMappingURL=chunk-I4ZI4W7S.js.map
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
safeWriteJsonAtomic,
|
|
54
54
|
setConfigHash,
|
|
55
55
|
tripClass
|
|
56
|
-
} from "../chunk-
|
|
56
|
+
} from "../chunk-I4ZI4W7S.js";
|
|
57
57
|
import {
|
|
58
58
|
getProjectDir as getProjectDir2,
|
|
59
59
|
getReadyTasks,
|
|
@@ -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.
|
|
11627
|
+
var agtCliVersion = true ? "0.28.583" : "dev";
|
|
11628
11628
|
function resolveBrewPath(execFileSync2) {
|
|
11629
11629
|
try {
|
|
11630
11630
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -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
|
|
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
|
-
|
|
41491
|
-
|
|
41492
|
-
|
|
41493
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
39963
|
-
|
|
39964
|
-
|
|
39965
|
-
|
|
39966
|
-
|
|
39967
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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:
|
package/package.json
CHANGED
|
File without changes
|