metheus-governance-mcp-cli 0.2.200 → 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 +298 -41
- 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);
|
|
@@ -5431,9 +5604,6 @@ function formatTelegramInboundArchiveComment(normalized) {
|
|
|
5431
5604
|
if (normalized.mentionUsernames.length > 0) {
|
|
5432
5605
|
headerLines.push(`mention_usernames: ${normalized.mentionUsernames.map((item) => `@${item}`).join(", ")}`);
|
|
5433
5606
|
}
|
|
5434
|
-
if (normalized.messageThreadID) {
|
|
5435
|
-
headerLines.push(`telegram_topic_id: ${normalized.messageThreadID}`);
|
|
5436
|
-
}
|
|
5437
5607
|
if (normalized.replyToMessageID > 0) {
|
|
5438
5608
|
headerLines.push(`reply_to_message_id: ${normalized.replyToMessageID}`);
|
|
5439
5609
|
headerLines.push(`reply_to_sender_is_bot: ${normalized.replyToFromIsBot ? "true" : "false"}`);
|
|
@@ -6186,6 +6356,29 @@ function buildRunnerSmallTalkReply({ route, executionPlan } = {}) {
|
|
|
6186
6356
|
return templatePool[stableTextModulo(`${displayName}:${roleProfileName}`, templatePool.length)];
|
|
6187
6357
|
}
|
|
6188
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
|
+
|
|
6189
6382
|
function summarizeRunnerRequestForStatusLookup(entryRaw) {
|
|
6190
6383
|
const entry = safeObject(entryRaw);
|
|
6191
6384
|
return {
|
|
@@ -6411,11 +6604,21 @@ async function resolveInformationalQueryReply({
|
|
|
6411
6604
|
}) {
|
|
6412
6605
|
const normalizedIntentType = String(intentType || "").trim();
|
|
6413
6606
|
const messageText = String(safeObject(selectedRecord?.parsedArchive).body || "").trim();
|
|
6607
|
+
const executionOverride = buildInformationalMiniExecutionOverride({ route, executionPlan });
|
|
6414
6608
|
if (normalizedIntentType === "small_talk") {
|
|
6415
6609
|
return {
|
|
6416
6610
|
handled: true,
|
|
6417
|
-
|
|
6611
|
+
response_mode: "lookup_only",
|
|
6612
|
+
reply: "",
|
|
6418
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,
|
|
6419
6622
|
};
|
|
6420
6623
|
}
|
|
6421
6624
|
if (false && normalizedIntentType === "small_talk") {
|
|
@@ -6433,9 +6636,14 @@ async function resolveInformationalQueryReply({
|
|
|
6433
6636
|
: "현재 이 프로젝트는 project-workspaces.json에 로컬 작업 폴더가 바인딩되어 있지 않습니다.";
|
|
6434
6637
|
return {
|
|
6435
6638
|
handled: true,
|
|
6436
|
-
|
|
6639
|
+
response_mode: "lookup_only",
|
|
6640
|
+
reply: "",
|
|
6437
6641
|
source: "project.workspace",
|
|
6438
|
-
lookup:
|
|
6642
|
+
lookup: {
|
|
6643
|
+
...workspace,
|
|
6644
|
+
proposed_summary: reply,
|
|
6645
|
+
},
|
|
6646
|
+
execution_override: executionOverride,
|
|
6439
6647
|
};
|
|
6440
6648
|
}
|
|
6441
6649
|
if (normalizedIntentType === "bot_role_query") {
|
|
@@ -6447,9 +6655,14 @@ async function resolveInformationalQueryReply({
|
|
|
6447
6655
|
});
|
|
6448
6656
|
return {
|
|
6449
6657
|
handled: true,
|
|
6450
|
-
|
|
6658
|
+
response_mode: "lookup_only",
|
|
6659
|
+
reply: "",
|
|
6451
6660
|
source: "project.bot_roles",
|
|
6452
|
-
lookup:
|
|
6661
|
+
lookup: {
|
|
6662
|
+
...payload,
|
|
6663
|
+
proposed_summary: buildProjectBotRolesText(payload),
|
|
6664
|
+
},
|
|
6665
|
+
execution_override: executionOverride,
|
|
6453
6666
|
};
|
|
6454
6667
|
}
|
|
6455
6668
|
if (normalizedIntentType === "status_query") {
|
|
@@ -6512,9 +6725,14 @@ async function resolveInformationalQueryReply({
|
|
|
6512
6725
|
});
|
|
6513
6726
|
return {
|
|
6514
6727
|
handled: true,
|
|
6515
|
-
|
|
6728
|
+
response_mode: "lookup_only",
|
|
6729
|
+
reply: "",
|
|
6516
6730
|
source: "project.file.locate",
|
|
6517
|
-
lookup:
|
|
6731
|
+
lookup: {
|
|
6732
|
+
...payload,
|
|
6733
|
+
proposed_summary: buildProjectFileLocateText(payload),
|
|
6734
|
+
},
|
|
6735
|
+
execution_override: executionOverride,
|
|
6518
6736
|
};
|
|
6519
6737
|
}
|
|
6520
6738
|
return null;
|
|
@@ -7142,6 +7360,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
|
|
|
7142
7360
|
selectedRecord,
|
|
7143
7361
|
selectedBotUsernames: selectedResponderSelectors,
|
|
7144
7362
|
runtime,
|
|
7363
|
+
archiveThreadID: archiveThread.threadID,
|
|
7145
7364
|
});
|
|
7146
7365
|
};
|
|
7147
7366
|
if (deferExecution) {
|
|
@@ -14190,6 +14409,44 @@ async function runSelftest(flags = {}) {
|
|
|
14190
14409
|
&& monitorGreeting !== reviewGreeting,
|
|
14191
14410
|
`monitor=${monitorGreeting} review=${reviewGreeting}`,
|
|
14192
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
|
+
);
|
|
14193
14450
|
const telegramEnvV2Parsed = parseSimpleEnvText(`
|
|
14194
14451
|
TELEGRAM_API_BASE_URL=http://127.0.0.1:8999/api
|
|
14195
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
|
{
|