metheus-governance-mcp-cli 0.2.279 → 0.2.281

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,51 +3902,63 @@ 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.root_execution_contract_type
3923
+ || "",
3924
+ ).trim().toLowerCase();
3925
+ }
3905
3926
 
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
3913
- : ensureArray(entry.root_execution_contract_targets).length
3914
- ? entry.root_execution_contract_targets
3915
- : [],
3916
- normalizeTelegramMentionUsername,
3917
- );
3918
- }
3927
+ function runnerRequestPreferredExecutionContractActionable(entryRaw) {
3928
+ const entry = safeObject(entryRaw);
3929
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3930
+ return decisionBundle.execution_contract_actionable === true || entry.execution_contract_actionable === true;
3931
+ }
3932
+
3933
+ function runnerRequestPreferredExecutionContractTargets(entryRaw) {
3934
+ const entry = safeObject(entryRaw);
3935
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3936
+ return uniqueOrderedStrings(
3937
+ ensureArray(decisionBundle.execution_contract_targets).length
3938
+ ? decisionBundle.execution_contract_targets
3939
+ : ensureArray(entry.execution_contract_targets).length
3940
+ ? entry.execution_contract_targets
3941
+ : ensureArray(entry.root_execution_contract_targets).length
3942
+ ? entry.root_execution_contract_targets
3943
+ : [],
3944
+ normalizeTelegramMentionUsername,
3945
+ );
3946
+ }
3919
3947
 
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
3927
- : ensureArray(entry.root_next_expected_responders).length
3928
- ? entry.root_next_expected_responders
3929
- : [],
3930
- normalizeTelegramMentionUsername,
3931
- );
3932
- }
3948
+ function runnerRequestPreferredNextExpectedResponders(entryRaw) {
3949
+ const entry = safeObject(entryRaw);
3950
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3951
+ return uniqueOrderedStrings(
3952
+ ensureArray(decisionBundle.next_expected_responders).length
3953
+ ? decisionBundle.next_expected_responders
3954
+ : ensureArray(entry.next_expected_responders).length
3955
+ ? entry.next_expected_responders
3956
+ : ensureArray(entry.root_next_expected_responders).length
3957
+ ? entry.root_next_expected_responders
3958
+ : [],
3959
+ normalizeTelegramMentionUsername,
3960
+ );
3961
+ }
3933
3962
 
3934
3963
  function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
3935
3964
  const entry = safeObject(entryRaw);
@@ -4968,17 +4997,18 @@ function resolveRunnerHumanCommentAuthorityContext({
4968
4997
  };
4969
4998
  }
