metheus-governance-mcp-cli 0.2.296 → 0.2.298

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
@@ -3874,68 +3874,83 @@ function runnerRequestAuthoritativeDecisionBundle(entryRaw) {
3874
3874
  function runnerRequestPreferredExecutionContractType(entryRaw) {
3875
3875
  const entry = safeObject(entryRaw);
3876
3876
  const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3877
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3877
3878
  return String(
3878
- decisionBundle.execution_contract_type
3879
+ (hasAuthoritativeDecisionBundle
3880
+ ? decisionBundle.execution_contract_type
3881
+ : "")
3879
3882
  || entry.execution_contract_type
3880
3883
  || entry.root_execution_contract_type
3881
3884
  || "",
3882
3885
  ).trim().toLowerCase();
3883
3886
  }
3884
-
3887
+
3885
3888
  function runnerRequestPreferredExecutionContractActionable(entryRaw) {
3886
3889
  const entry = safeObject(entryRaw);
3887
3890
  const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3888
- return decisionBundle.execution_contract_actionable === true || entry.execution_contract_actionable === true;
3891
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3892
+ if (hasAuthoritativeDecisionBundle) {
3893
+ return decisionBundle.execution_contract_actionable === true;
3894
+ }
3895
+ return entry.execution_contract_actionable === true;
3889
3896
  }
3890
3897
 
3891
3898
  function runnerRequestPreferredExecutionContractTargets(entryRaw) {
3892
3899
  const entry = safeObject(entryRaw);
3893
3900
  const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3901
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3894
3902
  return uniqueOrderedStrings(
3895
- ensureArray(decisionBundle.execution_contract_targets).length
3903
+ hasAuthoritativeDecisionBundle
3896
3904
  ? decisionBundle.execution_contract_targets
3897
3905
  : ensureArray(entry.execution_contract_targets).length
3898
- ? entry.execution_contract_targets
3899
- : ensureArray(entry.root_execution_contract_targets).length
3900
- ? entry.root_execution_contract_targets
3901
- : [],
3906
+ ? entry.execution_contract_targets
3907
+ : ensureArray(entry.root_execution_contract_targets).length
3908
+ ? entry.root_execution_contract_targets
3909
+ : [],
3902
3910
  normalizeTelegramMentionUsername,
3903
3911
  );
3904
3912
  }
3905
-
3913
+
3906
3914
  function runnerRequestPreferredNextExpectedResponders(entryRaw) {
3907
3915
  const entry = safeObject(entryRaw);
3908
3916
  const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3917
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3909
3918
  return uniqueOrderedStrings(
3910
- ensureArray(decisionBundle.next_expected_responders).length
3919
+ hasAuthoritativeDecisionBundle
3911
3920
  ? decisionBundle.next_expected_responders
3912
3921
  : ensureArray(entry.next_expected_responders).length
3913
- ? entry.next_expected_responders
3914
- : ensureArray(entry.root_next_expected_responders).length
3915
- ? entry.root_next_expected_responders
3916
- : [],
3922
+ ? entry.next_expected_responders
3923
+ : ensureArray(entry.root_next_expected_responders).length
3924
+ ? entry.root_next_expected_responders
3925
+ : [],
3917
3926
  normalizeTelegramMentionUsername,
3918
3927
  );
3919
3928
  }
3920
-
3929
+
3921
3930
  function runnerRequestPreferredAuthoritySelectedBotUsernames(entryRaw) {
3922
3931
  const entry = safeObject(entryRaw);
3932
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3933
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3923
3934
  const normalizedSummaryBot = normalizeTelegramMentionUsername(entry.conversation_summary_bot);
3924
3935
  return uniqueOrderedStrings(
3925
- runnerRequestPreferredNextExpectedResponders(entry).length
3926
- ? runnerRequestPreferredNextExpectedResponders(entry)
3927
- : runnerRequestPreferredExecutionContractTargets(entry).length
3928
- ? runnerRequestPreferredExecutionContractTargets(entry)
3929
- : ensureArray(entry.selected_bot_usernames).length
3930
- ? entry.selected_bot_usernames
3931
- : ensureArray(entry.conversation_initial_responders).length
3932
- ? entry.conversation_initial_responders
3933
- : normalizedSummaryBot
3934
- ? [normalizedSummaryBot]
3935
- : [],
3936
- normalizeTelegramMentionUsername,
3937
- );
3938
- }
3936
+ runnerRequestPreferredNextExpectedResponders(entry).length
3937
+ ? runnerRequestPreferredNextExpectedResponders(entry)
3938
+ : runnerRequestPreferredExecutionContractTargets(entry).length
3939
+ ? runnerRequestPreferredExecutionContractTargets(entry)
3940
+ : hasAuthoritativeDecisionBundle && ensureArray(decisionBundle.selected_bot_usernames).length
3941
+ ? decisionBundle.selected_bot_usernames
3942
+ : hasAuthoritativeDecisionBundle && ensureArray(decisionBundle.initial_responders).length
3943
+ ? decisionBundle.initial_responders
3944
+ : ensureArray(entry.selected_bot_usernames).length
3945
+ ? entry.selected_bot_usernames
3946
+ : ensureArray(entry.conversation_initial_responders).length
3947
+ ? entry.conversation_initial_responders
3948
+ : normalizedSummaryBot
3949
+ ? [normalizedSummaryBot]
3950
+ : [],
3951
+ normalizeTelegramMentionUsername,
3952
+ );
3953
+ }
3939
3954
 
