metheus-governance-mcp-cli 0.2.57 → 0.2.58
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/cli.mjs +264 -3
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -897,6 +897,80 @@ function parseArchivedChatComment(rawBody) {
|
|
|
897
897
|
};
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
+
function joinTextParts(parts, separator = " ") {
|
|
901
|
+
return ensureArray(parts)
|
|
902
|
+
.map((value) => String(value || "").trim())
|
|
903
|
+
.filter(Boolean)
|
|
904
|
+
.join(separator);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function toISOStringFromUnix(rawValue) {
|
|
908
|
+
const numeric = Number(rawValue || 0);
|
|
909
|
+
if (!Number.isFinite(numeric) || numeric <= 0) {
|
|
910
|
+
return new Date().toISOString();
|
|
911
|
+
}
|
|
912
|
+
return new Date(numeric * 1000).toISOString();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function collectTelegramUpdateText(message) {
|
|
916
|
+
return firstNonEmptyString([message?.text, message?.caption]);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function normalizeLocalTelegramUpdate(rawUpdate) {
|
|
920
|
+
const update = safeObject(rawUpdate);
|
|
921
|
+
const message = safeObject(update.message || update.edited_message);
|
|
922
|
+
if (!message || Object.keys(message).length === 0) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
const chat = safeObject(message.chat);
|
|
926
|
+
const from = safeObject(message.from);
|
|
927
|
+
const replyTo = safeObject(message.reply_to_message);
|
|
928
|
+
const text = collectTelegramUpdateText(message);
|
|
929
|
+
if (!text) {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
eventName: update.edited_message ? "telegram.message.updated" : "telegram.message.created",
|
|
934
|
+
updateID: intFromRawAllowZero(update.update_id, 0),
|
|
935
|
+
messageID: intFromRawAllowZero(message.message_id, 0),
|
|
936
|
+
chatID: String(chat.id || "").trim(),
|
|
937
|
+
chatType: String(chat.type || "").trim(),
|
|
938
|
+
chatTitle: firstNonEmptyString([chat.title, chat.username, joinTextParts([chat.first_name, chat.last_name]), chat.id]),
|
|
939
|
+
fromID: String(from.id || "").trim(),
|
|
940
|
+
fromName: firstNonEmptyString([joinTextParts([from.first_name, from.last_name]), from.username, from.id]),
|
|
941
|
+
fromUsername: String(from.username || "").trim(),
|
|
942
|
+
fromIsBot: Boolean(from.is_bot),
|
|
943
|
+
text,
|
|
944
|
+
occurredAt: toISOStringFromUnix(message.edit_date || message.date),
|
|
945
|
+
messageThreadID: String(message.message_thread_id || "").trim(),
|
|
946
|
+
replyToMessageID: intFromRawAllowZero(replyTo.message_id, 0),
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function buildArchivedInboundMessageKey(chatID, messageID) {
|
|
951
|
+
return `${String(chatID || "").trim()}:${intFromRawAllowZero(messageID, 0)}`;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function formatTelegramInboundArchiveComment(normalized) {
|
|
955
|
+
const headerLines = [
|
|
956
|
+
`[Telegram ${normalized.eventName === "telegram.message.updated" ? "edited" : "message"}]`,
|
|
957
|
+
`chat_id: ${normalized.chatID || "<missing>"}`,
|
|
958
|
+
`message_id: ${normalized.messageID || "<missing>"}`,
|
|
959
|
+
`occurred_at: ${normalized.occurredAt || new Date().toISOString()}`,
|
|
960
|
+
`sender: ${normalized.fromName || normalized.fromUsername || normalized.fromID || "unknown"}`,
|
|
961
|
+
];
|
|
962
|
+
if (normalized.fromUsername) {
|
|
963
|
+
headerLines.push(`telegram_username: @${normalized.fromUsername.replace(/^@+/, "")}`);
|
|
964
|
+
}
|
|
965
|
+
if (normalized.messageThreadID) {
|
|
966
|
+
headerLines.push(`telegram_topic_id: ${normalized.messageThreadID}`);
|
|
967
|
+
}
|
|
968
|
+
if (normalized.replyToMessageID > 0) {
|
|
969
|
+
headerLines.push(`reply_to_message_id: ${normalized.replyToMessageID}`);
|
|
970
|
+
}
|
|
971
|
+
return `${headerLines.join("\n")}\n\n${String(normalized.text || "").trim()}`;
|
|
972
|
+
}
|
|
973
|
+
|
|
900
974
|
function formatBotReplyArchiveComment({
|
|
901
975
|
provider,
|
|
902
976
|
bot,
|
|
@@ -1026,6 +1100,65 @@ async function createThreadComment({
|
|
|
1026
1100
|
return safeObject(parseJSONText(responseText));
|
|
1027
1101
|
}
|
|
1028
1102
|
|
|
1103
|
+
async function getTelegramAPIJSON(token, methodName, timeoutSeconds, query = {}) {
|
|
1104
|
+
const requestURL = new URL(`https://api.telegram.org/bot${token}/${methodName}`);
|
|
1105
|
+
Object.entries(safeObject(query)).forEach(([key, value]) => {
|
|
1106
|
+
if (value == null || value === "") return;
|
|
1107
|
+
requestURL.searchParams.set(key, String(value));
|
|
1108
|
+
});
|
|
1109
|
+
const parsed = safeObject(await getJSONWithoutAuth(requestURL.toString(), timeoutSeconds));
|
|
1110
|
+
if (parsed.ok === false) {
|
|
1111
|
+
throw new Error(String(parsed.description || `${methodName} failed`).trim());
|
|
1112
|
+
}
|
|
1113
|
+
return parsed;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async function postTelegramAPIJSON(token, methodName, timeoutSeconds, payload = {}) {
|
|
1117
|
+
const requestURL = `https://api.telegram.org/bot${token}/${methodName}`;
|
|
1118
|
+
const response = await postJSONWithoutAuth(requestURL, timeoutSeconds, payload);
|
|
1119
|
+
const parsed = safeObject(parseJSONText(response.bodyText));
|
|
1120
|
+
if (!(response.statusCode >= 200 && response.statusCode < 300) || parsed.ok === false) {
|
|
1121
|
+
const error = new Error(String(parsed.description || response.bodyText || `${methodName} failed`).trim() || `${methodName} failed`);
|
|
1122
|
+
error.statusCode = response.statusCode;
|
|
1123
|
+
error.responseBody = response.bodyText;
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
1126
|
+
return parsed;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
async function getTelegramWebhookInfo(token, timeoutSeconds) {
|
|
1130
|
+
return safeObject(await getTelegramAPIJSON(token, "getWebhookInfo", timeoutSeconds));
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async function deleteTelegramWebhook(token, timeoutSeconds) {
|
|
1134
|
+
return safeObject(await postTelegramAPIJSON(token, "deleteWebhook", timeoutSeconds, {
|
|
1135
|
+
drop_pending_updates: false,
|
|
1136
|
+
}));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async function getTelegramUpdates(token, timeoutSeconds, offset = 0) {
|
|
1140
|
+
try {
|
|
1141
|
+
return safeObject(await getTelegramAPIJSON(token, "getUpdates", timeoutSeconds, {
|
|
1142
|
+
timeout: 0,
|
|
1143
|
+
offset: offset > 0 ? offset : "",
|
|
1144
|
+
allowed_updates: JSON.stringify(["message", "edited_message"]),
|
|
1145
|
+
}));
|
|
1146
|
+
} catch (err) {
|
|
1147
|
+
const statusCode = Number(err?.statusCode || 0);
|
|
1148
|
+
const errorText = String(err?.responseBody || err?.message || "").toLowerCase();
|
|
1149
|
+
const webhookConflict = statusCode === 409 || errorText.includes("webhook") || errorText.includes("getupdates");
|
|
1150
|
+
if (!webhookConflict) {
|
|
1151
|
+
throw err;
|
|
1152
|
+
}
|
|
1153
|
+
await deleteTelegramWebhook(token, timeoutSeconds);
|
|
1154
|
+
return safeObject(await getTelegramAPIJSON(token, "getUpdates", timeoutSeconds, {
|
|
1155
|
+
timeout: 0,
|
|
1156
|
+
offset: offset > 0 ? offset : "",
|
|
1157
|
+
allowed_updates: JSON.stringify(["message", "edited_message"]),
|
|
1158
|
+
}));
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1029
1162
|
function looksLikeArchiveWorkItemTitle(title, provider) {
|
|
1030
1163
|
const text = String(title || "").trim().toLowerCase();
|
|
1031
1164
|
if (!text) return false;
|
|
@@ -1118,6 +1251,101 @@ async function discoverArchiveThreadForDestination({
|
|
|
1118
1251
|
);
|
|
1119
1252
|
}
|
|
1120
1253
|
|
|
1254
|
+
async function archiveLocalTelegramMessagesForRoute({
|
|
1255
|
+
routeKey,
|
|
1256
|
+
routeState,
|
|
1257
|
+
runtime,
|
|
1258
|
+
destination,
|
|
1259
|
+
archiveThread,
|
|
1260
|
+
}) {
|
|
1261
|
+
const envConfig = loadProviderEnvConfig("telegram");
|
|
1262
|
+
if (!envConfig.ok) {
|
|
1263
|
+
throw new Error(envConfig.error || "TELEGRAM_BOT_TOKEN is not configured");
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const state = safeObject(routeState);
|
|
1267
|
+
let webhookClearedURL = "";
|
|
1268
|
+
if (!boolFromRaw(state.local_telegram_polling_ready, false)) {
|
|
1269
|
+
const webhookInfo = safeObject(await getTelegramWebhookInfo(envConfig.token, runtime.timeoutSeconds).catch(() => ({})));
|
|
1270
|
+
const currentWebhookURL = String(safeObject(webhookInfo.result).url || "").trim();
|
|
1271
|
+
if (currentWebhookURL) {
|
|
1272
|
+
await deleteTelegramWebhook(envConfig.token, runtime.timeoutSeconds);
|
|
1273
|
+
webhookClearedURL = currentWebhookURL;
|
|
1274
|
+
}
|
|
1275
|
+
saveRunnerRouteState(routeKey, {
|
|
1276
|
+
local_receive_mode: "telegram_get_updates",
|
|
1277
|
+
local_telegram_polling_ready: true,
|
|
1278
|
+
local_telegram_webhook_cleared_url: webhookClearedURL,
|
|
1279
|
+
local_telegram_webhook_cleared_at: webhookClearedURL ? new Date().toISOString() : state.local_telegram_webhook_cleared_at,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const lastUpdateID = intFromRawAllowZero(state.last_provider_update_id ?? state.last_telegram_update_id, 0);
|
|
1284
|
+
const updatesEnvelope = await getTelegramUpdates(envConfig.token, runtime.timeoutSeconds, lastUpdateID > 0 ? lastUpdateID + 1 : 0);
|
|
1285
|
+
const updates = ensureArray(updatesEnvelope.result)
|
|
1286
|
+
.map(normalizeLocalTelegramUpdate)
|
|
1287
|
+
.filter(Boolean);
|
|
1288
|
+
const highestUpdateID = updates.reduce((max, item) => Math.max(max, intFromRawAllowZero(item.updateID, 0)), lastUpdateID);
|
|
1289
|
+
|
|
1290
|
+
saveRunnerRouteState(routeKey, {
|
|
1291
|
+
local_receive_mode: "telegram_get_updates",
|
|
1292
|
+
local_telegram_polling_ready: true,
|
|
1293
|
+
last_provider_update_id: highestUpdateID,
|
|
1294
|
+
last_local_poll_at: new Date().toISOString(),
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
if (!updates.length) {
|
|
1298
|
+
return {
|
|
1299
|
+
importedCommentIDs: [],
|
|
1300
|
+
importedCount: 0,
|
|
1301
|
+
lastUpdateID: highestUpdateID,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
const existingComments = await listThreadComments({
|
|
1306
|
+
siteBaseURL: runtime.baseURL,
|
|
1307
|
+
threadID: archiveThread.threadID,
|
|
1308
|
+
token: runtime.token,
|
|
1309
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
1310
|
+
limit: 200,
|
|
1311
|
+
actorUserID: runtime.actor.user_id,
|
|
1312
|
+
});
|
|
1313
|
+
const existingKeys = new Set(
|
|
1314
|
+
existingComments
|
|
1315
|
+
.map(normalizeArchiveCommentRecord)
|
|
1316
|
+
.map((record) => record.parsedArchive)
|
|
1317
|
+
.filter((parsed) => parsed && isInboundArchiveKind(parsed.kind) && parsed.chatID)
|
|
1318
|
+
.map((parsed) => buildArchivedInboundMessageKey(parsed.chatID, parsed.messageID)),
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1321
|
+
const importedCommentIDs = [];
|
|
1322
|
+
for (const update of updates) {
|
|
1323
|
+
if (String(update.chatID || "").trim() !== String(destination.chatID || "").trim()) continue;
|
|
1324
|
+
if (update.fromIsBot) continue;
|
|
1325
|
+
if (!String(update.text || "").trim()) continue;
|
|
1326
|
+
const dedupeKey = buildArchivedInboundMessageKey(update.chatID, update.messageID);
|
|
1327
|
+
if (existingKeys.has(dedupeKey)) continue;
|
|
1328
|
+
const createdComment = await createThreadComment({
|
|
1329
|
+
siteBaseURL: runtime.baseURL,
|
|
1330
|
+
token: runtime.token,
|
|
1331
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
1332
|
+
threadID: archiveThread.threadID,
|
|
1333
|
+
actorUserID: runtime.actor.user_id,
|
|
1334
|
+
body: formatTelegramInboundArchiveComment(update),
|
|
1335
|
+
});
|
|
1336
|
+
if (String(createdComment.id || "").trim()) {
|
|
1337
|
+
importedCommentIDs.push(String(createdComment.id || "").trim());
|
|
1338
|
+
}
|
|
1339
|
+
existingKeys.add(dedupeKey);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
return {
|
|
1343
|
+
importedCommentIDs,
|
|
1344
|
+
importedCount: importedCommentIDs.length,
|
|
1345
|
+
lastUpdateID: highestUpdateID,
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1121
1349
|
function buildRunnerShellInvocation(command) {
|
|
1122
1350
|
const text = String(command || "").trim();
|
|
1123
1351
|
if (!text) {
|
|
@@ -1244,9 +1472,11 @@ function buildRunnerRouteStateFromComment(record, patch = {}) {
|
|
|
1244
1472
|
|
|
1245
1473
|
function saveRunnerRouteState(routeKey, routeState) {
|
|
1246
1474
|
const current = loadBotRunnerState();
|
|
1475
|
+
const previous = safeObject(current.routes[routeKey]);
|
|
1247
1476
|
const nextRoutes = {
|
|
1248
1477
|
...safeObject(current.routes),
|
|
1249
1478
|
[routeKey]: {
|
|
1479
|
+
...previous,
|
|
1250
1480
|
...safeObject(routeState),
|
|
1251
1481
|
updated_at: new Date().toISOString(),
|
|
1252
1482
|
},
|
|
@@ -1462,6 +1692,20 @@ async function processRunnerRouteOnce(route, runtime, mode) {
|
|
|
1462
1692
|
archiveThreadID: normalizedRoute.archiveThreadID,
|
|
1463
1693
|
archiveWorkItemID: normalizedRoute.archiveWorkItemID,
|
|
1464
1694
|
});
|
|
1695
|
+
const importOutcome = await archiveLocalTelegramMessagesForRoute({
|
|
1696
|
+
routeKey,
|
|
1697
|
+
routeState: currentState,
|
|
1698
|
+
runtime,
|
|
1699
|
+
destination,
|
|
1700
|
+
archiveThread,
|
|
1701
|
+
});
|
|
1702
|
+
saveRunnerRouteState(routeKey, {
|
|
1703
|
+
local_receive_mode: "telegram_get_updates",
|
|
1704
|
+
local_telegram_polling_ready: true,
|
|
1705
|
+
last_provider_update_id: intFromRawAllowZero(importOutcome.lastUpdateID, 0),
|
|
1706
|
+
last_local_poll_at: new Date().toISOString(),
|
|
1707
|
+
});
|
|
1708
|
+
const refreshedState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
1465
1709
|
const comments = await listThreadComments({
|
|
1466
1710
|
siteBaseURL: runtime.baseURL,
|
|
1467
1711
|
threadID: archiveThread.threadID,
|
|
@@ -1470,7 +1714,22 @@ async function processRunnerRouteOnce(route, runtime, mode) {
|
|
|
1470
1714
|
actorUserID: runtime.actor.user_id,
|
|
1471
1715
|
limit: Math.max(50, normalizedRoute.contextComments * 6),
|
|
1472
1716
|
});
|
|
1473
|
-
const
|
|
1717
|
+
const orderedComments = ensureArray(comments)
|
|
1718
|
+
.map(normalizeArchiveCommentRecord)
|
|
1719
|
+
.filter((record) => record.id && record.parsedArchive)
|
|
1720
|
+
.sort(compareArchiveCommentRecords);
|
|
1721
|
+
const inboundComments = orderedComments.filter((record) => isInboundArchiveKind(record.parsedArchive.kind));
|
|
1722
|
+
const importedRecords = ensureArray(importOutcome.importedCommentIDs)
|
|
1723
|
+
.map((commentID) => orderedComments.find((record) => record.id === commentID))
|
|
1724
|
+
.filter(Boolean);
|
|
1725
|
+
const pending = importedRecords.length > 0
|
|
1726
|
+
? {
|
|
1727
|
+
ordered: orderedComments,
|
|
1728
|
+
latest: inboundComments.length ? inboundComments[inboundComments.length - 1] : null,
|
|
1729
|
+
shouldPrime: false,
|
|
1730
|
+
pending: importedRecords,
|
|
1731
|
+
}
|
|
1732
|
+
: selectPendingArchiveComments(comments, refreshedState, mode);
|
|
1474
1733
|
if (pending.shouldPrime && pending.latest) {
|
|
1475
1734
|
saveRunnerRouteState(routeKey, buildRunnerRouteStateFromComment(pending.latest, { primed: true }));
|
|
1476
1735
|
return {
|
|
@@ -1487,7 +1746,9 @@ async function processRunnerRouteOnce(route, runtime, mode) {
|
|
|
1487
1746
|
route_key: routeKey,
|
|
1488
1747
|
route_name: normalizedRoute.name,
|
|
1489
1748
|
outcome: "idle",
|
|
1490
|
-
detail:
|
|
1749
|
+
detail: importOutcome.importedCount > 0
|
|
1750
|
+
? "local telegram updates imported but no pending archive comments were selected"
|
|
1751
|
+
: "no new local telegram messages or archived inbound messages",
|
|
1491
1752
|
thread_id: archiveThread.threadID,
|
|
1492
1753
|
};
|
|
1493
1754
|
}
|
|
@@ -1596,7 +1857,6 @@ async function runRunnerOnce(flags) {
|
|
|
1596
1857
|
}
|
|
1597
1858
|
|
|
1598
1859
|
async function runRunnerStart(flags) {
|
|
1599
|
-
const runtime = await resolveRunnerContext(flags);
|
|
1600
1860
|
const jsonMode = boolFromRaw(flags.json, false);
|
|
1601
1861
|
const routes = resolveRunnerRoutes(flags, "start");
|
|
1602
1862
|
const schedules = new Map();
|
|
@@ -1621,6 +1881,7 @@ async function runRunnerStart(flags) {
|
|
|
1621
1881
|
continue;
|
|
1622
1882
|
}
|
|
1623
1883
|
try {
|
|
1884
|
+
const runtime = await resolveRunnerContext(flags);
|
|
1624
1885
|
const result = await processRunnerRouteOnce(normalizedRoute, runtime, "start");
|
|
1625
1886
|
printRunnerResult("start", result, jsonMode);
|
|
1626
1887
|
} catch (err) {
|