@integrity-labs/agt-cli 0.27.8-test.9 → 0.27.9-test.11
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/assets/impersonate-statusline.sh +7 -2
- package/dist/bin/agt.js +798 -98
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-HT6EETEL.js → chunk-45I2X4M7.js} +361 -26
- package/dist/chunk-45I2X4M7.js.map +1 -0
- package/dist/{chunk-NFBRJGNU.js → chunk-N2EBL4PZ.js} +915 -239
- package/dist/chunk-N2EBL4PZ.js.map +1 -0
- package/dist/{chunk-F4NG4EXD.js → chunk-SJPAXXTW.js} +512 -80
- package/dist/chunk-SJPAXXTW.js.map +1 -0
- package/dist/{claude-pair-runtime-OBAJZDXK.js → claude-pair-runtime-OUGV74LY.js} +2 -2
- package/dist/lib/manager-worker.js +2058 -508
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/index.js +143 -67
- package/dist/mcp/slack-channel.js +1059 -297
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +999 -140
- package/dist/{persistent-session-SBSOZG74.js → persistent-session-G3MR7JVI.js} +5 -3
- package/dist/responsiveness-probe-UHSBETBR.js +74 -0
- package/dist/responsiveness-probe-UHSBETBR.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-F4NG4EXD.js.map +0 -1
- package/dist/chunk-HT6EETEL.js.map +0 -1
- package/dist/chunk-NFBRJGNU.js.map +0 -1
- package/dist/responsiveness-probe-DU4IJ2RZ.js +0 -33
- package/dist/responsiveness-probe-DU4IJ2RZ.js.map +0 -1
- /package/dist/{claude-pair-runtime-OBAJZDXK.js.map → claude-pair-runtime-OUGV74LY.js.map} +0 -0
- /package/dist/{persistent-session-SBSOZG74.js.map → persistent-session-G3MR7JVI.js.map} +0 -0
|
@@ -6986,13 +6986,10 @@ __export(slack_block_kit_runtime_exports, {
|
|
|
6986
6986
|
DEFAULT_ASK_USER_LIMITS: () => DEFAULT_ASK_USER_LIMITS,
|
|
6987
6987
|
apiCall: () => apiCall,
|
|
6988
6988
|
buildAskUserBlocks: () => buildAskUserBlocks,
|
|
6989
|
-
buildRatingActionsBlock: () => buildRatingActionsBlock,
|
|
6990
6989
|
createPendingInteraction: () => createPendingInteraction,
|
|
6991
6990
|
decodeActionId: () => decodeActionId,
|
|
6992
|
-
defaultRatingLabels: () => defaultRatingLabels,
|
|
6993
6991
|
encodeActionId: () => encodeActionId,
|
|
6994
6992
|
generateOptionToken: () => generateOptionToken,
|
|
6995
|
-
ratingValuesForScale: () => ratingValuesForScale,
|
|
6996
6993
|
recordSlackDelivery: () => recordSlackDelivery,
|
|
6997
6994
|
resolveInteractive: () => resolveInteractive,
|
|
6998
6995
|
updatePendingInteractionMessageTs: () => updatePendingInteractionMessageTs,
|
|
@@ -7159,22 +7156,6 @@ function buildAskUserBlocks(opts) {
|
|
|
7159
7156
|
}
|
|
7160
7157
|
];
|
|
7161
7158
|
}
|
|
7162
|
-
function ratingValuesForScale(scale) {
|
|
7163
|
-
return scale === "1-5" ? [1, 2, 3, 4, 5] : [-1, 0, 1];
|
|
7164
|
-
}
|
|
7165
|
-
function defaultRatingLabels(scale) {
|
|
7166
|
-
return scale === "1-5" ? ["1", "2", "3", "4", "5"] : ["\u{1F44E}", "\u{1F914}", "\u{1F44D}"];
|
|
7167
|
-
}
|
|
7168
|
-
function buildRatingActionsBlock(opts) {
|
|
7169
|
-
return {
|
|
7170
|
-
type: "actions",
|
|
7171
|
-
elements: opts.options.map(({ label, token }) => ({
|
|
7172
|
-
type: "button",
|
|
7173
|
-
text: { type: "plain_text", text: label },
|
|
7174
|
-
action_id: encodeActionId(opts.callbackId, token)
|
|
7175
|
-
}))
|
|
7176
|
-
};
|
|
7177
|
-
}
|
|
7178
7159
|
async function resolveInteractive(cfg, input) {
|
|
7179
7160
|
const res = await apiCall(cfg, "POST", "/host/interactive/resolve", {
|
|
7180
7161
|
agent_id: cfg.agentId,
|
|
@@ -14545,23 +14526,33 @@ var DEFAULT_THROTTLE = {
|
|
|
14545
14526
|
windowMs: 12e4,
|
|
14546
14527
|
ringSize: 8
|
|
14547
14528
|
};
|
|
14548
|
-
|
|
14529
|
+
var DEFAULT_REPLY_QUEUE_DEPTH = 2;
|
|
14530
|
+
function planReplyDelivery(input) {
|
|
14549
14531
|
const cfg = input.config ?? DEFAULT_THROTTLE;
|
|
14532
|
+
const maxDepth = input.maxQueueDepth ?? DEFAULT_REPLY_QUEUE_DEPTH;
|
|
14550
14533
|
const cutoff = input.now - cfg.windowMs;
|
|
14551
|
-
const inWindow = input.recentReplyTimestamps.filter((t) => t >= cutoff);
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
recentCount: inWindow.length,
|
|
14556
|
-
reason: "throttle_threshold_exceeded"
|
|
14557
|
-
};
|
|
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" };
|
|
14558
14538
|
}
|
|
14539
|
+
if (input.queuedCount >= maxDepth) {
|
|
14540
|
+
return { action: "refuse", recentCount: inWindow.length, reason: "queue_full" };
|
|
14541
|
+
}
|
|
14542
|
+
const idx = Math.min(input.queuedCount, inWindow.length - 1);
|
|
14543
|
+
const opensAt = (inWindow[idx] ?? input.now) + cfg.windowMs;
|
|
14559
14544
|
return {
|
|
14560
|
-
|
|
14545
|
+
action: "queue",
|
|
14546
|
+
deliverAtMs: Math.max(opensAt, input.now),
|
|
14561
14547
|
recentCount: inWindow.length,
|
|
14562
|
-
reason: "
|
|
14548
|
+
reason: "queued_for_slot"
|
|
14563
14549
|
};
|
|
14564
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
|
+
}
|
|
14565
14556
|
var buffers = /* @__PURE__ */ new Map();
|
|
14566
14557
|
function key(channelId, threadKey) {
|
|
14567
14558
|
return `${channelId}${threadKey}`;
|
|
@@ -15163,8 +15154,316 @@ function createCrossTeamPeerAuditClient(args) {
|
|
|
15163
15154
|
};
|
|
15164
15155
|
}
|
|
15165
15156
|
|
|
15166
|
-
// src/
|
|
15157
|
+
// src/diagnostic-event-client.ts
|
|
15167
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/telegram-command-registry.ts
|
|
15268
|
+
var HELP = {
|
|
15269
|
+
command: "help",
|
|
15270
|
+
description: "Show available commands"
|
|
15271
|
+
};
|
|
15272
|
+
var RESTART = {
|
|
15273
|
+
command: "restart",
|
|
15274
|
+
description: "Restart this agent"
|
|
15275
|
+
};
|
|
15276
|
+
var INVESTIGATE = {
|
|
15277
|
+
command: "investigate",
|
|
15278
|
+
description: "Live tail of this agent's terminal pane (operators only)"
|
|
15279
|
+
};
|
|
15280
|
+
function buildCommandRegistrations() {
|
|
15281
|
+
return [
|
|
15282
|
+
{ scope: { type: "default" }, commands: [HELP, RESTART] },
|
|
15283
|
+
{ scope: { type: "all_private_chats" }, commands: [HELP, RESTART, INVESTIGATE] }
|
|
15284
|
+
];
|
|
15285
|
+
}
|
|
15286
|
+
|
|
15287
|
+
// src/pane-tail.ts
|
|
15288
|
+
import { execFile } from "child_process";
|
|
15289
|
+
import { promisify } from "util";
|
|
15290
|
+
import { open, stat } from "fs/promises";
|
|
15291
|
+
|
|
15292
|
+
// src/session-probe-runtime.ts
|
|
15293
|
+
import { execFileSync } from "child_process";
|
|
15294
|
+
function agentTmuxSessionName(codeName) {
|
|
15295
|
+
return `agt-${codeName}`;
|
|
15296
|
+
}
|
|
15297
|
+
function escapePgrepRegex(value) {
|
|
15298
|
+
return value.replace(/[.[\]{}()*+?^$|\\]/g, "\\$&");
|
|
15299
|
+
}
|
|
15300
|
+
function probeClaudeProcessInTmux(tmuxSession) {
|
|
15301
|
+
const escapedSession = escapePgrepRegex(tmuxSession);
|
|
15302
|
+
const pattern = `(^|[[:space:]])--name ${escapedSession}([[:space:]]|$)`;
|
|
15303
|
+
try {
|
|
15304
|
+
const out = execFileSync("pgrep", ["-f", "--", pattern], {
|
|
15305
|
+
encoding: "utf-8",
|
|
15306
|
+
timeout: 3e3
|
|
15307
|
+
}).trim();
|
|
15308
|
+
return out.length > 0 ? "alive" : "dead";
|
|
15309
|
+
} catch (err) {
|
|
15310
|
+
const e = err;
|
|
15311
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15312
|
+
return e?.status === 1 ? "dead" : "unknown";
|
|
15313
|
+
}
|
|
15314
|
+
}
|
|
15315
|
+
function probeTmuxSession(tmuxSession) {
|
|
15316
|
+
try {
|
|
15317
|
+
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15318
|
+
stdio: "ignore",
|
|
15319
|
+
timeout: 3e3
|
|
15320
|
+
});
|
|
15321
|
+
return "alive";
|
|
15322
|
+
} catch (err) {
|
|
15323
|
+
const e = err;
|
|
15324
|
+
if (e?.code === "ENOENT") return "unknown";
|
|
15325
|
+
return "dead";
|
|
15326
|
+
}
|
|
15327
|
+
}
|
|
15328
|
+
function probeAgentSession(codeName) {
|
|
15329
|
+
const session = agentTmuxSessionName(codeName);
|
|
15330
|
+
const tmux = probeTmuxSession(session);
|
|
15331
|
+
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15332
|
+
return { tmux, claude };
|
|
15333
|
+
}
|
|
15334
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
15335
|
+
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15336
|
+
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15337
|
+
const cached2 = probeCache.get(codeName);
|
|
15338
|
+
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15339
|
+
const value = probeAgentSession(codeName);
|
|
15340
|
+
probeCache.set(codeName, { at: now, value });
|
|
15341
|
+
return value;
|
|
15342
|
+
}
|
|
15343
|
+
|
|
15344
|
+
// src/pane-tail.ts
|
|
15345
|
+
var execFileAsync = promisify(execFile);
|
|
15346
|
+
var PANE_LOG_TAIL_BYTES = 64 * 1024;
|
|
15347
|
+
var TMUX_CAPTURE_TIMEOUT_MS = 2e3;
|
|
15348
|
+
async function capturePaneSnapshot(opts) {
|
|
15349
|
+
const scrollback = opts.scrollbackLines ?? 200;
|
|
15350
|
+
try {
|
|
15351
|
+
const { stdout } = await execFileAsync(
|
|
15352
|
+
"tmux",
|
|
15353
|
+
["capture-pane", "-p", "-t", agentTmuxSessionName(opts.codeName), "-S", `-${scrollback}`],
|
|
15354
|
+
{ timeout: TMUX_CAPTURE_TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024 }
|
|
15355
|
+
);
|
|
15356
|
+
return { source: "tmux", text: stdout };
|
|
15357
|
+
} catch {
|
|
15358
|
+
}
|
|
15359
|
+
if (!opts.agentDir) return null;
|
|
15360
|
+
const paneLogPath = `${opts.agentDir}/pane.log`;
|
|
15361
|
+
try {
|
|
15362
|
+
const tail = await readFileTail(paneLogPath, PANE_LOG_TAIL_BYTES);
|
|
15363
|
+
if (tail === null) return null;
|
|
15364
|
+
return { source: "pane-log", text: stripAnsi(tail) };
|
|
15365
|
+
} catch {
|
|
15366
|
+
return null;
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15369
|
+
async function readFileTail(path, maxBytes) {
|
|
15370
|
+
const st = await stat(path);
|
|
15371
|
+
if (!st.isFile()) return null;
|
|
15372
|
+
const start = Math.max(0, st.size - maxBytes);
|
|
15373
|
+
const length = st.size - start;
|
|
15374
|
+
if (length === 0) return "";
|
|
15375
|
+
const handle = await open(path, "r");
|
|
15376
|
+
try {
|
|
15377
|
+
const buf = Buffer.alloc(length);
|
|
15378
|
+
await handle.read(buf, 0, length, start);
|
|
15379
|
+
let text = buf.toString("utf8");
|
|
15380
|
+
if (start > 0) {
|
|
15381
|
+
const nl = text.indexOf("\n");
|
|
15382
|
+
if (nl !== -1) text = text.slice(nl + 1);
|
|
15383
|
+
}
|
|
15384
|
+
return text;
|
|
15385
|
+
} finally {
|
|
15386
|
+
await handle.close();
|
|
15387
|
+
}
|
|
15388
|
+
}
|
|
15389
|
+
function stripAnsi(text) {
|
|
15390
|
+
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;?<=>!]*[ -\/]*[@-~]/g, "").replace(/\x1b./g, "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
15391
|
+
}
|
|
15392
|
+
var SECRET_PATTERNS = [
|
|
15393
|
+
// Augmented host API keys
|
|
15394
|
+
{ re: /tlk_[A-Za-z0-9]{8,}/g, label: "agt-api-key" },
|
|
15395
|
+
// Slack tokens: bot/app/user/refresh/config
|
|
15396
|
+
{ re: /x(?:ox[abprs]|app)-[A-Za-z0-9-]{8,}/g, label: "slack-token" },
|
|
15397
|
+
// JWTs (three base64url segments, first one always starts with eyJ)
|
|
15398
|
+
{ re: /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, label: "jwt" },
|
|
15399
|
+
// OpenAI / Anthropic-style keys (sk-..., sk-ant-...)
|
|
15400
|
+
{ re: /sk-[A-Za-z0-9_-]{16,}/g, label: "sk-key" },
|
|
15401
|
+
// AWS access key IDs
|
|
15402
|
+
{ re: /(?:AKIA|ASIA)[0-9A-Z]{16}/g, label: "aws-key" },
|
|
15403
|
+
// GitHub tokens
|
|
15404
|
+
{ re: /gh[pousr]_[A-Za-z0-9]{20,}/g, label: "github-token" }
|
|
15405
|
+
];
|
|
15406
|
+
function redactPaneText(text) {
|
|
15407
|
+
let out = redactAugmentedPaths(text);
|
|
15408
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
15409
|
+
out = out.replace(re, `<redacted:${label}>`);
|
|
15410
|
+
}
|
|
15411
|
+
return out;
|
|
15412
|
+
}
|
|
15413
|
+
var DEFAULT_MAX_CHARS = 3900;
|
|
15414
|
+
var REFLOW_COLS = 80;
|
|
15415
|
+
function formatPaneSnapshot(opts) {
|
|
15416
|
+
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
15417
|
+
const commandName = opts.commandName ?? "/debug";
|
|
15418
|
+
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`;
|
|
15419
|
+
const fenceOverhead = header.length + "\n```\n".length + "\n```".length + 16;
|
|
15420
|
+
const contentBudget = Math.max(0, maxChars - fenceOverhead);
|
|
15421
|
+
const lines = reflowLines(sanitizeForCodeBlock(opts.text), REFLOW_COLS);
|
|
15422
|
+
const kept = [];
|
|
15423
|
+
let used = 0;
|
|
15424
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
15425
|
+
const cost = lines[i].length + 1;
|
|
15426
|
+
if (used + cost > contentBudget) break;
|
|
15427
|
+
kept.unshift(lines[i]);
|
|
15428
|
+
used += cost;
|
|
15429
|
+
}
|
|
15430
|
+
const truncated = kept.length < lines.length;
|
|
15431
|
+
const body = kept.join("\n").trimEnd();
|
|
15432
|
+
const block = body.length > 0 ? `\`\`\`
|
|
15433
|
+
${truncated ? "\u2026\n" : ""}${body}
|
|
15434
|
+
\`\`\`` : "_(pane is empty)_";
|
|
15435
|
+
return `${header}
|
|
15436
|
+
${block}`;
|
|
15437
|
+
}
|
|
15438
|
+
function formatCountdown(totalSeconds) {
|
|
15439
|
+
const s = Math.max(0, Math.floor(totalSeconds));
|
|
15440
|
+
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
|
|
15441
|
+
}
|
|
15442
|
+
function sanitizeForCodeBlock(text) {
|
|
15443
|
+
return text.replace(/```/g, "`\u200B`\u200B`");
|
|
15444
|
+
}
|
|
15445
|
+
function reflowLines(text, cols) {
|
|
15446
|
+
const out = [];
|
|
15447
|
+
for (const rawLine of text.split("\n")) {
|
|
15448
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
15449
|
+
if (line.length <= cols) {
|
|
15450
|
+
out.push(line);
|
|
15451
|
+
continue;
|
|
15452
|
+
}
|
|
15453
|
+
for (let i = 0; i < line.length; i += cols) {
|
|
15454
|
+
out.push(line.slice(i, i + cols));
|
|
15455
|
+
}
|
|
15456
|
+
}
|
|
15457
|
+
const collapsed = [];
|
|
15458
|
+
for (const line of out) {
|
|
15459
|
+
if (line === "" && collapsed[collapsed.length - 1] === "") continue;
|
|
15460
|
+
collapsed.push(line);
|
|
15461
|
+
}
|
|
15462
|
+
return collapsed;
|
|
15463
|
+
}
|
|
15464
|
+
|
|
15465
|
+
// src/observed-chat-client.ts
|
|
15466
|
+
var REQUEST_TIMEOUT_MS3 = 1e4;
|
|
15168
15467
|
function createObservedChatClient(args) {
|
|
15169
15468
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15170
15469
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15182,7 +15481,7 @@ function createObservedChatClient(args) {
|
|
|
15182
15481
|
method: "POST",
|
|
15183
15482
|
headers: { "Content-Type": "application/json" },
|
|
15184
15483
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15185
|
-
signal: AbortSignal.timeout(
|
|
15484
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
|
|
15186
15485
|
});
|
|
15187
15486
|
if (!resp.ok) {
|
|
15188
15487
|
const body = await resp.text().catch(() => "");
|
|
@@ -15199,7 +15498,7 @@ function createObservedChatClient(args) {
|
|
|
15199
15498
|
method: "POST",
|
|
15200
15499
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15201
15500
|
body: JSON.stringify({ agent_id: agentId, ...chat }),
|
|
15202
|
-
signal: AbortSignal.timeout(
|
|
15501
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS3)
|
|
15203
15502
|
});
|
|
15204
15503
|
}
|
|
15205
15504
|
async function emitAsync(chat) {
|
|
@@ -15246,7 +15545,7 @@ function createObservedChatClient(args) {
|
|
|
15246
15545
|
}
|
|
15247
15546
|
|
|
15248
15547
|
// src/conversation-ingest-client.ts
|
|
15249
|
-
var
|
|
15548
|
+
var REQUEST_TIMEOUT_MS4 = 1e4;
|
|
15250
15549
|
function createConversationIngestClient(args) {
|
|
15251
15550
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15252
15551
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15263,7 +15562,7 @@ function createConversationIngestClient(args) {
|
|
|
15263
15562
|
method: "POST",
|
|
15264
15563
|
headers: { "Content-Type": "application/json" },
|
|
15265
15564
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15266
|
-
signal: AbortSignal.timeout(
|
|
15565
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS4)
|
|
15267
15566
|
});
|
|
15268
15567
|
if (!resp.ok) {
|
|
15269
15568
|
const body = await resp.text().catch(() => "");
|
|
@@ -15286,7 +15585,7 @@ function createConversationIngestClient(args) {
|
|
|
15286
15585
|
method: "POST",
|
|
15287
15586
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15288
15587
|
body: JSON.stringify({ agent_id: agentId, ...payload }),
|
|
15289
|
-
signal: AbortSignal.timeout(
|
|
15588
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS4)
|
|
15290
15589
|
});
|
|
15291
15590
|
}
|
|
15292
15591
|
function refKey(payload) {
|
|
@@ -15318,7 +15617,7 @@ function createConversationIngestClient(args) {
|
|
|
15318
15617
|
}
|
|
15319
15618
|
|
|
15320
15619
|
// src/inbound-context-client.ts
|
|
15321
|
-
var
|
|
15620
|
+
var REQUEST_TIMEOUT_MS5 = 1e4;
|
|
15322
15621
|
function createInboundContextClient(args) {
|
|
15323
15622
|
if (!args.agtHost || !args.agtApiKey || !args.agentId) return null;
|
|
15324
15623
|
const fetchImpl = args.fetchImpl ?? fetch;
|
|
@@ -15335,7 +15634,7 @@ function createInboundContextClient(args) {
|
|
|
15335
15634
|
method: "POST",
|
|
15336
15635
|
headers: { "Content-Type": "application/json" },
|
|
15337
15636
|
body: JSON.stringify({ host_key: apiKey }),
|
|
15338
|
-
signal: AbortSignal.timeout(
|
|
15637
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS5)
|
|
15339
15638
|
});
|
|
15340
15639
|
if (!resp.ok) {
|
|
15341
15640
|
const body = await resp.text().catch(() => "");
|
|
@@ -15352,7 +15651,7 @@ function createInboundContextClient(args) {
|
|
|
15352
15651
|
method: "POST",
|
|
15353
15652
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
15354
15653
|
body: JSON.stringify(body),
|
|
15355
|
-
signal: AbortSignal.timeout(
|
|
15654
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS5)
|
|
15356
15655
|
});
|
|
15357
15656
|
}
|
|
15358
15657
|
async function emit(args2) {
|
|
@@ -15506,6 +15805,7 @@ import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
|
15506
15805
|
import { join as join3 } from "path";
|
|
15507
15806
|
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
15508
15807
|
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
15808
|
+
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
15509
15809
|
function decideAckReaction(i) {
|
|
15510
15810
|
if (!i.hasTarget) return "none";
|
|
15511
15811
|
if (!i.integrationReady) return "undeliverable";
|
|
@@ -15513,6 +15813,11 @@ function decideAckReaction(i) {
|
|
|
15513
15813
|
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
15514
15814
|
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
15515
15815
|
if (i.oldestPendingAgeMs != null && i.oldestPendingAgeMs > threshold) {
|
|
15816
|
+
const paneFreshThreshold = i.paneFreshThresholdMs ?? ACK_PANE_FRESH_THRESHOLD_MS;
|
|
15817
|
+
const paneIsFresh = i.paneLogFreshAgeMs != null && i.paneLogFreshAgeMs <= paneFreshThreshold;
|
|
15818
|
+
if (paneIsFresh && i.tmux === "alive" && i.claude === "alive") {
|
|
15819
|
+
return "ack";
|
|
15820
|
+
}
|
|
15516
15821
|
return "undeliverable";
|
|
15517
15822
|
}
|
|
15518
15823
|
return "ack";
|
|
@@ -15520,6 +15825,37 @@ function decideAckReaction(i) {
|
|
|
15520
15825
|
function decideRecoveryHeal(i) {
|
|
15521
15826
|
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
15522
15827
|
}
|
|
15828
|
+
var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
15829
|
+
function shouldPostUndeliverableNotice(lastNoticeAtMs, nowMs, throttleMs = UNDELIVERABLE_NOTICE_THROTTLE_MS) {
|
|
15830
|
+
return lastNoticeAtMs == null || nowMs - lastNoticeAtMs >= throttleMs;
|
|
15831
|
+
}
|
|
15832
|
+
function undeliverableNoticeText() {
|
|
15833
|
+
return "\u23F3 I can't get to this right now. Please resend in a few minutes \u2014 I may not see this one.";
|
|
15834
|
+
}
|
|
15835
|
+
var GIVE_UP_SIGNAL_FILENAME = "watchdog-give-up.json";
|
|
15836
|
+
var GIVE_UP_SIGNAL_MAX_AGE_MS = 30 * 60 * 1e3;
|
|
15837
|
+
function readGiveUpSignalAtMs(path, now = Date.now()) {
|
|
15838
|
+
if (!path) return null;
|
|
15839
|
+
try {
|
|
15840
|
+
const raw = JSON.parse(readFileSync2(path, "utf8"));
|
|
15841
|
+
if (typeof raw.gave_up_at !== "string") return null;
|
|
15842
|
+
const t = Date.parse(raw.gave_up_at);
|
|
15843
|
+
if (!Number.isFinite(t) || t > now) return null;
|
|
15844
|
+
return t;
|
|
15845
|
+
} catch {
|
|
15846
|
+
return null;
|
|
15847
|
+
}
|
|
15848
|
+
}
|
|
15849
|
+
function decideGiveUpNotice(input) {
|
|
15850
|
+
const maxAge = input.maxAgeMs ?? GIVE_UP_SIGNAL_MAX_AGE_MS;
|
|
15851
|
+
if (input.signalAtMs == null) return false;
|
|
15852
|
+
if (input.nowMs - input.signalAtMs > maxAge) return false;
|
|
15853
|
+
if (input.lastHandledAtMs != null && input.signalAtMs <= input.lastHandledAtMs) return false;
|
|
15854
|
+
return true;
|
|
15855
|
+
}
|
|
15856
|
+
function giveUpNoticeText() {
|
|
15857
|
+
return "\u26A0\uFE0F I couldn't read your last message \u2014 please resend it.";
|
|
15858
|
+
}
|
|
15523
15859
|
function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
15524
15860
|
if (!dir) return null;
|
|
15525
15861
|
let names;
|
|
@@ -15547,57 +15883,32 @@ function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
|
15547
15883
|
}
|
|
15548
15884
|
return oldest;
|
|
15549
15885
|
}
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
return
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
15558
|
-
|
|
15559
|
-
|
|
15560
|
-
|
|
15561
|
-
|
|
15562
|
-
|
|
15563
|
-
|
|
15564
|
-
|
|
15565
|
-
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
execFileSync("tmux", ["has-session", "-t", tmuxSession], {
|
|
15577
|
-
stdio: "ignore",
|
|
15578
|
-
timeout: 3e3
|
|
15579
|
-
});
|
|
15580
|
-
return "alive";
|
|
15581
|
-
} catch (err) {
|
|
15582
|
-
const e = err;
|
|
15583
|
-
if (e?.code === "ENOENT") return "unknown";
|
|
15584
|
-
return "dead";
|
|
15585
|
-
}
|
|
15586
|
-
}
|
|
15587
|
-
function probeAgentSession(codeName) {
|
|
15588
|
-
const session = agentTmuxSessionName(codeName);
|
|
15589
|
-
const tmux = probeTmuxSession(session);
|
|
15590
|
-
const claude = tmux === "alive" ? probeClaudeProcessInTmux(session) : tmux;
|
|
15591
|
-
return { tmux, claude };
|
|
15592
|
-
}
|
|
15593
|
-
var probeCache = /* @__PURE__ */ new Map();
|
|
15594
|
-
var SESSION_PROBE_TTL_MS = 15e3;
|
|
15595
|
-
function probeAgentSessionCached(codeName, ttlMs = SESSION_PROBE_TTL_MS, now = Date.now()) {
|
|
15596
|
-
const cached2 = probeCache.get(codeName);
|
|
15597
|
-
if (cached2 && now - cached2.at < ttlMs) return cached2.value;
|
|
15598
|
-
const value = probeAgentSession(codeName);
|
|
15599
|
-
probeCache.set(codeName, { at: now, value });
|
|
15600
|
-
return value;
|
|
15886
|
+
function isPendingMarkerStale(receivedAt, nowMs, thresholdMs) {
|
|
15887
|
+
if (!receivedAt) return true;
|
|
15888
|
+
const t = Date.parse(receivedAt);
|
|
15889
|
+
if (!Number.isFinite(t)) return true;
|
|
15890
|
+
if (t > nowMs) return true;
|
|
15891
|
+
return nowMs - t > thresholdMs;
|
|
15892
|
+
}
|
|
15893
|
+
function channelOrphanMarkerMs() {
|
|
15894
|
+
const raw = parseInt(process.env.AGT_CHANNEL_ORPHAN_MARKER_MS ?? "", 10);
|
|
15895
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 30 * 6e4;
|
|
15896
|
+
}
|
|
15897
|
+
var ORPHAN_SWEEP_INTERVAL_MS = 30 * 60 * 1e3;
|
|
15898
|
+
function orphanSweepIntervalMs() {
|
|
15899
|
+
return Math.max(6e4, Math.min(ORPHAN_SWEEP_INTERVAL_MS, channelOrphanMarkerMs()));
|
|
15900
|
+
}
|
|
15901
|
+
var MAX_MARKER_REPLAYS = 3;
|
|
15902
|
+
function channelReplayEnabled() {
|
|
15903
|
+
const v = process.env.AGT_CHANNEL_REPLAY_ENABLED;
|
|
15904
|
+
return v === "1" || v === "true";
|
|
15905
|
+
}
|
|
15906
|
+
function shouldReplayMarker(i) {
|
|
15907
|
+
if (!i.enabled) return false;
|
|
15908
|
+
if (!i.hasPayload) return false;
|
|
15909
|
+
if (!i.sessionAlive) return false;
|
|
15910
|
+
if (i.markerAgeMs < (i.minAgeMs ?? REPLY_WEDGED_THRESHOLD_MS)) return false;
|
|
15911
|
+
return i.replayCount < (i.maxReplays ?? MAX_MARKER_REPLAYS);
|
|
15601
15912
|
}
|
|
15602
15913
|
|
|
15603
15914
|
// src/telegram-channel.ts
|
|
@@ -15613,6 +15924,9 @@ var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
|
15613
15924
|
var ALLOWED_CHATS = new Set(
|
|
15614
15925
|
(process.env.TELEGRAM_ALLOWED_CHATS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15615
15926
|
);
|
|
15927
|
+
var DIAGNOSTIC_CHAT_IDS = new Set(
|
|
15928
|
+
(process.env.TELEGRAM_DIAGNOSTIC_CHAT_IDS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15929
|
+
);
|
|
15616
15930
|
var PEER_CLASSIFIER_CONFIG = {
|
|
15617
15931
|
peer_agent_mode: parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE),
|
|
15618
15932
|
peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
|
|
@@ -15648,6 +15962,13 @@ var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
|
|
|
15648
15962
|
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
15649
15963
|
`)
|
|
15650
15964
|
});
|
|
15965
|
+
var diagnosticEventClient = createDiagnosticEventClient({
|
|
15966
|
+
agtHost: AGT_HOST,
|
|
15967
|
+
agtApiKey: AGT_API_KEY,
|
|
15968
|
+
agentId: process.env.AGT_AGENT_ID ?? null,
|
|
15969
|
+
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
15970
|
+
`)
|
|
15971
|
+
});
|
|
15651
15972
|
var observedChatClient = createObservedChatClient({
|
|
15652
15973
|
agtHost: AGT_HOST,
|
|
15653
15974
|
agtApiKey: AGT_API_KEY,
|
|
@@ -15773,20 +16094,19 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
15773
16094
|
);
|
|
15774
16095
|
}
|
|
15775
16096
|
}
|
|
15776
|
-
var UNDELIVERABLE_NOTICE_THROTTLE_MS = 5 * 60 * 1e3;
|
|
15777
16097
|
var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
|
|
15778
16098
|
async function notifyUndeliverable(chatId, messageId) {
|
|
15779
16099
|
const key2 = String(chatId);
|
|
15780
16100
|
const now = Date.now();
|
|
15781
|
-
|
|
15782
|
-
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
16101
|
+
if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(key2), now)) return;
|
|
15783
16102
|
lastUndeliverableNoticeAt.set(key2, now);
|
|
15784
16103
|
try {
|
|
15785
16104
|
const resp = await telegramApiCall(
|
|
15786
16105
|
"sendMessage",
|
|
15787
16106
|
{
|
|
15788
16107
|
chat_id: chatId,
|
|
15789
|
-
|
|
16108
|
+
// ENG-6025: shared copy with the Slack undeliverable notice.
|
|
16109
|
+
text: undeliverableNoticeText(),
|
|
15790
16110
|
reply_to_message_id: Number(messageId),
|
|
15791
16111
|
allow_sending_without_reply: true
|
|
15792
16112
|
},
|
|
@@ -15812,8 +16132,7 @@ var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
|
|
|
15812
16132
|
function notifyBackOnline(chatId) {
|
|
15813
16133
|
const key2 = String(chatId);
|
|
15814
16134
|
const now = Date.now();
|
|
15815
|
-
|
|
15816
|
-
if (last != null && now - last < UNDELIVERABLE_NOTICE_THROTTLE_MS) return;
|
|
16135
|
+
if (!shouldPostUndeliverableNotice(lastBackOnlineNoticeAt.get(key2), now)) return;
|
|
15817
16136
|
lastBackOnlineNoticeAt.set(key2, now);
|
|
15818
16137
|
void (async () => {
|
|
15819
16138
|
try {
|
|
@@ -15849,7 +16168,8 @@ function buildTelegramHelpMessage(codeName) {
|
|
|
15849
16168
|
"",
|
|
15850
16169
|
`_Type these in any chat where the bot is present (intercepted by the agent):_`,
|
|
15851
16170
|
`\u2022 /help \u2014 show this help`,
|
|
15852
|
-
`\u2022 /restart \u2014 restart this agent
|
|
16171
|
+
`\u2022 /restart \u2014 restart this agent`,
|
|
16172
|
+
`\u2022 /investigate-${codeName} \u2014 live tail of this agent's terminal pane (DM only; team owners/admins and the agent's reports-to person)`
|
|
15853
16173
|
].join("\n");
|
|
15854
16174
|
}
|
|
15855
16175
|
async function handleHelpCommand(opts) {
|
|
@@ -15944,6 +16264,261 @@ async function handleRestartCommand(opts) {
|
|
|
15944
16264
|
}
|
|
15945
16265
|
}
|
|
15946
16266
|
}
|
|
16267
|
+
var INVESTIGATE_TAIL_WINDOW_MS = 12e4;
|
|
16268
|
+
var INVESTIGATE_TAIL_INTERVAL_MS = 3e3;
|
|
16269
|
+
var INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES = 4;
|
|
16270
|
+
var INVESTIGATE_MAX_CHARS = 3800;
|
|
16271
|
+
var investigateTailSlot = createSingleTailSlot();
|
|
16272
|
+
function investigateAuditLog(line) {
|
|
16273
|
+
process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): /investigate ${line}
|
|
16274
|
+
`);
|
|
16275
|
+
}
|
|
16276
|
+
function investigateNowUtcLabel() {
|
|
16277
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19)} UTC`;
|
|
16278
|
+
}
|
|
16279
|
+
function investigateSleep(ms) {
|
|
16280
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
16281
|
+
}
|
|
16282
|
+
async function classifyInvestigateCommand(text) {
|
|
16283
|
+
const needsUsername = /@[A-Za-z0-9_]{1,64}(?:\s|$)/.test(text);
|
|
16284
|
+
const ours = needsUsername ? await resolveBotUsername() : null;
|
|
16285
|
+
return classifyInvestigateText(text, { codeName: AGENT_CODE_NAME, botUsername: ours });
|
|
16286
|
+
}
|
|
16287
|
+
async function sendInvestigateReply(chatId, messageId, text) {
|
|
16288
|
+
try {
|
|
16289
|
+
const resp = await telegramApiCall(
|
|
16290
|
+
"sendMessage",
|
|
16291
|
+
{
|
|
16292
|
+
chat_id: chatId,
|
|
16293
|
+
text,
|
|
16294
|
+
parse_mode: "Markdown",
|
|
16295
|
+
reply_to_message_id: Number(messageId)
|
|
16296
|
+
},
|
|
16297
|
+
1e4
|
|
16298
|
+
);
|
|
16299
|
+
if (!resp.ok) {
|
|
16300
|
+
investigateAuditLog(`reply rejected by Telegram: ${resp.description ?? "unknown"}`);
|
|
16301
|
+
}
|
|
16302
|
+
} catch (err) {
|
|
16303
|
+
investigateAuditLog(`reply send failed: ${redactAugmentedPaths(err.message)}`);
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
async function runInvestigateTailLoop(opts) {
|
|
16307
|
+
const investigateCmd = `/investigate-${AGENT_CODE_NAME}`;
|
|
16308
|
+
let last = opts.initialFrame;
|
|
16309
|
+
let consecutiveFailures = 0;
|
|
16310
|
+
let delayMs = INVESTIGATE_TAIL_INTERVAL_MS;
|
|
16311
|
+
try {
|
|
16312
|
+
while (Date.now() + delayMs < opts.expiresAtMs) {
|
|
16313
|
+
await investigateSleep(delayMs);
|
|
16314
|
+
delayMs = INVESTIGATE_TAIL_INTERVAL_MS;
|
|
16315
|
+
const snapshot = await capturePaneSnapshot({
|
|
16316
|
+
codeName: AGENT_CODE_NAME,
|
|
16317
|
+
agentDir: TELEGRAM_AGENT_DIR
|
|
16318
|
+
});
|
|
16319
|
+
if (!snapshot) {
|
|
16320
|
+
consecutiveFailures++;
|
|
16321
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16322
|
+
investigateAuditLog(`tail aborted after ${consecutiveFailures} consecutive capture failures`);
|
|
16323
|
+
break;
|
|
16324
|
+
}
|
|
16325
|
+
continue;
|
|
16326
|
+
}
|
|
16327
|
+
const frame = {
|
|
16328
|
+
body: redactPaneText(snapshot.text),
|
|
16329
|
+
source: snapshot.source,
|
|
16330
|
+
capturedAtLabel: investigateNowUtcLabel()
|
|
16331
|
+
};
|
|
16332
|
+
if (frame.body === last.body) continue;
|
|
16333
|
+
const text = formatPaneSnapshot({
|
|
16334
|
+
codeName: AGENT_CODE_NAME,
|
|
16335
|
+
text: frame.body,
|
|
16336
|
+
source: frame.source,
|
|
16337
|
+
capturedAtLabel: frame.capturedAtLabel,
|
|
16338
|
+
status: {
|
|
16339
|
+
kind: "live",
|
|
16340
|
+
secondsRemaining: Math.max(0, Math.round((opts.expiresAtMs - Date.now()) / 1e3))
|
|
16341
|
+
},
|
|
16342
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16343
|
+
commandName: investigateCmd
|
|
16344
|
+
});
|
|
16345
|
+
try {
|
|
16346
|
+
const resp = await telegramApiCall(
|
|
16347
|
+
"editMessageText",
|
|
16348
|
+
{
|
|
16349
|
+
chat_id: opts.chatId,
|
|
16350
|
+
message_id: opts.messageId,
|
|
16351
|
+
text,
|
|
16352
|
+
parse_mode: "Markdown"
|
|
16353
|
+
},
|
|
16354
|
+
1e4
|
|
16355
|
+
);
|
|
16356
|
+
if (resp.ok) {
|
|
16357
|
+
last = frame;
|
|
16358
|
+
consecutiveFailures = 0;
|
|
16359
|
+
continue;
|
|
16360
|
+
}
|
|
16361
|
+
const retryAfter = resp.parameters?.retry_after;
|
|
16362
|
+
if (typeof retryAfter === "number" && retryAfter > 0) {
|
|
16363
|
+
delayMs = Math.max(INVESTIGATE_TAIL_INTERVAL_MS, retryAfter * 1e3);
|
|
16364
|
+
continue;
|
|
16365
|
+
}
|
|
16366
|
+
consecutiveFailures++;
|
|
16367
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16368
|
+
investigateAuditLog(
|
|
16369
|
+
`tail aborted after ${consecutiveFailures} consecutive update failures (last: ${resp.description ?? "unknown"})`
|
|
16370
|
+
);
|
|
16371
|
+
break;
|
|
16372
|
+
}
|
|
16373
|
+
} catch (err) {
|
|
16374
|
+
consecutiveFailures++;
|
|
16375
|
+
if (consecutiveFailures >= INVESTIGATE_TAIL_MAX_CONSECUTIVE_FAILURES) {
|
|
16376
|
+
investigateAuditLog(
|
|
16377
|
+
`tail aborted after ${consecutiveFailures} consecutive update failures (last: ${redactAugmentedPaths(err.message)})`
|
|
16378
|
+
);
|
|
16379
|
+
break;
|
|
16380
|
+
}
|
|
16381
|
+
}
|
|
16382
|
+
}
|
|
16383
|
+
} finally {
|
|
16384
|
+
investigateTailSlot.release();
|
|
16385
|
+
try {
|
|
16386
|
+
await telegramApiCall(
|
|
16387
|
+
"editMessageText",
|
|
16388
|
+
{
|
|
16389
|
+
chat_id: opts.chatId,
|
|
16390
|
+
message_id: opts.messageId,
|
|
16391
|
+
text: formatPaneSnapshot({
|
|
16392
|
+
codeName: AGENT_CODE_NAME,
|
|
16393
|
+
text: last.body,
|
|
16394
|
+
source: last.source,
|
|
16395
|
+
capturedAtLabel: last.capturedAtLabel,
|
|
16396
|
+
status: { kind: "expired" },
|
|
16397
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16398
|
+
commandName: investigateCmd
|
|
16399
|
+
}),
|
|
16400
|
+
parse_mode: "Markdown"
|
|
16401
|
+
},
|
|
16402
|
+
1e4
|
|
16403
|
+
);
|
|
16404
|
+
} catch (err) {
|
|
16405
|
+
investigateAuditLog(`expired-edit failed: ${redactAugmentedPaths(err.message)}`);
|
|
16406
|
+
}
|
|
16407
|
+
}
|
|
16408
|
+
}
|
|
16409
|
+
async function handleInvestigateCommand(opts) {
|
|
16410
|
+
const investigateCmd = `/investigate-${AGENT_CODE_NAME}`;
|
|
16411
|
+
const emitAudit = (outcome, reason) => {
|
|
16412
|
+
diagnosticEventClient?.emit({
|
|
16413
|
+
channel: "telegram",
|
|
16414
|
+
command: "investigate",
|
|
16415
|
+
outcome,
|
|
16416
|
+
reason,
|
|
16417
|
+
chat_id: opts.chatId,
|
|
16418
|
+
sender_chat_id: opts.senderId
|
|
16419
|
+
});
|
|
16420
|
+
};
|
|
16421
|
+
const verdict = evaluateInvestigateGate({
|
|
16422
|
+
chatType: opts.chatType,
|
|
16423
|
+
senderId: opts.senderId,
|
|
16424
|
+
diagnosticChatIds: DIAGNOSTIC_CHAT_IDS
|
|
16425
|
+
});
|
|
16426
|
+
if (!verdict.ok) {
|
|
16427
|
+
investigateAuditLog(
|
|
16428
|
+
`denied reason=${verdict.reason} sender=${opts.senderId ? redactId(opts.senderId) : "unknown"}`
|
|
16429
|
+
);
|
|
16430
|
+
emitAudit("denied", verdict.reason);
|
|
16431
|
+
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.`;
|
|
16432
|
+
await sendInvestigateReply(opts.chatId, opts.messageId, denialText);
|
|
16433
|
+
return;
|
|
16434
|
+
}
|
|
16435
|
+
const reservedExpiresAtMs = investigateTailSlot.reserve(INVESTIGATE_TAIL_WINDOW_MS);
|
|
16436
|
+
if (reservedExpiresAtMs === null) {
|
|
16437
|
+
const remaining = formatCountdown(investigateTailSlot.remainingMs() / 1e3);
|
|
16438
|
+
investigateAuditLog(
|
|
16439
|
+
`not-started reason=tail-already-running sender=${redactId(opts.senderId ?? "unknown")} remaining=${remaining}`
|
|
16440
|
+
);
|
|
16441
|
+
emitAudit("not_started", "tail-already-running");
|
|
16442
|
+
await sendInvestigateReply(
|
|
16443
|
+
opts.chatId,
|
|
16444
|
+
opts.messageId,
|
|
16445
|
+
`\u23F3 A \`${investigateCmd}\` tail is already running for \`${AGENT_CODE_NAME}\` (one at a time) \u2014 it expires in ${remaining}.`
|
|
16446
|
+
);
|
|
16447
|
+
return;
|
|
16448
|
+
}
|
|
16449
|
+
const snapshot = await capturePaneSnapshot({
|
|
16450
|
+
codeName: AGENT_CODE_NAME,
|
|
16451
|
+
agentDir: TELEGRAM_AGENT_DIR
|
|
16452
|
+
});
|
|
16453
|
+
if (!snapshot) {
|
|
16454
|
+
investigateTailSlot.release();
|
|
16455
|
+
investigateAuditLog(`not-started reason=no-pane-output sender=${redactId(opts.senderId ?? "unknown")}`);
|
|
16456
|
+
emitAudit("not_started", "no-pane-output");
|
|
16457
|
+
await sendInvestigateReply(
|
|
16458
|
+
opts.chatId,
|
|
16459
|
+
opts.messageId,
|
|
16460
|
+
`\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.)`
|
|
16461
|
+
);
|
|
16462
|
+
return;
|
|
16463
|
+
}
|
|
16464
|
+
investigateAuditLog(`granted sender=${redactId(opts.senderId ?? "unknown")} \u2014 starting live tail (source=${snapshot.source})`);
|
|
16465
|
+
emitAudit("granted", null);
|
|
16466
|
+
const initialFrame = {
|
|
16467
|
+
body: redactPaneText(snapshot.text),
|
|
16468
|
+
source: snapshot.source,
|
|
16469
|
+
capturedAtLabel: investigateNowUtcLabel()
|
|
16470
|
+
};
|
|
16471
|
+
let postedMessageId = null;
|
|
16472
|
+
try {
|
|
16473
|
+
const posted = await telegramApiCall(
|
|
16474
|
+
"sendMessage",
|
|
16475
|
+
{
|
|
16476
|
+
chat_id: opts.chatId,
|
|
16477
|
+
text: formatPaneSnapshot({
|
|
16478
|
+
codeName: AGENT_CODE_NAME,
|
|
16479
|
+
text: initialFrame.body,
|
|
16480
|
+
source: initialFrame.source,
|
|
16481
|
+
capturedAtLabel: initialFrame.capturedAtLabel,
|
|
16482
|
+
status: {
|
|
16483
|
+
kind: "live",
|
|
16484
|
+
secondsRemaining: Math.max(0, Math.round((reservedExpiresAtMs - Date.now()) / 1e3))
|
|
16485
|
+
},
|
|
16486
|
+
maxChars: INVESTIGATE_MAX_CHARS,
|
|
16487
|
+
commandName: investigateCmd
|
|
16488
|
+
}),
|
|
16489
|
+
parse_mode: "Markdown",
|
|
16490
|
+
reply_to_message_id: Number(opts.messageId)
|
|
16491
|
+
},
|
|
16492
|
+
1e4
|
|
16493
|
+
);
|
|
16494
|
+
const result = posted.result;
|
|
16495
|
+
if (posted.ok && typeof result?.message_id === "number") {
|
|
16496
|
+
postedMessageId = result.message_id;
|
|
16497
|
+
} else {
|
|
16498
|
+
investigateAuditLog(`not-started reason=post-failed error=${posted.description ?? "unknown"}`);
|
|
16499
|
+
}
|
|
16500
|
+
} catch (err) {
|
|
16501
|
+
investigateAuditLog(`not-started reason=post-failed error=${redactAugmentedPaths(err.message)}`);
|
|
16502
|
+
}
|
|
16503
|
+
if (postedMessageId === null) {
|
|
16504
|
+
investigateTailSlot.release();
|
|
16505
|
+
await sendInvestigateReply(
|
|
16506
|
+
opts.chatId,
|
|
16507
|
+
opts.messageId,
|
|
16508
|
+
`\u274C Failed to post the pane snapshot. Try again in a moment.`
|
|
16509
|
+
);
|
|
16510
|
+
return;
|
|
16511
|
+
}
|
|
16512
|
+
void runInvestigateTailLoop({
|
|
16513
|
+
chatId: opts.chatId,
|
|
16514
|
+
messageId: postedMessageId,
|
|
16515
|
+
initialFrame,
|
|
16516
|
+
expiresAtMs: reservedExpiresAtMs
|
|
16517
|
+
}).catch((err) => {
|
|
16518
|
+
investigateTailSlot.release();
|
|
16519
|
+
investigateAuditLog(`tail loop crashed: ${redactAugmentedPaths(err.message)}`);
|
|
16520
|
+
});
|
|
16521
|
+
}
|
|
15947
16522
|
var cachedBotUsername = null;
|
|
15948
16523
|
var cachedBotId = null;
|
|
15949
16524
|
async function refreshBotIdentity() {
|
|
@@ -16009,7 +16584,7 @@ function pendingInboundPath(chatId, messageId) {
|
|
|
16009
16584
|
if (!PENDING_INBOUND_DIR) return null;
|
|
16010
16585
|
return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
16011
16586
|
}
|
|
16012
|
-
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false) {
|
|
16587
|
+
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
|
|
16013
16588
|
const path = pendingInboundPath(chatId, messageId);
|
|
16014
16589
|
if (!path || !PENDING_INBOUND_DIR) return;
|
|
16015
16590
|
const marker = {
|
|
@@ -16018,7 +16593,10 @@ function writePendingInboundMarker(chatId, messageId, chatType, undeliverable =
|
|
|
16018
16593
|
chat_type: chatType,
|
|
16019
16594
|
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16020
16595
|
// Only persist the flag when set — keeps healthy-path markers byte-identical.
|
|
16021
|
-
...undeliverable ? { undeliverable: true } : {}
|
|
16596
|
+
...undeliverable ? { undeliverable: true } : {},
|
|
16597
|
+
// ENG-5969: carry the replay payload only when provided (the durable-replay
|
|
16598
|
+
// path). Absent ⇒ the marker can't be replayed (older / ack-only callers).
|
|
16599
|
+
...payload ? { payload } : {}
|
|
16022
16600
|
};
|
|
16023
16601
|
try {
|
|
16024
16602
|
mkdirSync3(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
@@ -16219,10 +16797,10 @@ function startRecoveryOutboxWatcher() {
|
|
|
16219
16797
|
}
|
|
16220
16798
|
startRecoveryOutboxWatcher();
|
|
16221
16799
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16222
|
-
function trackPendingMessage(chatId, messageId, chatType, undeliverable = false) {
|
|
16223
|
-
writePendingInboundMarker(chatId, messageId, chatType, undeliverable);
|
|
16800
|
+
function trackPendingMessage(chatId, messageId, chatType, undeliverable = false, payload) {
|
|
16801
|
+
writePendingInboundMarker(chatId, messageId, chatType, undeliverable, payload);
|
|
16224
16802
|
}
|
|
16225
|
-
function
|
|
16803
|
+
function sweepTelegramStaleMarkers(thresholdMs) {
|
|
16226
16804
|
if (!PENDING_INBOUND_DIR) return;
|
|
16227
16805
|
if (!existsSync2(PENDING_INBOUND_DIR)) return;
|
|
16228
16806
|
let filenames;
|
|
@@ -16257,31 +16835,152 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16257
16835
|
continue;
|
|
16258
16836
|
}
|
|
16259
16837
|
const { chat_id, message_id, received_at } = marker;
|
|
16260
|
-
if (!chat_id || !message_id ||
|
|
16838
|
+
if (!chat_id || !message_id || isPendingMarkerStale(received_at, now, thresholdMs)) {
|
|
16261
16839
|
try {
|
|
16262
16840
|
unlinkSync3(fullPath);
|
|
16263
16841
|
} catch {
|
|
16264
16842
|
}
|
|
16265
16843
|
cleared++;
|
|
16266
|
-
continue;
|
|
16267
16844
|
}
|
|
16268
|
-
|
|
16269
|
-
|
|
16845
|
+
}
|
|
16846
|
+
if (cleared > 0) {
|
|
16847
|
+
process.stderr.write(
|
|
16848
|
+
`telegram-channel(${AGENT_CODE_NAME}): stale-marker sweep cleared=${cleared}
|
|
16849
|
+
`
|
|
16850
|
+
);
|
|
16851
|
+
}
|
|
16852
|
+
}
|
|
16853
|
+
sweepTelegramStaleMarkers(STALE_MARKER_MS);
|
|
16854
|
+
var orphanSweepTimer = setInterval(() => {
|
|
16855
|
+
const probe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
16856
|
+
const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
|
|
16857
|
+
sweepTelegramStaleMarkers(sessionAlive ? channelOrphanMarkerMs() : STALE_MARKER_MS);
|
|
16858
|
+
checkWatchdogGiveUpNotice();
|
|
16859
|
+
}, orphanSweepIntervalMs());
|
|
16860
|
+
orphanSweepTimer.unref?.();
|
|
16861
|
+
var lastGiveUpHandledAtMs = null;
|
|
16862
|
+
function listPendingInboundChatIds() {
|
|
16863
|
+
if (!PENDING_INBOUND_DIR || !existsSync2(PENDING_INBOUND_DIR)) return [];
|
|
16864
|
+
const chats = /* @__PURE__ */ new Set();
|
|
16865
|
+
try {
|
|
16866
|
+
for (const name of readdirSync2(PENDING_INBOUND_DIR)) {
|
|
16867
|
+
if (!name.endsWith(".json")) continue;
|
|
16270
16868
|
try {
|
|
16271
|
-
|
|
16869
|
+
const marker = JSON.parse(
|
|
16870
|
+
readFileSync3(join4(PENDING_INBOUND_DIR, name), "utf8")
|
|
16871
|
+
);
|
|
16872
|
+
if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
|
|
16272
16873
|
} catch {
|
|
16273
16874
|
}
|
|
16274
|
-
cleared++;
|
|
16275
16875
|
}
|
|
16876
|
+
} catch {
|
|
16877
|
+
return [];
|
|
16276
16878
|
}
|
|
16277
|
-
|
|
16879
|
+
return [...chats];
|
|
16880
|
+
}
|
|
16881
|
+
async function notifyWatchdogGiveUp(chatId) {
|
|
16882
|
+
const now = Date.now();
|
|
16883
|
+
if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(chatId), now)) return;
|
|
16884
|
+
lastUndeliverableNoticeAt.set(chatId, now);
|
|
16885
|
+
try {
|
|
16886
|
+
const resp = await telegramApiCall(
|
|
16887
|
+
"sendMessage",
|
|
16888
|
+
{ chat_id: chatId, text: giveUpNoticeText() },
|
|
16889
|
+
1e4
|
|
16890
|
+
);
|
|
16891
|
+
if (!resp.ok) {
|
|
16892
|
+
process.stderr.write(
|
|
16893
|
+
`telegram-channel(${AGENT_CODE_NAME}): give-up notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
16894
|
+
`
|
|
16895
|
+
);
|
|
16896
|
+
return;
|
|
16897
|
+
}
|
|
16278
16898
|
process.stderr.write(
|
|
16279
|
-
`telegram-channel(${AGENT_CODE_NAME}):
|
|
16899
|
+
`telegram-channel(${AGENT_CODE_NAME}): give-up notice posted chat=${redactId(chatId)}
|
|
16900
|
+
`
|
|
16901
|
+
);
|
|
16902
|
+
} catch (err) {
|
|
16903
|
+
process.stderr.write(
|
|
16904
|
+
`telegram-channel(${AGENT_CODE_NAME}): give-up notice error: ${err.message}
|
|
16905
|
+
`
|
|
16906
|
+
);
|
|
16907
|
+
}
|
|
16908
|
+
}
|
|
16909
|
+
function checkWatchdogGiveUpNotice() {
|
|
16910
|
+
if (!AGENT_DIR) return;
|
|
16911
|
+
const signalAtMs = readGiveUpSignalAtMs(join4(AGENT_DIR, GIVE_UP_SIGNAL_FILENAME));
|
|
16912
|
+
const act = decideGiveUpNotice({
|
|
16913
|
+
signalAtMs,
|
|
16914
|
+
lastHandledAtMs: lastGiveUpHandledAtMs,
|
|
16915
|
+
nowMs: Date.now()
|
|
16916
|
+
});
|
|
16917
|
+
if (signalAtMs != null) {
|
|
16918
|
+
lastGiveUpHandledAtMs = Math.max(lastGiveUpHandledAtMs ?? 0, signalAtMs);
|
|
16919
|
+
}
|
|
16920
|
+
if (!act) return;
|
|
16921
|
+
const chats = listPendingInboundChatIds();
|
|
16922
|
+
if (chats.length === 0) {
|
|
16923
|
+
process.stderr.write(
|
|
16924
|
+
`telegram-channel(${AGENT_CODE_NAME}): give-up signal seen but no pending markers \u2014 nothing to notice
|
|
16925
|
+
`
|
|
16926
|
+
);
|
|
16927
|
+
return;
|
|
16928
|
+
}
|
|
16929
|
+
for (const chatId of chats) {
|
|
16930
|
+
void notifyWatchdogGiveUp(chatId);
|
|
16931
|
+
}
|
|
16932
|
+
}
|
|
16933
|
+
function __resetGiveUpNoticeStateForTests() {
|
|
16934
|
+
lastGiveUpHandledAtMs = null;
|
|
16935
|
+
}
|
|
16936
|
+
var queuedReplyCounts = /* @__PURE__ */ new Map();
|
|
16937
|
+
function _resetQueuedReplyCountsForTests() {
|
|
16938
|
+
queuedReplyCounts.clear();
|
|
16939
|
+
}
|
|
16940
|
+
async function deliverQueuedReply(p) {
|
|
16941
|
+
const remaining = (queuedReplyCounts.get(p.chatId) ?? 1) - 1;
|
|
16942
|
+
if (remaining <= 0) queuedReplyCounts.delete(p.chatId);
|
|
16943
|
+
else queuedReplyCounts.set(p.chatId, remaining);
|
|
16944
|
+
try {
|
|
16945
|
+
const killed = await isThreadKilled({
|
|
16946
|
+
channelType: "telegram",
|
|
16947
|
+
channelId: p.chatId,
|
|
16948
|
+
threadTs: "",
|
|
16949
|
+
agtHost: AGT_HOST,
|
|
16950
|
+
agtApiKey: AGT_API_KEY
|
|
16951
|
+
});
|
|
16952
|
+
if (killed) {
|
|
16953
|
+
process.stderr.write(
|
|
16954
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_dropped reason=killed chat=${redactId(p.chatId)}
|
|
16955
|
+
`
|
|
16956
|
+
);
|
|
16957
|
+
return;
|
|
16958
|
+
}
|
|
16959
|
+
const body = { chat_id: p.chatId, text: p.text };
|
|
16960
|
+
if (p.replyToMessageId) body.reply_to_message_id = Number(p.replyToMessageId);
|
|
16961
|
+
const data = await telegramApiCall("sendMessage", body, 15e3);
|
|
16962
|
+
if (!data.ok) {
|
|
16963
|
+
process.stderr.write(
|
|
16964
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_failed chat=${redactId(p.chatId)} error=${data.description ?? "unknown"}
|
|
16965
|
+
`
|
|
16966
|
+
);
|
|
16967
|
+
return;
|
|
16968
|
+
}
|
|
16969
|
+
recordReply(p.chatId, "", Date.now(), p.throttleCfg);
|
|
16970
|
+
if (p.isReplyTool) {
|
|
16971
|
+
clearPendingMessage(p.chatId, p.replyToMessageId);
|
|
16972
|
+
}
|
|
16973
|
+
process.stderr.write(
|
|
16974
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_delivered chat=${redactId(p.chatId)}
|
|
16975
|
+
`
|
|
16976
|
+
);
|
|
16977
|
+
} catch (err) {
|
|
16978
|
+
process.stderr.write(
|
|
16979
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queue_failed chat=${redactId(p.chatId)} error=${err.message}
|
|
16280
16980
|
`
|
|
16281
16981
|
);
|
|
16282
16982
|
}
|
|
16283
16983
|
}
|
|
16284
|
-
sweepTelegramStaleMarkersOnBoot();
|
|
16285
16984
|
function clearPendingMessage(chatId, messageId) {
|
|
16286
16985
|
if (messageId) {
|
|
16287
16986
|
clearPendingInboundMarker(chatId, messageId);
|
|
@@ -16515,28 +17214,56 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16515
17214
|
isError: true
|
|
16516
17215
|
};
|
|
16517
17216
|
}
|
|
16518
|
-
const
|
|
17217
|
+
const isPrivateChat = !chat_id.startsWith("-");
|
|
17218
|
+
const tgThrottleCfg = isPrivateChat ? relaxedThrottleConfig(configFromEnv()) : configFromEnv();
|
|
16519
17219
|
const tgThrottleNow = Date.now();
|
|
16520
|
-
const
|
|
17220
|
+
const tgPlan = planReplyDelivery({
|
|
16521
17221
|
recentReplyTimestamps: getRecentReplies(chat_id, "", tgThrottleNow, tgThrottleCfg),
|
|
17222
|
+
queuedCount: queuedReplyCounts.get(chat_id) ?? 0,
|
|
16522
17223
|
now: tgThrottleNow,
|
|
16523
17224
|
config: tgThrottleCfg
|
|
16524
17225
|
});
|
|
16525
|
-
if (
|
|
17226
|
+
if (tgPlan.action === "refuse") {
|
|
16526
17227
|
process.stderr.write(
|
|
16527
|
-
`telegram-channel(${AGENT_CODE_NAME}): reply_throttled chat=${redactId(chat_id)} count=${
|
|
17228
|
+
`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}
|
|
16528
17229
|
`
|
|
16529
17230
|
);
|
|
16530
17231
|
return {
|
|
16531
17232
|
content: [
|
|
16532
17233
|
{
|
|
16533
17234
|
type: "text",
|
|
16534
|
-
text: `Reply throttled: ${
|
|
17235
|
+
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.`
|
|
16535
17236
|
}
|
|
16536
17237
|
],
|
|
16537
17238
|
isError: true
|
|
16538
17239
|
};
|
|
16539
17240
|
}
|
|
17241
|
+
if (tgPlan.action === "queue" && tgPlan.deliverAtMs !== void 0) {
|
|
17242
|
+
const delayMs = Math.max(0, tgPlan.deliverAtMs - tgThrottleNow);
|
|
17243
|
+
queuedReplyCounts.set(chat_id, (queuedReplyCounts.get(chat_id) ?? 0) + 1);
|
|
17244
|
+
process.stderr.write(
|
|
17245
|
+
`telegram-channel(${AGENT_CODE_NAME}): reply_queued chat=${redactId(chat_id)} delay_ms=${delayMs} count=${tgPlan.recentCount} threshold=${tgThrottleCfg.threshold}
|
|
17246
|
+
`
|
|
17247
|
+
);
|
|
17248
|
+
const timer = setTimeout(() => {
|
|
17249
|
+
void deliverQueuedReply({
|
|
17250
|
+
chatId: chat_id,
|
|
17251
|
+
text,
|
|
17252
|
+
replyToMessageId: reply_to_message_id,
|
|
17253
|
+
isReplyTool: name === "telegram.reply",
|
|
17254
|
+
throttleCfg: tgThrottleCfg
|
|
17255
|
+
});
|
|
17256
|
+
}, delayMs);
|
|
17257
|
+
timer.unref();
|
|
17258
|
+
return {
|
|
17259
|
+
content: [
|
|
17260
|
+
{
|
|
17261
|
+
type: "text",
|
|
17262
|
+
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.`
|
|
17263
|
+
}
|
|
17264
|
+
]
|
|
17265
|
+
};
|
|
17266
|
+
}
|
|
16540
17267
|
try {
|
|
16541
17268
|
const body = { chat_id, text };
|
|
16542
17269
|
if (reply_to_message_id) body.reply_to_message_id = Number(reply_to_message_id);
|
|
@@ -16867,6 +17594,73 @@ async function ackCallbackQuery(callbackQueryId, text) {
|
|
|
16867
17594
|
}
|
|
16868
17595
|
}
|
|
16869
17596
|
await mcp.connect(new StdioServerTransport());
|
|
17597
|
+
var REPLAY_SCAN_INTERVAL_MS = 6e4;
|
|
17598
|
+
async function replayPendingTelegramMarkers() {
|
|
17599
|
+
if (!channelReplayEnabled()) return;
|
|
17600
|
+
if (!PENDING_INBOUND_DIR || !existsSync2(PENDING_INBOUND_DIR)) return;
|
|
17601
|
+
const probe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
17602
|
+
const sessionAlive = probe.tmux === "alive" && probe.claude === "alive";
|
|
17603
|
+
if (!sessionAlive) return;
|
|
17604
|
+
let filenames;
|
|
17605
|
+
try {
|
|
17606
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
17607
|
+
} catch {
|
|
17608
|
+
return;
|
|
17609
|
+
}
|
|
17610
|
+
const now = Date.now();
|
|
17611
|
+
const entries = [];
|
|
17612
|
+
for (const name of filenames) {
|
|
17613
|
+
if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
|
|
17614
|
+
const fullPath = join4(PENDING_INBOUND_DIR, name);
|
|
17615
|
+
let marker;
|
|
17616
|
+
try {
|
|
17617
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
17618
|
+
} catch {
|
|
17619
|
+
continue;
|
|
17620
|
+
}
|
|
17621
|
+
const t = Date.parse(marker.received_at ?? "");
|
|
17622
|
+
const ageMs = Number.isFinite(t) ? now - t : 0;
|
|
17623
|
+
entries.push({ path: fullPath, marker, ageMs });
|
|
17624
|
+
}
|
|
17625
|
+
entries.sort((a, b) => b.ageMs - a.ageMs);
|
|
17626
|
+
for (const { path, marker, ageMs } of entries) {
|
|
17627
|
+
if (!shouldReplayMarker({
|
|
17628
|
+
enabled: true,
|
|
17629
|
+
hasPayload: Boolean(marker.payload),
|
|
17630
|
+
sessionAlive,
|
|
17631
|
+
markerAgeMs: ageMs,
|
|
17632
|
+
replayCount: marker.replay_count ?? 0
|
|
17633
|
+
})) {
|
|
17634
|
+
continue;
|
|
17635
|
+
}
|
|
17636
|
+
try {
|
|
17637
|
+
await mcp.notification({
|
|
17638
|
+
method: "notifications/claude/channel",
|
|
17639
|
+
params: marker.payload
|
|
17640
|
+
});
|
|
17641
|
+
} catch (err) {
|
|
17642
|
+
process.stderr.write(
|
|
17643
|
+
`telegram-channel(${AGENT_CODE_NAME}): replay push failed: ${err.message}
|
|
17644
|
+
`
|
|
17645
|
+
);
|
|
17646
|
+
continue;
|
|
17647
|
+
}
|
|
17648
|
+
try {
|
|
17649
|
+
if (existsSync2(path)) {
|
|
17650
|
+
const updated = {
|
|
17651
|
+
...marker,
|
|
17652
|
+
replay_count: (marker.replay_count ?? 0) + 1
|
|
17653
|
+
};
|
|
17654
|
+
writeFileSync3(path, JSON.stringify(updated), { mode: 384 });
|
|
17655
|
+
}
|
|
17656
|
+
} catch {
|
|
17657
|
+
}
|
|
17658
|
+
}
|
|
17659
|
+
}
|
|
17660
|
+
var replayTimer = setInterval(() => {
|
|
17661
|
+
void replayPendingTelegramMarkers();
|
|
17662
|
+
}, REPLAY_SCAN_INTERVAL_MS);
|
|
17663
|
+
replayTimer.unref?.();
|
|
16870
17664
|
var LONG_POLL_SECONDS = 50;
|
|
16871
17665
|
var POLL_HTTP_TIMEOUT_MS = (LONG_POLL_SECONDS + 10) * 1e3;
|
|
16872
17666
|
var TELEGRAM_DOWNLOAD_TIMEOUT_MS = 15e3;
|
|
@@ -17032,6 +17826,35 @@ async function pollLoop() {
|
|
|
17032
17826
|
} catch (err) {
|
|
17033
17827
|
process.stderr.write(
|
|
17034
17828
|
`telegram-channel(${AGENT_CODE_NAME}): /restart verification-failed ack send failed: ${redactAugmentedPaths(err.message)}
|
|
17829
|
+
`
|
|
17830
|
+
);
|
|
17831
|
+
}
|
|
17832
|
+
}
|
|
17833
|
+
continue;
|
|
17834
|
+
}
|
|
17835
|
+
if (isInvestigateSyntax(trimmedContent)) {
|
|
17836
|
+
const disposition = await classifyInvestigateCommand(trimmedContent);
|
|
17837
|
+
if (disposition === "act") {
|
|
17838
|
+
await handleInvestigateCommand({
|
|
17839
|
+
chatId,
|
|
17840
|
+
messageId: String(msg.message_id),
|
|
17841
|
+
chatType: msg.chat.type ?? null,
|
|
17842
|
+
senderId: msg.from?.id != null ? String(msg.from.id) : null
|
|
17843
|
+
});
|
|
17844
|
+
} else if (disposition === "verification_failed") {
|
|
17845
|
+
try {
|
|
17846
|
+
await telegramApiCall(
|
|
17847
|
+
"sendMessage",
|
|
17848
|
+
{
|
|
17849
|
+
chat_id: chatId,
|
|
17850
|
+
text: `\u274C Couldn't verify the investigate target. Please retry in a few seconds.`,
|
|
17851
|
+
reply_to_message_id: Number(msg.message_id)
|
|
17852
|
+
},
|
|
17853
|
+
1e4
|
|
17854
|
+
);
|
|
17855
|
+
} catch (err) {
|
|
17856
|
+
process.stderr.write(
|
|
17857
|
+
`telegram-channel(${AGENT_CODE_NAME}): /investigate verification-failed ack send failed: ${redactAugmentedPaths(err.message)}
|
|
17035
17858
|
`
|
|
17036
17859
|
);
|
|
17037
17860
|
}
|
|
@@ -17141,20 +17964,28 @@ async function pollLoop() {
|
|
|
17141
17964
|
}
|
|
17142
17965
|
const messageId = String(msg.message_id);
|
|
17143
17966
|
const ackProbe = process.env.TMUX && AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? probeAgentSessionCached(AGENT_CODE_NAME) : { tmux: "unknown", claude: "unknown" };
|
|
17967
|
+
let paneLogFreshAgeMs = null;
|
|
17968
|
+
if (AGENT_DIR) {
|
|
17969
|
+
try {
|
|
17970
|
+
const paneMtimeMs = statSync(join4(AGENT_DIR, "pane.log")).mtimeMs;
|
|
17971
|
+
paneLogFreshAgeMs = Math.max(0, Date.now() - paneMtimeMs);
|
|
17972
|
+
} catch {
|
|
17973
|
+
}
|
|
17974
|
+
}
|
|
17144
17975
|
const ackDecision = decideAckReaction({
|
|
17145
17976
|
hasTarget: Boolean(chatId && messageId),
|
|
17146
17977
|
integrationReady: Boolean(BOT_TOKEN),
|
|
17147
17978
|
tmux: ackProbe.tmux,
|
|
17148
17979
|
claude: ackProbe.claude,
|
|
17149
17980
|
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17150
|
-
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR)
|
|
17981
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR),
|
|
17982
|
+
paneLogFreshAgeMs
|
|
17151
17983
|
});
|
|
17152
17984
|
if (ackDecision === "ack") {
|
|
17153
17985
|
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
17154
17986
|
} else if (ackDecision === "undeliverable") {
|
|
17155
17987
|
void notifyUndeliverable(chatId, messageId);
|
|
17156
17988
|
}
|
|
17157
|
-
trackPendingMessage(chatId, messageId, msg.chat.type, ackDecision === "undeliverable");
|
|
17158
17989
|
const fileMeta = [];
|
|
17159
17990
|
for (const attachment of classifiedAttachments) {
|
|
17160
17991
|
if (attachment.kind === "image") {
|
|
@@ -17182,30 +18013,38 @@ async function pollLoop() {
|
|
|
17182
18013
|
}
|
|
17183
18014
|
const downloadedImages = fileMeta.filter((f) => f.kind === "image" && typeof f.path === "string");
|
|
17184
18015
|
const imagePath = downloadedImages.length === 1 ? downloadedImages[0].path : void 0;
|
|
18016
|
+
const channelPayload = {
|
|
18017
|
+
content,
|
|
18018
|
+
meta: {
|
|
18019
|
+
source: "telegram",
|
|
18020
|
+
chat_id: chatId,
|
|
18021
|
+
chat_type: msg.chat.type,
|
|
18022
|
+
message_id: messageId,
|
|
18023
|
+
user: userId,
|
|
18024
|
+
user_name: userName,
|
|
18025
|
+
ts: String(msg.date),
|
|
18026
|
+
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
18027
|
+
...imagePath ? { image_path: imagePath } : {},
|
|
18028
|
+
// ENG-4902: peer-agent ingress carries distinct framing so the
|
|
18029
|
+
// runtime (MVP #5) can apply the peer-agent system preamble
|
|
18030
|
+
// and store the turn under a peer_agent memory role.
|
|
18031
|
+
...peerAgentMeta ? {
|
|
18032
|
+
source_role: "agent",
|
|
18033
|
+
peer_code_name: peerAgentMeta.code_name,
|
|
18034
|
+
peer_agent_id: peerAgentMeta.agent_id
|
|
18035
|
+
} : {}
|
|
18036
|
+
}
|
|
18037
|
+
};
|
|
18038
|
+
trackPendingMessage(
|
|
18039
|
+
chatId,
|
|
18040
|
+
messageId,
|
|
18041
|
+
msg.chat.type,
|
|
18042
|
+
ackDecision === "undeliverable",
|
|
18043
|
+
channelPayload
|
|
18044
|
+
);
|
|
17185
18045
|
await mcp.notification({
|
|
17186
18046
|
method: "notifications/claude/channel",
|
|
17187
|
-
params:
|
|
17188
|
-
content,
|
|
17189
|
-
meta: {
|
|
17190
|
-
source: "telegram",
|
|
17191
|
-
chat_id: chatId,
|
|
17192
|
-
chat_type: msg.chat.type,
|
|
17193
|
-
message_id: messageId,
|
|
17194
|
-
user: userId,
|
|
17195
|
-
user_name: userName,
|
|
17196
|
-
ts: String(msg.date),
|
|
17197
|
-
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
17198
|
-
...imagePath ? { image_path: imagePath } : {},
|
|
17199
|
-
// ENG-4902: peer-agent ingress carries distinct framing so the
|
|
17200
|
-
// runtime (MVP #5) can apply the peer-agent system preamble
|
|
17201
|
-
// and store the turn under a peer_agent memory role.
|
|
17202
|
-
...peerAgentMeta ? {
|
|
17203
|
-
source_role: "agent",
|
|
17204
|
-
peer_code_name: peerAgentMeta.code_name,
|
|
17205
|
-
peer_agent_id: peerAgentMeta.agent_id
|
|
17206
|
-
} : {}
|
|
17207
|
-
}
|
|
17208
|
-
}
|
|
18047
|
+
params: channelPayload
|
|
17209
18048
|
});
|
|
17210
18049
|
conversationIngestClient?.ingest({
|
|
17211
18050
|
channel: "telegram",
|
|
@@ -17273,6 +18112,24 @@ process.stderr.write(
|
|
|
17273
18112
|
`telegram-channel(${AGENT_CODE_NAME}): started, long-polling getUpdates
|
|
17274
18113
|
`
|
|
17275
18114
|
);
|
|
18115
|
+
void (async () => {
|
|
18116
|
+
for (const registration of buildCommandRegistrations()) {
|
|
18117
|
+
try {
|
|
18118
|
+
const resp = await telegramApiCall("setMyCommands", registration, 1e4);
|
|
18119
|
+
if (!resp.ok) {
|
|
18120
|
+
process.stderr.write(
|
|
18121
|
+
`telegram-channel(${AGENT_CODE_NAME}): setMyCommands(${registration.scope.type}) rejected: ${resp.description ?? "unknown"}
|
|
18122
|
+
`
|
|
18123
|
+
);
|
|
18124
|
+
}
|
|
18125
|
+
} catch (err) {
|
|
18126
|
+
process.stderr.write(
|
|
18127
|
+
`telegram-channel(${AGENT_CODE_NAME}): setMyCommands(${registration.scope.type}) failed: ${redactAugmentedPaths(err.message)}
|
|
18128
|
+
`
|
|
18129
|
+
);
|
|
18130
|
+
}
|
|
18131
|
+
}
|
|
18132
|
+
})();
|
|
17276
18133
|
pollLoop().catch((err) => {
|
|
17277
18134
|
process.stderr.write(
|
|
17278
18135
|
`telegram-channel(${AGENT_CODE_NAME}): poll loop died: ${err.message}
|
|
@@ -17282,5 +18139,7 @@ pollLoop().catch((err) => {
|
|
|
17282
18139
|
});
|
|
17283
18140
|
export {
|
|
17284
18141
|
__resetBackOnlineNoticeThrottle,
|
|
17285
|
-
|
|
18142
|
+
__resetGiveUpNoticeStateForTests,
|
|
18143
|
+
__resetUndeliverableNoticeThrottle,
|
|
18144
|
+
_resetQueuedReplyCountsForTests
|
|
17286
18145
|
};
|