metheus-governance-mcp-cli 0.2.228 → 0.2.230
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.
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import https from "node:https";
|
|
3
3
|
|
|
4
|
+
function sleep(ms) {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isTransientGatewayTransportError(err) {
|
|
9
|
+
const code = String(err?.code || "").trim().toUpperCase();
|
|
10
|
+
const text = String(err?.message || err || "").toLowerCase();
|
|
11
|
+
if ([
|
|
12
|
+
"ECONNRESET",
|
|
13
|
+
"EPIPE",
|
|
14
|
+
"ETIMEDOUT",
|
|
15
|
+
"ECONNABORTED",
|
|
16
|
+
"EAI_AGAIN",
|
|
17
|
+
].includes(code)) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return [
|
|
21
|
+
"timeout",
|
|
22
|
+
"socket hang up",
|
|
23
|
+
"bad record mac",
|
|
24
|
+
"decryption failed",
|
|
25
|
+
"tls_get_more_records",
|
|
26
|
+
].some((item) => text.includes(item));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function withTransientGatewayRetry(operation) {
|
|
30
|
+
const maxAttempts = 3;
|
|
31
|
+
let lastError = null;
|
|
32
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
33
|
+
try {
|
|
34
|
+
return await Promise.resolve(operation(attempt));
|
|
35
|
+
} catch (err) {
|
|
36
|
+
lastError = err;
|
|
37
|
+
if (attempt >= maxAttempts || !isTransientGatewayTransportError(err)) {
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
await sleep(250 * attempt);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw lastError || new Error("gateway transport failed");
|
|
44
|
+
}
|
|
45
|
+
|
|
4
46
|
function safeObject(value) {
|
|
5
47
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
6
48
|
return value;
|
|
@@ -32,7 +74,7 @@ export function parseToolEnvelopeFromRPCResult(resultObj, deps) {
|
|
|
32
74
|
}
|
|
33
75
|
|
|
34
76
|
export async function postJSON(urlText, timeoutSeconds, token, payload) {
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
77
|
+
return withTransientGatewayRetry(() => new Promise((resolve, reject) => {
|
|
36
78
|
const url = new URL(urlText);
|
|
37
79
|
const body = Buffer.from(JSON.stringify(payload));
|
|
38
80
|
const transport = url.protocol === "http:" ? http : https;
|
|
@@ -74,11 +116,11 @@ export async function postJSON(urlText, timeoutSeconds, token, payload) {
|
|
|
74
116
|
req.on("error", reject);
|
|
75
117
|
req.write(body);
|
|
76
118
|
req.end();
|
|
77
|
-
});
|
|
119
|
+
}));
|
|
78
120
|
}
|
|
79
121
|
|
|
80
122
|
export function getJSONWithAuthHeaders(urlText, timeoutSeconds, token, extraHeaders = {}) {
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
123
|
+
return withTransientGatewayRetry(() => new Promise((resolve, reject) => {
|
|
82
124
|
const url = new URL(urlText);
|
|
83
125
|
const transport = url.protocol === "http:" ? http : https;
|
|
84
126
|
const req = transport.request(
|
|
@@ -121,7 +163,7 @@ export function getJSONWithAuthHeaders(urlText, timeoutSeconds, token, extraHead
|
|
|
121
163
|
});
|
|
122
164
|
req.on("error", reject);
|
|
123
165
|
req.end();
|
|
124
|
-
});
|
|
166
|
+
}));
|
|
125
167
|
}
|
|
126
168
|
|
|
127
169
|
export function getJSONWithAuth(urlText, timeoutSeconds, token) {
|
|
@@ -2158,8 +2158,11 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2158
2158
|
lines.push(
|
|
2159
2159
|
"As an expected follow-up responder, contribute the assigned perspective or delegated result now.",
|
|
2160
2160
|
"A valid follow-up may be a concise opinion, analysis, review, comparison, synthesis, or direct delegated result.",
|
|
2161
|
+
"Do not ask the human to restate the project goal, current status, or blockers before contributing your assigned perspective.",
|
|
2162
|
+
"Use the current room context and execution contract first. If context is limited, state your assumptions explicitly and still provide the best concise contribution you can now.",
|
|
2161
2163
|
"Mentioning the lead bot for acknowledgment or handoff does not require a new delegation contract.",
|
|
2162
2164
|
"Do not wake any third bot. Only the lead bot may delegate new public work in this conversation.",
|
|
2165
|
+
"Do not create a new delegation contract unless the current contract explicitly authorizes this bot to hand work onward.",
|
|
2163
2166
|
"If any other expected responder besides this bot still remains in the current execution contract, do not emit contract.type=\"summary_request\" yet.",
|
|
2164
2167
|
"Only the final remaining contributor may hand control back to the designated summary bot with contract.type=\"summary_request\".",
|
|
2165
2168
|
);
|
|
@@ -5411,36 +5411,9 @@ export async function processRunnerSelectedRecord({
|
|
|
5411
5411
|
},
|
|
5412
5412
|
};
|
|
5413
5413
|
}
|
|
5414
|
-
if (
|
|
5415
|
-
conversationContext?.mode === "public_multi_bot"
|
|
5416
|
-
&& String(conversationContext.intentMode || "").trim() === "delegated_single_lead"
|
|
5417
|
-
&& currentBotSelector
|
|
5418
|
-
&& currentBotSelector !== normalizeMentionSelector(conversationContext.leadBotUsername)
|
|
5419
|
-
&& executionContract?.type === "delegation"
|
|
5420
|
-
) {
|
|
5421
|
-
const reason = "only the delegated lead bot may issue new public assignments";
|
|
5422
|
-
saveRunnerRouteState(
|
|
5423
|
-
routeKey,
|
|
5424
|
-
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5425
|
-
last_action: "conversation_skipped",
|
|
5426
|
-
last_reason: reason,
|
|
5427
|
-
last_conversation_id: String(conversationContext?.id || "").trim(),
|
|
5428
|
-
last_conversation_stage: String(conversationContext?.stage || "").trim(),
|
|
5429
|
-
...intentStatePatch,
|
|
5430
|
-
}),
|
|
5431
|
-
);
|
|
5432
|
-
return {
|
|
5433
|
-
kind: "skipped",
|
|
5434
|
-
skippedRecord: {
|
|
5435
|
-
id: selectedRecord.id,
|
|
5436
|
-
reason,
|
|
5437
|
-
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
5438
|
-
},
|
|
5439
|
-
};
|
|
5440
|
-
}
|
|
5441
5414
|
const replyToMessageID = aiResult.replyToMessageID > 0
|
|
5442
|
-
? aiResult.replyToMessageID
|
|
5443
|
-
: intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0);
|
|
5415
|
+
? aiResult.replyToMessageID
|
|
5416
|
+
: intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0);
|
|
5444
5417
|
const sanitizedReplyText = sanitizeForcedDirectReplyText({
|
|
5445
5418
|
replyText: aiResult.reply,
|
|
5446
5419
|
bot,
|
|
@@ -5476,6 +5449,80 @@ export async function processRunnerSelectedRecord({
|
|
|
5476
5449
|
};
|
|
5477
5450
|
}
|
|
5478
5451
|
}
|
|
5452
|
+
if (
|
|
5453
|
+
responseContractValidation.ok
|
|
5454
|
+
&& effectiveConversationContext?.mode === "public_multi_bot"
|
|
5455
|
+
&& String(effectiveConversationContext.intentMode || "").trim() === "delegated_single_lead"
|
|
5456
|
+
&& currentBotSelector
|
|
5457
|
+
&& currentBotSelector !== normalizeMentionSelector(effectiveConversationContext.leadBotUsername)
|
|
5458
|
+
&& normalizedExecutionContractType === "delegation"
|
|
5459
|
+
) {
|
|
5460
|
+
const unauthorizedDelegationTargets = uniqueOrdered([
|
|
5461
|
+
...ensureArray(executionContract?.assignments)
|
|
5462
|
+
.map((item) => normalizeMentionSelector(item?.targetBot))
|
|
5463
|
+
.filter(Boolean),
|
|
5464
|
+
...collectExecutionContractNextResponders(executionContract)
|
|
5465
|
+
.map((item) => normalizeMentionSelector(item))
|
|
5466
|
+
.filter(Boolean),
|
|
5467
|
+
]).filter((item) => item && item !== currentBotSelector);
|
|
5468
|
+
if (unauthorizedDelegationTargets.length > 0) {
|
|
5469
|
+
responseContractValidation = {
|
|
5470
|
+
ok: false,
|
|
5471
|
+
status: "non_lead_public_assignment",
|
|
5472
|
+
reason: "only the delegated lead bot may issue new public assignments",
|
|
5473
|
+
targets: unauthorizedDelegationTargets,
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
if (!responseContractValidation.ok && String(responseContractValidation.status || "").trim() === "non_lead_public_assignment") {
|
|
5478
|
+
const reason = String(responseContractValidation.reason || "").trim()
|
|
5479
|
+
|| "response contract validation failed";
|
|
5480
|
+
saveRunnerRouteState(
|
|
5481
|
+
routeKey,
|
|
5482
|
+
buildRunnerRouteStateFromComment(selectedRecord, {
|
|
5483
|
+
last_action: "conversation_skipped",
|
|
5484
|
+
last_reason: reason,
|
|
5485
|
+
last_conversation_id: String(effectiveConversationContext?.id || "").trim(),
|
|
5486
|
+
last_conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
|
|
5487
|
+
last_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5488
|
+
last_contract_validation_reason: reason,
|
|
5489
|
+
last_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5490
|
+
...intentStatePatch,
|
|
5491
|
+
}),
|
|
5492
|
+
);
|
|
5493
|
+
return {
|
|
5494
|
+
kind: "skipped",
|
|
5495
|
+
skippedRecord: {
|
|
5496
|
+
id: selectedRecord.id,
|
|
5497
|
+
reason,
|
|
5498
|
+
messageID: intFromRawAllowZero(selectedRecord?.parsedArchive?.messageID, 0),
|
|
5499
|
+
},
|
|
5500
|
+
result: {
|
|
5501
|
+
route_key: routeKey,
|
|
5502
|
+
route_name: normalizedRoute.name,
|
|
5503
|
+
outcome: "contract_validation_failed",
|
|
5504
|
+
detail: reason,
|
|
5505
|
+
thread_id: archiveThread.threadID,
|
|
5506
|
+
comment_id: selectedRecord.id,
|
|
5507
|
+
trigger_kind: String(effectiveTriggerDecision.trigger || "").trim(),
|
|
5508
|
+
conversation_id: String(effectiveConversationContext?.id || "").trim(),
|
|
5509
|
+
conversation_stage: String(effectiveConversationContext?.stage || "").trim(),
|
|
5510
|
+
conversation_intent_mode: String(effectiveConversationContext?.intentMode || "").trim(),
|
|
5511
|
+
conversation_lead_bot: String(effectiveConversationContext?.leadBotUsername || "").trim(),
|
|
5512
|
+
conversation_summary_bot: String(effectiveConversationContext?.summaryBotUsername || "").trim(),
|
|
5513
|
+
execution_contract_type: String(executionContract?.type || "").trim(),
|
|
5514
|
+
execution_contract_actionable: executionContract?.actionable === true,
|
|
5515
|
+
execution_contract_targets: ensureArray(executionContract?.assignments).map((item) => normalizeMentionSelector(item.targetBot)).filter(Boolean),
|
|
5516
|
+
next_expected_responders: collectExecutionContractNextResponders(executionContract),
|
|
5517
|
+
response_contract_validation_status: String(responseContractValidation.status || "").trim(),
|
|
5518
|
+
response_contract_validation_reason: reason,
|
|
5519
|
+
response_contract_validation_targets: ensureArray(responseContractValidation.targets),
|
|
5520
|
+
assignment_validation_status: String(assignmentExecutionValidation.status || "").trim(),
|
|
5521
|
+
assignment_validation_reason: String(assignmentExecutionValidation.reason || "").trim(),
|
|
5522
|
+
assignment_validation_modes: ensureArray(assignmentExecutionValidation.assignmentModes),
|
|
5523
|
+
},
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5479
5526
|
if (effectiveConversationContext?.mode === "public_multi_bot") {
|
|
5480
5527
|
const currentBotSelector = normalizeMentionSelector(bot?.username || bot?.name);
|
|
5481
5528
|
const currentSession = safeObject(effectiveConversationContext.session);
|
|
@@ -9505,12 +9505,291 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
9505
9505
|
&& !processed.skippedRecord,
|
|
9506
9506
|
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
|
|
9507
9507
|
);
|
|
9508
|
-
} catch (err) {
|
|
9509
|
-
push("delegated_single_lead_peer_to_peer_reply_reaches_ai_decision", false, String(err?.message || err));
|
|
9510
|
-
}
|
|
9511
|
-
|
|
9512
|
-
try {
|
|
9513
|
-
|
|
9508
|
+
} catch (err) {
|
|
9509
|
+
push("delegated_single_lead_peer_to_peer_reply_reaches_ai_decision", false, String(err?.message || err));
|
|
9510
|
+
}
|
|
9511
|
+
|
|
9512
|
+
try {
|
|
9513
|
+
let aiCalls = 0;
|
|
9514
|
+
const processed = await processRunnerSelectedRecord({
|
|
9515
|
+
routeKey: "delegated-single-lead-non-lead-summary-request-allowed-key",
|
|
9516
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
9517
|
+
name: "telegram-monitor-delegated-single-lead-non-lead-summary-request-allowed",
|
|
9518
|
+
project_id: selftestProjectID,
|
|
9519
|
+
provider: "telegram",
|
|
9520
|
+
role: "monitor",
|
|
9521
|
+
role_profile: "monitor",
|
|
9522
|
+
destination_id: "dest-1",
|
|
9523
|
+
destination_label: "Main Room",
|
|
9524
|
+
server_bot_name: "RyoAI3_bot",
|
|
9525
|
+
server_bot_id: "bot-peer-2",
|
|
9526
|
+
trigger_policy: {
|
|
9527
|
+
mentions_only: true,
|
|
9528
|
+
direct_messages: true,
|
|
9529
|
+
reply_to_bot_messages: true,
|
|
9530
|
+
},
|
|
9531
|
+
archive_policy: {
|
|
9532
|
+
mirror_replies: true,
|
|
9533
|
+
dedupe_inbound: true,
|
|
9534
|
+
dedupe_outbound: true,
|
|
9535
|
+
skip_bot_messages: true,
|
|
9536
|
+
},
|
|
9537
|
+
dry_run_delivery: true,
|
|
9538
|
+
}),
|
|
9539
|
+
selectedRecord: {
|
|
9540
|
+
id: "comment-delegated-single-lead-non-lead-summary-request-allowed",
|
|
9541
|
+
createdAt: "2026-03-16T00:02:21.000Z",
|
|
9542
|
+
parsedArchive: {
|
|
9543
|
+
kind: "bot_reply",
|
|
9544
|
+
conversationID: "comment-delegated-single-lead-open",
|
|
9545
|
+
conversationMode: "public_multi_bot",
|
|
9546
|
+
conversationStage: "bot_reply",
|
|
9547
|
+
conversationIntentMode: "delegated_single_lead",
|
|
9548
|
+
conversationAllowBotToBot: true,
|
|
9549
|
+
conversationLeadBotUsername: "ryoai_bot",
|
|
9550
|
+
conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9551
|
+
conversationInitialResponders: ["ryoai_bot"],
|
|
9552
|
+
conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9553
|
+
conversationSummaryBotUsername: "ryoai_bot",
|
|
9554
|
+
botUsername: "RyoAI_bot",
|
|
9555
|
+
botName: "RyoAI_bot",
|
|
9556
|
+
sender: "RyoAI_bot",
|
|
9557
|
+
body: "@RyoAI3_bot Review the biggest implementation risk.",
|
|
9558
|
+
mentionUsernames: ["ryoai3_bot"],
|
|
9559
|
+
executionContract: {
|
|
9560
|
+
type: "delegation",
|
|
9561
|
+
actionable: true,
|
|
9562
|
+
assignments: [
|
|
9563
|
+
{ target_bot: "ryoai3_bot", task: "Review the biggest implementation risk.", mode: "conversation_contribution" },
|
|
9564
|
+
],
|
|
9565
|
+
summary_bot: "ryoai_bot",
|
|
9566
|
+
next_responders: ["ryoai3_bot"],
|
|
9567
|
+
},
|
|
9568
|
+
},
|
|
9569
|
+
},
|
|
9570
|
+
pendingOrdered: [],
|
|
9571
|
+
bot: {
|
|
9572
|
+
id: "bot-peer-2",
|
|
9573
|
+
name: "RyoAI3_bot",
|
|
9574
|
+
username: "RyoAI3_bot",
|
|
9575
|
+
role: "monitor",
|
|
9576
|
+
provider: "telegram",
|
|
9577
|
+
},
|
|
9578
|
+
destination: {
|
|
9579
|
+
id: "dest-1",
|
|
9580
|
+
label: "Main Room",
|
|
9581
|
+
provider: "telegram",
|
|
9582
|
+
chatID: "-100123",
|
|
9583
|
+
},
|
|
9584
|
+
archiveThread: {
|
|
9585
|
+
threadID: "thread-1",
|
|
9586
|
+
workItemID: "work-item-1",
|
|
9587
|
+
},
|
|
9588
|
+
executionPlan: {
|
|
9589
|
+
mode: "role_profile",
|
|
9590
|
+
roleProfileName: "monitor",
|
|
9591
|
+
roleProfile: {
|
|
9592
|
+
client: "sample",
|
|
9593
|
+
model: "",
|
|
9594
|
+
permissionMode: "read_only",
|
|
9595
|
+
reasoningEffort: "low",
|
|
9596
|
+
},
|
|
9597
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-non-lead-summary-request-allowed"),
|
|
9598
|
+
workspaceSource: "selftest",
|
|
9599
|
+
usedCommandFallback: false,
|
|
9600
|
+
},
|
|
9601
|
+
runtime: {
|
|
9602
|
+
baseURL: "https://example.test",
|
|
9603
|
+
token: "selftest-token",
|
|
9604
|
+
timeoutSeconds: 30,
|
|
9605
|
+
actor: { user_id: "user-1" },
|
|
9606
|
+
},
|
|
9607
|
+
deps: {
|
|
9608
|
+
saveRunnerRouteState: () => {},
|
|
9609
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
9610
|
+
runRunnerAIExecution: async () => {
|
|
9611
|
+
aiCalls += 1;
|
|
9612
|
+
return {
|
|
9613
|
+
skip: false,
|
|
9614
|
+
reply: "현재 가장 큰 구현 리스크는 요구사항 경계가 아직 불명확하다는 점입니다. @RyoAI_bot 방향 정리를 부탁드립니다.",
|
|
9615
|
+
contract: {
|
|
9616
|
+
type: "summary_request",
|
|
9617
|
+
actionable: true,
|
|
9618
|
+
assignments: [],
|
|
9619
|
+
summary_bot: "ryoai_bot",
|
|
9620
|
+
next_responders: ["ryoai_bot"],
|
|
9621
|
+
},
|
|
9622
|
+
};
|
|
9623
|
+
},
|
|
9624
|
+
performLocalBotDelivery: async () => ({
|
|
9625
|
+
delivery: { dryRun: true, body: {} },
|
|
9626
|
+
archive: {},
|
|
9627
|
+
}),
|
|
9628
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
9629
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
9630
|
+
buildRunnerExecutionDeps: () => ({}),
|
|
9631
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
9632
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
9633
|
+
resolveConversationPeerBots: () => [
|
|
9634
|
+
{ id: "bot-lead-1", name: "RyoAI_bot" },
|
|
9635
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
9636
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
9637
|
+
],
|
|
9638
|
+
},
|
|
9639
|
+
});
|
|
9640
|
+
push(
|
|
9641
|
+
"delegated_single_lead_non_lead_summary_request_allowed",
|
|
9642
|
+
processed.kind === "replied"
|
|
9643
|
+
&& aiCalls === 1
|
|
9644
|
+
&& String(processed.result?.execution_contract_type || "") === "summary_request",
|
|
9645
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contract=${String(processed.result?.execution_contract_type || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
|
|
9646
|
+
);
|
|
9647
|
+
} catch (err) {
|
|
9648
|
+
push("delegated_single_lead_non_lead_summary_request_allowed", false, String(err?.message || err));
|
|
9649
|
+
}
|
|
9650
|
+
|
|
9651
|
+
try {
|
|
9652
|
+
let aiCalls = 0;
|
|
9653
|
+
const processed = await processRunnerSelectedRecord({
|
|
9654
|
+
routeKey: "delegated-single-lead-non-lead-new-assignment-blocked-key",
|
|
9655
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
9656
|
+
name: "telegram-monitor-delegated-single-lead-non-lead-new-assignment-blocked",
|
|
9657
|
+
project_id: selftestProjectID,
|
|
9658
|
+
provider: "telegram",
|
|
9659
|
+
role: "monitor",
|
|
9660
|
+
role_profile: "monitor",
|
|
9661
|
+
destination_id: "dest-1",
|
|
9662
|
+
destination_label: "Main Room",
|
|
9663
|
+
server_bot_name: "RyoAI3_bot",
|
|
9664
|
+
server_bot_id: "bot-peer-2",
|
|
9665
|
+
trigger_policy: {
|
|
9666
|
+
mentions_only: true,
|
|
9667
|
+
direct_messages: true,
|
|
9668
|
+
reply_to_bot_messages: true,
|
|
9669
|
+
},
|
|
9670
|
+
archive_policy: {
|
|
9671
|
+
mirror_replies: true,
|
|
9672
|
+
dedupe_inbound: true,
|
|
9673
|
+
dedupe_outbound: true,
|
|
9674
|
+
skip_bot_messages: true,
|
|
9675
|
+
},
|
|
9676
|
+
dry_run_delivery: true,
|
|
9677
|
+
}),
|
|
9678
|
+
selectedRecord: {
|
|
9679
|
+
id: "comment-delegated-single-lead-non-lead-new-assignment-blocked",
|
|
9680
|
+
createdAt: "2026-03-16T00:02:22.000Z",
|
|
9681
|
+
parsedArchive: {
|
|
9682
|
+
kind: "bot_reply",
|
|
9683
|
+
conversationID: "comment-delegated-single-lead-open",
|
|
9684
|
+
conversationMode: "public_multi_bot",
|
|
9685
|
+
conversationStage: "bot_reply",
|
|
9686
|
+
conversationIntentMode: "delegated_single_lead",
|
|
9687
|
+
conversationAllowBotToBot: true,
|
|
9688
|
+
conversationLeadBotUsername: "ryoai_bot",
|
|
9689
|
+
conversationParticipants: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9690
|
+
conversationInitialResponders: ["ryoai_bot"],
|
|
9691
|
+
conversationAllowedResponders: ["ryoai_bot", "ryoai2_bot", "ryoai3_bot"],
|
|
9692
|
+
conversationSummaryBotUsername: "ryoai_bot",
|
|
9693
|
+
botUsername: "RyoAI_bot",
|
|
9694
|
+
botName: "RyoAI_bot",
|
|
9695
|
+
sender: "RyoAI_bot",
|
|
9696
|
+
body: "@RyoAI3_bot Review the biggest implementation risk.",
|
|
9697
|
+
mentionUsernames: ["ryoai3_bot"],
|
|
9698
|
+
executionContract: {
|
|
9699
|
+
type: "delegation",
|
|
9700
|
+
actionable: true,
|
|
9701
|
+
assignments: [
|
|
9702
|
+
{ target_bot: "ryoai3_bot", task: "Review the biggest implementation risk.", mode: "conversation_contribution" },
|
|
9703
|
+
],
|
|
9704
|
+
summary_bot: "ryoai_bot",
|
|
9705
|
+
next_responders: ["ryoai3_bot"],
|
|
9706
|
+
},
|
|
9707
|
+
},
|
|
9708
|
+
},
|
|
9709
|
+
pendingOrdered: [],
|
|
9710
|
+
bot: {
|
|
9711
|
+
id: "bot-peer-2",
|
|
9712
|
+
name: "RyoAI3_bot",
|
|
9713
|
+
username: "RyoAI3_bot",
|
|
9714
|
+
role: "monitor",
|
|
9715
|
+
provider: "telegram",
|
|
9716
|
+
},
|
|
9717
|
+
destination: {
|
|
9718
|
+
id: "dest-1",
|
|
9719
|
+
label: "Main Room",
|
|
9720
|
+
provider: "telegram",
|
|
9721
|
+
chatID: "-100123",
|
|
9722
|
+
},
|
|
9723
|
+
archiveThread: {
|
|
9724
|
+
threadID: "thread-1",
|
|
9725
|
+
workItemID: "work-item-1",
|
|
9726
|
+
},
|
|
9727
|
+
executionPlan: {
|
|
9728
|
+
mode: "role_profile",
|
|
9729
|
+
roleProfileName: "monitor",
|
|
9730
|
+
roleProfile: {
|
|
9731
|
+
client: "sample",
|
|
9732
|
+
model: "",
|
|
9733
|
+
permissionMode: "read_only",
|
|
9734
|
+
reasoningEffort: "low",
|
|
9735
|
+
},
|
|
9736
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-non-lead-new-assignment-blocked"),
|
|
9737
|
+
workspaceSource: "selftest",
|
|
9738
|
+
usedCommandFallback: false,
|
|
9739
|
+
},
|
|
9740
|
+
runtime: {
|
|
9741
|
+
baseURL: "https://example.test",
|
|
9742
|
+
token: "selftest-token",
|
|
9743
|
+
timeoutSeconds: 30,
|
|
9744
|
+
actor: { user_id: "user-1" },
|
|
9745
|
+
},
|
|
9746
|
+
deps: {
|
|
9747
|
+
saveRunnerRouteState: () => {},
|
|
9748
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
9749
|
+
runRunnerAIExecution: async () => {
|
|
9750
|
+
aiCalls += 1;
|
|
9751
|
+
return {
|
|
9752
|
+
skip: false,
|
|
9753
|
+
reply: "@RyoAI2_bot 추가 검토를 해주세요.",
|
|
9754
|
+
contract: {
|
|
9755
|
+
type: "delegation",
|
|
9756
|
+
actionable: true,
|
|
9757
|
+
assignments: [
|
|
9758
|
+
{ target_bot: "ryoai2_bot", task: "추가 검토를 해주세요.", mode: "conversation_contribution" },
|
|
9759
|
+
],
|
|
9760
|
+
next_responders: ["ryoai2_bot"],
|
|
9761
|
+
},
|
|
9762
|
+
};
|
|
9763
|
+
},
|
|
9764
|
+
performLocalBotDelivery: async () => ({
|
|
9765
|
+
delivery: { dryRun: true, body: {} },
|
|
9766
|
+
archive: {},
|
|
9767
|
+
}),
|
|
9768
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
9769
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
9770
|
+
buildRunnerExecutionDeps: () => ({}),
|
|
9771
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
9772
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
9773
|
+
resolveConversationPeerBots: () => [
|
|
9774
|
+
{ id: "bot-lead-1", name: "RyoAI_bot" },
|
|
9775
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
9776
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
9777
|
+
],
|
|
9778
|
+
},
|
|
9779
|
+
});
|
|
9780
|
+
push(
|
|
9781
|
+
"delegated_single_lead_non_lead_new_assignment_blocked",
|
|
9782
|
+
processed.kind === "skipped"
|
|
9783
|
+
&& aiCalls === 1
|
|
9784
|
+
&& String(processed.result?.response_contract_validation_status || "") === "non_lead_public_assignment",
|
|
9785
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} reason=${String(processed.skippedRecord?.reason || "(none)")}`,
|
|
9786
|
+
);
|
|
9787
|
+
} catch (err) {
|
|
9788
|
+
push("delegated_single_lead_non_lead_new_assignment_blocked", false, String(err?.message || err));
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9791
|
+
try {
|
|
9792
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-artifacts-ok-"));
|
|
9514
9793
|
const artifactPath = path.join(workspaceDir, "docs", "plan.md");
|
|
9515
9794
|
fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
|
|
9516
9795
|
fs.writeFileSync(artifactPath, "# plan\n", "utf8");
|