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 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.replyToUsername
2821
- || parsedArchive.conversationTargetBotUsername,
2822
- );
2823
- if (username) {
2824
- return `@${username}`;
2825
- }
2826
- return firstNonEmptyString([
2827
- parsedArchive.sender,
2828
- parsedArchive.botName,
2829
- parsedArchive.replyToSender,
2830
- parsedArchive.chatTitle,
2831
- buildRunnerReplyChainSpeakerType(parsedArchive) === "bot" ? "bot" : "human",
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 = String(conversationID || parsed.conversationID || "").trim() || "-";
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
- : String(parsedArchive.replyToUsername || "").trim()
4024
- ? [parsedArchive.replyToUsername]
4025
- : [],
4026
- normalizeTelegramMentionUsername,
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
- return {
5210
- ok: false,
5211
- reason: "request_already_claimed",
5212
- requestKey,
5213
- };
5214
- }
5215
- const nowISO = new Date().toISOString();
5216
- const normalizedAuthoritativeSourceMessageEnvelope = normalizeRunnerTelegramMessageEnvelope(
5217
- authoritativeSourceMessageEnvelope,
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 = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
5220
- ? normalizedAuthoritativeSourceMessageEnvelope
5221
- : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
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
- createProjectContextItem: async (input) => {
18692
- capturedCreateArgs = input;
18693
- return {
18694
- id: "99999999-8888-4777-8666-555555555555",
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
- const previousRunnerBotID = process.env.METHEUS_RUNNER_BOT_ID;
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() === "active")
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
- "Active project context (stable facts and operating rules):",
2005
+ "Existing project context memory (active and suggested):",
1998
2006
  projectContextItems.length
1999
2007
  ? projectContextItems.map(formatProjectContextLine).join("\n")
2000
2008
  : "- none",
2001
- "Prefer these active project context items over stale room chatter when they conflict.",
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: normalizeProjectContextStatus(toolArgs.status, toolName === "project.context.suggest" ? "suggested" : "active"),
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
+ }
@@ -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, parsed.replyToSender]);
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: normalizeMentionSelector(parsed.replyToUsername),
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
- loadActiveProjectContextItems,
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 loadActiveProjectContextItems !== "function"
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 loadActiveProjectContextItems({
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
  ) {