Skip to content

Latest commit

 

History

History
181 lines (150 loc) · 8.97 KB

File metadata and controls

181 lines (150 loc) · 8.97 KB

Architecture

System boundary

Omarchy / Quickshell
  BarWidget.qml ─┐
  Panel + views ─┼─ persistent stdin/stdout NDJSON (protocol v1)
  Service.qml ───┘
                         │
                  t3-mini-bridge
                  ├─ native Clerk auth + Secret Service
                  ├─ custom-scheme → loopback callback handoff
                  ├─ relay + persistent DPoP key
                  ├─ remote environment authorization
                  ├─ upstream Effect RPC session
                  └─ upstream shell/thread reducers
                         │
                  T3 Connect / environment

QML is a presentation client. It has no Clerk/OAuth token, DPoP key, relay credential, WebSocket ticket, T3 RPC client, or raw upstream object. All T3 internals are isolated beneath bridge/src/t3; other bridge code speaks the local protocol types in bridge/src/protocol.

Omarchy audit and plugin lifecycle

The implementation was checked against Omarchy 4.0.0-1 (upstream tag v4.0.0, commit f0020448ca87329199de7cb12f2015ebc4a3e5e7) and Quickshell 0.3.0, revision 28771c7c74b42e20afca0b1b63980cb46515537c.

The current manifest schema is version 1. The plugin declares service and bar-widget entry points with keepLoaded: true:

  • Service.qml is the singleton background state and owns one persistent bridge process.
  • BarWidget.qml obtains the service through shell.serviceFor(pluginId), shows connection plus attention state, and owns the native Omarchy KeyboardPanel modal.
  • Panel.qml is modal content only and loads Login, Inbox, or Thread views. It does not create a top-level window.

Omarchy's plugin host exposes shell.summon(id, payloadJson). On auth.completed, the background service first emits the state transition and then calls it with { "route": "inbox" }. Omarchy routes that summon to the bar-owned modal, so the browser flow returns to a visible Inbox even if the user closed it while authenticating.

Local protocol

Quickshell's current Process type provides stdinEnabled, write(), and a persistent SplitParser for stdout. That made inherited stdin/stdout NDJSON the smallest and safest IPC: no socket path, listening port, or local auth secret is necessary.

Requests have:

{"protocolVersion":1,"requestId":"qml-…","type":"thread.settle","payload":{"threadId":""}}

Responses echo requestId; events carry event and a typed payload. Requests are handled sequentially, preserving the order of state mutations followed by sends. Input is limited to 1 MiB, operation payloads are validated, unknown versions and commands are rejected, and malformed lines produce an error response instead of terminating either process.

The stable command surface covers auth, environments, Inbox, open/create/send, interrupt, model/model-option/runtime/interaction modes, settle, snooze, pin, rename, approvals, user input, and clipboard screenshot staging. Screenshot bytes stay in bridge memory under random, thread-bound attachment IDs; QML receives a data-URL preview and sends only those IDs back with the turn. The T3 adapter resolves them to the pinned upload contract immediately before dispatch and consumes them only after a successful command. QML never references an upstream RPC method name.

Authentication and connection

NativeClerkProvider follows the working native desktop credential path:

  1. Initialize a private native Clerk client against clerk.t3.codes; persist its rotating client token in Secret Service.
  2. Bind an ephemeral listener to literal 127.0.0.1, create a 256-bit callback secret, and place that secret plus the port in Secret Service.
  3. Create a hidden callback entry for T3's allow-listed t3code://app/ scheme and temporarily register it, preserving any previous desktop owner.
  4. Open a local no-store login page. Google/GitHub selection creates Clerk's native OAuth sign-in and redirects the browser to the provider.
  5. The hidden desktop handler passes the resulting custom URI to a short-lived bridge invocation, which validates the URI and forwards it to loopback with the callback secret.
  6. Validate Clerk's rotating-token nonce, finish the native sign-in, close the listener, clear callback state, restore the previous URI handler, and remove the temporary desktop entry.
  7. Request the t3-relay JWT template for the active Clerk session. That correctly-audienced JWT enters upstream ManagedRelay, which performs Relay DPoP exchange, environment bootstrap, environment DPoP exchange, WebSocket ticketing, and Effect RPC.
  8. Emit auth.completed and summon Inbox automatically.

