metheus-governance-mcp-cli 0.2.199 → 0.2.201
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 +412 -45
- package/lib/runner-data.mjs +10 -15
- package/lib/runner-orchestration.mjs +78 -17
- package/lib/selftest-runner-scenarios.mjs +379 -8
- package/lib/selftest-telegram-e2e.mjs +51 -0
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2553,6 +2553,76 @@ async function findServerRunnerRequestForMessageID({
|
|
|
2553
2553
|
}
|
|
2554
2554
|
}
|
|
2555
2555
|
|
|
2556
|
+
async function loadRunnerArchiveThreadMessageIndex({
|
|
2557
|
+
runtime,
|
|
2558
|
+
threadID,
|
|
2559
|
+
cache = null,
|
|
2560
|
+
}) {
|
|
2561
|
+
const normalizedThreadID = String(threadID || "").trim();
|
|
2562
|
+
if (
|
|
2563
|
+
cache
|
|
2564
|
+
&& String(cache.threadID || "").trim() === normalizedThreadID
|
|
2565
|
+
&& cache.messageIndex instanceof Map
|
|
2566
|
+
) {
|
|
2567
|
+
return cache;
|
|
2568
|
+
}
|
|
2569
|
+
if (!normalizedThreadID || !runtime?.baseURL || !runtime?.token) {
|
|
2570
|
+
return {
|
|
2571
|
+
threadID: normalizedThreadID,
|
|
2572
|
+
messageIndex: new Map(),
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
try {
|
|
2576
|
+
const comments = await listThreadCommentsTail({
|
|
2577
|
+
siteBaseURL: runtime.baseURL,
|
|
2578
|
+
threadID: normalizedThreadID,
|
|
2579
|
+
token: runtime.token,
|
|
2580
|
+
timeoutSeconds: runtime.timeoutSeconds,
|
|
2581
|
+
actorUserID: runtime.actor?.user_id,
|
|
2582
|
+
tailLimit: 300,
|
|
2583
|
+
scanLimit: 1200,
|
|
2584
|
+
});
|
|
2585
|
+
const messageIndex = new Map();
|
|
2586
|
+
for (const commentRaw of ensureArray(comments)) {
|
|
2587
|
+
const comment = safeObject(commentRaw);
|
|
2588
|
+
const parsedArchive = safeObject(comment.parsedArchive || parseArchivedChatComment(comment.body));
|
|
2589
|
+
const messageID = intFromRawAllowZero(parsedArchive.messageID, 0);
|
|
2590
|
+
if (messageID > 0 && !messageIndex.has(messageID)) {
|
|
2591
|
+
messageIndex.set(messageID, {
|
|
2592
|
+
...comment,
|
|
2593
|
+
parsedArchive,
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
return {
|
|
2598
|
+
threadID: normalizedThreadID,
|
|
2599
|
+
messageIndex,
|
|
2600
|
+
};
|
|
2601
|
+
} catch {
|
|
2602
|
+
return {
|
|
2603
|
+
threadID: normalizedThreadID,
|
|
2604
|
+
messageIndex: new Map(),
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
async function findRunnerArchiveThreadMessageByID({
|
|
2610
|
+
runtime,
|
|
2611
|
+
threadID,
|
|
2612
|
+
messageID,
|
|
2613
|
+
cache = null,
|
|
2614
|
+
}) {
|
|
2615
|
+
const nextCache = await loadRunnerArchiveThreadMessageIndex({
|
|
2616
|
+
runtime,
|
|
2617
|
+
threadID,
|
|
2618
|
+
cache,
|
|
2619
|
+
});
|
|
2620
|
+
return {
|
|
2621
|
+
cache: nextCache,
|
|
2622
|
+
record: safeObject(nextCache.messageIndex.get(intFromRawAllowZero(messageID, 0))),
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2556
2626
|
function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
|
|
2557
2627
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2558
2628
|
const explicitConversationID = String(parsed.conversationID || "").trim();
|
|
@@ -2600,6 +2670,8 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
|
2600
2670
|
normalizedRoute,
|
|
2601
2671
|
selectedRecord,
|
|
2602
2672
|
runtime,
|
|
2673
|
+
archiveThreadID = "",
|
|
2674
|
+
hydrationAttempted = false,
|
|
2603
2675
|
}) {
|
|
2604
2676
|
const initialState = safeObject(state);
|
|
2605
2677
|
const initialContext = resolveRunnerReplyChainConversationContext(initialState, normalizedRoute, selectedRecord);
|
|
@@ -2611,11 +2683,11 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
|
2611
2683
|
};
|
|
2612
2684
|
}
|
|
2613
2685
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2614
|
-
const
|
|
2686
|
+
const initialReplyToMessageID = intFromRawAllowZero(
|
|
2615
2687
|
parsed.replyToMessageID || safeObject(initialContext).replyToMessageID,
|
|
2616
2688
|
0,
|
|
2617
2689
|
);
|
|
2618
|
-
if (
|
|
2690
|
+
if (initialReplyToMessageID <= 0) {
|
|
2619
2691
|
return {
|
|
2620
2692
|
state: initialState,
|
|
2621
2693
|
replyChainContext: initialContext,
|
|
@@ -2623,43 +2695,142 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
|
2623
2695
|
};
|
|
2624
2696
|
}
|
|
2625
2697
|
const chatID = String(parsed.chatID || parsed.chatId || "").trim();
|
|
2626
|
-
const
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2698
|
+
const normalizedArchiveThreadID = firstNonEmptyString([
|
|
2699
|
+
archiveThreadID,
|
|
2700
|
+
selectedRecord?.thread_id,
|
|
2701
|
+
selectedRecord?.threadID,
|
|
2702
|
+
selectedRecord?.threadId,
|
|
2703
|
+
]);
|
|
2704
|
+
let stateForLookup = initialState;
|
|
2705
|
+
let replyToMessageID = initialReplyToMessageID;
|
|
2706
|
+
let archiveThreadCache = null;
|
|
2707
|
+
let lastAnchorMessageID = intFromRawAllowZero(safeObject(initialContext).anchorMessageID, 0) || initialReplyToMessageID;
|
|
2708
|
+
const visitedMessageIDs = new Set();
|
|
2709
|
+
|
|
2710
|
+
const buildContextFromRequest = (referencedRequestRaw, reasonSuffix = "") => {
|
|
2711
|
+
const referencedRequest = safeObject(referencedRequestRaw);
|
|
2712
|
+
const anchorMessageID = intFromRawAllowZero(referencedRequest.source_message_id, 0) || replyToMessageID;
|
|
2636
2713
|
return {
|
|
2637
|
-
state:
|
|
2638
|
-
...initialState,
|
|
2639
|
-
requests: requestIndex,
|
|
2640
|
-
},
|
|
2714
|
+
state: stateForLookup,
|
|
2641
2715
|
replyChainContext: {
|
|
2642
|
-
conversationID: String(
|
|
2716
|
+
conversationID: String(referencedRequest.conversation_id || "").trim()
|
|
2643
2717
|
|| buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
|
|
2644
|
-
replyToMessageID,
|
|
2718
|
+
replyToMessageID: initialReplyToMessageID,
|
|
2645
2719
|
anchorMessageID,
|
|
2646
|
-
reason: String(
|
|
2647
|
-
?
|
|
2648
|
-
:
|
|
2649
|
-
referencedRequest
|
|
2720
|
+
reason: String(referencedRequest.conversation_id || "").trim()
|
|
2721
|
+
? `reply_request_conversation${reasonSuffix}`
|
|
2722
|
+
: `reply_request_synthetic${reasonSuffix}`,
|
|
2723
|
+
referencedRequest,
|
|
2650
2724
|
},
|
|
2651
|
-
hydrated:
|
|
2725
|
+
hydrated: hydrationAttempted,
|
|
2652
2726
|
};
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
for (let hop = 0; hop < 8 && replyToMessageID > 0; hop += 1) {
|
|
2730
|
+
if (visitedMessageIDs.has(replyToMessageID)) {
|
|
2731
|
+
break;
|
|
2732
|
+
}
|
|
2733
|
+
visitedMessageIDs.add(replyToMessageID);
|
|
2734
|
+
|
|
2735
|
+
const localReferencedRequest = safeObject(findRunnerRequestsForMessageID(stateForLookup, normalizedRoute, {
|
|
2736
|
+
chatID,
|
|
2737
|
+
messageID: replyToMessageID,
|
|
2738
|
+
})[0]);
|
|
2739
|
+
if (localReferencedRequest.request_key) {
|
|
2740
|
+
return buildContextFromRequest(localReferencedRequest, hop > 0 ? "_chain" : "");
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
if (runtime?.baseURL && runtime?.token) {
|
|
2744
|
+
const serverReferencedRequest = await findServerRunnerRequestForMessageID({
|
|
2745
|
+
normalizedRoute,
|
|
2746
|
+
runtime,
|
|
2747
|
+
chatID,
|
|
2748
|
+
messageID: replyToMessageID,
|
|
2749
|
+
});
|
|
2750
|
+
if (serverReferencedRequest.request_key) {
|
|
2751
|
+
const requestIndex = normalizeBotRunnerRequests(stateForLookup.requests);
|
|
2752
|
+
requestIndex[String(serverReferencedRequest.request_key || "").trim()] = serverReferencedRequest;
|
|
2753
|
+
stateForLookup = {
|
|
2754
|
+
...stateForLookup,
|
|
2755
|
+
requests: requestIndex,
|
|
2756
|
+
};
|
|
2757
|
+
return buildContextFromRequest(serverReferencedRequest, hop > 0 ? "_chain_server" : "_server");
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
if (!normalizedArchiveThreadID || !runtime?.baseURL || !runtime?.token) {
|
|
2762
|
+
break;
|
|
2763
|
+
}
|
|
2764
|
+
const archivedMessageLookup = await findRunnerArchiveThreadMessageByID({
|
|
2765
|
+
runtime,
|
|
2766
|
+
threadID: normalizedArchiveThreadID,
|
|
2767
|
+
messageID: replyToMessageID,
|
|
2768
|
+
cache: archiveThreadCache,
|
|
2769
|
+
});
|
|
2770
|
+
archiveThreadCache = archivedMessageLookup.cache;
|
|
2771
|
+
const archivedRecord = safeObject(archivedMessageLookup.record);
|
|
2772
|
+
const archivedParsed = safeObject(archivedRecord.parsedArchive || parseArchivedChatComment(archivedRecord.body));
|
|
2773
|
+
const archivedMessageID = intFromRawAllowZero(archivedParsed.messageID, 0) || replyToMessageID;
|
|
2774
|
+
if (archivedMessageID > 0) {
|
|
2775
|
+
lastAnchorMessageID = archivedMessageID;
|
|
2776
|
+
}
|
|
2777
|
+
const archivedConversationID = String(archivedParsed.conversationID || "").trim();
|
|
2778
|
+
if (archivedConversationID) {
|
|
2779
|
+
return {
|
|
2780
|
+
state: stateForLookup,
|
|
2781
|
+
replyChainContext: {
|
|
2782
|
+
conversationID: archivedConversationID,
|
|
2783
|
+
replyToMessageID: initialReplyToMessageID,
|
|
2784
|
+
anchorMessageID: archivedMessageID,
|
|
2785
|
+
reason: "reply_chain_archive_conversation",
|
|
2786
|
+
referencedRequest: null,
|
|
2787
|
+
},
|
|
2788
|
+
hydrated: hydrationAttempted,
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
const nextReplyToMessageID = intFromRawAllowZero(archivedParsed.replyToMessageID, 0);
|
|
2792
|
+
if (nextReplyToMessageID <= 0) {
|
|
2793
|
+
return {
|
|
2794
|
+
state: stateForLookup,
|
|
2795
|
+
replyChainContext: {
|
|
2796
|
+
conversationID: buildSyntheticReplyChainConversationID(normalizedRoute, chatID, lastAnchorMessageID || archivedMessageID),
|
|
2797
|
+
replyToMessageID: initialReplyToMessageID,
|
|
2798
|
+
anchorMessageID: lastAnchorMessageID || archivedMessageID,
|
|
2799
|
+
reason: "reply_chain_archive_synthetic",
|
|
2800
|
+
referencedRequest: null,
|
|
2801
|
+
},
|
|
2802
|
+
hydrated: hydrationAttempted,
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
replyToMessageID = nextReplyToMessageID;
|
|
2653
2806
|
}
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2807
|
+
|
|
2808
|
+
if (!hydrationAttempted && runtime?.baseURL && runtime?.token) {
|
|
2809
|
+
const hydratedState = await hydrateRunnerRequestLedgerFromServer({
|
|
2810
|
+
normalizedRoute,
|
|
2811
|
+
runtime,
|
|
2812
|
+
});
|
|
2813
|
+
const hydratedResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
2814
|
+
state: hydratedState,
|
|
2815
|
+
normalizedRoute,
|
|
2816
|
+
selectedRecord,
|
|
2817
|
+
runtime,
|
|
2818
|
+
archiveThreadID: normalizedArchiveThreadID,
|
|
2819
|
+
hydrationAttempted: true,
|
|
2820
|
+
});
|
|
2821
|
+
return {
|
|
2822
|
+
state: hydratedResolution.state,
|
|
2823
|
+
replyChainContext: hydratedResolution.replyChainContext,
|
|
2824
|
+
hydrated: true,
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2659
2828
|
return {
|
|
2660
|
-
state:
|
|
2661
|
-
replyChainContext:
|
|
2662
|
-
|
|
2829
|
+
state: stateForLookup,
|
|
2830
|
+
replyChainContext: hydrationAttempted
|
|
2831
|
+
? resolveRunnerReplyChainConversationContext(stateForLookup, normalizedRoute, selectedRecord)
|
|
2832
|
+
: initialContext,
|
|
2833
|
+
hydrated: hydrationAttempted,
|
|
2663
2834
|
};
|
|
2664
2835
|
}
|
|
2665
2836
|
|
|
@@ -2709,6 +2880,7 @@ async function claimRunnerRequestForHumanComment({
|
|
|
2709
2880
|
selectedBotUsernames = [],
|
|
2710
2881
|
normalizedIntent = "",
|
|
2711
2882
|
runtime = null,
|
|
2883
|
+
archiveThreadID = "",
|
|
2712
2884
|
}) {
|
|
2713
2885
|
const parsed = safeObject(selectedRecord?.parsedArchive);
|
|
2714
2886
|
const commentKind = String(parsed.kind || "").trim().toLowerCase();
|
|
@@ -2724,6 +2896,7 @@ async function claimRunnerRequestForHumanComment({
|
|
|
2724
2896
|
normalizedRoute,
|
|
2725
2897
|
selectedRecord,
|
|
2726
2898
|
runtime,
|
|
2899
|
+
archiveThreadID,
|
|
2727
2900
|
});
|
|
2728
2901
|
const replyChainContext = safeObject(replyChainResolution.replyChainContext);
|
|
2729
2902
|
const referencedRequest = safeObject(replyChainContext.referencedRequest);
|
|
@@ -3314,6 +3487,81 @@ function buildRunnerRootWorkItemTransitionPath(currentStatusRaw, targetStatusRaw
|
|
|
3314
3487
|
return [];
|
|
3315
3488
|
}
|
|
3316
3489
|
|
|
3490
|
+
function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, requestRaw, routeKeyHint = "") {
|
|
3491
|
+
const currentState = safeObject(currentStateRaw);
|
|
3492
|
+
const request = safeObject(requestRaw);
|
|
3493
|
+
const requestKey = String(request.request_key || "").trim();
|
|
3494
|
+
if (!requestKey) {
|
|
3495
|
+
return {};
|
|
3496
|
+
}
|
|
3497
|
+
const requestSourceMessageID = intFromRawAllowZero(request.last_source_message_id, 0)
|
|
3498
|
+
|| intFromRawAllowZero(request.source_message_id, 0);
|
|
3499
|
+
const routeKeys = uniqueOrderedStrings(
|
|
3500
|
+
[
|
|
3501
|
+
routeKeyHint,
|
|
3502
|
+
String(request.claimed_by_route || "").trim(),
|
|
3503
|
+
],
|
|
3504
|
+
(value) => String(value || "").trim(),
|
|
3505
|
+
).filter(Boolean);
|
|
3506
|
+
for (const routeKey of routeKeys) {
|
|
3507
|
+
const routeState = safeObject(safeObject(currentState.routes)[routeKey]);
|
|
3508
|
+
if (!Object.keys(routeState).length) {
|
|
3509
|
+
continue;
|
|
3510
|
+
}
|
|
3511
|
+
const routeSourceMessageID = intFromRawAllowZero(routeState.last_source_message_id, 0)
|
|
3512
|
+
|| intFromRawAllowZero(routeState.source_message_id, 0);
|
|
3513
|
+
if (requestSourceMessageID && routeSourceMessageID && requestSourceMessageID !== routeSourceMessageID) {
|
|
3514
|
+
continue;
|
|
3515
|
+
}
|
|
3516
|
+
const patch = {};
|
|
3517
|
+
const recoveredRootWorkItemID = firstNonEmptyString([
|
|
3518
|
+
routeState.active_root_work_item_id,
|
|
3519
|
+
routeState.last_root_work_item_id,
|
|
3520
|
+
]);
|
|
3521
|
+
if (!String(request.root_work_item_id || "").trim() && recoveredRootWorkItemID) {
|
|
3522
|
+
patch.root_work_item_id = recoveredRootWorkItemID;
|
|
3523
|
+
patch.root_work_item_title = firstNonEmptyString([
|
|
3524
|
+
request.root_work_item_title,
|
|
3525
|
+
routeState.active_root_work_item_title,
|
|
3526
|
+
routeState.last_root_work_item_title,
|
|
3527
|
+
]);
|
|
3528
|
+
patch.root_work_item_status = normalizeRunnerWorkItemStatus(
|
|
3529
|
+
request.root_work_item_status
|
|
3530
|
+
|| routeState.active_root_work_item_status
|
|
3531
|
+
|| routeState.last_root_work_item_status,
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3534
|
+
const requestStatus = normalizeRunnerRequestStatus(request.status);
|
|
3535
|
+
if (!isFinalRunnerRequestStatus(requestStatus)) {
|
|
3536
|
+
const routeAction = String(routeState.last_action || "").trim().toLowerCase();
|
|
3537
|
+
if (routeAction === "replied") {
|
|
3538
|
+
patch.status = "completed";
|
|
3539
|
+
patch.completed_at = firstNonEmptyString([
|
|
3540
|
+
request.completed_at,
|
|
3541
|
+
request.updated_at,
|
|
3542
|
+
new Date().toISOString(),
|
|
3543
|
+
]);
|
|
3544
|
+
} else if (routeAction === "error" || routeAction === "skipped" || routeAction === "closed") {
|
|
3545
|
+
patch.status = "closed";
|
|
3546
|
+
patch.closed_at = firstNonEmptyString([
|
|
3547
|
+
request.closed_at,
|
|
3548
|
+
request.updated_at,
|
|
3549
|
+
new Date().toISOString(),
|
|
3550
|
+
]);
|
|
3551
|
+
patch.closed_reason = firstNonEmptyString([
|
|
3552
|
+
request.closed_reason,
|
|
3553
|
+
routeState.last_reason,
|
|
3554
|
+
routeAction,
|
|
3555
|
+
]);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
if (Object.keys(patch).length) {
|
|
3559
|
+
return patch;
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
return {};
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3317
3565
|
async function syncRunnerRequestRootWorkItemForOutcome({
|
|
3318
3566
|
normalizedRoute,
|
|
3319
3567
|
runtime,
|
|
@@ -3327,7 +3575,19 @@ async function syncRunnerRequestRootWorkItemForOutcome({
|
|
|
3327
3575
|
};
|
|
3328
3576
|
}
|
|
3329
3577
|
const currentState = loadBotRunnerState();
|
|
3330
|
-
|
|
3578
|
+
let request = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
|
|
3579
|
+
const recoveryPatch = buildRunnerRequestRecoveryPatchFromRouteState(currentState, request);
|
|
3580
|
+
if (Object.keys(recoveryPatch).length) {
|
|
3581
|
+
const recovered = upsertRunnerRequest(currentState, key, recoveryPatch);
|
|
3582
|
+
request = safeObject(recovered.request);
|
|
3583
|
+
saveBotRunnerState({
|
|
3584
|
+
routes: currentState.routes,
|
|
3585
|
+
sharedInboxes: currentState.sharedInboxes || currentState.shared_inboxes,
|
|
3586
|
+
excludedComments: currentState.excludedComments || currentState.excluded_comments,
|
|
3587
|
+
requests: recovered.requests,
|
|
3588
|
+
consumedComments: currentState.consumedComments || currentState.consumed_comments,
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3331
3591
|
const rootWorkItemID = String(request.root_work_item_id || "").trim();
|
|
3332
3592
|
if (!rootWorkItemID) {
|
|
3333
3593
|
return {
|
|
@@ -3746,6 +4006,20 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
|
|
|
3746
4006
|
preserveLocalStringWhenServerBlank("root_thread_id");
|
|
3747
4007
|
preserveLocalStringWhenServerBlank("root_work_item_created_at");
|
|
3748
4008
|
preserveLocalStringWhenServerBlank("root_work_item_last_error");
|
|
4009
|
+
const localStatus = normalizeRunnerRequestStatus(localEntry.status);
|
|
4010
|
+
const serverStatus = normalizeRunnerRequestStatus(serverEntry.status);
|
|
4011
|
+
if (isFinalRunnerRequestStatus(localStatus) && !isFinalRunnerRequestStatus(serverStatus)) {
|
|
4012
|
+
merged.status = localStatus;
|
|
4013
|
+
if (String(localEntry.completed_at || "").trim()) {
|
|
4014
|
+
merged.completed_at = String(localEntry.completed_at || "").trim();
|
|
4015
|
+
}
|
|
4016
|
+
if (String(localEntry.closed_at || "").trim()) {
|
|
4017
|
+
merged.closed_at = String(localEntry.closed_at || "").trim();
|
|
4018
|
+
}
|
|
4019
|
+
if (String(localEntry.closed_reason || "").trim()) {
|
|
4020
|
+
merged.closed_reason = String(localEntry.closed_reason || "").trim();
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
3749
4023
|
return merged;
|
|
3750
4024
|
}
|
|
3751
4025
|
|
|
@@ -3930,9 +4204,18 @@ async function syncRunnerRequestLedgerForProjectToServer({ normalizedRoute, runt
|
|
|
3930
4204
|
const commentStates = buildProjectRunnerRequestCommentStatesForSync(state, normalizedRoute);
|
|
3931
4205
|
|
|
3932
4206
|
for (const request of requests) {
|
|
4207
|
+
const recoveryPatch = buildRunnerRequestRecoveryPatchFromRouteState(state, request);
|
|
4208
|
+
const recoveredRequest = Object.keys(recoveryPatch).length
|
|
4209
|
+
? normalizeBotRunnerRequests({
|
|
4210
|
+
[String(request.request_key || "").trim()]: {
|
|
4211
|
+
...safeObject(request),
|
|
4212
|
+
...recoveryPatch,
|
|
4213
|
+
},
|
|
4214
|
+
})[String(request.request_key || "").trim()]
|
|
4215
|
+
: request;
|
|
3933
4216
|
if (
|
|
3934
|
-
isActionableRunnerRequestIntent(
|
|
3935
|
-
&& !String(
|
|
4217
|
+
isActionableRunnerRequestIntent(recoveredRequest.normalized_intent)
|
|
4218
|
+
&& !String(recoveredRequest.root_work_item_id || "").trim()
|
|
3936
4219
|
) {
|
|
3937
4220
|
continue;
|
|
3938
4221
|
}
|
|
@@ -3942,7 +4225,7 @@ async function syncRunnerRequestLedgerForProjectToServer({ normalizedRoute, runt
|
|
|
3942
4225
|
token: runtime.token,
|
|
3943
4226
|
timeoutSeconds: runtime.timeoutSeconds,
|
|
3944
4227
|
actorUserID: runtime.actor?.user_id,
|
|
3945
|
-
request,
|
|
4228
|
+
request: recoveredRequest,
|
|
3946
4229
|
});
|
|
3947
4230
|
}
|
|
3948
4231
|
for (const commentState of commentStates) {
|
|
@@ -5321,9 +5604,6 @@ function formatTelegramInboundArchiveComment(normalized) {
|
|
|
5321
5604
|
if (normalized.mentionUsernames.length > 0) {
|
|
5322
5605
|
headerLines.push(`mention_usernames: ${normalized.mentionUsernames.map((item) => `@${item}`).join(", ")}`);
|
|
5323
5606
|
}
|
|
5324
|
-
if (normalized.messageThreadID) {
|
|
5325
|
-
headerLines.push(`telegram_topic_id: ${normalized.messageThreadID}`);
|
|
5326
|
-
}
|
|
5327
5607
|
if (normalized.replyToMessageID > 0) {
|
|
5328
5608
|
headerLines.push(`reply_to_message_id: ${normalized.replyToMessageID}`);
|
|
5329
5609
|
headerLines.push(`reply_to_sender_is_bot: ${normalized.replyToFromIsBot ? "true" : "false"}`);
|
|
@@ -6076,6 +6356,29 @@ function buildRunnerSmallTalkReply({ route, executionPlan } = {}) {
|
|
|
6076
6356
|
return templatePool[stableTextModulo(`${displayName}:${roleProfileName}`, templatePool.length)];
|
|
6077
6357
|
}
|
|
6078
6358
|
|
|
6359
|
+
function buildInformationalMiniExecutionOverride({ route, executionPlan } = {}) {
|
|
6360
|
+
const safeRoute = safeObject(route);
|
|
6361
|
+
const safeExecutionPlan = safeObject(executionPlan);
|
|
6362
|
+
const roleProfileName = normalizeRunnerRoleProfileName(
|
|
6363
|
+
safeExecutionPlan.roleProfileName || safeRoute.roleProfile || safeRoute.role || "monitor",
|
|
6364
|
+
) || "monitor";
|
|
6365
|
+
const lightweightModel = String(
|
|
6366
|
+
resolveResponderAdjudicatorModelDisplayName({ client: "gpt", env: process.env }) || "gpt-5.3-codex-spark",
|
|
6367
|
+
).trim() || "gpt-5.3-codex-spark";
|
|
6368
|
+
return {
|
|
6369
|
+
mode: "role_profile",
|
|
6370
|
+
role_profile_name: roleProfileName,
|
|
6371
|
+
role_profile: normalizeRunnerRoleProfile(roleProfileName, {
|
|
6372
|
+
client: "gpt",
|
|
6373
|
+
model: lightweightModel,
|
|
6374
|
+
permission_mode: "read_only",
|
|
6375
|
+
reasoning_effort: "low",
|
|
6376
|
+
}),
|
|
6377
|
+
workspace_dir: String(safeExecutionPlan.workspaceDir || safeRoute.workspaceDir || "").trim(),
|
|
6378
|
+
workspace_source: String(safeExecutionPlan.workspaceSource || "").trim(),
|
|
6379
|
+
};
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6079
6382
|
function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
6080
6383
|
const entry = safeObject(entryRaw);
|
|
6081
6384
|
return {
|
|
@@ -6301,11 +6604,21 @@ async function resolveInformationalQueryReply({
|
|
|
6301
6604
|
}) {
|
|
6302
6605
|
const normalizedIntentType = String(intentType || "").trim();
|
|
6303
6606
|
const messageText = String(safeObject(selectedRecord?.parsedArchive).body || "").trim();
|
|
6607
|
+
const executionOverride = buildInformationalMiniExecutionOverride({ route, executionPlan });
|
|
6304
6608
|
if (normalizedIntentType === "small_talk") {
|
|
6305
6609
|
return {
|
|
6306
6610
|
handled: true,
|
|
6307
|
-
|
|
6611
|
+
response_mode: "lookup_only",
|
|
6612
|
+
reply: "",
|
|
6308
6613
|
source: "small_talk",
|
|
6614
|
+
lookup: {
|
|
6615
|
+
intent_type: "small_talk",
|
|
6616
|
+
user_message: messageText,
|
|
6617
|
+
bot_name: firstNonEmptyString([route?.botName, route?.serverBotName, route?.server_bot_name, route?.name]),
|
|
6618
|
+
bot_role: String(route?.role || route?.roleProfile || "").trim(),
|
|
6619
|
+
proposed_summary: buildRunnerSmallTalkReply({ route, executionPlan }),
|
|
6620
|
+
},
|
|
6621
|
+
execution_override: executionOverride,
|
|
6309
6622
|
};
|
|
6310
6623
|
}
|
|
6311
6624
|
if (false && normalizedIntentType === "small_talk") {
|
|
@@ -6323,9 +6636,14 @@ async function resolveInformationalQueryReply({
|
|
|
6323
6636
|
: "현재 이 프로젝트는 project-workspaces.json에 로컬 작업 폴더가 바인딩되어 있지 않습니다.";
|
|
6324
6637
|
return {
|
|
6325
6638
|
handled: true,
|
|
6326
|
-
|
|
6639
|
+
response_mode: "lookup_only",
|
|
6640
|
+
reply: "",
|
|
6327
6641
|
source: "project.workspace",
|
|
6328
|
-
lookup:
|
|
6642
|
+
lookup: {
|
|
6643
|
+
...workspace,
|
|
6644
|
+
proposed_summary: reply,
|
|
6645
|
+
},
|
|
6646
|
+
execution_override: executionOverride,
|
|
6329
6647
|
};
|
|
6330
6648
|
}
|
|
6331
6649
|
if (normalizedIntentType === "bot_role_query") {
|
|
@@ -6337,9 +6655,14 @@ async function resolveInformationalQueryReply({
|
|
|
6337
6655
|
});
|
|
6338
6656
|
return {
|
|
6339
6657
|
handled: true,
|
|
6340
|
-
|
|
6658
|
+
response_mode: "lookup_only",
|
|
6659
|
+
reply: "",
|
|
6341
6660
|
source: "project.bot_roles",
|
|
6342
|
-
lookup:
|
|
6661
|
+
lookup: {
|
|
6662
|
+
...payload,
|
|
6663
|
+
proposed_summary: buildProjectBotRolesText(payload),
|
|
6664
|
+
},
|
|
6665
|
+
execution_override: executionOverride,
|
|
6343
6666
|
};
|
|
6344
6667
|
}
|
|
6345
6668
|
if (normalizedIntentType === "status_query") {
|
|
@@ -6402,9 +6725,14 @@ async function resolveInformationalQueryReply({
|
|
|
6402
6725
|
});
|
|
6403
6726
|
return {
|
|
6404
6727
|
handled: true,
|
|
6405
|
-
|
|
6728
|
+
response_mode: "lookup_only",
|
|
6729
|
+
reply: "",
|
|
6406
6730
|
source: "project.file.locate",
|
|
6407
|
-
lookup:
|
|
6731
|
+
lookup: {
|
|
6732
|
+
...payload,
|
|
6733
|
+
proposed_summary: buildProjectFileLocateText(payload),
|
|
6734
|
+
},
|
|
6735
|
+
execution_override: executionOverride,
|
|
6408
6736
|
};
|
|
6409
6737
|
}
|
|
6410
6738
|
return null;
|
|
@@ -7032,6 +7360,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7032
7360
|
selectedRecord,
|
|
7033
7361
|
selectedBotUsernames: selectedResponderSelectors,
|
|
7034
7362
|
runtime,
|
|
7363
|
+
archiveThreadID: archiveThread.threadID,
|
|
7035
7364
|
});
|
|
7036
7365
|
};
|
|
7037
7366
|
if (deferExecution) {
|
|
@@ -14080,6 +14409,44 @@ async function runSelftest(flags = {}) {
|
|
|
14080
14409
|
&& monitorGreeting !== reviewGreeting,
|
|
14081
14410
|
`monitor=${monitorGreeting} review=${reviewGreeting}`,
|
|
14082
14411
|
);
|
|
14412
|
+
const informationalMiniReply = await resolveInformationalQueryReply({
|
|
14413
|
+
intentType: "small_talk",
|
|
14414
|
+
route: { botName: "RyoAI_bot", role: "monitor", roleProfile: "monitor" },
|
|
14415
|
+
routeState: {},
|
|
14416
|
+
selectedRecord: {
|
|
14417
|
+
parsedArchive: {
|
|
14418
|
+
body: "@RyoAI_bot 하이",
|
|
14419
|
+
},
|
|
14420
|
+
},
|
|
14421
|
+
runtime: {},
|
|
14422
|
+
executionPlan: {
|
|
14423
|
+
mode: "role_profile",
|
|
14424
|
+
roleProfileName: "monitor",
|
|
14425
|
+
roleProfile: {
|
|
14426
|
+
client: "gpt",
|
|
14427
|
+
model: "gpt-5.4",
|
|
14428
|
+
permissionMode: "workspace_write",
|
|
14429
|
+
reasoningEffort: "medium",
|
|
14430
|
+
},
|
|
14431
|
+
workspaceDir: "C:\\selftest-workspace",
|
|
14432
|
+
workspaceSource: "selftest",
|
|
14433
|
+
},
|
|
14434
|
+
});
|
|
14435
|
+
const expectedInformationalModel = resolveResponderAdjudicatorModelDisplayName({
|
|
14436
|
+
client: "gpt",
|
|
14437
|
+
env: process.env,
|
|
14438
|
+
});
|
|
14439
|
+
push(
|
|
14440
|
+
"small_talk_reply_uses_lookup_only_mini_override",
|
|
14441
|
+
safeObject(informationalMiniReply).handled === true
|
|
14442
|
+
&& String(safeObject(informationalMiniReply).response_mode || "") === "lookup_only"
|
|
14443
|
+
&& String(safeObject(informationalMiniReply).reply || "") === ""
|
|
14444
|
+
&& String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.client || "") === "gpt"
|
|
14445
|
+
&& String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.model || "") === String(expectedInformationalModel || "")
|
|
14446
|
+
&& String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.permissionMode || "") === "read_only"
|
|
14447
|
+
&& String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.reasoningEffort || "") === "low",
|
|
14448
|
+
JSON.stringify(safeObject(informationalMiniReply).execution_override || {}),
|
|
14449
|
+
);
|
|
14083
14450
|
const telegramEnvV2Parsed = parseSimpleEnvText(`
|
|
14084
14451
|
TELEGRAM_API_BASE_URL=http://127.0.0.1:8999/api
|
|
14085
14452
|
TELEGRAM_AUTO_CLEAR_WEBHOOK=false
|
package/lib/runner-data.mjs
CHANGED
|
@@ -827,6 +827,7 @@ export async function discoverArchiveThreadForDestination(
|
|
|
827
827
|
const providerEnvConfig = requireDependency(deps, "providerEnvConfig");
|
|
828
828
|
const parseArchivedChatComment = requireDependency(deps, "parseArchivedChatComment");
|
|
829
829
|
const providerLabel = providerEnvConfig(provider).label;
|
|
830
|
+
const hasConfiguredArchiveWorkItem = archiveWorkItemID && isUUID(archiveWorkItemID);
|
|
830
831
|
|
|
831
832
|
if (archiveThreadID && isUUID(archiveThreadID)) {
|
|
832
833
|
return {
|
|
@@ -837,8 +838,7 @@ export async function discoverArchiveThreadForDestination(
|
|
|
837
838
|
}
|
|
838
839
|
|
|
839
840
|
const candidateWorkItemIDs = [];
|
|
840
|
-
|
|
841
|
-
if (archiveWorkItemID && isUUID(archiveWorkItemID)) {
|
|
841
|
+
if (hasConfiguredArchiveWorkItem) {
|
|
842
842
|
candidateWorkItemIDs.push(archiveWorkItemID);
|
|
843
843
|
} else {
|
|
844
844
|
const workItems = await listProjectWorkItems({
|
|
@@ -864,11 +864,9 @@ export async function discoverArchiveThreadForDestination(
|
|
|
864
864
|
token,
|
|
865
865
|
timeoutSeconds,
|
|
866
866
|
}, deps);
|
|
867
|
-
let fallbackThreadID = "";
|
|
868
867
|
for (const thread of threads) {
|
|
869
868
|
const threadID = String(thread.id || "").trim();
|
|
870
869
|
if (!threadID) continue;
|
|
871
|
-
if (!fallbackThreadID) fallbackThreadID = threadID;
|
|
872
870
|
const comments = await listThreadComments({
|
|
873
871
|
siteBaseURL,
|
|
874
872
|
threadID,
|
|
@@ -889,15 +887,8 @@ export async function discoverArchiveThreadForDestination(
|
|
|
889
887
|
};
|
|
890
888
|
}
|
|
891
889
|
}
|
|
892
|
-
if (
|
|
893
|
-
|
|
894
|
-
threadID: fallbackThreadID,
|
|
895
|
-
workItemID,
|
|
896
|
-
source: "fallback-thread",
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
if (!reusableWorkItemID) {
|
|
900
|
-
reusableWorkItemID = workItemID;
|
|
890
|
+
if (hasConfiguredArchiveWorkItem) {
|
|
891
|
+
break;
|
|
901
892
|
}
|
|
902
893
|
}
|
|
903
894
|
|
|
@@ -907,7 +898,9 @@ export async function discoverArchiveThreadForDestination(
|
|
|
907
898
|
const archiveDescription = `Auto-created archive work item for ${providerLabel} destination ${destinationLabel}.`;
|
|
908
899
|
try {
|
|
909
900
|
const threadBody = `Auto-created archive thread for ${providerLabel} destination ${destinationLabel} (chat_id=${destination.chatID}).`;
|
|
910
|
-
let resolvedWorkItemID =
|
|
901
|
+
let resolvedWorkItemID = hasConfiguredArchiveWorkItem
|
|
902
|
+
? String(archiveWorkItemID || "").trim()
|
|
903
|
+
: "";
|
|
911
904
|
if (!resolvedWorkItemID) {
|
|
912
905
|
const createdWorkItem = await createProjectWorkItem(
|
|
913
906
|
{
|
|
@@ -941,7 +934,9 @@ export async function discoverArchiveThreadForDestination(
|
|
|
941
934
|
return {
|
|
942
935
|
threadID: createdThreadID,
|
|
943
936
|
workItemID: resolvedWorkItemID,
|
|
944
|
-
source:
|
|
937
|
+
source: hasConfiguredArchiveWorkItem
|
|
938
|
+
? "auto-created-thread-on-configured-work-item"
|
|
939
|
+
: "auto-created-thread",
|
|
945
940
|
};
|
|
946
941
|
}
|
|
947
942
|
}
|
|
@@ -653,6 +653,62 @@ function isInformationalHumanIntentType(intentType) {
|
|
|
653
653
|
].includes(normalizeHumanIntentType(intentType));
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
function resolveInformationalQueryExecutionPlan(executionPlan, directInformationalReply, route) {
|
|
657
|
+
const basePlan = safeObject(executionPlan);
|
|
658
|
+
const override = safeObject(safeObject(directInformationalReply).execution_override);
|
|
659
|
+
const overrideRoleProfile = safeObject(override.role_profile || override.roleProfile);
|
|
660
|
+
if (String(override.mode || "").trim().toLowerCase() !== "role_profile") {
|
|
661
|
+
return basePlan;
|
|
662
|
+
}
|
|
663
|
+
if (!String(overrideRoleProfile.client || "").trim()) {
|
|
664
|
+
return basePlan;
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
...basePlan,
|
|
668
|
+
mode: "role_profile",
|
|
669
|
+
roleProfileName: String(
|
|
670
|
+
override.role_profile_name
|
|
671
|
+
|| override.roleProfileName
|
|
672
|
+
|| basePlan.roleProfileName
|
|
673
|
+
|| route?.roleProfile
|
|
674
|
+
|| route?.role
|
|
675
|
+
|| "",
|
|
676
|
+
).trim(),
|
|
677
|
+
roleProfile: {
|
|
678
|
+
...safeObject(basePlan.roleProfile),
|
|
679
|
+
client: String(overrideRoleProfile.client || safeObject(basePlan.roleProfile).client || "").trim(),
|
|
680
|
+
model: String(overrideRoleProfile.model || safeObject(basePlan.roleProfile).model || "").trim(),
|
|
681
|
+
permissionMode: String(
|
|
682
|
+
overrideRoleProfile.permission_mode
|
|
683
|
+
|| overrideRoleProfile.permissionMode
|
|
684
|
+
|| safeObject(basePlan.roleProfile).permissionMode
|
|
685
|
+
|| "",
|
|
686
|
+
).trim(),
|
|
687
|
+
reasoningEffort: String(
|
|
688
|
+
overrideRoleProfile.reasoning_effort
|
|
689
|
+
|| overrideRoleProfile.reasoningEffort
|
|
690
|
+
|| safeObject(basePlan.roleProfile).reasoningEffort
|
|
691
|
+
|| "",
|
|
692
|
+
).trim(),
|
|
693
|
+
},
|
|
694
|
+
workspaceDir: String(
|
|
695
|
+
override.workspace_dir
|
|
696
|
+
|| override.workspaceDir
|
|
697
|
+
|| basePlan.workspaceDir
|
|
698
|
+
|| route?.workspaceDir
|
|
699
|
+
|| "",
|
|
700
|
+
).trim(),
|
|
701
|
+
workspaceSource: String(
|
|
702
|
+
override.workspace_source
|
|
703
|
+
|| override.workspaceSource
|
|
704
|
+
|| basePlan.workspaceSource
|
|
705
|
+
|| "",
|
|
706
|
+
).trim(),
|
|
707
|
+
usedCommandFallback: false,
|
|
708
|
+
fallbackReason: "",
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
656
712
|
function buildHumanIntentContextWindowOptions(intentType) {
|
|
657
713
|
const normalizedIntentType = normalizeHumanIntentType(intentType);
|
|
658
714
|
if (normalizedIntentType === "small_talk") {
|
|
@@ -3168,7 +3224,7 @@ async function resolvePublicConversationContext({
|
|
|
3168
3224
|
managedMentions,
|
|
3169
3225
|
peerMap,
|
|
3170
3226
|
deps,
|
|
3171
|
-
executionPlan,
|
|
3227
|
+
executionPlan: effectiveExecutionPlan,
|
|
3172
3228
|
});
|
|
3173
3229
|
const participantSelectors = uniqueOrdered(ensureArray(humanIntent.participantSelectors));
|
|
3174
3230
|
const initialResponderSelectors = uniqueOrdered(
|
|
@@ -4263,6 +4319,11 @@ export async function processRunnerSelectedRecord({
|
|
|
4263
4319
|
};
|
|
4264
4320
|
}
|
|
4265
4321
|
}
|
|
4322
|
+
const effectiveExecutionPlan = resolveInformationalQueryExecutionPlan(
|
|
4323
|
+
executionPlan,
|
|
4324
|
+
directInformationalReply,
|
|
4325
|
+
normalizedRoute,
|
|
4326
|
+
);
|
|
4266
4327
|
const aiPayload = buildRunnerInputPayload({
|
|
4267
4328
|
route: normalizedRoute,
|
|
4268
4329
|
bot: {
|
|
@@ -4277,7 +4338,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4277
4338
|
selectedRecord,
|
|
4278
4339
|
contextWindow,
|
|
4279
4340
|
projectContextItems,
|
|
4280
|
-
executionPlan,
|
|
4341
|
+
executionPlan: effectiveExecutionPlan,
|
|
4281
4342
|
serializedTriggerPolicy: serializeRunnerTriggerPolicy(normalizedRoute.triggerPolicy),
|
|
4282
4343
|
serializedArchivePolicy: serializeRunnerArchivePolicy(normalizedRoute.archivePolicy),
|
|
4283
4344
|
triggerDecision: effectiveTriggerDecision,
|
|
@@ -4402,7 +4463,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4402
4463
|
bot,
|
|
4403
4464
|
destination,
|
|
4404
4465
|
archiveThread,
|
|
4405
|
-
executionPlan,
|
|
4466
|
+
executionPlan: effectiveExecutionPlan,
|
|
4406
4467
|
runtime,
|
|
4407
4468
|
executionDeps,
|
|
4408
4469
|
triggerDecision: effectiveTriggerDecision,
|
|
@@ -4426,7 +4487,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4426
4487
|
inputPayload: aiPayload,
|
|
4427
4488
|
route: normalizedRoute,
|
|
4428
4489
|
destination,
|
|
4429
|
-
executionPlan,
|
|
4490
|
+
executionPlan: effectiveExecutionPlan,
|
|
4430
4491
|
deps: executionDeps,
|
|
4431
4492
|
});
|
|
4432
4493
|
}
|
|
@@ -4482,12 +4543,12 @@ export async function processRunnerSelectedRecord({
|
|
|
4482
4543
|
|
|
4483
4544
|
const artifactValidation = artifactValidationOverride && Object.keys(artifactValidationOverride).length > 0
|
|
4484
4545
|
? artifactValidationOverride
|
|
4485
|
-
: validateWorkspaceArtifacts && String(
|
|
4546
|
+
: validateWorkspaceArtifacts && String(effectiveExecutionPlan.workspaceDir || "").trim()
|
|
4486
4547
|
? validateWorkspaceArtifacts(
|
|
4487
4548
|
ensureArray(aiResult?.artifacts),
|
|
4488
|
-
|
|
4549
|
+
effectiveExecutionPlan.workspaceDir,
|
|
4489
4550
|
{
|
|
4490
|
-
permissionMode: String(
|
|
4551
|
+
permissionMode: String(effectiveExecutionPlan.roleProfile?.permissionMode || "workspace_write").trim() || "workspace_write",
|
|
4491
4552
|
},
|
|
4492
4553
|
)
|
|
4493
4554
|
: {
|
|
@@ -4520,7 +4581,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4520
4581
|
last_artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
|
|
4521
4582
|
last_artifact_errors: artifactErrors,
|
|
4522
4583
|
last_boundary_violations: boundaryViolations,
|
|
4523
|
-
last_workspace_dir: String(
|
|
4584
|
+
last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
|
|
4524
4585
|
...intentStatePatch,
|
|
4525
4586
|
}),
|
|
4526
4587
|
);
|
|
@@ -4541,8 +4602,8 @@ export async function processRunnerSelectedRecord({
|
|
|
4541
4602
|
conversation_lead_bot: String(conversationContext?.leadBotUsername || "").trim(),
|
|
4542
4603
|
conversation_summary_bot: String(conversationContext?.summaryBotUsername || "").trim(),
|
|
4543
4604
|
conversation_allowed_responders: ensureArray(conversationContext?.allowedResponderSelectors),
|
|
4544
|
-
execution_mode:
|
|
4545
|
-
role_profile:
|
|
4605
|
+
execution_mode: effectiveExecutionPlan.mode,
|
|
4606
|
+
role_profile: effectiveExecutionPlan.roleProfileName,
|
|
4546
4607
|
artifact_validation: String(artifactValidation.status || "").trim() || "policy_violation",
|
|
4547
4608
|
artifact_paths: summarizeValidatedArtifactPaths(artifactValidation),
|
|
4548
4609
|
artifact_errors: artifactErrors,
|
|
@@ -4615,7 +4676,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4615
4676
|
botReplyText: String(aiResult.reply || "").trim(),
|
|
4616
4677
|
currentBotUsername: String(bot?.username || bot?.name || "").trim(),
|
|
4617
4678
|
managedBots: buildConversationParticipantViews(Array.from(auditPeerMap.keys()), auditPeerMap),
|
|
4618
|
-
workspaceDir: String(
|
|
4679
|
+
workspaceDir: String(effectiveExecutionPlan.workspaceDir || process.cwd()).trim() || process.cwd(),
|
|
4619
4680
|
});
|
|
4620
4681
|
if (audit?.requires_actionable_contract === true && audit?.reply_satisfies_request !== true) {
|
|
4621
4682
|
requiresActionableContract = true;
|
|
@@ -4661,7 +4722,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4661
4722
|
inputPayload: forcedContractPayload,
|
|
4662
4723
|
route: normalizedRoute,
|
|
4663
4724
|
destination,
|
|
4664
|
-
executionPlan,
|
|
4725
|
+
executionPlan: effectiveExecutionPlan,
|
|
4665
4726
|
deps: executionDeps,
|
|
4666
4727
|
});
|
|
4667
4728
|
executionContract = conversationContext?.mode === "public_multi_bot"
|
|
@@ -4727,7 +4788,7 @@ export async function processRunnerSelectedRecord({
|
|
|
4727
4788
|
last_reason: reason,
|
|
4728
4789
|
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
4729
4790
|
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
4730
|
-
last_workspace_dir: String(
|
|
4791
|
+
last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
|
|
4731
4792
|
...intentStatePatch,
|
|
4732
4793
|
}),
|
|
4733
4794
|
);
|
|
@@ -5022,7 +5083,7 @@ export async function processRunnerSelectedRecord({
|
|
|
5022
5083
|
? "error"
|
|
5023
5084
|
: "",
|
|
5024
5085
|
last_context_suggestion_error: String(projectContextSuggestion?.error || "").trim(),
|
|
5025
|
-
last_workspace_dir: String(
|
|
5086
|
+
last_workspace_dir: String(effectiveExecutionPlan.workspaceDir || "").trim(),
|
|
5026
5087
|
last_execution_plan: executedRolePlan && Object.keys(executedRolePlan).length > 0 ? executedRolePlan : undefined,
|
|
5027
5088
|
...intentStatePatch,
|
|
5028
5089
|
}),
|
|
@@ -5036,7 +5097,7 @@ export async function processRunnerSelectedRecord({
|
|
|
5036
5097
|
route_name: normalizedRoute.name,
|
|
5037
5098
|
outcome: deliveryResult.delivery.dryRun ? "dry_run" : "replied",
|
|
5038
5099
|
detail: [
|
|
5039
|
-
`${deliveryResult.delivery.dryRun ? "dry-run prepared" : "replied"} as ${bot.name || bot.id}${
|
|
5100
|
+
`${deliveryResult.delivery.dryRun ? "dry-run prepared" : "replied"} as ${bot.name || bot.id}${effectiveExecutionPlan.usedCommandFallback ? " (legacy command fallback)" : ""}`,
|
|
5040
5101
|
effectiveTriggerDecision?.trigger ? `trigger=${String(effectiveTriggerDecision.trigger || "").trim()}` : "",
|
|
5041
5102
|
effectiveConversationContext?.mode === "public_multi_bot"
|
|
5042
5103
|
? [
|
|
@@ -5082,8 +5143,8 @@ export async function processRunnerSelectedRecord({
|
|
|
5082
5143
|
evidence_ids: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).id || "").trim()).filter(Boolean),
|
|
5083
5144
|
evidence_paths: ensureArray(aiResult?.evidenceItems).map((item) => String(safeObject(item).path || "").trim()).filter(Boolean),
|
|
5084
5145
|
reply_chars: String(sanitizedReplyText || "").length,
|
|
5085
|
-
execution_mode:
|
|
5086
|
-
role_profile:
|
|
5146
|
+
execution_mode: effectiveExecutionPlan.mode,
|
|
5147
|
+
role_profile: effectiveExecutionPlan.roleProfileName,
|
|
5087
5148
|
executed_role_plan: executedRolePlan && Object.keys(executedRolePlan).length > 0 ? executedRolePlan : undefined,
|
|
5088
5149
|
archive_status: deliveryResult.archive?.dry_run
|
|
5089
5150
|
? "dry_run"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import http from "node:http";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import process from "node:process";
|
|
@@ -22,6 +23,67 @@ function ensureArray(value) {
|
|
|
22
23
|
return Array.isArray(value) ? value : [];
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
async function startReplyChainSelftestServer({
|
|
27
|
+
projectID,
|
|
28
|
+
threadID,
|
|
29
|
+
comments = [],
|
|
30
|
+
}) {
|
|
31
|
+
const state = {
|
|
32
|
+
comments: ensureArray(comments).slice(),
|
|
33
|
+
};
|
|
34
|
+
const writeJSON = (res, statusCode, payload) => {
|
|
35
|
+
const body = `${JSON.stringify(payload)}\n`;
|
|
36
|
+
res.writeHead(statusCode, {
|
|
37
|
+
"content-type": "application/json",
|
|
38
|
+
"content-length": Buffer.byteLength(body),
|
|
39
|
+
connection: "close",
|
|
40
|
+
});
|
|
41
|
+
res.end(body);
|
|
42
|
+
};
|
|
43
|
+
const server = http.createServer((req, res) => {
|
|
44
|
+
const requestURL = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`);
|
|
45
|
+
const pathname = requestURL.pathname;
|
|
46
|
+
if (req.method === "GET" && pathname === `/api/v1/projects/${encodeURIComponent(projectID)}/runner-requests`) {
|
|
47
|
+
writeJSON(res, 200, []);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (req.method === "GET" && pathname === `/api/v1/projects/${encodeURIComponent(projectID)}/runner-request-comments`) {
|
|
51
|
+
writeJSON(res, 200, []);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (req.method === "GET" && pathname === `/api/v1/threads/${encodeURIComponent(threadID)}/comments`) {
|
|
55
|
+
writeJSON(res, 200, state.comments);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
writeJSON(res, 404, { error: "not_found" });
|
|
59
|
+
});
|
|
60
|
+
await new Promise((resolve, reject) => {
|
|
61
|
+
server.listen(0, "127.0.0.1", (error) => {
|
|
62
|
+
if (error) {
|
|
63
|
+
reject(error);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
resolve();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const address = server.address();
|
|
70
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
71
|
+
return {
|
|
72
|
+
baseURL: `http://127.0.0.1:${port}`,
|
|
73
|
+
async close() {
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
server.close((error) => {
|
|
76
|
+
if (error) {
|
|
77
|
+
reject(error);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
25
87
|
export async function runSelftestRunnerScenarios(push, deps) {
|
|
26
88
|
const selftestProjectID = String(requireValue(deps, "selftestProjectID"));
|
|
27
89
|
const allowLegacyRunnerCommandEnvKey = String(requireValue(deps, "allowLegacyRunnerCommandEnvKey"));
|
|
@@ -569,8 +631,11 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
569
631
|
},
|
|
570
632
|
postJSONWithAuthHeaders: async (url, _timeoutSeconds, _token, payload) => {
|
|
571
633
|
createdCalls.push({ url, payload });
|
|
572
|
-
if (url.
|
|
573
|
-
return JSON.stringify({
|
|
634
|
+
if (url.endsWith("/api/v1/workitems")) {
|
|
635
|
+
return JSON.stringify({ work_item_id: "workitem-new-1" });
|
|
636
|
+
}
|
|
637
|
+
if (url.includes("/api/v1/workitems/workitem-new-1/threads")) {
|
|
638
|
+
return JSON.stringify({ id: "thread-new-1" });
|
|
574
639
|
}
|
|
575
640
|
throw new Error(`unexpected url ${url}`);
|
|
576
641
|
},
|
|
@@ -585,16 +650,72 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
585
650
|
},
|
|
586
651
|
);
|
|
587
652
|
push(
|
|
588
|
-
"
|
|
589
|
-
archiveThread.threadID === "thread-
|
|
590
|
-
&& archiveThread.workItemID === "
|
|
591
|
-
&& archiveThread.source === "auto-created-thread
|
|
653
|
+
"archive_thread_does_not_reuse_title_only_work_item_without_chat_match",
|
|
654
|
+
archiveThread.threadID === "thread-new-1"
|
|
655
|
+
&& archiveThread.workItemID === "workitem-new-1"
|
|
656
|
+
&& archiveThread.source === "auto-created-thread"
|
|
592
657
|
&& workItemListHeaders[0]?.["X-Actor-User-Id"] === "99999999-9999-9999-9999-999999999999"
|
|
593
|
-
&& createdCalls.length ===
|
|
658
|
+
&& createdCalls.length === 2,
|
|
594
659
|
`thread=${archiveThread.threadID} work_item=${archiveThread.workItemID} created_calls=${createdCalls.length} actor_header=${String(workItemListHeaders[0]?.["X-Actor-User-Id"] || "")}`,
|
|
595
660
|
);
|
|
596
661
|
} catch (err) {
|
|
597
|
-
push("
|
|
662
|
+
push("archive_thread_does_not_reuse_title_only_work_item_without_chat_match", false, String(err?.message || err));
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
try {
|
|
666
|
+
const createdCalls = [];
|
|
667
|
+
const archiveThread = await discoverArchiveThreadForDestination(
|
|
668
|
+
{
|
|
669
|
+
siteBaseURL: "https://example.test",
|
|
670
|
+
projectID: selftestProjectID,
|
|
671
|
+
provider: "telegram",
|
|
672
|
+
destination: {
|
|
673
|
+
id: "dest-1",
|
|
674
|
+
provider: "telegram",
|
|
675
|
+
label: "Main Room",
|
|
676
|
+
chatID: "-1001",
|
|
677
|
+
isActive: true,
|
|
678
|
+
},
|
|
679
|
+
token: "test-token",
|
|
680
|
+
timeoutSeconds: 10,
|
|
681
|
+
actorUserID: "99999999-9999-9999-9999-999999999999",
|
|
682
|
+
archiveWorkItemID: "12345678-1234-1234-1234-1234567890ab",
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
getJSONWithAuth: async () => [],
|
|
686
|
+
getJSONWithAuthHeaders: async (url) => {
|
|
687
|
+
if (url.includes("/api/v1/workitems/12345678-1234-1234-1234-1234567890ab/threads")) {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
return [];
|
|
691
|
+
},
|
|
692
|
+
postJSONWithAuthHeaders: async (url, _timeoutSeconds, _token, payload) => {
|
|
693
|
+
createdCalls.push({ url, payload });
|
|
694
|
+
if (url.includes("/api/v1/workitems/12345678-1234-1234-1234-1234567890ab/threads")) {
|
|
695
|
+
return JSON.stringify({ id: "thread-configured-1" });
|
|
696
|
+
}
|
|
697
|
+
throw new Error(`unexpected url ${url}`);
|
|
698
|
+
},
|
|
699
|
+
parseJSONText: JSON.parse,
|
|
700
|
+
normalizeChatDestination: (value) => value,
|
|
701
|
+
normalizeBotProvider: (value) => value,
|
|
702
|
+
normalizeBotRole: (value) => value,
|
|
703
|
+
normalizeRunnerRoute: (value) => value,
|
|
704
|
+
providerEnvConfig: () => ({ label: "Telegram" }),
|
|
705
|
+
parseArchivedChatComment: () => null,
|
|
706
|
+
isUUID: (value) => /^[0-9a-f-]{36}$/i.test(String(value || "")),
|
|
707
|
+
},
|
|
708
|
+
);
|
|
709
|
+
push(
|
|
710
|
+
"archive_thread_reuses_only_configured_archive_work_item_without_chat_match",
|
|
711
|
+
archiveThread.threadID === "thread-configured-1"
|
|
712
|
+
&& archiveThread.workItemID === "12345678-1234-1234-1234-1234567890ab"
|
|
713
|
+
&& archiveThread.source === "auto-created-thread-on-configured-work-item"
|
|
714
|
+
&& createdCalls.length === 1,
|
|
715
|
+
`thread=${archiveThread.threadID} work_item=${archiveThread.workItemID} created_calls=${createdCalls.length}`,
|
|
716
|
+
);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
push("archive_thread_reuses_only_configured_archive_work_item_without_chat_match", false, String(err?.message || err));
|
|
598
719
|
}
|
|
599
720
|
|
|
600
721
|
const aliasMaps = buildToolAliasMaps([
|
|
@@ -1716,6 +1837,103 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
1716
1837
|
`conversation=${String(replyChainStatusLoopLookup.resolved_conversation_id || "(none)")} source_message=${String(safeObject(replyChainStatusLoopLookup.related_request).source_message_id || "(none)")} intent=${String(safeObject(replyChainStatusLoopLookup.related_request).normalized_intent || "(none)")} root_work_item=${String(safeObject(replyChainStatusLoopLookup.root_work_item).id || "(none)")}`,
|
|
1717
1838
|
);
|
|
1718
1839
|
|
|
1840
|
+
const multihopConversationID = "reply_chain:telegram:-100123:701";
|
|
1841
|
+
const multihopThreadID = "reply-chain-thread-701";
|
|
1842
|
+
const multihopBotReplyBody = formatBotReplyArchiveComment({
|
|
1843
|
+
provider: "telegram",
|
|
1844
|
+
bot: {
|
|
1845
|
+
id: "bot-ryoai",
|
|
1846
|
+
name: "RyoAI_bot",
|
|
1847
|
+
username: "RyoAI_bot",
|
|
1848
|
+
role: "monitor",
|
|
1849
|
+
},
|
|
1850
|
+
destination: {
|
|
1851
|
+
chatID: "-100123",
|
|
1852
|
+
label: "Main Room",
|
|
1853
|
+
},
|
|
1854
|
+
replyText: "Still working on the original task.",
|
|
1855
|
+
messageID: 702,
|
|
1856
|
+
replyToMessageID: 701,
|
|
1857
|
+
});
|
|
1858
|
+
const multihopServer = await startReplyChainSelftestServer({
|
|
1859
|
+
projectID: selftestProjectID,
|
|
1860
|
+
threadID: multihopThreadID,
|
|
1861
|
+
comments: [
|
|
1862
|
+
{
|
|
1863
|
+
id: "comment-bot-reply-702",
|
|
1864
|
+
body: multihopBotReplyBody,
|
|
1865
|
+
},
|
|
1866
|
+
],
|
|
1867
|
+
});
|
|
1868
|
+
try {
|
|
1869
|
+
saveBotRunnerState({
|
|
1870
|
+
routes: {},
|
|
1871
|
+
sharedInboxes: {},
|
|
1872
|
+
excludedComments: {},
|
|
1873
|
+
requests: {
|
|
1874
|
+
"request-key-701": {
|
|
1875
|
+
request_key: "request-key-701",
|
|
1876
|
+
project_id: selftestProjectID,
|
|
1877
|
+
provider: "telegram",
|
|
1878
|
+
chat_id: "-100123",
|
|
1879
|
+
source_message_id: 701,
|
|
1880
|
+
conversation_id: "",
|
|
1881
|
+
selected_bot_usernames: ["ryoai_bot"],
|
|
1882
|
+
normalized_intent: "ctxpack_mutation",
|
|
1883
|
+
status: "running",
|
|
1884
|
+
claimed_by_route: requestRouteKey,
|
|
1885
|
+
root_work_item_id: "root-work-item-701",
|
|
1886
|
+
root_work_item_title: "Root reply-chain task",
|
|
1887
|
+
root_work_item_status: "doing",
|
|
1888
|
+
root_thread_id: "root-thread-701",
|
|
1889
|
+
updated_at: "2026-03-22T02:03:00.000Z",
|
|
1890
|
+
},
|
|
1891
|
+
},
|
|
1892
|
+
consumedComments: {},
|
|
1893
|
+
});
|
|
1894
|
+
const multihopReplyClaim = await claimRunnerRequestForHumanComment({
|
|
1895
|
+
normalizedRoute: requestRoute,
|
|
1896
|
+
routeKey: requestRouteKey,
|
|
1897
|
+
selectedRecord: {
|
|
1898
|
+
id: "comment-request-root-task-5",
|
|
1899
|
+
createdAt: "2026-03-22T00:07:00.000Z",
|
|
1900
|
+
updatedAt: "2026-03-22T00:07:00.000Z",
|
|
1901
|
+
parsedArchive: {
|
|
1902
|
+
kind: "telegram_message",
|
|
1903
|
+
chatID: "-100123",
|
|
1904
|
+
chatType: "supergroup",
|
|
1905
|
+
body: "@RyoAI_bot is the original task done now?",
|
|
1906
|
+
messageID: 704,
|
|
1907
|
+
replyToMessageID: 702,
|
|
1908
|
+
senderIsBot: false,
|
|
1909
|
+
mentionUsernames: ["ryoai_bot"],
|
|
1910
|
+
},
|
|
1911
|
+
},
|
|
1912
|
+
selectedBotUsernames: ["ryoai_bot"],
|
|
1913
|
+
normalizedIntent: "status_query",
|
|
1914
|
+
runtime: {
|
|
1915
|
+
baseURL: multihopServer.baseURL,
|
|
1916
|
+
token: "selftest-token",
|
|
1917
|
+
timeoutSeconds: 5,
|
|
1918
|
+
actor: { user_id: "selftest-user" },
|
|
1919
|
+
},
|
|
1920
|
+
archiveThreadID: multihopThreadID,
|
|
1921
|
+
});
|
|
1922
|
+
const multihopClaimedRequest = safeObject(
|
|
1923
|
+
safeObject(loadBotRunnerState().requests)[multihopReplyClaim.requestKey],
|
|
1924
|
+
);
|
|
1925
|
+
push(
|
|
1926
|
+
"runner_request_claim_resolves_multihop_reply_chain_anchor",
|
|
1927
|
+
multihopReplyClaim.ok === true
|
|
1928
|
+
&& String(multihopClaimedRequest.conversation_id || "") === multihopConversationID
|
|
1929
|
+
&& String(multihopClaimedRequest.root_work_item_id || "") === "root-work-item-701"
|
|
1930
|
+
&& String(multihopClaimedRequest.root_thread_id || "") === "root-thread-701",
|
|
1931
|
+
`conversation=${String(multihopClaimedRequest.conversation_id || "(none)")} root_work_item=${String(multihopClaimedRequest.root_work_item_id || "(none)")} root_thread=${String(multihopClaimedRequest.root_thread_id || "(none)")}`,
|
|
1932
|
+
);
|
|
1933
|
+
} finally {
|
|
1934
|
+
await multihopServer.close();
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1719
1937
|
const stateBeforeCleanup = loadBotRunnerState();
|
|
1720
1938
|
saveBotRunnerState({
|
|
1721
1939
|
...stateBeforeCleanup,
|
|
@@ -8401,6 +8619,159 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
8401
8619
|
push("small_talk_direct_mention_skips_dynamic_execution_plan", false, String(err?.message || err));
|
|
8402
8620
|
}
|
|
8403
8621
|
|
|
8622
|
+
try {
|
|
8623
|
+
let capturedExecutionPlan = null;
|
|
8624
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-small-talk-mini-"));
|
|
8625
|
+
const processed = await processRunnerSelectedRecord({
|
|
8626
|
+
routeKey: "small-talk-mini-execution-key",
|
|
8627
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
8628
|
+
name: "telegram-monitor-small-talk-mini",
|
|
8629
|
+
project_id: selftestProjectID,
|
|
8630
|
+
provider: "telegram",
|
|
8631
|
+
role: "monitor",
|
|
8632
|
+
role_profile: "monitor",
|
|
8633
|
+
destination_id: "dest-1",
|
|
8634
|
+
destination_label: "Main Room",
|
|
8635
|
+
server_bot_name: "RyoAI_bot",
|
|
8636
|
+
server_bot_id: "bot-1",
|
|
8637
|
+
trigger_policy: {
|
|
8638
|
+
mentions_only: true,
|
|
8639
|
+
direct_messages: true,
|
|
8640
|
+
reply_to_bot_messages: true,
|
|
8641
|
+
},
|
|
8642
|
+
archive_policy: {
|
|
8643
|
+
mirror_replies: true,
|
|
8644
|
+
dedupe_inbound: true,
|
|
8645
|
+
dedupe_outbound: true,
|
|
8646
|
+
skip_bot_messages: true,
|
|
8647
|
+
},
|
|
8648
|
+
dry_run_delivery: true,
|
|
8649
|
+
}),
|
|
8650
|
+
selectedRecord: {
|
|
8651
|
+
id: "comment-small-talk-mini-execution",
|
|
8652
|
+
createdAt: "2026-03-24T00:11:00.000Z",
|
|
8653
|
+
parsedArchive: {
|
|
8654
|
+
kind: "telegram_message",
|
|
8655
|
+
chatID: "-100123",
|
|
8656
|
+
chatType: "supergroup",
|
|
8657
|
+
body: "@RyoAI_bot 하이",
|
|
8658
|
+
messageID: 212,
|
|
8659
|
+
sender: "human",
|
|
8660
|
+
senderIsBot: false,
|
|
8661
|
+
mentionUsernames: ["ryoai_bot"],
|
|
8662
|
+
},
|
|
8663
|
+
},
|
|
8664
|
+
pendingOrdered: [],
|
|
8665
|
+
bot: {
|
|
8666
|
+
id: "bot-1",
|
|
8667
|
+
name: "RyoAI_bot",
|
|
8668
|
+
username: "RyoAI_bot",
|
|
8669
|
+
role: "monitor",
|
|
8670
|
+
provider: "telegram",
|
|
8671
|
+
},
|
|
8672
|
+
destination: {
|
|
8673
|
+
id: "dest-1",
|
|
8674
|
+
label: "Main Room",
|
|
8675
|
+
provider: "telegram",
|
|
8676
|
+
chatID: "-100123",
|
|
8677
|
+
},
|
|
8678
|
+
archiveThread: {
|
|
8679
|
+
threadID: "thread-1",
|
|
8680
|
+
workItemID: "work-item-1",
|
|
8681
|
+
},
|
|
8682
|
+
executionPlan: {
|
|
8683
|
+
mode: "role_profile",
|
|
8684
|
+
roleProfileName: "monitor",
|
|
8685
|
+
roleProfile: {
|
|
8686
|
+
client: "gpt",
|
|
8687
|
+
model: "gpt-5.4",
|
|
8688
|
+
permissionMode: "workspace_write",
|
|
8689
|
+
reasoningEffort: "medium",
|
|
8690
|
+
},
|
|
8691
|
+
workspaceDir,
|
|
8692
|
+
workspaceSource: "selftest",
|
|
8693
|
+
usedCommandFallback: false,
|
|
8694
|
+
},
|
|
8695
|
+
runtime: {
|
|
8696
|
+
baseURL: "https://example.test",
|
|
8697
|
+
token: "selftest-token",
|
|
8698
|
+
timeoutSeconds: 30,
|
|
8699
|
+
actor: { user_id: "user-1" },
|
|
8700
|
+
},
|
|
8701
|
+
deps: {
|
|
8702
|
+
saveRunnerRouteState: () => {},
|
|
8703
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
8704
|
+
runRunnerAIExecution: async ({ executionPlan }) => {
|
|
8705
|
+
capturedExecutionPlan = safeObject(executionPlan);
|
|
8706
|
+
return {
|
|
8707
|
+
skip: false,
|
|
8708
|
+
reply: "안녕하세요.",
|
|
8709
|
+
replyToMessageID: 212,
|
|
8710
|
+
};
|
|
8711
|
+
},
|
|
8712
|
+
performLocalBotDelivery: async () => ({
|
|
8713
|
+
delivery: { dryRun: true, body: {} },
|
|
8714
|
+
archive: {},
|
|
8715
|
+
}),
|
|
8716
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
8717
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
8718
|
+
buildRunnerExecutionDeps: () => ({
|
|
8719
|
+
resolveInformationalQueryReply: async () => ({
|
|
8720
|
+
handled: true,
|
|
8721
|
+
response_mode: "lookup_only",
|
|
8722
|
+
reply: "",
|
|
8723
|
+
source: "small_talk",
|
|
8724
|
+
lookup: { intent_type: "small_talk" },
|
|
8725
|
+
execution_override: {
|
|
8726
|
+
mode: "role_profile",
|
|
8727
|
+
role_profile_name: "monitor",
|
|
8728
|
+
role_profile: {
|
|
8729
|
+
client: "gpt",
|
|
8730
|
+
model: "gpt-5-mini",
|
|
8731
|
+
permissionMode: "read_only",
|
|
8732
|
+
reasoningEffort: "low",
|
|
8733
|
+
},
|
|
8734
|
+
workspace_dir: workspaceDir,
|
|
8735
|
+
workspace_source: "selftest",
|
|
8736
|
+
},
|
|
8737
|
+
}),
|
|
8738
|
+
planRoleExecutionWithAI: async () => ({
|
|
8739
|
+
requiresExecution: true,
|
|
8740
|
+
summaryRole: "worker",
|
|
8741
|
+
steps: [{ role: "worker", goal: "unexpected", artifactsRequired: true }],
|
|
8742
|
+
}),
|
|
8743
|
+
resolveRunnerExecutionPlanForRole: () => ({
|
|
8744
|
+
mode: "role_profile",
|
|
8745
|
+
roleProfileName: "worker",
|
|
8746
|
+
roleProfile: {
|
|
8747
|
+
client: "sample",
|
|
8748
|
+
model: "",
|
|
8749
|
+
permissionMode: "workspace_write",
|
|
8750
|
+
reasoningEffort: "medium",
|
|
8751
|
+
},
|
|
8752
|
+
workspaceDir,
|
|
8753
|
+
workspaceSource: "selftest",
|
|
8754
|
+
usedCommandFallback: false,
|
|
8755
|
+
}),
|
|
8756
|
+
}),
|
|
8757
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
8758
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
8759
|
+
resolveConversationPeerBots: () => [],
|
|
8760
|
+
},
|
|
8761
|
+
});
|
|
8762
|
+
push(
|
|
8763
|
+
"small_talk_lookup_only_uses_mini_execution_plan",
|
|
8764
|
+
processed.kind === "replied"
|
|
8765
|
+
&& String(safeObject(capturedExecutionPlan).roleProfile?.client || "") === "gpt"
|
|
8766
|
+
&& ["gpt-5-mini", "gpt-5.3-codex-spark"].includes(String(safeObject(capturedExecutionPlan).roleProfile?.model || ""))
|
|
8767
|
+
&& String(safeObject(capturedExecutionPlan).roleProfile?.permissionMode || "") === "read_only"
|
|
8768
|
+
&& String(safeObject(capturedExecutionPlan).roleProfile?.reasoningEffort || "") === "low",
|
|
8769
|
+
JSON.stringify(capturedExecutionPlan || {}),
|
|
8770
|
+
);
|
|
8771
|
+
} catch (err) {
|
|
8772
|
+
push("small_talk_lookup_only_uses_mini_execution_plan", false, String(err?.message || err));
|
|
8773
|
+
}
|
|
8774
|
+
|
|
8404
8775
|
try {
|
|
8405
8776
|
let aiCalls = 0;
|
|
8406
8777
|
let capturedInputPayload = null;
|
|
@@ -868,6 +868,57 @@ export async function runSelftestTelegramE2E(push, deps) {
|
|
|
868
868
|
`count=${telegramE2EServer.state.comments.filter((item) => String(item.body || "").includes("message_id: 353")).length}`,
|
|
869
869
|
);
|
|
870
870
|
|
|
871
|
+
telegramE2EServer.state.comments = [];
|
|
872
|
+
telegramE2EServer.state.updates = [
|
|
873
|
+
{
|
|
874
|
+
update_id: 354,
|
|
875
|
+
message: {
|
|
876
|
+
message_id: 354,
|
|
877
|
+
message_thread_id: 999,
|
|
878
|
+
date: Math.floor(Date.now() / 1000),
|
|
879
|
+
chat: {
|
|
880
|
+
id: Number(e2eDestination.chat_id),
|
|
881
|
+
type: "supergroup",
|
|
882
|
+
title: e2eDestination.label,
|
|
883
|
+
},
|
|
884
|
+
from: {
|
|
885
|
+
id: 5001,
|
|
886
|
+
is_bot: false,
|
|
887
|
+
first_name: "Operator",
|
|
888
|
+
username: "operator_user",
|
|
889
|
+
},
|
|
890
|
+
text: "topic metadata should not be archived",
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
];
|
|
894
|
+
await archiveLocalTelegramMessagesForRoute({
|
|
895
|
+
routeKey: pruneRouteKey,
|
|
896
|
+
route: pruneRoute,
|
|
897
|
+
routeState: safeObject(loadBotRunnerState().routes[pruneRouteKey]),
|
|
898
|
+
runtime: {
|
|
899
|
+
baseURL: telegramE2EServer.baseURL,
|
|
900
|
+
timeoutSeconds: 10,
|
|
901
|
+
token: e2eToken,
|
|
902
|
+
actor: {
|
|
903
|
+
user_id: e2eActorUserID,
|
|
904
|
+
},
|
|
905
|
+
},
|
|
906
|
+
bot: e2eBot,
|
|
907
|
+
destination: {
|
|
908
|
+
chatID: e2eDestination.chat_id,
|
|
909
|
+
},
|
|
910
|
+
archiveThread: {
|
|
911
|
+
threadID: e2eThreadID,
|
|
912
|
+
},
|
|
913
|
+
deps: buildRunnerRuntimeDeps(),
|
|
914
|
+
});
|
|
915
|
+
const topicArchiveBody = String(safeObject(telegramE2EServer.state.comments[0]).body || "");
|
|
916
|
+
push(
|
|
917
|
+
"telegram_inbound_archive_omits_unused_topic_metadata",
|
|
918
|
+
topicArchiveBody.includes("message_id: 354") && !topicArchiveBody.includes("telegram_topic_id:"),
|
|
919
|
+
topicArchiveBody,
|
|
920
|
+
);
|
|
921
|
+
|
|
871
922
|
telegramE2EServer.state.comments = [];
|
|
872
923
|
telegramE2EServer.state.updates = [
|
|
873
924
|
{
|