Files
tutti/docs/architecture/project-structure.md
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

530 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Structure
This document defines the current repository structure for `tutti`.
It explains what belongs in each top-level area and how new directories should be introduced.
## Top-Level Layout
```text
tutti/
apps/
config/
services/
packages/
tools/
docs/
```
## Top-Level Responsibilities
### `apps/`
`apps/` contains product entrypoints and user-facing shells.
Current area:
- `apps/cli`: bundled terminal entrypoint for the local daemon capability protocol
- `apps/desktop`: Electron desktop application
Rules:
- app directories may own presentation, runtime integration, and entrypoint-specific behavior
- app directories must not duplicate business logic already owned by `services/`
- do not materialize future app ideas as empty directories; keep them in docs until the module has a real interface and implementation
### `services/`
`services/` contains long-running product backends.
Current area:
- `services/tuttid`: local daemon and primary business core
Rules:
- service directories own business workflows, durable state, and persistence ownership
- if a feature requires domain decisions or state transitions, it should usually land in `services/tuttid`
### `config/`
`config/` contains repository-owned default sources that are consumed by generation or tooling.
Current area:
- `config/tutti.defaults.json`: single-source default names and budgets for local state, transport, and logging
Rules:
- keep `config/` focused on repository defaults, not per-user settings or secrets
- prefer generating runtime-specific code from `config/` rather than reading these files ad hoc inside packaged applications
- do not turn `config/` into a second `docs/` directory; only keep machine-consumable sources here
### `packages/`
`packages/` contains shared boundaries, not default implementation code.
Current grouping:
```text
packages/
browser/
clients/
configs/
connector/
device-link/
events/
ui/
workbench/
workspace/
```
Rules:
- organize packages by responsibility, not by language alone
- use `clients/*` for domain-specific client access
- use `device-link` for shared ICE/QUIC peer transport, product-neutral Relay stream mechanics, and the gomobile boundary consumed by Tutti, TSH, and mobile clients
- use `events/*` for schema-first shared business event protocol contracts, validators, and generated transport metadata that multiple hosts consume
- use `browser/*` for reusable browser/workbench node mechanics that are shared by desktop hosts without carrying product-specific bridge methods
- use `configs/*` for shared engineering configuration
- use `ui/*` for shared frontend foundation packages such as visual-system boundaries, host-agnostic React hooks, and host-agnostic i18n runtime support
- use `workbench/*` for the shared workbench snapshot contract and reusable workbench interaction surface intended to be shared by the open-source desktop and TSH
- use `workspace/*` for narrow reusable workspace-domain contracts and feature surfaces intended to be shared by the open-source desktop, TSH, and TACH
- do not create vague packages such as `shared`, `common`, or `client-sdk`
- do not pre-create package directories for future domains; add the package only when a real multi-consumer seam exists
- documentation alone does not make a package seam real; the package must expose
a narrow interface that a current consumer can use without learning its
implementation layout
### `tools/`
`tools/` contains repository support code such as:
- build helpers
- packaging helpers
- generation entrypoints
- validation or maintenance scripts
Core product behavior should not permanently live in ad hoc scripts when it belongs in a first-class application or service.
### `docs/`
`docs/` contains persistent repository documentation.
Current sub-areas:
- `docs/architecture`: structure notes and technical design context
- `docs/conventions`: coding, layering, naming, and storage rules
Rules:
- `docs/architecture` and `docs/conventions` are the long-lived source of truth
- temporary planning notes should not become the primary source of current repository rules
- once a design has landed and stabilized, durable rules should be promoted into `docs/architecture` or `docs/conventions`
## Current Structure Decisions
### `apps/cli`
`apps/cli` is responsible for:
- terminal argument parsing
- local daemon endpoint discovery and bearer authentication
- invoking the daemon-owned CLI capability protocol
- rendering daemon command output for terminal users
It must not become a second business core. Command metadata, workspace resolution, edition/context filtering, and command execution stay in `services/tuttid`.
### `apps/desktop`
`apps/desktop` is responsible for:
- renderer UI
- Electron main-process lifecycle
- preload bridge and IPC exposure
- native desktop integration
- supervising `tuttid`
It must not become a second business core.
Desktop keeps four top-level source areas:
```text
apps/desktop/src/
main/
preload/
renderer/
shared/
```
Desktop summary:
- `main/` owns Electron lifecycle, daemon/runtime composition, host access, transport, IPC, update integration, and window creation
- `preload/` owns the typed bridge surface exposed to renderer
- `renderer/src/app/windows/*` owns renderer window composition shells such as `dashboard` and `workspace`
- `renderer/src/features/*` owns reusable renderer feature modules
- `renderer/src/features/*/services/*` owns feature service public surfaces, while `services/internal/**` stays private to the owning feature
- `renderer` consumes shared visual foundations from `packages/ui/system` instead of growing its own token or primitive layer
- `shared/` stays narrow and desktop-local
- desktop-owned i18n resources stay under `shared/i18n/*`, while reusable package default i18n resources stay with the owning package and are merged by the renderer app-level i18n runtime
- `main/bootstrap.ts` stays a top-level coordinator; service assembly belongs in `desktopAppServices.ts`, `desktopDaemonRuntime.ts`, `desktopHostServices.ts`, and `desktopAppLifecycle.ts`
The authoritative desktop directory shape and ownership rules live in [docs/conventions/desktop-layering.md](../conventions/desktop-layering.md). Keep this repository-level document as a summary, not a second full desktop structure spec.
### `packages/desktop/*`
Desktop packages own product-neutral lifecycle and native-host integration
mechanics shared by more than one desktop product.
Current packages:
- `packages/desktop/update-admission`: minimum-version policy contracts,
validation, mandatory updater ownership, Electron admission lifecycle,
capability-minimal preload API, shared React presentation, and default i18n
Product policy transport, normal update preferences, release feeds, download
URLs, window assets, logging sinks, and business-window enumeration remain in
the consuming desktop app.
### `packages/connector/*`
Connector packages own host-neutral connector domain boundaries shared across
desktop daemons.
Current owners:
- `packages/connector/contracts`: versioned authorization wire contracts and
the local OpenAPI fragment
- `packages/connector/daemon/core`: connector catalog, installation, authorization,
compatibility, operation, and recovery application core
- `packages/connector/daemon/application`: reusable daemon lifecycle and workers
- `packages/connector/daemon/adapters/sqlite`: canonical local persistence and outbox
- `packages/connector/daemon/adapters/controlplane`: account-scoped authorization
HTTP and realtime protocol adapter
- `packages/connector/runtime`: latest-only artifact caching, no-network archive
import, same-machine composition, managed-runtime installation primitives,
Connector route/MCP registries, and the session-bound loopback MCP server
- `packages/connector/renderer`: React-free frontend application services plus
the only shared Connector-specific React and i18n owner
Remote endpoint authentication, credentials, state-root selection, generated
daemon clients, product command publication, and OS process integration remain
in host adapters.
### `services/tuttid`
`services/tuttid` is the primary business core.
It owns:
- business rules
- domain workflows
- local persistence
- long-running daemon behavior
### `packages/clients/*`
Client packages provide domain-specific access helpers for consumers.
They should remain focused, named by responsibility, and free of hidden business rules.
`packages/connector/daemon/adapters/controlplane` is the shared Go protocol client for
account-scoped Connector authorization. It owns authorization session,
snapshot, and realtime-event decoding plus bounded HTTPS transport. A product
host supplies the API prefix, HTTP client, and per-account request authorizer;
the package never stores account cookies, chooses an environment, or sends
credentials to a runtime VM. `packages/connector/daemon/core` selects this client only
for `remote_streamable_http` releases, while `managed_stdio` authorization
stays with the injected local implementation host.
`packages/clients/device-authority-go` is the shared Go client boundary for the
Device Authority owner lifecycle across Tutti and TSH. The initial package is a
staged cross-repository extraction from TSH: publish the stable module first,
then refactor TSH to the released version without changing behavior, then wire
the same client into Tutti. This ordering is required because cross-repository
consumers must not use workspace replacements or pseudo-versions. The package
owns the control-plane wire contract, canonical Ed25519 enrollment and
owner-token signing, bounded HTTP response semantics, and credential-binding
validation. Product adapters still own base-URL and API-prefix selection,
account/session/device headers, durable identity storage, Relay demand and lease
scheduling, logging, and retry policy. The client must not infer
local-versus-remote deployment policy or import Relay transport lifecycle code.
`packages/clients/market-go` is the market-neutral generated Go client boundary
for TSH Market. It pins the provider-generated protobuf and HTTP artifacts to an
exact `tsh-server` commit and SHA-256 values without importing the server
application module or copying the remote schema into Tutti's local OpenAPI.
The update tool verifies the source checkout commit before copying digest-matched
files. Product adapters inject the HTTP transport, gateway base path, and request
authorization. Connector consumers pass `itemType=connector`; future Skill
consumers reuse the same client with `itemType=skill`.
### `packages/device-link`
DeviceLink is the shared Go peer-transport boundary for Tutti Desktop, TSH
Desktop, and mobile clients. It owns ICE candidate negotiation, QUIC over the
selected packet path, mutual ephemeral certificate pinning, categorical path
classification, the gomobile build surface, and product-neutral authenticated
link lifecycle mechanics: generation-fenced admission, establishment
serialization, pooled stream ownership, connection racing, and annealed path
probing. It also owns generic Relay byte-stream dialing and the reusable
reference-counted WebSocket/yamux owner-tunnel mechanics, including liveness,
readiness ordering, reconnect backoff, stream dispatch, and close ordering.
Its `candidateexchange` subpackage also owns the Trickle ICE application
mechanics shared by Go and gomobile consumers: immediate credential snapshots,
candidate change coalescing, acknowledgement-bound exact-snapshot publication
retry, gathering completion, and push-hint plus authoritative-poll remote
refresh. Consumers provide rendezvous callbacks or execute facade actions and
retain product retry classification; account authorization, attempt state,
request signing, room/pairing semantics, and wire DTOs stay in their product
adapter. The callback-free Go `ActionPump` owns both candidate workers and
allows at most one unresolved action per worker, so a slow rendezvous read does
not block local publication. The gomobile facade exports scalar/JSON start,
next-action, resolve-action, notify, and stop/cancel operations. Mobile uses two
identical action drainers to execute signed authoritative reads/writes without
reimplementing ICE retry timing, wake cursors, polling, or worker cancellation
in TypeScript, Kotlin, or Objective-C.
It exposes authenticated bidirectional streams and generic Relay byte streams,
and must remain independent of Agent, Session, Workspace, account, pairing,
rendezvous, Relay authorization, and Relay product policy. Peer and owner keys
stay opaque; host services and apps inject path dialers, credentials, leases,
fallback timing, and application stream protocols. The `tuttid` Mobile Remote
owner is the first adapter for the shared link manager; its pairing and Agent
framing remain service-owned. TSH remains the product authority for its Relay
registration and lease lifecycle even when it consumes the shared owner-tunnel
mechanics. Raw addresses, candidates, credentials, and payloads must not enter
ordinary logs or metrics.
### `packages/events/*`
Event packages define shared business event protocol boundaries.
Current package:
- `packages/events/protocol`: repository-owned JSON Schema and event-definition source files for the business event stream, plus generated TypeScript protocol contracts and daemon transport registry output
Rules:
- keep schema-first source files in the package and keep those files as the only shared source of truth for business event topics
- keep generated TypeScript exports narrow and protocol-oriented
- keep WebSocket lifecycle management, daemon orchestration, and renderer feature behavior outside `events/*`
### `packages/browser/*`
Browser packages define reusable Browser Node mechanics for hosts that need to
embed web content in the shared Workbench.
Current packages:
- `packages/browser/workbench-node`: `@tutti-os/browser-node`, the shared
Workbench Browser Node package. It owns generic HTTP/HTTPS navigation,
session partition resolution, runtime state, bridge shape, Electron webview
guest management, and package-local i18n defaults. Host adapters own product
globals, backend-token access, preview proxy behavior, and business bridge
methods.
Rules:
- keep Browser Node mechanics in this package and host/product-specific methods
in the consuming app or integration
- require hosts to provide bridge namespaces; the package must not assume
`__tutti`, `__tsh`, or any other product global
- keep preview proxy interfaces inert until a host intentionally implements
route resolution
- keep daemon contracts out of Browser Node v1; the desktop host persists layout
through the existing Workbench snapshot
### `packages/configs/*`
Config packages exist to keep engineering defaults centralized and reusable.
They should stay small and boring.
### `packages/ui/*`
UI packages define shared frontend foundations.
Current packages:
- `packages/ui/system`: shared tokens, icons, styles, and primitives for renderer consumers; also the repository-owned host package for shared shadcn CLI and Radix primitive acquisition
- `packages/ui/i18n-runtime`: host-agnostic i18n runtime helpers for shared frontend packages and app-level runtime composition
- `packages/ui/react-hooks`: host-agnostic React hook helpers for shared frontend packages, including external-store snapshot and selector patterns
Rules:
- keep scope limited to frontend foundation concerns such as visual-system primitives, token-backed styles, host-agnostic i18n runtime composition, and host-agnostic React subscription helpers
- allow `ui/*` to own host-agnostic React hook foundations when the hook pattern is shared across packages and stays free of product workflows
- prefer routing shared `useSyncExternalStore` wiring through `packages/ui/react-hooks` instead of rebuilding new subscription wrappers in feature packages
- do not move business components, product workflows, or domain orchestration into `ui/*`
- prefer consuming `ui/*` from app renderers and reusable frontend packages rather than recreating the same foundations locally
- prefer narrow, stable package entrypoints over exporting internal file layout as public API
### `packages/workbench/*`
Workbench packages define the shared workbench boundary for the open-source
desktop and TSH.
Current packages:
- `packages/workbench/snapshot`: canonical TypeScript workbench snapshot types,
migrations, normalization, validation, and JSON Schema. The daemon OpenAPI
`WorkbenchSnapshot*` component schemas are synchronized from this package.
- `packages/workbench/service`: shared Go Workbench snapshot service, validation,
canonicalization, and persistence seam for daemon hosts.
- `packages/workbench/electron`: Electron main-process Dock preview capture and
bounded filesystem cache mechanics. Desktop hosts still own IPC
authorization, BrowserWindow ownership, cache paths, and logging.
- `packages/workbench/surface`: reusable workbench controller, reducer,
placement, stacking, `WorkbenchHost`, React surface primitives, shell snapshot
wiring, intent resolution, external-state render plumbing, and host/session
lifecycle mechanics for projected presence, launch requests, transient
activation, explicit close policy, and shell snapshot sanitation.
Rules:
- keep snapshot compatibility behavior in `snapshot`, not in app renderers
- keep shared Go Workbench validation, canonicalization, and storage seams in
`service`, not in host daemons
- keep shared Electron Dock preview capture and bounded cache mechanics in
`electron`; keep host window authorization and product diagnostics in the
consuming desktop app
- keep reusable workbench interaction mechanics in `surface`, not in
product-specific feature UI
- when `surface` exposes derived external-store snapshots through
`getSnapshot()`, unchanged source snapshots must preserve reference identity;
prefer the package-local derived-snapshot helper instead of rebuilding fresh
objects or arrays on every read
- keep product-specific node bodies, routing, and persistence adapters in the
owning app or service
- allow `surface` to own narrow default copy for generic workbench interaction
mechanics such as window chrome labels; keep product-specific workbench copy
in the owning app
- do not widen `workbench/*` into a generic desktop shell package
- keep package root exports intentionally small; root exports are the public
interface, not an index of every internal module
- do not export test fixtures, demo data, stack internals, reducer internals, or
low-level hooks from a package root unless an existing consumer needs that as a
stable interface
- keep adapter-specific durable state behind generic contract fields such as
`Record<string, unknown>` unless the adapter detail is itself part of the
shared snapshot contract
Host reuse model:
1. Keep generic workbench interaction mechanics, structural styles, and narrow
default copy in `packages/workbench/*`.
2. Keep host-specific node bodies, routing, persistence adapters, and
product-owned workbench copy in the consuming app or service.
3. Let the consuming host create one app-level i18n runtime that merges:
- host-owned i18n resources
- reusable package default i18n resources
4. Scope that runtime into package namespaces such as workbench window chrome
instead of reconstructing per-package message objects by hand.
### `packages/workspace/*`
Workspace packages define reusable workspace-domain contracts that are intended
to support a real multi-consumer boundary or a documented external contract as
additional hosts adopt them.
Current packages:
- `packages/workspace/files`: Go domain kernel for logical workspace file
semantics, path normalization, search scoring, and host-owned file adapters.
- `packages/workspace/file-manager`: TypeScript state, actions, adapter
contract, and optional React UI for a workspace file manager. Ownership
boundaries live in that package’s `CONTRACT.md`.
- `packages/workspace/terminal`: shared terminal node contract and frontend
surface for workbench hosts.
- `packages/workspace/issue-manager`: reusable issue-manager contracts, OpenAPI
fragment, i18n defaults, React surface, and workbench registration helpers
for workspace-scoped issue, task, and run workflows.
Release rule:
- npm package release participation is defined in the npm release conventions,
not by repeating package rosters in architecture docs
- shared non-npm modules follow their owning language and module conventions
unless a separate release contract is introduced
Rules:
- keep host-specific adapters in the owning host, such as `services/tuttid` or
an app renderer feature
- a reusable frontend workspace package may still own shared session state,
interaction flow, and optional React UI when those behaviors form the shared
workspace-domain surface rather than a product-specific host integration
- a reusable frontend workspace package may also own narrow default copy for its
shared UI surface; hosts should override through their app-level i18n runtime
rather than duplicating package strings locally
- keep shared state and UI on logical workspace paths such as `/workspace`, not
host absolute paths or VM mount paths
- do not move tuttid storage lookup, desktop preload calls, TSH room mapping, or
TACH-specific integration into `packages/workspace/*`
Host reuse model:
1. Reuse the shared package for session orchestration, view-model derivation,
interaction flow, and optional UI when those behaviors are truly host-neutral.
2. Implement host-specific transport and capability adapters in the consuming
host, for example preload calls, daemon clients, room bridges, or local file
selection surfaces.
3. Keep shared workspace UI copy defaults in the owning package and merge them
through the host's app-level i18n runtime instead of copying those strings
into the host.
4. Keep product-specific wording, shell integration, and user-facing host
behaviors in the consuming host.
## Adding New Directories
### Add a new top-level directory only when
- the new area has a distinct deployment or ownership boundary
- it cannot be described as part of an existing `apps/`, `services/`, `packages/`, `tools/`, or `docs/` area
### Add a new app directory when
- there is a new user-facing entrypoint with its own runtime shell
Examples:
- a desktop app
- a CLI app
Keep future app ideas in docs until they become real modules. Do not reserve them as empty directories.
### Add a new service directory when
- there is a new backend process with its own lifecycle and state ownership
### Add a new package when
- there is a real multi-consumer boundary
- the extracted API can be named narrowly by responsibility
- the shared code is not just convenience reuse
- the public entrypoint can stay much smaller than the implementation tree
Keep code local by default:
- desktop-only TypeScript stays in `apps/desktop`
- daemon-only Go stays in `services/tuttid`
Keep future packages in docs, not in placeholder directories:
- a README-only package is usually a shallow module
- a package name should appear in the tree only when contributors can follow it to a real interface and implementation
## Structure Review Questions
When reviewing a new directory or package, ask:
1. Which existing top-level area already owns this responsibility?
2. Is this a real boundary or just an attempt to spread code out?
3. Would keeping this code local be simpler and clearer?
4. Is the new name based on responsibility instead of a vague shared label?
5. Does the new structure reduce confusion, or just increase file count?
6. Can a caller use the module through a small interface, or does the package
root expose the implementation layout?