@integrity-labs/agt-cli 0.28.79 → 0.28.81
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-N7FYQX2R.js → chunk-DA3DAU5V.js} +2 -2
- package/dist/lib/manager-worker.js +8 -5
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/telegram-channel.js +246 -8
- package/package.json +1 -1
- /package/dist/{chunk-N7FYQX2R.js.map → chunk-DA3DAU5V.js.map} +0 -0
|
@@ -16232,6 +16232,12 @@ function markerArrivalMs(fullPath) {
|
|
|
16232
16232
|
}
|
|
16233
16233
|
}
|
|
16234
16234
|
function clearAllTelegramPendingMarkersForChat(pendingDir, chatId, clearMarkerFile, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
16235
|
+
return applyToChatMarkers(pendingDir, chatId, clearMarkerFile, cutoffMs);
|
|
16236
|
+
}
|
|
16237
|
+
function markSeenAllTelegramPendingMarkersForChat(pendingDir, chatId, markSeen, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
16238
|
+
return applyToChatMarkers(pendingDir, chatId, markSeen, cutoffMs);
|
|
16239
|
+
}
|
|
16240
|
+
function applyToChatMarkers(pendingDir, chatId, op, cutoffMs) {
|
|
16235
16241
|
const prefix = `${chatId.replace(/[^A-Za-z0-9_-]/g, "_")}__`;
|
|
16236
16242
|
const bounded = Number.isFinite(cutoffMs);
|
|
16237
16243
|
let filenames;
|
|
@@ -16240,16 +16246,69 @@ function clearAllTelegramPendingMarkersForChat(pendingDir, chatId, clearMarkerFi
|
|
|
16240
16246
|
} catch {
|
|
16241
16247
|
return 0;
|
|
16242
16248
|
}
|
|
16243
|
-
let
|
|
16249
|
+
let applied = 0;
|
|
16244
16250
|
for (const filename of filenames) {
|
|
16245
16251
|
if (!filename.startsWith(prefix)) continue;
|
|
16246
16252
|
if (!filename.endsWith(".json")) continue;
|
|
16247
16253
|
const fullPath = join4(pendingDir, filename);
|
|
16248
16254
|
if (bounded && markerArrivalMs(fullPath) > cutoffMs) continue;
|
|
16249
|
-
|
|
16250
|
-
|
|
16255
|
+
op(fullPath);
|
|
16256
|
+
applied++;
|
|
16257
|
+
}
|
|
16258
|
+
return applied;
|
|
16259
|
+
}
|
|
16260
|
+
|
|
16261
|
+
// src/channel-progress.ts
|
|
16262
|
+
function channelLiveProgressEnabled() {
|
|
16263
|
+
return resolveHostBooleanFlag({
|
|
16264
|
+
key: "channel-live-progress",
|
|
16265
|
+
envVar: "AGT_CHANNEL_PROGRESS_ENABLED",
|
|
16266
|
+
defaultValue: false
|
|
16267
|
+
});
|
|
16268
|
+
}
|
|
16269
|
+
function decideProgressAction(input) {
|
|
16270
|
+
const { now, heartbeat, target, tracked, freshnessMs, minPendingMs } = input;
|
|
16271
|
+
const fresh = heartbeat != null && now - heartbeat.updatedAtMs <= freshnessMs;
|
|
16272
|
+
const active = target != null && fresh;
|
|
16273
|
+
if (!active) {
|
|
16274
|
+
if (tracked) {
|
|
16275
|
+
return { type: "delete", channel: tracked.channel, threadTs: tracked.threadTs, ts: tracked.ts };
|
|
16276
|
+
}
|
|
16277
|
+
return { type: "none" };
|
|
16278
|
+
}
|
|
16279
|
+
const t = target;
|
|
16280
|
+
const hb = heartbeat;
|
|
16281
|
+
if (tracked && (tracked.threadTs !== t.threadTs || tracked.channel !== t.channel)) {
|
|
16282
|
+
return { type: "delete", channel: tracked.channel, threadTs: tracked.threadTs, ts: tracked.ts };
|
|
16283
|
+
}
|
|
16284
|
+
if (!tracked) {
|
|
16285
|
+
if (now - t.receivedAtMs < minPendingMs) return { type: "none" };
|
|
16286
|
+
return { type: "post", channel: t.channel, threadTs: t.threadTs, step: hb.step };
|
|
16287
|
+
}
|
|
16288
|
+
if (hb.step !== tracked.lastStep) {
|
|
16289
|
+
return { type: "update", channel: t.channel, threadTs: t.threadTs, ts: tracked.ts, step: hb.step };
|
|
16290
|
+
}
|
|
16291
|
+
return { type: "none" };
|
|
16292
|
+
}
|
|
16293
|
+
function progressHeartbeatFreshMs() {
|
|
16294
|
+
const raw = parseInt(process.env.AGT_CHANNEL_PROGRESS_FRESH_MS ?? "", 10);
|
|
16295
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 45e3;
|
|
16296
|
+
}
|
|
16297
|
+
function progressMinPendingMs() {
|
|
16298
|
+
const raw = parseInt(process.env.AGT_CHANNEL_PROGRESS_MIN_PENDING_MS ?? "", 10);
|
|
16299
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 12e3;
|
|
16300
|
+
}
|
|
16301
|
+
function parseProgressHeartbeat(raw) {
|
|
16302
|
+
if (!raw) return null;
|
|
16303
|
+
try {
|
|
16304
|
+
const obj = JSON.parse(raw);
|
|
16305
|
+
const step = typeof obj.step === "string" ? obj.step.trim() : "";
|
|
16306
|
+
const updatedAtMs = typeof obj.updated_at_ms === "number" ? obj.updated_at_ms : NaN;
|
|
16307
|
+
if (!step || !Number.isFinite(updatedAtMs)) return null;
|
|
16308
|
+
return { step, updatedAtMs };
|
|
16309
|
+
} catch {
|
|
16310
|
+
return null;
|
|
16251
16311
|
}
|
|
16252
|
-
return cleared;
|
|
16253
16312
|
}
|
|
16254
16313
|
|
|
16255
16314
|
// src/mcp-spawn-lock.ts
|
|
@@ -17666,6 +17725,33 @@ function clearTelegramMarkerFileWithHeal(fullPath) {
|
|
|
17666
17725
|
} catch {
|
|
17667
17726
|
}
|
|
17668
17727
|
}
|
|
17728
|
+
function markTelegramMarkerSeenInPlace(fullPath) {
|
|
17729
|
+
let marker;
|
|
17730
|
+
try {
|
|
17731
|
+
marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
|
|
17732
|
+
} catch {
|
|
17733
|
+
return;
|
|
17734
|
+
}
|
|
17735
|
+
if (marker.seen_at) return;
|
|
17736
|
+
marker.seen_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17737
|
+
if (marker.undeliverable) delete marker.undeliverable;
|
|
17738
|
+
rewriteTelegramMarkerInPlace(fullPath, marker);
|
|
17739
|
+
}
|
|
17740
|
+
function markTelegramMarkerSeenWithHeal(fullPath) {
|
|
17741
|
+
let marker = null;
|
|
17742
|
+
try {
|
|
17743
|
+
marker = JSON.parse(readFileSync8(fullPath, "utf-8"));
|
|
17744
|
+
} catch {
|
|
17745
|
+
return;
|
|
17746
|
+
}
|
|
17747
|
+
if (decideRecoveryHeal({
|
|
17748
|
+
wasUndeliverable: marker.undeliverable === true,
|
|
17749
|
+
hasTarget: Boolean(marker.chat_id)
|
|
17750
|
+
}) === "heal") {
|
|
17751
|
+
notifyBackOnline(marker.chat_id);
|
|
17752
|
+
}
|
|
17753
|
+
markTelegramMarkerSeenInPlace(fullPath);
|
|
17754
|
+
}
|
|
17669
17755
|
function readPendingInboundMarker(chatId, messageId) {
|
|
17670
17756
|
const path = pendingInboundPath(chatId, messageId);
|
|
17671
17757
|
if (!path || !existsSync5(path)) return null;
|
|
@@ -17911,6 +17997,143 @@ var orphanSweepTimer = setInterval(() => {
|
|
|
17911
17997
|
checkWatchdogGiveUpNotice();
|
|
17912
17998
|
}, orphanSweepIntervalMs());
|
|
17913
17999
|
orphanSweepTimer.unref?.();
|
|
18000
|
+
var TELEGRAM_PROGRESS_HEARTBEAT_PATH = AGENT_DIR ? join7(AGENT_DIR, "channel-progress-heartbeat.json") : null;
|
|
18001
|
+
var telegramTrackedProgress = null;
|
|
18002
|
+
var telegramProgressTickRunning = false;
|
|
18003
|
+
function readTelegramProgressHeartbeat() {
|
|
18004
|
+
if (!TELEGRAM_PROGRESS_HEARTBEAT_PATH || !existsSync5(TELEGRAM_PROGRESS_HEARTBEAT_PATH)) return null;
|
|
18005
|
+
try {
|
|
18006
|
+
return parseProgressHeartbeat(readFileSync8(TELEGRAM_PROGRESS_HEARTBEAT_PATH, "utf-8"));
|
|
18007
|
+
} catch {
|
|
18008
|
+
return null;
|
|
18009
|
+
}
|
|
18010
|
+
}
|
|
18011
|
+
function findTelegramProgressTarget() {
|
|
18012
|
+
if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return null;
|
|
18013
|
+
let best = null;
|
|
18014
|
+
let bestMs = Infinity;
|
|
18015
|
+
try {
|
|
18016
|
+
for (const name of readdirSync3(PENDING_INBOUND_DIR)) {
|
|
18017
|
+
if (!name.endsWith(".json")) continue;
|
|
18018
|
+
let m;
|
|
18019
|
+
try {
|
|
18020
|
+
m = JSON.parse(readFileSync8(join7(PENDING_INBOUND_DIR, name), "utf-8"));
|
|
18021
|
+
} catch {
|
|
18022
|
+
continue;
|
|
18023
|
+
}
|
|
18024
|
+
if (!m.chat_id) continue;
|
|
18025
|
+
if (m.undeliverable === true) continue;
|
|
18026
|
+
const ms = Date.parse(m.received_at ?? "");
|
|
18027
|
+
if (!Number.isFinite(ms)) continue;
|
|
18028
|
+
if (ms < bestMs) {
|
|
18029
|
+
bestMs = ms;
|
|
18030
|
+
best = { channel: m.chat_id, threadTs: m.chat_id, receivedAtMs: ms };
|
|
18031
|
+
}
|
|
18032
|
+
}
|
|
18033
|
+
} catch {
|
|
18034
|
+
return null;
|
|
18035
|
+
}
|
|
18036
|
+
return best;
|
|
18037
|
+
}
|
|
18038
|
+
var TELEGRAM_PROGRESS_PREFIX = "\u23F3";
|
|
18039
|
+
function telegramProgressText(step) {
|
|
18040
|
+
return `${TELEGRAM_PROGRESS_PREFIX} Working\u2026 \xB7 ${step}`;
|
|
18041
|
+
}
|
|
18042
|
+
async function postTelegramProgress(chatId, step) {
|
|
18043
|
+
if (!BOT_TOKEN) return null;
|
|
18044
|
+
try {
|
|
18045
|
+
const data = await telegramApiCall(
|
|
18046
|
+
"sendMessage",
|
|
18047
|
+
{ chat_id: chatId, text: telegramProgressText(step), disable_notification: true },
|
|
18048
|
+
8e3
|
|
18049
|
+
);
|
|
18050
|
+
return data.ok && data.result?.message_id != null ? String(data.result.message_id) : null;
|
|
18051
|
+
} catch {
|
|
18052
|
+
return null;
|
|
18053
|
+
}
|
|
18054
|
+
}
|
|
18055
|
+
async function updateTelegramProgress(chatId, messageId, step) {
|
|
18056
|
+
if (!BOT_TOKEN) return false;
|
|
18057
|
+
try {
|
|
18058
|
+
const data = await telegramApiCall(
|
|
18059
|
+
"editMessageText",
|
|
18060
|
+
{ chat_id: chatId, message_id: Number(messageId), text: telegramProgressText(step) },
|
|
18061
|
+
8e3
|
|
18062
|
+
);
|
|
18063
|
+
return data.ok === true || /not modified/i.test(data.description ?? "");
|
|
18064
|
+
} catch {
|
|
18065
|
+
return false;
|
|
18066
|
+
}
|
|
18067
|
+
}
|
|
18068
|
+
async function deleteTelegramProgress(chatId, messageId) {
|
|
18069
|
+
if (!BOT_TOKEN) return false;
|
|
18070
|
+
try {
|
|
18071
|
+
const data = await telegramApiCall(
|
|
18072
|
+
"deleteMessage",
|
|
18073
|
+
{ chat_id: chatId, message_id: Number(messageId) },
|
|
18074
|
+
8e3
|
|
18075
|
+
);
|
|
18076
|
+
return data.ok === true || /message to delete not found|message can't be deleted/i.test(data.description ?? "");
|
|
18077
|
+
} catch {
|
|
18078
|
+
return false;
|
|
18079
|
+
}
|
|
18080
|
+
}
|
|
18081
|
+
async function telegramProgressTick() {
|
|
18082
|
+
if (telegramProgressTickRunning) return;
|
|
18083
|
+
telegramProgressTickRunning = true;
|
|
18084
|
+
try {
|
|
18085
|
+
if (!channelLiveProgressEnabled()) {
|
|
18086
|
+
if (telegramTrackedProgress) {
|
|
18087
|
+
if (await deleteTelegramProgress(telegramTrackedProgress.channel, telegramTrackedProgress.ts)) {
|
|
18088
|
+
telegramTrackedProgress = null;
|
|
18089
|
+
}
|
|
18090
|
+
}
|
|
18091
|
+
return;
|
|
18092
|
+
}
|
|
18093
|
+
const action = decideProgressAction({
|
|
18094
|
+
now: Date.now(),
|
|
18095
|
+
heartbeat: readTelegramProgressHeartbeat(),
|
|
18096
|
+
target: findTelegramProgressTarget(),
|
|
18097
|
+
tracked: telegramTrackedProgress,
|
|
18098
|
+
freshnessMs: progressHeartbeatFreshMs(),
|
|
18099
|
+
minPendingMs: progressMinPendingMs()
|
|
18100
|
+
});
|
|
18101
|
+
switch (action.type) {
|
|
18102
|
+
case "post": {
|
|
18103
|
+
const id = await postTelegramProgress(action.channel, action.step);
|
|
18104
|
+
if (id) telegramTrackedProgress = { channel: action.channel, threadTs: action.threadTs, ts: id, lastStep: action.step };
|
|
18105
|
+
break;
|
|
18106
|
+
}
|
|
18107
|
+
case "update": {
|
|
18108
|
+
if (await updateTelegramProgress(action.channel, action.ts, action.step)) {
|
|
18109
|
+
if (telegramTrackedProgress) telegramTrackedProgress.lastStep = action.step;
|
|
18110
|
+
}
|
|
18111
|
+
break;
|
|
18112
|
+
}
|
|
18113
|
+
case "delete": {
|
|
18114
|
+
if (await deleteTelegramProgress(action.channel, action.ts)) {
|
|
18115
|
+
telegramTrackedProgress = null;
|
|
18116
|
+
}
|
|
18117
|
+
break;
|
|
18118
|
+
}
|
|
18119
|
+
case "none":
|
|
18120
|
+
break;
|
|
18121
|
+
}
|
|
18122
|
+
} catch {
|
|
18123
|
+
} finally {
|
|
18124
|
+
telegramProgressTickRunning = false;
|
|
18125
|
+
}
|
|
18126
|
+
}
|
|
18127
|
+
function telegramProgressPollMs() {
|
|
18128
|
+
const raw = parseInt(process.env.AGT_CHANNEL_PROGRESS_POLL_MS ?? "", 10);
|
|
18129
|
+
return Number.isFinite(raw) && raw >= 2e3 ? raw : 8e3;
|
|
18130
|
+
}
|
|
18131
|
+
if (BOT_TOKEN && PENDING_INBOUND_DIR) {
|
|
18132
|
+
const telegramProgressTimer = setInterval(() => {
|
|
18133
|
+
void telegramProgressTick();
|
|
18134
|
+
}, telegramProgressPollMs());
|
|
18135
|
+
telegramProgressTimer.unref?.();
|
|
18136
|
+
}
|
|
17914
18137
|
var lastGiveUpHandledAtMs = null;
|
|
17915
18138
|
function listPendingInboundChatIds() {
|
|
17916
18139
|
if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return [];
|
|
@@ -17922,6 +18145,7 @@ function listPendingInboundChatIds() {
|
|
|
17922
18145
|
const marker = JSON.parse(
|
|
17923
18146
|
readFileSync8(join7(PENDING_INBOUND_DIR, name), "utf8")
|
|
17924
18147
|
);
|
|
18148
|
+
if (typeof marker.seen_at === "string" && marker.seen_at) continue;
|
|
17925
18149
|
if (typeof marker.chat_id === "string" && marker.chat_id) chats.add(marker.chat_id);
|
|
17926
18150
|
} catch {
|
|
17927
18151
|
}
|
|
@@ -18044,9 +18268,18 @@ function clearPendingMessage(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
|
18044
18268
|
cutoffMs
|
|
18045
18269
|
);
|
|
18046
18270
|
}
|
|
18271
|
+
function markSeenPendingMessage(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
18272
|
+
if (!PENDING_INBOUND_DIR || !existsSync5(PENDING_INBOUND_DIR)) return;
|
|
18273
|
+
markSeenAllTelegramPendingMarkersForChat(
|
|
18274
|
+
PENDING_INBOUND_DIR,
|
|
18275
|
+
chatId,
|
|
18276
|
+
markTelegramMarkerSeenWithHeal,
|
|
18277
|
+
cutoffMs
|
|
18278
|
+
);
|
|
18279
|
+
}
|
|
18047
18280
|
function noteThreadActivity(chatId, cutoffMs = Number.POSITIVE_INFINITY) {
|
|
18048
18281
|
if (!chatId) return;
|
|
18049
|
-
|
|
18282
|
+
markSeenPendingMessage(chatId, cutoffMs);
|
|
18050
18283
|
}
|
|
18051
18284
|
var SKIP_REACTION_ON = channelSkipReactionEnabled() && TELEGRAM_SKIP_REACTION.length > 0;
|
|
18052
18285
|
var mcp = new Server(
|
|
@@ -18062,7 +18295,7 @@ var mcp = new Server(
|
|
|
18062
18295
|
instructions: [
|
|
18063
18296
|
// Highest-priority lines first — Claude Code truncates this string at
|
|
18064
18297
|
// 2048 chars, so anything appended late silently disappears.
|
|
18065
|
-
"CRITICAL: every response to a Telegram <channel> tag MUST go through telegram.reply with the chat_id from the tag.
|
|
18298
|
+
"CRITICAL: every response to a Telegram <channel> tag MUST go through telegram.reply with the chat_id from the tag \u2014 typed text never reaches the user. Slow work: interim ack (telegram.reply interim:true), then your FINAL answer as a separate telegram.reply (interim omitted). An ack is not the answer.",
|
|
18066
18299
|
'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.',
|
|
18067
18300
|
"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.",
|
|
18068
18301
|
'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.',
|
|
@@ -18125,6 +18358,10 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
18125
18358
|
reply_to_message_id: {
|
|
18126
18359
|
type: "string",
|
|
18127
18360
|
description: "Optional Telegram message_id to quote-reply to"
|
|
18361
|
+
},
|
|
18362
|
+
interim: {
|
|
18363
|
+
type: "boolean",
|
|
18364
|
+
description: 'Set true ONLY when this is an interim acknowledgement (e.g. "On it \u2014 checking now\u2026") and the real answer is still coming. The system keeps tracking this chat as awaiting your final reply. Your eventual substantive answer MUST be a separate telegram.reply with interim omitted/false \u2014 that is what marks the request complete. Omit (default false) for any reply that fully answers the user.'
|
|
18128
18365
|
}
|
|
18129
18366
|
},
|
|
18130
18367
|
required: ["chat_id", "text"]
|
|
@@ -18196,7 +18433,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
18196
18433
|
return handleChannelRequestInput(args ?? {});
|
|
18197
18434
|
}
|
|
18198
18435
|
if (name === "telegram.reply" || name === "telegram.send_message") {
|
|
18199
|
-
const { chat_id, text, reply_to_message_id } = args;
|
|
18436
|
+
const { chat_id, text, reply_to_message_id, interim } = args;
|
|
18200
18437
|
if (ALLOWED_CHATS.size > 0 && !ALLOWED_CHATS.has(chat_id)) {
|
|
18201
18438
|
return {
|
|
18202
18439
|
content: [{ type: "text", text: `Chat ${chat_id} is not in TELEGRAM_ALLOWED_CHATS` }],
|
|
@@ -18322,7 +18559,8 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
18322
18559
|
}
|
|
18323
18560
|
recordReply(chat_id, "", tgThrottleNow, tgThrottleCfg);
|
|
18324
18561
|
if (name === "telegram.reply") {
|
|
18325
|
-
|
|
18562
|
+
if (interim) markSeenPendingMessage(chat_id, drainCutoffMs);
|
|
18563
|
+
else clearPendingMessage(chat_id, drainCutoffMs);
|
|
18326
18564
|
}
|
|
18327
18565
|
return { content: [{ type: "text", text: "sent" }] };
|
|
18328
18566
|
} catch (err) {
|
package/package.json
CHANGED
|
File without changes
|