



















@@ -429,46 +429,148 @@ describe("normalizeMessagesForLlmBoundary", () => {
429429expect(input[0]).toHaveProperty("details");
430430});
431431432-it("keeps historical runtime-context transcript entries out of the LLM boundary", () => {
432+it("keeps only pre-user current-turn runtime context at the LLM boundary", () => {
433433const input = [
434+{
435+role: "user",
436+content: [{ type: "text", text: "old ask" }],
437+timestamp: 0,
438+},
439+{
440+role: "assistant",
441+content: [{ type: "text", text: "old answer" }],
442+timestamp: 1,
443+},
434444{
435445role: "custom",
436446customType: "openclaw.runtime-context",
437-content: "old secret runtime context",
447+content: "current secret runtime context",
438448display: false,
439-timestamp: 0,
449+timestamp: 2,
440450},
441451{
442452role: "user",
443453content: [{ type: "text", text: "visible ask" }],
444-timestamp: 1,
454+timestamp: 3,
445455},
446456{
447457role: "custom",
448458customType: "openclaw.runtime-context",
449-content: "secret runtime context",
459+content: "post-user stale runtime context",
450460display: false,
451-timestamp: 2,
461+timestamp: 4,
452462},
453463{
454464role: "custom",
455465customType: "other-extension-context",
456466content: "normal custom context",
457467display: false,
458-timestamp: 3,
468+timestamp: 5,
459469},
460470];
461471462472const output = normalizeMessagesForLlmBoundary(
463473input as Parameters<typeof normalizeMessagesForLlmBoundary>[0],
464474) as unknown as Array<Record<string, unknown>>;
465475466-expect(output).toHaveLength(3);
467-expect(output.some((item) => item.content === "old secret runtime context")).toBe(false);
468-expect(output.some((item) => item.content === "secret runtime context")).toBe(true);
476+expect(output).toHaveLength(5);
477+expect(output.some((item) => item.content === "current secret runtime context")).toBe(true);
478+expect(output.some((item) => item.content === "post-user stale runtime context")).toBe(false);
469479expect(output.some((item) => item.customType === "other-extension-context")).toBe(true);
470480});
471481482+it("keeps overflow retry runtime context immediately before the active user", () => {
483+const rebuiltAfterOverflow = [
484+{
485+role: "user",
486+content: [{ type: "text", text: "old ask" }],
487+timestamp: 0,
488+},
489+{
490+role: "assistant",
491+content: [{ type: "text", text: "old answer" }],
492+timestamp: 1,
493+},
494+{
495+role: "user",
496+content: [{ type: "text", text: "retry ask" }],
497+timestamp: 2,
498+},
499+];
500+const runtimeContext = {
501+role: "custom",
502+customType: "openclaw.runtime-context",
503+content: "retry runtime context",
504+display: false,
505+timestamp: 3,
506+};
507+508+const retryMessages = attemptTesting.insertRuntimeContextMessageForPrompt({
509+message: runtimeContext as Parameters<
510+typeof attemptTesting.insertRuntimeContextMessageForPrompt
511+>[0]["message"],
512+messages: rebuiltAfterOverflow as Parameters<typeof normalizeMessagesForLlmBoundary>[0],
513+});
514+const retryInput = normalizeMessagesForLlmBoundary(retryMessages) as unknown as Array<
515+Record<string, unknown>
516+>;
517+518+expect(retryInput.map((message) => message.role)).toEqual([
519+"user",
520+"assistant",
521+"custom",
522+"user",
523+]);
524+expect(retryInput[2]).toMatchObject({
525+customType: "openclaw.runtime-context",
526+content: "retry runtime context",
527+});
528+expect(retryInput[3]?.content).toEqual([{ type: "text", text: "retry ask" }]);
529+});
530+531+it("keeps prompt-local runtime context before the active user in existing sessions", () => {
532+const promptInput = [
533+{
534+role: "user",
535+content: [{ type: "text", text: "old ask" }],
536+timestamp: 0,
537+},
538+{
539+role: "assistant",
540+content: [{ type: "text", text: "old answer" }],
541+timestamp: 1,
542+},
543+{
544+role: "custom",
545+customType: "openclaw.runtime-context",
546+content: "current runtime context",
547+display: false,
548+timestamp: 2,
549+},
550+{
551+role: "user",
552+content: [{ type: "text", text: "visible ask" }],
553+timestamp: 3,
554+},
555+];
556+557+const modelInput = normalizeMessagesForLlmBoundary(
558+promptInput as Parameters<typeof normalizeMessagesForLlmBoundary>[0],
559+) as unknown as Array<Record<string, unknown>>;
560+561+expect(modelInput.map((message) => message.role)).toEqual([
562+"user",
563+"assistant",
564+"custom",
565+"user",
566+]);
567+expect(modelInput[2]).toMatchObject({
568+customType: "openclaw.runtime-context",
569+content: "current runtime context",
570+});
571+expect(modelInput[3]?.content).toEqual([{ type: "text", text: "visible ask" }]);
572+});
573+472574it("keeps only safe blocked metadata at the LLM boundary", () => {
473575const input = [
474576{
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。