metheus-governance-mcp-cli 0.2.241 → 0.2.247

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
@@ -133,16 +133,19 @@ import {
133
133
  tryRegister,
134
134
  withWorkspaceDirArg,
135
135
  } from "./lib/client-registration.mjs";
136
- import {
137
- applyPendingAgeSelection,
138
- buildRunnerRouteStateFromComment,
139
- buildProcessableArchiveLogicalKey,
140
- findEarlierProcessableArchiveDuplicate,
141
- isInboundArchiveKind,
142
- normalizeArchiveCommentRecord,
143
- selectPendingArchiveComments,
144
- printRunnerResult,
145
- } from "./lib/runner-helpers.mjs";
136
+ import {
137
+ applyPendingAgeSelection,
138
+ buildTelegramBotReplyEnvelope,
139
+ buildTelegramMessageEnvelopeFromParsedArchive as buildRunnerTelegramMessageEnvelopeFromParsedArchive,
140
+ buildRunnerRouteStateFromComment,
141
+ buildProcessableArchiveLogicalKey,
142
+ findEarlierProcessableArchiveDuplicate,
143
+ isInboundArchiveKind,
144
+ normalizeTelegramMessageEnvelope as normalizeRunnerTelegramMessageEnvelope,
145
+ normalizeArchiveCommentRecord,
146
+ selectPendingArchiveComments,
147
+ printRunnerResult,
148
+ } from "./lib/runner-helpers.mjs";
146
149
  import {
147
150
  createProjectContextItem as createProjectContextItemImpl,
148
151
  createProjectEvidence as createProjectEvidenceImpl,
@@ -1915,15 +1918,24 @@ function mergeRunnerStateRecords(preferred, fallback) {
1915
1918
  }
1916
1919
  return allowUndefined ? undefined : 0;
1917
1920
  };
