diff --git a/apps/desktop/src/hooks/use-local-chat.ts b/apps/desktop/src/hooks/use-local-chat.ts index 250858b82c..3150917ea6 100644 --- a/apps/desktop/src/hooks/use-local-chat.ts +++ b/apps/desktop/src/hooks/use-local-chat.ts @@ -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, diff --git a/packages/store/src/connection-store.ts b/packages/store/src/connection-store.ts index 5ede536a11..bd6893f1fd 100644 --- a/packages/store/src/connection-store.ts +++ b/packages/store/src/connection-store.ts @@ -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, diff --git a/src/agent/events.ts b/src/agent/events.ts index e0429d5a30..6177b9127b 100644 --- a/src/agent/events.ts +++ b/src/agent/events.ts @@ -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; diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 261f315f81..a6a1d951b4 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -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", - }); } /**