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

49 KiB
Raw Permalink Blame History

Agent Extensions

Status: current implemented architecture

Agent Extensions let independently released ACP agents integrate with Tutti without adding provider-specific executable code to this repository. An extension is declarative data: a manifest, discovery/tool/capability/composer/authentication/account-usage profiles, locale resources, and static assets.

Trust And Distribution

Configured sources live in config/tutti.defaults.json. Each source pins an agent key, exact Extension version, HTTPS versions.json URL, default activation state, signing key ID, and Ed25519 public key. tuttid accepts only that client-pinned release when it remains active and compatible and its canonical release JSON signature, artifact SHA-256, byte size, manifest identity, and package contents all validate. Publishing a newer release to the remote index does not change an already shipped Tutti client; adopting it requires a Tutti release that updates the source's pinnedVersion.

During a versioned metadata-path rollout, a source may declare ordered fallback index URLs. Fallback is permitted only when a higher-priority index cannot be fetched, such as before the new CloudFront path has been published. Once an index is fetched, its JSON shape, agent identity, compatibility, active or withdrawn state, and selected release signature are authoritative and fail closed; a legacy index must never revive a release rejected by that authority. Remove rollout fallbacks after the new metadata path is published and the supporting Tutti version has propagated.

minTuttiVersion is evaluated only after the complete versions document has been decoded. It therefore cannot protect an older strict decoder from a new field inside an embedded manifest. A release that extends the manifest wire shape must publish versions.json under a new versioned metadata path, leave the previous index unchanged for older Tutti builds, and switch the configured source URL only in a Tutti release that understands the new shape. Immutable artifacts may remain under the Agent's existing release path because older clients cannot select them through their frozen metadata index and exact version pin.

Release ZIPs are data-only. Installation rejects path traversal, symlinks, executable regular files, unsupported file types, excessive entry counts, and excessive compressed or expanded sizes. Directory entries may carry the normal execute/search bits required to traverse them. The package may describe an exact standard npm, pnpm, or uv runtime installation, or pin raw official executable artifacts, but it never carries executable code itself. Binary artifact metadata remains inside the v2 manifest covered by the extension release's Ed25519 signature and signed package digest; there is no manifest v3 or reinterpretation of existing package-manager manifests.

Each concrete Agent repository owns its reproducible archive, release signing, versions generation, verification, and S3/CloudFront workflow. Tutti consumes signed immutable releases but does not build or upload third-party Agent artifacts. The provider-independent setup and release procedure lives in the tutti-os/tutti-agent-extension-skill repository.

Local development has one explicit exception modeled after the App Center catalog override. In development only, TUTTI_AGENT_EXTENSION_<KEY>_PACKAGE_DIR may select an unpacked package for a configured source. The daemon applies the same data-only file, size, manifest, profile, asset, and runtime-contract validation, copies the package into its owned state, stamps a content-addressed +local.<digest> snapshot version, and registers the normal fixed Agent Target. It never runs from the mutable source directory. Every daemon start with an explicit local override synchronously snapshots and validates the currently configured directory before serving the Agent Target. A cached +local snapshot is never an offline fallback for a missing or invalid development directory; reconciliation removes the stale Target while preserving its enabled preference when a valid new snapshot is registered. Removing the override requires the signed remote source again. Production ignores this override and continues to require the signed HTTPS release path.

Development clients may use synthetic or zero application versions while the desktop is running from source. Remote extension selection still requires the configured active pin, signed release, and declared host capabilities; the release's minTuttiVersion gate is enforced only for packaged production clients, whose version is a release compatibility claim.

Installation And Runtime Ownership

Verified installations are immutable and stored under:

<state>/agent/extensions/<agentKey>/<version>/
<state>/agent/extensions/<agentKey>/active.json

Development package snapshots use the same layout and immutable installation contract. Their synthetic version separates local bytes from a published version with the same source manifest version; changing any package file creates a new fixed snapshot on the next daemon start.

Installation persistence follows daemon layering. biz/agentextension owns the installation record contract; service/agentextension owns release verification, package promotion, activation, and reconciliation; the narrow data/agentextension installation adapter alone derives agent/extensions paths and reads or atomically replaces installation.json and active.json. Service code must not reconstruct the daemon state root. Managed-first Runtime selection is derived from the running client's exact source pin and is not an independent durable installation preference. Readers accept and discard the legacy preferManagedRuntime field so strict decoding remains compatible with records written during the initial rollout.

The active record registers a system Agent Target with an agent_extension launch reference fixed to <agentKey>@<version>. The Target carries the package icon, optional mask icon, and optional home hero image as data URLs, so renderer code does not add presentation assets or provider branches for every extension. Package icon is the colored identity reused by the provider rail, conversation identity, Message Center, and mentions; optional package maskIcon is the mask-safe glyph for conversation rows. All assets originate in the verified package and remain pinned to the active installation version.

