metheus-governance-mcp-cli 0.2.200 → 0.2.202

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
@@ -2553,6 +2553,76 @@ async function findServerRunnerRequestForMessageID({
2553
2553
  }
2554
2554
  }
2555
2555
 
2556
+ async function loadRunnerArchiveThreadMessageIndex({
2557
+ runtime,
2558
+ threadID,
2559
+ cache = null,
2560
+ }) {
2561
+ const normalizedThreadID = String(threadID || "").trim();
2562
+ if (
2563
+ cache
2564
+ && String(cache.threadID || "").trim() === normalizedThreadID
2565
+ && cache.messageIndex instanceof Map
2566
+ ) {
2567
+ return cache;
2568
+ }
2569
+ if (!normalizedThreadID || !runtime?.baseURL || !runtime?.token) {
2570
+ return {
2571
+ threadID: normalizedThreadID,
2572
+ messageIndex: new Map(),
2573
+ };
2574
+ }
2575
+ try {
2576
+ const comments = await listThreadCommentsTail({
2577
+ siteBaseURL: runtime.baseURL,
2578
+ threadID: normalizedThreadID,
2579
+ token: runtime.token,
2580
+ timeoutSeconds: runtime.timeoutSeconds,
2581
+ actorUserID: runtime.actor?.user_id,
2582
+ tailLimit: 300,
2583
+ scanLimit: 1200,
2584
+ });
2585
+ const messageIndex = new Map();
2586
+ for (const commentRaw of ensureArray(comments)) {
2587
+ const comment = safeObject(commentRaw);
2588
+ const parsedArchive = safeObject(comment.parsedArchive || parseArchivedChatComment(comment.body));
2589
+ const messageID = intFromRawAllowZero(parsedArchive.messageID, 0);
2590
+ if (messageID > 0 && !messageIndex.has(messageID)) {
2591
+ messageIndex.set(messageID, {
2592
+ ...comment,
2593
+ parsedArchive,
2594
+ });
2595
+ }
2596
+ }
2597
+ return {
2598
+ threadID: normalizedThreadID,
2599
+ messageIndex,
2600
+ };
2601
+ } catch {
2602
+ return {
2603
+ threadID: normalizedThreadID,
2604
+ messageIndex: new Map(),
2605
+ };
2606
+ }
2607
+ }
2608
+
2609
+ async function findRunnerArchiveThreadMessageByID({
2610
+ runtime,
2611
+ threadID,
2612
+ messageID,
2613
+ cache = null,
2614
+ }) {
2615
+ const nextCache = await loadRunnerArchiveThreadMessageIndex({
2616
+ runtime,
2617
+ threadID,
2618
+ cache,
2619
+ });
2620
+ return {
2621
+ cache: nextCache,
2622
+ record: safeObject(nextCache.messageIndex.get(intFromRawAllowZero(messageID, 0))),
2623
+ };
2624
+ }
2625
+
2556
2626
  function resolveRunnerReplyChainConversationContext(state, normalizedRoute, selectedRecord) {
2557
2627
  const parsed = safeObject(selectedRecord?.parsedArchive);
2558
2628
  const explicitConversationID = String(parsed.conversationID || "").trim();
@@ -2600,6 +2670,8 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
2600
2670
  normalizedRoute,
2601
2671
  selectedRecord,
2602
2672
  runtime,
2673
+ archiveThreadID = "",
2674
+ hydrationAttempted = false,
2603
2675
  }) {
2604
2676
  const initialState = safeObject(state);
2605
2677
  const initialContext = resolveRunnerReplyChainConversationContext(initialState, normalizedRoute, selectedRecord);
@@ -2611,11 +2683,11 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
2611
2683
  };
2612
2684
  }
2613
2685
  const parsed = safeObject(selectedRecord?.parsedArchive);
