metheus-governance-mcp-cli 0.2.270 → 0.2.271

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.
@@ -254,12 +254,24 @@ function normalizePendingSelectionOptions(rawOptions) {
254
254
  };
255
255
  }
256
256
 
257
+ function resolveArchiveRecordEventTime(record) {
258
+ const normalizedRecord = safeObject(record);
259
+ const parsedArchive = safeObject(normalizedRecord.parsedArchive);
260
+ return firstNonEmptyString([
261
+ normalizedRecord.sourceOccurredAt,
262
+ parsedArchive.occurredAt,
263
+ parsedArchive.occurred_at,
264
+ normalizedRecord.createdAt,
265
+ normalizedRecord.updatedAt,
266
+ ]);
267
+ }
268
+
257
269
  function isArchiveRecordWithinPendingAgeLimit(record, rawOptions) {
258
270
  const options = normalizePendingSelectionOptions(rawOptions);
259
271
  if (!(options.maxPendingAgeMs > 0)) {
260
272
  return true;
261
273
  }
262
- const recordTime = Date.parse(firstNonEmptyString([record?.createdAt, record?.updatedAt]));
274
+ const recordTime = Date.parse(resolveArchiveRecordEventTime(record));
263
275
  if (!Number.isFinite(recordTime)) {
264
276
  return true;
265
277
  }
@@ -411,8 +423,8 @@ function contextRelatednessScore(record, selectedRecord, rawOptions) {
411
423
  }
412
424
 
413
425
  export function compareArchiveCommentRecords(left, right) {
414
- const leftTime = firstNonEmptyString([left.createdAt, left.updatedAt]);
415
- const rightTime = firstNonEmptyString([right.createdAt, right.updatedAt]);
426
+ const leftTime = resolveArchiveRecordEventTime(left);
427
+ const rightTime = resolveArchiveRecordEventTime(right);
416
428
  if (leftTime && rightTime && leftTime !== rightTime) {
417
429
  return leftTime < rightTime ? -1 : 1;
418
430
  }
@@ -521,13 +533,18 @@ export function buildRunnerRouteStateFromComment(record, patch = {}) {
521
533
  export function normalizeArchiveCommentRecord(rawComment, parseArchivedChatComment) {
522
534
  const comment = safeObject(rawComment);
523
535
  const body = String(comment.body || "").trim();
536
+ const parsedArchive = typeof parseArchivedChatComment === "function" ? parseArchivedChatComment(body) : null;
524
537
  return {
525
538
  id: String(comment.id || "").trim(),
526
539
  body,
527
540
  createdAt: firstNonEmptyString([comment.created_at, comment.createdAt, comment.updated_at, comment.updatedAt]),
528
541
  updatedAt: firstNonEmptyString([comment.updated_at, comment.updatedAt]),
529
542
  authorUserID: firstNonEmptyString([comment.author_user_id, comment.authorUserId, comment.created_by]),
530
- parsedArchive: typeof parseArchivedChatComment === "function" ? parseArchivedChatComment(body) : null,
543
+ sourceOccurredAt: firstNonEmptyString([
544
+ safeObject(parsedArchive).occurredAt,
545
+ safeObject(parsedArchive).occurred_at,
546
+ ]),
547
+ parsedArchive,
531
548
  };
532
549
  }
533
550
 
@@ -51,6 +51,48 @@ function detectDirectedManagedReplyTarget({
51
51
  return "";
52
52
  }
53
53
 
54
+ const DIRECTED_MANAGED_REPLY_ENGLISH_ACTION_PATTERN = "\\b(?:say|tell|greet|reply|answer|introduce|mention)\\b";
55
+ const DIRECTED_MANAGED_REPLY_KOREAN_ACTION_PATTERN = "(?:\\uC778\\uC0AC|\\uB9D0|\\uC804\\uB2EC|\\uB2F5|\\uC18C\\uAC1C|\\uC5B8\\uAE09)";
56
+ const DIRECTED_MANAGED_REPLY_ACTION_PATTERN = `(?:${DIRECTED_MANAGED_REPLY_ENGLISH_ACTION_PATTERN}|${DIRECTED_MANAGED_REPLY_KOREAN_ACTION_PATTERN})`;
57
+ const DIRECTED_MANAGED_REPLY_POSTPOSITION_PATTERN = "(?:\\uC5D0\\uAC8C|\\uD55C\\uD14C|\\uAED8|\\uBCF4\\uACE0)";
58
+
59
+ function detectDirectedManagedReplyTargetV2({
60
+ text,
61
+ currentBotSelector = "",
62
+ managedMentions = [],
63
+ }) {
64
+ const normalizedText = String(text || "").trim();
65
+ const currentSelector = String(currentBotSelector || "").trim().toLowerCase();
66
+ if (!normalizedText || !currentSelector) {
67
+ return "";
68
+ }
69
+ const instructionPattern = new RegExp(DIRECTED_MANAGED_REPLY_ACTION_PATTERN, "iu");
70
+ if (!instructionPattern.test(normalizedText)) {
71
+ return "";
72
+ }
73
+ const candidates = ensureArray(managedMentions)
74
+ .map((item) => String(item || "").trim().toLowerCase())
75
+ .filter((item) => item && item !== currentSelector);
76
+ for (const selector of candidates) {
77
+ const escapedSelector = escapeRegexText(String(selector || "").replace(/^@+/, ""));
78
+ const explicitTargetPattern = new RegExp(
79
+ `(?:@${escapedSelector}\\s*(?:${DIRECTED_MANAGED_REPLY_POSTPOSITION_PATTERN})?|(?:to|for)\\s+@${escapedSelector}\\b)`,
80
+ "iu",
81
+ );
82
+ if (explicitTargetPattern.test(normalizedText)) {
83
+ return selector;
84
+ }
85
+ const instructionWindowPattern = new RegExp(
86
+ `@${escapedSelector}(?:\\s*(?:${DIRECTED_MANAGED_REPLY_POSTPOSITION_PATTERN}))?(?:[^@\\n]{0,32}?)${DIRECTED_MANAGED_REPLY_ACTION_PATTERN}`,
87
+ "iu",
88
+ );
89
+ if (instructionWindowPattern.test(normalizedText)) {
90
+ return selector;
91
+ }
92
+ }
93
+ return "";
94
+ }
95
+
54
96
  export async function resolveHumanIntentContext({
55
97
  selectedRecord,
56
98
  normalizedRoute,
@@ -118,7 +160,7 @@ export async function resolveHumanIntentContext({
118
160
  runnerHumanIntentPromises.set(cacheKey, promise);
119
161
  }
120
162
  let humanIntent = await runnerHumanIntentPromises.get(cacheKey);
121
- const directedReplyTargetSelector = detectDirectedManagedReplyTarget({
163
+ const directedReplyTargetSelector = detectDirectedManagedReplyTargetV2({
122
164
  text: parsed.body,
123
165
  currentBotSelector,
124
166
  managedMentions,
@@ -740,11 +740,11 @@ export async function runSelftestRunnerScenarios(push, deps) {
740
740
  );
741
741
  }
742
742
 
743
- try {
744
- const pendingSelection = selectPendingArchiveComments(
745
- [
746
- {
747
- id: "dup-comment-1",
743
+ try {
744
+ const pendingSelection = selectPendingArchiveComments(
745
+ [
746
+ {
747
+ id: "dup-comment-1",
748
748
  createdAt: "2026-03-18T00:00:01.000Z",
749
749
  updatedAt: "2026-03-18T00:00:01.000Z",
750
750
  parsedArchive: { kind: "telegram_message", chatID: "-1001", messageID: 353, body: "@RyoAI_bot first copy" },
@@ -770,14 +770,83 @@ export async function runSelftestRunnerScenarios(push, deps) {
770
770
  } catch (err) {
771
771
  push(
772
772
  "runner_pending_selection_ignores_duplicate_archived_inbound_message_ids",
773
- false,
774
- String(err?.message || err),
775
- );
776
- }
777
-
773
+ false,
774
+ String(err?.message || err),
775
+ );
776
+ }
777
+
778
+ try {
779
+ const nowMs = Date.parse("2026-03-31T12:00:00.000Z");
780
+ const pendingSelection = selectPendingArchiveComments(
781
+ [
782
+ {
783
+ id: "cursor-comment",
784
+ createdAt: "2026-03-31T11:55:00.000Z",
785
+ updatedAt: "2026-03-31T11:55:00.000Z",
786
+ sourceOccurredAt: "2026-03-31T09:55:00.000Z",
787
+ parsedArchive: {
788
+ kind: "telegram_message",
789
+ chatID: "-1001",
790
+ messageID: 1145,
791
+ occurredAt: "2026-03-31T09:55:00.000Z",
792
+ body: "@RyoAI_bot 기준 댓글",
793
+ },
794
+ },
795
+ {
796
+ id: "stale-source-comment",
797
+ createdAt: "2026-03-31T11:59:58.000Z",
798
+ updatedAt: "2026-03-31T11:59:58.000Z",
799
+ sourceOccurredAt: "2026-03-31T10:00:00.000Z",
800
+ parsedArchive: {
801
+ kind: "telegram_message",
802
+ chatID: "-1001",
803
+ messageID: 325,
804
+ occurredAt: "2026-03-31T10:00:00.000Z",
805
+ body: "@RyoAI_bot 오래된 하이",
806
+ },
807
+ },
808
+ {
809
+ id: "fresh-source-comment",
810
+ createdAt: "2026-03-31T11:59:59.000Z",
811
+ updatedAt: "2026-03-31T11:59:59.000Z",
812
+ sourceOccurredAt: "2026-03-31T11:57:00.000Z",
813
+ parsedArchive: {
814
+ kind: "telegram_message",
815
+ chatID: "-1001",
816
+ messageID: 1146,
817
+ occurredAt: "2026-03-31T11:57:00.000Z",
818
+ body: "@RyoAI_bot 최신 하이",
819
+ },
820
+ },
821
+ ],
822
+ {
823
+ last_processed_comment_id: "cursor-comment",
824
+ },
825
+ "start",
826
+ (record) => record,
827
+ {
828
+ maxPendingAgeMs: 15 * 60 * 1000,
829
+ nowMs,
830
+ },
831
+ );
832
+ push(
833
+ "runner_pending_selection_uses_source_occurrence_time_for_stale_filtering",
834
+ pendingSelection.pending.length === 1
835
+ && String(pendingSelection.pending[0]?.id || "") === "fresh-source-comment"
836
+ && ensureArray(pendingSelection.staleSkipped).some((record) => String(record?.id || "") === "stale-source-comment"),
837
+ `pending=${pendingSelection.pending.map((item) => item.id).join(",") || "(none)"} stale=${ensureArray(pendingSelection.staleSkipped).map((item) => item.id).join(",") || "(none)"}`,
838
+ );
839
+ } catch (err) {
840
+ push(
841
+ "runner_pending_selection_uses_source_occurrence_time_for_stale_filtering",
842
+ false,
843
+ String(err?.message || err),
844
+ );
845
+ }
846
+
778
847
  try {
779
848
  const duplicateComments = [
780
- {
849
+ {
781
850
  id: "dup-comment-earliest",
782
851
  createdAt: "2026-03-18T00:00:01.000Z",
783
852
  updatedAt: "2026-03-18T00:00:01.000Z",
@@ -3225,15 +3294,76 @@ export async function runSelftestRunnerScenarios(push, deps) {
3225
3294
  && aliasHumanIntentContext?.reusedPersistedContract === true,
3226
3295
  `managedMentions=${JSON.stringify(ensureArray(aliasHumanIntentContext?.managedMentions))} reused=${String(aliasHumanIntentContext?.reusedPersistedContract)}`,
3227
3296
  );
3228
- } catch (err) {
3229
- push(
3230
- "runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
3231
- false,
3232
- String(err?.message || err),
3233
- );
3234
- }
3235
-
3236
- const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
3297
+ } catch (err) {
3298
+ push(
3299
+ "runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
3300
+ false,
3301
+ String(err?.message || err),
3302
+ );
3303
+ }
3304
+
3305
+ try {
3306
+ const replyTargetIntentContext = await resolveHumanIntentContext({
3307
+ selectedRecord: {
3308
+ id: "comment-directed-reply-target-korean",
3309
+ parsedArchive: {
3310
+ kind: "telegram_message",
3311
+ body: "@RyoAI_bot 너가 @SangHoon01_bot 인사 시켜봐",
3312
+ senderIsBot: false,
3313
+ },
3314
+ },
3315
+ normalizedRoute: {
3316
+ name: "telegram-monitor-ryoai-bot-2",
3317
+ },
3318
+ bot: {
3319
+ username: "ryoai_bot",
3320
+ name: "RyoAI_bot",
3321
+ },
3322
+ executionPlan: {},
3323
+ deps: {
3324
+ resolveConversationPeerBots: () => [
3325
+ { id: "bot-self-1", name: "RyoAI_bot" },
3326
+ { id: "bot-peer-1", name: "SangHoon01_bot" },
3327
+ ],
3328
+ },
3329
+ intentDeps: {
3330
+ normalizeMentionSelector: normalizeSelftestMentionSelector,
3331
+ buildConversationPeerMap: (_bot, _route, runtimeDeps) => new Map(
3332
+ ensureArray(runtimeDeps?.resolveConversationPeerBots?.() || []).map((item) => {
3333
+ const selector = normalizeSelftestMentionSelector(item?.name || item?.username);
3334
+ return [selector, item];
3335
+ }).filter(([selector]) => selector),
3336
+ ),
3337
+ extractOrderedMentionSelectors: (text) => Array.from(String(text || "").matchAll(/@([A-Za-z0-9_]+)/g)).map((match) => normalizeSelftestMentionSelector(match[1] || "")),
3338
+ uniqueOrdered: normalizeSelftestConversationSelectorList,
3339
+ buildHumanIntentFromPersistedRunnerRequest: () => null,
3340
+ buildRunnerHumanIntentCacheKey: () => "directed-reply-target-korean",
3341
+ runnerHumanIntentPromises: new Map(),
3342
+ analyzeHumanConversationIntentWithContractResolver: async () => ({
3343
+ intentMode: "single_bot",
3344
+ replyExpectation: "actionable",
3345
+ intentType: "actionable_request",
3346
+ }),
3347
+ scheduleRunnerHumanIntentCacheCleanup: () => {},
3348
+ isCompleteHumanIntentContract: () => true,
3349
+ normalizeHumanIntentType: (value, fallback = "") => String(value || "").trim() || fallback,
3350
+ },
3351
+ });
3352
+ push(
3353
+ "runner_human_intent_context_detects_korean_directed_reply_target",
3354
+ String(replyTargetIntentContext?.humanIntent?.replyTargetBotSelector || "") === "sanghoon01_bot"
3355
+ && JSON.stringify(ensureArray(replyTargetIntentContext?.managedMentions || [])) === JSON.stringify(["ryoai_bot", "sanghoon01_bot"]),
3356
+ `reply_target=${String(replyTargetIntentContext?.humanIntent?.replyTargetBotSelector || "(none)")} managed=${JSON.stringify(ensureArray(replyTargetIntentContext?.managedMentions || []))}`,
3357
+ );
3358
+ } catch (err) {
3359
+ push(
3360
+ "runner_human_intent_context_detects_korean_directed_reply_target",
3361
+ false,
3362
+ String(err?.message || err),
3363
+ );
3364
+ }
3365
+
3366
+ const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
3237
3367
  {
3238
3368
  id: "comment-2b",
3239
3369
  parsedArchive: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.270",
3
+ "version": "0.2.271",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [