@integrity-labs/agt-cli 0.19.18 → 0.19.20
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-IWFXAB4O.js} +427 -21
- package/dist/chunk-IWFXAB4O.js.map +1 -0
- package/dist/lib/manager-worker.js +111 -5
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +282 -0
- package/mcp/telegram-channel.js +600 -14
- package/package.json +1 -1
- package/dist/chunk-WTFROCJ3.js.map +0 -1
package/mcp/telegram-channel.js
CHANGED
|
@@ -14217,6 +14217,9 @@ function classifyPeerMessage(msg, cfg, self) {
|
|
|
14217
14217
|
if (self.bot_id !== null && msg.from.id === self.bot_id) {
|
|
14218
14218
|
return { kind: "self" };
|
|
14219
14219
|
}
|
|
14220
|
+
if (cfg.peer_disabled_mode === "all") {
|
|
14221
|
+
return { kind: "drop", reason: "peer_disabled_all" };
|
|
14222
|
+
}
|
|
14220
14223
|
if (cfg.peer_agent_mode === "off") {
|
|
14221
14224
|
return { kind: "drop", reason: "mode_off" };
|
|
14222
14225
|
}
|
|
@@ -14232,16 +14235,30 @@ function classifyPeerMessage(msg, cfg, self) {
|
|
|
14232
14235
|
if (!peer) {
|
|
14233
14236
|
return { kind: "drop", reason: "unknown_peer" };
|
|
14234
14237
|
}
|
|
14238
|
+
if (peer.gate_path === null) {
|
|
14239
|
+
return { kind: "drop", reason: "cross_team_grant_missing" };
|
|
14240
|
+
}
|
|
14241
|
+
if (cfg.peer_disabled_mode === "cross_team_only" && peer.gate_path !== void 0 && peer.gate_path !== "same_team") {
|
|
14242
|
+
return { kind: "drop", reason: "peer_disabled_cross_team" };
|
|
14243
|
+
}
|
|
14235
14244
|
if (self.bot_id === null || self.bot_username === null) {
|
|
14236
14245
|
return { kind: "drop", reason: "self_resolution_pending" };
|
|
14237
14246
|
}
|
|
14238
|
-
|
|
14239
|
-
|
|
14247
|
+
const addressing = classifyAddressing(msg, self.bot_id, self.bot_username);
|
|
14248
|
+
if (!addressing.addressed) {
|
|
14249
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
14250
|
+
}
|
|
14251
|
+
if (addressing.viaReplyOnly) {
|
|
14252
|
+
const inAllowedGroup = cfg.peer_group_ids.length > 0 && cfg.peer_group_ids.includes(chatId);
|
|
14253
|
+
const respondMode = cfg.peer_agent_mode === "respond";
|
|
14254
|
+
if (!(inAllowedGroup && respondMode)) {
|
|
14255
|
+
return { kind: "drop", reason: "not_addressed" };
|
|
14256
|
+
}
|
|
14240
14257
|
}
|
|
14241
|
-
return { kind: "
|
|
14258
|
+
return { kind: "peer-ingress", peer };
|
|
14242
14259
|
}
|
|
14243
|
-
function
|
|
14244
|
-
|
|
14260
|
+
function classifyAddressing(msg, ourBotId, ourBotUsername) {
|
|
14261
|
+
let viaMentionOrCommand = false;
|
|
14245
14262
|
const text = msg.text ?? msg.caption ?? "";
|
|
14246
14263
|
const entities = msg.entities ?? msg.caption_entities ?? [];
|
|
14247
14264
|
const usAt = "@" + ourBotUsername;
|
|
@@ -14249,18 +14266,22 @@ function isAddressedToUs(msg, ourBotId, ourBotUsername) {
|
|
|
14249
14266
|
const slice = text.slice(entity.offset, entity.offset + entity.length).toLowerCase();
|
|
14250
14267
|
if (entity.type === "bot_command") {
|
|
14251
14268
|
const at = slice.indexOf("@");
|
|
14252
|
-
if (at < 0) {
|
|
14253
|
-
|
|
14269
|
+
if (at < 0 || slice.slice(at + 1) === ourBotUsername) {
|
|
14270
|
+
viaMentionOrCommand = true;
|
|
14271
|
+
break;
|
|
14254
14272
|
}
|
|
14255
|
-
if (slice.slice(at + 1) === ourBotUsername) return true;
|
|
14256
14273
|
} else if (entity.type === "mention" && slice === usAt) {
|
|
14257
|
-
|
|
14274
|
+
viaMentionOrCommand = true;
|
|
14275
|
+
break;
|
|
14258
14276
|
}
|
|
14259
14277
|
}
|
|
14260
|
-
|
|
14278
|
+
const viaReply = msg.reply_to_message?.from?.id === ourBotId;
|
|
14279
|
+
if (viaMentionOrCommand) return { addressed: true, viaReplyOnly: false };
|
|
14280
|
+
if (viaReply) return { addressed: true, viaReplyOnly: true };
|
|
14281
|
+
return { addressed: false, viaReplyOnly: false };
|
|
14261
14282
|
}
|
|
14262
14283
|
var CODE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
14263
|
-
function parsePeersEnv(raw) {
|
|
14284
|
+
function parsePeersEnv(raw, gateRaw) {
|
|
14264
14285
|
if (!raw || raw.trim().length === 0) return [];
|
|
14265
14286
|
let parsed;
|
|
14266
14287
|
try {
|
|
@@ -14269,11 +14290,54 @@ function parsePeersEnv(raw) {
|
|
|
14269
14290
|
return [];
|
|
14270
14291
|
}
|
|
14271
14292
|
if (!Array.isArray(parsed)) return [];
|
|
14293
|
+
const gateMap = /* @__PURE__ */ new Map();
|
|
14294
|
+
let gateConfigInvalid = false;
|
|
14295
|
+
if (gateRaw && gateRaw.trim().length > 0) {
|
|
14296
|
+
try {
|
|
14297
|
+
const gateParsed = JSON.parse(gateRaw);
|
|
14298
|
+
if (!gateParsed || typeof gateParsed !== "object" || Array.isArray(gateParsed)) {
|
|
14299
|
+
gateConfigInvalid = true;
|
|
14300
|
+
} else {
|
|
14301
|
+
for (const [k, v] of Object.entries(gateParsed)) {
|
|
14302
|
+
if (v === null) {
|
|
14303
|
+
gateMap.set(k, null);
|
|
14304
|
+
} else if (typeof v === "string") {
|
|
14305
|
+
const isGrantPath = v.startsWith("grant:") && v.slice("grant:".length).trim().length > 0;
|
|
14306
|
+
if (v === "same_team" || v === "intra_org_unrestricted" || isGrantPath) {
|
|
14307
|
+
gateMap.set(k, v);
|
|
14308
|
+
} else {
|
|
14309
|
+
gateConfigInvalid = true;
|
|
14310
|
+
break;
|
|
14311
|
+
}
|
|
14312
|
+
} else {
|
|
14313
|
+
gateConfigInvalid = true;
|
|
14314
|
+
break;
|
|
14315
|
+
}
|
|
14316
|
+
}
|
|
14317
|
+
}
|
|
14318
|
+
} catch {
|
|
14319
|
+
gateConfigInvalid = true;
|
|
14320
|
+
}
|
|
14321
|
+
if (gateConfigInvalid) {
|
|
14322
|
+
console.error(
|
|
14323
|
+
"[telegram-peer-classifier] TELEGRAM_PEERS_GATE is present but malformed; failing closed (every peer drops as cross_team_grant_missing)"
|
|
14324
|
+
);
|
|
14325
|
+
}
|
|
14326
|
+
}
|
|
14272
14327
|
const out = [];
|
|
14273
14328
|
for (const entry of parsed) {
|
|
14274
14329
|
if (entry && typeof entry === "object" && typeof entry.code_name === "string" && CODE_NAME_RE.test(entry.code_name) && typeof entry.agent_id === "string" && typeof entry.bot_id === "number" && Number.isInteger(entry.bot_id) && entry.bot_id > 0) {
|
|
14275
14330
|
const e = entry;
|
|
14276
|
-
|
|
14331
|
+
const peer = { code_name: e.code_name, bot_id: e.bot_id, agent_id: e.agent_id };
|
|
14332
|
+
if (gateConfigInvalid) {
|
|
14333
|
+
peer.gate_path = null;
|
|
14334
|
+
} else {
|
|
14335
|
+
const key2 = String(e.bot_id);
|
|
14336
|
+
if (gateMap.has(key2)) {
|
|
14337
|
+
peer.gate_path = gateMap.get(key2) ?? null;
|
|
14338
|
+
}
|
|
14339
|
+
}
|
|
14340
|
+
out.push(peer);
|
|
14277
14341
|
}
|
|
14278
14342
|
}
|
|
14279
14343
|
return out;
|
|
@@ -14287,6 +14351,388 @@ function parsePeerAgentModeEnv(raw) {
|
|
|
14287
14351
|
return "off";
|
|
14288
14352
|
}
|
|
14289
14353
|
|
|
14354
|
+
// src/telegram-peer-rate-limiter.ts
|
|
14355
|
+
var SECOND_MS = 1e3;
|
|
14356
|
+
var MINUTE_MS = 60 * SECOND_MS;
|
|
14357
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
14358
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
14359
|
+
var DEFAULT_RATE_LIMITER_CONFIG = Object.freeze({
|
|
14360
|
+
pairBurstLimit: 1,
|
|
14361
|
+
pairBurstWindowMs: 10 * SECOND_MS,
|
|
14362
|
+
pairWindowLimit: 5,
|
|
14363
|
+
pairWindowMs: 5 * MINUTE_MS,
|
|
14364
|
+
chatLimit: 10,
|
|
14365
|
+
chatWindowMs: 5 * MINUTE_MS,
|
|
14366
|
+
agentDailyLimit: 200,
|
|
14367
|
+
agentDailyWindowMs: DAY_MS,
|
|
14368
|
+
dailyBudgetLimit: 500,
|
|
14369
|
+
dailyBudgetWindowMs: DAY_MS
|
|
14370
|
+
});
|
|
14371
|
+
function readSliding(window, windowMs, now) {
|
|
14372
|
+
if (!window) return 0;
|
|
14373
|
+
const cutoff = now - windowMs;
|
|
14374
|
+
let i = 0;
|
|
14375
|
+
while (i < window.timestamps.length && window.timestamps[i] <= cutoff) i++;
|
|
14376
|
+
if (i > 0) window.timestamps.splice(0, i);
|
|
14377
|
+
return window.timestamps.length;
|
|
14378
|
+
}
|
|
14379
|
+
function bumpSliding(window, now) {
|
|
14380
|
+
window.timestamps.push(now);
|
|
14381
|
+
}
|
|
14382
|
+
function createPeerRateLimiter(config2 = DEFAULT_RATE_LIMITER_CONFIG) {
|
|
14383
|
+
const counters = {
|
|
14384
|
+
pairBurst: /* @__PURE__ */ new Map(),
|
|
14385
|
+
pairWindow: /* @__PURE__ */ new Map(),
|
|
14386
|
+
chat: /* @__PURE__ */ new Map(),
|
|
14387
|
+
agentDaily: { timestamps: [] },
|
|
14388
|
+
dailyBudget: { timestamps: [] }
|
|
14389
|
+
};
|
|
14390
|
+
let humanInbound = 0;
|
|
14391
|
+
function pairKey(chatId, peerBotId) {
|
|
14392
|
+
return `${chatId}:${peerBotId}`;
|
|
14393
|
+
}
|
|
14394
|
+
function readMap(map, key2, windowMs, now) {
|
|
14395
|
+
return readSliding(map.get(key2), windowMs, now);
|
|
14396
|
+
}
|
|
14397
|
+
function bumpMap(map, key2, now) {
|
|
14398
|
+
let entry = map.get(key2);
|
|
14399
|
+
if (!entry) {
|
|
14400
|
+
entry = { timestamps: [] };
|
|
14401
|
+
map.set(key2, entry);
|
|
14402
|
+
}
|
|
14403
|
+
bumpSliding(entry, now);
|
|
14404
|
+
}
|
|
14405
|
+
return {
|
|
14406
|
+
recordInboundPeer(chatId, peerBotId, now) {
|
|
14407
|
+
const pk = pairKey(chatId, peerBotId);
|
|
14408
|
+
if (readMap(counters.pairBurst, pk, config2.pairBurstWindowMs, now) >= config2.pairBurstLimit) {
|
|
14409
|
+
return { decision: "rate_limit_pair", dailyBudgetWarn: false };
|
|
14410
|
+
}
|
|
14411
|
+
if (readMap(counters.pairWindow, pk, config2.pairWindowMs, now) >= config2.pairWindowLimit) {
|
|
14412
|
+
return { decision: "rate_limit_pair", dailyBudgetWarn: false };
|
|
14413
|
+
}
|
|
14414
|
+
if (readMap(counters.chat, chatId, config2.chatWindowMs, now) >= config2.chatLimit) {
|
|
14415
|
+
return { decision: "rate_limit_chat", dailyBudgetWarn: false };
|
|
14416
|
+
}
|
|
14417
|
+
if (readSliding(counters.agentDaily, config2.agentDailyWindowMs, now) >= config2.agentDailyLimit) {
|
|
14418
|
+
return { decision: "rate_limit_agent_daily", dailyBudgetWarn: false };
|
|
14419
|
+
}
|
|
14420
|
+
const budgetSoFar = readSliding(counters.dailyBudget, config2.dailyBudgetWindowMs, now);
|
|
14421
|
+
if (budgetSoFar >= config2.dailyBudgetLimit) {
|
|
14422
|
+
return { decision: "daily_budget_exhausted", dailyBudgetWarn: false };
|
|
14423
|
+
}
|
|
14424
|
+
bumpMap(counters.pairBurst, pk, now);
|
|
14425
|
+
bumpMap(counters.pairWindow, pk, now);
|
|
14426
|
+
bumpMap(counters.chat, chatId, now);
|
|
14427
|
+
bumpSliding(counters.agentDaily, now);
|
|
14428
|
+
bumpSliding(counters.dailyBudget, now);
|
|
14429
|
+
const newBudget = budgetSoFar + 1;
|
|
14430
|
+
const warn80 = newBudget >= Math.ceil(config2.dailyBudgetLimit * 0.8) && newBudget < config2.dailyBudgetLimit;
|
|
14431
|
+
return { decision: "ok", dailyBudgetWarn: warn80 };
|
|
14432
|
+
},
|
|
14433
|
+
recordInboundHuman(_chatId, _now) {
|
|
14434
|
+
humanInbound += 1;
|
|
14435
|
+
},
|
|
14436
|
+
snapshot(now) {
|
|
14437
|
+
const perChat = /* @__PURE__ */ new Map();
|
|
14438
|
+
for (const [k, v] of counters.chat) {
|
|
14439
|
+
perChat.set(k, readSliding(v, config2.chatWindowMs, now));
|
|
14440
|
+
}
|
|
14441
|
+
const perPair = /* @__PURE__ */ new Map();
|
|
14442
|
+
for (const [k, v] of counters.pairWindow) {
|
|
14443
|
+
perPair.set(k, readSliding(v, config2.pairWindowMs, now));
|
|
14444
|
+
}
|
|
14445
|
+
return {
|
|
14446
|
+
perAgentDaily: readSliding(counters.agentDaily, config2.agentDailyWindowMs, now),
|
|
14447
|
+
dailyBudget: readSliding(counters.dailyBudget, config2.dailyBudgetWindowMs, now),
|
|
14448
|
+
perChat,
|
|
14449
|
+
perPair,
|
|
14450
|
+
humanInbound
|
|
14451
|
+
};
|
|
14452
|
+
}
|
|
14453
|
+
};
|
|
14454
|
+
}
|
|
14455
|
+
function createPresenceTracker() {
|
|
14456
|
+
const lastHumanByChat = /* @__PURE__ */ new Map();
|
|
14457
|
+
return {
|
|
14458
|
+
noteInbound(chatId, kind, now) {
|
|
14459
|
+
if (kind === "human") lastHumanByChat.set(chatId, now);
|
|
14460
|
+
},
|
|
14461
|
+
shouldDropOutgress(chatId, now, maxAgeMs = 30 * MINUTE_MS) {
|
|
14462
|
+
const last = lastHumanByChat.get(chatId);
|
|
14463
|
+
if (last === void 0) return "no_human_recent";
|
|
14464
|
+
if (now - last >= maxAgeMs) return "no_human_recent";
|
|
14465
|
+
return null;
|
|
14466
|
+
}
|
|
14467
|
+
};
|
|
14468
|
+
}
|
|
14469
|
+
|
|
14470
|
+
// src/telegram-peer-rate-limiter-db.ts
|
|
14471
|
+
function createDbBackedPeerRateLimiter(deps) {
|
|
14472
|
+
const config2 = deps.config ?? DEFAULT_RATE_LIMITER_CONFIG;
|
|
14473
|
+
const readCacheMs = deps.readCacheMs ?? 2e3;
|
|
14474
|
+
const log = deps.log ?? (() => {
|
|
14475
|
+
});
|
|
14476
|
+
const caches = /* @__PURE__ */ new Map();
|
|
14477
|
+
let humanInbound = 0;
|
|
14478
|
+
const pendingWrites = /* @__PURE__ */ new Set();
|
|
14479
|
+
function trackPending(write) {
|
|
14480
|
+
pendingWrites.add(write);
|
|
14481
|
+
void write.finally(() => pendingWrites.delete(write));
|
|
14482
|
+
}
|
|
14483
|
+
function cacheKey2(layer, chatId, peerBotId) {
|
|
14484
|
+
return `${layer}|${chatId}|${peerBotId ?? ""}`;
|
|
14485
|
+
}
|
|
14486
|
+
const localBumpTimes = /* @__PURE__ */ new Map();
|
|
14487
|
+
function bumpLocal(layer, chatId, peerBotId, now) {
|
|
14488
|
+
const key2 = cacheKey2(layer, chatId, peerBotId);
|
|
14489
|
+
const list = localBumpTimes.get(key2);
|
|
14490
|
+
if (list) list.push(now);
|
|
14491
|
+
else localBumpTimes.set(key2, [now]);
|
|
14492
|
+
}
|
|
14493
|
+
function localBumpsSince(key2, fetchedAt) {
|
|
14494
|
+
const list = localBumpTimes.get(key2);
|
|
14495
|
+
if (!list) return 0;
|
|
14496
|
+
let n = 0;
|
|
14497
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
14498
|
+
if (list[i] >= fetchedAt) n++;
|
|
14499
|
+
else break;
|
|
14500
|
+
}
|
|
14501
|
+
return n;
|
|
14502
|
+
}
|
|
14503
|
+
function pruneLocalBumps(retentionMs, now) {
|
|
14504
|
+
const cutoff = now - retentionMs;
|
|
14505
|
+
for (const [key2, list] of localBumpTimes) {
|
|
14506
|
+
let i = 0;
|
|
14507
|
+
while (i < list.length && list[i] < cutoff) i++;
|
|
14508
|
+
if (i > 0) list.splice(0, i);
|
|
14509
|
+
if (list.length === 0) localBumpTimes.delete(key2);
|
|
14510
|
+
}
|
|
14511
|
+
}
|
|
14512
|
+
async function readCount(layer, chatId, peerBotId, windowMs, now) {
|
|
14513
|
+
const key2 = cacheKey2(layer, chatId, peerBotId);
|
|
14514
|
+
const cached2 = caches.get(key2);
|
|
14515
|
+
if (cached2 && now - cached2.fetchedAt < readCacheMs) {
|
|
14516
|
+
return cached2.count + localBumpsSince(key2, cached2.fetchedAt);
|
|
14517
|
+
}
|
|
14518
|
+
const since = new Date(now - windowMs).toISOString();
|
|
14519
|
+
let count;
|
|
14520
|
+
try {
|
|
14521
|
+
const args = {
|
|
14522
|
+
since,
|
|
14523
|
+
traffic_class: layer
|
|
14524
|
+
};
|
|
14525
|
+
if (layer === "pair_burst" || layer === "pair_window") {
|
|
14526
|
+
args.chat_id = chatId;
|
|
14527
|
+
args.peer_bot_id = peerBotId ?? void 0;
|
|
14528
|
+
} else if (layer === "chat") {
|
|
14529
|
+
args.chat_id = chatId;
|
|
14530
|
+
}
|
|
14531
|
+
count = await deps.api.countEvents(args);
|
|
14532
|
+
} catch (err) {
|
|
14533
|
+
log(`peer_rate_event read failed (layer=${layer}): ${err.message} \u2014 fail-open`);
|
|
14534
|
+
count = 0;
|
|
14535
|
+
}
|
|
14536
|
+
const fetchedAt = now;
|
|
14537
|
+
caches.set(key2, { fetchedAt, count });
|
|
14538
|
+
return count + localBumpsSince(key2, fetchedAt);
|
|
14539
|
+
}
|
|
14540
|
+
async function decide(chatId, peerBotId, now) {
|
|
14541
|
+
const layers = [
|
|
14542
|
+
{ layer: "pair_burst", chatId, peerBotId, limit: config2.pairBurstLimit, windowMs: config2.pairBurstWindowMs, reason: "rate_limit_pair" },
|
|
14543
|
+
{ layer: "pair_window", chatId, peerBotId, limit: config2.pairWindowLimit, windowMs: config2.pairWindowMs, reason: "rate_limit_pair" },
|
|
14544
|
+
{ layer: "chat", chatId, peerBotId: null, limit: config2.chatLimit, windowMs: config2.chatWindowMs, reason: "rate_limit_chat" },
|
|
14545
|
+
{ layer: "agent_daily", chatId: "", peerBotId: null, limit: config2.agentDailyLimit, windowMs: config2.agentDailyWindowMs, reason: "rate_limit_agent_daily" },
|
|
14546
|
+
{ layer: "daily_budget", chatId: "", peerBotId: null, limit: config2.dailyBudgetLimit, windowMs: config2.dailyBudgetWindowMs, reason: "daily_budget_exhausted" }
|
|
14547
|
+
];
|
|
14548
|
+
for (const l of layers) {
|
|
14549
|
+
const count = await readCount(l.layer, l.chatId, l.peerBotId, l.windowMs, now);
|
|
14550
|
+
if (count >= l.limit) {
|
|
14551
|
+
return { decision: l.reason, dailyBudgetWarn: false };
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14554
|
+
for (const l of layers) bumpLocal(l.layer, l.chatId, l.peerBotId, now);
|
|
14555
|
+
pruneLocalBumps(Math.max(config2.pairBurstWindowMs, 6e4), now);
|
|
14556
|
+
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}`)));
|
|
14557
|
+
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}`)));
|
|
14558
|
+
trackPending(deps.api.recordEvent({ chat_id: chatId, peer_bot_id: null, traffic_class: "chat" }).catch((err) => log(`peer_rate_event write failed: ${err.message}`)));
|
|
14559
|
+
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}`)));
|
|
14560
|
+
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}`)));
|
|
14561
|
+
const dbKey = cacheKey2("daily_budget", "", null);
|
|
14562
|
+
const dbCached = caches.get(dbKey);
|
|
14563
|
+
const dbBudget = (dbCached?.count ?? 0) + localBumpsSince(dbKey, dbCached?.fetchedAt ?? 0);
|
|
14564
|
+
const warn80 = dbBudget >= Math.ceil(config2.dailyBudgetLimit * 0.8) && dbBudget < config2.dailyBudgetLimit;
|
|
14565
|
+
return { decision: "ok", dailyBudgetWarn: warn80 };
|
|
14566
|
+
}
|
|
14567
|
+
return {
|
|
14568
|
+
recordInboundPeer(chatId, peerBotId, now) {
|
|
14569
|
+
return decide(chatId, peerBotId, now);
|
|
14570
|
+
},
|
|
14571
|
+
recordInboundHuman(_chatId, _now) {
|
|
14572
|
+
humanInbound += 1;
|
|
14573
|
+
},
|
|
14574
|
+
snapshot(now) {
|
|
14575
|
+
const perChat = /* @__PURE__ */ new Map();
|
|
14576
|
+
const perPair = /* @__PURE__ */ new Map();
|
|
14577
|
+
let perAgentDaily = 0;
|
|
14578
|
+
let dailyBudget = 0;
|
|
14579
|
+
for (const [k, v] of caches) {
|
|
14580
|
+
const local = localBumpsSince(k, v.fetchedAt);
|
|
14581
|
+
const layer = k.split("|")[0];
|
|
14582
|
+
if (layer === "pair_window") {
|
|
14583
|
+
const chatId = k.split("|")[1];
|
|
14584
|
+
const peerBotId = k.split("|")[2];
|
|
14585
|
+
perPair.set(`${chatId}:${peerBotId}`, v.count + local);
|
|
14586
|
+
} else if (layer === "chat") {
|
|
14587
|
+
const chatId = k.split("|")[1];
|
|
14588
|
+
perChat.set(chatId, v.count + local);
|
|
14589
|
+
} else if (layer === "agent_daily") {
|
|
14590
|
+
perAgentDaily = v.count + local;
|
|
14591
|
+
} else if (layer === "daily_budget") {
|
|
14592
|
+
dailyBudget = v.count + local;
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14595
|
+
void now;
|
|
14596
|
+
return { perAgentDaily, dailyBudget, perChat, perPair, humanInbound };
|
|
14597
|
+
},
|
|
14598
|
+
async flushPending() {
|
|
14599
|
+
await Promise.all([...pendingWrites]);
|
|
14600
|
+
}
|
|
14601
|
+
};
|
|
14602
|
+
}
|
|
14603
|
+
function createDefaultPeerRateApiClient(args) {
|
|
14604
|
+
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
14605
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
14606
|
+
const base = args.agtHost.replace(/\/+$/, "");
|
|
14607
|
+
let cachedToken = null;
|
|
14608
|
+
let cachedTokenExpiresAt = 0;
|
|
14609
|
+
async function getToken() {
|
|
14610
|
+
if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
|
|
14611
|
+
const resp = await fetchImpl(`${base}/host/exchange`, {
|
|
14612
|
+
method: "POST",
|
|
14613
|
+
headers: { "Content-Type": "application/json" },
|
|
14614
|
+
body: JSON.stringify({ host_key: args.agtApiKey })
|
|
14615
|
+
});
|
|
14616
|
+
if (!resp.ok) {
|
|
14617
|
+
const body = await resp.text().catch(() => "");
|
|
14618
|
+
throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
|
|
14619
|
+
}
|
|
14620
|
+
const data = await resp.json();
|
|
14621
|
+
cachedToken = data.token;
|
|
14622
|
+
cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
14623
|
+
return cachedToken;
|
|
14624
|
+
}
|
|
14625
|
+
async function authedFetch(path, init) {
|
|
14626
|
+
const headers = {
|
|
14627
|
+
Authorization: `Bearer ${await getToken()}`
|
|
14628
|
+
};
|
|
14629
|
+
if (init.body !== void 0) headers["Content-Type"] = "application/json";
|
|
14630
|
+
let resp = await fetchImpl(`${base}${path}`, { method: init.method, headers, body: init.body });
|
|
14631
|
+
if (resp.status === 401) {
|
|
14632
|
+
cachedToken = null;
|
|
14633
|
+
cachedTokenExpiresAt = 0;
|
|
14634
|
+
const retryHeaders = {
|
|
14635
|
+
Authorization: `Bearer ${await getToken()}`
|
|
14636
|
+
};
|
|
14637
|
+
if (init.body !== void 0) retryHeaders["Content-Type"] = "application/json";
|
|
14638
|
+
resp = await fetchImpl(`${base}${path}`, { method: init.method, headers: retryHeaders, body: init.body });
|
|
14639
|
+
}
|
|
14640
|
+
return resp;
|
|
14641
|
+
}
|
|
14642
|
+
return {
|
|
14643
|
+
async recordEvent({ chat_id, peer_bot_id, traffic_class }) {
|
|
14644
|
+
const resp = await authedFetch("/host/peer-rate-events", {
|
|
14645
|
+
method: "POST",
|
|
14646
|
+
body: JSON.stringify({ agent_id: args.agentId, chat_id, peer_bot_id, traffic_class })
|
|
14647
|
+
});
|
|
14648
|
+
if (!resp.ok) {
|
|
14649
|
+
throw new Error(`POST /host/peer-rate-events failed: ${resp.status} ${await resp.text()}`);
|
|
14650
|
+
}
|
|
14651
|
+
},
|
|
14652
|
+
async countEvents({ since, chat_id, peer_bot_id, traffic_class }) {
|
|
14653
|
+
const params = new URLSearchParams({
|
|
14654
|
+
agent_id: args.agentId,
|
|
14655
|
+
since
|
|
14656
|
+
});
|
|
14657
|
+
if (chat_id !== void 0) params.set("chat_id", chat_id);
|
|
14658
|
+
if (peer_bot_id !== void 0) params.set("peer_bot_id", String(peer_bot_id));
|
|
14659
|
+
if (traffic_class !== void 0) params.set("traffic_class", traffic_class);
|
|
14660
|
+
const resp = await authedFetch(`/host/peer-rate-events?${params}`, { method: "GET" });
|
|
14661
|
+
if (!resp.ok) {
|
|
14662
|
+
throw new Error(`GET /host/peer-rate-events failed: ${resp.status} ${await resp.text()}`);
|
|
14663
|
+
}
|
|
14664
|
+
const json = await resp.json();
|
|
14665
|
+
return typeof json.count === "number" ? json.count : 0;
|
|
14666
|
+
}
|
|
14667
|
+
};
|
|
14668
|
+
}
|
|
14669
|
+
|
|
14670
|
+
// src/cross-team-peer-audit-client.ts
|
|
14671
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
14672
|
+
function createCrossTeamPeerAuditClient(args) {
|
|
14673
|
+
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
14674
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
14675
|
+
const log = args.log ?? (() => {
|
|
14676
|
+
});
|
|
14677
|
+
const base = args.agtHost.replace(/\/+$/, "");
|
|
14678
|
+
const agentId = args.agentId;
|
|
14679
|
+
const apiKey = args.agtApiKey;
|
|
14680
|
+
let cachedToken = null;
|
|
14681
|
+
let cachedTokenExpiresAt = 0;
|
|
14682
|
+
async function getToken() {
|
|
14683
|
+
if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
|
|
14684
|
+
const resp = await fetchImpl(`${base}/host/exchange`, {
|
|
14685
|
+
method: "POST",
|
|
14686
|
+
headers: { "Content-Type": "application/json" },
|
|
14687
|
+
body: JSON.stringify({ host_key: apiKey }),
|
|
14688
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
14689
|
+
});
|
|
14690
|
+
if (!resp.ok) {
|
|
14691
|
+
const body = await resp.text().catch(() => "");
|
|
14692
|
+
throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
|
|
14693
|
+
}
|
|
14694
|
+
const data = await resp.json();
|
|
14695
|
+
cachedToken = data.token;
|
|
14696
|
+
cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
14697
|
+
return cachedToken;
|
|
14698
|
+
}
|
|
14699
|
+
async function postOnce(event) {
|
|
14700
|
+
const token = await getToken();
|
|
14701
|
+
return fetchImpl(`${base}/host/cross-team-peer-event`, {
|
|
14702
|
+
method: "POST",
|
|
14703
|
+
headers: {
|
|
14704
|
+
Authorization: `Bearer ${token}`,
|
|
14705
|
+
"Content-Type": "application/json"
|
|
14706
|
+
},
|
|
14707
|
+
body: JSON.stringify({ agent_id: agentId, ...event }),
|
|
14708
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
14709
|
+
});
|
|
14710
|
+
}
|
|
14711
|
+
async function emitAsync(event) {
|
|
14712
|
+
try {
|
|
14713
|
+
let resp = await postOnce(event);
|
|
14714
|
+
if (resp.status === 401) {
|
|
14715
|
+
cachedToken = null;
|
|
14716
|
+
cachedTokenExpiresAt = 0;
|
|
14717
|
+
resp = await postOnce(event);
|
|
14718
|
+
}
|
|
14719
|
+
if (!resp.ok) {
|
|
14720
|
+
const body = await resp.text().catch(() => "");
|
|
14721
|
+
log(
|
|
14722
|
+
`cross-team-peer-audit: POST failed (${resp.status}) event=${event.event_type}: ${body.slice(0, 200)}`
|
|
14723
|
+
);
|
|
14724
|
+
}
|
|
14725
|
+
} catch (err) {
|
|
14726
|
+
log(`cross-team-peer-audit: emit threw event=${event.event_type}: ${err.message}`);
|
|
14727
|
+
}
|
|
14728
|
+
}
|
|
14729
|
+
return {
|
|
14730
|
+
emit(event) {
|
|
14731
|
+
void emitAsync(event);
|
|
14732
|
+
}
|
|
14733
|
+
};
|
|
14734
|
+
}
|
|
14735
|
+
|
|
14290
14736
|
// src/telegram-channel.ts
|
|
14291
14737
|
function redactId(id) {
|
|
14292
14738
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
@@ -14301,8 +14747,50 @@ var ALLOWED_CHATS = new Set(
|
|
|
14301
14747
|
var PEER_CLASSIFIER_CONFIG = {
|
|
14302
14748
|
peer_agent_mode: parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE),
|
|
14303
14749
|
peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
|
|
14304
|
-
peers: parsePeersEnv(process.env.TELEGRAM_PEERS)
|
|
14750
|
+
peers: parsePeersEnv(process.env.TELEGRAM_PEERS, process.env.TELEGRAM_PEERS_GATE),
|
|
14751
|
+
peer_disabled_mode: "off"
|
|
14752
|
+
// populated below from PEER_DISABLED_MODE
|
|
14305
14753
|
};
|
|
14754
|
+
var PEER_DISABLED_MODE = (() => {
|
|
14755
|
+
const next = (process.env.PEER_DISABLED ?? "").trim().toLowerCase();
|
|
14756
|
+
if (next === "off" || next === "cross_team_only" || next === "all") return next;
|
|
14757
|
+
if (next.length > 0) {
|
|
14758
|
+
process.stderr.write(
|
|
14759
|
+
`telegram-channel(${AGENT_CODE_NAME}): invalid PEER_DISABLED=${JSON.stringify(process.env.PEER_DISABLED)} \u2014 defaulting to 'all' (fail-closed)
|
|
14760
|
+
`
|
|
14761
|
+
);
|
|
14762
|
+
return "all";
|
|
14763
|
+
}
|
|
14764
|
+
const legacy = (process.env.TELEGRAM_PEER_DISABLED ?? "").trim().toLowerCase();
|
|
14765
|
+
return legacy === "1" || legacy === "true" || legacy === "yes" ? "all" : "off";
|
|
14766
|
+
})();
|
|
14767
|
+
var PEER_DISABLED_GLOBAL = PEER_DISABLED_MODE === "all";
|
|
14768
|
+
if (PEER_DISABLED_MODE !== "off") {
|
|
14769
|
+
process.stderr.write(
|
|
14770
|
+
`telegram-channel(${AGENT_CODE_NAME}): PEER_DISABLED=${PEER_DISABLED_MODE} \u2014 ${PEER_DISABLED_MODE === "all" ? "every peer surface short-circuited" : "cross-team peers dropped, same-team peers admitted"}
|
|
14771
|
+
`
|
|
14772
|
+
);
|
|
14773
|
+
}
|
|
14774
|
+
PEER_CLASSIFIER_CONFIG.peer_disabled_mode = PEER_DISABLED_MODE;
|
|
14775
|
+
var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
|
|
14776
|
+
agtHost: AGT_HOST,
|
|
14777
|
+
agtApiKey: AGT_API_KEY,
|
|
14778
|
+
agentId: process.env.AGT_AGENT_ID ?? null,
|
|
14779
|
+
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
14780
|
+
`)
|
|
14781
|
+
});
|
|
14782
|
+
var peerRateApiClient = createDefaultPeerRateApiClient({
|
|
14783
|
+
agtHost: AGT_HOST,
|
|
14784
|
+
agtApiKey: AGT_API_KEY,
|
|
14785
|
+
agentId: process.env.AGT_AGENT_ID ?? null
|
|
14786
|
+
});
|
|
14787
|
+
var peerRateLimiter = peerRateApiClient && parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE) !== "off" ? createDbBackedPeerRateLimiter({
|
|
14788
|
+
api: peerRateApiClient,
|
|
14789
|
+
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
14790
|
+
`)
|
|
14791
|
+
}) : createPeerRateLimiter();
|
|
14792
|
+
var peerPresence = createPresenceTracker();
|
|
14793
|
+
var dailyBudgetWarned = false;
|
|
14306
14794
|
if (!BOT_TOKEN) {
|
|
14307
14795
|
process.stderr.write(
|
|
14308
14796
|
"telegram-channel: Missing TELEGRAM_BOT_TOKEN. Cannot start.\n"
|
|
@@ -14939,6 +15427,40 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
14939
15427
|
isError: true
|
|
14940
15428
|
};
|
|
14941
15429
|
}
|
|
15430
|
+
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);
|
|
15431
|
+
if (peerActiveOnChat) {
|
|
15432
|
+
if (PEER_DISABLED_GLOBAL) {
|
|
15433
|
+
process.stderr.write(
|
|
15434
|
+
`telegram-channel(${AGENT_CODE_NAME}): outgress_blocked reason=peer_disabled_global chat=${redactId(chat_id)}
|
|
15435
|
+
`
|
|
15436
|
+
);
|
|
15437
|
+
return {
|
|
15438
|
+
content: [
|
|
15439
|
+
{
|
|
15440
|
+
type: "text",
|
|
15441
|
+
text: "Multi-agent collaboration is disabled (TELEGRAM_PEER_DISABLED). Stop attempting to reply on this chat."
|
|
15442
|
+
}
|
|
15443
|
+
],
|
|
15444
|
+
isError: true
|
|
15445
|
+
};
|
|
15446
|
+
}
|
|
15447
|
+
const stale = peerPresence.shouldDropOutgress(chat_id, Date.now());
|
|
15448
|
+
if (stale === "no_human_recent") {
|
|
15449
|
+
process.stderr.write(
|
|
15450
|
+
`telegram-channel(${AGENT_CODE_NAME}): outgress_blocked reason=no_human_recent chat=${redactId(chat_id)}
|
|
15451
|
+
`
|
|
15452
|
+
);
|
|
15453
|
+
return {
|
|
15454
|
+
content: [
|
|
15455
|
+
{
|
|
15456
|
+
type: "text",
|
|
15457
|
+
text: "No human message in this chat in the last 30 minutes. Peer-agent collaboration is paused for this chat until a human posts."
|
|
15458
|
+
}
|
|
15459
|
+
],
|
|
15460
|
+
isError: true
|
|
15461
|
+
};
|
|
15462
|
+
}
|
|
15463
|
+
}
|
|
14942
15464
|
const killed = await isThreadKilled({
|
|
14943
15465
|
channelType: "telegram",
|
|
14944
15466
|
channelId: chat_id,
|
|
@@ -15245,11 +15767,23 @@ async function pollLoop() {
|
|
|
15245
15767
|
const userId = msg.from?.id != null ? String(msg.from.id) : "unknown";
|
|
15246
15768
|
const userName = msg.from?.username || [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() || userId;
|
|
15247
15769
|
const isFromBot = !!msg.from?.is_bot;
|
|
15770
|
+
const nowMs = Date.now();
|
|
15248
15771
|
if (chatId && !isFromBot) {
|
|
15249
15772
|
resetThread(chatId, "");
|
|
15250
15773
|
}
|
|
15774
|
+
peerPresence.noteInbound(chatId, isFromBot ? "bot" : "human", nowMs);
|
|
15775
|
+
if (!isFromBot) {
|
|
15776
|
+
peerRateLimiter.recordInboundHuman(chatId, nowMs);
|
|
15777
|
+
}
|
|
15251
15778
|
let peerAgentMeta = null;
|
|
15252
15779
|
if (isFromBot) {
|
|
15780
|
+
if (PEER_DISABLED_GLOBAL) {
|
|
15781
|
+
process.stderr.write(
|
|
15782
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=peer_disabled_global chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
|
|
15783
|
+
`
|
|
15784
|
+
);
|
|
15785
|
+
continue;
|
|
15786
|
+
}
|
|
15253
15787
|
const classifierMsg = {
|
|
15254
15788
|
message_id: msg.message_id,
|
|
15255
15789
|
text: msg.text,
|
|
@@ -15273,10 +15807,62 @@ async function pollLoop() {
|
|
|
15273
15807
|
`telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=${classification.reason} chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
|
|
15274
15808
|
`
|
|
15275
15809
|
);
|
|
15810
|
+
if (classification.reason === "cross_team_grant_missing") {
|
|
15811
|
+
crossTeamPeerAuditClient?.emit({
|
|
15812
|
+
event_type: "telegram.peer.drop.cross_team_grant_missing",
|
|
15813
|
+
peer_agent_id: null,
|
|
15814
|
+
gate_path: null,
|
|
15815
|
+
grant_id: null,
|
|
15816
|
+
chat_id: String(chatId),
|
|
15817
|
+
message_id: String(msg.message_id)
|
|
15818
|
+
});
|
|
15819
|
+
}
|
|
15276
15820
|
continue;
|
|
15277
15821
|
}
|
|
15278
15822
|
if (classification.kind === "peer-ingress") {
|
|
15279
|
-
|
|
15823
|
+
const limit = await peerRateLimiter.recordInboundPeer(
|
|
15824
|
+
chatId,
|
|
15825
|
+
classification.peer.bot_id,
|
|
15826
|
+
nowMs
|
|
15827
|
+
);
|
|
15828
|
+
if (limit.decision !== "ok") {
|
|
15829
|
+
process.stderr.write(
|
|
15830
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_drop reason=${limit.decision} chat=${redactId(chatId)} from=${redactId(String(msg.from?.id ?? "unknown"))}
|
|
15831
|
+
`
|
|
15832
|
+
);
|
|
15833
|
+
continue;
|
|
15834
|
+
}
|
|
15835
|
+
if (limit.dailyBudgetWarn && !dailyBudgetWarned) {
|
|
15836
|
+
dailyBudgetWarned = true;
|
|
15837
|
+
process.stderr.write(
|
|
15838
|
+
`telegram-channel(${AGENT_CODE_NAME}): peer_warn reason=daily_budget_80pct
|
|
15839
|
+
`
|
|
15840
|
+
);
|
|
15841
|
+
}
|
|
15842
|
+
peerAgentMeta = {
|
|
15843
|
+
code_name: classification.peer.code_name,
|
|
15844
|
+
agent_id: classification.peer.agent_id
|
|
15845
|
+
};
|
|
15846
|
+
const gate = classification.peer.gate_path;
|
|
15847
|
+
if (gate && gate !== "same_team" && gate !== "intra_org_unrestricted" && gate.startsWith("grant:")) {
|
|
15848
|
+
crossTeamPeerAuditClient?.emit({
|
|
15849
|
+
event_type: "telegram.peer.ingress.cross_team",
|
|
15850
|
+
peer_agent_id: classification.peer.agent_id || null,
|
|
15851
|
+
gate_path: gate,
|
|
15852
|
+
grant_id: gate.slice("grant:".length),
|
|
15853
|
+
chat_id: String(chatId),
|
|
15854
|
+
message_id: String(msg.message_id)
|
|
15855
|
+
});
|
|
15856
|
+
} else if (gate === "intra_org_unrestricted") {
|
|
15857
|
+
crossTeamPeerAuditClient?.emit({
|
|
15858
|
+
event_type: "telegram.peer.ingress.cross_team",
|
|
15859
|
+
peer_agent_id: classification.peer.agent_id || null,
|
|
15860
|
+
gate_path: gate,
|
|
15861
|
+
grant_id: null,
|
|
15862
|
+
chat_id: String(chatId),
|
|
15863
|
+
message_id: String(msg.message_id)
|
|
15864
|
+
});
|
|
15865
|
+
}
|
|
15280
15866
|
}
|
|
15281
15867
|
}
|
|
15282
15868
|
const messageId = String(msg.message_id);
|