@integrity-labs/agt-cli 0.20.1 → 0.20.5
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-Y3E5EFCM.js → chunk-LE6K3LZ6.js} +95 -13
- package/dist/chunk-LE6K3LZ6.js.map +1 -0
- package/dist/lib/manager-worker.js +169 -11
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +175 -49
- package/mcp/telegram-channel.js +128 -41
- package/package.json +1 -1
- package/dist/chunk-Y3E5EFCM.js.map +0 -1
package/mcp/slack-channel.js
CHANGED
|
@@ -14323,27 +14323,6 @@ async function isThreadKilled(opts) {
|
|
|
14323
14323
|
}
|
|
14324
14324
|
}
|
|
14325
14325
|
|
|
14326
|
-
// src/pending-timeout-rearm.ts
|
|
14327
|
-
var DEFAULT_STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
14328
|
-
var DEFAULT_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
14329
|
-
function decideRearmAction(opts) {
|
|
14330
|
-
const { receivedAtMs, nowMs } = opts;
|
|
14331
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
14332
|
-
const staleMs = opts.staleMs ?? DEFAULT_STALE_MARKER_MS;
|
|
14333
|
-
if (!Number.isFinite(receivedAtMs)) {
|
|
14334
|
-
return { kind: "stale" };
|
|
14335
|
-
}
|
|
14336
|
-
const elapsedMs = Math.max(0, nowMs - receivedAtMs);
|
|
14337
|
-
if (elapsedMs > staleMs) {
|
|
14338
|
-
return { kind: "stale" };
|
|
14339
|
-
}
|
|
14340
|
-
const remainingMs = timeoutMs - elapsedMs;
|
|
14341
|
-
if (remainingMs <= 0) {
|
|
14342
|
-
return { kind: "fire" };
|
|
14343
|
-
}
|
|
14344
|
-
return { kind: "arm", remainingMs };
|
|
14345
|
-
}
|
|
14346
|
-
|
|
14347
14326
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.27.1_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
|
|
14348
14327
|
import process2 from "process";
|
|
14349
14328
|
|
|
@@ -14439,16 +14418,21 @@ var StdioServerTransport = class {
|
|
|
14439
14418
|
// src/slack-channel.ts
|
|
14440
14419
|
import {
|
|
14441
14420
|
chmodSync,
|
|
14421
|
+
closeSync,
|
|
14442
14422
|
createWriteStream,
|
|
14443
14423
|
existsSync,
|
|
14424
|
+
fsyncSync,
|
|
14425
|
+
ftruncateSync,
|
|
14444
14426
|
mkdirSync as mkdirSync2,
|
|
14427
|
+
openSync,
|
|
14445
14428
|
readFileSync as readFileSync2,
|
|
14446
14429
|
readdirSync,
|
|
14447
14430
|
renameSync,
|
|
14448
14431
|
statSync,
|
|
14449
14432
|
unlinkSync,
|
|
14450
14433
|
watch,
|
|
14451
|
-
writeFileSync as writeFileSync2
|
|
14434
|
+
writeFileSync as writeFileSync2,
|
|
14435
|
+
writeSync
|
|
14452
14436
|
} from "fs";
|
|
14453
14437
|
import { basename, join as join2, resolve as resolve2 } from "path";
|
|
14454
14438
|
import { homedir as homedir2 } from "os";
|
|
@@ -15047,6 +15031,30 @@ function createSlackBotUserIdClient(args) {
|
|
|
15047
15031
|
};
|
|
15048
15032
|
}
|
|
15049
15033
|
|
|
15034
|
+
// src/pending-reminder-schedule.ts
|
|
15035
|
+
var REMINDER_SCHEDULE_MS = [5, 10, 20].map((m) => m * 6e4);
|
|
15036
|
+
var MAX_REMINDERS = REMINDER_SCHEDULE_MS.length;
|
|
15037
|
+
var RESPONSE_TIMEOUT_MS = REMINDER_SCHEDULE_MS[0];
|
|
15038
|
+
function nextReminderFireAtMs(receivedAtMs, currentReminderCount) {
|
|
15039
|
+
const safeCount = Number.isFinite(currentReminderCount) ? Math.max(0, Math.trunc(currentReminderCount)) : 0;
|
|
15040
|
+
let elapsed = 0;
|
|
15041
|
+
for (let i = 0; i <= safeCount && i < MAX_REMINDERS; i++) {
|
|
15042
|
+
elapsed += REMINDER_SCHEDULE_MS[i];
|
|
15043
|
+
}
|
|
15044
|
+
return receivedAtMs + elapsed;
|
|
15045
|
+
}
|
|
15046
|
+
function reminderText(reminderIndex, cancelCommandAvailable) {
|
|
15047
|
+
switch (reminderIndex) {
|
|
15048
|
+
case 0:
|
|
15049
|
+
return "Heads-up \u2014 I'm still on it. This one's taking more than the usual minute.";
|
|
15050
|
+
case 1:
|
|
15051
|
+
return "Still working. Going deeper than expected; back when I have something useful.";
|
|
15052
|
+
case 2:
|
|
15053
|
+
default:
|
|
15054
|
+
return cancelCommandAvailable ? "I'm 30+ min in on this. Type `/cancel` to abort, or ping me if it's urgent and I'll re-scope." : "I'm 30+ min in on this. If the answer's urgent, ping me and I'll pause to triage. Otherwise I'll keep going.";
|
|
15055
|
+
}
|
|
15056
|
+
}
|
|
15057
|
+
|
|
15050
15058
|
// src/slack-channel.ts
|
|
15051
15059
|
var BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
|
|
15052
15060
|
var APP_TOKEN = process.env.SLACK_APP_TOKEN;
|
|
@@ -15091,8 +15099,8 @@ var SLACK_PEER_CLASSIFIER_CONFIG = {
|
|
|
15091
15099
|
peers: parsePeersEnv(process.env.SLACK_PEERS, process.env.SLACK_PEERS_GATE),
|
|
15092
15100
|
peer_disabled_mode: SLACK_PEER_DISABLED_MODE
|
|
15093
15101
|
};
|
|
15094
|
-
var RESPONSE_TIMEOUT_MS = 3e5;
|
|
15095
15102
|
var pendingMessages = /* @__PURE__ */ new Map();
|
|
15103
|
+
var CANCEL_COMMAND_AVAILABLE = false;
|
|
15096
15104
|
var SLACK_AGENT_DIR = AGENT_CODE_NAME ? join2(homedir2(), ".augmented", AGENT_CODE_NAME) : null;
|
|
15097
15105
|
var SLACK_PENDING_INBOUND_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-pending-inbound") : null;
|
|
15098
15106
|
var SLACK_RECOVERY_OUTBOX_DIR = SLACK_AGENT_DIR ? join2(SLACK_AGENT_DIR, "slack-recovery-outbox") : null;
|
|
@@ -15128,6 +15136,49 @@ function writeSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
|
15128
15136
|
);
|
|
15129
15137
|
}
|
|
15130
15138
|
}
|
|
15139
|
+
function updateSlackPendingInboundMarker(marker) {
|
|
15140
|
+
const path = slackPendingInboundPath(marker.channel, marker.thread_ts, marker.message_ts);
|
|
15141
|
+
if (!path) return false;
|
|
15142
|
+
let fd;
|
|
15143
|
+
try {
|
|
15144
|
+
fd = openSync(path, "r+");
|
|
15145
|
+
} catch (err) {
|
|
15146
|
+
const code = err.code;
|
|
15147
|
+
if (code === "ENOENT") return false;
|
|
15148
|
+
process.stderr.write(
|
|
15149
|
+
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker open-for-update failed: ${err.message}
|
|
15150
|
+
`
|
|
15151
|
+
);
|
|
15152
|
+
return false;
|
|
15153
|
+
}
|
|
15154
|
+
try {
|
|
15155
|
+
const payload = Buffer.from(JSON.stringify(marker));
|
|
15156
|
+
ftruncateSync(fd, 0);
|
|
15157
|
+
writeSync(fd, payload, 0, payload.length, 0);
|
|
15158
|
+
fsyncSync(fd);
|
|
15159
|
+
return true;
|
|
15160
|
+
} catch (err) {
|
|
15161
|
+
process.stderr.write(
|
|
15162
|
+
`slack-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
15163
|
+
`
|
|
15164
|
+
);
|
|
15165
|
+
return false;
|
|
15166
|
+
} finally {
|
|
15167
|
+
try {
|
|
15168
|
+
closeSync(fd);
|
|
15169
|
+
} catch {
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15172
|
+
}
|
|
15173
|
+
function readSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
15174
|
+
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
15175
|
+
if (!path || !existsSync(path)) return null;
|
|
15176
|
+
try {
|
|
15177
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
15178
|
+
} catch {
|
|
15179
|
+
return null;
|
|
15180
|
+
}
|
|
15181
|
+
}
|
|
15131
15182
|
function clearSlackPendingInboundMarker(channel, threadTs, messageTs) {
|
|
15132
15183
|
const path = slackPendingInboundPath(channel, threadTs, messageTs);
|
|
15133
15184
|
if (!path) return;
|
|
@@ -15327,32 +15378,72 @@ function startSlackRecoveryOutboxWatcher() {
|
|
|
15327
15378
|
retryTimer.unref?.();
|
|
15328
15379
|
}
|
|
15329
15380
|
startSlackRecoveryOutboxWatcher();
|
|
15330
|
-
|
|
15381
|
+
var SLACK_REMINDER_FETCH_TIMEOUT_MS = 1e4;
|
|
15382
|
+
async function slackFetchWithTimeout(url, body) {
|
|
15383
|
+
const controller = new AbortController();
|
|
15384
|
+
const timeoutId = setTimeout(() => controller.abort(), SLACK_REMINDER_FETCH_TIMEOUT_MS);
|
|
15331
15385
|
try {
|
|
15332
|
-
await fetch(
|
|
15386
|
+
await fetch(url, {
|
|
15333
15387
|
method: "POST",
|
|
15334
15388
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${BOT_TOKEN}` },
|
|
15335
|
-
body: JSON.stringify(
|
|
15336
|
-
|
|
15337
|
-
thread_ts: threadTs,
|
|
15338
|
-
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."
|
|
15339
|
-
})
|
|
15389
|
+
body: JSON.stringify(body),
|
|
15390
|
+
signal: controller.signal
|
|
15340
15391
|
});
|
|
15341
|
-
}
|
|
15392
|
+
} finally {
|
|
15393
|
+
clearTimeout(timeoutId);
|
|
15394
|
+
}
|
|
15395
|
+
}
|
|
15396
|
+
async function fireSlackResponseTimeout(channel, threadTs, messageTs) {
|
|
15397
|
+
const marker = readSlackPendingInboundMarker(channel, threadTs, messageTs);
|
|
15398
|
+
if (!marker) {
|
|
15399
|
+
return;
|
|
15400
|
+
}
|
|
15401
|
+
const reminderIndex = marker.reminder_count ?? 0;
|
|
15402
|
+
if (reminderIndex >= MAX_REMINDERS) {
|
|
15403
|
+
return;
|
|
15342
15404
|
}
|
|
15343
15405
|
try {
|
|
15344
|
-
await
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15406
|
+
await slackFetchWithTimeout("https://slack.com/api/chat.postMessage", {
|
|
15407
|
+
channel,
|
|
15408
|
+
thread_ts: threadTs,
|
|
15409
|
+
text: reminderText(reminderIndex, CANCEL_COMMAND_AVAILABLE)
|
|
15348
15410
|
});
|
|
15349
15411
|
} catch {
|
|
15350
15412
|
}
|
|
15413
|
+
if (reminderIndex === 1) {
|
|
15414
|
+
try {
|
|
15415
|
+
await slackFetchWithTimeout("https://slack.com/api/reactions.remove", {
|
|
15416
|
+
channel,
|
|
15417
|
+
timestamp: messageTs,
|
|
15418
|
+
name: "eyes"
|
|
15419
|
+
});
|
|
15420
|
+
} catch {
|
|
15421
|
+
}
|
|
15422
|
+
try {
|
|
15423
|
+
await slackFetchWithTimeout("https://slack.com/api/reactions.add", {
|
|
15424
|
+
channel,
|
|
15425
|
+
timestamp: messageTs,
|
|
15426
|
+
name: "hourglass_flowing_sand"
|
|
15427
|
+
});
|
|
15428
|
+
} catch {
|
|
15429
|
+
}
|
|
15430
|
+
}
|
|
15431
|
+
const nextCount = reminderIndex + 1;
|
|
15432
|
+
const persisted = updateSlackPendingInboundMarker({ ...marker, reminder_count: nextCount });
|
|
15433
|
+
if (!persisted) {
|
|
15434
|
+
process.stderr.write(
|
|
15435
|
+
`slack-channel(${AGENT_CODE_NAME}): reminder ${nextCount}/${MAX_REMINDERS} posted but marker cleared mid-fire (agent replied) \u2014 not arming next timer
|
|
15436
|
+
`
|
|
15437
|
+
);
|
|
15438
|
+
return;
|
|
15439
|
+
}
|
|
15351
15440
|
process.stderr.write(
|
|
15352
|
-
`slack-channel:
|
|
15441
|
+
`slack-channel(${AGENT_CODE_NAME}): reminder ${nextCount}/${MAX_REMINDERS} posted for message ${redactSlackId(messageTs)} in ${redactSlackId(channel)} (marker left pending)
|
|
15353
15442
|
`
|
|
15354
15443
|
);
|
|
15355
|
-
|
|
15444
|
+
if (nextCount < MAX_REMINDERS) {
|
|
15445
|
+
armSlackPendingTimer(channel, threadTs, messageTs, REMINDER_SCHEDULE_MS[nextCount]);
|
|
15446
|
+
}
|
|
15356
15447
|
}
|
|
15357
15448
|
function armSlackPendingTimer(channel, threadTs, messageTs, durationMs) {
|
|
15358
15449
|
const key2 = `${channel}:${threadTs}:${messageTs}`;
|
|
@@ -15387,8 +15478,10 @@ function rearmSlackPendingTimersFromDisk() {
|
|
|
15387
15478
|
let armed = 0;
|
|
15388
15479
|
let firedNow = 0;
|
|
15389
15480
|
let cleared = 0;
|
|
15481
|
+
let exhausted = 0;
|
|
15390
15482
|
for (const filename of filenames) {
|
|
15391
15483
|
if (!filename.endsWith(".json")) continue;
|
|
15484
|
+
if (filename.endsWith(".tmp")) continue;
|
|
15392
15485
|
const fullPath = join2(SLACK_PENDING_INBOUND_DIR, filename);
|
|
15393
15486
|
let marker;
|
|
15394
15487
|
try {
|
|
@@ -15412,15 +15505,18 @@ function rearmSlackPendingTimersFromDisk() {
|
|
|
15412
15505
|
}
|
|
15413
15506
|
continue;
|
|
15414
15507
|
}
|
|
15415
|
-
const
|
|
15416
|
-
|
|
15417
|
-
|
|
15418
|
-
|
|
15419
|
-
|
|
15420
|
-
|
|
15421
|
-
|
|
15508
|
+
const receivedAtMs = Date.parse(received_at);
|
|
15509
|
+
if (!Number.isFinite(receivedAtMs)) {
|
|
15510
|
+
try {
|
|
15511
|
+
unlinkSync(fullPath);
|
|
15512
|
+
} catch {
|
|
15513
|
+
}
|
|
15514
|
+
cleared++;
|
|
15515
|
+
continue;
|
|
15516
|
+
}
|
|
15517
|
+
if (now - receivedAtMs > STALE_MARKER_MS) {
|
|
15422
15518
|
process.stderr.write(
|
|
15423
|
-
`slack-channel(${AGENT_CODE_NAME}): rearm skipping stale
|
|
15519
|
+
`slack-channel(${AGENT_CODE_NAME}): rearm skipping stale marker channel=${redactSlackId(channel)} message=${redactSlackId(message_ts)}
|
|
15424
15520
|
`
|
|
15425
15521
|
);
|
|
15426
15522
|
try {
|
|
@@ -15430,18 +15526,25 @@ function rearmSlackPendingTimersFromDisk() {
|
|
|
15430
15526
|
cleared++;
|
|
15431
15527
|
continue;
|
|
15432
15528
|
}
|
|
15433
|
-
|
|
15529
|
+
const reminderCount = marker.reminder_count ?? 0;
|
|
15530
|
+
if (reminderCount >= MAX_REMINDERS) {
|
|
15531
|
+
exhausted++;
|
|
15532
|
+
continue;
|
|
15533
|
+
}
|
|
15534
|
+
const nextFireAt = nextReminderFireAtMs(receivedAtMs, reminderCount);
|
|
15535
|
+
const remainingMs = nextFireAt - now;
|
|
15536
|
+
if (remainingMs <= 0) {
|
|
15434
15537
|
pendingMessages.delete(`${channel}:${thread_ts}:${message_ts}`);
|
|
15435
15538
|
void fireSlackResponseTimeout(channel, thread_ts, message_ts);
|
|
15436
15539
|
firedNow++;
|
|
15437
15540
|
} else {
|
|
15438
|
-
armSlackPendingTimer(channel, thread_ts, message_ts,
|
|
15541
|
+
armSlackPendingTimer(channel, thread_ts, message_ts, remainingMs);
|
|
15439
15542
|
armed++;
|
|
15440
15543
|
}
|
|
15441
15544
|
}
|
|
15442
|
-
if (armed > 0 || firedNow > 0 || cleared > 0) {
|
|
15545
|
+
if (armed > 0 || firedNow > 0 || cleared > 0 || exhausted > 0) {
|
|
15443
15546
|
process.stderr.write(
|
|
15444
|
-
`slack-channel(${AGENT_CODE_NAME}): rearm summary armed=${armed} fired_now=${firedNow} stale_cleared=${cleared}
|
|
15547
|
+
`slack-channel(${AGENT_CODE_NAME}): rearm summary armed=${armed} fired_now=${firedNow} stale_cleared=${cleared} reminders_exhausted=${exhausted}
|
|
15445
15548
|
`
|
|
15446
15549
|
);
|
|
15447
15550
|
}
|
|
@@ -15456,6 +15559,26 @@ function clearPendingMessage(channel, threadTs) {
|
|
|
15456
15559
|
}
|
|
15457
15560
|
clearAllSlackPendingMarkersForThread(channel, threadTs);
|
|
15458
15561
|
}
|
|
15562
|
+
function noteThreadActivity(channel, threadTs) {
|
|
15563
|
+
if (!channel || !threadTs) return;
|
|
15564
|
+
clearPendingMessage(channel, threadTs);
|
|
15565
|
+
}
|
|
15566
|
+
function noteThreadActivityByMessageTs(channel, messageTs) {
|
|
15567
|
+
if (!channel || !messageTs) return;
|
|
15568
|
+
clearPendingMessage(channel, messageTs);
|
|
15569
|
+
const suffix = `:${messageTs}`;
|
|
15570
|
+
for (const [key2, timer] of pendingMessages) {
|
|
15571
|
+
if (!key2.startsWith(`${channel}:`)) continue;
|
|
15572
|
+
if (!key2.endsWith(suffix)) continue;
|
|
15573
|
+
clearTimeout(timer);
|
|
15574
|
+
pendingMessages.delete(key2);
|
|
15575
|
+
const parts = key2.split(":");
|
|
15576
|
+
if (parts.length >= 3) {
|
|
15577
|
+
const threadTs = parts[1];
|
|
15578
|
+
clearSlackPendingInboundMarker(channel, threadTs, messageTs);
|
|
15579
|
+
}
|
|
15580
|
+
}
|
|
15581
|
+
}
|
|
15459
15582
|
var RESTART_FLAGS_DIR = join2(homedir2(), ".augmented", "restart-flags");
|
|
15460
15583
|
function hashChannelId(id) {
|
|
15461
15584
|
return createHash("sha256").update(id).digest("hex").slice(0, 8);
|
|
@@ -16221,6 +16344,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16221
16344
|
}
|
|
16222
16345
|
if (name === "slack.react") {
|
|
16223
16346
|
const { channel, timestamp, emoji: emoji2 } = args;
|
|
16347
|
+
noteThreadActivityByMessageTs(channel, timestamp);
|
|
16224
16348
|
try {
|
|
16225
16349
|
const res = await fetch("https://slack.com/api/reactions.add", {
|
|
16226
16350
|
method: "POST",
|
|
@@ -16248,6 +16372,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
16248
16372
|
if (name === "slack.read_thread") {
|
|
16249
16373
|
const { channel, thread_ts, limit: rawLimit } = args;
|
|
16250
16374
|
const limit = Math.min(Math.max(rawLimit ?? 50, 1), 200);
|
|
16375
|
+
noteThreadActivity(channel, thread_ts);
|
|
16251
16376
|
try {
|
|
16252
16377
|
const allMessages = [];
|
|
16253
16378
|
let cursor;
|
|
@@ -16637,6 +16762,7 @@ async function handleSendStructured(args) {
|
|
|
16637
16762
|
if (typeof text !== "string" || !text) {
|
|
16638
16763
|
return errResult("text is required (used as fallback for push notifications and unfurls)");
|
|
16639
16764
|
}
|
|
16765
|
+
noteThreadActivity(channel, thread_ts);
|
|
16640
16766
|
const validation = validateSlackBlocks2(blocks);
|
|
16641
16767
|
if (!validation.ok) {
|
|
16642
16768
|
return errResult(`Invalid blocks: ${validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
package/mcp/telegram-channel.js
CHANGED
|
@@ -13865,16 +13865,21 @@ var StdioServerTransport = class {
|
|
|
13865
13865
|
import https from "https";
|
|
13866
13866
|
import { createHash, randomUUID } from "crypto";
|
|
13867
13867
|
import {
|
|
13868
|
+
closeSync,
|
|
13868
13869
|
createWriteStream,
|
|
13869
13870
|
existsSync,
|
|
13871
|
+
fsyncSync,
|
|
13872
|
+
ftruncateSync,
|
|
13870
13873
|
mkdirSync as mkdirSync2,
|
|
13874
|
+
openSync,
|
|
13871
13875
|
readFileSync,
|
|
13872
13876
|
readdirSync,
|
|
13873
13877
|
renameSync as renameSync2,
|
|
13874
13878
|
statSync,
|
|
13875
13879
|
unlinkSync as unlinkSync2,
|
|
13876
13880
|
watch,
|
|
13877
|
-
writeFileSync as writeFileSync2
|
|
13881
|
+
writeFileSync as writeFileSync2,
|
|
13882
|
+
writeSync
|
|
13878
13883
|
} from "fs";
|
|
13879
13884
|
import { homedir as homedir2 } from "os";
|
|
13880
13885
|
import { join as join2 } from "path";
|
|
@@ -14000,27 +14005,6 @@ function redactAugmentedPaths(msg) {
|
|
|
14000
14005
|
);
|
|
14001
14006
|
}
|
|
14002
14007
|
|
|
14003
|
-
// src/pending-timeout-rearm.ts
|
|
14004
|
-
var DEFAULT_STALE_MARKER_MS = 24 * 60 * 60 * 1e3;
|
|
14005
|
-
var DEFAULT_RESPONSE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
14006
|
-
function decideRearmAction(opts) {
|
|
14007
|
-
const { receivedAtMs, nowMs } = opts;
|
|
14008
|
-
const timeoutMs = opts.timeoutMs ?? DEFAULT_RESPONSE_TIMEOUT_MS;
|
|
14009
|
-
const staleMs = opts.staleMs ?? DEFAULT_STALE_MARKER_MS;
|
|
14010
|
-
if (!Number.isFinite(receivedAtMs)) {
|
|
14011
|
-
return { kind: "stale" };
|
|
14012
|
-
}
|
|
14013
|
-
const elapsedMs = Math.max(0, nowMs - receivedAtMs);
|
|
14014
|
-
if (elapsedMs > staleMs) {
|
|
14015
|
-
return { kind: "stale" };
|
|
14016
|
-
}
|
|
14017
|
-
const remainingMs = timeoutMs - elapsedMs;
|
|
14018
|
-
if (remainingMs <= 0) {
|
|
14019
|
-
return { kind: "fire" };
|
|
14020
|
-
}
|
|
14021
|
-
return { kind: "arm", remainingMs };
|
|
14022
|
-
}
|
|
14023
|
-
|
|
14024
14008
|
// src/telegram-attachments.ts
|
|
14025
14009
|
import { mkdirSync, writeFileSync, chmodSync, renameSync, unlinkSync } from "fs";
|
|
14026
14010
|
function resolveTelegramInboundDir(codeName) {
|
|
@@ -14836,6 +14820,30 @@ function createObservedChatClient(args) {
|
|
|
14836
14820
|
};
|
|
14837
14821
|
}
|
|
14838
14822
|
|
|
14823
|
+
// src/pending-reminder-schedule.ts
|
|
14824
|
+
var REMINDER_SCHEDULE_MS = [5, 10, 20].map((m) => m * 6e4);
|
|
14825
|
+
var MAX_REMINDERS = REMINDER_SCHEDULE_MS.length;
|
|
14826
|
+
var RESPONSE_TIMEOUT_MS = REMINDER_SCHEDULE_MS[0];
|
|
14827
|
+
function nextReminderFireAtMs(receivedAtMs, currentReminderCount) {
|
|
14828
|
+
const safeCount = Number.isFinite(currentReminderCount) ? Math.max(0, Math.trunc(currentReminderCount)) : 0;
|
|
14829
|
+
let elapsed = 0;
|
|
14830
|
+
for (let i = 0; i <= safeCount && i < MAX_REMINDERS; i++) {
|
|
14831
|
+
elapsed += REMINDER_SCHEDULE_MS[i];
|
|
14832
|
+
}
|
|
14833
|
+
return receivedAtMs + elapsed;
|
|
14834
|
+
}
|
|
14835
|
+
function reminderText(reminderIndex, cancelCommandAvailable) {
|
|
14836
|
+
switch (reminderIndex) {
|
|
14837
|
+
case 0:
|
|
14838
|
+
return "Heads-up \u2014 I'm still on it. This one's taking more than the usual minute.";
|
|
14839
|
+
case 1:
|
|
14840
|
+
return "Still working. Going deeper than expected; back when I have something useful.";
|
|
14841
|
+
case 2:
|
|
14842
|
+
default:
|
|
14843
|
+
return cancelCommandAvailable ? "I'm 30+ min in on this. Type `/cancel` to abort, or ping me if it's urgent and I'll re-scope." : "I'm 30+ min in on this. If the answer's urgent, ping me and I'll pause to triage. Otherwise I'll keep going.";
|
|
14844
|
+
}
|
|
14845
|
+
}
|
|
14846
|
+
|
|
14839
14847
|
// src/telegram-channel.ts
|
|
14840
14848
|
function redactId(id) {
|
|
14841
14849
|
return createHash("sha256").update(String(id)).digest("hex").slice(0, 8);
|
|
@@ -14969,7 +14977,8 @@ function telegramApiCall(method, body, timeoutMs) {
|
|
|
14969
14977
|
});
|
|
14970
14978
|
}
|
|
14971
14979
|
var ACK_EMOJI = "\u{1F440}";
|
|
14972
|
-
var
|
|
14980
|
+
var CANCEL_COMMAND_AVAILABLE = false;
|
|
14981
|
+
var REMINDER_REACTION_EMOJI = "\u23F3";
|
|
14973
14982
|
async function setMessageReaction(chatId, messageId, emoji2) {
|
|
14974
14983
|
try {
|
|
14975
14984
|
const resp = await telegramApiCall(
|
|
@@ -15189,6 +15198,49 @@ function clearPendingInboundMarker(chatId, messageId) {
|
|
|
15189
15198
|
} catch {
|
|
15190
15199
|
}
|
|
15191
15200
|
}
|
|
15201
|
+
function updatePendingInboundMarker(marker) {
|
|
15202
|
+
const path = pendingInboundPath(marker.chat_id, marker.message_id);
|
|
15203
|
+
if (!path) return false;
|
|
15204
|
+
let fd;
|
|
15205
|
+
try {
|
|
15206
|
+
fd = openSync(path, "r+");
|
|
15207
|
+
} catch (err) {
|
|
15208
|
+
const code = err.code;
|
|
15209
|
+
if (code === "ENOENT") return false;
|
|
15210
|
+
process.stderr.write(
|
|
15211
|
+
`telegram-channel(${AGENT_CODE_NAME}): pending-inbound marker open-for-update failed: ${err.message}
|
|
15212
|
+
`
|
|
15213
|
+
);
|
|
15214
|
+
return false;
|
|
15215
|
+
}
|
|
15216
|
+
try {
|
|
15217
|
+
const payload = Buffer.from(JSON.stringify(marker));
|
|
15218
|
+
ftruncateSync(fd, 0);
|
|
15219
|
+
writeSync(fd, payload, 0, payload.length, 0);
|
|
15220
|
+
fsyncSync(fd);
|
|
15221
|
+
return true;
|
|
15222
|
+
} catch (err) {
|
|
15223
|
+
process.stderr.write(
|
|
15224
|
+
`telegram-channel(${AGENT_CODE_NAME}): pending-inbound marker write failed: ${err.message}
|
|
15225
|
+
`
|
|
15226
|
+
);
|
|
15227
|
+
return false;
|
|
15228
|
+
} finally {
|
|
15229
|
+
try {
|
|
15230
|
+
closeSync(fd);
|
|
15231
|
+
} catch {
|
|
15232
|
+
}
|
|
15233
|
+
}
|
|
15234
|
+
}
|
|
15235
|
+
function readPendingInboundMarker(chatId, messageId) {
|
|
15236
|
+
const path = pendingInboundPath(chatId, messageId);
|
|
15237
|
+
if (!path || !existsSync(path)) return null;
|
|
15238
|
+
try {
|
|
15239
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
15240
|
+
} catch {
|
|
15241
|
+
return null;
|
|
15242
|
+
}
|
|
15243
|
+
}
|
|
15192
15244
|
var MAX_RECOVERY_ATTEMPTS = 3;
|
|
15193
15245
|
function nextRetryName(filename) {
|
|
15194
15246
|
const match = filename.match(/^(.*?)(?:\.retry-(\d+))?\.json$/);
|
|
@@ -15356,13 +15408,16 @@ function startRecoveryOutboxWatcher() {
|
|
|
15356
15408
|
}
|
|
15357
15409
|
startRecoveryOutboxWatcher();
|
|
15358
15410
|
function fireTelegramResponseTimeout(chatId, messageId) {
|
|
15359
|
-
|
|
15360
|
-
|
|
15411
|
+
const marker = readPendingInboundMarker(chatId, messageId);
|
|
15412
|
+
if (!marker) return;
|
|
15413
|
+
const reminderIndex = marker.reminder_count ?? 0;
|
|
15414
|
+
if (reminderIndex >= MAX_REMINDERS) return;
|
|
15415
|
+
const chatType = marker.chat_type ?? "private";
|
|
15361
15416
|
void telegramApiCall(
|
|
15362
15417
|
"sendMessage",
|
|
15363
15418
|
{
|
|
15364
15419
|
chat_id: chatId,
|
|
15365
|
-
text:
|
|
15420
|
+
text: reminderText(reminderIndex, CANCEL_COMMAND_AVAILABLE),
|
|
15366
15421
|
reply_to_message_id: Number(messageId),
|
|
15367
15422
|
allow_sending_without_reply: true
|
|
15368
15423
|
},
|
|
@@ -15370,20 +15425,35 @@ function fireTelegramResponseTimeout(chatId, messageId) {
|
|
|
15370
15425
|
).then((resp) => {
|
|
15371
15426
|
if (!resp.ok) {
|
|
15372
15427
|
process.stderr.write(
|
|
15373
|
-
`telegram-channel(${AGENT_CODE_NAME}):
|
|
15428
|
+
`telegram-channel(${AGENT_CODE_NAME}): reminder sendMessage failed for chat ${redactId(chatId)} message ${redactId(messageId)}: ${resp.description ?? "unknown"}
|
|
15374
15429
|
`
|
|
15375
15430
|
);
|
|
15376
15431
|
}
|
|
15377
15432
|
}).catch((err) => {
|
|
15378
15433
|
process.stderr.write(
|
|
15379
|
-
`telegram-channel(${AGENT_CODE_NAME}):
|
|
15434
|
+
`telegram-channel(${AGENT_CODE_NAME}): reminder sendMessage error for chat ${redactId(chatId)} message ${redactId(messageId)}: ${err.message}
|
|
15380
15435
|
`
|
|
15381
15436
|
);
|
|
15382
15437
|
});
|
|
15438
|
+
if (reminderIndex === 1) {
|
|
15439
|
+
void setMessageReaction(chatId, messageId, REMINDER_REACTION_EMOJI);
|
|
15440
|
+
}
|
|
15441
|
+
const nextCount = reminderIndex + 1;
|
|
15442
|
+
const persisted = updatePendingInboundMarker({ ...marker, reminder_count: nextCount, chat_type: chatType });
|
|
15443
|
+
if (!persisted) {
|
|
15444
|
+
process.stderr.write(
|
|
15445
|
+
`telegram-channel(${AGENT_CODE_NAME}): reminder ${nextCount}/${MAX_REMINDERS} posted but marker cleared mid-fire (agent replied) \u2014 not arming next timer
|
|
15446
|
+
`
|
|
15447
|
+
);
|
|
15448
|
+
return;
|
|
15449
|
+
}
|
|
15383
15450
|
process.stderr.write(
|
|
15384
|
-
`telegram-channel(${AGENT_CODE_NAME}):
|
|
15451
|
+
`telegram-channel(${AGENT_CODE_NAME}): reminder ${nextCount}/${MAX_REMINDERS} posted for message ${redactId(messageId)} in chat ${redactId(chatId)} (marker left pending)
|
|
15385
15452
|
`
|
|
15386
15453
|
);
|
|
15454
|
+
if (nextCount < MAX_REMINDERS) {
|
|
15455
|
+
armTelegramPendingTimer(chatId, messageId, chatType, REMINDER_SCHEDULE_MS[nextCount]);
|
|
15456
|
+
}
|
|
15387
15457
|
}
|
|
15388
15458
|
function armTelegramPendingTimer(chatId, messageId, chatType, durationMs) {
|
|
15389
15459
|
const key2 = `${chatId}:${messageId}`;
|
|
@@ -15418,8 +15488,10 @@ function rearmPendingTimersFromDisk() {
|
|
|
15418
15488
|
let armed = 0;
|
|
15419
15489
|
let firedNow = 0;
|
|
15420
15490
|
let cleared = 0;
|
|
15491
|
+
let exhausted = 0;
|
|
15421
15492
|
for (const filename of filenames) {
|
|
15422
15493
|
if (!filename.endsWith(".json")) continue;
|
|
15494
|
+
if (filename.endsWith(".tmp")) continue;
|
|
15423
15495
|
const fullPath = join2(PENDING_INBOUND_DIR, filename);
|
|
15424
15496
|
let marker;
|
|
15425
15497
|
try {
|
|
@@ -15443,15 +15515,18 @@ function rearmPendingTimersFromDisk() {
|
|
|
15443
15515
|
}
|
|
15444
15516
|
continue;
|
|
15445
15517
|
}
|
|
15446
|
-
const
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15518
|
+
const receivedAtMs = Date.parse(received_at);
|
|
15519
|
+
if (!Number.isFinite(receivedAtMs)) {
|
|
15520
|
+
try {
|
|
15521
|
+
unlinkSync2(fullPath);
|
|
15522
|
+
} catch {
|
|
15523
|
+
}
|
|
15524
|
+
cleared++;
|
|
15525
|
+
continue;
|
|
15526
|
+
}
|
|
15527
|
+
if (now - receivedAtMs > STALE_MARKER_MS) {
|
|
15453
15528
|
process.stderr.write(
|
|
15454
|
-
`telegram-channel(${AGENT_CODE_NAME}): rearm skipping stale
|
|
15529
|
+
`telegram-channel(${AGENT_CODE_NAME}): rearm skipping stale marker chat=${redactId(chat_id)} message=${redactId(message_id)}
|
|
15455
15530
|
`
|
|
15456
15531
|
);
|
|
15457
15532
|
try {
|
|
@@ -15461,18 +15536,25 @@ function rearmPendingTimersFromDisk() {
|
|
|
15461
15536
|
cleared++;
|
|
15462
15537
|
continue;
|
|
15463
15538
|
}
|
|
15464
|
-
|
|
15539
|
+
const reminderCount = marker.reminder_count ?? 0;
|
|
15540
|
+
if (reminderCount >= MAX_REMINDERS) {
|
|
15541
|
+
exhausted++;
|
|
15542
|
+
continue;
|
|
15543
|
+
}
|
|
15544
|
+
const nextFireAt = nextReminderFireAtMs(receivedAtMs, reminderCount);
|
|
15545
|
+
const remainingMs = nextFireAt - now;
|
|
15546
|
+
if (remainingMs <= 0) {
|
|
15465
15547
|
pendingMessages.delete(`${chat_id}:${message_id}`);
|
|
15466
15548
|
fireTelegramResponseTimeout(chat_id, message_id);
|
|
15467
15549
|
firedNow++;
|
|
15468
15550
|
} else {
|
|
15469
|
-
armTelegramPendingTimer(chat_id, message_id, chat_type ?? "private",
|
|
15551
|
+
armTelegramPendingTimer(chat_id, message_id, chat_type ?? "private", remainingMs);
|
|
15470
15552
|
armed++;
|
|
15471
15553
|
}
|
|
15472
15554
|
}
|
|
15473
|
-
if (armed > 0 || firedNow > 0 || cleared > 0) {
|
|
15555
|
+
if (armed > 0 || firedNow > 0 || cleared > 0 || exhausted > 0) {
|
|
15474
15556
|
process.stderr.write(
|
|
15475
|
-
`telegram-channel(${AGENT_CODE_NAME}): rearm summary armed=${armed} fired_now=${firedNow} stale_cleared=${cleared}
|
|
15557
|
+
`telegram-channel(${AGENT_CODE_NAME}): rearm summary armed=${armed} fired_now=${firedNow} stale_cleared=${cleared} reminders_exhausted=${exhausted}
|
|
15476
15558
|
`
|
|
15477
15559
|
);
|
|
15478
15560
|
}
|
|
@@ -15511,6 +15593,10 @@ function clearPendingMessage(chatId, messageId) {
|
|
|
15511
15593
|
return;
|
|
15512
15594
|
}
|
|
15513
15595
|
}
|
|
15596
|
+
function noteThreadActivity(chatId, messageId) {
|
|
15597
|
+
if (!chatId) return;
|
|
15598
|
+
clearPendingMessage(chatId, messageId);
|
|
15599
|
+
}
|
|
15514
15600
|
var mcp = new Server(
|
|
15515
15601
|
{ name: "telegram", version: "0.1.0" },
|
|
15516
15602
|
{
|
|
@@ -15729,6 +15815,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
15729
15815
|
isError: true
|
|
15730
15816
|
};
|
|
15731
15817
|
}
|
|
15818
|
+
noteThreadActivity(chat_id, message_id);
|
|
15732
15819
|
try {
|
|
15733
15820
|
const data = await telegramApiCall(
|
|
15734
15821
|
"setMessageReaction",
|