At launch the runtime controller asks AgentRuntimeResolver for unknown providers. The resolver verifies the fixed installation reference, evaluates the declarative discovery profile, and creates the generic standard ACP adapter. Client-pinned remote installations prefer their matching Tutti-managed runtime, with a compatible local runtime as a temporary fallback while automatic convergence is pending. Local development snapshots retain local-first discovery. A candidate may add signed searchPaths entries with scope: "user"; every path must be a bounded relative path below the current user's home directory. Those paths are prepended to the shared runtime-command environment, so the extension can describe an official vendor install location without adding provider-specific filesystem code to tuttid. It never loads JavaScript, React, Go plugins, or native modules from the extension.

Composer skill roots remain declarative runtime inputs. Workspace roots must be safe relative paths at both installation validation and service consumption boundaries; absolute and parent-traversing paths are ignored before runtime preparation or discovery. Tutti materializes only its managed skills into those roots and replaces the same managed directories on repeated preparation, so a persistent workspace cannot accumulate suffixed copies. User-owned colliding directories are never replaced.

The generic adapter applies declarative tool aliases before canonical activity normalization and maps composer permission semantics onto runtime permission IDs. Standard ACP content diffs continue through the shared ACP diff normalizer, so Gemini and future extensions do not add provider branches to AgentGUI. Both standard ACP models state and legacy configOptions are normalized into the shared composer model descriptor; the catalog remains runtime-reported instead of being hardcoded for an extension provider. A prompt-free composer discovery session runs in the normalized selected project scope. When no project is selected, it uses the daemon-owned discovery directory under <state>/agent/discovery/<provider>, because standard ACP session creation requires a concrete working directory.

Provider-owned account usage

An Extension may declare the optional accountUsage profile with schema tutti.agent.account-usage-probe.v1. The profile pins one exact npm or pnpm companion package, node-script entry below ${installRoot}, fixed argv, and a bounded timeout. The Extension ZIP remains data-only: the companion is a separately published Provider-owned artifact with its own runtime identity, install root, activation, and verification lifecycle. It is never part of the primary ACP install command, runtime identity, activation, resolution, or adoption. A companion download, install, verification, or activation failure therefore leaves the Agent ready and only makes the account-usage endpoint return runtime_unavailable; a status read never downloads code. The independent reconciler persists a diagnostic-light failure record and its next bounded retry time, so daemon restart preserves backoff. Recovery deletes the record.

runtime.install may optionally give the companion its own installer when the Agent runtime itself is not managed by npm or pnpm. The installer is a closed declaration: runner is exactly npm or pnpm; args contains 1–8 bounded, shell-free argv elements, names the exact scoped package declared by runtime.package, and may use only the complete ${installRoot} and ${platform} placeholders. Without this declaration, the companion inherits the Agent runtime installer and that installer must already be npm-compatible. The independent installer changes only companion preparation; it does not change the Agent runtime's install, activation, command, or provider identity.

Before every probe, tuttid verifies the fixed host Node interpreter and the ordinary in-root CommonJS script independently. The script is supplied to Node as the already verified bytes instead of executing an npm .cmd/shell shim or reopening the mutable script pathname. This is the same contract on Windows, macOS, and Linux. Fingerprinting and snapshot construction observe the probe context. Windows keeps one content-addressed, verified node.exe snapshot in daemon-private state and holds it against writes while probes reuse it; after a daemon restart the snapshot is verified once instead of copying the source interpreter again.

Local Extension development may replace that managed companion with one explicit executable through TUTTI_AGENT_EXTENSION_<KEY>_ACCOUNT_USAGE_EXECUTABLE. This is accepted only for a development-mode installation with local-package provenance; the path must be an absolute, ordinary, non-symlinked, fingerprint-stable JavaScript file. The Node interpreter is still resolved and verified separately. Production ignores the override and continues to require the exact companion package pinned by the signed profile.

Every managed probe receives TUTTI_AGENT_RUNTIME_INSTALL_ROOT with the verified install root of the corresponding Agent runtime. This is a narrow Provider-facing bridge for companions that need to delegate usage lookup to their Agent package; it is not a general credential or endpoint channel. The value follows the host's native path syntax, so companions must consume the environment value directly rather than constructing a user-home path or assuming POSIX separators.

The companion owns all Provider-private behavior, including config and credential lookup, OAuth issuer-to-usage-origin binding, endpoint paths, refresh timing, and response conversion. It prints only one bounded versioned JSON result to stdout; stderr is discarded at the process boundary. tuttid strictly decodes a closed available | unsupported | error result, attaches the exact Agent Target and provider identity, and exposes only stable error codes and normalized quota fields. Unknown fields, schemas, enums, malformed numbers, empty subscription success, and trailing output fail closed. Desktop and AgentGUI never select this capability by provider name and never receive tokens, configured endpoints, paths, raw response bodies, or Provider error text.