3940
3955
  function extractRunnerExplicitSelectedBotUsernamesFromParsed(parsedArchiveRaw) {
3941
3956
  const parsedArchive = safeObject(parsedArchiveRaw);
@@ -3956,6 +3971,8 @@ function runnerRequestAllowsSharedHumanClaimTakeover({
3956
3971
  canonicalHumanMessageKey = "",
3957
3972
  }) {
3958
3973
  const entry = safeObject(entryRaw);
3974
+ const decisionBundle = runnerRequestAuthoritativeDecisionBundle(entry);
3975
+ const hasAuthoritativeDecisionBundle = Object.keys(decisionBundle).length > 0;
3959
3976
  const normalizedCurrentBotUsername = normalizeTelegramMentionUsername(currentBotUsername);
3960
3977
  const normalizedCanonicalHumanMessageKey = String(canonicalHumanMessageKey || "").trim();
3961
3978
  if (!normalizedCurrentBotUsername || !normalizedCanonicalHumanMessageKey) {
@@ -3969,11 +3986,17 @@ function runnerRequestAllowsSharedHumanClaimTakeover({
3969
3986
  return pendingResponders.includes(normalizedCurrentBotUsername);
3970
3987
  }
3971
3988
  const directHumanResponders = uniqueOrderedStrings(
3972
- [
3973
- ...ensureArray(entry.selected_bot_usernames),
3974
- ...ensureArray(entry.conversation_initial_responders),
3975
- ...ensureArray(entry.conversation_allowed_responders),
3976
- ],
3989
+ hasAuthoritativeDecisionBundle
3990
+ ? [
3991
+ ...ensureArray(decisionBundle.selected_bot_usernames),
3992
+ ...ensureArray(decisionBundle.initial_responders),
3993
+ ...ensureArray(decisionBundle.allowed_responders),
3994
+ ]
3995
+ : [
3996
+ ...ensureArray(entry.selected_bot_usernames),
3997
+ ...ensureArray(entry.conversation_initial_responders),
3998
+ ...ensureArray(entry.conversation_allowed_responders),
3999
+ ],
3977
4000
  normalizeTelegramMentionUsername,
3978
4001
  );
3979
4002
  return directHumanResponders.includes(normalizedCurrentBotUsername);
@@ -7096,18 +7119,30 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
7096
7119
  merged[fieldName] = localValue;
7097
7120
  }
7098
7121
  };
7099
- const preserveLocalArrayWhenServerEmpty = (fieldName) => {
7100
- const serverValues = ensureArray(serverEntry[fieldName]).filter(Boolean);
7101
- const localValues = ensureArray(localEntry[fieldName]).filter(Boolean);
7102
- if (serverValues.length === 0 && localValues.length > 0) {
7103
- merged[fieldName] = localValues;
7104
- }
7105
- };
7106
- const preserveLocalObjectWhenServerBlank = (fieldName) => {
7107
- const serverValue = safeObject(serverEntry[fieldName]);
7108
- const localValue = safeObject(localEntry[fieldName]);
7109
- if (!Object.keys(serverValue).length && Object.keys(localValue).length) {
7110
- merged[fieldName] = localValue;
7122
+ const preserveLocalArrayWhenServerEmpty = (fieldName) => {
7123
+ const serverValues = ensureArray(serverEntry[fieldName]).filter(Boolean);
7124
+ const localValues = ensureArray(localEntry[fieldName]).filter(Boolean);
7125
+ if (serverValues.length === 0 && localValues.length > 0) {
7126
+ merged[fieldName] = localValues;
7127
+ }
7128
+ };
7129
+ const clearMergedStringWhenServerBlank = (fieldName) => {
7130
+ const serverValue = String(serverEntry[fieldName] || "").trim();
7131
+ if (!serverValue) {
7132
+ delete merged[fieldName];
7133
+ }
7134
+ };
7135
+ const clearMergedArrayWhenServerEmpty = (fieldName) => {
7136
+ const serverValues = ensureArray(serverEntry[fieldName]).filter(Boolean);
7137
+ if (serverValues.length === 0) {
7138
+ delete merged[fieldName];
7139
+ }
7140
+ };
7141
+ const preserveLocalObjectWhenServerBlank = (fieldName) => {
7142
+ const serverValue = safeObject(serverEntry[fieldName]);
7143
+ const localValue = safeObject(localEntry[fieldName]);
7144
+ if (!Object.keys(serverValue).length && Object.keys(localValue).length) {
7145
+ merged[fieldName] = localValue;
7111
7146
  }
7112
7147
  };
7113
7148
  preserveLocalNumberWhenServerMissing("source_message_id");
@@ -7136,16 +7171,13 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
7136
7171
  preserveLocalStringWhenServerBlank("root_thread_id");
7137
7172
  preserveLocalStringWhenServerBlank("root_work_item_created_at");
7138
7173
  preserveLocalStringWhenServerBlank("root_work_item_last_error");
7139
- preserveLocalStringWhenServerBlank("root_execution_contract_type");
7140
- preserveLocalStringWhenServerBlank("root_ai_reply_preview");
7141
- preserveLocalStringWhenServerBlank("root_response_contract_validation_status");
7142
- preserveLocalStringWhenServerBlank("root_response_contract_validation_reason");
7143
- preserveLocalStringWhenServerBlank("followup_ai_reply_preview");
7144
- preserveLocalStringWhenServerBlank("followup_execution_contract_type");
7145
- preserveLocalStringWhenServerBlank("followup_normalized_execution_contract_type");
7146
- preserveLocalStringWhenServerBlank("followup_response_contract_validation_status");
7147
- preserveLocalStringWhenServerBlank("followup_response_contract_validation_reason");
7148
- preserveLocalStringWhenServerBlank("followup_assignment_validation_status");
7174
+ preserveLocalStringWhenServerBlank("root_ai_reply_preview");
7175
+ preserveLocalStringWhenServerBlank("root_response_contract_validation_status");
7176
+ preserveLocalStringWhenServerBlank("root_response_contract_validation_reason");
7177
+ preserveLocalStringWhenServerBlank("followup_ai_reply_preview");
7178
+ preserveLocalStringWhenServerBlank("followup_response_contract_validation_status");
7179
+ preserveLocalStringWhenServerBlank("followup_response_contract_validation_reason");
7180
+ preserveLocalStringWhenServerBlank("followup_assignment_validation_status");
7149
7181
  preserveLocalStringWhenServerBlank("followup_assignment_validation_reason");
7150
7182
  preserveLocalStringWhenServerBlank("followup_delivery_status");
7151
7183
  preserveLocalStringWhenServerBlank("followup_archive_status");
@@ -7154,24 +7186,23 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
7154
7186
  preserveLocalArrayWhenServerEmpty("conversation_participants");
7155
7187
  preserveLocalArrayWhenServerEmpty("conversation_initial_responders");
7156
7188
  preserveLocalArrayWhenServerEmpty("conversation_allowed_responders");
7157
- preserveLocalArrayWhenServerEmpty("execution_contract_targets");
7158
- preserveLocalArrayWhenServerEmpty("next_expected_responders");
7159
- preserveLocalArrayWhenServerEmpty("normalized_execution_contract_targets");
7160
- preserveLocalArrayWhenServerEmpty("normalized_execution_next_responders");
7161
- preserveLocalArrayWhenServerEmpty("root_execution_contract_targets");
7162
- preserveLocalArrayWhenServerEmpty("root_next_expected_responders");
7163
- preserveLocalArrayWhenServerEmpty("root_response_contract_validation_targets");
7164
- preserveLocalArrayWhenServerEmpty("followup_execution_contract_targets");
7165
- preserveLocalArrayWhenServerEmpty("followup_next_expected_responders");
7166
- preserveLocalArrayWhenServerEmpty("followup_normalized_execution_contract_targets");
7167
- preserveLocalArrayWhenServerEmpty("followup_normalized_execution_next_responders");
7189
+ preserveLocalArrayWhenServerEmpty("root_response_contract_validation_targets");
7168
7190
  preserveLocalArrayWhenServerEmpty("followup_response_contract_validation_targets");
7169
7191
  preserveLocalArrayWhenServerEmpty("followup_assignment_validation_modes");
7170
7192
  preserveLocalObjectWhenServerBlank("authoritative_decision_bundle");
7171
7193
  preserveLocalObjectWhenServerBlank("reply_chain_context");
7172
- if (serverEntry.conversation_allow_bot_to_bot !== true && localEntry.conversation_allow_bot_to_bot === true) {
7173
- merged.conversation_allow_bot_to_bot = true;
7174
- }
7194
+ clearMergedStringWhenServerBlank("root_execution_contract_type");
7195
+ clearMergedArrayWhenServerEmpty("root_execution_contract_targets");
7196
+ clearMergedArrayWhenServerEmpty("root_next_expected_responders");
7197
+ clearMergedStringWhenServerBlank("followup_execution_contract_type");
7198
+ clearMergedStringWhenServerBlank("followup_normalized_execution_contract_type");
7199
+ clearMergedArrayWhenServerEmpty("followup_execution_contract_targets");
7200
+ clearMergedArrayWhenServerEmpty("followup_next_expected_responders");
7201
+ clearMergedArrayWhenServerEmpty("followup_normalized_execution_contract_targets");
7202
+ clearMergedArrayWhenServerEmpty("followup_normalized_execution_next_responders");
7203
+ if (serverEntry.conversation_allow_bot_to_bot !== true && localEntry.conversation_allow_bot_to_bot === true) {
7204
+ merged.conversation_allow_bot_to_bot = true;
7205
+ }
7175
7206
  if (serverEntry.execution_contract_actionable !== true && localEntry.execution_contract_actionable === true) {
7176
7207
  merged.execution_contract_actionable = true;
7177
7208
  }
@@ -6,6 +6,10 @@ import {
6
6
  } from "./runner-orchestration-visibility.mjs";
7
7
  import {
8
8
  resolveRunnerConversationDecisionBundle,
9
+ deriveRunnerConversationContinuationKind,
10
+ normalizeRunnerConversationDecisionBundle,
11
+ resolveRunnerConversationDecisionBundleCurrentTurnResponders,
12
+ validateRunnerConversationDecisionBundle,
9
13
  } from "./runner-orchestration-decision-bundle.mjs";
10
14
 
11
15
  function safeObject(value) {
@@ -75,6 +79,36 @@ function isRunnerBotReplyContinuationAuthorizedForCurrentBot({
75
79
  if (normalizedSenderSelectors.includes(normalizedCurrentBotSelector)) {
76
80
  return false;
77
81
  }
82
+ const parsedAuthoritativeDecisionBundle = safeObject(parsed.authoritativeDecisionBundle);
83
+ const requestAuthoritativeDecisionBundle = safeObject(safeObject(persistedRequest).authoritative_decision_bundle);
84
+ const parsedAuthoritativeDecisionBundleValidation = Object.keys(parsedAuthoritativeDecisionBundle).length > 0
85
+ ? validateRunnerConversationDecisionBundle(parsedAuthoritativeDecisionBundle)
86
+ : null;
87
+ const requestAuthoritativeDecisionBundleValidation = Object.keys(requestAuthoritativeDecisionBundle).length > 0
88
+ ? validateRunnerConversationDecisionBundle(requestAuthoritativeDecisionBundle)
89
+ : null;
90
+ const authoritativeDecisionBundle = parsedAuthoritativeDecisionBundleValidation?.ok
91
+ ? parsedAuthoritativeDecisionBundleValidation.bundle
92
+ : requestAuthoritativeDecisionBundleValidation?.ok
93
+ ? requestAuthoritativeDecisionBundleValidation.bundle
94
+ : {};
95
+ if (Object.keys(authoritativeDecisionBundle).length > 0) {
96
+ const continuationKind = deriveRunnerConversationContinuationKind(authoritativeDecisionBundle);
97
+ const currentTurnResponders = resolveRunnerConversationDecisionBundleCurrentTurnResponders(
98
+ authoritativeDecisionBundle,
99
+ normalizedCurrentBotSelector,
100
+ );
101
+ if (currentTurnResponders.includes(normalizedCurrentBotSelector)) {
102
+ return true;
103
+ }
104
+ if (continuationKind === "public_multi_bot_followup") {
105
+ return ensureArray(authoritativeDecisionBundle.next_expected_responders).includes(normalizedCurrentBotSelector);
106
+ }
107
+ if (continuationKind === "delegation_followup") {
108
+ return ensureArray(authoritativeDecisionBundle.execution_contract_targets).includes(normalizedCurrentBotSelector)
109
+ || ensureArray(authoritativeDecisionBundle.next_expected_responders).includes(normalizedCurrentBotSelector);
110
+ }
111
+ }
78
112
  const explicitReplyTargets = ensureArray([
79
113
  ...(Array.isArray(parsed.mentionUsernames) ? parsed.mentionUsernames : []),
80
114
  parsed.replyToBotUsername,
@@ -87,11 +121,7 @@ function isRunnerBotReplyContinuationAuthorizedForCurrentBot({
87
121
  if (explicitReplyTargets.includes(normalizedCurrentBotSelector)) {
88
122
  return true;
89
123
  }
90
- const request = safeObject(persistedRequest);
91
- const nextExpectedResponders = ensureArray(request.next_expected_responders)
92
- .map((value) => normalizeMentionSelector(value))
93
- .filter(Boolean);
94
- return nextExpectedResponders.includes(normalizedCurrentBotSelector);
124
+ return false;
95
125
  }
96
126
 
97
127
  export async function resolveRunnerPrecomputedResponderAdjudication({
@@ -44,6 +44,23 @@ function normalizeBoolean(value, fallback = false) {
44
44
  return fallback;
45
45
  }
46
46
 
47
+ function intFromRawAllowZero(raw, fallback = 0) {
48
+ const text = String(raw ?? "").trim();
49
+ if (!text) {
50
+ return fallback;
51
+ }
52
+ const parsed = Number.parseInt(text, 10);
53
+ return Number.isFinite(parsed) ? parsed : fallback;
54
+ }
55
+
56
+ function normalizeContinuationKind(value) {
57
+ return String(value || "").trim().toLowerCase();
58
+ }
59
+
60
+ function normalizeReplyAnchorKind(value) {
61
+ return String(value || "").trim().toLowerCase();
62
+ }
63
+
47
64
  function normalizeContractAssignments(assignmentsRaw) {
48
65
  return uniqueOrderedSelectors(
49
66
  ensureArray(assignmentsRaw).map((item) => {
@@ -70,6 +87,24 @@ export function normalizeRunnerConversationDecisionBundle(bundleRaw) {
70
87
  ? bundle.selected_bot_usernames
71
88
  : initialResponders,
72
89
  );
90
+ const replyToMessageID = intFromRawAllowZero(
91
+ bundle.reply_to_message_id
92
+ || bundle.replyToMessageID,
93
+ 0,
94
+ );
95
+ const replyAnchorSource = String(
96
+ bundle.reply_anchor_source
97
+ || bundle.replyAnchorSource
98
+ || "",
99
+ ).trim().toLowerCase();
100
+ const replyAnchorKind = normalizeReplyAnchorKind(
101
+ bundle.reply_anchor_kind
102
+ || bundle.replyAnchorKind,
103
+ );
104
+ const continuationKind = normalizeContinuationKind(
105
+ bundle.continuation_kind
106
+ || bundle.continuationKind,
107
+ );
73
108
  return {
74
109
  schema_version: String(bundle.schema_version || "runner_conversation_decision.v1").trim(),
75
110
  decision_type: String(bundle.decision_type || "unspecified").trim().toLowerCase(),
@@ -87,6 +122,10 @@ export function normalizeRunnerConversationDecisionBundle(bundleRaw) {
87
122
  execution_contract_actionable: normalizeBoolean(bundle.execution_contract_actionable, false),
88
123
  execution_contract_targets: executionContractTargets,
89
124
  next_expected_responders: nextExpectedResponders,
125
+ reply_to_message_id: replyToMessageID > 0 ? replyToMessageID : 0,
126
+ reply_anchor_source: replyAnchorSource,
127
+ reply_anchor_kind: replyAnchorKind,
128
+ continuation_kind: continuationKind,
90
129
  should_close_after_reply: typeof bundle.should_close_after_reply === "boolean"
91
130
  ? bundle.should_close_after_reply
92
131
  : false,
@@ -98,6 +137,59 @@ export function normalizeRunnerConversationDecisionBundle(bundleRaw) {
98
137
  };
99
138
  }
100
139
 
140
+ export function deriveRunnerConversationContinuationKind(bundleRaw) {
141
+ const bundle = normalizeRunnerConversationDecisionBundle(bundleRaw);
142
+ if (bundle.continuation_kind) {
143
+ return bundle.continuation_kind;
144
+ }
145
+ if (bundle.execution_contract_type === "delegation" && (
146
+ bundle.execution_contract_targets.length > 0
147
+ || bundle.next_expected_responders.length > 0
148
+ )) {
149
+ return "delegation_followup";
150
+ }
151
+ if (bundle.conversation_intent_mode === "multi_bot_direct" && bundle.next_expected_responders.length > 0) {
152
+ return "public_multi_bot_followup";
153
+ }
154
+ if (bundle.allow_bot_to_bot === true && bundle.next_expected_responders.length > 0) {
155
+ return "relay_followup";
156
+ }
157
+ return "";
158
+ }
159
+
160
+ export function resolveRunnerConversationDecisionBundleReplyAnchor(bundleRaw) {
161
+ const bundle = normalizeRunnerConversationDecisionBundle(bundleRaw);
162
+ return {
163
+ replyToMessageID: intFromRawAllowZero(bundle.reply_to_message_id, 0),
164
+ replyAnchorSource: String(bundle.reply_anchor_source || "").trim().toLowerCase(),
165
+ replyAnchorKind: normalizeReplyAnchorKind(bundle.reply_anchor_kind),
166
+ };
167
+ }
168
+
169
+ export function resolveRunnerConversationDecisionBundleCurrentTurnResponders(bundleRaw, currentBotSelector = "") {
170
+ const bundle = normalizeRunnerConversationDecisionBundle(bundleRaw);
171
+ const normalizedCurrentBotSelector = normalizeMentionSelector(currentBotSelector);
172
+ const continuationKind = deriveRunnerConversationContinuationKind(bundle);
173
+ const candidateResponders = uniqueOrderedSelectors(
174
+ ensureArray(bundle.next_expected_responders).length
175
+ ? bundle.next_expected_responders
176
+ : ensureArray(bundle.execution_contract_targets).length
177
+ ? bundle.execution_contract_targets
178
+ : bundle.selected_bot_usernames,
179
+ );
180
+ if (!normalizedCurrentBotSelector) {
181
+ return continuationKind ? candidateResponders : bundle.selected_bot_usernames;
182
+ }
183
+ if (continuationKind) {
184
+ return candidateResponders.includes(normalizedCurrentBotSelector)
185
+ ? [normalizedCurrentBotSelector]
186
+ : [];
187
+ }
188
+ return bundle.selected_bot_usernames.includes(normalizedCurrentBotSelector)
189
+ ? [normalizedCurrentBotSelector]
190
+ : [];
191
+ }
192
+
101
193
  export function validateRunnerConversationDecisionBundle(bundleRaw) {
102
194
  const bundle = normalizeRunnerConversationDecisionBundle(bundleRaw);
103
195
  if (!Object.keys(safeObject(bundleRaw)).length) {
@@ -184,6 +276,14 @@ export function validateRunnerConversationDecisionBundle(bundleRaw) {
184
276
  bundle,
185
277
  };
186
278
  }
279
+ if (!(bundle.reply_to_message_id > 0) && (bundle.reply_anchor_source || bundle.reply_anchor_kind)) {
280
+ return {
281
+ ok: false,
282
+ status: "invalid_reply_anchor_without_message_id",
283
+ reason: "reply anchor metadata requires reply_to_message_id",
284
+ bundle,
285
+ };
286
+ }
187
287
  if (bundle.visible_handoff_required === true && bundle.visible_handoff_targets.length === 0) {
188
288
  return {
189
289
  ok: false,
@@ -212,6 +312,9 @@ export function buildRunnerConversationDecisionBundle({
212
312
  normalizedExecutionContractType = "",
213
313
  normalizedExecutionTargets = [],
214
314
  normalizedExecutionNextResponders = [],
315
+ replyToMessageID = 0,
316
+ replyAnchorSource = "",
317
+ replyAnchorKind = "",
215
318
  shouldCloseAfterReply,
216
319
  reasoningSummary = "",
217
320
  }) {
@@ -270,6 +373,9 @@ export function buildRunnerConversationDecisionBundle({
270
373
  execution_contract_actionable: contract.actionable === true,
271
374
  execution_contract_targets: ensureArray(normalizedExecutionTargets),
272
375
  next_expected_responders: ensureArray(normalizedExecutionNextResponders),
376
+ reply_to_message_id: intFromRawAllowZero(replyToMessageID, 0) || undefined,
377
+ reply_anchor_source: String(replyAnchorSource || "").trim().toLowerCase(),
378
+ reply_anchor_kind: normalizeReplyAnchorKind(replyAnchorKind),
273
379
  should_close_after_reply: typeof shouldCloseAfterReply === "boolean"
274
380
  ? shouldCloseAfterReply
275
381
  : undefined,