4970
4999
 
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
- }) {
5000
+ async function claimRunnerRequestForHumanComment({
5001
+ normalizedRoute,
5002
+ routeKey,
5003
+ selectedRecord,
5004
+ selectedBotUsernames = [],
5005
+ normalizedIntent = "",
5006
+ sharedHumanIntent = null,
5007
+ decisionBundle = null,
5008
+ runtime = null,
5009
+ archiveThreadID = "",
5010
+ authoritativeSourceMessageEnvelope = {},
5011
+ }) {
4982
5012
  const parsed = safeObject(selectedRecord?.parsedArchive);
4983
5013
  const commentKind = String(parsed.kind || "").trim().toLowerCase();
4984
5014
  if (!isInboundArchiveKind(commentKind)) {
@@ -5060,9 +5090,9 @@ async function claimRunnerRequestForHumanComment({
5060
5090
  excludeRequestKey: provisionalRequestKey,
5061
5091
  }));
5062
5092
  }
5063
- const authorityContext = resolveRunnerHumanCommentAuthorityContext({
5064
- normalizedRoute,
5065
- selectedRecord,
5093
+ const authorityContext = resolveRunnerHumanCommentAuthorityContext({
5094
+ normalizedRoute,
5095
+ selectedRecord,
5066
5096
  replyChainContext,
5067
5097
  referencedRequest,
5068
5098
  sharedConversationSource,
@@ -5070,24 +5100,44 @@ async function claimRunnerRequestForHumanComment({
5070
5100
  normalizedSharedHumanIntent,
5071
5101
  resolvedNormalizedIntent,
5072
5102
  });
5073
- const authoritySource = safeObject(authorityContext.authoritySource);
5074
- const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
5075
- selectedRecord,
5076
- replyChainContext,
5103
+ const authoritySource = safeObject(authorityContext.authoritySource);
5104
+ const decisionBundleValidation = validateRunnerConversationDecisionBundle(decisionBundle);
5105
+ if (
5106
+ Object.keys(safeObject(decisionBundle)).length > 0
5107
+ && decisionBundleValidation.ok !== true
5108
+ ) {
5109
+ return {
5110
+ ok: false,
5111
+ reason: "invalid_decision_bundle",
5112
+ detail: String(decisionBundleValidation.reason || "").trim() || "invalid_decision_bundle",
5113
+ };
5114
+ }
5115
+ const authoritativeDecisionBundle = decisionBundleValidation.ok === true
5116
+ ? safeObject(decisionBundleValidation.bundle)
5117
+ : {};
5118
+ const authorityReplyChainContext = await buildRunnerAuthorityReplyChainContext({
5119
+ selectedRecord,
5120
+ replyChainContext,
5077
5121
  authoritySource,
5078
5122
  runtime,
5079
5123
  archiveThreadID,
5080
5124
  });
5081
5125
  const resolvedConversationID = String(authorityContext.conversationID || "").trim();
5082
5126
  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
- });
5127
+ const requestSelectedBotUsernames = ensureArray(authorityContext.selectedBotUsernames);
5128
+ const effectiveSelectedBotUsernames = uniqueOrderedStrings(
5129
+ ensureArray(authoritativeDecisionBundle.selected_bot_usernames).length
5130
+ ? authoritativeDecisionBundle.selected_bot_usernames
5131
+ : requestSelectedBotUsernames,
5132
+ normalizeTelegramMentionUsername,
5133
+ );
5134
+ const requestKey = buildRunnerRequestKey({
5135
+ normalizedRoute,
5136
+ selectedRecord,
5137
+ selectedBotUsernames: effectiveSelectedBotUsernames,
5138
+ normalizedIntent: preferredNormalizedIntent,
5139
+ conversationID: resolvedConversationID,
5140
+ });
5091
5141
  const requests = normalizeBotRunnerRequests(stateForClaim.requests);
5092
5142
  const existing = safeObject(requests[requestKey]);
5093
5143
  if (isFinalRunnerRequestStatus(existing.status)) {
@@ -5115,7 +5165,20 @@ async function claimRunnerRequestForHumanComment({
5115
5165
  const sourceMessageEnvelope = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
5116
5166
  ? normalizedAuthoritativeSourceMessageEnvelope
5117
5167
  : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
5118
- const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
5168
+ const decisionConversationParticipants = uniqueOrderedStrings(
5169
+ ensureArray(authoritativeDecisionBundle.participants),
5170
+ normalizeTelegramMentionUsername,
5171
+ );
5172
+ const decisionInitialResponders = uniqueOrderedStrings(
5173
+ ensureArray(authoritativeDecisionBundle.initial_responders),
5174
+ normalizeTelegramMentionUsername,
5175
+ );
5176
+ const decisionAllowedResponders = uniqueOrderedStrings(
5177
+ ensureArray(authoritativeDecisionBundle.allowed_responders),
5178
+ normalizeTelegramMentionUsername,
5179
+ );
5180
+ const hasAuthoritativeConversationDecision = Object.keys(authoritativeDecisionBundle).length > 0;
5181
+ const { requests: nextRequests, request } = upsertRunnerRequest(stateForClaim, requestKey, {
5119
5182
  project_id: String(normalizedRoute?.projectID || "").trim(),
5120
5183
  provider: String(normalizedRoute?.provider || "").trim(),
5121
5184
  chat_id: String(parsed.chatID || parsed.chatId || "").trim(),
@@ -5126,81 +5189,118 @@ async function claimRunnerRequestForHumanComment({
5126
5189
  source_message_route_key: String(sourceMessageEnvelope.source_route_key || "").trim(),
5127
5190
  source_message_bot_username: normalizeTelegramMentionUsername(sourceMessageEnvelope.source_bot_username),
5128
5191
  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
5192
+ root_comment_id: String(selectedRecord?.id || "").trim(),
5193
+ root_comment_kind: commentKind,
5194
+ conversation_id: resolvedConversationID,
5195
+ reply_chain_context: authorityReplyChainContext,
5196
+ selected_bot_usernames: effectiveSelectedBotUsernames,
5197
+ authoritative_decision_bundle: authoritativeDecisionBundle,
5198
+ decision_bundle_validation_status: String(decisionBundleValidation.status || "").trim(),
5199
+ decision_bundle_validation_reason: String(decisionBundleValidation.reason || "").trim(),
5200
+ conversation_intent_mode: String(
5201
+ (hasAuthoritativeConversationDecision
5202
+ ? authoritativeDecisionBundle.conversation_intent_mode
5203
+ : "")
5204
+ || normalizedSharedHumanIntent.intentMode
5205
+ || existing.conversation_intent_mode
5206
+ || authoritySource.conversation_intent_mode
5207
+ || "",
5208
+ ).trim().toLowerCase(),
5209
+ conversation_lead_bot: normalizeTelegramMentionUsername(
5210
+ (hasAuthoritativeConversationDecision
5211
+ ? authoritativeDecisionBundle.conversation_lead_bot
5212
+ : "")
5213
+ || normalizedSharedHumanIntent.leadBotSelector
5214
+ || existing.conversation_lead_bot
5215
+ || authoritySource.conversation_lead_bot,
5216
+ ),
5217
+ conversation_summary_bot: normalizeTelegramMentionUsername(
5218
+ (hasAuthoritativeConversationDecision
5219
+ ? authoritativeDecisionBundle.conversation_summary_bot
5220
+ : "")
5221
+ || normalizedSharedHumanIntent.summaryBotSelector
5222
+ || existing.conversation_summary_bot
5223
+ || authoritySource.conversation_summary_bot,
5224
+ ),
5225
+ conversation_participants: uniqueOrderedStrings(
5226
+ hasAuthoritativeConversationDecision && decisionConversationParticipants.length
5227
+ ? decisionConversationParticipants
5228
+ : ensureArray(normalizedSharedHumanIntent.participantSelectors).length
5229
+ ? normalizedSharedHumanIntent.participantSelectors
5230
+ : ensureArray(existing.conversation_participants).length
5231
+ ? existing.conversation_participants
5155
5232
  : ensureArray(authoritySource.conversation_participants).length
5156
5233
  ? authoritySource.conversation_participants
5157
5234
  : [],
5158
5235
  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
5236
+ ),
5237
+ conversation_initial_responders: uniqueOrderedStrings(
5238
+ hasAuthoritativeConversationDecision && decisionInitialResponders.length
5239
+ ? decisionInitialResponders
5240
+ : ensureArray(normalizedSharedHumanIntent.initialResponderSelectors).length
5241
+ ? normalizedSharedHumanIntent.initialResponderSelectors
5242
+ : ensureArray(existing.conversation_initial_responders).length
5243
+ ? existing.conversation_initial_responders
5165
5244
  : ensureArray(authoritySource.conversation_initial_responders).length
5166
5245
  ? authoritySource.conversation_initial_responders
5167
5246
  : [],
5168
5247
  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
5248
+ ),
5249
+ conversation_allowed_responders: uniqueOrderedStrings(
5250
+ hasAuthoritativeConversationDecision && decisionAllowedResponders.length
5251
+ ? decisionAllowedResponders
5252
+ : ensureArray(normalizedSharedHumanIntent.allowedResponderSelectors).length
5253
+ ? normalizedSharedHumanIntent.allowedResponderSelectors
5254
+ : ensureArray(existing.conversation_allowed_responders).length
5255
+ ? existing.conversation_allowed_responders
5175
5256
  : ensureArray(authoritySource.conversation_allowed_responders).length
5176
5257
  ? authoritySource.conversation_allowed_responders
5177
5258
  : [],
5178
- 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,
5259
+ normalizeTelegramMentionUsername,
5260
+ ),
5261
+ conversation_allow_bot_to_bot: (hasAuthoritativeConversationDecision
5262
+ ? authoritativeDecisionBundle.allow_bot_to_bot === true
5263
+ : false)
5264
+ || normalizedSharedHumanIntent.allowBotToBot === true
5265
+ || existing.conversation_allow_bot_to_bot === true
5266
+ || authoritySource.conversation_allow_bot_to_bot === true,
5267
+ conversation_reply_expectation: String(
5268
+ (hasAuthoritativeConversationDecision
5269
+ ? authoritativeDecisionBundle.conversation_reply_expectation
5270
+ : "")
5271
+ || normalizedSharedHumanIntent.replyExpectation
5272
+ || existing.conversation_reply_expectation
5273
+ || authoritySource.conversation_reply_expectation
5274
+ || "",
5275
+ ).trim().toLowerCase(),
5276
+ execution_contract_type: String(
5277
+ (hasAuthoritativeConversationDecision
5278
+ ? authoritativeDecisionBundle.execution_contract_type
5279
+ : "")
5280
+ || runnerRequestPreferredExecutionContractType(existing)
5281
+ || runnerRequestPreferredExecutionContractType(authoritySource)
5282
+ || "",
5283
+ ).trim().toLowerCase(),
5284
+ execution_contract_actionable: (hasAuthoritativeConversationDecision
5285
+ ? authoritativeDecisionBundle.execution_contract_actionable === true
5286
+ : false)
5287
+ || runnerRequestPreferredExecutionContractActionable(existing)
5288
+ || runnerRequestPreferredExecutionContractActionable(authoritySource),
5289
+ execution_contract_targets: uniqueOrderedStrings(
5290
+ hasAuthoritativeConversationDecision && ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
5291
+ ? authoritativeDecisionBundle.execution_contract_targets
5292
+ : runnerRequestPreferredExecutionContractTargets(existing).length
5293
+ ? runnerRequestPreferredExecutionContractTargets(existing)
5294
+ : runnerRequestPreferredExecutionContractTargets(authoritySource),
5295
+ normalizeTelegramMentionUsername,
5296
+ ),
5297
+ next_expected_responders: uniqueOrderedStrings(
5298
+ hasAuthoritativeConversationDecision && ensureArray(authoritativeDecisionBundle.next_expected_responders).length
5299
+ ? authoritativeDecisionBundle.next_expected_responders
5300
+ : runnerRequestPreferredNextExpectedResponders(existing).length
5301
+ ? runnerRequestPreferredNextExpectedResponders(existing)
5302
+ : runnerRequestPreferredNextExpectedResponders(authoritySource),
5303
+ normalizeTelegramMentionUsername,
5204
5304
  ),
5205
5305
  normalized_intent: String(preferredNormalizedIntent || existing.normalized_intent || "").trim().toLowerCase(),
5206
5306
  status: "claimed",
@@ -5677,6 +5777,7 @@ async function prepareRunnerRequestClaimForExecution({
5677
5777
  selectedRecord,
5678
5778
  selectedResponderSelectors = [],
5679
5779
  sharedHumanIntent = null,
5780
+ decisionBundle = null,
5680
5781
  authoritativeSourceMessageEnvelope = {},
5681
5782
  runtime = null,
5682
5783
  archiveThreadID = "",
@@ -5688,6 +5789,7 @@ async function prepareRunnerRequestClaimForExecution({
5688
5789
  selectedBotUsernames: selectedResponderSelectors,
5689
5790
  normalizedIntent: String(safeObject(sharedHumanIntent).intentType || "").trim().toLowerCase(),
5690
5791
  sharedHumanIntent,
5792
+ decisionBundle,
5691
5793
  runtime,
5692
5794
  archiveThreadID,
5693
5795
  authoritativeSourceMessageEnvelope,
@@ -5826,6 +5928,7 @@ async function finalizePreparedRunnerSelectedRecordExecutionContext({
5826
5928
  selectedRecord,
5827
5929
  selectedResponderSelectors: ensureArray(requestPreparation.selectedResponderSelectors),
5828
5930
  sharedHumanIntent: requestPreparation.sharedHumanIntent || null,
5931
+ decisionBundle: safeObject(requestPreparation.decisionBundle),
5829
5932
  authoritativeSourceMessageEnvelope: safeObject(requestPreparation.authoritativeSourceMessageEnvelope),
5830
5933
  runtime,
5831
5934
  archiveThreadID,
@@ -6216,11 +6319,14 @@ function markRunnerRequestLifecycle({
6216
6319
  closedReason = "",
6217
6320
  aiReplyGenerated = false,
6218
6321
  aiReplyGeneratedAt = "",
6219
- aiReplyPreview = "",
6220
- normalizedExecutionContractType = "",
6221
- normalizedExecutionContractTargets = [],
6222
- normalizedExecutionNextResponders = [],
6223
- responseContractValidationStatus = "",
6322
+ aiReplyPreview = "",
6323
+ normalizedExecutionContractType = "",
6324
+ normalizedExecutionContractTargets = [],
6325
+ normalizedExecutionNextResponders = [],
6326
+ decisionBundle = null,
6327
+ decisionBundleValidationStatus = "",
6328
+ decisionBundleValidationReason = "",
6329
+ responseContractValidationStatus = "",
6224
6330
  responseContractValidationReason = "",
6225
6331
  responseContractValidationTargets = [],
6226
6332
  assignmentValidationStatus = "",
@@ -6255,7 +6361,19 @@ function markRunnerRequestLifecycle({
6255
6361
  const sourceMessageEnvelope = Object.keys(normalizedAuthoritativeSourceMessageEnvelope).length
6256
6362
  ? normalizedAuthoritativeSourceMessageEnvelope
6257
6363
  : normalizeRunnerTelegramMessageEnvelope(existing.source_message_envelope);
6258
- const effectiveReplyToMessageID = intFromRawAllowZero(
6364
+ const normalizedDecisionBundle = normalizeRunnerConversationDecisionBundle(decisionBundle);
6365
+ const resolvedDecisionBundleValidation = Object.keys(safeObject(decisionBundle)).length > 0
6366
+ ? validateRunnerConversationDecisionBundle(normalizedDecisionBundle)
6367
+ : {
6368
+ ok: false,
6369
+ status: String(decisionBundleValidationStatus || "").trim() || "missing",
6370
+ reason: String(decisionBundleValidationReason || "").trim(),
6371
+ bundle: {},
6372
+ };
6373
+ const authoritativeDecisionBundle = resolvedDecisionBundleValidation.ok === true
6374
+ ? safeObject(resolvedDecisionBundleValidation.bundle)
6375
+ : runnerRequestAuthoritativeDecisionBundle(existing);
6376
+ const effectiveReplyToMessageID = intFromRawAllowZero(
6259
6377
  replyToMessageID,
6260
6378
  intFromRawAllowZero(existing.last_reply_to_message_id, 0),
6261
6379
  );
@@ -6287,55 +6405,78 @@ function markRunnerRequestLifecycle({
6287
6405
  || intFromRawAllowZero(replyToMessageID, 0) > 0
6288
6406
  || intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
6289
6407
  );
6290
- const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
6291
- [
6292
- ...ensureArray(executionContractTargets),
6293
- ...ensureArray(normalizedExecutionContractTargets),
6294
- ...ensureArray(responseContractValidationTargets),
6408
+ const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
6409
+ [
6410
+ ...ensureArray(authoritativeDecisionBundle.execution_contract_targets),
6411
+ ...ensureArray(executionContractTargets),
6412
+ ...ensureArray(normalizedExecutionContractTargets),
6413
+ ...ensureArray(responseContractValidationTargets),
6295
6414
  ],
6296
6415
  normalizeTelegramMentionUsername,
6297
6416
  );
6298
- const rootEffectiveNextExpectedResponders = uniqueOrderedStrings(
6299
- [
6300
- ...ensureArray(nextExpectedResponders),
6301
- ...ensureArray(normalizedExecutionNextResponders),
6302
- ...ensureArray(responseContractValidationTargets),
6417
+ const rootEffectiveNextExpectedResponders = uniqueOrderedStrings(
6418
+ [
6419
+ ...ensureArray(authoritativeDecisionBundle.next_expected_responders),
6420
+ ...ensureArray(nextExpectedResponders),
6421
+ ...ensureArray(normalizedExecutionNextResponders),
6422
+ ...ensureArray(responseContractValidationTargets),
6303
6423
  ],
6304
6424
  normalizeTelegramMentionUsername,
6305
6425
  );
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();
6426
+ const continuationSelectors = uniqueOrderedStrings(
6427
+ [
6428
+ ...ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
6429
+ ? ensureArray(authoritativeDecisionBundle.execution_contract_targets)
6430
+ : ensureArray(executionContractTargets).length
6431
+ ? ensureArray(executionContractTargets)
6432
+ : ensureArray(existing.execution_contract_targets),
6433
+ ...ensureArray(authoritativeDecisionBundle.next_expected_responders).length
6434
+ ? ensureArray(authoritativeDecisionBundle.next_expected_responders)
6435
+ : ensureArray(nextExpectedResponders).length
6436
+ ? ensureArray(nextExpectedResponders)
6437
+ : ensureArray(existing.next_expected_responders),
6438
+ ],
6439
+ normalizeTelegramMentionUsername,
6440
+ ).filter((selector) => selector && selector !== normalizedCurrentBotSelector);
6441
+ const nextExecutionContractType = String(
6442
+ authoritativeDecisionBundle.execution_contract_type
6443
+ || executionContractType
6444
+ || existing.execution_contract_type
6445
+ || "",
6446
+ ).trim().toLowerCase();
6447
+ const normalizedOutcome = String(outcome || "").trim().toLowerCase();
6448
+ const shouldRemainRunningAfterReply = authoritativeDecisionBundle.should_close_after_reply === true
6449
+ ? false
6450
+ : authoritativeDecisionBundle.should_close_after_reply === false
6451
+ ? true
6452
+ : continuationSelectors.length > 0;
6453
+ const shouldRemainRunningAfterSkip = normalizedOutcome === "skipped"
6454
+ && parsedKind === "bot_reply"
6455
+ && authoritativeDecisionBundle.should_close_after_reply !== true
6456
+ && (
6457
+ nextExecutionContractType === "delegation"
6458
+ || rootEffectiveExecutionContractTargets.length > 0
6459
+ || rootEffectiveNextExpectedResponders.length > 0
6460
+ || continuationSelectors.length > 0
6461
+ );
6462
+ const nextConversationIntentMode = String(
6463
+ authoritativeDecisionBundle.conversation_intent_mode
6464
+ || conversationIntentMode
6465
+ || existing.conversation_intent_mode
6466
+ || "",
6467
+ ).trim().toLowerCase();
6468
+ const nextNormalizedIntent = (() => {
6469
+ const explicitIntent = String(
6470
+ authoritativeDecisionBundle.normalized_intent || normalizedIntent || "",
6471
+ ).trim().toLowerCase();
6330
6472
  if (explicitIntent) {
6331
6473
  return explicitIntent;
6332
6474
  }
6333
6475
  if (nextConversationIntentMode && !nextExecutionContractType) {
6334
6476
  return "";
6335
- }
6336
- return String(existing.normalized_intent || "").trim().toLowerCase();
6337
- })();
6338
- const normalizedOutcome = String(outcome || "").trim().toLowerCase();
6477
+ }
6478
+ return String(existing.normalized_intent || "").trim().toLowerCase();
6479
+ })();
6339
6480
  const nextStatus = (() => {
6340
6481
  if (normalizedOutcome === "claimed") return "claimed";
6341
6482
  if (normalizedOutcome === "running") return "running";
@@ -6345,9 +6486,11 @@ function markRunnerRequestLifecycle({
6345
6486
  }
6346
6487
  if (normalizedOutcome === "loop_closed") return "loop_closed";
6347
6488
  if (normalizedOutcome === "expired") return "expired";
6489
+ if (normalizedOutcome === "skipped") {
6490
+ return shouldRemainRunningAfterSkip ? "running" : "closed";
6491
+ }
6348
6492
  if (
6349
6493
  normalizedOutcome === "error"
6350
- || normalizedOutcome === "skipped"
6351
6494
  || normalizedOutcome === "closed"
6352
6495
  || normalizedOutcome === "execution_failed"
6353
6496
  || normalizedOutcome === "policy_violation"
@@ -6356,60 +6499,94 @@ function markRunnerRequestLifecycle({
6356
6499
  }
6357
6500
  return normalizeRunnerRequestStatus(existing.status);
6358
6501
  })();
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
6502
+ const nowISO = new Date().toISOString();
6503
+ const commentKind = String(parsed.kind || "").trim().toLowerCase();
6504
+ const isRootHumanComment = ["telegram_message", "telegram_edited_message"].includes(commentKind);
6505
+ const isFollowupComment = !isRootHumanComment;
6506
+ const patch = {
6507
+ authoritative_decision_bundle: Object.keys(authoritativeDecisionBundle).length > 0
6508
+ ? authoritativeDecisionBundle
6509
+ : safeObject(existing.authoritative_decision_bundle),
6510
+ decision_bundle_validation_status: String(
6511
+ resolvedDecisionBundleValidation.status
6512
+ || decisionBundleValidationStatus
6513
+ || existing.decision_bundle_validation_status
6514
+ || "",
6515
+ ).trim(),
6516
+ decision_bundle_validation_reason: String(
6517
+ resolvedDecisionBundleValidation.reason
6518
+ || decisionBundleValidationReason
6519
+ || existing.decision_bundle_validation_reason
6520
+ || "",
6521
+ ).trim(),
6522
+ conversation_id: conversationID,
6523
+ conversation_participants: uniqueOrderedStrings(
6524
+ ensureArray(authoritativeDecisionBundle.participants).length
6525
+ ? authoritativeDecisionBundle.participants
6526
+ : ensureArray(conversationParticipants).length
6527
+ ? conversationParticipants
6528
+ : existing.conversation_participants,
6529
+ normalizeTelegramMentionUsername,
6530
+ ),
6531
+ conversation_initial_responders: uniqueOrderedStrings(
6532
+ ensureArray(authoritativeDecisionBundle.initial_responders).length
6533
+ ? authoritativeDecisionBundle.initial_responders
6534
+ : ensureArray(conversationInitialResponders).length
6535
+ ? conversationInitialResponders
6536
+ : existing.conversation_initial_responders,
6537
+ normalizeTelegramMentionUsername,
6538
+ ),
6539
+ conversation_allowed_responders: uniqueOrderedStrings(
6540
+ ensureArray(authoritativeDecisionBundle.allowed_responders).length
6541
+ ? authoritativeDecisionBundle.allowed_responders
6542
+ : ensureArray(allowedResponders).length
6543
+ ? allowedResponders
6544
+ : existing.conversation_allowed_responders,
6545
+ normalizeTelegramMentionUsername,
6546
+ ),
6547
+ conversation_intent_mode: nextConversationIntentMode,
6548
+ conversation_lead_bot: normalizeTelegramMentionUsername(
6549
+ authoritativeDecisionBundle.conversation_lead_bot
6550
+ || conversationLeadBot
6551
+ || existing.conversation_lead_bot
6552
+ || "",
6553
+ ),
6554
+ conversation_summary_bot: normalizeTelegramMentionUsername(
6555
+ authoritativeDecisionBundle.conversation_summary_bot
6556
+ || conversationSummaryBot
6557
+ || existing.conversation_summary_bot
6558
+ || "",
6559
+ ),
6560
+ conversation_allow_bot_to_bot: authoritativeDecisionBundle.allow_bot_to_bot === true
6561
+ || conversationAllowBotToBot === true
6562
+ || existing.conversation_allow_bot_to_bot === true,
6563
+ conversation_reply_expectation: String(
6564
+ authoritativeDecisionBundle.conversation_reply_expectation
6565
+ || conversationReplyExpectation
6566
+ || existing.conversation_reply_expectation
6567
+ || "",
6568
+ ).trim().toLowerCase(),
6569
+ execution_contract_type: nextExecutionContractType,
6570
+ execution_contract_actionable: authoritativeDecisionBundle.execution_contract_actionable === true
6571
+ || executionContractActionable === true
6572
+ || existing.execution_contract_actionable === true,
6573
+ execution_contract_targets: uniqueOrderedStrings(
6574
+ ensureArray(authoritativeDecisionBundle.execution_contract_targets).length
6575
+ ? authoritativeDecisionBundle.execution_contract_targets
6576
+ : isRootHumanComment && rootEffectiveExecutionContractTargets.length
6577
+ ? rootEffectiveExecutionContractTargets
6578
+ : ensureArray(executionContractTargets).length
6579
+ ? executionContractTargets
6405
6580
  : existing.execution_contract_targets,
6406
6581
  normalizeTelegramMentionUsername,
6407
- ),
6408
- next_expected_responders: uniqueOrderedStrings(
6409
- isRootHumanComment && rootEffectiveNextExpectedResponders.length
6410
- ? rootEffectiveNextExpectedResponders
6411
- : ensureArray(nextExpectedResponders).length
6412
- ? nextExpectedResponders
6582
+ ),
6583
+ next_expected_responders: uniqueOrderedStrings(
6584
+ ensureArray(authoritativeDecisionBundle.next_expected_responders).length
6585
+ ? authoritativeDecisionBundle.next_expected_responders
6586
+ : isRootHumanComment && rootEffectiveNextExpectedResponders.length
6587
+ ? rootEffectiveNextExpectedResponders
6588
+ : ensureArray(nextExpectedResponders).length
6589
+ ? nextExpectedResponders
6413
6590
  : existing.next_expected_responders,
6414
6591
  normalizeTelegramMentionUsername,
6415
6592
  ),
@@ -6893,11 +7070,13 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6893
7070
  preserveLocalObjectWhenServerBlank("last_reply_message_envelope");
6894
7071
  preserveLocalObjectWhenServerBlank("attempted_delivery_envelope");
6895
7072
  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");
7073
+ preserveLocalStringWhenServerBlank("conversation_intent_mode");
7074
+ preserveLocalStringWhenServerBlank("conversation_lead_bot");
7075
+ preserveLocalStringWhenServerBlank("conversation_summary_bot");
7076
+ preserveLocalStringWhenServerBlank("conversation_reply_expectation");
7077
+ preserveLocalStringWhenServerBlank("decision_bundle_validation_status");
7078
+ preserveLocalStringWhenServerBlank("decision_bundle_validation_reason");
7079
+ preserveLocalStringWhenServerBlank("execution_contract_type");
6901
7080
  preserveLocalStringWhenServerBlank("normalized_execution_contract_type");
6902
7081
  preserveLocalStringWhenServerBlank("ai_reply_generated_at");
6903
7082
  preserveLocalStringWhenServerBlank("ai_reply_preview");
@@ -6922,9 +7101,9 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6922
7101
  preserveLocalStringWhenServerBlank("followup_archive_status");
6923
7102
  preserveLocalStringWhenServerBlank("followup_transport_error");
6924
7103
  preserveLocalStringWhenServerBlank("followup_archive_error");
6925
- preserveLocalArrayWhenServerEmpty("conversation_participants");
6926
- preserveLocalArrayWhenServerEmpty("conversation_initial_responders");
6927
- preserveLocalArrayWhenServerEmpty("conversation_allowed_responders");
7104
+ preserveLocalArrayWhenServerEmpty("conversation_participants");
7105
+ preserveLocalArrayWhenServerEmpty("conversation_initial_responders");
7106
+ preserveLocalArrayWhenServerEmpty("conversation_allowed_responders");
6928
7107
  preserveLocalArrayWhenServerEmpty("execution_contract_targets");
6929
7108
  preserveLocalArrayWhenServerEmpty("next_expected_responders");
6930
7109
  preserveLocalArrayWhenServerEmpty("normalized_execution_contract_targets");
@@ -6936,9 +7115,10 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
6936
7115
  preserveLocalArrayWhenServerEmpty("followup_next_expected_responders");
6937
7116
  preserveLocalArrayWhenServerEmpty("followup_normalized_execution_contract_targets");
6938
7117
  preserveLocalArrayWhenServerEmpty("followup_normalized_execution_next_responders");
6939
- preserveLocalArrayWhenServerEmpty("followup_response_contract_validation_targets");
6940
- preserveLocalArrayWhenServerEmpty("followup_assignment_validation_modes");
6941
- preserveLocalObjectWhenServerBlank("reply_chain_context");
7118
+ preserveLocalArrayWhenServerEmpty("followup_response_contract_validation_targets");
7119
+ preserveLocalArrayWhenServerEmpty("followup_assignment_validation_modes");
7120
+ preserveLocalObjectWhenServerBlank("authoritative_decision_bundle");
7121
+ preserveLocalObjectWhenServerBlank("reply_chain_context");
6942
7122
  if (serverEntry.conversation_allow_bot_to_bot !== true && localEntry.conversation_allow_bot_to_bot === true) {
6943
7123
  merged.conversation_allow_bot_to_bot = true;
6944
7124
  }