The normalized account-usage v2 snapshot separates billing identity from quota completeness. billingMode is api, subscription, coding_plan, or provider_account; quotaState is complete, unavailable, or not_applicable. Only complete may carry quota rows. api is explicitly not_applicable; a recognized plan or Provider account whose complete balance cannot be proven remains unavailable without an authoritative empty or partial balance. Exact balances use Provider-neutral amount/unit fields such as credits, while tokens, account IDs, and raw package records remain inside the companion. Tutti accepts the original v1 helper result for existing Extensions, normalizes it to v2 at the daemon boundary, and requires new companions to emit v2 whenever they need these completeness or exact-amount semantics.

Spawn Settings And ACP Workflow Modes

Composer profile v1 has an optional, closed launchSettings.permission declaration for agents whose permission tier is fixed when the process starts. It accepts only the ${permissionMode} placeholder as one complete discovery launchArgs element, requires exactly one occurrence, and requires exactly one unique runtime value for each Tutti semantic: ask-before-write, auto, and full-access. The default semantic is fixed to ask-before-write. Empty values, unknown or combined placeholders, unknown semantics, duplicate or ambiguous values, and shell syntax fail installation validation. Expansion is an argv-element replacement; it never invokes a shell or weakens the existing runtime command validation.

Spawn-time permission is not an ACP workflow mode. The selected permission is captured by conversation creation and changing it requires a new session. The adapter does not send session/set_mode for that permission. An extension may separately declare workflowModes.plan.enabledRuntimeId and disabledRuntimeId; the shared Plan switch and /plan then send those two validated values through standard session/set_mode. An agent whose Plan mode exists only as a launch-time permission may additionally declare updateStrategy: "restart-with-launch-permission". For that strategy the adapter replaces only the ACP process, loads the same provider session, uses the enabled runtime ID while Plan is active, and restores the conversation's fixed permission runtime value when Plan is disabled. A failed restart leaves the previous live process and canonical settings unchanged. Profiles without these optional declarations retain their existing ACP session-mode/config-option behavior.

Composer profile v1 may also opt in to setModel.reasoningEffortMeta. For that profile, the standard ACP SessionModelState.models.availableModels entries are the only model and reasoning catalog. Tutti preserves supportsReasoningEffort, reasoningEffort, reasoningEfforts (including runtime labels and descriptions), and supportsImageInput, whether supplied as supported model fields or in model _meta. The daemon projects those facts through typed composer options and reasoningOptionsByModel; AgentGUI therefore changes the reasoning selector when the selected model changes. Unsupported models carry an authoritative empty profile. A model or reasoning update uses standard session/set_model; the validated current effort is added as _meta.reasoningEffort only for a model that advertises reasoning support. The established standard ACP config-option reasoning path remains unchanged for profiles that do not opt in.

Slash-command narrowing remains declarative. A future extension can mark its signed command catalog authoritative and allow only shared commands such as compact, status, and plan; runtime-private commands are then removed before composer projection. Image blocks, tool calls, permission requests, cancel, resume/load, skills roots, and the client-provided standard ACP MCP array continue through the generic adapter. The current Tutti session-create contract supplies an empty MCP array and does not manage an agent's private MCP configuration; adding a non-empty product-level MCP input is a separate Host contract change.

Standard ACP process recycling is capability-driven rather than extension-name driven. When the live initialize response advertises session/load or session/resume, the shared 30-minute idle reaper may close only that ACP transport and CLI process while retaining the Tutti session and provider session id. The next execution launches a new process and restores the provider session. The reaper does not send session/close, skips providers without a proven restore method, and treats pending interactive requests as busy.

Extension composer controls stay runtime-owned after the model list is discovered. tuttid selects the newest context only within the exact workspace, normalized project, Agent Target, fixed installation, and request-settings scope. It may use an exact live or pinned persisted context, or a single-flight hidden discovery result, to project only the model, permission, and reasoning fields identified by the signed composer configOptions.acpOptionId references, plus availableCommands into the slash-command catalog. The same signed option IDs drive standard ACP startup and live setting writes; legacy top-level model and permission source declarations map to model, mode, and the established reasoning_effort alias. Persisted runtime context is an internal recovery input only: the public composer response publishes commands and per-model reasoning profiles through typed commands and reasoningOptionsByModel fields. Those typed fields are authoritative for desktop and AgentGUI projections; runtimeContext remains opaque legacy/diagnostic data and is not an expansion seam for composer capabilities. Legacy persisted contexts without the fixed installation and profile identity are not eligible for reuse. Hidden extension discovery is prompt-free and is closed immediately after success, start/terminal failure, cancellation, or timeout. When discovery completes after the caller's bounded wait has returned, tuttid publishes the existing model-catalog refresh signal; active composer consumers then reload the newly cached catalog instead of requiring a new conversation. The selected model is not part of the discovery cache identity because the hidden discovery session does not receive a model; acknowledging its advertised default must continue to address the same catalog. The standard ACP adapter canonicalizes provider-native reasoning option ids such as thought_level or effort to Tutti's reasoning_effort before they reach service or GUI projections, while retaining the original runtime id for ACP writes. Unknown provider-native options remain intact in the opaque runtime context; this does not imply a generic AgentGUI control for every unknown option.

