Files
kandev/apps/web/lib/ws
João Salavisa a1013342a3 feat: Real-time Git Status Integration (#8)
* feat: implement real-time git status integration

- Add workspace tracker to monitor git changes every 2 seconds
- Implement WebSocket streaming for git status updates
- Add database persistence for git status in agent session metadata
- Compute diffs backend-side with additions/deletions per file
- Integrate git status into frontend UI (top bar, files panel, changes panel)
- Display real-time ahead/behind commits and line changes
- Show file diffs with syntax highlighting in task view
- Add retry logic for WebSocket connections during reconciliation
- Fix context lifecycle issue in workspace tracker

* refactor: remove debug and unnecessary logging

- Remove debug logs from workspace tracker
- Remove info logs from WebSocket handlers
- Remove verbose git status update logs
- Keep only essential error logs

* fix: compare git diff against remote branch instead of HEAD

- Changed git diff comparison from HEAD to remote tracking branch
- Now shows all changes including committed files that haven't been pushed
- Files committed locally but not pushed will appear in the diff
- Falls back to HEAD if no remote branch is configured

* fix: prevent stale git status when switching tasks

- Clear git status when switching to a new task
- Only accept git.status WebSocket updates for the current task
- Prevents showing git data from previous task when switching

* refactor: remove unused diff stream client code

- Remove diffConn field from agentctl Client struct
- Remove StreamDiff() method (never called)
- Remove CloseDiffStream() method
- Remove DiffUpdate type from client.go
- All diff data is already included in GitStatusUpdate.Files
- Keep server-side diff endpoint in agentctl for potential future use

* refactor: remove unused diff stream server-side code

- Remove /workspace/diff/stream endpoint from agentctl API
- Remove handleDiffStreamWS handler
- Remove DiffUpdate type from workspace_tracker.go
- Remove DiffSubscriber type
- Remove updateDiff() and getDiff() methods
- Remove SubscribeDiff(), UnsubscribeDiff() methods
- Remove notifyDiffSubscribers() method
- Remove GetCurrentDiff() method
- Remove currentDiff field and diffSubscribers map
- All diff data is already included in GitStatusUpdate.Files

* refactor: remove unused output stream code

- Remove /output and /output/stream endpoints from agentctl API
- Remove handleGetOutput and handleOutputStreamWS handlers
- Delete output.go file entirely
- Remove OutputBuffer and related code (buffer.go deleted)
- Remove outputConn field from agentctl Client
- Remove StreamOutput() and CloseOutputStream() methods
- Remove OutputLine type from client.go
- Remove OutputBufferSize config option
- Update readStderr to just log instead of buffering

Output data flows through ACP stream (StreamUpdates) which includes
all session notifications. The separate output stream was never used.
2026-01-12 23:44:29 +00:00
..
2026-01-11 12:55:50 +00:00

WebSocket Client with Auto-Reconnect

This directory contains the WebSocket client implementation with automatic reconnection capabilities.

Features

1. Automatic Reconnection

  • Exponential Backoff: Reconnection delays increase exponentially to avoid overwhelming the server
  • Configurable Retry Limits: Set maximum number of reconnection attempts
  • Graceful Degradation: Properly handles connection failures and notifies the UI

2. Connection State Management

The client tracks the following connection states:

  • idle: Initial state before any connection attempt
  • connecting: Actively establishing a connection
  • open: Successfully connected and ready to send/receive messages
  • reconnecting: Attempting to reconnect after a disconnect
  • closed: Connection closed (intentionally or after max retries)
  • error: Connection failed and won't retry

3. Message Queue

  • Messages sent while disconnected are queued
  • Queued messages are automatically sent when connection is restored
  • Prevents message loss during brief disconnections

4. Subscription Management

  • Tracks task subscriptions across reconnections
  • Automatically re-subscribes to all tasks when reconnected
  • Provides subscribe() and unsubscribe() methods for easy subscription management

5. Pending Request Handling

  • Properly cleans up pending requests on disconnect
  • Rejects pending promises with appropriate error messages
  • Prevents memory leaks from abandoned requests

Configuration

Reconnect Options

interface ReconnectOptions {
  enabled?: boolean;           // Enable/disable auto-reconnect (default: true)
  maxAttempts?: number;        // Maximum reconnection attempts (default: 10)
  initialDelay?: number;       // Initial delay in ms (default: 1000)
  maxDelay?: number;           // Maximum delay in ms (default: 30000)
  backoffMultiplier?: number;  // Exponential backoff multiplier (default: 1.5)
}

Default Configuration

const DEFAULT_RECONNECT_OPTIONS = {
  enabled: true,
  maxAttempts: 10,
  initialDelay: 1000,      // 1 second
  maxDelay: 30000,         // 30 seconds
  backoffMultiplier: 1.5,
};

Usage

Basic Usage

import { WebSocketClient } from '@/lib/ws/client';

const client = new WebSocketClient(
  'ws://localhost:8080/ws',
  (status) => {
    console.log('Connection status:', status);
  }
);

client.connect();

Custom Reconnect Configuration

const client = new WebSocketClient(
  'ws://localhost:8080/ws',
  (status) => {
    console.log('Connection status:', status);
  },
  {
    enabled: true,
    maxAttempts: 5,
    initialDelay: 2000,
    maxDelay: 60000,
    backoffMultiplier: 2,
  }
);

Subscribing to Tasks

// Subscribe to task updates
client.subscribe('task-id-123');

// Unsubscribe when done
client.unsubscribe('task-id-123');

Sending Messages

// Send a message (queued if not connected)
client.send({ type: 'request', action: 'some.action', payload: {} });

// Send a request and wait for response
const response = await client.request('task.get', { id: 'task-123' });

Listening to Events

const unsubscribe = client.on('task.updated', (message) => {
  console.log('Task updated:', message.payload);
});

// Clean up when done
unsubscribe();

Reconnection Behavior

Exponential Backoff Example

With default settings:

  • Attempt 1: 1 second delay
  • Attempt 2: 1.5 seconds delay
  • Attempt 3: 2.25 seconds delay
  • Attempt 4: 3.375 seconds delay
  • ...
  • Attempt 10: 30 seconds delay (capped at maxDelay)

What Happens on Disconnect

  1. Connection closes (network issue, server restart, etc.)
  2. Client sets status to closed
  3. If not an intentional disconnect and reconnect is enabled:
    • Calculate delay using exponential backoff
    • Set status to reconnecting
    • Schedule reconnection attempt
  4. On successful reconnection:
    • Reset reconnect attempt counter
    • Set status to open
    • Flush queued messages
    • Re-subscribe to all tasks
  5. If max attempts reached:
    • Set status to error
    • Clean up pending requests
    • Stop reconnection attempts

Integration with React

The useWebSocket hook automatically sets up the client with reconnection:

import { useWebSocket } from '@/lib/ws/use-websocket';

export function MyComponent() {
  const store = useAppStoreApi();
  useWebSocket(store, 'ws://localhost:8080/ws');
  
  // Connection status is automatically synced to store
  const status = useAppStore((s) => s.connection.status);
  
  return <div>Status: {status}</div>;
}

Testing

To test reconnection behavior:

  1. Start the application
  2. Open browser DevTools → Network tab
  3. Observe WebSocket connection
  4. Stop the backend server
  5. Watch the client attempt to reconnect with increasing delays
  6. Restart the backend server
  7. Verify the client reconnects and re-subscribes to tasks

Best Practices

  1. Always use the provided methods: Use subscribe()/unsubscribe() instead of sending raw messages
  2. Handle connection states in UI: Show appropriate feedback for connecting, reconnecting, and error states
  3. Clean up subscriptions: Always unsubscribe when components unmount
  4. Configure appropriately: Adjust reconnect settings based on your use case
  5. Monitor console logs: Reconnection attempts are logged for debugging