1918
- const pickArrayField = (key, normalizer = (value) => String(value || "").trim()) => {
1919
- if (hasOwn(primary, key)) {
1920
- const value = uniqueOrderedStrings(ensureArray(primary[key]), normalizer).filter(Boolean);
1921
- if (value.length) {
1922
- return value;
1923
- }
1924
- }
1925
- return uniqueOrderedStrings(ensureArray(secondary[key]), normalizer).filter(Boolean);
1926
- };
1921
+ const pickArrayField = (key, normalizer = (value) => String(value || "").trim()) => {
1922
+ if (hasOwn(primary, key)) {
1923
+ const value = uniqueOrderedStrings(ensureArray(primary[key]), normalizer).filter(Boolean);
1924
+ if (value.length) {
1925
+ return value;
1926
+ }
1927
+ }
1928
+ return uniqueOrderedStrings(ensureArray(secondary[key]), normalizer).filter(Boolean);
1929
+ };
1930
+ const pickObjectField = (key) => {
1931
+ if (hasOwn(primary, key)) {
1932
+ const value = safeObject(primary[key]);
1933
+ if (Object.keys(value).length) {
1934
+ return value;
1935
+ }
1936
+ }
1937
+ return safeObject(secondary[key]);
1938
+ };
1927
1939
  return {
1928
1940
  last_processed_comment_id: pickStringField("last_processed_comment_id"),
1929
1941
  last_processed_created_at: pickStringField("last_processed_created_at"),
@@ -1970,11 +1982,14 @@ function mergeRunnerStateRecords(preferred, fallback) {
1970
1982
  last_followup_response_contract_validation_reason: pickStringField("last_followup_response_contract_validation_reason"),
1971
1983
  last_followup_assignment_validation_status: pickStringField("last_followup_assignment_validation_status"),
1972
1984
  last_followup_assignment_validation_reason: pickStringField("last_followup_assignment_validation_reason"),
1973
- last_followup_delivery_status: pickStringField("last_followup_delivery_status"),
1974
- last_followup_archive_status: pickStringField("last_followup_archive_status"),
1975
- last_followup_transport_error: pickStringField("last_followup_transport_error"),
1976
- last_followup_archive_error: pickStringField("last_followup_archive_error"),
1977
- last_contract_validation_targets: pickArrayField("last_contract_validation_targets", normalizeTelegramMentionUsername),
1985
+ last_followup_delivery_status: pickStringField("last_followup_delivery_status"),
1986
+ last_followup_archive_status: pickStringField("last_followup_archive_status"),
1987
+ last_followup_transport_error: pickStringField("last_followup_transport_error"),
1988
+ last_followup_archive_error: pickStringField("last_followup_archive_error"),
1989
+ last_followup_source_message_envelope: pickObjectField("last_followup_source_message_envelope"),
1990
+ last_followup_last_reply_message_envelope: pickObjectField("last_followup_last_reply_message_envelope"),
1991
+ last_followup_attempted_delivery_envelope: pickObjectField("last_followup_attempted_delivery_envelope"),
1992
+ last_contract_validation_targets: pickArrayField("last_contract_validation_targets", normalizeTelegramMentionUsername),
1978
1993
  last_normalized_execution_contract_targets: pickArrayField("last_normalized_execution_contract_targets", normalizeTelegramMentionUsername),
1979
1994
  last_normalized_execution_next_responders: pickArrayField("last_normalized_execution_next_responders", normalizeTelegramMentionUsername),
1980
1995
  last_followup_execution_contract_targets: pickArrayField("last_followup_execution_contract_targets", normalizeTelegramMentionUsername),
@@ -2114,11 +2129,11 @@ function loadBotRunnerState() {
2114
2129
  }
2115
2130
  }
2116
2131
 
2117
- function writeTextFileAtomic(filePath, text) {
2118
- const normalizedPath = String(filePath || "").trim();
2119
- if (!normalizedPath) {
2120
- throw new Error("filePath is required");
2121
- }
2132
+ function writeTextFileAtomic(filePath, text) {
2133
+ const normalizedPath = String(filePath || "").trim();
2134
+ if (!normalizedPath) {
2135
+ throw new Error("filePath is required");
2136
+ }
2122
2137
  const directoryPath = path.dirname(normalizedPath);
2123
2138
  fs.mkdirSync(directoryPath, { recursive: true });
2124
2139
  const tempPath = path.join(
@@ -2130,31 +2145,39 @@ function writeTextFileAtomic(filePath, text) {
2130
2145
  const sleepBuffer = new SharedArrayBuffer(4);
2131
2146
  const sleepArray = new Int32Array(sleepBuffer);
2132
2147
  let lastRenameError = null;
2133
- try {
2134
- for (const delayMs of renameRetryDelaysMs) {
2135
- if (delayMs > 0) {
2136
- Atomics.wait(sleepArray, 0, 0, delayMs);
2137
- }
2138
- try {
2139
- fs.renameSync(tempPath, normalizedPath);
2140
- lastRenameError = null;
2141
- break;
2142
- } catch (error) {
2143
- lastRenameError = error;
2144
- try {
2145
- fs.rmSync(normalizedPath, { force: true });
2146
- fs.renameSync(tempPath, normalizedPath);
2147
- lastRenameError = null;
2148
- break;
2149
- } catch (retryError) {
2150
- lastRenameError = retryError;
2151
- const errorCode = String(retryError?.code || error?.code || "").trim().toUpperCase();
2152
- if (!["EPERM", "EBUSY", "ENOTEMPTY"].includes(errorCode)) {
2153
- throw retryError;
2154
- }
2155
- }
2156
- }
2157
- }
2148
+ try {
2149
+ for (const delayMs of renameRetryDelaysMs) {
2150
+ if (delayMs > 0) {
2151
+ Atomics.wait(sleepArray, 0, 0, delayMs);
2152
+ }
2153
+ try {
2154
+ fs.renameSync(tempPath, normalizedPath);
2155
+ lastRenameError = null;
2156
+ break;
2157
+ } catch (error) {
2158
+ lastRenameError = error;
2159
+ try {
2160
+ fs.copyFileSync(tempPath, normalizedPath);
2161
+ lastRenameError = null;
2162
+ break;
2163
+ } catch (copyError) {
2164
+ lastRenameError = copyError;
2165
+ try {
2166
+ fs.writeFileSync(normalizedPath, text, "utf8");
2167
+ lastRenameError = null;
2168
+ break;
2169
+ } catch (writeError) {
2170
+ lastRenameError = writeError;
2171
+ }
2172
+ const errorCode = String(
2173
+ lastRenameError?.code || copyError?.code || error?.code || "",
2174
+ ).trim().toUpperCase();
2175
+ if (!["EPERM", "EBUSY", "ENOTEMPTY"].includes(errorCode)) {
2176
+ throw lastRenameError;
2177
+ }
2178
+ }
2179
+ }
2180
+ }
2158
2181
  if (lastRenameError) {
2159
2182
  throw lastRenameError;
2160
2183
  }
@@ -2468,6 +2491,28 @@ function normalizeRunnerReplyChainSnapshot(rawSnapshot) {
2468
2491
  };
2469
2492
  }
2470
2493
 
2494
+ function buildRunnerReplyChainSnapshotFromMessageEnvelope(envelopeRaw, overrides = {}) {
2495
+ const envelope = normalizeRunnerTelegramMessageEnvelope(envelopeRaw);
2496
+ if (!Object.keys(envelope).length) {
2497
+ return null;
2498
+ }
2499
+ return normalizeRunnerReplyChainSnapshot({
2500
+ message_id: envelope.message_id,
2501
+ message_thread_id: envelope.message_thread_id,
2502
+ reply_to_message_id: envelope.reply_to_message_id,
2503
+ speaker_type: firstNonEmptyString([
2504
+ overrides.speaker_type,
2505
+ envelope.sender_is_bot === true ? "bot" : "human",
2506
+ ]),
2507
+ speaker_label: firstNonEmptyString([
2508
+ overrides.speaker_label,
2509
+ envelope.sender_username ? `@${String(envelope.sender_username || "").trim().replace(/^@+/, "")}` : "",
2510
+ envelope.sender,
2511
+ ]),
2512
+ body: firstNonEmptyString([overrides.body, envelope.body]),
2513
+ });
2514
+ }
2515
+
2471
2516
  function normalizeRunnerReplyChainContext(rawContext) {
2472
2517
  const context = safeObject(rawContext);
2473
2518
  const normalized = {
@@ -2530,6 +2575,20 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
2530
2575
  0,
2531
2576
  ) || undefined,
2532
2577
  source_message_body: String(entry.source_message_body || entry.sourceMessageBody || "").trim(),
2578
+ source_message_envelope: normalizeRunnerTelegramMessageEnvelope(
2579
+ entry.source_message_envelope
2580
+ || entry.sourceMessageEnvelope
2581
+ || (intFromRawAllowZero(entry.source_message_id || entry.sourceMessageID, 0) > 0
2582
+ ? {
2583
+ chat_id: entry.chat_id || entry.chatID,
2584
+ message_id: entry.source_message_id || entry.sourceMessageID,
2585
+ message_thread_id: entry.source_message_thread_id || entry.sourceMessageThreadID,
2586
+ body: entry.source_message_body || entry.sourceMessageBody,
2587
+ sender: "human",
2588
+ sender_is_bot: false,
2589
+ }
2590
+ : {}),
2591
+ ),
2533
2592
  root_comment_id: String(entry.root_comment_id || entry.rootCommentID || "").trim(),
2534
2593
  root_comment_kind: String(entry.root_comment_kind || entry.rootCommentKind || "").trim().toLowerCase(),
2535
2594
  conversation_id: String(entry.conversation_id || entry.conversationId || "").trim(),
@@ -2662,11 +2721,43 @@ function normalizeBotRunnerRequests(rawRequests, nowMs = Date.now()) {
2662
2721
  entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID,
2663
2722
  0,
2664
2723
  ) || undefined,
2724
+ last_reply_to_message_id: intFromRawAllowZero(
2725
+ entry.last_reply_to_message_id || entry.lastReplyToMessageID,
2726
+ 0,
2727
+ ) || undefined,
2728
+ last_reply_message_envelope: normalizeRunnerTelegramMessageEnvelope(
2729
+ entry.last_reply_message_envelope
2730
+ || entry.lastReplyMessageEnvelope
2731
+ || (intFromRawAllowZero(entry.last_reply_message_id || entry.lastReplyMessageID, 0) > 0
2732
+ ? buildTelegramBotReplyEnvelope({
2733
+ chatID: entry.chat_id || entry.chatID,
2734
+ messageID: entry.last_reply_message_id || entry.lastReplyMessageID,
2735
+ messageThreadID: entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID,
2736
+ replyToMessageID: entry.last_reply_to_message_id || entry.lastReplyToMessageID,
2737
+ senderUsername: entry.conversation_summary_bot || ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)[0] || "",
2738
+ body: entry.followup_ai_reply_preview || entry.followupAiReplyPreview || entry.ai_reply_preview || entry.aiReplyPreview,
2739
+ })
2740
+ : {}),
2741
+ ),
2742
+ attempted_delivery_envelope: normalizeRunnerTelegramMessageEnvelope(
2743
+ entry.attempted_delivery_envelope
2744
+ || entry.attemptedDeliveryEnvelope
2745
+ || ((intFromRawAllowZero(entry.last_reply_to_message_id || entry.lastReplyToMessageID, 0) > 0
2746
+ || intFromRawAllowZero(entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID, 0) > 0)
2747
+ ? buildTelegramBotReplyEnvelope({
2748
+ chatID: entry.chat_id || entry.chatID,
2749
+ messageThreadID: entry.last_reply_message_thread_id || entry.lastReplyMessageThreadID,
2750
+ replyToMessageID: entry.last_reply_to_message_id || entry.lastReplyToMessageID,
2751
+ senderUsername: entry.conversation_summary_bot || ensureArray(entry.selected_bot_usernames || entry.selectedBotUsernames)[0] || "",
2752
+ body: entry.followup_ai_reply_preview || entry.followupAiReplyPreview || entry.ai_reply_preview || entry.aiReplyPreview,
2753
+ })
2754
+ : {}),
2755
+ ),
2665
2756
  updated_at: updatedAt || new Date(nowMs).toISOString(),
2666
- };
2667
- }
2668
- return normalized;
2669
- }
2757
+ };
2758
+ }
2759
+ return normalized;
2760
+ }
2670
2761
 
2671
2762
  function normalizeBotRunnerConsumedComments(rawConsumed, nowMs = Date.now()) {
2672
2763
  const normalized = {};
@@ -3366,23 +3457,26 @@ function runnerRequestPreferredArchiveError(entryRaw) {
3366
3457
  ).trim();
3367
3458
  }
3368
3459
 