Model consumption metadata follows the same adapter-first rule without making provider prose globally meaningful. A verified Extension may opt its model config reference into the closed descriptionMetadataFormat: "credit-consumption-multiplier-v1" declaration. Only then does the standard ACP adapter remove a standalone xN credits description segment and project its numeric token as the typed consumptionMultiplier model field. Unknown formats fail package validation and adapter construction; profiles without the declaration preserve the full description. The daemon API and activity adapter preserve the typed field, and AgentGUI renders it without parsing provider prose or branching on a provider name.

Signed composer profiles may narrow the provider-advertised slash-command catalog and attach shared command effects such as submit-immediate, show-status, activate-goal-mode, and toggle-plan-mode. tuttid applies that declarative policy before returning composer options, so extension commands can reuse the shared AgentGUI slash-command behavior without a provider-name branch. Signed profiles may also set skills.runtimeCommandProjection: "unlisted-as-skills" alongside an authoritative slash-command catalog. In that mode, runtime-advertised entries outside the signed core-command list are projected through the typed skills field with their exact slash trigger and runtime description. AgentGUI then renders separate command, capability, and skill groups without a provider-name branch; known Tutti-injected routing skills remain hidden from the composer picker. Signed capability profiles may declare canonical GUI capabilities such as compact and planMode. A declaration becomes effective only when current ACP runtime facts and host support also establish it. The closed, signed workflowModes.plan enabled/disabled ID pair is itself sufficient runtime contract evidence for planMode, including agents that implement session/set_mode without advertising a mode catalog from session/new. For the launch-restart strategy, the same evidence is combined with the closed spawn-permission declaration; the Plan runtime value must be distinct from all three permission values. Duplicates are removed, and unknown extension-local capability keys remain package metadata rather than entering the Agent Activity capability contract.

Extension-owned provider identities remain open metadata after an Agent Target authorizes the launch. The shared provider registry validates their canonical shape before activity events are created, so turn lifecycle and message events retain identities such as acp:example; runtime authority still comes only from the fixed agent_extension Target reference.

The standard ACP adapter stamps each turn transition with a sequenced, adapter-origin lifecycle snapshot. Reporters and GUI consumers copy that provider-independent snapshot, so completed, failed, and canceled extension turns clear their active turn reference without requiring the extension provider to be added to the built-in event projection catalog.

The current runtime adapter registry is still keyed by open provider ID for the daemon lifetime. A cached generic adapter now fails closed when the requested Target or fixed installation differs, while composer-context reuse uses the full scope above. Sessions persist agentTargetId and resume re-derives the extension installation from that Target. A composite session-pinned runtime/profile fingerprint remains required before any future automatic in-session migration. The daemon does not activate a newly published remote Extension dynamically: source activation is controlled by the Tutti client's exact version pins, and existing sessions stay pinned to their recorded Target installation.

Target-managed Runtime Setup

The daemon exposes Target-scoped setup resources at GET /v1/workspaces/{workspaceID}/agent-targets/{agentTargetID}/setup and POST /v1/workspaces/{workspaceID}/agent-targets/{agentTargetID}/setup/install or /setup/authenticate. Setup never requires a selected project. Install submission supplies only the daemon-issued plan digest and a client action ID. tuttid resolves the workspace, enabled Agent Target, fixed extension installation, and verified package manifest before deriving the runner argv, exact package identity, install root, executable, launch arguments, and SHA-256 plan digest. Renderer input cannot replace any execution field.

After a client-pinned remote Extension is activated, a daemon-owned background reconciler automatically installs the exact Runtime declared by that signed Extension into Tutti's private runtime root. It runs once at startup, wakes after source refresh or activation, and tracks retry state independently for each Target. Transient installation failures use exponential backoff capped at 30 minutes without rechecking settled Targets. Permanent contract, platform, or user-command ownership conflicts wait for a source or preference wake, or an explicit setup action, instead of retrying on a timer. It never writes to or replaces a user-owned PATH executable, and local development snapshots are excluded. The Target-scoped setup endpoints remain the observable repair and authentication surface; automatic convergence reuses the same verified install pipeline as an explicit setup action.

