@integrity-labs/agt-cli 0.19.18 → 0.19.19
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-WTFROCJ3.js → chunk-WQJL6EGC.js} +73 -13
- package/dist/chunk-WQJL6EGC.js.map +1 -0
- package/dist/lib/manager-worker.js +38 -5
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/telegram-channel.js +429 -11
- package/package.json +1 -1
- package/dist/chunk-WTFROCJ3.js.map +0 -1
package/mcp/telegram-channel.js
CHANGED
|
@@ -14235,13 +14235,21 @@ function classifyPeerMessage(msg, cfg, self) {
|
|
|
14235
14235
|
if (self.bot_id === null || self.bot_username === null) {
|
|
14236
14236
|
return { kind: "drop", reason: "self_resolution_pending" };
|
|
14237
14237
|
}
|
|
14238
|
-
|
|
14239
|
-
|
|
14238
|
+
const addressing = classifyAddressing(msg, self.bot_id, self.bot_username);
|
|
14239
|
+
if (!addressing.addressed) {
|
|
14240
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
14240
14241
|
}
|
|
14241
|
-
|
|
14242
|
+
if (addressing.viaReplyOnly) {
|
|
14243
|
+
const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(chatId);
|
|
14244
|
+
const respondMode = cfg.peer_agent_mode === "respond";
|
|
14245
|
+
if (!(inAllowedGroup && respondMode)) {
|
|
14246
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
14247
|
+
}
|
|
14248
|
+
}
|
|
14249
|
+
return { kind: "peer-ingress", peer };
|
|
14242
14250
|
}
|
|
14243
|
-
function
|
|
14244
|
-
|
|
14251
|
+
function classifyAddressing(msg, ourBotId, ourBotUsername) {
|
|
14252
|
+
let viaMentionOrCommand = false;
|
|
14245
14253
|
const text = msg.text ?? msg.caption ?? "";
|
|
14246
14254
|
const entities = msg.entities ?? msg.caption_entities ?? [];
|
|
14247
14255
|
const usAt = "@" + ourBotUsername;
|
|
@@ -14249,15 +14257,19 @@ function isAddressedToUs(msg, ourBotId, ourBotUsername) {
|
|
|
14249
14257
|
const slice = text.slice(entity.offset, entity.offset + entity.length).toLowerCase();
|
|
14250
14258
|
if (entity.type === "bot_command") {
|
|
14251
14259
|
const at = slice.indexOf("@");
|
|
14252
|
-
if (at < 0) {
|
|
14253
|
-
|
|
14260
|
+
if (at < 0 || slice.slice(at + 1) === ourBotUsername) {
|
|
14261
|
+
viaMentionOrCommand = true;
|
|
14262
|
+
break;
|
|
14254
14263
|
}
|
|
14255
|
-
if (slice.slice(at + 1) === ourBotUsername) return true;
|
|
14256
14264
|
} else if (entity.type === "mention" && slice === usAt) {
|
|
14257
|
-
|
|
14265
|
+
viaMentionOrCommand = true;
|
|
14266
|
+
break;
|
|
14258
14267
|
}
|
|
14259
14268
|
}
|
|
14260
|
-
|
|
14269
|
+
const viaReply = msg.reply_to_message?.from?.id === ourBotId;
|
|
14270
|
+
if (viaMentionOrCommand) return { addressed: true, viaReplyOnly: false };
|
|
14271
|
+
if (viaReply) return { addressed: true, viaReplyOnly: true };
|
|
14272
|
+
return { addressed: false, viaReplyOnly: false };
|
|
14261
14273
|
}
|
|
14262
14274
|
var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
14263
14275
|
function parsePeersEnv(raw) {
|
|
@@ -14287,6 +14299,322 @@ function parsePeerAgentModeEnv(raw) {
|
|
|
14287
14299
|
return "off";
|
|
14288
14300
|
}
|
|
14289
14301
|
|
|
14302
|
+
// src/telegram-peer-rate-limiter.ts
|
|
14303
|
+
var SECOND_MS = 1e3;
|
|
14304
|
+
var MINUTE_MS = 60 * SECOND_MS;
|
|
14305
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
14306
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
14307
|
+
var DEFAULT_RATE_LIMITER_CONFIG = Object.freeze({
|
|
14308
|
+
pairBurstLimit: 1,
|
|
14309
|
+
pairBurstWindowMs: 10 * SECOND_MS,
|
|
14310
|
+
pairWindowLimit: 5,
|
|
14311
|
+
pairWindowMs: 5 * MINUTE_MS,
|
|
14312
|
+
chatLimit: 10,
|
|
14313
|
+
chatWindowMs: 5 * MINUTE_MS,
|
|
14314
|
+
agentDailyLimit: 200,
|
|
14315
|
+
agentDailyWindowMs: DAY_MS,
|
|
14316
|
+
dailyBudgetLimit: 500,
|
|
14317
|
+
dailyBudgetWindowMs: DAY_MS
|
|
14318
|
+
});
|
|
14319
|
+
function readSliding(window, windowMs, now) {
|
|
14320
|
+
if (!window) return 0;
|
|
14321
|
+
const cutoff = now - windowMs;
|
|
14322
|
+
let i = 0;
|
|
14323
|
+
while (i < window.timestamps.length && window.timestamps[i] <= cutoff) i++;
|
|
14324
|
+
if (i > 0) window.timestamps.splice(0, i);
|
|
14325
|
+
return window.timestamps.length;
|
|
14326
|
+
}
|
|
14327
|
+
function bumpSliding(window, now) {
|
|
14328
|
+
window.timestamps.push(now);
|
|
14329
|
+
}
|
|
14330
|
+
function createPeerRateLimiter(config2 = DEFAULT_RATE_LIMITER_CONFIG) {
|
|
14331
|
+
const counters = {
|
|
14332
|
+
pairBurst: /* @__PURE__ */ new Map(),
|
|
14333
|
+
pairWindow: /* @__PURE__ */ new Map(),
|
|
14334
|
+
chat: /* @__PURE__ */ new Map(),
|
|
14335
|
+
agentDaily: { timestamps: [] },
|
|
14336
|
+
dailyBudget: { timestamps: [] }
|
|
14337
|
+
};
|
|
14338
|
+
let humanInbound = 0;
|
|
14339
|
+
function pairKey(chatId, peerBotId) {
|
|
14340
|
+
return `${chatId}:${peerBotId}`;
|
|
14341
|
+
}
|
|
14342
|
+
function readMap(map, key2, windowMs, now) {
|
|
14343
|
+
return readSliding(map.get(key2), windowMs, now);
|
|
14344
|
+
}
|
|
14345
|
+
function bumpMap(map, key2, now) {
|
|
14346
|
+
let entry = map.get(key2);
|
|
14347
|
+
if (!entry) {
|
|
14348
|
+
entry = { timestamps: [] };
|
|
14349
|
+
map.set(key2, entry);
|
|
14350
|
+
}
|
|
14351
|
+
bumpSliding(entry, now);
|
|
14352
|
+
}
|
|
14353
|
+
return {
|
|
14354
|
+
recordInboundPeer(chatId, peerBotId, now) {
|
|
14355
|
+
const pk = pairKey(chatId, peerBotId);
|
|
14356
|
+
if (readMap(counters.pairBurst, pk, config2.pairBurstWindowMs, now) >= config2.pairBurstLimit) {
|
|
14357
|
+
return { decision: "rate_limit_pair", dailyBudgetWarn: false };
|
|
14358
|
+
}
|
|
14359
|
+
if (readMap(counters.pairWindow, pk, config2.pairWindowMs, now) >= config2.pairWindowLimit) {
|
|
14360
|
+
return { decision: "rate_limit_pair", dailyBudgetWarn: false };
|
|
14361
|
+
}
|
|
14362
|
+
if (readMap(counters.chat, chatId, config2.chatWindowMs, now) >= config2.chatLimit) {
|
|
14363
|
+
return { decision: "rate_limit_chat", dailyBudgetWarn: false };
|
|
14364
|
+
}
|
|
14365
|
+
if (readSliding(counters.agentDaily, config2.agentDailyWindowMs, now) >= config2.agentDailyLimit) {
|
|
14366
|
+
return { decision: "rate_limit_agent_daily", dailyBudgetWarn: false };
|
|
14367
|
+
}
|
|
14368
|
+
const budgetSoFar = readSliding(counters.dailyBudget, config2.dailyBudgetWindowMs, now);
|
|
14369
|
+
if (budgetSoFar >= config2.dailyBudgetLimit) {
|
|
14370
|
+
return { decision: "daily_budget_exhausted", dailyBudgetWarn: false };
|
|
14371
|
+
}
|
|
14372
|
+
bumpMap(counters.pairBurst, pk, now);
|
|
14373
|
+
bumpMap(counters.pairWindow, pk, now);
|
|
14374
|
+
bumpMap(counters.chat, chatId, now);
|
|
14375
|
+
bumpSliding(counters.agentDaily, now);
|
|
14376
|
+
bumpSliding(counters.dailyBudget, now);
|
|
14377
|
+
const newBudget = budgetSoFar + 1;
|
|
14378
|
+
const warn80 = newBudget >= Math.ceil(config2.dailyBudgetLimit * 0.8) && newBudget < config2.dailyBudgetLimit;
|
|
14379
|
+
return { decision: "ok", dailyBudgetWarn: warn80 };
|
|
14380
|
+
},
|
|
14381
|
+
recordInboundHuman(_chatId, _now) {
|
|
14382
|
+
humanInbound += 1;
|
|
14383
|
+
},
|
|
14384
|
+
snapshot(now) {
|
|
14385
|
+
const perChat = /* @__PURE__ */ new Map();
|
|
14386
|
+
for (const [k, v] of counters.chat) {
|
|
14387
|
+
perChat.set(k, readSliding(v, config2.chatWindowMs, now));
|
|
14388
|
+
}
|
|
14389
|
+
const perPair = /* @__PURE__ */ new Map();
|
|
14390
|
+
for (const [k, v] of counters.pairWindow) {
|
|
14391
|
+
perPair.set(k, readSliding(v, config2.pairWindowMs, now));
|
|
14392
|
+
}
|
|
14393
|
+
return {
|
|
14394
|
+
perAgentDaily: readSliding(counters.agentDaily, config2.agentDailyWindowMs, now),
|
|
14395
|
+
dailyBudget: readSliding(counters.dailyBudget, config2.dailyBudgetWindowMs, now),
|
|
14396
|
+
perChat,
|
|
14397
|
+
perPair,
|
|
14398
|
+
humanInbound
|
|
14399
|
+
};
|
|
14400
|
+
}
|
|
14401
|
+
};
|
|
14402
|
+
}
|
|
14403
|
+
function createPresenceTracker() {
|
|
14404
|
+
const lastHumanByChat = /* @__PURE__ */ new Map();
|
|
14405
|
+
return {
|
|
14406
|
+
noteInbound(chatId, kind, now) {
|
|
14407
|
+
if (kind === "human") lastHumanByChat.set(chatId, now);
|
|
14408
|
+
},
|
|
14409
|
+
shouldDropOutgress(chatId, now, maxAgeMs = 30 * MINUTE_MS) {
|
|
14410
|
+
const last = lastHumanByChat.get(chatId);
|
|
14411
|
+
if (last === void 0) return "no_human_recent";
|
|
14412
|
+
if (now - last >= maxAgeMs) return "no_human_recent";
|
|
14413
|
+
return null;
|
|
14414
|
+
}
|
|
14415
|
+
};
|
|
14416
|
+
}
|
|
14417
|
+
|
|
14418
|
+
// src/telegram-peer-rate-limiter-db.ts
|
|
14419
|
+
function createDbBackedPeerRateLimiter(deps) {
|
|
14420
|
+
const config2 = deps.config ?? DEFAULT_RATE_LIMITER_CONFIG;
|
|
14421
|
+
const readCacheMs = deps.readCacheMs ?? 2e3;
|
|
14422
|
+
const log = deps.log ?? (() => {
|
|
14423
|
+
});
|
|
14424
|
+
const caches = /* @__PURE__ */ new Map();
|
|
14425
|
+
let humanInbound = 0;
|
|
14426
|
+
const pendingWrites = /* @__PURE__ */ new Set();
|
|
14427
|
+
function trackPending(write) {
|
|
14428
|
+
pendingWrites.add(write);
|
|
14429
|
+
void write.finally(() => pendingWrites.delete(write));
|
|
14430
|
+
}
|
|
14431
|
+
function cacheKey2(layer, chatId, peerBotId) {
|
|
14432
|
+
return `${layer}|${chatId}|${peerBotId ?? ""}`;
|
|
14433
|
+
}
|
|
14434
|
+
const localBumpTimes = /* @__PURE__ */ new Map();
|
|
14435
|
+
function bumpLocal(layer, chatId, peerBotId, now) {
|
|
14436
|
+
const key2 = cacheKey2(layer, chatId, peerBotId);
|
|
14437
|
+
const list = localBumpTimes.get(key2);
|
|
14438
|
+
if (list) list.push(now);
|
|
14439
|
+
else localBumpTimes.set(key2, [now]);
|
|
14440
|
+
}
|
|
14441
|
+
function localBumpsSince(key2, fetchedAt) {
|
|
14442
|
+
const list = localBumpTimes.get(key2);
|
|
14443
|
+
if (!list) return 0;
|
|
14444
|
+
let n = 0;
|
|
14445
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
14446
|
+
if (list[i] >= fetchedAt) n++;
|
|
14447
|
+
else break;
|
|
14448
|
+
}
|
|
14449
|
+
return n;
|
|
14450
|
+
}
|
|
14451
|
+
function pruneLocalBumps(retentionMs, now) {
|
|
14452
|
+
const cutoff = now - retentionMs;
|
|
14453
|
+
for (const [key2, list] of localBumpTimes) {
|
|
14454
|
+
let i = 0;
|
|
14455
|
+
while (i < list.length && list[i] < cutoff) i++;
|
|
14456
|
+
if (i > 0) list.splice(0, i);
|
|
14457
|
+
if (list.length === 0) localBumpTimes.delete(key2);
|
|
14458
|
+
}
|
|
14459
|
+
}
|
|
14460
|
+
async function readCount(layer, chatId, peerBotId, windowMs, now) {
|
|
14461
|
+
const key2 = cacheKey2(layer, chatId, peerBotId);
|
|
14462
|
+
const cached2 = caches.get(key2);
|
|
14463
|
+
if (cached2 && now - cached2.fetchedAt < readCacheMs) {
|
|
14464
|
+
return cached2.count + localBumpsSince(key2, cached2.fetchedAt);
|
|
14465
|
+
}
|
|
14466
|
+
const since = new Date(now - windowMs).toISOString();
|
|
14467
|
+
let count;
|
|
14468
|
+
try {
|
|
14469
|
+
const args = {
|
|
14470
|
+
since,
|
|
14471
|
+
traffic_class: layer
|
|
14472
|
+
};
|
|
14473
|
+
if (layer === "pair_burst" || layer === "pair_window") {
|
|
14474
|
+
args.chat_id = chatId;
|
|
14475
|
+
args.peer_bot_id = peerBotId ?? void 0;
|
|
14476
|
+
} else if (layer === "chat") {
|
|
14477
|
+
args.chat_id = chatId;
|
|
14478
|
+
}
|
|
14479
|
+
count = await deps.api.countEvents(args);
|
|
14480
|
+
} catch (err) {
|
|
14481
|
+
log(`peer_rate_event read failed (layer=${layer}): ${err.message} \u2014 fail-open`);
|
|
14482
|
+
count = 0;
|
|
14483
|
+
}
|
|
14484
|
+
const fetchedAt = now;
|
|
14485
|
+
caches.set(key2, { fetchedAt, count });
|
|
14486
|
+
return count + localBumpsSince(key2, fetchedAt);
|
|
14487
|
+
}
|
|
14488
|
+
async function decide(chatId, peerBotId, now) {
|
|
14489
|
+
const layers = [
|
|
14490
|
+
{ layer: "pair_burst", chatId, peerBotId, limit: config2.pairBurstLimit, windowMs: config2.pairBurstWindowMs, reason: "rate_limit_pair" },
|
|
14491
|
+
{ layer: "pair_window", chatId, peerBotId, limit: config2.pairWindowLimit, windowMs: config2.pairWindowMs, reason: "rate_limit_pair" },
|
|
14492
|
+
{ layer: "chat", chatId, peerBotId: null, limit: config2.chatLimit, windowMs: config2.chatWindowMs, reason: "rate_limit_chat" },
|
|
14493
|
+
{ layer: "agent_daily", chatId: "", peerBotId: null, limit: config2.agentDailyLimit, windowMs: config2.agentDailyWindowMs, reason: "rate_limit_agent_daily" },
|
|
14494
|
+
{ layer: "daily_budget", chatId: "", peerBotId: null, limit: config2.dailyBudgetLimit, windowMs: config2.dailyBudgetWindowMs, reason: "daily_budget_exhausted" }
|
|
14495
|
+
];
|
|
14496
|
+
for (const l of layers) {
|
|
14497
|
+
const count = await readCount(l.layer, l.chatId, l.peerBotId, l.windowMs, now);
|
|
14498
|
+
if (count >= l.limit) {
|
|
14499
|
+
return { decision: l.reason, dailyBudgetWarn: false };
|
|
14500
|
+
}
|
|
14501
|
+
}
|
|
14502
|
+
for (const l of layers) bumpLocal(l.layer, l.chatId, l.peerBotId, now);
|
|
14503
|
+
pruneLocalBumps(Math.max(config2.pairBurstWindowMs, 6e4), now);
|
|
14504
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: peerBotId, traffic_class: "pair_burst" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14505
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: peerBotId, traffic_class: "pair_window" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14506
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: null, traffic_class: "chat" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14507
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: null, traffic_class: "agent_daily" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14508
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: null, traffic_class: "daily_budget" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14509
|
+
const dbKey = cacheKey2("daily_budget", "", null);
|
|
14510
|
+
const dbCached = caches.get(dbKey);
|
|
14511
|
+
const dbBudget = (dbCached?.count ?? 0) + localBumpsSince(dbKey, dbCached?.fetchedAt ?? 0);
|
|
14512
|
+
const warn80 = dbBudget >= Math.ceil(config2.dailyBudgetLimit * 0.8) && dbBudget < config2.dailyBudgetLimit;
|
|
14513
|
+
return { decision: "ok", dailyBudgetWarn: warn80 };
|
|
14514
|
+
}
|
|
14515
|
+
return {
|
|
14516
|
+
recordInboundPeer(chatId, peerBotId, now) {
|
|
14517
|
+
return decide(chatId, peerBotId, now);
|
|
14518
|
+
},
|
|
14519
|
+
recordInboundHuman(_chatId, _now) {
|
|
14520
|
+
humanInbound += 1;
|
|
14521
|
+
},
|
|
14522
|
+
snapshot(now) {
|
|
14523
|
+
const perChat = /* @__PURE__ */ new Map();
|
|
14524
|
+
const perPair = /* @__PURE__ */ new Map();
|
|
14525
|
+
let perAgentDaily = 0;
|
|
14526
|
+
let dailyBudget = 0;
|
|
14527
|
+
for (const [k, v] of caches) {
|
|
14528
|
+
const local = localBumpsSince(k, v.fetchedAt);
|
|
14529
|
+
const layer = k.split("|")[0];
|
|
14530
|
+
if (layer === "pair_window") {
|
|
14531
|
+
const chatId = k.split("|")[1];
|
|
14532
|
+
const peerBotId = k.split("|")[2];
|
|
14533
|
+
perPair.set(`${chatId}:${peerBotId}`, v.count + local);
|
|
14534
|
+
} else if (layer === "chat") {
|
|
14535
|
+
const chatId = k.split("|")[1];
|
|
14536
|
+
perChat.set(chatId, v.count + local);
|
|
14537
|
+
} else if (layer === "agent_daily") {
|
|
14538
|
+
perAgentDaily = v.count + local;
|
|
14539
|
+
} else if (layer === "daily_budget") {
|
|
14540
|
+
dailyBudget = v.count + local;
|
|
14541
|
+
}
|
|
14542
|
+
}
|
|
14543
|
+
void now;
|
|
14544
|
+
return { perAgentDaily, dailyBudget, perChat, perPair, humanInbound };
|
|
14545
|
+
},
|
|
14546
|
+
async flushPending() {
|
|
14547
|
+
await Promise.all([...pendingWrites]);
|
|
14548
|
+
}
|
|
14549
|
+
};
|
|
14550
|
+
}
|
|
14551
|
+
function createDefaultPeerRateApiClient(args) {
|
|
14552
|
+
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
14553
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
14554
|
+
const base = args.agtHost.replace(/\/+$/, "");
|
|
14555
|
+
let cachedToken = null;
|
|
14556
|
+
let cachedTokenExpiresAt = 0;
|
|
14557
|
+
async function getToken() {
|
|
14558
|
+
if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
|
|
14559
|
+
const resp = await fetchImpl(`${base}/host/exchange`, {
|
|
14560
|
+
method: "POST",
|
|
14561
|
+
headers: { "Content-Type": "application/json" },
|
|
14562
|
+
body: JSON.stringify({ host_key: args.agtApiKey })
|
|
14563
|
+
});
|
|
14564
|
+
if (!resp.ok) {
|
|
14565
|
+
const body = await resp.text().catch(() => "");
|
|
14566
|
+
throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
|
|
14567
|
+
}
|
|
14568
|
+
const data = await resp.json();
|
|
14569
|
+
cachedToken = data.token;
|
|
14570
|
+
cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
14571
|
+
return cachedToken;
|
|
14572
|
+
}
|
|
14573
|
+
async function authedFetch(path, init) {
|
|
14574
|
+
const headers = {
|
|
14575
|
+
Authorization: `Bearer ${await getToken()}`
|
|
14576
|
+
};
|
|
14577
|
+
if (init.body !== void 0) headers["Content-Type"] = "application/json";
|
|
14578
|
+
let resp = await fetchImpl(`${base}${path}`, { method: init.method, headers, body: init.body });
|
|
14579
|
+
if (resp.status === 401) {
|
|
14580
|
+
cachedToken = null;
|
|
14581
|
+
cachedTokenExpiresAt = 0;
|
|
14582
|
+
const retryHeaders = {
|
|
14583
|
+
Authorization: `Bearer ${await getToken()}`
|
|
14584
|
+
};
|
|
14585
|
+
if (init.body !== void 0) retryHeaders["Content-Type"] = "application/json";
|
|
14586
|
+
resp = await fetchImpl(`${base}${path}`, { method: init.method, headers: retryHeaders, body: init.body });
|
|
14587
|
+
}
|
|
14588
|
+
return resp;
|
|
14589
|
+
}
|
|
14590
|
+
return {
|
|
14591
|
+
async recordEvent({ chat_id, peer_bot_id, traffic_class }) {
|
|
14592
|
+
const resp = await authedFetch("/host/peer-rate-events", {
|
|
14593
|
+
method: "POST",
|
|
14594
|
+
body: JSON.stringify({ agent_id: args.agentId, chat_id, peer_bot_id, traffic_class })
|
|
14595
|
+
});
|
|
14596
|
+
if (!resp.ok) {
|
|
14597
|
+
throw new Error(`POST /host/peer-rate-events failed: ${resp.status} ${await resp.text()}`);
|
|
14598
|
+
}
|
|
14599
|
+
},
|
|
14600
|
+
async countEvents({ since, chat_id, peer_bot_id, traffic_class }) {
|
|
14601
|
+
const params = new URLSearchParams({
|
|
14602
|
+
agent_id: args.agentId,
|
|
14603
|
+
since
|
|
14604
|
+
});
|
|
14605
|
+
if (chat_id !== void 0) params.set("chat_id", chat_id);
|
|
14606
|
+
if (peer_bot_id !== void 0) params.set("peer_bot_id", String(peer_bot_id));
|
|
14607
|
+
if (traffic_class !== void 0) params.set("traffic_class", traffic_class);
|
|
14608
|
+
const resp = await authedFetch(`/host/peer-rate-events?${params}`, { method: "GET" });
|
|
14609
|
+
if (!resp.ok) {
|
|
14610
|
+
throw new Error(`GET /host/peer-rate-events failed: ${resp.status} ${await resp.text()}`);
|
|
14611
|
+
}
|
|
14612
|
+
const json = await resp.json();
|
|
14613
|
+
return typeof json.count === "number" ? json.count : 0;
|
|
14614
|
+
}
|
|
14615
|
+
};
|
|
14616
|
+
}
|
|
14617
|
+
|
|
14290
14618
|
// src/telegram-channel.ts
|
|
14291
14619
|
function redactId(id) {
|
|
14292
14620
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
@@ -14303,6 +14631,28 @@ var PEER_CLASSIFIER_CONFIG = {
|
|
|
14303
14631
|
peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
|
|
14304
14632
|
peers: parsePeersEnv(process.env.TELEGRAM_PEERS)
|
|
14305
14633
|
};
|
|
14634
|
+
var PEER_DISABLED_GLOBAL = (() => {
|
|
14635
|
+
const v = (process.env.TELEGRAM_PEER_DISABLED ?? "").toLowerCase();
|
|
14636
|
+
return v === "1" || v === "true" || v === "yes";
|
|
14637
|
+
})();
|
|
14638
|
+
if (PEER_DISABLED_GLOBAL) {
|
|
14639
|
+
process.stderr.write(
|
|
14640
|
+
`telegram-channel(${AGENT_CODE_NAME}): TELEGRAM_PEER_DISABLED=true \u2014 peer ingress + outgress short-circuited
|
|
14641
|
+
`
|
|
14642
|
+
);
|
|
14643
|
+
}
|
|
14644
|
+
var peerRateApiClient = createDefaultPeerRateApiClient({
|
|
14645
|
+
agtHost: AGT_HOST,
|
|
14646
|
+
agtApiKey: AGT_API_KEY,
|
|
14647
|
+
agentId: process.env.AGT_AGENT_ID ?? null
|
|
14648
|
+
});
|
|
14649
|
+
var peerRateLimiter = peerRateApiClient && parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE) !== "off" ? createDbBackedPeerRateLimiter({
|
|
14650
|
+
api: peerRateApiClient,
|
|
14651
|
+
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
14652
|
+
`)
|
|
14653
|
+
}) : createPeerRateLimiter();
|
|
14654
|
+
var peerPresence = createPresenceTracker();
|
|
14655
|
+
var dailyBudgetWarned = false;
|
|
14306
14656
|
if (!BOT_TOKEN) {
|
|
14307
14657
|
process.stderr.write(
|
|
14308
14658
|
"telegram-channel: Missing TELEGRAM_BOT_TOKEN. Cannot start.\n"
|
|
@@ -14939,6 +15289,40 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
14939
15289
|
isError: true
|
|
14940
15290
|
};
|
|
14941
15291
|
}
|
|
15292
|
+
const peerActiveOnChat = PEER_CLASSIFIER_CONFIG.peer_agent_mode !== "off" && PEER_CLASSIFIER_CONFIG.peer_group_ids.length > 0 && PEER_CLASSIFIER_CONFIG.peer_group_ids.includes(chat_id);
|
|
15293
|
+
if (peerActiveOnChat) {
|
|
15294
|
+
if (PEER_DISABLED_GLOBAL) {
|
|
15295
|
+
process.stderr.write(
|
|
15296
|
+
`telegram-channel(${AGENT_CODE_NAME}): outgress_blocked reason=peer_disabled_global chat=${redactId(chat_id)}
|
|
15297
|
+
`
|
|
15298
|
+
);
|
|
15299
|
+
return {
|
|
15300
|
+
content: [
|
|
15301
|
+
{
|
|
15302
|
+
type: "text",
|
|
15303
|
+
text: "Multi-agent collaboration is disabled (TELEGRAM_PEER_DISABLED). Stop attempting to reply on this chat."
|
|
15304
|
+
}
|
|
15305
|
+
],
|
|
15306
|
+
isError: true
|
|
15307
|
+
};
|
|
15308
|
+
}
|
|
15309
|
+
const stale = peerPresence.shouldDropOutgress(chat_id, Date.now());
|
|
15310
|
+
if (stale === "no_human_recent") {
|
|
15311
|
+
process.stderr.write(
|
|
15312
|
+
`telegram-channel(${AGENT_CODE_NAME}): outgress_blocked reason=no_human_recent chat=${redactId(chat_id)}
|
|
15313
|
+
`
|
|
15314
|
+
);
|
|
15315
|
+
return {
|
|
15316
|
+
content: [
|
|
15317
|
+
{
|
|
15318
|
+
type: "text",
|
|
15319
|
+
text: "No human message in this chat in the last 30 minutes. Peer-agent collaboration is paused for this chat until a human posts."
|
|
15320
|
+
}
|
|
15321
|
+
],
|
|
15322
|
+
isError: true
|
|
15323
|
+
};
|
|
15324
|
+
}
|
|
15325
|
+
}
|
|
14942
15326
|
const killed = await isThreadKilled({
|
|
14943
15327
|
channelType: "telegram",
|
|
14944
15328
|
channelId: chat_id,
|
|
@@ -15245,11 +15629,23 @@ async function pollLoop() {
|
|
|
15245
15629
|
const userId = msg.from?.id != null ? String(msg.from.id) : "unknown";
|
|
15246
15630
|
const userName = msg.from?.username || [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() || userId;
|
|
15247
15631
|
const isFromBot = !!msg.from?.is_bot;
|
|
15632
|
+
const nowMs = Date.now();
|
|
15248
15633
|
if (chatId && !isFromBot) {
|
|
15249
15634
|
resetThread(chatId, "");
|
|
15250
15635
|
}
|
|
15636
|
+
peerPresence.noteInbound(chatId, isFromBot ? "bot" : "human", nowMs);
|
|
15637
|
+
if (!isFromBot) {
|
|
15638
|
+
peerRateLimiter.recordInboundHuman(chatId, nowMs);
|
|
15639
|
+
}
|
|
15251
15640
|
let peerAgentMeta = null;
|
|
15252
15641
|
if (isFromBot) {
|
|
15642
|
+
if (PEER_DISABLED_GLOBAL) {
|
|
15643
|
+
process.stderr.write(
|
|
15644
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=peer_disabled_global chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
|
|
15645
|
+
`
|
|
15646
|
+
);
|
|
15647
|
+
continue;
|
|
15648
|
+
}
|
|
15253
15649
|
const classifierMsg = {
|
|
15254
15650
|
message_id: msg.message_id,
|
|
15255
15651
|
text: msg.text,
|
|
@@ -15276,7 +15672,29 @@ async function pollLoop() {
|
|
|
15276
15672
|
continue;
|
|
15277
15673
|
}
|
|
15278
15674
|
if (classification.kind === "peer-ingress") {
|
|
15279
|
-
|
|
15675
|
+
const limit = await peerRateLimiter.recordInboundPeer(
|
|
15676
|
+
chatId,
|
|
15677
|
+
classification.peer.bot_id,
|
|
15678
|
+
nowMs
|
|
15679
|
+
);
|
|
15680
|
+
if (limit.decision !== "ok") {
|
|
15681
|
+
process.stderr.write(
|
|
15682
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=${limit.decision} chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
|
|
15683
|
+
`
|
|
15684
|
+
);
|
|
15685
|
+
continue;
|
|
15686
|
+
}
|
|
15687
|
+
if (limit.dailyBudgetWarn && !dailyBudgetWarned) {
|
|
15688
|
+
dailyBudgetWarned = true;
|
|
15689
|
+
process.stderr.write(
|
|
15690
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_warn reason=daily_budget_80pct
|
|
15691
|
+
`
|
|
15692
|
+
);
|
|
15693
|
+
}
|
|
15694
|
+
peerAgentMeta = {
|
|
15695
|
+
code_name: classification.peer.code_name,
|
|
15696
|
+
agent_id: classification.peer.agent_id
|
|
15697
|
+
};
|
|
15280
15698
|
}
|
|
15281
15699
|
}
|
|
15282
15700
|
const messageId = String(msg.message_id);
|