How the Maka Desktop Build System Streams Sessions, Displays Tool Timelines, and Supports Branching/Recovery

The Apache Maka Desktop client streams a live SSE transcript from the Runtime Host, materializes it into a flat timeline of tool activities and reasoning steps, folds consecutive processing spans into collapsible UI blocks, and surfaces branching controls alongside recovery actions for aborted turns.

The Maka Desktop build system combines an Electron-based shell with a React UI layer to deliver real-time AI coding assistance. According to the Apache Maka source code, the architecture relies on a stream-driven pipeline that transforms raw Runtime Host events into render-friendly views while preserving every tool invocation, error state, and branch point.

Streaming the Session Transcript from Runtime Host

The foundation of the Desktop client is a Server-Sent Events (SSE) stream (text/event-stream) emitted by the Runtime Host. The UI consumes this stream in packages/ui/src/tool-output-stream.ts, which handles ToolOutputChunk objects to build live tool output.

The stream processor performs three critical tasks:

  1. Dedupes and orders chunks by the seq field to handle out-of-order network packets.
  2. Redacts secrets and enforces size caps to prevent token leakage.
  3. Appends chunks to the corresponding ToolActivityItem.outputChunks array for immediate display.
// packages/ui/src/tool-output-stream.ts
export interface ToolOutputChunk {
  seq: number;
  stream: "stdout" | "stderr";
  text: string;
  redacted: boolean;
  createdAt: number;
}

The Live Turn Projection (live-turn-projection.ts) constructs a temporary TurnViewModel for the currently streaming turn. This transient model updates the UI in real time before merging into the persisted transcript once the turn settles, ensuring users see tool output the moment it arrives.

Building the Flat Tool Timeline

Once a turn completes, packages/ui/src/materialize.ts transforms stored messages into UI-ready objects. The materializeChat() function maps user, assistant, and system messages into ChatItem arrays, while materializeTools() extracts every tool activity with status indicators, argument previews, and accumulated output chunks.

The result is a flat timeline represented as an ordered array of TurnTimelineItem objects. Each item carries a kind discriminator ("thinking", "tools", "text", etc.) that preserves the exact chronological order of reasoning steps and tool invocations.

// packages/ui/src/materialize.ts
export function materializeChat(
  messages: readonly StoredMessage[], 
  locale: UiLocale = "en"
): ChatItem[] {
  // Maps stored messages into UI objects with tool activities attached
}

Folding the Timeline for Clean Rendering

The render layer never mutates the flat model directly. Instead, it derives a folded view just before rendering via packages/ui/src/timeline-fold.ts. The foldTimeline() function groups consecutive "thinking" and "tools" entries into a single ProcessingFold block, preventing visual clutter during long tool runs.

// packages/ui/src/timeline-fold.ts
export function foldTimeline(
  items: readonly TurnTimelineItem[]
): FoldedTimelineEntry[] {
  const out: FoldedTimelineEntry[] = [];
  let buffer: FoldedTimelineChild[] | null = null;
  
  const flush = (): void => {
    if (buffer && buffer.length > 0) {
      if (buffer.some((child) => child.kind === 'tools')) {
        out.push({ kind: 'processing', id: 'start', children: buffer });
      } else {
        out.push(...buffer);
      }
    }
    buffer = null;
  };
  
  // Iterate, grouping thinking+tools into ProcessingFold
  flush();
  return out;
}

This folding strategy collapses complex multi-step reasoning into a single disclosure unit ("Processing") while preserving the original order as nested children. Pure reasoning runs remain unwrapped, allowing "深度思考" (deep thinking) disclosures to render independently. The folded timeline is memoized per turn in chat-turn.tsx to prevent unnecessary re-renders.

Displaying Live Tool Activity