Runtime roots use ~/.local/share/tutti/agent-runtimes/<agentKey>/<runtimeIdentity>, where runtimeIdentity is derived from the runtime contract: Agent key, runtime kind, platform, install runner and argv, exact package identity, launch executable and args, and discovery profile. Extension metadata changes such as localized copy or presentation assets do not force a reinstall when this runtime contract is unchanged. This stable user-local root is shared by development and production; daemon state keeps only setup action metadata. ${platform} resolves to Go's <GOOS>-<GOARCH> pair. Plan digests still bind the Target and fixed extension installation in addition to the runtime identity, platform, and complete resolved command, so one managed runtime is reused across workspaces while setup actions remain tied to the exact Target installation that the user confirmed. Every install action recomputes the plan and compares its digest before creating files or processes.

The signed release is authoritative for runtime commands. Tutti persists both the complete signed release record (preserving its signed JSON shape) and the exact signed ZIP beside the extracted package. Every remote-package load reverifies the Ed25519 signature against the configured source key, checks the ZIP's signed SHA-256 and size, derives a canonical content identity directly from those signed ZIP bytes, and compares that identity and complete manifest with the active package. The copied manifest and digest in installation.json are metadata and cannot redefine authority. A same-version package directory whose content or authority differs is rejected during load and atomically replaced only after a newly downloaded signed artifact has been verified. Local development snapshots remain explicitly content-addressed and are not interpreted as signed releases.

Remote manifest-v2 npm, pnpm, and uv installations created before persisted signed-package authority remain readable offline through a closed legacy path. That path requires an installation-v1 record with no release digest/size, no persisted signed-release files, no binary artifacts, and no user-command publication override. Tutti validates the complete package and manifest twice around a canonical content fingerprint, then atomically records that fingerprint in the legacy installation record. Later loads require the exact migrated snapshot. Partial signed-authority state never falls back to legacy validation, and legacy packages are redownloaded into signed authority when their source is reachable rather than being reinterpreted as a signed installation.

Setup probes a compatible executable from signed user-relative search paths and the shared daemon runtime PATH. Local development snapshots remain local-first. For a client-pinned remote installation, an already verified managed Runtime wins; a compatible local Runtime is used only until automatic managed-runtime convergence succeeds. The desktop daemon does not source interactive shell startup files such as .zshrc; vendor install locations outside its process PATH belong in extension metadata, not provider-specific core code. Otherwise, the installer runs manifest-owned argv directly in a private staging root beside the fixed user-local installation; it does not invoke a shell or mutate any project package manifest, lockfile, node_modules, or global package state. Environment inheritance is allowlisted. Runner CWD and package-manager cache/config live in a Tutti-managed scratch directory under that same user-local runtime root. Tutti rejects symlinks in every existing ancestor of the configured managed root. Staging, scratch, replacement, rollback, and final activation use no-follow directory handles and relative directory operations; the held handles are compared with their path identities before each boundary on Unix. Activation metadata is written atomically relative to the held staging descriptor, so replacing the staging pathname or placing a link at the metadata name cannot redirect a write. Windows retains the established package-manager staging and activation path for npm, pnpm, and uv runtimes; raw binary runtimes remain fail-closed there until an equivalent no-follow activation implementation is available.

For package-manager runtimes, tuttid resolves a regular executable inside staging. A v2 manifest may instead set runtime.install.runner to binary and provide a bounded artifacts catalog. Every entry is a raw executable pinned by exact <GOOS>-<GOARCH> platform, semantic version, HTTPS URL, lowercase SHA-256, byte size, and an official-release HTTPS provenance URL. Unsupported platforms have no fallback and fail before download. The selected artifact is streamed directly into the private staging root with exclusive creation and a hard byte limit; Tutti verifies response transport, size, digest, executable mode, and native Mach-O/ELF/PE platform identity. It never invokes an upstream installer, shell, archive extractor, or PATH/rc mutation for this runtime kind.

The uv runner does not follow the stage-then-rename model: uv tool environments embed absolute paths (bin symlinks, venv shebangs, pyvenv.cfg) and cannot survive the activation rename. uv runtimes install in place. Tutti first resolves a Tutti-managed uv toolchain — a pinned uv version declared in config/tutti.defaults.json (agentRuntimeTools.uv) with per-platform archives, SHA-256, and byte sizes — downloaded with the same streaming verification as binary artifacts, extracted as a single pinned member, and cached under ~/.local/share/tutti/agent-runtimes/_tools/uv/<platform>/<version>. The user machine needs no preinstalled uv or Python. The install invokes the managed uv executable by absolute path, while also prepending its directory to PATH for uv subprocesses. Confinement variables point into the final root: UV_TOOL_DIR=<installRoot>/tools, UV_TOOL_BIN_DIR=<installRoot>/bin, UV_PYTHON_INSTALL_DIR=<installRoot>/python, a shared content-addressed UV_CACHE_DIR under _tools/uv/cache, and UV_NO_CONFIG=1. Tutti also sets UV_PYTHON=3.12 and UV_MANAGED_PYTHON=1, so package resolution and execution use a Tutti-owned Python 3.12 instead of whichever system Python happens to appear on the daemon PATH. A previously committed root (valid activation.json) is moved to <runtimeIdentity>.previous before installing and restored on any failure; an uncommitted partial root is discarded, and a missing root with a self-consistent backup (matching activation identity plus executable fingerprint) is restored instead of reinstalling. activation.json remains the commit marker, and the fingerprint/version/ACP-probe verification chain is unchanged.

