metheus-governance-mcp-cli 0.2.284 → 0.2.286
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 +188 -66
- 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/runner-recovery.mjs +83 -17
- package/lib/selftest-runner-scenarios.mjs +664 -45
- 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);
|
|
@@ -3564,7 +3562,9 @@ function buildRunnerRequestKey({
|
|
|
3564
3562
|
const intent = canonicalHumanKey
|
|
3565
3563
|
? "-"
|
|
3566
3564
|
: String(normalizedIntent || "").trim().toLowerCase() || "-";
|
|
3567
|
-
const resolvedConversationID =
|
|
3565
|
+
const resolvedConversationID = canonicalHumanKey
|
|
3566
|
+
? "-"
|
|
3567
|
+
: String(conversationID || parsed.conversationID || "").trim() || "-";
|
|
3568
3568
|
return [
|
|
3569
3569
|
String(normalizedRoute?.projectID || "").trim() || "-",
|
|
3570
3570
|
String(normalizedRoute?.provider || "").trim() || "-",
|
|
@@ -3996,10 +3996,10 @@ function runnerRequestPreferredNextExpectedResponders(entryRaw) {
|
|
|
3996
3996
|
);
|
|
3997
3997
|
}
|
|
3998
3998
|
|
|
3999
|
-
function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
4000
|
-
const entry = safeObject(entryRaw);
|
|
4001
|
-
const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
|
|
4002
|
-
return uniqueOrderedStrings(
|
|
3999
|
+
function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
4000
|
+
const entry = safeObject(entryRaw);
|
|
4001
|
+
const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
|
|
4002
|
+
return uniqueOrderedStrings(
|
|
4003
4003
|
runnerRequestPreferredNextExpectedResponders(entry).length
|
|
4004
4004
|
? runnerRequestPreferredNextExpectedResponders(entry)
|
|
4005
4005
|
: runnerRequestPreferredExecutionContractTargets(entry).length
|
|
@@ -4015,17 +4015,47 @@ function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
|
|
|
4015
4015
|
);
|
|
4016
4016
|
}
|
|
4017
4017
|
|
|
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
|
-
|
|
4018
|
+
function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
|
|
4019
|
+
const parsedArchive = safeObject(parsedArchiveRaw);
|
|
4020
|
+
return uniqueOrderedStrings(
|
|
4021
|
+
ensureArray(parsedArchive.mentionUsernames).length
|
|
4022
|
+
? parsedArchive.mentionUsernames
|
|
4023
|
+
: parsedArchive.replyToSenderIsBot === true
|
|
4024
|
+
&& String(parsedArchive.replyToUsername || "").trim()
|
|
4025
|
+
? [parsedArchive.replyToUsername]
|
|
4026
|
+
: [],
|
|
4027
|
+
normalizeTelegramMentionUsername,
|
|
4028
|
+
);
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
function runnerRequestAllowsSharedHumanClaimTakeover({
|
|
4032
|
+
entryRaw,
|
|
4033
|
+
currentBotUsername = "",
|
|
4034
|
+
canonicalHumanMessageKey = "",
|
|
4035
|
+
}) {
|
|
4036
|
+
const entry = safeObject(entryRaw);
|
|
4037
|
+
const normalizedCurrentBotUsername = normalizeTelegramMentionUsername(currentBotUsername);
|
|
4038
|
+
const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
|
|
4039
|
+
if (!normalizedCurrentBotUsername || !normalizedCanonicalHumanMessageKey) {
|
|
4040
|
+
return false;
|
|
4041
|
+
}
|
|
4042
|
+
if (String(entry.canonical_human_message_key || "").trim() !== normalizedCanonicalHumanMessageKey) {
|
|
4043
|
+
return false;
|
|
4044
|
+
}
|
|
4045
|
+
const pendingResponders = runnerRequestPreferredNextExpectedResponders(entry);
|
|
4046
|
+
if (pendingResponders.length > 0) {
|
|
4047
|
+
return pendingResponders.includes(normalizedCurrentBotUsername);
|
|
4048
|
+
}
|
|
4049
|
+
const directHumanResponders = uniqueOrderedStrings(
|
|
4050
|
+
[
|
|
4051
|
+
...ensureArray(entry.selected_bot_usernames),
|
|
4052
|
+
...ensureArray(entry.conversation_initial_responders),
|
|
4053
|
+
...ensureArray(entry.conversation_allowed_responders),
|
|
4054
|
+
],
|
|
4055
|
+
normalizeTelegramMentionUsername,
|
|
4056
|
+
);
|
|
4057
|
+
return directHumanResponders.includes(normalizedCurrentBotUsername);
|
|
4058
|
+
}
|
|
4029
4059
|
|
|
4030
4060
|
function runnerRequestPreferredAIReplyPreview(entryRaw) {
|
|
4031
4061
|
const entry = safeObject(entryRaw);
|
|
@@ -5068,8 +5098,8 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5068
5098
|
reason: "non_human_comment_cannot_create_request",
|
|
5069
5099
|
};
|
|
5070
5100
|
}
|
|
5071
|
-
const currentState = loadBotRunnerState();
|
|
5072
|
-
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
5101
|
+
const currentState = loadBotRunnerState();
|
|
5102
|
+
const currentRouteState = safeObject(safeObject(currentState.routes)[String(routeKey || "").trim()]);
|
|
5073
5103
|
const replyChainResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
|
|
5074
5104
|
state: currentState,
|
|
5075
5105
|
normalizedRoute,
|
|
@@ -5169,6 +5199,16 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5169
5199
|
const authoritativeDecisionBundle = decisionBundleValidation.ok === true
|
|
5170
5200
|
? safeObject(decisionBundleValidation.bundle)
|
|
5171
5201
|
: {};
|
|
5202
|
+
const normalizedAuthoritativeSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(
|
|
5203
|
+
authoritativeSourceMessageEnvelope,
|
|
5204
|
+
);
|
|
5205
|
+
const currentClaimBotUsername = normalizeTelegramMentionUsername(
|
|
5206
|
+
normalizedAuthoritativeSourceMessageEnvelope.source_bot_username
|
|
5207
|
+
|| normalizedAuthoritativeSourceMessageEnvelope.sender_username
|
|
5208
|
+
|| currentRouteState.last_source_bot_username
|
|
5209
|
+
|| currentRouteState.source_message_bot_username
|
|
5210
|
+
|| currentRouteState.last_speaker_bot_username,
|
|
5211
|
+
);
|
|
5172
5212
|
const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
|
|
5173
5213
|
selectedRecord,
|
|
5174
5214
|
replyChainContext,
|
|
@@ -5201,24 +5241,35 @@ async function claimRunnerRequestForHumanComment({
|
|
|
5201
5241
|
requestKey,
|
|
5202
5242
|
};
|
|
5203
5243
|
}
|
|
5204
|
-
if (
|
|
5205
|
-
isActiveRunnerRequestStatus(existing.status)
|
|
5206
|
-
&& String(existing.claimed_by_route || "").trim()
|
|
5207
|
-
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5244
|
+
if (
|
|
5245
|
+
isActiveRunnerRequestStatus(existing.status)
|
|
5246
|
+
&& String(existing.claimed_by_route || "").trim()
|
|
5247
|
+
&& String(existing.claimed_by_route || "").trim() !== String(routeKey || "").trim()
|
|
5248
|
+
&& !runnerRequestAllowsSharedHumanClaimTakeover({
|
|
5249
|
+
entryRaw: existing,
|
|
5250
|
+
currentBotUsername: currentClaimBotUsername,
|
|
5251
|
+
canonicalHumanMessageKey,
|
|
5252
|
+
})
|
|
5253
|
+
) {
|
|
5254
|
+
return {
|
|
5255
|
+
ok: false,
|
|
5256
|
+
reason: "request_already_claimed",
|
|
5257
|
+
requestKey,
|
|
5258
|
+
};
|
|
5259
|
+
}
|
|
5260
|
+
const nowISO = new Date().toISOString();
|
|
5261
|
+
const existingSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
|
|
5262
|
+
const preserveExistingCanonicalSourceEnvelope = Boolean(
|
|
5263
|
+
canonicalHumanMessageKey
|
|
5264
|
+
&& String(existing.canonical_human_message_key || "").trim() === canonicalHumanMessageKey
|
|
5265
|
+
&& intFromRawAllowZero(existing.source_message_id, 0) === currentMessageID
|
|
5266
|
+
&& Object.keys(existingSourceMessageEnvelope).length > 0
|
|
5218
5267
|
);
|
|
5219
|
-
const sourceMessageEnvelope =
|
|
5220
|
-
?
|
|
5221
|
-
:
|
|
5268
|
+
const sourceMessageEnvelope = preserveExistingCanonicalSourceEnvelope
|
|
5269
|
+
? existingSourceMessageEnvelope
|
|
5270
|
+
: Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
|
|
5271
|
+
? normalizedAuthoritativeSourceMessageEnvelope
|
|
5272
|
+
: existingSourceMessageEnvelope;
|
|
5222
5273
|
const decisionConversationParticipants = uniqueOrderedStrings(
|
|
5223
5274
|
ensureArray(authoritativeDecisionBundle.participants),
|
|
5224
5275
|
normalizeTelegramMentionUsername,
|
|
@@ -18685,13 +18736,14 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18685
18736
|
workspaceDir: "",
|
|
18686
18737
|
workspaceSignalTrusted: true,
|
|
18687
18738
|
},
|
|
18688
|
-
{
|
|
18689
|
-
...buildLocalProjectDispatchDeps(),
|
|
18690
|
-
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18691
|
-
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
|
|
18739
|
+
{
|
|
18740
|
+
...buildLocalProjectDispatchDeps(),
|
|
18741
|
+
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18742
|
+
listProjectContextItems: async () => ([]),
|
|
18743
|
+
createProjectContextItem: async (input) => {
|
|
18744
|
+
capturedCreateArgs = input;
|
|
18745
|
+
return {
|
|
18746
|
+
id: "99999999-8888-4777-8666-555555555555",
|
|
18695
18747
|
project_id: input.projectID,
|
|
18696
18748
|
title: input.title,
|
|
18697
18749
|
body: input.body,
|
|
@@ -18715,11 +18767,81 @@ TELEGRAM_BOT_REVIEW_TOKEN=review-token
|
|
|
18715
18767
|
&& String(safeObject(capturedCreateArgs).category || "").trim() === "bot_role",
|
|
18716
18768
|
`status=${String(safeObject(suggestStructured.item).status || "").trim() || "(none)"} category=${String(safeObject(capturedCreateArgs).category || "").trim() || "(none)"}`,
|
|
18717
18769
|
);
|
|
18718
|
-
} catch (err) {
|
|
18719
|
-
push("local_project_context_suggest_tool_dispatches", false, String(err?.message || err));
|
|
18720
|
-
}
|
|
18721
|
-
|
|
18722
|
-
|
|
18770
|
+
} catch (err) {
|
|
18771
|
+
push("local_project_context_suggest_tool_dispatches", false, String(err?.message || err));
|
|
18772
|
+
}
|
|
18773
|
+
|
|
18774
|
+
try {
|
|
18775
|
+
let duplicateCreateCalls = 0;
|
|
18776
|
+
const duplicateSuggestResponse = await handleLocalProjectToolDispatchImpl(
|
|
18777
|
+
{
|
|
18778
|
+
requestObj: {
|
|
18779
|
+
jsonrpc: "2.0",
|
|
18780
|
+
id: 5,
|
|
18781
|
+
method: "tools/call",
|
|
18782
|
+
params: {
|
|
18783
|
+
name: "project.context.suggest",
|
|
18784
|
+
arguments: {
|
|
18785
|
+
project_id: selftestProjectID,
|
|
18786
|
+
title: "Bot role split",
|
|
18787
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18788
|
+
category: "bot_role",
|
|
18789
|
+
importance: "high",
|
|
18790
|
+
},
|
|
18791
|
+
},
|
|
18792
|
+
},
|
|
18793
|
+
toolName: "project.context.suggest",
|
|
18794
|
+
toolArgs: {
|
|
18795
|
+
project_id: selftestProjectID,
|
|
18796
|
+
title: "Bot role split",
|
|
18797
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18798
|
+
category: "bot_role",
|
|
18799
|
+
importance: "high",
|
|
18800
|
+
},
|
|
18801
|
+
args: {
|
|
18802
|
+
baseURL: DEFAULT_SITE_URL,
|
|
18803
|
+
timeoutSeconds: 30,
|
|
18804
|
+
},
|
|
18805
|
+
token: "selftest-token",
|
|
18806
|
+
workspaceDir: "",
|
|
18807
|
+
workspaceSignalTrusted: true,
|
|
18808
|
+
},
|
|
18809
|
+
{
|
|
18810
|
+
...buildLocalProjectDispatchDeps(),
|
|
18811
|
+
extractActorFromToken: () => ({ user_id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" }),
|
|
18812
|
+
listProjectContextItems: async () => ([
|
|
18813
|
+
{
|
|
18814
|
+
id: "ctx-duplicate-suggested",
|
|
18815
|
+
project_id: selftestProjectID,
|
|
18816
|
+
title: "Bot role split",
|
|
18817
|
+
body: "RyoAI2_bot handles deep review when explicitly asked.",
|
|
18818
|
+
category: "bot_role",
|
|
18819
|
+
importance: "high",
|
|
18820
|
+
status: "suggested",
|
|
18821
|
+
},
|
|
18822
|
+
]),
|
|
18823
|
+
createProjectContextItem: async () => {
|
|
18824
|
+
duplicateCreateCalls += 1;
|
|
18825
|
+
return {};
|
|
18826
|
+
},
|
|
18827
|
+
},
|
|
18828
|
+
);
|
|
18829
|
+
const duplicateSuggestStructured = safeObject(safeObject(duplicateSuggestResponse).result)?.structuredContent || {};
|
|
18830
|
+
push(
|
|
18831
|
+
"local_project_context_suggest_tool_skips_exact_duplicate",
|
|
18832
|
+
duplicateSuggestStructured.ok === true
|
|
18833
|
+
&& duplicateSuggestStructured.created === false
|
|
18834
|
+
&& duplicateSuggestStructured.duplicate === true
|
|
18835
|
+
&& String(duplicateSuggestStructured.reason || "").trim() === "duplicate_suggested"
|
|
18836
|
+
&& duplicateCreateCalls === 0
|
|
18837
|
+
&& String(safeObject(duplicateSuggestStructured.item).id || "").trim() === "ctx-duplicate-suggested",
|
|
18838
|
+
`created=${String(duplicateSuggestStructured.created)} duplicate=${String(duplicateSuggestStructured.duplicate)} reason=${String(duplicateSuggestStructured.reason || "").trim() || "(none)"} create_calls=${duplicateCreateCalls}`,
|
|
18839
|
+
);
|
|
18840
|
+
} catch (err) {
|
|
18841
|
+
push("local_project_context_suggest_tool_skips_exact_duplicate", false, String(err?.message || err));
|
|
18842
|
+
}
|
|
18843
|
+
|
|
18844
|
+
const previousRunnerBotID = process.env.METHEUS_RUNNER_BOT_ID;
|
|
18723
18845
|
const previousRunnerBotName = process.env.METHEUS_RUNNER_BOT_NAME;
|
|
18724
18846
|
try {
|
|
18725
18847
|
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
|
) {
|