metheus-governance-mcp-cli 0.2.278 → 0.2.279
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.
|
@@ -9,6 +9,14 @@ function ensureArray(value) {
|
|
|
9
9
|
return Array.isArray(value) ? value : [];
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
function intFromRawAllowZero(raw, fallback = 0) {
|
|
13
|
+
if (raw === null || raw === undefined || raw === "") {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
const parsed = Number.parseInt(String(raw).trim(), 10);
|
|
17
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
function uniqueOrdered(values) {
|
|
13
21
|
const seen = new Set();
|
|
14
22
|
const ordered = [];
|
|
@@ -21,6 +29,104 @@ function uniqueOrdered(values) {
|
|
|
21
29
|
return ordered;
|
|
22
30
|
}
|
|
23
31
|
|
|
32
|
+
function normalizeSelectorList(values, normalizeMentionSelector) {
|
|
33
|
+
return uniqueOrdered(
|
|
34
|
+
ensureArray(values)
|
|
35
|
+
.map((item) => normalizeMentionSelector(item))
|
|
36
|
+
.filter(Boolean),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildImplicitPublicMultiBotExecutionContract({
|
|
41
|
+
conversationContext = null,
|
|
42
|
+
executionContract = null,
|
|
43
|
+
currentBotSelector = "",
|
|
44
|
+
normalizeMentionSelector,
|
|
45
|
+
}) {
|
|
46
|
+
const context = safeObject(conversationContext);
|
|
47
|
+
const normalizedCurrentBotSelector = normalizeMentionSelector(currentBotSelector);
|
|
48
|
+
if (
|
|
49
|
+
String(context.mode || "").trim() !== "public_multi_bot"
|
|
50
|
+
|| String(context.intentMode || "").trim() !== "multi_bot_collab"
|
|
51
|
+
|| context.allowBotToBot !== true
|
|
52
|
+
|| !normalizedCurrentBotSelector
|
|
53
|
+
) {
|
|
54
|
+
return safeObject(executionContract);
|
|
55
|
+
}
|
|
56
|
+
const normalizedAllowedResponders = normalizeSelectorList(
|
|
57
|
+
context.allowedResponderSelectors,
|
|
58
|
+
normalizeMentionSelector,
|
|
59
|
+
);
|
|
60
|
+
if (
|
|
61
|
+
normalizedAllowedResponders.length < 2
|
|
62
|
+
|| !normalizedAllowedResponders.includes(normalizedCurrentBotSelector)
|
|
63
|
+
) {
|
|
64
|
+
return safeObject(executionContract);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const explicitContract = safeObject(executionContract);
|
|
68
|
+
const explicitAssignments = normalizeSelectorList(
|
|
69
|
+
ensureArray(explicitContract.assignments).map((item) => (
|
|
70
|
+
safeObject(item).targetBot || safeObject(item).target_bot
|
|
71
|
+
)),
|
|
72
|
+
normalizeMentionSelector,
|
|
73
|
+
).filter((item) => item !== normalizedCurrentBotSelector);
|
|
74
|
+
const explicitNextResponders = normalizeSelectorList(
|
|
75
|
+
explicitContract.nextResponders || explicitContract.next_responders || explicitContract.responders,
|
|
76
|
+
normalizeMentionSelector,
|
|
77
|
+
).filter((item) => item !== normalizedCurrentBotSelector);
|
|
78
|
+
const explicitSummaryBot = normalizeMentionSelector(
|
|
79
|
+
explicitContract.summaryBot || explicitContract.summary_bot || context.summaryBotUsername,
|
|
80
|
+
);
|
|
81
|
+
const explicitType = String(explicitContract.type || "").trim().toLowerCase();
|
|
82
|
+
const hasExplicitFollowup = explicitAssignments.length > 0
|
|
83
|
+
|| explicitNextResponders.length > 0
|
|
84
|
+
|| (explicitType === "summary_request" && explicitSummaryBot && explicitSummaryBot !== normalizedCurrentBotSelector);
|
|
85
|
+
if (hasExplicitFollowup) {
|
|
86
|
+
return explicitContract;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const sessionSpeakerCounts = safeObject(safeObject(context.session).speaker_counts);
|
|
90
|
+
const projectedSpeakerCounts = {
|
|
91
|
+
...sessionSpeakerCounts,
|
|
92
|
+
[normalizedCurrentBotSelector]: intFromRawAllowZero(sessionSpeakerCounts[normalizedCurrentBotSelector], 0) + 1,
|
|
93
|
+
};
|
|
94
|
+
const remainingPeerResponders = normalizedAllowedResponders.filter((selector) => (
|
|
95
|
+
selector
|
|
96
|
+
&& selector !== normalizedCurrentBotSelector
|
|
97
|
+
&& intFromRawAllowZero(projectedSpeakerCounts[selector], 0) <= 0
|
|
98
|
+
));
|
|
99
|
+
if (remainingPeerResponders.length > 0) {
|
|
100
|
+
return {
|
|
101
|
+
type: "delegation",
|
|
102
|
+
actionable: true,
|
|
103
|
+
assignments: remainingPeerResponders.map((targetBot) => ({ targetBot })),
|
|
104
|
+
summaryBot: explicitSummaryBot,
|
|
105
|
+
nextResponders: remainingPeerResponders,
|
|
106
|
+
implicitFollowup: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const stage = String(context.stage || "").trim().toLowerCase();
|
|
111
|
+
if (
|
|
112
|
+
stage === "bot_reply"
|
|
113
|
+
&& explicitSummaryBot
|
|
114
|
+
&& explicitSummaryBot !== normalizedCurrentBotSelector
|
|
115
|
+
&& normalizedAllowedResponders.includes(explicitSummaryBot)
|
|
116
|
+
) {
|
|
117
|
+
return {
|
|
118
|
+
type: "summary_request",
|
|
119
|
+
actionable: true,
|
|
120
|
+
assignments: [],
|
|
121
|
+
summaryBot: explicitSummaryBot,
|
|
122
|
+
nextResponders: [explicitSummaryBot],
|
|
123
|
+
implicitFollowup: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return explicitContract;
|
|
128
|
+
}
|
|
129
|
+
|
|
24
130
|
export async function prepareRunnerSelectedRecordContractContext({
|
|
25
131
|
selectedRecord,
|
|
26
132
|
routeState,
|
|
@@ -64,7 +170,7 @@ export async function prepareRunnerSelectedRecordContractContext({
|
|
|
64
170
|
}
|
|
65
171
|
|
|
66
172
|
const effectiveResponseContractPayload = safeObject(aiPayload?.response_contract);
|
|
67
|
-
const
|
|
173
|
+
const normalizedExecutionContract = conversationContext?.mode === "public_multi_bot"
|
|
68
174
|
? normalizeConversationExecutionContract(
|
|
69
175
|
aiResult?.contract,
|
|
70
176
|
{
|
|
@@ -78,6 +184,12 @@ export async function prepareRunnerSelectedRecordContractContext({
|
|
|
78
184
|
effectiveResponseContractPayload,
|
|
79
185
|
{ currentBotSelector },
|
|
80
186
|
);
|
|
187
|
+
const executionContract = buildImplicitPublicMultiBotExecutionContract({
|
|
188
|
+
conversationContext,
|
|
189
|
+
executionContract: normalizedExecutionContract,
|
|
190
|
+
currentBotSelector,
|
|
191
|
+
normalizeMentionSelector,
|
|
192
|
+
});
|
|
81
193
|
let allowedActionableTypes = new Set(
|
|
82
194
|
ensureArray(effectiveResponseContractPayload.allowed_contract_types)
|
|
83
195
|
.map((item) => String(item || "").trim().toLowerCase())
|
|
@@ -99,7 +211,6 @@ export async function prepareRunnerSelectedRecordContractContext({
|
|
|
99
211
|
&& String(conversationContext?.stage || "").trim() === "human_opening"
|
|
100
212
|
&& conversationContext?.allowBotToBot === true
|
|
101
213
|
&& Boolean(currentBotSelector)
|
|
102
|
-
&& normalizedConversationInitialResponders.length === 1
|
|
103
214
|
&& normalizedConversationInitialResponders.includes(currentBotSelector)
|
|
104
215
|
&& peerAllowedResponders.length > 0;
|
|
105
216
|
const directHumanPeerMap = humanIntentContext?.peerMap instanceof Map
|
|
@@ -2951,6 +2951,50 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
2951
2951
|
`status=${String(continuedRequest?.status || "(none)")}`,
|
|
2952
2952
|
);
|
|
2953
2953
|
|
|
2954
|
+
saveBotRunnerState({
|
|
2955
|
+
routes: {
|
|
2956
|
+
[requestRouteKey]: {},
|
|
2957
|
+
},
|
|
2958
|
+
sharedInboxes: {},
|
|
2959
|
+
excludedComments: {},
|
|
2960
|
+
requests: {
|
|
2961
|
+
"request-key-2c": {
|
|
2962
|
+
request_key: "request-key-2c",
|
|
2963
|
+
project_id: selftestProjectID,
|
|
2964
|
+
provider: "telegram",
|
|
2965
|
+
chat_id: "-100123",
|
|
2966
|
+
source_message_id: 721,
|
|
2967
|
+
conversation_id: "conv-request-2c",
|
|
2968
|
+
status: "running",
|
|
2969
|
+
claimed_by_route: requestRouteKey,
|
|
2970
|
+
},
|
|
2971
|
+
},
|
|
2972
|
+
consumedComments: {},
|
|
2973
|
+
});
|
|
2974
|
+
const continuedByNextExpected = markRunnerRequestLifecycle({
|
|
2975
|
+
normalizedRoute: requestRoute,
|
|
2976
|
+
requestKey: "request-key-2c",
|
|
2977
|
+
selectedRecord: {
|
|
2978
|
+
id: "comment-request-finish-2c",
|
|
2979
|
+
parsedArchive: {
|
|
2980
|
+
kind: "bot_reply",
|
|
2981
|
+
chatID: "-100123",
|
|
2982
|
+
messageID: 722,
|
|
2983
|
+
conversationID: "conv-request-2c",
|
|
2984
|
+
},
|
|
2985
|
+
},
|
|
2986
|
+
routeKey: requestRouteKey,
|
|
2987
|
+
outcome: "replied",
|
|
2988
|
+
currentBotSelector: "@RyoAI_bot",
|
|
2989
|
+
executionContractTargets: [],
|
|
2990
|
+
nextExpectedResponders: ["@RyoAI2_bot", "@RyoAI3_bot"],
|
|
2991
|
+
});
|
|
2992
|
+
push(
|
|
2993
|
+
"runner_request_lifecycle_reply_remains_running_with_next_expected_responders",
|
|
2994
|
+
String(continuedByNextExpected?.status || "") === "running",
|
|
2995
|
+
`status=${String(continuedByNextExpected?.status || "(none)")}`,
|
|
2996
|
+
);
|
|
2997
|
+
|
|
2954
2998
|
saveBotRunnerState({
|
|
2955
2999
|
routes: {
|
|
2956
3000
|
[requestRouteKey]: {},
|
|
@@ -5450,16 +5494,162 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
5450
5494
|
&& String(deliveredConversation[0]?.summaryBotUsername || "") === "ryoai_bot",
|
|
5451
5495
|
`kind=${String(processed.kind || "(none)")} intent=${String(deliveredConversation[0]?.intentMode || "(none)")} summary=${String(deliveredConversation[0]?.summaryBotUsername || "(none)")}`,
|
|
5452
5496
|
);
|
|
5453
|
-
} catch (err) {
|
|
5454
|
-
push("intent_fallback_uses_repeated_managed_mention_for_summary_target", false, String(err?.message || err));
|
|
5455
|
-
}
|
|
5456
|
-
|
|
5457
|
-
try {
|
|
5458
|
-
|
|
5459
|
-
const
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5497
|
+
} catch (err) {
|
|
5498
|
+
push("intent_fallback_uses_repeated_managed_mention_for_summary_target", false, String(err?.message || err));
|
|
5499
|
+
}
|
|
5500
|
+
|
|
5501
|
+
try {
|
|
5502
|
+
let aiCalls = 0;
|
|
5503
|
+
const deliveredConversation = [];
|
|
5504
|
+
const processed = await processRunnerSelectedRecord({
|
|
5505
|
+
routeKey: "multi-bot-collab-human-opening-synthesizes-next-turn-contract-key",
|
|
5506
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
5507
|
+
name: "telegram-monitor-multi-bot-collab-human-opening-synthesizes-next-turn-contract",
|
|
5508
|
+
project_id: selftestProjectID,
|
|
5509
|
+
provider: "telegram",
|
|
5510
|
+
role: "monitor",
|
|
5511
|
+
role_profile: "monitor",
|
|
5512
|
+
destination_id: "dest-1",
|
|
5513
|
+
destination_label: "Main Room",
|
|
5514
|
+
server_bot_name: "RyoAI_bot",
|
|
5515
|
+
server_bot_id: "bot-lead-1",
|
|
5516
|
+
trigger_policy: {
|
|
5517
|
+
mentions_only: true,
|
|
5518
|
+
direct_messages: true,
|
|
5519
|
+
reply_to_bot_messages: true,
|
|
5520
|
+
},
|
|
5521
|
+
archive_policy: {
|
|
5522
|
+
mirror_replies: true,
|
|
5523
|
+
dedupe_inbound: true,
|
|
5524
|
+
dedupe_outbound: true,
|
|
5525
|
+
skip_bot_messages: true,
|
|
5526
|
+
},
|
|
5527
|
+
dry_run_delivery: true,
|
|
5528
|
+
}),
|
|
5529
|
+
selectedRecord: {
|
|
5530
|
+
id: "comment-multi-bot-collab-human-opening",
|
|
5531
|
+
body: "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같이 논의해줘.",
|
|
5532
|
+
threadID: "thread-1",
|
|
5533
|
+
createdAt: "2026-03-17T00:00:00.000Z",
|
|
5534
|
+
updatedAt: "2026-03-17T00:00:00.000Z",
|
|
5535
|
+
parsedArchive: {
|
|
5536
|
+
kind: "telegram_message",
|
|
5537
|
+
body: "@RyoAI_bot @RyoAI2_bot @RyoAI3_bot 같이 논의해줘.",
|
|
5538
|
+
sender: "User",
|
|
5539
|
+
senderName: "User",
|
|
5540
|
+
senderIsBot: false,
|
|
5541
|
+
username: "user1",
|
|
5542
|
+
chatType: "supergroup",
|
|
5543
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
5544
|
+
messageID: 1901,
|
|
5545
|
+
chatID: "-100123",
|
|
5546
|
+
},
|
|
5547
|
+
},
|
|
5548
|
+
routeState: {},
|
|
5549
|
+
pendingOrdered: [],
|
|
5550
|
+
bot: {
|
|
5551
|
+
id: "bot-lead-1",
|
|
5552
|
+
name: "RyoAI_bot",
|
|
5553
|
+
username: "RyoAI_bot",
|
|
5554
|
+
},
|
|
5555
|
+
destination: {
|
|
5556
|
+
id: "dest-1",
|
|
5557
|
+
label: "Main Room",
|
|
5558
|
+
provider: "telegram",
|
|
5559
|
+
chatID: "-100123",
|
|
5560
|
+
},
|
|
5561
|
+
archiveThread: {
|
|
5562
|
+
threadID: "thread-1",
|
|
5563
|
+
workItemID: "work-item-1",
|
|
5564
|
+
},
|
|
5565
|
+
executionPlan: {
|
|
5566
|
+
mode: "role_profile",
|
|
5567
|
+
roleProfileName: "monitor",
|
|
5568
|
+
roleProfile: {
|
|
5569
|
+
client: "sample",
|
|
5570
|
+
model: "",
|
|
5571
|
+
permissionMode: "read_only",
|
|
5572
|
+
reasoningEffort: "low",
|
|
5573
|
+
},
|
|
5574
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-multi-bot-collab-kickoff"),
|
|
5575
|
+
workspaceSource: "selftest",
|
|
5576
|
+
usedCommandFallback: false,
|
|
5577
|
+
},
|
|
5578
|
+
runtime: {
|
|
5579
|
+
baseURL: "https://example.test",
|
|
5580
|
+
token: "selftest-token",
|
|
5581
|
+
timeoutSeconds: 30,
|
|
5582
|
+
actor: { user_id: "user-1" },
|
|
5583
|
+
},
|
|
5584
|
+
deps: {
|
|
5585
|
+
saveRunnerRouteState: () => {},
|
|
5586
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
5587
|
+
runRunnerAIExecution: async () => {
|
|
5588
|
+
aiCalls += 1;
|
|
5589
|
+
return {
|
|
5590
|
+
skip: false,
|
|
5591
|
+
reply: "좋아요. 먼저 제 관점을 짧게 정리하고, 다른 봇 의견도 이어서 받겠습니다.",
|
|
5592
|
+
replyToMessageID: 0,
|
|
5593
|
+
};
|
|
5594
|
+
},
|
|
5595
|
+
performLocalBotDelivery: async ({ archiveConversation, archiveConversationContext, archiveExecutionContract }) => {
|
|
5596
|
+
deliveredConversation.push(buildSelftestArchiveConversation({
|
|
5597
|
+
archiveConversation,
|
|
5598
|
+
archiveConversationContext,
|
|
5599
|
+
archiveExecutionContract,
|
|
5600
|
+
}));
|
|
5601
|
+
return {
|
|
5602
|
+
delivery: { dryRun: true, body: {} },
|
|
5603
|
+
archive: {},
|
|
5604
|
+
};
|
|
5605
|
+
},
|
|
5606
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
5607
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
5608
|
+
buildRunnerExecutionDeps: () => ({
|
|
5609
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
5610
|
+
mode: "multi_bot_collab",
|
|
5611
|
+
intent_type: "explanation_query",
|
|
5612
|
+
lead_bot: "ryoai_bot",
|
|
5613
|
+
participants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
5614
|
+
initial_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
5615
|
+
allowed_responders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
5616
|
+
summary_bot: "ryoai_bot",
|
|
5617
|
+
allow_bot_to_bot: true,
|
|
5618
|
+
reply_expectation: "informational",
|
|
5619
|
+
}),
|
|
5620
|
+
}),
|
|
5621
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
5622
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
5623
|
+
resolveConversationPeerBots: () => [
|
|
5624
|
+
{ id: "bot-lead-1", name: "RyoAI_bot" },
|
|
5625
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
5626
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
5627
|
+
],
|
|
5628
|
+
},
|
|
5629
|
+
});
|
|
5630
|
+
push(
|
|
5631
|
+
"multi_bot_collab_human_opening_synthesizes_shared_next_turn_contract",
|
|
5632
|
+
processed.kind === "replied"
|
|
5633
|
+
&& aiCalls === 1
|
|
5634
|
+
&& String(deliveredConversation[0]?.mode || "") === "public_multi_bot"
|
|
5635
|
+
&& String(deliveredConversation[0]?.executionContract?.type || "") === "delegation"
|
|
5636
|
+
&& ensureArray(deliveredConversation[0]?.executionContract?.nextResponders).includes("ryoai2_bot")
|
|
5637
|
+
&& ensureArray(deliveredConversation[0]?.executionContract?.nextResponders).includes("ryoai3_bot")
|
|
5638
|
+
&& ensureArray(processed.result?.next_expected_responders).includes("ryoai2_bot")
|
|
5639
|
+
&& ensureArray(processed.result?.next_expected_responders).includes("ryoai3_bot")
|
|
5640
|
+
&& String(processed.result?.response_contract_validation_status || "") === "valid",
|
|
5641
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(deliveredConversation[0]?.executionContract?.type || "(none)")} next=${JSON.stringify(processed.result?.next_expected_responders || [])} validation=${String(processed.result?.response_contract_validation_status || "(none)")}`,
|
|
5642
|
+
);
|
|
5643
|
+
} catch (err) {
|
|
5644
|
+
push("multi_bot_collab_human_opening_synthesizes_shared_next_turn_contract", false, String(err?.message || err));
|
|
5645
|
+
}
|
|
5646
|
+
|
|
5647
|
+
try {
|
|
5648
|
+
const deliveredConversation = [];
|
|
5649
|
+
const processed = await processRunnerSelectedRecord({
|
|
5650
|
+
routeKey: "delegated-single-lead-human-open-key",
|
|
5651
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
5652
|
+
name: "telegram-monitor-delegated-single-lead-open",
|
|
5463
5653
|
project_id: selftestProjectID,
|
|
5464
5654
|
provider: "telegram",
|
|
5465
5655
|
role: "monitor",
|