For both install kinds, Tutti fingerprints the ordinary in-root executable, runs the discovery profile's version check, then performs ACP initialize and session/new. A binary runtime must report the exact artifact version, not merely satisfy the local-discovery compatibility range. Binary version probes start from the same verified descriptor on Linux or daemon-private immutable snapshot on macOS as the final ACP process; the replaceable managed pathname is never executed after an earlier fingerprint check. Fingerprints are rechecked after ACP process boundaries and again after the atomic staging rename. Authentication failures produce auth_required; protocol or runtime failures fail the action. Successful and auth-required runtimes receive an activation.json record. Symlinked package-manager bin shims are resolved to an ordinary in-root file before activation and launch.

Binary replacement uses <runtimeIdentity>.previous as its only durable intermediate state. Before activation removes any existing backup, and before resolution trusts an active root, Tutti resolves an interrupted replacement. If the active root matches the current installation identity and exact signed artifact, it remains authoritative and the backup may be removed. If the active root is absent or invalid, the backup is promoted only after its activation identity, executable path, SHA-256, size, executable mode, and native platform all match the current signed artifact; the held directory is checked again after the rename. An invalid backup is never adopted and, when no verified active root exists, is preserved for diagnosis. Thus interruption after either replacement rename converges on the verified old or verified new runtime without silently discarding the active runtime.

By default, successful activation also publishes the manifest launch executable's basename at ~/.local/bin/<agent-command>. The user entry points to ~/.local/share/tutti/agent-runtimes/<agentKey>/bin/<agent-command>; that stable per-Agent entry points to the executable in the fixed runtime root and is atomically repointed on upgrade. Unix uses Tutti-owned symlinks for both hops. Windows uses Tutti-owned .cmd launchers because ordinary desktop users do not have file-symlink privilege; the launcher preserves arguments and the child exit code. A pre-existing regular file, foreign symlink, or foreign Windows launcher at either entry is never overwritten. Feature disablement and daemon shutdown do not remove a published command, so it remains usable outside Tutti while the managed runtime files remain installed.

The optional v2 runtime.launch.publishUserCommand field may be false for a runtime that should be private to AgentGUI. Absence preserves publication for every existing manifest. An opted-out runtime launches the verified fixed executable inside installRoot and neither creates nor verifies the two publication entries. A pre-existing regular file, foreign symlink, or foreign Windows launcher at the user command path is therefore ignored and cannot prevent managed activation; Tutti still never overwrites, removes, or repoints that entry.

Declarative Launch Environment

The optional v2 runtime.launch.env field is a bounded map from the target process environment key to a host-owned reference. Values must use the exact form ${env:TUTTI_*}; Tutti resolves the referenced TUTTI_* variable at launch time and omits the entry when the host variable is unset. Manifests cannot read credentials or arbitrary environment variables, and they cannot embed literal paths or secrets in the package. Provider-specific mappings belong in the extension manifest, while the daemon only implements this generic, allowlisted resolution. For example, a Kimi extension can declare KIMI_SHELL_PATH: "${env:TUTTI_MANAGED_POSIX_SHELL}" without adding a Kimi branch to tuttid.

Discovery skips this two-link managed entry when probing PATH, then resolves it through the managed activation record. It therefore remains source=managed and retains fingerprint verification instead of being mistaken for an independent local installation. Both links are activation integrity: a missing, foreign, broken, or unexpectedly repointed entry produces runtime_integrity_failed and an explicit reinstall plan. Existing extension-version roots may be adopted into the runtime-identity root only when their Tutti activation record, package identity, executable fingerprint, and current discovery version check all match. Binary runtimes additionally require the executable SHA-256 and byte size to match the artifact in the current signed manifest before adoption; otherwise the candidate is ignored and the user receives an explicit reinstall plan. Candidate enumeration, activation-record reads and replacement, rename, rollback, and recursive cleanup operate relative to held no-follow directory descriptors on Unix. Binary executable identity is re-established inside the descriptor/snapshot version launcher before any candidate can execute, and is checked again around activation-record update and adoption rename.

Normal resolution also opens the active runtime-identity root relative to the held workspace descriptor on every attempt, even when no recovery backup is present. Activation metadata and executable bytes are read relative to that no-follow root handle and the handle's path identity is checked around the version probe. A file or symlink occupying the active-root name is rejected as an integrity failure; resolution never follows it or adopts its target.

