@integrity-labs/agt-cli 0.15.7 → 0.15.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-WIW5FIRY.js → chunk-C6UBNLUC.js} +124 -5
- package/dist/chunk-C6UBNLUC.js.map +1 -0
- package/dist/lib/manager-worker.js +348 -177
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/index.js +9 -0
- package/mcp/slack-channel.js +397 -16
- package/mcp/telegram-channel.js +421 -12
- package/package.json +1 -1
- package/dist/chunk-WIW5FIRY.js.map +0 -1
package/mcp/telegram-channel.js
CHANGED
|
@@ -13863,7 +13863,21 @@ var StdioServerTransport = class {
|
|
|
13863
13863
|
|
|
13864
13864
|
// src/telegram-channel.ts
|
|
13865
13865
|
import https from "https";
|
|
13866
|
-
import { createHash } from "crypto";
|
|
13866
|
+
import { createHash, randomUUID } from "crypto";
|
|
13867
|
+
import {
|
|
13868
|
+
createWriteStream,
|
|
13869
|
+
existsSync,
|
|
13870
|
+
mkdirSync as mkdirSync2,
|
|
13871
|
+
readFileSync,
|
|
13872
|
+
readdirSync,
|
|
13873
|
+
renameSync as renameSync2,
|
|
13874
|
+
statSync,
|
|
13875
|
+
unlinkSync as unlinkSync2,
|
|
13876
|
+
watch,
|
|
13877
|
+
writeFileSync as writeFileSync2
|
|
13878
|
+
} from "fs";
|
|
13879
|
+
import { homedir as homedir2 } from "os";
|
|
13880
|
+
import { join as join2 } from "path";
|
|
13867
13881
|
|
|
13868
13882
|
// src/channel-attachments.ts
|
|
13869
13883
|
import { homedir } from "os";
|
|
@@ -14110,6 +14124,28 @@ if (!BOT_TOKEN) {
|
|
|
14110
14124
|
);
|
|
14111
14125
|
process.exit(1);
|
|
14112
14126
|
}
|
|
14127
|
+
var stderrLogStream = null;
|
|
14128
|
+
if (AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown") {
|
|
14129
|
+
try {
|
|
14130
|
+
const logDir = join2(homedir2(), ".augmented", AGENT_CODE_NAME);
|
|
14131
|
+
mkdirSync2(logDir, { recursive: true });
|
|
14132
|
+
stderrLogStream = createWriteStream(join2(logDir, "telegram-channel-stderr.log"), {
|
|
14133
|
+
flags: "a",
|
|
14134
|
+
mode: 384
|
|
14135
|
+
});
|
|
14136
|
+
const origWrite = process.stderr.write.bind(process.stderr);
|
|
14137
|
+
process.stderr.write = (chunk, ...rest) => {
|
|
14138
|
+
try {
|
|
14139
|
+
const buf = typeof chunk === "string" ? chunk : String(chunk);
|
|
14140
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
14141
|
+
stderrLogStream?.write(`[${ts}] ${buf}${buf.endsWith("\n") ? "" : "\n"}`);
|
|
14142
|
+
} catch {
|
|
14143
|
+
}
|
|
14144
|
+
return origWrite(chunk, ...rest);
|
|
14145
|
+
};
|
|
14146
|
+
} catch {
|
|
14147
|
+
}
|
|
14148
|
+
}
|
|
14113
14149
|
function telegramApiCall(method, body, timeoutMs) {
|
|
14114
14150
|
return new Promise((resolve2, reject) => {
|
|
14115
14151
|
const postData = JSON.stringify(body);
|
|
@@ -14150,8 +14186,7 @@ function telegramApiCall(method, body, timeoutMs) {
|
|
|
14150
14186
|
});
|
|
14151
14187
|
}
|
|
14152
14188
|
var ACK_EMOJI = "\u{1F440}";
|
|
14153
|
-
var
|
|
14154
|
-
var RESPONSE_TIMEOUT_MS = 12e4;
|
|
14189
|
+
var RESPONSE_TIMEOUT_MS = 3e5;
|
|
14155
14190
|
async function setMessageReaction(chatId, messageId, emoji2) {
|
|
14156
14191
|
try {
|
|
14157
14192
|
const resp = await telegramApiCall(
|
|
@@ -14176,14 +14211,343 @@ async function setMessageReaction(chatId, messageId, emoji2) {
|
|
|
14176
14211
|
);
|
|
14177
14212
|
}
|
|
14178
14213
|
}
|
|
14214
|
+
var RESTART_FLAGS_DIR = join2(homedir2(), ".augmented", "restart-flags");
|
|
14215
|
+
async function handleRestartCommand(opts) {
|
|
14216
|
+
try {
|
|
14217
|
+
if (!existsSync(RESTART_FLAGS_DIR)) {
|
|
14218
|
+
mkdirSync2(RESTART_FLAGS_DIR, { recursive: true });
|
|
14219
|
+
}
|
|
14220
|
+
const flagPath = join2(RESTART_FLAGS_DIR, `${AGENT_CODE_NAME}.flag`);
|
|
14221
|
+
const flag = {
|
|
14222
|
+
codeName: AGENT_CODE_NAME,
|
|
14223
|
+
source: "telegram",
|
|
14224
|
+
ts: Date.now(),
|
|
14225
|
+
reply: { chat_id: opts.chatId, message_id: opts.messageId }
|
|
14226
|
+
};
|
|
14227
|
+
const tmpPath = `${flagPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
14228
|
+
writeFileSync2(tmpPath, JSON.stringify(flag) + "\n", "utf8");
|
|
14229
|
+
renameSync2(tmpPath, flagPath);
|
|
14230
|
+
process.stderr.write(
|
|
14231
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart queued from chat ${redactId(opts.chatId)}
|
|
14232
|
+
`
|
|
14233
|
+
);
|
|
14234
|
+
try {
|
|
14235
|
+
const ack = await telegramApiCall(
|
|
14236
|
+
"sendMessage",
|
|
14237
|
+
{
|
|
14238
|
+
chat_id: opts.chatId,
|
|
14239
|
+
text: `\u23F3 Restart queued for \`${AGENT_CODE_NAME}\`. The session will reconnect in a few seconds.`,
|
|
14240
|
+
reply_to_message_id: Number(opts.messageId),
|
|
14241
|
+
parse_mode: "Markdown"
|
|
14242
|
+
},
|
|
14243
|
+
1e4
|
|
14244
|
+
);
|
|
14245
|
+
if (!ack.ok) {
|
|
14246
|
+
process.stderr.write(
|
|
14247
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart ack rejected by Telegram (chat ${redactId(opts.chatId)}): ${ack.description ?? "unknown"}
|
|
14248
|
+
`
|
|
14249
|
+
);
|
|
14250
|
+
}
|
|
14251
|
+
} catch (err) {
|
|
14252
|
+
process.stderr.write(
|
|
14253
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart ack send failed: ${redactAugmentedPaths(err.message)}
|
|
14254
|
+
`
|
|
14255
|
+
);
|
|
14256
|
+
}
|
|
14257
|
+
} catch (err) {
|
|
14258
|
+
process.stderr.write(
|
|
14259
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart flag write failed: ${redactAugmentedPaths(err.message)}
|
|
14260
|
+
`
|
|
14261
|
+
);
|
|
14262
|
+
try {
|
|
14263
|
+
const errAck = await telegramApiCall(
|
|
14264
|
+
"sendMessage",
|
|
14265
|
+
{
|
|
14266
|
+
chat_id: opts.chatId,
|
|
14267
|
+
text: `\u274C Failed to queue restart for ${AGENT_CODE_NAME}. Please try again in a few seconds.`,
|
|
14268
|
+
reply_to_message_id: Number(opts.messageId)
|
|
14269
|
+
},
|
|
14270
|
+
1e4
|
|
14271
|
+
);
|
|
14272
|
+
if (!errAck.ok) {
|
|
14273
|
+
process.stderr.write(
|
|
14274
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart error-ack rejected by Telegram (chat ${redactId(opts.chatId)}): ${errAck.description ?? "unknown"}
|
|
14275
|
+
`
|
|
14276
|
+
);
|
|
14277
|
+
}
|
|
14278
|
+
} catch {
|
|
14279
|
+
}
|
|
14280
|
+
}
|
|
14281
|
+
}
|
|
14282
|
+
var cachedBotUsername = null;
|
|
14283
|
+
async function resolveBotUsername() {
|
|
14284
|
+
if (cachedBotUsername !== null) return cachedBotUsername;
|
|
14285
|
+
try {
|
|
14286
|
+
const resp = await telegramApiCall("getMe", {}, 5e3);
|
|
14287
|
+
if (resp.ok && resp.result && typeof resp.result.username === "string") {
|
|
14288
|
+
cachedBotUsername = resp.result.username.toLowerCase();
|
|
14289
|
+
return cachedBotUsername;
|
|
14290
|
+
}
|
|
14291
|
+
process.stderr.write(
|
|
14292
|
+
`telegram-channel(${AGENT_CODE_NAME}): getMe rejected: ${resp.description ?? "unknown"} \u2014 will retry on next /restart@<bot>
|
|
14293
|
+
`
|
|
14294
|
+
);
|
|
14295
|
+
} catch (err) {
|
|
14296
|
+
process.stderr.write(
|
|
14297
|
+
`telegram-channel(${AGENT_CODE_NAME}): getMe failed: ${redactAugmentedPaths(err.message)} \u2014 will retry on next /restart@<bot>
|
|
14298
|
+
`
|
|
14299
|
+
);
|
|
14300
|
+
}
|
|
14301
|
+
return null;
|
|
14302
|
+
}
|
|
14303
|
+
var RESTART_SYNTAX_RE = /^\/restart(?:@([A-Za-z0-9_]{1,64}))?(?:\s|$)/;
|
|
14304
|
+
function isRestartSyntax(text) {
|
|
14305
|
+
return RESTART_SYNTAX_RE.test(text);
|
|
14306
|
+
}
|
|
14307
|
+
async function classifyRestartCommand(text) {
|
|
14308
|
+
const m = RESTART_SYNTAX_RE.exec(text);
|
|
14309
|
+
if (!m) return "ignore";
|
|
14310
|
+
const target = m[1]?.toLowerCase();
|
|
14311
|
+
if (!target) return "act";
|
|
14312
|
+
const ours = await resolveBotUsername();
|
|
14313
|
+
if (!ours) return "verification_failed";
|
|
14314
|
+
return target === ours ? "act" : "ignore";
|
|
14315
|
+
}
|
|
14179
14316
|
var pendingMessages = /* @__PURE__ */ new Map();
|
|
14317
|
+
var AGENT_DIR = AGENT_CODE_NAME && AGENT_CODE_NAME !== "unknown" ? join2(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
14318
|
+
var PENDING_INBOUND_DIR = AGENT_DIR ? join2(AGENT_DIR, "telegram-pending-inbound") : null;
|
|
14319
|
+
var RECOVERY_OUTBOX_DIR = AGENT_DIR ? join2(AGENT_DIR, "telegram-recovery-outbox") : null;
|
|
14320
|
+
function safeMarkerName(chatId, messageId) {
|
|
14321
|
+
const safe = (s) => s.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
14322
|
+
return `${safe(chatId)}__${safe(messageId)}.json`;
|
|
14323
|
+
}
|
|
14324
|
+
function pendingInboundPath(chatId, messageId) {
|
|
14325
|
+
if (!PENDING_INBOUND_DIR) return null;
|
|
14326
|
+
return join2(PENDING_INBOUND_DIR, safeMarkerName(chatId, messageId));
|
|
14327
|
+
}
|
|
14328
|
+
function writePendingInboundMarker(chatId, messageId, chatType) {
|
|
14329
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
14330
|
+
if (!path || !PENDING_INBOUND_DIR) return;
|
|
14331
|
+
const marker = {
|
|
14332
|
+
chat_id: chatId,
|
|
14333
|
+
message_id: messageId,
|
|
14334
|
+
chat_type: chatType,
|
|
14335
|
+
received_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14336
|
+
};
|
|
14337
|
+
try {
|
|
14338
|
+
mkdirSync2(PENDING_INBOUND_DIR, { recursive: true, mode: 448 });
|
|
14339
|
+
writeFileSync2(path, JSON.stringify(marker), { mode: 384 });
|
|
14340
|
+
} catch (err) {
|
|
14341
|
+
process.stderr.write(
|
|
14342
|
+
`telegram-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
14343
|
+
`
|
|
14344
|
+
);
|
|
14345
|
+
}
|
|
14346
|
+
}
|
|
14347
|
+
function clearPendingInboundMarker(chatId, messageId) {
|
|
14348
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
14349
|
+
if (!path) return;
|
|
14350
|
+
try {
|
|
14351
|
+
if (existsSync(path)) unlinkSync2(path);
|
|
14352
|
+
} catch {
|
|
14353
|
+
}
|
|
14354
|
+
}
|
|
14355
|
+
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
14356
|
+
function nextRetryName(filename) {
|
|
14357
|
+
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
14358
|
+
if (!match) return null;
|
|
14359
|
+
const base = match[1];
|
|
14360
|
+
const prev = match[2] ? parseInt(match[2], 10) : 0;
|
|
14361
|
+
const attempt = prev + 1;
|
|
14362
|
+
if (attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
14363
|
+
return { next: `${base}.retry-${attempt}.poison.json`, attempt };
|
|
14364
|
+
}
|
|
14365
|
+
return { next: `${base}.retry-${attempt}.json`, attempt };
|
|
14366
|
+
}
|
|
14367
|
+
async function processRecoveryOutboxFile(filename) {
|
|
14368
|
+
if (!RECOVERY_OUTBOX_DIR) return;
|
|
14369
|
+
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return;
|
|
14370
|
+
const fullPath = join2(RECOVERY_OUTBOX_DIR, filename);
|
|
14371
|
+
let payload;
|
|
14372
|
+
try {
|
|
14373
|
+
const raw = readFileSync(fullPath, "utf-8");
|
|
14374
|
+
payload = JSON.parse(raw);
|
|
14375
|
+
} catch (err) {
|
|
14376
|
+
process.stderr.write(
|
|
14377
|
+
`telegram-channel(${AGENT_CODE_NAME}): recovery outbox parse failed (${filename}): ${err.message}
|
|
14378
|
+
`
|
|
14379
|
+
);
|
|
14380
|
+
try {
|
|
14381
|
+
renameSync2(fullPath, `${fullPath}.parse-error.poison`);
|
|
14382
|
+
} catch {
|
|
14383
|
+
}
|
|
14384
|
+
return;
|
|
14385
|
+
}
|
|
14386
|
+
if (!payload.chat_id || !payload.text) {
|
|
14387
|
+
process.stderr.write(
|
|
14388
|
+
`telegram-channel(${AGENT_CODE_NAME}): recovery outbox malformed (${filename}): missing chat_id or text
|
|
14389
|
+
`
|
|
14390
|
+
);
|
|
14391
|
+
try {
|
|
14392
|
+
renameSync2(fullPath, `${fullPath}.malformed.poison`);
|
|
14393
|
+
} catch {
|
|
14394
|
+
}
|
|
14395
|
+
return;
|
|
14396
|
+
}
|
|
14397
|
+
const text = `[recovered reply] ${payload.text}`;
|
|
14398
|
+
const body = {
|
|
14399
|
+
chat_id: payload.chat_id,
|
|
14400
|
+
text,
|
|
14401
|
+
allow_sending_without_reply: true
|
|
14402
|
+
};
|
|
14403
|
+
if (payload.message_id) body.reply_to_message_id = Number(payload.message_id);
|
|
14404
|
+
let sendSucceeded = false;
|
|
14405
|
+
try {
|
|
14406
|
+
const resp = await telegramApiCall("sendMessage", body, 15e3);
|
|
14407
|
+
if (resp.ok) {
|
|
14408
|
+
sendSucceeded = true;
|
|
14409
|
+
process.stderr.write(
|
|
14410
|
+
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery sent (chat=${redactId(payload.chat_id)} msg=${redactId(payload.message_id ?? "")})
|
|
14411
|
+
`
|
|
14412
|
+
);
|
|
14413
|
+
if (payload.message_id) clearPendingInboundMarker(payload.chat_id, payload.message_id);
|
|
14414
|
+
} else {
|
|
14415
|
+
process.stderr.write(
|
|
14416
|
+
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery failed (chat=${redactId(payload.chat_id)}): ${resp.description ?? "unknown"}
|
|
14417
|
+
`
|
|
14418
|
+
);
|
|
14419
|
+
}
|
|
14420
|
+
} catch (err) {
|
|
14421
|
+
process.stderr.write(
|
|
14422
|
+
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery error (chat=${redactId(payload.chat_id)}): ${err.message}
|
|
14423
|
+
`
|
|
14424
|
+
);
|
|
14425
|
+
}
|
|
14426
|
+
if (sendSucceeded) {
|
|
14427
|
+
try {
|
|
14428
|
+
unlinkSync2(fullPath);
|
|
14429
|
+
} catch {
|
|
14430
|
+
}
|
|
14431
|
+
return;
|
|
14432
|
+
}
|
|
14433
|
+
const next = nextRetryName(filename);
|
|
14434
|
+
if (next) {
|
|
14435
|
+
try {
|
|
14436
|
+
renameSync2(fullPath, join2(RECOVERY_OUTBOX_DIR, next.next));
|
|
14437
|
+
if (next.attempt >= MAX_RECOVERY_ATTEMPTS) {
|
|
14438
|
+
process.stderr.write(
|
|
14439
|
+
`telegram-channel(${AGENT_CODE_NAME}): ghost-reply recovery exhausted retries \u2014 moved to ${next.next}
|
|
14440
|
+
`
|
|
14441
|
+
);
|
|
14442
|
+
}
|
|
14443
|
+
} catch {
|
|
14444
|
+
}
|
|
14445
|
+
}
|
|
14446
|
+
}
|
|
14447
|
+
function isFirstAttemptOutboxFile(filename) {
|
|
14448
|
+
if (!filename.endsWith(".json")) return false;
|
|
14449
|
+
if (filename.includes(".retry-")) return false;
|
|
14450
|
+
if (filename.endsWith(".poison.json") || filename.endsWith(".tmp")) return false;
|
|
14451
|
+
if (filename.startsWith(".")) return false;
|
|
14452
|
+
return true;
|
|
14453
|
+
}
|
|
14454
|
+
var RETRY_SCAN_INTERVAL_MS = 6e4;
|
|
14455
|
+
var RETRY_BACKOFF_BASE_MS = 6e4;
|
|
14456
|
+
function shouldRetryNow(filename, mtimeMs) {
|
|
14457
|
+
const match = filename.match(/\.retry-(\d+)\.json$/);
|
|
14458
|
+
if (!match) return false;
|
|
14459
|
+
const attempt = parseInt(match[1], 10);
|
|
14460
|
+
const required2 = attempt * RETRY_BACKOFF_BASE_MS;
|
|
14461
|
+
return Date.now() - mtimeMs >= required2;
|
|
14462
|
+
}
|
|
14463
|
+
function scanRecoveryRetries() {
|
|
14464
|
+
if (!RECOVERY_OUTBOX_DIR) return;
|
|
14465
|
+
let entries;
|
|
14466
|
+
try {
|
|
14467
|
+
entries = readdirSync(RECOVERY_OUTBOX_DIR);
|
|
14468
|
+
} catch {
|
|
14469
|
+
return;
|
|
14470
|
+
}
|
|
14471
|
+
for (const f of entries) {
|
|
14472
|
+
if (!f.endsWith(".json")) continue;
|
|
14473
|
+
if (!f.includes(".retry-") || f.endsWith(".poison.json")) continue;
|
|
14474
|
+
let mtimeMs;
|
|
14475
|
+
try {
|
|
14476
|
+
mtimeMs = statSync(join2(RECOVERY_OUTBOX_DIR, f)).mtimeMs;
|
|
14477
|
+
} catch {
|
|
14478
|
+
continue;
|
|
14479
|
+
}
|
|
14480
|
+
if (shouldRetryNow(f, mtimeMs)) {
|
|
14481
|
+
void processRecoveryOutboxFile(f);
|
|
14482
|
+
}
|
|
14483
|
+
}
|
|
14484
|
+
}
|
|
14485
|
+
function startRecoveryOutboxWatcher() {
|
|
14486
|
+
if (!RECOVERY_OUTBOX_DIR) return;
|
|
14487
|
+
try {
|
|
14488
|
+
mkdirSync2(RECOVERY_OUTBOX_DIR, { recursive: true, mode: 448 });
|
|
14489
|
+
} catch (err) {
|
|
14490
|
+
process.stderr.write(
|
|
14491
|
+
`telegram-channel(${AGENT_CODE_NAME}): recovery outbox mkdir failed: ${err.message}
|
|
14492
|
+
`
|
|
14493
|
+
);
|
|
14494
|
+
return;
|
|
14495
|
+
}
|
|
14496
|
+
try {
|
|
14497
|
+
for (const f of readdirSync(RECOVERY_OUTBOX_DIR)) {
|
|
14498
|
+
if (isFirstAttemptOutboxFile(f)) void processRecoveryOutboxFile(f);
|
|
14499
|
+
}
|
|
14500
|
+
} catch {
|
|
14501
|
+
}
|
|
14502
|
+
try {
|
|
14503
|
+
const watcher = watch(RECOVERY_OUTBOX_DIR, (event, filename) => {
|
|
14504
|
+
if (event !== "rename" || !filename) return;
|
|
14505
|
+
if (!isFirstAttemptOutboxFile(filename)) return;
|
|
14506
|
+
if (existsSync(join2(RECOVERY_OUTBOX_DIR, filename))) {
|
|
14507
|
+
void processRecoveryOutboxFile(filename);
|
|
14508
|
+
}
|
|
14509
|
+
});
|
|
14510
|
+
watcher.unref?.();
|
|
14511
|
+
} catch (err) {
|
|
14512
|
+
process.stderr.write(
|
|
14513
|
+
`telegram-channel(${AGENT_CODE_NAME}): recovery outbox watch failed: ${err.message}
|
|
14514
|
+
`
|
|
14515
|
+
);
|
|
14516
|
+
}
|
|
14517
|
+
const retryTimer = setInterval(scanRecoveryRetries, RETRY_SCAN_INTERVAL_MS);
|
|
14518
|
+
retryTimer.unref?.();
|
|
14519
|
+
}
|
|
14520
|
+
startRecoveryOutboxWatcher();
|
|
14180
14521
|
function trackPendingMessage(chatId, messageId, chatType) {
|
|
14181
14522
|
const key = `${chatId}:${messageId}`;
|
|
14182
14523
|
const existing = pendingMessages.get(key);
|
|
14183
14524
|
if (existing) clearTimeout(existing.timer);
|
|
14184
14525
|
const timer = setTimeout(() => {
|
|
14185
14526
|
pendingMessages.delete(key);
|
|
14186
|
-
|
|
14527
|
+
clearPendingInboundMarker(chatId, messageId);
|
|
14528
|
+
void setMessageReaction(chatId, messageId, null);
|
|
14529
|
+
void telegramApiCall(
|
|
14530
|
+
"sendMessage",
|
|
14531
|
+
{
|
|
14532
|
+
chat_id: chatId,
|
|
14533
|
+
text: "Sorry \u2014 I didn't get a response back to you within 5 minutes, so this one fell on the floor. Please re-send if it's still relevant.",
|
|
14534
|
+
reply_to_message_id: Number(messageId),
|
|
14535
|
+
allow_sending_without_reply: true
|
|
14536
|
+
},
|
|
14537
|
+
1e4
|
|
14538
|
+
).then((resp) => {
|
|
14539
|
+
if (!resp.ok) {
|
|
14540
|
+
process.stderr.write(
|
|
14541
|
+
`telegram-channel(${AGENT_CODE_NAME}): timeout sendMessage failed for chat ${redactId(chatId)} message ${redactId(messageId)}: ${resp.description ?? "unknown"}
|
|
14542
|
+
`
|
|
14543
|
+
);
|
|
14544
|
+
}
|
|
14545
|
+
}).catch((err) => {
|
|
14546
|
+
process.stderr.write(
|
|
14547
|
+
`telegram-channel(${AGENT_CODE_NAME}): timeout sendMessage error for chat ${redactId(chatId)} message ${redactId(messageId)}: ${err.message}
|
|
14548
|
+
`
|
|
14549
|
+
);
|
|
14550
|
+
});
|
|
14187
14551
|
process.stderr.write(
|
|
14188
14552
|
`telegram-channel(${AGENT_CODE_NAME}): response timeout for message ${redactId(messageId)} in chat ${redactId(chatId)}
|
|
14189
14553
|
`
|
|
@@ -14191,6 +14555,7 @@ function trackPendingMessage(chatId, messageId, chatType) {
|
|
|
14191
14555
|
}, RESPONSE_TIMEOUT_MS);
|
|
14192
14556
|
timer.unref?.();
|
|
14193
14557
|
pendingMessages.set(key, { timer, chatType });
|
|
14558
|
+
writePendingInboundMarker(chatId, messageId, chatType);
|
|
14194
14559
|
}
|
|
14195
14560
|
function clearPendingMessage(chatId, messageId) {
|
|
14196
14561
|
if (messageId) {
|
|
@@ -14199,15 +14564,30 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
14199
14564
|
if (entry) {
|
|
14200
14565
|
clearTimeout(entry.timer);
|
|
14201
14566
|
pendingMessages.delete(key);
|
|
14567
|
+
clearPendingInboundMarker(chatId, messageId);
|
|
14202
14568
|
}
|
|
14203
14569
|
return;
|
|
14204
14570
|
}
|
|
14205
14571
|
const prefix = `${chatId}:`;
|
|
14572
|
+
let clearedPrivate = false;
|
|
14206
14573
|
for (const [key, entry] of pendingMessages) {
|
|
14207
14574
|
if (!key.startsWith(prefix)) continue;
|
|
14208
14575
|
if (entry.chatType !== "private") continue;
|
|
14209
14576
|
clearTimeout(entry.timer);
|
|
14210
14577
|
pendingMessages.delete(key);
|
|
14578
|
+
const msgId = key.slice(prefix.length);
|
|
14579
|
+
clearPendingInboundMarker(chatId, msgId);
|
|
14580
|
+
clearedPrivate = true;
|
|
14581
|
+
}
|
|
14582
|
+
if (clearedPrivate) return;
|
|
14583
|
+
for (const [key, entry] of pendingMessages) {
|
|
14584
|
+
if (!key.startsWith(prefix)) continue;
|
|
14585
|
+
if (entry.chatType === "private") continue;
|
|
14586
|
+
clearTimeout(entry.timer);
|
|
14587
|
+
pendingMessages.delete(key);
|
|
14588
|
+
const msgId = key.slice(prefix.length);
|
|
14589
|
+
clearPendingInboundMarker(chatId, msgId);
|
|
14590
|
+
return;
|
|
14211
14591
|
}
|
|
14212
14592
|
}
|
|
14213
14593
|
var mcp = new Server(
|
|
@@ -14221,13 +14601,14 @@ var mcp = new Server(
|
|
|
14221
14601
|
// Attachments rules live near the top because they get chopped otherwise
|
|
14222
14602
|
// and the agent silently loses attachment-handling guidance.
|
|
14223
14603
|
instructions: [
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
"
|
|
14227
|
-
'
|
|
14228
|
-
"
|
|
14229
|
-
|
|
14230
|
-
|
|
14604
|
+
// Highest-priority lines first — Claude Code truncates this string at
|
|
14605
|
+
// 2048 chars, so anything appended late silently disappears.
|
|
14606
|
+
"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.",
|
|
14607
|
+
'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.',
|
|
14608
|
+
"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.",
|
|
14609
|
+
'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.',
|
|
14610
|
+
"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.",
|
|
14611
|
+
"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. 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}."
|
|
14231
14612
|
].join(" ")
|
|
14232
14613
|
}
|
|
14233
14614
|
);
|
|
@@ -14287,7 +14668,7 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
14287
14668
|
},
|
|
14288
14669
|
{
|
|
14289
14670
|
name: "telegram.react",
|
|
14290
|
-
description:
|
|
14671
|
+
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 \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.",
|
|
14291
14672
|
inputSchema: {
|
|
14292
14673
|
type: "object",
|
|
14293
14674
|
properties: {
|
|
@@ -14539,6 +14920,34 @@ async function pollLoop() {
|
|
|
14539
14920
|
if (content.length === 0 && classifiedAttachments.length === 0) continue;
|
|
14540
14921
|
const chatId = String(msg.chat.id);
|
|
14541
14922
|
if (ALLOWED_CHATS.size > 0 && !ALLOWED_CHATS.has(chatId)) continue;
|
|
14923
|
+
const trimmedContent = content.trim();
|
|
14924
|
+
if (isRestartSyntax(trimmedContent)) {
|
|
14925
|
+
const disposition = await classifyRestartCommand(trimmedContent);
|
|
14926
|
+
if (disposition === "act") {
|
|
14927
|
+
await handleRestartCommand({
|
|
14928
|
+
chatId,
|
|
14929
|
+
messageId: String(msg.message_id)
|
|
14930
|
+
});
|
|
14931
|
+
} else if (disposition === "verification_failed") {
|
|
14932
|
+
try {
|
|
14933
|
+
await telegramApiCall(
|
|
14934
|
+
"sendMessage",
|
|
14935
|
+
{
|
|
14936
|
+
chat_id: chatId,
|
|
14937
|
+
text: `\u274C Couldn't verify the restart target. Please retry /restart in a few seconds.`,
|
|
14938
|
+
reply_to_message_id: Number(msg.message_id)
|
|
14939
|
+
},
|
|
14940
|
+
1e4
|
|
14941
|
+
);
|
|
14942
|
+
} catch (err) {
|
|
14943
|
+
process.stderr.write(
|
|
14944
|
+
`telegram-channel(${AGENT_CODE_NAME}): /restart verification-failed ack send failed: ${redactAugmentedPaths(err.message)}
|
|
14945
|
+
`
|
|
14946
|
+
);
|
|
14947
|
+
}
|
|
14948
|
+
}
|
|
14949
|
+
continue;
|
|
14950
|
+
}
|
|
14542
14951
|
const userId = msg.from?.id != null ? String(msg.from.id) : "unknown";
|
|
14543
14952
|
const userName = msg.from?.username || [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ").trim() || userId;
|
|
14544
14953
|
const messageId = String(msg.message_id);
|