@integrity-labs/agt-cli 0.27.93 → 0.27.95
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.
|
@@ -14526,23 +14526,33 @@ var DEFAULT_THROTTLE = {
|
|
|
14526
14526
|
windowMs: 12e4,
|
|
14527
14527
|
ringSize: 8
|
|
14528
14528
|
};
|
|
14529
|
-
|
|
14529
|
+
var DEFAULT_REPLY_QUEUE_DEPTH = 2;
|
|
14530
|
+
function planReplyDelivery(input) {
|
|
14530
14531
|
const cfg = input.config ?? DEFAULT_THROTTLE;
|
|
14532
|
+
const maxDepth = input.maxQueueDepth ?? DEFAULT_REPLY_QUEUE_DEPTH;
|
|
14531
14533
|
const cutoff = input.now - cfg.windowMs;
|
|
14532
|
-
const inWindow = input.recentReplyTimestamps.filter((t) => t >= cutoff);
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
};
|
|
14534
|
+
const inWindow = input.recentReplyTimestamps.filter((t) => t >= cutoff).sort((a, b) => a - b);
|
|
14535
|
+
const occupancy = inWindow.length + input.queuedCount;
|
|
14536
|
+
if (occupancy < cfg.threshold) {
|
|
14537
|
+
return { action: "send", recentCount: inWindow.length, reason: "under_threshold" };
|
|
14538
|
+
}
|
|
14539
|
+
if (input.queuedCount >= maxDepth) {
|
|
14540
|
+
return { action: "refuse", recentCount: inWindow.length, reason: "queue_full" };
|
|
14539
14541
|
}
|
|
14542
|
+
const idx = Math.min(input.queuedCount, inWindow.length - 1);
|
|
14543
|
+
const opensAt = (inWindow[idx] ?? input.now) + cfg.windowMs;
|
|
14540
14544
|
return {
|
|
14541
|
-
|
|
14545
|
+
action: "queue",
|
|
14546
|
+
deliverAtMs: Math.max(opensAt, input.now),
|
|
14542
14547
|
recentCount: inWindow.length,
|
|
14543
|
-
reason: "
|
|
14548
|
+
reason: "queued_for_slot"
|
|
14544
14549
|
};
|
|
14545
14550
|
}
|
|
14551
|
+
function relaxedThrottleConfig(cfg, env = process.env, envVar = "TELEGRAM_PRIVATE_REPLY_THROTTLE_COUNT") {
|
|
14552
|
+
const raw = Number(env[envVar]);
|
|
14553
|
+
const threshold = Number.isFinite(raw) && raw > 0 ? raw : cfg.threshold * 2;
|
|
14554
|
+
return { ...cfg, threshold, ringSize: Math.max(cfg.ringSize, threshold + 2) };
|
|
14555
|
+
}
|
|
14546
14556
|
var buffers = /* @__PURE__ */ new Map();
|
|
14547
14557
|
function key(channelId, threadKey) {
|
|
14548
14558
|
return `${channelId}${threadKey}`;
|
|
@@ -15144,8 +15154,296 @@ function createCrossTeamPeerAuditClient(args) {
|
|
|
15144
15154
|
};
|
|
15145
15155
|
}
|
|
15146
15156
|
|
|
15147
|
-
// src/
|
|
15157
|
+
// src/diagnostic-event-client.ts
|
|
15148
15158
|
var REQUEST_TIMEOUT_MS2 = 1e4;
|
|
15159
|
+
function createDiagnosticEventClient(args) {
|
|
15160
|
+
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15161
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
15162
|
+
const log = args.log ?? (() => {
|
|
15163
|
+
});
|
|
15164
|
+
const base = args.agtHost.replace(/\/+$/, "");
|
|
15165
|
+
const agentId = args.agentId;
|
|
15166
|
+
const apiKey = args.agtApiKey;
|
|
15167
|
+
let cachedToken = null;
|
|
15168
|
+
let cachedTokenExpiresAt = 0;
|
|
15169
|
+
async function getToken() {
|
|
15170
|
+
if (cachedToken && Date.now() < cachedTokenExpiresAt) return cachedToken;
|
|
15171
|
+
const resp = await fetchImpl(`${base}/host/exchange`, {
|
|
15172
|
+
method: "POST",
|
|
15173
|
+
headers: { "Content-Type": "application/json" },
|
|
15174
|
+
body: JSON.stringify({ host_key: apiKey }),
|
|
15175
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
15176
|
+
});
|
|
15177
|
+
if (!resp.ok) {
|
|
15178
|
+
const body = await resp.text().catch(() => "");
|
|
15179
|
+
throw new Error(`/host/exchange failed (${resp.status}): ${body.slice(0, 200)}`);
|
|
15180
|
+
}
|
|
15181
|
+
const data = await resp.json();
|
|
15182
|
+
cachedToken = data.token;
|
|
15183
|
+
cachedTokenExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() - 12e4 : Date.now() + 55 * 6e4;
|
|
15184
|
+
return cachedToken;
|
|
15185
|
+
}
|
|
15186
|
+
async function postOnce(event) {
|
|
15187
|
+
const token = await getToken();
|
|
15188
|
+
return fetchImpl(`${base}/host/diagnostic-command-event`, {
|
|
15189
|
+
method: "POST",
|
|
15190
|
+
headers: {
|
|
15191
|
+
Authorization: `Bearer ${token}`,
|
|
15192
|
+
"Content-Type": "application/json"
|
|
15193
|
+
},
|
|
15194
|
+
body: JSON.stringify({ agent_id: agentId, ...event }),
|
|
15195
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
|
|
15196
|
+
});
|
|
15197
|
+
}
|
|
15198
|
+
async function emitAsync(event) {
|
|
15199
|
+
try {
|
|
15200
|
+
let resp = await postOnce(event);
|
|
15201
|
+
if (resp.status === 401) {
|
|
15202
|
+
cachedToken = null;
|
|
15203
|
+
cachedTokenExpiresAt = 0;
|
|
15204
|
+
resp = await postOnce(event);
|
|
15205
|
+
}
|
|
15206
|
+
if (!resp.ok) {
|
|
15207
|
+
const body = await resp.text().catch(() => "");
|
|
15208
|
+
log(
|
|
15209
|
+
`diagnostic-event: POST failed (${resp.status}) outcome=${event.outcome}: ${body.slice(0, 200)}`
|
|
15210
|
+
);
|
|
15211
|
+
}
|
|
15212
|
+
} catch (err) {
|
|
15213
|
+
log(`diagnostic-event: emit threw outcome=${event.outcome}: ${err.message}`);
|
|
15214
|
+
}
|
|
15215
|
+
}
|
|
15216
|
+
return {
|
|
15217
|
+
emit(event) {
|
|
15218
|
+
void emitAsync(event);
|
|
15219
|
+
}
|
|
15220
|
+
};
|
|
15221
|
+
}
|
|
15222
|
+
|
|
15223
|
+
// src/telegram-investigate.ts
|
|
15224
|
+
var INVESTIGATE_SYNTAX_RE = /^\/investigate(?:[-_]([A-Za-z0-9_-]{1,64}))?(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
|
|
15225
|
+
function isInvestigateSyntax(text) {
|
|
15226
|
+
return INVESTIGATE_SYNTAX_RE.test(text);
|
|
15227
|
+
}
|
|
15228
|
+
function classifyInvestigateText(text, opts) {
|
|
15229
|
+
const m = INVESTIGATE_SYNTAX_RE.exec(text);
|
|
15230
|
+
if (!m) return "ignore";
|
|
15231
|
+
const codeSuffix = m[1];
|
|
15232
|
+
if (codeSuffix) {
|
|
15233
|
+
const normalise = (s) => s.toLowerCase().replace(/_/g, "-");
|
|
15234
|
+
if (normalise(codeSuffix) !== normalise(opts.codeName)) return "ignore";
|
|
15235
|
+
}
|
|
15236
|
+
const botSuffix = m[2]?.toLowerCase();
|
|
15237
|
+
if (!botSuffix) return "act";
|
|
15238
|
+
if (!opts.botUsername) return "verification_failed";
|
|
15239
|
+
return botSuffix === opts.botUsername.toLowerCase() ? "act" : "ignore";
|
|
15240
|
+
}
|
|
15241
|
+
function evaluateInvestigateGate(opts) {
|
|
15242
|
+
if (opts.chatType !== "private") return { ok: false, reason: "not-dm" };
|
|
15243
|
+
if (opts.diagnosticChatIds.size === 0) return { ok: false, reason: "allowlist-empty" };
|
|
15244
|
+
if (!opts.senderId || !opts.diagnosticChatIds.has(opts.senderId)) {
|
|
15245
|
+
return { ok: false, reason: "not-allowed" };
|
|
15246
|
+
}
|
|
15247
|
+
return { ok: true };
|
|
15248
|
+
}
|
|
15249
|
+
function createSingleTailSlot() {
|
|
15250
|
+
let expiresAtMs = null;
|
|
15251
|
+
return {
|
|
15252
|
+
reserve(windowMs, nowMs = Date.now()) {
|
|
15253
|
+
if (expiresAtMs !== null && expiresAtMs > nowMs) return null;
|
|
15254
|
+
expiresAtMs = nowMs + windowMs;
|
|
15255
|
+
return expiresAtMs;
|
|
15256
|
+
},
|
|
15257
|
+
remainingMs(nowMs = Date.now()) {
|
|
15258
|
+
if (expiresAtMs === null) return 0;
|
|
15259
|
+
return Math.max(0, expiresAtMs - nowMs);
|
|
15260
|
+
},
|
|
15261
|
+
release() {
|
|
15262
|
+
expiresAtMs = null;
|
|
15263
|
+
}
|
|
15264
|
+
};
|
|
15265
|
+
}
|
|
15266
|
+
|
|
15267
|
+
// src/pane-tail.ts
|
|
15268
|
+
import { execFile } from "child_process";
|
|
15269
|
+
import { promisify } from "util";
|
|
15270
|
+
import { open, stat } from "fs/promises";
|
|
15271
|
+
|
|
15272
|
+
// src/session-probe-runtime.ts
|
|
15273
|
+
import { execFileSync } from "child_process";
|
|
15274
|
+
function agentTmuxSessionName(codeName) {
|
|
15275
|
+
return `agt-${codeName}`;
|
|
15276
|
+
}
|
|
15277
|
+
function escapePgrepRegex(value) {
|
|
15278
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15279
|
+
}
|
|
15280
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15281
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15282
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15283
|
+
try {
|
|
15284
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15285
|
+
encoding: "utf-8",
|
|
15286
|
+
timeout: 3e3
|
|
15287
|
+
}).trim();
|
|
15288
|
+
return out.length > 0 ? "alive" : "dead";
|
|
15289
|
+
} catch (err) {
|
|
15290
|
+
const e = err;
|
|
15291
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15292
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
15293
|
+
}
|
|
15294
|
+
}
|
|
15295
|
+
function probeTmuxSession(tmuxSession) {
|
|
15296
|
+
try {
|
|
15297
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15298
|
+
stdio: "ignore",
|
|
15299
|
+
timeout: 3e3
|
|
15300
|
+
});
|
|
15301
|
+
return "alive";
|
|
15302
|
+
} catch (err) {
|
|
15303
|
+
const e = err;
|
|
15304
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15305
|
+
return "dead";
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15308
|
+
function probeAgentSession(codeName) {
|
|
15309
|
+
const session = agentTmuxSessionName(codeName);
|
|
15310
|
+
const tmux = probeTmuxSession(session);
|
|
15311
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15312
|
+
return { tmux, claude };
|
|
15313
|
+
}
|
|
15314
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
15315
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15316
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15317
|
+
const cached2 = probeCache.get(codeName);
|
|
15318
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15319
|
+
const value = probeAgentSession(codeName);
|
|
15320
|
+
probeCache.set(codeName, { at: now, value });
|
|
15321
|
+
return value;
|
|
15322
|
+
}
|
|
15323
|
+
|
|
15324
|
+
// src/pane-tail.ts
|
|
15325
|
+
var execFileAsync = promisify(execFile);
|
|
15326
|
+
var PANE_LOG_TAIL_BYTES = 64 * 1024;
|
|
15327
|
+
var TMUX_CAPTURE_TIMEOUT_MS = 2e3;
|
|
15328
|
+
async function capturePaneSnapshot(opts) {
|
|
15329
|
+
const scrollback = opts.scrollbackLines ?? 200;
|
|
15330
|
+
try {
|
|
15331
|
+
const { stdout } = await execFileAsync(
|
|
15332
|
+
"tmux",
|
|
15333
|
+
["capture-pane", "-p", "-t", agentTmuxSessionName(opts.codeName), "-S", `-${scrollback}`],
|
|
15334
|
+
{ timeout: TMUX_CAPTURE_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
|
|
15335
|
+
);
|
|
15336
|
+
return { source: "tmux", text: stdout };
|
|
15337
|
+
} catch {
|
|
15338
|
+
}
|
|
15339
|
+
if (!opts.agentDir) return null;
|
|
15340
|
+
const paneLogPath = `${opts.agentDir}/pane.log`;
|
|
15341
|
+
try {
|
|
15342
|
+
const tail = await readFileTail(paneLogPath, PANE_LOG_TAIL_BYTES);
|
|
15343
|
+
if (tail === null) return null;
|
|
15344
|
+
return { source: "pane-log", text: stripAnsi(tail) };
|
|
15345
|
+
} catch {
|
|
15346
|
+
return null;
|
|
15347
|
+
}
|
|
15348
|
+
}
|
|
15349
|
+
async function readFileTail(path, maxBytes) {
|
|
15350
|
+
const st = await stat(path);
|
|
15351
|
+
if (!st.isFile()) return null;
|
|
15352
|
+
const start = Math.max(0, st.size - maxBytes);
|
|
15353
|
+
const length = st.size - start;
|
|
15354
|
+
if (length === 0) return "";
|
|
15355
|
+
const handle = await open(path, "r");
|
|
15356
|
+
try {
|
|
15357
|
+
const buf = Buffer.alloc(length);
|
|
15358
|
+
await handle.read(buf, 0, length, start);
|
|
15359
|
+
let text = buf.toString("utf8");
|
|
15360
|
+
if (start > 0) {
|
|
15361
|
+
const nl = text.indexOf("\n");
|
|
15362
|
+
if (nl !== -1) text = text.slice(nl + 1);
|
|
15363
|
+
}
|
|
15364
|
+
return text;
|
|
15365
|
+
} finally {
|
|
15366
|
+
await handle.close();
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15369
|
+
function stripAnsi(text) {
|
|
15370
|
+
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;?<=>!]*[ -\/]*[@-~]/g, "").replace(/\x1b./g, "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
15371
|
+
}
|
|
15372
|
+
var SECRET_PATTERNS = [
|
|
15373
|
+
// Augmented host API keys
|
|
15374
|
+
{ re: /tlk_[A-Za-z0-9]{8,}/g, label: "agt-api-key" },
|
|
15375
|
+
// Slack tokens: bot/app/user/refresh/config
|
|
15376
|
+
{ re: /x(?:ox[abprs]|app)-[A-Za-z0-9-]{8,}/g, label: "slack-token" },
|
|
15377
|
+
// JWTs (three base64url segments, first one always starts with eyJ)
|
|
15378
|
+
{ re: /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, label: "jwt" },
|
|
15379
|
+
// OpenAI / Anthropic-style keys (sk-..., sk-ant-...)
|
|
15380
|
+
{ re: /sk-[A-Za-z0-9_-]{16,}/g, label: "sk-key" },
|
|
15381
|
+
// AWS access key IDs
|
|
15382
|
+
{ re: /(?:AKIA|ASIA)[0-9A-Z]{16}/g, label: "aws-key" },
|
|
15383
|
+
// GitHub tokens
|
|
15384
|
+
{ re: /gh[pousr]_[A-Za-z0-9]{20,}/g, label: "github-token" }
|
|
15385
|
+
];
|
|
15386
|
+
function redactPaneText(text) {
|
|
15387
|
+
let out = redactAugmentedPaths(text);
|
|
15388
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
15389
|
+
out = out.replace(re, `<redacted:${label}>`);
|
|
15390
|
+
}
|
|
15391
|
+
return out;
|
|
15392
|
+
}
|
|
15393
|
+
var DEFAULT_MAX_CHARS = 3900;
|
|
15394
|
+
var REFLOW_COLS = 80;
|
|
15395
|
+
function formatPaneSnapshot(opts) {
|
|
15396
|
+
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
15397
|
+
const commandName = opts.commandName ?? "/debug";
|
|
15398
|
+
const header = opts.status.kind === "live" ? `\u{1F50E} Live pane tail for \`${opts.codeName}\` \u2014 captured ${opts.capturedAtLabel} \xB7 updates ~3s \xB7 expires in ${formatCountdown(opts.status.secondsRemaining)}` : `\u{1F50E} Pane tail for \`${opts.codeName}\` \u2014 captured ${opts.capturedAtLabel} \xB7 *expired* \u2014 run \`${commandName}\` to restart`;
|
|
15399
|
+
const fenceOverhead = header.length + "\n```\n".length + "\n```".length + 16;
|
|
15400
|
+
const contentBudget = Math.max(0, maxChars - fenceOverhead);
|
|
15401
|
+
const lines = reflowLines(sanitizeForCodeBlock(opts.text), REFLOW_COLS);
|
|
15402
|
+
const kept = [];
|
|
15403
|
+
let used = 0;
|
|
15404
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
15405
|
+
const cost = lines[i].length + 1;
|
|
15406
|
+
if (used + cost > contentBudget) break;
|
|
15407
|
+
kept.unshift(lines[i]);
|
|
15408
|
+
used += cost;
|
|
15409
|
+
}
|
|
15410
|
+
const truncated = kept.length < lines.length;
|
|
15411
|
+
const body = kept.join("\n").trimEnd();
|
|
15412
|
+
const block = body.length > 0 ? `\`\`\`
|
|
15413
|
+
${truncated ? "\u2026\n" : ""}${body}
|
|
15414
|
+
\`\`\`` : "_(pane is empty)_";
|
|
15415
|
+
return `${header}
|
|
15416
|
+
${block}`;
|
|
15417
|
+
}
|
|
15418
|
+
function formatCountdown(totalSeconds) {
|
|
15419
|
+
const s = Math.max(0, Math.floor(totalSeconds));
|
|
15420
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
15421
|
+
}
|
|
15422
|
+
function sanitizeForCodeBlock(text) {
|
|
15423
|
+
return text.replace(/```/g, "`\u200B`\u200B`");
|
|
15424
|
+
}
|
|
15425
|
+
function reflowLines(text, cols) {
|
|
15426
|
+
const out = [];
|
|
15427
|
+
for (const rawLine of text.split("\n")) {
|
|
15428
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
15429
|
+
if (line.length <= cols) {
|
|
15430
|
+
out.push(line);
|
|
15431
|
+
continue;
|
|
15432
|
+
}
|
|
15433
|
+
for (let i = 0; i < line.length; i += cols) {
|
|
15434
|
+
out.push(line.slice(i, i + cols));
|
|
15435
|
+
}
|
|
15436
|
+
}
|
|
15437
|
+
const collapsed = [];
|
|
15438
|
+
for (const line of out) {
|
|
15439
|
+
if (line === "" && collapsed[collapsed.length - 1] === "") continue;
|
|
15440
|
+
collapsed.push(line);
|
|
15441
|
+
}
|
|
15442
|
+
return collapsed;
|
|
15443
|
+
}
|
|
15444
|
+
|
|
15445
|
+
// src/observed-chat-client.ts
|
|
15446
|
+
var REQUEST_TIMEOUT_MS3 = 1e4;
|
|
15149
15447
|
function createObservedChatClient(args) {
|
|
15150
15448
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15151
15449
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15163,7 +15461,7 @@ function createObservedChatClient(args) {
|
|
|
15163
15461
|
method: "POST",
|
|
15164
15462
|
headers: { "Content-Type": "application/json" },
|
|
15165
15463
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15166
|
-
signal: AbortSignal.timeout(
|
|
15464
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
|
|
15167
15465
|
});
|
|
15168
15466
|
if (!resp.ok) {
|
|
15169
15467
|
const body = await resp.text().catch(() => "");
|
|
@@ -15180,7 +15478,7 @@ function createObservedChatClient(args) {
|
|
|
15180
15478
|
method: "POST",
|
|
15181
15479
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15182
15480
|
body: JSON.stringify({ agent_id: agentId, ...chat }),
|
|
15183
|
-
signal: AbortSignal.timeout(
|
|
15481
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
|
|
15184
15482
|
});
|
|
15185
15483
|
}
|
|
15186
15484
|
async function emitAsync(chat) {
|
|
@@ -15227,7 +15525,7 @@ function createObservedChatClient(args) {
|
|
|
15227
15525
|
}
|
|
15228
15526
|
|
|
15229
15527
|
// src/conversation-ingest-client.ts
|
|
15230
|
-
var
|
|
15528
|
+
var REQUEST_TIMEOUT_MS4 = 1e4;
|
|
15231
15529
|
function createConversationIngestClient(args) {
|
|
15232
15530
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15233
15531
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15244,7 +15542,7 @@ function createConversationIngestClient(args) {
|
|
|
15244
15542
|
method: "POST",
|
|
15245
15543
|
headers: { "Content-Type": "application/json" },
|
|
15246
15544
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15247
|
-
signal: AbortSignal.timeout(
|
|
15545
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS4)
|
|
15248
15546
|
});
|
|
15249
15547
|
if (!resp.ok) {
|
|
15250
15548
|
const body = await resp.text().catch(() => "");
|
|
@@ -15267,7 +15565,7 @@ function createConversationIngestClient(args) {
|
|
|
15267
15565
|
method: "POST",
|
|
15268
15566
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15269
15567
|
body: JSON.stringify({ agent_id: agentId, ...payload }),
|
|
15270
|
-
signal: AbortSignal.timeout(
|
|
15568
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS4)
|
|
15271
15569
|
});
|
|
15272
15570
|
}
|
|
15273
15571
|
function refKey(payload) {
|
|
@@ -15299,7 +15597,7 @@ function createConversationIngestClient(args) {
|
|
|
15299
15597
|
}
|
|
15300
15598
|
|
|
15301
15599
|
// src/inbound-context-client.ts
|
|
15302
|
-
var
|
|
15600
|
+
var REQUEST_TIMEOUT_MS5 = 1e4;
|
|
15303
15601
|
function createInboundContextClient(args) {
|
|
15304
15602
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15305
15603
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15316,7 +15614,7 @@ function createInboundContextClient(args) {
|
|
|
15316
15614
|
method: "POST",
|
|
15317
15615
|
headers: { "Content-Type": "application/json" },
|
|
15318
15616
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15319
|
-
signal: AbortSignal.timeout(
|
|
15617
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS5)
|
|
15320
15618
|
});
|
|
15321
15619
|
if (!resp.ok) {
|
|
15322
15620
|
const body = await resp.text().catch(() => "");
|
|
@@ -15333,7 +15631,7 @@ function createInboundContextClient(args) {
|
|
|
15333
15631
|
method: "POST",
|
|
15334
15632
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15335
15633
|
body: JSON.stringify(body),
|
|
15336
|
-
signal: AbortSignal.timeout(
|
|
15634
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS5)
|
|
15337
15635
|
});
|
|
15338
15636
|
}
|
|
15339
15637
|
async function emit(args2) {
|
|
@@ -15569,58 +15867,6 @@ function shouldReplayMarker(i) {
|
|
|
15569
15867
|
return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
|
|
15570
15868
|
}
|
|
15571
15869
|
|
|
15572
|
-
// src/session-probe-runtime.ts
|
|
15573
|
-
import { execFileSync } from "child_process";
|
|
15574
|
-
function agentTmuxSessionName(codeName) {
|
|
15575
|
-
return `agt-${codeName}`;
|
|
15576
|
-
}
|
|
15577
|
-
function escapePgrepRegex(value) {
|
|
15578
|
-
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15579
|
-
}
|
|
15580
|
-
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15581
|
-
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15582
|
-
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15583
|
-
try {
|
|
15584
|
-
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15585
|
-
encoding: "utf-8",
|
|
15586
|
-
timeout: 3e3
|
|
15587
|
-
}).trim();
|
|
15588
|
-
return out.length > 0 ? "alive" : "dead";
|
|
15589
|
-
} catch (err) {
|
|
15590
|
-
const e = err;
|
|
15591
|
-
if (e?.code === "ENOENT") return "unknown";
|
|
15592
|
-
return e?.status === 1 ? "dead" : "unknown";
|
|
15593
|
-
}
|
|
15594
|
-
}
|
|
15595
|
-
function probeTmuxSession(tmuxSession) {
|
|
15596
|
-
try {
|
|
15597
|
-
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15598
|
-
stdio: "ignore",
|
|
15599
|
-
timeout: 3e3
|
|
15600
|
-
});
|
|
15601
|
-
return "alive";
|
|
15602
|
-
} catch (err) {
|
|
15603
|
-
const e = err;
|
|
15604
|
-
if (e?.code === "ENOENT") return "unknown";
|
|
15605
|
-
return "dead";
|
|
15606
|
-
}
|
|
15607
|
-
}
|
|
15608
|
-
function probeAgentSession(codeName) {
|
|
15609
|
-
const session = agentTmuxSessionName(codeName);
|
|
15610
|
-
const tmux = probeTmuxSession(session);
|
|
15611
|
-
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15612
|
-
return { tmux, claude };
|
|
15613
|
-
}
|
|
15614
|
-
var probeCache = /* @__PURE__ */ new Map();
|
|
15615
|
-
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15616
|
-
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15617
|
-
const cached2 = probeCache.get(codeName);
|
|
15618
|
-
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15619
|
-
const value = probeAgentSession(codeName);
|
|
15620
|
-
probeCache.set(codeName, { at: now, value });
|
|
15621
|
-
return value;
|
|
15622
|
-
}
|
|
15623
|
-
|
|
15624
15870
|
// src/telegram-channel.ts
|
|
15625
15871
|
function redactId(id) {
|
|
15626
15872
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
@@ -15634,6 +15880,9 @@ var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
|
15634
15880
|
var ALLOWED_CHATS = new Set(
|
|
15635
15881
|
(process.env.TELEGRAM_ALLOWED_CHATS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15636
15882
|
);
|
|
15883
|
+
var DIAGNOSTIC_CHAT_IDS = new Set(
|
|
15884
|
+
(process.env.TELEGRAM_DIAGNOSTIC_CHAT_IDS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15885
|
+
);
|
|
15637
15886
|
var PEER_CLASSIFIER_CONFIG = {
|
|
15638
15887
|
peer_agent_mode: parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE),
|
|
15639
15888
|
peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
|
|
@@ -15669,6 +15918,13 @@ var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
|
|
|
15669
15918
|
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
15670
15919
|
`)
|
|
15671
15920
|
});
|
|
15921
|
+
var diagnosticEventClient = createDiagnosticEventClient({
|
|
15922
|
+
agtHost: AGT_HOST,
|
|
15923
|
+
agtApiKey: AGT_API_KEY,
|
|
15924
|
+
agentId: process.env.AGT_AGENT_ID ?? null,
|
|
15925
|
+
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
15926
|
+
`)
|
|
15927
|
+
});
|
|
15672
15928
|
var observedChatClient = createObservedChatClient({
|
|
15673
15929
|
agtHost: AGT_HOST,
|
|
15674
15930
|
agtApiKey: AGT_API_KEY,
|
|
@@ -15868,7 +16124,8 @@ function buildTelegramHelpMessage(codeName) {
|
|
|
15868
16124
|
"",
|
|
15869
16125
|
`_Type these in any chat where the bot is present (intercepted by the agent):_`,
|
|
15870
16126
|
`\u2022 /help \u2014 show this help`,
|
|
15871
|
-
`\u2022 /restart \u2014 restart this agent
|
|
16127
|
+
`\u2022 /restart \u2014 restart this agent`,
|
|
16128
|
+
`\u2022 /investigate-${codeName} \u2014 live tail of this agent's terminal pane (DM only; team owners/admins and the agent's reports-to person)`
|
|
15872
16129
|
].join("\n");
|
|
15873
16130
|
}
|
|
15874
16131
|
async function handleHelpCommand(opts) {
|
|
@@ -15963,6 +16220,261 @@ async function handleRestartCommand(opts) {
|
|
|
15963
16220
|
}
|
|
15964
16221
|
}
|
|
15965
16222
|
}
|
|
16223
|
+
var INVESTIGATE_TAIL_WINDOW_MS = 12e4;
|
|
16224
|
+
var INVESTIGATE_TAIL_INTERVAL_MS = 3e3;
|
|
16225
|
+
var INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES = 4;
|
|
16226
|
+
var INVESTIGATE_MAX_CHARS = 3800;
|
|
16227
|
+
var investigateTailSlot = createSingleTailSlot();
|
|
16228
|
+
function investigateAuditLog(line) {
|
|
16229
|
+
process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): /investigate ${line}
|
|
16230
|
+
`);
|
|
16231
|
+
}
|
|
16232
|
+
function investigateNowUtcLabel() {
|
|
16233
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19)} UTC`;
|
|
16234
|
+
}
|
|
16235
|
+
function investigateSleep(ms) {
|
|
16236
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
16237
|
+
}
|
|
16238
|
+
async function classifyInvestigateCommand(text) {
|
|
16239
|
+
const needsUsername = /@[A-Za-z0-9_]{1,64}(?:\s|$)/.test(text);
|
|
16240
|
+
const ours = needsUsername ? await resolveBotUsername() : null;
|
|
16241
|
+
return classifyInvestigateText(text, { codeName: AGENT_CODE_NAME, botUsername: ours });
|
|
16242
|
+
}
|
|
16243
|
+
async function sendInvestigateReply(chatId, messageId, text) {
|
|
16244
|
+
try {
|
|
16245
|
+
const resp = await telegramApiCall(
|
|
16246
|
+
"sendMessage",
|
|
16247
|
+
{
|
|
16248
|
+
chat_id: chatId,
|
|
16249
|
+
text,
|
|
16250
|
+
parse_mode: "Markdown",
|
|
16251
|
+
reply_to_message_id: Number(messageId)
|
|
16252
|
+
},
|
|
16253
|
+
1e4
|
|
16254
|
+
);
|
|
16255
|
+
if (!resp.ok) {
|
|
16256
|
+
investigateAuditLog(`reply rejected by Telegram: ${resp.description ?? "unknown"}`);
|
|
16257
|
+
}
|
|
16258
|
+
} catch (err) {
|
|
16259
|
+
investigateAuditLog(`reply send failed: ${redactAugmentedPaths(err.message)}`);
|
|
16260
|
+
}
|
|
16261
|
+
}
|
|
16262
|
+
async function runInvestigateTailLoop(opts) {
|
|
16263
|
+
const investigateCmd = `/investigate-${AGENT_CODE_NAME}`;
|
|
16264
|
+
let last = opts.initialFrame;
|
|
16265
|
+
let consecutiveFailures = 0;
|
|
16266
|
+
let delayMs = INVESTIGATE_TAIL_INTERVAL_MS;
|
|
16267
|
+
try {
|
|
16268
|
+
while (Date.now() + delayMs < opts.expiresAtMs) {
|
|
16269
|
+
await investigateSleep(delayMs);
|
|
16270
|
+
delayMs = INVESTIGATE_TAIL_INTERVAL_MS;
|
|
16271
|
+
const snapshot = await capturePaneSnapshot({
|
|
16272
|
+
codeName: AGENT_CODE_NAME,
|
|
16273
|
+
agentDir: TELEGRAM_AGENT_DIR
|
|
16274
|
+
});
|
|
16275
|
+
if (!snapshot) {
|
|
16276
|
+
consecutiveFailures++;
|
|
16277
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16278
|
+
investigateAuditLog(`tail aborted after ${consecutiveFailures} consecutive capture failures`);
|
|
16279
|
+
break;
|
|
16280
|
+
}
|
|
16281
|
+
continue;
|
|
16282
|
+
}
|
|
16283
|
+
const frame = {
|
|
16284
|
+
body: redactPaneText(snapshot.text),
|
|
16285
|
+
source: snapshot.source,
|
|
16286
|
+
capturedAtLabel: investigateNowUtcLabel()
|
|
16287
|
+
};
|
|
16288
|
+
if (frame.body === last.body) continue;
|
|
16289
|
+
const text = formatPaneSnapshot({
|
|
16290
|
+
codeName: AGENT_CODE_NAME,
|
|
16291
|
+
text: frame.body,
|
|
16292
|
+
source: frame.source,
|
|
16293
|
+
capturedAtLabel: frame.capturedAtLabel,
|
|
16294
|
+
status: {
|
|
16295
|
+
kind: "live",
|
|
16296
|
+
secondsRemaining: Math.max(0, Math.round((opts.expiresAtMs - Date.now()) / 1e3))
|
|
16297
|
+
},
|
|
16298
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16299
|
+
commandName: investigateCmd
|
|
16300
|
+
});
|
|
16301
|
+
try {
|
|
16302
|
+
const resp = await telegramApiCall(
|
|
16303
|
+
"editMessageText",
|
|
16304
|
+
{
|
|
16305
|
+
chat_id: opts.chatId,
|
|
16306
|
+
message_id: opts.messageId,
|
|
16307
|
+
text,
|
|
16308
|
+
parse_mode: "Markdown"
|
|
16309
|
+
},
|
|
16310
|
+
1e4
|
|
16311
|
+
);
|
|
16312
|
+
if (resp.ok) {
|
|
16313
|
+
last = frame;
|
|
16314
|
+
consecutiveFailures = 0;
|
|
16315
|
+
continue;
|
|
16316
|
+
}
|
|
16317
|
+
const retryAfter = resp.parameters?.retry_after;
|
|
16318
|
+
if (typeof retryAfter === "number" && retryAfter > 0) {
|
|
16319
|
+
delayMs = Math.max(INVESTIGATE_TAIL_INTERVAL_MS, retryAfter * 1e3);
|
|
16320
|
+
continue;
|
|
16321
|
+
}
|
|
16322
|
+
consecutiveFailures++;
|
|
16323
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16324
|
+
investigateAuditLog(
|
|
16325
|
+
`tail aborted after ${consecutiveFailures} consecutive update failures (last: ${resp.description ?? "unknown"})`
|
|
16326
|
+
);
|
|
16327
|
+
break;
|
|
16328
|
+
}
|
|
16329
|
+
} catch (err) {
|
|
16330
|
+
consecutiveFailures++;
|
|
16331
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16332
|
+
investigateAuditLog(
|
|
16333
|
+
`tail aborted after ${consecutiveFailures} consecutive update failures (last: ${redactAugmentedPaths(err.message)})`
|
|
16334
|
+
);
|
|
16335
|
+
break;
|
|
16336
|
+
}
|
|
16337
|
+
}
|
|
16338
|
+
}
|
|
16339
|
+
} finally {
|
|
16340
|
+
investigateTailSlot.release();
|
|
16341
|
+
try {
|
|
16342
|
+
await telegramApiCall(
|
|
16343
|
+
"editMessageText",
|
|
16344
|
+
{
|
|
16345
|
+
chat_id: opts.chatId,
|
|
16346
|
+
message_id: opts.messageId,
|
|
16347
|
+
text: formatPaneSnapshot({
|
|
16348
|
+
codeName: AGENT_CODE_NAME,
|
|
16349
|
+
text: last.body,
|
|
16350
|
+
source: last.source,
|
|
16351
|
+
capturedAtLabel: last.capturedAtLabel,
|
|
16352
|
+
status: { kind: "expired" },
|
|
16353
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16354
|
+
commandName: investigateCmd
|
|
16355
|
+
}),
|
|
16356
|
+
parse_mode: "Markdown"
|
|
16357
|
+
},
|
|
16358
|
+
1e4
|
|
16359
|
+
);
|
|
16360
|
+
} catch (err) {
|
|
16361
|
+
investigateAuditLog(`expired-edit failed: ${redactAugmentedPaths(err.message)}`);
|
|
16362
|
+
}
|
|
16363
|
+
}
|
|
16364
|
+
}
|
|
16365
|
+
async function handleInvestigateCommand(opts) {
|
|
16366
|
+
const investigateCmd = `/investigate-${AGENT_CODE_NAME}`;
|
|
16367
|
+
const emitAudit = (outcome, reason) => {
|
|
16368
|
+
diagnosticEventClient?.emit({
|
|
16369
|
+
channel: "telegram",
|
|
16370
|
+
command: "investigate",
|
|
16371
|
+
outcome,
|
|
16372
|
+
reason,
|
|
16373
|
+
chat_id: opts.chatId,
|
|
16374
|
+
sender_chat_id: opts.senderId
|
|
16375
|
+
});
|
|
16376
|
+
};
|
|
16377
|
+
const verdict = evaluateInvestigateGate({
|
|
16378
|
+
chatType: opts.chatType,
|
|
16379
|
+
senderId: opts.senderId,
|
|
16380
|
+
diagnosticChatIds: DIAGNOSTIC_CHAT_IDS
|
|
16381
|
+
});
|
|
16382
|
+
if (!verdict.ok) {
|
|
16383
|
+
investigateAuditLog(
|
|
16384
|
+
`denied reason=${verdict.reason} sender=${opts.senderId ? redactId(opts.senderId) : "unknown"}`
|
|
16385
|
+
);
|
|
16386
|
+
emitAudit("denied", verdict.reason);
|
|
16387
|
+
const denialText = verdict.reason === "not-dm" ? `\u26A0\uFE0F \`${investigateCmd}\` only works in a direct message with \`${AGENT_CODE_NAME}\` \u2014 it shows the agent's raw terminal, so it stays 1:1. Open a DM with the bot and run it there.` : verdict.reason === "allowlist-empty" ? `\u{1F6AB} \`${investigateCmd}\` is disabled for \`${AGENT_CODE_NAME}\` \u2014 no diagnostic allowlist reached this host. It is derived from team owners/admins and the agent's reports-to person; if that's you, link your Telegram in your Augmented Team contact preferences and wait for the next refresh.` : `\u{1F6AB} \`${investigateCmd}\` denied \u2014 it's limited to team owners/admins and the person this agent reports to. If that's you, link your Telegram in your Augmented Team contact preferences and wait for the next refresh.`;
|
|
16388
|
+
await sendInvestigateReply(opts.chatId, opts.messageId, denialText);
|
|
16389
|
+
return;
|
|
16390
|
+
}
|
|
16391
|
+
const reservedExpiresAtMs = investigateTailSlot.reserve(INVESTIGATE_TAIL_WINDOW_MS);
|
|
16392
|
+
if (reservedExpiresAtMs === null) {
|
|
16393
|
+
const remaining = formatCountdown(investigateTailSlot.remainingMs() / 1e3);
|
|
16394
|
+
investigateAuditLog(
|
|
16395
|
+
`not-started reason=tail-already-running sender=${redactId(opts.senderId ?? "unknown")} remaining=${remaining}`
|
|
16396
|
+
);
|
|
16397
|
+
emitAudit("not_started", "tail-already-running");
|
|
16398
|
+
await sendInvestigateReply(
|
|
16399
|
+
opts.chatId,
|
|
16400
|
+
opts.messageId,
|
|
16401
|
+
`\u23F3 A \`${investigateCmd}\` tail is already running for \`${AGENT_CODE_NAME}\` (one at a time) \u2014 it expires in ${remaining}.`
|
|
16402
|
+
);
|
|
16403
|
+
return;
|
|
16404
|
+
}
|
|
16405
|
+
const snapshot = await capturePaneSnapshot({
|
|
16406
|
+
codeName: AGENT_CODE_NAME,
|
|
16407
|
+
agentDir: TELEGRAM_AGENT_DIR
|
|
16408
|
+
});
|
|
16409
|
+
if (!snapshot) {
|
|
16410
|
+
investigateTailSlot.release();
|
|
16411
|
+
investigateAuditLog(`not-started reason=no-pane-output sender=${redactId(opts.senderId ?? "unknown")}`);
|
|
16412
|
+
emitAudit("not_started", "no-pane-output");
|
|
16413
|
+
await sendInvestigateReply(
|
|
16414
|
+
opts.chatId,
|
|
16415
|
+
opts.messageId,
|
|
16416
|
+
`\u26A0\uFE0F No pane output available for \`${AGENT_CODE_NAME}\` yet \u2014 the agent session may not have started. (If the whole host is wedged, \`${investigateCmd}\` can't reach it either \u2014 fall back to the SSM diagnostics runbook.)`
|
|
16417
|
+
);
|
|
16418
|
+
return;
|
|
16419
|
+
}
|
|
16420
|
+
investigateAuditLog(`granted sender=${redactId(opts.senderId ?? "unknown")} \u2014 starting live tail (source=${snapshot.source})`);
|
|
16421
|
+
emitAudit("granted", null);
|
|
16422
|
+
const initialFrame = {
|
|
16423
|
+
body: redactPaneText(snapshot.text),
|
|
16424
|
+
source: snapshot.source,
|
|
16425
|
+
capturedAtLabel: investigateNowUtcLabel()
|
|
16426
|
+
};
|
|
16427
|
+
let postedMessageId = null;
|
|
16428
|
+
try {
|
|
16429
|
+
const posted = await telegramApiCall(
|
|
16430
|
+
"sendMessage",
|
|
16431
|
+
{
|
|
16432
|
+
chat_id: opts.chatId,
|
|
16433
|
+
text: formatPaneSnapshot({
|
|
16434
|
+
codeName: AGENT_CODE_NAME,
|
|
16435
|
+
text: initialFrame.body,
|
|
16436
|
+
source: initialFrame.source,
|
|
16437
|
+
capturedAtLabel: initialFrame.capturedAtLabel,
|
|
16438
|
+
status: {
|
|
16439
|
+
kind: "live",
|
|
16440
|
+
secondsRemaining: Math.max(0, Math.round((reservedExpiresAtMs - Date.now()) / 1e3))
|
|
16441
|
+
},
|
|
16442
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16443
|
+
commandName: investigateCmd
|
|
16444
|
+
}),
|
|
16445
|
+
parse_mode: "Markdown",
|
|
16446
|
+
reply_to_message_id: Number(opts.messageId)
|
|
16447
|
+
},
|
|
16448
|
+
1e4
|
|
16449
|
+
);
|
|
16450
|
+
const result = posted.result;
|
|
16451
|
+
if (posted.ok && typeof result?.message_id === "number") {
|
|
16452
|
+
postedMessageId = result.message_id;
|
|
16453
|
+
} else {
|
|
16454
|
+
investigateAuditLog(`not-started reason=post-failed error=${posted.description ?? "unknown"}`);
|
|
16455
|
+
}
|
|
16456
|
+
} catch (err) {
|
|
16457
|
+
investigateAuditLog(`not-started reason=post-failed error=${redactAugmentedPaths(err.message)}`);
|
|
16458
|
+
}
|
|
16459
|
+
if (postedMessageId === null) {
|
|
16460
|
+
investigateTailSlot.release();
|
|
16461
|
+
await sendInvestigateReply(
|
|
16462
|
+
opts.chatId,
|
|
16463
|
+
opts.messageId,
|
|
16464
|
+
`\u274C Failed to post the pane snapshot. Try again in a moment.`
|
|
16465
|
+
);
|
|
16466
|
+
return;
|
|
16467
|
+
}
|
|
16468
|
+
void runInvestigateTailLoop({
|
|
16469
|
+
chatId: opts.chatId,
|
|
16470
|
+
messageId: postedMessageId,
|
|
16471
|
+
initialFrame,
|
|
16472
|
+
expiresAtMs: reservedExpiresAtMs
|
|
16473
|
+
}).catch((err) => {
|
|
16474
|
+
investigateTailSlot.release();
|
|
16475
|
+
investigateAuditLog(`tail loop crashed: ${redactAugmentedPaths(err.message)}`);
|
|
16476
|
+
});
|
|
16477
|
+
}
|
|
15966
16478
|
var cachedBotUsername = null;
|
|
15967
16479
|
var cachedBotId = null;
|
|
15968
16480
|
async function refreshBotIdentity() {
|
|
@@ -16301,6 +16813,54 @@ var orphanSweepTimer = setInterval(() => {
|
|
|
16301
16813
|
sweepTelegramStaleMarkers(sessionAlive ? channelOrphanMarkerMs() : STALE_MARKER_MS);
|
|
16302
16814
|
}, orphanSweepIntervalMs());
|
|
16303
16815
|
orphanSweepTimer.unref?.();
|
|
16816
|
+
var queuedReplyCounts = /* @__PURE__ */ new Map();
|
|
16817
|
+
function _resetQueuedReplyCountsForTests() {
|
|
16818
|
+
queuedReplyCounts.clear();
|
|
16819
|
+
}
|
|
16820
|
+
async function deliverQueuedReply(p) {
|
|
16821
|
+
const remaining = (queuedReplyCounts.get(p.chatId) ?? 1) - 1;
|
|
16822
|
+
if (remaining <= 0) queuedReplyCounts.delete(p.chatId);
|
|
16823
|
+
else queuedReplyCounts.set(p.chatId, remaining);
|
|
16824
|
+
try {
|
|
16825
|
+
const killed = await isThreadKilled({
|
|
16826
|
+
channelType: "telegram",
|
|
16827
|
+
channelId: p.chatId,
|
|
16828
|
+
threadTs: "",
|
|
16829
|
+
agtHost: AGT_HOST,
|
|
16830
|
+
agtApiKey: AGT_API_KEY
|
|
16831
|
+
});
|
|
16832
|
+
if (killed) {
|
|
16833
|
+
process.stderr.write(
|
|
16834
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_dropped reason=killed chat=${redactId(p.chatId)}
|
|
16835
|
+
`
|
|
16836
|
+
);
|
|
16837
|
+
return;
|
|
16838
|
+
}
|
|
16839
|
+
const body = { chat_id: p.chatId, text: p.text };
|
|
16840
|
+
if (p.replyToMessageId) body.reply_to_message_id = Number(p.replyToMessageId);
|
|
16841
|
+
const data = await telegramApiCall("sendMessage", body, 15e3);
|
|
16842
|
+
if (!data.ok) {
|
|
16843
|
+
process.stderr.write(
|
|
16844
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_failed chat=${redactId(p.chatId)} error=${data.description ?? "unknown"}
|
|
16845
|
+
`
|
|
16846
|
+
);
|
|
16847
|
+
return;
|
|
16848
|
+
}
|
|
16849
|
+
recordReply(p.chatId, "", Date.now(), p.throttleCfg);
|
|
16850
|
+
if (p.isReplyTool) {
|
|
16851
|
+
clearPendingMessage(p.chatId, p.replyToMessageId);
|
|
16852
|
+
}
|
|
16853
|
+
process.stderr.write(
|
|
16854
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_delivered chat=${redactId(p.chatId)}
|
|
16855
|
+
`
|
|
16856
|
+
);
|
|
16857
|
+
} catch (err) {
|
|
16858
|
+
process.stderr.write(
|
|
16859
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_failed chat=${redactId(p.chatId)} error=${err.message}
|
|
16860
|
+
`
|
|
16861
|
+
);
|
|
16862
|
+
}
|
|
16863
|
+
}
|
|
16304
16864
|
function clearPendingMessage(chatId, messageId) {
|
|
16305
16865
|
if (messageId) {
|
|
16306
16866
|
clearPendingInboundMarker(chatId, messageId);
|
|
@@ -16534,28 +17094,56 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16534
17094
|
isError: true
|
|
16535
17095
|
};
|
|
16536
17096
|
}
|
|
16537
|
-
const
|
|
17097
|
+
const isPrivateChat = !chat_id.startsWith("-");
|
|
17098
|
+
const tgThrottleCfg = isPrivateChat ? relaxedThrottleConfig(configFromEnv()) : configFromEnv();
|
|
16538
17099
|
const tgThrottleNow = Date.now();
|
|
16539
|
-
const
|
|
17100
|
+
const tgPlan = planReplyDelivery({
|
|
16540
17101
|
recentReplyTimestamps: getRecentReplies(chat_id, "", tgThrottleNow, tgThrottleCfg),
|
|
17102
|
+
queuedCount: queuedReplyCounts.get(chat_id) ?? 0,
|
|
16541
17103
|
now: tgThrottleNow,
|
|
16542
17104
|
config: tgThrottleCfg
|
|
16543
17105
|
});
|
|
16544
|
-
if (
|
|
17106
|
+
if (tgPlan.action === "refuse") {
|
|
16545
17107
|
process.stderr.write(
|
|
16546
|
-
`telegram-channel(${AGENT_CODE_NAME}): reply_throttled chat=${redactId(chat_id)} count=${
|
|
17108
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_throttled chat=${redactId(chat_id)} count=${tgPlan.recentCount} queued=${queuedReplyCounts.get(chat_id) ?? 0} window_ms=${tgThrottleCfg.windowMs} threshold=${tgThrottleCfg.threshold}
|
|
16547
17109
|
`
|
|
16548
17110
|
);
|
|
16549
17111
|
return {
|
|
16550
17112
|
content: [
|
|
16551
17113
|
{
|
|
16552
17114
|
type: "text",
|
|
16553
|
-
text: `Reply throttled: ${
|
|
17115
|
+
text: `Reply throttled: ${tgPlan.recentCount} replies from this agent to this chat within the last ${Math.round(tgThrottleCfg.windowMs / 1e3)}s (threshold ${tgThrottleCfg.threshold}) and the delivery queue is full. The chat is paused for this agent until a human posts in it. Stop attempting to reply.`
|
|
16554
17116
|
}
|
|
16555
17117
|
],
|
|
16556
17118
|
isError: true
|
|
16557
17119
|
};
|
|
16558
17120
|
}
|
|
17121
|
+
if (tgPlan.action === "queue" && tgPlan.deliverAtMs !== void 0) {
|
|
17122
|
+
const delayMs = Math.max(0, tgPlan.deliverAtMs - tgThrottleNow);
|
|
17123
|
+
queuedReplyCounts.set(chat_id, (queuedReplyCounts.get(chat_id) ?? 0) + 1);
|
|
17124
|
+
process.stderr.write(
|
|
17125
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queued chat=${redactId(chat_id)} delay_ms=${delayMs} count=${tgPlan.recentCount} threshold=${tgThrottleCfg.threshold}
|
|
17126
|
+
`
|
|
17127
|
+
);
|
|
17128
|
+
const timer = setTimeout(() => {
|
|
17129
|
+
void deliverQueuedReply({
|
|
17130
|
+
chatId: chat_id,
|
|
17131
|
+
text,
|
|
17132
|
+
replyToMessageId: reply_to_message_id,
|
|
17133
|
+
isReplyTool: name === "telegram.reply",
|
|
17134
|
+
throttleCfg: tgThrottleCfg
|
|
17135
|
+
});
|
|
17136
|
+
}, delayMs);
|
|
17137
|
+
timer.unref();
|
|
17138
|
+
return {
|
|
17139
|
+
content: [
|
|
17140
|
+
{
|
|
17141
|
+
type: "text",
|
|
17142
|
+
text: `Reply queued, not yet sent: the reply-rate window is full (${tgPlan.recentCount} recent sends, threshold ${tgThrottleCfg.threshold}). It will be delivered automatically in ~${Math.ceil(delayMs / 1e3)}s. Do not re-send this message; avoid further sends to this chat until it delivers.`
|
|
17143
|
+
}
|
|
17144
|
+
]
|
|
17145
|
+
};
|
|
17146
|
+
}
|
|
16559
17147
|
try {
|
|
16560
17148
|
const body = { chat_id, text };
|
|
16561
17149
|
if (reply_to_message_id) body.reply_to_message_id = Number(reply_to_message_id);
|
|
@@ -17118,6 +17706,35 @@ async function pollLoop() {
|
|
|
17118
17706
|
} catch (err) {
|
|
17119
17707
|
process.stderr.write(
|
|
17120
17708
|
`telegram-channel(${AGENT_CODE_NAME}): /restart verification-failed ack send failed: ${redactAugmentedPaths(err.message)}
|
|
17709
|
+
`
|
|
17710
|
+
);
|
|
17711
|
+
}
|
|
17712
|
+
}
|
|
17713
|
+
continue;
|
|
17714
|
+
}
|
|
17715
|
+
if (isInvestigateSyntax(trimmedContent)) {
|
|
17716
|
+
const disposition = await classifyInvestigateCommand(trimmedContent);
|
|
17717
|
+
if (disposition === "act") {
|
|
17718
|
+
await handleInvestigateCommand({
|
|
17719
|
+
chatId,
|
|
17720
|
+
messageId: String(msg.message_id),
|
|
17721
|
+
chatType: msg.chat.type ?? null,
|
|
17722
|
+
senderId: msg.from?.id != null ? String(msg.from.id) : null
|
|
17723
|
+
});
|
|
17724
|
+
} else if (disposition === "verification_failed") {
|
|
17725
|
+
try {
|
|
17726
|
+
await telegramApiCall(
|
|
17727
|
+
"sendMessage",
|
|
17728
|
+
{
|
|
17729
|
+
chat_id: chatId,
|
|
17730
|
+
text: `\u274C Couldn't verify the investigate target. Please retry in a few seconds.`,
|
|
17731
|
+
reply_to_message_id: Number(msg.message_id)
|
|
17732
|
+
},
|
|
17733
|
+
1e4
|
|
17734
|
+
);
|
|
17735
|
+
} catch (err) {
|
|
17736
|
+
process.stderr.write(
|
|
17737
|
+
`telegram-channel(${AGENT_CODE_NAME}): /investigate verification-failed ack send failed: ${redactAugmentedPaths(err.message)}
|
|
17121
17738
|
`
|
|
17122
17739
|
);
|
|
17123
17740
|
}
|
|
@@ -17384,5 +18001,6 @@ pollLoop().catch((err) => {
|
|
|
17384
18001
|
});
|
|
17385
18002
|
export {
|
|
17386
18003
|
__resetBackOnlineNoticeThrottle,
|
|
17387
|
-
__resetUndeliverableNoticeThrottle
|
|
18004
|
+
__resetUndeliverableNoticeThrottle,
|
|
18005
|
+
_resetQueuedReplyCountsForTests
|
|
17388
18006
|
};
|