# Extension IPC Architecture

Extensions run in **separate processes** as the same user as Ulauncher (not sandboxed). They communicate with Ulauncher via Unix socket pairs using a JSON-line protocol.

## Architecture Diagram

```
┌─────────────────────────┐                           ┌──────────────────────────┐
│   Ulauncher Process     │                           │   Extension Process      │
│                         │                           │                          │
│  ExtensionRuntime       │    Unix Socket Pair       │  Client                  │
│  ↓                      │ ←──────────────────────→  │  ↓                       │
│  SocketMsgController    │   (JSON line protocol)    │  SocketMsgController     │
│                         │                           │  ↓                       │
│                         │                           │  Extension (user code)   │
└─────────────────────────┘                           └──────────────────────────┘
```

## Key Components

**Ulauncher side:**

- `ExtensionRuntime` (`modes/extensions/extension_runtime.py`) - Manages extension process lifecycle
- `ExtensionRecord` (`modes/extensions/extension_record.py`) - A process-agnostic handle on an installed extension's files, state and preferences
- `ExtensionService` (`modes/extensions/extension_service.py`) - Owns the running extensions and starts, stops and messages their processes (app process only), taking an `ExtensionRecord`
- `SocketMsgController` (`utils/socket_msg_controller.py`) - JSON message protocol handler

**Extension side:**

- `Client` (`api/client/Client.py`) - Extension IPC client
- `Extension` (`api/extension.py`) - Base class for extension implementations

## IPC Details

**Socket creation:**

- `socket.socketpair()` creates connected Unix domain sockets
- Parent (Ulauncher) keeps one socket, spawns child (extension) with the other
- Child socket FD passed via environment variable

**Message protocol:**

- JSON objects separated by newlines (`\n`)
- Each message is a complete JSON object on a single line
- Bidirectional communication (both processes send/receive)

**Async handling:**

- `SocketMsgController` uses GLib's async I/O streams (non-blocking)
- Messages dispatched to registered handlers by message type

## Message Flow Example

1. User types extension keyword → Ulauncher sends query via socket
2. Extension processes query → sends result items back via socket
3. User selects result → Ulauncher sends action event via socket
4. Extension handles action → may send new results back

All communication is asynchronous using GLib's event loop.

## Streaming Results

A handler that returns a list sends a single response. A handler that *yields* streams its
results as several `response` messages sharing one request id, each carrying an `append`
flag and a `final` flag. The type of each yielded value sets the `append` flag:

- `yield result` (a single Result) -> appends it to the current list (`append: true`)
- `yield [results]` (a list) -> replaces the current list (`append: false`)

Only a yielded list triggers the replace; a single result always appends.

Only the last batch is flagged `final`; Ulauncher keeps the request's callback alive until then.
Stale batches (a newer query has superseded this one) are dropped on both ends.
