metheus-governance-mcp-cli 0.2.283 → 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 +135 -59
- 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-delivery-archive-handoff.mjs +42 -4
- package/lib/runner-helpers.mjs +22 -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-selected-record-preparation.mjs +17 -2
- package/lib/runner-orchestration-selected-record-reply-outcome.mjs +22 -6
- package/lib/runner-orchestration.mjs +18 -18
- package/lib/runner-recorder-failure-delivery-handoff.mjs +2 -8
- package/lib/runner-recorder-failure-delivery-outcome-handoff.mjs +36 -8
- package/lib/selftest-runner-scenarios.mjs +1057 -129
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -145,10 +145,10 @@ import {
|
|
|
145
145
|
findEarlierProcessableArchiveDuplicate,
|
|
146
146
|
findRecentTelegramMessageEnvelope,
|
|
147
147
|
isTelegramLocalInboundEnvelopeForRoute,
|
|
148
|
-
isInboundArchiveKind,
|
|
149
|
-
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
150
|
-
normalizeArchiveCommentRecord,
|
|
151
|
-
selectPendingArchiveComments,
|
|
148
|
+
isInboundArchiveKind,
|
|
149
|
+
normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
|
|
150
|
+
normalizeArchiveCommentRecord,
|
|
151
|
+
selectPendingArchiveComments,
|
|
152
152
|
printRunnerResult,
|
|
153
153
|
} from "./lib/runner-helpers.mjs";
|
|
154
154
|
import {
|
|
@@ -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);
|
|
@@ -6430,10 +6429,7 @@ function markRunnerRequestLifecycle({
|
|
|
6430
6429
|
const authoritativeDecisionBundle = resolvedDecisionBundleValidation.ok === true
|
|
6431
6430
|
? safeObject(resolvedDecisionBundleValidation.bundle)
|
|
6432
6431
|
: runnerRequestAuthoritativeDecisionBundle(existing);
|
|
6433
|
-
const effectiveReplyToMessageID = intFromRawAllowZero(
|
|
6434
|
-
replyToMessageID,
|
|
6435
|
-
intFromRawAllowZero(existing.last_reply_to_message_id, 0),
|
|
6436
|
-
);
|
|
6432
|
+
const effectiveReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
6437
6433
|
const lastReplyMessageEnvelope = buildTelegramBotReplyEnvelope({
|
|
6438
6434
|
sourceEnvelope: sourceMessageEnvelope,
|
|
6439
6435
|
chatID: existing.chat_id,
|
|
@@ -6454,14 +6450,14 @@ function markRunnerRequestLifecycle({
|
|
|
6454
6450
|
senderUsername: normalizedCurrentBotSelector,
|
|
6455
6451
|
body: aiReplyPreview,
|
|
6456
6452
|
});
|
|
6457
|
-
const shouldRefreshAttemptedDeliveryEnvelope = (
|
|
6458
|
-
aiReplyGenerated === true
|
|
6459
|
-
|| String(aiReplyPreview || "").trim().length > 0
|
|
6460
|
-
|| String(deliveryStatus || "").trim().length > 0
|
|
6461
|
-
|| String(transportError || "").trim().length > 0
|
|
6462
|
-
|| intFromRawAllowZero(replyToMessageID, 0) > 0
|
|
6463
|
-
|| intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
|
|
6464
|
-
);
|
|
6453
|
+
const shouldRefreshAttemptedDeliveryEnvelope = (
|
|
6454
|
+
aiReplyGenerated === true
|
|
6455
|
+
|| String(aiReplyPreview || "").trim().length > 0
|
|
6456
|
+
|| String(deliveryStatus || "").trim().length > 0
|
|
6457
|
+
|| String(transportError || "").trim().length > 0
|
|
6458
|
+
|| intFromRawAllowZero(replyToMessageID, 0) > 0
|
|
6459
|
+
|| intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
|
|
6460
|
+
);
|
|
6465
6461
|
const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
|
|
6466
6462
|
[
|
|
6467
6463
|
...ensureArray(authoritativeDecisionBundle.execution_contract_targets),
|
|
@@ -6504,6 +6500,13 @@ function markRunnerRequestLifecycle({
|
|
|
6504
6500
|
const normalizedOutcome = String(outcome || "").trim().toLowerCase();
|
|
6505
6501
|
const normalizedFailureReplyClassification = String(failureReplyClassification || "").trim().toLowerCase();
|
|
6506
6502
|
const normalizedFailureFacts = safeObject(failureFacts);
|
|
6503
|
+
const shouldPersistReplyAnchor = (
|
|
6504
|
+
aiReplyGenerated === true
|
|
6505
|
+
|| intFromRawAllowZero(lastReplyMessageID, 0) > 0
|
|
6506
|
+
|| ["delivered", "dry_run", "archive_error", "failed_transport"].includes(normalizedDeliveryStatus)
|
|
6507
|
+
|| String(transportError || "").trim().length > 0
|
|
6508
|
+
|| ["replied", "delivery_failed_after_generation"].includes(normalizedOutcome)
|
|
6509
|
+
);
|
|
6507
6510
|
const shouldRemainRunningAfterReply = authoritativeDecisionBundle.should_close_after_reply === true
|
|
6508
6511
|
? false
|
|
6509
6512
|
: authoritativeDecisionBundle.should_close_after_reply === false
|
|
@@ -6907,7 +6910,9 @@ function markRunnerRequestLifecycle({
|
|
|
6907
6910
|
last_source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || existing.last_source_message_thread_id,
|
|
6908
6911
|
last_reply_message_id: intFromRawAllowZero(lastReplyMessageID, 0) || existing.last_reply_message_id,
|
|
6909
6912
|
last_reply_message_thread_id: intFromRawAllowZero(lastReplyMessageThreadID, 0) || existing.last_reply_message_thread_id,
|
|
6910
|
-
last_reply_to_message_id:
|
|
6913
|
+
last_reply_to_message_id: shouldPersistReplyAnchor
|
|
6914
|
+
? effectiveReplyToMessageID
|
|
6915
|
+
: existing.last_reply_to_message_id,
|
|
6911
6916
|
last_reply_message_envelope: persistSuccessfulReplyEnvelope
|
|
6912
6917
|
? lastReplyMessageEnvelope
|
|
6913
6918
|
: safeObject(existing.last_reply_message_envelope),
|
|
@@ -18679,13 +18684,14 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18679
18684
|
workspaceDir: "",
|
|
18680
18685
|
workspaceSignalTrusted: true,
|
|
18681
18686
|
},
|
|
18682
|
-
{
|
|
18683
|
-
...buildLocalProjectDispatchDeps(),
|
|
18684
|
-
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
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",
|
|
18689
18695
|
project_id: input.projectID,
|
|
18690
18696
|
title: input.title,
|
|
18691
18697
|
body: input.body,
|
|
@@ -18709,11 +18715,81 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18709
18715
|
&& String(safeObject(capturedCreateArgs).category || "").trim() === "bot_role",
|
|
18710
18716
|
`status=${String(safeObject(suggestStructured.item).status || "").trim() || "(none)"} category=${String(safeObject(capturedCreateArgs).category || "").trim() || "(none)"}`,
|
|
18711
18717
|
);
|
|
18712
|
-
} catch (err) {
|
|
18713
|
-
push("local_project_context_suggest_tool_dispatches", false, String(err?.message || err));
|
|
18714
|
-
}
|
|
18715
|
-
|
|
18716
|
-
|
|
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;
|
|
18717
18793
|
const previousRunnerBotName = process.env.METHEUS_RUNNER_BOT_NAME;
|
|
18718
18794
|
try {
|
|
18719
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
|
+
}
|
|
@@ -13,6 +13,14 @@ function intFromRawAllowZero(value, fallback = 0) {
|
|
|
13
13
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID) {
|
|
17
|
+
return `reply anchor mismatch: expected ${String(expectedReplyToMessageID || 0)}, observed ${String(observedReplyToMessageID || 0)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID) {
|
|
21
|
+
return `message thread mismatch: expected ${String(expectedMessageThreadID || 0)}, observed ${String(observedMessageThreadID || 0)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
function requireArchiveDependency(deps, key) {
|
|
17
25
|
const candidate = deps?.[key];
|
|
18
26
|
if (typeof candidate !== "function") {
|
|
@@ -83,11 +91,41 @@ export async function finalizeLocalBotDeliveryArchive({
|
|
|
83
91
|
deliveredResult.message_id ?? deliveredBody.message_id ?? deliveredBody.ts,
|
|
84
92
|
0,
|
|
85
93
|
);
|
|
86
|
-
const
|
|
94
|
+
const observedMessageThreadID = intFromRawAllowZero(
|
|
87
95
|
deliveredResult.message_thread_id ?? deliveredBody.message_thread_id ?? delivery.effectiveMessageThreadID,
|
|
88
96
|
intFromRawAllowZero(messageThreadID, 0),
|
|
89
97
|
);
|
|
90
|
-
const
|
|
98
|
+
const expectedMessageThreadID = intFromRawAllowZero(messageThreadID, 0);
|
|
99
|
+
const expectedReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
100
|
+
const observedReplyToMessageID = intFromRawAllowZero(delivery.effectiveReplyToMessageID, 0);
|
|
101
|
+
if (
|
|
102
|
+
(expectedReplyToMessageID > 0 || observedReplyToMessageID > 0)
|
|
103
|
+
&& observedReplyToMessageID !== expectedReplyToMessageID
|
|
104
|
+
) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
error: buildReplyAnchorMismatchError(expectedReplyToMessageID, observedReplyToMessageID),
|
|
108
|
+
reply_anchor_mismatch: true,
|
|
109
|
+
expected_reply_to_message_id: expectedReplyToMessageID,
|
|
110
|
+
observed_reply_to_message_id: observedReplyToMessageID,
|
|
111
|
+
thread_id: thread.threadID,
|
|
112
|
+
work_item_id: thread.workItemID,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (
|
|
116
|
+
(expectedMessageThreadID > 0 || observedMessageThreadID > 0)
|
|
117
|
+
&& observedMessageThreadID !== expectedMessageThreadID
|
|
118
|
+
) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
error: buildMessageThreadMismatchError(expectedMessageThreadID, observedMessageThreadID),
|
|
122
|
+
message_thread_mismatch: true,
|
|
123
|
+
expected_message_thread_id: expectedMessageThreadID,
|
|
124
|
+
observed_message_thread_id: observedMessageThreadID,
|
|
125
|
+
thread_id: thread.threadID,
|
|
126
|
+
work_item_id: thread.workItemID,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
91
129
|
if (archiveDedupeOutbound && deliveredMessageID > 0) {
|
|
92
130
|
const existingComments = await listThreadCommentsTail({
|
|
93
131
|
siteBaseURL,
|
|
@@ -119,8 +157,8 @@ export async function finalizeLocalBotDeliveryArchive({
|
|
|
119
157
|
destination,
|
|
120
158
|
replyText: text,
|
|
121
159
|
messageID: deliveredMessageID,
|
|
122
|
-
messageThreadID:
|
|
123
|
-
replyToMessageID:
|
|
160
|
+
messageThreadID: expectedMessageThreadID,
|
|
161
|
+
replyToMessageID: expectedReplyToMessageID,
|
|
124
162
|
conversation: archiveConversation,
|
|
125
163
|
});
|
|
126
164
|
const createdComment = await createThreadComment({
|
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]),
|
|
@@ -314,6 +312,27 @@ export function buildTelegramMessageEnvelopeFromParsedArchive(parsedArchiveRaw,
|
|
|
314
312
|
});
|
|
315
313
|
}
|
|
316
314
|
|
|
315
|
+
export function resolveTelegramReplyAnchorMessageID({
|
|
316
|
+
replyToMessageID = 0,
|
|
317
|
+
sourceEnvelope: sourceEnvelopeRaw = {},
|
|
318
|
+
fallbackReplyToMessageID = 0,
|
|
319
|
+
} = {}) {
|
|
320
|
+
const explicitReplyToMessageID = intFromRawAllowZero(replyToMessageID, 0);
|
|
321
|
+
if (explicitReplyToMessageID > 0) {
|
|
322
|
+
return explicitReplyToMessageID;
|
|
323
|
+
}
|
|
324
|
+
const sourceEnvelope = normalizeTelegramMessageEnvelope(sourceEnvelopeRaw);
|
|
325
|
+
const sourceMessageID = intFromRawAllowZero(sourceEnvelope.message_id, 0);
|
|
326
|
+
if (sourceMessageID > 0) {
|
|
327
|
+
return sourceMessageID;
|
|
328
|
+
}
|
|
329
|
+
const sourceReplyToMessageID = intFromRawAllowZero(sourceEnvelope.reply_to_message_id, 0);
|
|
330
|
+
if (sourceReplyToMessageID > 0) {
|
|
331
|
+
return sourceReplyToMessageID;
|
|
332
|
+
}
|
|
333
|
+
return intFromRawAllowZero(fallbackReplyToMessageID, 0);
|
|
334
|
+
}
|
|
335
|
+
|
|
317
336
|
export function buildTelegramBotReplyEnvelope({
|
|
318
337
|
sourceEnvelope: sourceEnvelopeRaw = {},
|
|
319
338
|
chatID = "",
|
|
@@ -499,7 +518,7 @@ function buildContextSpeakerLabel(parsed) {
|
|
|
499
518
|
if (username) {
|
|
500
519
|
return `@${String(username).replace(/^@+/, "")}`;
|
|
501
520
|
}
|
|
502
|
-
return firstNonEmptyString([parsed.sender, parsed.botName
|
|
521
|
+
return firstNonEmptyString([parsed.sender, parsed.botName]);
|
|
503
522
|
}
|
|
504
523
|
|
|
505
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
|
) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveTelegramReplyAnchorMessageID,
|
|
3
|
+
} from "./runner-helpers.mjs";
|
|
4
|
+
|
|
1
5
|
function safeObject(value) {
|
|
2
6
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3
7
|
return {};
|
|
@@ -154,9 +158,20 @@ export function prepareRunnerSelectedRecordIngress({
|
|
|
154
158
|
currentBotSelector,
|
|
155
159
|
});
|
|
156
160
|
const replyMessageThreadID = intFromRawAllowZero(sourceMessageEnvelope.message_thread_id, 0);
|
|
157
|
-
const
|
|
161
|
+
const sourceMessageID = intFromRawAllowZero(sourceMessageEnvelope.message_id, 0);
|
|
162
|
+
const sourceReplyToMessageID = intFromRawAllowZero(sourceMessageEnvelope.reply_to_message_id, 0);
|
|
163
|
+
const replyToMessageID = resolveTelegramReplyAnchorMessageID({
|
|
164
|
+
replyToMessageID: sourceMessageID,
|
|
165
|
+
sourceEnvelope: sourceMessageEnvelope,
|
|
166
|
+
});
|
|
158
167
|
const replyAnchorSource = String(sourceMessageEnvelope.source_origin || "").trim()
|
|
159
|
-
|| (replyToMessageID > 0
|
|
168
|
+
|| (replyToMessageID > 0
|
|
169
|
+
? sourceMessageID > 0
|
|
170
|
+
? "source_message_envelope"
|
|
171
|
+
: sourceReplyToMessageID > 0
|
|
172
|
+
? "source_message_envelope_reply_to"
|
|
173
|
+
: ""
|
|
174
|
+
: "");
|
|
160
175
|
|
|
161
176
|
return {
|
|
162
177
|
handledResult: null,
|