Files
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

346 lines
17 KiB
Markdown

# API Contracts
Use OpenAPI as the single source of truth for daemon HTTP contracts.
## Source Of Truth
The canonical daemon API contract lives at:
```text
services/tuttid/api/openapi/tuttid.v1.yaml
```
Generated outputs derive from that spec for:
- Go transport types and server contract code under `services/tuttid/api/generated`
- TypeScript client output under `packages/clients/tuttid-ts/src/generated`
Shared workbench snapshot schemas are the one exception inside the OpenAPI
components section. Their canonical source lives at:
```text
packages/workbench/snapshot/src/schema.json
```
The OpenAPI `WorkbenchSnapshot*` component schemas are synchronized from that
shared package so the open-source desktop and TSH can reuse the same workbench
snapshot contract while `tuttid` still exposes a normal OpenAPI transport
contract. The daemon workspace service also consumes a generated Go contract
file derived from the same shared snapshot sources for schema-version, enum,
and limit validation.
`WorkbenchSnapshot*` is an explicit shared-contract exception to the usual
"generated code stays in transport" default. When it avoids duplicate
hand-maintained mirrors, the synchronized Go snapshot contract may be reused
inside `packages/workbench/service`, with `services/tuttid/service/workspace`
consuming that shared package as a host adapter. This exception applies to the
shared workbench snapshot contract only, not to arbitrary generated request or
response DTOs.
The shared business event stream contract is the other explicit repository
exception to the HTTP OpenAPI source-of-truth rule. Its canonical sources live
at:
```text
packages/events/protocol/schemas/**/*.json
packages/events/protocol/definitions/**/*.event.json
```
Those file-based sources generate:
- TypeScript protocol contracts, validators, and topic registry metadata under
`packages/events/protocol/src/generated`
- Go transport contracts and topic registry metadata under
`services/tuttid/api/events/generated`
This exception is limited to the shared business event protocol boundary. The
shared package owns the schema-first event sources and the generated
TypeScript-facing protocol surface. The daemon still owns its WebSocket route,
its authoritative event catalog composition, and the generated Go transport
outputs that live under `services/tuttid/api/events/generated`.
This exception does not create a second source of truth for daemon HTTP routes
or a license to hand-maintain competing event DTOs in desktop or daemon code.
## Repository Rules
- change the OpenAPI spec before changing daemon HTTP request or response shapes
- change `packages/workbench/snapshot/src/schema.json` before changing
`WorkbenchSnapshot*` component schemas in the OpenAPI spec
- do not hand-maintain parallel transport DTOs once the generated boundary exists
- do not hand-edit OpenAPI `WorkbenchSnapshot*` schemas; run the sync command
instead
- do not hand-edit the daemon workbench snapshot contract constants; run the Go
sync command instead
- do not hand-edit generated business event protocol TypeScript or Go outputs;
run the event protocol generator instead
- do not assume generated Go structs enforce every OpenAPI or JSON Schema
constraint at runtime
- keep generated code limited to the transport layer
- exception: the synchronized Go `WorkbenchSnapshot*` contract may be reused in
`packages/workbench/service`, with host adapters such as
`services/tuttid/service/workspace` consuming that shared package, because it
is a repository-owned shared contract rather than an arbitrary route-local DTO
- exception: the shared business event protocol sources live in
`packages/events/protocol`, while daemon-owned generated Go transport outputs
live in `services/tuttid/api/events/generated`; keep daemon-side event-route
orchestration, catalog composition, and business workflows hand-written
- keep business rules, orchestration, and persistence hand-written
- keep compatibility aliases such as `/healthz` out of the versioned generated client surface unless they are part of the intended public contract
## Agent Provider And Target Identity
Agent `provider` values are an open, validated execution identity. OpenAPI,
generated clients, AgentGUI, and Workbench must preserve an unknown valid
provider string instead of coercing it to a built-in provider. Closed enums are
appropriate only for preferences that intentionally select among built-in
defaults.
`AgentTarget.launchRef` is a discriminated union. `builtin_local` is the
canonical built-in discriminator and `agent_extension` identifies a fixed,
verified extension installation. The daemon may accept legacy `local_cli` data
while reading durable state, but API output and new writes must never emit it.
Add a new union member before adding a new launch mechanism; do not overload a
provider string with launch or installation state.
## Agent Provider CLI Update Status
Provider status is local by default. `includeUpdates` is the explicit opt-in for
cached remote update discovery. `refresh` bypasses only local readiness;
`refreshUpdates` bypasses only update metadata and has no effect unless
`includeUpdates` is also true. Keep `includeNetwork` independent from both.
Every provider status carries a provider-neutral `update` object. Nullable
`updateAvailable` distinguishes unchecked or non-comparable state from a known
up-to-date result. Remote discovery errors belong in update `reasonCode` and
must not turn a valid local readiness snapshot into an HTTP failure.
Provider auth status distinguishes credential presence from remote proof:
`configured` means local credentials exist, `authenticated` means a
provider-backed operation succeeded, `required` means authentication is known
to be missing or rejected, and `unknown` means no settled evidence exists.
Clients may use `availability.status` to decide launchability; they must not
coerce `configured` to `authenticated` for display or analytics.
`update` is an explicit action id, not an alias for `install`. The daemon may
offer it only when both the provider descriptor and the resolved installation
prove a supported managed source. See [Agent Provider CLI Updates](../architecture/agent-provider-cli-updates.md).
## Error Contract
Daemon API failures should use the shared protocol-error shape in
`ApiErrorDetails` instead of relying on raw natural-language messages.
Rules:
- classify daemon-facing API failures through `services/tuttid/apierrors`
- treat `code` as the stable top-level contract for programmatic handling
- use `reason` to narrow copy lookup within a stable `code`
- use `params` only for structured interpolation or machine-readable context
- keep `developerMessage` diagnostic; it is not a user-facing copy contract
- use `correlationId` for cross-layer diagnostics when a request needs traceable failure context
- do not make renderer UX depend on exact `developerMessage` strings
- keep status-code mapping and transport validation at the `api` seam even when `apierrors` classifies the failure
Current canonical `ApiErrorDetails` fields are:
- `code`
- `reason`
- `params`
- `retryable`
- `developerMessage`
- `correlationId`
TypeScript consumers should normalize transport failures through
`@tutti-os/client-tuttid-ts` instead of re-inspecting raw response payloads in
each feature. Renderer and preload code may use `code`, `reason`, `params`, and
`retryable` for behavior and i18n lookup, while `developerMessage` and
`correlationId` remain diagnostic support fields.
## Resolved Defaults
When a route returns resolved defaults or effective settings, the response must
carry daemon-owned truth rather than echoing request input back to the client.
Rules:
- use explicit fields such as `effectiveSettings` for daemon-resolved values
- do not mirror request `settings` back in a response just to help clients keep
local fallback state alive
- return `null` or omit a field when the daemon cannot resolve it
- do not synthesize fallback defaults in renderer code when the daemon leaves a
field unresolved
`composer-options` follows this rule: request `settings` are input overrides,
while response `effectiveSettings` is the only contract for resolved homepage
composer defaults.
The same endpoint accepts an optional `section`: `core` returns the model,
reasoning, speed, permission, and effective-settings projection;
`capabilities` returns skills, commands, and capability-catalog data; omitted
or `full` preserves the combined response for compatibility. Sectioned
responses are additive projections, not alternate sources of resolved settings.
## Desktop Agent Conversation Detail Mode
`agentConversationDetailMode` is a global desktop preference, not a provider-specific
composer default. Keep it on the top-level desktop preferences contract and the
matching desktop preferences event payload; do not place it under
`agentComposerDefaultsByProvider`.
The stored value is the enum `coding | general`. Daemon and desktop shared
normalizers must treat missing, empty, or unknown values as `coding` so migrated
profiles keep the engineering-oriented default. `coding` does not inject extra
prompt guidance; it leaves provider defaults intact. `general` injects the
Codex-style `Non-technical UI` developer instruction section for new agent
sessions. For Codex app-server sessions, inject this through the session-scoped
Codex config before thread creation, not as a repeated per-turn
`turn/start.collaborationMode.settings.developer_instructions` override. The
same Codex session config should also include a Tutti-owned diagnostic marker
under `[tutti] conversationDetailMode = "coding" | "general"` so runtime
inspection can distinguish the global Tutti setting from Codex's own desktop
preferences. Do not confuse that with Codex collaboration mode presets: when
`collaborationMode/list` returns Default or Plan `developer_instructions`, the
Codex app-server adapter must pass the active preset instructions in
`turn/start.collaborationMode.settings.developer_instructions` so the active
mode matches Codex App behavior. Plan Mode and explicit planning-only flows
remain higher priority than conversation detail mode prompt guidance.
## Deleted Agent Conversation Retention
`deletedAgentConversationRetentionDays` is a device-global desktop preference
and must stay aligned across the OpenAPI `DesktopPreferences` schema and the
desktop preferences event schema. It is the closed integer set `15 | 30`; the
daemon normalizes missing durable values to 30 and rejects other transport
values. The renderer must publish the complete preference object through the
existing authoritative preference event flow rather than storing this setting
locally.
The Workspace deleted-conversation collection is
`/v1/workspaces/{workspaceID}/deleted-agent-sessions`:
- `GET` lists topmost deleted Session components: either a canonical root or a
child whose parent is not deleted. `searchQuery` searches titles only;
`projectPath` selects one exact original project, while
`projectScope=unscoped` selects Sessions without a project. Omitting both is
the all-project default. `cursor` and `limit` provide stable
`updatedAtUnixMs DESC, agentSessionId ASC` paging.
- The list response includes filtered `totalCount`, unfiltered
`workspaceTotalCount`, and all original non-empty project paths as
`projectOptions`, including paths whose project registration is unavailable.
A row explicitly reports whether it is restorable and why a legacy tombstone
is unavailable.
- `POST .../{agentSessionID}/restore` restores the exact complete component without
starting provider work. `DELETE .../{agentSessionID}` permanently removes one
topmost deleted component, and collection `DELETE` permanently removes every
topmost deleted component in that Workspace regardless of visible filters.
Permanent-delete endpoints return only aggregate Session, Message, and payload
byte counts. They use the daemon idle gate; restore does not. The API reports an
idle conflict as service unavailable and a maintenance failure as a Workspace
operation failure. Ordinary and typed high-risk confirmations are desktop
interactions, not HTTP request fields. Canonical deletion, Tutti Mode cleanup,
and the durable Session-scoped runtime/copied-attachment cleanup item commit
together. Because the current runtime and copied-attachment roots are keyed
only by Session ID, a pending item fences reuse of that ID across all
Workspaces; filesystem cleanup failure does not change the successful HTTP
result and is retried in a later idle window.
`POST /v1/agent-maintenance/deleted-conversations/purge` remains the
daemon-wide maintenance sweep without a Workspace path. Automatic retention is
daemon-owned and is not exposed as a second client scheduler. After an explicit
daemon-wide sweep, the daemon may make a strictly bounded best-effort
compaction attempt for a small database with substantial free pages; this
optional post-step is not run by automatic maintenance and is not part of the
aggregate response contract.
## Desktop Lab Preferences
`lab` is a top-level desktop preference group and must stay on both the
`DesktopPreferences` OpenAPI schema and the matching desktop preferences event
payload. It is not renderer-local state and it is not part of provider-specific
composer defaults.
The group owns:
- `enabled`: whether the hidden Lab settings tab is exposed after the developer
panel is visible.
- `features`: per-experiment toggles such as AgentGUI composer worktree launch
and workbench lab shortcuts.
- `shortcuts`: user-configured workbench shortcut bindings. Missing, empty, or
over-length bindings normalize to unbound/null before publishing.
Renderer settings may optimistically update Lab preferences, but the daemon
desktop preferences store and `preferences.desktop.updated` event remain the
authoritative durability and broadcast path.
## Runtime Validation
OpenAPI and generated files define the transport interface, but generated Go
decoding is not a complete schema validator. In particular, ordinary struct
decoding does not reject unknown JSON fields just because a schema says
`additionalProperties: false`.
When a request schema relies on constraints that generated code does not enforce,
choose one explicit validation strategy at the `api` seam:
- configure the generated decoder or runtime to enforce the constraint
- validate the raw request body against the canonical schema before mapping it to
generated types
- add a narrow hand-written transport validator in `api` that checks only the
missing generated-runtime guarantees
Do not push transport-shape validation into `service`. The service interface
should receive transport-agnostic input that has already crossed the HTTP
contract seam.
For `WorkbenchSnapshot*`, transport ownership still remains in `api`: HTTP
decoding, route-specific validation strategy, and status-code mapping stay at
the API seam even when host services consume the synchronized shared snapshot
contract through `packages/workbench/service`.
Add route tests for schema constraints that matter to daemon behavior and are
not obviously enforced by the generated runtime. Examples include unknown-field
rejection, object closure, array size limits, and numeric min/max constraints.
## Tooling Rules
- run `pnpm generate:api` when the OpenAPI contract changes
- run `pnpm check:api-generated` before finishing API contract changes; this
also runs `pnpm check:agent-protocol-enums`, which fails when the closed
agent turn/interaction enums in `tuttid.v1.yaml` drift from the
`activity.updated` event schema
(`packages/events/protocol/definitions/agent/activity.updated.event.json`)
- run `pnpm sync:workbench-openapi-schema` after changing the shared workbench
snapshot schema
- run `pnpm check:workbench-openapi-schema` when you only need to check that
the shared snapshot schema and OpenAPI component schemas are aligned
- run `pnpm sync:workbench-go-contract` after changing shared workbench snapshot
schema or limits that daemon validation depends on
- run `pnpm check:workbench-go-contract` when you only need to check that the
generated shared Go workbench snapshot contract is aligned with the shared
snapshot sources
- run `pnpm generate:event-protocol` after changing shared business event
protocol schemas or event-definition files
- run `pnpm check:event-protocol-generated` when you only need to check that
the generated TypeScript and Go event protocol outputs are aligned with the
shared event sources
- keep generator versions pinned in repository-managed dependencies or Go tool directives
## Scope Guidance
Generate:
- path and method contracts
- transport request and response models
- server interfaces and route glue
- shared TypeScript SDK surface
Do not generate:
- daemon business logic
- daemon persistence adapters
- Electron lifecycle code
- renderer workflows
- desktop IPC contracts that are not direct HTTP projections