3369
- function buildRunnerValidationAndDeliverySummary({
3370
- aiReplyPreview = "",
3371
- executionContractType = "",
3372
- executionContractTargets = [],
3373
- nextExpectedResponders = [],
3460
+ function buildRunnerValidationAndDeliverySummary({
3461
+ aiReplyPreview = "",
3462
+ executionContractType = "",
3463
+ executionContractTargets = [],
3464
+ nextExpectedResponders = [],
3374
3465
  responseContractValidationStatus = "",
3375
3466
  responseContractValidationReason = "",
3376
3467
  responseContractValidationTargets = [],
3377
3468
  assignmentValidationStatus = "",
3378
3469
  assignmentValidationReason = "",
3379
3470
  assignmentValidationModes = [],
3380
- deliveryStatus = "",
3381
- archiveStatus = "",
3382
- transportError = "",
3383
- archiveError = "",
3384
- } = {}) {
3385
- return {
3471
+ deliveryStatus = "",
3472
+ archiveStatus = "",
3473
+ transportError = "",
3474
+ archiveError = "",
3475
+ sourceMessageEnvelope = {},
3476
+ lastReplyMessageEnvelope = {},
3477
+ attemptedDeliveryEnvelope = {},
3478
+ } = {}) {
3479
+ return {
3386
3480
  ai_reply_preview: String(aiReplyPreview || "").trim(),
3387
3481
  execution_contract_type: String(executionContractType || "").trim().toLowerCase(),
3388
3482
  execution_contract_targets: uniqueOrderedStrings(
@@ -3405,12 +3499,15 @@ function buildRunnerValidationAndDeliverySummary({
3405
3499
  ensureArray(assignmentValidationModes),
3406
3500
  (value) => String(value || "").trim().toLowerCase(),
3407
3501
  ),
3408
- delivery_status: String(deliveryStatus || "").trim().toLowerCase(),
3409
- archive_status: String(archiveStatus || "").trim().toLowerCase(),
3410
- transport_error: String(transportError || "").trim(),
3411
- archive_error: String(archiveError || "").trim(),
3412
- };
3413
- }
3502
+ delivery_status: String(deliveryStatus || "").trim().toLowerCase(),
3503
+ archive_status: String(archiveStatus || "").trim().toLowerCase(),
3504
+ transport_error: String(transportError || "").trim(),
3505
+ archive_error: String(archiveError || "").trim(),
3506
+ source_message_envelope: safeObject(sourceMessageEnvelope),
3507
+ last_reply_message_envelope: safeObject(lastReplyMessageEnvelope),
3508
+ attempted_delivery_envelope: safeObject(attemptedDeliveryEnvelope),
3509
+ };
3510
+ }
3414
3511
 
3415
3512
  function buildRunnerRequestRootWorkItemSummary(entryRaw) {
3416
3513
  const entry = safeObject(entryRaw);
@@ -3489,12 +3586,15 @@ function buildRunnerRouteLastResultSummary(routeStateRaw) {
3489
3586
  responseContractValidationTargets,
3490
3587
  assignmentValidationStatus,
3491
3588
  assignmentValidationReason,
3492
- assignmentValidationModes,
3493
- deliveryStatus: routeState.last_followup_delivery_status,
3494
- archiveStatus: routeState.last_followup_archive_status,
3495
- transportError: routeState.last_followup_transport_error,
3496
- archiveError: routeState.last_followup_archive_error,
3497
- }),
3589
+ assignmentValidationModes,
3590
+ deliveryStatus: routeState.last_followup_delivery_status,
3591
+ archiveStatus: routeState.last_followup_archive_status,
3592
+ transportError: routeState.last_followup_transport_error,
3593
+ archiveError: routeState.last_followup_archive_error,
3594
+ sourceMessageEnvelope: routeState.last_followup_source_message_envelope,
3595
+ lastReplyMessageEnvelope: routeState.last_followup_last_reply_message_envelope,
3596
+ attemptedDeliveryEnvelope: routeState.last_followup_attempted_delivery_envelope,
3597
+ }),
3498
3598
  workspace_dir: String(routeState.last_workspace_dir || "").trim(),
3499
3599
  artifact_validation: String(routeState.last_artifact_validation || "").trim(),
3500
3600
  artifact_paths: ensureArray(routeState.last_artifact_paths).map((item) => String(item || "").trim()).filter(Boolean),
