metheus-governance-mcp-cli 0.2.278 → 0.2.280

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
@@ -148,10 +148,14 @@ import {
148
148
  normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
149
149
  normalizeArchiveCommentRecord,
150
150
  selectPendingArchiveComments,
151
- printRunnerResult,
152
- } from "./lib/runner-helpers.mjs";
153
- import {
154
- createProjectContextItem as createProjectContextItemImpl,
151
+ printRunnerResult,
152
+ } from "./lib/runner-helpers.mjs";
153
+ import {
154
+ normalizeRunnerConversationDecisionBundle,
155
+ validateRunnerConversationDecisionBundle,
156
+ } from "./lib/runner-orchestration-decision-bundle.mjs";
157
+ import {
158
+ createProjectContextItem as createProjectContextItemImpl,
155
159
  createProjectEvidence as createProjectEvidenceImpl,
156
160
  createProjectWorkItem as createProjectWorkItemImpl,
157
161
  createThreadComment as createThreadCommentImpl,
@@ -3154,13 +3158,26 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
3154
3158
  root_comment_id: String(entry.root_comment_id || entry.rootCommentID || "").trim(),
3155
3159
  root_comment_kind: String(entry.root_comment_kind || entry.rootCommentKind || "").trim().toLowerCase(),
3156
3160
  conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
3157
- reply_chain_context: normalizeRunnerReplyChainContext(entry.reply_chain_context || entry.replyChainContext),
3158
- selected_bot_usernames: ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)
3159
- .map((value) => normalizeTelegramMentionUsername(value))
3160
- .filter(Boolean),
3161
- conversation_allowed_responders: ensureArray(entry.conversation_allowed_responders || entry.conversationAllowedResponders)
3162
- .map((value) => normalizeTelegramMentionUsername(value))
3163
- .filter(Boolean),
3161
+ reply_chain_context: normalizeRunnerReplyChainContext(entry.reply_chain_context || entry.replyChainContext),
3162
+ selected_bot_usernames: ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)
3163
+ .map((value) => normalizeTelegramMentionUsername(value))
3164
+ .filter(Boolean),
3165
+ authoritative_decision_bundle: Object.keys(
3166
+ safeObject(entry.authoritative_decision_bundle || entry.authoritativeDecisionBundle),
3167
+ ).length
3168
+ ? normalizeRunnerConversationDecisionBundle(
3169
+ entry.authoritative_decision_bundle || entry.authoritativeDecisionBundle,
3170
+ )
3171
+ : {},
3172
+ decision_bundle_validation_status: String(
3173
+ entry.decision_bundle_validation_status || entry.decisionBundleValidationStatus || "",
3174
+ ).trim().toLowerCase(),
3175
+ decision_bundle_validation_reason: String(
3176
+ entry.decision_bundle_validation_reason || entry.decisionBundleValidationReason || "",
3177
+ ).trim(),
3178
+ conversation_allowed_responders: ensureArray(entry.conversation_allowed_responders || entry.conversationAllowedResponders)
3179
+ .map((value) => normalizeTelegramMentionUsername(value))
3180
+ .filter(Boolean),
3164
3181
  conversation_intent_mode: String(entry.conversation_intent_mode || entry.conversationIntentMode || "").trim().toLowerCase(),
3165
3182
  conversation_lead_bot: normalizeTelegramMentionUsername(entry.conversation_lead_bot || entry.conversationLeadBot),
3166
3183
  conversation_summary_bot: normalizeTelegramMentionUsername(entry.conversation_summary_bot || entry.conversationSummaryBot),
@@ -3870,10 +3887,10 @@ async function findServerRunnerRequestForMessageID({
3870
3887
  }
3871
3888
  }
3872
3889
 
