Merge pull request #136 from multica-ai/fix/agent-compaction

fix(agent): prevent context window overflow with 3-layer compaction defense
This commit is contained in:
LinYushen 2026-02-12 17:23:12 +08:00 committed by GitHub
commit 8bc36a9cc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 263 additions and 46 deletions

View file

@ -0,0 +1,45 @@
"use client"
import { memo } from "react"
import { Scissors } from "lucide-react"
import type { Message } from "@multica/store"
function formatTokens(n: number): string {
if (n >= 1000) return `~${(n / 1000).toFixed(1)}k`
return `${n}`
}
interface CompactionItemProps {
message: Message
}
export const CompactionItem = memo(function CompactionItem({ message }: CompactionItemProps) {
const info = message.compaction
if (!info) return null
const label = info.reason === "summary" ? "Context summarized" : "Context compacted"
const removed = `${info.removed} messages removed`
const tokens = info.tokensRemoved != null
? `, ${formatTokens(info.tokensRemoved)} tokens freed`
: ""
return (
<div className="py-0.5 px-2.5 text-sm text-muted-foreground">
<div className="flex items-center gap-1.5 px-2.5 py-1">
{/* Status dot */}
<span className="size-1.5 rounded-full shrink-0 bg-muted-foreground/40" />
{/* Icon */}
<Scissors className="size-3.5 shrink-0" />
{/* Label */}
<span className="font-medium shrink-0">{label}</span>
{/* Stats */}
<span className="ml-auto text-xs text-muted-foreground/60 shrink-0">
{removed}{tokens}
</span>
</div>
</div>
)
})

View file

@ -5,6 +5,7 @@ import { MemoizedMarkdown } from "@multica/ui/components/markdown";
import { StreamingMarkdown } from "@multica/ui/components/markdown/StreamingMarkdown";
import { ToolCallItem } from "@multica/ui/components/tool-call-item";
import { ThinkingItem } from "@multica/ui/components/thinking-item";
import { CompactionItem } from "@multica/ui/components/compaction-item";
import { cn, getTextContent } from "@multica/ui/lib/utils";
import type { Message } from "@multica/store";
import type { ContentBlock, ToolCall, ThinkingContent } from "@multica/sdk";
@ -78,6 +79,11 @@ export const MessageList = memo(function MessageList({ messages, streamingIds }:
return (
<div className="relative p-6 px-4 sm:px-10 max-w-4xl mx-auto">
{messages.map((msg) => {
// System messages (e.g. compaction notifications)
if (msg.role === "system") {
return <CompactionItem key={msg.id} message={msg} />
}
// ToolResult messages → render as tool execution item
if (msg.role === "toolResult") {
return <ToolCallItem key={msg.id} message={msg} />