@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
- function decideReplyThrottle(input) {
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
- if (inWindow.length >= cfg.threshold) {
14534
- return {
14535
- allow: false,
14536
- recentCount: inWindow.length,
14537
- reason: "throttle_threshold_exceeded"
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
- allow: true,
14545
+ action: "queue",
14546
+ deliverAtMs: Math.max(opensAt, input.now),
14542
14547
  recentCount: inWindow.length,
14543
- reason: "under_threshold"
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/observed-chat-client.ts
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(REQUEST_TIMEOUT_MS2)
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(REQUEST_TIMEOUT_MS2)
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 REQUEST_TIMEOUT_MS3 = 1e4;
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(REQUEST_TIMEOUT_MS3)
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(REQUEST_TIMEOUT_MS3)
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 REQUEST_TIMEOUT_MS4 = 1e4;
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(REQUEST_TIMEOUT_MS4)
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(REQUEST_TIMEOUT_MS4)
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 tgThrottleCfg = configFromEnv();
17097
+ const isPrivateChat = !chat_id.startsWith("-");
17098
+ const tgThrottleCfg = isPrivateChat ? relaxedThrottleConfig(configFromEnv()) : configFromEnv();
16538
17099
  const tgThrottleNow = Date.now();
16539
- const tgThrottleDecision = decideReplyThrottle({
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 (!tgThrottleDecision.allow) {
17106
+ if (tgPlan.action === "refuse") {
16545
17107
  process.stderr.write(
16546
- `telegram-channel(${AGENT_CODE_NAME}): reply_throttled chat=${redactId(chat_id)} count=${tgThrottleDecision.recentCount} window_ms=${tgThrottleCfg.windowMs} threshold=${tgThrottleCfg.threshold}
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: ${tgThrottleDecision.recentCount} replies from this agent to this chat within the last ${Math.round(tgThrottleCfg.windowMs / 1e3)}s (threshold ${tgThrottleCfg.threshold}). The chat is paused for this agent until a human posts in it. Stop attempting to reply.`
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
  };