Inside packages/ui/src/chat-turn.tsx, the ChatTurn component splits the folded timeline at user message boundaries and renders each segment according to type:

  • Thinking entries render as assistant message bubbles.
  • Tool entries render as grouped cards showing status icons, progress spinners, and live stdout/stderr streams pulled from ToolActivityItem.outputChunks.

The liveStreaming prop adds a global spinner that covers the entire turn while tools execute. This prevents the UI from displaying an empty "thinking" bubble that would appear abandoned during the initial tool setup phase.

// packages/ui/src/chat-turn.tsx
<ChatTurn
  turn={liveTurn}
  liveStreaming={{
    onStreamingSettled: () => console.log('settled'),
    runningStatus: true,
  }}
/>

Branching and Recovery Mechanisms

Session Branch Context

When a user edits a previous turn, the system creates a new branch session tracked by the SessionContextBranch interface in session-context-layer.tsx:

// packages/ui/src/session-context-layer.tsx
export interface SessionContextBranch {
  parentSessionId: string;
  parentSessionName: string;
  fromAbortedTurn?: boolean;
}

The UI renders a banner showing the parent session name and, when fromAbortedTurn is true, displays a localized "从中断前分支" (branch from interruption) label. Clicking the branch button on the active streaming tail creates a new session that copies the original transcript up to the selected turn, allowing users to pivot their workflow without losing prior context.

Recovery from Aborted Turns

When a turn fails due to tool errors or timeouts, the UI preserves the partial timeline (including any truncated output) and surfaces recovery actions:

  • Regenerate – Re-run the turn with identical inputs.
  • Branch – Start a new session from the failure point.

These actions are exposed via the onSwitchToBypassAndRetry callback in ChatTurn. The aborted-turn UI remains visible even when the provider produced no assistant event (see the handling around lines 61-64 in chat-turn.tsx), ensuring users can always recover from failures.

// In packages/ui/src/chat-turn.tsx
{turn.status === 'failed' && (
  <UiButton
    variant="ghost"
    size="sm"
    onClick={() => props.onSwitchToBypassAndRetry?.(turn.turnId)}
    label="Branch from failure"
  />
)}

Summary

  • Stream-driven architecture: The Desktop build consumes SSE events from the Runtime Host via tool-output-stream.ts, deduplicating and redacting chunks in real time.
  • Flat timeline model: materialize.ts converts stored messages into TurnTimelineItem arrays that preserve exact tool invocation order.
  • UI folding: timeline-fold.ts derives ProcessingFold blocks to collapse lengthy reasoning sequences without losing structural data.
  • Live rendering: ChatTurn displays tool cards with progress indicators and enforces a global spinner during active streaming to prevent UI abandonment.
  • Branching support: SessionContextBranch tracks parent relationships, while the UI offers explicit branch/regenerate controls for aborted turns via onSwitchToBypassAndRetry.

Frequently Asked Questions

How does Maka handle out-of-order chunks during session streaming?

The tool-output-stream.ts module sequences every ToolOutputChunk by its seq field before appending to ToolActivityItem.outputChunks. This guarantees that even if network jitter delivers stderr before stdout, the UI renders tool output in the exact order produced by the Runtime Host.

What is the difference between the flat timeline and the folded timeline?

The flat timeline is the canonical data model produced by materialize.ts, containing every thinking step and tool invocation as discrete TurnTimelineItem objects. The folded timeline is a derived, render-time optimization created by foldTimeline() that groups consecutive processing items into a single ProcessingFold block to reduce visual noise.

Can users recover a session if a tool invocation crashes mid-stream?

Yes. When a turn status becomes 'failed', ChatTurn preserves the partial timeline—including any output chunks received before the crash—and exposes the onSwitchToBypassAndRetry callback. Users can either regenerate the failed turn or branch into a new session that forks from the exact point of failure.

Where is the Desktop build configuration stored?

The Electron packaging configuration lives in apps/desktop/electron-builder.config.mjs. This file defines how the Runtime Host and @maka/ui package are bundled into the final Desktop application that ships to end users.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →