clawconnect-agent 0.2.9 → 0.2.11

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.
Files changed (63) hide show
  1. package/dist/commands/pair.d.ts +13 -0
  2. package/dist/commands/pair.js +68 -41
  3. package/dist/commands/pair.js.map +1 -1
  4. package/dist/commands/profile-hints.d.ts +1 -0
  5. package/dist/commands/profile-hints.js +3 -0
  6. package/dist/commands/profile-hints.js.map +1 -1
  7. package/dist/commands/run.js +24 -2
  8. package/dist/commands/run.js.map +1 -1
  9. package/dist/commands/status.d.ts +1 -1
  10. package/dist/commands/status.js +15 -6
  11. package/dist/commands/status.js.map +1 -1
  12. package/dist/config/config-backups.d.ts +12 -0
  13. package/dist/config/config-backups.js +44 -0
  14. package/dist/config/config-backups.js.map +1 -0
  15. package/dist/config/config.js +16 -2
  16. package/dist/config/config.js.map +1 -1
  17. package/dist/config/profile-log-rotation.d.ts +17 -0
  18. package/dist/config/profile-log-rotation.js +37 -0
  19. package/dist/config/profile-log-rotation.js.map +1 -0
  20. package/dist/core/relay/file-upload.js +4 -1
  21. package/dist/core/relay/file-upload.js.map +1 -1
  22. package/dist/core/relay/reconnect.d.ts +13 -3
  23. package/dist/core/relay/reconnect.js +20 -4
  24. package/dist/core/relay/reconnect.js.map +1 -1
  25. package/dist/core/relay/relay-liveness.d.ts +23 -0
  26. package/dist/core/relay/relay-liveness.js +61 -0
  27. package/dist/core/relay/relay-liveness.js.map +1 -0
  28. package/dist/core/relay/relay-server-connection.d.ts +10 -1
  29. package/dist/core/relay/relay-server-connection.js +11 -2
  30. package/dist/core/relay/relay-server-connection.js.map +1 -1
  31. package/dist/hermes/hermes-relay-manager.d.ts +6 -1
  32. package/dist/hermes/hermes-relay-manager.js +10 -1
  33. package/dist/hermes/hermes-relay-manager.js.map +1 -1
  34. package/dist/hermes/hermes-session-store.js +3 -1
  35. package/dist/hermes/hermes-session-store.js.map +1 -1
  36. package/dist/hermes/runtime/hermes-runtime-state-db.js +16 -2
  37. package/dist/hermes/runtime/hermes-runtime-state-db.js.map +1 -1
  38. package/dist/i18n/index.js +4 -0
  39. package/dist/i18n/index.js.map +1 -1
  40. package/dist/openclaw/relay/chat-history-normalizers.d.ts +50 -0
  41. package/dist/openclaw/relay/chat-history-normalizers.js +296 -0
  42. package/dist/openclaw/relay/chat-history-normalizers.js.map +1 -0
  43. package/dist/openclaw/relay/chat-history.d.ts +7 -0
  44. package/dist/openclaw/relay/chat-history.js +58 -308
  45. package/dist/openclaw/relay/chat-history.js.map +1 -1
  46. package/dist/openclaw/relay/openclaw-context-usage-publisher.d.ts +33 -0
  47. package/dist/openclaw/relay/openclaw-context-usage-publisher.js +81 -0
  48. package/dist/openclaw/relay/openclaw-context-usage-publisher.js.map +1 -0
  49. package/dist/openclaw/relay/openclaw-heartbeat-markers.d.ts +23 -0
  50. package/dist/openclaw/relay/openclaw-heartbeat-markers.js +53 -0
  51. package/dist/openclaw/relay/openclaw-heartbeat-markers.js.map +1 -0
  52. package/dist/openclaw/relay/relay-manager-protocol.d.ts +12 -1
  53. package/dist/openclaw/relay/source-commit-projection.js +20 -3
  54. package/dist/openclaw/relay/source-commit-projection.js.map +1 -1
  55. package/dist/openclaw/relay-manager.js +68 -109
  56. package/dist/openclaw/relay-manager.js.map +1 -1
  57. package/dist/runtime-adapters.d.ts +3 -1
  58. package/dist/runtime-adapters.js +2 -0
  59. package/dist/runtime-adapters.js.map +1 -1
  60. package/package.json +1 -1
  61. package/dist/hermes/hermes-runtime-test-support.d.ts +0 -27
  62. package/dist/hermes/hermes-runtime-test-support.js +0 -709
  63. package/dist/hermes/hermes-runtime-test-support.js.map +0 -1
