Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
239 lines
7.6 KiB
Markdown
239 lines
7.6 KiB
Markdown
# Desktop Backend Access
|
|
|
|
This document records the desktop backend access model for `tutti`.
|
|
The planned shared business-stream protocol is documented in
|
|
[Business Event Stream](./business-event-stream.md).
|
|
|
|
## Decision
|
|
|
|
`tuttid` is the local product backend for `tutti`.
|
|
|
|
Desktop uses a dual-path model:
|
|
|
|
- `renderer -> tuttid` for business capabilities and streaming transports
|
|
- `renderer -> preload -> main` for Electron and OS capabilities
|
|
|
|
`main -> tuttid` remains available for a small number of host-assisted flows,
|
|
but it is not the default business access path.
|
|
|
|
## Why
|
|
|
|
This model keeps one business authority while improving three areas that are
|
|
awkward under a main-process relay-only design:
|
|
|
|
- streaming transports such as WebSocket and SSE
|
|
- CLI access to the same local product backend
|
|
- desktop feature work that should not require a manual IPC wrapper for every
|
|
daemon capability
|
|
|
|
The goal is not to make Electron `main` irrelevant. The goal is to stop using
|
|
it as a broad business transport relay.
|
|
|
|
## Ownership Model
|
|
|
|
### `tuttid`
|
|
|
|
`tuttid` owns:
|
|
|
|
- business rules
|
|
- durable local state
|
|
- domain workflows
|
|
- backend contracts shared by desktop and CLI
|
|
|
|
### `main`
|
|
|
|
`main` owns:
|
|
|
|
- daemon lifecycle supervision
|
|
- loopback endpoint discovery and session-token issuance
|
|
- desktop-only logging and diagnostics
|
|
- Electron and OS integrations
|
|
- host-assisted flows that require both native capability and daemon access
|
|
|
|
`main` must not become a second business core.
|
|
|
|
### `preload`
|
|
|
|
`preload` owns the narrow renderer-facing desktop SDK.
|
|
|
|
It should expose:
|
|
|
|
- runtime configuration needed to reach the managed local backend
|
|
- narrow resolved route helpers for managed transport families when renderer
|
|
should not assemble route-local WebSocket URLs itself
|
|
- explicit host capabilities such as file pickers, window control, shell open,
|
|
notifications, and similar native actions
|
|
- a small number of host-assisted commands when renderer cannot complete the
|
|
flow alone
|
|
|
|
It should not expose:
|
|
|
|
- a generic `invoke(channel, payload)` surface for ordinary renderer work
|
|
- transport policy details beyond the minimum needed to reach the managed local
|
|
backend
|
|
|
|
### `renderer`
|
|
|
|
`renderer` owns:
|
|
|
|
- UI
|
|
- view state
|
|
- direct business calls to `tuttid`
|
|
- direct SSE / WebSocket consumption when the stream is business-facing
|
|
|
|
Renderer should treat `tuttid` as the backend and `window.tutti` as the host
|
|
capability surface.
|
|
|
|
## Default Routing Rule
|
|
|
|
When adding or changing a desktop capability, choose the path by
|
|
responsibility.
|
|
|
|
### Direct `renderer -> tuttid`
|
|
|
|
Use for:
|
|
|
|
- ordinary query and command APIs
|
|
- workbench and workspace data
|
|
- search, list, get, update, save
|
|
- terminal and agent streams
|
|
- business event subscriptions
|
|
|
|
Business event subscriptions and bidirectional business event publishing should
|
|
follow the shared event-stream contract rather than route-local ad hoc socket
|
|
payloads.
|
|
|
|
### Direct `renderer -> main`
|
|
|
|
Use for:
|
|
|
|
- file and directory pickers
|
|
- shell reveal and shell open
|
|
- notifications
|
|
- menus, tray, clipboard, window lifecycle, updater
|
|
- any capability that requires Electron or OS APIs
|
|
|
|
### Host-assisted `renderer -> main -> tuttid`
|
|
|
|
Reserve for flows that require both native host authority and daemon authority
|
|
in one operation.
|
|
|
|
Examples:
|
|
|
|
- choose an export destination, then ask `tuttid` to write backend-owned data
|
|
- open a file or resource whose resolution depends on daemon-owned metadata plus
|
|
native shell behavior
|
|
- choose a host directory, then pass that directory to a host capability that
|
|
needs explicit filesystem input
|
|
|
|
These flows should stay explicit and few.
|
|
|
|
## Transport Policy
|
|
|
|
Desktop transport follows these rules:
|
|
|
|
- `tuttid` binds to loopback only
|
|
- desktop prefers a managed random loopback port over a fixed public port
|
|
- `main` discovers or allocates the endpoint, then provides renderer with
|
|
runtime config
|
|
- business streaming uses HTTP, SSE, and WebSocket directly where appropriate
|
|
|
|
This favors a product-style local backend over a desktop-private relay model.
|
|
|
|
## Desktop Capability Classification
|
|
|
|
The desktop surface should be designed as three explicit capability groups.
|
|
|
|
### Backend Business Capabilities
|
|
|
|
These belong on the direct `renderer -> tuttid` path:
|
|
|
|
- health, availability, and backend bootstrap checks
|
|
- workspace catalog queries and mutations
|
|
- workbench snapshot load and save
|
|
- workspace file listing, search, creation, deletion, and upload flows
|
|
- streaming business APIs such as terminal, agent, and event streams
|
|
|
|
Terminal streams and business event streams are both direct business transports,
|
|
but they should remain separate protocol surfaces because they serve different
|
|
semantics.
|
|
|
|
Workspace file APIs on this path should stay logical-path based, for example
|
|
`/workspace/...`, rather than exposing host absolute paths as part of the
|
|
business contract.
|
|
|
|
This is the default path for business-facing desktop product behavior.
|
|
|
|
### Host Capabilities
|
|
|
|
These belong on the direct `renderer -> main` path through preload:
|
|
|
|
- system locale and system theme signals
|
|
- host-side preference sync needed for Electron-only side effects
|
|
- updater state and update actions
|
|
- file and directory pickers
|
|
- dropped-path resolution
|
|
- workspace window display and dashboard display
|
|
- shell reveal, shell open, native preview affordances
|
|
- window creation, focus, and replacement
|
|
- notifications, menu actions, tray, clipboard, and similar Electron or OS APIs
|
|
|
|
These capabilities should stay explicit on surfaces such as
|
|
`window.tutti.host.*`, `window.tutti.update.*`, and
|
|
`window.tutti.platform.*`.
|
|
|
|
Host capability rules:
|
|
|
|
- host adapters should normalize Electron-native outputs into durable product values before returning to renderer
|
|
- host window commands should stay window-oriented and avoid hiding backend mutations inside IPC handlers
|
|
|
|
Preference ownership rule:
|
|
|
|
- renderer should read and write user desktop preferences through `tuttid`
|
|
- `main` should stay limited to system-environment inputs and host-only effects such as theme application
|
|
|
|
### Host-assisted Compound Flows
|
|
|
|
These belong on the `renderer -> main -> tuttid` path:
|
|
|
|
- resolve and open a local file whose authority crosses daemon metadata and host
|
|
shell behavior
|
|
- read local preview content when host file access is still part of the
|
|
product contract
|
|
|
|
Use this path when host absolute-path resolution is required at runtime. Do not
|
|
push that resolution into the workspace catalog or ordinary business DTOs.
|
|
|
|
These flows should stay narrow, named, and intentional. They are not a second
|
|
business transport layer.
|
|
|
|
Update capability rule:
|
|
|
|
- preload-facing update configuration remains a typed contract, so `main/update/*` should validate supported `channel` and `policy` values instead of silently coercing unknown fields
|
|
|
|
## Security Constraints
|
|
|
|
Moving business traffic to managed loopback requires explicit guardrails:
|
|
|
|
- bind only to `127.0.0.1` or equivalent loopback
|
|
- prefer random allocated ports over fixed defaults
|
|
- require explicit desktop-issued authentication or capability material for
|
|
renderer-visible backend access
|
|
- keep untrusted content, external webviews, and arbitrary browser surfaces off
|
|
the desktop backend path
|
|
- keep preload surfaces narrow even when renderer can talk to `tuttid`
|
|
|
|
Renderer visibility of a local backend is acceptable only when the backend is
|
|
treated as a real product surface, not as an unguarded internal helper.
|
|
|
|
## CLI Relationship
|
|
|
|
This model supports a cleaner CLI story:
|
|
|
|
- CLI talks to `tuttid` as a client of the same local product backend
|
|
- desktop renderer talks to the same backend
|
|
- `main` stays a host supervisor, not a CLI dependency
|
|
|
|
That shared backend contract should reduce duplication across desktop, CLI, and
|
|
tests.
|