The managed-runtime activation record persists the resolved executable's runtime identity, package identity, SHA-256, and byte size. Every managed-runtime resolution recomputes both fingerprint fields before the version or ACP probe, and recomputes them again after the version probe. Binary runtimes also compare the bytes with the current signed artifact SHA-256 and size and revalidate the native platform on every resolution. A replacement, an artifact from superseded signed metadata, or even a replacement reporting the same compatible version is rejected with runtime_integrity_failed and returns the exact reinstall plan.

For a managed binary, that expected SHA-256 and size are also carried through the generic Standard ACP adapter into the final process specification. Linux starts the verified open descriptor directly. macOS copies that descriptor to a freshly created daemon-private snapshot, verifies it, marks both the file and directory immutable and non-writable, starts it, and removes the snapshot immediately after process start. The later process boundary therefore never resolves the replaceable managed-runtime pathname after trusting an earlier fingerprint.

An auth-required snapshot includes normalized methods from the fresh ACP initialize response. Authentication submission accepts only the advertised method ID and client action ID. The daemon initializes a new process, revalidates the method, calls ACP authenticate on that process, then requires session/new to succeed. Only that result produces ready; methods being advertised is never itself an auth verdict. Authentication actions are durable and never persist credentials.

A signed authentication profile may bind one runtime-advertised method ID to a closed terminal command declaration. Authentication profile v1 supports type: "terminal" with either command.strategy: "runtime-subcommand" and a bounded, non-empty argv array, or command.strategy: "runtime-slash-command" with exactly one safe slash-command name and a bounded literal readyText marker. For a slash command, the daemon exposes the quoted runtime launch plus one atomic typed startup action; it never exposes raw terminal input. The Desktop terminal adapter validates the action, generates the leading /, and submits it only after the matching terminal session emits the declared marker. Startup failure terminates setup readiness monitoring instead of leaving a background poll running. The extension cannot declare raw terminal input, control characters, or shell source.

A declaration may also replace the advertised method's display name and description with bounded presentation strings. Tutti applies those fields only when the declaration ID and type match the fresh ACP method, so provider-specific setup wording and commands remain in the extension package instead of daemon code. For runtime subcommands, the daemon quotes every projected argument before exposing the ready-to-run launch command.

Without a signed declaration, Tutti retains compatibility with provider metadata: it reads top-level ACP type/args fields first and falls back to _meta["terminal-auth"]. Methods of type terminal require an interactive terminal that can never complete inside the headless setup process, so the daemon rejects ACP authenticate for them immediately and lets the host surface or launch the resolved command. The profile changes terminal presentation and launch policy only; the fresh ACP initialize response remains authoritative for which authentication methods are actually available.

An ACP authenticate result may expose non-secret account identity through a namespaced _meta entry ending in /userinfo. Setup normalizes only the user ID, display name, organization, and selected auth method; it discards all other metadata. A successful authentication action persists that identity with its private action record so later ready snapshots can show the signed-in account without repeating authentication or storing credentials.

ACP runtimes may still accept authenticate and session/new before a real request touches provider credentials. The Agent runtime therefore feeds a failed formal-session authentication outcome back into the shared provider auth invalidation store. A later Target setup probe overrides an otherwise ready ACP probe with auth_required until an explicit re-authentication succeeds or a formal request completes successfully. This is outcome feedback, not a synthetic prompt probe during setup.

Install actions are idempotent by Target/fixed-installation scope and client action ID. Their phase and status are persisted under daemon state. A queued/running record not owned by the current daemon process is recovered as interrupted, allowing an explicit retry. Setup probes use a daemon-owned discovery CWD. Formal sessions pass their real CWD only to ACP and launch-argument expansion; runtime storage remains project-neutral.

Setup action ownership follows the daemon layers. biz/agentextension defines the action and scope values; service/agentextension owns transitions and depends on SetupActionStore plus SetupDiscoveryDirectory; the data/agentextension file adapters alone derive paths and perform filesystem I/O. Action JSON is scope-validated, decoded strictly, and replaced atomically with private directory/file permissions. Each setup operation resolves its discovery root once through that directory adapter, then passes the resolved root through runtime resolution, installation verification, probing, and authentication instead of recomputing or creating it in those workflows.

After a request accepts an install or authentication action, work no longer depends on that request context. It runs under the setup service's daemon-owned worker context. Daemon shutdown closes setup before the runtime and state store, refuses new actions, cancels and waits for accepted workers, then persists cancellation as interrupted. Persistence failures are returned by setup close; they are not discarded as background goroutine errors.