@@ -1,6 +1,7 @@
1
- import { createHash } from "node:crypto";
1
+ import { cleanHistoryString, extractAttachmentIds, fallbackTimelineCreatedAt, historyNumber, historyString, isRecord, messageSeq, normalizeCursor, normalizeHistoryCreatedAt, normalizeHistoryDirection, normalizeHistoryLimit, normalizeHistoryTimestamp, normalizeTimelineContentBlocks, normalizeTimelineCreatedAt, normalizeTimelineMessageState, normalizeTimelineRole, openClawHistoryString, openClawRunId, openClawTranscriptSource, paginateHistoryMessages, parseHistoryCursorSeq, requireProjectionIdentity, requireProjectionSequence, } from "./chat-history-normalizers.js";
2
2
  import { DatabaseSync } from "node:sqlite";
3
3
  import { join } from "node:path";
4
+ import { zstdDecompressSync } from "node:zlib";
4
5
  import { restoreGatewayHistoryMessages } from "./gateway-history-projection.js";
5
6
  import { readFile, stat } from "fs/promises";
6
7
  import { buildHistorySnapshotPage } from "../../core/relay/timeline-event-builder.js";
@@ -8,6 +9,7 @@ import { canonicalizeOpenClawSessionScope, resolveOpenClawSessionTranscript, ses
8
9
  import { resolveOpenClawStateDir } from "../runtime/openclaw-paths.js";
9
10
  import { normalizeOpenClawAssistantMediaSidecars, normalizeOpenClawAutomaticMediaReplies, } from "./assistant-media-sidecar.js";
10
11
  import { adaptOpenClawMessageToolDelivery } from "./outgoing-media-relay.js";
12
+ import { isOpenClawHeartbeatAckText, isOpenClawHeartbeatPromptText } from "./openclaw-heartbeat-markers.js";
11
13
  import { createProjectionMetadata, openClawAgentIdFromSessionKey, openClawSourceOrderScope, } from "../../core/relay/timeline-projection-v3.js";
12
14
  import { createSourceCommit } from "../../core/relay/source-commit.js";
13
15
  /**
@@ -55,11 +57,6 @@ export function readOpenClawSourceCommitCursor(rawParams, defaults) {
55
57
  database?.close();
56
58
  }
57
59
  }
58
- const DEFAULT_TRANSCRIPT_HISTORY_LIMIT = 100;
59
- const MAX_TRANSCRIPT_HISTORY_LIMIT = 200;
60
- const CURSOR_PREFIX = "seq:";
61
- const OPENCLAW_HEARTBEAT_TRANSCRIPT_PROMPT = "[OpenClaw heartbeat poll]";
62
- const OPENCLAW_HEARTBEAT_ACK = "HEARTBEAT_OK";
63
60
  const transcriptHistoryCache = new Map();
64
61
  export function clearTranscriptHistoryCache() {
65
62
  transcriptHistoryCache.clear();
@@ -100,12 +97,7 @@ function readOpenClawSqliteChatHistory(params, defaults) {
100
97
  const sessionId = cleanHistoryString(session?.current_session_id);
101
98
  if (!sessionId)
102
99
  return null;
103
- const rows = database.prepare(`
104
- SELECT seq, event_json
105
- FROM transcript_events
106
- WHERE session_id = ?
107
- ORDER BY seq ASC
108
- `).all(sessionId);
100
+ const rows = readSqliteTranscriptEventRows(database, sessionId);
109
101
  const visibleParentIds = resolveSqliteVisibleParentIds(rows);
110
102
  const messages = rows
111
103
  .map((row) => sqliteTranscriptHistoryMessage(row, sessionId, params.projectionVersion === 3, visibleParentIds))
@@ -490,20 +482,6 @@ function gatewayHistoryMessageId(input) {
490
482
  }
491
483
  return input.providerMessageId ?? `${input.role}-${input.turnId}`;
492
484
  }
493
- function openClawHistoryMetadata(message) {
494
- return isRecord(message.__openclaw) ? message.__openclaw : undefined;
495
- }
496
- function openClawHistoryString(message, ...fields) {
497
- const metadata = openClawHistoryMetadata(message);
498
- return metadata ? historyString(metadata, ...fields) : undefined;
499
- }
500
- function openClawRunId(message) {
501
- return openClawHistoryString(message, "runId", "run_id");
502
- }
503
- function openClawTranscriptSource(message) {
504
- const position = openClawHistoryMetadata(message)?.transcriptPosition;
505
- return isRecord(position) ? historyString(position, "source") : undefined;
506
- }
507
485
  function normalizeTranscriptHistoryParams(rawParams, fallbackSessionKey) {
508
486
  const record = isRecord(rawParams) ? rawParams : {};
509
487
  const sessionKey = typeof record.sessionKey === "string" && record.sessionKey.trim().length > 0
@@ -548,15 +526,47 @@ async function readIndexedTranscriptMessages(transcriptPath, sessionKey, project
548
526
  });
549
527
  return visibleMessages;
550
528
  }
529
+ /**
530
+ * OpenClaw 会把体积较大的 transcript 事件只写入 `event_zstd`(此时 `event_json` 为 NULL)。
531
+ * 这些行同样是权威时间线的一部分:漏读会造成 source seq 缺口,并切断 `parentId` 血缘链,
532
+ * 使后续嵌套 provider 运行(如 codex-app-server)的行无法回溯到发起它们的移动端 turn。
533
+ * 旧版本 OpenClaw 没有 `event_zstd` 列,因此按实际列集合决定查询语句。
534
+ */
535
+ function readSqliteTranscriptEventRows(database, sessionId) {
536
+ const columns = database.prepare("PRAGMA table_info(transcript_events)").all();
537
+ const hasZstdColumn = columns.some((column) => column.name === "event_zstd");
538
+ const projection = hasZstdColumn ? "seq, event_json, event_zstd" : "seq, event_json";
539
+ return database.prepare(`
540
+ SELECT ${projection}
541
+ FROM transcript_events
542
+ WHERE session_id = ?
543
+ ORDER BY seq ASC
544
+ `).all(sessionId);
545
+ }
546
+ function sqliteTranscriptEventJson(row) {
547
+ if (typeof row.event_json === "string")
548
+ return row.event_json;
549
+ if (row.event_zstd instanceof Uint8Array && row.event_zstd.byteLength > 0) {
550
+ try {
551
+ return zstdDecompressSync(row.event_zstd).toString("utf8");
552
+ }
553
+ catch (error) {
554
+ console.warn("[openclaw] failed to decompress transcript event", { seq: row.seq, error: String(error) });
555
+ return undefined;
556
+ }
557
+ }
558
+ return undefined;
559
+ }
551
560
  function sqliteTranscriptHistoryMessage(row, sessionId, strictProjectionV3 = false, visibleParentIds) {
552
561
  const seq = typeof row.seq === "number" && Number.isFinite(row.seq) && row.seq > 0
553
562
  ? Math.round(row.seq)
554
563
  : undefined;
555
- if (!seq || typeof row.event_json !== "string")
564
+ const eventJson = sqliteTranscriptEventJson(row);
565
+ if (!seq || eventJson === undefined)
556
566
  return null;
557
567
  let event;
558
568
  try {
559
- event = JSON.parse(row.event_json);
569
+ event = JSON.parse(eventJson);
560
570
  }
561
571
  catch {
562
572
  return null;
@@ -596,11 +606,12 @@ function sqliteTranscriptHistoryMessage(row, sessionId, strictProjectionV3 = fal
596
606
  function resolveSqliteVisibleParentIds(rows) {
597
607
  const nodes = new Map();
598
608
  for (const row of rows) {
599
- if (typeof row.event_json !== "string")
609
+ const eventJson = sqliteTranscriptEventJson(row);
610
+ if (eventJson === undefined)
600
611
  continue;
601
612
  let event;
602
613
  try {
603
- event = JSON.parse(row.event_json);
614
+ event = JSON.parse(eventJson);
604
615
  }
605
616
  catch {
606
617
  continue;
@@ -653,7 +664,21 @@ function prepareTranscriptHistoryMessages(messages, sessionKey, projectionVersio
653
664
  const explicitSidecars = normalizeOpenClawAssistantMediaSidecars(messages, sessionKey, relationOptions).messages;
654
665
  restoreTranscriptTurnLineage(explicitSidecars, projectionVersion);
655
666
  const foldedMessages = normalizeOpenClawAutomaticMediaReplies(explicitSidecars, sessionKey, relationOptions).messages;
656
- return filterOpenClawHeartbeatArtifacts(foldedMessages);
667
+ return filterOpenClawHiddenTranscriptRows(filterOpenClawHeartbeatArtifacts(foldedMessages));
668
+ }
669
+ const OPENCLAW_HIDDEN_TRANSCRIPT_ROLES = new Set(["custom"]);
670
+ /**
671
+ * OpenClaw transcript 里 `role: custom` 的 message 行是插件/工具搜索等隐藏元数据,
672
+ * Control UI 不展示。它们必须先参与血缘回溯(parentId 链经过这些行),
673
+ * 但不能进入投影:默认把未知角色当成 assistant 会产生空的“已完成回复”行,
674
+ * 客户端会把它当作结果锚点,提前消掉 waiting 占位。
675
+ */
676
+ export function filterOpenClawHiddenTranscriptRows(messages) {
677
+ const visible = messages.filter((message) => {
678
+ const role = typeof message.role === "string" ? message.role.trim().toLowerCase() : "";
679
+ return !OPENCLAW_HIDDEN_TRANSCRIPT_ROLES.has(role);
680
+ });
681
+ return visible.length === messages.length ? messages : visible;
657
682
  }
658
683
  /**
659
684
  * The installed OpenClaw producer records a settled message-tool delivery as
@@ -767,8 +792,7 @@ function isOpenClawHeartbeatPrompt(message) {
767
792
  if (content.hasNonTextContent) {
768
793
  return false;
769
794
  }
770
- const text = content.text.replace(/\r/g, "").trim();
771
- return text === "[OpenClaw heartbeat poll]" || text === "OpenClaw heartbeat poll";
795
+ return isOpenClawHeartbeatPromptText(content.text);
772
796
  }
773
797
  function isOpenClawHeartbeatAcknowledgement(message) {
774
798
  if (message.role !== "assistant") {
@@ -778,8 +802,7 @@ function isOpenClawHeartbeatAcknowledgement(message) {
778
802
  if (content.hasNonTextContent) {
779
803
  return false;
780
804
  }
781
- const text = content.text.replace(/\r/g, "").trim();
782
- return text === "HEARTBEAT_OK" || text === "HEARTBEAT OK";
805
+ return isOpenClawHeartbeatAckText(content.text);
783
806
  }
784
807
  function isHeartbeatToolArtifact(message) {
785
808
  if (message.role === "tool" || message.role === "toolResult" || message.role === "tool_result") {
@@ -909,279 +932,6 @@ function normalizeTranscriptTurnId(value) {
909
932
  const cleaned = cleanHistoryString(value);
910
933
  return cleaned?.replace(/:(?:user|assistant|tool|system)$/i, "") || undefined;
911
934
  }
912
- function cleanHistoryString(value) {
913
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
914
- }
915
- function paginateHistoryMessages(messages, opts) {
916
- if (messages.length === 0) {
917
- return { messages: [], hasMore: false };
918
- }
919
- if (opts.direction === "newer") {
920
- const startIndex = opts.cursorSeq === undefined
921
- ? Math.max(0, messages.length - opts.limit)
922
- : firstIndexAfterSeq(messages, opts.cursorSeq);
923
- const page = messages.slice(startIndex, startIndex + opts.limit);
924
- return {
925
- messages: page,
926
- hasMore: startIndex + page.length < messages.length,
927
- ...(lastMessageSeq(page) ? { newestCursor: formatHistoryCursor(lastMessageSeq(page)) } : {}),
928
- };
929
- }
930
- const endExclusive = resolveOlderEndExclusive(messages, opts.cursorSeq);
931
- const start = Math.max(0, endExclusive - opts.limit);
932
- const page = messages.slice(start, endExclusive);
933
- const firstSeq = firstMessageSeq(page);
934
- const newestSeq = lastMessageSeq(page);
935
- return {
936
- messages: page,
937
- hasMore: start > 0,
938
- ...(start > 0 && firstSeq ? { nextCursor: formatHistoryCursor(firstSeq) } : {}),
939
- ...(newestSeq ? { newestCursor: formatHistoryCursor(newestSeq) } : {}),
940
- };
941
- }
942
- function resolveOlderEndExclusive(messages, cursorSeq) {
943
- if (cursorSeq === undefined) {
944
- return messages.length;
945
- }
946
- const newestSeq = messageSeq(messages[messages.length - 1]);
947
- if (newestSeq !== undefined && cursorSeq > newestSeq) {
948
- return messages.length;
949
- }
950
- const index = messages.findIndex((message) => {
951
- const seq = messageSeq(message);
952
- return seq !== undefined && seq >= cursorSeq;
953
- });
954
- return index === -1 ? messages.length : Math.max(0, index);
955
- }
956
- function firstIndexAfterSeq(messages, cursorSeq) {
957
- const index = messages.findIndex((message) => {
958
- const seq = messageSeq(message);
959
- return seq !== undefined && seq > cursorSeq;
960
- });
961
- return index === -1 ? messages.length : index;
962
- }
963
- function normalizeHistoryLimit(value) {
964
- const parsed = typeof value === "number" && Number.isFinite(value)
965
- ? Math.round(value)
966
- : typeof value === "string" && value.trim().length > 0
967
- ? Number.parseInt(value.trim(), 10)
968
- : DEFAULT_TRANSCRIPT_HISTORY_LIMIT;
969
- if (!Number.isFinite(parsed)) {
970
- return DEFAULT_TRANSCRIPT_HISTORY_LIMIT;
971
- }
972
- return Math.max(1, Math.min(MAX_TRANSCRIPT_HISTORY_LIMIT, parsed));
973
- }
974
- function normalizeHistoryDirection(value) {
975
- return value === "newer" ? "newer" : "older";
976
- }
977
- function requireProjectionIdentity(value, field) {
978
- if (typeof value !== "string" || value.trim().length === 0) {
979
- throw new Error(`OpenClaw projection v3 identity is missing ${field}`);
980
- }
981
- return value.trim();
982
- }
983
- function requireProjectionSequence(value, index) {
984
- if (!Number.isSafeInteger(value) || (value ?? 0) <= 0) {
985
- throw new Error(`OpenClaw projection v3 sourceOrderSeq is missing or invalid at row ${index}`);
986
- }
987
- return value;
988
- }
989
- function normalizeCursor(value) {
990
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
991
- }
992
- function parseHistoryCursorSeq(value) {
993
- const cursor = normalizeCursor(value);
994
- if (!cursor) {
995
- return undefined;
996
- }
997
- const raw = cursor.startsWith(CURSOR_PREFIX) ? cursor.slice(CURSOR_PREFIX.length) : cursor;
998
- const parsed = Number.parseInt(raw, 10);
999
- return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
1000
- }
1001
- function formatHistoryCursor(seq) {
1002
- return `${CURSOR_PREFIX}${seq}`;
1003
- }
1004
- function firstMessageSeq(messages) {
1005
- return messageSeq(messages[0]);
1006
- }
1007
- function lastMessageSeq(messages) {
1008
- return messageSeq(messages[messages.length - 1]);
1009
- }
1010
- function messageSeq(message) {
1011
- const raw = message?.seq ?? (message ? historyNumber(openClawHistoryMetadata(message) ?? {}, "seq") : undefined);
1012
- return typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? Math.round(raw) : undefined;
1013
- }
1014
- function normalizeHistoryTimestamp(value) {
1015
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1016
- return Math.round(value > 10_000_000_000 ? value : value * 1000);
1017
- }
1018
- if (typeof value === "string" && value.trim().length > 0) {
1019
- const numeric = Number(value.trim());
1020
- if (Number.isFinite(numeric) && numeric > 0) {
1021
- return Math.round(numeric > 10_000_000_000 ? numeric : numeric * 1000);
1022
- }
1023
- const parsed = Date.parse(value.trim());
1024
- if (Number.isFinite(parsed) && parsed > 0) {
1025
- return Math.round(parsed);
1026
- }
1027
- }
1028
- return undefined;
1029
- }
1030
- function normalizeHistoryCreatedAt(value) {
1031
- const timestamp = normalizeHistoryTimestamp(value);
1032
- return timestamp === undefined ? undefined : new Date(timestamp).toISOString();
1033
- }
1034
- function normalizeTimelineRole(value) {
1035
- const role = typeof value === "string" ? value.trim().toLowerCase().replace("_", "") : "";
1036
- switch (role) {
1037
- case "user":
1038
- case "assistant":
1039
- case "system":
1040
- return role;
1041
- case "tool":
1042
- case "toolresult":
1043
- return "tool";
1044
- default:
1045
- return "assistant";
1046
- }
1047
- }
1048
- function normalizeTimelineMessageState(message) {
1049
- return typeof message.errorMessage === "string" && message.errorMessage.trim().length > 0
1050
- ? "failed"
1051
- : "completed";
1052
- }
1053
- function normalizeTimelineCreatedAt(message) {
1054
- if (typeof message.createdAt === "string" && message.createdAt.trim().length > 0) {
1055
- return message.createdAt.trim();
1056
- }
1057
- const timestamp = normalizeHistoryTimestamp(message.timestamp);
1058
- return timestamp === undefined ? undefined : new Date(timestamp).toISOString();
1059
- }
1060
- function fallbackTimelineCreatedAt(messages, index) {
1061
- for (let previous = index - 1; previous >= 0; previous -= 1) {
1062
- const createdAt = normalizeTimelineCreatedAt(messages[previous]);
1063
- if (!createdAt) {
1064
- continue;
1065
- }
1066
- const ms = Date.parse(createdAt);
1067
- if (Number.isFinite(ms)) {
1068
- return new Date(ms + (index - previous)).toISOString();
1069
- }
1070
- }
1071
- for (let next = index + 1; next < messages.length; next += 1) {
1072
- const createdAt = normalizeTimelineCreatedAt(messages[next]);
1073
- if (!createdAt) {
1074
- continue;
1075
- }
1076
- const ms = Date.parse(createdAt);
1077
- if (Number.isFinite(ms)) {
1078
- return new Date(Math.max(0, ms - (next - index))).toISOString();
1079
- }
1080
- }
1081
- return new Date().toISOString();
1082
- }
1083
- function normalizeTimelineContentBlocks(value) {
1084
- if (typeof value === "string") {
1085
- const text = value.trim();
1086
- return text ? [{ type: "text", text }] : [];
1087
- }
1088
- if (!Array.isArray(value)) {
1089
- return [];
1090
- }
1091
- return value.flatMap((block) => {
1092
- if (!isRecord(block)) {
1093
- return [];
1094
- }
1095
- const type = typeof block.type === "string" && block.type.trim().length > 0 ? block.type.trim() : "text";
1096
- return [normalizeTimelineContentBlock({ ...block, type })];
1097
- });
1098
- }
1099
- function normalizeTimelineContentBlock(block) {
1100
- const type = String(block.type).trim().toLowerCase();
1101
- if (!["image", "file", "audio", "voice", "video"].includes(type)) {
1102
- return { ...block, type };
1103
- }
1104
- const fileId = historyString(block, "fileId", "file_id");
1105
- const previewUrl = historyString(block, "previewUrl", "preview_url", "thumbnailUrl", "thumbnail_url");
1106
- const downloadUrl = historyString(block, "downloadUrl", "download_url", "downloadPath", "download_path", "url");
1107
- const declaredTransferState = historyString(block, "transferState", "transfer_state", "status");
1108
- // OpenClaw transcripts can retain an attachment identity after its media
1109
- // object has disappeared. Such a block is a stable unavailable placeholder,
1110
- // not an available attachment: Relay intentionally rejects available blocks
1111
- // that cannot be previewed or downloaded.
1112
- const claimsAvailability = declaredTransferState === undefined
1113
- || ["available", "linked", "uploaded"].includes(declaredTransferState.toLowerCase());
1114
- const transferState = !previewUrl && !downloadUrl && claimsAvailability
1115
- ? "expired"
1116
- : declaredTransferState ?? "available";
1117
- const attachmentId = historyString(block, "attachmentId", "attachment_id")
1118
- ?? fileId
1119
- ?? stableAttachmentId(block);
1120
- return compactBlock({
1121
- ...block,
1122
- type: type === "voice" ? "audio" : type,
1123
- ...(attachmentId ? { attachmentId } : {}),
1124
- ...(fileId ? { fileId } : {}),
1125
- ...(previewUrl ? { previewUrl } : {}),
1126
- ...(downloadUrl ? { downloadUrl } : {}),
1127
- ...(historyString(block, "fileName", "file_name", "name", "filename") ? {
1128
- fileName: historyString(block, "fileName", "file_name", "name", "filename"),
1129
- } : {}),
1130
- ...(historyString(block, "mimeType", "mime_type", "contentType", "content_type") ? {
1131
- mimeType: historyString(block, "mimeType", "mime_type", "contentType", "content_type"),
1132
- } : {}),
1133
- ...(historyNumber(block, "byteSize", "byte_size", "sizeBytes", "size_bytes") ? {
1134
- byteSize: historyNumber(block, "byteSize", "byte_size", "sizeBytes", "size_bytes"),
1135
- } : {}),
1136
- ...(historyNumber(block, "width", "imageWidth", "image_width") ? {
1137
- width: historyNumber(block, "width", "imageWidth", "image_width"),
1138
- } : {}),
1139
- ...(historyNumber(block, "height", "imageHeight", "image_height") ? {
1140
- height: historyNumber(block, "height", "imageHeight", "image_height"),
1141
- } : {}),
1142
- transferState,
1143
- ...(!previewUrl && !downloadUrl && transferState === "expired" ? { isRemoteExpired: true } : {}),
1144
- });
1145
- }
1146
- function extractAttachmentIds(blocks) {
1147
- return blocks
1148
- .map((block) => historyString(block, "attachmentId", "attachment_id", "fileId", "file_id"))
1149
- .filter((value) => Boolean(value))
1150
- .filter((value, index, values) => values.indexOf(value) === index);
1151
- }
1152
- function stableAttachmentId(block) {
1153
- const source = [
1154
- historyString(block, "fileName", "file_name", "name", "filename"),
1155
- historyString(block, "mimeType", "mime_type", "contentType", "content_type"),
1156
- historyString(block, "downloadUrl", "download_url", "downloadPath", "download_path", "url"),
1157
- historyNumber(block, "byteSize", "byte_size", "sizeBytes", "size_bytes"),
1158
- ].filter((value) => value !== undefined).join("\u0000");
1159
- return source ? `att_${createHash("sha256").update(source).digest("hex").slice(0, 16)}` : undefined;
1160
- }
1161
- function compactBlock(block) {
1162
- return Object.fromEntries(Object.entries(block).filter(([, value]) => value !== undefined));
1163
- }
1164
- function historyString(record, ...fields) {
1165
- for (const field of fields) {
1166
- const value = record[field];
1167
- if (typeof value === "string" && value.trim().length > 0) {
1168
- return value.trim();
1169
- }
1170
- }
1171
- return undefined;
1172
- }
1173
- function historyNumber(record, ...fields) {
1174
- for (const field of fields) {
1175
- const value = record[field];
1176
- if (typeof value === "number" && Number.isFinite(value)) {
1177
- return Math.round(value);
1178
- }
1179
- }
1180
- return undefined;
1181
- }
1182
- function isRecord(value) {
1183
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1184
- }
1185
935
  export function extractHistoryOutcome(history, context, preferredText) {
1186
936
  const messages = history?.messages ?? [];
1187
937
  if (messages.length === 0) {