metheus-governance-mcp-cli 0.2.273 → 0.2.274
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 +40 -31
- package/lib/runner-orchestration-entrypoints.mjs +109 -1
- package/lib/runner-runtime.mjs +15 -0
- package/lib/selftest-runner-scenarios.mjs +67 -4
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -3333,37 +3333,46 @@ function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
|
3333
3333
|
return normalized;
|
|
3334
3334
|
}
|
|
3335
3335
|
|
|
3336
|
-
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
3337
|
-
const commentID = String(commentIDRaw || "").trim();
|
|
3338
|
-
if (!commentID) return "";
|
|
3339
|
-
const routeKey = String(routeKeyRaw || "").trim();
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
const
|
|
3351
|
-
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID,
|
|
3352
|
-
const directEntry = safeObject(consumedComments[ledgerKey]);
|
|
3353
|
-
if (Object.keys(directEntry).length) {
|
|
3354
|
-
return directEntry;
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3336
|
+
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
3337
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
3338
|
+
if (!commentID) return "";
|
|
3339
|
+
const routeKey = String(routeKeyRaw || "").trim();
|
|
3340
|
+
if (routeKey) {
|
|
3341
|
+
return `${commentID}::${routeKey}`;
|
|
3342
|
+
}
|
|
3343
|
+
return commentID;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function findRunnerConsumedCommentEntry(rawConsumed, commentIDRaw, { routeKey = "", commentKind = "" } = {}) {
|
|
3347
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
3348
|
+
if (!commentID) return {};
|
|
3349
|
+
const consumedComments = normalizeBotRunnerConsumedComments(rawConsumed);
|
|
3350
|
+
const normalizedRouteKey = String(routeKey || "").trim();
|
|
3351
|
+
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID, normalizedRouteKey, commentKind);
|
|
3352
|
+
const directEntry = safeObject(consumedComments[ledgerKey]);
|
|
3353
|
+
if (Object.keys(directEntry).length) {
|
|
3354
|
+
return directEntry;
|
|
3355
|
+
}
|
|
3356
|
+
if (normalizedRouteKey) {
|
|
3357
|
+
const routeEntry = Object.values(consumedComments).find((entryRaw) => {
|
|
3358
|
+
const entry = safeObject(entryRaw);
|
|
3359
|
+
return String(entry.comment_id || "").trim() === commentID
|
|
3360
|
+
&& String(entry.route_key || "").trim() === normalizedRouteKey;
|
|
3361
|
+
});
|
|
3362
|
+
if (Object.keys(safeObject(routeEntry)).length > 0) {
|
|
3363
|
+
return safeObject(routeEntry);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
const legacyGlobalEntry = safeObject(consumedComments[commentID]);
|
|
3367
|
+
if (!Object.keys(legacyGlobalEntry).length) {
|
|
3368
|
+
return {};
|
|
3369
|
+
}
|
|
3370
|
+
const legacyRouteKey = String(legacyGlobalEntry.route_key || "").trim();
|
|
3371
|
+
if (normalizedRouteKey && legacyRouteKey && legacyRouteKey !== normalizedRouteKey) {
|
|
3372
|
+
return {};
|
|
3373
|
+
}
|
|
3374
|
+
return legacyGlobalEntry;
|
|
3375
|
+
}
|
|
3367
3376
|
|
|
3368
3377
|
function runnerRouteMatchesProjectConversationScope(candidateRouteRaw, normalizedRouteRaw) {
|
|
3369
3378
|
const candidateRoute = normalizeRunnerRoute(candidateRouteRaw);
|
|
@@ -51,6 +51,100 @@ function intFromRawAllowZero(raw, fallback = 0) {
|
|
|
51
51
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function buildRunnerArchiveSourceMessageKey(recordRaw) {
|
|
55
|
+
const parsed = safeObject(safeObject(recordRaw).parsedArchive);
|
|
56
|
+
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
57
|
+
const messageID = intFromRawAllowZero(parsed.messageID || parsed.messageId, 0);
|
|
58
|
+
if (!chatID || !(messageID > 0)) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
return `${chatID}:${messageID}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildRunnerLocalInboundReceiptKey(receiptRaw) {
|
|
65
|
+
const receipt = safeObject(receiptRaw);
|
|
66
|
+
const chatID = String(receipt.chat_id || receipt.chatID || "").trim();
|
|
67
|
+
const messageID = intFromRawAllowZero(receipt.message_id || receipt.messageID, 0);
|
|
68
|
+
if (!chatID || !(messageID > 0)) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
return `${chatID}:${messageID}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildRunnerReceiptReplaySortTime(receiptRaw) {
|
|
75
|
+
const receipt = safeObject(receiptRaw);
|
|
76
|
+
const receiptTime = Date.parse(firstNonEmptyString([
|
|
77
|
+
receipt.occurred_at,
|
|
78
|
+
receipt.occurredAt,
|
|
79
|
+
receipt.received_at,
|
|
80
|
+
receipt.receivedAt,
|
|
81
|
+
]));
|
|
82
|
+
return Number.isFinite(receiptTime) ? receiptTime : 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildRunnerReceiptBackedReplayRecord(recordRaw, receiptRaw, pendingSelectionOptions = {}) {
|
|
86
|
+
const record = safeObject(recordRaw);
|
|
87
|
+
const receipt = safeObject(receiptRaw);
|
|
88
|
+
const replayOccurredAt = firstNonEmptyString([
|
|
89
|
+
receipt.occurred_at,
|
|
90
|
+
receipt.occurredAt,
|
|
91
|
+
receipt.received_at,
|
|
92
|
+
receipt.receivedAt,
|
|
93
|
+
record.sourceOccurredAt,
|
|
94
|
+
safeObject(record.parsedArchive).occurredAt,
|
|
95
|
+
safeObject(record.parsedArchive).occurred_at,
|
|
96
|
+
record.createdAt,
|
|
97
|
+
record.updatedAt,
|
|
98
|
+
]);
|
|
99
|
+
let staleAfterAt = String(record.staleAfterAt || "").trim();
|
|
100
|
+
const maxPendingAgeMs = intFromRawAllowZero(safeObject(pendingSelectionOptions).maxPendingAgeMs, 0);
|
|
101
|
+
const replayOccurredAtMs = Date.parse(replayOccurredAt);
|
|
102
|
+
if (!staleAfterAt && maxPendingAgeMs > 0 && Number.isFinite(replayOccurredAtMs)) {
|
|
103
|
+
staleAfterAt = new Date(replayOccurredAtMs + maxPendingAgeMs).toISOString();
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
...record,
|
|
107
|
+
sourceOccurredAt: replayOccurredAt,
|
|
108
|
+
staleAfterAt,
|
|
109
|
+
replayTriggeredByLocalReceipt: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildRunnerReceiptBackedPendingArchiveComments({
|
|
114
|
+
orderedComments,
|
|
115
|
+
currentPollLocalInboundReceipts,
|
|
116
|
+
existingPendingIDs,
|
|
117
|
+
pendingSelectionOptions,
|
|
118
|
+
}) {
|
|
119
|
+
const pendingIDs = new Set(ensureArray(existingPendingIDs).map((value) => String(value || "").trim()).filter(Boolean));
|
|
120
|
+
const latestReceiptsByKey = new Map();
|
|
121
|
+
for (const receiptRaw of ensureArray(currentPollLocalInboundReceipts)) {
|
|
122
|
+
const receipt = safeObject(receiptRaw);
|
|
123
|
+
const receiptKey = buildRunnerLocalInboundReceiptKey(receipt);
|
|
124
|
+
if (!receiptKey) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const previous = safeObject(latestReceiptsByKey.get(receiptKey));
|
|
128
|
+
if (buildRunnerReceiptReplaySortTime(receipt) >= buildRunnerReceiptReplaySortTime(previous)) {
|
|
129
|
+
latestReceiptsByKey.set(receiptKey, receipt);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const replayCandidates = [];
|
|
133
|
+
for (const [receiptKey, receipt] of latestReceiptsByKey.entries()) {
|
|
134
|
+
const matchedRecord = orderedComments.find((record) => buildRunnerArchiveSourceMessageKey(record) === receiptKey);
|
|
135
|
+
const matchedID = String(safeObject(matchedRecord).id || "").trim();
|
|
136
|
+
if (!matchedID || pendingIDs.has(matchedID)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
replayCandidates.push(buildRunnerReceiptBackedReplayRecord(
|
|
140
|
+
matchedRecord,
|
|
141
|
+
receipt,
|
|
142
|
+
pendingSelectionOptions,
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
return replayCandidates.sort(compareArchiveCommentRecords);
|
|
146
|
+
}
|
|
147
|
+
|
|
54
148
|
function buildContextSpeakerType(parsedArchiveRaw) {
|
|
55
149
|
const parsed = safeObject(parsedArchiveRaw);
|
|
56
150
|
const kind = String(parsed.kind || "").trim();
|
|
@@ -862,10 +956,24 @@ export function selectRunnerPendingWork({
|
|
|
862
956
|
pending: importedPendingAfterCursor,
|
|
863
957
|
}, pendingSelectionOptions)
|
|
864
958
|
: fullPending;
|
|
959
|
+
const receiptBackedPending = buildRunnerReceiptBackedPendingArchiveComments({
|
|
960
|
+
orderedComments,
|
|
961
|
+
currentPollLocalInboundReceipts: ensureArray(importOutcome?.currentPollLocalInboundReceipts),
|
|
962
|
+
existingPendingIDs: ensureArray(safeObject(pending).pending).map((record) => String(safeObject(record).id || "").trim()),
|
|
963
|
+
pendingSelectionOptions,
|
|
964
|
+
});
|
|
965
|
+
const finalPending = ensureArray(safeObject(pending).pending).length === 0 && receiptBackedPending.length > 0
|
|
966
|
+
? applyPendingAgeSelection({
|
|
967
|
+
...safeObject(pending),
|
|
968
|
+
shouldPrime: false,
|
|
969
|
+
pending: receiptBackedPending,
|
|
970
|
+
}, pendingSelectionOptions)
|
|
971
|
+
: pending;
|
|
865
972
|
return {
|
|
866
973
|
orderedComments,
|
|
867
974
|
inboundComments,
|
|
868
975
|
importedRecords,
|
|
869
|
-
|
|
976
|
+
receiptBackedPending,
|
|
977
|
+
pending: finalPending,
|
|
870
978
|
};
|
|
871
979
|
}
|
package/lib/runner-runtime.mjs
CHANGED
|
@@ -575,6 +575,14 @@ function groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts) {
|
|
|
575
575
|
return Object.fromEntries(grouped.entries());
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
function buildRunnerCurrentPollLocalInboundReceipts(localInboundArtifactsByRoute, routeKey) {
|
|
579
|
+
return ensureArray(safeObject(localInboundArtifactsByRoute)[String(routeKey || "").trim()])
|
|
580
|
+
.map((artifactRaw) => ensureArray(safeObject(artifactRaw).receiptEntry))
|
|
581
|
+
.filter((entry) => entry.length === 2)
|
|
582
|
+
.map((entry) => safeObject(entry[1]))
|
|
583
|
+
.filter((receipt) => Object.keys(receipt).length > 0);
|
|
584
|
+
}
|
|
585
|
+
|
|
578
586
|
function buildRunnerRecentLocalInboundEnvelopes(routeStateRaw, recentLocalInboundReceipts) {
|
|
579
587
|
const routeState = safeObject(routeStateRaw);
|
|
580
588
|
const relevantEnvelopes = Object.values(safeObject(recentLocalInboundReceipts))
|
|
@@ -1144,6 +1152,10 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1144
1152
|
managedConversationBots,
|
|
1145
1153
|
);
|
|
1146
1154
|
const localInboundArtifactsByRoute = groupRunnerLocalInboundArtifactsByRoute(localInboundArtifacts);
|
|
1155
|
+
const currentPollLocalInboundReceipts = buildRunnerCurrentPollLocalInboundReceipts(
|
|
1156
|
+
localInboundArtifactsByRoute,
|
|
1157
|
+
routeKey,
|
|
1158
|
+
);
|
|
1147
1159
|
const recentLocalInboundReceipts = buildRunnerRecentLocalInboundReceipts(
|
|
1148
1160
|
routeState,
|
|
1149
1161
|
ensureArray(localInboundArtifactsByRoute[routeKey]),
|
|
@@ -1205,7 +1217,9 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1205
1217
|
persistPollingProgress([], lastUpdateID);
|
|
1206
1218
|
return {
|
|
1207
1219
|
importedCommentIDs: [],
|
|
1220
|
+
importedComments: [],
|
|
1208
1221
|
importedCount: 0,
|
|
1222
|
+
currentPollLocalInboundReceipts,
|
|
1209
1223
|
lastUpdateID,
|
|
1210
1224
|
};
|
|
1211
1225
|
}
|
|
@@ -1249,6 +1263,7 @@ export async function archiveLocalTelegramMessagesForRoute({
|
|
|
1249
1263
|
importedCommentIDs,
|
|
1250
1264
|
importedComments,
|
|
1251
1265
|
importedCount: importedCommentIDs.length,
|
|
1266
|
+
currentPollLocalInboundReceipts,
|
|
1252
1267
|
lastUpdateID: Math.max(lastUpdateID, handledUpdateID),
|
|
1253
1268
|
};
|
|
1254
1269
|
}
|
|
@@ -1007,6 +1007,63 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
1007
1007
|
);
|
|
1008
1008
|
}
|
|
1009
1009
|
|
|
1010
|
+
try {
|
|
1011
|
+
const pendingWork = selectRunnerPendingWork({
|
|
1012
|
+
comments: [
|
|
1013
|
+
{
|
|
1014
|
+
id: "archived-current-message",
|
|
1015
|
+
createdAt: "2026-03-18T00:00:01.000Z",
|
|
1016
|
+
updatedAt: "2026-03-18T00:00:01.000Z",
|
|
1017
|
+
parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 280, body: "@RyoAI3_bot 하이" },
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
id: "cursor-comment",
|
|
1021
|
+
createdAt: "2026-03-18T00:04:00.000Z",
|
|
1022
|
+
updatedAt: "2026-03-18T00:04:00.000Z",
|
|
1023
|
+
parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 281, body: "@RyoAI_bot already processed" },
|
|
1024
|
+
},
|
|
1025
|
+
],
|
|
1026
|
+
importOutcome: {
|
|
1027
|
+
importedCommentIDs: [],
|
|
1028
|
+
currentPollLocalInboundReceipts: [
|
|
1029
|
+
{
|
|
1030
|
+
chat_id: "-1001",
|
|
1031
|
+
message_id: 280,
|
|
1032
|
+
occurred_at: "2026-03-18T00:05:00.000Z",
|
|
1033
|
+
received_at: "2026-03-18T00:05:00.000Z",
|
|
1034
|
+
update_id: 2001,
|
|
1035
|
+
},
|
|
1036
|
+
],
|
|
1037
|
+
},
|
|
1038
|
+
refreshedState: {
|
|
1039
|
+
last_processed_comment_id: "cursor-comment",
|
|
1040
|
+
last_processed_created_at: "2026-03-18T00:04:00.000Z",
|
|
1041
|
+
},
|
|
1042
|
+
mode: "start",
|
|
1043
|
+
parseArchivedChatComment: () => null,
|
|
1044
|
+
deps: {
|
|
1045
|
+
normalizeArchiveCommentRecord: (record) => record,
|
|
1046
|
+
applyPendingAgeSelection: (selection) => selection,
|
|
1047
|
+
},
|
|
1048
|
+
pendingSelectionOptions: {
|
|
1049
|
+
maxPendingAgeMs: 15 * 60 * 1000,
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
push(
|
|
1053
|
+
"runner_pending_selection_replays_current_poll_local_receipt_against_existing_archive_comment",
|
|
1054
|
+
pendingWork.pending.pending.length === 1
|
|
1055
|
+
&& String(pendingWork.pending.pending[0]?.id || "").trim() === "archived-current-message"
|
|
1056
|
+
&& pendingWork.pending.pending[0]?.replayTriggeredByLocalReceipt === true,
|
|
1057
|
+
`pending=${pendingWork.pending.pending.map((item) => `${item.id}:${String(item.replayTriggeredByLocalReceipt === true)}`).join(",") || "(none)"}`,
|
|
1058
|
+
);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
push(
|
|
1061
|
+
"runner_pending_selection_replays_current_poll_local_receipt_against_existing_archive_comment",
|
|
1062
|
+
false,
|
|
1063
|
+
String(err?.message || err),
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1010
1067
|
try {
|
|
1011
1068
|
const selected = selectProjectChatDestination(
|
|
1012
1069
|
[
|
|
@@ -2042,10 +2099,16 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2042
2099
|
selectedRecord: humanRecord,
|
|
2043
2100
|
selectedBotUsernames: ["ryoai_bot"],
|
|
2044
2101
|
normalizedIntent: "coordination_request",
|
|
2045
|
-
});
|
|
2046
|
-
const claimedState = loadBotRunnerState();
|
|
2047
|
-
const claimedRequest = safeObject(safeObject(claimedState.requests)[claimed.requestKey]);
|
|
2048
|
-
const claimedConsumed = safeObject(
|
|
2102
|
+
});
|
|
2103
|
+
const claimedState = loadBotRunnerState();
|
|
2104
|
+
const claimedRequest = safeObject(safeObject(claimedState.requests)[claimed.requestKey]);
|
|
2105
|
+
const claimedConsumed = safeObject(
|
|
2106
|
+
Object.values(safeObject(claimedState.consumedComments)).find((entryRaw) => {
|
|
2107
|
+
const entry = safeObject(entryRaw);
|
|
2108
|
+
return String(entry.comment_id || "").trim() === humanRecord.id
|
|
2109
|
+
&& String(entry.route_key || "").trim() === requestRouteKey;
|
|
2110
|
+
}),
|
|
2111
|
+
);
|
|
2049
2112
|
push(
|
|
2050
2113
|
"runner_request_claim_persists_request_and_consumed_comment",
|
|
2051
2114
|
claimed.ok === true
|