

























@@ -13,6 +13,7 @@ import {
1313buildSystemRunApprovalEnvBinding,
1414} from "../../infra/system-run-approval-binding.js";
1515import { resetLogger, setLoggerOverride } from "../../logging.js";
16+import { asOptionalRecord } from "../../shared/record-coerce.js";
1617import { projectRecentChatDisplayMessages } from "../chat-display-projection.js";
1718import { ExecApprovalManager } from "../exec-approval-manager.js";
1819import { validateExecApprovalRequestParams } from "../protocol/index.js";
@@ -22,6 +23,7 @@ import { normalizeRpcAttachmentsToChatAttachments } from "./attachment-normalize
2223import {
2324DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS,
2425augmentChatHistoryWithCanvasBlocks,
26+dropPreSessionStartAnnouncePairs,
2527resolveEffectiveChatHistoryMaxChars,
2628sanitizeChatHistoryMessages,
2729sanitizeChatSendMessageInput,
@@ -1069,6 +1071,203 @@ describe("projectRecentChatDisplayMessages", () => {
10691071});
10701072});
107110731074+describe("dropPreSessionStartAnnouncePairs (#85648)", () => {
1075+const announceProvenance = {
1076+kind: "inter_session",
1077+sourceSessionKey: "agent:main:subagent:child",
1078+sourceChannel: "internal",
1079+sourceTool: "subagent_announce",
1080+};
1081+const cutoff = 1_700_000_000_000;
1082+1083+it("drops a pre-cutoff announce user message together with its adjacent assistant reply", () => {
1084+const messages = [
1085+{
1086+role: "user",
1087+content: [{ type: "text", text: "real prior" }],
1088+__openclaw: { seq: 1, recordTimestampMs: cutoff - 86_400_000 },
1089+},
1090+{
1091+role: "assistant",
1092+content: [{ type: "text", text: "real reply" }],
1093+__openclaw: { seq: 2, recordTimestampMs: cutoff - 86_400_000 },
1094+},
1095+{
1096+role: "user",
1097+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1098+provenance: announceProvenance,
1099+__openclaw: { seq: 3, recordTimestampMs: cutoff - 1_000 },
1100+},
1101+{
1102+role: "assistant",
1103+content: [{ type: "text", text: "fanfic lore-bible summary" }],
1104+__openclaw: { seq: 4, recordTimestampMs: cutoff - 1_000 },
1105+},
1106+{
1107+role: "user",
1108+content: [{ type: "text", text: "fresh user turn" }],
1109+__openclaw: { seq: 5, recordTimestampMs: cutoff + 5_000 },
1110+},
1111+];
1112+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1113+expect(out.map((m) => asOptionalRecord(asOptionalRecord(m)?.["__openclaw"])?.["seq"])).toEqual([
1114+1, 2, 5,
1115+]);
1116+});
1117+1118+it("drops imported CLI-shaped announce pairs using timestamp and text fallback", () => {
1119+const messages = [
1120+{
1121+role: "user",
1122+content: [
1123+"[Inter-session message] sourceSession=agent:main:subagent:child sourceChannel=internal sourceTool=subagent_announce",
1124+"This content was routed by OpenClaw from another session or internal tool.",
1125+].join("\n"),
1126+timestamp: cutoff - 1_000,
1127+},
1128+{
1129+role: "assistant",
1130+content: [{ type: "text", text: "stale imported assistant reply" }],
1131+timestamp: cutoff - 500,
1132+},
1133+{
1134+role: "user",
1135+content: "fresh imported turn",
1136+timestamp: cutoff + 1_000,
1137+},
1138+];
1139+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1140+expect(out).toEqual([messages[2]]);
1141+});
1142+1143+it("keeps a mid-session announce pair whose timestamp is at or after the cutoff", () => {
1144+const messages = [
1145+{
1146+role: "user",
1147+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1148+provenance: announceProvenance,
1149+__openclaw: { seq: 1, recordTimestampMs: cutoff + 1_000 },
1150+},
1151+{
1152+role: "assistant",
1153+content: [{ type: "text", text: "current-session reply" }],
1154+__openclaw: { seq: 2, recordTimestampMs: cutoff + 2_000 },
1155+},
1156+];
1157+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1158+expect(out).toEqual(messages);
1159+});
1160+1161+it("keeps an adjacent assistant reply when only the announce user predates the cutoff", () => {
1162+const messages = [
1163+{
1164+role: "user",
1165+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1166+provenance: announceProvenance,
1167+__openclaw: { seq: 1, recordTimestampMs: cutoff - 1_000 },
1168+},
1169+{
1170+role: "assistant",
1171+content: [{ type: "text", text: "fresh-session reply" }],
1172+__openclaw: { seq: 2, recordTimestampMs: cutoff + 1_000 },
1173+},
1174+];
1175+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1176+expect(out).toEqual([messages[1]]);
1177+});
1178+1179+it("keeps an adjacent assistant reply when its record timestamp is missing", () => {
1180+const messages = [
1181+{
1182+role: "user",
1183+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1184+provenance: announceProvenance,
1185+__openclaw: { seq: 1, recordTimestampMs: cutoff - 1_000 },
1186+},
1187+{
1188+role: "assistant",
1189+content: [{ type: "text", text: "timestampless reply" }],
1190+__openclaw: { seq: 2 },
1191+},
1192+];
1193+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1194+expect(out).toEqual([messages[1]]);
1195+});
1196+1197+it("returns the input unchanged when sessionStartedAt is undefined", () => {
1198+const messages = [
1199+{
1200+role: "user",
1201+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1202+provenance: announceProvenance,
1203+__openclaw: { seq: 1, recordTimestampMs: cutoff - 1_000 },
1204+},
1205+{
1206+role: "assistant",
1207+content: [{ type: "text", text: "would-be-stripped reply" }],
1208+__openclaw: { seq: 2, recordTimestampMs: cutoff - 1_000 },
1209+},
1210+];
1211+const out = dropPreSessionStartAnnouncePairs(messages, undefined);
1212+expect(out).toBe(messages);
1213+});
1214+1215+it("drops a trailing pre-cutoff announce user message even with no assistant reply", () => {
1216+const messages = [
1217+{
1218+role: "user",
1219+content: [{ type: "text", text: "real prior" }],
1220+__openclaw: { seq: 1, recordTimestampMs: cutoff + 1_000 },
1221+},
1222+{
1223+role: "user",
1224+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1225+provenance: announceProvenance,
1226+__openclaw: { seq: 2, recordTimestampMs: cutoff - 1_000 },
1227+},
1228+];
1229+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1230+expect(out.map((m) => asOptionalRecord(asOptionalRecord(m)?.["__openclaw"])?.["seq"])).toEqual([
1231+1,
1232+]);
1233+});
1234+1235+it("does not drop a normal pre-cutoff user message that is not a subagent_announce", () => {
1236+const messages = [
1237+{
1238+role: "user",
1239+content: [{ type: "text", text: "older user turn" }],
1240+__openclaw: { seq: 1, recordTimestampMs: cutoff - 1_000 },
1241+},
1242+{
1243+role: "assistant",
1244+content: [{ type: "text", text: "older reply" }],
1245+__openclaw: { seq: 2, recordTimestampMs: cutoff - 1_000 },
1246+},
1247+];
1248+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1249+expect(out).toEqual(messages);
1250+});
1251+1252+it("does not drop a pre-cutoff announce when its record timestamp is missing", () => {
1253+const messages = [
1254+{
1255+role: "user",
1256+content: [{ type: "text", text: "[Inter-session message] sourceTool=subagent_announce" }],
1257+provenance: announceProvenance,
1258+__openclaw: { seq: 1 },
1259+},
1260+{
1261+role: "assistant",
1262+content: [{ type: "text", text: "reply" }],
1263+__openclaw: { seq: 2 },
1264+},
1265+];
1266+const out = dropPreSessionStartAnnouncePairs(messages, cutoff);
1267+expect(out).toEqual(messages);
1268+});
1269+});
1270+10721271describe("resolveEffectiveChatHistoryMaxChars", () => {
10731272it("uses gateway.webchat.chatHistoryMaxChars when RPC maxChars is absent", () => {
10741273expect(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。