fix(agent): address compaction feedback review findings

- Wrap maybeCompact() in try/catch to ensure compaction_end always fires
- Widen multicaListeners type to match subscribeAll() callback signature
- Import CompactionEndEvent from SDK instead of inline type casts
- Add doc comment explaining reason field type difference (agent vs SDK)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yushen
2026-02-05 15:27:08 +08:00
parent 1316d329ee
commit 8f9dcbf7e5
4 changed files with 34 additions and 16 deletions

View File

@@ -6,7 +6,7 @@
*/
import { useState, useEffect, useCallback, useRef } from 'react'
import { useMessagesStore } from '@multica/store'
import type { ContentBlock } from '@multica/sdk'
import type { ContentBlock, CompactionEndEvent } from '@multica/sdk'
interface UseLocalChatOptions {
agentId: string
@@ -86,7 +86,7 @@ export function useLocalChat({ agentId }: UseLocalChatOptions): UseLocalChatRetu
return
}
if (agentEvent.type === 'compaction_end') {
const evt = agentEvent as { removed: number; kept: number; tokensRemoved?: number; tokensKept?: number; reason: string }
const evt = agentEvent as CompactionEndEvent
store.endCompaction({
removed: evt.removed,
kept: evt.kept,

View File

@@ -22,6 +22,7 @@ import {
type ConnectionState,
type StreamPayload,
type AgentEvent,
type CompactionEndEvent,
type GetAgentMessagesResult,
type ContentBlock,
} from "@multica/sdk"
@@ -148,7 +149,7 @@ function createClient(
break
}
case "compaction_end": {
const evt = event as { removed: number; kept: number; tokensRemoved?: number; tokensKept?: number; reason: string }
const evt = event as CompactionEndEvent
store.endCompaction({
removed: evt.removed,
kept: evt.kept,

View File

@@ -10,7 +10,13 @@ export type CompactionStartEvent = {
type: "compaction_start";
};
/** Emitted when context compaction completes */
/**
* Emitted when context compaction completes.
*
* Note: `reason` uses a narrow union here for type safety within the agent.
* The SDK's `CompactionEndEvent` uses `string` to allow future extensions
* without requiring SDK version bumps.
*/
export type CompactionEndEvent = {
type: "compaction_end";
removed: number;

View File

@@ -85,7 +85,8 @@ export class Agent {
private initialized = false;
// MulticaEvent subscribers (parallel to PiAgentCore's subscriber list)
private multicaListeners: Array<(event: MulticaEvent) => void> = [];
// Typed as AgentEvent | MulticaEvent to match subscribeAll() callback signature
private multicaListeners: Array<(event: AgentEvent | MulticaEvent) => void> = [];
// Auth profile rotation state
private resolvedProvider: string;
@@ -477,18 +478,28 @@ export class Agent {
if (!this.session.needsCompaction(messages)) return;
this.emitMulticaEvent({ type: "compaction_start" });
const result = await this.session.maybeCompact(messages);
if (result?.kept) {
this.agent.replaceMessages(result.kept);
try {
const result = await this.session.maybeCompact(messages);
if (result?.kept) {
this.agent.replaceMessages(result.kept);
}
this.emitMulticaEvent({
type: "compaction_end",
removed: result?.removedCount ?? 0,
kept: result?.kept.length ?? messages.length,
tokensRemoved: result?.tokensRemoved,
tokensKept: result?.tokensKept,
reason: result?.reason ?? "tokens",
});
} catch (err) {
this.emitMulticaEvent({
type: "compaction_end",
removed: 0,
kept: messages.length,
reason: "tokens",
});
throw err;
}
this.emitMulticaEvent({
type: "compaction_end",
removed: result?.removedCount ?? 0,
kept: result?.kept.length ?? messages.length,
tokensRemoved: result?.tokensRemoved,
tokensKept: result?.tokensKept,
reason: result?.reason ?? "tokens",
});
}
/**