2614
- const replyToMessageID = intFromRawAllowZero(
2686
+ const initialReplyToMessageID = intFromRawAllowZero(
2615
2687
  parsed.replyToMessageID || safeObject(initialContext).replyToMessageID,
2616
2688
  0,
2617
2689
  );
2618
- if (replyToMessageID <= 0 || !runtime?.baseURL || !runtime?.token) {
2690
+ if (initialReplyToMessageID <= 0) {
2619
2691
  return {
2620
2692
  state: initialState,
2621
2693
  replyChainContext: initialContext,
@@ -2623,43 +2695,142 @@ async function resolveRunnerReplyChainConversationContextWithServerFallback({
2623
2695
  };
2624
2696
  }
2625
2697
  const chatID = String(parsed.chatID || parsed.chatId || "").trim();
2626
- const serverReferencedRequest = await findServerRunnerRequestForMessageID({
2627
- normalizedRoute,
2628
- runtime,
2629
- chatID,
2630
- messageID: replyToMessageID,
2631
- });
2632
- if (serverReferencedRequest.request_key) {
2633
- const requestIndex = normalizeBotRunnerRequests(initialState.requests);
2634
- requestIndex[String(serverReferencedRequest.request_key || "").trim()] = serverReferencedRequest;
2635
- const anchorMessageID = intFromRawAllowZero(serverReferencedRequest.source_message_id, 0) || replyToMessageID;
2698
+ const normalizedArchiveThreadID = firstNonEmptyString([
2699
+ archiveThreadID,
2700
+ selectedRecord?.thread_id,
2701
+ selectedRecord?.threadID,
2702
+ selectedRecord?.threadId,
2703
+ ]);
2704
+ let stateForLookup = initialState;
2705
+ let replyToMessageID = initialReplyToMessageID;
2706
+ let archiveThreadCache = null;
2707
+ let lastAnchorMessageID = intFromRawAllowZero(safeObject(initialContext).anchorMessageID, 0) || initialReplyToMessageID;
2708
+ const visitedMessageIDs = new Set();
2709
+
2710
+ const buildContextFromRequest = (referencedRequestRaw, reasonSuffix = "") => {
2711
+ const referencedRequest = safeObject(referencedRequestRaw);
2712
+ const anchorMessageID = intFromRawAllowZero(referencedRequest.source_message_id, 0) || replyToMessageID;
2636
2713
  return {
2637
- state: {
2638
- ...initialState,
2639
- requests: requestIndex,
2640
- },
2714
+ state: stateForLookup,
2641
2715
  replyChainContext: {
2642
- conversationID: String(serverReferencedRequest.conversation_id || "").trim()
2716
+ conversationID: String(referencedRequest.conversation_id || "").trim()
2643
2717
  || buildSyntheticReplyChainConversationID(normalizedRoute, chatID, anchorMessageID),
2644
- replyToMessageID,
2718
+ replyToMessageID: initialReplyToMessageID,
2645
2719
  anchorMessageID,
2646
- reason: String(serverReferencedRequest.conversation_id || "").trim()
2647
- ? "reply_request_conversation_server"
2648
- : "reply_request_synthetic_server",
2649
- referencedRequest: serverReferencedRequest,
2720
+ reason: String(referencedRequest.conversation_id || "").trim()
2721
+ ? `reply_request_conversation${reasonSuffix}`
2722
+ : `reply_request_synthetic${reasonSuffix}`,
2723
+ referencedRequest,
2650
2724
  },
2651
- hydrated: false,
2725
+ hydrated: hydrationAttempted,
2652
2726
  };
2727
+ };
2728
+
2729
+ for (let hop = 0; hop < 8 && replyToMessageID > 0; hop += 1) {
2730
+ if (visitedMessageIDs.has(replyToMessageID)) {
2731
+ break;
2732
+ }
2733
+ visitedMessageIDs.add(replyToMessageID);
2734
+
2735
+ const localReferencedRequest = safeObject(findRunnerRequestsForMessageID(stateForLookup, normalizedRoute, {
2736
+ chatID,
2737
+ messageID: replyToMessageID,
2738
+ })[0]);
2739
+ if (localReferencedRequest.request_key) {
2740
+ return buildContextFromRequest(localReferencedRequest, hop > 0 ? "_chain" : "");
2741
+ }
2742
+
2743
+ if (runtime?.baseURL && runtime?.token) {
2744
+ const serverReferencedRequest = await findServerRunnerRequestForMessageID({
2745
+ normalizedRoute,
2746
+ runtime,
2747
+ chatID,
2748
+ messageID: replyToMessageID,
2749
+ });
2750
+ if (serverReferencedRequest.request_key) {
2751
+ const requestIndex = normalizeBotRunnerRequests(stateForLookup.requests);
2752
+ requestIndex[String(serverReferencedRequest.request_key || "").trim()] = serverReferencedRequest;
2753
+ stateForLookup = {
2754
+ ...stateForLookup,
2755
+ requests: requestIndex,
2756
+ };
2757
+ return buildContextFromRequest(serverReferencedRequest, hop > 0 ? "_chain_server" : "_server");
2758
+ }
2759
+ }
2760
+
2761
+ if (!normalizedArchiveThreadID || !runtime?.baseURL || !runtime?.token) {
2762
+ break;
2763
+ }
2764
+ const archivedMessageLookup = await findRunnerArchiveThreadMessageByID({
2765
+ runtime,
2766
+ threadID: normalizedArchiveThreadID,
2767
+ messageID: replyToMessageID,
2768
+ cache: archiveThreadCache,
2769
+ });
2770
+ archiveThreadCache = archivedMessageLookup.cache;
2771
+ const archivedRecord = safeObject(archivedMessageLookup.record);
2772
+ const archivedParsed = safeObject(archivedRecord.parsedArchive || parseArchivedChatComment(archivedRecord.body));
2773
+ const archivedMessageID = intFromRawAllowZero(archivedParsed.messageID, 0) || replyToMessageID;
2774
+ if (archivedMessageID > 0) {
2775
+ lastAnchorMessageID = archivedMessageID;
2776
+ }
2777
+ const archivedConversationID = String(archivedParsed.conversationID || "").trim();
2778
+ if (archivedConversationID) {
2779
+ return {
2780
+ state: stateForLookup,
2781
+ replyChainContext: {
2782
+ conversationID: archivedConversationID,
2783
+ replyToMessageID: initialReplyToMessageID,
2784
+ anchorMessageID: archivedMessageID,
2785
+ reason: "reply_chain_archive_conversation",
2786
+ referencedRequest: null,
2787
+ },
2788
+ hydrated: hydrationAttempted,
2789
+ };
2790
+ }
2791
+ const nextReplyToMessageID = intFromRawAllowZero(archivedParsed.replyToMessageID, 0);
2792
+ if (nextReplyToMessageID <= 0) {
2793
+ return {
2794
+ state: stateForLookup,
2795
+ replyChainContext: {
2796
+ conversationID: buildSyntheticReplyChainConversationID(normalizedRoute, chatID, lastAnchorMessageID || archivedMessageID),
2797
+ replyToMessageID: initialReplyToMessageID,
2798
+ anchorMessageID: lastAnchorMessageID || archivedMessageID,
2799
+ reason: "reply_chain_archive_synthetic",
2800
+ referencedRequest: null,
2801
+ },
2802
+ hydrated: hydrationAttempted,
2803
+ };
2804
+ }
2805
+ replyToMessageID = nextReplyToMessageID;
2653
2806
  }
2654
- const hydratedState = await hydrateRunnerRequestLedgerFromServer({
2655
- normalizedRoute,
2656
- runtime,
2657
- });
2658
- const hydratedContext = resolveRunnerReplyChainConversationContext(hydratedState, normalizedRoute, selectedRecord);
2807
+
2808
+ if (!hydrationAttempted && runtime?.baseURL && runtime?.token) {
2809
+ const hydratedState = await hydrateRunnerRequestLedgerFromServer({
2810
+ normalizedRoute,
2811
+ runtime,
2812
+ });
2813
+ const hydratedResolution = await resolveRunnerReplyChainConversationContextWithServerFallback({
2814
+ state: hydratedState,
2815
+ normalizedRoute,
2816
+ selectedRecord,
2817
+ runtime,
2818
+ archiveThreadID: normalizedArchiveThreadID,
2819
+ hydrationAttempted: true,
2820
+ });
2821
+ return {
2822
+ state: hydratedResolution.state,
2823
+ replyChainContext: hydratedResolution.replyChainContext,
2824
+ hydrated: true,
2825
+ };
2826
+ }
2827
+
2659
2828
  return {
2660
- state: hydratedState,
2661
- replyChainContext: hydratedContext,
2662
- hydrated: true,
2829
+ state: stateForLookup,
2830
+ replyChainContext: hydrationAttempted
2831
+ ? resolveRunnerReplyChainConversationContext(stateForLookup, normalizedRoute, selectedRecord)
2832
+ : initialContext,
2833
+ hydrated: hydrationAttempted,
2663
2834
  };
2664
2835
  }
2665
2836
 
@@ -2709,6 +2880,7 @@ async function claimRunnerRequestForHumanComment({
2709
2880
  selectedBotUsernames = [],
2710
2881
  normalizedIntent = "",
2711
2882
  runtime = null,
2883
+ archiveThreadID = "",
2712
2884
  }) {
2713
2885
  const parsed = safeObject(selectedRecord?.parsedArchive);
2714
2886
  const commentKind = String(parsed.kind || "").trim().toLowerCase();
@@ -2724,6 +2896,7 @@ async function claimRunnerRequestForHumanComment({
2724
2896
  normalizedRoute,
2725
2897
  selectedRecord,
2726
2898
  runtime,
2899
+ archiveThreadID,
2727
2900
  });
2728
2901
  const replyChainContext = safeObject(replyChainResolution.replyChainContext);
2729
2902
  const referencedRequest = safeObject(replyChainContext.referencedRequest);
@@ -5431,9 +5604,6 @@ function formatTelegramInboundArchiveComment(normalized) {
5431
5604
  if (normalized.mentionUsernames.length > 0) {
5432
5605
  headerLines.push(`mention_usernames: ${normalized.mentionUsernames.map((item) => `@${item}`).join(", ")}`);
5433
5606
  }
5434
- if (normalized.messageThreadID) {
5435
- headerLines.push(`telegram_topic_id: ${normalized.messageThreadID}`);
5436
- }
5437
5607
  if (normalized.replyToMessageID > 0) {
5438
5608
  headerLines.push(`reply_to_message_id: ${normalized.replyToMessageID}`);
5439
5609
  headerLines.push(`reply_to_sender_is_bot: ${normalized.replyToFromIsBot ? "true" : "false"}`);
@@ -6149,6 +6319,7 @@ function stableTextModulo(rawValue, modulo = 1) {
6149
6319
  return hash % normalizedModulo;
6150
6320
  }
6151
6321
 
6322
+ /* Dead small_talk helper kept only as commented legacy code after lookup-only migration.
6152
6323
  function buildRunnerSmallTalkReply({ route, executionPlan } = {}) {
6153
6324
  const normalizedRoute = safeObject(route);
6154
6325
  const roleProfileName = normalizeRunnerRoleProfileName(
@@ -6185,6 +6356,30 @@ function buildRunnerSmallTalkReply({ route, executionPlan } = {}) {
6185
6356
  const templatePool = roleSpecificReplies[roleProfileName] || roleSpecificReplies.monitor;
6186
6357
  return templatePool[stableTextModulo(`${displayName}:${roleProfileName}`, templatePool.length)];
6187
6358
  }
6359
+ */
6360
+
6361
+ function buildInformationalMiniExecutionOverride({ route, executionPlan } = {}) {
6362
+ const safeRoute = safeObject(route);
6363
+ const safeExecutionPlan = safeObject(executionPlan);
6364
+ const roleProfileName = normalizeRunnerRoleProfileName(
6365
+ safeExecutionPlan.roleProfileName || safeRoute.roleProfile || safeRoute.role || "monitor",
6366
+ ) || "monitor";
6367
+ const lightweightModel = String(
6368
+ resolveResponderAdjudicatorModelDisplayName({ client: "gpt", env: process.env }) || "gpt-5.3-codex-spark",
6369
+ ).trim() || "gpt-5.3-codex-spark";
6370
+ return {
6371
+ mode: "role_profile",
6372
+ role_profile_name: roleProfileName,
6373
+ role_profile: normalizeRunnerRoleProfile(roleProfileName, {
6374
+ client: "gpt",
6375
+ model: lightweightModel,
6376
+ permission_mode: "read_only",
6377
+ reasoning_effort: "low",
6378
+ }),
6379
+ workspace_dir: String(safeExecutionPlan.workspaceDir || safeRoute.workspaceDir || "").trim(),
6380
+ workspace_source: String(safeExecutionPlan.workspaceSource || "").trim(),
6381
+ };
6382
+ }
6188
6383
 
6189
6384
  function summarizeRunnerRequestForStatusLookup(entryRaw) {
6190
6385
  const entry = safeObject(entryRaw);
@@ -6411,13 +6606,23 @@ async function resolveInformationalQueryReply({
6411
6606
  }) {
6412
6607
  const normalizedIntentType = String(intentType || "").trim();
6413
6608
  const messageText = String(safeObject(selectedRecord?.parsedArchive).body || "").trim();
6609
+ const executionOverride = buildInformationalMiniExecutionOverride({ route, executionPlan });
6414
6610
  if (normalizedIntentType === "small_talk") {
6415
6611
  return {
6416
6612
  handled: true,
6417
- reply: buildRunnerSmallTalkReply({ route, executionPlan }),
6613
+ response_mode: "lookup_only",
6614
+ reply: "",
6418
6615
  source: "small_talk",
6616
+ lookup: {
6617
+ intent_type: "small_talk",
6618
+ user_message: messageText,
6619
+ bot_name: firstNonEmptyString([route?.botName, route?.serverBotName, route?.server_bot_name, route?.name]),
6620
+ bot_role: String(route?.role || route?.roleProfile || "").trim(),
6621
+ },
6622
+ execution_override: executionOverride,
6419
6623
  };
6420
6624
  }
6625
+ /* Dead legacy direct small_talk branch kept commented for reference.
6421
6626
  if (false && normalizedIntentType === "small_talk") {
6422
6627
  return {
6423
6628
  handled: true,
@@ -6425,6 +6630,7 @@ async function resolveInformationalQueryReply({
6425
6630
  source: "small_talk",
6426
6631
  };
6427
6632
  }
6633
+ */
6428
6634
  if (normalizedIntentType === "workspace_query") {
6429
6635
  const workspace = resolveProjectWorkspaceBindingSummary(route?.projectID, executionPlan?.workspaceDir || route?.workspaceDir || "");
6430
6636
  const workspaceDir = String(workspace.workspace_dir || "").trim();
@@ -6433,9 +6639,13 @@ async function resolveInformationalQueryReply({
6433
6639
  : "현재 이 프로젝트는 project-workspaces.json에 로컬 작업 폴더가 바인딩되어 있지 않습니다.";
6434
6640
  return {
6435
6641
  handled: true,
6436
- reply,
6642
+ response_mode: "lookup_only",
6643
+ reply: "",
6437
6644
  source: "project.workspace",
6438
- lookup: workspace,
6645
+ lookup: {
6646
+ ...workspace,
6647
+ },
6648
+ execution_override: executionOverride,
6439
6649
  };
6440
6650
  }
6441
6651
  if (normalizedIntentType === "bot_role_query") {
@@ -6447,9 +6657,13 @@ async function resolveInformationalQueryReply({
6447
6657
  });
6448
6658
  return {
6449
6659
  handled: true,
6450
- reply: buildProjectBotRolesText(payload),
6660
+ response_mode: "lookup_only",
6661
+ reply: "",
6451
6662
  source: "project.bot_roles",
6452
- lookup: payload,
6663
+ lookup: {
6664
+ ...payload,
6665
+ },
6666
+ execution_override: executionOverride,
6453
6667
  };
6454
6668
  }
6455
6669
  if (normalizedIntentType === "status_query") {
@@ -6512,9 +6726,13 @@ async function resolveInformationalQueryReply({
6512
6726
  });
6513
6727
  return {
6514
6728
  handled: true,
6515
- reply: buildProjectFileLocateText(payload),
6729
+ response_mode: "lookup_only",
6730
+ reply: "",
6516
6731
  source: "project.file.locate",
6517
- lookup: payload,
6732
+ lookup: {
6733
+ ...payload,
6734
+ },
6735
+ execution_override: executionOverride,
6518
6736
  };
6519
6737
  }
6520
6738
  return null;
@@ -7142,6 +7360,7 @@ async function processRunnerRouteOnce(route, runtime, mode, options = {}) {
7142
7360
  selectedRecord,
7143
7361
  selectedBotUsernames: selectedResponderSelectors,
7144
7362
  runtime,
7363
+ archiveThreadID: archiveThread.threadID,
7145
7364
  });
7146
7365
  };
7147
7366
  if (deferExecution) {
@@ -14175,20 +14394,44 @@ async function runSelftest(flags = {}) {
14175
14394
  String(parsedEnv.SLACK_BOT_TOKEN || "") === "xoxb-test",
14176
14395
  `token=${String(parsedEnv.SLACK_BOT_TOKEN || "(missing)")}`,
14177
14396
  );
14178
- const monitorGreeting = buildRunnerSmallTalkReply({
14179
- route: { botName: "RyoAI_bot", roleProfile: "monitor" },
14180
- executionPlan: { roleProfileName: "monitor" },
14397
+ const informationalMiniReply = await resolveInformationalQueryReply({
14398
+ intentType: "small_talk",
14399
+ route: { botName: "RyoAI_bot", role: "monitor", roleProfile: "monitor" },
14400
+ routeState: {},
14401
+ selectedRecord: {
14402
+ parsedArchive: {
14403
+ body: "@RyoAI_bot 하이",
14404
+ },
14405
+ },
14406
+ runtime: {},
14407
+ executionPlan: {
14408
+ mode: "role_profile",
14409
+ roleProfileName: "monitor",
14410
+ roleProfile: {
14411
+ client: "gpt",
14412
+ model: "gpt-5.4",
14413
+ permissionMode: "workspace_write",
14414
+ reasoningEffort: "medium",
14415
+ },
14416
+ workspaceDir: "C:\\selftest-workspace",
14417
+ workspaceSource: "selftest",
14418
+ },
14181
14419
  });
14182
- const reviewGreeting = buildRunnerSmallTalkReply({
14183
- route: { botName: "RyoAI2_bot", roleProfile: "review" },
14184
- executionPlan: { roleProfileName: "review" },
14420
+ const expectedInformationalModel = resolveResponderAdjudicatorModelDisplayName({
14421
+ client: "gpt",
14422
+ env: process.env,
14185
14423
  });
14186
14424
  push(
14187
- "small_talk_reply_varies_by_bot_and_role",
14188
- monitorGreeting.includes("RyoAI_bot")
14189
- && reviewGreeting.includes("RyoAI2_bot")
14190
- && monitorGreeting !== reviewGreeting,
14191
- `monitor=${monitorGreeting} review=${reviewGreeting}`,
14425
+ "small_talk_reply_uses_lookup_only_mini_override",
14426
+ safeObject(informationalMiniReply).handled === true
14427
+ && String(safeObject(informationalMiniReply).response_mode || "") === "lookup_only"
14428
+ && String(safeObject(informationalMiniReply).reply || "") === ""
14429
+ && !Object.prototype.hasOwnProperty.call(safeObject(safeObject(informationalMiniReply).lookup), "proposed_summary")
14430
+ && String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.client || "") === "gpt"
14431
+ && String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.model || "") === String(expectedInformationalModel || "")
14432
+ && String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.permissionMode || "") === "read_only"
14433
+ && String(safeObject(safeObject(informationalMiniReply).execution_override).role_profile?.reasoningEffort || "") === "low",
14434
+ JSON.stringify(safeObject(informationalMiniReply).execution_override || {}),
14192
14435
  );
14193
14436
  const telegramEnvV2Parsed = parseSimpleEnvText(`
14194
14437
  TELEGRAM_API_BASE_URL=http://127.0.0.1:8999/api
@@ -1786,9 +1786,6 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1786
1786
  if (queryLookupResponseMode) {
1787
1787
  lines.push(`- Response mode: ${queryLookupResponseMode}`);
1788
1788
  }
1789
- if (String(queryLookup.proposed_reply || "").trim()) {
1790
- lines.push(`- Proposed factual reply: ${String(queryLookup.proposed_reply || "").trim()}`);
1791
- }
1792
1789
  if (queryLookupFacts != null) {
1793
1790
  lines.push("Structured lookup facts:");
1794
1791
  lines.push(JSON.stringify(queryLookupFacts, null, 2));
@@ -1994,7 +1991,7 @@ export function buildLocalBotPrompt(payload, { terse = true } = {}) {
1994
1991
  String(conversation.intent_mode || "").trim() === "delegated_single_lead"
1995
1992
  ? (selfIsLeadBot
1996
1993
  ? "This bot is the lead bot for a delegated public collaboration. It may assign work publicly only to allowed responders, and it may not expand the participant set."
1997
- : "This bot is not the lead bot. Respond only when the lead bot or the human explicitly addresses this bot, and do not start peer-to-peer chains.")
1994
+ : "This bot is not the lead bot. Prefer replying when the human, the lead bot, or another managed bot explicitly addresses this bot in the live room context. Do not expand the participant set beyond the allowed responders.")
1998
1995
  : "Use the human conversation contract as the source of truth for who may reply.",
1999
1996
  conversation.allow_bot_to_bot === true
2000
1997
  ? "If another bot explicitly mentioned you and you are in the allowed responders list, you may answer that bot publicly in the room."
@@ -827,6 +827,7 @@ export async function discoverArchiveThreadForDestination(
827
827
  const providerEnvConfig = requireDependency(deps, "providerEnvConfig");
828
828
  const parseArchivedChatComment = requireDependency(deps, "parseArchivedChatComment");
829
829
  const providerLabel = providerEnvConfig(provider).label;
830
+ const hasConfiguredArchiveWorkItem = archiveWorkItemID && isUUID(archiveWorkItemID);
830
831
 
831
832
  if (archiveThreadID && isUUID(archiveThreadID)) {
832
833
  return {
@@ -837,8 +838,7 @@ export async function discoverArchiveThreadForDestination(
837
838
  }
838
839
 
839
840
  const candidateWorkItemIDs = [];
840
- let reusableWorkItemID = "";
841
- if (archiveWorkItemID && isUUID(archiveWorkItemID)) {
841
+ if (hasConfiguredArchiveWorkItem) {
842
842
  candidateWorkItemIDs.push(archiveWorkItemID);
843
843
  } else {
844
844
  const workItems = await listProjectWorkItems({
@@ -864,11 +864,9 @@ export async function discoverArchiveThreadForDestination(
864
864
  token,
865
865
  timeoutSeconds,
866
866
  }, deps);
867
- let fallbackThreadID = "";
868
867
  for (const thread of threads) {
869
868
  const threadID = String(thread.id || "").trim();
870
869
  if (!threadID) continue;
871
- if (!fallbackThreadID) fallbackThreadID = threadID;
872
870
  const comments = await listThreadComments({
873
871
  siteBaseURL,
874
872
  threadID,
@@ -889,15 +887,8 @@ export async function discoverArchiveThreadForDestination(
889
887
  };
890
888
  }
891
889
  }
892
- if (fallbackThreadID) {
893
- return {
894
- threadID: fallbackThreadID,
895
- workItemID,
896
- source: "fallback-thread",
897
- };
898
- }
899
- if (!reusableWorkItemID) {
900
- reusableWorkItemID = workItemID;
890
+ if (hasConfiguredArchiveWorkItem) {
891
+ break;
901
892
  }
902
893
  }
903
894
 
@@ -907,7 +898,9 @@ export async function discoverArchiveThreadForDestination(
907
898
  const archiveDescription = `Auto-created archive work item for ${providerLabel} destination ${destinationLabel}.`;
908
899
  try {
909
900
  const threadBody = `Auto-created archive thread for ${providerLabel} destination ${destinationLabel} (chat_id=${destination.chatID}).`;
910
- let resolvedWorkItemID = String(reusableWorkItemID || "").trim();
901
+ let resolvedWorkItemID = hasConfiguredArchiveWorkItem
902
+ ? String(archiveWorkItemID || "").trim()
903
+ : "";
911
904
  if (!resolvedWorkItemID) {
912
905
  const createdWorkItem = await createProjectWorkItem(
913
906
  {
@@ -941,7 +934,9 @@ export async function discoverArchiveThreadForDestination(
941
934
  return {
942
935
  threadID: createdThreadID,
943
936
  workItemID: resolvedWorkItemID,
944
- source: reusableWorkItemID ? "auto-created-thread-on-existing-work-item" : "auto-created-thread",
937
+ source: hasConfiguredArchiveWorkItem
938
+ ? "auto-created-thread-on-configured-work-item"
939
+ : "auto-created-thread",
945
940
  };
946
941
  }
947
942
  }