@integrity-labs/agt-cli 0.27.8 → 0.27.9-test.12
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 +946 -129
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-YSBGIXJG.js → chunk-I2OTQJH3.js} +361 -26
- package/dist/chunk-I2OTQJH3.js.map +1 -0
- package/dist/{chunk-GN4XPQWJ.js → chunk-MQJ5DMPT.js} +535 -94
- package/dist/chunk-MQJ5DMPT.js.map +1 -0
- package/dist/{chunk-IB655E5U.js → chunk-SXBZDUKN.js} +1086 -285
- package/dist/chunk-SXBZDUKN.js.map +1 -0
- package/dist/{claude-pair-runtime-ZBQKBBMT.js → claude-pair-runtime-F6USL3EW.js} +2 -2
- package/dist/lib/manager-worker.js +2071 -517
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +49 -455
- package/dist/mcp/index.js +232 -137
- package/dist/mcp/slack-channel.js +1379 -856
- package/dist/mcp/teams-channel.js +16018 -0
- package/dist/mcp/telegram-channel.js +1264 -618
- package/dist/{persistent-session-ICYFLUAM.js → persistent-session-OL5EDYB4.js} +5 -3
- package/dist/responsiveness-probe-B6LJJRUD.js +74 -0
- package/dist/responsiveness-probe-B6LJJRUD.js.map +1 -0
- package/package.json +2 -2
- package/dist/chunk-GN4XPQWJ.js.map +0 -1
- package/dist/chunk-IB655E5U.js.map +0 -1
- package/dist/chunk-YSBGIXJG.js.map +0 -1
- package/dist/responsiveness-probe-WZNQ2762.js +0 -33
- package/dist/responsiveness-probe-WZNQ2762.js.map +0 -1
- /package/dist/{claude-pair-runtime-ZBQKBBMT.js.map → claude-pair-runtime-F6USL3EW.js.map} +0 -0
- /package/dist/{persistent-session-ICYFLUAM.js.map → persistent-session-OL5EDYB4.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,
|
|
@@ -14293,13 +14274,13 @@ var StdioServerTransport = class {
|
|
|
14293
14274
|
|
|
14294
14275
|
// src/telegram-channel.ts
|
|
14295
14276
|
import https from "https";
|
|
14296
|
-
import { createHash, randomUUID
|
|
14277
|
+
import { createHash, randomUUID } from "crypto";
|
|
14297
14278
|
import {
|
|
14298
14279
|
createWriteStream,
|
|
14299
14280
|
existsSync as existsSync2,
|
|
14300
14281
|
mkdirSync as mkdirSync3,
|
|
14301
|
-
readFileSync as
|
|
14302
|
-
readdirSync,
|
|
14282
|
+
readFileSync as readFileSync3,
|
|
14283
|
+
readdirSync as readdirSync2,
|
|
14303
14284
|
renameSync as renameSync3,
|
|
14304
14285
|
statSync,
|
|
14305
14286
|
unlinkSync as unlinkSync3,
|
|
@@ -14307,7 +14288,7 @@ import {
|
|
|
14307
14288
|
writeFileSync as writeFileSync3
|
|
14308
14289
|
} from "fs";
|
|
14309
14290
|
import { homedir as homedir2 } from "os";
|
|
14310
|
-
import { join as
|
|
14291
|
+
import { join as join4 } from "path";
|
|
14311
14292
|
|
|
14312
14293
|
// src/channel-attachments.ts
|
|
14313
14294
|
import { homedir } from "os";
|
|
@@ -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
|
-
|
|
14556
|
-
|
|
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" };
|
|
14538
|
+
}
|
|
14539
|
+
if (input.queuedCount >= maxDepth) {
|
|
14540
|
+
return { action: "refuse", recentCount: inWindow.length, reason: "queue_full" };
|
|
14558
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) {
|
|
@@ -15386,465 +15685,87 @@ function createInboundContextClient(args) {
|
|
|
15386
15685
|
};
|
|
15387
15686
|
}
|
|
15388
15687
|
|
|
15389
|
-
// src/
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15396
|
-
const
|
|
15397
|
-
|
|
15398
|
-
const
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
15410
|
-
if (pendingTimer != null) {
|
|
15411
|
-
clearTimer(pendingTimer);
|
|
15412
|
-
pendingTimer = null;
|
|
15413
|
-
}
|
|
15414
|
-
lastFlushAt = now();
|
|
15415
|
-
const promise = opts.flush(snapshotState());
|
|
15416
|
-
inFlight = promise.then(
|
|
15417
|
-
() => {
|
|
15418
|
-
inFlight = null;
|
|
15419
|
-
},
|
|
15420
|
-
(err) => {
|
|
15421
|
-
inFlight = null;
|
|
15422
|
-
throw err;
|
|
15423
|
-
}
|
|
15424
|
-
);
|
|
15425
|
-
await inFlight;
|
|
15426
|
-
}
|
|
15427
|
-
function snapshotState() {
|
|
15428
|
-
return {
|
|
15429
|
-
initialLabel: state.initialLabel,
|
|
15430
|
-
mode: state.mode,
|
|
15431
|
-
stepLabel: state.stepLabel,
|
|
15432
|
-
stepNumber: state.stepNumber ? { ...state.stepNumber } : void 0,
|
|
15433
|
-
detail: state.detail,
|
|
15434
|
-
lastChangeAt: state.lastChangeAt,
|
|
15435
|
-
terminal: state.terminal ? { ...state.terminal } : void 0
|
|
15436
|
-
};
|
|
15437
|
-
}
|
|
15438
|
-
function applyUpdate(next) {
|
|
15439
|
-
if (next.mode !== void 0) state.mode = next.mode;
|
|
15440
|
-
if (next.stepLabel !== void 0) state.stepLabel = next.stepLabel;
|
|
15441
|
-
if (next.stepNumber !== void 0) state.stepNumber = { ...next.stepNumber };
|
|
15442
|
-
if (next.detail !== void 0) state.detail = next.detail;
|
|
15443
|
-
state.lastChangeAt = now();
|
|
15444
|
-
}
|
|
15445
|
-
async function scheduleOrFlush() {
|
|
15446
|
-
const elapsed = now() - lastFlushAt;
|
|
15447
|
-
if (elapsed >= debounceMs) {
|
|
15448
|
-
await doFlush();
|
|
15449
|
-
return;
|
|
15450
|
-
}
|
|
15451
|
-
if (pendingTimer != null) return;
|
|
15452
|
-
const remaining = debounceMs - elapsed;
|
|
15453
|
-
pendingTimer = setTimer(() => {
|
|
15454
|
-
pendingTimer = null;
|
|
15455
|
-
void doFlush().catch(() => {
|
|
15456
|
-
});
|
|
15457
|
-
}, remaining);
|
|
15458
|
-
}
|
|
15459
|
-
lastFlushAt = now();
|
|
15460
|
-
await opts.flush(snapshotState());
|
|
15688
|
+
// src/impersonation.ts
|
|
15689
|
+
var ENV_VAR = "AGT_ACT_AS_AGENT_ID";
|
|
15690
|
+
var OVERRIDE_ENV_VAR = "ENABLE_IMPERSONATION_CHANNELS";
|
|
15691
|
+
function isImpersonating() {
|
|
15692
|
+
return impersonatedAgentId() !== null;
|
|
15693
|
+
}
|
|
15694
|
+
function impersonatedAgentId() {
|
|
15695
|
+
const raw = process.env[ENV_VAR];
|
|
15696
|
+
if (typeof raw !== "string") return null;
|
|
15697
|
+
const trimmed = raw.trim();
|
|
15698
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
15699
|
+
}
|
|
15700
|
+
function channelsEnabledOverride() {
|
|
15701
|
+
const raw = process.env[OVERRIDE_ENV_VAR];
|
|
15702
|
+
if (typeof raw !== "string") return false;
|
|
15703
|
+
const trimmed = raw.trim().toLowerCase();
|
|
15704
|
+
return trimmed === "1" || trimmed === "true" || trimmed === "yes" || trimmed === "on";
|
|
15705
|
+
}
|
|
15706
|
+
var IMPERSONATION_REFUSAL_CODE = "CHANNEL.IMPERSONATION_DISABLED";
|
|
15707
|
+
var IMPERSONATION_REFUSAL_MESSAGE = "Posting as an agent under impersonation is disabled in v0 \u2014 quit impersonation with `agt impersonate exit` to send as yourself.";
|
|
15708
|
+
function buildImpersonationRefusal(toolName) {
|
|
15461
15709
|
return {
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
})
|
|
15472
|
-
try {
|
|
15473
|
-
onPostTerminalUpdate(peeked);
|
|
15474
|
-
} catch (err) {
|
|
15475
|
-
console.warn(
|
|
15476
|
-
"[progress-handle] onPostTerminalUpdate threw \u2014 swallowed to keep update() non-throwing:",
|
|
15477
|
-
err
|
|
15478
|
-
);
|
|
15479
|
-
}
|
|
15480
|
-
return;
|
|
15710
|
+
content: [
|
|
15711
|
+
{
|
|
15712
|
+
type: "text",
|
|
15713
|
+
text: JSON.stringify({
|
|
15714
|
+
error: {
|
|
15715
|
+
code: IMPERSONATION_REFUSAL_CODE,
|
|
15716
|
+
message: IMPERSONATION_REFUSAL_MESSAGE,
|
|
15717
|
+
tool: toolName
|
|
15718
|
+
}
|
|
15719
|
+
})
|
|
15481
15720
|
}
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
},
|
|
15485
|
-
async complete(summary) {
|
|
15486
|
-
if (terminal) return;
|
|
15487
|
-
terminal = true;
|
|
15488
|
-
state.terminal = { kind: "completed", message: summary };
|
|
15489
|
-
state.lastChangeAt = now();
|
|
15490
|
-
await doFlush();
|
|
15491
|
-
},
|
|
15492
|
-
async fail(reason) {
|
|
15493
|
-
if (terminal) return;
|
|
15494
|
-
terminal = true;
|
|
15495
|
-
state.terminal = { kind: "failed", message: reason };
|
|
15496
|
-
state.lastChangeAt = now();
|
|
15497
|
-
await doFlush();
|
|
15498
|
-
},
|
|
15499
|
-
isTerminal: () => terminal
|
|
15721
|
+
],
|
|
15722
|
+
isError: true
|
|
15500
15723
|
};
|
|
15501
15724
|
}
|
|
15502
|
-
function defaultPostTerminalWarn(state) {
|
|
15503
|
-
console.warn(
|
|
15504
|
-
`[progress-handle] update() called after terminal transition (${state.terminal?.kind}) \u2014 ignored.`
|
|
15505
|
-
);
|
|
15506
|
-
}
|
|
15507
15725
|
|
|
15508
|
-
// src/
|
|
15509
|
-
var
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
description: 'Short top-line label for the task (e.g. "diagnose vigil"). Stays constant for the lifetime of this progress anchor.'
|
|
15536
|
-
},
|
|
15537
|
-
initial_mode: {
|
|
15538
|
-
type: "string",
|
|
15539
|
-
enum: ["thinking", "working", "waiting"],
|
|
15540
|
-
description: 'Initial mode emoji \u2014 defaults to "working".'
|
|
15541
|
-
},
|
|
15542
|
-
...startArgsSchema.properties
|
|
15543
|
-
},
|
|
15544
|
-
required: ["label", ...startArgsSchema.required]
|
|
15545
|
-
}
|
|
15546
|
-
},
|
|
15547
|
-
{
|
|
15548
|
-
name: this.toolNames.update,
|
|
15549
|
-
description: `Update the progress anchor in place. The state machine debounces calls to one ${surfaceDescription} API call ${debounceText}, so spamming this every step is safe \u2014 only the latest pending state will paint. Any subset of fields can be passed; omitted ones retain their previous value.`,
|
|
15550
|
-
inputSchema: {
|
|
15551
|
-
type: "object",
|
|
15552
|
-
properties: {
|
|
15553
|
-
progress_id: { type: "string", description: "The progress_id returned from start." },
|
|
15554
|
-
step_label: { type: "string", description: 'Short label for the current step (e.g. "Querying CloudWatch logs").' },
|
|
15555
|
-
step_current: { type: "number", description: "1-based step counter \u2014 current." },
|
|
15556
|
-
step_total: { type: "number", description: "Total steps if known. Omit for open-ended progress." },
|
|
15557
|
-
mode: { type: "string", enum: ["thinking", "working", "waiting"] },
|
|
15558
|
-
detail: { type: "string", description: "Free-text detail line under the step label." }
|
|
15559
|
-
},
|
|
15560
|
-
required: ["progress_id"]
|
|
15561
|
-
}
|
|
15562
|
-
},
|
|
15563
|
-
{
|
|
15564
|
-
name: this.toolNames.complete,
|
|
15565
|
-
description: `Mark the progress anchor as \u2705 completed. Flushes any pending debounced state before painting the terminal banner \u2014 the operator never sees a stale "Step N" beside the \u2705. After this, the handle is dead; further updates are no-ops.`,
|
|
15566
|
-
inputSchema: {
|
|
15567
|
-
type: "object",
|
|
15568
|
-
properties: {
|
|
15569
|
-
progress_id: { type: "string" },
|
|
15570
|
-
summary: { type: "string", description: "Optional one-line summary of what was achieved." }
|
|
15571
|
-
},
|
|
15572
|
-
required: ["progress_id"]
|
|
15573
|
-
}
|
|
15574
|
-
},
|
|
15575
|
-
{
|
|
15576
|
-
name: this.toolNames.fail,
|
|
15577
|
-
description: `Mark the progress anchor as \u274C failed. Always include a one-sentence reason \u2014 the operator can't tell *why* from emoji alone. Same flush-then-paint behaviour as complete.`,
|
|
15578
|
-
inputSchema: {
|
|
15579
|
-
type: "object",
|
|
15580
|
-
properties: {
|
|
15581
|
-
progress_id: { type: "string" },
|
|
15582
|
-
reason: { type: "string", description: "One-sentence failure reason." }
|
|
15583
|
-
},
|
|
15584
|
-
required: ["progress_id", "reason"]
|
|
15585
|
-
}
|
|
15586
|
-
}
|
|
15587
|
-
];
|
|
15588
|
-
}
|
|
15589
|
-
/**
|
|
15590
|
-
* Dispatch a CallToolRequest. Returns `undefined` if the tool name
|
|
15591
|
-
* isn't one of ours (so the caller can keep walking its dispatch
|
|
15592
|
-
* chain). Returns a tool result otherwise.
|
|
15593
|
-
*/
|
|
15594
|
-
async handle(name, args) {
|
|
15595
|
-
if (name === this.toolNames.start) return this.handleStart(args);
|
|
15596
|
-
if (name === this.toolNames.update) return this.handleUpdate(args);
|
|
15597
|
-
if (name === this.toolNames.complete) return this.handleComplete(args);
|
|
15598
|
-
if (name === this.toolNames.fail) return this.handleFail(args);
|
|
15599
|
-
return void 0;
|
|
15600
|
-
}
|
|
15601
|
-
/** Live count of in-flight handles. Exposed for tests + diagnostics. */
|
|
15602
|
-
size() {
|
|
15603
|
-
return this.handles.size;
|
|
15604
|
-
}
|
|
15605
|
-
async handleStart(args) {
|
|
15606
|
-
const label = typeof args.label === "string" ? args.label : null;
|
|
15607
|
-
if (!label) return errResult(`${this.toolNames.start}: 'label' must be a non-empty string`);
|
|
15608
|
-
if ("initial_mode" in args && !isMode(args.initial_mode)) {
|
|
15609
|
-
return errResult(
|
|
15610
|
-
`${this.toolNames.start}: 'initial_mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
15611
|
-
);
|
|
15612
|
-
}
|
|
15613
|
-
for (const k of this.opts.startArgsSchema.required) {
|
|
15614
|
-
if (typeof args[k] !== "string" || args[k].length === 0) {
|
|
15615
|
-
return errResult(`${this.toolNames.start}: '${k}' must be a non-empty string`);
|
|
15616
|
-
}
|
|
15617
|
-
}
|
|
15618
|
-
const initialMode = isMode(args.initial_mode) ? args.initial_mode : void 0;
|
|
15619
|
-
const progressId = randomUUID();
|
|
15620
|
-
try {
|
|
15621
|
-
const flush = this.opts.createFlush(args);
|
|
15622
|
-
const handle = await startProgressHandle({
|
|
15623
|
-
initialLabel: label,
|
|
15624
|
-
initialMode,
|
|
15625
|
-
flush,
|
|
15626
|
-
debounceMs: this.opts.debounceMs ?? 1e3
|
|
15627
|
-
});
|
|
15628
|
-
this.handles.set(progressId, handle);
|
|
15629
|
-
return okResult(JSON.stringify({ progress_id: progressId }));
|
|
15630
|
-
} catch (err) {
|
|
15631
|
-
return errResult(`${this.toolNames.start} failed: ${err.message ?? String(err)}`);
|
|
15632
|
-
}
|
|
15633
|
-
}
|
|
15634
|
-
async handleUpdate(args) {
|
|
15635
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
15636
|
-
if (!id) return errResult(`${this.toolNames.update}: 'progress_id' must be a string`);
|
|
15637
|
-
const handle = this.handles.get(id);
|
|
15638
|
-
if (!handle) return errResult(`${this.toolNames.update}: unknown progress_id (already terminated, or never started)`);
|
|
15639
|
-
if ("mode" in args && !isMode(args.mode)) {
|
|
15640
|
-
return errResult(
|
|
15641
|
-
`${this.toolNames.update}: 'mode' must be one of 'thinking', 'working', or 'waiting'`
|
|
15642
|
-
);
|
|
15643
|
-
}
|
|
15644
|
-
if (typeof args.step_total === "number" && typeof args.step_current !== "number") {
|
|
15645
|
-
return errResult(
|
|
15646
|
-
`${this.toolNames.update}: 'step_total' requires 'step_current'`
|
|
15647
|
-
);
|
|
15648
|
-
}
|
|
15649
|
-
const next = {};
|
|
15650
|
-
if (typeof args.step_label === "string") next.stepLabel = args.step_label;
|
|
15651
|
-
if (typeof args.step_current === "number") {
|
|
15652
|
-
next.stepNumber = {
|
|
15653
|
-
current: args.step_current,
|
|
15654
|
-
...typeof args.step_total === "number" ? { total: args.step_total } : {}
|
|
15655
|
-
};
|
|
15656
|
-
}
|
|
15657
|
-
if (isMode(args.mode)) next.mode = args.mode;
|
|
15658
|
-
if (typeof args.detail === "string") next.detail = args.detail;
|
|
15659
|
-
try {
|
|
15660
|
-
await handle.update(next);
|
|
15661
|
-
return okResult("ok");
|
|
15662
|
-
} catch (err) {
|
|
15663
|
-
return errResult(`${this.toolNames.update} failed: ${err.message ?? String(err)}`);
|
|
15664
|
-
}
|
|
15726
|
+
// src/channel-egress-tools.ts
|
|
15727
|
+
var TELEGRAM_EGRESS_TOOLS = /* @__PURE__ */ new Set([
|
|
15728
|
+
"telegram.reply",
|
|
15729
|
+
"telegram.send_message",
|
|
15730
|
+
"telegram.react",
|
|
15731
|
+
"channel_request_input"
|
|
15732
|
+
]);
|
|
15733
|
+
|
|
15734
|
+
// src/mcp-spawn-lock.ts
|
|
15735
|
+
import {
|
|
15736
|
+
existsSync,
|
|
15737
|
+
mkdirSync as mkdirSync2,
|
|
15738
|
+
readFileSync,
|
|
15739
|
+
renameSync as renameSync2,
|
|
15740
|
+
unlinkSync as unlinkSync2,
|
|
15741
|
+
writeFileSync as writeFileSync2
|
|
15742
|
+
} from "fs";
|
|
15743
|
+
import { join as join2 } from "path";
|
|
15744
|
+
function defaultIsPidAlive(pid) {
|
|
15745
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
15746
|
+
try {
|
|
15747
|
+
process.kill(pid, 0);
|
|
15748
|
+
return true;
|
|
15749
|
+
} catch (err) {
|
|
15750
|
+
const code = err.code;
|
|
15751
|
+
if (code === "ESRCH") return false;
|
|
15752
|
+
return true;
|
|
15665
15753
|
}
|
|
15666
|
-
|
|
15667
|
-
|
|
15668
|
-
|
|
15669
|
-
|
|
15670
|
-
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
return
|
|
15754
|
+
}
|
|
15755
|
+
function acquireMcpSpawnLock(args) {
|
|
15756
|
+
const { agentDir, basename, options = {} } = args;
|
|
15757
|
+
if (!agentDir) return { kind: "no-agent-dir" };
|
|
15758
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
15759
|
+
const selfPid = options.selfPid ?? process.pid;
|
|
15760
|
+
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
15761
|
+
const path = join2(agentDir, basename);
|
|
15762
|
+
const existing = readLockHolder(path);
|
|
15763
|
+
if (existing) {
|
|
15764
|
+
if (existing.pid === selfPid) {
|
|
15765
|
+
return { kind: "acquired", path };
|
|
15678
15766
|
}
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
const id = typeof args.progress_id === "string" ? args.progress_id : null;
|
|
15682
|
-
if (!id) return errResult(`${this.toolNames.fail}: 'progress_id' must be a string`);
|
|
15683
|
-
const reason = typeof args.reason === "string" ? args.reason : null;
|
|
15684
|
-
if (!reason) return errResult(`${this.toolNames.fail}: 'reason' must be a non-empty string`);
|
|
15685
|
-
const handle = this.handles.get(id);
|
|
15686
|
-
if (!handle) return errResult(`${this.toolNames.fail}: unknown progress_id`);
|
|
15687
|
-
try {
|
|
15688
|
-
await handle.fail(reason);
|
|
15689
|
-
this.handles.delete(id);
|
|
15690
|
-
return okResult("ok");
|
|
15691
|
-
} catch (err) {
|
|
15692
|
-
return errResult(`${this.toolNames.fail} failed: ${err.message ?? String(err)}`);
|
|
15693
|
-
}
|
|
15694
|
-
}
|
|
15695
|
-
};
|
|
15696
|
-
function okResult(text) {
|
|
15697
|
-
return { content: [{ type: "text", text }] };
|
|
15698
|
-
}
|
|
15699
|
-
function errResult(text) {
|
|
15700
|
-
return { content: [{ type: "text", text }], isError: true };
|
|
15701
|
-
}
|
|
15702
|
-
function isMode(value) {
|
|
15703
|
-
return value === "thinking" || value === "working" || value === "waiting";
|
|
15704
|
-
}
|
|
15705
|
-
|
|
15706
|
-
// src/telegram-progress.ts
|
|
15707
|
-
var MODE_EMOJI = {
|
|
15708
|
-
thinking: "\u{1F4AD}",
|
|
15709
|
-
working: "\u{1F6E0}\uFE0F",
|
|
15710
|
-
waiting: "\u23F3"
|
|
15711
|
-
};
|
|
15712
|
-
var TERMINAL_EMOJI = {
|
|
15713
|
-
completed: "\u2705",
|
|
15714
|
-
failed: "\u274C"
|
|
15715
|
-
};
|
|
15716
|
-
function formatWallClock(ms, timeZone) {
|
|
15717
|
-
const fmt = new Intl.DateTimeFormat("en-GB", {
|
|
15718
|
-
hour: "2-digit",
|
|
15719
|
-
minute: "2-digit",
|
|
15720
|
-
second: "2-digit",
|
|
15721
|
-
hour12: false,
|
|
15722
|
-
timeZone,
|
|
15723
|
-
timeZoneName: "short"
|
|
15724
|
-
});
|
|
15725
|
-
const parts = fmt.formatToParts(new Date(ms));
|
|
15726
|
-
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
15727
|
-
return `${get("hour")}:${get("minute")}:${get("second")} ${get("timeZoneName")}`;
|
|
15728
|
-
}
|
|
15729
|
-
function escapeHtml(s) {
|
|
15730
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
15731
|
-
}
|
|
15732
|
-
function renderProgressHtml(state) {
|
|
15733
|
-
const lines = [];
|
|
15734
|
-
if (state.terminal) {
|
|
15735
|
-
const banner = state.terminal.kind === "completed" ? "Done" : "Failed";
|
|
15736
|
-
lines.push(`${TERMINAL_EMOJI[state.terminal.kind]} <b>${banner}</b> \u2014 ${escapeHtml(state.initialLabel)}`);
|
|
15737
|
-
if (state.terminal.message) lines.push(escapeHtml(state.terminal.message));
|
|
15738
|
-
const verb = state.terminal.kind === "completed" ? "completed" : "failed";
|
|
15739
|
-
lines.push(`<i>${verb}: ${formatWallClock(state.lastChangeAt)}</i>`);
|
|
15740
|
-
return lines.join("\n");
|
|
15741
|
-
}
|
|
15742
|
-
lines.push(`${MODE_EMOJI[state.mode]} <b>Working on:</b> ${escapeHtml(state.initialLabel)}`);
|
|
15743
|
-
if (state.stepNumber || state.stepLabel) {
|
|
15744
|
-
const parts = [];
|
|
15745
|
-
if (state.stepNumber) {
|
|
15746
|
-
if (state.stepNumber.total != null) {
|
|
15747
|
-
parts.push(`<b>Step ${state.stepNumber.current} of ${state.stepNumber.total}</b>`);
|
|
15748
|
-
} else {
|
|
15749
|
-
parts.push(`<b>Step ${state.stepNumber.current}</b>`);
|
|
15750
|
-
}
|
|
15751
|
-
}
|
|
15752
|
-
if (state.stepLabel) parts.push(escapeHtml(state.stepLabel));
|
|
15753
|
-
lines.push(parts.join(": "));
|
|
15754
|
-
}
|
|
15755
|
-
if (state.detail) lines.push(`<i>${escapeHtml(state.detail)}</i>`);
|
|
15756
|
-
lines.push(`<i>last update: ${formatWallClock(state.lastChangeAt)}</i>`);
|
|
15757
|
-
return lines.join("\n");
|
|
15758
|
-
}
|
|
15759
|
-
var TelegramApiError = class extends Error {
|
|
15760
|
-
constructor(endpoint, apiError) {
|
|
15761
|
-
super(`Telegram ${endpoint} failed: ${apiError}`);
|
|
15762
|
-
this.endpoint = endpoint;
|
|
15763
|
-
this.apiError = apiError;
|
|
15764
|
-
this.name = "TelegramApiError";
|
|
15765
|
-
}
|
|
15766
|
-
};
|
|
15767
|
-
function isMessageNotModified(description) {
|
|
15768
|
-
if (!description) return false;
|
|
15769
|
-
return /message is not modified/i.test(description);
|
|
15770
|
-
}
|
|
15771
|
-
function createTelegramProgressFlush(opts) {
|
|
15772
|
-
let anchorMessageId = null;
|
|
15773
|
-
return async function flush(state) {
|
|
15774
|
-
const text = renderProgressHtml(state);
|
|
15775
|
-
if (anchorMessageId == null) {
|
|
15776
|
-
const body = {
|
|
15777
|
-
chat_id: opts.chatId,
|
|
15778
|
-
text,
|
|
15779
|
-
parse_mode: "HTML",
|
|
15780
|
-
disable_notification: true
|
|
15781
|
-
};
|
|
15782
|
-
if (opts.messageThreadId != null) body.message_thread_id = opts.messageThreadId;
|
|
15783
|
-
if (opts.replyToMessageId != null) body.reply_to_message_id = opts.replyToMessageId;
|
|
15784
|
-
const res = await opts.callImpl("sendMessage", body);
|
|
15785
|
-
if (!res.ok || !res.result?.message_id) {
|
|
15786
|
-
throw new TelegramApiError("sendMessage", res.description ?? "missing message_id");
|
|
15787
|
-
}
|
|
15788
|
-
anchorMessageId = res.result.message_id;
|
|
15789
|
-
opts.onAnchorPosted?.(res.result.message_id);
|
|
15790
|
-
return;
|
|
15791
|
-
}
|
|
15792
|
-
try {
|
|
15793
|
-
const res = await opts.callImpl("editMessageText", {
|
|
15794
|
-
chat_id: opts.chatId,
|
|
15795
|
-
message_id: anchorMessageId,
|
|
15796
|
-
text,
|
|
15797
|
-
parse_mode: "HTML"
|
|
15798
|
-
});
|
|
15799
|
-
if (!res.ok) {
|
|
15800
|
-
if (isMessageNotModified(res.description)) return;
|
|
15801
|
-
throw new TelegramApiError("editMessageText", res.description ?? "unknown");
|
|
15802
|
-
}
|
|
15803
|
-
} catch (err) {
|
|
15804
|
-
if (opts.onApiError && err instanceof TelegramApiError) {
|
|
15805
|
-
await opts.onApiError(err);
|
|
15806
|
-
return;
|
|
15807
|
-
}
|
|
15808
|
-
throw err;
|
|
15809
|
-
}
|
|
15810
|
-
};
|
|
15811
|
-
}
|
|
15812
|
-
|
|
15813
|
-
// src/mcp-spawn-lock.ts
|
|
15814
|
-
import {
|
|
15815
|
-
existsSync,
|
|
15816
|
-
mkdirSync as mkdirSync2,
|
|
15817
|
-
readFileSync,
|
|
15818
|
-
renameSync as renameSync2,
|
|
15819
|
-
unlinkSync as unlinkSync2,
|
|
15820
|
-
writeFileSync as writeFileSync2
|
|
15821
|
-
} from "fs";
|
|
15822
|
-
import { join as join2 } from "path";
|
|
15823
|
-
function defaultIsPidAlive(pid) {
|
|
15824
|
-
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
15825
|
-
try {
|
|
15826
|
-
process.kill(pid, 0);
|
|
15827
|
-
return true;
|
|
15828
|
-
} catch (err) {
|
|
15829
|
-
const code = err.code;
|
|
15830
|
-
if (code === "ESRCH") return false;
|
|
15831
|
-
return true;
|
|
15832
|
-
}
|
|
15833
|
-
}
|
|
15834
|
-
function acquireMcpSpawnLock(args) {
|
|
15835
|
-
const { agentDir, basename, options = {} } = args;
|
|
15836
|
-
if (!agentDir) return { kind: "no-agent-dir" };
|
|
15837
|
-
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
15838
|
-
const selfPid = options.selfPid ?? process.pid;
|
|
15839
|
-
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
15840
|
-
const path = join2(agentDir, basename);
|
|
15841
|
-
const existing = readLockHolder(path);
|
|
15842
|
-
if (existing) {
|
|
15843
|
-
if (existing.pid === selfPid) {
|
|
15844
|
-
return { kind: "acquired", path };
|
|
15845
|
-
}
|
|
15846
|
-
if (isPidAlive(existing.pid)) {
|
|
15847
|
-
return { kind: "blocked", path, holder: existing };
|
|
15767
|
+
if (isPidAlive(existing.pid)) {
|
|
15768
|
+
return { kind: "blocked", path, holder: existing };
|
|
15848
15769
|
}
|
|
15849
15770
|
}
|
|
15850
15771
|
mkdirSync2(agentDir, { recursive: true, mode: 448 });
|
|
@@ -15879,19 +15800,133 @@ function readLockHolder(path) {
|
|
|
15879
15800
|
}
|
|
15880
15801
|
}
|
|
15881
15802
|
|
|
15803
|
+
// src/ack-reaction.ts
|
|
15804
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
15805
|
+
import { join as join3 } from "path";
|
|
15806
|
+
var REPLY_WEDGED_THRESHOLD_MS = 5 * 60 * 1e3;
|
|
15807
|
+
var ACK_STARTUP_GRACE_MS = 6e4;
|
|
15808
|
+
var ACK_PANE_FRESH_THRESHOLD_MS = 6e4;
|
|
15809
|
+
function decideAckReaction(i) {
|
|
15810
|
+
if (!i.hasTarget) return "none";
|
|
15811
|
+
if (!i.integrationReady) return "undeliverable";
|
|
15812
|
+
if (i.tmux === "dead") return "undeliverable";
|
|
15813
|
+
if (!i.withinStartupGrace && i.claude === "dead") return "undeliverable";
|
|
15814
|
+
const threshold = i.pendingStaleThresholdMs ?? REPLY_WEDGED_THRESHOLD_MS;
|
|
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
|
+
}
|
|
15821
|
+
return "undeliverable";
|
|
15822
|
+
}
|
|
15823
|
+
return "ack";
|
|
15824
|
+
}
|
|
15825
|
+
function decideRecoveryHeal(i) {
|
|
15826
|
+
return i.wasUndeliverable && i.hasTarget ? "heal" : "none";
|
|
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
|
+
}
|
|
15859
|
+
function oldestPendingMarkerAgeMs(dir, now = Date.now()) {
|
|
15860
|
+
if (!dir) return null;
|
|
15861
|
+
let names;
|
|
15862
|
+
try {
|
|
15863
|
+
names = readdirSync(dir);
|
|
15864
|
+
} catch {
|
|
15865
|
+
return null;
|
|
15866
|
+
}
|
|
15867
|
+
let oldest = null;
|
|
15868
|
+
for (const name of names) {
|
|
15869
|
+
if (!name.endsWith(".json")) continue;
|
|
15870
|
+
let receivedAt;
|
|
15871
|
+
try {
|
|
15872
|
+
const raw = JSON.parse(readFileSync2(join3(dir, name), "utf-8"));
|
|
15873
|
+
receivedAt = raw.received_at;
|
|
15874
|
+
} catch {
|
|
15875
|
+
continue;
|
|
15876
|
+
}
|
|
15877
|
+
if (!receivedAt) continue;
|
|
15878
|
+
const t = Date.parse(receivedAt);
|
|
15879
|
+
if (Number.isNaN(t)) continue;
|
|
15880
|
+
const age = now - t;
|
|
15881
|
+
if (age < 0) continue;
|
|
15882
|
+
if (oldest == null || age > oldest) oldest = age;
|
|
15883
|
+
}
|
|
15884
|
+
return oldest;
|
|
15885
|
+
}
|
|
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);
|
|
15912
|
+
}
|
|
15913
|
+
|
|
15882
15914
|
// src/telegram-channel.ts
|
|
15883
15915
|
function redactId(id) {
|
|
15884
15916
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
15885
15917
|
}
|
|
15886
15918
|
var BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
15887
15919
|
var AGENT_CODE_NAME = process.env.AGT_AGENT_CODE_NAME ?? "unknown";
|
|
15888
|
-
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
15920
|
+
var TELEGRAM_AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15889
15921
|
var AGT_HOST = process.env.AGT_HOST ?? null;
|
|
15890
15922
|
var AGT_API_KEY = process.env.AGT_API_KEY ?? null;
|
|
15891
15923
|
var AGT_AGENT_ID = process.env.AGT_AGENT_ID ?? null;
|
|
15892
15924
|
var ALLOWED_CHATS = new Set(
|
|
15893
15925
|
(process.env.TELEGRAM_ALLOWED_CHATS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15894
15926
|
);
|
|
15927
|
+
var DIAGNOSTIC_CHAT_IDS = new Set(
|
|
15928
|
+
(process.env.TELEGRAM_DIAGNOSTIC_CHAT_IDS ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
15929
|
+
);
|
|
15895
15930
|
var PEER_CLASSIFIER_CONFIG = {
|
|
15896
15931
|
peer_agent_mode: parsePeerAgentModeEnv(process.env.TELEGRAM_PEER_AGENT_MODE),
|
|
15897
15932
|
peer_group_ids: parsePeerGroupIdsEnv(process.env.TELEGRAM_PEER_GROUP_IDS),
|
|
@@ -15927,6 +15962,13 @@ var crossTeamPeerAuditClient = createCrossTeamPeerAuditClient({
|
|
|
15927
15962
|
log: (line) => process.stderr.write(`telegram-channel(${AGENT_CODE_NAME}): ${line}
|
|
15928
15963
|
`)
|
|
15929
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
|
+
});
|
|
15930
15972
|
var observedChatClient = createObservedChatClient({
|
|
15931
15973
|
agtHost: AGT_HOST,
|
|
15932
15974
|
agtApiKey: AGT_API_KEY,
|
|
@@ -15969,9 +16011,9 @@ if (!BOT_TOKEN) {
|
|
|
15969
16011
|
var stderrLogStream = null;
|
|
15970
16012
|
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
15971
16013
|
try {
|
|
15972
|
-
const logDir =
|
|
16014
|
+
const logDir = join4(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
15973
16015
|
mkdirSync3(logDir, { recursive: true });
|
|
15974
|
-
stderrLogStream = createWriteStream(
|
|
16016
|
+
stderrLogStream = createWriteStream(join4(logDir, "telegram-channel-stderr.log"), {
|
|
15975
16017
|
flags: "a",
|
|
15976
16018
|
mode: 384
|
|
15977
16019
|
});
|
|
@@ -16052,14 +16094,82 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
16052
16094
|
);
|
|
16053
16095
|
}
|
|
16054
16096
|
}
|
|
16055
|
-
var
|
|
16097
|
+
var lastUndeliverableNoticeAt = /* @__PURE__ */ new Map();
|
|
16098
|
+
async function notifyUndeliverable(chatId, messageId) {
|
|
16099
|
+
const key2 = String(chatId);
|
|
16100
|
+
const now = Date.now();
|
|
16101
|
+
if (!shouldPostUndeliverableNotice(lastUndeliverableNoticeAt.get(key2), now)) return;
|
|
16102
|
+
lastUndeliverableNoticeAt.set(key2, now);
|
|
16103
|
+
try {
|
|
16104
|
+
const resp = await telegramApiCall(
|
|
16105
|
+
"sendMessage",
|
|
16106
|
+
{
|
|
16107
|
+
chat_id: chatId,
|
|
16108
|
+
// ENG-6025: shared copy with the Slack undeliverable notice.
|
|
16109
|
+
text: undeliverableNoticeText(),
|
|
16110
|
+
reply_to_message_id: Number(messageId),
|
|
16111
|
+
allow_sending_without_reply: true
|
|
16112
|
+
},
|
|
16113
|
+
1e4
|
|
16114
|
+
);
|
|
16115
|
+
if (!resp.ok) {
|
|
16116
|
+
process.stderr.write(
|
|
16117
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
16118
|
+
`
|
|
16119
|
+
);
|
|
16120
|
+
}
|
|
16121
|
+
} catch (err) {
|
|
16122
|
+
process.stderr.write(
|
|
16123
|
+
`telegram-channel(${AGENT_CODE_NAME}): undeliverable notice error: ${err.message}
|
|
16124
|
+
`
|
|
16125
|
+
);
|
|
16126
|
+
}
|
|
16127
|
+
}
|
|
16128
|
+
function __resetUndeliverableNoticeThrottle() {
|
|
16129
|
+
lastUndeliverableNoticeAt.clear();
|
|
16130
|
+
}
|
|
16131
|
+
var lastBackOnlineNoticeAt = /* @__PURE__ */ new Map();
|
|
16132
|
+
function notifyBackOnline(chatId) {
|
|
16133
|
+
const key2 = String(chatId);
|
|
16134
|
+
const now = Date.now();
|
|
16135
|
+
if (!shouldPostUndeliverableNotice(lastBackOnlineNoticeAt.get(key2), now)) return;
|
|
16136
|
+
lastBackOnlineNoticeAt.set(key2, now);
|
|
16137
|
+
void (async () => {
|
|
16138
|
+
try {
|
|
16139
|
+
const resp = await telegramApiCall(
|
|
16140
|
+
"sendMessage",
|
|
16141
|
+
{
|
|
16142
|
+
chat_id: chatId,
|
|
16143
|
+
text: "\u2705 Back online \u2014 catching up on what I missed."
|
|
16144
|
+
},
|
|
16145
|
+
1e4
|
|
16146
|
+
);
|
|
16147
|
+
if (!resp.ok) {
|
|
16148
|
+
process.stderr.write(
|
|
16149
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice failed (chat=${redactId(chatId)}): ${resp.description ?? "unknown"}
|
|
16150
|
+
`
|
|
16151
|
+
);
|
|
16152
|
+
}
|
|
16153
|
+
} catch (err) {
|
|
16154
|
+
process.stderr.write(
|
|
16155
|
+
`telegram-channel(${AGENT_CODE_NAME}): back-online notice error: ${err.message}
|
|
16156
|
+
`
|
|
16157
|
+
);
|
|
16158
|
+
}
|
|
16159
|
+
})();
|
|
16160
|
+
}
|
|
16161
|
+
function __resetBackOnlineNoticeThrottle() {
|
|
16162
|
+
lastBackOnlineNoticeAt.clear();
|
|
16163
|
+
}
|
|
16164
|
+
var RESTART_FLAGS_DIR = join4(homedir2(), ".augmented", "restart-flags");
|
|
16056
16165
|
function buildTelegramHelpMessage(codeName) {
|
|
16057
16166
|
return [
|
|
16058
16167
|
`\u{1F916} *Available commands for \`${codeName}\`*`,
|
|
16059
16168
|
"",
|
|
16060
16169
|
`_Type these in any chat where the bot is present (intercepted by the agent):_`,
|
|
16061
16170
|
`\u2022 /help \u2014 show this help`,
|
|
16062
|
-
`\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)`
|
|
16063
16173
|
].join("\n");
|
|
16064
16174
|
}
|
|
16065
16175
|
async function handleHelpCommand(opts) {
|
|
@@ -16092,14 +16202,14 @@ async function handleRestartCommand(opts) {
|
|
|
16092
16202
|
if (!existsSync2(RESTART_FLAGS_DIR)) {
|
|
16093
16203
|
mkdirSync3(RESTART_FLAGS_DIR, { recursive: true });
|
|
16094
16204
|
}
|
|
16095
|
-
const flagPath =
|
|
16205
|
+
const flagPath = join4(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
16096
16206
|
const flag = {
|
|
16097
16207
|
codeName: AGENT_CODE_NAME,
|
|
16098
16208
|
source: "telegram",
|
|
16099
16209
|
ts: Date.now(),
|
|
16100
16210
|
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
16101
16211
|
};
|
|
16102
|
-
const tmpPath = `${flagPath}.${process.pid}.${
|
|
16212
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
16103
16213
|
writeFileSync3(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
16104
16214
|
renameSync3(tmpPath, flagPath);
|
|
16105
16215
|
process.stderr.write(
|
|
@@ -16154,6 +16264,261 @@ async function handleRestartCommand(opts) {
|
|
|
16154
16264
|
}
|
|
16155
16265
|
}
|
|
16156
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
|
+
}
|
|
16157
16522
|
var cachedBotUsername = null;
|
|
16158
16523
|
var cachedBotId = null;
|
|
16159
16524
|
async function refreshBotIdentity() {
|
|
@@ -16208,25 +16573,30 @@ async function classifyRestartCommand(text) {
|
|
|
16208
16573
|
if (!ours) return "verification_failed";
|
|
16209
16574
|
return target === ours ? "act" : "ignore";
|
|
16210
16575
|
}
|
|
16211
|
-
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ?
|
|
16212
|
-
var PENDING_INBOUND_DIR = AGENT_DIR ?
|
|
16213
|
-
var RECOVERY_OUTBOX_DIR = AGENT_DIR ?
|
|
16576
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join4(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
16577
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
16578
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join4(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
16214
16579
|
function safeMarkerName(chatId, messageId) {
|
|
16215
16580
|
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
16216
16581
|
return `${safe(chatId)}__${safe(messageId)}.json`;
|
|
16217
16582
|
}
|
|
16218
16583
|
function pendingInboundPath(chatId, messageId) {
|
|
16219
16584
|
if (!PENDING_INBOUND_DIR) return null;
|
|
16220
|
-
return
|
|
16585
|
+
return join4(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
16221
16586
|
}
|
|
16222
|
-
function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
16587
|
+
function writePendingInboundMarker(chatId, messageId, chatType, undeliverable = false, payload) {
|
|
16223
16588
|
const path = pendingInboundPath(chatId, messageId);
|
|
16224
16589
|
if (!path || !PENDING_INBOUND_DIR) return;
|
|
16225
16590
|
const marker = {
|
|
16226
16591
|
chat_id: chatId,
|
|
16227
16592
|
message_id: messageId,
|
|
16228
16593
|
chat_type: chatType,
|
|
16229
|
-
received_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
16594
|
+
received_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16595
|
+
// Only persist the flag when set — keeps healthy-path markers byte-identical.
|
|
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 } : {}
|
|
16230
16600
|
};
|
|
16231
16601
|
try {
|
|
16232
16602
|
mkdirSync3(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
@@ -16238,14 +16608,28 @@ function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
|
16238
16608
|
);
|
|
16239
16609
|
}
|
|
16240
16610
|
}
|
|
16241
|
-
function
|
|
16242
|
-
|
|
16243
|
-
|
|
16611
|
+
function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
16612
|
+
let marker = null;
|
|
16613
|
+
try {
|
|
16614
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16615
|
+
} catch {
|
|
16616
|
+
}
|
|
16617
|
+
if (marker && decideRecoveryHeal({
|
|
16618
|
+
wasUndeliverable: marker.undeliverable === true,
|
|
16619
|
+
hasTarget: Boolean(marker.chat_id)
|
|
16620
|
+
}) === "heal") {
|
|
16621
|
+
notifyBackOnline(marker.chat_id);
|
|
16622
|
+
}
|
|
16244
16623
|
try {
|
|
16245
|
-
if (existsSync2(
|
|
16624
|
+
if (existsSync2(fullPath)) unlinkSync3(fullPath);
|
|
16246
16625
|
} catch {
|
|
16247
16626
|
}
|
|
16248
16627
|
}
|
|
16628
|
+
function clearPendingInboundMarker(chatId, messageId) {
|
|
16629
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
16630
|
+
if (!path) return;
|
|
16631
|
+
clearTelegramMarkerFileWithHeal(path);
|
|
16632
|
+
}
|
|
16249
16633
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
16250
16634
|
function nextRetryName(filename) {
|
|
16251
16635
|
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
@@ -16261,10 +16645,10 @@ function nextRetryName(filename) {
|
|
|
16261
16645
|
async function processRecoveryOutboxFile(filename) {
|
|
16262
16646
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16263
16647
|
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
16264
|
-
const fullPath =
|
|
16648
|
+
const fullPath = join4(RECOVERY_OUTBOX_DIR, filename);
|
|
16265
16649
|
let payload;
|
|
16266
16650
|
try {
|
|
16267
|
-
const raw =
|
|
16651
|
+
const raw = readFileSync3(fullPath, "utf-8");
|
|
16268
16652
|
payload = JSON.parse(raw);
|
|
16269
16653
|
} catch (err) {
|
|
16270
16654
|
process.stderr.write(
|
|
@@ -16327,7 +16711,7 @@ async function processRecoveryOutboxFile(filename) {
|
|
|
16327
16711
|
const next = nextRetryName(filename);
|
|
16328
16712
|
if (next) {
|
|
16329
16713
|
try {
|
|
16330
|
-
renameSync3(fullPath,
|
|
16714
|
+
renameSync3(fullPath, join4(RECOVERY_OUTBOX_DIR, next.next));
|
|
16331
16715
|
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
16332
16716
|
process.stderr.write(
|
|
16333
16717
|
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
@@ -16358,7 +16742,7 @@ function scanRecoveryRetries() {
|
|
|
16358
16742
|
if (!RECOVERY_OUTBOX_DIR) return;
|
|
16359
16743
|
let entries;
|
|
16360
16744
|
try {
|
|
16361
|
-
entries =
|
|
16745
|
+
entries = readdirSync2(RECOVERY_OUTBOX_DIR);
|
|
16362
16746
|
} catch {
|
|
16363
16747
|
return;
|
|
16364
16748
|
}
|
|
@@ -16367,7 +16751,7 @@ function scanRecoveryRetries() {
|
|
|
16367
16751
|
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
16368
16752
|
let mtimeMs;
|
|
16369
16753
|
try {
|
|
16370
|
-
mtimeMs = statSync(
|
|
16754
|
+
mtimeMs = statSync(join4(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
16371
16755
|
} catch {
|
|
16372
16756
|
continue;
|
|
16373
16757
|
}
|
|
@@ -16388,7 +16772,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16388
16772
|
return;
|
|
16389
16773
|
}
|
|
16390
16774
|
try {
|
|
16391
|
-
for (const f of
|
|
16775
|
+
for (const f of readdirSync2(RECOVERY_OUTBOX_DIR)) {
|
|
16392
16776
|
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
16393
16777
|
}
|
|
16394
16778
|
} catch {
|
|
@@ -16397,7 +16781,7 @@ function startRecoveryOutboxWatcher() {
|
|
|
16397
16781
|
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
16398
16782
|
if (event !== "rename" || !filename) return;
|
|
16399
16783
|
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
16400
|
-
if (existsSync2(
|
|
16784
|
+
if (existsSync2(join4(RECOVERY_OUTBOX_DIR, filename))) {
|
|
16401
16785
|
void processRecoveryOutboxFile(filename);
|
|
16402
16786
|
}
|
|
16403
16787
|
});
|
|
@@ -16413,15 +16797,15 @@ function startRecoveryOutboxWatcher() {
|
|
|
16413
16797
|
}
|
|
16414
16798
|
startRecoveryOutboxWatcher();
|
|
16415
16799
|
var STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
16416
|
-
function trackPendingMessage(chatId, messageId, chatType) {
|
|
16417
|
-
writePendingInboundMarker(chatId, messageId, chatType);
|
|
16800
|
+
function trackPendingMessage(chatId, messageId, chatType, undeliverable = false, payload) {
|
|
16801
|
+
writePendingInboundMarker(chatId, messageId, chatType, undeliverable, payload);
|
|
16418
16802
|
}
|
|
16419
|
-
function
|
|
16803
|
+
function sweepTelegramStaleMarkers(thresholdMs) {
|
|
16420
16804
|
if (!PENDING_INBOUND_DIR) return;
|
|
16421
16805
|
if (!existsSync2(PENDING_INBOUND_DIR)) return;
|
|
16422
16806
|
let filenames;
|
|
16423
16807
|
try {
|
|
16424
|
-
filenames =
|
|
16808
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16425
16809
|
} catch (err) {
|
|
16426
16810
|
process.stderr.write(
|
|
16427
16811
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker readdir failed: ${err.message}
|
|
@@ -16434,10 +16818,10 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16434
16818
|
for (const filename of filenames) {
|
|
16435
16819
|
if (!filename.endsWith(".json")) continue;
|
|
16436
16820
|
if (filename.endsWith(".tmp")) continue;
|
|
16437
|
-
const fullPath =
|
|
16821
|
+
const fullPath = join4(PENDING_INBOUND_DIR, filename);
|
|
16438
16822
|
let marker;
|
|
16439
16823
|
try {
|
|
16440
|
-
marker = JSON.parse(
|
|
16824
|
+
marker = JSON.parse(readFileSync3(fullPath, "utf-8"));
|
|
16441
16825
|
} catch (err) {
|
|
16442
16826
|
process.stderr.write(
|
|
16443
16827
|
`telegram-channel(${AGENT_CODE_NAME}): stale-marker parse failed for ${redactId(filename)}: ${err.message}
|
|
@@ -16451,31 +16835,152 @@ function sweepTelegramStaleMarkersOnBoot() {
|
|
|
16451
16835
|
continue;
|
|
16452
16836
|
}
|
|
16453
16837
|
const { chat_id, message_id, received_at } = marker;
|
|
16454
|
-
if (!chat_id || !message_id ||
|
|
16838
|
+
if (!chat_id || !message_id || isPendingMarkerStale(received_at, now, thresholdMs)) {
|
|
16455
16839
|
try {
|
|
16456
16840
|
unlinkSync3(fullPath);
|
|
16457
16841
|
} catch {
|
|
16458
16842
|
}
|
|
16459
16843
|
cleared++;
|
|
16460
|
-
continue;
|
|
16461
16844
|
}
|
|
16462
|
-
|
|
16463
|
-
|
|
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;
|
|
16464
16868
|
try {
|
|
16465
|
-
|
|
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);
|
|
16466
16873
|
} catch {
|
|
16467
16874
|
}
|
|
16468
|
-
cleared++;
|
|
16469
16875
|
}
|
|
16876
|
+
} catch {
|
|
16877
|
+
return [];
|
|
16470
16878
|
}
|
|
16471
|
-
|
|
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
|
+
}
|
|
16472
16898
|
process.stderr.write(
|
|
16473
|
-
`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}
|
|
16474
16980
|
`
|
|
16475
16981
|
);
|
|
16476
16982
|
}
|
|
16477
16983
|
}
|
|
16478
|
-
sweepTelegramStaleMarkersOnBoot();
|
|
16479
16984
|
function clearPendingMessage(chatId, messageId) {
|
|
16480
16985
|
if (messageId) {
|
|
16481
16986
|
clearPendingInboundMarker(chatId, messageId);
|
|
@@ -16486,17 +16991,14 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
16486
16991
|
const prefix = `${safeChatId}__`;
|
|
16487
16992
|
let filenames;
|
|
16488
16993
|
try {
|
|
16489
|
-
filenames =
|
|
16994
|
+
filenames = readdirSync2(PENDING_INBOUND_DIR);
|
|
16490
16995
|
} catch {
|
|
16491
16996
|
return;
|
|
16492
16997
|
}
|
|
16493
16998
|
for (const filename of filenames) {
|
|
16494
16999
|
if (!filename.startsWith(prefix)) continue;
|
|
16495
17000
|
if (!filename.endsWith(".json")) continue;
|
|
16496
|
-
|
|
16497
|
-
unlinkSync3(join3(PENDING_INBOUND_DIR, filename));
|
|
16498
|
-
} catch {
|
|
16499
|
-
}
|
|
17001
|
+
clearTelegramMarkerFileWithHeal(join4(PENDING_INBOUND_DIR, filename));
|
|
16500
17002
|
}
|
|
16501
17003
|
}
|
|
16502
17004
|
function noteThreadActivity(chatId, messageId) {
|
|
@@ -16519,48 +17021,14 @@ var mcp = new Server(
|
|
|
16519
17021
|
"CRITICAL: every response to a Telegram <channel> tag MUST go through telegram.reply with the chat_id from the tag. Text in your session WITHOUT a telegram.reply call never reaches the user.",
|
|
16520
17022
|
'Messages from Telegram arrive as <channel source="telegram" chat_id="..." user="..." user_name="..." message_id="...">. Pass reply_to_message_id from the tag so the response lands as a quote-reply in busy chats.',
|
|
16521
17023
|
"Inbound attachments: <channel> `files` is a JSON-serialised array \u2014 JSON.parse it. If an entry has `path`, the image is already downloaded \u2014 Read it directly, do NOT call telegram.download_attachment. Use that tool only for entries with `file_id` but NO `path` (PDF, docx, voice, audio, video, animations): pass file_id + chat_id verbatim, then Read the returned path. Single-image messages also get a top-level `image_path`. Caption arrives as channel content. Don't surface internal file-handling errors that don't affect the answer.",
|
|
16522
|
-
'For work >30s follow CLAUDE.md kanban flow:
|
|
17024
|
+
'For work >30s follow CLAUDE.md kanban flow: kanban_add \u2192 reply "On it \u2014 tracking here: <kanban URL>" \u2192 move to in_progress \u2192 do the work \u2192 reply with the result. Simple lookups skip kanban but still reply.',
|
|
16523
17025
|
"Address users by user_name; user is the numeric Telegram ID. Resolve ambiguous times against your own Timezone from CLAUDE.md \u2014 do not ask.",
|
|
16524
|
-
|
|
17026
|
+
`Reaction taxonomy (use telegram.react sparingly \u2014 prefer telegram.reply): \u{1F440} = ack (already auto-added on inbound, do not duplicate); \u{1F44D} or \u{1F389} = success. NEVER react to signal failure of YOUR work. On failure, telegram.reply with one sentence explaining what went wrong. Free-tier emoji: \u{1F44D} \u{1F44E} \u2764 \u{1F525} \u{1F389} \u{1F914} \u{1F92F} \u{1F64F} \u{1F44C} \u{1F440} \u{1F4AF} \u270D \u{1FAE1} \u{1F192} \u{1F973} \u{1F494}. (If a message arrived while you were offline you may see a system-posted "can't respond right now" notice \u2014 that's the runtime, not you; don't repeat or apologise for it.)`
|
|
16525
17027
|
].join(" ")
|
|
16526
17028
|
}
|
|
16527
17029
|
);
|
|
16528
|
-
var TELEGRAM_PROGRESS_TIMEOUT_MS = 5e3;
|
|
16529
|
-
var progressRegistry = new ProgressToolRegistry({
|
|
16530
|
-
prefix: "telegram",
|
|
16531
|
-
surfaceDescription: "a Telegram chat",
|
|
16532
|
-
startArgsSchema: {
|
|
16533
|
-
properties: {
|
|
16534
|
-
chat_id: {
|
|
16535
|
-
type: "string",
|
|
16536
|
-
description: "Telegram chat id (from the `chat_id` attribute on the inbound <channel> tag). Numeric ids are passed as strings; the API accepts both."
|
|
16537
|
-
},
|
|
16538
|
-
message_thread_id: {
|
|
16539
|
-
type: "string",
|
|
16540
|
-
description: "Forum topic id (`message_thread_id`) \u2014 pass this if the inbound arrived in a forum topic so the anchor lands in the same topic. Omit for normal chats."
|
|
16541
|
-
},
|
|
16542
|
-
reply_to_message_id: {
|
|
16543
|
-
type: "string",
|
|
16544
|
-
description: "Optional message id to thread the anchor under (typically the inbound message_id from the <channel> tag)."
|
|
16545
|
-
}
|
|
16546
|
-
},
|
|
16547
|
-
required: ["chat_id"]
|
|
16548
|
-
},
|
|
16549
|
-
createFlush: (args) => {
|
|
16550
|
-
const chatId = args.chat_id;
|
|
16551
|
-
const threadId = typeof args.message_thread_id === "string" && args.message_thread_id.length > 0 ? Number(args.message_thread_id) : void 0;
|
|
16552
|
-
const replyTo = typeof args.reply_to_message_id === "string" && args.reply_to_message_id.length > 0 ? Number(args.reply_to_message_id) : void 0;
|
|
16553
|
-
return createTelegramProgressFlush({
|
|
16554
|
-
chatId,
|
|
16555
|
-
messageThreadId: Number.isFinite(threadId) ? threadId : void 0,
|
|
16556
|
-
replyToMessageId: Number.isFinite(replyTo) ? replyTo : void 0,
|
|
16557
|
-
callImpl: (method, body) => telegramApiCall(method, body, TELEGRAM_PROGRESS_TIMEOUT_MS)
|
|
16558
|
-
});
|
|
16559
|
-
}
|
|
16560
|
-
});
|
|
16561
17030
|
mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16562
17031
|
tools: [
|
|
16563
|
-
...progressRegistry.getDefinitions(),
|
|
16564
17032
|
{
|
|
16565
17033
|
name: "channel_request_input",
|
|
16566
17034
|
description: "Generic Yes/No or multi-choice picker. Renders as a Telegram inline keyboard; the tool blocks until the user taps an option (or the timeout elapses) and returns the resolved value. Use this when you need a structured pick rather than freeform text \u2014 e.g. confirming a draft action or disambiguating an ambiguous fork.",
|
|
@@ -16652,7 +17120,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16652
17120
|
},
|
|
16653
17121
|
{
|
|
16654
17122
|
name: "telegram.react",
|
|
16655
|
-
description:
|
|
17123
|
+
description: `Add an emoji reaction to a Telegram message. Use sparingly \u2014 prefer a text reply via telegram.reply. Reaction taxonomy: \u{1F44D} or \u{1F389} = action completed successfully. NEVER react to signal failure of your work \u2014 users can't tell why it failed from a reaction. On failure, call telegram.reply with one sentence explaining what went wrong instead. \u{1F440} is already auto-applied on inbound; do not duplicate. Only free-tier emoji reactions are available to bots (Premium-only emoji fail silently). Pass an empty string or omit emoji to clear the bot's reaction on that message. (Note: when a message arrives while the agent is offline/wedged, the runtime \u2014 not you \u2014 posts a brief "can't respond right now" notice; you never need to add a failure reaction for that.)`,
|
|
16656
17124
|
inputSchema: {
|
|
16657
17125
|
type: "object",
|
|
16658
17126
|
properties: {
|
|
@@ -16676,8 +17144,9 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
16676
17144
|
}));
|
|
16677
17145
|
mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
16678
17146
|
const { name, arguments: args } = req.params;
|
|
16679
|
-
|
|
16680
|
-
|
|
17147
|
+
if (isImpersonating() && !channelsEnabledOverride() && TELEGRAM_EGRESS_TOOLS.has(name)) {
|
|
17148
|
+
return buildImpersonationRefusal(name);
|
|
17149
|
+
}
|
|
16681
17150
|
if (name === "channel_request_input") {
|
|
16682
17151
|
return handleChannelRequestInput(args ?? {});
|
|
16683
17152
|
}
|
|
@@ -16745,28 +17214,56 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16745
17214
|
isError: true
|
|
16746
17215
|
};
|
|
16747
17216
|
}
|
|
16748
|
-
const
|
|
17217
|
+
const isPrivateChat = !chat_id.startsWith("-");
|
|
17218
|
+
const tgThrottleCfg = isPrivateChat ? relaxedThrottleConfig(configFromEnv()) : configFromEnv();
|
|
16749
17219
|
const tgThrottleNow = Date.now();
|
|
16750
|
-
const
|
|
17220
|
+
const tgPlan = planReplyDelivery({
|
|
16751
17221
|
recentReplyTimestamps: getRecentReplies(chat_id, "", tgThrottleNow, tgThrottleCfg),
|
|
17222
|
+
queuedCount: queuedReplyCounts.get(chat_id) ?? 0,
|
|
16752
17223
|
now: tgThrottleNow,
|
|
16753
17224
|
config: tgThrottleCfg
|
|
16754
17225
|
});
|
|
16755
|
-
if (
|
|
17226
|
+
if (tgPlan.action === "refuse") {
|
|
16756
17227
|
process.stderr.write(
|
|
16757
|
-
`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}
|
|
16758
17229
|
`
|
|
16759
17230
|
);
|
|
16760
17231
|
return {
|
|
16761
17232
|
content: [
|
|
16762
17233
|
{
|
|
16763
17234
|
type: "text",
|
|
16764
|
-
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.`
|
|
16765
17236
|
}
|
|
16766
17237
|
],
|
|
16767
17238
|
isError: true
|
|
16768
17239
|
};
|
|
16769
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
|
+
}
|
|
16770
17267
|
try {
|
|
16771
17268
|
const body = { chat_id, text };
|
|
16772
17269
|
if (reply_to_message_id) body.reply_to_message_id = Number(reply_to_message_id);
|
|
@@ -16877,12 +17374,12 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16877
17374
|
function channelRequestInputAvailable() {
|
|
16878
17375
|
return Boolean(AGT_HOST && AGT_API_KEY && AGT_AGENT_ID);
|
|
16879
17376
|
}
|
|
16880
|
-
function
|
|
17377
|
+
function errResult(text) {
|
|
16881
17378
|
return { content: [{ type: "text", text }], isError: true };
|
|
16882
17379
|
}
|
|
16883
17380
|
async function handleChannelRequestInput(args) {
|
|
16884
17381
|
if (!channelRequestInputAvailable()) {
|
|
16885
|
-
return
|
|
17382
|
+
return errResult(
|
|
16886
17383
|
"channel_request_input is disabled \u2014 missing AGT_HOST / AGT_API_KEY / AGT_AGENT_ID env wiring."
|
|
16887
17384
|
);
|
|
16888
17385
|
}
|
|
@@ -16890,12 +17387,12 @@ async function handleChannelRequestInput(args) {
|
|
|
16890
17387
|
const { apiCall: apiCall2, waitForResolution: waitForResolution2, validateAskUserOptions: validateAskUserOptions2 } = runtime;
|
|
16891
17388
|
const { thread_id, prompt_text, options, schema, request_id, timeout_seconds } = args;
|
|
16892
17389
|
if (typeof thread_id !== "string" || !thread_id) {
|
|
16893
|
-
return
|
|
17390
|
+
return errResult(
|
|
16894
17391
|
"thread_id is required (Telegram shape: `<chat_id>` or `<chat_id>:<reply_to_message_id>`)"
|
|
16895
17392
|
);
|
|
16896
17393
|
}
|
|
16897
17394
|
if (typeof prompt_text !== "string" || !prompt_text) {
|
|
16898
|
-
return
|
|
17395
|
+
return errResult("prompt_text is required");
|
|
16899
17396
|
}
|
|
16900
17397
|
const optsValidation = validateAskUserOptions2(options, {
|
|
16901
17398
|
maxOptions: 4,
|
|
@@ -16903,27 +17400,27 @@ async function handleChannelRequestInput(args) {
|
|
|
16903
17400
|
maxValueChars: 2e3
|
|
16904
17401
|
});
|
|
16905
17402
|
if (!optsValidation.ok) {
|
|
16906
|
-
return
|
|
17403
|
+
return errResult(
|
|
16907
17404
|
`Invalid options: ${optsValidation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`
|
|
16908
17405
|
);
|
|
16909
17406
|
}
|
|
16910
17407
|
const optionsArr = options;
|
|
16911
17408
|
if (optionsArr.length < 2) {
|
|
16912
|
-
return
|
|
17409
|
+
return errResult(
|
|
16913
17410
|
"options must contain at least 2 entries (this is a picker, not a notification)"
|
|
16914
17411
|
);
|
|
16915
17412
|
}
|
|
16916
17413
|
if (!schema || typeof schema.type !== "string") {
|
|
16917
|
-
return
|
|
17414
|
+
return errResult("schema is required with shape { type: 'yes_no' | 'choice' }");
|
|
16918
17415
|
}
|
|
16919
17416
|
if (schema.type !== "yes_no" && schema.type !== "choice") {
|
|
16920
|
-
return
|
|
17417
|
+
return errResult("schema.type must be 'yes_no' or 'choice'");
|
|
16921
17418
|
}
|
|
16922
17419
|
if (schema.type === "yes_no" && optionsArr.length !== 2) {
|
|
16923
|
-
return
|
|
17420
|
+
return errResult("schema.type='yes_no' requires exactly 2 options");
|
|
16924
17421
|
}
|
|
16925
17422
|
if (request_id !== void 0 && (typeof request_id !== "string" || !request_id)) {
|
|
16926
|
-
return
|
|
17423
|
+
return errResult("request_id must be a non-empty string when provided");
|
|
16927
17424
|
}
|
|
16928
17425
|
const requestedTimeout = typeof timeout_seconds === "number" ? timeout_seconds : 300;
|
|
16929
17426
|
const timeoutSec = Math.max(10, Math.min(3600, requestedTimeout));
|
|
@@ -16945,10 +17442,10 @@ async function handleChannelRequestInput(args) {
|
|
|
16945
17442
|
dispatchPayload = await res.json().catch(() => ({}));
|
|
16946
17443
|
if (!res.ok || !dispatchPayload.ok || !dispatchPayload.callback_id) {
|
|
16947
17444
|
const reason = dispatchPayload.reason ?? `http_${res.status}`;
|
|
16948
|
-
return
|
|
17445
|
+
return errResult(`channel_request_input dispatch failed: ${reason}`);
|
|
16949
17446
|
}
|
|
16950
17447
|
} catch (err) {
|
|
16951
|
-
return
|
|
17448
|
+
return errResult(
|
|
16952
17449
|
`channel_request_input dispatch failed: ${err.message}`
|
|
16953
17450
|
);
|
|
16954
17451
|
}
|
|
@@ -17097,6 +17594,73 @@ async function ackCallbackQuery(callbackQueryId, text) {
|
|
|
17097
17594
|
}
|
|
17098
17595
|
}
|
|
17099
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?.();
|
|
17100
17664
|
var LONG_POLL_SECONDS = 50;
|
|
17101
17665
|
var POLL_HTTP_TIMEOUT_MS = (LONG_POLL_SECONDS + 10) * 1e3;
|
|
17102
17666
|
var TELEGRAM_DOWNLOAD_TIMEOUT_MS = 15e3;
|
|
@@ -17262,6 +17826,35 @@ async function pollLoop() {
|
|
|
17262
17826
|
} catch (err) {
|
|
17263
17827
|
process.stderr.write(
|
|
17264
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)}
|
|
17265
17858
|
`
|
|
17266
17859
|
);
|
|
17267
17860
|
}
|
|
@@ -17370,8 +17963,29 @@ async function pollLoop() {
|
|
|
17370
17963
|
}
|
|
17371
17964
|
}
|
|
17372
17965
|
const messageId = String(msg.message_id);
|
|
17373
|
-
|
|
17374
|
-
|
|
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
|
+
}
|
|
17975
|
+
const ackDecision = decideAckReaction({
|
|
17976
|
+
hasTarget: Boolean(chatId && messageId),
|
|
17977
|
+
integrationReady: Boolean(BOT_TOKEN),
|
|
17978
|
+
tmux: ackProbe.tmux,
|
|
17979
|
+
claude: ackProbe.claude,
|
|
17980
|
+
withinStartupGrace: process.uptime() * 1e3 < ACK_STARTUP_GRACE_MS,
|
|
17981
|
+
oldestPendingAgeMs: oldestPendingMarkerAgeMs(PENDING_INBOUND_DIR),
|
|
17982
|
+
paneLogFreshAgeMs
|
|
17983
|
+
});
|
|
17984
|
+
if (ackDecision === "ack") {
|
|
17985
|
+
void setMessageReaction(chatId, messageId, ACK_EMOJI);
|
|
17986
|
+
} else if (ackDecision === "undeliverable") {
|
|
17987
|
+
void notifyUndeliverable(chatId, messageId);
|
|
17988
|
+
}
|
|
17375
17989
|
const fileMeta = [];
|
|
17376
17990
|
for (const attachment of classifiedAttachments) {
|
|
17377
17991
|
if (attachment.kind === "image") {
|
|
@@ -17399,30 +18013,38 @@ async function pollLoop() {
|
|
|
17399
18013
|
}
|
|
17400
18014
|
const downloadedImages = fileMeta.filter((f) => f.kind === "image" && typeof f.path === "string");
|
|
17401
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
|
+
);
|
|
17402
18045
|
await mcp.notification({
|
|
17403
18046
|
method: "notifications/claude/channel",
|
|
17404
|
-
params:
|
|
17405
|
-
content,
|
|
17406
|
-
meta: {
|
|
17407
|
-
source: "telegram",
|
|
17408
|
-
chat_id: chatId,
|
|
17409
|
-
chat_type: msg.chat.type,
|
|
17410
|
-
message_id: messageId,
|
|
17411
|
-
user: userId,
|
|
17412
|
-
user_name: userName,
|
|
17413
|
-
ts: String(msg.date),
|
|
17414
|
-
...fileMeta.length > 0 ? { files: JSON.stringify(fileMeta) } : {},
|
|
17415
|
-
...imagePath ? { image_path: imagePath } : {},
|
|
17416
|
-
// ENG-4902: peer-agent ingress carries distinct framing so the
|
|
17417
|
-
// runtime (MVP #5) can apply the peer-agent system preamble
|
|
17418
|
-
// and store the turn under a peer_agent memory role.
|
|
17419
|
-
...peerAgentMeta ? {
|
|
17420
|
-
source_role: "agent",
|
|
17421
|
-
peer_code_name: peerAgentMeta.code_name,
|
|
17422
|
-
peer_agent_id: peerAgentMeta.agent_id
|
|
17423
|
-
} : {}
|
|
17424
|
-
}
|
|
17425
|
-
}
|
|
18047
|
+
params: channelPayload
|
|
17426
18048
|
});
|
|
17427
18049
|
conversationIngestClient?.ingest({
|
|
17428
18050
|
channel: "telegram",
|
|
@@ -17490,6 +18112,24 @@ process.stderr.write(
|
|
|
17490
18112
|
`telegram-channel(${AGENT_CODE_NAME}): started, long-polling getUpdates
|
|
17491
18113
|
`
|
|
17492
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
|
+
})();
|
|
17493
18133
|
pollLoop().catch((err) => {
|
|
17494
18134
|
process.stderr.write(
|
|
17495
18135
|
`telegram-channel(${AGENT_CODE_NAME}): poll loop died: ${err.message}
|
|
@@ -17497,3 +18137,9 @@ pollLoop().catch((err) => {
|
|
|
17497
18137
|
);
|
|
17498
18138
|
process.exit(1);
|
|
17499
18139
|
});
|
|
18140
|
+
export {
|
|
18141
|
+
__resetBackOnlineNoticeThrottle,
|
|
18142
|
+
__resetGiveUpNoticeStateForTests,
|
|
18143
|
+
__resetUndeliverableNoticeThrottle,
|
|
18144
|
+
_resetQueuedReplyCountsForTests
|
|
18145
|
+
};
|