metheus-governance-mcp-cli 0.2.284 → 0.2.285
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 +112 -42
- package/lib/local-ai-adapters.mjs +15 -4
- package/lib/local-project-dispatch.mjs +49 -1
- package/lib/project-context-dedupe.mjs +48 -0
- package/lib/runner-helpers.mjs +1 -3
- package/lib/runner-orchestration-entrypoints.mjs +3 -1
- package/lib/runner-orchestration-selected-record-context.mjs +3 -3
- package/lib/runner-orchestration-selected-record-delivery-context.mjs +6 -0
- package/lib/runner-orchestration.mjs +18 -18
- package/lib/selftest-runner-scenarios.mjs +393 -40
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -2812,25 +2812,23 @@ function buildRunnerReplyChainSpeakerType(parsedArchiveRaw) {
|
|
|
2812
2812
|
return "human";
|
|
2813
2813
|
}
|
|
2814
2814
|
|
|
2815
|
-
function buildRunnerReplyChainSpeakerLabel(parsedArchiveRaw) {
|
|
2816
|
-
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
2817
|
-
const username = normalizeTelegramMentionUsername(
|
|
2818
|
-
parsedArchive.botUsername
|
|
2819
|
-
|| parsedArchive.username
|
|
2820
|
-
|| parsedArchive.
|
|
2821
|
-
|
|
2822
|
-
)
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
parsedArchive.
|
|
2828
|
-
parsedArchive.
|
|
2829
|
-
parsedArchive
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
]);
|
|
2833
|
-
}
|
|
2815
|
+
function buildRunnerReplyChainSpeakerLabel(parsedArchiveRaw) {
|
|
2816
|
+
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
2817
|
+
const username = normalizeTelegramMentionUsername(
|
|
2818
|
+
parsedArchive.botUsername
|
|
2819
|
+
|| parsedArchive.username
|
|
2820
|
+
|| parsedArchive.conversationTargetBotUsername,
|
|
2821
|
+
);
|
|
2822
|
+
if (username) {
|
|
2823
|
+
return `@${username}`;
|
|
2824
|
+
}
|
|
2825
|
+
return firstNonEmptyString([
|
|
2826
|
+
parsedArchive.sender,
|
|
2827
|
+
parsedArchive.botName,
|
|
2828
|
+
parsedArchive.chatTitle,
|
|
2829
|
+
buildRunnerReplyChainSpeakerType(parsedArchive) === "bot" ? "bot" : "human",
|
|
2830
|
+
]);
|
|
2831
|
+
}
|
|
2834
2832
|
|
|
2835
2833
|
function normalizeRunnerReplyChainSnapshot(rawSnapshot) {
|
|
2836
2834
|
const snapshot = safeObject(rawSnapshot);
|
|
@@ -4015,17 +4013,18 @@ function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
|
4015
4013
|
);
|
|
4016
4014
|
}
|
|
4017
4015
|
|
|
4018
|
-
function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
|
|
4019
|
-
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
4020
|
-
return uniqueOrderedStrings(
|
|
4021
|
-
ensureArray(parsedArchive.mentionUsernames).length
|
|
4022
|
-
? parsedArchive.mentionUsernames
|
|
4023
|
-
:
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4016
|
+
function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
|
|
4017
|
+
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
4018
|
+
return uniqueOrderedStrings(
|
|
4019
|
+
ensureArray(parsedArchive.mentionUsernames).length
|
|
4020
|
+
? parsedArchive.mentionUsernames
|
|
4021
|
+
: parsedArchive.replyToSenderIsBot === true
|
|
4022
|
+
&& String(parsedArchive.replyToUsername || "").trim()
|
|
4023
|
+
? [parsedArchive.replyToUsername]
|
|
4024
|
+
: [],
|
|
4025
|
+
normalizeTelegramMentionUsername,
|
|
4026
|
+
);
|
|
4027
|
+
}
|
|
4029
4028
|
|
|
4030
4029
|
function runnerRequestPreferredAIReplyPreview(entryRaw) {
|
|
4031
4030
|
const entry = safeObject(entryRaw);
|
|
@@ -18685,13 +18684,14 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18685
18684
|
workspaceDir: "",
|
|
18686
18685
|
workspaceSignalTrusted: true,
|
|
18687
18686
|
},
|
|
18688
|
-
{
|
|
18689
|
-
...buildLocalProjectDispatchDeps(),
|
|
18690
|
-
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
|
|
18687
|
+
{
|
|
18688
|
+
...buildLocalProjectDispatchDeps(),
|
|
18689
|
+
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18690
|
+
listProjectContextItems: async () => ([]),
|
|
18691
|
+
createProjectContextItem: async (input) => {
|
|
18692
|
+
capturedCreateArgs = input;
|
|
18693
|
+
return {
|
|
18694
|
+
id: "99999999-8888-4777-8666-555555555555",
|
|
18695
18695
|
project_id: input.projectID,
|
|
18696
18696
|
title: input.title,
|
|
18697
18697
|
body: input.body,
|
|
@@ -18715,11 +18715,81 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18715
18715
|
&& String(safeObject(capturedCreateArgs).category || "").trim() === "bot_role",
|
|
18716
18716
|
`status=${String(safeObject(suggestStructured.item).status || "").trim() || "(none)"} category=${String(safeObject(capturedCreateArgs).category || "").trim() || "(none)"}`,
|
|
18717
18717
|
);
|
|
18718
|
-
} catch (err) {
|
|
18719
|
-
push("local_project_context_suggest_tool_dispatches", false, String(err?.message || err));
|
|
18720
|
-
}
|
|
18721
|
-
|
|
18722
|
-
|
|
18718
|
+
} catch (err) {
|
|
18719
|
+
push("local_project_context_suggest_tool_dispatches", false, String(err?.message || err));
|
|
18720
|
+
}
|
|
18721
|
+
|
|
18722
|
+
try {
|
|
18723
|
+
let duplicateCreateCalls = 0;
|
|
18724
|
+
const duplicateSuggestResponse = await handleLocalProjectToolDispatchImpl(
|
|
18725
|
+
{
|
|
18726
|
+
requestObj: {
|
|
18727
|
+
jsonrpc: "2.0",
|
|
18728
|
+
id: 5,
|
|
18729
|
+
method: "tools/call",
|
|
18730
|
+
params: {
|
|
18731
|
+
name: "project.context.suggest",
|
|
18732
|
+
arguments: {
|
|
18733
|
+
project_id: selftestProjectID,
|
|
18734
|
+
title: "Bot role split",
|
|
18735
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18736
|
+
category: "bot_role",
|
|
18737
|
+
importance: "high",
|
|
18738
|
+
},
|
|
18739
|
+
},
|
|
18740
|
+
},
|
|
18741
|
+
toolName: "project.context.suggest",
|
|
18742
|
+
toolArgs: {
|
|
18743
|
+
project_id: selftestProjectID,
|
|
18744
|
+
title: "Bot role split",
|
|
18745
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18746
|
+
category: "bot_role",
|
|
18747
|
+
importance: "high",
|
|
18748
|
+
},
|
|
18749
|
+
args: {
|
|
18750
|
+
baseURL: DEFAULT_SITE_URL,
|
|
18751
|
+
timeoutSeconds: 30,
|
|
18752
|
+
},
|
|
18753
|
+
token: "selftest-token",
|
|
18754
|
+
workspaceDir: "",
|
|
18755
|
+
workspaceSignalTrusted: true,
|
|
18756
|
+
},
|
|
18757
|
+
{
|
|
18758
|
+
...buildLocalProjectDispatchDeps(),
|
|
18759
|
+
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18760
|
+
listProjectContextItems: async () => ([
|
|
18761
|
+
{
|
|
18762
|
+
id: "ctx-duplicate-suggested",
|
|
18763
|
+
project_id: selftestProjectID,
|
|
18764
|
+
title: "Bot role split",
|
|
18765
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18766
|
+
category: "bot_role",
|
|
18767
|
+
importance: "high",
|
|
18768
|
+
status: "suggested",
|
|
18769
|
+
},
|
|
18770
|
+
]),
|
|
18771
|
+
createProjectContextItem: async () => {
|
|
18772
|
+
duplicateCreateCalls += 1;
|
|
18773
|
+
return {};
|
|
18774
|
+
},
|
|
18775
|
+
},
|
|
18776
|
+
);
|
|
18777
|
+
const duplicateSuggestStructured = safeObject(safeObject(duplicateSuggestResponse).result)?.structuredContent || {};
|
|
18778
|
+
push(
|
|
18779
|
+
"local_project_context_suggest_tool_skips_exact_duplicate",
|
|
18780
|
+
duplicateSuggestStructured.ok === true
|
|
18781
|
+
&& duplicateSuggestStructured.created === false
|
|
18782
|
+
&& duplicateSuggestStructured.duplicate === true
|
|
18783
|
+
&& String(duplicateSuggestStructured.reason || "").trim() === "duplicate_suggested"
|
|
18784
|
+
&& duplicateCreateCalls === 0
|
|
18785
|
+
&& String(safeObject(duplicateSuggestStructured.item).id || "").trim() === "ctx-duplicate-suggested",
|
|
18786
|
+
`created=${String(duplicateSuggestStructured.created)} duplicate=${String(duplicateSuggestStructured.duplicate)} reason=${String(duplicateSuggestStructured.reason || "").trim() || "(none)"} create_calls=${duplicateCreateCalls}`,
|
|
18787
|
+
);
|
|
18788
|
+
} catch (err) {
|
|
18789
|
+
push("local_project_context_suggest_tool_skips_exact_duplicate", false, String(err?.message || err));
|
|
18790
|
+
}
|
|
18791
|
+
|
|
18792
|
+
const previousRunnerBotID = process.env.METHEUS_RUNNER_BOT_ID;
|
|
18723
18793
|
const previousRunnerBotName = process.env.METHEUS_RUNNER_BOT_NAME;
|
|
18724
18794
|
try {
|
|
18725
18795
|
process.env.METHEUS_RUNNER_BOT_ID = "bot-selftest-123";
|
|
@@ -1772,11 +1772,12 @@ function importanceRankForProjectContext(value) {
|
|
|
1772
1772
|
|
|
1773
1773
|
function formatProjectContextLine(item) {
|
|
1774
1774
|
const contextItem = safeObject(item);
|
|
1775
|
+
const status = String(contextItem.status || "active").trim() || "active";
|
|
1775
1776
|
const category = String(contextItem.category || "general").trim() || "general";
|
|
1776
1777
|
const importance = String(contextItem.importance || "normal").trim() || "normal";
|
|
1777
1778
|
const title = truncatePromptLineText(contextItem.title || "-", 100) || "-";
|
|
1778
1779
|
const body = truncatePromptLineText(contextItem.body || "(empty)", 240) || "(empty)";
|
|
1779
|
-
return `- [${category}/${importance}] ${title} | ${body}`;
|
|
1780
|
+
return `- [${status}/${category}/${importance}] ${title} | ${body}`;
|
|
1780
1781
|
}
|
|
1781
1782
|
|
|
1782
1783
|
function inferCurrentTurnPurpose({ trigger, conversation, selfBotUsername, otherMentionedBots }) {
|
|
@@ -1815,8 +1816,15 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1815
1816
|
const context = Array.isArray(safePayload.context_comments) ? safePayload.context_comments : [];
|
|
1816
1817
|
const projectContextItems = ensureArray(safePayload.project_context_items)
|
|
1817
1818
|
.map((item) => safeObject(item))
|
|
1818
|
-
.filter((item) => String(item.status || "active").trim().toLowerCase()
|
|
1819
|
+
.filter((item) => ["active", "suggested"].includes(String(item.status || "active").trim().toLowerCase()))
|
|
1819
1820
|
.sort((left, right) => {
|
|
1821
|
+
const leftStatus = String(left.status || "active").trim().toLowerCase();
|
|
1822
|
+
const rightStatus = String(right.status || "active").trim().toLowerCase();
|
|
1823
|
+
const leftStatusRank = leftStatus === "active" ? 0 : 1;
|
|
1824
|
+
const rightStatusRank = rightStatus === "active" ? 0 : 1;
|
|
1825
|
+
if (leftStatusRank !== rightStatusRank) {
|
|
1826
|
+
return leftStatusRank - rightStatusRank;
|
|
1827
|
+
}
|
|
1820
1828
|
const importanceDelta = importanceRankForProjectContext(right.importance) - importanceRankForProjectContext(left.importance);
|
|
1821
1829
|
if (importanceDelta !== 0) return importanceDelta;
|
|
1822
1830
|
const leftUpdated = Date.parse(firstNonEmptyString([left.updated_at, left.updatedAt, left.created_at, left.createdAt]));
|
|
@@ -1994,11 +2002,13 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
1994
2002
|
`- Workspace: ${workspaceDir || "-"}`,
|
|
1995
2003
|
`- Destination: ${destinationLabel || "-"} (${firstNonEmptyString([safePayload.destination?.chat_id, safePayload.destination?.chatID, process.env.METHEUS_RUNNER_CHAT_ID]) || "-"})`,
|
|
1996
2004
|
"",
|
|
1997
|
-
"
|
|
2005
|
+
"Existing project context memory (active and suggested):",
|
|
1998
2006
|
projectContextItems.length
|
|
1999
2007
|
? projectContextItems.map(formatProjectContextLine).join("\n")
|
|
2000
2008
|
: "- none",
|
|
2001
|
-
"
|
|
2009
|
+
"Active items are authoritative shared memory.",
|
|
2010
|
+
"Suggested items are pending shared-memory candidates and must not be duplicated if they already capture the same durable fact, rule, focus, or risk.",
|
|
2011
|
+
"Prefer active items over stale room chatter when they conflict.",
|
|
2002
2012
|
"",
|
|
2003
2013
|
"Recent human messages (prioritized context):",
|
|
2004
2014
|
recentHumanMessages.length ? recentHumanMessages.map(formatContextCommentLine).join("\n") : "- none",
|
|
@@ -2164,6 +2174,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
|
|
|
2164
2174
|
"- Only include context_suggestion when the human explicitly establishes a durable shared project rule, bot role assignment, project fact, current focus, or risk that should be remembered beyond this chat.",
|
|
2165
2175
|
"- Never include context_suggestion for greetings, questions, status checks, file-location questions, workspace paths, or temporary/personal requests.",
|
|
2166
2176
|
"- Do not use category \"workspace\" for chat-room context memory. Workspace paths must be answered from tools, not stored as shared chat context.",
|
|
2177
|
+
"- If an existing active or suggested project context item already captures the same durable meaning, do not emit a new context_suggestion with should_store=true.",
|
|
2167
2178
|
"- When you include context_suggestion, set should_store=true and keep title/body concise and durable.",
|
|
2168
2179
|
"",
|
|
2169
2180
|
isInternalExecutionStep
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
findExactProjectContextDuplicate,
|
|
5
|
+
} from "./project-context-dedupe.mjs";
|
|
3
6
|
|
|
4
7
|
function requireDependency(deps, key) {
|
|
5
8
|
const candidate = deps?.[key];
|
|
@@ -112,6 +115,22 @@ function buildProjectContextListText(projectID, items, statusFilter = "") {
|
|
|
112
115
|
return lines.join("\n");
|
|
113
116
|
}
|
|
114
117
|
|
|
118
|
+
function buildProjectContextDuplicateText(projectID, duplicate, attemptedStatus) {
|
|
119
|
+
const item = normalizeProjectContextRecord(duplicate);
|
|
120
|
+
return [
|
|
121
|
+
`project_id: ${projectID}`,
|
|
122
|
+
"created: false",
|
|
123
|
+
"duplicate: true",
|
|
124
|
+
`reason: duplicate_${String(item.status || "existing").trim().toLowerCase() || "existing"}`,
|
|
125
|
+
`existing_context_id: ${item.id || "-"}`,
|
|
126
|
+
`existing_status: ${item.status || "-"}`,
|
|
127
|
+
`attempted_status: ${String(attemptedStatus || "").trim() || "-"}`,
|
|
128
|
+
`title: ${item.title || "-"}`,
|
|
129
|
+
"",
|
|
130
|
+
item.body || "",
|
|
131
|
+
].join("\n").trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
115
134
|
function resolveProjectID(toolArgs, args, workspaceDir, deps) {
|
|
116
135
|
const resolveProjectIDForRequest = requireDependency(deps, "resolveProjectIDForRequest");
|
|
117
136
|
return String(
|
|
@@ -478,6 +497,35 @@ export async function handleLocalProjectToolDispatch(
|
|
|
478
497
|
if (!body) {
|
|
479
498
|
return jsonRpcError(requestObj, -32001, "body is required");
|
|
480
499
|
}
|
|
500
|
+
const requestedStatus = normalizeProjectContextStatus(
|
|
501
|
+
toolArgs.status,
|
|
502
|
+
toolName === "project.context.suggest" ? "suggested" : "active",
|
|
503
|
+
);
|
|
504
|
+
const existingItems = ensureArray(await listProjectContextItems({
|
|
505
|
+
siteBaseURL,
|
|
506
|
+
projectID,
|
|
507
|
+
token,
|
|
508
|
+
timeoutSeconds,
|
|
509
|
+
actorUserID,
|
|
510
|
+
})).map((item) => normalizeProjectContextRecord(item));
|
|
511
|
+
const duplicate = findExactProjectContextDuplicate(existingItems, {
|
|
512
|
+
title,
|
|
513
|
+
body,
|
|
514
|
+
}, {
|
|
515
|
+
statuses: ["active", "suggested"],
|
|
516
|
+
});
|
|
517
|
+
if (duplicate) {
|
|
518
|
+
return jsonRpcResult(requestObj, {
|
|
519
|
+
content: [{ type: "text", text: buildProjectContextDuplicateText(projectID, duplicate, requestedStatus) }],
|
|
520
|
+
structuredContent: {
|
|
521
|
+
ok: true,
|
|
522
|
+
created: false,
|
|
523
|
+
duplicate: true,
|
|
524
|
+
reason: `duplicate_${String(duplicate.status || "").trim().toLowerCase() || "existing"}`,
|
|
525
|
+
item: normalizeProjectContextRecord(duplicate),
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
}
|
|
481
529
|
const created = normalizeProjectContextRecord(await createProjectContextItem({
|
|
482
530
|
siteBaseURL,
|
|
483
531
|
token,
|
|
@@ -488,7 +536,7 @@ export async function handleLocalProjectToolDispatch(
|
|
|
488
536
|
body,
|
|
489
537
|
category: normalizeProjectContextCategory(toolArgs.category),
|
|
490
538
|
importance: normalizeProjectContextImportance(toolArgs.importance),
|
|
491
|
-
status:
|
|
539
|
+
status: requestedStatus,
|
|
492
540
|
}));
|
|
493
541
|
return jsonRpcResult(requestObj, {
|
|
494
542
|
content: [{ type: "text", text: buildProjectContextText(created) }],
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function safeObject(value) {
|
|
2
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
|
+
return {};
|
|
4
|
+
}
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function ensureArray(value) {
|
|
9
|
+
return Array.isArray(value) ? value : [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function normalizeProjectContextComparableText(value) {
|
|
13
|
+
return String(value || "")
|
|
14
|
+
.trim()
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/\s+/g, " ");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeProjectContextDuplicateRecord(rawItem) {
|
|
20
|
+
const item = safeObject(rawItem);
|
|
21
|
+
return {
|
|
22
|
+
id: String(item.id || item.context_id || "").trim(),
|
|
23
|
+
title: String(item.title || "").trim(),
|
|
24
|
+
body: String(item.body || "").trim(),
|
|
25
|
+
status: String(item.status || "").trim().toLowerCase(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function findExactProjectContextDuplicate(rawItems, rawCandidate, options = {}) {
|
|
30
|
+
const allowedStatuses = new Set(
|
|
31
|
+
ensureArray(options.statuses && options.statuses.length ? options.statuses : ["active", "suggested"])
|
|
32
|
+
.map((value) => String(value || "").trim().toLowerCase())
|
|
33
|
+
.filter(Boolean),
|
|
34
|
+
);
|
|
35
|
+
const candidate = normalizeProjectContextDuplicateRecord(rawCandidate);
|
|
36
|
+
if (!candidate.title || !candidate.body) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const candidateTitle = normalizeProjectContextComparableText(candidate.title);
|
|
40
|
+
const candidateBody = normalizeProjectContextComparableText(candidate.body);
|
|
41
|
+
return ensureArray(rawItems)
|
|
42
|
+
.map((item) => normalizeProjectContextDuplicateRecord(item))
|
|
43
|
+
.find((item) => (
|
|
44
|
+
allowedStatuses.has(item.status)
|
|
45
|
+
&& normalizeProjectContextComparableText(item.title) === candidateTitle
|
|
46
|
+
&& normalizeProjectContextComparableText(item.body) === candidateBody
|
|
47
|
+
)) || null;
|
|
48
|
+
}
|
package/lib/runner-helpers.mjs
CHANGED
|
@@ -275,13 +275,11 @@ export function buildTelegramMessageEnvelopeFromParsedArchive(parsedArchiveRaw,
|
|
|
275
275
|
overrides.sender,
|
|
276
276
|
parsedArchive.sender,
|
|
277
277
|
parsedArchive.botName,
|
|
278
|
-
parsedArchive.replyToSender,
|
|
279
278
|
]),
|
|
280
279
|
sender_username: firstNonEmptyString([
|
|
281
280
|
overrides.sender_username,
|
|
282
281
|
parsedArchive.botUsername,
|
|
283
282
|
parsedArchive.username,
|
|
284
|
-
parsedArchive.replyToUsername,
|
|
285
283
|
]),
|
|
286
284
|
sender_is_bot: overrides.sender_is_bot ?? parsedArchive.senderIsBot,
|
|
287
285
|
body: firstNonEmptyString([overrides.body, parsedArchive.body]),
|
|
@@ -520,7 +518,7 @@ function buildContextSpeakerLabel(parsed) {
|
|
|
520
518
|
if (username) {
|
|
521
519
|
return `@${String(username).replace(/^@+/, "")}`;
|
|
522
520
|
}
|
|
523
|
-
return firstNonEmptyString([parsed.sender, parsed.botName
|
|
521
|
+
return firstNonEmptyString([parsed.sender, parsed.botName]);
|
|
524
522
|
}
|
|
525
523
|
|
|
526
524
|
function normalizeContextWindowOptions(rawOptions) {
|
|
@@ -561,7 +561,9 @@ function summarizeStartupLoopFacts({
|
|
|
561
561
|
execution_contract_targets: ensureArray(parsed.executionContractAssignments)
|
|
562
562
|
.map((item) => normalizeMentionSelector(safeObject(item).targetBot))
|
|
563
563
|
.filter(Boolean),
|
|
564
|
-
reply_to_bot_username:
|
|
564
|
+
reply_to_bot_username: parsed.replyToSenderIsBot === true
|
|
565
|
+
? normalizeMentionSelector(parsed.replyToUsername)
|
|
566
|
+
: "",
|
|
565
567
|
newer_human_message_count: newerHumanMessages.length,
|
|
566
568
|
has_newer_human_message: newerHumanMessages.length > 0,
|
|
567
569
|
bot_reply_count_since_last_human: botReplysSinceLastHuman.length + 1,
|
|
@@ -52,7 +52,7 @@ export async function prepareRunnerSelectedRecordContext({
|
|
|
52
52
|
reduceContextWindowForHumanIntent,
|
|
53
53
|
buildRunnerContextWindow,
|
|
54
54
|
buildHumanIntentContextWindowOptions,
|
|
55
|
-
|
|
55
|
+
loadProjectContextItemsForPrompt,
|
|
56
56
|
isInformationalHumanIntentType,
|
|
57
57
|
resolveInformationalQueryExecutionPlan,
|
|
58
58
|
buildRunnerInputPayload,
|
|
@@ -76,7 +76,7 @@ export async function prepareRunnerSelectedRecordContext({
|
|
|
76
76
|
|| typeof reduceContextWindowForHumanIntent !== "function"
|
|
77
77
|
|| typeof buildRunnerContextWindow !== "function"
|
|
78
78
|
|| typeof buildHumanIntentContextWindowOptions !== "function"
|
|
79
|
-
|| typeof
|
|
79
|
+
|| typeof loadProjectContextItemsForPrompt !== "function"
|
|
80
80
|
|| typeof isInformationalHumanIntentType !== "function"
|
|
81
81
|
|| typeof resolveInformationalQueryExecutionPlan !== "function"
|
|
82
82
|
|| typeof buildRunnerInputPayload !== "function"
|
|
@@ -372,7 +372,7 @@ export async function prepareRunnerSelectedRecordContext({
|
|
|
372
372
|
),
|
|
373
373
|
directHumanResponseContract.intentType,
|
|
374
374
|
);
|
|
375
|
-
const projectContextItems = await
|
|
375
|
+
const projectContextItems = await loadProjectContextItemsForPrompt({
|
|
376
376
|
normalizedRoute,
|
|
377
377
|
runtime,
|
|
378
378
|
deps: executionDeps,
|
|
@@ -84,6 +84,11 @@ export async function prepareRunnerSelectedRecordDeliveryContext({
|
|
|
84
84
|
const normalizedConversationMode = String(effectiveConversationContext?.mode || "").trim().toLowerCase();
|
|
85
85
|
const normalizedConversationStage = String(effectiveConversationContext?.stage || "").trim().toLowerCase();
|
|
86
86
|
const normalizedGuideVisibleReplyTarget = normalizeMentionSelector(safeObject(guidePacket).visible_reply_target);
|
|
87
|
+
const managedPeerSelectors = new Set(
|
|
88
|
+
Array.from(directHumanPeerMap instanceof Map ? directHumanPeerMap.keys() : [])
|
|
89
|
+
.map((item) => normalizeMentionSelector(item))
|
|
90
|
+
.filter(Boolean),
|
|
91
|
+
);
|
|
87
92
|
const visibleDelegationTargets = uniqueOrdered(
|
|
88
93
|
normalizedExecutionContractType === "delegation"
|
|
89
94
|
? [
|
|
@@ -122,6 +127,7 @@ export async function prepareRunnerSelectedRecordDeliveryContext({
|
|
|
122
127
|
effectiveResponseContractValidation.ok
|
|
123
128
|
&& normalizedReplyTargetBotSelector
|
|
124
129
|
&& normalizedReplyTargetBotSelector !== currentBotSelector
|
|
130
|
+
&& managedPeerSelectors.has(normalizedReplyTargetBotSelector)
|
|
125
131
|
&& normalizedGuideVisibleReplyTarget
|
|
126
132
|
&& normalizedGuideVisibleReplyTarget === normalizedReplyTargetBotSelector
|
|
127
133
|
) {
|
|
@@ -107,6 +107,9 @@ import {
|
|
|
107
107
|
import {
|
|
108
108
|
isInternalRuntimeArtifactPath,
|
|
109
109
|
} from "./local-ai-adapters.mjs";
|
|
110
|
+
import {
|
|
111
|
+
findExactProjectContextDuplicate,
|
|
112
|
+
} from "./project-context-dedupe.mjs";
|
|
110
113
|
|
|
111
114
|
function safeObject(value) {
|
|
112
115
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -266,11 +269,14 @@ export function buildRunnerInputPayload({
|
|
|
266
269
|
.map((value) => String(value || "").trim().toLowerCase())
|
|
267
270
|
.filter(Boolean),
|
|
268
271
|
);
|
|
272
|
+
const parsedReplyTargetBot = parsed.replyToSenderIsBot === true
|
|
273
|
+
? parsed.replyToUsername
|
|
274
|
+
: "";
|
|
269
275
|
const normalizedReplyTargetBot = normalizeMentionSelector(
|
|
270
276
|
directHumanIntent.replyTargetBotSelector
|
|
271
277
|
|| directHumanIntent.reply_target_bot
|
|
272
278
|
|| directHumanIntent.reply_to_bot_username
|
|
273
|
-
||
|
|
279
|
+
|| parsedReplyTargetBot,
|
|
274
280
|
);
|
|
275
281
|
const normalizedIntentMode = String(
|
|
276
282
|
directHumanIntent.intentMode || directHumanIntent.intent_mode || directHumanIntent.mode || "",
|
|
@@ -626,6 +632,11 @@ function projectContextImportanceRank(value) {
|
|
|
626
632
|
function compareProjectContextItems(leftRaw, rightRaw) {
|
|
627
633
|
const left = normalizeProjectContextItem(leftRaw);
|
|
628
634
|
const right = normalizeProjectContextItem(rightRaw);
|
|
635
|
+
const leftStatus = String(left.status || "").trim().toLowerCase();
|
|
636
|
+
const rightStatus = String(right.status || "").trim().toLowerCase();
|
|
637
|
+
const leftStatusRank = leftStatus === "active" ? 0 : leftStatus === "suggested" ? 1 : 2;
|
|
638
|
+
const rightStatusRank = rightStatus === "active" ? 0 : rightStatus === "suggested" ? 1 : 2;
|
|
639
|
+
if (leftStatusRank !== rightStatusRank) return leftStatusRank - rightStatusRank;
|
|
629
640
|
const importanceDelta = projectContextImportanceRank(right.importance) - projectContextImportanceRank(left.importance);
|
|
630
641
|
if (importanceDelta !== 0) return importanceDelta;
|
|
631
642
|
const leftUpdated = Date.parse(String(left.updated_at || left.created_at || "").trim());
|
|
@@ -660,13 +671,6 @@ function normalizeProjectContextImportance(value) {
|
|
|
660
671
|
return "normal";
|
|
661
672
|
}
|
|
662
673
|
|
|
663
|
-
function normalizeProjectContextComparableText(value) {
|
|
664
|
-
return String(value || "")
|
|
665
|
-
.trim()
|
|
666
|
-
.toLowerCase()
|
|
667
|
-
.replace(/\s+/g, " ");
|
|
668
|
-
}
|
|
669
|
-
|
|
670
674
|
function looksLikeInterrogativeMessage(text) {
|
|
671
675
|
const normalizedText = String(text || "").trim();
|
|
672
676
|
if (!normalizedText) {
|
|
@@ -770,13 +774,9 @@ async function maybeCreateSuggestedProjectContext({
|
|
|
770
774
|
actorUserID,
|
|
771
775
|
}))
|
|
772
776
|
: [];
|
|
773
|
-
const duplicate = existingItems
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
["active", "suggested"].includes(String(item.status || "").trim().toLowerCase())
|
|
777
|
-
&& normalizeProjectContextComparableText(item.title) === normalizeProjectContextComparableText(candidate.title)
|
|
778
|
-
&& normalizeProjectContextComparableText(item.body) === normalizeProjectContextComparableText(candidate.body)
|
|
779
|
-
));
|
|
777
|
+
const duplicate = findExactProjectContextDuplicate(existingItems, candidate, {
|
|
778
|
+
statuses: ["active", "suggested"],
|
|
779
|
+
});
|
|
780
780
|
if (duplicate) {
|
|
781
781
|
return {
|
|
782
782
|
skipped: true,
|
|
@@ -817,7 +817,7 @@ async function maybeCreateSuggestedProjectContext({
|
|
|
817
817
|
}
|
|
818
818
|
}
|
|
819
819
|
|
|
820
|
-
async function
|
|
820
|
+
async function loadProjectContextItemsForPrompt({ normalizedRoute, runtime, deps }) {
|
|
821
821
|
const listProjectContextItems = typeof deps?.listProjectContextItems === "function"
|
|
822
822
|
? deps.listProjectContextItems
|
|
823
823
|
: null;
|
|
@@ -839,7 +839,7 @@ async function loadActiveProjectContextItems({ normalizedRoute, runtime, deps })
|
|
|
839
839
|
actorUserID: safeObject(runtime.actor).user_id,
|
|
840
840
|
}))
|
|
841
841
|
.map((item) => normalizeProjectContextItem(item))
|
|
842
|
-
.filter((item) => item.id && item.status.toLowerCase()
|
|
842
|
+
.filter((item) => item.id && ["active", "suggested"].includes(item.status.toLowerCase()))
|
|
843
843
|
.sort(compareProjectContextItems)
|
|
844
844
|
.slice(0, 8);
|
|
845
845
|
} catch {
|
|
@@ -3308,7 +3308,7 @@ export async function processRunnerSelectedRecord({
|
|
|
3308
3308
|
reduceContextWindowForHumanIntent,
|
|
3309
3309
|
buildRunnerContextWindow,
|
|
3310
3310
|
buildHumanIntentContextWindowOptions,
|
|
3311
|
-
|
|
3311
|
+
loadProjectContextItemsForPrompt,
|
|
3312
3312
|
isInformationalHumanIntentType,
|
|
3313
3313
|
resolveInformationalQueryExecutionPlan,
|
|
3314
3314
|
buildRunnerInputPayload,
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
resolveCodexRawTextProcessResult,
|
|
90
90
|
} from "./local-ai-adapters.mjs";
|
|
91
91
|
import {
|
|
92
|
+
buildTelegramMessageEnvelopeFromParsedArchive,
|
|
92
93
|
buildRunnerRouteDuplicateStateFromComment,
|
|
93
94
|
buildRunnerRouteStateFromComment,
|
|
94
95
|
} from "./runner-helpers.mjs";
|
|
@@ -115,6 +116,9 @@ import {
|
|
|
115
116
|
import {
|
|
116
117
|
resolveRunnerAuthoritativeTriggerDecision,
|
|
117
118
|
} from "./runner-trigger.mjs";
|
|
119
|
+
import {
|
|
120
|
+
findExactProjectContextDuplicate,
|
|
121
|
+
} from "./project-context-dedupe.mjs";
|
|
118
122
|
|
|
119
123
|
function requireDependency(deps, key) {
|
|
120
124
|
const candidate = deps?.[key];
|
|
@@ -7477,6 +7481,175 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
7477
7481
|
push("single_bot_direct_reply_target_prefixes_visible_target_when_ai_omits_mention", false, String(err?.message || err));
|
|
7478
7482
|
}
|
|
7479
7483
|
|
|
7484
|
+
try {
|
|
7485
|
+
let aiCalls = 0;
|
|
7486
|
+
let plannerCalls = 0;
|
|
7487
|
+
let deliveryCalls = 0;
|
|
7488
|
+
let deliveredText = "";
|
|
7489
|
+
const processed = await processRunnerSelectedRecord({
|
|
7490
|
+
routeKey: "single-bot-human-direct-reply-with-human-reply-target-key",
|
|
7491
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
7492
|
+
name: "telegram-monitor-single-bot-human-direct-reply-with-human-reply-target",
|
|
7493
|
+
project_id: selftestProjectID,
|
|
7494
|
+
provider: "telegram",
|
|
7495
|
+
role: "monitor",
|
|
7496
|
+
role_profile: "monitor",
|
|
7497
|
+
trigger_policy: {
|
|
7498
|
+
mentions_only: true,
|
|
7499
|
+
direct_messages: true,
|
|
7500
|
+
reply_to_bot_messages: true,
|
|
7501
|
+
},
|
|
7502
|
+
archive_policy: {
|
|
7503
|
+
mirror_replies: true,
|
|
7504
|
+
dedupe_inbound: true,
|
|
7505
|
+
dedupe_outbound: true,
|
|
7506
|
+
skip_bot_messages: true,
|
|
7507
|
+
},
|
|
7508
|
+
dry_run_delivery: true,
|
|
7509
|
+
}),
|
|
7510
|
+
selectedRecord: {
|
|
7511
|
+
id: "comment-single-bot-human-direct-reply-with-human-reply-target",
|
|
7512
|
+
createdAt: "2026-04-02T05:48:25.000Z",
|
|
7513
|
+
parsedArchive: {
|
|
7514
|
+
kind: "telegram_message",
|
|
7515
|
+
chatID: "-100123",
|
|
7516
|
+
chatType: "supergroup",
|
|
7517
|
+
senderIsBot: false,
|
|
7518
|
+
sender: "Human",
|
|
7519
|
+
username: "sopia19910",
|
|
7520
|
+
body: "@RyoAI2_bot 너 대답 안하지?",
|
|
7521
|
+
mentionUsernames: ["RyoAI2_bot"],
|
|
7522
|
+
messageID: 376,
|
|
7523
|
+
replyToMessageID: 375,
|
|
7524
|
+
replyToUsername: "sopia19910",
|
|
7525
|
+
replyToSenderIsBot: false,
|
|
7526
|
+
},
|
|
7527
|
+
},
|
|
7528
|
+
pendingOrdered: [],
|
|
7529
|
+
bot: {
|
|
7530
|
+
id: "bot-peer-1",
|
|
7531
|
+
name: "RyoAI2_bot",
|
|
7532
|
+
username: "RyoAI2_bot",
|
|
7533
|
+
role: "monitor",
|
|
7534
|
+
provider: "telegram",
|
|
7535
|
+
},
|
|
7536
|
+
destination: {
|
|
7537
|
+
id: "dest-1",
|
|
7538
|
+
label: "Main Room",
|
|
7539
|
+
provider: "telegram",
|
|
7540
|
+
chatID: "-100123",
|
|
7541
|
+
},
|
|
7542
|
+
archiveThread: {
|
|
7543
|
+
threadID: "thread-1",
|
|
7544
|
+
workItemID: "work-item-1",
|
|
7545
|
+
},
|
|
7546
|
+
executionPlan: {
|
|
7547
|
+
mode: "role_profile",
|
|
7548
|
+
roleProfileName: "monitor",
|
|
7549
|
+
roleProfile: {
|
|
7550
|
+
client: "sample",
|
|
7551
|
+
model: "",
|
|
7552
|
+
permissionMode: "read_only",
|
|
7553
|
+
reasoningEffort: "low",
|
|
7554
|
+
},
|
|
7555
|
+
workspaceDir: path.join(os.tmpdir(), "metheus-runner-selftest-single-bot-human-direct-reply-with-human-reply-target"),
|
|
7556
|
+
workspaceSource: "selftest",
|
|
7557
|
+
usedCommandFallback: false,
|
|
7558
|
+
},
|
|
7559
|
+
runtime: {
|
|
7560
|
+
baseURL: "https://example.test",
|
|
7561
|
+
token: "selftest-token",
|
|
7562
|
+
timeoutSeconds: 30,
|
|
7563
|
+
actor: { user_id: "user-1" },
|
|
7564
|
+
},
|
|
7565
|
+
deps: {
|
|
7566
|
+
saveRunnerRouteState: () => {},
|
|
7567
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
7568
|
+
runRunnerAIExecution: async () => {
|
|
7569
|
+
aiCalls += 1;
|
|
7570
|
+
return {
|
|
7571
|
+
skip: false,
|
|
7572
|
+
reply: "대답합니다.",
|
|
7573
|
+
replyToMessageID: 0,
|
|
7574
|
+
};
|
|
7575
|
+
},
|
|
7576
|
+
performLocalBotDelivery: async ({ text }) => {
|
|
7577
|
+
deliveryCalls += 1;
|
|
7578
|
+
deliveredText = String(text || "").trim();
|
|
7579
|
+
return {
|
|
7580
|
+
delivery: { dryRun: true, body: {} },
|
|
7581
|
+
archive: {},
|
|
7582
|
+
};
|
|
7583
|
+
},
|
|
7584
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
7585
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
7586
|
+
buildRunnerExecutionDeps: () => ({
|
|
7587
|
+
analyzeHumanConversationIntentWithAI: async () => ({
|
|
7588
|
+
mode: "single_bot",
|
|
7589
|
+
lead_bot: "ryoai2_bot",
|
|
7590
|
+
participants: ["ryoai2_bot"],
|
|
7591
|
+
initial_responders: ["ryoai2_bot"],
|
|
7592
|
+
allowed_responders: ["ryoai2_bot"],
|
|
7593
|
+
summary_bot: "",
|
|
7594
|
+
allow_bot_to_bot: false,
|
|
7595
|
+
reply_expectation: "informational",
|
|
7596
|
+
intent_type: "small_talk",
|
|
7597
|
+
}),
|
|
7598
|
+
planRoleExecutionWithAI: async () => {
|
|
7599
|
+
plannerCalls += 1;
|
|
7600
|
+
return {
|
|
7601
|
+
requiresExecution: true,
|
|
7602
|
+
summaryRole: "worker",
|
|
7603
|
+
steps: [{ role: "worker", goal: "unexpected", artifactsRequired: true }],
|
|
7604
|
+
};
|
|
7605
|
+
},
|
|
7606
|
+
}),
|
|
7607
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
7608
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
7609
|
+
resolveConversationPeerBots: () => [
|
|
7610
|
+
{ id: "bot-peer-1", name: "RyoAI2_bot" },
|
|
7611
|
+
{ id: "bot-peer-2", name: "RyoAI3_bot" },
|
|
7612
|
+
],
|
|
7613
|
+
},
|
|
7614
|
+
});
|
|
7615
|
+
push(
|
|
7616
|
+
"single_bot_human_direct_reply_does_not_require_visible_human_target",
|
|
7617
|
+
processed.kind === "replied"
|
|
7618
|
+
&& aiCalls === 1
|
|
7619
|
+
&& plannerCalls === 0
|
|
7620
|
+
&& deliveryCalls === 1
|
|
7621
|
+
&& String(processed.result?.response_contract_validation_status || "") !== "reply_target_not_visible_in_reply"
|
|
7622
|
+
&& deliveredText === "대답합니다.",
|
|
7623
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} planner_calls=${plannerCalls} delivery_calls=${deliveryCalls} validation=${String(processed.result?.response_contract_validation_status || "(none)")} delivered=${String(deliveredText || "(none)")}`,
|
|
7624
|
+
);
|
|
7625
|
+
} catch (err) {
|
|
7626
|
+
push("single_bot_human_direct_reply_does_not_require_visible_human_target", false, String(err?.message || err));
|
|
7627
|
+
}
|
|
7628
|
+
|
|
7629
|
+
try {
|
|
7630
|
+
const envelope = buildTelegramMessageEnvelopeFromParsedArchive({
|
|
7631
|
+
kind: "telegram_message",
|
|
7632
|
+
chatID: "-100123",
|
|
7633
|
+
messageID: 377,
|
|
7634
|
+
sender: "",
|
|
7635
|
+
username: "",
|
|
7636
|
+
senderIsBot: false,
|
|
7637
|
+
replyToSender: "Human Target",
|
|
7638
|
+
replyToUsername: "sopia19910",
|
|
7639
|
+
replyToSenderIsBot: false,
|
|
7640
|
+
body: "본문",
|
|
7641
|
+
occurredAt: "2026-04-02T05:48:35.000Z",
|
|
7642
|
+
});
|
|
7643
|
+
push(
|
|
7644
|
+
"telegram_message_envelope_does_not_promote_human_reply_target_to_sender_identity",
|
|
7645
|
+
String(envelope.sender_username || "") === ""
|
|
7646
|
+
&& String(envelope.sender || "") === "",
|
|
7647
|
+
`sender=${String(envelope.sender || "(none)")} sender_username=${String(envelope.sender_username || "(none)")}`,
|
|
7648
|
+
);
|
|
7649
|
+
} catch (err) {
|
|
7650
|
+
push("telegram_message_envelope_does_not_promote_human_reply_target_to_sender_identity", false, String(err?.message || err));
|
|
7651
|
+
}
|
|
7652
|
+
|
|
7480
7653
|
try {
|
|
7481
7654
|
let aiCalls = 0;
|
|
7482
7655
|
let deliveryCalls = 0;
|
|
@@ -12789,17 +12962,26 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
12789
12962
|
status: "archived",
|
|
12790
12963
|
updated_at: "2026-03-17T00:00:00.000Z",
|
|
12791
12964
|
},
|
|
12792
|
-
{
|
|
12793
|
-
id: "ctx-3",
|
|
12794
|
-
title: "Workspace Binding",
|
|
12795
|
-
body: "Workspace is C:/LUSH KOREA CLI.",
|
|
12796
|
-
category: "workspace",
|
|
12797
|
-
importance: "high",
|
|
12798
|
-
status: "active",
|
|
12799
|
-
updated_at: "2026-03-18T00:08:00.000Z",
|
|
12800
|
-
},
|
|
12801
|
-
|
|
12802
|
-
|
|
12965
|
+
{
|
|
12966
|
+
id: "ctx-3",
|
|
12967
|
+
title: "Workspace Binding",
|
|
12968
|
+
body: "Workspace is C:/LUSH KOREA CLI.",
|
|
12969
|
+
category: "workspace",
|
|
12970
|
+
importance: "high",
|
|
12971
|
+
status: "active",
|
|
12972
|
+
updated_at: "2026-03-18T00:08:00.000Z",
|
|
12973
|
+
},
|
|
12974
|
+
{
|
|
12975
|
+
id: "ctx-4",
|
|
12976
|
+
title: "Pending Risk",
|
|
12977
|
+
body: "BOT3 capacity failures should be tracked before closing a delegation turn.",
|
|
12978
|
+
category: "risk",
|
|
12979
|
+
importance: "normal",
|
|
12980
|
+
status: "suggested",
|
|
12981
|
+
updated_at: "2026-03-18T00:07:00.000Z",
|
|
12982
|
+
},
|
|
12983
|
+
]),
|
|
12984
|
+
}),
|
|
12803
12985
|
buildRunnerDeliveryDeps: () => ({}),
|
|
12804
12986
|
buildRunnerRuntimeDeps: () => ({}),
|
|
12805
12987
|
},
|
|
@@ -12807,26 +12989,63 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
12807
12989
|
const prompt = buildLocalBotPrompt(capturedInputPayload, { terse: true });
|
|
12808
12990
|
const projectContexts = ensureArray(capturedInputPayload?.project_context_items);
|
|
12809
12991
|
push(
|
|
12810
|
-
"
|
|
12811
|
-
processed.kind === "replied"
|
|
12812
|
-
&& aiCalls === 1
|
|
12813
|
-
&& projectContexts.length ===
|
|
12814
|
-
&& String(projectContexts[0]?.title || "") === "Operating Rule"
|
|
12815
|
-
&& String(projectContexts[1]?.title || "") === "Workspace Binding"
|
|
12816
|
-
&&
|
|
12817
|
-
&& prompt.includes("
|
|
12818
|
-
&& prompt.includes("
|
|
12819
|
-
&&
|
|
12820
|
-
|
|
12821
|
-
|
|
12822
|
-
|
|
12823
|
-
|
|
12824
|
-
}
|
|
12825
|
-
|
|
12826
|
-
|
|
12827
|
-
|
|
12828
|
-
|
|
12829
|
-
|
|
12992
|
+
"existing_project_context_is_injected_into_runner_prompt",
|
|
12993
|
+
processed.kind === "replied"
|
|
12994
|
+
&& aiCalls === 1
|
|
12995
|
+
&& projectContexts.length === 3
|
|
12996
|
+
&& String(projectContexts[0]?.title || "") === "Operating Rule"
|
|
12997
|
+
&& String(projectContexts[1]?.title || "") === "Workspace Binding"
|
|
12998
|
+
&& String(projectContexts[2]?.title || "") === "Pending Risk"
|
|
12999
|
+
&& prompt.includes("Existing project context memory")
|
|
13000
|
+
&& prompt.includes("Operating Rule")
|
|
13001
|
+
&& prompt.includes("Workspace Binding")
|
|
13002
|
+
&& prompt.includes("Pending Risk")
|
|
13003
|
+
&& !prompt.includes("Archived Note"),
|
|
13004
|
+
`kind=${String(processed.kind || "(none)")} ai_calls=${aiCalls} contexts=${projectContexts.map((item) => `${item.status}:${item.title}`).join(",") || "(none)"} prompt_has_context_memory=${String(prompt.includes("Existing project context memory"))}`,
|
|
13005
|
+
);
|
|
13006
|
+
} catch (err) {
|
|
13007
|
+
push("existing_project_context_is_injected_into_runner_prompt", false, String(err?.message || err));
|
|
13008
|
+
}
|
|
13009
|
+
|
|
13010
|
+
try {
|
|
13011
|
+
const duplicate = findExactProjectContextDuplicate([
|
|
13012
|
+
{
|
|
13013
|
+
id: "ctx-active-1",
|
|
13014
|
+
title: "Operating Rule",
|
|
13015
|
+
body: "Always answer with current project settings first.",
|
|
13016
|
+
status: "active",
|
|
13017
|
+
},
|
|
13018
|
+
{
|
|
13019
|
+
id: "ctx-suggested-1",
|
|
13020
|
+
title: " Pending Risk ",
|
|
13021
|
+
body: "BOT3 capacity failures should be tracked before closing a delegation turn.",
|
|
13022
|
+
status: "suggested",
|
|
13023
|
+
},
|
|
13024
|
+
{
|
|
13025
|
+
id: "ctx-archived-1",
|
|
13026
|
+
title: "Old Note",
|
|
13027
|
+
body: "old note",
|
|
13028
|
+
status: "archived",
|
|
13029
|
+
},
|
|
13030
|
+
], {
|
|
13031
|
+
title: "pending risk",
|
|
13032
|
+
body: "BOT3 capacity failures should be tracked before closing a delegation turn.",
|
|
13033
|
+
}, {
|
|
13034
|
+
statuses: ["active", "suggested"],
|
|
13035
|
+
});
|
|
13036
|
+
push(
|
|
13037
|
+
"project_context_exact_duplicate_helper_matches_active_and_suggested_only",
|
|
13038
|
+
String(duplicate?.id || "") === "ctx-suggested-1",
|
|
13039
|
+
`duplicate_id=${String(duplicate?.id || "(none)")} duplicate_status=${String(duplicate?.status || "(none)")}`,
|
|
13040
|
+
);
|
|
13041
|
+
} catch (err) {
|
|
13042
|
+
push("project_context_exact_duplicate_helper_matches_active_and_suggested_only", false, String(err?.message || err));
|
|
13043
|
+
}
|
|
13044
|
+
|
|
13045
|
+
try {
|
|
13046
|
+
const prompt = buildLocalBotPrompt({
|
|
13047
|
+
bot: {
|
|
13048
|
+
name: "RyoAI_bot",
|
|
12830
13049
|
username: "RyoAI_bot",
|
|
12831
13050
|
role: "monitor",
|
|
12832
13051
|
},
|
|
@@ -13096,14 +13315,148 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
13096
13315
|
&& String(safeObject(lastSavedState).last_context_suggestion_id || "") === "ctx-suggest-1",
|
|
13097
13316
|
`kind=${String(processed.kind || "(none)")} create_calls=${createCalls.length} status=${String(processed?.result?.context_suggestion_status || "(none)")} state_id=${String(safeObject(lastSavedState).last_context_suggestion_id || "(none)")}`,
|
|
13098
13317
|
);
|
|
13099
|
-
} catch (err) {
|
|
13100
|
-
push("human_role_rule_creates_suggested_project_context", false, String(err?.message || err));
|
|
13101
|
-
}
|
|
13102
|
-
|
|
13103
|
-
try {
|
|
13104
|
-
const createCalls = [];
|
|
13105
|
-
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-context-suggest-
|
|
13106
|
-
const processed = await processRunnerSelectedRecord({
|
|
13318
|
+
} catch (err) {
|
|
13319
|
+
push("human_role_rule_creates_suggested_project_context", false, String(err?.message || err));
|
|
13320
|
+
}
|
|
13321
|
+
|
|
13322
|
+
try {
|
|
13323
|
+
const createCalls = [];
|
|
13324
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-context-suggest-duplicate-"));
|
|
13325
|
+
const processed = await processRunnerSelectedRecord({
|
|
13326
|
+
routeKey: "context-suggest-duplicate-key",
|
|
13327
|
+
normalizedRoute: normalizeRunnerRoute({
|
|
13328
|
+
name: "telegram-monitor-context-suggest-duplicate",
|
|
13329
|
+
project_id: selftestProjectID,
|
|
13330
|
+
provider: "telegram",
|
|
13331
|
+
role: "monitor",
|
|
13332
|
+
role_profile: "monitor",
|
|
13333
|
+
destination_id: "dest-1",
|
|
13334
|
+
destination_label: "Main Room",
|
|
13335
|
+
server_bot_name: "RyoAI_bot",
|
|
13336
|
+
server_bot_id: "bot-1",
|
|
13337
|
+
trigger_policy: {
|
|
13338
|
+
mentions_only: true,
|
|
13339
|
+
direct_messages: true,
|
|
13340
|
+
reply_to_bot_messages: true,
|
|
13341
|
+
},
|
|
13342
|
+
archive_policy: {
|
|
13343
|
+
mirror_replies: true,
|
|
13344
|
+
dedupe_inbound: true,
|
|
13345
|
+
dedupe_outbound: true,
|
|
13346
|
+
skip_bot_messages: true,
|
|
13347
|
+
},
|
|
13348
|
+
dry_run_delivery: false,
|
|
13349
|
+
}),
|
|
13350
|
+
selectedRecord: {
|
|
13351
|
+
id: "comment-context-suggest-duplicate",
|
|
13352
|
+
createdAt: "2026-03-19T01:04:00.000Z",
|
|
13353
|
+
parsedArchive: {
|
|
13354
|
+
kind: "telegram_message",
|
|
13355
|
+
chatID: "-100123",
|
|
13356
|
+
chatType: "supergroup",
|
|
13357
|
+
body: "@RyoAI_bot Room rule: RyoAI_bot handles coordination and RyoAI2_bot handles review in this room.",
|
|
13358
|
+
messageID: 301,
|
|
13359
|
+
sender: "human",
|
|
13360
|
+
senderIsBot: false,
|
|
13361
|
+
mentionUsernames: ["ryoai_bot", "ryoai2_bot"],
|
|
13362
|
+
},
|
|
13363
|
+
},
|
|
13364
|
+
pendingOrdered: [],
|
|
13365
|
+
bot: {
|
|
13366
|
+
id: "bot-1",
|
|
13367
|
+
name: "RyoAI_bot",
|
|
13368
|
+
username: "RyoAI_bot",
|
|
13369
|
+
role: "monitor",
|
|
13370
|
+
provider: "telegram",
|
|
13371
|
+
},
|
|
13372
|
+
destination: {
|
|
13373
|
+
id: "dest-1",
|
|
13374
|
+
label: "Main Room",
|
|
13375
|
+
provider: "telegram",
|
|
13376
|
+
chatID: "-100123",
|
|
13377
|
+
},
|
|
13378
|
+
archiveThread: {
|
|
13379
|
+
threadID: "thread-1",
|
|
13380
|
+
workItemID: "work-item-1",
|
|
13381
|
+
},
|
|
13382
|
+
executionPlan: {
|
|
13383
|
+
mode: "role_profile",
|
|
13384
|
+
roleProfileName: "monitor",
|
|
13385
|
+
roleProfile: {
|
|
13386
|
+
client: "sample",
|
|
13387
|
+
model: "",
|
|
13388
|
+
permissionMode: "read_only",
|
|
13389
|
+
reasoningEffort: "low",
|
|
13390
|
+
},
|
|
13391
|
+
workspaceDir,
|
|
13392
|
+
workspaceSource: "selftest",
|
|
13393
|
+
usedCommandFallback: false,
|
|
13394
|
+
},
|
|
13395
|
+
runtime: {
|
|
13396
|
+
baseURL: "https://example.test",
|
|
13397
|
+
token: "selftest-token",
|
|
13398
|
+
timeoutSeconds: 30,
|
|
13399
|
+
actor: { user_id: "user-1" },
|
|
13400
|
+
},
|
|
13401
|
+
deps: {
|
|
13402
|
+
saveRunnerRouteState: () => {},
|
|
13403
|
+
startRunnerTypingHeartbeat: () => ({ async stop() {} }),
|
|
13404
|
+
runRunnerAIExecution: async () => ({
|
|
13405
|
+
skip: false,
|
|
13406
|
+
reply: "역할 규칙으로 기억하겠습니다.",
|
|
13407
|
+
replyToMessageID: 301,
|
|
13408
|
+
raw: {
|
|
13409
|
+
context_suggestion: {
|
|
13410
|
+
should_store: true,
|
|
13411
|
+
title: "Bot collaboration roles",
|
|
13412
|
+
body: "RyoAI_bot handles coordination and RyoAI2_bot handles review in this room.",
|
|
13413
|
+
category: "bot_role",
|
|
13414
|
+
importance: "high",
|
|
13415
|
+
},
|
|
13416
|
+
},
|
|
13417
|
+
}),
|
|
13418
|
+
performLocalBotDelivery: async () => ({
|
|
13419
|
+
delivery: { dryRun: false, body: { result: { message_id: 9003 } } },
|
|
13420
|
+
archive: { ok: true },
|
|
13421
|
+
}),
|
|
13422
|
+
serializeRunnerTriggerPolicy: (value) => value,
|
|
13423
|
+
serializeRunnerArchivePolicy: (value) => value,
|
|
13424
|
+
buildRunnerExecutionDeps: () => ({
|
|
13425
|
+
listProjectContextItems: async () => ([
|
|
13426
|
+
{
|
|
13427
|
+
id: "ctx-suggest-existing",
|
|
13428
|
+
project_id: selftestProjectID,
|
|
13429
|
+
title: "Bot collaboration roles",
|
|
13430
|
+
body: "RyoAI_bot handles coordination and RyoAI2_bot handles review in this room.",
|
|
13431
|
+
category: "bot_role",
|
|
13432
|
+
importance: "high",
|
|
13433
|
+
status: "suggested",
|
|
13434
|
+
},
|
|
13435
|
+
]),
|
|
13436
|
+
createProjectContextItem: async (input) => {
|
|
13437
|
+
createCalls.push(input);
|
|
13438
|
+
return { id: "ctx-should-not-create" };
|
|
13439
|
+
},
|
|
13440
|
+
}),
|
|
13441
|
+
buildRunnerDeliveryDeps: () => ({}),
|
|
13442
|
+
buildRunnerRuntimeDeps: () => ({}),
|
|
13443
|
+
},
|
|
13444
|
+
});
|
|
13445
|
+
push(
|
|
13446
|
+
"duplicate_suggested_project_context_is_not_created",
|
|
13447
|
+
processed.kind === "replied"
|
|
13448
|
+
&& createCalls.length === 0
|
|
13449
|
+
&& String(processed?.result?.context_suggestion_status || "") === "skipped:duplicate_suggested",
|
|
13450
|
+
`kind=${String(processed.kind || "(none)")} create_calls=${createCalls.length} status=${String(processed?.result?.context_suggestion_status || "(none)")}`,
|
|
13451
|
+
);
|
|
13452
|
+
} catch (err) {
|
|
13453
|
+
push("duplicate_suggested_project_context_is_not_created", false, String(err?.message || err));
|
|
13454
|
+
}
|
|
13455
|
+
|
|
13456
|
+
try {
|
|
13457
|
+
const createCalls = [];
|
|
13458
|
+
const workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), "metheus-runner-selftest-context-suggest-question-"));
|
|
13459
|
+
const processed = await processRunnerSelectedRecord({
|
|
13107
13460
|
routeKey: "context-suggest-question-key",
|
|
13108
13461
|
normalizedRoute: normalizeRunnerRoute({
|
|
13109
13462
|
name: "telegram-monitor-context-suggest-question",
|
|
@@ -19962,7 +20315,7 @@ export async function runSelftestRunnerScenarios(push, deps) {
|
|
|
19962
20315
|
reduceContextWindowForHumanIntent: (items) => items,
|
|
19963
20316
|
buildRunnerContextWindow: () => [{ id: "ctx-1" }],
|
|
19964
20317
|
buildHumanIntentContextWindowOptions: () => ({}),
|
|
19965
|
-
|
|
20318
|
+
loadProjectContextItemsForPrompt: async () => [{ id: "pc-1" }],
|
|
19966
20319
|
isInformationalHumanIntentType: (value) => String(value || "").trim().toLowerCase() === "status_query",
|
|
19967
20320
|
resolveInformationalQueryExecutionPlan: (plan) => ({ ...plan, mode: "lookup_only" }),
|
|
19968
20321
|
buildRunnerInputPayload: (payload) => ({
|