3873
- function runnerRequestHasConversationContractData(entryRaw) {
3874
- const entry = safeObject(entryRaw);
3875
- return Boolean(
3876
- String(entry.conversation_intent_mode || "").trim()
3890
+ function runnerRequestHasConversationContractData(entryRaw) {
3891
+ const entry = safeObject(entryRaw);
3892
+ return Boolean(
3893
+ String(entry.conversation_intent_mode || "").trim()
3877
3894
  || String(entry.conversation_lead_bot || "").trim()
3878
3895
  || String(entry.conversation_summary_bot || "").trim()
3879
3896
  || ensureArray(entry.conversation_participants).length
@@ -3885,31 +3902,45 @@ function runnerRequestHasConversationContractData(entryRaw) {
3885
3902
  || runnerRequestPreferredExecutionContractActionable(entry) === true
3886
3903
  || runnerRequestPreferredExecutionContractTargets(entry).length
3887
3904
  || runnerRequestPreferredNextExpectedResponders(entry).length
3888
- );
3889
- }
3890
-
3891
- function runnerRequestPreferredExecutionContractType(entryRaw) {
3892
- const entry = safeObject(entryRaw);
3893
- return String(
3894
- entry.execution_contract_type
3895
- || entry.followup_execution_contract_type
3896
- || entry.root_execution_contract_type
3897
- || "",
3898
- ).trim().toLowerCase();
3899
- }
3900
-
3901
- function runnerRequestPreferredExecutionContractActionable(entryRaw) {
3902
- const entry = safeObject(entryRaw);
3903
- return entry.execution_contract_actionable === true;
3904
- }
3905
+ );
3906
+ }
3907
+
3908
+ function runnerRequestAuthoritativeDecisionBundle(entryRaw) {
3909
+ const entry = safeObject(entryRaw);
3910
+ const validation = validateRunnerConversationDecisionBundle(
3911
+ safeObject(entry.authoritative_decision_bundle || entry.authoritativeDecisionBundle),
3912
+ );
3913
+ return validation.ok ? safeObject(validation.bundle) : {};
3914
+ }
3915
+
3916
+ function runnerRequestPreferredExecutionContractType(entryRaw) {
3917
+ const entry = safeObject(entryRaw);
3918
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3919
+ return String(
3920
+ decisionBundle.execution_contract_type
3921
+ || entry.execution_contract_type
3922
+ || entry.followup_execution_contract_type
3923
+ || entry.root_execution_contract_type
3924
+ || "",
3925
+ ).trim().toLowerCase();
3926
+ }
3905
3927
 
3906
- function runnerRequestPreferredExecutionContractTargets(entryRaw) {
3907
- const entry = safeObject(entryRaw);
3908
- return uniqueOrderedStrings(
3909
- ensureArray(entry.execution_contract_targets).length
3910
- ? entry.execution_contract_targets
3911
- : ensureArray(entry.followup_execution_contract_targets).length
3912
- ? entry.followup_execution_contract_targets
3928
+ function runnerRequestPreferredExecutionContractActionable(entryRaw) {
3929
+ const entry = safeObject(entryRaw);
3930
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3931
+ return decisionBundle.execution_contract_actionable === true || entry.execution_contract_actionable === true;
3932
+ }
3933
+
3934
+ function runnerRequestPreferredExecutionContractTargets(entryRaw) {
3935
+ const entry = safeObject(entryRaw);
3936
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3937
+ return uniqueOrderedStrings(
3938
+ ensureArray(decisionBundle.execution_contract_targets).length
3939
+ ? decisionBundle.execution_contract_targets
3940
+ : ensureArray(entry.execution_contract_targets).length
3941
+ ? entry.execution_contract_targets
3942
+ : ensureArray(entry.followup_execution_contract_targets).length
3943
+ ? entry.followup_execution_contract_targets
3913
3944
  : ensureArray(entry.root_execution_contract_targets).length
3914
3945
  ? entry.root_execution_contract_targets
3915
3946
  : [],
@@ -3917,13 +3948,16 @@ function runnerRequestPreferredExecutionContractTargets(entryRaw) {
3917
3948
  );
3918
3949
  }
3919
3950
 
3920
- function runnerRequestPreferredNextExpectedResponders(entryRaw) {
3921
- const entry = safeObject(entryRaw);
3922
- return uniqueOrderedStrings(
3923
- ensureArray(entry.next_expected_responders).length
3924
- ? entry.next_expected_responders
3925
- : ensureArray(entry.followup_next_expected_responders).length
3926
- ? entry.followup_next_expected_responders
3951
+ function runnerRequestPreferredNextExpectedResponders(entryRaw) {
3952
+ const entry = safeObject(entryRaw);
3953
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3954
+ return uniqueOrderedStrings(
3955
+ ensureArray(decisionBundle.next_expected_responders).length
3956
+ ? decisionBundle.next_expected_responders
3957
+ : ensureArray(entry.next_expected_responders).length
3958
+ ? entry.next_expected_responders
3959
+ : ensureArray(entry.followup_next_expected_responders).length
3960
+ ? entry.followup_next_expected_responders
3927
3961
  : ensureArray(entry.root_next_expected_responders).length
3928
3962
  ? entry.root_next_expected_responders
3929
3963
  : [],
@@ -4968,17 +5002,18 @@ function resolveRunnerHumanCommentAuthorityContext({
4968
5002
  };
4969
5003
  }
4970
5004
 
4971
- async function claimRunnerRequestForHumanComment({
4972
- normalizedRoute,
4973
- routeKey,
4974
- selectedRecord,
4975
- selectedBotUsernames = [],
4976
- normalizedIntent = "",
4977
- sharedHumanIntent = null,
4978
- runtime = null,
4979
- archiveThreadID = "",
4980
- authoritativeSourceMessageEnvelope = {},
4981
- }) {
5005
+ async function claimRunnerRequestForHumanComment({
5006
+ normalizedRoute,
5007
+ routeKey,
5008
+ selectedRecord,
5009
+ selectedBotUsernames = [],
5010
+ normalizedIntent = "",
5011
+ sharedHumanIntent = null,
5012
+ decisionBundle = null,
5013
+ runtime = null,
5014
+ archiveThreadID = "",
5015
+ authoritativeSourceMessageEnvelope = {},
5016
+ }) {
4982
5017
  const parsed = safeObject(selectedRecord?.parsedArchive);
4983
5018
  const commentKind = String(parsed.kind || "").trim().toLowerCase();
4984
5019
  if (!isInboundArchiveKind(commentKind)) {
@@ -5060,9 +5095,9 @@ async function claimRunnerRequestForHumanComment({
5060
5095
  excludeRequestKey: provisionalRequestKey,
5061
5096
  }));
5062
5097
  }
5063
- const authorityContext = resolveRunnerHumanCommentAuthorityContext({
5064
- normalizedRoute,
5065
- selectedRecord,
5098
+ const authorityContext = resolveRunnerHumanCommentAuthorityContext({
5099
+ normalizedRoute,
5100
+ selectedRecord,
5066
5101
  replyChainContext,
5067
5102
  referencedRequest,
5068
5103
  sharedConversationSource,
@@ -5070,24 +5105,44 @@ async function claimRunnerRequestForHumanComment({
5070
5105
  normalizedSharedHumanIntent,
5071
5106
  resolvedNormalizedIntent,
5072
5107
  });
5073
- const authoritySource = safeObject(authorityContext.authoritySource);
5074
- const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
5075
- selectedRecord,
5076
- replyChainContext,
5108
+ const authoritySource = safeObject(authorityContext.authoritySource);
5109
+ const decisionBundleValidation = validateRunnerConversationDecisionBundle(decisionBundle);
5110
+ if (
5111
+ Object.keys(safeObject(decisionBundle)).length > 0
5112
+ && decisionBundleValidation.ok !== true
5113
+ ) {
5114
+ return {
5115
+ ok: false,
5116
+ reason: "invalid_decision_bundle",
5117
+ detail: String(decisionBundleValidation.reason || "").trim() || "invalid_decision_bundle",
5118
+ };
5119
+ }
5120
+ const authoritativeDecisionBundle = decisionBundleValidation.ok === true
5121
+ ? safeObject(decisionBundleValidation.bundle)
5122
+ : {};
5123
+ const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
5124
+ selectedRecord,
5125
+ replyChainContext,
5077
5126
  authoritySource,
5078
5127
  runtime,
5079
5128
  archiveThreadID,
5080
5129
  });
5081
5130
  const resolvedConversationID = String(authorityContext.conversationID || "").trim();
5082
5131
  const preferredNormalizedIntent = String(authorityContext.normalizedIntent || "").trim().toLowerCase();
5083
- const requestSelectedBotUsernames = ensureArray(authorityContext.selectedBotUsernames);
5084
- const requestKey = buildRunnerRequestKey({
5085
- normalizedRoute,
5086
- selectedRecord,
5087
- selectedBotUsernames: requestSelectedBotUsernames,
5088
- normalizedIntent: preferredNormalizedIntent,
5089
- conversationID: resolvedConversationID,
5090
- });
5132
+ const requestSelectedBotUsernames = ensureArray(authorityContext.selectedBotUsernames);
5133
+ const effectiveSelectedBotUsernames = uniqueOrderedStrings(
5134
+ ensureArray(authoritativeDecisionBundle.selected_bot_usernames).length
5135
+ ? authoritativeDecisionBundle.selected_bot_usernames
5136
+ : requestSelectedBotUsernames,
5137
+ normalizeTelegramMentionUsername,
5138
+ );
5139
+ const requestKey = buildRunnerRequestKey({
5140
+ normalizedRoute,
5141
+ selectedRecord,
5142
+ selectedBotUsernames: effectiveSelectedBotUsernames,
5143
+ normalizedIntent: preferredNormalizedIntent,
5144
+ conversationID: resolvedConversationID,
5145
+ });
5091
5146
  const requests = normalizeBotRunnerRequests(stateForClaim.requests);
5092
5147
  const existing = safeObject(requests[requestKey]);
5093
5148
  if (isFinalRunnerRequestStatus(existing.status)) {
@@ -5115,7 +5170,19 @@ async function claimRunnerRequestForHumanComment({
5115
5170
  const sourceMessageEnvelope = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
5116
5171
  ? normalizedAuthoritativeSourceMessageEnvelope
5117
5172
  : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
5118
- const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
5173
+ const decisionConversationParticipants = uniqueOrderedStrings(
5174
+ ensureArray(authoritativeDecisionBundle.participants),
5175
+ normalizeTelegramMentionUsername,
5176
+ );
5177
+ const decisionInitialResponders = uniqueOrderedStrings(
5178
+ ensureArray(authoritativeDecisionBundle.initial_responders),
5179
+ normalizeTelegramMentionUsername,
5180
+ );
5181
+ const decisionAllowedResponders = uniqueOrderedStrings(
5182
+ ensureArray(authoritativeDecisionBundle.allowed_responders),
5183
+ normalizeTelegramMentionUsername,
5184
+ );
5185
+ const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
5119
5186
  project_id: String(normalizedRoute?.projectID || "").trim(),
5120
5187
  provider: String(normalizedRoute?.provider || "").trim(),
5121
5188
  chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
@@ -5126,81 +5193,104 @@ async function claimRunnerRequestForHumanComment({
5126
5193
  source_message_route_key: String(sourceMessageEnvelope.source_route_key || "").trim(),
5127
5194
  source_message_bot_username: normalizeTelegramMentionUsername(sourceMessageEnvelope.source_bot_username),
5128
5195
  source_message_envelope: sourceMessageEnvelope,
5129
- root_comment_id: String(selectedRecord?.id || "").trim(),
5130
- root_comment_kind: commentKind,
5131
- conversation_id: resolvedConversationID,
5132
- reply_chain_context: authorityReplyChainContext,
5133
- selected_bot_usernames: uniqueOrderedStrings(requestSelectedBotUsernames, normalizeTelegramMentionUsername),
5134
- conversation_intent_mode: String(
5135
- normalizedSharedHumanIntent.intentMode
5136
- || existing.conversation_intent_mode
5137
- || authoritySource.conversation_intent_mode
5138
- || "",
5139
- ).trim().toLowerCase(),
5140
- conversation_lead_bot: normalizeTelegramMentionUsername(
5141
- normalizedSharedHumanIntent.leadBotSelector
5142
- || existing.conversation_lead_bot
5143
- || authoritySource.conversation_lead_bot,
5144
- ),
5145
- conversation_summary_bot: normalizeTelegramMentionUsername(
5146
- normalizedSharedHumanIntent.summaryBotSelector
5147
- || existing.conversation_summary_bot
5148
- || authoritySource.conversation_summary_bot,
5149
- ),
5150
- conversation_participants: uniqueOrderedStrings(
5151
- ensureArray(normalizedSharedHumanIntent.participantSelectors).length
5152
- ? normalizedSharedHumanIntent.participantSelectors
5153
- : ensureArray(existing.conversation_participants).length
5154
- ? existing.conversation_participants
5196
+ root_comment_id: String(selectedRecord?.id || "").trim(),
5197
+ root_comment_kind: commentKind,
5198
+ conversation_id: resolvedConversationID,
5199
+ reply_chain_context: authorityReplyChainContext,
5200
+ selected_bot_usernames: effectiveSelectedBotUsernames,
5201
+ authoritative_decision_bundle: authoritativeDecisionBundle,
5202
+ decision_bundle_validation_status: String(decisionBundleValidation.status || "").trim(),
5203
+ decision_bundle_validation_reason: String(decisionBundleValidation.reason || "").trim(),
5204
+ conversation_intent_mode: String(
5205
+ authoritativeDecisionBundle.conversation_intent_mode
5206
+ || normalizedSharedHumanIntent.intentMode
5207
+ || existing.conversation_intent_mode
5208
+ || authoritySource.conversation_intent_mode
5209
+ || "",
5210
+ ).trim().toLowerCase(),
5211
+ conversation_lead_bot: normalizeTelegramMentionUsername(
5212
+ authoritativeDecisionBundle.conversation_lead_bot
5213
+ || normalizedSharedHumanIntent.leadBotSelector
5214
+ || existing.conversation_lead_bot
5215
+ || authoritySource.conversation_lead_bot,
5216
+ ),
5217
+ conversation_summary_bot: normalizeTelegramMentionUsername(
5218
+ authoritativeDecisionBundle.conversation_summary_bot
5219
+ || normalizedSharedHumanIntent.summaryBotSelector
5220
+ || existing.conversation_summary_bot
5221
+ || authoritySource.conversation_summary_bot,
5222
+ ),
5223
+ conversation_participants: uniqueOrderedStrings(
5224
+ decisionConversationParticipants.length
5225
+ ? decisionConversationParticipants
5226
+ : ensureArray(normalizedSharedHumanIntent.participantSelectors).length
5227
+ ? normalizedSharedHumanIntent.participantSelectors
5228
+ : ensureArray(existing.conversation_participants).length
5229
+ ? existing.conversation_participants
5155
5230
  : ensureArray(authoritySource.conversation_participants).length
5156
5231
  ? authoritySource.conversation_participants
5157
5232
  : [],
5158
5233
  normalizeTelegramMentionUsername,
5159
- ),
5160
- conversation_initial_responders: uniqueOrderedStrings(
5161
- ensureArray(normalizedSharedHumanIntent.initialResponderSelectors).length
5162
- ? normalizedSharedHumanIntent.initialResponderSelectors
5163
- : ensureArray(existing.conversation_initial_responders).length
5164
- ? existing.conversation_initial_responders
5234
+ ),
5235
+ conversation_initial_responders: uniqueOrderedStrings(
5236
+ decisionInitialResponders.length
5237
+ ? decisionInitialResponders
5238
+ : ensureArray(normalizedSharedHumanIntent.initialResponderSelectors).length
5239
+ ? normalizedSharedHumanIntent.initialResponderSelectors
5240
+ : ensureArray(existing.conversation_initial_responders).length
5241
+ ? existing.conversation_initial_responders
5165
5242
  : ensureArray(authoritySource.conversation_initial_responders).length
5166
5243
  ? authoritySource.conversation_initial_responders
5167
5244
  : [],
5168
5245
  normalizeTelegramMentionUsername,
5169
- ),
5170
- conversation_allowed_responders: uniqueOrderedStrings(
5171
- ensureArray(normalizedSharedHumanIntent.allowedResponderSelectors).length
5172
- ? normalizedSharedHumanIntent.allowedResponderSelectors
5173
- : ensureArray(existing.conversation_allowed_responders).length
5174
- ? existing.conversation_allowed_responders
5246
+ ),
5247
+ conversation_allowed_responders: uniqueOrderedStrings(
5248
+ decisionAllowedResponders.length
5249
+ ? decisionAllowedResponders
5250
+ : ensureArray(normalizedSharedHumanIntent.allowedResponderSelectors).length
5251
+ ? normalizedSharedHumanIntent.allowedResponderSelectors
5252
+ : ensureArray(existing.conversation_allowed_responders).length
5253
+ ? existing.conversation_allowed_responders
5175
5254
  : ensureArray(authoritySource.conversation_allowed_responders).length
5176
5255
  ? authoritySource.conversation_allowed_responders
5177
5256
  : [],
5178
5257
  normalizeTelegramMentionUsername,
5179
- ),
5180
- conversation_allow_bot_to_bot: normalizedSharedHumanIntent.allowBotToBot === true
5181
- || existing.conversation_allow_bot_to_bot === true
5182
- || authoritySource.conversation_allow_bot_to_bot === true,
5183
- conversation_reply_expectation: String(
5184
- normalizedSharedHumanIntent.replyExpectation
5185
- || existing.conversation_reply_expectation
5186
- || authoritySource.conversation_reply_expectation
5187
- || "",
5188
- ).trim().toLowerCase(),
5189
- execution_contract_type: runnerRequestPreferredExecutionContractType(existing)
5190
- || runnerRequestPreferredExecutionContractType(authoritySource),
5191
- execution_contract_actionable: runnerRequestPreferredExecutionContractActionable(existing)
5192
- || runnerRequestPreferredExecutionContractActionable(authoritySource),
5193
- execution_contract_targets: uniqueOrderedStrings(
5194
- runnerRequestPreferredExecutionContractTargets(existing).length
5195
- ? runnerRequestPreferredExecutionContractTargets(existing)
5196
- : runnerRequestPreferredExecutionContractTargets(authoritySource),
5197
- normalizeTelegramMentionUsername,
5198
- ),
5199
- next_expected_responders: uniqueOrderedStrings(
5200
- runnerRequestPreferredNextExpectedResponders(existing).length
5201
- ? runnerRequestPreferredNextExpectedResponders(existing)
5202
- : runnerRequestPreferredNextExpectedResponders(authoritySource),
5203
- normalizeTelegramMentionUsername,
5258
+ ),
5259
+ conversation_allow_bot_to_bot: authoritativeDecisionBundle.allow_bot_to_bot === true
5260
+ || normalizedSharedHumanIntent.allowBotToBot === true
5261
+ || existing.conversation_allow_bot_to_bot === true
5262
+ || authoritySource.conversation_allow_bot_to_bot === true,
5263
+ conversation_reply_expectation: String(
5264
+ authoritativeDecisionBundle.conversation_reply_expectation
5265
+ || normalizedSharedHumanIntent.replyExpectation
5266
+ || existing.conversation_reply_expectation
5267
+ || authoritySource.conversation_reply_expectation
5268
+ || "",
5269
+ ).trim().toLowerCase(),
5270
+ execution_contract_type: String(
5271
+ authoritativeDecisionBundle.execution_contract_type
5272
+ || runnerRequestPreferredExecutionContractType(existing)
5273
+ || runnerRequestPreferredExecutionContractType(authoritySource)
5274
+ || "",
5275
+ ).trim().toLowerCase(),
5276
+ execution_contract_actionable: authoritativeDecisionBundle.execution_contract_actionable === true
5277
+ || runnerRequestPreferredExecutionContractActionable(existing)
5278
+ || runnerRequestPreferredExecutionContractActionable(authoritySource),
5279
+ execution_contract_targets: uniqueOrderedStrings(
5280
+ ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
5281
+ ? authoritativeDecisionBundle.execution_contract_targets
5282
+ : runnerRequestPreferredExecutionContractTargets(existing).length
5283
+ ? runnerRequestPreferredExecutionContractTargets(existing)
5284
+ : runnerRequestPreferredExecutionContractTargets(authoritySource),
5285
+ normalizeTelegramMentionUsername,
5286
+ ),
5287
+ next_expected_responders: uniqueOrderedStrings(
5288
+ ensureArray(authoritativeDecisionBundle.next_expected_responders).length
5289
+ ? authoritativeDecisionBundle.next_expected_responders
5290
+ : runnerRequestPreferredNextExpectedResponders(existing).length
5291
+ ? runnerRequestPreferredNextExpectedResponders(existing)
5292
+ : runnerRequestPreferredNextExpectedResponders(authoritySource),
5293
+ normalizeTelegramMentionUsername,
5204
5294
  ),
5205
5295
  normalized_intent: String(preferredNormalizedIntent || existing.normalized_intent || "").trim().toLowerCase(),
5206
5296
  status: "claimed",
@@ -5677,6 +5767,7 @@ async function prepareRunnerRequestClaimForExecution({
5677
5767
  selectedRecord,
5678
5768
  selectedResponderSelectors = [],
5679
5769
  sharedHumanIntent = null,
5770
+ decisionBundle = null,
5680
5771
  authoritativeSourceMessageEnvelope = {},
5681
5772
  runtime = null,
5682
5773
  archiveThreadID = "",
@@ -5688,6 +5779,7 @@ async function prepareRunnerRequestClaimForExecution({
5688
5779
  selectedBotUsernames: selectedResponderSelectors,
5689
5780
  normalizedIntent: String(safeObject(sharedHumanIntent).intentType || "").trim().toLowerCase(),
5690
5781
  sharedHumanIntent,
5782
+ decisionBundle,
5691
5783
  runtime,
5692
5784
  archiveThreadID,
5693
5785
  authoritativeSourceMessageEnvelope,
@@ -5826,6 +5918,7 @@ async function finalizePreparedRunnerSelectedRecordExecutionContext({
5826
5918
  selectedRecord,
5827
5919
  selectedResponderSelectors: ensureArray(requestPreparation.selectedResponderSelectors),
5828
5920
  sharedHumanIntent: requestPreparation.sharedHumanIntent || null,
5921
+ decisionBundle: safeObject(requestPreparation.decisionBundle),
5829
5922
  authoritativeSourceMessageEnvelope: safeObject(requestPreparation.authoritativeSourceMessageEnvelope),
5830
5923
  runtime,
5831
5924
  archiveThreadID,
@@ -6216,11 +6309,14 @@ function markRunnerRequestLifecycle({
6216
6309
  closedReason = "",
6217
6310
  aiReplyGenerated = false,
6218
6311
  aiReplyGeneratedAt = "",
6219
- aiReplyPreview = "",
6220
- normalizedExecutionContractType = "",
6221
- normalizedExecutionContractTargets = [],
6222
- normalizedExecutionNextResponders = [],
6223
- responseContractValidationStatus = "",
6312
+ aiReplyPreview = "",
6313
+ normalizedExecutionContractType = "",
6314
+ normalizedExecutionContractTargets = [],
6315
+ normalizedExecutionNextResponders = [],
6316
+ decisionBundle = null,
6317
+ decisionBundleValidationStatus = "",
6318
+ decisionBundleValidationReason = "",
6319
+ responseContractValidationStatus = "",
6224
6320
  responseContractValidationReason = "",
6225
6321
  responseContractValidationTargets = [],
6226
6322
  assignmentValidationStatus = "",
@@ -6255,7 +6351,19 @@ function markRunnerRequestLifecycle({
6255
6351
  const sourceMessageEnvelope = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
6256
6352
  ? normalizedAuthoritativeSourceMessageEnvelope
6257
6353
  : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
6258
- const effectiveReplyToMessageID = intFromRawAllowZero(
6354
+ const normalizedDecisionBundle = normalizeRunnerConversationDecisionBundle(decisionBundle);
6355
+ const resolvedDecisionBundleValidation = Object.keys(safeObject(decisionBundle)).length > 0
6356
+ ? validateRunnerConversationDecisionBundle(normalizedDecisionBundle)
6357
+ : {
6358
+ ok: false,
6359
+ status: String(decisionBundleValidationStatus || "").trim() || "missing",
6360
+ reason: String(decisionBundleValidationReason || "").trim(),
6361
+ bundle: {},
6362
+ };
6363
+ const authoritativeDecisionBundle = resolvedDecisionBundleValidation.ok === true
6364
+ ? safeObject(resolvedDecisionBundleValidation.bundle)
6365
+ : runnerRequestAuthoritativeDecisionBundle(existing);
6366
+ const effectiveReplyToMessageID = intFromRawAllowZero(
6259
6367
  replyToMessageID,
6260
6368
  intFromRawAllowZero(existing.last_reply_to_message_id, 0),
6261
6369
  );
@@ -6287,46 +6395,60 @@ function markRunnerRequestLifecycle({
6287
6395
  || intFromRawAllowZero(replyToMessageID, 0) > 0
6288
6396
  || intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
6289
6397
  );
6290
- const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
6291
- [
6292
- ...ensureArray(executionContractTargets),
6293
- ...ensureArray(normalizedExecutionContractTargets),
6294
- ...ensureArray(responseContractValidationTargets),
6398
+ const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
6399
+ [
6400
+ ...ensureArray(authoritativeDecisionBundle.execution_contract_targets),
6401
+ ...ensureArray(executionContractTargets),
6402
+ ...ensureArray(normalizedExecutionContractTargets),
6403
+ ...ensureArray(responseContractValidationTargets),
6295
6404
  ],
6296
6405
  normalizeTelegramMentionUsername,
6297
6406
  );
6298
- const rootEffectiveNextExpectedResponders = uniqueOrderedStrings(
6299
- [
6300
- ...ensureArray(nextExpectedResponders),
6301
- ...ensureArray(normalizedExecutionNextResponders),
6302
- ...ensureArray(responseContractValidationTargets),
6407
+ const rootEffectiveNextExpectedResponders = uniqueOrderedStrings(
6408
+ [
6409
+ ...ensureArray(authoritativeDecisionBundle.next_expected_responders),
6410
+ ...ensureArray(nextExpectedResponders),
6411
+ ...ensureArray(normalizedExecutionNextResponders),
6412
+ ...ensureArray(responseContractValidationTargets),
6303
6413
  ],
6304
6414
  normalizeTelegramMentionUsername,
6305
6415
  );
6306
- const continuationSelectors = uniqueOrderedStrings(
6307
- [
6308
- ...ensureArray(executionContractTargets).length
6309
- ? ensureArray(executionContractTargets)
6310
- : ensureArray(existing.execution_contract_targets),
6311
- ...ensureArray(nextExpectedResponders).length
6312
- ? ensureArray(nextExpectedResponders)
6313
- : ensureArray(existing.next_expected_responders),
6314
- ],
6315
- normalizeTelegramMentionUsername,
6316
- ).filter((selector) => selector && selector !== normalizedCurrentBotSelector);
6317
- const shouldRemainRunningAfterReply = continuationSelectors.length > 0;
6318
- const nextConversationIntentMode = String(
6319
- conversationIntentMode
6320
- || existing.conversation_intent_mode
6321
- || "",
6322
- ).trim().toLowerCase();
6323
- const nextExecutionContractType = String(
6324
- executionContractType
6325
- || existing.execution_contract_type
6326
- || "",
6327
- ).trim().toLowerCase();
6328
- const nextNormalizedIntent = (() => {
6329
- const explicitIntent = String(normalizedIntent || "").trim().toLowerCase();
6416
+ const continuationSelectors = uniqueOrderedStrings(
6417
+ [
6418
+ ...ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
6419
+ ? ensureArray(authoritativeDecisionBundle.execution_contract_targets)
6420
+ : ensureArray(executionContractTargets).length
6421
+ ? ensureArray(executionContractTargets)
6422
+ : ensureArray(existing.execution_contract_targets),
6423
+ ...ensureArray(authoritativeDecisionBundle.next_expected_responders).length
6424
+ ? ensureArray(authoritativeDecisionBundle.next_expected_responders)
6425
+ : ensureArray(nextExpectedResponders).length
6426
+ ? ensureArray(nextExpectedResponders)
6427
+ : ensureArray(existing.next_expected_responders),
6428
+ ],
6429
+ normalizeTelegramMentionUsername,
6430
+ ).filter((selector) => selector && selector !== normalizedCurrentBotSelector);
6431
+ const shouldRemainRunningAfterReply = authoritativeDecisionBundle.should_close_after_reply === true
6432
+ ? false
6433
+ : authoritativeDecisionBundle.should_close_after_reply === false
6434
+ ? true
6435
+ : continuationSelectors.length > 0;
6436
+ const nextConversationIntentMode = String(
6437
+ authoritativeDecisionBundle.conversation_intent_mode
6438
+ || conversationIntentMode
6439
+ || existing.conversation_intent_mode
6440
+ || "",
6441
+ ).trim().toLowerCase();
6442
+ const nextExecutionContractType = String(
6443
+ authoritativeDecisionBundle.execution_contract_type
6444
+ || executionContractType
6445
+ || existing.execution_contract_type
6446
+ || "",
6447
+ ).trim().toLowerCase();
6448
+ const nextNormalizedIntent = (() => {
6449
+ const explicitIntent = String(
6450
+ authoritativeDecisionBundle.normalized_intent || normalizedIntent || "",
6451
+ ).trim().toLowerCase();
6330
6452
  if (explicitIntent) {
6331
6453
  return explicitIntent;
6332
6454
  }
@@ -6356,60 +6478,94 @@ function markRunnerRequestLifecycle({
6356
6478
  }
6357
6479
  return normalizeRunnerRequestStatus(existing.status);
6358
6480
  })();
6359
- const nowISO = new Date().toISOString();
6360
- const commentKind = String(parsed.kind || "").trim().toLowerCase();
6361
- const isRootHumanComment = ["telegram_message", "telegram_edited_message"].includes(commentKind);
6362
- const isFollowupComment = !isRootHumanComment;
6363
- const patch = {
6364
- conversation_id: conversationID,
6365
- conversation_participants: uniqueOrderedStrings(
6366
- ensureArray(conversationParticipants).length ? conversationParticipants : existing.conversation_participants,
6367
- normalizeTelegramMentionUsername,
6368
- ),
6369
- conversation_initial_responders: uniqueOrderedStrings(
6370
- ensureArray(conversationInitialResponders).length
6371
- ? conversationInitialResponders
6372
- : existing.conversation_initial_responders,
6373
- normalizeTelegramMentionUsername,
6374
- ),
6375
- conversation_allowed_responders: uniqueOrderedStrings(
6376
- ensureArray(allowedResponders).length ? allowedResponders : existing.conversation_allowed_responders,
6377
- normalizeTelegramMentionUsername,
6378
- ),
6379
- conversation_intent_mode: nextConversationIntentMode,
6380
- conversation_lead_bot: normalizeTelegramMentionUsername(
6381
- conversationLeadBot
6382
- || existing.conversation_lead_bot
6383
- || "",
6384
- ),
6385
- conversation_summary_bot: normalizeTelegramMentionUsername(
6386
- conversationSummaryBot
6387
- || existing.conversation_summary_bot
6388
- || "",
6389
- ),
6390
- conversation_allow_bot_to_bot: conversationAllowBotToBot === true
6391
- || existing.conversation_allow_bot_to_bot === true,
6392
- conversation_reply_expectation: String(
6393
- conversationReplyExpectation
6394
- || existing.conversation_reply_expectation
6395
- || "",
6396
- ).trim().toLowerCase(),
6397
- execution_contract_type: nextExecutionContractType,
6398
- execution_contract_actionable: executionContractActionable === true
6399
- || existing.execution_contract_actionable === true,
6400
- execution_contract_targets: uniqueOrderedStrings(
6401
- isRootHumanComment && rootEffectiveExecutionContractTargets.length
6402
- ? rootEffectiveExecutionContractTargets
6403
- : ensureArray(executionContractTargets).length
6404
- ? executionContractTargets
6481
+ const nowISO = new Date().toISOString();
6482
+ const commentKind = String(parsed.kind || "").trim().toLowerCase();
6483
+ const isRootHumanComment = ["telegram_message", "telegram_edited_message"].includes(commentKind);
6484
+ const isFollowupComment = !isRootHumanComment;
6485
+ const patch = {
6486
+ authoritative_decision_bundle: Object.keys(authoritativeDecisionBundle).length > 0
6487
+ ? authoritativeDecisionBundle
6488
+ : safeObject(existing.authoritative_decision_bundle),
6489
+ decision_bundle_validation_status: String(
6490
+ resolvedDecisionBundleValidation.status
6491
+ || decisionBundleValidationStatus
6492
+ || existing.decision_bundle_validation_status
6493
+ || "",
6494
+ ).trim(),
6495
+ decision_bundle_validation_reason: String(
6496
+ resolvedDecisionBundleValidation.reason
6497
+ || decisionBundleValidationReason
6498
+ || existing.decision_bundle_validation_reason
6499
+ || "",
6500
+ ).trim(),
6501
+ conversation_id: conversationID,
6502
+ conversation_participants: uniqueOrderedStrings(
6503
+ ensureArray(authoritativeDecisionBundle.participants).length
6504
+ ? authoritativeDecisionBundle.participants
6505
+ : ensureArray(conversationParticipants).length
6506
+ ? conversationParticipants
6507
+ : existing.conversation_participants,
6508
+ normalizeTelegramMentionUsername,
6509
+ ),
6510
+ conversation_initial_responders: uniqueOrderedStrings(
6511
+ ensureArray(authoritativeDecisionBundle.initial_responders).length
6512
+ ? authoritativeDecisionBundle.initial_responders
6513
+ : ensureArray(conversationInitialResponders).length
6514
+ ? conversationInitialResponders
6515
+ : existing.conversation_initial_responders,
6516
+ normalizeTelegramMentionUsername,
6517
+ ),
6518
+ conversation_allowed_responders: uniqueOrderedStrings(
6519
+ ensureArray(authoritativeDecisionBundle.allowed_responders).length
6520
+ ? authoritativeDecisionBundle.allowed_responders
6521
+ : ensureArray(allowedResponders).length
6522
+ ? allowedResponders
6523
+ : existing.conversation_allowed_responders,
6524
+ normalizeTelegramMentionUsername,
6525
+ ),
6526
+ conversation_intent_mode: nextConversationIntentMode,
6527
+ conversation_lead_bot: normalizeTelegramMentionUsername(
6528
+ authoritativeDecisionBundle.conversation_lead_bot
6529
+ || conversationLeadBot
6530
+ || existing.conversation_lead_bot
6531
+ || "",
6532
+ ),
6533
+ conversation_summary_bot: normalizeTelegramMentionUsername(
6534
+ authoritativeDecisionBundle.conversation_summary_bot
6535
+ || conversationSummaryBot
6536
+ || existing.conversation_summary_bot
6537
+ || "",
6538
+ ),
6539
+ conversation_allow_bot_to_bot: authoritativeDecisionBundle.allow_bot_to_bot === true
6540
+ || conversationAllowBotToBot === true
6541
+ || existing.conversation_allow_bot_to_bot === true,
6542
+ conversation_reply_expectation: String(
6543
+ authoritativeDecisionBundle.conversation_reply_expectation
6544
+ || conversationReplyExpectation
6545
+ || existing.conversation_reply_expectation
6546
+ || "",
6547
+ ).trim().toLowerCase(),
6548
+ execution_contract_type: nextExecutionContractType,
6549
+ execution_contract_actionable: authoritativeDecisionBundle.execution_contract_actionable === true
6550
+ || executionContractActionable === true
6551
+ || existing.execution_contract_actionable === true,
6552
+ execution_contract_targets: uniqueOrderedStrings(
6553
+ ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
6554
+ ? authoritativeDecisionBundle.execution_contract_targets
6555
+ : isRootHumanComment && rootEffectiveExecutionContractTargets.length
6556
+ ? rootEffectiveExecutionContractTargets
6557
+ : ensureArray(executionContractTargets).length
6558
+ ? executionContractTargets
6405
6559
  : existing.execution_contract_targets,
6406
6560
  normalizeTelegramMentionUsername,
6407
- ),
6408
- next_expected_responders: uniqueOrderedStrings(
6409
- isRootHumanComment && rootEffectiveNextExpectedResponders.length
6410
- ? rootEffectiveNextExpectedResponders
6411
- : ensureArray(nextExpectedResponders).length
6412
- ? nextExpectedResponders
6561
+ ),
6562
+ next_expected_responders: uniqueOrderedStrings(
6563
+ ensureArray(authoritativeDecisionBundle.next_expected_responders).length
6564
+ ? authoritativeDecisionBundle.next_expected_responders
6565
+ : isRootHumanComment && rootEffectiveNextExpectedResponders.length
6566
+ ? rootEffectiveNextExpectedResponders
6567
+ : ensureArray(nextExpectedResponders).length
6568
+ ? nextExpectedResponders
6413
6569
  : existing.next_expected_responders,
6414
6570
  normalizeTelegramMentionUsername,
6415
6571
  ),
@@ -6893,11 +7049,13 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6893
7049
  preserveLocalObjectWhenServerBlank("last_reply_message_envelope");
6894
7050
  preserveLocalObjectWhenServerBlank("attempted_delivery_envelope");
6895
7051
  preserveLocalStringWhenServerBlank("conversation_id");
6896
- preserveLocalStringWhenServerBlank("conversation_intent_mode");
6897
- preserveLocalStringWhenServerBlank("conversation_lead_bot");
6898
- preserveLocalStringWhenServerBlank("conversation_summary_bot");
6899
- preserveLocalStringWhenServerBlank("conversation_reply_expectation");
6900
- preserveLocalStringWhenServerBlank("execution_contract_type");
7052
+ preserveLocalStringWhenServerBlank("conversation_intent_mode");
7053
+ preserveLocalStringWhenServerBlank("conversation_lead_bot");
7054
+ preserveLocalStringWhenServerBlank("conversation_summary_bot");
7055
+ preserveLocalStringWhenServerBlank("conversation_reply_expectation");
7056
+ preserveLocalStringWhenServerBlank("decision_bundle_validation_status");
7057
+ preserveLocalStringWhenServerBlank("decision_bundle_validation_reason");
7058
+ preserveLocalStringWhenServerBlank("execution_contract_type");
6901
7059
  preserveLocalStringWhenServerBlank("normalized_execution_contract_type");
6902
7060
  preserveLocalStringWhenServerBlank("ai_reply_generated_at");
6903
7061
  preserveLocalStringWhenServerBlank("ai_reply_preview");
@@ -6922,9 +7080,9 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6922
7080
  preserveLocalStringWhenServerBlank("followup_archive_status");
6923
7081
  preserveLocalStringWhenServerBlank("followup_transport_error");
6924
7082
  preserveLocalStringWhenServerBlank("followup_archive_error");
6925
- preserveLocalArrayWhenServerEmpty("conversation_participants");
6926
- preserveLocalArrayWhenServerEmpty("conversation_initial_responders");
6927
- preserveLocalArrayWhenServerEmpty("conversation_allowed_responders");
7083
+ preserveLocalArrayWhenServerEmpty("conversation_participants");
7084
+ preserveLocalArrayWhenServerEmpty("conversation_initial_responders");
7085
+ preserveLocalArrayWhenServerEmpty("conversation_allowed_responders");
6928
7086
  preserveLocalArrayWhenServerEmpty("execution_contract_targets");
6929
7087
  preserveLocalArrayWhenServerEmpty("next_expected_responders");
6930
7088
  preserveLocalArrayWhenServerEmpty("normalized_execution_contract_targets");
@@ -6936,9 +7094,10 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6936
7094
  preserveLocalArrayWhenServerEmpty("followup_next_expected_responders");
6937
7095
  preserveLocalArrayWhenServerEmpty("followup_normalized_execution_contract_targets");
6938
7096
  preserveLocalArrayWhenServerEmpty("followup_normalized_execution_next_responders");
6939
- preserveLocalArrayWhenServerEmpty("followup_response_contract_validation_targets");
6940
- preserveLocalArrayWhenServerEmpty("followup_assignment_validation_modes");
6941
- preserveLocalObjectWhenServerBlank("reply_chain_context");
7097
+ preserveLocalArrayWhenServerEmpty("followup_response_contract_validation_targets");
7098
+ preserveLocalArrayWhenServerEmpty("followup_assignment_validation_modes");
7099
+ preserveLocalObjectWhenServerBlank("authoritative_decision_bundle");
7100
+ preserveLocalObjectWhenServerBlank("reply_chain_context");
6942
7101
  if (serverEntry.conversation_allow_bot_to_bot !== true && localEntry.conversation_allow_bot_to_bot === true) {
6943
7102
  merged.conversation_allow_bot_to_bot = true;
6944
7103
  }