This is not credential scraping: the plugin creates and owns its own Clerk client. The previous CLI PKCE provider remains isolated behind AuthProvider for compatibility evidence, but it is not the production default because the deployed Relay does not accept CLI OAuth at the DPoP exchange.

Upstream runtime reuse

The root pnpm workspace includes exactly the pinned source workspaces for @t3tools/contracts, @t3tools/client-runtime, and @t3tools/shared. esbuild bundles their used dependency graph into the bridge.

Directly reused behavior includes:

  • Clerk frontend discovery and the public T3 Connect configuration.
  • DPoP proof construction, access-token hashing, and JWK thumbprints.
  • ManagedRelay and remote authorization flows.
  • RpcSessionFactory, Effect WebSocket transport, method contracts, and command schemas.
  • shell and thread stream reducers.
  • settlement/snooze eligibility and effective lifecycle helpers.
  • pinned-order sorting and contract decoders.

Pending approval and input derivation is a small, behavior-matched adapter to the pinned web client's session-logic.ts. That app-private module cannot be consumed directly under NodeNext without pulling in the web build's resolver; compatibility tests cover the adapter's event shapes and ordering. Bundled upstream code remains MIT-licensed and notices ship with the plugin.

State and synchronization

The server is authoritative. The bridge subscribes to the shell projection, reduces updates with upstream reducers, then sends a bounded DTO to QML. Inbox partition order matches the official web client: effective snooze, pin, effective settle, then active. Pinned keys, static active creation order, soonest snooze wake, and settlement timestamps use upstream behavior.

Opening a thread starts the upstream detail stream. QML considers a subscription active only after its first matching server snapshot. Complete snapshots keep QML recoverable; delta/completion events reduce perceived latency. Pending approvals and questions are re-derived after every activity update. A dropped connection or unexpectedly completed shell/thread stream reconnects with bounded exponential backoff and re-subscribes the previously open thread after the shell snapshot returns. Writes queued for a bridge process are failed and discarded if that process exits, so mutations are never replayed into a replacement process without user intent.

Every lifecycle control is capability-gated for presentation and checked again in the bridge before dispatch. No settle, snooze, or pin is represented by a local flag.

Packaging

The public repository root is itself the single supported Omarchy plugin: manifest.json maps directly to qml/Service.qml and qml/BarWidget.qml. That is the layout cloned and validated by omarchy plugin add; there is no nested or second manifest and no symlink in the tracked tree. Development sources can coexist at the root because Omarchy loads only advertised entry points.

esbuild produces ESM and CommonJS bridge bundles. scripts/package.mjs uses Node's current single-executable support (or the legacy postject path on Node 24), with repository-relative SEA input paths. It runs an embedded self-test and copies only the plugin, bundled bridge, documentation, and license notices. Archive entries are sorted and normalized to epoch timestamps, numeric root ownership, and deterministic gzip headers. The T3 source submodule is a build input, not part of the installed plugin.

The root marketplace layout stores the x86-64 executable as a compressed, checksum-bound local payload. CI rebuilds it with the .node-version-pinned official Linux x64 Node distribution and fails unless the tracked decompressed payload byte-matches the fresh source build. The launcher expands it atomically inside the plugin checkout on first use and replaces it when a future payload checksum changes. Release packages instead carry the executable directly and retain the ESM bundle only as a diagnostic fallback when a compatible node is already available. The service owns process startup, restart, SIGTERM shutdown, and therefore leaves no separately managed daemon or systemd unit. The bridge creates its hidden freedesktop callback entry only for sign-in and restores an existing t3code scheme owner before removing it. The archive includes matching install and uninstall entry points; installation keeps at most one rollback copy.