metheus-governance-mcp-cli 0.2.270 → 0.2.272
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 +247 -116
- package/lib/runner-data.mjs +6 -0
- package/lib/runner-helpers.mjs +32 -4
- package/lib/runner-orchestration-intent-contracts.mjs +43 -1
- package/lib/selftest-runner-scenarios.mjs +215 -20
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2669,39 +2669,47 @@ function saveBotRunnerState(nextState) {
|
|
|
2669
2669
|
});
|
|
2670
2670
|
}
|
|
2671
2671
|
|
|
2672
|
-
function normalizeBotRunnerExcludedComments(rawExcluded, nowMs = Date.now()) {
|
|
2673
|
-
const normalized = {};
|
|
2674
|
-
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawExcluded))) {
|
|
2675
|
-
const commentID = String(commentIDRaw || "").trim();
|
|
2676
|
-
if (!commentID) continue;
|
|
2672
|
+
function normalizeBotRunnerExcludedComments(rawExcluded, nowMs = Date.now()) {
|
|
2673
|
+
const normalized = {};
|
|
2674
|
+
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawExcluded))) {
|
|
2675
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
2676
|
+
if (!commentID) continue;
|
|
2677
2677
|
const entry = safeObject(entryRaw);
|
|
2678
2678
|
const excludedAt = firstNonEmptyString([entry.excluded_at, entry.excludedAt, entry.updated_at, entry.updatedAt]);
|
|
2679
2679
|
const excludedAtMs = Date.parse(excludedAt);
|
|
2680
2680
|
if (Number.isFinite(excludedAtMs) && nowMs - excludedAtMs > BOT_RUNNER_EXCLUDED_COMMENT_KEEP_MS) {
|
|
2681
2681
|
continue;
|
|
2682
2682
|
}
|
|
2683
|
-
normalized[commentID] = {
|
|
2684
|
-
comment_id: commentID,
|
|
2685
|
-
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
2686
|
-
provider: String(entry.provider || "").trim(),
|
|
2687
|
-
request_key: String(entry.request_key || entry.requestKey || "").trim(),
|
|
2688
|
-
route_key: String(entry.route_key || entry.routeKey || "").trim(),
|
|
2689
|
-
excluded_at: excludedAt || new Date(nowMs).toISOString(),
|
|
2690
|
-
reason_code: String(entry.reason_code || entry.reasonCode || entry.reason || "").trim(),
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2683
|
+
normalized[commentID] = {
|
|
2684
|
+
comment_id: commentID,
|
|
2685
|
+
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
2686
|
+
provider: String(entry.provider || "").trim(),
|
|
2687
|
+
request_key: String(entry.request_key || entry.requestKey || "").trim(),
|
|
2688
|
+
route_key: String(entry.route_key || entry.routeKey || "").trim(),
|
|
2689
|
+
excluded_at: excludedAt || new Date(nowMs).toISOString(),
|
|
2690
|
+
reason_code: String(entry.reason_code || entry.reasonCode || entry.reason || "").trim(),
|
|
2691
|
+
decision: String(entry.decision || "").trim().toLowerCase(),
|
|
2692
|
+
action: String(entry.action || "").trim().toLowerCase(),
|
|
2693
|
+
conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
|
|
2694
|
+
source_message_id: intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) || undefined,
|
|
2695
|
+
comment_kind: String(entry.comment_kind || entry.commentKind || "").trim().toLowerCase(),
|
|
2696
|
+
request_status: normalizeRunnerRequestStatus(entry.request_status || entry.requestStatus),
|
|
2697
|
+
source_occurred_at: String(entry.source_occurred_at || entry.sourceOccurredAt || "").trim(),
|
|
2698
|
+
stale_after_at: String(entry.stale_after_at || entry.staleAfterAt || "").trim(),
|
|
2699
|
+
selection_state: normalizeRunnerCommentSelectionState(
|
|
2700
|
+
entry.selection_state || entry.selectionState,
|
|
2701
|
+
"superseded",
|
|
2702
|
+
),
|
|
2703
|
+
context_excluded: entry.context_excluded !== false,
|
|
2704
|
+
closed_reason: String(entry.closed_reason || entry.closedReason || "").trim(),
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
return normalized;
|
|
2700
2708
|
}
|
|
2701
2709
|
|
|
2702
|
-
function normalizeRunnerRequestStatus(rawStatus) {
|
|
2703
|
-
const status = String(rawStatus || "").trim().toLowerCase();
|
|
2704
|
-
return [
|
|
2710
|
+
function normalizeRunnerRequestStatus(rawStatus) {
|
|
2711
|
+
const status = String(rawStatus || "").trim().toLowerCase();
|
|
2712
|
+
return [
|
|
2705
2713
|
"planned",
|
|
2706
2714
|
"claimed",
|
|
2707
2715
|
"running",
|
|
@@ -2710,9 +2718,54 @@ function normalizeRunnerRequestStatus(rawStatus) {
|
|
|
2710
2718
|
"expired",
|
|
2711
2719
|
"loop_closed",
|
|
2712
2720
|
].includes(status)
|
|
2713
|
-
? status
|
|
2714
|
-
: "planned";
|
|
2715
|
-
}
|
|
2721
|
+
? status
|
|
2722
|
+
: "planned";
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function normalizeRunnerCommentSelectionState(rawState, fallback = "pending") {
|
|
2726
|
+
const normalizedFallback = String(fallback || "").trim().toLowerCase() || "pending";
|
|
2727
|
+
const normalized = String(rawState || "").trim().toLowerCase();
|
|
2728
|
+
return [
|
|
2729
|
+
"pending",
|
|
2730
|
+
"consumed",
|
|
2731
|
+
"closed",
|
|
2732
|
+
"superseded",
|
|
2733
|
+
"stale",
|
|
2734
|
+
].includes(normalized)
|
|
2735
|
+
? normalized
|
|
2736
|
+
: normalizedFallback;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
function resolveRunnerCommentStateSourceOccurredAt(recordRaw) {
|
|
2740
|
+
const record = safeObject(recordRaw);
|
|
2741
|
+
const parsed = safeObject(record.parsedArchive);
|
|
2742
|
+
return firstNonEmptyString([
|
|
2743
|
+
record.source_occurred_at,
|
|
2744
|
+
record.sourceOccurredAt,
|
|
2745
|
+
parsed.occurredAt,
|
|
2746
|
+
parsed.occurred_at,
|
|
2747
|
+
record.created_at,
|
|
2748
|
+
record.createdAt,
|
|
2749
|
+
record.updated_at,
|
|
2750
|
+
record.updatedAt,
|
|
2751
|
+
]);
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
function resolveRunnerCommentStateStaleAfterAt(recordRaw, maxAgeMs = BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS) {
|
|
2755
|
+
const explicit = firstNonEmptyString([
|
|
2756
|
+
safeObject(recordRaw).stale_after_at,
|
|
2757
|
+
safeObject(recordRaw).staleAfterAt,
|
|
2758
|
+
]);
|
|
2759
|
+
if (explicit) {
|
|
2760
|
+
return explicit;
|
|
2761
|
+
}
|
|
2762
|
+
const sourceOccurredAt = resolveRunnerCommentStateSourceOccurredAt(recordRaw);
|
|
2763
|
+
const sourceOccurredAtMs = Date.parse(sourceOccurredAt);
|
|
2764
|
+
if (!Number.isFinite(sourceOccurredAtMs) || !(maxAgeMs > 0)) {
|
|
2765
|
+
return "";
|
|
2766
|
+
}
|
|
2767
|
+
return new Date(sourceOccurredAtMs + maxAgeMs).toISOString();
|
|
2768
|
+
}
|
|
2716
2769
|
|
|
2717
2770
|
function isFinalRunnerRequestStatus(rawStatus) {
|
|
2718
2771
|
const status = normalizeRunnerRequestStatus(rawStatus);
|
|
@@ -3245,9 +3298,9 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
|
|
|
3245
3298
|
return normalized;
|
|
3246
3299
|
}
|
|
3247
3300
|
|
|
3248
|
-
function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
3249
|
-
const normalized = {};
|
|
3250
|
-
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawConsumed))) {
|
|
3301
|
+
function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
3302
|
+
const normalized = {};
|
|
3303
|
+
for (const [commentIDRaw, entryRaw] of Object.entries(safeObject(rawConsumed))) {
|
|
3251
3304
|
const entry = safeObject(entryRaw);
|
|
3252
3305
|
const commentID = String(entry.comment_id || commentIDRaw || "").trim();
|
|
3253
3306
|
if (!commentID) continue;
|
|
@@ -3257,21 +3310,27 @@ function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
|
|
|
3257
3310
|
if (Number.isFinite(consumedAtMs) && nowMs - consumedAtMs > BOT_RUNNER_REQUEST_KEEP_MS) {
|
|
3258
3311
|
continue;
|
|
3259
3312
|
}
|
|
3260
|
-
normalized[ledgerKey] = {
|
|
3261
|
-
ledger_key: ledgerKey,
|
|
3262
|
-
comment_id: commentID,
|
|
3263
|
-
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
3264
|
-
provider: String(entry.provider || "").trim(),
|
|
3265
|
-
request_key: String(entry.request_key || entry.requestKey || "").trim(),
|
|
3266
|
-
consumed_at: consumedAt || new Date(nowMs).toISOString(),
|
|
3267
|
-
route_key: String(entry.route_key || entry.routeKey || "").trim(),
|
|
3268
|
-
conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
|
|
3269
|
-
source_message_id: intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) || undefined,
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3313
|
+
normalized[ledgerKey] = {
|
|
3314
|
+
ledger_key: ledgerKey,
|
|
3315
|
+
comment_id: commentID,
|
|
3316
|
+
project_id: String(entry.project_id || entry.projectID || "").trim(),
|
|
3317
|
+
provider: String(entry.provider || "").trim(),
|
|
3318
|
+
request_key: String(entry.request_key || entry.requestKey || "").trim(),
|
|
3319
|
+
consumed_at: consumedAt || new Date(nowMs).toISOString(),
|
|
3320
|
+
route_key: String(entry.route_key || entry.routeKey || "").trim(),
|
|
3321
|
+
conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
|
|
3322
|
+
source_message_id: intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) || undefined,
|
|
3323
|
+
source_occurred_at: String(entry.source_occurred_at || entry.sourceOccurredAt || "").trim(),
|
|
3324
|
+
stale_after_at: String(entry.stale_after_at || entry.staleAfterAt || "").trim(),
|
|
3325
|
+
selection_state: normalizeRunnerCommentSelectionState(
|
|
3326
|
+
entry.selection_state || entry.selectionState,
|
|
3327
|
+
"consumed",
|
|
3328
|
+
),
|
|
3329
|
+
comment_kind: String(entry.comment_kind || entry.commentKind || "").trim().toLowerCase(),
|
|
3330
|
+
request_status: normalizeRunnerRequestStatus(entry.request_status || entry.requestStatus),
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
return normalized;
|
|
3275
3334
|
}
|
|
3276
3335
|
|
|
3277
3336
|
function buildRunnerConsumedCommentLedgerKey(commentIDRaw, routeKeyRaw = "", commentKindRaw = "") {
|
|
@@ -4788,10 +4847,10 @@ function upsertRunnerRequest(state, requestKey, patch = {}) {
|
|
|
4788
4847
|
};
|
|
4789
4848
|
}
|
|
4790
4849
|
|
|
4791
|
-
function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
4792
|
-
const commentID = String(commentIDRaw || "").trim();
|
|
4793
|
-
const currentState = safeObject(state);
|
|
4794
|
-
const consumedComments = normalizeBotRunnerConsumedComments(currentState.consumedComments || currentState.consumed_comments);
|
|
4850
|
+
function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
4851
|
+
const commentID = String(commentIDRaw || "").trim();
|
|
4852
|
+
const currentState = safeObject(state);
|
|
4853
|
+
const consumedComments = normalizeBotRunnerConsumedComments(currentState.consumedComments || currentState.consumed_comments);
|
|
4795
4854
|
const ledgerKey = buildRunnerConsumedCommentLedgerKey(commentID, patch.route_key, patch.comment_kind);
|
|
4796
4855
|
const existing = safeObject(consumedComments[ledgerKey]);
|
|
4797
4856
|
const nextEntry = {
|
|
@@ -4799,10 +4858,22 @@ function upsertRunnerConsumedComment(state, commentIDRaw, patch = {}) {
|
|
|
4799
4858
|
...safeObject(patch),
|
|
4800
4859
|
comment_id: commentID,
|
|
4801
4860
|
project_id: String(patch.project_id || existing.project_id || "").trim(),
|
|
4802
|
-
provider: String(patch.provider || existing.provider || "").trim(),
|
|
4803
|
-
consumed_at: new Date().toISOString(),
|
|
4804
|
-
|
|
4805
|
-
|
|
4861
|
+
provider: String(patch.provider || existing.provider || "").trim(),
|
|
4862
|
+
consumed_at: new Date().toISOString(),
|
|
4863
|
+
source_occurred_at: firstNonEmptyString([
|
|
4864
|
+
patch.source_occurred_at,
|
|
4865
|
+
existing.source_occurred_at,
|
|
4866
|
+
]),
|
|
4867
|
+
stale_after_at: firstNonEmptyString([
|
|
4868
|
+
patch.stale_after_at,
|
|
4869
|
+
existing.stale_after_at,
|
|
4870
|
+
]),
|
|
4871
|
+
selection_state: normalizeRunnerCommentSelectionState(
|
|
4872
|
+
patch.selection_state || existing.selection_state,
|
|
4873
|
+
"consumed",
|
|
4874
|
+
),
|
|
4875
|
+
request_status: normalizeRunnerRequestStatus(patch.request_status || existing.request_status),
|
|
4876
|
+
};
|
|
4806
4877
|
consumedComments[ledgerKey] = nextEntry;
|
|
4807
4878
|
return {
|
|
4808
4879
|
consumedComments,
|
|
@@ -5128,16 +5199,19 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5128
5199
|
last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
5129
5200
|
last_source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
|
|
5130
5201
|
});
|
|
5131
|
-
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(stateForClaim, selectedRecord?.id, {
|
|
5132
|
-
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
5133
|
-
provider: String(normalizedRoute?.provider || "").trim(),
|
|
5134
|
-
request_key: requestKey,
|
|
5135
|
-
route_key: String(routeKey || "").trim(),
|
|
5136
|
-
conversation_id: resolvedConversationID,
|
|
5137
|
-
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5202
|
+
const { consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(stateForClaim, selectedRecord?.id, {
|
|
5203
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
5204
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
5205
|
+
request_key: requestKey,
|
|
5206
|
+
route_key: String(routeKey || "").trim(),
|
|
5207
|
+
conversation_id: resolvedConversationID,
|
|
5208
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
5209
|
+
source_occurred_at: resolveRunnerCommentStateSourceOccurredAt(selectedRecord),
|
|
5210
|
+
stale_after_at: resolveRunnerCommentStateStaleAfterAt(selectedRecord),
|
|
5211
|
+
selection_state: "consumed",
|
|
5212
|
+
comment_kind: commentKind,
|
|
5213
|
+
request_status: "claimed",
|
|
5214
|
+
});
|
|
5141
5215
|
saveBotRunnerState({
|
|
5142
5216
|
routes: stateForClaim.routes,
|
|
5143
5217
|
sharedInboxes: stateForClaim.sharedInboxes || stateForClaim.shared_inboxes,
|
|
@@ -6561,17 +6635,20 @@ function markRunnerRequestLifecycle({
|
|
|
6561
6635
|
const commentID = String(selectedRecord?.id || "").trim();
|
|
6562
6636
|
let nextConsumedComments = currentState.consumedComments || currentState.consumed_comments;
|
|
6563
6637
|
if (commentID) {
|
|
6564
|
-
({ consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(currentState, commentID, {
|
|
6565
|
-
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
6566
|
-
provider: String(normalizedRoute?.provider || "").trim(),
|
|
6567
|
-
request_key: key,
|
|
6568
|
-
route_key: String(routeKey || "").trim(),
|
|
6569
|
-
conversation_id: conversationID,
|
|
6570
|
-
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6638
|
+
({ consumedComments: nextConsumedComments } = upsertRunnerConsumedComment(currentState, commentID, {
|
|
6639
|
+
project_id: String(normalizedRoute?.projectID || "").trim(),
|
|
6640
|
+
provider: String(normalizedRoute?.provider || "").trim(),
|
|
6641
|
+
request_key: key,
|
|
6642
|
+
route_key: String(routeKey || "").trim(),
|
|
6643
|
+
conversation_id: conversationID,
|
|
6644
|
+
source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
|
|
6645
|
+
source_occurred_at: resolveRunnerCommentStateSourceOccurredAt(selectedRecord),
|
|
6646
|
+
stale_after_at: resolveRunnerCommentStateStaleAfterAt(selectedRecord),
|
|
6647
|
+
selection_state: isFinalRunnerRequestStatus(nextStatus) ? "closed" : "consumed",
|
|
6648
|
+
comment_kind: String(parsed.kind || "").trim().toLowerCase(),
|
|
6649
|
+
request_status: nextStatus,
|
|
6650
|
+
}));
|
|
6651
|
+
}
|
|
6575
6652
|
saveBotRunnerState({
|
|
6576
6653
|
routes: currentState.routes,
|
|
6577
6654
|
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
@@ -6900,16 +6977,22 @@ function mergeServerRunnerRequestLedgerIntoLocalState(currentState, normalizedRo
|
|
|
6900
6977
|
const commentState = safeObject(commentStateRaw);
|
|
6901
6978
|
const commentID = String(commentState.comment_id || commentState.commentID || "").trim();
|
|
6902
6979
|
if (!commentID) continue;
|
|
6903
|
-
const normalizedCommentState = normalizeBotRunnerConsumedComments({ [commentID]: commentState })[commentID];
|
|
6904
|
-
if (normalizedCommentState) {
|
|
6905
|
-
nextConsumedComments[commentID] = normalizedCommentState;
|
|
6906
|
-
}
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6980
|
+
const normalizedCommentState = normalizeBotRunnerConsumedComments({ [commentID]: commentState })[commentID];
|
|
6981
|
+
if (normalizedCommentState) {
|
|
6982
|
+
nextConsumedComments[commentID] = normalizedCommentState;
|
|
6983
|
+
}
|
|
6984
|
+
const normalizedSelectionState = normalizeRunnerCommentSelectionState(commentState.selection_state, "pending");
|
|
6985
|
+
if (
|
|
6986
|
+
commentState.context_excluded === true
|
|
6987
|
+
|| normalizedSelectionState === "stale"
|
|
6988
|
+
|| normalizedSelectionState === "superseded"
|
|
6989
|
+
|| normalizedSelectionState === "closed"
|
|
6990
|
+
) {
|
|
6991
|
+
nextExcludedComments[commentID] = normalizeBotRunnerExcludedComments({
|
|
6992
|
+
[commentID]: {
|
|
6993
|
+
...commentState,
|
|
6994
|
+
excluded_at: commentState.updated_at || commentState.consumed_at,
|
|
6995
|
+
},
|
|
6913
6996
|
})[commentID];
|
|
6914
6997
|
}
|
|
6915
6998
|
}
|
|
@@ -6967,24 +7050,27 @@ function buildProjectRunnerRequestCommentStatesForSync(state, normalizedRoute) {
|
|
|
6967
7050
|
if (!runnerLedgerEntryMatchesProject(entry, normalizedRoute, requestIndex)) continue;
|
|
6968
7051
|
const commentID = String(entry.comment_id || "").trim();
|
|
6969
7052
|
if (!commentID) continue;
|
|
6970
|
-
commentStateMap.set(commentID, {
|
|
6971
|
-
comment_id: commentID,
|
|
6972
|
-
project_id: projectID,
|
|
6973
|
-
request_key: String(entry.request_key || "").trim(),
|
|
6974
|
-
provider: String(entry.provider || provider).trim(),
|
|
7053
|
+
commentStateMap.set(commentID, {
|
|
7054
|
+
comment_id: commentID,
|
|
7055
|
+
project_id: projectID,
|
|
7056
|
+
request_key: String(entry.request_key || "").trim(),
|
|
7057
|
+
provider: String(entry.provider || provider).trim(),
|
|
6975
7058
|
route_key: String(entry.route_key || "").trim(),
|
|
6976
7059
|
conversation_id: String(entry.conversation_id || "").trim(),
|
|
6977
7060
|
source_message_id: intFromRawAllowZero(entry.source_message_id, 0) || undefined,
|
|
6978
7061
|
comment_kind: String(entry.comment_kind || "").trim(),
|
|
6979
7062
|
request_status: String(entry.request_status || "").trim(),
|
|
6980
|
-
reason_code: "",
|
|
6981
|
-
decision: "",
|
|
6982
|
-
action: "",
|
|
6983
|
-
context_excluded: false,
|
|
6984
|
-
closed_reason: "",
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
7063
|
+
reason_code: "",
|
|
7064
|
+
decision: "",
|
|
7065
|
+
action: "",
|
|
7066
|
+
context_excluded: false,
|
|
7067
|
+
closed_reason: "",
|
|
7068
|
+
source_occurred_at: String(entry.source_occurred_at || "").trim(),
|
|
7069
|
+
stale_after_at: String(entry.stale_after_at || "").trim(),
|
|
7070
|
+
selection_state: normalizeRunnerCommentSelectionState(entry.selection_state, "consumed"),
|
|
7071
|
+
consumed_at: String(entry.consumed_at || "").trim(),
|
|
7072
|
+
});
|
|
7073
|
+
}
|
|
6988
7074
|
|
|
6989
7075
|
for (const entryRaw of Object.values(excludedComments)) {
|
|
6990
7076
|
const entry = safeObject(entryRaw);
|
|
@@ -7004,13 +7090,19 @@ function buildProjectRunnerRequestCommentStatesForSync(state, normalizedRoute) {
|
|
|
7004
7090
|
comment_kind: String(entry.comment_kind || existing.comment_kind || "").trim(),
|
|
7005
7091
|
request_status: String(entry.request_status || existing.request_status || "").trim(),
|
|
7006
7092
|
reason_code: String(entry.reason_code || "").trim(),
|
|
7007
|
-
decision: String(entry.decision || "").trim(),
|
|
7008
|
-
action: String(entry.action || "").trim(),
|
|
7009
|
-
context_excluded: entry.context_excluded !== false,
|
|
7010
|
-
closed_reason: String(entry.closed_reason || existing.closed_reason || "").trim(),
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7093
|
+
decision: String(entry.decision || "").trim(),
|
|
7094
|
+
action: String(entry.action || "").trim(),
|
|
7095
|
+
context_excluded: entry.context_excluded !== false,
|
|
7096
|
+
closed_reason: String(entry.closed_reason || existing.closed_reason || "").trim(),
|
|
7097
|
+
source_occurred_at: String(entry.source_occurred_at || existing.source_occurred_at || "").trim(),
|
|
7098
|
+
stale_after_at: String(entry.stale_after_at || existing.stale_after_at || "").trim(),
|
|
7099
|
+
selection_state: normalizeRunnerCommentSelectionState(
|
|
7100
|
+
entry.selection_state || existing.selection_state,
|
|
7101
|
+
entry.context_excluded !== false ? "superseded" : "pending",
|
|
7102
|
+
),
|
|
7103
|
+
consumed_at: String(existing.consumed_at || entry.excluded_at || "").trim(),
|
|
7104
|
+
});
|
|
7105
|
+
}
|
|
7014
7106
|
|
|
7015
7107
|
return Array.from(commentStateMap.values());
|
|
7016
7108
|
}
|
|
@@ -10258,10 +10350,10 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10258
10350
|
}
|
|
10259
10351
|
return true;
|
|
10260
10352
|
});
|
|
10261
|
-
const pendingWork = selectRunnerPendingWork({
|
|
10262
|
-
comments: commentsForPending,
|
|
10263
|
-
importOutcome,
|
|
10264
|
-
refreshedState,
|
|
10353
|
+
const pendingWork = selectRunnerPendingWork({
|
|
10354
|
+
comments: commentsForPending,
|
|
10355
|
+
importOutcome,
|
|
10356
|
+
refreshedState,
|
|
10265
10357
|
mode,
|
|
10266
10358
|
parseArchivedChatComment,
|
|
10267
10359
|
pendingSelectionOptions: {
|
|
@@ -10271,13 +10363,52 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
10271
10363
|
applyPendingAgeSelection,
|
|
10272
10364
|
normalizeArchiveCommentRecord,
|
|
10273
10365
|
},
|
|
10274
|
-
});
|
|
10275
|
-
const pending = pendingWork.pending;
|
|
10276
|
-
if (pending.staleSkippedLatest) {
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10366
|
+
});
|
|
10367
|
+
const pending = pendingWork.pending;
|
|
10368
|
+
if (pending.staleSkippedLatest) {
|
|
10369
|
+
let staleStateChanged = false;
|
|
10370
|
+
for (const staleRecordRaw of ensureArray(pending.staleSkipped)) {
|
|
10371
|
+
const staleRecord = safeObject(staleRecordRaw);
|
|
10372
|
+
const commentID = String(staleRecord.id || "").trim();
|
|
10373
|
+
if (!commentID) continue;
|
|
10374
|
+
const parsed = safeObject(staleRecord.parsedArchive);
|
|
10375
|
+
const existingExcludedEntry = safeObject(excludedComments[commentID]);
|
|
10376
|
+
const stalePatch = {
|
|
10377
|
+
project_id: String(normalizedRoute.projectID || "").trim(),
|
|
10378
|
+
provider: String(normalizedRoute.provider || "").trim(),
|
|
10379
|
+
route_key: routeKey,
|
|
10380
|
+
request_key: String(existingExcludedEntry.request_key || "").trim(),
|
|
10381
|
+
conversation_id: String(parsed.conversationID || parsed.conversationId || existingExcludedEntry.conversation_id || "").trim(),
|
|
10382
|
+
source_message_id: intFromRawAllowZero(parsed.messageID || existingExcludedEntry.source_message_id, 0) || undefined,
|
|
10383
|
+
comment_kind: String(parsed.kind || existingExcludedEntry.comment_kind || "").trim().toLowerCase(),
|
|
10384
|
+
request_status: "expired",
|
|
10385
|
+
source_occurred_at: resolveRunnerCommentStateSourceOccurredAt(staleRecord),
|
|
10386
|
+
stale_after_at: resolveRunnerCommentStateStaleAfterAt(staleRecord),
|
|
10387
|
+
selection_state: "stale",
|
|
10388
|
+
reason_code: "stale_archive_message",
|
|
10389
|
+
decision: "skip",
|
|
10390
|
+
action: "stale_skipped",
|
|
10391
|
+
context_excluded: true,
|
|
10392
|
+
closed_reason: "stale_source_message",
|
|
10393
|
+
};
|
|
10394
|
+
const updatedExcludedEntry = markBotRunnerExcludedComment(commentID, stalePatch);
|
|
10395
|
+
if (
|
|
10396
|
+
normalizeRunnerCommentSelectionState(updatedExcludedEntry.selection_state, "superseded") === "stale"
|
|
10397
|
+
&& String(updatedExcludedEntry.closed_reason || "").trim() === "stale_source_message"
|
|
10398
|
+
) {
|
|
10399
|
+
staleStateChanged = true;
|
|
10400
|
+
}
|
|
10401
|
+
}
|
|
10402
|
+
if (staleStateChanged) {
|
|
10403
|
+
await syncRunnerRequestLedgerForProjectToServer({
|
|
10404
|
+
normalizedRoute,
|
|
10405
|
+
runtime,
|
|
10406
|
+
});
|
|
10407
|
+
}
|
|
10408
|
+
saveRunnerRouteState(routeKey, buildRunnerRouteStateFromComment(pending.staleSkippedLatest, {
|
|
10409
|
+
last_reason: `skipped ${ensureArray(pending.staleSkipped).length} stale archive message(s) older than ${Math.floor(BOT_RUNNER_PENDING_COMMENT_MAX_AGE_MS / 60000)} minutes`,
|
|
10410
|
+
}));
|
|
10411
|
+
refreshedState = safeObject(loadBotRunnerState().routes[routeKey]);
|
|
10281
10412
|
}
|
|
10282
10413
|
if (pending.shouldPrime && pending.latest) {
|
|
10283
10414
|
saveRunnerRouteState(routeKey, buildRunnerRouteStateFromComment(pending.latest, { primed: true }));
|
package/lib/runner-data.mjs
CHANGED
|
@@ -478,6 +478,9 @@ export async function listProjectRunnerRequestCommentStates(
|
|
|
478
478
|
action: String(row.action || row.Action || "").trim(),
|
|
479
479
|
context_excluded: row.context_excluded === true || row.ContextExcluded === true,
|
|
480
480
|
closed_reason: String(row.closed_reason || row.closedReason || row.ClosedReason || "").trim(),
|
|
481
|
+
source_occurred_at: String(row.source_occurred_at || row.sourceOccurredAt || row.SourceOccurredAt || "").trim(),
|
|
482
|
+
stale_after_at: String(row.stale_after_at || row.staleAfterAt || row.StaleAfterAt || "").trim(),
|
|
483
|
+
selection_state: String(row.selection_state || row.selectionState || row.SelectionState || "").trim(),
|
|
481
484
|
consumed_at: String(row.consumed_at || row.consumedAt || row.ConsumedAt || "").trim(),
|
|
482
485
|
updated_at: String(row.updated_at || row.updatedAt || row.UpdatedAt || "").trim(),
|
|
483
486
|
};
|
|
@@ -513,6 +516,9 @@ export async function upsertProjectRunnerRequestCommentState(
|
|
|
513
516
|
action: String(raw.action || "").trim(),
|
|
514
517
|
context_excluded: raw.context_excluded === true,
|
|
515
518
|
closed_reason: String(raw.closed_reason || "").trim(),
|
|
519
|
+
source_occurred_at: String(raw.source_occurred_at || "").trim() || undefined,
|
|
520
|
+
stale_after_at: String(raw.stale_after_at || "").trim() || undefined,
|
|
521
|
+
selection_state: String(raw.selection_state || "").trim() || undefined,
|
|
516
522
|
consumed_at: String(raw.consumed_at || "").trim() || undefined,
|
|
517
523
|
};
|
|
518
524
|
const extraHeaders = actorUserID ? { "X-Actor-User-Id": actorUserID } : {};
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -254,12 +254,28 @@ 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
|
|
274
|
+
const staleAfterTime = Date.parse(String(safeObject(record).staleAfterAt || ""));
|
|
275
|
+
if (Number.isFinite(staleAfterTime)) {
|
|
276
|
+
return options.nowMs <= staleAfterTime;
|
|
277
|
+
}
|
|
278
|
+
const recordTime = Date.parse(resolveArchiveRecordEventTime(record));
|
|
263
279
|
if (!Number.isFinite(recordTime)) {
|
|
264
280
|
return true;
|
|
265
281
|
}
|
|
@@ -411,8 +427,8 @@ function contextRelatednessScore(record, selectedRecord, rawOptions) {
|
|
|
411
427
|
}
|
|
412
428
|
|
|
413
429
|
export function compareArchiveCommentRecords(left, right) {
|
|
414
|
-
const leftTime =
|
|
415
|
-
const rightTime =
|
|
430
|
+
const leftTime = resolveArchiveRecordEventTime(left);
|
|
431
|
+
const rightTime = resolveArchiveRecordEventTime(right);
|
|
416
432
|
if (leftTime && rightTime && leftTime !== rightTime) {
|
|
417
433
|
return leftTime < rightTime ? -1 : 1;
|
|
418
434
|
}
|
|
@@ -521,13 +537,25 @@ export function buildRunnerRouteStateFromComment(record, patch = {}) {
|
|
|
521
537
|
export function normalizeArchiveCommentRecord(rawComment, parseArchivedChatComment) {
|
|
522
538
|
const comment = safeObject(rawComment);
|
|
523
539
|
const body = String(comment.body || "").trim();
|
|
540
|
+
const parsedArchive = typeof parseArchivedChatComment === "function" ? parseArchivedChatComment(body) : null;
|
|
524
541
|
return {
|
|
525
542
|
id: String(comment.id || "").trim(),
|
|
526
543
|
body,
|
|
527
544
|
createdAt: firstNonEmptyString([comment.created_at, comment.createdAt, comment.updated_at, comment.updatedAt]),
|
|
528
545
|
updatedAt: firstNonEmptyString([comment.updated_at, comment.updatedAt]),
|
|
529
546
|
authorUserID: firstNonEmptyString([comment.author_user_id, comment.authorUserId, comment.created_by]),
|
|
530
|
-
|
|
547
|
+
sourceOccurredAt: firstNonEmptyString([
|
|
548
|
+
comment.source_occurred_at,
|
|
549
|
+
comment.sourceOccurredAt,
|
|
550
|
+
safeObject(parsedArchive).occurredAt,
|
|
551
|
+
safeObject(parsedArchive).occurred_at,
|
|
552
|
+
]),
|
|
553
|
+
staleAfterAt: firstNonEmptyString([
|
|
554
|
+
comment.stale_after_at,
|
|
555
|
+
comment.staleAfterAt,
|
|
556
|
+
]),
|
|
557
|
+
selectionState: String(comment.selection_state || comment.selectionState || "").trim().toLowerCase(),
|
|
558
|
+
parsedArchive,
|
|
531
559
|
};
|
|
532
560
|
}
|
|
533
561
|
|
|
@@ -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 =
|
|
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,148 @@ 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
|
+
|
|
847
|
+
try {
|
|
848
|
+
const nowMs = Date.parse("2026-03-31T12:00:00.000Z");
|
|
849
|
+
const pendingSelection = selectPendingArchiveComments(
|
|
850
|
+
[
|
|
851
|
+
{
|
|
852
|
+
id: "cursor-comment-persisted-stale",
|
|
853
|
+
createdAt: "2026-03-31T11:55:00.000Z",
|
|
854
|
+
updatedAt: "2026-03-31T11:55:00.000Z",
|
|
855
|
+
parsedArchive: {
|
|
856
|
+
kind: "telegram_message",
|
|
857
|
+
chatID: "-1001",
|
|
858
|
+
messageID: 1148,
|
|
859
|
+
body: "@RyoAI_bot 기준 댓글",
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
id: "stale-by-persisted-cutoff",
|
|
864
|
+
createdAt: "2026-03-31T11:59:58.000Z",
|
|
865
|
+
updatedAt: "2026-03-31T11:59:58.000Z",
|
|
866
|
+
staleAfterAt: "2026-03-31T11:50:00.000Z",
|
|
867
|
+
parsedArchive: {
|
|
868
|
+
kind: "telegram_message",
|
|
869
|
+
chatID: "-1001",
|
|
870
|
+
messageID: 327,
|
|
871
|
+
body: "@RyoAI_bot archive created recently but source already stale",
|
|
872
|
+
},
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
id: "fresh-by-persisted-cutoff",
|
|
876
|
+
createdAt: "2026-03-31T11:59:59.000Z",
|
|
877
|
+
updatedAt: "2026-03-31T11:59:59.000Z",
|
|
878
|
+
staleAfterAt: "2026-03-31T12:05:00.000Z",
|
|
879
|
+
parsedArchive: {
|
|
880
|
+
kind: "telegram_message",
|
|
881
|
+
chatID: "-1001",
|
|
882
|
+
messageID: 1149,
|
|
883
|
+
body: "@RyoAI_bot still fresh by persisted cutoff",
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
{
|
|
888
|
+
last_processed_comment_id: "cursor-comment-persisted-stale",
|
|
889
|
+
},
|
|
890
|
+
"start",
|
|
891
|
+
(record) => record,
|
|
892
|
+
{
|
|
893
|
+
maxPendingAgeMs: 15 * 60 * 1000,
|
|
894
|
+
nowMs,
|
|
895
|
+
},
|
|
896
|
+
);
|
|
897
|
+
push(
|
|
898
|
+
"runner_pending_selection_prefers_persisted_stale_after_at_over_recent_archive_created_at",
|
|
899
|
+
pendingSelection.pending.length === 1
|
|
900
|
+
&& String(pendingSelection.pending[0]?.id || "") === "fresh-by-persisted-cutoff"
|
|
901
|
+
&& ensureArray(pendingSelection.staleSkipped).some((record) => String(record?.id || "") === "stale-by-persisted-cutoff"),
|
|
902
|
+
`pending=${pendingSelection.pending.map((item) => item.id).join(",") || "(none)"} stale=${ensureArray(pendingSelection.staleSkipped).map((item) => item.id).join(",") || "(none)"}`,
|
|
903
|
+
);
|
|
904
|
+
} catch (err) {
|
|
905
|
+
push(
|
|
906
|
+
"runner_pending_selection_prefers_persisted_stale_after_at_over_recent_archive_created_at",
|
|
907
|
+
false,
|
|
908
|
+
String(err?.message || err),
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
778
912
|
try {
|
|
779
913
|
const duplicateComments = [
|
|
780
|
-
{
|
|
914
|
+
{
|
|
781
915
|
id: "dup-comment-earliest",
|
|
782
916
|
createdAt: "2026-03-18T00:00:01.000Z",
|
|
783
917
|
updatedAt: "2026-03-18T00:00:01.000Z",
|
|
@@ -3225,15 +3359,76 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
3225
3359
|
&& aliasHumanIntentContext?.reusedPersistedContract === true,
|
|
3226
3360
|
`managedMentions=${JSON.stringify(ensureArray(aliasHumanIntentContext?.managedMentions))} reused=${String(aliasHumanIntentContext?.reusedPersistedContract)}`,
|
|
3227
3361
|
);
|
|
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
|
-
|
|
3362
|
+
} catch (err) {
|
|
3363
|
+
push(
|
|
3364
|
+
"runner_human_intent_context_ignores_raw_alias_selector_for_managed_mentions",
|
|
3365
|
+
false,
|
|
3366
|
+
String(err?.message || err),
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
try {
|
|
3371
|
+
const replyTargetIntentContext = await resolveHumanIntentContext({
|
|
3372
|
+
selectedRecord: {
|
|
3373
|
+
id: "comment-directed-reply-target-korean",
|
|
3374
|
+
parsedArchive: {
|
|
3375
|
+
kind: "telegram_message",
|
|
3376
|
+
body: "@RyoAI_bot 너가 @SangHoon01_bot 인사 시켜봐",
|
|
3377
|
+
senderIsBot: false,
|
|
3378
|
+
},
|
|
3379
|
+
},
|
|
3380
|
+
normalizedRoute: {
|
|
3381
|
+
name: "telegram-monitor-ryoai-bot-2",
|
|
3382
|
+
},
|
|
3383
|
+
bot: {
|
|
3384
|
+
username: "ryoai_bot",
|
|
3385
|
+
name: "RyoAI_bot",
|
|
3386
|
+
},
|
|
3387
|
+
executionPlan: {},
|
|
3388
|
+
deps: {
|
|
3389
|
+
resolveConversationPeerBots: () => [
|
|
3390
|
+
{ id: "bot-self-1", name: "RyoAI_bot" },
|
|
3391
|
+
{ id: "bot-peer-1", name: "SangHoon01_bot" },
|
|
3392
|
+
],
|
|
3393
|
+
},
|
|
3394
|
+
intentDeps: {
|
|
3395
|
+
normalizeMentionSelector: normalizeSelftestMentionSelector,
|
|
3396
|
+
buildConversationPeerMap: (_bot, _route, runtimeDeps) => new Map(
|
|
3397
|
+
ensureArray(runtimeDeps?.resolveConversationPeerBots?.() || []).map((item) => {
|
|
3398
|
+
const selector = normalizeSelftestMentionSelector(item?.name || item?.username);
|
|
3399
|
+
return [selector, item];
|
|
3400
|
+
}).filter(([selector]) => selector),
|
|
3401
|
+
),
|
|
3402
|
+
extractOrderedMentionSelectors: (text) => Array.from(String(text || "").matchAll(/@([A-Za-z0-9_]+)/g)).map((match) => normalizeSelftestMentionSelector(match[1] || "")),
|
|
3403
|
+
uniqueOrdered: normalizeSelftestConversationSelectorList,
|
|
3404
|
+
buildHumanIntentFromPersistedRunnerRequest: () => null,
|
|
3405
|
+
buildRunnerHumanIntentCacheKey: () => "directed-reply-target-korean",
|
|
3406
|
+
runnerHumanIntentPromises: new Map(),
|
|
3407
|
+
analyzeHumanConversationIntentWithContractResolver: async () => ({
|
|
3408
|
+
intentMode: "single_bot",
|
|
3409
|
+
replyExpectation: "actionable",
|
|
3410
|
+
intentType: "actionable_request",
|
|
3411
|
+
}),
|
|
3412
|
+
scheduleRunnerHumanIntentCacheCleanup: () => {},
|
|
3413
|
+
isCompleteHumanIntentContract: () => true,
|
|
3414
|
+
normalizeHumanIntentType: (value, fallback = "") => String(value || "").trim() || fallback,
|
|
3415
|
+
},
|
|
3416
|
+
});
|
|
3417
|
+
push(
|
|
3418
|
+
"runner_human_intent_context_detects_korean_directed_reply_target",
|
|
3419
|
+
String(replyTargetIntentContext?.humanIntent?.replyTargetBotSelector || "") === "sanghoon01_bot"
|
|
3420
|
+
&& JSON.stringify(ensureArray(replyTargetIntentContext?.managedMentions || [])) === JSON.stringify(["ryoai_bot", "sanghoon01_bot"]),
|
|
3421
|
+
`reply_target=${String(replyTargetIntentContext?.humanIntent?.replyTargetBotSelector || "(none)")} managed=${JSON.stringify(ensureArray(replyTargetIntentContext?.managedMentions || []))}`,
|
|
3422
|
+
);
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
push(
|
|
3425
|
+
"runner_human_intent_context_detects_korean_directed_reply_target",
|
|
3426
|
+
false,
|
|
3427
|
+
String(err?.message || err),
|
|
3428
|
+
);
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
const mentionOverridesReplyForUnmentionedBot = evaluateTelegramRunnerTrigger(
|
|
3237
3432
|
{
|
|
3238
3433
|
id: "comment-2b",
|
|
3239
3434
|
parsedArchive: {
|