@@ -3677,35 +3777,22 @@ async function findRunnerArchiveThreadMessageByID({
3677
3777
 
3678
3778
  function buildRunnerReplyChainSnapshotFromParsedArchive(parsedArchiveRaw, overrides = {}) {
3679
3779
  const parsedArchive = safeObject(parsedArchiveRaw);
3680
- const messageID = intFromRawAllowZero(overrides.message_id ?? parsedArchive.messageID, 0);
3681
- const messageThreadID = intFromRawAllowZero(
3682
- overrides.message_thread_id ?? parsedArchive.messageThreadID,
3683
- 0,
3780
+ return buildRunnerReplyChainSnapshotFromMessageEnvelope(
3781
+ buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsedArchive, overrides),
3782
+ {
3783
+ speaker_type: firstNonEmptyString([
3784
+ overrides.speaker_type,
3785
+ buildRunnerReplyChainSpeakerType(parsedArchive),
3786
+ ]),
3787
+ speaker_label: firstNonEmptyString([
3788
+ overrides.speaker_label,
3789
+ buildRunnerReplyChainSpeakerLabel(parsedArchive),
3790
+ ]),
3791
+ body: truncateRunnerReplyChainText(
3792
+ firstNonEmptyString([overrides.body, parsedArchive.body]),
3793
+ ),
3794
+ },
3684
3795
  );
3685
- const replyToMessageID = intFromRawAllowZero(
3686
- overrides.reply_to_message_id ?? parsedArchive.replyToMessageID,
3687
- 0,
3688
- );
3689
- const body = truncateRunnerReplyChainText(
3690
- firstNonEmptyString([overrides.body, parsedArchive.body]),
3691
- );
3692
- if (!(messageID > 0) && !body) {
3693
- return null;
3694
- }
3695
- return normalizeRunnerReplyChainSnapshot({
3696
- message_id: messageID,
3697
- message_thread_id: messageThreadID,
3698
- reply_to_message_id: replyToMessageID,
3699
- speaker_type: firstNonEmptyString([
3700
- overrides.speaker_type,
3701
- buildRunnerReplyChainSpeakerType(parsedArchive),
3702
- ]),
3703
- speaker_label: firstNonEmptyString([
3704
- overrides.speaker_label,
3705
- buildRunnerReplyChainSpeakerLabel(parsedArchive),
3706
- ]),
3707
- body,
3708
- });
3709
3796
  }
3710
3797
 
3711
3798
  function buildRunnerReplyChainSnapshotFromArchiveRecord(recordRaw) {
@@ -3716,16 +3803,28 @@ function buildRunnerReplyChainSnapshotFromArchiveRecord(recordRaw) {
3716
3803
 
3717
3804
  function buildRunnerReplyChainSnapshotFromRequestSource(requestRaw) {
3718
3805
  const request = safeObject(requestRaw);
3719
- return buildRunnerReplyChainSnapshotFromParsedArchive({
3720
- messageID: request.source_message_id,
3721
- messageThreadID: request.source_message_thread_id,
3722
- body: request.source_message_body,
3723
- senderIsBot: false,
3724
- sender: "human",
3725
- }, {
3726
- speaker_type: "human",
3727
- speaker_label: "human",
3728
- });
3806
+ const explicitEnvelope = normalizeRunnerTelegramMessageEnvelope(
3807
+ request.source_message_envelope || request.sourceMessageEnvelope,
3808
+ );
3809
+ const fallbackEnvelope = intFromRawAllowZero(request.source_message_id, 0) > 0
3810
+ ? {
3811
+ chat_id: request.chat_id,
3812
+ message_id: request.source_message_id,
3813
+ message_thread_id: request.source_message_thread_id,
3814
+ body: request.source_message_body,
3815
+ sender: "human",
3816
+ sender_is_bot: false,
3817
+ }
3818
+ : null;
3819
+ return buildRunnerReplyChainSnapshotFromMessageEnvelope(
3820
+ explicitEnvelope && Object.keys(explicitEnvelope).length
3821
+ ? explicitEnvelope
3822
+ : fallbackEnvelope,
3823
+ {
3824
+ speaker_type: "human",
3825
+ speaker_label: "human",
3826
+ },
3827
+ );
3729
3828
  }
3730
3829
 
3731
3830
  function buildRunnerReplyChainSnapshotFromRequestReply(requestRaw) {
@@ -3734,20 +3833,35 @@ function buildRunnerReplyChainSnapshotFromRequestReply(requestRaw) {
3734
3833
  if (!preview) {
3735
3834
  return null;
3736
3835
  }
3737
- return normalizeRunnerReplyChainSnapshot({
3738
- message_id: intFromRawAllowZero(request.last_reply_message_id, 0) || undefined,
3739
- message_thread_id: intFromRawAllowZero(request.last_reply_message_thread_id, 0) || undefined,
3740
- reply_to_message_id: intFromRawAllowZero(request.last_source_message_id || request.source_message_id, 0) || undefined,
3741
- speaker_type: "bot",
3742
- speaker_label: firstNonEmptyString([
3743
- request.conversation_summary_bot ? `@${String(request.conversation_summary_bot || "").trim().replace(/^@+/, "")}` : "",
3744
- ensureArray(request.selected_bot_usernames)[0]
3745
- ? `@${String(ensureArray(request.selected_bot_usernames)[0] || "").trim().replace(/^@+/, "")}`
3746
- : "",
3747
- "bot",
3748
- ]),
3749
- body: preview,
3750
- });
3836
+ const explicitEnvelope = normalizeRunnerTelegramMessageEnvelope(
3837
+ request.last_reply_message_envelope || request.lastReplyMessageEnvelope,
3838
+ );
3839
+ const fallbackEnvelope = intFromRawAllowZero(request.last_reply_message_id, 0) > 0
3840
+ ? buildTelegramBotReplyEnvelope({
3841
+ chatID: request.chat_id,
3842
+ messageID: request.last_reply_message_id,
3843
+ messageThreadID: request.last_reply_message_thread_id,
3844
+ replyToMessageID: request.last_reply_to_message_id || request.last_source_message_id || request.source_message_id,
3845
+ senderUsername: request.conversation_summary_bot || ensureArray(request.selected_bot_usernames)[0] || "",
3846
+ body: preview,
3847
+ })
3848
+ : null;
3849
+ return buildRunnerReplyChainSnapshotFromMessageEnvelope(
3850
+ explicitEnvelope && intFromRawAllowZero(explicitEnvelope.message_id, 0) > 0
3851
+ ? explicitEnvelope
3852
+ : fallbackEnvelope,
3853
+ {
3854
+ speaker_type: "bot",
3855
+ speaker_label: firstNonEmptyString([
3856
+ request.conversation_summary_bot ? `@${String(request.conversation_summary_bot || "").trim().replace(/^@+/, "")}` : "",
3857
+ ensureArray(request.selected_bot_usernames)[0]
3858
+ ? `@${String(ensureArray(request.selected_bot_usernames)[0] || "").trim().replace(/^@+/, "")}`
3859
+ : "",
3860
+ "bot",
3861
+ ]),
3862
+ body: preview,
3863
+ },
3864
+ );
3751
3865
  }
3752
3866
 
3753
3867
  function uniqueRunnerReplyChainSnapshots(snapshots, limit = 4) {
@@ -4361,6 +4475,7 @@ async function claimRunnerRequestForHumanComment({
4361
4475
  source_message_id: intFromRawAllowZero(parsed.messageID, 0) || undefined,
4362
4476
  source_message_thread_id: intFromRawAllowZero(parsed.messageThreadID, 0) || undefined,
4363
4477
  source_message_body: String(parsed.body || "").trim(),
4478
+ source_message_envelope: buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsed),
4364
4479
  root_comment_id: String(selectedRecord?.id || "").trim(),
4365
4480
  root_comment_kind: commentKind,
4366
4481
  conversation_id: resolvedConversationID,
@@ -4971,7 +5086,7 @@ function buildRunnerRootWorkItemTransitionPath(currentStatusRaw, targetStatusRaw
4971
5086
  return [];
4972
5087
  }
4973
5088
 
4974
- function buildRunnerRouteFollowupSnapshotPatch(selectedRecordRaw, resultRaw = {}) {
5089
+ function buildRunnerRouteFollowupSnapshotPatch(selectedRecordRaw, resultRaw = {}) {
4975
5090
  const parsed = safeObject(safeObject(selectedRecordRaw).parsedArchive);
4976
5091
  const commentKind = String(parsed.kind || "").trim().toLowerCase();
4977
5092
  if (["telegram_message", "telegram_edited_message"].includes(commentKind)) {
@@ -5010,12 +5125,15 @@ function buildRunnerRouteFollowupSnapshotPatch(selectedRecordRaw, resultRaw = {}
5010
5125
  ensureArray(result.assignment_validation_modes),
5011
5126
  (value) => String(value || "").trim().toLowerCase(),
5012
5127
  ),
5013
- last_followup_delivery_status: String(result.delivery_status || "").trim().toLowerCase(),
5014
- last_followup_archive_status: String(result.archive_status || "").trim().toLowerCase(),
5015
- last_followup_transport_error: String(result.transport_error || "").trim(),
5016
- last_followup_archive_error: String(result.archive_error || "").trim(),
5017
- });
5018
- }
5128
+ last_followup_delivery_status: String(result.delivery_status || "").trim().toLowerCase(),
5129
+ last_followup_archive_status: String(result.archive_status || "").trim().toLowerCase(),
5130
+ last_followup_transport_error: String(result.transport_error || "").trim(),
5131
+ last_followup_archive_error: String(result.archive_error || "").trim(),
5132
+ last_followup_source_message_envelope: normalizeRunnerTelegramMessageEnvelope(result.source_message_envelope),
5133
+ last_followup_last_reply_message_envelope: normalizeRunnerTelegramMessageEnvelope(result.last_reply_message_envelope),
5134
+ last_followup_attempted_delivery_envelope: normalizeRunnerTelegramMessageEnvelope(result.attempted_delivery_envelope),
5135
+ });
5136
+ }
5019
5137
 
5020
5138
  function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, requestRaw, routeKeyHint = "") {
5021
5139
  const currentState = safeObject(currentStateRaw);
@@ -5095,11 +5213,29 @@ function buildRunnerRequestRecoveryPatchFromRouteState(currentStateRaw, requestR
5095
5213
  setFollowupStringPatch("followup_assignment_validation_status", ["last_followup_assignment_validation_status", "last_assignment_validation_status"]);
5096
5214
  setFollowupStringPatch("followup_assignment_validation_reason", ["last_followup_assignment_validation_reason", "last_assignment_validation_reason"]);
5097
5215
  setFollowupArrayPatch("followup_assignment_validation_modes", ["last_followup_assignment_validation_modes"], (value) => String(value || "").trim().toLowerCase());
5098
- setFollowupStringPatch("followup_delivery_status", ["last_followup_delivery_status"]);
5099
- setFollowupStringPatch("followup_archive_status", ["last_followup_archive_status"]);
5100
- setFollowupStringPatch("followup_transport_error", ["last_followup_transport_error"]);
5101
- setFollowupStringPatch("followup_archive_error", ["last_followup_archive_error"]);
5102
- const requestStatus = normalizeRunnerRequestStatus(request.status);
5216
+ setFollowupStringPatch("followup_delivery_status", ["last_followup_delivery_status"]);
5217
+ setFollowupStringPatch("followup_archive_status", ["last_followup_archive_status"]);
5218
+ setFollowupStringPatch("followup_transport_error", ["last_followup_transport_error"]);
5219
+ setFollowupStringPatch("followup_archive_error", ["last_followup_archive_error"]);
5220
+ if (!Object.keys(safeObject(request.source_message_envelope)).length) {
5221
+ const recoveredSourceEnvelope = normalizeRunnerTelegramMessageEnvelope(routeState.last_followup_source_message_envelope);
5222
+ if (Object.keys(recoveredSourceEnvelope).length) {
5223
+ patch.source_message_envelope = recoveredSourceEnvelope;
5224
+ }
5225
+ }
5226
+ if (!Object.keys(safeObject(request.last_reply_message_envelope)).length) {
5227
+ const recoveredReplyEnvelope = normalizeRunnerTelegramMessageEnvelope(routeState.last_followup_last_reply_message_envelope);
5228
+ if (Object.keys(recoveredReplyEnvelope).length) {
5229
+ patch.last_reply_message_envelope = recoveredReplyEnvelope;
5230
+ }
5231
+ }
5232
+ if (!Object.keys(safeObject(request.attempted_delivery_envelope)).length) {
5233
+ const recoveredAttemptEnvelope = normalizeRunnerTelegramMessageEnvelope(routeState.last_followup_attempted_delivery_envelope);
5234
+ if (Object.keys(recoveredAttemptEnvelope).length) {
5235
+ patch.attempted_delivery_envelope = recoveredAttemptEnvelope;
5236
+ }
5237
+ }
5238
+ const requestStatus = normalizeRunnerRequestStatus(request.status);
5103
5239
  if (!isFinalRunnerRequestStatus(requestStatus)) {
5104
5240
  const routeAction = String(routeState.last_action || "").trim().toLowerCase();
5105
5241
  if (routeAction === "replied") {
@@ -5432,19 +5568,50 @@ function markRunnerRequestLifecycle({
5432
5568
  const key = String(requestKey || "").trim();
5433
5569
  if (!key) return null;
5434
5570
  const currentState = loadBotRunnerState();
5435
- const existing = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
5436
- if (!Object.keys(existing).length) return null;
5437
- const parsed = safeObject(selectedRecord?.parsedArchive);
5438
- const conversationID = String(conversationIDRaw || existing.conversation_id || parsed.conversationID || "").trim();
5439
- const normalizedCurrentBotSelector = normalizeTelegramMentionUsername(
5571
+ const existing = safeObject(normalizeBotRunnerRequests(currentState.requests)[key]);
5572
+ if (!Object.keys(existing).length) return null;
5573
+ const parsed = safeObject(selectedRecord?.parsedArchive);
5574
+ const parsedKind = String(parsed.kind || "").trim().toLowerCase();
5575
+ const conversationID = String(conversationIDRaw || existing.conversation_id || parsed.conversationID || "").trim();
5576
+ const normalizedCurrentBotSelector = normalizeTelegramMentionUsername(
5440
5577
  currentBotSelector
5441
5578
  || safeObject(currentState.routes)[String(routeKey || "").trim()]?.last_speaker_bot_username
5442
- || "",
5443
- );
5444
- const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
5445
- [
5446
- ...ensureArray(executionContractTargets),
5447
- ...ensureArray(normalizedExecutionContractTargets),
5579
+ || "",
5580
+ );
5581
+ const sourceMessageEnvelope = buildRunnerTelegramMessageEnvelopeFromParsedArchive(parsed);
5582
+ const effectiveReplyToMessageID = intFromRawAllowZero(
5583
+ replyToMessageID,
5584
+ intFromRawAllowZero(existing.last_reply_to_message_id, 0),
5585
+ );
5586
+ const lastReplyMessageEnvelope = buildTelegramBotReplyEnvelope({
5587
+ sourceEnvelope: sourceMessageEnvelope,
5588
+ chatID: existing.chat_id,
5589
+ messageID: lastReplyMessageID,
5590
+ messageThreadID: lastReplyMessageThreadID,
5591
+ replyToMessageID: effectiveReplyToMessageID,
5592
+ senderUsername: normalizedCurrentBotSelector,
5593
+ body: aiReplyPreview,
5594
+ });
5595
+ const attemptedDeliveryEnvelope = buildTelegramBotReplyEnvelope({
5596
+ sourceEnvelope: sourceMessageEnvelope,
5597
+ chatID: existing.chat_id,
5598
+ messageThreadID: lastReplyMessageThreadID,
5599
+ replyToMessageID: effectiveReplyToMessageID,
5600
+ senderUsername: normalizedCurrentBotSelector,
5601
+ body: aiReplyPreview,
5602
+ });
5603
+ const shouldRefreshAttemptedDeliveryEnvelope = (
5604
+ aiReplyGenerated === true
5605
+ || String(aiReplyPreview || "").trim().length > 0
5606
+ || String(deliveryStatus || "").trim().length > 0
5607
+ || String(transportError || "").trim().length > 0
5608
+ || intFromRawAllowZero(replyToMessageID, 0) > 0
5609
+ || intFromRawAllowZero(lastReplyMessageThreadID, 0) > 0
5610
+ );
5611
+ const rootEffectiveExecutionContractTargets = uniqueOrderedStrings(
5612
+ [
5613
+ ...ensureArray(executionContractTargets),
5614
+ ...ensureArray(normalizedExecutionContractTargets),
5448
5615
  ...ensureArray(responseContractValidationTargets),
5449
5616
  ],
5450
5617
  normalizeTelegramMentionUsername,
@@ -5779,9 +5946,17 @@ function markRunnerRequestLifecycle({
5779
5946
  closed_at: (nextStatus === "closed" || nextStatus === "expired" || nextStatus === "loop_closed")
5780
5947
  ? nowISO
5781
5948
  : String(existing.closed_at || "").trim(),
5782
- closed_reason: (nextStatus === "closed" || nextStatus === "expired" || nextStatus === "loop_closed")
5783
- ? String(closedReason || existing.closed_reason || normalizedOutcome).trim()
5784
- : String(existing.closed_reason || "").trim(),
5949
+ closed_reason: (nextStatus === "closed" || nextStatus === "expired" || nextStatus === "loop_closed")
5950
+ ? String(closedReason || existing.closed_reason || normalizedOutcome).trim()
5951
+ : String(existing.closed_reason || "").trim(),
5952
+ source_message_body: parsedKind === "bot_reply"
5953
+ ? String(existing.source_message_body || "").trim()
5954
+ : String(parsed.body || existing.source_message_body || "").trim(),
5955
+ source_message_envelope: parsedKind === "bot_reply"
5956
+ ? safeObject(existing.source_message_envelope)
5957
+ : Object.keys(sourceMessageEnvelope).length
5958
+ ? sourceMessageEnvelope
5959
+ : safeObject(existing.source_message_envelope),
5785
5960
  last_comment_id: String(selectedRecord?.id || existing.last_comment_id || "").trim(),
5786
5961
  last_comment_kind: String(parsed.kind || existing.last_comment_kind || "").trim().toLowerCase(),
5787
5962
  last_source_message_id: intFromRawAllowZero(parsed.messageID, 0) || existing.last_source_message_id,
@@ -5789,6 +5964,12 @@ function markRunnerRequestLifecycle({
5789
5964
  last_reply_message_id: intFromRawAllowZero(lastReplyMessageID, 0) || existing.last_reply_message_id,
5790
5965
  last_reply_message_thread_id: intFromRawAllowZero(lastReplyMessageThreadID, 0) || existing.last_reply_message_thread_id,
5791
5966
  last_reply_to_message_id: intFromRawAllowZero(replyToMessageID, 0) || existing.last_reply_to_message_id,
5967
+ last_reply_message_envelope: intFromRawAllowZero(lastReplyMessageEnvelope.message_id, 0) > 0
5968
+ ? lastReplyMessageEnvelope
5969
+ : safeObject(existing.last_reply_message_envelope),
5970
+ attempted_delivery_envelope: shouldRefreshAttemptedDeliveryEnvelope
5971
+ ? attemptedDeliveryEnvelope
5972
+ : safeObject(existing.attempted_delivery_envelope),
5792
5973
  last_reply_fallback_used: replyFallbackUsed === true
5793
5974
  ? true
5794
5975
  : existing.last_reply_fallback_used === true,
@@ -5997,6 +6178,9 @@ function mergeRunnerRequestForServerHydration(localEntryRaw, serverEntryRaw) {
5997
6178
  }
5998
6179
  };
5999
6180
  preserveLocalStringWhenServerBlank("source_message_body");
6181
+ preserveLocalObjectWhenServerBlank("source_message_envelope");
6182
+ preserveLocalObjectWhenServerBlank("last_reply_message_envelope");
6183
+ preserveLocalObjectWhenServerBlank("attempted_delivery_envelope");
6000
6184
  preserveLocalStringWhenServerBlank("conversation_id");
6001
6185
  preserveLocalStringWhenServerBlank("conversation_intent_mode");
6002
6186
  preserveLocalStringWhenServerBlank("conversation_lead_bot");
@@ -8444,22 +8628,25 @@ function summarizeRunnerRequestForStatusLookup(entryRaw) {
8444
8628
  updated_at: String(entry.updated_at || "").trim(),
8445
8629
  source_message_id: intFromRawAllowZero(entry.source_message_id, 0) || undefined,
8446
8630
  last_source_message_id: intFromRawAllowZero(entry.last_source_message_id, 0) || undefined,
8447
- ...buildRunnerValidationAndDeliverySummary({
8448
- aiReplyPreview: runnerRequestPreferredAIReplyPreview(entry),
8449
- executionContractType: runnerRequestPreferredExecutionContractType(entry),
8450
- executionContractTargets: runnerRequestPreferredExecutionContractTargets(entry),
8451
- nextExpectedResponders: runnerRequestPreferredNextExpectedResponders(entry),
8631
+ ...buildRunnerValidationAndDeliverySummary({
8632
+ aiReplyPreview: runnerRequestPreferredAIReplyPreview(entry),
8633
+ executionContractType: runnerRequestPreferredExecutionContractType(entry),
8634
+ executionContractTargets: runnerRequestPreferredExecutionContractTargets(entry),
8635
+ nextExpectedResponders: runnerRequestPreferredNextExpectedResponders(entry),
8452
8636
  responseContractValidationStatus: runnerRequestPreferredResponseContractValidationStatus(entry),
8453
8637
  responseContractValidationReason: runnerRequestPreferredResponseContractValidationReason(entry),
8454
8638
  responseContractValidationTargets: runnerRequestPreferredResponseContractValidationTargets(entry),
8455
8639
  assignmentValidationStatus: runnerRequestPreferredAssignmentValidationStatus(entry),
8456
8640
  assignmentValidationReason: runnerRequestPreferredAssignmentValidationReason(entry),
8457
8641
  assignmentValidationModes: runnerRequestPreferredAssignmentValidationModes(entry),
8458
- deliveryStatus: runnerRequestPreferredDeliveryStatus(entry),
8459
- archiveStatus: runnerRequestPreferredArchiveStatus(entry),
8460
- transportError: runnerRequestPreferredTransportError(entry),
8461
- archiveError: runnerRequestPreferredArchiveError(entry),
8462
- }),
8642
+ deliveryStatus: runnerRequestPreferredDeliveryStatus(entry),
8643
+ archiveStatus: runnerRequestPreferredArchiveStatus(entry),
8644
+ transportError: runnerRequestPreferredTransportError(entry),
8645
+ archiveError: runnerRequestPreferredArchiveError(entry),
8646
+ sourceMessageEnvelope: entry.source_message_envelope,
8647
+ lastReplyMessageEnvelope: entry.last_reply_message_envelope,
8648
+ attemptedDeliveryEnvelope: entry.attempted_delivery_envelope,
8649
+ }),
8463
8650
  selected_bot_usernames: ensureArray(entry.selected_bot_usernames)
8464
8651
  .map((value) => normalizeTelegramMentionUsername(value))
8465
8652
  .filter(Boolean),
@@ -8569,10 +8756,10 @@ function resolveRunnerStatusLookupRequests({
8569
8756
 
8570
8757
  function buildRunnerShowLastRunPayload(lastRunSummaryRaw) {
8571
8758
  const lastRunSummary = safeObject(lastRunSummaryRaw);
8572
- return {
8573
- action: lastRunSummary.action || "-",
8574
- reason: lastRunSummary.reason || "-",
8575
- intent_type: lastRunSummary.intent_type || "-",
8759
+ return {
8760
+ action: lastRunSummary.action || "-",
8761
+ reason: lastRunSummary.reason || "-",
8762
+ intent_type: lastRunSummary.intent_type || "-",
8576
8763
  ai_reply_preview: lastRunSummary.ai_reply_preview || "-",
8577
8764
  execution_contract_type: lastRunSummary.execution_contract_type || "-",
8578
8765
  execution_contract_targets: ensureArray(lastRunSummary.execution_contract_targets),
@@ -8585,10 +8772,13 @@ function buildRunnerShowLastRunPayload(lastRunSummaryRaw) {
8585
8772
  assignment_validation_modes: ensureArray(lastRunSummary.assignment_validation_modes),
8586
8773
  delivery_status: lastRunSummary.delivery_status || "-",
8587
8774
  archive_status: lastRunSummary.archive_status || "-",
8588
- transport_error: lastRunSummary.transport_error || "-",
8589
- archive_error: lastRunSummary.archive_error || "-",
8590
- workspace_dir: lastRunSummary.workspace_dir || "-",
8591
- artifact_validation: lastRunSummary.artifact_validation || "-",
8775
+ transport_error: lastRunSummary.transport_error || "-",
8776
+ archive_error: lastRunSummary.archive_error || "-",
8777
+ source_message_envelope: safeObject(lastRunSummary.source_message_envelope),
8778
+ last_reply_message_envelope: safeObject(lastRunSummary.last_reply_message_envelope),
8779
+ attempted_delivery_envelope: safeObject(lastRunSummary.attempted_delivery_envelope),
8780
+ workspace_dir: lastRunSummary.workspace_dir || "-",
8781
+ artifact_validation: lastRunSummary.artifact_validation || "-",
8592
8782
  artifact_paths: ensureArray(lastRunSummary.artifact_paths),
8593
8783
  artifact_errors: ensureArray(lastRunSummary.artifact_errors),
8594
8784
  boundary_violations: ensureArray(lastRunSummary.boundary_violations),
@@ -23,6 +23,14 @@ function parseJSONText(rawText) {
23
23
  }
24
24
  }
25
25
 
26
+ function intFromRawAllowZero(value, fallback = 0) {
27
+ if (value === null || value === undefined || value === "") {
28
+ return fallback;
29
+ }
30
+ const parsed = Number.parseInt(String(value).trim(), 10);
31
+ return Number.isFinite(parsed) ? parsed : fallback;
32
+ }
33
+
26
34
  function sleep(ms) {
27
35
  return new Promise((resolve) => setTimeout(resolve, ms));
28
36
  }
@@ -31,6 +31,130 @@ function intFromRaw(raw, fallback) {
31
31
  return parsed > 0 ? parsed : fallback;
32
32
  }
33
33
 
34
+ export function normalizeTelegramMessageEnvelope(rawEnvelope) {
35
+ const envelope = safeObject(rawEnvelope);
36
+ const chatID = String(
37
+ envelope.chat_id
38
+ || envelope.chatID
39
+ || envelope.chatId
40
+ || "",
41
+ ).trim();
42
+ const messageID = intFromRawAllowZero(envelope.message_id || envelope.messageID, 0);
43
+ const messageThreadID = intFromRawAllowZero(
44
+ envelope.message_thread_id || envelope.messageThreadID,
45
+ 0,
46
+ );
47
+ const replyToMessageID = intFromRawAllowZero(
48
+ envelope.reply_to_message_id || envelope.replyToMessageID,
49
+ 0,
50
+ );
51
+ const kind = String(envelope.kind || "").trim().toLowerCase();
52
+ const senderUsername = normalizeMentionSelector(
53
+ envelope.sender_username
54
+ || envelope.senderUsername
55
+ || envelope.username
56
+ || envelope.bot_username
57
+ || envelope.botUsername
58
+ || "",
59
+ );
60
+ const sender = firstNonEmptyString([
61
+ envelope.sender,
62
+ senderUsername ? `@${senderUsername}` : "",
63
+ ]);
64
+ const rawSenderIsBot = envelope.sender_is_bot ?? envelope.senderIsBot;
65
+ const senderIsBot = rawSenderIsBot === true
66
+ || rawSenderIsBot === "true"
67
+ || rawSenderIsBot === 1
68
+ || rawSenderIsBot === "1"
69
+ || kind === "bot_reply";
70
+ const body = String(envelope.body || envelope.text || "").trim();
71
+ if (
72
+ !chatID
73
+ && !(messageID > 0)
74
+ && !(messageThreadID > 0)
75
+ && !(replyToMessageID > 0)
76
+ && !kind
77
+ && !sender
78
+ && !senderUsername
79
+ && !body
80
+ ) {
81
+ return {};
82
+ }
83
+ return {
84
+ ...(chatID ? { chat_id: chatID } : {}),
85
+ ...(messageID > 0 ? { message_id: messageID } : {}),
86
+ ...(messageThreadID > 0 ? { message_thread_id: messageThreadID } : {}),
87
+ ...(replyToMessageID > 0 ? { reply_to_message_id: replyToMessageID } : {}),
88
+ ...(kind ? { kind } : {}),
89
+ ...(sender ? { sender } : {}),
90
+ ...(senderUsername ? { sender_username: senderUsername } : {}),
91
+ sender_is_bot: senderIsBot === true,
92
+ ...(body ? { body } : {}),
93
+ };
94
+ }
95
+
96
+ export function buildTelegramMessageEnvelopeFromParsedArchive(parsedArchiveRaw, overrides = {}) {
97
+ const parsedArchive = safeObject(parsedArchiveRaw);
98
+ return normalizeTelegramMessageEnvelope({
99
+ chat_id: firstNonEmptyString([
100
+ overrides.chat_id,
101
+ parsedArchive.chatID,
102
+ parsedArchive.chatId,
103
+ ]),
104
+ message_id: overrides.message_id ?? parsedArchive.messageID,
105
+ message_thread_id: overrides.message_thread_id ?? parsedArchive.messageThreadID,
106
+ reply_to_message_id: overrides.reply_to_message_id ?? parsedArchive.replyToMessageID,
107
+ kind: firstNonEmptyString([overrides.kind, parsedArchive.kind]),
108
+ sender: firstNonEmptyString([
109
+ overrides.sender,
110
+ parsedArchive.sender,
111
+ parsedArchive.botName,
112
+ parsedArchive.replyToSender,
113
+ ]),
114
+ sender_username: firstNonEmptyString([
115
+ overrides.sender_username,
116
+ parsedArchive.botUsername,
117
+ parsedArchive.username,
118
+ parsedArchive.replyToUsername,
119
+ ]),
120
+ sender_is_bot: overrides.sender_is_bot ?? parsedArchive.senderIsBot,
121
+ body: firstNonEmptyString([overrides.body, parsedArchive.body]),
122
+ });
123
+ }
124
+
125
+ export function buildTelegramBotReplyEnvelope({
126
+ sourceEnvelope: sourceEnvelopeRaw = {},
127
+ chatID = "",
128
+ messageID = 0,
129
+ messageThreadID = 0,
130
+ replyToMessageID = 0,
131
+ sender = "",
132
+ senderUsername = "",
133
+ body = "",
134
+ } = {}) {
135
+ const sourceEnvelope = normalizeTelegramMessageEnvelope(sourceEnvelopeRaw);
136
+ const normalizedSenderUsername = normalizeMentionSelector(senderUsername);
137
+ const senderLabel = firstNonEmptyString([
138
+ sender,
139
+ normalizedSenderUsername ? `@${normalizedSenderUsername}` : "",
140
+ "bot",
141
+ ]);
142
+ return normalizeTelegramMessageEnvelope({
143
+ chat_id: firstNonEmptyString([
144
+ chatID,
145
+ sourceEnvelope.chat_id,
146
+ ]),
147
+ message_id: intFromRawAllowZero(messageID, 0) || undefined,
148
+ message_thread_id: intFromRawAllowZero(messageThreadID, 0) || undefined,
149
+ reply_to_message_id: intFromRawAllowZero(replyToMessageID, 0) || undefined,
150
+ kind: "bot_reply",
151
+ sender: senderLabel,
152
+ sender_username: normalizedSenderUsername,
153
+ sender_is_bot: true,
154
+ body: String(body || "").trim(),
155
+ });
156
+ }
157
+
34
158
  function normalizePendingSelectionOptions(rawOptions) {
35
159
  const options = safeObject(rawOptions);
36
160
  const maxPendingAgeMs = intFromRawAllowZero(options.maxPendingAgeMs, 0);
@@ -1,11 +1,14 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import {
4
+ buildTelegramBotReplyEnvelope,
5
+ buildTelegramMessageEnvelopeFromParsedArchive,
4
6
  buildRunnerContextWindow,
5
7
  buildRunnerRouteStateFromComment,
6
8
  compareArchiveCommentRecords,
7
9
  dedupeProcessableArchiveComments,
8
10
  isInboundArchiveKind,
11
+ normalizeTelegramMessageEnvelope,
9
12
  selectPendingArchiveComments,
10
13
  } from "./runner-helpers.mjs";
11
14
  import {
@@ -4717,16 +4720,18 @@ export async function processRunnerSelectedRecord({
4717
4720
  safeObject(persistedHumanIntentRequest).reply_chain_context
4718
4721
  || safeObject(persistedHumanIntentRequest).replyChainContext,
4719
4722
  );
4720
- const replyMessageThreadID = intFromRawAllowZero(
4721
- safeObject(selectedRecord?.parsedArchive).messageThreadID
4722
- || authorityReplyChainContext.current_message_thread_id
4723
- || safeObject(authorityReplyChainContext.parent_message).message_thread_id
4724
- || safeObject(authorityReplyChainContext.latest_prior_bot_reply).message_thread_id
4725
- || safeObject(persistedHumanIntentRequest).last_source_message_thread_id
4726
- || safeObject(persistedHumanIntentRequest).source_message_thread_id
4727
- || safeObject(persistedHumanIntentRequest).last_reply_message_thread_id,
4728
- 0,
4729
- );
4723
+ const sourceMessageEnvelope = buildTelegramMessageEnvelopeFromParsedArchive(selectedRecord?.parsedArchive);
4724
+ const replyMessageThreadID = intFromRawAllowZero(sourceMessageEnvelope.message_thread_id, 0);
4725
+ const replyToMessageID = intFromRawAllowZero(sourceMessageEnvelope.message_id, 0);
4726
+ const replyAnchorSource = replyToMessageID > 0 ? "source_message_envelope" : "";
4727
+ const attemptedDeliveryEnvelope = buildTelegramBotReplyEnvelope({
4728
+ sourceEnvelope: sourceMessageEnvelope,
4729
+ messageThreadID: replyMessageThreadID,
4730
+ replyToMessageID,
4731
+ sender: bot?.username ? `@${String(bot.username || "").trim().replace(/^@+/, "")}` : String(bot?.name || "bot").trim(),
4732
+ senderUsername: normalizeMentionSelector(bot?.username || bot?.name),
4733
+ body: sanitizedReplyText,
4734
+ });
4730
4735
  const normalizedPrecomputedHumanIntentContext = safeObject(precomputedHumanIntentContext);
4731
4736
  const validateWorkspaceArtifacts = typeof executionDeps.validateWorkspaceArtifacts === "function"
4732
4737
  ? executionDeps.validateWorkspaceArtifacts
@@ -5136,7 +5141,7 @@ export async function processRunnerSelectedRecord({
5136
5141
  text: replyText,
5137
5142
  disableWebPagePreview: true,
5138
5143
  messageThreadID: replyMessageThreadID,
5139
- replyToMessageID: intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0),
5144
+ replyToMessageID,
5140
5145
  archiveReplies: normalizedRoute.archivePolicy.mirrorReplies,
5141
5146
  archiveDedupeOutbound: normalizedRoute.archivePolicy.dedupeOutbound,
5142
5147
  archiveThreadID: archiveThread.threadID,
@@ -5488,9 +5493,6 @@ export async function processRunnerSelectedRecord({
5488
5493
  },
5489
5494
  };
5490
5495
  }
5491
- const replyToMessageID = aiResult.replyToMessageID > 0
5492
- ? aiResult.replyToMessageID
5493
- : intFromRawAllowZero(selectedRecord.parsedArchive?.messageID, 0);
5494
5496
  const sanitizedReplyText = sanitizeForcedDirectReplyText({
5495
5497
  replyText: aiResult.reply,
5496
5498
  bot,
@@ -5667,6 +5669,10 @@ export async function processRunnerSelectedRecord({
5667
5669
  ai_reply_generated: true,
5668
5670
  ai_reply_generated_at: aiReplyGeneratedAt,
5669
5671
  ai_reply_preview: aiReplyPreview,
5672
+ source_message_envelope: sourceMessageEnvelope,
5673
+ attempted_delivery_envelope: attemptedDeliveryEnvelope,
5674
+ reply_to_message_id: replyToMessageID,
5675
+ reply_message_thread_id: replyMessageThreadID,
5670
5676
  delivery_status: "failed_transport",
5671
5677
  archive_status: "not_attempted",
5672
5678
  transport_error: transportError,
@@ -5765,6 +5771,7 @@ export async function processRunnerSelectedRecord({
5765
5771
  0,
5766
5772
  ),
5767
5773
  last_reply_message_thread_id: intFromRawAllowZero(deliveryResult.delivery.effectiveMessageThreadID, replyMessageThreadID),
5774
+ last_reply_anchor_source: replyAnchorSource,
5768
5775
  last_contract_validation_status: String(responseContractValidation.status || "").trim(),
5769
5776
  last_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
5770
5777
  last_contract_validation_targets: ensureArray(responseContractValidation.targets),
@@ -5873,12 +5880,27 @@ export async function processRunnerSelectedRecord({
5873
5880
  ai_reply_generated: true,
5874
5881
  ai_reply_generated_at: aiReplyGeneratedAt,
5875
5882
  ai_reply_preview: aiReplyPreview,
5883
+ source_message_envelope: sourceMessageEnvelope,
5884
+ attempted_delivery_envelope: attemptedDeliveryEnvelope,
5876
5885
  last_reply_message_id: intFromRawAllowZero(
5877
5886
  safeObject(deliveryResult.delivery.body).result?.message_id ?? safeObject(deliveryResult.delivery.body).message_id,
5878
5887
  0,
5879
5888
  ),
5880
5889
  last_reply_message_thread_id: intFromRawAllowZero(deliveryResult.delivery.effectiveMessageThreadID, replyMessageThreadID),
5881
5890
  reply_to_message_id: intFromRawAllowZero(deliveryResult.delivery.effectiveReplyToMessageID, replyToMessageID),
5891
+ last_reply_message_envelope: buildTelegramBotReplyEnvelope({
5892
+ sourceEnvelope: sourceMessageEnvelope,
5893
+ messageID: intFromRawAllowZero(
5894
+ safeObject(deliveryResult.delivery.body).result?.message_id ?? safeObject(deliveryResult.delivery.body).message_id,
5895
+ 0,
5896
+ ),
5897
+ messageThreadID: intFromRawAllowZero(deliveryResult.delivery.effectiveMessageThreadID, replyMessageThreadID),
5898
+ replyToMessageID: intFromRawAllowZero(deliveryResult.delivery.effectiveReplyToMessageID, replyToMessageID),
5899
+ sender: bot?.username ? `@${String(bot.username || "").trim().replace(/^@+/, "")}` : String(bot?.name || "bot").trim(),
5900
+ senderUsername: normalizeMentionSelector(bot?.username || bot?.name),
5901
+ body: sanitizedReplyText,
5902
+ }),
5903
+ reply_anchor_source: replyAnchorSource,
5882
5904
  reply_fallback_used: deliveryResult.delivery.replyFallbackUsed === true,
5883
5905
  response_contract_validation_status: String(responseContractValidation.status || "").trim(),
5884
5906
  response_contract_validation_reason: String(responseContractValidation.reason || "").trim(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metheus-governance-mcp-cli",
3
- "version": "0.2.241",
3
+ "version": "0.2.247",
4
4
  "description": "Metheus Governance MCP CLI (setup + stdio proxy)",
5
5
  "type": "module",
6
6
  "files": [