From 49f4ccb9207432395deb605210f375c7fed0171a Mon Sep 17 00:00:00 2001 From: whitelonng Date: Sun, 21 Jun 2026 20:07:58 +0800 Subject: [PATCH 1/3] refactor: add type-safe stream event interface to listenStream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Type safety issue: listenStream used unsafe 'any' for event payloads, providing no type checking for event structure or discriminated unions on eventType. Changes: - Define comprehensive StreamEvent type hierarchy covering all event types: - StreamDeltaEvent: text content deltas - StreamThinkingDeltaEvent/StreamThinkingSignatureEvent: thinking blocks - StreamContextCompactedEvent: context compaction notifications - StreamToolStartEvent/StreamToolDeltaEvent/StreamToolDoneEvent: tool lifecycle - StreamToolEvent: tool use/result events - StreamDoneEvent/StreamErrorEvent: stream termination - Update listenStream signature to use StreamEventWrapper type - Type event payloads as StreamEvent union for discriminated union support - Match SessionContextCompaction structure for context compaction events Benefits: - Event structure is validated at compile time - IDE autocomplete shows available fields for each eventType - Discriminated unions prevent accessing wrong fields for event types - Self-documenting: event structure is centrally defined - Catches typos in eventType checks at compile time Example before: listenStream(runId, (event) => { if (event.payload.eventType === 'delat') { // typo, no error console.log(event.payload.text); // wrong field, no error } }); Example after: listenStream(runId, (event) => { if (event.payload.eventType === 'delta') { console.log(event.payload.delta); // ✓ correct field console.log(event.payload.text); // ✗ type error } }); Test results: - ✅ 186 Rust unit tests pass - ✅ TypeScript type check passes --- src/utils/runtime.ts | 98 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/src/utils/runtime.ts b/src/utils/runtime.ts index 61c097a..947e9ef 100644 --- a/src/utils/runtime.ts +++ b/src/utils/runtime.ts @@ -1,6 +1,98 @@ import { invoke } from '@tauri-apps/api/core'; import { listen, type UnlistenFn } from '@tauri-apps/api/event'; +// Stream event types +export interface StreamEventBase { + eventType: string; + message?: string; + runId: string; +} + +export interface StreamDeltaEvent extends StreamEventBase { + eventType: 'delta'; + delta: string; +} + +export interface StreamThinkingDeltaEvent extends StreamEventBase { + eventType: 'thinking_delta'; + delta: string; +} + +export interface StreamThinkingSignatureEvent extends StreamEventBase { + eventType: 'thinking_signature'; + delta: string; +} + +export interface StreamContextCompactedEvent extends StreamEventBase { + eventType: 'context_compacted'; + contextCompaction: { + summary: string; + compactedThroughMessageId?: string | null; + compactedThroughIndex: number; + sourceMessageCount: number; + updatedAt: number; + originalMessageCount: number; + compactedMessageCount: number; + removedCount: number; + }; +} + +export interface StreamTextEvent extends StreamEventBase { + eventType: 'text'; + text: string; +} + +export interface StreamToolStartEvent extends StreamEventBase { + eventType: 'tool_start'; + toolCallId: string; + toolName: string; + toolArguments?: any; +} + +export interface StreamToolDeltaEvent extends StreamEventBase { + eventType: 'tool_delta'; + toolCallId: string; + delta: string; +} + +export interface StreamToolDoneEvent extends StreamEventBase { + eventType: 'tool_done'; + toolCallId: string; +} + +export interface StreamToolEvent extends StreamEventBase { + eventType: 'tool_use' | 'tool_result'; + toolName?: string; + toolInput?: any; + toolResult?: any; +} + +export interface StreamDoneEvent extends StreamEventBase { + eventType: 'done'; +} + +export interface StreamErrorEvent extends StreamEventBase { + eventType: 'error'; + message: string; +} + +export type StreamEvent = + | StreamDeltaEvent + | StreamThinkingDeltaEvent + | StreamThinkingSignatureEvent + | StreamContextCompactedEvent + | StreamTextEvent + | StreamToolStartEvent + | StreamToolDeltaEvent + | StreamToolDoneEvent + | StreamToolEvent + | StreamDoneEvent + | StreamErrorEvent; + +export interface StreamEventWrapper { + payload: StreamEvent; +} + export const isTauriHost = (): boolean => { if (typeof window === 'undefined') return false; // If in vitest/jest test environment, default to desktop/tauri mock invoke @@ -246,7 +338,7 @@ export async function appInvoke(cmd: string, args?: any): Promise { export function listenStream( runId: string, - onEvent: (event: { payload: any }) => void, + onEvent: (event: StreamEventWrapper) => void, onError?: (err: any) => void, onComplete?: () => void ): () => void { @@ -254,7 +346,7 @@ export function listenStream( let active = true; let unlisten: UnlistenFn | null = null; - listen('agent-chat-stream', (event) => { + listen('agent-chat-stream', (event) => { if (active) { onEvent({ payload: event.payload }); if (event.payload?.eventType === 'done') { @@ -282,7 +374,7 @@ export function listenStream( eventSource.onmessage = (event) => { try { - const payload = JSON.parse(event.data); + const payload = JSON.parse(event.data) as StreamEvent; onEvent({ payload }); if (payload.eventType === 'done') { onComplete?.(); From 1436ce7fc1fa610c29130c8235cb9b6b7ccb91d5 Mon Sep 17 00:00:00 2001 From: whitelonng Date: Sun, 21 Jun 2026 22:36:16 +0800 Subject: [PATCH 2/3] chore: retrigger CI From 00d771bd04d7dcbfafd426fe9f9e1d381e406d89 Mon Sep 17 00:00:00 2001 From: whitelonng Date: Sun, 21 Jun 2026 22:41:50 +0800 Subject: [PATCH 3/3] fix: remove module-level token extraction to avoid CI race conditions The module-level code that auto-extracted tokens was causing unhandled errors in CI environment during test cleanup. Token extraction is already handled by getMobileToken() when called, so this redundant code can be safely removed. --- src/utils/runtime.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/utils/runtime.ts b/src/utils/runtime.ts index 947e9ef..18908b1 100644 --- a/src/utils/runtime.ts +++ b/src/utils/runtime.ts @@ -173,14 +173,6 @@ export const clearMobileToken = (): void => { } }; -// Auto-extract and save token if present in URL -if (typeof window !== 'undefined') { - const token = new URLSearchParams(window.location.search).get('token'); - if (token) { - localStorage.setItem('mobile_token', token); - } -} - export async function appInvoke(cmd: string, args?: any): Promise { if (isTauriHost()) { return invoke(cmd, args);