AgentGUI recognizes target-runtime setup metadata on the exact Target. Its shared panel owns explicit confirmation and plan presentation, progress polling, runtime-advertised auth method selection, login progress, and retry. A failed or interrupted install keeps its durable error visible while the retained plan can start a new action with a new client action ID; refreshing the old action is not an installation retry. Background action polling preserves the established detection result instead of restarting the detection presentation on every snapshot request. Failed explicit authentication keeps the provider's ACP error on the durable action and presents it beside the localized failure summary while leaving the auth method available for an explicit retry. Target selection never opens the dialog. Initial detection remains non-modal and blocks the empty-home composer with a checking gate; a non-ready snapshot shows an inline setup affordance, and only an explicit click opens the dialog. The selected Target's config menu also exposes this same dialog after setup is ready, including auth-method selection and explicit re-authentication. The empty-home gate and config-menu host reuse one Target-scoped watch rather than starting duplicate polls. Closing remains controlled, and the Dialog stays mounted through ready transitions so its pointer/scroll lock can clean up. Active conversations are never replaced by setup UI.

Activation And Failure Behavior

Source defaults come from generated configuration. A source with enabled: true is a stable integration and is always reconciled; historical agent.extension.<key> preference values no longer override it. Sources with enabled: false remain Early Access integrations that Desktop settings can activate through the generic agent.extension.<key> feature flag. A preference write reconciles only when one of those opt-in sources changes effective activation, then the desktop refreshes its Agent Target catalog. The daemon keeps this source-key driven rather than branching on individual providers.

Disabled Early Access sources do not perform network requests and their system Target is removed. When an active source cannot reach its index, a previously verified active installation remains available. If no verified installation exists, the source is not registered and tuttid logs one agent_extension.reconcile_failed record with a JSON payload.

Daemon startup restores and verifies every active source's local active installation before serving Agent Target reads. A remote installation is usable only when its version equals the current client's exact source pin; an active record from another client pin forces source reconciliation and fails closed if that pinned release cannot be obtained. When every active source has a usable local installation, release-index refresh runs in the background and does not delay the daemon listener. If any enabled source has no usable local installation, startup keeps the synchronous reconcile path so that a first installation is registered before the daemon serves its initial Target catalog. Preference-driven activation changes also remain synchronous. After activation, managed Runtime convergence continues in the background and does not delay the daemon listener.

Agent Target catalog reads continue to verify the installed extension package and managed runtime integrity before reporting availability. Successful runtime version probes are reused only while the resolved executable fingerprint, version arguments, and constraint are unchanged. Concurrent reads share the same in-flight probe. Failed probes are not cached, so repairing or installing a runtime is visible on the next read. Runtime launch and setup keep their authoritative integrity checks and do not trust catalog availability as launch authorization.

Composite session-pinned adapter cache keys, richer tool/event profiles, and removal of remaining built-in catalogs remain migration work. Composer discovery is not setup state and does not infer installation or authentication readiness. Client-pinned remote Extensions use an automatically managed Runtime with compatible local fallback while it is being installed; local development Extensions remain local-first. tuttid never installs into a user project.

Declarative Grok Compatibility

The generated source registry contains a default-off grok entry using key ID tutti-grok-release-v1 and the approved Ed25519 public key. Enabling remains a generic agent.extension.grok feature-flag decision; no Grok provider or renderer branch exists.

The separate Grok extension is expected to declare official CLI 0.2.103 for darwin-arm64 as a binary artifact with URL https://x.ai/cli/grok-0.2.103-macos-aarch64, size 121600480, SHA-256 1be9de92f31566f2d38992125f902220b022f4f1e3fb7330532a0513d1d6f0f2, and official provenance https://x.ai/cli/install.sh. Its launch executable is ${installRoot}/grok, publishUserCommand is false, and managed launch args are exactly --no-auto-update --permission-mode ${permissionMode} agent stdio. They remain extension-owned declarative data. Only Apple Silicon is initially listed; the generic catalog can add independently pinned platform entries in a later signed extension release.

For Grok CLI 0.2.103, the spawn-time permission values are default for ask-before-write, auto for automatic operation, and bypassPermissions for full access. These are CLI permission values, not ACP workflow mode IDs; Plan continues to use the independent plan / default session/set_mode pair.

Opt-in Real Grok ACP Smoke

The generic adapter has an explicit local smoke for an already installed and authenticated official Grok CLI:

TUTTI_REAL_GROK_ACP_SMOKE=1 \
TUTTI_GROK_BIN=/absolute/path/to/grok \
go test ./packages/agent/daemon/runtime -run TestRealGrokACPInitializeSmoke -count=1

It is skipped by default, reads no user authentication state in CI, creates no ACP session, and sends no prompt. When explicitly enabled it verifies the executable/version, performs standard ACP initialize, checks that the initialize model state has an available and current model, then closes the process. The fake ACP tests separately verify the exact declared spawn argv and all permission tiers; keeping those checks separate lets the smoke remain non-billable even when a CLI release makes session/new contact the service.

This support does not add a Grok provider, icon, executable wrapper, credential persistence, private authentication protocol, headless/TSH integration, automatic CLI updates, or private x.ai/* operations. End-to-end AgentGUI visibility still requires separately reviewed and signed external extension bytes; the default-off source alone does not make a release available.